GCC Code Coverage Report
Directory: src/ Exec Total Coverage
File: src/being/actorsprite.cpp Lines: 39 202 19.3 %
Date: 2018-07-14 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-2018  The ManaPlus Developers
5
 *
6
 *  This file is part of The ManaPlus Client.
7
 *
8
 *  This program is free software; you can redistribute it and/or modify
9
 *  it under the terms of the GNU General Public License as published by
10
 *  the Free Software Foundation; either version 2 of the License, or
11
 *  any later version.
12
 *
13
 *  This program is distributed in the hope that it will be useful,
14
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
15
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16
 *  GNU General Public License for more details.
17
 *
18
 *  You should have received a copy of the GNU General Public License
19
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
20
 */
21
22
#include "being/actorsprite.h"
23
24
#include "configuration.h"
25
#include "statuseffect.h"
26
27
#include "being/localplayer.h"
28
29
#include "const/utils/timer.h"
30
31
#include "gui/theme.h"
32
33
#include "listeners/debugmessagelistener.h"
34
35
#include "particle/particle.h"
36
37
#include "resources/db/statuseffectdb.h"
38
39
#include "resources/loaders/imageloader.h"
40
41
#include "resources/sprite/animatedsprite.h"
42
#include "resources/sprite/imagesprite.h"
43
#include "resources/sprite/spritereference.h"
44
45
#include "utils/checkutils.h"
46
#include "utils/delete2.h"
47
#include "utils/foreach.h"
48
#include "utils/timer.h"
49
50
#include "debug.h"
51
52
#define for_each_cursors() \
53
    for (int size = CAST_S32(TargetCursorSize::SMALL); \
54
         size < CAST_S32(TargetCursorSize::NUM_TC); \
55
         size ++) \
56
    { \
57
        for (int type = CAST_S32(TargetCursorType::NORMAL); \
58
             type < CAST_S32(TargetCursorType::NUM_TCT); \
59
             type ++) \
60
61
#define end_foreach }
62
63
AnimatedSprite *ActorSprite::targetCursor
64
    [CAST_SIZE(TargetCursorType::NUM_TCT)]
65
    [CAST_SIZE(TargetCursorSize::NUM_TC)];
66
bool ActorSprite::loaded = false;
67
68
103
ActorSprite::ActorSprite(const BeingId id) :
69
    CompoundSprite(),
70
    Actor(),
71
    mStatusEffects(),
72
    mStatusParticleEffects(nullptr, true),
73
    mChildParticleEffects(&mStatusParticleEffects, false),
74
    mHorseId(0),
75
    mId(id),
76
    mUsedTargetCursor(nullptr),
77
    mActorSpriteListeners(),
78
    mCursorPaddingX(0),
79
    mCursorPaddingY(0),
80
    mMustResetParticles(false),
81
    mPoison(false),
82
    mHaveCart(false),
83

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

309
    if (localPlayer != nullptr &&
95

112
        localPlayer != this &&
96
9
        localPlayer->getTarget() == this)
97
    {
98
        localPlayer->setTarget(nullptr);
99
    }
100
101
    // Notify listeners of the destruction.
102
309
    FOR_EACH (ActorSpriteListenerIterator, iter, mActorSpriteListeners)
103
    {
104
        if (reportFalse(*iter))
105
            (*iter)->actorSpriteDestroyed(*this);
106
    }
107
103
}
108
109
void ActorSprite::logic()
110
{
111
    BLOCK_START("ActorSprite::logic")
112
    // Update sprite animations
113
    update(tick_time * MILLISECONDS_IN_A_TICK);
114
115
    // Restart status/particle effects, if needed
116
    if (mMustResetParticles)
117
    {
118
        mMustResetParticles = false;
119
        FOR_EACH (std::set<int32_t>::const_iterator, it, mStatusEffects)
120
        {
121
            const StatusEffect *const effect
122
                = StatusEffectDB::getStatusEffect(*it, Enable_true);
123
            if (effect != nullptr &&
124
                effect->mIsPersistent)
125
            {
126
                updateStatusEffect(*it,
127
                    Enable_true,
128
                    IsStart_false);
129
            }
130
        }
131
    }
132
133
    // Update particle effects
134
    mChildParticleEffects.moveTo(mPos.x, mPos.y);
135
    BLOCK_END("ActorSprite::logic")
136
}
137
138
103
void ActorSprite::setMap(Map *const map)
139
{
140
103
    Actor::setMap(map);
141
142
    // Clear particle effect list because child particles became invalid
143
103
    mChildParticleEffects.clear();
144
103
    mMustResetParticles = true;  // Reset status particles on next redraw
145
103
}
146
147
void ActorSprite::controlAutoParticle(Particle *const particle)
148
{
149
    if (particle != nullptr)
150
    {
151
        particle->setActor(mId);
152
        mChildParticleEffects.addLocally(particle);
153
    }
154
}
155
156
void ActorSprite::controlCustomParticle(Particle *const particle)
157
{
158
    if (particle != nullptr)
159
    {
160
        // The effect may not die without the beings permission or we segfault
161
        particle->disableAutoDelete();
162
        mChildParticleEffects.addLocally(particle);
163
    }
164
}
165
166
void ActorSprite::controlParticleDeleted(const Particle *const particle)
167
{
168
    if (particle != nullptr)
169
        mChildParticleEffects.removeLocally(particle);
170
}
171
172
void ActorSprite::setTargetType(const TargetCursorTypeT type)
173
{
174
    if (type == TargetCursorType::NONE)
175
    {
176
        untarget();
177
    }
178
    else
179
    {
180
        const size_t sz = CAST_SIZE(getTargetCursorSize());
181
        mUsedTargetCursor = targetCursor[CAST_S32(type)][sz];
182
        if (mUsedTargetCursor != nullptr)
183
        {
184
            static const int targetWidths[CAST_SIZE(
185
                TargetCursorSize::NUM_TC)]
186
                = {0, 0, 0};
187
            static const int targetHeights[CAST_SIZE(
188
                TargetCursorSize::NUM_TC)]
189
                = {-mapTileSize / 2, -mapTileSize / 2, -mapTileSize};
190
191
            mCursorPaddingX = CAST_S32(targetWidths[sz]);
192
            mCursorPaddingY = CAST_S32(targetHeights[sz]);
193
        }
194
    }
195
}
196
197
void ActorSprite::setStatusEffect(const int32_t index,
198
                                  const Enable active,
199
                                  const IsStart start)
200
{
201
    const Enable wasActive = fromBool(
202
        mStatusEffects.find(index) != mStatusEffects.end(), Enable);
203
204
    if (active != wasActive)
205
    {
206
        updateStatusEffect(index, active, start);
207
        if (active == Enable_true)
208
        {
209
            mStatusEffects.insert(index);
210
        }
211
        else
212
        {
213
            mStatusEffects.erase(index);
214
        }
215
    }
216
}
217
218
static void applyEffectByOption(ActorSprite *const actor,
219
                                uint32_t option,
220
                                const char *const name,
221
                                const OptionsMap& options)
222
{
223
    FOR_EACH (OptionsMapCIter, it, options)
224
    {
225
        const uint32_t opt = (*it).first;
226
        const int32_t id = (*it).second;
227
        const Enable enable = (opt & option) != 0 ? Enable_true : Enable_false;
228
        option |= opt;
229
        option ^= opt;
230
        actor->setStatusEffect(id,
231
            enable,
232
            IsStart_false);
233
    }
234
    if (option != 0U &&
235
        config.getBoolValue("unimplimentedLog"))
236
    {
237
        const std::string str = strprintf(
238
            "Error: unknown effect by %s. "
239
            "Left value: %u",
240
            name,
241
            option);
242
            logger->log(str);
243
            DebugMessageListener::distributeEvent(str);
244
    }
245
}
246
247
static void applyEffectByOption1(ActorSprite *const actor,
248
                                 uint32_t option,
249
                                 const char *const name,
250
                                 const OptionsMap& options)
251
{
252
    FOR_EACH (OptionsMapCIter, it, options)
253
    {
254
        const uint32_t opt = (*it).first;
255
        const int32_t id = (*it).second;
256
        if (opt == option)
257
        {
258
            actor->setStatusEffect(id,
259
                Enable_true,
260
                IsStart_false);
261
            option = 0U;
262
        }
263
        else
264
        {
265
            actor->setStatusEffect(id,
266
                Enable_false,
267
                IsStart_false);
268
        }
269
    }
270
    if (option != 0 &&
271
        config.getBoolValue("unimplimentedLog"))
272
    {
273
        const std::string str = strprintf(
274
            "Error: unknown effect by %s. "
275
            "Left value: %u",
276
            name,
277
            option);
278
            logger->log(str);
279
            DebugMessageListener::distributeEvent(str);
280
    }
281
}
282
283
void ActorSprite::setStatusEffectOpitons(const uint32_t option,
284
                                         const uint32_t opt1,
285
                                         const uint32_t opt2,
286
                                         const uint32_t opt3)
