125 lines
4 KiB
C#
125 lines
4 KiB
C#
using System;
|
|
|
|
namespace AcDream.Core.Items;
|
|
|
|
public enum ExternalContainerTransitionKind
|
|
{
|
|
Opened,
|
|
ReplacementRequested,
|
|
Closed,
|
|
Reset,
|
|
}
|
|
|
|
public readonly record struct ExternalContainerTransition(
|
|
ExternalContainerTransitionKind Kind,
|
|
uint PreviousContainerId,
|
|
uint ContainerId);
|
|
|
|
/// <summary>
|
|
/// Owns retail <c>ClientUISystem::groundObject</c> and the outstanding external
|
|
/// container request. ACE sends ViewContents for the requested root and then for
|
|
/// nested containers; only the expected root may become the visible ground object.
|
|
/// Retail: <c>ClientUISystem::OnViewContents @ 0x00565870</c> and
|
|
/// <c>SetGroundObject @ 0x00564510</c>.
|
|
/// </summary>
|
|
public sealed class ExternalContainerState
|
|
{
|
|
public uint RequestedContainerId { get; private set; }
|
|
public uint CurrentContainerId { get; private set; }
|
|
|
|
public event Action<ExternalContainerTransition>? Changed;
|
|
|
|
public bool RequestOpen(uint containerId)
|
|
{
|
|
if (containerId == 0u || RequestedContainerId == containerId)
|
|
return false;
|
|
|
|
uint previous = CurrentContainerId;
|
|
RequestedContainerId = containerId;
|
|
|
|
// ClientUISystem::SetGroundObject @ 0x00564510 retires the old
|
|
// ground-object presentation synchronously when a different root is
|
|
// requested. ViewContents for the new root arrives later. Keeping the
|
|
// old root current during that interval lets its range watcher issue a
|
|
// close Use, which ACE interprets as cancellation of the new root's
|
|
// MoveTo chain.
|
|
if (previous != 0u && previous != containerId)
|
|
{
|
|
CurrentContainerId = 0u;
|
|
Changed?.Invoke(new ExternalContainerTransition(
|
|
ExternalContainerTransitionKind.ReplacementRequested,
|
|
previous,
|
|
containerId));
|
|
}
|
|
return true;
|
|
}
|
|
|
|
public bool ApplyViewContents(uint containerId)
|
|
{
|
|
if (containerId == 0u || containerId != RequestedContainerId)
|
|
return false;
|
|
|
|
uint previous = CurrentContainerId;
|
|
CurrentContainerId = containerId;
|
|
RequestedContainerId = containerId;
|
|
if (previous == containerId)
|
|
return false;
|
|
|
|
Changed?.Invoke(new ExternalContainerTransition(
|
|
ExternalContainerTransitionKind.Opened,
|
|
previous,
|
|
containerId));
|
|
return true;
|
|
}
|
|
|
|
public bool ApplyClose(uint containerId)
|
|
{
|
|
if (containerId == 0u || containerId != CurrentContainerId)
|
|
return false;
|
|
|
|
uint previous = CurrentContainerId;
|
|
CurrentContainerId = 0u;
|
|
RequestedContainerId = 0u;
|
|
Changed?.Invoke(new ExternalContainerTransition(
|
|
ExternalContainerTransitionKind.Closed,
|
|
previous,
|
|
0u));
|
|
return true;
|
|
}
|
|
|
|
public bool ApplyUseDone(uint weenieError)
|
|
{
|
|
if (weenieError == 0u || RequestedContainerId == CurrentContainerId)
|
|
return false;
|
|
RequestedContainerId = CurrentContainerId;
|
|
return true;
|
|
}
|
|
|
|
public bool Reset()
|
|
{
|
|
uint previous = CurrentContainerId;
|
|
bool changed = previous != 0u || RequestedContainerId != 0u;
|
|
CurrentContainerId = 0u;
|
|
RequestedContainerId = 0u;
|
|
|
|
var transition = new ExternalContainerTransition(
|
|
ExternalContainerTransitionKind.Reset,
|
|
previous,
|
|
0u);
|
|
Action<ExternalContainerTransition>? listeners = Changed;
|
|
if (listeners is not null)
|
|
{
|
|
List<Exception>? failures = null;
|
|
foreach (Action<ExternalContainerTransition> listener in listeners.GetInvocationList())
|
|
{
|
|
try { listener(transition); }
|
|
catch (Exception error) { (failures ??= []).Add(error); }
|
|
}
|
|
if (failures is not null)
|
|
throw new AggregateException(
|
|
"One or more external-container reset observers failed.",
|
|
failures);
|
|
}
|
|
return changed;
|
|
}
|
|
}
|