More walk changes and creature events

Improve walking, no more random cancelWalks
Add 3 new creatures events onAppear/onDisappear/onWalk
Add algorithm that calculates walk ping
Fix paralyze animation while walking
This commit is contained in:
Eduardo Bart 2012-08-02 04:58:49 -03:00
parent 76d32b5493
commit 57785d2001
11 changed files with 228 additions and 171 deletions

View File

@ -28,6 +28,7 @@
#include "item.h"
#include "game.h"
#include "effect.h"
#include "luavaluecasts.h"
#include <framework/graphics/graphics.h>
#include <framework/core/eventdispatcher.h>
@ -46,12 +47,12 @@ Creature::Creature() : Thing()
m_speed = 200;
m_direction = Otc::South;
m_walkAnimationPhase = 0;
m_walkInterval = 0;
m_walkAnimationInterval = 0;
m_walkedPixels = 0;
m_walkTurnDirection = Otc::InvalidDirection;
m_skull = Otc::SkullNone;
m_shield = Otc::ShieldNone;
m_emblem = Otc::EmblemNone;
m_lastStepDirection = Otc::InvalidDirection;
m_nameCache.setFont(g_fonts.getFont("verdana-11px-rounded"));
m_nameCache.setAlign(Fw::AlignTopCenter);
m_footStep = 0;
@ -255,34 +256,17 @@ void Creature::walk(const Position& oldPos, const Position& newPos)
return;
// get walk direction
Otc::Direction direction = oldPos.getDirectionFromPosition(newPos);
m_lastStepDirection = oldPos.getDirectionFromPosition(newPos);
// set current walking direction
setDirection(direction);
setDirection(m_lastStepDirection);
// starts counting walk
m_walking = true;
m_walkTimer.restart();
m_walkedPixels = 0;
// calculates walk interval
int groundSpeed = 0;
TilePtr tile = g_map.getTile(newPos);
if(tile)
groundSpeed = tile->getGroundSpeed();
float interval = 1000;
if(groundSpeed > 0 && m_speed > 0)
interval = (1000.0f * groundSpeed) / m_speed;
interval = std::floor(interval / g_game.getServerBeat()) * g_game.getServerBeat();
m_walkAnimationInterval = interval;
m_walkInterval = interval;
// diagonal walking lasts 3 times more.
if(direction == Otc::NorthWest || direction == Otc::NorthEast || direction == Otc::SouthWest || direction == Otc::SouthEast)
m_walkInterval *= 3;
// no direction needs to be changed when the walk ends
// no direction need to be changed when the walk ends
m_walkTurnDirection = Otc::InvalidDirection;
// starts updating walk
@ -302,9 +286,50 @@ void Creature::stopWalk()
terminateWalk();
}
void Creature::onMove(const Position& newPos, const Position& oldPos)
void Creature::onAppear()
{
walk(oldPos, newPos);
// cancel any disappear event
if(m_disappearEvent) {
m_disappearEvent->cancel();
m_disappearEvent = nullptr;
}
// creature appeared the first time or wasn't seen for a long time
if(m_removed) {
m_removed = false;
callLuaField("onAppear");
// walk
} else if(m_oldPosition != m_position && m_oldPosition.isInRange(m_position,1,1)) {
walk(m_oldPosition, m_position);
callLuaField("onWalk", m_oldPosition, m_position);
// teleport
} else if(m_oldPosition != m_position) {
callLuaField("onDisappear");
callLuaField("onAppear");
} // else turn
}
void Creature::onDisappear()
{
if(m_disappearEvent)
m_disappearEvent->cancel();
m_oldPosition = m_position;
// a pair onDisappear and onAppear events are fired even when creatures walks or turns,
// so we must filter
auto self = static_self_cast<Creature>();
m_disappearEvent = g_dispatcher.addEvent([self] {
self->m_removed = true;
self->stopWalk();
self->callLuaField("onDisappear");
// invalidate this creature position
self->m_position = Position();
self->m_oldPosition = Position();
self->m_disappearEvent = nullptr;
});
}
void Creature::updateWalkAnimation(int totalPixelsWalked)
@ -316,7 +341,7 @@ void Creature::updateWalkAnimation(int totalPixelsWalked)
int footAnimPhases = getAnimationPhases() - 1;
if(totalPixelsWalked == 32 || footAnimPhases == 0)
m_walkAnimationPhase = 0;
else if(m_footStepDrawn && m_footTimer.ticksElapsed() >= m_walkAnimationInterval / 4 ) {
else if(m_footStepDrawn && m_footTimer.ticksElapsed() >= getStepDuration() / 4 ) {
m_footStep++;
m_walkAnimationPhase = 1 + (m_footStep % footAnimPhases);
m_footStepDrawn = false;
@ -380,22 +405,26 @@ void Creature::nextWalkUpdate()
m_walkUpdateEvent = g_dispatcher.scheduleEvent([self] {
self->m_walkUpdateEvent = nullptr;
self->nextWalkUpdate();
}, m_walkAnimationInterval / 32);
}, getStepDuration() / 32);
}
}
void Creature::updateWalk()
{
float walkTicksPerPixel = m_walkAnimationInterval / 32;
int stepDuration = getStepDuration();
float walkTicksPerPixel = stepDuration / 32;
int totalPixelsWalked = std::min(m_walkTimer.ticksElapsed() / walkTicksPerPixel, 32.0f);
// needed for paralyze effect
m_walkedPixels = std::max(m_walkedPixels, totalPixelsWalked);
// update walk animation and offsets
updateWalkAnimation(totalPixelsWalked);
updateWalkOffset(totalPixelsWalked);
updateWalkOffset(m_walkedPixels);
updateWalkingTile();
// terminate walk
if(m_walking && m_walkTimer.ticksElapsed() >= m_walkInterval)
if(m_walking && m_walkTimer.ticksElapsed() >= stepDuration)
terminateWalk();
}
@ -419,6 +448,7 @@ void Creature::terminateWalk()
}
m_walking = false;
m_walkedPixels = 0;
}
void Creature::setName(const std::string& name)
@ -472,6 +502,15 @@ void Creature::setOutfit(const Outfit& outfit)
m_outfit = outfit;
}
void Creature::setSpeed(uint16 speed)
{
m_speed = speed;
// speed can change while walking (utani hur, paralyze, etc..)
if(m_walking)
nextWalkUpdate();
}
void Creature::setSkull(uint8 skull)
{
m_skull = skull;
@ -556,6 +595,27 @@ Point Creature::getDrawOffset()
return drawOffset;
}
int Creature::getStepDuration()
{
int groundSpeed = 0;
const TilePtr& tile = getTile();
if(tile)
groundSpeed = tile->getGroundSpeed();
int interval = 1000;
if(groundSpeed > 0 && m_speed > 0)
interval = (1000 * groundSpeed) / m_speed;
if(g_game.getClientVersion() >= 900)
interval = (interval / g_game.getServerBeat()) * g_game.getServerBeat();
if(m_lastStepDirection == Otc::NorthWest || m_lastStepDirection == Otc::NorthEast ||
m_lastStepDirection == Otc::SouthWest || m_lastStepDirection == Otc::SouthEast)
interval *= 3;
return interval;
}
const ThingTypePtr& Creature::getThingType()
{
return g_things.getThingType(m_outfit.getId(), m_outfit.getCategory());

View File

@ -55,7 +55,7 @@ public:
void setDirection(Otc::Direction direction);
void setOutfit(const Outfit& outfit);
void setLight(const Light& light) { m_light = light; }
void setSpeed(uint16 speed) { m_speed = speed; }
void setSpeed(uint16 speed);
void setSkull(uint8 skull);
void setShield(uint8 shield);
void setEmblem(uint8 emblem);
@ -63,7 +63,6 @@ public:
void setShieldTexture(const std::string& filename, bool blink);
void setEmblemTexture(const std::string& filename);
void setPassable(bool passable) { m_passable = passable; }
void setRemoved(bool removed) { m_removed = removed; }
void addTimedSquare(uint8 color);
void removeTimedSquare() { m_showTimedSquare = false; }
@ -83,6 +82,7 @@ public:
uint8 getEmblem() { return m_emblem; }
bool isPassable() { return m_passable; }
Point getDrawOffset();
int getStepDuration();
Point getWalkOffset() { return m_walkOffset; }
void updateShield();
@ -100,8 +100,8 @@ public:
const ThingTypePtr& getThingType();
ThingType *rawGetThingType();
protected:
virtual void onMove(const Position& newPos, const Position& oldPos);
virtual void onAppear();
virtual void onDisappear();
protected:
virtual void updateWalkAnimation(int totalPixelsWalked);
@ -131,23 +131,25 @@ protected:
Color m_staticSquareColor;
stdext::boolean<false> m_showTimedSquare;
stdext::boolean<false> m_showStaticSquare;
stdext::boolean<false> m_removed;
stdext::boolean<true> m_removed;
CachedText m_nameCache;
Color m_informationColor;
// walk related
int m_walkAnimationPhase;
int m_walkedPixels;
uint m_footStep;
Timer m_walkTimer;
Timer m_footTimer;
TilePtr m_walkingTile;
int m_walkInterval;
int m_walkAnimationInterval;
stdext::boolean<false> m_walking;
stdext::boolean<false> m_footStepDrawn;
ScheduledEventPtr m_walkUpdateEvent;
EventPtr m_disappearEvent;
Point m_walkOffset;
Otc::Direction m_walkTurnDirection;
Otc::Direction m_lastStepDirection;
Position m_oldPosition;
};
// @bindclass

