diff --git a/source/entities/Creature.cpp b/source/entities/Creature.cpp index b366fe8a9..411bb8f72 100755 --- a/source/entities/Creature.cpp +++ b/source/entities/Creature.cpp @@ -968,6 +968,14 @@ void Creature::doUpkeep() return; } + // The ground the creature is standing on, or the ground it is walking towards, may have + // stopped being walkable since it last decided anything. Selling or destroying a bridge + // is what does that: the tiles it covered go back to being lava or water, and nothing + // told the creatures using them. Both checks have to happen before the creature acts, so + // that it acts on where it can actually go. + checkStandsOnWalkableTile(); + checkWalkPathIsStillValid(); + // Check to see if we have earned enough experience to level up. checkLevelUp(); @@ -2480,6 +2488,86 @@ bool Creature::setDestination(Tile* tile) return true; } +void Creature::checkWalkPathIsStillValid() +{ + if(mWalkQueue.empty()) + return; + + bool isPathBlocked = false; + for(const Ogre::Vector2& step : mWalkQueue) + { + Tile* tile = getGameMap()->getTile(Helper::round(step.x), Helper::round(step.y)); + if(canGoThroughTile(tile)) + continue; + + // A building that will not let the creature through, a closed door or a prison, is + // another matter: those are meant to stop it where it is, and it was already + // walking towards one knowing what it would find. Only the ground going away from + // under the path is unexpected. + if((tile != nullptr) && (tile->getCoveringBuilding() != nullptr)) + continue; + + isPathBlocked = true; + break; + } + + if(!isPathBlocked) + return; + + const Ogre::Vector2& lastStep = mWalkQueue.back(); + Tile* destination = getGameMap()->getTile(Helper::round(lastStep.x), Helper::round(lastStep.y)); + + OD_LOG_INF("creature=" + getName() + " cannot walk its path anymore, destination=" + + Tile::displayAsString(destination)); + + // Stop where we are rather than walk into what is now lava or water. If there is no + // other way to the destination, the action that started the walk gets to decide what + // to do instead, exactly as it would have done had the creature arrived. + clearDestinations(EntityAnimation::idle_anim, true, true); + + if(!canGoThroughTile(destination)) + return; + + std::list result = getGameMap()->path(this, destination); + if(result.size() <= 1) + return; + + std::vector path; + tileToVector2(result, path, true, 0.0); + setWalkPath(EntityAnimation::walk_anim, EntityAnimation::idle_anim, true, true, path, true); +} + +void Creature::checkStandsOnWalkableTile() +{ + Tile* myTile = getPositionTile(); + if(canGoThroughTile(myTile)) + return; + + // Prisons, fenced rooms and locked doors give a null speed on purpose, to hold the + // creature where it is. What we are after here is the ground going away from under + // it, which leaves the tile covered by nothing at all. + if(myTile->getCoveringBuilding() != nullptr) + return; + + // And a bridge is the only thing that can be taken away from under a creature, so + // water and lava are the only ground it can be left standing on. Anything else that + // gives a null speed is left alone rather than guessed at. + if((myTile->getTileVisual() != TileVisual::waterGround) && + (myTile->getTileVisual() != TileVisual::lavaGround)) + { + return; + } + + // The creature cannot go through this tile, or canGoThroughTile above would have + // accepted it, so it cannot live in what it is now standing in. It does not get + // fished out or teleported ashore: it drowns, or burns. + OD_LOG_INF("creature=" + getName() + " lost the ground under its feet on tile=" + + Tile::displayAsString(myTile) + " and dies"); + + clearDestinations(EntityAnimation::idle_anim, true, true); + takeDamage(nullptr, getHP(), 0.0, 0.0, 0.0, myTile, false); +} + bool Creature::wanderRandomly(const std::string& animationState) { // We pick randomly a visible tile far away (at the end of visible tiles) diff --git a/source/entities/Creature.h b/source/entities/Creature.h index 4388de741..4e9b141ce 100755 --- a/source/entities/Creature.h +++ b/source/entities/Creature.h @@ -218,6 +218,17 @@ class Creature: public MovableGameEntity, public Subject bool setDestination(Tile* tile); + //! \brief Server side. Checks that the creature can still walk the path it is following and, + //! if it cannot, looks for another way to the same destination. Tiles the creature could walk + //! on when the path was computed can stop being walkable while it is on its way, which is what + //! happens to a bridge over lava or water that is sold or destroyed. + void checkWalkPathIsStillValid(); + + //! \brief Server side. Checks that the creature is standing somewhere it can stand and, if + //! the ground has turned into water or lava it cannot live in, kills it. A creature on a + //! tile it cannot walk on has a null move speed, so it could never leave by itself anyway. + void checkStandsOnWalkableTile(); + //! \brief Picks a destination far away in the visible tiles and goes there //! Returns true if a valid Tile was found. The creature will go there //! Returns false if no reachable Tile was found diff --git a/source/network/ODClient.cpp b/source/network/ODClient.cpp index e7cdfc60d..f30e36895 100755 --- a/source/network/ODClient.cpp +++ b/source/network/ODClient.cpp @@ -608,8 +608,13 @@ bool ODClient::processMessage(ServerNotificationType cmd, ODPacket& packetReceiv OD_ASSERT_TRUE(packetReceived >> objName >> dest); MovableGameEntity *obj = gameMap->getAnimatedObject(objName); + if (obj == nullptr) + { + OD_LOG_ERR("Server told us to teleport unknown entity name=" + objName); + break; + } obj->setPosition(dest); - + break; } case ServerNotificationType::entitySlapped: