feat(anim): Phase L.1c select combat maneuvers
This commit is contained in:
parent
831392a7b2
commit
646246ba84
6 changed files with 385 additions and 33 deletions
|
|
@ -60,6 +60,51 @@ ClassifyMotionCommand(fullCommand):
|
|||
return None
|
||||
```
|
||||
|
||||
## Maneuver Selection Pseudocode
|
||||
|
||||
```text
|
||||
SelectMotion(table, stance, attackHeight, attackType, powerLevel,
|
||||
isThrustSlashWeapon):
|
||||
candidates = []
|
||||
for maneuver in table.CombatManeuvers:
|
||||
if maneuver.Style == stance
|
||||
and maneuver.AttackHeight == attackHeight
|
||||
and maneuver.AttackType == attackType:
|
||||
candidates.append(maneuver.Motion)
|
||||
|
||||
if candidates is empty:
|
||||
return None
|
||||
|
||||
subdivision = isThrustSlashWeapon ? 0.66 : 0.33
|
||||
|
||||
if candidates.Count > 1 and powerLevel < subdivision:
|
||||
motion = candidates[1]
|
||||
else:
|
||||
motion = candidates[0]
|
||||
|
||||
return motion
|
||||
```
|
||||
|
||||
This matches ACE `CombatManeuverTable.GetMotion` plus
|
||||
`Player_Melee.GetSwingAnimation`. The `prevMotion` parameter is present in
|
||||
ACE's table API but the current ACE implementation does not use it; the
|
||||
power threshold chooses between multiple entries.
|
||||
|
||||
## Named Retail Motion IDs
|
||||
|
||||
`DatReaderWriter.Enums.MotionCommand` is shifted by three entries starting
|
||||
at `AllegianceHometownRecall`. Named retail command tables are:
|
||||
|
||||
- `command_ids` table lines 1017626-1017658:
|
||||
`0x016E..0x0197 -> 0x1000016E..0x10000197`.
|
||||
- command-name table lines 1068272-1068313:
|
||||
`OffhandSlashHigh = 0x10000170`, `AttackLow6 = 0x1000018B`,
|
||||
`PunchFastLow = 0x1000018E`, etc.
|
||||
|
||||
`MotionCommandResolver` therefore overrides that range after building the
|
||||
DRW reflection table, otherwise offhand and late unarmed attack actions
|
||||
resolve as UI/mappable commands and never reach `PlayAction`.
|
||||
|
||||
## Implementation Note
|
||||
|
||||
The next table-driven layer can use `DatReaderWriter.DBObjs.CombatTable`
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
89
src/AcDream.Core/Combat/CombatManeuverSelector.cs
Normal file
89
src/AcDream.Core/Combat/CombatManeuverSelector.cs
Normal 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);
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,17 +9,20 @@ public sealed class CombatAnimationPlannerTests
|
|||
[Theory]
|
||||
[InlineData(0x10000058u, CombatAnimationKind.MeleeSwing)] // ThrustMed
|
||||
[InlineData(0x1000005Bu, CombatAnimationKind.MeleeSwing)] // SlashHigh
|
||||
[InlineData(0x10000180u, CombatAnimationKind.MeleeSwing)] // OffhandDoubleThrustMed
|
||||
[InlineData(0x1000017Du, CombatAnimationKind.MeleeSwing)] // OffhandDoubleThrustMed
|
||||
[InlineData(0x1000018Eu, CombatAnimationKind.MeleeSwing)] // PunchFastLow
|
||||
[InlineData(0x10000061u, CombatAnimationKind.MissileAttack)] // Shoot
|
||||
[InlineData(0x100000D4u, CombatAnimationKind.MissileAttack)] // Reload
|
||||
[InlineData(0x10000062u, CombatAnimationKind.CreatureAttack)] // AttackHigh1
|
||||
[InlineData(0x1000018Eu, CombatAnimationKind.CreatureAttack)] // AttackLow6
|
||||
[InlineData(0x1000018Bu, CombatAnimationKind.CreatureAttack)] // AttackLow6
|
||||
[InlineData(0x400000D3u, CombatAnimationKind.SpellCast)] // CastSpell
|
||||
[InlineData(0x400000E0u, CombatAnimationKind.SpellCast)] // UseMagicStaff
|
||||
[InlineData(0x10000051u, CombatAnimationKind.HitReaction)] // Twitch1
|
||||
[InlineData(0x10000055u, CombatAnimationKind.HitReaction)] // StaggerBackward
|
||||
[InlineData(0x40000011u, CombatAnimationKind.Death)] // Dead
|
||||
[InlineData(0x8000003Eu, CombatAnimationKind.CombatStance)] // SwordCombat
|
||||
[InlineData(0x80000043u, CombatAnimationKind.CombatStance)] // SlingCombat
|
||||
[InlineData(0x80000044u, CombatAnimationKind.CombatStance)] // 2HandedSwordCombat
|
||||
public void ClassifyMotionCommand_RecognisesRetailCombatCommands(
|
||||
uint command,
|
||||
CombatAnimationKind expected)
|
||||
|
|
@ -27,6 +30,18 @@ public sealed class CombatAnimationPlannerTests
|
|||
Assert.Equal(expected, CombatAnimationPlanner.ClassifyMotionCommand(command));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0x0170, 0x10000170u)] // OffhandSlashHigh
|
||||
[InlineData(0x017D, 0x1000017Du)] // OffhandDoubleThrustMed
|
||||
[InlineData(0x018B, 0x1000018Bu)] // AttackLow6
|
||||
[InlineData(0x018E, 0x1000018Eu)] // PunchFastLow
|
||||
public void MotionCommandResolver_UsesNamedRetailLateCombatCommands(
|
||||
ushort wireCommand,
|
||||
uint expectedFullCommand)
|
||||
{
|
||||
Assert.Equal(expectedFullCommand, MotionCommandResolver.ReconstructFullCommand(wireCommand));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PlanFromWireCommand_Swing_IsActionOverlay()
|
||||
{
|
||||
|
|
|
|||
155
tests/AcDream.Core.Tests/Combat/CombatManeuverSelectorTests.cs
Normal file
155
tests/AcDream.Core.Tests/Combat/CombatManeuverSelectorTests.cs
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
using AcDream.Core.Combat;
|
||||
using DatReaderWriter.DBObjs;
|
||||
using DatReaderWriter.Types;
|
||||
using DatAttackHeight = DatReaderWriter.Enums.AttackHeight;
|
||||
using DatAttackType = DatReaderWriter.Enums.AttackType;
|
||||
using DatMotionCommand = DatReaderWriter.Enums.MotionCommand;
|
||||
using DatMotionStance = DatReaderWriter.Enums.MotionStance;
|
||||
using Xunit;
|
||||
|
||||
namespace AcDream.Core.Tests.Combat;
|
||||
|
||||
public sealed class CombatManeuverSelectorTests
|
||||
{
|
||||
[Fact]
|
||||
public void SelectMotion_UsesFirstEntryAtOrAboveSubdivision()
|
||||
{
|
||||
var table = MakeTable(
|
||||
Entry(DatMotionStance.SwordCombat, DatAttackHeight.Medium,
|
||||
DatAttackType.Slash, DatMotionCommand.SlashMed),
|
||||
Entry(DatMotionStance.SwordCombat, DatAttackHeight.Medium,
|
||||
DatAttackType.Slash, DatMotionCommand.BackhandMed));
|
||||
|
||||
var atThreshold = CombatManeuverSelector.SelectMotion(
|
||||
table,
|
||||
DatMotionStance.SwordCombat,
|
||||
DatAttackHeight.Medium,
|
||||
DatAttackType.Slash,
|
||||
powerLevel: CombatManeuverSelector.DefaultSubdivision);
|
||||
|
||||
var highPower = CombatManeuverSelector.SelectMotion(
|
||||
table,
|
||||
DatMotionStance.SwordCombat,
|
||||
DatAttackHeight.Medium,
|
||||
DatAttackType.Slash,
|
||||
powerLevel: 1f);
|
||||
|
||||
Assert.Equal(DatMotionCommand.SlashMed, atThreshold.Motion);
|
||||
Assert.Equal(DatMotionCommand.SlashMed, highPower.Motion);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SelectMotion_UsesSecondEntryBelowSubdivision()
|
||||
{
|
||||
var table = MakeTable(
|
||||
Entry(DatMotionStance.SwordCombat, DatAttackHeight.Medium,
|
||||
DatAttackType.Slash, DatMotionCommand.SlashMed),
|
||||
Entry(DatMotionStance.SwordCombat, DatAttackHeight.Medium,
|
||||
DatAttackType.Slash, DatMotionCommand.BackhandMed));
|
||||
|
||||
var selection = CombatManeuverSelector.SelectMotion(
|
||||
table,
|
||||
DatMotionStance.SwordCombat,
|
||||
DatAttackHeight.Medium,
|
||||
DatAttackType.Slash,
|
||||
powerLevel: 0.2f);
|
||||
|
||||
Assert.True(selection.Found);
|
||||
Assert.Equal(DatMotionCommand.BackhandMed, selection.Motion);
|
||||
Assert.Equal(DatAttackType.Slash, selection.EffectiveAttackType);
|
||||
Assert.Equal(2, selection.Candidates.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SelectMotion_ThrustSlashWeaponUsesTwoThirdsSubdivision()
|
||||
{
|
||||
var table = MakeTable(
|
||||
Entry(DatMotionStance.SwordCombat, DatAttackHeight.High,
|
||||
DatAttackType.Slash, DatMotionCommand.SlashHigh),
|
||||
Entry(DatMotionStance.SwordCombat, DatAttackHeight.High,
|
||||
DatAttackType.Slash, DatMotionCommand.BackhandHigh));
|
||||
|
||||
var normal = CombatManeuverSelector.SelectMotion(
|
||||
table,
|
||||
DatMotionStance.SwordCombat,
|
||||
DatAttackHeight.High,
|
||||
DatAttackType.Slash,
|
||||
powerLevel: 0.5f);
|
||||
|
||||
var thrustSlash = CombatManeuverSelector.SelectMotion(
|
||||
table,
|
||||
DatMotionStance.SwordCombat,
|
||||
DatAttackHeight.High,
|
||||
DatAttackType.Slash,
|
||||
powerLevel: 0.5f,
|
||||
isThrustSlashWeapon: true);
|
||||
|
||||
Assert.Equal(DatMotionCommand.SlashHigh, normal.Motion);
|
||||
Assert.Equal(DatMotionCommand.BackhandHigh, thrustSlash.Motion);
|
||||
Assert.Equal(CombatManeuverSelector.ThrustSlashSubdivision, thrustSlash.Subdivision);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SelectMotion_MissingLookupReturnsNone()
|
||||
{
|
||||
var table = MakeTable(
|
||||
Entry(DatMotionStance.BowCombat, DatAttackHeight.High,
|
||||
DatAttackType.Punch, DatMotionCommand.Shoot));
|
||||
|
||||
var selection = CombatManeuverSelector.SelectMotion(
|
||||
table,
|
||||
DatMotionStance.SwordCombat,
|
||||
DatAttackHeight.High,
|
||||
DatAttackType.Punch,
|
||||
powerLevel: 0.5f);
|
||||
|
||||
Assert.Equal(CombatManeuverSelection.None, selection);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FindMotions_PreservesRetailTableOrder()
|
||||
{
|
||||
var table = MakeTable(
|
||||
Entry(DatMotionStance.HandCombat, DatAttackHeight.Low,
|
||||
DatAttackType.Kick, DatMotionCommand.AttackLow1),
|
||||
Entry(DatMotionStance.HandCombat, DatAttackHeight.Low,
|
||||
DatAttackType.Kick, (DatMotionCommand)0x1000018Eu),
|
||||
Entry(DatMotionStance.HandCombat, DatAttackHeight.Low,
|
||||
DatAttackType.Punch, DatMotionCommand.AttackLow2));
|
||||
|
||||
var motions = CombatManeuverSelector.FindMotions(
|
||||
table,
|
||||
DatMotionStance.HandCombat,
|
||||
DatAttackHeight.Low,
|
||||
DatAttackType.Kick);
|
||||
|
||||
Assert.Equal(new[]
|
||||
{
|
||||
DatMotionCommand.AttackLow1,
|
||||
(DatMotionCommand)0x1000018Eu,
|
||||
}, motions);
|
||||
}
|
||||
|
||||
private static CombatTable MakeTable(params CombatManeuver[] maneuvers)
|
||||
{
|
||||
var table = new CombatTable();
|
||||
table.CombatManeuvers.AddRange(maneuvers);
|
||||
return table;
|
||||
}
|
||||
|
||||
private static CombatManeuver Entry(
|
||||
DatMotionStance stance,
|
||||
DatAttackHeight height,
|
||||
DatAttackType type,
|
||||
DatMotionCommand motion)
|
||||
{
|
||||
return new CombatManeuver
|
||||
{
|
||||
Style = stance,
|
||||
AttackHeight = height,
|
||||
AttackType = type,
|
||||
MinSkillLevel = 0,
|
||||
Motion = motion,
|
||||
};
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue