(b619092ec) Continue working on the Charybdis. Refactor enemy ai attack limb selection logic. Implement FallBackUntilCanAttack behaviour.
This commit is contained in:
@@ -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>();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user