Build 1.1.4.0

This commit is contained in:
Markus Isberg
2023-03-31 18:40:44 +03:00
parent efba17e0ff
commit 9470edead3
483 changed files with 17487 additions and 8548 deletions
@@ -245,11 +245,6 @@ namespace Barotrauma
{
throw new Exception($"Tried to create an enemy ai controller for human!");
}
if (Character.Params.Group == "human")
{
// Pet
Character.TeamID = CharacterTeamType.FriendlyNPC;
}
var mainElement = c.Params.OriginalElement.IsOverride() ? c.Params.OriginalElement.FirstElement() : c.Params.OriginalElement;
targetMemories = new Dictionary<AITarget, AITargetMemory>();
steeringManager = outsideSteering;
@@ -309,17 +304,20 @@ namespace Barotrauma
break;
}
}
//pets are friendly!
if (PetBehavior != null || Character.Group == "human")
{
Character.TeamID = CharacterTeamType.FriendlyNPC;
}
ReevaluateAttacks();
outsideSteering = new SteeringManager(this);
insideSteering = new IndoorsSteeringManager(this, Character.Params.AI.CanOpenDoors, canAttackDoors);
steeringManager = outsideSteering;
State = AIState.Idle;
requiredHoleCount = (int)Math.Ceiling(ConvertUnits.ToDisplayUnits(colliderWidth) / Structure.WallSectionSize);
myBodies = Character.AnimController.Limbs.Select(l => l.body.FarseerBody).ToList();
myBodies.Add(Character.AnimController.Collider.FarseerBody);
CreatureMetrics.UnlockInEditor(Character.SpeciesName);
}
private CharacterParams.AIParams _aiParams;
@@ -452,6 +450,7 @@ namespace Barotrauma
base.Update(deltaTime);
UpdateTriggers(deltaTime);
Character.ClearInputs();
IsTryingToSteerThroughGap = false;
Reverse = false;
bool ignorePlatforms = Character.AnimController.TargetMovement.Y < -0.5f && (-Character.AnimController.TargetMovement.Y > Math.Abs(Character.AnimController.TargetMovement.X));
@@ -558,8 +557,9 @@ namespace Barotrauma
}
}
if (AIParams.CanOpenDoors)
if (Character.Params.UsePathFinding && Character.Params.AI.UsePathFindingToGetInside && AIParams.CanOpenDoors)
{
// Meant for monsters outside the player sub that target something inside the sub and can use the doors to access the sub (Husk).
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);
if (Character.Submarine != null || HasValidPath() && IsCloseEnoughToTargetSub(maxSteeringBuffer) || IsCloseEnoughToTargetSub(steeringBuffer))
@@ -584,6 +584,7 @@ namespace Barotrauma
}
else
{
// Normally the monsters only use pathing inside submarines, not outside.
if (Character.Submarine != null && Character.Params.UsePathFinding)
{
if (steeringManager != insideSteering)
@@ -848,7 +849,7 @@ namespace Barotrauma
IsSteeringThroughGap = false;
if (SwarmBehavior != null)
{
SwarmBehavior.IsActive = State == AIState.Idle && Character.CurrentHull == null;
SwarmBehavior.IsActive = SwarmBehavior.ForceActive || State == AIState.Idle && Character.CurrentHull == null;
SwarmBehavior.Refresh();
SwarmBehavior.UpdateSteering(deltaTime);
}
@@ -876,7 +877,7 @@ namespace Barotrauma
var pathSteering = SteeringManager as IndoorsSteeringManager;
if (pathSteering == null)
{
if (SimPosition.Y < ConvertUnits.ToSimUnits(Character.CharacterHealth.CrushDepth * 0.75f))
if (Level.Loaded != null && Level.Loaded.GetRealWorldDepth(WorldPosition.Y) > Character.CharacterHealth.CrushDepth * 0.75f)
{
// Steer straight up if very deep
SteeringManager.SteeringManual(deltaTime, Vector2.UnitY);
@@ -1144,7 +1145,6 @@ namespace Barotrauma
return;
}
}
attackLimbSelectionTimer -= deltaTime;
if (AttackLimb == null || attackLimbSelectionTimer <= 0)
{
@@ -1154,7 +1154,8 @@ namespace Barotrauma
AttackLimb = GetAttackLimb(attackWorldPos);
}
}
Character targetCharacter = SelectedAiTarget.Entity as Character;
IDamageable damageTarget = wallTarget != null ? wallTarget.Structure : SelectedAiTarget.Entity as IDamageable;
bool canAttack = true;
bool pursue = false;
if (IsCoolDownRunning && (_previousAttackLimb == null || AttackLimb == null || AttackLimb.attack.CoolDownTimer > 0))
@@ -1379,7 +1380,6 @@ namespace Barotrauma
float distance = 0;
Limb attackTargetLimb = null;
Character targetCharacter = SelectedAiTarget.Entity as Character;
if (canAttack)
{
if (!Character.AnimController.SimplePhysicsEnabled)
@@ -1400,29 +1400,29 @@ namespace Barotrauma
attackSimPos = Character.GetRelativeSimPosition(attackTargetLimb);
}
}
Vector2 attackLimbPos = Character.AnimController.SimplePhysicsEnabled ? Character.WorldPosition : AttackLimb.WorldPosition;
Vector2 toTarget = attackWorldPos - attackLimbPos;
Vector2 toTargetOffset = toTarget;
// 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)
{
Vector2 margin = CalculateMargin(wallTarget.Structure.Submarine.Velocity);
toTarget += margin;
toTargetOffset += margin;
}
}
else if (targetCharacter != null)
{
Vector2 margin = CalculateMargin(targetCharacter.AnimController.Collider.LinearVelocity);
toTarget += margin;
toTargetOffset += margin;
}
else if (SelectedAiTarget.Entity is MapEntity e)
{
if (e.Submarine != null)
{
Vector2 margin = CalculateMargin(e.Submarine.Velocity);
toTarget += margin;
toTargetOffset += margin;
}
}
@@ -1430,7 +1430,7 @@ namespace Barotrauma
{
if (targetVelocity == Vector2.Zero) { return Vector2.Zero; }
float diff = AttackLimb.attack.Range - AttackLimb.attack.DamageRange;
if (diff <= 0 || toTarget.LengthSquared() <= MathUtils.Pow2(AttackLimb.attack.DamageRange)) { return Vector2.Zero; }
if (diff <= 0 || toTargetOffset.LengthSquared() <= MathUtils.Pow2(AttackLimb.attack.DamageRange)) { return Vector2.Zero; }
float dot = Vector2.Dot(Vector2.Normalize(targetVelocity), Vector2.Normalize(Character.AnimController.Collider.LinearVelocity));
if (dot <= 0 || !MathUtils.IsValid(dot)) { return Vector2.Zero; }
float distanceOffset = diff * AttackLimb.attack.Duration;
@@ -1439,7 +1439,7 @@ namespace Barotrauma
}
// Check that we can reach the target
distance = toTarget.Length();
distance = toTargetOffset.Length();
canAttack = distance < AttackLimb.attack.Range;
if (canAttack)
{
@@ -1523,20 +1523,18 @@ namespace Barotrauma
}
}
Limb steeringLimb = canAttack && !AttackLimb.attack.Ranged ? AttackLimb : null;
bool updateSteering = true;
if (steeringLimb == null)
{
// If the attacking limb is a hand or claw, for example, using it as the steering limb can end in the result where the character circles around the target.
steeringLimb = Character.AnimController.GetLimb(LimbType.Head) ?? Character.AnimController.GetLimb(LimbType.Torso);
}
if (steeringLimb == null)
{
State = AIState.Idle;
return;
}
var pathSteering = SteeringManager as IndoorsSteeringManager;
if (AttackLimb != null && AttackLimb.attack.Retreat)
{
UpdateFallBack(attackWorldPos, deltaTime, followThrough: false);
@@ -1603,7 +1601,7 @@ namespace Barotrauma
}
}
}
else
else if (!IsTryingToSteerThroughGap)
{
if (AttackLimb.attack.Ranged)
{
@@ -1624,6 +1622,10 @@ namespace Barotrauma
SteeringManager.Reset();
}
}
else
{
SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(SelectedAiTarget.Entity.WorldPosition - Character.WorldPosition));
}
}
else
{
@@ -1662,40 +1664,60 @@ namespace Barotrauma
if (IsAttackRunning && CirclePhase != CirclePhase.Strike) { break; }
if (selectedTargetingParams == null) { break; }
var targetSub = SelectedAiTarget.Entity?.Submarine;
if (targetSub == null) { break; }
float subSize = Math.Max(targetSub.Borders.Width, targetSub.Borders.Height) / 2;
float sqrDistToSub = Vector2.DistanceSquared(WorldPosition, targetSub.WorldPosition);
ISpatialEntity spatialTarget = targetSub ?? SelectedAiTarget.Entity;
float targetSize = 0;
if (!selectedTargetingParams.IgnoreTargetSize)
{
targetSize =
targetSub != null ? Math.Max(targetSub.Borders.Width, targetSub.Borders.Height) / 2 :
targetCharacter != null ? ConvertUnits.ToDisplayUnits(targetCharacter.AnimController.Collider.GetSize().X) : 100;
}
float sqrDistToTarget = Vector2.DistanceSquared(WorldPosition, spatialTarget.WorldPosition);
bool isProgressive = AIParams.MaxAggression - AIParams.StartAggression > 0;
switch (CirclePhase)
{
case CirclePhase.Start:
currentAttackIntensity = MathUtils.InverseLerp(AIParams.StartAggression, AIParams.MaxAggression, aggressionIntensity * Rand.Range(0.9f, 1.1f));
currentAttackIntensity = MathUtils.InverseLerp(AIParams.StartAggression, AIParams.MaxAggression, ClampIntensity(aggressionIntensity));
inverseDir = false;
circleDir = GetDirFromHeadingInRadius();
circleRotation = 0;
strikeTimer = 0;
blockCheckTimer = 0;
breakCircling = false;
float minRotationSpeed = 0.01f * selectedTargetingParams.CircleRotationSpeed;
float maxRotationSpeed = 0.5f * selectedTargetingParams.CircleRotationSpeed;
float minFallBackDistance = selectedTargetingParams.CircleStartDistance * 0.5f;
float maxFallBackDistance = selectedTargetingParams.CircleStartDistance;
float maxRandomOffset = selectedTargetingParams.CircleMaxRandomOffset;
// The lower the rotation speed, the slower the progression. Also the distance to the target stays longer.
// So basically if the value is higher, the creature will strike the sub more quickly and with more precision.
circleRotationSpeed = MathHelper.Lerp(minRotationSpeed, maxRotationSpeed, currentAttackIntensity * Rand.Range(0.9f, 1.1f));
circleFallbackDistance = MathHelper.Lerp(maxFallBackDistance, minFallBackDistance, currentAttackIntensity * Rand.Range(0.9f, 1.1f));
circleOffset = Rand.Vector(MathHelper.Lerp(selectedTargetingParams.CircleMaxRandomOffset, 0, currentAttackIntensity * Rand.Range(0.9f, 1.1f)));
canAttack = false;
float ClampIntensity(float intensity) => MathHelper.Clamp(intensity * Rand.Range(0.9f, 1.1f), AIParams.StartAggression, AIParams.MaxAggression);
if (isProgressive)
{
float intensity = ClampIntensity(currentAttackIntensity);
float minRotationSpeed = 0.01f * selectedTargetingParams.CircleRotationSpeed;
float maxRotationSpeed = 0.5f * selectedTargetingParams.CircleRotationSpeed;
circleRotationSpeed = MathHelper.Lerp(minRotationSpeed, maxRotationSpeed, intensity);
circleFallbackDistance = MathHelper.Lerp(maxFallBackDistance, minFallBackDistance, intensity);
circleOffset = Rand.Vector(MathHelper.Lerp(maxRandomOffset, 0, intensity));
}
else
{
circleRotationSpeed = selectedTargetingParams.CircleRotationSpeed;
circleFallbackDistance = maxFallBackDistance;
circleOffset = Rand.Vector(maxRandomOffset);
}
circleRotationSpeed *= Rand.Range(1 - selectedTargetingParams.CircleRandomRotationFactor, 1 + selectedTargetingParams.CircleRandomRotationFactor);
aggressionIntensity = Math.Clamp(aggressionIntensity, AIParams.StartAggression, AIParams.MaxAggression);
if (targetSub.Borders.Width < 1000)
DisableAttacksIfLimbNotRanged();
if (targetSub != null && targetSub.Borders.Width < 1000 && AttackLimb?.attack is { Ranged: false })
{
breakCircling = true;
CirclePhase = CirclePhase.CloseIn;
}
else if (sqrDistToSub > MathUtils.Pow2(subSize + selectedTargetingParams.CircleStartDistance))
else if (sqrDistToTarget > MathUtils.Pow2(targetSize + selectedTargetingParams.CircleStartDistance))
{
CirclePhase = CirclePhase.CloseIn;
}
else if (sqrDistToSub < MathUtils.Pow2(subSize + circleFallbackDistance))
else if (sqrDistToTarget < MathUtils.Pow2(targetSize + circleFallbackDistance))
{
CirclePhase = CirclePhase.FallBack;
}
@@ -1705,52 +1727,76 @@ namespace Barotrauma
}
break;
case CirclePhase.CloseIn:
if (AttackLimb != null && distance > 0 && distance < AttackLimb.attack.Range * GetStrikeDistanceMultiplier(targetSub.Velocity))
Vector2 targetVelocity = GetTargetVelocity();
float targetDistance = selectedTargetingParams.IgnoreTargetSize ? selectedTargetingParams.CircleStartDistance * 0.9f:
targetSize + selectedTargetingParams.CircleStartDistance / 2;
if (AttackLimb != null && distance > 0 && distance < AttackLimb.attack.Range * GetStrikeDistanceMultiplier(targetVelocity))
{
strikeTimer = AttackLimb.attack.CoolDown;
CirclePhase = CirclePhase.Strike;
}
else if (!breakCircling && sqrDistToSub <= MathUtils.Pow2(subSize + selectedTargetingParams.CircleStartDistance / 2) && targetSub.Velocity.LengthSquared() <= MathUtils.Pow2(GetTargetMaxSpeed()))
else if (!breakCircling && sqrDistToTarget <= MathUtils.Pow2(targetDistance) && targetVelocity.LengthSquared() <= MathUtils.Pow2(GetTargetMaxSpeed()))
{
CirclePhase = CirclePhase.Advance;
}
canAttack = false;
DisableAttacksIfLimbNotRanged();
break;
case CirclePhase.FallBack:
updateSteering = false;
bool isBlocked = !UpdateFallBack(attackWorldPos, deltaTime, followThrough: false, checkBlocking: true);
if (isBlocked || sqrDistToSub > MathUtils.Pow2(subSize + circleFallbackDistance))
if (isBlocked || sqrDistToTarget > MathUtils.Pow2(targetSize + circleFallbackDistance))
{
CirclePhase = CirclePhase.Advance;
break;
}
return;
DisableAttacksIfLimbNotRanged();
break;
case CirclePhase.Advance:
Vector2 subSpeed = targetSub.Velocity;
float requiredDistMultiplier = 1;
// If the target sub is moving fast, just steer towards the target until close enough to strike
if (breakCircling || subSpeed.LengthSquared() > MathUtils.Pow2(GetTargetMaxSpeed()) || sqrDistToSub > MathUtils.Pow2(subSize + selectedTargetingParams.CircleStartDistance * 1.2f))
Vector2 targetVel = GetTargetVelocity();
// If the target is moving fast, just steer towards the target
if (breakCircling || targetVel.LengthSquared() > MathUtils.Pow2(GetTargetMaxSpeed()))
{
CirclePhase = CirclePhase.CloseIn;
}
else if (sqrDistToTarget > MathUtils.Pow2(targetSize + selectedTargetingParams.CircleStartDistance * 1.2f))
{
if (selectedTargetingParams.DynamicCircleRotationSpeed && circleRotationSpeed < 100)
{
circleRotationSpeed *= 1 + deltaTime;
}
else
{
CirclePhase = CirclePhase.CloseIn;
}
}
else
{
circleRotation += deltaTime * circleRotationSpeed * circleDir;
if (circleRotation < -360)
float rotationStep = circleRotationSpeed * deltaTime * circleDir;
if (isProgressive)
{
circleRotation += 360;
circleRotation += rotationStep;
}
else if (circleRotation > 360)
else
{
circleRotation -= 360;
circleRotation = rotationStep;
}
Vector2 targetPos = attackSimPos + circleOffset;
if (Vector2.DistanceSquared(SimPosition, targetPos) < 100)
float targetDist = targetSize;
if (targetDist <= 0)
{
targetDist = circleFallbackDistance;
}
if (targetSub != null && AttackLimb?.attack is { Ranged: true })
{
targetDist += circleFallbackDistance / 2;
}
if (Vector2.DistanceSquared(SimPosition, targetPos) < ConvertUnits.ToSimUnits(targetDist))
{
// Too close to the target point
// When the offset position is outside of the sub it happens that the creature sometimes reaches the target point,
// which makes it continue circling around the point (as supposed)
// But when there is some offset and the offset is too near, this is not what we want.
if (AttackLimb != null && sqrDistToSub < MathUtils.Pow2(subSize + circleFallbackDistance))
if (canAttack && AttackLimb?.attack is { Ranged: false } && sqrDistToTarget < MathUtils.Pow2(targetSize + circleFallbackDistance))
{
CirclePhase = CirclePhase.Strike;
strikeTimer = AttackLimb.attack.CoolDown;
@@ -1762,7 +1808,6 @@ namespace Barotrauma
break;
}
steerPos = MathUtils.RotatePointAroundTarget(SimPosition, targetPos, circleRotation);
requiredDistMultiplier = GetStrikeDistanceMultiplier(subSpeed);
if (IsBlocked(deltaTime, steerPos))
{
if (!inverseDir)
@@ -1774,7 +1819,7 @@ namespace Barotrauma
else if (circleRotationSpeed < 1)
{
// Then try increasing the rotation speed to change the movement curve
circleRotationSpeed *= 1.1f;
circleRotationSpeed *= 1 + deltaTime;
}
else if (circleOffset.LengthSquared() > 0.1f)
{
@@ -1784,16 +1829,24 @@ namespace Barotrauma
else
{
// If we still fail, just steer towards the target
breakCircling = true;
breakCircling = AttackLimb?.attack is { Ranged: false };
if (!breakCircling)
{
CirclePhase = CirclePhase.FallBack;
}
}
}
}
if (AttackLimb != null && distance > 0 && distance < AttackLimb.attack.Range * requiredDistMultiplier && IsFacing(margin: MathHelper.Lerp(0.5f, 0.9f, currentAttackIntensity)))
if (AttackLimb?.attack is { Ranged: false })
{
strikeTimer = AttackLimb.attack.CoolDown;
CirclePhase = CirclePhase.Strike;
canAttack = false;
float requiredDistMultiplier = GetStrikeDistanceMultiplier(targetVel);
if (distance > 0 && distance < AttackLimb.attack.Range * requiredDistMultiplier && IsFacing(margin: MathHelper.Lerp(0.5f, 0.9f, currentAttackIntensity)))
{
strikeTimer = AttackLimb.attack.CoolDown;
CirclePhase = CirclePhase.Strike;
}
}
canAttack = false;
break;
case CirclePhase.Strike:
strikeTimer -= deltaTime;
@@ -1815,18 +1868,19 @@ namespace Barotrauma
return Vector2.Dot(Vector2.Normalize(attackWorldPos - WorldPosition), forward) > margin;
}
float GetStrikeDistanceMultiplier(Vector2 subSpeed)
float GetStrikeDistanceMultiplier(Vector2 targetVelocity)
{
if (selectedTargetingParams.CircleStrikeDistanceMultiplier < 1) { return 0; }
float requiredDistMultiplier = 2;
bool isHeading = Steering != null && Vector2.Dot(Vector2.Normalize(attackWorldPos - WorldPosition), Vector2.Normalize(Steering)) > 0.9f;
bool isHeading = Vector2.Dot(Vector2.Normalize(attackWorldPos - WorldPosition), Vector2.Normalize(Steering)) > 0.9f;
if (isHeading)
{
requiredDistMultiplier = selectedTargetingParams.CircleStrikeDistanceMultiplier;
float subSpeedHorizontal = Math.Abs(subSpeed.X);
if (subSpeedHorizontal > 1)
float targetVelocityHorizontal = Math.Abs(targetVelocity.X);
if (targetVelocityHorizontal > 1)
{
// Reduce the required distance if the target is moving.
requiredDistMultiplier -= MathHelper.Lerp(0, Math.Max(selectedTargetingParams.CircleStrikeDistanceMultiplier - 1, 1), Math.Clamp(subSpeedHorizontal / 10, 0, 1));
requiredDistMultiplier -= MathHelper.Lerp(0, Math.Max(selectedTargetingParams.CircleStrikeDistanceMultiplier - 1, 1), Math.Clamp(targetVelocityHorizontal / 10, 0, 1));
if (requiredDistMultiplier < 2)
{
requiredDistMultiplier = 2;
@@ -1843,19 +1897,59 @@ namespace Barotrauma
return angle > MathHelper.Pi || angle < -MathHelper.Pi ? -1 : 1;
}
float GetTargetMaxSpeed() => Character.ApplyTemporarySpeedLimits(Character.AnimController.CurrentSwimParams.MovementSpeed * 0.3f);
Vector2 GetTargetVelocity()
{
if (targetSub != null)
{
return targetSub.Velocity;
}
else if (targetCharacter != null)
{
return targetCharacter.AnimController.Collider.LinearVelocity;
}
return Vector2.Zero;
}
float GetTargetMaxSpeed() => Character.ApplyTemporarySpeedLimits(Character.AnimController.SwimFastParams.MovementSpeed * (targetSub != null ? 0.3f : 0.5f));
}
}
if (selectedTargetingParams.AttackPattern == AttackPattern.Straight && AttackLimb is Limb attackLimb && attackLimb.attack.Ranged)
if (updateSteering)
{
bool advance = !canAttack && Character.CurrentHull == null || distance > attackLimb.attack.Range * 0.9f;
bool fallBack = canAttack && distance < Math.Min(250, attackLimb.attack.Range * 0.25f);
if (fallBack)
if (selectedTargetingParams.AttackPattern == AttackPattern.Straight && AttackLimb is Limb attackLimb && attackLimb.attack.Ranged)
{
Reverse = true;
UpdateFallBack(attackWorldPos, deltaTime, followThrough: false);
bool advance = !canAttack && Character.CurrentHull == null || distance > attackLimb.attack.Range * 0.9f;
bool fallBack = canAttack && distance < Math.Min(250, attackLimb.attack.Range * 0.25f);
if (fallBack)
{
Reverse = true;
UpdateFallBack(attackWorldPos, deltaTime, followThrough: false);
}
else if (advance)
{
if (pathSteering != null)
{
pathSteering.SteeringSeek(steerPos, weight: 10, minGapWidth: minGapSize);
}
else
{
SteeringManager.SteeringSeek(steerPos, 10);
}
}
else
{
if (Character.CurrentHull == null && !canAttack)
{
SteeringManager.SteeringWander(avoidWanderingOutsideLevel: true);
SteeringManager.SteeringAvoid(deltaTime, lookAheadDistance: avoidLookAheadDistance, weight: 5);
}
else
{
SteeringManager.Reset();
FaceTarget(SelectedAiTarget.Entity);
}
}
}
else if (advance)
else if (!canAttack || distance > Math.Min(AttackLimb.attack.Range * 0.9f, 100))
{
if (pathSteering != null)
{
@@ -1866,41 +1960,18 @@ namespace Barotrauma
SteeringManager.SteeringSeek(steerPos, 10);
}
}
else
if (Character.CurrentHull == null && (SelectedAiTarget?.Entity is Character c && c.Submarine == null ||
distance == 0 ||
distance > ConvertUnits.ToDisplayUnits(avoidLookAheadDistance * 2) ||
AttackLimb != null && AttackLimb.attack.Ranged))
{
if (Character.CurrentHull == null && !canAttack)
{
SteeringManager.SteeringWander(avoidWanderingOutsideLevel: true);
SteeringManager.SteeringAvoid(deltaTime, lookAheadDistance: avoidLookAheadDistance, weight: 5);
}
else
{
SteeringManager.Reset();
FaceTarget(SelectedAiTarget.Entity);
}
SteeringManager.SteeringAvoid(deltaTime, lookAheadDistance: avoidLookAheadDistance, weight: 30);
}
}
else if (!canAttack || distance > Math.Min(AttackLimb.attack.Range * 0.9f, 100))
{
if (pathSteering != null)
{
pathSteering.SteeringSeek(steerPos, weight: 10, minGapWidth: minGapSize);
}
else
{
SteeringManager.SteeringSeek(steerPos, 10);
}
}
if (Character.CurrentHull == null && (SelectedAiTarget?.Entity is Character c && c.Submarine == null || distance == 0 || distance > ConvertUnits.ToDisplayUnits(avoidLookAheadDistance * 2)))
{
SteeringManager.SteeringAvoid(deltaTime, lookAheadDistance: avoidLookAheadDistance, weight: 30);
}
}
}
Entity targetEntity = wallTarget?.Structure ?? SelectedAiTarget?.Entity;
IDamageable damageTarget = targetEntity as IDamageable;
if (AttackLimb?.attack is Attack { Ranged: true} attack)
if (AttackLimb?.attack is Attack { Ranged: true } attack)
{
AimRangedAttack(attack, targetEntity);
}
@@ -1915,12 +1986,21 @@ namespace Barotrauma
{
AttackLimb.attack.ResetAttackTimer();
}
void DisableAttacksIfLimbNotRanged()
{
if (AttackLimb?.attack is { Ranged: false })
{
canAttack = false;
}
}
}
public void AimRangedAttack(Attack attack, Entity targetEntity)
{
if (attack is not { Ranged: true } || targetEntity is not { Removed: false }) { return; }
Character.SetInput(InputType.Aim, false, true);
if (attack.AimRotationTorque <= 0) { return; }
Limb limb = GetLimbToRotate(attack);
if (limb != null)
{
@@ -2003,9 +2083,18 @@ namespace Barotrauma
float prio = 1 + limb.attack.Priority;
if (Character.AnimController.SimplePhysicsEnabled) { return prio; }
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));
float distanceFactor = 1;
if (limb.attack.Ranged)
{
float min = 100;
distanceFactor = MathHelper.Lerp(1, 0, MathUtils.InverseLerp(min, Math.Max(limb.attack.Range / 2, min), dist));
}
else
{
// 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.
distanceFactor = MathHelper.Lerp(1, 0, MathUtils.InverseLerp(0, limb.attack.Range * 3, dist));
}
return prio * distanceFactor;
}
}
@@ -2164,7 +2253,9 @@ namespace Barotrauma
{
if (SelectedAiTarget?.Entity == null) { return false; }
if (AttackLimb?.attack == null) { return false; }
if (damageTarget == null) { return false; }
ISpatialEntity spatialTarget = wallTarget != null ? wallTarget.Structure : SelectedAiTarget.Entity as ISpatialEntity;
if (spatialTarget == null) { return false; }
ActiveAttack = AttackLimb.attack;
if (wallTarget != null)
{
// If the selected target is not the wall target, make the wall target the selected target.
@@ -2176,13 +2267,14 @@ namespace Barotrauma
return true;
}
}
if (damageTarget == null) { return false; }
ActiveAttack = AttackLimb.attack;
if (ActiveAttack.Ranged && ActiveAttack.RequiredAngleToShoot > 0)
{
Limb referenceLimb = GetLimbToRotate(ActiveAttack);
if (referenceLimb != null)
{
Vector2 toTarget = damageTarget.WorldPosition - referenceLimb.WorldPosition;
Vector2 toTarget = spatialTarget.WorldPosition - referenceLimb.WorldPosition;
float offset = referenceLimb.Params.GetSpriteOrientation() - MathHelper.PiOver2;
Vector2 forward = VectorExtensions.Forward(referenceLimb.body.TransformedRotation - offset * referenceLimb.Dir);
float angle = MathHelper.ToDegrees(VectorExtensions.Angle(forward, toTarget));
@@ -2200,16 +2292,20 @@ namespace Barotrauma
{
if (item.RequireAimToUse)
{
if (!Aim(deltaTime, damageTarget as ISpatialEntity, item))
if (!Aim(deltaTime, spatialTarget, item))
{
// Valid target, but can't shoot -> return true so that it will not be ignored.
return true;
}
}
Character.SetInput(item.IsShootable ? InputType.Shoot : InputType.Use, false, true);
item.Use(deltaTime, Character);
if (damageTarget != null)
{
Character.SetInput(item.IsShootable ? InputType.Shoot : InputType.Use, false, true);
item.Use(deltaTime, Character);
}
}
}
if (damageTarget == null) { return true; }
//simulate attack input to get the character to attack client-side
Character.SetInput(InputType.Attack, true, true);
if (!ActiveAttack.IsRunning)
@@ -2224,20 +2320,11 @@ namespace Barotrauma
Character.PlaySound(CharacterSound.SoundType.Attack, maxInterval: 3);
#endif
}
if (AttackLimb.UpdateAttack(deltaTime, attackSimPos, damageTarget, out AttackResult attackResult, distance, targetLimb))
{
if (ActiveAttack.CoolDownTimer > 0)
{
SetAimTimer(Math.Min(ActiveAttack.CoolDown, 1.5f));
// Managed to hit a living/non-destroyed target. Increase the priority more if the target is low in health -> dies easily/soon
float greed = AIParams.AggressionGreed;
if (damageTarget is not Barotrauma.Character)
{
// Halve the greed for attacking non-characters.
greed /= 2;
}
selectedTargetMemory.Priority += GetRelativeDamage(attackResult.Damage, damageTarget.Health) * greed;
}
if (LatchOntoAI != null && SelectedAiTarget.Entity is Character targetCharacter)
{
@@ -2269,10 +2356,19 @@ namespace Barotrauma
private float aimTimer;
private float visibilityCheckTimer;
private bool canSeeTarget;
private float sinTime;
private bool Aim(float deltaTime, ISpatialEntity target, Item weapon)
{
if (target == null || weapon == null) { return false; }
if (AttackLimb == null) { return false; }
Vector2 toTarget = target.WorldPosition - weapon.WorldPosition;
float dist = toTarget.Length();
Character.CursorPosition = target.WorldPosition;
if (AttackLimb.attack.SwayAmount > 0)
{
sinTime += deltaTime * AttackLimb.attack.SwayFrequency;
Character.CursorPosition += VectorExtensions.Forward(weapon.body.TransformedRotation + (float)Math.Sin(sinTime) / 2, dist / 2 * AttackLimb.attack.SwayAmount);
}
if (Character.Submarine != null)
{
Character.CursorPosition -= Character.Submarine.Position;
@@ -2294,11 +2390,11 @@ namespace Barotrauma
aimTimer -= deltaTime;
return false;
}
Vector2 toTarget = target.WorldPosition - weapon.WorldPosition;
float angle = VectorExtensions.Angle(VectorExtensions.Forward(weapon.body.TransformedRotation), toTarget);
float distanceFactor = MathHelper.Lerp(1.0f, 0.1f, MathUtils.InverseLerp(100, 1000, toTarget.Length()));
float minDistance = 300;
float distanceFactor = MathHelper.Lerp(1.0f, 0.1f, MathUtils.InverseLerp(minDistance, 1000, dist));
float margin = MathHelper.PiOver4 * distanceFactor;
if (angle < margin)
if (angle < margin || dist < minDistance)
{
var collisionCategories = Physics.CollisionCharacter | Physics.CollisionWall | Physics.CollisionLevel;
var pickedBody = Submarine.PickBody(weapon.SimPosition, Character.GetRelativeSimPosition(target), myBodies, collisionCategories, allowInsideFixture: true);
@@ -2591,13 +2687,19 @@ namespace Barotrauma
{
// Ignore all structures, items, and hulls inside these subs.
if (aiTarget.Entity.Submarine != null)
{
if (aiTarget.Entity.Submarine.Info.IsWreck ||
aiTarget.Entity.Submarine.Info.IsBeacon ||
{
if (aiTarget.Entity.Submarine.Info.IsWreck ||
aiTarget.Entity.Submarine.Info.IsBeacon ||
UnattackableSubmarines.Contains(aiTarget.Entity.Submarine))
{
continue;
}
//ignore the megaruin in end levels
if (aiTarget.Entity.Submarine.Info.OutpostGenerationParams != null &&
aiTarget.Entity.Submarine.Info.OutpostGenerationParams.ForceToEndLocationIndex > -1)
{
continue;
}
}
if (aiTarget.Entity is Hull hull)
{
@@ -2698,7 +2800,7 @@ namespace Barotrauma
}
else if (CanPassThroughHole(s, i))
{
valueModifier *= isInnerWall ? 1 : 0;
valueModifier *= isInnerWall ? 0.5f : 0;
}
else if (!canAttackWalls)
{
@@ -2968,7 +3070,8 @@ namespace Barotrauma
// In the attack state allow going into non-allowed zone only when chasing a target.
if (State == targetParams.State && SelectedAiTarget == aiTarget) { break; }
}
if (!IsPositionInsideAllowedZone(aiTarget.WorldPosition, out _))
bool insideSameSub = aiTarget?.Entity?.Submarine != null && aiTarget.Entity.Submarine == Character.Submarine;
if (!insideSameSub && !IsPositionInsideAllowedZone(aiTarget.WorldPosition, out _))
{
// If we have recently been damaged by the target (or another player/bot in the same team) allow targeting it even when we are in the idle state.
bool isTargetInPlayerTeam = IsTargetInPlayerTeam(aiTarget);
@@ -3401,10 +3504,10 @@ namespace Barotrauma
private readonly float stateResetCooldown = 10;
private float stateResetTimer;
private bool isStateChanged;
private readonly Dictionary<AITrigger, CharacterParams.TargetParams> activeTriggers = new Dictionary<AITrigger, CharacterParams.TargetParams>();
private readonly HashSet<AITrigger> inactiveTriggers = new HashSet<AITrigger>();
private readonly Dictionary<StatusEffect.AITrigger, CharacterParams.TargetParams> activeTriggers = new Dictionary<StatusEffect.AITrigger, CharacterParams.TargetParams>();
private readonly HashSet<StatusEffect.AITrigger> inactiveTriggers = new HashSet<StatusEffect.AITrigger>();
public void LaunchTrigger(AITrigger trigger)
public void LaunchTrigger(StatusEffect.AITrigger trigger)
{
if (trigger.IsTriggered) { return; }
if (activeTriggers.ContainsKey(trigger)) { return; }
@@ -3424,7 +3527,7 @@ namespace Barotrauma
{
foreach (var triggerObject in activeTriggers)
{
AITrigger trigger = triggerObject.Key;
StatusEffect.AITrigger trigger = triggerObject.Key;
if (trigger.IsPermanent) { continue; }
trigger.UpdateTimer(deltaTime);
if (!trigger.IsActive)
@@ -3434,7 +3537,7 @@ namespace Barotrauma
inactiveTriggers.Add(trigger);
}
}
foreach (AITrigger trigger in inactiveTriggers)
foreach (StatusEffect.AITrigger trigger in inactiveTriggers)
{
activeTriggers.Remove(trigger);
}
@@ -3588,6 +3691,11 @@ namespace Barotrauma
observeTimer = targetParams.Timer * Rand.Range(0.75f, 1.25f);
}
reachTimer = 0;
sinTime = 0;
if (breakCircling && strikeTimer <= 0)
{
CirclePhase = CirclePhase.Start;
}
}
protected override void OnStateChanged(AIState from, AIState to)
@@ -3609,6 +3717,11 @@ namespace Barotrauma
}
blockCheckTimer = 0;
reachTimer = 0;
sinTime = 0;
if (breakCircling && strikeTimer <= 0)
{
CirclePhase = CirclePhase.Start;
}
}
private void SetStateResetTimer() => stateResetTimer = stateResetCooldown * Rand.Range(0.75f, 1.25f);
@@ -3712,6 +3825,7 @@ namespace Barotrauma
{
targetDir = Vector2.Zero;
if (Level.Loaded == null) { return true; }
if (Level.Loaded.LevelData.Biome.IsEndBiome) { return true; }
if (AIParams.AvoidAbyss)
{
if (pos.Y < Level.Loaded.AbyssStart)
@@ -3771,6 +3885,7 @@ namespace Barotrauma
public override bool SteerThroughGap(Structure wall, WallSection section, Vector2 targetWorldPos, float deltaTime)
{
IsTryingToSteerThroughGap = true;
wallTarget = null;
LatchOntoAI?.DeattachFromBody(reset: true, cooldown: 2);
Character.AnimController.ReleaseStuckLimbs();