325 lines
9.1 KiB
C#
325 lines
9.1 KiB
C#
using AcDream.Core.Combat;
|
|
using AcDream.Core.Items;
|
|
using AcDream.Core.Net;
|
|
using AcDream.Core.Net.Messages;
|
|
using AcDream.Core.Physics;
|
|
using AcDream.Core.Spells;
|
|
using AcDream.Runtime.Entities;
|
|
using AcDream.Runtime.Gameplay;
|
|
|
|
namespace AcDream.Runtime.Tests.Gameplay;
|
|
|
|
public sealed class RuntimeHostileTargetQueryTests
|
|
{
|
|
private const uint Player = 0x50000001u;
|
|
|
|
[Fact]
|
|
public void FindClosest_UsesAbsoluteDerethCoordinatesAcrossLandblocks()
|
|
{
|
|
using GameRuntime runtime = Create();
|
|
runtime.PlayerIdentity.ServerGuid = Player;
|
|
Add(
|
|
runtime,
|
|
Player,
|
|
landblock: 0x01010001u,
|
|
x: 191f,
|
|
y: 100f,
|
|
PlayerObject(Player));
|
|
Add(
|
|
runtime,
|
|
0x50000010u,
|
|
landblock: 0x02010001u,
|
|
x: 1f,
|
|
y: 100f,
|
|
Hostile(0x50000010u));
|
|
Add(
|
|
runtime,
|
|
0x50000011u,
|
|
landblock: 0x01010001u,
|
|
x: 180f,
|
|
y: 100f,
|
|
Hostile(0x50000011u));
|
|
|
|
Assert.Equal(
|
|
0x50000010u,
|
|
RuntimeHostileTargetQuery.FindClosest(runtime));
|
|
Assert.True(
|
|
RuntimeHostileTargetQuery.IsHostile(
|
|
runtime,
|
|
0x50000010u));
|
|
}
|
|
|
|
[Fact]
|
|
public void Query_RejectsHiddenNoDrawDeadPlayersPetsNpcsAndNonCreatures()
|
|
{
|
|
using GameRuntime runtime = Create();
|
|
runtime.PlayerIdentity.ServerGuid = Player;
|
|
Add(runtime, Player, 0x01010001u, 10f, 10f, PlayerObject(Player));
|
|
Add(
|
|
runtime,
|
|
0x50000010u,
|
|
0x01010001u,
|
|
11f,
|
|
10f,
|
|
Hostile(0x50000010u),
|
|
PhysicsStateFlags.Hidden);
|
|
Add(
|
|
runtime,
|
|
0x50000011u,
|
|
0x01010001u,
|
|
12f,
|
|
10f,
|
|
Hostile(0x50000011u),
|
|
PhysicsStateFlags.NoDraw);
|
|
Add(
|
|
runtime,
|
|
0x50000012u,
|
|
0x01010001u,
|
|
13f,
|
|
10f,
|
|
Hostile(0x50000012u));
|
|
runtime.ActionOwner.Combat.OnUpdateHealth(0x50000012u, 0f);
|
|
Add(
|
|
runtime,
|
|
0x50000013u,
|
|
0x01010001u,
|
|
14f,
|
|
10f,
|
|
PlayerObject(0x50000013u));
|
|
Add(
|
|
runtime,
|
|
0x50000014u,
|
|
0x01010001u,
|
|
15f,
|
|
10f,
|
|
Hostile(0x50000014u, petOwner: Player));
|
|
Add(
|
|
runtime,
|
|
0x50000015u,
|
|
0x01010001u,
|
|
16f,
|
|
10f,
|
|
new ClientObject
|
|
{
|
|
ObjectId = 0x50000015u,
|
|
Type = ItemType.Misc,
|
|
PublicWeenieBitfield =
|
|
SelectedObjectHealthPolicy.BfAttackable,
|
|
});
|
|
Add(
|
|
runtime,
|
|
0x50000016u,
|
|
0x01010001u,
|
|
17f,
|
|
10f,
|
|
new ClientObject
|
|
{
|
|
ObjectId = 0x50000016u,
|
|
Type = ItemType.Creature,
|
|
});
|
|
Add(
|
|
runtime,
|
|
0x50000017u,
|
|
0x01010001u,
|
|
18f,
|
|
10f,
|
|
Hostile(0x50000017u));
|
|
|
|
Assert.Equal(
|
|
0x50000017u,
|
|
RuntimeHostileTargetQuery.FindClosest(runtime));
|
|
Assert.False(RuntimeHostileTargetQuery.IsHostile(runtime, 0u));
|
|
Assert.False(
|
|
RuntimeHostileTargetQuery.IsHostile(
|
|
runtime,
|
|
0x50000010u));
|
|
Assert.False(
|
|
RuntimeHostileTargetQuery.IsHostile(
|
|
runtime,
|
|
0x50000012u));
|
|
Assert.False(
|
|
RuntimeHostileTargetQuery.IsHostile(
|
|
runtime,
|
|
0x50000014u));
|
|
Assert.True(
|
|
RuntimeHostileTargetQuery.IsHostile(
|
|
runtime,
|
|
0x50000017u));
|
|
}
|
|
|
|
[Fact]
|
|
public void FindClosest_ReturnsNullWithoutPlayerPositionOrHostile()
|
|
{
|
|
using GameRuntime runtime = Create();
|
|
runtime.PlayerIdentity.ServerGuid = Player;
|
|
runtime.InventoryOwner.Objects.AddOrUpdate(PlayerObject(Player));
|
|
|
|
Assert.Null(RuntimeHostileTargetQuery.FindClosest(runtime));
|
|
|
|
Add(runtime, Player, 0x01010001u, 10f, 10f, PlayerObject(Player));
|
|
Add(
|
|
runtime,
|
|
0x50000010u,
|
|
0x01010001u,
|
|
11f,
|
|
10f,
|
|
new ClientObject
|
|
{
|
|
ObjectId = 0x50000010u,
|
|
Type = ItemType.Creature,
|
|
});
|
|
|
|
Assert.Null(RuntimeHostileTargetQuery.FindClosest(runtime));
|
|
}
|
|
|
|
private static GameRuntime Create()
|
|
{
|
|
var operations = new Operations();
|
|
return new GameRuntime(new GameRuntimeDependencies(
|
|
operations,
|
|
operations,
|
|
operations,
|
|
operations));
|
|
}
|
|
|
|
private static ClientObject PlayerObject(uint id) => new()
|
|
{
|
|
ObjectId = id,
|
|
Type = ItemType.Creature,
|
|
PublicWeenieBitfield =
|
|
SelectedObjectHealthPolicy.BfPlayer,
|
|
};
|
|
|
|
private static ClientObject Hostile(
|
|
uint id,
|
|
uint petOwner = 0u) => new()
|
|
{
|
|
ObjectId = id,
|
|
Type = ItemType.Creature,
|
|
PublicWeenieBitfield =
|
|
SelectedObjectHealthPolicy.BfAttackable,
|
|
PetOwnerId = petOwner,
|
|
};
|
|
|
|
private static void Add(
|
|
GameRuntime runtime,
|
|
uint guid,
|
|
uint landblock,
|
|
float x,
|
|
float y,
|
|
ClientObject item,
|
|
PhysicsStateFlags state = 0)
|
|
{
|
|
RuntimeEntityRecord record = runtime.EntityObjects
|
|
.RegisterEntity(
|
|
Spawn(guid, landblock, x, y, state))
|
|
.Canonical!;
|
|
Assert.True(runtime.EntityObjects.ApplyAcceptedSpawn(
|
|
record,
|
|
record.CreateIntegrationVersion,
|
|
record.Snapshot,
|
|
replaceGeneration: false));
|
|
runtime.InventoryOwner.Objects.AddOrUpdate(item);
|
|
}
|
|
|
|
private static WorldSession.EntitySpawn Spawn(
|
|
uint guid,
|
|
uint landblock,
|
|
float x,
|
|
float y,
|
|
PhysicsStateFlags state)
|
|
{
|
|
var position = new CreateObject.ServerPosition(
|
|
landblock,
|
|
x,
|
|
y,
|
|
5f,
|
|
1f,
|
|
0f,
|
|
0f,
|
|
0f);
|
|
var timestamps = new PhysicsTimestamps(
|
|
Position: 1,
|
|
Movement: 1,
|
|
State: 1,
|
|
Vector: 1,
|
|
Teleport: 0,
|
|
ServerControlledMove: 1,
|
|
ForcePosition: 0,
|
|
ObjDesc: 1,
|
|
Instance: 1);
|
|
var physics = new PhysicsSpawnData(
|
|
RawState: (uint)state,
|
|
Position: position,
|
|
Movement: null,
|
|
AnimationFrame: null,
|
|
SetupTableId: 0x02000001u,
|
|
MotionTableId: null,
|
|
SoundTableId: null,
|
|
PhysicsScriptTableId: null,
|
|
Parent: null,
|
|
Children: null,
|
|
Scale: null,
|
|
Friction: null,
|
|
Elasticity: null,
|
|
Translucency: null,
|
|
Velocity: null,
|
|
Acceleration: null,
|
|
AngularVelocity: null,
|
|
DefaultScriptType: null,
|
|
DefaultScriptIntensity: null,
|
|
Timestamps: timestamps);
|
|
return new WorldSession.EntitySpawn(
|
|
guid,
|
|
position,
|
|
0x02000001u,
|
|
[],
|
|
[],
|
|
[],
|
|
null,
|
|
null,
|
|
guid.ToString("X8"),
|
|
null,
|
|
null,
|
|
null,
|
|
PhysicsState: physics.RawState,
|
|
InstanceSequence: 1,
|
|
MovementSequence: 1,
|
|
ServerControlSequence: 1,
|
|
PositionSequence: 1,
|
|
Physics: physics);
|
|
}
|
|
|
|
private sealed class Operations :
|
|
IRuntimeCombatAttackOperations,
|
|
IRuntimeCombatTargetOperations,
|
|
IRuntimeCombatModeOperations,
|
|
IRuntimeSpellCastOperations
|
|
{
|
|
public bool CanStartAttack() => false;
|
|
public void PrepareAttackRequest() { }
|
|
public bool SendAttack(AttackHeight height, float power) => false;
|
|
public void SendCancelAttack() { }
|
|
public bool IsDualWield => false;
|
|
public bool PlayerReadyForAttack => false;
|
|
public bool AutoRepeatAttack => false;
|
|
public bool AutoTarget => false;
|
|
public uint? SelectClosestTarget() => null;
|
|
public bool IsInWorld => false;
|
|
public IReadOnlyList<ClientObject> GetOrderedEquipment() => [];
|
|
public void NotifyExplicitCombatModeRequest() { }
|
|
public void SendChangeCombatMode(CombatMode mode) { }
|
|
public uint LocalPlayerId => 0u;
|
|
public bool CanSend => false;
|
|
public bool HasRequiredComponents(uint spellId) => false;
|
|
public bool IsTargetCompatible(
|
|
uint targetId,
|
|
SpellMetadata spell,
|
|
bool showMessage) => false;
|
|
public void StopCompletely() { }
|
|
public void SendUntargeted(uint spellId) { }
|
|
public void SendTargeted(uint targetId, uint spellId) { }
|
|
public void DisplayMessage(string message) { }
|
|
public void IncrementBusy() { }
|
|
}
|
|
}
|