acdream/tests/AcDream.Core.Tests/Combat/SelectedObjectHealthPolicyTests.cs
Erik 2644d1d527 fix(ui): port exact selected health policy
Replace the toolbar's PWD-bit approximation with retail ObjectIsAttackable composed with the exact player and pet short-circuits. Carry second-header PetOwner through CreateObject, session, and ClientObject state so self, pet, and Free-PK cases match retail while friendly NPCs and attackable non-creatures remain name-only.

Co-Authored-By: Codex <codex@openai.com>
2026-07-11 11:42:04 +02:00

96 lines
3.9 KiB
C#

using AcDream.Core.Combat;
using AcDream.Core.Items;
using Xunit;
namespace AcDream.Core.Tests.Combat;
public sealed class SelectedObjectHealthPolicyTests
{
private const uint PlayerId = 0x50000001u;
private static ClientObject Obj(
uint id,
ItemType type = ItemType.Creature,
uint flags = 0,
uint petOwner = 0) => new()
{
ObjectId = id,
Type = type,
PublicWeenieBitfield = flags,
PetOwnerId = petOwner,
};
[Fact]
public void Toolbar_PlayerAndPetShortCircuitAttackability()
{
var local = Obj(PlayerId, flags: SelectedObjectHealthPolicy.BfPlayer);
var otherPlayer = Obj(0x50000002u, flags: SelectedObjectHealthPolicy.BfPlayer);
var pet = Obj(0x50000003u, petOwner: PlayerId);
Assert.True(SelectedObjectHealthPolicy.ShouldQueryHealth(PlayerId, local, otherPlayer));
Assert.True(SelectedObjectHealthPolicy.ShouldQueryHealth(PlayerId, local, pet));
}
[Fact]
public void Toolbar_SelfSelectionGetsHealthQuery()
{
var local = Obj(PlayerId, flags: SelectedObjectHealthPolicy.BfPlayer);
Assert.True(SelectedObjectHealthPolicy.ShouldQueryHealth(PlayerId, local, local));
}
[Fact]
public void Toolbar_AttackableCreatureTrue_FriendlyNpcAndAttackableDoorFalse()
{
var local = Obj(PlayerId, flags: SelectedObjectHealthPolicy.BfPlayer);
var monster = Obj(0x50000010u, flags: SelectedObjectHealthPolicy.BfAttackable);
var friendlyNpc = Obj(0x50000011u);
var door = Obj(0x50000012u, ItemType.Misc, SelectedObjectHealthPolicy.BfAttackable);
Assert.True(SelectedObjectHealthPolicy.ShouldQueryHealth(PlayerId, local, monster));
Assert.False(SelectedObjectHealthPolicy.ShouldQueryHealth(PlayerId, local, friendlyNpc));
Assert.False(SelectedObjectHealthPolicy.ShouldQueryHealth(PlayerId, local, door));
}
[Fact]
public void ObjectIsAttackable_FreePkOnEitherCreatureSideReturnsTrue()
{
var normalPlayer = Obj(PlayerId, flags: SelectedObjectHealthPolicy.BfPlayer);
var freePlayer = Obj(PlayerId,
flags: SelectedObjectHealthPolicy.BfPlayer | SelectedObjectHealthPolicy.BfFreePkStatus);
var normalCreature = Obj(0x50000020u);
var freeCreature = Obj(0x50000021u, flags: SelectedObjectHealthPolicy.BfFreePkStatus);
Assert.True(SelectedObjectHealthPolicy.ObjectIsAttackable(
PlayerId, normalPlayer, freeCreature.ObjectId, freeCreature));
Assert.True(SelectedObjectHealthPolicy.ObjectIsAttackable(
PlayerId, freePlayer, normalCreature.ObjectId, normalCreature));
}
[Fact]
public void ObjectIsAttackable_PlayerRequiresMatchingPkPool()
{
var pkPlayer = Obj(PlayerId,
flags: SelectedObjectHealthPolicy.BfPlayer | SelectedObjectHealthPolicy.BfPlayerKiller);
var pkTarget = Obj(0x50000030u,
flags: SelectedObjectHealthPolicy.BfPlayer | SelectedObjectHealthPolicy.BfPlayerKiller);
var pkLiteTarget = Obj(0x50000031u,
flags: SelectedObjectHealthPolicy.BfPlayer | SelectedObjectHealthPolicy.BfPkLiteStatus);
Assert.True(SelectedObjectHealthPolicy.ObjectIsAttackable(
PlayerId, pkPlayer, pkTarget.ObjectId, pkTarget));
Assert.False(SelectedObjectHealthPolicy.ObjectIsAttackable(
PlayerId, pkPlayer, pkLiteTarget.ObjectId, pkLiteTarget));
}
[Fact]
public void ObjectIsAttackable_ZeroOrSelfTrue_MissingPlayerBlocksNormalCreature()
{
var creature = Obj(0x50000040u, flags: SelectedObjectHealthPolicy.BfAttackable);
Assert.True(SelectedObjectHealthPolicy.ObjectIsAttackable(PlayerId, null, 0u, null));
Assert.True(SelectedObjectHealthPolicy.ObjectIsAttackable(PlayerId, null, PlayerId, null));
Assert.False(SelectedObjectHealthPolicy.ObjectIsAttackable(
PlayerId, null, creature.ObjectId, creature));
}
}