diff --git a/dGame/dComponents/CharacterComponent.cpp b/dGame/dComponents/CharacterComponent.cpp index 341b9cc78..0bf730018 100644 --- a/dGame/dComponents/CharacterComponent.cpp +++ b/dGame/dComponents/CharacterComponent.cpp @@ -585,15 +585,16 @@ std::optional CharacterComponent::GetPowerUpStatistic(const LOT lot } void CharacterComponent::TrackHealthDelta(int32_t health) { + // Live sent no TotalDamageHealed or TotalDamageTaken of 0 if (health > 0) { UpdatePlayerStatistic(TotalDamageHealed, health); - } else { + } else if (health < 0) { UpdatePlayerStatistic(TotalDamageTaken, -health); } } void CharacterComponent::TrackImaginationDelta(int32_t imagination) { - if (imagination > 0) { + if (imagination >= 0) { UpdatePlayerStatistic(TotalImaginationRestored, imagination); } else { UpdatePlayerStatistic(TotalImaginationUsed, -imagination); @@ -601,7 +602,7 @@ void CharacterComponent::TrackImaginationDelta(int32_t imagination) { } void CharacterComponent::TrackArmorDelta(int32_t armor) { - if (armor > 0) { + if (armor >= 0) { UpdatePlayerStatistic(TotalArmorRepaired, armor); } } diff --git a/dGame/dComponents/CharacterComponent.h b/dGame/dComponents/CharacterComponent.h index fa354ded7..3318d60ab 100644 --- a/dGame/dComponents/CharacterComponent.h +++ b/dGame/dComponents/CharacterComponent.h @@ -201,19 +201,20 @@ public: static std::optional GetPowerUpStatistic(LOT lot); /** - * Handles a change in health and updates the statistics + * Handles a change in health and updates the statistics: healing applied (DestroyableComponent::Heal) or health lost * @param health the health delta */ void TrackHealthDelta(int32_t health); /** - * Handles a change in imagination and updates the statistics + * Handles a change in imagination and updates the statistics: restored (DestroyableComponent::Imagine, 0 included) + * or spent * @param imagination the imagination delta */ void TrackImaginationDelta(int32_t imagination); /** - * Handles a change in armor and updates the statistics + * Handles armor repaired (DestroyableComponent::Repair, 0 included) and updates the statistics * @param armor the armor delta */ void TrackArmorDelta(int32_t armor); diff --git a/dGame/dComponents/DestroyableComponent.cpp b/dGame/dComponents/DestroyableComponent.cpp index d4dc0709d..ea8f50d5a 100644 --- a/dGame/dComponents/DestroyableComponent.cpp +++ b/dGame/dComponents/DestroyableComponent.cpp @@ -208,8 +208,9 @@ void DestroyableComponent::UpdateXml(tinyxml2::XMLDocument& doc) { void DestroyableComponent::SetHealth(int32_t value) { m_DirtyHealth = true; + // Health lost counts as damage taken; healing counts where it is applied (Heal) auto* characterComponent = m_Parent->GetComponent(); - if (characterComponent != nullptr) { + if (characterComponent != nullptr && value < m_iHealth) { characterComponent->TrackHealthDelta(value - m_iHealth); } @@ -251,11 +252,6 @@ void DestroyableComponent::SetArmor(int32_t value) { // If Destroyable Component already has zero armor do not trigger the passive ability again. bool hadArmor = m_iArmor > 0; - auto* characterComponent = m_Parent->GetComponent(); - if (characterComponent != nullptr) { - characterComponent->TrackArmorDelta(value - m_iArmor); - } - m_iArmor = value; auto* inventroyComponent = m_Parent->GetComponent(); @@ -294,8 +290,9 @@ void DestroyableComponent::SetMaxArmor(float value, bool playAnim) { void DestroyableComponent::SetImagination(int32_t value) { m_DirtyHealth = true; + // Imagination spent counts as used; restoring counts where it is applied (Imagine) auto* characterComponent = m_Parent->GetComponent(); - if (characterComponent != nullptr) { + if (characterComponent != nullptr && value < m_iImagination) { characterComponent->TrackImaginationDelta(value - m_iImagination); } @@ -499,8 +496,12 @@ void DestroyableComponent::Heal(const uint32_t health) { current = std::min(current, max); + const auto before = GetHealth(); SetHealth(current); + auto* characterComponent = m_Parent->GetComponent(); + if (characterComponent) characterComponent->TrackHealthDelta(GetHealth() - before); + Game::entityManager->SerializeEntity(m_Parent); } @@ -517,8 +518,13 @@ void DestroyableComponent::Imagine(const int32_t deltaImagination) { current = 0; } + const auto before = GetImagination(); SetImagination(current); + // What was restored, 0 included (live sent TotalImaginationRestored 0 for a power-up picked up when full) + auto* characterComponent = m_Parent->GetComponent(); + if (characterComponent && deltaImagination > 0) characterComponent->TrackImaginationDelta(GetImagination() - before); + Game::entityManager->SerializeEntity(m_Parent); } @@ -531,8 +537,13 @@ void DestroyableComponent::Repair(const uint32_t armor) { current = std::min(current, max); + const auto before = GetArmor(); SetArmor(current); + // What was repaired, 0 included (live sent TotalArmorRepaired 0 when the armor was already full) + auto* characterComponent = m_Parent->GetComponent(); + if (characterComponent) characterComponent->TrackArmorDelta(GetArmor() - before); + Game::entityManager->SerializeEntity(m_Parent); } diff --git a/tests/dGameTests/PlayerStatisticTests.cpp b/tests/dGameTests/PlayerStatisticTests.cpp index 45e390a78..e7e36ccc6 100644 --- a/tests/dGameTests/PlayerStatisticTests.cpp +++ b/tests/dGameTests/PlayerStatisticTests.cpp @@ -174,3 +174,33 @@ TEST_F(PlayerStatisticTest, SmashingCountsEnemiesAndSmashables) { EXPECT_TRUE(Statistics(smash(false, false)).empty()); killer->SetCharacter(nullptr); } + +// Live: what a heal, repair or restore applied, 0 included for armor and imagination (a power-up picked up when full); +// no TotalDamageHealed of 0 +TEST_F(PlayerStatisticTest, HealsRepairsAndRestoresCountWhatTheyApplied) { + auto* destroyable = entity->AddComponent(-1); + destroyable->SetMaxHealth(10.0f); + destroyable->SetMaxArmor(10.0f); + destroyable->SetMaxImagination(10.0f); + destroyable->SetHealth(10); + destroyable->SetArmor(8); + destroyable->SetImagination(10); + + const auto sent = [&](const std::function& action) { + std::vector> out; + for (const auto& statistic : Statistics(Capture(action))) out.emplace_back(statistic.updateID, statistic.updateValue); + return out; + }; + using Sent = std::vector>; + + EXPECT_EQ(sent([&] { destroyable->Heal(5); }), Sent{}); + EXPECT_EQ(sent([&] { destroyable->Repair(5); }), (Sent{ { TotalArmorRepaired, 2 } })); + EXPECT_EQ(sent([&] { destroyable->Repair(5); }), (Sent{ { TotalArmorRepaired, 0 } })); + EXPECT_EQ(sent([&] { destroyable->Imagine(3); }), (Sent{ { TotalImaginationRestored, 0 } })); + EXPECT_EQ(sent([&] { destroyable->Imagine(-4); }), (Sent{ { TotalImaginationUsed, 4 } })); + EXPECT_EQ(sent([&] { destroyable->Imagine(6); }), (Sent{ { TotalImaginationRestored, 4 } })); + EXPECT_EQ(sent([&] { destroyable->SetHealth(7); }), (Sent{ { TotalDamageTaken, 3 } })); + EXPECT_EQ(sent([&] { destroyable->Heal(1); }), (Sent{ { TotalDamageHealed, 1 } })); + // Setting stats up (loading, respawning) isn't healing + EXPECT_EQ(sent([&] { destroyable->SetHealth(10); destroyable->SetArmor(10); destroyable->SetImagination(10); }), Sent{}); +}