(6eeea9b7c) v0.9.10.0.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 }
|
||||
public enum AIState { Idle, Attack, Escape, Eat, Flee, Avoid, Aggressive, PassiveAggressive, Protect }
|
||||
|
||||
abstract partial class AIController : ISteerable
|
||||
{
|
||||
|
||||
@@ -40,6 +40,7 @@ namespace Barotrauma
|
||||
|
||||
private readonly float avoidLookAheadDistance;
|
||||
|
||||
private IndoorsSteeringManager PathSteering => insideSteering as IndoorsSteeringManager;
|
||||
private SteeringManager outsideSteering, insideSteering;
|
||||
|
||||
private float updateTargetsTimer;
|
||||
@@ -85,8 +86,9 @@ namespace Barotrauma
|
||||
private readonly float colliderWidth;
|
||||
private readonly float colliderLength;
|
||||
private readonly int requiredHoleCount;
|
||||
private readonly bool canAttackSub;
|
||||
private readonly bool canAttackCharacters;
|
||||
private bool canAttackWalls;
|
||||
private bool canAttackDoors;
|
||||
private bool canAttackCharacters;
|
||||
|
||||
private readonly float priorityFearIncreasement = 2;
|
||||
private readonly float memoryFadeTime = 0.5f;
|
||||
@@ -94,9 +96,13 @@ namespace Barotrauma
|
||||
|
||||
private float avoidTimer;
|
||||
|
||||
public bool StayInsideLevel = true;
|
||||
|
||||
public LatchOntoAI LatchOntoAI { get; private set; }
|
||||
public SwarmBehavior SwarmBehavior { get; private set; }
|
||||
|
||||
public CharacterParams.TargetParams SelectedTargetingParams { get { return selectedTargetingParams; } }
|
||||
|
||||
public bool AttackHumans
|
||||
{
|
||||
get
|
||||
@@ -186,24 +192,9 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
bool canBreakDoors = false;
|
||||
if (GetTarget("room")?.Priority > 0.0f)
|
||||
{
|
||||
var currentContexts = Character.GetAttackContexts();
|
||||
foreach (Limb limb in Character.AnimController.Limbs)
|
||||
{
|
||||
if (limb.attack == null) { continue; }
|
||||
if (!limb.attack.IsValidTarget(AttackTarget.Structure)) { continue; }
|
||||
if (limb.attack.IsValidContext(currentContexts) && limb.attack.StructureDamage > 0.0f)
|
||||
{
|
||||
canBreakDoors = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ReevaluateAttacks();
|
||||
outsideSteering = new SteeringManager(this);
|
||||
insideSteering = new IndoorsSteeringManager(this, false, canBreakDoors);
|
||||
insideSteering = new IndoorsSteeringManager(this, false, canAttackDoors);
|
||||
steeringManager = outsideSteering;
|
||||
State = AIState.Idle;
|
||||
|
||||
@@ -213,9 +204,6 @@ namespace Barotrauma
|
||||
requiredHoleCount = (int)Math.Ceiling(ConvertUnits.ToDisplayUnits(colliderWidth) / Structure.WallSectionSize);
|
||||
|
||||
avoidLookAheadDistance = Math.Max(colliderWidth * 3, 1.5f);
|
||||
|
||||
canAttackSub = Character.AnimController.CanAttackSubmarine;
|
||||
canAttackCharacters = Character.AnimController.CanAttackCharacters;
|
||||
}
|
||||
|
||||
private CharacterParams.AIParams AIParams => Character.Params.AI;
|
||||
@@ -226,31 +214,32 @@ namespace Barotrauma
|
||||
public void SelectTarget(AITarget target, float priority)
|
||||
{
|
||||
SelectedAiTarget = target;
|
||||
selectedTargetMemory = GetTargetMemory(target);
|
||||
selectedTargetMemory = GetTargetMemory(target, true);
|
||||
selectedTargetMemory.Priority = priority;
|
||||
}
|
||||
|
||||
private float escapeMargin;
|
||||
private float movementMargin;
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
if (DisableEnemyAI) { return; }
|
||||
|
||||
base.Update(deltaTime);
|
||||
bool ignorePlatforms = (-Character.AnimController.TargetMovement.Y > Math.Abs(Character.AnimController.TargetMovement.X));
|
||||
|
||||
if (steeringManager is IndoorsSteeringManager)
|
||||
bool ignorePlatforms = Character.AnimController.TargetMovement.Y < -0.5f && (-Character.AnimController.TargetMovement.Y > Math.Abs(Character.AnimController.TargetMovement.X));
|
||||
if (steeringManager == insideSteering)
|
||||
{
|
||||
var currPath = ((IndoorsSteeringManager)steeringManager).CurrentPath;
|
||||
var currPath = PathSteering.CurrentPath;
|
||||
if (currPath != null && currPath.CurrentNode != null)
|
||||
{
|
||||
if (currPath.CurrentNode.SimPosition.Y < Character.AnimController.GetColliderBottom().Y)
|
||||
{
|
||||
ignorePlatforms = true;
|
||||
// Don't allow to jump from too high.
|
||||
float allowedJumpHeight = Character.AnimController.ImpactTolerance / 2;
|
||||
float height = Math.Abs(currPath.CurrentNode.SimPosition.Y - Character.SimPosition.Y);
|
||||
ignorePlatforms = height < allowedJumpHeight;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Character.AnimController.IgnorePlatforms = ignorePlatforms;
|
||||
|
||||
//clients get the facing direction from the server
|
||||
@@ -396,7 +385,7 @@ namespace Barotrauma
|
||||
{
|
||||
bool isBeingChased = IsBeingChased;
|
||||
float reactDistance = !isBeingChased && selectedTargetingParams != null && selectedTargetingParams.ReactDistance > 0 ? selectedTargetingParams.ReactDistance : GetPerceivingRange(SelectedAiTarget);
|
||||
if (squaredDistance <= Math.Pow(reactDistance + escapeMargin, 2))
|
||||
if (squaredDistance <= Math.Pow(reactDistance + movementMargin, 2))
|
||||
{
|
||||
float halfReactDistance = reactDistance / 2;
|
||||
float attackDistance = selectedTargetingParams != null && selectedTargetingParams.AttackDistance > 0 ? selectedTargetingParams.AttackDistance : halfReactDistance;
|
||||
@@ -408,26 +397,56 @@ namespace Barotrauma
|
||||
else
|
||||
{
|
||||
run = isBeingChased ? true : squaredDistance < Math.Pow(halfReactDistance, 2);
|
||||
if (escapeMargin <= 0)
|
||||
if (movementMargin <= 0)
|
||||
{
|
||||
escapeMargin = halfReactDistance;
|
||||
movementMargin = halfReactDistance;
|
||||
}
|
||||
escapeMargin = MathHelper.Clamp(escapeMargin += deltaTime, halfReactDistance, reactDistance);
|
||||
movementMargin = MathHelper.Clamp(movementMargin += deltaTime, halfReactDistance, reactDistance);
|
||||
UpdateEscape(deltaTime);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
escapeMargin = 0;
|
||||
movementMargin = 0;
|
||||
UpdateIdle(deltaTime);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case AIState.Protect:
|
||||
if (SelectedAiTarget == null || SelectedAiTarget.Entity == null || SelectedAiTarget.Entity.Removed)
|
||||
{
|
||||
State = AIState.Idle;
|
||||
return;
|
||||
}
|
||||
if (SelectedAiTarget.Entity is Character targetCharacter && targetCharacter.LastAttacker is Character attacker)
|
||||
{
|
||||
// Attack the character that attacked the target we are protecting
|
||||
ChangeTargetState(attacker, AIState.Attack, selectedTargetingParams.Priority * 2);
|
||||
SelectTarget(attacker.AiTarget);
|
||||
return;
|
||||
}
|
||||
float sqrDist = Vector2.DistanceSquared(WorldPosition, SelectedAiTarget.WorldPosition);
|
||||
float reactDist = selectedTargetingParams != null && selectedTargetingParams.ReactDistance > 0 ? selectedTargetingParams.ReactDistance : GetPerceivingRange(SelectedAiTarget);
|
||||
if (sqrDist > Math.Pow(reactDist + movementMargin, 2))
|
||||
{
|
||||
movementMargin = reactDist;
|
||||
run = true;
|
||||
UpdateFollow(deltaTime);
|
||||
}
|
||||
else
|
||||
{
|
||||
movementMargin = MathHelper.Clamp(movementMargin -= deltaTime, 0, reactDist);
|
||||
UpdateIdle(deltaTime);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
LatchOntoAI?.Update(this, deltaTime);
|
||||
if (!Character.AnimController.SimplePhysicsEnabled)
|
||||
{
|
||||
LatchOntoAI?.Update(this, deltaTime);
|
||||
}
|
||||
IsSteeringThroughGap = false;
|
||||
if (SwarmBehavior != null)
|
||||
{
|
||||
@@ -435,6 +454,8 @@ namespace Barotrauma
|
||||
SwarmBehavior.Refresh();
|
||||
SwarmBehavior.UpdateSteering(deltaTime);
|
||||
}
|
||||
// Ensure that the creature keeps inside the level
|
||||
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);
|
||||
@@ -459,13 +480,12 @@ namespace Barotrauma
|
||||
SteeringManager.SteeringAvoid(deltaTime, lookAheadDistance: avoidLookAheadDistance, weight: 5);
|
||||
return;
|
||||
}
|
||||
SteerInsideLevel(deltaTime);
|
||||
}
|
||||
var target = SelectedAiTarget ?? _lastAiTarget;
|
||||
if (target?.Entity != null && !target.Entity.Removed && PreviousState == AIState.Attack && Character.CurrentHull == null)
|
||||
{
|
||||
// Keep heading to the last known position of the target
|
||||
var memory = GetTargetMemory(target);
|
||||
var memory = GetTargetMemory(target, false);
|
||||
if (memory != null)
|
||||
{
|
||||
var location = memory.Location;
|
||||
@@ -579,10 +599,10 @@ namespace Barotrauma
|
||||
}
|
||||
else if (pathSteering != null)
|
||||
{
|
||||
if (canAttackSub && hasValidPath)
|
||||
if (canAttackDoors && hasValidPath)
|
||||
{
|
||||
var door = pathSteering.CurrentPath.CurrentNode?.ConnectedDoor ?? pathSteering.CurrentPath.NextNode?.ConnectedDoor;
|
||||
if (door != null && !door.IsOpen)
|
||||
if (door != null && !door.IsOpen && !door.IsBroken)
|
||||
{
|
||||
if (SelectedAiTarget != door.Item.AiTarget)
|
||||
{
|
||||
@@ -618,7 +638,6 @@ namespace Barotrauma
|
||||
{
|
||||
SteeringManager.SteeringWander();
|
||||
SteeringManager.SteeringAvoid(deltaTime, lookAheadDistance: avoidLookAheadDistance, weight: 5);
|
||||
SteerInsideLevel(deltaTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -644,17 +663,18 @@ namespace Barotrauma
|
||||
if (SelectedAiTarget.Entity is Item item)
|
||||
{
|
||||
// If the item is held by a character, attack the character instead.
|
||||
var pickable = item.GetComponent<Pickable>();
|
||||
if (pickable != null)
|
||||
Character owner = GetOwner(item);
|
||||
if (owner != null)
|
||||
{
|
||||
Entity owner = pickable.Picker ?? item.ParentInventory?.Owner;
|
||||
if (owner != null)
|
||||
if (IsFriendly(Character, owner))
|
||||
{
|
||||
var target = owner.AiTarget;
|
||||
if (target?.Entity != null && !target.Entity.Removed)
|
||||
{
|
||||
SelectedAiTarget = target;
|
||||
}
|
||||
ResetAITarget();
|
||||
State = AIState.Idle;
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
SelectedAiTarget = owner.AiTarget;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -666,7 +686,8 @@ namespace Barotrauma
|
||||
{
|
||||
attackWorldPos += wallTarget.Structure.Submarine.Position;
|
||||
}
|
||||
attackSimPos = ConvertUnits.ToSimUnits(attackWorldPos);
|
||||
attackSimPos = Character.Submarine == wallTarget.Structure.Submarine ? wallTarget.Position : attackWorldPos;
|
||||
attackSimPos = ConvertUnits.ToSimUnits(attackSimPos);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -704,7 +725,7 @@ namespace Barotrauma
|
||||
var door = i.GetComponent<Door>();
|
||||
// Steer through the door manually if it's open or broken
|
||||
// Don't try to enter dry hulls if cannot walk or if the gap is too narrow
|
||||
if (door?.LinkedGap?.FlowTargetHull != null && !door.LinkedGap.IsRoomToRoom && door.IsOpen)
|
||||
if (door?.LinkedGap?.FlowTargetHull != null && !door.LinkedGap.IsRoomToRoom && (door.IsOpen || door.IsBroken))
|
||||
{
|
||||
if (Character.AnimController.CanWalk || door.LinkedGap.FlowTargetHull.WaterPercentage > 25)
|
||||
{
|
||||
@@ -722,7 +743,6 @@ namespace Barotrauma
|
||||
}
|
||||
else if (SelectedAiTarget.Entity is Structure w && wallTarget == null)
|
||||
{
|
||||
// Targeting only the outer walls
|
||||
bool isBroken = true;
|
||||
for (int i = 0; i < w.Sections.Length; i++)
|
||||
{
|
||||
@@ -893,35 +913,23 @@ namespace Barotrauma
|
||||
}
|
||||
canAttack = AttackingLimb != null && AttackingLimb.attack.CoolDownTimer <= 0;
|
||||
}
|
||||
if (!canAttack && SelectedAiTarget.Entity.Submarine != null && !canAttackSub)
|
||||
if (!Character.AnimController.SimplePhysicsEnabled && SelectedAiTarget.Entity.Submarine != null && Character.Submarine == null && (!canAttackDoors || !canAttackWalls || !AIParams.TargetOuterWalls))
|
||||
{
|
||||
float dist = Vector2.Distance(Character.AnimController.MainLimb.WorldPosition, attackWorldPos);
|
||||
if (wallTarget != null)
|
||||
if (Vector2.DistanceSquared(Character.WorldPosition, attackWorldPos) < 2000 * 2000)
|
||||
{
|
||||
// Steer towards the target, but turn away if a wall is blocking the way
|
||||
if (dist < ConvertUnits.ToDisplayUnits(colliderLength) * 3)
|
||||
{
|
||||
State = AIState.Idle;
|
||||
IgnoreTarget(SelectedAiTarget);
|
||||
// Resetting the ai target prevents the character from chasing it
|
||||
ResetAITarget();
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (dist < 1000)
|
||||
{
|
||||
// Check that we are not bumping into a door
|
||||
// Check that we are not bumping into a door or a wall
|
||||
Vector2 rayStart = SimPosition;
|
||||
if (Character.Submarine == null)
|
||||
{
|
||||
rayStart -= SelectedAiTarget.Entity.Submarine.SimPosition;
|
||||
}
|
||||
Vector2 toTarget = SelectedAiTarget.WorldPosition - WorldPosition;
|
||||
Vector2 rayEnd = rayStart + toTarget.ClampLength(Character.AnimController.Collider.GetLocalFront().Length() * 2);
|
||||
Vector2 dir = SelectedAiTarget.WorldPosition - WorldPosition;
|
||||
Vector2 rayEnd = rayStart + dir.ClampLength(Character.AnimController.Collider.GetLocalFront().Length() * 2);
|
||||
Body closestBody = Submarine.CheckVisibility(rayStart, rayEnd, ignoreSubs: true);
|
||||
if (Submarine.LastPickedFraction != 1.0f && closestBody != null && closestBody.UserData is Item i && i.Submarine != null && i.GetComponent<Door>() != null)
|
||||
if (Submarine.LastPickedFraction != 1.0f && closestBody != null &&
|
||||
(!AIParams.TargetOuterWalls || !canAttackWalls && closestBody.UserData is Structure s && s.Submarine != null || !canAttackDoors && closestBody.UserData is Item i && i.Submarine != null && i.GetComponent<Door>() != null))
|
||||
{
|
||||
// Target is unreachable, there's a door ahead
|
||||
// Target is unreachable, there's a door or wall ahead
|
||||
State = AIState.Idle;
|
||||
IgnoreTarget(SelectedAiTarget);
|
||||
ResetAITarget();
|
||||
@@ -934,23 +942,28 @@ namespace Barotrauma
|
||||
Character targetCharacter = SelectedAiTarget.Entity as Character;
|
||||
if (canAttack)
|
||||
{
|
||||
// Target a specific limb instead of the target center position
|
||||
if (wallTarget == null && targetCharacter != null)
|
||||
if (!Character.AnimController.SimplePhysicsEnabled)
|
||||
{
|
||||
var targetLimbType = AttackingLimb.Params.Attack.Attack.TargetLimbType;
|
||||
attackTargetLimb = GetTargetLimb(AttackingLimb, targetCharacter, targetLimbType);
|
||||
if (attackTargetLimb == null)
|
||||
// Target a specific limb instead of the target center position
|
||||
if (wallTarget == null && targetCharacter != null)
|
||||
{
|
||||
State = AIState.Idle;
|
||||
IgnoreTarget(SelectedAiTarget);
|
||||
ResetAITarget();
|
||||
return;
|
||||
var targetLimbType = AttackingLimb.Params.Attack.Attack.TargetLimbType;
|
||||
attackTargetLimb = GetTargetLimb(AttackingLimb, targetCharacter, targetLimbType);
|
||||
if (attackTargetLimb == null)
|
||||
{
|
||||
State = AIState.Idle;
|
||||
IgnoreTarget(SelectedAiTarget);
|
||||
ResetAITarget();
|
||||
return;
|
||||
}
|
||||
attackWorldPos = attackTargetLimb.WorldPosition;
|
||||
attackSimPos = Character.GetRelativeSimPosition(attackTargetLimb);
|
||||
}
|
||||
attackWorldPos = attackTargetLimb.WorldPosition;
|
||||
attackSimPos = Character.GetRelativeSimPosition(attackTargetLimb);
|
||||
}
|
||||
// Check that we can reach the target
|
||||
Vector2 toTarget = attackWorldPos - AttackingLimb.WorldPosition;
|
||||
|
||||
Vector2 attackLimbPos = Character.AnimController.SimplePhysicsEnabled ? Character.WorldPosition : AttackingLimb.WorldPosition;
|
||||
Vector2 toTarget = attackWorldPos - attackLimbPos;
|
||||
// Add a margin when the target is moving away, because otherwise it might be difficult to reach it (the attack takes some time to perform)
|
||||
if (wallTarget != null)
|
||||
{
|
||||
if (wallTarget.Structure.Submarine != null)
|
||||
@@ -961,7 +974,6 @@ namespace Barotrauma
|
||||
}
|
||||
else if (targetCharacter != null)
|
||||
{
|
||||
// Add a margin when the target is moving away, because otherwise it might be difficult to reach it (the attack takes some time to perform)
|
||||
Vector2 margin = CalculateMargin(targetCharacter.AnimController.Collider.LinearVelocity);
|
||||
toTarget += margin;
|
||||
}
|
||||
@@ -981,6 +993,7 @@ namespace Barotrauma
|
||||
return ConvertUnits.ToDisplayUnits(targetVelocity) * AttackingLimb.attack.Duration * dot;
|
||||
}
|
||||
|
||||
// Check that we can reach the target
|
||||
distance = toTarget.Length();
|
||||
canAttack = distance < AttackingLimb.attack.Range;
|
||||
if (!canAttack && !IsCoolDownRunning)
|
||||
@@ -1035,21 +1048,25 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
Vector2 offset = Character.SimPosition - steeringLimb.SimPosition;
|
||||
// Offset so that we don't overshoot the movement
|
||||
Vector2 steerPos = attackSimPos + offset;
|
||||
Vector2 steerPos = attackSimPos;
|
||||
if (!Character.AnimController.SimplePhysicsEnabled)
|
||||
{
|
||||
// Offset so that we don't overshoot the movement
|
||||
Vector2 offset = Character.SimPosition - steeringLimb.SimPosition;
|
||||
steerPos += offset;
|
||||
}
|
||||
if (SteeringManager is IndoorsSteeringManager pathSteering)
|
||||
{
|
||||
if (pathSteering.CurrentPath != null)
|
||||
{
|
||||
// Attack doors
|
||||
if (canAttackSub)
|
||||
if (canAttackDoors)
|
||||
{
|
||||
// If the target is in the same hull, there shouldn't be any doors blocking the path
|
||||
if (targetCharacter == null || targetCharacter.CurrentHull != Character.CurrentHull)
|
||||
{
|
||||
var door = pathSteering.CurrentPath.CurrentNode?.ConnectedDoor ?? pathSteering.CurrentPath.NextNode?.ConnectedDoor;
|
||||
if (door != null && !door.IsOpen)
|
||||
if (door != null && !door.IsOpen && !door.IsBroken)
|
||||
{
|
||||
if (door.Item.AiTarget != null && SelectedAiTarget != door.Item.AiTarget)
|
||||
{
|
||||
@@ -1063,13 +1080,14 @@ namespace Barotrauma
|
||||
if ((Character.AnimController.InWater || pursue || !Character.AnimController.CanWalk) &&
|
||||
(targetCharacter != null && VisibleHulls.Contains(targetCharacter.CurrentHull) || Character.CanSeeTarget(SelectedAiTarget.Entity)))
|
||||
{
|
||||
SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(attackSimPos - steeringLimb.SimPosition));
|
||||
Vector2 myPos = Character.AnimController.SimplePhysicsEnabled ? Character.SimPosition : steeringLimb.SimPosition;
|
||||
SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(steerPos - myPos));
|
||||
}
|
||||
else
|
||||
{
|
||||
SteeringManager.SteeringSeek(steerPos, 2);
|
||||
// Switch to Idle when cannot reach the target and if cannot damage the walls
|
||||
if ((!canAttackSub || wallTarget == null) && !pathSteering.IsPathDirty && pathSteering.CurrentPath.Unreachable)
|
||||
if ((!canAttackWalls || wallTarget == null) && !pathSteering.IsPathDirty && pathSteering.CurrentPath.Unreachable)
|
||||
{
|
||||
State = AIState.Idle;
|
||||
return;
|
||||
@@ -1145,7 +1163,7 @@ namespace Barotrauma
|
||||
if (attack == null) { continue; }
|
||||
if (attack.CoolDownTimer > 0) { continue; }
|
||||
if (!attack.IsValidContext(currentContexts)) { continue; }
|
||||
if (!attack.IsValidTarget(target)) { continue; }
|
||||
if (!attack.IsValidTarget(target as IDamageable)) { continue; }
|
||||
if (target is ISerializableEntity se && target is Character)
|
||||
{
|
||||
if (attack.Conditionals.Any(c => !c.Matches(se))) { continue; }
|
||||
@@ -1176,6 +1194,7 @@ namespace Barotrauma
|
||||
|
||||
float CalculatePriority(Limb limb, Vector2 attackPos)
|
||||
{
|
||||
if (Character.AnimController.SimplePhysicsEnabled) { return 1 + limb.attack.Priority; }
|
||||
float dist = Vector2.Distance(limb.WorldPosition, attackPos);
|
||||
// The limb is ignored if the target is not close. Prevents character going in reverse if very far away from it.
|
||||
// We also need a max value that is more than the actual range.
|
||||
@@ -1242,7 +1261,10 @@ namespace Barotrauma
|
||||
LatchOntoAI?.SetAttachTarget(wall.Submarine.PhysicsBody.FarseerBody, wall.Submarine, ConvertUnits.ToSimUnits(sectionPos), attachTargetNormal);
|
||||
if (Character.AnimController.CanEnterSubmarine || !wall.SectionBodyDisabled(sectionIndex) && !IsWallDisabled(wall))
|
||||
{
|
||||
wallTarget = new WallTarget(sectionPos, wall, sectionIndex);
|
||||
if (AIParams.TargetOuterWalls || wall.prefab.Tags.Contains("inner"))
|
||||
{
|
||||
wallTarget = new WallTarget(sectionPos, wall, sectionIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!Character.AnimController.CanEnterSubmarine && wallTarget == null)
|
||||
@@ -1271,7 +1293,7 @@ namespace Barotrauma
|
||||
}
|
||||
return isDisabled;
|
||||
}
|
||||
|
||||
|
||||
public override void OnAttacked(Character attacker, AttackResult attackResult)
|
||||
{
|
||||
float reactionTime = Rand.Range(0.1f, 0.3f);
|
||||
@@ -1281,7 +1303,7 @@ namespace Barotrauma
|
||||
Character.AnimController.ReleaseStuckLimbs();
|
||||
LatchOntoAI?.DeattachFromBody();
|
||||
if (attacker == null || attacker.AiTarget == null) { return; }
|
||||
bool isFriendly = attacker.SpeciesName == Character.SpeciesName || attacker.Params.Group == Character.Params.Group;
|
||||
bool isFriendly = IsFriendly(Character, attacker);
|
||||
if (wasLatched)
|
||||
{
|
||||
avoidTimer = avoidTime * Rand.Range(0.75f, 1.25f);
|
||||
@@ -1302,7 +1324,7 @@ namespace Barotrauma
|
||||
}
|
||||
if (!isFriendly && attackResult.Damage > 0.0f)
|
||||
{
|
||||
bool canAttack = attacker.Submarine == Character.Submarine && canAttackCharacters || attacker.Submarine != null && canAttackSub;
|
||||
bool canAttack = attacker.Submarine == Character.Submarine && canAttackCharacters || attacker.Submarine != null && canAttackWalls;
|
||||
if (Character.Params.AI.AttackWhenProvoked && canAttack)
|
||||
{
|
||||
if (attacker.IsHusk)
|
||||
@@ -1356,7 +1378,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
AITargetMemory targetMemory = GetTargetMemory(attacker.AiTarget);
|
||||
AITargetMemory targetMemory = GetTargetMemory(attacker.AiTarget, true);
|
||||
targetMemory.Priority += GetRelativeDamage(attackResult.Damage, Character.Vitality) * AggressionHurt;
|
||||
|
||||
// Only allow to react once. Otherwise would attack the target with only a fraction of a cooldown
|
||||
@@ -1399,7 +1421,7 @@ namespace Barotrauma
|
||||
var aiTarget = wallTarget.Structure.AiTarget;
|
||||
if (aiTarget != null && SelectedAiTarget != aiTarget)
|
||||
{
|
||||
SelectTarget(aiTarget, GetTargetMemory(SelectedAiTarget).Priority);
|
||||
SelectTarget(aiTarget, GetTargetMemory(SelectedAiTarget, true).Priority);
|
||||
}
|
||||
}
|
||||
IDamageable damageTarget = wallTarget != null ? wallTarget.Structure : SelectedAiTarget.Entity as IDamageable;
|
||||
@@ -1464,7 +1486,7 @@ namespace Barotrauma
|
||||
State = AIState.Idle;
|
||||
return;
|
||||
}
|
||||
Vector2 mouthPos = Character.AnimController.GetMouthPosition().Value;
|
||||
Vector2 mouthPos = Character.AnimController.SimplePhysicsEnabled ? SimPosition : Character.AnimController.GetMouthPosition().Value;
|
||||
Vector2 attackSimPosition = Character.GetRelativeSimPosition(target);
|
||||
Vector2 limbDiff = attackSimPosition - mouthPos;
|
||||
float extent = Math.Max(mouthLimb.body.GetMaxExtent(), 2);
|
||||
@@ -1493,6 +1515,25 @@ namespace Barotrauma
|
||||
|
||||
#endregion
|
||||
|
||||
private void UpdateFollow(float deltaTime)
|
||||
{
|
||||
if (SelectedAiTarget == null || SelectedAiTarget.Entity == null || SelectedAiTarget.Entity.Removed)
|
||||
{
|
||||
State = AIState.Idle;
|
||||
return;
|
||||
}
|
||||
Vector2 dir = Vector2.Normalize(SelectedAiTarget.Entity.WorldPosition - Character.WorldPosition);
|
||||
if (!MathUtils.IsValid(dir))
|
||||
{
|
||||
return;
|
||||
}
|
||||
steeringManager.SteeringManual(deltaTime, dir);
|
||||
if (Character.AnimController.InWater)
|
||||
{
|
||||
SteeringManager.SteeringAvoid(deltaTime, lookAheadDistance: avoidLookAheadDistance, weight: 15);
|
||||
}
|
||||
}
|
||||
|
||||
#region Targeting
|
||||
private bool IsLatchedOnSub => LatchOntoAI != null && LatchOntoAI.IsAttachedToSub;
|
||||
|
||||
@@ -1619,13 +1660,16 @@ namespace Barotrauma
|
||||
Door door = null;
|
||||
if (aiTarget.Entity is Item item)
|
||||
{
|
||||
//item inside and we're outside -> attack the hull
|
||||
if (item.CurrentHull != null && character.CurrentHull == null)
|
||||
{
|
||||
targetingTag = "room";
|
||||
}
|
||||
|
||||
door = item.GetComponent<Door>();
|
||||
bool targetingFromOutsideToInside = item.CurrentHull != null && character.CurrentHull == null;
|
||||
if (targetingFromOutsideToInside)
|
||||
{
|
||||
if (door != null && !canAttackDoors || !canAttackWalls)
|
||||
{
|
||||
// Can't reach
|
||||
continue;
|
||||
}
|
||||
}
|
||||
foreach (var prio in AIParams.Targets)
|
||||
{
|
||||
if (item.HasTag(prio.Tag))
|
||||
@@ -1634,7 +1678,25 @@ namespace Barotrauma
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (door == null && targetingTag == null)
|
||||
{
|
||||
if (item.GetComponent<Sonar>() != null)
|
||||
{
|
||||
targetingTag = "sonar";
|
||||
}
|
||||
else if (targetingFromOutsideToInside)
|
||||
{
|
||||
targetingTag = "room";
|
||||
}
|
||||
}
|
||||
else if (targetingTag == "nasonov")
|
||||
{
|
||||
if ((item.Submarine == null || !item.Submarine.Info.IsPlayer) && item.ParentInventory == null)
|
||||
{
|
||||
// Only target nasonovartifacts when they are held be a player or inside the playersub
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// Ignore the target if it's a decoy and the character is already inside a sub
|
||||
if (character.CurrentHull != null && targetingTag == "decoy")
|
||||
{
|
||||
@@ -1649,15 +1711,13 @@ namespace Barotrauma
|
||||
// Ignore structures that doesn't have a body (not walls)
|
||||
continue;
|
||||
}
|
||||
if (s.IsPlatform)
|
||||
if (s.IsPlatform) { continue; }
|
||||
if (s.Submarine == null) { continue; }
|
||||
bool isCharacterInside = character.CurrentHull != null;
|
||||
bool isInnerWall = s.prefab.Tags.Contains("inner");
|
||||
if (isInnerWall && !isCharacterInside)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
bool isCharacterOutside = s.Submarine == null || character.CurrentHull == null;
|
||||
bool targetInnerWalls = AIParams.TargetInnerWalls;
|
||||
if (!isCharacterOutside && !targetInnerWalls)
|
||||
{
|
||||
// Ignore walls when inside (walltargets still work)
|
||||
// Ignore inner walls when outside (walltargets still work)
|
||||
continue;
|
||||
}
|
||||
valueModifier = 1;
|
||||
@@ -1670,48 +1730,71 @@ namespace Barotrauma
|
||||
var section = s.Sections[i];
|
||||
if (section.gap == null) { continue; }
|
||||
bool leadsInside = !section.gap.IsRoomToRoom && section.gap.FlowTargetHull != null;
|
||||
isInnerWall = isInnerWall || !leadsInside;
|
||||
if (Character.AnimController.CanEnterSubmarine)
|
||||
{
|
||||
if (isCharacterOutside)
|
||||
if (!isCharacterInside)
|
||||
{
|
||||
if (CanPassThroughHole(s, i))
|
||||
{
|
||||
valueModifier *= leadsInside ? (AggressiveBoarding ? 5 : 1) : (targetInnerWalls ? 1 : 0);
|
||||
valueModifier *= leadsInside ? (AggressiveBoarding ? 5 : 1) : 0;
|
||||
}
|
||||
else
|
||||
else if (AggressiveBoarding && leadsInside && canAttackWalls && AIParams.TargetOuterWalls)
|
||||
{
|
||||
// Ignore holes that cannot be passed through if cannot attack items/structures. Holes that are big enough should be targeted, so that we can get in
|
||||
if (!canAttackSub)
|
||||
// Up to 100% priority increase for every gap in the wall when an aggressive boarder is outside
|
||||
valueModifier *= 1 + section.gap.Open;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Inside
|
||||
if (AggressiveBoarding)
|
||||
{
|
||||
if (!isInnerWall)
|
||||
{
|
||||
// Only interested in getting inside (aggressive boarder) -> don't target outer walls when already inside
|
||||
valueModifier = 0;
|
||||
break;
|
||||
}
|
||||
else if (CanPassThroughHole(s, i))
|
||||
{
|
||||
valueModifier *= isInnerWall ? 1 : 0;
|
||||
}
|
||||
else if (!canAttackWalls)
|
||||
{
|
||||
valueModifier = 0;
|
||||
break;
|
||||
}
|
||||
if (AggressiveBoarding && leadsInside)
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!canAttackWalls)
|
||||
{
|
||||
// Up to 100% priority increase for every gap in the wall when an aggressive boarder is outside
|
||||
valueModifier *= 1 + section.gap.Open;
|
||||
valueModifier = 0;
|
||||
break;
|
||||
}
|
||||
// We are actually interested in breaking things -> reduce the priority when the wall is already broken
|
||||
// (Terminalcells)
|
||||
valueModifier *= 1 - section.gap.Open * 0.25f;
|
||||
}
|
||||
}
|
||||
else if (!canAttackSub || CanPassThroughHole(s, i))
|
||||
}
|
||||
else
|
||||
{
|
||||
// Cannot enter
|
||||
if (isInnerWall || !canAttackWalls)
|
||||
{
|
||||
// Already inside -> ignore holes in the walls and ignore walls if cannot attack the sub.
|
||||
// Ignore inner walls and all walls if cannot do damage on walls.
|
||||
valueModifier = 0;
|
||||
break;
|
||||
}
|
||||
else if (canAttackSub && !AggressiveBoarding)
|
||||
else if (AggressiveBoarding)
|
||||
{
|
||||
// We are actually interested in breaking things -> reduce the priority when the wall is already broken
|
||||
valueModifier *= 1 - section.gap.Open * 0.25f;
|
||||
// Up to 100% priority increase for every gap in the wall when an aggressive boarder is outside
|
||||
// (Bonethreshers)
|
||||
valueModifier *= 1 + section.gap.Open;
|
||||
}
|
||||
}
|
||||
else if (!leadsInside || !canAttackSub)
|
||||
{
|
||||
// Can't get in, ignore inner walls
|
||||
// Also ignore all walls if cannot attack the sub
|
||||
valueModifier = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -1727,31 +1810,34 @@ namespace Barotrauma
|
||||
}
|
||||
if (door.Item.Submarine == null) { continue;}
|
||||
bool isOutdoor = door.LinkedGap?.FlowTargetHull != null && !door.LinkedGap.IsRoomToRoom;
|
||||
bool isOpen = door.IsOpen;
|
||||
if (!isOpen && (!canAttackSub))
|
||||
bool isOpen = door.IsOpen || door.IsBroken;
|
||||
if (!isOpen && !canAttackDoors || (isOutdoor && !AIParams.TargetOuterWalls))
|
||||
{
|
||||
// Ignore doors that are not open if cannot attack items/structures. Open doors should be targeted, so that we can get in if we are aggressive boarders
|
||||
valueModifier = 0;
|
||||
// Ignore doors that are not open if cannot attack doors or shouldn't target outer doors.
|
||||
continue;
|
||||
}
|
||||
if (character.CurrentHull == null)
|
||||
if (isOpen && (!Character.AnimController.CanEnterSubmarine || !AggressiveBoarding))
|
||||
{
|
||||
valueModifier = isOutdoor ? 1 : 0;
|
||||
// Ignore broken and open doors
|
||||
// Aggressive boarders don't ignore open doors, because they use them for get in.
|
||||
continue;
|
||||
}
|
||||
else if (AggressiveBoarding)
|
||||
if (AggressiveBoarding)
|
||||
{
|
||||
// Increase priority if the character is outside and an aggressive boarder, and the door is from outside to inside
|
||||
if (character.CurrentHull == null)
|
||||
// Increase the priority if the character is outside and the door is from outside to inside
|
||||
if (character.CurrentHull == null && isOutdoor)
|
||||
{
|
||||
valueModifier *= isOpen ? 5 : 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
valueModifier *= isOpen ? 0 : 1;
|
||||
// Inside
|
||||
valueModifier *= isOpen || isOutdoor ? 0 : 1;
|
||||
}
|
||||
}
|
||||
else if (!Character.AnimController.CanEnterSubmarine && isOpen) //ignore broken and open doors
|
||||
else if (character.CurrentHull == null)
|
||||
{
|
||||
continue;
|
||||
valueModifier = isOutdoor ? 1 : 0;
|
||||
}
|
||||
}
|
||||
else if (aiTarget.Entity is IDamageable targetDamageable && targetDamageable.Health <= 0.0f)
|
||||
@@ -1783,7 +1869,7 @@ namespace Barotrauma
|
||||
// -> just ignore the distance and attack whatever has the highest priority
|
||||
dist = Math.Max(dist, 100.0f);
|
||||
|
||||
AITargetMemory targetMemory = GetTargetMemory(aiTarget);
|
||||
AITargetMemory targetMemory = GetTargetMemory(aiTarget, true);
|
||||
if (Character.CurrentHull != null && Math.Abs(toTarget.Y) > Character.CurrentHull.Size.Y)
|
||||
{
|
||||
// Inside the sub, treat objects that are up or down, as they were farther away.
|
||||
@@ -1793,9 +1879,18 @@ namespace Barotrauma
|
||||
|
||||
if (valueModifier > targetValue)
|
||||
{
|
||||
// Don't target items that we own.
|
||||
// This is a rare case, and almost entirely related to Humanhusks, so let's check it last to reduce unnecessary checks (although the check shouldn't be expensive)
|
||||
if (aiTarget.Entity is Item i && i.IsOwnedBy(character)) { continue; }
|
||||
if (aiTarget.Entity is Item i)
|
||||
{
|
||||
Character owner = GetOwner(i);
|
||||
// Don't target items that we own.
|
||||
// This is a rare case, and almost entirely related to Humanhusks, so let's check it last to reduce unnecessary checks (although the check shouldn't be expensive)
|
||||
if (owner == character) { continue; }
|
||||
if (owner != null && IsFriendly(Character, owner))
|
||||
{
|
||||
// If the item is held by a friendly character, ignore it.
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (targetCharacter != null)
|
||||
{
|
||||
if (targetCharacter.Submarine != Character.Submarine)
|
||||
@@ -1820,7 +1915,7 @@ namespace Barotrauma
|
||||
foreach (var gap in Character.CurrentHull.ConnectedGaps)
|
||||
{
|
||||
var door = gap.ConnectedDoor;
|
||||
if (door == null || !door.IsOpen)
|
||||
if (door == null || !door.IsOpen && !door.IsBroken)
|
||||
{
|
||||
var wall = gap.ConnectedWall;
|
||||
if (wall != null)
|
||||
@@ -1855,12 +1950,15 @@ namespace Barotrauma
|
||||
return SelectedAiTarget;
|
||||
}
|
||||
|
||||
private AITargetMemory GetTargetMemory(AITarget target)
|
||||
private AITargetMemory GetTargetMemory(AITarget target, bool addIfNotFound)
|
||||
{
|
||||
if (!targetMemories.TryGetValue(target, out AITargetMemory memory))
|
||||
{
|
||||
memory = new AITargetMemory(target, 10);
|
||||
targetMemories.Add(target, memory);
|
||||
if (addIfNotFound)
|
||||
{
|
||||
memory = new AITargetMemory(target, 10);
|
||||
targetMemories.Add(target, memory);
|
||||
}
|
||||
}
|
||||
return memory;
|
||||
}
|
||||
@@ -1875,8 +1973,11 @@ namespace Barotrauma
|
||||
}
|
||||
else if (CanPerceive(_selectedAiTarget, distSquared: Vector2.DistanceSquared(Character.WorldPosition, _selectedAiTarget.WorldPosition)))
|
||||
{
|
||||
var memory = GetTargetMemory(_selectedAiTarget);
|
||||
memory.Location = _selectedAiTarget.WorldPosition;
|
||||
var memory = GetTargetMemory(_selectedAiTarget, false);
|
||||
if (memory != null)
|
||||
{
|
||||
memory.Location = _selectedAiTarget.WorldPosition;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2014,11 +2115,17 @@ namespace Barotrauma
|
||||
{
|
||||
// If the target is shooting from the submarine, we might not perceive it because it doesn't move.
|
||||
// --> Target the submarine too.
|
||||
if (target.Submarine != null && canAttackSub)
|
||||
if (target.Submarine != null && (canAttackDoors || canAttackWalls))
|
||||
{
|
||||
ChangeParams("room", state, priority);
|
||||
ChangeParams("wall", state, priority);
|
||||
ChangeParams("door", state, priority);
|
||||
if (canAttackWalls)
|
||||
{
|
||||
ChangeParams("wall", state, priority);
|
||||
}
|
||||
if (canAttackDoors)
|
||||
{
|
||||
ChangeParams("door", state, priority);
|
||||
}
|
||||
}
|
||||
ChangeParams("provocative", state, priority, onlyExisting: true);
|
||||
ChangeParams("light", state, priority, onlyExisting: true);
|
||||
@@ -2039,7 +2146,7 @@ namespace Barotrauma
|
||||
Character.AnimController.ReleaseStuckLimbs();
|
||||
escapeTarget = null;
|
||||
AttackingLimb = null;
|
||||
escapeMargin = 0;
|
||||
movementMargin = 0;
|
||||
allGapsSearched = false;
|
||||
unreachableGaps.Clear();
|
||||
if (isStateChanged && to == AIState.Idle && from != to)
|
||||
@@ -2064,28 +2171,66 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public void ReevaluateAttacks()
|
||||
{
|
||||
canAttackWalls = LatchOntoAI != null && LatchOntoAI.AttachToSub;
|
||||
canAttackDoors = false;
|
||||
canAttackCharacters = false;
|
||||
foreach (var limb in Character.AnimController.Limbs)
|
||||
{
|
||||
if (limb.IsSevered) { continue; }
|
||||
if (limb.attack == null) { continue; }
|
||||
if (!canAttackWalls)
|
||||
{
|
||||
canAttackWalls = limb.attack.IsValidTarget(AttackTarget.Structure) && limb.attack.StructureDamage > 0;
|
||||
}
|
||||
if (!canAttackDoors)
|
||||
{
|
||||
canAttackDoors = limb.attack.IsValidTarget(AttackTarget.Structure) && limb.attack.ItemDamage > 0;
|
||||
}
|
||||
if (!canAttackCharacters)
|
||||
{
|
||||
canAttackCharacters = limb.attack.IsValidTarget(AttackTarget.Character);
|
||||
}
|
||||
}
|
||||
if (PathSteering != null)
|
||||
{
|
||||
PathSteering.CanBreakDoors = canAttackDoors;
|
||||
}
|
||||
}
|
||||
|
||||
private Vector2 returnDir;
|
||||
private float returnTimer;
|
||||
private void SteerInsideLevel(float deltaTime)
|
||||
{
|
||||
if (Level.Loaded == null) { return; }
|
||||
|
||||
Vector2 levelSimSize = new Vector2(
|
||||
ConvertUnits.ToSimUnits(Level.Loaded.Size.X),
|
||||
ConvertUnits.ToSimUnits(Level.Loaded.Size.Y));
|
||||
|
||||
float margin = 10.0f;
|
||||
|
||||
if (SimPosition.Y < 0.0)
|
||||
if (SteeringManager is IndoorsSteeringManager || !StayInsideLevel) { return; }
|
||||
if (Level.Loaded == null) { return; }
|
||||
Vector2 levelSimSize = ConvertUnits.ToSimUnits(Level.Loaded.Size.X, Level.Loaded.Size.Y);
|
||||
float returnTime = 3;
|
||||
if (SimPosition.Y < 0)
|
||||
{
|
||||
steeringManager.SteeringManual(deltaTime, Vector2.UnitY * MathUtils.InverseLerp(0.0f, -margin, SimPosition.Y));
|
||||
// Too far down
|
||||
returnTimer = returnTime * Rand.Range(0.75f, 1.25f);
|
||||
returnDir = Vector2.UnitY;
|
||||
}
|
||||
if (SimPosition.X < 0.0f)
|
||||
if (SimPosition.X < 0)
|
||||
{
|
||||
steeringManager.SteeringManual(deltaTime, Vector2.UnitX * MathUtils.InverseLerp(0.0f, -margin, SimPosition.X));
|
||||
// Too far left
|
||||
returnTimer = returnTime * Rand.Range(0.75f, 1.25f);
|
||||
returnDir = Vector2.UnitX;
|
||||
}
|
||||
if (SimPosition.X > levelSimSize.X)
|
||||
{
|
||||
steeringManager.SteeringManual(deltaTime, Vector2.UnitX * MathUtils.InverseLerp(levelSimSize.X, levelSimSize.X + margin, SimPosition.X));
|
||||
}
|
||||
// Too far right
|
||||
returnTimer = returnTime * Rand.Range(0.75f, 1.25f);
|
||||
returnDir = -Vector2.UnitX;
|
||||
}
|
||||
if (returnTimer > 0)
|
||||
{
|
||||
returnTimer -= deltaTime;
|
||||
SteeringManager.Reset();
|
||||
SteeringManager.SteeringManual(deltaTime, returnDir);
|
||||
}
|
||||
}
|
||||
|
||||
private bool CanPassThroughHole(Structure wall, int sectionIndex)
|
||||
@@ -2116,7 +2261,6 @@ namespace Barotrauma
|
||||
targetLimbs.Clear();
|
||||
foreach (var limb in target.AnimController.Limbs)
|
||||
{
|
||||
if (limb.IsSevered) { continue; }
|
||||
if (limb.type == targetLimbType || targetLimbType == LimbType.None)
|
||||
{
|
||||
targetLimbs.Add(limb);
|
||||
@@ -2131,6 +2275,7 @@ namespace Barotrauma
|
||||
Limb targetLimb = null;
|
||||
foreach (Limb limb in targetLimbs)
|
||||
{
|
||||
if (limb.IsSevered) { continue; }
|
||||
float dist = Vector2.DistanceSquared(limb.WorldPosition, attackLimb.WorldPosition) / Math.Max(limb.AttackPriority, 0.1f);
|
||||
if (dist < closestDist)
|
||||
{
|
||||
@@ -2140,6 +2285,27 @@ namespace Barotrauma
|
||||
}
|
||||
return targetLimb;
|
||||
}
|
||||
|
||||
private Character GetOwner(Item item)
|
||||
{
|
||||
// If the item is held by a character, attack the character instead.
|
||||
var pickable = item.GetComponent<Pickable>();
|
||||
if (pickable != null)
|
||||
{
|
||||
Character owner = pickable.Picker ?? item.FindParentInventory(i => i.Owner is Character)?.Owner as Character;
|
||||
if (owner != null)
|
||||
{
|
||||
var target = owner.AiTarget;
|
||||
if (target?.Entity != null && !target.Entity.Removed)
|
||||
{
|
||||
return owner;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static bool IsFriendly(Character me, Character other) => other.SpeciesName == me.SpeciesName || other.Params.CompareGroup(me.Params.Group);
|
||||
}
|
||||
|
||||
//the "memory" of the Character
|
||||
|
||||
@@ -30,8 +30,9 @@ namespace Barotrauma
|
||||
|
||||
public static float HULL_SAFETY_THRESHOLD = 50;
|
||||
|
||||
public HashSet<Hull> UnreachableHulls { get; private set; } = new HashSet<Hull>();
|
||||
public HashSet<Hull> UnsafeHulls { get; private set; } = new HashSet<Hull>();
|
||||
public readonly HashSet<Hull> UnreachableHulls = new HashSet<Hull>();
|
||||
public readonly HashSet<Hull> UnsafeHulls = new HashSet<Hull>();
|
||||
public readonly List<Item> IgnoredItems = new List<Item>();
|
||||
|
||||
private SteeringManager outsideSteering, insideSteering;
|
||||
|
||||
@@ -55,13 +56,13 @@ namespace Barotrauma
|
||||
private set;
|
||||
}
|
||||
|
||||
public float CurrentHullSafety { get; private set; }
|
||||
public float CurrentHullSafety { get; private set; } = 100;
|
||||
|
||||
public HumanAIController(Character c) : base(c)
|
||||
{
|
||||
if (!c.IsHuman)
|
||||
{
|
||||
throw new System.Exception($"Tried to create a human ai controller for a non-human: {c.SpeciesName}!");
|
||||
throw new Exception($"Tried to create a human ai controller for a non-human: {c.SpeciesName}!");
|
||||
}
|
||||
insideSteering = new IndoorsSteeringManager(this, true, false);
|
||||
outsideSteering = new SteeringManager(this);
|
||||
@@ -85,7 +86,7 @@ namespace Barotrauma
|
||||
{
|
||||
unreachableClearTimer = clearUnreachableInterval;
|
||||
UnreachableHulls.Clear();
|
||||
ignoredContainers.Clear();
|
||||
IgnoredItems.Clear();
|
||||
}
|
||||
|
||||
// Use the pathfinding also outside of the sub, but not farther than the extents of the sub + 500 units.
|
||||
@@ -175,9 +176,7 @@ namespace Barotrauma
|
||||
}
|
||||
steeringManager.Update(Character.AnimController.GetCurrentSpeed(run && Character.CanRun));
|
||||
|
||||
bool ignorePlatforms = Character.AnimController.TargetMovement.Y < -0.5f &&
|
||||
(-Character.AnimController.TargetMovement.Y > Math.Abs(Character.AnimController.TargetMovement.X));
|
||||
|
||||
bool ignorePlatforms = Character.AnimController.TargetMovement.Y < -0.5f && (-Character.AnimController.TargetMovement.Y > Math.Abs(Character.AnimController.TargetMovement.X));
|
||||
if (steeringManager == insideSteering)
|
||||
{
|
||||
var currPath = PathSteering.CurrentPath;
|
||||
@@ -185,51 +184,24 @@ namespace Barotrauma
|
||||
{
|
||||
if (currPath.CurrentNode.SimPosition.Y < Character.AnimController.GetColliderBottom().Y)
|
||||
{
|
||||
// Don't allow to jump from too high. The formula might require tweaking.
|
||||
// Don't allow to jump from too high.
|
||||
float allowedJumpHeight = Character.AnimController.ImpactTolerance / 2;
|
||||
float height = Math.Abs(currPath.CurrentNode.SimPosition.Y - Character.SimPosition.Y);
|
||||
ignorePlatforms = height < allowedJumpHeight;
|
||||
}
|
||||
}
|
||||
|
||||
if (Character.IsClimbing && PathSteering.IsNextLadderSameAsCurrent)
|
||||
{
|
||||
Character.AnimController.TargetMovement = new Vector2(0.0f, Math.Sign(Character.AnimController.TargetMovement.Y));
|
||||
}
|
||||
}
|
||||
|
||||
Character.AnimController.IgnorePlatforms = ignorePlatforms;
|
||||
|
||||
Vector2 targetMovement = AnimController.TargetMovement;
|
||||
|
||||
if (!Character.AnimController.InWater)
|
||||
{
|
||||
targetMovement = new Vector2(Character.AnimController.TargetMovement.X, MathHelper.Clamp(Character.AnimController.TargetMovement.Y, -1.0f, 1.0f));
|
||||
}
|
||||
|
||||
if (Character.AnimController.InWater && targetMovement.LengthSquared() < 0.000001f)
|
||||
{
|
||||
bool isAiming = false;
|
||||
var holdable = Character.SelectedConstruction?.GetComponent<Holdable>();
|
||||
if (holdable != null)
|
||||
{
|
||||
isAiming = holdable.ControlPose;
|
||||
}
|
||||
bool swimInPlace = !isAiming;
|
||||
if (swimInPlace && ObjectiveManager.GetActiveObjective() is AIObjectiveGoTo goToObjective)
|
||||
{
|
||||
if (goToObjective.Target != Character)
|
||||
{
|
||||
swimInPlace = false;
|
||||
}
|
||||
}
|
||||
if (swimInPlace)
|
||||
{
|
||||
// Swim in place so that we don't fall motionless and look dead.
|
||||
targetMovement = new Vector2(targetMovement.X, Rand.Range(-0.001f, 0.001f));
|
||||
}
|
||||
}
|
||||
|
||||
Character.AnimController.TargetMovement = Character.ApplyMovementLimits(targetMovement, AnimController.GetCurrentSpeed(run));
|
||||
|
||||
flipTimer -= deltaTime;
|
||||
@@ -280,14 +252,14 @@ namespace Barotrauma
|
||||
else
|
||||
{
|
||||
findItemState = FindItemState.Extinguisher;
|
||||
if (FindSuitableContainer(Character, extinguisher, out Item targetContainer))
|
||||
if (FindSuitableContainer(extinguisher, out Item targetContainer))
|
||||
{
|
||||
findItemState = FindItemState.None;
|
||||
itemIndex = 0;
|
||||
if (targetContainer != null)
|
||||
{
|
||||
var decontainObjective = new AIObjectiveDecontainItem(Character, extinguisher, ObjectiveManager, targetContainer: targetContainer.GetComponent<ItemContainer>());
|
||||
decontainObjective.Abandoned += () => ignoredContainers.Add(targetContainer);
|
||||
decontainObjective.Abandoned += () => IgnoredItems.Add(targetContainer);
|
||||
ObjectiveManager.CurrentObjective.AddSubObjective(decontainObjective, addFirst: true);
|
||||
return;
|
||||
}
|
||||
@@ -310,42 +282,47 @@ namespace Barotrauma
|
||||
|| ObjectiveManager.IsCurrentObjective<AIObjectiveFindSafety>()
|
||||
|| ObjectiveManager.CurrentObjective.GetSubObjectivesRecursive(true).Any(o => o.KeepDivingGearOn);
|
||||
bool removeDivingSuit = !Character.AnimController.HeadInWater && oxygenLow;
|
||||
AIObjectiveGoTo gotoObjective = ObjectiveManager.GetActiveObjective<AIObjectiveGoTo>();
|
||||
bool takeMaskOff = !Character.AnimController.HeadInWater && oxygenLow;
|
||||
if (!removeDivingSuit)
|
||||
{
|
||||
bool targetHasNoSuit = gotoObjective != null && gotoObjective.mimic && !HasDivingSuit(gotoObjective.Target as Character);
|
||||
removeDivingSuit = !shouldKeepTheGearOn && (gotoObjective == null || targetHasNoSuit);
|
||||
}
|
||||
bool takeMaskOff = !Character.AnimController.HeadInWater && oxygenLow;
|
||||
if (!takeMaskOff && Character.CurrentHull.WaterPercentage < 40)
|
||||
{
|
||||
bool targetHasNoMask = gotoObjective != null && gotoObjective.mimic && !HasDivingMask(gotoObjective.Target as Character);
|
||||
takeMaskOff = !shouldKeepTheGearOn && (gotoObjective == null || targetHasNoMask);
|
||||
}
|
||||
if (gotoObjective != null)
|
||||
{
|
||||
if (gotoObjective.Target is Hull h)
|
||||
if (shouldKeepTheGearOn)
|
||||
{
|
||||
if (NeedsDivingGear(Character, h, out _))
|
||||
{
|
||||
removeDivingSuit = false;
|
||||
takeMaskOff = false;
|
||||
}
|
||||
removeDivingSuit = false;
|
||||
}
|
||||
else if (gotoObjective.Target is Character c)
|
||||
}
|
||||
if (!takeMaskOff)
|
||||
{
|
||||
if (shouldKeepTheGearOn)
|
||||
{
|
||||
if (NeedsDivingGear(Character, c.CurrentHull, out _))
|
||||
{
|
||||
removeDivingSuit = false;
|
||||
takeMaskOff = false;
|
||||
}
|
||||
takeMaskOff = false;
|
||||
}
|
||||
else if (gotoObjective.Target is Item i)
|
||||
}
|
||||
if (!shouldKeepTheGearOn && (!takeMaskOff || !removeDivingSuit))
|
||||
{
|
||||
foreach (var objective in ObjectiveManager.CurrentObjective.GetSubObjectivesRecursive(includingSelf: true))
|
||||
{
|
||||
if (NeedsDivingGear(Character, i.CurrentHull, out _))
|
||||
if (objective is AIObjectiveGoTo gotoObjective)
|
||||
{
|
||||
removeDivingSuit = false;
|
||||
takeMaskOff = false;
|
||||
bool insideSteering = SteeringManager == PathSteering && PathSteering.CurrentPath != null && !PathSteering.IsPathDirty;
|
||||
Hull targetHull = gotoObjective.GetTargetHull();
|
||||
bool targetIsOutside = (gotoObjective.Target != null && targetHull == null) || (insideSteering && PathSteering.CurrentPath.HasOutdoorsNodes);
|
||||
if (targetIsOutside || NeedsDivingGear(Character, targetHull, out _))
|
||||
{
|
||||
removeDivingSuit = false;
|
||||
takeMaskOff = false;
|
||||
break;
|
||||
}
|
||||
else if (gotoObjective.mimic)
|
||||
{
|
||||
if (!removeDivingSuit)
|
||||
{
|
||||
removeDivingSuit = !HasDivingSuit(gotoObjective.Target as Character);
|
||||
}
|
||||
if (!takeMaskOff)
|
||||
{
|
||||
takeMaskOff = !HasDivingMask(gotoObjective.Target as Character);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -363,7 +340,7 @@ namespace Barotrauma
|
||||
else
|
||||
{
|
||||
findItemState = FindItemState.DivingSuit;
|
||||
if (FindSuitableContainer(Character, divingSuit, out Item targetContainer))
|
||||
if (FindSuitableContainer(divingSuit, out Item targetContainer))
|
||||
{
|
||||
findItemState = FindItemState.None;
|
||||
itemIndex = 0;
|
||||
@@ -375,7 +352,7 @@ namespace Barotrauma
|
||||
};
|
||||
decontainObjective.Abandoned += () =>
|
||||
{
|
||||
ignoredContainers.Add(targetContainer);
|
||||
IgnoredItems.Add(targetContainer);
|
||||
};
|
||||
ObjectiveManager.CurrentObjective.AddSubObjective(decontainObjective, addFirst: true);
|
||||
return;
|
||||
@@ -405,14 +382,14 @@ namespace Barotrauma
|
||||
else
|
||||
{
|
||||
findItemState = FindItemState.DivingMask;
|
||||
if (FindSuitableContainer(Character, mask, out Item targetContainer))
|
||||
if (FindSuitableContainer(mask, out Item targetContainer))
|
||||
{
|
||||
findItemState = FindItemState.None;
|
||||
itemIndex = 0;
|
||||
if (targetContainer != null)
|
||||
{
|
||||
var decontainObjective = new AIObjectiveDecontainItem(Character, mask, ObjectiveManager, targetContainer: targetContainer.GetComponent<ItemContainer>());
|
||||
decontainObjective.Abandoned += () => ignoredContainers.Add(targetContainer);
|
||||
decontainObjective.Abandoned += () => IgnoredItems.Add(targetContainer);
|
||||
ObjectiveManager.CurrentObjective.AddSubObjective(decontainObjective, addFirst: true);
|
||||
return;
|
||||
}
|
||||
@@ -442,14 +419,14 @@ namespace Barotrauma
|
||||
{
|
||||
if (!item.AllowedSlots.Contains(InvSlotType.Any) || !Character.Inventory.TryPutItem(item, Character, new List<InvSlotType>() { InvSlotType.Any }))
|
||||
{
|
||||
if (FindSuitableContainer(Character, item, out Item targetContainer))
|
||||
if (FindSuitableContainer(item, out Item targetContainer))
|
||||
{
|
||||
findItemState = FindItemState.None;
|
||||
itemIndex = 0;
|
||||
if (targetContainer != null)
|
||||
{
|
||||
var decontainObjective = new AIObjectiveDecontainItem(Character, item, ObjectiveManager, targetContainer: targetContainer.GetComponent<ItemContainer>());
|
||||
decontainObjective.Abandoned += () => ignoredContainers.Add(targetContainer);
|
||||
decontainObjective.Abandoned += () => IgnoredItems.Add(targetContainer);
|
||||
ObjectiveManager.CurrentObjective.AddSubObjective(decontainObjective, addFirst: true);
|
||||
return;
|
||||
}
|
||||
@@ -478,11 +455,10 @@ namespace Barotrauma
|
||||
}
|
||||
private FindItemState findItemState;
|
||||
private int itemIndex;
|
||||
private List<Item> ignoredContainers = new List<Item>();
|
||||
public bool FindSuitableContainer(Character character, Item containableItem, out Item suitableContainer)
|
||||
public bool FindSuitableContainer(Item containableItem, out Item suitableContainer)
|
||||
{
|
||||
suitableContainer = null;
|
||||
if (character.FindItem(ref itemIndex, out Item targetContainer, ignoredItems: ignoredContainers, customPriorityFunction: i =>
|
||||
if (Character.FindItem(ref itemIndex, out Item targetContainer, ignoredItems: IgnoredItems, customPriorityFunction: i =>
|
||||
{
|
||||
var container = i.GetComponent<ItemContainer>();
|
||||
if (container == null) { return 0; }
|
||||
@@ -583,7 +559,7 @@ namespace Barotrauma
|
||||
if (item.CurrentHull != hull) { continue; }
|
||||
if (AIObjectiveRepairItems.IsValidTarget(item, Character))
|
||||
{
|
||||
if (item.Repairables.All(r => item.ConditionPercentage > r.AIRepairThreshold)) { continue; }
|
||||
if (item.Repairables.All(r => item.ConditionPercentage > r.RepairThreshold)) { continue; }
|
||||
if (AddTargets<AIObjectiveRepairItems, Item>(Character, item) && newOrder == null && !ObjectiveManager.HasActiveObjective<AIObjectiveRepairItem>())
|
||||
{
|
||||
var orderPrefab = Order.GetPrefab("reportbrokendevices");
|
||||
@@ -633,9 +609,13 @@ namespace Barotrauma
|
||||
if (ObjectiveManager.CurrentObjective is AIObjectiveFightIntruders) { return; }
|
||||
if (attacker == null || attacker.IsDead || attacker.Removed)
|
||||
{
|
||||
// Don't react on the damage if there's no attacker.
|
||||
// We might consider launching the retreat combat objective in some cases, so that the bot does not just stand somewhere getting damaged and dying.
|
||||
// But fires and enemies should already be handled by the FindSafetyObjective.
|
||||
return;
|
||||
// Ignore damage from falling etc that we shouldn't react to.
|
||||
if (Character.LastDamageSource == null) { return; }
|
||||
AddCombatObjective(AIObjectiveCombat.CombatMode.Retreat, Rand.Range(0.5f, 1f, Rand.RandSync.Unsynced));
|
||||
//if (Character.LastDamageSource == null) { return; }
|
||||
//AddCombatObjective(AIObjectiveCombat.CombatMode.Retreat, Rand.Range(0.5f, 1f, Rand.RandSync.Unsynced));
|
||||
}
|
||||
else if (IsFriendly(attacker))
|
||||
{
|
||||
@@ -784,7 +764,6 @@ namespace Barotrauma
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public static bool HasDivingGear(Character character, float conditionPercentage = 0) => HasDivingSuit(character, conditionPercentage) || HasDivingMask(character, conditionPercentage);
|
||||
|
||||
/// <summary>
|
||||
@@ -852,7 +831,7 @@ namespace Barotrauma
|
||||
if (item.CurrentHull != hull) { continue; }
|
||||
if (AIObjectiveRepairItems.IsValidTarget(item, character))
|
||||
{
|
||||
if (item.Repairables.All(r => item.ConditionPercentage >= r.AIRepairThreshold)) { continue; }
|
||||
if (item.Repairables.All(r => item.ConditionPercentage >= r.RepairThreshold)) { continue; }
|
||||
AddTargets<AIObjectiveRepairItems, Item>(character, item);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,8 @@ namespace Barotrauma
|
||||
private PathFinder pathFinder;
|
||||
private SteeringPath currentPath;
|
||||
|
||||
private bool canOpenDoors, canBreakDoors;
|
||||
private bool canOpenDoors;
|
||||
public bool CanBreakDoors { get; set; }
|
||||
|
||||
private Character character;
|
||||
|
||||
@@ -50,8 +51,8 @@ namespace Barotrauma
|
||||
/// </summary>
|
||||
public bool InLadders =>
|
||||
currentPath != null &&
|
||||
currentPath.CurrentNode != null && (currentPath.CurrentNode.Ladders != null ||
|
||||
(currentPath.NextNode != null && currentPath.NextNode.Ladders != null));
|
||||
currentPath.CurrentNode != null && (currentPath.CurrentNode.Ladders != null && !currentPath.CurrentNode.Ladders.Item.NonInteractable ||
|
||||
(currentPath.NextNode != null && currentPath.NextNode.Ladders != null && !currentPath.NextNode.Ladders.Item.NonInteractable));
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if any node in the path is in stairs
|
||||
@@ -69,6 +70,7 @@ namespace Barotrauma
|
||||
if (currentPath.NextNode == null) { return false; }
|
||||
var currentLadder = currentPath.CurrentNode.Ladders;
|
||||
if (currentLadder == null) { return false; }
|
||||
if (currentLadder.Item.NonInteractable) { return false; }
|
||||
var nextLadder = GetNextLadder();
|
||||
return nextLadder != null && nextLadder == currentLadder;
|
||||
}
|
||||
@@ -80,7 +82,7 @@ namespace Barotrauma
|
||||
pathFinder.GetNodePenalty = GetNodePenalty;
|
||||
|
||||
this.canOpenDoors = canOpenDoors;
|
||||
this.canBreakDoors = canBreakDoors;
|
||||
this.CanBreakDoors = canBreakDoors;
|
||||
|
||||
character = (host as AIController).Character;
|
||||
|
||||
@@ -103,6 +105,12 @@ namespace Barotrauma
|
||||
IsPathDirty = false;
|
||||
}
|
||||
|
||||
public void ResetPath()
|
||||
{
|
||||
currentPath = null;
|
||||
IsPathDirty = true;
|
||||
}
|
||||
|
||||
public void SteeringSeek(Vector2 target, float weight, Func<PathNode, bool> startNodeFilter = null, Func<PathNode, bool> endNodeFilter = null, Func<PathNode, bool> nodeFilter = null)
|
||||
{
|
||||
steering += CalculateSteeringSeek(target, weight, startNodeFilter, endNodeFilter, nodeFilter);
|
||||
@@ -115,7 +123,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (currentPath == null) { return null; }
|
||||
if (currentPath.NextNode == null) { return null; }
|
||||
if (currentPath.NextNode.Ladders != null)
|
||||
if (currentPath.NextNode.Ladders != null && !currentPath.NextNode.Ladders.Item.NonInteractable)
|
||||
{
|
||||
return currentPath.NextNode.Ladders;
|
||||
}
|
||||
@@ -126,7 +134,10 @@ namespace Barotrauma
|
||||
{
|
||||
var node = currentPath.Nodes[index];
|
||||
if (node == null) { return null; }
|
||||
return node.Ladders;
|
||||
if (node.Ladders != null && !node.Ladders.Item.NonInteractable)
|
||||
{
|
||||
return node.Ladders;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -134,7 +145,19 @@ namespace Barotrauma
|
||||
|
||||
private Vector2 CalculateSteeringSeek(Vector2 target, float weight, Func<PathNode, bool> startNodeFilter = null, Func<PathNode, bool> endNodeFilter = null, Func<PathNode, bool> nodeFilter = null)
|
||||
{
|
||||
bool needsNewPath = character.Params.PathFinderPriority > 0.5f && (currentPath == null || currentPath.Unreachable || currentPath.Finished || Vector2.DistanceSquared(target, currentTarget) > 1);
|
||||
Vector2 targetDiff = target - currentTarget;
|
||||
if (currentPath != null && currentPath.Nodes.Any())
|
||||
{
|
||||
//current path calculated relative to a different sub than where the character is now
|
||||
//take that into account when calculating if the target has moved
|
||||
Submarine currentPathSub = currentPath?.Nodes.First().Submarine;
|
||||
if (currentPathSub != character.Submarine && character.Submarine != null)
|
||||
{
|
||||
Vector2 subDiff = character.Submarine.SimPosition - currentPathSub.SimPosition;
|
||||
targetDiff += subDiff;
|
||||
}
|
||||
}
|
||||
bool needsNewPath = character.Params.PathFinderPriority > 0.5f && (currentPath == null || currentPath.Unreachable || currentPath.Finished || targetDiff.LengthSquared() > 1);
|
||||
//find a new path if one hasn't been found yet or the target is different from the current target
|
||||
if (needsNewPath || findPathTimer < -1.0f)
|
||||
{
|
||||
@@ -172,12 +195,13 @@ namespace Barotrauma
|
||||
|
||||
Vector2 diff = DiffToCurrentNode();
|
||||
var collider = character.AnimController.Collider;
|
||||
// Only humanoids can climb ladders
|
||||
bool canClimb = character.AnimController is HumanoidAnimController;
|
||||
//if not in water and the waypoint is between the top and bottom of the collider, no need to move vertically
|
||||
if (!character.AnimController.InWater && !character.IsClimbing && diff.Y < collider.height / 2 + collider.radius)
|
||||
if (canClimb && !character.AnimController.InWater && !character.IsClimbing && diff.Y < collider.height / 2 + collider.radius)
|
||||
{
|
||||
diff.Y = 0.0f;
|
||||
}
|
||||
//if (diff.LengthSquared() < 0.001f) { return -host.Steering; }
|
||||
if (diff == Vector2.Zero) { return Vector2.Zero; }
|
||||
return Vector2.Normalize(diff) * weight;
|
||||
}
|
||||
@@ -186,8 +210,10 @@ namespace Barotrauma
|
||||
|
||||
private Vector2 DiffToCurrentNode()
|
||||
{
|
||||
if (currentPath == null || currentPath.Unreachable) return Vector2.Zero;
|
||||
|
||||
if (currentPath == null || currentPath.Unreachable)
|
||||
{
|
||||
return Vector2.Zero;
|
||||
}
|
||||
if (currentPath.Finished)
|
||||
{
|
||||
Vector2 pos2 = host.SimPosition;
|
||||
@@ -197,15 +223,12 @@ namespace Barotrauma
|
||||
pos2 -= CurrentPath.Nodes.Last().Submarine.SimPosition;
|
||||
}
|
||||
return currentTarget - pos2;
|
||||
}
|
||||
|
||||
}
|
||||
if (canOpenDoors && !character.LockHands && buttonPressCooldown <= 0.0f)
|
||||
{
|
||||
CheckDoorsInPath();
|
||||
}
|
||||
|
||||
}
|
||||
Vector2 pos = host.SimPosition;
|
||||
|
||||
if (character != null && currentPath.CurrentNode != null)
|
||||
{
|
||||
if (CurrentPath.CurrentNode.Submarine != null)
|
||||
@@ -220,19 +243,17 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool isDiving = character.AnimController.InWater && character.AnimController.HeadInWater;
|
||||
|
||||
//only humanoids can climb ladders
|
||||
if (!isDiving && character.AnimController is HumanoidAnimController && IsNextLadderSameAsCurrent)
|
||||
// Only humanoids can climb ladders
|
||||
bool canClimb = character.AnimController is HumanoidAnimController;
|
||||
if (canClimb && !isDiving && IsNextLadderSameAsCurrent)
|
||||
{
|
||||
if (character.SelectedConstruction != currentPath.CurrentNode.Ladders.Item &&
|
||||
currentPath.CurrentNode.Ladders.Item.IsInsideTrigger(character.WorldPosition))
|
||||
var ladders = currentPath.CurrentNode.Ladders;
|
||||
if (character.SelectedConstruction != ladders.Item && ladders.Item.IsInsideTrigger(character.WorldPosition))
|
||||
{
|
||||
currentPath.CurrentNode.Ladders.Item.TryInteract(character, false, true);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
var collider = character.AnimController.Collider;
|
||||
if (character.IsClimbing && !isDiving)
|
||||
{
|
||||
@@ -252,13 +273,16 @@ namespace Barotrauma
|
||||
{
|
||||
diff.Y = Math.Max(diff.Y, 1.0f);
|
||||
}
|
||||
// We need some margin, because if a hatch has closed, it's possible that the height from floor is slightly negative.
|
||||
float margin = 0.1f;
|
||||
bool isAboveFloor = heightFromFloor > -margin && heightFromFloor < collider.height * 1.5f;
|
||||
// If the next waypoint is horizontally far, we don't want to keep holding the ladders
|
||||
if (nextLadder == null || Math.Abs(currentPath.CurrentNode.WorldPosition.X - currentPath.NextNode.WorldPosition.X) > 50)
|
||||
if (isAboveFloor && (nextLadder == null || Math.Abs(currentPath.CurrentNode.WorldPosition.X - currentPath.NextNode.WorldPosition.X) > 50))
|
||||
{
|
||||
character.AnimController.Anim = AnimController.Animation.None;
|
||||
character.SelectedConstruction = null;
|
||||
}
|
||||
else if (!nextLadderSameAsCurrent)
|
||||
else if (nextLadder != null && !nextLadderSameAsCurrent)
|
||||
{
|
||||
// Try to change the ladder (hatches between two submarines)
|
||||
if (character.SelectedConstruction != nextLadder.Item && nextLadder.Item.IsInsideTrigger(character.WorldPosition))
|
||||
@@ -266,9 +290,6 @@ namespace Barotrauma
|
||||
nextLadder.Item.TryInteract(character, false, true);
|
||||
}
|
||||
}
|
||||
// We need some margin, because if a hatch has closed, it's possible that the height from floor is slightly negative.
|
||||
float margin = 0.1f;
|
||||
bool isAboveFloor = heightFromFloor > -margin && heightFromFloor < collider.height * 1.5f;
|
||||
if (nextLadder != null || isAboveFloor)
|
||||
{
|
||||
currentPath.SkipToNextNode();
|
||||
@@ -286,7 +307,7 @@ namespace Barotrauma
|
||||
}
|
||||
return diff;
|
||||
}
|
||||
else if (character.AnimController.InWater)
|
||||
else if (!canClimb || character.AnimController.InWater)
|
||||
{
|
||||
// If the character is underwater, we don't need the ladders anymore
|
||||
if (character.IsClimbing && isDiving)
|
||||
@@ -294,49 +315,59 @@ namespace Barotrauma
|
||||
character.AnimController.Anim = AnimController.Animation.None;
|
||||
character.SelectedConstruction = null;
|
||||
}
|
||||
float multiplier = MathHelper.Lerp(1, 10, MathHelper.Clamp(collider.LinearVelocity.Length() / 10, 0, 1));
|
||||
float targetDistance = collider.GetSize().X * multiplier;
|
||||
float horizontalDistance = Math.Abs(character.WorldPosition.X - currentPath.CurrentNode.WorldPosition.X);
|
||||
float verticalDistance = Math.Abs(character.WorldPosition.Y - currentPath.CurrentNode.WorldPosition.Y);
|
||||
if (character.CurrentHull != currentPath.CurrentNode.CurrentHull)
|
||||
var door = currentPath.CurrentNode.ConnectedDoor;
|
||||
bool blockedByDoor = door != null && !door.IsOpen && !door.IsBroken;
|
||||
if (!blockedByDoor)
|
||||
{
|
||||
verticalDistance *= 2;
|
||||
}
|
||||
float distance = horizontalDistance + verticalDistance;
|
||||
if (ConvertUnits.ToSimUnits(distance) < targetDistance)
|
||||
{
|
||||
currentPath.SkipToNextNode();
|
||||
float multiplier = MathHelper.Lerp(1, 10, MathHelper.Clamp(collider.LinearVelocity.Length() / 10, 0, 1));
|
||||
float targetDistance = collider.GetSize().X * multiplier;
|
||||
float horizontalDistance = Math.Abs(character.WorldPosition.X - currentPath.CurrentNode.WorldPosition.X);
|
||||
float verticalDistance = Math.Abs(character.WorldPosition.Y - currentPath.CurrentNode.WorldPosition.Y);
|
||||
if (character.CurrentHull != currentPath.CurrentNode.CurrentHull)
|
||||
{
|
||||
verticalDistance *= 2;
|
||||
}
|
||||
float distance = horizontalDistance + verticalDistance;
|
||||
if (ConvertUnits.ToSimUnits(distance) < targetDistance)
|
||||
{
|
||||
currentPath.SkipToNextNode();
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (!IsNextLadderSameAsCurrent)
|
||||
{
|
||||
// Walking horizontally
|
||||
Vector2 colliderBottom = character.AnimController.GetColliderBottom();
|
||||
Vector2 colliderSize = collider.GetSize();
|
||||
Vector2 velocity = collider.LinearVelocity;
|
||||
// If the character is smaller than this, it fails to use the waypoint nodes, because they are always too high.
|
||||
// If the character is smaller than this, it would fail to use the waypoint nodes because they are always too high.
|
||||
float minHeight = 1;
|
||||
// Cannot use the head position, because not all characters have head or it can be below the total height of the character
|
||||
float characterHeight = Math.Max(colliderSize.Y + character.AnimController.ColliderHeightFromFloor, minHeight);
|
||||
float horizontalDistance = Math.Abs(collider.SimPosition.X - currentPath.CurrentNode.SimPosition.X);
|
||||
bool isAboveFeet = currentPath.CurrentNode.SimPosition.Y > colliderBottom.Y;
|
||||
bool isNotTooHigh = currentPath.CurrentNode.SimPosition.Y < colliderBottom.Y + characterHeight;
|
||||
var door = currentPath.CurrentNode.ConnectedDoor;
|
||||
bool blockedByDoor = door != null && !door.IsOpen && !door.IsBroken;
|
||||
float margin = MathHelper.Lerp(1, 10, MathHelper.Clamp(Math.Abs(velocity.X) / 10, 0, 1));
|
||||
float targetDistance = collider.radius * margin;
|
||||
if (horizontalDistance < targetDistance && isAboveFeet && isNotTooHigh)
|
||||
if (horizontalDistance < targetDistance && isAboveFeet && isNotTooHigh && !blockedByDoor)
|
||||
{
|
||||
currentPath.SkipToNextNode();
|
||||
}
|
||||
}
|
||||
|
||||
if (currentPath.CurrentNode == null) return Vector2.Zero;
|
||||
|
||||
if (currentPath.CurrentNode == null)
|
||||
{
|
||||
return Vector2.Zero;
|
||||
}
|
||||
return currentPath.CurrentNode.SimPosition - pos;
|
||||
}
|
||||
|
||||
private bool CanAccessDoor(Door door, Func<Controller, bool> buttonFilter = null)
|
||||
{
|
||||
if (door.IsOpen) { return true; }
|
||||
if (canBreakDoors) { return true; }
|
||||
if (door.Item.NonInteractable) { return false; }
|
||||
if (CanBreakDoors) { return true; }
|
||||
if (door.IsStuck) { return false; }
|
||||
if (!canOpenDoors || character.LockHands) { return false; }
|
||||
if (door.HasIntegratedButtons)
|
||||
@@ -345,7 +376,7 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
return door.Item.GetConnectedComponents<Controller>(true).Any(b => b.HasAccess(character) && (buttonFilter == null || buttonFilter(b)));
|
||||
return door.Item.GetConnectedComponents<Controller>(true).Any(b => !b.Item.NonInteractable && b.HasAccess(character) && (buttonFilter == null || buttonFilter(b)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -381,7 +412,11 @@ namespace Barotrauma
|
||||
{
|
||||
//the node we're heading towards is the last one in the path, and at a door
|
||||
//the door needs to be open for the character to reach the node
|
||||
shouldBeOpen = true;
|
||||
if (currentWaypoint.ConnectedDoor.LinkedGap != null && currentWaypoint.ConnectedDoor.LinkedGap.IsRoomToRoom)
|
||||
{
|
||||
shouldBeOpen = true;
|
||||
door = currentWaypoint.ConnectedDoor;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -519,9 +554,9 @@ namespace Barotrauma
|
||||
//non-humanoids can't climb up ladders
|
||||
if (!(character.AnimController is HumanoidAnimController))
|
||||
{
|
||||
if (node.Waypoint.Ladders != null && nextNode.Waypoint.Ladders != null &&
|
||||
nextNode.Position.Y - node.Position.Y > 1.0f && //more than one sim unit to climb up
|
||||
nextNode.Waypoint.CurrentHull != null && nextNode.Waypoint.CurrentHull.Surface < nextNode.Waypoint.Position.Y) //upper node not underwater
|
||||
if (node.Waypoint.Ladders != null && nextNode.Waypoint.Ladders != null && nextNode.Waypoint.Ladders.Item.NonInteractable ||
|
||||
(nextNode.Position.Y - node.Position.Y > 1.0f && //more than one sim unit to climb up
|
||||
nextNode.Waypoint.CurrentHull != null && nextNode.Waypoint.CurrentHull.Surface < nextNode.Waypoint.Position.Y)) //upper node not underwater
|
||||
{
|
||||
return null;
|
||||
}
|
||||
@@ -539,7 +574,10 @@ namespace Barotrauma
|
||||
}
|
||||
if (character.NeedsAir && hull.WaterVolume / hull.Rect.Width > 100.0f)
|
||||
{
|
||||
penalty += 500.0f;
|
||||
if (!HumanAIController.HasDivingSuit(character))
|
||||
{
|
||||
penalty += 500.0f;
|
||||
}
|
||||
}
|
||||
if (character.PressureProtection < 10.0f && hull.WaterVolume > hull.Volume)
|
||||
{
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
using FarseerPhysics;
|
||||
using FarseerPhysics.Common;
|
||||
using FarseerPhysics.Dynamics;
|
||||
using FarseerPhysics.Dynamics.Joints;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -19,8 +19,8 @@ namespace Barotrauma
|
||||
private Vector2 attachSurfaceNormal;
|
||||
private Submarine attachTargetSubmarine;
|
||||
|
||||
private bool attachToSub;
|
||||
private bool attachToWalls;
|
||||
public bool AttachToSub { get; private set; }
|
||||
public bool AttachToWalls { get; private set; }
|
||||
|
||||
private float minDeattachSpeed = 3.0f, maxDeattachSpeed = 10.0f;
|
||||
private float damageOnDetach = 0.0f, detachStun = 0.0f;
|
||||
@@ -58,8 +58,8 @@ namespace Barotrauma
|
||||
|
||||
public LatchOntoAI(XElement element, EnemyAIController enemyAI)
|
||||
{
|
||||
attachToWalls = element.GetAttributeBool("attachtowalls", false);
|
||||
attachToSub = element.GetAttributeBool("attachtosub", false);
|
||||
AttachToWalls = element.GetAttributeBool("attachtowalls", false);
|
||||
AttachToSub = element.GetAttributeBool("attachtosub", false);
|
||||
minDeattachSpeed = element.GetAttributeFloat("mindeattachspeed", 3.0f);
|
||||
maxDeattachSpeed = Math.Max(minDeattachSpeed, element.GetAttributeFloat("maxdeattachspeed", 10.0f));
|
||||
damageOnDetach = element.GetAttributeFloat("damageondetach", 0.0f);
|
||||
@@ -67,11 +67,19 @@ namespace Barotrauma
|
||||
localAttachPos = ConvertUnits.ToSimUnits(element.GetAttributeVector2("localattachpos", Vector2.Zero));
|
||||
attachLimbRotation = MathHelper.ToRadians(element.GetAttributeFloat("attachlimbrotation", 0.0f));
|
||||
|
||||
if (Enum.TryParse(element.GetAttributeString("attachlimb", "Head"), out LimbType attachLimbType))
|
||||
string limbString = element.GetAttributeString("attachlimb", null);
|
||||
attachLimb = enemyAI.Character.AnimController.Limbs.FirstOrDefault(l => string.Equals(l.Name, limbString, StringComparison.OrdinalIgnoreCase));
|
||||
if (attachLimb == null)
|
||||
{
|
||||
attachLimb = enemyAI.Character.AnimController.GetLimb(attachLimbType);
|
||||
if (Enum.TryParse(limbString, out LimbType attachLimbType))
|
||||
{
|
||||
attachLimb = enemyAI.Character.AnimController.GetLimb(attachLimbType);
|
||||
}
|
||||
}
|
||||
if (attachLimb == null)
|
||||
{
|
||||
attachLimb = enemyAI.Character.AnimController.MainLimb;
|
||||
}
|
||||
if (attachLimb == null) attachLimb = enemyAI.Character.AnimController.MainLimb;
|
||||
|
||||
enemyAI.Character.OnDeath += OnCharacterDeath;
|
||||
}
|
||||
@@ -108,7 +116,9 @@ namespace Barotrauma
|
||||
//something went wrong, limb body is very far from the joint anchor -> deattach
|
||||
if (Vector2.DistanceSquared(attachJoints[i].WorldAnchorB, attachJoints[i].BodyA.Position) > 10.0f * 10.0f)
|
||||
{
|
||||
#if DEBUG
|
||||
DebugConsole.ThrowError("Limb body of the character \"" + character.Name + "\" is very far from the attach joint anchor -> deattach");
|
||||
#endif
|
||||
DeattachFromBody();
|
||||
return;
|
||||
}
|
||||
@@ -131,7 +141,7 @@ namespace Barotrauma
|
||||
switch (enemyAI.State)
|
||||
{
|
||||
case AIState.Idle:
|
||||
if (attachToWalls && character.Submarine == null && Level.Loaded != null)
|
||||
if (AttachToWalls && character.Submarine == null && Level.Loaded != null)
|
||||
{
|
||||
if (!IsAttached)
|
||||
{
|
||||
@@ -180,8 +190,9 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
float dist = Vector2.Distance(character.SimPosition, wallAttachPos);
|
||||
if (dist < Math.Max(Math.Max(character.AnimController.Collider.radius, character.AnimController.Collider.width), character.AnimController.Collider.height) * 1.2f)
|
||||
float squaredDistance = Vector2.DistanceSquared(character.SimPosition, wallAttachPos);
|
||||
float targetDistance = Math.Max(Math.Max(character.AnimController.Collider.radius, character.AnimController.Collider.width), character.AnimController.Collider.height) * 1.2f;
|
||||
if (squaredDistance < targetDistance * targetDistance)
|
||||
{
|
||||
//close enough to a wall -> attach
|
||||
AttachToBody(character.AnimController.Collider, attachLimb, attachTargetBody, wallAttachPos);
|
||||
@@ -197,12 +208,13 @@ namespace Barotrauma
|
||||
}
|
||||
break;
|
||||
case AIState.Attack:
|
||||
case AIState.Aggressive:
|
||||
if (enemyAI.AttackingLimb != null)
|
||||
{
|
||||
if (attachToSub && !enemyAI.IsSteeringThroughGap && wallAttachPos != Vector2.Zero && attachTargetBody != null)
|
||||
if (AttachToSub && !enemyAI.IsSteeringThroughGap && wallAttachPos != Vector2.Zero && attachTargetBody != null)
|
||||
{
|
||||
// is not attached or is attached to something else
|
||||
if (!IsAttached || IsAttached && attachJoints[0].BodyB == attachTargetBody)
|
||||
if (!IsAttached || IsAttached && attachJoints[0].BodyB != attachTargetBody)
|
||||
{
|
||||
if (Vector2.DistanceSquared(ConvertUnits.ToDisplayUnits(transformedAttachPos), enemyAI.AttackingLimb.WorldPosition) < enemyAI.AttackingLimb.attack.DamageRange * enemyAI.AttackingLimb.attack.DamageRange)
|
||||
{
|
||||
@@ -247,16 +259,17 @@ namespace Barotrauma
|
||||
if (attachJoints.Count > 0)
|
||||
{
|
||||
//already attached to the target body, no need to do anything
|
||||
if (attachJoints[0].BodyB == targetBody) return;
|
||||
if (attachJoints[0].BodyB == targetBody) { return; }
|
||||
DeattachFromBody();
|
||||
}
|
||||
|
||||
jointDir = attachLimb.Dir;
|
||||
|
||||
Vector2 transformedLocalAttachPos = localAttachPos * attachLimb.Scale * attachLimb.Params.Ragdoll.LimbScale;
|
||||
if (jointDir < 0.0f) transformedLocalAttachPos.X = -transformedLocalAttachPos.X;
|
||||
|
||||
//transformedLocalAttachPos = Vector2.Transform(transformedLocalAttachPos, Matrix.CreateRotationZ(attachLimb.Rotation));
|
||||
if (jointDir < 0.0f)
|
||||
{
|
||||
transformedLocalAttachPos.X = -transformedLocalAttachPos.X;
|
||||
}
|
||||
|
||||
float angle = MathUtils.VectorToAngle(-attachSurfaceNormal) - MathHelper.PiOver2 + attachLimbRotation * attachLimb.Dir;
|
||||
attachLimb.body.SetTransform(attachPos + attachSurfaceNormal * transformedLocalAttachPos.Length(), angle);
|
||||
@@ -274,7 +287,10 @@ namespace Barotrauma
|
||||
|
||||
// Limb scale is already taken into account when creating the collider.
|
||||
Vector2 colliderFront = collider.GetLocalFront();
|
||||
if (jointDir < 0.0f) colliderFront.X = -colliderFront.X;
|
||||
if (jointDir < 0.0f)
|
||||
{
|
||||
colliderFront.X = -colliderFront.X;
|
||||
}
|
||||
collider.SetTransform(attachPos + attachSurfaceNormal * colliderFront.Length(), MathUtils.VectorToAngle(-attachSurfaceNormal) - MathHelper.PiOver2);
|
||||
|
||||
var colliderJoint = new WeldJoint(collider.FarseerBody, targetBody, colliderFront, targetBody.GetLocalPoint(attachPos), false)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Barotrauma.IO;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
@@ -375,9 +375,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
StreamWriter file = new StreamWriter(@"NPCConversations.csv");
|
||||
file.WriteLine(sb.ToString());
|
||||
file.Close();
|
||||
File.WriteAllText("NPCConversations.csv", sb.ToString());
|
||||
}
|
||||
|
||||
private static void WriteConversation(System.Text.StringBuilder sb, NPCConversation conv, int depthIndex)
|
||||
|
||||
@@ -118,7 +118,6 @@ namespace Barotrauma
|
||||
public void TryComplete(float deltaTime)
|
||||
{
|
||||
if (isCompleted) { return; }
|
||||
//if (Abandon && !IsLoop && subObjectives.None()) { return; }
|
||||
if (CheckState()) { return; }
|
||||
// Not ready -> act (can't do foreach because it's possible that the collection is modified in event callbacks.
|
||||
for (int i = 0; i < subObjectives.Count; i++)
|
||||
@@ -201,7 +200,7 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
Priority = CumulatedDevotion * PriorityModifier;
|
||||
Priority = CumulatedDevotion;
|
||||
}
|
||||
return Priority;
|
||||
}
|
||||
@@ -211,7 +210,7 @@ namespace Barotrauma
|
||||
var currentObjective = objectiveManager.CurrentObjective;
|
||||
if (currentObjective != null && (currentObjective == this || currentObjective.subObjectives.Any(so => so == this)))
|
||||
{
|
||||
CumulatedDevotion += Devotion * PriorityModifier * deltaTime;
|
||||
CumulatedDevotion += Devotion * deltaTime;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
@@ -20,6 +20,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (battery == null) { return false; }
|
||||
var item = battery.Item;
|
||||
if (item.NonInteractable) { return false; }
|
||||
if (item.Submarine == null) { return false; }
|
||||
if (item.CurrentHull == null) { return false; }
|
||||
if (item.Submarine.TeamID != character.TeamID) { return false; }
|
||||
|
||||
+10
-9
@@ -121,6 +121,12 @@ namespace Barotrauma
|
||||
|
||||
protected override bool Check()
|
||||
{
|
||||
if (initialMode == CombatMode.Offensive && Mode != CombatMode.Offensive)
|
||||
{
|
||||
Abandon = true;
|
||||
SteeringManager.Reset();
|
||||
return false;
|
||||
}
|
||||
bool completed = (Enemy != null && (Enemy.Removed || Enemy.IsDead)) || (initialMode != CombatMode.Offensive && coolDownTimer <= 0);
|
||||
if (completed)
|
||||
{
|
||||
@@ -465,7 +471,6 @@ namespace Barotrauma
|
||||
SteeringManager.Reset();
|
||||
return;
|
||||
}
|
||||
|
||||
retreatTarget = null;
|
||||
RemoveSubObjective(ref retreatObjective);
|
||||
RemoveSubObjective(ref seekAmmunition);
|
||||
@@ -482,9 +487,8 @@ namespace Barotrauma
|
||||
},
|
||||
onAbandon: () =>
|
||||
{
|
||||
Mode = CombatMode.Defensive;
|
||||
Abandon = true;
|
||||
SteeringManager.Reset();
|
||||
RemoveSubObjective(ref followTargetObjective);
|
||||
});
|
||||
if (followTargetObjective != null)
|
||||
{
|
||||
@@ -593,10 +597,7 @@ namespace Barotrauma
|
||||
|
||||
private void Attack(float deltaTime)
|
||||
{
|
||||
float squaredDistance = Vector2.DistanceSquared(character.Position, Enemy.Position);
|
||||
character.CursorPosition = Enemy.Position;
|
||||
float engageDistance = 500;
|
||||
if (character.CurrentHull != Enemy.CurrentHull && squaredDistance > engageDistance * engageDistance) { return; }
|
||||
if (!character.CanSeeCharacter(Enemy)) { return; }
|
||||
if (Weapon.RequireAimToUse)
|
||||
{
|
||||
@@ -604,7 +605,7 @@ namespace Barotrauma
|
||||
if (SteeringManager == PathSteering)
|
||||
{
|
||||
var door = PathSteering.CurrentPath?.CurrentNode?.ConnectedDoor;
|
||||
if (door != null && !door.IsOpen)
|
||||
if (door != null && !door.IsOpen && !door.IsBroken)
|
||||
{
|
||||
isOperatingButtons = door.HasIntegratedButtons || door.Item.GetConnectedComponents<Controller>(true).Any();
|
||||
}
|
||||
@@ -626,7 +627,7 @@ namespace Barotrauma
|
||||
}
|
||||
if (WeaponComponent is MeleeWeapon meleeWeapon)
|
||||
{
|
||||
if (squaredDistance <= meleeWeapon.Range * meleeWeapon.Range)
|
||||
if (Vector2.DistanceSquared(character.Position, Enemy.Position) <= meleeWeapon.Range * meleeWeapon.Range)
|
||||
{
|
||||
character.SetInput(InputType.Shoot, false, true);
|
||||
Weapon.Use(deltaTime, character);
|
||||
@@ -636,7 +637,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (WeaponComponent is RepairTool repairTool)
|
||||
{
|
||||
if (squaredDistance > repairTool.Range * repairTool.Range) { return; }
|
||||
if (Vector2.DistanceSquared(character.Position, Enemy.Position) > repairTool.Range * repairTool.Range) { return; }
|
||||
}
|
||||
if (VectorExtensions.Angle(VectorExtensions.Forward(Weapon.body.TransformedRotation), Enemy.Position - Weapon.Position) < MathHelper.PiOver4)
|
||||
{
|
||||
|
||||
+1
-1
@@ -158,7 +158,7 @@ namespace Barotrauma
|
||||
Abandon = true;
|
||||
}, onCompleted: () =>
|
||||
{
|
||||
if (getItemObjective.TargetItem != null)
|
||||
if (getItemObjective?.TargetItem != null)
|
||||
{
|
||||
containedItems.Add(getItemObjective.TargetItem);
|
||||
}
|
||||
|
||||
+1
-1
@@ -106,7 +106,7 @@ namespace Barotrauma
|
||||
if (SteeringManager == PathSteering)
|
||||
{
|
||||
var door = PathSteering.CurrentPath?.CurrentNode?.ConnectedDoor;
|
||||
if (door != null && !door.IsOpen)
|
||||
if (door != null && !door.IsOpen && !door.IsBroken)
|
||||
{
|
||||
isOperatingButtons = door.HasIntegratedButtons || door.Item.GetConnectedComponents<Controller>(true).Any();
|
||||
}
|
||||
|
||||
+1
-6
@@ -1,9 +1,4 @@
|
||||
using Barotrauma.Items.Components;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Barotrauma.Extensions;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
|
||||
+32
-26
@@ -43,7 +43,7 @@ namespace Barotrauma
|
||||
}
|
||||
if (character.CurrentHull == null)
|
||||
{
|
||||
Priority = objectiveManager.CurrentOrder is AIObjectiveGoTo ? 0 : 100;
|
||||
Priority = objectiveManager.CurrentOrder is AIObjectiveGoTo && HumanAIController.HasDivingSuit(character) ? 0 : 100;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -83,8 +83,9 @@ namespace Barotrauma
|
||||
else
|
||||
{
|
||||
float dangerFactor = (100 - currenthullSafety) / 100;
|
||||
Priority = Math.Min(Priority + dangerFactor * priorityIncrease * deltaTime, 100);
|
||||
Priority += dangerFactor * priorityIncrease * deltaTime;
|
||||
}
|
||||
Priority = MathHelper.Clamp(Priority, 0, 100);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,34 +94,39 @@ namespace Barotrauma
|
||||
protected override void Act(float deltaTime)
|
||||
{
|
||||
var currentHull = character.CurrentHull;
|
||||
bool needsDivingGear = HumanAIController.NeedsDivingGear(character, currentHull, out bool needsDivingSuit);
|
||||
bool needsEquipment = false;
|
||||
if (needsDivingSuit)
|
||||
bool dangerousPressure = currentHull == null || currentHull.LethalPressure > 0;
|
||||
if (!dangerousPressure)
|
||||
{
|
||||
needsEquipment = !HumanAIController.HasDivingSuit(character, AIObjectiveFindDivingGear.lowOxygenThreshold);
|
||||
}
|
||||
else if (needsDivingGear)
|
||||
{
|
||||
needsEquipment = !HumanAIController.HasDivingGear(character, AIObjectiveFindDivingGear.lowOxygenThreshold);
|
||||
}
|
||||
if (needsEquipment && divingGearObjective == null && !character.LockHands)
|
||||
{
|
||||
RemoveSubObjective(ref goToObjective);
|
||||
TryAddSubObjective(ref divingGearObjective,
|
||||
constructor: () => new AIObjectiveFindDivingGear(character, needsDivingSuit, objectiveManager),
|
||||
onAbandon: () =>
|
||||
{
|
||||
searchHullTimer = Math.Min(1, searchHullTimer);
|
||||
// Don't try to seek diving gear if the pressure is dangerous. Just get out.
|
||||
bool needsDivingGear = HumanAIController.NeedsDivingGear(character, currentHull, out bool needsDivingSuit);
|
||||
bool needsEquipment = false;
|
||||
if (needsDivingSuit)
|
||||
{
|
||||
needsEquipment = !HumanAIController.HasDivingSuit(character, AIObjectiveFindDivingGear.lowOxygenThreshold);
|
||||
}
|
||||
else if (needsDivingGear)
|
||||
{
|
||||
needsEquipment = !HumanAIController.HasDivingGear(character, AIObjectiveFindDivingGear.lowOxygenThreshold);
|
||||
}
|
||||
if (needsEquipment && divingGearObjective == null && !character.LockHands)
|
||||
{
|
||||
RemoveSubObjective(ref goToObjective);
|
||||
TryAddSubObjective(ref divingGearObjective,
|
||||
constructor: () => new AIObjectiveFindDivingGear(character, needsDivingSuit, objectiveManager),
|
||||
onAbandon: () =>
|
||||
{
|
||||
searchHullTimer = Math.Min(1, searchHullTimer);
|
||||
// Don't reset the diving gear objective, because it's possible that there is no diving gear -> seek a safe hull and then reset so that we can check again.
|
||||
},
|
||||
onCompleted: () =>
|
||||
{
|
||||
resetPriority = true;
|
||||
searchHullTimer = Math.Min(1, searchHullTimer);
|
||||
RemoveSubObjective(ref divingGearObjective);
|
||||
});
|
||||
onCompleted: () =>
|
||||
{
|
||||
resetPriority = true;
|
||||
searchHullTimer = Math.Min(1, searchHullTimer);
|
||||
RemoveSubObjective(ref divingGearObjective);
|
||||
});
|
||||
}
|
||||
}
|
||||
else if (divingGearObjective == null || !divingGearObjective.CanBeCompleted)
|
||||
if (divingGearObjective == null || !divingGearObjective.CanBeCompleted)
|
||||
{
|
||||
if (currenthullSafety < HumanAIController.HULL_SAFETY_THRESHOLD)
|
||||
{
|
||||
|
||||
+1
-1
@@ -37,7 +37,7 @@ namespace Barotrauma
|
||||
|
||||
protected override float TargetEvaluation()
|
||||
{
|
||||
int otherFixers = HumanAIController.CountCrew(c => c != HumanAIController && c.ObjectiveManager.IsCurrentObjective<AIObjectiveFixLeaks>(), onlyBots: true);
|
||||
int otherFixers = HumanAIController.CountCrew(c => c != HumanAIController && c.ObjectiveManager.IsCurrentObjective<AIObjectiveFixLeaks>() && !c.Character.IsIncapacitated, onlyBots: true);
|
||||
int totalLeaks = Targets.Count();
|
||||
if (totalLeaks == 0) { return 0; }
|
||||
int secondaryLeaks = Targets.Count(l => l.IsRoomToRoom);
|
||||
|
||||
+49
-18
@@ -3,6 +3,7 @@ using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Barotrauma.Extensions;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -20,7 +21,9 @@ namespace Barotrauma
|
||||
//can be either tags or identifiers
|
||||
private string[] itemIdentifiers;
|
||||
public IEnumerable<string> Identifiers => itemIdentifiers;
|
||||
private Item targetItem, moveToTarget, rootContainer;
|
||||
|
||||
private Item targetItem;
|
||||
private ISpatialEntity moveToTarget;
|
||||
private bool isDoneSeeking;
|
||||
public Item TargetItem => targetItem;
|
||||
private int currSearchIndex;
|
||||
@@ -29,6 +32,8 @@ namespace Barotrauma
|
||||
private float currItemPriority;
|
||||
private bool checkInventory;
|
||||
|
||||
public static float DefaultReach = 100;
|
||||
|
||||
public bool AllowToFindDivingGear { get; set; } = true;
|
||||
|
||||
public AIObjectiveGetItem(Character character, Item targetItem, AIObjectiveManager objectiveManager, bool equip = true, float priorityModifier = 1)
|
||||
@@ -37,6 +42,7 @@ namespace Barotrauma
|
||||
currSearchIndex = -1;
|
||||
this.equip = equip;
|
||||
this.targetItem = targetItem;
|
||||
moveToTarget = targetItem?.GetRootInventoryOwner();
|
||||
}
|
||||
|
||||
public AIObjectiveGetItem(Character character, string itemIdentifier, AIObjectiveManager objectiveManager, bool equip = true, bool checkInventory = true, float priorityModifier = 1)
|
||||
@@ -62,8 +68,7 @@ namespace Barotrauma
|
||||
if (item != null)
|
||||
{
|
||||
targetItem = item;
|
||||
rootContainer = item.GetRootContainer();
|
||||
moveToTarget = rootContainer ?? item;
|
||||
moveToTarget = item.GetRootInventoryOwner();
|
||||
}
|
||||
return item != null;
|
||||
}
|
||||
@@ -86,6 +91,15 @@ namespace Barotrauma
|
||||
}
|
||||
if (!isDoneSeeking)
|
||||
{
|
||||
bool dangerousPressure = character.CurrentHull == null || character.CurrentHull.LethalPressure > 0;
|
||||
if (dangerousPressure)
|
||||
{
|
||||
#if DEBUG
|
||||
DebugConsole.NewMessage($"{character.Name}: Seeking item aborted, because the pressure is dangerous.", Color.Yellow);
|
||||
#endif
|
||||
Abandon = true;
|
||||
return;
|
||||
}
|
||||
FindTargetItem();
|
||||
objectiveManager.GetObjective<AIObjectiveIdle>().Wander(deltaTime);
|
||||
return;
|
||||
@@ -108,7 +122,26 @@ namespace Barotrauma
|
||||
Reset();
|
||||
return;
|
||||
}
|
||||
if (character.CanInteractWith(targetItem, out _, checkLinked: false))
|
||||
bool canInteract = false;
|
||||
if (moveToTarget is Character c)
|
||||
{
|
||||
if (character == c)
|
||||
{
|
||||
canInteract = true;
|
||||
moveToTarget = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
character.SelectCharacter(c);
|
||||
canInteract = character.CanInteractWith(c, maxDist: DefaultReach);
|
||||
character.DeselectCharacter();
|
||||
}
|
||||
}
|
||||
else if (moveToTarget is Item parentItem)
|
||||
{
|
||||
canInteract = character.CanInteractWith(parentItem, out _, checkLinked: false);
|
||||
}
|
||||
if (canInteract)
|
||||
{
|
||||
var pickable = targetItem.GetComponent<Pickable>();
|
||||
if (pickable == null)
|
||||
@@ -173,17 +206,17 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
else if (moveToTarget != null)
|
||||
{
|
||||
TryAddSubObjective(ref goToObjective,
|
||||
constructor: () =>
|
||||
{
|
||||
return new AIObjectiveGoTo(moveToTarget, character, objectiveManager, repeat: false, getDivingGearIfNeeded: AllowToFindDivingGear)
|
||||
return new AIObjectiveGoTo(moveToTarget, character, objectiveManager, repeat: false, getDivingGearIfNeeded: AllowToFindDivingGear, closeEnough: DefaultReach)
|
||||
{
|
||||
// If the root container changes, the item is no longer where it was (taken by someone -> need to find another item)
|
||||
abortCondition = () => targetItem == null || targetItem.GetRootContainer() != rootContainer,
|
||||
abortCondition = () => targetItem == null || targetItem.GetRootInventoryOwner() != moveToTarget,
|
||||
DialogueIdentifier = "dialogcannotreachtarget",
|
||||
TargetName = moveToTarget.Name
|
||||
TargetName = (moveToTarget as MapEntity)?.Name ?? (moveToTarget as Character)?.Name ?? moveToTarget.ToString()
|
||||
};
|
||||
},
|
||||
onAbandon: () =>
|
||||
@@ -212,9 +245,9 @@ namespace Barotrauma
|
||||
{
|
||||
currSearchIndex++;
|
||||
var item = Item.ItemList[currSearchIndex];
|
||||
if (item.Submarine == null) { continue; }
|
||||
if (item.CurrentHull == null) { continue; }
|
||||
if (item.Submarine.TeamID != character.TeamID) { continue; }
|
||||
Submarine itemSub = item.Submarine ?? item.ParentInventory?.Owner?.Submarine;
|
||||
if (itemSub == null) { continue; }
|
||||
if (itemSub.TeamID != character.TeamID) { continue; }
|
||||
if (!CheckItem(item)) { continue; }
|
||||
if (ignoredContainerIdentifiers != null && item.Container != null)
|
||||
{
|
||||
@@ -222,8 +255,8 @@ namespace Barotrauma
|
||||
}
|
||||
if (character.Submarine != null)
|
||||
{
|
||||
if (item.Submarine.Info.Type != character.Submarine.Info.Type) { continue; }
|
||||
if (character.Submarine != null && !character.Submarine.IsEntityFoundOnThisSub(item, true)) { continue; }
|
||||
if (itemSub.Info.Type != character.Submarine.Info.Type) { continue; }
|
||||
if (character.Submarine.GetConnectedSubs().None(s => s == itemSub && itemSub.TeamID == character.TeamID && itemSub.Info.Type == character.Submarine.Info.Type)) { continue; }
|
||||
}
|
||||
if (character.IsItemTakenBySomeoneElse(item)) { continue; }
|
||||
float itemPriority = 1;
|
||||
@@ -231,8 +264,8 @@ namespace Barotrauma
|
||||
{
|
||||
itemPriority = GetItemPriority(item);
|
||||
}
|
||||
Item rootContainer = item.GetRootContainer();
|
||||
Vector2 itemPos = (rootContainer ?? item).WorldPosition;
|
||||
Entity rootInventoryOwner = item.GetRootInventoryOwner();
|
||||
Vector2 itemPos = (rootInventoryOwner ?? item).WorldPosition;
|
||||
float yDist = Math.Abs(character.WorldPosition.Y - itemPos.Y);
|
||||
yDist = yDist > 100 ? yDist * 5 : 0;
|
||||
float dist = Math.Abs(character.WorldPosition.X - itemPos.X) + yDist;
|
||||
@@ -243,8 +276,7 @@ namespace Barotrauma
|
||||
if (itemPriority < currItemPriority) { continue; }
|
||||
currItemPriority = itemPriority;
|
||||
targetItem = item;
|
||||
moveToTarget = rootContainer ?? item;
|
||||
this.rootContainer = rootContainer;
|
||||
moveToTarget = rootInventoryOwner ?? item;
|
||||
}
|
||||
if (currSearchIndex >= Item.ItemList.Count - 1)
|
||||
{
|
||||
@@ -293,7 +325,6 @@ namespace Barotrauma
|
||||
RemoveSubObjective(ref goToObjective);
|
||||
targetItem = null;
|
||||
moveToTarget = null;
|
||||
rootContainer = null;
|
||||
isDoneSeeking = false;
|
||||
currSearchIndex = 0;
|
||||
}
|
||||
|
||||
+37
-15
@@ -80,21 +80,28 @@ namespace Barotrauma
|
||||
this.repeat = repeat;
|
||||
waitUntilPathUnreachable = 3.0f;
|
||||
this.getDivingGearIfNeeded = getDivingGearIfNeeded;
|
||||
CloseEnough = closeEnough;
|
||||
if (Target is Item i)
|
||||
{
|
||||
CloseEnough = Math.Max(CloseEnough, i.InteractDistance + Math.Max(i.Rect.Width, i.Rect.Height) / 2);
|
||||
}
|
||||
else if (Target is Character)
|
||||
{
|
||||
CloseEnough = Math.Max(closeEnough, AIObjectiveGetItem.DefaultReach);
|
||||
}
|
||||
else
|
||||
{
|
||||
CloseEnough = closeEnough;
|
||||
}
|
||||
}
|
||||
|
||||
private void SpeakCannotReach()
|
||||
{
|
||||
#if DEBUG
|
||||
DebugConsole.NewMessage($"{character.Name}: Cannot reach the target: {Target.ToString()}", Color.Yellow);
|
||||
DebugConsole.NewMessage($"{character.Name}: Cannot reach the target: {Target}", Color.Yellow);
|
||||
#endif
|
||||
if (objectiveManager.CurrentOrder != null && DialogueIdentifier != null)
|
||||
{
|
||||
string msg = TargetName == null ? TextManager.Get(DialogueIdentifier, true) : TextManager.GetWithVariable(DialogueIdentifier, "[name]", TargetName, true);
|
||||
string msg = TargetName == null ? TextManager.Get(DialogueIdentifier, true) : TextManager.GetWithVariable(DialogueIdentifier, "[name]", TargetName, formatCapitals: !(Target is Character));
|
||||
if (msg != null)
|
||||
{
|
||||
character.Speak(msg, identifier: DialogueIdentifier, minDurationBetweenSimilar: 20.0f);
|
||||
@@ -213,10 +220,19 @@ namespace Barotrauma
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (repeat && IsCloseEnough)
|
||||
if (repeat)
|
||||
{
|
||||
OnCompleted();
|
||||
return;
|
||||
if (IsCloseEnough)
|
||||
{
|
||||
if (requiredCondition == null || requiredCondition())
|
||||
{
|
||||
if (character.CanSeeTarget(Target))
|
||||
{
|
||||
OnCompleted();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (SteeringManager == PathSteering)
|
||||
{
|
||||
@@ -244,7 +260,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private Hull GetTargetHull()
|
||||
public Hull GetTargetHull()
|
||||
{
|
||||
if (Target is Hull h)
|
||||
{
|
||||
@@ -284,13 +300,7 @@ namespace Barotrauma
|
||||
//otherwise characters can let go of the ladders too soon once they're close enough to the target
|
||||
if (PathSteering.CurrentPath.NextNode != null) { return false; }
|
||||
}
|
||||
|
||||
bool closeEnough = Vector2.DistanceSquared(Target.WorldPosition, character.WorldPosition) < CloseEnough * CloseEnough;
|
||||
if (closeEnough)
|
||||
{
|
||||
closeEnough = !(Target is Character) || Target is Character c && c.CurrentHull == character.CurrentHull;
|
||||
}
|
||||
return closeEnough;
|
||||
return Vector2.DistanceSquared(Target.WorldPosition, character.WorldPosition) < CloseEnough * CloseEnough;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -326,7 +336,9 @@ namespace Barotrauma
|
||||
}
|
||||
else if (Target is Character targetCharacter)
|
||||
{
|
||||
if (character.CanInteractWith(targetCharacter, CloseEnough)) { IsCompleted = true; }
|
||||
character.SelectCharacter(targetCharacter);
|
||||
if (character.CanInteractWith(targetCharacter, skipDistanceCheck: true)) { IsCompleted = true; }
|
||||
character.DeselectCharacter();
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -338,6 +350,16 @@ namespace Barotrauma
|
||||
return IsCompleted;
|
||||
}
|
||||
|
||||
protected override void OnAbandon()
|
||||
{
|
||||
StopMovement();
|
||||
if (SteeringManager == PathSteering)
|
||||
{
|
||||
PathSteering.ResetPath();
|
||||
}
|
||||
base.OnAbandon();
|
||||
}
|
||||
|
||||
private void StopMovement()
|
||||
{
|
||||
character.AIController.SteeringManager.Reset();
|
||||
|
||||
+21
-20
@@ -3,7 +3,6 @@ using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Barotrauma.Extensions;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -35,41 +34,43 @@ namespace Barotrauma
|
||||
{
|
||||
standStillTimer = Rand.Range(-10.0f, 10.0f);
|
||||
walkDuration = Rand.Range(0.0f, 10.0f);
|
||||
CalculatePriority();
|
||||
}
|
||||
|
||||
protected override bool Check() => false;
|
||||
public override bool CanBeCompleted => true;
|
||||
|
||||
public override bool IsLoop { get => true; set => throw new System.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: " + Environment.StackTrace); }
|
||||
|
||||
private float randomTimer;
|
||||
private float randomUpdateInterval = 5;
|
||||
public float Random { get; private set; }
|
||||
|
||||
public void CalculatePriority()
|
||||
public void CalculatePriority(float max = 0)
|
||||
{
|
||||
Random = Rand.Range(0.5f, 1.5f);
|
||||
randomTimer = randomUpdateInterval;
|
||||
float max = Math.Min(Math.Min(AIObjectiveManager.RunPriority, AIObjectiveManager.OrderPriority) - 1, 100);
|
||||
float initiative = character.GetSkillLevel("initiative");
|
||||
Priority = MathHelper.Lerp(1, max, MathUtils.InverseLerp(100, 0, initiative * Random));
|
||||
//Random = Rand.Range(0.5f, 1.5f);
|
||||
//randomTimer = randomUpdateInterval;
|
||||
//max = max > 0 ? max : Math.Min(Math.Min(AIObjectiveManager.RunPriority, AIObjectiveManager.OrderPriority) - 1, 100);
|
||||
//float initiative = character.GetSkillLevel("initiative");
|
||||
//Priority = MathHelper.Lerp(1, max, MathUtils.InverseLerp(100, 0, initiative * Random));
|
||||
Priority = 1;
|
||||
}
|
||||
|
||||
public override float GetPriority() => Priority;
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
if (objectiveManager.CurrentObjective == this)
|
||||
{
|
||||
if (randomTimer > 0)
|
||||
{
|
||||
randomTimer -= deltaTime;
|
||||
}
|
||||
else
|
||||
{
|
||||
CalculatePriority();
|
||||
}
|
||||
}
|
||||
//if (objectiveManager.CurrentObjective == this)
|
||||
//{
|
||||
// if (randomTimer > 0)
|
||||
// {
|
||||
// randomTimer -= deltaTime;
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// CalculatePriority();
|
||||
// }
|
||||
//}
|
||||
}
|
||||
|
||||
protected override void Act(float deltaTime)
|
||||
@@ -129,7 +130,7 @@ namespace Barotrauma
|
||||
//choose a random available hull
|
||||
currentTarget = ToolBox.SelectWeightedRandom(targetHulls, hullWeights, Rand.RandSync.Unsynced);
|
||||
bool isCurrentHullAllowed = !IsForbidden(character.CurrentHull);
|
||||
var path = PathSteering.PathFinder.FindPath(character.SimPosition, currentTarget.SimPosition, nodeFilter: node =>
|
||||
var path = PathSteering.PathFinder.FindPath(character.SimPosition, currentTarget.SimPosition, errorMsgStr: $"AIObjectiveIdle {character.DisplayName}", nodeFilter: node =>
|
||||
{
|
||||
if (node.Waypoint.CurrentHull == null) { return false; }
|
||||
// Check that there is no unsafe or forbidden hulls on the way to the target
|
||||
|
||||
+2
-2
@@ -47,7 +47,7 @@ namespace Barotrauma
|
||||
public override bool AllowSubObjectiveSorting => true;
|
||||
public virtual bool InverseTargetEvaluation => false;
|
||||
|
||||
public override bool IsLoop { get => true; set => throw new System.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); }
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
@@ -204,7 +204,7 @@ namespace Barotrauma
|
||||
{
|
||||
Objectives.Remove(target);
|
||||
ignoreList.Add(target);
|
||||
targetUpdateTimer = 0;
|
||||
targetUpdateTimer = Math.Min(0.1f, targetUpdateTimer);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
+71
-16
@@ -14,8 +14,11 @@ namespace Barotrauma
|
||||
public const float OrderPriority = 70;
|
||||
public const float RunPriority = 50;
|
||||
// Constantly increases the priority of the selected objective, unless overridden
|
||||
public const float baseDevotion = 3;
|
||||
public const float baseDevotion = 5;
|
||||
|
||||
/// <summary>
|
||||
/// Excluding the current order.
|
||||
/// </summary>
|
||||
public List<AIObjective> Objectives { get; private set; } = new List<AIObjective>();
|
||||
|
||||
private readonly Character character;
|
||||
@@ -88,8 +91,25 @@ namespace Barotrauma
|
||||
|
||||
public Dictionary<AIObjective, CoroutineHandle> DelayedObjectives { get; private set; } = new Dictionary<AIObjective, CoroutineHandle>();
|
||||
|
||||
private void ClearIgnored()
|
||||
{
|
||||
if (character.AIController is HumanAIController humanAi)
|
||||
{
|
||||
humanAi.UnreachableHulls.Clear();
|
||||
humanAi.IgnoredItems.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
public void CreateAutonomousObjectives()
|
||||
{
|
||||
if (character.IsDead)
|
||||
{
|
||||
#if DEBUG
|
||||
DebugConsole.ThrowError("Attempted to create autonomous orders for a dead character");
|
||||
#else
|
||||
return;
|
||||
#endif
|
||||
}
|
||||
foreach (var delayedObjective in DelayedObjectives)
|
||||
{
|
||||
CoroutineManager.StopCoroutines(delayedObjective.Value);
|
||||
@@ -99,15 +119,15 @@ namespace Barotrauma
|
||||
AddObjective(new AIObjectiveFindSafety(character, this));
|
||||
AddObjective(new AIObjectiveIdle(character, this));
|
||||
int objectiveCount = Objectives.Count;
|
||||
foreach (var automaticOrder in character.Info.Job.Prefab.AutomaticOrders)
|
||||
foreach (var autonomousObjective in character.Info.Job.Prefab.AutonomousObjective)
|
||||
{
|
||||
var orderPrefab = Order.GetPrefab(automaticOrder.identifier);
|
||||
if (orderPrefab == null) { throw new Exception($"Could not find a matching prefab by the identifier: '{automaticOrder.identifier}'"); }
|
||||
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, false)?.GetRandom() : null;
|
||||
var order = new Order(orderPrefab, item ?? character.CurrentHull as Entity,
|
||||
item?.Components.FirstOrDefault(ic => ic.GetType() == orderPrefab.ItemComponentType), orderGiver: character);
|
||||
if (order == null) { continue; }
|
||||
var objective = CreateObjective(order, automaticOrder.option, character, automaticOrder.priorityModifier);
|
||||
var objective = CreateObjective(order, autonomousObjective.option, character, isAutonomous: true, autonomousObjective.priorityModifier);
|
||||
if (objective != null && objective.CanBeCompleted)
|
||||
{
|
||||
AddObjective(objective, delay: Rand.Value() / 2);
|
||||
@@ -160,7 +180,7 @@ namespace Barotrauma
|
||||
{
|
||||
previousObjective?.OnDeselected();
|
||||
CurrentObjective?.OnSelected();
|
||||
GetObjective<AIObjectiveIdle>().CalculatePriority();
|
||||
GetObjective<AIObjectiveIdle>().CalculatePriority(Math.Max(CurrentObjective.Priority - 10, 0));
|
||||
}
|
||||
return CurrentObjective;
|
||||
}
|
||||
@@ -172,7 +192,21 @@ namespace Barotrauma
|
||||
|
||||
public void UpdateObjectives(float deltaTime)
|
||||
{
|
||||
CurrentOrder?.Update(deltaTime);
|
||||
if (CurrentOrder != null)
|
||||
{
|
||||
#if DEBUG
|
||||
// Note: don't automatically remove orders here. Removing orders needs to be done via dismissing.
|
||||
if (CurrentOrder.IsCompleted)
|
||||
{
|
||||
DebugConsole.NewMessage($"{character.Name}: ORDER {CurrentOrder.DebugTag} IS COMPLETED. CURRENTLY ALL ORDERS SHOULD BE LOOPING.", Color.Red);
|
||||
}
|
||||
else if (!CurrentOrder.CanBeCompleted)
|
||||
{
|
||||
DebugConsole.NewMessage($"{character.Name}: ORDER {CurrentOrder.DebugTag}, CANNOT BE COMPLETED.", Color.Red);
|
||||
}
|
||||
#endif
|
||||
CurrentOrder.Update(deltaTime);
|
||||
}
|
||||
if (WaitTimer > 0)
|
||||
{
|
||||
WaitTimer -= deltaTime;
|
||||
@@ -195,7 +229,7 @@ namespace Barotrauma
|
||||
#endif
|
||||
Objectives.Remove(objective);
|
||||
}
|
||||
else if (objective != CurrentOrder)
|
||||
else
|
||||
{
|
||||
objective.Update(deltaTime);
|
||||
}
|
||||
@@ -233,7 +267,16 @@ namespace Barotrauma
|
||||
|
||||
public void SetOrder(Order order, string option, Character orderGiver)
|
||||
{
|
||||
CurrentOrder = CreateObjective(order, option, orderGiver);
|
||||
if (character.IsDead)
|
||||
{
|
||||
#if DEBUG
|
||||
DebugConsole.ThrowError("Attempted to set an order for a dead character");
|
||||
#else
|
||||
return;
|
||||
#endif
|
||||
}
|
||||
ClearIgnored();
|
||||
CurrentOrder = CreateObjective(order, option, orderGiver, isAutonomous: false);
|
||||
if (CurrentOrder == null)
|
||||
{
|
||||
// Recreate objectives, because some of them may be removed, if impossible to complete (e.g. due to path finding)
|
||||
@@ -245,7 +288,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public AIObjective CreateObjective(Order order, string option, Character orderGiver, float priorityModifier = 1)
|
||||
public AIObjective CreateObjective(Order order, string option, Character orderGiver, bool isAutonomous, float priorityModifier = 1)
|
||||
{
|
||||
if (order == null) { return null; }
|
||||
AIObjective newObjective;
|
||||
@@ -284,14 +327,21 @@ namespace Barotrauma
|
||||
newObjective = new AIObjectiveRepairItems(character, this, priorityModifier: priorityModifier, prioritizedItem: order.TargetEntity as Item)
|
||||
{
|
||||
RelevantSkill = order.AppropriateSkill,
|
||||
RequireAdequateSkills = option == "jobspecific"
|
||||
RequireAdequateSkills = isAutonomous
|
||||
};
|
||||
break;
|
||||
case "pumpwater":
|
||||
if (order.TargetItemComponent is Pump targetPump)
|
||||
{
|
||||
newObjective = new AIObjectiveOperateItem(targetPump, character, this, option, false, priorityModifier: priorityModifier);
|
||||
// newObjective.Completed += DismissSelf;
|
||||
if (order.TargetItemComponent.Item.NonInteractable) { return null; }
|
||||
newObjective = new AIObjectiveOperateItem(targetPump, character, this, option, false, priorityModifier: priorityModifier)
|
||||
{
|
||||
IsLoop = true,
|
||||
Override = orderGiver != null && orderGiver.IsPlayer
|
||||
};
|
||||
// ItemComponent.AIOperate() returns false by default -> We'd have to set IsLoop = false and implement a custom override of AIOperate for the Pump.cs,
|
||||
// if we want that the bot just switches the pump on/off and continues doing something else.
|
||||
// If we want that the bot does the objective and then forgets about it, I think we could do the same plus dismiss when the bot is done.
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -306,9 +356,11 @@ namespace Barotrauma
|
||||
break;
|
||||
case "steer":
|
||||
var steering = (order?.TargetEntity as Item)?.GetComponent<Steering>();
|
||||
if (steering != null) steering.PosToMaintain = steering.Item.Submarine?.WorldPosition;
|
||||
if (steering != null) { steering.PosToMaintain = steering.Item.Submarine?.WorldPosition; }
|
||||
if (order.TargetItemComponent == null) { return null; }
|
||||
newObjective = new AIObjectiveOperateItem(order.TargetItemComponent, character, this, option, requireEquip: false, useController: order.UseController, priorityModifier: priorityModifier)
|
||||
if (order.TargetItemComponent.Item.NonInteractable) { return null; }
|
||||
newObjective = new AIObjectiveOperateItem(order.TargetItemComponent, character, this, option,
|
||||
requireEquip: false, useController: order.UseController, controller: order.ConnectedController, priorityModifier: priorityModifier)
|
||||
{
|
||||
IsLoop = true,
|
||||
// Don't override unless it's an order by a player
|
||||
@@ -317,12 +369,15 @@ namespace Barotrauma
|
||||
break;
|
||||
default:
|
||||
if (order.TargetItemComponent == null) { return null; }
|
||||
newObjective = new AIObjectiveOperateItem(order.TargetItemComponent, character, this, option, requireEquip: false, useController: order.UseController, priorityModifier: priorityModifier)
|
||||
if (order.TargetItemComponent.Item.NonInteractable) { return null; }
|
||||
newObjective = new AIObjectiveOperateItem(order.TargetItemComponent, character, this, option,
|
||||
requireEquip: false, useController: order.UseController, controller: order.ConnectedController, priorityModifier: priorityModifier)
|
||||
{
|
||||
IsLoop = true,
|
||||
// Don't override unless it's an order by a player
|
||||
Override = orderGiver != null && orderGiver.IsPlayer
|
||||
};
|
||||
if (newObjective.Abandon) { return null; }
|
||||
break;
|
||||
}
|
||||
return newObjective;
|
||||
|
||||
+47
-16
@@ -3,7 +3,6 @@ using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Barotrauma.Extensions;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -29,6 +28,7 @@ namespace Barotrauma
|
||||
public ItemComponent GetTarget() => useController ? controller : component;
|
||||
|
||||
public Func<bool> completionCondition;
|
||||
private bool isDoneOperating;
|
||||
|
||||
public override float GetPriority()
|
||||
{
|
||||
@@ -47,17 +47,41 @@ namespace Barotrauma
|
||||
{
|
||||
Priority = AIObjectiveManager.OrderPriority;
|
||||
}
|
||||
Item targetItem = GetTarget()?.Item;
|
||||
ItemComponent target = GetTarget();
|
||||
Item targetItem = target?.Item;
|
||||
if (targetItem == null)
|
||||
{
|
||||
#if DEBUG
|
||||
DebugConsole.ThrowError("Item or component of AI Objective Operate item wass null. This shouldn't happen.");
|
||||
DebugConsole.ThrowError("Item or component of AI Objective Operate item was null. This shouldn't happen.");
|
||||
#endif
|
||||
Abandon = true;
|
||||
Priority = 0;
|
||||
return 0.0f;
|
||||
return Priority;
|
||||
}
|
||||
if (targetItem.CurrentHull == null || targetItem.CurrentHull.FireSources.Any() || HumanAIController.IsItemOperatedByAnother(GetTarget(), out _))
|
||||
var reactor = component?.Item.GetComponent<Reactor>();
|
||||
if (reactor != null)
|
||||
{
|
||||
switch (Option)
|
||||
{
|
||||
case "shutdown":
|
||||
if (!reactor.PowerOn)
|
||||
{
|
||||
Priority = 0;
|
||||
return Priority;
|
||||
}
|
||||
break;
|
||||
case "powerup":
|
||||
// Check that we don't already have another order that is targeting the same item.
|
||||
// Without this the autonomous objective will tell the bot to turn the reactor on again.
|
||||
if (objectiveManager.CurrentOrder is AIObjectiveOperateItem operateOrder && operateOrder != this && operateOrder.GetTarget() == target)
|
||||
{
|
||||
Priority = 0;
|
||||
return Priority;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (targetItem.CurrentHull == null || targetItem.CurrentHull.FireSources.Any() || HumanAIController.IsItemOperatedByAnother(target, out _))
|
||||
{
|
||||
Priority = 0;
|
||||
}
|
||||
@@ -68,26 +92,33 @@ namespace Barotrauma
|
||||
else
|
||||
{
|
||||
float value = CumulatedDevotion + (AIObjectiveManager.OrderPriority * PriorityModifier);
|
||||
float max = MathHelper.Min((AIObjectiveManager.OrderPriority - 1), 90);
|
||||
float max = objectiveManager.CurrentOrder == this ? MathHelper.Min(AIObjectiveManager.OrderPriority, 90) : AIObjectiveManager.RunPriority - 1;
|
||||
Priority = MathHelper.Clamp(value, 0, max);
|
||||
}
|
||||
}
|
||||
return Priority;
|
||||
}
|
||||
|
||||
public AIObjectiveOperateItem(ItemComponent item, Character character, AIObjectiveManager objectiveManager, string option, bool requireEquip, Entity operateTarget = null, bool useController = false, float priorityModifier = 1)
|
||||
public AIObjectiveOperateItem(ItemComponent item, Character character, AIObjectiveManager objectiveManager, string option, bool requireEquip,
|
||||
Entity operateTarget = null, bool useController = false, ItemComponent controller = null, float priorityModifier = 1)
|
||||
: base(character, objectiveManager, priorityModifier, option)
|
||||
{
|
||||
this.component = item ?? throw new System.ArgumentNullException("item", "Attempted to create an AIObjectiveOperateItem with a null target.");
|
||||
component = item ?? throw new ArgumentNullException("item", "Attempted to create an AIObjectiveOperateItem with a null target.");
|
||||
this.requireEquip = requireEquip;
|
||||
this.operateTarget = operateTarget;
|
||||
this.useController = useController;
|
||||
if (useController)
|
||||
if (useController) { this.controller = controller ?? component?.Item?.FindController(); }
|
||||
var target = GetTarget();
|
||||
if (target == null)
|
||||
{
|
||||
//try finding the controller with the simpler non-recursive method first
|
||||
controller =
|
||||
component.Item.GetConnectedComponents<Controller>().FirstOrDefault() ??
|
||||
component.Item.GetConnectedComponents<Controller>(recursive: true).FirstOrDefault();
|
||||
#if DEBUG
|
||||
throw new Exception("target null");
|
||||
#endif
|
||||
Abandon = true;
|
||||
}
|
||||
else if (target.Item.NonInteractable)
|
||||
{
|
||||
Abandon = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,7 +153,7 @@ namespace Barotrauma
|
||||
}
|
||||
if (component.AIOperate(deltaTime, character, this))
|
||||
{
|
||||
IsCompleted = completionCondition == null || completionCondition();
|
||||
isDoneOperating = completionCondition == null || completionCondition();
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -189,12 +220,12 @@ namespace Barotrauma
|
||||
}
|
||||
if (component.AIOperate(deltaTime, character, this))
|
||||
{
|
||||
IsCompleted = completionCondition == null || completionCondition();
|
||||
isDoneOperating = completionCondition == null || completionCondition();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool Check() => IsCompleted && !IsLoop;
|
||||
protected override bool Check() => isDoneOperating && !IsLoop;
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -3,7 +3,6 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Barotrauma.Extensions;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -27,6 +26,7 @@ namespace Barotrauma
|
||||
protected override bool Filter(Pump pump)
|
||||
{
|
||||
if (pump == null) { return false; }
|
||||
if (pump.Item.NonInteractable) { return false; }
|
||||
if (pump.Item.HasTag("ballast")) { return false; }
|
||||
if (pump.Item.Submarine == null) { return false; }
|
||||
if (pump.Item.CurrentHull == null) { return false; }
|
||||
|
||||
+4
-5
@@ -3,7 +3,6 @@ using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Barotrauma.Extensions;
|
||||
using FarseerPhysics;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -52,12 +51,11 @@ namespace Barotrauma
|
||||
float dist = Math.Abs(character.WorldPosition.X - Item.WorldPosition.X) + yDist;
|
||||
distanceFactor = MathHelper.Lerp(1, 0.25f, MathUtils.InverseLerp(0, 5000, dist));
|
||||
}
|
||||
float damagePriority = isPriority ? 1 : MathHelper.Lerp(1, 0, Item.Condition / Item.MaxCondition);
|
||||
float successFactor = isPriority ? 1 : MathHelper.Lerp(0, 1, Item.Repairables.Average(r => r.DegreeOfSuccess(character)));
|
||||
float severity = isPriority ? 1 : AIObjectiveRepairItems.GetTargetPriority(Item, character);
|
||||
float isSelected = IsRepairing ? 50 : 0;
|
||||
float devotion = (CumulatedDevotion + isSelected) / 100;
|
||||
float max = MathHelper.Min(AIObjectiveManager.OrderPriority - 1, 90);
|
||||
Priority = MathHelper.Lerp(0, max, MathHelper.Clamp(devotion + (damagePriority * distanceFactor * successFactor * PriorityModifier), 0, 1));
|
||||
Priority = MathHelper.Lerp(0, max, MathHelper.Clamp(devotion + (severity * distanceFactor * PriorityModifier), 0, 1));
|
||||
}
|
||||
return Priority;
|
||||
}
|
||||
@@ -150,7 +148,8 @@ namespace Barotrauma
|
||||
{
|
||||
if (character.SelectedConstruction != Item)
|
||||
{
|
||||
if (!Item.TryInteract(character, true, true))
|
||||
if (!Item.TryInteract(character, ignoreRequiredItems: true, forceSelectKey: true) &&
|
||||
!Item.TryInteract(character, ignoreRequiredItems: true, forceActionKey: true))
|
||||
{
|
||||
Abandon = true;
|
||||
}
|
||||
|
||||
+20
-8
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Barotrauma.Items.Components;
|
||||
using Barotrauma.Extensions;
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -56,7 +57,7 @@ namespace Barotrauma
|
||||
{
|
||||
Objectives.Remove(item);
|
||||
ignoreList.Add(item);
|
||||
targetUpdateTimer = 0;
|
||||
targetUpdateTimer = Math.Min(0.1f, targetUpdateTimer);
|
||||
};
|
||||
}
|
||||
break;
|
||||
@@ -75,13 +76,9 @@ namespace Barotrauma
|
||||
if (item != character.SelectedConstruction)
|
||||
{
|
||||
float condition = item.ConditionPercentage;
|
||||
if (item.Repairables.All(r => condition >= r.AIRepairThreshold)) { return false; }
|
||||
if (item.Repairables.All(r => condition >= r.RepairThreshold)) { return false; }
|
||||
}
|
||||
}
|
||||
if (RequireAdequateSkills)
|
||||
{
|
||||
if (item.Repairables.Any(r => !r.HasRequiredSkills(character))) { return false; }
|
||||
}
|
||||
if (!string.IsNullOrWhiteSpace(RelevantSkill))
|
||||
{
|
||||
if (item.Repairables.None(r => r.requiredSkills.Any(s => s.Identifier.Equals(RelevantSkill, StringComparison.OrdinalIgnoreCase)))) { return false; }
|
||||
@@ -96,7 +93,7 @@ namespace Barotrauma
|
||||
// Don't stop fixing until done
|
||||
return 100;
|
||||
}
|
||||
int otherFixers = HumanAIController.CountCrew(c => c != HumanAIController && c.ObjectiveManager.IsCurrentObjective<AIObjectiveRepairItems>(), onlyBots: true);
|
||||
int otherFixers = HumanAIController.CountCrew(c => c != HumanAIController && c.ObjectiveManager.IsCurrentObjective<AIObjectiveRepairItems>() && !c.Character.IsIncapacitated, onlyBots: true);
|
||||
int items = Targets.Count;
|
||||
bool anyFixers = otherFixers > 0;
|
||||
float ratio = anyFixers ? items / (float)otherFixers : 1;
|
||||
@@ -111,10 +108,24 @@ namespace Barotrauma
|
||||
// Enough fixers
|
||||
return 0;
|
||||
}
|
||||
return Targets.Sum(t => 100 - t.ConditionPercentage) * ratio;
|
||||
if (RequireAdequateSkills)
|
||||
{
|
||||
return Targets.Sum(t => GetTargetPriority(t, character)) * ratio;
|
||||
}
|
||||
else
|
||||
{
|
||||
return Targets.Sum(t => 100 - t.ConditionPercentage) * ratio;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static float GetTargetPriority(Item item, Character character)
|
||||
{
|
||||
float damagePriority = MathHelper.Lerp(1, 0, item.Condition / item.MaxCondition);
|
||||
float successFactor = MathHelper.Lerp(0, 1, item.Repairables.Average(r => r.DegreeOfSuccess(character)));
|
||||
return MathHelper.Lerp(0, 100, MathHelper.Clamp(damagePriority * successFactor, 0, 1));
|
||||
}
|
||||
|
||||
protected override IEnumerable<Item> GetList() => Item.ItemList;
|
||||
|
||||
protected override AIObjective ObjectiveConstructor(Item item)
|
||||
@@ -126,6 +137,7 @@ namespace Barotrauma
|
||||
public static bool IsValidTarget(Item item, Character character)
|
||||
{
|
||||
if (item == null) { return false; }
|
||||
if (item.NonInteractable) { return false; }
|
||||
if (item.IsFullCondition) { return false; }
|
||||
if (item.CurrentHull == null) { return false; }
|
||||
if (item.Submarine == null) { return false; }
|
||||
|
||||
+33
-12
@@ -38,7 +38,19 @@ namespace Barotrauma
|
||||
}
|
||||
this.targetCharacter = targetCharacter;
|
||||
}
|
||||
|
||||
|
||||
protected override void OnAbandon()
|
||||
{
|
||||
character.SelectedCharacter = null;
|
||||
base.OnAbandon();
|
||||
}
|
||||
|
||||
protected override void OnCompleted()
|
||||
{
|
||||
character.SelectedCharacter = null;
|
||||
base.OnCompleted();
|
||||
}
|
||||
|
||||
protected override void Act(float deltaTime)
|
||||
{
|
||||
if (character.LockHands || targetCharacter == null || targetCharacter.CurrentHull == null || targetCharacter.Removed || targetCharacter.IsDead)
|
||||
@@ -46,16 +58,13 @@ namespace Barotrauma
|
||||
Abandon = true;
|
||||
return;
|
||||
}
|
||||
if (targetCharacter.SelectedBy != null && targetCharacter.SelectedBy != character)
|
||||
var otherRescuer = targetCharacter.SelectedBy;
|
||||
if (otherRescuer != null && otherRescuer != character)
|
||||
{
|
||||
var otherCharacter = character.SelectedBy;
|
||||
if (otherCharacter != null)
|
||||
{
|
||||
// Someone else is rescuing/holding the target.
|
||||
Abandon = otherCharacter.IsPlayer || character.GetSkillLevel("medical") < otherCharacter.GetSkillLevel("medical");
|
||||
}
|
||||
// Someone else is rescuing/holding the target.
|
||||
Abandon = otherRescuer.IsPlayer || character.GetSkillLevel("medical") < otherRescuer.GetSkillLevel("medical");
|
||||
return;
|
||||
}
|
||||
|
||||
if (targetCharacter != character)
|
||||
{
|
||||
// Incapacitated target is not in a safe place -> Move to a safe place first
|
||||
@@ -161,13 +170,23 @@ namespace Barotrauma
|
||||
|
||||
private readonly List<string> suitableItemIdentifiers = new List<string>();
|
||||
private readonly List<string> itemNameList = new List<string>();
|
||||
private Dictionary<string, float> currentTreatmentSuitabilities = new Dictionary<string, float>();
|
||||
private readonly Dictionary<string, float> currentTreatmentSuitabilities = new Dictionary<string, float>();
|
||||
private void GiveTreatment(float deltaTime)
|
||||
{
|
||||
if (targetCharacter == null)
|
||||
{
|
||||
string errorMsg = $"{character.Name}: Attempted to update a Rescue objective with no target!";
|
||||
DebugConsole.ThrowError(errorMsg);
|
||||
Abandon = true;
|
||||
return;
|
||||
}
|
||||
|
||||
SteeringManager?.Reset();
|
||||
|
||||
if (!targetCharacter.IsPlayer)
|
||||
{
|
||||
// If the target is a bot, don't let it move
|
||||
targetCharacter.AIController?.SteeringManager.Reset();
|
||||
targetCharacter.AIController?.SteeringManager?.Reset();
|
||||
}
|
||||
if (treatmentTimer > 0.0f)
|
||||
{
|
||||
@@ -182,6 +201,8 @@ namespace Barotrauma
|
||||
//check if we already have a suitable treatment for any of the afflictions
|
||||
foreach (Affliction affliction in GetSortedAfflictions(targetCharacter))
|
||||
{
|
||||
if (affliction == null) { throw new Exception("Affliction was null"); }
|
||||
if (affliction.Prefab == null) { throw new Exception("Affliction prefab was null"); }
|
||||
foreach (KeyValuePair<string, float> treatmentSuitability in affliction.Prefab.TreatmentSuitability)
|
||||
{
|
||||
if (currentTreatmentSuitabilities.ContainsKey(treatmentSuitability.Key) && currentTreatmentSuitabilities[treatmentSuitability.Key] > 0.0f)
|
||||
@@ -258,7 +279,7 @@ namespace Barotrauma
|
||||
ic.PlaySound(ActionType.OnUse, character);
|
||||
#endif
|
||||
ic.WasUsed = true;
|
||||
ic.ApplyStatusEffects(ActionType.OnUse, 1.0f, targetCharacter, targetLimb);
|
||||
ic.ApplyStatusEffects(ActionType.OnUse, 1.0f, targetCharacter, targetLimb, user: character);
|
||||
if (ic.DeleteOnUse)
|
||||
{
|
||||
remove = true;
|
||||
|
||||
+24
-34
@@ -33,46 +33,31 @@ namespace Barotrauma
|
||||
|
||||
protected override float TargetEvaluation()
|
||||
{
|
||||
int otherRescuers = HumanAIController.CountCrew(c => c != HumanAIController && c.ObjectiveManager.IsCurrentObjective<AIObjectiveRescueAll>(), onlyBots: true);
|
||||
int targetCount = Targets.Count;
|
||||
bool anyRescuers = otherRescuers > 0;
|
||||
float ratio = anyRescuers ? targetCount / (float)otherRescuers : 1;
|
||||
if (objectiveManager.CurrentOrder == this)
|
||||
if (objectiveManager.CurrentOrder != this)
|
||||
{
|
||||
return Targets.Min(t => GetVitalityFactor(t)) / ratio;
|
||||
}
|
||||
else
|
||||
{
|
||||
float multiplier = 1;
|
||||
if (anyRescuers)
|
||||
if (!character.IsMedic && HumanAIController.IsTrueForAnyCrewMember(c => c != HumanAIController && c.Character.IsMedic && !c.Character.IsUnconscious))
|
||||
{
|
||||
float mySkill = character.GetSkillLevel("medical");
|
||||
int betterRescuers = HumanAIController.CountCrew(c => c != HumanAIController && c.Character.Info.Job.GetSkillLevel("medical") >= mySkill, onlyBots: true);
|
||||
if (targetCount / (float)betterRescuers <= 1)
|
||||
{
|
||||
// Enough rescuers
|
||||
return 100;
|
||||
}
|
||||
else
|
||||
{
|
||||
bool foundOtherMedics = HumanAIController.IsTrueForAnyCrewMember(c => c != HumanAIController && c.Character.Info.Job.Prefab.Identifier == "medicaldoctor");
|
||||
if (foundOtherMedics)
|
||||
{
|
||||
if (character.Info.Job.Prefab.Identifier != "medicaldoctor")
|
||||
{
|
||||
// Double the vitality factor -> less likely to take action
|
||||
multiplier = 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Don't do anything if there's a medic on board and we are not a medic
|
||||
return 100;
|
||||
}
|
||||
return Targets.Min(t => GetVitalityFactor(t)) / ratio * multiplier;
|
||||
}
|
||||
float worstCondition = Targets.Min(t => GetVitalityFactor(t));
|
||||
if (Targets.Contains(character))
|
||||
{
|
||||
if (character.Bleeding > 10)
|
||||
{
|
||||
// Enforce the highest priority when bleeding out.
|
||||
worstCondition = 0;
|
||||
}
|
||||
// Boost the priority when wounded.
|
||||
worstCondition /= 2;
|
||||
}
|
||||
return worstCondition;
|
||||
}
|
||||
|
||||
public static float GetVitalityFactor(Character character)
|
||||
{
|
||||
float vitality = character.HealthPercentage - character.Bleeding - character.Bloodloss + Math.Min(character.Oxygen, 0);
|
||||
float vitality = character.HealthPercentage - (character.Bleeding * 2) - character.Bloodloss + Math.Min(character.Oxygen, 0);
|
||||
vitality -= character.CharacterHealth.GetAfflictionStrength("paralysis");
|
||||
return Math.Clamp(vitality, 0, 100);
|
||||
}
|
||||
@@ -92,6 +77,11 @@ namespace Barotrauma
|
||||
if (GetVitalityFactor(target) >= GetVitalityThreshold(humanAI.ObjectiveManager, character, target)) { return false; }
|
||||
if (!humanAI.ObjectiveManager.IsCurrentOrder<AIObjectiveRescueAll>())
|
||||
{
|
||||
if (!character.IsMedic && target != character)
|
||||
{
|
||||
// Don't allow to treat others autonomously
|
||||
return false;
|
||||
}
|
||||
// Ignore unsafe hulls, unless ordered
|
||||
if (humanAI.UnsafeHulls.Contains(target.CurrentHull))
|
||||
{
|
||||
@@ -111,10 +101,10 @@ namespace Barotrauma
|
||||
if (target.Submarine.Info.Type != character.Submarine.Info.Type) { return false; }
|
||||
if (character.Submarine != null && !character.Submarine.IsEntityFoundOnThisSub(target.CurrentHull, true)) { return false; }
|
||||
}
|
||||
if (!target.IsPlayer && HumanAIController.IsActive(target) && target.AIController is HumanAIController targetAI)
|
||||
if (target != character &&!target.IsPlayer && HumanAIController.IsActive(target) && target.AIController is HumanAIController targetAI)
|
||||
{
|
||||
// Ignore all concious targets that are currently fighting, fleeing or treating characters
|
||||
if (targetAI.ObjectiveManager.HasActiveObjective<AIObjectiveCombat>() ||
|
||||
if (targetAI.ObjectiveManager.HasActiveObjective<AIObjectiveCombat>() ||
|
||||
targetAI.ObjectiveManager.HasActiveObjective<AIObjectiveFindSafety>() ||
|
||||
targetAI.ObjectiveManager.HasActiveObjective<AIObjectiveRescue>())
|
||||
{
|
||||
|
||||
@@ -85,32 +85,22 @@ namespace Barotrauma
|
||||
//legacy support
|
||||
public readonly string[] AppropriateJobs;
|
||||
public readonly string[] Options;
|
||||
public readonly string[] OptionNames;
|
||||
private readonly Dictionary<string, string> OptionNames;
|
||||
|
||||
public readonly Dictionary<string, Sprite> OptionSprites;
|
||||
|
||||
private readonly Dictionary<string, Sprite> minimapIcons;
|
||||
public Dictionary<string, Sprite> MinimapIcons => IsPrefab ? minimapIcons : Prefab.minimapIcons;
|
||||
|
||||
public readonly float Weight;
|
||||
public readonly bool MustSetTarget;
|
||||
public readonly string AppropriateSkill;
|
||||
|
||||
public bool HasOptions
|
||||
{
|
||||
get
|
||||
{
|
||||
if (IsPrefab)
|
||||
{
|
||||
return MustSetTarget || Options.Length > 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
return Prefab.MustSetTarget || Prefab.Options.Length > 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
public bool HasOptions => (IsPrefab ? Options : Prefab.Options).Length > 1;
|
||||
public bool IsPrefab { get; private set; }
|
||||
public readonly bool MustManuallyAssign;
|
||||
|
||||
static Order()
|
||||
public static void Init()
|
||||
{
|
||||
Prefabs = new Dictionary<string, Order>();
|
||||
OrderCategoryIcons = new Dictionary<OrderCategory, Tuple<Sprite, Color>>();
|
||||
@@ -219,25 +209,18 @@ namespace Barotrauma
|
||||
MustSetTarget = orderElement.GetAttributeBool("mustsettarget", false);
|
||||
AppropriateSkill = orderElement.GetAttributeString("appropriateskill", null);
|
||||
|
||||
string translatedOptionNames = TextManager.Get("OrderOptions." + Identifier, true);
|
||||
if (translatedOptionNames == null)
|
||||
var optionNames = TextManager.Get("OrderOptions." + Identifier, true)?.Split(',', ',') ??
|
||||
orderElement.GetAttributeStringArray("optionnames", new string[0]);
|
||||
OptionNames = new Dictionary<string, string>();
|
||||
for (int i = 0; i < Options.Length && i < optionNames.Length; i++)
|
||||
{
|
||||
OptionNames = orderElement.GetAttributeStringArray("optionnames", new string[0]);
|
||||
OptionNames.Add(Options[i], optionNames[i].Trim());
|
||||
}
|
||||
else
|
||||
{
|
||||
string[] splitOptionNames = translatedOptionNames.Split(',', ',');
|
||||
OptionNames = new string[Options.Length];
|
||||
for (int i = 0; i < Options.Length && i < splitOptionNames.Length; i++)
|
||||
{
|
||||
OptionNames[i] = splitOptionNames[i].Trim();
|
||||
}
|
||||
}
|
||||
|
||||
if (OptionNames.Length != Options.Length)
|
||||
if (OptionNames.Count != Options.Length)
|
||||
{
|
||||
DebugConsole.ThrowError("Error in Order " + Name + " - the number of option names doesn't match the number of options.");
|
||||
OptionNames = Options;
|
||||
OptionNames.Clear();
|
||||
Options.ForEach(o => OptionNames.Add(o, o));
|
||||
}
|
||||
|
||||
var spriteElement = orderElement.GetChildElement("sprite");
|
||||
@@ -261,6 +244,15 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
minimapIcons = new Dictionary<string, Sprite>();
|
||||
var minimapIconElements = orderElement.GetChildElements("minimapicon");
|
||||
foreach (XElement minimapIconElement in minimapIconElements)
|
||||
{
|
||||
var id = minimapIconElement.GetAttributeString("id", null);
|
||||
if (string.IsNullOrWhiteSpace(id)) { continue; }
|
||||
minimapIcons.Add(id, new Sprite(minimapIconElement.GetChildElement("sprite"), lazyLoad: true));
|
||||
}
|
||||
|
||||
IsPrefab = true;
|
||||
MustManuallyAssign = orderElement.GetAttributeBool("mustmanuallyassign", false);
|
||||
}
|
||||
@@ -268,7 +260,7 @@ namespace Barotrauma
|
||||
/// <summary>
|
||||
/// Constructor for order instances
|
||||
/// </summary>
|
||||
public Order(Order prefab, Entity targetEntity, ItemComponent targetItem, Character orderGiver = null)
|
||||
public Order(Order prefab, Entity targetEntity, ItemComponent targetItem, Character orderGiver = null, bool isAutonomous = false)
|
||||
{
|
||||
Prefab = prefab;
|
||||
|
||||
@@ -293,27 +285,23 @@ namespace Barotrauma
|
||||
TargetEntity = targetEntity;
|
||||
if (targetItem != null)
|
||||
{
|
||||
if (UseController) { ConnectedController = FindController(targetItem); }
|
||||
if (UseController)
|
||||
{
|
||||
ConnectedController = targetItem.Item?.FindController();
|
||||
if (ConnectedController == null)
|
||||
{
|
||||
#if DEBUG
|
||||
throw new Exception("Tried to use controller, but couldn't find one");
|
||||
#endif
|
||||
UseController = false;
|
||||
}
|
||||
}
|
||||
TargetEntity = targetItem.Item;
|
||||
TargetItemComponent = targetItem;
|
||||
}
|
||||
|
||||
IsPrefab = false;
|
||||
}
|
||||
|
||||
private Controller FindController(ItemComponent targetComponent)
|
||||
{
|
||||
if (targetComponent?.Item == null) { return null; }
|
||||
//try finding the controller with the simpler non-recursive method first
|
||||
return targetComponent.Item.GetConnectedComponents<Controller>().FirstOrDefault() ??
|
||||
targetComponent.Item.GetConnectedComponents<Controller>(recursive: true).FirstOrDefault();
|
||||
}
|
||||
|
||||
private bool TryFindController(ItemComponent targetComponent, out Controller controller)
|
||||
{
|
||||
controller = FindController(targetComponent);
|
||||
return controller != null;
|
||||
}
|
||||
|
||||
public bool HasAppropriateJob(Character character)
|
||||
{
|
||||
@@ -368,7 +356,7 @@ namespace Barotrauma
|
||||
matchingItems.RemoveAll(it => it.NonInteractable);
|
||||
if (UseController)
|
||||
{
|
||||
matchingItems.RemoveAll(i => i.Components.None(c => c.GetType() == ItemComponentType && TryFindController(c, out _)));
|
||||
matchingItems.RemoveAll(i => i.Components.None(c => c.GetType() == ItemComponentType) && !i.TryFindController(out _));
|
||||
}
|
||||
}
|
||||
return matchingItems;
|
||||
@@ -381,5 +369,16 @@ namespace Barotrauma
|
||||
Submarine.MainSub;
|
||||
return GetMatchingItems(submarine, mustBelongToPlayerSub);
|
||||
}
|
||||
|
||||
public string GetOptionName(string id)
|
||||
{
|
||||
return Prefab == null ? OptionNames[id] : Prefab.OptionNames[id];
|
||||
}
|
||||
|
||||
public string GetOptionName(int index)
|
||||
{
|
||||
if (index < 0 || index >= Options.Length) { return null; }
|
||||
return GetOptionName(Options[index]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ namespace Barotrauma
|
||||
var nodes = new Dictionary<int, PathNode>();
|
||||
foreach (WayPoint wayPoint in wayPoints)
|
||||
{
|
||||
if (wayPoint == null) continue;
|
||||
if (wayPoint == null) { continue; }
|
||||
if (nodes.ContainsKey(wayPoint.ID))
|
||||
{
|
||||
#if DEBUG
|
||||
@@ -63,7 +63,7 @@ namespace Barotrauma
|
||||
{
|
||||
PathNode connectedNode = null;
|
||||
nodes.TryGetValue(linked.ID, out connectedNode);
|
||||
if (connectedNode == null) continue;
|
||||
if (connectedNode == null) { continue; }
|
||||
|
||||
node.Value.connections.Add(connectedNode);
|
||||
}
|
||||
@@ -107,17 +107,17 @@ namespace Barotrauma
|
||||
|
||||
void WaypointLinksChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
if (Submarine.Unloading) return;
|
||||
if (Submarine.Unloading) { return; }
|
||||
|
||||
var waypoints = sender as IEnumerable<MapEntity>;
|
||||
|
||||
foreach (MapEntity me in waypoints)
|
||||
{
|
||||
WayPoint wp = me as WayPoint;
|
||||
if (me == null) continue;
|
||||
if (me == null) { continue; }
|
||||
|
||||
var node = nodes.Find(n => n.Waypoint == wp);
|
||||
if (node == null) return;
|
||||
if (node == null) { return; }
|
||||
|
||||
if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Remove)
|
||||
{
|
||||
@@ -136,10 +136,10 @@ namespace Barotrauma
|
||||
for (int i = 0; i < wp.linkedTo.Count; i++)
|
||||
{
|
||||
WayPoint connected = wp.linkedTo[i] as WayPoint;
|
||||
if (connected == null) continue;
|
||||
if (connected == null) { continue; }
|
||||
|
||||
//already connected, continue
|
||||
if (node.connections.Any(n => n.Waypoint == connected)) continue;
|
||||
if (node.connections.Any(n => n.Waypoint == connected)) { continue; }
|
||||
|
||||
var matchingNode = nodes.Find(n => n.Waypoint == connected);
|
||||
if (matchingNode == null)
|
||||
@@ -201,8 +201,8 @@ namespace Barotrauma
|
||||
if (body != null)
|
||||
{
|
||||
//if (body.UserData is Submarine) continue;
|
||||
if (body.UserData is Structure && !((Structure)body.UserData).IsPlatform) continue;
|
||||
if (body.UserData is Item && body.FixtureList[0].CollisionCategories.HasFlag(Physics.CollisionWall)) continue;
|
||||
if (body.UserData is Structure && !((Structure)body.UserData).IsPlatform) { continue; }
|
||||
if (body.UserData is Item && body.FixtureList[0].CollisionCategories.HasFlag(Physics.CollisionWall)) { continue; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -236,9 +236,9 @@ namespace Barotrauma
|
||||
if (InsideSubmarine)
|
||||
{
|
||||
//much higher cost to waypoints that are outside
|
||||
if (node.Waypoint.CurrentHull == null) dist *= 10.0f;
|
||||
if (node.Waypoint.CurrentHull == null) { dist *= 10.0f; }
|
||||
//avoid stopping at a doorway
|
||||
if (node.Waypoint.ConnectedDoor != null) dist *= 10.0f;
|
||||
if (node.Waypoint.ConnectedDoor != null) { dist *= 10.0f; }
|
||||
}
|
||||
if (dist < closestDist || endNode == null)
|
||||
{
|
||||
@@ -251,8 +251,8 @@ namespace Barotrauma
|
||||
if (body != null)
|
||||
{
|
||||
//if (body.UserData is Submarine) continue;
|
||||
if (body.UserData is Structure && !((Structure)body.UserData).IsPlatform) continue;
|
||||
if (body.UserData is Item && body.FixtureList[0].CollisionCategories.HasFlag(Physics.CollisionWall)) continue;
|
||||
if (body.UserData is Structure && !((Structure)body.UserData).IsPlatform) { continue; }
|
||||
if (body.UserData is Item && body.FixtureList[0].CollisionCategories.HasFlag(Physics.CollisionWall)) { continue; }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,12 +9,31 @@ namespace Barotrauma
|
||||
|
||||
int currentIndex;
|
||||
|
||||
private float? totalLength;
|
||||
|
||||
public bool Unreachable
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public float TotalLength
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Unreachable) { return float.PositiveInfinity; }
|
||||
if (!totalLength.HasValue)
|
||||
{
|
||||
totalLength = 0.0f;
|
||||
for (int i = 0; i < nodes.Count - 1; i++)
|
||||
{
|
||||
totalLength += Vector2.Distance(nodes[i].WorldPosition, nodes[i + 1].WorldPosition);
|
||||
}
|
||||
}
|
||||
return totalLength.Value;
|
||||
}
|
||||
}
|
||||
|
||||
public SteeringPath(bool unreachable = false)
|
||||
{
|
||||
nodes = new List<WayPoint>();
|
||||
@@ -23,10 +42,10 @@ namespace Barotrauma
|
||||
|
||||
public void AddNode(WayPoint node)
|
||||
{
|
||||
if (node == null) return;
|
||||
if (node == null) { return; }
|
||||
nodes.Add(node);
|
||||
|
||||
if (node.CurrentHull == null) HasOutdoorsNodes = true;
|
||||
if (node.CurrentHull == null) { HasOutdoorsNodes = true; }
|
||||
}
|
||||
|
||||
public bool HasOutdoorsNodes
|
||||
@@ -48,10 +67,10 @@ namespace Barotrauma
|
||||
|
||||
public WayPoint PrevNode
|
||||
{
|
||||
get
|
||||
get
|
||||
{
|
||||
if (currentIndex-1 < 0 || currentIndex-1 > nodes.Count - 1) return null;
|
||||
return nodes[currentIndex-1];
|
||||
if (currentIndex - 1 < 0 || currentIndex - 1 > nodes.Count - 1) { return null; }
|
||||
return nodes[currentIndex - 1];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,7 +78,7 @@ namespace Barotrauma
|
||||
{
|
||||
get
|
||||
{
|
||||
if (currentIndex < 0 || currentIndex > nodes.Count - 1) return null;
|
||||
if (currentIndex < 0 || currentIndex > nodes.Count - 1) { return null; }
|
||||
return nodes[currentIndex];
|
||||
}
|
||||
}
|
||||
@@ -73,7 +92,7 @@ namespace Barotrauma
|
||||
{
|
||||
get
|
||||
{
|
||||
if (currentIndex+1 < 0 || currentIndex+1 > nodes.Count - 1) return null;
|
||||
if (currentIndex + 1 < 0 || currentIndex + 1 > nodes.Count - 1) { return null; }
|
||||
return nodes[currentIndex+1];
|
||||
}
|
||||
}
|
||||
@@ -90,8 +109,8 @@ namespace Barotrauma
|
||||
|
||||
public WayPoint CheckProgress(Vector2 simPosition, float minSimDistance = 0.1f)
|
||||
{
|
||||
if (nodes.Count == 0 || currentIndex>nodes.Count-1) return null;
|
||||
if (Vector2.Distance(simPosition, nodes[currentIndex].SimPosition) < minSimDistance) currentIndex++;
|
||||
if (nodes.Count == 0 || currentIndex > nodes.Count - 1) { return null; }
|
||||
if (Vector2.Distance(simPosition, nodes[currentIndex].SimPosition) < minSimDistance) { currentIndex++; }
|
||||
|
||||
return CurrentNode;
|
||||
}
|
||||
|
||||
@@ -268,7 +268,26 @@ namespace Barotrauma
|
||||
{
|
||||
if (Config.KillAgentsWhenEntityDies)
|
||||
{
|
||||
protectiveCells.ForEach(c => c.Kill(CauseOfDeathType.Unknown, null, isNetworkMessage: true));
|
||||
protectiveCells.ForEach(c => c.Kill(CauseOfDeathType.Unknown, null));
|
||||
if (!string.IsNullOrWhiteSpace(Config.OffensiveAgent))
|
||||
{
|
||||
foreach (var character in Character.CharacterList)
|
||||
{
|
||||
// Kills ALL offensive agents that are near the thalamus. Not the ideal solution,
|
||||
// but as long as spawning is handled via status effects, I don't know if there is any better way.
|
||||
// In practice there shouldn't be terminal cells from different thalamus organisms at the same time.
|
||||
// And if there was, the distance check should prevent killing the agents of a different organism.
|
||||
if (character.SpeciesName.Equals(Config.OffensiveAgent, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// Sonar distance is used also for wreck positioning. No wreck should be closer to each other than this.
|
||||
float maxDistance = Sonar.DefaultSonarRange;
|
||||
if (Vector2.DistanceSquared(character.WorldPosition, Wreck.WorldPosition) < maxDistance * maxDistance)
|
||||
{
|
||||
character.Kill(CauseOfDeathType.Unknown, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,9 @@ namespace Barotrauma
|
||||
[Serialize("", false)]
|
||||
public string DefensiveAgent { get; private set; }
|
||||
|
||||
[Serialize("", false)]
|
||||
public string OffensiveAgent { get; private set; }
|
||||
|
||||
[Serialize("", false)]
|
||||
public string Brain { get; private set; }
|
||||
|
||||
|
||||
Reference in New Issue
Block a user