~ubuntu-branches/ubuntu/trusty/manaplus/trusty-proposed

« back to all changes in this revision

Viewing changes to src/being/being.cpp

  • Committer: Package Import Robot
  • Author(s): Patrick Matthäi
  • Date: 2013-09-17 10:35:51 UTC
  • mfrom: (1.1.10)
  • Revision ID: package-import@ubuntu.com-20130917103551-az7p3nz9jgxwqjfn
Tags: 1.3.9.15-1
New upstream release.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 *  The ManaPlus Client
 
3
 *  Copyright (C) 2004-2009  The Mana World Development Team
 
4
 *  Copyright (C) 2009-2010  The Mana Developers
 
5
 *  Copyright (C) 2011-2013  The ManaPlus Developers
 
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/being.h"
 
24
 
 
25
#include "actorspritemanager.h"
 
26
#include "animatedsprite.h"
 
27
#include "beingequipbackend.h"
 
28
#include "client.h"
 
29
#include "configuration.h"
 
30
#include "effectmanager.h"
 
31
#include "guild.h"
 
32
#include "party.h"
 
33
#include "soundmanager.h"
 
34
#include "text.h"
 
35
 
 
36
#include "being/beingcacheentry.h"
 
37
#include "being/playerrelations.h"
 
38
 
 
39
#include "particle/particle.h"
 
40
 
 
41
#include "gui/equipmentwindow.h"
 
42
#include "gui/socialwindow.h"
 
43
#include "gui/speechbubble.h"
 
44
#include "gui/sdlfont.h"
 
45
#include "gui/skilldialog.h"
 
46
 
 
47
#include "net/charserverhandler.h"
 
48
#include "net/gamehandler.h"
 
49
#include "net/net.h"
 
50
#include "net/npchandler.h"
 
51
#include "net/playerhandler.h"
 
52
 
 
53
#include "resources/avatardb.h"
 
54
#include "resources/emotedb.h"
 
55
#include "resources/itemdb.h"
 
56
#include "resources/iteminfo.h"
 
57
#include "resources/monsterdb.h"
 
58
#include "resources/npcdb.h"
 
59
#include "resources/petdb.h"
 
60
#include "resources/resourcemanager.h"
 
61
 
 
62
#include "gui/widgets/langtab.h"
 
63
#include "gui/widgets/skilldata.h"
 
64
#include "gui/widgets/skillinfo.h"
 
65
 
 
66
#include "utils/gettext.h"
 
67
 
 
68
#include "debug.h"
 
69
 
 
70
const unsigned int CACHE_SIZE = 50;
 
71
 
 
72
int Being::mNumberOfHairstyles = 1;
 
73
int Being::mNumberOfRaces = 1;
 
74
 
 
75
int Being::mUpdateConfigTime = 0;
 
76
unsigned int Being::mConfLineLim = 0;
 
77
int Being::mSpeechType = 0;
 
78
bool Being::mHighlightMapPortals = false;
 
79
bool Being::mHighlightMonsterAttackRange = false;
 
80
bool Being::mLowTraffic = true;
 
81
bool Being::mDrawHotKeys = true;
 
82
bool Being::mShowBattleEvents = false;
 
83
bool Being::mShowMobHP = false;
 
84
bool Being::mShowOwnHP = false;
 
85
bool Being::mShowGender = false;
 
86
bool Being::mShowLevel = false;
 
87
bool Being::mShowPlayersStatus = false;
 
88
bool Being::mEnableReorderSprites = true;
 
89
bool Being::mHideErased = false;
 
90
bool Being::mMoveNames = false;
 
91
bool Being::mUseDiagonal = true;
 
92
int Being::mAwayEffect = -1;
 
93
 
 
94
std::list<BeingCacheEntry*> beingInfoCache;
 
95
typedef std::map<int, Guild*>::const_iterator GuildsMapCIter;
 
96
typedef std::map<int, int>::const_iterator IntMapCIter;
 
97
 
 
98
Being::Being(const int id, const Type type, const uint16_t subtype,
 
99
             Map *const map) :
 
100
    ActorSprite(id),
 
101
    mNextSound(),
 
102
    mInfo(BeingInfo::unknown),
 
103
    mEmotionSprite(nullptr),
 
104
    mAnimationEffect(nullptr),
 
105
    mSpriteAction(SpriteAction::STAND),
 
106
    mName(),
 
107
    mRaceName(),
 
108
    mPartyName(),
 
109
    mGuildName(),
 
110
    mSpeech(),
 
111
    mDispName(nullptr),
 
112
    mNameColor(nullptr),
 
113
    mEquippedWeapon(nullptr),
 
114
    mPath(),
 
115
    mText(nullptr),
 
116
    mTextColor(nullptr),
 
117
    mDest(),
 
118
    mSpriteColors(),
 
119
    mSpriteIDs(),
 
120
    mSpriteColorsIds(),
 
121
    mGuilds(),
 
122
    mParty(nullptr),
 
123
    mActionTime(0),
 
124
    mEmotionTime(0),
 
125
    mSpeechTime(0),
 
126
    mAttackSpeed(350),
 
127
    mLevel(0),
 
128
    mAttackRange(1),
 
129
    mGender(GENDER_UNSPECIFIED),
 
130
    mAction(STAND),
 
131
    mSubType(0xFFFF),
 
132
    mDirection(DOWN),
 
133
    mDirectionDelayed(0),
 
134
    mSpriteDirection(DIRECTION_DOWN),
 
135
    mShowName(false),
 
136
    mIsGM(false),
 
137
    mType(type),
 
138
    mSpeechBubble(new SpeechBubble),
 
139
    mWalkSpeed(Net::getPlayerHandler()->getDefaultWalkSpeed()),
 
140
    mSpeed(Net::getPlayerHandler()->getDefaultWalkSpeed().x),
 
141
    mIp(),
 
142
    mSpriteRemap(new int[20]),
 
143
    mSpriteHide(new int[20]),
 
144
    mComment(),
 
145
    mPet(nullptr),
 
146
    mOwner(nullptr),
 
147
    mSpecialParticle(nullptr),
 
148
    mX(0),
 
149
    mY(0),
 
150
    mDamageTaken(0),
 
151
    mHP(0),
 
152
    mMaxHP(0),
 
153
    mDistance(0),
 
154
    mIsReachable(REACH_UNKNOWN),
 
155
    mGoodStatus(-1),
 
156
    mMoveTime(0),
 
157
    mAttackTime(0),
 
158
    mTalkTime(0),
 
159
    mOtherTime(0),
 
160
    mTestTime(cur_time),
 
161
    mAttackDelay(0),
 
162
    mMinHit(0),
 
163
    mMaxHit(0),
 
164
    mCriticalHit(0),
 
165
    mPvpRank(0),
 
166
    mNumber(100),
 
167
    mPetId(0),
 
168
    mLook(0),
 
169
    mHairColor(0),
 
170
    mErased(false),
 
171
    mEnemy(false),
 
172
    mGotComment(false),
 
173
    mAdvanced(false),
 
174
    mShop(false),
 
175
    mAway(false),
 
176
    mInactive(false)
 
177
{
 
178
    for (int f = 0; f < 20; f ++)
 
179
    {
 
180
        mSpriteRemap[f] = f;
 
181
        mSpriteHide[f] = 0;
 
182
    }
 
183
 
 
184
    setMap(map);
 
185
    setSubtype(subtype, 0);
 
186
 
 
187
    if (mType == PLAYER)
 
188
        mShowName = config.getBoolValue("visiblenames");
 
189
    else if (mType != NPC)
 
190
        mGotComment = true;
 
191
 
 
192
    config.addListener("visiblenames", this);
 
193
 
 
194
    reReadConfig();
 
195
 
 
196
    if (mType == NPC)
 
197
        setShowName(true);
 
198
    else
 
199
        setShowName(mShowName);
 
200
 
 
201
    updateColors();
 
202
    updatePercentHP();
 
203
}
 
204
 
 
205
Being::~Being()
 
206
{
 
207
    config.removeListener("visiblenames", this);
 
208
 
 
209
    delete [] mSpriteRemap;
 
210
    mSpriteRemap = nullptr;
 
211
    delete [] mSpriteHide;
 
212
    mSpriteHide = nullptr;
 
213
 
 
214
    delete mSpeechBubble;
 
215
    mSpeechBubble = nullptr;
 
216
    delete mDispName;
 
217
    mDispName = nullptr;
 
218
    delete mText;
 
219
    mText = nullptr;
 
220
 
 
221
    delete mEmotionSprite;
 
222
    mEmotionSprite = nullptr;
 
223
    delete mAnimationEffect;
 
224
    mAnimationEffect = nullptr;
 
225
 
 
226
    if (mOwner)
 
227
        mOwner->setPet(nullptr);
 
228
    if (mPet)
 
229
        mPet->setOwner(nullptr);
 
230
}
 
231
 
 
232
void Being::setSubtype(const uint16_t subtype, const uint8_t look)
 
233
{
 
234
    if (!mInfo)
 
235
        return;
 
236
 
 
237
    if (subtype == mSubType && mLook == look)
 
238
        return;
 
239
 
 
240
    mSubType = subtype;
 
241
    mLook = look;
 
242
 
 
243
    if (mType == MONSTER)
 
244
    {
 
245
        mInfo = MonsterDB::get(mSubType);
 
246
        if (mInfo)
 
247
        {
 
248
            setName(mInfo->getName());
 
249
            setupSpriteDisplay(mInfo->getDisplay(), true, 0,
 
250
                mInfo->getColor(mLook));
 
251
            mYDiff = mInfo->getSortOffsetY();
 
252
        }
 
253
    }
 
254
    else if (mType == NPC)
 
255
    {
 
256
        mInfo = NPCDB::get(mSubType);
 
257
        if (mInfo)
 
258
        {
 
259
            setupSpriteDisplay(mInfo->getDisplay(), false);
 
260
            mYDiff = mInfo->getSortOffsetY();
 
261
        }
 
262
    }
 
263
    else if (mType == AVATAR)
 
264
    {
 
265
        mInfo = AvatarDB::get(mSubType);
 
266
        if (mInfo)
 
267
            setupSpriteDisplay(mInfo->getDisplay(), false);
 
268
    }
 
269
    else if (mType == PET)
 
270
    {
 
271
        mInfo = PETDB::get(mId);
 
272
        if (mInfo)
 
273
        {
 
274
            setupSpriteDisplay(mInfo->getDisplay(), false);
 
275
            mYDiff = mInfo->getSortOffsetY();
 
276
        }
 
277
    }
 
278
    else if (mType == PLAYER)
 
279
    {
 
280
        int id = -100 - subtype;
 
281
 
 
282
        // Prevent showing errors when sprite doesn't exist
 
283
        if (!ItemDB::exists(id))
 
284
        {
 
285
            id = -100;
 
286
            // TRANSLATORS: default race name
 
287
            setRaceName(_("Human"));
 
288
            if (Net::getCharServerHandler())
 
289
                setSprite(Net::getCharServerHandler()->baseSprite(), id);
 
290
        }
 
291
        else
 
292
        {
 
293
            const ItemInfo &info = ItemDB::get(id);
 
294
            setRaceName(info.getName());
 
295
            if (Net::getCharServerHandler())
 
296
            {
 
297
                setSprite(Net::getCharServerHandler()->baseSprite(),
 
298
                    id, info.getColor(mLook));
 
299
            }
 
300
        }
 
301
    }
 
302
}
 
303
 
 
304
ActorSprite::TargetCursorSize Being::getTargetCursorSize() const
 
305
{
 
306
    if (!mInfo)
 
307
        return ActorSprite::TC_SMALL;
 
308
 
 
309
    return mInfo->getTargetCursorSize();
 
310
}
 
311
 
 
312
void Being::setPosition(const Vector &pos)
 
313
{
 
314
    Actor::setPosition(pos);
 
315
 
 
316
    updateCoords();
 
317
 
 
318
    if (mText)
 
319
    {
 
320
        mText->adviseXY(static_cast<int>(pos.x), static_cast<int>(pos.y)
 
321
            - getHeight() - mText->getHeight() - 6, mMoveNames);
 
322
    }
 
323
}
 
324
 
 
325
void Being::setDestination(const int dstX, const int dstY)
 
326
{
 
327
    // We can't calculate anything without a map anyway.
 
328
    if (!mMap)
 
329
        return;
 
330
 
 
331
#ifdef MANASERV_SUPPORT
 
332
    if (Net::getNetworkType() != ServerInfo::MANASERV)
 
333
#endif
 
334
    {
 
335
        setPath(mMap->findPath(mX, mY, dstX, dstY, getWalkMask()));
 
336
        return;
 
337
    }
 
338
 
 
339
#ifdef MANASERV_SUPPORT
 
340
    // Don't handle flawed destinations from server...
 
341
    if (dstX == 0 || dstY == 0)
 
342
        return;
 
343
 
 
344
    // If the destination is unwalkable, don't bother trying to get there
 
345
    if (!mMap->getWalk(dstX / 32, dstY / 32))
 
346
        return;
 
347
 
 
348
    Position dest = mMap->checkNodeOffsets(getCollisionRadius(), getWalkMask(),
 
349
                                           dstX, dstY);
 
350
    Path thisPath = mMap->findPixelPath(static_cast<int>(mPos.x),
 
351
        static_cast<int>(mPos.y), dest.x, dest.y,
 
352
        static_cast<int>(getCollisionRadius()),
 
353
        static_cast<unsigned char>(getWalkMask()));
 
354
 
 
355
    if (thisPath.empty())
 
356
    {
 
357
        // If there is no path but the destination is on the same walkable tile,
 
358
        // we accept it.
 
359
        if (static_cast<int>(mPos.x) / 32 == dest.x / 32
 
360
            && static_cast<int>(mPos.y) / 32 == dest.y / 32)
 
361
        {
 
362
            mDest.x = static_cast<float>(dest.x);
 
363
            mDest.y = static_cast<float>(dest.y);
 
364
        }
 
365
        setPath(Path());
 
366
        return;
 
367
    }
 
368
 
 
369
    // The destination is valid, so we set it.
 
370
    mDest.x = static_cast<float>(dest.x);
 
371
    mDest.y = static_cast<float>(dest.y);
 
372
 
 
373
    setPath(thisPath);
 
374
#endif
 
375
}
 
376
 
 
377
void Being::clearPath()
 
378
{
 
379
    mPath.clear();
 
380
}
 
381
 
 
382
void Being::setPath(const Path &path)
 
