refactor(runtime): own combat and magic intent
Move attack build/repeat state, combat-mode policy, authoritative auto-target transitions, and spell-cast intent beneath RuntimeActionState. Keep App as the input, world-query, DAT-policy, transport, and presentation adapter while preserving retail request and busy ordering. Add direct/graphical parity, reset, failure, and instance-isolation coverage. Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
parent
81b31857c6
commit
20df9d155d
49 changed files with 1949 additions and 634 deletions
|
|
@ -1,73 +0,0 @@
|
|||
using AcDream.Core.Combat;
|
||||
using AcDream.Core.Physics;
|
||||
using AcDream.Core.Selection;
|
||||
|
||||
namespace AcDream.App.Combat;
|
||||
|
||||
/// <summary>
|
||||
/// Owns combat-target lifecycle around the shared selection state.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Retail <c>ClientCombatSystem::RecvNotice_SelectionChanged</c>
|
||||
/// (0x0056BD80) invokes <c>AutoTarget</c> when selection becomes zero while
|
||||
/// Auto Target is enabled in Melee/Missile combat. Authoritative Dead motion
|
||||
/// makes the selected combat object unavailable and enters that same notice
|
||||
/// path.
|
||||
/// </remarks>
|
||||
public sealed class CombatTargetController : IDisposable
|
||||
{
|
||||
private readonly CombatState _combat;
|
||||
private readonly SelectionState _selection;
|
||||
private readonly Func<bool> _autoTarget;
|
||||
private readonly Func<uint?> _selectClosestTarget;
|
||||
private bool _disposed;
|
||||
|
||||
public CombatTargetController(
|
||||
CombatState combat,
|
||||
SelectionState selection,
|
||||
Func<bool> autoTarget,
|
||||
Func<uint?> selectClosestTarget)
|
||||
{
|
||||
_combat = combat ?? throw new ArgumentNullException(nameof(combat));
|
||||
_selection = selection ?? throw new ArgumentNullException(nameof(selection));
|
||||
_autoTarget = autoTarget ?? throw new ArgumentNullException(nameof(autoTarget));
|
||||
_selectClosestTarget = selectClosestTarget
|
||||
?? throw new ArgumentNullException(nameof(selectClosestTarget));
|
||||
|
||||
_selection.Changed += OnSelectionChanged;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called after an accepted UpdateMotion has reached the entity's motion
|
||||
/// table. Dead is a persistent substate, so CurrentMotion is the
|
||||
/// authoritative availability signal rather than a damage prediction.
|
||||
/// </summary>
|
||||
public void OnMotionApplied(uint objectId, uint currentMotion)
|
||||
{
|
||||
if (currentMotion != MotionCommand.Dead
|
||||
|| _selection.SelectedObjectId != objectId)
|
||||
return;
|
||||
|
||||
_selection.Clear(
|
||||
SelectionChangeSource.System,
|
||||
SelectionChangeReason.CombatTargetDied);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed) return;
|
||||
_disposed = true;
|
||||
_selection.Changed -= OnSelectionChanged;
|
||||
}
|
||||
|
||||
private void OnSelectionChanged(SelectionTransition transition)
|
||||
{
|
||||
if (transition.SelectedObjectId is not null
|
||||
|| transition.Reason == SelectionChangeReason.SessionReset
|
||||
|| !_autoTarget()
|
||||
|| !CombatInputPlanner.SupportsTargetedAttack(_combat.CurrentMode))
|
||||
return;
|
||||
|
||||
_selectClosestTarget();
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue