(5a377a8ee) Unstable v0.9.1000.0

This commit is contained in:
Juan Pablo Arce
2020-05-13 12:55:42 -03:00
parent b143329701
commit a1ca41aa5d
426 changed files with 14384 additions and 5708 deletions
@@ -36,14 +36,17 @@ 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 IndoorsSteeringManager PathSteering => insideSteering as IndoorsSteeringManager;
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 +66,7 @@ namespace Barotrauma
get { return _attackingLimb; }
private set
{
attackLimbResetTimer = 0;
_attackingLimb = value;
attackVector = null;
Reverse = _attackingLimb != null && _attackingLimb.attack.Reverse;
@@ -82,8 +86,9 @@ namespace Barotrauma
private readonly float colliderWidth;
private readonly float colliderLength;
private readonly int requiredHoleCount;
private readonly bool canAttackSub;
private readonly bool canAttackCharacters;
private bool canAttackWalls;
private bool canAttackDoors;
private bool canAttackCharacters;
private readonly float priorityFearIncreasement = 2;
private readonly float memoryFadeTime = 0.5f;
@@ -183,24 +188,9 @@ namespace Barotrauma
}
}
bool canBreakDoors = false;
if (GetTarget("room")?.Priority > 0.0f)
{
var currentContexts = Character.GetAttackContexts();
foreach (Limb limb in Character.AnimController.Limbs)
{
if (limb.attack == null) { continue; }
if (!limb.attack.IsValidTarget(AttackTarget.Structure)) { continue; }
if (limb.attack.IsValidContext(currentContexts) && limb.attack.StructureDamage > 0.0f)
{
canBreakDoors = true;
break;
}
}
}
ReevaluateAttacks();
outsideSteering = new SteeringManager(this);
insideSteering = new IndoorsSteeringManager(this, false, canBreakDoors);
insideSteering = new IndoorsSteeringManager(this, false, canAttackDoors);
steeringManager = outsideSteering;
State = AIState.Idle;
@@ -210,9 +200,6 @@ namespace Barotrauma
requiredHoleCount = (int)Math.Ceiling(ConvertUnits.ToDisplayUnits(colliderWidth) / Structure.WallSectionSize);
avoidLookAheadDistance = Math.Max(colliderWidth * 3, 1.5f);
canAttackSub = Character.AnimController.CanAttackSubmarine;
canAttackCharacters = Character.AnimController.CanAttackCharacters;
}
private CharacterParams.AIParams AIParams => Character.Params.AI;
@@ -227,27 +214,28 @@ namespace Barotrauma
selectedTargetMemory.Priority = priority;
}
private float escapeMargin;
private float movementMargin;
public override void Update(float deltaTime)
{
if (DisableEnemyAI) { return; }
base.Update(deltaTime);
bool ignorePlatforms = (-Character.AnimController.TargetMovement.Y > Math.Abs(Character.AnimController.TargetMovement.X));
if (steeringManager is IndoorsSteeringManager)
bool ignorePlatforms = Character.AnimController.TargetMovement.Y < -0.5f && (-Character.AnimController.TargetMovement.Y > Math.Abs(Character.AnimController.TargetMovement.X));
if (steeringManager == insideSteering)
{
var currPath = ((IndoorsSteeringManager)steeringManager).CurrentPath;
var currPath = PathSteering.CurrentPath;
if (currPath != null && currPath.CurrentNode != null)
{
if (currPath.CurrentNode.SimPosition.Y < Character.AnimController.GetColliderBottom().Y)
{
ignorePlatforms = true;
// Don't allow to jump from too high.
float allowedJumpHeight = Character.AnimController.ImpactTolerance / 2;
float height = Math.Abs(currPath.CurrentNode.SimPosition.Y - Character.SimPosition.Y);
ignorePlatforms = height < allowedJumpHeight;
}
}
}
Character.AnimController.IgnorePlatforms = ignorePlatforms;
//clients get the facing direction from the server
@@ -376,7 +364,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;
@@ -393,7 +381,7 @@ namespace Barotrauma
{
bool isBeingChased = IsBeingChased;
float reactDistance = !isBeingChased && selectedTargetingParams != null && selectedTargetingParams.ReactDistance > 0 ? selectedTargetingParams.ReactDistance : GetPerceivingRange(SelectedAiTarget);
if (squaredDistance <= Math.Pow(reactDistance + escapeMargin, 2))
if (squaredDistance <= Math.Pow(reactDistance + movementMargin, 2))
{
float halfReactDistance = reactDistance / 2;
float attackDistance = selectedTargetingParams != null && selectedTargetingParams.AttackDistance > 0 ? selectedTargetingParams.AttackDistance : halfReactDistance;
@@ -405,21 +393,48 @@ namespace Barotrauma
else
{
run = isBeingChased ? true : squaredDistance < Math.Pow(halfReactDistance, 2);
if (escapeMargin <= 0)
if (movementMargin <= 0)
{
escapeMargin = halfReactDistance;
movementMargin = halfReactDistance;
}
escapeMargin = MathHelper.Clamp(escapeMargin += deltaTime, halfReactDistance, reactDistance);
movementMargin = MathHelper.Clamp(movementMargin += deltaTime, halfReactDistance, reactDistance);
UpdateEscape(deltaTime);
}
}
else
{
escapeMargin = 0;
movementMargin = 0;
UpdateIdle(deltaTime);
}
}
break;
case AIState.Protect:
if (SelectedAiTarget == null || SelectedAiTarget.Entity == null || SelectedAiTarget.Entity.Removed)
{
State = AIState.Idle;
return;
}
if (SelectedAiTarget.Entity is Character targetCharacter && targetCharacter.LastAttacker is Character attacker)
{
// Attack the character that attacked the target we are protecting
ChangeTargetState(attacker, AIState.Attack, selectedTargetingParams.Priority * 2);
SelectTarget(attacker.AiTarget);
return;
}
float sqrDist = Vector2.DistanceSquared(WorldPosition, SelectedAiTarget.WorldPosition);
float reactDist = selectedTargetingParams != null && selectedTargetingParams.ReactDistance > 0 ? selectedTargetingParams.ReactDistance : GetPerceivingRange(SelectedAiTarget);
if (sqrDist > Math.Pow(reactDist + movementMargin, 2))
{
movementMargin = reactDist;
run = true;
UpdateFollow(deltaTime);
}
else
{
movementMargin = MathHelper.Clamp(movementMargin -= deltaTime, 0, reactDist);
UpdateIdle(deltaTime);
}
break;
default:
throw new NotImplementedException();
}
@@ -432,6 +447,8 @@ namespace Barotrauma
SwarmBehavior.Refresh();
SwarmBehavior.UpdateSteering(deltaTime);
}
// Ensure that the creature keeps inside the level
SteerInsideLevel(deltaTime);
float speed = Character.AnimController.GetCurrentSpeed(run && Character.CanRun);
steeringManager.Update(speed);
Character.AnimController.TargetMovement = Character.ApplyMovementLimits(Steering, State == AIState.Idle && Character.AnimController.InWater ? Steering.Length() : speed);
@@ -456,7 +473,6 @@ namespace Barotrauma
SteeringManager.SteeringAvoid(deltaTime, lookAheadDistance: avoidLookAheadDistance, weight: 5);
return;
}
SteerInsideLevel(deltaTime);
}
var target = SelectedAiTarget ?? _lastAiTarget;
if (target?.Entity != null && !target.Entity.Removed && PreviousState == AIState.Attack && Character.CurrentHull == null)
@@ -576,10 +592,10 @@ namespace Barotrauma
}
else if (pathSteering != null)
{
if (canAttackSub && hasValidPath)
if (canAttackDoors && hasValidPath)
{
var door = pathSteering.CurrentPath.CurrentNode?.ConnectedDoor ?? pathSteering.CurrentPath.NextNode?.ConnectedDoor;
if (door != null && !door.IsOpen)
if (door != null && !door.IsOpen && !door.IsBroken)
{
if (SelectedAiTarget != door.Item.AiTarget)
{
@@ -615,7 +631,6 @@ namespace Barotrauma
{
SteeringManager.SteeringWander();
SteeringManager.SteeringAvoid(deltaTime, lookAheadDistance: avoidLookAheadDistance, weight: 5);
SteerInsideLevel(deltaTime);
}
}
}
@@ -641,13 +656,18 @@ namespace Barotrauma
if (SelectedAiTarget.Entity is Item item)
{
// If the item is held by a character, attack the character instead.
var pickable = item.GetComponent<Pickable>();
if (pickable != null)
Character owner = GetOwner(item);
if (owner != null)
{
var target = pickable.Picker?.AiTarget;
if (target?.Entity != null && !target.Entity.Removed)
if (IsFriendly(Character, owner))
{
SelectedAiTarget = target;
ResetAITarget();
State = AIState.Idle;
return;
}
else
{
SelectedAiTarget = owner.AiTarget;
}
}
}
@@ -659,7 +679,8 @@ namespace Barotrauma
{
attackWorldPos += wallTarget.Structure.Submarine.Position;
}
attackSimPos = ConvertUnits.ToSimUnits(attackWorldPos);
attackSimPos = Character.Submarine == wallTarget.Structure.Submarine ? wallTarget.Position : attackWorldPos;
attackSimPos = ConvertUnits.ToSimUnits(attackSimPos);
}
else
{
@@ -685,7 +706,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;
}
@@ -697,7 +718,7 @@ namespace Barotrauma
var door = i.GetComponent<Door>();
// Steer through the door manually if it's open or broken
// Don't try to enter dry hulls if cannot walk or if the gap is too narrow
if (door?.LinkedGap?.FlowTargetHull != null && !door.LinkedGap.IsRoomToRoom && door.IsOpen)
if (door?.LinkedGap?.FlowTargetHull != null && !door.LinkedGap.IsRoomToRoom && (door.IsOpen || door.IsBroken))
{
if (Character.AnimController.CanWalk || door.LinkedGap.FlowTargetHull.WaterPercentage > 25)
{
@@ -715,7 +736,6 @@ namespace Barotrauma
}
else if (SelectedAiTarget.Entity is Structure w && wallTarget == null)
{
// Targeting only the outer walls
bool isBroken = true;
for (int i = 0; i < w.Sections.Length; i++)
{
@@ -886,24 +906,11 @@ namespace Barotrauma
}
canAttack = AttackingLimb != null && AttackingLimb.attack.CoolDownTimer <= 0;
}
if (!canAttack && SelectedAiTarget.Entity.Submarine != null && !canAttackSub)
if (!Character.AnimController.SimplePhysicsEnabled && SelectedAiTarget.Entity.Submarine != null && Character.Submarine == null && (!canAttackDoors || !canAttackWalls || !AIParams.TargetOuterWalls))
{
float dist = Vector2.Distance(Character.AnimController.MainLimb.WorldPosition, attackWorldPos);
if (wallTarget != null)
if (Vector2.Distance(Character.WorldPosition, attackWorldPos) < 2000 * 2000)
{
// Steer towards the target, but turn away if a wall is blocking the way
if (dist < ConvertUnits.ToDisplayUnits(colliderLength) * 3)
{
State = AIState.Idle;
IgnoreTarget(SelectedAiTarget);
// Resetting the ai target prevents the character from chasing it
ResetAITarget();
return;
}
}
else if (dist < 1000)
{
// Check that we are not bumping into a door
// Check that we are not bumping into a door or a wall
Vector2 rayStart = SimPosition;
if (Character.Submarine == null)
{
@@ -912,9 +919,10 @@ namespace Barotrauma
Vector2 toTarget = SelectedAiTarget.WorldPosition - WorldPosition;
Vector2 rayEnd = rayStart + toTarget.ClampLength(Character.AnimController.Collider.GetLocalFront().Length() * 2);
Body closestBody = Submarine.CheckVisibility(rayStart, rayEnd, ignoreSubs: true);
if (Submarine.LastPickedFraction != 1.0f && closestBody != null && closestBody.UserData is Item i && i.Submarine != null && i.GetComponent<Door>() != null)
if (Submarine.LastPickedFraction != 1.0f && closestBody != null &&
(!AIParams.TargetOuterWalls || !canAttackWalls && closestBody.UserData is Structure s && s.Submarine != null || !canAttackDoors && closestBody.UserData is Item i && i.Submarine != null && i.GetComponent<Door>() != null))
{
// Target is unreachable, there's a door ahead
// Target is unreachable, there's a door or wall ahead
State = AIState.Idle;
IgnoreTarget(SelectedAiTarget);
ResetAITarget();
@@ -925,7 +933,7 @@ namespace Barotrauma
float distance = 0;
Limb attackTargetLimb = null;
Character targetCharacter = SelectedAiTarget.Entity as Character;
if (canAttack)
if (canAttack && !Character.AnimController.SimplePhysicsEnabled)
{
// Target a specific limb instead of the target center position
if (wallTarget == null && targetCharacter != null)
@@ -943,7 +951,7 @@ namespace Barotrauma
attackSimPos = Character.GetRelativeSimPosition(attackTargetLimb);
}
// Check that we can reach the target
Vector2 toTarget = attackWorldPos - AttackingLimb.WorldPosition;
Vector2 toTarget = attackWorldPos - (Character.AnimController.SimplePhysicsEnabled ? Character.WorldPosition : AttackingLimb.WorldPosition);
if (wallTarget != null)
{
if (wallTarget.Structure.Submarine != null)
@@ -980,7 +988,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;
@@ -1020,21 +1036,25 @@ namespace Barotrauma
}
else
{
Vector2 offset = Character.SimPosition - steeringLimb.SimPosition;
// Offset so that we don't overshoot the movement
Vector2 steerPos = attackSimPos + offset;
Vector2 steerPos = attackSimPos;
if (!Character.AnimController.SimplePhysicsEnabled)
{
// Offset so that we don't overshoot the movement
Vector2 offset = Character.SimPosition - steeringLimb.SimPosition;
steerPos += offset;
}
if (SteeringManager is IndoorsSteeringManager pathSteering)
{
if (pathSteering.CurrentPath != null)
{
// Attack doors
if (canAttackSub)
if (canAttackDoors)
{
// If the target is in the same hull, there shouldn't be any doors blocking the path
if (targetCharacter == null || targetCharacter.CurrentHull != Character.CurrentHull)
{
var door = pathSteering.CurrentPath.CurrentNode?.ConnectedDoor ?? pathSteering.CurrentPath.NextNode?.ConnectedDoor;
if (door != null && !door.IsOpen)
if (door != null && !door.IsOpen && !door.IsBroken)
{
if (door.Item.AiTarget != null && SelectedAiTarget != door.Item.AiTarget)
{
@@ -1045,15 +1065,17 @@ 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));
Vector2 myPos = Character.AnimController.SimplePhysicsEnabled ? Character.SimPosition : steeringLimb.SimPosition;
SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(steerPos - myPos));
}
else
{
SteeringManager.SteeringSeek(steerPos, 2);
// Switch to Idle when cannot reach the target and if cannot damage the walls
if ((!canAttackSub || wallTarget == null) && !pathSteering.IsPathDirty && pathSteering.CurrentPath.Unreachable)
if ((!canAttackWalls || wallTarget == null) && !pathSteering.IsPathDirty && pathSteering.CurrentPath.Unreachable)
{
State = AIState.Idle;
return;
@@ -1112,6 +1134,8 @@ namespace Barotrauma
return false;
}
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();
@@ -1127,23 +1151,38 @@ namespace Barotrauma
if (attack == null) { continue; }
if (attack.CoolDownTimer > 0) { continue; }
if (!attack.IsValidContext(currentContexts)) { continue; }
if (!attack.IsValidTarget(target)) { continue; }
if (!attack.IsValidTarget(target as IDamageable)) { continue; }
if (target is ISerializableEntity se && target is Character)
{
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;
float CalculatePriority(Limb limb, Vector2 attackPos)
{
if (Character.AnimController.SimplePhysicsEnabled) { return 1 + limb.attack.Priority; }
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.
@@ -1171,7 +1210,7 @@ namespace Barotrauma
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);
@@ -1210,7 +1249,10 @@ namespace Barotrauma
LatchOntoAI?.SetAttachTarget(wall.Submarine.PhysicsBody.FarseerBody, wall.Submarine, ConvertUnits.ToSimUnits(sectionPos), attachTargetNormal);
if (Character.AnimController.CanEnterSubmarine || !wall.SectionBodyDisabled(sectionIndex) && !IsWallDisabled(wall))
{
wallTarget = new WallTarget(sectionPos, wall, sectionIndex);
if (AIParams.TargetOuterWalls || wall.prefab.Tags.Contains("inner"))
{
wallTarget = new WallTarget(sectionPos, wall, sectionIndex);
}
}
}
if (!Character.AnimController.CanEnterSubmarine && wallTarget == null)
@@ -1239,7 +1281,7 @@ namespace Barotrauma
}
return isDisabled;
}
public override void OnAttacked(Character attacker, AttackResult attackResult)
{
float reactionTime = Rand.Range(0.1f, 0.3f);
@@ -1249,22 +1291,28 @@ namespace Barotrauma
Character.AnimController.ReleaseStuckLimbs();
LatchOntoAI?.DeattachFromBody();
if (attacker == null || attacker.AiTarget == null) { return; }
bool isFriendly = IsFriendly(Character, attacker);
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;
bool canAttack = attacker.Submarine == Character.Submarine && canAttackCharacters || attacker.Submarine != null && canAttackWalls;
if (Character.Params.AI.AttackWhenProvoked && canAttack)
{
if (attacker.IsHusk)
@@ -1322,7 +1370,7 @@ namespace Barotrauma
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)
@@ -1367,6 +1415,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)
@@ -1424,7 +1474,7 @@ namespace Barotrauma
State = AIState.Idle;
return;
}
Vector2 mouthPos = Character.AnimController.GetMouthPosition().Value;
Vector2 mouthPos = Character.AnimController.SimplePhysicsEnabled ? SimPosition : Character.AnimController.GetMouthPosition().Value;
Vector2 attackSimPosition = Character.GetRelativeSimPosition(target);
Vector2 limbDiff = attackSimPosition - mouthPos;
float extent = Math.Max(mouthLimb.body.GetMaxExtent(), 2);
@@ -1453,6 +1503,25 @@ namespace Barotrauma
#endregion
private void UpdateFollow(float deltaTime)
{
if (SelectedAiTarget == null || SelectedAiTarget.Entity == null || SelectedAiTarget.Entity.Removed)
{
State = AIState.Idle;
return;
}
Vector2 dir = Vector2.Normalize(SelectedAiTarget.Entity.WorldPosition - Character.WorldPosition);
if (!MathUtils.IsValid(dir))
{
return;
}
steeringManager.SteeringManual(deltaTime, dir);
if (Character.AnimController.InWater)
{
SteeringManager.SteeringAvoid(deltaTime, lookAheadDistance: avoidLookAheadDistance, weight: 15);
}
}
#region Targeting
private bool IsLatchedOnSub => LatchOntoAI != null && LatchOntoAI.IsAttachedToSub;
@@ -1579,13 +1648,16 @@ namespace Barotrauma
Door door = null;
if (aiTarget.Entity is Item item)
{
//item inside and we're outside -> attack the hull
if (item.CurrentHull != null && character.CurrentHull == null)
{
targetingTag = "room";
}
door = item.GetComponent<Door>();
bool targetingFromOutsideToInside = item.CurrentHull != null && character.CurrentHull == null;
if (targetingFromOutsideToInside)
{
if (door != null && !canAttackDoors || !canAttackWalls)
{
// Can't reach
continue;
}
}
foreach (var prio in AIParams.Targets)
{
if (item.HasTag(prio.Tag))
@@ -1594,7 +1666,25 @@ namespace Barotrauma
break;
}
}
if (door == null && targetingTag == null)
{
if (item.GetComponent<Sonar>() != null)
{
targetingTag = "sonar";
}
else if (targetingFromOutsideToInside)
{
targetingTag = "room";
}
}
else if (targetingTag == "nasonov")
{
if ((item.Submarine == null || !item.Submarine.Info.IsPlayer) && item.ParentInventory == null)
{
// Only target nasonovartifacts when they are held be a player or inside the playersub
continue;
}
}
// Ignore the target if it's a decoy and the character is already inside a sub
if (character.CurrentHull != null && targetingTag == "decoy")
{
@@ -1609,15 +1699,13 @@ namespace Barotrauma
// Ignore structures that doesn't have a body (not walls)
continue;
}
if (s.IsPlatform)
if (s.IsPlatform) { continue; }
if (s.Submarine == null) { continue; }
bool isCharacterInside = character.CurrentHull != null;
bool isInnerWall = s.prefab.Tags.Contains("inner");
if (isInnerWall && !isCharacterInside)
{
continue;
}
bool isCharacterOutside = s.Submarine == null || character.CurrentHull == null;
bool targetInnerWalls = AIParams.TargetInnerWalls;
if (!isCharacterOutside && !targetInnerWalls)
{
// Ignore walls when inside (walltargets still work)
// Ignore inner walls when outside (walltargets still work)
continue;
}
valueModifier = 1;
@@ -1630,39 +1718,70 @@ namespace Barotrauma
var section = s.Sections[i];
if (section.gap == null) { continue; }
bool leadsInside = !section.gap.IsRoomToRoom && section.gap.FlowTargetHull != null;
isInnerWall = isInnerWall || !leadsInside;
if (Character.AnimController.CanEnterSubmarine)
{
if (isCharacterOutside)
if (!isCharacterInside)
{
if (CanPassThroughHole(s, i))
{
valueModifier *= leadsInside ? (AggressiveBoarding ? 5 : 1) : (targetInnerWalls ? 1 : 0);
valueModifier *= leadsInside ? (AggressiveBoarding ? 5 : 1) : 0;
}
else if (AggressiveBoarding && leadsInside && canAttackWalls && AIParams.TargetOuterWalls)
{
// Up to 100% priority increase for every gap in the wall when an aggressive boarder is outside
valueModifier *= 1 + section.gap.Open;
}
}
else
{
// Inside
if (AggressiveBoarding)
{
if (!isInnerWall)
{
// Only interested in getting inside (aggressive boarder) -> don't target outer walls when already inside
valueModifier = 0;
break;
}
else if (CanPassThroughHole(s, i))
{
valueModifier *= isInnerWall ? 1 : 0;
}
else if (!canAttackWalls)
{
valueModifier = 0;
break;
}
}
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)
if (!canAttackWalls)
{
continue;
}
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;
valueModifier = 0;
break;
}
// We are actually interested in breaking things -> reduce the priority when the wall is already broken
// (Terminalcells)
valueModifier *= 1 - section.gap.Open * 0.25f;
}
}
else if (!canAttackSub || CanPassThroughHole(s, i))
{
// Already inside -> ignore holes in the walls and ignore walls if cannot attack the sub.
continue;
}
}
else if (!leadsInside || !canAttackSub)
else
{
// Can't get in, ignore inner walls
// Also ignore all walls if cannot attack the sub
continue;
// Cannot enter
if (isInnerWall || !canAttackWalls)
{
// Ignore inner walls and all walls if cannot do damage on walls.
valueModifier = 0;
break;
}
else if (AggressiveBoarding)
{
// Up to 100% priority increase for every gap in the wall when an aggressive boarder is outside
// (Bonethreshers)
valueModifier *= 1 + section.gap.Open;
}
}
}
}
@@ -1679,31 +1798,34 @@ namespace Barotrauma
}
if (door.Item.Submarine == null) { continue;}
bool isOutdoor = door.LinkedGap?.FlowTargetHull != null && !door.LinkedGap.IsRoomToRoom;
bool isOpen = door.IsOpen;
if (!isOpen && (!canAttackSub))
bool isOpen = door.IsOpen || door.IsBroken;
if (!isOpen && !canAttackDoors || (isOutdoor && !AIParams.TargetOuterWalls))
{
// Ignore doors that are not open if cannot attack items/structures. Open doors should be targeted, so that we can get in if we are aggressive boarders
valueModifier = 0;
// Ignore doors that are not open if cannot attack doors or shouldn't target outer doors.
continue;
}
if (character.CurrentHull == null)
if (isOpen && (!Character.AnimController.CanEnterSubmarine || !AggressiveBoarding))
{
valueModifier = isOutdoor ? 1 : 0;
// Ignore broken and open doors
// Aggressive boarders don't ignore open doors, because they use them for get in.
continue;
}
else if (AggressiveBoarding)
if (AggressiveBoarding)
{
// Increase priority if the character is outside and an aggressive boarder, and the door is from outside to inside
if (character.CurrentHull == null)
// Increase the priority if the character is outside and the door is from outside to inside
if (character.CurrentHull == null && isOutdoor)
{
valueModifier *= isOpen ? 5 : 1;
}
else
{
valueModifier *= isOpen ? 0 : 1;
// Inside
valueModifier *= isOpen || isOutdoor ? 0 : 1;
}
}
else if (!Character.AnimController.CanEnterSubmarine && isOpen) //ignore broken and open doors
else if (character.CurrentHull == null)
{
continue;
valueModifier = isOutdoor ? 1 : 0;
}
}
else if (aiTarget.Entity is IDamageable targetDamageable && targetDamageable.Health <= 0.0f)
@@ -1745,9 +1867,18 @@ namespace Barotrauma
if (valueModifier > targetValue)
{
// 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 (aiTarget.Entity is Item i)
{
Character owner = GetOwner(i);
// 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 (owner == character) { continue; }
if (owner != null && IsFriendly(Character, owner))
{
// If the item is held by a friendly character, ignore it.
continue;
}
}
if (targetCharacter != null)
{
if (targetCharacter.Submarine != Character.Submarine)
@@ -1772,7 +1903,7 @@ namespace Barotrauma
foreach (var gap in Character.CurrentHull.ConnectedGaps)
{
var door = gap.ConnectedDoor;
if (door == null || !door.IsOpen)
if (door == null || !door.IsOpen && !door.IsBroken)
{
var wall = gap.ConnectedWall;
if (wall != null)
@@ -1966,11 +2097,17 @@ namespace Barotrauma
{
// If the target is shooting from the submarine, we might not perceive it because it doesn't move.
// --> Target the submarine too.
if (target.Submarine != null && canAttackSub)
if (target.Submarine != null && (canAttackDoors || canAttackWalls))
{
ChangeParams("room", state, priority);
ChangeParams("wall", state, priority);
ChangeParams("door", state, priority);
if (canAttackWalls)
{
ChangeParams("wall", state, priority);
}
if (canAttackDoors)
{
ChangeParams("door", state, priority);
}
}
ChangeParams("provocative", state, priority, onlyExisting: true);
ChangeParams("light", state, priority, onlyExisting: true);
@@ -1991,7 +2128,7 @@ namespace Barotrauma
Character.AnimController.ReleaseStuckLimbs();
escapeTarget = null;
AttackingLimb = null;
escapeMargin = 0;
movementMargin = 0;
allGapsSearched = false;
unreachableGaps.Clear();
if (isStateChanged && to == AIState.Idle && from != to)
@@ -2016,28 +2153,66 @@ namespace Barotrauma
}
}
public void ReevaluateAttacks()
{
canAttackWalls = LatchOntoAI != null && LatchOntoAI.AttachToSub;
canAttackDoors = false;
canAttackCharacters = false;
foreach (var limb in Character.AnimController.Limbs)
{
if (limb.IsSevered) { continue; }
if (limb.attack == null) { continue; }
if (!canAttackWalls)
{
canAttackWalls = limb.attack.IsValidTarget(AttackTarget.Structure) && limb.attack.StructureDamage > 0;
}
if (!canAttackDoors)
{
canAttackDoors = limb.attack.IsValidTarget(AttackTarget.Structure) && limb.attack.ItemDamage > 0;
}
if (!canAttackCharacters)
{
canAttackCharacters = limb.attack.IsValidTarget(AttackTarget.Character);
}
}
if (PathSteering != null)
{
PathSteering.CanBreakDoors = canAttackDoors;
}
}
private Vector2 returnDir;
private float returnTimer;
private void SteerInsideLevel(float deltaTime)
{
if (Level.Loaded == null) { return; }
Vector2 levelSimSize = new Vector2(
ConvertUnits.ToSimUnits(Level.Loaded.Size.X),
ConvertUnits.ToSimUnits(Level.Loaded.Size.Y));
float margin = 10.0f;
if (SimPosition.Y < 0.0)
if (SteeringManager is IndoorsSteeringManager) { return; }
if (Level.Loaded == null) { return; }
Vector2 levelSimSize = ConvertUnits.ToSimUnits(Level.Loaded.Size.X, Level.Loaded.Size.Y);
float returnTime = 3;
if (SimPosition.Y < 0)
{
steeringManager.SteeringManual(deltaTime, Vector2.UnitY * MathUtils.InverseLerp(0.0f, -margin, SimPosition.Y));
// Too far down
returnTimer = returnTime * Rand.Range(0.75f, 1.25f);
returnDir = Vector2.UnitY;
}
if (SimPosition.X < 0.0f)
if (SimPosition.X < 0)
{
steeringManager.SteeringManual(deltaTime, Vector2.UnitX * MathUtils.InverseLerp(0.0f, -margin, SimPosition.X));
// Too far left
returnTimer = returnTime * Rand.Range(0.75f, 1.25f);
returnDir = Vector2.UnitX;
}
if (SimPosition.X > levelSimSize.X)
{
steeringManager.SteeringManual(deltaTime, Vector2.UnitX * MathUtils.InverseLerp(levelSimSize.X, levelSimSize.X + margin, SimPosition.X));
}
// Too far right
returnTimer = returnTime * Rand.Range(0.75f, 1.25f);
returnDir = -Vector2.UnitX;
}
if (returnTimer > 0)
{
returnTimer -= deltaTime;
SteeringManager.Reset();
SteeringManager.SteeringManual(deltaTime, returnDir);
}
}
private bool CanPassThroughHole(Structure wall, int sectionIndex)
@@ -2068,7 +2243,6 @@ namespace Barotrauma
targetLimbs.Clear();
foreach (var limb in target.AnimController.Limbs)
{
if (limb.IsSevered) { continue; }
if (limb.type == targetLimbType || targetLimbType == LimbType.None)
{
targetLimbs.Add(limb);
@@ -2083,6 +2257,7 @@ namespace Barotrauma
Limb targetLimb = null;
foreach (Limb limb in targetLimbs)
{
if (limb.IsSevered) { continue; }
float dist = Vector2.DistanceSquared(limb.WorldPosition, attackLimb.WorldPosition) / Math.Max(limb.AttackPriority, 0.1f);
if (dist < closestDist)
{
@@ -2092,6 +2267,27 @@ namespace Barotrauma
}
return targetLimb;
}
private Character GetOwner(Item item)
{
// If the item is held by a character, attack the character instead.
var pickable = item.GetComponent<Pickable>();
if (pickable != null)
{
Character owner = pickable.Picker ?? item.FindParentInventory(i => i.Owner is Character)?.Owner as Character;
if (owner != null)
{
var target = owner.AiTarget;
if (target?.Entity != null && !target.Entity.Removed)
{
return owner;
}
}
}
return null;
}
public static bool IsFriendly(Character me, Character other) => other.SpeciesName == me.SpeciesName || other.Params.CompareGroup(me.Params.Group);
}
//the "memory" of the Character