Unstable v0.10.600.0
This commit is contained in:
@@ -3,7 +3,7 @@ using System.Collections.Generic;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
public enum AIState { Idle, Attack, Escape, Eat, Flee, Avoid, Aggressive, PassiveAggressive, Protect }
|
||||
public enum AIState { Idle, Attack, Escape, Eat, Flee, Avoid, Aggressive, PassiveAggressive, Protect, Observe }
|
||||
|
||||
abstract partial class AIController : ISteerable
|
||||
{
|
||||
@@ -147,6 +147,8 @@ namespace Barotrauma
|
||||
_selectedAiTarget = null;
|
||||
}
|
||||
|
||||
public void FaceTarget(ISpatialEntity target) => Character.AnimController.TargetDir = target.WorldPosition.X > Character.WorldPosition.X ? Direction.Right : Direction.Left;
|
||||
|
||||
protected virtual void OnStateChanged(AIState from, AIState to) { }
|
||||
protected virtual void OnTargetChanged(AITarget previousTarget, AITarget newTarget) { }
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (float.IsNaN(value))
|
||||
{
|
||||
DebugConsole.ThrowError("Attempted to set the SoundRange of an AITarget to NaN.\n" + Environment.StackTrace);
|
||||
DebugConsole.ThrowError("Attempted to set the SoundRange of an AITarget to NaN.\n" + Environment.StackTrace.CleanupStackTrace());
|
||||
return;
|
||||
}
|
||||
soundRange = MathHelper.Clamp(value, MinSoundRange, MaxSoundRange);
|
||||
@@ -52,7 +52,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (float.IsNaN(value))
|
||||
{
|
||||
DebugConsole.ThrowError("Attempted to set the SightRange of an AITarget to NaN.\n" + Environment.StackTrace);
|
||||
DebugConsole.ThrowError("Attempted to set the SightRange of an AITarget to NaN.\n" + Environment.StackTrace.CleanupStackTrace());
|
||||
return;
|
||||
}
|
||||
sightRange = MathHelper.Clamp(value, MinSightRange, MaxSightRange);
|
||||
@@ -74,7 +74,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (!MathUtils.IsValid(value))
|
||||
{
|
||||
string errorMsg = "Invalid AITarget sector direction (" + value + ")\n" + Environment.StackTrace;
|
||||
string errorMsg = "Invalid AITarget sector direction (" + value + ")\n" + Environment.StackTrace.CleanupStackTrace();
|
||||
DebugConsole.ThrowError(errorMsg);
|
||||
GameAnalyticsManager.AddErrorEventOnce("AITarget.SectorDir:" + entity?.ToString(), GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
|
||||
return;
|
||||
@@ -113,11 +113,11 @@ namespace Barotrauma
|
||||
if (entity == null || entity.Removed)
|
||||
{
|
||||
#if DEBUG
|
||||
DebugConsole.ThrowError("Attempted to access a removed AITarget\n" + Environment.StackTrace);
|
||||
DebugConsole.ThrowError("Attempted to access a removed AITarget\n" + Environment.StackTrace.CleanupStackTrace());
|
||||
#endif
|
||||
GameAnalyticsManager.AddErrorEventOnce("AITarget.WorldPosition:EntityRemoved",
|
||||
GameAnalyticsSDK.Net.EGAErrorSeverity.Error,
|
||||
"Attempted to access a removed AITarget\n" + Environment.StackTrace);
|
||||
"Attempted to access a removed AITarget\n" + Environment.StackTrace.CleanupStackTrace());
|
||||
return Vector2.Zero;
|
||||
}
|
||||
|
||||
@@ -132,11 +132,11 @@ namespace Barotrauma
|
||||
if (entity == null || entity.Removed)
|
||||
{
|
||||
#if DEBUG
|
||||
DebugConsole.ThrowError("Attempted to access a removed AITarget\n" + Environment.StackTrace);
|
||||
DebugConsole.ThrowError("Attempted to access a removed AITarget\n" + Environment.StackTrace.CleanupStackTrace());
|
||||
#endif
|
||||
GameAnalyticsManager.AddErrorEventOnce("AITarget.WorldPosition:EntityRemoved",
|
||||
GameAnalyticsSDK.Net.EGAErrorSeverity.Error,
|
||||
"Attempted to access a removed AITarget\n" + Environment.StackTrace);
|
||||
"Attempted to access a removed AITarget\n" + Environment.StackTrace.CleanupStackTrace());
|
||||
return Vector2.Zero;
|
||||
}
|
||||
|
||||
|
||||
@@ -56,6 +56,8 @@ namespace Barotrauma
|
||||
private float AggressionHurt => Character.Params.AI.AggressionHurt;
|
||||
private bool AggressiveBoarding => Character.Params.AI.AggressiveBoarding;
|
||||
|
||||
private FishAnimController FishAnimController => Character.AnimController as FishAnimController;
|
||||
|
||||
//a point in a wall which the Character is currently targeting
|
||||
private WallTarget wallTarget;
|
||||
|
||||
@@ -70,10 +72,6 @@ namespace Barotrauma
|
||||
_attackingLimb = value;
|
||||
attackVector = null;
|
||||
Reverse = _attackingLimb != null && _attackingLimb.attack.Reverse;
|
||||
if (Character.AnimController is FishAnimController fishController)
|
||||
{
|
||||
fishController.reverse = Reverse;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,6 +93,7 @@ namespace Barotrauma
|
||||
private readonly float avoidTime = 3;
|
||||
|
||||
private float avoidTimer;
|
||||
private float observeTimer;
|
||||
|
||||
public bool StayInsideLevel = true;
|
||||
|
||||
@@ -107,7 +106,7 @@ namespace Barotrauma
|
||||
{
|
||||
get
|
||||
{
|
||||
var target = GetTarget(CharacterPrefab.HumanSpeciesName);
|
||||
var target = GetTargetParams(CharacterPrefab.HumanSpeciesName);
|
||||
return target != null && target.Priority > 0.0f && (target.State == AIState.Attack || target.State == AIState.Aggressive);
|
||||
}
|
||||
}
|
||||
@@ -116,7 +115,7 @@ namespace Barotrauma
|
||||
{
|
||||
get
|
||||
{
|
||||
var target = GetTarget("room");
|
||||
var target = GetTargetParams("room");
|
||||
return target != null && target.Priority > 0.0f && (target.State == AIState.Attack || target.State == AIState.Aggressive);
|
||||
}
|
||||
}
|
||||
@@ -145,7 +144,16 @@ namespace Barotrauma
|
||||
public bool IsBeingChasedBy(Character c) => c.AIController is EnemyAIController enemyAI && enemyAI.SelectedAiTarget?.Entity is Character && (enemyAI.State == AIState.Aggressive || enemyAI.State == AIState.Attack);
|
||||
private bool IsBeingChased => SelectedAiTarget?.Entity is Character targetCharacter && IsBeingChasedBy(targetCharacter);
|
||||
|
||||
public bool Reverse { get; private set; }
|
||||
private bool reverse;
|
||||
public bool Reverse
|
||||
{
|
||||
get { return reverse; }
|
||||
private set
|
||||
{
|
||||
reverse = value;
|
||||
FishAnimController.reverse = reverse;
|
||||
}
|
||||
}
|
||||
|
||||
public EnemyAIController(Character c, string seed) : base(c)
|
||||
{
|
||||
@@ -214,7 +222,69 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
private CharacterParams.AIParams AIParams => Character.Params.AI;
|
||||
private CharacterParams.TargetParams GetTarget(string targetTag) => AIParams.GetTarget(targetTag, false);
|
||||
private CharacterParams.TargetParams GetTargetParams(string targetTag) => AIParams.GetTarget(targetTag, false);
|
||||
private CharacterParams.TargetParams GetTargetParams(AITarget aiTarget) => GetTargetParams(GetTargetingTag(aiTarget));
|
||||
private string GetTargetingTag(AITarget aiTarget)
|
||||
{
|
||||
if (aiTarget.Entity == null) { return null; }
|
||||
string targetingTag = null;
|
||||
if (aiTarget.Entity is Character targetCharacter)
|
||||
{
|
||||
if (targetCharacter.IsDead)
|
||||
{
|
||||
targetingTag = "dead";
|
||||
}
|
||||
else if (AIParams.TryGetTarget(targetCharacter.SpeciesName, out CharacterParams.TargetParams tP))
|
||||
{
|
||||
targetingTag = tP.Tag;
|
||||
}
|
||||
else if (targetCharacter.AIController is EnemyAIController enemy)
|
||||
{
|
||||
if (targetCharacter.IsHusk && AIParams.HasTag("husk"))
|
||||
{
|
||||
targetingTag = "husk";
|
||||
}
|
||||
else
|
||||
{
|
||||
if (enemy.CombatStrength > CombatStrength)
|
||||
{
|
||||
targetingTag = "stronger";
|
||||
}
|
||||
else if (enemy.CombatStrength < CombatStrength)
|
||||
{
|
||||
targetingTag = "weaker";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (aiTarget.Entity is Item targetItem)
|
||||
{
|
||||
foreach (var prio in AIParams.Targets)
|
||||
{
|
||||
if (targetItem.HasTag(prio.Tag))
|
||||
{
|
||||
targetingTag = prio.Tag;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (targetingTag == null)
|
||||
{
|
||||
if (targetItem.GetComponent<Sonar>() != null)
|
||||
{
|
||||
targetingTag = "sonar";
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (aiTarget.Entity is Structure)
|
||||
{
|
||||
targetingTag = "wall";
|
||||
}
|
||||
else if (aiTarget.Entity is Hull)
|
||||
{
|
||||
targetingTag = "room";
|
||||
}
|
||||
return targetingTag;
|
||||
}
|
||||
|
||||
public override void SelectTarget(AITarget target) => SelectTarget(target, 100);
|
||||
|
||||
@@ -295,8 +365,9 @@ namespace Barotrauma
|
||||
updateMemoriesTimer = updateMemoriesInverval;
|
||||
}
|
||||
if (Character.HealthPercentage <= FleeHealthThreshold && SelectedAiTarget != null &&
|
||||
SelectedAiTarget.Entity is Character target && (target.IsPlayer || IsBeingChasedBy(target)))
|
||||
SelectedAiTarget.Entity is Character target && (target.IsHuman && CanPerceive(SelectedAiTarget) || IsBeingChasedBy(target)))
|
||||
{
|
||||
// Keep fleeing if being chased
|
||||
State = AIState.Flee;
|
||||
wallTarget = null;
|
||||
}
|
||||
@@ -348,6 +419,7 @@ namespace Barotrauma
|
||||
steeringManager = insideSteering;
|
||||
}
|
||||
|
||||
bool useSteeringLengthAsMovementSpeed = State == AIState.Idle && Character.AnimController.InWater;
|
||||
bool run = false;
|
||||
switch (State)
|
||||
{
|
||||
@@ -425,7 +497,7 @@ namespace Barotrauma
|
||||
State = AIState.Idle;
|
||||
return;
|
||||
}
|
||||
if (SelectedAiTarget.Entity is Character targetCharacter && targetCharacter.LastAttacker is Character attacker)
|
||||
if (SelectedAiTarget.Entity is Character targetCharacter && targetCharacter.LastAttacker is Character attacker && !attacker.Removed && !attacker.IsDead)
|
||||
{
|
||||
// Attack the character that attacked the target we are protecting
|
||||
ChangeTargetState(attacker, AIState.Attack, selectedTargetingParams.Priority * 2);
|
||||
@@ -446,6 +518,63 @@ namespace Barotrauma
|
||||
UpdateIdle(deltaTime);
|
||||
}
|
||||
break;
|
||||
case AIState.Observe:
|
||||
if (SelectedAiTarget == null || SelectedAiTarget.Entity == null || SelectedAiTarget.Entity.Removed)
|
||||
{
|
||||
State = AIState.Idle;
|
||||
return;
|
||||
}
|
||||
run = false;
|
||||
sqrDist = Vector2.DistanceSquared(WorldPosition, SelectedAiTarget.WorldPosition);
|
||||
reactDist = selectedTargetingParams != null && selectedTargetingParams.ReactDistance > 0 ? selectedTargetingParams.ReactDistance : GetPerceivingRange(SelectedAiTarget);
|
||||
float halfReactDist = reactDist / 2;
|
||||
float attackDist = selectedTargetingParams != null && selectedTargetingParams.AttackDistance > 0 ? selectedTargetingParams.AttackDistance : halfReactDist;
|
||||
if (sqrDist > Math.Pow(reactDist, 2))
|
||||
{
|
||||
// Too far to react
|
||||
UpdateIdle(deltaTime);
|
||||
}
|
||||
else if (sqrDist < Math.Pow(attackDist + movementMargin, 2))
|
||||
{
|
||||
movementMargin = attackDist;
|
||||
SteeringManager.Reset();
|
||||
if (Character.AnimController.InWater)
|
||||
{
|
||||
useSteeringLengthAsMovementSpeed = true;
|
||||
Vector2 dir = Vector2.Normalize(SelectedAiTarget.WorldPosition - Character.WorldPosition);
|
||||
if (sqrDist < Math.Pow(attackDist * 0.75f, 2))
|
||||
{
|
||||
// Keep the distance, if too close
|
||||
dir = -dir;
|
||||
useSteeringLengthAsMovementSpeed = false;
|
||||
Reverse = true;
|
||||
run = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Reverse = false;
|
||||
}
|
||||
SteeringManager.SteeringManual(deltaTime, dir * 0.2f);
|
||||
}
|
||||
else
|
||||
{
|
||||
FaceTarget(SelectedAiTarget.Entity);
|
||||
}
|
||||
observeTimer -= deltaTime;
|
||||
if (observeTimer < 0)
|
||||
{
|
||||
IgnoreTarget(SelectedAiTarget);
|
||||
State = AIState.Idle;
|
||||
ResetAITarget();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
run = sqrDist > Math.Pow(attackDist * 2, 2);
|
||||
movementMargin = MathHelper.Clamp(movementMargin -= deltaTime, 0, attackDist);
|
||||
UpdateFollow(deltaTime);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
@@ -465,7 +594,8 @@ namespace Barotrauma
|
||||
SteerInsideLevel(deltaTime);
|
||||
float speed = Character.AnimController.GetCurrentSpeed(run && Character.CanRun);
|
||||
steeringManager.Update(speed);
|
||||
Character.AnimController.TargetMovement = Character.ApplyMovementLimits(Steering, State == AIState.Idle && Character.AnimController.InWater ? Steering.Length() : speed);
|
||||
float targetMovement = useSteeringLengthAsMovementSpeed ? Steering.Length() : speed;
|
||||
Character.AnimController.TargetMovement = Character.ApplyMovementLimits(Steering, targetMovement);
|
||||
if (Character.CurrentHull != null && Character.AnimController.InWater)
|
||||
{
|
||||
// Halve the swimming speed inside the sub
|
||||
@@ -601,7 +731,7 @@ namespace Barotrauma
|
||||
{
|
||||
// Steer away from the target if in the same room
|
||||
Vector2 escapeDir = Vector2.Normalize(SelectedAiTarget != null ? WorldPosition - SelectedAiTarget.WorldPosition : Character.AnimController.TargetMovement);
|
||||
if (!MathUtils.IsValid(escapeDir)) escapeDir = Vector2.UnitY;
|
||||
if (!MathUtils.IsValid(escapeDir)) { escapeDir = Vector2.UnitY; }
|
||||
SteeringManager.SteeringManual(deltaTime, escapeDir);
|
||||
}
|
||||
else if (pathSteering != null)
|
||||
@@ -922,7 +1052,7 @@ namespace Barotrauma
|
||||
}
|
||||
if (!Character.AnimController.SimplePhysicsEnabled && SelectedAiTarget.Entity.Submarine != null && Character.Submarine == null && (!canAttackDoors || !canAttackWalls || !AIParams.TargetOuterWalls))
|
||||
{
|
||||
if (Vector2.DistanceSquared(Character.WorldPosition, attackWorldPos) < 2000 * 2000)
|
||||
if (wallTarget == null && Vector2.DistanceSquared(Character.WorldPosition, attackWorldPos) < 2000 * 2000)
|
||||
{
|
||||
// Check that we are not bumping into a door or a wall
|
||||
Vector2 rayStart = SimPosition;
|
||||
@@ -1193,7 +1323,7 @@ namespace Barotrauma
|
||||
}
|
||||
if (AIParams.RandomAttack)
|
||||
{
|
||||
selectedLimb = ToolBox.SelectWeightedRandom(attackLimbs, weights, Rand.RandSync.Server);
|
||||
selectedLimb = ToolBox.SelectWeightedRandom(attackLimbs, weights, Rand.RandSync.Unsynced);
|
||||
attackLimbs.Clear();
|
||||
weights.Clear();
|
||||
}
|
||||
@@ -1268,7 +1398,7 @@ namespace Barotrauma
|
||||
LatchOntoAI?.SetAttachTarget(wall.Submarine.PhysicsBody.FarseerBody, wall.Submarine, ConvertUnits.ToSimUnits(sectionPos), attachTargetNormal);
|
||||
if (Character.AnimController.CanEnterSubmarine || !wall.SectionBodyDisabled(sectionIndex) && !IsWallDisabled(wall))
|
||||
{
|
||||
if (AIParams.TargetOuterWalls || wall.prefab.Tags.Contains("inner"))
|
||||
if (AIParams.TargetOuterWalls || wall.prefab.Tags.Contains("inner") || wall.Submarine != null && wall.Submarine == Character.Submarine)
|
||||
{
|
||||
wallTarget = new WallTarget(sectionPos, wall, sectionIndex);
|
||||
}
|
||||
@@ -1309,7 +1439,7 @@ namespace Barotrauma
|
||||
bool wasLatched = IsLatchedOnSub;
|
||||
Character.AnimController.ReleaseStuckLimbs();
|
||||
LatchOntoAI?.DeattachFromBody(cooldown: 1);
|
||||
if (attacker == null || attacker.AiTarget == null) { return; }
|
||||
if (attacker == null || attacker.AiTarget == null || attacker.Removed || attacker.IsDead) { return; }
|
||||
bool isFriendly = IsFriendly(Character, attacker);
|
||||
if (wasLatched)
|
||||
{
|
||||
@@ -1414,6 +1544,11 @@ namespace Barotrauma
|
||||
avoidTimer = avoidTime * Rand.Range(0.75f, 1.25f);
|
||||
SelectTarget(attacker.AiTarget);
|
||||
}
|
||||
if (Character.HealthPercentage <= FleeHealthThreshold)
|
||||
{
|
||||
avoidTimer = Rand.Range(15, 30);
|
||||
SelectTarget(attacker.AiTarget);
|
||||
}
|
||||
}
|
||||
|
||||
// 10 dmg, 100 health -> 0.1
|
||||
@@ -1534,7 +1669,7 @@ namespace Barotrauma
|
||||
{
|
||||
return;
|
||||
}
|
||||
steeringManager.SteeringManual(deltaTime, dir);
|
||||
SteeringManager.SteeringSeek(Character.GetRelativeSimPosition(SelectedAiTarget.Entity), 5);
|
||||
if (Character.AnimController.InWater)
|
||||
{
|
||||
SteeringManager.SteeringAvoid(deltaTime, lookAheadDistance: avoidLookAheadDistance, weight: 15);
|
||||
@@ -1557,6 +1692,7 @@ namespace Barotrauma
|
||||
foreach (AITarget aiTarget in AITarget.List)
|
||||
{
|
||||
if (!aiTarget.Enabled) { continue; }
|
||||
if (aiTarget.Entity == null) { continue; }
|
||||
if (ignoredTargets.Contains(aiTarget)) { continue; }
|
||||
if (Level.Loaded != null && aiTarget.WorldPosition.Y > Level.Loaded.Size.Y)
|
||||
{
|
||||
@@ -1626,7 +1762,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (aiTarget.Entity != null)
|
||||
else
|
||||
{
|
||||
// Ignore all structures and items inside wrecks
|
||||
if (aiTarget.Entity.Submarine != null && aiTarget.Entity.Submarine.Info.IsWreck) { continue; }
|
||||
@@ -1784,8 +1920,10 @@ namespace Barotrauma
|
||||
{
|
||||
targetingTag = "door";
|
||||
}
|
||||
if (door.Item.Submarine == null) { continue;}
|
||||
if (door.Item.Submarine == null) { continue; }
|
||||
bool isOutdoor = door.LinkedGap?.FlowTargetHull != null && !door.LinkedGap.IsRoomToRoom;
|
||||
// Ignore inner doors when outside
|
||||
if (character.CurrentHull == null && !isOutdoor) { continue; }
|
||||
bool isOpen = door.IsOpen || door.IsBroken;
|
||||
if (!isOpen && !canAttackDoors || (isOutdoor && !AIParams.TargetOuterWalls))
|
||||
{
|
||||
@@ -1795,26 +1933,22 @@ namespace Barotrauma
|
||||
if (isOpen && (!Character.AnimController.CanEnterSubmarine || !AggressiveBoarding))
|
||||
{
|
||||
// Ignore broken and open doors
|
||||
// Aggressive boarders don't ignore open doors, because they use them for get in.
|
||||
// Aggressive boarders don't ignore open doors, because they use them for getting in.
|
||||
continue;
|
||||
}
|
||||
if (AggressiveBoarding)
|
||||
{
|
||||
// Increase the priority if the character is outside and the door is from outside to inside
|
||||
if (character.CurrentHull == null && isOutdoor)
|
||||
if (character.CurrentHull == null)
|
||||
{
|
||||
valueModifier *= isOpen ? 5 : 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Inside
|
||||
// Inside -> ignore open doors and outer doors
|
||||
valueModifier *= isOpen || isOutdoor ? 0 : 1;
|
||||
}
|
||||
}
|
||||
else if (character.CurrentHull == null)
|
||||
{
|
||||
valueModifier = isOutdoor ? 1 : 0;
|
||||
}
|
||||
}
|
||||
else if (aiTarget.Entity is IDamageable targetDamageable && targetDamageable.Health <= 0.0f)
|
||||
{
|
||||
@@ -1823,8 +1957,13 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
if (targetingTag == null) { continue; }
|
||||
var targetParams = GetTarget(targetingTag);
|
||||
var targetParams = GetTargetParams(targetingTag);
|
||||
if (targetParams == null) { continue; }
|
||||
if (targetCharacter != null && targetCharacter.Submarine != Character.Submarine && targetParams.State == AIState.Observe)
|
||||
{
|
||||
// Don't allow to observe characters that are inside a different submarine / outside when we are inside.
|
||||
continue;
|
||||
}
|
||||
valueModifier *= targetParams.Priority;
|
||||
|
||||
if (valueModifier == 0.0f) { continue; }
|
||||
@@ -1939,7 +2078,7 @@ namespace Barotrauma
|
||||
newTarget = aiTarget;
|
||||
selectedTargetMemory = targetMemory;
|
||||
targetValue = valueModifier;
|
||||
targetingParams = GetTarget(targetingTag);
|
||||
targetingParams = GetTargetParams(targetingTag);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1999,7 +2138,7 @@ namespace Barotrauma
|
||||
{
|
||||
_selectedAiTarget = null;
|
||||
}
|
||||
else if (CanPerceive(_selectedAiTarget, distSquared: Vector2.DistanceSquared(Character.WorldPosition, _selectedAiTarget.WorldPosition)))
|
||||
else if (CanPerceive(_selectedAiTarget))
|
||||
{
|
||||
var memory = GetTargetMemory(_selectedAiTarget, false);
|
||||
if (memory != null)
|
||||
@@ -2040,7 +2179,7 @@ namespace Barotrauma
|
||||
removals.ForEach(r => targetMemories.Remove(r));
|
||||
}
|
||||
|
||||
private readonly float targetIgnoreTime = 5;
|
||||
private readonly float targetIgnoreTime = 10;
|
||||
private float targetIgnoreTimer;
|
||||
private readonly HashSet<AITarget> ignoredTargets = new HashSet<AITarget>();
|
||||
public void IgnoreTarget(AITarget target)
|
||||
@@ -2168,6 +2307,17 @@ namespace Barotrauma
|
||||
}
|
||||
#endregion
|
||||
|
||||
protected override void OnTargetChanged(AITarget previousTarget, AITarget newTarget)
|
||||
{
|
||||
base.OnTargetChanged(previousTarget, newTarget);
|
||||
if (newTarget == null) { return; }
|
||||
var targetParams = GetTargetParams(newTarget);
|
||||
if (targetParams != null)
|
||||
{
|
||||
observeTimer = targetParams.Timer * Rand.Range(0.75f, 1.25f);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnStateChanged(AIState from, AIState to)
|
||||
{
|
||||
LatchOntoAI?.DeattachFromBody();
|
||||
@@ -2189,13 +2339,17 @@ namespace Barotrauma
|
||||
|
||||
private bool CanPerceive(AITarget target, float dist = -1, float distSquared = -1)
|
||||
{
|
||||
if (distSquared > -1)
|
||||
if (dist > 0)
|
||||
{
|
||||
return distSquared <= MathUtils.Pow(target.SightRange * Sight, 2) || distSquared <= MathUtils.Pow(target.SoundRange * Hearing, 2);
|
||||
return dist <= target.SightRange * Sight || dist <= target.SoundRange * Hearing;
|
||||
}
|
||||
else
|
||||
{
|
||||
return dist <= target.SightRange * Sight || dist <= target.SoundRange * Hearing;
|
||||
if (distSquared < 0)
|
||||
{
|
||||
distSquared = Vector2.DistanceSquared(Character.WorldPosition, target.WorldPosition);
|
||||
}
|
||||
return distSquared <= MathUtils.Pow(target.SightRange * Sight, 2) || distSquared <= MathUtils.Pow(target.SoundRange * Hearing, 2);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -35,6 +35,14 @@ namespace Barotrauma
|
||||
public readonly HashSet<Hull> UnsafeHulls = new HashSet<Hull>();
|
||||
public readonly List<Item> IgnoredItems = new List<Item>();
|
||||
|
||||
private float respondToAttackTimer;
|
||||
private const float RespondToAttackInterval = 1.0f;
|
||||
|
||||
/// <summary>
|
||||
/// List of previous attacks done to this character
|
||||
/// </summary>
|
||||
private readonly Dictionary<Character, AttackResult> previousAttackResults = new Dictionary<Character, AttackResult>();
|
||||
|
||||
private class HullSafety
|
||||
{
|
||||
public float safety;
|
||||
@@ -138,6 +146,17 @@ namespace Barotrauma
|
||||
}
|
||||
if (isIncapacitated) { return; }
|
||||
|
||||
respondToAttackTimer -= deltaTime;
|
||||
if (respondToAttackTimer <= 0.0f)
|
||||
{
|
||||
foreach (var previousAttackResult in previousAttackResults)
|
||||
{
|
||||
RespondToAttack(previousAttackResult.Key, previousAttackResult.Value);
|
||||
}
|
||||
previousAttackResults.Clear();
|
||||
respondToAttackTimer = RespondToAttackInterval;
|
||||
}
|
||||
|
||||
base.Update(deltaTime);
|
||||
|
||||
foreach (var values in knownHulls)
|
||||
@@ -316,6 +335,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (Character.LockHands) { return; }
|
||||
if (ObjectiveManager.CurrentObjective == null) { return; }
|
||||
if (Character.CurrentHull == null) { return; }
|
||||
bool oxygenLow = !Character.AnimController.HeadInWater && Character.OxygenAvailable < CharacterHealth.LowOxygenThreshold;
|
||||
bool isCarrying = ObjectiveManager.HasActiveObjective<AIObjectiveContainItem>() || ObjectiveManager.HasActiveObjective<AIObjectiveDecontainItem>();
|
||||
|
||||
@@ -349,7 +369,9 @@ namespace Barotrauma
|
||||
if (!NeedsDivingGear(Character.CurrentHull, out bool needsSuit) || !needsSuit || oxygenLow)
|
||||
{
|
||||
bool shouldKeepTheGearOn = Character.AnimController.HeadInWater
|
||||
|| Character.Submarine.TeamID != Character.TeamID && Character.Submarine.TeamID != Character.TeamType.FriendlyNPC
|
||||
|| ObjectiveManager.IsCurrentObjective<AIObjectiveFindSafety>()
|
||||
|| ObjectiveManager.CurrentOrder is AIObjectiveGoTo goTo && goTo.Target == Character // wait order
|
||||
|| ObjectiveManager.CurrentObjective.GetSubObjectivesRecursive(true).Any(o => o.KeepDivingGearOn);
|
||||
if (oxygenLow && Character.CurrentHull.Oxygen > 0)
|
||||
{
|
||||
@@ -425,12 +447,14 @@ namespace Barotrauma
|
||||
{
|
||||
var decontainObjective = new AIObjectiveDecontainItem(Character, divingSuit, ObjectiveManager, targetContainer: targetContainer.GetComponent<ItemContainer>())
|
||||
{
|
||||
DropIfFailsToContain = false
|
||||
DropIfFails = false
|
||||
};
|
||||
decontainObjective.Abandoned += () =>
|
||||
{
|
||||
ReequipUnequipped();
|
||||
IgnoredItems.Add(targetContainer);
|
||||
};
|
||||
decontainObjective.Completed += () => ReequipUnequipped();
|
||||
ObjectiveManager.CurrentObjective.AddSubObjective(decontainObjective, addFirst: true);
|
||||
return;
|
||||
}
|
||||
@@ -451,7 +475,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (!mask.AllowedSlots.Contains(InvSlotType.Any) || !Character.Inventory.TryPutItem(mask, Character, new List<InvSlotType>() { InvSlotType.Any }))
|
||||
{
|
||||
if (oxygenLow || ObjectiveManager.GetCurrentPriority() >= AIObjectiveManager.RunPriority)
|
||||
if (ObjectiveManager.GetCurrentPriority() >= AIObjectiveManager.RunPriority)
|
||||
{
|
||||
mask.Drop(Character);
|
||||
}
|
||||
@@ -465,7 +489,12 @@ namespace Barotrauma
|
||||
if (targetContainer != null)
|
||||
{
|
||||
var decontainObjective = new AIObjectiveDecontainItem(Character, mask, ObjectiveManager, targetContainer: targetContainer.GetComponent<ItemContainer>());
|
||||
decontainObjective.Abandoned += () => IgnoredItems.Add(targetContainer);
|
||||
decontainObjective.Abandoned += () =>
|
||||
{
|
||||
ReequipUnequipped();
|
||||
IgnoredItems.Add(targetContainer);
|
||||
};
|
||||
decontainObjective.Completed += () => ReequipUnequipped();
|
||||
ObjectiveManager.CurrentObjective.AddSubObjective(decontainObjective, addFirst: true);
|
||||
return;
|
||||
}
|
||||
@@ -476,6 +505,10 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ReequipUnequipped();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -504,7 +537,11 @@ namespace Barotrauma
|
||||
if (targetContainer != null)
|
||||
{
|
||||
var decontainObjective = new AIObjectiveDecontainItem(Character, item, ObjectiveManager, targetContainer: targetContainer.GetComponent<ItemContainer>());
|
||||
decontainObjective.Abandoned += () => IgnoredItems.Add(targetContainer);
|
||||
decontainObjective.Abandoned += () =>
|
||||
{
|
||||
ReequipUnequipped();
|
||||
IgnoredItems.Add(targetContainer);
|
||||
};
|
||||
ObjectiveManager.CurrentObjective.AddSubObjective(decontainObjective, addFirst: true);
|
||||
return;
|
||||
}
|
||||
@@ -519,6 +556,18 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public void ReequipUnequipped()
|
||||
{
|
||||
foreach (var item in unequippedItems)
|
||||
{
|
||||
if (item != null && !item.Removed && Character.HasItem(item))
|
||||
{
|
||||
TakeItem(item, Character.Inventory, equip: true, dropOtherIfCannotMove: true, allowSwapping: true, storeUnequipped: false);
|
||||
}
|
||||
}
|
||||
unequippedItems.Clear();
|
||||
}
|
||||
|
||||
private enum FindItemState
|
||||
{
|
||||
None,
|
||||
@@ -528,10 +577,13 @@ namespace Barotrauma
|
||||
}
|
||||
private FindItemState findItemState;
|
||||
private int itemIndex;
|
||||
public bool FindSuitableContainer(Item containableItem, out Item suitableContainer)
|
||||
|
||||
public bool FindSuitableContainer(Item containableItem, out Item suitableContainer) => FindSuitableContainer(Character, containableItem, IgnoredItems, ref itemIndex, out suitableContainer);
|
||||
|
||||
public static bool FindSuitableContainer(Character character, Item containableItem, List<Item> ignoredItems, ref int itemIndex, out Item suitableContainer)
|
||||
{
|
||||
suitableContainer = null;
|
||||
if (Character.FindItem(ref itemIndex, out Item targetContainer, ignoredItems: IgnoredItems, customPriorityFunction: i =>
|
||||
if (character.FindItem(ref itemIndex, out Item targetContainer, ignoredItems: ignoredItems, customPriorityFunction: i =>
|
||||
{
|
||||
var container = i.GetComponent<ItemContainer>();
|
||||
if (container == null) { return 0; }
|
||||
@@ -683,6 +735,38 @@ namespace Barotrauma
|
||||
|
||||
public override void OnAttacked(Character attacker, AttackResult attackResult)
|
||||
{
|
||||
if (Character.IsDead) { return; }
|
||||
if (attacker == null || Character.IsPlayer)
|
||||
{
|
||||
// The player characters need to "respond" to the attack always, because the update loop doesn't run for them.
|
||||
// Otherwise other NPCs totally ignore when player characters are attacked.
|
||||
RespondToAttack(attacker, attackResult);
|
||||
return;
|
||||
}
|
||||
if (previousAttackResults.ContainsKey(attacker))
|
||||
{
|
||||
foreach (Affliction newAffliction in attackResult.Afflictions)
|
||||
{
|
||||
var matchingAffliction = previousAttackResults[attacker].Afflictions.Find(a => a.Prefab == newAffliction.Prefab && a.Source == newAffliction.Source);
|
||||
if (matchingAffliction == null)
|
||||
{
|
||||
previousAttackResults[attacker].Afflictions.Add(newAffliction);
|
||||
}
|
||||
else
|
||||
{
|
||||
matchingAffliction.Strength += newAffliction.Strength;
|
||||
}
|
||||
}
|
||||
previousAttackResults[attacker] = new AttackResult(previousAttackResults[attacker].Afflictions, previousAttackResults[attacker].HitLimb);
|
||||
}
|
||||
else
|
||||
{
|
||||
previousAttackResults.Add(attacker, attackResult);
|
||||
}
|
||||
}
|
||||
|
||||
private void RespondToAttack(Character attacker, AttackResult attackResult)
|
||||
{
|
||||
// excluding poisons etc
|
||||
float realDamage = attackResult.Damage;
|
||||
// including poisons etc
|
||||
@@ -691,7 +775,7 @@ namespace Barotrauma
|
||||
{
|
||||
totalDamage -= affliction.Prefab.KarmaChangeOnApplied * affliction.Strength;
|
||||
}
|
||||
if (totalDamage <= 0) { return; }
|
||||
if (totalDamage <= 0.01f) { return; }
|
||||
if (Character.IsBot)
|
||||
{
|
||||
if (attacker != null)
|
||||
@@ -735,7 +819,7 @@ namespace Barotrauma
|
||||
return;
|
||||
}
|
||||
float cumulativeDamage = GetDamageDoneByAttacker(attacker);
|
||||
if (!Character.IsSecurity && attacker.IsBot && !attacker.IsInstigator)
|
||||
if (!Character.IsSecurity && attacker.IsBot && Character.CombatAction == null)
|
||||
{
|
||||
if (cumulativeDamage > 1)
|
||||
{
|
||||
@@ -834,7 +918,17 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
if (attacker.TeamID == Character.TeamType.FriendlyNPC)
|
||||
// If there are any enemies around, just ignore the friendly fire
|
||||
if (Character.CharacterList.Any(ch => ch.Submarine == Character.Submarine && !ch.Removed && !ch.IsDead && !ch.IsIncapacitated && !IsFriendly(ch) && VisibleHulls.Contains(ch.CurrentHull)))
|
||||
{
|
||||
return AIObjectiveCombat.CombatMode.None;
|
||||
}
|
||||
if (Character.IsInstigator && attacker.IsPlayer)
|
||||
{
|
||||
// The guards don't react when the player attacks instigators.
|
||||
return c.IsSecurity ? AIObjectiveCombat.CombatMode.None : (Character.CombatAction != null ? Character.CombatAction.WitnessReaction : AIObjectiveCombat.CombatMode.Retreat);
|
||||
}
|
||||
else if (attacker.TeamID == Character.TeamType.FriendlyNPC)
|
||||
{
|
||||
if (c.IsSecurity)
|
||||
{
|
||||
@@ -847,11 +941,7 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Character.IsInstigator)
|
||||
{
|
||||
return c.IsSecurity ? AIObjectiveCombat.CombatMode.Arrest : AIObjectiveCombat.CombatMode.Retreat;
|
||||
}
|
||||
else if (cumulativeDamage > dmgThreshold)
|
||||
if (cumulativeDamage > dmgThreshold)
|
||||
{
|
||||
if (c.IsSecurity)
|
||||
{
|
||||
@@ -1006,39 +1096,51 @@ namespace Barotrauma
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryToMoveItem(Item item, Inventory targetInventory, bool dropIfCannotMove = true)
|
||||
private readonly HashSet<Item> unequippedItems = new HashSet<Item>();
|
||||
public bool TakeItem(Item item, Inventory targetInventory, bool equip, bool dropOtherIfCannotMove = true, bool allowSwapping = false, bool storeUnequipped = false)
|
||||
{
|
||||
var pickable = item.GetComponent<Pickable>();
|
||||
if (pickable == null) { return false; }
|
||||
int targetSlot = -1;
|
||||
//check if all the slots required by the item are free
|
||||
foreach (InvSlotType slots in pickable.AllowedSlots)
|
||||
if (equip)
|
||||
{
|
||||
if (slots.HasFlag(InvSlotType.Any)) { continue; }
|
||||
for (int i = 0; i < targetInventory.Items.Length; i++)
|
||||
int targetSlot = -1;
|
||||
//check if all the slots required by the item are free
|
||||
foreach (InvSlotType slots in pickable.AllowedSlots)
|
||||
{
|
||||
if (targetInventory is CharacterInventory characterInventory)
|
||||
if (slots.HasFlag(InvSlotType.Any)) { continue; }
|
||||
for (int i = 0; i < targetInventory.Items.Length; i++)
|
||||
{
|
||||
//slot not needed by the item, continue
|
||||
if (!slots.HasFlag(characterInventory.SlotTypes[i])) { continue; }
|
||||
}
|
||||
targetSlot = i;
|
||||
//slot free, continue
|
||||
var otherItem = targetInventory.Items[i];
|
||||
if (otherItem == null) { continue; }
|
||||
//try to move the existing item to LimbSlot.Any and continue if successful
|
||||
if (otherItem.AllowedSlots.Contains(InvSlotType.Any) && targetInventory.TryPutItem(otherItem, Character, new List<InvSlotType>() { InvSlotType.Any }))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (dropIfCannotMove)
|
||||
{
|
||||
//if everything else fails, simply drop the existing item
|
||||
otherItem.Drop(Character);
|
||||
if (targetInventory is CharacterInventory characterInventory)
|
||||
{
|
||||
//slot not needed by the item, continue
|
||||
if (!slots.HasFlag(characterInventory.SlotTypes[i])) { continue; }
|
||||
}
|
||||
targetSlot = i;
|
||||
//slot free, continue
|
||||
var otherItem = targetInventory.Items[i];
|
||||
if (otherItem == null) { continue; }
|
||||
//try to move the existing item to LimbSlot.Any and continue if successful
|
||||
if (otherItem.AllowedSlots.Contains(InvSlotType.Any) && targetInventory.TryPutItem(otherItem, Character, CharacterInventory.anySlot))
|
||||
{
|
||||
if (storeUnequipped && targetInventory.Owner == Character)
|
||||
{
|
||||
unequippedItems.Add(otherItem);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (dropOtherIfCannotMove)
|
||||
{
|
||||
//if everything else fails, simply drop the existing item
|
||||
otherItem.Drop(Character);
|
||||
}
|
||||
}
|
||||
}
|
||||
return targetInventory.TryPutItem(item, targetSlot, allowSwapping, allowCombine: false, Character);
|
||||
}
|
||||
else
|
||||
{
|
||||
return targetInventory.TryPutItem(item, Character, CharacterInventory.anySlot);
|
||||
}
|
||||
return targetInventory.TryPutItem(item, targetSlot, false, false, Character);
|
||||
}
|
||||
|
||||
public static bool NeedsDivingGear(Hull hull, out bool needsSuit)
|
||||
@@ -1107,7 +1209,7 @@ namespace Barotrauma
|
||||
}
|
||||
//if (!otherCharacter.IsFacing(thief.WorldPosition)) { continue; }
|
||||
if (!otherCharacter.CanSeeCharacter(thief)) { continue; }
|
||||
if (!someoneSpoke)
|
||||
if (!someoneSpoke && !character.IsIncapacitated && character.Stun <= 0.0f)
|
||||
{
|
||||
if (!item.StolenDuringRound && GameMain.GameSession?.Campaign?.Map?.CurrentLocation != null)
|
||||
{
|
||||
@@ -1306,7 +1408,7 @@ namespace Barotrauma
|
||||
// Use the cached visible hulls
|
||||
visibleHulls = VisibleHulls;
|
||||
}
|
||||
bool ignoreFire = objectiveManager.HasActiveObjective<AIObjectiveExtinguishFire>();
|
||||
bool ignoreFire = objectiveManager.CurrentOrder is AIObjectiveExtinguishFires extinguishOrder && extinguishOrder.Priority > 0 || objectiveManager.HasActiveObjective<AIObjectiveExtinguishFire>();
|
||||
bool ignoreWater = HasDivingSuit(character);
|
||||
bool ignoreOxygen = ignoreWater || HasDivingMask(character);
|
||||
bool ignoreEnemies = ObjectiveManager.IsCurrentObjective<AIObjectiveFightIntruders>();
|
||||
@@ -1391,13 +1493,10 @@ namespace Barotrauma
|
||||
return hullSafety.safety;
|
||||
}
|
||||
|
||||
public void FaceTarget(ISpatialEntity target) => Character.AnimController.TargetDir = target.WorldPosition.X > Character.WorldPosition.X ? Direction.Right : Direction.Left;
|
||||
|
||||
public static bool IsFriendly(Character me, Character other, bool onlySameTeam = false)
|
||||
{
|
||||
bool sameTeam = me.TeamID == other.TeamID;
|
||||
// Only enemies are in the Team "None"
|
||||
bool friendlyTeam = me.TeamID != Character.TeamType.None && other.TeamID != Character.TeamType.None;
|
||||
bool friendlyTeam = IsOnFriendlyTeam(GameMain.GameSession?.GameMode, me, other);
|
||||
bool teamGood = sameTeam || friendlyTeam && !onlySameTeam;
|
||||
if (!teamGood) { return false; }
|
||||
bool speciesGood = other.SpeciesName == me.SpeciesName || other.Params.CompareGroup(me.Params.Group);
|
||||
@@ -1413,6 +1512,18 @@ namespace Barotrauma
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool IsOnFriendlyTeam(GameMode mode, Character me, Character other)
|
||||
{
|
||||
// Only enemies are in the Team "None"
|
||||
bool friendlyTeam = me.TeamID != Character.TeamType.None && other.TeamID != Character.TeamType.None;
|
||||
// When playing a combat mission, we need to be on the same team to be friendlies
|
||||
if (friendlyTeam && mode is MissionMode mm && mm.Mission is CombatMission)
|
||||
{
|
||||
friendlyTeam = me.TeamID == other.TeamID;
|
||||
}
|
||||
return friendlyTeam;
|
||||
}
|
||||
|
||||
public static bool IsActive(Character other) => other != null && !other.Removed && !other.IsDead && !other.IsUnconscious;
|
||||
|
||||
public static bool IsTrueForAllCrewMembers(Character character, Func<HumanAIController, bool> predicate)
|
||||
|
||||
@@ -191,7 +191,7 @@ namespace Barotrauma
|
||||
}
|
||||
pathFinder.InsideSubmarine = character.Submarine != null;
|
||||
var newPath = pathFinder.FindPath(currentPos, target, character.Submarine, "(Character: " + character.Name + ")", startNodeFilter, endNodeFilter, nodeFilter, checkVisibility: checkVisibility);
|
||||
bool useNewPath = needsNewPath || currentPath == null || currentPath.CurrentNode == null;
|
||||
bool useNewPath = needsNewPath || currentPath == null || currentPath.CurrentNode == null || findPathTimer < -1;
|
||||
if (!useNewPath && currentPath != null && currentPath.CurrentNode != null && newPath.Nodes.Any() && !newPath.Unreachable)
|
||||
{
|
||||
// It's possible that the current path was calculated from a start point that is no longer valid.
|
||||
|
||||
@@ -15,6 +15,7 @@ namespace Barotrauma
|
||||
public virtual bool IgnoreUnsafeHulls => false;
|
||||
public virtual bool AbandonWhenCannotCompleteSubjectives => true;
|
||||
public virtual bool AllowSubObjectiveSorting => false;
|
||||
public virtual bool ForceOrderPriority => true;
|
||||
|
||||
/// <summary>
|
||||
/// Can there be multiple objective instaces of the same type?
|
||||
@@ -34,6 +35,7 @@ namespace Barotrauma
|
||||
public virtual bool AllowAutomaticItemUnequipping => false;
|
||||
public virtual bool AllowOutsideSubmarine => false;
|
||||
public virtual bool AllowInFriendlySubs => false;
|
||||
public virtual bool AllowInAnySub => false;
|
||||
|
||||
protected readonly List<AIObjective> subObjectives = new List<AIObjective>();
|
||||
private float _cumulatedDevotion;
|
||||
@@ -198,12 +200,10 @@ namespace Barotrauma
|
||||
{
|
||||
get
|
||||
{
|
||||
if (AllowOutsideSubmarine) { return true; }
|
||||
if (character.Submarine == null) { return false; }
|
||||
return
|
||||
character.Submarine.TeamID == character.TeamID ||
|
||||
(AllowInFriendlySubs && character.Submarine.TeamID == Character.TeamType.FriendlyNPC) ||
|
||||
character.Submarine.DockedTo.Any(sub => sub.TeamID == character.TeamID);
|
||||
if (!AllowOutsideSubmarine && character.Submarine == null) { return false; }
|
||||
if (AllowInAnySub) { return true; }
|
||||
if (AllowInFriendlySubs && character.Submarine.TeamID == Character.TeamType.FriendlyNPC) { return true; }
|
||||
return character.Submarine.TeamID == character.TeamID || character.Submarine.DockedTo.Any(sub => sub.TeamID == character.TeamID);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -212,12 +212,14 @@ namespace Barotrauma
|
||||
/// </summary>
|
||||
public virtual float GetPriority()
|
||||
{
|
||||
bool isOrder = objectiveManager.CurrentOrder == this;
|
||||
if (!IsAllowed)
|
||||
{
|
||||
Priority = 0;
|
||||
Abandon = !isOrder;
|
||||
return Priority;
|
||||
}
|
||||
if (objectiveManager.CurrentOrder == this)
|
||||
if (isOrder)
|
||||
{
|
||||
Priority = AIObjectiveManager.OrderPriority;
|
||||
}
|
||||
@@ -306,7 +308,7 @@ namespace Barotrauma
|
||||
return true;
|
||||
}
|
||||
#if DEBUG
|
||||
DebugConsole.ThrowError("Attempted to add a duplicate subobjective!\n" + Environment.StackTrace);
|
||||
DebugConsole.ThrowError("Attempted to add a duplicate subobjective!\n" + Environment.StackTrace.CleanupStackTrace());
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
+124
@@ -0,0 +1,124 @@
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Items.Components;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class AIObjectiveCleanupItem : AIObjective
|
||||
{
|
||||
public override string DebugTag => "cleanup item";
|
||||
public override bool KeepDivingGearOn => true;
|
||||
public override bool AllowAutomaticItemUnequipping => false;
|
||||
|
||||
public readonly Item item;
|
||||
public bool IsPriority { get; set; }
|
||||
|
||||
private readonly List<Item> ignoredContainers = new List<Item>();
|
||||
private AIObjectiveDecontainItem decontainObjective;
|
||||
private int itemIndex = 0;
|
||||
|
||||
public AIObjectiveCleanupItem(Item item, Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1)
|
||||
: base(character, objectiveManager, priorityModifier)
|
||||
{
|
||||
this.item = item;
|
||||
}
|
||||
|
||||
public override float GetPriority()
|
||||
{
|
||||
if (!IsAllowed)
|
||||
{
|
||||
Priority = 0;
|
||||
Abandon = true;
|
||||
return Priority;
|
||||
}
|
||||
else
|
||||
{
|
||||
float distanceFactor = 0.9f;
|
||||
if (!IsPriority && item.CurrentHull != character.CurrentHull)
|
||||
{
|
||||
float yDist = Math.Abs(character.WorldPosition.Y - item.WorldPosition.Y);
|
||||
yDist = yDist > 100 ? yDist * 5 : 0;
|
||||
float dist = Math.Abs(character.WorldPosition.X - item.WorldPosition.X) + yDist;
|
||||
distanceFactor = MathHelper.Lerp(0.9f, 0, MathUtils.InverseLerp(0, 5000, dist));
|
||||
}
|
||||
bool isSelected = character.HasItem(item);
|
||||
float selectedBonus = isSelected ? 100 - MaxDevotion : 0;
|
||||
float devotion = (CumulatedDevotion + selectedBonus) / 100;
|
||||
float reduction = IsPriority ? 1 : isSelected ? 2 : 3;
|
||||
float max = MathHelper.Min(AIObjectiveManager.OrderPriority - reduction, 90);
|
||||
Priority = MathHelper.Lerp(0, max, MathHelper.Clamp(devotion + (distanceFactor * PriorityModifier), 0, 1));
|
||||
}
|
||||
return Priority;
|
||||
}
|
||||
|
||||
protected override void Act(float deltaTime)
|
||||
{
|
||||
// Only continue when the get item sub objectives have been completed.
|
||||
if (subObjectives.Any()) { return; }
|
||||
if (HumanAIController.FindSuitableContainer(character, item, ignoredContainers, ref itemIndex, out Item suitableContainer))
|
||||
{
|
||||
itemIndex = 0;
|
||||
if (suitableContainer != null)
|
||||
{
|
||||
bool equip = item.HasTag(AIObjectiveFindDivingGear.HEAVY_DIVING_GEAR) || (
|
||||
item.GetComponent<Wearable>() == null &&
|
||||
item.AllowedSlots.None(s =>
|
||||
s == InvSlotType.Card ||
|
||||
s == InvSlotType.Head ||
|
||||
s == InvSlotType.Headset ||
|
||||
s == InvSlotType.InnerClothes ||
|
||||
s == InvSlotType.OuterClothes));
|
||||
TryAddSubObjective(ref decontainObjective, () => new AIObjectiveDecontainItem(character, item, objectiveManager, targetContainer: suitableContainer.GetComponent<ItemContainer>())
|
||||
{
|
||||
Equip = equip,
|
||||
DropIfFails = true
|
||||
},
|
||||
onCompleted: () =>
|
||||
{
|
||||
if (equip)
|
||||
{
|
||||
HumanAIController.ReequipUnequipped();
|
||||
}
|
||||
IsCompleted = true;
|
||||
},
|
||||
onAbandon: () =>
|
||||
{
|
||||
if (equip)
|
||||
{
|
||||
HumanAIController.ReequipUnequipped();
|
||||
}
|
||||
if (decontainObjective != null && decontainObjective.ContainObjective != null && decontainObjective.ContainObjective.CanBeCompleted)
|
||||
{
|
||||
ignoredContainers.Add(suitableContainer);
|
||||
}
|
||||
else
|
||||
{
|
||||
Abandon = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
Abandon = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
objectiveManager.GetObjective<AIObjectiveIdle>().Wander(deltaTime);
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool Check() => IsCompleted;
|
||||
|
||||
public override void Reset()
|
||||
{
|
||||
base.Reset();
|
||||
ignoredContainers.Clear();
|
||||
itemIndex = 0;
|
||||
decontainObjective = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
using Barotrauma.Items.Components;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class AIObjectiveCleanupItems : AIObjectiveLoop<Item>
|
||||
{
|
||||
public override string DebugTag => "cleanup items";
|
||||
public override bool KeepDivingGearOn => true;
|
||||
public override bool AllowAutomaticItemUnequipping => false;
|
||||
public override bool ForceOrderPriority => false;
|
||||
|
||||
public readonly Item prioritizedItem;
|
||||
|
||||
public AIObjectiveCleanupItems(Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1, Item prioritizedItem = null)
|
||||
: base(character, objectiveManager, priorityModifier)
|
||||
{
|
||||
this.prioritizedItem = prioritizedItem;
|
||||
}
|
||||
|
||||
protected override float TargetEvaluation() => Targets.Any() ? AIObjectiveManager.RunPriority - 1 : 0;
|
||||
|
||||
protected override bool Filter(Item target)
|
||||
{
|
||||
// If the target was selected as a valid target, we'll have to accept it so that the objective can be completed.
|
||||
// The validity changes when a character picks the item up.
|
||||
if (!IsValidTarget(target, character)) { return Objectives.ContainsKey(target) && IsItemInsideValidSubmarine(target, character); }
|
||||
if (target.CurrentHull.FireSources.Count > 0) { return false; }
|
||||
// Don't repair items in rooms that have enemies inside.
|
||||
if (Character.CharacterList.Any(c => c.CurrentHull == target.CurrentHull && !HumanAIController.IsFriendly(c) && HumanAIController.IsActive(c))) { return false; }
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override IEnumerable<Item> GetList() => Item.ItemList;
|
||||
|
||||
protected override AIObjective ObjectiveConstructor(Item item)
|
||||
=> new AIObjectiveCleanupItem(item, character, objectiveManager, priorityModifier: PriorityModifier)
|
||||
{
|
||||
IsPriority = prioritizedItem == item
|
||||
};
|
||||
|
||||
protected override void OnObjectiveCompleted(AIObjective objective, Item target)
|
||||
=> HumanAIController.RemoveTargets<AIObjectiveCleanupItems, Item>(character, target);
|
||||
|
||||
private static bool IsItemInsideValidSubmarine(Item item, Character character)
|
||||
{
|
||||
if (item.CurrentHull == null) { return false; }
|
||||
if (item.Submarine == null) { return false; }
|
||||
if (item.Submarine.TeamID != character.TeamID) { return false; }
|
||||
if (character.Submarine != null)
|
||||
{
|
||||
if (!character.Submarine.IsConnectedTo(item.Submarine)) { return false; }
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool IsValidTarget(Item item, Character character)
|
||||
{
|
||||
if (item == null) { return false; }
|
||||
if (item.NonInteractable) { return false; }
|
||||
if (item.ParentInventory != null) { return false; }
|
||||
if (character != null && !IsItemInsideValidSubmarine(item, character)) { return false; }
|
||||
//var rootContainer = item.GetRootContainer();
|
||||
//// Only target items lying on the ground (= not inside a container) (do we need this check?)
|
||||
//if (rootContainer != null) { return false; }
|
||||
var pickable = item.GetComponent<Pickable>();
|
||||
if (pickable == null) { return false; }
|
||||
var wire = item.GetComponent<Wire>();
|
||||
if (wire != null)
|
||||
{
|
||||
if (wire.Connections.Any()) { return false; }
|
||||
}
|
||||
else
|
||||
{
|
||||
var connectionPanel = item.GetComponent<ConnectionPanel>();
|
||||
if (connectionPanel != null && connectionPanel.Connections.Any(c => c.Wires.Any(w => w != null)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return item.Prefab.PreferredContainers.Any();
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
-9
@@ -15,6 +15,7 @@ namespace Barotrauma
|
||||
public override bool KeepDivingGearOn => true;
|
||||
public override bool IgnoreUnsafeHulls => true;
|
||||
public override bool AllowOutsideSubmarine => true;
|
||||
public override bool AllowInAnySub => true;
|
||||
|
||||
private readonly CombatMode initialMode;
|
||||
|
||||
@@ -54,8 +55,8 @@ namespace Barotrauma
|
||||
if (_weaponComponent == null)
|
||||
{
|
||||
_weaponComponent =
|
||||
Weapon.GetComponent<RangedWeapon>() as ItemComponent ??
|
||||
Weapon.GetComponent<MeleeWeapon>() as ItemComponent ??
|
||||
Weapon.GetComponent<RangedWeapon>() ??
|
||||
Weapon.GetComponent<MeleeWeapon>() ??
|
||||
Weapon.GetComponent<RepairTool>() as ItemComponent;
|
||||
}
|
||||
return _weaponComponent;
|
||||
@@ -144,6 +145,7 @@ namespace Barotrauma
|
||||
if (Enemy.Submarine == null || (Enemy.Submarine.TeamID != character.TeamID && Enemy.Submarine != character.Submarine))
|
||||
{
|
||||
Priority = 0;
|
||||
Abandon = true;
|
||||
return Priority;
|
||||
}
|
||||
}
|
||||
@@ -760,11 +762,7 @@ namespace Barotrauma
|
||||
if (HumanAIController.HasItem(character, "handlocker", out IEnumerable<Item> matchingItems) && Enemy.Stun > 0 && character.CanInteractWith(Enemy))
|
||||
{
|
||||
var handCuffs = matchingItems.First();
|
||||
if (HumanAIController.TryToMoveItem(handCuffs, Enemy.Inventory))
|
||||
{
|
||||
handCuffs.Equip(Enemy);
|
||||
}
|
||||
else
|
||||
if (!HumanAIController.TakeItem(handCuffs, Enemy.Inventory, equip: true))
|
||||
{
|
||||
#if DEBUG
|
||||
DebugConsole.NewMessage($"{character.Name}: Failed to handcuff the target.", Color.Red);
|
||||
@@ -777,7 +775,7 @@ namespace Barotrauma
|
||||
if (item.StolenDuringRound)
|
||||
{
|
||||
item.Drop(character);
|
||||
character.Inventory.TryPutItem(item, character, new List<InvSlotType>() { InvSlotType.Any });
|
||||
character.Inventory.TryPutItem(item, character, CharacterInventory.anySlot);
|
||||
}
|
||||
}
|
||||
character.Speak(TextManager.Get("DialogTargetArrested"), null, 3.0f, "targetarrested", 30.0f);
|
||||
@@ -885,7 +883,11 @@ namespace Barotrauma
|
||||
|
||||
private void Attack(float deltaTime)
|
||||
{
|
||||
character.CursorPosition = Enemy.Position;
|
||||
character.CursorPosition = Enemy.WorldPosition;
|
||||
if (character.Submarine != null)
|
||||
{
|
||||
character.CursorPosition -= character.Submarine.Position;
|
||||
}
|
||||
visibilityCheckTimer -= deltaTime;
|
||||
if (visibilityCheckTimer <= 0.0f)
|
||||
{
|
||||
|
||||
+28
-20
@@ -142,6 +142,7 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: should we just use GetItem?
|
||||
TryAddSubObjective(ref goToObjective, () => new AIObjectiveGoTo(container.Item, character, objectiveManager, getDivingGearIfNeeded: AllowToFindDivingGear)
|
||||
{
|
||||
DialogueIdentifier = "dialogcannotreachtarget",
|
||||
@@ -153,27 +154,34 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
// No matching items in the inventory, try to get an item
|
||||
TryAddSubObjective(ref getItemObjective, () =>
|
||||
new AIObjectiveGetItem(character, itemIdentifiers, objectiveManager, equip: Equip, checkInventory: checkInventory, spawnItemIfNotFound: spawnItemIfNotFound)
|
||||
{
|
||||
GetItemPriority = GetItemPriority,
|
||||
ignoredContainerIdentifiers = ignoredContainerIdentifiers,
|
||||
ignoredItems = containedItems,
|
||||
AllowToFindDivingGear = AllowToFindDivingGear,
|
||||
AllowDangerousPressure = AllowDangerousPressure,
|
||||
TargetCondition = ConditionLevel
|
||||
}, onAbandon: () =>
|
||||
{
|
||||
Abandon = true;
|
||||
}, onCompleted: () =>
|
||||
{
|
||||
if (getItemObjective?.TargetItem != null)
|
||||
if (character.Submarine == null)
|
||||
{
|
||||
Abandon = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// No matching items in the inventory, try to get an item
|
||||
TryAddSubObjective(ref getItemObjective, () =>
|
||||
new AIObjectiveGetItem(character, itemIdentifiers, objectiveManager, equip: Equip, checkInventory: checkInventory, spawnItemIfNotFound: spawnItemIfNotFound)
|
||||
{
|
||||
containedItems.Add(getItemObjective.TargetItem);
|
||||
}
|
||||
RemoveSubObjective(ref getItemObjective);
|
||||
});
|
||||
GetItemPriority = GetItemPriority,
|
||||
ignoredContainerIdentifiers = ignoredContainerIdentifiers,
|
||||
ignoredItems = containedItems,
|
||||
AllowToFindDivingGear = AllowToFindDivingGear,
|
||||
AllowDangerousPressure = AllowDangerousPressure,
|
||||
TargetCondition = ConditionLevel
|
||||
}, onAbandon: () =>
|
||||
{
|
||||
Abandon = true;
|
||||
}, onCompleted: () =>
|
||||
{
|
||||
if (getItemObjective?.TargetItem != null)
|
||||
{
|
||||
containedItems.Add(getItemObjective.TargetItem);
|
||||
}
|
||||
RemoveSubObjective(ref getItemObjective);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+26
-38
@@ -16,9 +16,12 @@ namespace Barotrauma
|
||||
private ItemContainer targetContainer;
|
||||
private readonly Item targetItem;
|
||||
|
||||
private AIObjectiveGoTo goToObjective;
|
||||
private AIObjectiveGetItem getItemObjective;
|
||||
private AIObjectiveContainItem containObjective;
|
||||
|
||||
public AIObjectiveGetItem GetItemObjective => getItemObjective;
|
||||
public AIObjectiveContainItem ContainObjective => containObjective;
|
||||
|
||||
public bool Equip { get; set; }
|
||||
|
||||
/// <summary>
|
||||
@@ -26,7 +29,7 @@ namespace Barotrauma
|
||||
/// In both cases abandons the objective.
|
||||
/// Note that has no effect if the target container was not defined (always drops) -> completes when the item is dropped.
|
||||
/// </summary>
|
||||
public bool DropIfFailsToContain { get; set; } = true;
|
||||
public bool DropIfFails { get; set; } = true;
|
||||
|
||||
public AIObjectiveDecontainItem(Character character, Item targetItem, AIObjectiveManager objectiveManager, ItemContainer sourceContainer = null, ItemContainer targetContainer = null, float priorityModifier = 1)
|
||||
: base(character, objectiveManager, priorityModifier)
|
||||
@@ -74,54 +77,30 @@ namespace Barotrauma
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
else if (targetContainer.Inventory.Items.Contains(itemToDecontain))
|
||||
{
|
||||
if (targetContainer.Inventory.Items.Contains(itemToDecontain))
|
||||
{
|
||||
IsCompleted = true;
|
||||
return;
|
||||
}
|
||||
IsCompleted = true;
|
||||
return;
|
||||
}
|
||||
if (goToObjective == null && !itemToDecontain.IsOwnedBy(character))
|
||||
if (getItemObjective == null && !itemToDecontain.IsOwnedBy(character))
|
||||
{
|
||||
if (sourceContainer == null)
|
||||
{
|
||||
Abandon = true;
|
||||
return;
|
||||
}
|
||||
if (!character.CanInteractWith(sourceContainer.Item, out _, checkLinked: false))
|
||||
{
|
||||
TryAddSubObjective(ref goToObjective,
|
||||
constructor: () => new AIObjectiveGoTo(sourceContainer.Item, character, objectiveManager)
|
||||
{
|
||||
// If the container changes, the item is no longer where it was
|
||||
abortCondition = () => itemToDecontain.Container != sourceContainer.Item,
|
||||
DialogueIdentifier = "dialogcannotreachtarget",
|
||||
TargetName = sourceContainer.Item.Name
|
||||
},
|
||||
onAbandon: () => Abandon = true);
|
||||
return;
|
||||
}
|
||||
TryAddSubObjective(ref getItemObjective,
|
||||
constructor: () => new AIObjectiveGetItem(character, targetItem, objectiveManager, Equip),
|
||||
onAbandon: () => Abandon = true);
|
||||
return;
|
||||
}
|
||||
if (targetContainer != null)
|
||||
{
|
||||
TryAddSubObjective(ref containObjective,
|
||||
constructor: () => new AIObjectiveContainItem(character, itemToDecontain, targetContainer, objectiveManager)
|
||||
{
|
||||
Equip = this.Equip,
|
||||
Equip = Equip,
|
||||
RemoveEmpty = false,
|
||||
GetItemPriority = this.GetItemPriority,
|
||||
GetItemPriority = GetItemPriority,
|
||||
ignoredContainerIdentifiers = sourceContainer != null ? new string[] { sourceContainer.Item.Prefab.Identifier } : null
|
||||
},
|
||||
onCompleted: () => IsCompleted = true,
|
||||
onAbandon: () =>
|
||||
{
|
||||
if (DropIfFailsToContain)
|
||||
{
|
||||
itemToDecontain.Drop(character);
|
||||
}
|
||||
Abandon = true;
|
||||
});
|
||||
onAbandon: () => Abandon = true);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -133,8 +112,17 @@ namespace Barotrauma
|
||||
public override void Reset()
|
||||
{
|
||||
base.Reset();
|
||||
goToObjective = null;
|
||||
getItemObjective = null;
|
||||
containObjective = null;
|
||||
}
|
||||
|
||||
protected override void OnAbandon()
|
||||
{
|
||||
base.OnAbandon();
|
||||
if (DropIfFails && targetItem != null && targetItem.IsOwnedBy(character))
|
||||
{
|
||||
targetItem.Drop(character);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+28
-12
@@ -13,6 +13,8 @@ namespace Barotrauma
|
||||
public override bool ConcurrentObjectives => true;
|
||||
public override bool KeepDivingGearOn => true;
|
||||
|
||||
public override bool AllowInAnySub => true;
|
||||
|
||||
private readonly Hull targetHull;
|
||||
|
||||
private AIObjectiveGetItem getExtinguisherObjective;
|
||||
@@ -30,12 +32,15 @@ namespace Barotrauma
|
||||
if (!IsAllowed)
|
||||
{
|
||||
Priority = 0;
|
||||
Abandon = true;
|
||||
return Priority;
|
||||
}
|
||||
if (!objectiveManager.IsCurrentOrder<AIObjectiveExtinguishFires>()
|
||||
&& Character.CharacterList.Any(c => c.CurrentHull == targetHull && !HumanAIController.IsFriendly(c) && HumanAIController.IsActive(c)))
|
||||
bool isOrder = objectiveManager.IsCurrentOrder<AIObjectiveExtinguishFires>();
|
||||
if (!isOrder && Character.CharacterList.Any(c => c.CurrentHull == targetHull && !HumanAIController.IsFriendly(c) && HumanAIController.IsActive(c)))
|
||||
{
|
||||
// Don't go into rooms with any enemies, unless it's an order
|
||||
Priority = 0;
|
||||
Abandon = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -43,14 +48,22 @@ namespace Barotrauma
|
||||
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));
|
||||
if (targetHull == character.CurrentHull)
|
||||
if (targetHull == character.CurrentHull || HumanAIController.VisibleHulls.Contains(targetHull))
|
||||
{
|
||||
distanceFactor = 1;
|
||||
}
|
||||
float severity = AIObjectiveExtinguishFires.GetFireSeverity(targetHull);
|
||||
float severityFactor = MathHelper.Lerp(0, 1, severity / 100);
|
||||
float devotion = CumulatedDevotion / 100;
|
||||
Priority = MathHelper.Lerp(0, 100, MathHelper.Clamp(devotion + (severityFactor * distanceFactor * PriorityModifier), 0, 1));
|
||||
if (severity > 0.5f && !isOrder)
|
||||
{
|
||||
// Ignore severe fires unless ordered. (Let the fire drain all the oxygen instead).
|
||||
Priority = 0;
|
||||
Abandon = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
float devotion = CumulatedDevotion / 100;
|
||||
Priority = MathHelper.Lerp(0, 100, MathHelper.Clamp(devotion + (severity * distanceFactor * PriorityModifier), 0, 1));
|
||||
}
|
||||
}
|
||||
return Priority;
|
||||
}
|
||||
@@ -131,13 +144,16 @@ namespace Barotrauma
|
||||
if (move)
|
||||
{
|
||||
//go to the first firesource
|
||||
TryAddSubObjective(ref gotoObjective, () => new AIObjectiveGoTo(fs, character, objectiveManager, closeEnough: extinguisher.Range / 2)
|
||||
if (TryAddSubObjective(ref gotoObjective, () => new AIObjectiveGoTo(fs, character, objectiveManager, closeEnough: extinguisher.Range / 2)
|
||||
{
|
||||
DialogueIdentifier = "dialogcannotreachfire",
|
||||
TargetName = fs.Hull.DisplayName
|
||||
},
|
||||
onAbandon: () => Abandon = true,
|
||||
onCompleted: () => RemoveSubObjective(ref gotoObjective)))
|
||||
{
|
||||
DialogueIdentifier = "dialogcannotreachfire",
|
||||
TargetName = fs.Hull.DisplayName
|
||||
},
|
||||
onAbandon: () => Abandon = true,
|
||||
onCompleted: () => RemoveSubObjective(ref gotoObjective));
|
||||
gotoObjective.requiredCondition = () => HumanAIController.VisibleHulls.Contains(fs.Hull);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
+14
-19
@@ -1,6 +1,8 @@
|
||||
using System.Linq;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using Barotrauma.Extensions;
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -8,14 +10,22 @@ namespace Barotrauma
|
||||
{
|
||||
public override string DebugTag => "extinguish fires";
|
||||
public override bool ForceRun => true;
|
||||
public override bool AllowInAnySub => true;
|
||||
|
||||
public AIObjectiveExtinguishFires(Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1) : base(character, objectiveManager, priorityModifier) { }
|
||||
|
||||
protected override bool Filter(Hull hull) => IsValidTarget(hull, character);
|
||||
|
||||
protected override float TargetEvaluation() => Targets.Sum(t => GetFireSeverity(t));
|
||||
protected override float TargetEvaluation() =>
|
||||
// If any target is visible -> 100 priority
|
||||
Targets.Any(t => t == character.CurrentHull || HumanAIController.VisibleHulls.Contains(t)) ? 100 :
|
||||
// Else based on the fire severity
|
||||
Targets.Sum(t => GetFireSeverity(t) * 100);
|
||||
|
||||
public static float GetFireSeverity(Hull hull) => hull.FireSources.Sum(fs => fs.Size.X);
|
||||
/// <summary>
|
||||
/// 0-1 based on the horizontal size of all of the fires in the hull.
|
||||
/// </summary>
|
||||
public static float GetFireSeverity(Hull hull) => MathHelper.Lerp(0, 1, MathUtils.InverseLerp(0, Math.Min(hull.Rect.Width, 1000), hull.FireSources.Sum(fs => fs.Size.X)));
|
||||
|
||||
protected override IEnumerable<Hull> GetList() => Hull.hullList;
|
||||
|
||||
@@ -31,22 +41,7 @@ namespace Barotrauma
|
||||
if (hull.FireSources.None()) { return false; }
|
||||
if (hull.Submarine == null) { return false; }
|
||||
if (character.Submarine == null) { return false; }
|
||||
if (!character.Submarine.IsConnectedTo(hull.Submarine)) { return false; }
|
||||
if (character.AIController is HumanAIController humanAI)
|
||||
{
|
||||
if (hull.Submarine.TeamID != character.TeamID)
|
||||
{
|
||||
if (humanAI.ObjectiveManager.IsCurrentOrder<AIObjectiveExtinguishFires>())
|
||||
{
|
||||
// For orders, allow targets in the current sub (for example if the bot is inside an outpost or a wreck)
|
||||
if (hull.Submarine != character.Submarine) { return false; }
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!character.Submarine.IsEntityFoundOnThisSub(hull, includingConnectedSubs: true)) { return false; }
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -17,7 +17,7 @@ namespace Barotrauma
|
||||
private Item targetItem;
|
||||
|
||||
public static float MIN_OXYGEN = 10;
|
||||
public static string HEAVY_DIVING_GEAR = "heavydiving";
|
||||
public static string HEAVY_DIVING_GEAR = "deepdiving";
|
||||
public static string LIGHT_DIVING_GEAR = "lightdiving";
|
||||
public static string OXYGEN_SOURCE = "oxygensource";
|
||||
|
||||
|
||||
+2
-1
@@ -14,8 +14,9 @@ namespace Barotrauma
|
||||
public override bool IgnoreUnsafeHulls => true;
|
||||
public override bool ConcurrentObjectives => true;
|
||||
public override bool AllowOutsideSubmarine => true;
|
||||
public override bool AllowInAnySub => true;
|
||||
public override bool AbandonWhenCannotCompleteSubjectives => false;
|
||||
public override bool IsLoop { get => true; set => throw new Exception("Trying to set the value for IsLoop from: " + Environment.StackTrace); }
|
||||
public override bool IsLoop { get => true; set => throw new Exception("Trying to set the value for IsLoop from: " + Environment.StackTrace.CleanupStackTrace()); }
|
||||
|
||||
// TODO: expose?
|
||||
const float priorityIncrease = 100;
|
||||
|
||||
+13
-7
@@ -12,6 +12,7 @@ namespace Barotrauma
|
||||
public override string DebugTag => "fix leak";
|
||||
public override bool ForceRun => true;
|
||||
public override bool KeepDivingGearOn => true;
|
||||
public override bool AllowInAnySub => true;
|
||||
|
||||
public Gap Leak { get; private set; }
|
||||
|
||||
@@ -35,11 +36,7 @@ namespace Barotrauma
|
||||
if (!IsAllowed)
|
||||
{
|
||||
Priority = 0;
|
||||
return Priority;
|
||||
}
|
||||
if (Leak.Removed || Leak.Open <= 0)
|
||||
{
|
||||
Priority = 0;
|
||||
Abandon = true;
|
||||
}
|
||||
else if (HumanAIController.IsTrueForAnyCrewMember(other => other != HumanAIController && other.ObjectiveManager.GetActiveObjective<AIObjectiveFixLeak>()?.Leak == Leak))
|
||||
{
|
||||
@@ -116,7 +113,7 @@ namespace Barotrauma
|
||||
{
|
||||
HumanAIController.AnimController.Crouching = true;
|
||||
}
|
||||
float reach = repairTool.Range + ConvertUnits.ToDisplayUnits(((HumanoidAnimController)character.AnimController).ArmLength);
|
||||
float reach = CalculateReach(repairTool, character);
|
||||
bool canOperate = toLeak.LengthSquared() < reach * reach;
|
||||
if (canOperate)
|
||||
{
|
||||
@@ -144,7 +141,7 @@ namespace Barotrauma
|
||||
onAbandon: () =>
|
||||
{
|
||||
if (Check()) { IsCompleted = true; }
|
||||
else if ((Leak.WorldPosition - character.WorldPosition).LengthSquared() > reach * reach * 2)
|
||||
else if ((Leak.WorldPosition - character.WorldPosition).LengthSquared() > MathUtils.Pow(reach * 2, 2))
|
||||
{
|
||||
// Too far
|
||||
Abandon = true;
|
||||
@@ -167,5 +164,14 @@ namespace Barotrauma
|
||||
gotoObjective = null;
|
||||
operateObjective = null;
|
||||
}
|
||||
|
||||
public static float CalculateReach(RepairTool repairTool, Character character)
|
||||
{
|
||||
float armLength = ConvertUnits.ToDisplayUnits(((HumanoidAnimController)character.AnimController).ArmLength);
|
||||
// This is an approximation, because we don't know the exact reach until the pose is taken.
|
||||
// And even then the actual range depends on the direction we are aiming to.
|
||||
// Found out that without any multiplier the value (209) is often too short.
|
||||
return repairTool.Range + armLength * 1.2f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+4
-6
@@ -9,6 +9,7 @@ namespace Barotrauma
|
||||
public override string DebugTag => "fix leaks";
|
||||
public override bool ForceRun => true;
|
||||
public override bool KeepDivingGearOn => true;
|
||||
public override bool AllowInAnySub => true;
|
||||
private Hull PrioritizedHull { get; set; }
|
||||
|
||||
public AIObjectiveFixLeaks(Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1, Hull prioritizedHull = null) : base(character, objectiveManager, priorityModifier)
|
||||
@@ -71,12 +72,9 @@ namespace Barotrauma
|
||||
{
|
||||
if (gap == null) { return false; }
|
||||
if (gap.ConnectedWall == null || gap.ConnectedDoor != null || gap.Open <= 0 || gap.linkedTo.All(l => l == null)) { return false; }
|
||||
if (gap.Submarine == null) { return false; }
|
||||
if (gap.Submarine.TeamID != character.TeamID) { return false; }
|
||||
if (character.Submarine != null)
|
||||
{
|
||||
if (!character.Submarine.IsConnectedTo(gap.Submarine)) { return false; }
|
||||
}
|
||||
if (gap.Submarine == null || character.Submarine == null) { return false; }
|
||||
// Don't allow going into another sub, unless it's connected and of the same team and type.
|
||||
if (!character.Submarine.IsEntityFoundOnThisSub(gap, includingConnectedSubs: true)) { return false; }
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
+44
-13
@@ -37,6 +37,7 @@ namespace Barotrauma
|
||||
public static float DefaultReach = 100;
|
||||
|
||||
public bool AllowToFindDivingGear { get; set; } = true;
|
||||
public bool MustBeSpecificItem { get; set; }
|
||||
|
||||
public AIObjectiveGetItem(Character character, Item targetItem, AIObjectiveManager objectiveManager, bool equip = true, float priorityModifier = 1)
|
||||
: base(character, objectiveManager, priorityModifier)
|
||||
@@ -84,6 +85,11 @@ namespace Barotrauma
|
||||
Abandon = true;
|
||||
return;
|
||||
}
|
||||
if (character.Submarine == null)
|
||||
{
|
||||
Abandon = true;
|
||||
return;
|
||||
}
|
||||
if (identifiersOrTags != null && !isDoneSeeking)
|
||||
{
|
||||
if (checkInventory)
|
||||
@@ -109,7 +115,10 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
FindTargetItem();
|
||||
objectiveManager.GetObjective<AIObjectiveIdle>().Wander(deltaTime);
|
||||
if (!objectiveManager.IsCurrentOrder<AIObjectiveGoTo>())
|
||||
{
|
||||
objectiveManager.GetObjective<AIObjectiveIdle>().Wander(deltaTime);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -137,7 +146,8 @@ namespace Barotrauma
|
||||
if (originalTarget == null)
|
||||
{
|
||||
// Try again
|
||||
Reset();
|
||||
ignoredItems.Add(targetItem);
|
||||
ResetInternal();
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -176,12 +186,8 @@ namespace Barotrauma
|
||||
return;
|
||||
}
|
||||
|
||||
if (HumanAIController.TryToMoveItem(targetItem, character.Inventory))
|
||||
if (HumanAIController.TakeItem(targetItem, character.Inventory, equip, storeUnequipped: true))
|
||||
{
|
||||
if (equip)
|
||||
{
|
||||
targetItem.Equip(character);
|
||||
}
|
||||
IsCompleted = true;
|
||||
}
|
||||
else
|
||||
@@ -207,8 +213,16 @@ namespace Barotrauma
|
||||
},
|
||||
onAbandon: () =>
|
||||
{
|
||||
ignoredItems.Add(targetItem);
|
||||
Reset();
|
||||
if (originalTarget == null)
|
||||
{
|
||||
// Try again
|
||||
ignoredItems.Add(targetItem);
|
||||
ResetInternal();
|
||||
}
|
||||
else
|
||||
{
|
||||
Abandon = true;
|
||||
}
|
||||
},
|
||||
onCompleted: () => RemoveSubObjective(ref goToObjective));
|
||||
}
|
||||
@@ -235,13 +249,13 @@ namespace Barotrauma
|
||||
Submarine mySub = character.Submarine;
|
||||
if (itemSub == null) { continue; }
|
||||
if (mySub == null) { continue; }
|
||||
if (itemSub.TeamID != mySub.TeamID && itemSub.TeamID != character.TeamID) { continue; }
|
||||
if (!CheckItem(item)) { continue; }
|
||||
if (ignoredContainerIdentifiers != null && item.Container != null)
|
||||
{
|
||||
if (ignoredContainerIdentifiers.Contains(item.ContainerIdentifier)) { continue; }
|
||||
}
|
||||
if (!mySub.IsConnectedTo(itemSub)) { continue; }
|
||||
// Don't allow going into another sub, unless it's connected and of the same team and type.
|
||||
if (!character.Submarine.IsEntityFoundOnThisSub(item, includingConnectedSubs: true)) { continue; }
|
||||
if (character.IsItemTakenBySomeoneElse(item)) { continue; }
|
||||
float itemPriority = 1;
|
||||
if (GetItemPriority != null)
|
||||
@@ -272,7 +286,7 @@ namespace Barotrauma
|
||||
if (!(MapEntityPrefab.List.FirstOrDefault(me => me is ItemPrefab ip && identifiersOrTags.Any(id => id == ip.Identifier || ip.Tags.Contains(id))) is ItemPrefab prefab))
|
||||
{
|
||||
#if DEBUG
|
||||
DebugConsole.NewMessage($"{character.Name}: Cannot find the item with the following identifier(s): {string.Join(", ", identifiersOrTags)}, tried to spawn the item but no matching item prefabs were found.", Color.Yellow);
|
||||
DebugConsole.NewMessage($"{character.Name}: Cannot find the item with the following identifier(s) or tag(s): {string.Join(", ", identifiersOrTags)}, tried to spawn the item but no matching item prefabs were found.", Color.Yellow);
|
||||
#endif
|
||||
Abandon = true;
|
||||
}
|
||||
@@ -291,7 +305,7 @@ namespace Barotrauma
|
||||
else
|
||||
{
|
||||
#if DEBUG
|
||||
DebugConsole.NewMessage($"{character.Name}: Cannot find the item with the following identifier(s): {string.Join(", ", identifiersOrTags)}", Color.Yellow);
|
||||
DebugConsole.NewMessage($"{character.Name}: Cannot find the item with the following identifier(s) or tag(s): {string.Join(", ", identifiersOrTags)}", Color.Yellow);
|
||||
#endif
|
||||
Abandon = true;
|
||||
}
|
||||
@@ -330,11 +344,28 @@ namespace Barotrauma
|
||||
public override void Reset()
|
||||
{
|
||||
base.Reset();
|
||||
ResetInternal();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Does not reset the ignored items list
|
||||
/// </summary>
|
||||
private void ResetInternal()
|
||||
{
|
||||
goToObjective = null;
|
||||
targetItem = originalTarget;
|
||||
moveToTarget = targetItem?.GetRootInventoryOwner();
|
||||
isDoneSeeking = false;
|
||||
currSearchIndex = 0;
|
||||
}
|
||||
|
||||
protected override void OnAbandon()
|
||||
{
|
||||
base.OnAbandon();
|
||||
if (objectiveManager.CurrentOrder != null)
|
||||
{
|
||||
character.Speak(TextManager.Get("DialogCannotFindItem"), null, 0.0f, "cannotfinditem", 10.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+117
-15
@@ -1,5 +1,6 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
@@ -50,6 +51,7 @@ namespace Barotrauma
|
||||
public override bool AbandonWhenCannotCompleteSubjectives => !repeat;
|
||||
|
||||
public override bool AllowOutsideSubmarine => AllowGoingOutside;
|
||||
public override bool AllowInAnySub => true;
|
||||
|
||||
public string DialogueIdentifier { get; set; }
|
||||
public string TargetName { get; set; }
|
||||
@@ -60,17 +62,27 @@ namespace Barotrauma
|
||||
|
||||
public override float GetPriority()
|
||||
{
|
||||
bool isOrder = objectiveManager.CurrentOrder == this;
|
||||
if (!IsAllowed)
|
||||
{
|
||||
Priority = 0;
|
||||
Abandon = !isOrder;
|
||||
return Priority;
|
||||
}
|
||||
if (followControlledCharacter && Character.Controlled == null)
|
||||
{
|
||||
Priority = 0;
|
||||
Abandon = !isOrder;
|
||||
}
|
||||
if (Target is Entity e && e.Removed)
|
||||
{
|
||||
Priority = 0;
|
||||
Abandon = !isOrder;
|
||||
}
|
||||
if (IgnoreIfTargetDead && Target is Character character && character.IsDead)
|
||||
{
|
||||
Priority = 0;
|
||||
Abandon = !isOrder;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -84,7 +96,7 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
Priority = objectiveManager.CurrentOrder == this ? AIObjectiveManager.OrderPriority : 10;
|
||||
Priority = isOrder ? AIObjectiveManager.OrderPriority : 10;
|
||||
}
|
||||
}
|
||||
return Priority;
|
||||
@@ -93,7 +105,7 @@ namespace Barotrauma
|
||||
public AIObjectiveGoTo(ISpatialEntity target, Character character, AIObjectiveManager objectiveManager, bool repeat = false, bool getDivingGearIfNeeded = true, float priorityModifier = 1, float closeEnough = 0)
|
||||
: base(character, objectiveManager, priorityModifier)
|
||||
{
|
||||
this.Target = target;
|
||||
Target = target;
|
||||
this.repeat = repeat;
|
||||
waitUntilPathUnreachable = 3.0f;
|
||||
this.getDivingGearIfNeeded = getDivingGearIfNeeded;
|
||||
@@ -260,6 +272,53 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!character.AnimController.InWater)
|
||||
{
|
||||
useScooter = false;
|
||||
checkScooterTimer = 0;
|
||||
}
|
||||
else if (checkScooterTimer <= 0)
|
||||
{
|
||||
useScooter = false;
|
||||
checkScooterTimer = checkScooterTime;
|
||||
string scooterTag = "scooter";
|
||||
string batteryTag = "mobilebattery";
|
||||
Item scooter = null;
|
||||
bool isScooterEquipped = false;
|
||||
float closeEnough = 250;
|
||||
float squaredDistance = Vector2.DistanceSquared(character.WorldPosition, Target.WorldPosition);
|
||||
bool shouldUseScooter = squaredDistance > closeEnough * closeEnough && (!mimic ||
|
||||
(Target is Character targetCharacter && targetCharacter.HasEquippedItem(scooterTag, allowBroken: false)) || squaredDistance > Math.Pow(closeEnough * 2, 2));
|
||||
if (HumanAIController.HasItem(character, scooterTag, out IEnumerable<Item> equippedScooters, batteryTag, requireEquipped: true))
|
||||
{
|
||||
scooter = equippedScooters.FirstOrDefault();
|
||||
isScooterEquipped = scooter != null;
|
||||
}
|
||||
else if (shouldUseScooter && HumanAIController.HasItem(character, scooterTag, out IEnumerable<Item> scooters, batteryTag, requireEquipped: false))
|
||||
{
|
||||
scooter = scooters.FirstOrDefault();
|
||||
if (scooter != null)
|
||||
{
|
||||
isScooterEquipped = HumanAIController.TakeItem(scooter, character.Inventory, equip: true, dropOtherIfCannotMove: false, allowSwapping: true, storeUnequipped: false);
|
||||
}
|
||||
}
|
||||
if (scooter != null && isScooterEquipped)
|
||||
{
|
||||
if (shouldUseScooter)
|
||||
{
|
||||
useScooter = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Unequip
|
||||
character.Inventory.TryPutItem(scooter, character, CharacterInventory.anySlot);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
checkScooterTimer -= deltaTime;
|
||||
}
|
||||
if (SteeringManager == PathSteering)
|
||||
{
|
||||
Func<PathNode, bool> nodeFilter = null;
|
||||
@@ -274,47 +333,90 @@ namespace Barotrauma
|
||||
}, endNodeFilter, nodeFilter, CheckVisibility);
|
||||
if (!isInside && PathSteering.CurrentPath == null || PathSteering.IsPathDirty || PathSteering.CurrentPath.Unreachable)
|
||||
{
|
||||
SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(Target.WorldPosition - character.WorldPosition));
|
||||
if (character.AnimController.InWater)
|
||||
if (useScooter)
|
||||
{
|
||||
SteeringManager.SteeringAvoid(deltaTime, lookAheadDistance: 5, weight: 15);
|
||||
UseScooter(Target.WorldPosition);
|
||||
}
|
||||
else
|
||||
{
|
||||
SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(Target.WorldPosition - character.WorldPosition));
|
||||
if (character.AnimController.InWater)
|
||||
{
|
||||
SteeringManager.SteeringAvoid(deltaTime, lookAheadDistance: 5, weight: 15);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (useScooter && PathSteering.CurrentPath?.CurrentNode != null)
|
||||
{
|
||||
UseScooter(PathSteering.CurrentPath.CurrentNode.WorldPosition);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
SteeringManager.SteeringSeek(character.GetRelativeSimPosition(Target), 10);
|
||||
SteeringManager.SteeringAvoid(deltaTime, lookAheadDistance: 5, weight: 15);
|
||||
if (useScooter)
|
||||
{
|
||||
UseScooter(Target.WorldPosition);
|
||||
}
|
||||
else
|
||||
{
|
||||
SteeringManager.SteeringSeek(character.GetRelativeSimPosition(Target), 10);
|
||||
SteeringManager.SteeringAvoid(deltaTime, lookAheadDistance: 5, weight: 15);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UseScooter(Vector2 targetWorldPos)
|
||||
{
|
||||
SteeringManager.Reset();
|
||||
character.CursorPosition = targetWorldPos;
|
||||
if (character.Submarine != null)
|
||||
{
|
||||
character.CursorPosition -= character.Submarine.Position;
|
||||
}
|
||||
Vector2 dir = Vector2.Normalize(character.CursorPosition - character.Position);
|
||||
if (!MathUtils.IsValid(dir)) { dir = Vector2.UnitY; }
|
||||
SteeringManager.SteeringManual(1.0f, dir);
|
||||
character.SetInput(InputType.Aim, false, true);
|
||||
character.SetInput(InputType.Shoot, false, true);
|
||||
}
|
||||
}
|
||||
|
||||
public Hull GetTargetHull()
|
||||
private bool useScooter;
|
||||
private float checkScooterTimer;
|
||||
private readonly float checkScooterTime = 0.2f;
|
||||
|
||||
public Hull GetTargetHull() => GetTargetHull(Target);
|
||||
|
||||
public static Hull GetTargetHull(ISpatialEntity target)
|
||||
{
|
||||
if (Target is Hull h)
|
||||
if (target is Hull h)
|
||||
{
|
||||
return h;
|
||||
}
|
||||
else if (Target is Item i)
|
||||
else if (target is Item i)
|
||||
{
|
||||
return i.CurrentHull;
|
||||
}
|
||||
else if (Target is Character c)
|
||||
else if (target is Character c)
|
||||
{
|
||||
return c.CurrentHull;
|
||||
}
|
||||
else if (Target is Gap g)
|
||||
else if (target is Gap g)
|
||||
{
|
||||
return g.FlowTargetHull;
|
||||
}
|
||||
else if (Target is WayPoint wp)
|
||||
else if (target is WayPoint wp)
|
||||
{
|
||||
return wp.CurrentHull;
|
||||
}
|
||||
else if (Target is FireSource fs)
|
||||
else if (target is FireSource fs)
|
||||
{
|
||||
return fs.Hull;
|
||||
}
|
||||
else if (target is OrderTarget ot)
|
||||
{
|
||||
return ot.Hull;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -361,7 +463,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (Target is Item item)
|
||||
{
|
||||
if (character.CanInteractWith(item, out _, checkLinked: false)) { IsCompleted = true; }
|
||||
if (!character.IsClimbing && character.CanInteractWith(item, out _, checkLinked: false)) { IsCompleted = true; }
|
||||
}
|
||||
else if (Target is Character targetCharacter)
|
||||
{
|
||||
|
||||
+90
-48
@@ -1,4 +1,5 @@
|
||||
using Barotrauma.Items.Components;
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Items.Components;
|
||||
using FarseerPhysics;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
@@ -11,14 +12,14 @@ namespace Barotrauma
|
||||
{
|
||||
public override string DebugTag => "idle";
|
||||
public override bool AllowAutomaticItemUnequipping => true;
|
||||
public override bool AllowOutsideSubmarine => true;
|
||||
public override bool AllowInAnySub => true;
|
||||
|
||||
private BehaviorType behavior;
|
||||
public BehaviorType Behavior
|
||||
{
|
||||
get { return behavior; }
|
||||
set
|
||||
{
|
||||
set
|
||||
{
|
||||
behavior = value;
|
||||
switch (behavior)
|
||||
{
|
||||
@@ -27,16 +28,13 @@ namespace Barotrauma
|
||||
newTargetIntervalMax = 20;
|
||||
standStillMin = 2;
|
||||
standStillMax = 10;
|
||||
walkDurationMin = 5;
|
||||
walkDurationMax = 10;
|
||||
break;
|
||||
case BehaviorType.Passive:
|
||||
case BehaviorType.StayInHull:
|
||||
newTargetIntervalMin = 60;
|
||||
newTargetIntervalMax = 120;
|
||||
standStillMin = 30;
|
||||
standStillMax = 60;
|
||||
walkDurationMin = 5;
|
||||
walkDurationMax = 10;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -46,8 +44,8 @@ namespace Barotrauma
|
||||
private float newTargetIntervalMax;
|
||||
private float standStillMin;
|
||||
private float standStillMax;
|
||||
private float walkDurationMin;
|
||||
private float walkDurationMax;
|
||||
private readonly float walkDurationMin = 5;
|
||||
private readonly float walkDurationMax = 10;
|
||||
|
||||
public enum BehaviorType
|
||||
{
|
||||
@@ -55,7 +53,7 @@ namespace Barotrauma
|
||||
Passive,
|
||||
StayInHull
|
||||
}
|
||||
|
||||
public Hull TargetHull { get; set; }
|
||||
private Hull currentTarget;
|
||||
private float newTargetTimer;
|
||||
|
||||
@@ -86,7 +84,7 @@ namespace Barotrauma
|
||||
protected override bool Check() => false;
|
||||
public override bool CanBeCompleted => true;
|
||||
|
||||
public override bool IsLoop { get => true; set => throw new Exception("Trying to set the value for IsLoop from: " + Environment.StackTrace); }
|
||||
public override bool IsLoop { get => true; set => throw new Exception("Trying to set the value for IsLoop from: " + Environment.StackTrace.CleanupStackTrace()); }
|
||||
|
||||
public readonly HashSet<string> PreferredOutpostModuleTypes = new HashSet<string>();
|
||||
|
||||
@@ -142,6 +140,8 @@ namespace Barotrauma
|
||||
timerMargin = 0;
|
||||
}
|
||||
|
||||
private bool IsSteeringFinished() => PathSteering.CurrentPath != null && (PathSteering.CurrentPath.Finished || PathSteering.CurrentPath.Unreachable);
|
||||
|
||||
protected override void Act(float deltaTime)
|
||||
{
|
||||
if (PathSteering == null) { return; }
|
||||
@@ -164,12 +164,30 @@ namespace Barotrauma
|
||||
character.DeselectCharacter();
|
||||
}
|
||||
|
||||
if (behavior != BehaviorType.StayInHull)
|
||||
if (!character.IsClimbing)
|
||||
{
|
||||
bool currentTargetIsInvalid = currentTarget == null || IsForbidden(currentTarget) ||
|
||||
(PathSteering.CurrentPath != null && PathSteering.CurrentPath.Nodes.Any(n => HumanAIController.UnsafeHulls.Contains(n.CurrentHull)));
|
||||
character.SelectedConstruction = null;
|
||||
}
|
||||
|
||||
bool IsSteeringFinished() => PathSteering.CurrentPath != null && (PathSteering.CurrentPath.Finished || PathSteering.CurrentPath.Unreachable);
|
||||
CleanupItems(deltaTime);
|
||||
|
||||
if (behavior == BehaviorType.StayInHull)
|
||||
{
|
||||
currentTarget = TargetHull;
|
||||
bool stayInHull = character.CurrentHull == currentTarget && IsSteeringFinished() && !character.IsClimbing;
|
||||
if (stayInHull)
|
||||
{
|
||||
Wander(deltaTime);
|
||||
}
|
||||
else if (currentTarget != null)
|
||||
{
|
||||
PathSteering.SteeringSeek(character.GetRelativeSimPosition(currentTarget), weight: 1, nodeFilter: node => node.Waypoint.CurrentHull != null);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
bool currentTargetIsInvalid = currentTarget == null || IsForbidden(currentTarget) ||
|
||||
(PathSteering.CurrentPath != null && PathSteering.CurrentPath.Nodes.Any(n => HumanAIController.UnsafeHulls.Contains(n.CurrentHull)));
|
||||
|
||||
if (currentTarget != null && !currentTargetIsInvalid)
|
||||
{
|
||||
@@ -226,7 +244,7 @@ namespace Barotrauma
|
||||
searchingNewHull = true;
|
||||
return;
|
||||
}
|
||||
else if (targetHulls.Count > 0)
|
||||
else if (targetHulls.Any())
|
||||
{
|
||||
//choose a random available hull
|
||||
currentTarget = ToolBox.SelectWeightedRandom(targetHulls, hullWeights, Rand.RandSync.Unsynced);
|
||||
@@ -242,12 +260,13 @@ namespace Barotrauma
|
||||
});
|
||||
if (path.Unreachable)
|
||||
{
|
||||
//can't go to this room, remove it from the list and try another room next frame
|
||||
//can't go to this room, remove it from the list and try another room
|
||||
int index = targetHulls.IndexOf(currentTarget);
|
||||
targetHulls.RemoveAt(index);
|
||||
hullWeights.RemoveAt(index);
|
||||
PathSteering.Reset();
|
||||
currentTarget = null;
|
||||
SetTargetTimerLow();
|
||||
return;
|
||||
}
|
||||
searchingNewHull = false;
|
||||
@@ -263,47 +282,25 @@ namespace Barotrauma
|
||||
{
|
||||
character.AIController.SelectTarget(currentTarget.AiTarget);
|
||||
string errorMsg = null;
|
||||
#if DEBUG
|
||||
#if DEBUG
|
||||
bool isRoomNameFound = currentTarget.DisplayName != null;
|
||||
errorMsg = "(Character " + character.Name + " idling, target " + (isRoomNameFound ? currentTarget.DisplayName : currentTarget.ToString()) + ")";
|
||||
#endif
|
||||
#endif
|
||||
var path = PathSteering.PathFinder.FindPath(character.SimPosition, currentTarget.SimPosition, errorMsgStr: errorMsg, nodeFilter: node => node.Waypoint.CurrentHull != null);
|
||||
PathSteering.SetPath(path);
|
||||
}
|
||||
SetTargetTimerNormal();
|
||||
}
|
||||
}
|
||||
newTargetTimer -= deltaTime;
|
||||
}
|
||||
|
||||
//wander randomly
|
||||
// - if reached the end of the path
|
||||
// - if the target is unreachable
|
||||
// - if the path requires going outside
|
||||
if (!character.IsClimbing)
|
||||
{
|
||||
if (behavior == BehaviorType.StayInHull || SteeringManager != PathSteering || (PathSteering.CurrentPath != null &&
|
||||
(PathSteering.CurrentPath.Finished || PathSteering.CurrentPath.Unreachable || PathSteering.CurrentPath.HasOutdoorsNodes)))
|
||||
if (!character.IsClimbing && IsSteeringFinished())
|
||||
{
|
||||
Wander(deltaTime);
|
||||
return;
|
||||
}
|
||||
character.SelectedConstruction = null;
|
||||
}
|
||||
|
||||
if (currentTarget != null)
|
||||
{
|
||||
if (SteeringManager == PathSteering)
|
||||
else if (currentTarget != null)
|
||||
{
|
||||
PathSteering.SteeringSeek(character.GetRelativeSimPosition(currentTarget), weight: 1, nodeFilter: node => node.Waypoint.CurrentHull != null);
|
||||
}
|
||||
else
|
||||
{
|
||||
character.AIController.SteeringManager.SteeringSeek(character.GetRelativeSimPosition(currentTarget));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Wander(deltaTime);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -328,7 +325,7 @@ namespace Barotrauma
|
||||
{
|
||||
//if there are characters too close on both sides, don't try to steer away from them
|
||||
//because it'll cause the character to spaz out trying to avoid both
|
||||
if (tooCloseCharacter != null &&
|
||||
if (tooCloseCharacter != null &&
|
||||
Math.Sign(tooCloseCharacter.WorldPosition.X - character.WorldPosition.X) != Math.Sign(c.WorldPosition.X - character.WorldPosition.X))
|
||||
{
|
||||
tooCloseCharacter = null;
|
||||
@@ -336,7 +333,7 @@ namespace Barotrauma
|
||||
}
|
||||
tooCloseCharacter = c;
|
||||
}
|
||||
HumanAIController.FaceTarget(c);
|
||||
HumanAIController.FaceTarget(c);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -344,7 +341,7 @@ namespace Barotrauma
|
||||
{
|
||||
Vector2 diff = character.WorldPosition - tooCloseCharacter.WorldPosition;
|
||||
if (diff.LengthSquared() < 0.0001f) { diff = Rand.Vector(1.0f); }
|
||||
if (Math.Abs(diff.X) > 0 &&
|
||||
if (Math.Abs(diff.X) > 0 &&
|
||||
(character.WorldPosition.X > currentHull.WorldRect.Right - 50 || character.WorldPosition.X < currentHull.WorldRect.Left + 50))
|
||||
{
|
||||
// Between a wall and a character -> move away
|
||||
@@ -459,6 +456,48 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
#region Cleaning
|
||||
private readonly float checkItemsInterval = 1;
|
||||
private float checkItemsTimer;
|
||||
private readonly List<Item> itemsToClean = new List<Item>();
|
||||
private readonly List<Item> ignoredItems = new List<Item>();
|
||||
|
||||
private void CleanupItems(float deltaTime)
|
||||
{
|
||||
if (checkItemsTimer <= 0)
|
||||
{
|
||||
checkItemsTimer = checkItemsInterval * Rand.Range(0.9f, 1.1f);
|
||||
var hull = character.CurrentHull;
|
||||
if (hull != null)
|
||||
{
|
||||
itemsToClean.Clear();
|
||||
foreach (Item item in Item.ItemList)
|
||||
{
|
||||
if (item.CurrentHull != hull) { continue; }
|
||||
if (AIObjectiveCleanupItems.IsValidTarget(item, character) && !ignoredItems.Contains(item))
|
||||
{
|
||||
itemsToClean.Add(item);
|
||||
}
|
||||
}
|
||||
if (itemsToClean.Any())
|
||||
{
|
||||
var targetItem = itemsToClean.OrderBy(i => Math.Abs(character.WorldPosition.X - i.WorldPosition.X)).FirstOrDefault();
|
||||
if (targetItem != null)
|
||||
{
|
||||
var cleanupObjective = new AIObjectiveCleanupItem(targetItem, character, objectiveManager, PriorityModifier);
|
||||
cleanupObjective.Abandoned += () => ignoredItems.Add(targetItem);
|
||||
subObjectives.Add(cleanupObjective);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
checkItemsTimer -= deltaTime;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
public static bool IsForbidden(Hull hull)
|
||||
{
|
||||
if (hull == null) { return true; }
|
||||
@@ -475,6 +514,9 @@ namespace Barotrauma
|
||||
tooCloseCharacter = null;
|
||||
targetHulls.Clear();
|
||||
hullWeights.Clear();
|
||||
checkItemsTimer = 0;;
|
||||
itemsToClean.Clear();
|
||||
ignoredItems.Clear();
|
||||
autonomousObjectiveRetryTimer = 10;
|
||||
}
|
||||
}
|
||||
|
||||
+8
-4
@@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Barotrauma.Extensions;
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
namespace Barotrauma
|
||||
@@ -46,7 +45,7 @@ namespace Barotrauma
|
||||
public override bool AllowSubObjectiveSorting => true;
|
||||
public virtual bool InverseTargetEvaluation => false;
|
||||
|
||||
public override bool IsLoop { get => true; set => throw new Exception("Trying to set the value for IsLoop from: " + System.Environment.StackTrace); }
|
||||
public override bool IsLoop { get => true; set => throw new Exception("Trying to set the value for IsLoop from: " + System.Environment.StackTrace.CleanupStackTrace()); }
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
@@ -79,7 +78,12 @@ namespace Barotrauma
|
||||
var target = objective.Key;
|
||||
if (!Targets.Contains(target))
|
||||
{
|
||||
subObjectives.Remove(objective.Value);
|
||||
var subObjective = objective.Value;
|
||||
if (CurrentSubObjective == subObjective)
|
||||
{
|
||||
CurrentSubObjective.Abandon = !CurrentSubObjective.IsCompleted;
|
||||
}
|
||||
subObjectives.Remove(subObjective);
|
||||
}
|
||||
}
|
||||
SyncRemovedObjectives(Objectives, GetList());
|
||||
@@ -137,7 +141,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (objectiveManager.CurrentOrder == this)
|
||||
{
|
||||
Priority = AIObjectiveManager.OrderPriority;
|
||||
Priority = ForceOrderPriority ? AIObjectiveManager.OrderPriority : targetValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
+32
-6
@@ -70,7 +70,7 @@ namespace Barotrauma
|
||||
if (objective == null)
|
||||
{
|
||||
#if DEBUG
|
||||
DebugConsole.ThrowError("Attempted to add a null objective to AIObjectiveManager\n" + Environment.StackTrace);
|
||||
DebugConsole.ThrowError("Attempted to add a null objective to AIObjectiveManager\n" + Environment.StackTrace.CleanupStackTrace());
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
@@ -128,8 +128,7 @@ namespace Barotrauma
|
||||
var orderPrefab = Order.GetPrefab(autonomousObjective.identifier);
|
||||
if (orderPrefab == null) { throw new Exception($"Could not find a matching prefab by the identifier: '{autonomousObjective.identifier}'"); }
|
||||
var item = orderPrefab.MustSetTarget ? orderPrefab.GetMatchingItems(character.Submarine, mustBelongToPlayerSub: false, requiredTeam: character.Info.TeamID)?.GetRandom() : null;
|
||||
var order = new Order(orderPrefab, item ?? character.CurrentHull as Entity,
|
||||
item?.Components.FirstOrDefault(ic => ic.GetType() == orderPrefab.ItemComponentType), orderGiver: character);
|
||||
var order = new Order(orderPrefab, item ?? character.CurrentHull as Entity, orderPrefab.GetTargetItemComponent(item), orderGiver: character);
|
||||
if (order == null) { continue; }
|
||||
if (autonomousObjective.ignoreAtOutpost && Level.IsLoadedOutpost && character.TeamID != Character.TeamType.FriendlyNPC) { continue; }
|
||||
var objective = CreateObjective(order, autonomousObjective.option, character, isAutonomous: true, autonomousObjective.priorityModifier);
|
||||
@@ -147,7 +146,7 @@ namespace Barotrauma
|
||||
if (objective == null)
|
||||
{
|
||||
#if DEBUG
|
||||
DebugConsole.ThrowError($"{character.Name}: Attempted to add a null objective to AIObjectiveManager\n" + Environment.StackTrace);
|
||||
DebugConsole.ThrowError($"{character.Name}: Attempted to add a null objective to AIObjectiveManager\n" + Environment.StackTrace.CleanupStackTrace());
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
@@ -314,9 +313,10 @@ namespace Barotrauma
|
||||
};
|
||||
break;
|
||||
case "wait":
|
||||
newObjective = new AIObjectiveGoTo(order.TargetEntity ?? character, character, this, repeat: true, priorityModifier: priorityModifier)
|
||||
newObjective = new AIObjectiveGoTo(order.TargetSpatialEntity ?? character, character, this, repeat: true, priorityModifier: priorityModifier)
|
||||
{
|
||||
AllowGoingOutside = character.CurrentHull == null
|
||||
AllowGoingOutside = order.TargetSpatialEntity == null ? character.CurrentHull == null :
|
||||
character.Submarine == null || character.Submarine != order.TargetSpatialEntity.Submarine
|
||||
};
|
||||
break;
|
||||
case "fixleaks":
|
||||
@@ -374,6 +374,32 @@ namespace Barotrauma
|
||||
Override = orderGiver != null && orderGiver.IsPlayer
|
||||
};
|
||||
break;
|
||||
case "setchargepct":
|
||||
newObjective = new AIObjectiveOperateItem(order.TargetItemComponent, character, this, option, false, priorityModifier: priorityModifier)
|
||||
{
|
||||
IsLoop = false,
|
||||
Override = character.CurrentOrder != null,
|
||||
completionCondition = () =>
|
||||
{
|
||||
if (float.TryParse(option, out float pct))
|
||||
{
|
||||
var targetRatio = Math.Clamp(pct, 0f, 1f);
|
||||
var currentRatio = (order.TargetItemComponent as PowerContainer).RechargeRatio;
|
||||
return Math.Abs(targetRatio - currentRatio) < 0.05f;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
break;
|
||||
case "getitem":
|
||||
newObjective = new AIObjectiveGetItem(character, order.TargetEntity as Item ?? order.TargetItemComponent?.Item, this, false, priorityModifier: priorityModifier)
|
||||
{
|
||||
MustBeSpecificItem = true
|
||||
};
|
||||
break;
|
||||
case "cleanupitems":
|
||||
newObjective = new AIObjectiveCleanupItems(character, this, priorityModifier, order.TargetEntity as Item);
|
||||
break;
|
||||
default:
|
||||
if (order.TargetItemComponent == null) { return null; }
|
||||
if (order.TargetItemComponent.Item.NonInteractable) { return null; }
|
||||
|
||||
+6
-3
@@ -11,6 +11,7 @@ namespace Barotrauma
|
||||
public override string DebugTag => $"operate item {component.Name}";
|
||||
public override bool AllowAutomaticItemUnequipping => true;
|
||||
public override bool AllowMultipleInstances => true;
|
||||
public override bool AllowInAnySub => true;
|
||||
|
||||
private ItemComponent component, controller;
|
||||
private Entity operateTarget;
|
||||
@@ -35,9 +36,11 @@ namespace Barotrauma
|
||||
|
||||
public override float GetPriority()
|
||||
{
|
||||
bool isOrder = objectiveManager.CurrentOrder == this;
|
||||
if (!IsAllowed || character.LockHands)
|
||||
{
|
||||
Priority = 0;
|
||||
Abandon = !isOrder;
|
||||
return Priority;
|
||||
}
|
||||
if (component.Item.ConditionPercentage <= 0)
|
||||
@@ -46,7 +49,6 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
bool isOrder = objectiveManager.CurrentOrder == this;
|
||||
if (isOrder)
|
||||
{
|
||||
Priority = AIObjectiveManager.OrderPriority;
|
||||
@@ -172,7 +174,7 @@ namespace Barotrauma
|
||||
}
|
||||
if (target.CanBeSelected)
|
||||
{
|
||||
if (character.CanInteractWith(target.Item, out _, checkLinked: false))
|
||||
if (!character.IsClimbing && character.CanInteractWith(target.Item, out _, checkLinked: false))
|
||||
{
|
||||
HumanAIController.FaceTarget(target.Item);
|
||||
if (character.SelectedConstruction != target.Item)
|
||||
@@ -189,7 +191,8 @@ namespace Barotrauma
|
||||
TryAddSubObjective(ref goToObjective, () => new AIObjectiveGoTo(target.Item, character, objectiveManager, closeEnough: 50)
|
||||
{
|
||||
DialogueIdentifier = "dialogcannotreachtarget",
|
||||
TargetName = target.Item.Name
|
||||
TargetName = target.Item.Name,
|
||||
endNodeFilter = node => node.Waypoint.Ladders == null
|
||||
},
|
||||
onAbandon: () => Abandon = true,
|
||||
onCompleted: () => RemoveSubObjective(ref goToObjective));
|
||||
|
||||
+10
-3
@@ -10,6 +10,8 @@ namespace Barotrauma
|
||||
{
|
||||
public override string DebugTag => "repair item";
|
||||
|
||||
public override bool AllowInAnySub => true;
|
||||
|
||||
public Item Item { get; private set; }
|
||||
|
||||
private AIObjectiveGoTo goToObjective;
|
||||
@@ -34,6 +36,7 @@ namespace Barotrauma
|
||||
if (!IsAllowed)
|
||||
{
|
||||
Priority = 0;
|
||||
Abandon = true;
|
||||
return Priority;
|
||||
}
|
||||
// TODO: priority list?
|
||||
@@ -69,7 +72,7 @@ namespace Barotrauma
|
||||
IsCompleted = Item.IsFullCondition;
|
||||
if (IsCompleted && IsRepairing())
|
||||
{
|
||||
character?.Speak(TextManager.GetWithVariable("DialogItemRepaired", "[itemname]", Item.Name, true), null, 0.0f, "itemrepaired", 10.0f);
|
||||
character.Speak(TextManager.GetWithVariable("DialogItemRepaired", "[itemname]", Item.Name, true), null, 0.0f, "itemrepaired", 10.0f);
|
||||
}
|
||||
return IsCompleted;
|
||||
}
|
||||
@@ -134,7 +137,7 @@ namespace Barotrauma
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (character.CanInteractWith(Item, out _, checkLinked: false))
|
||||
if (!character.IsClimbing && character.CanInteractWith(Item, out _, checkLinked: false))
|
||||
{
|
||||
HumanAIController.FaceTarget(Item);
|
||||
if (repairTool != null)
|
||||
@@ -235,7 +238,11 @@ namespace Barotrauma
|
||||
|
||||
private void OperateRepairTool(float deltaTime)
|
||||
{
|
||||
character.CursorPosition = Item.Position;
|
||||
character.CursorPosition = Item.WorldPosition;
|
||||
if (character.Submarine != null)
|
||||
{
|
||||
character.CursorPosition -= character.Submarine.Position;
|
||||
}
|
||||
if (repairTool.Item.RequireAimToUse)
|
||||
{
|
||||
character.SetInput(InputType.Aim, false, true);
|
||||
|
||||
+4
-8
@@ -24,11 +24,11 @@ namespace Barotrauma
|
||||
private readonly Item prioritizedItem;
|
||||
|
||||
public override bool AllowMultipleInstances => true;
|
||||
public override bool AllowInAnySub => true;
|
||||
|
||||
public readonly static float RequiredSuccessFactor = 0.4f;
|
||||
|
||||
public override bool IsDuplicate<T>(T otherObjective) =>
|
||||
(otherObjective as AIObjective) is AIObjectiveRepairItems repairObjective && repairObjective.RequireAdequateSkills == RequireAdequateSkills;
|
||||
public override bool IsDuplicate<T>(T otherObjective) => otherObjective is AIObjectiveRepairItems repairObjective && repairObjective.RequireAdequateSkills == RequireAdequateSkills;
|
||||
|
||||
public AIObjectiveRepairItems(Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1, Item prioritizedItem = null)
|
||||
: base(character, objectiveManager, priorityModifier)
|
||||
@@ -151,13 +151,9 @@ namespace Barotrauma
|
||||
if (item.NonInteractable) { return false; }
|
||||
if (item.IsFullCondition) { return false; }
|
||||
if (item.CurrentHull == null) { return false; }
|
||||
if (item.Submarine == null) { return false; }
|
||||
if (item.Submarine.TeamID != character.TeamID) { return false; }
|
||||
if (item.Submarine == null || character.Submarine == null) { return false; }
|
||||
if (!character.Submarine.IsEntityFoundOnThisSub(item, includingConnectedSubs: true)) { return false; }
|
||||
if (item.Repairables.None()) { return false; }
|
||||
if (character.Submarine != null)
|
||||
{
|
||||
if (!character.Submarine.IsConnectedTo(item.Submarine)) { return false; }
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
+4
-1
@@ -14,6 +14,7 @@ namespace Barotrauma
|
||||
public override bool KeepDivingGearOn => true;
|
||||
|
||||
public override bool AllowOutsideSubmarine => true;
|
||||
public override bool AllowInAnySub => true;
|
||||
|
||||
const float TreatmentDelay = 0.5f;
|
||||
|
||||
@@ -35,7 +36,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (targetCharacter == null)
|
||||
{
|
||||
string errorMsg = $"{character.Name}: Attempted to create a Rescue objective with no target!\n" + Environment.StackTrace;
|
||||
string errorMsg = $"{character.Name}: Attempted to create a Rescue objective with no target!\n" + Environment.StackTrace.CleanupStackTrace();
|
||||
DebugConsole.ThrowError(errorMsg);
|
||||
GameAnalyticsManager.AddErrorEventOnce("AIObjectiveRescue:ctor:targetnull", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
|
||||
Abandon = true;
|
||||
@@ -392,11 +393,13 @@ namespace Barotrauma
|
||||
if (!IsAllowed)
|
||||
{
|
||||
Priority = 0;
|
||||
Abandon = true;
|
||||
return Priority;
|
||||
}
|
||||
if (character.LockHands || targetCharacter == null || targetCharacter.CurrentHull == null || targetCharacter.Removed || targetCharacter.IsDead)
|
||||
{
|
||||
Priority = 0;
|
||||
Abandon = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
+1
@@ -11,6 +11,7 @@ namespace Barotrauma
|
||||
public override bool ForceRun => true;
|
||||
public override bool InverseTargetEvaluation => true;
|
||||
public override bool AllowOutsideSubmarine => true;
|
||||
public override bool AllowInAnySub => true;
|
||||
|
||||
private const float vitalityThreshold = 75;
|
||||
private const float vitalityThresholdForOrders = 85;
|
||||
|
||||
@@ -38,7 +38,8 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
public bool MatchesOrder(Order order, string option) =>
|
||||
order.Identifier == Order.Identifier && option == OrderOption && order.TargetEntity == Order.TargetEntity;
|
||||
order.Identifier == Order.Identifier &&
|
||||
option == OrderOption;
|
||||
}
|
||||
|
||||
class Order
|
||||
@@ -54,7 +55,7 @@ namespace Barotrauma
|
||||
}
|
||||
return order;
|
||||
}
|
||||
|
||||
|
||||
public Order Prefab { get; private set; }
|
||||
|
||||
public readonly string Name;
|
||||
@@ -62,7 +63,8 @@ namespace Barotrauma
|
||||
public readonly Sprite SymbolSprite;
|
||||
|
||||
public readonly Type ItemComponentType;
|
||||
public readonly string[] ItemIdentifiers;
|
||||
public readonly bool CanTypeBeSubclass;
|
||||
public readonly string[] TargetItems;
|
||||
|
||||
public readonly string Identifier;
|
||||
|
||||
@@ -89,14 +91,14 @@ namespace Barotrauma
|
||||
color = value;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
//if true, the order is issued to all available characters
|
||||
public bool TargetAllCharacters;
|
||||
|
||||
public readonly float FadeOutTime;
|
||||
|
||||
public Entity TargetEntity;
|
||||
public Entity TargetEntity;
|
||||
public ItemComponent TargetItemComponent;
|
||||
public readonly bool UseController;
|
||||
public Controller ConnectedController;
|
||||
@@ -123,6 +125,18 @@ namespace Barotrauma
|
||||
public bool IsPrefab { get; private set; }
|
||||
public readonly bool MustManuallyAssign;
|
||||
|
||||
public readonly OrderTarget TargetPosition;
|
||||
|
||||
private ISpatialEntity targetSpatialEntity;
|
||||
public ISpatialEntity TargetSpatialEntity
|
||||
{
|
||||
get
|
||||
{
|
||||
if (targetSpatialEntity == null) { targetSpatialEntity = TargetEntity ?? TargetPosition as ISpatialEntity; }
|
||||
return targetSpatialEntity;
|
||||
}
|
||||
}
|
||||
|
||||
public static void Init()
|
||||
{
|
||||
Prefabs = new Dictionary<string, Order>();
|
||||
@@ -218,8 +232,9 @@ namespace Barotrauma
|
||||
DebugConsole.ThrowError("Error in the order definitions: item component type " + targetItemType + " not found", e);
|
||||
}
|
||||
}
|
||||
CanTypeBeSubclass = orderElement.GetAttributeBool("cantypebesubclass", false);
|
||||
|
||||
ItemIdentifiers = orderElement.GetAttributeStringArray("targetitemidentifiers", new string[0], trim: true, convertToLowerInvariant: true);
|
||||
TargetItems = orderElement.GetAttributeStringArray("targetitems", new string[0], trim: true, convertToLowerInvariant: true);
|
||||
color = orderElement.GetAttributeColor("color");
|
||||
FadeOutTime = orderElement.GetAttributeFloat("fadeouttime", 0.0f);
|
||||
UseController = orderElement.GetAttributeBool("usecontroller", false);
|
||||
@@ -228,7 +243,6 @@ namespace Barotrauma
|
||||
Options = orderElement.GetAttributeStringArray("options", new string[0]);
|
||||
var category = orderElement.GetAttributeString("category", null);
|
||||
if (!string.IsNullOrWhiteSpace(category)) { this.Category = (OrderCategory)Enum.Parse(typeof(OrderCategory), category, true); }
|
||||
Weight = orderElement.GetAttributeFloat(0.0f, "weight");
|
||||
MustSetTarget = orderElement.GetAttributeBool("mustsettarget", false);
|
||||
AppropriateSkill = orderElement.GetAttributeString("appropriateskill", null);
|
||||
|
||||
@@ -279,7 +293,7 @@ namespace Barotrauma
|
||||
IsPrefab = true;
|
||||
MustManuallyAssign = orderElement.GetAttributeBool("mustmanuallyassign", false);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Constructor for order instances
|
||||
/// </summary>
|
||||
@@ -290,7 +304,8 @@ namespace Barotrauma
|
||||
Name = prefab.Name;
|
||||
Identifier = prefab.Identifier;
|
||||
ItemComponentType = prefab.ItemComponentType;
|
||||
ItemIdentifiers = prefab.ItemIdentifiers;
|
||||
CanTypeBeSubclass = prefab.CanTypeBeSubclass;
|
||||
TargetItems = prefab.TargetItems;
|
||||
Options = prefab.Options;
|
||||
SymbolSprite = prefab.SymbolSprite;
|
||||
Color = prefab.Color;
|
||||
@@ -298,7 +313,6 @@ namespace Barotrauma
|
||||
TargetAllCharacters = prefab.TargetAllCharacters;
|
||||
AppropriateJobs = prefab.AppropriateJobs;
|
||||
FadeOutTime = prefab.FadeOutTime;
|
||||
Weight = prefab.Weight;
|
||||
MustSetTarget = prefab.MustSetTarget;
|
||||
AppropriateSkill = prefab.AppropriateSkill;
|
||||
Category = prefab.Category;
|
||||
@@ -325,6 +339,11 @@ namespace Barotrauma
|
||||
|
||||
IsPrefab = false;
|
||||
}
|
||||
|
||||
public Order(Order prefab, OrderTarget target, Character orderGiver = null) : this(prefab, targetEntity: null, targetItem: null, orderGiver)
|
||||
{
|
||||
TargetPosition = target;
|
||||
}
|
||||
|
||||
public bool HasAppropriateJob(Character character)
|
||||
{
|
||||
@@ -358,15 +377,38 @@ namespace Barotrauma
|
||||
return msg;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the target item component based on the target item type
|
||||
/// </summary>
|
||||
public ItemComponent GetTargetItemComponent(Item item)
|
||||
{
|
||||
if (item?.Components == null || ItemComponentType == null) { return null; }
|
||||
foreach (ItemComponent component in item.Components)
|
||||
{
|
||||
if (component?.GetType() is Type componentType)
|
||||
{
|
||||
if (componentType == ItemComponentType) { return component; }
|
||||
if (CanTypeBeSubclass && componentType.IsSubclassOf(ItemComponentType)) { return component; }
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public bool TryGetTargetItemComponent(Item item, out ItemComponent firstMatchingComponent)
|
||||
{
|
||||
firstMatchingComponent = GetTargetItemComponent(item);
|
||||
return firstMatchingComponent != null;
|
||||
}
|
||||
|
||||
public List<Item> GetMatchingItems(Submarine submarine, bool mustBelongToPlayerSub, Character.TeamType? requiredTeam = null)
|
||||
{
|
||||
List<Item> matchingItems = new List<Item>();
|
||||
if (submarine == null) { return matchingItems; }
|
||||
if (ItemComponentType != null || ItemIdentifiers.Length > 0)
|
||||
if (ItemComponentType != null || TargetItems.Length > 0)
|
||||
{
|
||||
matchingItems = ItemIdentifiers.Length > 0 ?
|
||||
Item.ItemList.FindAll(it => ItemIdentifiers.Contains(it.Prefab.Identifier) || it.HasTag(ItemIdentifiers)) :
|
||||
Item.ItemList.FindAll(it => it.Components.Any(ic => ic.GetType() == ItemComponentType));
|
||||
matchingItems = TargetItems.Length > 0 ?
|
||||
Item.ItemList.FindAll(it => TargetItems.Contains(it.Prefab.Identifier) || it.HasTag(TargetItems)) :
|
||||
Item.ItemList.FindAll(it => TryGetTargetItemComponent(it, out _));
|
||||
if (mustBelongToPlayerSub)
|
||||
{
|
||||
matchingItems.RemoveAll(it => it.Submarine?.Info != null && it.Submarine.Info.Type != SubmarineType.Player);
|
||||
|
||||
Reference in New Issue
Block a user