GCC Code Coverage Report
Directory: src/ Exec Total Coverage
File: src/being/actorsprite.cpp Lines: 39 202 19.3 %
Date: 2021-03-17 Branches: 31 237 13.1 %

Line Branch Exec Source
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
69
103
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

309
    mTrickDead(false)
85
{
86
103
}
87
88
412
ActorSprite::~ActorSprite()
89
{
90
103
    mChildParticleEffects.clear();
91
103
    mMustResetParticles = true;
92
93
103
    mUsedTargetCursor = nullptr;
94
95

309
    if (localPlayer != nullptr &&
96

112
        localPlayer != this &&
97
9
        localPlayer->getTarget() == this)
98
    {
99
        localPlayer->setTarget(nullptr);
100
    }
101
102
    // Notify listeners of the destruction.
103
309
    FOR_EACH (ActorSpriteListenerIterator, iter, mActorSpriteListeners)
104
    {
105
        if (reportFalse(*iter))
106
            (*iter)->actorSpriteDestroyed(*this);
107
    }
108
103
}
109
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
139
103
void ActorSprite::setMap(Map *const map)
140
{
141
103
    Actor::setMap(map);
142
143
    // Clear particle effect list because child particles became invalid
144
103
    mChildParticleEffects.clear();
145
103
    mMustResetParticles = true;  // Reset status particles on next redraw
146
103
}
147
148
void ActorSprite::controlAutoParticle(Particle *const particle)
149
{
150
    if (particle != nullptr)
151
    {
152
        particle->setActor(mId);
153
        mChildParticleEffects.addLocally(particle);
154
    }
155
}
156
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
167
void ActorSprite::controlParticleDeleted(const Particle *const particle)
168
{
169
    if (particle != nullptr)
170
        mChildParticleEffects.removeLocally(particle);
171
}
172
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
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
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
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
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
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
311
void ActorSprite::setStatusEffectOpiton0(const uint32_t option)
312
{
313
    applyEffectByOption(this, option, "option",
314
        StatusEffectDB::getOptionMap());
315
}
316
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
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
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
435
215
void ActorSprite::load()
436
{
437
215
    if (loaded)
438
        unload();
439
440
215
    initTargetCursor();
441
442
215
    loaded = true;
443
215
}
444
445
215
void ActorSprite::unload()
446
{
447

215
    if (reportTrue(!loaded))
448
        return;
449
450
215
    cleanupTargetCursors();
451
215
    loaded = false;
452
}
453
454
void ActorSprite::addActorSpriteListener(ActorSpriteListener *const listener)
455
{
456
    mActorSpriteListeners.push_front(listener);
457
}
458
459
void ActorSprite::removeActorSpriteListener(ActorSpriteListener *const
460
                                            listener)
461
{
462
    mActorSpriteListeners.remove(listener);
463
}
464
465
static const char *cursorType(const TargetCursorTypeT type)
466
{
467
1290
    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
479
static const char *cursorSize(const TargetCursorSizeT size)
480
{
481
1290
    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
494
215
void ActorSprite::initTargetCursor()
495
{
496

217
    static const std::string targetCursorFile("target-cursor-%s-%s.xml");
497
498
    // Load target cursors
499

2150
    for_each_cursors()
500
    {
501
1290
        targetCursor[type][size] = AnimatedSprite::load(
502
6450
            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
215
}
510
511
215
void ActorSprite::cleanupTargetCursors()
512
{
513

1505
    for_each_cursors()
514
    {
515
1290
        delete2(targetCursor[type][size])
516
    }
517
    end_foreach
518
215
}
519
520
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
2
}