(f0d812055) v0.9.9.0

This commit is contained in:
Joonas Rikkonen
2020-04-23 19:19:37 +03:00
parent b647059b93
commit ac37a3b0e4
391 changed files with 15054 additions and 5420 deletions
@@ -36,14 +36,16 @@ namespace Barotrauma
private readonly float updateTargetsInterval = 1;
private readonly float updateMemoriesInverval = 1;
private readonly float attackLimbResetInterval = 2;
private float avoidLookAheadDistance;
private readonly float avoidLookAheadDistance;
private SteeringManager outsideSteering, insideSteering;
private float updateTargetsTimer;
private float updateMemoriesTimer;
private float attackLimbResetTimer;
private bool IsCoolDownRunning => AttackingLimb != null && AttackingLimb.attack.CoolDownTimer > 0;
public float CombatStrength => Character.Params.AI.CombatStrength;
private float Sight => Character.Params.AI.Sight;
@@ -63,6 +65,7 @@ namespace Barotrauma
get { return _attackingLimb; }
private set
{
attackLimbResetTimer = 0;
_attackingLimb = value;
attackVector = null;
Reverse = _attackingLimb != null && _attackingLimb.attack.Reverse;
@@ -376,7 +379,7 @@ namespace Barotrauma
return;
}
float squaredDistance = Vector2.DistanceSquared(WorldPosition, SelectedAiTarget.WorldPosition);
var attackLimb = GetAttackLimb(SelectedAiTarget.WorldPosition);
var attackLimb = AttackingLimb ?? GetAttackLimb(SelectedAiTarget.WorldPosition);
if (attackLimb != null && squaredDistance <= Math.Pow(attackLimb.attack.Range, 2))
{
run = true;
@@ -644,10 +647,14 @@ namespace Barotrauma
var pickable = item.GetComponent<Pickable>();
if (pickable != null)
{
var target = pickable.Picker?.AiTarget;
if (target?.Entity != null && !target.Entity.Removed)
Entity owner = pickable.Picker ?? item.ParentInventory?.Owner;
if (owner != null)
{
SelectedAiTarget = target;
var target = owner.AiTarget;
if (target?.Entity != null && !target.Entity.Removed)
{
SelectedAiTarget = target;
}
}
}
}
@@ -685,7 +692,7 @@ namespace Barotrauma
WallSection section = wall.Sections[i];
if (CanPassThroughHole(wall, i) && section?.gap != null)
{
if (SteerThroughGap(wall, section, section.gap.WorldPosition, deltaTime))
if (SteerThroughGap(wall, section, wall.SectionPosition(i, true), deltaTime))
{
return;
}
@@ -980,7 +987,15 @@ namespace Barotrauma
{
// If not, reset the attacking limb, if the cooldown is not running
// Don't use the property, because we don't want cancel reversing, if we are reversing.
_attackingLimb = null;
if (attackLimbResetTimer > attackLimbResetInterval)
{
_attackingLimb = null;
attackLimbResetTimer = 0;
}
else
{
attackLimbResetTimer += deltaTime;
}
}
}
Limb steeringLimb = canAttack ? AttackingLimb : null;
@@ -1045,7 +1060,8 @@ namespace Barotrauma
}
}
// Steer towards the target if in the same room and swimming
if ((Character.AnimController.InWater || pursue) && targetCharacter != null && VisibleHulls.Contains(targetCharacter.CurrentHull))
if ((Character.AnimController.InWater || pursue || !Character.AnimController.CanWalk) &&
(targetCharacter != null && VisibleHulls.Contains(targetCharacter.CurrentHull) || Character.CanSeeTarget(SelectedAiTarget.Entity)))
{
SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(attackSimPos - steeringLimb.SimPosition));
}
@@ -1112,31 +1128,13 @@ namespace Barotrauma
return false;
}
private bool CanAttack(Entity target)
{
if (target == null) { return false; }
if (target is Character c)
{
if (Character.CurrentHull == null && c.CurrentHull != null || Character.CurrentHull != null && c.CurrentHull == null)
{
return false;
}
}
else if (target is Item i && i.GetComponent<Door>() == null)
{
if (Character.CurrentHull == null && i.CurrentHull != null || Character.CurrentHull != null && i.CurrentHull == null)
{
return false;
}
}
return true;
}
private readonly List<Limb> attackLimbs = new List<Limb>();
private readonly List<float> weights = new List<float>();
private Limb GetAttackLimb(Vector2 attackWorldPos, Limb ignoredLimb = null)
{
var currentContexts = Character.GetAttackContexts();
Entity target = wallTarget != null ? wallTarget.Structure : SelectedAiTarget?.Entity;
if (!CanAttack(target)) { return null; }
if (target == null) { return null; }
Limb selectedLimb = null;
float currentPriority = -1;
foreach (Limb limb in Character.AnimController.Limbs)
@@ -1153,12 +1151,26 @@ namespace Barotrauma
if (attack.Conditionals.Any(c => !c.Matches(se))) { continue; }
}
if (attack.Conditionals.Any(c => c.TargetSelf && !c.Matches(Character))) { continue; }
float priority = CalculatePriority(limb, attackWorldPos);
if (priority > currentPriority)
if (AIParams.RandomAttack)
{
currentPriority = priority;
selectedLimb = limb;
attackLimbs.Add(limb);
weights.Add(limb.attack.Priority);
}
else
{
float priority = CalculatePriority(limb, attackWorldPos);
if (priority > currentPriority)
{
currentPriority = priority;
selectedLimb = limb;
}
}
}
if (AIParams.RandomAttack)
{
selectedLimb = ToolBox.SelectWeightedRandom(attackLimbs, weights, Rand.RandSync.Server);
attackLimbs.Clear();
weights.Clear();
}
return selectedLimb;
@@ -1176,18 +1188,22 @@ namespace Barotrauma
{
wallTarget = null;
if (SelectedAiTarget == null) { return; }
if (SelectedAiTarget.Entity == null) { return; }
//check if there's a wall between the target and the Character
Vector2 rayStart = SimPosition;
Vector2 rayEnd = SelectedAiTarget.SimPosition;
bool offset = SelectedAiTarget.Entity.Submarine != null && Character.Submarine == null;
if (offset)
if (SelectedAiTarget.Entity.Submarine != null && Character.Submarine == null)
{
rayStart -= SelectedAiTarget.Entity.Submarine.SimPosition;
}
else if (SelectedAiTarget.Entity.Submarine == null && Character.Submarine != null)
{
rayEnd -= Character.Submarine.SimPosition;
}
Body closestBody = Submarine.CheckVisibility(rayStart, rayEnd, ignoreSubs: true, ignoreSensors: CanEnterSubmarine, ignoreDisabledWalls: CanEnterSubmarine);
if (Submarine.LastPickedFraction != 1.0f && closestBody != null)
{
if (closestBody.UserData is Structure wall && wall.Submarine != null)
if (closestBody.UserData is Structure wall && wall.Submarine != null && (wall.Submarine.Info.IsPlayer || wall.Submarine.Info.IsOutpost && TargetOutposts))
{
int sectionIndex = wall.FindSectionIndex(ConvertUnits.ToDisplayUnits(Submarine.LastPickedPosition));
float sectionDamage = wall.SectionDamage(sectionIndex);
@@ -1265,20 +1281,26 @@ namespace Barotrauma
Character.AnimController.ReleaseStuckLimbs();
LatchOntoAI?.DeattachFromBody();
if (attacker == null || attacker.AiTarget == null) { return; }
bool isFriendly = attacker.SpeciesName == Character.SpeciesName || attacker.Params.Group == Character.Params.Group;
if (wasLatched)
{
avoidTimer = avoidTime * Rand.Range(0.75f, 1.25f);
SelectTarget(attacker.AiTarget);
if (!isFriendly)
{
SelectTarget(attacker.AiTarget);
}
return;
}
if (State == AIState.Flee)
{
SelectTarget(attacker.AiTarget);
if (!isFriendly)
{
SelectTarget(attacker.AiTarget);
}
return;
}
if (attackResult.Damage > 0.0f)
if (!isFriendly && attackResult.Damage > 0.0f)
{
bool canAttack = attacker.Submarine == Character.Submarine && canAttackCharacters || attacker.Submarine != null && canAttackSub;
if (Character.Params.AI.AttackWhenProvoked && canAttack)
@@ -1325,13 +1347,20 @@ namespace Barotrauma
ChangeTargetState(attacker, canAttack ? AIState.Attack : AIState.Escape, 100);
}
}
else if (canAttack && attacker.IsHuman && AIParams.TryGetTarget(attacker.SpeciesName, out CharacterParams.TargetParams targetingParams))
{
if (targetingParams.State == AIState.Aggressive)
{
ChangeTargetState(attacker, AIState.Attack, 100);
}
}
}
AITargetMemory targetMemory = GetTargetMemory(attacker.AiTarget);
targetMemory.Priority += GetRelativeDamage(attackResult.Damage, Character.Vitality) * AggressionHurt;
// Only allow to react once. Otherwise would attack the target with only a fraction of a cooldown
bool retaliate = SelectedAiTarget != attacker.AiTarget && attacker.Submarine == Character.Submarine;
bool retaliate = !isFriendly && SelectedAiTarget != attacker.AiTarget && attacker.Submarine == Character.Submarine;
bool avoidGunFire = Character.Params.AI.AvoidGunfire && attacker.Submarine != Character.Submarine;
if (State == AIState.Attack && !IsCoolDownRunning)
@@ -1376,6 +1405,8 @@ namespace Barotrauma
IDamageable damageTarget = wallTarget != null ? wallTarget.Structure : SelectedAiTarget.Entity as IDamageable;
if (damageTarget != null)
{
//simulate attack input to get the character to attack client-side
Character.SetInput(InputType.Attack, true, true);
if (attackingLimb.UpdateAttack(deltaTime, attackSimPos, damageTarget, out AttackResult attackResult, distance, targetLimb))
{
if (damageTarget.Health > 0)
@@ -1519,7 +1550,7 @@ namespace Barotrauma
if (aiTarget.Type == AITarget.TargetType.HumanOnly) { continue; }
if (!TargetOutposts)
{
if (aiTarget.Entity.Submarine != null && aiTarget.Entity.Submarine.IsOutpost) { continue; }
if (aiTarget.Entity.Submarine != null && aiTarget.Entity.Submarine.Info.IsOutpost) { continue; }
}
Character targetCharacter = aiTarget.Entity as Character;
//ignore the aitarget if it is the Character itself
@@ -1529,30 +1560,6 @@ namespace Barotrauma
string targetingTag = null;
if (targetCharacter != null)
{
if (targetCharacter.Submarine != Character.Submarine)
{
// In a different sub or the target is outside when we are inside or vice versa.
// Make an exception for humans so that creatures cannot avoid/flee from humans that are inside a sub.
if (!targetCharacter.IsHuman && State == AIState.Avoid && State == AIState.Escape & State == AIState.Flee)
{
// If we are escaping, let's not ignore the target entirely, because there can be a gaps where we or they can go freely
if (targetCharacter.Submarine != null)
{
// Target is inside -> reduce the priority
valueModifier *= 0.5f;
if (Character.Submarine != null)
{
// Both inside different submarines -> can ignore safely
continue;
}
}
}
else
{
// Don't attack targets that are not in the same submarine
continue;
}
}
if (targetCharacter.IsDead)
{
targetingTag = "dead";
@@ -1604,9 +1611,11 @@ namespace Barotrauma
}
else if (aiTarget.Entity != null)
{
// Ignore all structures and items inside wrecks
if (aiTarget.Entity.Submarine != null && aiTarget.Entity.Submarine.Info.IsWreck) { continue; }
// Ignore the target if it's a room and the character is already inside a sub
if (character.CurrentHull != null && aiTarget.Entity is Hull) { continue; }
Door door = null;
if (aiTarget.Entity is Item item)
{
@@ -1644,15 +1653,13 @@ namespace Barotrauma
{
continue;
}
if (character.CurrentHull != null)
bool isCharacterOutside = s.Submarine == null || character.CurrentHull == null;
bool targetInnerWalls = AIParams.TargetInnerWalls;
if (!isCharacterOutside && !targetInnerWalls)
{
// Ignore walls when inside (walltargets still work)
continue;
}
if (s.Submarine == null)
{
continue;
}
valueModifier = 1;
if (!Character.AnimController.CanEnterSubmarine && IsWallDisabled(s))
{
@@ -1665,28 +1672,43 @@ namespace Barotrauma
bool leadsInside = !section.gap.IsRoomToRoom && section.gap.FlowTargetHull != null;
if (Character.AnimController.CanEnterSubmarine)
{
if (CanPassThroughHole(s, i))
if (isCharacterOutside)
{
valueModifier *= leadsInside ? (AggressiveBoarding ? 5 : 1) : 0;
if (CanPassThroughHole(s, i))
{
valueModifier *= leadsInside ? (AggressiveBoarding ? 5 : 1) : (targetInnerWalls ? 1 : 0);
}
else
{
// Ignore holes that cannot be passed through if cannot attack items/structures. Holes that are big enough should be targeted, so that we can get in
if (!canAttackSub)
{
valueModifier = 0;
break;
}
if (AggressiveBoarding && leadsInside)
{
// Up to 100% priority increase for every gap in the wall when an aggressive boarder is outside
valueModifier *= 1 + section.gap.Open;
}
}
}
else
else if (!canAttackSub || CanPassThroughHole(s, i))
{
// Ignore holes that cannot be passed through if cannot attack items/structures. Holes that are big enough should be targeted, so that we can get in
if (!canAttackSub)
{
valueModifier = 0;
break;
}
if (AggressiveBoarding)
{
// Up to 100% priority increase for every gap in the wall
valueModifier *= 1 + section.gap.Open;
}
// Already inside -> ignore holes in the walls and ignore walls if cannot attack the sub.
valueModifier = 0;
break;
}
else if (canAttackSub && !AggressiveBoarding)
{
// We are actually interested in breaking things -> reduce the priority when the wall is already broken
valueModifier *= 1 - section.gap.Open * 0.25f;
}
}
else if (!leadsInside)
else if (!leadsInside || !canAttackSub)
{
// Ignore inner walls
// Can't get in, ignore inner walls
// Also ignore all walls if cannot attack the sub
valueModifier = 0;
break;
}
@@ -1774,6 +1796,50 @@ namespace Barotrauma
// Don't target items that we own.
// This is a rare case, and almost entirely related to Humanhusks, so let's check it last to reduce unnecessary checks (although the check shouldn't be expensive)
if (aiTarget.Entity is Item i && i.IsOwnedBy(character)) { continue; }
if (targetCharacter != null)
{
if (targetCharacter.Submarine != Character.Submarine)
{
if (targetCharacter.Submarine != null)
{
// Target is inside -> reduce the priority
valueModifier *= 0.5f;
if (Character.Submarine != null)
{
// Both inside different submarines -> can ignore safely
continue;
}
}
else if (Character.CurrentHull != null)
{
// Target outside, but we are inside -> Check if we can get to the target.
// Only check if we are not already targeting the character.
// If we are, keep the target (unless we choose another).
if (SelectedAiTarget?.Entity != targetCharacter)
{
foreach (var gap in Character.CurrentHull.ConnectedGaps)
{
var door = gap.ConnectedDoor;
if (door == null || !door.IsOpen)
{
var wall = gap.ConnectedWall;
if (wall != null)
{
for (int j = 0; j < wall.Sections.Length; j++)
{
WallSection section = wall.Sections[j];
if (!CanPassThroughHole(wall, j) && section?.gap != null)
{
continue;
}
}
}
}
}
}
}
}
}
newTarget = aiTarget;
selectedTargetMemory = targetMemory;
targetValue = valueModifier;