38f1ddb...178a853: v0.8.9.1, removed content folder
This commit is contained in:
@@ -2,15 +2,17 @@
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class AIController : ISteerable
|
||||
abstract partial class AIController : ISteerable
|
||||
{
|
||||
public enum AIState { None, Attack, GoTo, Escape, Eat }
|
||||
public enum AIState { Idle, Attack, GoTo, Escape, Eat }
|
||||
|
||||
public bool Enabled;
|
||||
|
||||
public readonly Character Character;
|
||||
|
||||
protected AIState state;
|
||||
|
||||
private AIState state;
|
||||
|
||||
protected AITarget selectedAiTarget;
|
||||
|
||||
protected SteeringManager steeringManager;
|
||||
|
||||
@@ -24,7 +26,7 @@ namespace Barotrauma
|
||||
get { return Character.AnimController.TargetMovement; }
|
||||
set { Character.AnimController.TargetMovement = value; }
|
||||
}
|
||||
|
||||
|
||||
public Vector2 SimPosition
|
||||
{
|
||||
get { return Character.SimPosition; }
|
||||
@@ -40,10 +42,35 @@ namespace Barotrauma
|
||||
get { return Character.AnimController.Collider.LinearVelocity; }
|
||||
}
|
||||
|
||||
public virtual bool CanEnterSubmarine
|
||||
{
|
||||
get { return true; }
|
||||
}
|
||||
|
||||
public virtual bool CanFlip
|
||||
{
|
||||
get { return true; }
|
||||
}
|
||||
|
||||
public virtual AIObjectiveManager ObjectiveManager
|
||||
{
|
||||
get { return null; }
|
||||
}
|
||||
|
||||
public AITarget SelectedAiTarget
|
||||
{
|
||||
get { return selectedAiTarget; }
|
||||
}
|
||||
|
||||
public AIState State
|
||||
{
|
||||
get { return state; }
|
||||
set { state = value; }
|
||||
set
|
||||
{
|
||||
if (state == value) return;
|
||||
OnStateChanged(state, value);
|
||||
state = value;
|
||||
}
|
||||
}
|
||||
|
||||
public AIController (Character c)
|
||||
@@ -53,13 +80,13 @@ namespace Barotrauma
|
||||
Enabled = true;
|
||||
}
|
||||
|
||||
public virtual void OnAttacked(Character attacker, float amount) { }
|
||||
public virtual void OnAttacked(Character attacker, AttackResult attackResult) { }
|
||||
|
||||
public virtual void SelectTarget(AITarget target) { }
|
||||
|
||||
public virtual void Update(float deltaTime) { }
|
||||
|
||||
//protected Structure lastStructurePicked;
|
||||
|
||||
protected virtual void OnStateChanged(AIState from, AIState to) { }
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -20,15 +21,45 @@ namespace Barotrauma
|
||||
public float SoundRange
|
||||
{
|
||||
get { return soundRange; }
|
||||
set { soundRange = Math.Max(value, 0.0f); }
|
||||
set { soundRange = Math.Max(value, MinSoundRange); }
|
||||
}
|
||||
|
||||
public float SightRange
|
||||
{
|
||||
get { return sightRange; }
|
||||
set { sightRange = Math.Max(value, 0.0f); }
|
||||
set { sightRange = Math.Max(value, MinSightRange); }
|
||||
}
|
||||
|
||||
private float sectorRad = MathHelper.TwoPi;
|
||||
public float SectorDegrees
|
||||
{
|
||||
get { return MathHelper.ToDegrees(sectorRad); }
|
||||
set { sectorRad = MathHelper.ToRadians(value); }
|
||||
}
|
||||
|
||||
private Vector2 sectorDir;
|
||||
public Vector2 SectorDir
|
||||
{
|
||||
get { return sectorDir; }
|
||||
set
|
||||
{
|
||||
if (!MathUtils.IsValid(value))
|
||||
{
|
||||
string errorMsg = "Invalid AITarget sector direction (" + value + ")\n" + Environment.StackTrace;
|
||||
DebugConsole.ThrowError(errorMsg);
|
||||
GameAnalyticsManager.AddErrorEventOnce("AITarget.SectorDir:" + Entity?.ToString(), GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
|
||||
return;
|
||||
}
|
||||
sectorDir = value;
|
||||
}
|
||||
}
|
||||
|
||||
public string SonarLabel;
|
||||
|
||||
public bool Enabled = true;
|
||||
|
||||
public float MinSoundRange, MinSightRange;
|
||||
|
||||
public Vector2 WorldPosition
|
||||
{
|
||||
get
|
||||
@@ -67,12 +98,27 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public AITarget(Entity e, XElement element) : this(e)
|
||||
{
|
||||
SightRange = MinSightRange = element.GetAttributeFloat("sightrange", 0.0f);
|
||||
SoundRange = MinSoundRange = element.GetAttributeFloat("soundrange", 0.0f);
|
||||
SonarLabel = element.GetAttributeString("sonarlabel", "");
|
||||
}
|
||||
|
||||
public AITarget(Entity e)
|
||||
{
|
||||
Entity = e;
|
||||
List.Add(this);
|
||||
}
|
||||
|
||||
public bool IsWithinSector(Vector2 worldPosition)
|
||||
{
|
||||
if (sectorRad >= MathHelper.TwoPi) return true;
|
||||
|
||||
Vector2 diff = worldPosition - WorldPosition;
|
||||
return MathUtils.GetShortestAngle(MathUtils.VectorToAngle(diff), MathUtils.VectorToAngle(sectorDir)) <= sectorRad * 0.5f;
|
||||
}
|
||||
|
||||
public void Remove()
|
||||
{
|
||||
List.Remove(this);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,7 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using Barotrauma.Networking;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -11,10 +13,19 @@ namespace Barotrauma
|
||||
|
||||
private AIObjectiveManager objectiveManager;
|
||||
|
||||
private AITarget selectedAiTarget;
|
||||
|
||||
private float updateObjectiveTimer;
|
||||
|
||||
private bool shouldCrouch;
|
||||
private float crouchRaycastTimer;
|
||||
const float CrouchRaycastInterval = 1.0f;
|
||||
|
||||
private SteeringManager outsideSteering, insideSteering;
|
||||
|
||||
public override AIObjectiveManager ObjectiveManager
|
||||
{
|
||||
get { return objectiveManager; }
|
||||
}
|
||||
|
||||
public Order CurrentOrder
|
||||
{
|
||||
get;
|
||||
@@ -29,7 +40,8 @@ namespace Barotrauma
|
||||
|
||||
public HumanAIController(Character c) : base(c)
|
||||
{
|
||||
steeringManager = new IndoorsSteeringManager(this, true);
|
||||
insideSteering = new IndoorsSteeringManager(this, true, false);
|
||||
outsideSteering = new SteeringManager(this);
|
||||
|
||||
objectiveManager = new AIObjectiveManager(c);
|
||||
objectiveManager.AddObjective(new AIObjectiveFindSafety(c));
|
||||
@@ -44,12 +56,23 @@ namespace Barotrauma
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
if (DisableCrewAI || Character.IsUnconscious) return;
|
||||
|
||||
if (Character.Submarine != null || selectedAiTarget?.Entity?.Submarine != null)
|
||||
{
|
||||
if (steeringManager != insideSteering) insideSteering.Reset();
|
||||
steeringManager = insideSteering;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (steeringManager != outsideSteering) outsideSteering.Reset();
|
||||
steeringManager = outsideSteering;
|
||||
}
|
||||
|
||||
(Character.AnimController as HumanoidAnimController).Crouching = shouldCrouch;
|
||||
CheckCrouching(deltaTime);
|
||||
Character.ClearInputs();
|
||||
|
||||
//steeringManager = Character.AnimController.CurrentHull == null ? outdoorsSteeringManager : indoorsSteeringManager;
|
||||
|
||||
if (updateObjectiveTimer>0.0f)
|
||||
|
||||
if (updateObjectiveTimer > 0.0f)
|
||||
{
|
||||
updateObjectiveTimer -= deltaTime;
|
||||
}
|
||||
@@ -59,22 +82,23 @@ namespace Barotrauma
|
||||
updateObjectiveTimer = UpdateObjectiveInterval;
|
||||
}
|
||||
|
||||
if (Character.SpeechImpediment < 100.0f)
|
||||
{
|
||||
ReportProblems();
|
||||
UpdateSpeaking();
|
||||
}
|
||||
|
||||
objectiveManager.DoCurrentObjective(deltaTime);
|
||||
|
||||
float currObjectivePriority = objectiveManager.GetCurrentPriority(Character);
|
||||
float moveSpeed = 1.0f;
|
||||
float currObjectivePriority = objectiveManager.GetCurrentPriority();
|
||||
|
||||
if (currObjectivePriority > 30.0f)
|
||||
{
|
||||
moveSpeed *= Character.AnimController.InWater ? Character.AnimController.SwimSpeedMultiplier : Character.AnimController.RunSpeedMultiplier;
|
||||
}
|
||||
|
||||
steeringManager.Update(moveSpeed);
|
||||
bool run = currObjectivePriority > 30.0f;
|
||||
steeringManager.Update(Character.AnimController.GetCurrentSpeed(run));
|
||||
|
||||
bool ignorePlatforms = Character.AnimController.TargetMovement.Y < -0.5f &&
|
||||
(-Character.AnimController.TargetMovement.Y > Math.Abs(Character.AnimController.TargetMovement.X));
|
||||
|
||||
var currPath = (steeringManager as IndoorsSteeringManager).CurrentPath;
|
||||
var currPath = (steeringManager as IndoorsSteeringManager)?.CurrentPath;
|
||||
if (currPath != null && currPath.CurrentNode != null)
|
||||
{
|
||||
if (currPath.CurrentNode.SimPosition.Y < Character.AnimController.GetColliderBottom().Y)
|
||||
@@ -84,22 +108,34 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
Character.AnimController.IgnorePlatforms = ignorePlatforms;
|
||||
(Character.AnimController as HumanoidAnimController).Crouching = false;
|
||||
|
||||
if (!Character.AnimController.InWater)
|
||||
{
|
||||
Character.AnimController.TargetMovement = new Vector2(
|
||||
Vector2 targetMovement = new Vector2(
|
||||
Character.AnimController.TargetMovement.X,
|
||||
MathHelper.Clamp(Character.AnimController.TargetMovement.Y, -1.0f, 1.0f)) * Character.SpeedMultiplier;
|
||||
MathHelper.Clamp(Character.AnimController.TargetMovement.Y, -1.0f, 1.0f));
|
||||
|
||||
Character.SpeedMultiplier = 1.0f;
|
||||
float maxSpeed = Character.GetCurrentMaxSpeed(run);
|
||||
targetMovement.X = MathHelper.Clamp(targetMovement.X, -maxSpeed, maxSpeed);
|
||||
targetMovement.Y = MathHelper.Clamp(targetMovement.Y, -maxSpeed, maxSpeed);
|
||||
|
||||
//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
|
||||
|
||||
Character.AnimController.TargetMovement = targetMovement;
|
||||
}
|
||||
|
||||
if (Character.SelectedConstruction != null && Character.SelectedConstruction.GetComponent<Items.Components.Ladder>()!=null)
|
||||
if (Character.AnimController.Anim == AnimController.Animation.Climbing &&
|
||||
Character.SelectedConstruction != null &&
|
||||
Character.SelectedConstruction.GetComponent<Items.Components.Ladder>() != null)
|
||||
{
|
||||
if (currPath != null && currPath.CurrentNode != null && currPath.CurrentNode.Ladders != null)
|
||||
{
|
||||
Character.AnimController.TargetMovement = new Vector2( 0.0f, Math.Sign(Character.AnimController.TargetMovement.Y));
|
||||
Character.AnimController.TargetMovement = new Vector2(0.0f, Math.Sign(Character.AnimController.TargetMovement.Y));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,7 +148,7 @@ namespace Barotrauma
|
||||
//-> take the suit off
|
||||
if (canTakeOffSuit && (Character.Oxygen < 50.0f || objectiveManager.CurrentObjective is AIObjectiveIdle))
|
||||
{
|
||||
var divingSuit = Character.Inventory.FindItem("Diving Suit");
|
||||
var divingSuit = Character.Inventory.FindItemByIdentifier("divingsuit") ?? Character.Inventory.FindItemByTag("divingsuit");
|
||||
if (divingSuit != null) divingSuit.Drop(Character);
|
||||
}
|
||||
|
||||
@@ -136,27 +172,104 @@ namespace Barotrauma
|
||||
Character.AnimController.TargetDir = Character.AnimController.TargetMovement.X > 0.0f ? Direction.Right : Direction.Left;
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnAttacked(Character attacker, float amount)
|
||||
|
||||
private void ReportProblems()
|
||||
{
|
||||
if (amount <= 0.0f) return;
|
||||
if (GameMain.Client != null) return;
|
||||
|
||||
var enemy = attacker as Character;
|
||||
if (enemy == null || enemy == Character) return;
|
||||
Order newOrder = null;
|
||||
if (Character.CurrentHull != null)
|
||||
{
|
||||
if (Character.CurrentHull.FireSources.Count > 0)
|
||||
{
|
||||
var orderPrefab = Order.PrefabList.Find(o => o.AITag == "reportfire");
|
||||
newOrder = new Order(orderPrefab, Character.CurrentHull, null);
|
||||
}
|
||||
|
||||
objectiveManager.AddObjective(new AIObjectiveCombat(Character, enemy));
|
||||
if (Character.CurrentHull.ConnectedGaps.Any(g => !g.IsRoomToRoom && g.ConnectedDoor == null && g.Open > 0.0f))
|
||||
{
|
||||
var orderPrefab = Order.PrefabList.Find(o => o.AITag == "reportbreach");
|
||||
newOrder = new Order(orderPrefab, Character.CurrentHull, null);
|
||||
}
|
||||
|
||||
//the objective in the manager is not necessarily the same as the one we just instantiated,
|
||||
//because the objective isn't added if there's already an identical objective in the manager
|
||||
var combatObjective = objectiveManager.GetObjective<AIObjectiveCombat>();
|
||||
combatObjective.MaxEnemyDamage = Math.Max(amount, combatObjective.MaxEnemyDamage);
|
||||
foreach (Character c in Character.CharacterList)
|
||||
{
|
||||
if (c.CurrentHull == Character.CurrentHull && !c.IsDead &&
|
||||
(c.AIController is EnemyAIController || c.TeamID != Character.TeamID))
|
||||
{
|
||||
var orderPrefab = Order.PrefabList.Find(o => o.AITag == "reportintruders");
|
||||
newOrder = new Order(orderPrefab, Character.CurrentHull, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Character.CurrentHull != null && (Character.Bleeding > 1.0f || Character.Vitality < Character.MaxVitality * 0.1f))
|
||||
{
|
||||
var orderPrefab = Order.PrefabList.Find(o => o.AITag == "requestfirstaid");
|
||||
newOrder = new Order(orderPrefab, Character.CurrentHull, null);
|
||||
}
|
||||
|
||||
if (newOrder != null)
|
||||
{
|
||||
if (GameMain.GameSession?.CrewManager != null && GameMain.GameSession.CrewManager.AddOrder(newOrder, newOrder.FadeOutTime))
|
||||
{
|
||||
Character.Speak(
|
||||
newOrder.GetChatMessage("", Character.CurrentHull?.RoomName), ChatMessageType.Order);
|
||||
|
||||
if (GameMain.Server != null)
|
||||
{
|
||||
OrderChatMessage msg = new OrderChatMessage(newOrder, "", Character.CurrentHull, null, Character);
|
||||
GameMain.Server.SendOrderChatMessage(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void SetOrder(Order order, string option)
|
||||
private void UpdateSpeaking()
|
||||
{
|
||||
if (Character.Oxygen < 20.0f)
|
||||
{
|
||||
Character.Speak(TextManager.Get("DialogLowOxygen"), null, 0, "lowoxygen", 30.0f);
|
||||
}
|
||||
|
||||
if (Character.Bleeding > 2.0f)
|
||||
{
|
||||
Character.Speak(TextManager.Get("DialogBleeding"), null, 0, "bleeding", 30.0f);
|
||||
}
|
||||
|
||||
if (Character.PressureTimer > 50.0f && Character.CurrentHull != null)
|
||||
{
|
||||
Character.Speak(TextManager.Get("DialogPressure").Replace("[roomname]", Character.CurrentHull.RoomName), null, 0, "pressure", 30.0f);
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnAttacked(Character attacker, AttackResult attackResult)
|
||||
{
|
||||
float totalDamage = attackResult.Damage;
|
||||
if (totalDamage <= 0.0f || attacker == null) return;
|
||||
|
||||
if (attacker.AnimController.Anim == AnimController.Animation.CPR && attacker.SelectedCharacter == Character)
|
||||
{
|
||||
// Don't attack characters that damage you while doing cpr, because let's assume that they are helping you.
|
||||
// Should not cancel any existing ai objectives (so that if the character attacked you and then helped, we still would want to retaliate).
|
||||
return;
|
||||
}
|
||||
|
||||
objectiveManager.AddObjective(new AIObjectiveCombat(Character, attacker), Rand.Range(0.5f, 1, Rand.RandSync.Unsynced), () =>
|
||||
{
|
||||
//the objective in the manager is not necessarily the same as the one we just instantiated,
|
||||
//because the objective isn't added if there's already an identical objective in the manager
|
||||
var combatObjective = objectiveManager.GetObjective<AIObjectiveCombat>();
|
||||
combatObjective.MaxEnemyDamage = Math.Max(totalDamage, combatObjective.MaxEnemyDamage);
|
||||
});
|
||||
}
|
||||
|
||||
public void SetOrder(Order order, string option, Character orderGiver, bool speak = true)
|
||||
{
|
||||
CurrentOrderOption = option;
|
||||
CurrentOrder = order;
|
||||
objectiveManager.SetOrder(order, option);
|
||||
objectiveManager.SetOrder(order, option, orderGiver);
|
||||
if (speak && Character.SpeechImpediment < 100.0f) Character.Speak(TextManager.Get("DialogAffirmative"), null, 1.0f);
|
||||
|
||||
SetOrderProjSpecific(order);
|
||||
}
|
||||
@@ -166,5 +279,21 @@ namespace Barotrauma
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace Barotrauma
|
||||
private PathFinder pathFinder;
|
||||
private SteeringPath currentPath;
|
||||
|
||||
private bool canOpenDoors;
|
||||
private bool canOpenDoors, canBreakDoors;
|
||||
|
||||
private Character character;
|
||||
|
||||
@@ -33,20 +33,27 @@ namespace Barotrauma
|
||||
get { return currentTarget; }
|
||||
}
|
||||
|
||||
public IndoorsSteeringManager(ISteerable host, bool canOpenDoors)
|
||||
public bool IsPathDirty
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public IndoorsSteeringManager(ISteerable host, bool canOpenDoors, bool canBreakDoors)
|
||||
: base(host)
|
||||
{
|
||||
pathFinder = new PathFinder(WayPoint.WayPointList.FindAll(wp => wp.SpawnType == SpawnType.Path), true);
|
||||
pathFinder.GetNodePenalty = GetNodePenalty;
|
||||
|
||||
this.canOpenDoors = canOpenDoors;
|
||||
this.canBreakDoors = canBreakDoors;
|
||||
|
||||
character = (host as AIController).Character;
|
||||
|
||||
findPathTimer = Rand.Range(0.0f, 1.0f);
|
||||
}
|
||||
|
||||
public override void Update(float speed = 1)
|
||||
public override void Update(float speed)
|
||||
{
|
||||
base.Update(speed);
|
||||
|
||||
@@ -58,16 +65,18 @@ namespace Barotrauma
|
||||
currentPath = path;
|
||||
if (path.Nodes.Any()) currentTarget = path.Nodes[path.Nodes.Count - 1].SimPosition;
|
||||
findPathTimer = 1.0f;
|
||||
IsPathDirty = false;
|
||||
}
|
||||
|
||||
|
||||
protected override Vector2 DoSteeringSeek(Vector2 target, float speed = 1)
|
||||
protected override Vector2 DoSteeringSeek(Vector2 target, float speed)
|
||||
{
|
||||
//find a new path if one hasn't been found yet or the target is different from the current target
|
||||
if (currentPath == null || Vector2.Distance(target, currentTarget) > 1.0f || findPathTimer < -1.0f)
|
||||
{
|
||||
if (findPathTimer > 0.0f) return Vector2.Zero;
|
||||
IsPathDirty = true;
|
||||
|
||||
if (findPathTimer > 0.0f) return Vector2.Zero;
|
||||
|
||||
currentTarget = target;
|
||||
Vector2 pos = host.SimPosition;
|
||||
if (character != null && character.Submarine == null)
|
||||
@@ -75,15 +84,16 @@ namespace Barotrauma
|
||||
var targetHull = Hull.FindHull(FarseerPhysics.ConvertUnits.ToDisplayUnits(target), null, false);
|
||||
if (targetHull != null && targetHull.Submarine != null)
|
||||
{
|
||||
pos -= targetHull.SimPosition;
|
||||
pos -= targetHull.Submarine.SimPosition;
|
||||
}
|
||||
}
|
||||
|
||||
currentPath = pathFinder.FindPath(pos, target);
|
||||
currentPath = pathFinder.FindPath(pos, target, "(Character: " + character.Name + ")");
|
||||
|
||||
findPathTimer = Rand.Range(1.0f, 1.2f);
|
||||
|
||||
return DiffToCurrentNode();
|
||||
IsPathDirty = false;
|
||||
return DiffToCurrentNode();
|
||||
}
|
||||
|
||||
Vector2 diff = DiffToCurrentNode();
|
||||
@@ -116,8 +126,11 @@ namespace Barotrauma
|
||||
}
|
||||
return currentTarget - pos2;
|
||||
}
|
||||
|
||||
if (canOpenDoors && !character.LockHands) CheckDoorsInPath();
|
||||
|
||||
if (canOpenDoors && !character.LockHands)
|
||||
{
|
||||
CheckDoorsInPath();
|
||||
}
|
||||
|
||||
Vector2 pos = host.SimPosition;
|
||||
|
||||
@@ -134,10 +147,12 @@ namespace Barotrauma
|
||||
pos -= FarseerPhysics.ConvertUnits.ToSimUnits(currentPath.CurrentNode.Submarine.Position-character.Submarine.Position);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (currentPath.CurrentNode != null && currentPath.CurrentNode.Ladders != null)
|
||||
//only humanoids can climb ladders
|
||||
if (currentPath.CurrentNode != null &&
|
||||
currentPath.CurrentNode.Ladders != null &&
|
||||
character.AnimController is HumanoidAnimController)
|
||||
{
|
||||
if (character.SelectedConstruction != currentPath.CurrentNode.Ladders.Item &&
|
||||
currentPath.CurrentNode.Ladders.Item.IsInsideTrigger(character.WorldPosition))
|
||||
@@ -158,9 +173,6 @@ namespace Barotrauma
|
||||
//at the same height as the waypoint
|
||||
if (Math.Abs(collider.SimPosition.Y - currentPath.CurrentNode.SimPosition.Y) < (collider.height / 2 + collider.radius) * 1.25f)
|
||||
{
|
||||
//climbing ladders -> don't move horizontally
|
||||
diff.X = 0.0f;
|
||||
|
||||
float heightFromFloor = character.AnimController.GetColliderBottom().Y - character.AnimController.FloorY;
|
||||
if (heightFromFloor <= 0.0f)
|
||||
{
|
||||
@@ -179,10 +191,29 @@ namespace Barotrauma
|
||||
currentPath.SkipToNextNode();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//if the current node is below the character and the next one is above (or vice versa)
|
||||
//and both are on ladders, we can skip directly to the next one
|
||||
|
||||
//e.g. no point in going down to reach the starting point of a path when we could go directly to the one above
|
||||
if (currentPath.CurrentNode.Ladders != null && currentPath.CurrentNode.Ladders == currentPath.NextNode?.Ladders &&
|
||||
Math.Sign(currentPath.CurrentNode.WorldPosition.Y - character.WorldPosition.Y) != Math.Sign(currentPath.NextNode.WorldPosition.Y - character.WorldPosition.Y))
|
||||
{
|
||||
currentPath.SkipToNextNode();
|
||||
}
|
||||
}
|
||||
|
||||
character.AnimController.IgnorePlatforms = false;
|
||||
return diff;
|
||||
}
|
||||
else if (character.AnimController.InWater)
|
||||
{
|
||||
if (Vector2.DistanceSquared(pos, currentPath.CurrentNode.SimPosition) < collider.radius * collider.radius)
|
||||
{
|
||||
currentPath.SkipToNextNode();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Vector2 colliderBottom = character.AnimController.GetColliderBottom();
|
||||
@@ -203,42 +234,54 @@ namespace Barotrauma
|
||||
{
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
WayPoint node = null;
|
||||
WayPoint nextNode = null;
|
||||
|
||||
if (i==0)
|
||||
{
|
||||
node = currentPath.CurrentNode;
|
||||
nextNode = currentPath.NextNode;
|
||||
}
|
||||
else
|
||||
{
|
||||
node = currentPath.PrevNode;
|
||||
nextNode = currentPath.CurrentNode;
|
||||
}
|
||||
|
||||
if (node == null || node.ConnectedGap == null || node.ConnectedGap.ConnectedDoor == null) continue;
|
||||
|
||||
if (nextNode == null) continue;
|
||||
|
||||
var door = node.ConnectedGap.ConnectedDoor;
|
||||
|
||||
Door door = null;
|
||||
bool shouldBeOpen = false;
|
||||
|
||||
if (door.LinkedGap.IsHorizontal)
|
||||
if (currentPath.Nodes.Count == 1)
|
||||
{
|
||||
int currentDir = Math.Sign(nextNode.WorldPosition.X - door.Item.WorldPosition.X);
|
||||
|
||||
shouldBeOpen = (door.Item.WorldPosition.X - character.WorldPosition.X) * currentDir > -50.0f;
|
||||
door = currentPath.Nodes.First().ConnectedDoor;
|
||||
shouldBeOpen = door != null;
|
||||
}
|
||||
else
|
||||
{
|
||||
int currentDir = Math.Sign(nextNode.WorldPosition.Y - door.Item.WorldPosition.Y);
|
||||
WayPoint node = null;
|
||||
WayPoint nextNode = null;
|
||||
if (i == 0)
|
||||
{
|
||||
node = currentPath.CurrentNode;
|
||||
nextNode = currentPath.NextNode;
|
||||
}
|
||||
else
|
||||
{
|
||||
node = currentPath.PrevNode;
|
||||
nextNode = currentPath.CurrentNode;
|
||||
}
|
||||
if (node?.ConnectedDoor == null) continue;
|
||||
|
||||
shouldBeOpen = (door.Item.WorldPosition.Y - character.WorldPosition.Y) * currentDir > -80.0f;
|
||||
if (nextNode == null)
|
||||
{
|
||||
//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;
|
||||
}
|
||||
else
|
||||
{
|
||||
door = node.ConnectedGap.ConnectedDoor;
|
||||
if (door.LinkedGap.IsHorizontal)
|
||||
{
|
||||
int currentDir = Math.Sign(nextNode.WorldPosition.X - door.Item.WorldPosition.X);
|
||||
shouldBeOpen = (door.Item.WorldPosition.X - character.WorldPosition.X) * currentDir > -50.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
int currentDir = Math.Sign(nextNode.WorldPosition.Y - door.Item.WorldPosition.Y);
|
||||
shouldBeOpen = (door.Item.WorldPosition.Y - character.WorldPosition.Y) * currentDir > -80.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (door == null) return;
|
||||
|
||||
//toggle the door if it's the previous node and open, or if it's current node and closed
|
||||
if (door.IsOpen != shouldBeOpen)
|
||||
{
|
||||
@@ -267,7 +310,7 @@ namespace Barotrauma
|
||||
return;
|
||||
}
|
||||
|
||||
closestButton.Item.TryInteract(character, false, true, true);
|
||||
closestButton.Item.TryInteract(character, false, true, false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -281,23 +324,37 @@ namespace Barotrauma
|
||||
float penalty = 0.0f;
|
||||
if (nextNode.Waypoint.ConnectedGap != null && nextNode.Waypoint.ConnectedGap.Open < 0.9f)
|
||||
{
|
||||
if (nextNode.Waypoint.ConnectedGap.ConnectedDoor == null)
|
||||
if (nextNode.Waypoint.ConnectedDoor == null)
|
||||
{
|
||||
penalty = 100.0f;
|
||||
}
|
||||
|
||||
//door closed and the character can't open doors -> node can't be traversed
|
||||
if (!canOpenDoors || character.LockHands) return null;
|
||||
|
||||
var doorButtons = nextNode.Waypoint.ConnectedGap.ConnectedDoor.Item.GetConnectedComponents<Controller>();
|
||||
if (!doorButtons.Any()) return null;
|
||||
|
||||
foreach (Controller button in doorButtons)
|
||||
if (!canBreakDoors)
|
||||
{
|
||||
if (Math.Sign(button.Item.Position.X - nextNode.Waypoint.Position.X) !=
|
||||
Math.Sign(node.Position.X - nextNode.Position.X)) continue;
|
||||
//door closed and the character can't open doors -> node can't be traversed
|
||||
if (!canOpenDoors || character.LockHands) return null;
|
||||
|
||||
if (!button.HasRequiredItems(character, false)) return null;
|
||||
var doorButtons = nextNode.Waypoint.ConnectedDoor.Item.GetConnectedComponents<Controller>();
|
||||
if (!doorButtons.Any()) return null;
|
||||
|
||||
foreach (Controller button in doorButtons)
|
||||
{
|
||||
if (Math.Sign(button.Item.Position.X - nextNode.Waypoint.Position.X) !=
|
||||
Math.Sign(node.Position.X - nextNode.Position.X)) continue;
|
||||
|
||||
if (!button.HasRequiredItems(character, false)) return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//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
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,296 @@
|
||||
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;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class LatchOntoAI
|
||||
{
|
||||
const float RaycastInterval = 5.0f;
|
||||
|
||||
private float raycastTimer;
|
||||
|
||||
private Body attachTargetBody;
|
||||
private Vector2 attachSurfaceNormal;
|
||||
private Submarine attachTargetSubmarine;
|
||||
|
||||
private bool attachToSub;
|
||||
private bool attachToWalls;
|
||||
|
||||
private float minDeattachSpeed = 3.0f, maxDeattachSpeed = 10.0f;
|
||||
private float damageOnDetach = 0.0f, detachStun = 0.0f;
|
||||
private float deattachTimer;
|
||||
|
||||
private Vector2 wallAttachPos;
|
||||
|
||||
private float attachCooldown;
|
||||
|
||||
private Limb attachLimb;
|
||||
private Vector2 localAttachPos;
|
||||
private float attachLimbRotation;
|
||||
|
||||
private float jointDir;
|
||||
|
||||
private List<WeldJoint> attachJoints = new List<WeldJoint>();
|
||||
|
||||
public List<WeldJoint> AttachJoints
|
||||
{
|
||||
get { return attachJoints; }
|
||||
}
|
||||
|
||||
public Vector2? WallAttachPos
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public bool IsAttached
|
||||
{
|
||||
get { return attachJoints.Count > 0; }
|
||||
}
|
||||
|
||||
public LatchOntoAI(XElement element, EnemyAIController enemyAI)
|
||||
{
|
||||
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);
|
||||
detachStun = element.GetAttributeFloat("detachstun", 0.0f);
|
||||
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))
|
||||
{
|
||||
attachLimb = enemyAI.Character.AnimController.GetLimb(attachLimbType);
|
||||
}
|
||||
if (attachLimb == null) attachLimb = enemyAI.Character.AnimController.MainLimb;
|
||||
|
||||
enemyAI.Character.OnDeath += OnCharacterDeath;
|
||||
}
|
||||
|
||||
public void SetAttachTarget(Body attachTarget, Submarine attachTargetSub, Vector2 attachPos, Vector2 attachSurfaceNormal)
|
||||
{
|
||||
attachTargetBody = attachTarget;
|
||||
attachTargetSubmarine = attachTargetSub;
|
||||
this.attachSurfaceNormal = attachSurfaceNormal;
|
||||
wallAttachPos = attachPos;
|
||||
}
|
||||
|
||||
public void Update(EnemyAIController enemyAI, float deltaTime)
|
||||
{
|
||||
Character character = enemyAI.Character;
|
||||
|
||||
if (character.Submarine != null)
|
||||
{
|
||||
DeattachFromBody();
|
||||
WallAttachPos = null;
|
||||
return;
|
||||
}
|
||||
if (attachJoints.Count > 0)
|
||||
{
|
||||
if (Math.Sign(attachLimb.Dir) != Math.Sign(jointDir))
|
||||
{
|
||||
attachJoints[0].LocalAnchorA =
|
||||
new Vector2(-attachJoints[0].LocalAnchorA.X, attachJoints[0].LocalAnchorA.Y);
|
||||
attachJoints[0].ReferenceAngle = -attachJoints[0].ReferenceAngle;
|
||||
jointDir = attachLimb.Dir;
|
||||
}
|
||||
for (int i = 0; i < attachJoints.Count; i++)
|
||||
{
|
||||
//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)
|
||||
{
|
||||
DebugConsole.ThrowError("Limb body of the character \"" + character.Name + "\" is very far from the attach joint anchor -> deattach");
|
||||
DeattachFromBody();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
attachCooldown -= deltaTime;
|
||||
deattachTimer -= deltaTime;
|
||||
|
||||
Vector2 transformedAttachPos = wallAttachPos;
|
||||
if (character.Submarine == null && attachTargetSubmarine != null)
|
||||
{
|
||||
transformedAttachPos += ConvertUnits.ToSimUnits(attachTargetSubmarine.Position);
|
||||
}
|
||||
if (transformedAttachPos != Vector2.Zero)
|
||||
{
|
||||
WallAttachPos = transformedAttachPos;
|
||||
}
|
||||
|
||||
switch (enemyAI.State)
|
||||
{
|
||||
case AIController.AIState.Idle:
|
||||
if (attachToWalls && character.Submarine == null && Level.Loaded != null)
|
||||
{
|
||||
raycastTimer -= deltaTime;
|
||||
//check if there are any walls nearby the character could attach to
|
||||
if (raycastTimer < 0.0f)
|
||||
{
|
||||
wallAttachPos = Vector2.Zero;
|
||||
|
||||
var cells = Level.Loaded.GetCells(character.WorldPosition, 1);
|
||||
if (cells.Count > 0)
|
||||
{
|
||||
foreach (Voronoi2.VoronoiCell cell in cells)
|
||||
{
|
||||
foreach (Voronoi2.GraphEdge edge in cell.Edges)
|
||||
{
|
||||
if (MathUtils.GetLineIntersection(edge.Point1, edge.Point2, character.WorldPosition, cell.Center, out Vector2 intersection))
|
||||
{
|
||||
attachSurfaceNormal = edge.GetNormal(cell);
|
||||
attachTargetBody = cell.Body;
|
||||
wallAttachPos = ConvertUnits.ToSimUnits(intersection);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (WallAttachPos != Vector2.Zero) break;
|
||||
}
|
||||
}
|
||||
raycastTimer = RaycastInterval;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
wallAttachPos = Vector2.Zero;
|
||||
}
|
||||
|
||||
if (wallAttachPos == Vector2.Zero)
|
||||
{
|
||||
DeattachFromBody();
|
||||
}
|
||||
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)
|
||||
{
|
||||
//close enough to a wall -> attach
|
||||
AttachToBody(character.AnimController.Collider, attachLimb, attachTargetBody, wallAttachPos);
|
||||
enemyAI.SteeringManager.Reset();
|
||||
}
|
||||
else
|
||||
{
|
||||
//move closer to the wall
|
||||
DeattachFromBody();
|
||||
enemyAI.SteeringManager.SteeringAvoid(deltaTime, 1.0f, character.AnimController.GetCurrentSpeed(false) * 0.1f);
|
||||
enemyAI.SteeringManager.SteeringSeek(wallAttachPos, character.AnimController.GetCurrentSpeed(true));
|
||||
}
|
||||
}
|
||||
break;
|
||||
case AIController.AIState.Attack:
|
||||
if (enemyAI.AttackingLimb != null)
|
||||
{
|
||||
if (attachToSub && wallAttachPos != Vector2.Zero && attachTargetBody != null)
|
||||
{
|
||||
// is not attached or is attached to something else
|
||||
if (!IsAttached || IsAttached && attachJoints[0].BodyB == attachTargetBody)
|
||||
{
|
||||
if (Vector2.DistanceSquared(ConvertUnits.ToDisplayUnits(transformedAttachPos), enemyAI.AttackingLimb.WorldPosition) < enemyAI.AttackingLimb.attack.Range * enemyAI.AttackingLimb.attack.Range)
|
||||
{
|
||||
AttachToBody(character.AnimController.Collider, attachLimb, attachTargetBody, transformedAttachPos);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
WallAttachPos = null;
|
||||
DeattachFromBody();
|
||||
break;
|
||||
}
|
||||
|
||||
if (attachTargetBody != null && deattachTimer < 0.0f)
|
||||
{
|
||||
Entity entity = attachTargetBody.UserData as Entity;
|
||||
Submarine attachedSub = entity is Submarine ? (Submarine)entity : entity?.Submarine;
|
||||
if (attachedSub != null)
|
||||
{
|
||||
float velocity = attachedSub.Velocity == Vector2.Zero ? 0.0f : attachedSub.Velocity.Length();
|
||||
float velocityFactor = (maxDeattachSpeed - minDeattachSpeed <= 0.0f) ?
|
||||
Math.Sign(Math.Abs(velocity) - minDeattachSpeed) :
|
||||
(Math.Abs(velocity) - minDeattachSpeed) / (maxDeattachSpeed - minDeattachSpeed);
|
||||
|
||||
if (Rand.Range(0.0f, 1.0f) < velocityFactor)
|
||||
{
|
||||
DeattachFromBody();
|
||||
character.AddDamage(character.WorldPosition, new List<Affliction>() { AfflictionPrefab.InternalDamage.Instantiate(damageOnDetach) }, detachStun, true);
|
||||
attachCooldown = 5.0f;
|
||||
}
|
||||
}
|
||||
|
||||
deattachTimer = 5.0f;
|
||||
}
|
||||
}
|
||||
|
||||
private void AttachToBody(PhysicsBody collider, Limb attachLimb, Body targetBody, Vector2 attachPos)
|
||||
{
|
||||
//already attached to something
|
||||
if (attachJoints.Count > 0)
|
||||
{
|
||||
//already attached to the target body, no need to do anything
|
||||
if (attachJoints[0].BodyB == targetBody) return;
|
||||
DeattachFromBody();
|
||||
}
|
||||
|
||||
jointDir = attachLimb.Dir;
|
||||
|
||||
Vector2 transformedLocalAttachPos = localAttachPos * attachLimb.character.AnimController.RagdollParams.LimbScale;
|
||||
if (jointDir < 0.0f) transformedLocalAttachPos.X = -transformedLocalAttachPos.X;
|
||||
|
||||
//transformedLocalAttachPos = Vector2.Transform(transformedLocalAttachPos, Matrix.CreateRotationZ(attachLimb.Rotation));
|
||||
|
||||
float angle = MathUtils.VectorToAngle(-attachSurfaceNormal) - MathHelper.PiOver2 + attachLimbRotation * attachLimb.Dir;
|
||||
attachLimb.body.SetTransform(attachPos + attachSurfaceNormal * transformedLocalAttachPos.Length(), angle);
|
||||
|
||||
var limbJoint = new WeldJoint(attachLimb.body.FarseerBody, targetBody,
|
||||
transformedLocalAttachPos, targetBody.GetLocalPoint(attachPos), false)
|
||||
{
|
||||
FrequencyHz = 10.0f,
|
||||
DampingRatio = 0.5f,
|
||||
KinematicBodyB = true,
|
||||
CollideConnected = false,
|
||||
};
|
||||
GameMain.World.AddJoint(limbJoint);
|
||||
attachJoints.Add(limbJoint);
|
||||
|
||||
// Limb scale is already taken into account when creating the collider.
|
||||
Vector2 colliderFront = collider.GetLocalFront();
|
||||
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)
|
||||
{
|
||||
FrequencyHz = 10.0f,
|
||||
DampingRatio = 0.5f,
|
||||
KinematicBodyB = true,
|
||||
CollideConnected = false,
|
||||
//Length = 0.1f
|
||||
};
|
||||
GameMain.World.AddJoint(colliderJoint);
|
||||
attachJoints.Add(colliderJoint);
|
||||
}
|
||||
|
||||
public void DeattachFromBody()
|
||||
{
|
||||
foreach (Joint joint in attachJoints)
|
||||
{
|
||||
GameMain.World.RemoveJoint(joint);
|
||||
}
|
||||
attachJoints.Clear();
|
||||
}
|
||||
|
||||
private void OnCharacterDeath(Character character, CauseOfDeath causeOfDeath)
|
||||
{
|
||||
DeattachFromBody();
|
||||
character.OnDeath -= OnCharacterDeath;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,315 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class NPCConversation
|
||||
{
|
||||
const int MaxPreviousConversations = 20;
|
||||
|
||||
private static List<NPCConversation> list = new List<NPCConversation>();
|
||||
|
||||
public readonly string Line;
|
||||
|
||||
public readonly List<JobPrefab> AllowedJobs;
|
||||
|
||||
public readonly List<string> Flags;
|
||||
|
||||
//The line can only be selected when eventmanager intensity is between these values
|
||||
//null = no restriction
|
||||
public float? maxIntensity, minIntensity;
|
||||
|
||||
public readonly List<NPCConversation> Responses;
|
||||
private readonly int speakerIndex;
|
||||
private readonly List<string> allowedSpeakerTags;
|
||||
public static void LoadAll(IEnumerable<string> filePaths)
|
||||
{
|
||||
//language, identifier, filepath
|
||||
List<Tuple<string, string, string>> contentPackageFiles = new List<Tuple<string, string, string>>();
|
||||
foreach (string filePath in filePaths)
|
||||
{
|
||||
XDocument doc = XMLExtensions.TryLoadXml(filePath);
|
||||
if (doc == null || doc.Root == null) continue;
|
||||
string language = doc.Root.GetAttributeString("Language", "English");
|
||||
string identifier = doc.Root.GetAttributeString("Identifier", "unknown");
|
||||
contentPackageFiles.Add(new Tuple<string, string, string>(language, identifier, filePath));
|
||||
}
|
||||
|
||||
List<Tuple<string, string, string>> translationFiles = new List<Tuple<string, string, string>>();
|
||||
foreach (string filePath in Directory.GetFiles(Path.Combine("Content", "NPCConversations")))
|
||||
{
|
||||
XDocument doc = XMLExtensions.TryLoadXml(filePath);
|
||||
if (doc == null || doc.Root == null) continue;
|
||||
string language = doc.Root.GetAttributeString("Language", "English");
|
||||
string identifier = doc.Root.GetAttributeString("Identifier", "unknown");
|
||||
translationFiles.Add(new Tuple<string, string, string>(language, identifier, filePath));
|
||||
}
|
||||
|
||||
|
||||
//get the languages and identifiers of the files
|
||||
for (int i = 0; i < contentPackageFiles.Count; i++)
|
||||
{
|
||||
var contentPackageFile = contentPackageFiles[i];
|
||||
//correct language, all good
|
||||
if (contentPackageFile.Item1 == TextManager.Language) continue;
|
||||
|
||||
//attempt to find a translation file with the correct language and a matching identifier
|
||||
//if it fails, we'll just use the original file with the incorrect language
|
||||
var translation = translationFiles.Find(t => t.Item1 == TextManager.Language && t.Item2 == contentPackageFile.Item2);
|
||||
if (translation != null) contentPackageFiles[i] = translation; //replace with the translation file
|
||||
}
|
||||
|
||||
foreach (var file in contentPackageFiles)
|
||||
{
|
||||
Load(file.Item3);
|
||||
}
|
||||
}
|
||||
|
||||
private static void Load(string file)
|
||||
{
|
||||
XDocument doc = XMLExtensions.TryLoadXml(file);
|
||||
if (doc == null || doc.Root == null) return;
|
||||
|
||||
string language = doc.Root.GetAttributeString("Language", "English");
|
||||
if (language != TextManager.Language) return;
|
||||
|
||||
foreach (XElement subElement in doc.Root.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "conversation":
|
||||
list.Add(new NPCConversation(subElement));
|
||||
break;
|
||||
case "personalitytrait":
|
||||
new NPCPersonalityTrait(subElement);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public NPCConversation(XElement element)
|
||||
{
|
||||
Line = element.GetAttributeString("line", "");
|
||||
|
||||
speakerIndex = element.GetAttributeInt("speaker", 0);
|
||||
|
||||
AllowedJobs = new List<JobPrefab>();
|
||||
string allowedJobsStr = element.GetAttributeString("allowedjobs", "");
|
||||
foreach (string allowedJobIdentifier in allowedJobsStr.Split(','))
|
||||
{
|
||||
var jobPrefab = JobPrefab.List.Find(jp => jp.Identifier.ToLowerInvariant() == allowedJobIdentifier.ToLowerInvariant());
|
||||
if (jobPrefab != null) AllowedJobs.Add(jobPrefab);
|
||||
}
|
||||
|
||||
Flags = new List<string>(element.GetAttributeStringArray("flags", new string[0]));
|
||||
|
||||
allowedSpeakerTags = new List<string>();
|
||||
string allowedSpeakerTagsStr = element.GetAttributeString("speakertags", "");
|
||||
foreach (string tag in allowedSpeakerTagsStr.Split(','))
|
||||
{
|
||||
if (string.IsNullOrEmpty(tag)) continue;
|
||||
allowedSpeakerTags.Add(tag.Trim().ToLowerInvariant());
|
||||
}
|
||||
|
||||
if (element.Attribute("minintensity") != null) minIntensity = element.GetAttributeFloat("minintensity", 0.0f);
|
||||
if (element.Attribute("maxintensity") != null) maxIntensity = element.GetAttributeFloat("maxintensity", 1.0f);
|
||||
|
||||
Responses = new List<NPCConversation>();
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
Responses.Add(new NPCConversation(subElement));
|
||||
}
|
||||
}
|
||||
|
||||
private static List<string> GetCurrentFlags(Character speaker)
|
||||
{
|
||||
var currentFlags = new List<string>();
|
||||
if (Submarine.MainSub != null && Submarine.MainSub.AtDamageDepth) currentFlags.Add("SubmarineDeep");
|
||||
if (GameMain.GameSession != null && Timing.TotalTime < GameMain.GameSession.RoundStartTime + 30.0f) currentFlags.Add("Initial");
|
||||
if (speaker != null)
|
||||
{
|
||||
if (speaker.AnimController.InWater) currentFlags.Add("Underwater");
|
||||
currentFlags.Add(speaker.CurrentHull == null ? "Outside" : "Inside");
|
||||
|
||||
if (Character.Controlled != null)
|
||||
{
|
||||
if (Character.Controlled.CharacterHealth.GetAffliction("psychosis") != null)
|
||||
{
|
||||
currentFlags.Add(speaker != Character.Controlled ? "Psychosis" : "PsychosisSelf");
|
||||
}
|
||||
}
|
||||
|
||||
var afflictions = speaker.CharacterHealth.GetAllAfflictions();
|
||||
foreach (Affliction affliction in afflictions)
|
||||
{
|
||||
var currentEffect = affliction.Prefab.GetActiveEffect(affliction.Strength);
|
||||
if (currentEffect != null && !string.IsNullOrEmpty(currentEffect.DialogFlag) && !currentFlags.Contains(currentEffect.DialogFlag))
|
||||
{
|
||||
currentFlags.Add(currentEffect.DialogFlag);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return currentFlags;
|
||||
}
|
||||
|
||||
private static List<NPCConversation> previousConversations = new List<NPCConversation>();
|
||||
|
||||
public static List<Pair<Character, string>> CreateRandom(List<Character> availableSpeakers)
|
||||
{
|
||||
Dictionary<int, Character> assignedSpeakers = new Dictionary<int, Character>();
|
||||
List<Pair<Character, string>> lines = new List<Pair<Character, string>>();
|
||||
|
||||
CreateConversation(availableSpeakers, assignedSpeakers, null, lines, availableConversations: list);
|
||||
return lines;
|
||||
}
|
||||
|
||||
public static List<Pair<Character, string>> CreateRandom(List<Character> availableSpeakers, List<string> requiredFlags)
|
||||
{
|
||||
Dictionary<int, Character> assignedSpeakers = new Dictionary<int, Character>();
|
||||
List<Pair<Character, string>> lines = new List<Pair<Character, string>>();
|
||||
var availableConversations = list.FindAll(conversation => requiredFlags.All(f => conversation.Flags.Contains(f)));
|
||||
if (availableConversations.Count > 0)
|
||||
{
|
||||
CreateConversation(availableSpeakers, assignedSpeakers, null, lines, availableConversations: availableConversations, ignoreFlags: true);
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
|
||||
private static void CreateConversation(
|
||||
List<Character> availableSpeakers,
|
||||
Dictionary<int, Character> assignedSpeakers,
|
||||
NPCConversation baseConversation,
|
||||
List<Pair<Character, string>> lineList,
|
||||
List<NPCConversation> availableConversations,
|
||||
bool ignoreFlags = false)
|
||||
{
|
||||
List<NPCConversation> conversations = baseConversation == null ? availableConversations : baseConversation.Responses;
|
||||
if (conversations.Count == 0) return;
|
||||
|
||||
int conversationIndex = Rand.Int(conversations.Count);
|
||||
NPCConversation selectedConversation = conversations[conversationIndex];
|
||||
if (string.IsNullOrEmpty(selectedConversation.Line)) return;
|
||||
|
||||
Character speaker = null;
|
||||
//speaker already assigned for this line
|
||||
if (assignedSpeakers.ContainsKey(selectedConversation.speakerIndex))
|
||||
{
|
||||
//check if the character has all required flags to say the line
|
||||
var characterFlags = GetCurrentFlags(assignedSpeakers[selectedConversation.speakerIndex]);
|
||||
if (selectedConversation.Flags.All(flag => characterFlags.Contains(flag)))
|
||||
{
|
||||
speaker = assignedSpeakers[selectedConversation.speakerIndex];
|
||||
}
|
||||
}
|
||||
if (speaker == null)
|
||||
{
|
||||
var allowedSpeakers = new List<Character>();
|
||||
|
||||
List<NPCConversation> potentialLines = new List<NPCConversation>(conversations);
|
||||
|
||||
//remove lines that are not appropriate for the intensity of the current situation
|
||||
if (GameMain.GameSession?.EventManager != null)
|
||||
{
|
||||
potentialLines.RemoveAll(l =>
|
||||
(l.minIntensity.HasValue && GameMain.GameSession.EventManager.CurrentIntensity < l.minIntensity) ||
|
||||
(l.maxIntensity.HasValue && GameMain.GameSession.EventManager.CurrentIntensity > l.maxIntensity));
|
||||
}
|
||||
|
||||
while (potentialLines.Count > 0)
|
||||
{
|
||||
//select a random line and attempt to find a speaker for it
|
||||
// and if no valid speaker is found, choose another random line
|
||||
selectedConversation = GetRandomConversation(potentialLines, baseConversation == null);
|
||||
if (selectedConversation == null || string.IsNullOrEmpty(selectedConversation.Line)) return;
|
||||
|
||||
//speaker already assigned for this line
|
||||
if (assignedSpeakers.ContainsKey(selectedConversation.speakerIndex))
|
||||
{
|
||||
speaker = assignedSpeakers[selectedConversation.speakerIndex];
|
||||
break;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
//check if the character has all required flags to say the line
|
||||
if (!ignoreFlags)
|
||||
{
|
||||
var characterFlags = GetCurrentFlags(potentialSpeaker);
|
||||
if (!selectedConversation.Flags.All(flag => characterFlags.Contains(flag))) continue;
|
||||
}
|
||||
|
||||
//check if the character has an appropriate personality
|
||||
if (selectedConversation.allowedSpeakerTags.Count > 0)
|
||||
{
|
||||
if (potentialSpeaker.Info?.PersonalityTrait == null) continue;
|
||||
if (!selectedConversation.allowedSpeakerTags.Any(t => potentialSpeaker.Info.PersonalityTrait.AllowedDialogTags.Any(t2 => t2 == t))) continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (potentialSpeaker.Info?.PersonalityTrait != null &&
|
||||
!potentialSpeaker.Info.PersonalityTrait.AllowedDialogTags.Contains("none"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
allowedSpeakers.Add(potentialSpeaker);
|
||||
}
|
||||
|
||||
if (allowedSpeakers.Count == 0)
|
||||
{
|
||||
potentialLines.Remove(selectedConversation);
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (allowedSpeakers.Count == 0) return;
|
||||
speaker = allowedSpeakers[Rand.Int(allowedSpeakers.Count)];
|
||||
availableSpeakers.Remove(speaker);
|
||||
assignedSpeakers.Add(selectedConversation.speakerIndex, speaker);
|
||||
}
|
||||
|
||||
if (baseConversation == null)
|
||||
{
|
||||
previousConversations.Insert(0, selectedConversation);
|
||||
if (previousConversations.Count > MaxPreviousConversations) previousConversations.RemoveAt(MaxPreviousConversations);
|
||||
}
|
||||
lineList.Add(new Pair<Character, string>(speaker, selectedConversation.Line));
|
||||
CreateConversation(availableSpeakers, assignedSpeakers, selectedConversation, lineList, availableConversations);
|
||||
}
|
||||
|
||||
private static NPCConversation GetRandomConversation(List<NPCConversation> conversations, bool avoidPreviouslyUsed)
|
||||
{
|
||||
if (!avoidPreviouslyUsed)
|
||||
{
|
||||
return conversations.Count == 0 ? null : conversations[Rand.Int(conversations.Count)];
|
||||
}
|
||||
|
||||
List<float> probabilities = new List<float>();
|
||||
foreach (NPCConversation conversation in conversations)
|
||||
{
|
||||
probabilities.Add(GetConversationProbability(conversation));
|
||||
}
|
||||
return ToolBox.SelectWeightedRandom(conversations, probabilities, Rand.RandSync.Unsynced);
|
||||
}
|
||||
|
||||
private static float GetConversationProbability(NPCConversation conversation)
|
||||
{
|
||||
int index = previousConversations.IndexOf(conversation);
|
||||
if (index < 0) return 10.0f;
|
||||
|
||||
return 1.0f - 1.0f / (index + 1);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -38,7 +38,7 @@ namespace Barotrauma
|
||||
/// </summary>
|
||||
public void TryComplete(float deltaTime)
|
||||
{
|
||||
subObjectives.RemoveAll(s => s.IsCompleted() || !s.CanBeCompleted);
|
||||
subObjectives.RemoveAll(s => s.IsCompleted() || !s.CanBeCompleted || ShouldInterruptSubObjective(s));
|
||||
|
||||
foreach (AIObjective objective in subObjectives)
|
||||
{
|
||||
@@ -66,6 +66,18 @@ namespace Barotrauma
|
||||
return currentSubObjective;
|
||||
}
|
||||
|
||||
public void SortSubObjectives(AIObjectiveManager objectiveManager)
|
||||
{
|
||||
if (!subObjectives.Any()) return;
|
||||
subObjectives.Sort((x, y) => y.GetPriority(objectiveManager).CompareTo(x.GetPriority(objectiveManager)));
|
||||
subObjectives[0].SortSubObjectives(objectiveManager);
|
||||
}
|
||||
|
||||
protected virtual bool ShouldInterruptSubObjective(AIObjective subObjective)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
protected abstract void Act(float deltaTime);
|
||||
|
||||
public abstract bool IsCompleted();
|
||||
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
using Barotrauma.Items.Components;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class AIObjectiveChargeBatteries : AIObjective
|
||||
{
|
||||
private List<PowerContainer> availableBatteries;
|
||||
|
||||
private string orderOption;
|
||||
|
||||
public AIObjectiveChargeBatteries(Character character, string option)
|
||||
: base(character, option)
|
||||
{
|
||||
orderOption = option;
|
||||
|
||||
availableBatteries = new List<PowerContainer>();
|
||||
foreach (Item item in Item.ItemList)
|
||||
{
|
||||
if (item.Submarine == null) continue;
|
||||
if (item.Prefab.Identifier != "battery" && !item.HasTag("battery")) continue;
|
||||
|
||||
var powerContainer = item.GetComponent<PowerContainer>();
|
||||
availableBatteries.Add(powerContainer);
|
||||
}
|
||||
|
||||
if (availableBatteries.Count == 0)
|
||||
{
|
||||
character?.Speak(TextManager.Get("DialogNoBatteries"), null, 4.0f, "nobatteries", 10.0f);
|
||||
}
|
||||
}
|
||||
|
||||
public override float GetPriority(AIObjectiveManager objectiveManager)
|
||||
{
|
||||
if (objectiveManager.CurrentOrder == this)
|
||||
{
|
||||
return AIObjectiveManager.OrderPriority;
|
||||
}
|
||||
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
public override bool IsCompleted()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public override bool IsDuplicate(AIObjective otherObjective)
|
||||
{
|
||||
return otherObjective is AIObjectiveChargeBatteries other && other.orderOption == orderOption;
|
||||
}
|
||||
|
||||
protected override void Act(float deltaTime)
|
||||
{
|
||||
if (availableBatteries.Count == 0)
|
||||
{
|
||||
AddSubObjective(new AIObjectiveIdle(character));
|
||||
return;
|
||||
}
|
||||
foreach (PowerContainer battery in availableBatteries)
|
||||
{
|
||||
AddSubObjective(new AIObjectiveOperateItem(battery, character, orderOption, false));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using Barotrauma.Items.Components;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
@@ -17,21 +18,13 @@ namespace Barotrauma
|
||||
|
||||
private AIObjectiveFindSafety escapeObjective;
|
||||
|
||||
float coolDownTimer;
|
||||
private AIObjectiveContainItem reloadWeaponObjective;
|
||||
|
||||
private readonly float enemyStrength;
|
||||
private float coolDownTimer;
|
||||
|
||||
public AIObjectiveCombat(Character character, Character enemy)
|
||||
: base(character, "")
|
||||
public AIObjectiveCombat(Character character, Character enemy) : base(character, "")
|
||||
{
|
||||
this.enemy = enemy;
|
||||
|
||||
foreach (Limb limb in enemy.AnimController.Limbs)
|
||||
{
|
||||
if (limb.attack == null) continue;
|
||||
enemyStrength += limb.attack.GetDamage(1.0f);
|
||||
}
|
||||
|
||||
coolDownTimer = CoolDown;
|
||||
}
|
||||
|
||||
@@ -39,26 +32,66 @@ namespace Barotrauma
|
||||
{
|
||||
coolDownTimer -= deltaTime;
|
||||
|
||||
var weapon = character.Inventory.FindItem("weapon");
|
||||
|
||||
var weapon = character.Inventory.FindItemByTag("weapon");
|
||||
if (weapon == null)
|
||||
{
|
||||
Escape(deltaTime);
|
||||
}
|
||||
else
|
||||
{
|
||||
//TODO: make sure the weapon is ready to use (projectiles/batteries loaded)
|
||||
if (!character.SelectedItems.Contains(weapon))
|
||||
{
|
||||
if (character.Inventory.TryPutItem(weapon, 3, false, false, character))
|
||||
if (character.Inventory.TryPutItem(weapon, 3, true, false, character))
|
||||
{
|
||||
weapon.Equip(character);
|
||||
}
|
||||
else
|
||||
{
|
||||
//couldn't equip the item, escape
|
||||
Escape(deltaTime);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
//make sure the weapon is loaded
|
||||
var weaponComponent =
|
||||
weapon.GetComponent<RangedWeapon>() as ItemComponent ??
|
||||
weapon.GetComponent<MeleeWeapon>() as ItemComponent ??
|
||||
weapon.GetComponent<RepairTool>() as ItemComponent;
|
||||
if (weaponComponent != null && weaponComponent.requiredItems.ContainsKey(RelatedItem.RelationType.Contained))
|
||||
{
|
||||
Item[] 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));
|
||||
if (containedItem == null)
|
||||
{
|
||||
var newReloadWeaponObjective = new AIObjectiveContainItem(character, requiredItem.Identifiers, weapon.GetComponent<ItemContainer>());
|
||||
if (!newReloadWeaponObjective.IsDuplicate(reloadWeaponObjective))
|
||||
{
|
||||
reloadWeaponObjective = newReloadWeaponObjective;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (reloadWeaponObjective != null)
|
||||
{
|
||||
if (reloadWeaponObjective.IsCompleted())
|
||||
{
|
||||
reloadWeaponObjective = null;
|
||||
}
|
||||
else if (!reloadWeaponObjective.CanBeCompleted)
|
||||
{
|
||||
Escape(deltaTime);
|
||||
}
|
||||
else
|
||||
{
|
||||
reloadWeaponObjective.TryComplete(deltaTime);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
character.CursorPosition = enemy.Position;
|
||||
character.SetInput(InputType.Aim, false, true);
|
||||
|
||||
@@ -116,7 +149,7 @@ namespace Barotrauma
|
||||
//clamp the strength to the health of this character
|
||||
//(it doesn't make a difference whether the enemy does 200 or 600 damage, it's one hit kill anyway)
|
||||
|
||||
float enemyDanger = Math.Min(Math.Max(enemyStrength, MaxEnemyDamage), character.Health) + enemy.Health / 10.0f;
|
||||
float enemyDanger = Math.Min(Math.Max(CalculateEnemyStrength(), MaxEnemyDamage), character.Health) + enemy.Health / 10.0f;
|
||||
|
||||
EnemyAIController enemyAI = enemy.AIController as EnemyAIController;
|
||||
if (enemyAI != null)
|
||||
@@ -134,5 +167,19 @@ namespace Barotrauma
|
||||
|
||||
return objective.enemy == enemy;
|
||||
}
|
||||
|
||||
private float CalculateEnemyStrength()
|
||||
{
|
||||
float enemyStrength = 0;
|
||||
AttackContext currentContext = character.GetAttackContext();
|
||||
foreach (Limb limb in enemy.AnimController.Limbs)
|
||||
{
|
||||
if (limb.attack == null) continue;
|
||||
if (!limb.attack.IsValidContext(currentContext)) { continue; }
|
||||
if (!limb.attack.IsValidTarget(AttackTarget.Character)) { continue; }
|
||||
enemyStrength += limb.attack.GetTotalDamage(false);
|
||||
}
|
||||
return enemyStrength;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+29
-15
@@ -9,7 +9,8 @@ namespace Barotrauma
|
||||
{
|
||||
public int MinContainedAmount = 1;
|
||||
|
||||
private string[] itemNames;
|
||||
//can either be a tag or an identifier
|
||||
private string[] itemIdentifiers;
|
||||
|
||||
private ItemContainer container;
|
||||
|
||||
@@ -21,16 +22,21 @@ namespace Barotrauma
|
||||
|
||||
private AIObjectiveGetItem getItemObjective;
|
||||
private AIObjectiveGoTo goToObjective;
|
||||
|
||||
public AIObjectiveContainItem(Character character, string itemName, ItemContainer container)
|
||||
: this(character, new string[] { itemName }, container)
|
||||
|
||||
public AIObjectiveContainItem(Character character, string itemIdentifier, ItemContainer container)
|
||||
: this(character, new string[] { itemIdentifier }, container)
|
||||
{
|
||||
}
|
||||
|
||||
public AIObjectiveContainItem(Character character, string[] itemNames, ItemContainer container)
|
||||
public AIObjectiveContainItem(Character character, string[] itemIdentifiers, ItemContainer container)
|
||||
: base (character, "")
|
||||
{
|
||||
this.itemNames = itemNames;
|
||||
this.itemIdentifiers = itemIdentifiers;
|
||||
for (int i = 0; i < itemIdentifiers.Length; i++)
|
||||
{
|
||||
itemIdentifiers[i] = itemIdentifiers[i].ToLowerInvariant();
|
||||
}
|
||||
|
||||
this.container = container;
|
||||
}
|
||||
|
||||
@@ -41,7 +47,7 @@ namespace Barotrauma
|
||||
int containedItemCount = 0;
|
||||
foreach (Item item in container.Inventory.Items)
|
||||
{
|
||||
if (item != null && itemNames.Any(name => item.Prefab.NameMatches(name) || item.HasTag(name))) containedItemCount++;
|
||||
if (item != null && itemIdentifiers.Any(id => item.Prefab.Identifier == id || item.HasTag(id))) containedItemCount++;
|
||||
}
|
||||
|
||||
return containedItemCount >= MinContainedAmount;
|
||||
@@ -56,7 +62,7 @@ namespace Barotrauma
|
||||
return goToObjective.CanBeCompleted;
|
||||
}
|
||||
|
||||
return getItemObjective == null || !getItemObjective.CanBeCompleted;
|
||||
return getItemObjective == null || getItemObjective.CanBeCompleted;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,12 +81,20 @@ namespace Barotrauma
|
||||
if (isCompleted) return;
|
||||
|
||||
//get the item that should be contained
|
||||
var itemToContain = character.Inventory.FindItem(itemNames);
|
||||
Item itemToContain = null;
|
||||
foreach (string identifier in itemIdentifiers)
|
||||
{
|
||||
itemToContain = character.Inventory.FindItemByIdentifier(identifier) ?? character.Inventory.FindItemByTag(identifier);
|
||||
if (itemToContain != null) break;
|
||||
}
|
||||
|
||||
if (itemToContain == null)
|
||||
{
|
||||
getItemObjective = new AIObjectiveGetItem(character, itemNames);
|
||||
getItemObjective.GetItemPriority = GetItemPriority;
|
||||
getItemObjective.IgnoreContainedItems = IgnoreAlreadyContainedItems;
|
||||
getItemObjective = new AIObjectiveGetItem(character, itemIdentifiers)
|
||||
{
|
||||
GetItemPriority = GetItemPriority,
|
||||
IgnoreContainedItems = IgnoreAlreadyContainedItems
|
||||
};
|
||||
AddSubObjective(getItemObjective);
|
||||
return;
|
||||
}
|
||||
@@ -116,11 +130,11 @@ namespace Barotrauma
|
||||
AIObjectiveContainItem objective = otherObjective as AIObjectiveContainItem;
|
||||
if (objective == null) return false;
|
||||
if (objective.container != container) return false;
|
||||
if (objective.itemNames.Length != itemNames.Length) return false;
|
||||
if (objective.itemIdentifiers.Length != itemIdentifiers.Length) return false;
|
||||
|
||||
for (int i = 0; i < itemNames.Length; i++)
|
||||
for (int i = 0; i < itemIdentifiers.Length; i++)
|
||||
{
|
||||
if (objective.itemNames[i] != itemNames[i]) return false;
|
||||
if (objective.itemIdentifiers[i] != itemIdentifiers[i]) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
using Barotrauma.Items.Components;
|
||||
using FarseerPhysics;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class AIObjectiveExtinguishFire : AIObjective
|
||||
{
|
||||
private Hull targetHull;
|
||||
|
||||
private AIObjectiveGetItem getExtinguisherObjective;
|
||||
|
||||
private AIObjectiveGoTo gotoObjective;
|
||||
|
||||
private float useExtinquisherTimer;
|
||||
|
||||
public AIObjectiveExtinguishFire(Character character, Hull targetHull) :
|
||||
base(character, "")
|
||||
{
|
||||
this.targetHull = targetHull;
|
||||
}
|
||||
|
||||
public override float GetPriority(AIObjectiveManager objectiveManager)
|
||||
{
|
||||
return targetHull.FireSources.Sum(fs => fs.Size.X * 0.1f);
|
||||
}
|
||||
|
||||
public override bool IsCompleted()
|
||||
{
|
||||
return targetHull.FireSources.Count == 0;
|
||||
}
|
||||
|
||||
public override bool IsDuplicate(AIObjective otherObjective)
|
||||
{
|
||||
var otherExtinguishFire = otherObjective as AIObjectiveExtinguishFire;
|
||||
return otherExtinguishFire != null && otherExtinguishFire.targetHull == targetHull;
|
||||
}
|
||||
|
||||
public override bool CanBeCompleted
|
||||
{
|
||||
get { return getExtinguisherObjective == null || getExtinguisherObjective.CanBeCompleted; }
|
||||
}
|
||||
|
||||
protected override void Act(float deltaTime)
|
||||
{
|
||||
var extinguisherItem = character.Inventory.FindItemByIdentifier("extinguisher") ?? character.Inventory.FindItemByTag("extinguisher");
|
||||
if (extinguisherItem == null || extinguisherItem.Condition <= 0.0f || !character.HasEquippedItem(extinguisherItem))
|
||||
{
|
||||
if (getExtinguisherObjective == null)
|
||||
{
|
||||
character.Speak(TextManager.Get("DialogFindExtinguisher"), null, 2.0f, "findextinguisher", 30.0f);
|
||||
getExtinguisherObjective = new AIObjectiveGetItem(character, "extinguisher", true);
|
||||
}
|
||||
else
|
||||
{
|
||||
getExtinguisherObjective.TryComplete(deltaTime);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var extinguisher = extinguisherItem.GetComponent<RepairTool>();
|
||||
if (extinguisher == null)
|
||||
{
|
||||
DebugConsole.ThrowError("AIObjectiveExtinguishFire failed - the item \"" + extinguisherItem + "\" has no RepairTool component but is tagged as an extinguisher");
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (FireSource fs in targetHull.FireSources.ToList())
|
||||
{
|
||||
if (fs.IsInDamageRange(character, MathHelper.Clamp(fs.DamageRange * 1.5f, extinguisher.Range * 0.5f, extinguisher.Range)) || useExtinquisherTimer > 0.0f)
|
||||
{
|
||||
useExtinquisherTimer += deltaTime;
|
||||
if (useExtinquisherTimer > 2.0f) useExtinquisherTimer = 0.0f;
|
||||
|
||||
character.CursorPosition = fs.Position;
|
||||
character.SetInput(InputType.Aim, false, true);
|
||||
character.AIController.SteeringManager.Reset();
|
||||
extinguisher.Use(deltaTime, character);
|
||||
|
||||
if (!targetHull.FireSources.Contains(fs))
|
||||
{
|
||||
character.Speak(TextManager.Get("DialogPutOutFire").Replace("[roomname]", targetHull.Name), null, 0, "putoutfire", 10.0f);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (FireSource fs in targetHull.FireSources)
|
||||
{
|
||||
//go to the first firesource
|
||||
if (gotoObjective == null || !gotoObjective.CanBeCompleted || gotoObjective.IsCompleted())
|
||||
{
|
||||
gotoObjective = new AIObjectiveGoTo(ConvertUnits.ToSimUnits(fs.Position), character);
|
||||
}
|
||||
else
|
||||
{
|
||||
gotoObjective.TryComplete(deltaTime);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class AIObjectiveExtinguishFires : AIObjective
|
||||
{
|
||||
public AIObjectiveExtinguishFires(Character character) :
|
||||
base(character, "")
|
||||
{
|
||||
if (!Hull.hullList.Any(h => h.FireSources.Count > 0))
|
||||
{
|
||||
character?.Speak(TextManager.Get("DialogNoFire"), null, 3.0f, "nofire", 30.0f);
|
||||
}
|
||||
}
|
||||
|
||||
public override float GetPriority(AIObjectiveManager objectiveManager)
|
||||
{
|
||||
if (objectiveManager.CurrentObjective == this)
|
||||
{
|
||||
return AIObjectiveManager.OrderPriority;
|
||||
}
|
||||
|
||||
return Hull.hullList.Count(h => h.FireSources.Count > 0) * 10;
|
||||
}
|
||||
|
||||
public override bool IsCompleted()
|
||||
{
|
||||
return !Hull.hullList.Any(h => h.FireSources.Count > 0);
|
||||
}
|
||||
|
||||
public override bool IsDuplicate(AIObjective otherObjective)
|
||||
{
|
||||
return otherObjective is AIObjectiveExtinguishFires;
|
||||
}
|
||||
|
||||
protected override void Act(float deltaTime)
|
||||
{
|
||||
foreach (Hull hull in Hull.hullList)
|
||||
{
|
||||
if (hull.FireSources.Count > 0)
|
||||
{
|
||||
AddSubObjective(new AIObjectiveExtinguishFire(character, hull));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+12
-10
@@ -7,19 +7,19 @@ namespace Barotrauma
|
||||
{
|
||||
private AIObjective subObjective;
|
||||
|
||||
private string gearName;
|
||||
private string gearTag;
|
||||
|
||||
public override bool IsCompleted()
|
||||
{
|
||||
for (int i = 0; i < character.Inventory.Items.Length; i++)
|
||||
{
|
||||
if (CharacterInventory.limbSlots[i] == InvSlotType.Any || character.Inventory.Items[i] == null) continue;
|
||||
if (character.Inventory.Items[i].Prefab.NameMatches(gearName) || character.Inventory.Items[i].HasTag(gearName))
|
||||
if (character.Inventory.SlotTypes[i] == InvSlotType.Any || character.Inventory.Items[i] == null) continue;
|
||||
if (character.Inventory.Items[i].HasTag(gearTag))
|
||||
{
|
||||
var containedItems = character.Inventory.Items[i].ContainedItems;
|
||||
if (containedItems == null) continue;
|
||||
|
||||
var oxygenTank = Array.Find(containedItems, it => (it.Prefab.NameMatches("Oxygen Tank") || it.HasTag("oxygensource")) && it.Condition > 0.0f);
|
||||
var oxygenTank = Array.Find(containedItems, it => (it.Prefab.Identifier == "oxygentank" || it.HasTag("oxygensource")) && it.Condition > 0.0f);
|
||||
if (oxygenTank != null) return true;
|
||||
}
|
||||
}
|
||||
@@ -30,18 +30,19 @@ namespace Barotrauma
|
||||
public AIObjectiveFindDivingGear(Character character, bool needDivingSuit)
|
||||
: base(character, "")
|
||||
{
|
||||
gearName = needDivingSuit ? "Diving Suit" : "diving";
|
||||
gearTag = needDivingSuit ? "divingsuit" : "diving";
|
||||
}
|
||||
|
||||
protected override void Act(float deltaTime)
|
||||
{
|
||||
var item = character.Inventory.FindItem(gearName);
|
||||
if (item == null)
|
||||
var item = character.Inventory.FindItemByTag(gearTag);
|
||||
if (item == null || !character.HasEquippedItem(item))
|
||||
{
|
||||
//get a diving mask/suit first
|
||||
if (!(subObjective is AIObjectiveGetItem))
|
||||
{
|
||||
subObjective = new AIObjectiveGetItem(character, gearName, true);
|
||||
character.Speak(TextManager.Get("DialogGetDivingGear"), null, 0.0f, "getdivinggear", 30.0f);
|
||||
subObjective = new AIObjectiveGetItem(character, gearTag, true);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -57,7 +58,7 @@ namespace Barotrauma
|
||||
{
|
||||
containedItem.Drop();
|
||||
}
|
||||
else if (containedItem.Prefab.NameMatches("Oxygen Tank") || containedItem.HasTag("oxygensource"))
|
||||
else if (containedItem.Prefab.Identifier == "oxygentank" || containedItem.HasTag("oxygensource"))
|
||||
{
|
||||
//we've got an oxygen source inside the mask/suit, all good
|
||||
return;
|
||||
@@ -66,7 +67,8 @@ namespace Barotrauma
|
||||
|
||||
if (!(subObjective is AIObjectiveContainItem) || subObjective.IsCompleted())
|
||||
{
|
||||
subObjective = new AIObjectiveContainItem(character, new string[] { "Oxygen Tank", "oxygensource" }, item.GetComponent<ItemContainer>());
|
||||
character.Speak(TextManager.Get("DialogGetOxygenTank"), null, 0, "getoxygentank", 30.0f);
|
||||
subObjective = new AIObjectiveContainItem(character, new string[] { "oxygentank", "oxygensource" }, item.GetComponent<ItemContainer>());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+81
-21
@@ -55,7 +55,10 @@ namespace Barotrauma
|
||||
var bestHull = FindBestHull();
|
||||
if (bestHull != null)
|
||||
{
|
||||
goToObjective = new AIObjectiveGoTo(bestHull, character);
|
||||
goToObjective = new AIObjectiveGoTo(bestHull, character)
|
||||
{
|
||||
AllowGoingOutside = true
|
||||
};
|
||||
}
|
||||
|
||||
searchHullTimer = SearchHullInterval;
|
||||
@@ -63,15 +66,57 @@ namespace Barotrauma
|
||||
|
||||
if (goToObjective != null)
|
||||
{
|
||||
var pathSteering = character.AIController.SteeringManager as IndoorsSteeringManager;
|
||||
if (pathSteering != null && pathSteering.CurrentPath != null &&
|
||||
goToObjective.TryComplete(deltaTime);
|
||||
|
||||
if (character.AIController.SteeringManager is IndoorsSteeringManager pathSteering &&
|
||||
pathSteering.CurrentPath != null &&
|
||||
pathSteering.CurrentPath.Unreachable && !unreachable.Contains(goToObjective.Target))
|
||||
{
|
||||
unreachable.Add(goToObjective.Target as Hull);
|
||||
goToObjective = null;
|
||||
}
|
||||
}
|
||||
//goto objective doesn't exist (a safe hull not found, or a path to a safe hull not found)
|
||||
// -> attempt to manually steer away from hazards
|
||||
else if (currentHull != null)
|
||||
{
|
||||
Vector2 escapeVel = Vector2.Zero;
|
||||
foreach (FireSource fireSource in currentHull.FireSources)
|
||||
{
|
||||
int dir = Math.Sign(character.Position.X - fireSource.Position.X);
|
||||
|
||||
float distMultiplier = MathHelper.Clamp(100.0f / Vector2.Distance(fireSource.Position, character.Position), 0.1f, 10.0f);
|
||||
escapeVel += new Vector2(dir * distMultiplier, 0.0f);
|
||||
}
|
||||
|
||||
foreach (Character enemy in Character.CharacterList)
|
||||
{
|
||||
if (enemy.CurrentHull == currentHull && !enemy.IsDead && !enemy.IsUnconscious &&
|
||||
(enemy.AIController is EnemyAIController || enemy.TeamID != character.TeamID))
|
||||
{
|
||||
float distMultiplier = MathHelper.Clamp(100.0f / Vector2.Distance(enemy.Position, character.Position), 0.1f, 10.0f);
|
||||
escapeVel += new Vector2(Math.Sign(character.Position.X - enemy.Position.X) * distMultiplier, 0.0f);
|
||||
}
|
||||
}
|
||||
|
||||
goToObjective.TryComplete(deltaTime);
|
||||
if (escapeVel != Vector2.Zero)
|
||||
{
|
||||
//only move if we haven't reached the edge of the room
|
||||
if ((escapeVel.X < 0 && character.Position.X > currentHull.Rect.X + 50) ||
|
||||
(escapeVel.X > 0 && character.Position.X < currentHull.Rect.Right - 50))
|
||||
{
|
||||
character.AIController.SteeringManager.SteeringManual(deltaTime, escapeVel);
|
||||
}
|
||||
else
|
||||
{
|
||||
character.AnimController.TargetDir = escapeVel.X < 0.0f ? Direction.Right : Direction.Left;
|
||||
character.AIController.SteeringManager.Reset();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
character.AIController.SteeringManager.Reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,26 +182,30 @@ namespace Barotrauma
|
||||
return 150.0f - character.Oxygen;
|
||||
}
|
||||
|
||||
if (character.AnimController.CurrentHull == null) return 5.0f;
|
||||
currenthullSafety = GetHullSafety(character.AnimController.CurrentHull, character);
|
||||
if (character.CurrentHull == null) return 5.0f;
|
||||
currenthullSafety = GetHullSafety(character.CurrentHull, character);
|
||||
priority = 100.0f - currenthullSafety;
|
||||
|
||||
var nearbyHulls = character.AnimController.CurrentHull.GetConnectedHulls(3);
|
||||
//var nearbyHulls = character.CurrentHull.GetConnectedHulls(3);
|
||||
|
||||
foreach (Hull hull in nearbyHulls)
|
||||
//increase priority slightly if there's a fire in the room
|
||||
//(will increase more heavily if near the damage range of the fire)
|
||||
if (character.CurrentHull.FireSources.Count > 0)
|
||||
{
|
||||
priority += 5.0f;
|
||||
}
|
||||
|
||||
/*foreach (Hull hull in nearbyHulls)
|
||||
{
|
||||
foreach (FireSource fireSource in hull.FireSources)
|
||||
{
|
||||
//increase priority if almost within damage range of a fire
|
||||
if (character.Position.X > fireSource.Position.X - fireSource.DamageRange * 2 &&
|
||||
character.Position.X < fireSource.Position.X + fireSource.Size.X + fireSource.DamageRange * 2 &&
|
||||
character.Position.Y > hull.Rect.Y - hull.Rect.Height &&
|
||||
character.Position.Y < hull.Rect.Y)
|
||||
//heavily increase priority if almost within damage range of a fire
|
||||
if (fireSource.IsInDamageRange(character, fireSource.DamageRange * 1.25f))
|
||||
{
|
||||
priority += Math.Max(fireSource.Size.X, 50.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
if (NeedsDivingGear())
|
||||
{
|
||||
@@ -189,25 +238,36 @@ namespace Barotrauma
|
||||
if (hull.OxygenPercentage < 30.0f) safety -= (30.0f - hull.OxygenPercentage) * 5.0f;
|
||||
|
||||
if (safety <= 0.0f) return 0.0f;
|
||||
|
||||
|
||||
bool extinguishFires =
|
||||
character.AIController.ObjectiveManager?.CurrentOrder is AIObjectiveExtinguishFires ||
|
||||
character.AIController.ObjectiveManager?.CurrentOrder is AIObjectiveExtinguishFire;
|
||||
|
||||
float fireAmount = 0.0f;
|
||||
var nearbyHulls = hull.GetConnectedHulls(3);
|
||||
foreach (Hull hull2 in nearbyHulls)
|
||||
{
|
||||
foreach (FireSource fireSource in hull2.FireSources)
|
||||
{
|
||||
//increase priority if almost within damage range of a fire
|
||||
if (character.Position.X > fireSource.Position.X - fireSource.DamageRange * 2 &&
|
||||
character.Position.X < fireSource.Position.X + fireSource.Size.X + fireSource.DamageRange * 2 &&
|
||||
character.Position.Y > hull2.Rect.Y - hull2.Rect.Height &&
|
||||
character.Position.Y < hull2.Rect.Y)
|
||||
//increase priority if near the damage range of a fire
|
||||
//if extinguishing fires, the character can go closer the damage range
|
||||
if (fireSource.IsInDamageRange(character, fireSource.DamageRange * (extinguishFires ? 1.25f : 5.0f)))
|
||||
{
|
||||
fireAmount += Math.Max(fireSource.Size.X, 50.0f);
|
||||
fireAmount += Math.Max(fireSource.Size.X, AIObjectiveManager.OrderPriority + 1.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
safety -= fireAmount;
|
||||
|
||||
foreach (Character enemy in Character.CharacterList)
|
||||
{
|
||||
if (enemy.CurrentHull == hull && !enemy.IsDead && !enemy.IsUnconscious &&
|
||||
(enemy.AIController is EnemyAIController || enemy.TeamID != character.TeamID))
|
||||
{
|
||||
safety -= 10.0f;
|
||||
}
|
||||
}
|
||||
|
||||
return MathHelper.Clamp(safety, 0.0f, 100.0f);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,9 +8,7 @@ namespace Barotrauma
|
||||
class AIObjectiveFixLeak : AIObjective
|
||||
{
|
||||
private readonly Gap leak;
|
||||
|
||||
private AIObjectiveGoTo gotoObjective;
|
||||
|
||||
|
||||
public Gap Leak
|
||||
{
|
||||
get { return leak; }
|
||||
@@ -47,11 +45,11 @@ namespace Barotrauma
|
||||
|
||||
protected override void Act(float deltaTime)
|
||||
{
|
||||
var weldingTool = character.Inventory.FindItem("Welding Tool");
|
||||
var weldingTool = character.Inventory.FindItemByTag("weldingtool");
|
||||
|
||||
if (weldingTool == null)
|
||||
{
|
||||
AddSubObjective(new AIObjectiveGetItem(character, "Welding Tool", true));
|
||||
AddSubObjective(new AIObjectiveGetItem(character, "weldingtool", true));
|
||||
return;
|
||||
}
|
||||
else
|
||||
@@ -59,11 +57,10 @@ namespace Barotrauma
|
||||
var containedItems = weldingTool.ContainedItems;
|
||||
if (containedItems == null) return;
|
||||
|
||||
var fuelTank = Array.Find(containedItems, i => i.Prefab.NameMatches("Welding Fuel Tank") && i.Condition > 0.0f);
|
||||
|
||||
var fuelTank = Array.Find(containedItems, i => i.HasTag("weldingfueltank") && i.Condition > 0.0f);
|
||||
if (fuelTank == null)
|
||||
{
|
||||
AddSubObjective(new AIObjectiveContainItem(character, "Welding Fuel Tank", weldingTool.GetComponent<ItemContainer>()));
|
||||
AddSubObjective(new AIObjectiveContainItem(character, "weldingfueltank", weldingTool.GetComponent<ItemContainer>()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -73,7 +70,15 @@ namespace Barotrauma
|
||||
|
||||
Vector2 standPosition = GetStandPosition();
|
||||
|
||||
if (Vector2.DistanceSquared(character.WorldPosition, leak.WorldPosition) > 100.0f * 100.0f)
|
||||
Vector2 gapDiff = leak.WorldPosition - character.WorldPosition;
|
||||
|
||||
if (!character.AnimController.InWater && character.AnimController is HumanoidAnimController &&
|
||||
Math.Abs(gapDiff.X) < 100.0f && gapDiff.Y < 0.0f && gapDiff.Y > -150.0f)
|
||||
{
|
||||
((HumanoidAnimController)character.AnimController).Crouching = true;
|
||||
}
|
||||
|
||||
if (Math.Abs(gapDiff.X) > 100.0f || Math.Abs(gapDiff.Y) > 150.0f)
|
||||
{
|
||||
var gotoObjective = new AIObjectiveGoTo(ConvertUnits.ToSimUnits(standPosition), character);
|
||||
if (!gotoObjective.IsCompleted())
|
||||
|
||||
@@ -105,9 +105,20 @@ namespace Barotrauma
|
||||
{
|
||||
if (gap.ConnectedWall == null) continue;
|
||||
if (gap.ConnectedDoor != null || gap.Open <= 0.0f) continue;
|
||||
|
||||
if (character.TeamID == 0)
|
||||
{
|
||||
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];
|
||||
|
||||
//TODO: prevent the AI characters from fixing leaks in the enemy sub in sub-vs-sub missions if/when multiplayer bots are implemented
|
||||
if (gap.Submarine == null) continue;
|
||||
if (gap.Submarine != mySub) continue;
|
||||
}
|
||||
|
||||
float gapPriority = GetGapFixPriority(gap);
|
||||
|
||||
|
||||
@@ -10,7 +10,8 @@ namespace Barotrauma
|
||||
{
|
||||
public Func<Item, float> GetItemPriority;
|
||||
|
||||
private string[] itemNames;
|
||||
//can be either tags or identifiers
|
||||
private string[] itemIdentifiers;
|
||||
|
||||
private Item targetItem, moveToTarget;
|
||||
|
||||
@@ -45,29 +46,65 @@ namespace Barotrauma
|
||||
: base(character, "")
|
||||
{
|
||||
canBeCompleted = true;
|
||||
|
||||
currSearchIndex = -1;
|
||||
this.equip = equip;
|
||||
|
||||
currSearchIndex = 0;
|
||||
|
||||
this.targetItem = targetItem;
|
||||
}
|
||||
|
||||
public AIObjectiveGetItem(Character character, string itemName, bool equip = false)
|
||||
: this(character, new string[] { itemName }, equip)
|
||||
public AIObjectiveGetItem(Character character, string itemIdentifier, bool equip = false)
|
||||
: this(character, new string[] { itemIdentifier }, equip)
|
||||
{
|
||||
}
|
||||
|
||||
public AIObjectiveGetItem(Character character, string[] itemNames, bool equip = false)
|
||||
public AIObjectiveGetItem(Character character, string[] itemIdentifiers, bool equip = false)
|
||||
: base(character, "")
|
||||
{
|
||||
canBeCompleted = true;
|
||||
|
||||
currSearchIndex = -1;
|
||||
this.equip = equip;
|
||||
this.itemIdentifiers = itemIdentifiers;
|
||||
for (int i = 0; i < itemIdentifiers.Length; i++)
|
||||
{
|
||||
itemIdentifiers[i] = itemIdentifiers[i].ToLowerInvariant();
|
||||
}
|
||||
|
||||
currSearchIndex = 0;
|
||||
CheckInventory();
|
||||
}
|
||||
|
||||
this.itemNames = itemNames;
|
||||
private void CheckInventory()
|
||||
{
|
||||
if (itemIdentifiers == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < character.Inventory.Items.Length; i++)
|
||||
{
|
||||
if (character.Inventory.Items[i] == null || character.Inventory.Items[i].Condition <= 0.0f) continue;
|
||||
if (itemIdentifiers.Any(id => character.Inventory.Items[i].Prefab.Identifier == id || character.Inventory.Items[i].HasTag(id)))
|
||||
{
|
||||
targetItem = character.Inventory.Items[i];
|
||||
moveToTarget = targetItem;
|
||||
currItemPriority = 100.0f;
|
||||
break;
|
||||
}
|
||||
//check items inside items (tool inside a toolbox etc)
|
||||
var containedItems = character.Inventory.Items[i].ContainedItems;
|
||||
if (containedItems != null)
|
||||
{
|
||||
foreach (Item containedItem in containedItems)
|
||||
{
|
||||
if (containedItem == null || containedItem.Condition <= 0.0f) continue;
|
||||
if (itemIdentifiers.Any(id => containedItem.Prefab.Identifier == id || containedItem.HasTag(id)))
|
||||
{
|
||||
targetItem = containedItem;
|
||||
moveToTarget = character.Inventory.Items[i];
|
||||
currItemPriority = 100.0f;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Act(float deltaTime)
|
||||
@@ -99,7 +136,7 @@ namespace Barotrauma
|
||||
for (int i = 0; i < character.Inventory.Items.Length; i++)
|
||||
{
|
||||
//slot not needed by the item, continue
|
||||
if (!slots.HasFlag(CharacterInventory.limbSlots[i])) continue;
|
||||
if (!slots.HasFlag(character.Inventory.SlotTypes[i])) continue;
|
||||
|
||||
targetSlot = i;
|
||||
|
||||
@@ -117,7 +154,7 @@ namespace Barotrauma
|
||||
|
||||
targetItem.TryInteract(character, false, true);
|
||||
|
||||
if (targetSlot > -1 && character.Inventory.IsInLimbSlot(targetItem, InvSlotType.Any))
|
||||
if (targetSlot > -1 && !character.HasEquippedItem(targetItem))
|
||||
{
|
||||
character.Inventory.TryPutItem(targetItem, targetSlot, false, false, character);
|
||||
}
|
||||
@@ -127,14 +164,15 @@ namespace Barotrauma
|
||||
if (goToObjective == null || moveToTarget != goToObjective.Target)
|
||||
{
|
||||
//check if we're already looking for a diving gear
|
||||
bool gettingDivingGear = (targetItem != null && targetItem.Prefab.NameMatches("Diving Gear") || targetItem.HasTag("diving")) ||
|
||||
(itemNames != null && (itemNames.Contains("diving") || itemNames.Contains("Diving Gear")));
|
||||
bool gettingDivingGear = (targetItem != null && targetItem.Prefab.Identifier == "divingsuit" || targetItem.HasTag("diving")) ||
|
||||
(itemIdentifiers != null && (itemIdentifiers.Contains("diving") || itemIdentifiers.Contains("divingsuit")));
|
||||
|
||||
//don't attempt to get diving gear to reach the destination if the item we're trying to get is diving gear
|
||||
goToObjective = new AIObjectiveGoTo(moveToTarget, character, false, !gettingDivingGear);
|
||||
}
|
||||
|
||||
goToObjective.TryComplete(deltaTime);
|
||||
if (!goToObjective.CanBeCompleted) targetItem = null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -144,7 +182,7 @@ namespace Barotrauma
|
||||
/// </summary>
|
||||
private void FindTargetItem()
|
||||
{
|
||||
if (itemNames == null)
|
||||
if (itemIdentifiers == null)
|
||||
{
|
||||
if (targetItem == null) canBeCompleted = false;
|
||||
return;
|
||||
@@ -152,7 +190,7 @@ namespace Barotrauma
|
||||
|
||||
float currDist = moveToTarget == null ? 0.0f : Vector2.DistanceSquared(moveToTarget.Position, character.Position);
|
||||
|
||||
for (int i = 0; i < 10 && currSearchIndex < Item.ItemList.Count - 2; i++)
|
||||
for (int i = 0; i < 10 && currSearchIndex < Item.ItemList.Count - 1; i++)
|
||||
{
|
||||
currSearchIndex++;
|
||||
|
||||
@@ -160,21 +198,19 @@ namespace Barotrauma
|
||||
|
||||
if (item.CurrentHull == null || item.Condition <= 0.0f) continue;
|
||||
if (IgnoreContainedItems && item.Container != null) continue;
|
||||
if (!itemNames.Any(name => item.Prefab.NameMatches(name) || item.HasTag(name))) continue;
|
||||
if (!itemIdentifiers.Any(id => item.Prefab.Identifier == id || item.HasTag(id))) continue;
|
||||
|
||||
//if the item is inside a character's inventory, don't steal it unless the character is dead
|
||||
if (item.ParentInventory is CharacterInventory)
|
||||
{
|
||||
Character owner = item.ParentInventory.Owner as Character;
|
||||
if (owner != null && !owner.IsDead) continue;
|
||||
if (item.ParentInventory.Owner is Character owner && !owner.IsDead) continue;
|
||||
}
|
||||
|
||||
//if the item is inside an item, which is inside a character's inventory, don't steal it
|
||||
Item rootContainer = item.GetRootContainer();
|
||||
if (rootContainer != null && rootContainer.ParentInventory is CharacterInventory)
|
||||
{
|
||||
Character owner = rootContainer.ParentInventory.Owner as Character;
|
||||
if (owner != null && !owner.IsDead) continue;
|
||||
if (rootContainer.ParentInventory.Owner is Character owner && !owner.IsDead) continue;
|
||||
}
|
||||
|
||||
float itemPriority = 0.0f;
|
||||
@@ -194,10 +230,11 @@ namespace Barotrauma
|
||||
|
||||
targetItem = item;
|
||||
moveToTarget = rootContainer ?? item;
|
||||
|
||||
}
|
||||
|
||||
//if searched through all the items and a target wasn't found, can't be completed
|
||||
if (currSearchIndex >= Item.ItemList.Count && targetItem == null) canBeCompleted = false;
|
||||
if (currSearchIndex >= Item.ItemList.Count - 1 && targetItem == null) canBeCompleted = false;
|
||||
}
|
||||
|
||||
public override bool IsDuplicate(AIObjective otherObjective)
|
||||
@@ -205,16 +242,16 @@ namespace Barotrauma
|
||||
AIObjectiveGetItem getItem = otherObjective as AIObjectiveGetItem;
|
||||
if (getItem == null) return false;
|
||||
if (getItem.equip != equip) return false;
|
||||
if (getItem.itemNames != null && itemNames != null)
|
||||
if (getItem.itemIdentifiers != null && itemIdentifiers != null)
|
||||
{
|
||||
if (getItem.itemNames.Length != itemNames.Length) return false;
|
||||
for (int i = 0; i < getItem.itemNames.Length; i++)
|
||||
if (getItem.itemIdentifiers.Length != itemIdentifiers.Length) return false;
|
||||
for (int i = 0; i < getItem.itemIdentifiers.Length; i++)
|
||||
{
|
||||
if (getItem.itemNames[i] != itemNames[i]) return false;
|
||||
if (getItem.itemIdentifiers[i] != itemIdentifiers[i]) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else if (getItem.itemNames == null && itemNames == null)
|
||||
else if (getItem.itemIdentifiers == null && itemIdentifiers == null)
|
||||
{
|
||||
return getItem.targetItem == targetItem;
|
||||
}
|
||||
@@ -224,11 +261,11 @@ namespace Barotrauma
|
||||
|
||||
public override bool IsCompleted()
|
||||
{
|
||||
if (itemNames != null)
|
||||
if (itemIdentifiers != null)
|
||||
{
|
||||
foreach (string itemName in itemNames)
|
||||
foreach (string itemName in itemIdentifiers)
|
||||
{
|
||||
var matchingItem = character.Inventory.FindItem(itemName);
|
||||
var matchingItem = character.Inventory.FindItemByTag(itemName) ?? character.Inventory.FindItemByIdentifier(itemName);
|
||||
if (matchingItem != null && (!equip || character.HasEquippedItem(matchingItem))) return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
@@ -7,8 +7,6 @@ namespace Barotrauma
|
||||
{
|
||||
class AIObjectiveGoTo : AIObjective
|
||||
{
|
||||
private Entity target;
|
||||
|
||||
private Vector2 targetPos;
|
||||
|
||||
private bool repeat;
|
||||
@@ -18,8 +16,17 @@ namespace Barotrauma
|
||||
|
||||
private bool getDivingGearIfNeeded;
|
||||
|
||||
public float CloseEnough = 0.5f;
|
||||
|
||||
public bool IgnoreIfTargetDead;
|
||||
|
||||
public bool AllowGoingOutside = false;
|
||||
|
||||
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 (objectiveManager.CurrentOrder == this)
|
||||
{
|
||||
return AIObjectiveManager.OrderPriority;
|
||||
@@ -32,25 +39,26 @@ namespace Barotrauma
|
||||
{
|
||||
get
|
||||
{
|
||||
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;
|
||||
|
||||
return (!pathSteering.CurrentPath.Unreachable);
|
||||
if (!AllowGoingOutside && pathSteering.CurrentPath.HasOutdoorsNodes) return false;
|
||||
|
||||
return !pathSteering.CurrentPath.Unreachable;
|
||||
}
|
||||
}
|
||||
|
||||
public Entity Target
|
||||
{
|
||||
get { return target; }
|
||||
}
|
||||
public Entity Target { get; private set; }
|
||||
|
||||
public AIObjectiveGoTo(Entity target, Character character, bool repeat = false, bool getDivingGearIfNeeded = true)
|
||||
: base (character, "")
|
||||
{
|
||||
this.target = target;
|
||||
this.Target = target;
|
||||
this.repeat = repeat;
|
||||
|
||||
waitUntilPathUnreachable = 5.0f;
|
||||
@@ -70,58 +78,65 @@ namespace Barotrauma
|
||||
|
||||
protected override void Act(float deltaTime)
|
||||
{
|
||||
if (target == character)
|
||||
if (Target == character)
|
||||
{
|
||||
character.AIController.SteeringManager.Reset();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
waitUntilPathUnreachable -= deltaTime;
|
||||
|
||||
if (character.SelectedConstruction!=null && character.SelectedConstruction.GetComponent<Ladder>()==null)
|
||||
if (character.SelectedConstruction != null && character.SelectedConstruction.GetComponent<Ladder>() == null)
|
||||
{
|
||||
character.SelectedConstruction = null;
|
||||
}
|
||||
|
||||
if (target != null) character.AIController.SelectTarget(target.AiTarget);
|
||||
if (Target != null) character.AIController.SelectTarget(Target.AiTarget);
|
||||
|
||||
Vector2 currTargetPos = Vector2.Zero;
|
||||
|
||||
if (target == null)
|
||||
if (Target == null)
|
||||
{
|
||||
currTargetPos = targetPos;
|
||||
}
|
||||
else
|
||||
{
|
||||
currTargetPos = target.SimPosition;
|
||||
currTargetPos = Target.SimPosition;
|
||||
|
||||
//if character is outside the sub and target isn't, transform the position
|
||||
if (character.Submarine != null && target.Submarine == null)
|
||||
//if character is inside the sub and target isn't, transform the position
|
||||
if (character.Submarine != null && Target.Submarine == null)
|
||||
{
|
||||
currTargetPos -= character.Submarine.SimPosition;
|
||||
}
|
||||
}
|
||||
|
||||
if (Vector2.DistanceSquared(currTargetPos, character.SimPosition) < 0.5f * 0.5f)
|
||||
if (Vector2.DistanceSquared(currTargetPos, character.SimPosition) < CloseEnough * CloseEnough)
|
||||
{
|
||||
character.AIController.SteeringManager.Reset();
|
||||
character.AnimController.TargetDir = currTargetPos.X > character.SimPosition.X ? Direction.Right : Direction.Left;
|
||||
}
|
||||
else
|
||||
{
|
||||
character.AIController.SteeringManager.SteeringSeek(currTargetPos);
|
||||
|
||||
var indoorsSteering = character.AIController.SteeringManager as IndoorsSteeringManager;
|
||||
|
||||
if (indoorsSteering.CurrentPath == null || indoorsSteering.CurrentPath.Unreachable)
|
||||
{
|
||||
indoorsSteering.SteeringWander();
|
||||
}
|
||||
else if (getDivingGearIfNeeded && indoorsSteering.CurrentPath != null && indoorsSteering.CurrentPath.HasOutdoorsNodes)
|
||||
float normalSpeed = character.AnimController.GetCurrentSpeed(false);
|
||||
character.AIController.SteeringManager.SteeringSeek(currTargetPos, normalSpeed);
|
||||
if (getDivingGearIfNeeded && Target?.Submarine == null && AllowGoingOutside)
|
||||
{
|
||||
AddSubObjective(new AIObjectiveFindDivingGear(character, true));
|
||||
}
|
||||
else if (character.AIController.SteeringManager is IndoorsSteeringManager indoorsSteering)
|
||||
{
|
||||
if (indoorsSteering.CurrentPath == null || indoorsSteering.CurrentPath.Unreachable)
|
||||
{
|
||||
indoorsSteering.SteeringWander(normalSpeed);
|
||||
}
|
||||
else if (AllowGoingOutside &&
|
||||
getDivingGearIfNeeded &&
|
||||
indoorsSteering.CurrentPath != null &&
|
||||
indoorsSteering.CurrentPath.HasOutdoorsNodes)
|
||||
{
|
||||
AddSubObjective(new AIObjectiveFindDivingGear(character, true));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,15 +147,18 @@ namespace Barotrauma
|
||||
bool completed = false;
|
||||
|
||||
float allowedDistance = 0.5f;
|
||||
var item = target as Item;
|
||||
|
||||
if (item != null)
|
||||
if (Target is Item item)
|
||||
{
|
||||
allowedDistance = Math.Max(ConvertUnits.ToSimUnits(item.InteractDistance), allowedDistance);
|
||||
if (item.IsInsideTrigger(character.WorldPosition)) completed = true;
|
||||
}
|
||||
else if (Target is Character targetCharacter)
|
||||
{
|
||||
if (character.CanInteractWith(targetCharacter)) completed = true;
|
||||
}
|
||||
|
||||
completed = completed || Vector2.DistanceSquared(target != null ? target.SimPosition : targetPos, character.SimPosition) < allowedDistance * allowedDistance;
|
||||
completed = completed || Vector2.DistanceSquared(Target != null ? Target.SimPosition : targetPos, character.SimPosition) < allowedDistance * allowedDistance;
|
||||
|
||||
if (completed) character.AIController.SteeringManager.Reset();
|
||||
|
||||
@@ -152,7 +170,7 @@ namespace Barotrauma
|
||||
AIObjectiveGoTo objective = otherObjective as AIObjectiveGoTo;
|
||||
if (objective == null) return false;
|
||||
|
||||
if (objective.target == target) return true;
|
||||
if (objective.Target == Target) return true;
|
||||
|
||||
return (objective.targetPos == targetPos);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using Barotrauma.Items.Components;
|
||||
using FarseerPhysics;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
@@ -12,10 +14,15 @@ namespace Barotrauma
|
||||
private AITarget currentTarget;
|
||||
private float newTargetTimer;
|
||||
|
||||
private float standStillTimer;
|
||||
private float walkDuration;
|
||||
|
||||
private AIObjectiveFindSafety findSafety;
|
||||
|
||||
public AIObjectiveIdle(Character character) : base(character, "")
|
||||
{
|
||||
standStillTimer = Rand.Range(-10.0f, 10.0f);
|
||||
walkDuration = Rand.Range(0.0f, 10.0f);
|
||||
}
|
||||
|
||||
public override bool IsCompleted()
|
||||
@@ -33,6 +40,16 @@ namespace Barotrauma
|
||||
var pathSteering = character.AIController.SteeringManager as IndoorsSteeringManager;
|
||||
if (pathSteering == null) return;
|
||||
|
||||
//don't keep dragging others when idling
|
||||
if (character.SelectedCharacter != null)
|
||||
{
|
||||
character.DeselectCharacter();
|
||||
}
|
||||
if (character.SelectedConstruction != null && character.SelectedConstruction.GetComponent<Ladder>() == null)
|
||||
{
|
||||
character.SelectedConstruction = null;
|
||||
}
|
||||
|
||||
if (character.AnimController.InWater)
|
||||
{
|
||||
//attempt to find a safer place if in water
|
||||
@@ -48,10 +65,13 @@ namespace Barotrauma
|
||||
if (currentTarget != null)
|
||||
{
|
||||
Vector2 pos = character.SimPosition;
|
||||
if (character != null && character.Submarine == null) pos -= Submarine.MainSub.SimPosition;
|
||||
|
||||
var path = pathSteering.PathFinder.FindPath(pos, currentTarget.SimPosition);
|
||||
if (path.Cost > 200.0f && character.AnimController.CurrentHull!=null) return;
|
||||
if (character != null && character.Submarine == null) { pos -= Submarine.MainSub.SimPosition; }
|
||||
|
||||
string errorMsg = "(Character " + character.Name + " idling, target "
|
||||
+ ((currentTarget.Entity is Hull hull && hull.RoomName != null) ? hull.RoomName : currentTarget.Entity.ToString()) + ")";
|
||||
|
||||
var path = pathSteering.PathFinder.FindPath(pos, currentTarget.SimPosition, errorMsg);
|
||||
if (path.Cost > 1000.0f && character.AnimController.CurrentHull!=null) return;
|
||||
|
||||
pathSteering.SetPath(path);
|
||||
}
|
||||
@@ -70,6 +90,19 @@ namespace Barotrauma
|
||||
if (pathSteering == null || (pathSteering.CurrentPath != null &&
|
||||
(pathSteering.CurrentPath.NextNode == null || pathSteering.CurrentPath.Unreachable || pathSteering.CurrentPath.HasOutdoorsNodes)))
|
||||
{
|
||||
standStillTimer -= deltaTime;
|
||||
if (standStillTimer > 0.0f)
|
||||
{
|
||||
walkDuration = Rand.Range(1.0f, 5.0f);
|
||||
pathSteering.Reset();
|
||||
return;
|
||||
}
|
||||
|
||||
if (standStillTimer < -walkDuration)
|
||||
{
|
||||
standStillTimer = Rand.Range(1.0f, 10.0f);
|
||||
}
|
||||
|
||||
//steer away from edges of the hull
|
||||
if (character.AnimController.CurrentHull != null)
|
||||
{
|
||||
@@ -102,7 +135,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
character.AIController.SteeringManager.SteeringWander();
|
||||
character.AIController.SteeringManager.SteeringWander(character.AnimController.GetCurrentSpeed(false));
|
||||
//reset vertical steering to prevent dropping down from platforms etc
|
||||
character.AIController.SteeringManager.ResetY();
|
||||
|
||||
@@ -115,20 +148,21 @@ namespace Barotrauma
|
||||
currentTarget = null;
|
||||
return;
|
||||
}
|
||||
character.AIController.SteeringManager.SteeringSeek(currentTarget.SimPosition, 2.0f);
|
||||
character.AIController.SteeringManager.SteeringSeek(currentTarget.SimPosition, character.AnimController.GetCurrentSpeed(true));
|
||||
}
|
||||
|
||||
private AITarget FindRandomTarget()
|
||||
{
|
||||
if (Rand.Int(5)==1)
|
||||
//random chance of navigating back to the room where the character spawned
|
||||
if (Rand.Int(5) == 1)
|
||||
{
|
||||
var idCard = character.Inventory.FindItem("ID Card");
|
||||
if (idCard==null) return null;
|
||||
var idCard = character.Inventory.FindItemByIdentifier("idcard");
|
||||
if (idCard == null) return null;
|
||||
|
||||
foreach (WayPoint wp in WayPoint.WayPointList)
|
||||
{
|
||||
if (wp.SpawnType != SpawnType.Human || wp.CurrentHull==null) continue;
|
||||
|
||||
if (wp.SpawnType != SpawnType.Human || wp.CurrentHull == null) continue;
|
||||
|
||||
foreach (string tag in wp.IdCardTags)
|
||||
{
|
||||
if (idCard.HasTag(tag)) return wp.CurrentHull.AiTarget;
|
||||
@@ -140,9 +174,31 @@ namespace Barotrauma
|
||||
List<Hull> targetHulls = new List<Hull>(Hull.hullList);
|
||||
//ignore all hulls with fires or water in them
|
||||
targetHulls.RemoveAll(h => h.FireSources.Any() || h.WaterVolume / h.Volume > 0.1f);
|
||||
if (!targetHulls.Any()) return null;
|
||||
if (character.Submarine != null)
|
||||
{
|
||||
targetHulls.RemoveAll(h => h.Submarine != character.Submarine);
|
||||
}
|
||||
|
||||
return targetHulls[Rand.Range(0, targetHulls.Count)].AiTarget;
|
||||
//remove ballast hulls
|
||||
foreach (Item item in Item.ItemList)
|
||||
{
|
||||
if (item.HasTag("ballast") && targetHulls.Contains(item.CurrentHull))
|
||||
{
|
||||
targetHulls.Remove(item.CurrentHull);
|
||||
}
|
||||
}
|
||||
|
||||
//ignore hulls that are too low to stand inside
|
||||
if (character.AnimController is HumanoidAnimController animController)
|
||||
{
|
||||
float minHeight = ConvertUnits.ToDisplayUnits(animController.HeadPosition.Value);
|
||||
targetHulls.RemoveAll(h => h.CeilingHeight < minHeight);
|
||||
}
|
||||
if (!targetHulls.Any()) return null;
|
||||
|
||||
//prefer larger hulls
|
||||
var targetHull = ToolBox.SelectWeightedRandom(targetHulls, targetHulls.Select(h => h.Volume).ToList(), Rand.RandSync.Unsynced);
|
||||
return targetHull?.AiTarget;
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using Barotrauma.Items.Components;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
@@ -12,7 +14,12 @@ namespace Barotrauma
|
||||
private Character character;
|
||||
|
||||
private AIObjective currentOrder;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// When set above zero, the character will stand still doing nothing until the timer runs out (assuming they don't a high priority order active)
|
||||
/// </summary>
|
||||
public float WaitTimer;
|
||||
|
||||
public AIObjective CurrentOrder
|
||||
{
|
||||
get { return currentOrder; }
|
||||
@@ -38,6 +45,23 @@ namespace Barotrauma
|
||||
objectives.Add(objective);
|
||||
}
|
||||
|
||||
public Dictionary<AIObjective, CoroutineHandle> DelayedObjectives { get; private set; } = new Dictionary<AIObjective, CoroutineHandle>();
|
||||
public void AddObjective(AIObjective objective, float delay, Action callback = null)
|
||||
{
|
||||
if (DelayedObjectives.TryGetValue(objective, out CoroutineHandle coroutine))
|
||||
{
|
||||
CoroutineManager.StopCoroutines(coroutine);
|
||||
DelayedObjectives.Remove(objective);
|
||||
}
|
||||
coroutine = CoroutineManager.InvokeAfter(() =>
|
||||
{
|
||||
DelayedObjectives.Remove(objective);
|
||||
AddObjective(objective);
|
||||
callback?.Invoke();
|
||||
}, delay);
|
||||
DelayedObjectives.Add(objective, coroutine);
|
||||
}
|
||||
|
||||
public T GetObjective<T>() where T : AIObjective
|
||||
{
|
||||
foreach (AIObjective objective in objectives)
|
||||
@@ -47,15 +71,21 @@ namespace Barotrauma
|
||||
return null;
|
||||
}
|
||||
|
||||
public float GetCurrentPriority(Character character)
|
||||
private AIObjective GetCurrentObjective()
|
||||
{
|
||||
if (CurrentOrder != null &&
|
||||
(objectives.Count == 0 || currentOrder.GetPriority(this) > objectives[0].GetPriority(this)))
|
||||
{
|
||||
return CurrentOrder.GetPriority(this);
|
||||
return CurrentOrder;
|
||||
}
|
||||
|
||||
return objectives.Count == 0 ? 0.0f : objectives[0].GetPriority(this);
|
||||
return objectives.Count == 0 ? null : objectives[0];
|
||||
}
|
||||
|
||||
public float GetCurrentPriority()
|
||||
{
|
||||
var currentObjective = GetCurrentObjective();
|
||||
return currentObjective == null ? 0.0f : currentObjective.GetPriority(this);
|
||||
}
|
||||
|
||||
public void UpdateObjectives()
|
||||
@@ -67,46 +97,77 @@ namespace Barotrauma
|
||||
|
||||
//sort objectives according to priority
|
||||
objectives.Sort((x, y) => y.GetPriority(this).CompareTo(x.GetPriority(this)));
|
||||
GetCurrentObjective()?.SortSubObjectives(this);
|
||||
}
|
||||
|
||||
|
||||
public void DoCurrentObjective(float deltaTime)
|
||||
{
|
||||
if (currentOrder != null && (!objectives.Any() || objectives[0].GetPriority(this) < currentOrder.GetPriority(this)))
|
||||
CurrentObjective = GetCurrentObjective();
|
||||
|
||||
if (CurrentObjective == null || (CurrentObjective.GetPriority(this) < OrderPriority && WaitTimer > 0.0f))
|
||||
{
|
||||
CurrentObjective = currentOrder;
|
||||
currentOrder.TryComplete(deltaTime);
|
||||
WaitTimer -= deltaTime;
|
||||
character.AIController.SteeringManager.Reset();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!objectives.Any()) return;
|
||||
objectives[0].TryComplete(deltaTime);
|
||||
|
||||
CurrentObjective = objectives[0];
|
||||
CurrentObjective?.TryComplete(deltaTime);
|
||||
}
|
||||
|
||||
public void SetOrder(AIObjective objective)
|
||||
{
|
||||
currentOrder = objective;
|
||||
}
|
||||
|
||||
public void SetOrder(Order order, string option)
|
||||
public void SetOrder(Order order, string option, Character orderGiver)
|
||||
{
|
||||
currentOrder = null;
|
||||
if (order == null) return;
|
||||
|
||||
currentOrder = null;
|
||||
|
||||
switch (order.Name.ToLowerInvariant())
|
||||
switch (order.AITag.ToLowerInvariant())
|
||||
{
|
||||
case "follow":
|
||||
currentOrder = new AIObjectiveGoTo(Character.Controlled, character, true);
|
||||
currentOrder = new AIObjectiveGoTo(orderGiver, character, true)
|
||||
{
|
||||
CloseEnough = 1.5f,
|
||||
AllowGoingOutside = true,
|
||||
IgnoreIfTargetDead = true
|
||||
};
|
||||
break;
|
||||
case "wait":
|
||||
currentOrder = new AIObjectiveGoTo(character, character, true);
|
||||
currentOrder = new AIObjectiveGoTo(character, character, true)
|
||||
{
|
||||
AllowGoingOutside = true
|
||||
};
|
||||
break;
|
||||
case "fixleaks":
|
||||
case "fix leaks":
|
||||
currentOrder = new AIObjectiveFixLeaks(character);
|
||||
break;
|
||||
case "chargebatteries":
|
||||
currentOrder = new AIObjectiveChargeBatteries(character, option);
|
||||
break;
|
||||
case "rescue":
|
||||
currentOrder = new AIObjectiveRescueAll(character);
|
||||
break;
|
||||
case "repairsystems":
|
||||
currentOrder = new AIObjectiveRepairItems(character) { RequireAdequateSkills = option != "all" };
|
||||
break;
|
||||
case "pumpwater":
|
||||
currentOrder = new AIObjectivePumpWater(character, option);
|
||||
break;
|
||||
case "extinguishfires":
|
||||
currentOrder = new AIObjectiveExtinguishFires(character);
|
||||
break;
|
||||
case "steer":
|
||||
var steering = (order?.TargetEntity as Item)?.GetComponent<Steering>();
|
||||
if (steering != null) steering.PosToMaintain = steering.Item.Submarine?.WorldPosition;
|
||||
if (order.TargetItemComponent == null) return;
|
||||
currentOrder = new AIObjectiveOperateItem(order.TargetItemComponent, character, option, false, null, order.UseController);
|
||||
break;
|
||||
default:
|
||||
if (order.TargetItem == null) return;
|
||||
|
||||
currentOrder = new AIObjectiveOperateItem(order.TargetItem, character, option, false, null, order.UseController);
|
||||
|
||||
if (order.TargetItemComponent == null) return;
|
||||
currentOrder = new AIObjectiveOperateItem(order.TargetItemComponent, character, option, false, null, order.UseController);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
+30
-9
@@ -17,10 +17,18 @@ namespace Barotrauma
|
||||
|
||||
private bool requireEquip;
|
||||
|
||||
private bool useController;
|
||||
|
||||
private AIObjectiveGoTo gotoObjective;
|
||||
|
||||
public override bool CanBeCompleted
|
||||
{
|
||||
get
|
||||
{
|
||||
if (gotoObjective != null && !gotoObjective.CanBeCompleted) return false;
|
||||
|
||||
if (useController && controller == null) return false;
|
||||
|
||||
return canBeCompleted;
|
||||
}
|
||||
}
|
||||
@@ -43,23 +51,30 @@ namespace Barotrauma
|
||||
public AIObjectiveOperateItem(ItemComponent item, Character character, string option, bool requireEquip, Entity operateTarget = null, bool useController = false)
|
||||
: base (character, option)
|
||||
{
|
||||
this.component = item;
|
||||
this.component = item ?? throw new System.ArgumentNullException("item", "Attempted to create an AIObjectiveOperateItem with a null target.");
|
||||
this.requireEquip = requireEquip;
|
||||
this.operateTarget = operateTarget;
|
||||
this.useController = useController;
|
||||
|
||||
if (useController)
|
||||
{
|
||||
var controllers = item.Item.GetConnectedComponents<Controller>();
|
||||
var controllers = component.Item.GetConnectedComponents<Controller>();
|
||||
if (controllers.Any()) controller = controllers[0];
|
||||
}
|
||||
|
||||
|
||||
canBeCompleted = true;
|
||||
}
|
||||
|
||||
protected override void Act(float deltaTime)
|
||||
{
|
||||
ItemComponent target = controller == null ? component : controller;
|
||||
ItemComponent target = useController ? controller : component;
|
||||
|
||||
if (useController && controller == null)
|
||||
{
|
||||
character.Speak(TextManager.Get("DialogCantFindController").Replace("[item]", component.Item.Name), null, 2.0f, "cantfindcontroller", 30.0f);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (target.CanBeSelected)
|
||||
{
|
||||
@@ -75,11 +90,17 @@ namespace Barotrauma
|
||||
return;
|
||||
}
|
||||
|
||||
AddSubObjective(new AIObjectiveGoTo(target.Item, character));
|
||||
AddSubObjective(gotoObjective = new AIObjectiveGoTo(target.Item, character));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!character.Inventory.Items.Contains(component.Item))
|
||||
if (component.Item.GetComponent<Pickable>() == null)
|
||||
{
|
||||
//controller/target can't be selected and the item cannot be picked -> objective can't be completed
|
||||
canBeCompleted = false;
|
||||
return;
|
||||
}
|
||||
else if (!character.Inventory.Items.Contains(component.Item))
|
||||
{
|
||||
AddSubObjective(new AIObjectiveGetItem(character, component.Item, true));
|
||||
}
|
||||
@@ -95,10 +116,10 @@ namespace Barotrauma
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < CharacterInventory.limbSlots.Length; i++)
|
||||
for (int i = 0; i < character.Inventory.Capacity; i++)
|
||||
{
|
||||
if (CharacterInventory.limbSlots[i] == InvSlotType.Any ||
|
||||
!holdable.AllowedSlots.Any(s => s.HasFlag(CharacterInventory.limbSlots[i])))
|
||||
if (character.Inventory.SlotTypes[i] == InvSlotType.Any ||
|
||||
!holdable.AllowedSlots.Any(s => s.HasFlag(character.Inventory.SlotTypes[i])))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
using Barotrauma.Items.Components;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class AIObjectivePumpWater : AIObjective
|
||||
{
|
||||
private const float FindPumpsInterval = 5.0f;
|
||||
|
||||
private string orderOption;
|
||||
private List<Pump> pumps;
|
||||
private float lastFindPumpsTime;
|
||||
|
||||
public AIObjectivePumpWater(Character character, string option)
|
||||
: base(character, option)
|
||||
{
|
||||
orderOption = option;
|
||||
}
|
||||
|
||||
public override float GetPriority(AIObjectiveManager objectiveManager)
|
||||
{
|
||||
if (Timing.TotalTime >= lastFindPumpsTime + FindPumpsInterval)
|
||||
{
|
||||
FindPumps();
|
||||
}
|
||||
|
||||
if (objectiveManager.CurrentOrder == this && pumps.Count > 0)
|
||||
{
|
||||
return AIObjectiveManager.OrderPriority;
|
||||
}
|
||||
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
public override bool IsCompleted()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public override bool IsDuplicate(AIObjective otherObjective)
|
||||
{
|
||||
return otherObjective is AIObjectivePumpWater;
|
||||
}
|
||||
|
||||
protected override void Act(float deltaTime)
|
||||
{
|
||||
if (Timing.TotalTime < lastFindPumpsTime + FindPumpsInterval)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
FindPumps();
|
||||
}
|
||||
|
||||
private void FindPumps()
|
||||
{
|
||||
lastFindPumpsTime = (float)Timing.TotalTime;
|
||||
|
||||
pumps = new List<Pump>();
|
||||
foreach (Item item in Item.ItemList)
|
||||
{
|
||||
//don't attempt to use pumps outside the sub
|
||||
if (item.Submarine == null) { continue; }
|
||||
|
||||
var pump = item.GetComponent<Pump>();
|
||||
if (pump == null) continue;
|
||||
|
||||
if (item.HasTag("ballast")) continue;
|
||||
|
||||
//if the pump is connected to an item with a steering component, it must be a ballast pump
|
||||
//(This may not work correctly if the signals are passed through some fancy circuit or a wifi component,
|
||||
//which is why sub creators are encouraged to tag the ballast pumps)
|
||||
bool connectedToSteering = false;
|
||||
foreach (Connection c in item.Connections)
|
||||
{
|
||||
if (c.IsPower) continue;
|
||||
if (item.GetConnectedComponentsRecursive<Steering>(c).Count > 0)
|
||||
{
|
||||
connectedToSteering = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (connectedToSteering) continue;
|
||||
|
||||
if (orderOption.ToLowerInvariant() == "stop pumping")
|
||||
{
|
||||
if (!pump.IsActive || pump.FlowPercentage == 0.0f) continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!pump.Item.InWater) continue;
|
||||
if (pump.IsActive && pump.FlowPercentage <= -90.0f) continue;
|
||||
}
|
||||
|
||||
pumps.Add(pump);
|
||||
}
|
||||
|
||||
|
||||
foreach (Pump pump in pumps)
|
||||
{
|
||||
AddSubObjective(new AIObjectiveOperateItem(pump, character, orderOption, false));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
using Barotrauma.Items.Components;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class AIObjectiveRepairItem : AIObjective
|
||||
{
|
||||
private Item item;
|
||||
|
||||
public AIObjectiveRepairItem(Character character, Item item)
|
||||
: base(character, "")
|
||||
{
|
||||
this.item = item;
|
||||
}
|
||||
|
||||
public override float GetPriority(AIObjectiveManager objectiveManager)
|
||||
{
|
||||
bool insufficientSkills = true;
|
||||
bool repairablesFound = false;
|
||||
foreach (Repairable repairable in item.Repairables)
|
||||
{
|
||||
if (item.Condition > repairable.ShowRepairUIThreshold) { continue; }
|
||||
if (repairable.DegreeOfSuccess(character) >= 0.5f) { insufficientSkills = false; }
|
||||
repairablesFound = true;
|
||||
}
|
||||
|
||||
if (!repairablesFound) { return 0.0f; }
|
||||
|
||||
float priority = 100.0f - 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) +
|
||||
Math.Abs(character.WorldPosition.Y - item.WorldPosition.Y) * 2.0f;
|
||||
|
||||
//heavily increase the priority if the item is already selected
|
||||
//so characters don't keep switching between nearby damaged items
|
||||
if (character.SelectedConstruction == item)
|
||||
{
|
||||
priority += 50.0f;
|
||||
}
|
||||
if (insufficientSkills)
|
||||
{
|
||||
return MathHelper.Lerp(0.0f, 50.0f, priority / 100.0f / Math.Max(dist / 100.0f, 1.0f));
|
||||
}
|
||||
else
|
||||
{
|
||||
return MathHelper.Lerp(50.0f, 100.0f, priority / 100.0f / Math.Max(dist / 100.0f, 1.0f));
|
||||
}
|
||||
}
|
||||
|
||||
public override bool IsCompleted()
|
||||
{
|
||||
foreach (Repairable repairable in item.Repairables)
|
||||
{
|
||||
if (item.Condition < Math.Max(repairable.ShowRepairUIThreshold, item.Prefab.Health * 0.98f)) return false;
|
||||
}
|
||||
|
||||
character?.Speak(TextManager.Get("DialogItemRepaired").Replace("[itemname]", item.Name), null, 0.0f, "itemrepaired", 10.0f);
|
||||
return true;
|
||||
}
|
||||
|
||||
public override bool IsDuplicate(AIObjective otherObjective)
|
||||
{
|
||||
return otherObjective is AIObjectiveRepairItem repairObjective && repairObjective.item == item;
|
||||
}
|
||||
|
||||
protected override void Act(float deltaTime)
|
||||
{
|
||||
foreach (Repairable repairable in item.Repairables)
|
||||
{
|
||||
//make sure we have all the items required to fix the target item
|
||||
foreach (var kvp in repairable.requiredItems)
|
||||
{
|
||||
foreach (RelatedItem requiredItem in kvp.Value)
|
||||
{
|
||||
if (!character.Inventory.Items.Any(it => it != null && requiredItem.MatchesItem(it)))
|
||||
{
|
||||
AddSubObjective(new AIObjectiveGetItem(character, requiredItem.Identifiers, true));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (character.CanInteractWith(item))
|
||||
{
|
||||
foreach (Repairable repairable in item.Repairables)
|
||||
{
|
||||
if (character.SelectedConstruction != item) { item.TryInteract(character, true, true); }
|
||||
repairable.CurrentFixer = character;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
AddSubObjective(new AIObjectiveGoTo(item, character));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
using Barotrauma.Items.Components;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class AIObjectiveRepairItems : AIObjective
|
||||
{
|
||||
/// <summary>
|
||||
/// Should the character only attempt to fix items they have the skills to fix, or any damaged item
|
||||
/// </summary>
|
||||
public bool RequireAdequateSkills;
|
||||
|
||||
public AIObjectiveRepairItems(Character character)
|
||||
: base(character, "")
|
||||
{
|
||||
}
|
||||
|
||||
public override float GetPriority(AIObjectiveManager objectiveManager)
|
||||
{
|
||||
GetBrokenItems();
|
||||
if (subObjectives.Count > 0 && objectiveManager.CurrentOrder == this)
|
||||
{
|
||||
return AIObjectiveManager.OrderPriority;
|
||||
}
|
||||
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
public override bool IsCompleted()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public override bool IsDuplicate(AIObjective otherObjective)
|
||||
{
|
||||
return otherObjective is AIObjectiveRepairItems repairItems && repairItems.RequireAdequateSkills == RequireAdequateSkills;
|
||||
}
|
||||
|
||||
protected override void Act(float deltaTime)
|
||||
{
|
||||
GetBrokenItems();
|
||||
}
|
||||
|
||||
private void GetBrokenItems()
|
||||
{
|
||||
foreach (Item item in Item.ItemList)
|
||||
{
|
||||
//ignore items that are in full condition
|
||||
if (item.Condition >= 100.0f) continue;
|
||||
foreach (Repairable repairable in item.Repairables)
|
||||
{
|
||||
//ignore ones that are already fixed
|
||||
if (item.Condition > repairable.ShowRepairUIThreshold) continue;
|
||||
|
||||
if (RequireAdequateSkills)
|
||||
{
|
||||
if (!repairable.HasRequiredSkills(character)) { continue; }
|
||||
}
|
||||
|
||||
AddSubObjective(new AIObjectiveRepairItem(character, item));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,37 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using Barotrauma.Items.Components;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
/*class AIObjectiveRescue : AIObjective
|
||||
class AIObjectiveRescue : AIObjective
|
||||
{
|
||||
const float TreatmentDelay = 0.5f;
|
||||
|
||||
private readonly Character targetCharacter;
|
||||
|
||||
private AIObjectiveGoTo goToObjective;
|
||||
|
||||
private float treatmentTimer;
|
||||
|
||||
public override bool CanBeCompleted
|
||||
{
|
||||
get
|
||||
{
|
||||
if (targetCharacter.Removed) return false;
|
||||
if (goToObjective != null && !goToObjective.CanBeCompleted) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public AIObjectiveRescue(Character character, Character targetCharacter)
|
||||
: base (character, "")
|
||||
: base(character, "")
|
||||
{
|
||||
Debug.Assert(character != targetCharacter);
|
||||
|
||||
this.targetCharacter = targetCharacter;
|
||||
}
|
||||
|
||||
@@ -21,14 +41,189 @@ namespace Barotrauma
|
||||
return rescueObjective != null && rescueObjective.targetCharacter == targetCharacter;
|
||||
}
|
||||
|
||||
public override float GetPriority(Character character)
|
||||
protected override void Act(float deltaTime)
|
||||
{
|
||||
if (targetCharacter.AnimController.CurrentHull == null) return 0.0f;
|
||||
//target in water -> move to a dry place first
|
||||
if (targetCharacter.AnimController.InWater)
|
||||
{
|
||||
if (character.SelectedCharacter != targetCharacter)
|
||||
{
|
||||
if (!character.CanInteractWith(targetCharacter))
|
||||
{
|
||||
AddSubObjective(goToObjective = new AIObjectiveGoTo(targetCharacter, character));
|
||||
}
|
||||
else
|
||||
{
|
||||
character.SelectCharacter(targetCharacter);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
AddSubObjective(new AIObjectiveFindSafety(character));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
float distance = Vector2.DistanceSquared(character.WorldPosition, targetCharacter.WorldPosition);
|
||||
//target not in water -> we can start applying treatment
|
||||
if (!character.CanInteractWith(targetCharacter))
|
||||
{
|
||||
AddSubObjective(goToObjective = new AIObjectiveGoTo(targetCharacter, character));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (character.SelectedCharacter == null)
|
||||
{
|
||||
character?.Speak(TextManager.Get("DialogFoundUnconsciousTarget")
|
||||
.Replace("[targetname]", targetCharacter.Name).Replace("[roomname]", character.CurrentHull.RoomName),
|
||||
null, 1.0f,
|
||||
"foundunconscioustarget" + targetCharacter.Name, 60.0f);
|
||||
}
|
||||
|
||||
return targetCharacter.IsDead ? 1000.0f / distance : 10000.0f / distance;
|
||||
character.SelectCharacter(targetCharacter);
|
||||
GiveTreatment(deltaTime);
|
||||
}
|
||||
}
|
||||
|
||||
}*/
|
||||
protected override bool ShouldInterruptSubObjective(AIObjective subObjective)
|
||||
{
|
||||
if (subObjective is AIObjectiveFindSafety)
|
||||
{
|
||||
if (character.SelectedCharacter != targetCharacter) return true;
|
||||
if (character.AnimController.InWater || targetCharacter.AnimController.InWater) return false;
|
||||
|
||||
foreach (Limb limb in targetCharacter.AnimController.Limbs)
|
||||
{
|
||||
if (!Submarine.RectContains(targetCharacter.CurrentHull.WorldRect, limb.WorldPosition)) return false;
|
||||
}
|
||||
|
||||
return !character.AnimController.InWater && !targetCharacter.AnimController.InWater &&
|
||||
AIObjectiveFindSafety.GetHullSafety(character.CurrentHull, character) > 50.0f;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void GiveTreatment(float deltaTime)
|
||||
{
|
||||
if (treatmentTimer > 0.0f)
|
||||
{
|
||||
treatmentTimer -= deltaTime;
|
||||
}
|
||||
treatmentTimer = TreatmentDelay;
|
||||
|
||||
var allAfflictions = targetCharacter.CharacterHealth.GetAllAfflictions()
|
||||
.Where(a => a.GetVitalityDecrease(targetCharacter.CharacterHealth) > 0)
|
||||
.ToList();
|
||||
|
||||
allAfflictions.Sort((a1, a2) =>
|
||||
{
|
||||
return Math.Sign(a2.GetVitalityDecrease(targetCharacter.CharacterHealth) - a1.GetVitalityDecrease(targetCharacter.CharacterHealth));
|
||||
});
|
||||
|
||||
//check if we already have a suitable treatment for any of the afflictions
|
||||
foreach (Affliction affliction in allAfflictions)
|
||||
{
|
||||
foreach (KeyValuePair<string, float> treatmentSuitability in affliction.Prefab.TreatmentSuitability)
|
||||
{
|
||||
if (treatmentSuitability.Value > 0.0f)
|
||||
{
|
||||
Item matchingItem = character.Inventory.FindItemByIdentifier(treatmentSuitability.Key);
|
||||
if (matchingItem == null) { continue; }
|
||||
ApplyTreatment(affliction, matchingItem);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//didn't have any suitable treatments available, try to find some medical items
|
||||
HashSet<string> suitableItemIdentifiers = new HashSet<string>();
|
||||
foreach (Affliction affliction in allAfflictions)
|
||||
{
|
||||
foreach (KeyValuePair<string, float> treatmentSuitability in affliction.Prefab.TreatmentSuitability)
|
||||
{
|
||||
if (treatmentSuitability.Value > 0.0f)
|
||||
{
|
||||
suitableItemIdentifiers.Add(treatmentSuitability.Key);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (suitableItemIdentifiers.Count > 0)
|
||||
{
|
||||
List<string> itemNameList = new List<string>();
|
||||
foreach (string itemIdentifier in suitableItemIdentifiers)
|
||||
{
|
||||
if (MapEntityPrefab.Find(null, itemIdentifier, showErrorMessages: false) is ItemPrefab itemPrefab)
|
||||
{
|
||||
itemNameList.Add(itemPrefab.Name);
|
||||
}
|
||||
//only list the first 4 items
|
||||
if (itemNameList.Count >= 4) break;
|
||||
}
|
||||
|
||||
if (itemNameList.Count > 0)
|
||||
{
|
||||
string itemListStr = "";
|
||||
if (itemNameList.Count == 1)
|
||||
{
|
||||
itemListStr = itemNameList[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
itemListStr = string.Join(" or ", string.Join(", ", itemNameList.Take(itemNameList.Count - 1)), itemNameList.Last());
|
||||
}
|
||||
character?.Speak(TextManager.Get("DialogListRequiredTreatments")
|
||||
.Replace("[targetname]", targetCharacter.Name)
|
||||
.Replace("[treatmentlist]", itemListStr),
|
||||
null, 2.0f, "listrequiredtreatments" + targetCharacter.Name, 60.0f);
|
||||
}
|
||||
|
||||
character.DeselectCharacter();
|
||||
AddSubObjective(new AIObjectiveGetItem(character, suitableItemIdentifiers.ToArray(), true));
|
||||
}
|
||||
|
||||
character.AnimController.Anim = AnimController.Animation.CPR;
|
||||
}
|
||||
|
||||
private void ApplyTreatment(Affliction affliction, Item item)
|
||||
{
|
||||
var targetLimb = targetCharacter.CharacterHealth.GetAfflictionLimb(affliction);
|
||||
|
||||
bool remove = false;
|
||||
foreach (ItemComponent ic in item.components)
|
||||
{
|
||||
if (!ic.HasRequiredContainedItems(addMessage: false)) continue;
|
||||
#if CLIENT
|
||||
ic.PlaySound(ActionType.OnUse, character.WorldPosition, character);
|
||||
#endif
|
||||
ic.WasUsed = true;
|
||||
ic.ApplyStatusEffects(ActionType.OnUse, 1.0f, targetCharacter, targetLimb);
|
||||
if (ic.DeleteOnUse) remove = true;
|
||||
}
|
||||
|
||||
if (remove)
|
||||
{
|
||||
Entity.Spawner?.AddToRemoveQueue(item);
|
||||
}
|
||||
}
|
||||
|
||||
public override bool IsCompleted()
|
||||
{
|
||||
bool isCompleted = !targetCharacter.IsUnconscious || targetCharacter.IsDead;
|
||||
|
||||
if (isCompleted)
|
||||
{
|
||||
character?.Speak(TextManager.Get("DialogTargetHealed").Replace("[targetname]", targetCharacter.Name),
|
||||
null, 1.0f, "targethealed" + targetCharacter.Name, 60.0f);
|
||||
}
|
||||
|
||||
return isCompleted;
|
||||
}
|
||||
|
||||
public override float GetPriority(AIObjectiveManager objectiveManager)
|
||||
{
|
||||
if (targetCharacter.AnimController.CurrentHull == null) return 0.0f;
|
||||
float distance = Vector2.DistanceSquared(character.WorldPosition, targetCharacter.WorldPosition);
|
||||
return targetCharacter.IsDead ? 1000.0f / distance : 10000.0f / distance;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+17
-7
@@ -3,7 +3,7 @@ using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
/*class AIObjectiveRescueAll : AIObjective
|
||||
class AIObjectiveRescueAll : AIObjective
|
||||
{
|
||||
private List<Character> rescueTargets;
|
||||
|
||||
@@ -18,13 +18,19 @@ namespace Barotrauma
|
||||
return true;
|
||||
}
|
||||
|
||||
public override float GetPriority(Character character)
|
||||
public override float GetPriority(AIObjectiveManager objectiveManager)
|
||||
{
|
||||
GetRescueTargets();
|
||||
if (!rescueTargets.Any()) { return 0.0f; }
|
||||
|
||||
if (objectiveManager.CurrentObjective == this)
|
||||
{
|
||||
return AIObjectiveManager.OrderPriority;
|
||||
}
|
||||
|
||||
//if there are targets to rescue, the priority is slightly less
|
||||
//than the priority of explicit orders given to the character
|
||||
return rescueTargets.Any() ? AIObjectiveManager.OrderPriority - 5.0f : 0.0f;
|
||||
return AIObjectiveManager.OrderPriority - 5.0f;
|
||||
}
|
||||
|
||||
private void GetRescueTargets()
|
||||
@@ -32,9 +38,7 @@ namespace Barotrauma
|
||||
rescueTargets = Character.CharacterList.FindAll(c =>
|
||||
c.AIController is HumanAIController &&
|
||||
c != character &&
|
||||
(c.IsDead || c.IsUnconscious) &&
|
||||
c.AnimController.CurrentHull != null &&
|
||||
AIObjectiveFindSafety.GetHullSafety(c.AnimController.CurrentHull, c) < 50.0f);
|
||||
c.IsUnconscious);
|
||||
}
|
||||
|
||||
protected override void Act(float deltaTime)
|
||||
@@ -44,5 +48,11 @@ namespace Barotrauma
|
||||
AddSubObjective(new AIObjectiveRescue(character, target));
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
public override bool IsCompleted()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,12 @@ namespace Barotrauma
|
||||
private static string ConfigFile = Path.Combine("Content", "Orders.xml");
|
||||
|
||||
public static List<Order> PrefabList;
|
||||
|
||||
public Order Prefab
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public readonly string Name;
|
||||
public readonly string DoingText;
|
||||
@@ -19,15 +25,25 @@ namespace Barotrauma
|
||||
public readonly Sprite SymbolSprite;
|
||||
|
||||
public readonly Type ItemComponentType;
|
||||
public readonly string ItemName;
|
||||
public readonly string[] ItemIdentifiers;
|
||||
|
||||
public readonly string AITag;
|
||||
|
||||
public readonly Color Color;
|
||||
|
||||
//if true, the order is issued to all available characters
|
||||
public bool TargetAllCharacters;
|
||||
|
||||
public readonly float FadeOutTime;
|
||||
|
||||
public Entity TargetEntity;
|
||||
public ItemComponent TargetItemComponent;
|
||||
public readonly bool UseController;
|
||||
|
||||
public ItemComponent TargetItem;
|
||||
|
||||
public Controller ConnectedController;
|
||||
|
||||
public readonly string[] AppropriateJobs;
|
||||
public readonly string[] Options;
|
||||
public readonly string[] OptionNames;
|
||||
|
||||
static Order()
|
||||
{
|
||||
@@ -39,98 +55,125 @@ namespace Barotrauma
|
||||
foreach (XElement orderElement in doc.Root.Elements())
|
||||
{
|
||||
if (orderElement.Name.ToString().ToLowerInvariant() != "order") continue;
|
||||
|
||||
PrefabList.Add(new Order(orderElement));
|
||||
var newOrder = new Order(orderElement);
|
||||
newOrder.Prefab = newOrder;
|
||||
PrefabList.Add(newOrder);
|
||||
}
|
||||
|
||||
//PrefabList.Add(new Order("Follow", "Following"));
|
||||
|
||||
//PrefabList.Add(new Order("Dismiss", "Dismissed"));
|
||||
|
||||
//PrefabList.Add(new Order("Wait", "Wait"));
|
||||
|
||||
//PrefabList.Add(new Order("Operate Reactor", "Operating reactor", typeof(Reactor), new string[] {"Power up", "Shutdown"}));
|
||||
//PrefabList.Add(new Order("Operate Railgun", "Operating railgun", typeof(Turret), new string[] { "Fire at will", "Hold fire" }));
|
||||
|
||||
|
||||
}
|
||||
|
||||
private Order(XElement orderElement)
|
||||
{
|
||||
Name = orderElement.GetAttributeString("name", "Name not found");
|
||||
DoingText = orderElement.GetAttributeString("doingtext", "");
|
||||
AITag = orderElement.GetAttributeString("aitag", "");
|
||||
Name = TextManager.Get("OrderName." + AITag, true) ?? orderElement.GetAttributeString("name", "Name not found");
|
||||
DoingText = TextManager.Get("OrderNameDoing." + AITag, true) ?? orderElement.GetAttributeString("doingtext", "");
|
||||
|
||||
string targetItemName = orderElement.GetAttributeString("targetitemtype", "");
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(targetItemName))
|
||||
string targetItemType = orderElement.GetAttributeString("targetitemtype", "");
|
||||
if (!string.IsNullOrWhiteSpace(targetItemType))
|
||||
{
|
||||
try
|
||||
{
|
||||
ItemComponentType = Type.GetType("Barotrauma.Items.Components." + targetItemName, true, true);
|
||||
ItemComponentType = Type.GetType("Barotrauma.Items.Components." + targetItemType, true, true);
|
||||
}
|
||||
|
||||
catch (Exception e)
|
||||
{
|
||||
DebugConsole.ThrowError("Error in " + ConfigFile + ", item component type " + targetItemName + " not found", e);
|
||||
DebugConsole.ThrowError("Error in " + ConfigFile + ", item component type " + targetItemType + " not found", e);
|
||||
}
|
||||
}
|
||||
|
||||
ItemName = orderElement.GetAttributeString("targetitemname", "");
|
||||
|
||||
Color = new Color(orderElement.GetAttributeVector4("color", new Vector4(1.0f, 1.0f, 1.0f, 1.0f)));
|
||||
|
||||
ItemIdentifiers = orderElement.GetAttributeStringArray("targetitemidentifiers", new string[0], trim: true, convertToLowerInvariant: true);
|
||||
Color = orderElement.GetAttributeColor("color", Color.White);
|
||||
FadeOutTime = orderElement.GetAttributeFloat("fadeouttime", 0.0f);
|
||||
UseController = orderElement.GetAttributeBool("usecontroller", false);
|
||||
TargetAllCharacters = orderElement.GetAttributeBool("targetallcharacters", false);
|
||||
AppropriateJobs = orderElement.GetAttributeStringArray("appropriatejobs", new string[0]);
|
||||
Options = orderElement.GetAttributeStringArray("options", new string[0]);
|
||||
|
||||
string optionStr = orderElement.GetAttributeString("options", "");
|
||||
|
||||
if (string.IsNullOrWhiteSpace(optionStr))
|
||||
string translatedOptionNames = TextManager.Get("OrderOptions." + AITag, true);
|
||||
if (translatedOptionNames == null)
|
||||
{
|
||||
Options = new string[0];
|
||||
OptionNames = orderElement.GetAttributeStringArray("optionnames", new string[0]);
|
||||
}
|
||||
else
|
||||
{
|
||||
Options = optionStr.Split(',');
|
||||
|
||||
for (int i = 0; i<Options.Length; i++)
|
||||
string[] splitOptionNames = translatedOptionNames.Split(',');
|
||||
OptionNames = new string[Options.Length];
|
||||
for (int i = 0; i < Options.Length && i < splitOptionNames.Length; i++)
|
||||
{
|
||||
Options[i] = Options[i].Trim();
|
||||
OptionNames[i] = splitOptionNames[i].Trim();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (OptionNames.Length != Options.Length)
|
||||
{
|
||||
DebugConsole.ThrowError("Error in Order " + Name + " - the number of option names doesn't match the number of options.");
|
||||
OptionNames = Options;
|
||||
}
|
||||
|
||||
foreach (XElement subElement in orderElement.Elements())
|
||||
{
|
||||
if (subElement.Name.ToString().ToLowerInvariant() != "sprite") continue;
|
||||
SymbolSprite = new Sprite(subElement);
|
||||
break;
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "sprite":
|
||||
SymbolSprite = new Sprite(subElement);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Order(string name, string doingText, Type itemComponentType, string[] parameters = null)
|
||||
|
||||
public Order(Order prefab, Entity targetEntity, ItemComponent targetItem)
|
||||
{
|
||||
Name = name;
|
||||
DoingText = doingText;
|
||||
ItemComponentType = itemComponentType;
|
||||
Options = parameters == null ? new string[0] : parameters;
|
||||
}
|
||||
Prefab = prefab;
|
||||
|
||||
public Order(Order prefab, ItemComponent targetItem)
|
||||
{
|
||||
Name = prefab.Name;
|
||||
AITag = prefab.AITag;
|
||||
DoingText = prefab.DoingText;
|
||||
ItemComponentType = prefab.ItemComponentType;
|
||||
Options = prefab.Options;
|
||||
SymbolSprite = prefab.SymbolSprite;
|
||||
Color = prefab.Color;
|
||||
UseController = prefab.UseController;
|
||||
TargetAllCharacters = prefab.TargetAllCharacters;
|
||||
AppropriateJobs = prefab.AppropriateJobs;
|
||||
FadeOutTime = prefab.FadeOutTime;
|
||||
|
||||
TargetItem = targetItem;
|
||||
TargetEntity = targetEntity;
|
||||
if (targetItem != null)
|
||||
{
|
||||
if (UseController)
|
||||
{
|
||||
var controllers = targetItem.Item.GetConnectedComponents<Controller>();
|
||||
if (controllers.Count > 0) ConnectedController = controllers[0];
|
||||
}
|
||||
TargetEntity = targetItem.Item;
|
||||
TargetItemComponent = targetItem;
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasAppropriateJob(Character character)
|
||||
{
|
||||
if (AppropriateJobs == null || AppropriateJobs.Length == 0) return true;
|
||||
if (character.Info == null || character.Info.Job == null) return false;
|
||||
for (int i = 0; i < AppropriateJobs.Length; i++)
|
||||
{
|
||||
if (character.Info.Job.Prefab.Identifier.ToLowerInvariant() == AppropriateJobs[i].ToLowerInvariant()) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private Order(string name, string doingText, string[] parameters = null)
|
||||
:this (name,doingText, null, parameters)
|
||||
public string GetChatMessage(string targetCharacterName, string targetRoomName, string orderOption = "")
|
||||
{
|
||||
orderOption = orderOption ?? "";
|
||||
|
||||
string messageTag = "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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
@@ -154,24 +155,29 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public SteeringPath FindPath(Vector2 start, Vector2 end)
|
||||
{
|
||||
System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
|
||||
sw.Start();
|
||||
|
||||
public SteeringPath FindPath(Vector2 start, Vector2 end, string errorMsgStr)
|
||||
{
|
||||
float closestDist = 0.0f;
|
||||
PathNode startNode = null;
|
||||
foreach (PathNode node in nodes)
|
||||
{
|
||||
Vector2 nodePos = node.Position;
|
||||
|
||||
float dist = System.Math.Abs(start.X - nodePos.X) +
|
||||
System.Math.Abs(start.Y - nodePos.Y) * 10.0f; //higher cost for vertical movement
|
||||
|
||||
float xDiff = System.Math.Abs(start.X - nodePos.X);
|
||||
float yDiff = System.Math.Abs(start.Y - nodePos.Y);
|
||||
|
||||
if (yDiff > 1.0f && node.Waypoint.Ladders == null && node.Waypoint.Stairs == null)
|
||||
{
|
||||
yDiff += 10.0f;
|
||||
}
|
||||
|
||||
float dist = xDiff + (insideSubmarine ? yDiff * 10.0f : yDiff); //higher cost for vertical movement when inside the sub
|
||||
|
||||
//prefer nodes that are closer to the end position
|
||||
dist += Vector2.Distance(end, nodePos) / 10.0f;
|
||||
|
||||
if (dist<closestDist || startNode==null)
|
||||
dist += (Math.Abs(end.X - nodePos.X) + Math.Abs(end.Y - nodePos.Y)) / 2.0f;
|
||||
//much higher cost to waypoints that are outside
|
||||
if (node.Waypoint.CurrentHull == null) dist *= 10.0f;
|
||||
if (dist < closestDist || startNode == null)
|
||||
{
|
||||
//if searching for a path inside the sub, make sure the waypoint is visible
|
||||
if (insideSubmarine)
|
||||
@@ -182,9 +188,9 @@ 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;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -195,7 +201,7 @@ namespace Barotrauma
|
||||
|
||||
if (startNode == null)
|
||||
{
|
||||
DebugConsole.NewMessage("Pathfinding error, couldn't find a start node", Color.DarkRed);
|
||||
DebugConsole.NewMessage("Pathfinding error, couldn't find a start node. "+ errorMsgStr, Color.DarkRed);
|
||||
|
||||
return new SteeringPath();
|
||||
}
|
||||
@@ -207,6 +213,10 @@ namespace Barotrauma
|
||||
Vector2 nodePos = node.Position;
|
||||
|
||||
float dist = Vector2.Distance(end, nodePos);
|
||||
//much higher cost to waypoints that are outside
|
||||
if (node.Waypoint.CurrentHull == null) dist *= 10.0f;
|
||||
//avoid stopping at a doorway
|
||||
if (node.Waypoint.ConnectedDoor != null) dist *= 10.0f;
|
||||
if (dist < closestDist || endNode == null)
|
||||
{
|
||||
//if searching for a path inside the sub, make sure the waypoint is visible
|
||||
@@ -223,16 +233,13 @@ namespace Barotrauma
|
||||
|
||||
if (endNode == null)
|
||||
{
|
||||
DebugConsole.NewMessage("Pathfinding error, couldn't find an end node", Color.DarkRed);
|
||||
DebugConsole.NewMessage("Pathfinding error, couldn't find an end node. "+ errorMsgStr, Color.DarkRed);
|
||||
return new SteeringPath();
|
||||
}
|
||||
|
||||
|
||||
var path = FindPath(startNode,endNode);
|
||||
|
||||
sw.Stop();
|
||||
System.Diagnostics.Debug.WriteLine("findpath: " + sw.ElapsedMilliseconds+" ms");
|
||||
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
@@ -253,10 +260,9 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (startNode == null || endNode == null)
|
||||
{
|
||||
DebugConsole.NewMessage("Pathfinding error, couldn't find matching pathnodes to waypoints", Color.DarkRed);
|
||||
DebugConsole.NewMessage("Pathfinding error, couldn't find matching pathnodes to waypoints.", Color.DarkRed);
|
||||
return new SteeringPath();
|
||||
}
|
||||
|
||||
|
||||
@@ -15,9 +15,7 @@ namespace Barotrauma
|
||||
|
||||
private Vector2 steering;
|
||||
|
||||
//the steering amount when avoiding obstacles
|
||||
//(needs a separate variable because it's only updated when a raycast is done to detect any nearby obstacles)
|
||||
private Vector2 avoidSteering;
|
||||
private Vector2? avoidObstaclePos;
|
||||
private float rayCastTimer;
|
||||
|
||||
private float wanderAngle;
|
||||
@@ -35,19 +33,19 @@ namespace Barotrauma
|
||||
wanderAngle = Rand.Range(0.0f, MathHelper.TwoPi);
|
||||
}
|
||||
|
||||
public void SteeringSeek(Vector2 targetSimPos, float speed = 1.0f)
|
||||
public void SteeringSeek(Vector2 targetSimPos, float speed)
|
||||
{
|
||||
steering += DoSteeringSeek(targetSimPos, speed);
|
||||
}
|
||||
|
||||
public void SteeringWander(float speed = 1.0f)
|
||||
public void SteeringWander(float speed)
|
||||
{
|
||||
steering += DoSteeringWander(speed);
|
||||
}
|
||||
|
||||
public void SteeringAvoid(float deltaTime, float speed)
|
||||
public void SteeringAvoid(float deltaTime, float lookAheadDistance, float speed)
|
||||
{
|
||||
steering += DoSteeringAvoid(deltaTime, speed);
|
||||
steering += DoSteeringAvoid(deltaTime, lookAheadDistance, speed);
|
||||
}
|
||||
|
||||
public void SteeringManual(float deltaTime, Vector2 velocity)
|
||||
@@ -70,7 +68,7 @@ namespace Barotrauma
|
||||
steering.Y = 0.0f;
|
||||
}
|
||||
|
||||
public virtual void Update(float speed = 1.0f)
|
||||
public virtual void Update(float speed)
|
||||
{
|
||||
if (steering == Vector2.Zero || !MathUtils.IsValid(steering))
|
||||
{
|
||||
@@ -88,7 +86,7 @@ namespace Barotrauma
|
||||
host.Steering = steering;
|
||||
}
|
||||
|
||||
protected virtual Vector2 DoSteeringSeek(Vector2 target, float speed = 1.0f)
|
||||
protected virtual Vector2 DoSteeringSeek(Vector2 target, float speed)
|
||||
{
|
||||
Vector2 targetVel = target - host.SimPosition;
|
||||
|
||||
@@ -108,9 +106,9 @@ namespace Barotrauma
|
||||
return newSteering;
|
||||
}
|
||||
|
||||
protected virtual Vector2 DoSteeringWander(float speed = 1.0f)
|
||||
protected virtual Vector2 DoSteeringWander(float speed)
|
||||
{
|
||||
Vector2 circleCenter = (host.Steering == Vector2.Zero) ? new Vector2(speed, 0.0f) : host.Steering;
|
||||
Vector2 circleCenter = (host.Steering == Vector2.Zero) ? Rand.Vector(speed) : host.Steering;
|
||||
circleCenter = Vector2.Normalize(circleCenter) * CircleDistance;
|
||||
|
||||
Vector2 displacement = new Vector2(
|
||||
@@ -132,21 +130,19 @@ namespace Barotrauma
|
||||
return newSteering;
|
||||
}
|
||||
|
||||
protected virtual Vector2 DoSteeringAvoid(float deltaTime, float speed = 1.0f)
|
||||
protected virtual Vector2 DoSteeringAvoid(float deltaTime, float lookAheadDistance, float speed)
|
||||
{
|
||||
if (steering == Vector2.Zero || host.Steering == Vector2.Zero) return Vector2.Zero;
|
||||
|
||||
float maxDistance = 2.0f;
|
||||
|
||||
Vector2 ahead = host.SimPosition + Vector2.Normalize(host.Steering) * maxDistance;
|
||||
|
||||
float maxDistance = lookAheadDistance;
|
||||
if (rayCastTimer <= 0.0f)
|
||||
{
|
||||
Vector2 ahead = host.SimPosition + Vector2.Normalize(host.Steering) * maxDistance;
|
||||
rayCastTimer = RayCastInterval;
|
||||
Body closestBody = Submarine.CheckVisibility(host.SimPosition, ahead);
|
||||
if (closestBody == null)
|
||||
{
|
||||
avoidSteering = Vector2.Zero;
|
||||
avoidObstaclePos = null;
|
||||
return Vector2.Zero;
|
||||
}
|
||||
else
|
||||
@@ -163,19 +159,19 @@ namespace Barotrauma
|
||||
obstaclePosition.X = closestStructure.SimPosition.X;
|
||||
}
|
||||
|
||||
avoidSteering = Vector2.Normalize(Submarine.LastPickedPosition - obstaclePosition);
|
||||
avoidObstaclePos = obstaclePosition;
|
||||
//avoidSteering = Vector2.Normalize(Submarine.LastPickedPosition - obstaclePosition);
|
||||
}
|
||||
else if (closestBody.UserData is Item item)
|
||||
/*else if (closestBody.UserData is Item)
|
||||
{
|
||||
avoidSteering = Vector2.Normalize(Submarine.LastPickedPosition - item.SimPosition);
|
||||
}
|
||||
}*/
|
||||
else
|
||||
{
|
||||
avoidSteering = Vector2.Normalize(host.SimPosition - Submarine.LastPickedPosition);
|
||||
|
||||
avoidObstaclePos = Submarine.LastPickedPosition;
|
||||
//avoidSteering = Vector2.Normalize(host.SimPosition - Submarine.LastPickedPosition);
|
||||
}
|
||||
//failed to normalize (the obstacle to avoid is at the same position as the character?)
|
||||
// -> move to a random direction
|
||||
if (!MathUtils.IsValid(avoidSteering)) avoidSteering = Rand.Vector(1.0f);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -184,7 +180,14 @@ namespace Barotrauma
|
||||
rayCastTimer -= deltaTime;
|
||||
}
|
||||
|
||||
return avoidSteering * speed;
|
||||
if (!avoidObstaclePos.HasValue) return Vector2.Zero;
|
||||
|
||||
Vector2 diff = avoidObstaclePos.Value - host.SimPosition;
|
||||
float dist = diff.Length();
|
||||
|
||||
if (dist > maxDistance) return Vector2.Zero;
|
||||
|
||||
return -diff * (1.0f - dist / maxDistance) * speed;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user