feat(anim): Phase L.1c select combat maneuvers

This commit is contained in:
Erik 2026-04-28 11:44:17 +02:00
parent 831392a7b2
commit 646246ba84
6 changed files with 385 additions and 33 deletions

View file

@ -53,9 +53,12 @@ public static class CombatAnimationPlanner
or CombatAnimationMotionCommands.SwordCombat
or CombatAnimationMotionCommands.SwordShieldCombat
or CombatAnimationMotionCommands.TwoHandedSwordCombat
or CombatAnimationMotionCommands.TwoHandedStaffCombat
or CombatAnimationMotionCommands.BowCombat
or CombatAnimationMotionCommands.CrossbowCombat
or CombatAnimationMotionCommands.SlingCombat
or CombatAnimationMotionCommands.DualWieldCombat
or CombatAnimationMotionCommands.ThrownWeaponCombat
or CombatAnimationMotionCommands.AtlatlCombat
or CombatAnimationMotionCommands.ThrownShieldCombat
or CombatAnimationMotionCommands.Magic => CombatAnimationKind.CombatStance,
@ -99,7 +102,19 @@ public static class CombatAnimationPlanner
or CombatAnimationMotionCommands.OffhandTripleThrustLow
or CombatAnimationMotionCommands.OffhandTripleThrustMed
or CombatAnimationMotionCommands.OffhandTripleThrustHigh
or CombatAnimationMotionCommands.OffhandKick => CombatAnimationKind.MeleeSwing,
or CombatAnimationMotionCommands.OffhandKick
or CombatAnimationMotionCommands.PunchFastHigh
or CombatAnimationMotionCommands.PunchFastMed
or CombatAnimationMotionCommands.PunchFastLow
or CombatAnimationMotionCommands.PunchSlowHigh
or CombatAnimationMotionCommands.PunchSlowMed
or CombatAnimationMotionCommands.PunchSlowLow
or CombatAnimationMotionCommands.OffhandPunchFastHigh
or CombatAnimationMotionCommands.OffhandPunchFastMed
or CombatAnimationMotionCommands.OffhandPunchFastLow
or CombatAnimationMotionCommands.OffhandPunchSlowHigh
or CombatAnimationMotionCommands.OffhandPunchSlowMed
or CombatAnimationMotionCommands.OffhandPunchSlowLow => CombatAnimationKind.MeleeSwing,
CombatAnimationMotionCommands.Shoot
or CombatAnimationMotionCommands.MissileAttack1
@ -192,8 +207,11 @@ internal static class CombatAnimationMotionCommands
public const uint BowCombat = 0x8000003Fu;
public const uint SwordShieldCombat = 0x80000040u;
public const uint CrossbowCombat = 0x80000041u;
public const uint TwoHandedSwordCombat = 0x80000046u;
public const uint SlingCombat = 0x80000047u;
public const uint SlingCombat = 0x80000043u;
public const uint TwoHandedSwordCombat = 0x80000044u;
public const uint TwoHandedStaffCombat = 0x80000045u;
public const uint DualWieldCombat = 0x80000046u;
public const uint ThrownWeaponCombat = 0x80000047u;
public const uint Magic = 0x80000049u;
public const uint AtlatlCombat = 0x8000013Bu;
public const uint ThrownShieldCombat = 0x8000013Cu;
@ -247,32 +265,44 @@ internal static class CombatAnimationMotionCommands
public const uint TripleThrustMed = 0x10000129u;
public const uint TripleThrustHigh = 0x1000012Au;
public const uint OffhandSlashHigh = 0x10000173u;
public const uint OffhandSlashMed = 0x10000174u;
public const uint OffhandSlashLow = 0x10000175u;
public const uint OffhandThrustHigh = 0x10000176u;
public const uint OffhandThrustMed = 0x10000177u;
public const uint OffhandThrustLow = 0x10000178u;
public const uint OffhandDoubleSlashLow = 0x10000179u;
public const uint OffhandDoubleSlashMed = 0x1000017Au;
public const uint OffhandDoubleSlashHigh = 0x1000017Bu;
public const uint OffhandTripleSlashLow = 0x1000017Cu;
public const uint OffhandTripleSlashMed = 0x1000017Du;
public const uint OffhandTripleSlashHigh = 0x1000017Eu;
public const uint OffhandDoubleThrustLow = 0x1000017Fu;
public const uint OffhandDoubleThrustMed = 0x10000180u;
public const uint OffhandDoubleThrustHigh = 0x10000181u;
public const uint OffhandTripleThrustLow = 0x10000182u;
public const uint OffhandTripleThrustMed = 0x10000183u;
public const uint OffhandTripleThrustHigh = 0x10000184u;
public const uint OffhandKick = 0x10000185u;
public const uint AttackHigh4 = 0x10000186u;
public const uint AttackMed4 = 0x10000187u;
public const uint AttackLow4 = 0x10000188u;
public const uint AttackHigh5 = 0x10000189u;
public const uint AttackMed5 = 0x1000018Au;
public const uint AttackLow5 = 0x1000018Bu;
public const uint AttackHigh6 = 0x1000018Cu;
public const uint AttackMed6 = 0x1000018Du;
public const uint AttackLow6 = 0x1000018Eu;
public const uint OffhandSlashHigh = 0x10000170u;
public const uint OffhandSlashMed = 0x10000171u;
public const uint OffhandSlashLow = 0x10000172u;
public const uint OffhandThrustHigh = 0x10000173u;
public const uint OffhandThrustMed = 0x10000174u;
public const uint OffhandThrustLow = 0x10000175u;
public const uint OffhandDoubleSlashLow = 0x10000176u;
public const uint OffhandDoubleSlashMed = 0x10000177u;
public const uint OffhandDoubleSlashHigh = 0x10000178u;
public const uint OffhandTripleSlashLow = 0x10000179u;
public const uint OffhandTripleSlashMed = 0x1000017Au;
public const uint OffhandTripleSlashHigh = 0x1000017Bu;
public const uint OffhandDoubleThrustLow = 0x1000017Cu;
public const uint OffhandDoubleThrustMed = 0x1000017Du;
public const uint OffhandDoubleThrustHigh = 0x1000017Eu;
public const uint OffhandTripleThrustLow = 0x1000017Fu;
public const uint OffhandTripleThrustMed = 0x10000180u;
public const uint OffhandTripleThrustHigh = 0x10000181u;
public const uint OffhandKick = 0x10000182u;
public const uint AttackHigh4 = 0x10000183u;
public const uint AttackMed4 = 0x10000184u;
public const uint AttackLow4 = 0x10000185u;
public const uint AttackHigh5 = 0x10000186u;
public const uint AttackMed5 = 0x10000187u;
public const uint AttackLow5 = 0x10000188u;
public const uint AttackHigh6 = 0x10000189u;
public const uint AttackMed6 = 0x1000018Au;
public const uint AttackLow6 = 0x1000018Bu;
public const uint PunchFastHigh = 0x1000018Cu;
public const uint PunchFastMed = 0x1000018Du;
public const uint PunchFastLow = 0x1000018Eu;
public const uint PunchSlowHigh = 0x1000018Fu;
public const uint PunchSlowMed = 0x10000190u;
public const uint PunchSlowLow = 0x10000191u;
public const uint OffhandPunchFastHigh = 0x10000192u;
public const uint OffhandPunchFastMed = 0x10000193u;
public const uint OffhandPunchFastLow = 0x10000194u;
public const uint OffhandPunchSlowHigh = 0x10000195u;
public const uint OffhandPunchSlowMed = 0x10000196u;
public const uint OffhandPunchSlowLow = 0x10000197u;
}

