Renamed project folders from Subsurface to Barotrauma

This commit is contained in:
Regalis
2017-06-04 15:00:53 +03:00
parent ad03c8bf0d
commit 94c6a8ea1b
697 changed files with 157 additions and 211 deletions
@@ -0,0 +1,71 @@
using Lidgren.Network;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace Barotrauma
{
class AIController : ISteerable
{
public enum AiState { None, Attack, GoTo, Escape }
public enum SteeringState { Wander, Seek, Escape }
public bool Enabled;
public readonly Character Character;
protected AiState state;
protected SteeringManager steeringManager;
public SteeringManager SteeringManager
{
get { return steeringManager; }
}
public Vector2 Steering
{
get { return Character.AnimController.TargetMovement; }
set { Character.AnimController.TargetMovement = value; }
}
public Vector2 SimPosition
{
get { return Character.SimPosition; }
}
public Vector2 WorldPosition
{
get { return Character.WorldPosition; }
}
public Vector2 Velocity
{
get { return Character.AnimController.Collider.LinearVelocity; }
}
public AiState State
{
get { return state; }
set { state = value; }
}
public AIController (Character c)
{
Character = c;
Enabled = true;
}
public virtual void DebugDraw(SpriteBatch spriteBatch) { }
public virtual void OnAttacked(IDamageable attacker, float amount) { }
public virtual void SelectTarget(AITarget target) { }
public virtual void Update(float deltaTime) { }
//protected Structure lastStructurePicked;
}
}
@@ -0,0 +1,72 @@
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace Barotrauma
{
class AITarget
{
public static bool ShowAITargets;
public static List<AITarget> List = new List<AITarget>();
public readonly Entity Entity;
private float soundRange;
private float sightRange;
public float SoundRange
{
get { return soundRange; }
set { soundRange = Math.Max(value, 0.0f); }
}
public float SightRange
{
get { return sightRange; }
set { sightRange = Math.Max(value, 0.0f); }
}
public Vector2 WorldPosition
{
get { return Entity.WorldPosition; }
}
public Vector2 SimPosition
{
get { return Entity.SimPosition; }
}
public AITarget(Entity e)
{
Entity = e;
List.Add(this);
}
public void Remove()
{
List.Remove(this);
}
public void Draw(SpriteBatch spriteBatch)
{
if (!ShowAITargets) return;
var rangeSprite = GUI.SubmarineIcon;
if (soundRange > 0.0f)
rangeSprite.Draw(spriteBatch,
new Vector2(WorldPosition.X, -WorldPosition.Y),
Color.Cyan * 0.1f, rangeSprite.Origin,
0.0f, soundRange / rangeSprite.size.X);
if (sightRange > 0.0f)
rangeSprite.Draw(spriteBatch,
new Vector2(WorldPosition.X, -WorldPosition.Y),
Color.Orange * 0.1f, rangeSprite.Origin,
0.0f, sightRange / rangeSprite.size.X);
}
}
}
@@ -0,0 +1,313 @@
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Linq;
using System.Collections.Generic;
namespace Barotrauma
{
class CrewCommander
{
private CrewManager crewManager;
private GUIFrame frame;
//GUIListBox characterList;
private bool infoTextShown;
private int characterFrameBottom;
public GUIFrame Frame
{
get { return IsOpen ? frame : null; }
}
public bool IsOpen
{
get;
private set;
}
public CrewCommander(CrewManager crewManager)
{
this.crewManager = crewManager;
}
public void ToggleGUIFrame()
{
IsOpen = !IsOpen;
if (IsOpen)
{
if (!infoTextShown)
{
GUI.AddMessage("Press " + GameMain.Config.KeyBind(InputType.CrewOrders) + " to open/close the command menu", Color.Cyan, 5.0f);
infoTextShown = true;
}
if (frame == null) CreateGUIFrame();
UpdateCharacters();
}
}
private void CreateGUIFrame()
{
frame = new GUIFrame(Rectangle.Empty, Color.Black * 0.6f, null);
frame.Padding = new Vector4(200.0f, 100.0f, 200.0f, 100.0f);
GUIButton closeButton = new GUIButton(new Rectangle(0, 50, 100, 20), "Close", Alignment.BottomCenter, "", frame);
closeButton.OnClicked = (GUIButton button, object userData) =>
{
ToggleGUIFrame();
return false;
};
//UpdateCharacters();
int buttonWidth = 130;
int spacing = 20;
int y = 50;
for (int n = 0; n<2; n++)
{
List<Order> orders = (n==0) ?
Order.PrefabList.FindAll(o => o.ItemComponentType == null && string.IsNullOrEmpty(o.ItemName)) :
Order.PrefabList.FindAll(o => o.ItemComponentType != null || !string.IsNullOrEmpty(o.ItemName));
int startX = -(buttonWidth * orders.Count + spacing * (orders.Count - 1)) / 2;
int i=0;
foreach (Order order in orders)
{
int x = startX + (buttonWidth + spacing) * (i % orders.Count);
if (order.ItemComponentType!=null || !string.IsNullOrEmpty(order.ItemName))
{
List<Item> matchingItems = !string.IsNullOrEmpty(order.ItemName) ?
Item.ItemList.FindAll(it => it.Name == order.ItemName) :
Item.ItemList.FindAll(it => it.components.Any(ic => ic.GetType() == order.ItemComponentType));
int y2 = y;
foreach (Item it in matchingItems)
{
var newOrder = new Order(order, it.components.Find(ic => ic.GetType() == order.ItemComponentType));
CreateOrderButton(new Rectangle(x + buttonWidth / 2, y2, buttonWidth, 20), newOrder, y2==y);
y2 += 25;
}
}
else
{
CreateOrderButton(new Rectangle(x + buttonWidth / 2, y, buttonWidth, 20), order);
}
i++;
}
y += 100;
}
}
private GUIButton CreateOrderButton(Rectangle rect, Order order, bool createSymbol = true)
{
var orderButton = new GUIButton(rect, order.Name, null, Alignment.TopCenter, Alignment.Center, "GUITextBox", frame);
orderButton.Padding = new Vector4(5.0f, 5.0f, 5.0f, 5.0f);
/*orderButton.TextColor = Color.White;
orderButton.Color = Color.Black * 0.5f;
orderButton.HoverColor = Color.LightGray * 0.5f;
orderButton.OutlineColor = Color.LightGray * 0.8f;*/
orderButton.UserData = order;
orderButton.OnClicked = SetOrder;
if (createSymbol)
{
var symbol = new GUIImage(new Rectangle(0,-60,64,64), order.SymbolSprite, Alignment.TopCenter, orderButton);
symbol.Color = order.Color;
orderButton.children.Insert(0, symbol);
orderButton.children.RemoveAt(orderButton.children.Count-1);
}
return orderButton;
}
public void UpdateCharacters()
{
CreateGUIFrame();
List<GUIComponent> prevCharacterFrames = new List<GUIComponent>();
foreach (GUIComponent child in frame.children)
{
if (!(child.UserData is Character)) continue;
prevCharacterFrames.Add(child);
}
foreach (GUIComponent child in prevCharacterFrames)
{
frame.RemoveChild(child);
}
List<Character> aliveCharacters = crewManager.characters.FindAll(c => !c.IsDead && c.AIController is HumanAIController);
characterFrameBottom = 0;
int charactersPerRow = 4;
int spacing = 5;
int i = 0;
foreach (Character character in aliveCharacters)
{
int rowCharacterCount = Math.Min(charactersPerRow, aliveCharacters.Count);
//if (i >= aliveCharacters.Count - charactersPerRow && aliveCharacters.Count % charactersPerRow > 0) rowCharacterCount = aliveCharacters.Count % charactersPerRow;
// rowCharacterCount = Math.Min(rowCharacterCount, aliveCharacters.Count - i);
int startX = -(150 * rowCharacterCount + spacing * (rowCharacterCount - 1)) / 2;
int x = startX + (150 + spacing) * (i % Math.Min(charactersPerRow, aliveCharacters.Count));
int y = (105 + spacing)*((int)Math.Floor((double)i / charactersPerRow));
GUIButton characterButton = new GUIButton(new Rectangle(x+75, y, 150, 40), "", null, Alignment.TopCenter, "GUITextBox", frame);
characterButton.UserData = character;
characterButton.Padding = new Vector4(5.0f, 5.0f, 5.0f, 5.0f);
characterButton.Color = Character.Controlled == character ? Color.Gold : Color.White;
/*characterButton.HoverColor = Color.LightGray * 0.5f;
characterButton.SelectedColor = Color.Gold * 0.6f;
characterButton.OutlineColor = Color.LightGray * 0.8f;*/
characterFrameBottom = Math.Max(characterFrameBottom, characterButton.Rect.Bottom);
string name = character.Info.Name;
if (character.Info.Job != null) name += '\n' + "(" + character.Info.Job.Name + ")";
GUITextBlock textBlock = new GUITextBlock(
new Rectangle(40, 0, 0, 25),
name,
Color.Transparent, null,
Alignment.Left, Alignment.Left,
"", characterButton, false);
textBlock.Font = GUI.SmallFont;
textBlock.Padding = new Vector4(5.0f, 0.0f, 5.0f, 0.0f);
new GUIImage(new Rectangle(-5, -5, 0, 0), character.AnimController.Limbs[0].sprite, Alignment.Left, characterButton);
var humanAi = character.AIController as HumanAIController;
if (humanAi != null && humanAi.CurrentOrder != null)
{
CreateCharacterOrderFrame(characterButton, humanAi.CurrentOrder, humanAi.CurrentOrderOption);
}
i++;
}
foreach (GUIComponent child in frame.children)
{
if (!(child.UserData is Order)) continue;
Rectangle rect = child.Rect;
rect.Y += characterFrameBottom;
child.Rect = rect;
}
}
public void SelectCharacter(Character character)
{
var characterButton = frame.FindChild(character) as GUIButton;
if (characterButton == null) return;
characterButton.Selected = true;
}
private bool SetOrder(GUIButton button, object userData)
{
Order order = userData as Order;
foreach (GUIComponent child in frame.children)
{
var characterButton = child as GUIButton;
characterButton.State = GUIComponent.ComponentState.None;
if (!characterButton.Selected) continue;
characterButton.Selected = false;
CreateCharacterOrderFrame(characterButton, order, "");
var humanAi = (characterButton.UserData as Character).AIController as HumanAIController;
humanAi.SetOrder(order, "");
}
//characterList.Deselect();
return true;
}
private void CreateCharacterOrderFrame(GUIComponent characterFrame, Order order, string selectedOption)
{
var character = characterFrame.UserData as Character;
if (character == null) return;
var humanAi = character.AIController as HumanAIController;
if (humanAi == null) return;
var existingOrder = characterFrame.children.Find(c => c.UserData is Order);
if (existingOrder != null) characterFrame.RemoveChild(existingOrder);
var orderFrame = new GUIFrame(new Rectangle(-5, characterFrame.Rect.Height, characterFrame.Rect.Width, 30 + order.Options.Length * 15), "InnerFrame", characterFrame);
/*orderFrame.OutlineColor = Color.LightGray * 0.5f;*/
orderFrame.Padding = new Vector4(5.0f, 5.0f, 5.0f, 5.0f);
orderFrame.UserData = order;
var img = new GUIImage(new Rectangle(0, 0, 20, 20), order.SymbolSprite, Alignment.TopLeft, orderFrame);
img.Scale = 20.0f / img.SourceRect.Width;
img.Color = order.Color;
img.CanBeFocused = false;
new GUITextBlock(new Rectangle(img.Rect.Width, 0, 0, 20), order.DoingText, "", Alignment.TopLeft, Alignment.CenterLeft, orderFrame);
var optionList = new GUIListBox(new Rectangle(0, 20, 0, 80), Color.Transparent, null, orderFrame);
optionList.UserData = order;
for (int i = 0; i < order.Options.Length; i++ )
{
var optionBox = new GUITextBlock(new Rectangle(0, 0, 0, 15), order.Options[i], "", Alignment.TopLeft, Alignment.CenterLeft, optionList);
optionBox.Font = GUI.SmallFont;
optionBox.UserData = order.Options[i];
if (selectedOption == order.Options[i])
{
optionList.Select(i);
}
}
optionList.OnSelected = SelectOrderOption;
}
private bool SelectOrderOption(GUIComponent component, object userData)
{
string option = userData.ToString();
Order order = component.Parent.UserData as Order;
Character character = component.Parent.Parent.Parent.UserData as Character;
var humanAi = character.AIController as HumanAIController;
if (humanAi == null) return false;
humanAi.SetOrder(order, option);
return true;
}
public void Draw(SpriteBatch spriteBatch)
{
if (!IsOpen) return;
frame.Draw(spriteBatch);
}
}
}
@@ -0,0 +1,576 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Xml.Linq;
using FarseerPhysics;
using Lidgren.Network;
using Microsoft.Xna.Framework;
using FarseerPhysics.Dynamics;
using Microsoft.Xna.Framework.Graphics;
namespace Barotrauma
{
class EnemyAIController : AIController
{
private const float UpdateTargetsInterval = 0.5f;
private const float RaycastInterval = 1.0f;
private bool attackWhenProvoked;
//the preference to attack a specific type of target (-1.0 - 1.0)
//0.0 = doesn't attack targets of the type
//positive values = attacks targets of this type
//negative values = escapes targets of this type
private float attackRooms, attackHumans, attackWeaker, attackStronger;
private SteeringManager outsideSteering, insideSteering;
private float updateTargetsTimer;
private float raycastTimer;
//a timer for attacks such as biting that last for a specific amount of time
//the duration is determined by the attackDuration of the attacking limb
private float attackTimer;
//a "cooldown time" after an attack during which the Character doesn't try to attack again
private float attackCoolDown;
private float coolDownTimer;
//a point in a wall which the Character is currently targeting
private Vector2 wallAttackPos;
//the entity (a wall) which the Character is targeting
private IDamageable targetEntity;
//the limb selected for the current attack
private Limb attackingLimb;
private AITarget selectedAiTarget;
private AITargetMemory selectedTargetMemory;
private float targetValue;
private Dictionary<AITarget, AITargetMemory> targetMemories;
//the eyesight of the NPC (0.0 = blind, 1.0 = sees every target within sightRange)
private float sight;
//how far the NPC can hear targets from (0.0 = deaf, 1.0 = hears every target within soundRange)
private float hearing;
public AITarget SelectedAiTarget
{
get { return selectedAiTarget; }
}
public EnemyAIController(Character c, string file) : base(c)
{
targetMemories = new Dictionary<AITarget, AITargetMemory>();
XDocument doc = ToolBox.TryLoadXml(file);
if (doc == null || doc.Root == null) return;
XElement aiElement = doc.Root.Element("ai");
if (aiElement == null) return;
attackRooms = ToolBox.GetAttributeFloat(aiElement, "attackrooms", 0.0f) / 100.0f;
attackHumans = ToolBox.GetAttributeFloat(aiElement, "attackhumans", 0.0f) / 100.0f;
attackWeaker = ToolBox.GetAttributeFloat(aiElement, "attackweaker", 0.0f) / 100.0f;
attackStronger = ToolBox.GetAttributeFloat(aiElement, "attackstronger", 0.0f) / 100.0f;
attackCoolDown = ToolBox.GetAttributeFloat(aiElement, "attackcooldown", 5.0f);
sight = ToolBox.GetAttributeFloat(aiElement, "sight", 0.0f);
hearing = ToolBox.GetAttributeFloat(aiElement, "hearing", 0.0f);
attackWhenProvoked = ToolBox.GetAttributeBool(aiElement, "attackwhenprovoked", false);
outsideSteering = new SteeringManager(this);
insideSteering = new IndoorsSteeringManager(this, false);
steeringManager = outsideSteering;
state = AiState.None;
}
public override void SelectTarget(AITarget target)
{
selectedAiTarget = target;
selectedTargetMemory = FindTargetMemory(target);
targetValue = 100.0f;
}
public override void Update(float deltaTime)
{
bool ignorePlatforms = (-Character.AnimController.TargetMovement.Y > Math.Abs(Character.AnimController.TargetMovement.X));
if (steeringManager is IndoorsSteeringManager)
{
var currPath = ((IndoorsSteeringManager)steeringManager).CurrentPath;
if (currPath != null && currPath.CurrentNode != null)
{
if (currPath.CurrentNode.SimPosition.Y < Character.AnimController.GetColliderBottom().Y)
{
ignorePlatforms = true;
}
}
}
Character.AnimController.IgnorePlatforms = ignorePlatforms;
if (Character.AnimController is HumanoidAnimController)
{
if (Math.Abs(Character.AnimController.movement.X) > 0.1f && !Character.AnimController.InWater)
{
Character.AnimController.TargetDir = Character.AnimController.movement.X > 0.0f ? Direction.Right : Direction.Left;
}
}
if (updateTargetsTimer > 0.0)
{
updateTargetsTimer -= deltaTime;
}
else
{
UpdateTargets(Character);
updateTargetsTimer = UpdateTargetsInterval;
if (selectedAiTarget == null)
{
state = AiState.None;
}
else
{
state = (targetValue > 0.0f) ? AiState.Attack : AiState.Escape;
}
//if (coolDownTimer >= 0.0f) return;
}
steeringManager = Character.Submarine == null ? outsideSteering : insideSteering;
switch (state)
{
case AiState.None:
UpdateNone(deltaTime);
break;
case AiState.Attack:
UpdateAttack(deltaTime);
break;
}
steeringManager.Update();
}
private void UpdateNone(float deltaTime)
{
//wander around randomly
if (Character.Submarine==null && SimPosition.Y < ConvertUnits.ToSimUnits(SubmarineBody.DamageDepth*0.5f))
{
steeringManager.SteeringManual(deltaTime, Vector2.UnitY);
}
else
{
steeringManager.SteeringAvoid(deltaTime, 0.1f);
steeringManager.SteeringWander(0.5f);
}
attackingLimb = null;
attackTimer = 0.0f;
coolDownTimer -= deltaTime;
}
private void UpdateAttack(float deltaTime)
{
if (selectedAiTarget == null || selectedAiTarget.Entity == null || selectedAiTarget.Entity.Removed)
{
state = AiState.None;
return;
}
selectedTargetMemory.Priority -= deltaTime;
Vector2 attackSimPosition = Character.Submarine == null ? ConvertUnits.ToSimUnits(selectedAiTarget.WorldPosition) : selectedAiTarget.SimPosition;
if (wallAttackPos != Vector2.Zero && targetEntity != null)
{
attackSimPosition = wallAttackPos;
if (selectedAiTarget.Entity != null && Character.Submarine == null && selectedAiTarget.Entity.Submarine != null) attackSimPosition += ConvertUnits.ToSimUnits(selectedAiTarget.Entity.Submarine.Position);
}
if (Math.Abs(Character.AnimController.movement.X) > 0.1f && !Character.AnimController.InWater)
{
Character.AnimController.TargetDir = Character.SimPosition.X < attackSimPosition.X ? Direction.Right : Direction.Left;
}
if (coolDownTimer > 0.0f)
{
UpdateCoolDown(attackSimPosition, deltaTime);
return;
}
if (raycastTimer > 0.0)
{
raycastTimer -= deltaTime;
}
else
{
GetTargetEntity();
raycastTimer = RaycastInterval;
}
//steeringManager.SteeringAvoid(deltaTime, 1.0f);
Limb attackLimb = attackingLimb;
//check if any of the limbs is close enough to attack the target
if (attackingLimb == null)
{
foreach (Limb limb in Character.AnimController.Limbs)
{
if (limb.attack==null) continue;
attackLimb = limb;
if (ConvertUnits.ToDisplayUnits(Vector2.Distance(limb.SimPosition, attackSimPosition)) > limb.attack.Range) continue;
attackingLimb = limb;
break;
}
if (Character.IsRemotePlayer)
{
if (!Character.IsKeyDown(InputType.Attack)) return;
}
}
if (attackLimb != null)
{
steeringManager.SteeringSeek(attackSimPosition - (attackLimb.SimPosition - SimPosition));
if (steeringManager is IndoorsSteeringManager)
{
var indoorsSteering = (IndoorsSteeringManager)steeringManager;
if (indoorsSteering.CurrentPath!=null && (indoorsSteering.CurrentPath.Finished || indoorsSteering.CurrentPath.Unreachable))
{
steeringManager.SteeringManual(deltaTime, attackSimPosition - attackLimb.SimPosition);
}
}
if (attackingLimb != null) UpdateLimbAttack(deltaTime, attackingLimb, attackSimPosition);
}
}
private void UpdateCoolDown(Vector2 attackPosition, float deltaTime)
{
coolDownTimer -= deltaTime;
attackingLimb = null;
if (selectedAiTarget.Entity is Hull ||
Vector2.Distance(attackPosition, Character.AnimController.Limbs[0].SimPosition) < ConvertUnits.ToSimUnits(500.0f))
{
steeringManager.SteeringSeek(attackPosition, -0.8f);
steeringManager.SteeringAvoid(deltaTime, 1.0f);
}
else
{
steeringManager.SteeringSeek(attackPosition, -0.5f);
steeringManager.SteeringAvoid(deltaTime, 1.0f);
}
}
private void GetTargetEntity()
{
targetEntity = null;
if (Character.AnimController.CurrentHull != null)
{
wallAttackPos = Vector2.Zero;
return;
}
//check if there's a wall between the target and the Character
Vector2 rayStart = Character.SimPosition;
Vector2 rayEnd = selectedAiTarget.SimPosition;
if (selectedAiTarget.Entity.Submarine!=null && Character.Submarine==null)
{
rayStart -= ConvertUnits.ToSimUnits(selectedAiTarget.Entity.Submarine.Position);
}
Body closestBody = Submarine.CheckVisibility(rayStart, rayEnd);
if (Submarine.LastPickedFraction == 1.0f || closestBody == null)
{
wallAttackPos = Vector2.Zero;
return;
}
Structure wall = closestBody.UserData as Structure;
if (wall == null)
{
wallAttackPos = Submarine.LastPickedPosition;
if (selectedAiTarget.Entity.Submarine!=null && Character.Submarine == null) wallAttackPos -= ConvertUnits.ToSimUnits(selectedAiTarget.Entity.Submarine.Position);
}
else
{
int sectionIndex = wall.FindSectionIndex(ConvertUnits.ToDisplayUnits(Submarine.LastPickedPosition));
float sectionDamage = wall.SectionDamage(sectionIndex);
for (int i = sectionIndex - 2; i <= sectionIndex + 2; i++)
{
if (wall.SectionBodyDisabled(i))
{
sectionIndex = i;
break;
}
if (wall.SectionDamage(i) > sectionDamage) sectionIndex = i;
}
wallAttackPos = wall.SectionPosition(sectionIndex);
//if (wall.Submarine != null) wallAttackPos += wall.Submarine.Position;
wallAttackPos = ConvertUnits.ToSimUnits(wallAttackPos);
}
targetEntity = closestBody.UserData as IDamageable;
}
public override void OnAttacked(IDamageable attacker, float amount)
{
updateTargetsTimer = Math.Min(updateTargetsTimer, 0.1f);
coolDownTimer *= 0.1f;
if (amount > 0.1f && attackWhenProvoked)
{
attackHumans = 100.0f;
attackRooms = 100.0f;
}
if (attacker==null || attacker.AiTarget==null) return;
AITargetMemory targetMemory = FindTargetMemory(attacker.AiTarget);
targetMemory.Priority += amount;
}
private void UpdateLimbAttack(float deltaTime, Limb limb, Vector2 attackPosition)
{
var damageTarget = (wallAttackPos != Vector2.Zero && targetEntity != null) ? targetEntity : selectedAiTarget.Entity as IDamageable;
limb.UpdateAttack(deltaTime, attackPosition, damageTarget);
if (limb.AttackTimer >= limb.attack.Duration)
{
wallAttackPos = Vector2.Zero;
limb.AttackTimer = 0.0f;
coolDownTimer = attackCoolDown;
}
}
//goes through all the AItargets, evaluates how preferable it is to attack the target,
//whether the Character can see/hear the target and chooses the most preferable target within
//sight/hearing range
public void UpdateTargets(Character character)
{
var prevAiTarget = selectedAiTarget;
selectedAiTarget = null;
selectedTargetMemory = null;
targetValue = 0.0f;
UpdateTargetMemories();
foreach (AITarget target in AITarget.List)
{
if (Level.Loaded != null && target.WorldPosition.Y > Level.Loaded.Size.Y)
{
continue;
}
float valueModifier = 0.0f;
float dist = 0.0f;
IDamageable targetDamageable = target.Entity as IDamageable;
if (targetDamageable!=null && targetDamageable.Health <= 0.0f) continue;
Character targetCharacter = target.Entity as Character;
//ignore the aitarget if it is the Character itself
if (targetCharacter == character) continue;
if (targetCharacter!=null)
{
if (attackHumans == 0.0f || targetCharacter.SpeciesName != "human") continue;
valueModifier = attackHumans;
}
else if (target.Entity!=null && attackRooms != 0.0f)
{
//skip the target if it's the room the Character is inside of
if (character.AnimController.CurrentHull != null && character.AnimController.CurrentHull == target.Entity as Hull) continue;
valueModifier = attackRooms;
}
dist = Vector2.Distance(character.WorldPosition, target.WorldPosition);
//if the target has been within range earlier, the character will notice it more easily
//(i.e. remember where the target was)
if (targetMemories.ContainsKey(target)) dist *= 0.1f;
//ignore target if it's too far to see or hear
if (dist > target.SightRange * sight && dist > target.SoundRange * hearing) continue;
AITargetMemory targetMemory = FindTargetMemory(target);
valueModifier = valueModifier * targetMemory.Priority / dist;
if (Math.Abs(valueModifier) > Math.Abs(targetValue))
{
Vector2 rayStart = character.AnimController.Limbs[0].SimPosition;
Vector2 rayEnd = target.SimPosition;
if (target.Entity.Submarine != null && character.Submarine==null)
{
rayStart -= ConvertUnits.ToSimUnits(target.Entity.Submarine.Position);
}
Body closestBody = Submarine.CheckVisibility(rayStart, rayEnd);
Structure closestStructure = (closestBody == null) ? null : closestBody.UserData as Structure;
if (targetDamageable != null)
{
valueModifier = valueModifier / targetDamageable.Health;
}
else if (closestStructure!=null)
{
valueModifier = valueModifier / ((IDamageable)closestStructure).Health;
}
else
{
valueModifier = valueModifier / 1000.0f;
}
if (selectedAiTarget == null || Math.Abs(valueModifier) > Math.Abs(targetValue))
{
selectedAiTarget = target;
selectedTargetMemory = targetMemory;
targetValue = valueModifier;
}
}
}
if (selectedAiTarget != prevAiTarget)
{
wallAttackPos = Vector2.Zero;
}
}
//find the targetMemory that corresponds to some AItarget or create if there isn't one yet
private AITargetMemory FindTargetMemory(AITarget target)
{
AITargetMemory memory = null;
if (targetMemories.TryGetValue(target, out memory))
{
return memory;
}
memory = new AITargetMemory(100.0f);
targetMemories.Add(target, memory);
return memory;
}
//go through all the targetmemories and delete ones that don't
//have a corresponding AItarget or whose priority is 0.0f
private void UpdateTargetMemories()
{
List<AITarget> toBeRemoved = new List<AITarget>();
foreach(KeyValuePair<AITarget, AITargetMemory> memory in targetMemories)
{
memory.Value.Priority += 0.5f;
if (Math.Abs(memory.Value.Priority) < 1.0f || !AITarget.List.Contains(memory.Key)) toBeRemoved.Add(memory.Key);
}
foreach (AITarget target in toBeRemoved)
{
targetMemories.Remove(target);
}
}
public override void DebugDraw(SpriteBatch spriteBatch)
{
if (Character.IsDead) return;
Vector2 pos = Character.WorldPosition;
pos.Y = -pos.Y;
if (selectedAiTarget!=null)
{
GUI.DrawLine(spriteBatch, pos, new Vector2(selectedAiTarget.WorldPosition.X, -selectedAiTarget.WorldPosition.Y), Color.Red);
if (wallAttackPos!=Vector2.Zero)
{
GUI.DrawRectangle(spriteBatch, ConvertUnits.ToDisplayUnits(new Vector2(wallAttackPos.X, -wallAttackPos.Y)) - new Vector2(10.0f, 10.0f), new Vector2(20.0f, 20.0f), Color.Red, false);
}
GUI.Font.DrawString(spriteBatch, targetValue.ToString(), pos - Vector2.UnitY*20.0f, Color.Red);
}
if (selectedAiTarget != null)
{
GUI.DrawLine(spriteBatch,
new Vector2(Character.DrawPosition.X, -Character.DrawPosition.Y),
new Vector2(selectedAiTarget.WorldPosition.X, -selectedAiTarget.WorldPosition.Y), Color.Red);
}
GUI.Font.DrawString(spriteBatch, targetValue.ToString(), pos - Vector2.UnitY * 80.0f, Color.Red);
GUI.Font.DrawString(spriteBatch, "updatetargets: "+updateTargetsTimer, pos - Vector2.UnitY * 100.0f, Color.Red);
GUI.Font.DrawString(spriteBatch, "cooldown: " + coolDownTimer, pos - Vector2.UnitY * 120.0f, Color.Red);
IndoorsSteeringManager pathSteering = steeringManager as IndoorsSteeringManager;
if (pathSteering == null || pathSteering.CurrentPath == null || pathSteering.CurrentPath.CurrentNode == null) return;
GUI.DrawLine(spriteBatch,
new Vector2(Character.DrawPosition.X, -Character.DrawPosition.Y),
new Vector2(pathSteering.CurrentPath.CurrentNode.DrawPosition.X, -pathSteering.CurrentPath.CurrentNode.DrawPosition.Y),
Color.LightGreen);
for (int i = 1; i < pathSteering.CurrentPath.Nodes.Count; i++)
{
GUI.DrawLine(spriteBatch,
new Vector2(pathSteering.CurrentPath.Nodes[i].DrawPosition.X, -pathSteering.CurrentPath.Nodes[i].DrawPosition.Y),
new Vector2(pathSteering.CurrentPath.Nodes[i - 1].DrawPosition.X, -pathSteering.CurrentPath.Nodes[i - 1].DrawPosition.Y),
Color.LightGreen);
GUI.SmallFont.DrawString(spriteBatch,
pathSteering.CurrentPath.Nodes[i].ID.ToString(),
new Vector2(pathSteering.CurrentPath.Nodes[i].DrawPosition.X, -pathSteering.CurrentPath.Nodes[i].DrawPosition.Y - 10),
Color.LightGreen);
}
}
}
//the "memory" of the Character
//keeps track of how preferable it is to attack a specific target
//(if the Character can't inflict much damage the target, the priority decreases
//and if the target attacks the Character, the priority increases)
class AITargetMemory
{
private float priority;
public float Priority
{
get { return priority; }
set { priority = MathHelper.Clamp(value, 1.0f, 100.0f); }
}
public AITargetMemory(float priority)
{
this.priority = priority;
}
}
}
@@ -0,0 +1,196 @@
using Microsoft.Xna.Framework;
using System;
namespace Barotrauma
{
class HumanAIController : AIController
{
public static bool DisableCrewAI;
const float UpdateObjectiveInterval = 0.5f;
private AIObjectiveManager objectiveManager;
private AITarget selectedAiTarget;
private float updateObjectiveTimer;
public Order CurrentOrder
{
get;
private set;
}
public string CurrentOrderOption
{
get;
private set;
}
public HumanAIController(Character c) : base(c)
{
steeringManager = new IndoorsSteeringManager(this, true);
objectiveManager = new AIObjectiveManager(c);
objectiveManager.AddObjective(new AIObjectiveFindSafety(c));
objectiveManager.AddObjective(new AIObjectiveIdle(c));
updateObjectiveTimer = Rand.Range(0.0f, UpdateObjectiveInterval);
if (GameMain.GameSession!=null && GameMain.GameSession.CrewManager!=null)
{
CurrentOrder = Order.PrefabList.Find(o => o.Name.ToLowerInvariant() == "dismissed");
objectiveManager.SetOrder(CurrentOrder, "");
GameMain.GameSession.CrewManager.SetCharacterOrder(Character, CurrentOrder);
}
}
public override void Update(float deltaTime)
{
if (DisableCrewAI || Character.IsUnconscious) return;
Character.ClearInputs();
//steeringManager = Character.AnimController.CurrentHull == null ? outdoorsSteeringManager : indoorsSteeringManager;
if (updateObjectiveTimer>0.0f)
{
updateObjectiveTimer -= deltaTime;
}
else
{
objectiveManager.UpdateObjectives();
updateObjectiveTimer = UpdateObjectiveInterval;
}
objectiveManager.DoCurrentObjective(deltaTime);
float currObjectivePriority = objectiveManager.GetCurrentPriority(Character);
float moveSpeed = MathHelper.Clamp(currObjectivePriority/10.0f, 1.0f, 3.0f);
steeringManager.Update(moveSpeed);
bool ignorePlatforms = Character.AnimController.TargetMovement.Y < -0.5f &&
(-Character.AnimController.TargetMovement.Y > Math.Abs(Character.AnimController.TargetMovement.X));
var currPath = (steeringManager as IndoorsSteeringManager).CurrentPath;
if (currPath != null && currPath.CurrentNode != null)
{
if (currPath.CurrentNode.SimPosition.Y < Character.AnimController.GetColliderBottom().Y)
{
ignorePlatforms = true;
}
}
Character.AnimController.IgnorePlatforms = ignorePlatforms;
(Character.AnimController as HumanoidAnimController).Crouching = false;
if (!Character.AnimController.InWater)
{
Character.AnimController.TargetMovement = new Vector2(
Character.AnimController.TargetMovement.X,
MathHelper.Clamp(Character.AnimController.TargetMovement.Y, -1.0f, 1.0f)) * Character.SpeedMultiplier;
Character.SpeedMultiplier = 1.0f;
}
if (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));
}
}
//suit can be taken off if there character is inside a hull and there's air in the room
bool canTakeOffSuit = Character.AnimController.CurrentHull != null &&
Character.AnimController.CurrentHull.OxygenPercentage > 30.0f &&
Character.AnimController.CurrentHull.Volume < Character.AnimController.CurrentHull.FullVolume * 0.3f;
//the suit can be taken off and the character is running out of oxygen (couldn't find a tank for the suit?) or idling
//-> take the suit off
if (canTakeOffSuit && (Character.Oxygen < 50.0f || objectiveManager.CurrentObjective is AIObjectiveIdle))
{
var divingSuit = Character.Inventory.FindItem("Diving Suit");
if (divingSuit != null) divingSuit.Drop(Character);
}
if (Character.IsKeyDown(InputType.Aim))
{
var cursorDiffX = Character.CursorPosition.X - Character.Position.X;
if (cursorDiffX > 10.0f)
{
Character.AnimController.TargetDir = Direction.Right;
}
else if (cursorDiffX < -10.0f)
{
Character.AnimController.TargetDir = Direction.Left;
}
if (Character.SelectedConstruction != null) Character.SelectedConstruction.SecondaryUse(deltaTime, Character);
}
else if (Math.Abs(Character.AnimController.TargetMovement.X) > 0.1f && !Character.AnimController.InWater)
{
Character.AnimController.TargetDir = Character.AnimController.TargetMovement.X > 0.0f ? Direction.Right : Direction.Left;
}
}
public override void OnAttacked(IDamageable attacker, float amount)
{
if (amount <= 0.0f) return;
var enemy = attacker as Character;
if (enemy == null || enemy == Character) return;
objectiveManager.AddObjective(new AIObjectiveCombat(Character, enemy));
}
public void SetOrder(Order order, string option)
{
CurrentOrderOption = option;
CurrentOrder = order;
objectiveManager.SetOrder(order, option);
GameMain.GameSession.CrewManager.SetCharacterOrder(Character, order);
}
public override void SelectTarget(AITarget target)
{
selectedAiTarget = target;
}
public override void DebugDraw(Microsoft.Xna.Framework.Graphics.SpriteBatch spriteBatch)
{
if (selectedAiTarget != null)
{
GUI.DrawLine(spriteBatch,
new Vector2(Character.DrawPosition.X, -Character.DrawPosition.Y),
new Vector2(selectedAiTarget.WorldPosition.X, -selectedAiTarget.WorldPosition.Y), Color.Red);
}
IndoorsSteeringManager pathSteering = steeringManager as IndoorsSteeringManager;
if (pathSteering == null || pathSteering.CurrentPath == null || pathSteering.CurrentPath.CurrentNode==null) return;
GUI.DrawLine(spriteBatch,
new Vector2(Character.DrawPosition.X, -Character.DrawPosition.Y),
new Vector2(pathSteering.CurrentPath.CurrentNode.DrawPosition.X, -pathSteering.CurrentPath.CurrentNode.DrawPosition.Y),
Color.LightGreen);
for (int i = 1; i < pathSteering.CurrentPath.Nodes.Count; i++)
{
GUI.DrawLine(spriteBatch,
new Vector2(pathSteering.CurrentPath.Nodes[i].DrawPosition.X, -pathSteering.CurrentPath.Nodes[i].DrawPosition.Y),
new Vector2(pathSteering.CurrentPath.Nodes[i - 1].DrawPosition.X, -pathSteering.CurrentPath.Nodes[i - 1].DrawPosition.Y),
Color.LightGreen);
GUI.SmallFont.DrawString(spriteBatch,
pathSteering.CurrentPath.Nodes[i].ID.ToString(),
new Vector2(pathSteering.CurrentPath.Nodes[i].DrawPosition.X, -pathSteering.CurrentPath.Nodes[i].DrawPosition.Y - 10),
Color.LightGreen);
}
}
}
}
@@ -0,0 +1,32 @@
using Microsoft.Xna.Framework;
namespace Barotrauma
{
interface ISteerable
{
Vector2 Steering
{
get;
set;
}
Vector2 Velocity
{
get;
}
Vector2 SimPosition
{
get;
}
Vector2 WorldPosition
{
get;
}
}
}
@@ -0,0 +1,313 @@
using System;
using System.Linq;
using Microsoft.Xna.Framework;
using Barotrauma.Items.Components;
namespace Barotrauma
{
class IndoorsSteeringManager : SteeringManager
{
private PathFinder pathFinder;
private SteeringPath currentPath;
private bool canOpenDoors;
private Character character;
private Vector2 currentTarget;
private float findPathTimer;
public SteeringPath CurrentPath
{
get { return currentPath; }
}
public PathFinder PathFinder
{
get { return pathFinder; }
}
public Vector2 CurrentTarget
{
get { return currentTarget; }
}
public IndoorsSteeringManager(ISteerable host, bool canOpenDoors)
: base(host)
{
pathFinder = new PathFinder(WayPoint.WayPointList.FindAll(wp => wp.SpawnType == SpawnType.Path), true);
pathFinder.GetNodePenalty = GetNodePenalty;
this.canOpenDoors = canOpenDoors;
character = (host as AIController).Character;
findPathTimer = Rand.Range(0.0f, 1.0f);
}
public override void Update(float speed = 1)
{
base.Update(speed);
findPathTimer -= 1.0f / 60.0f;
}
public void SetPath(SteeringPath path)
{
currentPath = path;
if (path.Nodes.Any()) currentTarget = path.Nodes[path.Nodes.Count - 1].SimPosition;
findPathTimer = 1.0f;
}
protected override Vector2 DoSteeringSeek(Vector2 target, float speed = 1)
{
//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 < -5.0f)
{
if (findPathTimer > 0.0f) return Vector2.Zero;
currentTarget = target;
Vector2 pos = host.SimPosition;
if (character != null && character.Submarine == null)
{
var targetHull = Hull.FindHull(FarseerPhysics.ConvertUnits.ToDisplayUnits(target), null, false);
if (targetHull!=null && targetHull.Submarine != null)
{
pos -= targetHull.SimPosition;
}
}
currentPath = pathFinder.FindPath(pos, target);
findPathTimer = Rand.Range(1.0f,1.2f);
return DiffToCurrentNode();
}
Vector2 diff = DiffToCurrentNode();
var collider = character.AnimController.Collider;
//if not in water and the waypoint is between the top and bottom of the collider, no need to move vertically
if (!character.AnimController.InWater &&
character.AnimController.Anim != AnimController.Animation.Climbing &&
diff.Y < collider.height / 2 + collider.radius)
{
diff.Y = 0.0f;
}
if (diff.LengthSquared() < 0.001f) return -host.Steering;
return Vector2.Normalize(diff) * speed;
}
private Vector2 DiffToCurrentNode()
{
if (currentPath == null || currentPath.Finished || currentPath.Unreachable) return Vector2.Zero;
if (currentPath.Finished)
{
Vector2 pos2 = host.SimPosition;
if (character != null && character.Submarine == null && CurrentPath.Nodes.Last().Submarine != null)
{
//todo: take multiple subs into account
pos2 -= CurrentPath.Nodes.Last().Submarine.SimPosition;
}
return currentTarget-pos2;
}
if (canOpenDoors && !character.LockHands) CheckDoorsInPath();
Vector2 pos = host.SimPosition;
if (character != null && currentPath.CurrentNode != null)
{
if (CurrentPath.CurrentNode.Submarine != null)
{
if (character.Submarine == null)
{
pos -= CurrentPath.CurrentNode.Submarine.SimPosition;
}
else if (character.Submarine != currentPath.CurrentNode.Submarine)
{
pos -= FarseerPhysics.ConvertUnits.ToSimUnits(currentPath.CurrentNode.Submarine.Position-character.Submarine.Position);
}
}
}
if (currentPath.CurrentNode!= null && currentPath.CurrentNode.Ladders!=null)
{
if (character.SelectedConstruction != currentPath.CurrentNode.Ladders.Item && currentPath.CurrentNode.Ladders.Item.IsInsideTrigger(character.WorldPosition))
{
currentPath.CurrentNode.Ladders.Item.Pick(character, false, true);
}
}
var collider = character.AnimController.Collider;
Vector2 colliderBottom = character.AnimController.GetColliderBottom();
if (Math.Abs(collider.SimPosition.X - currentPath.CurrentNode.SimPosition.X) < collider.radius * 2 &&
currentPath.CurrentNode.SimPosition.Y > colliderBottom.Y &&
currentPath.CurrentNode.SimPosition.Y < colliderBottom.Y + 1.5f)
{
currentPath.SkipToNextNode();
}
if (currentPath.CurrentNode == null) return Vector2.Zero;
if (character.AnimController.Anim == AnimController.Animation.Climbing)
{
Vector2 diff = currentPath.CurrentNode.SimPosition - pos;
if (currentPath.CurrentNode.Ladders != null)
{
//climbing ladders -> don't move horizontally
diff.X = 0.0f;
//at the same height as the waypoint
if (Math.Abs(collider.SimPosition.Y - currentPath.CurrentNode.SimPosition.Y) < collider.height / 2 + collider.radius)
{
float heightFromFloor = character.AnimController.GetColliderBottom().Y - character.AnimController.FloorY;
//we can safely skip to the next waypoint if the character is at a safe height above the floor,
//or if the next waypoint in the path is also on ladders
if ((heightFromFloor > 0.0f && heightFromFloor < collider.height) ||
(currentPath.NextNode != null && currentPath.NextNode.Ladders != null))
{
currentPath.SkipToNextNode();
}
}
}
character.AnimController.IgnorePlatforms = false;
return diff;
}
return currentPath.CurrentNode.SimPosition - pos;
}
private void CheckDoorsInPath()
{
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;
bool shouldBeOpen = false;
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;
}
//toggle the door if it's the previous node and open, or if it's current node and closed
if (door.IsOpen != shouldBeOpen)
{
var buttons = door.Item.GetConnectedComponents<Controller>(true);
Controller closestButton = null;
float closestDist = 0.0f;
foreach (Controller controller in buttons)
{
float dist = Vector2.Distance(controller.Item.WorldPosition, character.WorldPosition);
if (dist > controller.Item.PickDistance * 2.0f) continue;
if (dist < closestDist || closestButton == null)
{
closestButton = controller;
closestDist = dist;
}
}
if (closestButton != null)
{
if (!closestButton.HasRequiredItems(character, false) && shouldBeOpen)
{
currentPath.Unreachable = true;
return;
}
closestButton.Item.Pick(character, false, true);
break;
}
}
}
}
private float? GetNodePenalty(PathNode node, PathNode nextNode)
{
if (character == null) return 0.0f;
float penalty = 0.0f;
if (nextNode.Waypoint.ConnectedGap != null && nextNode.Waypoint.ConnectedGap.Open < 0.9f)
{
if (nextNode.Waypoint.ConnectedGap.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 (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;
}
}
if (node.Waypoint!=null && node.Waypoint.CurrentHull!=null)
{
var hull = node.Waypoint.CurrentHull;
if (hull.FireSources.Count > 0)
{
foreach (FireSource fs in hull.FireSources)
{
penalty += fs.Size.X * 10.0f;
}
}
if (character.NeedsAir && hull.Volume / hull.Rect.Width > 100.0f) penalty += 500.0f;
if (character.PressureProtection < 10.0f && hull.Volume > hull.FullVolume) penalty += 1000.0f;
}
return penalty;
}
}
}
@@ -0,0 +1,88 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Barotrauma
{
class AIObjective
{
protected List<AIObjective> subObjectives;
protected float priority;
protected Character character;
protected string option;
public virtual bool IsCompleted()
{
return false;
}
public virtual bool CanBeCompleted
{
get { return true; }
}
public string Option
{
get { return option; }
}
public AIObjective(Character character, string option)
{
subObjectives = new List<AIObjective>();
this.character = character;
this.option = option;
#if DEBUG
IsDuplicate(null);
#endif
}
/// <summary>
/// makes the character act according to the objective, or according to any subobjectives that
/// need to be completed before this one
/// </summary>
public void TryComplete(float deltaTime)
{
subObjectives.RemoveAll(s => s.IsCompleted() || !s.CanBeCompleted);
foreach (AIObjective objective in subObjectives)
{
objective.TryComplete(deltaTime);
return;
}
Act(deltaTime);
}
protected virtual void Act(float deltaTime) { }
public void AddSubObjective(AIObjective objective)
{
if (subObjectives.Any(o => o.IsDuplicate(objective))) return;
subObjectives.Add(objective);
}
public virtual float GetPriority(Character character)
{
return 0.0f;
}
public virtual bool IsDuplicate(AIObjective otherObjective)
{
#if DEBUG
throw new NotImplementedException();
#else
return (this.GetType() == otherObjective.GetType());
#endif
}
}
}
@@ -0,0 +1,130 @@
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Barotrauma
{
class AIObjectiveCombat : AIObjective
{
const float CoolDown = 10.0f;
private Character enemy;
private AIObjectiveFindSafety escapeObjective;
float coolDownTimer;
private readonly float enemyStrength;
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;
}
protected override void Act(float deltaTime)
{
coolDownTimer -= deltaTime;
var weapon = character.Inventory.FindItem("weapon");
if (weapon==null)
{
Escape(deltaTime);
}
else
{
if (!character.SelectedItems.Contains(weapon))
{
character.Inventory.TryPutItem(weapon, 3, false);
weapon.Equip(character);
}
character.CursorPosition = enemy.Position;
character.SetInput(InputType.Aim, false, true);
Vector2 enemyDiff = Vector2.Normalize(enemy.Position - character.Position);
float weaponAngle = ((weapon.body.Dir == 1.0f) ? weapon.body.Rotation : weapon.body.Rotation - MathHelper.Pi);
Vector2 weaponDir = new Vector2((float)Math.Cos(weaponAngle), (float)Math.Sin(weaponAngle));
if (Vector2.Dot(enemyDiff, weaponDir) < 0.9f) return;
List<FarseerPhysics.Dynamics.Body> ignoredBodies = new List<FarseerPhysics.Dynamics.Body>();
foreach (Limb limb in character.AnimController.Limbs)
{
ignoredBodies.Add(limb.body.FarseerBody);
}
var pickedBody = Submarine.PickBody(character.SimPosition, enemy.SimPosition, ignoredBodies);
if (pickedBody != null && !(pickedBody.UserData is Limb)) return;
weapon.Use(deltaTime, character);
}
}
private void Escape(float deltaTime)
{
if (escapeObjective == null)
{
escapeObjective = new AIObjectiveFindSafety(character);
}
if (enemy.AnimController.CurrentHull == character.AnimController.CurrentHull)
{
escapeObjective.OverrideCurrentHullSafety = 0.0f;
}
else
{
escapeObjective.OverrideCurrentHullSafety = null;
}
escapeObjective.TryComplete(deltaTime);
//if (Vector2.Distance(character.SimPosition, enemy.SimPosition) < 3.0f)
//{
// character.AIController.SteeringManager.SteeringManual(deltaTime,
// new Vector2(Math.Sign(character.SimPosition.X - enemy.SimPosition.X), 0.0f));
// coolDownTimer = CoolDown;
//}
}
public override bool IsCompleted()
{
return enemy.IsDead || coolDownTimer <= 0.0f;
}
public override float GetPriority(Character character)
{
//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(enemyStrength, character.Health) + enemy.Health / 10.0f;
EnemyAIController enemyAI = enemy.AIController as EnemyAIController;
if (enemyAI != null)
{
if (enemyAI.SelectedAiTarget == character.AiTarget) enemyDanger *= 2.0f;
}
return Math.Max(enemyDanger, AIObjectiveManager.OrderPriority);
}
public override bool IsDuplicate(AIObjective otherObjective)
{
AIObjectiveCombat objective = otherObjective as AIObjectiveCombat;
if (objective == null) return false;
return objective.enemy == enemy;
}
}
}
@@ -0,0 +1,90 @@
using Barotrauma.Items.Components;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Barotrauma
{
class AIObjectiveContainItem: AIObjective
{
private string itemName;
private ItemContainer container;
bool isCompleted;
public bool IgnoreAlreadyContainedItems;
public AIObjectiveContainItem(Character character, string itemName, ItemContainer container)
: base (character, "")
{
this.itemName = itemName;
this.container = container;
//check if the container has room for more items
//canBeCompleted = false;
//foreach (Item contained in container.inventory.Items)
//{
// if (contained != null) continue;
// canBeCompleted = true;
// break;
//}
}
public override bool IsCompleted()
{
return isCompleted || container.Inventory.FindItem(itemName)!=null;
}
protected override void Act(float deltaTime)
{
if (isCompleted) return;
//get the item that should be contained
var itemToContain = character.Inventory.FindItem(itemName);
if (itemToContain == null)
{
var getItem = new AIObjectiveGetItem(character, itemName);
getItem.IgnoreContainedItems = IgnoreAlreadyContainedItems;
AddSubObjective(getItem);
return;
}
if (container.Item.ParentInventory == character.Inventory)
{
var containedItems = container.Inventory.Items;
//if there's already something in the mask (empty oxygen tank?), drop it
var existingItem = containedItems.FirstOrDefault(i => i != null);
if (existingItem != null) existingItem.Drop(character);
character.Inventory.RemoveItem(itemToContain);
container.Inventory.TryPutItem(itemToContain, null);
}
else
{
if (Vector2.Distance(character.Position, container.Item.Position) > container.Item.PickDistance
&& !container.Item.IsInsideTrigger(character.Position))
{
AddSubObjective(new AIObjectiveGoTo(container.Item, character));
return;
}
container.Combine(itemToContain);
}
isCompleted = true;
}
public override bool IsDuplicate(AIObjective otherObjective)
{
AIObjectiveContainItem objective = otherObjective as AIObjectiveContainItem;
if (objective == null) return false;
return objective.itemName == itemName && objective.container == container;
}
}
}
@@ -0,0 +1,89 @@
using Barotrauma.Items.Components;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Barotrauma
{
class AIObjectiveFindDivingGear : AIObjective
{
private AIObjective subObjective;
private string gearName;
public override bool IsCompleted()
{
var item = character.Inventory.FindItem(gearName);
if (item == null) return false;
var containedItems = item.ContainedItems;
var oxygenTank = Array.Find(containedItems, i => i.Name == "Oxygen Tank" && i.Condition > 0.0f);
return oxygenTank != null;
}
public AIObjectiveFindDivingGear(Character character, bool needDivingSuit)
: base(character, "")
{
gearName = needDivingSuit ? "Diving Suit" : "diving";
}
protected override void Act(float deltaTime)
{
var item = character.Inventory.FindItem(gearName);
if (item == null)
{
//get a diving mask/suit first
if (!(subObjective is AIObjectiveGetItem))
{
subObjective = new AIObjectiveGetItem(character, gearName, true);
}
}
else
{
var containedItems = item.ContainedItems;
if (containedItems == null) return;
//check if there's an oxygen tank in the mask
var oxygenTank = Array.Find(containedItems, i => i.Name == "Oxygen Tank");
if (oxygenTank != null)
{
if (oxygenTank.Condition > 0.0f)
{
return;
}
else
{
oxygenTank.Drop();
}
}
if (!(subObjective is AIObjectiveContainItem) || subObjective.IsCompleted())
{
subObjective = new AIObjectiveContainItem(character, "Oxygen Tank", item.GetComponent<ItemContainer>());
}
}
if (subObjective != null)
{
subObjective.TryComplete(deltaTime);
//isCompleted = subObjective.IsCompleted();
}
}
public override float GetPriority(Character character)
{
if (character.AnimController.CurrentHull == null) return 100.0f;
return 100.0f - character.Oxygen;
}
public override bool IsDuplicate(AIObjective otherObjective)
{
return otherObjective is AIObjectiveFindDivingGear;
}
}
}
@@ -0,0 +1,216 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma
{
class AIObjectiveFindSafety : AIObjective
{
const float SearchHullInterval = 3.0f;
const float MinSafety = 50.0f;
private AIObjectiveGoTo goToObjective;
private List<Hull> unreachable;
private float currenthullSafety;
private float searchHullTimer;
private AIObjective divingGearObjective;
public float? OverrideCurrentHullSafety;
public AIObjectiveFindSafety(Character character)
: base(character, "")
{
unreachable = new List<Hull>();
}
protected override void Act(float deltaTime)
{
var currentHull = character.AnimController.CurrentHull;
currenthullSafety = OverrideCurrentHullSafety == null ?
GetHullSafety(currentHull, character) : (float)OverrideCurrentHullSafety;
if (currentHull != null)
{
if (NeedsDivingGear())
{
if (!FindDivingGear(deltaTime)) return;
}
if (currenthullSafety > MinSafety)
{
if (Math.Abs(currentHull.WorldPosition.X - character.WorldPosition.X) > 100.0f)
{
character.AIController.SteeringManager.SteeringSeek(currentHull.SimPosition, 0.5f);
}
else
{
character.AIController.SteeringManager.Reset();
}
character.AIController.SelectTarget(null);
goToObjective = null;
return;
}
}
if (searchHullTimer > 0.0f)
{
searchHullTimer -= deltaTime;
}
else
{
var bestHull = FindBestHull();
if (bestHull != null)
{
goToObjective = new AIObjectiveGoTo(bestHull, character);
}
searchHullTimer = SearchHullInterval;
}
if (goToObjective != null)
{
var pathSteering = character.AIController.SteeringManager as IndoorsSteeringManager;
if (pathSteering!=null && pathSteering.CurrentPath!= null &&
pathSteering.CurrentPath.Unreachable && !unreachable.Contains(goToObjective.Target))
{
unreachable.Add(goToObjective.Target as Hull);
}
goToObjective.TryComplete(deltaTime);
}
}
private bool FindDivingGear(float deltaTime)
{
if (divingGearObjective==null)
{
divingGearObjective = new AIObjectiveFindDivingGear(character, false);
}
if (divingGearObjective.IsCompleted()) return true;
divingGearObjective.TryComplete(deltaTime);
return divingGearObjective.IsCompleted();
}
private Hull FindBestHull()
{
Hull bestHull = null;
float bestValue = currenthullSafety;
foreach (Hull hull in Hull.hullList)
{
if (hull == character.AnimController.CurrentHull || unreachable.Contains(hull)) continue;
float hullValue = GetHullSafety(hull, character);
hullValue -= (float)Math.Sqrt(Math.Abs(character.Position.X - hull.Position.X));
hullValue -= (float)Math.Sqrt(Math.Abs(character.Position.Y - hull.Position.Y) * 2.0f);
if (bestHull == null || hullValue > bestValue)
{
bestHull = hull;
bestValue = hullValue;
}
}
return bestHull;
}
public override bool IsDuplicate(AIObjective otherObjective)
{
return (otherObjective is AIObjectiveFindSafety);
}
private bool NeedsDivingGear()
{
var currentHull = character.AnimController.CurrentHull;
if (currentHull == null) return true;
//there's lots of water in the room -> get a suit
if (currentHull.Volume / currentHull.FullVolume > 0.5f) return true;
if (currentHull.OxygenPercentage < 30.0f) return true;
return false;
}
public override float GetPriority(Character character)
{
if (character.Oxygen < 80.0f)
{
return 150.0f - character.Oxygen;
}
if (character.AnimController.CurrentHull == null) return 5.0f;
currenthullSafety = GetHullSafety(character.AnimController.CurrentHull, character);
priority = 100.0f - currenthullSafety;
var nearbyHulls = character.AnimController.CurrentHull.GetConnectedHulls(3);
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)
{
priority += Math.Max(fireSource.Size.X, 50.0f);
}
}
}
if (NeedsDivingGear())
{
if (divingGearObjective != null && !divingGearObjective.IsCompleted()) priority += 20.0f;
}
return priority;
}
private static float GetHullSafety(Hull hull, Character character)
{
if (hull == null) return 0.0f;
float waterPercentage = (hull.Volume / hull.FullVolume) * 100.0f;
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)
{
fireAmount += Math.Max(fireSource.Size.X, 50.0f);
}
}
}
float safety = 100.0f - fireAmount;
if (waterPercentage > 30.0f && character.OxygenAvailable <= 0.0f) safety -= waterPercentage;
if (hull.OxygenPercentage < 30.0f) safety -= (30.0f - hull.OxygenPercentage) * 5.0f;
return safety;
}
}
}
@@ -0,0 +1,98 @@
using Barotrauma.Items.Components;
using FarseerPhysics;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Barotrauma
{
class AIObjectiveFixLeak : AIObjective
{
private Gap leak;
public Gap Leak
{
get { return leak; }
}
public AIObjectiveFixLeak(Gap leak, Character character)
: base (character, "")
{
this.leak = leak;
}
public override float GetPriority(Character character)
{
if (leak.Open == 0.0f) return 0.0f;
float leakSize = (leak.isHorizontal ? leak.Rect.Height : leak.Rect.Width) * Math.Max(leak.Open, 0.1f);
float dist = Vector2.DistanceSquared(character.SimPosition, leak.SimPosition);
dist = Math.Max(dist/100.0f, 1.0f);
return Math.Min(leakSize/dist, 40.0f);
}
public override bool IsDuplicate(AIObjective otherObjective)
{
AIObjectiveFixLeak fixLeak = otherObjective as AIObjectiveFixLeak;
if (fixLeak == null) return false;
return fixLeak.leak == leak;
}
protected override void Act(float deltaTime)
{
var weldingTool = character.Inventory.FindItem("Welding Tool");
if (weldingTool == null)
{
subObjectives.Add(new AIObjectiveGetItem(character, "Welding Tool", true));
return;
}
else
{
var containedItems = weldingTool.ContainedItems;
if (containedItems == null) return;
var fuelTank = Array.Find(containedItems, i => i.Name == "Welding Fuel Tank" && i.Condition > 0.0f);
if (fuelTank == null)
{
AddSubObjective(new AIObjectiveContainItem(character, "Welding Fuel Tank", weldingTool.GetComponent<ItemContainer>()));
}
}
var repairTool = weldingTool.GetComponent<RepairTool>();
if (repairTool == null) return;
if (Vector2.Distance(character.WorldPosition, leak.WorldPosition) > 300.0f)
{
AddSubObjective(new AIObjectiveGoTo(ConvertUnits.ToSimUnits(GetStandPosition()), character));
}
else
{
AddSubObjective(new AIObjectiveOperateItem(repairTool, character, "", leak));
}
}
private Vector2 GetStandPosition()
{
Vector2 standPos = leak.Position;
var hull = leak.FlowTargetHull;
if (hull == null) return standPos;
if (leak.isHorizontal)
{
standPos += Vector2.UnitX * Math.Sign(hull.Position.X - leak.Position.X) * leak.Rect.Width;
}
else
{
standPos += Vector2.UnitY * Math.Sign(hull.Position.Y - leak.Position.Y) * leak.Rect.Height;
}
return standPos;
}
}
}
@@ -0,0 +1,101 @@
using System;
using Microsoft.Xna.Framework;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma
{
class AIObjectiveFixLeaks : AIObjective
{
const float UpdateGapListInterval = 10.0f;
private float updateGapListTimer;
private AIObjectiveIdle idleObjective;
private List<AIObjectiveFixLeak> objectiveList;
public AIObjectiveFixLeaks(Character character)
: base (character, "")
{
objectiveList = new List<AIObjectiveFixLeak>();
}
public override bool IsCompleted()
{
return false;
}
protected override void Act(float deltaTime)
{
updateGapListTimer -= deltaTime;
if (updateGapListTimer<=0.0f)
{
UpdateGapList();
updateGapListTimer = UpdateGapListInterval;
}
if (objectiveList.Any())
{
objectiveList[objectiveList.Count - 1].TryComplete(deltaTime);
if (!objectiveList[objectiveList.Count - 1].CanBeCompleted ||
objectiveList[objectiveList.Count - 1].IsCompleted())
{
objectiveList.RemoveAt(objectiveList.Count - 1);
}
}
else
{
if (idleObjective == null) idleObjective = new AIObjectiveIdle(character);
idleObjective.TryComplete(deltaTime);
}
}
private void UpdateGapList()
{
objectiveList.Clear();
foreach (Gap gap in Gap.GapList)
{
if (gap.ConnectedWall == null) continue;
if (gap.ConnectedDoor != null || gap.Open <= 0.0f) continue;
float gapPriority = GetGapFixPriority(gap);
int index = 0;
while (index < objectiveList.Count &&
GetGapFixPriority(objectiveList[index].Leak) < gapPriority)
{
index++;
}
objectiveList.Insert(index, new AIObjectiveFixLeak(gap, character));
}
}
private float GetGapFixPriority(Gap gap)
{
if (gap == null) return 0.0f;
//larger gap -> higher priority
float gapPriority = (gap.isHorizontal ? gap.Rect.Width : gap.Rect.Height) * gap.Open;
//prioritize gaps that are close
gapPriority /= Math.Max(Vector2.Distance(character.WorldPosition, gap.WorldPosition), 1.0f);
//gaps to outside are much higher priority
if (!gap.IsRoomToRoom) gapPriority *= 10.0f;
return gapPriority;
}
public override bool IsDuplicate(AIObjective otherObjective)
{
return otherObjective is AIObjectiveFixLeaks;
}
}
}
@@ -0,0 +1,183 @@
using Barotrauma.Items.Components;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Barotrauma
{
class AIObjectiveGetItem : AIObjective
{
private string itemName;
private Item targetItem, moveToTarget;
private int currSearchIndex;
private bool canBeCompleted;
public bool IgnoreContainedItems;
private AIObjectiveGoTo goToObjective;
private bool equip;
public override bool CanBeCompleted
{
get { return canBeCompleted; }
}
public AIObjectiveGetItem(Character character, Item targetItem, bool equip = false)
: base(character, "")
{
canBeCompleted = true;
this.equip = equip;
currSearchIndex = 0;
this.targetItem = targetItem;
}
public AIObjectiveGetItem(Character character, string itemName, bool equip=false)
: base (character, "")
{
canBeCompleted = true;
this.equip = equip;
currSearchIndex = 0;
this.itemName = itemName;
}
protected override void Act(float deltaTime)
{
FindTargetItem();
if (targetItem == null || moveToTarget == null) return;
if (Vector2.Distance(character.Position, moveToTarget.Position) < targetItem.PickDistance*2.0f)
{
int targetSlot = -1;
if (equip)
{
var pickable = targetItem.GetComponent<Pickable>();
if (pickable == null)
{
canBeCompleted = false;
return;
}
//check if all the slots required by the item are free
foreach (InvSlotType slots in pickable.AllowedSlots)
{
if (slots.HasFlag(InvSlotType.Any)) continue;
for (int i = 0; i<character.Inventory.Items.Length; i++)
{
//slot not needed by the item, continue
if (!slots.HasFlag(CharacterInventory.limbSlots[i])) continue;
targetSlot = i;
//slot free, continue
if (character.Inventory.Items[i] == null) continue;
//try to move the existing item to LimbSlot.Any and continue if successful
if (character.Inventory.TryPutItem(character.Inventory.Items[i], new List<InvSlotType>() { InvSlotType.Any })) continue;
//if everything else fails, simply drop the existing item
character.Inventory.Items[i].Drop();
}
}
}
targetItem.Pick(character, false, true);
if (targetSlot > -1 && character.Inventory.IsInLimbSlot(targetItem, InvSlotType.Any))
{
character.Inventory.TryPutItem(targetItem, targetSlot, false);
}
}
else
{
if (goToObjective == null)
{
bool gettingDivingGear = itemName == "diving" || itemName == "Diving Gear";
goToObjective = new AIObjectiveGoTo(moveToTarget, character, false, !gettingDivingGear);
}
goToObjective.TryComplete(deltaTime);
}
}
/// <summary>
/// searches for an item that matches the desired item and adds a goto subobjective if one is found
/// </summary>
private void FindTargetItem()
{
if (itemName == null)
{
if (targetItem == null) canBeCompleted = false;
return;
}
float currDist = moveToTarget == null ? 0.0f : Vector2.DistanceSquared(moveToTarget.Position, character.Position);
for (int i = 0; i < 10 && currSearchIndex < Item.ItemList.Count - 2; i++)
{
currSearchIndex++;
var item = Item.ItemList[currSearchIndex];
if (item.CurrentHull == null || item.Condition <= 0.0f) continue;
if (IgnoreContainedItems && item.Container != null) continue;
if (item.Name != itemName && !item.HasTag(itemName)) continue;
//if the item is inside a character's inventory, don't steal it
if (item.ParentInventory is CharacterInventory) continue;
//if the item is inside an item, which is inside a character's inventory, don't steal it
if (item.ParentInventory != null && item.ParentInventory.Owner is Item)
{
if (((Item)item.ParentInventory.Owner).ParentInventory is CharacterInventory) continue;
}
//ignore if item is further away than the currently targeted item
Item rootContainer = item.GetRootContainer();
if (moveToTarget != null && Vector2.DistanceSquared((rootContainer ?? item).Position, character.Position) > currDist) continue;
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;
}
public override bool IsDuplicate(AIObjective otherObjective)
{
AIObjectiveGetItem getItem = otherObjective as AIObjectiveGetItem;
if (getItem == null) return false;
return (getItem.itemName == itemName);
}
public override bool IsCompleted()
{
if (itemName!=null)
{
return character.Inventory.FindItem(itemName) != null;
}
else if (targetItem!= null)
{
return character.Inventory.Items.Contains(targetItem);
}
else
{
return false;
}
}
}
}
@@ -0,0 +1,150 @@
using Barotrauma.Items.Components;
using FarseerPhysics;
using Microsoft.Xna.Framework;
using System;
namespace Barotrauma
{
class AIObjectiveGoTo : AIObjective
{
private Entity target;
private Vector2 targetPos;
private bool repeat;
//how long until the path to the target is declared unreachable
private float waitUntilPathUnreachable;
private bool getDivingGearIfNeeded;
public override bool CanBeCompleted
{
get
{
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;
return (!pathSteering.CurrentPath.Unreachable);
}
}
public Entity Target
{
get { return target; }
}
public AIObjectiveGoTo(Entity target, Character character, bool repeat = false, bool getDivingGearIfNeeded = true)
: base (character, "")
{
this.target = target;
this.repeat = repeat;
waitUntilPathUnreachable = 5.0f;
this.getDivingGearIfNeeded = getDivingGearIfNeeded;
}
public AIObjectiveGoTo(Vector2 simPos, Character character, bool repeat = false, bool getDivingGearIfNeeded = true)
: base(character, "")
{
this.targetPos = simPos;
this.repeat = repeat;
waitUntilPathUnreachable = 5.0f;
this.getDivingGearIfNeeded = getDivingGearIfNeeded;
}
protected override void Act(float deltaTime)
{
if (target == character)
{
character.AIController.SteeringManager.Reset();
return;
}
waitUntilPathUnreachable -= deltaTime;
if (character.SelectedConstruction!=null && character.SelectedConstruction.GetComponent<Ladder>()==null)
{
character.SelectedConstruction = null;
}
if (target != null) character.AIController.SelectTarget(target.AiTarget);
Vector2 currTargetPos = Vector2.Zero;
if (target == null)
{
currTargetPos = targetPos;
}
else
{
currTargetPos = target.SimPosition;
//if character is outside the sub and target isn't, transform the position
if (character.Submarine != null && target.Submarine == null)
{
currTargetPos -= character.Submarine.SimPosition;
}
}
if (Vector2.Distance(currTargetPos, character.SimPosition) < 1.0f)
{
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)
{
AddSubObjective(new AIObjectiveFindDivingGear(character, true));
}
}
}
public override bool IsCompleted()
{
if (repeat) return false;
bool completed = false;
float allowedDistance = 0.5f;
var item = target as Item;
if (item != null)
{
allowedDistance = Math.Max(ConvertUnits.ToSimUnits(item.PickDistance), allowedDistance);
if (item.IsInsideTrigger(character.WorldPosition)) completed = true;
}
completed = completed || Vector2.Distance(target != null ? target.SimPosition : targetPos, character.SimPosition) < allowedDistance;
if (completed) character.AIController.SteeringManager.Reset();
return completed;
}
public override bool IsDuplicate(AIObjective otherObjective)
{
AIObjectiveGoTo objective = otherObjective as AIObjectiveGoTo;
if (objective == null) return false;
if (objective.target == target) return true;
return (objective.targetPos == targetPos);
}
}
}
@@ -0,0 +1,149 @@
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma
{
class AIObjectiveIdle : AIObjective
{
const float WallAvoidDistance = 150.0f;
AITarget currentTarget;
private float newTargetTimer;
public AIObjectiveIdle(Character character) : base(character, "")
{
}
public override float GetPriority(Character character)
{
return 1.0f;
}
protected override void Act(float deltaTime)
{
var pathSteering = character.AIController.SteeringManager as IndoorsSteeringManager;
if (pathSteering==null)
{
return;
}
if (newTargetTimer <= 0.0f)
{
currentTarget = FindRandomTarget();
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;
pathSteering.SetPath(path);
}
newTargetTimer = currentTarget == null ? 5.0f : 15.0f;
}
newTargetTimer -= deltaTime;
//wander randomly
// - if reached the end of the path
// - if the target is unreachable
// - if the path requires going outside
if (pathSteering==null || (pathSteering.CurrentPath != null &&
(pathSteering.CurrentPath.NextNode == null || pathSteering.CurrentPath.Unreachable || pathSteering.CurrentPath.HasOutdoorsNodes)))
{
//steer away from edges of the hull
if (character.AnimController.CurrentHull!=null)
{
float leftDist = character.Position.X - character.AnimController.CurrentHull.Rect.X;
float rightDist = character.AnimController.CurrentHull.Rect.Right - character.Position.X;
if (leftDist < WallAvoidDistance && rightDist < WallAvoidDistance)
{
if (Math.Abs(rightDist - leftDist) > WallAvoidDistance / 2)
{
pathSteering.SteeringManual(deltaTime, Vector2.UnitX * Math.Sign(rightDist - leftDist));
}
else
{
pathSteering.Reset();
return;
}
}
else if (leftDist < WallAvoidDistance)
{
pathSteering.SteeringManual(deltaTime, Vector2.UnitX * (WallAvoidDistance-leftDist)/WallAvoidDistance);
pathSteering.WanderAngle = 0.0f;
return;
}
else if (rightDist < WallAvoidDistance)
{
pathSteering.SteeringManual(deltaTime, -Vector2.UnitX * (WallAvoidDistance-rightDist)/WallAvoidDistance);
pathSteering.WanderAngle = MathHelper.Pi;
return;
}
}
if (character.AnimController.InWater)
{
character.AIController.SteeringManager.SteeringManual(deltaTime, new Vector2(0.0f, 0.5f));
}
else
{
character.AIController.SteeringManager.SteeringWander();
//reset vertical steering to prevent dropping down from platforms etc
character.AIController.SteeringManager.ResetY();
}
return;
}
if (currentTarget == null) return;
character.AIController.SteeringManager.SteeringSeek(currentTarget.SimPosition, 2.0f);
}
private AITarget FindRandomTarget()
{
if (Rand.Int(5)==1)
{
var idCard = character.Inventory.FindItem("ID Card");
if (idCard==null) return null;
foreach (WayPoint wp in WayPoint.WayPointList)
{
if (wp.SpawnType != SpawnType.Human || wp.CurrentHull==null) continue;
foreach (string tag in wp.IdCardTags)
{
if (idCard.HasTag(tag)) return wp.CurrentHull.AiTarget;
}
}
}
else
{
List<Hull> targetHulls = new List<Hull>(Hull.hullList);
//ignore all hulls with fires or water in them
targetHulls.RemoveAll(h => h.FireSources.Any() || (h.Volume/h.FullVolume)>0.1f);
if (!targetHulls.Any()) return null;
return targetHulls[Rand.Range(0, targetHulls.Count)].AiTarget;
}
return null;
}
public override bool IsDuplicate(AIObjective otherObjective)
{
return (otherObjective is AIObjectiveIdle);
}
}
}
@@ -0,0 +1,95 @@
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma
{
class AIObjectiveManager
{
public const float OrderPriority = 50.0f;
private List<AIObjective> objectives;
private Character character;
private AIObjective currentObjective;
public AIObjective CurrentObjective
{
get
{
if (currentObjective != null) return currentObjective;
return objectives.Any() ? objectives[0] : null;
}
}
public AIObjectiveManager(Character character)
{
this.character = character;
objectives = new List<AIObjective>();
}
public void AddObjective(AIObjective objective)
{
if (objectives.Find(o => o.IsDuplicate(objective)) != null) return;
objectives.Add(objective);
}
public float GetCurrentPriority(Character character)
{
if (currentObjective != null) return OrderPriority;
return (CurrentObjective == null) ? 0.0f : CurrentObjective.GetPriority(character);
}
public void UpdateObjectives()
{
if (!objectives.Any()) return;
//remove completed objectives and ones that can't be completed
objectives = objectives.FindAll(o => !o.IsCompleted() && o.CanBeCompleted);
//sort objectives according to priority
objectives.Sort((x, y) => y.GetPriority(character).CompareTo(x.GetPriority(character)));
}
public void DoCurrentObjective(float deltaTime)
{
if (currentObjective != null && (!objectives.Any() || objectives[0].GetPriority(character) < OrderPriority))
{
currentObjective.TryComplete(deltaTime);
return;
}
if (!objectives.Any()) return;
objectives[0].TryComplete(deltaTime);
}
public void SetOrder(Order order, string option)
{
if (order == null) return;
currentObjective = null;
switch (order.Name.ToLowerInvariant())
{
case "follow":
currentObjective = new AIObjectiveGoTo(Character.Controlled, character, true);
break;
case "wait":
currentObjective = new AIObjectiveGoTo(character, character, true);
break;
case "fixleaks":
case "fix leaks":
currentObjective = new AIObjectiveFixLeaks(character);
break;
default:
if (order.TargetItem == null) return;
currentObjective = new AIObjectiveOperateItem(order.TargetItem, character, option, null, order.UseController);
break;
}
}
}
}
@@ -0,0 +1,96 @@
using Barotrauma.Items.Components;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Barotrauma
{
class AIObjectiveOperateItem : AIObjective
{
private ItemComponent component, controller;
private Entity operateTarget;
private bool isCompleted;
private bool canBeCompleted;
public override bool CanBeCompleted
{
get
{
return canBeCompleted;
}
}
public Entity OperateTarget
{
get { return operateTarget; }
}
public AIObjectiveOperateItem(ItemComponent item, Character character, string option, Entity operateTarget = null, bool useController = false)
:base (character, option)
{
this.component = item;
this.operateTarget = operateTarget;
if (useController)
{
var controllers = item.Item.GetConnectedComponents<Controller>();
if (controllers.Any()) controller = controllers[0];
}
canBeCompleted = true;
}
protected override void Act(float deltaTime)
{
ItemComponent target = controller == null ? component : controller;
if (target.CanBeSelected)
{
if (Vector2.Distance(character.Position, target.Item.Position) < target.Item.PickDistance
|| target.Item.IsInsideTrigger(character.WorldPosition))
{
if (character.SelectedConstruction != target.Item && target.CanBeSelected)
{
target.Item.Pick(character, false, true);
}
if (component.AIOperate(deltaTime, character, this)) isCompleted = true;
return;
}
AddSubObjective(new AIObjectiveGoTo(target.Item, character));
}
else
{
if (!character.Inventory.Items.Contains(component.Item))
{
AddSubObjective(new AIObjectiveGetItem(character, component.Item, true));
}
else
{
if (component.AIOperate(deltaTime, character, this)) isCompleted = true;
}
}
}
public override bool IsCompleted()
{
return isCompleted;
}
public override bool IsDuplicate(AIObjective otherObjective)
{
AIObjectiveOperateItem operateItem = otherObjective as AIObjectiveOperateItem;
if (operateItem == null) return false;
return (operateItem.component == component);
}
}
}
@@ -0,0 +1,38 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
namespace Barotrauma
{
class AIObjectiveRescue : AIObjective
{
private readonly Character targetCharacter;
public AIObjectiveRescue(Character character, Character targetCharacter)
: base (character, "")
{
Debug.Assert(character != targetCharacter);
this.targetCharacter = targetCharacter;
}
public override bool IsDuplicate(AIObjective otherObjective)
{
AIObjectiveRescue rescueObjective = otherObjective as AIObjectiveRescue;
return rescueObjective != null && rescueObjective.targetCharacter == targetCharacter;
}
public override float GetPriority(Character character)
{
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;
}
}
}
@@ -0,0 +1,50 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Barotrauma
{
class AIObjectiveRescueAll : AIObjective
{
private List<Character> rescueTargets;
public AIObjectiveRescueAll(Character character)
: base (character, "")
{
rescueTargets = new List<Character>();
}
public override bool IsDuplicate(AIObjective otherObjective)
{
return true;
}
public override float GetPriority(Character character)
{
GetRescueTargets();
//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;
}
private void GetRescueTargets()
{
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);
}
protected override void Act(float deltaTime)
{
foreach (Character target in rescueTargets)
{
AddSubObjective(new AIObjectiveRescue(character, target));
}
}
}
}
+139
View File
@@ -0,0 +1,139 @@
using Barotrauma.Items.Components;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml.Linq;
namespace Barotrauma
{
class Order
{
private static string ConfigFile = Path.Combine("Content", "Orders.xml");
public static List<Order> PrefabList;
public readonly string Name;
public readonly string DoingText;
public readonly Sprite SymbolSprite;
public readonly Type ItemComponentType;
public readonly string ItemName;
public readonly Color Color;
public readonly bool UseController;
public ItemComponent TargetItem;
public readonly string[] Options;
static Order()
{
PrefabList = new List<Order>();
XDocument doc = ToolBox.TryLoadXml(ConfigFile);
if (doc == null || doc.Root == null) return;
foreach (XElement orderElement in doc.Root.Elements())
{
if (orderElement.Name.ToString().ToLowerInvariant() != "order") continue;
PrefabList.Add(new Order(orderElement));
}
//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 = ToolBox.GetAttributeString(orderElement, "name", "Name not found");
DoingText = ToolBox.GetAttributeString(orderElement, "doingtext", "");
string targetItemName = ToolBox.GetAttributeString(orderElement, "targetitemtype", "");
if (!string.IsNullOrWhiteSpace(targetItemName))
{
try
{
ItemComponentType = Type.GetType("Barotrauma.Items.Components." + targetItemName, true, true);
}
catch (Exception e)
{
DebugConsole.ThrowError("Error in " + ConfigFile + ", item component type " + targetItemName + " not found", e);
}
}
ItemName = ToolBox.GetAttributeString(orderElement, "targetitemname", "");
Color = new Color(ToolBox.GetAttributeVector4(orderElement, "color", new Vector4(1.0f, 1.0f, 1.0f, 1.0f)));
UseController = ToolBox.GetAttributeBool(orderElement, "usecontroller", false);
string optionStr = ToolBox.GetAttributeString(orderElement, "options", "");
if (string.IsNullOrWhiteSpace(optionStr))
{
Options = new string[0];
}
else
{
Options = optionStr.Split(',');
for (int i = 0; i<Options.Length; i++)
{
Options[i] = Options[i].Trim();
}
}
foreach (XElement subElement in orderElement.Elements())
{
if (subElement.Name.ToString().ToLowerInvariant() != "sprite") continue;
SymbolSprite = new Sprite(subElement);
break;
}
}
private Order(string name, string doingText, Type itemComponentType, string[] parameters = null)
{
Name = name;
DoingText = doingText;
ItemComponentType = itemComponentType;
Options = parameters == null ? new string[0] : parameters;
}
public Order(Order prefab, ItemComponent targetItem)
{
Name = prefab.Name;
DoingText = prefab.DoingText;
ItemComponentType = prefab.ItemComponentType;
Options = prefab.Options;
SymbolSprite = prefab.SymbolSprite;
Color = prefab.Color;
UseController = prefab.UseController;
TargetItem = targetItem;
}
private Order(string name, string doingText, string[] parameters = null)
:this (name,doingText, null, parameters)
{
}
}
}
@@ -0,0 +1,387 @@
using Microsoft.Xna.Framework;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma
{
class PathNode
{
private WayPoint wayPoint;
private int wayPointID;
public int state;
public PathNode Parent;
private Vector2 position;
public float F,G,H;
public List<PathNode> connections;
public List<float> distances;
public WayPoint Waypoint
{
get { return wayPoint; }
}
public Vector2 Position
{
get {return position;}
}
public PathNode(WayPoint wayPoint)
{
this.wayPoint = wayPoint;
this.position = wayPoint.SimPosition;
wayPointID = wayPoint.ID;
connections = new List<PathNode>();
}
public static List<PathNode> GenerateNodes(List<WayPoint> wayPoints)
{
var nodes = new Dictionary<int, PathNode>();
foreach (WayPoint wayPoint in wayPoints)
{
if (wayPoint == null) continue;
if (nodes.ContainsKey(wayPoint.ID))
{
DebugConsole.ThrowError("Error in PathFinder.GenerateNodes (duplicate ID \"" + wayPoint.ID + "\")");
continue;
}
nodes.Add(wayPoint.ID, new PathNode(wayPoint));
}
foreach (KeyValuePair<int,PathNode> node in nodes)
{
foreach (MapEntity linked in node.Value.wayPoint.linkedTo)
{
PathNode connectedNode = null;
nodes.TryGetValue(linked.ID, out connectedNode);
if (connectedNode == null) continue;
node.Value.connections.Add(connectedNode);
}
}
var nodeList = nodes.Values.ToList();
nodeList.RemoveAll(n => n.connections.Count == 0);
foreach (PathNode node in nodeList)
{
node.distances = new List<float>();
for (int i = 0; i< node.connections.Count; i++)
{
node.distances.Add(Vector2.Distance(node.position, node.connections[i].position));
}
}
return nodeList;
}
}
class PathFinder
{
public delegate float? GetNodePenaltyHandler(PathNode node, PathNode prevNode);
public GetNodePenaltyHandler GetNodePenalty;
private List<PathNode> nodes;
private bool insideSubmarine;
public PathFinder(List<WayPoint> wayPoints, bool insideSubmarine = false)
{
nodes = PathNode.GenerateNodes(wayPoints.FindAll(w => w.MoveWithLevel != insideSubmarine));
foreach (WayPoint wp in wayPoints)
{
wp.linkedTo.CollectionChanged += WaypointLinksChanged;
}
this.insideSubmarine = insideSubmarine;
}
void WaypointLinksChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
if (Submarine.Unloading) return;
var waypoints = sender as IEnumerable<MapEntity>;
foreach (MapEntity me in waypoints)
{
WayPoint wp = me as WayPoint;
if (me == null) continue;
var node = nodes.Find(n => n.Waypoint == wp);
if (node == null) return;
if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Remove)
{
for (int i = node.connections.Count - 1; i >= 0; i--)
{
//remove connection if the waypoint isn't connected anymore
if (wp.linkedTo.FirstOrDefault(l => l == node.connections[i].Waypoint) == null)
{
node.connections.RemoveAt(i);
node.distances.RemoveAt(i);
}
}
}
else if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add)
{
for (int i = 0; i < wp.linkedTo.Count; i++)
{
WayPoint connected = wp.linkedTo[i] as WayPoint;
if (connected == null) continue;
//already connected, continue
if (node.connections.Any(n => n.Waypoint == connected)) continue;
var matchingNode = nodes.Find(n => n.Waypoint == connected);
if (matchingNode == null)
{
#if DEBUG
DebugConsole.ThrowError("Waypoint connections were changed, no matching path node found in PathFinder");
#endif
return;
}
node.connections.Add(matchingNode);
node.distances.Add(Vector2.Distance(node.Position, matchingNode.Position));
}
}
}
}
public SteeringPath FindPath(Vector2 start, Vector2 end)
{
System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
sw.Start();
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
//prefer nodes that are closer to the end position
dist += Vector2.Distance(end, nodePos) / 10.0f;
if (dist<closestDist || startNode==null)
{
//if searching for a path inside the sub, make sure the waypoint is visible
if (insideSubmarine)
{
var body = Submarine.PickBody(
start, node.Waypoint.SimPosition, null,
Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionStairs | Physics.CollisionPlatform);
if (body != null)
{
if (body.UserData is Structure && !((Structure)body.UserData).IsPlatform) continue;
if (body.UserData is Item && body.FixtureList[0].CollisionCategories.HasFlag(Physics.CollisionWall)) continue;
}
}
closestDist = dist;
startNode = node;
}
}
if (startNode == null)
{
DebugConsole.NewMessage("Pathfinding error, couldn't find a start node", Color.DarkRed);
return new SteeringPath();
}
closestDist = 0.0f;
PathNode endNode = null;
foreach (PathNode node in nodes)
{
Vector2 nodePos = node.Position;
float dist = Vector2.Distance(end, nodePos);
if (dist < closestDist || endNode == null)
{
//if searching for a path inside the sub, make sure the waypoint is visible
if (insideSubmarine)
{
var body = Submarine.CheckVisibility(end, node.Waypoint.SimPosition);
if (body != null && body.UserData is Structure) continue;
}
closestDist = dist;
endNode = node;
}
}
if (endNode == null)
{
DebugConsole.NewMessage("Pathfinding error, couldn't find an end node", Color.DarkRed);
return new SteeringPath();
}
var path = FindPath(startNode,endNode);
sw.Stop();
System.Diagnostics.Debug.WriteLine("findpath: " + sw.ElapsedMilliseconds+" ms");
return path;
}
public SteeringPath FindPath(WayPoint start, WayPoint end)
{
PathNode startNode=null, endNode=null;
foreach (PathNode node in nodes)
{
if (node.Waypoint == start)
{
startNode = node;
if (endNode != null) break;
}
if (node.Waypoint == end)
{
endNode = node;
if (startNode != null) break;
}
}
if (startNode == null || endNode == null)
{
DebugConsole.NewMessage("Pathfinding error, couldn't find matching pathnodes to waypoints", Color.DarkRed);
return new SteeringPath();
}
return FindPath(startNode, endNode);
}
private SteeringPath FindPath(PathNode start, PathNode end)
{
if (start == end)
{
var path1 = new SteeringPath();
path1.AddNode(start.Waypoint);
return path1;
}
foreach (PathNode node in nodes)
{
node.state = 0;
node.F = 0.0f;
node.G = 0.0f;
node.H = 0.0f;
}
start.state = 1;
while (true)
{
PathNode currNode = null;
float dist = 10000.0f;
foreach (PathNode node in nodes)
{
if (node.state != 1) continue;
if (node.F < dist)
{
dist = node.F;
currNode = node;
}
}
if (currNode == null || currNode == end) break;
currNode.state = 2;
for (int i = 0; i < currNode.connections.Count; i++)
{
PathNode nextNode = currNode.connections[i];
//a node that hasn't been searched yet
if (nextNode.state==0)
{
nextNode.H = Vector2.Distance(nextNode.Position,end.Position);
if (GetNodePenalty != null)
{
float? nodePenalty =GetNodePenalty(currNode, nextNode);
if (nodePenalty == null)
{
nextNode.state = -1;
continue;
}
nextNode.H += (float)nodePenalty;
}
nextNode.G = currNode.G + currNode.distances[i];
nextNode.F = nextNode.G + nextNode.H;
nextNode.Parent = currNode;
nextNode.state = 1;
}
//node that has been searched
else if (nextNode.state==1)
{
float tempG = currNode.G + currNode.distances[i];
//only use if this new route is better than the
//route the node was a part of
if (tempG < nextNode.G)
{
nextNode.G = tempG;
nextNode.F = nextNode.G + nextNode.H;
nextNode.Parent = currNode;
}
}
}
}
if (end.state == 0 || end.Parent == null)
{
//path not found
return new SteeringPath(true);
}
SteeringPath path = new SteeringPath();
List<WayPoint> finalPath = new List<WayPoint>();
PathNode pathNode = end;
while (pathNode != start && pathNode != null)
{
finalPath.Add(pathNode.Waypoint);
//there was one bug report that seems to have been caused by this loop never terminating:
//couldn't reproduce or figure out what caused it, but here's a workaround that prevents the game from crashing in case it happens again
if (finalPath.Count > nodes.Count)
{
DebugConsole.ThrowError("Pathfinding error: constructing final path failed");
return new SteeringPath(true);
}
path.Cost += pathNode.F;
pathNode = pathNode.Parent;
}
finalPath.Add(start.Waypoint);
finalPath.Reverse();
foreach (WayPoint wayPoint in finalPath)
{
path.AddNode(wayPoint);
}
return path;
}
}
}
@@ -0,0 +1,190 @@
using System;
using Microsoft.Xna.Framework;
using FarseerPhysics.Dynamics;
namespace Barotrauma
{
class SteeringManager
{
protected const float CircleDistance = 2.5f;
protected const float CircleRadius = 0.3f;
protected const float RayCastInterval = 0.5f;
protected ISteerable host;
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 float rayCastTimer;
private float wanderAngle;
public float WanderAngle
{
get { return wanderAngle; }
set { wanderAngle = value; }
}
public SteeringManager(ISteerable host)
{
this.host = host;
wanderAngle = Rand.Range(0.0f, MathHelper.TwoPi);
}
public void SteeringSeek(Vector2 targetSimPos, float speed = 1.0f)
{
steering += DoSteeringSeek(targetSimPos, speed);
}
public void SteeringWander(float speed = 1.0f)
{
steering += DoSteeringWander(speed);
}
public void SteeringAvoid(float deltaTime, float speed)
{
steering += DoSteeringAvoid(deltaTime, speed);
}
public void SteeringManual(float deltaTime, Vector2 velocity)
{
steering += velocity;
}
public void Reset()
{
steering = Vector2.Zero;
}
public void ResetX()
{
steering.X = 0.0f;
}
public void ResetY()
{
steering.Y = 0.0f;
}
public virtual void Update(float speed = 1.0f)
{
if (steering == Vector2.Zero || !MathUtils.IsValid(steering))
{
steering = Vector2.Zero;
host.Steering = Vector2.Zero;
return;
}
float steeringSpeed = steering.Length();
if (steeringSpeed>speed)
{
steering = Vector2.Normalize(steering) * Math.Abs(speed);
}
host.Steering = steering;
}
protected virtual Vector2 DoSteeringSeek(Vector2 target, float speed = 1.0f)
{
Vector2 targetVel = target - host.SimPosition;
if (targetVel.LengthSquared() < 0.00001f) return Vector2.Zero;
targetVel = Vector2.Normalize(targetVel) * speed;
Vector2 newSteering = targetVel - host.Steering;
if (newSteering==Vector2.Zero) return Vector2.Zero;
float steeringSpeed = (newSteering + host.Steering).Length();
if (steeringSpeed > Math.Abs(speed))
{
newSteering = Vector2.Normalize(newSteering)*Math.Abs(speed);
}
return newSteering;
}
protected virtual Vector2 DoSteeringWander(float speed = 1.0f)
{
Vector2 circleCenter = (host.Steering == Vector2.Zero) ? new Vector2(speed, 0.0f) : host.Steering;
circleCenter = Vector2.Normalize(circleCenter) * CircleDistance;
Vector2 displacement = new Vector2(
(float)Math.Cos(wanderAngle),
(float)Math.Sin(wanderAngle));
displacement = displacement * CircleRadius;
float angleChange = 1.5f;
wanderAngle += Rand.Range(0.0f, 1.0f) * angleChange - angleChange * 0.5f;
Vector2 newSteering = circleCenter + displacement;
float steeringSpeed = (newSteering + host.Steering).Length();
if (steeringSpeed > speed)
{
newSteering = Vector2.Normalize(newSteering) * speed;
}
return newSteering;
}
protected virtual Vector2 DoSteeringAvoid(float deltaTime, float speed = 1.0f)
{
if (steering == Vector2.Zero || host.Steering == Vector2.Zero) return Vector2.Zero;
float maxDistance = 2.0f;
Vector2 ahead = host.SimPosition + Vector2.Normalize(host.Steering) * maxDistance;
if (rayCastTimer <= 0.0f)
{
rayCastTimer = RayCastInterval;
Body closestBody = Submarine.CheckVisibility(host.SimPosition, ahead);
if (closestBody == null)
{
avoidSteering = Vector2.Zero;
return Vector2.Zero;
}
else
{
Structure closestStructure = closestBody.UserData as Structure;
if (closestStructure != null)
{
Vector2 obstaclePosition = Submarine.LastPickedPosition;
if (closestStructure.IsHorizontal)
{
obstaclePosition.Y = closestStructure.SimPosition.Y;
}
else
{
obstaclePosition.X = closestStructure.SimPosition.X;
}
avoidSteering = Vector2.Normalize(Submarine.LastPickedPosition - obstaclePosition);
}
else if (closestBody.UserData is Item)
{
Item item = (Item)closestBody.UserData;
avoidSteering = Vector2.Normalize(Submarine.LastPickedPosition - item.SimPosition);
}
else
{
avoidSteering = Vector2.Normalize(host.SimPosition - Submarine.LastPickedPosition);
}
}
}
else
{
rayCastTimer -= deltaTime;
}
return avoidSteering * speed;
}
}
}
@@ -0,0 +1,104 @@
using System.Collections.Generic;
using Microsoft.Xna.Framework;
namespace Barotrauma
{
class SteeringPath
{
private List<WayPoint> nodes;
int currentIndex;
public bool Unreachable
{
get;
set;
}
public SteeringPath(bool unreachable = false)
{
nodes = new List<WayPoint>();
Unreachable = unreachable;
}
public void AddNode(WayPoint node)
{
if (node == null) return;
nodes.Add(node);
if (node.CurrentHull == null) HasOutdoorsNodes = true;
}
public bool HasOutdoorsNodes
{
get;
private set;
}
public int CurrentIndex
{
get { return currentIndex; }
}
public float Cost
{
get;
set;
}
public WayPoint PrevNode
{
get
{
if (currentIndex-1 < 0 || currentIndex-1 > nodes.Count - 1) return null;
return nodes[currentIndex-1];
}
}
public WayPoint CurrentNode
{
get
{
if (currentIndex < 0 || currentIndex > nodes.Count - 1) return null;
return nodes[currentIndex];
}
}
public List<WayPoint> Nodes
{
get { return nodes; }
}
public WayPoint NextNode
{
get
{
if (currentIndex+1 < 0 || currentIndex+1 > nodes.Count - 1) return null;
return nodes[currentIndex+1];
}
}
public bool Finished
{
get { return currentIndex >= nodes.Count; }
}
public void SkipToNextNode()
{
currentIndex++;
}
public WayPoint CheckProgress(Vector2 simPosition, float minSimDistance = 0.1f)
{
if (nodes.Count == 0 || currentIndex>nodes.Count-1) return null;
if (Vector2.Distance(simPosition, nodes[currentIndex].SimPosition) < minSimDistance) currentIndex++;
return CurrentNode;
}
public void ClearPath()
{
nodes.Clear();
}
}
}