refactor(input): own gameplay action routing
Move the sole semantic action-priority graph, combat and diagnostic commands, and retained-root item-drop lifetime behind focused typed owners. Preserve retail toggle behavior, explicit auto-wield cancellation, shutdown quiescence, and symmetric callback cleanup. Co-authored-by: Codex <codex@openai.com>
This commit is contained in:
parent
8b8afeefa3
commit
4eae9b4f5a
25 changed files with 2608 additions and 418 deletions
|
|
@ -36,6 +36,14 @@ public enum CombatAttackAction
|
|||
High,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retail's default-mode lookup returns the selected mode and, on the one
|
||||
/// rejected held-item branch, the object used to format its user notice.
|
||||
/// </summary>
|
||||
public readonly record struct DefaultCombatModeDecision(
|
||||
CombatMode Mode,
|
||||
ClientObject? IncompatibleHeldItem);
|
||||
|
||||
/// <summary>
|
||||
/// Retail input-facing combat decisions. The heavyweight parts of the combat
|
||||
/// system remain server authoritative; this helper only maps UI intent to the
|
||||
|
|
@ -92,6 +100,14 @@ public static class CombatInputPlanner
|
|||
/// object intersecting the requested location mask.
|
||||
/// </summary>
|
||||
public static CombatMode GetDefaultCombatMode(
|
||||
IReadOnlyList<ClientObject> orderedPlayerContents) =>
|
||||
GetDefaultCombatModeDecision(orderedPlayerContents).Mode;
|
||||
|
||||
/// <summary>
|
||||
/// Complete result of retail <c>GetDefaultCombatMode</c>, including the
|
||||
/// incompatible Held object used by its exact failure notice.
|
||||
/// </summary>
|
||||
public static DefaultCombatModeDecision GetDefaultCombatModeDecision(
|
||||
IReadOnlyList<ClientObject> orderedPlayerContents)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(orderedPlayerContents);
|
||||
|
|
@ -102,20 +118,22 @@ public static class CombatInputPlanner
|
|||
{
|
||||
// Retail COMBAT_USE_MISSILE = 2. Every other combat-use value in
|
||||
// this primary weapon slot selects melee.
|
||||
return weapon.CombatUse == 2
|
||||
? CombatMode.Missile
|
||||
: CombatMode.Melee;
|
||||
return new(
|
||||
weapon.CombatUse == 2
|
||||
? CombatMode.Missile
|
||||
: CombatMode.Melee,
|
||||
IncompatibleHeldItem: null);
|
||||
}
|
||||
|
||||
ClientObject? held = GetObjectAtLocation(
|
||||
orderedPlayerContents, EquipMask.Held);
|
||||
if (held is null)
|
||||
return CombatMode.Melee;
|
||||
return new(CombatMode.Melee, IncompatibleHeldItem: null);
|
||||
|
||||
// The decomp's byte-1 sign test is ITEM_TYPE bit 15 (Caster).
|
||||
return (held.Type & ItemType.Caster) != 0
|
||||
? CombatMode.Magic
|
||||
: CombatMode.NonCombat;
|
||||
? new(CombatMode.Magic, IncompatibleHeldItem: null)
|
||||
: new(CombatMode.NonCombat, held);
|
||||
}
|
||||
|
||||
private static ClientObject? GetObjectAtLocation(
|
||||
|
|
@ -136,12 +154,13 @@ public static class CombatInputPlanner
|
|||
CombatMode currentMode,
|
||||
CombatMode defaultCombatMode = CombatMode.Melee)
|
||||
{
|
||||
if ((currentMode & CombatMode.CombatCombat) != 0)
|
||||
// ClientCombatSystem::ToggleCombatMode @ 0x0056C8C0 compares the
|
||||
// current value directly with NonCombat. It does not coerce a
|
||||
// rejected/default NonCombat result to unarmed Melee.
|
||||
if (currentMode != CombatMode.NonCombat)
|
||||
return CombatMode.NonCombat;
|
||||
|
||||
return (defaultCombatMode & CombatMode.CombatCombat) != 0
|
||||
? defaultCombatMode
|
||||
: CombatMode.Melee;
|
||||
return defaultCombatMode;
|
||||
}
|
||||
|
||||
public static bool SupportsTargetedAttack(CombatMode mode) =>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue