refactor(runtime): own canonical action state
Move selection, combat, and interaction target mode under one Runtime owner; make plugins, retained UI, session routing, and typed runtime views borrow its exact children; and add failure-safe reset, instance isolation, source ownership, and normalized checkpoint coverage without changing retail ordering. Co-authored-by: Codex <codex@openai.com>
This commit is contained in:
parent
bb45afef33
commit
b298f99f91
38 changed files with 711 additions and 93 deletions
96
src/AcDream.Runtime/Gameplay/InteractionState.cs
Normal file
96
src/AcDream.Runtime/Gameplay/InteractionState.cs
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
namespace AcDream.Runtime.Gameplay;
|
||||
|
||||
public enum InteractionModeKind
|
||||
{
|
||||
None,
|
||||
Use,
|
||||
Examine,
|
||||
UseItemOnTarget,
|
||||
}
|
||||
|
||||
public readonly record struct InteractionMode(
|
||||
InteractionModeKind Kind,
|
||||
uint SourceObjectId = 0)
|
||||
{
|
||||
public static InteractionMode None => new(InteractionModeKind.None);
|
||||
}
|
||||
|
||||
public readonly record struct InteractionModeTransition(
|
||||
InteractionMode Previous,
|
||||
InteractionMode Current);
|
||||
|
||||
/// <summary>
|
||||
/// Canonical presentation-independent owner for retail's temporary target
|
||||
/// mode. App projects this state into a cursor image; it does not own or copy
|
||||
/// the mode.
|
||||
/// </summary>
|
||||
public sealed class InteractionState
|
||||
{
|
||||
public InteractionMode Current { get; private set; } = InteractionMode.None;
|
||||
|
||||
public event Action<InteractionModeTransition>? Changed;
|
||||
|
||||
public bool EnterUse() => Set(new InteractionMode(InteractionModeKind.Use));
|
||||
|
||||
public bool EnterExamine() =>
|
||||
Set(new InteractionMode(InteractionModeKind.Examine));
|
||||
|
||||
public bool EnterUseItemOnTarget(uint sourceObjectId)
|
||||
{
|
||||
if (sourceObjectId == 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(sourceObjectId));
|
||||
return Set(new InteractionMode(
|
||||
InteractionModeKind.UseItemOnTarget,
|
||||
sourceObjectId));
|
||||
}
|
||||
|
||||
public bool Clear() => Set(InteractionMode.None);
|
||||
|
||||
/// <summary>
|
||||
/// Publishes the session-reset edge even when the mode is already clear so
|
||||
/// a retry can repair any observer that missed an earlier notification.
|
||||
/// State commits before observers run and every observer is attempted.
|
||||
/// </summary>
|
||||
public void ResetSession()
|
||||
{
|
||||
InteractionMode previous = Current;
|
||||
Current = InteractionMode.None;
|
||||
Action<InteractionModeTransition>? listeners = Changed;
|
||||
if (listeners is null)
|
||||
return;
|
||||
|
||||
var transition = new InteractionModeTransition(
|
||||
previous,
|
||||
InteractionMode.None);
|
||||
List<Exception>? failures = null;
|
||||
foreach (Action<InteractionModeTransition> listener
|
||||
in listeners.GetInvocationList())
|
||||
{
|
||||
try
|
||||
{
|
||||
listener(transition);
|
||||
}
|
||||
catch (Exception error)
|
||||
{
|
||||
(failures ??= []).Add(error);
|
||||
}
|
||||
}
|
||||
|
||||
if (failures is not null)
|
||||
{
|
||||
throw new AggregateException(
|
||||
"One or more interaction-mode reset observers failed.",
|
||||
failures);
|
||||
}
|
||||
}
|
||||
|
||||
private bool Set(InteractionMode mode)
|
||||
{
|
||||
if (Current == mode)
|
||||
return false;
|
||||
InteractionMode previous = Current;
|
||||
Current = mode;
|
||||
Changed?.Invoke(new InteractionModeTransition(previous, mode));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue