Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

improve: walking system independent of server response #1087

Merged
merged 12 commits into from
Feb 1, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions modules/game_walk/walk.lua
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ local function walk(dir)
if not canChangeFloor(toPos, 1) and not canChangeFloor(toPos, -1) then
return false
end
elseif not player:isPreWalking() then
else
player:preWalk(dir)
end
end
Expand All @@ -88,7 +88,7 @@ local function addWalkEvent(dir, delay)
walk(smartWalkDir or dir)
end

walkEvent = delay == 0 and addEvent(walkCallback) or scheduleEvent(walkCallback, delay or 10)
walkEvent = delay == 0 and addEvent(walkCallback) or scheduleEvent(walkCallback, delay or 5)
end

--- Initiates a smart walk in the given direction.
Expand Down
4 changes: 2 additions & 2 deletions src/client/creature.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -614,7 +614,7 @@ void Creature::updateWalkingTile()

// only render creatures where bottom right is inside tile rect
if (virtualTileRect.contains(virtualCreatureRect.bottomRight())) {
newWalkingTile = g_map.getOrCreateTile(m_position.translated(xi, yi, 0));
newWalkingTile = g_map.getOrCreateTile(getPosition().translated(xi, yi, 0));
}
}
}
Expand Down Expand Up @@ -655,7 +655,7 @@ void Creature::nextWalkUpdate()

void Creature::updateWalk()
{
const float walkTicksPerPixel = (getStepDuration(true) + 8.f) / static_cast<float>(g_gameConfig.getSpriteSize());
const float walkTicksPerPixel = getStepDuration(true) / static_cast<float>(g_gameConfig.getSpriteSize());

const int totalPixelsWalked = std::min<int>(m_walkTimer.ticksElapsed() / walkTicksPerPixel, g_gameConfig.getSpriteSize());

Expand Down
4 changes: 2 additions & 2 deletions src/client/creature.h
Original file line number Diff line number Diff line change
Expand Up @@ -186,8 +186,8 @@ minHeight,
bool canShoot(int distance);

protected:
virtual void updateWalkOffset(uint8_t totalPixelsWalked);
virtual void terminateWalk();
void updateWalkOffset(uint8_t totalPixelsWalked);
void updateWalk();

ThingType* getThingType() const override;
Expand All @@ -202,6 +202,7 @@ minHeight,
Otc::Direction m_direction{ Otc::South };

Timer m_walkTimer;
Position m_lastStepToPosition;

private:
void nextWalkUpdate();
Expand Down Expand Up @@ -245,7 +246,6 @@ minHeight,
CachedStep m_stepCache;

Position m_lastStepFromPosition;
Position m_lastStepToPosition;
Position m_oldPosition;

Timer m_footTimer;
Expand Down
4 changes: 4 additions & 0 deletions src/client/game.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ void Game::resetGameStates()
m_serverBeat = 50;
m_seq = 0;
m_ping = -1;
m_walkTicks = 0;
setCanReportBugs(false);
m_fightMode = Otc::FightBalanced;
m_chaseMode = Otc::DontChase;
Expand Down Expand Up @@ -684,6 +685,9 @@ void Game::forceWalk(const Otc::Direction direction)
if (!canPerformGameAction())
return;

m_walkTimer.restart();
m_walkTicks = -1;

switch (direction) {
case Otc::North:
m_protocolGame->sendWalkNorth();
Expand Down
4 changes: 4 additions & 0 deletions src/client/game.h
Original file line number Diff line number Diff line change
Expand Up @@ -776,6 +776,8 @@ class Game
void requestBossSlootInfo();
void requestBossSlotAction(uint8_t action, uint32_t raceId);
void sendStatusTrackerBestiary(uint16_t raceId, bool status);
auto getServerWalkTicks() const { return m_walkTicks; }
auto getWalkTicksElapsed() const { return m_walkTimer.ticksElapsed(); }
protected:
void enableBotCall() { m_denyBotCall = false; }
void disableBotCall() { m_denyBotCall = true; }
Expand Down Expand Up @@ -827,6 +829,8 @@ class Game
stdext::timer m_pingTimer;

ticks_t m_ping{ -1 };
ticks_t m_walkTicks{ 0 };
Timer m_walkTimer;
};

extern Game g_game;
49 changes: 17 additions & 32 deletions src/client/localplayer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
m_walkLockExpiration = std::max<ticks_t>(m_walkLockExpiration, g_clock.millis() + millis);
}

bool LocalPlayer::canWalk(const Otc::Direction dir, const bool ignoreLock)

Check warning on line 34 in src/client/localplayer.cpp

View workflow job for this annotation

GitHub Actions / ubuntu-22.04-linux-debug

unused parameter ‘dir’ [-Wunused-parameter]

Check warning on line 34 in src/client/localplayer.cpp

View workflow job for this annotation

GitHub Actions / ubuntu-24.04-linux-debug

unused parameter ‘dir’ [-Wunused-parameter]
{
// paralyzed
if (isDead())
Expand All @@ -46,37 +46,42 @@
if (isPreWalking()) return false; // allow only single prewalk
}

return m_walkTimer.ticksElapsed() >= getStepDuration(); // allow only if walk done, ex. diagonals may need additional ticks before taking another step
if (g_game.getServerWalkTicks() == -1)
return false;

// allow only if walk done, ex. diagonals may need additional ticks before taking another step
return m_walkTimer.ticksElapsed() >= std::max<int>(getStepDuration(), g_game.getServerWalkTicks());
}

void LocalPlayer::walk(const Position& oldPos, const Position& newPos)
{
m_autoWalkRetries = 0;

if (isPreWalking()) {
if (newPos == m_lastPrewalkDestination) {
m_lastPrewalkDestination = {};
updateWalk();
if (oldPos.z == newPos.z) {
if (m_lastPrewalkDestination == newPos || g_game.getWalkTicksElapsed() <= 1)
return;
}

m_lastPrewalkDestination = {};
if (g_game.getServerWalkTicks() - getStepDuration() > g_game.getWalkTicksElapsed())
return;
}

m_serverWalk = true;

Creature::walk(oldPos, newPos);
}

void LocalPlayer::preWalk(const Otc::Direction direction)
{
auto pos = m_position.translatedToDirection(direction);
// avoid reanimating prewalks
if (m_lastPrewalkDestination.isValid() || m_lastPrewalkDestination == pos)
if (m_lastPrewalkDestination.isValid())
return;

auto pos = m_position.translatedToDirection(direction);

if (m_lastPrewalkDestination == pos)
return;

// start walking to direction
m_lastPrewalkDestination = pos;
Creature::walk(m_position, m_lastPrewalkDestination);
Creature::walk(m_position, m_lastPrewalkDestination = std::move(pos));
}

bool LocalPlayer::retryAutoWalk()
Expand Down Expand Up @@ -185,26 +190,6 @@
m_autoWalkContinueEvent->cancel();
}

void LocalPlayer::updateWalkOffset(const uint8_t totalPixelsWalked)
{
if (!isPreWalking()) {
Creature::updateWalkOffset(totalPixelsWalked);
return;
}

// pre walks offsets are calculated in the oposite direction
m_walkOffset = {};
if (m_direction == Otc::North || m_direction == Otc::NorthEast || m_direction == Otc::NorthWest)
m_walkOffset.y = -totalPixelsWalked;
else if (m_direction == Otc::South || m_direction == Otc::SouthEast || m_direction == Otc::SouthWest)
m_walkOffset.y = totalPixelsWalked;

if (m_direction == Otc::East || m_direction == Otc::NorthEast || m_direction == Otc::SouthEast)
m_walkOffset.x = totalPixelsWalked;
else if (m_direction == Otc::West || m_direction == Otc::NorthWest || m_direction == Otc::SouthWest)
m_walkOffset.x = -totalPixelsWalked;
}

