Build 1.1.4.0
This commit is contained in:
@@ -168,6 +168,7 @@ namespace Barotrauma
|
||||
public void FaceTarget(ISpatialEntity target) => Character.AnimController.TargetDir = target.WorldPosition.X > Character.WorldPosition.X ? Direction.Right : Direction.Left;
|
||||
|
||||
public bool IsSteeringThroughGap { get; protected set; }
|
||||
public bool IsTryingToSteerThroughGap { get; protected set; }
|
||||
|
||||
public virtual bool SteerThroughGap(Structure wall, WallSection section, Vector2 targetWorldPos, float deltaTime)
|
||||
{
|
||||
@@ -444,7 +445,7 @@ namespace Barotrauma
|
||||
if (EscapeTarget != null)
|
||||
{
|
||||
var door = EscapeTarget.ConnectedDoor;
|
||||
bool isClosedDoor = door != null && !door.IsOpen;
|
||||
bool isClosedDoor = door != null && door.IsClosed;
|
||||
Vector2 diff = EscapeTarget.WorldPosition - Character.WorldPosition;
|
||||
float sqrDist = diff.LengthSquared();
|
||||
bool isClose = sqrDist < MathUtils.Pow2(100);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
using Barotrauma.Networking;
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Items.Components;
|
||||
using Barotrauma.Networking;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Items.Components;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -395,6 +395,8 @@ namespace Barotrauma
|
||||
}
|
||||
objectiveManager.UpdateObjectives(deltaTime);
|
||||
|
||||
UpdateDragged(deltaTime);
|
||||
|
||||
if (reportProblemsTimer > 0)
|
||||
{
|
||||
reportProblemsTimer -= deltaTime;
|
||||
@@ -430,7 +432,7 @@ namespace Barotrauma
|
||||
}
|
||||
if (reportProblemsTimer <= 0.0f)
|
||||
{
|
||||
if (Character.Submarine != null && (Character.Submarine.TeamID == Character.TeamID || Character.IsEscorted) && !Character.Submarine.Info.IsWreck)
|
||||
if (Character.Submarine != null && (Character.Submarine.TeamID == Character.TeamID || Character.Submarine.TeamID == Character.OriginalTeamID || Character.IsEscorted) && !Character.Submarine.Info.IsWreck)
|
||||
{
|
||||
ReportProblems();
|
||||
}
|
||||
@@ -444,7 +446,7 @@ namespace Barotrauma
|
||||
if (objectiveManager.CurrentObjective == null) { return; }
|
||||
|
||||
objectiveManager.DoCurrentObjective(deltaTime);
|
||||
bool run = objectiveManager.CurrentObjective.ForceRun || !objectiveManager.CurrentObjective.ForceWalk && objectiveManager.GetCurrentPriority() > AIObjectiveManager.RunPriority;
|
||||
bool run = (objectiveManager.CurrentObjective.ForceRun && !objectiveManager.CurrentObjective.ForceWalk) || (!objectiveManager.CurrentObjective.ForceWalk && objectiveManager.GetCurrentPriority() > AIObjectiveManager.RunPriority);
|
||||
if (ObjectiveManager.CurrentObjective is AIObjectiveGoTo goTo && goTo.Target != null)
|
||||
{
|
||||
if (Character.CurrentHull == null)
|
||||
@@ -546,12 +548,11 @@ namespace Barotrauma
|
||||
|
||||
bool NeedsDivingGearOnPath(AIObjectiveGoTo gotoObjective)
|
||||
{
|
||||
if (!Character.NeedsAir) { return false; }
|
||||
bool insideSteering = SteeringManager == PathSteering && PathSteering.CurrentPath != null && !PathSteering.IsPathDirty;
|
||||
Hull targetHull = gotoObjective.GetTargetHull();
|
||||
return gotoObjective.Target != null && targetHull == null ||
|
||||
return (gotoObjective.Target != null && targetHull == null && !Character.IsImmuneToPressure) ||
|
||||
NeedsDivingGear(targetHull, out _) ||
|
||||
insideSteering && (PathSteering.CurrentPath.HasOutdoorsNodes || PathSteering.CurrentPath.Nodes.Any(n => NeedsDivingGear(n.CurrentHull, out _)));
|
||||
(insideSteering && ((PathSteering.CurrentPath.HasOutdoorsNodes && !Character.IsImmuneToPressure) || PathSteering.CurrentPath.Nodes.Any(n => NeedsDivingGear(n.CurrentHull, out _))));
|
||||
}
|
||||
|
||||
if (isCarrying)
|
||||
@@ -584,7 +585,7 @@ namespace Barotrauma
|
||||
Character.AnimController.InWater ||
|
||||
Character.AnimController.HeadInWater ||
|
||||
Character.Submarine == null ||
|
||||
(Character.Submarine.TeamID != Character.TeamID && !Character.IsEscorted) ||
|
||||
(!Character.IsOnFriendlyTeam(Character.TeamID, Character.Submarine.TeamID) && !Character.IsEscorted) ||
|
||||
ObjectiveManager.CurrentOrders.Any(o => o.Objective.KeepDivingGearOnAlsoWhenInactive) ||
|
||||
ObjectiveManager.CurrentObjective.GetSubObjectivesRecursive(true).Any(o => o.KeepDivingGearOn) ||
|
||||
Character.CurrentHull.OxygenPercentage < HULL_LOW_OXYGEN_PERCENTAGE + 10 ||
|
||||
@@ -621,9 +622,10 @@ namespace Barotrauma
|
||||
}
|
||||
else if (gotoObjective.Mimic)
|
||||
{
|
||||
bool targetHasDivingGear = HasDivingGear(gotoObjective.Target as Character, requireOxygenTank: false);
|
||||
if (!removeSuit)
|
||||
{
|
||||
removeDivingSuit = !HasDivingSuit(gotoObjective.Target as Character);
|
||||
removeDivingSuit = !targetHasDivingGear;
|
||||
if (removeDivingSuit)
|
||||
{
|
||||
removeSuit = true;
|
||||
@@ -631,7 +633,7 @@ namespace Barotrauma
|
||||
}
|
||||
if (!removeMask)
|
||||
{
|
||||
takeMaskOff = !HasDivingMask(gotoObjective.Target as Character);
|
||||
takeMaskOff = !targetHasDivingGear;
|
||||
if (takeMaskOff)
|
||||
{
|
||||
removeMask = true;
|
||||
@@ -783,20 +785,23 @@ namespace Barotrauma
|
||||
|
||||
private void HandleRelocation(Item item)
|
||||
{
|
||||
if (item.Submarine?.TeamID == CharacterTeamType.FriendlyNPC)
|
||||
if (item.SpawnedInCurrentOutpost) { return; }
|
||||
if (item.Submarine == null) { return; }
|
||||
// Only affects bots in the player team
|
||||
if (!Character.IsOnPlayerTeam) { return; }
|
||||
// Don't relocate if the item is on a sub of the same team
|
||||
if (item.Submarine.TeamID == Character.TeamID) { return; }
|
||||
if (itemsToRelocate.Contains(item)) { return; }
|
||||
itemsToRelocate.Add(item);
|
||||
if (item.Submarine.ConnectedDockingPorts.TryGetValue(Submarine.MainSub, out DockingPort myPort))
|
||||
{
|
||||
if (itemsToRelocate.Contains(item)) { return; }
|
||||
itemsToRelocate.Add(item);
|
||||
if (item.Submarine.ConnectedDockingPorts.TryGetValue(Submarine.MainSub, out DockingPort myPort))
|
||||
{
|
||||
myPort.OnUnDocked += Relocate;
|
||||
}
|
||||
var campaign = GameMain.GameSession.Campaign;
|
||||
if (campaign != null)
|
||||
{
|
||||
// In the campaign mode, undocking happens after leaving the outpost, so we can't use that.
|
||||
campaign.BeforeLevelLoading += Relocate;
|
||||
}
|
||||
myPort.OnUnDocked += Relocate;
|
||||
}
|
||||
var campaign = GameMain.GameSession.Campaign;
|
||||
if (campaign != null)
|
||||
{
|
||||
// In the campaign mode, undocking happens after leaving the outpost, so we can't use that.
|
||||
campaign.BeforeLevelLoading += Relocate;
|
||||
}
|
||||
|
||||
void Relocate()
|
||||
@@ -907,6 +912,35 @@ namespace Barotrauma
|
||||
return false;
|
||||
}
|
||||
|
||||
private float draggedTimer;
|
||||
private float refuseDraggingTimer;
|
||||
private const float RefuseDraggingAfter = 10.0f;
|
||||
private const float RefuseDraggingDuration = 30.0f;
|
||||
|
||||
private void UpdateDragged(float deltaTime)
|
||||
{
|
||||
if (Character.HumanPrefab is { AllowDraggingIndefinitely: true }) { return; }
|
||||
|
||||
//don't allow player characters who aren't in the same team to drag us for more than x seconds
|
||||
if (Character.SelectedBy == null ||
|
||||
!Character.SelectedBy.IsPlayer ||
|
||||
Character.SelectedBy.TeamID == Character.TeamID)
|
||||
{
|
||||
refuseDraggingTimer -= deltaTime;
|
||||
return;
|
||||
}
|
||||
|
||||
draggedTimer += deltaTime;
|
||||
if (draggedTimer > RefuseDraggingAfter ||
|
||||
(draggedTimer > 0.5f && refuseDraggingTimer > 0.0f))
|
||||
{
|
||||
draggedTimer = 0.0f;
|
||||
refuseDraggingTimer = RefuseDraggingDuration;
|
||||
Character.SelectedBy.DeselectCharacter();
|
||||
Character.Speak(TextManager.Get("dialogrefusedragging").Value, delay: 0.5f, identifier: "refusedragging".ToIdentifier(), minDurationBetweenSimilar: 5.0f);
|
||||
}
|
||||
}
|
||||
|
||||
protected void ReportProblems()
|
||||
{
|
||||
Order newOrder = null;
|
||||
@@ -989,8 +1023,8 @@ namespace Barotrauma
|
||||
targetHull = hull;
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach (Item item in Item.ItemList)
|
||||
}
|
||||
foreach (Item item in Item.RepairableItems)
|
||||
{
|
||||
if (item.CurrentHull != hull) { continue; }
|
||||
if (AIObjectiveRepairItems.IsValidTarget(item, Character))
|
||||
@@ -1209,7 +1243,7 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
isAttackerInfected = attacker.CharacterHealth.GetAfflictionStrength("alieninfection") > 0;
|
||||
isAttackerInfected = attacker.CharacterHealth.GetAfflictionStrength(AfflictionPrefab.AlienInfectedType) > 0;
|
||||
// Inform other NPCs
|
||||
if (isAttackerInfected || cumulativeDamage > minorDamageThreshold || totalDamage > minorDamageThreshold)
|
||||
{
|
||||
@@ -1523,7 +1557,7 @@ namespace Barotrauma
|
||||
{
|
||||
margin *= 2;
|
||||
}
|
||||
float minCeilingDist = mainCollider.height / 2 + mainCollider.radius + margin;
|
||||
float minCeilingDist = mainCollider.Height / 2 + mainCollider.Radius + margin;
|
||||
|
||||
shouldCrouch = Submarine.PickBody(startPos, startPos + Vector2.UnitY * minCeilingDist, null, Physics.CollisionWall, customPredicate: (fixture) => { return fixture.Body.UserData is not Submarine; }) != null;
|
||||
}
|
||||
@@ -1546,23 +1580,19 @@ namespace Barotrauma
|
||||
|
||||
public bool NeedsDivingGear(Hull hull, out bool needsSuit)
|
||||
{
|
||||
if (!Character.NeedsAir)
|
||||
{
|
||||
needsSuit = false;
|
||||
return false;
|
||||
}
|
||||
needsSuit = false;
|
||||
bool needsAir = Character.NeedsAir && Character.CharacterHealth.OxygenLowResistance < 1;
|
||||
if (hull == null ||
|
||||
hull.WaterPercentage > 90 ||
|
||||
hull.LethalPressure > 0 ||
|
||||
hull.ConnectedGaps.Any(gap => !gap.IsRoomToRoom && gap.Open > 0.9f))
|
||||
{
|
||||
needsSuit = !Character.HasAbilityFlag(AbilityFlags.ImmuneToPressure);
|
||||
return true;
|
||||
needsSuit = !Character.IsProtectedFromPressure;
|
||||
return needsAir || needsSuit;
|
||||
}
|
||||
if (hull.WaterPercentage > 60 || hull.OxygenPercentage < HULL_LOW_OXYGEN_PERCENTAGE + 1)
|
||||
{
|
||||
return true;
|
||||
return needsAir;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -1641,7 +1671,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (otherCharacter == character || otherCharacter.TeamID == character.TeamID || otherCharacter.IsDead ||
|
||||
otherCharacter.Info?.Job == null ||
|
||||
!(otherCharacter.AIController is HumanAIController otherHumanAI) ||
|
||||
otherCharacter.AIController is not HumanAIController otherHumanAI ||
|
||||
!otherHumanAI.VisibleHulls.Contains(character.CurrentHull))
|
||||
{
|
||||
continue;
|
||||
@@ -1654,10 +1684,10 @@ namespace Barotrauma
|
||||
float accumulatedDamage = Math.Max(otherHumanAI.structureDamageAccumulator[character], maxAccumulatedDamage);
|
||||
maxAccumulatedDamage = Math.Max(accumulatedDamage, maxAccumulatedDamage);
|
||||
|
||||
if (GameMain.GameSession?.Campaign?.Map?.CurrentLocation != null)
|
||||
if (GameMain.GameSession?.Campaign?.Map?.CurrentLocation?.Reputation != null && character.IsPlayer)
|
||||
{
|
||||
var reputationLoss = damageAmount * Reputation.ReputationLossPerWallDamage;
|
||||
GameMain.GameSession.Campaign.Map.CurrentLocation.Reputation.AddReputation(-reputationLoss);
|
||||
GameMain.GameSession.Campaign.Map.CurrentLocation.Reputation.AddReputation(-reputationLoss, Reputation.MaxReputationLossFromWallDamage);
|
||||
}
|
||||
|
||||
if (accumulatedDamage <= WarningThreshold) { return; }
|
||||
@@ -1745,12 +1775,14 @@ namespace Barotrauma
|
||||
}
|
||||
if (!someoneSpoke)
|
||||
{
|
||||
if (!item.StolenDuringRound && GameMain.GameSession?.Campaign?.Map?.CurrentLocation != null)
|
||||
if (!item.StolenDuringRound &&
|
||||
Level.Loaded?.Type == LevelData.LevelType.Outpost &&
|
||||
GameMain.GameSession?.Campaign?.Map?.CurrentLocation != null)
|
||||
{
|
||||
var reputationLoss = MathHelper.Clamp(
|
||||
(item.Prefab.GetMinPrice() ?? 0) * Reputation.ReputationLossPerStolenItemPrice,
|
||||
Reputation.MinReputationLossPerStolenItem, Reputation.MaxReputationLossPerStolenItem);
|
||||
GameMain.GameSession.Campaign.Map.CurrentLocation.Reputation.AddReputation(-reputationLoss);
|
||||
GameMain.GameSession.Campaign.Map.CurrentLocation.Reputation?.AddReputation(-reputationLoss);
|
||||
}
|
||||
item.StolenDuringRound = true;
|
||||
otherCharacter.Speak(TextManager.Get("dialogstealwarning").Value, null, Rand.Range(0.5f, 1.0f), "thief".ToIdentifier(), 10.0f);
|
||||
@@ -1843,7 +1875,7 @@ namespace Barotrauma
|
||||
}
|
||||
break;
|
||||
case "reportbrokendevices":
|
||||
foreach (var item in Item.ItemList)
|
||||
foreach (var item in Item.RepairableItems)
|
||||
{
|
||||
if (item.CurrentHull != hull) { continue; }
|
||||
if (AIObjectiveRepairItems.IsValidTarget(item, character))
|
||||
@@ -1924,11 +1956,12 @@ namespace Barotrauma
|
||||
bool isCurrentHull = character == Character && character.CurrentHull == hull;
|
||||
if (hull == null)
|
||||
{
|
||||
float hullSafety = character.IsProtectedFromPressure ? 0 : 100;
|
||||
if (isCurrentHull)
|
||||
{
|
||||
CurrentHullSafety = character.NeedsAir ? 0 : 100;
|
||||
CurrentHullSafety = hullSafety;
|
||||
}
|
||||
return CurrentHullSafety;
|
||||
return hullSafety;
|
||||
}
|
||||
if (isCurrentHull && visibleHulls == null)
|
||||
{
|
||||
@@ -1936,10 +1969,9 @@ namespace Barotrauma
|
||||
visibleHulls = VisibleHulls;
|
||||
}
|
||||
bool ignoreFire = objectiveManager.CurrentOrder is AIObjectiveExtinguishFires extinguishOrder && extinguishOrder.Priority > 0 || objectiveManager.HasActiveObjective<AIObjectiveExtinguishFire>();
|
||||
bool ignoreWater = character.IsProtectedFromPressure();
|
||||
bool ignoreOxygen = HasDivingGear(character);
|
||||
bool ignoreOxygen = HasDivingGear(character);
|
||||
bool ignoreEnemies = ObjectiveManager.IsCurrentOrder<AIObjectiveFightIntruders>() || ObjectiveManager.IsCurrentObjective<AIObjectiveFightIntruders>();
|
||||
float safety = CalculateHullSafety(hull, visibleHulls, character, ignoreWater, ignoreOxygen, ignoreFire, ignoreEnemies);
|
||||
float safety = CalculateHullSafety(hull, visibleHulls, character, ignoreWater: false, ignoreOxygen, ignoreFire, ignoreEnemies);
|
||||
if (isCurrentHull)
|
||||
{
|
||||
CurrentHullSafety = safety;
|
||||
@@ -1949,15 +1981,33 @@ namespace Barotrauma
|
||||
|
||||
private static float CalculateHullSafety(Hull hull, IEnumerable<Hull> visibleHulls, Character character, bool ignoreWater = false, bool ignoreOxygen = false, bool ignoreFire = false, bool ignoreEnemies = false)
|
||||
{
|
||||
if (hull == null) { return character.NeedsAir ? 0 : 100; }
|
||||
if (hull.LethalPressure > 0 && character.PressureProtection <= 0 && !character.HasAbilityFlag(AbilityFlags.ImmuneToPressure)) { return 0; }
|
||||
bool isProtectedFromPressure = character.IsProtectedFromPressure;
|
||||
if (hull == null) { return isProtectedFromPressure ? 100 : 0; }
|
||||
if (hull.LethalPressure > 0 && !isProtectedFromPressure) { return 0; }
|
||||
// Oxygen factor should be 1 with 70% oxygen or more and 0.1 when the oxygen level is 30% or lower.
|
||||
// With insufficient oxygen, the safety of the hull should be 39, all the other factors aside. So, just below the HULL_SAFETY_THRESHOLD.
|
||||
float oxygenFactor = ignoreOxygen ? 1 : MathHelper.Lerp((HULL_SAFETY_THRESHOLD - 1) / 100, 1, MathUtils.InverseLerp(HULL_LOW_OXYGEN_PERCENTAGE, 100 - HULL_LOW_OXYGEN_PERCENTAGE, hull.OxygenPercentage));
|
||||
float waterFactor = ignoreWater ? 1 : MathHelper.Lerp(1, HULL_SAFETY_THRESHOLD / 2 / 100, hull.WaterPercentage / 100);
|
||||
if (!character.NeedsAir)
|
||||
float waterFactor = 1;
|
||||
if (!ignoreWater)
|
||||
{
|
||||
if (visibleHulls != null)
|
||||
{
|
||||
// Take the visible hulls into account too, because otherwise multi-hull rooms on several floors (with platforms) will yield unexpected results.
|
||||
float relativeWaterVolume = visibleHulls.Sum(s => s.WaterVolume) / visibleHulls.Sum(s => s.Volume);
|
||||
waterFactor = MathHelper.Lerp(1, HULL_SAFETY_THRESHOLD / 2 / 100, relativeWaterVolume);
|
||||
}
|
||||
else
|
||||
{
|
||||
float relativeWaterVolume = hull.WaterVolume / hull.Volume;
|
||||
waterFactor = MathHelper.Lerp(1, HULL_SAFETY_THRESHOLD / 2 / 100, relativeWaterVolume);
|
||||
}
|
||||
}
|
||||
if (!character.NeedsOxygen || character.CharacterHealth.OxygenLowResistance >= 1)
|
||||
{
|
||||
oxygenFactor = 1;
|
||||
}
|
||||
if (isProtectedFromPressure)
|
||||
{
|
||||
waterFactor = 1;
|
||||
}
|
||||
float fireFactor = 1;
|
||||
@@ -2005,6 +2055,10 @@ namespace Barotrauma
|
||||
|
||||
public float GetHullSafety(Hull hull, Character character, IEnumerable<Hull> visibleHulls = null)
|
||||
{
|
||||
if (hull == null)
|
||||
{
|
||||
return CalculateHullSafety(hull, character, visibleHulls);
|
||||
}
|
||||
if (!knownHulls.TryGetValue(hull, out HullSafety hullSafety))
|
||||
{
|
||||
hullSafety = new HullSafety(CalculateHullSafety(hull, character, visibleHulls));
|
||||
@@ -2019,6 +2073,10 @@ namespace Barotrauma
|
||||
|
||||
public static float GetHullSafety(Hull hull, IEnumerable<Hull> visibleHulls, Character character, bool ignoreWater = false, bool ignoreOxygen = false, bool ignoreFire = false, bool ignoreEnemies = false)
|
||||
{
|
||||
if (hull == null)
|
||||
{
|
||||
return CalculateHullSafety(hull, visibleHulls, character, ignoreWater, ignoreOxygen, ignoreFire, ignoreEnemies);
|
||||
}
|
||||
HullSafety hullSafety;
|
||||
if (character.AIController is HumanAIController controller)
|
||||
{
|
||||
@@ -2047,21 +2105,53 @@ namespace Barotrauma
|
||||
bool sameTeam = me.TeamID == other.TeamID;
|
||||
bool teamGood = sameTeam || !onlySameTeam && me.IsOnFriendlyTeam(other);
|
||||
if (!teamGood) { return false; }
|
||||
if (!me.IsSameSpeciesOrGroup(other)) { return false; }
|
||||
if (me.TeamID == CharacterTeamType.FriendlyNPC && other.TeamID == CharacterTeamType.Team1 && GameMain.GameSession?.GameMode is CampaignMode campaign)
|
||||
if (other.IsPet)
|
||||
{
|
||||
// Hostile NPCs are hostile to all pets, unless they are in the same team.
|
||||
if (!sameTeam && me.TeamID == CharacterTeamType.None) { return false; }
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!me.IsSameSpeciesOrGroup(other)) { return false; }
|
||||
}
|
||||
if (GameMain.GameSession?.GameMode is CampaignMode)
|
||||
{
|
||||
if ((me.TeamID == CharacterTeamType.FriendlyNPC && other.TeamID == CharacterTeamType.Team1) ||
|
||||
(me.TeamID == CharacterTeamType.Team1 && other.TeamID == CharacterTeamType.FriendlyNPC))
|
||||
{
|
||||
Character npc = me.TeamID == CharacterTeamType.FriendlyNPC ? me : other;
|
||||
//NPCs that allow some campaign interaction are not turned hostile by low reputation
|
||||
if (npc.CampaignInteractionType != CampaignMode.InteractionType.None) { return true; }
|
||||
if (!npc.IsEscorted && npc.AIController is HumanAIController npcAI)
|
||||
{
|
||||
return !npcAI.IsInHostileFaction();
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool IsInHostileFaction()
|
||||
{
|
||||
if (GameMain.GameSession?.GameMode is not CampaignMode campaign) { return false; }
|
||||
|
||||
Identifier npcFaction = Character.Faction;
|
||||
Identifier currentLocationFaction = campaign.Map?.CurrentLocation?.Faction?.Prefab.Identifier ?? Identifier.Empty;
|
||||
|
||||
if (npcFaction.IsEmpty)
|
||||
{
|
||||
//if faction identifier is not specified, assume the NPC is a member of the faction that owns the outpost
|
||||
npcFaction = currentLocationFaction;
|
||||
}
|
||||
if (!currentLocationFaction.IsEmpty && npcFaction == currentLocationFaction)
|
||||
{
|
||||
var reputation = campaign.Map?.CurrentLocation?.Reputation;
|
||||
if (reputation != null && reputation.NormalizedValue < Reputation.HostileThreshold)
|
||||
{
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (!sameTeam && me.TeamID == CharacterTeamType.None && other.IsPet)
|
||||
{
|
||||
// Hostile NPCs are hostile to all pets, unless they are in the same team.
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool IsActive(Character other) => other != null && !other.Removed && !other.IsDead && !other.IsUnconscious;
|
||||
|
||||
@@ -26,7 +26,7 @@ namespace Barotrauma
|
||||
|
||||
private float findPathTimer;
|
||||
|
||||
private const float buttonPressCooldown = 3;
|
||||
private const float ButtonPressCooldown = 1;
|
||||
private float checkDoorsTimer;
|
||||
private float buttonPressTimer;
|
||||
|
||||
@@ -96,7 +96,7 @@ namespace Barotrauma
|
||||
base.Update(speed);
|
||||
float step = 1.0f / 60.0f;
|
||||
checkDoorsTimer -= step;
|
||||
if (lastDoor.door == null || !lastDoor.shouldBeOpen || lastDoor.door.IsOpen)
|
||||
if (lastDoor.door == null || !lastDoor.shouldBeOpen || lastDoor.door.IsFullyOpen)
|
||||
{
|
||||
buttonPressTimer = 0;
|
||||
}
|
||||
@@ -211,7 +211,7 @@ namespace Barotrauma
|
||||
currentTarget = target;
|
||||
Vector2 currentPos = host.SimPosition;
|
||||
pathFinder.InsideSubmarine = character.Submarine != null && !character.Submarine.Info.IsRuin;
|
||||
pathFinder.ApplyPenaltyToOutsideNodes = character.Submarine != null && character.PressureProtection <= 0;
|
||||
pathFinder.ApplyPenaltyToOutsideNodes = character.Submarine != null && !character.IsProtectedFromPressure;
|
||||
var newPath = pathFinder.FindPath(currentPos, target, character.Submarine, "(Character: " + character.Name + ")", minGapSize, startNodeFilter, endNodeFilter, nodeFilter, checkVisibility: checkVisibility);
|
||||
bool useNewPath = needsNewPath || currentPath == null || currentPath.CurrentNode == null || character.Submarine != null && findPathTimer < -1 && Math.Abs(character.AnimController.TargetMovement.Combine()) <= 0;
|
||||
if (!useNewPath && currentPath?.CurrentNode != null && newPath.Nodes.Any() && !newPath.Unreachable)
|
||||
@@ -310,7 +310,7 @@ namespace Barotrauma
|
||||
// Only humanoids can climb ladders
|
||||
bool canClimb = character.AnimController is HumanoidAnimController;
|
||||
//if not in water and the waypoint is between the top and bottom of the collider, no need to move vertically
|
||||
if (canClimb && !character.AnimController.InWater && !character.IsClimbing && diff.Y < collider.height / 2 + collider.radius)
|
||||
if (canClimb && !character.AnimController.InWater && !character.IsClimbing && diff.Y < collider.Height / 2 + collider.Radius)
|
||||
{
|
||||
diff.Y = 0.0f;
|
||||
}
|
||||
@@ -342,7 +342,7 @@ namespace Barotrauma
|
||||
CheckDoorsInPath();
|
||||
doorsChecked = true;
|
||||
}
|
||||
if (buttonPressTimer > 0 && lastDoor.door != null && lastDoor.shouldBeOpen && !lastDoor.door.IsOpen)
|
||||
if (buttonPressTimer > 0 && lastDoor.door != null && lastDoor.shouldBeOpen && !lastDoor.door.IsFullyOpen)
|
||||
{
|
||||
// We have pressed the button and are waiting for the door to open -> Hold still until we can press the button again.
|
||||
Reset();
|
||||
@@ -395,7 +395,7 @@ namespace Barotrauma
|
||||
}
|
||||
//at the same height as the waypoint
|
||||
float heightDiff = Math.Abs(collider.SimPosition.Y - currentPath.CurrentNode.SimPosition.Y);
|
||||
float colliderSize = (collider.height / 2 + collider.radius) * 1.25f;
|
||||
float colliderSize = (collider.Height / 2 + collider.Radius) * 1.25f;
|
||||
if (heightDiff < colliderSize)
|
||||
{
|
||||
float heightFromFloor = character.AnimController.GetHeightFromFloor();
|
||||
@@ -510,7 +510,7 @@ namespace Barotrauma
|
||||
private bool CanAccessDoor(Door door, Func<Controller, bool> buttonFilter = null)
|
||||
{
|
||||
if (door.IsBroken) { return true; }
|
||||
if (!door.IsOpen)
|
||||
if (door.IsClosed)
|
||||
{
|
||||
if (!door.Item.IsInteractable(character)) { return false; }
|
||||
if (!ShouldBreakDoor(door))
|
||||
@@ -536,7 +536,7 @@ namespace Barotrauma
|
||||
}
|
||||
foreach (var linked in door.Item.linkedTo)
|
||||
{
|
||||
if (!(linked is Item linkedItem)) { continue; }
|
||||
if (linked is not Item linkedItem) { continue; }
|
||||
var button = linkedItem.GetComponent<Controller>();
|
||||
if (button == null) { continue; }
|
||||
if (button.HasAccess(character) && (buttonFilter == null || buttonFilter(button)))
|
||||
@@ -694,7 +694,7 @@ namespace Barotrauma
|
||||
if (door.Item.TryInteract(character, forceSelectKey: true))
|
||||
{
|
||||
lastDoor = (door, shouldBeOpen);
|
||||
buttonPressTimer = shouldBeOpen ? buttonPressCooldown : 0;
|
||||
buttonPressTimer = shouldBeOpen ? ButtonPressCooldown : 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -712,7 +712,7 @@ namespace Barotrauma
|
||||
if (closestButton.Item.TryInteract(character, forceSelectKey: true))
|
||||
{
|
||||
lastDoor = (door, shouldBeOpen);
|
||||
buttonPressTimer = shouldBeOpen ? buttonPressCooldown : 0;
|
||||
buttonPressTimer = shouldBeOpen ? ButtonPressCooldown : 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -785,7 +785,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (hull.WaterVolume / hull.Rect.Width > 100.0f)
|
||||
{
|
||||
if (!HumanAIController.HasDivingSuit(character))
|
||||
if (!HumanAIController.HasDivingSuit(character) && character.CharacterHealth.OxygenLowResistance < 1)
|
||||
{
|
||||
penalty += 500.0f;
|
||||
}
|
||||
@@ -808,7 +808,7 @@ namespace Barotrauma
|
||||
|
||||
private float? GetSingleNodePenalty(PathNode node)
|
||||
{
|
||||
if (node.Waypoint.isObstructed) { return null; }
|
||||
if (!node.Waypoint.IsTraversable) { return null; }
|
||||
if (node.IsBlocked()) { return null; }
|
||||
float penalty = 0.0f;
|
||||
if (node.Waypoint.ConnectedGap != null && node.Waypoint.ConnectedGap.Open < 0.9f)
|
||||
|
||||
@@ -244,7 +244,7 @@ namespace Barotrauma
|
||||
else
|
||||
{
|
||||
float squaredDistance = Vector2.DistanceSquared(character.SimPosition, _attachPos);
|
||||
float targetDistance = Math.Max(Math.Max(character.AnimController.Collider.radius, character.AnimController.Collider.width), character.AnimController.Collider.height) * 1.2f;
|
||||
float targetDistance = Math.Max(Math.Max(character.AnimController.Collider.Radius, character.AnimController.Collider.Width), character.AnimController.Collider.Height) * 1.2f;
|
||||
if (squaredDistance < targetDistance * targetDistance)
|
||||
{
|
||||
//close enough to a wall -> attach
|
||||
|
||||
@@ -107,7 +107,6 @@ namespace Barotrauma
|
||||
{
|
||||
return MentalType.Normal;
|
||||
}
|
||||
// test this later
|
||||
int psychosisIndex = (int)(affliction.Strength / (affliction.Prefab.MaxStrength / MentalTypeCount) * Rand.Range(1f, 1.2f));
|
||||
psychosisIndex = Math.Clamp(psychosisIndex, 0, 4);
|
||||
MentalType mentalType = psychosisIndex switch
|
||||
|
||||
@@ -83,11 +83,16 @@ namespace Barotrauma
|
||||
{
|
||||
if (GameMain.GameSession.RoundDuration < 120.0f &&
|
||||
speaker?.CurrentHull != null &&
|
||||
GameMain.GameSession.Map?.CurrentLocation?.Reputation?.Value >= 0.0f &&
|
||||
(speaker.TeamID == CharacterTeamType.FriendlyNPC || speaker.TeamID == CharacterTeamType.None) &&
|
||||
Character.CharacterList.Any(c => c.TeamID != speaker.TeamID && c.CurrentHull == speaker.CurrentHull))
|
||||
{
|
||||
currentFlags.Add("EnterOutpost".ToIdentifier());
|
||||
}
|
||||
if (Level.Loaded.IsEndBiome)
|
||||
{
|
||||
currentFlags.Add("EndLevel".ToIdentifier());
|
||||
}
|
||||
}
|
||||
if (GameMain.GameSession.EventManager.CurrentIntensity <= 0.2f)
|
||||
{
|
||||
@@ -117,7 +122,7 @@ namespace Barotrauma
|
||||
foreach (Affliction affliction in afflictions)
|
||||
{
|
||||
var currentEffect = affliction.GetActiveEffect();
|
||||
if (currentEffect != null && !string.IsNullOrEmpty(currentEffect.DialogFlag.Value) && !currentFlags.Contains(currentEffect.DialogFlag))
|
||||
if (currentEffect is { DialogFlag.IsEmpty: false } && !currentFlags.Contains(currentEffect.DialogFlag))
|
||||
{
|
||||
currentFlags.Add(currentEffect.DialogFlag);
|
||||
}
|
||||
@@ -126,6 +131,10 @@ namespace Barotrauma
|
||||
if (speaker.TeamID == CharacterTeamType.FriendlyNPC && speaker.Submarine != null && speaker.Submarine.Info.IsOutpost)
|
||||
{
|
||||
currentFlags.Add("OutpostNPC".ToIdentifier());
|
||||
if (GameMain.GameSession?.Level?.StartLocation?.Faction is Faction faction)
|
||||
{
|
||||
currentFlags.Add($"OutpostNPC{faction.Prefab.Identifier}".ToIdentifier());
|
||||
}
|
||||
}
|
||||
if (speaker.CampaignInteractionType != CampaignMode.InteractionType.None)
|
||||
{
|
||||
|
||||
@@ -256,7 +256,9 @@ namespace Barotrauma
|
||||
if (!AllowOutsideSubmarine && character.Submarine == null) { return false; }
|
||||
if (AllowInAnySub) { return true; }
|
||||
if ((AllowInFriendlySubs && character.Submarine.TeamID == CharacterTeamType.FriendlyNPC) || character.IsEscorted) { return true; }
|
||||
return character.Submarine.TeamID == character.TeamID || character.Submarine.DockedTo.Any(sub => sub.TeamID == character.TeamID);
|
||||
return character.Submarine.TeamID == character.TeamID ||
|
||||
character.Submarine.TeamID == character.OriginalTeamID ||
|
||||
character.Submarine.DockedTo.Any(sub => sub.TeamID == character.TeamID || sub.TeamID == character.OriginalTeamID);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+11
-10
@@ -648,11 +648,11 @@ namespace Barotrauma
|
||||
{
|
||||
statusEffects = statusEffects.Concat(hitEffects);
|
||||
}
|
||||
float afflictionsStun = attack.Afflictions.Keys.Sum(a => a.Identifier == "stun" ? a.Strength : 0);
|
||||
float afflictionsStun = attack.Afflictions.Keys.Sum(a => a.Identifier == AfflictionPrefab.StunType ? a.Strength : 0);
|
||||
float effectsStun = statusEffects.None() ? 0 : statusEffects.Max(se =>
|
||||
{
|
||||
float stunAmount = 0;
|
||||
var stunAffliction = se.Afflictions.Find(a => a.Identifier == "stun");
|
||||
var stunAffliction = se.Afflictions.Find(a => a.Identifier == AfflictionPrefab.StunType);
|
||||
if (stunAffliction != null)
|
||||
{
|
||||
stunAmount = stunAffliction.Strength;
|
||||
@@ -1176,30 +1176,31 @@ namespace Barotrauma
|
||||
if (sqrDistance > repairTool.Range * repairTool.Range) { return; }
|
||||
}
|
||||
float aimFactor = MathHelper.PiOver2 * (1 - AimAccuracy);
|
||||
if (VectorExtensions.Angle(VectorExtensions.Forward(Weapon.body.TransformedRotation), Enemy.Position - Weapon.Position) < MathHelper.PiOver4 + aimFactor)
|
||||
if (VectorExtensions.Angle(VectorExtensions.Forward(Weapon.body.TransformedRotation), Enemy.WorldPosition - Weapon.WorldPosition) < MathHelper.PiOver4 + aimFactor)
|
||||
{
|
||||
if (myBodies == null)
|
||||
{
|
||||
myBodies = character.AnimController.Limbs.Select(l => l.body.FarseerBody);
|
||||
}
|
||||
var collisionCategories = Physics.CollisionCharacter | Physics.CollisionWall | Physics.CollisionLevel;
|
||||
var pickedBody = Submarine.PickBody(Weapon.SimPosition, Enemy.SimPosition, myBodies, collisionCategories, allowInsideFixture: true);
|
||||
if (pickedBody != null)
|
||||
// Check that we don't hit friendlies. No need to check the walls, because there's a separate check for that at 1096 (which intentionally has a small delay)
|
||||
var pickedBodies = Submarine.PickBodies(Weapon.SimPosition, Character.GetRelativeSimPosition(from: Weapon, to: Enemy), myBodies, Physics.CollisionCharacter);
|
||||
foreach (var body in pickedBodies)
|
||||
{
|
||||
Character target = null;
|
||||
if (pickedBody.UserData is Character c)
|
||||
if (body.UserData is Character c)
|
||||
{
|
||||
target = c;
|
||||
}
|
||||
else if (pickedBody.UserData is Limb limb)
|
||||
else if (body.UserData is Limb limb)
|
||||
{
|
||||
target = limb.character;
|
||||
}
|
||||
if (target != null && (target == Enemy || !HumanAIController.IsFriendly(target)))
|
||||
if (target != null && target != Enemy && HumanAIController.IsFriendly(target))
|
||||
{
|
||||
UseWeapon(deltaTime);
|
||||
return;
|
||||
}
|
||||
}
|
||||
UseWeapon(deltaTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+3
-1
@@ -4,6 +4,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -200,7 +201,8 @@ namespace Barotrauma
|
||||
(container.Item.GetRootContainer()?.OwnInventory?.Locked ?? false) ||
|
||||
ItemToContain == null || ItemToContain.Removed ||
|
||||
!ItemToContain.IsOwnedBy(character) || container.Item.GetRootInventoryOwner() is Character c && c != character,
|
||||
SpeakIfFails = !objectiveManager.IsCurrentOrder<AIObjectiveCleanupItems>()
|
||||
SpeakIfFails = !objectiveManager.IsCurrentOrder<AIObjectiveCleanupItems>(),
|
||||
endNodeFilter = n => Vector2.DistanceSquared(n.Waypoint.WorldPosition, container.Item.WorldPosition) <= MathUtils.Pow2(AIObjectiveGetItem.DefaultReach)
|
||||
},
|
||||
onAbandon: () => Abandon = true,
|
||||
onCompleted: () => RemoveSubObjective(ref goToObjective));
|
||||
|
||||
+39
-29
@@ -19,7 +19,6 @@ namespace Barotrauma
|
||||
|
||||
private AIObjectiveGetItem getExtinguisherObjective;
|
||||
private AIObjectiveGoTo gotoObjective;
|
||||
private float useExtinquisherTimer;
|
||||
|
||||
public AIObjectiveExtinguishFire(Character character, Hull targetHull, AIObjectiveManager objectiveManager, float priorityModifier = 1)
|
||||
: base(character, objectiveManager, priorityModifier)
|
||||
@@ -44,7 +43,8 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
float yDist = Math.Abs(character.WorldPosition.Y - targetHull.WorldPosition.Y);
|
||||
float characterY = character.CurrentHull?.WorldPosition.Y ?? character.WorldPosition.Y;
|
||||
float yDist = Math.Abs(characterY - targetHull.WorldPosition.Y);
|
||||
yDist = yDist > 100 ? yDist * 3 : 0;
|
||||
float dist = Math.Abs(character.WorldPosition.X - targetHull.WorldPosition.X) + yDist;
|
||||
float distanceFactor = MathHelper.Lerp(1, 0.1f, MathUtils.InverseLerp(0, 5000, dist));
|
||||
@@ -119,24 +119,18 @@ namespace Barotrauma
|
||||
Abandon = true;
|
||||
break;
|
||||
}
|
||||
float xDist = Math.Abs(character.WorldPosition.X - fs.WorldPosition.X) - fs.DamageRange;
|
||||
float yDist = Math.Abs(character.WorldPosition.Y - fs.WorldPosition.Y);
|
||||
bool inRange = xDist + yDist < extinguisher.Range;
|
||||
// Use the hull position, because the fire x pos is sometimes inside a wall -> the bot can't ever see it and continues running towards the wall.
|
||||
ISpatialEntity lookTarget = character.CurrentHull == targetHull || character.CurrentHull.linkedTo.Contains(targetHull) ? targetHull : fs as ISpatialEntity;
|
||||
bool move = !inRange || !character.CanSeeTarget(lookTarget);
|
||||
if ((inRange && character.CanSeeTarget(lookTarget)) || useExtinquisherTimer > 0)
|
||||
float xDist = Math.Abs(character.WorldPosition.X - fs.WorldPosition.X);
|
||||
float yDist = Math.Abs(character.CurrentHull.WorldPosition.Y - targetHull.WorldPosition.Y);
|
||||
float dist = xDist + yDist;
|
||||
bool inRange = dist < extinguisher.Range;
|
||||
bool isInDamageRange = fs.IsInDamageRange(character, fs.DamageRange) && character.CanSeeTarget(targetHull);
|
||||
bool moveCloser = !isInDamageRange && (!inRange || !character.CanSeeTarget(targetHull));
|
||||
bool operateExtinguisher = !moveCloser || (dist < extinguisher.Range * 1.2f && character.CanSeeTarget(targetHull));
|
||||
if (operateExtinguisher)
|
||||
{
|
||||
useExtinquisherTimer += deltaTime;
|
||||
if (useExtinquisherTimer > 2.0f)
|
||||
{
|
||||
useExtinquisherTimer = 0.0f;
|
||||
}
|
||||
// Aim
|
||||
character.CursorPosition = fs.Position;
|
||||
Vector2 fromCharacterToFireSource = fs.WorldPosition - character.WorldPosition;
|
||||
float dist = fromCharacterToFireSource.Length();
|
||||
character.CursorPosition += VectorExtensions.Forward(extinguisherItem.body.TransformedRotation + (float)Math.Sin(sinTime) / 2, dist / 2);
|
||||
character.CursorPosition += VectorExtensions.Forward(extinguisherItem.body.TransformedRotation + (float)Math.Sin(sinTime) / 2, fromCharacterToFireSource.Length() / 2);
|
||||
if (extinguisherItem.RequireAimToUse)
|
||||
{
|
||||
character.SetInput(InputType.Aim, false, true);
|
||||
@@ -148,25 +142,29 @@ namespace Barotrauma
|
||||
{
|
||||
character.Speak(TextManager.GetWithVariable("DialogPutOutFire", "[roomname]", targetHull.DisplayName, FormatCapitals.Yes).Value, null, 0, "putoutfire".ToIdentifier(), 10.0f);
|
||||
}
|
||||
// Prevents running into the flames.
|
||||
objectiveManager.CurrentObjective.ForceWalk = true;
|
||||
}
|
||||
if (move)
|
||||
if (moveCloser)
|
||||
{
|
||||
//go to the first firesource
|
||||
if (TryAddSubObjective(ref gotoObjective, () => new AIObjectiveGoTo(fs, character, objectiveManager, closeEnough: Math.Max(fs.DamageRange, extinguisher.Range * 0.7f))
|
||||
{
|
||||
DialogueIdentifier = "dialogcannotreachfire".ToIdentifier(),
|
||||
TargetName = fs.Hull.DisplayName
|
||||
},
|
||||
onAbandon: () => Abandon = true,
|
||||
onCompleted: () => RemoveSubObjective(ref gotoObjective)))
|
||||
if (TryAddSubObjective(ref gotoObjective, () => new AIObjectiveGoTo(fs, character, objectiveManager, closeEnough: extinguisher.Range * 0.8f)
|
||||
{
|
||||
DialogueIdentifier = "dialogcannotreachfire".ToIdentifier(),
|
||||
TargetName = fs.Hull.DisplayName,
|
||||
},
|
||||
onAbandon: () => Abandon = true,
|
||||
onCompleted: () => RemoveSubObjective(ref gotoObjective)))
|
||||
{
|
||||
gotoObjective.requiredCondition = () => character.CanSeeTarget(targetHull);
|
||||
}
|
||||
}
|
||||
else
|
||||
else if (!operateExtinguisher || isInDamageRange)
|
||||
{
|
||||
character.AIController.SteeringManager.Reset();
|
||||
// Don't walk into the flames.
|
||||
RemoveSubObjective(ref gotoObjective);
|
||||
SteeringManager.Reset();
|
||||
}
|
||||
// Only target one fire source at the time.
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -177,8 +175,20 @@ namespace Barotrauma
|
||||
base.Reset();
|
||||
getExtinguisherObjective = null;
|
||||
gotoObjective = null;
|
||||
useExtinquisherTimer = 0;
|
||||
sinTime = 0;
|
||||
SteeringManager.Reset();
|
||||
}
|
||||
|
||||
protected override void OnCompleted()
|
||||
{
|
||||
base.OnCompleted();
|
||||
SteeringManager.Reset();
|
||||
}
|
||||
|
||||
protected override void OnAbandon()
|
||||
{
|
||||
base.OnAbandon();
|
||||
SteeringManager.Reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+8
-2
@@ -24,7 +24,7 @@ namespace Barotrauma
|
||||
protected override float TargetEvaluation()
|
||||
{
|
||||
if (Targets.None()) { return 0; }
|
||||
if (!character.IsOnPlayerTeam) { return 100; }
|
||||
if (!character.IsOnPlayerTeam && !character.IsOriginallyOnPlayerTeam) { return 100; }
|
||||
if (character.IsSecurity) { return 100; }
|
||||
if (objectiveManager.IsOrder(this)) { return 100; }
|
||||
// If there's any security officers onboard, leave fighting for them.
|
||||
@@ -66,7 +66,13 @@ namespace Barotrauma
|
||||
if (target.CurrentHull == null) { return false; }
|
||||
if (HumanAIController.IsFriendly(character, target)) { return false; }
|
||||
if (!character.Submarine.IsConnectedTo(target.Submarine)) { return false; }
|
||||
if (!targetCharactersInOtherSubs && character.Submarine.TeamID != target.Submarine.TeamID) { return false; }
|
||||
if (!targetCharactersInOtherSubs)
|
||||
{
|
||||
if (character.Submarine.TeamID != target.Submarine.TeamID && character.OriginalTeamID != target.Submarine.TeamID)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (target.HasAbilityFlag(AbilityFlags.IgnoredByEnemyAI)) { return false; }
|
||||
if (target.IsArrested) { return false; }
|
||||
if (EnemyAIController.IsLatchedToSomeoneElse(target, character)) { return false; }
|
||||
|
||||
+24
-19
@@ -47,19 +47,12 @@ namespace Barotrauma
|
||||
}
|
||||
if (character.CurrentHull == null)
|
||||
{
|
||||
if (!character.NeedsAir)
|
||||
{
|
||||
Priority = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
Priority = (
|
||||
objectiveManager.HasOrder<AIObjectiveGoTo>(o => o.Priority > 0) ||
|
||||
objectiveManager.HasOrder<AIObjectiveReturn>(o => o.Priority > 0) ||
|
||||
objectiveManager.HasActiveObjective<AIObjectiveRescue>() ||
|
||||
objectiveManager.Objectives.Any(o => o is AIObjectiveCombat && o.Priority > 0))
|
||||
&& HumanAIController.HasDivingSuit(character) ? 0 : 100;
|
||||
}
|
||||
Priority = (
|
||||
objectiveManager.HasOrder<AIObjectiveGoTo>(o => o.Priority > 0) ||
|
||||
objectiveManager.HasOrder<AIObjectiveReturn>(o => o.Priority > 0) ||
|
||||
objectiveManager.HasActiveObjective<AIObjectiveRescue>() ||
|
||||
objectiveManager.Objectives.Any(o => o is AIObjectiveCombat && o.Priority > 0))
|
||||
&& ((character.IsImmuneToPressure && !character.IsLowInOxygen)|| HumanAIController.HasDivingSuit(character)) ? 0 : 100;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -118,6 +111,11 @@ namespace Barotrauma
|
||||
if (currenthullSafety > HumanAIController.HULL_SAFETY_THRESHOLD)
|
||||
{
|
||||
Priority -= priorityDecrease * deltaTime;
|
||||
if (currenthullSafety >= 100)
|
||||
{
|
||||
// Reduce the priority to zero so that the bot can get switch to other objectives immediately, e.g. when entering the airlock.
|
||||
Priority = 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -140,8 +138,8 @@ namespace Barotrauma
|
||||
{
|
||||
if (resetPriority) { return; }
|
||||
var currentHull = character.CurrentHull;
|
||||
bool dangerousPressure = !character.IsProtectedFromPressure && (currentHull == null || currentHull.LethalPressure > 0);
|
||||
bool shouldActOnSuffocation = character.IsLowInOxygen && !character.AnimController.HeadInWater && HumanAIController.HasDivingSuit(character, requireOxygenTank: false);
|
||||
bool dangerousPressure = currentHull == null || currentHull.LethalPressure > 0 && character.PressureProtection <= 0;
|
||||
if (!character.LockHands && (!dangerousPressure || shouldActOnSuffocation || cannotFindSafeHull))
|
||||
{
|
||||
bool needsDivingGear = HumanAIController.NeedsDivingGear(currentHull, out bool needsDivingSuit);
|
||||
@@ -221,7 +219,11 @@ namespace Barotrauma
|
||||
TryAddSubObjective(ref goToObjective,
|
||||
constructor: () => new AIObjectiveGoTo(currentSafeHull, character, objectiveManager, getDivingGearIfNeeded: true)
|
||||
{
|
||||
AllowGoingOutside = HumanAIController.HasDivingSuit(character, conditionPercentage: 50)
|
||||
AllowGoingOutside =
|
||||
character.IsProtectedFromPressure ||
|
||||
character.CurrentHull == null ||
|
||||
character.CurrentHull.IsTaggedAirlock() ||
|
||||
character.CurrentHull.LeadsOutside(character)
|
||||
},
|
||||
onCompleted: () =>
|
||||
{
|
||||
@@ -352,8 +354,8 @@ namespace Barotrauma
|
||||
//tends to make the method much faster, because we find a potential hull earlier and can discard further-away hulls more easily
|
||||
//(for instance, an NPC in an outpost might otherwise go through all the hulls in the main sub first and do tons of expensive
|
||||
//path calculations, only to discard all of them when going through the hulls in the outpost)
|
||||
float hullSuitability = EstimateHullSuitability(character, hull);
|
||||
if (!hulls.Any())
|
||||
float hullSuitability = EstimateHullSuitability(character, hull);
|
||||
if (hulls.None())
|
||||
{
|
||||
hulls.Add(hull);
|
||||
}
|
||||
@@ -448,9 +450,12 @@ namespace Barotrauma
|
||||
{
|
||||
hullSafety = 100;
|
||||
}
|
||||
float characterY = character.CurrentHull?.WorldPosition.Y ?? character.WorldPosition.Y;
|
||||
float yDist = Math.Abs(characterY - potentialHull.WorldPosition.Y);
|
||||
yDist = yDist > 100 ? yDist * 3 : 0;
|
||||
float distance = Math.Abs(character.WorldPosition.X - potentialHull.WorldPosition.X) + yDist;
|
||||
// Huge preference for closer targets
|
||||
float distance = Vector2.DistanceSquared(character.WorldPosition, potentialHull.WorldPosition);
|
||||
float distanceFactor = MathHelper.Lerp(1, 0.2f, MathUtils.InverseLerp(0, MathUtils.Pow(100000, 2), distance));
|
||||
float distanceFactor = MathHelper.Lerp(1, 0.2f, MathUtils.InverseLerp(0, 10000, distance));
|
||||
hullSafety *= distanceFactor;
|
||||
// If the target is not inside a friendly submarine, considerably reduce the hull safety.
|
||||
// Intentionally exclude wrecks from this check
|
||||
|
||||
+14
-10
@@ -155,17 +155,21 @@ namespace Barotrauma
|
||||
bool canOperate = toLeak.LengthSquared() < reach * reach;
|
||||
if (canOperate)
|
||||
{
|
||||
TryAddSubObjective(ref operateObjective, () => new AIObjectiveOperateItem(repairTool, character, objectiveManager, option: Identifier.Empty, requireEquip: true, operateTarget: Leak),
|
||||
onAbandon: () => Abandon = true,
|
||||
onCompleted: () =>
|
||||
TryAddSubObjective(ref operateObjective, () => new AIObjectiveOperateItem(repairTool, character, objectiveManager, option: Identifier.Empty, requireEquip: true, operateTarget: Leak)
|
||||
{
|
||||
// Use an empty filter to override the default
|
||||
EndNodeFilter = n => true
|
||||
},
|
||||
onAbandon: () => Abandon = true,
|
||||
onCompleted: () =>
|
||||
{
|
||||
if (CheckObjectiveSpecific()) { IsCompleted = true; }
|
||||
else
|
||||
{
|
||||
if (CheckObjectiveSpecific()) { IsCompleted = true; }
|
||||
else
|
||||
{
|
||||
// Failed to operate. Probably too far.
|
||||
Abandon = true;
|
||||
}
|
||||
});
|
||||
// Failed to operate. Probably too far.
|
||||
Abandon = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
+18
-7
@@ -123,6 +123,11 @@ namespace Barotrauma
|
||||
return ignoredTags;
|
||||
}
|
||||
|
||||
public static Func<PathNode, bool> CreateEndNodeFilter(ISpatialEntity targetEntity)
|
||||
{
|
||||
return n => (n.Waypoint.Ladders == null || n.Waypoint.IsInWater) && Vector2.DistanceSquared(n.Waypoint.WorldPosition, targetEntity.WorldPosition) <= MathUtils.Pow2(DefaultReach);
|
||||
}
|
||||
|
||||
private bool CheckInventory()
|
||||
{
|
||||
if (IdentifiersOrTags == null) { return false; }
|
||||
@@ -155,11 +160,6 @@ namespace Barotrauma
|
||||
Abandon = true;
|
||||
return;
|
||||
}
|
||||
if (character.Submarine == null)
|
||||
{
|
||||
Abandon = true;
|
||||
return;
|
||||
}
|
||||
if (IdentifiersOrTags != null && !isDoneSeeking)
|
||||
{
|
||||
if (checkInventory)
|
||||
@@ -171,9 +171,14 @@ namespace Barotrauma
|
||||
}
|
||||
if (!isDoneSeeking)
|
||||
{
|
||||
if (character.Submarine == null)
|
||||
{
|
||||
Abandon = true;
|
||||
return;
|
||||
}
|
||||
if (!AllowDangerousPressure)
|
||||
{
|
||||
bool dangerousPressure = character.CurrentHull == null || character.CurrentHull.LethalPressure > 0 && character.PressureProtection <= 0;
|
||||
bool dangerousPressure = !character.IsProtectedFromPressure && (character.CurrentHull == null || character.CurrentHull.LethalPressure > 0);
|
||||
if (dangerousPressure)
|
||||
{
|
||||
#if DEBUG
|
||||
@@ -192,6 +197,11 @@ namespace Barotrauma
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (character.Submarine == null)
|
||||
{
|
||||
Abandon = true;
|
||||
return;
|
||||
}
|
||||
if (targetItem == null || targetItem.Removed)
|
||||
{
|
||||
#if DEBUG
|
||||
@@ -307,7 +317,8 @@ namespace Barotrauma
|
||||
{
|
||||
// If the root container changes, the item is no longer where it was (taken by someone -> need to find another item)
|
||||
AbortCondition = obj => targetItem == null || targetItem.GetRootInventoryOwner() != moveToTarget,
|
||||
SpeakIfFails = false
|
||||
SpeakIfFails = false,
|
||||
endNodeFilter = CreateEndNodeFilter(moveToTarget)
|
||||
};
|
||||
},
|
||||
onAbandon: () =>
|
||||
|
||||
+56
-25
@@ -33,6 +33,11 @@ namespace Barotrauma
|
||||
public bool DebugLogWhenFails { get; set; } = true;
|
||||
public bool UsePathingOutside { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Which event action created this objective (if any)
|
||||
/// </summary>
|
||||
public EventAction SourceEventAction;
|
||||
|
||||
public float ExtraDistanceWhileSwimming;
|
||||
public float ExtraDistanceOutsideSub;
|
||||
private float _closeEnoughMultiplier = 1;
|
||||
@@ -45,6 +50,7 @@ namespace Barotrauma
|
||||
private readonly float minDistance = 50;
|
||||
private readonly float seekGapsInterval = 1;
|
||||
private float seekGapsTimer;
|
||||
private bool cantFindDivingGear;
|
||||
|
||||
/// <summary>
|
||||
/// Display units
|
||||
@@ -85,7 +91,7 @@ namespace Barotrauma
|
||||
/// </summary>
|
||||
public bool UseDistanceRelativeToAimSourcePos { get; set; } = false;
|
||||
|
||||
public override bool AbandonWhenCannotCompleteSubjectives => !repeat;
|
||||
public override bool AbandonWhenCannotCompleteSubjectives => false;
|
||||
|
||||
public override bool AllowOutsideSubmarine => AllowGoingOutside;
|
||||
public override bool AllowInAnySub => true;
|
||||
@@ -258,48 +264,73 @@ namespace Barotrauma
|
||||
}
|
||||
if (!Abandon)
|
||||
{
|
||||
if (getDivingGearIfNeeded && !character.LockHands)
|
||||
if (getDivingGearIfNeeded)
|
||||
{
|
||||
Character followTarget = Target as Character;
|
||||
bool needsDivingSuit = (!isInside || hasOutdoorNodes) && character.NeedsAir && !character.HasAbilityFlag(AbilityFlags.ImmuneToPressure);
|
||||
bool needsDivingGear = needsDivingSuit || HumanAIController.NeedsDivingGear(targetHull, out needsDivingSuit);
|
||||
if (Mimic)
|
||||
bool needsDivingSuit = (!isInside || hasOutdoorNodes) && !character.IsImmuneToPressure;
|
||||
bool tryToGetDivingGear = needsDivingSuit || HumanAIController.NeedsDivingGear(targetHull, out needsDivingSuit);
|
||||
bool tryToGetDivingSuit = needsDivingSuit;
|
||||
if (Mimic && !character.IsImmuneToPressure)
|
||||
{
|
||||
if (HumanAIController.HasDivingSuit(followTarget))
|
||||
{
|
||||
needsDivingGear = true;
|
||||
needsDivingSuit = true;
|
||||
tryToGetDivingGear = true;
|
||||
tryToGetDivingSuit = true;
|
||||
}
|
||||
else if (HumanAIController.HasDivingMask(followTarget))
|
||||
else if (HumanAIController.HasDivingMask(followTarget) && character.CharacterHealth.OxygenLowResistance < 1)
|
||||
{
|
||||
needsDivingGear = true;
|
||||
tryToGetDivingGear = true;
|
||||
}
|
||||
}
|
||||
bool needsEquipment = false;
|
||||
float minOxygen = AIObjectiveFindDivingGear.GetMinOxygen(character);
|
||||
if (needsDivingSuit)
|
||||
if (tryToGetDivingSuit)
|
||||
{
|
||||
needsEquipment = !HumanAIController.HasDivingSuit(character, minOxygen);
|
||||
}
|
||||
else if (needsDivingGear)
|
||||
else if (tryToGetDivingGear)
|
||||
{
|
||||
needsEquipment = !HumanAIController.HasDivingGear(character, minOxygen);
|
||||
}
|
||||
if (needsEquipment)
|
||||
if (character.LockHands)
|
||||
{
|
||||
cantFindDivingGear = true;
|
||||
}
|
||||
if (cantFindDivingGear && needsDivingSuit)
|
||||
{
|
||||
// Don't try to reach the target without a suit because it's lethal.
|
||||
Abandon = true;
|
||||
return;
|
||||
}
|
||||
if (needsEquipment && !cantFindDivingGear)
|
||||
{
|
||||
SteeringManager.Reset();
|
||||
if (findDivingGear != null && !findDivingGear.CanBeCompleted)
|
||||
{
|
||||
TryAddSubObjective(ref findDivingGear, () => new AIObjectiveFindDivingGear(character, needsDivingSuit: false, objectiveManager),
|
||||
onAbandon: () => Abandon = true,
|
||||
onCompleted: () => RemoveSubObjective(ref findDivingGear));
|
||||
}
|
||||
else
|
||||
{
|
||||
TryAddSubObjective(ref findDivingGear, () => new AIObjectiveFindDivingGear(character, needsDivingSuit, objectiveManager),
|
||||
onAbandon: () => Abandon = true,
|
||||
onCompleted: () => RemoveSubObjective(ref findDivingGear));
|
||||
}
|
||||
TryAddSubObjective(ref findDivingGear, () => new AIObjectiveFindDivingGear(character, needsDivingSuit: tryToGetDivingSuit, objectiveManager),
|
||||
onAbandon: () =>
|
||||
{
|
||||
cantFindDivingGear = true;
|
||||
if (needsDivingSuit)
|
||||
{
|
||||
// Shouldn't try to reach the target without a suit, because it's lethal.
|
||||
Abandon = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Try again without requiring the diving suit
|
||||
RemoveSubObjective(ref findDivingGear);
|
||||
TryAddSubObjective(ref findDivingGear, () => new AIObjectiveFindDivingGear(character, needsDivingSuit: false, objectiveManager),
|
||||
onAbandon: () =>
|
||||
{
|
||||
Abandon = character.CurrentHull != null && (objectiveManager.CurrentOrder != this || Target.Submarine == null);
|
||||
RemoveSubObjective(ref findDivingGear);
|
||||
},
|
||||
onCompleted: () =>
|
||||
{
|
||||
RemoveSubObjective(ref findDivingGear);
|
||||
});
|
||||
}
|
||||
},
|
||||
onCompleted: () => RemoveSubObjective(ref findDivingGear));
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -593,7 +624,7 @@ namespace Barotrauma
|
||||
}
|
||||
else if (target is Character c)
|
||||
{
|
||||
return c.CurrentHull;
|
||||
return c.CurrentHull ?? c.AnimController.CurrentHull;
|
||||
}
|
||||
else if (target is Structure structure)
|
||||
{
|
||||
|
||||
+2
-4
@@ -170,7 +170,8 @@ namespace Barotrauma
|
||||
TargetHull = character.CurrentHull;
|
||||
}
|
||||
|
||||
if (behavior == BehaviorType.StayInHull)
|
||||
bool currentTargetIsInvalid = currentTarget == null || IsForbidden(currentTarget) || (PathSteering.CurrentPath != null && PathSteering.CurrentPath.Nodes.Any(n => HumanAIController.UnsafeHulls.Contains(n.CurrentHull)));
|
||||
if (behavior == BehaviorType.StayInHull && !currentTargetIsInvalid)
|
||||
{
|
||||
currentTarget = TargetHull;
|
||||
bool stayInHull = character.CurrentHull == currentTarget && IsSteeringFinished() && !character.IsClimbing;
|
||||
@@ -190,9 +191,6 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
bool currentTargetIsInvalid = currentTarget == null || IsForbidden(currentTarget) ||
|
||||
(PathSteering.CurrentPath != null && PathSteering.CurrentPath.Nodes.Any(n => HumanAIController.UnsafeHulls.Contains(n.CurrentHull)));
|
||||
|
||||
if (currentTarget != null && !currentTargetIsInvalid)
|
||||
{
|
||||
if (character.TeamID == CharacterTeamType.FriendlyNPC && !character.IsEscorted)
|
||||
|
||||
+1
-1
@@ -98,7 +98,7 @@ namespace Barotrauma
|
||||
{
|
||||
foreach (var item in itemContainer.ContainableItems)
|
||||
{
|
||||
if (CheckStatusEffects(item.statusEffects) == CheckStatus.Finished)
|
||||
if (CheckStatusEffects(item.StatusEffects) == CheckStatus.Finished)
|
||||
{
|
||||
return CheckStatus.Finished;
|
||||
}
|
||||
|
||||
+8
-3
@@ -23,6 +23,11 @@ namespace Barotrauma
|
||||
private AIObjectiveGoTo goToObjective;
|
||||
private AIObjectiveGetItem getItemObjective;
|
||||
|
||||
/// <summary>
|
||||
/// If undefined, a default filter will be used.
|
||||
/// </summary>
|
||||
public Func<PathNode, bool> EndNodeFilter;
|
||||
|
||||
public bool Override { get; set; } = true;
|
||||
|
||||
public override bool CanBeCompleted => base.CanBeCompleted && (!useController || controller != null);
|
||||
@@ -222,7 +227,7 @@ namespace Barotrauma
|
||||
{
|
||||
target.Item.TryInteract(character, forceSelectKey: true);
|
||||
}
|
||||
if (component.AIOperate(deltaTime, character, this))
|
||||
if (component.CrewAIOperate(deltaTime, character, this))
|
||||
{
|
||||
isDoneOperating = completionCondition == null || completionCondition();
|
||||
}
|
||||
@@ -232,7 +237,7 @@ namespace Barotrauma
|
||||
TryAddSubObjective(ref goToObjective, () => new AIObjectiveGoTo(target.Item, character, objectiveManager, closeEnough: 50)
|
||||
{
|
||||
TargetName = target.Item.Name,
|
||||
endNodeFilter = node => node.Waypoint.Ladders == null
|
||||
endNodeFilter = EndNodeFilter ?? AIObjectiveGetItem.CreateEndNodeFilter(target.Item)
|
||||
},
|
||||
onAbandon: () => Abandon = true,
|
||||
onCompleted: () => RemoveSubObjective(ref goToObjective));
|
||||
@@ -290,7 +295,7 @@ namespace Barotrauma
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (component.AIOperate(deltaTime, character, this))
|
||||
if (component.CrewAIOperate(deltaTime, character, this))
|
||||
{
|
||||
isDoneOperating = completionCondition == null || completionCondition();
|
||||
}
|
||||
|
||||
+22
-12
@@ -17,7 +17,6 @@ namespace Barotrauma
|
||||
|
||||
private AIObjectiveGoTo goToObjective;
|
||||
private AIObjectiveContainItem refuelObjective;
|
||||
private float previousCondition = -1;
|
||||
private RepairTool repairTool;
|
||||
|
||||
private const float WaitTimeBeforeRepair = 0.5f;
|
||||
@@ -196,15 +195,7 @@ namespace Barotrauma
|
||||
Abandon = true;
|
||||
}
|
||||
}
|
||||
if (previousCondition == -1)
|
||||
{
|
||||
previousCondition = Item.Condition;
|
||||
}
|
||||
else if (Item.Condition < previousCondition)
|
||||
{
|
||||
// If the current condition is less than the previous condition, we can't complete the task, so let's abandon it. The item is probably deteriorating at a greater speed than we can repair it.
|
||||
Abandon = true;
|
||||
}
|
||||
CheckPreviousCondition(deltaTime);
|
||||
}
|
||||
if (Abandon)
|
||||
{
|
||||
@@ -229,7 +220,6 @@ namespace Barotrauma
|
||||
TryAddSubObjective(ref goToObjective,
|
||||
constructor: () =>
|
||||
{
|
||||
previousCondition = -1;
|
||||
var objective = new AIObjectiveGoTo(Item, character, objectiveManager)
|
||||
{
|
||||
TargetName = Item.Name
|
||||
@@ -251,6 +241,27 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private const float conditionCheckDelay = 1;
|
||||
private float conditionCheckTimer;
|
||||
private float previousCondition;
|
||||
private void CheckPreviousCondition(float deltaTime)
|
||||
{
|
||||
if (Item == null || Item.Removed) { return; }
|
||||
conditionCheckTimer -= deltaTime;
|
||||
if (conditionCheckTimer > 0) { return; }
|
||||
conditionCheckTimer = conditionCheckDelay;
|
||||
if (previousCondition > -1 && Item.Condition < previousCondition)
|
||||
{
|
||||
// If the current condition is less than the previous condition, we can't complete the task, so let's abandon it. The item is probably deteriorating at a greater speed than we can repair it.
|
||||
Abandon = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// If the previous condition is not yet stored or if it's valid (greater or equal to current condition), save the condition for the next check here.
|
||||
previousCondition = Item.Condition;
|
||||
}
|
||||
}
|
||||
|
||||
private void FindRepairTool()
|
||||
{
|
||||
foreach (Repairable repairable in Item.Repairables)
|
||||
@@ -303,7 +314,6 @@ namespace Barotrauma
|
||||
base.Reset();
|
||||
goToObjective = null;
|
||||
refuelObjective = null;
|
||||
previousCondition = -1;
|
||||
repairTool = null;
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -139,14 +139,14 @@ namespace Barotrauma
|
||||
recursive: true);
|
||||
}
|
||||
}
|
||||
if (character.Submarine != null)
|
||||
if (character.Submarine != null && targetCharacter.CurrentHull != null)
|
||||
{
|
||||
if (HumanAIController.GetHullSafety(targetCharacter.CurrentHull, targetCharacter) < HumanAIController.HULL_SAFETY_THRESHOLD)
|
||||
{
|
||||
// Incapacitated target is not in a safe place -> Move to a safe place first
|
||||
if (character.SelectedCharacter != targetCharacter)
|
||||
{
|
||||
if (targetCharacter.CurrentHull != null && HumanAIController.VisibleHulls.Contains(targetCharacter.CurrentHull) && targetCharacter.CurrentHull.DisplayName != null)
|
||||
if (HumanAIController.VisibleHulls.Contains(targetCharacter.CurrentHull) && targetCharacter.CurrentHull.DisplayName != null)
|
||||
{
|
||||
character.Speak(TextManager.GetWithVariables("DialogFoundUnconsciousTarget",
|
||||
("[targetname]", targetCharacter.Name, FormatCapitals.No),
|
||||
|
||||
+2
-2
@@ -71,11 +71,11 @@ namespace Barotrauma
|
||||
{
|
||||
float strength = character.CharacterHealth.GetPredictedStrength(affliction, predictFutureDuration: 10.0f);
|
||||
vitality -= affliction.GetVitalityDecrease(character.CharacterHealth, strength) / character.MaxVitality * 100;
|
||||
if (affliction.Prefab.AfflictionType == "paralysis")
|
||||
if (affliction.Prefab.AfflictionType == AfflictionPrefab.ParalysisType)
|
||||
{
|
||||
vitality -= affliction.Strength;
|
||||
}
|
||||
else if (affliction.Prefab.AfflictionType == "poison")
|
||||
else if (affliction.Prefab.AfflictionType == AfflictionPrefab.PoisonType)
|
||||
{
|
||||
vitality -= affliction.Strength;
|
||||
}
|
||||
|
||||
@@ -12,6 +12,9 @@ namespace Barotrauma
|
||||
private AIObjectiveGoTo moveInsideObjective, moveOutsideObjective;
|
||||
private bool usingEscapeBehavior, isSteeringThroughGap;
|
||||
|
||||
public override bool AllowOutsideSubmarine => true;
|
||||
public override bool AllowInAnySub => true;
|
||||
|
||||
public AIObjectiveReturn(Character character, Character orderGiver, AIObjectiveManager objectiveManager, float priorityModifier = 1.0f) : base(character, objectiveManager, priorityModifier)
|
||||
{
|
||||
ReturnTarget = GetReturnTarget(Submarine.MainSubs) ?? GetReturnTarget(Submarine.Loaded);
|
||||
|
||||
@@ -465,6 +465,10 @@ namespace Barotrauma
|
||||
public readonly OrderTarget TargetPosition;
|
||||
|
||||
private ISpatialEntity targetSpatialEntity;
|
||||
|
||||
/// <summary>
|
||||
/// Note this property doesn't return the follow target of the Follow objective, as expected!
|
||||
/// </summary>
|
||||
public ISpatialEntity TargetSpatialEntity
|
||||
{
|
||||
get
|
||||
|
||||
@@ -348,6 +348,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (body.UserData is Submarine) { return false; }
|
||||
if (body.UserData is Structure s && !s.IsPlatform) { return false; }
|
||||
if (body.UserData is Voronoi2.VoronoiCell) { return false; }
|
||||
if (body.UserData is Item && body.FixtureList[0].CollisionCategories.HasFlag(Physics.CollisionWall)) { return false; }
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -47,7 +47,8 @@ namespace Barotrauma
|
||||
public void SetOrder(Character orderedCharacter)
|
||||
{
|
||||
OrderedCharacter = orderedCharacter;
|
||||
if (OrderedCharacter.AIController is HumanAIController humanAI && humanAI.ObjectiveManager.CurrentOrders.None(o => o.MatchesOrder(SuggestedOrder.Identifier, Option)))
|
||||
if (OrderedCharacter.AIController is HumanAIController humanAI &&
|
||||
humanAI.ObjectiveManager.CurrentOrders.None(o => o.MatchesOrder(SuggestedOrder.Identifier, Option) && o.TargetEntity == TargetItem))
|
||||
{
|
||||
if (orderedCharacter != CommandingCharacter)
|
||||
{
|
||||
|
||||
+14
-1
@@ -17,7 +17,20 @@ namespace Barotrauma
|
||||
float GetTargetingImportance(Entity entity)
|
||||
{
|
||||
float currentDistanceToEnemy = Vector2.Distance(entity.WorldPosition, TargetItem.WorldPosition);
|
||||
return MathHelper.Clamp(100 - (currentDistanceToEnemy / 100f), MinImportance, MaxImportance);
|
||||
|
||||
float importance = MathHelper.Clamp(100 - (currentDistanceToEnemy / 100f), MinImportance, MaxImportance * 0.5f);
|
||||
if (TargetItem.Submarine != null)
|
||||
{
|
||||
Vector2 dir = entity.WorldPosition - TargetItem.WorldPosition;
|
||||
Vector2 submarineDir = TargetItem.WorldPosition - TargetItem.Submarine.WorldPosition;
|
||||
if (Vector2.Dot(dir, submarineDir) < 0)
|
||||
{
|
||||
//direction from the weapon to the target is opposite to the direction from the sub to the weapon
|
||||
// = the turret is most likely on the wrong side of the sub, reduce importance
|
||||
importance *= 0.1f;
|
||||
}
|
||||
}
|
||||
return importance;
|
||||
}
|
||||
|
||||
public override void CalculateImportanceSpecific()
|
||||
|
||||
@@ -229,7 +229,7 @@ namespace Barotrauma
|
||||
#if DEBUG
|
||||
ShipCommandLog("Current importance for " + shipIssueWorker + " was " + importance + " and it was already being attended by " + shipIssueWorker.OrderedCharacter);
|
||||
#endif
|
||||
attendedIssues.Add(shipIssueWorker);
|
||||
InsertIssue(shipIssueWorker, attendedIssues);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -237,12 +237,19 @@ namespace Barotrauma
|
||||
ShipCommandLog("Current importance for " + shipIssueWorker + " was " + importance + " and it is not attended to");
|
||||
#endif
|
||||
shipIssueWorker.RemoveOrder();
|
||||
availableIssues.Add(shipIssueWorker);
|
||||
InsertIssue(shipIssueWorker, availableIssues);
|
||||
}
|
||||
}
|
||||
|
||||
availableIssues.Sort((x, y) => y.Importance.CompareTo(x.Importance));
|
||||
attendedIssues.Sort((x, y) => x.Importance.CompareTo(y.Importance));
|
||||
static void InsertIssue(ShipIssueWorker issue, List<ShipIssueWorker> list)
|
||||
{
|
||||
int index = 0;
|
||||
while (index < list.Count && list[index].Importance > issue.Importance)
|
||||
{
|
||||
index++;
|
||||
}
|
||||
list.Insert(index, issue);
|
||||
}
|
||||
|
||||
ShipIssueWorker mostImportantIssue = availableIssues.FirstOrDefault();
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ namespace Barotrauma
|
||||
private readonly float minDistFromClosest;
|
||||
private readonly float maxDistFromCenter;
|
||||
private readonly float cohesion;
|
||||
public bool ForceActive { get; private set; }
|
||||
|
||||
public List<AICharacter> Members { get; private set; } = new List<AICharacter>();
|
||||
public HashSet<AICharacter> ActiveMembers { get; private set; } = new HashSet<AICharacter>();
|
||||
@@ -26,9 +27,10 @@ namespace Barotrauma
|
||||
public SwarmBehavior(XElement element, EnemyAIController ai)
|
||||
{
|
||||
this.ai = ai;
|
||||
minDistFromClosest = ConvertUnits.ToSimUnits(element.GetAttributeFloat("mindistfromclosest", 10.0f));
|
||||
maxDistFromCenter = ConvertUnits.ToSimUnits(element.GetAttributeFloat("maxdistfromcenter", 1000.0f));
|
||||
cohesion = element.GetAttributeFloat("cohesion", 1) / 10;
|
||||
minDistFromClosest = ConvertUnits.ToSimUnits(element.GetAttributeFloat(nameof(minDistFromClosest), 10.0f));
|
||||
maxDistFromCenter = ConvertUnits.ToSimUnits(element.GetAttributeFloat(nameof(maxDistFromCenter), 1000.0f));
|
||||
cohesion = element.GetAttributeFloat(nameof(cohesion), 1) / 10;
|
||||
ForceActive = element.GetAttributeBool(nameof(ForceActive), false);
|
||||
}
|
||||
|
||||
public static void CreateSwarm(IEnumerable<AICharacter> swarm)
|
||||
|
||||
@@ -8,16 +8,90 @@ using System;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class WreckAI : IServerSerializable
|
||||
internal class SubmarineTurretAI
|
||||
{
|
||||
public Submarine Wreck { get; private set; }
|
||||
public Submarine Submarine { get; protected set; }
|
||||
protected readonly List<Turret> turrets = new List<Turret>();
|
||||
public Identifier FriendlyTag;
|
||||
|
||||
public SubmarineTurretAI(Submarine submarine, Identifier friendlyTag = default)
|
||||
{
|
||||
FriendlyTag = friendlyTag;
|
||||
Submarine = submarine;
|
||||
foreach (Item item in Item.ItemList)
|
||||
{
|
||||
if (item.Submarine != Submarine) { continue; }
|
||||
var turret = item.GetComponent<Turret>();
|
||||
if (turret != null)
|
||||
{
|
||||
turrets.Add(turret);
|
||||
// Set false, because we manage the turrets in the Update method.
|
||||
turret.AutoOperate = false;
|
||||
// Set to full condition, because items don't work when they are broken.
|
||||
turret.Item.Condition = turret.Item.MaxCondition;
|
||||
foreach (MapEntity linkedEntity in turret.Item.linkedTo)
|
||||
{
|
||||
if (linkedEntity is Item linkedItem)
|
||||
{
|
||||
linkedItem.Condition = linkedItem.MaxCondition;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
LoadAllTurrets();
|
||||
}
|
||||
|
||||
public virtual void Update(float deltaTime)
|
||||
{
|
||||
if (Submarine == null || Submarine.Removed) { return; }
|
||||
OperateTurrets(deltaTime, FriendlyTag);
|
||||
}
|
||||
|
||||
protected virtual void LoadAllTurrets()
|
||||
{
|
||||
foreach (var turret in turrets)
|
||||
{
|
||||
LoadTurret(turret);
|
||||
}
|
||||
}
|
||||
|
||||
protected void LoadTurret(Turret turret, Func<ItemPrefab, bool> ammoFilter = null)
|
||||
{
|
||||
foreach (var linkedItem in turret.Item.GetLinkedEntities<Item>())
|
||||
{
|
||||
var container = linkedItem.GetComponent<ItemContainer>();
|
||||
if (container == null) { continue; }
|
||||
for (int i = 0; i < container.Inventory.Capacity; i++)
|
||||
{
|
||||
if (container.Inventory.GetItemAt(i) != null) { continue; }
|
||||
if (MapEntityPrefab.List.GetRandom(e => e is ItemPrefab ip && container.CanBeContained(ip, i) && (ammoFilter == null || ammoFilter(ip)), Rand.RandSync.ServerAndClient) is ItemPrefab ammoPrefab)
|
||||
{
|
||||
Item ammo = new Item(ammoPrefab, container.Item.WorldPosition, Submarine);
|
||||
if (!container.Inventory.TryPutItem(ammo, i, allowSwapping: false, allowCombine: false, user: null, createNetworkEvent: false))
|
||||
{
|
||||
turret.Item.Remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void OperateTurrets(float deltaTime, Identifier friendlyTag)
|
||||
{
|
||||
foreach (var turret in turrets)
|
||||
{
|
||||
turret.UpdateAutoOperate(deltaTime, friendlyTag);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
partial class WreckAI : SubmarineTurretAI, IServerSerializable
|
||||
{
|
||||
public bool IsAlive { get; private set; }
|
||||
|
||||
private readonly List<Item> allItems;
|
||||
private readonly List<Item> thalamusItems;
|
||||
private readonly List<Structure> thalamusStructures;
|
||||
private readonly List<Turret> turrets = new List<Turret>();
|
||||
private readonly List<WayPoint> wayPoints = new List<WayPoint>();
|
||||
private readonly List<Hull> hulls = new List<Hull>();
|
||||
private readonly List<Item> spawnOrgans = new List<Item>();
|
||||
@@ -25,7 +99,7 @@ namespace Barotrauma
|
||||
|
||||
private bool initialCellsSpawned;
|
||||
|
||||
public readonly WreckAIConfig Config;
|
||||
public WreckAIConfig Config { get; private set; }
|
||||
|
||||
private bool IsClient => GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient;
|
||||
|
||||
@@ -44,15 +118,10 @@ namespace Barotrauma
|
||||
return wreckAI;
|
||||
}
|
||||
|
||||
private WreckAI(Submarine wreck)
|
||||
private WreckAI(Submarine wreck) : base(wreck)
|
||||
{
|
||||
Wreck = wreck;
|
||||
Config = WreckAIConfig.GetRandom();
|
||||
if (Config == null)
|
||||
{
|
||||
DebugConsole.ThrowError("WreckAI: No wreck AI config found!");
|
||||
return;
|
||||
}
|
||||
GetConfig();
|
||||
if (Config == null) { return; }
|
||||
var thalamusPrefabs = ItemPrefab.Prefabs.Where(p => IsThalamus(p));
|
||||
var brainPrefab = thalamusPrefabs.GetRandom(i => i.Tags.Contains(Config.Brain), Rand.RandSync.ServerAndClient);
|
||||
if (brainPrefab == null)
|
||||
@@ -60,20 +129,20 @@ namespace Barotrauma
|
||||
DebugConsole.ThrowError($"WreckAI: Could not find any brain prefab with the tag {Config.Brain}! Cannot continue. Failed to create wreck AI.");
|
||||
return;
|
||||
}
|
||||
allItems = Wreck.GetItems(false);
|
||||
allItems = wreck.GetItems(false);
|
||||
thalamusItems = allItems.FindAll(i => IsThalamus(((MapEntity)i).Prefab));
|
||||
hulls.AddRange(Wreck.GetHulls(false));
|
||||
hulls.AddRange(wreck.GetHulls(false));
|
||||
var potentialBrainHulls = new List<(Hull hull, float weight)>();
|
||||
brain = new Item(brainPrefab, Vector2.Zero, Wreck);
|
||||
brain = new Item(brainPrefab, Vector2.Zero, wreck);
|
||||
thalamusItems.Add(brain);
|
||||
Point minSize = brain.Rect.Size.Multiply(brain.Scale);
|
||||
// Bigger hulls are allowed, but not preferred more than what's sufficent.
|
||||
Vector2 sufficentSize = new Vector2(minSize.X * 2, minSize.Y * 1.1f);
|
||||
// Shrink the horizontal axis so that the brain is not placed in the left or right side, where we often have curved walls.
|
||||
Rectangle shrinkedBounds = ToolBox.GetWorldBounds(Wreck.WorldPosition.ToPoint(), new Point(Wreck.Borders.Width - 500, Wreck.Borders.Height));
|
||||
Rectangle shrinkedBounds = ToolBox.GetWorldBounds(wreck.WorldPosition.ToPoint(), new Point(wreck.Borders.Width - 500, wreck.Borders.Height));
|
||||
foreach (Hull hull in hulls)
|
||||
{
|
||||
float distanceFromCenter = Vector2.Distance(Wreck.WorldPosition, hull.WorldPosition);
|
||||
float distanceFromCenter = Vector2.Distance(wreck.WorldPosition, hull.WorldPosition);
|
||||
float distanceFactor = MathHelper.Lerp(1.0f, 0.5f, MathUtils.InverseLerp(0, Math.Max(shrinkedBounds.Width, shrinkedBounds.Height) / 2, distanceFromCenter));
|
||||
float horizontalSizeFactor = MathHelper.Lerp(0.5f, 1.0f, MathUtils.InverseLerp(minSize.X, sufficentSize.X, hull.Rect.Width));
|
||||
float verticalSizeFactor = MathHelper.Lerp(0.5f, 1.0f, MathUtils.InverseLerp(minSize.Y, sufficentSize.Y, hull.Rect.Height));
|
||||
@@ -121,7 +190,7 @@ namespace Barotrauma
|
||||
var backgroundPrefab = thalamusStructurePrefabs.GetRandom(i => i.Tags.Contains(Config.BrainRoomBackground), Rand.RandSync.ServerAndClient);
|
||||
if (backgroundPrefab != null)
|
||||
{
|
||||
new Structure(brainHull.Rect, backgroundPrefab, Wreck);
|
||||
new Structure(brainHull.Rect, backgroundPrefab, wreck);
|
||||
}
|
||||
var horizontalWallPrefab = thalamusStructurePrefabs.GetRandom(p => p.Tags.Contains(Config.BrainRoomHorizontalWall), Rand.RandSync.ServerAndClient);
|
||||
if (horizontalWallPrefab != null)
|
||||
@@ -129,8 +198,8 @@ namespace Barotrauma
|
||||
int height = (int)horizontalWallPrefab.Size.Y;
|
||||
int halfHeight = height / 2;
|
||||
int quarterHeight = halfHeight / 2;
|
||||
new Structure(new Rectangle(brainHull.Rect.Left, brainHull.Rect.Top + quarterHeight, brainHull.Rect.Width, height), horizontalWallPrefab, Wreck);
|
||||
new Structure(new Rectangle(brainHull.Rect.Left, brainHull.Rect.Top - brainHull.Rect.Height + halfHeight + quarterHeight, brainHull.Rect.Width, height), horizontalWallPrefab, Wreck);
|
||||
new Structure(new Rectangle(brainHull.Rect.Left, brainHull.Rect.Top + quarterHeight, brainHull.Rect.Width, height), horizontalWallPrefab, wreck);
|
||||
new Structure(new Rectangle(brainHull.Rect.Left, brainHull.Rect.Top - brainHull.Rect.Height + halfHeight + quarterHeight, brainHull.Rect.Width, height), horizontalWallPrefab, wreck);
|
||||
}
|
||||
var verticalWallPrefab = thalamusStructurePrefabs.GetRandom(p => p.Tags.Contains(Config.BrainRoomVerticalWall), Rand.RandSync.ServerAndClient);
|
||||
if (verticalWallPrefab != null)
|
||||
@@ -138,50 +207,13 @@ namespace Barotrauma
|
||||
int width = (int)verticalWallPrefab.Size.X;
|
||||
int halfWidth = width / 2;
|
||||
int quarterWidth = halfWidth / 2;
|
||||
new Structure(new Rectangle(brainHull.Rect.Left - quarterWidth, brainHull.Rect.Top, width, brainHull.Rect.Height), verticalWallPrefab, Wreck);
|
||||
new Structure(new Rectangle(brainHull.Rect.Right - halfWidth - quarterWidth, brainHull.Rect.Top, width, brainHull.Rect.Height), verticalWallPrefab, Wreck);
|
||||
new Structure(new Rectangle(brainHull.Rect.Left - quarterWidth, brainHull.Rect.Top, width, brainHull.Rect.Height), verticalWallPrefab, wreck);
|
||||
new Structure(new Rectangle(brainHull.Rect.Right - halfWidth - quarterWidth, brainHull.Rect.Top, width, brainHull.Rect.Height), verticalWallPrefab, wreck);
|
||||
}
|
||||
foreach (Item item in allItems)
|
||||
foreach (Item item in thalamusItems)
|
||||
{
|
||||
if (thalamusItems.Contains(item))
|
||||
{
|
||||
// Ensure that thalamus items are visible
|
||||
item.HiddenInGame = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Load regular turrets
|
||||
var turret = item.GetComponent<Turret>();
|
||||
if (turret != null)
|
||||
{
|
||||
foreach (var linkedItem in item.GetLinkedEntities<Item>())
|
||||
{
|
||||
var container = linkedItem.GetComponent<ItemContainer>();
|
||||
if (container == null) { continue; }
|
||||
for (int i = 0; i < container.Inventory.Capacity; i++)
|
||||
{
|
||||
if (container.Inventory.GetItemAt(i) != null) { continue; }
|
||||
if (MapEntityPrefab.List.GetRandom(e => e is ItemPrefab ip && container.CanBeContained(ip, i) &&
|
||||
Config.ForbiddenAmmunition.None(id => id == ip.Identifier), Rand.RandSync.ServerAndClient) is ItemPrefab ammoPrefab)
|
||||
{
|
||||
Item ammo = new Item(ammoPrefab, container.Item.WorldPosition, Wreck);
|
||||
if (!container.Inventory.TryPutItem(ammo, i, allowSwapping: false, allowCombine: false, user: null, createNetworkEvent: false))
|
||||
{
|
||||
item.Remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach (var item in allItems)
|
||||
{
|
||||
var turret = item.GetComponent<Turret>();
|
||||
if (turret != null)
|
||||
{
|
||||
turrets.Add(turret);
|
||||
}
|
||||
// Ensure that thalamus items are visible
|
||||
item.HiddenInGame = false;
|
||||
if (item.HasTag(Config.Spawner))
|
||||
{
|
||||
if (!spawnOrgans.Contains(item))
|
||||
@@ -195,16 +227,34 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
wayPoints.AddRange(Wreck.GetWaypoints(false));
|
||||
wayPoints.AddRange(wreck.GetWaypoints(false));
|
||||
IsAlive = true;
|
||||
thalamusStructures = GetThalamusEntities<Structure>(Wreck, Config.Entity).ToList();
|
||||
thalamusStructures = GetThalamusEntities<Structure>(wreck, Config.Entity).ToList();
|
||||
}
|
||||
|
||||
private void GetConfig()
|
||||
{
|
||||
Config ??= WreckAIConfig.GetRandom();
|
||||
if (Config == null)
|
||||
{
|
||||
DebugConsole.ThrowError("WreckAI: No wreck AI config found!");
|
||||
}
|
||||
}
|
||||
|
||||
protected override void LoadAllTurrets()
|
||||
{
|
||||
GetConfig();
|
||||
foreach (var turret in turrets)
|
||||
{
|
||||
LoadTurret(turret, ip => Config.ForbiddenAmmunition.None(id => id == ip.Identifier));
|
||||
}
|
||||
}
|
||||
|
||||
private readonly List<Item> destroyedOrgans = new List<Item>();
|
||||
public void Update(float deltaTime)
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
if (!IsAlive) { return; }
|
||||
if (Wreck == null || Wreck.Removed)
|
||||
if (Submarine == null || Submarine.Removed)
|
||||
{
|
||||
Remove();
|
||||
return;
|
||||
@@ -223,34 +273,60 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
destroyedOrgans.ForEach(o => spawnOrgans.Remove(o));
|
||||
bool someoneNearby = false;
|
||||
bool isSomeoneNearby = false;
|
||||
float minDist = Sonar.DefaultSonarRange * 2.0f;
|
||||
foreach (Submarine submarine in Submarine.Loaded)
|
||||
#if SERVER
|
||||
foreach (var client in GameMain.Server.ConnectedClients)
|
||||
{
|
||||
if (submarine.Info.Type != SubmarineType.Player) { continue; }
|
||||
if (Vector2.DistanceSquared(submarine.WorldPosition, Wreck.WorldPosition) < minDist * minDist)
|
||||
var spectatePos = client.SpectatePos;
|
||||
if (spectatePos.HasValue)
|
||||
{
|
||||
someoneNearby = true;
|
||||
break;
|
||||
if (IsCloseEnough(spectatePos.Value, minDist))
|
||||
{
|
||||
isSomeoneNearby = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach (Character c in Character.CharacterList)
|
||||
#else
|
||||
if (IsCloseEnough(GameMain.GameScreen.Cam.Position, minDist))
|
||||
{
|
||||
if (c != Character.Controlled && !c.IsRemotePlayer) { continue; }
|
||||
if (Vector2.DistanceSquared(c.WorldPosition, Wreck.WorldPosition) < minDist * minDist)
|
||||
isSomeoneNearby = true;
|
||||
}
|
||||
#endif
|
||||
if (!isSomeoneNearby)
|
||||
{
|
||||
foreach (Submarine submarine in Submarine.Loaded)
|
||||
{
|
||||
someoneNearby = true;
|
||||
break;
|
||||
if (submarine.Info.Type != SubmarineType.Player) { continue; }
|
||||
if (IsCloseEnough(submarine.WorldPosition, minDist))
|
||||
{
|
||||
isSomeoneNearby = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!someoneNearby) { return; }
|
||||
OperateTurrets(deltaTime);
|
||||
if (!isSomeoneNearby)
|
||||
{
|
||||
foreach (Character c in Character.CharacterList)
|
||||
{
|
||||
if (!c.IsPlayer && !c.IsOnPlayerTeam) { continue; }
|
||||
if (IsCloseEnough(c.WorldPosition, minDist))
|
||||
{
|
||||
isSomeoneNearby = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!isSomeoneNearby) { return; }
|
||||
OperateTurrets(deltaTime, Config.Entity);
|
||||
if (!IsClient)
|
||||
{
|
||||
if (!initialCellsSpawned) { SpawnInitialCells(); }
|
||||
UpdateReinforcements(deltaTime);
|
||||
}
|
||||
}
|
||||
private bool IsCloseEnough(Vector2 targetPos, float minDist) => Vector2.DistanceSquared(targetPos, Submarine.WorldPosition) < minDist * minDist;
|
||||
|
||||
private void SpawnInitialCells()
|
||||
{
|
||||
@@ -287,7 +363,7 @@ namespace Barotrauma
|
||||
// Snap all tendons
|
||||
foreach (Item item in turret.ActiveProjectiles)
|
||||
{
|
||||
if (item.GetComponent<Projectile>()?.IsStuckToTarget ?? false)
|
||||
if (item.GetComponent<Projectile>() is { IsStuckToTarget: true })
|
||||
{
|
||||
item.Condition = 0;
|
||||
}
|
||||
@@ -314,7 +390,7 @@ namespace Barotrauma
|
||||
{
|
||||
// Sonar distance is used also for wreck positioning. No wreck should be closer to each other than this.
|
||||
float maxDistance = Sonar.DefaultSonarRange;
|
||||
if (Vector2.DistanceSquared(character.WorldPosition, Wreck.WorldPosition) < maxDistance * maxDistance)
|
||||
if (Vector2.DistanceSquared(character.WorldPosition, Submarine.WorldPosition) < maxDistance * maxDistance)
|
||||
{
|
||||
character.Kill(CauseOfDeathType.Unknown, null);
|
||||
}
|
||||
@@ -333,7 +409,7 @@ namespace Barotrauma
|
||||
public void Remove()
|
||||
{
|
||||
Kill();
|
||||
RemoveThalamusItems(Wreck);
|
||||
RemoveThalamusItems(Submarine);
|
||||
thalamusItems?.Clear();
|
||||
thalamusStructures?.Clear();
|
||||
}
|
||||
@@ -387,7 +463,7 @@ namespace Barotrauma
|
||||
return MathHelper.Lerp(max, min, MathUtils.InverseLerp(0, 100, t));
|
||||
}
|
||||
|
||||
void UpdateReinforcements(float deltaTime)
|
||||
private void UpdateReinforcements(float deltaTime)
|
||||
{
|
||||
if (spawnOrgans.Count == 0) { return; }
|
||||
cellSpawnTimer -= deltaTime;
|
||||
@@ -398,7 +474,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
bool TrySpawnCell(out Character cell, ISpatialEntity targetEntity = null)
|
||||
private bool TrySpawnCell(out Character cell, ISpatialEntity targetEntity = null)
|
||||
{
|
||||
cell = null;
|
||||
if (protectiveCells.Count >= MaxCellCount) { return false; }
|
||||
@@ -424,19 +500,6 @@ namespace Barotrauma
|
||||
cellSpawnTimer = GetSpawnTime();
|
||||
return true;
|
||||
}
|
||||
|
||||
void OperateTurrets(float deltaTime)
|
||||
{
|
||||
foreach (var turret in turrets)
|
||||
{
|
||||
// Never target other creatures than humans with the turrets.
|
||||
turret.ThalamusOperate(this, deltaTime,
|
||||
!turret.Item.HasTag("ignorecharacters"),
|
||||
targetOtherCreatures: false,
|
||||
!turret.Item.HasTag("ignoresubmarines"),
|
||||
turret.Item.HasTag("ignoreaimdelay"));
|
||||
}
|
||||
}
|
||||
|
||||
void OnCellDeath(Character character, CauseOfDeath causeOfDeath)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user