2f107db...5202af9
This commit is contained in:
@@ -12,7 +12,17 @@ namespace Barotrauma
|
||||
|
||||
private AIState state;
|
||||
|
||||
protected AITarget selectedAiTarget;
|
||||
protected AITarget _previousAiTarget;
|
||||
protected AITarget _selectedAiTarget;
|
||||
public AITarget SelectedAiTarget
|
||||
{
|
||||
get { return _selectedAiTarget; }
|
||||
protected set
|
||||
{
|
||||
_previousAiTarget = _selectedAiTarget;
|
||||
_selectedAiTarget = value;
|
||||
}
|
||||
}
|
||||
|
||||
protected SteeringManager steeringManager;
|
||||
|
||||
@@ -57,11 +67,6 @@ namespace Barotrauma
|
||||
get { return null; }
|
||||
}
|
||||
|
||||
public AITarget SelectedAiTarget
|
||||
{
|
||||
get { return selectedAiTarget; }
|
||||
}
|
||||
|
||||
public AIState State
|
||||
{
|
||||
get { return state; }
|
||||
|
||||
@@ -27,7 +27,7 @@ namespace Barotrauma
|
||||
public float SightRange
|
||||
{
|
||||
get { return sightRange; }
|
||||
set { sightRange = MathHelper.Clamp(value, MinSightRange, MaxSoundRange); }
|
||||
set { sightRange = MathHelper.Clamp(value, MinSightRange, MaxSightRange); }
|
||||
}
|
||||
|
||||
private float sectorRad = MathHelper.TwoPi;
|
||||
@@ -58,8 +58,8 @@ namespace Barotrauma
|
||||
|
||||
public bool Enabled = true;
|
||||
|
||||
public readonly float MinSoundRange, MinSightRange;
|
||||
public readonly float MaxSoundRange = float.MaxValue, MaxSightRange = float.MaxValue;
|
||||
public float MinSoundRange, MinSightRange;
|
||||
public float MaxSoundRange = float.MaxValue, MaxSightRange = float.MaxValue;
|
||||
|
||||
public Vector2 WorldPosition
|
||||
{
|
||||
@@ -101,19 +101,12 @@ namespace Barotrauma
|
||||
|
||||
public AITarget(Entity e, XElement element) : this(e)
|
||||
{
|
||||
SightRange = MinSightRange = element.GetAttributeFloat("sightrange", 0.0f);
|
||||
SoundRange = MinSoundRange = element.GetAttributeFloat("soundrange", 0.0f);
|
||||
// Use the min and max definitions if found.
|
||||
if (element.Attribute("minsightrange") != null)
|
||||
{
|
||||
MinSightRange = element.GetAttributeFloat("minsightrange", MinSightRange);
|
||||
}
|
||||
if (element.Attribute("minsoundrange") != null)
|
||||
{
|
||||
MinSoundRange = element.GetAttributeFloat("minsoundrange", MinSoundRange);
|
||||
}
|
||||
MaxSightRange = element.GetAttributeFloat("maxsightrange", MaxSightRange);
|
||||
MaxSoundRange = element.GetAttributeFloat("maxsoundrange", MaxSoundRange);
|
||||
SightRange = element.GetAttributeFloat("sightrange", 0.0f);
|
||||
SoundRange = element.GetAttributeFloat("soundrange", 0.0f);
|
||||
MinSightRange = element.GetAttributeFloat("minsightrange", SightRange);
|
||||
MinSoundRange = element.GetAttributeFloat("minsoundrange", SoundRange);
|
||||
MaxSightRange = element.GetAttributeFloat("maxsightrange", SightRange);
|
||||
MaxSoundRange = element.GetAttributeFloat("maxsoundrange", SoundRange);
|
||||
SonarLabel = element.GetAttributeString("sonarlabel", "");
|
||||
}
|
||||
|
||||
|
||||
@@ -88,9 +88,7 @@ namespace Barotrauma
|
||||
|
||||
private AITargetMemory selectedTargetMemory;
|
||||
private float targetValue;
|
||||
|
||||
private float eatTimer;
|
||||
|
||||
|
||||
private Dictionary<AITarget, AITargetMemory> targetMemories;
|
||||
|
||||
//the eyesight of the NPC (0.0 = blind, 1.0 = sees every target within sightRange)
|
||||
@@ -250,7 +248,7 @@ namespace Barotrauma
|
||||
|
||||
public override void SelectTarget(AITarget target)
|
||||
{
|
||||
selectedAiTarget = target;
|
||||
SelectedAiTarget = target;
|
||||
selectedTargetMemory = FindTargetMemory(target);
|
||||
|
||||
targetValue = 100.0f;
|
||||
@@ -292,7 +290,7 @@ namespace Barotrauma
|
||||
UpdateTargets(Character, out targetingPriority);
|
||||
updateTargetsTimer = UpdateTargetsInterval;
|
||||
|
||||
if (selectedAiTarget == null)
|
||||
if (SelectedAiTarget == null)
|
||||
{
|
||||
State = AIState.Idle;
|
||||
}
|
||||
@@ -308,7 +306,7 @@ namespace Barotrauma
|
||||
|
||||
latchOntoAI?.Update(this, deltaTime);
|
||||
|
||||
if (selectedAiTarget != null && (selectedAiTarget.Entity == null || selectedAiTarget.Entity.Removed))
|
||||
if (SelectedAiTarget != null && (SelectedAiTarget.Entity == null || SelectedAiTarget.Entity.Removed))
|
||||
{
|
||||
State = AIState.Idle;
|
||||
return;
|
||||
@@ -368,9 +366,9 @@ namespace Barotrauma
|
||||
|
||||
if (wallTarget != null) return;
|
||||
|
||||
if (selectedAiTarget != null)
|
||||
if (SelectedAiTarget != null)
|
||||
{
|
||||
Vector2 targetSimPos = Character.Submarine == null ? ConvertUnits.ToSimUnits(selectedAiTarget.WorldPosition) : selectedAiTarget.SimPosition;
|
||||
Vector2 targetSimPos = Character.Submarine == null ? ConvertUnits.ToSimUnits(SelectedAiTarget.WorldPosition) : SelectedAiTarget.SimPosition;
|
||||
|
||||
steeringManager.SteeringAvoid(deltaTime, colliderSize * 3.0f, speed);
|
||||
steeringManager.SteeringSeek(targetSimPos, speed);
|
||||
@@ -392,13 +390,13 @@ namespace Barotrauma
|
||||
|
||||
private void UpdateEscape(float deltaTime)
|
||||
{
|
||||
if (selectedAiTarget == null || selectedAiTarget.Entity == null || selectedAiTarget.Entity.Removed)
|
||||
if (SelectedAiTarget == null || SelectedAiTarget.Entity == null || SelectedAiTarget.Entity.Removed)
|
||||
{
|
||||
State = AIState.Idle;
|
||||
return;
|
||||
}
|
||||
|
||||
Vector2 escapeDir = Vector2.Normalize(SimPosition - selectedAiTarget.SimPosition);
|
||||
Vector2 escapeDir = Vector2.Normalize(SimPosition - SelectedAiTarget.SimPosition);
|
||||
if (!MathUtils.IsValid(escapeDir)) escapeDir = Vector2.UnitY;
|
||||
float speed = Character.AnimController.GetCurrentSpeed(useMaxSpeed: true);
|
||||
SteeringManager.SteeringManual(deltaTime, escapeDir * speed);
|
||||
@@ -415,7 +413,7 @@ namespace Barotrauma
|
||||
|
||||
private void UpdateAttack(float deltaTime)
|
||||
{
|
||||
if (selectedAiTarget == null)
|
||||
if (SelectedAiTarget == null)
|
||||
{
|
||||
State = AIState.Idle;
|
||||
return;
|
||||
@@ -425,23 +423,23 @@ namespace Barotrauma
|
||||
|
||||
selectedTargetMemory.Priority -= deltaTime * 0.1f;
|
||||
|
||||
Vector2 attackSimPosition = Character.Submarine == null ? ConvertUnits.ToSimUnits(selectedAiTarget.WorldPosition) : selectedAiTarget.SimPosition;
|
||||
Vector2 attackSimPosition = Character.Submarine == null ? ConvertUnits.ToSimUnits(SelectedAiTarget.WorldPosition) : SelectedAiTarget.SimPosition;
|
||||
|
||||
if (Character.Submarine != null && selectedAiTarget.Entity.Submarine != null && Character.Submarine != selectedAiTarget.Entity.Submarine)
|
||||
if (Character.Submarine != null && SelectedAiTarget.Entity.Submarine != null && Character.Submarine != SelectedAiTarget.Entity.Submarine)
|
||||
{
|
||||
attackSimPosition = ConvertUnits.ToSimUnits(selectedAiTarget.WorldPosition - Character.Submarine.Position);
|
||||
attackSimPosition = ConvertUnits.ToSimUnits(SelectedAiTarget.WorldPosition - Character.Submarine.Position);
|
||||
}
|
||||
|
||||
if (selectedAiTarget.Entity is Item item)
|
||||
if (SelectedAiTarget.Entity is Item item)
|
||||
{
|
||||
// If the item is held by a character, attack the character instead.
|
||||
var pickable = item.components.Select(c => c as Pickable).FirstOrDefault();
|
||||
var pickable = item.GetComponent<Pickable>();
|
||||
if (pickable != null)
|
||||
{
|
||||
var target = pickable.Picker?.AiTarget;
|
||||
if (target != null)
|
||||
{
|
||||
selectedAiTarget = target;
|
||||
SelectedAiTarget = target;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -449,13 +447,13 @@ namespace Barotrauma
|
||||
if (wallTarget != null)
|
||||
{
|
||||
attackSimPosition = ConvertUnits.ToSimUnits(wallTarget.Position);
|
||||
if (Character.Submarine == null && selectedAiTarget.Entity?.Submarine != null) attackSimPosition += ConvertUnits.ToSimUnits(selectedAiTarget.Entity.Submarine.Position);
|
||||
if (Character.Submarine == null && SelectedAiTarget.Entity?.Submarine != null) attackSimPosition += ConvertUnits.ToSimUnits(SelectedAiTarget.Entity.Submarine.Position);
|
||||
}
|
||||
else if (selectedAiTarget.Entity is Character)
|
||||
else if (SelectedAiTarget.Entity is Character)
|
||||
{
|
||||
//target the closest limb if the target is a character
|
||||
float closestDist = Vector2.DistanceSquared(selectedAiTarget.SimPosition, SimPosition) * 10.0f;
|
||||
foreach (Limb limb in ((Character)selectedAiTarget.Entity).AnimController.Limbs)
|
||||
float closestDist = Vector2.DistanceSquared(SelectedAiTarget.SimPosition, SimPosition) * 10.0f;
|
||||
foreach (Limb limb in ((Character)SelectedAiTarget.Entity).AnimController.Limbs)
|
||||
{
|
||||
if (limb == null) continue;
|
||||
float dist = Vector2.DistanceSquared(limb.SimPosition, SimPosition) / Math.Max(limb.AttackPriority, 0.1f);
|
||||
@@ -514,9 +512,9 @@ namespace Barotrauma
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (selectedAiTarget.Entity is Item)
|
||||
else if (SelectedAiTarget.Entity is Item)
|
||||
{
|
||||
var door = ((Item)selectedAiTarget.Entity).GetComponent<Door>();
|
||||
var door = ((Item)SelectedAiTarget.Entity).GetComponent<Door>();
|
||||
//steer through the door manually if it's open or broken
|
||||
if (door?.LinkedGap?.FlowTargetHull != null && !door.LinkedGap.IsRoomToRoom && (door.IsOpen || door.Item.Condition <= 0.0f))
|
||||
{
|
||||
@@ -566,24 +564,39 @@ namespace Barotrauma
|
||||
{
|
||||
if (attackingLimb.attack.SecondaryCoolDownTimer <= 0)
|
||||
{
|
||||
// If the secondary cooldown is defined and expired, check if we can switch the attack
|
||||
var previousLimb = attackingLimb;
|
||||
var newLimb = GetAttackLimb(attackSimPosition, previousLimb);
|
||||
if (newLimb != null)
|
||||
// Don't allow attacking when the attack target has changed.
|
||||
if (_previousAiTarget != null && SelectedAiTarget != _previousAiTarget)
|
||||
{
|
||||
attackingLimb = newLimb;
|
||||
canAttack = false;
|
||||
if (attackingLimb.attack.AfterAttack == AIBehaviorAfterAttack.PursueIfCanAttack)
|
||||
{
|
||||
// Fall back if cannot attack.
|
||||
UpdateFallBack(attackSimPosition, deltaTime);
|
||||
return;
|
||||
}
|
||||
attackingLimb = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
// No new limb was found.
|
||||
if (attackingLimb.attack.AfterAttack == AIBehaviorAfterAttack.Pursue)
|
||||
// If the secondary cooldown is defined and expired, check if we can switch the attack
|
||||
var previousLimb = attackingLimb;
|
||||
var newLimb = GetAttackLimb(attackSimPosition, previousLimb);
|
||||
if (newLimb != null)
|
||||
{
|
||||
canAttack = false;
|
||||
attackingLimb = newLimb;
|
||||
}
|
||||
else
|
||||
{
|
||||
UpdateFallBack(attackSimPosition, deltaTime);
|
||||
return;
|
||||
// No new limb was found.
|
||||
if (attackingLimb.attack.AfterAttack == AIBehaviorAfterAttack.Pursue)
|
||||
{
|
||||
canAttack = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
UpdateFallBack(attackSimPosition, deltaTime);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -648,12 +661,12 @@ namespace Barotrauma
|
||||
else if (indoorsSteering.CurrentPath.CurrentNode?.ConnectedDoor != null)
|
||||
{
|
||||
wallTarget = null;
|
||||
selectedAiTarget = indoorsSteering.CurrentPath.CurrentNode.ConnectedDoor.Item.AiTarget;
|
||||
SelectedAiTarget = indoorsSteering.CurrentPath.CurrentNode.ConnectedDoor.Item.AiTarget;
|
||||
}
|
||||
else if (indoorsSteering.CurrentPath.NextNode?.ConnectedDoor != null)
|
||||
{
|
||||
wallTarget = null;
|
||||
selectedAiTarget = indoorsSteering.CurrentPath.NextNode.ConnectedDoor.Item.AiTarget;
|
||||
SelectedAiTarget = indoorsSteering.CurrentPath.NextNode.ConnectedDoor.Item.AiTarget;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -668,7 +681,7 @@ namespace Barotrauma
|
||||
private Limb GetAttackLimb(Vector2 attackSimPosition, Limb ignoredLimb = null)
|
||||
{
|
||||
AttackContext currentContext = Character.GetAttackContext();
|
||||
var target = wallTarget != null ? wallTarget.Structure : selectedAiTarget.Entity;
|
||||
var target = wallTarget != null ? wallTarget.Structure : SelectedAiTarget.Entity;
|
||||
var limbs = Character.AnimController.Limbs
|
||||
.Where(l =>
|
||||
l != ignoredLimb &&
|
||||
@@ -677,7 +690,7 @@ namespace Barotrauma
|
||||
!l.IsStuck &&
|
||||
l.attack.IsValidContext(currentContext) &&
|
||||
l.attack.IsValidTarget(target) &&
|
||||
l.attack.Conditionals.All(c => (target is ISerializableEntity se && c.Matches(se)) || !(target is ISerializableEntity)))
|
||||
l.attack.Conditionals.All(c => (target is ISerializableEntity se && c.Matches(se)) || !(target is ISerializableEntity) || !(target is Character)))
|
||||
.OrderByDescending(l => l.attack.Priority)
|
||||
.ThenBy(l => ConvertUnits.ToDisplayUnits(Vector2.Distance(l.SimPosition, attackSimPosition)));
|
||||
// TODO: priority should probably not override the distance -> use values instead of booleans
|
||||
@@ -695,11 +708,11 @@ namespace Barotrauma
|
||||
|
||||
//check if there's a wall between the target and the Character
|
||||
Vector2 rayStart = Character.SimPosition;
|
||||
Vector2 rayEnd = selectedAiTarget.SimPosition;
|
||||
Vector2 rayEnd = SelectedAiTarget.SimPosition;
|
||||
|
||||
if (selectedAiTarget.Entity.Submarine != null && Character.Submarine == null)
|
||||
if (SelectedAiTarget.Entity.Submarine != null && Character.Submarine == null)
|
||||
{
|
||||
rayStart -= ConvertUnits.ToSimUnits(selectedAiTarget.Entity.Submarine.Position);
|
||||
rayStart -= ConvertUnits.ToSimUnits(SelectedAiTarget.Entity.Submarine.Position);
|
||||
}
|
||||
|
||||
Body closestBody = Submarine.CheckVisibility(rayStart, rayEnd);
|
||||
@@ -795,7 +808,7 @@ namespace Barotrauma
|
||||
|
||||
private void UpdateLimbAttack(float deltaTime, Limb limb, Vector2 attackPosition, float distance = -1)
|
||||
{
|
||||
var damageTarget = wallTarget != null ? wallTarget.Structure : selectedAiTarget.Entity as IDamageable;
|
||||
var damageTarget = wallTarget != null ? wallTarget.Structure : SelectedAiTarget.Entity as IDamageable;
|
||||
if (damageTarget == null) return;
|
||||
|
||||
float prevHealth = damageTarget.Health;
|
||||
@@ -834,7 +847,7 @@ namespace Barotrauma
|
||||
|
||||
private void UpdateEating(float deltaTime)
|
||||
{
|
||||
if (selectedAiTarget == null)
|
||||
if (SelectedAiTarget == null)
|
||||
{
|
||||
State = AIState.Idle;
|
||||
return;
|
||||
@@ -850,13 +863,13 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
Vector2 mouthPos = Character.AnimController.GetMouthPosition().Value;
|
||||
Vector2 attackSimPosition = Character.Submarine == null ? ConvertUnits.ToSimUnits(selectedAiTarget.WorldPosition) : selectedAiTarget.SimPosition;
|
||||
Vector2 attackSimPosition = Character.Submarine == null ? ConvertUnits.ToSimUnits(SelectedAiTarget.WorldPosition) : SelectedAiTarget.SimPosition;
|
||||
|
||||
Vector2 limbDiff = attackSimPosition - mouthPos;
|
||||
float limbDist = limbDiff.Length();
|
||||
if (limbDist < 2.0f)
|
||||
{
|
||||
Character.SelectCharacter(selectedAiTarget.Entity as Character);
|
||||
Character.SelectCharacter(SelectedAiTarget.Entity as Character);
|
||||
steeringManager.SteeringManual(deltaTime, limbDiff);
|
||||
Character.AnimController.Collider.ApplyForce(limbDiff * mouthLimb.Mass * 50.0f, mouthPos);
|
||||
}
|
||||
@@ -875,10 +888,8 @@ namespace Barotrauma
|
||||
//sight/hearing range
|
||||
public void UpdateTargets(Character character, out TargetingPriority targetingPriority)
|
||||
{
|
||||
var prevAiTarget = selectedAiTarget;
|
||||
|
||||
targetingPriority = null;
|
||||
selectedAiTarget = null;
|
||||
SelectedAiTarget = null;
|
||||
selectedTargetMemory = null;
|
||||
targetValue = 0.0f;
|
||||
|
||||
@@ -936,11 +947,9 @@ namespace Barotrauma
|
||||
//skip the target if it's a room and the character is already inside a sub
|
||||
if (character.CurrentHull != null && target.Entity is Hull) continue;
|
||||
|
||||
//multiply the priority of the target if it's a door from outside to inside and the AI is an aggressive boarder
|
||||
Door door = null;
|
||||
if (target.Entity is Item item)
|
||||
{
|
||||
|
||||
//item inside and we're outside -> attack the hull
|
||||
if (item.CurrentHull != null && character.CurrentHull == null)
|
||||
{
|
||||
@@ -963,21 +972,20 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
if (door != null)
|
||||
{
|
||||
{
|
||||
//increase priority if the character is outside and an aggressive boarder, and the door is from outside to inside
|
||||
if (character.CurrentHull == null && aggressiveBoarding && !door.LinkedGap.IsRoomToRoom)
|
||||
{
|
||||
valueModifier = door.IsOpen ? 5.0f : 3.0f;
|
||||
valueModifier = door.IsOpen ? 10 : 5;
|
||||
}
|
||||
else if (door.IsOpen || door.Item.Condition <= 0.0f) //ignore broken and open doors
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else
|
||||
else if (target.Entity is IDamageable targetDamageable && targetDamageable.Health <= 0.0f)
|
||||
{
|
||||
IDamageable targetDamageable = target.Entity as IDamageable;
|
||||
if (targetDamageable != null && targetDamageable.Health <= 0.0f) continue;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -988,7 +996,8 @@ namespace Barotrauma
|
||||
|
||||
if (valueModifier == 0.0f) continue;
|
||||
|
||||
dist = Vector2.Distance(character.WorldPosition, target.WorldPosition);
|
||||
Vector2 toTarget = target.WorldPosition - character.WorldPosition;
|
||||
dist = toTarget.Length();
|
||||
|
||||
//if the target has been within range earlier, the character will notice it more easily
|
||||
//(i.e. remember where the target was)
|
||||
@@ -1003,21 +1012,27 @@ namespace Barotrauma
|
||||
dist = Math.Max(dist, 100.0f);
|
||||
|
||||
AITargetMemory targetMemory = FindTargetMemory(target);
|
||||
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.
|
||||
dist *= 3;
|
||||
}
|
||||
valueModifier = valueModifier * targetMemory.Priority / (float)Math.Sqrt(dist);
|
||||
|
||||
if (valueModifier > targetValue)
|
||||
{
|
||||
selectedAiTarget = target;
|
||||
SelectedAiTarget = target;
|
||||
selectedTargetMemory = targetMemory;
|
||||
targetingPriority = targetingPriorities[targetingTag];
|
||||
targetValue = valueModifier;
|
||||
}
|
||||
}
|
||||
|
||||
if (selectedAiTarget != prevAiTarget)
|
||||
if (SelectedAiTarget != _previousAiTarget)
|
||||
{
|
||||
wallTarget = null;
|
||||
}
|
||||
}
|
||||
_previousAiTarget = SelectedAiTarget;
|
||||
}
|
||||
|
||||
//find the targetMemory that corresponds to some AItarget or create if there isn't one yet
|
||||
|
||||
@@ -57,7 +57,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (DisableCrewAI || Character.IsUnconscious) return;
|
||||
|
||||
if (Character.Submarine != null || selectedAiTarget?.Entity?.Submarine != null)
|
||||
if (Character.Submarine != null || SelectedAiTarget?.Entity?.Submarine != null)
|
||||
{
|
||||
if (steeringManager != insideSteering) insideSteering.Reset();
|
||||
steeringManager = insideSteering;
|
||||
@@ -122,9 +122,11 @@ namespace Barotrauma
|
||||
//apply speed multiplier if
|
||||
// a. it's boosting the movement speed and the character is trying to move fast (= running)
|
||||
// b. it's a debuff that decreases movement speed
|
||||
if (run || Character.SpeedMultiplier <= 0.0f) targetMovement *= Character.SpeedMultiplier;
|
||||
|
||||
Character.SpeedMultiplier = 1.0f; // Reset, items will set the value before the next update
|
||||
|
||||
float speedMultiplier = Character.SpeedMultiplier;
|
||||
if (run || speedMultiplier <= 0.0f) targetMovement *= speedMultiplier;
|
||||
|
||||
Character.ResetSpeedMultiplier(); // Reset, items will set the value before the next update
|
||||
|
||||
Character.AnimController.TargetMovement = targetMovement;
|
||||
}
|
||||
@@ -225,6 +227,8 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
partial void ReportProblems();
|
||||
|
||||
private void UpdateSpeaking()
|
||||
{
|
||||
if (Character.Oxygen < 20.0f)
|
||||
@@ -277,7 +281,23 @@ namespace Barotrauma
|
||||
|
||||
public override void SelectTarget(AITarget target)
|
||||
{
|
||||
selectedAiTarget = target;
|
||||
SelectedAiTarget = target;
|
||||
}
|
||||
|
||||
private void CheckCrouching(float deltaTime)
|
||||
{
|
||||
crouchRaycastTimer -= deltaTime;
|
||||
if (crouchRaycastTimer > 0.0f) return;
|
||||
|
||||
crouchRaycastTimer = CrouchRaycastInterval;
|
||||
|
||||
//start the raycast in front of the character in the direction it's heading to
|
||||
Vector2 startPos = Character.SimPosition;
|
||||
startPos.X += MathHelper.Clamp(Character.AnimController.TargetMovement.X, -1.0f, 1.0f);
|
||||
|
||||
//do a raycast upwards to find any walls
|
||||
float minCeilingDist = Character.AnimController.Collider.height / 2 + Character.AnimController.Collider.radius + 0.1f;
|
||||
shouldCrouch = Submarine.PickBody(startPos, startPos + Vector2.UnitY * minCeilingDist, null, Physics.CollisionWall) != null;
|
||||
}
|
||||
|
||||
private void CheckCrouching(float deltaTime)
|
||||
|
||||
@@ -18,6 +18,10 @@ namespace Barotrauma
|
||||
|
||||
private float findPathTimer;
|
||||
|
||||
private float buttonPressCooldown;
|
||||
|
||||
const float ButtonPressInterval = 0.5f;
|
||||
|
||||
public SteeringPath CurrentPath
|
||||
{
|
||||
get { return currentPath; }
|
||||
@@ -57,6 +61,7 @@ namespace Barotrauma
|
||||
{
|
||||
base.Update(speed);
|
||||
|
||||
buttonPressCooldown -= 1.0f / 60.0f;
|
||||
findPathTimer -= 1.0f / 60.0f;
|
||||
}
|
||||
|
||||
@@ -127,7 +132,7 @@ namespace Barotrauma
|
||||
return currentTarget - pos2;
|
||||
}
|
||||
|
||||
if (canOpenDoors && !character.LockHands)
|
||||
if (canOpenDoors && !character.LockHands && buttonPressCooldown <= 0.0f)
|
||||
{
|
||||
CheckDoorsInPath();
|
||||
}
|
||||
@@ -311,6 +316,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
closestButton.Item.TryInteract(character, false, true, false);
|
||||
buttonPressCooldown = ButtonPressInterval;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -236,7 +236,11 @@ namespace Barotrauma
|
||||
foreach (Character potentialSpeaker in availableSpeakers)
|
||||
{
|
||||
//check if the character has an appropriate job to say the line
|
||||
if (selectedConversation.AllowedJobs.Count > 0 && !selectedConversation.AllowedJobs.Contains(potentialSpeaker.Info?.Job.Prefab)) continue;
|
||||
if ((potentialSpeaker.Info?.Job != null && potentialSpeaker.Info.Job.Prefab.OnlyJobSpecificDialog) ||
|
||||
selectedConversation.AllowedJobs.Count > 0)
|
||||
{
|
||||
if (!selectedConversation.AllowedJobs.Contains(potentialSpeaker.Info?.Job.Prefab)) continue;
|
||||
}
|
||||
|
||||
//check if the character has all required flags to say the line
|
||||
if (!ignoreFlags)
|
||||
@@ -310,6 +314,81 @@ namespace Barotrauma
|
||||
|
||||
return 1.0f - 1.0f / (index + 1);
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
public static void WriteToCSV()
|
||||
{
|
||||
System.Text.StringBuilder sb = new System.Text.StringBuilder();
|
||||
|
||||
for (int i = 0; i < list.Count; i++)
|
||||
{
|
||||
NPCConversation current = list[i];
|
||||
WriteConversation(sb, current, 0);
|
||||
WriteSubConversations(sb, current.Responses, 1);
|
||||
WriteEmptyRow(sb);
|
||||
}
|
||||
|
||||
StreamWriter file = new StreamWriter(@"NPCConversations.csv");
|
||||
file.WriteLine(sb.ToString());
|
||||
file.Close();
|
||||
}
|
||||
|
||||
private static void WriteConversation(System.Text.StringBuilder sb, NPCConversation conv, int depthIndex)
|
||||
{
|
||||
sb.Append(conv.speakerIndex); // Speaker index
|
||||
sb.Append('*');
|
||||
sb.Append(depthIndex); // Depth index
|
||||
sb.Append('*');
|
||||
sb.Append(conv.Line); // Original
|
||||
sb.Append('*');
|
||||
// Translated
|
||||
sb.Append('*');
|
||||
sb.Append(string.Join(",", conv.Flags)); // Flags
|
||||
sb.Append('*');
|
||||
|
||||
for (int i = 0; i < conv.AllowedJobs.Count; i++) // Jobs
|
||||
{
|
||||
sb.Append(conv.AllowedJobs[i].Identifier);
|
||||
|
||||
if (i < conv.AllowedJobs.Count - 1)
|
||||
{
|
||||
sb.Append(",");
|
||||
}
|
||||
}
|
||||
|
||||
sb.Append('*');
|
||||
sb.Append(string.Join(",", conv.allowedSpeakerTags)); // Traits
|
||||
sb.Append('*');
|
||||
sb.Append(conv.minIntensity); // Minimum intensity
|
||||
sb.Append('*');
|
||||
sb.Append(conv.maxIntensity); // Maximum intensity
|
||||
sb.Append('*');
|
||||
// Comments
|
||||
sb.AppendLine();
|
||||
}
|
||||
|
||||
private static void WriteSubConversations(System.Text.StringBuilder sb, List<NPCConversation> responses, int depthIndex)
|
||||
{
|
||||
for (int i = 0; i < responses.Count; i++)
|
||||
{
|
||||
WriteConversation(sb, responses[i], depthIndex);
|
||||
|
||||
if (responses[i].Responses != null && responses[i].Responses.Count > 0)
|
||||
{
|
||||
WriteSubConversations(sb, responses[i].Responses, depthIndex + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void WriteEmptyRow(System.Text.StringBuilder sb)
|
||||
{
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
sb.Append('*');
|
||||
}
|
||||
sb.AppendLine();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -60,10 +60,10 @@ namespace Barotrauma
|
||||
weapon.GetComponent<RepairTool>() as ItemComponent;
|
||||
if (weaponComponent != null && weaponComponent.requiredItems.ContainsKey(RelatedItem.RelationType.Contained))
|
||||
{
|
||||
Item[] containedItems = weapon.ContainedItems;
|
||||
var containedItems = weapon.ContainedItems;
|
||||
foreach (RelatedItem requiredItem in weaponComponent.requiredItems[RelatedItem.RelationType.Contained])
|
||||
{
|
||||
Item containedItem = Array.Find(containedItems, it => it != null && it.Condition > 0.0f && requiredItem.MatchesItem(it));
|
||||
Item containedItem = containedItems.FirstOrDefault(it => it.Condition > 0.0f && requiredItem.MatchesItem(it));
|
||||
if (containedItem == null)
|
||||
{
|
||||
var newReloadWeaponObjective = new AIObjectiveContainItem(character, requiredItem.Identifiers, weapon.GetComponent<ItemContainer>());
|
||||
|
||||
-1
@@ -1,7 +1,6 @@
|
||||
using Barotrauma.Items.Components;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
|
||||
+2
-1
@@ -1,5 +1,6 @@
|
||||
using Barotrauma.Items.Components;
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -19,7 +20,7 @@ namespace Barotrauma
|
||||
var containedItems = character.Inventory.Items[i].ContainedItems;
|
||||
if (containedItems == null) continue;
|
||||
|
||||
var oxygenTank = Array.Find(containedItems, it => (it.Prefab.Identifier == "oxygentank" || it.HasTag("oxygensource")) && it.Condition > 0.0f);
|
||||
var oxygenTank = containedItems.FirstOrDefault(it => (it.Prefab.Identifier == "oxygentank" || it.HasTag("oxygensource")) && it.Condition > 0.0f);
|
||||
if (oxygenTank != null) return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using FarseerPhysics;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -60,7 +61,7 @@ namespace Barotrauma
|
||||
var containedItems = weldingTool.ContainedItems;
|
||||
if (containedItems == null) return;
|
||||
|
||||
var fuelTank = Array.Find(containedItems, i => i.HasTag("weldingfueltank") && i.Condition > 0.0f);
|
||||
var fuelTank = containedItems.FirstOrDefault(i => i.HasTag("weldingfueltank") && i.Condition > 0.0f);
|
||||
if (fuelTank == null)
|
||||
{
|
||||
AddSubObjective(new AIObjectiveContainItem(character, "weldingfueltank", weldingTool.GetComponent<ItemContainer>()));
|
||||
|
||||
@@ -108,16 +108,20 @@ namespace Barotrauma
|
||||
//not linked to a hull -> ignore
|
||||
if (gap.linkedTo.All(l => l == null)) { continue; }
|
||||
|
||||
if (character.TeamID == 0)
|
||||
if (character.TeamID == Character.TeamType.None)
|
||||
{
|
||||
//TODO: make sure the gap isn't in another sub (outpost, respawn shuttle...?)
|
||||
if (gap.Submarine == null) continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
//prevent characters from attempting to fix leaks in the enemy sub
|
||||
//team 1 plays in sub 0, team 2 in sub 1
|
||||
Submarine mySub = character.TeamID < 1 || character.TeamID > Submarine.MainSubs.Length ?
|
||||
Submarine.MainSub : Submarine.MainSubs[character.TeamID - 1];
|
||||
Submarine mySub = Submarine.MainSub;
|
||||
if (character.TeamID == Character.TeamType.Team2 && Submarine.MainSubs.Length > 1)
|
||||
{
|
||||
mySub = Submarine.MainSubs[1];
|
||||
}
|
||||
|
||||
if (gap.Submarine != mySub) continue;
|
||||
}
|
||||
|
||||
@@ -24,8 +24,9 @@ namespace Barotrauma
|
||||
|
||||
public override float GetPriority(AIObjectiveManager objectiveManager)
|
||||
{
|
||||
if (Target != null && Target.Removed) return 0.0f;
|
||||
if (IgnoreIfTargetDead && Target is Character character && character.IsDead) return 0.0f;
|
||||
if (FollowControlledCharacter && Character.Controlled == null) { return 0.0f; }
|
||||
if (Target != null && Target.Removed) { return 0.0f; }
|
||||
if (IgnoreIfTargetDead && Target is Character character && character.IsDead) { return 0.0f; }
|
||||
|
||||
if (objectiveManager.CurrentOrder == this)
|
||||
{
|
||||
@@ -39,15 +40,17 @@ namespace Barotrauma
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Target != null && Target.Removed) return false;
|
||||
if (FollowControlledCharacter && Character.Controlled == null) { return false; }
|
||||
|
||||
if (repeat || waitUntilPathUnreachable > 0.0f) return true;
|
||||
if (Target != null && Target.Removed) { return false; }
|
||||
|
||||
if (repeat || waitUntilPathUnreachable > 0.0f) { return true; }
|
||||
var pathSteering = character.AIController.SteeringManager as IndoorsSteeringManager;
|
||||
|
||||
//path doesn't exist (= hasn't been searched for yet), assume for now that the target is reachable
|
||||
if (pathSteering?.CurrentPath == null) return true;
|
||||
if (pathSteering?.CurrentPath == null) { return true; }
|
||||
|
||||
if (!AllowGoingOutside && pathSteering.CurrentPath.HasOutdoorsNodes) return false;
|
||||
if (!AllowGoingOutside && pathSteering.CurrentPath.HasOutdoorsNodes) { return false; }
|
||||
|
||||
return !pathSteering.CurrentPath.Unreachable;
|
||||
}
|
||||
@@ -55,6 +58,8 @@ namespace Barotrauma
|
||||
|
||||
public Entity Target { get; private set; }
|
||||
|
||||
public bool FollowControlledCharacter;
|
||||
|
||||
public AIObjectiveGoTo(Entity target, Character character, bool repeat = false, bool getDivingGearIfNeeded = true)
|
||||
: base (character, "")
|
||||
{
|
||||
@@ -78,12 +83,18 @@ namespace Barotrauma
|
||||
|
||||
protected override void Act(float deltaTime)
|
||||
{
|
||||
if (FollowControlledCharacter)
|
||||
{
|
||||
if (Character.Controlled == null) { return; }
|
||||
Target = Character.Controlled;
|
||||
}
|
||||
|
||||
if (Target == character)
|
||||
{
|
||||
character.AIController.SteeringManager.Reset();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
waitUntilPathUnreachable -= deltaTime;
|
||||
|
||||
if (character.SelectedConstruction != null && character.SelectedConstruction.GetComponent<Ladder>() == null)
|
||||
|
||||
@@ -132,7 +132,8 @@ namespace Barotrauma
|
||||
{
|
||||
CloseEnough = 1.5f,
|
||||
AllowGoingOutside = true,
|
||||
IgnoreIfTargetDead = true
|
||||
IgnoreIfTargetDead = true,
|
||||
FollowControlledCharacter = orderGiver == character
|
||||
};
|
||||
break;
|
||||
case "wait":
|
||||
|
||||
+1
-1
@@ -28,7 +28,7 @@ namespace Barotrauma
|
||||
|
||||
if (!repairablesFound) { return 0.0f; }
|
||||
|
||||
float priority = 100.0f - item.Condition;
|
||||
float priority = item.Prefab.Health - item.Condition;
|
||||
//vertical distance matters more than horizontal (climbing up/down is harder than moving horizontally)
|
||||
float dist =
|
||||
Math.Abs(character.WorldPosition.X - item.WorldPosition.X) +
|
||||
|
||||
+1
-1
@@ -48,7 +48,7 @@ namespace Barotrauma
|
||||
foreach (Item item in Item.ItemList)
|
||||
{
|
||||
//ignore items that are in full condition
|
||||
if (item.Condition >= 100.0f) continue;
|
||||
if (item.Condition >= item.Prefab.Health) continue;
|
||||
foreach (Repairable repairable in item.Repairables)
|
||||
{
|
||||
//ignore ones that are already fixed
|
||||
|
||||
@@ -194,7 +194,7 @@ namespace Barotrauma
|
||||
var targetLimb = targetCharacter.CharacterHealth.GetAfflictionLimb(affliction);
|
||||
|
||||
bool remove = false;
|
||||
foreach (ItemComponent ic in item.components)
|
||||
foreach (ItemComponent ic in item.Components)
|
||||
{
|
||||
if (!ic.HasRequiredContainedItems(addMessage: false)) continue;
|
||||
#if CLIENT
|
||||
|
||||
@@ -161,16 +161,16 @@ namespace Barotrauma
|
||||
return false;
|
||||
}
|
||||
|
||||
public string GetChatMessage(string targetCharacterName, string targetRoomName, string orderOption = "")
|
||||
public string GetChatMessage(string targetCharacterName, string targetRoomName, bool givingOrderToSelf, string orderOption = "")
|
||||
{
|
||||
orderOption = orderOption ?? "";
|
||||
|
||||
string messageTag = "OrderDialog." + AITag;
|
||||
string messageTag = (givingOrderToSelf && !TargetAllCharacters ? "OrderDialogSelf." : "OrderDialog.") + AITag;
|
||||
if (!string.IsNullOrEmpty(orderOption)) messageTag += "." + orderOption;
|
||||
|
||||
string msg = TextManager.Get(messageTag, true);
|
||||
if (msg == null) return "";
|
||||
|
||||
|
||||
if (targetCharacterName == null) targetCharacterName = "";
|
||||
if (targetRoomName == null) targetRoomName = "";
|
||||
return msg.Replace("[name]", targetCharacterName).Replace("[roomname]", targetRoomName);
|
||||
|
||||
Reference in New Issue
Block a user