using AcDream.App.UI.Layout; using AcDream.Core.Net.Messages; namespace AcDream.App.UI; /// /// Ports the server-driven confirmation ownership in /// gmGamePlayUI @ 0x004E9D70..0x004E9EB6. The dialog factory broadcasts /// completion; this semantic owner retains the server type/context and sends the /// matching confirmation response. /// public sealed class GameplayConfirmationController : IDisposable { private readonly RetailDialogFactory _dialogs; private readonly Action _sendResponse; private uint _dialogContext; private uint _serverType; private uint _serverContext; private bool _disposed; public GameplayConfirmationController( RetailDialogFactory dialogs, Action sendResponse) { _dialogs = dialogs ?? throw new ArgumentNullException(nameof(dialogs)); _sendResponse = sendResponse ?? throw new ArgumentNullException(nameof(sendResponse)); _dialogs.DialogClosed += OnDialogClosed; } public uint ActiveDialogContext => _dialogContext; public bool HandleRequest(GameEvents.CharacterConfirmationRequest request) { // ClientUISystem::Handle_Character__ConfirmationRequest @ 0x005640A0 // routes types 2/3/5/6 to handlers that append " Continue?"; the generic // type-7 YesNo request preserves the server text verbatim. Types 1 and 4 // have allegiance/fellowship semantic owners but use the same response // tuple, so this controller retains that tuple until those panels exist. // Each retail RecvNotice_* handler stores the tuple before calling the // shared maker, including the already-open case. _serverType = request.Type; _serverContext = request.ContextId; // gmGamePlayUI::MakeGameplayConfirmationDialog @ 0x004EB890 refuses a // second gameplay confirmation while its context is non-zero. if (_dialogContext != 0u) return false; string message = request.Type is 2u or 3u or 5u or 6u ? request.Message + " Continue?" : request.Message; var data = RetailDialogData.Confirmation(message) .Set(RetailDialogProperty.ElementAttribute40, true); _dialogContext = _dialogs.MakeDialog(data); return _dialogContext != 0u; } public bool HandleDone(GameEvents.CharacterConfirmationDone done) { // gmGamePlayUI::RecvNotice_AbortConfirmationRequest @ 0x004E9D70 // matches both server fields before closing the local dialog context. if (_dialogContext == 0u || done.Type != _serverType || done.ContextId != _serverContext) return false; return _dialogs.CloseDialog(_dialogContext); } public void Dispose() { if (_disposed) return; _disposed = true; _dialogs.DialogClosed -= OnDialogClosed; } private void OnDialogClosed(uint context, RetailDialogData data) { if (context != _dialogContext || data.GetUInt32(RetailDialogProperty.Type) != (uint)RetailDialogType.Confirmation) return; bool accepted = data.GetBoolean(RetailDialogProperty.ConfirmationResult); // gmGamePlayUI::CloseGameplayConfirmationDialog @ 0x004E9E80 sends // before clearing its retained server/dialog context fields. _sendResponse(_serverType, _serverContext, accepted); _dialogContext = 0u; _serverType = 0u; _serverContext = 0u; } }