/*
* The Mana Client
* Copyright (C) 2004-2009 The Mana World Development Team
* Copyright (C) 2009-2010 The Mana Developers
*
* This file is part of The Mana 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 "localplayer.h"
#include "client.h"
#include "configuration.h"
#include "effectmanager.h"
#include "equipment.h"
#include "flooritem.h"
#include "graphics.h"
#include "guild.h"
#include "inventory.h"
#include "item.h"
#include "log.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/inventorywindow.h"
#include "gui/ministatus.h"
#include "gui/skilldialog.h"
#include "gui/statuswindow.h"
#include "gui/theme.h"
#include "gui/userpalette.h"
#include "gui/widgets/chattab.h"
#include "net/guildhandler.h"
#include "net/inventoryhandler.h"
#include "net/net.h"
#include "net/partyhandler.h"
#include "net/playerhandler.h"
#include "net/specialhandler.h"
#include "net/tradehandler.h"
#include "resources/animation.h"
#include "resources/imageset.h"
#include "resources/itemdb.h"
#include "resources/iteminfo.h"
#include "resources/resourcemanager.h"
#include "utils/gettext.h"
#include "utils/stringutils.h"
#include <cassert>
// This is the minimal delay between to permitted
// setDestination() calls using the keyboard.
// TODO: This can fine tuned later on when running is added...
const short walkingKeyboardDelay = 1000;
LocalPlayer *player_node = NULL;
LocalPlayer::LocalPlayer(int id, int subtype):
Player(id, subtype, 0),
mEquipment(new Equipment),
mAttackRange(0),
mTargetTime(-1),
mLastTarget(-1),
mCharacterPoints(0),
mCorrectionPoints(0),
mSpecialRechargeUpdateNeeded(0),
mLevel(1),
mExp(0), mExpNeeded(0),
mMp(0), mMaxMp(0),
mMoney(0),
mTotalWeight(1), mMaxWeight(1),
mHp(1), mMaxHp(1),
mSkillPoints(0),
mTarget(NULL),
mPlayerFollowed(""),
mPickUpTarget(NULL),
mTrading(false), mGoingToTarget(false), mKeepAttacking(false),
mLastAction(-1),
mWalkingDir(0),
mPathSetByMouse(false),
mInventory(new Inventory(Inventory::INVENTORY)),
mLocalWalkTime(-1),
mMessageTime(0)
{
mUpdateName = true;
mTextColor = &Theme::getThemeColor(Theme::PLAYER);
mNameColor = &userPalette->getColor(UserPalette::SELF);
initTargetCursor();
config.addListener("showownname", this);
setShowName(config.getValue("showownname", 1));
}
LocalPlayer::~LocalPlayer()
{
delete mInventory;
config.removeListener("showownname", this);
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()
{
// Actions are allowed once per second
if (get_elapsed_time(mLastAction) >= 1000)
mLastAction = -1;
// Show XP messages
if (!mMessages.empty())
{
if (mMessageTime == 0)
{
//const Vector &pos = getPosition();
MessagePair info = mMessages.front();
particleEngine->addTextRiseFadeOutEffect(
info.first,
/*(int) pos.x,
(int) pos.y - 48,*/
getPixelX(),
getPixelY() - 48,
&userPalette->getColor(info.second),
gui->getInfoParticleFont(), true);
mMessages.pop_front();
mMessageTime = 30;
}
mMessageTime--;
}
if ((mSpecialRechargeUpdateNeeded%11) == 0)
{
mSpecialRechargeUpdateNeeded = 0;
for (std::map<int, Special>::iterator i = mSpecials.begin();
i != mSpecials.end();
i++)
{
i->second.currentMana += i->second.recharge;
if (i->second.currentMana > i->second.neededMana)
{
i->second.currentMana = i->second.neededMana;
}
}
}
mSpecialRechargeUpdateNeeded++;
// 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
// TODO: Make this nicer, probably using getPosition() only
const int rangeX =
(Net::getNetworkType() == ServerInfo::MANASERV) ?
abs(mTarget->getPosition().x - getPosition().x) :
abs(mTarget->getTileX() - getTileX());
const int rangeY =
(Net::getNetworkType() == ServerInfo::MANASERV) ?
abs(mTarget->getPosition().y - getPosition().y) :
abs(mTarget->getTileY() - getTileY());
const int attackRange = getAttackRange();
const int inRange = rangeX > attackRange || rangeY > attackRange
? 1 : 0;
mTarget->setTargetAnimation(
mTargetCursor[inRange][mTarget->getTargetCursorSize()]);
if (!mTarget->isAlive())
stopAttack();
if (mKeepAttacking && mTarget)
attack(mTarget, true);
}
}
Player::logic();
}
void LocalPlayer::setAction(Action action, int attackType)
{
if (action == DEAD)
{
mLastTarget = -1;
setTarget(NULL);
}
Player::setAction(action, attackType);
}
void LocalPlayer::setGM(bool gm)
{
mIsGM = gm;
}
void LocalPlayer::setGMLevel(int level)
{
mGMLevel = level;
if (level > 0)
setGM(true);
}
Position LocalPlayer::getNextWalkPosition(unsigned char dir)
{
// 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++;
Vector pos = getPosition();
// If no map or no direction is given, give back the current player position
if (!mMap || (!dx && !dy))
return Position((int)pos.x, (int)pos.y);
// Get the current tile pos and its offset
int tileX = (int)pos.x / mMap->getTileWidth();
int tileY = (int)pos.y / mMap->getTileHeight();
int offsetX = (int)pos.x % mMap->getTileWidth();
int offsetY = (int)pos.y % mMap->getTileHeight();
// Get the walkability of every surrounding tiles.
bool wTopLeft = mMap->getWalk(tileX - 1, tileY - 1, getWalkMask());
bool wTop = mMap->getWalk(tileX, tileY - 1, getWalkMask());
bool wTopRight = mMap->getWalk(tileX + 1, tileY - 1, getWalkMask());
bool wLeft = mMap->getWalk(tileX - 1, tileY, getWalkMask());
bool wRight = mMap->getWalk(tileX + 1, tileY, getWalkMask());
bool wBottomLeft = mMap->getWalk(tileX - 1, tileY + 1, getWalkMask());
bool wBottom = mMap->getWalk(tileX, tileY + 1, getWalkMask());
bool wBottomRight = mMap->getWalk(tileX + 1, tileY + 1, getWalkMask());
// 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 - getCollisionRadius(),
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(getCollisionRadius(),
getWalkMask(),
Position((int)pos.x + 32, (int)pos.y - 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 + getCollisionRadius(),
tileY * 32 + getCollisionRadius());
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(getCollisionRadius(),
getWalkMask(),
Position((int)pos.x - 32, (int)pos.y - 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 + getCollisionRadius(),
tileY * 32 + 32 - getCollisionRadius());
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(getCollisionRadius(),
getWalkMask(),
Position((int)pos.x - 32, (int)pos.y + 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 - getCollisionRadius(),
tileY * 32 + 32 - getCollisionRadius());
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(getCollisionRadius(),
getWalkMask(),
Position((int)pos.x + 32, (int)pos.y + 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 - getCollisionRadius(), (int)pos.y);
else
{
if (!wTopRight)
{
// If we're going to collide with the top-right corner
if (offsetY - getCollisionRadius() < 0)
{
// We make the player corrects its offset
// before going further
return Position(tileX * 32 + 32 - getCollisionRadius(),
tileY * 32 + getCollisionRadius());
}
}
if (!wBottomRight)
{
// If we're going to collide with the bottom-right corner
if (offsetY + getCollisionRadius() > 32)
{
// We make the player corrects its offset
// before going further
return Position(tileX * 32 + 32 - getCollisionRadius(),
tileY * 32 + 32 - getCollisionRadius());
}
}
// If the way is clear, step up one checked tile ahead.
return mMap->checkNodeOffsets(getCollisionRadius(), getWalkMask(),
Position((int)pos.x + 32, (int)pos.y));
}
}
// 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 + getCollisionRadius(), (int)pos.y);
else
{
if (!wTopLeft)
{
// If we're going to collide with the top-left corner
if (offsetY - getCollisionRadius() < 0)
{
// We make the player corrects its offset
// before going further
return Position(tileX * 32 + getCollisionRadius(),
tileY * 32 + getCollisionRadius());
}
}
if (!wBottomLeft)
{
// If we're going to collide with the bottom-left corner
if (offsetY + getCollisionRadius() > 32)
{
// We make the player corrects its offset
// before going further
return Position(tileX * 32 + getCollisionRadius(),
tileY * 32 + 32 - getCollisionRadius());
}
}
// If the way is clear, step up one checked tile ahead.
return mMap->checkNodeOffsets(getCollisionRadius(), getWalkMask(),
Position((int)pos.x - 32, (int)pos.y));
}
}
// Up direction
if (!dx && dy < 0)
{
// If the straight destination is blocked,
// Make the player go the closest possible.
if (!wTop)
return Position((int)pos.x, tileY * 32 + getCollisionRadius());
else
{
if (!wTopLeft)
{
// If we're going to collide with the top-left corner
if (offsetX - getCollisionRadius() < 0)
{
// We make the player corrects its offset
// before going further
return Position(tileX * 32 + getCollisionRadius(),
tileY * 32 + getCollisionRadius());
}
}
if (!wTopRight)
{
// If we're going to collide with the top-right corner
if (offsetX + getCollisionRadius() > 32)
{
// We make the player corrects its offset
// before going further
return Position(tileX * 32 + 32 - getCollisionRadius(),
tileY * 32 + getCollisionRadius());
}
}
// If the way is clear, step up one checked tile ahead.
return mMap->checkNodeOffsets(getCollisionRadius(), getWalkMask(),
Position((int)pos.x, (int)pos.y - 32));
}
}
// Down direction
if (!dx && dy > 0)
{
// If the straight destination is blocked,
// Make the player go the closest possible.
if (!wBottom)
return Position((int)pos.x, tileY * 32 + 32 - getCollisionRadius());
else
{
if (!wBottomLeft)
{
// If we're going to collide with the bottom-left corner
if (offsetX - getCollisionRadius() < 0)
{
// We make the player corrects its offset
// before going further
return Position(tileX * 32 + getCollisionRadius(),
tileY * 32 + 32 - getCollisionRadius());
}
}
if (!wBottomRight)
{
// If we're going to collide with the bottom-right corner
if (offsetX + getCollisionRadius() > 32)
{
// We make the player corrects its offset
// before going further
return Position(tileX * 32 + 32 - getCollisionRadius(),
tileY * 32 + 32 - getCollisionRadius());
}
}
// If the way is clear, step up one checked tile ahead.
return mMap->checkNodeOffsets(getCollisionRadius(), getWalkMask(),
Position((int)pos.x, (int)pos.y + 32));
}
}
// Return the current position if everything else has failed.
return Position((int)pos.x, (int)pos.y);
}
void LocalPlayer::nextTile(unsigned char dir = 0)
{
if (Net::getNetworkType() == ServerInfo::TMWATHENA)
{
// 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)
startWalking(mWalkingDir);
}
// TODO: Fix automatically walking within range of target, when wanted
if (mGoingToTarget && mTarget && withinAttackRange(mTarget))
{
mAction = Being::STAND;
attack(mTarget, true);
mGoingToTarget = false;
mPath.clear();
return;
}
else if (mGoingToTarget && !mTarget)
{
mGoingToTarget = false;
mPath.clear();
}
Player::nextTile();
}
else
{
if (!mMap || !dir)
return;
const Vector &pos = getPosition();
Position destination = getNextWalkPosition(dir);
if ((int)pos.x != destination.x
|| (int)pos.y != destination.y)
{
setDestination(destination.x, destination.y);
}
else if (dir != mDirection)
{
// If the being can't move, just change direction
Net::getPlayerHandler()->setDirection(dir);
setDirection(dir);
}
}
}
bool LocalPlayer::checkInviteRights(const std::string &guildName)
{
Guild *guild = getGuild(guildName);
if (guild)
{
return guild->getInviteRights();
}
return false;
}
void LocalPlayer::inviteToGuild(Being *being)
{
if (being->getType() != PLAYER)
return;
Player *player = static_cast<Player*>(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<int, Guild*>::iterator itr = mGuilds.begin();
std::map<int, Guild*>::iterator itr_end = mGuilds.end();
for (; itr != itr_end; ++itr)
{
if (checkInviteRights(itr->second->getName()))
{
Net::getGuildHandler()->invite(itr->second->getId(), player);
return;
}
}
}
void LocalPlayer::clearInventory()
{
mEquipment->clear();
mInventory->clear();
}
void LocalPlayer::setInvItem(int index, int id, int amount)
{
bool equipment = false;
int itemType = ItemDB::get(id).getType();
if (itemType != ITEM_UNUSABLE && itemType != ITEM_USABLE)
equipment = true;
mInventory->setItem(index, id, amount, equipment);
}
void LocalPlayer::pickUp(FloorItem *item)
{
int dx = item->getX() - (int) getPosition().x / 32;
int dy = item->getY() - (int) getPosition().y / 32;
if (dx * dx + dy * dy < 4)
{
Net::getPlayerHandler()->pickUp(item);
mPickUpTarget = NULL;
}
else
{
if (Net::getNetworkType() == ServerInfo::MANASERV)
{
setDestination(item->getX() * 32 + 16, item->getY() * 32 + 16);
mPickUpTarget = item;
}
else
{
setDestination(item->getX(), item->getY());
mPickUpTarget = item;
stopAttack();
}
}
}
Being *LocalPlayer::getTarget() const
{
return mTarget;
}
void LocalPlayer::setTarget(Being *target)
{
if ((mLastTarget != -1 || target == this) && target)
return;
if (target)
mLastTarget = tick_time;
if (target == mTarget)
return;
if (target || mAction == ATTACK)
{
mTargetTime = tick_time;
}
else
{
mKeepAttacking = false;
mTargetTime = -1;
}
if (mTarget)
mTarget->untarget();
if (mTarget && mTarget->getType() == Being::MONSTER)
mTarget->setShowName(false);
mTarget = target;
if (target && target->getType() == Being::MONSTER)
target->setShowName(true);
}
void LocalPlayer::setDestination(int x, int y)
{
// Only send a new message to the server when destination changes
if (x != mDest.x || y != mDest.y)
{
Being::setDestination(x, y);
// Manaserv:
// If the destination given to being class is accepted,
// we inform the Server.
if ((x == mDest.x && y == mDest.y)
|| Net::getNetworkType() == ServerInfo::TMWATHENA)
Net::getPlayerHandler()->setDestination(x, y, mDirection);
}
mPickUpTarget = NULL;
mKeepAttacking = false;
}
void LocalPlayer::setWalkingDir(int dir)
{
// This function is called by Game::handleInput()
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()))
player_node->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)
player_node->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;
}
mWalkingDir = dir;
// If we're not already walking, start walking.
if (mAction != WALK && dir)
{
startWalking(dir);
}
else if (mAction == WALK && (Net::getNetworkType() == ServerInfo::MANASERV))
{
nextTile(dir);
}
}
void LocalPlayer::startWalking(unsigned char dir)
{
// This function is called by setWalkingDir(),
// but also by nextTile() for TMW-Athena...
if (!mMap || !dir)
return;
if (mAction == WALK && !mPath.empty())
{
// Just finish the current action, otherwise we get out of sync
if (Net::getNetworkType() == ServerInfo::MANASERV)
{
const Vector &pos = getPosition();
Being::setDestination(pos.x, pos.y);
}
else
Being::setDestination(getTileX(), getTileY());
return;
}
int dx = 0, dy = 0;
if (dir & UP)
dy--;
if (dir & DOWN)
dy++;
if (dir & LEFT)
dx--;
if (dir & RIGHT)
dx++;
if (Net::getNetworkType() == ServerInfo::TMWATHENA)
{
// Prevent skipping corners over colliding tiles
if (dx && !mMap->getWalk(getTileX() + dx, getTileY(), getWalkMask()))
dx = 0;
if (dy && !mMap->getWalk(getTileX(), getTileY() + dy, getWalkMask()))
dy = 0;
// Choose a straight direction when diagonal target is blocked
if (dx && dy && !mMap->getWalk(getTileX() + dx, getTileY() + dy,
getWalkMask()))
dx = 0;
// Walk to where the player can actually go
if ((dx || dy) && mMap->getWalk(getTileX() + dx, getTileY() + dy,
getWalkMask()))
{
setDestination(getTileX() + dx, getTileY() + dy);
}
else if (dir != mDirection)
{
// If the being can't move, just change direction
Net::getPlayerHandler()->setDirection(dir);
setDirection(dir);
}
}
else
nextTile(dir);
}
void LocalPlayer::stopWalking(bool sendToServer)
{
if (mAction == WALK && mWalkingDir)
{
mWalkingDir = 0;
mLocalWalkTime = 0;
setDestination((int) getPosition().x, (int) getPosition().y);
if (sendToServer)
Net::getPlayerHandler()->setDestination((int) getPosition().x,
(int) getPosition().y);
setAction(STAND);
}
// No path set anymore, so we reset the path by mouse flag
mPathSetByMouse = false;
clearPath();
}
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;
}
Net::getPlayerHandler()->changeAction(newAction);
}
void LocalPlayer::emote(Uint8 emotion)
{
if (mLastAction != -1)
return;
mLastAction = tick_time;
Net::getPlayerHandler()->emote(emotion);
}
void LocalPlayer::useSpecial(int special)
{
Net::getSpecialHandler()->use(special);
}
void LocalPlayer::setSpecialStatus(int id, int current, int max, int recharge)
{
logger->log("SpecialUpdate Skill #%d -- (%d/%d) -> %d", id, current, max,
recharge);
mSpecials[id].currentMana = current;
mSpecials[id].neededMana = max;
mSpecials[id].recharge = recharge;
}
void LocalPlayer::attack(Being *target, bool keep)
{
if (Net::getNetworkType() == ServerInfo::MANASERV)
{
if (mLastAction != -1)
return;
// Can only attack when standing still
if (mAction != STAND && mAction != ATTACK)
return;
}
mKeepAttacking = keep;
if (!target || target->getType() == Being::NPC)
return;
if (mTarget != target || !mTarget)
{
mLastTarget = -1;
setTarget(target);
}
if (Net::getNetworkType() == ServerInfo::MANASERV)
{
Vector plaPos = this->getPosition();
Vector tarPos = mTarget->getPosition();
int dist_x = plaPos.x - tarPos.x;
int dist_y = 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
{
int dist_x = target->getTileX() - getTileX();
int dist_y = target->getTileY() - getTileY();
// 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");
}
Net::getPlayerHandler()->attack(target->getId());
if ((Net::getNetworkType() == ServerInfo::TMWATHENA) && !keep)
stopAttack();
}
void LocalPlayer::stopAttack()
{
if (mTarget)
{
if (mAction == ATTACK)
setAction(STAND);
setTarget(NULL);
}
mLastTarget = -1;
}
void LocalPlayer::raiseAttribute(int attr)
{
// we assume that the server allows the change.
// When not we will undo it later.
mCharacterPoints--;
IntMap::iterator it = mAttributeBase.find(attr);
if (it != mAttributeBase.end())
(*it).second++;
Net::getPlayerHandler()->increaseAttribute(attr);
}
void LocalPlayer::lowerAttribute(int attr)
{
// we assume that the server allows the change.
// When not we will undo it later.
mCorrectionPoints--;
mCharacterPoints++;
IntMap::iterator it = mAttributeBase.find(attr);
if (it != mAttributeBase.end())
(*it).second--;
Net::getPlayerHandler()->decreaseAttribute(attr);
}
void LocalPlayer::setTotalWeight(int value)
{
mTotalWeight = value;
inventoryWindow->updateWeight();
}
void LocalPlayer::setMaxWeight(int value)
{
mMaxWeight = value;
inventoryWindow->updateWeight();
}
void LocalPlayer::setAttributeBase(int num, int value, bool notify)
{
int old = mAttributeBase[num];
mAttributeBase[num] = value;
if (skillDialog)
{
if (skillDialog->update(num).empty() || !(value > old))
return;
if (old != 0 && notify)
effectManager->trigger(1, this);
}
if (statusWindow)
statusWindow->update(num);
}
void LocalPlayer::setAttributeEffective(int num, int value)
{
mAttributeEffective[num] = value;
if (skillDialog)
skillDialog->update(num);
if (statusWindow)
statusWindow->update(num);
}
void LocalPlayer::setCharacterPoints(int n)
{
mCharacterPoints = n;
if (statusWindow)
statusWindow->update(StatusWindow::CHAR_POINTS);
}
void LocalPlayer::setCorrectionPoints(int n)
{
mCorrectionPoints = n;
if (statusWindow)
statusWindow->update(StatusWindow::CHAR_POINTS);
}
void LocalPlayer::setSkillPoints(int points)
{
mSkillPoints = points;
if (skillDialog)
skillDialog->update();
}
void LocalPlayer::setExperience(int skill, int current, int next, bool notify)
{
std::pair<int, int> cur = getExperience(skill);
int diff = current - cur.first;
cur = std::pair<int, int>(current, next);
mSkillExp[skill] = cur;
std::string name;
if (skillDialog)
name = skillDialog->update(skill);
if (mMap && notify && cur.first != -1 && diff > 0 && !name.empty())
{
addMessageToQueue(strprintf("%d %s xp", diff, name.c_str()));
}
if (statusWindow)
statusWindow->update(skill);
}
std::pair<int, int> LocalPlayer::getExperience(int skill)
{
return mSkillExp[skill];
}
void LocalPlayer::setHp(int value)
{
mHp = value;
if (statusWindow)
statusWindow->update(StatusWindow::HP);
}
void LocalPlayer::setMaxHp(int value)
{
mMaxHp = value;
if (statusWindow)
statusWindow->update(StatusWindow::HP);
}
void LocalPlayer::setLevel(int value)
{
mLevel = value;
if (statusWindow)
statusWindow->update(StatusWindow::LEVEL);
}
void LocalPlayer::setExp(int value, bool notify)
{
if (mMap && notify && value > mExp)
{
addMessageToQueue(toString(value - mExp) + " xp");
}
mExp = value;
if (statusWindow)
statusWindow->update(StatusWindow::EXP);
}
void LocalPlayer::setExpNeeded(int value)
{
mExpNeeded = value;
if (statusWindow)
statusWindow->update(StatusWindow::EXP);
}
void LocalPlayer::setMP(int value)
{
mMp = value;
if (statusWindow)
statusWindow->update(StatusWindow::MP);
}
void LocalPlayer::setMaxMP(int value)
{
mMaxMp = value;
if (statusWindow)
statusWindow->update(StatusWindow::MP);
}
void LocalPlayer::setMoney(int value)
{
mMoney = value;
if (statusWindow)
statusWindow->update(StatusWindow::MONEY);
}
void LocalPlayer::pickedUp(const ItemInfo &itemInfo, int amount)
{
if (!amount)
{
if (config.getValue("showpickupchat", 1))
{
localChatTab->chatLog(_("Unable to pick up item."), BY_SERVER);
}
}
else
{
if (config.getValue("showpickupchat", 1))
{
// TRANSLATORS: This sentence may be translated differently
// for different grammatical numbers (singular, plural, ...)
localChatTab->chatLog(strprintf(ngettext("You picked up %d "
"[@@%d|%s@@].", "You picked up %d [@@%d|%s@@].", amount),
amount, itemInfo.getId(), itemInfo.getName().c_str()),
BY_SERVER);
}
if (mMap && config.getValue("showpickupparticle", 0))
{
// Show pickup notification
addMessageToQueue(itemInfo.getName(), UserPalette::PICKUP_INFO);
}
}
}
int LocalPlayer::getAttackRange()
{
if (mAttackRange > -1)
{
return mAttackRange;
}
else
{
Item *weapon = mEquipment->getEquipment(EQUIP_FIGHT1_SLOT);
if (weapon)
{
const ItemInfo info = weapon->getInfo();
return info.getAttackRange();
}
return 48; // unarmed range
}
}
bool LocalPlayer::withinAttackRange(Being *target)
{
if (Net::getNetworkType() == ServerInfo::MANASERV)
{
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->getTileX() - getTileY());
int dist_y = abs(target->getTileY() - getTileX());
if (dist_x > getAttackRange() || dist_y > getAttackRange())
{
return false;
}
return true;
}
}
void LocalPlayer::setGotoTarget(Being *target)
{
mLastTarget = -1;
if (Net::getNetworkType() == ServerInfo::MANASERV)
{
mTarget = target;
mGoingToTarget = true;
const Vector &targetPos = target->getPosition();
setDestination(targetPos.x, targetPos.y);
}
else
{
setTarget(target);
mGoingToTarget = true;
setDestination(target->getTileX(), target->getTileY());
}
}
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("target-cursor-blue-s.png", 44, 35, false, TC_SMALL);
loadTargetCursor("target-cursor-red-s.png", 44, 35, true, TC_SMALL);
loadTargetCursor("target-cursor-blue-m.png", 62, 44, false, TC_MEDIUM);
loadTargetCursor("target-cursor-red-m.png", 62, 44, true, TC_MEDIUM);
loadTargetCursor("target-cursor-blue-l.png", 82, 60, false, TC_LARGE);
loadTargetCursor("target-cursor-red-l.png", 82, 60, true, TC_LARGE);
}
void LocalPlayer::loadTargetCursor(const std::string &filename,
int width, int height,
bool outRange, TargetCursorSize size)
{
assert(size > -1);
assert(size < 3);
ImageSet *currentImageSet = Theme::getImageSetFromTheme(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)));
}
SimpleAnimation *currentCursor = new SimpleAnimation(anim);
const int index = outRange ? 1 : 0;
mTargetCursorImages[index][size] = currentImageSet;
mTargetCursor[index][size] = currentCursor;
}
void LocalPlayer::addMessageToQueue(const std::string &message, int color)
{
mMessages.push_back(MessagePair(message, color));
}
void LocalPlayer::optionChanged(const std::string &value)
{
if (value == "showownname")
{
setShowName(config.getValue("showownname", 1));
}
}