fix: a player with PVP on is not its own enemy

LWOCharacterComponent::SendMessage (0x00d34330), IsValidPVPTarget: the
target is never the player itself. LWODestroyableComponent's IsEnemy asks
it for every character. DLU called a player with PVP on an enemy of
itself.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Aaron Kimbrell
2026-11-10 13:07:27 -06:00
parent 0a0e37e96f
commit 9bbe335acc
2 changed files with 37 additions and 0 deletions

View File

@@ -532,6 +532,8 @@ void DestroyableComponent::AddFaction(const int32_t factionID, const bool ignore
bool DestroyableComponent::IsEnemy(const Entity* other) const {
if (IsEnemyImplentation.ExecuteWithDefault(other, false)) return true;
if (m_Parent->IsPlayer() && other->IsPlayer()) {
// A player is never its own PVP target
if (other == m_Parent) return false;
auto* thisCharacterComponent = m_Parent->GetComponent<CharacterComponent>();
if (!thisCharacterComponent) return false;
auto* otherCharacterComponent = other->GetComponent<CharacterComponent>();

View File

@@ -622,6 +622,41 @@ TEST_F(DestroyableTest, PlayerDieDoesNotClaimAClientDeath) {
player.SetCharacter(nullptr);
}
// LWOCharacterComponent::SendMessage, IsValidPVPTarget: two players with PVP on are enemies, a player is never its own
TEST_F(DestroyableTest, APlayerWithPvpIsNotItsOwnEnemy) {
auto playerInfo = info;
playerInfo.lot = 1;
SystemAddress address;
address.binaryAddress = 0x0100007f;
address.port = 2003;
Entity player(0x1000000000000001LL, playerInfo);
Character character(1, nullptr);
player.SetCharacter(&character);
character.SetEntity(&player);
auto* playerCharacter = player.AddComponent<CharacterComponent>(-1, &character, address);
auto* destroyable = player.AddComponent<DestroyableComponent>(-1);
Entity other(0x1000000000000002LL, playerInfo);
Character otherCharacter(2, nullptr);
other.SetCharacter(&otherCharacter);
otherCharacter.SetEntity(&other);
auto* otherCharacterComponent = other.AddComponent<CharacterComponent>(-1, &otherCharacter, address);
other.AddComponent<DestroyableComponent>(-1);
ASSERT_TRUE(player.IsPlayer());
ASSERT_TRUE(other.IsPlayer());
EXPECT_FALSE(destroyable->IsEnemy(&other));
playerCharacter->SetPvpEnabled(true);
EXPECT_FALSE(destroyable->IsEnemy(&other));
otherCharacterComponent->SetPvpEnabled(true);
EXPECT_TRUE(destroyable->IsEnemy(&other));
EXPECT_FALSE(destroyable->IsEnemy(&player));
player.SetCharacter(nullptr);
other.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; };