summaryrefslogtreecommitdiff
path: root/src/being
diff options
context:
space:
mode:
authorAndrei Karas <akaras@inbox.ru>2013-08-31 22:42:10 +0300
committerAndrei Karas <akaras@inbox.ru>2013-08-31 22:42:10 +0300
commit00cda69b883d6354f093be6ee39a7936cb798979 (patch)
treef1daa290abfb53180bd8420a45fe6dff1c7a2ab3 /src/being
parent5919cdc663d5f60a8c5cc7e50ad0c43a18cf9829 (diff)
downloadmv-00cda69b883d6354f093be6ee39a7936cb798979.tar.gz
mv-00cda69b883d6354f093be6ee39a7936cb798979.tar.bz2
mv-00cda69b883d6354f093be6ee39a7936cb798979.tar.xz
mv-00cda69b883d6354f093be6ee39a7936cb798979.zip
move being related files into being dir.
Diffstat (limited to 'src/being')
-rw-r--r--src/being/actor.cpp71
-rw-r--r--src/being/actor.h142
-rw-r--r--src/being/actorsprite.cpp398
-rw-r--r--src/being/actorsprite.h247
-rw-r--r--src/being/actorspritelistener.h44
-rw-r--r--src/being/being.cpp3041
-rw-r--r--src/being/being.h1063
-rw-r--r--src/being/beingcacheentry.h128
-rw-r--r--src/being/compoundsprite.cpp567
-rw-r--r--src/being/compoundsprite.h161
-rw-r--r--src/being/localplayer.cpp4355
-rw-r--r--src/being/localplayer.h652
-rw-r--r--src/being/playerinfo.cpp398
-rw-r--r--src/being/playerinfo.h267
-rw-r--r--src/being/playerrelations.cpp630
-rw-r--r--src/being/playerrelations.h285
16 files changed, 12449 insertions, 0 deletions
diff --git a/src/being/actor.cpp b/src/being/actor.cpp
new file mode 100644
index 000000000..0efed5d18
--- /dev/null
+++ b/src/being/actor.cpp
@@ -0,0 +1,71 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2010 The Mana Developers
+ * Copyright (C) 2011-2013 The ManaPlus Developers
+ *
+ * This file is part of The ManaPlus Client.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "being/actor.h"
+
+#include "map.h"
+
+#include "resources/image.h"
+#include "resources/resourcemanager.h"
+
+#include "debug.h"
+
+Actor::Actor():
+ mMap(nullptr),
+ mPos(),
+ mYDiff(0),
+ mMapActor()
+{
+}
+
+Actor::~Actor()
+{
+ setMap(nullptr);
+}
+
+void Actor::setMap(Map *const map)
+{
+ // Remove Actor from potential previous map
+ if (mMap)
+ mMap->removeActor(mMapActor);
+
+ mMap = map;
+
+ // Add Actor to potential new map
+ if (mMap)
+ mMapActor = mMap->addActor(this);
+}
+
+int Actor::getTileX() const
+{
+ if (!mMap || !mMap->getTileWidth())
+ return 0;
+
+ return getPixelX() / mMap->getTileWidth();
+}
+
+int Actor::getTileY() const
+{
+ if (!mMap || !mMap->getTileHeight())
+ return 0;
+
+ return getPixelY() / mMap->getTileHeight();
+}
diff --git a/src/being/actor.h b/src/being/actor.h
new file mode 100644
index 000000000..326b9f40c
--- /dev/null
+++ b/src/being/actor.h
@@ -0,0 +1,142 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2010 The Mana Developers
+ * Copyright (C) 2011-2013 The ManaPlus Developers
+ *
+ * This file is part of The ManaPlus Client.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef BEING_ACTOR_H
+#define BEING_ACTOR_H
+
+#include "vector.h"
+
+#include <list>
+
+#include "localconsts.h"
+
+class Actor;
+class Graphics;
+class Image;
+class Map;
+
+typedef std::list<Actor*> Actors;
+typedef Actors::const_iterator ActorsCIter;
+
+class Actor
+{
+public:
+ Actor();
+
+ A_DELETE_COPY(Actor)
+
+ virtual ~Actor();
+
+ /**
+ * Draws the Actor to the given graphics context.
+ *
+ * Note: this function could be simplified if the graphics context
+ * would support setting a translation offset. It already does this
+ * partly with the clipping rectangle support.
+ */
+ virtual bool draw(Graphics *const graphics,
+ const int offsetX, const int offsetY) const = 0;
+
+ /**
+ * Returns the horizontal size of the actors graphical representation
+ * in pixels or 0 when it is undefined.
+ */
+ virtual int getWidth() const A_WARN_UNUSED
+ { return 0; }
+
+ /**
+ * Returns the vertical size of the actors graphical representation
+ * in pixels or 0 when it is undefined.
+ */
+ virtual int getHeight() const A_WARN_UNUSED
+ { return 0; }
+
+ /**
+ * Returns the pixel position of this actor.
+ */
+ const Vector &getPosition() const A_WARN_UNUSED
+ { return mPos; }
+
+ /**
+ * Sets the pixel position of this actor.
+ */
+ virtual void setPosition(const Vector &pos)
+ { mPos = pos; }
+
+ /**
+ * Returns the pixels X coordinate of the actor.
+ */
+ int getPixelX() const A_WARN_UNUSED
+ { return static_cast<int>(mPos.x); }
+
+ /**
+ * Returns the pixel Y coordinate of the actor.
+ */
+ virtual int getPixelY() const A_WARN_UNUSED
+ { return static_cast<int>(mPos.y); }
+
+ /**
+ * Returns the pixel Y coordinate of the actor for sorting only.
+ */
+ virtual int getSortPixelY() const A_WARN_UNUSED
+ { return static_cast<int>(mPos.y) - mYDiff; }
+
+ /**
+ * Returns the x coordinate in tiles of the actor.
+ */
+ virtual int getTileX() const A_WARN_UNUSED;
+
+ /**
+ * Returns the y coordinate in tiles of the actor.
+ */
+ virtual int getTileY() const A_WARN_UNUSED;
+
+ /**
+ * Returns the number of Image layers used to draw the actor.
+ */
+ virtual int getNumberOfLayers() const A_WARN_UNUSED
+ { return 0; }
+
+ /**
+ * Returns the current alpha value used to draw the actor.
+ */
+ virtual float getAlpha() const A_WARN_UNUSED = 0;
+
+ /**
+ * Sets the alpha value used to draw the actor.
+ */
+ virtual void setAlpha(float alpha) = 0;
+
+ virtual void setMap(Map *const map);
+
+ const Map* getMap() const A_WARN_UNUSED
+ { return mMap; }
+
+protected:
+ Map *mMap;
+ Vector mPos; /**< Position in pixels relative to map. */
+ int mYDiff;
+
+private:
+ Actors::iterator mMapActor;
+};
+
+#endif // BEING_ACTOR_H
diff --git a/src/being/actorsprite.cpp b/src/being/actorsprite.cpp
new file mode 100644
index 000000000..9c5ff6def
--- /dev/null
+++ b/src/being/actorsprite.cpp
@@ -0,0 +1,398 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2010 The Mana Developers
+ * Copyright (C) 2011-2013 The ManaPlus Developers
+ *
+ * This file is part of The ManaPlus Client.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "being/actorsprite.h"
+
+#include "client.h"
+#include "configuration.h"
+#include "effectmanager.h"
+#include "imagesprite.h"
+#include "simpleanimation.h"
+#include "soundmanager.h"
+#include "statuseffect.h"
+
+#include "being/actorspritelistener.h"
+#include "being/localplayer.h"
+
+#include "gui/theme.h"
+
+#include "net/net.h"
+
+#include "resources/imageset.h"
+#include "resources/resourcemanager.h"
+
+#include "utils/checkutils.h"
+
+#include "debug.h"
+
+AnimatedSprite *ActorSprite::targetCursor[2][NUM_TC];
+bool ActorSprite::loaded = false;
+
+ActorSprite::ActorSprite(const int id) :
+ CompoundSprite(),
+ Actor(),
+ mStatusEffects(),
+ mStunParticleEffects(),
+ mStatusParticleEffects(&mStunParticleEffects, false),
+ mChildParticleEffects(&mStatusParticleEffects, false),
+ mId(id),
+ mStunMode(0),
+ mUsedTargetCursor(nullptr),
+ mActorSpriteListeners(),
+ mCursorPaddingX(0),
+ mCursorPaddingY(0),
+ mMustResetParticles(false)
+{
+}
+
+ActorSprite::~ActorSprite()
+{
+ setMap(nullptr);
+
+ mUsedTargetCursor = nullptr;
+
+ if (player_node && player_node->getTarget() == this)
+ player_node->setTarget(nullptr);
+
+ // Notify listeners of the destruction.
+ FOR_EACH (ActorSpriteListenerIterator, iter, mActorSpriteListeners)
+ {
+ if (reportFalse(*iter))
+ (*iter)->actorSpriteDestroyed(*this);
+ }
+}
+
+bool ActorSprite::draw(Graphics *const graphics,
+ const int offsetX, const int offsetY) const
+{
+ FUNC_BLOCK("ActorSprite::draw", 1)
+ const int px = getPixelX() + offsetX - 16;
+ // Temporary fix to the Y offset.
+#ifdef MANASERV_SUPPORT
+ const int py = getPixelY() + offsetY -
+ ((Net::getNetworkType() == ServerInfo::MANASERV) ? 15 : 32);
+#else
+ const int py = getPixelY() + offsetY - 32;
+#endif
+
+ if (mUsedTargetCursor)
+ {
+ mUsedTargetCursor->update(tick_time * MILLISECONDS_IN_A_TICK);
+ mUsedTargetCursor->draw(graphics,
+ px + getTargetOffsetX() - mCursorPaddingX,
+ py + getTargetOffsetY() - mCursorPaddingY);
+ }
+
+ return drawSpriteAt(graphics, px, py);
+}
+
+bool ActorSprite::drawSpriteAt(Graphics *const graphics,
+ const int x, const int y) const
+{
+ return CompoundSprite::draw(graphics, x, y);
+}
+
+void ActorSprite::logic()
+{
+ BLOCK_START("ActorSprite::logic")
+ // Update sprite animations
+ update(tick_time * MILLISECONDS_IN_A_TICK);
+
+ // Restart status/particle effects, if needed
+ if (mMustResetParticles)
+ {
+ mMustResetParticles = false;
+ FOR_EACH (std::set<int>::const_iterator, it, mStatusEffects)
+ {
+ const StatusEffect *const effect
+ = StatusEffect::getStatusEffect(*it, true);
+ if (effect && effect->particleEffectIsPersistent())
+ updateStatusEffect(*it, true);
+ }
+ }
+
+ // Update particle effects
+ mChildParticleEffects.moveTo(mPos.x, mPos.y);
+ BLOCK_END("ActorSprite::logic")
+}
+
+void ActorSprite::actorLogic()
+{
+}
+
+void ActorSprite::setMap(Map *const map)
+{
+ Actor::setMap(map);
+
+ // Clear particle effect list because child particles became invalid
+ mChildParticleEffects.clear();
+ mMustResetParticles = true; // Reset status particles on next redraw
+}
+
+void ActorSprite::controlParticle(Particle *const particle)
+{
+ mChildParticleEffects.addLocally(particle);
+}
+
+void ActorSprite::setTargetType(const TargetCursorType type)
+{
+ static const int targetWidths[ActorSprite::NUM_TC] = {0, 0, 0};
+ static const int targetHeights[ActorSprite::NUM_TC] = {-16, -16, -32};
+
+ if (type == TCT_NONE)
+ {
+ untarget();
+ }
+ else
+ {
+ const TargetCursorSize sz = getTargetCursorSize();
+ mUsedTargetCursor = targetCursor[type][sz];
+ if (mUsedTargetCursor)
+ {
+ mCursorPaddingX = targetWidths[sz];
+ mCursorPaddingY = targetHeights[sz];
+ }
+ }
+}
+
+struct EffectDescription final
+{
+ std::string mGFXEffect;
+ std::string mSFXEffect;
+};
+
+void ActorSprite::setStatusEffect(const int index, const bool active)
+{
+ const bool wasActive = mStatusEffects.find(index) != mStatusEffects.end();
+
+ if (active != wasActive)
+ {
+ updateStatusEffect(index, active);
+ if (active)
+ mStatusEffects.insert(index);
+ else
+ mStatusEffects.erase(index);
+ }
+}
+
+void ActorSprite::setStatusEffectBlock(const int offset,
+ const uint16_t newEffects)
+{
+ for (unsigned i = 0; i < STATUS_EFFECTS; i++)
+ {
+ const int index = StatusEffect::blockEffectIndexToEffectIndex(
+ offset + i);
+
+ if (index != -1)
+ setStatusEffect(index, (newEffects & (1 << i)) > 0);
+ }
+}
+
+void ActorSprite::updateStunMode(const int oldMode, const int newMode)
+{
+ handleStatusEffect(StatusEffect::getStatusEffect(oldMode, false), -1);
+ handleStatusEffect(StatusEffect::getStatusEffect(newMode, true), -1);
+}
+
+void ActorSprite::updateStatusEffect(const int index, const bool newStatus)
+{
+ handleStatusEffect(StatusEffect::getStatusEffect(index, newStatus), index);
+}
+
+void ActorSprite::handleStatusEffect(StatusEffect *const effect,
+ const int effectId)
+{
+ if (!effect)
+ return;
+
+ Particle *const particle = effect->getParticle();
+
+ if (effectId >= 0)
+ {
+ mStatusParticleEffects.setLocally(effectId, particle);
+ }
+ else
+ {
+ mStunParticleEffects.clearLocally();
+ if (particle)
+ mStunParticleEffects.addLocally(particle);
+ }
+}
+
+void ActorSprite::setupSpriteDisplay(const SpriteDisplay &display,
+ const bool forceDisplay,
+ const int imageType,
+ const std::string &color)
+{
+ clear();
+
+ FOR_EACH (SpriteRefs, it, display.sprites)
+ {
+ if (!*it)
+ continue;
+ const std::string file = paths.getStringValue("sprites").append(
+ combineDye3((*it)->sprite, color));
+
+ const int variant = (*it)->variant;
+ addSprite(AnimatedSprite::delayedLoad(file, variant));
+ }
+
+ // Ensure that something is shown, if desired
+ if (empty() && forceDisplay)
+ {
+ if (display.image.empty())
+ {
+ addSprite(AnimatedSprite::delayedLoad(
+ paths.getStringValue("sprites").append(
+ paths.getStringValue("spriteErrorFile"))));
+ }
+ else
+ {
+ ResourceManager *const resman = ResourceManager::getInstance();
+ std::string imagePath;
+ switch (imageType)
+ {
+ case 0:
+ default:
+ imagePath = paths.getStringValue("itemIcons").append(
+ display.image);
+ break;
+ case 1:
+ imagePath = paths.getStringValue("itemIcons").append(
+ display.floor);
+ break;
+ }
+ imagePath = combineDye2(imagePath, color);
+
+ Image *img = resman->getImage(imagePath);
+
+ if (!img)
+ img = Theme::getImageFromTheme("unknown-item.png");
+
+ addSprite(new ImageSprite(img));
+ if (img)
+ img->decRef();
+ }
+ }
+
+ mChildParticleEffects.clear();
+
+ // setup particle effects
+ if (Particle::enabled && particleEngine)
+ {
+ FOR_EACH (StringVectCIter, itr, display.particles)
+ {
+ Particle *const p = particleEngine->addEffect(*itr, 0, 0);
+ controlParticle(p);
+ }
+ }
+
+ mMustResetParticles = true;
+}
+
+void ActorSprite::load()
+{
+ if (loaded)
+ unload();
+
+ initTargetCursor();
+
+ loaded = true;
+}
+
+void ActorSprite::unload()
+{
+ if (reportTrue(!loaded))
+ return;
+
+ cleanupTargetCursors();
+ loaded = false;
+}
+
+void ActorSprite::addActorSpriteListener(ActorSpriteListener *const listener)
+{
+ mActorSpriteListeners.push_front(listener);
+}
+
+void ActorSprite::removeActorSpriteListener(ActorSpriteListener *const
+ listener)
+{
+ mActorSpriteListeners.remove(listener);
+}
+
+static const char *cursorType(const int type)
+{
+ switch (type)
+ {
+ case ActorSprite::TCT_IN_RANGE:
+ return "in-range";
+ default:
+ case ActorSprite::TCT_NORMAL:
+ return "normal";
+ }
+}
+
+static const char *cursorSize(const int size)
+{
+ switch (size)
+ {
+ case ActorSprite::TC_LARGE:
+ return "l";
+ case ActorSprite::TC_MEDIUM:
+ return "m";
+ default:
+ case ActorSprite::TC_SMALL:
+ return "s";
+ }
+}
+
+void ActorSprite::initTargetCursor()
+{
+ static const std::string targetCursorFile = "%s/target-cursor-%s-%s.xml";
+
+ const std::string path = branding.getStringValue("guiPath");
+
+ // Load target cursors
+ for (int size = TC_SMALL; size < NUM_TC; size++)
+ {
+ for (int type = TCT_NORMAL; type < NUM_TCT; type++)
+ {
+ targetCursor[type][size] = AnimatedSprite::load(strprintf(
+ targetCursorFile.c_str(), path.c_str(), cursorType(type),
+ cursorSize(size)));
+ }
+ }
+}
+
+void ActorSprite::cleanupTargetCursors()
+{
+ for (int size = TC_SMALL; size < NUM_TC; size++)
+ {
+ for (int type = TCT_NORMAL; type < NUM_TCT; type++)
+ {
+ if (targetCursor[type][size])
+ {
+ delete targetCursor[type][size];
+ targetCursor[type][size] = nullptr;
+ }
+ }
+ }
+}
diff --git a/src/being/actorsprite.h b/src/being/actorsprite.h
new file mode 100644
index 000000000..debdc69d1
--- /dev/null
+++ b/src/being/actorsprite.h
@@ -0,0 +1,247 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2010 The Mana Developers
+ * Copyright (C) 2011-2013 The ManaPlus Developers
+ *
+ * This file is part of The ManaPlus Client.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef BEING_ACTORSPRITE_H
+#define BEING_ACTORSPRITE_H
+
+#include "localconsts.h"
+#include "map.h"
+
+#include "being/actor.h"
+#include "being/compoundsprite.h"
+
+#include "particle/particlecontainer.h"
+
+#include <SDL_types.h>
+
+#include <set>
+#include <list>
+
+#include "localconsts.h"
+
+class AnimatedSprite;
+class StatusEffect;
+class ActorSpriteListener;
+
+class ActorSprite : public CompoundSprite, public Actor
+{
+public:
+ enum Type
+ {
+ UNKNOWN = 0,
+ PLAYER,
+ NPC,
+ MONSTER,
+ FLOOR_ITEM,
+ PORTAL,
+ PET,
+ AVATAR
+ };
+
+ enum TargetCursorSize
+ {
+ TC_SMALL = 0,
+ TC_MEDIUM,
+ TC_LARGE,
+ NUM_TC
+ };
+
+ enum TargetCursorType
+ {
+ TCT_NONE = -1,
+ TCT_NORMAL = 0,
+ TCT_IN_RANGE,
+ NUM_TCT
+ };
+
+ explicit ActorSprite(const int id);
+
+ A_DELETE_COPY(ActorSprite)
+
+ ~ActorSprite();
+
+ int getId() const A_WARN_UNUSED
+ { return mId; }
+
+ void setId(const int id)
+ { mId = id; }
+
+ /**
+ * Returns the type of the ActorSprite.
+ */
+ virtual Type getType() const A_WARN_UNUSED
+ { return UNKNOWN; }
+
+ virtual bool draw(Graphics *const graphics,
+ const int offsetX, const int offsetY) const override;
+
+ virtual bool drawSpriteAt(Graphics *const graphics,
+ const int x, const int y) const;
+
+ virtual void logic();
+
+ static void actorLogic();
+
+ void setMap(Map *const map) override;
+
+ /**
+ * Gets the way the object blocks pathfinding for other objects
+ */
+ virtual Map::BlockType getBlockType() const A_WARN_UNUSED
+ { return Map::BLOCKTYPE_NONE; }
+
+ /**
+ * Take control of a particle.
+ */
+ void controlParticle(Particle *const particle);
+
+ /**
+ * Returns the required size of a target cursor for this being.
+ */
+ virtual TargetCursorSize getTargetCursorSize() const A_WARN_UNUSED
+ { return TC_MEDIUM; }
+
+ virtual int getTargetOffsetX() const A_WARN_UNUSED
+ { return 0; }
+
+ virtual int getTargetOffsetY() const A_WARN_UNUSED
+ { return 0; }
+
+ /**
+ * Sets the target animation for this actor.
+ */
+ void setTargetType(const TargetCursorType type);
+
+ /**
+ * Untargets the actor.
+ */
+ void untarget()
+ { mUsedTargetCursor = nullptr; }
+
+ /**
+ * Sets the actor's stun mode. If zero, the being is `normal', otherwise it
+ * is `stunned' in some fashion.
+ */
+ void setStunMode(const uint16_t stunMode)
+ {
+ if (mStunMode != stunMode)
+ updateStunMode(mStunMode, stunMode);
+ mStunMode = stunMode;
+ }
+
+ void setStatusEffect(const int index, const bool active);
+
+ /**
+ * A status effect block is a 16 bit mask of status effects. We assign each
+ * such flag a block ID of offset + bitnr.
+ *
+ * These are NOT the same as the status effect indices.
+ */
+ void setStatusEffectBlock(const int offset, const uint16_t flags);
+
+ virtual void setAlpha(const float alpha) override
+ { CompoundSprite::setAlpha(alpha); }
+
+ virtual float getAlpha() const override A_WARN_UNUSED
+ { return CompoundSprite::getAlpha(); }
+
+ virtual int getWidth() const override A_WARN_UNUSED
+ { return CompoundSprite::getWidth(); }
+
+ virtual int getHeight() const override A_WARN_UNUSED
+ { return CompoundSprite::getHeight(); }
+
+ static void load();
+
+ static void unload();
+
+ /**
+ * Add an ActorSprite listener.
+ */
+ void addActorSpriteListener(ActorSpriteListener *const listener);
+
+ /**
+ * Remove an ActorSprite listener.
+ */
+ void removeActorSpriteListener(ActorSpriteListener *const listener);
+
+protected:
+ /**
+ * Notify self that the stun mode has been updated. Invoked by
+ * setStunMode if something changed.
+ */
+ virtual void updateStunMode(const int oldMode, const int newMode);
+
+ /**
+ * Notify self that a status effect has flipped.
+ * The new flag is passed.
+ */
+ virtual void updateStatusEffect(const int index, const bool newStatus);
+
+ /**
+ * Handle an update to a status or stun effect
+ *
+ * \param The StatusEffect to effect
+ * \param effectId -1 for stun, otherwise the effect index
+ */
+ virtual void handleStatusEffect(StatusEffect *const effect,
+ const int effectId);
+
+ void setupSpriteDisplay(const SpriteDisplay &display,
+ const bool forceDisplay = true,
+ const int imageType = 0,
+ const std::string &color = "");
+
+ std::set<int> mStatusEffects; /**< set of active status effects */
+
+ ParticleList mStunParticleEffects;
+ ParticleVector mStatusParticleEffects;
+ ParticleList mChildParticleEffects;
+ int mId;
+ uint16_t mStunMode; /**< Stun mode; zero if not stunned */
+
+private:
+ /** Load the target cursors into memory */
+ static void initTargetCursor();
+
+ /** Remove the target cursors from memory */
+ static void cleanupTargetCursors();
+
+ /** Animated target cursors. */
+ static AnimatedSprite *targetCursor[NUM_TCT][NUM_TC];
+
+ static bool loaded;
+
+ /** Target cursor being used */
+ AnimatedSprite *mUsedTargetCursor;
+
+ typedef std::list<ActorSpriteListener*> ActorSpriteListeners;
+ typedef ActorSpriteListeners::iterator ActorSpriteListenerIterator;
+ ActorSpriteListeners mActorSpriteListeners;
+
+ int mCursorPaddingX;
+ int mCursorPaddingY;
+
+ /** Reset particle status effects on next redraw? */
+ bool mMustResetParticles;
+};
+
+#endif // BEING_ACTORSPRITE_H
diff --git a/src/being/actorspritelistener.h b/src/being/actorspritelistener.h
new file mode 100644
index 000000000..3d5a48443
--- /dev/null
+++ b/src/being/actorspritelistener.h
@@ -0,0 +1,44 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2010 The Mana Developers
+ * Copyright (C) 2011-2013 The ManaPlus Developers
+ *
+ * This file is part of The ManaPlus Client.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef BEING_ACTORSPRITELISTENER_H
+#define BEING_ACTORSPRITELISTENER_H
+
+class ActorSprite;
+
+class ActorSpriteListener
+{
+ public:
+ /**
+ * Destructor.
+ */
+ virtual ~ActorSpriteListener()
+ { }
+
+ /**
+ * Called when the ActorSprite has been destroyed. The listener will
+ * have to be registered first.
+ * @param actorSprite the ActorSprite being destroyed.
+ */
+ virtual void actorSpriteDestroyed(const ActorSprite &actorSprite) = 0;
+};
+
+#endif // BEING_ACTORSPRITELISTENER_H
diff --git a/src/being/being.cpp b/src/being/being.cpp
new file mode 100644
index 000000000..7dfe9d9b6
--- /dev/null
+++ b/src/being/being.cpp
@@ -0,0 +1,3041 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ * Copyright (C) 2011-2013 The ManaPlus Developers
+ *
+ * This file is part of The ManaPlus Client.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "being/being.h"
+
+#include "actorspritemanager.h"
+#include "animatedsprite.h"
+#include "beingequipbackend.h"
+#include "client.h"
+#include "effectmanager.h"
+#include "guild.h"
+#include "party.h"
+#include "soundconsts.h"
+#include "soundmanager.h"
+#include "text.h"
+
+#include "being/beingcacheentry.h"
+#include "being/playerrelations.h"
+
+#include "particle/particle.h"
+
+#include "gui/equipmentwindow.h"
+#include "gui/socialwindow.h"
+#include "gui/speechbubble.h"
+#include "gui/sdlfont.h"
+#include "gui/skilldialog.h"
+
+#include "net/charserverhandler.h"
+#include "net/gamehandler.h"
+#include "net/inventoryhandler.h"
+#include "net/net.h"
+#include "net/npchandler.h"
+#include "net/playerhandler.h"
+
+#include "resources/avatardb.h"
+#include "resources/emotedb.h"
+#include "resources/iteminfo.h"
+#include "resources/monsterdb.h"
+#include "resources/npcdb.h"
+#include "resources/petdb.h"
+#include "resources/resourcemanager.h"
+
+#include "gui/widgets/langtab.h"
+#include "gui/widgets/skillinfo.h"
+
+#include "utils/gettext.h"
+
+#include <cmath>
+
+#include "debug.h"
+
+const unsigned int CACHE_SIZE = 50;
+
+int Being::mNumberOfHairstyles = 1;
+int Being::mNumberOfRaces = 1;
+
+int Being::mUpdateConfigTime = 0;
+unsigned int Being::mConfLineLim = 0;
+int Being::mSpeechType = 0;
+bool Being::mHighlightMapPortals = false;
+bool Being::mHighlightMonsterAttackRange = false;
+bool Being::mLowTraffic = true;
+bool Being::mDrawHotKeys = true;
+bool Being::mShowBattleEvents = false;
+bool Being::mShowMobHP = false;
+bool Being::mShowOwnHP = false;
+bool Being::mShowGender = false;
+bool Being::mShowLevel = false;
+bool Being::mShowPlayersStatus = false;
+bool Being::mEnableReorderSprites = true;
+bool Being::mHideErased = false;
+bool Being::mMoveNames = false;
+int Being::mAwayEffect = -1;
+
+std::list<BeingCacheEntry*> beingInfoCache;
+typedef std::map<int, Guild*>::const_iterator GuildsMapCIter;
+typedef std::map<int, int>::const_iterator IntMapCIter;
+
+Being::Being(const int id, const Type type, const uint16_t subtype,
+ Map *const map) :
+ ActorSprite(id),
+ mNextSound(),
+ mInfo(BeingInfo::unknown),
+ mEmotionSprite(nullptr),
+ mAnimationEffect(nullptr),
+ mSpriteAction(SpriteAction::STAND),
+ mName(),
+ mRaceName(),
+ mPartyName(),
+ mGuildName(),
+ mSpeech(),
+ mDispName(nullptr),
+ mNameColor(nullptr),
+ mEquippedWeapon(nullptr),
+ mPath(),
+ mText(nullptr),
+ mTextColor(nullptr),
+ mDest(),
+ mSpriteColors(),
+ mSpriteIDs(),
+ mSpriteColorsIds(),
+ mGuilds(),
+ mParty(nullptr),
+ mActionTime(0),
+ mEmotionTime(0),
+ mSpeechTime(0),
+ mAttackSpeed(350),
+ mLevel(0),
+ mAttackRange(1),
+ mGender(GENDER_UNSPECIFIED),
+ mAction(STAND),
+ mSubType(0xFFFF),
+ mDirection(DOWN),
+ mDirectionDelayed(0),
+ mSpriteDirection(DIRECTION_DOWN),
+ mShowName(false),
+ mIsGM(false),
+ mType(type),
+ mSpeechBubble(new SpeechBubble),
+ mWalkSpeed(Net::getPlayerHandler()->getDefaultWalkSpeed()),
+ mIp(),
+ mSpriteRemap(new int[20]),
+ mSpriteHide(new int[20]),
+ mComment(),
+ mPet(nullptr),
+ mOwner(nullptr),
+ mSpecialParticle(nullptr),
+ mX(0),
+ mY(0),
+ mDamageTaken(0),
+ mHP(0),
+ mMaxHP(0),
+ mDistance(0),
+ mIsReachable(REACH_UNKNOWN),
+ mGoodStatus(-1),
+ mMoveTime(0),
+ mAttackTime(0),
+ mTalkTime(0),
+ mOtherTime(0),
+ mTestTime(cur_time),
+ mAttackDelay(0),
+ mMinHit(0),
+ mMaxHit(0),
+ mCriticalHit(0),
+ mPvpRank(0),
+ mNumber(100),
+ mPetId(0),
+ mLook(0),
+ mHairColor(0),
+ mErased(false),
+ mEnemy(false),
+ mGotComment(false),
+ mAdvanced(false),
+ mShop(false),
+ mAway(false),
+ mInactive(false)
+{
+ for (int f = 0; f < 20; f ++)
+ {
+ mSpriteRemap[f] = f;
+ mSpriteHide[f] = 0;
+ }
+
+ setMap(map);
+ setSubtype(subtype, 0);
+
+ if (mType == PLAYER)
+ mShowName = config.getBoolValue("visiblenames");
+ else if (mType != NPC)
+ mGotComment = true;
+
+ config.addListener("visiblenames", this);
+
+ reReadConfig();
+
+ if (mType == NPC)
+ setShowName(true);
+ else
+ setShowName(mShowName);
+
+ updateColors();
+ updatePercentHP();
+}
+
+Being::~Being()
+{
+ config.removeListener("visiblenames", this);
+
+ delete [] mSpriteRemap;
+ mSpriteRemap = nullptr;
+ delete [] mSpriteHide;
+ mSpriteHide = nullptr;
+
+ delete mSpeechBubble;
+ mSpeechBubble = nullptr;
+ delete mDispName;
+ mDispName = nullptr;
+ delete mText;
+ mText = nullptr;
+
+ delete mEmotionSprite;
+ mEmotionSprite = nullptr;
+ delete mAnimationEffect;
+ mAnimationEffect = nullptr;
+
+ if (mOwner)
+ mOwner->setPet(nullptr);
+ if (mPet)
+ mPet->setOwner(nullptr);
+}
+
+void Being::setSubtype(const uint16_t subtype, const uint8_t look)
+{
+ if (!mInfo)
+ return;
+
+ if (subtype == mSubType && mLook == look)
+ return;
+
+ mSubType = subtype;
+ mLook = look;
+
+ if (mType == MONSTER)
+ {
+ mInfo = MonsterDB::get(mSubType);
+ if (mInfo)
+ {
+ setName(mInfo->getName());
+ setupSpriteDisplay(mInfo->getDisplay(), true, 0,
+ mInfo->getColor(mLook));
+ mYDiff = mInfo->getSortOffsetY();
+ }
+ }
+ else if (mType == NPC)
+ {
+ mInfo = NPCDB::get(mSubType);
+ if (mInfo)
+ {
+ setupSpriteDisplay(mInfo->getDisplay(), false);
+ mYDiff = mInfo->getSortOffsetY();
+ }
+ }
+ else if (mType == AVATAR)
+ {
+ mInfo = AvatarDB::get(mSubType);
+ if (mInfo)
+ setupSpriteDisplay(mInfo->getDisplay(), false);
+ }
+ else if (mType == PET)
+ {
+ mInfo = PETDB::get(mId);
+ if (mInfo)
+ {
+ setupSpriteDisplay(mInfo->getDisplay(), false);
+ mYDiff = mInfo->getSortOffsetY();
+ }
+ }
+ else if (mType == PLAYER)
+ {
+ int id = -100 - subtype;
+
+ // Prevent showing errors when sprite doesn't exist
+ if (!ItemDB::exists(id))
+ {
+ id = -100;
+ // TRANSLATORS: default race name
+ setRaceName(_("Human"));
+ if (Net::getCharServerHandler())
+ setSprite(Net::getCharServerHandler()->baseSprite(), id);
+ }
+ else
+ {
+ const ItemInfo &info = ItemDB::get(id);
+ setRaceName(info.getName());
+ if (Net::getCharServerHandler())
+ {
+ setSprite(Net::getCharServerHandler()->baseSprite(),
+ id, info.getColor(mLook));
+ }
+ }
+ }
+}
+
+ActorSprite::TargetCursorSize Being::getTargetCursorSize() const
+{
+ if (!mInfo)
+ return ActorSprite::TC_SMALL;
+
+ return mInfo->getTargetCursorSize();
+}
+
+void Being::setPosition(const Vector &pos)
+{
+ Actor::setPosition(pos);
+
+ updateCoords();
+
+ if (mText)
+ {
+ mText->adviseXY(static_cast<int>(pos.x), static_cast<int>(pos.y)
+ - getHeight() - mText->getHeight() - 6, mMoveNames);
+ }
+}
+
+void Being::setDestination(const int dstX, const int dstY)
+{
+ // We can't calculate anything without a map anyway.
+ if (!mMap)
+ return;
+
+#ifdef MANASERV_SUPPORT
+ if (Net::getNetworkType() != ServerInfo::MANASERV)
+#endif
+ {
+ setPath(mMap->findPath(mX, mY, dstX, dstY, getWalkMask()));
+ return;
+ }
+
+#ifdef MANASERV_SUPPORT
+ // Don't handle flawed destinations from server...
+ if (dstX == 0 || dstY == 0)
+ return;
+
+ // If the destination is unwalkable, don't bother trying to get there
+ if (!mMap->getWalk(dstX / 32, dstY / 32))
+ return;
+
+ Position dest = mMap->checkNodeOffsets(getCollisionRadius(), getWalkMask(),
+ dstX, dstY);
+ Path thisPath = mMap->findPixelPath(static_cast<int>(mPos.x),
+ static_cast<int>(mPos.y), dest.x, dest.y,
+ static_cast<int>(getCollisionRadius()),
+ static_cast<unsigned char>(getWalkMask()));
+
+ if (thisPath.empty())
+ {
+ // If there is no path but the destination is on the same walkable tile,
+ // we accept it.
+ if (static_cast<int>(mPos.x) / 32 == dest.x / 32
+ && static_cast<int>(mPos.y) / 32 == dest.y / 32)
+ {
+ mDest.x = static_cast<float>(dest.x);
+ mDest.y = static_cast<float>(dest.y);
+ }
+ setPath(Path());
+ return;
+ }
+
+ // The destination is valid, so we set it.
+ mDest.x = static_cast<float>(dest.x);
+ mDest.y = static_cast<float>(dest.y);
+
+ setPath(thisPath);
+#endif
+}
+
+void Being::clearPath()
+{
+ mPath.clear();
+}
+
+void Being::setPath(const Path &path)
+{
+ mPath = path;
+ if (mPath.empty())
+ return;
+
+#ifdef MANASERV_SUPPORT
+ if ((Net::getNetworkType() != ServerInfo::MANASERV) &&
+ mAction != MOVE && mAction != DEAD)
+#else
+ if (mAction != MOVE && mAction != DEAD)
+#endif
+ {
+ nextTile();
+ mActionTime = tick_time;
+ }
+}
+
+void Being::setSpeech(const std::string &text, const std::string &channel,
+ int time)
+{
+ if (!userPalette)
+ return;
+
+ if (!channel.empty() && (langChatTab && langChatTab->getChannelName()
+ != channel))
+ {
+ return;
+ }
+
+ // Remove colors
+ mSpeech = removeColors(text);
+
+ // Trim whitespace
+ trim(mSpeech);
+
+ const unsigned int lineLim = mConfLineLim;
+ if (lineLim > 0 && mSpeech.length() > lineLim)
+ mSpeech = mSpeech.substr(0, lineLim);
+
+ trim(mSpeech);
+ if (mSpeech.empty())
+ return;
+
+ if (!time)
+ {
+ const size_t sz = mSpeech.size();
+ if (sz < 200)
+ time = static_cast<int>(SPEECH_TIME - 300 + (3 * sz));
+ }
+
+ if (time < static_cast<int>(SPEECH_MIN_TIME))
+ time = static_cast<int>(SPEECH_MIN_TIME);
+
+ // Check for links
+ size_t start = mSpeech.find('[');
+ size_t e = mSpeech.find(']', start);
+
+ while (start != std::string::npos && e != std::string::npos)
+ {
+ // Catch multiple embeds and ignore them so it doesn't crash the client.
+ while ((mSpeech.find('[', start + 1) != std::string::npos) &&
+ (mSpeech.find('[', start + 1) < e))
+ {
+ start = mSpeech.find('[', start + 1);
+ }
+
+ size_t position = mSpeech.find('|');
+ if (mSpeech[start + 1] == '@' && mSpeech[start + 2] == '@')
+ {
+ mSpeech.erase(e, 1);
+ mSpeech.erase(start, (position - start) + 1);
+ }
+ position = mSpeech.find('@');
+
+ while (position != std::string::npos)
+ {
+ mSpeech.erase(position, 2);
+ position = mSpeech.find('@');
+ }
+
+ start = mSpeech.find('[', start + 1);
+ e = mSpeech.find(']', start);
+ }
+
+ if (!mSpeech.empty())
+ {
+ mSpeechTime = time <= static_cast<int>(SPEECH_MAX_TIME)
+ ? time : static_cast<int>(SPEECH_MAX_TIME);
+ }
+
+ const int speech = mSpeechType;
+ if (speech == TEXT_OVERHEAD && userPalette)
+ {
+ delete mText;
+
+ mText = new Text(mSpeech,
+ getPixelX(), getPixelY() - getHeight(),
+ gcn::Graphics::CENTER,
+ &userPalette->getColor(UserPalette::PARTICLE),
+ true);
+ }
+}
+
+void Being::takeDamage(Being *const attacker, const int amount,
+ const AttackType type, const int attackId)
+{
+ if (!userPalette || !attacker)
+ return;
+
+ gcn::Font *font = nullptr;
+ // TRANSLATORS: hit or miss message in attacks
+ const std::string damage = amount ? toString(amount) : type == FLEE ?
+ _("dodge") : _("miss");
+ const gcn::Color *color;
+
+ if (gui)
+ font = gui->getInfoParticleFont();
+
+ // Selecting the right color
+ if (type == CRITICAL || type == FLEE)
+ {
+ if (type == CRITICAL)
+ attacker->setCriticalHit(amount);
+
+ if (attacker == player_node)
+ {
+ color = &userPalette->getColor(
+ UserPalette::HIT_LOCAL_PLAYER_CRITICAL);
+ }
+ else
+ {
+ color = &userPalette->getColor(UserPalette::HIT_CRITICAL);
+ }
+ }
+ else if (!amount)
+ {
+ if (attacker == player_node)
+ {
+ // This is intended to be the wrong direction to visually
+ // differentiate between hits and misses
+ color = &userPalette->getColor(UserPalette::HIT_LOCAL_PLAYER_MISS);
+ }
+ else
+ {
+ color = &userPalette->getColor(UserPalette::MISS);
+ }
+ }
+ else if (mType == MONSTER)
+ {
+ if (attacker == player_node)
+ {
+ color = &userPalette->getColor(
+ UserPalette::HIT_LOCAL_PLAYER_MONSTER);
+ }
+ else
+ {
+ color = &userPalette->getColor(
+ UserPalette::HIT_PLAYER_MONSTER);
+ }
+ }
+ else if (mType == PLAYER && attacker != player_node
+ && this == player_node)
+ {
+ // here player was attacked by other player. mark him as enemy.
+ color = &userPalette->getColor(UserPalette::HIT_PLAYER_PLAYER);
+ attacker->setEnemy(true);
+ attacker->updateColors();
+ }
+ else
+ {
+ color = &userPalette->getColor(UserPalette::HIT_MONSTER_PLAYER);
+ }
+
+ if (chatWindow && mShowBattleEvents)
+ {
+ if (this == player_node)
+ {
+ if (attacker->mType == PLAYER || amount)
+ {
+ chatWindow->battleChatLog(strprintf("%s : Hit you -%d",
+ attacker->getName().c_str(), amount), BY_OTHER);
+ }
+ }
+ else if (attacker == player_node && amount)
+ {
+ chatWindow->battleChatLog(strprintf("%s : You hit %s -%d",
+ attacker->getName().c_str(), getName().c_str(), amount),
+ BY_PLAYER);
+ }
+ }
+ if (font && particleEngine)
+ {
+ // Show damage number
+ particleEngine->addTextSplashEffect(damage,
+ getPixelX(), getPixelY() - 16, color, font, true);
+ }
+
+ if (type != SKILL)
+ attacker->updateHit(amount);
+
+ if (amount > 0)
+ {
+ if (player_node && player_node == this)
+ player_node->setLastHitFrom(attacker->getName());
+
+ mDamageTaken += amount;
+ if (mInfo)
+ {
+ playSfx(mInfo->getSound(SOUND_EVENT_HURT), this, false, mX, mY);
+
+ if (!mInfo->isStaticMaxHP())
+ {
+ if (!mHP && mInfo->getMaxHP() < mDamageTaken)
+ mInfo->setMaxHP(mDamageTaken);
+ }
+ }
+ if (mHP && isAlive())
+ {
+ mHP -= amount;
+ if (mHP < 0)
+ mHP = 0;
+ }
+
+ if (mType == MONSTER)
+ {
+ updatePercentHP();
+ updateName();
+ }
+ else if (mType == PLAYER && socialWindow && getName() != "")
+ {
+ socialWindow->updateAvatar(getName());
+ }
+
+ if (effectManager)
+ {
+ const int hitEffectId = getHitEffect(attacker, type, attackId);
+ if (hitEffectId >= 0)
+ effectManager->trigger(hitEffectId, this);
+ }
+ }
+ else
+ {
+ if (effectManager)
+ {
+ const int hitEffectId = getHitEffect(attacker,
+ MISS, attackId);
+ if (hitEffectId >= 0)
+ effectManager->trigger(hitEffectId, this);
+ }
+ }
+}
+
+int Being::getHitEffect(const Being *const attacker,
+ const AttackType type, const int attackId) const
+{
+ if (!effectManager)
+ return 0;
+
+ // Init the particle effect path based on current
+ // weapon or default.
+ int hitEffectId = 0;
+ if (type != SKILL)
+ {
+ if (attacker)
+ {
+ const ItemInfo *attackerWeapon = attacker->getEquippedWeapon();
+ if (attackerWeapon && attacker->getType() == PLAYER)
+ {
+ if (type == MISS)
+ hitEffectId = attackerWeapon->getMissEffectId();
+ else if (type != CRITICAL)
+ hitEffectId = attackerWeapon->getHitEffectId();
+ else
+ hitEffectId = attackerWeapon->getCriticalHitEffectId();
+ }
+ else if (attacker->getType() == MONSTER)
+ {
+ const BeingInfo *const info = attacker->getInfo();
+ if (info)
+ {
+ const Attack *atk = info->getAttack(attackId);
+ if (atk)
+ {
+ if (type == MISS)
+ hitEffectId = atk->mMissEffectId;
+ else if (type != CRITICAL)
+ hitEffectId = atk->mHitEffectId;
+ else
+ hitEffectId = atk->mCriticalHitEffectId;
+ }
+ else
+ {
+ hitEffectId = getDefaultEffectId(type);
+ }
+ }
+ }
+ else
+ {
+ hitEffectId = getDefaultEffectId(type);
+ }
+ }
+ else
+ {
+ hitEffectId = getDefaultEffectId(type);
+ }
+ }
+ else
+ {
+ // move skills effects to +100000 in effects list
+ hitEffectId = attackId + 100000;
+ }
+ return hitEffectId;
+}
+
+int Being::getDefaultEffectId(const int type)
+{
+ if (type == MISS)
+ return paths.getIntValue("missEffectId");
+ else if (type != CRITICAL)
+ return paths.getIntValue("hitEffectId");
+ else
+ return paths.getIntValue("criticalHitEffectId");
+}
+
+void Being::handleAttack(Being *const victim, const int damage,
+ const int attackId)
+{
+ if (!victim || !mInfo)
+ return;
+
+ if (this != player_node)
+ setAction(Being::ATTACK, attackId);
+
+ if (mType == PLAYER && mEquippedWeapon)
+ fireMissile(victim, mEquippedWeapon->getMissileParticleFile());
+ else if (mInfo->getAttack(attackId))
+ fireMissile(victim, mInfo->getAttack(attackId)->mMissileParticle);
+
+#ifdef MANASERV_SUPPORT
+ if (Net::getNetworkType() != ServerInfo::MANASERV)
+#endif
+ {
+ reset();
+ mActionTime = tick_time;
+ }
+
+ if (this != player_node)
+ {
+ const uint8_t dir = calcDirection(victim->getTileX(),
+ victim->getTileY());
+ if (dir)
+ setDirection(dir);
+ }
+ if (damage && victim->mType == PLAYER && victim->mAction == SIT)
+ victim->setAction(STAND);
+
+ if (mType == PLAYER)
+ {
+ if (mSpriteIDs.size() >= 10)
+ {
+ // here 10 is weapon slot
+ int weaponId = mSpriteIDs[10];
+ if (!weaponId)
+ weaponId = -100 - mSubType;
+ const ItemInfo &info = ItemDB::get(weaponId);
+ playSfx(info.getSound((damage > 0) ?
+ SOUND_EVENT_HIT : SOUND_EVENT_MISS), victim, true, mX, mY);
+ }
+ }
+ else
+ {
+ playSfx(mInfo->getSound((damage > 0) ?
+ SOUND_EVENT_HIT : SOUND_EVENT_MISS), victim, true, mX, mY);
+ }
+}
+
+void Being::handleSkill(Being *const victim, const int damage,
+ const int skillId, const int skillLevel)
+{
+ if (!victim || !mInfo || !skillDialog)
+ return;
+
+ if (this != player_node)
+ setAction(Being::ATTACK, 1);
+
+ SkillInfo *const skill = skillDialog->getSkill(skillId);
+ const SkillData *const data = skill
+ ? skill->getData1(skillLevel) : nullptr;
+ if (data)
+ fireMissile(victim, data->particle);
+
+#ifdef MANASERV_SUPPORT
+ if (Net::getNetworkType() != ServerInfo::MANASERV)
+#endif
+ {
+ reset();
+ mActionTime = tick_time;
+ }
+
+ if (this != player_node)
+ {
+ const uint8_t dir = calcDirection(victim->getTileX(),
+ victim->getTileY());
+ if (dir)
+ setDirection(dir);
+ }
+ if (damage && victim->mType == PLAYER && victim->mAction == SIT)
+ victim->setAction(STAND);
+ if (data)
+ {
+ if (damage > 0)
+ playSfx(data->soundHit, victim, true, mX, mY);
+ else
+ playSfx(data->soundMiss, victim, true, mX, mY);
+ }
+ else
+ {
+ playSfx(mInfo->getSound((damage > 0) ?
+ SOUND_EVENT_HIT : SOUND_EVENT_MISS), victim, true, mX, mY);
+ }
+}
+
+void Being::setName(const std::string &name)
+{
+ if (mType == NPC)
+ {
+ mName = name.substr(0, name.find('#', 0));
+ showName();
+ }
+ else
+ {
+ mName = name;
+
+ if (mType == PLAYER && getShowName())
+ showName();
+ }
+}
+
+void Being::setShowName(const bool doShowName)
+{
+ if (mShowName == doShowName)
+ return;
+
+ mShowName = doShowName;
+
+ if (doShowName)
+ {
+ showName();
+ }
+ else
+ {
+ delete mDispName;
+ mDispName = nullptr;
+ }
+}
+
+void Being::setGuildName(const std::string &name)
+{
+ mGuildName = name;
+}
+
+void Being::setGuildPos(const std::string &pos A_UNUSED)
+{
+}
+
+void Being::addGuild(Guild *const guild)
+{
+ if (!guild)
+ return;
+
+ mGuilds[guild->getId()] = guild;
+
+ if (this == player_node && socialWindow)
+ socialWindow->addTab(guild);
+}
+
+void Being::removeGuild(const int id)
+{
+ if (this == player_node && socialWindow)
+ socialWindow->removeTab(mGuilds[id]);
+
+ if (mGuilds[id])
+ mGuilds[id]->removeMember(getName());
+ mGuilds.erase(id);
+}
+
+Guild *Being::getGuild(const std::string &guildName) const
+{
+ FOR_EACH (GuildsMapCIter, itr, mGuilds)
+ {
+ Guild *const guild = itr->second;
+ if (guild && guild->getName() == guildName)
+ return guild;
+ }
+
+ return nullptr;
+}
+
+Guild *Being::getGuild(const int id) const
+{
+ const std::map<int, Guild*>::const_iterator itr = mGuilds.find(id);
+ if (itr != mGuilds.end())
+ return itr->second;
+
+ return nullptr;
+}
+
+Guild *Being::getGuild() const
+{
+ const std::map<int, Guild*>::const_iterator itr = mGuilds.begin();
+ if (itr != mGuilds.end())
+ return itr->second;
+
+ return nullptr;
+}
+
+void Being::clearGuilds()
+{
+ FOR_EACH (GuildsMapCIter, itr, mGuilds)
+ {
+ Guild *const guild = itr->second;
+
+ if (guild)
+ {
+ if (this == player_node && socialWindow)
+ socialWindow->removeTab(guild);
+
+ guild->removeMember(mId);
+ }
+ }
+
+ mGuilds.clear();
+}
+
+void Being::setParty(Party *const party)
+{
+ if (party == mParty)
+ return;
+
+ Party *const old = mParty;
+ mParty = party;
+
+ if (old)
+ old->removeMember(mId);
+
+ if (party)
+ party->addMember(mId, mName);
+
+ updateColors();
+
+ if (this == player_node && socialWindow)
+ {
+ if (old)
+ socialWindow->removeTab(old);
+
+ if (party)
+ socialWindow->addTab(party);
+ }
+}
+
+void Being::updateGuild()
+{
+ if (!player_node)
+ return;
+
+ Guild *const guild = player_node->getGuild();
+ if (!guild)
+ {
+ clearGuilds();
+ updateColors();
+ return;
+ }
+ if (guild->getMember(getName()))
+ {
+ setGuild(guild);
+ if (!guild->getName().empty())
+ mGuildName = guild->getName();
+ }
+ updateColors();
+}
+
+void Being::setGuild(Guild *const guild)
+{
+ Guild *const old = getGuild();
+ if (guild == old)
+ return;
+
+ clearGuilds();
+ addGuild(guild);
+
+ if (old)
+ old->removeMember(mName);
+
+ updateColors();
+
+ if (this == player_node && socialWindow)
+ {
+ if (old)
+ socialWindow->removeTab(old);
+
+ if (guild)
+ socialWindow->addTab(guild);
+ }
+}
+
+void Being::fireMissile(Being *const victim, const std::string &particle) const
+{
+ if (!victim || particle.empty() || !particleEngine)
+ return;
+
+ Particle *const target = particleEngine->createChild();
+
+ if (!target)
+ return;
+
+ Particle *const missile = target->addEffect(
+ particle, getPixelX(), getPixelY());
+
+ if (missile)
+ {
+ target->moveBy(Vector(0.0f, 0.0f, 32.0f));
+ target->setLifetime(1000);
+ victim->controlParticle(target);
+
+ missile->setDestination(target, 7, 0);
+ missile->setDieDistance(8);
+ missile->setLifetime(900);
+ }
+}
+
+std::string Being::getSitAction() const
+{
+ if (serverVersion < 0)
+ {
+ return SpriteAction::SIT;
+ }
+ else
+ {
+ if (mMap)
+ {
+ const unsigned char mask = mMap->getBlockMask(mX, mY);
+ if (mask & Map::BLOCKMASK_GROUNDTOP)
+ return SpriteAction::SITTOP;
+ else if (mask & Map::BLOCKMASK_AIR)
+ return SpriteAction::SITSKY;
+ else if (mask & Map::BLOCKMASK_WATER)
+ return SpriteAction::SITWATER;
+ }
+ return SpriteAction::SIT;
+ }
+}
+
+
+std::string Being::getMoveAction() const
+{
+ if (serverVersion < 0)
+ {
+ return SpriteAction::MOVE;
+ }
+ else
+ {
+ if (mMap)
+ {
+ const unsigned char mask = mMap->getBlockMask(mX, mY);
+ if (mask & Map::BLOCKMASK_AIR)
+ return SpriteAction::FLY;
+ else if (mask & Map::BLOCKMASK_WATER)
+ return SpriteAction::SWIM;
+ }
+ return SpriteAction::MOVE;
+ }
+}
+
+std::string Being::getWeaponAttackAction(const ItemInfo *const weapon) const
+{
+ if (!weapon)
+ return SpriteAction::ATTACK;
+
+ if (serverVersion < 0 || !weapon)
+ {
+ return weapon->getAttackAction();
+ }
+ else
+ {
+ if (mMap)
+ {
+ const unsigned char mask = mMap->getBlockMask(mX, mY);
+ if (mask & Map::BLOCKMASK_AIR)
+ return weapon->getSkyAttackAction();
+ else if (mask & Map::BLOCKMASK_WATER)
+ return weapon->getWaterAttackAction();
+ }
+ return weapon->getAttackAction();
+ }
+}
+
+std::string Being::getAttackAction(const Attack *const attack1) const
+{
+ if (!attack1)
+ return SpriteAction::ATTACK;
+
+ if (serverVersion < 0 || !attack1)
+ {
+ return attack1->mAction;
+ }
+ else
+ {
+ if (mMap)
+ {
+ const unsigned char mask = mMap->getBlockMask(mX, mY);
+ if (mask & Map::BLOCKMASK_AIR)
+ return attack1->mSkyAction;
+ else if (mask & Map::BLOCKMASK_WATER)
+ return attack1->mWaterAction;
+ }
+ return attack1->mAction;
+ }
+}
+
+#define getSpriteAction(func, action) \
+ std::string Being::get##func##Action() const \
+{ \
+ if (serverVersion < 0) \
+ { \
+ return SpriteAction::action; \
+ } \
+ else \
+ { \
+ if (mMap) \
+ { \
+ const unsigned char mask = mMap->getBlockMask(mX, mY); \
+ if (mask & Map::BLOCKMASK_AIR) \
+ return SpriteAction::action##SKY; \
+ else if (mask & Map::BLOCKMASK_WATER) \
+ return SpriteAction::action##WATER; \
+ } \
+ return SpriteAction::action; \
+ } \
+}
+
+getSpriteAction(Dead, DEAD)
+getSpriteAction(Stand, STAND)
+getSpriteAction(Spawn, SPAWN)
+
+void Being::setAction(const Action &action, const int attackId)
+{
+ std::string currentAction = SpriteAction::INVALID;
+
+ switch (action)
+ {
+ case MOVE:
+ if (mInfo)
+ {
+ playSfx(mInfo->getSound(
+ SOUND_EVENT_MOVE), nullptr, true, mX, mY);
+ }
+ currentAction = getMoveAction();
+ // Note: When adding a run action,
+ // Differentiate walk and run with action name,
+ // while using only the ACTION_MOVE.
+ break;
+ case SIT:
+ currentAction = getSitAction();
+ if (mInfo)
+ {
+ SoundEvent event;
+ if (currentAction == SpriteAction::SITTOP)
+ event = SOUND_EVENT_SITTOP;
+ else
+ event = SOUND_EVENT_SIT;
+ playSfx(mInfo->getSound(event), nullptr, true, mX, mY);
+ }
+ break;
+ case ATTACK:
+ if (mEquippedWeapon)
+ {
+ currentAction = getWeaponAttackAction(mEquippedWeapon);
+ reset();
+ }
+ else
+ {
+ if (!mInfo || !mInfo->getAttack(attackId))
+ break;
+
+ currentAction = getAttackAction(mInfo->getAttack(attackId));
+ reset();
+
+ // attack particle effect
+ if (Particle::enabled)
+ {
+ const int effectId = mInfo->getAttack(attackId)->mEffectId;
+
+ int rotation;
+ switch (mSpriteDirection)
+ {
+ case DIRECTION_DOWN:
+ default:
+ rotation = 0;
+ break;
+ case DIRECTION_LEFT:
+ rotation = 90;
+ break;
+ case DIRECTION_UP:
+ rotation = 180;
+ break;
+ case DIRECTION_RIGHT:
+ rotation = 270;
+ break;
+ }
+ if (Particle::enabled && effectManager && effectId >= 0)
+ effectManager->trigger(effectId, this, rotation);
+ }
+ }
+ break;
+ case HURT:
+ if (mInfo)
+ {
+ playSfx(mInfo->getSound(SOUND_EVENT_HURT),
+ this, false, mX, mY);
+ }
+ break;
+ case DEAD:
+ currentAction = getDeadAction();
+ if (mInfo)
+ {
+ playSfx(mInfo->getSound(SOUND_EVENT_DIE), this, true, mX, mY);
+ if (mType == MONSTER || mType == NPC)
+ mYDiff = mInfo->getDeadSortOffsetY();
+ }
+ break;
+ case STAND:
+ currentAction = getStandAction();
+ break;
+ case SPAWN:
+ if (mInfo)
+ {
+ playSfx(mInfo->getSound(SOUND_EVENT_SPAWN),
+ nullptr, true, mX, mY);
+ }
+ currentAction = getSpawnAction();
+ break;
+ default:
+ logger->log("Being::setAction unknown action: "
+ + toString(static_cast<unsigned>(action)));
+ break;
+ }
+
+ if (currentAction != SpriteAction::INVALID)
+ {
+ mSpriteAction = currentAction;
+ play(currentAction);
+ if (mEmotionSprite)
+ mEmotionSprite->play(currentAction);
+ if (mAnimationEffect)
+ mAnimationEffect->play(currentAction);
+ mAction = action;
+ }
+
+ if (currentAction != SpriteAction::MOVE
+ && currentAction != SpriteAction::FLY
+ && currentAction != SpriteAction::SWIM)
+ {
+ mActionTime = tick_time;
+ }
+}
+
+void Being::setDirection(const uint8_t direction)
+{
+ if (mDirection == direction)
+ return;
+
+ mDirection = direction;
+
+ mDirectionDelayed = 0;
+
+ // if the direction does not change much, keep the common component
+ int mFaceDirection = mDirection & direction;
+ if (!mFaceDirection)
+ mFaceDirection = direction;
+
+ SpriteDirection dir;
+ if (mFaceDirection & UP)
+ {
+ if (mFaceDirection & LEFT)
+ dir = DIRECTION_UPLEFT;
+ else if (mFaceDirection & RIGHT)
+ dir = DIRECTION_UPRIGHT;
+ else
+ dir = DIRECTION_UP;
+ }
+ else if (mFaceDirection & DOWN)
+ {
+ if (mFaceDirection & LEFT)
+ dir = DIRECTION_DOWNLEFT;
+ else if (mFaceDirection & RIGHT)
+ dir = DIRECTION_DOWNRIGHT;
+ else
+ dir = DIRECTION_DOWN;
+ }
+ else if (mFaceDirection & RIGHT)
+ {
+ dir = DIRECTION_RIGHT;
+ }
+ else
+ {
+ dir = DIRECTION_LEFT;
+ }
+ mSpriteDirection = static_cast<uint8_t>(dir);
+
+ CompoundSprite::setSpriteDirection(dir);
+ if (mEmotionSprite)
+ mEmotionSprite->setSpriteDirection(dir);
+ if (mAnimationEffect)
+ mAnimationEffect->setSpriteDirection(dir);
+ recalcSpritesOrder();
+}
+
+uint8_t Being::calcDirection() const
+{
+ uint8_t dir = 0;
+ if (mDest.x > mX)
+ dir |= RIGHT;
+ else if (mDest.x < mX)
+ dir |= LEFT;
+ if (mDest.y > mY)
+ dir |= DOWN;
+ else if (mDest.y < mY)
+ dir |= UP;
+ return dir;
+}
+
+uint8_t Being::calcDirection(const int dstX, const int dstY) const
+{
+ uint8_t dir = 0;
+ if (dstX > mX)
+ dir |= RIGHT;
+ else if (dstX < mX)
+ dir |= LEFT;
+ if (dstY > mY)
+ dir |= DOWN;
+ else if (dstY < mY)
+ dir |= UP;
+ return dir;
+}
+
+void Being::nextTile()
+{
+ if (mPath.empty())
+ {
+ setAction(STAND);
+ return;
+ }
+
+ const Position pos = mPath.front();
+ mPath.pop_front();
+
+ const uint8_t dir = calcDirection(pos.x, pos.y);
+ if (dir)
+ setDirection(dir);
+
+ if (!mMap || !mMap->getWalk(pos.x, pos.y, getWalkMask()))
+ {
+ setAction(STAND);
+ return;
+ }
+
+ mX = pos.x;
+ mY = pos.y;
+ setAction(MOVE);
+ mActionTime += static_cast<int>(mWalkSpeed.x / 10);
+}
+
+void Being::logic()
+{
+ BLOCK_START("Being::logic")
+ // Reduce the time that speech is still displayed
+ if (mSpeechTime > 0)
+ mSpeechTime--;
+
+ // Remove text and speechbubbles if speech boxes aren't being used
+ if (mSpeechTime == 0 && mText)
+ {
+ delete mText;
+ mText = nullptr;
+ }
+
+ const int time = tick_time * MILLISECONDS_IN_A_TICK;
+ if (mEmotionSprite)
+ mEmotionSprite->update(time);
+
+ if (mAnimationEffect)
+ {
+ mAnimationEffect->update(time);
+ if (mAnimationEffect->isTerminated())
+ {
+ delete mAnimationEffect;
+ mAnimationEffect = nullptr;
+ }
+ }
+
+ int frameCount = static_cast<int>(getFrameCount());
+#ifdef MANASERV_SUPPORT
+ if ((Net::getNetworkType() == ServerInfo::MANASERV) && (mAction != DEAD))
+ {
+ const Vector dest = (mPath.empty()) ?
+ mDest : Vector(static_cast<float>(mPath.front().x),
+ static_cast<float>(mPath.front().y));
+
+ // This is a hack that stops NPCs from running off the map...
+ if (mDest.x <= 0 && mDest.y <= 0)
+ {
+ BLOCK_END("Being::logic")
+ return;
+ }
+
+ // The Vector representing the difference between current position
+ // and the next destination path node.
+ Vector dir = dest - mPos;
+
+ const float nominalLength = dir.length();
+
+ // When we've not reached our destination, move to it.
+ if (nominalLength > 0.0f && !mWalkSpeed.isNull())
+ {
+ // The deplacement of a point along a vector is calculated
+ // using the Unit Vector (â) multiplied by the point speed.
+ // â = a / ||a|| (||a|| is the a length.)
+ // Then, diff = (dir/||dir||) * speed.
+ const Vector normalizedDir = dir.normalized();
+ Vector diff(normalizedDir.x * mWalkSpeed.x,
+ normalizedDir.y * mWalkSpeed.y);
+
+ // Test if we don't miss the destination by a move too far:
+ if (diff.length() > nominalLength)
+ {
+ setPosition(mPos + dir);
+
+ // Also, if the destination is reached, try to get the next
+ // path point, if existing.
+ if (!mPath.empty())
+ mPath.pop_front();
+ }
+ // Otherwise, go to it using the nominal speed.
+ else
+ {
+ setPosition(mPos + diff);
+ }
+
+ if (mAction != MOVE)
+ setAction(MOVE);
+
+ // Update the player sprite direction.
+ // N.B.: We only change this if the distance is more than one pixel.
+ if (nominalLength > 1.0f)
+ {
+ int direction = 0;
+ const float dx = std::abs(dir.x);
+ float dy = std::abs(dir.y);
+
+ // When not using mouse for the player, we slightly prefer
+ // UP and DOWN position, especially when walking diagonally.
+ if (player_node && this == player_node &&
+ !player_node->isPathSetByMouse())
+ {
+ dy = dy + 2;
+ }
+
+ if (dx > dy)
+ direction |= (dir.x > 0) ? RIGHT : LEFT;
+ else
+ direction |= (dir.y > 0) ? DOWN : UP;
+
+ setDirection(static_cast<uint8_t>(direction));
+ }
+ }
+ else if (!mPath.empty())
+ {
+ // If the current path node has been reached,
+ // remove it and go to the next one.
+ mPath.pop_front();
+ }
+ else if (mAction == MOVE)
+ {
+ setAction(STAND);
+ }
+ }
+ else
+ if (Net::getNetworkType() != ServerInfo::MANASERV)
+#endif
+ {
+ switch (mAction)
+ {
+ case STAND:
+ case SIT:
+ case DEAD:
+ case HURT:
+ case SPAWN:
+ default:
+ break;
+
+ case MOVE:
+ {
+ if (getWalkSpeed().x && static_cast<int> ((static_cast<float>(
+ get_elapsed_time(mActionTime)) * static_cast<float>(
+ frameCount)) / getWalkSpeed().x)
+ >= frameCount)
+ {
+ nextTile();
+ }
+ break;
+ }
+
+ case ATTACK:
+ {
+// std::string particleEffect("");
+
+ if (!mActionTime)
+ break;
+
+ int curFrame = 0;
+ if (mAttackSpeed)
+ {
+ curFrame = (get_elapsed_time(mActionTime) * frameCount)
+ / mAttackSpeed;
+ }
+
+ if (this == player_node && curFrame >= frameCount)
+ nextTile();
+
+ break;
+ }
+ }
+
+ // Update pixel coordinates
+ setPosition(static_cast<float>(mX * 32 + 16 + getXOffset()),
+ static_cast<float>(mY * 32 + 32 + getYOffset()));
+ }
+
+ if (mEmotionSprite)
+ {
+ mEmotionTime--;
+ if (mEmotionTime == 0)
+ {
+ delete mEmotionSprite;
+ mEmotionSprite = nullptr;
+ }
+ }
+
+ ActorSprite::logic();
+
+ if (frameCount < 10)
+ frameCount = 10;
+
+ if (!isAlive() && getWalkSpeed().x
+ && Net::getGameHandler()->removeDeadBeings()
+ && static_cast<int> ((static_cast<float>(get_elapsed_time(mActionTime))
+ / static_cast<float>(getWalkSpeed().x))) >= frameCount)
+ {
+ if (mType != PLAYER && actorSpriteManager)
+ actorSpriteManager->destroy(this);
+ }
+
+ const SoundInfo *const sound = mNextSound.sound;
+ if (sound)
+ {
+ const int time2 = tick_time;
+ if (time2 > mNextSound.time)
+ {
+ soundManager.playSfx(sound->sound, mNextSound.x, mNextSound.y);
+
+ mNextSound.sound = nullptr;
+ mNextSound.time = time2 + sound->delay;
+ }
+ }
+
+ BLOCK_END("Being::logic")
+}
+
+void Being::drawEmotion(Graphics *const graphics, const int offsetX,
+ const int offsetY)
+{
+ const int px = getPixelX() - offsetX - 16;
+ const int py = getPixelY() - offsetY - 64 - 32;
+ if (mEmotionSprite)
+ mEmotionSprite->draw(graphics, px, py);
+ if (mAnimationEffect)
+ mAnimationEffect->draw(graphics, px, py);
+}
+
+void Being::drawSpeech(const int offsetX, const int offsetY)
+{
+ if (!mSpeechBubble || mSpeech.empty())
+ return;
+
+ const int px = getPixelX() - offsetX;
+ const int py = getPixelY() - offsetY;
+ const int speech = mSpeechType;
+
+ // Draw speech above this being
+ if (mSpeechTime == 0)
+ {
+ if (mSpeechBubble->isVisible())
+ mSpeechBubble->setVisible(false);
+ }
+ else if (mSpeechTime > 0 && (speech == NAME_IN_BUBBLE ||
+ speech == NO_NAME_IN_BUBBLE))
+ {
+ const bool isShowName = (speech == NAME_IN_BUBBLE);
+
+ delete mText;
+ mText = nullptr;
+
+ mSpeechBubble->setCaption(isShowName ? mName : "");
+
+ mSpeechBubble->setText(mSpeech, isShowName);
+ mSpeechBubble->setPosition(px - (mSpeechBubble->getWidth() / 2),
+ py - getHeight() - (mSpeechBubble->getHeight()));
+ mSpeechBubble->setVisible(true);
+ }
+ else if (mSpeechTime > 0 && speech == TEXT_OVERHEAD)
+ {
+ mSpeechBubble->setVisible(false);
+
+ if (!mText && userPalette)
+ {
+ mText = new Text(mSpeech, getPixelX(), getPixelY() - getHeight(),
+ gcn::Graphics::CENTER, &Theme::getThemeColor(
+ Theme::BUBBLE_TEXT), true);
+ }
+ }
+ else if (speech == NO_SPEECH)
+ {
+ mSpeechBubble->setVisible(false);
+
+ delete mText;
+ mText = nullptr;
+ }
+}
+
+int Being::getOffset(const signed char pos, const signed char neg) const
+{
+ // Check whether we're walking in the requested direction
+ if (mAction != MOVE || !(mDirection & (pos | neg)))
+ return 0;
+
+ int offset = 0;
+
+ if (mMap)
+ {
+ const int time = get_elapsed_time(mActionTime);
+ offset = (pos == LEFT && neg == RIGHT) ?
+ static_cast<int>((static_cast<float>(time)
+ * static_cast<float>(mMap->getTileWidth()))
+ / static_cast<float>(mWalkSpeed.x)) :
+ static_cast<int>((static_cast<float>(time)
+ * static_cast<float>(mMap->getTileHeight()))
+ / static_cast<float>(mWalkSpeed.y));
+ }
+
+ // We calculate the offset _from_ the _target_ location
+ offset -= 32;
+ if (offset > 0)
+ offset = 0;
+
+ // Going into negative direction? Invert the offset.
+ if (mDirection & pos)
+ offset = -offset;
+
+ if (offset > 32)
+ offset = 32;
+ if (offset < -32)
+ offset = -32;
+
+ return offset;
+}
+
+void Being::updateCoords()
+{
+ if (!mDispName)
+ return;
+
+ int offsetX = getPixelX();
+ int offsetY = getPixelY();
+ if (mInfo)
+ {
+ offsetX += mInfo->getNameOffsetX();
+ offsetY += mInfo->getNameOffsetY();
+ }
+ // Monster names show above the sprite instead of below it
+ if (mType == MONSTER)
+ offsetY += - getHeight() - mDispName->getHeight();
+
+ mDispName->adviseXY(offsetX, offsetY, mMoveNames);
+}
+
+void Being::optionChanged(const std::string &value)
+{
+ if (mType == PLAYER && value == "visiblenames")
+ setShowName(config.getBoolValue("visiblenames"));
+}
+
+void Being::flashName(const int time)
+{
+ if (mDispName)
+ mDispName->flash(time);
+}
+
+std::string Being::getGenderSignWithSpace() const
+{
+ const std::string &str = getGenderSign();
+ if (str.empty())
+ return str;
+ else
+ return std::string(" ").append(str);
+}
+
+std::string Being::getGenderSign() const
+{
+ std::string str;
+ if (mShowGender)
+ {
+ if (getGender() == GENDER_FEMALE)
+ str = "\u2640";
+ else if (getGender() == GENDER_MALE)
+ str = "\u2642";
+ }
+ if (mShowPlayersStatus && mAdvanced)
+ {
+ if (mShop)
+ str.append("$");
+ if (mAway)
+ {
+ // TRANSLATORS: this away status writed in player nick
+ str.append(_("A"));
+ }
+ else if (mInactive)
+ {
+ // TRANSLATORS: this inactive status writed in player nick
+ str.append(_("I"));
+ }
+ }
+ return str;
+}
+
+void Being::showName()
+{
+ if (mName.empty())
+ return;
+
+ delete mDispName;
+ mDispName = nullptr;
+
+ if (mHideErased && player_relations.getRelation(mName) ==
+ PlayerRelation::ERASED)
+ {
+ return;
+ }
+
+ std::string displayName(mName);
+
+ if (mType != MONSTER && (mShowGender || mShowLevel))
+ {
+ displayName.append(" ");
+ if (mShowLevel && getLevel() != 0)
+ displayName.append(toString(getLevel()));
+
+ displayName.append(getGenderSign());
+ }
+
+ if (mType == MONSTER)
+ {
+ if (config.getBoolValue("showMonstersTakedDamage"))
+ displayName.append(", ").append(toString(getDamageTaken()));
+ }
+
+ gcn::Font *font = nullptr;
+ if (player_node && player_node->getTarget() == this
+ && mType != MONSTER)
+ {
+ font = boldFont;
+ }
+ else if (mType == PLAYER && !player_relations.isGoodName(this) && gui)
+ {
+ font = gui->getSecureFont();
+ }
+
+ if (mInfo)
+ {
+ mDispName = new FlashText(displayName,
+ getPixelX() + mInfo->getNameOffsetX(),
+ getPixelY() + mInfo->getNameOffsetY(),
+ gcn::Graphics::CENTER, mNameColor, font);
+ }
+ else
+ {
+ mDispName = new FlashText(displayName, getPixelX(), getPixelY(),
+ gcn::Graphics::CENTER, mNameColor, font);
+ }
+
+ updateCoords();
+}
+
+void Being::updateColors()
+{
+ if (userPalette)
+ {
+ if (mType == MONSTER)
+ {
+ mNameColor = &userPalette->getColor(UserPalette::MONSTER);
+ mTextColor = &userPalette->getColor(UserPalette::MONSTER);
+ }
+ else if (mType == NPC)
+ {
+ mNameColor = &userPalette->getColor(UserPalette::NPC);
+ mTextColor = &userPalette->getColor(UserPalette::NPC);
+ }
+ else if (this == player_node)
+ {
+ mNameColor = &userPalette->getColor(UserPalette::SELF);
+ mTextColor = &Theme::getThemeColor(Theme::PLAYER);
+ }
+ else
+ {
+ mTextColor = &Theme::getThemeColor(Theme::PLAYER);
+
+ if (player_relations.getRelation(mName) != PlayerRelation::ERASED)
+ mErased = false;
+ else
+ mErased = true;
+
+ if (mIsGM)
+ {
+ mTextColor = &userPalette->getColor(UserPalette::GM);
+ mNameColor = &userPalette->getColor(UserPalette::GM);
+ }
+ else if (mEnemy)
+ {
+ mNameColor = &userPalette->getColor(UserPalette::MONSTER);
+ }
+ else if (mParty && mParty == player_node->getParty())
+ {
+ mNameColor = &userPalette->getColor(UserPalette::PARTY);
+ }
+ else if (player_node && getGuild()
+ && getGuild() == player_node->getGuild())
+ {
+ mNameColor = &userPalette->getColor(UserPalette::GUILD);
+ }
+ else if (player_relations.getRelation(mName) ==
+ PlayerRelation::FRIEND)
+ {
+ mNameColor = &userPalette->getColor(UserPalette::FRIEND);
+ }
+ else if (player_relations.getRelation(mName) ==
+ PlayerRelation::DISREGARDED
+ || player_relations.getRelation(mName) ==
+ PlayerRelation::BLACKLISTED)
+ {
+ mNameColor = &userPalette->getColor(UserPalette::DISREGARDED);
+ }
+ else if (player_relations.getRelation(mName) ==
+ PlayerRelation::IGNORED
+ || player_relations.getRelation(mName) ==
+ PlayerRelation::ENEMY2)
+ {
+ mNameColor = &userPalette->getColor(UserPalette::IGNORED);
+ }
+ else if (player_relations.getRelation(mName) ==
+ PlayerRelation::ERASED)
+ {
+ mNameColor = &userPalette->getColor(UserPalette::ERASED);
+ }
+ else
+ {
+ mNameColor = &userPalette->getColor(UserPalette::PC);
+ }
+ }
+
+ if (mDispName)
+ mDispName->setColor(mNameColor);
+ }
+}
+
+void Being::setSprite(const unsigned int slot, const int id,
+ std::string color, const unsigned char colorId,
+ const bool isWeapon, const bool isTempSprite)
+{
+ if (slot >= Net::getCharServerHandler()->maxSprite())
+ return;
+
+ if (slot >= size())
+ ensureSize(slot + 1);
+
+ if (slot >= mSpriteIDs.size())
+ mSpriteIDs.resize(slot + 1, 0);
+
+ if (slot >= mSpriteColors.size())
+ mSpriteColors.resize(slot + 1, "");
+
+ if (slot >= mSpriteColorsIds.size())
+ mSpriteColorsIds.resize(slot + 1, 1);
+
+ // id = 0 means unequip
+ if (id == 0)
+ {
+ removeSprite(slot);
+
+ if (isWeapon)
+ mEquippedWeapon = nullptr;
+ const int id1 = mSpriteIDs[slot];
+ if (id1)
+ {
+ const ItemInfo &info = ItemDB::get(id1);
+ if (mMap)
+ {
+ const int pet = info.getPet();
+ if (pet)
+ removePet();
+ }
+ }
+ }
+ else
+ {
+ const ItemInfo &info = ItemDB::get(id);
+ const std::string filename = info.getSprite(mGender, mSubType);
+ AnimatedSprite *equipmentSprite = nullptr;
+
+ if (mType == PLAYER)
+ {
+ const int pet = info.getPet();
+ if (pet)
+ addPet(pet);
+ }
+
+ if (!filename.empty())
+ {
+ if (color.empty())
+ color = info.getDyeColorsString(colorId);
+
+ equipmentSprite = AnimatedSprite::delayedLoad(
+ paths.getStringValue("sprites").append(
+ combineDye(filename, color)));
+ }
+
+ if (equipmentSprite)
+ equipmentSprite->setSpriteDirection(getSpriteDirection());
+
+ CompoundSprite::setSprite(slot, equipmentSprite);
+
+ if (isWeapon)
+ mEquippedWeapon = &ItemDB::get(id);
+
+ setAction(mAction);
+ }
+
+ if (!isTempSprite)
+ {
+ mSpriteIDs[slot] = id;
+ mSpriteColors[slot] = color;
+ mSpriteColorsIds[slot] = colorId;
+ recalcSpritesOrder();
+ if (beingEquipmentWindow)
+ beingEquipmentWindow->updateBeing(this);
+ }
+}
+
+void Being::setSpriteID(const unsigned int slot, const int id)
+{
+ setSprite(slot, id, mSpriteColors[slot]);
+}
+
+void Being::setSpriteColor(const unsigned int slot, const std::string &color)
+{
+ setSprite(slot, mSpriteIDs[slot], color);
+}
+
+void Being::setHairStyle(const unsigned int slot, const int id)
+{
+// dumpSprites();
+ setSprite(slot, id, ItemDB::get(id).getDyeColorsString(mHairColor));
+// dumpSprites();
+}
+
+void Being::setHairColor(const unsigned int slot, const unsigned char color)
+{
+ mHairColor = color;
+ setSprite(slot, mSpriteIDs[slot], ItemDB::get(
+ getSpriteID(slot)).getDyeColorsString(color));
+}
+
+void Being::dumpSprites() const
+{
+ std::vector<int>::const_iterator it1 = mSpriteIDs.begin();
+ const std::vector<int>::const_iterator it1_end = mSpriteIDs.end();
+ StringVectCIter it2 = mSpriteColors.begin();
+ const StringVectCIter it2_end = mSpriteColors.end();
+ std::vector<int>::const_iterator it3 = mSpriteColorsIds.begin();
+ const std::vector<int>::const_iterator it3_end = mSpriteColorsIds.end();
+
+ logger->log("sprites");
+ for (; it1 != it1_end && it2 != it2_end && it3 != it3_end;
+ ++ it1, ++ it2, ++ it3)
+ {
+ logger->log("%d,%s,%d", *it1, (*it2).c_str(), *it3);
+ }
+}
+
+void Being::load()
+{
+ // Hairstyles are encoded as negative numbers. Count how far negative
+ // we can go.
+ int hairstyles = 1;
+ while (ItemDB::get(-hairstyles).getSprite(GENDER_MALE, 0) !=
+ paths.getStringValue("spriteErrorFile"))
+ {
+ hairstyles ++;
+ }
+ mNumberOfHairstyles = hairstyles;
+
+ int races = 100;
+ while (ItemDB::get(-races).getSprite(GENDER_MALE, 0) !=
+ paths.getStringValue("spriteErrorFile"))
+ {
+ races ++;
+ }
+ mNumberOfRaces = races - 100;
+}
+
+void Being::updateName()
+{
+ if (mShowName)
+ showName();
+}
+
+void Being::reReadConfig()
+{
+ BLOCK_START("Being::reReadConfig")
+ if (mUpdateConfigTime + 1 < cur_time)
+ {
+ mAwayEffect = paths.getIntValue("afkEffectId");
+ mHighlightMapPortals = config.getBoolValue("highlightMapPortals");
+ mConfLineLim = config.getIntValue("chatMaxCharLimit");
+ mSpeechType = config.getIntValue("speech");
+ mHighlightMonsterAttackRange =
+ config.getBoolValue("highlightMonsterAttackRange");
+ mLowTraffic = config.getBoolValue("lowTraffic");
+ mDrawHotKeys = config.getBoolValue("drawHotKeys");
+ mShowBattleEvents = config.getBoolValue("showBattleEvents");
+ mShowMobHP = config.getBoolValue("showMobHP");
+ mShowOwnHP = config.getBoolValue("showOwnHP");
+ mShowGender = config.getBoolValue("showgender");
+ mShowLevel = config.getBoolValue("showlevel");
+ mShowPlayersStatus = config.getBoolValue("showPlayersStatus");
+ mEnableReorderSprites = config.getBoolValue("enableReorderSprites");
+ mHideErased = config.getBoolValue("hideErased");
+ mMoveNames = config.getBoolValue("moveNames");
+
+ mUpdateConfigTime = cur_time;
+ }
+ BLOCK_END("Being::reReadConfig")
+}
+
+bool Being::updateFromCache()
+{
+ const BeingCacheEntry *const entry = Being::getCacheEntry(getId());
+
+ if (entry && entry->getTime() + 120 >= cur_time)
+ {
+ if (!entry->getName().empty())
+ setName(entry->getName());
+ setPartyName(entry->getPartyName());
+ setGuildName(entry->getGuildName());
+ setLevel(entry->getLevel());
+ setPvpRank(entry->getPvpRank());
+ setIp(entry->getIp());
+
+ mAdvanced = entry->isAdvanced();
+ if (mAdvanced)
+ {
+ const int flags = entry->getFlags();
+ mShop = ((flags & FLAG_SHOP) != 0);
+ mAway = ((flags & FLAG_AWAY) != 0);
+ mInactive = ((flags & FLAG_INACTIVE) != 0);
+ if (mShop || mAway || mInactive)
+ updateName();
+ }
+ else
+ {
+ mShop = false;
+ mAway = false;
+ mInactive = false;
+ }
+
+ updateAwayEffect();
+ if (mType == PLAYER)
+ updateColors();
+ return true;
+ }
+ return false;
+}
+
+void Being::addToCache() const
+{
+ if (player_node == this)
+ return;
+
+ BeingCacheEntry *entry = Being::getCacheEntry(getId());
+ if (!entry)
+ {
+ entry = new BeingCacheEntry(getId());
+ beingInfoCache.push_front(entry);
+
+ if (beingInfoCache.size() >= CACHE_SIZE)
+ {
+ delete beingInfoCache.back();
+ beingInfoCache.pop_back();
+ }
+ }
+ if (!mLowTraffic)
+ return;
+
+ entry->setName(getName());
+ entry->setLevel(getLevel());
+ entry->setPartyName(getPartyName());
+ entry->setGuildName(getGuildName());
+ entry->setTime(cur_time);
+ entry->setPvpRank(getPvpRank());
+ entry->setIp(getIp());
+ entry->setAdvanced(isAdvanced());
+ if (isAdvanced())
+ {
+ int flags = 0;
+ if (mShop)
+ flags += FLAG_SHOP;
+ if (mAway)
+ flags += FLAG_AWAY;
+ if (mInactive)
+ flags += FLAG_INACTIVE;
+ entry->setFlags(flags);
+ }
+ else
+ {
+ entry->setFlags(0);
+ }
+}
+
+BeingCacheEntry* Being::getCacheEntry(const int id)
+{
+ FOR_EACH (std::list<BeingCacheEntry*>::iterator, i, beingInfoCache)
+ {
+ if (!*i)
+ continue;
+
+ if (id == (*i)->getId())
+ {
+ // Raise priority: move it to front
+ if ((*i)->getTime() + 120 < cur_time)
+ {
+ beingInfoCache.splice(beingInfoCache.begin(),
+ beingInfoCache, i);
+ }
+ return *i;
+ }
+ }
+ return nullptr;
+}
+
+
+void Being::setGender(const Gender gender)
+{
+ if (gender != mGender)
+ {
+ mGender = gender;
+
+ // Reload all subsprites
+ for (unsigned int i = 0; i < mSpriteIDs.size(); i++)
+ {
+ if (mSpriteIDs.at(i) != 0)
+ setSprite(i, mSpriteIDs.at(i), mSpriteColors.at(i));
+ }
+
+ updateName();
+ }
+}
+
+void Being::setGM(const bool gm)
+{
+ mIsGM = gm;
+
+ updateColors();
+}
+
+void Being::talkTo() const
+{
+ if (!client->limitPackets(PACKET_NPC_TALK))
+ return;
+
+ Net::getNpcHandler()->talk(mId);
+}
+
+bool Being::draw(Graphics *const graphics,
+ const int offsetX, const int offsetY) const
+{
+ bool res = true;
+ if (!mErased)
+ res = ActorSprite::draw(graphics, offsetX, offsetY);
+
+ return res;
+}
+
+void Being::drawSprites(Graphics *const graphics,
+ const int posX, const int posY) const
+{
+ const int sz = getNumberOfLayers();
+ for (int f = 0; f < sz; f ++)
+ {
+ const int rSprite = mSpriteHide[mSpriteRemap[f]];
+ if (rSprite == 1)
+ continue;
+
+ Sprite *const sprite = getSprite(mSpriteRemap[f]);
+ if (sprite)
+ {
+ sprite->setAlpha(mAlpha);
+ sprite->draw(graphics, posX, posY);
+ }
+ }
+}
+
+void Being::drawSpritesSDL(Graphics *const graphics,
+ const int posX, const int posY) const
+{
+ const size_t sz = size();
+ for (unsigned f = 0; f < sz; f ++)
+ {
+ const int rSprite = mSpriteHide[mSpriteRemap[f]];
+ if (rSprite == 1)
+ continue;
+
+ const Sprite *const sprite = getSprite(mSpriteRemap[f]);
+ if (sprite)
+ sprite->draw(graphics, posX, posY);
+ }
+}
+
+bool Being::drawSpriteAt(Graphics *const graphics,
+ const int x, const int y) const
+{
+ bool res = true;
+
+ if (!mErased)
+ res = ActorSprite::drawSpriteAt(graphics, x, y);
+
+ if (!userPalette)
+ return res;
+
+ if (mHighlightMapPortals && mMap && mSubType == 45 && !mMap->getHasWarps())
+ {
+ graphics->setColor(userPalette->
+ getColorWithAlpha(UserPalette::PORTAL_HIGHLIGHT));
+
+ graphics->fillRectangle(gcn::Rectangle(x, y, 32, 32));
+
+ if (mDrawHotKeys && !mName.empty())
+ {
+ gcn::Font *const font = gui->getFont();
+ if (font)
+ {
+ graphics->setColor(userPalette->getColor(UserPalette::BEING));
+ font->drawString(graphics, mName, x, y);
+ }
+ }
+ }
+
+ if (mHighlightMonsterAttackRange && mType == ActorSprite::MONSTER
+ && isAlive())
+ {
+ int attackRange;
+ if (mAttackRange)
+ attackRange = 32 * mAttackRange;
+ else
+ attackRange = 32;
+
+ graphics->setColor(userPalette->getColorWithAlpha(
+ UserPalette::MONSTER_ATTACK_RANGE));
+
+ graphics->fillRectangle(gcn::Rectangle(
+ x - attackRange, y - attackRange,
+ 2 * attackRange + 32, 2 * attackRange + 32));
+ }
+
+ if (mShowMobHP && mInfo && player_node && player_node->getTarget() == this
+ && mType == MONSTER)
+ {
+ // show hp bar here
+ int maxHP = mMaxHP;
+ if (!maxHP)
+ maxHP = mInfo->getMaxHP();
+
+ drawHpBar(graphics, maxHP, mHP, mDamageTaken,
+ UserPalette::MONSTER_HP, UserPalette::MONSTER_HP2,
+ x - 50 + 16 + mInfo->getHpBarOffsetX(),
+ y + 32 - 6 + mInfo->getHpBarOffsetY(),
+ 2 * 50, 4);
+ }
+ if (mShowOwnHP && mInfo && player_node == this && mAction != DEAD)
+ {
+ drawHpBar(graphics, PlayerInfo::getAttribute(PlayerInfo::MAX_HP),
+ PlayerInfo::getAttribute(PlayerInfo::HP), 0,
+ UserPalette::PLAYER_HP, UserPalette::PLAYER_HP2,
+ x - 50 + 16 + mInfo->getHpBarOffsetX(),
+ y + 32 - 6 + mInfo->getHpBarOffsetY(),
+ 2 * 50, 4);
+ }
+ return res;
+}
+
+void Being::drawHpBar(Graphics *const graphics, const int maxHP, const int hp,
+ const int damage, const int color1, const int color2,
+ const int x, const int y, const int width,
+ const int height) const
+{
+ if (maxHP <= 0 || !userPalette)
+ return;
+
+ float p;
+
+ if (hp)
+ {
+ p = static_cast<float>(maxHP) / static_cast<float>(hp);
+ }
+ else if (maxHP != damage)
+ {
+ p = static_cast<float>(maxHP)
+ / static_cast<float>(maxHP - damage);
+ }
+ else
+ {
+ p = 1;
+ }
+
+ if (p <= 0 || p > width)
+ return;
+
+ const int dx = static_cast<const int>(static_cast<float>(width) / p);
+
+ if (serverVersion < 1)
+ { // old servers
+ if ((!damage && (this != player_node || hp == maxHP))
+ || (!hp && maxHP == damage))
+ {
+ graphics->setColor(userPalette->getColorWithAlpha(color1));
+ graphics->fillRectangle(gcn::Rectangle(
+ x, y, dx, height));
+ return;
+ }
+ else if (width - dx <= 0)
+ {
+ graphics->setColor(userPalette->getColorWithAlpha(color2));
+ graphics->fillRectangle(gcn::Rectangle(
+ x, y, width, height));
+ return;
+ }
+ }
+ else
+ { // evol servers
+ if (hp == maxHP)
+ {
+ graphics->setColor(userPalette->getColorWithAlpha(color1));
+ graphics->fillRectangle(gcn::Rectangle(
+ x, y, dx, height));
+ return;
+ }
+ else if (width - dx <= 0)
+ {
+ graphics->setColor(userPalette->getColorWithAlpha(color2));
+ graphics->fillRectangle(gcn::Rectangle(
+ x, y, width, height));
+ return;
+ }
+ }
+
+ graphics->setColor(userPalette->getColorWithAlpha(color1));
+ graphics->fillRectangle(gcn::Rectangle(
+ x, y, dx, height));
+
+ graphics->setColor(userPalette->getColorWithAlpha(color2));
+ graphics->fillRectangle(gcn::Rectangle(
+ x + dx, y, width - dx, height));
+}
+
+void Being::setHP(const int hp)
+{
+ mHP = hp;
+ if (mMaxHP < mHP)
+ mMaxHP = mHP;
+ if (mType == MONSTER)
+ updatePercentHP();
+}
+
+void Being::setMaxHP(const int hp)
+{
+ mMaxHP = hp;
+ if (mMaxHP < mHP)
+ mMaxHP = mHP;
+}
+
+void Being::resetCounters()
+{
+ mMoveTime = 0;
+ mAttackTime = 0;
+ mTalkTime = 0;
+ mOtherTime = 0;
+ mTestTime = cur_time;
+}
+
+void Being::recalcSpritesOrder()
+{
+ if (!mEnableReorderSprites)
+ return;
+
+// logger->log("recalcSpritesOrder");
+ const unsigned sz = static_cast<unsigned>(size());
+ if (sz < 1)
+ return;
+
+ std::vector<int> slotRemap;
+ std::map<int, int> itemSlotRemap;
+
+ std::vector<int>::iterator it;
+ int oldHide[20];
+ int dir = mSpriteDirection;
+ if (dir < 0 || dir >= 9)
+ dir = 0;
+ // hack for allow different logic in dead player
+ if (mAction == DEAD)
+ dir = 9;
+
+ const unsigned int hairSlot = Net::getCharServerHandler()->hairSprite();
+
+ for (unsigned slot = 0; slot < sz; slot ++)
+ {
+ oldHide[slot] = mSpriteHide[slot];
+ mSpriteHide[slot] = 0;
+ }
+
+ const size_t spriteIdSize = mSpriteIDs.size();
+ for (unsigned slot = 0; slot < sz; slot ++)
+ {
+ slotRemap.push_back(slot);
+
+ if (spriteIdSize <= slot)
+ continue;
+
+ const int id = mSpriteIDs[slot];
+ if (!id)
+ continue;
+
+ const ItemInfo &info = ItemDB::get(id);
+
+ if (info.isRemoveSprites())
+ {
+ SpriteToItemMap *const spriteToItems
+ = info.getSpriteToItemReplaceMap(dir);
+
+ if (spriteToItems)
+ {
+ FOR_EACHP (SpriteToItemMapCIter, itr, spriteToItems)
+ {
+ const int remSprite = itr->first;
+ const std::map<int, int> &itemReplacer = itr->second;
+ if (remSprite >= 0)
+ { // slot known
+ if (itemReplacer.empty())
+ {
+ mSpriteHide[remSprite] = 1;
+ }
+ else
+ {
+ std::map<int, int>::const_iterator repIt
+ = itemReplacer.find(mSpriteIDs[remSprite]);
+ if (repIt == itemReplacer.end())
+ {
+ repIt = itemReplacer.find(0);
+ if (repIt->second == 0)
+ repIt = itemReplacer.end();
+ }
+ if (repIt != itemReplacer.end())
+ {
+ mSpriteHide[remSprite] = repIt->second;
+ if (repIt->second != 1)
+ {
+ if (static_cast<unsigned>(remSprite)
+ != hairSlot)
+ {
+ setSprite(remSprite, repIt->second,
+ mSpriteColors[remSprite],
+ 1, false, true);
+ }
+ else
+ {
+ setSprite(remSprite, repIt->second,
+ ItemDB::get(repIt->second)
+ .getDyeColorsString(mHairColor),
+ 1, false, true);
+ }
+ }
+ }
+ }
+ }
+ else
+ { // slot unknown. Search for real slot, this can be slow
+ FOR_EACH (IntMapCIter, repIt, itemReplacer)
+ {
+ for (unsigned slot2 = 0; slot2 < sz; slot2 ++)
+ {
+ if (mSpriteIDs[slot2] == repIt->first)
+ {
+ mSpriteHide[slot2] = repIt->second;
+ if (repIt->second != 1)
+ {
+ if (slot2 != hairSlot)
+ {
+ setSprite(slot2, repIt->second,
+ mSpriteColors[slot2],
+ 1, false, true);
+ }
+ else
+ {
+ setSprite(slot2, repIt->second,
+ ItemDB::get(repIt->second)
+ .getDyeColorsString(
+ mHairColor),
+ 1, false, true);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if (info.mDrawBefore[dir] > 0)
+ {
+ const int id2 = mSpriteIDs[info.mDrawBefore[dir]];
+ if (itemSlotRemap.find(id2) != itemSlotRemap.end())
+ {
+// logger->log("found duplicate (before)");
+ const ItemInfo &info2 = ItemDB::get(id2);
+ if (info.mDrawPriority[dir] < info2.mDrawPriority[dir])
+ {
+// logger->log("old more priority");
+ continue;
+ }
+ else
+ {
+// logger->log("new more priority");
+ itemSlotRemap.erase(id2);
+ }
+ }
+
+ itemSlotRemap[id] = -info.mDrawBefore[dir];
+ }
+ else if (info.mDrawAfter[dir] > 0)
+ {
+ const int id2 = mSpriteIDs[info.mDrawAfter[dir]];
+ if (itemSlotRemap.find(id2) != itemSlotRemap.end())
+ {
+ const ItemInfo &info2 = ItemDB::get(id2);
+ if (info.mDrawPriority[dir] < info2.mDrawPriority[dir])
+ {
+// logger->log("old more priority");
+ continue;
+ }
+ else
+ {
+// logger->log("new more priority");
+ itemSlotRemap.erase(id2);
+ }
+ }
+
+ itemSlotRemap[id] = info.mDrawAfter[dir];
+// logger->log("item slot->slot %d %d->%d", id, slot, itemSlotRemap[id]);
+ }
+ }
+// logger->log("preparation end");
+
+ int lastRemap = 0;
+ unsigned cnt = 0;
+
+ while (cnt < 15 && lastRemap >= 0)
+ {
+ lastRemap = -1;
+ cnt ++;
+// logger->log("iteration");
+
+ for (unsigned slot0 = 0; slot0 < sz; slot0 ++)
+ {
+ const int slot = searchSlotValue(slotRemap, slot0);
+ const int val = slotRemap.at(slot);
+ int id = 0;
+
+ if (static_cast<int>(spriteIdSize) > val)
+ id = mSpriteIDs[val];
+
+ int idx = -1;
+ int idx1 = -1;
+// logger->log("item %d, id=%d", slot, id);
+ int reorder = 0;
+ const std::map<int, int>::const_iterator
+ orderIt = itemSlotRemap.find(id);
+ if (orderIt != itemSlotRemap.end())
+ reorder = orderIt->second;
+
+ if (reorder < 0)
+ {
+// logger->log("move item %d before %d", slot, -reorder);
+ searchSlotValueItr(it, idx, slotRemap, -reorder);
+ if (it == slotRemap.end())
+ return;
+ searchSlotValueItr(it, idx1, slotRemap, val);
+ if (it == slotRemap.end())
+ return;
+ lastRemap = idx1;
+ if (idx1 + 1 != idx)
+ {
+ slotRemap.erase(it);
+ searchSlotValueItr(it, idx, slotRemap, -reorder);
+ slotRemap.insert(it, val);
+ }
+ }
+ else if (reorder > 0)
+ {
+// logger->log("move item %d after %d", slot, reorder);
+ searchSlotValueItr(it, idx, slotRemap, reorder);
+ searchSlotValueItr(it, idx1, slotRemap, val);
+ if (it == slotRemap.end())
+ return;
+ lastRemap = idx1;
+ if (idx1 != idx + 1)
+ {
+ slotRemap.erase(it);
+ searchSlotValueItr(it, idx, slotRemap, reorder);
+ if (it != slotRemap.end())
+ {
+ ++ it;
+ if (it != slotRemap.end())
+ slotRemap.insert(it, val);
+ else
+ slotRemap.push_back(val);
+ }
+ else
+ {
+ slotRemap.push_back(val);
+ }
+ }
+ }
+ }
+ }
+
+// logger->log("after remap");
+ for (unsigned slot = 0; slot < sz; slot ++)
+ {
+ mSpriteRemap[slot] = slotRemap[slot];
+ if (oldHide[slot] != 0 && oldHide[slot] != 1 && mSpriteHide[slot] == 0)
+ {
+ const int id = mSpriteIDs[slot];
+ if (!id)
+ continue;
+
+ setSprite(slot, id, mSpriteColors[slot], 1, false, true);
+ }
+// logger->log("slot %d = %d", slot, mSpriteRemap[slot]);
+ }
+}
+
+int Being::searchSlotValue(const std::vector<int> &slotRemap,
+ const int val) const
+{
+ const size_t sz = size();
+ for (size_t slot = 0; slot < sz; slot ++)
+ {
+ if (slotRemap[slot] == val)
+ return slot;
+ }
+ return getNumberOfLayers() - 1;
+}
+
+void Being::searchSlotValueItr(std::vector<int>::iterator &it, int &idx,
+ std::vector<int> &slotRemap,
+ const int val) const
+{
+// logger->log("searching %d", val);
+ it = slotRemap.begin();
+ const std::vector<int>::iterator it_end = slotRemap.end();
+ idx = 0;
+ while (it != it_end)
+ {
+// logger->log("testing %d", *it);
+ if (*it == val)
+ {
+// logger->log("found at %d", idx);
+ return;
+ }
+ ++ it;
+ idx ++;
+ }
+// logger->log("not found");
+ idx = -1;
+ return;
+}
+
+void Being::updateHit(const int amount)
+{
+ if (amount > 0)
+ {
+ if (!mMinHit || amount < mMinHit)
+ mMinHit = amount;
+ if (amount != mCriticalHit && (!mMaxHit || amount > mMaxHit))
+ mMaxHit = amount;
+ }
+}
+
+Equipment *Being::getEquipment()
+{
+ Equipment *const eq = new Equipment();
+ Equipment::Backend *const bk = new BeingEquipBackend(this);
+ eq->setBackend(bk);
+ return eq;
+}
+
+void Being::undressItemById(const int id)
+{
+ const size_t sz = mSpriteIDs.size();
+
+ for (size_t f = 0; f < sz; f ++)
+ {
+ if (id == mSpriteIDs[f])
+ {
+ setSprite(static_cast<unsigned int>(f), 0);
+ break;
+ }
+ }
+}
+
+void Being::clearCache()
+{
+ delete_all(beingInfoCache);
+ beingInfoCache.clear();
+}
+
+void Being::updateComment()
+{
+ if (mGotComment || mName.empty())
+ return;
+
+ mGotComment = true;
+ mComment = loadComment(mName, mType);
+}
+
+std::string Being::loadComment(const std::string &name, const int type)
+{
+ std::string str;
+ switch (type)
+ {
+ case PLAYER:
+ str = client->getUsersDirectory();
+ break;
+ case NPC:
+ str = client->getNpcsDirectory();
+ break;
+ default:
+ return "";
+ }
+
+ str.append(stringToHexPath(name)).append("/comment.txt");
+ logger->log("load from: %s", str.c_str());
+ StringVect lines;
+
+ const ResourceManager *const resman = ResourceManager::getInstance();
+ if (resman->existsLocal(str))
+ {
+ lines = resman->loadTextFileLocal(str);
+ if (lines.size() >= 2)
+ return lines[1];
+ }
+ return "";
+}
+
+void Being::saveComment(const std::string &name,
+ const std::string &comment, const int type)
+{
+ std::string dir;
+ switch (type)
+ {
+ case PLAYER:
+ dir = client->getUsersDirectory();
+ break;
+ case NPC:
+ dir = client->getNpcsDirectory();
+ break;
+ default:
+ return;
+ }
+ dir.append(stringToHexPath(name));
+ const ResourceManager *const resman = ResourceManager::getInstance();
+ resman->saveTextFile(dir, "comment.txt",
+ (name + "\n").append(comment));
+}
+
+void Being::setState(const uint8_t state)
+{
+ const bool shop = ((state & FLAG_SHOP) != 0);
+ const bool away = ((state & FLAG_AWAY) != 0);
+ const bool inactive = ((state & FLAG_INACTIVE) != 0);
+ const bool needUpdate = (shop != mShop || away != mAway
+ || inactive != mInactive);
+
+ mShop = shop;
+ mAway = away;
+ mInactive = inactive;
+ updateAwayEffect();
+
+ if (needUpdate)
+ {
+ if (shop || away || inactive)
+ mAdvanced = true;
+ updateName();
+ addToCache();
+ }
+}
+
+void Being::setEmote(const uint8_t emotion, const int emote_time)
+{
+ if ((emotion & FLAG_SPECIAL) == FLAG_SPECIAL)
+ {
+ setState(emotion);
+ mAdvanced = true;
+ }
+ else
+ {
+ const int emotionIndex = emotion - 1;
+ if (emotionIndex >= 0 && emotionIndex <= EmoteDB::getLast())
+ {
+ delete mEmotionSprite;
+ mEmotionSprite = nullptr;
+ const EmoteInfo *const info = EmoteDB::get2(emotionIndex, true);
+ if (info)
+ {
+ const EmoteSprite *const sprite = info->sprites.front();
+ if (sprite)
+ {
+ mEmotionSprite = AnimatedSprite::clone(sprite->sprite);
+ if (mEmotionSprite)
+ mEmotionTime = info->time;
+ else
+ mEmotionTime = emote_time;
+ }
+ }
+ }
+
+ if (mEmotionSprite)
+ {
+ mEmotionSprite->play(mSpriteAction);
+ mEmotionSprite->setSpriteDirection(static_cast<SpriteDirection>(
+ mSpriteDirection));
+ }
+ else
+ {
+ mEmotionTime = 0;
+ }
+ }
+}
+
+void Being::updatePercentHP()
+{
+ if (!mMaxHP || !serverVersion)
+ return;
+ if (mHP)
+ {
+ const unsigned num = mHP * 100 / mMaxHP;
+ if (num != mNumber)
+ {
+ mNumber = num;
+ if (updateNumber(mNumber))
+ setAction(mAction);
+ }
+ }
+}
+
+uint8_t Being::genderToInt(const Gender sex)
+{
+ switch (sex)
+ {
+ case GENDER_FEMALE:
+ case GENDER_UNSPECIFIED:
+ default:
+ return 0;
+ case GENDER_MALE:
+ return 1;
+ case GENDER_OTHER:
+ return 3;
+ }
+}
+
+Gender Being::intToGender(const uint8_t sex)
+{
+ switch (sex)
+ {
+ case 0:
+ default:
+ return GENDER_FEMALE;
+ case 1:
+ return GENDER_MALE;
+ case 3:
+ return GENDER_OTHER;
+ }
+}
+
+int Being::getSpriteID(const int slot) const
+{
+ if (slot < 0 || static_cast<unsigned>(slot) >= mSpriteIDs.size())
+ return -1;
+
+ return mSpriteIDs[slot];
+}
+
+void Being::addAfkEffect()
+{
+ addSpecialEffect(mAwayEffect);
+}
+
+void Being::removeAfkEffect()
+{
+ removeSpecialEffect();
+}
+
+void Being::addSpecialEffect(const int effect)
+{
+ if (effectManager && Particle::enabled
+ && !mSpecialParticle && effect != -1)
+ {
+ mSpecialParticle = effectManager->triggerReturn(effect, this);
+ }
+}
+
+void Being::removeSpecialEffect()
+{
+ if (effectManager && mSpecialParticle)
+ {
+ mChildParticleEffects.removeLocally(mSpecialParticle);
+ mSpecialParticle = nullptr;
+ }
+ delete mAnimationEffect;
+ mAnimationEffect = nullptr;
+}
+
+void Being::updateAwayEffect()
+{
+ if (mAway)
+ addAfkEffect();
+ else
+ removeAfkEffect();
+}
+
+void Being::addEffect(const std::string &name)
+{
+ delete mAnimationEffect;
+ mAnimationEffect = AnimatedSprite::load(
+ paths.getStringValue("sprites") + name);
+}
+
+void Being::addPet(const int id)
+{
+ if (!actorSpriteManager)
+ return;
+
+ removePet();
+ Being *const being = actorSpriteManager->createBeing(
+ id, ActorSprite::PET, 0);
+ if (being)
+ {
+ being->setTileCoords(getTileX(), getTileY());
+ being->setOwner(this);
+ mPetId = id;
+ mPet = being;
+ }
+}
+
+void Being::removePet()
+{
+ if (!actorSpriteManager)
+ return;
+
+ mPetId = 0;
+ if (mPet)
+ {
+ mPet->setOwner(nullptr);
+ actorSpriteManager->destroy(mPet);
+ mPet = nullptr;
+ }
+}
+
+void Being::updatePets()
+{
+ removePet();
+ FOR_EACH (std::vector<int>::const_iterator, it, mSpriteIDs)
+ {
+ const int id = *it;
+ if (!id)
+ continue;
+ const ItemInfo &info = ItemDB::get(id);
+ const int pet = info.getPet();
+ if (pet)
+ {
+ addPet(pet);
+ return;
+ }
+ }
+}
+
+void Being::playSfx(const SoundInfo &sound, Being *const being,
+ const bool main, const int x, const int y)
+{
+ if (being)
+ {
+ // here need add timer and delay sound
+ const int time = tick_time;
+ if (main)
+ {
+ being->mNextSound.sound = nullptr;
+ being->mNextSound.time = time + sound.delay;
+ soundManager.playSfx(sound.sound, x, y);
+ }
+ else if (mNextSound.time <= time)
+ { // old event sound time is gone. we can play new sound
+ being->mNextSound.sound = nullptr;
+ being->mNextSound.time = time + sound.delay;
+ soundManager.playSfx(sound.sound, x, y);
+ }
+ else
+ { // old event sound in progress. need save sound and wait
+ being->mNextSound.sound = &sound;
+ being->mNextSound.x = x;
+ being->mNextSound.y = y;
+ }
+ }
+ else
+ {
+ soundManager.playSfx(sound.sound, x, y);
+ }
+}
+
+void Being::setLook(const int look)
+{
+ if (mType == PLAYER)
+ setSubtype(mSubType, look);
+}
diff --git a/src/being/being.h b/src/being/being.h
new file mode 100644
index 000000000..29b735956
--- /dev/null
+++ b/src/being/being.h
@@ -0,0 +1,1063 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ * Copyright (C) 2011-2013 The ManaPlus Developers
+ *
+ * This file is part of The ManaPlus Client.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef BEING_BEING_H
+#define BEING_BEING_H
+
+#include "equipment.h"
+
+#include "resources/beinginfo.h"
+
+#include <guichan/color.hpp>
+
+#include <SDL_types.h>
+
+#include <map>
+#include <set>
+
+#include "localconsts.h"
+
+static const unsigned int FIRST_IGNORE_EMOTE = 14;
+static const unsigned int STATUS_EFFECTS = 32;
+
+static const unsigned int SPEECH_TIME = 500;
+static const unsigned int SPEECH_MIN_TIME = 200;
+static const unsigned int SPEECH_MAX_TIME = 800;
+
+static const int DEFAULT_BEING_WIDTH = 32;
+static const int DEFAULT_BEING_HEIGHT = 32;
+
+class AnimatedSprite;
+class BeingCacheEntry;
+class Being;
+class FlashText;
+class Guild;
+class Inventory;
+class ItemInfo;
+class Item;
+class Particle;
+class Party;
+class SpeechBubble;
+class Text;
+
+struct Position;
+
+extern volatile int cur_time;
+
+enum Gender
+{
+ GENDER_MALE = 0,
+ GENDER_FEMALE = 1,
+ GENDER_UNSPECIFIED = 2,
+ GENDER_OTHER = 3
+};
+
+
+struct NextSoundInfo
+{
+ NextSoundInfo() :
+ sound(nullptr),
+ x(0),
+ y(0),
+ time(0)
+ {
+ }
+
+ const SoundInfo *sound;
+ int x;
+ int y;
+ int time;
+};
+
+class Being : public ActorSprite, public ConfigListener
+{
+ public:
+ friend class BeingEquipBackend;
+ friend class LocalPlayer;
+
+ enum FLAGS
+ {
+ FLAG_SHOP = 1,
+ FLAG_AWAY = 2,
+ FLAG_INACTIVE = 4,
+ FLAG_GENDER_OTHER = 32,
+ FLAG_GM = 64,
+ FLAG_GENDER_MALE = 128,
+ FLAG_SPECIAL = 128 + 64
+ };
+
+ /**
+ * Action the being is currently performing
+ * WARNING: Has to be in sync with the same enum in the Being class
+ * of the server!
+ */
+ enum Action
+ {
+ STAND = 0,
+ MOVE,
+ ATTACK,
+ SIT,
+ DEAD,
+ HURT,
+ SPAWN
+ };
+
+ enum Speech
+ {
+ NO_SPEECH = 0,
+ TEXT_OVERHEAD,
+ NO_NAME_IN_BUBBLE,
+ NAME_IN_BUBBLE
+ };
+
+ enum AttackType
+ {
+ HIT = 0x00,
+ CRITICAL = 0x0a,
+ MULTI = 0x08,
+ REFLECT = 0x04,
+ FLEE = 0x0b,
+ SKILL = 0xff,
+ MISS = 0xffff // pseudo value for miss attacks
+ };
+
+ enum Reachable
+ {
+ REACH_UNKNOWN = 0,
+ REACH_YES = 1,
+ REACH_NO = 2
+ };
+
+ /**
+ * Directions, to be used as bitmask values
+ */
+ enum BeingDirection
+ {
+ DOWN = 1,
+ LEFT = 2,
+ UP = 4,
+ RIGHT = 8
+ };
+
+ /**
+ * Constructor.
+ *
+ * @param id a unique being id
+ * @param subtype partly determines the type of the being
+ * @param map the map the being is on
+ */
+ Being(const int id, const Type type, const uint16_t subtype,
+ Map *const map);
+
+ A_DELETE_COPY(Being)
+
+ virtual ~Being();
+
+ Type getType() const A_WARN_UNUSED
+ { return mType; }
+
+ /**
+ * Removes all path nodes from this being.
+ */
+ void clearPath();
+
+ /**
+ * Returns the time spent in the current action.
+ */
+ int getActionTime() const A_WARN_UNUSED
+ { return mActionTime; }
+
+ /**
+ * Set the current action time.
+ * @see Ea::BeingHandler that set it to tick time.
+ */
+ void setActionTime(const int actionTime)
+ { mActionTime = actionTime; }
+
+ /**
+ * Makes this being take the next tile of its path.
+ * TODO: Used by eAthena only?
+ */
+ virtual void nextTile();
+
+ /**
+ * Get the current X pixel offset.
+ * TODO: Used by eAthena only?
+ */
+ int getXOffset() const A_WARN_UNUSED
+ { return getOffset(LEFT, RIGHT); }
+
+ /**
+ * Get the current Y pixel offset.
+ * TODO: Used by eAthena only?
+ */
+ int getYOffset() const A_WARN_UNUSED
+ { return getOffset(UP, DOWN); }
+
+ /**
+ * Creates a path for the being from current position to ex and ey
+ */
+ void setDestination(const int dstX, const int dstY);
+
+ /**
+ * Returns the destination for this being.
+ */
+ const Vector &getDestination() const A_WARN_UNUSED
+ { return mDest; }
+
+ /**
+ * Returns the tile x coord
+ */
+ int getTileX() const A_WARN_UNUSED
+ { return mX; }
+
+ /**
+ * Returns the tile y coord
+ */
+ int getTileY() const A_WARN_UNUSED
+ { return mY; }
+
+ /**
+ * Sets the tile x and y coord
+ */
+ void setTileCoords(const int x, const int y)
+ { mX = x; mY = y; }
+
+ /**
+ * Puts a "speech balloon" above this being for the specified amount
+ * of time.
+ *
+ * @param text The text that should appear.
+ * @param time The amount of time the text should stay in milliseconds.
+ */
+ void setSpeech(const std::string &text,
+ const std::string &channel = "",
+ int time = 0);
+
+ /**
+ * Puts a damage bubble above this being.
+ *
+ * @param attacker the attacking being
+ * @param damage the amount of damage recieved (0 means miss)
+ * @param type the attack type
+ * @param id skill id
+ */
+ void takeDamage(Being *const attacker, const int damage,
+ const AttackType type, const int attackId = 1);
+
+ /**
+ * Handles an attack of another being by this being.
+ *
+ * @param victim the victim being
+ * @param damage the amount of damage dealt (0 means miss)
+ * @param type the attack type
+ */
+ void handleAttack(Being *const victim, const int damage,
+ const int attackId = 1);
+
+ virtual void handleSkill(Being *const victim, const int damage,
+ const int skillId, const int skillLevel);
+
+ const ItemInfo *getEquippedWeapon() const A_WARN_UNUSED
+ { return mEquippedWeapon; }
+
+ /**
+ * Returns the name of the being.
+ */
+ const std::string &getName() const A_WARN_UNUSED
+ { return mName; }
+
+ /**
+ * Sets the name for the being.
+ *
+ * @param name The name that should appear.
+ */
+ void setName(const std::string &name);
+
+ bool getShowName() const A_WARN_UNUSED
+ { return mShowName; }
+
+ void setShowName(const bool doShowName);
+
+ /**
+ * Sets the name of the party the being is in. Shown in BeingPopup.
+ */
+ void setPartyName(const std::string &name)
+ { mPartyName = name; }
+
+ const std::string &getPartyName() const A_WARN_UNUSED
+ { return mPartyName; }
+
+ const std::string &getGuildName() const A_WARN_UNUSED
+ { return mGuildName; }
+
+ /**
+ * Sets the name of the primary guild the being is in. Shown in
+ * BeingPopup (eventually).
+ */
+ void setGuildName(const std::string &name);
+
+ void setGuildPos(const std::string &pos);
+
+ /**
+ * Adds a guild to the being.
+ */
+ void addGuild(Guild *const guild);
+
+ /**
+ * Removers a guild from the being.
+ */
+ void removeGuild(const int id);
+
+ /**
+ * Returns a pointer to the specified guild that the being is in.
+ */
+ Guild *getGuild(const std::string &guildName) const A_WARN_UNUSED;
+
+ /**
+ * Returns a pointer to the specified guild that the being is in.
+ */
+ Guild *getGuild(const int id) const A_WARN_UNUSED;
+
+ /**
+ * Returns a pointer to the specified guild that the being is in.
+ */
+ Guild *getGuild() const A_WARN_UNUSED;
+
+ /**
+ * Returns all guilds the being is in.
+ */
+ const std::map<int, Guild*> &getGuilds() const A_WARN_UNUSED
+ { return mGuilds; }
+
+ /**
+ * Removes all guilds the being is in.
+ */
+ void clearGuilds();
+
+ /**
+ * Get number of guilds the being belongs to.
+ */
+ int16_t getNumberOfGuilds() const A_WARN_UNUSED
+ { return static_cast<int16_t>(mGuilds.size()); }
+
+ bool isInParty() const A_WARN_UNUSED
+ { return mParty; }
+
+ void setParty(Party *const party);
+
+ void setGuild(Guild *const guild);
+
+ void updateGuild();
+
+ Party *getParty() const A_WARN_UNUSED
+ { return mParty; }
+
+ int getSpritesCount() const A_WARN_UNUSED
+ { return static_cast<int>(size()); }
+
+ /**
+ * Sets visible equipments for this being.
+ */
+ void setSprite(const unsigned int slot, const int id,
+ std::string color = "",
+ const unsigned char colorId = 1,
+ const bool isWeapon = false,
+ const bool isTempSprite = false);
+
+ void setSpriteID(const unsigned int slot, const int id);
+
+ void setSpriteColor(const unsigned int slot,
+ const std::string &color = "");
+
+ /**
+ * Get the number of hairstyles implemented
+ */
+ static int getNumOfHairstyles() A_WARN_UNUSED
+ { return mNumberOfHairstyles; }
+
+ /**
+ * Get the number of races implemented
+ */
+ static int getNumOfRaces() A_WARN_UNUSED
+ { return mNumberOfRaces; }
+
+ /**
+ * Get the number of layers used to draw the being
+ */
+ int getNumberOfLayers() const A_WARN_UNUSED
+ { return CompoundSprite::getNumberOfLayers(); }
+
+ /**
+ * Performs being logic.
+ */
+ virtual void logic() override;
+
+ /**
+ * Draws the speech text above the being.
+ */
+ void drawSpeech(const int offsetX, const int offsetY);
+
+ /**
+ * Draws the emotion picture above the being.
+ */
+ void drawEmotion(Graphics *const graphics, const int offsetX,
+ const int offsetY);
+
+ uint16_t getSubType() const
+ { return mSubType; }
+
+ /**
+ * Set Being's subtype (mostly for view for monsters and NPCs)
+ */
+ void setSubtype(const uint16_t subtype, const uint8_t look);
+
+ const BeingInfo *getInfo() const A_WARN_UNUSED
+ { return mInfo; }
+
+ TargetCursorSize getTargetCursorSize() const A_WARN_UNUSED;
+
+ int getTargetOffsetX() const A_WARN_UNUSED
+ {
+ if (!mInfo)
+ return 0;
+ return mInfo->getTargetOffsetX();
+ }
+
+ int getTargetOffsetY() const A_WARN_UNUSED
+ {
+ if (!mInfo)
+ return 0;
+ return mInfo->getTargetOffsetY();
+ }
+
+ /**
+ * Gets the way the object is blocked by other objects.
+ */
+ virtual unsigned char getWalkMask() const A_WARN_UNUSED
+ {
+ if (!mInfo)
+ return 0;
+ return mInfo->getWalkMask();
+ }
+
+ /**
+ * Gets the way the monster blocks pathfinding for other objects
+ */
+ Map::BlockType getBlockType() const A_WARN_UNUSED
+ {
+ if (!mInfo)
+ return Map::BLOCKTYPE_NONE;
+ return mInfo->getBlockType();
+ }
+
+ /**
+ * Sets the walk speed.
+ * in pixels per second for eAthena,
+ * in tiles per second for Manaserv.
+ */
+ void setWalkSpeed(const Vector &speed)
+ { mWalkSpeed = speed; }
+
+ /**
+ * Gets the walk speed.
+ * in pixels per second for eAthena,
+ * in tiles per second for Manaserv (0.1 precision).
+ */
+ Vector getWalkSpeed() const A_WARN_UNUSED
+ { return mWalkSpeed; }
+
+ /**
+ * Sets the attack speed.
+ * @todo In what unit?
+ */
+ void setAttackSpeed(const int speed)
+ { mAttackSpeed = speed; }
+
+ /**
+ * Gets the attack speed.
+ * @todo In what unit?
+ */
+ int getAttackSpeed() const A_WARN_UNUSED
+ { return mAttackSpeed; }
+
+ /**
+ * Sets the current action.
+ */
+ virtual void setAction(const Action &action, const int attackType = 0);
+
+ /**
+ * Get the being's action currently performed.
+ */
+ Action getCurrentAction() const A_WARN_UNUSED
+ { return mAction; }
+
+ /**
+ * Returns whether this being is still alive.
+ */
+ bool isAlive() const A_WARN_UNUSED
+ { return mAction != DEAD; }
+
+ /**
+ * Returns the current direction.
+ */
+ uint8_t getDirection() const A_WARN_UNUSED
+ { return mDirection; }
+
+ /**
+ * Sets the current direction.
+ */
+ virtual void setDirection(const uint8_t direction);
+
+ virtual void setDirectionDelayed(const uint8_t direction)
+ { mDirectionDelayed = direction; }
+
+ uint8_t getDirectionDelayed() const A_WARN_UNUSED
+ { return mDirectionDelayed; }
+
+ /**
+ * Returns the direction the being is facing.
+ */
+ SpriteDirection getSpriteDirection() const A_WARN_UNUSED
+ { return static_cast<SpriteDirection>(mSpriteDirection); }
+
+ void setPosition(const Vector &pos);
+
+ /**
+ * Overloaded method provided for convenience.
+ *
+ * @see setPosition(const Vector &pos)
+ */
+ inline void setPosition(const float x, const float y,
+ const float z = 0.0f)
+ { setPosition(Vector(x, y, z)); }
+
+ /**
+ * Returns the horizontal size of the current base sprite of the being.
+ */
+ virtual int getWidth() const override A_WARN_UNUSED
+ { return std::max(CompoundSprite::getWidth(), DEFAULT_BEING_WIDTH); }
+
+ /**
+ * Returns the vertical size of the current base sprite of the being.
+ */
+ virtual int getHeight() const override A_WARN_UNUSED
+ { return std::max(CompoundSprite::getHeight(), DEFAULT_BEING_HEIGHT); }
+
+ /**
+ * Returns the being's pixel radius used to detect collisions.
+ */
+ virtual int getCollisionRadius() const A_WARN_UNUSED
+ { return 16; }
+
+ /**
+ * Shoots a missile particle from this being, to target being
+ */
+ void fireMissile(Being *const target,
+ const std::string &particle) const;
+
+ /**
+ * Returns the path this being is following. An empty path is returned
+ * when this being isn't following any path currently.
+ */
+ const Path &getPath() const A_WARN_UNUSED
+ { return mPath; }
+
+ int getDistance() const A_WARN_UNUSED
+ { return mDistance; }
+
+ void setDistance(const int n)
+ { mDistance = n; }
+
+ /**
+ * Set the Emoticon type and time displayed above
+ * the being.
+ */
+ void setEmote(const uint8_t emotion, const int emote_time);
+
+ void setState(const uint8_t state);
+
+ virtual void drawSprites(Graphics *const graphics,
+ int posX, int posY) const override;
+
+ virtual void drawSpritesSDL(Graphics *const graphics,
+ int posX, int posY) const override;
+
+ void drawHpBar(Graphics *const graphics, const int x, const int y,
+ const int maxHP, const int hp, const int damage,
+ const int color1, const int color2, const int width,
+ const int height) const;
+
+ static void load();
+
+ virtual void optionChanged(const std::string &value) override;
+
+ void flashName(const int time);
+
+ int getDamageTaken() const A_WARN_UNUSED
+ { return mDamageTaken; }
+
+ void setDamageTaken(const int damage)
+ { mDamageTaken = damage; }
+
+ void updateName();
+
+ void setLevel(const int n)
+ { mLevel = n; }
+
+ virtual int getLevel() const A_WARN_UNUSED
+ { return mLevel; }
+
+ void setIsReachable(const int n)
+ { mIsReachable = n; }
+
+ int isReachable() const A_WARN_UNUSED
+ { return mIsReachable; }
+
+ static void reReadConfig();
+
+ static BeingCacheEntry* getCacheEntry(const int id) A_WARN_UNUSED;
+
+ void addToCache() const;
+
+ bool updateFromCache();
+
+ /**
+ * Sets the gender of this being.
+ */
+ virtual void setGender(const Gender gender);
+
+ Gender getGender() const A_WARN_UNUSED
+ { return mGender; }
+
+ /**
+ * Return sprite sit action for current environment.
+ */
+ std::string getSitAction() const A_WARN_UNUSED;
+
+ std::string getMoveAction() const A_WARN_UNUSED;
+
+ std::string getDeadAction() const A_WARN_UNUSED;
+
+ std::string getStandAction() const A_WARN_UNUSED;
+
+ std::string getSpawnAction() const A_WARN_UNUSED;
+
+ std::string getWeaponAttackAction(const ItemInfo *const weapon) const;
+
+ std::string getAttackAction(const Attack *const attack) const;
+
+ /**
+ * Whether or not this player is a GM.
+ */
+ bool isGM() const A_WARN_UNUSED
+ { return mIsGM; }
+
+ /**
+ * Triggers whether or not to show the name as a GM name.
+ */
+ void setGM(const bool gm);
+
+ bool canTalk() const A_WARN_UNUSED
+ { return mType == NPC; }
+
+ void talkTo() const;
+
+ bool draw(Graphics *const graphics,
+ const int offsetX, const int offsetY) const override;
+
+ bool drawSpriteAt(Graphics *const graphics,
+ const int x, const int y) const;
+
+ void setMoveTime()
+ { mMoveTime = cur_time; }
+
+ void setAttackTime()
+ { mAttackTime = cur_time; }
+
+ void setTalkTime()
+ { mTalkTime = cur_time; }
+
+ void setTestTime()
+ { mTestTime = cur_time; }
+
+ void setOtherTime()
+ { mOtherTime = cur_time; }
+
+ unsigned int getMoveTime() const
+ { return mMoveTime; }
+
+ unsigned int getAttackTime() const
+ { return mAttackTime; }
+
+ unsigned int getTalkTime() const
+ { return mTalkTime; }
+
+ unsigned int getTestTime() const
+ { return mTestTime; }
+
+ unsigned int getOtherTime() const
+ { return mOtherTime; }
+
+ void resetCounters();
+
+ virtual void updateColors();
+
+ void setEnemy(const bool n)
+ { mEnemy = n; }
+
+ const std::string &getIp() const A_WARN_UNUSED
+ { return mIp; }
+
+ void setIp(std::string ip)
+ { mIp = ip; }
+
+ unsigned int getPvpRank() const A_WARN_UNUSED
+ { return mPvpRank; }
+
+ void setPvpRank(const unsigned int rank)
+ { mPvpRank = rank; }
+
+ void setHP(const int n);
+
+ void setMaxHP(const int hp);
+
+ int getHP() const A_WARN_UNUSED
+ { return mHP; }
+
+ uint8_t calcDirection(const int dstX,
+ const int dstY) const A_WARN_UNUSED;
+
+ uint8_t calcDirection() const A_WARN_UNUSED;
+
+ void setAttackDelay(const int n)
+ { mAttackDelay = n; }
+
+ int getAttackDelay() const A_WARN_UNUSED
+ { return mAttackDelay; }
+
+ int getMinHit() const A_WARN_UNUSED
+ { return mMinHit; }
+
+ void setMinHit(const int n)
+ { mMinHit = n; }
+
+ int getMaxHit() const A_WARN_UNUSED
+ { return mMaxHit; }
+
+ void setMaxHit(const int n)
+ { mMaxHit = n; }
+
+ int getCriticalHit() const A_WARN_UNUSED
+ { return mCriticalHit; }
+
+ void setCriticalHit(const int n)
+ { mCriticalHit = n; }
+
+ void updateHit(const int amount);
+
+ Equipment *getEquipment() A_WARN_UNUSED;
+
+ void undressItemById(const int id);
+
+ int getGoodStatus() const A_WARN_UNUSED
+ { return mGoodStatus; }
+
+ void setGoodStatus(const int n)
+ { mGoodStatus = n; }
+
+ std::string getGenderSign() const A_WARN_UNUSED;
+
+ std::string getGenderSignWithSpace() const A_WARN_UNUSED;
+
+ void updateComment();
+
+ const std::string getComment() const A_WARN_UNUSED
+ { return mComment; }
+
+ void setComment(std::string n)
+ { mComment = n; }
+
+ static void clearCache();
+
+ static std::string loadComment(const std::string &name,
+ const int type) A_WARN_UNUSED;
+
+ static void saveComment(const std::string &name,
+ const std::string &comment, const int type);
+
+ bool isAdvanced() const A_WARN_UNUSED
+ { return mAdvanced; }
+
+ void setAdvanced(const bool n)
+ { mAdvanced = n; addToCache(); }
+
+ bool isShopEnabled() const A_WARN_UNUSED
+ { return mShop; }
+
+ void enableShop(const bool b)
+ { mShop = b; }
+
+ /**
+ * Sets the attack range.
+ */
+ void setAttackRange(const int range)
+ { mAttackRange = range; }
+
+ void attack(Being *target = nullptr, bool keep = false,
+ bool dontChangeEquipment = false);
+
+ void attack2(Being *target = nullptr, bool keep = false,
+ bool dontChangeEquipment = false);
+
+ void updatePercentHP();
+
+ void setRaceName(std::string name)
+ { mRaceName = name; }
+
+ std::string getRaceName() const A_WARN_UNUSED
+ { return mRaceName; }
+
+ int getSpriteID(const int slot) const A_WARN_UNUSED;
+
+ void setHairStyle(const unsigned int slot, const int id);
+
+ void setHairColor(const unsigned int slot,
+ const unsigned char color);
+
+ void setHairColor(const unsigned char color)
+ { mHairColor = color; }
+
+ unsigned char getHairColor() const A_WARN_UNUSED
+ { return mHairColor; }
+
+ void recalcSpritesOrder();
+
+ int getHitEffect(const Being *const attacker,
+ const AttackType type,
+ const int attackId) const A_WARN_UNUSED;
+
+ Cursor::Cursor getHoverCursor() const A_WARN_UNUSED
+ { return mInfo ? mInfo->getHoverCursor() : Cursor::CURSOR_POINTER; }
+
+ void addAfkEffect();
+
+ void removeAfkEffect();
+
+ void updateAwayEffect();
+
+ void addSpecialEffect(const int effect);
+
+ void removeSpecialEffect();
+
+ void addEffect(const std::string &name);
+
+ void addPet(const int id);
+
+ void removePet();
+
+ void updatePets();
+
+ Being *getPet()
+ { return mPet; }
+
+ void setPet(Being *const pet)
+ { mPet = pet; }
+
+ void setOwner(Being *const owner)
+ { mOwner = owner; }
+
+ void playSfx(const SoundInfo &sound, Being *const being,
+ const bool main, const int x, const int y);
+
+ int getLook()
+ { return mLook; }
+
+ void setLook(const int look);
+
+ static uint8_t genderToInt(const Gender sex) A_WARN_UNUSED;
+
+ static Gender intToGender(uint8_t sex) A_WARN_UNUSED;
+
+ NextSoundInfo mNextSound;
+
+ protected:
+ /**
+ * Sets the new path for this being.
+ */
+ void setPath(const Path &path);
+
+ /**
+ * Updates name's location.
+ */
+ virtual void updateCoords();
+
+ void showName();
+
+ static int getDefaultEffectId(const int type);
+
+ BeingInfo *mInfo;
+ AnimatedSprite *mEmotionSprite;
+ AnimatedSprite* mAnimationEffect;
+
+ std::string mSpriteAction;
+ std::string mName; /**< Name of character */
+ std::string mRaceName;
+ std::string mPartyName;
+ std::string mGuildName;
+ std::string mSpeech;
+
+ /**
+ * Holds a text object when the being displays it's name, 0 otherwise
+ */
+ FlashText *mDispName;
+ const gcn::Color *mNameColor;
+
+ /** Engine-related infos about weapon. */
+ const ItemInfo *mEquippedWeapon;
+
+ static int mNumberOfHairstyles; /** Number of hair styles in use */
+ static int mNumberOfRaces; /** Number of races in use */
+
+ Path mPath;
+ Text *mText;
+ const gcn::Color *mTextColor;
+
+ Vector mDest; /**< destination coordinates. */
+
+ StringVect mSpriteColors;
+ std::vector<int> mSpriteIDs;
+ std::vector<int> mSpriteColorsIds;
+
+ // Character guild information
+ std::map<int, Guild*> mGuilds;
+ Party *mParty;
+
+ int mActionTime; /**< Time spent in current action */
+ int mEmotionTime; /**< Time until emotion disappears */
+
+ /** Time until the last speech sentence disappears */
+ int mSpeechTime;
+ int mAttackSpeed; /**< Attack speed */
+
+ int mLevel;
+ int mAttackRange;
+ Gender mGender;
+ Action mAction; /**< Action the being is performing */
+ uint16_t mSubType; /**< Subtype (graphical view, basically) */
+ uint8_t mDirection; /**< Facing direction */
+ uint8_t mDirectionDelayed; /**< Facing direction */
+ uint8_t mSpriteDirection; /**< Facing direction */
+ bool mShowName;
+ bool mIsGM;
+
+ private:
+ /**
+ * Calculates the offset in the given directions.
+ * If walking in direction 'neg' the value is negated.
+ * TODO: Used by eAthena only?
+ */
+ int getOffset(const signed char pos,
+ const signed char neg) const A_WARN_UNUSED;
+
+ int searchSlotValue(const std::vector<int> &slotRemap,
+ const int val) const A_WARN_UNUSED;
+
+ void searchSlotValueItr(std::vector<int>::iterator &it, int &idx,
+ std::vector<int> &slotRemap,
+ const int val) const;
+
+ void dumpSprites() const;
+
+ const Type mType;
+
+ /** Speech Bubble components */
+ SpeechBubble *mSpeechBubble;
+
+ /**
+ * Walk speed for x and y movement values.
+ * In pixels per second for eAthena,
+ * In pixels per ticks for Manaserv.
+ * @see MILLISECONDS_IN_A_TICK
+ */
+ Vector mWalkSpeed;
+ std::string mIp;
+ int *mSpriteRemap;
+ int *mSpriteHide;
+ std::string mComment;
+ Being *mPet;
+ Being *mOwner;
+ Particle *mSpecialParticle;
+
+ int mX, mY; /**< Position in tile */
+
+ int mDamageTaken;
+ int mHP;
+ int mMaxHP;
+ int mDistance;
+ int mIsReachable; /**< 0 - unknown, 1 - reachable, 2 - not reachable*/
+ int mGoodStatus;
+
+ static int mUpdateConfigTime;
+ static unsigned int mConfLineLim;
+ static int mSpeechType;
+ static bool mHighlightMapPortals;
+ static bool mHighlightMonsterAttackRange;
+ static bool mLowTraffic;
+ static bool mDrawHotKeys;
+ static bool mShowBattleEvents;
+ static bool mShowMobHP;
+ static bool mShowOwnHP;
+ static bool mShowGender;
+ static bool mShowLevel;
+ static bool mShowPlayersStatus;
+ static bool mEnableReorderSprites;
+ static bool mHideErased;
+ static bool mMoveNames;
+ static int mAwayEffect;
+
+ unsigned int mMoveTime;
+ unsigned int mAttackTime;
+ unsigned int mTalkTime;
+ unsigned int mOtherTime;
+ unsigned int mTestTime;
+ int mAttackDelay;
+ int mMinHit;
+ int mMaxHit;
+ int mCriticalHit;
+ unsigned int mPvpRank;
+ unsigned int mNumber;
+ int mPetId;
+ int mLook;
+ unsigned char mHairColor;
+ bool mErased;
+ bool mEnemy;
+ bool mGotComment;
+ bool mAdvanced;
+ bool mShop;
+ bool mAway;
+ bool mInactive;
+};
+
+extern std::list<BeingCacheEntry*> beingInfoCache;
+
+#endif // BEING_BEING_H
diff --git a/src/being/beingcacheentry.h b/src/being/beingcacheentry.h
new file mode 100644
index 000000000..2890d44a6
--- /dev/null
+++ b/src/being/beingcacheentry.h
@@ -0,0 +1,128 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2011-2013 The ManaPlus Developers
+ *
+ * This file is part of The ManaPlus Client.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef BEING_BEINGCACHEENTRY_H
+#define BEING_BEINGCACHEENTRY_H
+
+#include "localconsts.h"
+
+#include <string>
+
+class BeingCacheEntry final
+{
+ public:
+ explicit BeingCacheEntry(const int id):
+ mName(),
+ mPartyName(),
+ mGuildName(),
+ mIp(),
+ mId(id),
+ mLevel(0),
+ mPvpRank(0),
+ mTime(0),
+ mFlags(0),
+ mIsAdvanced(false)
+ {
+ }
+
+ A_DELETE_COPY(BeingCacheEntry)
+
+ int getId() const
+ { return mId; }
+
+ /**
+ * Returns the name of the being.
+ */
+ const std::string &getName() const
+ { return mName; }
+
+ /**
+ * Sets the name for the being.
+ *
+ * @param name The name that should appear.
+ */
+ void setName(const std::string &name)
+ { mName = name; }
+
+ /**
+ * Following are set from the server (mainly for players)
+ */
+ void setPartyName(const std::string &name)
+ { mPartyName = name; }
+
+ void setGuildName(const std::string &name)
+ { mGuildName = name; }
+
+ const std::string &getPartyName() const
+ { return mPartyName; }
+
+ const std::string &getGuildName() const
+ { return mGuildName; }
+
+ void setLevel(const int n)
+ { mLevel = n; }
+
+ int getLevel() const
+ { return mLevel; }
+
+ void setTime(const int n)
+ { mTime = n; }
+
+ int getTime() const
+ { return mTime; }
+
+ unsigned getPvpRank() const
+ { return mPvpRank; }
+
+ void setPvpRank(const int r)
+ { mPvpRank = r; }
+
+ std::string getIp() const
+ { return mIp; }
+
+ void setIp(std::string ip)
+ { mIp = ip; }
+
+ bool isAdvanced() const
+ { return mIsAdvanced; }
+
+ void setAdvanced(const bool a)
+ { mIsAdvanced = a; }
+
+ int getFlags() const
+ { return mFlags; }
+
+ void setFlags(const int flags)
+ { mFlags = flags; }
+
+ protected:
+ std::string mName; /**< Name of character */
+ std::string mPartyName;
+ std::string mGuildName;
+ std::string mIp;
+ int mId; /**< Unique sprite id */
+ int mLevel;
+ unsigned int mPvpRank;
+ int mTime;
+ int mFlags;
+ bool mIsAdvanced;
+};
+
+#endif // BEING_BEINGCACHEENTRY_H
diff --git a/src/being/compoundsprite.cpp b/src/being/compoundsprite.cpp
new file mode 100644
index 000000000..68a0d42e3
--- /dev/null
+++ b/src/being/compoundsprite.cpp
@@ -0,0 +1,567 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2010 The Mana Developers
+ * Copyright (C) 2011-2013 The ManaPlus Developers
+ *
+ * This file is part of The ManaPlus Client.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "being/compoundsprite.h"
+
+#include "client.h"
+#include "configuration.h"
+#include "game.h"
+
+#ifdef USE_OPENGL
+#include "main.h"
+#endif
+
+#include "map.h"
+#include "sdlshared.h"
+
+#include "render/surfacegraphics.h"
+
+#include "resources/image.h"
+#include "resources/imagehelper.h"
+
+#include "utils/dtor.h"
+#include "utils/sdlcheckutils.h"
+
+#include <SDL.h>
+
+#include "debug.h"
+
+static const int BUFFER_WIDTH = 100;
+static const int BUFFER_HEIGHT = 100;
+
+static const unsigned cache_max_size = 10;
+static const unsigned cache_clean_part = 3;
+bool CompoundSprite::mEnableDelay = true;
+
+CompoundSprite::CompoundSprite() :
+ imagesCache(),
+ mCacheItem(nullptr),
+ mImage(nullptr),
+ mAlphaImage(nullptr),
+ mOffsetX(0),
+ mOffsetY(0),
+ mSprites(),
+ mNextRedrawTime(0),
+ mNeedsRedraw(false),
+ mEnableAlphaFix(config.getBoolValue("enableAlphaFix")),
+ mDisableAdvBeingCaching(config.getBoolValue("disableAdvBeingCaching")),
+ mDisableBeingCaching(config.getBoolValue("disableBeingCaching"))
+{
+ mAlpha = 1.0f;
+}
+
+CompoundSprite::~CompoundSprite()
+{
+ clear();
+ mImage = nullptr;
+ mAlphaImage = nullptr;
+}
+
+bool CompoundSprite::reset()
+{
+ bool ret = false;
+ FOR_EACH (SpriteIterator, it, mSprites)
+ {
+ if (*it)
+ ret |= (*it)->reset();
+ }
+ mNeedsRedraw |= ret;
+ return ret;
+}
+
+bool CompoundSprite::play(const std::string &action)
+{
+ bool ret = false;
+ FOR_EACH (SpriteIterator, it, mSprites)
+ {
+ if (*it)
+ ret |= (*it)->play(action);
+ }
+ mNeedsRedraw |= ret;
+ return ret;
+}
+
+bool CompoundSprite::update(const int time)
+{
+ bool ret = false;
+ FOR_EACH (SpriteIterator, it, mSprites)
+ {
+ if (*it)
+ ret |= (*it)->update(time);
+ }
+ mNeedsRedraw |= ret;
+ return ret;
+}
+
+bool CompoundSprite::draw(Graphics *const graphics,
+ const int posX, const int posY) const
+{
+ FUNC_BLOCK("CompoundSprite::draw", 1)
+ if (mNeedsRedraw)
+ updateImages();
+
+ if (mSprites.empty()) // Nothing to draw
+ return false;
+
+ if (mAlpha == 1.0f && mImage)
+ {
+ return graphics->drawImage(mImage, posX + mOffsetX, posY + mOffsetY);
+ }
+ else if (mAlpha && mAlphaImage)
+ {
+ mAlphaImage->setAlpha(mAlpha);
+ return graphics->drawImage(mAlphaImage,
+ posX + mOffsetX, posY + mOffsetY);
+ }
+ else
+ {
+ drawSprites(graphics, posX, posY);
+ }
+ return false;
+}
+
+void CompoundSprite::drawSprites(Graphics *const graphics,
+ const int posX, const int posY) const
+{
+ FOR_EACH (SpriteConstIterator, it, mSprites)
+ {
+ if (*it)
+ {
+ (*it)->setAlpha(mAlpha);
+ (*it)->draw(graphics, posX, posY);
+ }
+ }
+}
+
+void CompoundSprite::drawSpritesSDL(Graphics *const graphics,
+ const int posX, const int posY) const
+{
+ FOR_EACH (SpriteConstIterator, it, mSprites)
+ {
+ if (*it)
+ (*it)->draw(graphics, posX, posY);
+ }
+}
+
+int CompoundSprite::getWidth() const
+{
+ FOR_EACH (SpriteConstIterator, it, mSprites)
+ {
+ const Sprite *const base = *it;
+ if (base)
+ return base->getWidth();
+ }
+
+ return 0;
+}
+
+int CompoundSprite::getHeight() const
+{
+ FOR_EACH (SpriteConstIterator, it, mSprites)
+ {
+ const Sprite *const base = *it;
+ if (base)
+ return base->getHeight();
+ }
+
+ return 0;
+}
+
+const Image *CompoundSprite::getImage() const
+{
+ return mImage;
+}
+
+bool CompoundSprite::setSpriteDirection(const SpriteDirection direction)
+{
+ bool ret = false;
+ FOR_EACH (SpriteIterator, it, mSprites)
+ {
+ if (*it)
+ ret |= (*it)->setSpriteDirection(direction);
+ }
+ mNeedsRedraw |= ret;
+ return ret;
+}
+
+int CompoundSprite::getNumberOfLayers() const
+{
+ if (mImage || mAlphaImage)
+ return 1;
+ else
+ return static_cast<int>(size());
+}
+
+unsigned int CompoundSprite::getCurrentFrame() const
+{
+ FOR_EACH (SpriteConstIterator, it, mSprites)
+ {
+ if (*it)
+ return (*it)->getCurrentFrame();
+ }
+ return 0;
+}
+
+unsigned int CompoundSprite::getFrameCount() const
+{
+ FOR_EACH (SpriteConstIterator, it, mSprites)
+ {
+ if (*it)
+ return (*it)->getFrameCount();
+ }
+ return 0;
+}
+
+void CompoundSprite::addSprite(Sprite *const sprite)
+{
+ mSprites.push_back(sprite);
+ mNeedsRedraw = true;
+}
+
+void CompoundSprite::setSprite(const int layer, Sprite *const sprite)
+{
+ // Skip if it won't change anything
+ if (mSprites.at(layer) == sprite)
+ return;
+
+ delete mSprites.at(layer);
+ mSprites[layer] = sprite;
+ mNeedsRedraw = true;
+}
+
+void CompoundSprite::removeSprite(const int layer)
+{
+ // Skip if it won't change anything
+ if (!mSprites.at(layer))
+ return;
+
+ delete mSprites.at(layer);
+ mSprites.at(layer) = nullptr;
+ mNeedsRedraw = true;
+}
+
+void CompoundSprite::clear()
+{
+ // Skip if it won't change anything
+ if (!mSprites.empty())
+ {
+ delete_all(mSprites);
+ mSprites.clear();
+ }
+ mNeedsRedraw = true;
+ delete_all(imagesCache);
+ imagesCache.clear();
+ delete mCacheItem;
+ mCacheItem = nullptr;
+}
+
+void CompoundSprite::ensureSize(size_t layerCount)
+{
+ // Skip if it won't change anything
+ if (mSprites.size() >= layerCount)
+ return;
+
+// resize(layerCount, nullptr);
+ mSprites.resize(layerCount);
+}
+
+/**
+ * Returns the curent frame in the current animation of the given layer.
+ */
+unsigned int CompoundSprite::getCurrentFrame(unsigned int layer) const
+{
+ if (layer >= mSprites.size())
+ return 0;
+
+ const Sprite *const s = getSprite(layer);
+ if (s)
+ return s->getCurrentFrame();
+
+ return 0;
+}
+
+/**
+ * Returns the frame count in the current animation of the given layer.
+ */
+unsigned int CompoundSprite::getFrameCount(unsigned int layer)
+{
+ if (layer >= mSprites.size())
+ return 0;
+
+ const Sprite *const s = getSprite(layer);
+ if (s)
+ return s->getFrameCount();
+
+ return 0;
+}
+
+void CompoundSprite::redraw() const
+{
+#ifndef USE_SDL2
+
+#if SDL_BYTEORDER == SDL_BIG_ENDIAN
+ const int rmask = 0xff000000;
+ const int gmask = 0x00ff0000;
+ const int bmask = 0x0000ff00;
+ const int amask = 0x000000ff;
+#else
+ const int rmask = 0x000000ff;
+ const int gmask = 0x0000ff00;
+ const int bmask = 0x00ff0000;
+ const int amask = 0xff000000;
+#endif
+
+ SDL_Surface *const surface = MSDL_CreateRGBSurface(SDL_HWSURFACE,
+ BUFFER_WIDTH, BUFFER_HEIGHT, 32, rmask, gmask, bmask, amask);
+
+ if (!surface)
+ return;
+
+ SurfaceGraphics *graphics = new SurfaceGraphics();
+ graphics->setBlitMode(SurfaceGraphics::BLIT_GFX);
+ graphics->setTarget(surface);
+ graphics->_beginDraw();
+
+ int tileX = 32 / 2;
+ int tileY = 32;
+
+ const Game *const game = Game::instance();
+ if (game)
+ {
+ const Map *const map = game->getCurrentMap();
+ if (map)
+ {
+ tileX = map->getTileWidth() / 2;
+ tileY = map->getTileWidth();
+ }
+ }
+
+ const int posX = BUFFER_WIDTH / 2 - tileX;
+ const int posY = BUFFER_HEIGHT - tileY;
+
+ mOffsetX = tileX - BUFFER_WIDTH / 2;
+ mOffsetY = tileY - BUFFER_HEIGHT;
+
+ drawSpritesSDL(graphics, posX, posY);
+
+ delete graphics;
+ graphics = nullptr;
+
+ SDL_Surface *const surfaceA = MSDL_CreateRGBSurface(SDL_HWSURFACE,
+ BUFFER_WIDTH, BUFFER_HEIGHT, 32, rmask, gmask, bmask, amask);
+
+#ifdef USE_SDL2
+ SDL_SetSurfaceAlphaMod(surface, 255);
+#else
+ SDL_SetAlpha(surface, 0, SDL_ALPHA_OPAQUE);
+#endif
+ SDL_BlitSurface(surface, nullptr, surfaceA, nullptr);
+
+ delete mImage;
+ delete mAlphaImage;
+
+ mImage = imageHelper->load(surface);
+ MSDL_FreeSurface(surface);
+
+ if (ImageHelper::mEnableAlpha)
+ {
+ mAlphaImage = imageHelper->load(surfaceA);
+ MSDL_FreeSurface(surfaceA);
+ }
+ else
+ {
+ mAlphaImage = nullptr;
+ }
+#endif
+}
+
+void CompoundSprite::setAlpha(float alpha)
+{
+ if (alpha != mAlpha)
+ {
+#ifdef USE_OPENGL
+ if (mEnableAlphaFix && imageHelper->useOpenGL() == 0
+ && size() > 3)
+#else
+ if (mEnableAlphaFix && size() > 3)
+#endif
+ {
+ FOR_EACH (SpriteConstIterator, it, mSprites)
+ {
+ if (*it)
+ (*it)->setAlpha(alpha);
+ }
+ }
+ mAlpha = alpha;
+ }
+}
+
+void CompoundSprite::updateImages() const
+{
+#ifndef USE_SDL2
+#ifdef USE_OPENGL
+ if (imageHelper->useOpenGL())
+ return;
+#endif
+
+ if (mEnableDelay)
+ {
+ if (get_elapsed_time1(mNextRedrawTime) < 10)
+ return;
+ mNextRedrawTime = tick_time;
+ }
+ mNeedsRedraw = false;
+
+ if (!mDisableBeingCaching)
+ {
+ if (size() <= 3)
+ return;
+
+ if (!mDisableAdvBeingCaching)
+ {
+ if (updateFromCache())
+ return;
+
+ redraw();
+
+ if (mImage)
+ initCurrentCacheItem();
+ }
+ else
+ {
+ redraw();
+ }
+ }
+#endif
+}
+
+bool CompoundSprite::updateFromCache() const
+{
+#ifndef USE_SDL2
+// static int hits = 0;
+// static int miss = 0;
+
+ if (mCacheItem && mCacheItem->image)
+ {
+ imagesCache.push_front(mCacheItem);
+ mCacheItem = nullptr;
+ if (imagesCache.size() > cache_max_size)
+ {
+ for (unsigned f = 0; f < cache_clean_part; f ++)
+ {
+ CompoundItem *item = imagesCache.back();
+ imagesCache.pop_back();
+ delete item;
+ }
+ }
+ }
+
+// logger->log("cache size: %d, hit %d, miss %d",
+// (int)imagesCache.size(), hits, miss);
+
+ const size_t sz = size();
+ FOR_EACH (ImagesCache::iterator, it, imagesCache)
+ {
+ CompoundItem *const ic = *it;
+ if (ic && ic->data.size() == sz)
+ {
+ bool fail(false);
+ VectorPointers::const_iterator it2 = ic->data.begin();
+ const VectorPointers::const_iterator it2_end = ic->data.end();
+
+ for (SpriteConstIterator it1 = mSprites.begin(),
+ it1_end = mSprites.end();
+ it1 != it1_end && it2 != it2_end;
+ ++ it1, ++ it2)
+ {
+ const void *ptr1 = nullptr;
+ const void *ptr2 = nullptr;
+ if (*it1)
+ ptr1 = (*it1)->getHash();
+ if (*it2)
+ ptr2 = *it2;
+ if (ptr1 != ptr2)
+ {
+ fail = true;
+ break;
+ }
+ }
+ if (!fail)
+ {
+// hits ++;
+ mImage = (*it)->image;
+ mAlphaImage = (*it)->alphaImage;
+ imagesCache.erase(it);
+ mCacheItem = ic;
+ return true;
+ }
+ }
+ }
+ mImage = nullptr;
+ mAlphaImage = nullptr;
+// miss++;
+#endif
+ return false;
+}
+
+void CompoundSprite::initCurrentCacheItem() const
+{
+ delete mCacheItem;
+ mCacheItem = new CompoundItem();
+ mCacheItem->image = mImage;
+ mCacheItem->alphaImage = mAlphaImage;
+// mCacheItem->alpha = mAlpha;
+
+ FOR_EACH (SpriteConstIterator, it, mSprites)
+ {
+ if (*it)
+ mCacheItem->data.push_back((*it)->getHash());
+ else
+ mCacheItem->data.push_back(nullptr);
+ }
+}
+
+bool CompoundSprite::updateNumber(unsigned num)
+{
+ bool res(false);
+ FOR_EACH (SpriteConstIterator, it, mSprites)
+ {
+ if (*it)
+ {
+ if ((*it)->updateNumber(num))
+ res = true;
+ }
+ }
+ return res;
+}
+
+CompoundItem::CompoundItem() :
+ data(),
+ image(nullptr),
+ alphaImage(nullptr)
+{
+}
+
+CompoundItem::~CompoundItem()
+{
+ delete image;
+ delete alphaImage;
+}
diff --git a/src/being/compoundsprite.h b/src/being/compoundsprite.h
new file mode 100644
index 000000000..5024f0122
--- /dev/null
+++ b/src/being/compoundsprite.h
@@ -0,0 +1,161 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2010 The Mana Developers
+ * Copyright (C) 2011-2013 The ManaPlus Developers
+ *
+ * This file is part of The ManaPlus Client.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef BEING_COMPOUNDSPRITE_H
+#define BEING_COMPOUNDSPRITE_H
+
+#include "sprite.h"
+
+#include <list>
+#include <vector>
+
+#include "localconsts.h"
+
+class Image;
+
+typedef std::list <void*> VectorPointers;
+
+class CompoundItem final
+{
+ public:
+ CompoundItem();
+
+ A_DELETE_COPY(CompoundItem)
+
+ ~CompoundItem();
+
+ VectorPointers data;
+ Image *image;
+ Image *alphaImage;
+};
+
+class CompoundSprite : public Sprite
+{
+public:
+ typedef std::vector<Sprite*>::iterator SpriteIterator;
+ typedef std::vector<Sprite*>::const_iterator SpriteConstIterator;
+
+ CompoundSprite();
+
+ A_DELETE_COPY(CompoundSprite)
+
+ ~CompoundSprite();
+
+ virtual bool reset() override;
+
+ virtual bool play(const std::string &action) override;
+
+ virtual bool update(const int time) override;
+
+ virtual bool draw(Graphics *const graphics,
+ const int posX, const int posY) const override;
+
+ /**
+ * Gets the width in pixels of the first sprite in the list.
+ */
+ virtual int getWidth() const override A_WARN_UNUSED;
+
+ /**
+ * Gets the height in pixels of the first sprite in the list.
+ */
+ virtual int getHeight() const override A_WARN_UNUSED;
+
+ virtual const Image *getImage() const override A_WARN_UNUSED;
+
+ virtual bool setSpriteDirection(const SpriteDirection direction) override;
+
+ int getNumberOfLayers() const A_WARN_UNUSED;
+
+ unsigned int getCurrentFrame() const override A_WARN_UNUSED;
+
+ unsigned int getFrameCount() const override A_WARN_UNUSED;
+
+ size_t size() const A_WARN_UNUSED
+ { return mSprites.size(); }
+
+ bool empty() const A_WARN_UNUSED
+ { return mSprites.empty(); }
+
+ void addSprite(Sprite *const sprite);
+
+ void setSprite(const int layer, Sprite *const sprite);
+
+ Sprite *getSprite(int layer) const A_WARN_UNUSED
+ { return mSprites.at(layer); }
+
+ void removeSprite(const int layer);
+
+ void clear();
+
+ void ensureSize(size_t layerCount);
+
+ virtual void drawSprites(Graphics *const graphics,
+ int posX, int posY) const;
+
+ virtual void drawSpritesSDL(Graphics *const graphics,
+ int posX, int posY) const;
+
+ /**
+ * Returns the curent frame in the current animation of the given layer.
+ */
+ virtual unsigned int getCurrentFrame(unsigned int layer)
+ const A_WARN_UNUSED;
+
+ /**
+ * Returns the frame count in the current animation of the given layer.
+ */
+ virtual unsigned int getFrameCount(unsigned int layer) A_WARN_UNUSED;
+
+ virtual void setAlpha(float alpha) override;
+
+ bool updateNumber(const unsigned num) override;
+
+ static void setEnableDelay(bool b)
+ { mEnableDelay = b; }
+
+private:
+ void redraw() const;
+
+ void updateImages() const;
+
+ bool updateFromCache() const;
+
+ void initCurrentCacheItem() const;
+
+ typedef std::list<CompoundItem*> ImagesCache;
+ mutable ImagesCache imagesCache;
+ mutable CompoundItem *mCacheItem;
+
+ mutable Image *mImage;
+ mutable Image *mAlphaImage;
+
+ mutable int mOffsetX;
+ mutable int mOffsetY;
+ std::vector<Sprite*> mSprites;
+ mutable int mNextRedrawTime;
+ static bool mEnableDelay;
+ mutable bool mNeedsRedraw;
+ bool mEnableAlphaFix;
+ bool mDisableAdvBeingCaching;
+ bool mDisableBeingCaching;
+};
+
+#endif // BEING_COMPOUNDSPRITE_H
diff --git a/src/being/localplayer.cpp b/src/being/localplayer.cpp
new file mode 100644
index 000000000..6a0aa1b19
--- /dev/null
+++ b/src/being/localplayer.cpp
@@ -0,0 +1,4355 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ * Copyright (C) 2011-2013 The ManaPlus Developers
+ *
+ * This file is part of The ManaPlus Client.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "being/localplayer.h"
+
+#include "actorspritemanager.h"
+#include "client.h"
+#include "configuration.h"
+#include "dropshortcut.h"
+#include "effectmanager.h"
+#include "guild.h"
+#include "item.h"
+#include "maplayer.h"
+#include "party.h"
+#include "simpleanimation.h"
+#include "soundconsts.h"
+#include "soundmanager.h"
+#include "statuseffect.h"
+#include "walklayer.h"
+
+#include "being/playerinfo.h"
+#include "being/playerrelations.h"
+
+#include "particle/particle.h"
+
+#include "input/keyboardconfig.h"
+
+#include "gui/chatwindow.h"
+#include "gui/gui.h"
+#include "gui/ministatuswindow.h"
+#include "gui/okdialog.h"
+#include "gui/outfitwindow.h"
+#include "gui/shopwindow.h"
+#include "gui/sdlfont.h"
+#include "gui/skilldialog.h"
+#include "gui/socialwindow.h"
+#include "gui/updaterwindow.h"
+#include "gui/viewport.h"
+
+#include "gui/widgets/gmtab.h"
+#include "gui/widgets/whispertab.h"
+
+#include "render/graphics.h"
+
+#include "net/beinghandler.h"
+#include "net/chathandler.h"
+#include "net/guildhandler.h"
+#include "net/inventoryhandler.h"
+#include "net/net.h"
+#include "net/partyhandler.h"
+#include "net/playerhandler.h"
+#include "net/skillhandler.h"
+#include "net/tradehandler.h"
+
+#include "resources/imageset.h"
+#include "resources/iteminfo.h"
+#include "resources/resourcemanager.h"
+
+#include "utils/gettext.h"
+
+#include "mumblemanager.h"
+
+#include <climits>
+
+#include "debug.h"
+
+static const int16_t awayLimitTimer = 60;
+static const int MAX_TICK_VALUE = INT_MAX / 2;
+
+typedef std::map<int, Guild*>::const_iterator GuildMapCIter;
+
+LocalPlayer *player_node = nullptr;
+
+extern std::list<BeingCacheEntry*> beingInfoCache;
+extern OkDialog *weightNotice;
+extern int weightNoticeTime;
+extern MiniStatusWindow *miniStatusWindow;
+extern SkillDialog *skillDialog;
+
+LocalPlayer::LocalPlayer(const int id, const int subtype) :
+ Being(id, PLAYER, subtype, nullptr),
+ mGMLevel(0),
+ mInvertDirection(0),
+ mCrazyMoveType(config.getIntValue("crazyMoveType")),
+ mCrazyMoveState(0),
+ mAttackWeaponType(config.getIntValue("attackWeaponType")),
+ mQuickDropCounter(config.getIntValue("quickDropCounter")),
+ mMoveState(0),
+ mPickUpType(config.getIntValue("pickUpType")),
+ mMagicAttackType(config.getIntValue("magicAttackType")),
+ mPvpAttackType(config.getIntValue("pvpAttackType")),
+ mMoveToTargetType(config.getIntValue("moveToTargetType")),
+ mAttackType(config.getIntValue("attackType")),
+ mFollowMode(config.getIntValue("followMode")),
+ mImitationMode(config.getIntValue("imitationMode")),
+ mLastTargetX(0),
+ mLastTargetY(0),
+ mHomes(),
+ mTarget(nullptr),
+ mPlayerFollowed(),
+ mPlayerImitated(),
+ mNextDestX(0),
+ mNextDestY(0),
+ mPickUpTarget(nullptr),
+ mLastAction(-1),
+ mStatusEffectIcons(),
+ mLocalWalkTime(-1),
+ mMessages(),
+ mMessageTime(0),
+ mAwayListener(new AwayListener),
+ mAwayDialog(nullptr),
+ mPingSendTick(0),
+ mPingTime(0),
+ mAfkTime(0),
+ mActivityTime(0),
+ mNavigateX(0),
+ mNavigateY(0),
+ mNavigateId(0),
+ mCrossX(0),
+ mCrossY(0),
+ mOldX(0),
+ mOldY(0),
+ mOldTileX(0),
+ mOldTileY(0),
+ mNavigatePath(),
+ mLastHitFrom(),
+ mWaitFor(),
+ mAdvertTime(0),
+ mTestParticle(nullptr),
+ mTestParticleName(),
+ mTestParticleTime(0),
+ mTestParticleHash(0l),
+ mWalkingDir(0),
+ mUpdateName(true),
+ mBlockAdvert(false),
+ mTargetDeadPlayers(config.getBoolValue("targetDeadPlayers")),
+ mServerAttack(config.getBoolValue("serverAttack")),
+ mEnableAdvert(config.getBoolValue("enableAdvert")),
+ mTradebot(config.getBoolValue("tradebot")),
+ mTargetOnlyReachable(config.getBoolValue("targetOnlyReachable")),
+ mDisableGameModifiers(config.getBoolValue("disableGameModifiers")),
+ mIsServerBuggy(serverConfig.getValueBool("enableBuggyServers", true)),
+ mSyncPlayerMove(config.getBoolValue("syncPlayerMove")),
+ mDrawPath(config.getBoolValue("drawPath")),
+ mAttackMoving(config.getBoolValue("attackMoving")),
+ mAttackNext(config.getBoolValue("attackNext")),
+ mShowJobExp(config.getBoolValue("showJobExp")),
+ mNextStep(false),
+ mDisableCrazyMove(false),
+ mGoingToTarget(false),
+ mKeepAttacking(false),
+ mPathSetByMouse(false),
+ mWaitPing(false),
+ mAwayMode(false),
+ mPseudoAwayMode(false),
+ mShowNavigePath(false)
+{
+ logger->log1("LocalPlayer::LocalPlayer");
+
+ listen(CHANNEL_ATTRIBUTES);
+
+ mAttackRange = 0;
+ mLevel = 1;
+ mAdvanced = true;
+ mTextColor = &Theme::getThemeColor(Theme::PLAYER);
+ if (userPalette)
+ mNameColor = &userPalette->getColor(UserPalette::SELF);
+ else
+ mNameColor = nullptr;
+
+ PlayerInfo::setStatBase(PlayerInfo::WALK_SPEED,
+ static_cast<int>(getWalkSpeed().x));
+ PlayerInfo::setStatMod(PlayerInfo::WALK_SPEED, 0);
+
+ loadHomes();
+
+ config.addListener("showownname", this);
+ config.addListener("targetDeadPlayers", this);
+ serverConfig.addListener("enableBuggyServers", this);
+ config.addListener("syncPlayerMove", this);
+ config.addListener("drawPath", this);
+ config.addListener("serverAttack", this);
+ config.addListener("attackMoving", this);
+ config.addListener("attackNext", this);
+ config.addListener("showJobExp", this);
+ config.addListener("enableAdvert", this);
+ config.addListener("tradebot", this);
+ config.addListener("targetOnlyReachable", this);
+ setShowName(config.getBoolValue("showownname"));
+}
+
+LocalPlayer::~LocalPlayer()
+{
+ logger->log1("LocalPlayer::~LocalPlayer");
+
+ config.removeListeners(this);
+ serverConfig.removeListener("enableBuggyServers", this);
+
+ if (mAwayDialog)
+ {
+ soundManager.volumeRestore();
+ delete mAwayDialog;
+ mAwayDialog = nullptr;
+ }
+ delete mAwayListener;
+ mAwayListener = nullptr;
+}
+
+void LocalPlayer::logic()
+{
+ BLOCK_START("LocalPlayer::logic")
+#ifdef USE_MUMBLE
+ if (mumbleManager)
+ mumbleManager->setPos(mX, mY, mDirection);
+#endif
+
+ // Actions are allowed once per second
+ if (get_elapsed_time(mLastAction) >= 1000)
+ mLastAction = -1;
+
+ if (mActivityTime == 0 || mLastAction != -1)
+ mActivityTime = cur_time;
+
+ if ((mAction != MOVE || mNextStep) && !mNavigatePath.empty())
+ {
+ mNextStep = false;
+ int dist = 5;
+ if (!mSyncPlayerMove)
+ dist = 20;
+
+ if ((mNavigateX || mNavigateY) &&
+ ((mCrossX + dist >= mX && mCrossX <= mX + dist
+ && mCrossY + dist >= mY && mCrossY <= mY + dist)
+ || (!mCrossX && !mCrossY)))
+ {
+ const Path::const_iterator i = mNavigatePath.begin();
+ if ((*i).x == mX && (*i).y == mY)
+ mNavigatePath.pop_front();
+ else
+ moveTo((*i).x, (*i).y);
+ }
+ }
+
+ // Show XP messages
+ if (!mMessages.empty())
+ {
+ if (mMessageTime == 0)
+ {
+ MessagePair info = mMessages.front();
+
+ if (particleEngine)
+ {
+ particleEngine->addTextRiseFadeOutEffect(
+ info.first,
+ getPixelX(),
+ getPixelY() - 48,
+ &userPalette->getColor(info.second),
+ gui->getInfoParticleFont(), true);
+ }
+
+ mMessages.pop_front();
+ mMessageTime = 30;
+ }
+ mMessageTime--;
+ }
+
+#ifdef MANASERV_SUPPORT
+ PlayerInfo::logic();
+#endif
+
+ if (mTarget)
+ {
+ if (mTarget->getType() == ActorSprite::NPC)
+ {
+ // NPCs are always in range
+ mTarget->setTargetType(TCT_IN_RANGE);
+ }
+ else
+ {
+ // Find whether target is in range
+#ifdef MANASERV_SUPPORT
+ const int rangeX =
+ (Net::getNetworkType() == ServerInfo::MANASERV) ?
+ static_cast<int>(abs(static_cast<int>(mTarget->getPosition().x
+ - getPosition().x))) :
+ static_cast<int>(abs(mTarget->getTileX() - getTileX()));
+ const int rangeY =
+ (Net::getNetworkType() == ServerInfo::MANASERV) ?
+ static_cast<int>(abs(static_cast<int>(mTarget->getPosition().y
+ - getPosition().y))) :
+ static_cast<int>(abs(mTarget->getTileY() - getTileY()));
+#else
+ const int rangeX = static_cast<int>(
+ abs(mTarget->getTileX() - getTileX()));
+ const int rangeY = static_cast<int>(
+ abs(mTarget->getTileY() - getTileY()));
+#endif
+ const int attackRange = getAttackRange();
+ const TargetCursorType targetType = rangeX > attackRange ||
+ rangeY > attackRange ?
+ TCT_NORMAL : TCT_IN_RANGE;
+ mTarget->setTargetType(targetType);
+
+ if (!mTarget->isAlive() && (!mTargetDeadPlayers
+ || mTarget->getType() != Being::PLAYER))
+ {
+ stopAttack(true);
+ }
+
+ if (mKeepAttacking && mTarget)
+ attack(mTarget, true);
+ }
+ }
+
+ Being::logic();
+ BLOCK_END("LocalPlayer::logic")
+}
+
+void LocalPlayer::slowLogic()
+{
+ BLOCK_START("LocalPlayer::slowLogic")
+ const int time = cur_time;
+ if (weightNotice && weightNoticeTime < time)
+ {
+ weightNotice->scheduleDelete();
+ weightNotice = nullptr;
+ weightNoticeTime = 0;
+ }
+
+ if (serverVersion < 4 && mEnableAdvert && !mBlockAdvert
+ && mAdvertTime < cur_time)
+ {
+ uint8_t smile = FLAG_SPECIAL;
+ if (mTradebot && shopWindow && !shopWindow->isShopEmpty())
+ smile |= FLAG_SHOP;
+
+ if (mAwayMode || mPseudoAwayMode)
+ smile |= FLAG_AWAY;
+
+ if (mInactive)
+ smile |= FLAG_INACTIVE;
+
+ if (emote(smile))
+ mAdvertTime = time + 60;
+ else
+ mAdvertTime = time + 30;
+ }
+
+ if (mTestParticleTime != time && !mTestParticleName.empty())
+ {
+ unsigned long hash = UpdaterWindow::getFileHash(mTestParticleName);
+ if (hash != mTestParticleHash)
+ {
+ setTestParticle(mTestParticleName, false);
+ mTestParticleHash = hash;
+ }
+ mTestParticleTime = time;
+ }
+
+ BLOCK_END("LocalPlayer::slowLogic")
+}
+
+void LocalPlayer::setAction(const Action &action, const int attackType)
+{
+ if (action == DEAD)
+ {
+ if (!mLastHitFrom.empty())
+ {
+ // TRANSLATORS: chat message after death
+ debugMsg(strprintf(_("You were killed by %s"),
+ mLastHitFrom.c_str()));
+ mLastHitFrom.clear();
+ }
+ setTarget(nullptr);
+ }
+
+ Being::setAction(action, attackType);
+#ifdef USE_MUMBLE
+ if (mumbleManager)
+ mumbleManager->setAction(static_cast<int>(action));
+#endif
+}
+
+void LocalPlayer::setGMLevel(const int level)
+{
+ mGMLevel = level;
+
+ if (level > 0)
+ {
+ setGM(true);
+ if (chatWindow)
+ {
+ chatWindow->loadGMCommands();
+ if (!gmChatTab && config.getBoolValue("enableGmTab"))
+ gmChatTab = new GmTab(chatWindow);
+ }
+ }
+}
+
+#ifdef MANASERV_SUPPORT
+Position LocalPlayer::getNextWalkPosition(const unsigned char dir) const
+{
+ // Compute where the next tile will be set.
+ int dx = 0, dy = 0;
+ if (dir & Being::UP)
+ dy--;
+ if (dir & Being::DOWN)
+ dy++;
+ if (dir & Being::LEFT)
+ dx--;
+ if (dir & Being::RIGHT)
+ dx++;
+
+ const Vector &pos = getPosition();
+
+ // If no map or no direction is given, give back the current player position
+ if (!mMap || (!dx && !dy))
+ return Position(static_cast<int>(pos.x), static_cast<int>(pos.y));
+
+ const int posX = static_cast<int>(pos.x);
+ const int posY = static_cast<int>(pos.y);
+ // Get the current tile pos and its offset
+ const int tileX = posX / mMap->getTileWidth();
+ const int tileY = posY / mMap->getTileHeight();
+ const int offsetX = posX % mMap->getTileWidth();
+ const int offsetY = posY % mMap->getTileHeight();
+ const unsigned char walkMask = getWalkMask();
+ const int radius = getCollisionRadius();
+
+ // Get the walkability of every surrounding tiles.
+ bool wTopLeft = mMap->getWalk(tileX - 1, tileY - 1, walkMask);
+ const bool wTop = mMap->getWalk(tileX, tileY - 1, walkMask);
+ bool wTopRight = mMap->getWalk(tileX + 1, tileY - 1, walkMask);
+ const bool wLeft = mMap->getWalk(tileX - 1, tileY, walkMask);
+ const bool wRight = mMap->getWalk(tileX + 1, tileY, walkMask);
+ bool wBottomLeft = mMap->getWalk(tileX - 1, tileY + 1, walkMask);
+ const bool wBottom = mMap->getWalk(tileX, tileY + 1, walkMask);
+ bool wBottomRight = mMap->getWalk(tileX + 1, tileY + 1, walkMask);
+
+ // Make diagonals unwalkable when both straight directions are blocking
+ if (!wTop)
+ {
+ if (!wRight)
+ wTopRight = false;
+ if (!wLeft)
+ wTopLeft = false;
+ }
+ if (!wBottom)
+ {
+ if (!wRight)
+ wBottomRight = false;
+ if (!wLeft)
+ wBottomLeft = false;
+ }
+
+ // We'll make tests for each desired direction
+
+ // Handle diagonal cases by setting the way back to a straight direction
+ // when necessary.
+ if (dx && dy)
+ {
+ // Going top-right
+ if (dx > 0 && dy < 0)
+ {
+ if (!wTopRight)
+ {
+ // Choose a straight direction when diagonal target is blocked
+ if (!wTop && wRight)
+ {
+ dy = 0;
+ }
+ else if (wTop && !wRight)
+ {
+ dx = 0;
+ }
+ else if (!wTop && !wRight)
+ {
+ return Position(tileX * 32 + 32 - radius,
+ tileY * 32 + getCollisionRadius());
+ }
+ else // Both straight direction are walkable
+ {
+ // Go right when below the corner
+ if (offsetY >= (offsetX / mMap->getTileHeight()
+ - (offsetX / mMap->getTileWidth()
+ * mMap->getTileHeight()) ))
+ {
+ dy = 0;
+ }
+ else // Go up otherwise
+ {
+ dx = 0;
+ }
+ }
+ }
+ else // The diagonal is walkable
+ {
+ return mMap->checkNodeOffsets(radius,
+ walkMask, Position(posX + 32, posY - 32));
+ }
+ }
+
+ // Going top-left
+ if (dx < 0 && dy < 0)
+ {
+ if (!wTopLeft)
+ {
+ // Choose a straight direction when diagonal target is blocked
+ if (!wTop && wLeft)
+ {
+ dy = 0;
+ }
+ else if (wTop && !wLeft)
+ {
+ dx = 0;
+ }
+ else if (!wTop && !wLeft)
+ {
+ return Position(tileX * 32 + radius,
+ tileY * 32 + radius);
+ }
+ else // Both straight direction are walkable
+ {
+ // Go left when below the corner
+ if (offsetY >= (offsetX / mMap->getTileWidth()
+ * mMap->getTileHeight()))
+ {
+ dy = 0;
+ }
+ else // Go up otherwise
+ {
+ dx = 0;
+ }
+ }
+ }
+ else // The diagonal is walkable
+ {
+ return mMap->checkNodeOffsets(radius,
+ walkMask, Position(posX - 32, posY - 32));
+ }
+ }
+
+ // Going bottom-left
+ if (dx < 0 && dy > 0)
+ {
+ if (!wBottomLeft)
+ {
+ // Choose a straight direction when diagonal target is blocked
+ if (!wBottom && wLeft)
+ {
+ dy = 0;
+ }
+ else if (wBottom && !wLeft)
+ {
+ dx = 0;
+ }
+ else if (!wBottom && !wLeft)
+ {
+ return Position(tileX * 32 + radius,
+ tileY * 32 + 32 - radius);
+ }
+ else // Both straight direction are walkable
+ {
+ // Go down when below the corner
+ if (offsetY >= (offsetX / mMap->getTileHeight()
+ - (offsetX / mMap->getTileWidth()
+ * mMap->getTileHeight())))
+ {
+ dx = 0;
+ }
+ else // Go left otherwise
+ {
+ dy = 0;
+ }
+ }
+ }
+ else // The diagonal is walkable
+ {
+ return mMap->checkNodeOffsets(radius,
+ walkMask, Position(posX - 32, posY + 32));
+ }
+ }
+
+ // Going bottom-right
+ if (dx > 0 && dy > 0)
+ {
+ if (!wBottomRight)
+ {
+ // Choose a straight direction when diagonal target is blocked
+ if (!wBottom && wRight)
+ {
+ dy = 0;
+ }
+ else if (wBottom && !wRight)
+ {
+ dx = 0;
+ }
+ else if (!wBottom && !wRight)
+ {
+ return Position(tileX * 32 + 32 - radius,
+ tileY * 32 + 32 - radius);
+ }
+ else // Both straight direction are walkable
+ {
+ // Go down when below the corner
+ if (offsetY >= (offsetX / mMap->getTileWidth()
+ * mMap->getTileHeight()))
+ {
+ dx = 0;
+ }
+ else // Go right otherwise
+ {
+ dy = 0;
+ }
+ }
+ }
+ else // The diagonal is walkable
+ {
+ return mMap->checkNodeOffsets(radius,
+ walkMask, Position(posX + 32, posY + 32));
+ }
+ }
+ } // End of diagonal cases
+
+ // Straight directions
+ // Right direction
+ if (dx > 0 && !dy)
+ {
+ // If the straight destination is blocked,
+ // Make the player go the closest possible.
+ if (!wRight)
+ {
+ return Position(tileX * 32 + 32 - radius, posY);
+ }
+ else
+ {
+ if (!wTopRight)
+ {
+ // If we're going to collide with the top-right corner
+ if (offsetY - radius < 0)
+ {
+ // We make the player corrects its offset
+ // before going further
+ return Position(tileX * 32 + 32 - radius,
+ tileY * 32 + radius);
+ }
+ }
+
+ if (!wBottomRight)
+ {
+ // If we're going to collide with the bottom-right corner
+ if (offsetY + radius > 32)
+ {
+ // We make the player corrects its offset
+ // before going further
+ return Position(tileX * 32 + 32 - radius,
+ tileY * 32 + 32 - radius);
+ }
+ }
+ // If the way is clear, step up one checked tile ahead.
+ return mMap->checkNodeOffsets(radius,
+ walkMask, Position(posX + 32, posY));
+ }
+ }
+
+ // Left direction
+ if (dx < 0 && !dy)
+ {
+ // If the straight destination is blocked,
+ // Make the player go the closest possible.
+ if (!wLeft)
+ {
+ return Position(tileX * 32 + radius, posY);
+ }
+ else
+ {
+ if (!wTopLeft)
+ {
+ // If we're going to collide with the top-left corner
+ if (offsetY - radius < 0)
+ {
+ // We make the player corrects its offset
+ // before going further
+ return Position(tileX * 32 + radius,
+ tileY * 32 + radius);
+ }
+ }
+
+ if (!wBottomLeft)
+ {
+ // If we're going to collide with the bottom-left corner
+ if (offsetY + radius > 32)
+ {
+ // We make the player corrects its offset
+ // before going further
+ return Position(tileX * 32 + radius,
+ tileY * 32 + 32 - radius);
+ }
+ }
+ // If the way is clear, step up one checked tile ahead.
+ return mMap->checkNodeOffsets(radius,
+ walkMask, Position(posX - 32, posY));
+ }
+ }
+
+ // Up direction
+ if (!dx && dy < 0)
+ {
+ // If the straight destination is blocked,
+ // Make the player go the closest possible.
+ if (!wTop)
+ {
+ return Position(posX, tileY * 32 + radius);
+ }
+ else
+ {
+ if (!wTopLeft)
+ {
+ // If we're going to collide with the top-left corner
+ if (offsetX - radius < 0)
+ {
+ // We make the player corrects its offset
+ // before going further
+ return Position(tileX * 32 + radius,
+ tileY * 32 + radius);
+ }
+ }
+
+ if (!wTopRight)
+ {
+ // If we're going to collide with the top-right corner
+ if (offsetX + radius > 32)
+ {
+ // We make the player corrects its offset
+ // before going further
+ return Position(tileX * 32 + 32 - radius,
+ tileY * 32 + radius);
+ }
+ }
+ // If the way is clear, step up one checked tile ahead.
+ return mMap->checkNodeOffsets(radius,
+ walkMask, Position(posX, posY - 32));
+ }
+ }
+
+ // Down direction
+ if (!dx && dy > 0)
+ {
+ // If the straight destination is blocked,
+ // Make the player go the closest possible.
+ if (!wBottom)
+ {
+ return Position(posX, tileY * 32 + 32 - radius);
+ }
+ else
+ {
+ if (!wBottomLeft)
+ {
+ // If we're going to collide with the bottom-left corner
+ if (offsetX - radius < 0)
+ {
+ // We make the player corrects its offset
+ // before going further
+ return Position(tileX * 32 + radius,
+ tileY * 32 + 32 - radius);
+ }
+ }
+
+ if (!wBottomRight)
+ {
+ // If we're going to collide with the bottom-right corner
+ if (offsetX + radius > 32)
+ {
+ // We make the player corrects its offset
+ // before going further
+ return Position(tileX * 32 + 32 - radius,
+ tileY * 32 + 32 - radius);
+ }
+ }
+ // If the way is clear, step up one checked tile ahead.
+ return mMap->checkNodeOffsets(radius,
+ walkMask, Position(posX, posY + 32));
+ }
+ }
+
+ // Return the current position if everything else has failed.
+ return Position(posX, posY);
+}
+#endif
+
+void LocalPlayer::nextTile(unsigned char dir A_UNUSED = 0)
+{
+#ifdef MANASERV_SUPPORT
+ if (Net::getNetworkType() != ServerInfo::MANASERV)
+#endif
+ {
+ Party *const party = Party::getParty(1);
+ if (party)
+ {
+ PartyMember *const pm = party->getMember(getName());
+ if (pm)
+ {
+ pm->setX(mX);
+ pm->setY(mY);
+ }
+ }
+
+ if (mPath.empty())
+ {
+ if (mPickUpTarget)
+ pickUp(mPickUpTarget);
+
+ if (mWalkingDir)
+ startWalking(mWalkingDir);
+ }
+ else if (mPath.size() == 1)
+ {
+ if (mPickUpTarget)
+ pickUp(mPickUpTarget);
+ }
+
+ if (mGoingToTarget && mTarget && withinAttackRange(mTarget))
+ {
+ mAction = Being::STAND;
+ attack(mTarget, true);
+ mGoingToTarget = false;
+ mPath.clear();
+ return;
+ }
+ else if (mGoingToTarget && !mTarget)
+ {
+ mGoingToTarget = false;
+ mPath.clear();
+ }
+
+ if (mPath.empty())
+ {
+ if (mNavigatePath.empty() || mAction != MOVE)
+ setAction(STAND);
+ else
+ mNextStep = true;
+ }
+ else
+ {
+ Being::nextTile();
+ }
+ }
+#ifdef MANASERV_SUPPORT
+ else
+ {
+ if (!mMap || !dir)
+ return;
+
+ const Vector &pos = getPosition();
+ const Position destination = getNextWalkPosition(dir);
+
+ if (static_cast<int>(pos.x) != destination.x
+ || static_cast<int>(pos.y) != destination.y)
+ {
+ setDestination(destination.x, destination.y);
+ }
+ else if (dir != mDirection)
+ {
+ Net::getPlayerHandler()->setDirection(dir);
+ setDirection(dir);
+ }
+ }
+#endif
+}
+
+bool LocalPlayer::pickUp(FloorItem *const item)
+{
+ if (!item)
+ return false;
+
+ if (!client->limitPackets(PACKET_PICKUP))
+ return false;
+
+ const int dx = item->getTileX() - mX;
+ const int dy = item->getTileY() - mY;
+ int dist = 6;
+
+ if (mPickUpType >= 4 && mPickUpType <= 6)
+ dist = 4;
+
+ if (dx * dx + dy * dy < dist)
+ {
+ if (actorSpriteManager && actorSpriteManager->checkForPickup(item))
+ {
+ Net::getPlayerHandler()->pickUp(item);
+ mPickUpTarget = nullptr;
+ }
+ }
+ else if (mPickUpType >= 4 && mPickUpType <= 6)
+ {
+#ifdef MANASERV_SUPPORT
+ if (Net::getNetworkType() == ServerInfo::MANASERV)
+ {
+ setDestination(item->getPixelX() + 16, item->getPixelY() + 16);
+ mPickUpTarget = item;
+ mPickUpTarget->addActorSpriteListener(this);
+ }
+ else
+#endif
+ {
+ const Vector &playerPos = getPosition();
+ const Path debugPath = mMap->findPath(
+ static_cast<int>(playerPos.x - 16) / 32,
+ static_cast<int>(playerPos.y - 32) / 32,
+ item->getTileX(), item->getTileY(), getWalkMask(), 0);
+ if (!debugPath.empty())
+ navigateTo(item->getTileX(), item->getTileY());
+ else
+ setDestination(item->getTileX(), item->getTileY());
+
+ mPickUpTarget = item;
+ mPickUpTarget->addActorSpriteListener(this);
+ }
+ }
+ return true;
+}
+
+void LocalPlayer::actorSpriteDestroyed(const ActorSprite &actorSprite)
+{
+ if (mPickUpTarget == &actorSprite)
+ mPickUpTarget = nullptr;
+}
+
+Being *LocalPlayer::getTarget() const
+{
+ return mTarget;
+}
+
+void LocalPlayer::setTarget(Being *const target)
+{
+ if (target == this && target)
+ return;
+
+ if (target == mTarget)
+ return;
+
+ Being *oldTarget = nullptr;
+ if (mTarget)
+ {
+ mTarget->untarget();
+ oldTarget = mTarget;
+ }
+
+ if (mTarget && mTarget->getType() == ActorSprite::MONSTER)
+ mTarget->setShowName(false);
+
+ mTarget = target;
+
+ if (oldTarget)
+ oldTarget->updateName();
+
+ if (mTarget)
+ {
+ mLastTargetX = mTarget->getTileX();
+ mLastTargetY = mTarget->getTileY();
+ mTarget->updateName();
+ }
+
+ if (target && target->getType() == ActorSprite::MONSTER)
+ target->setShowName(true);
+}
+
+void LocalPlayer::setDestination(const int x, const int y)
+{
+ mActivityTime = cur_time;
+
+ if (getAttackType() == 0 || !mAttackMoving)
+ mKeepAttacking = false;
+
+ // Only send a new message to the server when destination changes
+ if (x != mDest.x || y != mDest.y)
+ {
+ if (mInvertDirection != 1)
+ {
+ Net::getPlayerHandler()->setDestination(x, y, mDirection);
+ Being::setDestination(x, y);
+ }
+ else if (mInvertDirection == 1)
+ {
+ uint8_t newDir = 0;
+ if (mDirection & UP)
+ newDir |= DOWN;
+ if (mDirection & LEFT)
+ newDir |= RIGHT;
+ if (mDirection & DOWN)
+ newDir |= UP;
+ if (mDirection & RIGHT)
+ newDir |= LEFT;
+
+ Net::getPlayerHandler()->setDestination(x, y, newDir);
+
+// if (client->limitPackets(PACKET_DIRECTION))
+ {
+ setDirection(newDir);
+ Net::getPlayerHandler()->setDirection(newDir);
+ }
+
+ Being::setDestination(x, y);
+ }
+ else
+ {
+#ifdef MANASERV_SUPPORT
+ // Manaserv:
+ // If the destination given to being class is accepted,
+ // we inform the Server.
+ if ((x == mDest.x && y == mDest.y)
+ || Net::getNetworkType() != ServerInfo::MANASERV)
+#endif
+ {
+ Net::getPlayerHandler()->setDestination(x, y, mDirection);
+ }
+ }
+ }
+}
+
+void LocalPlayer::setWalkingDir(const unsigned char dir)
+{
+ // This function is called by Game::handleInput()
+
+#ifdef MANASERV_SUPPORT
+ if (Net::getNetworkType() == ServerInfo::MANASERV)
+ {
+ // First if player is pressing key for the direction he is already
+ // going, do nothing more...
+
+ // Else if he is pressing a key, and its different from what he has
+ // been pressing, stop (do not send this stop to the server) and
+ // start in the new direction
+ if (dir && (dir != getWalkingDir()))
+ stopWalking(false);
+
+ // Else, he is not pressing a key,
+ // and the current path hasn't been sent by mouse,
+ // then, stop (sending to server).
+ else if (!dir)
+ {
+ if (!mPathSetByMouse)
+ stopWalking(true);
+ return;
+ }
+
+ // If the delay to send another walk message to the server hasn't
+ // expired, don't do anything or we could get disconnected for
+ // spamming the server
+ if (get_elapsed_time(mLocalWalkTime) < walkingKeyboardDelay)
+ return;
+ }
+#endif
+
+ mWalkingDir = dir;
+
+ // If we're not already walking, start walking.
+ if (mAction != MOVE && dir)
+ {
+ startWalking(dir);
+ }
+#ifdef MANASERV_SUPPORT
+ else if (mAction == MOVE && (Net::getNetworkType()
+ == ServerInfo::MANASERV))
+ {
+ nextTile(dir);
+ }
+#endif
+}
+
+void LocalPlayer::startWalking(const unsigned char dir)
+{
+ // This function is called by setWalkingDir(),
+ // but also by nextTile() for TMW-Athena...
+ if (!mMap || !dir)
+ return;
+
+ mPickUpTarget = nullptr;
+ if (mAction == MOVE && !mPath.empty())
+ {
+ // Just finish the current action, otherwise we get out of sync
+#ifdef MANASERV_SUPPORT
+ if (Net::getNetworkType() == ServerInfo::MANASERV)
+ {
+ const Vector &pos = getPosition();
+ Being::setDestination(static_cast<int>(pos.x),
+ static_cast<int>(pos.y));
+ }
+ else
+#endif
+ {
+ Being::setDestination(mX, mY);
+ }
+ return;
+ }
+
+ int dx = 0, dy = 0;
+ if (dir & UP)
+ dy--;
+ if (dir & DOWN)
+ dy++;
+ if (dir & LEFT)
+ dx--;
+ if (dir & RIGHT)
+ dx++;
+
+#ifdef MANASERV_SUPPORT
+ if (Net::getNetworkType() != ServerInfo::MANASERV)
+#endif
+ {
+ const unsigned char walkMask = getWalkMask();
+ // Prevent skipping corners over colliding tiles
+ if (dx && !mMap->getWalk(mX + dx, mY, walkMask))
+ dx = 0;
+ if (dy && !mMap->getWalk(mX, mY + dy, walkMask))
+ dy = 0;
+
+ // Choose a straight direction when diagonal target is blocked
+ if (dx && dy && !mMap->getWalk(mX + dx, mY + dy, walkMask))
+ dx = 0;
+
+ // Walk to where the player can actually go
+ if ((dx || dy) && mMap->getWalk(mX + dx, mY + dy, walkMask))
+ {
+ setDestination(mX + dx, mY + dy);
+ }
+ else if (dir != mDirection)
+ {
+ // If the being can't move, just change direction
+
+// if (client->limitPackets(PACKET_DIRECTION))
+ {
+ Net::getPlayerHandler()->setDirection(dir);
+ setDirection(dir);
+ }
+ }
+ }
+#ifdef MANASERV_SUPPORT
+ else
+ {
+ nextTile(dir);
+ }
+#endif
+}
+
+void LocalPlayer::stopWalking(const bool sendToServer)
+{
+ if (mAction == MOVE && mWalkingDir)
+ {
+ mWalkingDir = 0;
+ mLocalWalkTime = 0;
+ mPickUpTarget = nullptr;
+
+ setDestination(static_cast<int>(getPosition().x),
+ static_cast<int>(getPosition().y));
+ if (sendToServer)
+ {
+ Net::getPlayerHandler()->setDestination(
+ static_cast<int>(getPosition().x),
+ static_cast<int>(getPosition().y), -1);
+ }
+ setAction(STAND);
+ }
+
+ // No path set anymore, so we reset the path by mouse flag
+ mPathSetByMouse = false;
+
+ clearPath();
+ navigateClean();
+}
+
+bool LocalPlayer::toggleSit() const
+{
+ if (!client->limitPackets(PACKET_SIT))
+ return false;
+
+ Being::Action newAction;
+ switch (mAction)
+ {
+ case STAND:
+ case SPAWN:
+ newAction = SIT;
+ break;
+ case SIT:
+ newAction = STAND;
+ break;
+ case MOVE:
+ case ATTACK:
+ case DEAD:
+ case HURT:
+ default:
+ return true;
+ }
+
+ Net::getPlayerHandler()->changeAction(newAction);
+ return true;
+}
+
+bool LocalPlayer::updateSit() const
+{
+ if (!client->limitPackets(PACKET_SIT))
+ return false;
+
+ Net::getPlayerHandler()->changeAction(mAction);
+ return true;
+}
+
+bool LocalPlayer::emote(const uint8_t emotion)
+{
+ if (!client->limitPackets(PACKET_EMOTE))
+ return false;
+
+ Net::getPlayerHandler()->emote(emotion);
+ return true;
+}
+
+void LocalPlayer::attack(Being *const target, const bool keep,
+ const bool dontChangeEquipment)
+{
+#ifdef MANASERV_SUPPORT
+ if (Net::getNetworkType() == ServerInfo::MANASERV)
+ {
+ if (mLastAction != -1)
+ return;
+
+ // Can only attack when standing still
+ if (mAction != STAND && mAction != ATTACK)
+ return;
+ }
+#endif
+
+ mKeepAttacking = keep;
+
+ if (!target || target->getType() == ActorSprite::NPC)
+ return;
+
+ if (mTarget != target || !mTarget)
+ setTarget(target);
+
+#ifdef MANASERV_SUPPORT
+ if (Net::getNetworkType() == ServerInfo::MANASERV)
+ {
+ const Vector &plaPos = this->getPosition();
+ const Vector &tarPos = mTarget->getPosition();
+ const int dist_x = static_cast<int>(plaPos.x - tarPos.x);
+ const int dist_y = static_cast<int>(plaPos.y - tarPos.y);
+
+ if (abs(dist_y) >= abs(dist_x))
+ {
+ if (dist_y < 0)
+ setDirection(DOWN);
+ else
+ setDirection(UP);
+ }
+ else
+ {
+ if (dist_x < 0)
+ setDirection(RIGHT);
+ else
+ setDirection(LEFT);
+ }
+
+ mLastAction = tick_time;
+ }
+ else
+#endif
+ {
+ const int dist_x = target->getTileX() - mX;
+ const int dist_y = target->getTileY() - mY;
+
+ // Must be standing or sitting to attack
+ if (mAction != STAND && mAction != SIT)
+ return;
+
+ if (abs(dist_y) >= abs(dist_x))
+ {
+ if (dist_y > 0)
+ setDirection(DOWN);
+ else
+ setDirection(UP);
+ }
+ else
+ {
+ if (dist_x > 0)
+ setDirection(RIGHT);
+ else
+ setDirection(LEFT);
+ }
+
+ mActionTime = tick_time;
+ }
+
+ if (target->getType() != Being::PLAYER || checAttackPermissions(target))
+ {
+ setAction(ATTACK);
+
+ if (!client->limitPackets(PACKET_ATTACK))
+ return;
+
+ if (!dontChangeEquipment)
+ changeEquipmentBeforeAttack(target);
+
+ Net::getPlayerHandler()->attack(target->getId(), mServerAttack);
+ }
+
+#ifdef MANASERV_SUPPORT
+ if ((Net::getNetworkType() != ServerInfo::MANASERV) && !keep)
+#else
+ if (!keep)
+#endif
+ stopAttack();
+}
+
+void LocalPlayer::stopAttack(const bool keepAttack)
+{
+ if (!client->limitPackets(PACKET_STOPATTACK))
+ return;
+
+ if (mServerAttack && mAction == ATTACK)
+ Net::getPlayerHandler()->stopAttack();
+
+ untarget();
+ if (!keepAttack || !mAttackNext)
+ mKeepAttacking = false;
+}
+
+void LocalPlayer::untarget()
+{
+ if (mAction == ATTACK)
+ setAction(STAND);
+
+ if (mTarget)
+ setTarget(nullptr);
+}
+
+void LocalPlayer::pickedUp(const ItemInfo &itemInfo, const int amount,
+ const unsigned char color, const int floorItemId,
+ const unsigned char fail)
+{
+ if (fail)
+ {
+ if (actorSpriteManager && floorItemId)
+ {
+ FloorItem *const item = actorSpriteManager->findItem(floorItemId);
+ if (item)
+ {
+ if (!item->getShowMsg())
+ return;
+ item->setShowMsg(false);
+ }
+ }
+ const char* msg;
+ switch (fail)
+ {
+ case PICKUP_BAD_ITEM:
+ // TRANSLATORS: pickup error message
+ msg = N_("Tried to pick up nonexistent item.");
+ break;
+ case PICKUP_TOO_HEAVY:
+ // TRANSLATORS: pickup error message
+ msg = N_("Item is too heavy.");
+ break;
+ case PICKUP_TOO_FAR:
+ // TRANSLATORS: pickup error message
+ msg = N_("Item is too far away.");
+ break;
+ case PICKUP_INV_FULL:
+ // TRANSLATORS: pickup error message
+ msg = N_("Inventory is full.");
+ break;
+ case PICKUP_STACK_FULL:
+ // TRANSLATORS: pickup error message
+ msg = N_("Stack is too big.");
+ break;
+ case PICKUP_DROP_STEAL:
+ // TRANSLATORS: pickup error message
+ msg = N_("Item belongs to someone else.");
+ break;
+ default:
+ // TRANSLATORS: pickup error message
+ msg = N_("Unknown problem picking up item.");
+ break;
+ }
+ if (localChatTab && config.getBoolValue("showpickupchat"))
+ localChatTab->chatLog(gettext(msg), BY_SERVER);
+
+ if (mMap && config.getBoolValue("showpickupparticle"))
+ {
+ // Show pickup notification
+ addMessageToQueue(gettext(msg), UserPalette::PICKUP_INFO);
+ }
+ }
+ else
+ {
+ std::string str;
+ if (serverVersion > 0)
+ str = itemInfo.getName(color);
+ else
+ str = itemInfo.getName();
+
+ if (config.getBoolValue("showpickupchat") && localChatTab)
+ {
+ // TRANSLATORS: %d is number,
+ // [@@%d|%s@@] - here player can see link to item
+ localChatTab->chatLog(strprintf(ngettext("You picked up %d "
+ "[@@%d|%s@@].", "You picked up %d [@@%d|%s@@].", amount),
+ amount, itemInfo.getId(), str.c_str()), BY_SERVER);
+ }
+
+ if (mMap && config.getBoolValue("showpickupparticle"))
+ {
+ // Show pickup notification
+ if (amount > 1)
+ {
+ addMessageToQueue(strprintf("%d x %s", amount,
+ str.c_str()), UserPalette::PICKUP_INFO);
+ }
+ else
+ {
+ addMessageToQueue(str, UserPalette::PICKUP_INFO);
+ }
+ }
+ }
+}
+
+int LocalPlayer::getAttackRange() const
+{
+ if (mAttackRange > -1)
+ {
+ return mAttackRange;
+ }
+ else
+ {
+ const Item *const weapon = PlayerInfo::getEquipment(EQUIP_FIGHT1_SLOT);
+ if (weapon)
+ {
+ const ItemInfo &info = weapon->getInfo();
+ return info.getAttackRange();
+ }
+ return 48; // unarmed range
+ }
+}
+
+bool LocalPlayer::withinAttackRange(const Being *const target,
+ const bool fixDistance,
+ const int addRange) const
+{
+ if (!target)
+ return false;
+
+ int range = getAttackRange() + addRange;
+ int dx;
+ int dy;
+
+ if (fixDistance && range == 1)
+ range = 2;
+
+#ifdef MANASERV_SUPPORT
+ if (Net::getNetworkType() == ServerInfo::MANASERV)
+ {
+ const Vector &targetPos = target->getPosition();
+ const Vector &pos = getPosition();
+ dx = static_cast<int>(abs(static_cast<int>(targetPos.x - pos.x)));
+ dy = static_cast<int>(abs(static_cast<int>(targetPos.y - pos.y)));
+ }
+ else
+#endif
+ {
+ dx = static_cast<int>(abs(target->getTileX() - mX));
+ dy = static_cast<int>(abs(target->getTileY() - mY));
+ }
+ return !(dx > range || dy > range);
+}
+
+void LocalPlayer::setGotoTarget(Being *const target)
+{
+ if (!target)
+ return;
+
+ mPickUpTarget = nullptr;
+#ifdef MANASERV_SUPPORT
+ if (Net::getNetworkType() == ServerInfo::MANASERV)
+ {
+ mTarget = target;
+ mGoingToTarget = true;
+ const Vector &targetPos = target->getPosition();
+ setDestination(static_cast<int>(targetPos.x),
+ static_cast<int>(targetPos.y));
+ }
+ else
+#endif
+ {
+ setTarget(target);
+ mGoingToTarget = true;
+ setDestination(target->getTileX(), target->getTileY());
+ }
+}
+
+void LocalPlayer::handleStatusEffect(StatusEffect *const effect,
+ const int effectId)
+{
+ Being::handleStatusEffect(effect, effectId);
+
+ if (effect)
+ {
+ effect->deliverMessage();
+ effect->playSFX();
+
+ AnimatedSprite *const sprite = effect->getIcon();
+
+ if (!sprite)
+ {
+ // delete sprite, if necessary
+ for (unsigned int i = 0; i < mStatusEffectIcons.size(); )
+ {
+ if (mStatusEffectIcons[i] == effectId)
+ {
+ mStatusEffectIcons.erase(mStatusEffectIcons.begin() + i);
+ if (miniStatusWindow)
+ miniStatusWindow->eraseIcon(i);
+ }
+ else
+ {
+ i++;
+ }
+ }
+ }
+ else
+ {
+ // replace sprite or append
+ bool found = false;
+ const unsigned int sz = mStatusEffectIcons.size();
+ for (unsigned int i = 0; i < sz; i++)
+ {
+ if (mStatusEffectIcons[i] == effectId)
+ {
+ if (miniStatusWindow)
+ miniStatusWindow->setIcon(i, sprite);
+ found = true;
+ break;
+ }
+ }
+
+ if (!found)
+ { // add new
+ const int offset = static_cast<int>(mStatusEffectIcons.size());
+ if (miniStatusWindow)
+ miniStatusWindow->setIcon(offset, sprite);
+ mStatusEffectIcons.push_back(effectId);
+ }
+ }
+ }
+}
+
+void LocalPlayer::addMessageToQueue(const std::string &message,
+ const int color)
+{
+ if (mMessages.size() < 20)
+ mMessages.push_back(MessagePair(message, color));
+}
+
+void LocalPlayer::optionChanged(const std::string &value)
+{
+ if (value == "showownname")
+ setShowName(config.getBoolValue("showownname"));
+ else if (value == "targetDeadPlayers")
+ mTargetDeadPlayers = config.getBoolValue("targetDeadPlayers");
+ else if (value == "enableBuggyServers")
+ mIsServerBuggy = serverConfig.getBoolValue("enableBuggyServers");
+ else if (value == "syncPlayerMove")
+ mSyncPlayerMove = config.getBoolValue("syncPlayerMove");
+ else if (value == "drawPath")
+ mDrawPath = config.getBoolValue("drawPath");
+ else if (value == "serverAttack")
+ mServerAttack = config.getBoolValue("serverAttack");
+ else if (value == "attackMoving")
+ mAttackMoving = config.getBoolValue("attackMoving");
+ else if (value == "attackNext")
+ mAttackNext = config.getBoolValue("attackNext");
+ else if (value == "showJobExp")
+ mShowJobExp = config.getBoolValue("showJobExp");
+ else if (value == "enableAdvert")
+ mEnableAdvert = config.getBoolValue("enableAdvert");
+ else if (value == "tradebot")
+ mTradebot = config.getBoolValue("tradebot");
+ else if (value == "targetOnlyReachable")
+ mTargetOnlyReachable = config.getBoolValue("targetOnlyReachable");
+}
+
+void LocalPlayer::processEvent(Channels channel,
+ const DepricatedEvent &event)
+{
+ if (channel == CHANNEL_ATTRIBUTES)
+ {
+ if (event.getName() == EVENT_UPDATEATTRIBUTE)
+ {
+ switch (event.getInt("id"))
+ {
+ case PlayerInfo::EXP:
+ {
+ if (event.getInt("oldValue") > event.getInt("newValue"))
+ break;
+
+ const int change = event.getInt("newValue")
+ - event.getInt("oldValue");
+
+ if (change != 0)
+ {
+ // TRANSLATORS: get xp message
+ addMessageToQueue(strprintf("%d %s", change, _("xp")));
+ }
+ break;
+ }
+ case PlayerInfo::LEVEL:
+ mLevel = event.getInt("newValue");
+ break;
+ default:
+ break;
+ };
+ }
+ else if (event.getName() == EVENT_UPDATESTAT)
+ {
+ if (!mShowJobExp)
+ return;
+
+ const int id = event.getInt("id");
+ if (id == Net::getPlayerHandler()->getJobLocation())
+ {
+ const std::pair<int, int> exp
+ = PlayerInfo::getStatExperience(id);
+ if (event.getInt("oldValue1") > exp.first
+ || !event.getInt("oldValue2"))
+ {
+ return;
+ }
+
+ const int change = exp.first - event.getInt("oldValue1");
+ if (change != 0 && mMessages.size() < 20)
+ {
+ if (!mMessages.empty())
+ {
+ MessagePair pair = mMessages.back();
+ // TRANSLATORS: this is normal experience
+ if (pair.first.find(strprintf(" %s",
+ _("xp"))) == pair.first.size()
+ - strlen(_("xp")) - 1)
+ {
+ mMessages.pop_back();
+ // TRANSLATORS: this is job experience
+ pair.first.append(strprintf(", %d %s",
+ change, _("job")));
+ mMessages.push_back(pair);
+ }
+ else
+ {
+ // TRANSLATORS: this is job experience
+ addMessageToQueue(strprintf("%d %s",
+ change, _("job")));
+ }
+ }
+ else
+ {
+ // TRANSLATORS: this is job experience
+ addMessageToQueue(strprintf(
+ "%d %s", change, _("job")));
+ }
+ }
+ }
+ }
+ }
+}
+
+void LocalPlayer::moveTo(const int x, const int y)
+{
+ setDestination(x, y);
+}
+
+void LocalPlayer::move(const int dX, const int dY)
+{
+ mPickUpTarget = nullptr;
+ moveTo(mX + dX, mY + dY);
+}
+
+void LocalPlayer::moveToTarget(int dist)
+{
+ bool gotPos(false);
+ Path debugPath;
+
+ const Vector &playerPos = getPosition();
+ unsigned int limit(0);
+
+ if (dist == -1)
+ {
+ dist = mMoveToTargetType;
+ if (mMoveToTargetType == 0)
+ {
+ dist = 0;
+ }
+ else
+ {
+ switch (mMoveToTargetType)
+ {
+ case 1:
+ dist = 1;
+ break;
+ case 2:
+ dist = 2;
+ break;
+ case 3:
+ dist = 3;
+ break;
+ case 4:
+ dist = 5;
+ break;
+ case 5:
+ dist = 7;
+ break;
+ case 6:
+ case 7:
+ dist = mAttackRange;
+ if (dist == 1 && serverVersion < 1)
+ dist = 2;
+ break;
+ case 8:
+ dist = mAttackRange - 1;
+ if (dist < 1)
+ dist = 1;
+ if (dist == 1 && serverVersion < 1)
+ dist = 2;
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ if (mTarget)
+ {
+ if (mMap)
+ {
+ debugPath = mMap->findPath(static_cast<int>(playerPos.x - 16) / 32,
+ static_cast<int>(playerPos.y - 32) / 32,
+ mTarget->getTileX(), mTarget->getTileY(), getWalkMask(), 0);
+ }
+
+ const unsigned int sz = debugPath.size();
+ if (sz < static_cast<unsigned int>(dist))
+ return;
+ limit = static_cast<int>(sz) - dist;
+ gotPos = true;
+ }
+ else if (mNavigateX || mNavigateY)
+ {
+ debugPath = mNavigatePath;
+ limit = dist;
+ gotPos = true;
+ }
+
+ if (gotPos)
+ {
+ if (dist == 0)
+ {
+ if (mTarget)
+ navigateTo(mTarget);
+ }
+ else
+ {
+ Position pos(0, 0);
+ unsigned int f = 0;
+
+ for (Path::const_iterator i = debugPath.begin(),
+ i_end = debugPath.end();
+ i != i_end && f < limit; ++i, f++)
+ {
+ pos = (*i);
+ }
+ navigateTo(pos.x, pos.y);
+ }
+ }
+ else if (mLastTargetX || mLastTargetY)
+ {
+ navigateTo(mLastTargetX, mLastTargetY);
+ }
+}
+
+void LocalPlayer::moveToHome()
+{
+ mPickUpTarget = nullptr;
+ if ((mX != mCrossX || mY != mCrossY) && mCrossX && mCrossY)
+ {
+ moveTo(mCrossX, mCrossY);
+ }
+ else if (mMap)
+ {
+ const std::map<std::string, Vector>::const_iterator iter =
+ mHomes.find(mMap->getProperty("_realfilename"));
+
+ if (iter != mHomes.end())
+ {
+ const Vector pos = mHomes[(*iter).first];
+ if (mX == pos.x && mY == pos.y)
+ {
+ Net::getPlayerHandler()->setDestination(
+ static_cast<int>(pos.x),
+ static_cast<int>(pos.y),
+ static_cast<int>(mDirection));
+ }
+ else
+ {
+ navigateTo(static_cast<int>(pos.x), static_cast<int>(pos.y));
+ }
+ }
+ }
+}
+
+static const unsigned invertDirectionSize = 5;
+
+void LocalPlayer::changeMode(unsigned *const var, const unsigned limit,
+ const char *const conf,
+ std::string (LocalPlayer::*const func)(),
+ const unsigned def,
+ const bool save)
+{
+ if (!var)
+ return;
+
+ (*var) ++;
+ if (*var >= limit)
+ *var = def;
+ if (save)
+ config.setValue(conf, *var);
+ if (miniStatusWindow)
+ miniStatusWindow->updateStatus();
+ const std::string str = (this->*func)();
+ if (str.size() > 4)
+ debugMsg(str.substr(4));
+}
+
+void LocalPlayer::invertDirection()
+{
+ mMoveState = 0;
+ changeMode(&mInvertDirection, invertDirectionSize, "invertMoveDirection",
+ &LocalPlayer::getInvertDirectionString, 0, false);
+}
+
+static const char *const invertDirectionStrings[] =
+{
+ // TRANSLATORS: move type in status bar
+ N_("(D) default moves"),
+ // TRANSLATORS: move type in status bar
+ N_("(I) invert moves"),
+ // TRANSLATORS: move type in status bar
+ N_("(c) moves with some crazy moves"),
+ // TRANSLATORS: move type in status bar
+ N_("(C) moves with crazy moves"),
+ // TRANSLATORS: move type in status bar
+ N_("(d) double normal + crazy"),
+ // TRANSLATORS: move type in status bar
+ N_("(?) unknown move")
+};
+
+std::string LocalPlayer::getInvertDirectionString()
+{
+ return gettext(getVarItem(&invertDirectionStrings[0],
+ mInvertDirection, invertDirectionSize));
+}
+
+static const unsigned crazyMoveTypeSize = 11;
+
+void LocalPlayer::changeCrazyMoveType()
+{
+ mCrazyMoveState = 0;
+ changeMode(&mCrazyMoveType, crazyMoveTypeSize, "crazyMoveType",
+ &LocalPlayer::getCrazyMoveTypeString, 1);
+}
+
+std::string LocalPlayer::getCrazyMoveTypeString()
+{
+ if (mCrazyMoveType < crazyMoveTypeSize - 1)
+ {
+ // TRANSLATORS: crazy move type in status bar
+ return strprintf(_("(%u) crazy move number %u"),
+ mCrazyMoveType, mCrazyMoveType);
+ }
+ else if (mCrazyMoveType == crazyMoveTypeSize - 1)
+ {
+ // TRANSLATORS: crazy move type in status bar
+ return _("(a) custom crazy move");
+ }
+ else
+ {
+ // TRANSLATORS: crazy move type in status bar
+ return _("(?) crazy move");
+ }
+}
+
+static const unsigned moveToTargetTypeSize = 9;
+
+void LocalPlayer::changeMoveToTargetType()
+{
+ changeMode(&mMoveToTargetType, moveToTargetTypeSize, "moveToTargetType",
+ &LocalPlayer::getMoveToTargetTypeString);
+}
+
+static const char *const moveToTargetTypeStrings[] =
+{
+ // TRANSLATORS: move to target type in status bar
+ N_("(0) default moves to target"),
+ // TRANSLATORS: move to target type in status bar
+ N_("(1) moves to target in distance 1"),
+ // TRANSLATORS: move to target type in status bar
+ N_("(2) moves to target in distance 2"),
+ // TRANSLATORS: move to target type in status bar
+ N_("(3) moves to target in distance 3"),
+ // TRANSLATORS: move to target type in status bar
+ N_("(5) moves to target in distance 5"),
+ // TRANSLATORS: move to target type in status bar
+ N_("(7) moves to target in distance 7"),
+ // TRANSLATORS: move to target type in status bar
+ N_("(A) moves to target in attack range"),
+ // TRANSLATORS: move to target type in status bar
+ N_("(a) archer attack range"),
+ // TRANSLATORS: move to target type in status bar
+ N_("(B) moves to target in attack range - 1"),
+ // TRANSLATORS: move to target type in status bar
+ N_("(?) move to target")
+};
+
+std::string LocalPlayer::getMoveToTargetTypeString()
+{
+ return gettext(getVarItem(&moveToTargetTypeStrings[0],
+ mMoveToTargetType, moveToTargetTypeSize));
+}
+
+static const unsigned followModeSize = 4;
+
+void LocalPlayer::changeFollowMode()
+{
+ changeMode(&mFollowMode, followModeSize, "followMode",
+ &LocalPlayer::getFollowModeString);
+}
+
+static const char *const followModeStrings[] =
+{
+ // TRANSLATORS: folow mode in status bar
+ N_("(D) default follow"),
+ // TRANSLATORS: folow mode in status bar
+ N_("(R) relative follow"),
+ // TRANSLATORS: folow mode in status bar
+ N_("(M) mirror follow"),
+ // TRANSLATORS: folow mode in status bar
+ N_("(P) pet follow"),
+ // TRANSLATORS: folow mode in status bar
+ N_("(?) unknown follow")
+};
+
+std::string LocalPlayer::getFollowModeString()
+{
+ return gettext(getVarItem(&followModeStrings[0],
+ mFollowMode, followModeSize));
+}
+
+const unsigned attackWeaponTypeSize = 4;
+
+void LocalPlayer::changeAttackWeaponType()
+{
+ changeMode(&mAttackWeaponType, attackWeaponTypeSize, "attackWeaponType",
+ &LocalPlayer::getAttackWeaponTypeString, 1);
+}
+
+static const char *attackWeaponTypeStrings[] =
+{
+ // TRANSLATORS: switch attack type in status bar
+ N_("(?) attack"),
+ // TRANSLATORS: switch attack type in status bar
+ N_("(D) default attack"),
+ // TRANSLATORS: switch attack type in status bar
+ N_("(s) switch attack without shield"),
+ // TRANSLATORS: switch attack type in status bar
+ N_("(S) switch attack with shield"),
+ // TRANSLATORS: switch attack type in status bar
+ N_("(?) attack")
+};
+
+std::string LocalPlayer::getAttackWeaponTypeString()
+{
+ return gettext(getVarItem(&attackWeaponTypeStrings[0],
+ mAttackWeaponType, attackWeaponTypeSize));
+}
+
+const unsigned attackTypeSize = 4;
+
+void LocalPlayer::changeAttackType()
+{
+ changeMode(&mAttackType, attackTypeSize, "attackType",
+ &LocalPlayer::getAttackTypeString);
+}
+
+static const char *const attackTypeStrings[] =
+{
+ // TRANSLATORS: attack type in status bar
+ N_("(D) default attack"),
+ // TRANSLATORS: attack type in status bar
+ N_("(G) go and attack"),
+ // TRANSLATORS: attack type in status bar
+ N_("(A) go, attack, pickup"),
+ // TRANSLATORS: attack type in status bar
+ N_("(d) without auto attack"),
+ // TRANSLATORS: attack type in status bar
+ N_("(?) attack")
+};
+
+std::string LocalPlayer::getAttackTypeString()
+{
+ return gettext(getVarItem(&attackTypeStrings[0],
+ mAttackType, attackTypeSize));
+}
+
+const unsigned quickDropCounterSize = 31;
+
+void LocalPlayer::changeQuickDropCounter()
+{
+ changeMode(&mQuickDropCounter, quickDropCounterSize, "quickDropCounter",
+ &LocalPlayer::getQuickDropCounterString, 1);
+}
+
+std::string LocalPlayer::getQuickDropCounterString()
+{
+ if (mQuickDropCounter > 9)
+ {
+ return strprintf("(%c) drop counter %u", static_cast<signed char>(
+ 'a' + mQuickDropCounter - 10), mQuickDropCounter);
+ }
+ else
+ {
+ return strprintf("(%u) drop counter %u",
+ mQuickDropCounter, mQuickDropCounter);
+ }
+}
+
+void LocalPlayer::setQuickDropCounter(const int n)
+{
+ if (n < 1 || n >= static_cast<signed>(quickDropCounterSize))
+ return;
+ mQuickDropCounter = n;
+ config.setValue("quickDropCounter", mQuickDropCounter);
+ if (miniStatusWindow)
+ miniStatusWindow->updateStatus();
+}
+
+const unsigned pickUpTypeSize = 7;
+
+void LocalPlayer::changePickUpType()
+{
+ changeMode(&mPickUpType, pickUpTypeSize, "pickUpType",
+ &LocalPlayer::getPickUpTypeString);
+}
+
+static const char *const pickUpTypeStrings[] =
+{
+ // TRANSLATORS: pickup size in status bar
+ N_("(S) small pick up 1x1 cells"),
+ // TRANSLATORS: pickup size in status bar
+ N_("(D) default pick up 2x1 cells"),
+ // TRANSLATORS: pickup size in status bar
+ N_("(F) forward pick up 2x3 cells"),
+ // TRANSLATORS: pickup size in status bar
+ N_("(3) pick up 3x3 cells"),
+ // TRANSLATORS: pickup size in status bar
+ N_("(g) go and pick up in distance 4"),
+ // TRANSLATORS: pickup size in status bar
+ N_("(G) go and pick up in distance 8"),
+ // TRANSLATORS: pickup size in status bar
+ N_("(A) go and pick up in max distance"),
+ // TRANSLATORS: pickup size in status bar
+ N_("(?) pick up")
+};
+
+std::string LocalPlayer::getPickUpTypeString()
+{
+ return gettext(getVarItem(&pickUpTypeStrings[0],
+ mPickUpType, pickUpTypeSize));
+}
+
+const unsigned debugPathSize = 5;
+
+static const char *const debugPathStrings[] =
+{
+ // TRANSLATORS: map view type in status bar
+ N_("(N) normal map view"),
+ // TRANSLATORS: map view type in status bar
+ N_("(D) debug map view"),
+ // TRANSLATORS: map view type in status bar
+ N_("(u) ultra map view"),
+ // TRANSLATORS: map view type in status bar
+ N_("(U) ultra map view 2"),
+ // TRANSLATORS: map view type in status bar
+ N_("(e) empty map view"),
+ // TRANSLATORS: map view type in status bar
+ N_("(b) black & white map view")
+};
+
+std::string LocalPlayer::getDebugPathString() const
+{
+ return gettext(getVarItem(&debugPathStrings[0],
+ viewport->getDebugPath(), debugPathSize));
+}
+
+const unsigned magicAttackSize = 5;
+
+void LocalPlayer::switchMagicAttack()
+{
+ changeMode(&mMagicAttackType, magicAttackSize, "magicAttackType",
+ &LocalPlayer::getMagicAttackString);
+}
+
+static const char *const magicAttackStrings[] =
+{
+ // TRANSLATORS: magic attack in status bar
+ N_("(f) use #flar for magic attack"),
+ // TRANSLATORS: magic attack in status bar
+ N_("(c) use #chiza for magic attack"),
+ // TRANSLATORS: magic attack in status bar
+ N_("(I) use #ingrav for magic attack"),
+ // TRANSLATORS: magic attack in status bar
+ N_("(F) use #frillyar for magic attack"),
+ // TRANSLATORS: magic attack in status bar
+ N_("(U) use #upmarmu for magic attack"),
+ // TRANSLATORS: magic attack in status bar
+ N_("(?) magic attack")
+};
+
+std::string LocalPlayer::getMagicAttackString()
+{
+ return gettext(getVarItem(&magicAttackStrings[0],
+ mMagicAttackType, magicAttackSize));
+}
+
+const unsigned pvpAttackSize = 4;
+
+void LocalPlayer::switchPvpAttack()
+{
+ changeMode(&mPvpAttackType, pvpAttackSize, "pvpAttackType",
+ &LocalPlayer::getPvpAttackString);
+}
+
+static const char *const pvpAttackStrings[] =
+{
+ // TRANSLATORS: player attack type in status bar
+ N_("(a) attack all players"),
+ // TRANSLATORS: player attack type in status bar
+ N_("(f) attack all except friends"),
+ // TRANSLATORS: player attack type in status bar
+ N_("(b) attack bad relations"),
+ // TRANSLATORS: player attack type in status bar
+ N_("(d) don't attack players"),
+ // TRANSLATORS: player attack type in status bar
+ N_("(?) pvp attack")
+};
+
+std::string LocalPlayer::getPvpAttackString()
+{
+ return gettext(getVarItem(&pvpAttackStrings[0],
+ mPvpAttackType, pvpAttackSize));
+}
+
+const unsigned imitationModeSize = 2;
+
+void LocalPlayer::changeImitationMode()
+{
+ changeMode(&mImitationMode, imitationModeSize, "imitationMode",
+ &LocalPlayer::getImitationModeString);
+}
+
+static const char *const imitationModeStrings[] =
+{
+ // TRANSLATORS: imitation type in status bar
+ N_("(D) default imitation"),
+ // TRANSLATORS: imitation type in status bar
+ N_("(O) outfits imitation"),
+ // TRANSLATORS: imitation type in status bar
+ N_("(?) imitation")
+};
+
+std::string LocalPlayer::getImitationModeString()
+{
+ return gettext(getVarItem(&imitationModeStrings[0],
+ mImitationMode, imitationModeSize));
+}
+
+const unsigned awayModeSize = 2;
+
+void LocalPlayer::changeAwayMode()
+{
+ mAwayMode = !mAwayMode;
+ mAfkTime = 0;
+ mInactive = false;
+ updateName();
+ if (miniStatusWindow)
+ miniStatusWindow->updateStatus();
+ if (mAwayMode)
+ {
+ if (chatWindow)
+ chatWindow->clearAwayLog();
+
+ cancelFollow();
+ navigateClean();
+ if (outfitWindow)
+ outfitWindow->wearAwayOutfit();
+ // TRANSLATORS: away message box header
+ mAwayDialog = new OkDialog(_("Away"),
+ config.getStringValue("afkMessage"),
+ DIALOG_SILENCE, true, false);
+ mAwayDialog->addActionListener(mAwayListener);
+ soundManager.volumeOff();
+ addAfkEffect();
+ }
+ else
+ {
+ mAwayDialog = nullptr;
+ soundManager.volumeRestore();
+ if (chatWindow)
+ {
+ chatWindow->displayAwayLog();
+ chatWindow->clearAwayLog();
+ }
+ removeAfkEffect();
+ }
+}
+
+static const char *awayModeStrings[] =
+{
+ // TRANSLATORS: away type in status bar
+ N_("(O) on keyboard"),
+ // TRANSLATORS: away type in status bar
+ N_("(A) away"),
+ // TRANSLATORS: away type in status bar
+ N_("(?) away")
+};
+
+std::string LocalPlayer::getAwayModeString()
+{
+ return gettext(getVarItem(&awayModeStrings[0],
+ mAwayMode, awayModeSize));
+}
+
+const unsigned cameraModeSize = 2;
+
+static const char *cameraModeStrings[] =
+{
+ // TRANSLATORS: camera mode in status bar
+ N_("(G) game camera mode"),
+ // TRANSLATORS: camera mode in status bar
+ N_("(F) free camera mode"),
+ // TRANSLATORS: camera mode in status bar
+ N_("(?) away")
+};
+
+std::string LocalPlayer::getCameraModeString() const
+{
+ return gettext(getVarItem(&cameraModeStrings[0],
+ viewport->getCameraMode(), cameraModeSize));
+}
+
+const unsigned gameModifiersSize = 2;
+
+void LocalPlayer::switchGameModifiers()
+{
+ mDisableGameModifiers = !mDisableGameModifiers;
+ config.setValue("disableGameModifiers", mDisableGameModifiers);
+ miniStatusWindow->updateStatus();
+
+ const std::string str = getGameModifiersString();
+ if (str.size() > 4)
+ debugMsg(str.substr(4));
+}
+
+static const char *const gameModifiersStrings[] =
+{
+ // TRANSLATORS: game modifiers state in status bar
+ N_("Game modifiers are enabled"),
+ // TRANSLATORS: game modifiers state in status bar
+ N_("Game modifiers are disabled"),
+ // TRANSLATORS: game modifiers state in status bar
+ N_("Game modifiers are unknown")
+};
+
+std::string LocalPlayer::getGameModifiersString()
+{
+ return gettext(getVarItem(&gameModifiersStrings[0],
+ mDisableGameModifiers, gameModifiersSize));
+}
+
+
+void LocalPlayer::changeEquipmentBeforeAttack(const Being *const target) const
+{
+ if (mAttackWeaponType == 1 || !target || !PlayerInfo::getInventory())
+ return;
+
+ bool allowSword = false;
+ const int dx = target->getTileX() - mX;
+ const int dy = target->getTileY() - mY;
+ const Item *item = nullptr;
+
+ if (dx * dx + dy * dy > 80)
+ return;
+
+ if (dx * dx + dy * dy < 8)
+ allowSword = true;
+
+ const Inventory *const inv = PlayerInfo::getInventory();
+ if (!inv)
+ return;
+
+ // if attack distance for sword
+ if (allowSword)
+ {
+ // finding sword
+ item = inv->findItem(571, 0);
+
+ if (!item)
+ item = inv->findItem(570, 0);
+
+ if (!item)
+ item = inv->findItem(579, 0);
+
+ if (!item)
+ item = inv->findItem(867, 0);
+
+ if (!item)
+ item = inv->findItem(536, 0);
+
+ if (!item)
+ item = inv->findItem(758, 0);
+
+ // no swords
+ if (!item)
+ return;
+
+ // if sword not equiped
+ if (!item->isEquipped())
+ Net::getInventoryHandler()->equipItem(item);
+
+ // if need equip shield too
+ if (mAttackWeaponType == 3)
+ {
+ // finding shield
+ item = inv->findItem(601, 0);
+ if (!item)
+ item = inv->findItem(602, 0);
+ if (item && !item->isEquipped())
+ Net::getInventoryHandler()->equipItem(item);
+ }
+ }
+ // big distance. allowed only bow
+ else
+ {
+ // finding bow
+ item = inv->findItem(545, 0);
+
+ if (!item)
+ item = inv->findItem(530, 0);
+
+ // no bow
+ if (!item)
+ return;
+
+ if (!item->isEquipped())
+ Net::getInventoryHandler()->equipItem(item);
+ }
+}
+
+void LocalPlayer::crazyMove()
+{
+ const bool oldDisableCrazyMove = mDisableCrazyMove;
+ mDisableCrazyMove = true;
+ switch (mCrazyMoveType)
+ {
+ case 1:
+ crazyMove1();
+ break;
+ case 2:
+ crazyMove2();
+ break;
+ case 3:
+ crazyMove3();
+ break;
+ case 4:
+ crazyMove4();
+ break;
+ case 5:
+ crazyMove5();
+ break;
+ case 6:
+ crazyMove6();
+ break;
+ case 7:
+ crazyMove7();
+ break;
+ case 8:
+ crazyMove8();
+ break;
+ case 9:
+ crazyMove9();
+ break;
+ case 10:
+ crazyMoveA();
+ break;
+ default:
+ break;
+ }
+ mDisableCrazyMove = oldDisableCrazyMove;
+}
+
+void LocalPlayer::crazyMove1()
+{
+ if (mAction == MOVE)
+ return;
+
+// if (!client->limitPackets(PACKET_DIRECTION))
+// return;
+
+ if (mDirection == Being::UP)
+ {
+ setWalkingDir(Being::UP);
+ setDirection(Being::LEFT);
+ Net::getPlayerHandler()->setDirection(Being::LEFT);
+ }
+ else if (mDirection == Being::LEFT)
+ {
+ setWalkingDir(Being::LEFT);
+ setDirection(Being::DOWN);
+ Net::getPlayerHandler()->setDirection(Being::DOWN);
+ }
+ else if (mDirection == Being::DOWN)
+ {
+ setWalkingDir(Being::DOWN);
+ setDirection(Being::RIGHT);
+ Net::getPlayerHandler()->setDirection(Being::RIGHT);
+ }
+ else if (mDirection == Being::RIGHT)
+ {
+ setWalkingDir(Being::RIGHT);
+ setDirection(Being::UP);
+ Net::getPlayerHandler()->setDirection(Being::UP);
+ }
+}
+
+void LocalPlayer::crazyMove2()
+{
+ if (mAction == MOVE)
+ return;
+
+// if (!client->limitPackets(PACKET_DIRECTION))
+// return;
+
+ if (mDirection == Being::UP)
+ {
+ setWalkingDir(Being::UP | Being::LEFT);
+ setDirection(Being::RIGHT);
+ Net::getPlayerHandler()->setDirection(Being::DOWN | Being::RIGHT);
+ }
+ else if (mDirection == Being::RIGHT)
+ {
+ setWalkingDir(Being::UP | Being::RIGHT);
+ setDirection(Being::DOWN);
+ Net::getPlayerHandler()->setDirection(Being::DOWN | Being::LEFT);
+ }
+ else if (mDirection == Being::DOWN)
+ {
+ setWalkingDir(Being::DOWN | Being::RIGHT);
+ setDirection(Being::LEFT);
+ Net::getPlayerHandler()->setDirection(Being::UP | Being::LEFT);
+ }
+ else if (mDirection == Being::LEFT)
+ {
+ setWalkingDir(Being::DOWN | Being::LEFT);
+ setDirection(Being::UP);
+ Net::getPlayerHandler()->setDirection(Being::UP | Being::RIGHT);
+ }
+}
+
+void LocalPlayer::crazyMove3()
+{
+ if (mAction == MOVE)
+ return;
+
+ switch (mCrazyMoveState)
+ {
+ case 0:
+ move(1, 1);
+ mCrazyMoveState = 1;
+ break;
+ case 1:
+ move(1, -1);
+ mCrazyMoveState = 2;
+ break;
+ case 2:
+ move(-1, -1);
+ mCrazyMoveState = 3;
+ break;
+ case 3:
+ move(-1, 1);
+ mCrazyMoveState = 0;
+ break;
+ default:
+ break;
+ }
+
+// if (!client->limitPackets(PACKET_DIRECTION))
+// return;
+
+ setDirection(Being::DOWN);
+ Net::getPlayerHandler()->setDirection(Being::DOWN);
+}
+
+void LocalPlayer::crazyMove4()
+{
+ if (mAction == MOVE)
+ return;
+
+ switch (mCrazyMoveState)
+ {
+ case 0:
+ move(7, 0);
+ mCrazyMoveState = 1;
+ break;
+ case 1:
+ move(-7, 0);
+ mCrazyMoveState = 0;
+ break;
+ default:
+ break;
+ }
+}
+
+void LocalPlayer::crazyMove5()
+{
+ if (mAction == MOVE)
+ return;
+
+ switch (mCrazyMoveState)
+ {
+ case 0:
+ move(0, 7);
+ mCrazyMoveState = 1;
+ break;
+ case 1:
+ move(0, -7);
+ mCrazyMoveState = 0;
+ break;
+ default:
+ break;
+ }
+}
+
+void LocalPlayer::crazyMove6()
+{
+ if (mAction == MOVE)
+ return;
+
+ switch (mCrazyMoveState)
+ {
+ case 0:
+ move(3, 0);
+ mCrazyMoveState = 1;
+ break;
+ case 1:
+ move(2, -2);
+ mCrazyMoveState = 2;
+ break;
+ case 2:
+ move(0, -3);
+ mCrazyMoveState = 3;
+ break;
+ case 3:
+ move(-2, -2);
+ mCrazyMoveState = 4;
+ break;
+ case 4:
+ move(-3, 0);
+ mCrazyMoveState = 5;
+ break;
+ case 5:
+ move(-2, 2);
+ mCrazyMoveState = 6;
+ break;
+ case 6:
+ move(0, 3);
+ mCrazyMoveState = 7;
+ break;
+ case 7:
+ move(2, 2);
+ mCrazyMoveState = 0;
+ break;
+ default:
+ break;
+ }
+}
+
+void LocalPlayer::crazyMove7()
+{
+ if (mAction == MOVE)
+ return;
+
+ switch (mCrazyMoveState)
+ {
+ case 0:
+ move(1, 1);
+ mCrazyMoveState = 1;
+ break;
+ case 1:
+ move(-1, 1);
+ mCrazyMoveState = 2;
+ break;
+ case 2:
+ move(-1, -1);
+ mCrazyMoveState = 3;
+ break;
+ case 3:
+ move(1, -1);
+ mCrazyMoveState = 0;
+ break;
+ default:
+ break;
+ }
+}
+
+void LocalPlayer::crazyMove8()
+{
+ if (mAction == MOVE || !mMap)
+ return;
+ int idx = 0;
+ const int dist = 1;
+
+// look
+// up, ri,do,le
+ static const int movesX[][4] =
+ {
+ {-1, 0, 1, 0}, // move left
+ { 0, 1, 0, -1}, // move up
+ { 1, 0, -1, 0}, // move right
+ { 0, -1, 0, 1} // move down
+ };
+
+// look
+// up, ri,do,le
+ static const int movesY[][4] =
+ {
+ { 0, -1, 0, 1}, // move left
+ {-1, 0, 1, 0}, // move up
+ { 0, 1, 0, -1}, // move right
+ { 1, 0, -1, 0} // move down
+ };
+
+ if (mDirection == Being::UP)
+ idx = 0;
+ else if (mDirection == Being::RIGHT)
+ idx = 1;
+ else if (mDirection == Being::DOWN)
+ idx = 2;
+ else if (mDirection == Being::LEFT)
+ idx = 3;
+
+
+ int mult = 1;
+ const unsigned char walkMask = getWalkMask();
+ if (mMap->getWalk(mX + movesX[idx][0],
+ mY + movesY[idx][0], walkMask))
+ {
+ while (mMap->getWalk(mX + movesX[idx][0] * mult,
+ mY + movesY[idx][0] * mult,
+ getWalkMask()) && mult <= dist)
+ {
+ mult ++;
+ }
+ move(movesX[idx][0] * (mult - 1), movesY[idx][0] * (mult - 1));
+ }
+ else if (mMap->getWalk(mX + movesX[idx][1],
+ mY + movesY[idx][1], walkMask))
+ {
+ while (mMap->getWalk(mX + movesX[idx][1] * mult,
+ mY + movesY[idx][1] * mult,
+ getWalkMask()) && mult <= dist)
+ {
+ mult ++;
+ }
+ move(movesX[idx][1] * (mult - 1), movesY[idx][1] * (mult - 1));
+ }
+ else if (mMap->getWalk(mX + movesX[idx][2],
+ mY + movesY[idx][2], walkMask))
+ {
+ while (mMap->getWalk(mX + movesX[idx][2] * mult,
+ mY + movesY[idx][2] * mult,
+ getWalkMask()) && mult <= dist)
+ {
+ mult ++;
+ }
+ move(movesX[idx][2] * (mult - 1), movesY[idx][2] * (mult - 1));
+ }
+ else if (mMap->getWalk(mX + movesX[idx][3],
+ mY + movesY[idx][3], walkMask))
+ {
+ while (mMap->getWalk(mX + movesX[idx][3] * mult,
+ mY + movesY[idx][3] * mult,
+ getWalkMask()) && mult <= dist)
+ {
+ mult ++;
+ }
+ move(movesX[idx][3] * (mult - 1), movesY[idx][3] * (mult - 1));
+ }
+}
+
+void LocalPlayer::crazyMove9()
+{
+ int dx = 0;
+ int dy = 0;
+
+ if (mAction == MOVE)
+ return;
+
+ switch (mCrazyMoveState)
+ {
+ case 0:
+ switch (mDirection)
+ {
+ case UP : dy = -1; break;
+ case DOWN : dy = 1; break;
+ case LEFT : dx = -1; break;
+ case RIGHT: dx = 1; break;
+ default: break;
+ }
+ move(dx, dy);
+ mCrazyMoveState = 1;
+ break;
+ case 1:
+ mCrazyMoveState = 2;
+ if (!allowAction())
+ return;
+ Net::getPlayerHandler()->changeAction(SIT);
+ break;
+ case 2:
+ mCrazyMoveState = 3;
+ break;
+ case 3:
+ mCrazyMoveState = 0;
+ break;
+ default:
+ break;
+ }
+}
+
+void LocalPlayer::crazyMoveA()
+{
+ const std::string mMoveProgram(config.getStringValue("crazyMoveProgram"));
+
+ if (mAction == MOVE)
+ return;
+
+ if (mMoveProgram.empty())
+ return;
+
+ if (mCrazyMoveState >= mMoveProgram.length())
+ mCrazyMoveState = 0;
+
+ // move command
+ if (mMoveProgram[mCrazyMoveState] == 'm')
+ {
+ mCrazyMoveState ++;
+ if (mCrazyMoveState < mMoveProgram.length())
+ {
+ int dx = 0;
+ int dy = 0;
+
+ signed char param = mMoveProgram[mCrazyMoveState++];
+ if (param == '?')
+ {
+ const char cmd[] = {'l', 'r', 'u', 'd'};
+ srand(tick_time);
+ param = cmd[rand() % 4];
+ }
+ switch (param)
+ {
+ case 'd':
+ move(0, 1);
+ break;
+ case 'u':
+ move(0, -1);
+ break;
+ case 'l':
+ move(-1, 0);
+ break;
+ case 'r':
+ move(1, 0);
+ break;
+ case 'f':
+ switch (mDirection)
+ {
+ case UP : dy = -1; break;
+ case DOWN : dy = 1; break;
+ case LEFT : dx = -1; break;
+ case RIGHT: dx = 1; break;
+ default: break;
+ }
+ move(dx, dy);
+ break;
+ case 'b':
+ switch (mDirection)
+ {
+ case UP : dy = 1; break;
+ case DOWN : dy = -1; break;
+ case LEFT : dx = 1; break;
+ case RIGHT: dx = -1; break;
+ default: break;
+ }
+ move(dx, dy);
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ // direction command
+ else if (mMoveProgram[mCrazyMoveState] == 'd')
+ {
+ mCrazyMoveState ++;
+
+ if (mCrazyMoveState < mMoveProgram.length())
+ {
+ signed char param = mMoveProgram[mCrazyMoveState++];
+ if (param == '?')
+ {
+ const char cmd[] = {'l', 'r', 'u', 'd'};
+ srand(tick_time);
+ param = cmd[rand() % 4];
+ }
+ switch (param)
+ {
+ case 'd':
+
+// if (client->limitPackets(PACKET_DIRECTION))
+ {
+ setDirection(Being::DOWN);
+ Net::getPlayerHandler()->setDirection(Being::DOWN);
+ }
+ break;
+ case 'u':
+// if (client->limitPackets(PACKET_DIRECTION))
+ {
+ setDirection(Being::UP);
+ Net::getPlayerHandler()->setDirection(Being::UP);
+ }
+ break;
+ case 'l':
+// if (client->limitPackets(PACKET_DIRECTION))
+ {
+ setDirection(Being::LEFT);
+ Net::getPlayerHandler()->setDirection(Being::LEFT);
+ }
+ break;
+ case 'r':
+// if (client->limitPackets(PACKET_DIRECTION))
+ {
+ setDirection(Being::RIGHT);
+ Net::getPlayerHandler()->setDirection(Being::RIGHT);
+ }
+ break;
+ case 'L':
+// if (client->limitPackets(PACKET_DIRECTION))
+ {
+ uint8_t dir = 0;
+ switch (mDirection)
+ {
+ case UP : dir = Being::LEFT; break;
+ case DOWN : dir = Being::RIGHT; break;
+ case LEFT : dir = Being::DOWN; break;
+ case RIGHT : dir = Being::UP; break;
+ default: break;
+ }
+ setDirection(dir);
+ Net::getPlayerHandler()->setDirection(dir);
+ }
+ break;
+ case 'R':
+// if (client->limitPackets(PACKET_DIRECTION))
+ {
+ uint8_t dir = 0;
+ switch (mDirection)
+ {
+ case UP : dir = Being::RIGHT; break;
+ case DOWN : dir = Being::LEFT; break;
+ case LEFT : dir = Being::UP; break;
+ case RIGHT : dir = Being::DOWN; break;
+ default: break;
+ }
+ setDirection(dir);
+ Net::getPlayerHandler()->setDirection(dir);
+ }
+ break;
+ case 'b':
+// if (client->limitPackets(PACKET_DIRECTION))
+ {
+ uint8_t dir = 0;
+ switch (mDirection)
+ {
+ case UP : dir = Being::DOWN; break;
+ case DOWN : dir = Being::UP; break;
+ case LEFT : dir = Being::RIGHT; break;
+ case RIGHT : dir = Being::LEFT; break;
+ default: break;
+ }
+ setDirection(dir);
+ Net::getPlayerHandler()->setDirection(dir);
+ }
+ break;
+ case '0':
+ dropShortcut->dropFirst();
+ break;
+ case 'a':
+ dropShortcut->dropItems();
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ // sit command
+ else if (mMoveProgram[mCrazyMoveState] == 's')
+ {
+ mCrazyMoveState ++;
+ if (toggleSit())
+ mCrazyMoveState ++;
+ }
+ // wear outfits
+ else if (mMoveProgram[mCrazyMoveState] == 'o')
+ {
+ mCrazyMoveState ++;
+ if (mCrazyMoveState < mMoveProgram.length())
+ {
+ // wear next outfit
+ if (mMoveProgram[mCrazyMoveState] == 'n')
+ {
+ mCrazyMoveState ++;
+ outfitWindow->wearNextOutfit();
+ }
+ // wear previous outfit
+ else if (mMoveProgram[mCrazyMoveState] == 'p')
+ {
+ mCrazyMoveState ++;
+ outfitWindow->wearPreviousOutfit();
+ }
+ }
+ }
+ // pause
+ else if (mMoveProgram[mCrazyMoveState] == 'w')
+ {
+ mCrazyMoveState ++;
+ }
+ // pick up
+ else if (mMoveProgram[mCrazyMoveState] == 'p')
+ {
+ mCrazyMoveState ++;
+ pickUpItems();
+ }
+ // emote
+ else if (mMoveProgram[mCrazyMoveState] == 'e')
+ {
+ mCrazyMoveState ++;
+ const signed char emo = mMoveProgram[mCrazyMoveState];
+ if (emo == '?')
+ {
+ srand(tick_time);
+ emote(static_cast<unsigned char>(1 + (rand() % 13)));
+ }
+ else
+ {
+ if (emo >= '0' && emo <= '9')
+ emote(static_cast<unsigned char>(emo - '0' + 1));
+ else if (emo >= 'a' && emo <= 'd')
+ emote(static_cast<unsigned char>(emo - 'a' + 11));
+ }
+
+ mCrazyMoveState ++;
+ }
+ else
+ {
+ mCrazyMoveState ++;
+ }
+
+ if (mCrazyMoveState >= mMoveProgram.length())
+ mCrazyMoveState = 0;
+}
+
+bool LocalPlayer::isReachable(Being *const being,
+ const int maxCost)
+{
+ if (!being || !mMap)
+ return false;
+
+ if (being->isReachable() == Being::REACH_NO)
+ return false;
+
+ if (being->getTileX() == mX
+ && being->getTileY() == mY)
+ {
+ being->setDistance(0);
+ being->setIsReachable(Being::REACH_YES);
+ return true;
+ }
+ else if (being->getTileX() - 1 <= mX
+ && being->getTileX() + 1 >= mX
+ && being->getTileY() - 1 <= mY
+ && being->getTileY() + 1 >= mY)
+ {
+ being->setDistance(1);
+ being->setIsReachable(Being::REACH_YES);
+ return true;
+ }
+
+ const Vector &playerPos = getPosition();
+
+ const Path debugPath = mMap->findPath(
+ static_cast<int>(playerPos.x - 16) / 32,
+ static_cast<int>(playerPos.y - 32) / 32,
+ being->getTileX(), being->getTileY(), getWalkMask(), maxCost);
+
+ being->setDistance(static_cast<int>(debugPath.size()));
+ if (!debugPath.empty())
+ {
+ being->setIsReachable(Being::REACH_YES);
+ return true;
+ }
+ else
+ {
+ being->setIsReachable(Being::REACH_NO);
+ return false;
+ }
+}
+
+bool LocalPlayer::isReachable(const int x, const int y,
+ const bool allowCollision) const
+{
+ const WalkLayer *const walk = mMap->getWalkLayer();
+ if (!walk)
+ return false;
+ int num = walk->getDataAt(x, y);
+ if (allowCollision && num < 0)
+ num = -num;
+
+ return walk->getDataAt(mX, mY) == num;
+}
+
+bool LocalPlayer::pickUpItems(int pickUpType)
+{
+ if (!actorSpriteManager)
+ return false;
+
+ bool status = false;
+ int x = mX;
+ int y = mY;
+
+ // first pick up item on player position
+ FloorItem *item =
+ actorSpriteManager->findItem(x, y);
+ if (item)
+ status = pickUp(item);
+
+ if (pickUpType == 0)
+ pickUpType = mPickUpType;
+
+ if (pickUpType == 0)
+ return status;
+
+ int x1, y1, x2, y2;
+ switch (pickUpType)
+ {
+ case 1:
+ switch (mDirection)
+ {
+ case UP : --y; break;
+ case DOWN : ++y; break;
+ case LEFT : --x; break;
+ case RIGHT: ++x; break;
+ default: break;
+ }
+ item = actorSpriteManager->findItem(x, y);
+ if (item)
+ status = pickUp(item);
+ break;
+ case 2:
+ switch (mDirection)
+ {
+ case UP : x1 = x - 1; y1 = y - 1; x2 = x + 1; y2 = y; break;
+ case DOWN : x1 = x - 1; y1 = y; x2 = x + 1; y2 = y + 1; break;
+ case LEFT : x1 = x - 1; y1 = y - 1; x2 = x; y2 = y + 1; break;
+ case RIGHT: x1 = x; y1 = y - 1; x2 = x + 1; y2 = y + 1; break;
+ default: x1 = x; x2 = x; y1 = y; y2 = y; break;
+ }
+ if (actorSpriteManager->pickUpAll(x1, y1, x2, y2))
+ status = true;
+ break;
+ case 3:
+ if (actorSpriteManager->pickUpAll(x - 1, y - 1, x + 1, y + 1))
+ status = true;
+ break;
+
+ case 4:
+ if (!actorSpriteManager->pickUpAll(x - 1, y - 1, x + 1, y + 1))
+ {
+ if (actorSpriteManager->pickUpNearest(x, y, 4))
+ status = true;
+ }
+ else
+ {
+ status = true;
+ }
+ break;
+
+ case 5:
+ if (!actorSpriteManager->pickUpAll(x - 1, y - 1, x + 1, y + 1))
+ {
+ if (actorSpriteManager->pickUpNearest(x, y, 8))
+ status = true;
+ }
+ else
+ {
+ status = true;
+ }
+ break;
+
+ case 6:
+ if (!actorSpriteManager->pickUpAll(x - 1, y - 1, x + 1, y + 1))
+ {
+ if (actorSpriteManager->pickUpNearest(x, y, 90))
+ status = true;
+ }
+ else
+ {
+ status = true;
+ }
+ break;
+
+ default:
+ break;
+ }
+ return status;
+}
+
+
+void LocalPlayer::moveByDirection(const unsigned char dir)
+{
+ int dx = 0, dy = 0;
+#ifdef MANASERV_SUPPORT
+ if (dir & UP)
+ dy -= 32;
+ if (dir & DOWN)
+ dy += 32;
+ if (dir & LEFT)
+ dx -= 32;
+ if (dir & RIGHT)
+ dx += 32;
+#else
+ if (dir & UP)
+ dy--;
+ if (dir & DOWN)
+ dy++;
+ if (dir & LEFT)
+ dx--;
+ if (dir & RIGHT)
+ dx++;
+#endif
+
+ move(dx, dy);
+}
+
+void LocalPlayer::specialMove(const unsigned char direction)
+{
+ if (direction && (mNavigateX || mNavigateY))
+ navigateClean();
+
+ if (direction && (mInvertDirection >= 2
+ && mInvertDirection <= 4)
+ && !mIsServerBuggy)
+ {
+ if (mAction == MOVE)
+ return;
+
+ int max;
+
+ if (mInvertDirection == 2)
+ max = 5;
+ else if (mInvertDirection == 4)
+ max = 1;
+ else
+ max = 3;
+
+ if (getMoveState() < max)
+ {
+ moveByDirection(direction);
+ mMoveState ++;
+ }
+ else
+ {
+ mMoveState = 0;
+ crazyMove();
+ }
+ }
+ else
+ {
+ setWalkingDir(direction);
+ }
+}
+
+void LocalPlayer::debugMsg(const std::string &str) const
+{
+ if (debugChatTab)
+ debugChatTab->chatLog(str);
+}
+
+void LocalPlayer::magicAttack() const
+{
+ if (!chatWindow || !isAlive()
+ || !Net::getPlayerHandler()->canUseMagic())
+ {
+ return;
+ }
+
+ switch (mMagicAttackType)
+ {
+ // flar W00
+ case 0:
+ tryMagic("#flar", 1, 0, 10);
+ break;
+ // chiza W01
+ case 1:
+ tryMagic("#chiza", 1, 0, 9);
+ break;
+ // ingrav W10
+ case 2:
+ tryMagic("#ingrav", 2, 2, 20);
+ break;
+ // frillyar W11
+ case 3:
+ tryMagic("#frillyar", 2, 2, 25);
+ break;
+ // upmarmu W12
+ case 4:
+ tryMagic("#upmarmu", 2, 2, 20);
+ break;
+ default:
+ break;
+ }
+}
+
+void LocalPlayer::tryMagic(const std::string &spell, const int baseMagic,
+ const int schoolMagic, const int mana)
+{
+ if (!chatWindow)
+ return;
+
+ if (PlayerInfo::getSkillLevel(340) >= baseMagic
+ && PlayerInfo::getSkillLevel(342) >= schoolMagic)
+ {
+ if (PlayerInfo::getAttribute(PlayerInfo::MP) >= mana)
+ {
+ if (!client->limitPackets(PACKET_CHAT))
+ return;
+
+ chatWindow->localChatInput(spell);
+ }
+ }
+}
+
+void LocalPlayer::loadHomes()
+{
+ if (serverVersion > 0)
+ return;
+ std::string buf;
+ std::stringstream ss(serverConfig.getValue("playerHomes",
+ "maps/018-1.tmx 71 76 maps/013-3.tmx 71 24"));
+
+ while (ss >> buf)
+ {
+ Vector pos;
+ ss >> pos.x;
+ ss >> pos.y;
+ mHomes[buf] = pos;
+ }
+}
+
+void LocalPlayer::setMap(Map *const map)
+{
+ if (map)
+ {
+ if (socialWindow)
+ socialWindow->updateActiveList();
+ }
+ navigateClean();
+ mCrossX = 0;
+ mCrossY = 0;
+
+ Being::setMap(map);
+ updateNavigateList();
+}
+
+void LocalPlayer::setHome()
+{
+ if (!mMap || !socialWindow)
+ return;
+
+ SpecialLayer *const specialLayer = mMap->getSpecialLayer();
+
+ if (!specialLayer)
+ return;
+
+ const std::string key = mMap->getProperty("_realfilename");
+ Vector pos = mHomes[key];
+
+ if (mAction == SIT)
+ {
+ const std::map<std::string, Vector>::const_iterator
+ iter = mHomes.find(key);
+
+ if (iter != mHomes.end())
+ {
+ socialWindow->removePortal(static_cast<int>(pos.x),
+ static_cast<int>(pos.y));
+ }
+
+ if (iter != mHomes.end() && mX == static_cast<int>(pos.x)
+ && mY == static_cast<int>(pos.y))
+ {
+ mMap->updatePortalTile("", MapItem::EMPTY,
+ static_cast<int>(pos.x), static_cast<int>(pos.y));
+
+ mHomes.erase(key);
+ socialWindow->removePortal(static_cast<int>(pos.x),
+ static_cast<int>(pos.y));
+ }
+ else
+ {
+ if (specialLayer && iter != mHomes.end())
+ {
+ specialLayer->setTile(static_cast<int>(pos.x),
+ static_cast<int>(pos.y), MapItem::EMPTY);
+ }
+
+ pos.x = static_cast<float>(mX);
+ pos.y = static_cast<float>(mY);
+ mHomes[key] = pos;
+ mMap->updatePortalTile("home", MapItem::HOME,
+ mX, mY);
+ socialWindow->addPortal(mX, mY);
+ }
+ MapItem *const mapItem = specialLayer->getTile(mX, mY);
+ if (mapItem)
+ {
+ const int idx = socialWindow->getPortalIndex(mX, mY);
+ mapItem->setName(keyboard.getKeyShortString(
+ outfitWindow->keyName(idx)));
+ }
+ saveHomes();
+ }
+ else
+ {
+ MapItem *mapItem = specialLayer->getTile(mX, mY);
+ int type = 0;
+
+ const std::map<std::string, Vector>::iterator iter = mHomes.find(key);
+ if (iter != mHomes.end() && mX == pos.x && mY == pos.y)
+ {
+ mHomes.erase(key);
+ saveHomes();
+ }
+
+ if (!mapItem || mapItem->getType() == MapItem::EMPTY)
+ {
+ if (mDirection & UP)
+ type = MapItem::ARROW_UP;
+ else if (mDirection & LEFT)
+ type = MapItem::ARROW_LEFT;
+ else if (mDirection & DOWN)
+ type = MapItem::ARROW_DOWN;
+ else if (mDirection & RIGHT)
+ type = MapItem::ARROW_RIGHT;
+ }
+ else
+ {
+ type = MapItem::EMPTY;
+ }
+ mMap->updatePortalTile("", type, mX, mY);
+
+ if (type != MapItem::EMPTY)
+ {
+ socialWindow->addPortal(mX, mY);
+ mapItem = specialLayer->getTile(mX, mY);
+ if (mapItem)
+ {
+ const int idx = socialWindow->getPortalIndex(mX, mY);
+ mapItem->setName(keyboard.getKeyShortString(
+ outfitWindow->keyName(idx)));
+ }
+ }
+ else
+ {
+ specialLayer->setTile(mX, mY, MapItem::EMPTY);
+ socialWindow->removePortal(mX, mY);
+ }
+ }
+}
+
+void LocalPlayer::saveHomes()
+{
+ std::stringstream ss;
+
+ for (std::map<std::string, Vector>::const_iterator iter = mHomes.begin(),
+ iter_end = mHomes.end(); iter != iter_end; ++iter )
+ {
+ const Vector &pos = (*iter).second;
+
+ if (iter != mHomes.begin())
+ ss << " ";
+ ss << (*iter).first << " " << pos.x << " " << pos.y;
+ }
+
+ serverConfig.setValue("playerHomes", ss.str());
+}
+
+void LocalPlayer::pingRequest()
+{
+ const int time = tick_time;
+ if (mWaitPing == true && mPingSendTick != 0)
+ {
+ if (time >= mPingSendTick && (time - mPingSendTick) > 1000)
+ return;
+ }
+
+ mPingSendTick = time;
+ mWaitPing = true;
+ Net::getBeingHandler()->requestNameById(getId());
+}
+
+std::string LocalPlayer::getPingTime() const
+{
+ std::string str;
+ if (!mWaitPing)
+ {
+ if (!mPingTime)
+ str = "?";
+ else
+ str = toString(mPingTime);
+ }
+ else
+ {
+ int time = tick_time;
+ if (time > mPingSendTick)
+ time -= mPingSendTick;
+ else
+ time += MAX_TICK_VALUE - mPingSendTick;
+ if (time <= mPingTime)
+ time = mPingTime;
+ if (mPingTime != time)
+ str = strprintf("%d (%d)", mPingTime, time);
+ else
+ str = toString(time);
+ }
+ return str;
+}
+
+void LocalPlayer::pingResponse()
+{
+ if (mWaitPing == true && mPingSendTick > 0)
+ {
+ mWaitPing = false;
+ const int time = tick_time;
+ if (time < mPingSendTick)
+ {
+ mPingSendTick = 0;
+ mPingTime = 0;
+ }
+ else
+ {
+ mPingTime = (time - mPingSendTick) * 10;
+ }
+ }
+}
+
+void LocalPlayer::tryPingRequest()
+{
+ if (mPingSendTick == 0 || tick_time < mPingSendTick
+ || (tick_time - mPingSendTick) > 200)
+ {
+ pingRequest();
+ }
+}
+
+
+void LocalPlayer::setAway(const std::string &message)
+{
+ if (!message.empty())
+ config.setValue("afkMessage", message);
+ changeAwayMode();
+ updateStatus();
+}
+
+void LocalPlayer::setPseudoAway(const std::string &message)
+{
+ if (!message.empty())
+ config.setValue("afkMessage", message);
+ mPseudoAwayMode = !mPseudoAwayMode;
+}
+
+void LocalPlayer::afkRespond(ChatTab *const tab, const std::string &nick)
+{
+ if (mAwayMode)
+ {
+ const int time = cur_time;
+ if (mAfkTime == 0 || time < mAfkTime
+ || time - mAfkTime > awayLimitTimer)
+ {
+ const std::string msg = "*AFK*: "
+ + config.getStringValue("afkMessage");
+
+ if (!tab)
+ {
+ Net::getChatHandler()->privateMessage(nick, msg);
+ if (localChatTab)
+ {
+ localChatTab->chatLog(std::string(getName()).append(
+ " : ").append(msg), ACT_WHISPER, false);
+ }
+ }
+ else
+ {
+ if (tab->getNoAway())
+ return;
+ Net::getChatHandler()->privateMessage(nick, msg);
+ tab->chatLog(getName(), msg);
+ }
+ mAfkTime = time;
+ }
+ }
+}
+
+bool LocalPlayer::navigateTo(const int x, const int y)
+{
+ if (!mMap)
+ return false;
+
+ SpecialLayer *const tmpLayer = mMap->getTempLayer();
+ if (!tmpLayer)
+ return false;
+
+ const Vector &playerPos = getPosition();
+ mShowNavigePath = true;
+ mOldX = static_cast<int>(playerPos.x);
+ mOldY = static_cast<int>(playerPos.y);
+ mOldTileX = mX;
+ mOldTileY = mY;
+ mNavigateX = x;
+ mNavigateY = y;
+ mNavigateId = 0;
+
+ mNavigatePath = mMap->findPath(
+ static_cast<int>(playerPos.x - 16) / 32,
+ static_cast<int>(playerPos.y - 32) / 32,
+ x, y, getWalkMask(), 0);
+
+ if (mDrawPath)
+ tmpLayer->addRoad(mNavigatePath);
+ return !mNavigatePath.empty();
+}
+
+void LocalPlayer::navigateTo(const Being *const being)
+{
+ if (!mMap || !being)
+ return;
+
+ SpecialLayer *const tmpLayer = mMap->getTempLayer();
+ if (!tmpLayer)
+ return;
+
+ const Vector &playerPos = getPosition();
+ mShowNavigePath = true;
+ mOldX = static_cast<int>(playerPos.x);
+ mOldY = static_cast<int>(playerPos.y);
+ mOldTileX = mX;
+ mOldTileY = mY;
+ mNavigateX = being->getTileX();
+ mNavigateY = being->getTileY();
+
+ mNavigatePath = mMap->findPath(
+ static_cast<int>(playerPos.x - 16) / 32,
+ static_cast<int>(playerPos.y - 32) / 32,
+ being->getTileX(), being->getTileY(),
+ getWalkMask(), 0);
+
+ if (mDrawPath)
+ tmpLayer->addRoad(mNavigatePath);
+}
+
+void LocalPlayer::navigateClean()
+{
+ if (!mMap)
+ return;
+
+ mShowNavigePath = false;
+ mOldX = 0;
+ mOldY = 0;
+ mOldTileX = 0;
+ mOldTileY = 0;
+ mNavigateX = 0;
+ mNavigateY = 0;
+ mNavigateId = 0;
+
+ mNavigatePath.clear();
+
+ const SpecialLayer *const tmpLayer = mMap->getTempLayer();
+ if (!tmpLayer)
+ return;
+
+ tmpLayer->clean();
+}
+
+void LocalPlayer::updateCoords()
+{
+ Being::updateCoords();
+
+ const Vector &playerPos = getPosition();
+ // probably map not loaded.
+ if (!playerPos.x || !playerPos.y)
+ return;
+
+ if (mX != mOldTileX || mY != mOldTileY)
+ {
+ if (socialWindow)
+ socialWindow->updatePortals();
+ if (viewport)
+ viewport->hideBeingPopup();
+ if (mMap)
+ {
+ std::string str = mMap->getObjectData(mX, mY,
+ MapItem::MUSIC);
+ if (str.empty())
+ str = mMap->getMusicFile();
+ if (str != soundManager.getCurrentMusicFile())
+ {
+ if (str.empty())
+ soundManager.fadeOutMusic();
+ else
+ soundManager.fadeOutAndPlayMusic(str);
+ }
+ }
+ }
+
+ if (mShowNavigePath)
+ {
+ if (mMap && (mX != mOldTileX || mY != mOldTileY))
+ {
+ SpecialLayer *const tmpLayer = mMap->getTempLayer();
+ if (!tmpLayer)
+ return;
+
+ const int x = static_cast<int>(playerPos.x - 16) / 32;
+ const int y = static_cast<int>(playerPos.y - 32) / 32;
+ if (mNavigateId)
+ {
+ if (!actorSpriteManager)
+ {
+ navigateClean();
+ return;
+ }
+
+ const Being *const being = actorSpriteManager
+ ->findBeing(mNavigateId);
+ if (!being)
+ {
+ navigateClean();
+ return;
+ }
+ mNavigateX = being->getTileX();
+ mNavigateY = being->getTileY();
+ }
+
+ if (mNavigateX == x && mNavigateY == y)
+ {
+ navigateClean();
+ return;
+ }
+ else
+ {
+ for (Path::const_iterator i = mNavigatePath.begin(),
+ i_end = mNavigatePath.end(); i != i_end; ++ i)
+ {
+ if ((*i).x == mX && (*i).y == mY)
+ {
+ mNavigatePath.pop_front();
+ break;
+ }
+ }
+
+ if (mDrawPath)
+ {
+ tmpLayer->clean();
+ tmpLayer->addRoad(mNavigatePath);
+ }
+ }
+ }
+ }
+ mOldX = static_cast<int>(playerPos.x);
+ mOldY = static_cast<int>(playerPos.y);
+ mOldTileX = mX;
+ mOldTileY = mY;
+}
+
+void LocalPlayer::targetMoved() const
+{
+/*
+ if (mKeepAttacking)
+ {
+ if (mTarget && mServerAttack)
+ {
+ logger->log("LocalPlayer::targetMoved0");
+ if (!client->limitPackets(PACKET_ATTACK))
+ return;
+ logger->log("LocalPlayer::targetMoved");
+ Net::getPlayerHandler()->attack(mTarget->getId(), mServerAttack);
+ }
+ }
+*/
+}
+
+int LocalPlayer::getPathLength(const Being *const being) const
+{
+ if (!mMap || !being)
+ return 0;
+
+ const Vector &playerPos = getPosition();
+
+ if (being->mX == mX && being->mY == mY)
+ return 0;
+
+ if (being->mX - 1 <= mX && being->mX + 1 >= mX
+ && being->mY - 1 <= mY && being->mY + 1 >= mY)
+ {
+ return 1;
+ }
+
+ if (mTargetOnlyReachable)
+ {
+ const Path debugPath = mMap->findPath(
+ static_cast<int>(playerPos.x - 16) / 32,
+ static_cast<int>(playerPos.y - 32) / 32,
+ being->getTileX(), being->getTileY(),
+ getWalkMask(), 0);
+ return static_cast<int>(debugPath.size());
+ }
+ else
+ {
+ const int dx = static_cast<int>(abs(being->mX - mX));
+ const int dy = static_cast<int>(abs(being->mY - mY));
+ if (dx > dy)
+ return dx;
+ return dy;
+ }
+}
+
+int LocalPlayer::getAttackRange2() const
+{
+ int range = getAttackRange();
+ if (range == 1)
+ range = 2;
+ return range;
+}
+
+void LocalPlayer::attack2(Being *const target, const bool keep,
+ const bool dontChangeEquipment)
+{
+ if (!dontChangeEquipment && target)
+ changeEquipmentBeforeAttack(target);
+
+ // probably need cache getPathLength(target)
+ if ((!target || mAttackType == 0 || mAttackType == 3)
+ || (withinAttackRange(target, serverVersion < 1,
+ serverVersion < 1 ? 1 : 0)
+ && getPathLength(target) <= getAttackRange2()))
+ {
+ attack(target, keep);
+ if (mAttackType == 2)
+ {
+ if (!target)
+ {
+ if (pickUpItems())
+ return;
+ }
+ else
+ {
+ pickUpItems(3);
+ }
+ }
+ }
+ else if (!mPickUpTarget)
+ {
+ if (mAttackType == 2)
+ {
+ if (pickUpItems())
+ return;
+ }
+ setTarget(target);
+ if (target && target->getType() != Being::NPC)
+ {
+ mKeepAttacking = true;
+ moveToTarget();
+ }
+ }
+}
+
+void LocalPlayer::setFollow(const std::string &player)
+{
+ mPlayerFollowed = player;
+ if (!mPlayerFollowed.empty())
+ {
+ // TRANSLATORS: follow command message
+ std::string msg = strprintf(_("Follow: %s"), player.c_str());
+ debugMsg(msg);
+ }
+ else
+ {
+ // TRANSLATORS: follow command message
+ debugMsg(_("Follow canceled"));
+ }
+}
+
+void LocalPlayer::setImitate(const std::string &player)
+{
+ mPlayerImitated = player;
+ if (!mPlayerImitated.empty())
+ {
+ // TRANSLATORS: imitate command message
+ std::string msg = strprintf(_("Imitation: %s"), player.c_str());
+ debugMsg(msg);
+ }
+ else
+ {
+ // TRANSLATORS: imitate command message
+ debugMsg(_("Imitation canceled"));
+ }
+}
+
+void LocalPlayer::cancelFollow()
+{
+ if (!mPlayerFollowed.empty())
+ {
+ // TRANSLATORS: cancel follow message
+ debugMsg(_("Follow canceled"));
+ }
+ if (!mPlayerImitated.empty())
+ {
+ // TRANSLATORS: cancel follow message
+ debugMsg(_("Imitation canceled"));
+ }
+ mPlayerFollowed.clear();
+ mPlayerImitated.clear();
+}
+
+void LocalPlayer::imitateEmote(const Being *const being,
+ const unsigned char action) const
+{
+ if (!being)
+ return;
+
+ std::string player_imitated = getImitate();
+ if (!player_imitated.empty() && being->getName() == player_imitated)
+ emote(action);
+}
+
+void LocalPlayer::imitateAction(const Being *const being,
+ const Being::Action &action)
+{
+ if (!being)
+ return;
+
+ if (!mPlayerImitated.empty() && being->getName() == mPlayerImitated)
+ {
+ setAction(action);
+ Net::getPlayerHandler()->changeAction(action);
+ }
+}
+
+void LocalPlayer::imitateDirection(const Being *const being,
+ const unsigned char dir)
+{
+ if (!being)
+ return;
+
+ if (!mPlayerImitated.empty() && being->getName() == mPlayerImitated)
+ {
+ if (!client->limitPackets(PACKET_DIRECTION))
+ return;
+
+ if (mFollowMode == 2)
+ {
+ uint8_t dir2 = 0;
+ if (dir & Being::LEFT)
+ dir2 |= Being::RIGHT;
+ else if (dir & Being::RIGHT)
+ dir2 |= Being::LEFT;
+ if (dir & Being::UP)
+ dir2 |= Being::DOWN;
+ else if (dir & Being::DOWN)
+ dir2 |= Being::UP;
+
+ setDirection(dir2);
+ Net::getPlayerHandler()->setDirection(dir2);
+ }
+ else
+ {
+ setDirection(dir);
+ Net::getPlayerHandler()->setDirection(dir);
+ }
+ }
+}
+
+void LocalPlayer::imitateOutfit(Being *const player, const int sprite) const
+{
+ if (!player)
+ return;
+
+ if (mImitationMode == 1 && !mPlayerImitated.empty()
+ && player->getName() == mPlayerImitated)
+ {
+ if (sprite < 0 || sprite >= player->getNumberOfLayers())
+ return;
+
+ const AnimatedSprite *const equipmentSprite
+ = dynamic_cast<const AnimatedSprite *const>(
+ player->getSprite(sprite));
+
+ if (equipmentSprite)
+ {
+// logger->log("have equipmentSprite");
+ const Inventory *const inv = PlayerInfo::getInventory();
+ if (!inv)
+ return;
+
+ const std::string &path = equipmentSprite->getIdPath();
+ if (path.empty())
+ return;
+
+// logger->log("idPath: " + path);
+ const Item *const item = inv->findItemBySprite(path,
+ player->getGender(), player->getSubType());
+ if (item && !item->isEquipped())
+ Net::getInventoryHandler()->equipItem(item);
+ }
+ else
+ {
+// logger->log("have unequip %d", sprite);
+ const int equipmentSlot = Net::getInventoryHandler()
+ ->convertFromServerSlot(sprite);
+// logger->log("equipmentSlot: " + toString(equipmentSlot));
+ if (equipmentSlot == EQUIP_PROJECTILE_SLOT)
+ return;
+
+ const Item *const item = PlayerInfo::getEquipment(equipmentSlot);
+ if (item)
+ {
+// logger->log("unequiping");
+ Net::getInventoryHandler()->unequipItem(item);
+ }
+ }
+ }
+}
+
+void LocalPlayer::followMoveTo(const Being *const being,
+ const int x, const int y)
+{
+ if (being && !mPlayerFollowed.empty()
+ && being->getName() == mPlayerFollowed)
+ {
+ mPickUpTarget = nullptr;
+ setDestination(x, y);
+ }
+}
+
+void LocalPlayer::followMoveTo(const Being *const being,
+ const int x1, const int y1,
+ const int x2, const int y2)
+{
+ if (!being)
+ return;
+
+ mPickUpTarget = nullptr;
+ if (!mPlayerFollowed.empty() && being->getName() == mPlayerFollowed)
+ {
+ switch (mFollowMode)
+ {
+ case 0:
+ setDestination(x1, y1);
+ setNextDest(x2, y2);
+ break;
+ case 1:
+ if (x1 != x2 || y1 != y2)
+ {
+ setDestination(mX + x2 - x1, mY + y2 - y1);
+ setNextDest(mX + x2 - x1, mY + y2 - y1);
+ }
+ break;
+ case 2:
+ if (x1 != x2 || y1 != y2)
+ {
+ setDestination(mX + x1 - x2, mY + y1 - y2);
+ setNextDest(mX + x1 - x2, mY + y1 - y2);
+ }
+ break;
+ case 3:
+ if (!mTarget || mTarget->getName() != mPlayerFollowed)
+ {
+ if (actorSpriteManager)
+ {
+ Being *const b = actorSpriteManager->findBeingByName(
+ mPlayerFollowed, Being::PLAYER);
+ setTarget(b);
+ }
+ }
+ moveToTarget();
+ setNextDest(x2, y2);
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+void LocalPlayer::setNextDest(const int x, const int y)
+{
+ mNextDestX = x;
+ mNextDestY = y;
+}
+
+bool LocalPlayer::allowAction()
+{
+ if (mIsServerBuggy)
+ {
+ if (mLastAction != -1)
+ return false;
+ mLastAction = tick_time;
+ }
+ return true;
+}
+
+/*
+bool LocalPlayer::allowMove() const
+{
+ if (mIsServerBuggy)
+ {
+ if (mAction == MOVE)
+ return false;
+ }
+ return true;
+}
+*/
+
+void LocalPlayer::fixPos(const int maxDist)
+{
+ if (!mCrossX && !mCrossY)
+ return;
+
+ const int dx = abs(mX - mCrossX);
+ const int dy = abs(mY - mCrossY);
+ const int dest = (dx * dx) + (dy * dy);
+ const int time = cur_time;
+
+ if (dest > maxDist && mActivityTime
+ && (time < mActivityTime || time - mActivityTime > 2))
+ {
+ mActivityTime = time;
+ moveTo(mCrossX, mCrossY);
+ }
+}
+
+void LocalPlayer::setRealPos(const int x, const int y)
+{
+ if (!mMap)
+ return;
+
+ SpecialLayer *const layer = mMap->getTempLayer();
+ if (layer)
+ {
+ fixPos(1);
+
+ if ((mCrossX || mCrossY) && layer->getTile(mCrossX, mCrossY)
+ && layer->getTile(mCrossX, mCrossY)->getType() == MapItem::CROSS)
+ {
+ layer->setTile(mCrossX, mCrossY, MapItem::EMPTY);
+ }
+
+ if (!layer->getTile(x, y)
+ || layer->getTile(x, y)->getType() == MapItem::EMPTY)
+ {
+ if (getTileX() != x && getTileY() != y)
+ layer->setTile(x, y, MapItem::CROSS);
+ }
+
+ mCrossX = x;
+ mCrossY = y;
+ }
+ if (mMap->isCustom())
+ mMap->setWalk(x, y, true);
+}
+void LocalPlayer::fixAttackTarget()
+{
+ if (!mMap || !mTarget)
+ return;
+
+ if (mMoveToTargetType == 7 || !mAttackType
+ || !config.getBoolValue("autofixPos"))
+ {
+ return;
+ }
+
+ const Vector &playerPos = getPosition();
+ const Path debugPath = mMap->findPath(
+ static_cast<int>(playerPos.x - 16) / 32,
+ static_cast<int>(playerPos.y - 32) / 32,
+ mTarget->getTileX(), mTarget->getTileY(),
+ getWalkMask(), 0);
+
+ if (!debugPath.empty())
+ {
+ const Path::const_iterator i = debugPath.begin();
+ moveTo((*i).x, (*i).y);
+ }
+}
+
+void LocalPlayer::respawn()
+{
+ navigateClean();
+}
+
+int LocalPlayer::getLevel() const
+{
+ return PlayerInfo::getAttribute(PlayerInfo::LEVEL);
+}
+
+void LocalPlayer::updateNavigateList()
+{
+ if (mMap)
+ {
+ const std::map<std::string, Vector>::const_iterator iter =
+ mHomes.find(mMap->getProperty("_realfilename"));
+
+ if (iter != mHomes.end())
+ {
+ const Vector &pos = mHomes[(*iter).first];
+ if (pos.x && pos.y)
+ {
+ mMap->addPortalTile("home", MapItem::HOME,
+ static_cast<int>(pos.x), static_cast<int>(pos.y));
+ }
+ }
+ }
+}
+
+void LocalPlayer::waitFor(const std::string &nick)
+{
+ mWaitFor = nick;
+}
+
+void LocalPlayer::checkNewName(Being *const being)
+{
+ if (!being)
+ return;
+
+ const std::string nick = being->getName();
+ if (being->getType() == ActorSprite::PLAYER)
+ {
+ const Guild *const guild = getGuild();
+ if (guild)
+ {
+ const GuildMember *const gm = guild->getMember(nick);
+ if (gm)
+ {
+ const int level = gm->getLevel();
+ if (level > 1 && being->getLevel() != level)
+ {
+ being->setLevel(level);
+ being->updateName();
+ }
+ }
+ }
+ if (chatWindow)
+ {
+ WhisperTab *const tab = chatWindow->getWhisperTab(nick);
+ if (tab)
+ tab->setWhisperTabColors();
+ }
+ }
+
+ if (!mWaitFor.empty() && mWaitFor == nick)
+ {
+ // TRANSLATORS: wait player/monster message
+ debugMsg(strprintf(_("You see %s"), mWaitFor.c_str()));
+ soundManager.playGuiSound(SOUND_INFO);
+ mWaitFor.clear();
+ }
+}
+
+void LocalPlayer::resetYellowBar()
+{
+ mInvertDirection = 0;
+ mCrazyMoveType = config.resetIntValue("crazyMoveType");
+ mMoveToTargetType = config.resetIntValue("moveToTargetType");
+ mFollowMode = config.resetIntValue("followMode");
+ mAttackWeaponType = config.resetIntValue("attackWeaponType");
+ mAttackType = config.resetIntValue("attackType");
+ mMagicAttackType = config.resetIntValue("magicAttackType");
+ mPvpAttackType = config.resetIntValue("pvpAttackType");
+ mQuickDropCounter = config.resetIntValue("quickDropCounter");
+ mPickUpType = config.resetIntValue("pickUpType");
+ if (viewport)
+ {
+ viewport->setDebugPath(0);
+ if (viewport->getCameraMode())
+ viewport->toggleCameraMode();
+ }
+ if (mMap)
+ mMap->setDebugFlags(0);
+ mImitationMode = config.resetIntValue("imitationMode");
+ mDisableGameModifiers = config.resetBoolValue("disableGameModifiers");
+
+ if (miniStatusWindow)
+ miniStatusWindow->updateStatus();
+}
+
+unsigned char LocalPlayer::getWalkMask() const
+{
+ // for now blocking all types of collisions
+ return Map::BLOCKMASK_WALL | Map::BLOCKMASK_AIR | Map::BLOCKMASK_WATER;
+}
+
+void LocalPlayer::removeHome()
+{
+ if (!mMap)
+ return;
+
+ const std::string key = mMap->getProperty("_realfilename");
+ const std::map<std::string, Vector>::iterator iter = mHomes.find(key);
+
+ if (iter != mHomes.end())
+ mHomes.erase(key);
+}
+
+void LocalPlayer::stopAdvert()
+{
+ mBlockAdvert = true;
+}
+
+bool LocalPlayer::checAttackPermissions(const Being *const target) const
+{
+ if (!target)
+ return false;
+
+ switch (mPvpAttackType)
+ {
+ case 0:
+ return true;
+ case 1:
+ return !(player_relations.getRelation(target->getName())
+ == PlayerRelation::FRIEND);
+ case 2:
+ return player_relations.checkBadRelation(target->getName());
+ default:
+ case 3:
+ return false;
+ }
+}
+
+
+const char *LocalPlayer::getVarItem(const char *const *const arr,
+ const unsigned index,
+ const unsigned sz) const
+{
+ if (index < sz)
+ return arr[index];
+ return arr[sz];
+}
+
+void LocalPlayer::updateStatus() const
+{
+ if (serverVersion >= 4 && mEnableAdvert)
+ {
+ uint8_t status = 0;
+ if (mTradebot && shopWindow && !shopWindow->isShopEmpty())
+ status |= FLAG_SHOP;
+
+ if (mAwayMode || mPseudoAwayMode)
+ status |= FLAG_AWAY;
+
+ if (mInactive)
+ status |= FLAG_INACTIVE;
+
+ Net::getPlayerHandler()->updateStatus(status);
+ }
+}
+
+void LocalPlayer::setTestParticle(const std::string &fileName, bool updateHash)
+{
+ mTestParticleName = fileName;
+ mTestParticleTime = cur_time;
+ if (mTestParticle)
+ {
+ mChildParticleEffects.removeLocally(mTestParticle);
+ mTestParticle = nullptr;
+ }
+ if (!fileName.empty())
+ {
+ mTestParticle = particleEngine->addEffect(fileName, 0, 0, false);
+ controlParticle(mTestParticle);
+ if (updateHash)
+ mTestParticleHash = UpdaterWindow::getFileHash(mTestParticleName);
+ }
+}
+
+void AwayListener::action(const gcn::ActionEvent &event)
+{
+ if (event.getId() == "ok" && player_node && player_node->getAway())
+ {
+ player_node->changeAwayMode();
+ player_node->updateStatus();
+ if (outfitWindow)
+ outfitWindow->unwearAwayOutfit();
+ if (miniStatusWindow)
+ miniStatusWindow->updateStatus();
+ }
+}
diff --git a/src/being/localplayer.h b/src/being/localplayer.h
new file mode 100644
index 000000000..4f57df0d7
--- /dev/null
+++ b/src/being/localplayer.h
@@ -0,0 +1,652 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2004-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ * Copyright (C) 2011-2013 The ManaPlus Developers
+ *
+ * This file is part of The ManaPlus Client.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef BEING_LOCALPLAYER_H
+#define BEING_LOCALPLAYER_H
+
+#include "depricatedlistener.h"
+
+#include "being/actorspritelistener.h"
+#include "being/being.h"
+
+#include "gui/userpalette.h"
+
+#include <guichan/actionlistener.hpp>
+
+#include <memory>
+#include <vector>
+
+#include "localconsts.h"
+
+class ChatTab;
+class FloorItem;
+class ImageSet;
+class Item;
+class Map;
+class OkDialog;
+
+class AwayListener final : public gcn::ActionListener
+{
+ public:
+ void action(const gcn::ActionEvent &event) override;
+};
+
+/**
+ * Reasons an item can fail to be picked up.
+ */
+enum
+{
+ PICKUP_OKAY = 0,
+ PICKUP_BAD_ITEM,
+ PICKUP_TOO_HEAVY,
+ PICKUP_TOO_FAR,
+ PICKUP_INV_FULL,
+ PICKUP_STACK_FULL,
+ PICKUP_DROP_STEAL
+};
+
+/**
+ * The local player character.
+ */
+class LocalPlayer final : public Being,
+ public ActorSpriteListener,
+ public DepricatedListener
+{
+ public:
+ /**
+ * Constructor.
+ */
+ explicit LocalPlayer(const int id = 65535, const int subtype = 0);
+
+ A_DELETE_COPY(LocalPlayer)
+
+ /**
+ * Destructor.
+ */
+ ~LocalPlayer();
+
+ virtual void logic() override;
+
+ void slowLogic();
+
+ virtual void setAction(const Action &action,
+ const int attackType = 0) override;
+
+ /**
+ * Compute the next pathnode location when walking using keyboard.
+ * used by nextTile().
+ */
+ Position getNextWalkPosition(const unsigned char dir)
+ const A_WARN_UNUSED;
+
+ /**
+ * Adds a new tile to the path when walking.
+ * @note Eathena
+ * Also, when specified, it picks up an item at the end of a path
+ * or attack target.
+ */
+ virtual void nextTile() override
+ { nextTile(0); }
+
+ virtual void nextTile(unsigned char dir);
+
+ bool pickUp(FloorItem *const item);
+
+ /**
+ * Called when an ActorSprite has been destroyed.
+ * @param actorSprite the ActorSprite being destroyed.
+ */
+ void actorSpriteDestroyed(const ActorSprite &actorSprite) override;
+
+ /**
+ * Gets the attack range.
+ */
+ int getAttackRange() const A_WARN_UNUSED;
+
+ int getAttackRange2() const A_WARN_UNUSED;
+
+ void attack(Being *const target = nullptr, const bool keep = false,
+ const bool dontChangeEquipment = false);
+
+ void attack2(Being *const target = nullptr, const bool keep = false,
+ const bool dontChangeEquipment = false);
+
+ void setGMLevel(const int level);
+
+ int getGMLevel() const A_WARN_UNUSED
+ { return mGMLevel; }
+
+ void stopAttack(const bool keepAttack = false);
+
+ void untarget();
+
+ /**
+ * Returns the current target of the player. Returns 0 if no being is
+ * currently targeted.
+ */
+ Being *getTarget() const A_WARN_UNUSED;
+
+ /**
+ * Sets the target being of the player.
+ */
+ void setTarget(Being *const target);
+
+ /**
+ * Sets a new destination for this being to walk to.
+ */
+ virtual void setDestination(const int x, const int y);
+
+ /**
+ * Sets a new direction to keep walking in.
+ */
+ void setWalkingDir(const unsigned char dir);
+
+ /**
+ * Gets the walking direction
+ */
+ unsigned char getWalkingDir() const A_WARN_UNUSED
+ { return mWalkingDir; }
+
+ /**
+ * Sets going to being to attack
+ */
+ void setGotoTarget(Being *const target);
+
+ /**
+ * Returns whether the target is in range to attack
+ */
+ bool withinAttackRange(const Being *const target,
+ const bool fixDistance = false,
+ const int addRange = 0) const A_WARN_UNUSED;
+
+ /**
+ * Stops the player dead in his tracks
+ */
+ void stopWalking(const bool sendToServer = true);
+
+ bool toggleSit() const;
+
+ bool updateSit() const;
+
+ static bool emote(const uint8_t emotion);
+
+ /**
+ * Shows item pickup notifications.
+ */
+ void pickedUp(const ItemInfo &itemInfo, const int amount,
+ const unsigned char color, const int floorItemId,
+ const unsigned char fail);
+
+ int getLevel() const override A_WARN_UNUSED;
+
+ /** Tells that the path has been set by mouse. */
+ void pathSetByMouse()
+ { mPathSetByMouse = true; }
+
+ /** Tells if the path has been set by mouse. */
+ bool isPathSetByMouse() const A_WARN_UNUSED
+ { return mPathSetByMouse; }
+
+ int getInvertDirection() const A_WARN_UNUSED
+ { return mInvertDirection; }
+
+ void setInvertDirection(const int n)
+ { mInvertDirection = n; }
+
+ void invertDirection();
+
+ int getAttackWeaponType() const A_WARN_UNUSED
+ { return mAttackWeaponType; }
+
+ int getAttackType() const A_WARN_UNUSED
+ { return mAttackType; }
+
+ int getFollowMode() const A_WARN_UNUSED
+ { return mFollowMode; }
+
+ int getImitationMode() const A_WARN_UNUSED
+ { return mImitationMode; }
+
+ void changeAttackWeaponType();
+
+ void changeAttackType();
+
+ void changeFollowMode();
+
+ void changeImitationMode();
+
+ void changePickUpType();
+
+ int getCrazyMoveType() const A_WARN_UNUSED
+ { return mCrazyMoveType; }
+
+ int getPickUpType() const A_WARN_UNUSED
+ { return mPickUpType; }
+
+ int getQuickDropCounter() const A_WARN_UNUSED
+ { return mQuickDropCounter; }
+
+ void setQuickDropCounter(const int n);
+
+ void changeQuickDropCounter();
+
+ int getMoveState() const A_WARN_UNUSED
+ { return mMoveState; }
+
+ void setMoveState(const int n)
+ { mMoveState = n; }
+
+ void switchMagicAttack();
+
+ void switchPvpAttack();
+
+ int getMagicAttackType() const A_WARN_UNUSED
+ { return mMagicAttackType; }
+
+ int getPvpAttackType() const A_WARN_UNUSED
+ { return mPvpAttackType; }
+
+ int getMoveToTargetType() const A_WARN_UNUSED
+ { return mMoveToTargetType; }
+
+ int getDisableGameModifiers() const A_WARN_UNUSED
+ { return mDisableGameModifiers; }
+
+ std::string getPingTime() const A_WARN_UNUSED;
+
+ void tryPingRequest();
+
+ void changeMoveToTargetType();
+
+ void switchGameModifiers();
+
+ void magicAttack() const;
+
+ void specialMove(const unsigned char direction);
+
+ void moveByDirection(const unsigned char dir);
+
+ bool pickUpItems(int pickUpType = 0);
+
+ void changeCrazyMoveType();
+
+ void crazyMove();
+
+ void moveTo(const int x, const int y);
+
+ void move(const int dX, const int dY);
+
+ void moveToTarget(int dist = -1);
+
+ void moveToHome();
+
+ void debugMsg(const std::string &str) const;
+
+ bool isReachable(Being *const being,
+ const int maxCost = 0) A_WARN_UNUSED;
+
+ bool isReachable(const int x, const int y,
+ const bool allowCollision) const A_WARN_UNUSED;
+
+ void setHome();
+
+ void pingRequest();
+
+ void pingResponse();
+
+ void changeAwayMode();
+
+ void setAway(const std::string &message);
+
+ void setPseudoAway(const std::string &message);
+
+ bool getAway() const A_WARN_UNUSED
+ { return mAwayMode; }
+
+ bool getPseudoAway() const A_WARN_UNUSED
+ { return mPseudoAwayMode; }
+
+ void setHalfAway(const bool n)
+ { mInactive = n; }
+
+ bool getHalfAway() const A_WARN_UNUSED
+ { return mInactive; }
+
+ void afkRespond(ChatTab *const tab, const std::string &nick);
+
+ bool navigateTo(const int x, const int y);
+
+ void navigateTo(const Being *const being);
+
+ void navigateClean();
+
+ void imitateEmote(const Being *const being,
+ const unsigned char emote) const;
+
+ void imitateAction(const Being *const being,
+ const Being::Action &action);
+
+ void imitateDirection(const Being *const being,
+ const unsigned char dir);
+
+ void imitateOutfit(Being *const player, const int sprite = -1) const;
+
+ void followMoveTo(const Being *const being, const int x, const int y);
+
+ void followMoveTo(const Being *const being, const int x1, const int y1,
+ const int x2, const int y2);
+
+ bool allowAction() A_WARN_UNUSED;
+
+ void setRealPos(const int x, const int y);
+
+ bool isServerBuggy() const A_WARN_UNUSED
+ { return mIsServerBuggy; }
+
+ void fixPos(const int maxDist = 1);
+
+ /**
+ * Sets the map the being is on
+ */
+ void setMap(Map *const map);
+
+ void addMessageToQueue(const std::string &message,
+ const int color = UserPalette::EXP_INFO);
+
+ /**
+ * Called when a option (set with config.addListener()) is changed
+ */
+ void optionChanged(const std::string &value) override;
+
+ void processEvent(Channels channel,
+ const DepricatedEvent &event) override;
+
+ /**
+ * set a following player.
+ */
+ void setFollow(const std::string &player);
+
+ /**
+ * set an imitation player.
+ */
+ void setImitate(const std::string &player);
+
+ /**
+ * setting the next destination of the following, in case of warp
+ */
+ void setNextDest(const int x, const int y);
+
+ int getNextDestX() const A_WARN_UNUSED
+ { return mNextDestX; }
+
+ int getNextDestY() const A_WARN_UNUSED
+ { return mNextDestY; }
+
+ void respawn();
+
+ const FloorItem *getPickUpTarget() const A_WARN_UNUSED
+ { return mPickUpTarget; }
+
+ void unSetPickUpTarget()
+ { mPickUpTarget = nullptr; }
+
+ /**
+ * Stop following a player.
+ */
+ void cancelFollow();
+
+ /**
+ * Get the playername followed by the current player.
+ */
+ const std::string &getFollow() const A_WARN_UNUSED
+ { return mPlayerFollowed; }
+
+ /**
+ * Get the playername imitated by the current player.
+ */
+ const std::string &getImitate() const A_WARN_UNUSED
+ { return mPlayerImitated; }
+
+ /**
+ * Tells the engine whether to check
+ * if the Player Name is to be displayed.
+ */
+ void setCheckNameSetting(const bool checked)
+ { mUpdateName = checked; }
+
+ /**
+ * Gets if the engine has to check
+ * if the Player Name is to be displayed.
+ */
+ bool getCheckNameSetting() const A_WARN_UNUSED
+ { return mUpdateName; }
+
+ void fixAttackTarget();
+
+ void updateNavigateList();
+
+ int getPathLength(const Being *const being) const A_WARN_UNUSED;
+
+ void targetMoved() const;
+
+ void setLastHitFrom(const std::string &n)
+ { mLastHitFrom = n; }
+
+ void waitFor(const std::string &nick);
+
+ void checkNewName(Being *const being);
+
+ void resetYellowBar();
+
+ unsigned char getWalkMask() const override A_WARN_UNUSED;
+
+ void saveHomes();
+
+ void removeHome();
+
+ void stopAdvert();
+
+ bool checAttackPermissions(const Being *const target)
+ const A_WARN_UNUSED;
+
+ void updateStatus() const;
+
+ void setTestParticle(const std::string &fileName,
+ bool updateHash = true);
+
+ std::string getInvertDirectionString();
+
+ std::string getCrazyMoveTypeString();
+
+ std::string getMoveToTargetTypeString();
+
+ std::string getFollowModeString();
+
+ std::string getAttackWeaponTypeString();
+
+ std::string getAttackTypeString();
+
+ std::string getQuickDropCounterString();
+
+ std::string getPickUpTypeString();
+
+ std::string getDebugPathString() const;
+
+ std::string getMagicAttackString();
+
+ std::string getPvpAttackString();
+
+ std::string getImitationModeString();
+
+ std::string getAwayModeString();
+
+ std::string getCameraModeString() const;
+
+ std::string getGameModifiersString();
+
+ protected:
+ void updateCoords() override;
+
+
+ virtual void handleStatusEffect(StatusEffect *const effect,
+ const int effectId);
+
+ void startWalking(const unsigned char dir);
+
+ void changeEquipmentBeforeAttack(const Being *const target) const;
+
+ static void tryMagic(const std::string &spell, const int baseMagic,
+ const int schoolMagic, const int mana);
+
+ const char *getVarItem(const char *const *const arr,
+ const unsigned index,
+ const unsigned sz) const A_WARN_UNUSED;
+
+ void changeMode(unsigned *const var, const unsigned limit,
+ const char *const conf,
+ std::string (LocalPlayer::*const func)(),
+ const unsigned def = 0,
+ const bool save = true);
+
+ void crazyMove1();
+ void crazyMove2();
+ void crazyMove3();
+ void crazyMove4();
+ void crazyMove5();
+ void crazyMove6();
+ void crazyMove7();
+ void crazyMove8();
+ void crazyMove9();
+ void crazyMoveA();
+
+ void loadHomes();
+
+ int mGMLevel;
+
+ // move type
+ unsigned int mInvertDirection;
+ // crazy move type
+ unsigned int mCrazyMoveType;
+ // crazy move state
+ unsigned int mCrazyMoveState;
+ // attack weapon type
+ unsigned int mAttackWeaponType;
+ // quick drop counter
+ unsigned int mQuickDropCounter;
+ // move state. used if mInvertDirection == 2
+ unsigned int mMoveState;
+ // pick up type 1x1, normal aka 2x1, forward aka 2x3, 3x3, 3x3 + 1
+ unsigned int mPickUpType;
+ // magic attack type
+ unsigned int mMagicAttackType;
+ // pvp attack type
+ unsigned int mPvpAttackType;
+ // type how move to target
+ unsigned int mMoveToTargetType;
+ unsigned int mAttackType;
+ unsigned int mFollowMode;
+ unsigned int mImitationMode;
+
+ int mLastTargetX;
+ int mLastTargetY;
+
+ std::map<std::string, Vector> mHomes;
+
+ Being *mTarget;
+
+ /** Follow system **/
+ std::string mPlayerFollowed;
+ std::string mPlayerImitated;
+ int mNextDestX;
+ int mNextDestY;
+
+ FloorItem *mPickUpTarget;
+
+ int mLastAction; // Time stamp of the last action, -1 if none.
+
+ std::vector<int> mStatusEffectIcons;
+
+ int mLocalWalkTime; // Timestamp used to control keyboard walk
+ // messages flooding
+
+ typedef std::pair<std::string, int> MessagePair;
+ /** Queued messages*/
+ std::list<MessagePair> mMessages;
+ int mMessageTime;
+ AwayListener *mAwayListener;
+ OkDialog *mAwayDialog;
+
+ int mPingSendTick;
+ int mPingTime;
+ int mAfkTime;
+ int mActivityTime;
+ int mNavigateX;
+ int mNavigateY;
+ int mNavigateId;
+ int mCrossX;
+ int mCrossY;
+ int mOldX;
+ int mOldY;
+ int mOldTileX;
+ int mOldTileY;
+ Path mNavigatePath;
+
+ std::string mLastHitFrom;
+ std::string mWaitFor;
+ int mAdvertTime;
+ Particle *mTestParticle;
+ std::string mTestParticleName;
+ int mTestParticleTime;
+ unsigned long mTestParticleHash;
+ unsigned char mWalkingDir; // The direction the player is walking in.
+ /** Whether or not the name settings have changed */
+ bool mUpdateName;
+ bool mBlockAdvert;
+ bool mTargetDeadPlayers;
+ bool mServerAttack;
+ bool mEnableAdvert;
+ bool mTradebot;
+ bool mTargetOnlyReachable;
+ bool mDisableGameModifiers;
+ bool mIsServerBuggy;
+ bool mSyncPlayerMove;
+ bool mDrawPath;
+ bool mAttackMoving;
+ bool mAttackNext;
+ bool mShowJobExp;
+ bool mNextStep;
+ // temporary disable crazy moves in moves
+ bool mDisableCrazyMove;
+ bool mGoingToTarget;
+ // Whether or not to continue to attack
+ bool mKeepAttacking;
+ // Tells if the path was set using mouse
+ bool mPathSetByMouse;
+ bool mWaitPing;
+ bool mAwayMode;
+ bool mPseudoAwayMode;
+ bool mShowNavigePath;
+};
+
+extern LocalPlayer *player_node;
+
+#endif // BEING_LOCALPLAYER_H
diff --git a/src/being/playerinfo.cpp b/src/being/playerinfo.cpp
new file mode 100644
index 000000000..9abd617a5
--- /dev/null
+++ b/src/being/playerinfo.cpp
@@ -0,0 +1,398 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2010 The Mana Developers
+ * Copyright (C) 2011-2013 The ManaPlus Developers
+ *
+ * This file is part of The ManaPlus Client.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "being/playerinfo.h"
+
+#include "client.h"
+#include "configuration.h"
+#include "depricatedevent.h"
+#include "inventory.h"
+#include "depricatedlistener.h"
+#include "logger.h"
+
+#include "gui/inventorywindow.h"
+#include "gui/npcdialog.h"
+#include "gui/npcpostdialog.h"
+
+#include "resources/iteminfo.h"
+
+#include "net/inventoryhandler.h"
+#include "net/playerhandler.h"
+
+#include "debug.h"
+
+namespace PlayerInfo
+{
+
+PlayerInfoBackend mData;
+int mCharId = 0;
+
+Inventory *mInventory = nullptr;
+Equipment *mEquipment = nullptr;
+
+#ifdef MANASERV_SUPPORT
+std::map<int, Special> mSpecials;
+signed char mSpecialRechargeUpdateNeeded = 0;
+#endif
+
+bool mTrading = false;
+int mLevelProgress = 0;
+std::set<int> mProtectedItems;
+
+// --- Triggers ---------------------------------------------------------------
+
+void triggerAttr(const int id, const int old)
+{
+ DepricatedEvent event(EVENT_UPDATEATTRIBUTE);
+ event.setInt("id", id);
+ event.setInt("oldValue", old);
+ event.setInt("newValue", mData.mAttributes.find(id)->second);
+ DepricatedEvent::trigger(CHANNEL_ATTRIBUTES, event);
+}
+
+void triggerStat(const int id, const std::string &changed,
+ const int old1, const int old2)
+{
+ const StatMap::const_iterator it = mData.mStats.find(id);
+ if (it == mData.mStats.end())
+ return;
+
+ DepricatedEvent event(EVENT_UPDATESTAT);
+ event.setInt("id", id);
+ const Stat &stat = it->second;
+ event.setInt("base", stat.base);
+ event.setInt("mod", stat.mod);
+ event.setInt("exp", stat.exp);
+ event.setInt("expNeeded", stat.expNeed);
+ event.setString("changed", changed);
+ event.setInt("oldValue1", old1);
+ event.setInt("oldValue2", old2);
+ DepricatedEvent::trigger(CHANNEL_ATTRIBUTES, event);
+}
+
+// --- Attributes -------------------------------------------------------------
+
+int getAttribute(const int id)
+{
+ const IntMap::const_iterator it = mData.mAttributes.find(id);
+ if (it != mData.mAttributes.end())
+ return it->second;
+ else
+ return 0;
+}
+
+void setAttribute(const int id, const int value, const bool notify)
+{
+ const int old = mData.mAttributes[id];
+ mData.mAttributes[id] = value;
+ if (notify)
+ triggerAttr(id, old);
+}
+
+int getSkillLevel(const int id)
+{
+ const IntMap::const_iterator it = mData.mSkills.find(id);
+ if (it != mData.mSkills.end())
+ return it->second;
+ else
+ return 0;
+}
+
+void setSkillLevel(const int id, const int value)
+{
+ mData.mSkills[id] = value;
+}
+
+// --- Stats ------------------------------------------------------------------
+
+int getStatBase(const int id)
+{
+ const StatMap::const_iterator it = mData.mStats.find(id);
+ if (it != mData.mStats.end())
+ return it->second.base;
+ else
+ return 0;
+}
+
+void setStatBase(const int id, const int value, const bool notify)
+{
+ const int old = mData.mStats[id].base;
+ mData.mStats[id].base = value;
+ if (notify)
+ triggerStat(id, "base", old);
+}
+
+int getStatMod(const int id)
+{
+ const StatMap::const_iterator it = mData.mStats.find(id);
+ if (it != mData.mStats.end())
+ return it->second.mod;
+ else
+ return 0;
+}
+
+void setStatMod(const int id, const int value, const bool notify)
+{
+ const int old = mData.mStats[id].mod;
+ mData.mStats[id].mod = value;
+ if (notify)
+ triggerStat(id, "mod", old);
+}
+
+int getStatEffective(const int id)
+{
+ const StatMap::const_iterator it = mData.mStats.find(id);
+ if (it != mData.mStats.end())
+ return it->second.base + it->second.mod;
+ else
+ return 0;
+}
+
+const std::pair<int, int> getStatExperience(const int id)
+{
+ const StatMap::const_iterator it = mData.mStats.find(id);
+ int a, b;
+ if (it != mData.mStats.end())
+ {
+ a = it->second.exp;
+ b = it->second.expNeed;
+ }
+ else
+ {
+ a = 0;
+ b = 0;
+ }
+ return std::pair<int, int>(a, b);
+}
+
+void setStatExperience(const int id, const int have,
+ const int need, const bool notify)
+{
+ Stat &stat = mData.mStats[id];
+
+ const int oldExp = stat.exp;
+ const int oldExpNeed = stat.expNeed;
+ stat.exp = have;
+ stat.expNeed = need;
+ if (notify)
+ triggerStat(id, "exp", oldExp, oldExpNeed);
+}
+
+// --- Inventory / Equipment --------------------------------------------------
+
+Inventory *getInventory()
+{
+ return mInventory;
+}
+
+Inventory *getStorageInventory()
+{
+ return Net::getInventoryHandler()->getStorage();
+}
+
+void clearInventory()
+{
+ if (mEquipment)
+ mEquipment->clear();
+ if (mInventory)
+ mInventory->clear();
+}
+
+void setInventoryItem(const int index, const int id,
+ const int amount, const int refine)
+{
+ bool equipment = false;
+ const int itemType = ItemDB::get(id).getType();
+ if (itemType != ITEM_UNUSABLE && itemType != ITEM_USABLE)
+ equipment = true;
+ if (mInventory)
+ mInventory->setItem(index, id, amount, refine, equipment);
+}
+
+Equipment *getEquipment()
+{
+ return mEquipment;
+}
+
+Item *getEquipment(const unsigned int slot)
+{
+ if (mEquipment)
+ return mEquipment->getEquipment(slot);
+ else
+ return nullptr;
+}
+
+void setEquipmentBackend(Equipment::Backend *const backend)
+{
+ if (mEquipment)
+ mEquipment->setBackend(backend);
+}
+
+// --- Misc -------------------------------------------------------------------
+
+void setBackend(const PlayerInfoBackend &backend)
+{
+ mData = backend;
+}
+
+void setCharId(const int charId)
+{
+ mCharId = charId;
+}
+
+int getCharId()
+{
+ return mCharId;
+}
+
+void logic()
+{
+#ifdef MANASERV_SUPPORT
+ if ((mSpecialRechargeUpdateNeeded % 11) == 0)
+ {
+ mSpecialRechargeUpdateNeeded = 0;
+ FOR_EACH (SpecialsMap::iterator, it, mSpecials)
+ {
+ Special &special = it->second;
+ special.currentMana += special.recharge;
+ if (special.currentMana > special.neededMana)
+ special.currentMana = special.neededMana;
+ }
+ }
+ mSpecialRechargeUpdateNeeded++;
+#endif
+}
+
+bool isTrading()
+{
+ return mTrading;
+}
+
+void setTrading(const bool trading)
+{
+ mTrading = trading;
+}
+
+void updateAttrs()
+{
+ const Net::PlayerHandler *const handler = Net::getPlayerHandler();
+ if (!handler)
+ return;
+ const int attr = handler->getAttackLocation();
+ const int attackDelay = getStatBase(ATTACK_DELAY);
+ if (attr != -1 && attackDelay)
+ {
+ setStatBase(ATTACK_SPEED, getStatBase(attr) * 1000
+ / attackDelay, false);
+ setStatMod(ATTACK_SPEED, getStatMod(attr) * 1000
+ / attackDelay, true);
+ }
+ else
+ {
+ setStatBase(ATTACK_SPEED, 0, false);
+ setStatMod(ATTACK_SPEED, 0, true);
+ }
+}
+
+void init()
+{
+}
+
+void deinit()
+{
+ clearInventory();
+}
+
+void loadData()
+{
+ mProtectedItems.clear();
+ splitToIntSet(mProtectedItems,
+ serverConfig.getStringValue("protectedItems"), ',');
+}
+
+void clear()
+{
+ mData.mSkills.clear();
+}
+
+bool isTalking()
+{
+ return NpcDialog::isActive() || NpcPostDialog::isActive()
+ || InventoryWindow::isStorageActive();
+}
+
+void gameDestroyed()
+{
+ delete mInventory;
+ mInventory = nullptr;
+ delete mEquipment;
+ mEquipment = nullptr;
+}
+
+void stateChange(const int state)
+{
+ if (state == STATE_GAME)
+ {
+ if (!mInventory)
+ {
+ mInventory = new Inventory(Inventory::INVENTORY);
+ mEquipment = new Equipment();
+ }
+ }
+}
+
+static void saveProtectedItems()
+{
+ std::string str;
+ std::set<int>::const_iterator it = mProtectedItems.begin();
+ std::set<int>::const_iterator it_end = mProtectedItems.end();
+ if (it != it_end)
+ {
+ str.append(toString(*it));
+ ++ it;
+ }
+ while (it != it_end)
+ {
+ str.append(",").append(toString(*it));
+ ++ it;
+ }
+ serverConfig.setValue("protectedItems", str);
+ serverConfig.write();
+}
+
+void protectItem(const int id)
+{
+ mProtectedItems.insert(id);
+ saveProtectedItems();
+}
+
+void unprotectItem(const int id)
+{
+ mProtectedItems.erase(id);
+ saveProtectedItems();
+}
+
+bool isItemProtected(const int id)
+{
+ return mProtectedItems.find(id) != mProtectedItems.end();
+}
+
+} // namespace PlayerInfo
diff --git a/src/being/playerinfo.h b/src/being/playerinfo.h
new file mode 100644
index 000000000..37aa9736c
--- /dev/null
+++ b/src/being/playerinfo.h
@@ -0,0 +1,267 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2010 The Mana Developers
+ * Copyright (C) 2011-2013 The ManaPlus Developers
+ *
+ * This file is part of The ManaPlus Client.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef BEING_PLAYERINFO_H
+#define BEING_PLAYERINFO_H
+
+#include "equipment.h"
+
+#include <map>
+#include <string>
+
+
+/**
+ * Stat information storage structure.
+ */
+struct Stat
+{
+ int base;
+ int mod;
+ int exp;
+ int expNeed;
+};
+
+typedef std::map<int, int> IntMap;
+typedef std::map<int, Stat> StatMap;
+
+/**
+ * Backend for core player information.
+ */
+struct PlayerInfoBackend final
+{
+ PlayerInfoBackend() :
+ mAttributes(),
+ mStats(),
+ mSkills()
+ {
+ }
+
+ IntMap mAttributes;
+ StatMap mStats;
+ IntMap mSkills;
+};
+
+class Equipment;
+class Inventory;
+class Item;
+
+/**
+ * Special information storage structure.
+ */
+struct Special final
+{
+ int currentMana;
+ int neededMana;
+ int recharge;
+};
+
+typedef std::map<int, Special> SpecialsMap;
+
+/**
+ * A database like namespace which holds global info about the localplayer
+ *
+ * NOTE: 'bool notify' is used to determine if a event is to be triggered.
+ */
+namespace PlayerInfo
+{
+ /**
+ * Standard attributes for players.
+ */
+ enum Attribute
+ {
+ LEVEL = 0,
+ HP,
+ MAX_HP,
+ MP,
+ MAX_MP,
+ EXP,
+ EXP_NEEDED,
+ MONEY,
+ TOTAL_WEIGHT,
+ MAX_WEIGHT,
+ SKILL_POINTS,
+ CHAR_POINTS,
+ CORR_POINTS,
+ ATTACK_DELAY = 100,
+ ATTACK_RANGE = 101,
+ WALK_SPEED = 102,
+ ATTACK_SPEED = 103
+ };
+
+// --- Attributes -------------------------------------------------------------
+
+ /**
+ * Returns the value of the given attribute.
+ */
+ int getAttribute(const int id) A_WARN_UNUSED;
+
+ /**
+ * Changes the value of the given attribute.
+ */
+ void setAttribute(const int id, const int value,
+ const bool notify = true);
+
+ int getSkillLevel(const int id) A_WARN_UNUSED;
+
+ void setSkillLevel(const int id, const int value);
+
+// --- Stats ------------------------------------------------------------------
+
+ /**
+ * Returns the base value of the given stat.
+ */
+ int getStatBase(const int id) A_WARN_UNUSED;
+
+ /**
+ * Changes the base value of the given stat.
+ */
+ void setStatBase(const int id, const int value,
+ const bool notify = true);
+
+ /**
+ * Returns the modifier for the given stat.
+ */
+ int getStatMod(const int id) A_WARN_UNUSED;
+
+ /**
+ * Changes the modifier for the given stat.
+ */
+ void setStatMod(const int id, const int value,
+ const bool notify = true);
+
+ /**
+ * Returns the current effective value of the given stat. Effective is base
+ * + mod
+ */
+ int getStatEffective(const int id) A_WARN_UNUSED;
+
+ /**
+ * Changes the level of the given stat.
+ */
+ void setStatLevel(int id, int value, bool notify = true);
+
+ /**
+ * Returns the experience of the given stat.
+ */
+ const std::pair<int, int> getStatExperience(const int id) A_WARN_UNUSED;
+
+ /**
+ * Changes the experience of the given stat.
+ */
+ void setStatExperience(const int id, const int have,
+ const int need, const bool notify = true);
+
+// --- Inventory / Equipment --------------------------------------------------
+
+ /**
+ * Returns the player's inventory.
+ */
+ Inventory *getInventory() A_WARN_UNUSED;
+
+ Inventory *getStorageInventory() A_WARN_UNUSED;
+
+ /**
+ * Clears the player's inventory and equipment.
+ */
+ void clearInventory();
+
+ void clear();
+
+ /**
+ * Changes the inventory item at the given slot.
+ */
+ void setInventoryItem(const int index, const int id,
+ const int amount, const int refine);
+
+ /**
+ * Returns the player's equipment.
+ */
+ Equipment *getEquipment() A_WARN_UNUSED;
+
+ /**
+ * Returns the player's equipment at the given slot.
+ */
+ Item *getEquipment(const unsigned int slot) A_WARN_UNUSED;
+
+// --- Misc -------------------------------------------------------------------
+
+ /**
+ * Changes the internal PlayerInfoBackend reference;
+ */
+ void setBackend(const PlayerInfoBackend &backend);
+
+ void setCharId(const int charId);
+
+ int getCharId();
+
+ /**
+ * Does necessary updates every tick.
+ */
+ void logic();
+
+ /**
+ * Returns true if the player is involved in a trade at the moment, false
+ * otherwise.
+ */
+ bool isTrading();
+
+ /**
+ * Sets whether the player is currently involved in trade or not.
+ */
+ void setTrading(const bool trading);
+
+ void updateAttrs();
+
+ /**
+ * Initializes some internals.
+ */
+ void init();
+
+ void deinit();
+
+ void loadData();
+
+ bool isTalking();
+
+ void gameDestroyed();
+
+ void stateChange(const int state);
+
+ void triggerAttr(int id);
+
+ void triggerAttr(const int id, const int old);
+
+ void triggerStat(int id);
+
+ void triggerStat(const int id, const std::string &changed,
+ const int old1, const int old2 = 0);
+
+ void setEquipmentBackend(Equipment::Backend *const backend);
+
+ void protectItem(const int id);
+
+ void unprotectItem(const int id);
+
+ bool isItemProtected(const int id);
+
+} // namespace PlayerInfo
+
+#endif // BEING_PLAYERINFO_H
diff --git a/src/being/playerrelations.cpp b/src/being/playerrelations.cpp
new file mode 100644
index 000000000..cdd9ca4bc
--- /dev/null
+++ b/src/being/playerrelations.cpp
@@ -0,0 +1,630 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2008-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ * Copyright (C) 2011-2013 The ManaPlus Developers
+ *
+ * This file is part of The ManaPlus Client.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "being/playerrelations.h"
+
+#include "actorspritemanager.h"
+#include "configuration.h"
+
+#include "being/localplayer.h"
+
+#include "render/graphics.h"
+
+#include "utils/dtor.h"
+#include "utils/gettext.h"
+
+#include <algorithm>
+
+#include "debug.h"
+
+static const char *const PLAYER_IGNORE_STRATEGY_NOP = "nop";
+static const char *const PLAYER_IGNORE_STRATEGY_EMOTE0 = "emote0";
+static const char *const DEFAULT_IGNORE_STRATEGY
+ = PLAYER_IGNORE_STRATEGY_EMOTE0;
+
+static const char *const NAME = "name";
+static const char *const RELATION = "relation";
+
+static const unsigned int IGNORE_EMOTE_TIME = 100;
+
+typedef std::map<std::string, PlayerRelation *> PlayerRelations;
+typedef PlayerRelations::const_iterator PlayerRelationsCIter;
+typedef std::list<PlayerRelationsListener *> PlayerRelationListeners;
+typedef PlayerRelationListeners::const_iterator PlayerRelationListenersCIter;
+
+class SortPlayersFunctor final
+{
+ public:
+ bool operator() (const std::string &str1,
+ const std::string &str2) const
+ {
+ std::string s1 = str1;
+ std::string s2 = str2;
+ toLower(s1);
+ toLower(s2);
+ if (s1 == s2)
+ return str1 < str2;
+ return s1 < s2;
+ }
+} playersRelSorter;
+
+// (De)serialisation class
+class PlayerConfSerialiser final :
+ public ConfigurationListManager<std::pair<std::string, PlayerRelation *>,
+ std::map<std::string, PlayerRelation *> *>
+{
+public:
+ virtual ConfigurationObject *writeConfigItem(
+ const std::pair<std::string, PlayerRelation *> &value,
+ ConfigurationObject *const cobj) const override
+ {
+ if (!cobj || !value.second)
+ return nullptr;
+ cobj->setValue(NAME, value.first);
+ cobj->setValue(RELATION, toString(
+ static_cast<int>(value.second->mRelation)));
+
+ return cobj;
+ }
+
+ virtual std::map<std::string, PlayerRelation *> *
+ readConfigItem(const ConfigurationObject *const cobj,
+ std::map<std::string, PlayerRelation *>
+ *const container) const override
+ {
+ if (!cobj)
+ return container;
+ const std::string name = cobj->getValue(NAME, "");
+ if (name.empty())
+ return container;
+
+ if (!(*container)[name])
+ {
+ const int v = cobj->getValueInt(RELATION,
+ static_cast<int>(PlayerRelation::NEUTRAL));
+
+ (*container)[name] = new PlayerRelation(
+ static_cast<PlayerRelation::Relation>(v));
+ }
+ // otherwise ignore the duplicate entry
+
+ return container;
+ }
+};
+
+static PlayerConfSerialiser player_conf_serialiser; // stateless singleton
+
+const unsigned int PlayerRelation::RELATION_PERMISSIONS[RELATIONS_NR] =
+{
+ /* NEUTRAL */ 0, // we always fall back to the defaults anyway
+ /* FRIEND */ EMOTE | SPEECH_FLOAT | SPEECH_LOG | WHISPER | TRADE,
+ /* DISREGARDED*/ EMOTE | SPEECH_FLOAT,
+ /* IGNORED */ 0,
+ /* ERASED */ INVISIBLE,
+ /* BLACKLISTED */ SPEECH_LOG | WHISPER,
+ /* ENEMY2 */ EMOTE | SPEECH_FLOAT | SPEECH_LOG | WHISPER | TRADE
+};
+
+PlayerRelation::PlayerRelation(const Relation relation) :
+ mRelation(relation)
+{
+}
+
+PlayerRelationsManager::PlayerRelationsManager() :
+ mPersistIgnores(false),
+ mDefaultPermissions(PlayerRelation::DEFAULT),
+ mIgnoreStrategy(nullptr),
+ mRelations(),
+ mListeners(),
+ mIgnoreStrategies()
+{
+}
+
+PlayerRelationsManager::~PlayerRelationsManager()
+{
+ delete_all(mIgnoreStrategies);
+
+ FOR_EACH (PlayerRelationsCIter, it, mRelations)
+ delete it->second;
+ mRelations.clear();
+}
+
+void PlayerRelationsManager::clear()
+{
+ StringVect *const names = getPlayers();
+ FOR_EACHP (StringVectCIter, it, names)
+ removePlayer(*it);
+ delete names;
+}
+
+static const char *const PERSIST_IGNORE_LIST = "persistent-player-list";
+static const char *const PLAYER_IGNORE_STRATEGY = "player-ignore-strategy";
+static const char *const DEFAULT_PERMISSIONS = "default-player-permissions";
+
+int PlayerRelationsManager::getPlayerIgnoreStrategyIndex(
+ const std::string &name)
+{
+ const std::vector<PlayerIgnoreStrategy *> *const strategies
+ = getPlayerIgnoreStrategies();
+
+ if (!strategies)
+ return -1;
+
+ const size_t sz = strategies->size();
+ for (size_t i = 0; i < sz; i++)
+ {
+ if ((*strategies)[i]->mShortName == name)
+ return i;
+ }
+
+ return -1;
+}
+
+void PlayerRelationsManager::load(const bool oldConfig)
+{
+ Configuration *cfg;
+ if (oldConfig)
+ cfg = &config;
+ else
+ cfg = &serverConfig;
+ clear();
+
+ mPersistIgnores = cfg->getValue(PERSIST_IGNORE_LIST, 1);
+ mDefaultPermissions = static_cast<int>(cfg->getValue(DEFAULT_PERMISSIONS,
+ mDefaultPermissions));
+
+ const std::string ignore_strategy_name = cfg->getValue(
+ PLAYER_IGNORE_STRATEGY, DEFAULT_IGNORE_STRATEGY);
+ const int ignore_strategy_index = getPlayerIgnoreStrategyIndex(
+ ignore_strategy_name);
+
+ if (ignore_strategy_index >= 0)
+ {
+ setPlayerIgnoreStrategy((*getPlayerIgnoreStrategies())
+ [ignore_strategy_index]);
+ }
+
+ cfg->getList<std::pair<std::string, PlayerRelation *>,
+ std::map<std::string, PlayerRelation *> *>
+ ("player", &(mRelations), &player_conf_serialiser);
+}
+
+
+void PlayerRelationsManager::init()
+{
+ load();
+
+ if (!mPersistIgnores)
+ {
+ clear(); // Yes, we still keep them around in the config file
+ // until the next update.
+ }
+
+ FOR_EACH (PlayerRelationListenersCIter, it, mListeners)
+ (*it)->updateAll();
+}
+
+void PlayerRelationsManager::store() const
+{
+ serverConfig.setList<std::map<std::string,
+ PlayerRelation *>::const_iterator,
+ std::pair<std::string, PlayerRelation *>,
+ std::map<std::string, PlayerRelation *> *>
+ ("player", mRelations.begin(), mRelations.end(),
+ &player_conf_serialiser);
+
+ serverConfig.setValue(DEFAULT_PERMISSIONS, mDefaultPermissions);
+ serverConfig.setValue(PERSIST_IGNORE_LIST, mPersistIgnores);
+ serverConfig.setValue(PLAYER_IGNORE_STRATEGY,
+ mIgnoreStrategy ? mIgnoreStrategy->mShortName
+ : DEFAULT_IGNORE_STRATEGY);
+
+ serverConfig.write();
+}
+
+void PlayerRelationsManager::signalUpdate(const std::string &name)
+{
+ FOR_EACH (PlayerRelationListenersCIter, it, mListeners)
+ (*it)->updatedPlayer(name);
+
+ if (actorSpriteManager)
+ {
+ Being *const being = actorSpriteManager->findBeingByName(
+ name, Being::PLAYER);
+
+ if (being && being->getType() == Being::PLAYER)
+ being->updateColors();
+ }
+}
+
+unsigned int PlayerRelationsManager::checkPermissionSilently(
+ const std::string &player_name, const unsigned int flags) const
+{
+ const std::map<std::string, PlayerRelation *>::const_iterator
+ it = mRelations.find(player_name);
+ if (it == mRelations.end())
+ {
+ return mDefaultPermissions & flags;
+ }
+ else
+ {
+ const PlayerRelation *const r = (*it).second;
+ unsigned int permissions =
+ PlayerRelation::RELATION_PERMISSIONS[r->mRelation];
+
+ switch (r->mRelation)
+ {
+ case PlayerRelation::NEUTRAL:
+ permissions = mDefaultPermissions;
+ break;
+
+ case PlayerRelation::FRIEND:
+ permissions |= mDefaultPermissions; // widen
+ break;
+
+ case PlayerRelation::DISREGARDED:
+ case PlayerRelation::IGNORED:
+ case PlayerRelation::ERASED:
+ case PlayerRelation::BLACKLISTED:
+ case PlayerRelation::ENEMY2:
+ default:
+ permissions &= mDefaultPermissions; // narrow
+ }
+
+ return permissions & flags;
+ }
+}
+
+bool PlayerRelationsManager::hasPermission(const Being *const being,
+ const unsigned int flags) const
+{
+ if (!being)
+ return false;
+
+ if (being->getType() == ActorSprite::PLAYER)
+ return hasPermission(being->getName(), flags) == flags;
+ return true;
+}
+
+bool PlayerRelationsManager::hasPermission(const std::string &name,
+ const unsigned int flags) const
+{
+ if (!actorSpriteManager)
+ return false;
+
+ const unsigned int rejections = flags
+ & ~checkPermissionSilently(name, flags);
+ const bool permitted = (rejections == 0);
+
+ if (!permitted)
+ {
+ // execute `ignore' strategy, if possible
+ if (mIgnoreStrategy)
+ {
+ Being *const b = actorSpriteManager->findBeingByName(
+ name, ActorSprite::PLAYER);
+
+ if (b && b->getType() == ActorSprite::PLAYER)
+ mIgnoreStrategy->ignore(b, rejections);
+ }
+ }
+
+ return permitted;
+}
+
+void PlayerRelationsManager::setRelation(const std::string &player_name,
+ const PlayerRelation::Relation
+ relation)
+{
+ if (!player_node || (relation != PlayerRelation::NEUTRAL
+ && player_node->getName() == player_name))
+ {
+ return;
+ }
+
+ PlayerRelation *const r = mRelations[player_name];
+ if (!r)
+ mRelations[player_name] = new PlayerRelation(relation);
+ else
+ r->mRelation = relation;
+
+ signalUpdate(player_name);
+}
+
+StringVect *PlayerRelationsManager::getPlayers() const
+{
+ StringVect *const retval = new StringVect();
+
+ FOR_EACH (PlayerRelationsCIter, it, mRelations)
+ {
+ if (it->second)
+ retval->push_back(it->first);
+ }
+
+ std::sort(retval->begin(), retval->end(), playersRelSorter);
+
+ return retval;
+}
+
+StringVect *PlayerRelationsManager::getPlayersByRelation(
+ const PlayerRelation::Relation rel) const
+{
+ StringVect *const retval = new StringVect();
+
+ FOR_EACH (PlayerRelationsCIter, it, mRelations)
+ {
+ if (it->second && it->second->mRelation == rel)
+ retval->push_back(it->first);
+ }
+
+ std::sort(retval->begin(), retval->end(), playersRelSorter);
+
+ return retval;
+}
+
+void PlayerRelationsManager::removePlayer(const std::string &name)
+{
+ delete mRelations[name];
+ mRelations.erase(name);
+ signalUpdate(name);
+}
+
+
+PlayerRelation::Relation PlayerRelationsManager::getRelation(
+ const std::string &name) const
+{
+ const std::map<std::string, PlayerRelation *>::const_iterator
+ it = mRelations.find(name);
+ if (it != mRelations.end())
+ return (*it).second->mRelation;
+
+ return PlayerRelation::NEUTRAL;
+}
+
+////////////////////////////////////////
+// defaults
+
+unsigned int PlayerRelationsManager::getDefault() const
+{
+ return mDefaultPermissions;
+}
+
+void PlayerRelationsManager::setDefault(const unsigned int permissions)
+{
+ mDefaultPermissions = permissions;
+
+ store();
+ signalUpdate("");
+}
+
+void PlayerRelationsManager::ignoreTrade(const std::string &name)
+{
+ if (name.empty())
+ return;
+
+ const PlayerRelation::Relation relation = getRelation(name);
+
+ if (relation == PlayerRelation::IGNORED
+ || relation == PlayerRelation::DISREGARDED
+ || relation == PlayerRelation::BLACKLISTED
+ || relation == PlayerRelation::ERASED)
+ {
+ return;
+ }
+ else
+ {
+ player_relations.setRelation(name, PlayerRelation::BLACKLISTED);
+ }
+}
+
+bool PlayerRelationsManager::checkBadRelation(const std::string &name) const
+{
+ if (name.empty())
+ return true;
+
+ const PlayerRelation::Relation relation = getRelation(name);
+
+ if (relation == PlayerRelation::IGNORED
+ || relation == PlayerRelation::DISREGARDED
+ || relation == PlayerRelation::BLACKLISTED
+ || relation == PlayerRelation::ERASED
+ || relation == PlayerRelation::ENEMY2)
+ {
+ return true;
+ }
+ return false;
+}
+
+////////////////////////////////////////
+// ignore strategies
+
+
+class PIS_nothing final : public PlayerIgnoreStrategy
+{
+public:
+ PIS_nothing() :
+ PlayerIgnoreStrategy()
+ {
+ // TRANSLATORS: ignore/unignore action
+ mDescription = _("Completely ignore");
+ mShortName = PLAYER_IGNORE_STRATEGY_NOP;
+ }
+
+ virtual void ignore(Being *const being A_UNUSED,
+ const unsigned int flags A_UNUSED) const override
+ {
+ }
+};
+
+class PIS_dotdotdot final : public PlayerIgnoreStrategy
+{
+public:
+ PIS_dotdotdot() :
+ PlayerIgnoreStrategy()
+ {
+ // TRANSLATORS: ignore/unignore action
+ mDescription = _("Print '...'");
+ mShortName = "dotdotdot";
+ }
+
+ virtual void ignore(Being *const being,
+ const unsigned int flags A_UNUSED) const override
+ {
+ if (!being)
+ return;
+
+ logger->log("ignoring: " + being->getName());
+ being->setSpeech("...");
+ }
+};
+
+
+class PIS_blinkname final : public PlayerIgnoreStrategy
+{
+public:
+ PIS_blinkname() :
+ PlayerIgnoreStrategy()
+ {
+ // TRANSLATORS: ignore/unignore action
+ mDescription = _("Blink name");
+ mShortName = "blinkname";
+ }
+
+ virtual void ignore(Being *const being,
+ const unsigned int flags A_UNUSED) const override
+ {
+ if (!being)
+ return;
+
+ logger->log("ignoring: " + being->getName());
+ being->flashName(200);
+ }
+};
+
+class PIS_emote final : public PlayerIgnoreStrategy
+{
+public:
+ PIS_emote(const uint8_t emote_nr, const std::string &description,
+ const std::string &shortname) :
+ PlayerIgnoreStrategy(),
+ mEmotion(emote_nr)
+ {
+ mDescription = description;
+ mShortName = shortname;
+ }
+
+ virtual void ignore(Being *const being,
+ const unsigned int flags A_UNUSED) const override
+ {
+ if (!being)
+ return;
+
+ being->setEmote(mEmotion, IGNORE_EMOTE_TIME);
+ }
+ uint8_t mEmotion;
+};
+
+std::vector<PlayerIgnoreStrategy *> *
+PlayerRelationsManager::getPlayerIgnoreStrategies()
+{
+ if (mIgnoreStrategies.empty())
+ {
+ // not initialised yet?
+ mIgnoreStrategies.push_back(new PIS_emote(FIRST_IGNORE_EMOTE,
+ // TRANSLATORS: ignore strategi
+ _("Floating '...' bubble"),
+ PLAYER_IGNORE_STRATEGY_EMOTE0));
+ mIgnoreStrategies.push_back(new PIS_emote(FIRST_IGNORE_EMOTE + 1,
+ // TRANSLATORS: ignore strategi
+ _("Floating bubble"),
+ "emote1"));
+ mIgnoreStrategies.push_back(new PIS_nothing);
+ mIgnoreStrategies.push_back(new PIS_dotdotdot);
+ mIgnoreStrategies.push_back(new PIS_blinkname);
+ }
+ return &mIgnoreStrategies;
+}
+
+bool PlayerRelationsManager::isGoodName(const std::string &name) const
+{
+ const size_t size = name.size();
+
+ if (size < 3)
+ return true;
+
+ const std::map<std::string, PlayerRelation *>::const_iterator
+ it = mRelations.find(name);
+ if (it != mRelations.end())
+ return true;
+
+ return checkName(name);
+}
+
+bool PlayerRelationsManager::isGoodName(Being *const being) const
+{
+ if (!being)
+ return false;
+ if (being->getGoodStatus() != -1)
+ return (being->getGoodStatus() == 1);
+
+ const std::string name = being->getName();
+ const size_t size = name.size();
+
+ if (size < 3)
+ return true;
+
+ const std::map<std::string, PlayerRelation *>::const_iterator
+ it = mRelations.find(name);
+ if (it != mRelations.end())
+ return true;
+
+ const bool status = checkName(name);
+ being->setGoodStatus(status ? 1 : 0);
+ return status;
+}
+
+bool PlayerRelationsManager::checkName(const std::string &name) const
+{
+ const size_t size = name.size();
+ const std::string check = config.getStringValue("unsecureChars");
+ const std::string lastChar = name.substr(size - 1, 1);
+
+ if (name.substr(0, 1) == " " || lastChar == " " || lastChar == "."
+ || name.find(" ") != std::string::npos)
+ {
+ return false;
+ }
+ else if (check.empty())
+ {
+ return true;
+ }
+ else if (name.find_first_of(check) != std::string::npos)
+ {
+ return false;
+ }
+ else
+ {
+ return true;
+ }
+}
+
+PlayerRelationsManager player_relations;
diff --git a/src/being/playerrelations.h b/src/being/playerrelations.h
new file mode 100644
index 000000000..f7e9eba60
--- /dev/null
+++ b/src/being/playerrelations.h
@@ -0,0 +1,285 @@
+/*
+ * The ManaPlus Client
+ * Copyright (C) 2008-2009 The Mana World Development Team
+ * Copyright (C) 2009-2010 The Mana Developers
+ * Copyright (C) 2011-2013 The ManaPlus Developers
+ *
+ * This file is part of The ManaPlus Client.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef BEING_PLAYERRELATIONS_H
+#define BEING_PLAYERRELATIONS_H
+
+#include "utils/stringvector.h"
+
+#include <list>
+#include <map>
+
+#include "localconsts.h"
+
+class Being;
+
+struct PlayerRelation final
+{
+ static const unsigned int EMOTE = (1 << 0);
+ static const unsigned int SPEECH_FLOAT = (1 << 1);
+ static const unsigned int SPEECH_LOG = (1 << 2);
+ static const unsigned int WHISPER = (1 << 3);
+ static const unsigned int TRADE = (1 << 4);
+ static const unsigned int INVISIBLE = (1 << 5);
+ static const unsigned int BLACKLIST = (1 << 6);
+ static const unsigned int ENEMY = (1 << 7);
+
+ static const unsigned int RELATIONS_NR = 7;
+ static const unsigned int RELATION_PERMISSIONS[RELATIONS_NR];
+
+ static const unsigned int DEFAULT = EMOTE
+ | SPEECH_FLOAT
+ | SPEECH_LOG
+ | WHISPER
+ | TRADE;
+ enum Relation
+ {
+ NEUTRAL = 0,
+ FRIEND = 1,
+ DISREGARDED = 2,
+ IGNORED = 3,
+ ERASED = 4,
+ BLACKLISTED = 5,
+ ENEMY2 = 6
+ };
+
+ explicit PlayerRelation(const Relation relation);
+
+ A_DELETE_COPY(PlayerRelation)
+
+ Relation mRelation; // bitmask for all of the above
+};
+
+
+/**
+ * Ignore strategy: describes how we should handle ignores.
+ */
+class PlayerIgnoreStrategy
+{
+ public:
+ std::string mDescription;
+ std::string mShortName;
+
+ PlayerIgnoreStrategy() :
+ mDescription(),
+ mShortName()
+ {
+ }
+
+ A_DELETE_COPY(PlayerIgnoreStrategy)
+
+ virtual ~PlayerIgnoreStrategy()
+ { }
+
+ /**
+ * Handle the ignoring of the indicated action by the indicated player.
+ */
+ virtual void ignore(Being *const being,
+ const unsigned int flags) const = 0;
+};
+
+class PlayerRelationsListener
+{
+ public:
+ PlayerRelationsListener()
+ { }
+
+ virtual ~PlayerRelationsListener()
+ { }
+
+ virtual void updatedPlayer(const std::string &name) = 0;
+
+ virtual void updateAll() = 0;
+};
+
+/**
+ * Player relations class, represents any particular relations and/or
+ * preferences the user of the local client has wrt other players (identified
+ * by std::string).
+ */
+class PlayerRelationsManager final
+{
+ public:
+ PlayerRelationsManager();
+
+ A_DELETE_COPY(PlayerRelationsManager)
+
+ ~PlayerRelationsManager();
+
+ /**
+ * Initialise player relations manager (load config file etc.)
+ */
+ void init();
+
+ /**
+ * Load configuration from our config file, or substitute defaults.
+ */
+ void load(const bool oldConfig = false);
+
+ /**
+ * Save configuration to our config file.
+ */
+ void store() const;
+
+ /**
+ * Determines whether the player in question is being ignored, filtered by
+ * the specified flags.
+ */
+ unsigned int checkPermissionSilently(const std::string &player_name,
+ const unsigned int flags)
+ const A_WARN_UNUSED;
+
+ /**
+ * Tests whether the player in question is being ignored for any of the
+ * actions in the specified flags. If so, trigger appropriate side effects
+ * if requested by the player.
+ */
+ bool hasPermission(const Being *const being,
+ const unsigned int flags) const A_WARN_UNUSED;
+
+ bool hasPermission(const std::string &being,
+ const unsigned int flags) const A_WARN_UNUSED;
+
+ /**
+ * Updates the relationship with this player.
+ */
+ void setRelation(const std::string &name,
+ const PlayerRelation::Relation relation);
+
+ /**
+ * Updates the relationship with this player.
+ */
+ PlayerRelation::Relation getRelation(const std::string &name)
+ const A_WARN_UNUSED;
+
+ /**
+ * Deletes the information recorded for a player.
+ */
+ void removePlayer(const std::string &name);
+
+ /**
+ * Retrieves the default permissions.
+ */
+ unsigned int getDefault() const A_WARN_UNUSED;
+
+ /**
+ * Sets the default permissions.
+ */
+ void setDefault(const unsigned int permissions);
+
+ /**
+ * Retrieves all known player ignore strategies.
+ *
+ * The player ignore strategies are allocated statically and must
+ * not be deleted.
+ */
+ std::vector<PlayerIgnoreStrategy *> *getPlayerIgnoreStrategies()
+ A_WARN_UNUSED;
+
+ /**
+ * Return the current player ignore strategy.
+ *
+ * \return A player ignore strategy, or nullptr
+ */
+ const PlayerIgnoreStrategy *getPlayerIgnoreStrategy() const
+ A_WARN_UNUSED
+ { return mIgnoreStrategy; }
+
+ /**
+ * Sets the strategy to call when ignoring players.
+ */
+ void setPlayerIgnoreStrategy(PlayerIgnoreStrategy *const strategy)
+ { mIgnoreStrategy = strategy; }
+
+ /**
+ * For a given ignore strategy short name, find the appropriate index
+ * in the ignore strategies vector.
+ *
+ * \param The short name of the ignore strategy to look up
+ * \return The appropriate index, or -1
+ */
+ int getPlayerIgnoreStrategyIndex(const std::string &shortname)
+ A_WARN_UNUSED;
+
+ /**
+ * Retrieves a sorted vector of all players for which we have any
+ * relations recorded.
+ */
+ StringVect *getPlayers() const A_WARN_UNUSED;
+
+ StringVect *getPlayersByRelation(const PlayerRelation::Relation rel)
+ const A_WARN_UNUSED;
+
+ /**
+ * Removes all recorded player info.
+ */
+ void clear();
+
+ /**
+ * Do we persist our `ignore' setup?
+ */
+ bool getPersistIgnores() const
+ { return mPersistIgnores; }
+
+ void ignoreTrade(const std::string &name);
+
+ bool isGoodName(Being *const being) const A_WARN_UNUSED;
+
+ bool isGoodName(const std::string &name) const A_WARN_UNUSED;
+
+ /**
+ * Change the `ignore persist' flag.
+ *
+ * @param value Whether to persist ignores
+ */
+ void setPersistIgnores(const bool value)
+ { mPersistIgnores = value; }
+
+ void addListener(PlayerRelationsListener *const listener)
+ { mListeners.push_back(listener); }
+
+ void removeListener(PlayerRelationsListener *const listener)
+ { mListeners.remove(listener); }
+
+ bool checkBadRelation(const std::string &name) const A_WARN_UNUSED;
+
+ private:
+ void signalUpdate(const std::string &name);
+
+ bool mPersistIgnores; // If NOT set, we delete the
+ // ignored data upon reloading
+ unsigned int mDefaultPermissions;
+
+ bool checkName(const std::string &name) const A_WARN_UNUSED;
+
+ PlayerIgnoreStrategy *mIgnoreStrategy;
+ std::map<std::string, PlayerRelation *> mRelations;
+ std::list<PlayerRelationsListener *> mListeners;
+ std::vector<PlayerIgnoreStrategy *> mIgnoreStrategies;
+};
+
+
+extern PlayerRelationsManager player_relations; // singleton representation
+ // of player relations
+
+
+#endif // BEING_PLAYERRELATIONS_H