1 /*
2 * The ManaPlus Client
3 * Copyright (C) 2010 The Mana Developers
4 * Copyright (C) 2011-2019 The ManaPlus Developers
5 * Copyright (C) 2019-2021 Andrei Karas
6 *
7 * This file is part of The ManaPlus Client.
8 *
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program. If not, see <http://www.gnu.org/licenses/>.
21 */
22
23 #include "being/actorsprite.h"
24
25 #include "configuration.h"
26 #include "statuseffect.h"
27
28 #include "being/localplayer.h"
29
30 #include "const/utils/timer.h"
31
32 #include "gui/theme.h"
33
34 #include "listeners/debugmessagelistener.h"
35
36 #include "particle/particle.h"
37
38 #include "resources/db/statuseffectdb.h"
39
40 #include "resources/loaders/imageloader.h"
41
42 #include "resources/sprite/animatedsprite.h"
43 #include "resources/sprite/imagesprite.h"
44 #include "resources/sprite/spritereference.h"
45
46 #include "utils/checkutils.h"
47 #include "utils/delete2.h"
48 #include "utils/foreach.h"
49 #include "utils/timer.h"
50
51 #include "debug.h"
52
53 #define for_each_cursors() \
54 for (int size = CAST_S32(TargetCursorSize::SMALL); \
55 size < CAST_S32(TargetCursorSize::NUM_TC); \
56 size ++) \
57 { \
58 for (int type = CAST_S32(TargetCursorType::NORMAL); \
59 type < CAST_S32(TargetCursorType::NUM_TCT); \
60 type ++) \
61
62 #define end_foreach }
63
64 AnimatedSprite *ActorSprite::targetCursor
65 [CAST_SIZE(TargetCursorType::NUM_TCT)]
66 [CAST_SIZE(TargetCursorSize::NUM_TC)];
67 bool ActorSprite::loaded = false;
68
ActorSprite(const BeingId id)69 ActorSprite::ActorSprite(const BeingId id) :
70 CompoundSprite(),
71 Actor(),
72 mStatusEffects(),
73 mStatusParticleEffects(nullptr, true),
74 mChildParticleEffects(&mStatusParticleEffects, false),
75 mHorseId(0),
76 mId(id),
77 mUsedTargetCursor(nullptr),
78 mActorSpriteListeners(),
79 mCursorPaddingX(0),
80 mCursorPaddingY(0),
81 mMustResetParticles(false),
82 mPoison(false),
83 mHaveCart(false),
84 mTrickDead(false)
85 {
86 }
87
~ActorSprite()88 ActorSprite::~ActorSprite()
89 {
90 mChildParticleEffects.clear();
91 mMustResetParticles = true;
92
93 mUsedTargetCursor = nullptr;
94
95 if (localPlayer != nullptr &&
96 localPlayer != this &&
97 localPlayer->getTarget() == this)
98 {
99 localPlayer->setTarget(nullptr);
100 }
101
102 // Notify listeners of the destruction.
103 FOR_EACH (ActorSpriteListenerIterator, iter, mActorSpriteListeners)
104 {
105 if (reportFalse(*iter))
106 (*iter)->actorSpriteDestroyed(*this);
107 }
108 }
109
logic()110 void ActorSprite::logic()
111 {
112 BLOCK_START("ActorSprite::logic")
113 // Update sprite animations
114 update(tick_time * MILLISECONDS_IN_A_TICK);
115
116 // Restart status/particle effects, if needed
117 if (mMustResetParticles)
118 {
119 mMustResetParticles = false;
120 FOR_EACH (std::set<int32_t>::const_iterator, it, mStatusEffects)
121 {
122 const StatusEffect *const effect
123 = StatusEffectDB::getStatusEffect(*it, Enable_true);
124 if (effect != nullptr &&
125 effect->mIsPersistent)
126 {
127 updateStatusEffect(*it,
128 Enable_true,
129 IsStart_false);
130 }
131 }
132 }
133
134 // Update particle effects
135 mChildParticleEffects.moveTo(mPos.x, mPos.y);
136 BLOCK_END("ActorSprite::logic")
137 }
138
setMap(Map * const map)139 void ActorSprite::setMap(Map *const map)
140 {
141 Actor::setMap(map);
142
143 // Clear particle effect list because child particles became invalid
144 mChildParticleEffects.clear();
145 mMustResetParticles = true; // Reset status particles on next redraw
146 }
147
controlAutoParticle(Particle * const particle)148 void ActorSprite::controlAutoParticle(Particle *const particle)
149 {
150 if (particle != nullptr)
151 {
152 particle->setActor(mId);
153 mChildParticleEffects.addLocally(particle);
154 }
155 }
156
controlCustomParticle(Particle * const particle)157 void ActorSprite::controlCustomParticle(Particle *const particle)
158 {
159 if (particle != nullptr)
160 {
161 // The effect may not die without the beings permission or we segfault
162 particle->disableAutoDelete();
163 mChildParticleEffects.addLocally(particle);
164 }
165 }
166
controlParticleDeleted(const Particle * const particle)167 void ActorSprite::controlParticleDeleted(const Particle *const particle)
168 {
169 if (particle != nullptr)
170 mChildParticleEffects.removeLocally(particle);
171 }
172
setTargetType(const TargetCursorTypeT type)173 void ActorSprite::setTargetType(const TargetCursorTypeT type)
174 {
175 if (type == TargetCursorType::NONE)
176 {
177 untarget();
178 }
179 else
180 {
181 const size_t sz = CAST_SIZE(getTargetCursorSize());
182 mUsedTargetCursor = targetCursor[CAST_S32(type)][sz];
183 if (mUsedTargetCursor != nullptr)
184 {
185 static const int targetWidths[CAST_SIZE(
186 TargetCursorSize::NUM_TC)]
187 = {0, 0, 0};
188 static const int targetHeights[CAST_SIZE(
189 TargetCursorSize::NUM_TC)]
190 = {-mapTileSize / 2, -mapTileSize / 2, -mapTileSize};
191
192 mCursorPaddingX = CAST_S32(targetWidths[sz]);
193 mCursorPaddingY = CAST_S32(targetHeights[sz]);
194 }
195 }
196 }
197
setStatusEffect(const int32_t index,const Enable active,const IsStart start)198 void ActorSprite::setStatusEffect(const int32_t index,
199 const Enable active,
200 const IsStart start)
201 {
202 const Enable wasActive = fromBool(
203 mStatusEffects.find(index) != mStatusEffects.end(), Enable);
204
205 if (active != wasActive)
206 {
207 updateStatusEffect(index, active, start);
208 if (active == Enable_true)
209 {
210 mStatusEffects.insert(index);
211 }
212 else
213 {
214 mStatusEffects.erase(index);
215 }
216 }
217 }
218
applyEffectByOption(ActorSprite * const actor,uint32_t option,const char * const name,const OptionsMap & options)219 static void applyEffectByOption(ActorSprite *const actor,
220 uint32_t option,
221 const char *const name,
222 const OptionsMap& options)
223 {
224 FOR_EACH (OptionsMapCIter, it, options)
225 {
226 const uint32_t opt = (*it).first;
227 const int32_t id = (*it).second;
228 const Enable enable = (opt & option) != 0 ? Enable_true : Enable_false;
229 option |= opt;
230 option ^= opt;
231 actor->setStatusEffect(id,
232 enable,
233 IsStart_false);
234 }
235 if (option != 0U &&
236 config.getBoolValue("unimplimentedLog"))
237 {
238 const std::string str = strprintf(
239 "Error: unknown effect by %s. "
240 "Left value: %u",
241 name,
242 option);
243 logger->log(str);
244 DebugMessageListener::distributeEvent(str);
245 }
246 }
247
applyEffectByOption1(ActorSprite * const actor,uint32_t option,const char * const name,const OptionsMap & options)248 static void applyEffectByOption1(ActorSprite *const actor,
249 uint32_t option,
250 const char *const name,
251 const OptionsMap& options)
252 {
253 FOR_EACH (OptionsMapCIter, it, options)
254 {
255 const uint32_t opt = (*it).first;
256 const int32_t id = (*it).second;
257 if (opt == option)
258 {
259 actor->setStatusEffect(id,
260 Enable_true,
261 IsStart_false);
262 option = 0U;
263 }
264 else
265 {
266 actor->setStatusEffect(id,
267 Enable_false,
268 IsStart_false);
269 }
270 }
271 if (option != 0 &&
272 config.getBoolValue("unimplimentedLog"))
273 {
274 const std::string str = strprintf(
275 "Error: unknown effect by %s. "
276 "Left value: %u",
277 name,
278 option);
279 logger->log(str);
280 DebugMessageListener::distributeEvent(str);
281 }
282 }
283
setStatusEffectOpitons(const uint32_t option,const uint32_t opt1,const uint32_t opt2,const uint32_t opt3)284 void ActorSprite::setStatusEffectOpitons(const uint32_t option,
285 const uint32_t opt1,
286 const uint32_t opt2,
287 const uint32_t opt3)
288 {
289 applyEffectByOption(this, option, "option",
290 StatusEffectDB::getOptionMap());
291 applyEffectByOption1(this, opt1, "opt1",
292 StatusEffectDB::getOpt1Map());
293 applyEffectByOption(this, opt2, "opt2",
294 StatusEffectDB::getOpt2Map());
295 applyEffectByOption(this, opt3, "opt3",
296 StatusEffectDB::getOpt3Map());
297 }
298
setStatusEffectOpitons(const uint32_t option,const uint32_t opt1,const uint32_t opt2)299 void ActorSprite::setStatusEffectOpitons(const uint32_t option,
300 const uint32_t opt1,
301 const uint32_t opt2)
302 {
303 applyEffectByOption(this, option, "option",
304 StatusEffectDB::getOptionMap());
305 applyEffectByOption1(this, opt1, "opt1",
306 StatusEffectDB::getOpt1Map());
307 applyEffectByOption(this, opt2, "opt2",
308 StatusEffectDB::getOpt2Map());
309 }
310
setStatusEffectOpiton0(const uint32_t option)311 void ActorSprite::setStatusEffectOpiton0(const uint32_t option)
312 {
313 applyEffectByOption(this, option, "option",
314 StatusEffectDB::getOptionMap());
315 }
316
updateStatusEffect(const int32_t index,const Enable newStatus,const IsStart start)317 void ActorSprite::updateStatusEffect(const int32_t index,
318 const Enable newStatus,
319 const IsStart start)
320 {
321 StatusEffect *const effect = StatusEffectDB::getStatusEffect(
322 index, newStatus);
323 if (effect == nullptr)
324 return;
325 if (effect->mIsPoison && getType() == ActorType::Player)
326 setPoison(newStatus == Enable_true);
327 else if (effect->mIsCart && localPlayer == this)
328 setHaveCart(newStatus == Enable_true);
329 else if (effect->mIsRiding)
330 setRiding(newStatus == Enable_true);
331 else if (effect->mIsTrickDead)
332 setTrickDead(newStatus == Enable_true);
333 else if (effect->mIsPostDelay)
334 stopCast(newStatus == Enable_true);
335 handleStatusEffect(effect, index, newStatus, start);
336 }
337
handleStatusEffect(const StatusEffect * const effect,const int32_t effectId,const Enable newStatus,const IsStart start)338 void ActorSprite::handleStatusEffect(const StatusEffect *const effect,
339 const int32_t effectId,
340 const Enable newStatus,
341 const IsStart start)
342 {
343 if (effect == nullptr)
344 return;
345
346 if (newStatus == Enable_true)
347 {
348 if (effectId >= 0)
349 {
350 Particle *particle = nullptr;
351 if (start == IsStart_true)
352 particle = effect->getStartParticle();
353 if (particle == nullptr)
354 particle = effect->getParticle();
355 if (particle != nullptr)
356 mStatusParticleEffects.setLocally(effectId, particle);
357 }
358 }
359 else
360 {
361 mStatusParticleEffects.delLocally(effectId);
362 }
363 }
364
setupSpriteDisplay(const SpriteDisplay & display,const ForceDisplay forceDisplay,const DisplayTypeT displayType,const std::string & color)365 void ActorSprite::setupSpriteDisplay(const SpriteDisplay &display,
366 const ForceDisplay forceDisplay,
367 const DisplayTypeT displayType,
368 const std::string &color)
369 {
370 clear();
371
372 FOR_EACH (SpriteRefs, it, display.sprites)
373 {
374 if (*it == nullptr)
375 continue;
376 const std::string file = pathJoin(paths.getStringValue("sprites"),
377 combineDye3((*it)->sprite, color));
378
379 const int variant = (*it)->variant;
380 addSprite(AnimatedSprite::delayedLoad(file, variant));
381 }
382
383 // Ensure that something is shown, if desired
384 if (mSprites.empty() && forceDisplay == ForceDisplay_true)
385 {
386 if (display.image.empty())
387 {
388 addSprite(AnimatedSprite::delayedLoad(pathJoin(
389 paths.getStringValue("sprites"),
390 paths.getStringValue("spriteErrorFile")),
391 0));
392 }
393 else
394 {
395 std::string imagePath;
396 switch (displayType)
397 {
398 case DisplayType::Item:
399 default:
400 imagePath = pathJoin(paths.getStringValue("itemIcons"),
401 display.image);
402 break;
403 case DisplayType::Floor:
404 imagePath = pathJoin(paths.getStringValue("itemIcons"),
405 display.floor);
406 break;
407 }
408 imagePath = combineDye2(imagePath, color);
409 Image *img = Loader::getImage(imagePath);
410
411 if (img == nullptr)
412 img = Theme::getImageFromTheme("unknown-item.png");
413
414 addSprite(new ImageSprite(img));
415 if (img != nullptr)
416 img->decRef();
417 }
418 }
419
420 mChildParticleEffects.clear();
421
422 // setup particle effects
423 if (ParticleEngine::enabled && (particleEngine != nullptr))
424 {
425 FOR_EACH (StringVectCIter, itr, display.particles)
426 {
427 Particle *const p = particleEngine->addEffect(*itr, 0, 0, 0);
428 controlAutoParticle(p);
429 }
430 }
431
432 mMustResetParticles = true;
433 }
434
load()435 void ActorSprite::load()
436 {
437 if (loaded)
438 unload();
439
440 initTargetCursor();
441
442 loaded = true;
443 }
444
unload()445 void ActorSprite::unload()
446 {
447 if (reportTrue(!loaded))
448 return;
449
450 cleanupTargetCursors();
451 loaded = false;
452 }
453
addActorSpriteListener(ActorSpriteListener * const listener)454 void ActorSprite::addActorSpriteListener(ActorSpriteListener *const listener)
455 {
456 mActorSpriteListeners.push_front(listener);
457 }
458
removeActorSpriteListener(ActorSpriteListener * const listener)459 void ActorSprite::removeActorSpriteListener(ActorSpriteListener *const
460 listener)
461 {
462 mActorSpriteListeners.remove(listener);
463 }
464
cursorType(const TargetCursorTypeT type)465 static const char *cursorType(const TargetCursorTypeT type)
466 {
467 switch (type)
468 {
469 case TargetCursorType::IN_RANGE:
470 return "in-range";
471 default:
472 case TargetCursorType::NONE:
473 case TargetCursorType::NUM_TCT:
474 case TargetCursorType::NORMAL:
475 return "normal";
476 }
477 }
478
cursorSize(const TargetCursorSizeT size)479 static const char *cursorSize(const TargetCursorSizeT size)
480 {
481 switch (size)
482 {
483 case TargetCursorSize::LARGE:
484 return "l";
485 case TargetCursorSize::MEDIUM:
486 return "m";
487 default:
488 case TargetCursorSize::NUM_TC:
489 case TargetCursorSize::SMALL:
490 return "s";
491 }
492 }
493
initTargetCursor()494 void ActorSprite::initTargetCursor()
495 {
496 static const std::string targetCursorFile("target-cursor-%s-%s.xml");
497
498 // Load target cursors
499 for_each_cursors()
500 {
501 targetCursor[type][size] = AnimatedSprite::load(
502 Theme::resolveThemePath(strprintf(
503 targetCursorFile.c_str(),
504 cursorType(static_cast<TargetCursorTypeT>(type)),
505 cursorSize(static_cast<TargetCursorSizeT>(size)))),
506 0);
507 }
508 end_foreach
509 }
510
cleanupTargetCursors()511 void ActorSprite::cleanupTargetCursors()
512 {
513 for_each_cursors()
514 {
515 delete2(targetCursor[type][size])
516 }
517 end_foreach
518 }
519
getStatusEffectsString() const520 std::string ActorSprite::getStatusEffectsString() const
521 {
522 std::string effectsStr;
523 if (!mStatusEffects.empty())
524 {
525 FOR_EACH (std::set<int32_t>::const_iterator, it, mStatusEffects)
526 {
527 const StatusEffect *const effect =
528 StatusEffectDB::getStatusEffect(
529 *it,
530 Enable_true);
531 if (effect == nullptr)
532 continue;
533 if (!effectsStr.empty())
534 effectsStr.append(", ");
535 effectsStr.append(effect->mName);
536 }
537 }
538 return effectsStr;
539 }
540