/* * The ManaPlus Client * Copyright (C) 2004-2009 The Mana World Development Team * Copyright (C) 2009-2010 The Mana Developers * Copyright (C) 2011-2020 The ManaPlus Developers * Copyright (C) 2020-2023 The ManaVerse 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 . */ #include "being/localplayer.h" #include "actormanager.h" #include "configuration.h" #include "gamemodifiers.h" #include "guild.h" #include "party.h" #include "settings.h" #include "soundmanager.h" #include "statuseffect.h" #include "being/beingflag.h" #include "being/crazymoves.h" #include "being/playerinfo.h" #include "being/playerrelations.h" #include "const/sound.h" #include "enums/equipslot.h" #include "enums/being/beingdirection.h" #include "enums/resources/map/blockmask.h" #include "enums/resources/map/mapitemtype.h" #include "particle/particleengine.h" #include "input/keyboardconfig.h" #include "gui/gui.h" #include "gui/userpalette.h" #include "gui/popupmanager.h" #include "gui/windows/chatwindow.h" #include "gui/windows/ministatuswindow.h" #include "gui/windows/okdialog.h" #include "gui/windows/outfitwindow.h" #include "gui/windows/shopwindow.h" #include "gui/windows/socialwindow.h" #include "gui/windows/statuswindow.h" #include "gui/windows/updaterwindow.h" #include "gui/widgets/tabs/chat/whispertab.h" #include "listeners/awaylistener.h" #include "net/beinghandler.h" #include "net/chathandler.h" #include "net/inventoryhandler.h" #include "net/net.h" #include "net/packetlimiter.h" #include "net/playerhandler.h" #include "net/serverfeatures.h" #include "resources/iteminfo.h" #include "resources/db/weaponsdb.h" #include "resources/item/item.h" #include "resources/map/map.h" #include "resources/map/mapitem.h" #include "resources/map/speciallayer.h" #include "resources/map/walklayer.h" #include "resources/sprite/animatedsprite.h" #include "utils/delete2.h" #include "utils/foreach.h" #include "utils/gettext.h" #include "utils/timer.h" #ifdef USE_MUMBLE #include "mumblemanager.h" #endif // USE_MUMBLE #include #include "debug.h" static const int16_t awayLimitTimer = 60; static const int MAX_TICK_VALUE = INT_MAX / 2; typedef std::map::const_iterator GuildMapCIter; LocalPlayer *localPlayer = nullptr; extern OkDialog *weightNotice; extern time_t weightNoticeTime; LocalPlayer::LocalPlayer(const BeingId id, const BeingTypeId subType) : Being(id, ActorType::Player), ActorSpriteListener(), AttributeListener(), PlayerDeathListener(), mMoveState(0), mLastTargetX(0), mLastTargetY(0), mHomes(), mTarget(nullptr), mPlayerFollowed(), mPlayerImitated(), mNextDestX(0), mNextDestY(0), mPickUpTarget(nullptr), mLastAction(-1), mStatusEffectIcons(), mMessages(), mMessageTime(0), mAwayListener(new AwayListener), mAwayDialog(nullptr), mPingSendTick(0), mPingTime(0), mAfkTime(0), mActivityTime(0), mNavigateX(0), mNavigateY(0), mNavigateId(BeingId_zero), mCrossX(0), mCrossY(0), mOldX(0), mOldY(0), mOldTileX(0), mOldTileY(0), mNavigatePath(), mLastHitFrom(), mWaitFor(), mAdvertTime(0), mTestParticle(nullptr), mTestParticleName(), mTestParticleTime(0), mTestParticleHash(0L), mSyncPlayerMoveDistance(config.getIntValue("syncPlayerMoveDistance")), mUnfreezeTime(0), mWalkingDir(0), mUpdateName(true), mBlockAdvert(false), mTargetDeadPlayers(config.getBoolValue("targetDeadPlayers")), mServerAttack(fromBool(config.getBoolValue("serverAttack"), Keep)), mVisibleNames(static_cast( config.getIntValue("visiblenames"))), mEnableAdvert(config.getBoolValue("enableAdvert")), mTradebot(config.getBoolValue("tradebot")), mTargetOnlyReachable(config.getBoolValue("targetOnlyReachable")), 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")), mShowServerPos(config.getBoolValue("showserverpos")), mNextStep(false), mGoingToTarget(false), mKeepAttacking(false), mPathSetByMouse(false), mWaitPing(false), mShowNavigePath(false), mAllowRename(false), mFreezed(false) { logger->log1("LocalPlayer::LocalPlayer"); #ifdef TMWA_SUPPORT if (Net::getNetworkType() == ServerType::TMWATHENA) { mSyncPlayerMoveDistance = config.getIntValue("syncPlayerMoveDistanceLegacy"); } #endif postInit(subType, nullptr); mAttackRange = 0; mLevel = 1; mAdvanced = true; mTextColor = &theme->getColor(ThemeColorId::PLAYER, 255); if (userPalette != nullptr) mNameColor = &userPalette->getColor(UserColorId::SELF, 255U); else mNameColor = nullptr; PlayerInfo::setStatBase(Attributes::PLAYER_WALK_SPEED, getWalkSpeed(), Notify_true); PlayerInfo::setStatMod(Attributes::PLAYER_WALK_SPEED, 0, Notify_true); loadHomes(); config.addListener("showownname", this); config.addListener("targetDeadPlayers", this); serverConfig.addListener("enableBuggyServers", this); config.addListener("syncPlayerMove", this); config.addListener("syncPlayerMoveDistance", this); #ifdef TMWA_SUPPORT config.addListener("syncPlayerMoveDistanceLegacy", this); #endif 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); config.addListener("showserverpos", this); config.addListener("visiblenames", this); setShowName(config.getBoolValue("showownname")); } LocalPlayer::~LocalPlayer() { logger->log1("LocalPlayer::~LocalPlayer"); config.removeListeners(this); serverConfig.removeListener("enableBuggyServers", this); navigateClean(); mCrossX = 0; mCrossY = 0; updateNavigateList(); if (mAwayDialog != nullptr) { soundManager.volumeRestore(); delete2(mAwayDialog) } delete2(mAwayListener) } void LocalPlayer::logic() { BLOCK_START("LocalPlayer::logic") #ifdef USE_MUMBLE if (mumbleManager) mumbleManager->setPos(mX, mY, mDirection); #endif // USE_MUMBLE // Actions are allowed once per second if (get_elapsed_time(mLastAction) >= 1000) mLastAction = -1; if (mActivityTime == 0 || mLastAction != -1) mActivityTime = cur_time; if (mUnfreezeTime > 0 && mUnfreezeTime <= tick_time) { mUnfreezeTime = 0; mFreezed = false; } if ((mAction != BeingAction::MOVE || mNextStep) && !mNavigatePath.empty()) { mNextStep = false; int dist = 5; if (!mSyncPlayerMove) dist = 20; if (((mNavigateX != 0) || (mNavigateY != 0)) && ((mCrossX + dist >= mX && mCrossX <= mX + dist && mCrossY + dist >= mY && mCrossY <= mY + dist) || ((mCrossX == 0) && (mCrossY == 0)))) { const Path::const_iterator i = mNavigatePath.begin(); if ((*i).x == mX && (*i).y == mY) mNavigatePath.pop_front(); else setDestination((*i).x, (*i).y); } } // Show XP messages if (!mMessages.empty()) { if (mMessageTime == 0) { const MessagePair info = mMessages.front(); if ((particleEngine != nullptr) && (gui != nullptr)) { particleEngine->addTextRiseFadeOutEffect( info.first, mPixelX, mPixelY - 48, &userPalette->getColor(info.second, 255U), gui->getInfoParticleFont(), true); } mMessages.pop_front(); mMessageTime = 30; } mMessageTime--; } if (mTarget != nullptr) { if (mTarget->getType() == ActorType::Npc) { // NPCs are always in range mTarget->setTargetType(TargetCursorType::IN_RANGE); } else { // Find whether target is in range const int rangeX = CAST_S32( abs(mTarget->mX - mX)); const int rangeY = CAST_S32( abs(mTarget->mY - mY)); const int attackRange = getAttackRange(); const TargetCursorTypeT targetType = rangeX > attackRange || rangeY > attackRange ? TargetCursorType::NORMAL : TargetCursorType::IN_RANGE; mTarget->setTargetType(targetType); if (!mTarget->isAlive() && (!mTargetDeadPlayers || mTarget->getType() != ActorType::Player)) { stopAttack(true); } if (mKeepAttacking && (mTarget != nullptr)) attack(mTarget, true, false); } } Being::logic(); BLOCK_END("LocalPlayer::logic") } void LocalPlayer::slowLogic() { BLOCK_START("LocalPlayer::slowLogic") const time_t time = cur_time; if ((weightNotice != nullptr) && weightNoticeTime < time) { weightNotice->scheduleDelete(); weightNotice = nullptr; weightNoticeTime = 0; } if ((serverFeatures != nullptr) && !serverFeatures->havePlayerStatusUpdate() && mEnableAdvert && !mBlockAdvert && mAdvertTime < cur_time) { uint8_t smile = BeingFlag::SPECIAL; if (mTradebot && shopWindow != nullptr && !shopWindow->isShopEmpty()) { smile |= BeingFlag::SHOP; } if (settings.awayMode || settings.pseudoAwayMode) smile |= BeingFlag::AWAY; if (mInactive) smile |= BeingFlag::INACTIVE; if (emote(smile)) mAdvertTime = time + 60; else mAdvertTime = time + 30; } if (mTestParticleTime != time && !mTestParticleName.empty()) { const unsigned long hash = UpdaterWindow::getFileHash( mTestParticleName); if (hash != mTestParticleHash) { setTestParticle(mTestParticleName, false); mTestParticleHash = hash; } mTestParticleTime = time; } BLOCK_END("LocalPlayer::slowLogic") } void LocalPlayer::setAction(const BeingActionT &action, const int attackId) { if (action == BeingAction::DEAD) { if (!mLastHitFrom.empty() && !serverFeatures->haveKillerId()) { // TRANSLATORS: chat message after death debugMsg(strprintf(_("You were killed by %s."), mLastHitFrom.c_str())) mLastHitFrom.clear(); } setTarget(nullptr); } Being::setAction(action, attackId); #ifdef USE_MUMBLE if (mumbleManager) mumbleManager->setAction(CAST_S32(action)); #endif // USE_MUMBLE } void LocalPlayer::setGroupId(const int id) { Being::setGroupId(id); if (mIsGM != 0) { if (chatWindow != nullptr) { chatWindow->loadGMCommands(); chatWindow->showGMTab(); } } if (statusWindow != nullptr) statusWindow->updateLevelLabel(); } void LocalPlayer::nextTile() { const Party *const party = Party::getParty(1); if (party != nullptr) { PartyMember *const pm = party->getMember(mName); if (pm != nullptr) { pm->setX(mX); pm->setY(mY); } } if (mPath.empty()) { if (mPickUpTarget != nullptr) pickUp(mPickUpTarget); if (mWalkingDir != 0U) startWalking(mWalkingDir); } else if (mPath.size() == 1) { if (mPickUpTarget != nullptr) pickUp(mPickUpTarget); } if (mGoingToTarget && mTarget != nullptr && withinAttackRange(mTarget, false, 0)) { mAction = BeingAction::STAND; attack(mTarget, true, false); mGoingToTarget = false; mPath.clear(); return; } else if (mGoingToTarget && (mTarget == nullptr)) { mGoingToTarget = false; mPath.clear(); } if (mPath.empty()) { if (mNavigatePath.empty() || mAction != BeingAction::MOVE) { setAction(BeingAction::STAND, 0); // +++ probably sync position here always? } else { mNextStep = true; } } else { Being::nextTile(); } fixPos(); } bool LocalPlayer::pickUp(FloorItem *const item) { if (item == nullptr) return false; const int dx = item->getTileX() - mX; const int dy = item->getTileY() - mY; int dist = 6; const unsigned int pickUpType = settings.pickUpType; if (pickUpType >= 4 && pickUpType <= 6) dist = 4; if (dx * dx + dy * dy < dist) { if ((actorManager != nullptr) && actorManager->checkForPickup(item)) { if (!PacketLimiter::limitPackets(PacketType::PACKET_PICKUP)) return false; PlayerInfo::pickUpItem(item, Sfx_true); mPickUpTarget = nullptr; } } else if (pickUpType >= 4 && pickUpType <= 6) { const Path debugPath = mMap->findPath( (mPixelX - mapTileSize / 2) / mapTileSize, (mPixelY - mapTileSize) / mapTileSize, item->getTileX(), item->getTileY(), getBlockWalkMask(), 0); switch (debugPath.size()) { case 0: setDestination(item->getTileX(), item->getTileY()); break; case 1:// should never happen due to above check for adjecent-tile // pickup, but you never know. navigateTo(item->getTileX(), item->getTileY()); break; default: { // at least two spots, move nearby. const Position& nearby = *(++debugPath.rbegin()); navigateTo(nearby.x, nearby.y); break; } } 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 != nullptr)) return; if (target == mTarget) return; Being *oldTarget = nullptr; if (mTarget != nullptr) { mTarget->untarget(); oldTarget = mTarget; } if (mTarget != nullptr) { if (mTarget->getType() == ActorType::Monster) mTarget->setShowName(false); } mTarget = target; if (oldTarget != nullptr) oldTarget->updateName(); if (target != nullptr) { mLastTargetX = target->mX; mLastTargetY = target->mY; target->updateName(); if (mVisibleNames == VisibleName::ShowOnSelection) target->setShowName(true); } if (oldTarget != nullptr && mVisibleNames == VisibleName::ShowOnSelection) oldTarget->setShowName(false); if (target != nullptr && target->getType() == ActorType::Monster) target->setShowName(true); } Being *LocalPlayer::setNewTarget(const ActorTypeT type, const AllowSort allowSort) { if (actorManager != nullptr) { Being *const target = actorManager->findNearestLivingBeing( localPlayer, 20, type, allowSort); if ((target != nullptr) && target != mTarget) setTarget(target); return target; } return nullptr; } void LocalPlayer::setDestination(const int x, const int y) { mActivityTime = cur_time; if (settings.attackType == 0 || !mAttackMoving) mKeepAttacking = false; // Only send a new message to the server when destination changes if (x != mDest.x || y != mDest.y) { if (settings.moveType != 1) { playerHandler->setDestination(x, y, mDirection); Being::setDestination(x, y); } else { uint8_t newDir = 0; if ((mDirection & BeingDirection::UP) != 0) newDir |= BeingDirection::DOWN; if ((mDirection & BeingDirection::LEFT) != 0) newDir |= BeingDirection::RIGHT; if ((mDirection & BeingDirection::DOWN) != 0) newDir |= BeingDirection::UP; if ((mDirection & BeingDirection::RIGHT) != 0) newDir |= BeingDirection::LEFT; playerHandler->setDestination(x, y, newDir); // if (PacketLimiter::limitPackets(PacketType::PACKET_DIRECTION)) { setDirection(newDir); playerHandler->setDirection(newDir); } Being::setDestination(x, y); playerHandler->setDestination(x, y, mDirection); } } } void LocalPlayer::setWalkingDir(const unsigned char dir) { // This function is called by Game::handleInput() mWalkingDir = dir; // If we're not already walking, start walking. if (mAction != BeingAction::MOVE && (dir != 0U)) startWalking(dir); } void LocalPlayer::startWalking(const unsigned char dir) { // This function is called by setWalkingDir(), // but also by nextTile() for TMW-Athena... if ((mMap == nullptr) || (dir == 0U)) return; mPickUpTarget = nullptr; if (mAction == BeingAction::MOVE && !mPath.empty()) { // Just finish the current action, otherwise we get out of sync Being::setDestination(mX, mY); return; } int dx = 0; int dy = 0; if ((dir & BeingDirection::UP) != 0) dy--; if ((dir & BeingDirection::DOWN) != 0) dy++; if ((dir & BeingDirection::LEFT) != 0) dx--; if ((dir & BeingDirection::RIGHT) != 0) dx++; const unsigned char blockWalkMask = getBlockWalkMask(); // Prevent skipping corners over colliding tiles if ((dx != 0) && !mMap->getWalk(mX + dx, mY, blockWalkMask)) dx = 0; if ((dy != 0) && !mMap->getWalk(mX, mY + dy, blockWalkMask)) dy = 0; // Choose a straight direction when diagonal target is blocked if (dx != 0 && dy != 0 && !mMap->getWalk(mX + dx, mY + dy, blockWalkMask)) dx = 0; // Walk to where the player can actually go if ((dx != 0 || dy != 0) && mMap->getWalk(mX + dx, mY + dy, blockWalkMask)) { setDestination(mX + dx, mY + dy); } else if (dir != mDirection) { // If the being can't move, just change direction // if (PacketLimiter::limitPackets(PacketType::PACKET_DIRECTION)) { playerHandler->setDirection(dir); setDirection(dir); } } } void LocalPlayer::stopWalking(const bool sendToServer) { if (mAction == BeingAction::MOVE && (mWalkingDir != 0U)) { mWalkingDir = 0; mPickUpTarget = nullptr; setDestination(mPixelX, mPixelY); if (sendToServer) { playerHandler->setDestination( mPixelX, mPixelY, -1); } setAction(BeingAction::STAND, 0); } // No path set anymore, so we reset the path by mouse flag mPathSetByMouse = false; clearPath(); navigateClean(); } bool LocalPlayer::toggleSit() const { if (!PacketLimiter::limitPackets(PacketType::PACKET_SIT)) return false; BeingActionT newAction; switch (mAction) { case BeingAction::STAND: case BeingAction::PRESTAND: case BeingAction::SPAWN: newAction = BeingAction::SIT; break; case BeingAction::SIT: newAction = BeingAction::STAND; break; case BeingAction::MOVE: case BeingAction::ATTACK: case BeingAction::DEAD: case BeingAction::HURT: case BeingAction::CAST: default: return true; } playerHandler->changeAction(newAction); return true; } bool LocalPlayer::updateSit() const { if (!PacketLimiter::limitPackets(PacketType::PACKET_SIT)) return false; playerHandler->changeAction(mAction); return true; } bool LocalPlayer::emote(const uint8_t emotion) { if (!PacketLimiter::limitPackets(PacketType::PACKET_EMOTE)) return false; playerHandler->emote(emotion); return true; } void LocalPlayer::attack(Being *const target, const bool keep, const bool dontChangeEquipment) { mKeepAttacking = keep; if ((target == nullptr) || target->getType() == ActorType::Npc) return; if (mTarget != target) setTarget(target); // Must be standing or sitting or casting to attack if (mAction != BeingAction::STAND && mAction != BeingAction::SIT && mAction != BeingAction::CAST) { return; } #ifdef TMWA_SUPPORT if (Net::getNetworkType() == ServerType::TMWATHENA) { const int dist_x = target->mX - mX; const int dist_y = target->mY - mY; if (abs(dist_y) >= abs(dist_x)) { if (dist_y > 0) setDirection(BeingDirection::DOWN); else setDirection(BeingDirection::UP); } else { if (dist_x > 0) setDirection(BeingDirection::RIGHT); else setDirection(BeingDirection::LEFT); } } #endif // TMWA_SUPPORT mActionTime = tick_time; if (target->getType() != ActorType::Player || checAttackPermissions(target)) { setAction(BeingAction::ATTACK, 0); if (!PacketLimiter::limitPackets(PacketType::PACKET_ATTACK)) return; if (!dontChangeEquipment) changeEquipmentBeforeAttack(target); const BeingId targetId = target->getId(); playerHandler->attack(targetId, mServerAttack); PlayerInfo::updateAttackAi(targetId, mServerAttack); } if (!keep) stopAttack(false); } void LocalPlayer::stopAttack(const bool keepAttack) { if (!PacketLimiter::limitPackets(PacketType::PACKET_STOPATTACK)) return; if (mServerAttack == Keep_true && mAction == BeingAction::ATTACK) playerHandler->stopAttack(); untarget(); if (!keepAttack || !mAttackNext) mKeepAttacking = false; } void LocalPlayer::untarget() { if (mAction == BeingAction::ATTACK) setAction(BeingAction::STAND, 0); if (mTarget != nullptr) setTarget(nullptr); } void LocalPlayer::pickedUp(const ItemInfo &itemInfo, const int amount, const ItemColor color, const BeingId floorItemId, const PickupT fail) { if (fail != Pickup::OKAY) { if ((actorManager != nullptr) && floorItemId != BeingId_zero) { FloorItem *const item = actorManager->findItem(floorItemId); if (item != nullptr) { if (!item->getShowMsg()) return; item->setShowMsg(false); } } const char* msg = nullptr; 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; case Pickup::MAX_AMOUNT: // TRANSLATORS: pickup error message msg = N_("You can't pickup this amount of items."); break; case Pickup::STACK_AMOUNT: // TRANSLATORS: pickup error message msg = N_("Your item stack has max amount."); break; case Pickup::OKAY: break; default: case Pickup::UNKNOWN: // TRANSLATORS: pickup error message msg = N_("Unknown problem picking up item."); break; } if (localChatTab != nullptr && config.getBoolValue("showpickupchat")) { localChatTab->chatLog(gettext(msg), ChatMsgType::BY_SERVER, IgnoreRecord_false, TryRemoveColors_true); } if ((mMap != nullptr) && config.getBoolValue("showpickupparticle")) { // Show pickup notification addMessageToQueue(gettext(msg), UserColorId::PICKUP_INFO); } } else { std::string str; #ifdef TMWA_SUPPORT if (Net::getNetworkType() == ServerType::TMWATHENA) { str = itemInfo.getName(); } else #endif // TMWA_SUPPORT { str = itemInfo.getName(color); } if (config.getBoolValue("showpickupchat") && (localChatTab != nullptr)) { // 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()), ChatMsgType::BY_SERVER, IgnoreRecord_false, TryRemoveColors_true); } if ((mMap != nullptr) && config.getBoolValue("showpickupparticle")) { // Show pickup notification if (amount > 1) { addMessageToQueue(strprintf("%d x %s", amount, str.c_str()), UserColorId::PICKUP_INFO); } else { addMessageToQueue(str, UserColorId::PICKUP_INFO); } } } } int LocalPlayer::getAttackRange() const { if (mAttackRange > -1) { return mAttackRange; } const Item *const weapon = PlayerInfo::getEquipment( EquipSlot::FIGHT1_SLOT); if (weapon != nullptr) { 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 == nullptr) return false; int range = getAttackRange() + addRange; int dx; int dy; if (fixDistance && range == 1) range = 2; dx = CAST_S32(abs(target->mX - mX)); dy = CAST_S32(abs(target->mY - mY)); return !(dx > range || dy > range); } void LocalPlayer::setGotoTarget(Being *const target) { if (target == nullptr) return; mPickUpTarget = nullptr; setTarget(target); mGoingToTarget = true; navigateTo(target->mX, target->mY); } void LocalPlayer::handleStatusEffect(const StatusEffect *const effect, const int32_t effectId, const Enable newStatus, const IsStart start) { Being::handleStatusEffect(effect, effectId, newStatus, start); if (effect != nullptr) { effect->deliverMessage(); effect->playSFX(); AnimatedSprite *const sprite = effect->getIcon(); if (sprite == nullptr) { // delete sprite, if necessary for (size_t i = 0; i < mStatusEffectIcons.size(); ) { if (mStatusEffectIcons[i] == effectId) { mStatusEffectIcons.erase(mStatusEffectIcons.begin() + i); if (miniStatusWindow != nullptr) miniStatusWindow->eraseIcon(CAST_S32(i)); } else { i++; } } } else { // replace sprite or append bool found = false; const size_t sz = mStatusEffectIcons.size(); for (size_t i = 0; i < sz; i++) { if (mStatusEffectIcons[i] == effectId) { if (miniStatusWindow != nullptr) miniStatusWindow->setIcon(CAST_S32(i), sprite); found = true; break; } } if (!found) { // add new if (miniStatusWindow != nullptr) { const int offset = CAST_S32(mStatusEffectIcons.size()); miniStatusWindow->setIcon(offset, sprite); } mStatusEffectIcons.push_back(effectId); } } } } void LocalPlayer::addMessageToQueue(const std::string &message, const UserColorIdT 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 == "syncPlayerMoveDistance") { #ifdef TMWA_SUPPORT if (Net::getNetworkType() != ServerType::TMWATHENA) #endif { mSyncPlayerMoveDistance = config.getIntValue("syncPlayerMoveDistance"); } } #ifdef TMWA_SUPPORT else if (value == "syncPlayerMoveDistanceLegacy") { if (Net::getNetworkType() == ServerType::TMWATHENA) { mSyncPlayerMoveDistance = config.getIntValue("syncPlayerMoveDistanceLegacy"); } } #endif else if (value == "drawPath") { mDrawPath = config.getBoolValue("drawPath"); } else if (value == "serverAttack") { mServerAttack = fromBool(config.getBoolValue("serverAttack"), Keep); } 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"); } else if (value == "showserverpos") { mShowServerPos = config.getBoolValue("showserverpos"); } else if (value == "visiblenames") { mVisibleNames = static_cast( config.getIntValue("visiblenames")); } } void LocalPlayer::addJobMessage(const int64_t change) { if (change != 0 && mMessages.size() < 20) { const std::string xpStr = toString(CAST_U64(change)); if (!mMessages.empty()) { MessagePair pair = mMessages.back(); // TRANSLATORS: this is normal experience if (pair.first.find(strprintf(" %s", _("xp"))) == // TRANSLATORS: this is normal experience pair.first.size() - strlen(_("xp")) - 1) { mMessages.pop_back(); pair.first.append(strprintf(", %s %s", xpStr.c_str(), // TRANSLATORS: this is job experience _("job"))); mMessages.push_back(pair); } else { addMessageToQueue(strprintf("%s %s", xpStr.c_str(), // TRANSLATORS: this is job experience _("job")), UserColorId::EXP_INFO); } } else { addMessageToQueue(strprintf("%s %s", xpStr.c_str(), // TRANSLATORS: this is job experience _("job")), UserColorId::EXP_INFO); } } } void LocalPlayer::addXpMessage(const int64_t change) { if (change != 0 && mMessages.size() < 20) { addMessageToQueue(strprintf("%s %s", toString(CAST_U64(change)).c_str(), // TRANSLATORS: get xp message _("xp")), UserColorId::EXP_INFO); } } void LocalPlayer::addHomunXpMessage(const int change) { if (change != 0 && mMessages.size() < 20) { addMessageToQueue(strprintf("%s %d %s", // TRANSLATORS: get homunculus xp message _("Homun"), change, // TRANSLATORS: get xp message _("xp")), UserColorId::EXP_INFO); } } void LocalPlayer::addHpMessage(const int change) { if (change != 0 && mMessages.size() < 20) { // TRANSLATORS: get hp message addMessageToQueue(strprintf("%d %s", change, _("hp")), UserColorId::EXP_INFO); } } void LocalPlayer::addSpMessage(const int change) { if (change != 0 && mMessages.size() < 20) { // TRANSLATORS: get hp message addMessageToQueue(strprintf("%d %s", change, _("mana")), UserColorId::EXP_INFO); } } void LocalPlayer::attributeChanged(const AttributesT id, const int64_t oldVal, const int64_t newVal) { PRAGMA45(GCC diagnostic push) PRAGMA45(GCC diagnostic ignored "-Wswitch-enum") switch (id) { case Attributes::PLAYER_EXP: { if (Net::getNetworkType() != ServerType::TMWATHENA) break; if (oldVal > newVal) break; const int change = CAST_S32(newVal - oldVal); addXpMessage(change); break; } case Attributes::PLAYER_BASE_LEVEL: mLevel = CAST_S32(newVal); break; case Attributes::PLAYER_HP: if (oldVal != 0 && newVal == 0) PlayerDeathListener::distributeEvent(); break; case Attributes::PLAYER_JOB_EXP: { if (!mShowJobExp || Net::getNetworkType() != ServerType::TMWATHENA) { return; } if (oldVal > newVal || PlayerInfo::getAttribute( Attributes::PLAYER_JOB_EXP_NEEDED) == 0) { return; } const int32_t change = CAST_S32(newVal - oldVal); addJobMessage(change); break; } default: break; } PRAGMA45(GCC diagnostic pop) } void LocalPlayer::move(const int dX, const int dY) { mPickUpTarget = nullptr; setDestination(mX + dX, mY + dY); } void LocalPlayer::moveToTarget(int dist) { bool gotPos(false); Path debugPath; size_t limit(0); if (dist == -1) { dist = settings.moveToTargetType; if (dist != 0) { const bool broken = (Net::getNetworkType() == ServerType::TMWATHENA); switch (dist) { case 11: // archer dist = mAttackRange; if (dist == 1 && broken) dist = 2; break; case 12: // range-1 dist = mAttackRange - 1; if (dist < 1) dist = 1; if (dist == 1 && broken) dist = 2; break; default: break; } } } if (mTarget != nullptr) { if (mMap != nullptr) { debugPath = mMap->findPath( (mPixelX - mapTileSize / 2) / mapTileSize, (mPixelY - mapTileSize) / mapTileSize, mTarget->mX, mTarget->mY, getBlockWalkMask(), 0); } const size_t sz = debugPath.size(); if (sz < CAST_SIZE(dist)) return; limit = CAST_S32(sz) - dist; gotPos = true; } else if ((mNavigateX != 0) || (mNavigateY != 0)) { debugPath = mNavigatePath; limit = dist; gotPos = true; } if (gotPos) { if (dist == 0) { if (mTarget != nullptr) navigateTo(mTarget->mX, mTarget->mY); } else { Position pos(0, 0); size_t f = 0; for (Path::const_iterator i = debugPath.begin(), i_fend = debugPath.end(); i != i_fend && f < limit; ++i, f++) { pos = (*i); } navigateTo(pos.x, pos.y); } } else if ((mLastTargetX != 0) || (mLastTargetY != 0)) { navigateTo(mLastTargetX, mLastTargetY); } } void LocalPlayer::moveToHome() { mPickUpTarget = nullptr; if ((mX != mCrossX || mY != mCrossY) && (mCrossX != 0) && (mCrossY != 0)) { setDestination(mCrossX, mCrossY); } else if (mMap != nullptr) { const std::map::const_iterator iter = mHomes.find(mMap->getProperty("_realfilename", std::string())); if (iter != mHomes.end()) { const Vector pos = mHomes[(*iter).first]; if (mX == pos.x && mY == pos.y) { playerHandler->setDestination( CAST_S32(pos.x), CAST_S32(pos.y), CAST_S32(mDirection)); } else { navigateTo(CAST_S32(pos.x), CAST_S32(pos.y)); } } } } void LocalPlayer::changeEquipmentBeforeAttack(const Being *const target) const { if (settings.attackWeaponType == 1 || (target == nullptr) || (PlayerInfo::getInventory() == nullptr)) { return; } bool allowSword = false; const int dx = target->mX - mX; const int dy = target->mY - 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 == nullptr) return; // if attack distance for sword if (allowSword) { // searching swords const WeaponsInfos &swords = WeaponsDB::getSwords(); FOR_EACH (WeaponsInfosIter, it, swords) { item = inv->findItem(*it, ItemColor_zero); if (item != nullptr) break; } // no swords if (item == nullptr) return; // if sword not equiped if (item->isEquipped() == Equipped_false) PlayerInfo::equipItem(item, Sfx_true); // if need equip shield too if (settings.attackWeaponType == 3) { // searching shield const WeaponsInfos &shields = WeaponsDB::getShields(); FOR_EACH (WeaponsInfosIter, it, shields) { item = inv->findItem(*it, ItemColor_zero); if (item != nullptr) break; } if ((item != nullptr) && item->isEquipped() == Equipped_false) PlayerInfo::equipItem(item, Sfx_true); } } // big distance. allowed only bow else { // searching bow const WeaponsInfos &bows = WeaponsDB::getBows(); FOR_EACH (WeaponsInfosIter, it, bows) { item = inv->findItem(*it, ItemColor_zero); if (item != nullptr) break; } // no bow if (item == nullptr) return; if (item->isEquipped() == Equipped_false) PlayerInfo::equipItem(item, Sfx_true); } } bool LocalPlayer::isReachable(Being *const being, const int maxCost) { if ((being == nullptr) || (mMap == nullptr)) return false; if (being->getReachable() == Reachable::REACH_NO) return false; if (being->mX == mX && being->mY == mY) { being->setDistance(0); being->setReachable(Reachable::REACH_YES); return true; } else if (being->mX - 1 <= mX && being->mX + 1 >= mX && being->mY - 1 <= mY && being->mY + 1 >= mY) { being->setDistance(1); being->setReachable(Reachable::REACH_YES); return true; } const Path debugPath = mMap->findPath( (mPixelX - mapTileSize / 2) / mapTileSize, (mPixelY - mapTileSize) / mapTileSize, being->mX, being->mY, getBlockWalkMask(), maxCost); being->setDistance(CAST_S32(debugPath.size())); if (!debugPath.empty()) { being->setReachable(Reachable::REACH_YES); return true; } being->setReachable(Reachable::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 == nullptr) 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 (actorManager == nullptr) return false; bool status = false; int x = mX; int y = mY; // first pick up item on player position FloorItem *item = actorManager->findItem(x, y); if (item != nullptr) status = pickUp(item); if (pickUpType == 0) pickUpType = settings.pickUpType; if (pickUpType == 0) return status; int x1; int y1; int x2; int y2; switch (pickUpType) { case 1: switch (mDirection) { case BeingDirection::UP : --y; break; case BeingDirection::DOWN : ++y; break; case BeingDirection::LEFT : --x; break; case BeingDirection::RIGHT: ++x; break; default: break; } item = actorManager->findItem(x, y); if (item != nullptr) status = pickUp(item); break; case 2: switch (mDirection) { case BeingDirection::UP: x1 = x - 1; y1 = y - 1; x2 = x + 1; y2 = y; break; case BeingDirection::DOWN: x1 = x - 1; y1 = y; x2 = x + 1; y2 = y + 1; break; case BeingDirection::LEFT: x1 = x - 1; y1 = y - 1; x2 = x; y2 = y + 1; break; case BeingDirection::RIGHT: x1 = x; y1 = y - 1; x2 = x + 1; y2 = y + 1; break; default: x1 = x; x2 = x; y1 = y; y2 = y; break; } if (actorManager->pickUpAll(x1, y1, x2, y2, false)) status = true; break; case 3: if (actorManager->pickUpAll(x - 1, y - 1, x + 1, y + 1, false)) status = true; break; case 4: if (actorManager->pickUpNearest(x, y, 4)) status = true; break; case 5: if (actorManager->pickUpNearest(x, y, 8)) status = true; break; case 6: if (actorManager->pickUpNearest(x, y, 90)) status = true; break; default: break; } return status; } void LocalPlayer::moveByDirection(const unsigned char dir) { int dx = 0; int dy = 0; if ((dir & BeingDirection::UP) != 0) dy--; if ((dir & BeingDirection::DOWN) != 0) dy++; if ((dir & BeingDirection::LEFT) != 0) dx--; if ((dir & BeingDirection::RIGHT) != 0) dx++; move(dx, dy); } void LocalPlayer::specialMove(const unsigned char direction) { if ((direction != 0U) && ((mNavigateX != 0) || (mNavigateY != 0))) navigateClean(); if ((direction != 0U) && (settings.moveType >= 2 && settings.moveType <= 4)) { if (mAction == BeingAction::MOVE) return; unsigned int max; if (settings.moveType == 2) max = 5; else if (settings.moveType == 4) max = 1; else max = 3; if (getMoveState() < max) { moveByDirection(direction); mMoveState ++; } else { mMoveState = 0; crazyMoves->crazyMove(); } } else { setWalkingDir(direction); } } #ifdef TMWA_SUPPORT void LocalPlayer::magicAttack() const { if (Net::getNetworkType() != ServerType::TMWATHENA) return; if (chatWindow == nullptr || !isAlive() || !playerHandler->canUseMagic()) { return; } switch (settings.magicAttackType) { // 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 == nullptr) return; if (PlayerInfo::getSkillLevel(340) >= baseMagic && PlayerInfo::getSkillLevel(342) >= schoolMagic) { if (PlayerInfo::getAttribute(Attributes::PLAYER_MP) >= mana) { if (!PacketLimiter::limitPackets(PacketType::PACKET_CHAT)) return; chatWindow->localChatInput(spell); } } } #endif // TMWA_SUPPORT void LocalPlayer::loadHomes() { std::string buf; std::stringstream ss(serverConfig.getValue("playerHomes", "")); while (ss >> buf) { Vector pos; ss >> pos.x; ss >> pos.y; mHomes[buf] = pos; } } void LocalPlayer::setMap(Map *const map) { BLOCK_START("LocalPlayer::setMap") if (map != nullptr) { if (socialWindow != nullptr) socialWindow->updateActiveList(); } navigateClean(); mCrossX = 0; mCrossY = 0; Being::setMap(map); updateNavigateList(); BLOCK_END("LocalPlayer::setMap") } void LocalPlayer::setHome() { if ((mMap == nullptr) || (socialWindow == nullptr)) return; SpecialLayer *const specialLayer = mMap->getSpecialLayer(); if (specialLayer == nullptr) return; const std::string key = mMap->getProperty("_realfilename", std::string()); Vector pos = mHomes[key]; if (mAction == BeingAction::SIT) { const std::map::const_iterator iter = mHomes.find(key); if (iter != mHomes.end()) { socialWindow->removePortal(CAST_S32(pos.x), CAST_S32(pos.y)); } if (iter != mHomes.end() && mX == CAST_S32(pos.x) && mY == CAST_S32(pos.y)) { mMap->updatePortalTile("", MapItemType::EMPTY, CAST_S32(pos.x), CAST_S32(pos.y), true); mHomes.erase(key); socialWindow->removePortal(CAST_S32(pos.x), CAST_S32(pos.y)); } else { if (iter != mHomes.end()) { specialLayer->setTile(CAST_S32(pos.x), CAST_S32(pos.y), MapItemType::EMPTY); specialLayer->updateCache(); } pos.x = static_cast(mX); pos.y = static_cast(mY); mHomes[key] = pos; mMap->updatePortalTile("home", MapItemType::HOME, mX, mY, true); socialWindow->addPortal(mX, mY); } MapItem *const mapItem = specialLayer->getTile(mX, mY); if (mapItem != nullptr) { const int idx = socialWindow->getPortalIndex(mX, mY); mapItem->setName(KeyboardConfig::getKeyShortString( OutfitWindow::keyName(idx))); } saveHomes(); } else { MapItem *mapItem = specialLayer->getTile(mX, mY); int type = 0; const std::map::iterator iter = mHomes.find(key); if (iter != mHomes.end() && mX == pos.x && mY == pos.y) { mHomes.erase(key); saveHomes(); } if ((mapItem == nullptr) || mapItem->getType() == MapItemType::EMPTY) { if ((mDirection & BeingDirection::UP) != 0) type = MapItemType::ARROW_UP; else if ((mDirection & BeingDirection::LEFT) != 0) type = MapItemType::ARROW_LEFT; else if ((mDirection & BeingDirection::DOWN) != 0) type = MapItemType::ARROW_DOWN; else if ((mDirection & BeingDirection::RIGHT) != 0) type = MapItemType::ARROW_RIGHT; } else { type = MapItemType::EMPTY; } mMap->updatePortalTile("", type, mX, mY, true); if (type != MapItemType::EMPTY) { socialWindow->addPortal(mX, mY); mapItem = specialLayer->getTile(mX, mY); if (mapItem != nullptr) { const int idx = socialWindow->getPortalIndex(mX, mY); mapItem->setName(KeyboardConfig::getKeyShortString( OutfitWindow::keyName(idx))); } } else { specialLayer->setTile(mX, mY, MapItemType::EMPTY); specialLayer->updateCache(); socialWindow->removePortal(mX, mY); } } } void LocalPlayer::saveHomes() { std::stringstream ss; for (std::map::const_iterator iter = mHomes.begin(), iter_fend = mHomes.end(); iter != iter_fend; ++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; beingHandler->requestNameById(getId()); } std::string LocalPlayer::getPingTime() const { std::string str; if (!mWaitPing) { if (mPingTime == 0) str = "?"; else str = toString(CAST_S32(mPingTime)); } else { time_t 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)", CAST_S32(mPingTime), CAST_S32(time)); else str = toString(CAST_S32(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) const { setAfkMessage(message); GameModifiers::changeAwayMode(true); updateStatus(); } void LocalPlayer::setAfkMessage(std::string message) { if (!message.empty()) { if (message.size() > 4 && message.substr(0, 4) == "/me ") { message = message.substr(4); config.setValue("afkFormat", 1); } else { config.setValue("afkFormat", 0); } serverConfig.setValue("afkMessage", message); } } void LocalPlayer::setPseudoAway(const std::string &message) { setAfkMessage(message); settings.pseudoAwayMode = !settings.pseudoAwayMode; } void LocalPlayer::afkRespond(ChatTab *const tab, const std::string &nick) { if (settings.awayMode) { const time_t time = cur_time; if (mAfkTime == 0 || time < mAfkTime || time - mAfkTime > awayLimitTimer) { std::string str(serverConfig.getValue("afkMessage", "I am away from keyboard.")); if (str.find("'NAME'") != std::string::npos) replaceAll(str, "'NAME'", nick); std::string msg("*AFK*: " + str); if (config.getIntValue("afkFormat") == 1) msg = "*" + msg + "*"; if (tab == nullptr) { chatHandler->privateMessage(nick, msg); if (localChatTab != nullptr) { localChatTab->chatLog(std::string(mName).append( " : ").append(msg), ChatMsgType::ACT_WHISPER, IgnoreRecord_false, TryRemoveColors_true); } } else { if (tab->getNoAway()) return; chatHandler->privateMessage(nick, msg); tab->chatLog(mName, msg); } mAfkTime = time; } } } bool LocalPlayer::navigateTo(const int x, const int y) { if (mMap == nullptr) return false; SpecialLayer *const tmpLayer = mMap->getTempLayer(); if (tmpLayer == nullptr) return false; mShowNavigePath = true; mOldX = mPixelX; mOldY = mPixelY; mOldTileX = mX; mOldTileY = mY; mNavigateX = x; mNavigateY = y; mNavigateId = BeingId_zero; mNavigatePath = mMap->findPath( (mPixelX - mapTileSize / 2) / mapTileSize, (mPixelY - mapTileSize) / mapTileSize, x, y, getBlockWalkMask(), 0); if (mDrawPath) tmpLayer->addRoad(mNavigatePath); return !mNavigatePath.empty(); } void LocalPlayer::navigateClean() { if (mMap == nullptr) return; mShowNavigePath = false; mOldX = 0; mOldY = 0; mOldTileX = 0; mOldTileY = 0; mNavigateX = 0; mNavigateY = 0; mNavigateId = BeingId_zero; mNavigatePath.clear(); SpecialLayer *const tmpLayer = mMap->getTempLayer(); if (tmpLayer == nullptr) return; tmpLayer->clean(); } void LocalPlayer::updateMusic() const { if (mMap != nullptr) { std::string str = mMap->getObjectData(mX, mY, MapItemType::MUSIC); if (str.empty()) str = mMap->getMusicFile(); if (str != soundManager.getCurrentMusicFile()) { if (str.empty()) soundManager.fadeOutMusic(1000); else soundManager.fadeOutAndPlayMusic(str, 1000); } } } void LocalPlayer::updateCoords() { Being::updateCoords(); // probably map not loaded. if ((mPixelX == 0) || (mPixelY == 0)) return; if (mX != mOldTileX || mY != mOldTileY) { if (socialWindow != nullptr) socialWindow->updatePortals(); PopupManager::hideBeingPopup(); updateMusic(); } if ((mMap != nullptr) && (mX != mOldTileX || mY != mOldTileY)) { SpecialLayer *const tmpLayer = mMap->getTempLayer(); if (tmpLayer == nullptr) return; const int x = (mPixelX - mapTileSize / 2) / mapTileSize; const int y = (mPixelY - mapTileSize) / mapTileSize; if (mNavigateId != BeingId_zero) { if (actorManager == nullptr) { navigateClean(); return; } const Being *const being = actorManager ->findBeing(mNavigateId); if (being == nullptr) { navigateClean(); return; } mNavigateX = being->mX; mNavigateY = being->mY; } if (mNavigateX == x && mNavigateY == y) { navigateClean(); return; } for (Path::const_iterator i = mNavigatePath.begin(), i_fend = mNavigatePath.end(); i != i_fend; ++i) { if ((*i).x == mX && (*i).y == mY) { mNavigatePath.pop_front(); fixPos(); break; } } if (mDrawPath && mShowNavigePath) { tmpLayer->clean(); tmpLayer->addRoad(mNavigatePath); } } mOldX = mPixelX; mOldY = mPixelY; mOldTileX = mX; mOldTileY = mY; } void LocalPlayer::targetMoved() const { /* if (mKeepAttacking) { if (mTarget && mServerAttack == Keep_true) { logger->log("LocalPlayer::targetMoved0"); if (!PacketLimiter::limitPackets(PacketType::PACKET_ATTACK)) return; logger->log("LocalPlayer::targetMoved"); playerHandler->attack(mTarget->getId(), mServerAttack); } } */ } int LocalPlayer::getPathLength(const Being *const being) const { if ((mMap == nullptr) || (being == nullptr)) return 0; 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( (mPixelX - mapTileSize / 2) / mapTileSize, (mPixelY - mapTileSize) / mapTileSize, being->mX, being->mY, getBlockWalkMask(), 0); return CAST_S32(debugPath.size()); } const int dx = CAST_S32(abs(being->mX - mX)); const int dy = CAST_S32(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 != nullptr)) changeEquipmentBeforeAttack(target); const bool broken = (Net::getNetworkType() == ServerType::TMWATHENA); // probably need cache getPathLength(target) if ((target == nullptr || settings.attackType == 0 || settings.attackType == 3) || (withinAttackRange(target, broken, broken ? 1 : 0) && getPathLength(target) <= getAttackRange2())) { attack(target, keep, false); if (settings.attackType == 2) { if (target == nullptr) { if (pickUpItems(0)) return; } else { pickUpItems(3); } } } else if (mPickUpTarget == nullptr) { if (settings.attackType == 2) { if (pickUpItems(0)) return; } setTarget(target); if (target->getType() != ActorType::Npc) { mKeepAttacking = true; moveToTarget(-1); } } } 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 == nullptr) return; std::string player_imitated = getImitate(); if (!player_imitated.empty() && being->mName == player_imitated) emote(action); } void LocalPlayer::imitateAction(const Being *const being, const BeingActionT &action) { if (being == nullptr) return; if (!mPlayerImitated.empty() && being->mName == mPlayerImitated) { setAction(action, 0); playerHandler->changeAction(action); } } void LocalPlayer::imitateDirection(const Being *const being, const unsigned char dir) { if (being == nullptr) return; if (!mPlayerImitated.empty() && being->mName == mPlayerImitated) { if (!PacketLimiter::limitPackets(PacketType::PACKET_DIRECTION)) return; if (settings.followMode == 2) { uint8_t dir2 = 0; if ((dir & BeingDirection::LEFT) != 0) dir2 |= BeingDirection::RIGHT; else if ((dir & BeingDirection::RIGHT) != 0) dir2 |= BeingDirection::LEFT; if ((dir & BeingDirection::UP) != 0) dir2 |= BeingDirection::DOWN; else if ((dir & BeingDirection::DOWN) != 0) dir2 |= BeingDirection::UP; setDirection(dir2); playerHandler->setDirection(dir2); } else { setDirection(dir); playerHandler->setDirection(dir); } } } void LocalPlayer::imitateOutfit(const Being *const player, const int sprite) const { if (player == nullptr) return; if (settings.imitationMode == 1 && !mPlayerImitated.empty() && player->mName == mPlayerImitated) { if (sprite < 0 || sprite >= player->getNumberOfLayers()) return; const AnimatedSprite *const equipmentSprite = dynamic_cast( player->mSprites[sprite]); if (equipmentSprite != nullptr) { // logger->log("have equipmentSprite"); const Inventory *const inv = PlayerInfo::getInventory(); if (inv == nullptr) 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 != nullptr) && item->isEquipped() == Equipped_false) PlayerInfo::equipItem(item, Sfx_false); } else { // logger->log("have unequip %d", sprite); const int equipmentSlot = inventoryHandler ->convertFromServerSlot(sprite); // logger->log("equipmentSlot: " + toString(equipmentSlot)); if (equipmentSlot == inventoryHandler->getProjectileSlot()) return; const Item *const item = PlayerInfo::getEquipment(equipmentSlot); if (item != nullptr) { // logger->log("unequiping"); PlayerInfo::unequipItem(item, Sfx_false); } } } } void LocalPlayer::followMoveTo(const Being *const being, const int x, const int y) { if ((being != nullptr) && !mPlayerFollowed.empty() && being->mName == mPlayerFollowed) { mPickUpTarget = nullptr; navigateTo(x, y); } } void LocalPlayer::followMoveTo(const Being *const being, const int x1, const int y1, const int x2, const int y2) { if (being == nullptr) return; mPickUpTarget = nullptr; if (!mPlayerFollowed.empty() && being->mName == mPlayerFollowed) { switch (settings.followMode) { case 0: navigateTo(x1, y1); setNextDest(x2, y2); break; case 1: if (x1 != x2 || y1 != y2) { navigateTo(mX + x2 - x1, mY + y2 - y1); setNextDest(mX + x2 - x1, mY + y2 - y1); } break; case 2: if (x1 != x2 || y1 != y2) { navigateTo(mX + x1 - x2, mY + y1 - y2); setNextDest(mX + x1 - x2, mY + y1 - y2); } break; case 3: if (mTarget == nullptr || mTarget->mName != mPlayerFollowed) { if (actorManager != nullptr) { Being *const b = actorManager->findBeingByName( mPlayerFollowed, ActorType::Player); setTarget(b); } } moveToTarget(-1); 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; } void LocalPlayer::fixPos() { if ((mCrossX == 0) && (mCrossY == 0)) return; const int dx = (mX >= mCrossX) ? mX - mCrossX : mCrossX - mX; const int dy = (mY >= mCrossY) ? mY - mCrossY : mCrossY - mY; const int dist = dx > dy ? dx : dy; const time_t time = cur_time; #ifdef TMWA_SUPPORT int maxDist; if (mSyncPlayerMove) { maxDist = mSyncPlayerMoveDistance; } else { if (Net::getNetworkType() == ServerType::TMWATHENA) maxDist = 30; else maxDist = 10; } #else const int maxDist = mSyncPlayerMove ? mSyncPlayerMoveDistance : 10; #endif if (dist > maxDist) { mActivityTime = time; #ifdef ENABLEDEBUGLOG logger->dlog(strprintf("Fix position from (%d,%d) to (%d,%d)", mX, mY, mCrossX, mCrossY)); #endif setTileCoords(mCrossX, mCrossY); /* if (mNavigateX != 0 || mNavigateY != 0) { #ifdef ENABLEDEBUGLOG logger->dlog(strprintf("Renavigate to (%d,%d)", mNavigateX, mNavigateY)); #endif navigateTo(mNavigateX, mNavigateY); } */ // alternative way to fix, move to real position // setDestination(mCrossX, mCrossY); } } void LocalPlayer::setTileCoords(const int x, const int y) restrict2 { Being::setTileCoords(x, y); mCrossX = x; mCrossY = y; } void LocalPlayer::setRealPos(const int x, const int y) { if (mMap == nullptr) return; SpecialLayer *const layer = mMap->getTempLayer(); if (layer != nullptr) { bool cacheUpdated(false); if (((mCrossX != 0) || (mCrossY != 0)) && (layer->getTile(mCrossX, mCrossY) != nullptr) && layer->getTile(mCrossX, mCrossY)->getType() == MapItemType::CROSS) { layer->setTile(mCrossX, mCrossY, MapItemType::EMPTY); layer->updateCache(); cacheUpdated = true; } if (mShowServerPos) { const MapItem *const mapItem = layer->getTile(x, y); if (mapItem == nullptr || mapItem->getType() == MapItemType::EMPTY) { if (mX != x && mY != y) { layer->setTile(x, y, MapItemType::CROSS); if (cacheUpdated == false) layer->updateCache(); } } } if (mCrossX != x || mCrossY != y) { mCrossX = x; mCrossY = y; // +++ possible configuration option fixPos(); } } if (mMap->isCustom()) mMap->setWalk(x, y); } void LocalPlayer::fixAttackTarget() { if ((mMap == nullptr) || (mTarget == nullptr)) return; // 11 == archer if (settings.moveToTargetType == 11 || (settings.attackType == 0U) || !config.getBoolValue("autofixPos")) { return; } const Path debugPath = mMap->findPath( (mPixelX - mapTileSize / 2) / mapTileSize, (mPixelY - mapTileSize) / mapTileSize, mTarget->mX, mTarget->mY, getBlockWalkMask(), 0); if (!debugPath.empty()) { const Path::const_iterator i = debugPath.begin(); setDestination((*i).x, (*i).y); } } void LocalPlayer::respawn() { navigateClean(); } int LocalPlayer::getLevel() const { return PlayerInfo::getAttribute(Attributes::PLAYER_BASE_LEVEL); } void LocalPlayer::updateNavigateList() { if (mMap != nullptr) { const std::map::const_iterator iter = mHomes.find(mMap->getProperty("_realfilename", std::string())); if (iter != mHomes.end()) { const Vector &pos = mHomes[(*iter).first]; if ((pos.x != 0.0F) && (pos.y != 0.0F)) { mMap->addPortalTile("home", MapItemType::HOME, CAST_S32(pos.x), CAST_S32(pos.y)); } } } } void LocalPlayer::failMove(const int x A_UNUSED, const int y A_UNUSED) { fixPos(); } void LocalPlayer::waitFor(const std::string &nick) { mWaitFor = nick; } void LocalPlayer::checkNewName(Being *const being) { if (being == nullptr) return; const std::string &nick = being->mName; if (being->getType() == ActorType::Player) { const Guild *const guild = getGuild(); if (guild != nullptr) { const GuildMember *const gm = guild->getMember(nick); if (gm != nullptr) { const int level = gm->getLevel(); if (level > 1 && being->getLevel() != level) { being->setLevel(level); being->updateName(); } } } if (chatWindow != nullptr) { WhisperTab *const tab = chatWindow->getWhisperTab(nick); if (tab != nullptr) 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(); } } unsigned char LocalPlayer::getBlockWalkMask() const { // for now blocking all types of collisions return BlockMask::WALL | BlockMask::AIR | BlockMask::WATER | BlockMask::PLAYERWALL; } void LocalPlayer::removeHome() { if (mMap == nullptr) return; const std::string key = mMap->getProperty("_realfilename", std::string()); const std::map::iterator iter = mHomes.find(key); if (iter != mHomes.end()) mHomes.erase(key); } void LocalPlayer::stopAdvert() { mBlockAdvert = true; } bool LocalPlayer::checAttackPermissions(const Being *const target) { if (target == nullptr) return false; switch (settings.pvpAttackType) { case 0: return true; case 1: return !(playerRelations.getRelation(target->mName) == Relation::FRIEND); case 2: return playerRelations.checkBadRelation(target->mName); default: case 3: return false; } } void LocalPlayer::updateStatus() const { if (serverFeatures->havePlayerStatusUpdate() && mEnableAdvert) { uint8_t status = 0; if (Net::getNetworkType() == ServerType::TMWATHENA) { if (mTradebot && shopWindow != nullptr && !shopWindow->isShopEmpty()) { status |= BeingFlag::SHOP; } } if (settings.awayMode || settings.pseudoAwayMode) status |= BeingFlag::AWAY; if (mInactive) status |= BeingFlag::INACTIVE; playerHandler->updateStatus(status); } } void LocalPlayer::setTestParticle(const std::string &fileName, const bool updateHash) { mTestParticleName = fileName; mTestParticleTime = cur_time; if (mTestParticle != nullptr) { mChildParticleEffects.removeLocally(mTestParticle); mTestParticle = nullptr; } if (!fileName.empty()) { mTestParticle = particleEngine->addEffect(fileName, 0, 0, 0); controlCustomParticle(mTestParticle); if (updateHash) mTestParticleHash = UpdaterWindow::getFileHash(mTestParticleName); } } void LocalPlayer::playerDeath() { if (mAction != BeingAction::DEAD) { setAction(BeingAction::DEAD, 0); recalcSpritesOrder(); } } bool LocalPlayer::canMove() const { return !mFreezed && mAction != BeingAction::DEAD && (serverFeatures->haveMoveWhileSit() || mAction != BeingAction::SIT); } void LocalPlayer::freezeMoving(const int timeWaitTicks) { if (timeWaitTicks <= 0) return; const int nextTime = tick_time + timeWaitTicks; if (mUnfreezeTime < nextTime) mUnfreezeTime = nextTime; if (mUnfreezeTime > 0) mFreezed = true; }