383
{
 
384
    mPath = path;
 
385
    if (mPath.empty())
 
386
        return;
 
387
 
 
388
#ifdef MANASERV_SUPPORT
 
389
    if ((Net::getNetworkType() != ServerInfo::MANASERV) &&
 
390
        mAction != MOVE && mAction != DEAD)
 
391
#else
 
392
    if (mAction != MOVE && mAction != DEAD)
 
393
#endif
 
394
    {
 
395
        nextTile();
 
396
        mActionTime = tick_time;
 
397
    }
 
398
}
 
399
 
 
400
void Being::setSpeech(const std::string &text, const std::string &channel,
 
401
                      int time)
 
402
{
 
403
    if (!userPalette)
 
404
        return;
 
405
 
 
406
    if (!channel.empty() && (langChatTab && langChatTab->getChannelName()
 
407
        != channel))
 
408
    {
 
409
        return;
 
410
    }
 
411
 
 
412
    // Remove colors
 
413
    mSpeech = removeColors(text);
 
414
 
 
415
    // Trim whitespace
 
416
    trim(mSpeech);
 
417
 
 
418
    const unsigned int lineLim = mConfLineLim;
 
419
    if (lineLim > 0 && mSpeech.length() > lineLim)
 
420
        mSpeech = mSpeech.substr(0, lineLim);
 
421
 
 
422
    trim(mSpeech);
 
423
    if (mSpeech.empty())
 
424
        return;
 
425
 
 
426
    if (!time)
 
427
    {
 
428
        const size_t sz = mSpeech.size();
 
429
        if (sz < 200)
 
430
            time = static_cast<int>(SPEECH_TIME - 300 + (3 * sz));
 
431
    }
 
432
 
 
433
    if (time < static_cast<int>(SPEECH_MIN_TIME))
 
434
        time = static_cast<int>(SPEECH_MIN_TIME);
 
435
 
 
436
    // Check for links
 
437
    size_t start = mSpeech.find('[');
 
438
    size_t e = mSpeech.find(']', start);
 
439
 
 
440
    while (start != std::string::npos && e != std::string::npos)
 
441
    {
 
442
        // Catch multiple embeds and ignore them so it doesn't crash the client.
 
443
        while ((mSpeech.find('[', start + 1) != std::string::npos) &&
 
444
               (mSpeech.find('[', start + 1) < e))
 
445
        {
 
446
            start = mSpeech.find('[', start + 1);
 
447
        }
 
448
 
 
449
        size_t position = mSpeech.find('|');
 
450
        if (mSpeech[start + 1] == '@' && mSpeech[start + 2] == '@')
 
451
        {
 
452
            mSpeech.erase(e, 1);
 
453
            mSpeech.erase(start, (position - start) + 1);
 
454
        }
 
455
        position = mSpeech.find('@');
 
456
 
 
457
        while (position != std::string::npos)
 
458
        {
 
459
            mSpeech.erase(position, 2);
 
460
            position = mSpeech.find('@');
 
461
        }
 
462
 
 
463
        start = mSpeech.find('[', start + 1);
 
464
        e = mSpeech.find(']', start);
 
465
    }
 
466
 
 
467
    if (!mSpeech.empty())
 
468
    {
 
469
        mSpeechTime = time <= static_cast<int>(SPEECH_MAX_TIME)
 
470
            ? time : static_cast<int>(SPEECH_MAX_TIME);
 
471
    }
 
472
 
 
473
    const int speech = mSpeechType;
 
474
    if (speech == TEXT_OVERHEAD && userPalette)
 
475
    {
 
476
        delete mText;
 
477
 
 
478
        mText = new Text(mSpeech,
 
479
                         getPixelX(), getPixelY() - getHeight(),
 
480
                         gcn::Graphics::CENTER,
 
481
                         &userPalette->getColor(UserPalette::PARTICLE),
 
482
                         true);
 
483
    }
 
484
}
 
485
 
 
486
void Being::takeDamage(Being *const attacker, const int amount,
 
487
                       const AttackType type, const int attackId)
 
488
{
 
489
    if (!userPalette || !attacker)
 
490
        return;
 
491
 
 
492
    gcn::Font *font = nullptr;
 
493
    // TRANSLATORS: hit or miss message in attacks
 
494
    const std::string damage = amount ? toString(amount) : type == FLEE ?
 
495
            _("dodge") : _("miss");
 
496
    const gcn::Color *color;
 
497
 
 
498
    if (gui)
 
499
        font = gui->getInfoParticleFont();
 
500
 
 
501
    // Selecting the right color
 
502
    if (type == CRITICAL || type == FLEE)
 
503
    {
 
504
        if (type == CRITICAL)
 
505
            attacker->setCriticalHit(amount);
 
506
 
 
507
        if (attacker == player_node)
 
508
        {
 
509
            color = &userPalette->getColor(
 
510
                UserPalette::HIT_LOCAL_PLAYER_CRITICAL);
 
511
        }
 
512
        else
 
513
        {
 
514
            color = &userPalette->getColor(UserPalette::HIT_CRITICAL);
 
515
        }
 
516
    }
 
517
    else if (!amount)
 
518
    {
 
519
        if (attacker == player_node)
 
520
        {
 
521
            // This is intended to be the wrong direction to visually
 
522
            // differentiate between hits and misses
 
523
            color = &userPalette->getColor(UserPalette::HIT_LOCAL_PLAYER_MISS);
 
524
        }
 
525
        else
 
526
        {
 
527
            color = &userPalette->getColor(UserPalette::MISS);
 
528
        }
 
529
    }
 
530
    else if (mType == MONSTER)
 
531
    {
 
532
        if (attacker == player_node)
 
533
        {
 
534
            color = &userPalette->getColor(
 
535
                UserPalette::HIT_LOCAL_PLAYER_MONSTER);
 
536
        }
 
537
        else
 
538
        {
 
539
            color = &userPalette->getColor(
 
540
                UserPalette::HIT_PLAYER_MONSTER);
 
541
        }
 
542
    }
 
543
    else if (mType == PLAYER && attacker != player_node
 
544
             && this == player_node)
 
545
    {
 
546
        // here player was attacked by other player. mark him as enemy.
 
547
        color = &userPalette->getColor(UserPalette::HIT_PLAYER_PLAYER);
 
548
        attacker->setEnemy(true);
 
549
        attacker->updateColors();
 
550
    }
 
551
    else
 
552
    {
 
553
        color = &userPalette->getColor(UserPalette::HIT_MONSTER_PLAYER);
 
554
    }
 
555
 
 
556
    if (chatWindow && mShowBattleEvents)
 
557
    {
 
558
        if (this == player_node)
 
559
        {
 
560
            if (attacker->mType == PLAYER || amount)
 
561
            {
 
562
                chatWindow->battleChatLog(strprintf("%s : Hit you  -%d",
 
563
                    attacker->getName().c_str(), amount), BY_OTHER);
 
564
            }
 
565
        }
 
566
        else if (attacker == player_node && amount)
 
567
        {
 
568
            chatWindow->battleChatLog(strprintf("%s : You hit %s -%d",
 
569
                attacker->getName().c_str(), getName().c_str(), amount),
 
570
                BY_PLAYER);
 
571
        }
 
572
    }
 
573
    if (font && particleEngine)
 
574
    {
 
575
        // Show damage number
 
576
        particleEngine->addTextSplashEffect(damage,
 
577
            getPixelX(), getPixelY() - 16, color, font, true);
 
578
    }
 
579
 
 
580
    if (type != SKILL)
 
581
        attacker->updateHit(amount);
 
582
 
 
583
    if (amount > 0)
 
584
    {
 
585
        if (player_node && player_node == this)
 
586
            player_node->setLastHitFrom(attacker->getName());
 
587
 
 
588
        mDamageTaken += amount;
 
589
        if (mInfo)
 
590
        {
 
591
            playSfx(mInfo->getSound(SOUND_EVENT_HURT), this, false, mX, mY);
 
592
 
 
593
            if (!mInfo->isStaticMaxHP())
 
594
            {
 
595
                if (!mHP && mInfo->getMaxHP() < mDamageTaken)
 
596
                    mInfo->setMaxHP(mDamageTaken);
 
597
            }
 
598
        }
 
599
        if (mHP && isAlive())
 
600
        {
 
601
            mHP -= amount;
 
602
            if (mHP < 0)
 
603
                mHP = 0;
 
604
        }
 
605
 
 
606
        if (mType == MONSTER)
 
607
        {
 
608
            updatePercentHP();
 
609
            updateName();
 
610
        }
 
611
        else if (mType == PLAYER && socialWindow && getName() != "")
 
612
        {
 
613
            socialWindow->updateAvatar(getName());
 
614
        }
 
615
 
 
616
        if (effectManager)
 
617
        {
 
618
            const int hitEffectId = getHitEffect(attacker, type, attackId);
 
619
            if (hitEffectId >= 0)
 
620
                effectManager->trigger(hitEffectId, this);
 
621
        }
 
622
    }
 
623
    else
 
624
    {
 
625
        if (effectManager)
 
626
        {
 
627
            const int hitEffectId = getHitEffect(attacker,
 
628
                MISS, attackId);
 
629
            if (hitEffectId >= 0)
 
630
                effectManager->trigger(hitEffectId, this);
 
631
        }
 
632
    }
 
633
}
 
634
 
 
635
int Being::getHitEffect(const Being *const attacker,
 
636
                        const AttackType type, const int attackId) const
 
637
{
 
638
    if (!effectManager)
 
639
        return 0;
 
640
 
 
641
    // Init the particle effect path based on current
 
642
    // weapon or default.
 
643
    int hitEffectId = 0;
 
644
    if (type != SKILL)
 
645
    {
 
646
        if (attacker)
 
647
        {
 
648
            const ItemInfo *attackerWeapon = attacker->getEquippedWeapon();
 
649
            if (attackerWeapon && attacker->getType() == PLAYER)
 
650
            {
 
651
                if (type == MISS)
 
652
                    hitEffectId = attackerWeapon->getMissEffectId();
 
653
                else if (type != CRITICAL)
 
654
                    hitEffectId = attackerWeapon->getHitEffectId();
 
655
                else
 
656
                    hitEffectId = attackerWeapon->getCriticalHitEffectId();
 
657
            }
 
658
            else if (attacker->getType() == MONSTER)
 
659
            {
 
660
                const BeingInfo *const info = attacker->getInfo();
 
661
                if (info)
 
662
                {
 
663
                    const Attack *atk = info->getAttack(attackId);
 
664
                    if (atk)
 
665
                    {
 
666
                        if (type == MISS)
 
667
                            hitEffectId = atk->mMissEffectId;
 
668
                        else if (type != CRITICAL)
 
669
                            hitEffectId = atk->mHitEffectId;
 
670
                        else
 
671
                            hitEffectId = atk->mCriticalHitEffectId;
 
672
                    }
 
673
                    else
 
674
                    {
 
675
                        hitEffectId = getDefaultEffectId(type);
 
676
                    }
 
677
                }
 
678
            }
 
679
            else
 
680
            {
 
681
                hitEffectId = getDefaultEffectId(type);
 
682
            }
 
683
        }
 
684
        else
 
685
        {
 
686
            hitEffectId = getDefaultEffectId(type);
 
687
        }
 
688
    }
 
689
    else
 
690
    {
 
691
        // move skills effects to +100000 in effects list
 
692
        hitEffectId = attackId + 100000;
 
693
    }
 
694
    return hitEffectId;
 
695
}
 
696
 
 
697
int Being::getDefaultEffectId(const int type)
 
698
{
 
699
    if (type == MISS)
 
700
        return paths.getIntValue("missEffectId");
 
701
    else if (type != CRITICAL)
 
702
        return paths.getIntValue("hitEffectId");
 
703
    else
 
704
        return paths.getIntValue("criticalHitEffectId");
 
705
}
 
706
 
 
707
void Being::handleAttack(Being *const victim, const int damage,
 
708
                         const int attackId)
 
709
{
 
710
    if (!victim || !mInfo)
 
711
        return;
 
712
 
 
713
    if (this != player_node)
 
714
        setAction(Being::ATTACK, attackId);
 
715
 
 
716
    if (mType == PLAYER && mEquippedWeapon)
 
717
        fireMissile(victim, mEquippedWeapon->getMissileParticleFile());
 
718
    else if (mInfo->getAttack(attackId))
 
719
        fireMissile(victim, mInfo->getAttack(attackId)->mMissileParticle);
 
720
 
 
721
#ifdef MANASERV_SUPPORT
 
722
    if (Net::getNetworkType() != ServerInfo::MANASERV)
 
723
#endif
 
724
    {
 
725
        reset();
 
726
        mActionTime = tick_time;
 
727
    }
 
728
 
 
729
    if (this != player_node)
 
730
    {
 
731
        const uint8_t dir = calcDirection(victim->getTileX(),
 
732
            victim->getTileY());
 
733
        if (dir)
 
734
            setDirection(dir);
 
735
    }
 
736
    if (damage && victim->mType == PLAYER && victim->mAction == SIT)
 
737
        victim->setAction(STAND);
 
738
 
 
739
    if (mType == PLAYER)
 
740
    {
 
741
        if (mSpriteIDs.size() >= 10)
 
742
        {
 
743
            // here 10 is weapon slot
 
744
            int weaponId = mSpriteIDs[10];
 
745
            if (!weaponId)
 
746
                weaponId = -100 - mSubType;
 
747
            const ItemInfo &info = ItemDB::get(weaponId);
 
748
            playSfx(info.getSound((damage > 0) ?
 
749
                SOUND_EVENT_HIT : SOUND_EVENT_MISS), victim, true, mX, mY);
 
750
        }
 
751
    }
 
752
    else
 
753
    {
 
754
        playSfx(mInfo->getSound((damage > 0) ?
 
755
            SOUND_EVENT_HIT : SOUND_EVENT_MISS), victim, true, mX, mY);
 
756
    }
 
757
}
 
758
 
 
759
void Being::handleSkill(Being *const victim, const int damage,
 
760
                        const int skillId, const int skillLevel)
 
