diff --git a/dGame/dComponents/DestroyableComponent.cpp b/dGame/dComponents/DestroyableComponent.cpp index 7955ce687..51fcf0366 100644 --- a/dGame/dComponents/DestroyableComponent.cpp +++ b/dGame/dComponents/DestroyableComponent.cpp @@ -1391,10 +1391,12 @@ void DestroyableComponent::Resurrect(Entity& entityRef) { // Add a half second in case someone decides to cheat and move during the death animation // and just make sure the client has time to be ready. constexpr float respawnTime = 3.66700005531311f + 0.5f; + if (auto* const destroyable = entity->GetComponent()) destroyable->m_IsResurrecting = true; entity->AddCallbackTimer(respawnTime, [=]() { GameMessages::PlayerResurrectionFinished msg; entity->NotifyPlayerResurrectionFinished(msg); auto* destroyableComponent = entity->GetComponent(); + if (destroyableComponent) destroyableComponent->m_IsResurrecting = false; if (destroyableComponent != nullptr && entity->GetLOT() == 1) { destroyableComponent->SetIsDead(false); diff --git a/dGame/dComponents/DestroyableComponent.h b/dGame/dComponents/DestroyableComponent.h index a6bf02950..ab5c5ae03 100644 --- a/dGame/dComponents/DestroyableComponent.h +++ b/dGame/dComponents/DestroyableComponent.h @@ -471,6 +471,9 @@ public: */ static void Resurrect(Entity& entity); + // Whether a resurrection has started and not finished (the client's isResurrecting) + bool GetIsResurrecting() const { return m_IsResurrecting; } + /** * Push or Pop a layer of status immunity to this entity */ @@ -634,6 +637,9 @@ private: */ bool m_IsDead; + // A resurrection has started (Resurrect) and its respawn animation hasn't finished + bool m_IsResurrecting = false; + /** * Whether this entity is smashed. Unused, here for serialization */ diff --git a/dGame/dGameMessages/CombatMessages.cpp b/dGame/dGameMessages/CombatMessages.cpp index 778dc9173..9242f6998 100644 --- a/dGame/dGameMessages/CombatMessages.cpp +++ b/dGame/dGameMessages/CombatMessages.cpp @@ -112,7 +112,11 @@ namespace GameMessages { return true; } + // LWODestroyableComponent::SendMessage (0x00d29e40): only an object that is dead and not already resurrecting is + // resurrected (live: all 17 requests in the captures came after the player's Die and before its Resurrect) void RequestResurrect::Handle(Entity& entity, const SystemAddress& sysAddr) { + const auto* const destroyable = entity.GetComponent(); + if (!destroyable || !destroyable->GetIsDead() || destroyable->GetIsResurrecting()) return; DestroyableComponent::Resurrect(entity); } diff --git a/tests/dGameTests/dComponentsTests/DestroyableComponentTests.cpp b/tests/dGameTests/dComponentsTests/DestroyableComponentTests.cpp index c2c421e19..27d7829a0 100644 --- a/tests/dGameTests/dComponentsTests/DestroyableComponentTests.cpp +++ b/tests/dGameTests/dComponentsTests/DestroyableComponentTests.cpp @@ -718,3 +718,16 @@ TEST_F(DestroyableTest, DamageReductionsAddUp) { destroyableComponent->RemoveDamageReduction(10); EXPECT_EQ(destroyableComponent->GetDamageReduction(), 0); } + +// The client resurrects only a dead object that isn't already resurrecting; a RequestResurrect from a living player +// does nothing +TEST_F(DestroyableTest, RequestResurrectOnlyWhenDead) { + GameMessages::RequestResurrect request; + destroyableComponent->SetIsDead(false); + request.Handle(*baseEntity, UNASSIGNED_SYSTEM_ADDRESS); + EXPECT_FALSE(destroyableComponent->GetIsResurrecting()); + + destroyableComponent->SetIsDead(true); + request.Handle(*baseEntity, UNASSIGNED_SYSTEM_ADDRESS); + EXPECT_TRUE(destroyableComponent->GetIsResurrecting()); +}