fix: complete retail parity stability pass
All checks were successful
CI / linux-portable (push) Successful in 3m41s
CI / windows-gate (push) Successful in 6m49s
CI / release (push) Successful in 3m22s

This commit is contained in:
Erik 2026-08-28 20:01:39 +02:00
parent d3df4cb20a
commit f7aa8e0eb7
131 changed files with 7765 additions and 1190 deletions

View file

@ -60,6 +60,41 @@ public static class ClientCommandRequests
public const uint AddPlayerPermissionOpcode = 0x0219u;
public const uint RemovePlayerPermissionOpcode = 0x021Au;
public const uint AbandonHouseOpcode = 0x021Fu;
public const uint QueryAllegianceNameOpcode = 0x0030u;
public const uint ClearAllegianceNameOpcode = 0x0031u;
public const uint SetAllegianceNameOpcode = 0x0033u;
public const uint SetAllegianceOfficerOpcode = 0x003Bu;
public const uint SetAllegianceOfficerTitleOpcode = 0x003Cu;
public const uint ListAllegianceOfficerTitlesOpcode = 0x003Du;
public const uint ClearAllegianceOfficerTitlesOpcode = 0x003Eu;
public const uint DoAllegianceLockActionOpcode = 0x003Fu;
public const uint SetAllegianceApprovedVassalOpcode = 0x0040u;
public const uint AllegianceChatGagOpcode = 0x0041u;
public const uint DoAllegianceHouseActionOpcode = 0x0042u;
public const uint AddPermanentGuestOpcode = 0x0245u;
public const uint RemovePermanentGuestOpcode = 0x0246u;
public const uint SetOpenHouseStatusOpcode = 0x0247u;
public const uint ChangeStoragePermissionOpcode = 0x0249u;
public const uint BootSpecificHouseGuestOpcode = 0x024Au;
public const uint RemoveAllStoragePermissionOpcode = 0x024Cu;
public const uint RequestFullGuestListOpcode = 0x024Du;
public const uint SetMotdOpcode = 0x0254u;
public const uint QueryMotdOpcode = 0x0255u;
public const uint ClearMotdOpcode = 0x0256u;
public const uint AddAllStoragePermissionOpcode = 0x025Cu;
public const uint RemoveAllPermanentGuestsOpcode = 0x025Eu;
public const uint BootEveryoneOpcode = 0x025Fu;
public const uint SetHooksVisibilityOpcode = 0x0266u;
public const uint ModifyAllegianceGuestPermissionOpcode = 0x0267u;
public const uint ModifyAllegianceStoragePermissionOpcode = 0x0268u;
public const uint BreakAllegianceBootOpcode = 0x0277u;
public const uint AllegianceChatBootOpcode = 0x02A0u;
public const uint AddAllegianceBanOpcode = 0x02A1u;
public const uint RemoveAllegianceBanOpcode = 0x02A2u;
public const uint ListAllegianceBansOpcode = 0x02A3u;
public const uint RemoveAllegianceOfficerOpcode = 0x02A5u;
public const uint ListAllegianceOfficersOpcode = 0x02A6u;
public const uint ClearAllegianceOfficersOpcode = 0x02A7u;
// Batch C (Map/House toolbar panel, 2026-08-17): the query the House
// tab needs to populate. ACE GameActionHouseQuery.cs: [GameAction(
// GameActionType.HouseQuery)] (0x021E), Handle reads no payload and
@ -283,8 +318,9 @@ public static class ClientCommandRequests
public static byte[] BuildRecallAllegianceHometown(uint sequence) =>
BuildParameterless(sequence, RecallAllegianceHometownOpcode);
// "@allegiance info [name]" — GameActionAllegianceInfoRequest.Handle:
// ReadString16L() player name (empty string = self).
// "@allegiance info <name>" — GameActionAllegianceInfoRequest.Handle:
// ReadString16L() player name. Retail refuses an empty argument before
// constructing this request.
public static byte[] BuildAllegianceInfoRequest(uint sequence, string playerName) =>
BuildString(sequence, AllegianceInfoRequestOpcode, playerName);
@ -308,6 +344,133 @@ public static class ClientCommandRequests
public static byte[] BuildAbandonHouse(uint sequence) =>
BuildParameterless(sequence, AbandonHouseOpcode);
// Complete named-retail @allegiance/@house administration family.
// Opcodes and field order are cross-checked against ACE's individual
// GameAction readers; the client-side grammar is ported separately by
// ClientCommandController from DoAllegiance/DoHouse and their helpers.
public static byte[] BuildBreakAllegianceBoot(
uint sequence, string playerName, bool accountBoot) =>
BuildStringUInt32(
sequence, BreakAllegianceBootOpcode, playerName, accountBoot ? 1u : 0u);
public static byte[] BuildAllegianceChatBoot(
uint sequence, string playerName, string reason) =>
BuildTwoStrings(sequence, AllegianceChatBootOpcode, playerName, reason);
public static byte[] BuildAllegianceChatGag(
uint sequence, string playerName, bool enabled) =>
BuildStringUInt32(
sequence, AllegianceChatGagOpcode, playerName, enabled ? 1u : 0u);
public static byte[] BuildAddAllegianceBan(uint sequence, string playerName) =>
BuildString(sequence, AddAllegianceBanOpcode, playerName);
public static byte[] BuildRemoveAllegianceBan(uint sequence, string playerName) =>
BuildString(sequence, RemoveAllegianceBanOpcode, playerName);
public static byte[] BuildListAllegianceBans(uint sequence) =>
BuildParameterless(sequence, ListAllegianceBansOpcode);
public static byte[] BuildSetAllegianceOfficer(
uint sequence, string playerName, uint officerLevel) =>
BuildStringUInt32(
sequence, SetAllegianceOfficerOpcode, playerName, officerLevel);
public static byte[] BuildRemoveAllegianceOfficer(
uint sequence, string playerName) =>
BuildString(sequence, RemoveAllegianceOfficerOpcode, playerName);
public static byte[] BuildListAllegianceOfficers(uint sequence) =>
BuildParameterless(sequence, ListAllegianceOfficersOpcode);
public static byte[] BuildClearAllegianceOfficers(uint sequence) =>
BuildParameterless(sequence, ClearAllegianceOfficersOpcode);
public static byte[] BuildSetAllegianceOfficerTitle(
uint sequence, uint officerLevel, string title) =>
BuildUInt32String(
sequence, SetAllegianceOfficerTitleOpcode, officerLevel, title);
public static byte[] BuildListAllegianceOfficerTitles(uint sequence) =>
BuildParameterless(sequence, ListAllegianceOfficerTitlesOpcode);
public static byte[] BuildClearAllegianceOfficerTitles(uint sequence) =>
BuildParameterless(sequence, ClearAllegianceOfficerTitlesOpcode);
public static byte[] BuildQueryAllegianceName(uint sequence) =>
BuildParameterless(sequence, QueryAllegianceNameOpcode);
public static byte[] BuildSetAllegianceName(uint sequence, string name) =>
BuildString(sequence, SetAllegianceNameOpcode, name);
public static byte[] BuildClearAllegianceName(uint sequence) =>
BuildParameterless(sequence, ClearAllegianceNameOpcode);
public static byte[] BuildAllegianceLockAction(uint sequence, uint action) =>
BuildUInt32(sequence, DoAllegianceLockActionOpcode, action);
public static byte[] BuildSetAllegianceApprovedVassal(
uint sequence, string playerName) =>
BuildString(sequence, SetAllegianceApprovedVassalOpcode, playerName);
public static byte[] BuildAllegianceHouseAction(uint sequence, uint action) =>
BuildUInt32(sequence, DoAllegianceHouseActionOpcode, action);
public static byte[] BuildQueryMotd(uint sequence) =>
BuildParameterless(sequence, QueryMotdOpcode);
public static byte[] BuildSetMotd(uint sequence, string motd) =>
BuildString(sequence, SetMotdOpcode, motd);
public static byte[] BuildClearMotd(uint sequence) =>
BuildParameterless(sequence, ClearMotdOpcode);
public static byte[] BuildSetOpenHouseStatus(uint sequence, bool isOpen) =>
BuildUInt32(sequence, SetOpenHouseStatusOpcode, isOpen ? 1u : 0u);
public static byte[] BuildAddPermanentGuest(uint sequence, string playerName) =>
BuildString(sequence, AddPermanentGuestOpcode, playerName);
public static byte[] BuildRemovePermanentGuest(uint sequence, string playerName) =>
BuildString(sequence, RemovePermanentGuestOpcode, playerName);
public static byte[] BuildRemoveAllPermanentGuests(uint sequence) =>
BuildParameterless(sequence, RemoveAllPermanentGuestsOpcode);
public static byte[] BuildChangeStoragePermission(
uint sequence, string playerName, bool enabled) =>
BuildStringUInt32(
sequence, ChangeStoragePermissionOpcode, playerName, enabled ? 1u : 0u);
public static byte[] BuildAddAllStoragePermission(uint sequence) =>
BuildParameterless(sequence, AddAllStoragePermissionOpcode);
public static byte[] BuildRemoveAllStoragePermission(uint sequence) =>
BuildParameterless(sequence, RemoveAllStoragePermissionOpcode);
public static byte[] BuildRequestFullGuestList(uint sequence) =>
BuildParameterless(sequence, RequestFullGuestListOpcode);
public static byte[] BuildBootSpecificHouseGuest(
uint sequence, string playerName) =>
BuildString(sequence, BootSpecificHouseGuestOpcode, playerName);
public static byte[] BuildBootEveryone(uint sequence) =>
BuildParameterless(sequence, BootEveryoneOpcode);
public static byte[] BuildSetHooksVisibility(uint sequence, bool visible) =>
BuildUInt32(sequence, SetHooksVisibilityOpcode, visible ? 1u : 0u);
public static byte[] BuildModifyAllegianceGuestPermission(
uint sequence, bool enabled) =>
BuildUInt32(
sequence, ModifyAllegianceGuestPermissionOpcode, enabled ? 1u : 0u);
public static byte[] BuildModifyAllegianceStoragePermission(
uint sequence, bool enabled) =>
BuildUInt32(
sequence, ModifyAllegianceStoragePermissionOpcode, enabled ? 1u : 0u);
// Queries the local player's house info (owned house data, or a
// no-house status) — GameActionHouseQuery.Handle: no payload read.
public static byte[] BuildHouseQuery(uint sequence) =>
@ -337,6 +500,39 @@ public static class ClientCommandRequests
return body;
}
private static byte[] BuildStringUInt32(
uint sequence, uint opcode, string value, uint number)
{
byte[] packed = PackString16L(value);
byte[] body = CreateBody(sequence, opcode, packed.Length + 4);
packed.CopyTo(body, 12);
BinaryPrimitives.WriteUInt32LittleEndian(
body.AsSpan(12 + packed.Length), number);
return body;
}
private static byte[] BuildUInt32String(
uint sequence, uint opcode, uint number, string value)
{
byte[] packed = PackString16L(value);
byte[] body = CreateBody(sequence, opcode, 4 + packed.Length);
BinaryPrimitives.WriteUInt32LittleEndian(body.AsSpan(12), number);
packed.CopyTo(body, 16);
return body;
}
private static byte[] BuildTwoStrings(
uint sequence, uint opcode, string first, string second)
{
byte[] packedFirst = PackString16L(first);
byte[] packedSecond = PackString16L(second);
byte[] body = CreateBody(
sequence, opcode, packedFirst.Length + packedSecond.Length);
packedFirst.CopyTo(body, 12);
packedSecond.CopyTo(body, 12 + packedFirst.Length);
return body;
}
private static byte[] CreateBody(uint sequence, uint opcode, int payloadLength)
{
byte[] body = new byte[12 + payloadLength];