761
{
 
762
    if (!victim || !mInfo || !skillDialog)
 
763
        return;
 
764
 
 
765
    if (this != player_node)
 
766
        setAction(Being::ATTACK, 1);
 
767
 
 
768
    SkillInfo *const skill = skillDialog->getSkill(skillId);
 
769
    const SkillData *const data = skill
 
770
        ? skill->getData1(skillLevel) : nullptr;
 
771
    if (data)
 
772
        fireMissile(victim, data->particle);
 
773
 
 
774
#ifdef MANASERV_SUPPORT
 
775
    if (Net::getNetworkType() != ServerInfo::MANASERV)
 
776
#endif
 
777
    {
 
778
        reset();
 
779
        mActionTime = tick_time;
 
780
    }
 
781
 
 
782
    if (this != player_node)
 
783
    {
 
784
        const uint8_t dir = calcDirection(victim->getTileX(),
 
785
            victim->getTileY());
 
786
        if (dir)
 
787
            setDirection(dir);
 
788
    }
 
789
    if (damage && victim->mType == PLAYER && victim->mAction == SIT)
 
790
        victim->setAction(STAND);
 
791
    if (data)
 
792
    {
 
793
        if (damage > 0)
 
794
            playSfx(data->soundHit, victim, true, mX, mY);
 
795
        else
 
796
            playSfx(data->soundMiss, victim, true, mX, mY);
 
797
    }
 
798
    else
 
799
    {
 
800
        playSfx(mInfo->getSound((damage > 0) ?
 
801
            SOUND_EVENT_HIT : SOUND_EVENT_MISS), victim, true, mX, mY);
 
802
    }
 
803
}
 
804
 
 
805
void Being::setName(const std::string &name)
 
806
{
 
807
    if (mType == NPC)
 
808
    {
 
809
        mName = name.substr(0, name.find('#', 0));
 
810
        showName();
 
811
    }
 
812
    else
 
813
    {
 
814
        mName = name;
 
815
 
 
816
        if (mType == PLAYER && getShowName())
 
817
            showName();
 
818
    }
 
819
}
 
820
 
 
821
void Being::setShowName(const bool doShowName)
 
822
{
 
823
    if (mShowName == doShowName)
 
824
        return;
 
825
 
 
826
    mShowName = doShowName;
 
827
 
 
828
    if (doShowName)
 
829
    {
 
830
        showName();
 
831
    }
 
832
    else
 
833
    {
 
834
        delete mDispName;
 
835
        mDispName = nullptr;
 
836
    }
 
837
}
 
838
 
 
839
void Being::setGuildName(const std::string &name)
 
840
{
 
841
    mGuildName = name;
 
842
}
 
843
 
 
844
void Being::setGuildPos(const std::string &pos A_UNUSED)
 
845
{
 
846
}
 
847
 
 
848
void Being::addGuild(Guild *const guild)
 
849
{
 
850
    if (!guild)
 
851
        return;
 
852
 
 
853
    mGuilds[guild->getId()] = guild;
 
854
 
 
855
    if (this == player_node && socialWindow)
 
856
        socialWindow->addTab(guild);
 
857
}
 
858
 
 
859
void Being::removeGuild(const int id)
 
860
{
 
861
    if (this == player_node && socialWindow)
 
862
        socialWindow->removeTab(mGuilds[id]);
 
863
 
 
864
    if (mGuilds[id])
 
865
        mGuilds[id]->removeMember(getName());
 
866
    mGuilds.erase(id);
 
867
}
 
868
 
 
869
Guild *Being::getGuild(const std::string &guildName) const
 
870
{
 
871
    FOR_EACH (GuildsMapCIter, itr, mGuilds)
 
872
    {
 
873
        Guild *const guild = itr->second;
 
874
        if (guild && guild->getName() == guildName)
 
875
            return guild;
 
876
    }
 
877
 
 
878
    return nullptr;
 
879
}
 
880
 
 
881
Guild *Being::getGuild(const int id) const
 
882
{
 
883
    const std::map<int, Guild*>::const_iterator itr = mGuilds.find(id);
 
884
    if (itr != mGuilds.end())
 
885
        return itr->second;
 
886
 
 
887
    return nullptr;
 
888
}
 
889
 
 
890
Guild *Being::getGuild() const
 
891
{
 
892
    const std::map<int, Guild*>::const_iterator itr = mGuilds.begin();
 
893
    if (itr != mGuilds.end())
 
894
        return itr->second;
 
895
 
 
896
    return nullptr;
 
897
}
 
898
 
 
899
void Being::clearGuilds()
 
900
{
 
901
    FOR_EACH (GuildsMapCIter, itr, mGuilds)
 
902
    {
 
903
        Guild *const guild = itr->second;
 
904
 
 
905
        if (guild)
 
906
        {
 
907
            if (this == player_node && socialWindow)
 
908
                socialWindow->removeTab(guild);
 
909
 
 
910
            guild->removeMember(mId);
 
911
        }
 
912
    }
 
913
 
 
914
    mGuilds.clear();
 
915
}
 
916
 
 
917
void Being::setParty(Party *const party)
 
918
{
 
919
    if (party == mParty)
 
920
        return;
 
921
 
 
922
    Party *const old = mParty;
 
923
    mParty = party;
 
924
 
 
925
    if (old)
 
926
        old->removeMember(mId);
 
927
 
 
928
    if (party)
 
929
        party->addMember(mId, mName);
 
930
 
 
931
    updateColors();
 
932
 
 
933
    if (this == player_node && socialWindow)
 
934
    {
 
935
        if (old)
 
936
            socialWindow->removeTab(old);
 
937
 
 
938
        if (party)
 
939
            socialWindow->addTab(party);
 
940
    }
 
941
}
 
942
 
 
943
void Being::updateGuild()
 
944
{
 
945
    if (!player_node)
 
946
        return;
 
947
 
 
948
    Guild *const guild = player_node->getGuild();
 
949
    if (!guild)
 
950
    {
 
951
        clearGuilds();
 
952
        updateColors();
 
953
        return;
 
954
    }
 
955
    if (guild->getMember(getName()))
 
956
    {
 
957
        setGuild(guild);
 
958
        if (!guild->getName().empty())
 
959
            mGuildName = guild->getName();
 
960
    }
 
961
    updateColors();
 
962
}
 
963
 
 
964
void Being::setGuild(Guild *const guild)
 
965
{
 
966
    Guild *const old = getGuild();
 
967
    if (guild == old)
 
968
        return;
 
969
 
 
970
    clearGuilds();
 
971
    addGuild(guild);
 
972
 
 
973
    if (old)
 
974
        old->removeMember(mName);
 
975
 
 
976
    updateColors();
 
977
 
 
978
    if (this == player_node && socialWindow)
 
979
    {
 
980
        if (old)
 
981
            socialWindow->removeTab(old);
 
982
 
 
983
        if (guild)
 
984
            socialWindow->addTab(guild);
 
985
    }
 
986
}
 
987
 
 
988
void Being::fireMissile(Being *const victim, const std::string &particle) const
 
989
{
 
990
    if (!victim || particle.empty() || !particleEngine)
 
991
        return;
 
992
 
 
993
    Particle *const target = particleEngine->createChild();
 
994
 
 
995
    if (!target)
 
996
        return;
 
997
 
 
998
    Particle *const missile = target->addEffect(
 
999
        particle, getPixelX(), getPixelY());
 
1000
 
 
1001
    if (missile)
 
1002
    {
 
1003
        target->moveBy(Vector(0.0f, 0.0f, 32.0f));
 
1004
        target->setLifetime(1000);
 
1005
        victim->controlParticle(target);
 
1006
 
 
1007
        missile->setDestination(target, 7, 0);
 
1008
        missile->setDieDistance(8);
 
1009
        missile->setLifetime(900);
 
1010
    }
 
1011
}
 
1012
 
 
1013
std::string Being::getSitAction() const
 
1014
{
 
1015
    if (serverVersion < 0)
 
1016
    {
 
1017
        return SpriteAction::SIT;
 
1018
    }
 
1019
    else
 
1020
    {
 
1021
        if (mMap)
 
1022
        {
 
1023
            const unsigned char mask = mMap->getBlockMask(mX, mY);
 
1024
            if (mask & Map::BLOCKMASK_GROUNDTOP)
 
1025
                return SpriteAction::SITTOP;
 
1026
            else if (mask & Map::BLOCKMASK_AIR)
 
1027
                return SpriteAction::SITSKY;
 
1028
            else if (mask & Map::BLOCKMASK_WATER)
 
1029
                return SpriteAction::SITWATER;
 
1030
        }
 
1031
        return SpriteAction::SIT;
 
1032
    }
 
1033
}
 
1034
 
 
1035
 
 
1036
std::string Being::getMoveAction() const
 
1037
{
 
1038
    if (serverVersion < 0)
 
1039
    {
 
1040
        return SpriteAction::MOVE;
 
1041
    }
 
1042
    else
 
1043
    {
 
1044
        if (mMap)
 
1045
        {
 
1046
            const unsigned char mask = mMap->getBlockMask(mX, mY);
 
1047
            if (mask & Map::BLOCKMASK_AIR)
 
1048
                return SpriteAction::FLY;
 
1049
            else if (mask & Map::BLOCKMASK_WATER)
 
1050
                return SpriteAction::SWIM;
 
1051
        }
 
1052
        return SpriteAction::MOVE;
 
1053
    }
 
1054
}
 
1055
 
 
1056
std::string Being::getWeaponAttackAction(const ItemInfo *const weapon) const
 
1057
{
 
1058
    if (!weapon)
 
1059
        return SpriteAction::ATTACK;
 
1060
 
 
1061
    if (serverVersion < 0 || !weapon)
 
1062
    {
 
1063
        return weapon->getAttackAction();
 
1064
    }
 
1065
    else
 
1066
    {
 
1067
        if (mMap)
 
1068
        {
 
1069
            const unsigned char mask = mMap->getBlockMask(mX, mY);
 
1070
            if (mask & Map::BLOCKMASK_AIR)
 
1071
                return weapon->getSkyAttackAction();
 
1072
            else if (mask & Map::BLOCKMASK_WATER)
 
1073
                return weapon->getWaterAttackAction();
 
1074
        }
 
1075
        return weapon->getAttackAction();
 
1076
    }
 
1077
}
 
1078
 
 
1079
std::string Being::getAttackAction(const Attack *const attack1) const
 
1080
{
 
1081
    if (!attack1)
 
1082
        return SpriteAction::ATTACK;
 
1083
 
 
1084
    if (serverVersion < 0 || !attack1)
 
1085
    {
 
1086
        return attack1->mAction;
 
1087
    }
 
1088
    else
 
1089
    {
 
1090
        if (mMap)
 
1091
        {
 
1092
            const unsigned char mask = mMap->getBlockMask(mX, mY);
 
1093
            if (mask & Map::BLOCKMASK_AIR)
 
1094
                return attack1->mSkyAction;
 
1095
            else if (mask & Map::BLOCKMASK_WATER)
 
1096
                return attack1->mWaterAction;
 
1097
        }
 
1098
        return attack1->mAction;
 
1099
    }
 
1100
}
 
1101
 
 
1102
#define getSpriteAction(func, action) \
 
1103
    std::string Being::get##func##Action() const \
 
1104
{ \
 
1105
    if (serverVersion < 0) \
 
1106
    { \
 
1107
        return SpriteAction::action; \
 
1108
    } \
 
1109
    else \
 
1110
    { \
 
1111
        if (mMap) \
 
1112
        { \
 
1113
            const unsigned char mask = mMap->getBlockMask(mX, mY); \
 
1114
            if (mask & Map::BLOCKMASK_AIR) \
 
1115
                return SpriteAction::action##SKY; \
 
1116
            else if (mask & Map::BLOCKMASK_WATER) \
 
1117
                return SpriteAction::action##WATER; \
 
1118
        } \
 
1119
        return SpriteAction::action; \
 
1120
    } \
 
1121
}
 
1122
 
 
1123
getSpriteAction(Dead, DEAD)
 
1124
getSpriteAction(Stand, STAND)
 
1125
getSpriteAction(Spawn, SPAWN)
 
1126
 
 
1127
void Being::setAction(const Action &action, const int attackId)
 
1128
{
 
1129
    std::string currentAction = SpriteAction::INVALID;
 
1130
 
 
1131
    switch (action)
 
1132
    {
 
1133
        case MOVE:
 
1134
            if (mInfo)
 
1135
            {
 
1136
                playSfx(mInfo->getSound(
 
1137
                    SOUND_EVENT_MOVE), nullptr, true, mX, mY);
 
1138
            }
 
1139
            currentAction = getMoveAction();
 
1140
            // Note: When adding a run action,
 
1141
            // Differentiate walk and run with action name,
 
1142
            // while using only the ACTION_MOVE.
 
1143
            break;
 
1144
        case SIT:
 
1145
            currentAction = getSitAction();
 
1146
            if (mInfo)
 
1147
            {
 
1148
                SoundEvent event;
 
1149
                if (currentAction == SpriteAction::SITTOP)
 
1150
                    event = SOUND_EVENT_SITTOP;
 
1151
                else
 
1152
                    event = SOUND_EVENT_SIT;
 
1153
                playSfx(mInfo->getSound(event), nullptr, true, mX, mY);
 
1154
            }
 
1155
            break;
 
1156
        case ATTACK:
 
1157
            if (mEquippedWeapon)
 
1158
            {
 
1159
                currentAction = getWeaponAttackAction(mEquippedWeapon);
 
1160
                reset();
 
1161
            }
 
1162
            else
 
1163
            {
 
1164
                if (!mInfo || !mInfo->getAttack(attackId))
 
1165
                    break;
 
1166
 
 
1167
                currentAction = getAttackAction(mInfo->getAttack(attackId));
 
1168
                reset();
 
1169
 
 
1170
                // attack particle effect
 
1171
                if (Particle::enabled)
 
1172
                {
 
1173
                    const int effectId = mInfo->getAttack(attackId)->mEffectId;
 
1174
 
 
1175
                    int rotation;
 
1176
                    switch (mSpriteDirection)
 
1177
                    {
 
1178
                        case DIRECTION_DOWN:
 
1179
                        default:
 
1180
                            rotation = 0;
 
1181
                            break;
 
1182
                        case DIRECTION_LEFT:
 
1183
                            rotation = 90;
 
1184
                            break;
 
1185
                        case DIRECTION_UP:
 
1186
                            rotation = 180;
 
1187
                            break;
 
1188
                        case DIRECTION_RIGHT:
 
1189
                            rotation = 270;
 
1190
                            break;
 
1191
                    }
 
1192
                    if (Particle::enabled && effectManager && effectId >= 0)
 
1193
                        effectManager->trigger(effectId, this, rotation);
 
1194
                }
 
1195
            }
 
1196
            break;
 
1197
        case HURT:
 
1198
            if (mInfo)
 
1199
            {
 
1200
                playSfx(mInfo->getSound(SOUND_EVENT_HURT),
 
1201
                    this, false, mX, mY);
 
1202
            }
 
1203
            break;
 
1204
        case DEAD:
 
1205
            currentAction = getDeadAction();
 
1206
            if (mInfo)
 
1207
            {
 
1208
                playSfx(mInfo->getSound(SOUND_EVENT_DIE), this, false, mX, mY);
 
1209
                if (mType == MONSTER || mType == NPC)
 
1210
                    mYDiff = mInfo->getDeadSortOffsetY();
 
1211
            }
 
1212
            break;
 
1213
        case STAND:
 
1214
            currentAction = getStandAction();
 
1215
            break;
 
1216
        case SPAWN:
 
1217
            if (mInfo)
 
1218
            {
 
1219
                playSfx(mInfo->getSound(SOUND_EVENT_SPAWN),
 
1220
                    nullptr, true, mX, mY);
 
1221
            }
 
1222
            currentAction = getSpawnAction();
 
1223
            break;
 
1224
        default:
 
1225
            logger->log("Being::setAction unknown action: "
 
1226
                + toString(static_cast<unsigned>(action)));
 
1227
            break;
 
1228
    }
 
1229
 
 
1230
    if (currentAction != SpriteAction::INVALID)
 
1231
    {
 
1232
        mSpriteAction = currentAction;
 
1233
        play(currentAction);
 
1234
        if (mEmotionSprite)
 
1235
            mEmotionSprite->play(currentAction);
 
1236
        if (mAnimationEffect)
 
1237
            mAnimationEffect->play(currentAction);
 
1238
        mAction = action;
 
1239
    }
 
1240
 
 
1241
    if (currentAction != SpriteAction::MOVE
 
1242
        && currentAction != SpriteAction::FLY
 
1243
        && currentAction != SpriteAction::SWIM)
 
1244
    {
 
1245
        mActionTime = tick_time;
 
1246
    }
 
1247
}
 
