Unstable v0.1300.0.1
This commit is contained in:
@@ -232,8 +232,7 @@ namespace Barotrauma
|
||||
|
||||
public bool IsWithinSector(Vector2 worldPosition)
|
||||
{
|
||||
if (sectorRad >= MathHelper.TwoPi) return true;
|
||||
|
||||
if (sectorRad >= MathHelper.TwoPi) { return true; }
|
||||
Vector2 diff = worldPosition - WorldPosition;
|
||||
return MathUtils.GetShortestAngle(MathUtils.VectorToAngle(diff), MathUtils.VectorToAngle(sectorDir)) <= sectorRad * 0.5f;
|
||||
}
|
||||
|
||||
@@ -298,7 +298,7 @@ namespace Barotrauma
|
||||
private CharacterParams.TargetParams GetTargetParams(AITarget aiTarget) => GetTargetParams(GetTargetingTag(aiTarget));
|
||||
private string GetTargetingTag(AITarget aiTarget)
|
||||
{
|
||||
if (aiTarget.Entity == null) { return null; }
|
||||
if (aiTarget?.Entity == null) { return null; }
|
||||
string targetingTag = null;
|
||||
if (aiTarget.Entity is Character targetCharacter)
|
||||
{
|
||||
@@ -377,6 +377,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (DisableEnemyAI) { return; }
|
||||
base.Update(deltaTime);
|
||||
UpdateTriggers(deltaTime);
|
||||
|
||||
bool ignorePlatforms = Character.AnimController.TargetMovement.Y < -0.5f && (-Character.AnimController.TargetMovement.Y > Math.Abs(Character.AnimController.TargetMovement.X));
|
||||
if (steeringManager == insideSteering)
|
||||
@@ -462,7 +463,7 @@ namespace Barotrauma
|
||||
{
|
||||
updateTargetsTimer -= deltaTime;
|
||||
}
|
||||
else if (avoidTimer <= 0)
|
||||
else if (avoidTimer <= 0 || activeTriggers.Any() && returnTimer <= 0)
|
||||
{
|
||||
CharacterParams.TargetParams targetingParams = null;
|
||||
UpdateTargets(Character, out targetingParams);
|
||||
@@ -1448,8 +1449,12 @@ namespace Barotrauma
|
||||
break;
|
||||
case AttackPattern.Circle:
|
||||
if (IsCoolDownRunning) { break; }
|
||||
if (IsAttackRunning) { break; }
|
||||
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);
|
||||
switch (CirclePhase)
|
||||
{
|
||||
case CirclePhase.Start:
|
||||
@@ -1471,22 +1476,31 @@ namespace Barotrauma
|
||||
circleOffset = Rand.Vector(MathHelper.Lerp(selectedTargetingParams.CircleMaxRandomOffset, 0, currentAttackIntensity * Rand.Range(0.9f, 1.1f)));
|
||||
canAttack = false;
|
||||
aggressionIntensity = Math.Clamp(aggressionIntensity, AIParams.StartAggression, AIParams.MaxAggression);
|
||||
CirclePhase = Vector2.DistanceSquared(WorldPosition, attackWorldPos) > MathUtils.Pow2(circleFallbackDistance) ? CirclePhase.CloseIn : CirclePhase.FallBack;
|
||||
if (targetSub.Borders.Width < 1000)
|
||||
{
|
||||
breakCircling = true;
|
||||
CirclePhase = CirclePhase.CloseIn;
|
||||
}
|
||||
else if (sqrDistToSub > MathUtils.Pow2(subSize + selectedTargetingParams.CircleStartDistance))
|
||||
{
|
||||
CirclePhase = CirclePhase.CloseIn;
|
||||
}
|
||||
else if (sqrDistToSub < MathUtils.Pow2(subSize + circleFallbackDistance))
|
||||
{
|
||||
CirclePhase = CirclePhase.FallBack;
|
||||
}
|
||||
else
|
||||
{
|
||||
CirclePhase = CirclePhase.Advance;
|
||||
}
|
||||
break;
|
||||
case CirclePhase.CloseIn:
|
||||
var sub = SelectedAiTarget.Entity?.Submarine;
|
||||
if (sub == null)
|
||||
{
|
||||
CirclePhase = CirclePhase.Start;
|
||||
break;
|
||||
}
|
||||
if (AttackingLimb != null && distance > 0 && distance < AttackingLimb.attack.Range * GetStrikeDistanceMultiplier(sub.Velocity))
|
||||
if (AttackingLimb != null && distance > 0 && distance < AttackingLimb.attack.Range * GetStrikeDistanceMultiplier(targetSub.Velocity))
|
||||
{
|
||||
strikeTimer = AttackingLimb.attack.CoolDown;
|
||||
CirclePhase = CirclePhase.Strike;
|
||||
}
|
||||
else if (!breakCircling && Vector2.DistanceSquared(WorldPosition, attackWorldPos) <= MathUtils.Pow2(circleFallbackDistance - 1000) &&
|
||||
sub.Velocity.LengthSquared() <= MathUtils.Pow2(GetTargetMaxSpeed()))
|
||||
else if (!breakCircling && sqrDistToSub <= MathUtils.Pow2(subSize + selectedTargetingParams.CircleStartDistance / 2) && targetSub.Velocity.LengthSquared() <= MathUtils.Pow2(GetTargetMaxSpeed()))
|
||||
{
|
||||
CirclePhase = CirclePhase.Advance;
|
||||
}
|
||||
@@ -1494,23 +1508,17 @@ namespace Barotrauma
|
||||
break;
|
||||
case CirclePhase.FallBack:
|
||||
bool isBlocked = !UpdateFallBack(attackWorldPos, deltaTime, followThrough: false, checkBlocking: true);
|
||||
if (isBlocked || Vector2.DistanceSquared(WorldPosition, attackWorldPos) > MathUtils.Pow2(circleFallbackDistance))
|
||||
if (isBlocked || sqrDistToSub > MathUtils.Pow2(subSize + circleFallbackDistance))
|
||||
{
|
||||
CirclePhase = CirclePhase.Advance;
|
||||
break;
|
||||
}
|
||||
return;
|
||||
case CirclePhase.Advance:
|
||||
var targetSub = SelectedAiTarget.Entity?.Submarine;
|
||||
if (targetSub == null)
|
||||
{
|
||||
CirclePhase = CirclePhase.Start;
|
||||
break;
|
||||
}
|
||||
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()) || distance > selectedTargetingParams.CircleStartDistance + 1000)
|
||||
if (breakCircling || subSpeed.LengthSquared() > MathUtils.Pow2(GetTargetMaxSpeed()) || sqrDistToSub > MathUtils.Pow2(subSize + selectedTargetingParams.CircleStartDistance * 1.2f))
|
||||
{
|
||||
CirclePhase = CirclePhase.CloseIn;
|
||||
}
|
||||
@@ -1532,7 +1540,7 @@ namespace Barotrauma
|
||||
// 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 (targetSub.Borders.ContainsWorld(attackWorldPos + ConvertUnits.ToDisplayUnits(circleOffset)))
|
||||
if (AttackingLimb != null && sqrDistToSub < MathUtils.Pow2(subSize + circleFallbackDistance))
|
||||
{
|
||||
CirclePhase = CirclePhase.Strike;
|
||||
strikeTimer = AttackingLimb.attack.CoolDown;
|
||||
@@ -1646,15 +1654,15 @@ namespace Barotrauma
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (UpdateLimbAttack(deltaTime, AttackingLimb, attackSimPos, distance, attackTargetLimb))
|
||||
{
|
||||
CirclePhase = CirclePhase.Start;
|
||||
}
|
||||
else
|
||||
if (!UpdateLimbAttack(deltaTime, AttackingLimb, attackSimPos, distance, attackTargetLimb))
|
||||
{
|
||||
IgnoreTarget(SelectedAiTarget);
|
||||
}
|
||||
}
|
||||
else if (IsAttackRunning)
|
||||
{
|
||||
AttackingLimb.attack.ResetAttackTimer();
|
||||
}
|
||||
}
|
||||
|
||||
private readonly List<Limb> attackLimbs = new List<Limb>();
|
||||
@@ -2060,6 +2068,7 @@ namespace Barotrauma
|
||||
targetValue = 0;
|
||||
selectedTargetMemory = null;
|
||||
targetingParams = null;
|
||||
bool isAnyTargetClose = false;
|
||||
|
||||
foreach (AITarget aiTarget in AITarget.List)
|
||||
{
|
||||
@@ -2163,6 +2172,14 @@ namespace Barotrauma
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (door == null)
|
||||
{
|
||||
// Ignore items inside ruins, unless we are in the same hull. We can't target the ruin walls.
|
||||
if (item.Submarine == null && item.CurrentHull != Character.CurrentHull)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
foreach (var prio in AIParams.Targets)
|
||||
{
|
||||
if (item.HasTag(prio.Tag))
|
||||
@@ -2379,18 +2396,16 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!aiTarget.IsWithinSector(WorldPosition)) { continue; }
|
||||
Vector2 toTarget = aiTarget.WorldPosition - character.WorldPosition;
|
||||
float dist = toTarget.Length();
|
||||
|
||||
float nonModifiedDist = dist;
|
||||
//if the target has been within range earlier, the character will notice it more easily
|
||||
if (targetMemories.ContainsKey(aiTarget))
|
||||
{
|
||||
dist *= 0.9f;
|
||||
}
|
||||
|
||||
if (!CanPerceive(aiTarget, dist)) { continue; }
|
||||
if (!aiTarget.IsWithinSector(WorldPosition)) { continue; }
|
||||
|
||||
//if the target is very close, the distance doesn't make much difference
|
||||
// -> just ignore the distance and attack whatever has the highest priority
|
||||
@@ -2405,14 +2420,26 @@ namespace Barotrauma
|
||||
|
||||
if (targetParams.AttackPattern == AttackPattern.Circle)
|
||||
{
|
||||
if (Character.Submarine == null && aiTarget.Entity?.Submarine != null)
|
||||
if (Character.Submarine == null && aiTarget.Entity?.Submarine != null && !isAnyTargetClose)
|
||||
{
|
||||
if (Submarine.MainSubs.Contains(aiTarget.Entity.Submarine))
|
||||
{
|
||||
// Prioritize targets that are near the horizontal center of the sub
|
||||
// Prioritize targets that are near the horizontal center of the sub, but only when none of the targets is reachable.
|
||||
float horizontalDistanceToSubCenter = Math.Abs(aiTarget.WorldPosition.X - aiTarget.Entity.Submarine.WorldPosition.X);
|
||||
dist *= MathHelper.Lerp(1f, 5f, MathUtils.InverseLerp(0, 10000, horizontalDistanceToSubCenter));
|
||||
}
|
||||
else
|
||||
{
|
||||
dist *= 5;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Don't target characters that are outside of the allowed zone, unless attacking or escaping
|
||||
if (targetParams.State != AIState.Attack && targetParams.State != AIState.Escape && targetParams.State != AIState.Avoid)
|
||||
{
|
||||
if (!IsPositionInsideAllowedZone(aiTarget.WorldPosition, out _))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2486,11 +2513,26 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (targetCharacter.Submarine == null && Character.Submarine == null)
|
||||
{
|
||||
// Ignore the target when it's far enough and blocked by the level geometry, because the steering avoidance probably can't get us to the target.
|
||||
if (dist > Math.Clamp(ConvertUnits.ToDisplayUnits(colliderLength) * 10, 1000, 5000))
|
||||
{
|
||||
if (Submarine.PickBodies(SimPosition, targetCharacter.SimPosition, collisionCategory: Physics.CollisionLevel).Any())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
newTarget = aiTarget;
|
||||
selectedTargetMemory = targetMemory;
|
||||
targetValue = valueModifier;
|
||||
targetingParams = targetParams;
|
||||
if (!isAnyTargetClose)
|
||||
{
|
||||
isAnyTargetClose = ConvertUnits.ToDisplayUnits(colliderLength) > nonModifiedDist;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2619,7 +2661,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!Character.AnimController.CanEnterSubmarine && wallTarget == null)
|
||||
if (!Character.AnimController.CanEnterSubmarine && wallTarget == null && selectedTargetingParams?.AttackPattern == AttackPattern.Straight)
|
||||
{
|
||||
if (closestBody.UserData is Structure w && w.Submarine != null && w.Submarine == SelectedAiTarget.Entity?.Submarine ||
|
||||
closestBody.UserData is Item i && i.Submarine != null && i.Submarine == SelectedAiTarget.Entity?.Submarine)
|
||||
@@ -2753,6 +2795,44 @@ 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>();
|
||||
|
||||
public void LaunchTrigger(AITrigger trigger)
|
||||
{
|
||||
if (trigger.IsTriggered) { return; }
|
||||
if (activeTriggers.ContainsKey(trigger)) { return; }
|
||||
if (activeTriggers.ContainsValue(selectedTargetingParams))
|
||||
{
|
||||
if (!trigger.AllowToOverride) { return; }
|
||||
var existingTrigger = activeTriggers.FirstOrDefault(kvp => kvp.Value == selectedTargetingParams && kvp.Key.AllowToBeOverridden);
|
||||
if (existingTrigger.Key == null) { return; }
|
||||
activeTriggers.Remove(existingTrigger.Key);
|
||||
}
|
||||
trigger.Launch();
|
||||
activeTriggers.Add(trigger, selectedTargetingParams);
|
||||
ChangeParams(selectedTargetingParams, trigger.State);
|
||||
}
|
||||
|
||||
private void UpdateTriggers(float deltaTime)
|
||||
{
|
||||
foreach (var triggerObject in activeTriggers)
|
||||
{
|
||||
AITrigger trigger = triggerObject.Key;
|
||||
trigger.UpdateTimer(deltaTime);
|
||||
if (!trigger.IsActive)
|
||||
{
|
||||
trigger.Reset();
|
||||
ResetParams(triggerObject.Value);
|
||||
inactiveTriggers.Add(trigger);
|
||||
}
|
||||
}
|
||||
foreach (AITrigger trigger in inactiveTriggers)
|
||||
{
|
||||
activeTriggers.Remove(trigger);
|
||||
}
|
||||
inactiveTriggers.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the target's state to the original value defined in the xml.
|
||||
@@ -2768,11 +2848,7 @@ namespace Barotrauma
|
||||
tempParams.Values.ForEach(t => AIParams.RemoveTarget(t));
|
||||
tempParams.Remove(tag);
|
||||
}
|
||||
targetParams.Reset();
|
||||
ResetAITarget();
|
||||
// Enforce the idle state so that we don't keep following the target if there's one
|
||||
State = AIState.Idle;
|
||||
PreviousState = AIState.Idle;
|
||||
ResetParams(targetParams);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
@@ -2784,6 +2860,27 @@ namespace Barotrauma
|
||||
private readonly Dictionary<string, CharacterParams.TargetParams> modifiedParams = new Dictionary<string, CharacterParams.TargetParams>();
|
||||
private readonly Dictionary<string, CharacterParams.TargetParams> tempParams = new Dictionary<string, CharacterParams.TargetParams>();
|
||||
|
||||
private void ChangeParams(CharacterParams.TargetParams targetParams, AIState state, float? priority = null)
|
||||
{
|
||||
if (targetParams == null) { return; }
|
||||
if (priority.HasValue)
|
||||
{
|
||||
targetParams.Priority = priority.Value;
|
||||
}
|
||||
targetParams.State = state;
|
||||
}
|
||||
|
||||
private void ResetParams(CharacterParams.TargetParams targetParams)
|
||||
{
|
||||
targetParams?.Reset();
|
||||
if (selectedTargetingParams == targetParams || State == AIState.Idle)
|
||||
{
|
||||
ResetAITarget();
|
||||
State = AIState.Idle;
|
||||
PreviousState = AIState.Idle;
|
||||
}
|
||||
}
|
||||
|
||||
private void ChangeParams(string tag, AIState state, float? priority = null, bool onlyExisting = false)
|
||||
{
|
||||
if (!AIParams.TryGetTarget(tag, out CharacterParams.TargetParams targetParams))
|
||||
@@ -2938,45 +3035,56 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsPositionInsideAllowedZone(Vector2 pos, out Vector2 targetDir)
|
||||
{
|
||||
targetDir = Vector2.Zero;
|
||||
if (AIParams.AvoidAbyss)
|
||||
{
|
||||
if (pos.Y < Level.Loaded.AbyssStart)
|
||||
{
|
||||
// Too far down
|
||||
targetDir = Vector2.UnitY;
|
||||
}
|
||||
}
|
||||
if (AIParams.StayInAbyss)
|
||||
{
|
||||
if (pos.Y > Level.Loaded.AbyssStart)
|
||||
{
|
||||
// Too far up
|
||||
targetDir = -Vector2.UnitY;
|
||||
}
|
||||
else if (pos.Y < Level.Loaded.AbyssEnd)
|
||||
{
|
||||
// Too far down
|
||||
targetDir = Vector2.UnitY;
|
||||
}
|
||||
}
|
||||
float margin = 30000;
|
||||
if (pos.X < -margin)
|
||||
{
|
||||
// Too far left
|
||||
targetDir = Vector2.UnitX;
|
||||
}
|
||||
else if (pos.X > Level.Loaded.Size.X + margin)
|
||||
{
|
||||
// Too far right
|
||||
targetDir = -Vector2.UnitX;
|
||||
}
|
||||
return targetDir == Vector2.Zero;
|
||||
}
|
||||
|
||||
private Vector2 returnDir;
|
||||
private float returnTimer;
|
||||
private void SteerInsideLevel(float deltaTime)
|
||||
{
|
||||
if (State == AIState.Attack) { return; }
|
||||
if (SteeringManager is IndoorsSteeringManager) { return; }
|
||||
if (Level.Loaded == null) { return; }
|
||||
Point levelSize = Level.Loaded.Size;
|
||||
float returnTime = 10;
|
||||
if (AIParams.AvoidAbyss)
|
||||
if (State == AIState.Attack && returnTimer <= 0) { return; }
|
||||
float returnTime = 5;
|
||||
if (!IsPositionInsideAllowedZone(WorldPosition, out Vector2 targetDir))
|
||||
{
|
||||
if (WorldPosition.Y < Level.Loaded.AbyssStart)
|
||||
{
|
||||
// Too far down
|
||||
returnTimer = returnTime * Rand.Range(0.75f, 1.25f);
|
||||
returnDir = Vector2.UnitY;
|
||||
}
|
||||
}
|
||||
else if (AIParams.StayInAbyss)
|
||||
{
|
||||
if (WorldPosition.Y > Level.Loaded.AbyssStart)
|
||||
{
|
||||
// Too far up
|
||||
returnTimer = returnTime * Rand.Range(0.75f, 1.25f);
|
||||
returnDir = -Vector2.UnitY;
|
||||
}
|
||||
}
|
||||
float margin = AIParams.AvoidAbyss ? 0 : 30000;
|
||||
if (WorldPosition.X < margin)
|
||||
{
|
||||
// Too far left
|
||||
returnDir = targetDir;
|
||||
returnTimer = returnTime * Rand.Range(0.75f, 1.25f);
|
||||
returnDir = Vector2.UnitX;
|
||||
}
|
||||
if (WorldPosition.X > levelSize.X + margin)
|
||||
{
|
||||
// Too far right
|
||||
returnTimer = returnTime * Rand.Range(0.75f, 1.25f);
|
||||
returnDir = -Vector2.UnitX;
|
||||
}
|
||||
if (returnTimer > 0)
|
||||
{
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace Barotrauma
|
||||
|
||||
private readonly AIObjectiveManager objectiveManager;
|
||||
|
||||
private float sortTimer;
|
||||
public float SortTimer { get; set; }
|
||||
private float crouchRaycastTimer;
|
||||
private float reactTimer;
|
||||
private float unreachableClearTimer;
|
||||
@@ -131,7 +131,7 @@ namespace Barotrauma
|
||||
outsideSteering = new SteeringManager(this);
|
||||
objectiveManager = new AIObjectiveManager(c);
|
||||
reactTimer = GetReactionTime();
|
||||
sortTimer = Rand.Range(0f, sortObjectiveInterval);
|
||||
SortTimer = Rand.Range(0f, sortObjectiveInterval);
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
@@ -218,6 +218,7 @@ namespace Barotrauma
|
||||
foreach (Character c in Character.CharacterList)
|
||||
{
|
||||
if (c.Submarine != Character.Submarine) { continue; }
|
||||
if (c.Removed || c.IsDead || c.IsIncapacitated) { continue; }
|
||||
if (IsFriendly(c)) { continue; }
|
||||
Vector2 toTarget = c.WorldPosition - WorldPosition;
|
||||
float dist = toTarget.LengthSquared();
|
||||
@@ -264,14 +265,14 @@ namespace Barotrauma
|
||||
CheckCrouching(deltaTime);
|
||||
Character.ClearInputs();
|
||||
|
||||
if (sortTimer > 0.0f)
|
||||
if (SortTimer > 0.0f)
|
||||
{
|
||||
sortTimer -= deltaTime;
|
||||
SortTimer -= deltaTime;
|
||||
}
|
||||
else
|
||||
{
|
||||
objectiveManager.SortObjectives();
|
||||
sortTimer = sortObjectiveInterval;
|
||||
SortTimer = sortObjectiveInterval;
|
||||
}
|
||||
objectiveManager.UpdateObjectives(deltaTime);
|
||||
|
||||
@@ -288,14 +289,14 @@ namespace Barotrauma
|
||||
{
|
||||
if (Character.CurrentHull != null)
|
||||
{
|
||||
if (Character.TeamID == CharacterTeamType.FriendlyNPC)
|
||||
if (Character.IsOnPlayerTeam)
|
||||
{
|
||||
// Outpost npcs don't inform each other about threats, like crew members do.
|
||||
VisibleHulls.ForEach(h => RefreshHullSafety(h));
|
||||
VisibleHulls.ForEach(h => PropagateHullSafety(Character, h));
|
||||
}
|
||||
else
|
||||
{
|
||||
VisibleHulls.ForEach(h => PropagateHullSafety(Character, h));
|
||||
// Outpost npcs don't inform each other about threats, like crew members do.
|
||||
VisibleHulls.ForEach(h => RefreshHullSafety(h));
|
||||
}
|
||||
}
|
||||
if (Character.SpeechImpediment < 100.0f)
|
||||
@@ -1065,7 +1066,9 @@ namespace Barotrauma
|
||||
{
|
||||
if (!IsFriendly(attacker))
|
||||
{
|
||||
return c.IsSecurity ? AIObjectiveCombat.CombatMode.Offensive : AIObjectiveCombat.CombatMode.Defensive;
|
||||
return c.AIController is HumanAIController humanAI &&
|
||||
(humanAI.ObjectiveManager.IsCurrentOrder<AIObjectiveFightIntruders>() || humanAI.ObjectiveManager.Objectives.Any(o => o is AIObjectiveFightIntruders))
|
||||
? AIObjectiveCombat.CombatMode.Offensive : AIObjectiveCombat.CombatMode.Defensive;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1192,7 +1195,7 @@ namespace Barotrauma
|
||||
{
|
||||
base.Reset();
|
||||
objectiveManager.SortObjectives();
|
||||
sortTimer = sortObjectiveInterval;
|
||||
SortTimer = sortObjectiveInterval;
|
||||
float waitDuration = characterWaitOnSwitch;
|
||||
if (ObjectiveManager.IsCurrentObjective<AIObjectiveIdle>())
|
||||
{
|
||||
@@ -1418,6 +1421,9 @@ namespace Barotrauma
|
||||
item.StolenDuringRound = true;
|
||||
otherCharacter.Speak(TextManager.Get("dialogstealwarning"), null, Rand.Range(0.5f, 1.0f), "thief", 10.0f);
|
||||
someoneSpoke = true;
|
||||
#if CLIENT
|
||||
HintManager.OnStoleItem(thief, item);
|
||||
#endif
|
||||
}
|
||||
// React if we are security
|
||||
if (!TriggerSecurity(otherHumanAI))
|
||||
@@ -1554,7 +1560,7 @@ namespace Barotrauma
|
||||
targetAdded = true;
|
||||
}
|
||||
}
|
||||
}, (caller.AIController as HumanAIController)?.ReportRange ?? float.PositiveInfinity);
|
||||
}, range: (caller.AIController as HumanAIController)?.ReportRange ?? float.PositiveInfinity);
|
||||
return targetAdded;
|
||||
}
|
||||
|
||||
@@ -1726,11 +1732,9 @@ namespace Barotrauma
|
||||
switch (myTeam)
|
||||
{
|
||||
case CharacterTeamType.None:
|
||||
// Only enemies are in the Team "None"
|
||||
return false;
|
||||
case CharacterTeamType.Team1:
|
||||
case CharacterTeamType.Team2:
|
||||
// Team1 is only friendly to Team1 and friendly NPCs
|
||||
// Only friendly to the same team and friendly NPCs
|
||||
return otherTeam == CharacterTeamType.FriendlyNPC;
|
||||
case CharacterTeamType.FriendlyNPC:
|
||||
// Friendly NPCs are friendly to both teams
|
||||
|
||||
@@ -221,6 +221,19 @@ namespace Barotrauma
|
||||
{
|
||||
currentFlags.Add("CampaignNPC." + speaker.CampaignInteractionType);
|
||||
}
|
||||
|
||||
if (GameMain.GameSession?.GameMode is CampaignMode campaignMode &&
|
||||
(campaignMode.Map?.CurrentLocation?.Type?.Identifier.Equals("abandoned", StringComparison.OrdinalIgnoreCase) ?? false))
|
||||
{
|
||||
if (speaker.TeamID == CharacterTeamType.None)
|
||||
{
|
||||
currentFlags.Add("Bandit");
|
||||
}
|
||||
else if (speaker.TeamID == CharacterTeamType.FriendlyNPC)
|
||||
{
|
||||
currentFlags.Add("Hostage");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return currentFlags;
|
||||
|
||||
+28
-16
@@ -79,6 +79,7 @@ namespace Barotrauma
|
||||
private float coolDownTimer;
|
||||
private IEnumerable<Body> myBodies;
|
||||
private float aimTimer;
|
||||
private float reloadTimer;
|
||||
private float spreadTimer;
|
||||
|
||||
private bool canSeeTarget;
|
||||
@@ -147,6 +148,7 @@ namespace Barotrauma
|
||||
Mode = CombatMode.Retreat;
|
||||
}
|
||||
spreadTimer = Rand.Range(-10, 10);
|
||||
HumanAIController.SortTimer = 0;
|
||||
}
|
||||
|
||||
public override float GetPriority()
|
||||
@@ -170,6 +172,10 @@ namespace Barotrauma
|
||||
base.Update(deltaTime);
|
||||
ignoreWeaponTimer -= deltaTime;
|
||||
checkWeaponsTimer -= deltaTime;
|
||||
if (reloadTimer > 0)
|
||||
{
|
||||
reloadTimer -= deltaTime;
|
||||
}
|
||||
if (ignoreWeaponTimer < 0)
|
||||
{
|
||||
ignoredWeapons.Clear();
|
||||
@@ -219,7 +225,11 @@ namespace Barotrauma
|
||||
{
|
||||
OperateWeapon(deltaTime);
|
||||
}
|
||||
if (!HoldPosition && seekAmmunitionObjective == null && seekWeaponObjective == null)
|
||||
if (HoldPosition)
|
||||
{
|
||||
SteeringManager.Reset();
|
||||
}
|
||||
else if (seekAmmunitionObjective == null && seekWeaponObjective == null)
|
||||
{
|
||||
Move(deltaTime);
|
||||
}
|
||||
@@ -641,7 +651,7 @@ namespace Barotrauma
|
||||
var slots = Weapon.AllowedSlots.Where(s => s == InvSlotType.LeftHand || s == InvSlotType.RightHand || s == (InvSlotType.LeftHand | InvSlotType.RightHand));
|
||||
if (character.Inventory.TryPutItem(Weapon, character, slots))
|
||||
{
|
||||
aimTimer = Rand.Range(1f, 1.5f) / AimSpeed;
|
||||
aimTimer = Rand.Range(0.2f, 0.4f) / AimSpeed;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -912,7 +922,7 @@ namespace Barotrauma
|
||||
}
|
||||
if (!canSeeTarget)
|
||||
{
|
||||
aimTimer = Rand.Range(0.2f, 1f) / AimSpeed;
|
||||
aimTimer = Rand.Range(0.2f, 0.4f) / AimSpeed;
|
||||
return;
|
||||
}
|
||||
if (Weapon.RequireAimToUse)
|
||||
@@ -930,6 +940,7 @@ namespace Barotrauma
|
||||
aimTimer -= deltaTime;
|
||||
return;
|
||||
}
|
||||
if (reloadTimer > 0) { return; }
|
||||
if (Mode == CombatMode.Arrest && isLethalWeapon && Enemy.Stun > 1) { return; }
|
||||
if (holdFireCondition != null && holdFireCondition()) { return; }
|
||||
float sqrDist = Vector2.DistanceSquared(character.Position, Enemy.Position);
|
||||
@@ -1010,18 +1021,25 @@ namespace Barotrauma
|
||||
|
||||
private void UseWeapon(float deltaTime)
|
||||
{
|
||||
character.SetInput(InputType.Shoot, false, true);
|
||||
Weapon.Use(deltaTime, character);
|
||||
float reloadTime = 0;
|
||||
if (WeaponComponent is RangedWeapon rangedWeapon)
|
||||
{
|
||||
reloadTime = rangedWeapon.Reload;
|
||||
// If the weapon is just equipped, we can't shoot just yet.
|
||||
if (rangedWeapon.ReloadTimer <= 0)
|
||||
{
|
||||
reloadTime = rangedWeapon.Reload;
|
||||
}
|
||||
}
|
||||
if (WeaponComponent is MeleeWeapon mw)
|
||||
{
|
||||
reloadTime = mw.Reload;
|
||||
if (!((HumanoidAnimController)character.AnimController).Crouching)
|
||||
{
|
||||
reloadTime = mw.Reload;
|
||||
}
|
||||
}
|
||||
aimTimer = Math.Max(reloadTime, reloadTime * Rand.Range(1f, 1.5f) / AimSpeed);
|
||||
character.SetInput(InputType.Shoot, false, true);
|
||||
Weapon.Use(deltaTime, character);
|
||||
reloadTimer = Math.Max(reloadTime, reloadTime * Rand.Range(1f, 1.25f) / AimSpeed);
|
||||
}
|
||||
|
||||
protected override void OnCompleted()
|
||||
@@ -1031,10 +1049,7 @@ namespace Barotrauma
|
||||
{
|
||||
Unequip();
|
||||
}
|
||||
if (!HoldPosition)
|
||||
{
|
||||
SteeringManager.Reset();
|
||||
}
|
||||
SteeringManager.Reset();
|
||||
}
|
||||
|
||||
protected override void OnAbandon()
|
||||
@@ -1044,10 +1059,7 @@ namespace Barotrauma
|
||||
{
|
||||
Unequip();
|
||||
}
|
||||
if (!HoldPosition)
|
||||
{
|
||||
SteeringManager.Reset();
|
||||
}
|
||||
SteeringManager.Reset();
|
||||
}
|
||||
|
||||
public override void Reset()
|
||||
|
||||
+4
-1
@@ -10,6 +10,8 @@ namespace Barotrauma
|
||||
protected override float IgnoreListClearInterval => 30;
|
||||
public override bool IgnoreUnsafeHulls => true;
|
||||
|
||||
protected override float TargetUpdateTimeMultiplier => 0.2f;
|
||||
|
||||
public AIObjectiveFightIntruders(Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1)
|
||||
: base(character, objectiveManager, priorityModifier) { }
|
||||
|
||||
@@ -48,7 +50,8 @@ namespace Barotrauma
|
||||
|
||||
public static bool IsValidTarget(Character target, Character character)
|
||||
{
|
||||
if (target == null || target.IsDead || target.Removed) { return false; }
|
||||
if (target == null || target.Removed) { return false; }
|
||||
if (target.IsDead || target.IsUnconscious) { return false; }
|
||||
if (target == character) { return false; }
|
||||
if (target.Submarine == null) { return false; }
|
||||
if (character.Submarine == null) { return false; }
|
||||
|
||||
@@ -555,6 +555,13 @@ namespace Barotrauma
|
||||
//otherwise characters can let go of the ladders too soon once they're close enough to the target
|
||||
if (PathSteering.CurrentPath.NextNode != null) { return false; }
|
||||
}
|
||||
if (!character.AnimController.InWater)
|
||||
{
|
||||
float yDiff = Math.Abs(Target.WorldPosition.Y - character.WorldPosition.Y);
|
||||
if (yDiff > CloseEnough) { return false; }
|
||||
float xDiff = Math.Abs(Target.WorldPosition.X - character.WorldPosition.X);
|
||||
return xDiff <= CloseEnough;
|
||||
}
|
||||
return Vector2.DistanceSquared(Target.WorldPosition, character.WorldPosition) < CloseEnough * CloseEnough;
|
||||
}
|
||||
}
|
||||
|
||||
+6
-5
@@ -11,6 +11,7 @@ namespace Barotrauma
|
||||
protected HashSet<T> ignoreList = new HashSet<T>();
|
||||
private float ignoreListTimer;
|
||||
protected float targetUpdateTimer;
|
||||
protected virtual float TargetUpdateTimeMultiplier { get; } = 1;
|
||||
|
||||
private float syncTimer;
|
||||
private readonly float syncTime = 1;
|
||||
@@ -61,7 +62,7 @@ namespace Barotrauma
|
||||
ignoreListTimer += deltaTime;
|
||||
}
|
||||
}
|
||||
if (targetUpdateTimer < 0)
|
||||
if (targetUpdateTimer <= 0)
|
||||
{
|
||||
UpdateTargets();
|
||||
}
|
||||
@@ -69,9 +70,9 @@ namespace Barotrauma
|
||||
{
|
||||
targetUpdateTimer -= deltaTime;
|
||||
}
|
||||
if (syncTimer < 0)
|
||||
if (syncTimer <= 0)
|
||||
{
|
||||
syncTimer = syncTime * Rand.Range(0.9f, 1.1f);
|
||||
syncTimer = Math.Min(syncTime * Rand.Range(0.9f, 1.1f), targetUpdateTimer);
|
||||
// Sync objectives, subobjectives and targets
|
||||
foreach (var objective in Objectives)
|
||||
{
|
||||
@@ -95,7 +96,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
// the timer is set between 1 and 10 seconds, depending on the priority modifier and a random +-25%
|
||||
private float SetTargetUpdateTimer() => targetUpdateTimer = 1 / MathHelper.Clamp(PriorityModifier * Rand.Range(0.75f, 1.25f), 0.1f, 1);
|
||||
private float CalculateTargetUpdateTimer() => targetUpdateTimer = 1 / MathHelper.Clamp(PriorityModifier * Rand.Range(0.75f, 1.25f), 0.1f, 1) * TargetUpdateTimeMultiplier;
|
||||
|
||||
public override void Reset()
|
||||
{
|
||||
@@ -156,7 +157,7 @@ namespace Barotrauma
|
||||
|
||||
protected void UpdateTargets()
|
||||
{
|
||||
SetTargetUpdateTimer();
|
||||
CalculateTargetUpdateTimer();
|
||||
Targets.Clear();
|
||||
FindTargets();
|
||||
CreateObjectives();
|
||||
|
||||
+3
-1
@@ -386,7 +386,9 @@ namespace Barotrauma
|
||||
Abandon = true;
|
||||
return false;
|
||||
}
|
||||
bool isCompleted = AIObjectiveRescueAll.GetVitalityFactor(targetCharacter) >= AIObjectiveRescueAll.GetVitalityThreshold(objectiveManager, character, targetCharacter);
|
||||
bool isCompleted =
|
||||
AIObjectiveRescueAll.GetVitalityFactor(targetCharacter) >= AIObjectiveRescueAll.GetVitalityThreshold(objectiveManager, character, targetCharacter) ||
|
||||
targetCharacter.CharacterHealth.GetAllAfflictions().All(a => a.Strength < a.Prefab.TreatmentThreshold);
|
||||
if (isCompleted && targetCharacter != character && character.IsOnPlayerTeam)
|
||||
{
|
||||
character.Speak(TextManager.GetWithVariable("DialogTargetHealed", "[targetname]", targetCharacter.Name),
|
||||
|
||||
+3
-3
@@ -25,8 +25,8 @@ namespace Barotrauma
|
||||
{
|
||||
// When targeting player characters, always treat them when ordered, else use the threshold so that minor/non-severe damage is ignored.
|
||||
// If we ignore any damage when the player orders a bot to do healings, it's observed to cause confusion among the players.
|
||||
// On the other hand, if the bots too eagerly heal characters when it's not nevessary, it's inefficient and can feel frustrating, because it can't be controlled.
|
||||
return character == target || manager.CurrentOrder is AIObjectiveRescueAll ? (target.IsPlayer ? 100 : vitalityThresholdForOrders) : vitalityThreshold;
|
||||
// On the other hand, if the bots too eagerly heal characters when it's not necessary, it's inefficient and can feel frustrating, because it can't be controlled.
|
||||
return character == target || manager.HasOrder<AIObjectiveRescueAll>() ? (target.IsPlayer ? 100 : vitalityThresholdForOrders) : vitalityThreshold;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@ namespace Barotrauma
|
||||
if (character.AIController is HumanAIController humanAI)
|
||||
{
|
||||
if (GetVitalityFactor(target) >= GetVitalityThreshold(humanAI.ObjectiveManager, character, target)) { return false; }
|
||||
if (!humanAI.ObjectiveManager.IsCurrentOrder<AIObjectiveRescueAll>())
|
||||
if (!humanAI.ObjectiveManager.HasOrder<AIObjectiveRescueAll>())
|
||||
{
|
||||
if (!character.IsMedic && target != character)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user