287
{
288
    applyEffectByOption(this, option, "option",
289
        StatusEffectDB::getOptionMap());
290
    applyEffectByOption1(this, opt1, "opt1",
291
        StatusEffectDB::getOpt1Map());
292
    applyEffectByOption(this, opt2, "opt2",
293
        StatusEffectDB::getOpt2Map());
294
    applyEffectByOption(this, opt3, "opt3",
295
        StatusEffectDB::getOpt3Map());
296
}
297
298
void ActorSprite::setStatusEffectOpitons(const uint32_t option,
299
                                         const uint32_t opt1,
300
                                         const uint32_t opt2)
301
{
302
    applyEffectByOption(this, option, "option",
303
        StatusEffectDB::getOptionMap());
304
    applyEffectByOption1(this, opt1, "opt1",
305
        StatusEffectDB::getOpt1Map());
306
    applyEffectByOption(this, opt2, "opt2",
307
        StatusEffectDB::getOpt2Map());
308
}
309
310
void ActorSprite::setStatusEffectOpiton0(const uint32_t option)
311
{
312
    applyEffectByOption(this, option, "option",
313
        StatusEffectDB::getOptionMap());
314
}
315
316
void ActorSprite::updateStatusEffect(const int32_t index,
317
                                     const Enable newStatus,
318
                                     const IsStart start)
319
{
320
    StatusEffect *const effect = StatusEffectDB::getStatusEffect(
321
        index, newStatus);
322
    if (effect == nullptr)
323
        return;
324
    if (effect->mIsPoison && getType() == ActorType::Player)
325
        setPoison(newStatus == Enable_true);
326
    else if (effect->mIsCart && localPlayer == this)
327
        setHaveCart(newStatus == Enable_true);
328
    else if (effect->mIsRiding)
329
        setRiding(newStatus == Enable_true);
330
    else if (effect->mIsTrickDead)
331
        setTrickDead(newStatus == Enable_true);
332
    else if (effect->mIsPostDelay)
333
        stopCast(newStatus == Enable_true);
334
    handleStatusEffect(effect, index, newStatus, start);
335
}
336
337
void ActorSprite::handleStatusEffect(const StatusEffect *const effect,
338
                                     const int32_t effectId,
339
                                     const Enable newStatus,
340
                                     const IsStart start)
341
{
342
    if (effect == nullptr)
343
        return;
344
345
    if (newStatus == Enable_true)
346
    {
347
        if (effectId >= 0)
348
        {
349
            Particle *particle = nullptr;
350
            if (start == IsStart_true)
351
                particle = effect->getStartParticle();
352
            if (particle == nullptr)
353
                particle = effect->getParticle();
354
            if (particle != nullptr)
355
                mStatusParticleEffects.setLocally(effectId, particle);
356
        }
357
    }
358
    else
359
    {
360
        mStatusParticleEffects.delLocally(effectId);
361
    }
362
}
363
364
void ActorSprite::setupSpriteDisplay(const SpriteDisplay &display,
365
                                     const ForceDisplay forceDisplay,
366
                                     const DisplayTypeT displayType,
367
                                     const std::string &color)
