(b619092ec) Continue working on the Charybdis. Refactor enemy ai attack limb selection logic. Implement FallBackUntilCanAttack behaviour.

This commit is contained in:
Joonas Rikkonen
2019-03-29 17:26:12 +02:00
parent a64096b5aa
commit 4eba8faccf
2 changed files with 79 additions and 15 deletions
@@ -635,6 +635,7 @@ namespace Barotrauma
var newLimb = GetAttackLimb(attackWorldPos, previousLimb); var newLimb = GetAttackLimb(attackWorldPos, previousLimb);
if (newLimb != null) if (newLimb != null)
{ {
// Attack with the new limb
AttackingLimb = newLimb; AttackingLimb = newLimb;
} }
else else
@@ -659,6 +660,49 @@ namespace Barotrauma
} }
} }
break; break;
case AIBehaviorAfterAttack.FallBackUntilCanAttack:
if (AttackingLimb.attack.SecondaryCoolDown <= 0)
{
// No (valid) secondary cooldown defined.
UpdateFallBack(attackWorldPos, deltaTime);
return;
}
else
{
if (AttackingLimb.attack.SecondaryCoolDownTimer <= 0)
{
// Don't allow attacking when the attack target has just changed.
if (_previousAiTarget != null && SelectedAiTarget != _previousAiTarget)
{
UpdateFallBack(attackWorldPos, deltaTime);
return;
}
else
{
// If the secondary cooldown is defined and expired, check if we can switch the attack
var previousLimb = AttackingLimb;
var newLimb = GetAttackLimb(attackWorldPos, previousLimb);
if (newLimb != null)
{
// Attack with the new limb
AttackingLimb = newLimb;
}
else
{
// No new limb was found.
UpdateFallBack(attackWorldPos, deltaTime);
return;
}
}
}
else
{
// Cooldown not yet expired -> steer away from the target
UpdateFallBack(attackWorldPos, deltaTime);
return;
}
}
break;
case AIBehaviorAfterAttack.FallBack: case AIBehaviorAfterAttack.FallBack:
default: default:
UpdateFallBack(attackWorldPos, deltaTime); UpdateFallBack(attackWorldPos, deltaTime);
@@ -784,20 +828,39 @@ namespace Barotrauma
{ {
AttackContext currentContext = Character.GetAttackContext(); AttackContext currentContext = Character.GetAttackContext();
var target = wallTarget != null ? wallTarget.Structure : SelectedAiTarget.Entity; var target = wallTarget != null ? wallTarget.Structure : SelectedAiTarget.Entity;
var limbs = Character.AnimController.Limbs Limb selectedLimb = null;
.Where(l => float currentPriority = 0;
l != ignoredLimb && foreach (Limb limb in Character.AnimController.Limbs)
l.attack != null && {
l.attack.CoolDownTimer <= 0 && if (limb == ignoredLimb) { continue; }
!l.IsSevered && if (limb.IsSevered || limb.IsStuck) { continue; }
!l.IsStuck && var attack = limb.attack;
l.attack.IsValidContext(currentContext) && if (attack == null) { continue; }
l.attack.IsValidTarget(target) && if (attack.CoolDownTimer > 0) { continue; }
l.attack.Conditionals.All(c => (target is ISerializableEntity se && c.Matches(se)) || !(target is ISerializableEntity) || !(target is Character))) if (!attack.IsValidContext(currentContext)) { continue; }
.OrderByDescending(l => l.attack.Priority) if (!attack.IsValidTarget(target)) { continue; }
.ThenBy(l => Vector2.Distance(l.WorldPosition, attackWorldPos)); if (target is ISerializableEntity se && target is Character)
// TODO: priority should probably not override the distance -> use values instead of booleans {
return limbs.FirstOrDefault(); // TODO: allow conditionals of which matching any is enough instead of having to fulfill all
if (attack.Conditionals.Any(c => !c.Matches(se))) { continue; }
}
float priority = CalculatePriority(limb, attackWorldPos);
if (priority > currentPriority)
{
currentPriority = priority;
selectedLimb = limb;
}
}
return selectedLimb;
float CalculatePriority(Limb limb, Vector2 attackPos)
{
float dist = Vector2.Distance(limb.WorldPosition, attackPos);
// The limb is ignored if the target is not close. Prevents character going in reverse if very far away from it.
// We also need a max value that is more than the actual range.
float distanceFactor = MathHelper.Lerp(1, 0, MathUtils.InverseLerp(0, limb.attack.Range * 3, dist));
return (1 + limb.attack.Priority) * distanceFactor;
}
} }
private void UpdateWallTarget() private void UpdateWallTarget()
@@ -28,6 +28,7 @@ namespace Barotrauma
public enum AIBehaviorAfterAttack public enum AIBehaviorAfterAttack
{ {
FallBack, FallBack,
FallBackUntilCanAttack,
PursueIfCanAttack, PursueIfCanAttack,
Pursue Pursue
} }
@@ -188,7 +189,7 @@ namespace Barotrauma
public readonly List<Affliction> Afflictions = new List<Affliction>(); public readonly List<Affliction> Afflictions = new List<Affliction>();
/// <summary> /// <summary>
/// Only affects ai decision making. /// Only affects ai decision making. All the conditionals has to be met in order to select the attack. TODO: allow to define conditionals using any (implemented in StatusEffect -> move from there to PropertyConditional?)
/// </summary> /// </summary>
public List<PropertyConditional> Conditionals { get; private set; } = new List<PropertyConditional>(); public List<PropertyConditional> Conditionals { get; private set; } = new List<PropertyConditional>();