diff options
Diffstat (limited to 'src/being/localplayer.cpp')
-rw-r--r-- | src/being/localplayer.cpp | 4355 |
1 files changed, 4355 insertions, 0 deletions
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(); + } +} |