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
7
* This file is part of The ManaPlus Client.
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
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.
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/>.
23
#include "being/being.h"
25
#include "actorspritemanager.h"
26
#include "animatedsprite.h"
27
#include "beingequipbackend.h"
29
#include "configuration.h"
30
#include "effectmanager.h"
33
#include "soundmanager.h"
36
#include "being/beingcacheentry.h"
37
#include "being/playerrelations.h"
39
#include "particle/particle.h"
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"
47
#include "net/charserverhandler.h"
48
#include "net/gamehandler.h"
50
#include "net/npchandler.h"
51
#include "net/playerhandler.h"
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"
62
#include "gui/widgets/langtab.h"
63
#include "gui/widgets/skilldata.h"
64
#include "gui/widgets/skillinfo.h"
66
#include "utils/gettext.h"
70
const unsigned int CACHE_SIZE = 50;
72
int Being::mNumberOfHairstyles = 1;
73
int Being::mNumberOfRaces = 1;
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;
94
std::list<BeingCacheEntry*> beingInfoCache;
95
typedef std::map<int, Guild*>::const_iterator GuildsMapCIter;
96
typedef std::map<int, int>::const_iterator IntMapCIter;
98
Being::Being(const int id, const Type type, const uint16_t subtype,
102
mInfo(BeingInfo::unknown),
103
mEmotionSprite(nullptr),
104
mAnimationEffect(nullptr),
105
mSpriteAction(SpriteAction::STAND),
113
mEquippedWeapon(nullptr),
129
mGender(GENDER_UNSPECIFIED),
133
mDirectionDelayed(0),
134
mSpriteDirection(DIRECTION_DOWN),
138
mSpeechBubble(new SpeechBubble),
139
mWalkSpeed(Net::getPlayerHandler()->getDefaultWalkSpeed()),
140
mSpeed(Net::getPlayerHandler()->getDefaultWalkSpeed().x),
142
mSpriteRemap(new int[20]),
143
mSpriteHide(new int[20]),
147
mSpecialParticle(nullptr),
154
mIsReachable(REACH_UNKNOWN),
178
for (int f = 0; f < 20; f ++)
185
setSubtype(subtype, 0);
188
mShowName = config.getBoolValue("visiblenames");
189
else if (mType != NPC)
192
config.addListener("visiblenames", this);
199
setShowName(mShowName);
207
config.removeListener("visiblenames", this);
209
delete [] mSpriteRemap;
210
mSpriteRemap = nullptr;
211
delete [] mSpriteHide;
212
mSpriteHide = nullptr;
214
delete mSpeechBubble;
215
mSpeechBubble = nullptr;
221
delete mEmotionSprite;
222
mEmotionSprite = nullptr;
223
delete mAnimationEffect;
224
mAnimationEffect = nullptr;
227
mOwner->setPet(nullptr);
229
mPet->setOwner(nullptr);
232
void Being::setSubtype(const uint16_t subtype, const uint8_t look)
237
if (subtype == mSubType && mLook == look)
243
if (mType == MONSTER)
245
mInfo = MonsterDB::get(mSubType);
248
setName(mInfo->getName());
249
setupSpriteDisplay(mInfo->getDisplay(), true, 0,
250
mInfo->getColor(mLook));
251
mYDiff = mInfo->getSortOffsetY();
254
else if (mType == NPC)
256
mInfo = NPCDB::get(mSubType);
259
setupSpriteDisplay(mInfo->getDisplay(), false);
260
mYDiff = mInfo->getSortOffsetY();
263
else if (mType == AVATAR)
265
mInfo = AvatarDB::get(mSubType);
267
setupSpriteDisplay(mInfo->getDisplay(), false);
269
else if (mType == PET)
271
mInfo = PETDB::get(mId);
274
setupSpriteDisplay(mInfo->getDisplay(), false);
275
mYDiff = mInfo->getSortOffsetY();
278
else if (mType == PLAYER)
280
int id = -100 - subtype;
282
// Prevent showing errors when sprite doesn't exist
283
if (!ItemDB::exists(id))
286
// TRANSLATORS: default race name
287
setRaceName(_("Human"));
288
if (Net::getCharServerHandler())
289
setSprite(Net::getCharServerHandler()->baseSprite(), id);
293
const ItemInfo &info = ItemDB::get(id);
294
setRaceName(info.getName());
295
if (Net::getCharServerHandler())
297
setSprite(Net::getCharServerHandler()->baseSprite(),
298
id, info.getColor(mLook));
304
ActorSprite::TargetCursorSize Being::getTargetCursorSize() const
307
return ActorSprite::TC_SMALL;
309
return mInfo->getTargetCursorSize();
312
void Being::setPosition(const Vector &pos)
314
Actor::setPosition(pos);
320
mText->adviseXY(static_cast<int>(pos.x), static_cast<int>(pos.y)
321
- getHeight() - mText->getHeight() - 6, mMoveNames);
325
void Being::setDestination(const int dstX, const int dstY)
327
// We can't calculate anything without a map anyway.
331
#ifdef MANASERV_SUPPORT
332
if (Net::getNetworkType() != ServerInfo::MANASERV)
335
setPath(mMap->findPath(mX, mY, dstX, dstY, getWalkMask()));
339
#ifdef MANASERV_SUPPORT
340
// Don't handle flawed destinations from server...
341
if (dstX == 0 || dstY == 0)
344
// If the destination is unwalkable, don't bother trying to get there
345
if (!mMap->getWalk(dstX / 32, dstY / 32))
348
Position dest = mMap->checkNodeOffsets(getCollisionRadius(), getWalkMask(),
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()));
355
if (thisPath.empty())
357
// If there is no path but the destination is on the same walkable tile,
359
if (static_cast<int>(mPos.x) / 32 == dest.x / 32
360
&& static_cast<int>(mPos.y) / 32 == dest.y / 32)
362
mDest.x = static_cast<float>(dest.x);
363
mDest.y = static_cast<float>(dest.y);
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);
377
void Being::clearPath()
382
void Being::setPath(const Path &path)
388
#ifdef MANASERV_SUPPORT
389
if ((Net::getNetworkType() != ServerInfo::MANASERV) &&
390
mAction != MOVE && mAction != DEAD)
392
if (mAction != MOVE && mAction != DEAD)
396
mActionTime = tick_time;
400
void Being::setSpeech(const std::string &text, const std::string &channel,
406
if (!channel.empty() && (langChatTab && langChatTab->getChannelName()
413
mSpeech = removeColors(text);
418
const unsigned int lineLim = mConfLineLim;
419
if (lineLim > 0 && mSpeech.length() > lineLim)
420
mSpeech = mSpeech.substr(0, lineLim);
428
const size_t sz = mSpeech.size();
430
time = static_cast<int>(SPEECH_TIME - 300 + (3 * sz));
433
if (time < static_cast<int>(SPEECH_MIN_TIME))
434
time = static_cast<int>(SPEECH_MIN_TIME);
437
size_t start = mSpeech.find('[');
438
size_t e = mSpeech.find(']', start);
440
while (start != std::string::npos && e != std::string::npos)
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))
446
start = mSpeech.find('[', start + 1);
449
size_t position = mSpeech.find('|');
450
if (mSpeech[start + 1] == '@' && mSpeech[start + 2] == '@')
453
mSpeech.erase(start, (position - start) + 1);
455
position = mSpeech.find('@');
457
while (position != std::string::npos)
459
mSpeech.erase(position, 2);
460
position = mSpeech.find('@');
463
start = mSpeech.find('[', start + 1);
464
e = mSpeech.find(']', start);
467
if (!mSpeech.empty())
469
mSpeechTime = time <= static_cast<int>(SPEECH_MAX_TIME)
470
? time : static_cast<int>(SPEECH_MAX_TIME);
473
const int speech = mSpeechType;
474
if (speech == TEXT_OVERHEAD && userPalette)
478
mText = new Text(mSpeech,
479
getPixelX(), getPixelY() - getHeight(),
480
gcn::Graphics::CENTER,
481
&userPalette->getColor(UserPalette::PARTICLE),
486
void Being::takeDamage(Being *const attacker, const int amount,
487
const AttackType type, const int attackId)
489
if (!userPalette || !attacker)
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;
499
font = gui->getInfoParticleFont();
501
// Selecting the right color
502
if (type == CRITICAL || type == FLEE)
504
if (type == CRITICAL)
505
attacker->setCriticalHit(amount);
507
if (attacker == player_node)
509
color = &userPalette->getColor(
510
UserPalette::HIT_LOCAL_PLAYER_CRITICAL);
514
color = &userPalette->getColor(UserPalette::HIT_CRITICAL);
519
if (attacker == player_node)
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);
527
color = &userPalette->getColor(UserPalette::MISS);
530
else if (mType == MONSTER)
532
if (attacker == player_node)
534
color = &userPalette->getColor(
535
UserPalette::HIT_LOCAL_PLAYER_MONSTER);
539
color = &userPalette->getColor(
540
UserPalette::HIT_PLAYER_MONSTER);
543
else if (mType == PLAYER && attacker != player_node
544
&& this == player_node)
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();
553
color = &userPalette->getColor(UserPalette::HIT_MONSTER_PLAYER);
556
if (chatWindow && mShowBattleEvents)
558
if (this == player_node)
560
if (attacker->mType == PLAYER || amount)
562
chatWindow->battleChatLog(strprintf("%s : Hit you -%d",
563
attacker->getName().c_str(), amount), BY_OTHER);
566
else if (attacker == player_node && amount)
568
chatWindow->battleChatLog(strprintf("%s : You hit %s -%d",
569
attacker->getName().c_str(), getName().c_str(), amount),
573
if (font && particleEngine)
575
// Show damage number
576
particleEngine->addTextSplashEffect(damage,
577
getPixelX(), getPixelY() - 16, color, font, true);
581
attacker->updateHit(amount);
585
if (player_node && player_node == this)
586
player_node->setLastHitFrom(attacker->getName());
588
mDamageTaken += amount;
591
playSfx(mInfo->getSound(SOUND_EVENT_HURT), this, false, mX, mY);
593
if (!mInfo->isStaticMaxHP())
595
if (!mHP && mInfo->getMaxHP() < mDamageTaken)
596
mInfo->setMaxHP(mDamageTaken);
599
if (mHP && isAlive())
606
if (mType == MONSTER)
611
else if (mType == PLAYER && socialWindow && getName() != "")
613
socialWindow->updateAvatar(getName());
618
const int hitEffectId = getHitEffect(attacker, type, attackId);
619
if (hitEffectId >= 0)
620
effectManager->trigger(hitEffectId, this);
627
const int hitEffectId = getHitEffect(attacker,
629
if (hitEffectId >= 0)
630
effectManager->trigger(hitEffectId, this);
635
int Being::getHitEffect(const Being *const attacker,
636
const AttackType type, const int attackId) const
641
// Init the particle effect path based on current
642
// weapon or default.
648
const ItemInfo *attackerWeapon = attacker->getEquippedWeapon();
649
if (attackerWeapon && attacker->getType() == PLAYER)
652
hitEffectId = attackerWeapon->getMissEffectId();
653
else if (type != CRITICAL)
654
hitEffectId = attackerWeapon->getHitEffectId();
656
hitEffectId = attackerWeapon->getCriticalHitEffectId();
658
else if (attacker->getType() == MONSTER)
660
const BeingInfo *const info = attacker->getInfo();
663
const Attack *atk = info->getAttack(attackId);
667
hitEffectId = atk->mMissEffectId;
668
else if (type != CRITICAL)
669
hitEffectId = atk->mHitEffectId;
671
hitEffectId = atk->mCriticalHitEffectId;
675
hitEffectId = getDefaultEffectId(type);
681
hitEffectId = getDefaultEffectId(type);
686
hitEffectId = getDefaultEffectId(type);
691
// move skills effects to +100000 in effects list
692
hitEffectId = attackId + 100000;
697
int Being::getDefaultEffectId(const int type)
700
return paths.getIntValue("missEffectId");
701
else if (type != CRITICAL)
702
return paths.getIntValue("hitEffectId");
704
return paths.getIntValue("criticalHitEffectId");
707
void Being::handleAttack(Being *const victim, const int damage,
710
if (!victim || !mInfo)
713
if (this != player_node)
714
setAction(Being::ATTACK, attackId);
716
if (mType == PLAYER && mEquippedWeapon)
717
fireMissile(victim, mEquippedWeapon->getMissileParticleFile());
718
else if (mInfo->getAttack(attackId))
719
fireMissile(victim, mInfo->getAttack(attackId)->mMissileParticle);
721
#ifdef MANASERV_SUPPORT
722
if (Net::getNetworkType() != ServerInfo::MANASERV)
726
mActionTime = tick_time;
729
if (this != player_node)
731
const uint8_t dir = calcDirection(victim->getTileX(),
736
if (damage && victim->mType == PLAYER && victim->mAction == SIT)
737
victim->setAction(STAND);
741
if (mSpriteIDs.size() >= 10)
743
// here 10 is weapon slot
744
int weaponId = mSpriteIDs[10];
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);
754
playSfx(mInfo->getSound((damage > 0) ?
755
SOUND_EVENT_HIT : SOUND_EVENT_MISS), victim, true, mX, mY);
759
void Being::handleSkill(Being *const victim, const int damage,
760
const int skillId, const int skillLevel)
762
if (!victim || !mInfo || !skillDialog)
765
if (this != player_node)
766
setAction(Being::ATTACK, 1);
768
SkillInfo *const skill = skillDialog->getSkill(skillId);
769
const SkillData *const data = skill
770
? skill->getData1(skillLevel) : nullptr;
772
fireMissile(victim, data->particle);
774
#ifdef MANASERV_SUPPORT
775
if (Net::getNetworkType() != ServerInfo::MANASERV)
779
mActionTime = tick_time;
782
if (this != player_node)
784
const uint8_t dir = calcDirection(victim->getTileX(),
789
if (damage && victim->mType == PLAYER && victim->mAction == SIT)
790
victim->setAction(STAND);
794
playSfx(data->soundHit, victim, true, mX, mY);
796
playSfx(data->soundMiss, victim, true, mX, mY);
800
playSfx(mInfo->getSound((damage > 0) ?
801
SOUND_EVENT_HIT : SOUND_EVENT_MISS), victim, true, mX, mY);
805
void Being::setName(const std::string &name)
809
mName = name.substr(0, name.find('#', 0));
816
if (mType == PLAYER && getShowName())
821
void Being::setShowName(const bool doShowName)
823
if (mShowName == doShowName)
826
mShowName = doShowName;
839
void Being::setGuildName(const std::string &name)
844
void Being::setGuildPos(const std::string &pos A_UNUSED)
848
void Being::addGuild(Guild *const guild)
853
mGuilds[guild->getId()] = guild;
855
if (this == player_node && socialWindow)
856
socialWindow->addTab(guild);
859
void Being::removeGuild(const int id)
861
if (this == player_node && socialWindow)
862
socialWindow->removeTab(mGuilds[id]);
865
mGuilds[id]->removeMember(getName());
869
Guild *Being::getGuild(const std::string &guildName) const
871
FOR_EACH (GuildsMapCIter, itr, mGuilds)
873
Guild *const guild = itr->second;
874
if (guild && guild->getName() == guildName)
881
Guild *Being::getGuild(const int id) const
883
const std::map<int, Guild*>::const_iterator itr = mGuilds.find(id);
884
if (itr != mGuilds.end())
890
Guild *Being::getGuild() const
892
const std::map<int, Guild*>::const_iterator itr = mGuilds.begin();
893
if (itr != mGuilds.end())
899
void Being::clearGuilds()
901
FOR_EACH (GuildsMapCIter, itr, mGuilds)
903
Guild *const guild = itr->second;
907
if (this == player_node && socialWindow)
908
socialWindow->removeTab(guild);
910
guild->removeMember(mId);
917
void Being::setParty(Party *const party)
922
Party *const old = mParty;
926
old->removeMember(mId);
929
party->addMember(mId, mName);
933
if (this == player_node && socialWindow)
936
socialWindow->removeTab(old);
939
socialWindow->addTab(party);
943
void Being::updateGuild()
948
Guild *const guild = player_node->getGuild();
955
if (guild->getMember(getName()))
958
if (!guild->getName().empty())
959
mGuildName = guild->getName();
964
void Being::setGuild(Guild *const guild)
966
Guild *const old = getGuild();
974
old->removeMember(mName);
978
if (this == player_node && socialWindow)
981
socialWindow->removeTab(old);
984
socialWindow->addTab(guild);
988
void Being::fireMissile(Being *const victim, const std::string &particle) const
990
if (!victim || particle.empty() || !particleEngine)
993
Particle *const target = particleEngine->createChild();
998
Particle *const missile = target->addEffect(
999
particle, getPixelX(), getPixelY());
1003
target->moveBy(Vector(0.0f, 0.0f, 32.0f));
1004
target->setLifetime(1000);
1005
victim->controlParticle(target);
1007
missile->setDestination(target, 7, 0);
1008
missile->setDieDistance(8);
1009
missile->setLifetime(900);
1013
std::string Being::getSitAction() const
1015
if (serverVersion < 0)
1017
return SpriteAction::SIT;
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;
1031
return SpriteAction::SIT;
1036
std::string Being::getMoveAction() const
1038
if (serverVersion < 0)
1040
return SpriteAction::MOVE;
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;
1052
return SpriteAction::MOVE;
1056
std::string Being::getWeaponAttackAction(const ItemInfo *const weapon) const
1059
return SpriteAction::ATTACK;
1061
if (serverVersion < 0 || !weapon)
1063
return weapon->getAttackAction();
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();
1075
return weapon->getAttackAction();
1079
std::string Being::getAttackAction(const Attack *const attack1) const
1082
return SpriteAction::ATTACK;
1084
if (serverVersion < 0 || !attack1)
1086
return attack1->mAction;
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;
1098
return attack1->mAction;
1102
#define getSpriteAction(func, action) \
1103
std::string Being::get##func##Action() const \
1105
if (serverVersion < 0) \
1107
return SpriteAction::action; \
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; \
1119
return SpriteAction::action; \
1123
getSpriteAction(Dead, DEAD)
1124
getSpriteAction(Stand, STAND)
1125
getSpriteAction(Spawn, SPAWN)
1127
void Being::setAction(const Action &action, const int attackId)
1129
std::string currentAction = SpriteAction::INVALID;
1136
playSfx(mInfo->getSound(
1137
SOUND_EVENT_MOVE), nullptr, true, mX, mY);
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.
1145
currentAction = getSitAction();
1149
if (currentAction == SpriteAction::SITTOP)
1150
event = SOUND_EVENT_SITTOP;
1152
event = SOUND_EVENT_SIT;
1153
playSfx(mInfo->getSound(event), nullptr, true, mX, mY);
1157
if (mEquippedWeapon)
1159
currentAction = getWeaponAttackAction(mEquippedWeapon);
1164
if (!mInfo || !mInfo->getAttack(attackId))
1167
currentAction = getAttackAction(mInfo->getAttack(attackId));
1170
// attack particle effect
1171
if (Particle::enabled)
1173
const int effectId = mInfo->getAttack(attackId)->mEffectId;
1176
switch (mSpriteDirection)
1178
case DIRECTION_DOWN:
1182
case DIRECTION_LEFT:
1188
case DIRECTION_RIGHT:
1192
if (Particle::enabled && effectManager && effectId >= 0)
1193
effectManager->trigger(effectId, this, rotation);
1200
playSfx(mInfo->getSound(SOUND_EVENT_HURT),
1201
this, false, mX, mY);
1205
currentAction = getDeadAction();
1208
playSfx(mInfo->getSound(SOUND_EVENT_DIE), this, false, mX, mY);
1209
if (mType == MONSTER || mType == NPC)
1210
mYDiff = mInfo->getDeadSortOffsetY();
1214
currentAction = getStandAction();
1219
playSfx(mInfo->getSound(SOUND_EVENT_SPAWN),
1220
nullptr, true, mX, mY);
1222
currentAction = getSpawnAction();
1225
logger->log("Being::setAction unknown action: "
1226
+ toString(static_cast<unsigned>(action)));
1230
if (currentAction != SpriteAction::INVALID)
1232
mSpriteAction = currentAction;
1233
play(currentAction);
1235
mEmotionSprite->play(currentAction);
1236
if (mAnimationEffect)
1237
mAnimationEffect->play(currentAction);
1241
if (currentAction != SpriteAction::MOVE
1242
&& currentAction != SpriteAction::FLY
1243
&& currentAction != SpriteAction::SWIM)
1245
mActionTime = tick_time;
1249
void Being::setDirection(const uint8_t direction)
1251
if (mDirection == direction)
1254
mDirection = direction;
1256
mDirectionDelayed = 0;
1258
// if the direction does not change much, keep the common component
1259
int mFaceDirection = mDirection & direction;
1260
if (!mFaceDirection)
1261
mFaceDirection = direction;
1263
SpriteDirection dir;
1264
if (mFaceDirection & UP)
1266
if (mFaceDirection & LEFT)
1267
dir = DIRECTION_UPLEFT;
1268
else if (mFaceDirection & RIGHT)
1269
dir = DIRECTION_UPRIGHT;
1273
else if (mFaceDirection & DOWN)
1275
if (mFaceDirection & LEFT)
1276
dir = DIRECTION_DOWNLEFT;
1277
else if (mFaceDirection & RIGHT)
1278
dir = DIRECTION_DOWNRIGHT;
1280
dir = DIRECTION_DOWN;
1282
else if (mFaceDirection & RIGHT)
1284
dir = DIRECTION_RIGHT;
1288
dir = DIRECTION_LEFT;
1290
mSpriteDirection = static_cast<uint8_t>(dir);
1292
CompoundSprite::setSpriteDirection(dir);
1294
mEmotionSprite->setSpriteDirection(dir);
1295
if (mAnimationEffect)
1296
mAnimationEffect->setSpriteDirection(dir);
1297
recalcSpritesOrder();
1300
uint8_t Being::calcDirection() const
1305
else if (mDest.x < mX)
1309
else if (mDest.y < mY)
1314
uint8_t Being::calcDirection(const int dstX, const int dstY) const
1328
void Being::nextTile()
1336
const Position pos = mPath.front();
1339
const uint8_t dir = calcDirection(pos.x, pos.y);
1343
if (!mMap || !mMap->getWalk(pos.x, pos.y, getWalkMask()))
1349
mActionTime += static_cast<int>(mSpeed / 10);
1350
if ((mType != PLAYER || mUseDiagonal) && mX != pos.x && mY != pos.y)
1351
mSpeed = mWalkSpeed.x * 1.4;
1353
mSpeed = mWalkSpeed.x;
1362
BLOCK_START("Being::logic")
1363
// Reduce the time that speech is still displayed
1364
if (mSpeechTime > 0)
1367
// Remove text and speechbubbles if speech boxes aren't being used
1368
if (mSpeechTime == 0 && mText)
1374
const int time = tick_time * MILLISECONDS_IN_A_TICK;
1376
mEmotionSprite->update(time);
1378
if (mAnimationEffect)
1380
mAnimationEffect->update(time);
1381
if (mAnimationEffect->isTerminated())
1383
delete mAnimationEffect;
1384
mAnimationEffect = nullptr;
1388
int frameCount = static_cast<int>(getFrameCount());
1389
#ifdef MANASERV_SUPPORT
1390
if ((Net::getNetworkType() == ServerInfo::MANASERV) && (mAction != DEAD))
1392
const Vector dest = (mPath.empty()) ?
1393
mDest : Vector(static_cast<float>(mPath.front().x),
1394
static_cast<float>(mPath.front().y));
1396
// This is a hack that stops NPCs from running off the map...
1397
if (mDest.x <= 0 && mDest.y <= 0)
1399
BLOCK_END("Being::logic")
1403
// The Vector representing the difference between current position
1404
// and the next destination path node.
1405
Vector dir = dest - mPos;
1407
const float nominalLength = dir.length();
1409
// When we've not reached our destination, move to it.
1410
if (nominalLength > 0.0f && !mWalkSpeed.isNull())
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);
1420
// Test if we don't miss the destination by a move too far:
1421
if (diff.length() > nominalLength)
1423
setPosition(mPos + dir);
1425
// Also, if the destination is reached, try to get the next
1426
// path point, if existing.
1430
// Otherwise, go to it using the nominal speed.
1433
setPosition(mPos + diff);
1436
if (mAction != MOVE)
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)
1444
const float dx = std::abs(dir.x);
1445
float dy = std::abs(dir.y);
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())
1456
direction |= (dir.x > 0) ? RIGHT : LEFT;
1458
direction |= (dir.y > 0) ? DOWN : UP;
1460
setDirection(static_cast<uint8_t>(direction));
1463
else if (!mPath.empty())
1465
// If the current path node has been reached,
1466
// remove it and go to the next one.
1469
else if (mAction == MOVE)
1475
if (Net::getNetworkType() != ServerInfo::MANASERV)
1490
if (static_cast<float>(get_elapsed_time(
1491
mActionTime)) >= mSpeed)
1506
curFrame = (get_elapsed_time(mActionTime) * frameCount)
1510
if (this == player_node && curFrame >= frameCount)
1517
// Update pixel coordinates
1518
setPosition(static_cast<float>(mX * 32 + 16 + getXOffset()),
1519
static_cast<float>(mY * 32 + 32 + getYOffset()));
1525
if (mEmotionTime == 0)
1527
delete mEmotionSprite;
1528
mEmotionSprite = nullptr;
1532
ActorSprite::logic();
1534
if (frameCount < 10)
1537
if (!isAlive() && mSpeed && Net::getGameHandler()->removeDeadBeings()
1538
&& static_cast<int> ((static_cast<float>(get_elapsed_time(mActionTime))
1539
/ mSpeed)) >= frameCount)
1541
if (mType != PLAYER && actorSpriteManager)
1542
actorSpriteManager->destroy(this);
1545
const SoundInfo *const sound = mNextSound.sound;
1548
const int time2 = tick_time;
1549
if (time2 > mNextSound.time)
1551
soundManager.playSfx(sound->sound, mNextSound.x, mNextSound.y);
1553
mNextSound.sound = nullptr;
1554
mNextSound.time = time2 + sound->delay;
1558
BLOCK_END("Being::logic")
1561
void Being::drawEmotion(Graphics *const graphics, const int offsetX,
1564
const int px = getPixelX() - offsetX - 16;
1565
const int py = getPixelY() - offsetY - 64 - 32;
1567
mEmotionSprite->draw(graphics, px, py);
1568
if (mAnimationEffect)
1569
mAnimationEffect->draw(graphics, px, py);
1572
void Being::drawSpeech(const int offsetX, const int offsetY)
1574
if (!mSpeechBubble || mSpeech.empty())
1577
const int px = getPixelX() - offsetX;
1578
const int py = getPixelY() - offsetY;
1579
const int speech = mSpeechType;
1581
// Draw speech above this being
1582
if (mSpeechTime == 0)
1584
if (mSpeechBubble->isVisible())
1585
mSpeechBubble->setVisible(false);
1587
else if (mSpeechTime > 0 && (speech == NAME_IN_BUBBLE ||
1588
speech == NO_NAME_IN_BUBBLE))
1590
const bool isShowName = (speech == NAME_IN_BUBBLE);
1595
mSpeechBubble->setCaption(isShowName ? mName : "");
1597
mSpeechBubble->setText(mSpeech, isShowName);
1598
mSpeechBubble->setPosition(px - (mSpeechBubble->getWidth() / 2),
1599
py - getHeight() - (mSpeechBubble->getHeight()));
1600
mSpeechBubble->setVisible(true);
1602
else if (mSpeechTime > 0 && speech == TEXT_OVERHEAD)
1604
mSpeechBubble->setVisible(false);
1606
if (!mText && userPalette)
1608
mText = new Text(mSpeech, getPixelX(), getPixelY() - getHeight(),
1609
gcn::Graphics::CENTER, &Theme::getThemeColor(
1610
Theme::BUBBLE_TEXT), true);
1613
else if (speech == NO_SPEECH)
1615
mSpeechBubble->setVisible(false);
1622
int Being::getOffset(const signed char pos, const signed char neg) const
1624
// Check whether we're walking in the requested direction
1625
if (mAction != MOVE || !(mDirection & (pos | neg)))
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);
1640
// We calculate the offset _from_ the _target_ location
1645
// Going into negative direction? Invert the offset.
1646
if (mDirection & pos)
1657
void Being::updateCoords()
1662
int offsetX = getPixelX();
1663
int offsetY = getPixelY();
1666
offsetX += mInfo->getNameOffsetX();
1667
offsetY += mInfo->getNameOffsetY();
1669
// Monster names show above the sprite instead of below it
1670
if (mType == MONSTER)
1671
offsetY += - getHeight() - mDispName->getHeight();
1673
mDispName->adviseXY(offsetX, offsetY, mMoveNames);
1676
void Being::optionChanged(const std::string &value)
1678
if (mType == PLAYER && value == "visiblenames")
1679
setShowName(config.getBoolValue("visiblenames"));
1682
void Being::flashName(const int time)
1685
mDispName->flash(time);
1688
std::string Being::getGenderSignWithSpace() const
1690
const std::string &str = getGenderSign();
1694
return std::string(" ").append(str);
1697
std::string Being::getGenderSign() const
1702
if (getGender() == GENDER_FEMALE)
1704
else if (getGender() == GENDER_MALE)
1707
if (mShowPlayersStatus && mAdvanced)
1713
// TRANSLATORS: this away status writed in player nick
1718
// TRANSLATORS: this inactive status writed in player nick
1725
void Being::showName()
1731
mDispName = nullptr;
1733
if (mHideErased && player_relations.getRelation(mName) ==
1734
PlayerRelation::ERASED)
1739
std::string displayName(mName);
1741
if (mType != MONSTER && (mShowGender || mShowLevel))
1743
displayName.append(" ");
1744
if (mShowLevel && getLevel() != 0)
1745
displayName.append(toString(getLevel()));
1747
displayName.append(getGenderSign());
1750
if (mType == MONSTER)
1752
if (config.getBoolValue("showMonstersTakedDamage"))
1753
displayName.append(", ").append(toString(getDamageTaken()));
1756
gcn::Font *font = nullptr;
1757
if (player_node && player_node->getTarget() == this
1758
&& mType != MONSTER)
1762
else if (mType == PLAYER && !player_relations.isGoodName(this) && gui)
1764
font = gui->getSecureFont();
1769
mDispName = new FlashText(displayName,
1770
getPixelX() + mInfo->getNameOffsetX(),
1771
getPixelY() + mInfo->getNameOffsetY(),
1772
gcn::Graphics::CENTER, mNameColor, font);
1776
mDispName = new FlashText(displayName, getPixelX(), getPixelY(),
1777
gcn::Graphics::CENTER, mNameColor, font);
1783
void Being::updateColors()
1787
if (mType == MONSTER)
1789
mNameColor = &userPalette->getColor(UserPalette::MONSTER);
1790
mTextColor = &userPalette->getColor(UserPalette::MONSTER);
1792
else if (mType == NPC)
1794
mNameColor = &userPalette->getColor(UserPalette::NPC);
1795
mTextColor = &userPalette->getColor(UserPalette::NPC);
1797
else if (this == player_node)
1799
mNameColor = &userPalette->getColor(UserPalette::SELF);
1800
mTextColor = &Theme::getThemeColor(Theme::PLAYER);
1804
mTextColor = &Theme::getThemeColor(Theme::PLAYER);
1806
if (player_relations.getRelation(mName) != PlayerRelation::ERASED)
1813
mTextColor = &userPalette->getColor(UserPalette::GM);
1814
mNameColor = &userPalette->getColor(UserPalette::GM);
1818
mNameColor = &userPalette->getColor(UserPalette::MONSTER);
1820
else if (mParty && mParty == player_node->getParty())
1822
mNameColor = &userPalette->getColor(UserPalette::PARTY);
1824
else if (player_node && getGuild()
1825
&& getGuild() == player_node->getGuild())
1827
mNameColor = &userPalette->getColor(UserPalette::GUILD);
1829
else if (player_relations.getRelation(mName) ==
1830
PlayerRelation::FRIEND)
1832
mNameColor = &userPalette->getColor(UserPalette::FRIEND);
1834
else if (player_relations.getRelation(mName) ==
1835
PlayerRelation::DISREGARDED
1836
|| player_relations.getRelation(mName) ==
1837
PlayerRelation::BLACKLISTED)
1839
mNameColor = &userPalette->getColor(UserPalette::DISREGARDED);
1841
else if (player_relations.getRelation(mName) ==
1842
PlayerRelation::IGNORED
1843
|| player_relations.getRelation(mName) ==
1844
PlayerRelation::ENEMY2)
1846
mNameColor = &userPalette->getColor(UserPalette::IGNORED);
1848
else if (player_relations.getRelation(mName) ==
1849
PlayerRelation::ERASED)
1851
mNameColor = &userPalette->getColor(UserPalette::ERASED);
1855
mNameColor = &userPalette->getColor(UserPalette::PC);
1860
mDispName->setColor(mNameColor);
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)
1868
if (slot >= Net::getCharServerHandler()->maxSprite())
1872
ensureSize(slot + 1);
1874
if (slot >= mSpriteIDs.size())
1875
mSpriteIDs.resize(slot + 1, 0);
1877
if (slot >= mSpriteColors.size())
1878
mSpriteColors.resize(slot + 1, "");
1880
if (slot >= mSpriteColorsIds.size())
1881
mSpriteColorsIds.resize(slot + 1, 1);
1883
// id = 0 means unequip
1889
mEquippedWeapon = nullptr;
1890
const int id1 = mSpriteIDs[slot];
1893
const ItemInfo &info = ItemDB::get(id1);
1896
const int pet = info.getPet();
1904
const ItemInfo &info = ItemDB::get(id);
1905
const std::string filename = info.getSprite(mGender, mSubType);
1906
AnimatedSprite *equipmentSprite = nullptr;
1908
if (mType == PLAYER)
1910
const int pet = info.getPet();
1915
if (!filename.empty())
1918
color = info.getDyeColorsString(colorId);
1920
equipmentSprite = AnimatedSprite::delayedLoad(
1921
paths.getStringValue("sprites").append(
1922
combineDye(filename, color)));
1925
if (equipmentSprite)
1926
equipmentSprite->setSpriteDirection(getSpriteDirection());
1928
CompoundSprite::setSprite(slot, equipmentSprite);
1931
mEquippedWeapon = &ItemDB::get(id);
1938
mSpriteIDs[slot] = id;
1939
mSpriteColors[slot] = color;
1940
mSpriteColorsIds[slot] = colorId;
1941
recalcSpritesOrder();
1942
if (beingEquipmentWindow)
1943
beingEquipmentWindow->updateBeing(this);
1947
void Being::setSpriteID(const unsigned int slot, const int id)
1949
setSprite(slot, id, mSpriteColors[slot]);
1952
void Being::setSpriteColor(const unsigned int slot, const std::string &color)
1954
setSprite(slot, mSpriteIDs[slot], color);
1957
void Being::setHairStyle(const unsigned int slot, const int id)
1960
setSprite(slot, id, ItemDB::get(id).getDyeColorsString(mHairColor));
1964
void Being::setHairColor(const unsigned int slot, const unsigned char color)
1967
setSprite(slot, mSpriteIDs[slot], ItemDB::get(
1968
getSpriteID(slot)).getDyeColorsString(color));
1971
void Being::dumpSprites() const
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();
1980
logger->log("sprites");
1981
for (; it1 != it1_end && it2 != it2_end && it3 != it3_end;
1982
++ it1, ++ it2, ++ it3)
1984
logger->log("%d,%s,%d", *it1, (*it2).c_str(), *it3);
1990
// Hairstyles are encoded as negative numbers. Count how far negative
1993
while (ItemDB::get(-hairstyles).getSprite(GENDER_MALE, 0) !=
1994
paths.getStringValue("spriteErrorFile"))
1998
mNumberOfHairstyles = hairstyles;
2001
while (ItemDB::get(-races).getSprite(GENDER_MALE, 0) !=
2002
paths.getStringValue("spriteErrorFile"))
2006
mNumberOfRaces = races - 100;
2009
void Being::updateName()
2015
void Being::reReadConfig()
2017
BLOCK_START("Being::reReadConfig")
2018
if (mUpdateConfigTime + 1 < cur_time)
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");
2039
mUpdateConfigTime = cur_time;
2041
BLOCK_END("Being::reReadConfig")
2044
bool Being::updateFromCache()
2046
const BeingCacheEntry *const entry = Being::getCacheEntry(getId());
2048
if (entry && entry->getTime() + 120 >= cur_time)
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());
2058
mAdvanced = entry->isAdvanced();
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)
2076
if (mType == PLAYER)
2083
void Being::addToCache() const
2085
if (player_node == this)
2088
BeingCacheEntry *entry = Being::getCacheEntry(getId());
2091
entry = new BeingCacheEntry(getId());
2092
beingInfoCache.push_front(entry);
2094
if (beingInfoCache.size() >= CACHE_SIZE)
2096
delete beingInfoCache.back();
2097
beingInfoCache.pop_back();
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());
2119
flags += FLAG_INACTIVE;
2120
entry->setFlags(flags);
2128
BeingCacheEntry* Being::getCacheEntry(const int id)
2130
FOR_EACH (std::list<BeingCacheEntry*>::iterator, i, beingInfoCache)
2135
if (id == (*i)->getId())
2137
// Raise priority: move it to front
2138
if ((*i)->getTime() + 120 < cur_time)
2140
beingInfoCache.splice(beingInfoCache.begin(),
2150
void Being::setGender(const Gender gender)
2152
if (gender != mGender)
2156
// Reload all subsprites
2157
for (unsigned int i = 0; i < mSpriteIDs.size(); i++)
2159
if (mSpriteIDs.at(i) != 0)
2160
setSprite(i, mSpriteIDs.at(i), mSpriteColors.at(i));
2167
void Being::setGM(const bool gm)
2174
void Being::talkTo() const
2176
if (!client->limitPackets(PACKET_NPC_TALK))
2179
Net::getNpcHandler()->talk(mId);
2182
bool Being::draw(Graphics *const graphics,
2183
const int offsetX, const int offsetY) const
2187
res = ActorSprite::draw(graphics, offsetX, offsetY);
2192
void Being::drawSprites(Graphics *const graphics,
2193
const int posX, const int posY) const
2195
const int sz = getNumberOfLayers();
2196
for (int f = 0; f < sz; f ++)
2198
const int rSprite = mSpriteHide[mSpriteRemap[f]];
2202
Sprite *const sprite = getSprite(mSpriteRemap[f]);
2205
sprite->setAlpha(mAlpha);
2206
sprite->draw(graphics, posX, posY);
2211
void Being::drawSpritesSDL(Graphics *const graphics,
2212
const int posX, const int posY) const
2214
const size_t sz = size();
2215
for (unsigned f = 0; f < sz; f ++)
2217
const int rSprite = mSpriteHide[mSpriteRemap[f]];
2221
const Sprite *const sprite = getSprite(mSpriteRemap[f]);
2223
sprite->draw(graphics, posX, posY);
2227
bool Being::drawSpriteAt(Graphics *const graphics,
2228
const int x, const int y) const
2233
res = ActorSprite::drawSpriteAt(graphics, x, y);
2238
if (mHighlightMapPortals && mMap && mSubType == 45 && !mMap->getHasWarps())
2240
graphics->setColor(userPalette->
2241
getColorWithAlpha(UserPalette::PORTAL_HIGHLIGHT));
2243
graphics->fillRectangle(gcn::Rectangle(x, y, 32, 32));
2245
if (mDrawHotKeys && !mName.empty())
2247
gcn::Font *const font = gui->getFont();
2250
graphics->setColor(userPalette->getColor(UserPalette::BEING));
2251
font->drawString(graphics, mName, x, y);
2256
if (mHighlightMonsterAttackRange && mType == ActorSprite::MONSTER
2261
attackRange = 32 * mAttackRange;
2265
graphics->setColor(userPalette->getColorWithAlpha(
2266
UserPalette::MONSTER_ATTACK_RANGE));
2268
graphics->fillRectangle(gcn::Rectangle(
2269
x - attackRange, y - attackRange,
2270
2 * attackRange + 32, 2 * attackRange + 32));
2273
if (mShowMobHP && mInfo && player_node && player_node->getTarget() == this
2274
&& mType == MONSTER)
2279
maxHP = mInfo->getMaxHP();
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(),
2287
if (mShowOwnHP && mInfo && player_node == this && mAction != DEAD)
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(),
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
2304
if (maxHP <= 0 || !userPalette)
2311
p = static_cast<float>(maxHP) / static_cast<float>(hp);
2313
else if (maxHP != damage)
2315
p = static_cast<float>(maxHP)
2316
/ static_cast<float>(maxHP - damage);
2323
if (p <= 0 || p > width)
2326
const int dx = static_cast<const int>(static_cast<float>(width) / p);
2328
if (serverVersion < 1)
2330
if ((!damage && (this != player_node || hp == maxHP))
2331
|| (!hp && maxHP == damage))
2333
graphics->setColor(userPalette->getColorWithAlpha(color1));
2334
graphics->fillRectangle(gcn::Rectangle(
2338
else if (width - dx <= 0)
2340
graphics->setColor(userPalette->getColorWithAlpha(color2));
2341
graphics->fillRectangle(gcn::Rectangle(
2342
x, y, width, height));
2350
graphics->setColor(userPalette->getColorWithAlpha(color1));
2351
graphics->fillRectangle(gcn::Rectangle(
2355
else if (width - dx <= 0)
2357
graphics->setColor(userPalette->getColorWithAlpha(color2));
2358
graphics->fillRectangle(gcn::Rectangle(
2359
x, y, width, height));
2364
graphics->setColor(userPalette->getColorWithAlpha(color1));
2365
graphics->fillRectangle(gcn::Rectangle(
2368
graphics->setColor(userPalette->getColorWithAlpha(color2));
2369
graphics->fillRectangle(gcn::Rectangle(
2370
x + dx, y, width - dx, height));
2373
void Being::setHP(const int hp)
2378
if (mType == MONSTER)
2382
void Being::setMaxHP(const int hp)
2389
void Being::resetCounters()
2395
mTestTime = cur_time;
2398
void Being::recalcSpritesOrder()
2400
if (!mEnableReorderSprites)
2403
// logger->log("recalcSpritesOrder");
2404
const unsigned sz = static_cast<unsigned>(size());
2408
std::vector<int> slotRemap;
2409
std::map<int, int> itemSlotRemap;
2411
std::vector<int>::iterator it;
2413
int dir = mSpriteDirection;
2414
if (dir < 0 || dir >= 9)
2416
// hack for allow different logic in dead player
2417
if (mAction == DEAD)
2420
const unsigned int hairSlot = Net::getCharServerHandler()->hairSprite();
2422
for (unsigned slot = 0; slot < sz; slot ++)
2424
oldHide[slot] = mSpriteHide[slot];
2425
mSpriteHide[slot] = 0;
2428
const size_t spriteIdSize = mSpriteIDs.size();
2429
for (unsigned slot = 0; slot < sz; slot ++)
2431
slotRemap.push_back(slot);
2433
if (spriteIdSize <= slot)
2436
const int id = mSpriteIDs[slot];
2440
const ItemInfo &info = ItemDB::get(id);
2442
if (info.isRemoveSprites())
2444
SpriteToItemMap *const spriteToItems
2445
= info.getSpriteToItemReplaceMap(dir);
2449
FOR_EACHP (SpriteToItemMapCIter, itr, spriteToItems)
2451
const int remSprite = itr->first;
2452
const std::map<int, int> &itemReplacer = itr->second;
2455
if (itemReplacer.empty())
2457
mSpriteHide[remSprite] = 1;
2461
std::map<int, int>::const_iterator repIt
2462
= itemReplacer.find(mSpriteIDs[remSprite]);
2463
if (repIt == itemReplacer.end())
2465
repIt = itemReplacer.find(0);
2466
if (repIt->second == 0)
2467
repIt = itemReplacer.end();
2469
if (repIt != itemReplacer.end())
2471
mSpriteHide[remSprite] = repIt->second;
2472
if (repIt->second != 1)
2474
if (static_cast<unsigned>(remSprite)
2477
setSprite(remSprite, repIt->second,
2478
mSpriteColors[remSprite],
2483
setSprite(remSprite, repIt->second,
2484
ItemDB::get(repIt->second)
2485
.getDyeColorsString(mHairColor),
2493
{ // slot unknown. Search for real slot, this can be slow
2494
FOR_EACH (IntMapCIter, repIt, itemReplacer)
2496
for (unsigned slot2 = 0; slot2 < sz; slot2 ++)
2498
if (mSpriteIDs[slot2] == repIt->first)
2500
mSpriteHide[slot2] = repIt->second;
2501
if (repIt->second != 1)
2503
if (slot2 != hairSlot)
2505
setSprite(slot2, repIt->second,
2506
mSpriteColors[slot2],
2511
setSprite(slot2, repIt->second,
2512
ItemDB::get(repIt->second)
2513
.getDyeColorsString(
2526
if (info.mDrawBefore[dir] > 0)
2528
const int id2 = mSpriteIDs[info.mDrawBefore[dir]];
2529
if (itemSlotRemap.find(id2) != itemSlotRemap.end())
2531
// logger->log("found duplicate (before)");
2532
const ItemInfo &info2 = ItemDB::get(id2);
2533
if (info.mDrawPriority[dir] < info2.mDrawPriority[dir])
2535
// logger->log("old more priority");
2540
// logger->log("new more priority");
2541
itemSlotRemap.erase(id2);
2545
itemSlotRemap[id] = -info.mDrawBefore[dir];
2547
else if (info.mDrawAfter[dir] > 0)
2549
const int id2 = mSpriteIDs[info.mDrawAfter[dir]];
2550
if (itemSlotRemap.find(id2) != itemSlotRemap.end())
2552
const ItemInfo &info2 = ItemDB::get(id2);
2553
if (info.mDrawPriority[dir] < info2.mDrawPriority[dir])
2555
// logger->log("old more priority");
2560
// logger->log("new more priority");
2561
itemSlotRemap.erase(id2);
2565
itemSlotRemap[id] = info.mDrawAfter[dir];
2566
// logger->log("item slot->slot %d %d->%d", id, slot, itemSlotRemap[id]);
2569
// logger->log("preparation end");
2574
while (cnt < 15 && lastRemap >= 0)
2578
// logger->log("iteration");
2580
for (unsigned slot0 = 0; slot0 < sz; slot0 ++)
2582
const int slot = searchSlotValue(slotRemap, slot0);
2583
const int val = slotRemap.at(slot);
2586
if (static_cast<int>(spriteIdSize) > val)
2587
id = mSpriteIDs[val];
2591
// logger->log("item %d, id=%d", slot, id);
2593
const std::map<int, int>::const_iterator
2594
orderIt = itemSlotRemap.find(id);
2595
if (orderIt != itemSlotRemap.end())
2596
reorder = orderIt->second;
2600
// logger->log("move item %d before %d", slot, -reorder);
2601
searchSlotValueItr(it, idx, slotRemap, -reorder);
2602
if (it == slotRemap.end())
2604
searchSlotValueItr(it, idx1, slotRemap, val);
2605
if (it == slotRemap.end())
2608
if (idx1 + 1 != idx)
2610
slotRemap.erase(it);
2611
searchSlotValueItr(it, idx, slotRemap, -reorder);
2612
slotRemap.insert(it, val);
2615
else if (reorder > 0)
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())
2623
if (idx1 != idx + 1)
2625
slotRemap.erase(it);
2626
searchSlotValueItr(it, idx, slotRemap, reorder);
2627
if (it != slotRemap.end())
2630
if (it != slotRemap.end())
2631
slotRemap.insert(it, val);
2633
slotRemap.push_back(val);
2637
slotRemap.push_back(val);
2644
// logger->log("after remap");
2645
for (unsigned slot = 0; slot < sz; slot ++)
2647
mSpriteRemap[slot] = slotRemap[slot];
2648
if (oldHide[slot] != 0 && oldHide[slot] != 1 && mSpriteHide[slot] == 0)
2650
const int id = mSpriteIDs[slot];
2654
setSprite(slot, id, mSpriteColors[slot], 1, false, true);
2656
// logger->log("slot %d = %d", slot, mSpriteRemap[slot]);
2660
int Being::searchSlotValue(const std::vector<int> &slotRemap,
2661
const int val) const
2663
const size_t sz = size();
2664
for (size_t slot = 0; slot < sz; slot ++)
2666
if (slotRemap[slot] == val)
2669
return getNumberOfLayers() - 1;
2672
void Being::searchSlotValueItr(std::vector<int>::iterator &it, int &idx,
2673
std::vector<int> &slotRemap,
2674
const int val) const
2676
// logger->log("searching %d", val);
2677
it = slotRemap.begin();
2678
const std::vector<int>::iterator it_end = slotRemap.end();
2680
while (it != it_end)
2682
// logger->log("testing %d", *it);
2685
// logger->log("found at %d", idx);
2691
// logger->log("not found");
2696
void Being::updateHit(const int amount)
2700
if (!mMinHit || amount < mMinHit)
2702
if (amount != mCriticalHit && (!mMaxHit || amount > mMaxHit))
2707
Equipment *Being::getEquipment()
2709
Equipment *const eq = new Equipment();
2710
Equipment::Backend *const bk = new BeingEquipBackend(this);
2715
void Being::undressItemById(const int id)
2717
const size_t sz = mSpriteIDs.size();
2719
for (size_t f = 0; f < sz; f ++)
2721
if (id == mSpriteIDs[f])
2723
setSprite(static_cast<unsigned int>(f), 0);
2729
void Being::clearCache()
2731
delete_all(beingInfoCache);
2732
beingInfoCache.clear();
2735
void Being::updateComment()
2737
if (mGotComment || mName.empty())
2741
mComment = loadComment(mName, mType);
2744
std::string Being::loadComment(const std::string &name, const int type)
2750
str = client->getUsersDirectory();
2753
str = client->getNpcsDirectory();
2759
str.append(stringToHexPath(name)).append("/comment.txt");
2760
logger->log("load from: %s", str.c_str());
2763
const ResourceManager *const resman = ResourceManager::getInstance();
2764
if (resman->existsLocal(str))
2766
lines = resman->loadTextFileLocal(str);
2767
if (lines.size() >= 2)
2773
void Being::saveComment(const std::string &name,
2774
const std::string &comment, const int type)
2780
dir = client->getUsersDirectory();
2783
dir = client->getNpcsDirectory();
2788
dir.append(stringToHexPath(name));
2789
const ResourceManager *const resman = ResourceManager::getInstance();
2790
resman->saveTextFile(dir, "comment.txt",
2791
(name + "\n").append(comment));
2794
void Being::setState(const uint8_t state)
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);
2804
mInactive = inactive;
2809
if (shop || away || inactive)
2816
void Being::setEmote(const uint8_t emotion, const int emote_time)
2818
if ((emotion & FLAG_SPECIAL) == FLAG_SPECIAL)
2825
const int emotionIndex = emotion - 1;
2826
if (emotionIndex >= 0 && emotionIndex <= EmoteDB::getLast())
2828
delete mEmotionSprite;
2829
mEmotionSprite = nullptr;
2830
const EmoteInfo *const info = EmoteDB::get2(emotionIndex, true);
2833
const EmoteSprite *const sprite = info->sprites.front();
2836
mEmotionSprite = AnimatedSprite::clone(sprite->sprite);
2838
mEmotionTime = info->time;
2840
mEmotionTime = emote_time;
2847
mEmotionSprite->play(mSpriteAction);
2848
mEmotionSprite->setSpriteDirection(static_cast<SpriteDirection>(
2858
void Being::updatePercentHP()
2860
if (!mMaxHP || !serverVersion)
2864
const unsigned num = mHP * 100 / mMaxHP;
2868
if (updateNumber(mNumber))
2874
uint8_t Being::genderToInt(const Gender sex)
2879
case GENDER_UNSPECIFIED:
2889
Gender Being::intToGender(const uint8_t sex)
2895
return GENDER_FEMALE;
2899
return GENDER_OTHER;
2903
int Being::getSpriteID(const int slot) const
2905
if (slot < 0 || static_cast<unsigned>(slot) >= mSpriteIDs.size())
2908
return mSpriteIDs[slot];
2911
void Being::addAfkEffect()
2913
addSpecialEffect(mAwayEffect);
2916
void Being::removeAfkEffect()
2918
removeSpecialEffect();
2921
void Being::addSpecialEffect(const int effect)
2923
if (effectManager && Particle::enabled
2924
&& !mSpecialParticle && effect != -1)
2926
mSpecialParticle = effectManager->triggerReturn(effect, this);
2930
void Being::removeSpecialEffect()
2932
if (effectManager && mSpecialParticle)
2934
mChildParticleEffects.removeLocally(mSpecialParticle);
2935
mSpecialParticle = nullptr;
2937
delete mAnimationEffect;
2938
mAnimationEffect = nullptr;
2941
void Being::updateAwayEffect()
2949
void Being::addEffect(const std::string &name)
2951
delete mAnimationEffect;
2952
mAnimationEffect = AnimatedSprite::load(
2953
paths.getStringValue("sprites") + name);
2956
void Being::addPet(const int id)
2958
if (!actorSpriteManager)
2962
Being *const being = actorSpriteManager->createBeing(
2963
id, ActorSprite::PET, 0);
2966
being->setTileCoords(getTileX(), getTileY());
2967
being->setOwner(this);
2973
void Being::removePet()
2975
if (!actorSpriteManager)
2981
mPet->setOwner(nullptr);
2982
actorSpriteManager->destroy(mPet);
2987
void Being::updatePets()
2990
FOR_EACH (std::vector<int>::const_iterator, it, mSpriteIDs)
2995
const ItemInfo &info = ItemDB::get(id);
2996
const int pet = info.getPet();
3005
void Being::playSfx(const SoundInfo &sound, Being *const being,
3006
const bool main, const int x, const int y)
3010
// here need add timer and delay sound
3011
const int time = tick_time;
3014
being->mNextSound.sound = nullptr;
3015
being->mNextSound.time = time + sound.delay;
3016
soundManager.playSfx(sound.sound, x, y);
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);
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;
3033
soundManager.playSfx(sound.sound, x, y);
3037
void Being::setLook(const int look)
3039
if (mType == PLAYER)
3040
setSubtype(mSubType, look);