Renamed project folders from Subsurface to Barotrauma
This commit is contained in:
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class AICharacter : Character
|
||||
{
|
||||
private AIController aiController;
|
||||
|
||||
public override AIController AIController
|
||||
{
|
||||
get { return aiController; }
|
||||
}
|
||||
|
||||
public AICharacter(string file, Vector2 position, CharacterInfo characterInfo = null, bool isNetworkPlayer = false)
|
||||
: base(file, position, characterInfo, isNetworkPlayer)
|
||||
{
|
||||
soundTimer = Rand.Range(0.0f, soundInterval);
|
||||
}
|
||||
|
||||
public void SetAI(AIController aiController)
|
||||
{
|
||||
this.aiController = aiController;
|
||||
}
|
||||
|
||||
public override void Update(Camera cam, float deltaTime)
|
||||
{
|
||||
base.Update(cam, deltaTime);
|
||||
|
||||
if (!Enabled) return;
|
||||
|
||||
float dist = Vector2.DistanceSquared(cam.WorldViewCenter, WorldPosition);
|
||||
if (dist > 8000.0f * 8000.0f)
|
||||
{
|
||||
AnimController.SimplePhysicsEnabled = true;
|
||||
}
|
||||
else if (dist < 7000.0f * 7000.0f)
|
||||
{
|
||||
AnimController.SimplePhysicsEnabled = false;
|
||||
}
|
||||
|
||||
if (IsDead || Health <= 0.0f || IsUnconscious || Stun > 0.0f) return;
|
||||
|
||||
if (Controlled == this || !aiController.Enabled) return;
|
||||
|
||||
if (soundTimer > 0)
|
||||
{
|
||||
soundTimer -= deltaTime;
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (aiController.State)
|
||||
{
|
||||
case AIController.AiState.Attack:
|
||||
PlaySound(CharacterSound.SoundType.Attack);
|
||||
break;
|
||||
default:
|
||||
PlaySound(CharacterSound.SoundType.Idle);
|
||||
break;
|
||||
}
|
||||
soundTimer = soundInterval;
|
||||
}
|
||||
|
||||
aiController.Update(deltaTime);
|
||||
}
|
||||
|
||||
public override void DrawFront(Microsoft.Xna.Framework.Graphics.SpriteBatch spriteBatch,Camera cam)
|
||||
{
|
||||
base.DrawFront(spriteBatch,cam);
|
||||
|
||||
if (GameMain.DebugDraw && !IsDead) aiController.DebugDraw(spriteBatch);
|
||||
}
|
||||
|
||||
public override void AddDamage(CauseOfDeath causeOfDeath, float amount, IDamageable attacker)
|
||||
{
|
||||
base.AddDamage(causeOfDeath, amount, attacker);
|
||||
|
||||
if (attacker!=null) aiController.OnAttacked(attacker, amount);
|
||||
}
|
||||
|
||||
public override AttackResult AddDamage(IDamageable attacker, Vector2 worldPosition, Attack attack, float deltaTime, bool playSound = false)
|
||||
{
|
||||
AttackResult result = base.AddDamage(attacker, worldPosition, attack, deltaTime, playSound);
|
||||
|
||||
aiController.OnAttacked(attacker, (result.Damage + result.Bleeding) / Math.Max(Health, 1.0f));
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
using System.Xml.Linq;
|
||||
using FarseerPhysics;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class AnimController : Ragdoll
|
||||
{
|
||||
public enum Animation { None, Climbing, UsingConstruction, Struggle, CPR };
|
||||
public Animation Anim;
|
||||
|
||||
protected Character character;
|
||||
|
||||
protected float walkSpeed, swimSpeed;
|
||||
|
||||
protected float walkPos;
|
||||
|
||||
protected readonly Vector2 stepSize;
|
||||
protected readonly float legTorque;
|
||||
|
||||
public AnimController(Character character, XElement element)
|
||||
: base(character, element)
|
||||
{
|
||||
this.character = character;
|
||||
|
||||
stepSize = ToolBox.GetAttributeVector2(element, "stepsize", Vector2.One);
|
||||
stepSize = ConvertUnits.ToSimUnits(stepSize);
|
||||
|
||||
walkSpeed = ToolBox.GetAttributeFloat(element, "walkspeed", 1.0f);
|
||||
swimSpeed = ToolBox.GetAttributeFloat(element, "swimspeed", 1.0f);
|
||||
|
||||
legTorque = ToolBox.GetAttributeFloat(element, "legtorque", 0.0f);
|
||||
}
|
||||
|
||||
public virtual void UpdateAnim(float deltaTime) { }
|
||||
|
||||
public virtual void HoldItem(float deltaTime, Item item, Vector2[] handlePos, Vector2 holdPos, Vector2 aimPos, bool aim, float holdAngle) { }
|
||||
|
||||
public virtual void DragCharacter(Character target, LimbType rightHandTarget = LimbType.RightHand, LimbType leftHandTarget = LimbType.LeftHand) { }
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,342 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using FarseerPhysics;
|
||||
using FarseerPhysics.Dynamics.Joints;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class FishAnimController : AnimController
|
||||
{
|
||||
//amplitude and wave length of the "sine wave" swimming animation
|
||||
//if amplitude = 0, sine wave animation isn't used
|
||||
private float waveAmplitude;
|
||||
private float waveLength;
|
||||
|
||||
private bool rotateTowardsMovement;
|
||||
|
||||
private bool mirror, flip;
|
||||
|
||||
private float flipTimer;
|
||||
|
||||
private float? footRotation;
|
||||
|
||||
public FishAnimController(Character character, XElement element)
|
||||
: base(character, element)
|
||||
{
|
||||
waveAmplitude = ConvertUnits.ToSimUnits(ToolBox.GetAttributeFloat(element, "waveamplitude", 0.0f));
|
||||
waveLength = ConvertUnits.ToSimUnits(ToolBox.GetAttributeFloat(element, "wavelength", 0.0f));
|
||||
|
||||
flip = ToolBox.GetAttributeBool(element, "flip", true);
|
||||
mirror = ToolBox.GetAttributeBool(element, "mirror", false);
|
||||
|
||||
float footRot = ToolBox.GetAttributeFloat(element,"footrotation", float.NaN);
|
||||
if (float.IsNaN(footRot))
|
||||
{
|
||||
footRotation = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
footRotation = MathHelper.ToRadians(footRot);
|
||||
}
|
||||
|
||||
rotateTowardsMovement = ToolBox.GetAttributeBool(element, "rotatetowardsmovement", true);
|
||||
}
|
||||
|
||||
public override void UpdateAnim(float deltaTime)
|
||||
{
|
||||
if (Frozen) return;
|
||||
|
||||
if (character.IsDead || character.IsUnconscious || character.Stun > 0.0f)
|
||||
{
|
||||
Collider.FarseerBody.FixedRotation = false;
|
||||
|
||||
if (character.IsRemotePlayer)
|
||||
{
|
||||
MainLimb.pullJoint.WorldAnchorB = Collider.SimPosition;
|
||||
MainLimb.pullJoint.Enabled = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Collider.LinearVelocity = (MainLimb.SimPosition - Collider.SimPosition) * 60.0f;
|
||||
Collider.SmoothRotate(MainLimb.Rotation);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
//re-enable collider
|
||||
if (!Collider.FarseerBody.Enabled)
|
||||
{
|
||||
var lowestLimb = FindLowestLimb();
|
||||
|
||||
Collider.SetTransform(new Vector2(
|
||||
Collider.SimPosition.X,
|
||||
Math.Max(lowestLimb.SimPosition.Y + (Collider.radius + Collider.height / 2), Collider.SimPosition.Y)),
|
||||
0.0f);
|
||||
|
||||
Collider.FarseerBody.Enabled = true;
|
||||
}
|
||||
|
||||
ResetPullJoints();
|
||||
|
||||
if (strongestImpact > 0.0f)
|
||||
{
|
||||
character.Stun = MathHelper.Clamp(strongestImpact * 0.5f, character.Stun, 5.0f);
|
||||
strongestImpact = 0.0f;
|
||||
}
|
||||
|
||||
|
||||
if (inWater)
|
||||
{
|
||||
Collider.FarseerBody.FixedRotation = false;
|
||||
UpdateSineAnim(deltaTime);
|
||||
}
|
||||
else if (currentHull != null && CanEnterSubmarine)
|
||||
{
|
||||
if (Math.Abs(MathUtils.GetShortestAngle(Collider.Rotation, 0.0f)) > 0.001f)
|
||||
{
|
||||
//rotate collider back upright
|
||||
Collider.AngularVelocity = MathUtils.GetShortestAngle(Collider.Rotation, 0.0f) * 60.0f;
|
||||
Collider.FarseerBody.FixedRotation = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
Collider.FarseerBody.FixedRotation = true;
|
||||
}
|
||||
|
||||
UpdateWalkAnim(deltaTime);
|
||||
}
|
||||
|
||||
|
||||
if (mirror || !inWater)
|
||||
{
|
||||
if (!character.IsRemotePlayer)
|
||||
{
|
||||
//targetDir = (movement.X > 0.0f) ? Direction.Right : Direction.Left;
|
||||
if (targetMovement.X > 0.1f && targetMovement.X > Math.Abs(targetMovement.Y) * 0.5f)
|
||||
{
|
||||
TargetDir = Direction.Right;
|
||||
}
|
||||
else if (targetMovement.X < -0.1f && targetMovement.X < -Math.Abs(targetMovement.Y) * 0.5f)
|
||||
{
|
||||
TargetDir = Direction.Left;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
Limb head = GetLimb(LimbType.Head);
|
||||
if (head == null) head = GetLimb(LimbType.Torso);
|
||||
|
||||
float rotation = MathUtils.WrapAngleTwoPi(head.Rotation);
|
||||
rotation = MathHelper.ToDegrees(rotation);
|
||||
|
||||
if (rotation < 0.0f) rotation += 360;
|
||||
|
||||
if (rotation > 20 && rotation < 160)
|
||||
{
|
||||
TargetDir = Direction.Left;
|
||||
}
|
||||
else if (rotation > 200 && rotation < 340)
|
||||
{
|
||||
TargetDir = Direction.Right;
|
||||
}
|
||||
}
|
||||
|
||||
if (!flip) return;
|
||||
|
||||
flipTimer += deltaTime;
|
||||
|
||||
if (TargetDir != Direction.None && TargetDir != dir)
|
||||
{
|
||||
if (flipTimer>1.0f || character.IsRemotePlayer)
|
||||
{
|
||||
Flip();
|
||||
if (mirror || !inWater) Mirror();
|
||||
flipTimer = 0.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateSineAnim(float deltaTime)
|
||||
{
|
||||
movement = TargetMovement*swimSpeed;
|
||||
|
||||
MainLimb.pullJoint.Enabled = true;
|
||||
MainLimb.pullJoint.WorldAnchorB = Collider.SimPosition;
|
||||
|
||||
if (movement.LengthSquared() < 0.00001f) return;
|
||||
|
||||
float movementAngle = MathUtils.VectorToAngle(movement) - MathHelper.PiOver2;
|
||||
|
||||
if (rotateTowardsMovement)
|
||||
{
|
||||
Collider.SmoothRotate(movementAngle, 25.0f);
|
||||
MainLimb.body.SmoothRotate(movementAngle, 25.0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
Collider.SmoothRotate(HeadAngle * Dir, 25.0f);
|
||||
MainLimb.body.SmoothRotate(HeadAngle * Dir, 25.0f);
|
||||
}
|
||||
|
||||
Limb tail = GetLimb(LimbType.Tail);
|
||||
if (tail != null && waveAmplitude > 0.0f)
|
||||
{
|
||||
walkPos -= movement.Length();
|
||||
|
||||
float waveRotation = (float)Math.Sin(walkPos / waveLength);
|
||||
|
||||
tail.body.ApplyTorque(waveRotation * tail.Mass * 100.0f * waveAmplitude);
|
||||
}
|
||||
|
||||
|
||||
for (int i = 0; i < Limbs.Length; i++)
|
||||
{
|
||||
if (Limbs[i].SteerForce <= 0.0f) continue;
|
||||
|
||||
Vector2 pullPos = Limbs[i].pullJoint == null ? Limbs[i].SimPosition : Limbs[i].pullJoint.WorldAnchorA;
|
||||
Limbs[i].body.ApplyForce(movement * Limbs[i].SteerForce * Limbs[i].Mass, pullPos);
|
||||
|
||||
/*if (Limbs[i] == MainLimb) continue;
|
||||
|
||||
float dist = (MainLimb.SimPosition - Limbs[i].SimPosition).Length();
|
||||
|
||||
Vector2 limbPos = MainLimb.SimPosition - Vector2.Normalize(movement) * dist;
|
||||
|
||||
Limbs[i].body.ApplyForce(((limbPos - Limbs[i].SimPosition) * 3.0f - Limbs[i].LinearVelocity * 3.0f) * Limbs[i].Mass);*/
|
||||
}
|
||||
|
||||
Collider.LinearVelocity = Vector2.Lerp(Collider.LinearVelocity, movement, 0.5f);
|
||||
|
||||
floorY = Limbs[0].SimPosition.Y;
|
||||
}
|
||||
|
||||
void UpdateWalkAnim(float deltaTime)
|
||||
{
|
||||
movement = MathUtils.SmoothStep(movement, TargetMovement * walkSpeed, 0.2f);
|
||||
if (movement == Vector2.Zero) return;
|
||||
|
||||
float mainLimbHeight, mainLimbAngle;
|
||||
if (MainLimb.type == LimbType.Torso)
|
||||
{
|
||||
mainLimbHeight = TorsoPosition;
|
||||
mainLimbAngle = torsoAngle;
|
||||
}
|
||||
else
|
||||
{
|
||||
mainLimbHeight = HeadPosition;
|
||||
mainLimbAngle = headAngle;
|
||||
}
|
||||
|
||||
MainLimb.body.SmoothRotate(mainLimbAngle * Dir, 50.0f);
|
||||
|
||||
Collider.LinearVelocity = new Vector2(
|
||||
movement.X,
|
||||
Collider.LinearVelocity.Y > 0.0f ? Collider.LinearVelocity.Y * 0.5f : Collider.LinearVelocity.Y);
|
||||
|
||||
MainLimb.MoveToPos(GetColliderBottom() + Vector2.UnitY * mainLimbHeight, 10.0f);
|
||||
|
||||
MainLimb.pullJoint.Enabled = true;
|
||||
MainLimb.pullJoint.WorldAnchorB = GetColliderBottom() + Vector2.UnitY * mainLimbHeight;
|
||||
|
||||
walkPos -= MainLimb.LinearVelocity.X * 0.05f;
|
||||
|
||||
Vector2 transformedStepSize = new Vector2(
|
||||
(float)Math.Cos(walkPos) * stepSize.X * 3.0f,
|
||||
(float)Math.Sin(walkPos) * stepSize.Y * 2.0f);
|
||||
|
||||
foreach (Limb limb in Limbs)
|
||||
{
|
||||
switch (limb.type)
|
||||
{
|
||||
case LimbType.LeftFoot:
|
||||
case LimbType.RightFoot:
|
||||
Vector2 footPos = new Vector2(limb.SimPosition.X, MainLimb.SimPosition.Y - mainLimbHeight);
|
||||
|
||||
if (limb.RefJointIndex>-1)
|
||||
{
|
||||
RevoluteJoint refJoint = limbJoints[limb.RefJointIndex];
|
||||
footPos.X = refJoint.WorldAnchorA.X;
|
||||
}
|
||||
footPos.X += limb.StepOffset.X * Dir;
|
||||
footPos.Y += limb.StepOffset.Y;
|
||||
|
||||
if (limb.type == LimbType.LeftFoot)
|
||||
{
|
||||
limb.MoveToPos(footPos +new Vector2(
|
||||
transformedStepSize.X + movement.X * 0.1f,
|
||||
(transformedStepSize.Y > 0.0f) ? transformedStepSize.Y : 0.0f),
|
||||
8.0f);
|
||||
}
|
||||
else if (limb.type == LimbType.RightFoot)
|
||||
{
|
||||
limb.MoveToPos(footPos + new Vector2(
|
||||
-transformedStepSize.X + movement.X * 0.1f,
|
||||
(-transformedStepSize.Y > 0.0f) ? -transformedStepSize.Y : 0.0f),
|
||||
8.0f);
|
||||
}
|
||||
|
||||
if (footRotation != null) limb.body.SmoothRotate((float)footRotation * Dir, 50.0f);
|
||||
|
||||
break;
|
||||
case LimbType.LeftLeg:
|
||||
case LimbType.RightLeg:
|
||||
if (legTorque != 0.0f) limb.body.ApplyTorque(limb.Mass * legTorque * Dir);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateDying(float deltaTime)
|
||||
{
|
||||
Limb head = GetLimb(LimbType.Head);
|
||||
Limb tail = GetLimb(LimbType.Tail);
|
||||
|
||||
if (head != null) head.body.ApplyTorque(head.Mass * Dir * (float)Math.Sin(walkPos) * 5.0f);
|
||||
if (tail != null) tail.body.ApplyTorque(tail.Mass * -Dir * (float)Math.Sin(walkPos) * 5.0f);
|
||||
|
||||
walkPos += deltaTime * 5.0f;
|
||||
|
||||
Vector2 centerOfMass = GetCenterOfMass();
|
||||
|
||||
foreach (Limb limb in Limbs)
|
||||
{
|
||||
if (limb.type == LimbType.Head || limb.type == LimbType.Tail) continue;
|
||||
|
||||
limb.body.ApplyForce((centerOfMass - limb.SimPosition) * (float)Math.Sin(walkPos) * limb.Mass * 10.0f);
|
||||
}
|
||||
}
|
||||
|
||||
public override void Flip()
|
||||
{
|
||||
base.Flip();
|
||||
|
||||
foreach (Limb l in Limbs)
|
||||
{
|
||||
if (!l.DoesFlip) continue;
|
||||
|
||||
l.body.SetTransform(l.SimPosition,
|
||||
-l.body.Rotation);
|
||||
}
|
||||
}
|
||||
|
||||
private void Mirror()
|
||||
{
|
||||
Vector2 centerOfMass = GetCenterOfMass();
|
||||
|
||||
foreach (Limb l in Limbs)
|
||||
{
|
||||
TrySetLimbPosition(l,
|
||||
centerOfMass,
|
||||
new Vector2(centerOfMass.X - (l.SimPosition.X - centerOfMass.X), l.SimPosition.Y),
|
||||
true);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,159 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using Barotrauma.Particles;
|
||||
using System;
|
||||
using System.Xml.Linq;
|
||||
using System.Collections.Generic;
|
||||
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
enum CauseOfDeath
|
||||
{
|
||||
Damage, Bloodloss, Pressure, Suffocation, Drowning, Burn, Husk, Disconnected
|
||||
}
|
||||
|
||||
public enum DamageType { None, Blunt, Slash, Burn }
|
||||
|
||||
struct AttackResult
|
||||
{
|
||||
public readonly float Damage;
|
||||
public readonly float Bleeding;
|
||||
|
||||
public readonly bool HitArmor;
|
||||
|
||||
public AttackResult(float damage, float bleeding, bool hitArmor=false)
|
||||
{
|
||||
this.Damage = damage;
|
||||
this.Bleeding = bleeding;
|
||||
|
||||
this.HitArmor = hitArmor;
|
||||
}
|
||||
}
|
||||
|
||||
class Attack
|
||||
{
|
||||
public readonly float Range;
|
||||
public readonly float Duration;
|
||||
|
||||
public readonly DamageType DamageType;
|
||||
|
||||
private readonly float structureDamage;
|
||||
private readonly float damage;
|
||||
private readonly float bleedingDamage;
|
||||
|
||||
private readonly List<StatusEffect> statusEffects;
|
||||
|
||||
public readonly float Force;
|
||||
|
||||
public readonly float Torque;
|
||||
|
||||
public readonly float TargetForce;
|
||||
|
||||
private Sound sound;
|
||||
|
||||
private ParticleEmitterPrefab particleEmitterPrefab;
|
||||
|
||||
public readonly float Stun;
|
||||
|
||||
private float priority;
|
||||
|
||||
public float GetDamage(float deltaTime)
|
||||
{
|
||||
return (Duration == 0.0f) ? damage : damage * deltaTime;
|
||||
}
|
||||
|
||||
public float GetBleedingDamage(float deltaTime)
|
||||
{
|
||||
return (Duration == 0.0f) ? bleedingDamage : bleedingDamage * deltaTime;
|
||||
}
|
||||
|
||||
public float GetStructureDamage(float deltaTime)
|
||||
{
|
||||
return (Duration == 0.0f) ? structureDamage : structureDamage * deltaTime;
|
||||
}
|
||||
|
||||
public Attack(XElement element)
|
||||
{
|
||||
try
|
||||
{
|
||||
DamageType = (DamageType)Enum.Parse(typeof(DamageType), ToolBox.GetAttributeString(element, "damagetype", "None"), true);
|
||||
}
|
||||
catch
|
||||
{
|
||||
DamageType = DamageType.None;
|
||||
}
|
||||
|
||||
|
||||
damage = ToolBox.GetAttributeFloat(element, "damage", 0.0f);
|
||||
structureDamage = ToolBox.GetAttributeFloat(element, "structuredamage", 0.0f);
|
||||
bleedingDamage = ToolBox.GetAttributeFloat(element, "bleedingdamage", 0.0f);
|
||||
|
||||
Force = ToolBox.GetAttributeFloat(element,"force", 0.0f);
|
||||
TargetForce = ToolBox.GetAttributeFloat(element, "targetforce", 0.0f);
|
||||
|
||||
Torque = ToolBox.GetAttributeFloat(element, "torque", 0.0f);
|
||||
|
||||
Stun = ToolBox.GetAttributeFloat(element, "stun", 0.0f);
|
||||
|
||||
string soundPath = ToolBox.GetAttributeString(element, "sound", "");
|
||||
if (!string.IsNullOrWhiteSpace(soundPath))
|
||||
{
|
||||
sound = Sound.Load(soundPath);
|
||||
}
|
||||
|
||||
Range = ToolBox.GetAttributeFloat(element, "range", 0.0f);
|
||||
|
||||
Duration = ToolBox.GetAttributeFloat(element, "duration", 0.0f);
|
||||
|
||||
priority = ToolBox.GetAttributeFloat(element, "priority", 1.0f);
|
||||
|
||||
statusEffects = new List<StatusEffect>();
|
||||
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "particleemitter":
|
||||
particleEmitterPrefab = new ParticleEmitterPrefab(subElement);
|
||||
break;
|
||||
case "statuseffect":
|
||||
statusEffects.Add(StatusEffect.Load(subElement));
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public AttackResult DoDamage(IDamageable attacker, IDamageable target, Vector2 worldPosition, float deltaTime, bool playSound = true)
|
||||
{
|
||||
if (particleEmitterPrefab != null)
|
||||
{
|
||||
particleEmitterPrefab.Emit(worldPosition);
|
||||
}
|
||||
|
||||
if (sound != null)
|
||||
{
|
||||
sound.Play(1.0f, 500.0f, worldPosition);
|
||||
}
|
||||
|
||||
var attackResult = target.AddDamage(attacker, worldPosition, this, deltaTime, playSound);
|
||||
|
||||
var effectType = attackResult.Damage > 0.0f ? ActionType.OnUse : ActionType.OnFailure;
|
||||
|
||||
foreach (StatusEffect effect in statusEffects)
|
||||
{
|
||||
if (effect.Targets.HasFlag(StatusEffect.TargetType.This) && attacker is Character)
|
||||
{
|
||||
effect.Apply(effectType, deltaTime, (Character)attacker, (Character)attacker);
|
||||
}
|
||||
if (effect.Targets.HasFlag(StatusEffect.TargetType.Character) && target is Character)
|
||||
{
|
||||
effect.Apply(effectType, deltaTime, (Character)target, (Character)target);
|
||||
}
|
||||
}
|
||||
|
||||
return attackResult;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,238 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
|
||||
class BackgroundCreature : ISteerable
|
||||
{
|
||||
const float MaxDepth = 100.0f;
|
||||
|
||||
const float CheckWallsInterval = 5.0f;
|
||||
|
||||
public bool Enabled;
|
||||
|
||||
private BackgroundCreaturePrefab prefab;
|
||||
|
||||
private Vector2 position;
|
||||
|
||||
private Vector3 velocity;
|
||||
|
||||
private float depth;
|
||||
|
||||
private SteeringManager steeringManager;
|
||||
|
||||
private float checkWallsTimer;
|
||||
|
||||
public Swarm Swarm;
|
||||
|
||||
Vector2 drawPosition;
|
||||
public Vector2 TransformedPosition
|
||||
{
|
||||
get { return drawPosition; }
|
||||
}
|
||||
|
||||
public Vector2 SimPosition
|
||||
{
|
||||
get { return FarseerPhysics.ConvertUnits.ToSimUnits(position); }
|
||||
}
|
||||
|
||||
public Vector2 WorldPosition
|
||||
{
|
||||
get { return position; }
|
||||
}
|
||||
|
||||
public Vector2 Velocity
|
||||
{
|
||||
get { return new Vector2(velocity.X, velocity.Y); }
|
||||
}
|
||||
|
||||
public Vector2 Steering
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public BackgroundCreature(BackgroundCreaturePrefab prefab, Vector2 position)
|
||||
{
|
||||
this.prefab = prefab;
|
||||
|
||||
this.position = position;
|
||||
|
||||
drawPosition = position;
|
||||
|
||||
steeringManager = new SteeringManager(this);
|
||||
|
||||
velocity = new Vector3(
|
||||
Rand.Range(-prefab.Speed, prefab.Speed),
|
||||
Rand.Range(-prefab.Speed, prefab.Speed),
|
||||
Rand.Range(0.0f, prefab.WanderZAmount));
|
||||
|
||||
checkWallsTimer = Rand.Range(0.0f, CheckWallsInterval);
|
||||
|
||||
}
|
||||
|
||||
float ang;
|
||||
Vector2 obstacleDiff;
|
||||
|
||||
public void Update(float deltaTime)
|
||||
{
|
||||
position += new Vector2(velocity.X, velocity.Y) * deltaTime;
|
||||
depth = MathHelper.Clamp(depth + velocity.Z * deltaTime, 0.0f, MaxDepth);
|
||||
|
||||
checkWallsTimer -= deltaTime;
|
||||
if (checkWallsTimer <= 0.0f && Level.Loaded != null)
|
||||
{
|
||||
checkWallsTimer = CheckWallsInterval;
|
||||
|
||||
obstacleDiff = Vector2.Zero;
|
||||
if (position.Y > Level.Loaded.Size.Y)
|
||||
{
|
||||
obstacleDiff = Vector2.UnitY;
|
||||
}
|
||||
else if (position.Y < 0.0f)
|
||||
{
|
||||
obstacleDiff = -Vector2.UnitY;
|
||||
}
|
||||
else if (position.X < 0.0f)
|
||||
{
|
||||
obstacleDiff = Vector2.UnitX;
|
||||
}
|
||||
else if (position.X > Level.Loaded.Size.X)
|
||||
{
|
||||
obstacleDiff = -Vector2.UnitX;
|
||||
}
|
||||
else
|
||||
{
|
||||
var cells = Level.Loaded.GetCells(position, 1);
|
||||
if (cells.Count > 0)
|
||||
{
|
||||
|
||||
foreach (Voronoi2.VoronoiCell cell in cells)
|
||||
{
|
||||
obstacleDiff += cell.Center - position;
|
||||
}
|
||||
obstacleDiff /= cells.Count;
|
||||
|
||||
obstacleDiff = Vector2.Normalize(obstacleDiff) * prefab.Speed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
if (Swarm!=null)
|
||||
{
|
||||
Vector2 midPoint = Swarm.MidPoint();
|
||||
float midPointDist = Vector2.Distance(SimPosition, midPoint);
|
||||
|
||||
if (midPointDist > Swarm.MaxDistance)
|
||||
{
|
||||
steeringManager.SteeringSeek(midPoint, (midPointDist / Swarm.MaxDistance) * prefab.Speed);
|
||||
}
|
||||
}
|
||||
|
||||
if (prefab.WanderAmount > 0.0f)
|
||||
{
|
||||
steeringManager.SteeringWander(prefab.Speed);
|
||||
}
|
||||
|
||||
//Level.Loaded.Size
|
||||
|
||||
|
||||
if (obstacleDiff != Vector2.Zero)
|
||||
{
|
||||
steeringManager.SteeringSeek(SimPosition-obstacleDiff, prefab.Speed*2.0f);
|
||||
}
|
||||
|
||||
steeringManager.Update(prefab.Speed);
|
||||
|
||||
if (prefab.WanderZAmount>0.0f)
|
||||
{
|
||||
ang += Rand.Range(-prefab.WanderZAmount, prefab.WanderZAmount);
|
||||
velocity.Z = (float)Math.Sin(ang)*prefab.Speed;
|
||||
}
|
||||
|
||||
velocity = Vector3.Lerp(velocity, new Vector3(Steering.X, Steering.Y, velocity.Z), deltaTime);
|
||||
}
|
||||
|
||||
public void Draw(SpriteBatch spriteBatch)
|
||||
{
|
||||
float rotation = 0.0f;
|
||||
if (!prefab.DisableRotation)
|
||||
{
|
||||
rotation = MathUtils.VectorToAngle(new Vector2(velocity.X, -velocity.Y));
|
||||
if (velocity.X < 0.0f) rotation -= MathHelper.Pi;
|
||||
}
|
||||
|
||||
drawPosition = position;// +Level.Loaded.Position;
|
||||
|
||||
if (depth > 0.0f)
|
||||
{
|
||||
Vector2 camOffset = drawPosition - GameMain.GameScreen.Cam.WorldViewCenter;
|
||||
|
||||
drawPosition -= camOffset * (depth / MaxDepth) * 0.05f;
|
||||
}
|
||||
|
||||
prefab.Sprite.Draw(spriteBatch, new Vector2(drawPosition.X, -drawPosition.Y), Color.Lerp(Color.White, Color.DarkBlue, (depth/MaxDepth)*0.3f),
|
||||
rotation, 1.0f - (depth / MaxDepth) * 0.2f, velocity.X > 0.0f ? SpriteEffects.None : SpriteEffects.FlipHorizontally, (depth / MaxDepth));
|
||||
}
|
||||
}
|
||||
|
||||
class Swarm
|
||||
{
|
||||
public List<BackgroundCreature> Members;
|
||||
|
||||
public readonly float MaxDistance;
|
||||
|
||||
public Vector2 MidPoint()
|
||||
{
|
||||
if (Members.Count == 0) return Vector2.Zero;
|
||||
|
||||
Vector2 midPoint = Vector2.Zero;
|
||||
|
||||
foreach (BackgroundCreature member in Members)
|
||||
{
|
||||
midPoint += member.SimPosition;
|
||||
}
|
||||
|
||||
midPoint /= Members.Count;
|
||||
|
||||
return midPoint;
|
||||
}
|
||||
|
||||
public Vector2 AvgVelocity()
|
||||
{
|
||||
if (Members.Count == 0) return Vector2.Zero;
|
||||
|
||||
Vector2 avgVel = Vector2.Zero;
|
||||
|
||||
foreach (BackgroundCreature member in Members)
|
||||
{
|
||||
avgVel += member.Velocity;
|
||||
}
|
||||
|
||||
avgVel /= Members.Count;
|
||||
|
||||
return avgVel;
|
||||
}
|
||||
|
||||
public Swarm(List<BackgroundCreature> members, float maxDistance)
|
||||
{
|
||||
this.Members = members;
|
||||
|
||||
this.MaxDistance = maxDistance;
|
||||
|
||||
foreach (BackgroundCreature bgSprite in members)
|
||||
{
|
||||
bgSprite.Swarm = this;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class BackgroundCreatureManager
|
||||
{
|
||||
const int MaxSprites = 100;
|
||||
|
||||
const float checkActiveInterval = 1.0f;
|
||||
|
||||
float checkActiveTimer;
|
||||
|
||||
private List<BackgroundCreaturePrefab> prefabs = new List<BackgroundCreaturePrefab>();
|
||||
private List<BackgroundCreature> activeSprites = new List<BackgroundCreature>();
|
||||
|
||||
public BackgroundCreatureManager(string configPath)
|
||||
{
|
||||
LoadConfig(configPath);
|
||||
}
|
||||
public BackgroundCreatureManager(List<string> files)
|
||||
{
|
||||
foreach(var file in files)
|
||||
{
|
||||
LoadConfig(file);
|
||||
}
|
||||
}
|
||||
private void LoadConfig(string configPath)
|
||||
{
|
||||
try
|
||||
{
|
||||
XDocument doc = ToolBox.TryLoadXml(configPath);
|
||||
if (doc == null || doc.Root == null) return;
|
||||
|
||||
foreach (XElement element in doc.Root.Elements())
|
||||
{
|
||||
prefabs.Add(new BackgroundCreaturePrefab(element));
|
||||
};
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
DebugConsole.ThrowError(String.Format("Failed to load BackgroundCreatures from {0}", configPath), e);
|
||||
}
|
||||
}
|
||||
public void SpawnSprites(int count, Vector2? position = null)
|
||||
{
|
||||
activeSprites.Clear();
|
||||
|
||||
if (prefabs.Count == 0) return;
|
||||
|
||||
count = Math.Min(count, MaxSprites);
|
||||
|
||||
for (int i = 0; i < count; i++ )
|
||||
{
|
||||
Vector2 pos = Vector2.Zero;
|
||||
|
||||
if (position == null)
|
||||
{
|
||||
var wayPoints = WayPoint.WayPointList.FindAll(wp => wp.Submarine==null);
|
||||
if (wayPoints.Any())
|
||||
{
|
||||
WayPoint wp = wayPoints[Rand.Int(wayPoints.Count, false)];
|
||||
|
||||
pos = new Vector2(wp.Rect.X, wp.Rect.Y);
|
||||
pos += Rand.Vector(200.0f, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
pos = Rand.Vector(2000.0f, false);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
pos = (Vector2)position;
|
||||
}
|
||||
|
||||
|
||||
var prefab = prefabs[Rand.Int(prefabs.Count, false)];
|
||||
|
||||
int amount = Rand.Range(prefab.SwarmMin, prefab.SwarmMax, false);
|
||||
List<BackgroundCreature> swarmMembers = new List<BackgroundCreature>();
|
||||
|
||||
for (int n = 0; n < amount; n++)
|
||||
{
|
||||
var newSprite = new BackgroundCreature(prefab, pos);
|
||||
activeSprites.Add(newSprite);
|
||||
swarmMembers.Add(newSprite);
|
||||
}
|
||||
if (amount > 0)
|
||||
{
|
||||
new Swarm(swarmMembers, prefab.SwarmRadius);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ClearSprites()
|
||||
{
|
||||
activeSprites.Clear();
|
||||
}
|
||||
|
||||
public void Update(Camera cam, float deltaTime)
|
||||
{
|
||||
if (checkActiveTimer<0.0f)
|
||||
{
|
||||
foreach (BackgroundCreature sprite in activeSprites)
|
||||
{
|
||||
sprite.Enabled = (Math.Abs(sprite.TransformedPosition.X - cam.WorldViewCenter.X) < 4000.0f &&
|
||||
Math.Abs(sprite.TransformedPosition.Y - cam.WorldViewCenter.Y) < 4000.0f);
|
||||
}
|
||||
|
||||
checkActiveTimer = checkActiveInterval;
|
||||
}
|
||||
else
|
||||
{
|
||||
checkActiveTimer -= deltaTime;
|
||||
}
|
||||
|
||||
foreach (BackgroundCreature sprite in activeSprites)
|
||||
{
|
||||
if (!sprite.Enabled) continue;
|
||||
sprite.Update(deltaTime);
|
||||
}
|
||||
}
|
||||
|
||||
public void Draw(SpriteBatch spriteBatch)
|
||||
{
|
||||
foreach (BackgroundCreature sprite in activeSprites)
|
||||
{
|
||||
if (!sprite.Enabled) continue;
|
||||
sprite.Draw(spriteBatch);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class BackgroundCreaturePrefab
|
||||
{
|
||||
|
||||
public readonly Sprite Sprite;
|
||||
|
||||
public readonly float Speed;
|
||||
|
||||
public readonly float WanderAmount;
|
||||
|
||||
public readonly float WanderZAmount;
|
||||
|
||||
public readonly int SwarmMin, SwarmMax;
|
||||
|
||||
public readonly float SwarmRadius;
|
||||
|
||||
public readonly bool DisableRotation;
|
||||
|
||||
public BackgroundCreaturePrefab(XElement element)
|
||||
{
|
||||
Speed = ToolBox.GetAttributeFloat(element, "speed", 1.0f);
|
||||
|
||||
WanderAmount = ToolBox.GetAttributeFloat(element, "wanderamount", 0.0f);
|
||||
|
||||
WanderZAmount = ToolBox.GetAttributeFloat(element, "wanderzamount", 0.0f);
|
||||
|
||||
SwarmMin = ToolBox.GetAttributeInt(element, "swarmmin", 1);
|
||||
SwarmMax = ToolBox.GetAttributeInt(element, "swarmmax", 1);
|
||||
|
||||
SwarmRadius = ToolBox.GetAttributeFloat(element, "swarmradius", 200.0f);
|
||||
|
||||
DisableRotation = ToolBox.GetAttributeBool(element, "disablerotation", false);
|
||||
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
if (subElement.Name.ToString().ToLowerInvariant() != "sprite") continue;
|
||||
|
||||
Sprite = new Sprite(subElement);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,349 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Xml.Linq;
|
||||
using Voronoi2;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class BackgroundSprite
|
||||
{
|
||||
public readonly BackgroundSpritePrefab Prefab;
|
||||
public Vector3 Position;
|
||||
|
||||
public float Scale;
|
||||
|
||||
public float Rotation;
|
||||
|
||||
//public Vector2[] spriteCorners;
|
||||
|
||||
public BackgroundSprite(BackgroundSpritePrefab prefab, Vector3 position, float scale, float rotation = 0.0f)
|
||||
{
|
||||
this.Prefab = prefab;
|
||||
this.Position = position;
|
||||
|
||||
this.Scale = scale;
|
||||
|
||||
this.Rotation = rotation;
|
||||
}
|
||||
}
|
||||
|
||||
class BackgroundSpriteManager
|
||||
{
|
||||
const int GridSize = 1000;
|
||||
|
||||
private List<BackgroundSpritePrefab> prefabs = new List<BackgroundSpritePrefab>();
|
||||
|
||||
private List<BackgroundSprite>[,] sprites;
|
||||
|
||||
private float swingTimer;
|
||||
|
||||
public BackgroundSpriteManager(string configPath)
|
||||
{
|
||||
LoadConfig(configPath);
|
||||
}
|
||||
public BackgroundSpriteManager(List<string> files)
|
||||
{
|
||||
foreach (var file in files)
|
||||
{
|
||||
LoadConfig(file);
|
||||
}
|
||||
}
|
||||
private void LoadConfig(string configPath)
|
||||
{
|
||||
try
|
||||
{
|
||||
XDocument doc = ToolBox.TryLoadXml(configPath);
|
||||
if (doc == null || doc.Root == null) return;
|
||||
|
||||
foreach (XElement element in doc.Root.Elements())
|
||||
{
|
||||
prefabs.Add(new BackgroundSpritePrefab(element));
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
DebugConsole.ThrowError(String.Format("Failed to load BackgroundSprites from {0}", configPath), e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void PlaceSprites(Level level, int amount)
|
||||
{
|
||||
sprites = new List<BackgroundSprite>[
|
||||
(int)Math.Ceiling(level.Size.X / GridSize),
|
||||
(int)Math.Ceiling(level.Size.Y / GridSize)];
|
||||
|
||||
for (int x = 0; x < sprites.GetLength(0); x++)
|
||||
{
|
||||
for (int y = 0; y < sprites.GetLength(1); y++)
|
||||
{
|
||||
sprites[x, y] = new List<BackgroundSprite>();
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0 ; i < amount; i++)
|
||||
{
|
||||
BackgroundSpritePrefab prefab = GetRandomPrefab(level.GenerationParams.Name);
|
||||
GraphEdge selectedEdge = null;
|
||||
Vector2 edgeNormal = Vector2.One;
|
||||
Vector2? pos = FindSpritePosition(level, prefab, out selectedEdge, out edgeNormal);
|
||||
|
||||
if (pos == null) continue;
|
||||
|
||||
float rotation = 0.0f;
|
||||
if (prefab.AlignWithSurface)
|
||||
{
|
||||
rotation = MathUtils.VectorToAngle(new Vector2(edgeNormal.Y, edgeNormal.X));
|
||||
}
|
||||
|
||||
rotation += Rand.Range(prefab.RandomRotation.X, prefab.RandomRotation.Y, false);
|
||||
|
||||
var newSprite = new BackgroundSprite(prefab,
|
||||
new Vector3((Vector2)pos, Rand.Range(prefab.DepthRange.X, prefab.DepthRange.Y, false)), Rand.Range(prefab.Scale.X, prefab.Scale.Y, false), rotation);
|
||||
|
||||
//calculate the positions of the corners of the rotated sprite
|
||||
Vector2 halfSize = newSprite.Prefab.Sprite.size * newSprite.Scale / 2;
|
||||
var spriteCorners = new Vector2[]
|
||||
{
|
||||
-halfSize, new Vector2(-halfSize.X, halfSize.Y),
|
||||
halfSize, new Vector2(halfSize.X, -halfSize.Y)
|
||||
};
|
||||
|
||||
Vector2 pivotOffset = newSprite.Prefab.Sprite.Origin * newSprite.Scale - halfSize;
|
||||
pivotOffset.X = -pivotOffset.X;
|
||||
pivotOffset = new Vector2(
|
||||
(float)(pivotOffset.X * Math.Cos(-rotation) - pivotOffset.Y * Math.Sin(-rotation)),
|
||||
(float)(pivotOffset.X * Math.Sin(-rotation) + pivotOffset.Y * Math.Cos(-rotation)));
|
||||
|
||||
for (int j = 0; j < 4; j++)
|
||||
{
|
||||
spriteCorners[j] = new Vector2(
|
||||
(float)(spriteCorners[j].X * Math.Cos(-rotation) - spriteCorners[j].Y * Math.Sin(-rotation)),
|
||||
(float)(spriteCorners[j].X * Math.Sin(-rotation) + spriteCorners[j].Y * Math.Cos(-rotation)));
|
||||
|
||||
spriteCorners[j] += (Vector2)pos + pivotOffset;
|
||||
}
|
||||
|
||||
//newSprite.spriteCorners = spriteCorners;
|
||||
|
||||
int minX = (int)Math.Floor((spriteCorners.Min(c => c.X) - newSprite.Position.Z) / GridSize);
|
||||
int maxX = (int)Math.Floor((spriteCorners.Max(c => c.X) + newSprite.Position.Z) / GridSize);
|
||||
if (minX < 0 || maxX >= sprites.GetLength(0)) continue;
|
||||
|
||||
int minY = (int)Math.Floor((spriteCorners.Min(c => c.Y) - newSprite.Position.Z) / GridSize);
|
||||
int maxY = (int)Math.Floor((spriteCorners.Max(c => c.Y) + newSprite.Position.Z) / GridSize);
|
||||
if (minY < 0 || maxY >= sprites.GetLength(1)) continue;
|
||||
|
||||
for (int x = minX; x <= maxX; x++)
|
||||
{
|
||||
for (int y = minY; y <= maxY; y++)
|
||||
{
|
||||
sprites[x, y].Add(newSprite);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Vector2? FindSpritePosition(Level level, BackgroundSpritePrefab prefab, out GraphEdge closestEdge, out Vector2 edgeNormal)
|
||||
{
|
||||
closestEdge = null;
|
||||
edgeNormal = Vector2.One;
|
||||
|
||||
Vector2 randomPos = new Vector2(
|
||||
Rand.Range(0.0f, level.Size.X, false),
|
||||
Rand.Range(0.0f, level.Size.Y, false));
|
||||
|
||||
if (!prefab.SpawnOnWalls) return randomPos;
|
||||
|
||||
List<GraphEdge> edges = new List<GraphEdge>();
|
||||
List<Vector2> normals = new List<Vector2>();
|
||||
|
||||
var cells = level.GetCells(randomPos);
|
||||
|
||||
if (cells.Any())
|
||||
{
|
||||
VoronoiCell cell = cells[Rand.Int(cells.Count, false)];
|
||||
|
||||
foreach (GraphEdge edge in cell.edges)
|
||||
{
|
||||
if (!edge.isSolid || edge.OutsideLevel) continue;
|
||||
|
||||
Vector2 normal = edge.GetNormal(cell);
|
||||
|
||||
if (prefab.Alignment.HasFlag(Alignment.Bottom) && normal.Y < -0.5f)
|
||||
{
|
||||
edges.Add(edge);
|
||||
}
|
||||
else if (prefab.Alignment.HasFlag(Alignment.Top) && normal.Y > 0.5f)
|
||||
{
|
||||
edges.Add(edge);
|
||||
}
|
||||
else if (prefab.Alignment.HasFlag(Alignment.Left) && normal.X < -0.5f)
|
||||
{
|
||||
edges.Add(edge);
|
||||
}
|
||||
else if (prefab.Alignment.HasFlag(Alignment.Right) && normal.X > 0.5f)
|
||||
{
|
||||
edges.Add(edge);
|
||||
}
|
||||
else
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
normals.Add(normal);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (RuinGeneration.Ruin ruin in Level.Loaded.Ruins)
|
||||
{
|
||||
Rectangle expandedArea = ruin.Area;
|
||||
expandedArea.Inflate(ruin.Area.Width, ruin.Area.Height);
|
||||
if (!expandedArea.Contains(randomPos)) continue;
|
||||
|
||||
foreach (var ruinShape in ruin.RuinShapes)
|
||||
{
|
||||
foreach (var wall in ruinShape.Walls)
|
||||
{
|
||||
if (!prefab.Alignment.HasFlag(ruinShape.GetLineAlignment(wall))) continue;
|
||||
|
||||
edges.Add(new GraphEdge(wall.A, wall.B));
|
||||
normals.Add((wall.A + wall.B) / 2.0f - ruinShape.Center);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!edges.Any()) return null;
|
||||
|
||||
int index = Rand.Int(edges.Count, false);
|
||||
closestEdge = edges[index];
|
||||
edgeNormal = normals[index];
|
||||
|
||||
float length = Vector2.Distance(closestEdge.point1, closestEdge.point2);
|
||||
Vector2 dir = (closestEdge.point1 - closestEdge.point2) / length;
|
||||
Vector2 pos = closestEdge.point2 + dir * Rand.Range(prefab.Sprite.size.X / 2.0f, length - prefab.Sprite.size.X / 2.0f, false);
|
||||
|
||||
return pos;
|
||||
}
|
||||
|
||||
public void Update(float deltaTime)
|
||||
{
|
||||
swingTimer += deltaTime;
|
||||
}
|
||||
|
||||
public void DrawSprites(SpriteBatch spriteBatch, Camera cam)
|
||||
{
|
||||
Rectangle indices = Rectangle.Empty;
|
||||
indices.X = (int)Math.Floor(cam.WorldView.X / (float)GridSize);
|
||||
if (indices.X >= sprites.GetLength(0)) return;
|
||||
indices.Y = (int)Math.Floor((cam.WorldView.Y - cam.WorldView.Height) / (float)GridSize);
|
||||
if (indices.Y >= sprites.GetLength(1)) return;
|
||||
|
||||
indices.Width = (int)Math.Floor(cam.WorldView.Right / (float)GridSize)+1;
|
||||
if (indices.Width < 0) return;
|
||||
indices.Height = (int)Math.Floor(cam.WorldView.Y / (float)GridSize)+1;
|
||||
if (indices.Height < 0) return;
|
||||
|
||||
indices.X = Math.Max(indices.X, 0);
|
||||
indices.Y = Math.Max(indices.Y, 0);
|
||||
indices.Width = Math.Min(indices.Width, sprites.GetLength(0)-1);
|
||||
indices.Height = Math.Min(indices.Height, sprites.GetLength(1)-1);
|
||||
|
||||
float swingState = (float)Math.Sin(swingTimer * 0.1f);
|
||||
|
||||
List<BackgroundSprite> visibleSprites = new List<BackgroundSprite>();
|
||||
|
||||
float z = 0.0f;
|
||||
for (int x = indices.X; x <= indices.Width; x++)
|
||||
{
|
||||
for (int y = indices.Y; y <= indices.Height; y++)
|
||||
{
|
||||
foreach (BackgroundSprite sprite in sprites[x, y])
|
||||
{
|
||||
int drawOrderIndex = 0;
|
||||
for (int i = 0; i < visibleSprites.Count; i++)
|
||||
{
|
||||
if (visibleSprites[i] == sprite)
|
||||
{
|
||||
drawOrderIndex = -1;
|
||||
break;
|
||||
}
|
||||
|
||||
if (visibleSprites[i].Position.Z > sprite.Position.Z)
|
||||
{
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
drawOrderIndex = i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (drawOrderIndex >= 0)
|
||||
{
|
||||
visibleSprites.Insert(drawOrderIndex, sprite);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (BackgroundSprite sprite in visibleSprites)
|
||||
{
|
||||
Vector2 camDiff = new Vector2(sprite.Position.X, sprite.Position.Y) - cam.WorldViewCenter;
|
||||
camDiff.Y = -camDiff.Y;
|
||||
|
||||
sprite.Prefab.Sprite.Draw(
|
||||
spriteBatch,
|
||||
new Vector2(sprite.Position.X, -sprite.Position.Y) - camDiff * sprite.Position.Z / 10000.0f,
|
||||
Color.Lerp(Color.White, Level.Loaded.BackgroundColor, sprite.Position.Z / 5000.0f),
|
||||
sprite.Rotation + swingState * sprite.Prefab.SwingAmount,
|
||||
sprite.Scale,
|
||||
SpriteEffects.None,
|
||||
z);
|
||||
|
||||
/*for (int i = 0; i < 4; i++)
|
||||
{
|
||||
GUI.DrawLine(spriteBatch,
|
||||
new Vector2(sprite.spriteCorners[i].X, -sprite.spriteCorners[i].Y),
|
||||
new Vector2(sprite.spriteCorners[(i + 1) % 4].X, -sprite.spriteCorners[(i + 1) % 4].Y),
|
||||
Color.White, 0, 5);
|
||||
}*/
|
||||
|
||||
if (GameMain.DebugDraw)
|
||||
{
|
||||
GUI.DrawRectangle(spriteBatch, new Vector2(sprite.Position.X, -sprite.Position.Y), new Vector2(10.0f, 10.0f), Color.Red, true);
|
||||
}
|
||||
|
||||
z += 0.0001f;
|
||||
}
|
||||
}
|
||||
|
||||
private BackgroundSpritePrefab GetRandomPrefab(string levelType)
|
||||
{
|
||||
int totalCommonness = 0;
|
||||
foreach (BackgroundSpritePrefab prefab in prefabs)
|
||||
{
|
||||
totalCommonness += prefab.GetCommonness(levelType);
|
||||
}
|
||||
|
||||
float randomNumber = Rand.Int(totalCommonness+1, false);
|
||||
|
||||
foreach (BackgroundSpritePrefab prefab in prefabs)
|
||||
{
|
||||
if (randomNumber <= prefab.GetCommonness(levelType))
|
||||
{
|
||||
return prefab;
|
||||
}
|
||||
|
||||
randomNumber -= prefab.GetCommonness(levelType);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class BackgroundSpritePrefab
|
||||
{
|
||||
public readonly Sprite Sprite;
|
||||
|
||||
public readonly Alignment Alignment;
|
||||
|
||||
public readonly Vector2 Scale;
|
||||
|
||||
public bool SpawnOnWalls;
|
||||
|
||||
public readonly bool AlignWithSurface;
|
||||
|
||||
public readonly Vector2 RandomRotation;
|
||||
|
||||
public readonly Vector2 DepthRange;
|
||||
|
||||
public readonly float SwingAmount;
|
||||
|
||||
public readonly int Commonness;
|
||||
|
||||
public Dictionary<string, int> OverrideCommonness;
|
||||
|
||||
public BackgroundSpritePrefab(XElement element)
|
||||
{
|
||||
string alignmentStr = ToolBox.GetAttributeString(element, "alignment", "");
|
||||
|
||||
if (string.IsNullOrEmpty(alignmentStr) || !Enum.TryParse(alignmentStr, out Alignment))
|
||||
{
|
||||
Alignment = Alignment.Top | Alignment.Bottom | Alignment.Left | Alignment.Right;
|
||||
}
|
||||
|
||||
Commonness = ToolBox.GetAttributeInt(element, "commonness", 1);
|
||||
|
||||
SpawnOnWalls = ToolBox.GetAttributeBool(element, "spawnonwalls", true);
|
||||
|
||||
Scale.X = ToolBox.GetAttributeFloat(element, "minsize", 1.0f);
|
||||
Scale.Y = ToolBox.GetAttributeFloat(element, "maxsize", 1.0f);
|
||||
|
||||
DepthRange = ToolBox.GetAttributeVector2(element, "depthrange", new Vector2(0.0f, 1.0f));
|
||||
|
||||
AlignWithSurface = ToolBox.GetAttributeBool(element, "alignwithsurface", false);
|
||||
|
||||
RandomRotation = ToolBox.GetAttributeVector2(element, "randomrotation", Vector2.Zero);
|
||||
RandomRotation.X = MathHelper.ToRadians(RandomRotation.X);
|
||||
RandomRotation.Y = MathHelper.ToRadians(RandomRotation.Y);
|
||||
|
||||
SwingAmount = MathHelper.ToRadians(ToolBox.GetAttributeFloat(element, "swingamount", 0.0f));
|
||||
|
||||
OverrideCommonness = new Dictionary<string, int>();
|
||||
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
switch(subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "sprite":
|
||||
Sprite = new Sprite(subElement);
|
||||
break;
|
||||
case "overridecommonness":
|
||||
string levelType = ToolBox.GetAttributeString(subElement, "leveltype", "");
|
||||
if (!OverrideCommonness.ContainsKey(levelType))
|
||||
{
|
||||
OverrideCommonness.Add(levelType, ToolBox.GetAttributeInt(subElement, "commonness", 1));
|
||||
}
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int GetCommonness(string levelType)
|
||||
{
|
||||
int commonness = 0;
|
||||
if (!OverrideCommonness.TryGetValue(levelType, out commonness))
|
||||
{
|
||||
return Commonness;
|
||||
}
|
||||
|
||||
return commonness;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,339 @@
|
||||
using Barotrauma.Networking;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using System;
|
||||
using Barotrauma.Items.Components;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class CharacterHUD
|
||||
{
|
||||
private static Sprite statusIcons;
|
||||
|
||||
private static Sprite noiseOverlay, damageOverlay;
|
||||
|
||||
private static GUIButton cprButton;
|
||||
|
||||
private static GUIButton suicideButton;
|
||||
|
||||
private static GUIProgressBar drowningBar, healthBar;
|
||||
|
||||
public static float damageOverlayTimer { get; private set; }
|
||||
|
||||
public static void Reset()
|
||||
{
|
||||
damageOverlayTimer = 0.0f;
|
||||
}
|
||||
|
||||
public static void TakeDamage(float amount)
|
||||
{
|
||||
healthBar.Flash();
|
||||
|
||||
damageOverlayTimer = MathHelper.Clamp(amount * 0.1f, 0.2f, 5.0f);
|
||||
}
|
||||
|
||||
public static void AddToGUIUpdateList(Character character)
|
||||
{
|
||||
if (GUI.DisableHUD) return;
|
||||
|
||||
if (cprButton != null && cprButton.Visible) cprButton.AddToGUIUpdateList();
|
||||
|
||||
if (suicideButton != null && suicideButton.Visible) suicideButton.AddToGUIUpdateList();
|
||||
|
||||
if (!character.IsUnconscious && character.Stun <= 0.0f)
|
||||
{
|
||||
|
||||
if (character.Inventory != null)
|
||||
{
|
||||
for (int i = 0; i < character.Inventory.Items.Length - 1; i++)
|
||||
{
|
||||
var item = character.Inventory.Items[i];
|
||||
if (item == null || CharacterInventory.limbSlots[i] == InvSlotType.Any) continue;
|
||||
|
||||
foreach (ItemComponent ic in item.components)
|
||||
{
|
||||
if (ic.DrawHudWhenEquipped) ic.AddToGUIUpdateList();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void Update(float deltaTime, Character character)
|
||||
{
|
||||
if (drowningBar != null)
|
||||
{
|
||||
drowningBar.Update(deltaTime);
|
||||
if (character.Oxygen < 10.0f) drowningBar.Flash();
|
||||
}
|
||||
if (healthBar != null) healthBar.Update(deltaTime);
|
||||
|
||||
if (cprButton != null && cprButton.Visible) cprButton.Update(deltaTime);
|
||||
|
||||
if (suicideButton != null && suicideButton.Visible) suicideButton.Update(deltaTime);
|
||||
|
||||
if (damageOverlayTimer > 0.0f) damageOverlayTimer -= deltaTime;
|
||||
|
||||
if (!character.IsUnconscious && character.Stun <= 0.0f)
|
||||
{
|
||||
|
||||
if (character.Inventory != null)
|
||||
{
|
||||
if (!character.LockHands && character.Stun >= -0.1f)
|
||||
{
|
||||
character.Inventory.Update(deltaTime);
|
||||
}
|
||||
|
||||
for (int i = 0; i < character.Inventory.Items.Length - 1; i++)
|
||||
{
|
||||
var item = character.Inventory.Items[i];
|
||||
if (item == null || CharacterInventory.limbSlots[i] == InvSlotType.Any) continue;
|
||||
|
||||
foreach (ItemComponent ic in item.components)
|
||||
{
|
||||
if (ic.DrawHudWhenEquipped) ic.UpdateHUD(character);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (character.SelectedCharacter != null && character.SelectedCharacter.Inventory != null)
|
||||
{
|
||||
character.SelectedCharacter.Inventory.Update(deltaTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void Draw(SpriteBatch spriteBatch, Character character, Camera cam)
|
||||
{
|
||||
if (statusIcons == null)
|
||||
{
|
||||
statusIcons = new Sprite("Content/UI/statusIcons.png", Vector2.Zero);
|
||||
}
|
||||
|
||||
if (noiseOverlay == null)
|
||||
{
|
||||
noiseOverlay = new Sprite("Content/UI/noise.png", Vector2.Zero);
|
||||
}
|
||||
|
||||
if (damageOverlay == null)
|
||||
{
|
||||
damageOverlay = new Sprite("Content/UI/damageOverlay.png", Vector2.Zero);
|
||||
}
|
||||
|
||||
if (GUI.DisableHUD) return;
|
||||
|
||||
if (character.Inventory != null)
|
||||
{
|
||||
for (int i = 0; i < character.Inventory.Items.Length - 1; i++)
|
||||
{
|
||||
var item = character.Inventory.Items[i];
|
||||
if (item == null || CharacterInventory.limbSlots[i] == InvSlotType.Any) continue;
|
||||
|
||||
foreach (ItemComponent ic in item.components)
|
||||
{
|
||||
if (ic.DrawHudWhenEquipped) ic.DrawHUD(spriteBatch, character);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DrawStatusIcons(spriteBatch, character);
|
||||
|
||||
if (!character.IsUnconscious && character.Stun <= 0.0f)
|
||||
{
|
||||
if (character.Inventory != null && !character.LockHands && character.Stun >= -0.1f)
|
||||
{
|
||||
character.Inventory.DrawOffset = Vector2.Zero;
|
||||
character.Inventory.DrawOwn(spriteBatch);
|
||||
}
|
||||
|
||||
if (character.SelectedCharacter != null && character.SelectedCharacter.Inventory != null)
|
||||
{
|
||||
character.SelectedCharacter.Inventory.DrawOffset = new Vector2(320.0f, 0.0f);
|
||||
character.SelectedCharacter.Inventory.DrawOwn(spriteBatch);
|
||||
|
||||
if (cprButton == null)
|
||||
{
|
||||
cprButton = new GUIButton(
|
||||
new Rectangle(character.SelectedCharacter.Inventory.SlotPositions[0].ToPoint() + new Point(320, -30), new Point(130, 20)), "Perform CPR", "");
|
||||
|
||||
cprButton.OnClicked = (button, userData) =>
|
||||
{
|
||||
if (Character.Controlled == null || Character.Controlled.SelectedCharacter == null) return false;
|
||||
|
||||
Character.Controlled.AnimController.Anim = (Character.Controlled.AnimController.Anim == AnimController.Animation.CPR) ?
|
||||
AnimController.Animation.None : AnimController.Animation.CPR;
|
||||
|
||||
foreach (Limb limb in Character.Controlled.SelectedCharacter.AnimController.Limbs)
|
||||
{
|
||||
limb.pullJoint.Enabled = false;
|
||||
}
|
||||
|
||||
if (GameMain.Client != null)
|
||||
{
|
||||
GameMain.Client.CreateEntityEvent(Character.Controlled, new object[] { NetEntityEvent.Type.Repair });
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
//cprButton.Visible = character.GetSkillLevel("Medical") > 20.0f;
|
||||
|
||||
if (cprButton.Visible) cprButton.Draw(spriteBatch);
|
||||
}
|
||||
|
||||
if (character.ClosestCharacter != null && character.ClosestCharacter.CanBeSelected)
|
||||
{
|
||||
Vector2 startPos = character.DrawPosition + (character.ClosestCharacter.DrawPosition - character.DrawPosition) * 0.7f;
|
||||
startPos = cam.WorldToScreen(startPos);
|
||||
|
||||
Vector2 textPos = startPos;
|
||||
textPos -= new Vector2(GUI.Font.MeasureString(character.ClosestCharacter.Info.Name).X / 2, 20);
|
||||
|
||||
GUI.DrawString(spriteBatch, textPos, character.ClosestCharacter.Info.Name, Color.White, Color.Black, 2);
|
||||
}
|
||||
else if (character.SelectedCharacter == null && character.ClosestItem != null && character.SelectedConstruction == null)
|
||||
{
|
||||
var hudTexts = character.ClosestItem.GetHUDTexts(character);
|
||||
|
||||
Vector2 startPos = new Vector2((int)(GameMain.GraphicsWidth / 2.0f), GameMain.GraphicsHeight);
|
||||
startPos.Y -= 50 + hudTexts.Count * 25;
|
||||
|
||||
Vector2 textPos = startPos;
|
||||
textPos -= new Vector2((int)GUI.Font.MeasureString(character.ClosestItem.Name).X / 2, 20);
|
||||
|
||||
GUI.DrawString(spriteBatch, textPos, character.ClosestItem.Name, Color.White, Color.Black * 0.7f, 2);
|
||||
|
||||
textPos.Y += 30.0f;
|
||||
foreach (ColoredText coloredText in hudTexts)
|
||||
{
|
||||
textPos.X = (int)(startPos.X - GUI.SmallFont.MeasureString(coloredText.Text).X / 2);
|
||||
|
||||
GUI.DrawString(spriteBatch, textPos, coloredText.Text, coloredText.Color, Color.Black * 0.7f, 2, GUI.SmallFont);
|
||||
|
||||
textPos.Y += 25;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (HUDProgressBar progressBar in character.HUDProgressBars.Values)
|
||||
{
|
||||
progressBar.Draw(spriteBatch, cam);
|
||||
}
|
||||
}
|
||||
|
||||
if (Screen.Selected == GameMain.EditMapScreen) return;
|
||||
|
||||
if (character.IsUnconscious || (character.Oxygen < 80.0f && !character.IsDead))
|
||||
{
|
||||
Vector2 offset = Rand.Vector(noiseOverlay.size.X);
|
||||
offset.X = Math.Abs(offset.X);
|
||||
offset.Y = Math.Abs(offset.Y);
|
||||
|
||||
float alpha = character.IsUnconscious ? 1.0f : Math.Min((80.0f - character.Oxygen)/50.0f, 0.8f);
|
||||
|
||||
noiseOverlay.DrawTiled(spriteBatch, Vector2.Zero - offset, new Vector2(GameMain.GraphicsWidth, GameMain.GraphicsHeight) + offset,
|
||||
Vector2.Zero,
|
||||
Color.White * alpha);
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
if (suicideButton != null) suicideButton.Visible = false;
|
||||
}
|
||||
|
||||
if (damageOverlayTimer>0.0f)
|
||||
{
|
||||
damageOverlay.Draw(spriteBatch, Vector2.Zero, Color.White * damageOverlayTimer, Vector2.Zero, 0.0f,
|
||||
new Vector2(GameMain.GraphicsWidth / damageOverlay.size.X, GameMain.GraphicsHeight / damageOverlay.size.Y));
|
||||
}
|
||||
|
||||
if (character.IsUnconscious && !character.IsDead)
|
||||
{
|
||||
if (suicideButton == null)
|
||||
{
|
||||
suicideButton = new GUIButton(
|
||||
new Rectangle(new Point(GameMain.GraphicsWidth / 2 - 60, 20), new Point(120, 20)), "Give in", "");
|
||||
|
||||
|
||||
suicideButton.ToolTip = GameMain.NetworkMember == null ?
|
||||
"The character can no longer be revived if you give in." :
|
||||
"Let go of your character and enter spectator mode (other players will no longer be able to revive you)";
|
||||
|
||||
suicideButton.OnClicked = (button, userData) =>
|
||||
{
|
||||
GUIComponent.ForceMouseOn(null);
|
||||
if (Character.Controlled != null)
|
||||
{
|
||||
if (GameMain.Client != null)
|
||||
{
|
||||
GameMain.Client.CreateEntityEvent(Character.Controlled, new object[] { NetEntityEvent.Type.Status });
|
||||
}
|
||||
else
|
||||
{
|
||||
Character.Controlled.Kill(Character.Controlled.CauseOfDeath);
|
||||
Character.Controlled = null;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
suicideButton.Visible = true;
|
||||
suicideButton.Draw(spriteBatch);
|
||||
}
|
||||
}
|
||||
|
||||
private static void DrawStatusIcons(SpriteBatch spriteBatch, Character character)
|
||||
{
|
||||
if (GameMain.DebugDraw)
|
||||
{
|
||||
GUI.DrawString(spriteBatch, new Vector2(30, GameMain.GraphicsHeight - 260), "Stun: "+character.Stun, Color.White);
|
||||
}
|
||||
|
||||
if (drowningBar == null)
|
||||
{
|
||||
int width = 100, height = 20;
|
||||
|
||||
drowningBar = new GUIProgressBar(new Rectangle(30, GameMain.GraphicsHeight - 200, width, height), Color.Blue, "", 1.0f, Alignment.TopLeft);
|
||||
new GUIImage(new Rectangle(-27, -7, 20, 20), new Rectangle(17, 0, 20, 24), statusIcons, Alignment.TopLeft, drowningBar);
|
||||
|
||||
healthBar = new GUIProgressBar(new Rectangle(30, GameMain.GraphicsHeight - 230, width, height), Color.Red, "", 1.0f, Alignment.TopLeft);
|
||||
new GUIImage(new Rectangle(-26, -7, 20, 20), new Rectangle(0, 0, 13, 24), statusIcons, Alignment.TopLeft, healthBar);
|
||||
}
|
||||
|
||||
drowningBar.BarSize = character.Oxygen / 100.0f;
|
||||
if (drowningBar.BarSize < 0.99f)
|
||||
{
|
||||
drowningBar.Draw(spriteBatch);
|
||||
}
|
||||
|
||||
healthBar.BarSize = character.Health / character.MaxHealth;
|
||||
if (healthBar.BarSize < 1.0f)
|
||||
{
|
||||
healthBar.Draw(spriteBatch);
|
||||
}
|
||||
|
||||
float bloodDropCount = character.Bleeding;
|
||||
bloodDropCount = MathHelper.Clamp(bloodDropCount, 0.0f, 5.0f);
|
||||
for (int i = 0; i < Math.Ceiling(bloodDropCount); i++)
|
||||
{
|
||||
float alpha = MathHelper.Clamp(bloodDropCount-i, 0.2f, 1.0f);
|
||||
spriteBatch.Draw(statusIcons.Texture, new Vector2(25.0f + 20 * i, healthBar.Rect.Y - 20.0f), new Rectangle(39, 3, 15, 19), Color.White * alpha);
|
||||
}
|
||||
|
||||
float pressureFactor = (character.AnimController.CurrentHull == null) ?
|
||||
100.0f : Math.Min(character.AnimController.CurrentHull.LethalPressure,100.0f);
|
||||
if (character.PressureProtection > 0.0f) pressureFactor = 0.0f;
|
||||
|
||||
if (pressureFactor>0.0f)
|
||||
{
|
||||
float indicatorAlpha = ((float)Math.Sin(character.PressureTimer * 0.1f) + 1.0f) * 0.5f;
|
||||
|
||||
indicatorAlpha = MathHelper.Clamp(indicatorAlpha, 0.1f, pressureFactor/100.0f);
|
||||
|
||||
spriteBatch.Draw(statusIcons.Texture, new Vector2(10.0f, healthBar.Rect.Y - 60.0f), new Rectangle(0, 24, 24, 25), Color.White * indicatorAlpha);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,380 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
public enum Gender { None, Male, Female };
|
||||
|
||||
class CharacterInfo
|
||||
{
|
||||
public string Name;
|
||||
|
||||
public Character Character;
|
||||
|
||||
public readonly string File;
|
||||
|
||||
public Job Job;
|
||||
|
||||
private List<ushort> pickedItems;
|
||||
|
||||
public ushort? HullID = null;
|
||||
|
||||
private Vector2[] headSpriteRange;
|
||||
|
||||
private Gender gender;
|
||||
|
||||
public int Salary;
|
||||
|
||||
private int headSpriteId;
|
||||
private Sprite headSprite;
|
||||
|
||||
public bool StartItemsGiven;
|
||||
|
||||
public List<ushort> PickedItemIDs
|
||||
{
|
||||
get { return pickedItems; }
|
||||
}
|
||||
|
||||
public Sprite HeadSprite
|
||||
{
|
||||
get
|
||||
{
|
||||
if (headSprite == null) LoadHeadSprite();
|
||||
return headSprite;
|
||||
}
|
||||
}
|
||||
|
||||
public List<string> SpriteTags
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public int HeadSpriteId
|
||||
{
|
||||
get { return headSpriteId; }
|
||||
set
|
||||
{
|
||||
int oldId = headSpriteId;
|
||||
|
||||
headSpriteId = value;
|
||||
Vector2 spriteRange = headSpriteRange[gender == Gender.Male ? 0 : 1];
|
||||
|
||||
if (headSpriteId < (int)spriteRange.X) headSpriteId = (int)(spriteRange.Y);
|
||||
if (headSpriteId > (int)spriteRange.Y) headSpriteId = (int)(spriteRange.X);
|
||||
|
||||
if (headSpriteId != oldId) headSprite = null;
|
||||
}
|
||||
}
|
||||
|
||||
public Gender Gender
|
||||
{
|
||||
get { return gender; }
|
||||
set
|
||||
{
|
||||
if (gender == value) return;
|
||||
gender = value;
|
||||
|
||||
int genderIndex = (this.gender == Gender.Female) ? 1 : 0;
|
||||
if (headSpriteRange[genderIndex] != Vector2.Zero)
|
||||
{
|
||||
HeadSpriteId = Rand.Range((int)headSpriteRange[genderIndex].X, (int)headSpriteRange[genderIndex].Y + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
HeadSpriteId = 0;
|
||||
}
|
||||
|
||||
LoadHeadSprite();
|
||||
}
|
||||
}
|
||||
|
||||
public CharacterInfo(string file, string name = "", Gender gender = Gender.None, JobPrefab jobPrefab = null)
|
||||
{
|
||||
this.File = file;
|
||||
|
||||
headSpriteRange = new Vector2[2];
|
||||
|
||||
pickedItems = new List<ushort>();
|
||||
|
||||
SpriteTags = new List<string>();
|
||||
|
||||
//ID = -1;
|
||||
|
||||
XDocument doc = ToolBox.TryLoadXml(file);
|
||||
if (doc == null) return;
|
||||
|
||||
if (ToolBox.GetAttributeBool(doc.Root, "genders", false))
|
||||
{
|
||||
if (gender == Gender.None)
|
||||
{
|
||||
float femaleRatio = ToolBox.GetAttributeFloat(doc.Root, "femaleratio", 0.5f);
|
||||
this.gender = (Rand.Range(0.0f, 1.0f, false) < femaleRatio) ? Gender.Female : Gender.Male;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.gender = gender;
|
||||
}
|
||||
}
|
||||
|
||||
headSpriteRange[0] = ToolBox.GetAttributeVector2(doc.Root, "headid", Vector2.Zero);
|
||||
headSpriteRange[1] = headSpriteRange[0];
|
||||
if (headSpriteRange[0] == Vector2.Zero)
|
||||
{
|
||||
headSpriteRange[0] = ToolBox.GetAttributeVector2(doc.Root, "maleheadid", Vector2.Zero);
|
||||
headSpriteRange[1] = ToolBox.GetAttributeVector2(doc.Root, "femaleheadid", Vector2.Zero);
|
||||
}
|
||||
|
||||
int genderIndex = (this.gender == Gender.Female) ? 1 : 0;
|
||||
if (headSpriteRange[genderIndex] != Vector2.Zero)
|
||||
{
|
||||
HeadSpriteId = Rand.Range((int)headSpriteRange[genderIndex].X, (int)headSpriteRange[genderIndex].Y + 1);
|
||||
}
|
||||
|
||||
this.Job = (jobPrefab == null) ? Job.Random() : new Job(jobPrefab);
|
||||
|
||||
if (!string.IsNullOrEmpty(name))
|
||||
{
|
||||
this.Name = name;
|
||||
return;
|
||||
}
|
||||
|
||||
name = "";
|
||||
|
||||
if (doc.Root.Element("name") != null)
|
||||
{
|
||||
string firstNamePath = ToolBox.GetAttributeString(doc.Root.Element("name"), "firstname", "");
|
||||
if (firstNamePath != "")
|
||||
{
|
||||
firstNamePath = firstNamePath.Replace("[GENDER]", (this.gender == Gender.Female) ? "f" : "");
|
||||
this.Name = ToolBox.GetRandomLine(firstNamePath);
|
||||
}
|
||||
|
||||
string lastNamePath = ToolBox.GetAttributeString(doc.Root.Element("name"), "lastname", "");
|
||||
if (lastNamePath != "")
|
||||
{
|
||||
lastNamePath = lastNamePath.Replace("[GENDER]", (this.gender == Gender.Female) ? "f" : "");
|
||||
if (this.Name != "") this.Name += " ";
|
||||
this.Name += ToolBox.GetRandomLine(lastNamePath);
|
||||
}
|
||||
}
|
||||
|
||||
Salary = CalculateSalary();
|
||||
}
|
||||
|
||||
private void LoadHeadSprite()
|
||||
{
|
||||
XDocument doc = ToolBox.TryLoadXml(File);
|
||||
if (doc == null) return;
|
||||
|
||||
XElement ragdollElement = doc.Root.Element("ragdoll");
|
||||
foreach (XElement limbElement in ragdollElement.Elements())
|
||||
{
|
||||
if (ToolBox.GetAttributeString(limbElement, "type", "").ToLowerInvariant() != "head") continue;
|
||||
|
||||
XElement spriteElement = limbElement.Element("sprite");
|
||||
|
||||
string spritePath = spriteElement.Attribute("texture").Value;
|
||||
|
||||
spritePath = spritePath.Replace("[GENDER]", (this.gender == Gender.Female) ? "f" : "");
|
||||
spritePath = spritePath.Replace("[HEADID]", HeadSpriteId.ToString());
|
||||
|
||||
string fileName = Path.GetFileNameWithoutExtension(spritePath);
|
||||
|
||||
//go through the files in the directory to find a matching sprite
|
||||
var files = Directory.GetFiles(Path.GetDirectoryName(spritePath)).ToList();
|
||||
foreach (string file in files)
|
||||
{
|
||||
string fileWithoutTags = Path.GetFileNameWithoutExtension(file);
|
||||
fileWithoutTags = fileWithoutTags.Split('[', ']').First();
|
||||
|
||||
if (fileWithoutTags != fileName) continue;
|
||||
|
||||
headSprite = new Sprite(spriteElement, "", file);
|
||||
|
||||
//extract the tags out of the filename
|
||||
SpriteTags = file.Split('[', ']').Skip(1).ToList();
|
||||
if (SpriteTags.Any())
|
||||
{
|
||||
SpriteTags.RemoveAt(SpriteTags.Count-1);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public GUIFrame CreateInfoFrame(Rectangle rect)
|
||||
{
|
||||
GUIFrame frame = new GUIFrame(rect, Color.Transparent);
|
||||
frame.Padding = new Vector4(10.0f,10.0f,10.0f,10.0f);
|
||||
|
||||
return CreateInfoFrame(frame);
|
||||
}
|
||||
|
||||
public GUIFrame CreateInfoFrame(GUIFrame frame)
|
||||
{
|
||||
new GUIImage(new Rectangle(0,0,30,30), HeadSprite, Alignment.TopLeft, frame);
|
||||
|
||||
ScalableFont font = frame.Rect.Width<280 ? GUI.SmallFont : GUI.Font;
|
||||
|
||||
int x = 0, y = 0;
|
||||
new GUITextBlock(new Rectangle(x+60, y, 200, 20), Name, "", frame, font);
|
||||
y += 20;
|
||||
|
||||
if (Job!=null)
|
||||
{
|
||||
new GUITextBlock(new Rectangle(x + 60, y, 200, 20), Job.Name, "", frame, font);
|
||||
y += 30;
|
||||
|
||||
var skills = Job.Skills;
|
||||
skills.Sort((s1, s2) => -s1.Level.CompareTo(s2.Level));
|
||||
|
||||
new GUITextBlock(new Rectangle(x, y, 200, 20), "Skills:", "", frame, font);
|
||||
y += 20;
|
||||
foreach (Skill skill in skills)
|
||||
{
|
||||
Color textColor = Color.White * (0.5f + skill.Level/200.0f);
|
||||
new GUITextBlock(new Rectangle(x, y, 200, 20), skill.Name, Color.Transparent, textColor, Alignment.Left, "", frame).Font = font;
|
||||
new GUITextBlock(new Rectangle(x, y, 200, 20), skill.Level.ToString(), Color.Transparent, textColor, Alignment.Right, "", frame).Font = font;
|
||||
y += 20;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
public GUIFrame CreateCharacterFrame(GUIComponent parent, string text, object userData)
|
||||
{
|
||||
GUIFrame frame = new GUIFrame(new Rectangle(0, 0, 0, 40), Color.Transparent, "ListBoxElement", parent);
|
||||
frame.UserData = userData;
|
||||
|
||||
GUITextBlock textBlock = new GUITextBlock(
|
||||
new Rectangle(40, 0, 0, 25),
|
||||
text,
|
||||
null, null,
|
||||
Alignment.Left, Alignment.Left,
|
||||
"", frame, 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), HeadSprite, Alignment.Left, frame);
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
public void UpdateCharacterItems()
|
||||
{
|
||||
pickedItems.Clear();
|
||||
foreach (Item item in Character.Inventory.Items)
|
||||
{
|
||||
pickedItems.Add(item == null ? (ushort)0 : item.ID);
|
||||
}
|
||||
}
|
||||
|
||||
public CharacterInfo(XElement element)
|
||||
{
|
||||
Name = ToolBox.GetAttributeString(element, "name", "unnamed");
|
||||
|
||||
string genderStr = ToolBox.GetAttributeString(element, "gender", "male").ToLowerInvariant();
|
||||
gender = (genderStr == "m") ? Gender.Male : Gender.Female;
|
||||
|
||||
File = ToolBox.GetAttributeString(element, "file", "");
|
||||
Salary = ToolBox.GetAttributeInt(element, "salary", 1000);
|
||||
headSpriteId = ToolBox.GetAttributeInt(element, "headspriteid", 1);
|
||||
StartItemsGiven = ToolBox.GetAttributeBool(element, "startitemsgiven", false);
|
||||
|
||||
int hullId = ToolBox.GetAttributeInt(element, "hull", -1);
|
||||
if (hullId > 0 && hullId <= ushort.MaxValue) this.HullID = (ushort)hullId;
|
||||
|
||||
pickedItems = new List<ushort>();
|
||||
|
||||
string pickedItemString = ToolBox.GetAttributeString(element, "items", "");
|
||||
if (!string.IsNullOrEmpty(pickedItemString))
|
||||
{
|
||||
string[] itemIds = pickedItemString.Split(',');
|
||||
foreach (string s in itemIds)
|
||||
{
|
||||
pickedItems.Add((ushort)int.Parse(s));
|
||||
}
|
||||
}
|
||||
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
if (subElement.Name.ToString().ToLowerInvariant() != "job") continue;
|
||||
|
||||
Job = new Job(subElement);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private int CalculateSalary()
|
||||
{
|
||||
if (Name == null || Job == null) return 0;
|
||||
|
||||
int salary = Math.Abs(Name.GetHashCode()) % 100;
|
||||
|
||||
foreach (Skill skill in Job.Skills)
|
||||
{
|
||||
salary += skill.Level * 10;
|
||||
}
|
||||
|
||||
return salary;
|
||||
}
|
||||
|
||||
public virtual XElement Save(XElement parentElement)
|
||||
{
|
||||
XElement charElement = new XElement("Character");
|
||||
|
||||
charElement.Add(
|
||||
new XAttribute("name", Name),
|
||||
new XAttribute("file", File),
|
||||
new XAttribute("gender", gender == Gender.Male ? "m" : "f"),
|
||||
new XAttribute("salary", Salary),
|
||||
new XAttribute("headspriteid", HeadSpriteId),
|
||||
new XAttribute("startitemsgiven", StartItemsGiven));
|
||||
|
||||
if (Character != null)
|
||||
{
|
||||
if (Character.Inventory != null)
|
||||
{
|
||||
UpdateCharacterItems();
|
||||
}
|
||||
|
||||
if (Character.AnimController.CurrentHull != null)
|
||||
{
|
||||
HullID = Character.AnimController.CurrentHull.ID;
|
||||
charElement.Add(new XAttribute("hull", Character.AnimController.CurrentHull.ID));
|
||||
}
|
||||
}
|
||||
|
||||
if (pickedItems.Count > 0)
|
||||
{
|
||||
charElement.Add(new XAttribute("items", string.Join(",", pickedItems)));
|
||||
}
|
||||
|
||||
Job.Save(charElement);
|
||||
|
||||
parentElement.Add(charElement);
|
||||
return charElement;
|
||||
}
|
||||
|
||||
public void Remove()
|
||||
{
|
||||
Character = null;
|
||||
//if (headSprite != null)
|
||||
//{
|
||||
// headSprite.Remove();
|
||||
// headSprite = null;
|
||||
//}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,851 @@
|
||||
using Barotrauma.Networking;
|
||||
using Lidgren.Network;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class CharacterStateInfo : PosInfo
|
||||
{
|
||||
public readonly Direction Direction;
|
||||
|
||||
public readonly Entity Interact; //the entity being interacted with
|
||||
|
||||
public readonly AnimController.Animation Animation;
|
||||
|
||||
public CharacterStateInfo(Vector2 pos, float time, Direction dir, Entity interact, AnimController.Animation animation = AnimController.Animation.None)
|
||||
: this(pos, 0, time, dir, interact, animation)
|
||||
{
|
||||
}
|
||||
|
||||
public CharacterStateInfo(Vector2 pos, UInt16 ID, Direction dir, Entity interact, AnimController.Animation animation = AnimController.Animation.None)
|
||||
: this(pos, ID, 0.0f, dir, interact, animation)
|
||||
{
|
||||
}
|
||||
|
||||
protected CharacterStateInfo(Vector2 pos, UInt16 ID, float time, Direction dir, Entity interact, AnimController.Animation animation = AnimController.Animation.None)
|
||||
: base(pos, ID, time)
|
||||
{
|
||||
Direction = dir;
|
||||
Interact = interact;
|
||||
|
||||
Animation = animation;
|
||||
}
|
||||
}
|
||||
|
||||
partial class Character
|
||||
{
|
||||
[Flags]
|
||||
private enum InputNetFlags : ushort
|
||||
{
|
||||
None = 0x0,
|
||||
Left = 0x1,
|
||||
Right = 0x2,
|
||||
Up = 0x4,
|
||||
Down = 0x8,
|
||||
FacingLeft = 0x10,
|
||||
Run = 0x20,
|
||||
Crouch = 0x40,
|
||||
Select = 0x80,
|
||||
Use = 0x100,
|
||||
Aim = 0x200,
|
||||
Attack = 0x400,
|
||||
|
||||
MaxVal = 0x7FF
|
||||
}
|
||||
private InputNetFlags dequeuedInput = 0;
|
||||
private InputNetFlags prevDequeuedInput = 0;
|
||||
|
||||
public UInt16 LastNetworkUpdateID = 0;
|
||||
|
||||
/// <summary>
|
||||
/// ID of the last inputs the server has processed
|
||||
/// </summary>
|
||||
public UInt16 LastProcessedID;
|
||||
|
||||
private struct NetInputMem
|
||||
{
|
||||
public InputNetFlags states; //keys pressed/other boolean states at this step
|
||||
public UInt16 intAim; //aim angle, represented as an unsigned short where 0=0º, 65535=just a bit under 360º
|
||||
public UInt16 interact; //id of the entity being interacted with
|
||||
|
||||
public UInt16 networkUpdateID;
|
||||
}
|
||||
|
||||
private List<NetInputMem> memInput = new List<NetInputMem>();
|
||||
|
||||
private List<CharacterStateInfo> memState = new List<CharacterStateInfo>();
|
||||
private List<CharacterStateInfo> memLocalState = new List<CharacterStateInfo>();
|
||||
|
||||
private bool networkUpdateSent;
|
||||
|
||||
public bool isSynced = false;
|
||||
|
||||
public List<CharacterStateInfo> MemState
|
||||
{
|
||||
get { return memState; }
|
||||
}
|
||||
|
||||
public List<CharacterStateInfo> MemLocalState
|
||||
{
|
||||
get { return memLocalState; }
|
||||
}
|
||||
|
||||
private void UpdateNetInput()
|
||||
{
|
||||
if (this != Character.Controlled)
|
||||
{
|
||||
if (GameMain.Client != null)
|
||||
{
|
||||
//freeze AI characters if more than 1 seconds have passed since last update from the server
|
||||
if (lastRecvPositionUpdateTime < NetTime.Now - 1.0f)
|
||||
{
|
||||
AnimController.Frozen = true;
|
||||
memState.Clear();
|
||||
//hide after 2 seconds
|
||||
if (lastRecvPositionUpdateTime < NetTime.Now - 2.0f)
|
||||
{
|
||||
Enabled = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (GameMain.Server != null && !(this is AICharacter))
|
||||
{
|
||||
if (!AllowInput)
|
||||
{
|
||||
AnimController.Frozen = false;
|
||||
}
|
||||
else if (memInput.Count == 0)
|
||||
{
|
||||
AnimController.Frozen = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
AnimController.Frozen = false;
|
||||
prevDequeuedInput = dequeuedInput;
|
||||
|
||||
LastProcessedID = memInput[memInput.Count - 1].networkUpdateID;
|
||||
dequeuedInput = memInput[memInput.Count - 1].states;
|
||||
|
||||
double aimAngle = ((double)memInput[memInput.Count - 1].intAim / 65535.0) * 2.0 * Math.PI;
|
||||
cursorPosition = (ViewTarget == null ? AnimController.Collider.Position : ViewTarget.Position)
|
||||
+ new Vector2((float)Math.Cos(aimAngle), (float)Math.Sin(aimAngle)) * 60.0f;
|
||||
|
||||
var closestEntity = Entity.FindEntityByID(memInput[memInput.Count - 1].interact);
|
||||
if (closestEntity is Item)
|
||||
{
|
||||
closestItem = (Item)closestEntity;
|
||||
closestCharacter = null;
|
||||
}
|
||||
else if (closestEntity is Character)
|
||||
{
|
||||
closestCharacter = (Character)closestEntity;
|
||||
closestItem = null;
|
||||
}
|
||||
|
||||
memInput.RemoveAt(memInput.Count - 1);
|
||||
|
||||
TransformCursorPos();
|
||||
|
||||
if ((dequeuedInput == InputNetFlags.None || dequeuedInput == InputNetFlags.FacingLeft) && Math.Abs(AnimController.Collider.LinearVelocity.X) < 0.005f && Math.Abs(AnimController.Collider.LinearVelocity.Y) < 0.2f)
|
||||
{
|
||||
while (memInput.Count > 5 && memInput[memInput.Count - 1].states == dequeuedInput)
|
||||
{
|
||||
//remove inputs where the player is not moving at all
|
||||
//helps the server catch up, shouldn't affect final position
|
||||
LastProcessedID = memInput[memInput.Count - 1].networkUpdateID;
|
||||
memInput.RemoveAt(memInput.Count - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (GameMain.Client != null)
|
||||
{
|
||||
var posInfo = new CharacterStateInfo(
|
||||
SimPosition,
|
||||
LastNetworkUpdateID,
|
||||
AnimController.TargetDir,
|
||||
selectedCharacter == null ? (Entity)selectedConstruction : (Entity)selectedCharacter,
|
||||
AnimController.Anim);
|
||||
|
||||
memLocalState.Add(posInfo);
|
||||
|
||||
InputNetFlags newInput = InputNetFlags.None;
|
||||
if (IsKeyDown(InputType.Left)) newInput |= InputNetFlags.Left;
|
||||
if (IsKeyDown(InputType.Right)) newInput |= InputNetFlags.Right;
|
||||
if (IsKeyDown(InputType.Up)) newInput |= InputNetFlags.Up;
|
||||
if (IsKeyDown(InputType.Down)) newInput |= InputNetFlags.Down;
|
||||
if (IsKeyDown(InputType.Run)) newInput |= InputNetFlags.Run;
|
||||
if (IsKeyDown(InputType.Crouch)) newInput |= InputNetFlags.Crouch;
|
||||
if (IsKeyHit(InputType.Select)) newInput |= InputNetFlags.Select; //TODO: clean up the way this input is registered
|
||||
if (IsKeyDown(InputType.Use)) newInput |= InputNetFlags.Use;
|
||||
if (IsKeyDown(InputType.Aim)) newInput |= InputNetFlags.Aim;
|
||||
if (IsKeyDown(InputType.Attack)) newInput |= InputNetFlags.Attack;
|
||||
|
||||
if (AnimController.TargetDir == Direction.Left) newInput |= InputNetFlags.FacingLeft;
|
||||
|
||||
Vector2 relativeCursorPos = cursorPosition - (ViewTarget == null ? AnimController.Collider.Position : ViewTarget.Position);
|
||||
relativeCursorPos.Normalize();
|
||||
UInt16 intAngle = (UInt16)(65535.0 * Math.Atan2(relativeCursorPos.Y, relativeCursorPos.X) / (2.0 * Math.PI));
|
||||
|
||||
NetInputMem newMem = new NetInputMem();
|
||||
newMem.states = newInput;
|
||||
newMem.intAim = intAngle;
|
||||
if (closestItem != null)
|
||||
{
|
||||
newMem.interact = closestItem.ID;
|
||||
}
|
||||
else if (closestCharacter != null)
|
||||
{
|
||||
newMem.interact = closestCharacter.ID;
|
||||
}
|
||||
|
||||
memInput.Insert(0, newMem);
|
||||
LastNetworkUpdateID++;
|
||||
if (memInput.Count > 60)
|
||||
{
|
||||
memInput.RemoveRange(60, memInput.Count - 60);
|
||||
}
|
||||
}
|
||||
else //this == Character.Controlled && GameMain.Client == null
|
||||
{
|
||||
AnimController.Frozen = false;
|
||||
}
|
||||
|
||||
if (networkUpdateSent)
|
||||
{
|
||||
foreach (Key key in keys)
|
||||
{
|
||||
key.DequeueHit();
|
||||
key.DequeueHeld();
|
||||
}
|
||||
|
||||
networkUpdateSent = false;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void ClientWrite(NetBuffer msg, object[] extraData = null)
|
||||
{
|
||||
if (GameMain.Server != null) return;
|
||||
|
||||
if (extraData != null)
|
||||
{
|
||||
switch ((NetEntityEvent.Type)extraData[0])
|
||||
{
|
||||
case NetEntityEvent.Type.InventoryState:
|
||||
msg.WriteRangedInteger(0, 2, 0);
|
||||
inventory.ClientWrite(msg, extraData);
|
||||
break;
|
||||
case NetEntityEvent.Type.Repair:
|
||||
msg.WriteRangedInteger(0, 2, 1);
|
||||
msg.Write(AnimController.Anim == AnimController.Animation.CPR);
|
||||
break;
|
||||
case NetEntityEvent.Type.Status:
|
||||
msg.WriteRangedInteger(0, 2, 2);
|
||||
break;
|
||||
}
|
||||
msg.WritePadBits();
|
||||
}
|
||||
else
|
||||
{
|
||||
msg.Write((byte)ClientNetObject.CHARACTER_INPUT);
|
||||
|
||||
if (memInput.Count > 60)
|
||||
{
|
||||
memInput.RemoveRange(60, memInput.Count - 60);
|
||||
}
|
||||
|
||||
msg.Write(LastNetworkUpdateID);
|
||||
byte inputCount = Math.Min((byte)memInput.Count, (byte)60);
|
||||
msg.Write(inputCount);
|
||||
for (int i = 0; i < inputCount; i++)
|
||||
{
|
||||
msg.WriteRangedInteger(0, (int)InputNetFlags.MaxVal, (int)memInput[i].states);
|
||||
if (memInput[i].states.HasFlag(InputNetFlags.Aim))
|
||||
{
|
||||
msg.Write(memInput[i].intAim);
|
||||
}
|
||||
if (memInput[i].states.HasFlag(InputNetFlags.Select) || memInput[i].states.HasFlag(InputNetFlags.Use))
|
||||
{
|
||||
msg.Write(memInput[i].interact);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
public virtual void ServerRead(ClientNetObject type, NetBuffer msg, Client c)
|
||||
{
|
||||
if (GameMain.Server == null) return;
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case ClientNetObject.CHARACTER_INPUT:
|
||||
|
||||
if (c.Character != this)
|
||||
{
|
||||
#if DEBUG
|
||||
DebugConsole.Log("Received a character update message from a client who's not controlling the character");
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
UInt16 networkUpdateID = msg.ReadUInt16();
|
||||
byte inputCount = msg.ReadByte();
|
||||
|
||||
if (AllowInput) Enabled = true;
|
||||
|
||||
for (int i = 0; i < inputCount; i++)
|
||||
{
|
||||
InputNetFlags newInput = (InputNetFlags)msg.ReadRangedInteger(0, (int)InputNetFlags.MaxVal);
|
||||
UInt16 newAim = 0;
|
||||
UInt16 newInteract = 0;
|
||||
|
||||
if (newInput.HasFlag(InputNetFlags.Aim))
|
||||
{
|
||||
newAim = msg.ReadUInt16();
|
||||
}
|
||||
if (newInput.HasFlag(InputNetFlags.Select) || newInput.HasFlag(InputNetFlags.Use))
|
||||
{
|
||||
newInteract = msg.ReadUInt16();
|
||||
}
|
||||
|
||||
if (AllowInput)
|
||||
{
|
||||
if (NetIdUtils.IdMoreRecent((ushort)(networkUpdateID - i), LastNetworkUpdateID) && (i < 60))
|
||||
{
|
||||
NetInputMem newMem = new NetInputMem();
|
||||
newMem.states = newInput;
|
||||
newMem.intAim = newAim;
|
||||
newMem.interact = newInteract;
|
||||
|
||||
newMem.networkUpdateID = (ushort)(networkUpdateID - i);
|
||||
|
||||
memInput.Insert(i, newMem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (NetIdUtils.IdMoreRecent(networkUpdateID, LastNetworkUpdateID))
|
||||
{
|
||||
LastNetworkUpdateID = networkUpdateID;
|
||||
}
|
||||
if (memInput.Count > 60)
|
||||
{
|
||||
//deleting inputs from the queue here means the server is way behind and data needs to be dropped
|
||||
//we'll make the server drop down to 30 inputs for good measure
|
||||
memInput.RemoveRange(30, memInput.Count - 30);
|
||||
}
|
||||
break;
|
||||
|
||||
case ClientNetObject.ENTITY_STATE:
|
||||
int eventType = msg.ReadRangedInteger(0,2);
|
||||
switch (eventType)
|
||||
{
|
||||
case 0:
|
||||
inventory.ServerRead(type, msg, c);
|
||||
break;
|
||||
case 1:
|
||||
if (c.Character != this)
|
||||
{
|
||||
#if DEBUG
|
||||
DebugConsole.Log("Received a character update message from a client who's not controlling the character");
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
bool doingCPR = msg.ReadBoolean();
|
||||
AnimController.Anim = doingCPR ? AnimController.Animation.CPR : AnimController.Animation.None;
|
||||
break;
|
||||
case 2:
|
||||
if (c.Character != this)
|
||||
{
|
||||
#if DEBUG
|
||||
DebugConsole.Log("Received a character update message from a client who's not controlling the character");
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
if (IsUnconscious)
|
||||
{
|
||||
Kill(lastAttackCauseOfDeath);
|
||||
}
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
msg.ReadPadBits();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void ServerWrite(NetBuffer msg, Client c, object[] extraData = null)
|
||||
{
|
||||
if (GameMain.Server == null) return;
|
||||
|
||||
if (extraData != null)
|
||||
{
|
||||
switch ((NetEntityEvent.Type)extraData[0])
|
||||
{
|
||||
case NetEntityEvent.Type.InventoryState:
|
||||
msg.Write(true);
|
||||
inventory.ClientWrite(msg, extraData);
|
||||
break;
|
||||
case NetEntityEvent.Type.Status:
|
||||
msg.Write(false);
|
||||
WriteStatus(msg);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
msg.Write(ID);
|
||||
|
||||
NetBuffer tempBuffer = new NetBuffer();
|
||||
|
||||
if (this == c.Character)
|
||||
{
|
||||
tempBuffer.Write(true);
|
||||
if (LastNetworkUpdateID < memInput.Count + 1)
|
||||
{
|
||||
tempBuffer.Write((UInt16)0);
|
||||
}
|
||||
else
|
||||
{
|
||||
tempBuffer.Write((UInt16)(LastNetworkUpdateID - memInput.Count - 1));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
tempBuffer.Write(false);
|
||||
|
||||
bool aiming = false;
|
||||
bool use = false;
|
||||
bool attack = false;
|
||||
|
||||
if (IsRemotePlayer)
|
||||
{
|
||||
aiming = dequeuedInput.HasFlag(InputNetFlags.Aim);
|
||||
use = dequeuedInput.HasFlag(InputNetFlags.Use);
|
||||
|
||||
attack = dequeuedInput.HasFlag(InputNetFlags.Attack);
|
||||
}
|
||||
else
|
||||
{
|
||||
aiming = keys[(int)InputType.Aim].GetHeldQueue;
|
||||
use = keys[(int)InputType.Use].GetHeldQueue;
|
||||
|
||||
attack = keys[(int)InputType.Attack].GetHeldQueue;
|
||||
|
||||
networkUpdateSent = true;
|
||||
}
|
||||
|
||||
tempBuffer.Write(aiming);
|
||||
tempBuffer.Write(use);
|
||||
|
||||
bool hasAttackLimb = AnimController.Limbs.Any(l => l != null && l.attack != null);
|
||||
tempBuffer.Write(hasAttackLimb);
|
||||
if (hasAttackLimb) tempBuffer.Write(attack);
|
||||
|
||||
if (aiming)
|
||||
{
|
||||
Vector2 relativeCursorPos = cursorPosition - (ViewTarget == null ? AnimController.Collider.Position : ViewTarget.Position);
|
||||
tempBuffer.Write((UInt16)(65535.0 * Math.Atan2(relativeCursorPos.Y, relativeCursorPos.X) / (2.0 * Math.PI)));
|
||||
}
|
||||
|
||||
tempBuffer.Write(AnimController.TargetDir == Direction.Right);
|
||||
}
|
||||
|
||||
if (selectedCharacter != null || selectedConstruction != null)
|
||||
{
|
||||
tempBuffer.Write(true);
|
||||
tempBuffer.Write(selectedCharacter != null ? selectedCharacter.ID : selectedConstruction.ID);
|
||||
if (selectedCharacter != null)
|
||||
{
|
||||
tempBuffer.Write(AnimController.Anim == AnimController.Animation.CPR);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
tempBuffer.Write(false);
|
||||
}
|
||||
|
||||
tempBuffer.Write(SimPosition.X);
|
||||
tempBuffer.Write(SimPosition.Y);
|
||||
|
||||
tempBuffer.WritePadBits();
|
||||
|
||||
msg.Write((byte)tempBuffer.LengthBytes);
|
||||
msg.Write(tempBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void ClientRead(ServerNetObject type, NetBuffer msg, float sendingTime)
|
||||
{
|
||||
if (GameMain.Server != null) return;
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case ServerNetObject.ENTITY_POSITION:
|
||||
bool facingRight = AnimController.Dir > 0.0f;
|
||||
|
||||
lastRecvPositionUpdateTime = (float)NetTime.Now;
|
||||
|
||||
AnimController.Frozen = false;
|
||||
Enabled = true;
|
||||
|
||||
UInt16 networkUpdateID = 0;
|
||||
if (msg.ReadBoolean())
|
||||
{
|
||||
networkUpdateID = msg.ReadUInt16();
|
||||
}
|
||||
else
|
||||
{
|
||||
bool aimInput = msg.ReadBoolean();
|
||||
keys[(int)InputType.Aim].Held = aimInput;
|
||||
keys[(int)InputType.Aim].SetState(false, aimInput);
|
||||
|
||||
bool useInput = msg.ReadBoolean();
|
||||
keys[(int)InputType.Use].Held = useInput;
|
||||
keys[(int)InputType.Use].SetState(false, useInput);
|
||||
|
||||
bool hasAttackLimb = msg.ReadBoolean();
|
||||
if (hasAttackLimb)
|
||||
{
|
||||
bool attackInput = msg.ReadBoolean();
|
||||
keys[(int)InputType.Attack].Held = attackInput;
|
||||
keys[(int)InputType.Attack].SetState(false, attackInput);
|
||||
}
|
||||
|
||||
if (aimInput)
|
||||
{
|
||||
double aimAngle = ((double)msg.ReadUInt16() / 65535.0) * 2.0 * Math.PI;
|
||||
cursorPosition = (ViewTarget == null ? AnimController.Collider.Position : ViewTarget.Position)
|
||||
+ new Vector2((float)Math.Cos(aimAngle), (float)Math.Sin(aimAngle)) * 60.0f;
|
||||
|
||||
TransformCursorPos();
|
||||
}
|
||||
facingRight = msg.ReadBoolean();
|
||||
}
|
||||
|
||||
bool entitySelected = msg.ReadBoolean();
|
||||
Entity selectedEntity = null;
|
||||
|
||||
AnimController.Animation animation = AnimController.Animation.None;
|
||||
if (entitySelected)
|
||||
{
|
||||
ushort entityID = msg.ReadUInt16();
|
||||
selectedEntity = FindEntityByID(entityID);
|
||||
if (selectedEntity is Character)
|
||||
{
|
||||
bool doingCpr = msg.ReadBoolean();
|
||||
if (doingCpr && selectedCharacter != null)
|
||||
{
|
||||
animation = AnimController.Animation.CPR;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Vector2 pos = new Vector2(
|
||||
msg.ReadFloat(),
|
||||
msg.ReadFloat());
|
||||
|
||||
|
||||
int index = 0;
|
||||
if (GameMain.NetworkMember.Character == this && AllowInput)
|
||||
{
|
||||
var posInfo = new CharacterStateInfo(pos, networkUpdateID, facingRight ? Direction.Right : Direction.Left, selectedEntity, animation);
|
||||
while (index < memState.Count && NetIdUtils.IdMoreRecent(posInfo.ID, memState[index].ID))
|
||||
index++;
|
||||
|
||||
memState.Insert(index, posInfo);
|
||||
}
|
||||
else
|
||||
{
|
||||
var posInfo = new CharacterStateInfo(pos, sendingTime, facingRight ? Direction.Right : Direction.Left, selectedEntity, animation);
|
||||
while (index < memState.Count && posInfo.Timestamp > memState[index].Timestamp)
|
||||
index++;
|
||||
|
||||
memState.Insert(index, posInfo);
|
||||
}
|
||||
|
||||
break;
|
||||
case ServerNetObject.ENTITY_EVENT:
|
||||
bool isInventoryUpdate = msg.ReadBoolean();
|
||||
|
||||
if (isInventoryUpdate)
|
||||
{
|
||||
inventory.ClientRead(type, msg, sendingTime);
|
||||
}
|
||||
else
|
||||
{
|
||||
ReadStatus(msg);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void WriteStatus(NetBuffer msg)
|
||||
{
|
||||
if (GameMain.Client != null)
|
||||
{
|
||||
DebugConsole.ThrowError("Client attempted to write character status to a networked message");
|
||||
return;
|
||||
}
|
||||
|
||||
msg.Write(isDead);
|
||||
if (isDead)
|
||||
{
|
||||
msg.Write((byte)causeOfDeath);
|
||||
}
|
||||
else
|
||||
{
|
||||
msg.WriteRangedSingle(health, minHealth, maxHealth, 8);
|
||||
|
||||
msg.Write(oxygen < 100.0f);
|
||||
if (oxygen < 100.0f)
|
||||
{
|
||||
msg.WriteRangedSingle(oxygen, -100.0f, 100.0f, 8);
|
||||
}
|
||||
|
||||
msg.Write(bleeding > 0.0f);
|
||||
if (bleeding > 0.0f)
|
||||
{
|
||||
msg.WriteRangedSingle(bleeding, 0.0f, 5.0f, 8);
|
||||
}
|
||||
|
||||
msg.Write(Stun > 0.0f);
|
||||
if (Stun > 0.0f)
|
||||
{
|
||||
Stun = MathHelper.Clamp(Stun, 0.0f, 60.0f);
|
||||
msg.WriteRangedSingle(Stun, 0.0f, 60.0f, 8);
|
||||
}
|
||||
|
||||
msg.Write(HuskInfectionState > 0.0f);
|
||||
}
|
||||
}
|
||||
|
||||
private void ReadStatus(NetBuffer msg)
|
||||
{
|
||||
if (GameMain.Server != null)
|
||||
{
|
||||
DebugConsole.ThrowError("Server attempted to read character status from a networked message");
|
||||
return;
|
||||
}
|
||||
|
||||
bool isDead = msg.ReadBoolean();
|
||||
if (isDead)
|
||||
{
|
||||
causeOfDeath = (CauseOfDeath)msg.ReadByte();
|
||||
if (causeOfDeath == CauseOfDeath.Pressure)
|
||||
{
|
||||
Implode(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
Kill(causeOfDeath, true);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
health = msg.ReadRangedSingle(minHealth, maxHealth, 8);
|
||||
|
||||
bool lowOxygen = msg.ReadBoolean();
|
||||
if (lowOxygen)
|
||||
{
|
||||
Oxygen = msg.ReadRangedSingle(-100.0f, 100.0f, 8);
|
||||
}
|
||||
else
|
||||
{
|
||||
Oxygen = 100.0f;
|
||||
}
|
||||
|
||||
bool isBleeding = msg.ReadBoolean();
|
||||
if (isBleeding)
|
||||
{
|
||||
bleeding = msg.ReadRangedSingle(0.0f, 5.0f, 8);
|
||||
}
|
||||
else
|
||||
{
|
||||
bleeding = 0.0f;
|
||||
}
|
||||
|
||||
bool stunned = msg.ReadBoolean();
|
||||
if (stunned)
|
||||
{
|
||||
float newStunTimer = msg.ReadRangedSingle(0.0f, 60.0f, 8);
|
||||
SetStun(newStunTimer, true, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
SetStun(0.0f, true, true);
|
||||
}
|
||||
|
||||
bool huskInfected = msg.ReadBoolean();
|
||||
if (huskInfected)
|
||||
{
|
||||
HuskInfectionState = Math.Max(HuskInfectionState, 0.01f);
|
||||
}
|
||||
else
|
||||
{
|
||||
HuskInfectionState = 0.0f;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public void WriteSpawnData(NetBuffer msg)
|
||||
{
|
||||
if (GameMain.Server == null) return;
|
||||
|
||||
msg.Write(Info == null);
|
||||
msg.Write(ID);
|
||||
msg.Write(ConfigPath);
|
||||
|
||||
msg.Write(WorldPosition.X);
|
||||
msg.Write(WorldPosition.Y);
|
||||
|
||||
msg.Write(Enabled);
|
||||
|
||||
//character with no characterinfo (e.g. some monster)
|
||||
if (Info == null) return;
|
||||
|
||||
Client ownerClient = GameMain.Server.ConnectedClients.Find(c => c.Character == this);
|
||||
if (ownerClient != null)
|
||||
{
|
||||
msg.Write(true);
|
||||
msg.Write(ownerClient.ID);
|
||||
}
|
||||
else if (GameMain.Server.Character == this)
|
||||
{
|
||||
msg.Write(true);
|
||||
msg.Write((byte)0);
|
||||
}
|
||||
else
|
||||
{
|
||||
msg.Write(false);
|
||||
}
|
||||
|
||||
msg.Write(Info.Name);
|
||||
msg.Write(TeamID);
|
||||
|
||||
msg.Write(this is AICharacter);
|
||||
msg.Write(Info.Gender == Gender.Female);
|
||||
msg.Write((byte)Info.HeadSpriteId);
|
||||
if (info.Job != null)
|
||||
{
|
||||
msg.Write(Info.Job.Name);
|
||||
msg.Write((byte)info.Job.Skills.Count);
|
||||
foreach (Skill skill in info.Job.Skills)
|
||||
{
|
||||
msg.WriteRangedInteger(0, 100, MathHelper.Clamp(skill.Level, 0, 100));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
msg.Write("");
|
||||
}
|
||||
}
|
||||
|
||||
public static Character ReadSpawnData(NetBuffer inc, bool spawn = true)
|
||||
{
|
||||
if (GameMain.Server != null) return null;
|
||||
|
||||
bool noInfo = inc.ReadBoolean();
|
||||
ushort id = inc.ReadUInt16();
|
||||
string configPath = inc.ReadString();
|
||||
|
||||
Vector2 position = new Vector2(inc.ReadFloat(), inc.ReadFloat());
|
||||
|
||||
bool enabled = inc.ReadBoolean();
|
||||
|
||||
DebugConsole.Log("Received spawn data for " + configPath);
|
||||
|
||||
Character character = null;
|
||||
if (noInfo)
|
||||
{
|
||||
if (!spawn) return null;
|
||||
|
||||
character = Character.Create(configPath, position, null, true);
|
||||
character.ID = id;
|
||||
}
|
||||
else
|
||||
{
|
||||
bool hasOwner = inc.ReadBoolean();
|
||||
int ownerId = hasOwner ? inc.ReadByte() : -1;
|
||||
|
||||
|
||||
string newName = inc.ReadString();
|
||||
byte teamID = inc.ReadByte();
|
||||
|
||||
bool hasAi = inc.ReadBoolean();
|
||||
bool isFemale = inc.ReadBoolean();
|
||||
int headSpriteID = inc.ReadByte();
|
||||
string jobName = inc.ReadString();
|
||||
|
||||
JobPrefab jobPrefab = null;
|
||||
List<int> skillLevels = new List<int>();
|
||||
if (!string.IsNullOrEmpty(jobName))
|
||||
{
|
||||
jobPrefab = JobPrefab.List.Find(jp => jp.Name == jobName);
|
||||
int skillCount = inc.ReadByte();
|
||||
for (int i = 0; i < skillCount; i++)
|
||||
{
|
||||
skillLevels.Add(inc.ReadRangedInteger(0, 100));
|
||||
}
|
||||
}
|
||||
|
||||
if (!spawn) return null;
|
||||
|
||||
|
||||
CharacterInfo ch = new CharacterInfo(configPath, newName, isFemale ? Gender.Female : Gender.Male, jobPrefab);
|
||||
ch.HeadSpriteId = headSpriteID;
|
||||
|
||||
System.Diagnostics.Debug.Assert(skillLevels.Count == ch.Job.Skills.Count);
|
||||
if (ch.Job != null)
|
||||
{
|
||||
for (int i = 0; i < skillLevels.Count && i < ch.Job.Skills.Count; i++)
|
||||
{
|
||||
ch.Job.Skills[i].Level = skillLevels[i];
|
||||
}
|
||||
}
|
||||
|
||||
character = Create(configPath, position, ch, GameMain.Client.ID != ownerId, hasAi);
|
||||
character.ID = id;
|
||||
character.TeamID = teamID;
|
||||
|
||||
if (GameMain.Client.ID == ownerId)
|
||||
{
|
||||
GameMain.Client.Character = character;
|
||||
Controlled = character;
|
||||
|
||||
GameMain.LightManager.LosEnabled = true;
|
||||
|
||||
character.memInput.Clear();
|
||||
character.memState.Clear();
|
||||
character.memLocalState.Clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
var ownerClient = GameMain.Client.ConnectedClients.Find(c => c.ID == ownerId);
|
||||
if (ownerClient != null)
|
||||
{
|
||||
ownerClient.Character = character;
|
||||
}
|
||||
}
|
||||
|
||||
if (configPath == Character.HumanConfigFile)
|
||||
{
|
||||
GameMain.GameSession.CrewManager.characters.Add(character);
|
||||
}
|
||||
}
|
||||
|
||||
character.Enabled = Controlled == character || enabled;
|
||||
|
||||
return character;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class CharacterSound
|
||||
{
|
||||
public enum SoundType
|
||||
{
|
||||
Idle, Attack, Die
|
||||
}
|
||||
|
||||
public readonly Sound Sound;
|
||||
|
||||
public readonly SoundType Type;
|
||||
|
||||
public readonly float Range;
|
||||
|
||||
public CharacterSound(XElement element)
|
||||
{
|
||||
Sound = Sound.Load(element.Attribute("file").Value);
|
||||
Range = ToolBox.GetAttributeFloat(element, "range", 1000.0f);
|
||||
|
||||
Enum.TryParse<SoundType>(ToolBox.GetAttributeString(element, "state", "Idle"), true, out Type);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class DelayedEffect : StatusEffect
|
||||
{
|
||||
public static List<DelayedEffect> List = new List<DelayedEffect>();
|
||||
|
||||
private float delay;
|
||||
|
||||
private float startTimer;
|
||||
|
||||
private Entity entity;
|
||||
|
||||
private List<IPropertyObject> targets;
|
||||
|
||||
public float StartTimer
|
||||
{
|
||||
get { return startTimer; }
|
||||
}
|
||||
|
||||
public DelayedEffect(XElement element)
|
||||
: base(element)
|
||||
{
|
||||
delay = ToolBox.GetAttributeFloat(element, "delay", 1.0f);
|
||||
}
|
||||
|
||||
public override void Apply(ActionType type, float deltaTime, Entity entity, List<IPropertyObject> targets)
|
||||
{
|
||||
if (this.type != type) return;
|
||||
|
||||
startTimer = delay;
|
||||
this.entity = entity;
|
||||
|
||||
this.targets = targets;
|
||||
|
||||
List.Add(this);
|
||||
}
|
||||
|
||||
public void Update(float deltaTime)
|
||||
{
|
||||
startTimer -= deltaTime;
|
||||
|
||||
if (startTimer > 0.0f) return;
|
||||
|
||||
base.Apply(1.0f, entity, targets);
|
||||
List.Remove(this);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using System;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class HUDProgressBar
|
||||
{
|
||||
private float progress;
|
||||
|
||||
public float Progress
|
||||
{
|
||||
get { return progress; }
|
||||
set { progress = MathHelper.Clamp(value, 0.0f, 1.0f); }
|
||||
}
|
||||
|
||||
public float FadeTimer;
|
||||
|
||||
private Color fullColor, emptyColor;
|
||||
|
||||
private Vector2 worldPosition;
|
||||
|
||||
public Vector2 WorldPosition
|
||||
{
|
||||
get
|
||||
{
|
||||
return worldPosition;
|
||||
}
|
||||
set
|
||||
{
|
||||
worldPosition = value;
|
||||
if (parentSub != null)
|
||||
{
|
||||
worldPosition -= parentSub.DrawPosition;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Vector2 Size;
|
||||
|
||||
private Submarine parentSub;
|
||||
|
||||
public HUDProgressBar(Vector2 worldPosition, Submarine parentSubmarine = null)
|
||||
: this(worldPosition, parentSubmarine, Color.Red, Color.Green)
|
||||
{
|
||||
}
|
||||
|
||||
public HUDProgressBar(Vector2 worldPosition, Submarine parentSubmarine, Color emptyColor, Color fullColor)
|
||||
{
|
||||
this.emptyColor = emptyColor;
|
||||
this.fullColor = fullColor;
|
||||
|
||||
parentSub = parentSubmarine;
|
||||
|
||||
WorldPosition = worldPosition;
|
||||
|
||||
Size = new Vector2(100.0f, 20.0f);
|
||||
|
||||
FadeTimer = 1.0f;
|
||||
}
|
||||
|
||||
public void Update(float deltatime)
|
||||
{
|
||||
FadeTimer -= deltatime;
|
||||
}
|
||||
|
||||
public void Draw(SpriteBatch spriteBatch, Camera cam)
|
||||
{
|
||||
float a = Math.Min(FadeTimer, 1.0f);
|
||||
|
||||
Vector2 pos = new Vector2(WorldPosition.X - Size.X / 2, WorldPosition.Y + Size.Y / 2);
|
||||
|
||||
if (parentSub != null)
|
||||
{
|
||||
pos += parentSub.DrawPosition;
|
||||
}
|
||||
|
||||
pos = cam.WorldToScreen(pos);
|
||||
|
||||
GUI.DrawProgressBar(spriteBatch,
|
||||
new Vector2(pos.X, -pos.Y),
|
||||
Size, progress,
|
||||
Color.Lerp(emptyColor, fullColor, progress) * a,
|
||||
Color.White * a * 0.8f);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class HuskInfection
|
||||
{
|
||||
public enum InfectionState
|
||||
{
|
||||
Dormant, Transition, Active
|
||||
}
|
||||
|
||||
const float IncubationDuration = 300.0f;
|
||||
|
||||
private InfectionState state;
|
||||
|
||||
private float incubationTimer;
|
||||
public float IncubationTimer
|
||||
{
|
||||
get { return incubationTimer; }
|
||||
set
|
||||
{
|
||||
incubationTimer = MathHelper.Clamp(value, 0.0f, 1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
public InfectionState State
|
||||
{
|
||||
get { return state; }
|
||||
}
|
||||
|
||||
public bool CanSpeak
|
||||
{
|
||||
get { return IncubationTimer < 0.5f; }
|
||||
}
|
||||
|
||||
public HuskInfection(Character character)
|
||||
{
|
||||
character.OnDeath += CharacterDead;
|
||||
}
|
||||
|
||||
public void Update(float deltaTime, Character character)
|
||||
{
|
||||
if (IncubationTimer < 0.5f)
|
||||
{
|
||||
UpdateDormantState(deltaTime, character);
|
||||
}
|
||||
else if (IncubationTimer < 1.0f)
|
||||
{
|
||||
UpdateTransitionState(deltaTime, character);
|
||||
}
|
||||
else
|
||||
{
|
||||
UpdateActiveState(deltaTime, character);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateDormantState(float deltaTime, Character character)
|
||||
{
|
||||
float prevTimer = IncubationTimer;
|
||||
|
||||
state = InfectionState.Dormant;
|
||||
|
||||
IncubationTimer += deltaTime / IncubationDuration;
|
||||
|
||||
if (Character.Controlled != character) return;
|
||||
|
||||
if (prevTimer % 0.1f > 0.05f && IncubationTimer % 0.1f < 0.05f)
|
||||
{
|
||||
GUI.AddMessage(InfoTextManager.GetInfoText("HuskDormant"), Color.Red, 4.0f);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateTransitionState(float deltaTime, Character character)
|
||||
{
|
||||
IncubationTimer += deltaTime / IncubationDuration;
|
||||
|
||||
if (state == InfectionState.Dormant && Character.Controlled == character)
|
||||
{
|
||||
new GUIMessageBox("", InfoTextManager.GetInfoText("HuskCantSpeak"));
|
||||
}
|
||||
|
||||
state = InfectionState.Transition;
|
||||
}
|
||||
|
||||
private void UpdateActiveState(float deltaTime, Character character)
|
||||
{
|
||||
if (state != InfectionState.Active)
|
||||
{
|
||||
if (Character.Controlled==character) new GUIMessageBox("", InfoTextManager.GetInfoText("HuskActivate"));
|
||||
ActivateHusk(character);
|
||||
state = InfectionState.Active;
|
||||
}
|
||||
|
||||
character.AddDamage(CauseOfDeath.Husk, 0.5f*deltaTime, null);
|
||||
}
|
||||
|
||||
|
||||
private void ActivateHusk(Character character)
|
||||
{
|
||||
character.NeedsAir = false;
|
||||
AttachHuskAppendage(character);
|
||||
}
|
||||
|
||||
private void AttachHuskAppendage(Character character)
|
||||
{
|
||||
XDocument doc = ToolBox.TryLoadXml(Path.Combine("Content", "Characters", "Human", "huskappendage.xml"));
|
||||
if (doc == null || doc.Root == null) return;
|
||||
|
||||
var limbElement = doc.Root.Element("limb");
|
||||
if (limbElement == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Error in huskappendage.xml - limb element not found");
|
||||
return;
|
||||
}
|
||||
|
||||
var jointElement = doc.Root.Element("joint");
|
||||
if (jointElement == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Error in huskappendage.xml - joint element not found");
|
||||
return;
|
||||
}
|
||||
|
||||
character.SetStun(0.5f);
|
||||
if (character.AnimController.Dir < 1.0f)
|
||||
{
|
||||
character.AnimController.Flip();
|
||||
}
|
||||
|
||||
var torso = character.AnimController.GetLimb(LimbType.Torso);
|
||||
|
||||
var newLimb = new Limb(character, limbElement);
|
||||
newLimb.body.Submarine = character.Submarine;
|
||||
newLimb.body.SetTransform(torso.SimPosition, torso.Rotation);
|
||||
|
||||
character.AnimController.AddLimb(newLimb);
|
||||
character.AnimController.AddJoint(jointElement);
|
||||
}
|
||||
|
||||
public void Remove(Character character)
|
||||
{
|
||||
if (character != null)
|
||||
character.OnDeath -= CharacterDead;
|
||||
}
|
||||
|
||||
private void CharacterDead(Character character, CauseOfDeath causeOfDeath)
|
||||
{
|
||||
if (GameMain.Client != null) return;
|
||||
|
||||
var husk = Character.Create(
|
||||
Path.Combine("Content", "Characters", "Human", "humanhusk.xml"),
|
||||
character.WorldPosition,
|
||||
character.Info,
|
||||
false, true);
|
||||
|
||||
foreach (Limb limb in husk.AnimController.Limbs)
|
||||
{
|
||||
if (limb.type == LimbType.None)
|
||||
{
|
||||
limb.body.SetTransform(character.SimPosition, 0.0f);
|
||||
continue;
|
||||
}
|
||||
|
||||
var matchingLimb = character.AnimController.GetLimb(limb.type);
|
||||
limb.body.SetTransform(matchingLimb.SimPosition, matchingLimb.Rotation);
|
||||
}
|
||||
|
||||
for (int i = 0; i < character.Inventory.Items.Length; i++)
|
||||
{
|
||||
if (character.Inventory.Items[i] == null) continue;
|
||||
husk.Inventory.TryPutItem(character.Inventory.Items[i], i, true);
|
||||
}
|
||||
|
||||
character.Enabled = false;
|
||||
Entity.Spawner.AddToRemoveQueue(character);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class Job
|
||||
{
|
||||
|
||||
private readonly JobPrefab prefab;
|
||||
|
||||
private Dictionary<string, Skill> skills;
|
||||
|
||||
public string Name
|
||||
{
|
||||
get { return prefab.Name; }
|
||||
}
|
||||
|
||||
public string Description
|
||||
{
|
||||
get { return prefab.Description; }
|
||||
}
|
||||
|
||||
public JobPrefab Prefab
|
||||
{
|
||||
get { return prefab; }
|
||||
}
|
||||
|
||||
public XElement SpawnItems
|
||||
{
|
||||
get { return prefab.Items; }
|
||||
}
|
||||
|
||||
//public List<bool> EquipSpawnItem
|
||||
//{
|
||||
// get { return prefab.EquipItem; }
|
||||
//}
|
||||
|
||||
public List<Skill> Skills
|
||||
{
|
||||
get { return skills.Values.ToList(); }
|
||||
}
|
||||
|
||||
public Job(JobPrefab jobPrefab)
|
||||
{
|
||||
prefab = jobPrefab;
|
||||
|
||||
skills = new Dictionary<string, Skill>();
|
||||
foreach (SkillPrefab skillPrefab in prefab.Skills)
|
||||
{
|
||||
skills.Add(skillPrefab.Name, new Skill(skillPrefab));
|
||||
}
|
||||
}
|
||||
|
||||
public Job(XElement element)
|
||||
{
|
||||
string name = ToolBox.GetAttributeString(element, "name", "").ToLowerInvariant();
|
||||
prefab = JobPrefab.List.Find(jp => jp.Name.ToLowerInvariant() == name);
|
||||
|
||||
skills = new Dictionary<string, Skill>();
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
if (subElement.Name.ToString().ToLowerInvariant() != "skill") continue;
|
||||
string skillName = ToolBox.GetAttributeString(subElement, "name", "");
|
||||
if (string.IsNullOrEmpty(name)) continue;
|
||||
skills.Add(
|
||||
skillName,
|
||||
new Skill(skillName, ToolBox.GetAttributeInt(subElement, "level", 0)));
|
||||
}
|
||||
}
|
||||
|
||||
public static Job Random()
|
||||
{
|
||||
JobPrefab prefab = JobPrefab.List[Rand.Int(JobPrefab.List.Count - 1, false)];
|
||||
|
||||
return new Job(prefab);
|
||||
}
|
||||
|
||||
public int GetSkillLevel(string skillName)
|
||||
{
|
||||
Skill skill = null;
|
||||
skills.TryGetValue(skillName, out skill);
|
||||
|
||||
return (skill==null) ? 0 : skill.Level;
|
||||
}
|
||||
|
||||
public void GiveJobItems(Character character, WayPoint spawnPoint)
|
||||
{
|
||||
if (SpawnItems == null) return;
|
||||
|
||||
foreach (XElement itemElement in SpawnItems.Elements())
|
||||
{
|
||||
InitializeJobItem(character, spawnPoint, itemElement);
|
||||
}
|
||||
}
|
||||
|
||||
private void InitializeJobItem(Character character, WayPoint spawnPoint, XElement itemElement, Item parentItem = null)
|
||||
{
|
||||
string itemName = ToolBox.GetAttributeString(itemElement, "name", "");
|
||||
|
||||
ItemPrefab itemPrefab = ItemPrefab.list.Find(ip => ip.Name == itemName) as ItemPrefab;
|
||||
if (itemPrefab == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Tried to spawn \"" + Name + "\" with the item \"" + itemName + "\". Matching item prefab not found.");
|
||||
return;
|
||||
}
|
||||
|
||||
Item item = new Item(itemPrefab, character.Position, null);
|
||||
|
||||
if (GameMain.Server != null && Entity.Spawner != null)
|
||||
{
|
||||
Entity.Spawner.CreateNetworkEvent(item, false);
|
||||
}
|
||||
|
||||
if (ToolBox.GetAttributeBool(itemElement, "equip", false))
|
||||
{
|
||||
List<InvSlotType> allowedSlots = new List<InvSlotType>(item.AllowedSlots);
|
||||
allowedSlots.Remove(InvSlotType.Any);
|
||||
|
||||
character.Inventory.TryPutItem(item, allowedSlots);
|
||||
}
|
||||
else
|
||||
{
|
||||
character.Inventory.TryPutItem(item, item.AllowedSlots);
|
||||
}
|
||||
|
||||
if (item.Prefab.Name == "ID Card" && spawnPoint != null)
|
||||
{
|
||||
foreach (string s in spawnPoint.IdCardTags)
|
||||
{
|
||||
item.AddTag(s);
|
||||
}
|
||||
}
|
||||
|
||||
if (parentItem != null) parentItem.Combine(item);
|
||||
|
||||
foreach (XElement childItemElement in itemElement.Elements())
|
||||
{
|
||||
InitializeJobItem(character, spawnPoint, childItemElement, item);
|
||||
}
|
||||
}
|
||||
|
||||
public XElement Save(XElement parentElement)
|
||||
{
|
||||
XElement jobElement = new XElement("job");
|
||||
|
||||
jobElement.Add(new XAttribute("name", Name));
|
||||
|
||||
foreach (KeyValuePair<string, Skill> skill in skills)
|
||||
{
|
||||
jobElement.Add(new XElement("skill", new XAttribute("name", skill.Value.Name), new XAttribute("level", skill.Value.Level)));
|
||||
}
|
||||
|
||||
parentElement.Add(jobElement);
|
||||
return jobElement;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Xml.Linq;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class JobPrefab
|
||||
{
|
||||
public static List<JobPrefab> List;
|
||||
|
||||
public readonly XElement Items;
|
||||
public readonly List<string> ItemNames;
|
||||
|
||||
public List<SkillPrefab> Skills;
|
||||
|
||||
public string Name
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public string Description
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
|
||||
//if set to true, a client that has chosen this as their preferred job will get it no matter what
|
||||
public bool AllowAlways
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
//how many crew members can have the job (only one captain etc)
|
||||
public int MaxNumber
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
//how many crew members are REQUIRED to have the job
|
||||
//(i.e. if one captain is required, one captain is chosen even if all the players have set captain to lowest preference)
|
||||
public int MinNumber
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public float Commonness
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public JobPrefab(XElement element)
|
||||
{
|
||||
Name = ToolBox.GetAttributeString(element, "name", "name not found");
|
||||
|
||||
Description = ToolBox.GetAttributeString(element, "description", "");
|
||||
|
||||
MinNumber = ToolBox.GetAttributeInt(element, "minnumber", 0);
|
||||
MaxNumber = ToolBox.GetAttributeInt(element, "maxnumber", 10);
|
||||
|
||||
Commonness = ToolBox.GetAttributeInt(element, "commonness", 10);
|
||||
|
||||
AllowAlways = ToolBox.GetAttributeBool(element, "allowalways", false);
|
||||
|
||||
ItemNames = new List<string>();
|
||||
|
||||
Skills = new List<SkillPrefab>();
|
||||
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "items":
|
||||
Items = subElement;
|
||||
foreach (XElement itemElement in subElement.Elements())
|
||||
{
|
||||
string itemName = ToolBox.GetAttributeString(itemElement, "name", "");
|
||||
if (!string.IsNullOrWhiteSpace(itemName)) ItemNames.Add(itemName);
|
||||
}
|
||||
break;
|
||||
case "skills":
|
||||
foreach (XElement skillElement in subElement.Elements())
|
||||
{
|
||||
Skills.Add(new SkillPrefab(skillElement));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Skills.Sort((x,y) => y.LevelRange.X.CompareTo(x.LevelRange.X));
|
||||
}
|
||||
|
||||
public static JobPrefab Random()
|
||||
{
|
||||
return List[Rand.Int(List.Count)];
|
||||
}
|
||||
|
||||
public GUIFrame CreateInfoFrame()
|
||||
{
|
||||
int width = 500, height = 400;
|
||||
|
||||
GUIFrame backFrame = new GUIFrame(Rectangle.Empty, Color.Black*0.5f);
|
||||
backFrame.Padding = Vector4.Zero;
|
||||
|
||||
GUIFrame frame = new GUIFrame(new Rectangle(GameMain.GraphicsWidth / 2 - width / 2, GameMain.GraphicsHeight / 2 - height / 2, width, height), "", backFrame);
|
||||
frame.Padding = new Vector4(30.0f, 30.0f, 30.0f, 30.0f);
|
||||
|
||||
new GUITextBlock(new Rectangle(0,0,100,20), Name, "", Alignment.TopLeft, Alignment.TopLeft, frame, false, GUI.LargeFont);
|
||||
|
||||
var descriptionBlock = new GUITextBlock(new Rectangle(0, 40, 0, 0), Description, "", Alignment.TopLeft, Alignment.TopLeft, frame, true, GUI.SmallFont);
|
||||
|
||||
new GUITextBlock(new Rectangle(0, 40 + descriptionBlock.Rect.Height + 20, 100, 20), "Skills: ", "", Alignment.TopLeft, Alignment.TopLeft, frame, false, GUI.LargeFont);
|
||||
|
||||
int y = 40 + descriptionBlock.Rect.Height + 50;
|
||||
foreach (SkillPrefab skill in Skills)
|
||||
{
|
||||
string skillDescription = Skill.GetLevelName((int)skill.LevelRange.X);
|
||||
string skillDescription2 = Skill.GetLevelName((int)skill.LevelRange.Y);
|
||||
|
||||
if (skillDescription2!= skillDescription)
|
||||
{
|
||||
skillDescription += "/"+skillDescription2;
|
||||
}
|
||||
new GUITextBlock(new Rectangle(0, y, 100, 20),
|
||||
" - " + skill.Name + ": " + skillDescription, "", Alignment.TopLeft, Alignment.TopLeft, frame, false, GUI.SmallFont);
|
||||
|
||||
y += 20;
|
||||
}
|
||||
|
||||
new GUITextBlock(new Rectangle(250, 40 + descriptionBlock.Rect.Height + 20, 0, 20), "Items: ", "", Alignment.TopLeft, Alignment.TopLeft, frame, false, GUI.LargeFont);
|
||||
|
||||
y = 40 + descriptionBlock.Rect.Height + 50;
|
||||
foreach (string itemName in ItemNames)
|
||||
{
|
||||
new GUITextBlock(new Rectangle(250, y, 100, 20),
|
||||
" - " + itemName, "", Alignment.TopLeft, Alignment.TopLeft, frame, false, GUI.SmallFont);
|
||||
|
||||
y += 20;
|
||||
}
|
||||
|
||||
return backFrame;
|
||||
}
|
||||
|
||||
public static void LoadAll(List<string> filePaths)
|
||||
{
|
||||
List = new List<JobPrefab>();
|
||||
|
||||
foreach (string filePath in filePaths)
|
||||
{
|
||||
XDocument doc = ToolBox.TryLoadXml(filePath);
|
||||
if (doc == null || doc.Root == null) return;
|
||||
|
||||
foreach (XElement element in doc.Root.Elements())
|
||||
{
|
||||
JobPrefab job = new JobPrefab(element);
|
||||
List.Add(job);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class Skill
|
||||
{
|
||||
SkillPrefab prefab;
|
||||
|
||||
string name;
|
||||
int level;
|
||||
|
||||
static string[] levelNames = new string[] {
|
||||
"Untrained", "Incompetent", "Novice",
|
||||
"Adequate", "Competent", "Proficient",
|
||||
"Professional", "Master", "Legendary" };
|
||||
|
||||
public string Name
|
||||
{
|
||||
get { return name; }
|
||||
}
|
||||
|
||||
public int Level
|
||||
{
|
||||
get { return level; }
|
||||
set { level = MathHelper.Clamp(value, 0, 100); }
|
||||
}
|
||||
|
||||
public Skill(SkillPrefab prefab)
|
||||
{
|
||||
this.prefab = prefab;
|
||||
this.name = prefab.Name;
|
||||
|
||||
this.level = (int)Rand.Range(prefab.LevelRange.X, prefab.LevelRange.Y);
|
||||
}
|
||||
|
||||
public Skill(string name, int level)
|
||||
{
|
||||
this.name = name;
|
||||
|
||||
this.level = level;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// returns the "name" of some skill level (0-10 -> untrained, etc)
|
||||
/// </summary>
|
||||
public static string GetLevelName(int level)
|
||||
{
|
||||
level = MathHelper.Clamp(level, 0, 100);
|
||||
int scaledLevel = (int)Math.Floor((level / 100.0f) * levelNames.Length);
|
||||
|
||||
return levelNames[Math.Min(scaledLevel, levelNames.Length - 1)];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class SkillPrefab
|
||||
{
|
||||
private string name;
|
||||
|
||||
private string description;
|
||||
|
||||
private Vector2 levelRange;
|
||||
|
||||
public string Name
|
||||
{
|
||||
get { return name; }
|
||||
}
|
||||
|
||||
public string Description
|
||||
{
|
||||
get { return description; }
|
||||
}
|
||||
|
||||
public Vector2 LevelRange
|
||||
{
|
||||
get { return levelRange; }
|
||||
}
|
||||
|
||||
public SkillPrefab(XElement element)
|
||||
{
|
||||
name = ToolBox.GetAttributeString(element, "name", "");
|
||||
|
||||
var levelString = ToolBox.GetAttributeString(element, "level", "");
|
||||
if (levelString.Contains(","))
|
||||
{
|
||||
levelRange = ToolBox.ParseToVector2(levelString, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
float skillLevel = float.Parse(levelString, System.Globalization.CultureInfo.InvariantCulture);
|
||||
levelRange = new Vector2(skillLevel, skillLevel);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,584 @@
|
||||
using System;
|
||||
using System.Xml.Linq;
|
||||
using FarseerPhysics;
|
||||
using FarseerPhysics.Dynamics;
|
||||
using FarseerPhysics.Dynamics.Joints;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using Barotrauma.Items.Components;
|
||||
using System.Collections.Generic;
|
||||
using Barotrauma.Lights;
|
||||
using System.Linq;
|
||||
using System.IO;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
public enum LimbType
|
||||
{
|
||||
None, LeftHand, RightHand, LeftArm, RightArm,
|
||||
LeftLeg, RightLeg, LeftFoot, RightFoot, Head, Torso, Tail, Legs, RightThigh, LeftThigh, Waist
|
||||
};
|
||||
|
||||
class Limb
|
||||
{
|
||||
private const float LimbDensity = 15;
|
||||
private const float LimbAngularDamping = 7;
|
||||
|
||||
public readonly Character character;
|
||||
|
||||
//the physics body of the limb
|
||||
public PhysicsBody body;
|
||||
|
||||
private readonly int refJointIndex;
|
||||
|
||||
private readonly float steerForce;
|
||||
|
||||
private readonly bool doesFlip;
|
||||
|
||||
protected readonly Vector2 stepOffset;
|
||||
|
||||
public Sprite sprite, damagedSprite;
|
||||
|
||||
public bool inWater;
|
||||
|
||||
public FixedMouseJoint pullJoint;
|
||||
|
||||
public readonly Lights.LightSource LightSource;
|
||||
|
||||
public readonly LimbType type;
|
||||
|
||||
public readonly bool ignoreCollisions;
|
||||
|
||||
private float damage, burnt;
|
||||
|
||||
private readonly Vector2 armorSector;
|
||||
private readonly float armorValue;
|
||||
|
||||
Sound hitSound;
|
||||
//a timer for delaying when a hitsound/attacksound can be played again
|
||||
public float soundTimer;
|
||||
public const float SoundInterval = 0.4f;
|
||||
|
||||
public readonly Attack attack;
|
||||
|
||||
private Direction dir;
|
||||
|
||||
private List<WearableSprite> wearingItems;
|
||||
|
||||
private Vector2 animTargetPos;
|
||||
|
||||
public bool DoesFlip
|
||||
{
|
||||
get { return doesFlip; }
|
||||
}
|
||||
|
||||
public Vector2 WorldPosition
|
||||
{
|
||||
get { return character.Submarine == null ? Position : Position + character.Submarine.Position; }
|
||||
}
|
||||
|
||||
public Vector2 Position
|
||||
{
|
||||
get { return ConvertUnits.ToDisplayUnits(body.SimPosition); }
|
||||
}
|
||||
|
||||
public Vector2 SimPosition
|
||||
{
|
||||
get { return body.SimPosition; }
|
||||
}
|
||||
|
||||
public float Rotation
|
||||
{
|
||||
get { return body.Rotation; }
|
||||
}
|
||||
|
||||
//where an animcontroller is trying to pull the limb, only used for debug visualization
|
||||
public Vector2 AnimTargetPos
|
||||
{
|
||||
get { return animTargetPos; }
|
||||
}
|
||||
|
||||
public float SteerForce
|
||||
{
|
||||
get { return steerForce; }
|
||||
}
|
||||
|
||||
public float Mass
|
||||
{
|
||||
get { return body.Mass; }
|
||||
}
|
||||
|
||||
public bool Disabled { get; set; }
|
||||
|
||||
public Sound HitSound
|
||||
{
|
||||
get { return hitSound; }
|
||||
}
|
||||
|
||||
public Vector2 LinearVelocity
|
||||
{
|
||||
get { return body.LinearVelocity; }
|
||||
}
|
||||
|
||||
public float Dir
|
||||
{
|
||||
get { return ((dir == Direction.Left) ? -1.0f : 1.0f); }
|
||||
set { dir = (value==-1.0f) ? Direction.Left : Direction.Right; }
|
||||
}
|
||||
|
||||
public int RefJointIndex
|
||||
{
|
||||
get { return refJointIndex; }
|
||||
}
|
||||
|
||||
public Vector2 StepOffset
|
||||
{
|
||||
get { return stepOffset; }
|
||||
}
|
||||
|
||||
public float Burnt
|
||||
{
|
||||
get { return burnt; }
|
||||
set { burnt = MathHelper.Clamp(value,0.0f,100.0f); }
|
||||
}
|
||||
|
||||
private float scale;
|
||||
|
||||
public float AttackTimer;
|
||||
|
||||
//public float Damage
|
||||
//{
|
||||
// get { return damage; }
|
||||
// set
|
||||
// {
|
||||
// damage = Math.Max(value, 0.0f);
|
||||
// if (damage >=maxHealth) Character.Kill();
|
||||
// }
|
||||
//}
|
||||
|
||||
//public float MaxHealth
|
||||
//{
|
||||
// get { return maxHealth; }
|
||||
//}
|
||||
|
||||
//public float Bleeding
|
||||
//{
|
||||
// get { return bleeding; }
|
||||
// set { bleeding = MathHelper.Clamp(value, 0.0f, 100.0f); }
|
||||
//}
|
||||
|
||||
public List<WearableSprite> WearingItems
|
||||
{
|
||||
get { return wearingItems; }
|
||||
set { wearingItems = value; }
|
||||
}
|
||||
|
||||
//public WearableSprite WearingItemSprite
|
||||
//{
|
||||
// get { return wearingItemSprite; }
|
||||
// set { wearingItemSprite = value; }
|
||||
//}
|
||||
|
||||
public Limb (Character character, XElement element, float scale = 1.0f)
|
||||
{
|
||||
this.character = character;
|
||||
|
||||
WearingItems = new List<WearableSprite>();
|
||||
|
||||
dir = Direction.Right;
|
||||
|
||||
doesFlip = ToolBox.GetAttributeBool(element, "flip", false);
|
||||
|
||||
this.scale = scale;
|
||||
|
||||
body = new PhysicsBody(element, scale);
|
||||
|
||||
if (ToolBox.GetAttributeBool(element, "ignorecollisions", false))
|
||||
{
|
||||
body.CollisionCategories = Category.None;
|
||||
body.CollidesWith = Category.None;
|
||||
|
||||
ignoreCollisions = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
//limbs don't collide with each other
|
||||
body.CollisionCategories = Physics.CollisionCharacter;
|
||||
body.CollidesWith = Physics.CollisionAll & ~Physics.CollisionCharacter & ~Physics.CollisionItem;
|
||||
}
|
||||
|
||||
body.UserData = this;
|
||||
|
||||
refJointIndex = -1;
|
||||
|
||||
Vector2 pullJointPos = Vector2.Zero;
|
||||
|
||||
if (element.Attribute("type") != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
type = (LimbType)Enum.Parse(typeof(LimbType), element.Attribute("type").Value, true);
|
||||
}
|
||||
catch
|
||||
{
|
||||
type = LimbType.None;
|
||||
DebugConsole.ThrowError("Error in "+element+"! \""+element.Attribute("type").Value+"\" is not a valid limb type");
|
||||
}
|
||||
|
||||
|
||||
pullJointPos = ToolBox.GetAttributeVector2(element, "pullpos", Vector2.Zero) * scale;
|
||||
pullJointPos = ConvertUnits.ToSimUnits(pullJointPos);
|
||||
|
||||
stepOffset = ToolBox.GetAttributeVector2(element, "stepoffset", Vector2.Zero) * scale;
|
||||
stepOffset = ConvertUnits.ToSimUnits(stepOffset);
|
||||
|
||||
refJointIndex = ToolBox.GetAttributeInt(element, "refjoint", -1);
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
type = LimbType.None;
|
||||
}
|
||||
|
||||
pullJoint = new FixedMouseJoint(body.FarseerBody, pullJointPos);
|
||||
pullJoint.Enabled = false;
|
||||
pullJoint.MaxForce = ((type == LimbType.LeftHand || type == LimbType.RightHand) ? 400.0f : 150.0f) * body.Mass;
|
||||
|
||||
GameMain.World.AddJoint(pullJoint);
|
||||
|
||||
steerForce = ToolBox.GetAttributeFloat(element, "steerforce", 0.0f);
|
||||
|
||||
//maxHealth = Math.Max(ToolBox.GetAttributeFloat(element, "health", 100.0f),1.0f);
|
||||
|
||||
armorSector = ToolBox.GetAttributeVector2(element, "armorsector", Vector2.Zero);
|
||||
armorSector.X = MathHelper.ToRadians(armorSector.X);
|
||||
armorSector.Y = MathHelper.ToRadians(armorSector.Y);
|
||||
|
||||
armorValue = Math.Max(ToolBox.GetAttributeFloat(element, "armor", 0.0f), 0.0f);
|
||||
|
||||
body.BodyType = BodyType.Dynamic;
|
||||
body.FarseerBody.AngularDamping = LimbAngularDamping;
|
||||
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "sprite":
|
||||
string spritePath = subElement.Attribute("texture").Value;
|
||||
|
||||
string spritePathWithTags = spritePath;
|
||||
|
||||
if (character.Info != null)
|
||||
{
|
||||
spritePath = spritePath.Replace("[GENDER]", (character.Info.Gender == Gender.Female) ? "f" : "");
|
||||
spritePath = spritePath.Replace("[HEADID]", character.Info.HeadSpriteId.ToString());
|
||||
|
||||
if (character.Info.HeadSprite != null && character.Info.SpriteTags.Any())
|
||||
{
|
||||
string tags = "";
|
||||
character.Info.SpriteTags.ForEach(tag => tags += "[" + tag + "]");
|
||||
|
||||
spritePathWithTags = Path.Combine(
|
||||
Path.GetDirectoryName(spritePath),
|
||||
Path.GetFileNameWithoutExtension(spritePath) + tags + Path.GetExtension(spritePath));
|
||||
}
|
||||
}
|
||||
|
||||
if (File.Exists(spritePathWithTags))
|
||||
{
|
||||
sprite = new Sprite(subElement, "", spritePathWithTags);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
sprite = new Sprite(subElement, "", spritePath);
|
||||
}
|
||||
|
||||
break;
|
||||
case "damagedsprite":
|
||||
string damagedSpritePath = subElement.Attribute("texture").Value;
|
||||
|
||||
if (character.Info != null)
|
||||
{
|
||||
damagedSpritePath = damagedSpritePath.Replace("[GENDER]", (character.Info.Gender == Gender.Female) ? "f" : "");
|
||||
damagedSpritePath = damagedSpritePath.Replace("[HEADID]", character.Info.HeadSpriteId.ToString());
|
||||
}
|
||||
|
||||
damagedSprite = new Sprite(subElement, "", damagedSpritePath);
|
||||
break;
|
||||
case "lightsource":
|
||||
LightSource = new LightSource(subElement);
|
||||
|
||||
break;
|
||||
case "attack":
|
||||
attack = new Attack(subElement);
|
||||
break;
|
||||
case "sound":
|
||||
hitSound = Sound.Load(ToolBox.GetAttributeString(subElement, "file", ""));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void MoveToPos(Vector2 pos, float force, bool pullFromCenter=false)
|
||||
{
|
||||
Vector2 pullPos = body.SimPosition;
|
||||
if (pullJoint!=null && !pullFromCenter)
|
||||
{
|
||||
pullPos = pullJoint.WorldAnchorA;
|
||||
}
|
||||
|
||||
animTargetPos = pos;
|
||||
|
||||
body.MoveToPos(pos, force, pullPos);
|
||||
}
|
||||
|
||||
public AttackResult AddDamage(Vector2 position, DamageType damageType, float amount, float bleedingAmount, bool playSound)
|
||||
{
|
||||
DamageSoundType damageSoundType = (damageType == DamageType.Blunt) ? DamageSoundType.LimbBlunt : DamageSoundType.LimbSlash;
|
||||
|
||||
bool hitArmor = false;
|
||||
float totalArmorValue = 0.0f;
|
||||
|
||||
if (armorValue>0.0f && SectorHit(armorSector, position))
|
||||
{
|
||||
hitArmor = true;
|
||||
totalArmorValue += armorValue;
|
||||
}
|
||||
|
||||
foreach (WearableSprite wearable in wearingItems)
|
||||
{
|
||||
if (wearable.WearableComponent.ArmorValue > 0.0f &&
|
||||
SectorHit(wearable.WearableComponent.ArmorSectorLimits, position))
|
||||
{
|
||||
hitArmor = true;
|
||||
totalArmorValue += wearable.WearableComponent.ArmorValue;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (hitArmor)
|
||||
{
|
||||
totalArmorValue = Math.Max(totalArmorValue, 0.0f);
|
||||
|
||||
damageSoundType = DamageSoundType.LimbArmor;
|
||||
amount = Math.Max(0.0f, amount - totalArmorValue);
|
||||
bleedingAmount = Math.Max(0.0f, bleedingAmount - totalArmorValue);
|
||||
}
|
||||
|
||||
if (playSound)
|
||||
{
|
||||
SoundPlayer.PlayDamageSound(damageSoundType, amount, position);
|
||||
}
|
||||
|
||||
//Bleeding += bleedingAmount;
|
||||
//Damage += amount;
|
||||
|
||||
float bloodAmount = hitArmor || bleedingAmount<=0.0f ? 0 : (int)Math.Min((int)(amount * 2.0f), 20);
|
||||
|
||||
for (int i = 0; i < bloodAmount; i++)
|
||||
{
|
||||
Vector2 particleVel = SimPosition - position;
|
||||
if (particleVel != Vector2.Zero) particleVel = Vector2.Normalize(particleVel);
|
||||
|
||||
GameMain.ParticleManager.CreateParticle("blood",
|
||||
WorldPosition,
|
||||
particleVel * Rand.Range(100.0f, 300.0f), 0.0f, character.AnimController.CurrentHull);
|
||||
}
|
||||
|
||||
for (int i = 0; i < bloodAmount / 2; i++)
|
||||
{
|
||||
GameMain.ParticleManager.CreateParticle("waterblood", WorldPosition, Vector2.Zero, 0.0f, character.AnimController.CurrentHull);
|
||||
}
|
||||
|
||||
damage += Math.Max(amount,bleedingAmount) / character.MaxHealth * 100.0f;
|
||||
|
||||
|
||||
return new AttackResult(amount, bleedingAmount, hitArmor);
|
||||
}
|
||||
|
||||
public bool SectorHit(Vector2 armorSector, Vector2 simPosition)
|
||||
{
|
||||
if (armorSector == Vector2.Zero) return false;
|
||||
|
||||
float rot = body.Rotation;
|
||||
if (Dir == -1) rot -= MathHelper.Pi;
|
||||
|
||||
Vector2 armorLimits = new Vector2(rot - armorSector.X * Dir, rot - armorSector.Y * Dir);
|
||||
|
||||
float mid = (armorLimits.X + armorLimits.Y) / 2.0f;
|
||||
float angleDiff = MathUtils.GetShortestAngle(MathUtils.VectorToAngle(simPosition - SimPosition), mid);
|
||||
|
||||
return (Math.Abs(angleDiff) < (armorSector.Y - armorSector.X) / 2.0f);
|
||||
}
|
||||
|
||||
public void Update(float deltaTime)
|
||||
{
|
||||
if (LightSource != null)
|
||||
{
|
||||
LightSource.ParentSub = body.Submarine;
|
||||
}
|
||||
|
||||
if (!character.IsDead) damage = Math.Max(0.0f, damage-deltaTime*0.1f);
|
||||
|
||||
if (burnt > 0.0f) Burnt -= deltaTime;
|
||||
|
||||
if (LinearVelocity.X > 500.0f)
|
||||
{
|
||||
//DebugConsole.ThrowError("CHARACTER EXPLODED");
|
||||
body.ResetDynamics();
|
||||
body.SetTransform(character.SimPosition, 0.0f);
|
||||
}
|
||||
|
||||
if (inWater)
|
||||
{
|
||||
body.ApplyWaterForces();
|
||||
}
|
||||
|
||||
if (character.IsDead) return;
|
||||
|
||||
soundTimer -= deltaTime;
|
||||
|
||||
//if (MathUtils.RandomFloat(0.0f, 1000.0f) < Bleeding)
|
||||
//{
|
||||
// Game1.particleManager.CreateParticle(
|
||||
// !inWater ? "blood" : "waterblood",
|
||||
// SimPosition, Vector2.Zero);
|
||||
//}
|
||||
}
|
||||
|
||||
public void ActivateDamagedSprite()
|
||||
{
|
||||
damage = 100.0f;
|
||||
}
|
||||
|
||||
public void UpdateAttack(float deltaTime, Vector2 attackPosition, IDamageable damageTarget)
|
||||
{
|
||||
float dist = ConvertUnits.ToDisplayUnits(Vector2.Distance(SimPosition, attackPosition));
|
||||
|
||||
AttackTimer += deltaTime;
|
||||
|
||||
body.ApplyTorque(Mass * character.AnimController.Dir * attack.Torque);
|
||||
|
||||
if (dist < attack.Range * 0.5f)
|
||||
{
|
||||
if (AttackTimer >= attack.Duration && damageTarget != null)
|
||||
{
|
||||
attack.DoDamage(character, damageTarget, WorldPosition, 1.0f, (soundTimer <= 0.0f));
|
||||
|
||||
soundTimer = Limb.SoundInterval;
|
||||
}
|
||||
}
|
||||
|
||||
Vector2 diff = attackPosition - SimPosition;
|
||||
if (diff.LengthSquared() > 0.00001f)
|
||||
{
|
||||
Vector2 pos = pullJoint == null ? body.SimPosition : pullJoint.WorldAnchorA;
|
||||
body.ApplyLinearImpulse(Mass * attack.Force *
|
||||
Vector2.Normalize(attackPosition - SimPosition), pos);
|
||||
}
|
||||
}
|
||||
|
||||
public void Draw(SpriteBatch spriteBatch)
|
||||
{
|
||||
float brightness = 1.0f - (burnt / 100.0f) * 0.5f;
|
||||
Color color = new Color(brightness, brightness, brightness);
|
||||
|
||||
body.Dir = Dir;
|
||||
|
||||
bool hideLimb = wearingItems.Any(w => w != null && w.HideLimb);
|
||||
|
||||
if (!hideLimb)
|
||||
{
|
||||
body.Draw(spriteBatch, sprite, color, null, scale);
|
||||
}
|
||||
else
|
||||
{
|
||||
body.UpdateDrawPosition();
|
||||
}
|
||||
|
||||
if (LightSource != null)
|
||||
{
|
||||
LightSource.Position = body.DrawPosition;
|
||||
}
|
||||
|
||||
foreach (WearableSprite wearable in wearingItems)
|
||||
{
|
||||
SpriteEffects spriteEffect = (dir == Direction.Right) ? SpriteEffects.None : SpriteEffects.FlipHorizontally;
|
||||
|
||||
Vector2 origin = wearable.Sprite.Origin;
|
||||
if (body.Dir == -1.0f) origin.X = wearable.Sprite.SourceRect.Width - origin.X;
|
||||
|
||||
float depth = sprite.Depth - 0.000001f;
|
||||
|
||||
if (wearable.DepthLimb != LimbType.None)
|
||||
{
|
||||
Limb depthLimb = character.AnimController.GetLimb(wearable.DepthLimb);
|
||||
if (depthLimb != null)
|
||||
{
|
||||
depth = depthLimb.sprite.Depth - 0.000001f;
|
||||
}
|
||||
}
|
||||
|
||||
wearable.Sprite.Draw(spriteBatch,
|
||||
new Vector2(body.DrawPosition.X, -body.DrawPosition.Y),
|
||||
color, origin,
|
||||
-body.DrawRotation,
|
||||
scale, spriteEffect, depth);
|
||||
}
|
||||
|
||||
if (damage > 0.0f && damagedSprite != null && !hideLimb)
|
||||
{
|
||||
SpriteEffects spriteEffect = (dir == Direction.Right) ? SpriteEffects.None : SpriteEffects.FlipHorizontally;
|
||||
|
||||
float depth = sprite.Depth - 0.0000015f;
|
||||
|
||||
damagedSprite.Draw(spriteBatch,
|
||||
new Vector2(body.DrawPosition.X, -body.DrawPosition.Y),
|
||||
color * Math.Min(damage / 50.0f, 1.0f), sprite.Origin,
|
||||
-body.DrawRotation,
|
||||
1.0f, spriteEffect, depth);
|
||||
}
|
||||
|
||||
if (!GameMain.DebugDraw) return;
|
||||
|
||||
if (pullJoint != null)
|
||||
{
|
||||
Vector2 pos = ConvertUnits.ToDisplayUnits(pullJoint.WorldAnchorB);
|
||||
GUI.DrawRectangle(spriteBatch, new Rectangle((int)pos.X, (int)-pos.Y, 5, 5), Color.Red, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void Remove()
|
||||
{
|
||||
if (sprite != null)
|
||||
{
|
||||
sprite.Remove();
|
||||
sprite = null;
|
||||
}
|
||||
|
||||
if (LightSource != null)
|
||||
{
|
||||
LightSource.Remove();
|
||||
}
|
||||
if (damagedSprite != null)
|
||||
{
|
||||
damagedSprite.Remove();
|
||||
damagedSprite = null;
|
||||
}
|
||||
|
||||
if (body != null)
|
||||
{
|
||||
body.Remove();
|
||||
body = null;
|
||||
}
|
||||
|
||||
if (hitSound != null)
|
||||
{
|
||||
hitSound.Remove();
|
||||
hitSound = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,337 @@
|
||||
using Barotrauma.Particles;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class StatusEffect
|
||||
{
|
||||
[Flags]
|
||||
public enum TargetType
|
||||
{
|
||||
This = 1, Parent = 2, Character = 4, Contained = 8, Nearby = 16, UseTarget = 32, Hull = 64
|
||||
}
|
||||
|
||||
private TargetType targetTypes;
|
||||
private HashSet<string> targetNames;
|
||||
|
||||
private List<RelatedItem> requiredItems;
|
||||
|
||||
public string[] propertyNames;
|
||||
private object[] propertyEffects;
|
||||
|
||||
private bool setValue;
|
||||
|
||||
private bool disableDeltaTime;
|
||||
|
||||
private HashSet<string> onContainingNames;
|
||||
|
||||
private readonly float duration;
|
||||
|
||||
private readonly bool useItem;
|
||||
|
||||
public readonly ActionType type;
|
||||
|
||||
private Explosion explosion;
|
||||
|
||||
private List<ParticleEmitterPrefab> particleEmitters;
|
||||
|
||||
public readonly float FireSize;
|
||||
|
||||
private Sound sound;
|
||||
|
||||
public TargetType Targets
|
||||
{
|
||||
get { return targetTypes; }
|
||||
}
|
||||
|
||||
public HashSet<string> TargetNames
|
||||
{
|
||||
get { return targetNames; }
|
||||
}
|
||||
|
||||
public HashSet<string> OnContainingNames
|
||||
{
|
||||
get { return onContainingNames; }
|
||||
}
|
||||
|
||||
public static StatusEffect Load(XElement element)
|
||||
{
|
||||
if (element.Attribute("delay")!=null)
|
||||
{
|
||||
return new DelayedEffect(element);
|
||||
}
|
||||
|
||||
return new StatusEffect(element);
|
||||
}
|
||||
|
||||
protected StatusEffect(XElement element)
|
||||
{
|
||||
requiredItems = new List<RelatedItem>();
|
||||
particleEmitters = new List<ParticleEmitterPrefab>();
|
||||
|
||||
IEnumerable<XAttribute> attributes = element.Attributes();
|
||||
List<XAttribute> propertyAttributes = new List<XAttribute>();
|
||||
|
||||
foreach (XAttribute attribute in attributes)
|
||||
{
|
||||
switch (attribute.Name.ToString())
|
||||
{
|
||||
case "type":
|
||||
try
|
||||
{
|
||||
type = (ActionType)Enum.Parse(typeof(ActionType), attribute.Value, true);
|
||||
}
|
||||
|
||||
catch
|
||||
{
|
||||
string[] split = attribute.Value.Split('=');
|
||||
type = (ActionType)Enum.Parse(typeof(ActionType), split[0], true);
|
||||
|
||||
string[] containingNames = split[1].Split(',');
|
||||
onContainingNames = new HashSet<string>();
|
||||
for (int i = 0; i < containingNames.Length; i++)
|
||||
{
|
||||
onContainingNames.Add(containingNames[i].Trim());
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
case "target":
|
||||
string[] Flags = attribute.Value.Split(',');
|
||||
foreach (string s in Flags)
|
||||
{
|
||||
targetTypes |= (TargetType)Enum.Parse(typeof(TargetType), s, true);
|
||||
}
|
||||
|
||||
break;
|
||||
case "disabledeltatime":
|
||||
disableDeltaTime = ToolBox.GetAttributeBool(attribute, false);
|
||||
break;
|
||||
case "setvalue":
|
||||
setValue = ToolBox.GetAttributeBool(attribute, false);
|
||||
break;
|
||||
case "targetnames":
|
||||
string[] names = attribute.Value.Split(',');
|
||||
targetNames = new HashSet<string>();
|
||||
for (int i=0; i < names.Length; i++ )
|
||||
{
|
||||
targetNames.Add(names[i].Trim());
|
||||
}
|
||||
break;
|
||||
case "sound":
|
||||
sound = Sound.Load(attribute.Value.ToString());
|
||||
break;
|
||||
case "duration":
|
||||
duration = ToolBox.GetAttributeFloat(attribute, 0.0f);
|
||||
break;
|
||||
default:
|
||||
propertyAttributes.Add(attribute);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int count = propertyAttributes.Count;
|
||||
propertyNames = new string[count];
|
||||
propertyEffects = new object[count];
|
||||
|
||||
int n = 0;
|
||||
foreach (XAttribute attribute in propertyAttributes)
|
||||
{
|
||||
propertyNames[n] = attribute.Name.ToString().ToLowerInvariant();
|
||||
propertyEffects[n] = ToolBox.GetAttributeObject(attribute);
|
||||
n++;
|
||||
}
|
||||
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "explosion":
|
||||
explosion = new Explosion(subElement);
|
||||
break;
|
||||
case "fire":
|
||||
FireSize = ToolBox.GetAttributeFloat(subElement,"size",10.0f);
|
||||
break;
|
||||
case "use":
|
||||
case "useitem":
|
||||
useItem = true;
|
||||
break;
|
||||
case "particleemitter":
|
||||
particleEmitters.Add(new ParticleEmitterPrefab(subElement));
|
||||
break;
|
||||
case "requireditem":
|
||||
case "requireditems":
|
||||
RelatedItem newRequiredItem = RelatedItem.Load(subElement);
|
||||
|
||||
if (newRequiredItem == null) continue;
|
||||
|
||||
requiredItems.Add(newRequiredItem);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool HasRequiredItems(Entity entity)
|
||||
{
|
||||
if (requiredItems == null) return true;
|
||||
foreach (RelatedItem requiredItem in requiredItems)
|
||||
{
|
||||
Item item = entity as Item;
|
||||
if (item != null)
|
||||
{
|
||||
if (!requiredItem.CheckRequirements(null, item)) return false;
|
||||
}
|
||||
Character character = entity as Character;
|
||||
if (character != null)
|
||||
{
|
||||
if (!requiredItem.CheckRequirements(character, null)) return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public virtual void Apply(ActionType type, float deltaTime, Entity entity, IPropertyObject target)
|
||||
{
|
||||
if (this.type != type || !HasRequiredItems(entity)) return;
|
||||
|
||||
if (targetNames != null && !targetNames.Contains(target.Name)) return;
|
||||
|
||||
List<IPropertyObject> targets = new List<IPropertyObject>();
|
||||
targets.Add(target);
|
||||
|
||||
Apply(deltaTime, entity, targets);
|
||||
}
|
||||
|
||||
public virtual void Apply(ActionType type, float deltaTime, Entity entity, List<IPropertyObject> targets)
|
||||
{
|
||||
if (this.type != type || !HasRequiredItems(entity)) return;
|
||||
|
||||
Apply(deltaTime, entity, targets);
|
||||
}
|
||||
|
||||
protected void Apply(float deltaTime, Entity entity, List<IPropertyObject> targets)
|
||||
{
|
||||
if (sound != null) sound.Play(1.0f, 1000.0f, entity.WorldPosition);
|
||||
|
||||
if (useItem)
|
||||
{
|
||||
foreach (Item item in targets.FindAll(t => t is Item).Cast<Item>())
|
||||
{
|
||||
item.Use(deltaTime, targets.FirstOrDefault(t => t is Character) as Character);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (IPropertyObject target in targets)
|
||||
{
|
||||
for (int i = 0; i < propertyNames.Length; i++)
|
||||
{
|
||||
ObjectProperty property;
|
||||
|
||||
if (!target.ObjectProperties.TryGetValue(propertyNames[i], out property)) continue;
|
||||
|
||||
if (duration > 0.0f)
|
||||
{
|
||||
CoroutineManager.StartCoroutine(
|
||||
ApplyToPropertyOverDuration(duration, property, propertyEffects[i]), "statuseffect");
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
ApplyToProperty(property, propertyEffects[i], deltaTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (explosion != null) explosion.Explode(entity.WorldPosition);
|
||||
|
||||
|
||||
Hull hull = null;
|
||||
if (entity is Character)
|
||||
{
|
||||
hull = ((Character)entity).AnimController.CurrentHull;
|
||||
}
|
||||
else if (entity is Item)
|
||||
{
|
||||
hull = ((Item)entity).CurrentHull;
|
||||
}
|
||||
|
||||
if (FireSize > 0.0f)
|
||||
{
|
||||
var fire = new FireSource(entity.WorldPosition, hull);
|
||||
|
||||
fire.Size = new Vector2(FireSize, fire.Size.Y);
|
||||
}
|
||||
|
||||
foreach (ParticleEmitterPrefab emitter in particleEmitters)
|
||||
{
|
||||
emitter.Emit(entity.WorldPosition, hull);
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<object> ApplyToPropertyOverDuration(float duration, ObjectProperty property, object value)
|
||||
{
|
||||
float timer = duration;
|
||||
while (timer > 0.0f)
|
||||
{
|
||||
ApplyToProperty(property, value, CoroutineManager.UnscaledDeltaTime);
|
||||
|
||||
timer -= CoroutineManager.DeltaTime;
|
||||
|
||||
yield return CoroutineStatus.Running;
|
||||
}
|
||||
|
||||
yield return CoroutineStatus.Success;
|
||||
}
|
||||
|
||||
private void ApplyToProperty(ObjectProperty property, object value, float deltaTime)
|
||||
{
|
||||
if (disableDeltaTime || setValue) deltaTime = 1.0f;
|
||||
|
||||
Type type = value.GetType();
|
||||
if (type == typeof(float) ||
|
||||
(type == typeof(int) && property.GetValue() is float))
|
||||
{
|
||||
float floatValue = Convert.ToSingle(value) * deltaTime;
|
||||
|
||||
if (!setValue) floatValue += (float)property.GetValue();
|
||||
property.TrySetValue(floatValue);
|
||||
}
|
||||
else if (type == typeof(int) && value is int)
|
||||
{
|
||||
int intValue = (int)((int)value * deltaTime);
|
||||
if (!setValue) intValue += (int)property.GetValue();
|
||||
property.TrySetValue(intValue);
|
||||
}
|
||||
else if (type == typeof(bool) && value is bool)
|
||||
{
|
||||
property.TrySetValue((bool)value);
|
||||
}
|
||||
else if (type == typeof(string))
|
||||
{
|
||||
property.TrySetValue((string)value);
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.ThrowError("Couldn't apply value "+value.ToString()+" ("+type+") to property \""+property.Name+"\" ("+property.GetValue().GetType()+")! "
|
||||
+"Make sure the type of the value set in the config files matches the type of the property.");
|
||||
}
|
||||
}
|
||||
|
||||
public static void UpdateAll(float deltaTime)
|
||||
{
|
||||
for (int i = DelayedEffect.List.Count-1; i>= 0; i--)
|
||||
{
|
||||
DelayedEffect.List[i].Update(deltaTime);
|
||||
}
|
||||
}
|
||||
|
||||
public static void StopAll()
|
||||
{
|
||||
CoroutineManager.StopCoroutines("statuseffect");
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user