using System; using System.Buffers.Binary; using System.Text; using AcDream.Core.Chat; using AcDream.Core.Combat; using AcDream.Core.Items; using AcDream.Core.Net; using AcDream.Core.Net.Messages; using AcDream.Core.Spells; using Xunit; namespace AcDream.Core.Net.Tests; public sealed class GameEventWiringTests { private static byte[] MakeString16L(string s) { byte[] data = Encoding.ASCII.GetBytes(s); int recordSize = 2 + data.Length; int padding = (4 - (recordSize & 3)) & 3; byte[] result = new byte[recordSize + padding]; BinaryPrimitives.WriteUInt16LittleEndian(result, (ushort)data.Length); Array.Copy(data, 0, result, 2, data.Length); return result; } private static byte[] WrapEnvelope(GameEventType type, byte[] payload) { byte[] body = new byte[GameEventEnvelope.HeaderSize + payload.Length]; BinaryPrimitives.WriteUInt32LittleEndian(body, GameEventEnvelope.Opcode); BinaryPrimitives.WriteUInt32LittleEndian(body.AsSpan(4), 0u); BinaryPrimitives.WriteUInt32LittleEndian(body.AsSpan(8), 0u); BinaryPrimitives.WriteUInt32LittleEndian(body.AsSpan(12), (uint)type); Array.Copy(payload, 0, body, GameEventEnvelope.HeaderSize, payload.Length); return body; } private static (GameEventDispatcher, ItemRepository, CombatState, Spellbook, ChatLog) MakeAll() { var dispatcher = new GameEventDispatcher(); var items = new ItemRepository(); var combat = new CombatState(); var spellbook = new Spellbook(); var chat = new ChatLog(); GameEventWiring.WireAll(dispatcher, items, combat, spellbook, chat); return (dispatcher, items, combat, spellbook, chat); } [Fact] public void WireAll_ChannelBroadcast_RoutesToChatLog() { var (d, _, _, _, chat) = MakeAll(); byte[] payload = new byte[4 + MakeString16L("Alice").Length + MakeString16L("hi").Length]; BinaryPrimitives.WriteUInt32LittleEndian(payload, 42); int p = 4; var senderBytes = MakeString16L("Alice"); Array.Copy(senderBytes, 0, payload, p, senderBytes.Length); p += senderBytes.Length; var msgBytes = MakeString16L("hi"); Array.Copy(msgBytes, 0, payload, p, msgBytes.Length); var env = GameEventEnvelope.TryParse(WrapEnvelope(GameEventType.ChannelBroadcast, payload)); d.Dispatch(env!.Value); Assert.Equal(1, chat.Count); var entry = chat.Snapshot()[0]; Assert.Equal(ChatKind.Channel, entry.Kind); Assert.Equal("Alice", entry.Sender); } [Fact] public void WireAll_UpdateHealth_RoutesToCombatState() { var (d, _, combat, _, _) = MakeAll(); byte[] payload = new byte[8]; BinaryPrimitives.WriteUInt32LittleEndian(payload, 0xCAFE); BinaryPrimitives.WriteSingleLittleEndian(payload.AsSpan(4), 0.42f); var env = GameEventEnvelope.TryParse(WrapEnvelope(GameEventType.UpdateHealth, payload)); d.Dispatch(env!.Value); Assert.Equal(0.42f, combat.GetHealthPercent(0xCAFE), 4); } [Fact] public void WireAll_MagicUpdateSpell_RoutesToSpellbook() { var (d, _, _, book, _) = MakeAll(); byte[] payload = new byte[4]; BinaryPrimitives.WriteUInt32LittleEndian(payload, 0x3E1); var env = GameEventEnvelope.TryParse(WrapEnvelope(GameEventType.MagicUpdateSpell, payload)); d.Dispatch(env!.Value); Assert.True(book.Knows(0x3E1)); } [Fact] public void WireAll_WieldObject_RoutesToItemRepository() { var (d, items, _, _, _) = MakeAll(); items.AddOrUpdate(new ItemInstance { ObjectId = 0x1000, WeenieClassId = 1 }); byte[] payload = new byte[12]; BinaryPrimitives.WriteUInt32LittleEndian(payload, 0x1000); BinaryPrimitives.WriteUInt32LittleEndian(payload.AsSpan(4), (uint)EquipMask.MeleeWeapon); BinaryPrimitives.WriteUInt32LittleEndian(payload.AsSpan(8), 0x2000); var env = GameEventEnvelope.TryParse(WrapEnvelope(GameEventType.WieldObject, payload)); d.Dispatch(env!.Value); var item = items.GetItem(0x1000); Assert.NotNull(item); Assert.Equal(EquipMask.MeleeWeapon, item!.CurrentlyEquippedLocation); Assert.Equal(0x2000u, item.ContainerId); } [Fact] public void WireAll_PopupString_RoutesToChatLog() { var (d, _, _, _, chat) = MakeAll(); byte[] payload = MakeString16L("A modal message"); var env = GameEventEnvelope.TryParse(WrapEnvelope(GameEventType.PopupString, payload)); d.Dispatch(env!.Value); Assert.Equal(1, chat.Count); Assert.Equal(ChatKind.Popup, chat.Snapshot()[0].Kind); } [Fact] public void WireAll_MagicPurgeEnchantments_CallsOnPurgeAll() { var (d, _, _, book, _) = MakeAll(); book.OnEnchantmentAdded(1, 1, 100f, 0); book.OnEnchantmentAdded(2, 2, 100f, 0); Assert.Equal(2, book.ActiveCount); var env = GameEventEnvelope.TryParse(WrapEnvelope(GameEventType.MagicPurgeEnchantments, Array.Empty())); d.Dispatch(env!.Value); Assert.Equal(0, book.ActiveCount); } }