feat(net): Map/House panel — slice 4a, House wire parsing groundwork

Adds the outbound HouseQuery action (0x021E, ClientCommandRequests.
BuildHouseQuery / WorldSession.SendHouseQuery — ACE GameActionHouseQuery.
Handle reads no payload) and inbound parsers for all four House wire
opcodes GameEventType already defined (0x0225-0x0228, gmHouseUI::PostInit's
registered notice handlers): GameEvents.ParseHouseData (BuyTime/RentTime/
Type/MaintenanceFree/Buy list/Rent list/Position — the Position field
reuses CreateObject.ServerPosition's existing 32-byte Cell+Pos.XYZ+
Rotation.WXYZ shape rather than a new type), ParseHouseStatus (WeenieError
u32), ParseUpdateRentTime, ParseUpdateRentPayment. Wire shapes verified
against ACE's HouseDataExtensions/HousePaymentExtensions (references/ACE/
Source/ACE.Server/Network/Structure/HouseData.cs, HousePayment.cs) — noted
that ACE's own UpdateRentTime/UpdateRentPayment writers are stubs (always
0u / always an empty list), captured as such rather than assumed live.

GameEventWiring.WireAll gets four new optional delegate holes
(onHouseData/onHouseStatus/onHouseUpdateRentTime/onHouseUpdateRentPayment)
following the exact trade-family precedent — registered only when non-null,
every existing caller compiles unchanged.

This is the "enum/parser groundwork" half of Slice 4's pre-authorized
fallback. NOT included (filed as an ISSUES entry): a RuntimeHouseState
GameRuntime owner (construction-transaction ceremony, fault-injection
points, disposal/convergence tracking — the same weight as
RuntimeTradeState's integration, judged disproportionate for tonight
alongside the completed Map tab), HousePageController's real Lines/
OnShown wiring, the DisplayPurchaseTimeText port, and the six other
Display* line builders. The House tab currently mounts with genuinely
empty content, matching retail's own PostInit (verified via
MapHousePanelSlotProbeTests' live-DAT probe, not assumed).

9 new HouseEventsTests (parser round-trips + truncation), 1 new
GameEventWiringTests case (all four opcodes reach their callbacks).
Core.Net.Tests: 1004/1004 passed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-08-17 02:09:34 +02:00
parent 6b0fa4ff0d
commit dd6c7e09f0
6 changed files with 381 additions and 1 deletions

View file

@ -1771,6 +1771,48 @@ public sealed class GameEventWiringTests
Assert.True(observed.Value.IsLoggedIn);
}
/// <summary>Batch C (Map/House toolbar panel, 2026-08-17): all four
/// house wire opcodes (0x0225-0x0228) reach their registered
/// callbacks.</summary>
[Fact]
public void WireAll_HouseFamily_ReachesTheirCallbacks()
{
var dispatcher = new GameEventDispatcher();
GameEvents.HouseData? data = null;
uint? status = null;
uint? rentTime = null;
IReadOnlyList<GameEvents.HousePayment>? rentPayment = null;
GameEventWiring.WireAll(
dispatcher, new ClientObjectTable(), new CombatState(), new Spellbook(), new ChatLog(),
onHouseData: d => data = d,
onHouseStatus: code => status = code,
onHouseUpdateRentTime: t => rentTime = t,
onHouseUpdateRentPayment: p => rentPayment = p);
byte[] houseDataWire = new AceWireWriter()
.Write(0u).Write(0u).Write(0u).Write(0u)
.Write(0).Write(0)
.Write(0x00120001u)
.Write(0f).Write(0f).Write(0f)
.Write(1f).Write(0f).Write(0f).Write(0f)
.ToArray();
dispatcher.Dispatch(GameEventEnvelope.TryParse(
WrapEnvelope(GameEventType.HouseData, houseDataWire))!.Value);
dispatcher.Dispatch(GameEventEnvelope.TryParse(WrapEnvelope(
GameEventType.HouseStatus, new AceWireWriter().Write(0u).ToArray()))!.Value);
dispatcher.Dispatch(GameEventEnvelope.TryParse(WrapEnvelope(
GameEventType.UpdateRentTime, new AceWireWriter().Write(1_700_000_000u).ToArray()))!.Value);
dispatcher.Dispatch(GameEventEnvelope.TryParse(WrapEnvelope(
GameEventType.UpdateRentPayment, new AceWireWriter().Write(0).ToArray()))!.Value);
Assert.NotNull(data);
Assert.Equal(0x00120001u, data!.Value.Position.LandblockId);
Assert.Equal(0u, status);
Assert.Equal(1_700_000_000u, rentTime);
Assert.NotNull(rentPayment);
Assert.Empty(rentPayment);
}
private static byte[] BuildEnchantment(
ushort spellId,
ushort layer,