summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/actor.h2
-rw-r--r--src/actorsprite.cpp4
-rw-r--r--src/actorspritemanager.cpp4
-rw-r--r--src/actorspritemanager.h6
-rw-r--r--src/being.cpp368
-rw-r--r--src/being.h90
-rw-r--r--src/flooritem.cpp17
-rw-r--r--src/flooritem.h12
-rw-r--r--src/game.cpp61
-rw-r--r--src/gui/viewport.cpp134
-rw-r--r--src/localplayer.cpp359
-rw-r--r--src/localplayer.h11
-rw-r--r--src/map.cpp33
-rw-r--r--src/map.h29
-rw-r--r--src/net/manaserv/beinghandler.cpp38
-rw-r--r--src/net/manaserv/beinghandler.h8
-rw-r--r--src/net/manaserv/gamehandler.cpp3
-rw-r--r--src/net/manaserv/itemhandler.cpp18
-rw-r--r--src/net/manaserv/playerhandler.cpp31
-rw-r--r--src/net/manaserv/playerhandler.h7
-rw-r--r--src/net/playerhandler.h20
-rw-r--r--src/net/tmwa/beinghandler.cpp108
-rw-r--r--src/net/tmwa/charserverhandler.cpp6
-rw-r--r--src/net/tmwa/gamehandler.cpp15
-rw-r--r--src/net/tmwa/gamehandler.h7
-rw-r--r--src/net/tmwa/inventoryhandler.cpp23
-rw-r--r--src/net/tmwa/itemhandler.cpp14
-rw-r--r--src/net/tmwa/playerhandler.cpp60
-rw-r--r--src/net/tmwa/playerhandler.h7
-rw-r--r--src/resources/iteminfo.h4
-rw-r--r--src/sound.cpp7
-rw-r--r--src/sound.h4
32 files changed, 741 insertions, 769 deletions
diff --git a/src/actor.h b/src/actor.h
index 367bcd75..1442d4b3 100644
--- a/src/actor.h
+++ b/src/actor.h
@@ -112,7 +112,7 @@ public:
*/
virtual void setAlpha(float alpha) = 0;
- void setMap(Map *map);
+ virtual void setMap(Map *map);
Map* getMap() const
{ return mMap; }
diff --git a/src/actorsprite.cpp b/src/actorsprite.cpp
index 42086e00..988a25e5 100644
--- a/src/actorsprite.cpp
+++ b/src/actorsprite.cpp
@@ -71,9 +71,7 @@ bool ActorSprite::draw(Graphics *graphics, int offsetX, int offsetY) const
// these translations aren't necessary anymore. The sprites know
// best where their base point should be.
const int px = getPixelX() + offsetX - 16;
- // Temporary fix to the Y offset.
- const int py = getPixelY() + offsetY -
- ((Net::getNetworkType() == ServerInfo::MANASERV) ? 15 : 32);
+ const int py = getPixelY() + offsetY - 16;
if (mUsedTargetCursor)
{
diff --git a/src/actorspritemanager.cpp b/src/actorspritemanager.cpp
index a4b61ed3..2437f7f7 100644
--- a/src/actorspritemanager.cpp
+++ b/src/actorspritemanager.cpp
@@ -124,9 +124,9 @@ Being *ActorSpriteManager::createBeing(int id, ActorSprite::Type type, int subty
return being;
}
-FloorItem *ActorSpriteManager::createItem(int id, int itemId, int x, int y)
+FloorItem *ActorSpriteManager::createItem(int id, int itemId, const Vector &pos)
{
- FloorItem *floorItem = new FloorItem(id, itemId, x, y, mMap);
+ FloorItem *floorItem = new FloorItem(id, itemId, pos, mMap);
mActors.insert(floorItem);
return floorItem;
diff --git a/src/actorspritemanager.h b/src/actorspritemanager.h
index d6aa609b..64703396 100644
--- a/src/actorspritemanager.h
+++ b/src/actorspritemanager.h
@@ -59,8 +59,12 @@ class ActorSpriteManager
/**
* Create a FloorItem and add it to the list of ActorSprites.
+ *
+ * @param id the unique ID of this item instance
+ * @param itemId the item ID
+ * @param position the position in pixels
*/
- FloorItem *createItem(int id, int itemId, int x, int y);
+ FloorItem *createItem(int id, int itemId, const Vector &position);
/**
* Destroys the given ActorSprite at the end of
diff --git a/src/being.cpp b/src/being.cpp
index 788422fb..5911b03c 100644
--- a/src/being.cpp
+++ b/src/being.cpp
@@ -71,7 +71,6 @@ static const int DEFAULT_BEING_WIDTH = 32;
static const int DEFAULT_BEING_HEIGHT = 32;
int Being::mNumberOfHairstyles = 1;
-// TODO: mWalkTime used by eAthena only
Being::Being(int id, Type type, int subtype, Map *map):
ActorSprite(id),
mInfo(BeingInfo::Unknown),
@@ -91,7 +90,7 @@ Being::Being(int id, Type type, int subtype, Map *map):
mParty(NULL),
mIsGM(false),
mType(type),
- mX(0), mY(0),
+ mSpeedPixelsPerTick(Vector(0.0f, 0.0f, 0.0f)),
mDamageTaken(0),
mIp(0)
{
@@ -100,7 +99,7 @@ Being::Being(int id, Type type, int subtype, Map *map):
mSpeechBubble = new SpeechBubble;
- mWalkSpeed = Net::getPlayerHandler()->getDefaultWalkSpeed();
+ mMoveSpeed = Net::getPlayerHandler()->getDefaultMoveSpeed();
if (getType() == PLAYER)
mShowName = config.getBoolValue("visiblenames");
@@ -168,6 +167,15 @@ Map::BlockType Being::getBlockType() const
return mInfo->getBlockType();
}
+void Being::setMoveSpeed(const Vector &speed)
+{
+ mMoveSpeed = speed;
+ // If we already can, recalculate the system speed right away.
+ if (mMap)
+ mSpeedPixelsPerTick =
+ Net::getPlayerHandler()->getPixelsPerTickMoveSpeed(speed);
+}
+
void Being::setPosition(const Vector &pos)
{
Actor::setPosition(pos);
@@ -181,39 +189,46 @@ void Being::setPosition(const Vector &pos)
void Being::setDestination(int dstX, int dstY)
{
- if (Net::getNetworkType() == ServerInfo::TMWATHENA)
- {
- if (mMap)
- setPath(mMap->findPath(mX, mY, dstX, dstY, getWalkMask()));
- return;
- }
-
- // Manaserv's part:
-
// We can't calculate anything without a map anyway.
if (!mMap)
return;
// Don't handle flawed destinations from server...
- if (dstX == 0 || dstY == 0)
+ if (dstX <= 0 || dstY <= 0)
return;
// If the destination is unwalkable, don't bother trying to get there
- if (!mMap->getWalk(dstX / 32, dstY / 32))
+ int tileWidth = mMap->getTileWidth();
+ int tileHeight = mMap->getTileHeight();
+ if (!mMap->getWalk(dstX / tileWidth, dstY / tileHeight))
return;
- Position dest = mMap->checkNodeOffsets(getCollisionRadius(), getWalkMask(),
- dstX, dstY);
- Path thisPath = mMap->findPixelPath((int) mPos.x, (int) mPos.y,
- dest.x, dest.y,
- getCollisionRadius(), getWalkMask());
+ Position dest(0, 0);
+ Path thisPath;
+ if (Net::getPlayerHandler()->usePixelPrecision())
+ {
+ dest = mMap->checkNodeOffsets(getCollisionRadius(), getWalkMask(),
+ dstX, dstY);
+ thisPath = mMap->findPixelPath((int) mPos.x, (int) mPos.y,
+ dest.x, dest.y,
+ getCollisionRadius(), getWalkMask());
+ }
+ else
+ {
+ // We center the destination.
+ dest.x = (dstX / tileWidth) * tileWidth + tileWidth / 2;
+ dest.y = (dstY / tileHeight) * tileHeight + tileHeight / 2;
+ // and find a tile centered pixel path
+ thisPath = mMap->findTilePath((int) mPos.x, (int) mPos.y,
+ dest.x, dest.y, getWalkMask());
+ }
if (thisPath.empty())
{
// If there is no path but the destination is on the same walkable tile,
// we accept it.
- if ((int)mPos.x / 32 == dest.x / 32
- && (int)mPos.y / 32 == dest.y / 32)
+ if ((int)mPos.x / tileWidth == dest.x / tileWidth
+ && (int)mPos.y / tileHeight == dest.y / tileHeight)
{
mDest.x = dest.x;
mDest.y = dest.y;
@@ -237,13 +252,6 @@ void Being::clearPath()
void Being::setPath(const Path &path)
{
mPath = path;
-
- if ((Net::getNetworkType() == ServerInfo::TMWATHENA) &&
- mAction != MOVE && mAction != DEAD)
- {
- nextTile();
- mActionTime = tick_time;
- }
}
void Being::setSpeech(const std::string &text, int time)
@@ -393,19 +401,17 @@ void Being::handleAttack(Being *victim, int damage, AttackType type)
if (this != player_node)
setAction(Being::ATTACK, 1);
+ if (victim)
+ lookAt(victim->getPosition());
+
if (getType() == PLAYER && victim && mEquippedWeapon)
fireMissile(victim, mEquippedWeapon->getMissileParticle());
else
fireMissile(victim, mInfo->getAttack(mAttackType)->missileParticle);
- if (Net::getNetworkType() == ServerInfo::TMWATHENA)
- {
- reset();
- mActionTime = tick_time;
- }
-
sound.playSfx(mInfo->getSound((damage > 0) ?
- SOUND_EVENT_HIT : SOUND_EVENT_MISS), mX, mY);
+ SOUND_EVENT_HIT : SOUND_EVENT_MISS),
+ getPixelX(), getPixelY());
}
void Being::setName(const std::string &name)
@@ -597,28 +603,26 @@ void Being::setAction(Action action, int attackType)
currentAction = mInfo->getAttack(attackType)->action;
reset();
- if (Net::getNetworkType() == ServerInfo::MANASERV)
+ int rotation = 0;
+ //attack particle effect
+ std::string particleEffect = mInfo->getAttack(attackType)
+ ->particleEffect;
+ if (!particleEffect.empty() && Particle::enabled)
{
- int rotation = 0;
- //attack particle effect
- std::string particleEffect = mInfo->getAttack(attackType)
- ->particleEffect;
- if (!particleEffect.empty() && Particle::enabled)
+ switch (mSpriteDirection)
{
- switch (mSpriteDirection)
- {
- case DIRECTION_DOWN: rotation = 0; break;
- case DIRECTION_LEFT: rotation = 90; break;
- case DIRECTION_UP: rotation = 180; break;
- case DIRECTION_RIGHT: rotation = 270; break;
- default: break;
- }
- Particle *p;
- p = particleEngine->addEffect(particleEffect, 0, 0,
- rotation);
- controlParticle(p);
+ case DIRECTION_DOWN: rotation = 0; break;
+ case DIRECTION_LEFT: rotation = 90; break;
+ case DIRECTION_UP: rotation = 180; break;
+ case DIRECTION_RIGHT: rotation = 270; break;
+ default: break;
}
+ Particle *p;
+ p = particleEngine->addEffect(particleEffect, 0, 0,
+ rotation);
+ controlParticle(p);
}
+
}
break;
@@ -630,7 +634,8 @@ void Being::setAction(Action action, int attackType)
break;
case DEAD:
currentAction = SpriteAction::DEAD;
- sound.playSfx(mInfo->getSound(SOUND_EVENT_DIE), mX, mY);
+ sound.playSfx(mInfo->getSound(SOUND_EVENT_DIE),
+ getPixelX(), getPixelY());
break;
case STAND:
currentAction = SpriteAction::STAND;
@@ -647,13 +652,90 @@ void Being::setAction(Action action, int attackType)
mActionTime = tick_time;
}
-void Being::setDirection(Uint8 direction)
+void Being::lookAt(const Vector &destPos)
{
- if (Net::getNetworkType() == ServerInfo::MANASERV)
+ // We first handle simple cases
+
+ // If the two positions are the same,
+ // don't update the direction since it's only a matter of keeping
+ // the previous one.
+ if (mPos.x == destPos.x && mPos.y == destPos.y)
+ return;
+
+ if (mPos.x == destPos.x)
+ {
+ if (mPos.y > destPos.y)
+ setDirection(UP);
+ else
+ setDirection(DOWN);
+ return;
+ }
+
+ if (mPos.y == destPos.y)
+ {
+ if (mPos.x > destPos.x)
+ setDirection(LEFT);
+ else
+ setDirection(RIGHT);
+ return;
+ }
+
+ // Now let's handle diagonal cases
+ // First, find the lower angle:
+ if (mPos.x < destPos.x)
+ {
+ // Up-right direction
+ if (mPos.y > destPos.y)
+ {
+ // Compute tan of the angle
+ if ((mPos.y - destPos.y) / (destPos.x - mPos.x) < 1)
+ // The angle is less than 45°, we look to the right
+ setDirection(RIGHT);
+ else
+ setDirection(UP);
+ return;
+ }
+ else // Down-right
+ {
+ // Compute tan of the angle
+ if ((destPos.y - mPos.y) / (destPos.x - mPos.x) < 1)
+ // The angle is less than 45°, we look to the right
+ setDirection(RIGHT);
+ else
+ setDirection(DOWN);
+ return;
+ }
+ }
+ else
{
- if (mDirection == direction)
+ // Up-left direction
+ if (mPos.y > destPos.y)
+ {
+ // Compute tan of the angle
+ if ((mPos.y - destPos.y) / (mPos.x - destPos.x) < 1)
+ // The angle is less than 45°, we look to the left
+ setDirection(LEFT);
+ else
+ setDirection(UP);
+ return;
+ }
+ else // Down-left
+ {
+ // Compute tan of the angle
+ if ((destPos.y - mPos.y) / (mPos.x - destPos.x) < 1)
+ // The angle is less than 45°, we look to the left
+ setDirection(LEFT);
+ else
+ setDirection(DOWN);
return;
+ }
}
+}
+
+void Being::setDirection(Uint8 direction)
+{
+ if (!direction || mDirection == direction)
+ return;
mDirection = direction;
@@ -671,45 +753,9 @@ void Being::setDirection(Uint8 direction)
CompoundSprite::setDirection(dir);
}
-/** Note: Used by Tmw-Athena only */
-void Being::nextTile()
-{
- if (mPath.empty())
- {
- setAction(STAND);
- return;
- }
-
- Position pos = mPath.front();
- mPath.pop_front();
-
- int dir = 0;
- if (pos.x > mX)
- dir |= RIGHT;
- else if (pos.x < mX)
- dir |= LEFT;
- if (pos.y > mY)
- dir |= DOWN;
- else if (pos.y < mY)
- dir |= UP;
-
- setDirection(dir);
-
- if (!mMap->getWalk(pos.x, pos.y, getWalkMask()))
- {
- setAction(STAND);
- return;
- }
-
- mX = pos.x;
- mY = pos.y;
- setAction(MOVE);
- mActionTime += (int)(mWalkSpeed.x / 10);
-}
-
int Being::getCollisionRadius() const
{
- // FIXME: Get this from XML file
+ // FIXME: Get this from XML file once a better pathfinding algorithm is up.
return 16;
}
@@ -726,15 +772,14 @@ void Being::logic()
mText = 0;
}
- if ((Net::getNetworkType() == ServerInfo::MANASERV) && (mAction != DEAD)
- && !mWalkSpeed.isNull())
+ if ((mAction != DEAD) && !mSpeedPixelsPerTick.isNull())
{
const Vector dest = (mPath.empty()) ?
mDest : Vector(mPath.front().x,
mPath.front().y);
// Avoid going to flawed destinations
- if (mDest.x <= 0 && mDest.y <= 0)
+ if (dest.x <= 0 || dest.y <= 0)
{
// We make the being stop move in that case.
mDest = mPos;
@@ -758,8 +803,8 @@ void Being::logic()
// â = a / ||a|| (||a|| is the a length.)
// Then, diff = (dir/||dir||) * speed.
const Vector normalizedDir = dir.normalized();
- Vector diff(normalizedDir.x * mWalkSpeed.x,
- normalizedDir.y * mWalkSpeed.y);
+ Vector diff(normalizedDir.x * mSpeedPixelsPerTick.x,
+ normalizedDir.y * mSpeedPixelsPerTick.y);
// Test if we don't miss the destination by a move too far:
if (diff.length() > distance)
@@ -784,8 +829,10 @@ void Being::logic()
// Update the player sprite direction.
// N.B.: We only change this if the distance is more than one pixel
- // to avoid flawing the ending direction.
- if (distance > 1.0f)
+ // to avoid flawing the ending direction for players,
+ // but always for very slow beings.
+ float maxDistance = mSpeedPixelsPerTick.length();
+ if (distance > ((maxDistance > 1.0f) ? 1.0f : 0.0f))
{
// The player direction is handled for keyboard
// by LocalPlayer::startWalking(), we shouldn't get
@@ -821,85 +868,12 @@ void Being::logic()
setAction(STAND);
}
}
- else if (Net::getNetworkType() == ServerInfo::TMWATHENA)
- {
- int frameCount = getFrameCount();
-
- switch (mAction)
- {
- case STAND:
- case SIT:
- case DEAD:
- case HURT:
- break;
-
- case MOVE:
- if ((int) ((get_elapsed_time(mActionTime) * frameCount)
- / getWalkSpeed().x) >= frameCount)
- nextTile();
- break;
-
- case ATTACK:
- int rotation = 0;
- std::string particleEffect = "";
-
- int curFrame = (get_elapsed_time(mActionTime) * frameCount)
- / mAttackSpeed;
-
- //attack particle effect
- if (mEquippedWeapon)
- {
- particleEffect = mEquippedWeapon->getParticleEffect();
-
- if (!particleEffect.empty() &&
- findSameSubstring(particleEffect,
- paths.getStringValue("particles")).empty())
- particleEffect = paths.getStringValue("particles")
- + particleEffect;
- }
- else
- {
- particleEffect = mInfo->getAttack(mAttackType)
- ->particleEffect;
- }
-
- if (!particleEffect.empty() && Particle::enabled
- && curFrame == 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(particleEffect, 0, 0,
- rotation);
- controlParticle(p);
- }
-
- if (curFrame >= frameCount)
- nextTile();
-
- break;
- }
-
- // Update pixel coordinates
- setPosition(mX * 32 + 16 + getXOffset(),
- mY * 32 + 32 + getYOffset());
- }
ActorSprite::logic();
- int frameCount = getFrameCount();
- if (frameCount < 10)
- frameCount = 10;
-
+ // Remove it after 3 secs. TODO: Just play the dead animation before removing
if (!isAlive() && Net::getGameHandler()->removeDeadBeings() &&
- (int) ((get_elapsed_time(mActionTime)
- / getWalkSpeed().x) >= frameCount))
+ get_elapsed_time(mActionTime) > 3000)
{
if (getType() != PLAYER)
actorSpriteManager->destroy(this);
@@ -960,36 +934,6 @@ void Being::drawSpeech(int offsetX, int offsetY)
}
}
-/** Note: Used by Tmw-Athena only */
-int Being::getOffset(char pos, char neg) const
-{
- // Check whether we're walking in the requested direction
- if (mAction != MOVE || !(mDirection & (pos | neg)))
- return 0;
-
- int offset = 0;
-
- if (mMap)
- {
- offset = (pos == LEFT && neg == RIGHT) ?
- (int)((get_elapsed_time(mActionTime)
- * mMap->getTileWidth()) / mWalkSpeed.x) :
- (int)((get_elapsed_time(mActionTime)
- * mMap->getTileHeight()) / mWalkSpeed.y);
- }
-
- // We calculate the offset _from_ the _target_ location
- offset -= 32;
- if (offset > 0)
- offset = 0;
-
- // Going into negative direction? Invert the offset.
- if (mDirection & pos)
- offset = -offset;
-
- return offset;
-}
-
int Being::getWidth() const
{
return std::max(CompoundSprite::getWidth(), DEFAULT_BEING_WIDTH);
@@ -1253,3 +1197,15 @@ void Being::event(Event::Channel channel, const Event &event)
}
}
+
+void Being::setMap(Map *map)
+{
+ Actor::setMap(map);
+
+ // Recalculate pixel/tick speed
+ if (map && !mMoveSpeed.isNull())
+ {
+ mSpeedPixelsPerTick =
+ Net::getPlayerHandler()->getPixelsPerTickMoveSpeed(mMoveSpeed, map);
+ }
+}
diff --git a/src/being.h b/src/being.h
index 7be216ca..a4e2e443 100644
--- a/src/being.h
+++ b/src/being.h
@@ -118,7 +118,8 @@ class Being : public ActorSprite, public EventListener
virtual ~Being();
- Type getType() const { return mType; }
+ Type getType() const
+ { return mType; }
/**
* Removes all path nodes from this being.
@@ -126,37 +127,6 @@ class Being : public ActorSprite, public EventListener
void clearPath();
/**
- * Returns the time spent in the current action.
- */
- int getActionTime() const { return mActionTime; }
-
- /**
- * Set the current action time.
- * @see Ea::BeingHandler that set it to tick time.
- */
- void setActionTime(int actionTime) { mActionTime = actionTime; }
-
- /**
- * Makes this being take the next tile of its path.
- * TODO: Used by eAthena only?
- */
- virtual void nextTile();
-
- /**
- * Get the current X pixel offset.
- * TODO: Used by eAthena only?
- */
- int getXOffset() const
- { return getOffset(LEFT, RIGHT); }
-
- /**
- * Get the current Y pixel offset.
- * TODO: Used by eAthena only?
- */
- int getYOffset() const
- { return getOffset(UP, DOWN); }
-
- /**
* Creates a path for the being from current position to ex and ey
*/
void setDestination(int ex, int ey);
@@ -164,25 +134,20 @@ class Being : public ActorSprite, public EventListener
/**
* Returns the destination for this being.
*/
- const Vector &getDestination() const { return mDest; }
+ const Vector &getDestination() const
+ { return mDest; }
/**
* Returns the tile x coord
*/
int getTileX() const
- { return mX; }
+ { return mPos.x / mMap->getTileWidth(); }
/**
* Returns the tile y coord
*/
int getTileY() const
- { return mY; }
-
- /**
- * Sets the tile x and y coord
- */
- void setTileCoords(int x, int y)
- { mX = x; mY = y; }
+ { return mPos.y / mMap->getTileHeight(); }
/**
* Puts a "speech balloon" above this being for the specified amount
@@ -343,18 +308,18 @@ class Being : public ActorSprite, public EventListener
Map::BlockType getBlockType() const;
/**
- * Sets the walk speed.
- * in pixels per second for eAthena,
- * in tiles per second for Manaserv.
+ * Sets the move speed.
+ * in ticks per tile for eAthena,
+ * in tiles per second for Manaserv (0.1 precision).
*/
- void setWalkSpeed(Vector speed) { mWalkSpeed = speed; }
+ void setMoveSpeed(const Vector &speed);
/**
- * Gets the walk speed.
- * in pixels per second for eAthena,
+ * Gets the original Move speed.
+ * in ticks per tile for eAthena,
* in tiles per second for Manaserv (0.1 precision).
*/
- Vector getWalkSpeed() const { return mWalkSpeed; }
+ Vector getMoveSpeed() const { return mMoveSpeed; }
/**
* Sets the attack speed.
@@ -483,6 +448,13 @@ class Being : public ActorSprite, public EventListener
void event(Event::Channel channel, const Event &event);
+ void setMap(Map *map);
+
+ /**
+ * Make the being look at a given pixel position.
+ */
+ void lookAt(const Vector &destPos);
+
protected:
/**
* Sets the new path for this being.
@@ -500,7 +472,7 @@ class Being : public ActorSprite, public EventListener
BeingInfo *mInfo;
- int mActionTime; /**< Time spent in current action */
+ int mActionTime; /**< Time spent in current action. TODO: Remove use of it */
/** Time until the last speech sentence disappears */
int mSpeechTime;
@@ -547,13 +519,6 @@ class Being : public ActorSprite, public EventListener
private:
- /**
- * Calculates the offset in the given directions.
- * If walking in direction 'neg' the value is negated.
- * TODO: Used by eAthena only?
- */
- int getOffset(char pos, char neg) const;
-
const Type mType;
/** Speech Bubble components */
@@ -561,13 +526,16 @@ class Being : public ActorSprite, public EventListener
/**
* Walk speed for x and y movement values.
- * In pixels per second for eAthena,
- * In pixels per ticks for Manaserv.
- * @see MILLISECONDS_IN_A_TICK
+ * In ticks per tile for eAthena,
+ * In pixels per second for Manaserv.
*/
- Vector mWalkSpeed;
+ Vector mMoveSpeed;
- int mX, mY; /**< Position in tile */
+ /**
+ * Being speed in pixel per ticks. Used internally for the being logic.
+ * @see MILLISECONDS_IN_A_TICK
+ */
+ Vector mSpeedPixelsPerTick;
int mDamageTaken;
diff --git a/src/flooritem.cpp b/src/flooritem.cpp
index db62ce9a..c92619ff 100644
--- a/src/flooritem.cpp
+++ b/src/flooritem.cpp
@@ -22,28 +22,25 @@
#include "flooritem.h"
#include "net/net.h"
+#include "net/playerhandler.h"
#include "resources/itemdb.h"
#include "resources/iteminfo.h"
FloorItem::FloorItem(int id,
int itemId,
- int x,
- int y,
+ const Vector &position,
Map *map):
ActorSprite(id),
mItemId(itemId),
- mX(x),
- mY(y)
+ mX(0), mY(0)
{
+ mPos = position;
+
setMap(map);
- // TODO: Eventually, we probably should fix all sprite offsets so that
- // these translations aren't necessary anymore. The sprites know
- // best where their base point should be.
- mPos.x = x * map->getTileWidth() + 16;
- mPos.y = y * map->getTileHeight() +
- ((Net::getNetworkType() == ServerInfo::MANASERV) ? 15 : 32);
+ mX = (int)position.x / map->getTileWidth();
+ mY = (int)position.y / map->getTileHeight();
setupSpriteDisplay(itemDb->get(itemId).getDisplay());
}
diff --git a/src/flooritem.h b/src/flooritem.h
index ad052461..37fed4b2 100644
--- a/src/flooritem.h
+++ b/src/flooritem.h
@@ -35,16 +35,14 @@ class FloorItem : public ActorSprite
/**
* Constructor.
*
- * @param id the unique ID of this item instance
- * @param itemId the item ID
- * @param x the x position in tiles
- * @param y the y position in tiles
- * @param map the map this item is on
+ * @param id the unique ID of this item instance
+ * @param itemId the item ID
+ * @param position the position in pixels
+ * @param map the map this item is on
*/
FloorItem(int id,
int itemId,
- int x,
- int y,
+ const Vector &position,
Map *map);
Type getType() const { return FLOOR_ITEM; }
diff --git a/src/game.cpp b/src/game.cpp
index 8c8fb205..bcfd9ac2 100644
--- a/src/game.cpp
+++ b/src/game.cpp
@@ -390,6 +390,40 @@ void Game::logic()
}
/**
+ * handle item pick up case.
+ */
+static void handleItemPickUp()
+{
+ int x = player_node->getTileX();
+ int y = player_node->getTileY();
+
+ // Let's look for items around until you find one.
+ bool found = false;
+ for (int xX = x - 1; xX < x + 2; ++xX)
+ {
+ for (int yY = y - 1; yY < y + 2; ++yY)
+ {
+ FloorItem *item = actorSpriteManager->findItem(xX, yY);
+ if (item)
+ {
+ found = true;
+ player_node->pickUp(item);
+
+ // We found it, so set the player
+ // direction accordingly,
+ player_node->lookAt(
+ player_node->getMap()->getTileCenter(xX, yY));
+
+ // Get out of the loops
+ break;
+ }
+ }
+ if (found)
+ break;
+ }
+}
+
+/**
* The huge input handling method.
*/
void Game::handleInput()
@@ -621,32 +655,7 @@ void Game::handleInput()
{
case KeyboardConfig::KEY_PICKUP:
{
- int x = player_node->getTileX();
- int y = player_node->getTileY();
-
- FloorItem *item =
- actorSpriteManager->findItem(x, y);
-
- // If none below the player, try the tile in front
- // of the player
- if (!item)
- {
- // Temporary until tile-based picking is
- // removed.
- switch (player_node->getSpriteDirection())
- {
- case DIRECTION_UP : --y; break;
- case DIRECTION_DOWN : ++y; break;
- case DIRECTION_LEFT : --x; break;
- case DIRECTION_RIGHT: ++x; break;
- default: break;
- }
-
- item = actorSpriteManager->findItem(x, y);
- }
-
- if (item)
- player_node->pickUp(item);
+ handleItemPickUp();
used = true;
}
diff --git a/src/gui/viewport.cpp b/src/gui/viewport.cpp
index e5211baa..f6166c8f 100644
--- a/src/gui/viewport.cpp
+++ b/src/gui/viewport.cpp
@@ -37,6 +37,7 @@
#include "gui/beingpopup.h"
#include "net/net.h"
+#include "net/playerhandler.h"
#include "resources/resourcemanager.h"
@@ -297,43 +298,53 @@ void Viewport::_drawDebugPath(Graphics *graphics)
Path debugPath;
- if (Net::getNetworkType() == ServerInfo::TMWATHENA)
+ const Vector &playerPos = player_node->getPosition();
+ const int playerRadius = player_node->getCollisionRadius();
+ // Draw player collision rectangle
+ graphics->setColor(gcn::Color(128, 128, 0, 120));
+ graphics->fillRectangle(
+ gcn::Rectangle((int) playerPos.x - (int) mPixelViewX - playerRadius,
+ (int) playerPos.y - (int) mPixelViewY - playerRadius,
+ playerRadius * 2, playerRadius * 2));
+
+ // Prepare the walkmask corresponding to the protocol
+ unsigned char walkMask = 0;
+ switch (Net::getNetworkType())
{
- const int mouseTileX = (mMouseX + (int) mPixelViewX) / 32;
- const int mouseTileY = (mMouseY + (int) mPixelViewY) / 32;
- const Vector &playerPos = player_node->getPosition();
-
- debugPath = mMap->findPath(
- (int) (playerPos.x - 16) / 32,
- (int) (playerPos.y - 32) / 32,
- mouseTileX, mouseTileY, 0xFF);
-
- _drawPath(graphics, debugPath);
+ case ServerInfo::TMWATHENA:
+ walkMask = Map::BLOCKMASK_WALL | Map::BLOCKMASK_CHARACTER;
+ break;
+ case ServerInfo::MANASERV:
+ default:
+ walkMask = Map::BLOCKMASK_WALL;
+ break;
}
- else if (Net::getNetworkType() == ServerInfo::MANASERV)
- {
- const Vector &playerPos = player_node->getPosition();
- const int playerRadius = player_node->getCollisionRadius();
- // Draw player collision rectangle
- graphics->setColor(gcn::Color(128, 128, 0, 120));
- graphics->fillRectangle(
- gcn::Rectangle((int) playerPos.x - (int) mPixelViewX - playerRadius,
- (int) playerPos.y - (int) mPixelViewY - playerRadius,
- playerRadius * 2, playerRadius * 2));
+ // Adapt the path finding to the precision requested
+ if (Net::getPlayerHandler()->usePixelPrecision())
+ {
debugPath = mMap->findPixelPath(
- (int) playerPos.x,
- (int) playerPos.y,
- mMouseX + (int) mPixelViewX,
- mMouseY + (int) mPixelViewY,
- playerRadius, 0xFF);
+ (int) playerPos.x,
+ (int) playerPos.y,
+ mMouseX + (int) mPixelViewX,
+ mMouseY + (int) mPixelViewY,
+ playerRadius, walkMask);
+ }
+ else
+ {
+ debugPath = mMap->findTilePath(
+ (int) playerPos.x,
+ (int) playerPos.y,
+ mMouseX + (int) mPixelViewX,
+ mMouseY + (int) mPixelViewY,
+ walkMask);
+ }
- // We draw the path proposed by mouse
- _drawPath(graphics, debugPath, gcn::Color(128, 0, 128));
+ // We draw the path proposed by mouse
+ _drawPath(graphics, debugPath, gcn::Color(128, 0, 128));
- // But also the one currently walked on.
- _drawPath(graphics, player_node->getPath(), gcn::Color(0, 0, 255));
- }
+ // But also the one currently walked on.
+ _drawPath(graphics, player_node->getPath(), gcn::Color(0, 0, 255));
}
void Viewport::_drawPath(Graphics *graphics, const Path &path,
@@ -341,33 +352,16 @@ void Viewport::_drawPath(Graphics *graphics, const Path &path,
{
graphics->setColor(color);
- if (Net::getNetworkType() == ServerInfo::TMWATHENA)
- {
- for (Path::const_iterator i = path.begin(); i != path.end(); ++i)
- {
- int squareX = i->x * 32 - (int) mPixelViewX + 12;
- int squareY = i->y * 32 - (int) mPixelViewY + 12;
-
- graphics->fillRectangle(gcn::Rectangle(squareX, squareY, 8, 8));
- graphics->drawText(
- toString(mMap->getMetaTile(i->x, i->y)->Gcost),
- squareX + 4, squareY + 12, gcn::Graphics::CENTER);
- }
- }
- else if (Net::getNetworkType() == ServerInfo::MANASERV)
+ for (Path::const_iterator i = path.begin(); i != path.end(); ++i)
{
- for (Path::const_iterator i = path.begin(); i != path.end(); ++i)
- {
- int squareX = i->x - (int) mPixelViewX;
- int squareY = i->y - (int) mPixelViewY;
-
- graphics->fillRectangle(gcn::Rectangle(squareX - 4, squareY - 4,
- 8, 8));
- graphics->drawText(
- toString(mMap->getMetaTile(i->x / 32, i->y / 32)->Gcost),
- squareX + 4, squareY + 12, gcn::Graphics::CENTER);
- }
-
+ int squareX = i->x - (int) mPixelViewX;
+ int squareY = i->y - (int) mPixelViewY;
+
+ graphics->fillRectangle(gcn::Rectangle(squareX - 4, squareY - 4,
+ 8, 8));
+ graphics->drawText(
+ toString(mMap->getMetaTile(i->x / 32, i->y / 32)->Gcost),
+ squareX + 4, squareY + 12, gcn::Graphics::CENTER);
}
}
@@ -477,25 +471,12 @@ void Viewport::mouseDragged(gcn::MouseEvent &event)
if (mPlayerFollowMouse && !event.isShiftPressed())
{
- if (Net::getNetworkType() == ServerInfo::MANASERV)
+ if (get_elapsed_time(mLocalWalkTime) >= walkingMouseDelay)
{
- if (get_elapsed_time(mLocalWalkTime) >= walkingMouseDelay)
- {
- mLocalWalkTime = tick_time;
- player_node->setDestination(event.getX() + (int) mPixelViewX,
- event.getY() + (int) mPixelViewY);
- player_node->pathSetByMouse();
- }
- }
- else
- {
- if (mLocalWalkTime != player_node->getActionTime())
- {
- mLocalWalkTime = player_node->getActionTime();
- int destX = (event.getX() + mPixelViewX) / mMap->getTileWidth();
- int destY = (event.getY() + mPixelViewY) / mMap->getTileHeight();
- player_node->setDestination(destX, destY);
- }
+ mLocalWalkTime = tick_time;
+ player_node->setDestination(event.getX() + (int) mPixelViewX,
+ event.getY() + (int) mPixelViewY);
+ player_node->pathSetByMouse();
}
}
}
@@ -503,9 +484,6 @@ void Viewport::mouseDragged(gcn::MouseEvent &event)
void Viewport::mouseReleased(gcn::MouseEvent &event)
{
mPlayerFollowMouse = false;
-
- // Only useful for eAthena but doesn't hurt under ManaServ
- mLocalWalkTime = -1;
}
void Viewport::showPopup(Window *parent, int x, int y, Item *item,
diff --git a/src/localplayer.cpp b/src/localplayer.cpp
index d710a51f..99e42411 100644
--- a/src/localplayer.cpp
+++ b/src/localplayer.cpp
@@ -69,7 +69,7 @@ LocalPlayer *player_node = NULL;
LocalPlayer::LocalPlayer(int id, int subtype):
Being(id, PLAYER, subtype, 0),
- mAttackRange(0),
+ mAttackRange(ATTACK_RANGE_NOT_SET),
mTargetTime(-1),
mLastTarget(-1),
mTarget(NULL),
@@ -114,14 +114,10 @@ void LocalPlayer::logic()
{
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),
@@ -157,15 +153,8 @@ void LocalPlayer::logic()
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 rangeX = abs(mTarget->getPosition().x - getPosition().x);
+ const int rangeY = abs(mTarget->getPosition().y - getPosition().y);
const int attackRange = getAttackRange();
const TargetCursorType targetType = rangeX > attackRange ||
@@ -549,56 +538,22 @@ Position LocalPlayer::getNextWalkPosition(unsigned char dir)
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();
- }
+ if (!mMap || !dir)
+ return;
+ const Vector &pos = getPosition();
+ Position destination = getNextWalkPosition(dir);
- Being::nextTile();
+ if ((int)pos.x != destination.x
+ || (int)pos.y != destination.y)
+ {
+ setDestination(destination.x, destination.y);
}
- else
+ else if (dir != mDirection)
{
- 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);
- }
+ // If the being can't move, just change direction
+ Net::getPlayerHandler()->setDirection(dir);
+ setDirection(dir);
}
}
@@ -637,9 +592,10 @@ void LocalPlayer::pickUp(FloorItem *item)
if (!item)
return;
- int dx = item->getTileX() - (int) getPosition().x / mMap->getTileWidth();
- int dy = item->getTileY() - ((int) getPosition().y - 1)
- / mMap->getTileHeight();
+ int tileWidth = mMap->getTileWidth();
+ int tileHeight = mMap->getTileHeight();
+ int dx = item->getTileX() - (int) getPosition().x / tileWidth;
+ int dy = item->getTileY() - ((int) getPosition().y - 1) / tileHeight;
if (dx * dx + dy * dy < 4)
{
@@ -648,17 +604,9 @@ void LocalPlayer::pickUp(FloorItem *item)
}
else
{
- if (Net::getNetworkType() == ServerInfo::MANASERV)
- {
- setDestination(item->getPixelX() + 16, item->getPixelY() + 16);
- mPickUpTarget = item;
- }
- else
- {
- setDestination(item->getTileX(), item->getTileY());
- mPickUpTarget = item;
- stopAttack();
- }
+ setDestination(item->getTileX() * tileWidth + tileWidth / 2,
+ item->getTileY() * tileHeight + tileHeight / 2);
+ mPickUpTarget = item;
}
}
@@ -711,17 +659,40 @@ void LocalPlayer::setTarget(Being *target)
void LocalPlayer::setDestination(int x, int y)
{
+ int srcX = x;
+ int srcY = y;
+ int dstX = (int)mDest.x;
+ int dstY = (int)mDest.y;
+ int tileWidth = mMap->getTileWidth();
+ int tileHeight = mMap->getTileHeight();
+ if (!Net::getPlayerHandler()->usePixelPrecision())
+ {
+ // For tile-based clients, we accept positions on the same tile.
+ srcX = srcX / tileWidth;
+ srcY = srcY / tileHeight;
+ dstX = dstX / tileWidth;
+ dstY = dstY / tileHeight;
+ }
+
// Only send a new message to the server when destination changes
- if (x != mDest.x || y != mDest.y)
+ if (srcX != dstX || srcY != dstY)
{
Being::setDestination(x, y);
+ // Note: Being::setDestination() updates mDest, so we get the new
+ // destination.
+ dstX = (int)mDest.x;
+ dstY = (int)mDest.y;
+
+ if (!Net::getPlayerHandler()->usePixelPrecision())
+ {
+ dstX = dstX / tileWidth;
+ dstY = dstY / tileHeight;
+ }
- // 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);
+ if (srcX == dstX && srcY == dstY)
+ Net::getPlayerHandler()->setDestination(x, y, mDirection);
}
mPickUpTarget = NULL;
@@ -732,29 +703,26 @@ 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 let the path die (1/2 tile after that.)
- // This permit to avoid desyncs with other clients.
- else if (!dir)
- return;
+ // First if player is pressing key for the direction he is already
+ // going, do nothing more...
- // 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;
- }
+ // 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 let the path die (1/2 tile after that.)
+ // This permit to avoid desyncs with other clients.
+ else if (!dir)
+ 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;
@@ -763,7 +731,7 @@ void LocalPlayer::setWalkingDir(int dir)
{
startWalking(dir);
}
- else if (mAction == MOVE && (Net::getNetworkType() == ServerInfo::MANASERV))
+ else if (mAction == MOVE)
{
nextTile(dir);
}
@@ -779,13 +747,8 @@ void LocalPlayer::startWalking(unsigned char dir)
if (mAction == MOVE && !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());
+ const Vector &pos = getPosition();
+ Being::setDestination(pos.x, pos.y);
return;
}
@@ -800,37 +763,9 @@ void LocalPlayer::startWalking(unsigned char dir)
dx++;
// Update the direction when the walk just start
- if (Net::getNetworkType() == ServerInfo::MANASERV)
- setDirection(dir);
+ setDirection(dir);
- 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);
+ nextTile(dir);
}
void LocalPlayer::stopWalking(bool sendToServer)
@@ -881,15 +816,12 @@ void LocalPlayer::emote(Uint8 emotion)
void LocalPlayer::attack(Being *target, bool keep)
{
- if (Net::getNetworkType() == ServerInfo::MANASERV)
- {
- if (mLastAction != -1)
- return;
+ if (mLastAction != -1)
+ return;
- // Can only attack when standing still
- if (mAction != STAND && mAction != ATTACK)
- return;
- }
+ // Can only attack when standing still
+ if (mAction != STAND && mAction != ATTACK)
+ return;
mKeepAttacking = keep;
@@ -902,61 +834,28 @@ void LocalPlayer::attack(Being *target, bool keep)
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;
+ 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);
- }
+ if (abs(dist_y) >= abs(dist_x))
+ {
+ if (dist_y < 0)
+ setDirection(DOWN);
else
- {
- if (dist_x < 0)
- setDirection(RIGHT);
- else
- setDirection(LEFT);
- }
-
- mLastAction = tick_time;
+ setDirection(UP);
}
else
{
- int dist_x = target->getTileX() - getTileX();
- int dist_y = target->getTileY() - getTileY();
-
- // Must be standing to attack
- if (mAction != STAND)
- return;
-
- Uint8 direction = 0;
- if (abs(dist_y) >= abs(dist_x))
- {
- if (dist_y > 0)
- direction = DOWN;
- else
- direction = UP;
- }
+ if (dist_x < 0)
+ setDirection(RIGHT);
else
- {
- if (dist_x > 0)
- direction = RIGHT;
- else
- direction = LEFT;
- }
- Net::getPlayerHandler()->setDirection(direction);
- setDirection(direction);
-
- mActionTime = tick_time;
- mTargetTime = tick_time;
+ setDirection(LEFT);
}
+ mLastAction = tick_time;
+
setAction(ATTACK);
if (mEquippedWeapon)
@@ -966,11 +865,11 @@ void LocalPlayer::attack(Being *target, bool keep)
sound.playSfx(soundFile);
}
else
+ {
sound.playSfx(paths.getValue("attackSfxFile", "fist-swish.ogg"));
+ }
Net::getPlayerHandler()->attack(target->getId());
- if ((Net::getNetworkType() == ServerInfo::TMWATHENA) && !keep)
- stopAttack();
}
void LocalPlayer::stopAttack()
@@ -1035,69 +934,45 @@ void LocalPlayer::pickedUp(const ItemInfo &itemInfo, int amount,
}
}
-int LocalPlayer::getAttackRange()
+void LocalPlayer::setAttackRange(int range)
{
- if (mAttackRange > -1)
+ // When the range is more than the minimal, we accept it
+ if (range > ATTACK_RANGE_NOT_SET)
{
- return mAttackRange;
+ mAttackRange = range;
}
- else
+ else if (Net::getNetworkType() == ServerInfo::TMWATHENA)
{
- if (Net::getNetworkType() == ServerInfo::TMWATHENA)
+ // TODO: Fix this to be more generic
+ Item *weapon = PlayerInfo::getEquipment(TmwAthena::EQUIP_FIGHT1_SLOT);
+ if (weapon)
{
- // TODO: Fix this to be more generic
- Item *weapon = PlayerInfo::getEquipment(
- TmwAthena::EQUIP_FIGHT1_SLOT);
- if (weapon)
- {
- const ItemInfo info = weapon->getInfo();
- return info.getAttackRange();
- }
+ const ItemInfo info = weapon->getInfo();
+ if (info.getAttackRange() > ATTACK_RANGE_NOT_SET)
+ mAttackRange = 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() - getTileX());
- int dist_y = abs(target->getTileY() - getTileY());
-
- if (dist_x > getAttackRange() || dist_y > getAttackRange())
- return false;
+ 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 true;
- }
+ return !(dx > range || dy > range);
}
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());
- }
+
+ mTarget = target;
+ mGoingToTarget = true;
+ const Vector &targetPos = target->getPosition();
+ setDestination(targetPos.x, targetPos.y);
}
void LocalPlayer::addMessageToQueue(const std::string &message, int color)
diff --git a/src/localplayer.h b/src/localplayer.h
index e7fd78f6..4f7e32a6 100644
--- a/src/localplayer.h
+++ b/src/localplayer.h
@@ -58,6 +58,11 @@ enum
PICKUP_DROP_STEAL
};
+/**
+ * Attack range not set value
+ */
+enum { ATTACK_RANGE_NOT_SET = -1 };
+
/**
* The local player character.
@@ -105,12 +110,14 @@ class LocalPlayer : public Being
/**
* Sets the attack range.
*/
- void setAttackRange(int range) { mAttackRange = range; }
+ void setAttackRange(int range);
/**
* Gets the attack range.
*/
- int getAttackRange();
+ int getAttackRange()
+ { return mAttackRange; }
+
void attack(Being *target = NULL, bool keep = false);
void setGMLevel(int level);
diff --git a/src/map.cpp b/src/map.cpp
index 151cbf53..da962253 100644
--- a/src/map.cpp
+++ b/src/map.cpp
@@ -604,6 +604,15 @@ bool Map::occupied(int x, int y) const
return false;
}
+Vector Map::getTileCenter(int x, int y)
+{
+ Vector tileCenterPos;
+
+ tileCenterPos.x = x * mTileWidth + mTileWidth / 2;
+ tileCenterPos.y = y * mTileHeight + mTileHeight / 2;
+ return tileCenterPos;
+}
+
bool Map::contains(int x, int y) const
{
return x >= 0 && y >= 0 && x < mWidth && y < mHeight;
@@ -707,6 +716,30 @@ Position Map::checkNodeOffsets(int radius, unsigned char walkMask,
return Position(tx * 32 + fx, ty * 32 + fy);
}
+Path Map::findTilePath(int startPixelX, int startPixelY, int endPixelX,
+ int endPixelY, unsigned char walkMask, int maxCost)
+{
+ Path myPath = findPath(startPixelX / mTileWidth, startPixelY / mTileHeight,
+ endPixelX / mTileWidth, endPixelY / mTileHeight,
+ walkMask, maxCost);
+
+ // Don't compute empty coordinates.
+ if (myPath.empty())
+ return myPath;
+
+ // Convert the map path to pixels from the tile position
+ Path::iterator it = myPath.begin();
+ while (it != myPath.end())
+ {
+ // The new pixel position will be the tile center.
+ *it = Position(it->x * mTileWidth + mTileWidth / 2,
+ it->y * mTileHeight + mTileHeight / 2);
+ ++it;
+ }
+
+ return myPath;
+}
+
Path Map::findPixelPath(int startPixelX, int startPixelY, int endPixelX,
int endPixelY,
int radius, unsigned char walkMask, int maxCost)
diff --git a/src/map.h b/src/map.h
index cfc0b7ef..67135a99 100644
--- a/src/map.h
+++ b/src/map.h
@@ -258,6 +258,14 @@ class Map : public Properties
int getTileHeight() const
{ return mTileHeight; }
+ /**
+ * Returns the tile center position in pixel coordinates.
+ *
+ * @param x the horizontal tile position
+ * @param y the vertical tile position
+ */
+ Vector getTileCenter(int x, int y);
+
const std::string getMusicFile() const;
const std::string getName() const;
@@ -277,17 +285,19 @@ class Map : public Properties
{ return checkNodeOffsets(radius, walkMask, Position(x, y)); }
/**
- * Find a pixel path from one location to the next.
+ * Find a tile-centered path in pixel coordinates
+ * from one location to the next.
*/
- Path findPixelPath(int startPixelX, int startPixelY,
- int destPixelX, int destPixelY,
- int radius, unsigned char walkmask, int maxCost = 20);
+ Path findTilePath(int startPixelX, int startPixelY, int endPixelX,
+ int endPixelY, unsigned char walkMask,
+ int maxCost = 20);
/**
- * Find a path from one location to the next.
+ * Find a pixel path from one location to the next using free offsets.
*/
- Path findPath(int startX, int startY, int destX, int destY,
- unsigned char walkmask, int maxCost = 20);
+ Path findPixelPath(int startPixelX, int startPixelY,
+ int destPixelX, int destPixelY,
+ int radius, unsigned char walkmask, int maxCost = 20);
/**
* Adds a particle effect
@@ -328,6 +338,11 @@ class Map : public Properties
void removeActor(Actors::iterator iterator);
private:
+ /**
+ * Find a path from one location to the next in tile coordinates.
+ */
+ Path findPath(int startX, int startY, int destX, int destY,
+ unsigned char walkmask, int maxCost = 20);
enum LayerType
{
diff --git a/src/net/manaserv/beinghandler.cpp b/src/net/manaserv/beinghandler.cpp
index 414f914d..f8cefecf 100644
--- a/src/net/manaserv/beinghandler.cpp
+++ b/src/net/manaserv/beinghandler.cpp
@@ -91,37 +91,6 @@ void BeingHandler::handleMessage(Net::MessageIn &msg)
}
}
-Vector BeingHandler::giveSpeedInPixelsPerTicks(float speedInTilesPerSeconds)
-{
- Vector speedInTicks;
- Game *game = Game::instance();
- Map *map = 0;
- if (game)
- {
- map = game->getCurrentMap();
- if (map)
- {
- speedInTicks.x = speedInTilesPerSeconds
- * (float)map->getTileWidth()
- / 1000 * (float) MILLISECONDS_IN_A_TICK;
- speedInTicks.y = speedInTilesPerSeconds
- * (float)map->getTileHeight()
- / 1000 * (float) MILLISECONDS_IN_A_TICK;
- }
- }
-
- if (!game || !map)
- {
- speedInTicks.x = speedInTicks.y = 0;
- logger->log("Manaserv::BeingHandler: Speed wasn't given back"
- " because game/Map not initialized.");
- }
- // We don't use z for now.
- speedInTicks.z = 0;
-
- return speedInTicks;
-}
-
static void handleLooks(Being *being, Net::MessageIn &msg)
{
// Order of sent slots. Has to be in sync with the server code.
@@ -246,11 +215,10 @@ void BeingHandler::handleBeingsMoveMessage(Net::MessageIn &msg)
* The being's speed is transfered in tiles per second * 10
* to keep it transferable in a Byte.
* We set it back to tiles per second and in a float.
- * Then, we translate it in pixels per ticks, to correspond
- * with the Being::logic() function calls
- * @see MILLISECONDS_IN_A_TICK
*/
- being->setWalkSpeed(giveSpeedInPixelsPerTicks((float) speed / 10));
+ float speedTilesSeconds = (float) speed / 10;
+ being->setMoveSpeed(Vector(speedTilesSeconds, speedTilesSeconds,
+ 0));
}
// Ignore messages from the server for the local player
diff --git a/src/net/manaserv/beinghandler.h b/src/net/manaserv/beinghandler.h
index 2e9eb333..04c766d9 100644
--- a/src/net/manaserv/beinghandler.h
+++ b/src/net/manaserv/beinghandler.h
@@ -35,14 +35,6 @@ class BeingHandler : public MessageHandler
void handleMessage(Net::MessageIn &msg);
- /**
- * Translate a given speed in tiles per seconds
- * into pixels per ticks.
- * Used to optimize Being::logic() calls.
- * @see MILLISECONDS_IN_A_TICKS
- */
- static Vector giveSpeedInPixelsPerTicks(float speedInTilesPerSeconds);
-
private:
void handleBeingAttackMessage(Net::MessageIn &msg);
void handleBeingEnterMessage(Net::MessageIn &msg);
diff --git a/src/net/manaserv/gamehandler.cpp b/src/net/manaserv/gamehandler.cpp
index 040a5e6c..63f84fd1 100644
--- a/src/net/manaserv/gamehandler.cpp
+++ b/src/net/manaserv/gamehandler.cpp
@@ -141,7 +141,8 @@ void GameHandler::gameLoading()
chatHandler->connect();
// Attack range from item DB
- player_node->setAttackRange(-1);
+ // TODO: Deharcode it through equipment handling
+ player_node->setAttackRange(48);
}
} // namespace ManaServ
diff --git a/src/net/manaserv/itemhandler.cpp b/src/net/manaserv/itemhandler.cpp
index af3457db..d8365d79 100644
--- a/src/net/manaserv/itemhandler.cpp
+++ b/src/net/manaserv/itemhandler.cpp
@@ -26,8 +26,6 @@
#include "net/manaserv/manaserv_protocol.h"
#include "net/manaserv/messagein.h"
-#include "game.h"
-#include "map.h"
#include "log.h"
namespace ManaServ {
@@ -58,21 +56,7 @@ void ItemHandler::handleMessage(Net::MessageIn &msg)
if (itemId)
{
- if (Game *game = Game::instance())
- {
- if (Map *map = game->getCurrentMap())
- {
- actorSpriteManager->createItem(id, itemId,
- x / map->getTileWidth(),
- y / map->getTileHeight());
- }
- else
- {
- logger->log(
- "ItemHandler: An item wasn't created "
- "because of Game/Map not initialized...");
- }
- }
+ actorSpriteManager->createItem(id, itemId, Vector(x, y));
}
else if (FloorItem *item = actorSpriteManager->findItem(id))
{
diff --git a/src/net/manaserv/playerhandler.cpp b/src/net/manaserv/playerhandler.cpp
index e86f9afa..4249bac8 100644
--- a/src/net/manaserv/playerhandler.cpp
+++ b/src/net/manaserv/playerhandler.cpp
@@ -412,10 +412,35 @@ int PlayerHandler::getJobLocation()
return -1;
}
-Vector PlayerHandler::getDefaultWalkSpeed()
+Vector PlayerHandler::getDefaultMoveSpeed() const
{
- // Return translation in pixels per ticks.
- return ManaServ::BeingHandler::giveSpeedInPixelsPerTicks(6.0f);
+ // Return default speed at 6 tiles per second.
+ return Vector(6.0f, 6.0f, 0.0f);
+}
+
+Vector PlayerHandler::getPixelsPerTickMoveSpeed(const Vector &speed, Map *map)
+{
+ Vector speedInTicks;
+
+ Game *game = Game::instance();
+ if (game && !map)
+ map = game->getCurrentMap();
+
+ if (!map)
+ {
+ logger->log("Manaserv::PlayerHandler: Speed wasn't given back"
+ " because Map not initialized.");
+ return speedInTicks;
+ }
+
+ speedInTicks.x = speed.x
+ * (float)map->getTileWidth()
+ / 1000 * (float) MILLISECONDS_IN_A_TICK;
+ speedInTicks.y = speed.y
+ * (float)map->getTileHeight()
+ / 1000 * (float) MILLISECONDS_IN_A_TICK;
+
+ return speedInTicks;
}
} // namespace ManaServ
diff --git a/src/net/manaserv/playerhandler.h b/src/net/manaserv/playerhandler.h
index 5796b0d3..3e3f8aad 100644
--- a/src/net/manaserv/playerhandler.h
+++ b/src/net/manaserv/playerhandler.h
@@ -65,7 +65,12 @@ class PlayerHandler : public MessageHandler, public Net::PlayerHandler
int getJobLocation();
- Vector getDefaultWalkSpeed();
+ Vector getDefaultMoveSpeed() const;
+
+ Vector getPixelsPerTickMoveSpeed(const Vector &speed, Map *map = 0);
+
+ bool usePixelPrecision()
+ { return true; }
private:
void handleMapChangeMessage(Net::MessageIn &msg);
diff --git a/src/net/playerhandler.h b/src/net/playerhandler.h
index d7676a92..f9396caf 100644
--- a/src/net/playerhandler.h
+++ b/src/net/playerhandler.h
@@ -62,7 +62,25 @@ class PlayerHandler
virtual int getJobLocation() = 0;
- virtual Vector getDefaultWalkSpeed() = 0;
+ /**
+ * Get the original default movement speed.
+ * Example:
+ * In ticks per tiles for eAthena
+ * In tiles per second for Manaserv
+ */
+ virtual Vector getDefaultMoveSpeed() const = 0;
+
+ /**
+ * Convert the original speed in pixel per tick for internal use.
+ */
+ virtual Vector getPixelsPerTickMoveSpeed(const Vector &speed,
+ Map *map = 0) = 0;
+
+ /**
+ * Tells whether the client has to use pixel paths.
+ * Return false when tiles-center positions only are to be used.
+ */
+ virtual bool usePixelPrecision() = 0;
};
} // namespace Net
diff --git a/src/net/tmwa/beinghandler.cpp b/src/net/tmwa/beinghandler.cpp
index 61491692..960f08ed 100644
--- a/src/net/tmwa/beinghandler.cpp
+++ b/src/net/tmwa/beinghandler.cpp
@@ -20,11 +20,13 @@
*/
#include "net/tmwa/beinghandler.h"
+#include "net/tmwa/playerhandler.h"
#include "actorspritemanager.h"
#include "being.h"
#include "client.h"
#include "effectmanager.h"
+#include "game.h"
#include "guild.h"
#include "localplayer.h"
#include "log.h"
@@ -37,9 +39,13 @@
#include "resources/emotedb.h"
#include <iostream>
+#include <cmath>
namespace TmwAthena {
+// Number of pixels where we decide that the position doesn't need to be reset.
+static const float POS_DEST_DIFF_TOLERANCE = 48.0f;
+
BeingHandler::BeingHandler(bool enableSync):
mSync(enableSync)
{
@@ -95,13 +101,58 @@ Being *createBeing(int id, short job)
return being;
}
+static void handleMoveMessage(Map *map, Being *dstBeing,
+ Uint16 srcX, Uint16 srcY,
+ Uint16 dstX, Uint16 dstY)
+{
+ // Avoid dealing with flawed destination
+ if (map && dstBeing && srcX && srcY && dstX && dstY)
+ {
+ Vector pos = map->getTileCenter(srcX, srcY);
+ Vector dest = map->getTileCenter(dstX, dstY);
+
+ // Don't set the position as the movement algorithm
+ // can guess it and it would break the animation played,
+ // when we're close enough.
+ if (std::abs(dest.x - pos.x) > POS_DEST_DIFF_TOLERANCE
+ || std::abs(dest.y - pos.y) > POS_DEST_DIFF_TOLERANCE)
+ dstBeing->setPosition(pos);
+
+ dstBeing->setDestination(dest.x, dest.y);
+ }
+}
+
+static void handlePosMessage(Map *map, Being *dstBeing, Uint16 x, Uint16 y,
+ Uint8 dir = 0)
+{
+ // Avoid dealing with flawed destination
+ if (map && dstBeing && x && y)
+ {
+ Vector pos = map->getTileCenter(x, y);
+ Vector beingPos = dstBeing->getPosition();
+ // Don't set the position as the movement algorithm
+ // can guess it and it would break the animation played,
+ // when we're close enough.
+ if (std::abs(beingPos.x - pos.x) > POS_DEST_DIFF_TOLERANCE
+ || std::abs(beingPos.y - pos.y) > POS_DEST_DIFF_TOLERANCE)
+ dstBeing->setPosition(pos);
+
+ // Set also the destination to the desired position.
+ dstBeing->setDestination(pos.x, pos.y);
+
+ if (dir)
+ dstBeing->setDirection(dir);
+ }
+}
+
void BeingHandler::handleMessage(Net::MessageIn &msg)
{
if (!actorSpriteManager)
return;
int id;
- short job, speed, gender;
+ short job, gender;
+ float speed;
Uint16 headTop, headMid, headBottom;
Uint16 shoes, gloves;
Uint16 weapon, shield;
@@ -114,13 +165,16 @@ void BeingHandler::handleMessage(Net::MessageIn &msg)
Being *srcBeing, *dstBeing;
int hairStyle, hairColor, flag;
+ // Prepare useful translation variables
+ Map *map = Game::instance()->getCurrentMap();
+
switch (msg.getId())
{
case SMSG_BEING_VISIBLE:
case SMSG_BEING_MOVE:
// Information about a being in range
id = msg.readInt32();
- speed = msg.readInt16();
+ speed = (float)msg.readInt16();
stunMode = msg.readInt16(); // opt1
statusEffects = msg.readInt16(); // opt2
statusEffects |= ((Uint32)msg.readInt16()) << 16; // option
@@ -146,15 +200,14 @@ void BeingHandler::handleMessage(Net::MessageIn &msg)
if (msg.getId() == SMSG_BEING_VISIBLE)
{
dstBeing->clearPath();
- dstBeing->setActionTime(tick_time);
dstBeing->setAction(Being::STAND);
}
-
// Prevent division by 0 when calculating frame
- if (speed == 0) { speed = 150; }
+ if (speed == 0)
+ speed = 150.0f; // In ticks per tile * 10
- dstBeing->setWalkSpeed(Vector(speed, speed, 0));
+ dstBeing->setMoveSpeed(Vector(speed / 10, speed / 10));
dstBeing->setSubtype(job);
hairStyle = msg.readInt16();
weapon = msg.readInt16();
@@ -205,17 +258,14 @@ void BeingHandler::handleMessage(Net::MessageIn &msg)
{
Uint16 srcX, srcY, dstX, dstY;
msg.readCoordinatePair(srcX, srcY, dstX, dstY);
- dstBeing->setAction(Being::STAND);
- dstBeing->setTileCoords(srcX, srcY);
- dstBeing->setDestination(dstX, dstY);
+ handleMoveMessage(map, dstBeing, srcX, srcY, dstX, dstY);
}
else
{
Uint8 dir;
Uint16 x, y;
msg.readCoordinates(x, y, dir);
- dstBeing->setTileCoords(x, y);
- dstBeing->setDirection(dir);
+ handlePosMessage(map, dstBeing, x, y, dir);
}
msg.readInt8(); // unknown
@@ -235,6 +285,7 @@ void BeingHandler::handleMessage(Net::MessageIn &msg)
break;
case SMSG_BEING_MOVE2:
+ {
/*
* A simplified movement packet, used by the
* later versions of eAthena for both mobs and
@@ -254,11 +305,8 @@ void BeingHandler::handleMessage(Net::MessageIn &msg)
Uint16 srcX, srcY, dstX, dstY;
msg.readCoordinatePair(srcX, srcY, dstX, dstY);
msg.readInt32(); // Server tick
-
- dstBeing->setAction(Being::STAND);
- dstBeing->setTileCoords(srcX, srcY);
- dstBeing->setDestination(dstX, dstY);
-
+ handleMoveMessage(map, dstBeing, srcX, srcY, dstX, dstY);
+ }
break;
case SMSG_BEING_REMOVE:
@@ -479,15 +527,17 @@ void BeingHandler::handleMessage(Net::MessageIn &msg)
}
break;
case SMSG_BEING_CHANGE_DIRECTION:
+ {
if (!(dstBeing = actorSpriteManager->findBeing(msg.readInt32())))
{
break;
}
msg.readInt16(); // unused
-
- dstBeing->setDirection(msg.readInt8());
-
+ Uint8 dir = msg.readInt8();
+ if (dir)
+ dstBeing->setDirection(dir);
+ }
break;
case SMSG_PLAYER_UPDATE_1:
@@ -519,7 +569,12 @@ void BeingHandler::handleMessage(Net::MessageIn &msg)
}
}
- dstBeing->setWalkSpeed(Vector(speed, speed, 0));
+ // The original speed is ticks per tile * 10
+ if (speed)
+ dstBeing->setMoveSpeed(Vector(speed / 10, speed / 10));
+ else
+ dstBeing->setMoveSpeed(Net::getPlayerHandler()->getDefaultMoveSpeed());
+
dstBeing->setSubtype(job);
hairStyle = msg.readInt16();
weapon = msg.readInt16();
@@ -561,16 +616,14 @@ void BeingHandler::handleMessage(Net::MessageIn &msg)
{
Uint16 srcX, srcY, dstX, dstY;
msg.readCoordinatePair(srcX, srcY, dstX, dstY);
- dstBeing->setTileCoords(srcX, srcY);
- dstBeing->setDestination(dstX, dstY);
+ handleMoveMessage(map, dstBeing, srcX, srcY, dstX, dstY);
}
else
{
Uint8 dir;
Uint16 x, y;
msg.readCoordinates(x, y, dir);
- dstBeing->setTileCoords(x, y);
- dstBeing->setDirection(dir);
+ handlePosMessage(map, dstBeing, x, y, dir);
}
gmstatus = msg.readInt16();
@@ -598,9 +651,6 @@ void BeingHandler::handleMessage(Net::MessageIn &msg)
msg.readInt8(); // Lv
msg.readInt8(); // unknown
- dstBeing->setActionTime(tick_time);
- dstBeing->reset();
-
dstBeing->setStunMode(stunMode);
dstBeing->setStatusEffectBlock(0, (statusEffects >> 16) & 0xffff);
dstBeing->setStatusEffectBlock(16, statusEffects & 0xffff);
@@ -628,9 +678,7 @@ void BeingHandler::handleMessage(Net::MessageIn &msg)
Uint16 x, y;
x = msg.readInt16();
y = msg.readInt16();
- dstBeing->setTileCoords(x, y);
- if (dstBeing->getCurrentAction() == Being::MOVE)
- dstBeing->setAction(Being::STAND);
+ handlePosMessage(map, dstBeing, x, y);
}
}
break;
diff --git a/src/net/tmwa/charserverhandler.cpp b/src/net/tmwa/charserverhandler.cpp
index 1063ee39..e6dc84a2 100644
--- a/src/net/tmwa/charserverhandler.cpp
+++ b/src/net/tmwa/charserverhandler.cpp
@@ -185,7 +185,11 @@ void CharServerHandler::handleMessage(Net::MessageIn &msg)
mNetwork->disconnect();
Client::setState(STATE_CHANGE_MAP);
- player_node->setTileCoords(x, y);
+ Map *map = player_node->getMap();
+ int tileWidth = map->getTileWidth();
+ int tileHeight = map->getTileHeight();
+ player_node->setPosition(Vector(x * tileWidth + tileWidth / 2,
+ y * tileHeight + tileHeight / 2));
player_node->setMap(0);
}
break;
diff --git a/src/net/tmwa/gamehandler.cpp b/src/net/tmwa/gamehandler.cpp
index 0b3c7d38..2194c77d 100644
--- a/src/net/tmwa/gamehandler.cpp
+++ b/src/net/tmwa/gamehandler.cpp
@@ -76,7 +76,8 @@ void GameHandler::handleMessage(Net::MessageIn &msg)
x, y, direction);
// Switch now or we'll have problems
Client::setState(STATE_GAME);
- player_node->setTileCoords(x, y);
+ // Stores the position until the map is loaded.
+ mTileX = x; mTileY = y;
} break;
case SMSG_SERVER_PING:
@@ -111,7 +112,17 @@ void GameHandler::event(Event::Channel channel, const Event &event)
{
if (event.getType() == Event::EnginesInitialized)
{
- Game::instance()->changeMap(mMap);
+ Game *game = Game::instance();
+ game->changeMap(mMap);
+ Map *map = game->getCurrentMap();
+ int tileWidth = map->getTileWidth();
+ int tileHeight = map->getTileHeight();
+ if (mTileX && mTileY)
+ {
+ player_node->setPosition(Vector(mTileX * tileWidth + tileWidth / 2,
+ mTileY * tileHeight + tileHeight / 2));
+ mTileX = mTileY = 0;
+ }
}
else if (event.getType() == Event::MapLoaded)
{
diff --git a/src/net/tmwa/gamehandler.h b/src/net/tmwa/gamehandler.h
index a1a2e7ec..016df18e 100644
--- a/src/net/tmwa/gamehandler.h
+++ b/src/net/tmwa/gamehandler.h
@@ -65,8 +65,13 @@ class GameHandler : public MessageHandler, public Net::GameHandler,
bool canUseMagicBar() const { return true; }
private:
- std::string mMap;
+ std::string mMap; ///< Keeps the map filename.
int mCharID; /// < Saved for map-server switching
+ /**
+ * Keeps the local character position until the map is loaded
+ * to permit the translation in pixels.
+ */
+ int mTileX, mTileY;
};
} // namespace TmwAthena
diff --git a/src/net/tmwa/inventoryhandler.cpp b/src/net/tmwa/inventoryhandler.cpp
index 18e8febc..1340a091 100644
--- a/src/net/tmwa/inventoryhandler.cpp
+++ b/src/net/tmwa/inventoryhandler.cpp
@@ -24,6 +24,7 @@
#include "configuration.h"
#include "equipment.h"
#include "event.h"
+#include "game.h"
#include "inventory.h"
#include "item.h"
#include "itemshortcut.h"
@@ -408,13 +409,33 @@ void InventoryHandler::handleMessage(Net::MessageIn &msg)
flag = msg.readInt8();
if (!flag)
+ {
SERVER_NOTICE(_("Unable to unequip."))
+ }
else
+ {
mEquips.setEquipment(getSlot(equipType), -1);
+ // Reset the attack range to unarmed.
+ player_node->setAttackRange(ATTACK_RANGE_NOT_SET);
+ }
break;
case SMSG_PLAYER_ATTACK_RANGE:
- player_node->setAttackRange(msg.readInt16());
+ {
+ // The range is in tiles, so we translate it back to pixels
+ Map *map = Game::instance()->getCurrentMap();
+ if (map)
+ {
+ player_node->setAttackRange(msg.readInt16()
+ * map->getTileWidth());
+ }
+ else
+ {
+ logger->log("Couldn't set attacke range due to the lack"
+ "of an initialized map.");
+ player_node->setAttackRange(ATTACK_RANGE_NOT_SET);
+ }
+ }
break;
case SMSG_PLAYER_ARROW_EQUIP:
diff --git a/src/net/tmwa/itemhandler.cpp b/src/net/tmwa/itemhandler.cpp
index a8e98860..e452ccf2 100644
--- a/src/net/tmwa/itemhandler.cpp
+++ b/src/net/tmwa/itemhandler.cpp
@@ -21,12 +21,14 @@
#include "net/tmwa/itemhandler.h"
-#include "actorspritemanager.h"
-
#include "net/messagein.h"
#include "net/tmwa/protocol.h"
+#include "actorspritemanager.h"
+#include "game.h"
+#include "map.h"
+
namespace TmwAthena {
ItemHandler::ItemHandler()
@@ -54,7 +56,13 @@ void ItemHandler::handleMessage(Net::MessageIn &msg)
int y = msg.readInt16();
msg.skip(4); // amount,subX,subY / subX,subY,amount
- actorSpriteManager->createItem(id, itemId, x, y);
+ Game *game = Game::instance();
+ if (!game)
+ break;
+
+ if (Map *map = game->getCurrentMap())
+ actorSpriteManager->createItem(id, itemId,
+ map->getTileCenter(x, y));
}
break;
diff --git a/src/net/tmwa/playerhandler.cpp b/src/net/tmwa/playerhandler.cpp
index ec004917..4bd637c3 100644
--- a/src/net/tmwa/playerhandler.cpp
+++ b/src/net/tmwa/playerhandler.cpp
@@ -20,7 +20,9 @@
*/
#include "net/tmwa/playerhandler.h"
+#include "net/tmwa/beinghandler.h"
+#include "client.h"
#include "event.h"
#include "game.h"
#include "localplayer.h"
@@ -198,20 +200,23 @@ void PlayerHandler::handleMessage(Net::MessageIn &msg)
float scrollOffsetX = 0.0f;
float scrollOffsetY = 0.0f;
- /* Scroll if neccessary */
+ /* Scroll if necessary */
+ Map *map = game->getCurrentMap();
+ int tileX = player_node->getTileX();
+ int tileY = player_node->getTileY();
if (!sameMap
- || (abs(x - player_node->getTileX()) > MAP_TELEPORT_SCROLL_DISTANCE)
- || (abs(y - player_node->getTileY()) > MAP_TELEPORT_SCROLL_DISTANCE))
+ || (abs(x - tileX) > MAP_TELEPORT_SCROLL_DISTANCE)
+ || (abs(y - tileY) > MAP_TELEPORT_SCROLL_DISTANCE))
{
- Map *map = game->getCurrentMap();
- scrollOffsetX = (x - player_node->getTileX())
- * map->getTileWidth();
- scrollOffsetY = (y - player_node->getTileY())
- * map->getTileHeight();
+ scrollOffsetX = (x - tileX) * map->getTileWidth();
+ scrollOffsetY = (y - tileY) * map->getTileHeight();
}
player_node->setAction(Being::STAND);
- player_node->setTileCoords(x, y);
+ Vector pos = map->getTileCenter(x, y);
+ player_node->setPosition(pos);
+ // Stop movement
+ player_node->setDestination(pos.x, pos.y);
logger->log("Adjust scrolling by %d:%d", (int) scrollOffsetX,
(int) scrollOffsetY);
@@ -228,7 +233,8 @@ void PlayerHandler::handleMessage(Net::MessageIn &msg)
switch (type)
{
case 0x0000:
- player_node->setWalkSpeed(Vector(value, value, 0));
+ player_node->setMoveSpeed(Vector(value / 10,
+ value / 10, 0));
break;
case 0x0004: break; // manner
case 0x0005: PlayerInfo::setAttribute(HP, value); break;
@@ -553,8 +559,12 @@ void PlayerHandler::setDirection(char direction)
void PlayerHandler::setDestination(int x, int y, int direction)
{
+ // The destination coordinates are received in pixel, so we translate them
+ // into tiles.
+ Map *map = Game::instance()->getCurrentMap();
MessageOut outMsg(CMSG_PLAYER_CHANGE_DEST);
- outMsg.writeCoordinates(x, y, direction);
+ outMsg.writeCoordinates(x / map->getTileWidth(), y / map->getTileHeight(),
+ direction);
}
void PlayerHandler::changeAction(Being::Action action)
@@ -603,11 +613,35 @@ int PlayerHandler::getJobLocation()
return JOB;
}
-Vector PlayerHandler::getDefaultWalkSpeed()
+Vector PlayerHandler::getDefaultMoveSpeed() const
{
// Return an normalized speed for any side
// as the offset is calculated elsewhere.
- return Vector(150, 150, 0);
+ // in ticks per tile.
+ return Vector(15.0f, 15.0f, 0.0f);
+}
+
+Vector PlayerHandler::getPixelsPerTickMoveSpeed(const Vector &speed, Map *map)
+{
+ Game *game = Game::instance();
+
+ if (game && !map)
+ map = game->getCurrentMap();
+
+ if (!map || speed.x == 0 || speed.y == 0)
+ {
+ logger->log("TmwAthena::PlayerHandler: Speed set to default: "
+ "Map not yet initialized or invalid speed.");
+ return getDefaultMoveSpeed();
+ }
+
+ Vector speedInTicks;
+
+ // speedInTicks.z = 0; // We don't use z for now.
+ speedInTicks.x = 1 / speed.x * (float)map->getTileWidth();
+ speedInTicks.y = 1 / speed.y * (float)map->getTileHeight();
+
+ return speedInTicks;
}
} // namespace TmwAthena
diff --git a/src/net/tmwa/playerhandler.h b/src/net/tmwa/playerhandler.h
index cb352110..63812f47 100644
--- a/src/net/tmwa/playerhandler.h
+++ b/src/net/tmwa/playerhandler.h
@@ -58,7 +58,12 @@ class PlayerHandler : public MessageHandler, public Net::PlayerHandler
int getJobLocation();
- Vector getDefaultWalkSpeed();
+ Vector getDefaultMoveSpeed() const;
+
+ Vector getPixelsPerTickMoveSpeed(const Vector &speed, Map *map = 0);
+
+ bool usePixelPrecision()
+ { return false; }
};
} // namespace TmwAthena
diff --git a/src/resources/iteminfo.h b/src/resources/iteminfo.h
index 7b9e7287..76a74111 100644
--- a/src/resources/iteminfo.h
+++ b/src/resources/iteminfo.h
@@ -160,7 +160,9 @@ class ItemInfo
* Attack action sub-types (bow, sword, ...) are defined in items.xml.
*/
std::string mAttackAction;
- int mAttackRange; /**< Attack range, will be zero if non weapon. */
+
+ /** Attack range, will be equal to ATTACK_RANGE_NOT_SET if no weapon. */
+ int mAttackRange;
// Particle to be shown when weapon attacks
std::string mMissileParticle;
diff --git a/src/sound.cpp b/src/sound.cpp
index 636da7c7..4216c8fa 100644
--- a/src/sound.cpp
+++ b/src/sound.cpp
@@ -22,6 +22,7 @@
#include <SDL.h>
#include "configuration.h"
+#include "game.h"
#include "localplayer.h"
#include "log.h"
#include "sound.h"
@@ -291,8 +292,10 @@ void Sound::playSfx(const std::string &path, int x, int y)
int vol = 120;
if (player_node && x > 0 && y > 0)
{
- int dx = player_node->getTileX() - x;
- int dy = player_node->getTileY() - y;
+ Vector pos = player_node->getPosition();
+ Map *map = Game::instance()->getCurrentMap();
+ int dx = ((int)pos.x - x) / map->getTileWidth();
+ int dy = ((int)pos.y - y) / map->getTileHeight();
if (dx < 0)
dx = -dx;
if (dy < 0)
diff --git a/src/sound.h b/src/sound.h
index 1dee55e7..032ff62b 100644
--- a/src/sound.h
+++ b/src/sound.h
@@ -95,8 +95,8 @@ class Sound
* Plays an item.
*
* @param path The resource path to the sound file.
- * @param x The vertical distance of the sound in tiles.
- * @param y The horizontal distance of the sound in tiles.
+ * @param x The vertical distance of the sound in pixels.
+ * @param y The horizontal distance of the sound in pixels.
*/
void playSfx(const std::string &path, int x = 0, int y = 0);