using AcDream.Core.Items;
namespace AcDream.App.World;
///
/// Owns the network side effect of replacing retail's current ground object.
/// Presentation controllers observe the same state but do not own session
/// lifecycle messages.
///
public sealed class ExternalContainerLifecycleController : IDisposable
{
private readonly ExternalContainerState _state;
private readonly ClientObjectTable _objects;
private readonly Action _sendNoLongerViewingContents;
private bool _disposed;
public ExternalContainerLifecycleController(
ExternalContainerState state,
ClientObjectTable objects,
Action sendNoLongerViewingContents)
{
_state = state ?? throw new ArgumentNullException(nameof(state));
_objects = objects ?? throw new ArgumentNullException(nameof(objects));
_sendNoLongerViewingContents = sendNoLongerViewingContents
?? throw new ArgumentNullException(nameof(sendNoLongerViewingContents));
_state.Changed += OnChanged;
}
private void OnChanged(ExternalContainerTransition transition)
{
// ClientUISystem::SetGroundObject @ 0x00564510 always queues the old
// root's contents tree for destruction when the ground object changes.
if (transition.PreviousContainerId != 0u)
_objects.StopViewingContentsTree(transition.PreviousContainerId);
// SetGroundObject(new, notifyServer:true) emits this while the new
// request is still pending, not after ViewContents arrives. The
// authored close/range path instead sends Use(root) and waits for
// event 0x0052.
if (transition.Kind == ExternalContainerTransitionKind.ReplacementRequested
&& transition.PreviousContainerId != 0u)
{
_sendNoLongerViewingContents(transition.PreviousContainerId);
}
}
public void Dispose()
{
if (_disposed) return;
_disposed = true;
_state.Changed -= OnChanged;
}
}