GCC Code Coverage Report
Directory: src/ Exec Total Coverage
File: src/being/actorsprite.cpp Lines: 42 225 18.7 %
Date: 2017-11-29 Branches: 30 255 11.8 %

Line Branch Exec Source
1
/*
2
 *  The ManaPlus Client
3
 *  Copyright (C) 2010  The Mana Developers
4
 *  Copyright (C) 2011-2017  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 "settings.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
static const unsigned int STATUS_EFFECTS = 32;
65
66
AnimatedSprite *ActorSprite::targetCursor
67
    [CAST_SIZE(TargetCursorType::NUM_TCT)]
68
    [CAST_SIZE(TargetCursorSize::NUM_TC)];
69
bool ActorSprite::loaded = false;
70
71
204
ActorSprite::ActorSprite(const BeingId id) :
72
    CompoundSprite(),
73
    Actor(),
74
    mStatusEffects(),
75
    mStatusParticleEffects(),
76
    mChildParticleEffects(&mStatusParticleEffects, false),
77
    mHorseId(0),
78
    mId(id),
79
    mUsedTargetCursor(nullptr),
80
    mActorSpriteListeners(),
81
    mCursorPaddingX(0),
82
    mCursorPaddingY(0),
83
    mMustResetParticles(false),
84
    mPoison(false),
85
    mHaveCart(false),
86

612
    mTrickDead(false)
87
{
88
204
}
89
90
816
ActorSprite::~ActorSprite()
91
{
92
204
    mChildParticleEffects.clear();
93
204
    mMustResetParticles = true;
94
95
204
    mUsedTargetCursor = nullptr;
96
97
408
    if (localPlayer != nullptr &&
98

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

384
    if (reportTrue(!loaded))
512
        return;
513
514
384
    cleanupTargetCursors();
515
384
    loaded = false;
516
}
517
518
void ActorSprite::addActorSpriteListener(ActorSpriteListener *const listener)
519
{
520
    mActorSpriteListeners.push_front(listener);
521
}
522
523
void ActorSprite::removeActorSpriteListener(ActorSpriteListener *const
524
                                            listener)
525
{
526
    mActorSpriteListeners.remove(listener);
527
}
528
529
static const char *cursorType(const TargetCursorTypeT type)
530
{
531
2304
    switch (type)
532
    {
533
        case TargetCursorType::IN_RANGE:
534
            return "in-range";
535
1152
        default:
536
        case TargetCursorType::NONE:
537
        case TargetCursorType::NUM_TCT:
538
        case TargetCursorType::NORMAL:
539
            return "normal";
540
    }
541
}
542
543
static const char *cursorSize(const TargetCursorSizeT size)
544
{
545
2304
    switch (size)
546
    {
547
        case TargetCursorSize::LARGE:
548
            return "l";
549
768
        case TargetCursorSize::MEDIUM:
550
            return "m";
551
768
        default:
552
        case TargetCursorSize::NUM_TC:
553
        case TargetCursorSize::SMALL:
554
            return "s";
555
    }
556
}
557
558
384
void ActorSprite::initTargetCursor()
559
{
560

388
    static const std::string targetCursorFile("target-cursor-%s-%s.xml");
561
562
    // Load target cursors
563

3840
    for_each_cursors()
564
    {
565
2304
        targetCursor[type][size] = AnimatedSprite::load(
566
11520
            Theme::resolveThemePath(strprintf(
567
            targetCursorFile.c_str(),
568
            cursorType(static_cast<TargetCursorTypeT>(type)),
569
            cursorSize(static_cast<TargetCursorSizeT>(size)))));
570
    }
571
    end_foreach
572
384
}
573
574
384
void ActorSprite::cleanupTargetCursors()
575
{
576

2688
    for_each_cursors()
577
    {
578
2304
        delete2(targetCursor[type][size])
579
    }
580
    end_foreach
581
384
}
582
583
std::string ActorSprite::getStatusEffectsString() const
584
{
585
    std::string effectsStr;
586
    if (!mStatusEffects.empty())
587
    {
588
        FOR_EACH (std::set<int32_t>::const_iterator, it, mStatusEffects)
589
        {
590
            const StatusEffect *const effect =
591
                StatusEffectDB::getStatusEffect(
592
                *it,
593
                Enable_true);
594
            if (effect == nullptr)
595
                continue;
596
            if (!effectsStr.empty())
597
                effectsStr.append(", ");
598
            effectsStr.append(effect->mName);
599
        }
600
    }
601
    return effectsStr;
602
4
}