View File

@ -247,26 +247,6 @@ void Game::processInventoryChange(int slot, const ItemPtr& item)
m_localPlayer->setInventoryItem((Otc::InventorySlot)slot, item);
}
void Game::processCreatureMove(const CreaturePtr& creature, const Position& oldPos, const Position& newPos)
{
// animate walk
creature->walk(oldPos, newPos);
g_lua.callGlobalField("g_game", "onCreatureMove", creature, oldPos, newPos);
}
void Game::processCreatureTeleport(const CreaturePtr& creature)
{
// stop walking on creature teleports
creature->stopWalk();
// locks the walk for a while when teleporting
if(creature == m_localPlayer)
m_localPlayer->lockWalk();
g_lua.callGlobalField("g_game", "onCreatureTeleport", creature);
}
void Game::processChannelList(const std::vector<std::tuple<int, std::string> >& channelList)
{
g_lua.callGlobalField("g_game", "onChannelList", channelList);
@ -552,9 +532,11 @@ void Game::autoWalk(const std::vector<Otc::Direction>& dirs)
cancelFollow();
Otc::Direction direction = dirs.front();
if(m_localPlayer->canWalk(direction)) {
TilePtr toTile = g_map.getTile(m_localPlayer->getPosition().translatedToDirection(direction));
if(toTile && toTile->isWalkable() && !m_localPlayer->isAutoWalking())
m_localPlayer->preWalk(direction);
}
m_protocolGame->sendAutoWalk(dirs);

View File

@ -65,8 +65,6 @@ protected:
void processGMActions(const std::vector<uint8>& actions);
void processInventoryChange(int slot, const ItemPtr& item);
void processCreatureMove(const CreaturePtr& creature, const Position& oldPos, const Position& newPos);
void processCreatureTeleport(const CreaturePtr& creature);
void processAttackCancel(uint seq);
void processWalkCancel(Otc::Direction direction);

View File

@ -29,7 +29,6 @@
LocalPlayer::LocalPlayer()
{
m_preWalking = false;
m_walkLocked = false;
m_lastPrewalkDone = true;
m_autoWalking = false;
m_known = false;
@ -37,7 +36,7 @@ LocalPlayer::LocalPlayer()
m_states = 0;
m_vocation = 0;
m_walkLockInterval = 0;
m_walkLockExpiration = 0;
m_skillsLevel.fill(-1);
m_skillsLevelPercent.fill(-1);
@ -58,33 +57,29 @@ LocalPlayer::LocalPlayer()
void LocalPlayer::lockWalk(int millis)
{
// prevents double locks
if(m_walkLocked)
return;
m_walkLocked = true;
m_walkLockTimer.restart();
m_walkLockInterval = millis;
m_walkLockExpiration = std::max(m_walkLockExpiration, (ticks_t) g_clock.millis() + millis);
}
bool LocalPlayer::canWalk(Otc::Direction direction)
{
// prewalk has a timeout, because for some reason that I don't know yet the server sometimes doesn't answer the prewalk
bool prewalkTimeouted = m_walking && m_preWalking && m_walkTimer.ticksElapsed() >= m_walkInterval + PREWALK_TIMEOUT;
// cannot walk while already walking
if(m_walking && !prewalkTimeouted)
// cannot walk while locked
if(m_walkLockExpiration != 0 && g_clock.millis() < m_walkLockExpiration)
return false;
// last walk is not done yet
if(m_walkTimer.ticksElapsed() < getStepDuration())
return false;
// prewalk has a timeout, because for some reason that I don't know yet the server sometimes doesn't answer the prewalk
bool prewalkTimeouted = m_walking && m_preWalking && m_walkTimer.ticksElapsed() >= getStepDuration() + PREWALK_TIMEOUT;
// avoid doing more walks than wanted when receiving a lot of walks from server
if(!m_lastPrewalkDone && m_preWalking && !prewalkTimeouted)
return false;
// cannot walk while locked
if(m_walkLocked && m_walkLockTimer.ticksElapsed() <= m_walkLockInterval)
// cannot walk while already walking
if(m_walking && !prewalkTimeouted)
return false;
else
m_walkLocked = false;
return true;
}
@ -93,6 +88,16 @@ void LocalPlayer::walk(const Position& oldPos, const Position& newPos)
{
// a prewalk was going on
if(m_preWalking) {
if(m_waitingWalkPong) {
if(newPos == m_lastPrewalkDestionation) {
m_lastWalkPings.push_back(m_walkPingTimer.ticksElapsed());
if(m_lastWalkPings.size() > 10)
m_lastWalkPings.pop_front();
}
m_waitingWalkPong = false;
}
// switch to normal walking
m_preWalking = false;
m_lastPrewalkDone = true;
@ -115,13 +120,18 @@ void LocalPlayer::walk(const Position& oldPos, const Position& newPos)
void LocalPlayer::preWalk(Otc::Direction direction)
{
// start walking to direction
Position newPos = m_position.translatedToDirection(direction);
// avoid reanimating prewalks
if(m_preWalking && m_lastPrewalkDestionation == newPos)
return;
m_preWalking = true;
if(m_autoWalkEndEvent)
m_autoWalkEndEvent->cancel();
// start walking to direction
m_lastPrewalkDone = false;
m_lastPrewalkDestionation = newPos;
Creature::walk(m_position, newPos);
@ -134,6 +144,7 @@ void LocalPlayer::cancelWalk(Otc::Direction direction)
stopWalk();
m_lastPrewalkDone = true;
m_waitingWalkPong = false;
// turn to the cancel direction
if(direction != Otc::InvalidDirection)
@ -167,7 +178,8 @@ void LocalPlayer::updateWalkOffset(int totalPixelsWalked)
void LocalPlayer::updateWalk()
{
float walkTicksPerPixel = m_walkAnimationInterval / 32.0f;
int stepDuration = getStepDuration();
float walkTicksPerPixel = stepDuration / 32.0f;
int totalPixelsWalked = std::min(m_walkTimer.ticksElapsed() / walkTicksPerPixel, 32.0f);
// update walk animation and offsets
@ -176,7 +188,7 @@ void LocalPlayer::updateWalk()
updateWalkingTile();
// terminate walk only when client and server side walk are complated
if(m_walking && !m_preWalking && m_walkTimer.ticksElapsed() >= m_walkInterval)
if(m_walking && !m_preWalking && m_walkTimer.ticksElapsed() >= stepDuration)
terminateWalk();
}
@ -196,6 +208,15 @@ void LocalPlayer::terminateWalk()
}
}
void LocalPlayer::onAppear()
{
Creature::onAppear();
// on teleports lock the walk
if(!m_oldPosition.isInRange(m_position,1,1))
lockWalk();
}
void LocalPlayer::setStates(int states)
{
if(m_states != states) {
@ -351,3 +372,14 @@ void LocalPlayer::setPremium(bool premium)
callLuaField("onPremiumChange", premium);
}
}
double LocalPlayer::getWalkPing()
{
if(m_lastWalkPings.empty())
return 9999;
double sum = 0;
for(int p : m_lastWalkPings)
sum += p;
return sum / (double)m_lastWalkPings.size();
}

View File

@ -35,7 +35,7 @@ class LocalPlayer : public Player
public:
LocalPlayer();
void unlockWalk() { m_walkLocked = false; }
void unlockWalk() { m_walkLockExpiration = 0; }
void lockWalk(int millis = 250);
bool canWalk(Otc::Direction direction);
@ -58,6 +58,7 @@ public:
int getSkillLevel(Otc::Skill skill) { return m_skillsLevel[skill]; }
int getSkillLevelPercent(Otc::Skill skill) { return m_skillsLevelPercent[skill]; }
int getVocation() { return m_vocation; }
double getWalkPing();
double getHealth() { return m_health; }
double getMaxHealth() { return m_maxHealth; }
double getFreeCapacity() { return m_freeCapacity; }
@ -80,6 +81,8 @@ public:
LocalPlayerPtr asLocalPlayer() { return static_self_cast<LocalPlayer>(); }
bool isLocalPlayer() { return true; }
virtual void onAppear();
protected:
void walk(const Position& oldPos, const Position& newPos);
void preWalk(Otc::Direction direction);
@ -97,13 +100,14 @@ private:
// walk related
bool m_preWalking;
bool m_lastPrewalkDone;
bool m_walkLocked;
bool m_autoWalking;
bool m_premium;
Position m_lastPrewalkDestionation;
Timer m_walkLockTimer;
ItemPtr m_inventoryItems[Otc::LastInventorySlot];
ScheduledEventPtr m_autoWalkEndEvent;
stdext::boolean<false> m_waitingWalkPong;
Timer m_walkPingTimer;
std::deque<int> m_lastWalkPings;
std::array<int, Otc::LastSkill> m_skillsLevel;
std::array<int, Otc::LastSkill> m_skillsLevelPercent;
@ -111,7 +115,7 @@ private:
bool m_known;
int m_states;
int m_vocation;
int m_walkLockInterval;
ticks_t m_walkLockExpiration;
double m_health;
double m_maxHealth;

View File

@ -78,7 +78,6 @@ void Map::cleanDynamicThings()
for(const auto& pair : m_knownCreatures) {
const CreaturePtr& creature = pair.second;
removeThing(creature);
creature->setRemoved(true);
}
m_knownCreatures.clear();
@ -99,50 +98,35 @@ void Map::addThing(const ThingPtr& thing, const Position& pos, int stackPos)
if(!thing)
return;
TilePtr tile = getOrCreateTile(pos);
Position oldPos = thing->getPosition();
if(thing->isItem() || thing->isCreature() || thing->isEffect()) {
const TilePtr& tile = getOrCreateTile(pos);
tile->addThing(thing, stackPos);
} else if(thing->isMissile()) {
} else {
if(thing->isMissile()) {
m_floorMissiles[pos.z].push_back(thing->static_self_cast<Missile>());
thing->onAppear();
} else if(thing->isAnimatedText()) {
m_animatedTexts.push_back(thing->static_self_cast<AnimatedText>());
AnimatedTextPtr animatedText = thing->static_self_cast<AnimatedText>();
m_animatedTexts.push_back(animatedText);
} else if(thing->isStaticText()) {
StaticTextPtr staticText = thing->static_self_cast<StaticText>();
bool mustAdd = true;
for(auto it = m_staticTexts.begin(), end = m_staticTexts.end(); it != end; ++it) {
StaticTextPtr cStaticText = *it;
if(cStaticText->getPosition() == pos) {
for(auto other : m_staticTexts) {
// try to combine messages
if(cStaticText->addMessage(staticText->getName(), staticText->getMessageMode(), staticText->getFirstMessage())) {
if(other->getPosition() == pos && other->addMessage(staticText->getName(), staticText->getMessageMode(), staticText->getFirstMessage())) {
mustAdd = false;
break;
} else {
// must add another message and rearrenge current
}
}
}
if(mustAdd)
if(mustAdd) {
m_staticTexts.push_back(staticText);
staticText->onAppear();
}
}
if(!thing->isCreature())
thing->onAppear();
thing->setPosition(pos);
if(thing->isCreature()) {
CreaturePtr creature = thing->static_self_cast<Creature>();
if(oldPos != pos) {
if(oldPos.isInRange(pos,1,1))
g_game.processCreatureMove(creature, oldPos, pos);
else
g_game.processCreatureTeleport(creature);
}
thing->onAppear();
}
notificateTileUpdateToMapViews(pos);
@ -289,8 +273,6 @@ void Map::removeCreatureById(uint32 id)
auto it = m_knownCreatures.find(id);
if(it != m_knownCreatures.end())
it->second->setRemoved(true);
m_knownCreatures.erase(it);
}
@ -318,12 +300,10 @@ void Map::setCentralPosition(const Position& centralPosition)
Position oldPos = localPlayer->getPosition();
Position pos = m_centralPosition;
localPlayer->setPosition(pos);
if(oldPos != pos) {
if(oldPos.isInRange(pos,1,1))
g_game.processCreatureMove(localPlayer, oldPos, pos);
else
g_game.processCreatureTeleport(localPlayer);
localPlayer->onDisappear();
localPlayer->setPosition(pos);
localPlayer->onAppear();
}
});

View File

@ -506,10 +506,6 @@ void ProtocolGame::parseCreatureMove(const InputMessagePtr& msg)
g_map.removeThing(thing);
g_map.addThing(thing, newPos, stackPos);
//CreaturePtr creature = thing->static_self_cast<Creature>();
//Position oldPos = thing->getPosition();
//creature->onMove(newPos, oldPos);
}
void ProtocolGame::parseOpenContainer(const InputMessagePtr& msg)

View File

@ -35,6 +35,9 @@ Thing::Thing() :
void Thing::setPosition(const Position& position)
{
if(m_position == position)
return;
Position oldPos = m_position;
m_position = position;
onPositionChange(position, oldPos);

View File

@ -118,13 +118,11 @@ public:
bool isMarketable() { return rawGetThingType()->isMarketable(); }
MarketData getMarketData() { return rawGetThingType()->getMarketData(); }
protected:
virtual void onPositionChange(const Position& newPos, const Position& oldPos) { }
virtual void onAppear() { }
virtual void onDisappear() { }
friend class Map;
protected:
Position m_position;
uint16 m_datId;
};

View File

@ -101,8 +101,6 @@ void Tile::draw(const Point& dest, float scaleFactor, int drawFlags)
if(drawFlags & Otc::DrawCreatures) {
if(animate) {
for(const CreaturePtr& creature : m_walkingCreatures) {
if(creature->isRemoved())
continue;
creature->draw(Point(dest.x + ((creature->getPosition().x - m_position.x)*Otc::TILE_PIXELS - m_drawElevation)*scaleFactor,
dest.y + ((creature->getPosition().y - m_position.y)*Otc::TILE_PIXELS - m_drawElevation)*scaleFactor), scaleFactor, animate);
@ -159,9 +157,7 @@ void Tile::addThing(const ThingPtr& thing, int stackPos)
if(thing->isEffect()) {
m_effects.push_back(thing->static_self_cast<Effect>());
return;
}
} else {
// the items stackpos follows this order:
// 0 - ground
// 1 - ground borders
@ -197,6 +193,10 @@ void Tile::addThing(const ThingPtr& thing, int stackPos)
*/
}
thing->setPosition(m_position);
thing->onAppear();
}
bool Tile::removeThing(ThingPtr thing)
{
if(!thing)
@ -219,6 +219,8 @@ bool Tile::removeThing(ThingPtr thing)
}
}
thing->onDisappear();
return removed;
}