368
{
369
    clear();
370
371
    FOR_EACH (SpriteRefs, it, display.sprites)
372
    {
373
        if (*it == nullptr)
374
            continue;
375
        const std::string file = pathJoin(paths.getStringValue("sprites"),
376
            combineDye3((*it)->sprite, color));
377
378
        const int variant = (*it)->variant;
379
        addSprite(AnimatedSprite::delayedLoad(file, variant));
380
    }
381
382
    // Ensure that something is shown, if desired
383
    if (mSprites.empty() && forceDisplay == ForceDisplay_true)
384
    {
385
        if (display.image.empty())
386
        {
387
            addSprite(AnimatedSprite::delayedLoad(pathJoin(
388
                paths.getStringValue("sprites"),
389
                paths.getStringValue("spriteErrorFile")),
390
                0));
391
        }
392
        else
393
        {
394
            std::string imagePath;
395
            switch (displayType)
396
            {
397
                case DisplayType::Item:
398
                default:
399
                    imagePath = pathJoin(paths.getStringValue("itemIcons"),
400
                        display.image);
401
                    break;
402
                case DisplayType::Floor:
403
                    imagePath = pathJoin(paths.getStringValue("itemIcons"),
404
                        display.floor);
405
                    break;
406
            }
407
            imagePath = combineDye2(imagePath, color);
408
            Image *img = Loader::getImage(imagePath);
409
410
            if (img == nullptr)
411
                img = Theme::getImageFromTheme("unknown-item.png");
412
413
            addSprite(new ImageSprite(img));
414
            if (img != nullptr)
415
                img->decRef();
416
        }
417
    }
418
419
    mChildParticleEffects.clear();
420
421
    // setup particle effects
422
    if (ParticleEngine::enabled && (particleEngine != nullptr))
423
    {
424
        FOR_EACH (StringVectCIter, itr, display.particles)
425
        {
426
            Particle *const p = particleEngine->addEffect(*itr, 0, 0, 0);
427
            controlAutoParticle(p);
428
        }
429
    }
430
431
    mMustResetParticles = true;
432
}
433
434
214
void ActorSprite::load()
435
{
436
214
    if (loaded)
437
        unload();
438
439
214
    initTargetCursor();
440
441
214
    loaded = true;
442
214
}
443
444
214
void ActorSprite::unload()
445
{
446

214
    if (reportTrue(!loaded))
447
        return;
448
449
214
    cleanupTargetCursors();
450
214
    loaded = false;
451
}
452
453
void ActorSprite::addActorSpriteListener(ActorSpriteListener *const listener)
454
{
455
    mActorSpriteListeners.push_front(listener);
456
}
457
458
void ActorSprite::removeActorSpriteListener(ActorSpriteListener *const
459
                                            listener)
460
{
461
    mActorSpriteListeners.remove(listener);
462
}
463
464
static const char *cursorType(const TargetCursorTypeT type)
465
{
466
1284
    switch (type)
467
    {
468
        case TargetCursorType::IN_RANGE:
469
            return "in-range";
470
        default:
471
        case TargetCursorType::NONE:
472
        case TargetCursorType::NUM_TCT:
473
        case TargetCursorType::NORMAL:
474
            return "normal";
475
    }
476
}
477
478
static const char *cursorSize(const TargetCursorSizeT size)
479
{
480
1284
    switch (size)
481
    {
482
        case TargetCursorSize::LARGE:
483
            return "l";
484
        case TargetCursorSize::MEDIUM:
485
            return "m";
486
        default:
487
        case TargetCursorSize::NUM_TC:
488
        case TargetCursorSize::SMALL:
489
            return "s";
490
    }
491
}
492
493
214
void ActorSprite::initTargetCursor()
494
{
495

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

2140
    for_each_cursors()
499
    {
500
1284
        targetCursor[type][size] = AnimatedSprite::load(
501
6420
            Theme::resolveThemePath(strprintf(
502
            targetCursorFile.c_str(),
503
            cursorType(static_cast<TargetCursorTypeT>(type)),
504
            cursorSize(static_cast<TargetCursorSizeT>(size)))),
505
            0);
506
    }
507
    end_foreach
508
214
}
509
510
214
void ActorSprite::cleanupTargetCursors()
511
{
512

1498
    for_each_cursors()
513
    {
514
1284
        delete2(targetCursor[type][size])
515
    }
516
    end_foreach
517
214
}
518
519
std::string ActorSprite::getStatusEffectsString() const
520
{
521
    std::string effectsStr;
522
    if (!mStatusEffects.empty())
523
    {
524
        FOR_EACH (std::set<int32_t>::const_iterator, it, mStatusEffects)
525
        {
526
            const StatusEffect *const effect =
527
                StatusEffectDB::getStatusEffect(
528
                *it,
529
                Enable_true);
530
            if (effect == nullptr)
531
                continue;
532
            if (!effectsStr.empty())
533
                effectsStr.append(", ");
534
            effectsStr.append(effect->mName);
535
        }
536
    }
537
    return effectsStr;
538
2
}