1 |
|
|
/* |
2 |
|
|
* The ManaPlus Client |
3 |
|
|
* Copyright (C) 2006-2009 The Mana World Development Team |
4 |
|
|
* Copyright (C) 2009-2010 The Mana Developers |
5 |
|
|
* Copyright (C) 2011-2019 The ManaPlus Developers |
6 |
|
|
* Copyright (C) 2019-2021 Andrei Karas |
7 |
|
|
* |
8 |
|
|
* This file is part of The ManaPlus Client. |
9 |
|
|
* |
10 |
|
|
* This program is free software; you can redistribute it and/or modify |
11 |
|
|
* it under the terms of the GNU General Public License as published by |
12 |
|
|
* the Free Software Foundation; either version 2 of the License, or |
13 |
|
|
* any later version. |
14 |
|
|
* |
15 |
|
|
* This program is distributed in the hope that it will be useful, |
16 |
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
17 |
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
18 |
|
|
* GNU General Public License for more details. |
19 |
|
|
* |
20 |
|
|
* You should have received a copy of the GNU General Public License |
21 |
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>. |
22 |
|
|
*/ |
23 |
|
|
|
24 |
|
|
#include "configuration.h" |
25 |
|
|
|
26 |
|
|
#include "gui/viewport.h" |
27 |
|
|
|
28 |
|
|
#include "particle/animationparticle.h" |
29 |
|
|
#include "particle/particleemitter.h" |
30 |
|
|
#include "particle/rotationalparticle.h" |
31 |
|
|
#include "particle/textparticle.h" |
32 |
|
|
|
33 |
|
|
#include "resources/dye/dye.h" |
34 |
|
|
|
35 |
|
|
#include "resources/loaders/imageloader.h" |
36 |
|
|
#include "resources/loaders/xmlloader.h" |
37 |
|
|
|
38 |
|
|
#include "utils/dtor.h" |
39 |
|
|
|
40 |
|
|
#include "debug.h" |
41 |
|
|
|
42 |
|
|
ParticleEngine *particleEngine = nullptr; |
43 |
|
|
|
44 |
|
|
class Image; |
45 |
|
|
|
46 |
|
|
int ParticleEngine::particleCount = 0; |
47 |
|
|
int ParticleEngine::maxCount = 0; |
48 |
|
|
ParticlePhysicsT ParticleEngine::fastPhysics = ParticlePhysics::Best; |
49 |
|
|
int ParticleEngine::emitterSkip = 1; |
50 |
|
|
bool ParticleEngine::enabled = true; |
51 |
|
|
const float ParticleEngine::PARTICLE_SKY = 800.0F; |
52 |
|
|
|
53 |
|
|
ParticleEngine::ParticleEngine() : |
54 |
|
|
mChildParticles(), |
55 |
|
|
mChildMoveParticles(), |
56 |
|
|
mMap(nullptr) |
57 |
|
|
{ |
58 |
|
|
ParticleEngine::particleCount++; |
59 |
|
|
} |
60 |
|
|
|
61 |
|
|
ParticleEngine::~ParticleEngine() |
62 |
|
|
{ |
63 |
|
|
// Delete child emitters and child particles |
64 |
|
|
clear(); |
65 |
|
|
ParticleEngine::particleCount--; |
66 |
|
|
} |
67 |
|
|
|
68 |
|
|
void ParticleEngine::setupEngine() |
69 |
|
|
{ |
70 |
|
|
ParticleEngine::maxCount = config.getIntValue("particleMaxCount"); |
71 |
|
|
ParticleEngine::fastPhysics = fromInt(config.getIntValue( |
72 |
|
|
"particleFastPhysics"), |
73 |
|
|
ParticlePhysicsT); |
74 |
|
|
ParticleEngine::emitterSkip = |
75 |
|
|
config.getIntValue("particleEmitterSkip") + 1; |
76 |
|
|
if (ParticleEngine::emitterSkip == 0) |
77 |
|
|
ParticleEngine::emitterSkip = 1; |
78 |
|
|
ParticleEngine::enabled = config.getBoolValue("particleeffects"); |
79 |
|
|
logger->log1("Particle engine set up"); |
80 |
|
|
} |
81 |
|
|
|
82 |
|
|
bool ParticleEngine::update() restrict2 |
83 |
|
|
{ |
84 |
|
|
if (mChildParticles.empty() || (mMap == nullptr)) |
85 |
|
|
return true; |
86 |
|
|
|
87 |
|
|
// Update child particles |
88 |
|
|
|
89 |
|
|
const int cameraX = viewport->getCameraX(); |
90 |
|
|
const int cameraY = viewport->getCameraY(); |
91 |
|
|
const float x1 = static_cast<float>(cameraX - 3000); |
92 |
|
|
const float y1 = static_cast<float>(cameraY - 2000); |
93 |
|
|
const float x2 = static_cast<float>(cameraX + 3000); |
94 |
|
|
const float y2 = static_cast<float>(cameraY + 2000); |
95 |
|
|
|
96 |
|
|
for (ParticleIterator p = mChildParticles.begin(), |
97 |
|
|
fp2 = mChildParticles.end(); p != fp2; ) |
98 |
|
|
{ |
99 |
|
|
Particle *restrict const particle = *p; |
100 |
|
|
const float posX = particle->mPos.x; |
101 |
|
|
const float posY = particle->mPos.y; |
102 |
|
|
if (posX < x1 || posX > x2 || posY < y1 || posY > y2) |
103 |
|
|
{ |
104 |
|
|
++p; |
105 |
|
|
continue; |
106 |
|
|
} |
107 |
|
|
// update particle |
108 |
|
|
if (particle->update()) |
109 |
|
|
{ |
110 |
|
|
++p; |
111 |
|
|
} |
112 |
|
|
else |
113 |
|
|
{ |
114 |
|
|
mChildMoveParticles.remove(*p); |
115 |
|
|
delete particle; |
116 |
|
|
p = mChildParticles.erase(p); |
117 |
|
|
} |
118 |
|
|
} |
119 |
|
|
return true; |
120 |
|
|
} |
121 |
|
|
|
122 |
|
|
Particle *ParticleEngine::createChild() restrict2 |
123 |
|
|
{ |
124 |
|
|
Particle *const newParticle = new Particle; |
125 |
|
|
newParticle->setMap(mMap); |
126 |
|
|
mChildParticles.push_back(newParticle); |
127 |
|
|
return newParticle; |
128 |
|
|
} |
129 |
|
|
|
130 |
|
|
Particle *ParticleEngine::addEffect(const std::string &restrict |
131 |
|
|
particleEffectFile, |
132 |
|
|
const int pixelX, |
133 |
|
|
const int pixelY, |
134 |
|
|
const int rotation) restrict2 |
135 |
|
|
{ |
136 |
|
|
Particle *newParticle = nullptr; |
137 |
|
|
|
138 |
|
|
const size_t pos = particleEffectFile.find('|'); |
139 |
|
|
const std::string dyePalettes = (pos != std::string::npos) |
140 |
|
|
? particleEffectFile.substr(pos + 1) : ""; |
141 |
|
|
XML::Document *doc = Loader::getXml( |
142 |
|
|
particleEffectFile.substr(0, pos), |
143 |
|
|
UseVirtFs_true, |
144 |
|
|
SkipError_false); |
145 |
|
|
if (doc == nullptr) |
146 |
|
|
return nullptr; |
147 |
|
|
|
148 |
|
|
XmlNodeConstPtrConst rootNode = doc->rootNode(); |
149 |
|
|
|
150 |
|
|
if ((rootNode == nullptr) || !xmlNameEqual(rootNode, "effect")) |
151 |
|
|
{ |
152 |
|
|
logger->log("Error loading particle: %s", particleEffectFile.c_str()); |
153 |
|
|
doc->decRef(); |
154 |
|
|
return nullptr; |
155 |
|
|
} |
156 |
|
|
|
157 |
|
|
// Parse particles |
158 |
|
|
for_each_xml_child_node(effectChildNode, rootNode) |
159 |
|
|
{ |
160 |
|
|
// We're only interested in particles |
161 |
|
|
if (!xmlNameEqual(effectChildNode, "particle")) |
162 |
|
|
continue; |
163 |
|
|
|
164 |
|
|
// Determine the exact particle type |
165 |
|
|
XmlNodePtr node; |
166 |
|
|
|
167 |
|
|
// Animation |
168 |
|
|
if ((node = XML::findFirstChildByName(effectChildNode, "animation")) != |
169 |
|
|
nullptr) |
170 |
|
|
{ |
171 |
|
|
newParticle = new AnimationParticle(node, dyePalettes); |
172 |
|
|
newParticle->setMap(mMap); |
173 |
|
|
} |
174 |
|
|
// Rotational |
175 |
|
|
else if ((node = XML::findFirstChildByName( |
176 |
|
|
effectChildNode, "rotation")) != nullptr) |
177 |
|
|
{ |
178 |
|
|
newParticle = new RotationalParticle(node, dyePalettes); |
179 |
|
|
newParticle->setMap(mMap); |
180 |
|
|
} |
181 |
|
|
// Image |
182 |
|
|
else if ((node = XML::findFirstChildByName(effectChildNode, |
183 |
|
|
"image")) != nullptr) |
184 |
|
|
{ |
185 |
|
|
std::string imageSrc; |
186 |
|
|
if (XmlHaveChildContent(node)) |
187 |
|
|
imageSrc = XmlChildContent(node); |
188 |
|
|
if (!imageSrc.empty() && !dyePalettes.empty()) |
189 |
|
|
Dye::instantiate(imageSrc, dyePalettes); |
190 |
|
|
Image *const img = Loader::getImage(imageSrc); |
191 |
|
|
|
192 |
|
|
newParticle = new ImageParticle(img); |
193 |
|
|
newParticle->setMap(mMap); |
194 |
|
|
} |
195 |
|
|
// Other |
196 |
|
|
else |
197 |
|
|
{ |
198 |
|
|
newParticle = new Particle; |
199 |
|
|
newParticle->setMap(mMap); |
200 |
|
|
} |
201 |
|
|
|
202 |
|
|
// Read and set the basic properties of the particle |
203 |
|
|
const float offsetX = XML::getFloatProperty( |
204 |
|
|
effectChildNode, "position-x", 0); |
205 |
|
|
const float offsetY = XML::getFloatProperty( |
206 |
|
|
effectChildNode, "position-y", 0); |
207 |
|
|
const float offsetZ = XML::getFloatProperty( |
208 |
|
|
effectChildNode, "position-z", 0); |
209 |
|
|
const Vector position(static_cast<float>(pixelX) + offsetX, |
210 |
|
|
static_cast<float>(pixelY) + offsetY, |
211 |
|
|
offsetZ); |
212 |
|
|
newParticle->moveTo(position); |
213 |
|
|
|
214 |
|
|
const int lifetime = XML::getProperty(effectChildNode, "lifetime", -1); |
215 |
|
|
newParticle->setLifetime(lifetime); |
216 |
|
|
const bool resizeable = "false" != XML::getProperty(effectChildNode, |
217 |
|
|
"size-adjustable", "false"); |
218 |
|
|
|
219 |
|
|
newParticle->setAllowSizeAdjust(resizeable); |
220 |
|
|
|
221 |
|
|
// Look for additional emitters for this particle |
222 |
|
|
for_each_xml_child_node(emitterNode, effectChildNode) |
223 |
|
|
{ |
224 |
|
|
if (xmlNameEqual(emitterNode, "emitter")) |
225 |
|
|
{ |
226 |
|
|
ParticleEmitter *restrict const newEmitter = |
227 |
|
|
new ParticleEmitter( |
228 |
|
|
emitterNode, |
229 |
|
|
newParticle, |
230 |
|
|
mMap, |
231 |
|
|
rotation, |
232 |
|
|
dyePalettes); |
233 |
|
|
newParticle->addEmitter(newEmitter); |
234 |
|
|
} |
235 |
|
|
else if (xmlNameEqual(emitterNode, "deatheffect")) |
236 |
|
|
{ |
237 |
|
|
std::string deathEffect; |
238 |
|
|
if ((node != nullptr) && XmlHaveChildContent(node)) |
239 |
|
|
deathEffect = XmlChildContent(emitterNode); |
240 |
|
|
|
241 |
|
|
char deathEffectConditions = 0x00; |
242 |
|
|
if (XML::getBoolProperty(emitterNode, "on-floor", true)) |
243 |
|
|
{ |
244 |
|
|
deathEffectConditions += CAST_S8( |
245 |
|
|
AliveStatus::DEAD_FLOOR); |
246 |
|
|
} |
247 |
|
|
if (XML::getBoolProperty(emitterNode, "on-sky", true)) |
248 |
|
|
{ |
249 |
|
|
deathEffectConditions += CAST_S8( |
250 |
|
|
AliveStatus::DEAD_SKY); |
251 |
|
|
} |
252 |
|
|
if (XML::getBoolProperty(emitterNode, "on-other", false)) |
253 |
|
|
{ |
254 |
|
|
deathEffectConditions += CAST_S8( |
255 |
|
|
AliveStatus::DEAD_OTHER); |
256 |
|
|
} |
257 |
|
|
if (XML::getBoolProperty(emitterNode, "on-impact", true)) |
258 |
|
|
{ |
259 |
|
|
deathEffectConditions += CAST_S8( |
260 |
|
|
AliveStatus::DEAD_IMPACT); |
261 |
|
|
} |
262 |
|
|
if (XML::getBoolProperty(emitterNode, "on-timeout", true)) |
263 |
|
|
{ |
264 |
|
|
deathEffectConditions += CAST_S8( |
265 |
|
|
AliveStatus::DEAD_TIMEOUT); |
266 |
|
|
} |
267 |
|
|
newParticle->setDeathEffect( |
268 |
|
|
deathEffect, deathEffectConditions); |
269 |
|
|
} |
270 |
|
|
} |
271 |
|
|
|
272 |
|
|
mChildParticles.push_back(newParticle); |
273 |
|
|
} |
274 |
|
|
|
275 |
|
|
doc->decRef(); |
276 |
|
|
return newParticle; |
277 |
|
|
} |
278 |
|
|
|
279 |
|
|
Particle *ParticleEngine::addTextSplashEffect(const std::string &restrict text, |
280 |
|
|
const int x, |
281 |
|
|
const int y, |
282 |
|
|
const Color *restrict const |
283 |
|
|
color, |
284 |
|
|
Font *restrict const font, |
285 |
|
|
const bool outline) restrict2 |
286 |
|
|
{ |
287 |
|
|
Particle *const newParticle = new TextParticle( |
288 |
|
|
text, |
289 |
|
|
color, |
290 |
|
|
font, |
291 |
|
|
outline); |
292 |
|
|
newParticle->setMap(mMap); |
293 |
|
|
newParticle->moveTo(static_cast<float>(x), |
294 |
|
|
static_cast<float>(y)); |
295 |
|
|
newParticle->setVelocity( |
296 |
|
|
static_cast<float>((rand() % 100) - 50) / 200.0F, // X |
297 |
|
|
static_cast<float>((rand() % 100) - 50) / 200.0F, // Y |
298 |
|
|
(static_cast<float>((rand() % 100)) / 200.0F) + 4.0F); // Z |
299 |
|
|
|
300 |
|
|
newParticle->setGravity(0.1F); |
301 |
|
|
newParticle->setBounce(0.5F); |
302 |
|
|
newParticle->setLifetime(200); |
303 |
|
|
newParticle->setFadeOut(100); |
304 |
|
|
|
305 |
|
|
mChildParticles.push_back(newParticle); |
306 |
|
|
|
307 |
|
|
return newParticle; |
308 |
|
|
} |
309 |
|
|
|
310 |
|
|
Particle *ParticleEngine::addTextRiseFadeOutEffect(const std::string &restrict |
311 |
|
|
text, |
312 |
|
|
const int x, |
313 |
|
|
const int y, |
314 |
|
|
const Color *restrict const |
315 |
|
|
color, |
316 |
|
|
Font *restrict const font, |
317 |
|
|
const bool outline) |
318 |
|
|
restrict2 |
319 |
|
|
{ |
320 |
|
|
Particle *const newParticle = new TextParticle( |
321 |
|
|
text, |
322 |
|
|
color, |
323 |
|
|
font, |
324 |
|
|
outline); |
325 |
|
|
newParticle->setMap(mMap); |
326 |
|
|
newParticle->moveTo(static_cast<float>(x), |
327 |
|
|
static_cast<float>(y)); |
328 |
|
|
newParticle->setVelocity(0.0F, 0.0F, 0.5F); |
329 |
|
|
newParticle->setGravity(0.0015F); |
330 |
|
|
newParticle->setLifetime(300); |
331 |
|
|
newParticle->setFadeOut(100); |
332 |
|
|
newParticle->setFadeIn(0); |
333 |
|
|
|
334 |
|
|
mChildParticles.push_back(newParticle); |
335 |
|
|
|
336 |
|
|
return newParticle; |
337 |
|
|
} |
338 |
|
|
|
339 |
|
|
void ParticleEngine::clear() restrict2 |
340 |
|
|
{ |
341 |
|
|
delete_all(mChildParticles); |
342 |
|
|
mChildParticles.clear(); |
343 |
|
|
mChildMoveParticles.clear(); |
344 |
|
2 |
} |