feat(combat): Phase L.1c wire live attack input
This commit is contained in:
parent
d1fb68f419
commit
4874d8595a
6 changed files with 367 additions and 11 deletions
99
tests/AcDream.Core.Net.Tests/Messages/CreateObjectTests.cs
Normal file
99
tests/AcDream.Core.Net.Tests/Messages/CreateObjectTests.cs
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
using System.Buffers.Binary;
|
||||
using System.Text;
|
||||
using AcDream.Core.Items;
|
||||
using AcDream.Core.Net.Messages;
|
||||
|
||||
namespace AcDream.Core.Net.Tests.Messages;
|
||||
|
||||
public sealed class CreateObjectTests
|
||||
{
|
||||
[Fact]
|
||||
public void TryParse_WeenieHeaderPrefix_ReturnsNameAndItemType()
|
||||
{
|
||||
byte[] body = BuildMinimalCreateObjectWithWeenieHeader(
|
||||
guid: 0x50000002u,
|
||||
name: "Drudge",
|
||||
itemType: (uint)ItemType.Creature);
|
||||
|
||||
var parsed = CreateObject.TryParse(body);
|
||||
|
||||
Assert.NotNull(parsed);
|
||||
Assert.Equal(0x50000002u, parsed.Value.Guid);
|
||||
Assert.Equal("Drudge", parsed.Value.Name);
|
||||
Assert.Equal((uint)ItemType.Creature, parsed.Value.ItemType);
|
||||
}
|
||||
|
||||
private static byte[] BuildMinimalCreateObjectWithWeenieHeader(
|
||||
uint guid,
|
||||
string name,
|
||||
uint itemType)
|
||||
{
|
||||
var bytes = new List<byte>();
|
||||
WriteU32(bytes, CreateObject.Opcode);
|
||||
WriteU32(bytes, guid);
|
||||
|
||||
// ModelData header: marker, subpalette count, texture count, animpart count.
|
||||
bytes.Add(0x11);
|
||||
bytes.Add(0);
|
||||
bytes.Add(0);
|
||||
bytes.Add(0);
|
||||
|
||||
// PhysicsData: no flags, empty physics state, then 9 sequence stamps.
|
||||
WriteU32(bytes, 0);
|
||||
WriteU32(bytes, 0);
|
||||
for (int i = 0; i < 9; i++)
|
||||
WriteU16(bytes, 0);
|
||||
Align4(bytes);
|
||||
|
||||
// Fixed WeenieHeader prefix per ACE SerializeCreateObject.
|
||||
WriteU32(bytes, 0); // weenieFlags
|
||||
WriteString16L(bytes, name);
|
||||
WritePackedDword(bytes, 0x1234); // WeenieClassId
|
||||
WritePackedDword(bytes, 0); // IconId via known-type writer
|
||||
WriteU32(bytes, itemType);
|
||||
WriteU32(bytes, 0); // ObjectDescriptionFlags
|
||||
Align4(bytes);
|
||||
|
||||
return bytes.ToArray();
|
||||
}
|
||||
|
||||
private static void WriteU32(List<byte> bytes, uint value)
|
||||
{
|
||||
Span<byte> tmp = stackalloc byte[4];
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(tmp, value);
|
||||
bytes.AddRange(tmp.ToArray());
|
||||
}
|
||||
|
||||
private static void WriteU16(List<byte> bytes, ushort value)
|
||||
{
|
||||
Span<byte> tmp = stackalloc byte[2];
|
||||
BinaryPrimitives.WriteUInt16LittleEndian(tmp, value);
|
||||
bytes.AddRange(tmp.ToArray());
|
||||
}
|
||||
|
||||
private static void WritePackedDword(List<byte> bytes, uint value)
|
||||
{
|
||||
if (value <= 0x7FFF)
|
||||
{
|
||||
WriteU16(bytes, (ushort)value);
|
||||
return;
|
||||
}
|
||||
|
||||
WriteU16(bytes, (ushort)(((value >> 16) & 0x7FFF) | 0x8000));
|
||||
WriteU16(bytes, (ushort)(value & 0xFFFF));
|
||||
}
|
||||
|
||||
private static void WriteString16L(List<byte> bytes, string value)
|
||||
{
|
||||
byte[] encoded = Encoding.GetEncoding(1252).GetBytes(value);
|
||||
WriteU16(bytes, checked((ushort)encoded.Length));
|
||||
bytes.AddRange(encoded);
|
||||
Align4(bytes);
|
||||
}
|
||||
|
||||
private static void Align4(List<byte> bytes)
|
||||
{
|
||||
while ((bytes.Count & 3) != 0)
|
||||
bytes.Add(0);
|
||||
}
|
||||
}
|
||||
43
tests/AcDream.Core.Tests/Combat/CombatInputPlannerTests.cs
Normal file
43
tests/AcDream.Core.Tests/Combat/CombatInputPlannerTests.cs
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
using AcDream.Core.Combat;
|
||||
|
||||
namespace AcDream.Core.Tests.Combat;
|
||||
|
||||
public sealed class CombatInputPlannerTests
|
||||
{
|
||||
[Fact]
|
||||
public void ToggleMode_FromNonCombat_UsesDefaultCombatMode()
|
||||
{
|
||||
Assert.Equal(CombatMode.Melee, CombatInputPlanner.ToggleMode(CombatMode.NonCombat));
|
||||
Assert.Equal(
|
||||
CombatMode.Missile,
|
||||
CombatInputPlanner.ToggleMode(CombatMode.NonCombat, CombatMode.Missile));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ToggleMode_FromCombat_ReturnsNonCombat()
|
||||
{
|
||||
Assert.Equal(CombatMode.NonCombat, CombatInputPlanner.ToggleMode(CombatMode.Melee));
|
||||
Assert.Equal(CombatMode.NonCombat, CombatInputPlanner.ToggleMode(CombatMode.Magic));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(CombatAttackAction.Low, AttackHeight.Low)]
|
||||
[InlineData(CombatAttackAction.Medium, AttackHeight.Medium)]
|
||||
[InlineData(CombatAttackAction.High, AttackHeight.High)]
|
||||
public void HeightFor_MapsRetailAttackKeys(CombatAttackAction action, AttackHeight expected)
|
||||
{
|
||||
Assert.Equal(expected, CombatInputPlanner.HeightFor(action));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(CombatMode.Melee, true)]
|
||||
[InlineData(CombatMode.Missile, true)]
|
||||
[InlineData(CombatMode.NonCombat, false)]
|
||||
[InlineData(CombatMode.Magic, false)]
|
||||
public void SupportsTargetedAttack_MatchesRetailExecuteAttackModes(
|
||||
CombatMode mode,
|
||||
bool expected)
|
||||
{
|
||||
Assert.Equal(expected, CombatInputPlanner.SupportsTargetedAttack(mode));
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue