/* * The Mana World * Copyright (C) 2004 The Mana World Development Team * * This file is part of The Mana World. * * 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, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include "configuration.h" #include "equipment.h" #include "floor_item.h" #include "game.h" #include "graphics.h" #include "inventory.h" #include "item.h" #include "localplayer.h" #include "map.h" #include "monster.h" #include "particle.h" #include "simpleanimation.h" #include "sound.h" #include "statuseffect.h" #include "text.h" #include "gui/gui.h" #include "gui/ministatus.h" #include "gui/palette.h" #ifdef EATHENA_SUPPORT #include "gui/storagewindow.h" #endif #ifdef TMWSERV_SUPPORT #include "effectmanager.h" #include "guild.h" #include "net/tmwserv/gameserver/player.h" #include "net/tmwserv/chatserver/guild.h" #include "net/tmwserv/chatserver/party.h" #endif #ifdef EATHENA_SUPPORT #include "net/messageout.h" #include "net/ea/protocol.h" #endif #include "resources/animation.h" #include "resources/imageset.h" #include "resources/iteminfo.h" #include "resources/resourcemanager.h" #include "utils/gettext.h" #include "utils/stringutils.h" #ifdef TMWSERV_SUPPORT const short walkingKeyboardDelay = 100; #endif LocalPlayer *player_node = NULL; #ifdef TMWSERV_SUPPORT LocalPlayer::LocalPlayer(): Player(65535, 0, NULL), mEquipment(new Equipment), mAttributeBase(NB_CHARACTER_ATTRIBUTES, -1), mAttributeEffective(NB_CHARACTER_ATTRIBUTES, -1), mExpCurrent(CHAR_SKILL_NB, -1), mExpNext(CHAR_SKILL_NB, -1), mCharacterPoints(-1), mCorrectionPoints(-1), mLevelProgress(0), #else LocalPlayer::LocalPlayer(int id, int job, Map *map): Player(id, job, map), mCharId(0), mJobXp(0), mJobLevel(0), mXpForNextLevel(0), mJobXpForNextLevel(0), mMp(0), mMaxMp(0), mAttackRange(0), ATK(0), MATK(0), DEF(0), MDEF(0), HIT(0), FLEE(0), ATK_BONUS(0), MATK_BONUS(0), DEF_BONUS(0), MDEF_BONUS(0), FLEE_BONUS(0), mStatPoint(0), mSkillPoint(0), mStatsPointsToAttribute(0), mEquipment(new Equipment), mXp(0), mInStorage(false), mTargetTime(-1), mLastTarget(-1), #endif mLevel(1), mMoney(0), mTotalWeight(1), mMaxWeight(1), mHp(1), mMaxHp(1), mTarget(NULL), mPickUpTarget(NULL), mTrading(false), mGoingToTarget(false), mLastAction(-1), mWalkingDir(0), mDestX(0), mDestY(0), #ifdef TMWSERV_SUPPORT mLocalWalkTime(-1), mInventory(new Inventory(INVENTORY_SIZE)), mExpMessageTime(0) #else mInventory(new Inventory(INVENTORY_SIZE, 2)), mStorage(new Inventory(STORAGE_SIZE, 1)) #endif { // Variable to keep the local player from doing certain actions before a map // is initialized. e.g. drawing a player's name using the TextManager, since // it appears to be dependant upon map coordinates for updating drawing. mMapInitialized = false; mUpdateName = true; initTargetCursor(); } LocalPlayer::~LocalPlayer() { delete mInventory; #ifdef EATHENA_SUPPORT delete mStorage; #endif for (int i = Being::TC_SMALL; i < Being::NUM_TC; i++) { delete mTargetCursor[0][i]; delete mTargetCursor[1][i]; mTargetCursorImages[0][i]->decRef(); mTargetCursorImages[1][i]->decRef(); } } void LocalPlayer::logic() { #ifdef EATHENA_SUPPORT switch (mAction) { case STAND: break; case SIT: break; case DEAD: break; case HURT: break; case WALK: mFrame = (get_elapsed_time(mWalkTime) * 6) / getWalkSpeed(); if (mFrame >= 6) nextStep(); break; case ATTACK: int rotation = 0; std::string particleEffect = ""; int frames = 4; if (mEquippedWeapon && mEquippedWeapon->getAttackType() == ACTION_ATTACK_BOW) frames = 5; mFrame = (get_elapsed_time(mWalkTime) * frames) / mAttackSpeed; //attack particle effect if (mEquippedWeapon) particleEffect = mEquippedWeapon->getParticleEffect(); if (!particleEffect.empty() && mParticleEffects && mFrame == 1) { switch (mDirection) { case DOWN: rotation = 0; break; case LEFT: rotation = 90; break; case UP: rotation = 180; break; case RIGHT: rotation = 270; break; default: break; } Particle *p; p = particleEngine->addEffect("graphics/particles/" + particleEffect, 0, 0, rotation); controlParticle(p); } if (mFrame >= frames) nextStep(); break; } #endif // Actions are allowed once per second if (get_elapsed_time(mLastAction) >= 1000) mLastAction = -1; #ifdef TMWSERV_SUPPORT // Show XP messages if (!mExpMessages.empty()) { if (mExpMessageTime == 0) { const Vector &pos = getPosition(); particleEngine->addTextRiseFadeOutEffect( mExpMessages.front(), (int) pos.x + 16, (int) pos.y - 16, &guiPalette->getColor(Palette::EXP_INFO), gui->getInfoParticleFont(), true); mExpMessages.pop_front(); mExpMessageTime = 30; } mExpMessageTime--; } #else // Targeting allowed 4 times a second if (get_elapsed_time(mLastTarget) >= 250) mLastTarget = -1; // Remove target if its been on a being for more than a minute if (get_elapsed_time(mTargetTime) >= 60000) { mTargetTime = -1; setTarget(NULL); mLastTarget = -1; } if (mTarget) { if (mTarget->getType() == Being::NPC) { // NPCs are always in range mTarget->setTargetAnimation( mTargetCursor[0][mTarget->getTargetCursorSize()]); } else { // Find whether target is in range const int rangeX = abs(mTarget->mX - mX); const int rangeY = abs(mTarget->mY - mY); const int attackRange = getAttackRange(); const int inRange = rangeX > attackRange || rangeY > attackRange ? 1 : 0; mTarget->setTargetAnimation( mTargetCursor[inRange][mTarget->getTargetCursorSize()]); if (mTarget->mAction == DEAD) stopAttack(); if (mKeepAttacking && mTarget) attack(mTarget, true); } } #endif Being::logic(); } void LocalPlayer::setGM() { mIsGM = !mIsGM; mNameColor = mIsGM ? &guiPalette->getColor(Palette::GM) : &guiPalette->getColor(Palette::PLAYER); setName(getName()); config.setValue(getName() + "GMassert", mIsGM); } void LocalPlayer::setName(const std::string &name) { if (mName) { delete mName; mName = 0; } if (config.getValue("showownname", false) && mMapInitialized) Player::setName(name); else Being::setName(name); } void LocalPlayer::nextStep() { // TODO: Fix picking up when reaching target (this method is obsolete) // TODO: Fix holding walking button to keep walking smoothly if (mPath.empty()) { if (mPickUpTarget) pickUp(mPickUpTarget); if (mWalkingDir) walk(mWalkingDir); } // TODO: Fix automatically walking within range of target, when wanted if (mGoingToTarget && mTarget && withinAttackRange(mTarget)) { mAction = Being::STAND; #ifdef EATHENA_SUPPORT attack(mTarget, true); #endif mGoingToTarget = false; mPath.clear(); return; } else if (mGoingToTarget && !mTarget) { mGoingToTarget = false; mPath.clear(); } #ifdef EATHENA_SUPPORT Player::nextStep(); #endif } #ifdef TMWSERV_SUPPORT bool LocalPlayer::checkInviteRights(const std::string &guildName) { Guild *guild = getGuild(guildName); if (guild) { return guild->getInviteRights(); } return false; } void LocalPlayer::inviteToGuild(Being *being) { // TODO: Allow user to choose which guild to invite being to // For now, just invite to the first guild you have permissions to invite with std::map::iterator itr = mGuilds.begin(); std::map::iterator itr_end = mGuilds.end(); for (; itr != itr_end; ++itr) { if (checkInviteRights(itr->second->getName())) { Net::ChatServer::Guild::invitePlayer(being->getName(), itr->second->getId()); return; } } } void LocalPlayer::inviteToParty(const std::string &name) { Net::ChatServer::Party::invitePlayer(name); } void LocalPlayer::clearInventory() { mEquipment->clear(); mInventory->clear(); } void LocalPlayer::setInvItem(int index, int id, int amount) { mInventory->setItem(index, id, amount); } #endif void LocalPlayer::moveInvItem(Item *item, int newIndex) { // special case, the old and new cannot copy over each other. if (item->getInvIndex() == newIndex) return; #ifdef TMWSERV_SUPPORT Net::GameServer::Player::moveItem( item->getInvIndex(), newIndex, item->getQuantity()); #endif // TODO: eAthena support } void LocalPlayer::equipItem(Item *item) { #ifdef TMWSERV_SUPPORT Net::GameServer::Player::equip(item->getInvIndex()); #else MessageOut outMsg(CMSG_PLAYER_EQUIP); outMsg.writeInt16(item->getInvIndex()); outMsg.writeInt16(0); #endif } #ifdef TMWSERV_SUPPORT void LocalPlayer::unequipItem(int slot) { Net::GameServer::Player::unequip(slot); // Tidy equipment directly to avoid weapon still shown bug, for instance mEquipment->setEquipment(slot, 0); } void LocalPlayer::useItem(int slot) { Net::GameServer::Player::useItem(slot); } #else void LocalPlayer::unequipItem(Item *item) { if (!item) return; MessageOut outMsg(CMSG_PLAYER_UNEQUIP); outMsg.writeInt16(item->getInvIndex()); // Tidy equipment directly to avoid weapon still shown bug, for instance mEquipment->removeEquipment(item->getInvIndex()); } void LocalPlayer::useItem(Item *item) { MessageOut outMsg(CMSG_PLAYER_INVENTORY_USE); outMsg.writeInt16(item->getInvIndex()); outMsg.writeInt32(item->getId()); // Note: id is dest of item, usually player_node->account_ID ?? } #endif void LocalPlayer::dropItem(Item *item, int quantity) { #ifdef TMWSERV_SUPPORT Net::GameServer::Player::drop(item->getInvIndex(), quantity); #else // TODO: Fix wrong coordinates of drops, serverside? MessageOut outMsg(CMSG_PLAYER_INVENTORY_DROP); outMsg.writeInt16(item->getInvIndex()); outMsg.writeInt16(quantity); #endif } #ifdef TMWSERV_SUPPORT void LocalPlayer::splitItem(Item *item, int quantity) { int newIndex = mInventory->getFreeSlot(); if (newIndex > Inventory::NO_SLOT_INDEX) { Net::GameServer::Player::moveItem( item->getInvIndex(), newIndex, quantity); } } #endif void LocalPlayer::pickUp(FloorItem *item) { #ifdef TMWSERV_SUPPORT int dx = item->getX() - (int) getPosition().x / 32; int dy = item->getY() - (int) getPosition().y / 32; #else int dx = item->getX() - mX; int dy = item->getY() - mY; #endif if (dx * dx + dy * dy < 4) { #ifdef TMWSERV_SUPPORT int id = item->getId(); Net::GameServer::Player::pickUp(id >> 16, id & 0xFFFF); #else MessageOut outMsg(CMSG_ITEM_PICKUP); outMsg.writeInt32(item->getId()); #endif mPickUpTarget = NULL; } else { #ifdef TMWSERV_SUPPORT setDestination(item->getX() * 32 + 16, item->getY() * 32 + 16); #else setDestination(item->getX(), item->getY()); #endif mPickUpTarget = item; #ifdef EATHENA_SUPPORT stopAttack(); #endif } } void LocalPlayer::walk(unsigned char dir) { // TODO: Evaluate the implementation of this method for tmwserv if (!mMap || !dir) return; #ifdef TMWSERV_SUPPORT const Vector &pos = getPosition(); #endif if (mAction == WALK && !mPath.empty()) { // Just finish the current action, otherwise we get out of sync #ifdef TMWSERV_SUPPORT Being::setDestination(pos.x, pos.y); #else Being::setDestination(mX, mY); #endif return; } int dx = 0, dy = 0; #ifdef TMWSERV_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 // Prevent skipping corners over colliding tiles #ifdef TMWSERV_SUPPORT if (dx && !mMap->getWalk(((int) pos.x + dx) / 32, (int) pos.y / 32, getWalkMask())) dx = 16 - (int) pos.x % 32; if (dy && !mMap->getWalk((int) pos.x / 32, ((int) pos.y + dy) / 32, getWalkMask())) dy = 16 - (int) pos.y % 32; #else if (dx && !mMap->getWalk(mX + dx, mY, getWalkMask())) dx = 0; if (dy && !mMap->getWalk(mX, mY + dy, getWalkMask())) dy = 0; #endif // Choose a straight direction when diagonal target is blocked #ifdef TMWSERV_SUPPORT if (dx && dy && !mMap->getWalk((pos.x + dx) / 32, (pos.y + dy) / 32, getWalkMask())) dx = 16 - (int) pos.x % 32; int dScaler; // Distance to walk // Checks our path up to 5 tiles, if a blocking tile is found // We go to the last good tile, and break out of the loop for (dScaler = 1; dScaler <= 10; dScaler++) { if ( (dx || dy) && !mMap->getWalk( ((int) pos.x + (dx * dScaler)) / 32, ((int) pos.y + (dy * dScaler)) / 32, getWalkMask()) ) { dScaler--; break; } } if (dScaler >= 0) { setDestination((int) pos.x + (dx * dScaler), (int) pos.y + (dy * dScaler)); } #else if (dx && dy && !mMap->getWalk(mX + dx, mY + dy, getWalkMask())) dx = 0; // Walk to where the player can actually go if ((dx || dy) && mMap->getWalk(mX + dx, mY + dy, getWalkMask())) { setDestination(mX + dx, mY + dy); } #endif else if (dir) { // If the being can't move, just change direction #ifdef TMWSERV_SUPPORT Net::GameServer::Player::changeDir(dir); #else // TODO: Communicate this to the server #endif setDirection(dir); } } Being* LocalPlayer::getTarget() const { return mTarget; } void LocalPlayer::setTarget(Being *target) { #ifdef EATHENA_SUPPORT if (mLastTarget != -1 || target == this) return; mLastTarget = tick_time; if (target == mTarget) target = NULL; if (target || mAction == ATTACK) { mTargetTime = tick_time; } else { mKeepAttacking = false; mTargetTime = -1; } #endif if (mTarget) mTarget->untarget(); if (mTarget && mTarget->getType() == Being::MONSTER) static_cast(mTarget)->setShowName(false); mTarget = target; if (target && target->getType() == Being::MONSTER) static_cast(target)->setShowName(true); } #ifdef TMWSERV_SUPPORT void LocalPlayer::setDestination(int x, int y) #else void LocalPlayer::setDestination(Uint16 x, Uint16 y) #endif { #ifdef TMWSERV_SUPPORT // Fix coordinates so that the player does not seem to dig into walls. const int tx = x / 32; const int ty = y / 32; int fx = x % 32; int fy = y % 32; if (fx != 16 && !mMap->getWalk(tx + fx / 16 * 2 - 1, ty, getWalkMask())) fx = 16; if (fy != 16 && !mMap->getWalk(tx, ty + fy / 16 * 2 - 1, getWalkMask())) fy = 16; if (fx != 16 && fy != 16 && !mMap->getWalk(tx + fx / 16 * 2 - 1, ty + fy / 16 * 2 - 1, getWalkMask())) fx = 16; x = tx * 32 + fx; y = ty * 32 + fy; #endif // Only send a new message to the server when destination changes if (x != mDestX || y != mDestY) { mDestX = x; mDestY = y; #ifdef TMWSERV_SUPPORT Net::GameServer::Player::walk(x, y); //Debugging fire burst effectManager->trigger(15,x,y); #else char temp[4] = ""; set_coordinates(temp, x, y, mDirection); MessageOut outMsg(0x0085); outMsg.writeString(temp, 3); #endif } mPickUpTarget = NULL; Being::setDestination(x, y); } void LocalPlayer::setWalkingDir(int dir) { mWalkingDir = dir; // If we're not already walking, start walking. if (mAction != WALK && dir #ifdef TMWSERV_SUPPORT && get_elapsed_time(mLocalWalkTime) >= walkingKeyboardDelay #endif ) { walk(dir); } } #ifdef TMWSERV_SUPPORT void LocalPlayer::stopWalking(bool sendToServer) { if (mAction == WALK && mWalkingDir) { mWalkingDir = 0; mLocalWalkTime = 0; Being::setDestination(getPosition().x,getPosition().y); if (sendToServer) Net::GameServer::Player::walk(getPosition().x, getPosition().y); setAction(STAND); } clearPath(); } #endif #ifdef EATHENA_SUPPORT void LocalPlayer::raiseAttribute(Attribute attr) { MessageOut outMsg(CMSG_STAT_UPDATE_REQUEST); switch (attr) { case STR: outMsg.writeInt16(0x000d); break; case AGI: outMsg.writeInt16(0x000e); break; case VIT: outMsg.writeInt16(0x000f); break; case INT: outMsg.writeInt16(0x0010); break; case DEX: outMsg.writeInt16(0x0011); break; case LUK: outMsg.writeInt16(0x0012); break; } outMsg.writeInt8(1); } void LocalPlayer::raiseSkill(Uint16 skillId) { if (mSkillPoint <= 0) return; MessageOut outMsg(CMSG_SKILL_LEVELUP_REQUEST); outMsg.writeInt16(skillId); } #endif void LocalPlayer::toggleSit() { if (mLastAction != -1) return; mLastAction = tick_time; Being::Action newAction; switch (mAction) { case STAND: newAction = SIT; break; case SIT: newAction = STAND; break; default: return; } #ifdef TMWSERV_SUPPORT setAction(newAction); Net::GameServer::Player::changeAction(newAction); #else MessageOut outMsg(0x0089); outMsg.writeInt32(0); outMsg.writeInt8((newAction == SIT) ? 2 : 3); #endif } void LocalPlayer::emote(Uint8 emotion) { if (mLastAction != -1) return; mLastAction = tick_time; // XXX Convert for new server #ifdef EATHENA_SUPPORT MessageOut outMsg(0x00bf); outMsg.writeInt8(emotion); #endif } #ifdef EATHENA_SUPPORT void LocalPlayer::tradeReply(bool accept) { if (!accept) mTrading = false; MessageOut outMsg(CMSG_TRADE_RESPONSE); outMsg.writeInt8(accept ? 3 : 4); } #endif void LocalPlayer::trade(Being *being) const { #ifdef TMWSERV_SUPPORT extern std::string tradePartnerName; extern int tradePartnerID; tradePartnerName = being->getName(); tradePartnerID = being->getId(); Net::GameServer::Player::requestTrade(tradePartnerID); #else MessageOut outMsg(CMSG_TRADE_REQUEST); outMsg.writeInt32(being->getId()); #endif } bool LocalPlayer::tradeRequestOk() const { return !mTrading; } #ifdef TMWSERV_SUPPORT void LocalPlayer::attack() { if (mLastAction != -1) return; // Can only attack when standing still if (mAction != STAND && mAction != ATTACK) return; //Face direction of the target if(mTarget){ unsigned char dir = 0; int x = 0, y = 0; Vector plaPos = this->getPosition(); Vector tarPos = mTarget->getPosition(); x = plaPos.x - tarPos.x; y = plaPos.y - tarPos.y; if(abs(x) < abs(y)){ //Check to see if target is above me or below me if(y > 0){ dir = UP; } else { dir = DOWN; } } else { //check to see if the target is to the left or right of me if(x > 0){ dir = LEFT; } else { dir = RIGHT; } } setDirection(dir); } mLastAction = tick_time; setAction(ATTACK); if (mEquippedWeapon) { std::string soundFile = mEquippedWeapon->getSound(EQUIP_EVENT_STRIKE); if (soundFile != "") sound.playSfx(soundFile); } else { sound.playSfx("sfx/fist-swish.ogg"); } Net::GameServer::Player::attack(getSpriteDirection()); } void LocalPlayer::useSpecial(int special) { Net::GameServer::Player::useSpecial(special); } #else void LocalPlayer::attack(Being *target, bool keep) { mKeepAttacking = keep; if (!target || target->getType() == Being::NPC) return; if ((mTarget != target) || !mTarget) { mLastTarget = -1; setTarget(target); } int dist_x = target->mX - mX; int dist_y = target->mY - mY; // Must be standing to attack if (mAction != STAND) 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); } mWalkTime = tick_time; mTargetTime = tick_time; setAction(ATTACK); if (mEquippedWeapon) { std::string soundFile = mEquippedWeapon->getSound(EQUIP_EVENT_STRIKE); if (!soundFile.empty()) sound.playSfx(soundFile); } else { sound.playSfx("sfx/fist-swish.ogg"); } MessageOut outMsg(0x0089); outMsg.writeInt32(target->getId()); outMsg.writeInt8(0); if (!keep) stopAttack(); } void LocalPlayer::stopAttack() { if (mTarget) { setAction(STAND); mLastTarget = -1; } setTarget(NULL); mLastTarget = -1; } #endif // no TMWSERV_SUPPORT void LocalPlayer::revive() { // XXX Convert for new server #ifdef EATHENA_SUPPORT MessageOut outMsg(0x00b2); outMsg.writeInt8(0); #endif } #ifdef TMWSERV_SUPPORT void LocalPlayer::raiseAttribute(size_t attr) { // we assume that the server allows the change. When not we will undo it later. mCharacterPoints--; mAttributeBase.at(attr)++; Net::GameServer::Player::raiseAttribute(attr + CHAR_ATTR_BEGIN); } void LocalPlayer::lowerAttribute(size_t attr) { // we assume that the server allows the change. When not we will undo it later. mCorrectionPoints--; mCharacterPoints++; mAttributeBase.at(attr)--; Net::GameServer::Player::lowerAttribute(attr + CHAR_ATTR_BEGIN); } const struct LocalPlayer::SkillInfo& LocalPlayer::getSkillInfo(int skill) { static const SkillInfo skills[CHAR_SKILL_NB + 1] = { { _("Unarmed"), "graphics/images/unarmed.png" }, // CHAR_SKILL_WEAPON_NONE { _("Knife"), "graphics/images/knife.png" }, // CHAR_SKILL_WEAPON_KNIFE { _("Sword"), "graphics/images/sword.png" }, // CHAR_SKILL_WEAPON_SWORD { _("Polearm"), "graphics/images/polearm.png" }, // CHAR_SKILL_WEAPON_POLEARM { _("Staff"), "graphics/images/staff.png" }, // CHAR_SKILL_WEAPON_STAFF { _("Whip"), "graphics/images/whip.png" }, // CHAR_SKILL_WEAPON_WHIP { _("Bow"), "graphics/images/bow.png" }, // CHAR_SKILL_WEAPON_BOW { _("Shooting"), "graphics/images/shooting.png" }, // CHAR_SKILL_WEAPON_SHOOTING { _("Mace"), "graphics/images/mace.png" }, // CHAR_SKILL_WEAPON_MACE { _("Axe"), "graphics/images/axe.png" }, // CHAR_SKILL_WEAPON_AXE { _("Thrown"), "graphics/images/thrown.png" }, // CHAR_SKILL_WEAPON_THROWN { _("Magic"), "graphics/images/magic.png" }, // CHAR_SKILL_MAGIC_IAMJUSTAPLACEHOLDER { _("Craft"), "graphics/images/craft.png" }, // CHAR_SKILL_CRAFT_IAMJUSTAPLACEHOLDER { _("Unknown Skill"), "graphics/images/unknown.png" } }; if ((skill < 0) || (skill > CHAR_SKILL_NB)) { return skills[CHAR_SKILL_NB]; } else { return skills[skill]; } } void LocalPlayer::setExperience(int skill, int current, int next) { int diff = current - mExpCurrent.at(skill); if (mMap && mExpCurrent.at(skill) != -1 && diff > 0) { const std::string text = toString(diff) + " " + getSkillInfo(skill).name + " xp"; mExpMessages.push_back(text); } mExpCurrent.at(skill) = current; mExpNext.at(skill) = next; } std::pair LocalPlayer::getExperience(int skill) { return std::pair (mExpCurrent.at(skill), mExpNext.at(skill)); } #else void LocalPlayer::setXp(int xp) { if (mMap && xp > mXp) { const std::string text = toString(xp - mXp) + " xp"; // Show XP number particleEngine->addTextRiseFadeOutEffect( text, getPixelX() + 16, getPixelY() - 16, &guiPalette->getColor(Palette::EXP_INFO), gui->getInfoParticleFont(), true); } mXp = xp; } #endif void LocalPlayer::pickedUp(const std::string &item) { if (mMap) { // Show pickup notification particleEngine->addTextRiseFadeOutEffect( item, getPixelX() + 16, getPixelY() - 16, &guiPalette->getColor(Palette::PICKUP_INFO), gui->getInfoParticleFont (), true); } } int LocalPlayer::getAttackRange() { #ifdef TMWSERV_SUPPORT Item *weapon = mEquipment->getEquipment(EQUIP_FIGHT1_SLOT); if (weapon) { const ItemInfo info = weapon->getInfo(); return info.getAttackRange(); } return 32; // unarmed range #else return mAttackRange; #endif } bool LocalPlayer::withinAttackRange(Being *target) { #ifdef TMWSERV_SUPPORT const Vector &targetPos = target->getPosition(); const Vector &pos = getPosition(); const int dx = abs(targetPos.x - pos.x); const int dy = abs(targetPos.y - pos.y); const int range = getAttackRange(); return !(dx > range || dy > range); #else int dist_x = abs(target->mX - mX); int dist_y = abs(target->mY - mY); if (dist_x > getAttackRange() || dist_y > getAttackRange()) { return false; } return true; #endif } void LocalPlayer::setGotoTarget(Being *target) { #ifdef TMWSERV_SUPPORT mTarget = target; mGoingToTarget = true; const Vector &targetPos = target->getPosition(); setDestination(targetPos.x, targetPos.y); #else mLastTarget = -1; setTarget(target); mGoingToTarget = true; setDestination(target->mX, target->mY); #endif } extern MiniStatusWindow *miniStatusWindow; void LocalPlayer::handleStatusEffect(StatusEffect *effect, int effectId) { Being::handleStatusEffect(effect, effectId); if (effect) { effect->deliverMessage(); effect->playSFX(); AnimatedSprite *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); miniStatusWindow->eraseIcon(i); } else i++; } else { // replace sprite or append bool found = false; for (unsigned int i = 0; i < mStatusEffectIcons.size(); i++) if (mStatusEffectIcons[i] == effectId) { miniStatusWindow->setIcon(i, sprite); found = true; break; } if (!found) { // add new int offset = mStatusEffectIcons.size(); miniStatusWindow->setIcon(offset, sprite); mStatusEffectIcons.push_back(effectId); } } } } void LocalPlayer::initTargetCursor() { // Load target cursors loadTargetCursor("graphics/gui/target-cursor-blue-s.png", 44, 35, false, TC_SMALL); loadTargetCursor("graphics/gui/target-cursor-red-s.png", 44, 35, true, TC_SMALL); loadTargetCursor("graphics/gui/target-cursor-blue-m.png", 62, 44, false, TC_MEDIUM); loadTargetCursor("graphics/gui/target-cursor-red-m.png", 62, 44, true, TC_MEDIUM); loadTargetCursor("graphics/gui/target-cursor-blue-l.png", 82, 60, false, TC_LARGE); loadTargetCursor("graphics/gui/target-cursor-red-l.png", 82, 60, true, TC_LARGE); } void LocalPlayer::loadTargetCursor(std::string filename, int width, int height, bool outRange, TargetCursorSize size) { assert(size > -1); assert(size < 3); ImageSet* currentImageSet; SimpleAnimation* currentCursor; ResourceManager *resman = ResourceManager::getInstance(); currentImageSet = resman->getImageSet(filename, width, height); Animation *anim = new Animation; for (unsigned int i = 0; i < currentImageSet->size(); ++i) { anim->addFrame(currentImageSet->get(i), 75, (16 - (currentImageSet->getWidth() / 2)), (16 - (currentImageSet->getHeight() / 2))); } currentCursor = new SimpleAnimation(anim); const int index = outRange ? 1 : 0; mTargetCursorImages[index][size] = currentImageSet; mTargetCursor[index][size] = currentCursor; } #ifdef EATHENA_SUPPORT void LocalPlayer::setInStorage(bool inStorage) { mInStorage = inStorage; storageWindow->setVisible(inStorage); } #endif