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