1248
 
 
1249
void Being::setDirection(const uint8_t direction)
 
1250
{
 
1251
    if (mDirection == direction)
 
1252
        return;
 
1253
 
 
1254
    mDirection = direction;
 
1255
 
 
1256
    mDirectionDelayed = 0;
 
1257
 
 
1258
    // if the direction does not change much, keep the common component
 
1259
    int mFaceDirection = mDirection & direction;
 
1260
    if (!mFaceDirection)
 
1261
        mFaceDirection = direction;
 
1262
 
 
1263
    SpriteDirection dir;
 
1264
    if (mFaceDirection & UP)
 
1265
    {
 
1266
        if (mFaceDirection & LEFT)
 
1267
            dir = DIRECTION_UPLEFT;
 
1268
        else if (mFaceDirection & RIGHT)
 
1269
            dir = DIRECTION_UPRIGHT;
 
1270
        else
 
1271
            dir = DIRECTION_UP;
 
1272
    }
 
1273
    else if (mFaceDirection & DOWN)
 
1274
    {
 
1275
        if (mFaceDirection & LEFT)
 
1276
            dir = DIRECTION_DOWNLEFT;
 
1277
        else if (mFaceDirection & RIGHT)
 
1278
            dir = DIRECTION_DOWNRIGHT;
 
1279
        else
 
1280
            dir = DIRECTION_DOWN;
 
1281
    }
 
1282
    else if (mFaceDirection & RIGHT)
 
1283
    {
 
1284
        dir = DIRECTION_RIGHT;
 
1285
    }
 
1286
    else
 
1287
    {
 
1288
        dir = DIRECTION_LEFT;
 
1289
    }
 
1290
    mSpriteDirection = static_cast<uint8_t>(dir);
 
1291
 
 
1292
    CompoundSprite::setSpriteDirection(dir);
 
1293
    if (mEmotionSprite)
 
1294
        mEmotionSprite->setSpriteDirection(dir);
 
1295
    if (mAnimationEffect)
 
1296
        mAnimationEffect->setSpriteDirection(dir);
 
1297
    recalcSpritesOrder();
 
1298
}
 
1299
 
 
1300
uint8_t Being::calcDirection() const
 
1301
{
 
1302
    uint8_t dir = 0;
 
1303
    if (mDest.x > mX)
 
1304
        dir |= RIGHT;
 
1305
    else if (mDest.x < mX)
 
1306
        dir |= LEFT;
 
1307
    if (mDest.y > mY)
 
1308
        dir |= DOWN;
 
1309
    else if (mDest.y < mY)
 
1310
        dir |= UP;
 
1311
    return dir;
 
1312
}
 
1313
 
 
1314
uint8_t Being::calcDirection(const int dstX, const int dstY) const
 
1315
{
 
1316
    uint8_t dir = 0;
 
1317
    if (dstX > mX)
 
1318
        dir |= RIGHT;
 
1319
    else if (dstX < mX)
 
1320
        dir |= LEFT;
 
1321
    if (dstY > mY)
 
1322
        dir |= DOWN;
 
1323
    else if (dstY < mY)
 
1324
        dir |= UP;
 
1325
    return dir;
 
1326
}
 
1327
 
 
1328
void Being::nextTile()
 
1329
{
 
1330
    if (mPath.empty())
 
1331
    {
 
1332
        setAction(STAND);
 
1333
        return;
 
1334
    }
 
1335
 
 
1336
    const Position pos = mPath.front();
 
1337
    mPath.pop_front();
 
1338
 
 
1339
    const uint8_t dir = calcDirection(pos.x, pos.y);
 
1340
    if (dir)
 
1341
        setDirection(dir);
 
1342
 
 
1343
    if (!mMap || !mMap->getWalk(pos.x, pos.y, getWalkMask()))
 
1344
    {
 
1345
        setAction(STAND);
 
1346
        return;
 
1347
    }
 
1348
 
 
1349
    mActionTime += static_cast<int>(mSpeed / 10);
 
1350
    if ((mType != PLAYER || mUseDiagonal) && mX != pos.x && mY != pos.y)
 
1351
        mSpeed = mWalkSpeed.x * 1.4;
 
1352
    else
 
1353
        mSpeed = mWalkSpeed.x;
 
1354
 
 
1355
    mX = pos.x;
 
1356
    mY = pos.y;
 
1357
    setAction(MOVE);
 
1358
}
 
1359
 
 
1360
void Being::logic()
 
1361
{
 
1362
    BLOCK_START("Being::logic")
 
1363
    // Reduce the time that speech is still displayed
 
1364
    if (mSpeechTime > 0)
 
1365
        mSpeechTime--;
 
1366
 
 
1367
    // Remove text and speechbubbles if speech boxes aren't being used
 
1368
    if (mSpeechTime == 0 && mText)
 
1369
    {
 
1370
        delete mText;
 
1371
        mText = nullptr;
 
1372
    }
 
1373
 
 
1374
    const int time = tick_time * MILLISECONDS_IN_A_TICK;
 
1375
    if (mEmotionSprite)
 
1376
        mEmotionSprite->update(time);
 
1377
 
 
1378
    if (mAnimationEffect)
 
1379
    {
 
1380
        mAnimationEffect->update(time);
 
1381
        if (mAnimationEffect->isTerminated())
 
1382
        {
 
1383
            delete mAnimationEffect;
 
1384
            mAnimationEffect = nullptr;
 
1385
        }
 
1386
    }
 
1387
 
 
1388
    int frameCount = static_cast<int>(getFrameCount());
 
1389
#ifdef MANASERV_SUPPORT
 
1390
    if ((Net::getNetworkType() == ServerInfo::MANASERV) && (mAction != DEAD))
 
1391
    {
 
1392
        const Vector dest = (mPath.empty()) ?
 
1393
            mDest : Vector(static_cast<float>(mPath.front().x),
 
1394
                           static_cast<float>(mPath.front().y));
 
1395
 
 
1396
        // This is a hack that stops NPCs from running off the map...
 
1397
        if (mDest.x <= 0 && mDest.y <= 0)
 
1398
        {
 
1399
            BLOCK_END("Being::logic")
 
1400
            return;
 
1401
        }
 
1402
 
 
1403
        // The Vector representing the difference between current position
 
1404
        // and the next destination path node.
 
1405
        Vector dir = dest - mPos;
 
1406
 
 
1407
        const float nominalLength = dir.length();
 
1408
 
 
1409
        // When we've not reached our destination, move to it.
 
1410
        if (nominalLength > 0.0f && !mWalkSpeed.isNull())
 
1411
        {
 
1412
            // The deplacement of a point along a vector is calculated
 
1413
            // using the Unit Vector (â) multiplied by the point speed.
 
1414
            // â = a / ||a|| (||a|| is the a length.)
 
1415
            // Then, diff = (dir/||dir||) * speed.
 
1416
            const Vector normalizedDir = dir.normalized();
 
1417
            Vector diff(normalizedDir.x * mWalkSpeed.x,
 
1418
                        normalizedDir.y * mWalkSpeed.y);
 
1419
 
 
1420
            // Test if we don't miss the destination by a move too far:
 
1421
            if (diff.length() > nominalLength)
 
1422
            {
 
1423
                setPosition(mPos + dir);
 
1424
 
 
1425
                // Also, if the destination is reached, try to get the next
 
1426
                // path point, if existing.
 
1427
                if (!mPath.empty())
 
1428
                    mPath.pop_front();
 
1429
            }
 
1430
            // Otherwise, go to it using the nominal speed.
 
1431
            else
 
1432
            {
 
1433
                setPosition(mPos + diff);
 
1434
            }
 
1435
 
 
1436
            if (mAction != MOVE)
 
1437
                setAction(MOVE);
 
1438
 
 
1439
            // Update the player sprite direction.
 
1440
            // N.B.: We only change this if the distance is more than one pixel.
 
1441
            if (nominalLength > 1.0f)
 
1442
            {
 
1443
                int direction = 0;
 
1444
                const float dx = std::abs(dir.x);
 
1445
                float dy = std::abs(dir.y);
 
1446
 
 
1447
                // When not using mouse for the player, we slightly prefer
 
1448
                // UP and DOWN position, especially when walking diagonally.
 
1449
                if (player_node && this == player_node &&
 
1450
                    !player_node->isPathSetByMouse())
 
1451
                {
 
1452
                    dy = dy + 2;
 
1453
                }
 
1454
 
 
1455
                if (dx > dy)
 
1456
                     direction |= (dir.x > 0) ? RIGHT : LEFT;
 
1457
                else
 
1458
                     direction |= (dir.y > 0) ? DOWN : UP;
 
1459
 
 
1460
                setDirection(static_cast<uint8_t>(direction));
 
1461
            }
 
1462
        }
 
1463
        else if (!mPath.empty())
 
1464
        {
 
1465
            // If the current path node has been reached,
 
1466
            // remove it and go to the next one.
 
1467
            mPath.pop_front();
 
1468
        }
 
1469
        else if (mAction == MOVE)
 
1470
        {
 
1471
            setAction(STAND);
 
1472
        }
 
1473
    }
 
1474
    else
 
1475
    if (Net::getNetworkType() != ServerInfo::MANASERV)
 
1476
#endif
 
1477
    {
 
1478
        switch (mAction)
 
1479
        {
 
1480
            case STAND:
 
1481
            case SIT:
 
1482
            case DEAD:
 
1483
            case HURT:
 
1484
            case SPAWN:
 
1485
            default:
 
1486
               break;
 
1487
 
 
1488
            case MOVE:
 
1489
            {
 
1490
                if (static_cast<float>(get_elapsed_time(
 
1491
                    mActionTime)) >= mSpeed)
 
1492
                {
 
1493
                    nextTile();
 
1494
                }
 
1495
                break;
 
1496
            }
 
1497
 
 
1498
            case ATTACK:
 
1499
            {
 
1500
                if (!mActionTime)
 
1501
                    break;
 
1502
 
 
1503
                int curFrame = 0;
 
1504
                if (mAttackSpeed)
 
1505
                {
 
1506
                    curFrame = (get_elapsed_time(mActionTime) * frameCount)
 
1507
                        / mAttackSpeed;
 
1508
                }
 
1509
 
 
1510
                if (this == player_node && curFrame >= frameCount)
 
1511
                    nextTile();
 
1512
 
 
1513
                break;
 
1514
            }
 
1515
        }
 
1516
 
 
1517
        // Update pixel coordinates
 
1518
        setPosition(static_cast<float>(mX * 32 + 16 + getXOffset()),
 
1519
                    static_cast<float>(mY * 32 + 32 + getYOffset()));
 
1520
    }
 
1521
 
 
1522
    if (mEmotionSprite)
 
1523
    {
 
1524
        mEmotionTime--;
 
1525
        if (mEmotionTime == 0)
 
1526
        {
 
1527
            delete mEmotionSprite;
 
1528
            mEmotionSprite = nullptr;
 
1529
        }
 
1530
    }
 
1531
 
 
1532
    ActorSprite::logic();
 
1533
 
 
1534
    if (frameCount < 10)
 
1535
        frameCount = 10;
 
1536
 
 
1537
    if (!isAlive() && mSpeed && Net::getGameHandler()->removeDeadBeings()
 
1538
        && static_cast<int> ((static_cast<float>(get_elapsed_time(mActionTime))
 
1539
        / mSpeed)) >= frameCount)
 
1540
    {
 
1541
        if (mType != PLAYER && actorSpriteManager)
 
1542
            actorSpriteManager->destroy(this);
 
1543
    }
 
1544
 
 
1545
    const SoundInfo *const sound = mNextSound.sound;
 
1546
    if (sound)
 
1547
    {
 
1548
        const int time2 = tick_time;
 
1549
        if (time2 > mNextSound.time)
 
1550
        {
 
1551
            soundManager.playSfx(sound->sound, mNextSound.x, mNextSound.y);
 
1552
 
 
1553
            mNextSound.sound = nullptr;
 
1554
            mNextSound.time = time2 + sound->delay;
 
1555
        }
 
1556
    }
 
1557
 
 
1558
    BLOCK_END("Being::logic")
 
1559
}
 
1560
 
 
1561
void Being::drawEmotion(Graphics *const graphics, const int offsetX,
 
1562
                        const int offsetY)
 
1563
{
 
1564
    const int px = getPixelX() - offsetX - 16;
 
1565
    const int py = getPixelY() - offsetY - 64 - 32;
 
1566
    if (mEmotionSprite)
 
1567
        mEmotionSprite->draw(graphics, px, py);
 
1568
    if (mAnimationEffect)
 
1569
        mAnimationEffect->draw(graphics, px, py);
 
1570
}
 
