v0.14.6.0

This commit is contained in:
Joonas Rikkonen
2021-06-17 17:54:52 +03:00
parent 3f324b14e8
commit c27e2ea5ab
348 changed files with 13156 additions and 4266 deletions
@@ -16,6 +16,13 @@ namespace Barotrauma
public enum CirclePhase { Start, CloseIn, FallBack, Advance, Strike }
public enum WallTargetingMethod
{
Target = 0x1,
Heading = 0x2,
Steering = 0x4
}
partial class EnemyAIController : AIController
{
public static bool DisableEnemyAI;
@@ -54,23 +61,27 @@ namespace Barotrauma
private float attackLimbResetTimer;
private bool IsAttackRunning => AttackingLimb != null && AttackingLimb.attack.IsRunning;
private bool IsCoolDownRunning => AttackingLimb != null && AttackingLimb.attack.CoolDownTimer > 0;
private bool IsCoolDownRunning => AttackingLimb != null && AttackingLimb.attack.CoolDownTimer > 0 || _previousAttackingLimb != null && _previousAttackingLimb.attack.CoolDownTimer > 0;
public float CombatStrength => AIParams.CombatStrength;
private float Sight => AIParams.Sight;
private float Hearing => AIParams.Hearing;
private float FleeHealthThreshold => AIParams.FleeHealthThreshold;
private bool AggressiveBoarding => AIParams.AggressiveBoarding;
private bool IsAggressiveBoarder => AIParams.AggressiveBoarding;
private FishAnimController FishAnimController => Character.AnimController as FishAnimController;
//the limb selected for the current attack
private Limb _attackingLimb;
private Limb _previousAttackingLimb;
public Limb AttackingLimb
{
get { return _attackingLimb; }
private set
{
attackLimbResetTimer = 0;
if (_attackingLimb != value)
{
_previousAttackingLimb = _attackingLimb;
}
_attackingLimb = value;
attackVector = null;
Reverse = _attackingLimb != null && _attackingLimb.attack.Reverse;
@@ -342,6 +353,10 @@ namespace Barotrauma
{
targetingTag = "weaker";
}
else
{
targetingTag = "equal";
}
}
}
}
@@ -381,6 +396,7 @@ namespace Barotrauma
SelectedAiTarget = target;
selectedTargetMemory = GetTargetMemory(target, true);
selectedTargetMemory.Priority = priority;
ignoredTargets.Remove(target);
}
private float movementMargin;
@@ -479,10 +495,6 @@ namespace Barotrauma
{
CharacterParams.TargetParams targetingParams = null;
UpdateTargets(Character, out targetingParams);
if (!IsLatchedOnSub)
{
UpdateWallTarget(requiredHoleCount);
}
updateTargetsTimer = updateTargetsInterval * Rand.Range(0.75f, 1.25f);
if (SelectedAiTarget == null)
{
@@ -493,10 +505,14 @@ namespace Barotrauma
selectedTargetingParams = targetingParams;
State = targetingParams.State;
}
if (SelectedAiTarget?.Entity != null && !IsLatchedOnSub && State == AIState.Attack || State == AIState.Aggressive || State == AIState.PassiveAggressive)
{
UpdateWallTarget(requiredHoleCount);
}
}
}
if (AIParams.Infiltrate)
if (AIParams.CanOpenDoors)
{
bool IsCloseEnoughToTargetSub(float threshold) => SelectedAiTarget?.Entity?.Submarine is Submarine sub && sub != null && Vector2.DistanceSquared(Character.WorldPosition, sub.WorldPosition) < MathUtils.Pow(Math.Max(sub.Borders.Size.X, sub.Borders.Size.Y) / 2 + threshold, 2);
@@ -787,7 +803,7 @@ namespace Barotrauma
if (pathSteering != null && !Character.AnimController.InWater)
{
// Wander around inside
pathSteering.Wander(deltaTime, ConvertUnits.ToDisplayUnits(colliderLength), stayStillInTightSpace: false);
pathSteering.Wander(deltaTime, Math.Max(ConvertUnits.ToDisplayUnits(colliderLength), 100.0f), stayStillInTightSpace: false);
}
else
{
@@ -1003,12 +1019,8 @@ namespace Barotrauma
if (!w.SectionBodyDisabled(i))
{
isBroken = false;
Vector2 sectionPos = w.SectionPosition(i);
Vector2 sectionPos = w.SectionPosition(i, world: true);
attackWorldPos = sectionPos;
if (w.Submarine != null)
{
attackWorldPos += w.Submarine.Position;
}
attackSimPos = ConvertUnits.ToSimUnits(attackWorldPos);
break;
}
@@ -1026,18 +1038,19 @@ namespace Barotrauma
bool pursue = false;
if (IsCoolDownRunning)
{
if (AttackingLimb.attack.CoolDownTimer >= AttackingLimb.attack.CoolDown + AttackingLimb.attack.CurrentRandomCoolDown - AttackingLimb.attack.AfterAttackDelay)
var currentAttackLimb = AttackingLimb ?? _previousAttackingLimb;
if (currentAttackLimb.attack.CoolDownTimer >= currentAttackLimb.attack.CoolDown + currentAttackLimb.attack.CurrentRandomCoolDown - currentAttackLimb.attack.AfterAttackDelay)
{
return;
}
switch (AttackingLimb.attack.AfterAttack)
switch (currentAttackLimb.attack.AfterAttack)
{
case AIBehaviorAfterAttack.Pursue:
case AIBehaviorAfterAttack.PursueIfCanAttack:
if (AttackingLimb.attack.SecondaryCoolDown <= 0)
if (currentAttackLimb.attack.SecondaryCoolDown <= 0)
{
// No (valid) secondary cooldown defined.
if (AttackingLimb.attack.AfterAttack == AIBehaviorAfterAttack.Pursue)
if (currentAttackLimb.attack.AfterAttack == AIBehaviorAfterAttack.Pursue)
{
canAttack = false;
pursue = true;
@@ -1050,13 +1063,13 @@ namespace Barotrauma
}
else
{
if (AttackingLimb.attack.SecondaryCoolDownTimer <= 0)
if (currentAttackLimb.attack.SecondaryCoolDownTimer <= 0)
{
// Don't allow attacking when the attack target has just changed.
if (_previousAiTarget != null && SelectedAiTarget != _previousAiTarget)
{
canAttack = false;
if (AttackingLimb.attack.AfterAttack == AIBehaviorAfterAttack.PursueIfCanAttack)
if (currentAttackLimb.attack.AfterAttack == AIBehaviorAfterAttack.PursueIfCanAttack)
{
// Fall back if cannot attack.
UpdateFallBack(attackWorldPos, deltaTime, true);
@@ -1067,7 +1080,7 @@ namespace Barotrauma
else
{
// If the secondary cooldown is defined and expired, check if we can switch the attack
var newLimb = GetAttackLimb(attackWorldPos, AttackingLimb);
var newLimb = GetAttackLimb(attackWorldPos, currentAttackLimb);
if (newLimb != null)
{
// Attack with the new limb
@@ -1076,7 +1089,7 @@ namespace Barotrauma
else
{
// No new limb was found.
if (AttackingLimb.attack.AfterAttack == AIBehaviorAfterAttack.Pursue)
if (currentAttackLimb.attack.AfterAttack == AIBehaviorAfterAttack.Pursue)
{
canAttack = false;
pursue = true;
@@ -1098,26 +1111,26 @@ namespace Barotrauma
break;
case AIBehaviorAfterAttack.FallBackUntilCanAttack:
case AIBehaviorAfterAttack.FollowThroughUntilCanAttack:
if (AttackingLimb.attack.SecondaryCoolDown <= 0)
if (currentAttackLimb.attack.SecondaryCoolDown <= 0)
{
// No (valid) secondary cooldown defined.
UpdateFallBack(attackWorldPos, deltaTime, AttackingLimb.attack.AfterAttack == AIBehaviorAfterAttack.FollowThroughUntilCanAttack);
UpdateFallBack(attackWorldPos, deltaTime, currentAttackLimb.attack.AfterAttack == AIBehaviorAfterAttack.FollowThroughUntilCanAttack);
return;
}
else
{
if (AttackingLimb.attack.SecondaryCoolDownTimer <= 0)
if (currentAttackLimb.attack.SecondaryCoolDownTimer <= 0)
{
// Don't allow attacking when the attack target has just changed.
if (_previousAiTarget != null && SelectedAiTarget != _previousAiTarget)
{
UpdateFallBack(attackWorldPos, deltaTime, AttackingLimb.attack.AfterAttack == AIBehaviorAfterAttack.FollowThroughUntilCanAttack);
UpdateFallBack(attackWorldPos, deltaTime, currentAttackLimb.attack.AfterAttack == AIBehaviorAfterAttack.FollowThroughUntilCanAttack);
return;
}
else
{
// If the secondary cooldown is defined and expired, check if we can switch the attack
var newLimb = GetAttackLimb(attackWorldPos, AttackingLimb);
var newLimb = GetAttackLimb(attackWorldPos, currentAttackLimb);
if (newLimb != null)
{
// Attack with the new limb
@@ -1126,7 +1139,7 @@ namespace Barotrauma
else
{
// No new limb was found.
UpdateFallBack(attackWorldPos, deltaTime, AttackingLimb.attack.AfterAttack == AIBehaviorAfterAttack.FollowThroughUntilCanAttack);
UpdateFallBack(attackWorldPos, deltaTime, currentAttackLimb.attack.AfterAttack == AIBehaviorAfterAttack.FollowThroughUntilCanAttack);
return;
}
}
@@ -1134,13 +1147,13 @@ namespace Barotrauma
else
{
// Cooldown not yet expired -> steer away from the target
UpdateFallBack(attackWorldPos, deltaTime, AttackingLimb.attack.AfterAttack == AIBehaviorAfterAttack.FollowThroughUntilCanAttack);
UpdateFallBack(attackWorldPos, deltaTime, currentAttackLimb.attack.AfterAttack == AIBehaviorAfterAttack.FollowThroughUntilCanAttack);
return;
}
}
break;
case AIBehaviorAfterAttack.IdleUntilCanAttack:
if (AttackingLimb.attack.SecondaryCoolDown <= 0)
if (currentAttackLimb.attack.SecondaryCoolDown <= 0)
{
// No (valid) secondary cooldown defined.
UpdateIdle(deltaTime, followLastTarget: false);
@@ -1148,7 +1161,7 @@ namespace Barotrauma
}
else
{
if (AttackingLimb.attack.SecondaryCoolDownTimer <= 0)
if (currentAttackLimb.attack.SecondaryCoolDownTimer <= 0)
{
// Don't allow attacking when the attack target has just changed.
if (_previousAiTarget != null && SelectedAiTarget != _previousAiTarget)
@@ -1159,7 +1172,7 @@ namespace Barotrauma
else
{
// If the secondary cooldown is defined and expired, check if we can switch the attack
var newLimb = GetAttackLimb(attackWorldPos, AttackingLimb);
var newLimb = GetAttackLimb(attackWorldPos, currentAttackLimb);
if (newLimb != null)
{
// Attack with the new limb
@@ -1203,7 +1216,7 @@ namespace Barotrauma
}
canAttack = AttackingLimb != null && AttackingLimb.attack.CoolDownTimer <= 0;
}
if (!AIParams.Infiltrate)
if (!AIParams.CanOpenDoors)
{
if (!Character.AnimController.SimplePhysicsEnabled && SelectedAiTarget.Entity.Submarine != null && Character.Submarine == null && (!canAttackDoors || !canAttackWalls || !AIParams.TargetOuterWalls))
{
@@ -1257,8 +1270,8 @@ namespace Barotrauma
Vector2 attackLimbPos = Character.AnimController.SimplePhysicsEnabled ? Character.WorldPosition : AttackingLimb.WorldPosition;
Vector2 toTarget = attackWorldPos - attackLimbPos;
// Add a margin when the target is moving away, because otherwise it might be difficult to reach it (the attack takes some time to perform)
if (wallTarget != null)
// Add a margin when the target is moving away, because otherwise it might be difficult to reach it if the attack takes some time to execute
if (wallTarget != null && Character.Submarine == null)
{
if (wallTarget.Structure.Submarine != null)
{
@@ -1282,9 +1295,14 @@ namespace Barotrauma
Vector2 CalculateMargin(Vector2 targetVelocity)
{
if (targetVelocity == Vector2.Zero) { return targetVelocity; }
if (targetVelocity == Vector2.Zero) { return Vector2.Zero; }
float diff = AttackingLimb.attack.Range - AttackingLimb.attack.DamageRange;
if (diff <= 0 || toTarget.LengthSquared() <= MathUtils.Pow2(AttackingLimb.attack.DamageRange)) { return Vector2.Zero; }
float dot = Vector2.Dot(Vector2.Normalize(targetVelocity), Vector2.Normalize(Character.AnimController.Collider.LinearVelocity));
return ConvertUnits.ToDisplayUnits(targetVelocity) * AttackingLimb.attack.Duration * dot;
if (dot <= 0 || !MathUtils.IsValid(dot)) { return Vector2.Zero; }
float distanceOffset = diff * AttackingLimb.attack.Duration;
// Intentionally omit the unit conversion because we use distanceOffset as a multiplier.
return targetVelocity * distanceOffset * dot;
}
// Check that we can reach the target
@@ -1422,10 +1440,11 @@ namespace Barotrauma
else
{
pathSteering.SteeringSeek(steerPos, 2, startNodeFilter: n => (n.Waypoint.CurrentHull == null) == (Character.CurrentHull == null), checkVisiblity: true);
// Switch to Idle when cannot reach the target and if cannot damage the walls
if ((!canAttackWalls || wallTarget == null) && !pathSteering.IsPathDirty && pathSteering.CurrentPath.Unreachable)
if (!pathSteering.IsPathDirty && pathSteering.CurrentPath.Unreachable)
{
State = AIState.Idle;
IgnoreTarget(SelectedAiTarget);
ResetAITarget();
return;
}
}
@@ -1648,7 +1667,7 @@ namespace Barotrauma
float GetTargetMaxSpeed() => Character.ApplyTemporarySpeedLimits(Character.AnimController.CurrentSwimParams.MovementSpeed * 0.3f);
}
SteeringManager.SteeringSeek(steerPos, 10);
if (SelectedAiTarget?.Entity is Character || distance == 0 || distance > ConvertUnits.ToDisplayUnits(avoidLookAheadDistance * 2))
if (SelectedAiTarget?.Entity is Character c && c.Submarine == null || distance == 0 || distance > ConvertUnits.ToDisplayUnits(avoidLookAheadDistance * 2))
{
SteeringManager.SteeringAvoid(deltaTime, lookAheadDistance: avoidLookAheadDistance, weight: 30);
}
@@ -1656,16 +1675,6 @@ namespace Barotrauma
}
if (canAttack)
{
if (SelectedAiTarget.Entity is Item targetItem)
{
var door = targetItem.GetComponent<Door>();
if (door != null && door.CanBeTraversed)
{
ResetAITarget();
State = PreviousState;
return;
}
}
if (!UpdateLimbAttack(deltaTime, AttackingLimb, attackSimPos, distance, attackTargetLimb))
{
IgnoreTarget(SelectedAiTarget);
@@ -1777,6 +1786,7 @@ namespace Barotrauma
}
if (!isFriendly && attackResult.Damage > 0.0f)
{
ignoredTargets.Remove(attacker.AiTarget);
bool canAttack = attacker.Submarine == Character.Submarine && canAttackCharacters || attacker.Submarine != null && canAttackWalls;
if (AIParams.AttackWhenProvoked && canAttack)
{
@@ -1811,9 +1821,8 @@ namespace Barotrauma
ChangeTargetState(attacker, canAttack ? AIState.Attack : AIState.Escape, 100);
}
}
else
else if (!AIParams.HasTag("equal"))
{
// Equal strength
ChangeTargetState(attacker, canAttack ? AIState.Attack : AIState.Escape, 100);
}
}
@@ -1893,16 +1902,28 @@ namespace Barotrauma
{
//simulate attack input to get the character to attack client-side
Character.SetInput(InputType.Attack, true, true);
#if SERVER
GameMain.NetworkMember.CreateEntityEvent(Character, new object[]
{
Networking.NetEntityEvent.Type.SetAttackTarget,
attackingLimb,
(damageTarget as Entity)?.ID ?? Entity.NullEntityID,
damageTarget is Character character && targetLimb != null ? Array.IndexOf(character.AnimController.Limbs, targetLimb) : 0,
SimPosition.X,
SimPosition.Y
});
#endif
if (attackingLimb.UpdateAttack(deltaTime, attackSimPos, damageTarget, out AttackResult attackResult, distance, targetLimb))
{
if (damageTarget.Health > 0)
if (damageTarget.Health > 0 && attackResult.Damage > 0)
{
// Managed to hit a living/non-destroyed target. Increase the priority more if the target is low in health -> dies easily/soon
selectedTargetMemory.Priority += GetRelativeDamage(attackResult.Damage, damageTarget.Health) * AIParams.AggressionGreed;
}
else
{
selectedTargetMemory.Priority = 0;
selectedTargetMemory.Priority -= Math.Max(selectedTargetMemory.Priority / 2, 1);
return selectedTargetMemory.Priority > 1;
}
}
return true;
@@ -2138,6 +2159,10 @@ namespace Barotrauma
{
targetingTag = "weaker";
}
else
{
targetingTag = "equal";
}
if (targetingTag == "stronger" && (State == AIState.Avoid || State == AIState.Escape || State == AIState.Flee))
{
if (SelectedAiTarget == aiTarget)
@@ -2184,7 +2209,7 @@ namespace Barotrauma
bool targetingFromOutsideToInside = item.CurrentHull != null && character.CurrentHull == null;
if (targetingFromOutsideToInside)
{
if (door != null && (!canAttackDoors && !AIParams.Infiltrate) || !canAttackWalls)
if (door != null && (!canAttackDoors && !AIParams.CanOpenDoors) || !canAttackWalls)
{
// Can't reach
continue;
@@ -2258,25 +2283,24 @@ 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 (!isCharacterInside)
{
if (CanPassThroughHole(s, i))
{
valueModifier *= leadsInside ? (AggressiveBoarding ? 5 : 1) : 0;
valueModifier *= leadsInside ? (IsAggressiveBoarder ? 3 : 1) : 0;
}
else if (AggressiveBoarding && leadsInside && canAttackWalls && AIParams.TargetOuterWalls)
else if (IsAggressiveBoarder && 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;
// Up to 25% priority increase for every gap in the wall when an aggressive boarder is outside
valueModifier *= 1 + section.gap.Open * 0.25f;
}
}
else
{
// Inside
if (AggressiveBoarding)
if (IsAggressiveBoarder)
{
if (!isInnerWall)
{
@@ -2293,6 +2317,10 @@ namespace Barotrauma
valueModifier = 0;
break;
}
else
{
valueModifier = 0.1f;
}
}
else
{
@@ -2316,7 +2344,7 @@ namespace Barotrauma
valueModifier = 0;
break;
}
else if (AggressiveBoarding)
else if (IsAggressiveBoarder)
{
// Up to 100% priority increase for every gap in the wall when an aggressive boarder is outside
// (Bonethreshers)
@@ -2350,17 +2378,24 @@ namespace Barotrauma
// Ignore broken and open doors, if cannot enter submarine
continue;
}
if (AggressiveBoarding)
if (IsAggressiveBoarder)
{
// Increase the priority if the character is outside and the door is from outside to inside
if (character.CurrentHull == null)
{
valueModifier *= isOpen ? 5 : 1;
// Increase the priority if the character is outside and the door is from outside to inside
if (door.CanBeTraversed)
{
valueModifier = 3;
}
else if (door.LinkedGap != null)
{
valueModifier = 1 + door.LinkedGap.Open;
}
}
else
{
// Inside -> ignore open doors and outer doors
valueModifier *= isOpen || isOutdoor ? 0 : 1;
valueModifier = isOpen || isOutdoor ? 0 : 1;
}
}
}
@@ -2605,91 +2640,165 @@ namespace Barotrauma
}
private WallTarget wallTarget;
private readonly List<(Body, int, Vector2)> wallHits = new List<(Body, int, Vector2)>(3);
private void UpdateWallTarget(int requiredHoleCount)
{
wallTarget = null;
if (State == AIState.Flee || State == AIState.Escape) { return; }
if (AIParams.Infiltrate && HasValidPath(requireNonDirty: true)) { return; }
if (SelectedAiTarget == null) { return; }
if (SelectedAiTarget.Entity == null) { return; }
Vector2 rayStart = SimPosition;
Vector2 rayEnd = SelectedAiTarget.SimPosition;
if (SelectedAiTarget.Entity.Submarine != null && Character.Submarine == null)
if (SelectedAiTarget.Entity == null) { return; }
if (HasValidPath(requireNonDirty: true)) { return; }
wallHits.Clear();
Structure wall = null;
if (AIParams.WallTargetingMethod.HasFlag(WallTargetingMethod.Target))
{
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 && (Character.IsBot || wall.Submarine.Info.IsPlayer || wall.Submarine.Info.IsOutpost && TargetOutposts))
Vector2 rayStart = SimPosition;
Vector2 rayEnd = SelectedAiTarget.SimPosition;
if (SelectedAiTarget.Entity.Submarine != null && Character.Submarine == null)
{
int sectionIndex = wall.FindSectionIndex(ConvertUnits.ToDisplayUnits(Submarine.LastPickedPosition));
float sectionDamage = wall.SectionDamage(sectionIndex);
for (int i = sectionIndex - 2; i <= sectionIndex + 2; i++)
rayStart -= SelectedAiTarget.Entity.Submarine.SimPosition;
}
else if (SelectedAiTarget.Entity.Submarine == null && Character.Submarine != null)
{
rayEnd -= Character.Submarine.SimPosition;
}
DoRayCast(rayStart, rayEnd);
}
if (AIParams.WallTargetingMethod.HasFlag(WallTargetingMethod.Heading))
{
Vector2 rayStart = SimPosition;
Vector2 rayEnd = rayStart + VectorExtensions.Forward(Character.AnimController.Collider.Rotation + MathHelper.PiOver2, avoidLookAheadDistance * 5);
if (SelectedAiTarget.Entity.Submarine != null && Character.Submarine == null)
{
rayStart -= SelectedAiTarget.Entity.Submarine.SimPosition;
rayEnd -= SelectedAiTarget.Entity.Submarine.SimPosition;
}
else if (SelectedAiTarget.Entity.Submarine == null && Character.Submarine != null)
{
rayStart -= Character.Submarine.SimPosition;
rayEnd -= Character.Submarine.SimPosition;
}
DoRayCast(rayStart, rayEnd);
}
if (AIParams.WallTargetingMethod.HasFlag(WallTargetingMethod.Steering))
{
Vector2 rayStart = SimPosition;
Vector2 rayEnd = rayStart + Steering * 5;
if (SelectedAiTarget.Entity.Submarine != null && Character.Submarine == null)
{
rayStart -= SelectedAiTarget.Entity.Submarine.SimPosition;
rayEnd -= SelectedAiTarget.Entity.Submarine.SimPosition;
}
else if (SelectedAiTarget.Entity.Submarine == null && Character.Submarine != null)
{
rayStart -= Character.Submarine.SimPosition;
rayEnd -= Character.Submarine.SimPosition;
}
DoRayCast(rayStart, rayEnd);
}
if (wallHits.Any())
{
Body closestBody = null;
float closestDistance = 0;
int sectionIndex = -1;
Vector2 sectionPos = Vector2.Zero;
foreach ((Body body, int index, Vector2 sectionPosition) in wallHits)
{
float distance = Vector2.DistanceSquared(SimPosition, sectionPosition);
if (closestBody == null || closestDistance == 0 || distance < closestDistance)
{
if (wall.SectionBodyDisabled(i))
{
if (Character.AnimController.CanEnterSubmarine && CanPassThroughHole(wall, i, requiredHoleCount))
{
sectionIndex = i;
break;
}
else
{
// Ignore and keep breaking other sections
continue;
}
}
if (wall.SectionDamage(i) > sectionDamage)
{
sectionIndex = i;
}
closestBody = body;
closestDistance = distance;
wall = closestBody.UserData as Structure;
sectionPos = sectionPosition;
sectionIndex = index;
}
Vector2 sectionPos = wall.SectionPosition(sectionIndex);
Vector2 attachTargetNormal;
if (wall.IsHorizontal)
}
if (closestBody == null || sectionIndex == -1) { return; }
Vector2 attachTargetNormal;
if (wall.IsHorizontal)
{
attachTargetNormal = new Vector2(0.0f, Math.Sign(WorldPosition.Y - wall.WorldPosition.Y));
sectionPos.Y += (wall.BodyHeight <= 0.0f ? wall.Rect.Height : wall.BodyHeight) / 2 * attachTargetNormal.Y;
}
else
{
attachTargetNormal = new Vector2(Math.Sign(WorldPosition.X - wall.WorldPosition.X), 0.0f);
sectionPos.X += (wall.BodyWidth <= 0.0f ? wall.Rect.Width : wall.BodyWidth) / 2 * attachTargetNormal.X;
}
LatchOntoAI?.SetAttachTarget(wall, ConvertUnits.ToSimUnits(sectionPos), attachTargetNormal);
if (Character.AnimController.CanEnterSubmarine || !wall.SectionBodyDisabled(sectionIndex) && !IsWallDisabled(wall))
{
if (wall.NoAITarget && Character.AnimController.CanEnterSubmarine)
{
attachTargetNormal = new Vector2(0.0f, Math.Sign(WorldPosition.Y - wall.WorldPosition.Y));
sectionPos.Y += (wall.BodyHeight <= 0.0f ? wall.Rect.Height : wall.BodyHeight) / 2 * attachTargetNormal.Y;
bool isTargetingDoor = SelectedAiTarget.Entity is Item i && i.GetComponent<Door>() != null;
// Blocked by a wall that shouldn't be targeted. The main intention here is to prevent monsters from entering the the tail and the nose pieces.
if (!isTargetingDoor)
{
ResetAITarget();
}
}
else
{
attachTargetNormal = new Vector2(Math.Sign(WorldPosition.X - wall.WorldPosition.X), 0.0f);
sectionPos.X += (wall.BodyWidth <= 0.0f ? wall.Rect.Width : wall.BodyWidth) / 2 * attachTargetNormal.X;
wallTarget = new WallTarget(sectionPos, wall, sectionIndex);
}
LatchOntoAI?.SetAttachTarget(wall, ConvertUnits.ToSimUnits(sectionPos), attachTargetNormal);
if (Character.AnimController.CanEnterSubmarine || !wall.SectionBodyDisabled(sectionIndex) && !IsWallDisabled(wall))
}
else
{
// Blocked by a disabled wall.
ResetAITarget();
}
}
void DoRayCast(Vector2 rayStart, Vector2 rayEnd)
{
Body hitTarget = Submarine.CheckVisibility(rayStart, rayEnd, ignoreSubs: true, ignoreSensors: CanEnterSubmarine, ignoreDisabledWalls: CanEnterSubmarine);
if (hitTarget != null && IsValid(hitTarget, out wall))
{
int sectionIndex = wall.FindSectionIndex(ConvertUnits.ToDisplayUnits(Submarine.LastPickedPosition));
if (sectionIndex >= 0)
{
if (AIParams.TargetOuterWalls || wall.prefab.Tags.Contains("inner") || wall.Submarine != null && wall.Submarine == Character.Submarine)
wallHits.Add((hitTarget, sectionIndex, GetSectionPosition(wall, sectionIndex)));
}
}
}
Vector2 GetSectionPosition(Structure wall, int sectionIndex)
{
float sectionDamage = wall.SectionDamage(sectionIndex);
for (int i = sectionIndex - 2; i <= sectionIndex + 2; i++)
{
if (wall.SectionBodyDisabled(i))
{
if (Character.AnimController.CanEnterSubmarine && CanPassThroughHole(wall, i, requiredHoleCount))
{
if (wall.NoAITarget && Character.AnimController.CanEnterSubmarine)
{
// Blocked by a wall that shouldn't be targeted. The main intention here is to prevents monsters from entering the the tail and the nose pieces.
IgnoreTarget(SelectedAiTarget);
ResetAITarget();
}
else
{
wallTarget = new WallTarget(sectionPos, wall, sectionIndex);
}
sectionIndex = i;
break;
}
else
{
// Ignore and keep breaking other sections
continue;
}
}
}
if (!Character.AnimController.CanEnterSubmarine && wallTarget == null && selectedTargetingParams?.AttackPattern == AttackPattern.Straight)
{
if (closestBody.UserData is Structure w && w.Submarine != null && w.Submarine == SelectedAiTarget.Entity?.Submarine ||
closestBody.UserData is Item i && i.Submarine != null && i.Submarine == SelectedAiTarget.Entity?.Submarine)
if (wall.SectionDamage(i) > sectionDamage)
{
// Cannot reach the target, because it's blocked by a disabled wall or a door
IgnoreTarget(SelectedAiTarget);
ResetAITarget();
sectionIndex = i;
}
}
return wall.SectionPosition(sectionIndex, world: false);
}
bool IsValid(Body hit, out Structure wall)
{
wall = null;
if (Submarine.LastPickedFraction == 1.0f) { return false; }
if (!(hit.UserData is Structure w)) { return false; }
if (w.Submarine == null) { return false; }
if (w.Submarine != SelectedAiTarget.Entity.Submarine) { return false; }
if (Character.Submarine == null && w.prefab.Tags.Contains("inner")) { return false; }
if (!AIParams.TargetOuterWalls && !w.prefab.Tags.Contains("inner")) { return false; }
wall = w;
return true;
}
}
@@ -2698,7 +2807,7 @@ namespace Barotrauma
if (wallTarget != null && wallTarget.SectionIndex > -1 && CanPassThroughHole(wallTarget.Structure, wallTarget.SectionIndex, requiredHoleCount))
{
WallSection section = wallTarget.Structure.GetSection(wallTarget.SectionIndex);
Vector2 targetPos = wallTarget.Structure.SectionPosition(wallTarget.SectionIndex, true);
Vector2 targetPos = wallTarget.Structure.SectionPosition(wallTarget.SectionIndex, world: true);
return section?.gap != null && SteerThroughGap(wallTarget.Structure, section, targetPos, deltaTime);
}
else if (SelectedAiTarget != null)