using Ananta.SDK.Serialization; namespace Ananta.Server.Protocol.Client4229938; /// One parsed StoryClientCommand from the C2S story channel. internal sealed record StoryClientCommand4229938( byte TypeMark, uint Nid, string Name, long RpcId, bool HasRpcId, string? PayloadKind, ulong? PayloadVehicleId, List? PayloadSeatIndices, bool? PayloadBool, long? PayloadInt, ulong? PayloadULong, // ★ S002 里希盘查(2026-10-01 从捐赠端 DRMK-PS_09_7k-v0.5-2 移植): // Police = 解出来的 S002 警用 payload;PayloadString = StringPayload(mark 42)。 // ⛑ 42 原来在本文件里**没有**,GATE Lua 回执曾经只能用 44(ULongPayload)。 PoliceStoryPayload4229938? Police = null, string? PayloadString = null); /// /// S002 警用 payload(捐赠端同名 record 照搬)。 /// ⛑ Tag 是 **payload 的 mark**,不是 InteractionId: /// 19=EnterExamRequest / 20=EscortFromVehicleRequest / 21=EscortRequest / /// 18,22=带车 / 23=只带车 / 24=一次盘查选项(InteractionId + SectionId)。 /// internal sealed record PoliceStoryPayload4229938(byte Tag, ulong AgentId = 0, bool StartInteract = false, uint InteractionId = 0, uint SectionId = 0, bool IsStandUp = false, ulong VehicleId = 0, int SeatIndex = -1, int[]? Fines = null); /// /// Parser for the 4229938 story channel (SyncStoryCoreClientInfo, 67130745): /// an inline List7Bit of complex StoryClientCommand entries. /// Command marks: ConfirmRpc 2, Message 4, SetObservable-shaped 5, reliable RpcId-only 3/6. /// Payload marks: Bool 5, Byte 6, Int 12, S011EnterVehicleRequest 35 (NOT 36 — V2's value /// is from another build; proven live 2026-09-12 on the sibling 08.05 build: hex /// ...-23--FF--), S011VehicleAndSeat 37, ULong 44. Shapes verified /// against dump.cs (StoryClientCommand base, payload V fields, /// S011EnterVehicleRequestPayload{VehicleId, SeatIndices}, S011VehicleAndSeatPayload). /// An unknown command or payload aborts that message's parse; the prefix is still returned. /// internal static class StoryVehicleCodec4229938 { internal static IReadOnlyList Parse(ReadOnlySpan body, out bool fullyConsumed) { var commands = new List(); var reader = new UxReader(body.ToArray()); fullyConsumed = false; try { // Nullable list framing: 0x00 = null, 0xFF + Int7 count + items. if (reader.U8() == 0x00) { fullyConsumed = true; return commands; } int count = reader.Int7(); for (int i = 0; i < count; i++) { byte mark = reader.U8(); if (mark == 0x00) continue; switch (mark) { case 2: // ConfirmRpcCommand { ConfirmRpcId i64, RpcId i64, HasRpcId bool } { long confirmRpcId = reader.I64(); (long rpcId, bool hasRpcId) = ParseCommandBase(reader); commands.Add(new StoryClientCommand4229938(2, 0, "ConfirmRpc", rpcId, hasRpcId, "Confirm", (ulong)confirmRpcId, null, null, null, null)); break; } case 4: // MessageCommand { Nid u32, Name string, Value StoryNetPayload, RpcId, HasRpcId } { uint nid = reader.U32(); string name = reader.UxStringNullable() ?? string.Empty; var payload = ParsePayload(reader); (long rpcId, bool hasRpcId) = ParseCommandBase(reader); commands.Add(new StoryClientCommand4229938(4, nid, name, rpcId, hasRpcId, payload.Kind, payload.VehicleId, payload.SeatIndices, payload.Bool, payload.Int, payload.ULong, payload.Police, payload.Text)); break; } case 5: // { Nid u32, Name string, Value StoryNetPayload, RpcId, HasRpcId } { uint nid = reader.U32(); string name = reader.UxStringNullable() ?? string.Empty; var payload = ParsePayload(reader); (long rpcId, bool hasRpcId) = ParseCommandBase(reader); commands.Add(new StoryClientCommand4229938(5, nid, name, rpcId, hasRpcId, payload.Kind, payload.VehicleId, payload.SeatIndices, payload.Bool, payload.Int, payload.ULong, payload.Police, payload.Text)); break; } case 3: case 6: // { RpcId i64, HasRpcId bool } { (long rpcId, bool hasRpcId) = ParseCommandBase(reader); commands.Add(new StoryClientCommand4229938(mark, 0, mark == 3 ? "Rpc3" : "Rpc6", rpcId, hasRpcId, null, null, null, null, null, null)); break; } default: return commands; } } fullyConsumed = true; return commands; } catch (Exception) { return commands; } } private static (long RpcId, bool HasRpcId) ParseCommandBase(UxReader reader) => (reader.I64(), reader.Bool()); private static (string Kind, ulong? VehicleId, List? SeatIndices, bool? Bool, long? Int, ulong? ULong, PoliceStoryPayload4229938? Police, string? Text) ParsePayload(UxReader reader) { byte mark = reader.U8(); switch (mark) { case 5: // BoolPayload { V bool } return ("Bool", null, null, reader.Bool(), null, null, null, null); case 6: // BytePayload { V byte } - S011 boarding phases arrive in this shape return ("Byte", null, null, null, reader.U8(), null, null, null); case 12: // IntPayload { V i32 } return ("Int", null, null, null, reader.I32(), null, null, null); case 44: // ULongPayload { V u64 } return ("ULong", null, null, null, null, reader.U64(), null, null); // ---- S002 里希盘查(移植自捐赠端)---- case 42: // StringPayload { V string } return ("String", null, null, null, null, null, null, reader.UxStringNullable()); case 43: // UIntPayload { V u32 } return ("UInt", null, null, null, reader.U32(), null, null, null); case 19: // S002EnterExamRequestPayload { AgentId u64, StartInteract bool } return ("S002", null, null, null, null, null, new PoliceStoryPayload4229938(mark, AgentId: reader.U64(), StartInteract: reader.Bool()), null); case 20: // S002EscortFromVehicleRequestPayload { AgentId u64 } case 21: // S002EscortRequestPayload { AgentId u64 } return ("S002", null, null, null, null, null, new PoliceStoryPayload4229938(mark, AgentId: reader.U64()), null); case 18: case 22: // { AgentId u64, VehicleId u64 } return ("S002", null, null, null, null, null, new PoliceStoryPayload4229938(mark, AgentId: reader.U64(), VehicleId: reader.U64()), null); case 23: // { VehicleId u64 } return ("S002", null, null, null, null, null, new PoliceStoryPayload4229938(mark, VehicleId: reader.U64()), null); case 24: // S002ExamInteractPayload { InteractionId u32, SectionId u32, IsStandUp bool, params } { uint interaction = reader.U32(), section = reader.U32(); bool stand = reader.Bool(); byte param = reader.U8(); ulong vehicle = 0; int seat = -1; int[]? fines = null; if (param == 2) { vehicle = reader.U64(); seat = reader.I32(); } else if (param == 3 && reader.U8() != 0) { int count = reader.Int7(); if (count < 0 || count > 64) throw new InvalidDataException("invalid fine count"); fines = new int[count]; for (int i = 0; i < count; i++) fines[i] = reader.I32(); } else if (param != 0 && param != 3) throw new InvalidDataException("unknown police parameters"); return ("S002", null, null, null, null, null, new PoliceStoryPayload4229938(mark, InteractionId: interaction, SectionId: section, IsStandUp: stand, VehicleId: vehicle, SeatIndex: seat, Fines: fines), null); } case 35: // S011EnterVehicleRequestPayload { VehicleId u64, SeatIndices byte[]? } { ulong vehicleId = reader.U64(); List? seats = null; if (reader.U8() != 0x00) { int n = reader.Int7(); seats = new List(n); for (int i = 0; i < n; i++) seats.Add(reader.U8()); } return ("S011EnterVehicleRequest", vehicleId, seats, null, null, null, null, null); } case 36: // same shape under V2's build; kept for tolerance { ulong vehicleId = reader.U64(); List? seats = null; if (reader.U8() != 0x00) { int n = reader.Int7(); seats = new List(n); for (int i = 0; i < n; i++) seats.Add(reader.U8()); } return ("S011EnterVehicleRequest", vehicleId, seats, null, null, null, null, null); } case 37: // S011VehicleAndSeatPayload { VehicleId u64, SeatIndex byte } { ulong vehicleId = reader.U64(); byte seat = reader.U8(); return ("S011VehicleAndSeat", vehicleId, [seat], null, null, null, null, null); } default: throw new InvalidDataException($"unknown StoryNetPayload type mark {mark}"); } } }