1571
 
 
1572
void Being::drawSpeech(const int offsetX, const int offsetY)
 
1573
{
 
1574
    if (!mSpeechBubble || mSpeech.empty())
 
1575
        return;
 
1576
 
 
1577
    const int px = getPixelX() - offsetX;
 
1578
    const int py = getPixelY() - offsetY;
 
1579
    const int speech = mSpeechType;
 
1580
 
 
1581
    // Draw speech above this being
 
1582
    if (mSpeechTime == 0)
 
1583
    {
 
1584
        if (mSpeechBubble->isVisible())
 
1585
            mSpeechBubble->setVisible(false);
 
1586
    }
 
1587
    else if (mSpeechTime > 0 && (speech == NAME_IN_BUBBLE ||
 
1588
             speech == NO_NAME_IN_BUBBLE))
 
1589
    {
 
1590
        const bool isShowName = (speech == NAME_IN_BUBBLE);
 
1591
 
 
1592
        delete mText;
 
1593
        mText = nullptr;
 
1594
 
 
1595
        mSpeechBubble->setCaption(isShowName ? mName : "");
 
1596
 
 
1597
        mSpeechBubble->setText(mSpeech, isShowName);
 
1598
        mSpeechBubble->setPosition(px - (mSpeechBubble->getWidth() / 2),
 
1599
            py - getHeight() - (mSpeechBubble->getHeight()));
 
1600
        mSpeechBubble->setVisible(true);
 
1601
    }
 
1602
    else if (mSpeechTime > 0 && speech == TEXT_OVERHEAD)
 
1603
    {
 
1604
        mSpeechBubble->setVisible(false);
 
1605
 
 
1606
        if (!mText && userPalette)
 
1607
        {
 
1608
            mText = new Text(mSpeech, getPixelX(), getPixelY() - getHeight(),
 
1609
                gcn::Graphics::CENTER, &Theme::getThemeColor(
 
1610
                Theme::BUBBLE_TEXT), true);
 
1611
        }
 
1612
    }
 
1613
    else if (speech == NO_SPEECH)
 
1614
    {
 
1615
        mSpeechBubble->setVisible(false);
 
1616
 
 
1617
        delete mText;
 
1618
        mText = nullptr;
 
1619
    }
 
1620
}
 
1621
 
 
1622
int Being::getOffset(const signed char pos, const signed char neg) const
 
1623
{
 
1624
    // Check whether we're walking in the requested direction
 
1625
    if (mAction != MOVE ||  !(mDirection & (pos | neg)))
 
1626
        return 0;
 
1627
 
 
1628
    int offset = 0;
 
1629
 
 
1630
    if (mMap)
 
1631
    {
 
1632
        const int time = get_elapsed_time(mActionTime);
 
1633
        offset = (pos == LEFT && neg == RIGHT) ?
 
1634
            static_cast<int>((static_cast<float>(time)
 
1635
            * static_cast<float>(mMap->getTileWidth())) / mSpeed) :
 
1636
            static_cast<int>((static_cast<float>(time)
 
1637
            * static_cast<float>(mMap->getTileHeight())) / mSpeed);
 
1638
    }
 
1639
 
 
1640
    // We calculate the offset _from_ the _target_ location
 
1641
    offset -= 32;
 
1642
    if (offset > 0)
 
1643
        offset = 0;
 
1644
 
 
1645
    // Going into negative direction? Invert the offset.
 
1646
    if (mDirection & pos)
 
1647
        offset = -offset;
 
1648
 
 
1649
    if (offset > 32)
 
1650
        offset = 32;
 
1651
    if (offset < -32)
 
1652
        offset = -32;
 
1653
 
 
1654
    return offset;
 
1655
}
 
1656
 
 
1657
void Being::updateCoords()
 
1658
{
 
1659
    if (!mDispName)
 
1660
        return;
 
1661
 
 
1662
    int offsetX = getPixelX();
 
1663
    int offsetY = getPixelY();
 
1664
    if (mInfo)
 
1665
    {
 
1666
        offsetX += mInfo->getNameOffsetX();
 
1667
        offsetY += mInfo->getNameOffsetY();
 
1668
    }
 
1669
    // Monster names show above the sprite instead of below it
 
1670
    if (mType == MONSTER)
 
1671
        offsetY += - getHeight() - mDispName->getHeight();
 
1672
 
 
1673
    mDispName->adviseXY(offsetX, offsetY, mMoveNames);
 
1674
}
 
1675
 
 
1676
void Being::optionChanged(const std::string &value)
 
1677
{
 
1678
    if (mType == PLAYER && value == "visiblenames")
 
1679
        setShowName(config.getBoolValue("visiblenames"));
 
1680
}
 
1681
 
 
1682
void Being::flashName(const int time)
 
1683
{
 
1684
    if (mDispName)
 
1685
        mDispName->flash(time);
 
1686
}
 
1687
 
 
1688
std::string Being::getGenderSignWithSpace() const
 
1689
{
 
1690
    const std::string &str = getGenderSign();
 
1691
    if (str.empty())
 
1692
        return str;
 
1693
    else
 
1694
        return std::string(" ").append(str);
 
1695
}
 
1696
 
 
1697
std::string Being::getGenderSign() const
 
1698
{
 
1699
    std::string str;
 
1700
    if (mShowGender)
 
1701
    {
 
1702
        if (getGender() == GENDER_FEMALE)
 
1703
            str = "\u2640";
 
1704
        else if (getGender() == GENDER_MALE)
 
1705
            str = "\u2642";
 
1706
    }
 
1707
    if (mShowPlayersStatus && mAdvanced)
 
1708
    {
 
1709
        if (mShop)
 
1710
            str.append("$");
 
1711
        if (mAway)
 
1712
        {
 
1713
            // TRANSLATORS: this away status writed in player nick
 
1714
            str.append(_("A"));
 
1715
        }
 
1716
        else if (mInactive)
 
1717
        {
 
1718
            // TRANSLATORS: this inactive status writed in player nick
 
1719
            str.append(_("I"));
 
1720
        }
 
1721
    }
 
1722
    return str;
 
1723
}
 
1724
 
 
1725
void Being::showName()
 
1726
{
 
1727
    if (mName.empty())
 
1728
        return;
 
1729
 
 
1730
    delete mDispName;
 
1731
    mDispName = nullptr;
 
1732
 
 
1733
    if (mHideErased && player_relations.getRelation(mName) ==
 
1734
        PlayerRelation::ERASED)
 
1735
    {
 
1736
        return;
 
1737
    }
 
1738
 
 
1739
    std::string displayName(mName);
 
1740
 
 
1741
    if (mType != MONSTER && (mShowGender || mShowLevel))
 
1742
    {
 
1743
        displayName.append(" ");
 
1744
        if (mShowLevel && getLevel() != 0)
 
1745
            displayName.append(toString(getLevel()));
 
1746
 
 
1747
        displayName.append(getGenderSign());
 
1748
    }
 
1749
 
 
1750
    if (mType == MONSTER)
 
1751
    {
 
1752
        if (config.getBoolValue("showMonstersTakedDamage"))
 
1753
            displayName.append(", ").append(toString(getDamageTaken()));
 
1754
    }
 
1755
 
 
1756
    gcn::Font *font = nullptr;
 
1757
    if (player_node && player_node->getTarget() == this
 
1758
        && mType != MONSTER)
 
1759
    {
 
1760
        font = boldFont;
 
1761
    }
 
1762
    else if (mType == PLAYER && !player_relations.isGoodName(this) && gui)
 
1763
    {
 
1764
        font = gui->getSecureFont();
 
1765
    }
 
1766
 
 
1767
    if (mInfo)
 
1768
    {
 
1769
        mDispName = new FlashText(displayName,
 
1770
            getPixelX() + mInfo->getNameOffsetX(),
 
1771
            getPixelY() + mInfo->getNameOffsetY(),
 
1772
            gcn::Graphics::CENTER, mNameColor, font);
 
1773
    }
 
1774
    else
 
1775
    {
 
1776
        mDispName = new FlashText(displayName, getPixelX(), getPixelY(),
 
1777
            gcn::Graphics::CENTER, mNameColor, font);
 
1778
    }
 
1779
 
 
1780
    updateCoords();
 
1781
}
 
1782
 
 
1783
void Being::updateColors()
 
1784
{
 
1785
    if (userPalette)
 
1786
    {
 
1787
        if (mType == MONSTER)
 
1788
        {
 
1789
            mNameColor = &userPalette->getColor(UserPalette::MONSTER);
 
1790
            mTextColor = &userPalette->getColor(UserPalette::MONSTER);
 
1791
        }
 
1792
        else if (mType == NPC)
 
1793
        {
 
1794
            mNameColor = &userPalette->getColor(UserPalette::NPC);
 
1795
            mTextColor = &userPalette->getColor(UserPalette::NPC);
 
1796
        }
 
1797
        else if (this == player_node)
 
1798
        {
 
1799
            mNameColor = &userPalette->getColor(UserPalette::SELF);
 
1800
            mTextColor = &Theme::getThemeColor(Theme::PLAYER);
 
1801
        }
 
1802
        else
 
1803
        {
 
1804
            mTextColor = &Theme::getThemeColor(Theme::PLAYER);
 
1805
 
 
1806
            if (player_relations.getRelation(mName) != PlayerRelation::ERASED)
 
1807
                mErased = false;
 
1808
            else
 
1809
                mErased = true;
 
1810
 
 
1811
            if (mIsGM)
 
1812
            {
 
1813
                mTextColor = &userPalette->getColor(UserPalette::GM);
 
1814
                mNameColor = &userPalette->getColor(UserPalette::GM);
 
1815
            }
 
1816
            else if (mEnemy)
 
1817
            {
 
1818
                mNameColor = &userPalette->getColor(UserPalette::MONSTER);
 
1819
            }
 
1820
            else if (mParty && mParty == player_node->getParty())
 
1821
            {
 
1822
                mNameColor = &userPalette->getColor(UserPalette::PARTY);
 
1823
            }
 
1824
            else if (player_node && getGuild()
 
1825
                     && getGuild() == player_node->getGuild())
 
1826
            {
 
1827
                mNameColor = &userPalette->getColor(UserPalette::GUILD);
 
1828
            }
 
1829
            else if (player_relations.getRelation(mName) ==
 
1830
                     PlayerRelation::FRIEND)
 
1831
            {
 
1832
                mNameColor = &userPalette->getColor(UserPalette::FRIEND);
 
1833
            }
 
1834
            else if (player_relations.getRelation(mName) ==
 
1835
                     PlayerRelation::DISREGARDED
 
1836
                     || player_relations.getRelation(mName) ==
 
1837
                     PlayerRelation::BLACKLISTED)
 
1838
            {
 
1839
                mNameColor = &userPalette->getColor(UserPalette::DISREGARDED);
 
1840
            }
 
1841
            else if (player_relations.getRelation(mName) ==
 
1842
                     PlayerRelation::IGNORED
 
1843
                     || player_relations.getRelation(mName) ==
 
1844
                     PlayerRelation::ENEMY2)
 
1845
            {
 
1846
                mNameColor = &userPalette->getColor(UserPalette::IGNORED);
 
1847
            }
 
1848
            else if (player_relations.getRelation(mName) ==
 
1849
                     PlayerRelation::ERASED)
 
1850
            {
 
1851
                mNameColor = &userPalette->getColor(UserPalette::ERASED);
 
1852
            }
 
1853
            else
 
1854
            {
 
1855
                mNameColor = &userPalette->getColor(UserPalette::PC);
 
1856
            }
 
1857
        }
 
1858
 
 
1859
        if (mDispName)
 
1860
            mDispName->setColor(mNameColor);
 
1861
    }
 
1862
}
 
1863
 
 
1864
void Being::setSprite(const unsigned int slot, const int id,
 
1865
                      std::string color, const unsigned char colorId,
 
1866
                      const bool isWeapon, const bool isTempSprite)
 
1867
{
 
1868
    if (slot >= Net::getCharServerHandler()->maxSprite())
 
1869
        return;
 
1870
 
 
1871
    if (slot >= size())
 
1872
        ensureSize(slot + 1);
 
1873
 
 
1874
    if (slot >= mSpriteIDs.size())
 
1875
        mSpriteIDs.resize(slot + 1, 0);
 
1876
 
 
1877
    if (slot >= mSpriteColors.size())
 
1878
        mSpriteColors.resize(slot + 1, "");
 
1879
 
 
1880
    if (slot >= mSpriteColorsIds.size())
 
1881
        mSpriteColorsIds.resize(slot + 1, 1);
 
1882
 
 
1883
    // id = 0 means unequip
 
1884
    if (id == 0)
 
1885
    {
 
1886
        removeSprite(slot);
 
1887
 
 
1888
        if (isWeapon)
 
1889
            mEquippedWeapon = nullptr;
 
1890
        const int id1 = mSpriteIDs[slot];
 
1891
        if (id1)
 
1892
        {
 
1893
            const ItemInfo &info = ItemDB::get(id1);
 
1894
            if (mMap)
 
1895
            {
 
1896
                const int pet = info.getPet();
 
1897
                if (pet)
 
1898
                    removePet();
 
1899
            }
 
1900
        }
 
1901
    }
 
1902
    else
 
1903
    {
 
1904
        const ItemInfo &info = ItemDB::get(id);
 
1905
        const std::string filename = info.getSprite(mGender, mSubType);
 
1906
        AnimatedSprite *equipmentSprite = nullptr;
 
1907
 
 
1908
        if (mType == PLAYER)
 
1909
        {
 
1910
            const int pet = info.getPet();
 
1911
            if (pet)
 
1912
                addPet(pet);
 
1913
        }
 
1914
 
 
1915
        if (!filename.empty())
 
1916
        {
 
1917
            if (color.empty())
 
1918
                color = info.getDyeColorsString(colorId);
 
1919
 
 
1920
            equipmentSprite = AnimatedSprite::delayedLoad(
 
1921
                paths.getStringValue("sprites").append(
 
1922
                combineDye(filename, color)));
 
1923
        }
 
1924
 
 
1925
        if (equipmentSprite)
 
1926
            equipmentSprite->setSpriteDirection(getSpriteDirection());
 
1927
 
 
1928
        CompoundSprite::setSprite(slot, equipmentSprite);
 
1929
 
 
1930
        if (isWeapon)
 
1931
            mEquippedWeapon = &ItemDB::get(id);
 
1932
 
 
1933
        setAction(mAction);
 
1934
    }
 
1935
 
 
1936
    if (!isTempSprite)
 
1937
    {
 
1938
        mSpriteIDs[slot] = id;
 
1939
        mSpriteColors[slot] = color;
 
1940
        mSpriteColorsIds[slot] = colorId;
 
1941
        recalcSpritesOrder();
 
1942
        if (beingEquipmentWindow)
 
1943
            beingEquipmentWindow->updateBeing(this);
 
1944
    }
 
1945
}
 