View file

@ -0,0 +1,89 @@
using DatReaderWriter.DBObjs;
using DatMotionCommand = DatReaderWriter.Enums.MotionCommand;
using DatMotionStance = DatReaderWriter.Enums.MotionStance;
using DatAttackHeight = DatReaderWriter.Enums.AttackHeight;
using DatAttackType = DatReaderWriter.Enums.AttackType;
namespace AcDream.Core.Combat;
/// <summary>
/// Selects combat swing motions from the retail <c>CombatTable</c> DBObj.
///
/// Retail evidence:
/// - <c>CombatManeuverTable::Get</c> (0x0056AB60) loads DB type
/// <c>0x1000000D</c> for a 0x30xxxxxx combat table id.
/// - ACE <c>CombatManeuverTable.GetMotion</c> indexes maneuvers by
/// stance, attack height, and attack type, returning all matching motions.
/// - ACE <c>Player_Melee.GetSwingAnimation</c> then chooses
/// <c>motions[1]</c> when more than one motion exists and power is below
/// the subdivision threshold; otherwise it uses <c>motions[0]</c>.
/// </summary>
public static class CombatManeuverSelector
{
public const float DefaultSubdivision = 0.33f;
public const float ThrustSlashSubdivision = 0.66f;
public static CombatManeuverSelection SelectMotion(
CombatTable table,
DatMotionStance stance,
DatAttackHeight attackHeight,
DatAttackType attackType,
float powerLevel,
bool isThrustSlashWeapon = false)
{
var motions = FindMotions(table, stance, attackHeight, attackType);
if (motions.Count == 0)
return CombatManeuverSelection.None;
float subdivision = isThrustSlashWeapon
? ThrustSlashSubdivision
: DefaultSubdivision;
var motion = motions.Count > 1 && powerLevel < subdivision
? motions[1]
: motions[0];
return new CombatManeuverSelection(
Found: true,
Motion: motion,
Candidates: motions,
EffectiveAttackType: attackType,
Subdivision: subdivision);
}
public static IReadOnlyList<DatMotionCommand> FindMotions(
CombatTable table,
DatMotionStance stance,
DatAttackHeight attackHeight,
DatAttackType attackType)
{
var result = new List<DatMotionCommand>();
foreach (var maneuver in table.CombatManeuvers)
{
if (maneuver.Style == stance
&& maneuver.AttackHeight == attackHeight
&& maneuver.AttackType == attackType)
{
result.Add(maneuver.Motion);
}
}
return result;
}
}
public readonly record struct CombatManeuverSelection(
bool Found,
DatMotionCommand Motion,
IReadOnlyList<DatMotionCommand> Candidates,
DatAttackType EffectiveAttackType,
float Subdivision)
{
public static CombatManeuverSelection None { get; } = new(
Found: false,
Motion: DatMotionCommand.Invalid,
Candidates: Array.Empty<DatMotionCommand>(),
EffectiveAttackType: DatAttackType.Undef,
Subdivision: 0f);
}

View file

@ -84,6 +84,24 @@ public static class MotionCommandResolver
result[lo] = full;
}
}
ApplyNamedRetailOverrides(result);
return result;
}
private static void ApplyNamedRetailOverrides(Dictionary<ushort, uint> result)
{
// The generated DRW enum is shifted by three entries starting at
// AllegianceHometownRecall. The named Sept 2013 retail command_ids
// table is authoritative here:
// named-retail/acclient_2013_pseudo_c.txt lines 1017626-1017658
// and command-name table lines 1068272-1068313.
//
// These values cover recall, offhand, attack 4-6, and fast/slow punch
// actions. Without the override, wire command 0x0170 reconstructs to
// IssueSlashCommand instead of OffhandSlashHigh, so offhand swing
// animations route as UI commands and never play.
for (ushort lo = 0x016E; lo <= 0x0197; lo++)
result[lo] = 0x10000000u | lo;
}
}