void LocalPlayer::terminateWalk()
{
Creature::terminateWalk();
Expand Down
5 changes: 2 additions & 3 deletions src/client/localplayer.h
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ class LocalPlayer final : public Player

bool hasSight(const Position& pos);
bool isKnown() { return m_known; }
bool isServerWalking() { return m_serverWalk; }
bool isPreWalking() { return m_lastPrewalkDestination.isValid(); }
bool isAutoWalking() { return m_autoWalkDestination.isValid(); }
bool isPremium() { return m_premium; }
Expand All @@ -117,13 +118,11 @@ class LocalPlayer final : public Player
void onPositionChange(const Position& newPos, const Position& oldPos) override;

void preWalk(Otc::Direction direction);
Position getLastPrewalkingPosition() { return m_lastPrewalkDestination; }

bool isServerWalking() { return m_serverWalk; }
Position getPosition() override { return m_lastStepToPosition.isValid() && m_lastStepToPosition.z == m_position.z && m_position.distance(m_lastStepToPosition) < 2 ? m_lastStepToPosition : m_position; }

protected:
void walk(const Position& oldPos, const Position& newPos) override;
void updateWalkOffset(uint8_t totalPixelsWalked) override;
void terminateWalk() override;

friend class Game;
Expand Down
2 changes: 0 additions & 2 deletions src/client/luafunctions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -851,9 +851,7 @@ void Client::registerLuaFunctions()
g_lua.bindClassMemberFunction<LocalPlayer>("getBlessings", &LocalPlayer::getBlessings);
g_lua.bindClassMemberFunction<LocalPlayer>("isPremium", &LocalPlayer::isPremium);
g_lua.bindClassMemberFunction<LocalPlayer>("isKnown", &LocalPlayer::isKnown);
g_lua.bindClassMemberFunction<LocalPlayer>("isPreWalking", &LocalPlayer::isPreWalking);
g_lua.bindClassMemberFunction<LocalPlayer>("preWalk", &LocalPlayer::preWalk);
g_lua.bindClassMemberFunction<LocalPlayer>("getLastPrewalkingPosition", &LocalPlayer::getLastPrewalkingPosition);
g_lua.bindClassMemberFunction<LocalPlayer>("hasSight", &LocalPlayer::hasSight);
g_lua.bindClassMemberFunction<LocalPlayer>("isAutoWalking", &LocalPlayer::isAutoWalking);
g_lua.bindClassMemberFunction<LocalPlayer>("stopAutoWalk", &LocalPlayer::stopAutoWalk);
Expand Down
4 changes: 2 additions & 2 deletions src/client/map.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ bool Map::removeThing(const ThingPtr& thing)
return false;

if (thing->isMissile()) {
auto& missiles = m_floors[thing->getPosition().z].missiles;
auto& missiles = m_floors[thing->getServerPosition().z].missiles;
const auto it = std::ranges::find(missiles, thing->static_self_cast<Missile>());
if (it == missiles.end())
return false;
Expand All @@ -255,7 +255,7 @@ bool Map::removeThing(const ThingPtr& thing)

if (const auto& tile = thing->getTile()) {
if (tile->removeThing(thing)) {
notificateTileUpdate(thing->getPosition(), thing, Otc::OPERATION_REMOVE);
notificateTileUpdate(thing->getServerPosition(), thing, Otc::OPERATION_REMOVE);
return true;
}
}
Expand Down
15 changes: 10 additions & 5 deletions src/client/protocolgameparse.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1366,6 +1366,11 @@ void ProtocolGame::parseCreatureMove(const InputMessagePtr& msg)
const auto& creature = thing->static_self_cast<Creature>();
creature->allowAppearWalk();

if (creature->isLocalPlayer() && g_game.m_walkTicks == -1) {
g_game.m_walkTicks = g_game.m_walkTimer.ticksElapsed();
g_game.m_walkTimer.restart();
}

g_map.addThing(thing, newPos, -1);
}

Expand Down Expand Up @@ -3867,17 +3872,17 @@ void ProtocolGame::parseCyclopediaHousesInfo(const InputMessagePtr& msg)
// TO-DO Lua // Otui
}

void ProtocolGame::parseCyclopediaHouseList(const InputMessagePtr& msg)
void ProtocolGame::parseCyclopediaHouseList(const InputMessagePtr& msg)
{
const uint16_t housesCount = msg->getU16(); // housesCount
for (auto i = 0; i < housesCount; ++i) {
msg->getU32(); // clientId
msg->getU8(); // 0x00 = Renovation, 0x01 = Available

const auto type = static_cast<Otc::CyclopediaHouseState_t>(msg->getU8());
switch (type) {
case Otc::CYCLOPEDIA_HOUSE_STATE_AVAILABLE: {
std::string bidderName = msg->getString();
std::string bidderName = msg->getString();
const auto isBidder = static_cast<bool>(msg->getU8());
msg->getU8(); // disableIndex

Expand All @@ -3893,7 +3898,7 @@ void ProtocolGame::parseCyclopediaHouseList(const InputMessagePtr& msg)
case Otc::CYCLOPEDIA_HOUSE_STATE_RENTED: {
msg->getString(); // ownerName
msg->getU32(); // paidUntil

const auto isRented = static_cast<bool>(msg->getU8());
if (isRented) {
msg->getU8(); // unknown
Expand All @@ -3913,7 +3918,7 @@ void ProtocolGame::parseCyclopediaHouseList(const InputMessagePtr& msg)
msg->getString(); // bidderName
msg->getU8(); // unknown
msg->getU64(); // internalBid

const auto isNewOwner = static_cast<bool>(msg->getU8());
if (isNewOwner) {
msg->getU8(); // acceptTransferError
Expand Down
3 changes: 2 additions & 1 deletion src/client/thing.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ class Thing : public AttachableObject
virtual uint32_t getId() { return m_clientId; }
uint16_t getClientId() const { return m_clientId; }

Position getPosition() { return m_position; }
virtual Position getPosition() { return m_position; }
Position getServerPosition() { return m_position; }

const TilePtr& getTile();
ContainerPtr getParentContainer();
Expand Down
13 changes: 12 additions & 1 deletion src/client/tile.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -113,10 +113,16 @@ void Tile::drawCreature(const Point& dest, const int flags, const bool forceDraw
if (!forceDraw && !m_drawTopAndCreature)
return;

bool localPlayerDrawed = false;
if (hasCreature()) {
for (const auto& thing : m_things) {
if (!thing->isCreature() || thing->static_self_cast<Creature>()->isWalking()) continue;

if (thing->isLocalPlayer()) {
if (thing->getPosition() != m_position) continue;
localPlayerDrawed = true;
}

drawThing(thing, dest, flags, drawElevation);
}
}
Expand All @@ -129,6 +135,11 @@ void Tile::drawCreature(const Point& dest, const int flags, const bool forceDraw

creature->draw(cDest, flags & Otc::DrawThings);
}

// draw the local character if he is on a virtual tile, that is, his visual position is not the same as the server.
if (!localPlayerDrawed && g_game.getLocalPlayer() && !g_game.getLocalPlayer()->isWalking() && g_game.getLocalPlayer()->getPosition() == m_position) {
drawThing(g_game.getLocalPlayer(), dest, flags, drawElevation);
}
}

void Tile::drawTop(const Point& dest, const int flags, const bool forceDraw, uint8_t drawElevation)
Expand Down Expand Up @@ -977,4 +988,4 @@ bool Tile::canShoot(int distance)
if (distance > 0 && std::max<int>(std::abs(m_position.x - playerPos.x), std::abs(m_position.y - playerPos.y)) > distance)
return false;
return g_map.isSightClear(playerPos, m_position);
}
}
Loading