1946
 
 
1947
void Being::setSpriteID(const unsigned int slot, const int id)
 
1948
{
 
1949
    setSprite(slot, id, mSpriteColors[slot]);
 
1950
}
 
1951
 
 
1952
void Being::setSpriteColor(const unsigned int slot, const std::string &color)
 
1953
{
 
1954
    setSprite(slot, mSpriteIDs[slot], color);
 
1955
}
 
1956
 
 
1957
void Being::setHairStyle(const unsigned int slot, const int id)
 
1958
{
 
1959
//    dumpSprites();
 
1960
    setSprite(slot, id, ItemDB::get(id).getDyeColorsString(mHairColor));
 
1961
//    dumpSprites();
 
1962
}
 
1963
 
 
1964
void Being::setHairColor(const unsigned int slot, const unsigned char color)
 
1965
{
 
1966
    mHairColor = color;
 
1967
    setSprite(slot, mSpriteIDs[slot], ItemDB::get(
 
1968
        getSpriteID(slot)).getDyeColorsString(color));
 
1969
}
 
1970
 
 
1971
void Being::dumpSprites() const
 
1972
{
 
1973
    std::vector<int>::const_iterator it1 = mSpriteIDs.begin();
 
1974
    const std::vector<int>::const_iterator it1_end = mSpriteIDs.end();
 
1975
    StringVectCIter it2 = mSpriteColors.begin();
 
1976
    const StringVectCIter it2_end = mSpriteColors.end();
 
1977
    std::vector<int>::const_iterator it3 = mSpriteColorsIds.begin();
 
1978
    const std::vector<int>::const_iterator it3_end = mSpriteColorsIds.end();
 
1979
 
 
1980
    logger->log("sprites");
 
1981
    for (; it1 != it1_end && it2 != it2_end && it3 != it3_end;
 
1982
         ++ it1, ++ it2, ++ it3)
 
1983
    {
 
1984
        logger->log("%d,%s,%d", *it1, (*it2).c_str(), *it3);
 
1985
    }
 
1986
}
 
1987
 
 
1988
void Being::load()
 
1989
{
 
1990
    // Hairstyles are encoded as negative numbers. Count how far negative
 
1991
    // we can go.
 
1992
    int hairstyles = 1;
 
1993
    while (ItemDB::get(-hairstyles).getSprite(GENDER_MALE, 0) !=
 
1994
           paths.getStringValue("spriteErrorFile"))
 
1995
    {
 
1996
        hairstyles ++;
 
1997
    }
 
1998
    mNumberOfHairstyles = hairstyles;
 
1999
 
 
2000
    int races = 100;
 
2001
    while (ItemDB::get(-races).getSprite(GENDER_MALE, 0) !=
 
2002
           paths.getStringValue("spriteErrorFile"))
 
2003
    {
 
2004
        races ++;
 
2005
    }
 
2006
    mNumberOfRaces = races - 100;
 
2007
}
 
2008
 
 
2009
void Being::updateName()
 
2010
{
 
2011
    if (mShowName)
 
2012
        showName();
 
2013
}
 
2014
 
 
2015
void Being::reReadConfig()
 
2016
{
 
2017
    BLOCK_START("Being::reReadConfig")
 
2018
    if (mUpdateConfigTime + 1 < cur_time)
 
2019
    {
 
2020
        mAwayEffect = paths.getIntValue("afkEffectId");
 
2021
        mHighlightMapPortals = config.getBoolValue("highlightMapPortals");
 
2022
        mConfLineLim = config.getIntValue("chatMaxCharLimit");
 
2023
        mSpeechType = config.getIntValue("speech");
 
2024
        mHighlightMonsterAttackRange =
 
2025
            config.getBoolValue("highlightMonsterAttackRange");
 
2026
        mLowTraffic = config.getBoolValue("lowTraffic");
 
2027
        mDrawHotKeys = config.getBoolValue("drawHotKeys");
 
2028
        mShowBattleEvents = config.getBoolValue("showBattleEvents");
 
2029
        mShowMobHP = config.getBoolValue("showMobHP");
 
2030
        mShowOwnHP = config.getBoolValue("showOwnHP");
 
2031
        mShowGender = config.getBoolValue("showgender");
 
2032
        mShowLevel = config.getBoolValue("showlevel");
 
2033
        mShowPlayersStatus = config.getBoolValue("showPlayersStatus");
 
2034
        mEnableReorderSprites = config.getBoolValue("enableReorderSprites");
 
2035
        mHideErased = config.getBoolValue("hideErased");
 
2036
        mMoveNames = config.getBoolValue("moveNames");
 
2037
        mUseDiagonal = config.getBoolValue("useDiagonalSpeed");
 
2038
 
 
2039
        mUpdateConfigTime = cur_time;
 
2040
    }
 
2041
    BLOCK_END("Being::reReadConfig")
 
2042
}
 
2043
 
 
2044
bool Being::updateFromCache()
 
2045
{
 
2046
    const BeingCacheEntry *const entry = Being::getCacheEntry(getId());
 
2047
 
 
2048
    if (entry && entry->getTime() + 120 >= cur_time)
 
2049
    {
 
2050
        if (!entry->getName().empty())
 
2051
            setName(entry->getName());
 
2052
        setPartyName(entry->getPartyName());
 
2053
        setGuildName(entry->getGuildName());
 
2054
        setLevel(entry->getLevel());
 
2055
        setPvpRank(entry->getPvpRank());
 
2056
        setIp(entry->getIp());
 
2057
 
 
2058
        mAdvanced = entry->isAdvanced();
 
2059
        if (mAdvanced)
 
2060
        {
 
2061
            const int flags = entry->getFlags();
 
2062
            mShop = ((flags & FLAG_SHOP) != 0);
 
2063
            mAway = ((flags & FLAG_AWAY) != 0);
 
2064
            mInactive = ((flags & FLAG_INACTIVE) != 0);
 
2065
            if (mShop || mAway || mInactive)
 
2066
                updateName();
 
2067
        }
 
2068
        else
 
2069
        {
 
2070
            mShop = false;
 
2071
            mAway = false;
 
2072
            mInactive = false;
 
2073
        }
 
2074
 
 
2075
        updateAwayEffect();
 
2076
        if (mType == PLAYER)
 
2077
            updateColors();
 
2078
        return true;
 
2079
    }
 
2080
    return false;
 
2081
}
 
2082
 
 
2083
void Being::addToCache() const
 
2084
{
 
2085
    if (player_node == this)
 
2086
        return;
 
2087
 
 
2088
    BeingCacheEntry *entry = Being::getCacheEntry(getId());
 
2089
    if (!entry)
 
2090
    {
 
2091
        entry = new BeingCacheEntry(getId());
 
2092
        beingInfoCache.push_front(entry);
 
2093
 
 
2094
        if (beingInfoCache.size() >= CACHE_SIZE)
 
2095
        {
 
2096
            delete beingInfoCache.back();
 
2097
            beingInfoCache.pop_back();
 
2098
        }
 
2099
    }
 
2100
    if (!mLowTraffic)
 
2101
        return;
 
2102
 
 
2103
    entry->setName(getName());
 
2104
    entry->setLevel(getLevel());
 
2105
    entry->setPartyName(getPartyName());
 
2106
    entry->setGuildName(getGuildName());
 
2107
    entry->setTime(cur_time);
 
2108
    entry->setPvpRank(getPvpRank());
 
2109
    entry->setIp(getIp());
 
2110
    entry->setAdvanced(isAdvanced());
 
2111
    if (isAdvanced())
 
2112
    {
 
2113
        int flags = 0;
 
2114
        if (mShop)
 
2115
            flags += FLAG_SHOP;
 
2116
        if (mAway)
 
2117
            flags += FLAG_AWAY;
 
2118
        if (mInactive)
 
2119
            flags += FLAG_INACTIVE;
 
2120
        entry->setFlags(flags);
 
2121
    }
 
2122
    else
 
2123
    {
 
2124
        entry->setFlags(0);
 
2125
    }
 
2126
}
 
2127
 
 
2128
BeingCacheEntry* Being::getCacheEntry(const int id)
 
2129
{
 
2130
    FOR_EACH (std::list<BeingCacheEntry*>::iterator, i, beingInfoCache)
 
2131
    {
 
2132
        if (!*i)
 
2133
            continue;
 
2134
 
 
2135
        if (id == (*i)->getId())
 
2136
        {
 
2137
            // Raise priority: move it to front
 
2138
            if ((*i)->getTime() + 120 < cur_time)
 
2139
            {
 
2140
                beingInfoCache.splice(beingInfoCache.begin(),
 
2141
                                      beingInfoCache, i);
 
2142
            }
 
2143
            return *i;
 
2144
        }
 
2145
    }
 
2146
    return nullptr;
 
2147
}
 
2148
 
 
2149
 
 
2150
void Being::setGender(const Gender gender)
 
2151
{
 
2152
    if (gender != mGender)
 
2153
    {
 
2154
        mGender = gender;
 
2155
 
 
2156
        // Reload all subsprites
 
2157
        for (unsigned int i = 0; i < mSpriteIDs.size(); i++)
 
2158
        {
 
2159
            if (mSpriteIDs.at(i) != 0)
 
2160
                setSprite(i, mSpriteIDs.at(i), mSpriteColors.at(i));
 
2161
        }
 
2162
 
 
2163
        updateName();
 
2164
    }
 
2165
}
 
2166
 
 
2167
void Being::setGM(const bool gm)
 
2168
{
 
2169
    mIsGM = gm;
 
2170
 
 
2171
    updateColors();
 
2172
}
 
2173
 
 
2174
void Being::talkTo() const
 
2175
{
 
2176
    if (!client->limitPackets(PACKET_NPC_TALK))
 
2177
        return;
 
2178
 
 
2179
    Net::getNpcHandler()->talk(mId);
 
2180
}
 
2181
 
 
2182
bool Being::draw(Graphics *const graphics,
 
2183
                 const int offsetX, const int offsetY) const
 
2184
{
 
2185
    bool res = true;
 
2186
    if (!mErased)
 
2187
        res = ActorSprite::draw(graphics, offsetX, offsetY);
 
2188
 
 
2189
    return res;
 
2190
}
 
2191
 
 
2192
void Being::drawSprites(Graphics *const graphics,
 
2193
                        const int posX, const int posY) const
 
2194
{
 
2195
    const int sz = getNumberOfLayers();
 
2196
    for (int f = 0; f < sz; f ++)
 
2197
    {
 
2198
        const int rSprite = mSpriteHide[mSpriteRemap[f]];
 
2199
        if (rSprite == 1)
 
2200
            continue;
 
2201
 
 
2202
        Sprite *const sprite = getSprite(mSpriteRemap[f]);
 
2203
        if (sprite)
 
2204
        {
 
2205
            sprite->setAlpha(mAlpha);
 
2206
            sprite->draw(graphics, posX, posY);
 
2207
        }
 
2208
    }
 
2209
}
 
2210
 
 
2211
void Being::drawSpritesSDL(Graphics *const graphics,
 
2212
                           const int posX, const int posY) const
 
2213
{
 
2214
    const size_t sz = size();
 
2215
    for (unsigned f = 0; f < sz; f ++)
 
2216
    {
 
2217
        const int rSprite = mSpriteHide[mSpriteRemap[f]];
 
2218
        if (rSprite == 1)
 
2219
            continue;
 
2220
 
 
2221
        const Sprite *const sprite = getSprite(mSpriteRemap[f]);
 
2222
        if (sprite)
 
2223
            sprite->draw(graphics, posX, posY);
 
2224
    }
 
2225
}
 
2226
 
 
2227
bool Being::drawSpriteAt(Graphics *const graphics,
 
2228
                         const int x, const int y) const
 
2229
{
 
2230
    bool res = true;
 
2231
 
 
2232
    if (!mErased)
 
2233
        res = ActorSprite::drawSpriteAt(graphics, x, y);
 
2234
 
 
2235
    if (!userPalette)
 
2236
        return res;
 
2237
 
 
2238
    if (mHighlightMapPortals && mMap && mSubType == 45 && !mMap->getHasWarps())
 
2239
    {
 
2240
        graphics->setColor(userPalette->
 
2241
                getColorWithAlpha(UserPalette::PORTAL_HIGHLIGHT));
 
2242
 
 
2243
        graphics->fillRectangle(gcn::Rectangle(x, y, 32, 32));
 
2244
 
 
2245
        if (mDrawHotKeys && !mName.empty())
 
2246
        {
 
2247
            gcn::Font *const font = gui->getFont();
 
2248
            if (font)
 
2249
            {
 
2250
                graphics->setColor(userPalette->getColor(UserPalette::BEING));
 
2251
                font->drawString(graphics, mName, x, y);
 
2252
            }
 
2253
        }
 
2254
    }
 
2255
 
 
2256
    if (mHighlightMonsterAttackRange && mType == ActorSprite::MONSTER
 
2257
        && isAlive())
 
2258
    {
 
2259
        int attackRange;
 
2260
        if (mAttackRange)
 
2261
            attackRange = 32 * mAttackRange;
 
2262
        else
 
2263
            attackRange = 32;
 
2264
 
 
2265
        graphics->setColor(userPalette->getColorWithAlpha(
 
2266
            UserPalette::MONSTER_ATTACK_RANGE));
 
2267
 
 
2268
        graphics->fillRectangle(gcn::Rectangle(
 
2269
            x - attackRange, y - attackRange,
 
2270
            2 * attackRange + 32, 2 * attackRange + 32));
 
2271
    }
 
2272
 
 
2273
    if (mShowMobHP && mInfo && player_node && player_node->getTarget() == this
 
2274
        && mType == MONSTER)
 
2275
    {
 
2276
        // show hp bar here
 
2277
        int maxHP = mMaxHP;
 
2278
        if (!maxHP)
 
2279
            maxHP = mInfo->getMaxHP();
 
2280
 
 
2281
        drawHpBar(graphics, maxHP, mHP, mDamageTaken,
 
2282
                  UserPalette::MONSTER_HP, UserPalette::MONSTER_HP2,
 
2283
                  x - 50 + 16 + mInfo->getHpBarOffsetX(),
 
2284
                  y + 32 - 6 + mInfo->getHpBarOffsetY(),
 
2285
                  2 * 50, 4);
 
2286
    }
 
2287
    if (mShowOwnHP && mInfo && player_node == this && mAction != DEAD)
 
2288
    {
 
2289
        drawHpBar(graphics, PlayerInfo::getAttribute(PlayerInfo::MAX_HP),
 
2290
                  PlayerInfo::getAttribute(PlayerInfo::HP), 0,
 
2291
                  UserPalette::PLAYER_HP, UserPalette::PLAYER_HP2,
 
2292
                  x - 50 + 16 + mInfo->getHpBarOffsetX(),
 
2293
                  y + 32 - 6 + mInfo->getHpBarOffsetY(),
 
2294
                  2 * 50, 4);
 
2295
    }
 
2296
    return res;
 
2297
}
 
