using System.Buffers.Binary;
using AcDream.Core.Net.Packets;
namespace AcDream.Core.Net.Messages;
///
/// Retail character-delete request and server acknowledgement, both riding
/// opcode 0xF655.
///
///
/// Wire layout ported from retail Proto_UI::SendDeleteCharacter at
/// 0x00546b30: the opcode, then AC1Legacy::PStringBase<char>::Pack
/// of the account id as a String16L, then a trailing u32 written directly
/// after the packed string (*(uint32_t*)var_4 = arg2):
///
///
///
/// u32 opcode (0xF655)
/// String16L accountName
/// u32 characterSlot (NOT the character guid)
///
///
///
/// The caller, CPlayerSystem::DeleteCharacter at 0x0055f830,
/// resolves that trailing u32 from the target character's guid via
/// CharacterSet::GetSlot(persistentData + 4, guid) before sending —
/// retail deletes by **account + SLOT INDEX**, never the character guid.
/// This builder takes the already-resolved slot; resolving a selected
/// character to its slot is Runtime selection-state work (Campaign LA
/// slice LA7b), not this file's job.
///
///
///
/// The server's acknowledgement reuses the same opcode with no trailing
/// payload — ACE's GameMessageCharacterDelete constructs a bare
/// 4-byte body
/// (ACE.Server/Network/GameMessages/Messages/GameMessageCharacterDelete.cs,
/// base constructor called with bodyLength: 4 and no further
/// Writer.Write calls). holtburger's inbound dispatcher
/// (holtburger-protocol/src/messages/game_message/unpack.rs:50-58)
/// disambiguates request vs. ack the identical way we do here — a request
/// has bytes remaining after the opcode, the ack does not.
///
///
///
/// Routing note for LA7b: retail transmits this request via
/// Proto_UI::SendToLogon (the restore request rides
/// SendToControl); ACE sends its acknowledgement and the follow-up
/// refreshed CharacterList on GameMessageGroup.UIQueue.
///
///
///
/// After the ack, ACE immediately follows with a fresh
/// so the roster reflects the character's new pending-delete state
/// (CharacterHandler.CharacterDelete,
/// ACE.Server/Network/Handlers/CharacterHandler.cs:322, inside the
/// SaveCharacter success callback). Requesting and re-rendering that
/// refreshed roster belongs to LA7b's Runtime selection state — this file
/// only builds the request and recognizes the ack.
///
///
public static class CharacterDelete
{
public const uint Opcode = 0xF655u;
///
/// Build the body bytes for an outbound CharacterDelete request.
/// Layout: opcode(4) + String16L(accountName) + characterSlot(4).
///
public static byte[] BuildRequestBody(string accountName, uint characterSlot)
{
ArgumentNullException.ThrowIfNull(accountName);
var w = new PacketWriter(32);
w.WriteUInt32(Opcode);
w.WriteString16L(accountName);
w.WriteUInt32(characterSlot);
return w.ToArray();
}
///
/// Returns whether a complete game-message body is the server's
/// delete acknowledgement — the canonical four-byte opcode-only form
/// ACE emits. A fresh follows separately
/// and is not this method's concern.
///
public static bool IsAcknowledgement(ReadOnlySpan body) =>
body.Length == sizeof(uint) &&
BinaryPrimitives.ReadUInt32LittleEndian(body) == Opcode;
}