diff --git a/apps/openmw-mp/Script/Functions/Actors.cpp b/apps/openmw-mp/Script/Functions/Actors.cpp index b0040c625..1a616dec5 100644 --- a/apps/openmw-mp/Script/Functions/Actors.cpp +++ b/apps/openmw-mp/Script/Functions/Actors.cpp @@ -212,6 +212,11 @@ const char *ActorFunctions::GetActorKillerName(unsigned int index) noexcept return readActorList->baseActors.at(index).killer.name.c_str(); } +unsigned int ActorFunctions::GetActorDeathState(unsigned int index) noexcept +{ + return readActorList->baseActors.at(index).deathState; +} + bool ActorFunctions::DoesActorHavePosition(unsigned int index) noexcept { return readActorList->baseActors.at(index).hasPositionData; @@ -316,6 +321,16 @@ void ActorFunctions::SetActorSound(const char* sound) noexcept tempActor.sound = sound; } +void ActorFunctions::SetActorDeathState(unsigned int deathState) noexcept +{ + tempActor.deathState = deathState; +} + +void ActorFunctions::SetActorDeathInstant(bool isInstant) noexcept +{ + tempActor.isInstantDeath = isInstant; +} + void ActorFunctions::SetActorAIAction(unsigned int action) noexcept { tempActor.aiAction = action; @@ -483,6 +498,25 @@ void ActorFunctions::SendActorSpeech(bool sendToOtherVisitors, bool skipAttached } } +void ActorFunctions::SendActorDeath(bool sendToOtherVisitors, bool skipAttachedPlayer) noexcept +{ + mwmp::ActorPacket *actorPacket = mwmp::Networking::get().getActorPacketController()->GetPacket(ID_ACTOR_DEATH); + actorPacket->setActorList(&writeActorList); + + if (!skipAttachedPlayer) + actorPacket->Send(writeActorList.guid); + + if (sendToOtherVisitors) + { + Cell *serverCell = CellController::get()->getCell(&writeActorList.cell); + + if (serverCell != nullptr) + { + serverCell->sendToLoaded(actorPacket, &writeActorList); + } + } +} + void ActorFunctions::SendActorAI(bool sendToOtherVisitors, bool skipAttachedPlayer) noexcept { mwmp::ActorPacket *actorPacket = mwmp::Networking::get().getActorPacketController()->GetPacket(ID_ACTOR_AI); diff --git a/apps/openmw-mp/Script/Functions/Actors.hpp b/apps/openmw-mp/Script/Functions/Actors.hpp index a6035e9da..1f886ca7b 100644 --- a/apps/openmw-mp/Script/Functions/Actors.hpp +++ b/apps/openmw-mp/Script/Functions/Actors.hpp @@ -46,6 +46,7 @@ {"GetActorKillerRefNum", ActorFunctions::GetActorKillerRefNum},\ {"GetActorKillerMpNum", ActorFunctions::GetActorKillerMpNum},\ {"GetActorKillerName", ActorFunctions::GetActorKillerName},\ + {"GetActorDeathState", ActorFunctions::GetActorDeathState},\ \ {"DoesActorHavePosition", ActorFunctions::DoesActorHavePosition},\ {"DoesActorHaveStatsDynamic", ActorFunctions::DoesActorHaveStatsDynamic},\ @@ -71,6 +72,8 @@ {"SetActorFatigueCurrent", ActorFunctions::SetActorFatigueCurrent},\ {"SetActorFatigueModified", ActorFunctions::SetActorFatigueModified},\ \ + {"SetActorDeathState", ActorFunctions::SetActorDeathState},\ + {"SetActorDeathInstant", ActorFunctions::SetActorDeathInstant},\ {"SetActorSound", ActorFunctions::SetActorSound},\ \ {"SetActorAIAction", ActorFunctions::SetActorAIAction},\ @@ -92,6 +95,7 @@ {"SendActorStatsDynamic", ActorFunctions::SendActorStatsDynamic},\ {"SendActorEquipment", ActorFunctions::SendActorEquipment},\ {"SendActorSpeech", ActorFunctions::SendActorSpeech},\ + {"SendActorDeath", ActorFunctions::SendActorDeath},\ {"SendActorAI", ActorFunctions::SendActorAI},\ {"SendActorCellChange", ActorFunctions::SendActorCellChange},\ \ @@ -402,6 +406,14 @@ public: */ static const char *GetActorKillerName(unsigned int index) noexcept; + /** + * \brief Get the deathState of the actor at a certain index in the read actor list. + * + * \param index The index of the actor. + * \return The deathState. + */ + static unsigned int GetActorDeathState(unsigned int index) noexcept; + /** * \brief Check whether there is any positional data for the actor at a certain index in * the read actor list. @@ -581,6 +593,23 @@ public: */ static void SetActorSound(const char* sound) noexcept; + /** + * \brief Set the deathState of the temporary actor stored on the server. + * + * \param deathState The deathState. + * \return void + */ + static void SetActorDeathState(unsigned int deathState) noexcept; + + /** + * \brief Set whether the death of the temporary actor stored on the server should + * be instant or not. + * + * \param isInstant Whether the death should be instant. + * \return void + */ + static void SetActorDeathInstant(bool isInstant) noexcept; + /** * \brief Set the AI action of the temporary actor stored on the server. * @@ -742,6 +771,17 @@ public: */ static void SendActorSpeech(bool sendToOtherVisitors, bool skipAttachedPlayer) noexcept; + /** + * \brief Send an ActorDeath packet. + * + * \param sendToOtherVisitors Whether this packet should be sent to cell visitors other + * than the player attached to the packet (false by default). + * \param skipAttachedPlayer Whether the packet should skip being sent to the player attached + * to the packet (false by default). + * \return void + */ + static void SendActorDeath(bool sendToOtherVisitors, bool skipAttachedPlayer) noexcept; + /** * \brief Send an ActorAI packet. * diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index ebba7f81a..b96b2d516 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -813,11 +813,18 @@ void CharacterController::playRandomDeath(float startpoint) Start tes3mp change (major) If this is a DedicatedPlayer, use the deathState received from their PlayerDeath packet + + If this is a DedicatedActor, use the deathState from their ActorDeath packet */ if (mwmp::PlayerList::isDedicatedPlayer(mPtr)) { mDeathState = static_cast(mwmp::PlayerList::getPlayer(mPtr)->deathState); } + else if (mwmp::Main::get().getCellController()->hasQueuedDeathState(mPtr)) + { + mDeathState = static_cast(mwmp::Main::get().getCellController()->getQueuedDeathState(mPtr)); + mwmp::Main::get().getCellController()->clearQueuedDeathState(mPtr); + } else if(mHitState == CharState_SwimKnockDown && mAnimation->hasAnimation("swimdeathknockdown")) /* End of tes3mp change (major) @@ -849,12 +856,19 @@ void CharacterController::playRandomDeath(float startpoint) /* Start of tes3mp addition - Send a PlayerDeath packet with the decided-upon death animation + If this is the local player, send a PlayerDeath packet with the decided-upon + death animation + + If this is a local actor, send an ActorDeath packet with the animation */ if (mPtr == getPlayer()) { mwmp::Main::get().getLocalPlayer()->sendDeath(mDeathState); } + else if (!mPtr.getClass().getCreatureStats(mPtr).isDeathAnimationFinished() && mwmp::Main::get().getCellController()->isLocalActor(mPtr)) + { + mwmp::Main::get().getCellController()->getLocalActor(mPtr)->sendDeath(mDeathState); + } /* End of tes3mp addition */ diff --git a/apps/openmw/mwmp/Cell.cpp b/apps/openmw/mwmp/Cell.cpp index bf7070365..9a7142e76 100644 --- a/apps/openmw/mwmp/Cell.cpp +++ b/apps/openmw/mwmp/Cell.cpp @@ -203,14 +203,37 @@ void Cell::readStatsDynamic(ActorList& actorList) // That way, if this actor is about to become a LocalActor, initial data about it // received from the server still gets set actor->setStatsDynamic(); + } + } + } +} - // Actors loaded as dead from the server need special handling to skip their death animations - // and disable their collision - if (actor->creatureStats.mDynamic[0].mCurrent < 1) - { - actor->getPtr().getClass().getCreatureStats(actor->getPtr()).setDeathAnimationFinished(true); - MWBase::Environment::get().getWorld()->enableActorCollision(actor->getPtr(), false); - } +void Cell::readDeath(ActorList& actorList) +{ + initializeDedicatedActors(actorList); + + if (dedicatedActors.empty()) return; + + for (const auto &baseActor : actorList.baseActors) + { + std::string mapIndex = Main::get().getCellController()->generateMapIndex(baseActor); + + if (dedicatedActors.count(mapIndex) > 0) + { + DedicatedActor *actor = dedicatedActors[mapIndex]; + actor->creatureStats.mDead = true; + actor->creatureStats.mDynamic[0].mCurrent = 0; + + Main::get().getCellController()->setQueuedDeathState(actor->getPtr(), baseActor.deathState); + + LOG_MESSAGE_SIMPLE(TimedLog::LOG_INFO, "In Cell::readDeath, deathState is %i and isInstantDeath is %s", + baseActor.deathState, + baseActor.isInstantDeath ? "true" : "false"); + + if (baseActor.isInstantDeath) + { + actor->getPtr().getClass().getCreatureStats(actor->getPtr()).setDeathAnimationFinished(true); + MWBase::Environment::get().getWorld()->enableActorCollision(actor->getPtr(), false); } } } @@ -419,11 +442,6 @@ void Cell::initializeLocalActor(const MWWorld::Ptr& ptr) actor->cell = *store->getCell(); actor->setPtr(ptr); - // Note that this actor was already dead when we were given control over it, - // to avoid sending an ActorDeath packet - if (ptr.getClass().getCreatureStats(ptr).isDead()) - actor->wasDead = true; - localActors[mapIndex] = actor; Main::get().getCellController()->setLocalActorRecord(mapIndex, getDescription()); diff --git a/apps/openmw/mwmp/Cell.hpp b/apps/openmw/mwmp/Cell.hpp index d0d6d0ccf..73f633513 100644 --- a/apps/openmw/mwmp/Cell.hpp +++ b/apps/openmw/mwmp/Cell.hpp @@ -22,6 +22,7 @@ namespace mwmp void readAnimFlags(ActorList& actorList); void readAnimPlay(ActorList& actorList); void readStatsDynamic(ActorList& actorList); + void readDeath(ActorList& actorList); void readEquipment(ActorList& actorList); void readSpeech(ActorList& actorList); void readAi(ActorList& actorList); diff --git a/apps/openmw/mwmp/CellController.cpp b/apps/openmw/mwmp/CellController.cpp index af2aab6b2..e55cf808a 100644 --- a/apps/openmw/mwmp/CellController.cpp +++ b/apps/openmw/mwmp/CellController.cpp @@ -17,6 +17,7 @@ using namespace mwmp; std::map CellController::cellsInitialized; std::map CellController::localActorsToCells; std::map CellController::dedicatedActorsToCells; +std::map CellController::queuedDeathStates; mwmp::CellController::CellController() { @@ -134,6 +135,17 @@ void CellController::readStatsDynamic(ActorList& actorList) cellsInitialized[mapIndex]->readStatsDynamic(actorList); } +void CellController::readDeath(ActorList& actorList) +{ + std::string mapIndex = actorList.cell.getDescription(); + + initializeCell(actorList.cell); + + // If this now exists, send it the data + if (cellsInitialized.count(mapIndex) > 0) + cellsInitialized[mapIndex]->readDeath(actorList); +} + void CellController::readEquipment(ActorList& actorList) { std::string mapIndex = actorList.cell.getDescription(); @@ -200,6 +212,34 @@ void CellController::readCellChange(ActorList& actorList) cellsInitialized[mapIndex]->readCellChange(actorList); } +bool CellController::hasQueuedDeathState(MWWorld::Ptr ptr) +{ + std::string actorIndex = generateMapIndex(ptr); + + return queuedDeathStates.count(actorIndex) > 0; +} + +unsigned int CellController::getQueuedDeathState(MWWorld::Ptr ptr) +{ + std::string actorIndex = generateMapIndex(ptr); + + return queuedDeathStates[actorIndex]; +} + +void CellController::clearQueuedDeathState(MWWorld::Ptr ptr) +{ + std::string actorIndex = generateMapIndex(ptr); + + queuedDeathStates.erase(actorIndex); +} + +void CellController::setQueuedDeathState(MWWorld::Ptr ptr, unsigned int deathState) +{ + std::string actorIndex = generateMapIndex(ptr); + + queuedDeathStates[actorIndex] = deathState; +} + void CellController::setLocalActorRecord(std::string actorIndex, std::string cellIndex) { localActorsToCells[actorIndex] = cellIndex; diff --git a/apps/openmw/mwmp/CellController.hpp b/apps/openmw/mwmp/CellController.hpp index ac89e7414..6a9093a30 100644 --- a/apps/openmw/mwmp/CellController.hpp +++ b/apps/openmw/mwmp/CellController.hpp @@ -25,6 +25,7 @@ namespace mwmp void readAnimFlags(mwmp::ActorList& actorList); void readAnimPlay(mwmp::ActorList& actorList); void readStatsDynamic(mwmp::ActorList& actorList); + void readDeath(mwmp::ActorList& actorList); void readEquipment(mwmp::ActorList& actorList); void readSpeech(mwmp::ActorList& actorList); void readAi(mwmp::ActorList& actorList); @@ -32,6 +33,11 @@ namespace mwmp void readCast(mwmp::ActorList& actorList); void readCellChange(mwmp::ActorList& actorList); + bool hasQueuedDeathState(MWWorld::Ptr ptr); + unsigned int getQueuedDeathState(MWWorld::Ptr ptr); + void clearQueuedDeathState(MWWorld::Ptr ptr); + void setQueuedDeathState(MWWorld::Ptr ptr, unsigned int deathState); + void setLocalActorRecord(std::string actorIndex, std::string cellIndex); void removeLocalActorRecord(std::string actorIndex); @@ -68,6 +74,7 @@ namespace mwmp static std::map cellsInitialized; static std::map localActorsToCells; static std::map dedicatedActorsToCells; + static std::map queuedDeathStates; }; } diff --git a/apps/openmw/mwmp/LocalActor.cpp b/apps/openmw/mwmp/LocalActor.cpp index 61133bd8f..19202001c 100644 --- a/apps/openmw/mwmp/LocalActor.cpp +++ b/apps/openmw/mwmp/LocalActor.cpp @@ -31,7 +31,6 @@ LocalActor::LocalActor() wasForceJumping = false; wasForceMoveJumping = false; wasFlying = false; - wasDead = false; attack.type = Attack::MELEE; attack.shouldSend = false; @@ -194,21 +193,6 @@ void LocalActor::updateStatsDynamic(bool forceUpdate) creatureStats.mDead = ptrCreatureStats->isDead(); mwmp::Main::get().getNetworking()->getActorList()->addStatsDynamicActor(*this); - - if (creatureStats.mDead && !wasDead) - { - if (MechanicsHelper::isEmptyTarget(killer)) - killer = MechanicsHelper::getTarget(ptr); - - LOG_MESSAGE_SIMPLE(TimedLog::LOG_INFO, "Sending ID_ACTOR_DEATH about %s %i-%i in cell %s to server", - refId.c_str(), refNum, mpNum, cell.getDescription().c_str()); - - mwmp::Main::get().getNetworking()->getActorList()->addDeathActor(*this); - - MechanicsHelper::clearTarget(killer); - } - - wasDead = creatureStats.mDead; } } @@ -270,6 +254,25 @@ void LocalActor::updateAttackOrCast() } } +void LocalActor::sendDeath(char newDeathState) +{ + deathState = newDeathState; + + if (MechanicsHelper::isEmptyTarget(killer)) + killer = MechanicsHelper::getTarget(ptr); + + LOG_MESSAGE_SIMPLE(TimedLog::LOG_INFO, "Sending ID_ACTOR_DEATH about %s %i-%i in cell %s to server\n- deathState: %d", + refId.c_str(), refNum, mpNum, cell.getDescription().c_str(), deathState); + + ActorList actorList; + actorList.cell = cell; + actorList.addActor(*this); + Main::get().getNetworking()->getActorPacket(ID_ACTOR_DEATH)->setActorList(&actorList); + Main::get().getNetworking()->getActorPacket(ID_ACTOR_DEATH)->Send(); + + MechanicsHelper::clearTarget(killer); +} + MWWorld::Ptr LocalActor::getPtr() { return ptr; diff --git a/apps/openmw/mwmp/LocalActor.hpp b/apps/openmw/mwmp/LocalActor.hpp index 83585de29..59dce27cf 100644 --- a/apps/openmw/mwmp/LocalActor.hpp +++ b/apps/openmw/mwmp/LocalActor.hpp @@ -25,11 +25,12 @@ namespace mwmp void updateEquipment(bool forceUpdate); void updateAttackOrCast(); + void sendDeath(char newDeathState); + MWWorld::Ptr getPtr(); void setPtr(const MWWorld::Ptr& newPtr); bool hasSentData; - bool wasDead; private: MWWorld::Ptr ptr; diff --git a/apps/openmw/mwmp/processors/actor/ProcessorActorDeath.hpp b/apps/openmw/mwmp/processors/actor/ProcessorActorDeath.hpp index 65322a03b..18f23295a 100644 --- a/apps/openmw/mwmp/processors/actor/ProcessorActorDeath.hpp +++ b/apps/openmw/mwmp/processors/actor/ProcessorActorDeath.hpp @@ -17,7 +17,7 @@ namespace mwmp virtual void Do(ActorPacket &packet, ActorList &actorList) { - //Main::get().getCellController()->readDeath(actorList); + Main::get().getCellController()->readDeath(actorList); } }; } diff --git a/components/openmw-mp/Base/BaseActor.hpp b/components/openmw-mp/Base/BaseActor.hpp index 2513e31d0..c44469091 100644 --- a/components/openmw-mp/Base/BaseActor.hpp +++ b/components/openmw-mp/Base/BaseActor.hpp @@ -37,6 +37,8 @@ namespace mwmp SimpleCreatureStats creatureStats; Animation animation; + char deathState; + bool isInstantDeath = false; Attack attack; Cast cast; diff --git a/components/openmw-mp/Packets/Actor/PacketActorDeath.cpp b/components/openmw-mp/Packets/Actor/PacketActorDeath.cpp index e9bdbee5a..c52a7a448 100644 --- a/components/openmw-mp/Packets/Actor/PacketActorDeath.cpp +++ b/components/openmw-mp/Packets/Actor/PacketActorDeath.cpp @@ -13,6 +13,8 @@ void PacketActorDeath::Actor(BaseActor &actor, bool send) { RW(actor.refId, send); + RW(actor.deathState, send); + RW(actor.isInstantDeath, send); RW(actor.killer.isPlayer, send); if (actor.killer.isPlayer)