2298
 
 
2299
void Being::drawHpBar(Graphics *const graphics, const int maxHP, const int hp,
 
2300
                      const int damage, const int color1, const int color2,
 
2301
                      const int x, const int y, const int width,
 
2302
                      const int height) const
 
2303
{
 
2304
    if (maxHP <= 0 || !userPalette)
 
2305
        return;
 
2306
 
 
2307
    float p;
 
2308
 
 
2309
    if (hp)
 
2310
    {
 
2311
        p = static_cast<float>(maxHP) / static_cast<float>(hp);
 
2312
    }
 
2313
    else if (maxHP != damage)
 
2314
    {
 
2315
        p = static_cast<float>(maxHP)
 
2316
            / static_cast<float>(maxHP - damage);
 
2317
    }
 
2318
    else
 
2319
    {
 
2320
        p = 1;
 
2321
    }
 
2322
 
 
2323
    if (p <= 0 || p > width)
 
2324
        return;
 
2325
 
 
2326
    const int dx = static_cast<const int>(static_cast<float>(width) / p);
 
2327
 
 
2328
    if (serverVersion < 1)
 
2329
    {   // old servers
 
2330
        if ((!damage && (this != player_node || hp == maxHP))
 
2331
            || (!hp && maxHP == damage))
 
2332
        {
 
2333
            graphics->setColor(userPalette->getColorWithAlpha(color1));
 
2334
            graphics->fillRectangle(gcn::Rectangle(
 
2335
                x, y, dx, height));
 
2336
            return;
 
2337
        }
 
2338
        else if (width - dx <= 0)
 
2339
        {
 
2340
            graphics->setColor(userPalette->getColorWithAlpha(color2));
 
2341
            graphics->fillRectangle(gcn::Rectangle(
 
2342
                x, y, width, height));
 
2343
            return;
 
2344
        }
 
2345
    }
 
2346
    else
 
2347
    {   // evol servers
 
2348
        if (hp == maxHP)
 
2349
        {
 
2350
            graphics->setColor(userPalette->getColorWithAlpha(color1));
 
2351
            graphics->fillRectangle(gcn::Rectangle(
 
2352
                x, y, dx, height));
 
2353
            return;
 
2354
        }
 
2355
        else if (width - dx <= 0)
 
2356
        {
 
2357
            graphics->setColor(userPalette->getColorWithAlpha(color2));
 
2358
            graphics->fillRectangle(gcn::Rectangle(
 
2359
                x, y, width, height));
 
2360
            return;
 
2361
        }
 
2362
    }
 
2363
 
 
2364
    graphics->setColor(userPalette->getColorWithAlpha(color1));
 
2365
    graphics->fillRectangle(gcn::Rectangle(
 
2366
        x, y, dx, height));
 
2367
 
 
2368
    graphics->setColor(userPalette->getColorWithAlpha(color2));
 
2369
    graphics->fillRectangle(gcn::Rectangle(
 
2370
        x + dx, y, width - dx, height));
 
2371
}
 
2372
 
 
2373
void Being::setHP(const int hp)
 
2374
{
 
2375
    mHP = hp;
 
2376
    if (mMaxHP < mHP)
 
2377
        mMaxHP = mHP;
 
2378
    if (mType == MONSTER)
 
2379
        updatePercentHP();
 
2380
}
 
2381
 
 
2382
void Being::setMaxHP(const int hp)
 
2383
{
 
2384
    mMaxHP = hp;
 
2385
    if (mMaxHP < mHP)
 
2386
        mMaxHP = mHP;
 
2387
}
 
2388
 
 
2389
void Being::resetCounters()
 
2390
{
 
2391
    mMoveTime = 0;
 
2392
    mAttackTime = 0;
 
2393
    mTalkTime = 0;
 
2394
    mOtherTime = 0;
 
2395
    mTestTime = cur_time;
 
2396
}
 
2397
 
 
2398
void Being::recalcSpritesOrder()
 
2399
{
 
2400
    if (!mEnableReorderSprites)
 
2401
        return;
 
2402
 
 
2403
//    logger->log("recalcSpritesOrder");
 
2404
    const unsigned sz = static_cast<unsigned>(size());
 
2405
    if (sz < 1)
 
2406
        return;
 
2407
 
 
2408
    std::vector<int> slotRemap;
 
2409
    std::map<int, int> itemSlotRemap;
 
2410
 
 
2411
    std::vector<int>::iterator it;
 
2412
    int oldHide[20];
 
2413
    int dir = mSpriteDirection;
 
2414
    if (dir < 0 || dir >= 9)
 
2415
        dir = 0;
 
2416
    // hack for allow different logic in dead player
 
2417
    if (mAction == DEAD)
 
2418
        dir = 9;
 
2419
 
 
2420
    const unsigned int hairSlot = Net::getCharServerHandler()->hairSprite();
 
2421
 
 
2422
    for (unsigned slot = 0; slot < sz; slot ++)
 
2423
    {
 
2424
        oldHide[slot] = mSpriteHide[slot];
 
2425
        mSpriteHide[slot] = 0;
 
2426
    }
 
2427
 
 
2428
    const size_t spriteIdSize = mSpriteIDs.size();
 
2429
    for (unsigned slot = 0; slot < sz; slot ++)
 
2430
    {
 
2431
        slotRemap.push_back(slot);
 
2432
 
 
2433
        if (spriteIdSize <= slot)
 
2434
            continue;
 
2435
 
 
2436
        const int id = mSpriteIDs[slot];
 
2437
        if (!id)
 
2438
            continue;
 
2439
 
 
2440
        const ItemInfo &info = ItemDB::get(id);
 
2441
 
 
2442
        if (info.isRemoveSprites())
 
2443
        {
 
2444
            SpriteToItemMap *const spriteToItems
 
2445
                = info.getSpriteToItemReplaceMap(dir);
 
2446
 
 
2447
            if (spriteToItems)
 
2448
            {
 
2449
                FOR_EACHP (SpriteToItemMapCIter, itr, spriteToItems)
 
2450
                {
 
2451
                    const int remSprite = itr->first;
 
2452
                    const std::map<int, int> &itemReplacer = itr->second;
 
2453
                    if (remSprite >= 0)
 
2454
                    {   // slot known
 
2455
                        if (itemReplacer.empty())
 
2456
                        {
 
2457
                            mSpriteHide[remSprite] = 1;
 
2458
                        }
 
2459
                        else
 
2460
                        {
 
2461
                            std::map<int, int>::const_iterator repIt
 
2462
                                = itemReplacer.find(mSpriteIDs[remSprite]);
 
2463
                            if (repIt == itemReplacer.end())
 
2464
                            {
 
2465
                                repIt = itemReplacer.find(0);
 
2466
                                if (repIt->second == 0)
 
2467
                                    repIt = itemReplacer.end();
 
2468
                            }
 
2469
                            if (repIt != itemReplacer.end())
 
2470
                            {
 
2471
                                mSpriteHide[remSprite] = repIt->second;
 
2472
                                if (repIt->second != 1)
 
2473
                                {
 
2474
                                    if (static_cast<unsigned>(remSprite)
 
2475
                                        != hairSlot)
 
2476
                                    {
 
2477
                                        setSprite(remSprite, repIt->second,
 
2478
                                            mSpriteColors[remSprite],
 
2479
                                            1, false, true);
 
2480
                                    }
 
2481
                                    else
 
2482
                                    {
 
2483
                                        setSprite(remSprite, repIt->second,
 
2484
                                            ItemDB::get(repIt->second)
 
2485
                                            .getDyeColorsString(mHairColor),
 
2486
                                            1, false, true);
 
2487
                                    }
 
2488
                                }
 
2489
                            }
 
2490
                        }
 
2491
                    }
 
2492
                    else
 
2493
                    {   // slot unknown. Search for real slot, this can be slow
 
2494
                        FOR_EACH (IntMapCIter, repIt, itemReplacer)
 
2495
                        {
 
2496
                            for (unsigned slot2 = 0; slot2 < sz; slot2 ++)
 
2497
                            {
 
2498
                                if (mSpriteIDs[slot2] == repIt->first)
 
2499
                                {
 
2500
                                    mSpriteHide[slot2] = repIt->second;
 
2501
                                    if (repIt->second != 1)
 
2502
                                    {
 
2503
                                        if (slot2 != hairSlot)
 
2504
                                        {
 
2505
                                            setSprite(slot2, repIt->second,
 
2506
                                                mSpriteColors[slot2],
 
2507
                                                1, false, true);
 
2508
                                        }
 
2509
                                        else
 
2510
                                        {
 
2511
                                            setSprite(slot2, repIt->second,
 
2512
                                                ItemDB::get(repIt->second)
 
2513
                                                .getDyeColorsString(
 
2514
                                                mHairColor),
 
2515
                                                1, false, true);
 
2516
                                        }
 
2517
                                    }
 
2518
                                }
 
2519
                            }
 
2520
                        }
 
2521
                    }
 
2522
                }
 
2523
            }
 
2524
        }
 
2525
 
 
2526
        if (info.mDrawBefore[dir] > 0)
 
2527
        {
 
2528
            const int id2 = mSpriteIDs[info.mDrawBefore[dir]];
 
2529
            if (itemSlotRemap.find(id2) != itemSlotRemap.end())
 
2530
            {
 
2531
//                logger->log("found duplicate (before)");
 
2532
                const ItemInfo &info2 = ItemDB::get(id2);
 
2533
                if (info.mDrawPriority[dir] < info2.mDrawPriority[dir])
 
2534
                {
 
2535
//                    logger->log("old more priority");
 
2536
                    continue;
 
2537
                }
 
2538
                else
 
2539
                {
 
2540
//                    logger->log("new more priority");
 
2541
                    itemSlotRemap.erase(id2);
 
2542
                }
 
2543
            }
 
2544
 
 
2545
            itemSlotRemap[id] = -info.mDrawBefore[dir];
 
2546
        }
 
2547
        else if (info.mDrawAfter[dir] > 0)
 
2548
        {
 
2549
            const int id2 = mSpriteIDs[info.mDrawAfter[dir]];
 
2550
            if (itemSlotRemap.find(id2) != itemSlotRemap.end())
 
2551
            {
 
2552
                const ItemInfo &info2 = ItemDB::get(id2);
 
2553
                if (info.mDrawPriority[dir] < info2.mDrawPriority[dir])
 
2554
                {
 
2555
//                    logger->log("old more priority");
 
2556
                    continue;
 
2557
                }
 
2558
                else
 
2559
                {
 
2560
//                    logger->log("new more priority");
 
2561
                    itemSlotRemap.erase(id2);
 
2562
                }
 
2563
            }
 
2564
 
 
2565
            itemSlotRemap[id] = info.mDrawAfter[dir];
 
2566
//       logger->log("item slot->slot %d %d->%d", id, slot, itemSlotRemap[id]);
 
2567
        }
 
2568
    }
 
2569
//    logger->log("preparation end");
 
2570
 
 
2571
    int lastRemap = 0;
 
2572
    unsigned cnt = 0;
 
2573
 
 
2574
    while (cnt < 15 && lastRemap >= 0)
 
2575
    {
 
2576
        lastRemap = -1;
 
2577
        cnt ++;
 
2578
//        logger->log("iteration");
 
2579
 
 
2580
        for (unsigned slot0 = 0; slot0 < sz; slot0 ++)
 
2581
        {
 
2582
            const int slot = searchSlotValue(slotRemap, slot0);
 
2583
            const int val = slotRemap.at(slot);
 
2584
            int id = 0;
 
2585
 
 
2586
            if (static_cast<int>(spriteIdSize) > val)
 
2587
                id = mSpriteIDs[val];
 
2588
 
 
2589
            int idx = -1;
 
2590
            int idx1 = -1;
 
2591
//            logger->log("item %d, id=%d", slot, id);
 
2592
            int reorder = 0;
 
2593
            const std::map<int, int>::const_iterator
 
2594
                orderIt = itemSlotRemap.find(id);
 
2595
            if (orderIt != itemSlotRemap.end())
 
2596
                reorder = orderIt->second;
 
2597
 
 
2598
            if (reorder < 0)
 
2599
            {
 
2600
//                logger->log("move item %d before %d", slot, -reorder);
 
2601
                searchSlotValueItr(it, idx, slotRemap, -reorder);
 
2602
                if (it == slotRemap.end())
 
2603
                    return;
 
2604
                searchSlotValueItr(it, idx1, slotRemap, val);
 
2605
                if (it == slotRemap.end())
 
2606
                    return;
 
2607
                lastRemap = idx1;
 
2608
                if (idx1 + 1 != idx)
 
2609
                {
 
2610
                    slotRemap.erase(it);
 
2611
                    searchSlotValueItr(it, idx, slotRemap, -reorder);
 
2612
                    slotRemap.insert(it, val);
 
2613
                }
 
2614
            }
 
2615
            else if (reorder > 0)
 
2616
            {
 
2617
//                logger->log("move item %d after %d", slot, reorder);
 
2618
                searchSlotValueItr(it, idx, slotRemap, reorder);
 
2619
                searchSlotValueItr(it, idx1, slotRemap, val);
 
2620
                if (it == slotRemap.end())
 
2621
                    return;
 
2622
                lastRemap = idx1;
 
2623
                if (idx1 != idx + 1)
 
2624
                {
 
2625
                    slotRemap.erase(it);
 
2626
                    searchSlotValueItr(it, idx, slotRemap, reorder);
 
2627
                    if (it != slotRemap.end())
 
2628
                    {
 
2629
                        ++ it;
 
2630
                        if (it != slotRemap.end())
 
2631
                            slotRemap.insert(it, val);
 
2632
                        else
 
2633
                            slotRemap.push_back(val);
 
2634
                    }
 
2635
                    else
 
2636
                    {
 
2637
                        slotRemap.push_back(val);
 
2638
                    }
 
2639
                }
 
2640
            }
 
2641
        }
 
2642
    }
 
2643
 
 
2644
//    logger->log("after remap");
 
2645
    for (unsigned slot = 0; slot < sz; slot ++)
 
2646
    {
 
2647
        mSpriteRemap[slot] = slotRemap[slot];
 
2648
        if (oldHide[slot] != 0 && oldHide[slot] != 1 && mSpriteHide[slot] == 0)
 
2649
        {
 
2650
            const int id = mSpriteIDs[slot];
 
2651
            if (!id)
 
2652
                continue;
 
2653
 
 
2654
            setSprite(slot, id, mSpriteColors[slot], 1, false, true);
 
2655
        }
 
2656
//        logger->log("slot %d = %d", slot, mSpriteRemap[slot]);
 
2657
    }
 
2658
}
 
