mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-10-02 19:03:43 +00:00
Live filled Die's direction_relative_angle_xz, _angle_y and _force on 11,606 of 19,428 deaths. The values are the dir_angle_xz, dir_angle_y (degrees, sent as radians) and dir_force parameters of the BasicAttack that dealt the killing blow: per killing skill the triple is constant (skill 1210: -25 degrees, force 12 on 540 of 549 kills; skill 10: force 7 on 1,830 of 1,835), and deaths with no killer (5,948) never have one. BasicAttack is the only behavior with these parameters apart from one Grab. The client passes them to PerformSmashableDeath, which throws the smashable's pieces that way; DLU sent 0. The radians are rounded the way live did (-25 is 0xbedf66f3, 20 is 0x3eb2b8c3). Not covered: use_caster_velocity (one behavior, racing). Check: smash crates and enemies with different weapons; the pieces fly off to the side/up per weapon instead of straight out. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
669 lines
30 KiB
C++
669 lines
30 KiB
C++
#include "GameDependencies.h"
|
|
#include <gtest/gtest.h>
|
|
|
|
#include "BitStream.h"
|
|
#include "DestroyableComponent.h"
|
|
#include "Entity.h"
|
|
#include "eReplicaComponentType.h"
|
|
#include "eStateChangeType.h"
|
|
#include "Character.h"
|
|
#include "CharacterComponent.h"
|
|
#include "CombatMessages.h"
|
|
#include "PacketTestUtils.h"
|
|
#include "dCommonVars.h"
|
|
|
|
#include <cstring>
|
|
#include <optional>
|
|
|
|
namespace {
|
|
// The first game message with this ID among captured packets, read into T.
|
|
template<typename T>
|
|
std::optional<T> FindGameMessage(const std::vector<CapturedPacket>& packets, const MessageType::Game id) {
|
|
for (const auto& packet : packets) {
|
|
RakNet::BitStream bitStream(const_cast<unsigned char*>(packet.bytes.data()), packet.bytes.size(), false);
|
|
bitStream.IgnoreBytes(8); // CLIENT GAME_MSG header
|
|
LWOOBJID target{};
|
|
MessageType::Game messageID{};
|
|
if (!bitStream.Read(target) || !bitStream.Read(messageID) || messageID != id) continue;
|
|
T msg;
|
|
if (!msg.Deserialize(bitStream)) continue;
|
|
msg.target = target;
|
|
return msg;
|
|
}
|
|
return std::nullopt;
|
|
}
|
|
}
|
|
|
|
class DestroyableTest : public GameDependenciesTest {
|
|
protected:
|
|
Entity* baseEntity;
|
|
DestroyableComponent* destroyableComponent;
|
|
RakNet::BitStream bitStream;
|
|
uint32_t flags = 0;
|
|
void SetUp() override {
|
|
SetUpDependencies();
|
|
baseEntity = new Entity(15, GameDependenciesTest::info);
|
|
destroyableComponent = baseEntity->AddComponent<DestroyableComponent>(-1);
|
|
// Initialize some values to be not default
|
|
destroyableComponent->SetMaxHealth(12345.0f);
|
|
destroyableComponent->SetHealth(23);
|
|
destroyableComponent->SetMaxArmor(14.0f);
|
|
destroyableComponent->SetArmor(7);
|
|
destroyableComponent->SetMaxImagination(14000.0f);
|
|
destroyableComponent->SetImagination(6000);
|
|
destroyableComponent->SetIsSmashable(true);
|
|
destroyableComponent->SetExplodeFactor(1.1f);
|
|
destroyableComponent->AddFactionNoLookup(-1);
|
|
destroyableComponent->AddFactionNoLookup(6);
|
|
}
|
|
|
|
void TearDown() override {
|
|
delete baseEntity;
|
|
TearDownDependencies();
|
|
}
|
|
};
|
|
|
|
TEST_F(DestroyableTest, PlacementNewAddComponentTest) {
|
|
ASSERT_NE(destroyableComponent, nullptr);
|
|
ASSERT_EQ(destroyableComponent->GetArmor(), 7);
|
|
baseEntity->AddComponent<DestroyableComponent>(-1);
|
|
ASSERT_NE(baseEntity->GetComponent<DestroyableComponent>(), nullptr);
|
|
ASSERT_EQ(destroyableComponent->GetArmor(), 0);
|
|
}
|
|
|
|
/**
|
|
* Test Construction of a DestroyableComponent
|
|
*/
|
|
TEST_F(DestroyableTest, DestroyableComponentSerializeConstructionTest) {
|
|
destroyableComponent->Serialize(bitStream, true);
|
|
// Assert that the full number of bits are present
|
|
ASSERT_EQ(bitStream.GetNumberOfUnreadBits(), 748);
|
|
{
|
|
// Now read in the full serialized construction BitStream
|
|
bool optionStatusImmunityInfo{};
|
|
uint32_t ImmuneToBasicAttackCount{};
|
|
uint32_t ImmuneToDamageOverTimeCount{};
|
|
uint32_t ImmuneToKnockbackCount{};
|
|
uint32_t ImmuneToInterruptCount{};
|
|
uint32_t ImmuneToSpeedCount{};
|
|
uint32_t ImmuneToImaginationGainCount{};
|
|
uint32_t ImmuneToImaginationLossCount{};
|
|
uint32_t ImmuneToQuickbuildInterruptCount{};
|
|
uint32_t ImmuneToPullToPointCount{};
|
|
bool optionStatsInfo{};
|
|
uint32_t currentHealth{};
|
|
float maxHealth{};
|
|
uint32_t currentArmor{};
|
|
float maxArmor{};
|
|
uint32_t currentImagination{};
|
|
float maxImagination{};
|
|
uint32_t damageAbsorptionPoints{};
|
|
bool hasImmunity{};
|
|
bool isGmImmune{};
|
|
bool isShielded{};
|
|
float actualMaxHealth{};
|
|
float actualMaxArmor{};
|
|
float actualMaxImagination{};
|
|
uint32_t factionsSize{};
|
|
std::vector<int32_t> factions{};
|
|
bool isSmashable{};
|
|
bool isDead{};
|
|
bool isSmashed{};
|
|
bool isModuleAssembly{};
|
|
bool optionExplodeFactor{};
|
|
float explodeFactor{};
|
|
bool optionIsOnThreatList{};
|
|
bool isThreatened{};
|
|
bitStream.Read(optionStatusImmunityInfo);
|
|
bitStream.Read(ImmuneToBasicAttackCount);
|
|
bitStream.Read(ImmuneToDamageOverTimeCount);
|
|
bitStream.Read(ImmuneToKnockbackCount);
|
|
bitStream.Read(ImmuneToInterruptCount);
|
|
bitStream.Read(ImmuneToSpeedCount);
|
|
bitStream.Read(ImmuneToImaginationGainCount);
|
|
bitStream.Read(ImmuneToImaginationLossCount);
|
|
bitStream.Read(ImmuneToQuickbuildInterruptCount);
|
|
bitStream.Read(ImmuneToPullToPointCount);
|
|
bitStream.Read(optionStatsInfo);
|
|
bitStream.Read(currentHealth);
|
|
bitStream.Read(maxHealth);
|
|
bitStream.Read(currentArmor);
|
|
bitStream.Read(maxArmor);
|
|
bitStream.Read(currentImagination);
|
|
bitStream.Read(maxImagination);
|
|
bitStream.Read(damageAbsorptionPoints);
|
|
bitStream.Read(hasImmunity);
|
|
bitStream.Read(isGmImmune);
|
|
bitStream.Read(isShielded);
|
|
bitStream.Read(actualMaxHealth);
|
|
bitStream.Read(actualMaxArmor);
|
|
bitStream.Read(actualMaxImagination);
|
|
bitStream.Read(factionsSize);
|
|
for (uint32_t i = 0; i < factionsSize; i++) {
|
|
int32_t factionID{};
|
|
bitStream.Read(factionID);
|
|
factions.push_back(factionID);
|
|
}
|
|
bitStream.Read(isSmashable); // This is an option later and also a flag at this spot
|
|
bitStream.Read(isDead);
|
|
bitStream.Read(isSmashed);
|
|
// if IsSmashable is true, read the next bits.
|
|
bitStream.Read(isModuleAssembly);
|
|
bitStream.Read(optionExplodeFactor);
|
|
bitStream.Read(explodeFactor);
|
|
|
|
bitStream.Read(optionIsOnThreatList);
|
|
bitStream.Read(isThreatened);
|
|
EXPECT_EQ(optionStatusImmunityInfo, true);
|
|
EXPECT_EQ(ImmuneToBasicAttackCount, 0);
|
|
EXPECT_EQ(ImmuneToDamageOverTimeCount, 0);
|
|
EXPECT_EQ(ImmuneToKnockbackCount, 0);
|
|
EXPECT_EQ(ImmuneToInterruptCount, 0);
|
|
EXPECT_EQ(ImmuneToSpeedCount, 0);
|
|
EXPECT_EQ(ImmuneToImaginationGainCount, 0);
|
|
EXPECT_EQ(ImmuneToImaginationLossCount, 0);
|
|
EXPECT_EQ(ImmuneToQuickbuildInterruptCount, 0);
|
|
EXPECT_EQ(ImmuneToPullToPointCount, 0);
|
|
|
|
EXPECT_EQ(optionStatsInfo, true);
|
|
EXPECT_EQ(currentHealth, 23);
|
|
EXPECT_EQ(maxHealth, 12345.0f);
|
|
EXPECT_EQ(currentArmor, 7);
|
|
EXPECT_EQ(maxArmor, 14.0f);
|
|
EXPECT_EQ(currentImagination, 6000);
|
|
EXPECT_EQ(maxImagination, 14000.0f);
|
|
EXPECT_EQ(damageAbsorptionPoints, 0.0f);
|
|
EXPECT_EQ(hasImmunity, false);
|
|
EXPECT_EQ(isGmImmune, false);
|
|
EXPECT_EQ(isShielded, false);
|
|
EXPECT_EQ(actualMaxHealth, 12345.0f);
|
|
EXPECT_EQ(actualMaxArmor, 14.0f);
|
|
EXPECT_EQ(actualMaxImagination, 14000.0f);
|
|
EXPECT_EQ(factionsSize, 2);
|
|
EXPECT_NE(std::find(factions.begin(), factions.end(), -1), factions.end());
|
|
EXPECT_NE(std::find(factions.begin(), factions.end(), 6), factions.end());
|
|
EXPECT_EQ(isSmashable, true);
|
|
EXPECT_EQ(isDead, false);
|
|
EXPECT_EQ(isSmashed, false);
|
|
EXPECT_EQ(isSmashable, true); // For the sake of readability with the struct viewers, we will test this twice since its used as an option here, but as a bool above.
|
|
EXPECT_EQ(isModuleAssembly, false);
|
|
EXPECT_EQ(optionExplodeFactor, true);
|
|
EXPECT_EQ(explodeFactor, 1.1f);
|
|
|
|
EXPECT_EQ(optionIsOnThreatList, true);
|
|
EXPECT_EQ(isThreatened, false);
|
|
}
|
|
bitStream.Reset();
|
|
}
|
|
|
|
/**
|
|
* Test serialization of a DestroyableComponent
|
|
*/
|
|
TEST_F(DestroyableTest, DestroyableComponentSerializeTest) {
|
|
bitStream.Reset();
|
|
// Initialize some values to be not default so we can test a full serialization
|
|
destroyableComponent->SetMaxHealth(1233.0f);
|
|
|
|
// Now we test a serialization for correctness.
|
|
destroyableComponent->Serialize(bitStream, false);
|
|
ASSERT_EQ(bitStream.GetNumberOfUnreadBits(), 422);
|
|
{
|
|
// Now read in the full serialized BitStream
|
|
bool optionStatsInfo{};
|
|
uint32_t currentHealth{};
|
|
float maxHealth{};
|
|
uint32_t currentArmor{};
|
|
float maxArmor{};
|
|
uint32_t currentImagination{};
|
|
float maxImagination{};
|
|
uint32_t damageAbsorptionPoints{};
|
|
bool hasImmunity{};
|
|
bool isGmImmune{};
|
|
bool isShielded{};
|
|
float actualMaxHealth{};
|
|
float actualMaxArmor{};
|
|
float actualMaxImagination{};
|
|
uint32_t factionsSize{};
|
|
std::vector<int32_t> factions{};
|
|
bool isSmashable{};
|
|
bool optionIsOnThreatList{};
|
|
bitStream.Read(optionStatsInfo);
|
|
bitStream.Read(currentHealth);
|
|
bitStream.Read(maxHealth);
|
|
bitStream.Read(currentArmor);
|
|
bitStream.Read(maxArmor);
|
|
bitStream.Read(currentImagination);
|
|
bitStream.Read(maxImagination);
|
|
bitStream.Read(damageAbsorptionPoints);
|
|
bitStream.Read(hasImmunity);
|
|
bitStream.Read(isGmImmune);
|
|
bitStream.Read(isShielded);
|
|
bitStream.Read(actualMaxHealth);
|
|
bitStream.Read(actualMaxArmor);
|
|
bitStream.Read(actualMaxImagination);
|
|
bitStream.Read(factionsSize);
|
|
for (uint32_t i = 0; i < factionsSize; i++) {
|
|
int32_t factionID{};
|
|
bitStream.Read(factionID);
|
|
factions.push_back(factionID);
|
|
}
|
|
bitStream.Read(isSmashable);
|
|
|
|
bitStream.Read(optionIsOnThreatList);
|
|
|
|
EXPECT_EQ(optionStatsInfo, true);
|
|
EXPECT_EQ(currentHealth, 23);
|
|
EXPECT_EQ(maxHealth, 1233.0f);
|
|
EXPECT_EQ(currentArmor, 7);
|
|
EXPECT_EQ(maxArmor, 14.0f);
|
|
EXPECT_EQ(currentImagination, 6000);
|
|
EXPECT_EQ(maxImagination, 14000.0f);
|
|
EXPECT_EQ(damageAbsorptionPoints, 0.0f);
|
|
EXPECT_EQ(hasImmunity, false);
|
|
EXPECT_EQ(isGmImmune, false);
|
|
EXPECT_EQ(isShielded, false);
|
|
EXPECT_EQ(actualMaxHealth, 1233.0f);
|
|
EXPECT_EQ(actualMaxArmor, 14.0f);
|
|
EXPECT_EQ(actualMaxImagination, 14000.0f);
|
|
EXPECT_EQ(factionsSize, 2);
|
|
EXPECT_NE(std::find(factions.begin(), factions.end(), -1), factions.end());
|
|
EXPECT_NE(std::find(factions.begin(), factions.end(), 6), factions.end());
|
|
EXPECT_EQ(isSmashable, true);
|
|
|
|
EXPECT_EQ(optionIsOnThreatList, false); // Always zero for now on serialization
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Test the Damage method of DestroyableComponent
|
|
*/
|
|
TEST_F(DestroyableTest, DestroyableComponentDamageTest) {
|
|
// Do some actions
|
|
destroyableComponent->SetMaxHealth(100.0f);
|
|
destroyableComponent->SetHealth(100);
|
|
destroyableComponent->SetMaxArmor(0.0f);
|
|
destroyableComponent->Damage(10, LWOOBJID_EMPTY);
|
|
// Check that we take damage
|
|
ASSERT_EQ(destroyableComponent->GetHealth(), 90);
|
|
// Check that if we have armor, we take the correct amount of damage
|
|
destroyableComponent->SetMaxArmor(10.0f);
|
|
destroyableComponent->SetArmor(5);
|
|
destroyableComponent->Damage(10, LWOOBJID_EMPTY);
|
|
ASSERT_EQ(destroyableComponent->GetHealth(), 85);
|
|
// Check that if we have damage absorption we take the correct damage
|
|
destroyableComponent->SetDamageToAbsorb(10);
|
|
destroyableComponent->Damage(9, LWOOBJID_EMPTY);
|
|
ASSERT_EQ(destroyableComponent->GetHealth(), 85);
|
|
ASSERT_EQ(destroyableComponent->GetDamageToAbsorb(), 1);
|
|
destroyableComponent->Damage(6, LWOOBJID_EMPTY);
|
|
ASSERT_EQ(destroyableComponent->GetHealth(), 80);
|
|
// Check that we take the correct reduced damage if we take reduced damage
|
|
destroyableComponent->SetDamageReduction(2);
|
|
destroyableComponent->Damage(7, LWOOBJID_EMPTY);
|
|
ASSERT_EQ(destroyableComponent->GetHealth(), 75);
|
|
destroyableComponent->Damage(2, LWOOBJID_EMPTY);
|
|
ASSERT_EQ(destroyableComponent->GetHealth(), 74);
|
|
ASSERT_EQ(destroyableComponent->GetDamageReduction(), 2);
|
|
destroyableComponent->SetDamageReduction(0);
|
|
// Check that blocking works
|
|
destroyableComponent->SetAttacksToBlock(1);
|
|
destroyableComponent->Damage(UINT32_MAX, LWOOBJID_EMPTY);
|
|
ASSERT_EQ(destroyableComponent->GetHealth(), 74);
|
|
destroyableComponent->Damage(4, LWOOBJID_EMPTY);
|
|
ASSERT_EQ(destroyableComponent->GetHealth(), 70);
|
|
// Check that immunity works
|
|
destroyableComponent->SetIsImmune(true);
|
|
destroyableComponent->Damage(UINT32_MAX, LWOOBJID_EMPTY);
|
|
ASSERT_EQ(destroyableComponent->GetHealth(), 70);
|
|
ASSERT_TRUE(destroyableComponent->IsImmune());
|
|
destroyableComponent->SetIsImmune(false);
|
|
destroyableComponent->SetIsGMImmune(true);
|
|
destroyableComponent->Damage(UINT32_MAX, LWOOBJID_EMPTY);
|
|
ASSERT_EQ(destroyableComponent->GetHealth(), 70);
|
|
ASSERT_TRUE(destroyableComponent->IsImmune());
|
|
destroyableComponent->SetIsGMImmune(false);
|
|
// Check knockback immunity
|
|
destroyableComponent->SetIsShielded(true);
|
|
ASSERT_TRUE(destroyableComponent->IsKnockbackImmune());
|
|
// Finally deal enough damage to kill the Entity
|
|
destroyableComponent->Damage(71, LWOOBJID_EMPTY);
|
|
ASSERT_EQ(destroyableComponent->GetHealth(), 0);
|
|
// Now lets heal some stats
|
|
destroyableComponent->Heal(15);
|
|
ASSERT_EQ(destroyableComponent->GetHealth(), 15);
|
|
destroyableComponent->Heal(15000);
|
|
ASSERT_EQ(destroyableComponent->GetHealth(), 100);
|
|
destroyableComponent->Repair(10);
|
|
ASSERT_EQ(destroyableComponent->GetArmor(), 10);
|
|
destroyableComponent->Repair(15000);
|
|
ASSERT_EQ(destroyableComponent->GetArmor(), 10);
|
|
destroyableComponent->SetMaxImagination(100.0f);
|
|
destroyableComponent->SetImagination(0);
|
|
destroyableComponent->Imagine(99);
|
|
ASSERT_EQ(destroyableComponent->GetImagination(), 99);
|
|
destroyableComponent->Imagine(4);
|
|
ASSERT_EQ(destroyableComponent->GetImagination(), 100);
|
|
}
|
|
|
|
TEST_F(DestroyableTest, DestroyableComponentFactionTest) {
|
|
ASSERT_TRUE(destroyableComponent->HasFaction(-1));
|
|
ASSERT_TRUE(destroyableComponent->HasFaction(6));
|
|
}
|
|
|
|
TEST_F(DestroyableTest, DestroyableComponentValiditiyTest) {
|
|
auto* enemyEntity = new Entity(19, info);
|
|
enemyEntity->AddComponent<DestroyableComponent>(-1)->AddFactionNoLookup(16);
|
|
destroyableComponent->AddEnemyFaction(16);
|
|
EXPECT_TRUE(destroyableComponent->IsEnemy(enemyEntity));
|
|
EXPECT_FALSE(destroyableComponent->IsFriend(enemyEntity));
|
|
delete enemyEntity;
|
|
}
|
|
|
|
TEST_F(DestroyableTest, DestroyableComponentImmunityTest) {
|
|
// assert to show that they are empty
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToBasicAttack());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToDamageOverTime());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToKnockback());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToInterrupt());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToSpeed());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToImaginationGain());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToImaginationLoss());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToQuickbuildInterrupt());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToPullToPoint());
|
|
// set them all to true (count 1) and check
|
|
destroyableComponent->SetStatusImmunity(eStateChangeType::PUSH, true, true, true, true, true, true, true, true, true);
|
|
ASSERT_TRUE(destroyableComponent->GetImmuneToBasicAttack());
|
|
ASSERT_TRUE(destroyableComponent->GetImmuneToDamageOverTime());
|
|
ASSERT_TRUE(destroyableComponent->GetImmuneToKnockback());
|
|
ASSERT_TRUE(destroyableComponent->GetImmuneToInterrupt());
|
|
ASSERT_TRUE(destroyableComponent->GetImmuneToSpeed());
|
|
ASSERT_TRUE(destroyableComponent->GetImmuneToImaginationGain());
|
|
ASSERT_TRUE(destroyableComponent->GetImmuneToImaginationLoss());
|
|
ASSERT_TRUE(destroyableComponent->GetImmuneToQuickbuildInterrupt());
|
|
ASSERT_TRUE(destroyableComponent->GetImmuneToPullToPoint());
|
|
|
|
// remove them to check that they get removed properly
|
|
destroyableComponent->SetStatusImmunity(eStateChangeType::POP, true, true, true, true, true, true, true, true, true);
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToBasicAttack());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToDamageOverTime());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToKnockback());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToInterrupt());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToSpeed());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToImaginationGain());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToImaginationLoss());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToQuickbuildInterrupt());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToPullToPoint());
|
|
|
|
// should not crash to remove them again
|
|
destroyableComponent->SetStatusImmunity(eStateChangeType::POP, true, true, true, true, true, true, true, true, true);
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToBasicAttack());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToDamageOverTime());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToKnockback());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToInterrupt());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToSpeed());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToImaginationGain());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToImaginationLoss());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToQuickbuildInterrupt());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToPullToPoint());
|
|
|
|
// just do one
|
|
destroyableComponent->SetStatusImmunity(eStateChangeType::PUSH, true);
|
|
ASSERT_TRUE(destroyableComponent->GetImmuneToBasicAttack());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToDamageOverTime());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToKnockback());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToInterrupt());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToSpeed());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToImaginationGain());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToImaginationLoss());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToQuickbuildInterrupt());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToPullToPoint());
|
|
// now stack it to 2 on basic attack
|
|
destroyableComponent->SetStatusImmunity(eStateChangeType::PUSH, true);
|
|
ASSERT_TRUE(destroyableComponent->GetImmuneToBasicAttack());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToDamageOverTime());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToKnockback());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToInterrupt());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToSpeed());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToImaginationGain());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToImaginationLoss());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToQuickbuildInterrupt());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToPullToPoint());
|
|
// remove one and still shoudl be true
|
|
destroyableComponent->SetStatusImmunity(eStateChangeType::POP, true);
|
|
ASSERT_TRUE(destroyableComponent->GetImmuneToBasicAttack());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToDamageOverTime());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToKnockback());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToInterrupt());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToSpeed());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToImaginationGain());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToImaginationLoss());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToQuickbuildInterrupt());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToPullToPoint());
|
|
// go back to 0
|
|
destroyableComponent->SetStatusImmunity(eStateChangeType::POP, true);
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToBasicAttack());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToDamageOverTime());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToKnockback());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToInterrupt());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToSpeed());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToImaginationGain());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToImaginationLoss());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToQuickbuildInterrupt());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToPullToPoint());
|
|
// check individual ones now
|
|
destroyableComponent->SetStatusImmunity(eStateChangeType::PUSH, true, false, false, false, false, false, false, false, false);
|
|
ASSERT_TRUE(destroyableComponent->GetImmuneToBasicAttack());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToDamageOverTime());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToKnockback());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToInterrupt());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToSpeed());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToImaginationGain());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToImaginationLoss());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToQuickbuildInterrupt());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToPullToPoint());
|
|
destroyableComponent->SetStatusImmunity(eStateChangeType::POP, true, false, false, false, false, false, false, false, false);
|
|
|
|
destroyableComponent->SetStatusImmunity(eStateChangeType::PUSH, false, true, false, false, false, false, false, false, false);
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToBasicAttack());
|
|
ASSERT_TRUE(destroyableComponent->GetImmuneToDamageOverTime());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToKnockback());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToInterrupt());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToSpeed());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToImaginationGain());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToImaginationLoss());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToQuickbuildInterrupt());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToPullToPoint());
|
|
destroyableComponent->SetStatusImmunity(eStateChangeType::POP, false, true, false, false, false, false, false, false, false);
|
|
|
|
destroyableComponent->SetStatusImmunity(eStateChangeType::PUSH, false, false, true, false, false, false, false, false, false);
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToBasicAttack());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToDamageOverTime());
|
|
ASSERT_TRUE(destroyableComponent->GetImmuneToKnockback());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToInterrupt());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToSpeed());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToImaginationGain());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToImaginationLoss());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToQuickbuildInterrupt());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToPullToPoint());
|
|
destroyableComponent->SetStatusImmunity(eStateChangeType::POP, false, false, true, false, false, false, false, false, false);
|
|
|
|
destroyableComponent->SetStatusImmunity(eStateChangeType::PUSH, false, false, false, true, false, false, false, false, false);
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToBasicAttack());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToDamageOverTime());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToKnockback());
|
|
ASSERT_TRUE(destroyableComponent->GetImmuneToInterrupt());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToSpeed());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToImaginationGain());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToImaginationLoss());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToQuickbuildInterrupt());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToPullToPoint());
|
|
destroyableComponent->SetStatusImmunity(eStateChangeType::POP, false, false, false, true, false, false, false, false, false);
|
|
|
|
destroyableComponent->SetStatusImmunity(eStateChangeType::PUSH, false, false, false, false, true, false, false, false, false);
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToBasicAttack());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToDamageOverTime());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToKnockback());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToInterrupt());
|
|
ASSERT_TRUE(destroyableComponent->GetImmuneToSpeed());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToImaginationGain());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToImaginationLoss());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToQuickbuildInterrupt());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToPullToPoint());
|
|
destroyableComponent->SetStatusImmunity(eStateChangeType::POP, false, false, false, false, true, false, false, false, false);
|
|
|
|
destroyableComponent->SetStatusImmunity(eStateChangeType::PUSH, false, false, false, false, false, true, false, false, false);
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToBasicAttack());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToDamageOverTime());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToKnockback());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToInterrupt());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToSpeed());
|
|
ASSERT_TRUE(destroyableComponent->GetImmuneToImaginationGain());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToImaginationLoss());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToQuickbuildInterrupt());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToPullToPoint());
|
|
destroyableComponent->SetStatusImmunity(eStateChangeType::POP, false, false, false, false, false, true, false, false, false);
|
|
|
|
destroyableComponent->SetStatusImmunity(eStateChangeType::PUSH, false, false, false, false, false, false, true, false, false);
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToBasicAttack());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToDamageOverTime());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToKnockback());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToInterrupt());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToSpeed());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToImaginationGain());
|
|
ASSERT_TRUE(destroyableComponent->GetImmuneToImaginationLoss());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToQuickbuildInterrupt());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToPullToPoint());
|
|
destroyableComponent->SetStatusImmunity(eStateChangeType::POP, false, false, false, false, false, false, true, false, false);
|
|
|
|
|
|
destroyableComponent->SetStatusImmunity(eStateChangeType::PUSH, false, false, false, false, false, false, false, true, false);
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToBasicAttack());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToDamageOverTime());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToKnockback());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToInterrupt());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToSpeed());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToImaginationGain());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToImaginationLoss());
|
|
ASSERT_TRUE(destroyableComponent->GetImmuneToQuickbuildInterrupt());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToPullToPoint());
|
|
destroyableComponent->SetStatusImmunity(eStateChangeType::POP, false, false, false, false, false, false, false, true, false);
|
|
|
|
|
|
destroyableComponent->SetStatusImmunity(eStateChangeType::PUSH, false, false, false, false, false, false, false, false, true);
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToBasicAttack());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToDamageOverTime());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToKnockback());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToInterrupt());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToSpeed());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToImaginationGain());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToImaginationLoss());
|
|
ASSERT_FALSE(destroyableComponent->GetImmuneToQuickbuildInterrupt());
|
|
ASSERT_TRUE(destroyableComponent->GetImmuneToPullToPoint());
|
|
destroyableComponent->SetStatusImmunity(eStateChangeType::POP, false, false, false, false, false, false, false, false, true);
|
|
|
|
}
|
|
|
|
/**
|
|
* Test the Damage cooldown timer of DestroyableComponent
|
|
*/
|
|
TEST_F(DestroyableTest, DestroyableComponentDamageCooldownTest) {
|
|
// Test the damage immune timer state (anything above 0.0f)
|
|
destroyableComponent->SetDamageCooldownTimer(1.0f);
|
|
EXPECT_FLOAT_EQ(destroyableComponent->GetDamageCooldownTimer(), 1.0f);
|
|
ASSERT_TRUE(destroyableComponent->IsCooldownImmune());
|
|
|
|
// Test that the Update() function correctly decrements the damage cooldown timer
|
|
destroyableComponent->Update(0.5f);
|
|
EXPECT_FLOAT_EQ(destroyableComponent->GetDamageCooldownTimer(), 0.5f);
|
|
ASSERT_TRUE(destroyableComponent->IsCooldownImmune());
|
|
|
|
// Test the non damage immune timer state (anything below or equal to 0.0f)
|
|
destroyableComponent->SetDamageCooldownTimer(0.0f);
|
|
EXPECT_FLOAT_EQ(destroyableComponent->GetDamageCooldownTimer(), 0.0f);
|
|
ASSERT_FALSE(destroyableComponent->IsCooldownImmune());
|
|
}
|
|
|
|
// Live Die never sets client_death (19,428 live Die, 452 of them players).
|
|
TEST_F(DestroyableTest, DieDoesNotClaimAClientDeath) {
|
|
destroyableComponent->SetMaxArmor(0.0f);
|
|
const auto sent = PacketTestUtils::Capture([&] { destroyableComponent->Damage(UINT32_MAX, LWOOBJID_EMPTY); });
|
|
const auto die = FindGameMessage<GameMessages::Die>(sent, MessageType::Game::DIE);
|
|
ASSERT_TRUE(die.has_value());
|
|
EXPECT_EQ(die->target, baseEntity->GetObjectID());
|
|
EXPECT_FALSE(die->bClientDeath);
|
|
EXPECT_TRUE(die->bSpawnLoot);
|
|
}
|
|
|
|
TEST_F(DestroyableTest, PlayerDieDoesNotClaimAClientDeath) {
|
|
auto playerInfo = info;
|
|
playerInfo.lot = 1;
|
|
Entity player(0x1000000000000001LL, playerInfo);
|
|
Character character(1, nullptr);
|
|
player.SetCharacter(&character);
|
|
character.SetEntity(&player);
|
|
SystemAddress address;
|
|
address.binaryAddress = 0x0100007f;
|
|
address.port = 2003;
|
|
player.AddComponent<CharacterComponent>(-1, &character, address);
|
|
auto* destroyable = player.AddComponent<DestroyableComponent>(-1);
|
|
destroyable->SetMaxHealth(4.0f);
|
|
destroyable->SetHealth(4);
|
|
ASSERT_TRUE(player.IsPlayer());
|
|
|
|
const auto sent = PacketTestUtils::Capture([&] { destroyable->Smash(LWOOBJID_EMPTY); });
|
|
const auto die = FindGameMessage<GameMessages::Die>(sent, MessageType::Game::DIE);
|
|
ASSERT_TRUE(die.has_value());
|
|
EXPECT_EQ(die->target, player.GetObjectID());
|
|
EXPECT_FALSE(die->bClientDeath);
|
|
player.SetCharacter(nullptr);
|
|
}
|
|
|
|
// BasicAttack's dir_angle_* are degrees; live's Die carries them as these exact floats.
|
|
TEST_F(DestroyableTest, DeathDirectionDegreesMatchLiveBits) {
|
|
const auto bits = [](float value) { uint32_t out; std::memcpy(&out, &value, sizeof(out)); return out; };
|
|
EXPECT_EQ(bits(DeathDirection::DegreesToRadians(-25.0f)), 0xbedf66f3u);
|
|
EXPECT_EQ(bits(DeathDirection::DegreesToRadians(25.0f)), 0x3edf66f3u);
|
|
EXPECT_EQ(bits(DeathDirection::DegreesToRadians(20.0f)), 0x3eb2b8c3u);
|
|
EXPECT_EQ(bits(DeathDirection::DegreesToRadians(-20.0f)), 0xbeb2b8c3u);
|
|
EXPECT_EQ(bits(DeathDirection::DegreesToRadians(75.0f)), 0x3fa78d36u);
|
|
EXPECT_EQ(bits(DeathDirection::DegreesToRadians(80.0f)), 0x3fb2b8c3u);
|
|
EXPECT_EQ(bits(DeathDirection::DegreesToRadians(15.0f)), 0x3e860a92u);
|
|
EXPECT_EQ(bits(DeathDirection::DegreesToRadians(45.0f)), 0x3f490fdbu);
|
|
EXPECT_EQ(DeathDirection::DegreesToRadians(0.0f), 0.0f);
|
|
}
|
|
|
|
// The killing blow's direction goes into Die. Bytes: a live Die from a -25 degree, force 12 BasicAttack with the
|
|
// object IDs replaced.
|
|
TEST_F(DestroyableTest, KillingBlowDirectionIsSentInDie) {
|
|
Entity target(0x0102030405060708LL, info);
|
|
auto* destroyable = target.AddComponent<DestroyableComponent>(-1);
|
|
destroyable->SetMaxHealth(4.0f);
|
|
destroyable->SetHealth(4);
|
|
|
|
const DeathDirection direction{ DeathDirection::DegreesToRadians(-25.0f), 0.0f, 12.0f };
|
|
const LWOOBJID killer = 0x1000000000000001LL;
|
|
auto sent = PacketTestUtils::Capture([&] { destroyable->Damage(1, killer, 0, true, direction); });
|
|
EXPECT_FALSE(FindGameMessage<GameMessages::Die>(sent, MessageType::Game::DIE).has_value()); // not a killing blow
|
|
|
|
sent = PacketTestUtils::Capture([&] { destroyable->Damage(3, killer, 0, true, direction); });
|
|
bool found = false;
|
|
for (const auto& packet : sent) {
|
|
if (packet.bytes.size() < 18 || packet.bytes[16] != 0x25 || packet.bytes[17] != 0x00) continue;
|
|
found = true;
|
|
EXPECT_PACKET_EQ(PacketTestUtils::FromHex(
|
|
"53 05 00 0c 00 00 00 00 08 07 06 05 04 03 02 01 25 00 40 00 00 00 3c d9 b7 ef 80 00 00 00 00 00 10 10 "
|
|
"40 20 00 00 00 00 00 02 10 10 00 00 00 00 00 01 00", 404), PacketTestUtils::FromCapture(packet));
|
|
}
|
|
EXPECT_TRUE(found);
|
|
}
|
|
|
|
// Deaths that are not a BasicAttack killing blow carry no direction.
|
|
TEST_F(DestroyableTest, SmashWithoutAttackHasNoDirection) {
|
|
const auto sent = PacketTestUtils::Capture([&] { destroyableComponent->Smash(0x1000000000000001LL); });
|
|
const auto die = FindGameMessage<GameMessages::Die>(sent, MessageType::Game::DIE);
|
|
ASSERT_TRUE(die.has_value());
|
|
EXPECT_EQ(die->directionRelative_AngleXZ, 0.0f);
|
|
EXPECT_EQ(die->directionRelative_AngleY, 0.0f);
|
|
EXPECT_EQ(die->directionRelative_Force, 0.0f);
|
|
}
|