/// <summary> /// Selects a random combat maneuver for a monster's next attack /// </summary> public MotionCommand?GetCombatManeuver() { // similar to Player.GetSwingAnimation(), more logging if (CombatTable == null) { log.Error($"{Name} ({Guid}).GetCombatManeuver() - CombatTable is null"); return(null); } //ShowCombatTable(); var motionTable = DatManager.PortalDat.ReadFromDat <DatLoader.FileTypes.MotionTable>(MotionTableId); if (motionTable == null) { log.Error($"{Name} ({Guid}).GetCombatManeuver() - motionTable is null"); return(null); } if (!CombatTable.Stances.TryGetValue(CurrentMotionState.Stance, out var stanceManeuvers)) { log.Error($"{Name} ({Guid}).GetCombatManeuver() - couldn't find stance {CurrentMotionState.Stance} in CMT {CombatTableDID:X8}"); return(null); } var stanceKey = (uint)CurrentMotionState.Stance << 16 | ((uint)MotionCommand.Ready & 0xFFFFF); motionTable.Links.TryGetValue(stanceKey, out var motions); if (motions == null) { log.Error($"{Name} ({Guid}).GetCombatManeuver() - couldn't find stance {CurrentMotionState.Stance} in MotionTable {MotionTableId:X8}"); return(null); } // choose a random attack height? // apparently this might have been based on monster Z vs. player Z? // 28659 - Uber Penguin (CMT 30000040) doesn't have High attack height // do a more thorough investigation for this... var startHeight = stanceManeuvers.Table.Count == 3 ? 1 : 2; AttackHeight = (AttackHeight)ThreadSafeRandom.Next(startHeight, 3); if (!stanceManeuvers.Table.TryGetValue(AttackHeight.Value, out var attackTypes)) { log.Error($"{Name} ({Guid}).GetCombatManeuver() - couldn't find attack height {AttackHeight} for stance {CurrentMotionState.Stance} in CMT {CombatTableDID:X8}"); return(null); } if (IsDualWieldAttack) { DualWieldAlternate = !DualWieldAlternate; } var offhand = IsDualWieldAttack && !DualWieldAlternate; var weapon = GetEquippedMeleeWeapon(); // monsters supposedly always used 0.5 PowerLevel according to anon docs, // which translates into a 1.0 PowerMod if (weapon != null) { AttackType = weapon.GetAttackType(CurrentMotionState.Stance, 0.5f, offhand); } else { if (AttackHeight != ACE.Entity.Enum.AttackHeight.Low) { AttackType = AttackType.Punch; } else { AttackType = AttackType.Kick; } } if (!attackTypes.Table.TryGetValue(AttackType, out var maneuvers) || maneuvers.Count == 0) { if (AttackType == AttackType.Kick) { AttackType = AttackType.Punch; if (!attackTypes.Table.TryGetValue(AttackType, out maneuvers) || maneuvers.Count == 0) { log.Error($"{Name} ({Guid}).GetCombatManeuver() - couldn't find attack type Kick or Punch for attack height {AttackHeight} and stance {CurrentMotionState.Stance} in CMT {CombatTableDID:X8}"); return(null); } } else if (AttackType.IsMultiStrike()) { var reduced = AttackType.ReduceMultiStrike(); if (!attackTypes.Table.TryGetValue(reduced, out maneuvers) || maneuvers.Count == 0) { log.Error($"{Name} ({Guid}).GetCombatManeuver() - couldn't find attack type {reduced} for attack height {AttackHeight} and stance {CurrentMotionState.Stance} in CMT {CombatTableDID:X8}"); return(null); } //else //log.Info($"{Name} ({Guid}).GetCombatManeuver() - successfully reduced attack type {AttackType} to {reduced} for attack height {AttackHeight} and stance {CurrentMotionState.Stance} in CMT {CombatTableDID:X8}"); } else { log.Error($"{Name} ({Guid}).GetCombatManeuver() - couldn't find attack type {AttackType} for attack height {AttackHeight} and stance {CurrentMotionState.Stance} in CMT {CombatTableDID:X8}"); return(null); } } var motionCommand = maneuvers[0]; if (maneuvers.Count > 1) { // only used for special attacks? // note that with rolling for AttackHeight first, // for a CMT with high, med, med-special, and low // the chance of rolling the special attack is reduced from 1/4 to 1/6 -- investigate var rng = ThreadSafeRandom.Next(0, maneuvers.Count - 1); motionCommand = maneuvers[rng]; } // ensure this motionCommand exists in monster's motion table if (!motions.ContainsKey((uint)motionCommand)) { // for some reason, the combat maneuvers table can return stance motions that don't exist in the motion table // ie. skeletons (combat maneuvers table 0x30000000, motion table 0x09000025) // for sword combat, they have double and triple strikes (dagger / two-handed only?) if (motionCommand.IsMultiStrike()) { var singleStrike = motionCommand.ReduceMultiStrike(); if (motions.ContainsKey((uint)singleStrike)) { //log.Info($"{Name} ({Guid}).GetCombatManeuver() - successfully reduced {motionCommand} to {singleStrike}"); return(singleStrike); } } else if (motionCommand.IsSubsequent()) { var firstCommand = motionCommand.ReduceSubsequent(); if (motions.ContainsKey((uint)firstCommand)) { //log.Info($"{Name} ({Guid}).GetCombatManeuver() - successfully reduced {motionCommand} to {firstCommand}"); return(firstCommand); } } log.Error($"{Name} ({Guid}).GetCombatManeuver() - couldn't find {motionCommand} in MotionTable {MotionTableId:X8}"); return(null); } //Console.WriteLine(motionCommand); return(motionCommand); }