2659
 
 
2660
int Being::searchSlotValue(const std::vector<int> &slotRemap,
 
2661
                           const int val) const
 
2662
{
 
2663
    const size_t sz = size();
 
2664
    for (size_t slot = 0; slot < sz; slot ++)
 
2665
    {
 
2666
        if (slotRemap[slot] == val)
 
2667
            return slot;
 
2668
    }
 
2669
    return getNumberOfLayers() - 1;
 
2670
}
 
2671
 
 
2672
void Being::searchSlotValueItr(std::vector<int>::iterator &it, int &idx,
 
2673
                               std::vector<int> &slotRemap,
 
2674
                               const int val) const
 
2675
{
 
2676
//    logger->log("searching %d", val);
 
2677
    it = slotRemap.begin();
 
2678
    const std::vector<int>::iterator it_end = slotRemap.end();
 
2679
    idx = 0;
 
2680
    while (it != it_end)
 
2681
    {
 
2682
//        logger->log("testing %d", *it);
 
2683
        if (*it == val)
 
2684
        {
 
2685
//            logger->log("found at %d", idx);
 
2686
            return;
 
2687
        }
 
2688
        ++ it;
 
2689
        idx ++;
 
2690
    }
 
2691
//    logger->log("not found");
 
2692
    idx = -1;
 
2693
    return;
 
2694
}
 
2695
 
 
2696
void Being::updateHit(const int amount)
 
2697
{
 
2698
    if (amount > 0)
 
2699
    {
 
2700
        if (!mMinHit || amount < mMinHit)
 
2701
            mMinHit = amount;
 
2702
        if (amount != mCriticalHit && (!mMaxHit || amount > mMaxHit))
 
2703
            mMaxHit = amount;
 
2704
    }
 
2705
}
 
2706
 
 
2707
Equipment *Being::getEquipment()
 
2708
{
 
2709
    Equipment *const eq = new Equipment();
 
2710
    Equipment::Backend *const bk = new BeingEquipBackend(this);
 
2711
    eq->setBackend(bk);
 
2712
    return eq;
 
2713
}
 
2714
 
 
2715
void Being::undressItemById(const int id)
 
2716
{
 
2717
    const size_t sz = mSpriteIDs.size();
 
2718
 
 
2719
    for (size_t f = 0; f < sz; f ++)
 
2720
    {
 
2721
        if (id == mSpriteIDs[f])
 
2722
        {
 
2723
            setSprite(static_cast<unsigned int>(f), 0);
 
2724
            break;
 
2725
        }
 
2726
    }
 
2727
}
 
2728
 
 
2729
void Being::clearCache()
 
2730
{
 
2731
    delete_all(beingInfoCache);
 
2732
    beingInfoCache.clear();
 
2733
}
 
2734
 
 
2735
void Being::updateComment()
 
2736
{
 
2737
    if (mGotComment || mName.empty())
 
2738
        return;
 
2739
 
 
2740
    mGotComment = true;
 
2741
    mComment = loadComment(mName, mType);
 
2742
}
 
2743
 
 
2744
std::string Being::loadComment(const std::string &name, const int type)
 
2745
{
 
2746
    std::string str;
 
2747
    switch (type)
 
2748
    {
 
2749
        case PLAYER:
 
2750
            str = client->getUsersDirectory();
 
2751
            break;
 
2752
        case NPC:
 
2753
            str = client->getNpcsDirectory();
 
2754
            break;
 
2755
        default:
 
2756
            return "";
 
2757
    }
 
2758
 
 
2759
    str.append(stringToHexPath(name)).append("/comment.txt");
 
2760
    logger->log("load from: %s", str.c_str());
 
2761
    StringVect lines;
 
2762
 
 
2763
    const ResourceManager *const resman = ResourceManager::getInstance();
 
2764
    if (resman->existsLocal(str))
 
2765
    {
 
2766
        lines = resman->loadTextFileLocal(str);
 
2767
        if (lines.size() >= 2)
 
2768
            return lines[1];
 
2769
    }
 
2770
    return "";
 
2771
}
 
2772
 
 
2773
void Being::saveComment(const std::string &name,
 
2774
                        const std::string &comment, const int type)
 
2775
{
 
2776
    std::string dir;
 
2777
    switch (type)
 
2778
    {
 
2779
        case PLAYER:
 
2780
            dir = client->getUsersDirectory();
 
2781
            break;
 
2782
        case NPC:
 
2783
            dir = client->getNpcsDirectory();
 
2784
            break;
 
2785
        default:
 
2786
            return;
 
2787
    }
 
2788
    dir.append(stringToHexPath(name));
 
2789
    const ResourceManager *const resman = ResourceManager::getInstance();
 
2790
    resman->saveTextFile(dir, "comment.txt",
 
2791
        (name + "\n").append(comment));
 
2792
}
 
2793
 
 
2794
void Being::setState(const uint8_t state)
 
2795
{
 
2796
    const bool shop = ((state & FLAG_SHOP) != 0);
 
2797
    const bool away = ((state & FLAG_AWAY) != 0);
 
2798
    const bool inactive = ((state & FLAG_INACTIVE) != 0);
 
2799
    const bool needUpdate = (shop != mShop || away != mAway
 
2800
        || inactive != mInactive);
 
2801
 
 
2802
    mShop = shop;
 
2803
    mAway = away;
 
2804
    mInactive = inactive;
 
2805
    updateAwayEffect();
 
2806
 
 
2807
    if (needUpdate)
 
2808
    {
 
2809
        if (shop || away || inactive)
 
2810
            mAdvanced = true;
 
2811
        updateName();
 
2812
        addToCache();
 
2813
    }
 
2814
}
 
2815
 
 
2816
void Being::setEmote(const uint8_t emotion, const int emote_time)
 
2817
{
 
2818
    if ((emotion & FLAG_SPECIAL) == FLAG_SPECIAL)
 
2819
    {
 
2820
        setState(emotion);
 
2821
        mAdvanced = true;
 
2822
    }
 
2823
    else
 
2824
    {
 
2825
        const int emotionIndex = emotion - 1;
 
2826
        if (emotionIndex >= 0 && emotionIndex <= EmoteDB::getLast())
 
2827
        {
 
2828
            delete mEmotionSprite;
 
2829
            mEmotionSprite = nullptr;
 
2830
            const EmoteInfo *const info = EmoteDB::get2(emotionIndex, true);
 
2831
            if (info)
 
2832
            {
 
2833
                const EmoteSprite *const sprite = info->sprites.front();
 
2834
                if (sprite)
 
2835
                {
 
2836
                    mEmotionSprite = AnimatedSprite::clone(sprite->sprite);
 
2837
                    if (mEmotionSprite)
 
2838
                        mEmotionTime = info->time;
 
2839
                    else
 
2840
                        mEmotionTime = emote_time;
 
2841
                }
 
2842
            }
 
2843
        }
 
2844
 
 
2845
        if (mEmotionSprite)
 
2846
        {
 
2847
            mEmotionSprite->play(mSpriteAction);
 
2848
            mEmotionSprite->setSpriteDirection(static_cast<SpriteDirection>(
 
2849
                mSpriteDirection));
 
2850
        }
 
2851
        else
 
2852
        {
 
2853
            mEmotionTime = 0;
 
2854
        }
 
2855
    }
 
2856
}
 
2857
 
 
2858
void Being::updatePercentHP()
 
2859
{
 
2860
    if (!mMaxHP || !serverVersion)
 
2861
        return;
 
2862
    if (mHP)
 
2863
    {
 
2864
        const unsigned num = mHP * 100 / mMaxHP;
 
2865
        if (num != mNumber)
 
2866
        {
 
2867
            mNumber = num;
 
2868
            if (updateNumber(mNumber))
 
2869
                setAction(mAction);
 
2870
        }
 
2871
    }
 
2872
}
 
2873
 
 
2874
uint8_t Being::genderToInt(const Gender sex)
 
2875
{
 
2876
    switch (sex)
 
2877
    {
 
2878
        case GENDER_FEMALE:
 
2879
        case GENDER_UNSPECIFIED:
 
2880
        default:
 
2881
            return 0;
 
2882
        case GENDER_MALE:
 
2883
            return 1;
 
2884
        case GENDER_OTHER:
 
2885
            return 3;
 
2886
    }
 
2887
}
 
2888
 
 
2889
Gender Being::intToGender(const uint8_t sex)
 
2890
{
 
2891
    switch (sex)
 
2892
    {
 
2893
        case 0:
 
2894
        default:
 
2895
            return GENDER_FEMALE;
 
2896
        case 1:
 
2897
            return GENDER_MALE;
 
2898
        case 3:
 
2899
            return GENDER_OTHER;
 
2900
    }
 
2901
}
 
2902
 
 
2903
int Being::getSpriteID(const int slot) const
 
2904
{
 
2905
    if (slot < 0 || static_cast<unsigned>(slot) >= mSpriteIDs.size())
 
2906
        return -1;
 
2907
 
 
2908
    return mSpriteIDs[slot];
 
2909
}
 
2910
 
 
2911
void Being::addAfkEffect()
 
2912
{
 
2913
    addSpecialEffect(mAwayEffect);
 
2914
}
 
2915
 
 
2916
void Being::removeAfkEffect()
 
2917
{
 
2918
    removeSpecialEffect();
 
2919
}
 
2920
 
 
2921
void Being::addSpecialEffect(const int effect)
 
2922
{
 
2923
    if (effectManager && Particle::enabled
 
2924
        && !mSpecialParticle && effect != -1)
 
2925
    {
 
2926
        mSpecialParticle = effectManager->triggerReturn(effect, this);
 
2927
    }
 
2928
}
 
2929
 
 
2930
void Being::removeSpecialEffect()
 
2931
{
 
2932
    if (effectManager && mSpecialParticle)
 
2933
    {
 
2934
        mChildParticleEffects.removeLocally(mSpecialParticle);
 
2935
        mSpecialParticle = nullptr;
 
2936
    }
 
2937
    delete mAnimationEffect;
 
2938
    mAnimationEffect = nullptr;
 
2939
}
 
2940
 
 
2941
void Being::updateAwayEffect()
 
2942
{
 
2943
    if (mAway)
 
2944
        addAfkEffect();
 
2945
    else
 
2946
        removeAfkEffect();
 
2947
}
 
2948
 
 
2949
void Being::addEffect(const std::string &name)
 
2950
{
 
2951
    delete mAnimationEffect;
 
2952
    mAnimationEffect = AnimatedSprite::load(
 
2953
        paths.getStringValue("sprites") + name);
 
2954
}
 
2955
 
 
2956
void Being::addPet(const int id)
 
2957
{
 
2958
    if (!actorSpriteManager)
 
2959
        return;
 
2960
 
 
2961
    removePet();
 
2962
    Being *const being = actorSpriteManager->createBeing(
 
2963
        id, ActorSprite::PET, 0);
 
2964
    if (being)
 
2965
    {
 
2966
        being->setTileCoords(getTileX(), getTileY());
 
2967
        being->setOwner(this);
 
2968
        mPetId = id;
 
2969
        mPet = being;
 
2970
    }
 
2971
}
 
2972
 
 
2973
void Being::removePet()
 
2974
{
 
2975
    if (!actorSpriteManager)
 
2976
        return;
 
2977
 
 
2978
    mPetId = 0;
 
2979
    if (mPet)
 
2980
    {
 
2981
        mPet->setOwner(nullptr);
 
2982
        actorSpriteManager->destroy(mPet);
 
2983
        mPet = nullptr;
 
2984
    }
 
2985
}
 
2986
 
 
2987
void Being::updatePets()
 
2988
{
 
2989
    removePet();
 
2990
    FOR_EACH (std::vector<int>::const_iterator, it, mSpriteIDs)
 
2991
    {
 
2992
        const int id = *it;
 
2993
        if (!id)
 
2994
            continue;
 
2995
        const ItemInfo &info = ItemDB::get(id);
 
2996
        const int pet = info.getPet();
 
2997
        if (pet)
 
2998
        {
 
2999
            addPet(pet);
 
3000
            return;
 
3001
        }
 
3002
    }
 
3003
}
 
3004
 
 
3005
void Being::playSfx(const SoundInfo &sound, Being *const being,
 
3006
                    const bool main, const int x, const int y)
 
3007
{
 
3008
    if (being)
 
3009
    {
 
3010
        // here need add timer and delay sound
 
3011
        const int time = tick_time;
 
3012
        if (main)
 
3013
        {
 
3014
            being->mNextSound.sound = nullptr;
 
3015
            being->mNextSound.time = time + sound.delay;
 
3016
            soundManager.playSfx(sound.sound, x, y);
 
3017
        }
 
3018
        else if (mNextSound.time <= time)
 
3019
        {   // old event sound time is gone. we can play new sound
 
3020
            being->mNextSound.sound = nullptr;
 
3021
            being->mNextSound.time = time + sound.delay;
 
3022
            soundManager.playSfx(sound.sound, x, y);
 
3023
        }
 
3024
        else
 
3025
        {   // old event sound in progress. need save sound and wait
 
3026
            being->mNextSound.sound = &sound;
 
3027
            being->mNextSound.x = x;
 
3028
            being->mNextSound.y = y;
 
3029
        }
 
3030
    }
 
3031
    else
 
3032
    {
 
3033
        soundManager.playSfx(sound.sound, x, y);
 
3034
    }
 
3035
}
 
3036
 
 
3037
void Being::setLook(const int look)
 
3038
{
 
3039
    if (mType == PLAYER)
 
3040
        setSubtype(mSubType, look);
 
3041
}