diff options
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) @@ -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); |