Merge branch 'master' into ai-overhaul
This commit is contained in:
@@ -0,0 +1,265 @@
|
||||
using System;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
public class Camera
|
||||
{
|
||||
public static bool FollowSub = true;
|
||||
|
||||
const float DefaultZoom = 1.0f;
|
||||
const float ZoomSmoothness = 8.0f;
|
||||
const float MoveSmoothness = 8.0f;
|
||||
|
||||
private float zoom;
|
||||
|
||||
private float offsetAmount;
|
||||
|
||||
private Matrix transform, shaderTransform, viewMatrix;
|
||||
private Vector2 position;
|
||||
private float rotation;
|
||||
|
||||
private Vector2 prevPosition;
|
||||
private float prevZoom;
|
||||
|
||||
public float Shake;
|
||||
private Vector2 shakePosition;
|
||||
private Vector2 shakeTargetPosition;
|
||||
|
||||
//the area of the world inside the camera view
|
||||
private Rectangle worldView;
|
||||
|
||||
private Point resolution;
|
||||
|
||||
private Vector2 targetPos;
|
||||
|
||||
public float Zoom
|
||||
{
|
||||
get { return zoom; }
|
||||
set
|
||||
{
|
||||
zoom = Math.Max(value, GameMain.DebugDraw ? 0.01f : 0.1f);
|
||||
|
||||
Vector2 center = WorldViewCenter;
|
||||
float newWidth = resolution.X / zoom;
|
||||
float newHeight = resolution.Y / zoom;
|
||||
|
||||
worldView = new Rectangle(
|
||||
(int)(center.X - newWidth / 2.0f),
|
||||
(int)(center.Y + newHeight / 2.0f),
|
||||
(int)newWidth,
|
||||
(int)newHeight);
|
||||
|
||||
//UpdateTransform();
|
||||
}
|
||||
}
|
||||
|
||||
public float Rotation
|
||||
{
|
||||
get { return rotation; }
|
||||
set { rotation = value; }
|
||||
}
|
||||
|
||||
public float OffsetAmount
|
||||
{
|
||||
get { return offsetAmount; }
|
||||
set { offsetAmount = value; }
|
||||
}
|
||||
|
||||
public Point Resolution
|
||||
{
|
||||
get { return resolution; }
|
||||
}
|
||||
|
||||
public Rectangle WorldView
|
||||
{
|
||||
get { return worldView; }
|
||||
}
|
||||
|
||||
public Vector2 WorldViewCenter
|
||||
{
|
||||
get
|
||||
{
|
||||
return new Vector2(
|
||||
worldView.X + worldView.Width / 2.0f,
|
||||
worldView.Y - worldView.Height / 2.0f);
|
||||
}
|
||||
}
|
||||
|
||||
public Matrix Transform
|
||||
{
|
||||
get { return transform; }
|
||||
}
|
||||
|
||||
public Matrix ShaderTransform
|
||||
{
|
||||
get { return shaderTransform; }
|
||||
}
|
||||
|
||||
public Camera()
|
||||
{
|
||||
zoom = 1.0f;
|
||||
rotation = 0.0f;
|
||||
position = Vector2.Zero;
|
||||
|
||||
worldView = new Rectangle(0,0,
|
||||
GameMain.GraphicsWidth,
|
||||
GameMain.GraphicsHeight);
|
||||
|
||||
resolution = new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight);
|
||||
|
||||
viewMatrix =
|
||||
Matrix.CreateTranslation(new Vector3(GameMain.GraphicsWidth / 2.0f, GameMain.GraphicsHeight / 2.0f, 0));
|
||||
|
||||
UpdateTransform();
|
||||
}
|
||||
|
||||
public Vector2 TargetPos
|
||||
{
|
||||
get { return targetPos; }
|
||||
set { targetPos = value; }
|
||||
}
|
||||
|
||||
// Auxiliary function to move the camera
|
||||
public void Translate(Vector2 amount)
|
||||
{
|
||||
position += amount;
|
||||
|
||||
}
|
||||
|
||||
public void UpdateTransform(bool interpolate = true, bool clampPos = false)
|
||||
{
|
||||
Vector2 interpolatedPosition = interpolate ? Timing.Interpolate(prevPosition, position) : position;
|
||||
|
||||
float interpolatedZoom = interpolate ? Timing.Interpolate(prevZoom, zoom) : zoom;
|
||||
|
||||
worldView.X = (int)(interpolatedPosition.X - worldView.Width / 2.0);
|
||||
worldView.Y = (int)(interpolatedPosition.Y + worldView.Height / 2.0);
|
||||
|
||||
if (Level.Loaded != null && clampPos)
|
||||
{
|
||||
position.Y -= Math.Max(worldView.Y - Level.Loaded.Size.Y, 0.0f);
|
||||
interpolatedPosition.Y -= Math.Max(worldView.Y - Level.Loaded.Size.Y, 0.0f);
|
||||
|
||||
worldView.Y = Math.Min((int)Level.Loaded.Size.Y, worldView.Y);
|
||||
}
|
||||
|
||||
transform = Matrix.CreateTranslation(
|
||||
new Vector3(-interpolatedPosition.X, interpolatedPosition.Y, 0)) *
|
||||
Matrix.CreateScale(new Vector3(interpolatedZoom, interpolatedZoom, 1)) *
|
||||
viewMatrix;
|
||||
|
||||
shaderTransform = Matrix.CreateTranslation(
|
||||
new Vector3(
|
||||
-interpolatedPosition.X - resolution.X / interpolatedZoom / 2.0f,
|
||||
-interpolatedPosition.Y - resolution.Y / interpolatedZoom / 2.0f, 0)) *
|
||||
Matrix.CreateScale(new Vector3(interpolatedZoom, interpolatedZoom, 1)) *
|
||||
viewMatrix;
|
||||
|
||||
Sound.CameraPos = new Vector3(WorldViewCenter.X, WorldViewCenter.Y, 0.0f);
|
||||
|
||||
if (!interpolate)
|
||||
{
|
||||
prevPosition = position;
|
||||
prevZoom = zoom;
|
||||
}
|
||||
}
|
||||
|
||||
public void MoveCamera(float deltaTime, bool allowMove = true, bool allowZoom = true)
|
||||
{
|
||||
prevPosition = position;
|
||||
prevZoom = zoom;
|
||||
|
||||
float moveSpeed = 20.0f/zoom;
|
||||
|
||||
Vector2 moveCam = Vector2.Zero;
|
||||
if (targetPos == Vector2.Zero)
|
||||
{
|
||||
if (allowMove && GUIComponent.KeyboardDispatcher.Subscriber == null)
|
||||
{
|
||||
if (PlayerInput.KeyDown(Keys.LeftShift)) moveSpeed *= 2.0f;
|
||||
if (PlayerInput.KeyDown(Keys.LeftControl)) moveSpeed *= 0.5f;
|
||||
|
||||
if (GameMain.Config.KeyBind(InputType.Left).IsDown()) moveCam.X -= moveSpeed;
|
||||
if (GameMain.Config.KeyBind(InputType.Right).IsDown()) moveCam.X += moveSpeed;
|
||||
if (GameMain.Config.KeyBind(InputType.Down).IsDown()) moveCam.Y -= moveSpeed;
|
||||
if (GameMain.Config.KeyBind(InputType.Up).IsDown()) moveCam.Y += moveSpeed;
|
||||
}
|
||||
|
||||
if (Screen.Selected == GameMain.GameScreen && FollowSub)
|
||||
{
|
||||
var closestSub = Submarine.FindClosest(WorldViewCenter);
|
||||
if (closestSub != null)
|
||||
{
|
||||
moveCam += FarseerPhysics.ConvertUnits.ToDisplayUnits(closestSub.Velocity * deltaTime);
|
||||
}
|
||||
}
|
||||
|
||||
moveCam = moveCam * deltaTime * 60.0f;
|
||||
|
||||
if (allowZoom)
|
||||
{
|
||||
Vector2 mouseInWorld = ScreenToWorld(PlayerInput.MousePosition);
|
||||
Vector2 diffViewCenter;
|
||||
diffViewCenter = ((mouseInWorld - Position) * Zoom);
|
||||
Zoom = MathHelper.Clamp(zoom + (PlayerInput.ScrollWheelSpeed / 1000.0f) * zoom, GameMain.DebugDraw ? 0.01f : 0.1f, 2.0f);
|
||||
if (!PlayerInput.KeyDown(Keys.F)) Position = mouseInWorld - (diffViewCenter / Zoom);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Vector2 mousePos = PlayerInput.MousePosition;
|
||||
|
||||
Vector2 offset = mousePos - new Vector2(resolution.X / 2.0f, resolution.Y / 2.0f);
|
||||
|
||||
offset.X = offset.X / (resolution.X * 0.4f);
|
||||
offset.Y = -offset.Y / (resolution.Y * 0.3f);
|
||||
|
||||
if (offset.Length() > 1.0f) offset.Normalize();
|
||||
|
||||
offset = offset * offsetAmount;
|
||||
|
||||
float newZoom = Math.Min(DefaultZoom - Math.Min(offset.Length() / resolution.Y, 1.0f),1.0f);
|
||||
Zoom += (newZoom - zoom) / ZoomSmoothness;
|
||||
|
||||
Vector2 diff = (targetPos + offset) - position;
|
||||
|
||||
moveCam = diff / MoveSmoothness;
|
||||
}
|
||||
|
||||
shakeTargetPosition = Rand.Vector(Shake);
|
||||
shakePosition = Vector2.Lerp(shakePosition, shakeTargetPosition, 0.5f);
|
||||
Shake = MathHelper.Lerp(Shake, 0.0f, deltaTime * 2.0f);
|
||||
|
||||
Translate(moveCam + shakePosition);
|
||||
}
|
||||
|
||||
public Vector2 Position
|
||||
{
|
||||
get { return position; }
|
||||
set
|
||||
{
|
||||
if (!MathUtils.IsValid(value))
|
||||
{
|
||||
return;
|
||||
}
|
||||
position = value;
|
||||
}
|
||||
}
|
||||
|
||||
public Vector2 ScreenToWorld(Vector2 coords)
|
||||
{
|
||||
Vector2 worldCoords = Vector2.Transform(coords, Matrix.Invert(transform));
|
||||
return new Vector2(worldCoords.X, -worldCoords.Y);
|
||||
}
|
||||
|
||||
public Vector2 WorldToScreen(Vector2 coords)
|
||||
{
|
||||
coords.Y = -coords.Y;
|
||||
//Vector2 screenCoords = Vector2.Transform(coords, transform);
|
||||
return Vector2.Transform(coords, transform);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,617 @@
|
||||
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 float combatStrength;
|
||||
|
||||
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, 0.0f, "attackrooms", "attackpriorityrooms") / 100.0f;
|
||||
attackHumans = ToolBox.GetAttributeFloat(aiElement, 0.0f, "attackhumans", "attackpriorityhumans") / 100.0f;
|
||||
attackWeaker = ToolBox.GetAttributeFloat(aiElement, 0.0f, "attackweaker", "attackpriorityweaker") / 100.0f;
|
||||
attackStronger = ToolBox.GetAttributeFloat(aiElement, 0.0f, "attackstronger", "attackprioritystronger") / 100.0f;
|
||||
|
||||
combatStrength = ToolBox.GetAttributeFloat(aiElement, "combatstrength", 1.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;
|
||||
case AiState.Escape:
|
||||
UpdateEscape(deltaTime);
|
||||
break;
|
||||
default:
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
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 UpdateEscape(float deltaTime)
|
||||
{
|
||||
SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(SimPosition - selectedAiTarget.SimPosition));
|
||||
SteeringManager.SteeringWander(1.0f);
|
||||
SteeringManager.SteeringAvoid(deltaTime, 2f);
|
||||
}
|
||||
|
||||
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 (targetCharacter.SpeciesName == "human")
|
||||
{
|
||||
if (attackHumans == 0.0f) continue;
|
||||
valueModifier = attackHumans;
|
||||
}
|
||||
else
|
||||
{
|
||||
EnemyAIController enemy = targetCharacter.AIController as EnemyAIController;
|
||||
if (enemy != null)
|
||||
{
|
||||
if (enemy.combatStrength > combatStrength)
|
||||
{
|
||||
valueModifier = attackStronger;
|
||||
}
|
||||
else if (enemy.combatStrength < combatStrength)
|
||||
{
|
||||
valueModifier = attackWeaker;
|
||||
}
|
||||
else
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
if (valueModifier == 0.0f) continue;
|
||||
|
||||
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.5f;
|
||||
|
||||
//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;
|
||||
|
||||
/* float targetStrength = 0.0f;
|
||||
if (targetDamageable != null)
|
||||
{
|
||||
targetStrength = targetDamageable.Health;
|
||||
}
|
||||
else if (closestStructure!=null)
|
||||
{
|
||||
targetStrength = ((IDamageable)closestStructure).Health;
|
||||
}
|
||||
else
|
||||
{
|
||||
targetStrength = 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 || IsRemotePlayer) 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,898 @@
|
||||
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; }
|
||||
}
|
||||
|
||||
public void ResetNetState()
|
||||
{
|
||||
memInput.Clear();
|
||||
memState.Clear();
|
||||
memLocalState.Clear();
|
||||
|
||||
LastNetworkUpdateID = 0;
|
||||
LastProcessedID = 0;
|
||||
}
|
||||
|
||||
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) || IsRemotePlayer))
|
||||
{
|
||||
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.WriteRangedInteger(0, 2, 0);
|
||||
inventory.ClientWrite(msg, extraData);
|
||||
break;
|
||||
case NetEntityEvent.Type.Control:
|
||||
msg.WriteRangedInteger(0, 2, 1);
|
||||
Client owner = ((Client)extraData[1]);
|
||||
msg.Write(owner == null ? (byte)0 : owner.ID);
|
||||
break;
|
||||
case NetEntityEvent.Type.Status:
|
||||
msg.WriteRangedInteger(0, 2, 2);
|
||||
WriteStatus(msg);
|
||||
break;
|
||||
}
|
||||
msg.WritePadBits();
|
||||
}
|
||||
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:
|
||||
|
||||
int eventType = msg.ReadRangedInteger(0, 2);
|
||||
switch (eventType)
|
||||
{
|
||||
case 0:
|
||||
inventory.ClientRead(type, msg, sendingTime);
|
||||
break;
|
||||
case 1:
|
||||
byte ownerID = msg.ReadByte();
|
||||
ResetNetState();
|
||||
if (ownerID == GameMain.Client.ID)
|
||||
{
|
||||
if (controlled != null)
|
||||
{
|
||||
LastNetworkUpdateID = controlled.LastNetworkUpdateID;
|
||||
}
|
||||
|
||||
controlled = this;
|
||||
IsRemotePlayer = false;
|
||||
GameMain.Client.Character = this;
|
||||
}
|
||||
else if (controlled == this)
|
||||
{
|
||||
controlled = null;
|
||||
IsRemotePlayer = ownerID > 0;
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
ReadStatus(msg);
|
||||
break;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
msg.WriteRangedSingle(MathHelper.Clamp(Stun, 0.0f, MaxStun), 0.0f, MaxStun, 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.Write(skill.Name);
|
||||
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;
|
||||
Dictionary<string, int> skillLevels = new Dictionary<string, int>();
|
||||
if (!string.IsNullOrEmpty(jobName))
|
||||
{
|
||||
jobPrefab = JobPrefab.List.Find(jp => jp.Name == jobName);
|
||||
int skillCount = inc.ReadByte();
|
||||
for (int i = 0; i < skillCount; i++)
|
||||
{
|
||||
string skillName = inc.ReadString();
|
||||
int skillLevel = inc.ReadRangedInteger(0, 100);
|
||||
|
||||
skillLevels.Add(skillName, skillLevel);
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
foreach (KeyValuePair<string, int> skill in skillLevels)
|
||||
{
|
||||
Skill matchingSkill = ch.Job.Skills.Find(s => s.Name == skill.Key);
|
||||
if (matchingSkill == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Skill \""+skill.Key+"\" not found in character \""+newName+"\"");
|
||||
continue;
|
||||
}
|
||||
matchingSkill.Level = skill.Value;
|
||||
}
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,241 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
public enum ContentType
|
||||
{
|
||||
None,
|
||||
Jobs,
|
||||
Item,
|
||||
Character,
|
||||
Structure,
|
||||
Executable,
|
||||
LocationTypes,
|
||||
LevelGenerationParameters,
|
||||
RandomEvents,
|
||||
Missions,
|
||||
BackgroundCreaturePrefabs, BackgroundSpritePrefabs
|
||||
}
|
||||
|
||||
public class ContentPackage
|
||||
{
|
||||
|
||||
public static string Folder = "Data/ContentPackages/";
|
||||
|
||||
public static List<ContentPackage> list = new List<ContentPackage>();
|
||||
|
||||
|
||||
string name;
|
||||
|
||||
public string Name
|
||||
{
|
||||
get { return name; }
|
||||
}
|
||||
|
||||
public string Path
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
private Md5Hash md5Hash;
|
||||
|
||||
public Md5Hash MD5hash
|
||||
{
|
||||
get
|
||||
{
|
||||
if (md5Hash == null) CalculateHash();
|
||||
return md5Hash;
|
||||
}
|
||||
}
|
||||
|
||||
public List<ContentFile> files;
|
||||
|
||||
private ContentPackage()
|
||||
{
|
||||
files = new List<ContentFile>();
|
||||
}
|
||||
|
||||
public ContentPackage(string filePath)
|
||||
: this()
|
||||
{
|
||||
XDocument doc = ToolBox.TryLoadXml(filePath);
|
||||
|
||||
Path = filePath;
|
||||
|
||||
if (doc==null)
|
||||
{
|
||||
DebugConsole.ThrowError("Couldn't load content package \""+filePath+"\"!");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
name = ToolBox.GetAttributeString(doc.Root, "name", "");
|
||||
|
||||
foreach (XElement subElement in doc.Root.Elements())
|
||||
{
|
||||
ContentType type = (ContentType)Enum.Parse(typeof(ContentType), subElement.Name.ToString(), true);
|
||||
files.Add(new ContentFile(ToolBox.GetAttributeString(subElement, "file", ""), type));
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return name;
|
||||
}
|
||||
|
||||
public static ContentPackage CreatePackage(string name)
|
||||
{
|
||||
ContentPackage newPackage = new ContentPackage("Content/Data/"+name);
|
||||
newPackage.name = name;
|
||||
newPackage.Path = Folder + name;
|
||||
list.Add(newPackage);
|
||||
|
||||
return newPackage;
|
||||
}
|
||||
|
||||
public ContentFile AddFile(string path, ContentType type)
|
||||
{
|
||||
if (files.Find(file => file.path == path && file.type == type) != null) return null;
|
||||
|
||||
ContentFile cf = new ContentFile(path, type);
|
||||
files.Add(cf);
|
||||
|
||||
return cf;
|
||||
}
|
||||
|
||||
public void RemoveFile(ContentFile file)
|
||||
{
|
||||
files.Remove(file);
|
||||
}
|
||||
|
||||
public void Save(string filePath)
|
||||
{
|
||||
XDocument doc = new XDocument();
|
||||
doc.Add(new XElement("contentpackage",
|
||||
new XAttribute("name", name),
|
||||
new XAttribute("path", Path)));
|
||||
|
||||
//doc.Root.Add(
|
||||
// new XElement("jobs", new XAttribute("file", JobFile)),
|
||||
// new XElement("structures", new XAttribute("file", StructureFile)));
|
||||
|
||||
foreach (ContentFile file in files)
|
||||
{
|
||||
doc.Root.Add(new XElement(file.type.ToString(), new XAttribute("file", file.path)));
|
||||
}
|
||||
|
||||
//foreach (string itemFile in itemFiles)
|
||||
//{
|
||||
// doc.Root.Add(new XElement("item", new XAttribute("file", itemFile)));
|
||||
//}
|
||||
doc.Save(System.IO.Path.Combine(filePath, name+".xml"));
|
||||
}
|
||||
|
||||
private void CalculateHash()
|
||||
{
|
||||
List<byte[]> hashes = new List<byte[]>();
|
||||
|
||||
//foreach (ContentFile file in files)
|
||||
//{
|
||||
// if (file.path.EndsWith(".xml", true, System.Globalization.CultureInfo.InvariantCulture))
|
||||
// {
|
||||
// XDocument doc = ToolBox.TryLoadXml(file.path);
|
||||
// sb.Append(doc.ToString());
|
||||
// }
|
||||
//}
|
||||
var md5 = MD5.Create();
|
||||
foreach (ContentFile file in files)
|
||||
{
|
||||
if (file.type == ContentType.Executable) continue;
|
||||
|
||||
try
|
||||
{
|
||||
using (var stream = File.OpenRead(file.path))
|
||||
{
|
||||
hashes.Add(md5.ComputeHash(stream));
|
||||
}
|
||||
}
|
||||
|
||||
catch (Exception e)
|
||||
{
|
||||
DebugConsole.ThrowError("Error while calculating content package hash: ", e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//string str = sb.ToString();
|
||||
byte[] bytes = new byte[hashes.Count*16];
|
||||
for (int i = 0; i < hashes.Count; i++ )
|
||||
{
|
||||
hashes[i].CopyTo(bytes, i*16);
|
||||
}
|
||||
//System.Buffer.BlockCopy(str.ToCharArray(), 0, bytes, 0, bytes.Length);
|
||||
|
||||
md5Hash = new Md5Hash(bytes);
|
||||
}
|
||||
|
||||
public List<string> GetFilesOfType(ContentType type)
|
||||
{
|
||||
List<ContentFile> contentFiles = files.FindAll(f => f.type == type);
|
||||
|
||||
List<string> filePaths = new List<string>();
|
||||
foreach (ContentFile contentFile in contentFiles)
|
||||
{
|
||||
filePaths.Add(contentFile.path);
|
||||
}
|
||||
return filePaths;
|
||||
}
|
||||
|
||||
public static void LoadAll(string folder)
|
||||
{
|
||||
if (!Directory.Exists(folder))
|
||||
{
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(folder);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
string[] files = Directory.GetFiles(folder, "*.xml");
|
||||
|
||||
list.Clear();
|
||||
|
||||
foreach (string filePath in files)
|
||||
{
|
||||
ContentPackage package = new ContentPackage(filePath);
|
||||
list.Add(package);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class ContentFile
|
||||
{
|
||||
public string path;
|
||||
public ContentType type;
|
||||
|
||||
public ContentFile(string path, ContentType type)
|
||||
{
|
||||
Directory.GetCurrentDirectory();
|
||||
//Path.get
|
||||
this.path = path;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
enum CoroutineStatus
|
||||
{
|
||||
Running, Success, Failure
|
||||
}
|
||||
|
||||
class CoroutineHandle
|
||||
{
|
||||
public readonly IEnumerator<object> Coroutine;
|
||||
public readonly string Name;
|
||||
|
||||
public CoroutineHandle(IEnumerator<object> coroutine, string name = "")
|
||||
{
|
||||
Coroutine = coroutine;
|
||||
Name = string.IsNullOrWhiteSpace(name) ? coroutine.ToString() : name;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Keeps track of all running coroutines, and runs them till the end.
|
||||
static class CoroutineManager
|
||||
{
|
||||
static readonly List<CoroutineHandle> Coroutines = new List<CoroutineHandle>();
|
||||
|
||||
public static float UnscaledDeltaTime, DeltaTime;
|
||||
|
||||
public static CoroutineHandle StartCoroutine(IEnumerable<object> func, string name = "")
|
||||
{
|
||||
var handle = new CoroutineHandle(func.GetEnumerator(), name);
|
||||
Coroutines.Add(handle);
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
public static bool IsCoroutineRunning(string name)
|
||||
{
|
||||
return Coroutines.Any(c => c.Name == name);
|
||||
}
|
||||
|
||||
public static bool IsCoroutineRunning(CoroutineHandle handle)
|
||||
{
|
||||
return Coroutines.Contains(handle);
|
||||
}
|
||||
|
||||
public static void StopCoroutines(string name)
|
||||
{
|
||||
Coroutines.RemoveAll(c => c.Name == name);
|
||||
}
|
||||
|
||||
public static void StopCoroutines(CoroutineHandle handle)
|
||||
{
|
||||
Coroutines.RemoveAll(c => c == handle);
|
||||
}
|
||||
private static bool IsDone(CoroutineHandle handle)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (handle.Coroutine.Current != null)
|
||||
{
|
||||
WaitForSeconds wfs = handle.Coroutine.Current as WaitForSeconds;
|
||||
if (wfs != null)
|
||||
{
|
||||
if (!wfs.CheckFinished(UnscaledDeltaTime)) return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
switch ((CoroutineStatus)handle.Coroutine.Current)
|
||||
{
|
||||
case CoroutineStatus.Success:
|
||||
return true;
|
||||
|
||||
case CoroutineStatus.Failure:
|
||||
DebugConsole.ThrowError("Coroutine \"" + handle.Name + "\" has failed");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handle.Coroutine.MoveNext();
|
||||
return false;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
DebugConsole.ThrowError("Coroutine " + handle.Name + " threw an exception: " + e.Message + "\n" + e.StackTrace.ToString());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// Updating just means stepping through all the coroutines
|
||||
public static void Update(float unscaledDeltaTime, float deltaTime)
|
||||
{
|
||||
UnscaledDeltaTime = unscaledDeltaTime;
|
||||
DeltaTime = deltaTime;
|
||||
|
||||
foreach (var x in Coroutines.ToList())
|
||||
if(IsDone(x))
|
||||
Coroutines.Remove(x);
|
||||
}
|
||||
}
|
||||
|
||||
class WaitForSeconds
|
||||
{
|
||||
float timer;
|
||||
|
||||
public WaitForSeconds(float time)
|
||||
{
|
||||
timer = time;
|
||||
}
|
||||
|
||||
public bool CheckFinished(float deltaTime)
|
||||
{
|
||||
timer -= deltaTime;
|
||||
return timer<=0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,232 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
|
||||
namespace EventInput
|
||||
{
|
||||
#if WINDOWS
|
||||
public class KeyboardLayout
|
||||
{
|
||||
const uint KLF_ACTIVATE = 1; //activate the layout
|
||||
const int KL_NAMELENGTH = 9; // length of the keyboard buffer
|
||||
const string LANG_EN_US = "00000409";
|
||||
const string LANG_HE_IL = "0001101A";
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern long LoadKeyboardLayout(
|
||||
string pwszKLID, // input locale identifier
|
||||
uint Flags // input locale identifier options
|
||||
);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern long GetKeyboardLayoutName(
|
||||
StringBuilder pwszKLID //[out] string that receives the name of the locale identifier
|
||||
);
|
||||
|
||||
public static string getName()
|
||||
{
|
||||
StringBuilder name = new StringBuilder(KL_NAMELENGTH);
|
||||
GetKeyboardLayoutName(name);
|
||||
return name.ToString();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
public class CharacterEventArgs : EventArgs
|
||||
{
|
||||
private readonly char character;
|
||||
private readonly int lParam;
|
||||
|
||||
public CharacterEventArgs(char character, int lParam)
|
||||
{
|
||||
this.character = character;
|
||||
this.lParam = lParam;
|
||||
}
|
||||
|
||||
public char Character
|
||||
{
|
||||
get { return character; }
|
||||
}
|
||||
|
||||
public int Param
|
||||
{
|
||||
get { return lParam; }
|
||||
}
|
||||
|
||||
public int RepeatCount
|
||||
{
|
||||
get { return lParam & 0xffff; }
|
||||
}
|
||||
|
||||
public bool ExtendedKey
|
||||
{
|
||||
get { return (lParam & (1 << 24)) > 0; }
|
||||
}
|
||||
|
||||
public bool AltPressed
|
||||
{
|
||||
get { return (lParam & (1 << 29)) > 0; }
|
||||
}
|
||||
|
||||
public bool PreviousState
|
||||
{
|
||||
get { return (lParam & (1 << 30)) > 0; }
|
||||
}
|
||||
|
||||
public bool TransitionState
|
||||
{
|
||||
get { return (lParam & (1 << 31)) > 0; }
|
||||
}
|
||||
}
|
||||
|
||||
public class KeyEventArgs : EventArgs
|
||||
{
|
||||
private Keys keyCode;
|
||||
|
||||
public KeyEventArgs(Keys keyCode)
|
||||
{
|
||||
this.keyCode = keyCode;
|
||||
}
|
||||
|
||||
public Keys KeyCode
|
||||
{
|
||||
get { return keyCode; }
|
||||
}
|
||||
}
|
||||
|
||||
public delegate void CharEnteredHandler(object sender, CharacterEventArgs e);
|
||||
public delegate void KeyEventHandler(object sender, KeyEventArgs e);
|
||||
|
||||
public static class EventInput
|
||||
{
|
||||
/// <summary>
|
||||
/// Event raised when a Character has been entered.
|
||||
/// </summary>
|
||||
public static event CharEnteredHandler CharEntered;
|
||||
|
||||
/// <summary>
|
||||
/// Event raised when a key has been pressed down. May fire multiple times due to keyboard repeat.
|
||||
/// </summary>
|
||||
public static event KeyEventHandler KeyDown;
|
||||
|
||||
/// <summary>
|
||||
/// Event raised when a key has been released.
|
||||
/// </summary>
|
||||
public static event KeyEventHandler KeyUp;
|
||||
|
||||
static bool initialized;
|
||||
|
||||
#if WINDOWS
|
||||
delegate IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);
|
||||
|
||||
|
||||
static IntPtr prevWndProc;
|
||||
static WndProc hookProcDelegate;
|
||||
static IntPtr hIMC;
|
||||
|
||||
//various Win32 constants that we need
|
||||
const int GWL_WNDPROC = -4;
|
||||
const int WM_KEYDOWN = 0x100;
|
||||
const int WM_KEYUP = 0x101;
|
||||
const int WM_CHAR = 0x102;
|
||||
const int WM_IME_SETCONTEXT = 0x0281;
|
||||
const int WM_INPUTLANGCHANGE = 0x51;
|
||||
const int WM_GETDLGCODE = 0x87;
|
||||
const int WM_IME_COMPOSITION = 0x10f;
|
||||
const int DLGC_WANTALLKEYS = 4;
|
||||
|
||||
//Win32 functions that we're using
|
||||
[DllImport("Imm32.dll", CharSet = CharSet.Unicode)]
|
||||
static extern IntPtr ImmGetContext(IntPtr hWnd);
|
||||
|
||||
[DllImport("Imm32.dll", CharSet = CharSet.Unicode)]
|
||||
static extern IntPtr ImmAssociateContext(IntPtr hWnd, IntPtr hIMC);
|
||||
|
||||
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
|
||||
static extern IntPtr CallWindowProc(IntPtr lpPrevWndFunc, IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
|
||||
|
||||
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
|
||||
static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Initialize the TextInput with the given GameWindow.
|
||||
/// </summary>
|
||||
/// <param name="window">The XNA window to which text input should be linked.</param>
|
||||
public static void Initialize(GameWindow window)
|
||||
{
|
||||
if (initialized)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
#if WINDOWS
|
||||
hookProcDelegate = HookProc;
|
||||
|
||||
prevWndProc = (IntPtr)SetWindowLong(window.Handle, GWL_WNDPROC,
|
||||
(int)Marshal.GetFunctionPointerForDelegate(hookProcDelegate));
|
||||
|
||||
hIMC = ImmGetContext(window.Handle);
|
||||
#else
|
||||
window.TextInput += ReceiveInput;
|
||||
#endif
|
||||
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
private static void ReceiveInput(object sender, TextInputEventArgs e)
|
||||
{
|
||||
OnCharEntered(e.Character);
|
||||
}
|
||||
|
||||
public static void OnCharEntered(char character)
|
||||
{
|
||||
if (CharEntered != null) CharEntered(null, new CharacterEventArgs(character, 0));
|
||||
}
|
||||
#if WINDOWS
|
||||
static IntPtr HookProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam)
|
||||
{
|
||||
IntPtr returnCode = CallWindowProc(prevWndProc, hWnd, msg, wParam, lParam);
|
||||
|
||||
switch (msg)
|
||||
{
|
||||
case WM_GETDLGCODE:
|
||||
returnCode = (IntPtr)(returnCode.ToInt32() | DLGC_WANTALLKEYS);
|
||||
break;
|
||||
|
||||
case WM_KEYDOWN:
|
||||
if (KeyDown != null)
|
||||
KeyDown(null, new KeyEventArgs((Keys)wParam));
|
||||
|
||||
break;
|
||||
|
||||
case WM_KEYUP:
|
||||
if (KeyUp != null)
|
||||
KeyUp(null, new KeyEventArgs((Keys)wParam));
|
||||
break;
|
||||
|
||||
case WM_CHAR:
|
||||
if (CharEntered != null)
|
||||
CharEntered(null, new CharacterEventArgs((char)wParam, lParam.ToInt32()));
|
||||
break;
|
||||
|
||||
case WM_IME_SETCONTEXT:
|
||||
if (wParam.ToInt32() == 1)
|
||||
ImmAssociateContext(hWnd, hIMC);
|
||||
break;
|
||||
|
||||
case WM_INPUTLANGCHANGE:
|
||||
ImmAssociateContext(hWnd, hIMC);
|
||||
returnCode = (IntPtr)1;
|
||||
break;
|
||||
}
|
||||
|
||||
return returnCode;
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
#if WINDOWS
|
||||
using System.Windows;
|
||||
#endif
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
|
||||
namespace EventInput
|
||||
{
|
||||
public interface IKeyboardSubscriber
|
||||
{
|
||||
void ReceiveTextInput(char inputChar);
|
||||
void ReceiveTextInput(string text);
|
||||
void ReceiveCommandInput(char command);
|
||||
void ReceiveSpecialInput(Keys key);
|
||||
|
||||
bool Selected { get; set; } //or Focused
|
||||
}
|
||||
|
||||
public class KeyboardDispatcher
|
||||
{
|
||||
public KeyboardDispatcher(GameWindow window)
|
||||
{
|
||||
EventInput.Initialize(window);
|
||||
EventInput.CharEntered += EventInput_CharEntered;
|
||||
EventInput.KeyDown += EventInput_KeyDown;
|
||||
}
|
||||
|
||||
void EventInput_KeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
if (_subscriber == null)
|
||||
return;
|
||||
|
||||
_subscriber.ReceiveSpecialInput(e.KeyCode);
|
||||
}
|
||||
|
||||
void EventInput_CharEntered(object sender, CharacterEventArgs e)
|
||||
{
|
||||
if (_subscriber == null)
|
||||
return;
|
||||
if (char.IsControl(e.Character))
|
||||
{
|
||||
//ctrl-v
|
||||
if (e.Character == 0x16)
|
||||
{
|
||||
#if WINDOWS
|
||||
//XNA runs in Multiple Thread Apartment state, which cannot recieve clipboard
|
||||
Thread thread = new Thread(PasteThread);
|
||||
thread.SetApartmentState(ApartmentState.STA);
|
||||
thread.Start();
|
||||
thread.Join();
|
||||
_subscriber.ReceiveTextInput(_pasteResult);
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
_subscriber.ReceiveCommandInput(e.Character);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_subscriber.ReceiveTextInput(e.Character);
|
||||
}
|
||||
}
|
||||
|
||||
IKeyboardSubscriber _subscriber;
|
||||
public IKeyboardSubscriber Subscriber
|
||||
{
|
||||
get { return _subscriber; }
|
||||
set
|
||||
{
|
||||
if (_subscriber != null)
|
||||
_subscriber.Selected = false;
|
||||
_subscriber = value;
|
||||
if (value != null)
|
||||
value.Selected = true;
|
||||
}
|
||||
}
|
||||
|
||||
#if WINDOWS
|
||||
//Thread has to be in Single Thread Apartment state in order to receive clipboard
|
||||
string _pasteResult = "";
|
||||
[STAThread]
|
||||
void PasteThread()
|
||||
{
|
||||
_pasteResult = Clipboard.ContainsText() ? Clipboard.GetText() : "";
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class ArtifactEvent : ScriptedEvent
|
||||
{
|
||||
private ItemPrefab itemPrefab;
|
||||
|
||||
private Item item;
|
||||
|
||||
private int state;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return "ScriptedEvent (" + (itemPrefab==null ? "null" : itemPrefab.Name) + ")";
|
||||
}
|
||||
|
||||
public ArtifactEvent(XElement element)
|
||||
: base(element)
|
||||
{
|
||||
string itemName = ToolBox.GetAttributeString(element, "itemname", "");
|
||||
|
||||
itemPrefab = ItemPrefab.list.Find(ip => ip.Name == itemName) as ItemPrefab;
|
||||
|
||||
if (itemPrefab == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Error in SalvageMission: couldn't find an item prefab with the name "+itemName);
|
||||
}
|
||||
}
|
||||
|
||||
public override void Init()
|
||||
{
|
||||
base.Init();
|
||||
|
||||
Vector2 position = Level.Loaded.GetRandomItemPos(
|
||||
Level.PositionType.Cave | Level.PositionType.MainPath | Level.PositionType.Ruin, 500.0f, 30.0f);
|
||||
|
||||
item = new Item(itemPrefab, position, null);
|
||||
item.MoveWithLevel = true;
|
||||
item.body.FarseerBody.IsKinematic = true;
|
||||
|
||||
//try to find a nearby artifact holder (or any alien itemcontainer) and place the artifact inside it
|
||||
foreach (Item it in Item.ItemList)
|
||||
{
|
||||
if (it.Submarine != null || !it.HasTag("alien")) continue;
|
||||
|
||||
if (Math.Abs(item.WorldPosition.X - it.WorldPosition.X) > 2000.0f) continue;
|
||||
if (Math.Abs(item.WorldPosition.Y - it.WorldPosition.Y) > 2000.0f) continue;
|
||||
|
||||
var itemContainer = it.GetComponent<Items.Components.ItemContainer>();
|
||||
if (itemContainer == null) continue;
|
||||
|
||||
itemContainer.Combine(item);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case 0:
|
||||
if (item.ParentInventory!=null) item.body.FarseerBody.IsKinematic = false;
|
||||
if (item.CurrentHull == null) return;
|
||||
|
||||
state = 1;
|
||||
break;
|
||||
case 1:
|
||||
if (!Submarine.MainSub.AtEndPosition && !Submarine.MainSub.AtStartPosition) return;
|
||||
|
||||
Finished();
|
||||
state = 2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
using Barotrauma.Items.Components;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class CargoMission : Mission
|
||||
{
|
||||
private XElement itemConfig;
|
||||
|
||||
private List<Item> items;
|
||||
|
||||
private int requiredDeliveryAmount;
|
||||
|
||||
public CargoMission(XElement element, Location[] locations)
|
||||
: base(element, locations)
|
||||
{
|
||||
itemConfig = element.Element("Items");
|
||||
|
||||
requiredDeliveryAmount = ToolBox.GetAttributeInt(element, "requireddeliveryamount", 0);
|
||||
}
|
||||
|
||||
private void InitItems()
|
||||
{
|
||||
items = new List<Item>();
|
||||
|
||||
if (itemConfig==null)
|
||||
{
|
||||
DebugConsole.ThrowError("Failed to initialize items for cargo mission (itemConfig == null)");
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (XElement subElement in itemConfig.Elements())
|
||||
{
|
||||
LoadItemAsChild(subElement, null);
|
||||
}
|
||||
|
||||
if (requiredDeliveryAmount == 0) requiredDeliveryAmount = items.Count;
|
||||
}
|
||||
|
||||
private void LoadItemAsChild(XElement element, Item parent)
|
||||
{
|
||||
string itemName = ToolBox.GetAttributeString(element, "name", "");
|
||||
|
||||
ItemPrefab itemPrefab = ItemPrefab.list.Find(ip => ip.Name == itemName) as ItemPrefab;
|
||||
if (itemPrefab==null)
|
||||
{
|
||||
DebugConsole.ThrowError("Couldn't spawn item for cargo mission: item prefab \""+element.Name.ToString()+"\" not found");
|
||||
return;
|
||||
}
|
||||
|
||||
WayPoint cargoSpawnPos = WayPoint.GetRandom(SpawnType.Cargo, null, Submarine.MainSub, true);
|
||||
if (cargoSpawnPos==null)
|
||||
{
|
||||
DebugConsole.ThrowError("Couldn't spawn items for cargo mission, cargo spawnpoint not found");
|
||||
return;
|
||||
}
|
||||
|
||||
var cargoRoom = cargoSpawnPos.CurrentHull;
|
||||
|
||||
if (cargoRoom == null)
|
||||
{
|
||||
DebugConsole.ThrowError("A waypoint marked as Cargo must be placed inside a room!");
|
||||
return;
|
||||
}
|
||||
|
||||
Vector2 position = new Vector2(
|
||||
cargoSpawnPos.Position.X + Rand.Range(-20.0f, 20.0f, false),
|
||||
cargoRoom.Rect.Y - cargoRoom.Rect.Height + itemPrefab.Size.Y / 2);
|
||||
|
||||
var item = new Item(itemPrefab, position, cargoRoom.Submarine);
|
||||
item.FindHull();
|
||||
|
||||
|
||||
items.Add(item);
|
||||
|
||||
if (parent != null) parent.Combine(item);
|
||||
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
int amount = ToolBox.GetAttributeInt(subElement, "amount", 1);
|
||||
for (int i = 0; i < amount; i++)
|
||||
{
|
||||
LoadItemAsChild(subElement, item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void Start(Level level)
|
||||
{
|
||||
InitItems();
|
||||
}
|
||||
|
||||
public override void End()
|
||||
{
|
||||
if (Submarine.MainSub != null && Submarine.MainSub.AtEndPosition)
|
||||
{
|
||||
int deliveredItemCount = items.Count(i => i.CurrentHull != null && i.Condition > 0.0f);
|
||||
|
||||
if (deliveredItemCount >= requiredDeliveryAmount)
|
||||
{
|
||||
GiveReward();
|
||||
|
||||
completed = true;
|
||||
}
|
||||
}
|
||||
|
||||
items.ForEach(i => i.Remove());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
using Barotrauma.Networking;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class CombatMission : Mission
|
||||
{
|
||||
private Submarine[] subs;
|
||||
private List<Character>[] crews;
|
||||
|
||||
private int state = 0;
|
||||
private int winner = -1;
|
||||
|
||||
private string[] descriptions;
|
||||
|
||||
private static string[] teamNames = { "Team A", "Team B" };
|
||||
|
||||
private bool initialized = false;
|
||||
|
||||
public override bool AllowRespawn
|
||||
{
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
public override string Description
|
||||
{
|
||||
get
|
||||
{
|
||||
if (GameMain.NetworkMember==null || GameMain.NetworkMember.Character==null)
|
||||
{
|
||||
//non-team-specific description
|
||||
return descriptions[0];
|
||||
}
|
||||
|
||||
//team specific
|
||||
return descriptions[GameMain.NetworkMember.Character.TeamID];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public override string SuccessMessage
|
||||
{
|
||||
get
|
||||
{
|
||||
if (winner == -1) return "";
|
||||
|
||||
return successMessage
|
||||
.Replace("[loser]", teamNames[1 - winner])
|
||||
.Replace("[winner]", teamNames[winner]);
|
||||
}
|
||||
}
|
||||
|
||||
public CombatMission(XElement element, Location[] locations)
|
||||
: base(element, locations)
|
||||
{
|
||||
descriptions = new string[]
|
||||
{
|
||||
ToolBox.GetAttributeString(element, "descriptionneutral", ""),
|
||||
ToolBox.GetAttributeString(element, "description1", ""),
|
||||
ToolBox.GetAttributeString(element, "description2", "")
|
||||
};
|
||||
|
||||
for (int i = 0; i < descriptions.Length; i++)
|
||||
{
|
||||
for (int n = 0; n < 2; n++)
|
||||
{
|
||||
descriptions[i] = descriptions[i].Replace("[location" + (n + 1) + "]", locations[n].Name);
|
||||
}
|
||||
}
|
||||
|
||||
teamNames = new string[]
|
||||
{
|
||||
ToolBox.GetAttributeString(element, "teamname1", "Team A"),
|
||||
ToolBox.GetAttributeString(element, "teamname2", "Team B")
|
||||
};
|
||||
}
|
||||
|
||||
public static string GetTeamName(int teamID)
|
||||
{
|
||||
//team IDs start from 1, while teamName array starts from 0
|
||||
teamID--;
|
||||
|
||||
if (teamID < 0 || teamID >= teamNames.Length)
|
||||
{
|
||||
return "Team " + teamID;
|
||||
}
|
||||
|
||||
return teamNames[teamID];
|
||||
}
|
||||
|
||||
public override bool AssignTeamIDs(List<Client> clients, out byte hostTeam)
|
||||
{
|
||||
List<Client> randList = new List<Client>(clients);
|
||||
for (int i = 0; i < randList.Count; i++)
|
||||
{
|
||||
Client a = randList[i];
|
||||
int oi = Rand.Range(0, randList.Count - 1);
|
||||
Client b = randList[oi];
|
||||
randList[i] = b;
|
||||
randList[oi] = a;
|
||||
}
|
||||
int halfPlayers = randList.Count / 2;
|
||||
for (int i = 0; i < randList.Count; i++)
|
||||
{
|
||||
if (i < halfPlayers)
|
||||
{
|
||||
randList[i].TeamID = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
randList[i].TeamID = 2;
|
||||
}
|
||||
}
|
||||
if (halfPlayers * 2 == randList.Count)
|
||||
{
|
||||
hostTeam = (byte)Rand.Range(1, 2);
|
||||
}
|
||||
else if (halfPlayers * 2 < randList.Count)
|
||||
{
|
||||
hostTeam = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
hostTeam = 2;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override void Start(Level level)
|
||||
{
|
||||
if (GameMain.NetworkMember == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Combat missions cannot be played in the single player mode.");
|
||||
return;
|
||||
}
|
||||
|
||||
subs = new Submarine[] { Submarine.MainSubs[0], Submarine.MainSubs[1] };
|
||||
subs[1].SetPosition(Level.Loaded.EndPosition - new Vector2(0.0f, 2000.0f));
|
||||
subs[1].FlipX();
|
||||
|
||||
crews = new List<Character>[] { new List<Character>(), new List<Character>() };
|
||||
|
||||
foreach (Submarine submarine in Submarine.Loaded)
|
||||
{
|
||||
//hide all subs from radar to make sneak attacks possible
|
||||
submarine.OnRadar = false;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
if (!initialized)
|
||||
{
|
||||
crews[0].Clear();
|
||||
crews[1].Clear();
|
||||
foreach (Character character in Character.CharacterList)
|
||||
{
|
||||
if (character.TeamID == 1)
|
||||
{
|
||||
crews[0].Add(character);
|
||||
}
|
||||
else if (character.TeamID == 2)
|
||||
{
|
||||
crews[1].Add(character);
|
||||
}
|
||||
}
|
||||
|
||||
if (GameMain.Client != null)
|
||||
{
|
||||
//no characters in one of the teams, the client may not have received all spawn messages yet
|
||||
if (crews[0].Count == 0 || crews[1].Count == 0) return;
|
||||
}
|
||||
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
bool[] teamDead =
|
||||
{
|
||||
crews[0].All(c => c.IsDead || c.IsUnconscious),
|
||||
crews[1].All(c => c.IsDead || c.IsUnconscious)
|
||||
};
|
||||
|
||||
if (state == 0)
|
||||
{
|
||||
for (int i = 0; i < teamDead.Length; i++)
|
||||
{
|
||||
if (!teamDead[i] && teamDead[1-i])
|
||||
{
|
||||
//make sure nobody in the other team can be revived because that would be pretty weird
|
||||
crews[1-i].ForEach(c => { if (!c.IsDead) c.Kill(CauseOfDeath.Damage); });
|
||||
|
||||
winner = i;
|
||||
|
||||
ShowMessage(i);
|
||||
state = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (winner>=0 && subs[winner] != null &&
|
||||
(winner == 0 && subs[winner].AtStartPosition) || (winner == 1 && subs[winner].AtEndPosition) &&
|
||||
crews[winner].Any(c => !c.IsDead && c.Submarine == subs[winner]))
|
||||
{
|
||||
GameMain.GameSession.CrewManager.WinningTeam = winner+1;
|
||||
if (GameMain.Server != null) GameMain.Server.EndGame();
|
||||
}
|
||||
}
|
||||
|
||||
if (teamDead[0] && teamDead[1])
|
||||
{
|
||||
GameMain.GameSession.CrewManager.WinningTeam = 0;
|
||||
winner = -1;
|
||||
if (GameMain.Server != null) GameMain.Server.EndGame();
|
||||
}
|
||||
}
|
||||
|
||||
public override void End()
|
||||
{
|
||||
if (GameMain.NetworkMember == null) return;
|
||||
|
||||
if (winner > -1)
|
||||
{
|
||||
GiveReward();
|
||||
completed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,270 @@
|
||||
using Barotrauma.Networking;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class Mission
|
||||
{
|
||||
public static List<string> MissionTypes = new List<string>() { "Random" };
|
||||
|
||||
private string name;
|
||||
|
||||
private string description;
|
||||
|
||||
protected bool completed;
|
||||
|
||||
protected string successMessage;
|
||||
protected string failureMessage;
|
||||
|
||||
protected string radarLabel;
|
||||
|
||||
protected List<string> headers;
|
||||
protected List<string> messages;
|
||||
|
||||
private int reward;
|
||||
|
||||
public string Name
|
||||
{
|
||||
get { return name; }
|
||||
}
|
||||
|
||||
public virtual string Description
|
||||
{
|
||||
get { return description; }
|
||||
}
|
||||
|
||||
public int Reward
|
||||
{
|
||||
get { return reward; }
|
||||
}
|
||||
|
||||
public bool Completed
|
||||
{
|
||||
get { return completed; }
|
||||
set { completed = value; }
|
||||
}
|
||||
|
||||
public virtual bool AllowRespawn
|
||||
{
|
||||
get { return true; }
|
||||
}
|
||||
|
||||
public virtual string RadarLabel
|
||||
{
|
||||
get { return radarLabel; }
|
||||
}
|
||||
|
||||
public virtual Vector2 RadarPosition
|
||||
{
|
||||
get { return Vector2.Zero; }
|
||||
}
|
||||
|
||||
virtual public string SuccessMessage
|
||||
{
|
||||
get { return successMessage; }
|
||||
}
|
||||
|
||||
public string FailureMessage
|
||||
{
|
||||
get { return failureMessage; }
|
||||
}
|
||||
|
||||
public static void Init()
|
||||
{
|
||||
var files = GameMain.SelectedPackage.GetFilesOfType(ContentType.Missions);
|
||||
foreach (string file in files)
|
||||
{
|
||||
XDocument doc = ToolBox.TryLoadXml(file);
|
||||
if (doc == null || doc.Root == null) continue;
|
||||
|
||||
foreach (XElement element in doc.Root.Elements())
|
||||
{
|
||||
string missionTypeName = element.Name.ToString();
|
||||
missionTypeName = missionTypeName.Replace("Mission", "");
|
||||
|
||||
if (!MissionTypes.Contains(missionTypeName)) MissionTypes.Add(missionTypeName);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public Mission(XElement element, Location[] locations)
|
||||
{
|
||||
name = ToolBox.GetAttributeString(element, "name", "");
|
||||
|
||||
description = ToolBox.GetAttributeString(element, "description", "");
|
||||
|
||||
reward = ToolBox.GetAttributeInt(element, "reward", 1);
|
||||
|
||||
successMessage = ToolBox.GetAttributeString(element, "successmessage",
|
||||
"Mission completed successfully");
|
||||
failureMessage = ToolBox.GetAttributeString(element, "failuremessage",
|
||||
"Mission failed");
|
||||
|
||||
radarLabel = ToolBox.GetAttributeString(element, "radarlabel", "");
|
||||
|
||||
messages = new List<string>();
|
||||
headers = new List<string>();
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
if (subElement.Name.ToString().ToLowerInvariant() != "message") continue;
|
||||
headers.Add(ToolBox.GetAttributeString(subElement, "header", ""));
|
||||
messages.Add(ToolBox.GetAttributeString(subElement, "text", ""));
|
||||
}
|
||||
|
||||
for (int n = 0; n < 2; n++)
|
||||
{
|
||||
description = description.Replace("[location" + (n + 1) + "]", locations[n].Name);
|
||||
|
||||
successMessage = successMessage.Replace("[location" + (n + 1) + "]", locations[n].Name);
|
||||
failureMessage = failureMessage.Replace("[location" + (n + 1) + "]", locations[n].Name);
|
||||
|
||||
for (int m = 0; m < messages.Count; m++)
|
||||
{
|
||||
messages[m] = messages[m].Replace("[location" + (n + 1) + "]", locations[n].Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static Mission LoadRandom(Location[] locations, MTRandom rand, string missionType = "", bool isSinglePlayer = false)
|
||||
{
|
||||
missionType = missionType.ToLowerInvariant();
|
||||
|
||||
var files = GameMain.SelectedPackage.GetFilesOfType(ContentType.Missions);
|
||||
string configFile = files[rand.Next(files.Count)];
|
||||
|
||||
XDocument doc = ToolBox.TryLoadXml(configFile);
|
||||
if (doc == null) return null;
|
||||
|
||||
int eventCount = doc.Root.Elements().Count();
|
||||
//int[] commonness = new int[eventCount];
|
||||
float[] eventProbability = new float[eventCount];
|
||||
|
||||
float probabilitySum = 0.0f;
|
||||
|
||||
List<XElement> matchingElements = new List<XElement>();
|
||||
|
||||
if (missionType == "random")
|
||||
{
|
||||
matchingElements = doc.Root.Elements().ToList();
|
||||
}
|
||||
else if (missionType == "none")
|
||||
{
|
||||
return null;
|
||||
}
|
||||
else if (string.IsNullOrWhiteSpace(missionType))
|
||||
{
|
||||
matchingElements = doc.Root.Elements().ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
matchingElements = doc.Root.Elements().ToList().FindAll(m => m.Name.ToString().ToLowerInvariant().Replace("mission", "") == missionType);
|
||||
}
|
||||
|
||||
if (isSinglePlayer)
|
||||
{
|
||||
matchingElements.RemoveAll(m => ToolBox.GetAttributeBool(m, "multiplayeronly", false));
|
||||
}
|
||||
else
|
||||
{
|
||||
matchingElements.RemoveAll(m => ToolBox.GetAttributeBool(m, "singleplayeronly", false));
|
||||
}
|
||||
|
||||
int i = 0;
|
||||
foreach (XElement element in matchingElements)
|
||||
{
|
||||
eventProbability[i] = ToolBox.GetAttributeInt(element, "commonness", 1);
|
||||
|
||||
probabilitySum += eventProbability[i];
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
float randomNumber = (float)rand.NextDouble() * probabilitySum;
|
||||
|
||||
i = 0;
|
||||
foreach (XElement element in matchingElements)
|
||||
{
|
||||
if (randomNumber <= eventProbability[i])
|
||||
{
|
||||
Type t;
|
||||
string type = element.Name.ToString();
|
||||
|
||||
try
|
||||
{
|
||||
t = Type.GetType("Barotrauma." + type, true, true);
|
||||
if (t == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Error in " + configFile + "! Could not find a mission class of the type \"" + type + "\".");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
DebugConsole.ThrowError("Error in " + configFile + "! Could not find a mission class of the type \"" + type + "\".");
|
||||
continue;
|
||||
}
|
||||
|
||||
ConstructorInfo constructor = t.GetConstructor(new[] { typeof(XElement), typeof(Location[]) });
|
||||
|
||||
object instance = constructor.Invoke(new object[] { element, locations });
|
||||
|
||||
Mission mission = (Mission)instance;
|
||||
|
||||
return mission;
|
||||
}
|
||||
|
||||
randomNumber -= eventProbability[i];
|
||||
i++;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public virtual void Start(Level level) { }
|
||||
|
||||
public virtual void Update(float deltaTime) { }
|
||||
|
||||
public virtual bool AssignTeamIDs(List<Networking.Client> clients, out byte hostTeam)
|
||||
{
|
||||
clients.ForEach(c => c.TeamID = 1);
|
||||
hostTeam = 1;
|
||||
return false;
|
||||
}
|
||||
|
||||
public void ShowMessage(int index)
|
||||
{
|
||||
if (index >= headers.Count && index >= messages.Count) return;
|
||||
|
||||
string header = index < headers.Count ? headers[index] : "";
|
||||
string message = index < messages.Count ? messages[index] : "";
|
||||
|
||||
GameServer.Log("Mission info: " + header + " - " + message, ServerLog.MessageType.ServerMessage);
|
||||
|
||||
new GUIMessageBox(header, message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// End the mission and give a reward if it was completed successfully
|
||||
/// </summary>
|
||||
public virtual void End()
|
||||
{
|
||||
completed = true;
|
||||
|
||||
GiveReward();
|
||||
}
|
||||
|
||||
public void GiveReward()
|
||||
{
|
||||
var mode = GameMain.GameSession.gameMode as SinglePlayerMode;
|
||||
if (mode == null) return;
|
||||
|
||||
mode.Money += reward;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
using Barotrauma.Networking;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class MonsterMission : Mission
|
||||
{
|
||||
private string monsterFile;
|
||||
|
||||
private int state;
|
||||
|
||||
private Character monster;
|
||||
|
||||
private Vector2 radarPosition;
|
||||
|
||||
public override Vector2 RadarPosition
|
||||
{
|
||||
get { return monster != null && !monster.IsDead ? radarPosition : Vector2.Zero; }
|
||||
}
|
||||
|
||||
public MonsterMission(XElement element, Location[] locations)
|
||||
: base(element, locations)
|
||||
{
|
||||
monsterFile = ToolBox.GetAttributeString(element, "monsterfile", "");
|
||||
}
|
||||
|
||||
public override void Start(Level level)
|
||||
{
|
||||
Vector2 spawnPos = Level.Loaded.GetRandomInterestingPosition(true, Level.PositionType.MainPath, true);
|
||||
|
||||
monster = Character.Create(monsterFile, spawnPos, null, GameMain.Client != null);
|
||||
monster.Enabled = false;
|
||||
radarPosition = spawnPos;
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case 0:
|
||||
if (monster.Enabled)
|
||||
{
|
||||
radarPosition = monster.Position;
|
||||
}
|
||||
|
||||
if (!monster.IsDead) return;
|
||||
ShowMessage(state);
|
||||
state = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public override void End()
|
||||
{
|
||||
if (!monster.IsDead) return;
|
||||
|
||||
GiveReward();
|
||||
|
||||
completed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
using FarseerPhysics;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class SalvageMission : Mission
|
||||
{
|
||||
private ItemPrefab itemPrefab;
|
||||
|
||||
private Item item;
|
||||
|
||||
private Level.PositionType spawnPositionType;
|
||||
|
||||
private int state;
|
||||
|
||||
public override Vector2 RadarPosition
|
||||
{
|
||||
get
|
||||
{
|
||||
return state>0 ? Vector2.Zero : ConvertUnits.ToDisplayUnits(item.SimPosition);
|
||||
}
|
||||
}
|
||||
|
||||
public SalvageMission(XElement element, Location[] locations)
|
||||
: base(element, locations)
|
||||
{
|
||||
string itemName = ToolBox.GetAttributeString(element, "itemname", "");
|
||||
|
||||
itemPrefab = ItemPrefab.list.Find(ip => ip.Name == itemName) as ItemPrefab;
|
||||
|
||||
string spawnPositionTypeStr = ToolBox.GetAttributeString(element, "spawntype", "");
|
||||
|
||||
if (string.IsNullOrWhiteSpace(spawnPositionTypeStr) ||
|
||||
!Enum.TryParse<Level.PositionType>(spawnPositionTypeStr, true, out spawnPositionType))
|
||||
{
|
||||
spawnPositionType = Level.PositionType.Cave | Level.PositionType.Ruin;
|
||||
}
|
||||
|
||||
if (itemPrefab == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Error in SalvageMission: couldn't find an item prefab with the name "+itemName);
|
||||
}
|
||||
}
|
||||
|
||||
public override void Start(Level level)
|
||||
{
|
||||
Vector2 position = Level.Loaded.GetRandomItemPos(spawnPositionType, 100.0f, 30.0f);
|
||||
|
||||
item = new Item(itemPrefab, position, null);
|
||||
item.MoveWithLevel = true;
|
||||
item.body.FarseerBody.IsKinematic = true;
|
||||
|
||||
if (item.HasTag("alien"))
|
||||
{
|
||||
//try to find a nearby artifact holder (or any alien itemcontainer) and place the artifact inside it
|
||||
foreach (Item it in Item.ItemList)
|
||||
{
|
||||
if (it.Submarine != null || !it.HasTag("alien")) continue;
|
||||
|
||||
if (Math.Abs(item.WorldPosition.X - it.WorldPosition.X) > 2000.0f) continue;
|
||||
if (Math.Abs(item.WorldPosition.Y - it.WorldPosition.Y) > 2000.0f) continue;
|
||||
|
||||
var itemContainer = it.GetComponent<Items.Components.ItemContainer>();
|
||||
if (itemContainer == null) continue;
|
||||
|
||||
itemContainer.Combine(item);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case 0:
|
||||
//item.body.LinearVelocity = Vector2.Zero;
|
||||
if (item.ParentInventory!=null) item.body.FarseerBody.IsKinematic = false;
|
||||
if (item.CurrentHull == null) return;
|
||||
|
||||
ShowMessage(state);
|
||||
state = 1;
|
||||
break;
|
||||
case 1:
|
||||
if (!Submarine.MainSub.AtEndPosition && !Submarine.MainSub.AtStartPosition) return;
|
||||
ShowMessage(state);
|
||||
state = 2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public override void End()
|
||||
{
|
||||
if (item.CurrentHull == null || !item.CurrentHull.Submarine.AtEndPosition || item.Removed) return;
|
||||
item.Remove();
|
||||
|
||||
GiveReward();
|
||||
|
||||
completed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
using System.Xml.Linq;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class MonsterEvent : ScriptedEvent
|
||||
{
|
||||
private string characterFile;
|
||||
|
||||
private int minAmount, maxAmount;
|
||||
|
||||
private Character[] monsters;
|
||||
|
||||
private bool spawnDeep;
|
||||
|
||||
private bool disallowed;
|
||||
|
||||
private bool repeat;
|
||||
|
||||
private Level.PositionType spawnPosType;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return "ScriptedEvent (" + characterFile + ")";
|
||||
}
|
||||
|
||||
private bool isActive;
|
||||
public override bool IsActive
|
||||
{
|
||||
get
|
||||
{
|
||||
return isActive;
|
||||
}
|
||||
}
|
||||
|
||||
public MonsterEvent(XElement element)
|
||||
: base (element)
|
||||
{
|
||||
characterFile = ToolBox.GetAttributeString(element, "characterfile", "");
|
||||
|
||||
int defaultAmount = ToolBox.GetAttributeInt(element, "amount", 1);
|
||||
|
||||
minAmount = ToolBox.GetAttributeInt(element, "minamount", defaultAmount);
|
||||
maxAmount = Math.Max(ToolBox.GetAttributeInt(element, "maxamount", 1), minAmount);
|
||||
|
||||
var spawnPosTypeStr = ToolBox.GetAttributeString(element, "spawntype", "");
|
||||
|
||||
if (string.IsNullOrWhiteSpace(spawnPosTypeStr) ||
|
||||
!Enum.TryParse<Level.PositionType>(spawnPosTypeStr, true, out spawnPosType))
|
||||
{
|
||||
spawnPosType = Level.PositionType.MainPath;
|
||||
}
|
||||
|
||||
spawnDeep = ToolBox.GetAttributeBool(element, "spawndeep", false);
|
||||
|
||||
repeat = ToolBox.GetAttributeBool(element, "repeat", repeat);
|
||||
|
||||
if (GameMain.NetworkMember != null)
|
||||
{
|
||||
List<string> monsterNames = GameMain.NetworkMember.monsterEnabled.Keys.ToList();
|
||||
string tryKey = monsterNames.Find(s => characterFile.ToLower().Contains(s.ToLower()));
|
||||
if (!string.IsNullOrWhiteSpace(tryKey))
|
||||
{
|
||||
if (!GameMain.NetworkMember.monsterEnabled[tryKey]) disallowed = true; //spawn was disallowed by host
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void Init()
|
||||
{
|
||||
base.Init();
|
||||
|
||||
SpawnMonsters(Rand.Range(minAmount, maxAmount, false));
|
||||
}
|
||||
|
||||
private Character[] SpawnMonsters(int amount)
|
||||
{
|
||||
if (disallowed) return null;
|
||||
|
||||
Vector2 spawnPos = Level.Loaded.GetRandomInterestingPosition(true, spawnPosType, true);
|
||||
|
||||
|
||||
var monsters = new Character[amount];
|
||||
|
||||
if (spawnDeep) spawnPos.Y -= Level.Loaded.Size.Y;
|
||||
|
||||
for (int i = 0; i < amount; i++)
|
||||
{
|
||||
spawnPos.X += Rand.Range(-0.5f, 0.5f, false);
|
||||
spawnPos.Y += Rand.Range(-0.5f, 0.5f, false);
|
||||
monsters[i] = Character.Create(characterFile, spawnPos, null, GameMain.Client != null);
|
||||
}
|
||||
|
||||
return monsters;
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
if (disallowed)
|
||||
{
|
||||
Finished();
|
||||
return;
|
||||
}
|
||||
|
||||
if (monsters == null)
|
||||
{
|
||||
monsters = SpawnMonsters(Rand.Range(minAmount, maxAmount, false));
|
||||
return;
|
||||
}
|
||||
|
||||
if (repeat)
|
||||
{
|
||||
//clients aren't allowed to spawn more monsters mid-round
|
||||
if (GameMain.Client != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < monsters.Length; i++)
|
||||
{
|
||||
if (monsters[i] == null || monsters[i].Removed || monsters[i].IsDead)
|
||||
{
|
||||
monsters[i] = SpawnMonsters(1)[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isFinished) return;
|
||||
|
||||
isActive = false;
|
||||
|
||||
Entity targetEntity = null;
|
||||
if (Character.Controlled != null)
|
||||
{
|
||||
targetEntity = Character.Controlled;
|
||||
}
|
||||
else
|
||||
{
|
||||
targetEntity = Submarine.FindClosest(GameMain.GameScreen.Cam.WorldViewCenter);
|
||||
}
|
||||
|
||||
bool monstersDead = true;
|
||||
foreach (Character monster in monsters)
|
||||
{
|
||||
if (!monster.IsDead)
|
||||
{
|
||||
monstersDead = false;
|
||||
|
||||
if (targetEntity != null && Vector2.DistanceSquared(monster.WorldPosition, targetEntity.WorldPosition) < 5000.0f * 5000.0f)
|
||||
{
|
||||
isActive = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (monstersDead && !repeat) Finished();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
namespace Barotrauma
|
||||
{
|
||||
class PropertyTask : Task
|
||||
{
|
||||
Item item;
|
||||
|
||||
|
||||
public delegate bool IsFinishedHandler();
|
||||
private IsFinishedHandler IsFinishedChecker;
|
||||
|
||||
public PropertyTask(Item item, IsFinishedHandler isFinished, float priority, string name)
|
||||
: base(priority, name)
|
||||
{
|
||||
if (taskManager == null) return;
|
||||
|
||||
this.item = item;
|
||||
IsFinishedChecker = isFinished;
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
if (IsFinishedChecker())
|
||||
{
|
||||
Finished();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
namespace Barotrauma
|
||||
{
|
||||
class RepairTask : Task
|
||||
{
|
||||
Item item;
|
||||
|
||||
public RepairTask(Item item, float priority, string name)
|
||||
: base(priority, name)
|
||||
{
|
||||
if (taskManager == null) return;
|
||||
|
||||
this.item = item;
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
if (item.Condition > 50.0f) Finished();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class ScriptedEvent
|
||||
{
|
||||
protected string name;
|
||||
protected string description;
|
||||
|
||||
protected int commonness;
|
||||
protected int difficulty;
|
||||
|
||||
protected bool isFinished;
|
||||
|
||||
public string Name
|
||||
{
|
||||
get { return name; }
|
||||
}
|
||||
|
||||
public string Description
|
||||
{
|
||||
get { return description; }
|
||||
}
|
||||
|
||||
public int Commonness
|
||||
{
|
||||
get { return commonness; }
|
||||
}
|
||||
|
||||
public string MusicType
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public virtual bool IsActive
|
||||
{
|
||||
get { return true; }
|
||||
}
|
||||
|
||||
public bool IsFinished
|
||||
{
|
||||
get { return isFinished; }
|
||||
}
|
||||
|
||||
public int Difficulty
|
||||
{
|
||||
get { return difficulty; }
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return "ScriptedEvent ("+name+")";
|
||||
}
|
||||
|
||||
public ScriptedEvent(XElement element)
|
||||
{
|
||||
name = ToolBox.GetAttributeString(element, "name", "");
|
||||
description = ToolBox.GetAttributeString(element, "description", "");
|
||||
|
||||
difficulty = ToolBox.GetAttributeInt(element, "difficulty", 1);
|
||||
commonness = ToolBox.GetAttributeInt(element, "commonness", 1);
|
||||
|
||||
MusicType = ToolBox.GetAttributeString(element, "musictype", "default");
|
||||
}
|
||||
|
||||
|
||||
public static ScriptedEvent LoadRandom(Random rand)
|
||||
{
|
||||
var configFiles = GameMain.Config.SelectedContentPackage.GetFilesOfType(ContentType.RandomEvents);
|
||||
|
||||
if (!configFiles.Any())
|
||||
{
|
||||
DebugConsole.ThrowError("No config files for random events found in the selected content package");
|
||||
return null;
|
||||
}
|
||||
|
||||
string configFile = configFiles[0];
|
||||
|
||||
XDocument doc = ToolBox.TryLoadXml(configFile);
|
||||
if (doc == null) return null;
|
||||
|
||||
int eventCount = doc.Root.Elements().Count();
|
||||
//int[] commonness = new int[eventCount];
|
||||
float[] eventProbability = new float[eventCount];
|
||||
|
||||
float probabilitySum = 0.0f;
|
||||
|
||||
int i = 0;
|
||||
foreach (XElement element in doc.Root.Elements())
|
||||
{
|
||||
eventProbability[i] = ToolBox.GetAttributeInt(element, "commonness", 1);
|
||||
|
||||
//if the event has been previously selected, it's less likely to be selected now
|
||||
//int previousEventIndex = previousEvents.FindIndex(x => x == i);
|
||||
//if (previousEventIndex >= 0)
|
||||
//{
|
||||
// //how many shifts ago was the event last selected
|
||||
// int eventDist = eventCount - previousEventIndex;
|
||||
|
||||
// float weighting = (1.0f / eventDist) * PreviouslyUsedWeight;
|
||||
|
||||
// eventProbability[i] *= weighting;
|
||||
//}
|
||||
|
||||
probabilitySum += eventProbability[i];
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
float randomNumber = (float)rand.NextDouble() * probabilitySum;
|
||||
|
||||
i = 0;
|
||||
foreach (XElement element in doc.Root.Elements())
|
||||
{
|
||||
if (randomNumber <= eventProbability[i])
|
||||
{
|
||||
Type t;
|
||||
string type = element.Name.ToString();
|
||||
|
||||
try
|
||||
{
|
||||
t = Type.GetType("Barotrauma." + type, true, true);
|
||||
if (t == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Error in " + configFile + "! Could not find an event class of the type \"" + type + "\".");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
DebugConsole.ThrowError("Error in " + configFile + "! Could not find an event class of the type \"" + type + "\".");
|
||||
continue;
|
||||
}
|
||||
|
||||
ConstructorInfo constructor = t.GetConstructor(new[] { typeof(XElement) });
|
||||
object instance = null;
|
||||
try
|
||||
{
|
||||
instance = constructor.Invoke(new object[] { element });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
DebugConsole.ThrowError(ex.InnerException!=null ? ex.InnerException.ToString() : ex.ToString());
|
||||
}
|
||||
|
||||
//previousEvents.Add(i);
|
||||
|
||||
return (ScriptedEvent)instance;
|
||||
}
|
||||
|
||||
randomNumber -= eventProbability[i];
|
||||
i++;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public virtual void Init()
|
||||
{
|
||||
isFinished = false;
|
||||
}
|
||||
|
||||
public virtual void Update(float deltaTime)
|
||||
{
|
||||
}
|
||||
|
||||
public virtual void Finished()
|
||||
{
|
||||
isFinished = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
namespace Barotrauma
|
||||
{
|
||||
class ScriptedTask : Task
|
||||
{
|
||||
private ScriptedEvent scriptedEvent;
|
||||
|
||||
public override bool IsStarted
|
||||
{
|
||||
get
|
||||
{
|
||||
return scriptedEvent.IsActive;
|
||||
}
|
||||
}
|
||||
|
||||
public ScriptedTask(ScriptedEvent scriptedEvent)
|
||||
: base(scriptedEvent.Difficulty, scriptedEvent.Name)
|
||||
{
|
||||
if (taskManager == null) return;
|
||||
|
||||
this.musicType = scriptedEvent.MusicType;
|
||||
|
||||
this.scriptedEvent = scriptedEvent;
|
||||
scriptedEvent.Init();
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
scriptedEvent.Update(deltaTime);
|
||||
if (scriptedEvent.IsFinished) Finished();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class Task
|
||||
{
|
||||
|
||||
protected string name;
|
||||
|
||||
private float priority;
|
||||
|
||||
protected string musicType;
|
||||
|
||||
protected TaskManager taskManager;
|
||||
|
||||
protected bool isFinished;
|
||||
|
||||
public string Name
|
||||
{
|
||||
get { return name; }
|
||||
}
|
||||
|
||||
public float Priority
|
||||
{
|
||||
get { return priority; }
|
||||
}
|
||||
|
||||
public string MusicType
|
||||
{
|
||||
get { return musicType; }
|
||||
}
|
||||
|
||||
public bool IsFinished
|
||||
{
|
||||
get { return isFinished; }
|
||||
}
|
||||
|
||||
public virtual bool IsStarted
|
||||
{
|
||||
get { return true; }
|
||||
}
|
||||
|
||||
public Task(float priority, string name)
|
||||
{
|
||||
if (GameMain.GameSession==null || GameMain.GameSession.TaskManager == null) return;
|
||||
|
||||
taskManager = GameMain.GameSession.TaskManager;
|
||||
musicType = "repair";
|
||||
this.priority = priority;
|
||||
this.name = name;
|
||||
|
||||
taskManager.AddTask(this);
|
||||
}
|
||||
|
||||
public virtual void Update(float deltaTime)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
protected virtual void Finished()
|
||||
{
|
||||
isFinished = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class TaskManager
|
||||
{
|
||||
const float CriticalPriority = 50.0f;
|
||||
|
||||
private List<Task> tasks;
|
||||
|
||||
public List<Task> Tasks
|
||||
{
|
||||
get { return tasks; }
|
||||
}
|
||||
|
||||
public bool CriticalTasks
|
||||
{
|
||||
get
|
||||
{
|
||||
return tasks.Any(task => task.Priority >= CriticalPriority);
|
||||
}
|
||||
}
|
||||
|
||||
public TaskManager(GameSession session)
|
||||
{
|
||||
tasks = new List<Task>();
|
||||
}
|
||||
|
||||
public void AddTask(Task newTask)
|
||||
{
|
||||
if (tasks.Contains(newTask)) return;
|
||||
|
||||
tasks.Add(newTask);
|
||||
}
|
||||
|
||||
public void StartShift(Level level)
|
||||
{
|
||||
CreateScriptedEvents(level);
|
||||
}
|
||||
|
||||
|
||||
public void EndShift()
|
||||
{
|
||||
tasks.Clear();
|
||||
}
|
||||
|
||||
private void CreateScriptedEvents(Level level)
|
||||
{
|
||||
MTRandom rand = new MTRandom(ToolBox.StringToInt(level.Seed));
|
||||
|
||||
float totalDifficulty = level.Difficulty;
|
||||
|
||||
int tries = 0;
|
||||
while (tries < 5)
|
||||
{
|
||||
ScriptedEvent scriptedEvent = ScriptedEvent.LoadRandom(rand);
|
||||
if (scriptedEvent==null || scriptedEvent.Difficulty > totalDifficulty)
|
||||
{
|
||||
tries++;
|
||||
continue;
|
||||
}
|
||||
DebugConsole.Log("Created scripted event " + scriptedEvent.ToString());
|
||||
|
||||
AddTask(new ScriptedTask(scriptedEvent));
|
||||
totalDifficulty -= scriptedEvent.Difficulty;
|
||||
tries = 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void Update(float deltaTime)
|
||||
{
|
||||
foreach (Task task in tasks)
|
||||
{
|
||||
if (!task.IsFinished)
|
||||
{
|
||||
task.Update(deltaTime);
|
||||
}
|
||||
}
|
||||
|
||||
tasks.RemoveAll(t => t.IsFinished);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,287 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using SharpFont;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
public class ScalableFont
|
||||
{
|
||||
private static List<ScalableFont> FontList = new List<ScalableFont>();
|
||||
private static Library Lib = null;
|
||||
|
||||
private string filename;
|
||||
private Face face;
|
||||
private uint size;
|
||||
private int baseHeight;
|
||||
//private int lineHeight;
|
||||
private Dictionary<uint, GlyphData> texCoords;
|
||||
private List<Texture2D> textures;
|
||||
private GraphicsDevice graphicsDevice;
|
||||
|
||||
public uint Size
|
||||
{
|
||||
get
|
||||
{
|
||||
return size;
|
||||
}
|
||||
set
|
||||
{
|
||||
size = value;
|
||||
if (graphicsDevice!=null) RenderAtlas(graphicsDevice, charRanges, texDims, baseChar);
|
||||
}
|
||||
}
|
||||
|
||||
private uint[] charRanges;
|
||||
private int texDims;
|
||||
private uint baseChar;
|
||||
|
||||
private struct GlyphData
|
||||
{
|
||||
public int texIndex;
|
||||
public Vector2 drawOffset;
|
||||
public float advance;
|
||||
public Rectangle texCoords;
|
||||
}
|
||||
|
||||
public ScalableFont(string filename, uint size, GraphicsDevice gd=null)
|
||||
{
|
||||
if (Lib == null) Lib = new Library();
|
||||
this.filename = filename;
|
||||
this.face = null;
|
||||
foreach (ScalableFont font in FontList)
|
||||
{
|
||||
if (font.filename == filename)
|
||||
{
|
||||
this.face = font.face;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (this.face == null)
|
||||
{
|
||||
this.face = new Face(Lib, filename);
|
||||
}
|
||||
this.size = size;
|
||||
|
||||
this.textures = new List<Texture2D>();
|
||||
|
||||
this.texCoords = new Dictionary<uint, GlyphData>();
|
||||
|
||||
if (gd != null) RenderAtlas(gd);
|
||||
|
||||
FontList.Add(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Renders the font into at least one texture atlas, which is simply a collection of all glyphs in the ranges defined by charRanges.
|
||||
/// Don't call this too often or with very large sizes.
|
||||
/// </summary>
|
||||
/// <param name="gd">Graphics device, required to create textures.</param>
|
||||
/// <param name="charRanges">Character ranges between each even element with their corresponding odd element. Default is 0x20 to 0xFFFF.</param>
|
||||
/// <param name="texDims">Texture dimensions. Default is 512x512.</param>
|
||||
/// <param name="baseChar">Base character used to shift all other characters downwards when rendering. Defaults to T.</param>
|
||||
public void RenderAtlas(GraphicsDevice gd, uint[] charRanges = null, int texDims = 512, uint baseChar = 0x54)
|
||||
{
|
||||
if (charRanges == null)
|
||||
{
|
||||
charRanges = new uint[] { 0x20, 0xFFFF };
|
||||
}
|
||||
this.charRanges = charRanges;
|
||||
this.texDims = texDims;
|
||||
this.baseChar = baseChar;
|
||||
|
||||
face.SetPixelSizes(0, size);
|
||||
textures.ForEach(t => t.Dispose());
|
||||
textures.Clear();
|
||||
texCoords.Clear();
|
||||
|
||||
uint[] pixelBuffer = new uint[texDims * texDims];
|
||||
for (int i = 0; i < texDims * texDims; i++)
|
||||
{
|
||||
pixelBuffer[i] = 0;
|
||||
}
|
||||
|
||||
textures.Add(new Texture2D(gd, texDims, texDims, false, SurfaceFormat.Color));
|
||||
int texIndex = 0;
|
||||
|
||||
Vector2 currentCoords = Vector2.Zero;
|
||||
int nextY = 0;
|
||||
|
||||
face.LoadGlyph(face.GetCharIndex(baseChar), LoadFlags.Default, LoadTarget.Normal);
|
||||
baseHeight = face.Glyph.Metrics.Height.ToInt32();
|
||||
//lineHeight = baseHeight;
|
||||
for (int i = 0; i < charRanges.Length; i += 2)
|
||||
{
|
||||
uint start = charRanges[i];
|
||||
uint end = charRanges[i + 1];
|
||||
for (uint j = start; j <= end; j++)
|
||||
{
|
||||
uint glyphIndex = face.GetCharIndex(j);
|
||||
if (glyphIndex == 0) continue;
|
||||
face.LoadGlyph(glyphIndex, LoadFlags.Default, LoadTarget.Normal);
|
||||
if (face.Glyph.Metrics.Width == 0 || face.Glyph.Metrics.Height == 0)
|
||||
{
|
||||
if (face.Glyph.Metrics.HorizontalAdvance > 0)
|
||||
{
|
||||
//glyph is empty, but char still applies advance
|
||||
GlyphData blankData = new GlyphData();
|
||||
blankData.advance = (float)face.Glyph.Metrics.HorizontalAdvance;
|
||||
blankData.texIndex = -1; //indicates no texture because the glyph is empty
|
||||
texCoords.Add(j, blankData);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
//stacktrace doesn't really work that well when RenderGlyph throws an exception
|
||||
face.Glyph.RenderGlyph(RenderMode.Normal);
|
||||
byte[] bitmap = face.Glyph.Bitmap.BufferData;
|
||||
int glyphWidth = face.Glyph.Bitmap.Width;
|
||||
int glyphHeight = bitmap.Length / glyphWidth;
|
||||
|
||||
//if (glyphHeight>lineHeight) lineHeight=glyphHeight;
|
||||
|
||||
if (glyphWidth > texDims - 1 || glyphHeight > texDims - 1)
|
||||
{
|
||||
throw new Exception(filename + ", " + size.ToString() + ", "+ (char)j + "; Glyph dimensions exceed texture atlas dimensions");
|
||||
}
|
||||
|
||||
nextY = Math.Max(nextY, glyphHeight + 2);
|
||||
|
||||
if (currentCoords.X + glyphWidth + 2 > texDims - 1)
|
||||
{
|
||||
currentCoords.X = 0;
|
||||
currentCoords.Y += nextY;
|
||||
nextY = 0;
|
||||
}
|
||||
if (currentCoords.Y + glyphHeight + 2 > texDims - 1)
|
||||
{
|
||||
currentCoords.X = 0;
|
||||
currentCoords.Y = 0;
|
||||
textures[texIndex].SetData<uint>(pixelBuffer);
|
||||
textures.Add(new Texture2D(gd, texDims, texDims, false, SurfaceFormat.Color));
|
||||
texIndex++;
|
||||
for (int k = 0; k < texDims * texDims; k++)
|
||||
{
|
||||
pixelBuffer[k] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
GlyphData newData = new GlyphData();
|
||||
newData.advance = (float)face.Glyph.Metrics.HorizontalAdvance;
|
||||
newData.texIndex = texIndex;
|
||||
newData.texCoords = new Rectangle((int)currentCoords.X, (int)currentCoords.Y, glyphWidth, glyphHeight);
|
||||
newData.drawOffset = new Vector2(face.Glyph.BitmapLeft, baseHeight*14/10 - face.Glyph.BitmapTop);
|
||||
texCoords.Add(j, newData);
|
||||
|
||||
for (int y = 0; y < glyphHeight; y++)
|
||||
{
|
||||
for (int x = 0; x < glyphWidth; x++)
|
||||
{
|
||||
byte byteColor = bitmap[x + y * glyphWidth];
|
||||
pixelBuffer[((int)currentCoords.X + x) + ((int)currentCoords.Y + y) * texDims] = (uint)(byteColor << 24 | byteColor << 16 | byteColor << 8 | byteColor);
|
||||
}
|
||||
}
|
||||
|
||||
currentCoords.X += glyphWidth + 2;
|
||||
}
|
||||
textures[texIndex].SetData<uint>(pixelBuffer);
|
||||
}
|
||||
|
||||
graphicsDevice = gd;
|
||||
}
|
||||
|
||||
public void DrawString(SpriteBatch sb, string text, Vector2 position, Color color, float rotation, Vector2 origin, Vector2 scale, SpriteEffects se, float layerDepth)
|
||||
{
|
||||
if (textures.Count == 0) return;
|
||||
|
||||
int lineNum = 0;
|
||||
Vector2 currentPos = position;
|
||||
Vector2 advanceUnit = new Vector2((float)Math.Cos(rotation), (float)Math.Sin(rotation));
|
||||
for (int i = 0; i < text.Length; i++)
|
||||
{
|
||||
if (text[i]=='\n')
|
||||
{
|
||||
lineNum++;
|
||||
currentPos = position;
|
||||
currentPos.X -= baseHeight * 1.8f * lineNum * advanceUnit.Y * scale.Y;
|
||||
currentPos.Y += baseHeight * 1.8f * lineNum * advanceUnit.X * scale.Y;
|
||||
continue;
|
||||
}
|
||||
uint charIndex = text[i];
|
||||
GlyphData gd;
|
||||
if (texCoords.TryGetValue(charIndex, out gd))
|
||||
{
|
||||
if (gd.texIndex >= 0)
|
||||
{
|
||||
Texture2D tex = textures[gd.texIndex];
|
||||
Vector2 drawOffset;
|
||||
drawOffset.X = gd.drawOffset.X * advanceUnit.X * scale.X - gd.drawOffset.Y * advanceUnit.Y * scale.Y;
|
||||
drawOffset.Y = gd.drawOffset.X * advanceUnit.Y * scale.Y + gd.drawOffset.Y * advanceUnit.X * scale.X;
|
||||
|
||||
sb.Draw(tex, currentPos + drawOffset, null, gd.texCoords, origin, rotation, scale, color, se, layerDepth);
|
||||
}
|
||||
currentPos += gd.advance * advanceUnit * scale.X;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void DrawString(SpriteBatch sb, string text, Vector2 position, Color color, float rotation, Vector2 origin, float scale, SpriteEffects se, float layerDepth)
|
||||
{
|
||||
DrawString(sb, text, position, color, rotation, origin, new Vector2(scale), se, layerDepth);
|
||||
}
|
||||
|
||||
public void DrawString(SpriteBatch sb, string text, Vector2 position, Color color)
|
||||
{
|
||||
if (textures.Count == 0) return;
|
||||
|
||||
Vector2 currentPos = position;
|
||||
for (int i = 0; i < text.Length; i++)
|
||||
{
|
||||
if (text[i] == '\n')
|
||||
{
|
||||
currentPos.X = position.X;
|
||||
currentPos.Y += baseHeight * 1.8f;
|
||||
continue;
|
||||
}
|
||||
uint charIndex = text[i];
|
||||
GlyphData gd;
|
||||
if (texCoords.TryGetValue(charIndex, out gd))
|
||||
{
|
||||
if (gd.texIndex >= 0)
|
||||
{
|
||||
Texture2D tex = textures[gd.texIndex];
|
||||
sb.Draw(tex, currentPos + gd.drawOffset, gd.texCoords, color);
|
||||
}
|
||||
currentPos.X += gd.advance;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Vector2 MeasureString(string text)
|
||||
{
|
||||
float currentLineX = 0.0f;
|
||||
Vector2 retVal = Vector2.Zero;
|
||||
retVal.Y = baseHeight*1.8f;
|
||||
for (int i = 0; i < text.Length; i++)
|
||||
{
|
||||
if (text[i] == '\n')
|
||||
{
|
||||
currentLineX = 0.0f;
|
||||
retVal.Y += baseHeight*18/10;
|
||||
continue;
|
||||
}
|
||||
uint charIndex = text[i];
|
||||
GlyphData gd;
|
||||
if (texCoords.TryGetValue(charIndex, out gd))
|
||||
{
|
||||
currentLineX += gd.advance;
|
||||
}
|
||||
retVal.X = Math.Max(retVal.X,currentLineX);
|
||||
}
|
||||
return retVal;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
public class FrameCounter
|
||||
{
|
||||
public long TotalFrames { get; private set; }
|
||||
public double TotalSeconds { get; private set; }
|
||||
public double AverageFramesPerSecond { get; private set; }
|
||||
public double CurrentFramesPerSecond { get; private set; }
|
||||
|
||||
public const int MaximumSamples = 10;
|
||||
|
||||
private Queue<double> sampleBuffer = new Queue<double>();
|
||||
|
||||
public bool Update(double deltaTime)
|
||||
{
|
||||
//float deltaTime = stopwatch.ElapsedMilliseconds / 1000.0f;
|
||||
|
||||
if (deltaTime == 0.0f) { return false; }
|
||||
//stopwatch.Restart();
|
||||
|
||||
CurrentFramesPerSecond = (1.0 / deltaTime);
|
||||
|
||||
sampleBuffer.Enqueue(CurrentFramesPerSecond);
|
||||
|
||||
if (sampleBuffer.Count > MaximumSamples)
|
||||
{
|
||||
sampleBuffer.Dequeue();
|
||||
AverageFramesPerSecond = sampleBuffer.Average(i => i);
|
||||
}
|
||||
else
|
||||
{
|
||||
AverageFramesPerSecond = CurrentFramesPerSecond;
|
||||
}
|
||||
|
||||
if (AverageFramesPerSecond < 0 || AverageFramesPerSecond > 500) { }
|
||||
|
||||
TotalFrames++;
|
||||
TotalSeconds += deltaTime;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
public class UISprite
|
||||
{
|
||||
public Sprite Sprite
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public bool Tile
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public bool Slice
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public Rectangle[] Slices
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public bool MaintainAspectRatio
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public UISprite(Sprite sprite, bool tile, bool maintainAspectRatio)
|
||||
{
|
||||
Sprite = sprite;
|
||||
Tile = tile;
|
||||
MaintainAspectRatio = maintainAspectRatio;
|
||||
}
|
||||
}
|
||||
|
||||
public class GUIComponentStyle
|
||||
{
|
||||
public readonly Vector4 Padding;
|
||||
|
||||
public readonly Color Color;
|
||||
|
||||
public readonly Color textColor;
|
||||
|
||||
public readonly Color HoverColor;
|
||||
public readonly Color SelectedColor;
|
||||
|
||||
public readonly Color OutlineColor;
|
||||
|
||||
public readonly Dictionary<GUIComponent.ComponentState, List<UISprite>> Sprites;
|
||||
|
||||
public Dictionary<string, GUIComponentStyle> ChildStyles;
|
||||
|
||||
public GUIComponentStyle(XElement element)
|
||||
{
|
||||
Sprites = new Dictionary<GUIComponent.ComponentState, List<UISprite>>();
|
||||
foreach (GUIComponent.ComponentState state in Enum.GetValues(typeof(GUIComponent.ComponentState)))
|
||||
{
|
||||
Sprites[state] = new List<UISprite>();
|
||||
}
|
||||
|
||||
ChildStyles = new Dictionary<string, GUIComponentStyle>();
|
||||
|
||||
Padding = ToolBox.GetAttributeVector4(element, "padding", Vector4.Zero);
|
||||
|
||||
Vector4 colorVector = ToolBox.GetAttributeVector4(element, "color", new Vector4(0.0f, 0.0f, 0.0f, 0.0f));
|
||||
Color = new Color(colorVector.X, colorVector.Y, colorVector.Z, colorVector.W);
|
||||
|
||||
colorVector = ToolBox.GetAttributeVector4(element, "textcolor", new Vector4(0.0f, 0.0f, 0.0f, 1.0f));
|
||||
textColor = new Color(colorVector.X, colorVector.Y, colorVector.Z, colorVector.W);
|
||||
|
||||
colorVector = ToolBox.GetAttributeVector4(element, "hovercolor", new Vector4(0.0f, 0.0f, 0.0f, 0.0f));
|
||||
HoverColor = new Color(colorVector.X, colorVector.Y, colorVector.Z, colorVector.W);
|
||||
|
||||
colorVector = ToolBox.GetAttributeVector4(element, "selectedcolor", new Vector4(0.0f, 0.0f, 0.0f, 0.0f));
|
||||
SelectedColor = new Color(colorVector.X, colorVector.Y, colorVector.Z, colorVector.W);
|
||||
|
||||
colorVector = ToolBox.GetAttributeVector4(element, "outlinecolor", new Vector4(0.0f, 0.0f, 0.0f, 0.0f));
|
||||
OutlineColor = new Color(colorVector.X, colorVector.Y, colorVector.Z, colorVector.W);
|
||||
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "sprite":
|
||||
Sprite sprite = new Sprite(subElement);
|
||||
bool maintainAspect = ToolBox.GetAttributeBool(subElement, "maintainaspectratio",false);
|
||||
bool tile = ToolBox.GetAttributeBool(subElement, "tile", true);
|
||||
|
||||
string stateStr = ToolBox.GetAttributeString(subElement, "state", "None");
|
||||
GUIComponent.ComponentState spriteState = GUIComponent.ComponentState.None;
|
||||
Enum.TryParse(stateStr, out spriteState);
|
||||
|
||||
UISprite newSprite = new UISprite(sprite, tile, maintainAspect);
|
||||
|
||||
Vector4 sliceVec = ToolBox.GetAttributeVector4(subElement, "slice", Vector4.Zero);
|
||||
if (sliceVec != Vector4.Zero)
|
||||
{
|
||||
Rectangle slice = new Rectangle((int)sliceVec.X, (int)sliceVec.Y, (int)(sliceVec.Z - sliceVec.X), (int)(sliceVec.W - sliceVec.Y));
|
||||
|
||||
newSprite.Slice = true;
|
||||
|
||||
newSprite.Slices = new Rectangle[9];
|
||||
|
||||
//top-left
|
||||
newSprite.Slices[0] = new Rectangle(newSprite.Sprite.SourceRect.Location, slice.Location - newSprite.Sprite.SourceRect.Location);
|
||||
//top-mid
|
||||
newSprite.Slices[1] = new Rectangle(slice.Location.X, newSprite.Slices[0].Y, slice.Width, newSprite.Slices[0].Height);
|
||||
//top-right
|
||||
newSprite.Slices[2] = new Rectangle(slice.Right, newSprite.Slices[0].Y, newSprite.Sprite.SourceRect.Right - slice.Right, newSprite.Slices[0].Height);
|
||||
|
||||
//mid-left
|
||||
newSprite.Slices[3] = new Rectangle(newSprite.Slices[0].X, slice.Y, newSprite.Slices[0].Width, slice.Height);
|
||||
//center
|
||||
newSprite.Slices[4] = slice;
|
||||
//mid-right
|
||||
newSprite.Slices[5] = new Rectangle(newSprite.Slices[2].X, slice.Y, newSprite.Slices[2].Width, slice.Height);
|
||||
|
||||
//bottom-left
|
||||
newSprite.Slices[6] = new Rectangle(newSprite.Slices[0].X, slice.Bottom, newSprite.Slices[0].Width, newSprite.Sprite.SourceRect.Bottom - slice.Bottom);
|
||||
//bottom-mid
|
||||
newSprite.Slices[7] = new Rectangle(newSprite.Slices[1].X, slice.Bottom, newSprite.Slices[1].Width, newSprite.Sprite.SourceRect.Bottom - slice.Bottom);
|
||||
//bottom-right
|
||||
newSprite.Slices[8] = new Rectangle(newSprite.Slices[2].X, slice.Bottom, newSprite.Slices[2].Width, newSprite.Sprite.SourceRect.Bottom - slice.Bottom);
|
||||
}
|
||||
|
||||
Sprites[spriteState].Add(newSprite);
|
||||
break;
|
||||
default:
|
||||
ChildStyles.Add(subElement.Name.ToString().ToLowerInvariant(), new GUIComponentStyle(subElement));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,615 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Content;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
[Flags]
|
||||
public enum Alignment
|
||||
{
|
||||
CenterX = 1, Left = 2, Right = 4, CenterY = 8, Top = 16, Bottom = 32,
|
||||
TopLeft = (Top | Left), TopCenter = (CenterX | Top), TopRight = (Top | Right),
|
||||
CenterLeft = (Left | CenterY), Center = (CenterX | CenterY), CenterRight = (Right | CenterY),
|
||||
BottomLeft = (Bottom | Left), BottomCenter = (CenterX | Bottom), BottomRight = (Bottom | Right),
|
||||
}
|
||||
|
||||
public enum GUISoundType
|
||||
{
|
||||
Message,
|
||||
RadioMessage,
|
||||
DeadMessage,
|
||||
Click,
|
||||
Inventory,
|
||||
}
|
||||
|
||||
public class GUI
|
||||
{
|
||||
|
||||
public static GUIStyle Style;
|
||||
|
||||
private static Texture2D t;
|
||||
public static ScalableFont Font, SmallFont, LargeFont;
|
||||
|
||||
private static Sprite cursor;
|
||||
|
||||
private static GraphicsDevice graphicsDevice;
|
||||
public static GraphicsDevice GraphicsDevice
|
||||
{
|
||||
get
|
||||
{
|
||||
return graphicsDevice;
|
||||
}
|
||||
set
|
||||
{
|
||||
graphicsDevice = value;
|
||||
}
|
||||
}
|
||||
|
||||
private static List<GUIMessage> messages = new List<GUIMessage>();
|
||||
|
||||
private static Sound[] sounds;
|
||||
|
||||
private static bool pauseMenuOpen, settingsMenuOpen;
|
||||
private static GUIFrame pauseMenu;
|
||||
|
||||
public static Color ScreenOverlayColor;
|
||||
|
||||
private static Sprite submarineIcon, arrow;
|
||||
|
||||
public static Sprite SubmarineIcon
|
||||
{
|
||||
get { return submarineIcon; }
|
||||
}
|
||||
|
||||
public static Sprite SpeechBubbleIcon
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public static Sprite Arrow
|
||||
{
|
||||
get { return arrow; }
|
||||
}
|
||||
|
||||
public static bool DisableHUD;
|
||||
|
||||
public static void Init(ContentManager content)
|
||||
{
|
||||
Font = new ScalableFont("Content/Exo2-Medium.otf", 14, graphicsDevice);
|
||||
SmallFont = new ScalableFont("Content/Exo2-Light.otf", 12, graphicsDevice);
|
||||
LargeFont = new ScalableFont("Content/Code Pro Bold.otf", 22, graphicsDevice);
|
||||
|
||||
cursor = new Sprite("Content/UI/cursor.png", Vector2.Zero);
|
||||
|
||||
Style = new GUIStyle("Content/UI/style.xml");
|
||||
}
|
||||
|
||||
public static bool PauseMenuOpen
|
||||
{
|
||||
get { return pauseMenuOpen; }
|
||||
}
|
||||
|
||||
public static bool SettingsMenuOpen
|
||||
{
|
||||
get { return settingsMenuOpen; }
|
||||
}
|
||||
|
||||
public static void LoadContent(bool loadSounds = true)
|
||||
{
|
||||
if (loadSounds)
|
||||
{
|
||||
sounds = new Sound[Enum.GetValues(typeof(GUISoundType)).Length];
|
||||
sounds[(int)GUISoundType.Message] = Sound.Load("Content/Sounds/UI/UImsg.ogg", false);
|
||||
sounds[(int)GUISoundType.RadioMessage] = Sound.Load("Content/Sounds/UI/radiomsg.ogg", false);
|
||||
sounds[(int)GUISoundType.DeadMessage] = Sound.Load("Content/Sounds/UI/deadmsg.ogg", false);
|
||||
sounds[(int)GUISoundType.Click] = Sound.Load("Content/Sounds/UI/beep-shinymetal.ogg", false);
|
||||
|
||||
sounds[(int)GUISoundType.Inventory] = Sound.Load("Content/Sounds/pickItem.ogg", false);
|
||||
|
||||
}
|
||||
|
||||
// create 1x1 texture for line drawing
|
||||
t = new Texture2D(graphicsDevice, 1, 1);
|
||||
t.SetData(new Color[] { Color.White });// fill the texture with white
|
||||
|
||||
submarineIcon = new Sprite("Content/UI/uiIcons.png", new Rectangle(0, 192, 64, 64), null);
|
||||
submarineIcon.Origin = submarineIcon.size / 2;
|
||||
|
||||
arrow = new Sprite("Content/UI/uiIcons.png", new Rectangle(80, 240, 16, 16), null);
|
||||
arrow.Origin = arrow.size / 2;
|
||||
|
||||
SpeechBubbleIcon = new Sprite("Content/UI/uiIcons.png", new Rectangle(0, 129, 65, 61), null);
|
||||
SpeechBubbleIcon.Origin = SpeechBubbleIcon.size / 2;
|
||||
}
|
||||
|
||||
public static void TogglePauseMenu()
|
||||
{
|
||||
if (Screen.Selected == GameMain.MainMenuScreen) return;
|
||||
|
||||
settingsMenuOpen = false;
|
||||
|
||||
TogglePauseMenu(null, null);
|
||||
|
||||
if (pauseMenuOpen)
|
||||
{
|
||||
pauseMenu = new GUIFrame(new Rectangle(0, 0, 200, 300), null, Alignment.Center, "");
|
||||
|
||||
int y = 0;
|
||||
var button = new GUIButton(new Rectangle(0, y, 0, 30), "Resume", Alignment.CenterX, "", pauseMenu);
|
||||
button.OnClicked = TogglePauseMenu;
|
||||
|
||||
y += 60;
|
||||
|
||||
button = new GUIButton(new Rectangle(0, y, 0, 30), "Settings", Alignment.CenterX, "", pauseMenu);
|
||||
button.OnClicked = (btn, userData) =>
|
||||
{
|
||||
TogglePauseMenu();
|
||||
settingsMenuOpen = !settingsMenuOpen;
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
|
||||
y += 60;
|
||||
|
||||
if (Screen.Selected == GameMain.GameScreen && GameMain.GameSession != null)
|
||||
{
|
||||
SinglePlayerMode spMode = GameMain.GameSession.gameMode as SinglePlayerMode;
|
||||
if (spMode != null)
|
||||
{
|
||||
button = new GUIButton(new Rectangle(0, y, 0, 30), "Load previous", Alignment.CenterX, "", pauseMenu);
|
||||
button.OnClicked += TogglePauseMenu;
|
||||
button.OnClicked += GameMain.GameSession.LoadPrevious;
|
||||
|
||||
y += 60;
|
||||
}
|
||||
}
|
||||
|
||||
if (Screen.Selected == GameMain.LobbyScreen)
|
||||
{
|
||||
SinglePlayerMode spMode = GameMain.GameSession.gameMode as SinglePlayerMode;
|
||||
if (spMode != null)
|
||||
{
|
||||
button = new GUIButton(new Rectangle(0, y, 0, 30), "Save & quit", Alignment.CenterX, "", pauseMenu);
|
||||
button.OnClicked += QuitClicked;
|
||||
button.OnClicked += TogglePauseMenu;
|
||||
button.UserData = "save";
|
||||
|
||||
y += 60;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
button = new GUIButton(new Rectangle(0, y, 0, 30), "Quit", Alignment.CenterX, "", pauseMenu);
|
||||
button.OnClicked += QuitClicked;
|
||||
button.OnClicked += TogglePauseMenu;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static bool TogglePauseMenu(GUIButton button, object obj)
|
||||
{
|
||||
pauseMenuOpen = !pauseMenuOpen;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool QuitClicked(GUIButton button, object obj)
|
||||
{
|
||||
if (button.UserData as string == "save")
|
||||
{
|
||||
SaveUtil.SaveGame(GameMain.GameSession.SaveFile);
|
||||
}
|
||||
|
||||
if (GameMain.NetworkMember != null)
|
||||
{
|
||||
GameMain.NetworkMember.Disconnect();
|
||||
GameMain.NetworkMember = null;
|
||||
}
|
||||
|
||||
CoroutineManager.StopCoroutines("EndCinematic");
|
||||
|
||||
GameMain.GameSession = null;
|
||||
|
||||
GameMain.MainMenuScreen.Select();
|
||||
//Game1.MainMenuScreen.SelectTab(null, (int)MainMenuScreen.Tabs.Main);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void DrawLine(SpriteBatch sb, Vector2 start, Vector2 end, Color clr, float depth = 0.0f, int width = 1)
|
||||
{
|
||||
Vector2 edge = end - start;
|
||||
// calculate angle to rotate line
|
||||
float angle = (float)Math.Atan2(edge.Y, edge.X);
|
||||
|
||||
sb.Draw(t,
|
||||
new Rectangle(// rectangle defines shape of line and position of start of line
|
||||
(int)start.X,
|
||||
(int)start.Y,
|
||||
(int)edge.Length(), //sb will strech the texture to fill this rectangle
|
||||
width), //width of line, change this to make thicker line
|
||||
null,
|
||||
clr, //colour of line
|
||||
angle, //angle of line (calulated above)
|
||||
new Vector2(0, 0), // point in line about which to rotate
|
||||
SpriteEffects.None,
|
||||
depth);
|
||||
}
|
||||
|
||||
public static void DrawString(SpriteBatch sb, Vector2 pos, string text, Color color, Color? backgroundColor=null, int backgroundPadding=0, ScalableFont font = null)
|
||||
{
|
||||
|
||||
if (font == null) font = Font;
|
||||
if (backgroundColor != null)
|
||||
{
|
||||
Vector2 textSize = font.MeasureString(text);
|
||||
DrawRectangle(sb, pos - Vector2.One * backgroundPadding, textSize + Vector2.One * 2.0f * backgroundPadding, (Color)backgroundColor, true);
|
||||
}
|
||||
|
||||
font.DrawString(sb, text, pos, color);
|
||||
}
|
||||
|
||||
public static void DrawRectangle(SpriteBatch sb, Vector2 start, Vector2 size, Color clr, bool isFilled = false, float depth = 0.0f, int thickness = 1)
|
||||
{
|
||||
if (size.X < 0)
|
||||
{
|
||||
start.X += size.X;
|
||||
size.X = -size.X;
|
||||
}
|
||||
if (size.Y < 0)
|
||||
{
|
||||
start.Y += size.Y;
|
||||
size.Y = -size.Y;
|
||||
}
|
||||
DrawRectangle(sb, new Rectangle((int)start.X, (int)start.Y, (int)size.X, (int)size.Y), clr, isFilled, depth, thickness);
|
||||
}
|
||||
|
||||
public static void DrawRectangle(SpriteBatch sb, Rectangle rect, Color clr, bool isFilled = false, float depth = 0.0f, int thickness = 1)
|
||||
{
|
||||
if (isFilled)
|
||||
{
|
||||
sb.Draw(t, rect, null, clr, 0.0f, Vector2.Zero, SpriteEffects.None, depth);
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.Draw(t, new Rectangle(rect.X + thickness, rect.Y, rect.Width - thickness * 2, thickness), null, clr, 0.0f, Vector2.Zero, SpriteEffects.None, depth);
|
||||
sb.Draw(t, new Rectangle(rect.X + thickness, rect.Y + rect.Height - thickness, rect.Width - thickness * 2, thickness), null, clr, 0.0f, Vector2.Zero, SpriteEffects.None, depth);
|
||||
|
||||
sb.Draw(t, new Rectangle(rect.X, rect.Y, thickness, rect.Height), null, clr, 0.0f, Vector2.Zero, SpriteEffects.None, depth);
|
||||
sb.Draw(t, new Rectangle(rect.X + rect.Width - thickness, rect.Y, thickness, rect.Height), null, clr, 0.0f, Vector2.Zero, SpriteEffects.None, depth);
|
||||
}
|
||||
}
|
||||
|
||||
public static void DrawProgressBar(SpriteBatch sb, Vector2 start, Vector2 size, float progress, Color clr, float depth = 0.0f)
|
||||
{
|
||||
DrawProgressBar(sb, start, size, progress, clr, new Color(0.5f, 0.57f, 0.6f, 1.0f), depth);
|
||||
}
|
||||
|
||||
public static void DrawProgressBar(SpriteBatch sb, Vector2 start, Vector2 size, float progress, Color clr, Color outlineColor, float depth = 0.0f)
|
||||
{
|
||||
DrawRectangle(sb, new Vector2(start.X, -start.Y), size, outlineColor, false, depth);
|
||||
|
||||
int padding = 2;
|
||||
DrawRectangle(sb, new Rectangle((int)start.X + padding, -(int)(start.Y - padding), (int)((size.X - padding * 2) * progress), (int)size.Y - padding * 2),
|
||||
clr, true, depth);
|
||||
}
|
||||
|
||||
|
||||
public static Texture2D CreateCircle(int radius)
|
||||
{
|
||||
int outerRadius = radius * 2 + 2; // So circle doesn't go out of bounds
|
||||
Texture2D texture = new Texture2D(graphicsDevice, outerRadius, outerRadius);
|
||||
|
||||
Color[] data = new Color[outerRadius * outerRadius];
|
||||
|
||||
// Colour the entire texture transparent first.
|
||||
for (int i = 0; i < data.Length; i++)
|
||||
data[i] = Color.Transparent;
|
||||
|
||||
// Work out the minimum step necessary using trigonometry + sine approximation.
|
||||
double angleStep = 1f / radius;
|
||||
|
||||
for (double angle = 0; angle < Math.PI * 2; angle += angleStep)
|
||||
{
|
||||
// Use the parametric definition of a circle: http://en.wikipedia.org/wiki/Circle#Cartesian_coordinates
|
||||
int x = (int)Math.Round(radius + radius * Math.Cos(angle));
|
||||
int y = (int)Math.Round(radius + radius * Math.Sin(angle));
|
||||
|
||||
data[y * outerRadius + x + 1] = Color.White;
|
||||
}
|
||||
|
||||
texture.SetData(data);
|
||||
return texture;
|
||||
}
|
||||
|
||||
public static Texture2D CreateCapsule(int radius, int height)
|
||||
{
|
||||
int textureWidth = radius * 2, textureHeight = height + radius * 2;
|
||||
|
||||
Texture2D texture = new Texture2D(graphicsDevice, textureWidth, textureHeight);
|
||||
Color[] data = new Color[textureWidth * textureHeight];
|
||||
|
||||
// Colour the entire texture transparent first.
|
||||
for (int i = 0; i < data.Length; i++)
|
||||
data[i] = Color.Transparent;
|
||||
|
||||
// Work out the minimum step necessary using trigonometry + sine approximation.
|
||||
double angleStep = 1f / radius;
|
||||
|
||||
for (int i = 0; i < 2; i++ )
|
||||
{
|
||||
for (double angle = 0; angle < Math.PI * 2; angle += angleStep)
|
||||
{
|
||||
// Use the parametric definition of a circle: http://en.wikipedia.org/wiki/Circle#Cartesian_coordinates
|
||||
int x = (int)Math.Round(radius + radius * Math.Cos(angle));
|
||||
int y = (height-1)*i + (int)Math.Round(radius + radius * Math.Sin(angle));
|
||||
|
||||
data[y * textureWidth + x] = Color.White;
|
||||
}
|
||||
}
|
||||
|
||||
for (int y = radius; y<textureHeight-radius; y++)
|
||||
{
|
||||
data[y * textureWidth] = Color.White;
|
||||
data[y * textureWidth + (textureWidth-1)] = Color.White;
|
||||
}
|
||||
|
||||
texture.SetData(data);
|
||||
return texture;
|
||||
}
|
||||
|
||||
public static Texture2D CreateRectangle(int width, int height)
|
||||
{
|
||||
Texture2D texture = new Texture2D(graphicsDevice, width, height);
|
||||
Color[] data = new Color[width * height];
|
||||
|
||||
for (int i = 0; i < data.Length; i++)
|
||||
data[i] = Color.Transparent;
|
||||
|
||||
for (int y = 0; y < height; y++)
|
||||
{
|
||||
data[y * width] = Color.White;
|
||||
data[y * width + (width-1)] = Color.White;
|
||||
}
|
||||
|
||||
for (int x = 0; x < width; x++)
|
||||
{
|
||||
data[x] = Color.White;
|
||||
data[(height - 1) * width + x] = Color.White;
|
||||
}
|
||||
|
||||
texture.SetData(data);
|
||||
return texture;
|
||||
}
|
||||
|
||||
public static bool DrawButton(SpriteBatch sb, Rectangle rect, string text, Color color, bool isHoldable = false)
|
||||
{
|
||||
bool clicked = false;
|
||||
|
||||
if (rect.Contains(PlayerInput.MousePosition))
|
||||
{
|
||||
clicked = PlayerInput.LeftButtonHeld();
|
||||
|
||||
color = clicked ?
|
||||
new Color((int)(color.R * 0.8f), (int)(color.G * 0.8f), (int)(color.B * 0.8f), color.A) :
|
||||
new Color((int)(color.R * 1.2f), (int)(color.G * 1.2f), (int)(color.B * 1.2f), color.A);
|
||||
|
||||
if (!isHoldable) clicked = PlayerInput.LeftButtonClicked();
|
||||
}
|
||||
|
||||
DrawRectangle(sb, rect, color, true);
|
||||
|
||||
Vector2 origin;
|
||||
try
|
||||
{
|
||||
origin = Font.MeasureString(text)/2;
|
||||
}
|
||||
catch
|
||||
{
|
||||
origin = Vector2.Zero;
|
||||
}
|
||||
|
||||
Font.DrawString(sb, text, new Vector2(rect.Center.X, rect.Center.Y) , Color.White, 0.0f, origin, 1.0f, SpriteEffects.None, 0.0f);
|
||||
|
||||
return clicked;
|
||||
}
|
||||
|
||||
public static void Draw(float deltaTime, SpriteBatch spriteBatch, Camera cam)
|
||||
{
|
||||
if (ScreenOverlayColor.A>0.0f)
|
||||
{
|
||||
DrawRectangle(
|
||||
spriteBatch,
|
||||
new Rectangle(0, 0, GameMain.GraphicsWidth, GameMain.GraphicsHeight),
|
||||
ScreenOverlayColor, true);
|
||||
}
|
||||
|
||||
if (GameMain.DebugDraw)
|
||||
{
|
||||
DrawString(spriteBatch, new Vector2(10, 10),
|
||||
"FPS: " + (int)GameMain.FrameCounter.AverageFramesPerSecond,
|
||||
Color.White, Color.Black * 0.5f, 0, SmallFont);
|
||||
|
||||
DrawString(spriteBatch, new Vector2(10, 25),
|
||||
"Physics: " + GameMain.World.UpdateTime,
|
||||
Color.White, Color.Black * 0.5f, 0, SmallFont);
|
||||
|
||||
DrawString(spriteBatch, new Vector2(10, 40),
|
||||
"Bodies: " + GameMain.World.BodyList.Count + " (" + GameMain.World.BodyList.FindAll(b => b.Awake && b.Enabled).Count + " awake)",
|
||||
Color.White, Color.Black * 0.5f, 0, SmallFont);
|
||||
|
||||
if (Screen.Selected.Cam != null)
|
||||
{
|
||||
DrawString(spriteBatch, new Vector2(10, 55),
|
||||
"Camera pos: " + Screen.Selected.Cam.Position.ToPoint(),
|
||||
Color.White, Color.Black * 0.5f, 0, SmallFont);
|
||||
}
|
||||
|
||||
if (Submarine.MainSub != null)
|
||||
{
|
||||
DrawString(spriteBatch, new Vector2(10, 70),
|
||||
"Sub pos: " + Submarine.MainSub.Position.ToPoint(),
|
||||
Color.White, Color.Black * 0.5f, 0, SmallFont);
|
||||
}
|
||||
|
||||
for (int i = 1; i < Sounds.SoundManager.DefaultSourceCount; i++)
|
||||
{
|
||||
Color clr = Color.White;
|
||||
|
||||
string soundStr = i+": ";
|
||||
|
||||
var playingSound = Sounds.SoundManager.GetPlayingSound(i);
|
||||
|
||||
if (playingSound == null)
|
||||
{
|
||||
soundStr+= "none";
|
||||
clr *= 0.5f;
|
||||
}
|
||||
else
|
||||
{
|
||||
soundStr += System.IO.Path.GetFileNameWithoutExtension(playingSound.FilePath);
|
||||
|
||||
if (Sounds.SoundManager.IsLooping(i))
|
||||
{
|
||||
soundStr += " (looping)";
|
||||
clr = Color.Yellow;
|
||||
}
|
||||
}
|
||||
|
||||
GUI.DrawString(spriteBatch, new Vector2(200, i * 15), soundStr, clr, Color.Black * 0.5f, 0, GUI.SmallFont);
|
||||
}
|
||||
}
|
||||
|
||||
if (GameMain.NetworkMember != null) GameMain.NetworkMember.Draw(spriteBatch);
|
||||
|
||||
DrawMessages(spriteBatch, (float)deltaTime);
|
||||
|
||||
if (GUIMessageBox.VisibleBox != null)
|
||||
{
|
||||
GUIMessageBox.VisibleBox.Draw(spriteBatch);
|
||||
}
|
||||
|
||||
if (pauseMenuOpen)
|
||||
{
|
||||
pauseMenu.Draw(spriteBatch);
|
||||
}
|
||||
|
||||
if (settingsMenuOpen)
|
||||
{
|
||||
GameMain.Config.SettingsFrame.Draw(spriteBatch);
|
||||
}
|
||||
|
||||
DebugConsole.Draw(spriteBatch);
|
||||
|
||||
if (GUIComponent.MouseOn != null && !string.IsNullOrWhiteSpace(GUIComponent.MouseOn.ToolTip)) GUIComponent.MouseOn.DrawToolTip(spriteBatch);
|
||||
|
||||
if (!GUI.DisableHUD)
|
||||
cursor.Draw(spriteBatch, PlayerInput.LatestMousePosition);
|
||||
}
|
||||
|
||||
public static void AddToGUIUpdateList()
|
||||
{
|
||||
if (pauseMenuOpen)
|
||||
{
|
||||
pauseMenu.AddToGUIUpdateList();
|
||||
}
|
||||
|
||||
if (settingsMenuOpen)
|
||||
{
|
||||
GameMain.Config.SettingsFrame.AddToGUIUpdateList();
|
||||
}
|
||||
|
||||
if (GUIMessageBox.VisibleBox != null)
|
||||
{
|
||||
GUIMessageBox.VisibleBox.AddToGUIUpdateList();
|
||||
}
|
||||
}
|
||||
|
||||
public static void Update(float deltaTime)
|
||||
{
|
||||
if (pauseMenuOpen)
|
||||
{
|
||||
pauseMenu.Update(deltaTime);
|
||||
}
|
||||
|
||||
if (settingsMenuOpen)
|
||||
{
|
||||
GameMain.Config.SettingsFrame.Update(deltaTime);
|
||||
}
|
||||
|
||||
if (GUIMessageBox.VisibleBox != null)
|
||||
{
|
||||
GUIMessageBox.VisibleBox.Update(deltaTime);
|
||||
}
|
||||
}
|
||||
|
||||
public static void AddMessage(string message, Color color, float lifeTime = 3.0f, bool playSound = true)
|
||||
{
|
||||
if (messages.Count>0 && messages[messages.Count-1].Text == message)
|
||||
{
|
||||
messages[messages.Count - 1].LifeTime = lifeTime;
|
||||
return;
|
||||
}
|
||||
|
||||
Vector2 currPos = new Vector2(GameMain.GraphicsWidth / 2.0f, GameMain.GraphicsHeight * 0.7f);
|
||||
currPos.Y += messages.Count * 30;
|
||||
|
||||
messages.Add(new GUIMessage(message, color, currPos, lifeTime));
|
||||
if (playSound) PlayUISound(GUISoundType.Message);
|
||||
}
|
||||
|
||||
public static void PlayUISound(GUISoundType soundType)
|
||||
{
|
||||
if (sounds == null) return;
|
||||
|
||||
int soundIndex = (int)soundType;
|
||||
if (soundIndex < 0 || soundIndex >= sounds.Length) return;
|
||||
|
||||
sounds[soundIndex].Play();
|
||||
}
|
||||
|
||||
private static void DrawMessages(SpriteBatch spriteBatch, float deltaTime)
|
||||
{
|
||||
if (messages.Count == 0) return;
|
||||
|
||||
Vector2 currPos = new Vector2(GameMain.GraphicsWidth / 2.0f, GameMain.GraphicsHeight * 0.7f);
|
||||
|
||||
int i = 1;
|
||||
foreach (GUIMessage msg in messages)
|
||||
{
|
||||
float alpha = 1.0f;
|
||||
|
||||
if (msg.LifeTime < 1.0f)
|
||||
{
|
||||
alpha -= 1.0f - msg.LifeTime;
|
||||
}
|
||||
|
||||
msg.Pos = MathUtils.SmoothStep(msg.Pos, currPos, deltaTime*20.0f);
|
||||
|
||||
Font.DrawString(spriteBatch, msg.Text,
|
||||
new Vector2((int)msg.Pos.X - 1, (int)msg.Pos.Y - 1),
|
||||
Color.Black * alpha*0.5f, 0.0f,
|
||||
new Vector2((int)(0.5f * msg.Size.X), (int)(0.5f * msg.Size.Y)), 1.0f, SpriteEffects.None, 0.0f);
|
||||
|
||||
Font.DrawString(spriteBatch, msg.Text,
|
||||
new Vector2((int)msg.Pos.X, (int)msg.Pos.Y),
|
||||
msg.Color * alpha, 0.0f,
|
||||
new Vector2((int)(0.5f * msg.Size.X), (int)(0.5f * msg.Size.Y)), 1.0f, SpriteEffects.None, 0.0f);
|
||||
|
||||
|
||||
|
||||
currPos.Y += 30.0f;
|
||||
|
||||
messages[0].LifeTime -= deltaTime/i;
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
if (messages[0].LifeTime <= 0.0f) messages.Remove(messages[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,227 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
public class GUIButton : GUIComponent
|
||||
{
|
||||
protected GUITextBlock textBlock;
|
||||
protected GUIFrame frame;
|
||||
|
||||
public delegate bool OnClickedHandler(GUIButton button, object obj);
|
||||
public OnClickedHandler OnClicked;
|
||||
|
||||
public delegate bool OnPressedHandler();
|
||||
public OnPressedHandler OnPressed;
|
||||
|
||||
public bool CanBeSelected = true;
|
||||
|
||||
private bool enabled;
|
||||
|
||||
public bool Enabled
|
||||
{
|
||||
get
|
||||
{
|
||||
return enabled;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (value == enabled) return;
|
||||
|
||||
enabled = value;
|
||||
frame.Color = enabled ? color : Color.Gray * 0.7f;
|
||||
}
|
||||
}
|
||||
|
||||
public override Color Color
|
||||
{
|
||||
get { return base.Color; }
|
||||
set
|
||||
{
|
||||
base.Color = value;
|
||||
frame.Color = value;
|
||||
}
|
||||
}
|
||||
|
||||
public override Color HoverColor
|
||||
{
|
||||
get { return base.HoverColor; }
|
||||
set
|
||||
{
|
||||
base.HoverColor = value;
|
||||
frame.HoverColor = value;
|
||||
}
|
||||
}
|
||||
|
||||
public override Color SelectedColor
|
||||
{
|
||||
get
|
||||
{
|
||||
return base.SelectedColor;
|
||||
}
|
||||
set
|
||||
{
|
||||
base.SelectedColor = value;
|
||||
frame.SelectedColor = value;
|
||||
}
|
||||
}
|
||||
|
||||
public override Color OutlineColor
|
||||
{
|
||||
get { return base.OutlineColor; }
|
||||
set
|
||||
{
|
||||
base.OutlineColor = value;
|
||||
if (frame != null) frame.OutlineColor = value;
|
||||
}
|
||||
}
|
||||
|
||||
public Color TextColor
|
||||
{
|
||||
get { return textBlock.TextColor; }
|
||||
set { textBlock.TextColor = value; }
|
||||
}
|
||||
|
||||
public override ScalableFont Font
|
||||
{
|
||||
get
|
||||
{
|
||||
return (textBlock==null) ? GUI.Font : textBlock.Font;
|
||||
}
|
||||
set
|
||||
{
|
||||
base.Font = value;
|
||||
if (textBlock != null) textBlock.Font = value;
|
||||
}
|
||||
}
|
||||
|
||||
public string Text
|
||||
{
|
||||
get { return textBlock.Text; }
|
||||
set { textBlock.Text = value; }
|
||||
}
|
||||
|
||||
public override string ToolTip
|
||||
{
|
||||
get
|
||||
{
|
||||
return base.ToolTip;
|
||||
}
|
||||
set
|
||||
{
|
||||
textBlock.ToolTip = value;
|
||||
base.ToolTip = value;
|
||||
}
|
||||
}
|
||||
|
||||
public override Rectangle Rect
|
||||
{
|
||||
get
|
||||
{
|
||||
return rect;
|
||||
}
|
||||
set
|
||||
{
|
||||
base.Rect = value;
|
||||
|
||||
frame.Rect = new Rectangle(value.X, value.Y, frame.Rect.Width, frame.Rect.Height);
|
||||
textBlock.Rect = value;
|
||||
}
|
||||
}
|
||||
|
||||
public bool Selected { get; set; }
|
||||
|
||||
public GUIButton(Rectangle rect, string text, string style, GUIComponent parent = null)
|
||||
: this(rect, text, null, Alignment.Left, style, parent)
|
||||
{
|
||||
}
|
||||
|
||||
public GUIButton(Rectangle rect, string text, Alignment alignment, string style, GUIComponent parent = null)
|
||||
: this(rect, text, null, alignment, style, parent)
|
||||
{
|
||||
}
|
||||
|
||||
public GUIButton(Rectangle rect, string text, Color? color, string style, GUIComponent parent = null)
|
||||
: this(rect, text, color, (Alignment.Left | Alignment.Top), style, parent)
|
||||
{
|
||||
}
|
||||
|
||||
public GUIButton(Rectangle rect, string text, Color? color, Alignment alignment, string style = "", GUIComponent parent = null)
|
||||
: this(rect, text, color, alignment, Alignment.Center, style, parent)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public GUIButton(Rectangle rect, string text, Color? color, Alignment alignment, Alignment textAlignment, string style = "", GUIComponent parent = null)
|
||||
: base(style)
|
||||
{
|
||||
this.rect = rect;
|
||||
if (color != null) this.color = (Color)color;
|
||||
this.alignment = alignment;
|
||||
|
||||
if (parent != null) parent.AddChild(this);
|
||||
|
||||
frame = new GUIFrame(Rectangle.Empty, style, this);
|
||||
GUI.Style.Apply(frame, style == "" ? "GUIButton" : style);
|
||||
|
||||
textBlock = new GUITextBlock(Rectangle.Empty, text,
|
||||
Color.Transparent, (this.style == null) ? Color.Black : this.style.textColor,
|
||||
textAlignment, null, this);
|
||||
GUI.Style.Apply(textBlock, style, this);
|
||||
|
||||
Enabled = true;
|
||||
}
|
||||
|
||||
public override void ApplyStyle(GUIComponentStyle style)
|
||||
{
|
||||
base.ApplyStyle(style);
|
||||
|
||||
if (frame != null) frame.ApplyStyle(style);
|
||||
if (textBlock != null) textBlock.ApplyStyle(style);
|
||||
}
|
||||
|
||||
public override void Draw(SpriteBatch spriteBatch)
|
||||
{
|
||||
if (!Visible) return;
|
||||
|
||||
DrawChildren(spriteBatch);
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
if (!Visible) return;
|
||||
base.Update(deltaTime);
|
||||
if (rect.Contains(PlayerInput.MousePosition) && CanBeSelected && Enabled && (MouseOn == null || MouseOn == this || IsParentOf(MouseOn)))
|
||||
{
|
||||
state = ComponentState.Hover;
|
||||
if (PlayerInput.LeftButtonHeld())
|
||||
{
|
||||
if (OnPressed != null)
|
||||
{
|
||||
if (OnPressed()) state = ComponentState.Pressed;
|
||||
}
|
||||
}
|
||||
else if (PlayerInput.LeftButtonClicked())
|
||||
{
|
||||
GUI.PlayUISound(GUISoundType.Click);
|
||||
if (OnClicked != null)
|
||||
{
|
||||
if (OnClicked(this, UserData) && CanBeSelected) state = ComponentState.Selected;
|
||||
}
|
||||
else
|
||||
{
|
||||
Selected = !Selected;
|
||||
// state = state == ComponentState.Selected ? ComponentState.None : ComponentState.Selected;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
state = Selected ? ComponentState.Selected : ComponentState.None;
|
||||
}
|
||||
frame.State = state;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,543 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using EventInput;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
public abstract class GUIComponent
|
||||
{
|
||||
const float FlashDuration = 1.5f;
|
||||
|
||||
public static GUIComponent MouseOn
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
public static void ForceMouseOn(GUIComponent c)
|
||||
{
|
||||
MouseOn = c;
|
||||
}
|
||||
|
||||
protected static List<GUIComponent> ComponentsToUpdate = new List<GUIComponent>();
|
||||
|
||||
public virtual void AddToGUIUpdateList()
|
||||
{
|
||||
if (!Visible) return;
|
||||
if (ComponentsToUpdate.Contains(this)) return;
|
||||
ComponentsToUpdate.Add(this);
|
||||
|
||||
try
|
||||
{
|
||||
List<GUIComponent> fixedChildren = new List<GUIComponent>(children);
|
||||
foreach (GUIComponent c in fixedChildren)
|
||||
{
|
||||
c.AddToGUIUpdateList();
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
DebugConsole.NewMessage("Error in AddToGUIUPdateList! GUIComponent runtime type: "+this.GetType().ToString()+"; children count: "+children.Count.ToString(),Color.Red);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public static void ClearUpdateList()
|
||||
{
|
||||
if (keyboardDispatcher != null &&
|
||||
KeyboardDispatcher.Subscriber is GUIComponent &&
|
||||
!ComponentsToUpdate.Contains((GUIComponent)KeyboardDispatcher.Subscriber))
|
||||
{
|
||||
KeyboardDispatcher.Subscriber = null;
|
||||
}
|
||||
|
||||
ComponentsToUpdate.Clear();
|
||||
}
|
||||
|
||||
public static GUIComponent UpdateMouseOn()
|
||||
{
|
||||
MouseOn = null;
|
||||
for (int i=ComponentsToUpdate.Count-1;i>=0;i--)
|
||||
{
|
||||
GUIComponent c = ComponentsToUpdate[i];
|
||||
if (c.MouseRect.Contains(PlayerInput.MousePosition))
|
||||
{
|
||||
MouseOn = c;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return MouseOn;
|
||||
}
|
||||
|
||||
protected static KeyboardDispatcher keyboardDispatcher;
|
||||
|
||||
public enum ComponentState { None, Hover, Pressed, Selected };
|
||||
|
||||
protected Alignment alignment;
|
||||
|
||||
protected GUIComponentStyle style;
|
||||
|
||||
protected object userData;
|
||||
|
||||
protected Rectangle rect;
|
||||
|
||||
public bool CanBeFocused;
|
||||
|
||||
protected Vector4 padding;
|
||||
|
||||
protected Color color;
|
||||
protected Color hoverColor;
|
||||
protected Color selectedColor;
|
||||
|
||||
protected GUIComponent parent;
|
||||
public List<GUIComponent> children;
|
||||
|
||||
protected ComponentState state;
|
||||
|
||||
protected Color flashColor;
|
||||
protected float flashTimer;
|
||||
|
||||
public virtual ScalableFont Font
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public virtual string ToolTip
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public GUIComponentStyle Style
|
||||
{
|
||||
get { return style; }
|
||||
}
|
||||
|
||||
public bool Visible
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public bool TileSprites;
|
||||
|
||||
private static GUITextBlock toolTipBlock;
|
||||
|
||||
//protected float alpha;
|
||||
|
||||
public GUIComponent Parent
|
||||
{
|
||||
get { return parent; }
|
||||
}
|
||||
|
||||
public Vector2 Center
|
||||
{
|
||||
get { return new Vector2(rect.Center.X, rect.Center.Y); }
|
||||
}
|
||||
|
||||
public virtual Rectangle Rect
|
||||
{
|
||||
get { return rect; }
|
||||
set
|
||||
{
|
||||
int prevX = rect.X, prevY = rect.Y;
|
||||
int prevWidth = rect.Width, prevHeight = rect.Height;
|
||||
|
||||
rect = value;
|
||||
|
||||
if (prevX == rect.X && prevY == rect.Y && rect.Width == prevWidth && rect.Height == prevHeight) return;
|
||||
|
||||
foreach (GUIComponent child in children)
|
||||
{
|
||||
child.Rect = new Rectangle(
|
||||
child.rect.X + (rect.X - prevX),
|
||||
child.rect.Y + (rect.Y - prevY),
|
||||
Math.Max(child.rect.Width + (rect.Width - prevWidth),0),
|
||||
Math.Max(child.rect.Height + (rect.Height - prevHeight),0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public virtual Rectangle MouseRect
|
||||
{
|
||||
get { return CanBeFocused ? rect : Rectangle.Empty; }
|
||||
}
|
||||
|
||||
public Dictionary<GUIComponent.ComponentState, List<UISprite>> sprites;
|
||||
//public Alignment SpriteAlignment { get; set; }
|
||||
//public bool RepeatSpriteX, RepeatSpriteY;
|
||||
|
||||
public virtual Color OutlineColor { get; set; }
|
||||
|
||||
public ComponentState State
|
||||
{
|
||||
get { return state; }
|
||||
set { state = value; }
|
||||
}
|
||||
|
||||
public object UserData
|
||||
{
|
||||
get { return userData; }
|
||||
set { userData = value; }
|
||||
}
|
||||
|
||||
public virtual Vector4 Padding
|
||||
{
|
||||
get { return padding; }
|
||||
set { padding = value; }
|
||||
}
|
||||
|
||||
public int CountChildren
|
||||
{
|
||||
get { return children.Count; }
|
||||
}
|
||||
|
||||
public virtual Color Color
|
||||
{
|
||||
get { return color; }
|
||||
set { color = value; }
|
||||
}
|
||||
|
||||
public virtual Color HoverColor
|
||||
{
|
||||
get { return hoverColor; }
|
||||
set { hoverColor = value; }
|
||||
}
|
||||
|
||||
public virtual Color SelectedColor
|
||||
{
|
||||
get { return selectedColor; }
|
||||
set { selectedColor = value; }
|
||||
}
|
||||
|
||||
public static KeyboardDispatcher KeyboardDispatcher
|
||||
{
|
||||
get { return keyboardDispatcher; }
|
||||
}
|
||||
|
||||
protected GUIComponent(string style)
|
||||
{
|
||||
Visible = true;
|
||||
|
||||
TileSprites = true;
|
||||
|
||||
OutlineColor = Color.Transparent;
|
||||
|
||||
Font = GUI.Font;
|
||||
|
||||
children = new List<GUIComponent>();
|
||||
|
||||
CanBeFocused = true;
|
||||
|
||||
if (style != null)
|
||||
GUI.Style.Apply(this, style);
|
||||
}
|
||||
|
||||
public static void Init(GameWindow window)
|
||||
{
|
||||
keyboardDispatcher = new KeyboardDispatcher(window);
|
||||
}
|
||||
|
||||
public T GetChild<T>() where T : GUIComponent
|
||||
{
|
||||
foreach (GUIComponent child in children)
|
||||
{
|
||||
if (child is T) return (T)(object)child;
|
||||
}
|
||||
|
||||
return default(T);
|
||||
}
|
||||
|
||||
public GUIComponent GetChild(object obj)
|
||||
{
|
||||
foreach (GUIComponent child in children)
|
||||
{
|
||||
if (child.UserData == obj) return child;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public bool IsParentOf(GUIComponent component)
|
||||
{
|
||||
for(int i = children.Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (children[i] == component) return true;
|
||||
if (children[i].IsParentOf(component)) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public virtual void Flash(Color? color = null)
|
||||
{
|
||||
flashTimer = FlashDuration;
|
||||
flashColor = (color == null) ? Color.Red * 0.8f : (Color)color;
|
||||
|
||||
//foreach (GUIComponent child in children)
|
||||
//{
|
||||
// child.Flash();
|
||||
//}
|
||||
}
|
||||
|
||||
public virtual void Draw(SpriteBatch spriteBatch)
|
||||
{
|
||||
if (!Visible) return;
|
||||
|
||||
Color currColor = color;
|
||||
if (state == ComponentState.Selected) currColor = selectedColor;
|
||||
if (state == ComponentState.Hover) currColor = hoverColor;
|
||||
|
||||
if (flashTimer > 0.0f)
|
||||
{
|
||||
GUI.DrawRectangle(spriteBatch,
|
||||
new Rectangle(rect.X - 5, rect.Y - 5, rect.Width + 10, rect.Height + 10),
|
||||
flashColor * (flashTimer / FlashDuration), true);
|
||||
}
|
||||
|
||||
if (currColor.A > 0.0f && (sprites == null || !sprites.Any())) GUI.DrawRectangle(spriteBatch, rect, currColor * (currColor.A / 255.0f), true);
|
||||
|
||||
if (sprites != null && sprites[state] != null && currColor.A > 0.0f)
|
||||
{
|
||||
foreach (UISprite uiSprite in sprites[state])
|
||||
{
|
||||
if (uiSprite.Slice)
|
||||
{
|
||||
Vector2 pos = new Vector2(rect.X, rect.Y);
|
||||
|
||||
int centerWidth = Math.Max(rect.Width - uiSprite.Slices[0].Width - uiSprite.Slices[2].Width, 0);
|
||||
int centerHeight = Math.Max(rect.Height - uiSprite.Slices[0].Height - uiSprite.Slices[8].Height, 0);
|
||||
|
||||
Vector2 scale = new Vector2(
|
||||
MathHelper.Clamp((float)rect.Width / (uiSprite.Slices[0].Width + uiSprite.Slices[2].Width),0, 1),
|
||||
MathHelper.Clamp((float)rect.Height / (uiSprite.Slices[0].Height + uiSprite.Slices[6].Height), 0, 1));
|
||||
|
||||
for (int x = 0; x < 3; x++)
|
||||
{
|
||||
float width = (x == 1 ? centerWidth : uiSprite.Slices[x].Width) * scale.X;
|
||||
for (int y = 0; y < 3; y++)
|
||||
{
|
||||
float height = (y == 1 ? centerHeight : uiSprite.Slices[x + y * 3].Height) * scale.Y;
|
||||
|
||||
spriteBatch.Draw(uiSprite.Sprite.Texture,
|
||||
new Rectangle((int)pos.X, (int)pos.Y, (int)width, (int)height),
|
||||
uiSprite.Slices[x + y * 3],
|
||||
currColor * (currColor.A / 255.0f));
|
||||
|
||||
pos.Y += height;
|
||||
}
|
||||
pos.X += width;
|
||||
pos.Y = rect.Y;
|
||||
}
|
||||
}
|
||||
else if (uiSprite.Tile)
|
||||
{
|
||||
Vector2 startPos = new Vector2(rect.X, rect.Y);
|
||||
Vector2 size = new Vector2(Math.Min(uiSprite.Sprite.SourceRect.Width, rect.Width), Math.Min(uiSprite.Sprite.SourceRect.Height, rect.Height));
|
||||
|
||||
if (uiSprite.Sprite.size.X == 0.0f) size.X = rect.Width;
|
||||
if (uiSprite.Sprite.size.Y == 0.0f) size.Y = rect.Height;
|
||||
|
||||
uiSprite.Sprite.DrawTiled(spriteBatch, startPos, size, currColor * (currColor.A / 255.0f));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (uiSprite.MaintainAspectRatio)
|
||||
{
|
||||
float scale = (float)(rect.Width) / uiSprite.Sprite.SourceRect.Width;
|
||||
|
||||
spriteBatch.Draw(uiSprite.Sprite.Texture, rect,
|
||||
new Rectangle(uiSprite.Sprite.SourceRect.X, uiSprite.Sprite.SourceRect.Y, (int)(uiSprite.Sprite.SourceRect.Width), (int)(rect.Height / scale)),
|
||||
currColor * (currColor.A / 255.0f), 0.0f, Vector2.Zero, SpriteEffects.None, 0.0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
spriteBatch.Draw(uiSprite.Sprite.Texture, rect, uiSprite.Sprite.SourceRect, currColor * (currColor.A / 255.0f));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Color newColor = color;
|
||||
//if (state == ComponentState.Selected) newColor = selectedColor;
|
||||
//if (state == ComponentState.Hover) newColor = hoverColor;
|
||||
|
||||
//GUI.DrawRectangle(spriteBatch, rect, newColor*alpha, true);
|
||||
//DrawChildren(spriteBatch);
|
||||
}
|
||||
|
||||
public void DrawToolTip(SpriteBatch spriteBatch)
|
||||
{
|
||||
if (!Visible) return;
|
||||
|
||||
int width = 400;
|
||||
if (toolTipBlock == null || (string)toolTipBlock.userData != ToolTip)
|
||||
{
|
||||
toolTipBlock = new GUITextBlock(new Rectangle(0, 0, width, 18), ToolTip, "GUIToolTip", Alignment.TopLeft, Alignment.TopLeft, null, true, GUI.SmallFont);
|
||||
toolTipBlock.padding = new Vector4(5.0f, 5.0f, 5.0f, 5.0f);
|
||||
toolTipBlock.rect.Width = (int)(GUI.SmallFont.MeasureString(toolTipBlock.WrappedText).X + 20);
|
||||
toolTipBlock.rect.Height = toolTipBlock.WrappedText.Split('\n').Length * 18 + 7;
|
||||
toolTipBlock.userData = ToolTip;
|
||||
|
||||
}
|
||||
|
||||
toolTipBlock.rect = new Rectangle(MouseOn.Rect.Center.X, MouseOn.rect.Bottom, toolTipBlock.rect.Width, toolTipBlock.rect.Height);
|
||||
if (toolTipBlock.rect.Right > GameMain.GraphicsWidth - 10)
|
||||
{
|
||||
toolTipBlock.rect.Location -= new Point(toolTipBlock.rect.Right - (GameMain.GraphicsWidth - 10), 0);
|
||||
}
|
||||
|
||||
toolTipBlock.Draw(spriteBatch);
|
||||
}
|
||||
|
||||
public virtual void Update(float deltaTime)
|
||||
{
|
||||
if (!Visible) return;
|
||||
|
||||
if (flashTimer>0.0f) flashTimer -= deltaTime;
|
||||
|
||||
/*if (CanBeFocused)
|
||||
{
|
||||
if (rect.Contains(PlayerInput.MousePosition))
|
||||
{
|
||||
MouseOn = this;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (MouseOn == this) MouseOn = null;
|
||||
}
|
||||
|
||||
}*/
|
||||
|
||||
try
|
||||
{
|
||||
//use a fixed list since children can change their order in the main children list
|
||||
//TODO: maybe find a more efficient way of handling changes in list order
|
||||
List<GUIComponent> fixedChildren = new List<GUIComponent>(children);
|
||||
foreach (GUIComponent c in fixedChildren)
|
||||
{
|
||||
if (!c.Visible) continue;
|
||||
c.Update(deltaTime);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
DebugConsole.NewMessage("Error in Update! GUIComponent runtime type: " + this.GetType().ToString() + "; children count: " + children.Count.ToString(), Color.Red);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void UpdateDimensions(GUIComponent parent = null)
|
||||
{
|
||||
Rectangle parentRect = (parent==null) ? new Rectangle(0,0,GameMain.GraphicsWidth, GameMain.GraphicsHeight) : parent.rect;
|
||||
|
||||
Vector4 padding = (parent == null) ? Vector4.Zero : parent.padding;
|
||||
|
||||
if (rect.Width == 0) rect.Width = parentRect.Width - rect.X
|
||||
- (int)padding.X - (int)padding.Z;
|
||||
|
||||
if (rect.Height == 0) rect.Height = parentRect.Height - rect.Y
|
||||
- (int)padding.Y - (int)padding.W;
|
||||
|
||||
if (alignment.HasFlag(Alignment.CenterX))
|
||||
{
|
||||
rect.X += parentRect.X + (int)parentRect.Width/2 - (int)rect.Width/2;
|
||||
}
|
||||
else if (alignment.HasFlag(Alignment.Right))
|
||||
{
|
||||
rect.X += parentRect.X + (int)parentRect.Width - (int)padding.Z - (int)rect.Width;
|
||||
}
|
||||
else
|
||||
{
|
||||
rect.X += parentRect.X + (int)padding.X;
|
||||
}
|
||||
|
||||
if (alignment.HasFlag(Alignment.CenterY))
|
||||
{
|
||||
rect.Y += parentRect.Y + (int)parentRect.Height / 2 - (int)rect.Height / 2;
|
||||
}
|
||||
else if (alignment.HasFlag(Alignment.Bottom))
|
||||
{
|
||||
rect.Y += parentRect.Y + (int)parentRect.Height - (int)padding.W - (int)rect.Height;
|
||||
}
|
||||
else
|
||||
{
|
||||
rect.Y += parentRect.Y + (int)padding.Y;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void ApplyStyle(GUIComponentStyle style)
|
||||
{
|
||||
if (style == null) return;
|
||||
|
||||
color = style.Color;
|
||||
hoverColor = style.HoverColor;
|
||||
selectedColor = style.SelectedColor;
|
||||
|
||||
padding = style.Padding;
|
||||
sprites = style.Sprites;
|
||||
|
||||
OutlineColor = style.OutlineColor;
|
||||
|
||||
this.style = style;
|
||||
}
|
||||
|
||||
public virtual void DrawChildren(SpriteBatch spriteBatch)
|
||||
{
|
||||
for (int i = 0; i < children.Count; i++ )
|
||||
{
|
||||
children[i].Draw(spriteBatch);
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void AddChild(GUIComponent child)
|
||||
{
|
||||
if (child == null) return;
|
||||
if (child.IsParentOf(this))
|
||||
{
|
||||
DebugConsole.ThrowError("Tried to add the parent of a GUIComponent as a child.\n" + Environment.StackTrace);
|
||||
return;
|
||||
}
|
||||
if (child == this)
|
||||
{
|
||||
DebugConsole.ThrowError("Tried to add a GUIComponent as its own child\n" + Environment.StackTrace);
|
||||
return;
|
||||
}
|
||||
if (children.Contains(child))
|
||||
{
|
||||
DebugConsole.ThrowError("Tried to add a the same child twice to a GUIComponent" + Environment.StackTrace);
|
||||
return;
|
||||
}
|
||||
|
||||
child.parent = this;
|
||||
child.UpdateDimensions(this);
|
||||
|
||||
children.Add(child);
|
||||
}
|
||||
|
||||
public virtual void RemoveChild(GUIComponent child)
|
||||
{
|
||||
if (child == null) return;
|
||||
if (children.Contains(child)) children.Remove(child);
|
||||
}
|
||||
|
||||
public GUIComponent FindChild(object userData)
|
||||
{
|
||||
return children.FirstOrDefault(c => c.userData == userData);
|
||||
}
|
||||
|
||||
public List<GUIComponent> FindChildren(object userData)
|
||||
{
|
||||
return children.FindAll(c => c.userData == userData);
|
||||
}
|
||||
|
||||
public virtual void ClearChildren()
|
||||
{
|
||||
children.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
public class GUIDropDown : GUIComponent
|
||||
{
|
||||
|
||||
public delegate bool OnSelectedHandler(GUIComponent selected, object obj = null);
|
||||
public OnSelectedHandler OnSelected;
|
||||
|
||||
private GUIButton button;
|
||||
private GUIListBox listBox;
|
||||
|
||||
public bool Dropped { get; set; }
|
||||
|
||||
public object SelectedItemData
|
||||
{
|
||||
get
|
||||
{
|
||||
if (listBox.Selected == null) return null;
|
||||
return listBox.Selected.UserData;
|
||||
}
|
||||
}
|
||||
|
||||
public bool Enabled
|
||||
{
|
||||
get { return listBox.Enabled; }
|
||||
set { listBox.Enabled = value; }
|
||||
}
|
||||
|
||||
public GUIComponent Selected
|
||||
{
|
||||
get { return listBox.Selected; }
|
||||
}
|
||||
|
||||
public GUIListBox ListBox
|
||||
{
|
||||
get { return listBox; }
|
||||
}
|
||||
|
||||
public object SelectedData
|
||||
{
|
||||
get
|
||||
{
|
||||
return (listBox.Selected == null) ? null : listBox.Selected.UserData;
|
||||
}
|
||||
}
|
||||
|
||||
public int SelectedIndex
|
||||
{
|
||||
get
|
||||
{
|
||||
if (listBox.Selected == null) return -1;
|
||||
return listBox.children.FindIndex(x => x == listBox.Selected);
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToolTip
|
||||
{
|
||||
get
|
||||
{
|
||||
return base.ToolTip;
|
||||
}
|
||||
set
|
||||
{
|
||||
base.ToolTip = value;
|
||||
button.ToolTip = value;
|
||||
listBox.ToolTip = value;
|
||||
}
|
||||
}
|
||||
|
||||
public GUIDropDown(Rectangle rect, string text, string style, GUIComponent parent = null)
|
||||
: base(style)
|
||||
{
|
||||
this.rect = rect;
|
||||
|
||||
if (parent != null) parent.AddChild(this);
|
||||
|
||||
button = new GUIButton(this.rect, text, Color.White, Alignment.TopLeft, Alignment.CenterLeft, "GUIDropDown", null);
|
||||
GUI.Style.Apply(button, style, this);
|
||||
|
||||
button.OnClicked = OnClicked;
|
||||
|
||||
listBox = new GUIListBox(new Rectangle(this.rect.X, this.rect.Bottom, this.rect.Width, 200), style, null);
|
||||
listBox.OnSelected = SelectItem;
|
||||
}
|
||||
|
||||
public override void AddChild(GUIComponent child)
|
||||
{
|
||||
listBox.AddChild(child);
|
||||
}
|
||||
|
||||
public void AddItem(string text, object userData = null)
|
||||
{
|
||||
GUITextBlock textBlock = new GUITextBlock(new Rectangle(0,0,0,20), text, "ListBoxElement", Alignment.TopLeft, Alignment.CenterLeft, listBox);
|
||||
textBlock.UserData = userData;
|
||||
}
|
||||
|
||||
public override void ClearChildren()
|
||||
{
|
||||
listBox.ClearChildren();
|
||||
}
|
||||
|
||||
public List<GUIComponent> GetChildren()
|
||||
{
|
||||
return listBox.children;
|
||||
}
|
||||
|
||||
private bool SelectItem(GUIComponent component, object obj)
|
||||
{
|
||||
GUITextBlock textBlock = component as GUITextBlock;
|
||||
if (textBlock==null) return false;
|
||||
button.Text = textBlock.Text;
|
||||
|
||||
Dropped = false;
|
||||
|
||||
if (OnSelected != null) OnSelected(component, component.UserData);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void SelectItem(object userData)
|
||||
{
|
||||
//GUIComponent child = listBox.children.FirstOrDefault(c => c.UserData == userData);
|
||||
|
||||
//if (child == null) return;
|
||||
|
||||
listBox.Select(userData);
|
||||
|
||||
//SelectItem(child, userData);
|
||||
}
|
||||
|
||||
public void Select(int index)
|
||||
{
|
||||
listBox.Select(index);
|
||||
}
|
||||
|
||||
|
||||
private bool wasOpened;
|
||||
|
||||
private bool OnClicked(GUIComponent component, object obj)
|
||||
{
|
||||
if (wasOpened) return false;
|
||||
|
||||
wasOpened = true;
|
||||
Dropped = !Dropped;
|
||||
|
||||
if (Dropped && parent.children[parent.children.Count-1]!=this)
|
||||
{
|
||||
parent.children.Remove(this);
|
||||
parent.children.Add(this);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override void AddToGUIUpdateList()
|
||||
{
|
||||
base.AddToGUIUpdateList();
|
||||
button.AddToGUIUpdateList();
|
||||
if (Dropped) listBox.AddToGUIUpdateList();
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
if (!Visible) return;
|
||||
|
||||
wasOpened = false;
|
||||
|
||||
base.Update(deltaTime);
|
||||
|
||||
if (Dropped && PlayerInput.LeftButtonClicked())
|
||||
{
|
||||
Rectangle listBoxRect = listBox.Rect;
|
||||
listBoxRect.Width += 20;
|
||||
if (!listBoxRect.Contains(PlayerInput.MousePosition) && !button.Rect.Contains(PlayerInput.MousePosition))
|
||||
{
|
||||
Dropped = false;
|
||||
}
|
||||
}
|
||||
|
||||
button.Update(deltaTime);
|
||||
|
||||
if (Dropped) listBox.Update(deltaTime);
|
||||
}
|
||||
|
||||
public override void Draw(SpriteBatch spriteBatch)
|
||||
{
|
||||
if (!Visible) return;
|
||||
|
||||
base.Draw(spriteBatch);
|
||||
|
||||
button.Draw(spriteBatch);
|
||||
|
||||
if (!Dropped) return;
|
||||
|
||||
listBox.Draw(spriteBatch);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
public class GUIFrame : GUIComponent
|
||||
{
|
||||
public GUIFrame(Rectangle rect, string style = "", GUIComponent parent = null)
|
||||
: this(rect, null, (Alignment.Left | Alignment.Top), style, parent)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
public GUIFrame(Rectangle rect, Color color, string style = "", GUIComponent parent = null)
|
||||
: this(rect, color, (Alignment.Left | Alignment.Top), style, parent)
|
||||
{
|
||||
}
|
||||
|
||||
public GUIFrame(Rectangle rect, Color? color, Alignment alignment, string style = "", GUIComponent parent = null)
|
||||
: base(style)
|
||||
{
|
||||
this.rect = rect;
|
||||
|
||||
this.alignment = alignment;
|
||||
|
||||
if (color != null) this.color = (Color)color;
|
||||
|
||||
if (parent != null)
|
||||
{
|
||||
parent.AddChild(this);
|
||||
}
|
||||
else
|
||||
{
|
||||
UpdateDimensions();
|
||||
}
|
||||
|
||||
//if (style != null) ApplyStyle(style);
|
||||
}
|
||||
|
||||
public override void Draw(Microsoft.Xna.Framework.Graphics.SpriteBatch spriteBatch)
|
||||
{
|
||||
if (!Visible) return;
|
||||
|
||||
Color currColor = color;
|
||||
if (state == ComponentState.Selected) currColor = selectedColor;
|
||||
if (state == ComponentState.Hover) currColor = hoverColor;
|
||||
|
||||
if (sprites == null || !sprites.Any()) GUI.DrawRectangle(spriteBatch, rect, currColor * (currColor.A/255.0f), true);
|
||||
base.Draw(spriteBatch);
|
||||
|
||||
if (OutlineColor != Color.Transparent)
|
||||
{
|
||||
GUI.DrawRectangle(spriteBatch, rect, OutlineColor * (OutlineColor.A/255.0f), false);
|
||||
}
|
||||
|
||||
|
||||
DrawChildren(spriteBatch);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
using System;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
public class GUIImage : GUIComponent
|
||||
{
|
||||
public float Rotation;
|
||||
|
||||
private Sprite sprite;
|
||||
|
||||
private Rectangle sourceRect;
|
||||
|
||||
bool crop;
|
||||
|
||||
public bool Crop
|
||||
{
|
||||
get
|
||||
{
|
||||
return crop;
|
||||
}
|
||||
set
|
||||
{
|
||||
crop = value;
|
||||
if (crop)
|
||||
{
|
||||
sourceRect.Width = Math.Min(sprite.SourceRect.Width, Rect.Width);
|
||||
sourceRect.Height = Math.Min(sprite.SourceRect.Height, Rect.Height);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public float Scale
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public Rectangle SourceRect
|
||||
{
|
||||
get { return sourceRect; }
|
||||
set { sourceRect = value; }
|
||||
}
|
||||
|
||||
public GUIImage(Rectangle rect, string spritePath, Alignment alignment, GUIComponent parent = null)
|
||||
: this(rect, new Sprite(spritePath, Vector2.Zero), alignment, parent)
|
||||
{
|
||||
}
|
||||
|
||||
public GUIImage(Rectangle rect, Sprite sprite, Alignment alignment, GUIComponent parent = null)
|
||||
: this(rect, sprite==null ? Rectangle.Empty : sprite.SourceRect, sprite, alignment, parent)
|
||||
{
|
||||
}
|
||||
|
||||
public GUIImage(Rectangle rect, Rectangle sourceRect, Sprite sprite, Alignment alignment, GUIComponent parent = null)
|
||||
: base(null)
|
||||
{
|
||||
this.rect = rect;
|
||||
|
||||
this.alignment = alignment;
|
||||
|
||||
color = Color.White;
|
||||
|
||||
//alpha = 1.0f;
|
||||
|
||||
Scale = 1.0f;
|
||||
|
||||
this.sprite = sprite;
|
||||
|
||||
if (rect.Width == 0) this.rect.Width = (int)sprite.size.X;
|
||||
if (rect.Height == 0) this.rect.Height = (int)Math.Min(sprite.size.Y, sprite.size.Y * (this.rect.Width / sprite.size.X));
|
||||
|
||||
this.sourceRect = sourceRect;
|
||||
|
||||
if (parent != null) parent.AddChild(this);
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
public override void Draw(SpriteBatch spriteBatch)
|
||||
{
|
||||
if (!Visible) return;
|
||||
|
||||
Color currColor = color;
|
||||
if (state == ComponentState.Hover) currColor = hoverColor;
|
||||
if (state == ComponentState.Selected) currColor = selectedColor;
|
||||
|
||||
if (sprite != null && sprite.Texture != null)
|
||||
{
|
||||
spriteBatch.Draw(sprite.Texture, new Vector2(rect.X, rect.Y), sourceRect, currColor * (currColor.A / 255.0f), Rotation, Vector2.Zero,
|
||||
Scale, SpriteEffects.None, 0.0f);
|
||||
}
|
||||
|
||||
DrawChildren(spriteBatch);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,426 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Diagnostics;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
public class GUIListBox : GUIComponent
|
||||
{
|
||||
protected List<GUIComponent> selected;
|
||||
|
||||
public delegate bool OnSelectedHandler(GUIComponent component, object obj);
|
||||
public OnSelectedHandler OnSelected;
|
||||
|
||||
public delegate object CheckSelectedHandler();
|
||||
public CheckSelectedHandler CheckSelected;
|
||||
|
||||
private GUIScrollBar scrollBar;
|
||||
private GUIFrame frame;
|
||||
|
||||
private int totalSize;
|
||||
|
||||
private int spacing;
|
||||
|
||||
private bool scrollBarEnabled;
|
||||
private bool scrollBarHidden;
|
||||
|
||||
private bool enabled;
|
||||
|
||||
public bool SelectMultiple;
|
||||
|
||||
public GUIComponent Selected
|
||||
{
|
||||
get
|
||||
{
|
||||
return selected.Any() ? selected[0] : null;
|
||||
}
|
||||
}
|
||||
|
||||
public List<GUIComponent> AllSelected
|
||||
{
|
||||
get { return selected; }
|
||||
}
|
||||
|
||||
public object SelectedData
|
||||
{
|
||||
get
|
||||
{
|
||||
return (Selected == null) ? null : Selected.UserData;
|
||||
}
|
||||
}
|
||||
|
||||
public int SelectedIndex
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Selected == null) return -1;
|
||||
return children.FindIndex(x => x == Selected);
|
||||
}
|
||||
}
|
||||
|
||||
public float BarScroll
|
||||
{
|
||||
get { return scrollBar.BarScroll; }
|
||||
set { scrollBar.BarScroll = value; }
|
||||
}
|
||||
|
||||
public float BarSize
|
||||
{
|
||||
get { return scrollBar.BarSize; }
|
||||
}
|
||||
|
||||
public int Spacing
|
||||
{
|
||||
get { return spacing; }
|
||||
set { spacing = value; }
|
||||
}
|
||||
|
||||
public bool Enabled
|
||||
{
|
||||
get { return enabled; }
|
||||
set { enabled = value; }
|
||||
}
|
||||
|
||||
public override Color Color
|
||||
{
|
||||
get
|
||||
{
|
||||
return base.Color;
|
||||
}
|
||||
set
|
||||
{
|
||||
base.Color = value;
|
||||
|
||||
frame.Color = value;
|
||||
}
|
||||
}
|
||||
|
||||
public bool ScrollBarEnabled
|
||||
{
|
||||
get { return scrollBarEnabled; }
|
||||
set
|
||||
{
|
||||
scrollBarEnabled = value;
|
||||
}
|
||||
}
|
||||
|
||||
public GUIListBox(Rectangle rect, string style, GUIComponent parent = null)
|
||||
: this(rect, style, Alignment.TopLeft, parent)
|
||||
{
|
||||
}
|
||||
|
||||
public GUIListBox(Rectangle rect, string style, Alignment alignment, GUIComponent parent = null)
|
||||
: this(rect, null, alignment, style, parent, false)
|
||||
{
|
||||
}
|
||||
|
||||
public GUIListBox(Rectangle rect, Color? color, string style = null, GUIComponent parent = null)
|
||||
: this(rect, color, (Alignment.Left | Alignment.Top), style, parent)
|
||||
{
|
||||
}
|
||||
|
||||
public GUIListBox(Rectangle rect, Color? color, Alignment alignment, string style = null, GUIComponent parent = null, bool isHorizontal = false)
|
||||
: base(style)
|
||||
{
|
||||
this.rect = rect;
|
||||
this.alignment = alignment;
|
||||
|
||||
selected = new List<GUIComponent>();
|
||||
|
||||
if (color != null) this.color = (Color)color;
|
||||
|
||||
if (parent != null)
|
||||
parent.AddChild(this);
|
||||
|
||||
scrollBarHidden = true;
|
||||
|
||||
if (isHorizontal)
|
||||
{
|
||||
scrollBar = new GUIScrollBar(
|
||||
new Rectangle(this.rect.X, this.rect.Bottom - 20, this.rect.Width, 20), null, 1.0f, "");
|
||||
}
|
||||
else
|
||||
{
|
||||
scrollBar = new GUIScrollBar(
|
||||
new Rectangle(this.rect.Right - 20, this.rect.Y, 20, this.rect.Height), null, 1.0f, "");
|
||||
}
|
||||
|
||||
scrollBar.IsHorizontal = isHorizontal;
|
||||
|
||||
frame = new GUIFrame(new Rectangle(0, 0, this.rect.Width, this.rect.Height), style, this);
|
||||
if (style != null) GUI.Style.Apply(frame, style, this);
|
||||
|
||||
UpdateScrollBarSize();
|
||||
|
||||
children.Clear();
|
||||
|
||||
enabled = true;
|
||||
|
||||
scrollBarEnabled = true;
|
||||
|
||||
scrollBar.BarScroll = 0.0f;
|
||||
}
|
||||
|
||||
public void Select(object userData, bool force = false)
|
||||
{
|
||||
for (int i = 0; i < children.Count; i++)
|
||||
{
|
||||
if (!children[i].UserData.Equals(userData)) continue;
|
||||
|
||||
Select(i, force);
|
||||
|
||||
//if (OnSelected != null) OnSelected(Selected, Selected.UserData);
|
||||
if (!SelectMultiple) return;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateChildrenRect(float deltaTime)
|
||||
{
|
||||
int x = rect.X, y = rect.Y;
|
||||
|
||||
if (!scrollBarHidden)
|
||||
{
|
||||
if (scrollBar.IsHorizontal)
|
||||
{
|
||||
x -= (int)((totalSize - rect.Width) * scrollBar.BarScroll);
|
||||
}
|
||||
else
|
||||
{
|
||||
y -= (int)((totalSize - rect.Height) * scrollBar.BarScroll);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < children.Count; i++)
|
||||
{
|
||||
GUIComponent child = children[i];
|
||||
if (child == frame || !child.Visible) continue;
|
||||
|
||||
child.Rect = new Rectangle(x, y, child.Rect.Width, child.Rect.Height);
|
||||
if (scrollBar.IsHorizontal)
|
||||
{
|
||||
x += child.Rect.Width + spacing;
|
||||
}
|
||||
else
|
||||
{
|
||||
y += child.Rect.Height + spacing;
|
||||
}
|
||||
|
||||
if (deltaTime>0.0f) child.Update(deltaTime);
|
||||
if (enabled && child.CanBeFocused &&
|
||||
(MouseOn == this || (MouseOn != null && this.IsParentOf(MouseOn))) && child.Rect.Contains(PlayerInput.MousePosition))
|
||||
{
|
||||
child.State = ComponentState.Hover;
|
||||
if (PlayerInput.LeftButtonClicked())
|
||||
{
|
||||
Select(i);
|
||||
}
|
||||
}
|
||||
else if (selected.Contains(child))
|
||||
{
|
||||
child.State = ComponentState.Selected;
|
||||
|
||||
if (CheckSelected != null)
|
||||
{
|
||||
if (CheckSelected() != child.UserData) selected.Remove(child);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
child.State = ComponentState.None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void AddToGUIUpdateList()
|
||||
{
|
||||
if (!Visible) return;
|
||||
if (ComponentsToUpdate.Contains(this)) return;
|
||||
ComponentsToUpdate.Add(this);
|
||||
|
||||
try
|
||||
{
|
||||
List<GUIComponent> fixedChildren = new List<GUIComponent>(children);
|
||||
int lastVisible = 0;
|
||||
for (int i = 0; i < fixedChildren.Count; i++)
|
||||
{
|
||||
if (fixedChildren[i] == frame) continue;
|
||||
|
||||
if (!IsChildVisible(fixedChildren[i]))
|
||||
{
|
||||
if (lastVisible > 0) break;
|
||||
continue;
|
||||
}
|
||||
|
||||
lastVisible = i;
|
||||
fixedChildren[i].AddToGUIUpdateList();
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
DebugConsole.NewMessage("Error in AddToGUIUpdateList! GUIComponent runtime type: " + this.GetType().ToString() + "; children count: " + children.Count.ToString(), Color.Red);
|
||||
throw;
|
||||
}
|
||||
|
||||
if (scrollBarEnabled && !scrollBarHidden) scrollBar.AddToGUIUpdateList();
|
||||
}
|
||||
|
||||
public override Rectangle MouseRect
|
||||
{
|
||||
get
|
||||
{
|
||||
return rect;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
if (!Visible) return;
|
||||
|
||||
UpdateChildrenRect(deltaTime);
|
||||
|
||||
//base.Update(deltaTime);
|
||||
|
||||
if (scrollBarEnabled && !scrollBarHidden) scrollBar.Update(deltaTime);
|
||||
|
||||
if ((MouseOn == this || MouseOn == scrollBar || IsParentOf(MouseOn)) && PlayerInput.ScrollWheelSpeed != 0)
|
||||
{
|
||||
scrollBar.BarScroll -= (PlayerInput.ScrollWheelSpeed / 500.0f) * BarSize;
|
||||
}
|
||||
}
|
||||
|
||||
public void Select(int childIndex, bool force = false)
|
||||
{
|
||||
if (childIndex >= children.Count || childIndex < 0) return;
|
||||
|
||||
bool wasSelected = true;
|
||||
if (OnSelected != null) wasSelected = OnSelected(children[childIndex], children[childIndex].UserData) || force;
|
||||
|
||||
if (!wasSelected) return;
|
||||
|
||||
if (SelectMultiple)
|
||||
{
|
||||
if (selected.Contains(children[childIndex]))
|
||||
{
|
||||
selected.Remove(children[childIndex]);
|
||||
}
|
||||
else
|
||||
{
|
||||
selected.Add(children[childIndex]);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
selected.Clear();
|
||||
selected.Add(children[childIndex]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void Deselect()
|
||||
{
|
||||
selected.Clear();
|
||||
}
|
||||
|
||||
public void UpdateScrollBarSize()
|
||||
{
|
||||
totalSize = 0;
|
||||
foreach (GUIComponent child in children)
|
||||
{
|
||||
if (child == frame) continue;
|
||||
totalSize += (scrollBar.IsHorizontal) ? child.Rect.Width : child.Rect.Height;
|
||||
totalSize += spacing;
|
||||
}
|
||||
|
||||
scrollBar.BarSize = scrollBar.IsHorizontal ?
|
||||
Math.Max(Math.Min((float)rect.Width / (float)totalSize, 1.0f), 5.0f / rect.Width) :
|
||||
Math.Max(Math.Min((float)rect.Height / (float)totalSize, 1.0f), 5.0f / rect.Height);
|
||||
|
||||
scrollBarHidden = scrollBar.BarSize >= 1.0f;
|
||||
}
|
||||
|
||||
public override void AddChild(GUIComponent child)
|
||||
{
|
||||
//temporarily reduce the size of the rect to prevent the child from expanding over the scrollbar
|
||||
if (scrollBar.IsHorizontal)
|
||||
rect.Height -= scrollBar.Rect.Height;
|
||||
else
|
||||
rect.Width -= scrollBar.Rect.Width;
|
||||
|
||||
base.AddChild(child);
|
||||
|
||||
if (scrollBar.IsHorizontal)
|
||||
rect.Height += scrollBar.Rect.Height;
|
||||
else
|
||||
rect.Width += scrollBar.Rect.Width;
|
||||
|
||||
UpdateScrollBarSize();
|
||||
UpdateChildrenRect(0.0f);
|
||||
}
|
||||
|
||||
public override void ClearChildren()
|
||||
{
|
||||
base.ClearChildren();
|
||||
selected.Clear();
|
||||
}
|
||||
|
||||
public override void RemoveChild(GUIComponent child)
|
||||
{
|
||||
base.RemoveChild(child);
|
||||
|
||||
if (selected.Contains(child)) selected.Remove(child);
|
||||
|
||||
UpdateScrollBarSize();
|
||||
}
|
||||
|
||||
public override void Draw(SpriteBatch spriteBatch)
|
||||
{
|
||||
if (!Visible) return;
|
||||
|
||||
frame.Draw(spriteBatch);
|
||||
|
||||
if (!scrollBarHidden) scrollBar.Draw(spriteBatch);
|
||||
|
||||
Rectangle prevScissorRect = spriteBatch.GraphicsDevice.ScissorRectangle;
|
||||
spriteBatch.GraphicsDevice.ScissorRectangle = frame.Rect;
|
||||
|
||||
int lastVisible = 0;
|
||||
for (int i = 0; i < children.Count; i++)
|
||||
{
|
||||
GUIComponent child = children[i];
|
||||
if (child == frame || !child.Visible) continue;
|
||||
|
||||
if (!IsChildVisible(child))
|
||||
{
|
||||
if (lastVisible > 0) break;
|
||||
continue;
|
||||
}
|
||||
|
||||
lastVisible = i;
|
||||
child.Draw(spriteBatch);
|
||||
}
|
||||
|
||||
spriteBatch.GraphicsDevice.ScissorRectangle = prevScissorRect;
|
||||
}
|
||||
|
||||
private bool IsChildVisible(GUIComponent child)
|
||||
{
|
||||
if (child == null) return false;
|
||||
|
||||
if (scrollBar.IsHorizontal)
|
||||
{
|
||||
if (child.Rect.Right < rect.X) return false;
|
||||
if (child.Rect.X > rect.Right) return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (child.Rect.Bottom < rect.Y) return false;
|
||||
if (child.Rect.Y > rect.Bottom) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class GUIMessage
|
||||
{
|
||||
private ColoredText coloredText;
|
||||
private Vector2 pos;
|
||||
|
||||
private float lifeTime;
|
||||
|
||||
private Vector2 size;
|
||||
|
||||
public string Text
|
||||
{
|
||||
get { return coloredText.Text; }
|
||||
}
|
||||
|
||||
public Color Color
|
||||
{
|
||||
get { return coloredText.Color; }
|
||||
}
|
||||
|
||||
public Vector2 Pos
|
||||
{
|
||||
get { return pos; }
|
||||
set { pos = value; }
|
||||
}
|
||||
|
||||
public Vector2 Size
|
||||
{
|
||||
get { return size; }
|
||||
}
|
||||
|
||||
|
||||
public float LifeTime
|
||||
{
|
||||
get { return lifeTime; }
|
||||
set { lifeTime = value; }
|
||||
}
|
||||
|
||||
public GUIMessage(string text, Color color, Vector2 position, float lifeTime)
|
||||
{
|
||||
coloredText = new ColoredText(text, color);
|
||||
pos = position;
|
||||
this.lifeTime = lifeTime;
|
||||
|
||||
size = GUI.Font.MeasureString(text);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
public class GUIMessageBox : GUIFrame
|
||||
{
|
||||
public static List<GUIComponent> MessageBoxes = new List<GUIComponent>();
|
||||
|
||||
const int DefaultWidth=400, DefaultHeight=250;
|
||||
|
||||
//public delegate bool OnClickedHandler(GUIButton button, object obj);
|
||||
//public OnClickedHandler OnClicked;
|
||||
|
||||
//GUIFrame frame;
|
||||
public GUIButton[] Buttons;
|
||||
|
||||
public static GUIComponent VisibleBox
|
||||
{
|
||||
get { return MessageBoxes.Count == 0 ? null : MessageBoxes[0]; }
|
||||
}
|
||||
|
||||
public string Text
|
||||
{
|
||||
get { return (children[0].children[1] as GUITextBlock).Text; }
|
||||
set { (children[0].children[1] as GUITextBlock).Text = value; }
|
||||
}
|
||||
|
||||
public GUIMessageBox(string headerText, string text)
|
||||
: this(headerText, text, new string[] {"OK"})
|
||||
{
|
||||
this.Buttons[0].OnClicked = Close;
|
||||
}
|
||||
|
||||
public GUIMessageBox(string headerText, string text, int width, int height)
|
||||
: this(headerText, text, new string[] { "OK" }, width, height)
|
||||
{
|
||||
this.Buttons[0].OnClicked = Close;
|
||||
}
|
||||
|
||||
public GUIMessageBox(string headerText, string text, string[] buttons, int width = DefaultWidth, int height = DefaultHeight, Alignment textAlignment = Alignment.TopLeft, GUIComponent parent = null)
|
||||
: base(new Rectangle(0, 0, GameMain.GraphicsWidth, GameMain.GraphicsHeight),
|
||||
Color.Black * 0.5f, Alignment.TopLeft, null, parent)
|
||||
{
|
||||
if (height == 0)
|
||||
{
|
||||
string wrappedText = ToolBox.WrapText(text, width, GUI.Font);
|
||||
string[] lines = wrappedText.Split('\n');
|
||||
foreach (string line in lines)
|
||||
{
|
||||
height += (int)GUI.Font.MeasureString(line).Y;
|
||||
}
|
||||
height += 220;
|
||||
}
|
||||
|
||||
var frame = new GUIFrame(new Rectangle(0, 0, width, height), null, Alignment.Center, "", this);
|
||||
GUI.Style.Apply(frame, "", this);
|
||||
|
||||
var header = new GUITextBlock(new Rectangle(0, 0, 0, 30), headerText, null, null, textAlignment, "", frame, true);
|
||||
GUI.Style.Apply(header, "", this);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(text))
|
||||
{
|
||||
var textBlock = new GUITextBlock(new Rectangle(0, 30, 0, height - 70), text,
|
||||
null, null, textAlignment, "", frame, true);
|
||||
GUI.Style.Apply(textBlock, "", this);
|
||||
}
|
||||
|
||||
int x = 0;
|
||||
this.Buttons = new GUIButton[buttons.Length];
|
||||
for (int i = 0; i < buttons.Length; i++)
|
||||
{
|
||||
this.Buttons[i] = new GUIButton(new Rectangle(x, 0, 150, 30), buttons[i], Alignment.Left | Alignment.Bottom, "", frame);
|
||||
|
||||
x += this.Buttons[i].Rect.Width + 20;
|
||||
}
|
||||
|
||||
MessageBoxes.Add(this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public bool Close(GUIButton button, object obj)
|
||||
{
|
||||
if (parent != null) parent.RemoveChild(this);
|
||||
if (MessageBoxes.Contains(this)) MessageBoxes.Remove(this);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void CloseAll()
|
||||
{
|
||||
MessageBoxes.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
public class GUIProgressBar : GUIComponent
|
||||
{
|
||||
private bool isHorizontal;
|
||||
|
||||
private GUIFrame frame, slider;
|
||||
private float barSize;
|
||||
|
||||
public delegate float ProgressGetterHandler();
|
||||
public ProgressGetterHandler ProgressGetter;
|
||||
|
||||
public bool IsHorizontal
|
||||
{
|
||||
get { return isHorizontal; }
|
||||
set { isHorizontal = value; }
|
||||
}
|
||||
|
||||
public float BarSize
|
||||
{
|
||||
get { return barSize; }
|
||||
set
|
||||
{
|
||||
float oldBarSize = barSize;
|
||||
barSize = MathHelper.Clamp(value, 0.0f, 1.0f);
|
||||
if (barSize != oldBarSize) UpdateRect();
|
||||
}
|
||||
}
|
||||
|
||||
public GUIProgressBar(Rectangle rect, Color color, float barSize, GUIComponent parent = null)
|
||||
: this(rect, color, barSize, (Alignment.Left | Alignment.Top), parent)
|
||||
{
|
||||
}
|
||||
|
||||
public GUIProgressBar(Rectangle rect, Color color, float barSize, Alignment alignment, GUIComponent parent = null)
|
||||
: this(rect, color, null, barSize, alignment, parent)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public GUIProgressBar(Rectangle rect, Color color, string style, float barSize, Alignment alignment, GUIComponent parent = null)
|
||||
: base(style)
|
||||
{
|
||||
this.rect = rect;
|
||||
this.color = color;
|
||||
isHorizontal = (rect.Width > rect.Height);
|
||||
|
||||
this.alignment = alignment;
|
||||
|
||||
if (parent != null)
|
||||
parent.AddChild(this);
|
||||
|
||||
frame = new GUIFrame(new Rectangle(0, 0, 0, 0), null, this);
|
||||
GUI.Style.Apply(frame, "", this);
|
||||
|
||||
slider = new GUIFrame(new Rectangle(0, 0, 0, 0), null);
|
||||
GUI.Style.Apply(slider, "Slider", this);
|
||||
|
||||
this.barSize = barSize;
|
||||
UpdateRect();
|
||||
}
|
||||
|
||||
/*public override void ApplyStyle(GUIComponentStyle style)
|
||||
{
|
||||
if (frame == null) return;
|
||||
|
||||
frame.Color = style.Color;
|
||||
frame.HoverColor = style.HoverColor;
|
||||
frame.SelectedColor = style.SelectedColor;
|
||||
|
||||
Padding = style.Padding;
|
||||
|
||||
frame.OutlineColor = style.OutlineColor;
|
||||
|
||||
this.style = style;
|
||||
}*/
|
||||
|
||||
private void UpdateRect()
|
||||
{
|
||||
slider.Rect = new Rectangle(
|
||||
(int)(frame.Rect.X + padding.X),
|
||||
(int)(frame.Rect.Y + padding.Y),
|
||||
isHorizontal ? (int)((frame.Rect.Width - padding.X - padding.Z) * barSize) : frame.Rect.Width,
|
||||
isHorizontal ? (int)(frame.Rect.Height - padding.Y - padding.W) : (int)(frame.Rect.Height * barSize));
|
||||
}
|
||||
|
||||
public override void Draw(SpriteBatch spriteBatch)
|
||||
{
|
||||
if (!Visible) return;
|
||||
|
||||
if (ProgressGetter != null) BarSize = ProgressGetter();
|
||||
|
||||
DrawChildren(spriteBatch);
|
||||
|
||||
Color currColor = color;
|
||||
if (state == ComponentState.Selected) currColor = selectedColor;
|
||||
if (state == ComponentState.Hover) currColor = hoverColor;
|
||||
|
||||
if (slider.sprites != null && slider.sprites[state].Count > 0)
|
||||
{
|
||||
foreach (UISprite uiSprite in slider.sprites[state])
|
||||
{
|
||||
if (uiSprite.Tile)
|
||||
{
|
||||
uiSprite.Sprite.DrawTiled(spriteBatch, slider.Rect.Location.ToVector2(), slider.Rect.Size.ToVector2(), currColor);
|
||||
}
|
||||
else
|
||||
{
|
||||
spriteBatch.Draw(uiSprite.Sprite.Texture,
|
||||
slider.Rect, new Rectangle(
|
||||
uiSprite.Sprite.SourceRect.X,
|
||||
uiSprite.Sprite.SourceRect.Y,
|
||||
(int)(uiSprite.Sprite.SourceRect.Width * (isHorizontal ? barSize : 1.0f)),
|
||||
(int)(uiSprite.Sprite.SourceRect.Height * (isHorizontal ? 1.0f : barSize))),
|
||||
currColor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,218 @@
|
||||
using System;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
public class GUIScrollBar : GUIComponent
|
||||
{
|
||||
public static GUIScrollBar draggingBar;
|
||||
|
||||
private bool isHorizontal;
|
||||
|
||||
private GUIFrame frame;
|
||||
private GUIButton bar;
|
||||
private float barSize;
|
||||
private float barScroll;
|
||||
|
||||
private float step;
|
||||
|
||||
private bool enabled;
|
||||
|
||||
public delegate bool OnMovedHandler(GUIScrollBar scrollBar, float barScroll);
|
||||
public OnMovedHandler OnMoved;
|
||||
|
||||
public bool IsHorizontal
|
||||
{
|
||||
get { return isHorizontal; }
|
||||
set
|
||||
{
|
||||
if (isHorizontal == value) return;
|
||||
isHorizontal = value;
|
||||
UpdateRect();
|
||||
}
|
||||
}
|
||||
|
||||
public bool Enabled
|
||||
{
|
||||
get { return enabled; }
|
||||
set { enabled = value; }
|
||||
}
|
||||
|
||||
public float BarScroll
|
||||
{
|
||||
get { return step == 0.0f ? barScroll : MathUtils.RoundTowardsClosest(barScroll, step); }
|
||||
set
|
||||
{
|
||||
barScroll = MathHelper.Clamp(value, 0.0f, 1.0f);
|
||||
int newX = bar.Rect.X - frame.Rect.X;
|
||||
int newY = bar.Rect.Y - frame.Rect.Y;
|
||||
|
||||
float newScroll = step == 0.0f ? barScroll : MathUtils.RoundTowardsClosest(barScroll, step);
|
||||
|
||||
if (isHorizontal)
|
||||
{
|
||||
newX = (int)(frame.Padding.X + newScroll * (frame.Rect.Width - bar.Rect.Width - frame.Padding.X - frame.Padding.Z));
|
||||
newX = MathHelper.Clamp(newX, (int)frame.Padding.X, frame.Rect.Width - bar.Rect.Width - (int)frame.Padding.Z);
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
newY = (int)(frame.Padding.Y + newScroll * (frame.Rect.Height - bar.Rect.Height - frame.Padding.Y - frame.Padding.W));
|
||||
newY = MathHelper.Clamp(newY, (int)frame.Padding.Y, frame.Rect.Height - bar.Rect.Height - (int)frame.Padding.W);
|
||||
|
||||
}
|
||||
bar.Rect = new Rectangle(newX + frame.Rect.X, newY + frame.Rect.Y, bar.Rect.Width, bar.Rect.Height);
|
||||
}
|
||||
}
|
||||
|
||||
public float Step
|
||||
{
|
||||
get
|
||||
{
|
||||
return step;
|
||||
}
|
||||
set
|
||||
{
|
||||
step = MathHelper.Clamp(value, 0.0f, 1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
public float BarSize
|
||||
{
|
||||
get { return barSize; }
|
||||
set
|
||||
{
|
||||
float oldBarSize = barSize;
|
||||
barSize = Math.Min(Math.Max(value, 0.0f), 1.0f);
|
||||
if (barSize != oldBarSize) UpdateRect();
|
||||
}
|
||||
}
|
||||
|
||||
public GUIScrollBar(Rectangle rect, string style, float barSize, GUIComponent parent = null)
|
||||
: this(rect, null, barSize, style, parent)
|
||||
{
|
||||
}
|
||||
|
||||
public GUIScrollBar(Rectangle rect, Color? color, float barSize, string style = "", GUIComponent parent = null)
|
||||
: this(rect, color, barSize, Alignment.TopLeft, style, parent)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
public GUIScrollBar(Rectangle rect, Color? color, float barSize, Alignment alignment, string style = "", GUIComponent parent = null)
|
||||
: base(style)
|
||||
{
|
||||
this.rect = rect;
|
||||
//GetDimensions(parent);
|
||||
|
||||
this.alignment = alignment;
|
||||
|
||||
if (parent != null)
|
||||
parent.AddChild(this);
|
||||
|
||||
isHorizontal = (rect.Width > rect.Height);
|
||||
frame = new GUIFrame(new Rectangle(0,0,0,0), style, this);
|
||||
GUI.Style.Apply(frame, isHorizontal ? "GUIFrameHorizontal" : "GUIFrameVertical", this);
|
||||
|
||||
this.barSize = barSize;
|
||||
|
||||
bar = new GUIButton(new Rectangle(0, 0, 0, 0), "", color, "", this);
|
||||
GUI.Style.Apply(bar, isHorizontal ? "GUIButtonHorizontal" : "GUIButtoneVertical", this);
|
||||
|
||||
bar.OnPressed = SelectBar;
|
||||
|
||||
enabled = true;
|
||||
|
||||
UpdateRect();
|
||||
}
|
||||
|
||||
private void UpdateRect()
|
||||
{
|
||||
float width = frame.Rect.Width - frame.Padding.X - frame.Padding.Z;
|
||||
float height = frame.Rect.Height - frame.Padding.Y - frame.Padding.W;
|
||||
|
||||
bar.Rect = new Rectangle(
|
||||
bar.Rect.X,
|
||||
bar.Rect.Y,
|
||||
isHorizontal ? (int)(width * barSize) : (int)width,
|
||||
isHorizontal ? (int)height : (int)(height * barSize));
|
||||
|
||||
ClampRect();
|
||||
|
||||
foreach (GUIComponent child in bar.children)
|
||||
{
|
||||
child.Rect = bar.Rect;
|
||||
}
|
||||
}
|
||||
|
||||
private void ClampRect()
|
||||
{
|
||||
bar.Rect = new Rectangle(
|
||||
(int)MathHelper.Clamp(bar.Rect.X, frame.Rect.X + frame.Padding.X, frame.Rect.Right - bar.Rect.Width - frame.Padding.X - frame.Padding.Z),
|
||||
(int)MathHelper.Clamp(bar.Rect.Y, frame.Rect.Y + frame.Padding.Y, frame.Rect.Bottom - bar.Rect.Height - frame.Padding.Y - frame.Padding.W),
|
||||
bar.Rect.Width,
|
||||
bar.Rect.Height);
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
if (!Visible) return;
|
||||
|
||||
base.Update(deltaTime);
|
||||
|
||||
if (MouseOn == frame)
|
||||
{
|
||||
if (PlayerInput.LeftButtonClicked())
|
||||
{
|
||||
MoveButton(new Vector2(
|
||||
Math.Sign(PlayerInput.MousePosition.X - bar.Rect.Center.X) * bar.Rect.Width,
|
||||
Math.Sign(PlayerInput.MousePosition.Y - bar.Rect.Center.Y) * bar.Rect.Height));
|
||||
}
|
||||
}
|
||||
|
||||
if (draggingBar == this)
|
||||
{
|
||||
if (!PlayerInput.LeftButtonHeld()) draggingBar = null;
|
||||
MoveButton(PlayerInput.MouseSpeed);
|
||||
}
|
||||
}
|
||||
|
||||
public override void Draw(SpriteBatch spriteBatch)
|
||||
{
|
||||
if (!Visible) return;
|
||||
|
||||
DrawChildren(spriteBatch);
|
||||
}
|
||||
|
||||
private bool SelectBar()
|
||||
{
|
||||
if (!enabled) return false;
|
||||
if (barSize == 1.0f) return false;
|
||||
|
||||
draggingBar = this;
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
private void MoveButton(Vector2 moveAmount)
|
||||
{
|
||||
if (isHorizontal)
|
||||
{
|
||||
moveAmount.Y = 0.0f;
|
||||
barScroll += moveAmount.X / (frame.Rect.Width - bar.Rect.Width - frame.Padding.X - frame.Padding.Z);
|
||||
}
|
||||
else
|
||||
{
|
||||
moveAmount.X = 0.0f;
|
||||
barScroll += moveAmount.Y / (frame.Rect.Height - bar.Rect.Height - frame.Padding.Y - frame.Padding.W);
|
||||
}
|
||||
|
||||
BarScroll = barScroll;
|
||||
|
||||
if (moveAmount != Vector2.Zero && OnMoved != null) OnMoved(this, BarScroll);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
using System.Xml.Linq;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
public class GUIStyle
|
||||
{
|
||||
private Dictionary<string, GUIComponentStyle> componentStyles;
|
||||
|
||||
public GUIStyle(string file)
|
||||
{
|
||||
componentStyles = new Dictionary<string, GUIComponentStyle>();
|
||||
|
||||
XDocument doc;
|
||||
try
|
||||
{
|
||||
ToolBox.IsProperFilenameCase(file);
|
||||
doc = XDocument.Load(file);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
DebugConsole.ThrowError("Loading style \"" + file + "\" failed", e);
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (XElement subElement in doc.Root.Elements())
|
||||
{
|
||||
GUIComponentStyle componentStyle = new GUIComponentStyle(subElement);
|
||||
componentStyles.Add(subElement.Name.ToString().ToLowerInvariant(), componentStyle);
|
||||
}
|
||||
}
|
||||
|
||||
public void Apply(GUIComponent targetComponent, string styleName = "", GUIComponent parent = null)
|
||||
{
|
||||
GUIComponentStyle componentStyle = null;
|
||||
if (parent != null)
|
||||
{
|
||||
|
||||
GUIComponentStyle parentStyle = parent.Style;
|
||||
|
||||
if (parent.Style == null)
|
||||
{
|
||||
string parentStyleName = parent.GetType().Name.ToLowerInvariant();
|
||||
|
||||
if (!componentStyles.TryGetValue(parentStyleName, out parentStyle))
|
||||
{
|
||||
DebugConsole.ThrowError("Couldn't find a GUI style \""+ parentStyleName + "\"");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
string childStyleName = string.IsNullOrEmpty(styleName) ? targetComponent.GetType().Name : styleName;
|
||||
parentStyle.ChildStyles.TryGetValue(childStyleName.ToLowerInvariant(), out componentStyle);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (string.IsNullOrEmpty(styleName))
|
||||
{
|
||||
styleName = targetComponent.GetType().Name;
|
||||
}
|
||||
if (!componentStyles.TryGetValue(styleName.ToLowerInvariant(), out componentStyle))
|
||||
{
|
||||
DebugConsole.ThrowError("Couldn't find a GUI style \""+ styleName+"\"");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
targetComponent.ApplyStyle(componentStyle);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,308 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using System;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
public class GUITextBlock : GUIComponent
|
||||
{
|
||||
protected string text;
|
||||
|
||||
protected Alignment textAlignment;
|
||||
|
||||
private float textScale;
|
||||
|
||||
protected Vector2 textPos;
|
||||
protected Vector2 origin;
|
||||
|
||||
protected Vector2 caretPos;
|
||||
|
||||
protected Color textColor;
|
||||
|
||||
private string wrappedText;
|
||||
|
||||
public delegate string TextGetterHandler();
|
||||
public TextGetterHandler TextGetter;
|
||||
|
||||
public bool Wrap;
|
||||
|
||||
private bool overflowClipActive;
|
||||
public bool OverflowClip;
|
||||
|
||||
private float textDepth;
|
||||
|
||||
public override Vector4 Padding
|
||||
{
|
||||
get { return padding; }
|
||||
set
|
||||
{
|
||||
padding = value;
|
||||
SetTextPos();
|
||||
}
|
||||
}
|
||||
|
||||
public string Text
|
||||
{
|
||||
get { return text; }
|
||||
set
|
||||
{
|
||||
if (Text == value) return;
|
||||
|
||||
text = value;
|
||||
wrappedText = value;
|
||||
SetTextPos();
|
||||
}
|
||||
}
|
||||
|
||||
public string WrappedText
|
||||
{
|
||||
get { return wrappedText; }
|
||||
}
|
||||
|
||||
public override Rectangle Rect
|
||||
{
|
||||
get
|
||||
{
|
||||
return base.Rect;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (base.Rect == value) return;
|
||||
foreach (GUIComponent child in children)
|
||||
{
|
||||
child.Rect = new Rectangle(child.Rect.X + value.X - rect.X, child.Rect.Y + value.Y - rect.Y, child.Rect.Width, child.Rect.Height);
|
||||
}
|
||||
|
||||
if (value.Width != rect.Width || value.Height != rect.Height)
|
||||
{
|
||||
SetTextPos();
|
||||
}
|
||||
|
||||
rect = value;
|
||||
}
|
||||
}
|
||||
|
||||
public float TextDepth
|
||||
{
|
||||
get { return textDepth; }
|
||||
set { textDepth = MathHelper.Clamp(value, 0.0f, 1.0f); }
|
||||
}
|
||||
|
||||
public Vector2 TextPos
|
||||
{
|
||||
get { return textPos; }
|
||||
}
|
||||
|
||||
public float TextScale
|
||||
{
|
||||
get { return textScale; }
|
||||
set
|
||||
{
|
||||
if (value != textScale)
|
||||
{
|
||||
textScale = value;
|
||||
SetTextPos();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Vector2 Origin
|
||||
{
|
||||
get { return origin; }
|
||||
}
|
||||
|
||||
public Color TextColor
|
||||
{
|
||||
get { return textColor; }
|
||||
set { textColor = value; }
|
||||
}
|
||||
|
||||
public Vector2 CaretPos
|
||||
{
|
||||
get { return caretPos; }
|
||||
}
|
||||
|
||||
public GUITextBlock(Rectangle rect, string text, string style, GUIComponent parent, ScalableFont font)
|
||||
: this(rect, text, style, Alignment.TopLeft, Alignment.TopLeft, parent, false, font)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
public GUITextBlock(Rectangle rect, string text, string style, GUIComponent parent = null, bool wrap = false)
|
||||
: this(rect, text, style, Alignment.TopLeft, Alignment.TopLeft, parent, wrap)
|
||||
{
|
||||
}
|
||||
|
||||
public GUITextBlock(Rectangle rect, string text, Color? color, Color? textColor, Alignment textAlignment = Alignment.Left, string style = null, GUIComponent parent = null, bool wrap = false)
|
||||
: this(rect, text,color, textColor, Alignment.TopLeft, textAlignment, style, parent, wrap)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void UpdateDimensions(GUIComponent parent = null)
|
||||
{
|
||||
base.UpdateDimensions(parent);
|
||||
|
||||
SetTextPos();
|
||||
}
|
||||
|
||||
public override void ApplyStyle(GUIComponentStyle style)
|
||||
{
|
||||
if (style == null) return;
|
||||
base.ApplyStyle(style);
|
||||
|
||||
textColor = style.textColor;
|
||||
}
|
||||
|
||||
|
||||
public GUITextBlock(Rectangle rect, string text, Color? color, Color? textColor, Alignment alignment, Alignment textAlignment = Alignment.Left, string style = null, GUIComponent parent = null, bool wrap = false, ScalableFont font = null)
|
||||
: this (rect, text, style, alignment, textAlignment, parent, wrap, font)
|
||||
{
|
||||
if (color != null) this.color = (Color)color;
|
||||
if (textColor != null) this.textColor = (Color)textColor;
|
||||
}
|
||||
|
||||
public GUITextBlock(Rectangle rect, string text, string style, Alignment alignment = Alignment.TopLeft, Alignment textAlignment = Alignment.TopLeft, GUIComponent parent = null, bool wrap = false, ScalableFont font = null)
|
||||
: base(style)
|
||||
{
|
||||
this.Font = font == null ? GUI.Font : font;
|
||||
|
||||
this.rect = rect;
|
||||
|
||||
this.text = text;
|
||||
|
||||
this.alignment = alignment;
|
||||
|
||||
this.padding = new Vector4(5.0f, 5.0f, 5.0f, 5.0f);
|
||||
|
||||
this.textAlignment = textAlignment;
|
||||
|
||||
if (parent != null)
|
||||
parent.AddChild(this);
|
||||
|
||||
this.Wrap = wrap;
|
||||
|
||||
SetTextPos();
|
||||
|
||||
TextScale = 1.0f;
|
||||
|
||||
if (rect.Height == 0 && !string.IsNullOrEmpty(Text))
|
||||
{
|
||||
this.rect.Height = (int)Font.MeasureString(wrappedText).Y;
|
||||
}
|
||||
}
|
||||
|
||||
public void SetTextPos()
|
||||
{
|
||||
if (text == null) return;
|
||||
|
||||
overflowClipActive = false;
|
||||
|
||||
wrappedText = text;
|
||||
|
||||
Vector2 size = MeasureText(text);
|
||||
|
||||
if (Wrap && rect.Width > 0)
|
||||
{
|
||||
wrappedText = ToolBox.WrapText(text, rect.Width - padding.X - padding.Z, Font, textScale);
|
||||
size = MeasureText(wrappedText);
|
||||
}
|
||||
else if (OverflowClip)
|
||||
{
|
||||
overflowClipActive = size.X > rect.Width;
|
||||
}
|
||||
|
||||
textPos = new Vector2(rect.Width / 2.0f, rect.Height / 2.0f);
|
||||
origin = size * 0.5f;
|
||||
|
||||
if (textAlignment.HasFlag(Alignment.Left) && !overflowClipActive)
|
||||
origin.X += (rect.Width / 2.0f - padding.X) - size.X / 2;
|
||||
|
||||
if (textAlignment.HasFlag(Alignment.Right) || overflowClipActive)
|
||||
origin.X -= (rect.Width / 2.0f - padding.Z) - size.X / 2;
|
||||
|
||||
if (textAlignment.HasFlag(Alignment.Top))
|
||||
origin.Y += (rect.Height / 2.0f - padding.Y) - size.Y / 2;
|
||||
|
||||
if (textAlignment.HasFlag(Alignment.Bottom))
|
||||
origin.Y -= (rect.Height / 2.0f - padding.W) - size.Y / 2;
|
||||
|
||||
origin.X = (int)origin.X;
|
||||
origin.Y = (int)origin.Y;
|
||||
|
||||
textPos.X = (int)textPos.X;
|
||||
textPos.Y = (int)textPos.Y;
|
||||
|
||||
if (wrappedText.Contains("\n"))
|
||||
{
|
||||
string[] lines = wrappedText.Split('\n');
|
||||
Vector2 lastLineSize = MeasureText(lines[lines.Length-1]);
|
||||
caretPos = new Vector2(rect.X + lastLineSize.X, rect.Y + size.Y - lastLineSize.Y) + textPos - origin;
|
||||
}
|
||||
else
|
||||
{
|
||||
caretPos = new Vector2(rect.X + size.X, rect.Y) + textPos - origin;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private Vector2 MeasureText(string text)
|
||||
{
|
||||
if (Font == null) return Vector2.Zero;
|
||||
|
||||
Vector2 size = Vector2.Zero;
|
||||
while (size == Vector2.Zero)
|
||||
{
|
||||
try { size = Font.MeasureString((text == "") ? " " : text); }
|
||||
catch { text = text.Substring(0, text.Length - 1); }
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
public override void Draw(SpriteBatch spriteBatch)
|
||||
{
|
||||
Draw(spriteBatch, Vector2.Zero);
|
||||
}
|
||||
|
||||
public void Draw(SpriteBatch spriteBatch, Vector2 offset)
|
||||
{
|
||||
if (!Visible) return;
|
||||
|
||||
Color currColor = color;
|
||||
if (state == ComponentState.Hover) currColor = hoverColor;
|
||||
if (state == ComponentState.Selected) currColor = selectedColor;
|
||||
|
||||
Rectangle drawRect = rect;
|
||||
if (offset != Vector2.Zero) drawRect.Location += offset.ToPoint();
|
||||
|
||||
base.Draw(spriteBatch);
|
||||
|
||||
if (TextGetter != null) Text = TextGetter();
|
||||
|
||||
Rectangle prevScissorRect = spriteBatch.GraphicsDevice.ScissorRectangle;
|
||||
if (overflowClipActive)
|
||||
{
|
||||
spriteBatch.GraphicsDevice.ScissorRectangle = rect;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(text))
|
||||
{
|
||||
Font.DrawString(spriteBatch,
|
||||
Wrap ? wrappedText : text,
|
||||
new Vector2(rect.X, rect.Y) + textPos + offset,
|
||||
textColor * (textColor.A / 255.0f),
|
||||
0.0f, origin, TextScale,
|
||||
SpriteEffects.None, textDepth);
|
||||
}
|
||||
|
||||
if (overflowClipActive)
|
||||
{
|
||||
spriteBatch.GraphicsDevice.ScissorRectangle = prevScissorRect;
|
||||
}
|
||||
|
||||
DrawChildren(spriteBatch);
|
||||
|
||||
if (OutlineColor.A * currColor.A > 0.0f) GUI.DrawRectangle(spriteBatch, rect, OutlineColor * (currColor.A / 255.0f), false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,335 @@
|
||||
using System;
|
||||
using EventInput;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
|
||||
delegate void TextBoxEvent(GUITextBox sender, Keys key);
|
||||
|
||||
class GUITextBox : GUIComponent, IKeyboardSubscriber
|
||||
{
|
||||
public event TextBoxEvent OnSelected;
|
||||
|
||||
bool caretVisible;
|
||||
float caretTimer;
|
||||
|
||||
GUITextBlock textBlock;
|
||||
|
||||
public delegate bool OnEnterHandler(GUITextBox textBox, string text);
|
||||
public OnEnterHandler OnEnterPressed;
|
||||
|
||||
public event TextBoxEvent OnKeyHit;
|
||||
|
||||
public delegate bool OnTextChangedHandler(GUITextBox textBox, string text);
|
||||
public OnTextChangedHandler OnTextChanged;
|
||||
|
||||
public bool CaretEnabled;
|
||||
|
||||
private int? maxTextLength;
|
||||
|
||||
public GUITextBlock.TextGetterHandler TextGetter
|
||||
{
|
||||
get { return textBlock.TextGetter; }
|
||||
set { textBlock.TextGetter = value; }
|
||||
}
|
||||
|
||||
public bool Wrap
|
||||
{
|
||||
get { return textBlock.Wrap; }
|
||||
set { textBlock.Wrap = value; }
|
||||
}
|
||||
|
||||
public int? MaxTextLength
|
||||
{
|
||||
get { return maxTextLength; }
|
||||
set
|
||||
{
|
||||
textBlock.OverflowClip = true;
|
||||
maxTextLength = value;
|
||||
}
|
||||
}
|
||||
|
||||
public bool Enabled
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public override string ToolTip
|
||||
{
|
||||
get
|
||||
{
|
||||
return base.ToolTip;
|
||||
}
|
||||
set
|
||||
{
|
||||
base.ToolTip = value;
|
||||
textBlock.ToolTip = value;
|
||||
}
|
||||
}
|
||||
|
||||
public override ScalableFont Font
|
||||
{
|
||||
set
|
||||
{
|
||||
base.Font = value;
|
||||
if (textBlock == null) return;
|
||||
textBlock.Font = value;
|
||||
}
|
||||
}
|
||||
|
||||
public override Color Color
|
||||
{
|
||||
get { return color; }
|
||||
set
|
||||
{
|
||||
color = value;
|
||||
textBlock.Color = color;
|
||||
}
|
||||
}
|
||||
|
||||
public Color TextColor
|
||||
{
|
||||
get { return textBlock.TextColor; }
|
||||
set { textBlock.TextColor = value; }
|
||||
}
|
||||
|
||||
public override Color HoverColor
|
||||
{
|
||||
get
|
||||
{
|
||||
return base.HoverColor;
|
||||
}
|
||||
set
|
||||
{
|
||||
base.HoverColor = value;
|
||||
textBlock.HoverColor = value;
|
||||
}
|
||||
}
|
||||
|
||||
public override Rectangle Rect
|
||||
{
|
||||
get
|
||||
{
|
||||
return base.Rect;
|
||||
}
|
||||
set
|
||||
{
|
||||
base.Rect = value;
|
||||
|
||||
textBlock.Rect = value;
|
||||
}
|
||||
}
|
||||
|
||||
public string Text
|
||||
{
|
||||
get
|
||||
{
|
||||
return textBlock.Text;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (textBlock.Text == value) return;
|
||||
|
||||
textBlock.Text = value;
|
||||
if (textBlock.Text == null) textBlock.Text = "";
|
||||
|
||||
if (textBlock.Text != "")
|
||||
{
|
||||
if (!Wrap)
|
||||
{
|
||||
if (maxTextLength != null)
|
||||
{
|
||||
if (Text.Length > maxTextLength)
|
||||
{
|
||||
Text = textBlock.Text.Substring(0, (int)maxTextLength);
|
||||
}
|
||||
}
|
||||
else if (Font.MeasureString(textBlock.Text).X > (int)(textBlock.Rect.Width - textBlock.Padding.X - textBlock.Padding.Z))
|
||||
{
|
||||
Text = textBlock.Text.Substring(0, textBlock.Text.Length - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public GUITextBox(Rectangle rect, string style = null, GUIComponent parent = null)
|
||||
: this(rect, null, null, Alignment.Left, Alignment.Left, style, parent)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public GUITextBox(Rectangle rect, Alignment alignment = Alignment.Left, string style = null, GUIComponent parent = null)
|
||||
: this(rect, null, null, alignment, Alignment.Left, style, parent)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public GUITextBox(Rectangle rect, Color? color, Color? textColor, Alignment alignment, Alignment textAlignment = Alignment.CenterLeft, string style = null, GUIComponent parent = null)
|
||||
: base(style)
|
||||
{
|
||||
Enabled = true;
|
||||
|
||||
this.rect = rect;
|
||||
|
||||
if (color != null) this.color = (Color)color;
|
||||
|
||||
this.alignment = alignment;
|
||||
|
||||
if (parent != null)
|
||||
parent.AddChild(this);
|
||||
|
||||
|
||||
textBlock = new GUITextBlock(new Rectangle(0,0,0,0), "", color, textColor, textAlignment, style, this);
|
||||
|
||||
Font = GUI.Font;
|
||||
|
||||
GUI.Style.Apply(textBlock, style == "" ? "GUITextBox" : style);
|
||||
textBlock.Padding = new Vector4(3.0f, 0.0f, 3.0f, 0.0f);
|
||||
|
||||
CaretEnabled = true;
|
||||
}
|
||||
|
||||
public void Select()
|
||||
{
|
||||
Selected = true;
|
||||
keyboardDispatcher.Subscriber = this;
|
||||
//if (Clicked != null) Clicked(this);
|
||||
}
|
||||
|
||||
public void Deselect()
|
||||
{
|
||||
Selected = false;
|
||||
if (keyboardDispatcher.Subscriber == this) keyboardDispatcher.Subscriber = null;
|
||||
}
|
||||
|
||||
public override void Flash(Color? color = null)
|
||||
{
|
||||
textBlock.Flash(color);
|
||||
}
|
||||
|
||||
//MouseState previousMouse;
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
if (!Visible) return;
|
||||
|
||||
if (flashTimer > 0.0f) flashTimer -= deltaTime;
|
||||
if (!Enabled) return;
|
||||
|
||||
if (rect.Contains(PlayerInput.MousePosition) && Enabled &&
|
||||
(MouseOn == null || MouseOn == this || IsParentOf(MouseOn) || MouseOn.IsParentOf(this)))
|
||||
{
|
||||
|
||||
state = ComponentState.Hover;
|
||||
if (PlayerInput.LeftButtonClicked())
|
||||
{
|
||||
Select();
|
||||
if (OnSelected != null) OnSelected(this, Keys.None);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
state = ComponentState.None;
|
||||
}
|
||||
|
||||
|
||||
if (CaretEnabled)
|
||||
{
|
||||
caretTimer += deltaTime;
|
||||
caretVisible = ((caretTimer * 1000.0f) % 1000) < 500;
|
||||
}
|
||||
|
||||
if (keyboardDispatcher.Subscriber == this)
|
||||
{
|
||||
state = ComponentState.Selected;
|
||||
Character.DisableControls = true;
|
||||
if (OnEnterPressed != null && PlayerInput.KeyHit(Keys.Enter))
|
||||
{
|
||||
string input = Text;
|
||||
Text = "";
|
||||
OnEnterPressed(this, input);
|
||||
}
|
||||
#if LINUX
|
||||
else if (PlayerInput.KeyHit(Keys.Back) && Text.Length>0)
|
||||
{
|
||||
Text = Text.Substring(0, Text.Length-1);
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
textBlock.State = state;
|
||||
textBlock.Update(deltaTime);
|
||||
}
|
||||
|
||||
public override void Draw(SpriteBatch spriteBatch)
|
||||
{
|
||||
if (!Visible) return;
|
||||
|
||||
DrawChildren(spriteBatch);
|
||||
|
||||
if (!CaretEnabled) return;
|
||||
|
||||
Vector2 caretPos = textBlock.CaretPos;
|
||||
|
||||
if (caretVisible && Selected)
|
||||
{
|
||||
GUI.DrawLine(spriteBatch,
|
||||
new Vector2((int)caretPos.X + 2, caretPos.Y + 3),
|
||||
new Vector2((int)caretPos.X + 2, caretPos.Y + Font.MeasureString("I").Y - 3),
|
||||
textBlock.TextColor * (textBlock.TextColor.A / 255.0f));
|
||||
}
|
||||
}
|
||||
|
||||
public void ReceiveTextInput(char inputChar)
|
||||
{
|
||||
Text = Text + inputChar;
|
||||
|
||||
if (OnTextChanged!=null) OnTextChanged(this, Text);
|
||||
}
|
||||
public void ReceiveTextInput(string text)
|
||||
{
|
||||
Text = Text + text;
|
||||
|
||||
if (OnTextChanged != null) OnTextChanged(this, Text);
|
||||
}
|
||||
public void ReceiveCommandInput(char command)
|
||||
{
|
||||
if (Text == null) Text = "";
|
||||
|
||||
switch (command)
|
||||
{
|
||||
case '\b': //backspace
|
||||
if (Text.Length > 0) Text = Text.Substring(0, Text.Length - 1);
|
||||
break;
|
||||
//case '\r': //return
|
||||
// if (OnEnterPressed != null)
|
||||
// OnEnterPressed(this);
|
||||
// break;
|
||||
//case '\t': //tab
|
||||
// if (OnTabPressed != null)
|
||||
// OnTabPressed(this);
|
||||
// break;
|
||||
}
|
||||
|
||||
if (OnTextChanged != null) OnTextChanged(this, Text);
|
||||
}
|
||||
|
||||
public void ReceiveSpecialInput(Keys key)
|
||||
{
|
||||
if (OnKeyHit != null) OnKeyHit(this, key);
|
||||
}
|
||||
|
||||
//public event TextBoxEvent OnTabPressed;
|
||||
|
||||
public bool Selected
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
public class GUITickBox : GUIComponent
|
||||
{
|
||||
private GUIFrame box;
|
||||
private GUITextBlock text;
|
||||
|
||||
public delegate bool OnSelectedHandler(GUITickBox obj);
|
||||
public OnSelectedHandler OnSelected;
|
||||
|
||||
private bool selected;
|
||||
|
||||
public bool Selected
|
||||
{
|
||||
get { return selected; }
|
||||
set
|
||||
{
|
||||
if (value == selected) return;
|
||||
selected = value;
|
||||
state = (selected) ? ComponentState.Selected : ComponentState.None;
|
||||
|
||||
box.State = state;
|
||||
}
|
||||
}
|
||||
|
||||
private bool enabled;
|
||||
|
||||
public bool Enabled
|
||||
{
|
||||
get
|
||||
{
|
||||
return enabled;
|
||||
}
|
||||
set
|
||||
{
|
||||
enabled = value;
|
||||
}
|
||||
}
|
||||
|
||||
public override Rectangle Rect
|
||||
{
|
||||
get
|
||||
{
|
||||
return rect;
|
||||
}
|
||||
set
|
||||
{
|
||||
base.Rect = value;
|
||||
|
||||
box.Rect = new Rectangle(value.X,value.Y,box.Rect.Width,box.Rect.Height);
|
||||
text.Rect = new Rectangle(box.Rect.Right + 10, box.Rect.Y + 2, 20, box.Rect.Height);
|
||||
}
|
||||
}
|
||||
|
||||
public Color TextColor
|
||||
{
|
||||
get { return text.TextColor; }
|
||||
set { text.TextColor = value; }
|
||||
}
|
||||
|
||||
public override Rectangle MouseRect
|
||||
{
|
||||
get { return box.Rect; }
|
||||
}
|
||||
|
||||
public override ScalableFont Font
|
||||
{
|
||||
get
|
||||
{
|
||||
return base.Font;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
base.Font = value;
|
||||
if (text != null) text.Font = value;
|
||||
}
|
||||
}
|
||||
|
||||
public GUITickBox(Rectangle rect, string label, Alignment alignment, GUIComponent parent)
|
||||
: this(rect, label, alignment, GUI.Font, parent)
|
||||
{
|
||||
}
|
||||
|
||||
public GUITickBox(Rectangle rect, string label, Alignment alignment, ScalableFont font, GUIComponent parent)
|
||||
: base(null)
|
||||
{
|
||||
if (parent != null)
|
||||
parent.AddChild(this);
|
||||
|
||||
box = new GUIFrame(rect, Color.DarkGray, "", this);
|
||||
box.HoverColor = Color.Gray;
|
||||
box.SelectedColor = Color.DarkGray;
|
||||
box.CanBeFocused = false;
|
||||
|
||||
GUI.Style.Apply(box, "GUITickBox");
|
||||
|
||||
text = new GUITextBlock(new Rectangle(rect.Right, rect.Y, 20, rect.Height), label, "", Alignment.TopLeft, Alignment.Left | Alignment.CenterY, this, false, font);
|
||||
GUI.Style.Apply(text, "GUIButtonHorizontal", this);
|
||||
|
||||
this.rect = new Rectangle(box.Rect.X, box.Rect.Y, 240, rect.Height);
|
||||
|
||||
Enabled = true;
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
if (!Visible) return;
|
||||
|
||||
if (MouseOn == this && Enabled)
|
||||
{
|
||||
box.State = ComponentState.Hover;
|
||||
|
||||
if (PlayerInput.LeftButtonHeld())
|
||||
{
|
||||
box.State = ComponentState.Selected;
|
||||
}
|
||||
|
||||
if (PlayerInput.LeftButtonClicked())
|
||||
{
|
||||
Selected = !Selected;
|
||||
if (OnSelected != null) OnSelected(this);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
box.State = ComponentState.None;
|
||||
}
|
||||
|
||||
if (selected)
|
||||
{
|
||||
box.State = ComponentState.Selected;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Draw(SpriteBatch spriteBatch)
|
||||
{
|
||||
if (!Visible) return;
|
||||
|
||||
DrawChildren(spriteBatch);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,244 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
using Microsoft.Xna.Framework.Media;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class LoadingScreen
|
||||
{
|
||||
private Texture2D backgroundTexture,monsterTexture,titleTexture;
|
||||
|
||||
readonly RenderTarget2D renderTarget;
|
||||
|
||||
float state;
|
||||
|
||||
public Vector2 CenterPosition;
|
||||
|
||||
public Vector2 TitlePosition;
|
||||
|
||||
private float? loadState;
|
||||
#if !LINUX
|
||||
Video splashScreenVideo;
|
||||
VideoPlayer videoPlayer;
|
||||
#endif
|
||||
public Vector2 TitleSize
|
||||
{
|
||||
get { return new Vector2(titleTexture.Width, titleTexture.Height); }
|
||||
}
|
||||
|
||||
public float Scale
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public float? LoadState
|
||||
{
|
||||
get { return loadState; }
|
||||
set
|
||||
{
|
||||
loadState = value;
|
||||
DrawLoadingText = true;
|
||||
}
|
||||
}
|
||||
|
||||
public bool DrawLoadingText
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public LoadingScreen(GraphicsDevice graphics)
|
||||
{
|
||||
#if !LINUX
|
||||
|
||||
if (GameMain.Config.EnableSplashScreen)
|
||||
{
|
||||
try
|
||||
{
|
||||
splashScreenVideo = GameMain.Instance.Content.Load<Video>("utg_4");
|
||||
}
|
||||
|
||||
catch (Exception e)
|
||||
{
|
||||
DebugConsole.ThrowError("Failed to load splashscreen", e);
|
||||
GameMain.Config.EnableSplashScreen = false;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
backgroundTexture = TextureLoader.FromFile("Content/UI/titleBackground.png");
|
||||
monsterTexture = TextureLoader.FromFile("Content/UI/titleMonster.png");
|
||||
titleTexture = TextureLoader.FromFile("Content/UI/titleText.png");
|
||||
|
||||
renderTarget = new RenderTarget2D(graphics, GameMain.GraphicsWidth, GameMain.GraphicsHeight);
|
||||
|
||||
DrawLoadingText = true;
|
||||
}
|
||||
|
||||
|
||||
public void Draw(SpriteBatch spriteBatch, GraphicsDevice graphics, float deltaTime)
|
||||
{
|
||||
#if !LINUX
|
||||
if (GameMain.Config.EnableSplashScreen && splashScreenVideo != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
DrawSplashScreen(spriteBatch);
|
||||
if (videoPlayer != null && videoPlayer.State == MediaState.Playing)
|
||||
return;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
DebugConsole.ThrowError("Playing splash screen video failed", e);
|
||||
GameMain.Config.EnableSplashScreen = false;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
drawn = true;
|
||||
|
||||
graphics.SetRenderTarget(renderTarget);
|
||||
|
||||
Scale = GameMain.GraphicsHeight/1500.0f;
|
||||
|
||||
state += deltaTime;
|
||||
|
||||
if (DrawLoadingText)
|
||||
{
|
||||
CenterPosition = new Vector2(GameMain.GraphicsWidth*0.3f, GameMain.GraphicsHeight/2.0f);
|
||||
TitlePosition = CenterPosition + new Vector2(-0.0f + (float)Math.Sqrt(state) * 220.0f, 0.0f) * Scale;
|
||||
TitlePosition.X = Math.Min(TitlePosition.X, (float)GameMain.GraphicsWidth / 2.0f);
|
||||
}
|
||||
|
||||
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend);
|
||||
graphics.Clear(Color.Black);
|
||||
|
||||
spriteBatch.Draw(backgroundTexture, CenterPosition, null, Color.White * Math.Min(state / 5.0f, 1.0f), 0.0f,
|
||||
new Vector2(backgroundTexture.Width / 2.0f, backgroundTexture.Height / 2.0f),
|
||||
Scale*1.5f, SpriteEffects.None, 0.2f);
|
||||
|
||||
spriteBatch.Draw(monsterTexture,
|
||||
CenterPosition + new Vector2((state % 40) * 100.0f - 1800.0f, (state % 40) * 30.0f - 200.0f) * Scale, null,
|
||||
Color.White, 0.0f, Vector2.Zero, Scale, SpriteEffects.None, 0.1f);
|
||||
|
||||
spriteBatch.Draw(titleTexture,
|
||||
TitlePosition, null,
|
||||
Color.White * Math.Min((state - 1.0f) / 5.0f, 1.0f), 0.0f, new Vector2(titleTexture.Width / 2.0f, titleTexture.Height / 2.0f), Scale, SpriteEffects.None, 0.0f);
|
||||
|
||||
spriteBatch.End();
|
||||
|
||||
graphics.SetRenderTarget(null);
|
||||
|
||||
if (Hull.renderer != null)
|
||||
{
|
||||
Hull.renderer.ScrollWater(deltaTime);
|
||||
Hull.renderer.RenderBack(spriteBatch, renderTarget, 0.0f);
|
||||
}
|
||||
|
||||
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend);
|
||||
|
||||
spriteBatch.Draw(titleTexture,
|
||||
TitlePosition, null,
|
||||
Color.White * Math.Min((state - 3.0f) / 5.0f, 1.0f), 0.0f, new Vector2(titleTexture.Width / 2.0f, titleTexture.Height / 2.0f), Scale, SpriteEffects.None, 0.0f);
|
||||
|
||||
if (DrawLoadingText)
|
||||
{
|
||||
string loadText = "";
|
||||
if (loadState == 100.0f)
|
||||
{
|
||||
loadText = "Press any key to continue";
|
||||
}
|
||||
else
|
||||
{
|
||||
loadText = "Loading... ";
|
||||
if (loadState!=null)
|
||||
{
|
||||
loadText += (int)loadState + " %";
|
||||
}
|
||||
}
|
||||
|
||||
if (GUI.LargeFont!=null)
|
||||
{
|
||||
GUI.LargeFont.DrawString(spriteBatch, loadText,
|
||||
new Vector2(GameMain.GraphicsWidth/2.0f - GUI.LargeFont.MeasureString(loadText).X/2.0f, GameMain.GraphicsHeight*0.8f),
|
||||
Color.White);
|
||||
}
|
||||
|
||||
}
|
||||
spriteBatch.End();
|
||||
|
||||
}
|
||||
|
||||
#if !LINUX
|
||||
private void DrawSplashScreen(SpriteBatch spriteBatch)
|
||||
{
|
||||
if (videoPlayer == null)
|
||||
{
|
||||
videoPlayer = new VideoPlayer();
|
||||
videoPlayer.Play(splashScreenVideo);
|
||||
videoPlayer.Volume = GameMain.Config.SoundVolume;
|
||||
}
|
||||
else
|
||||
{
|
||||
Texture2D videoTexture = null;
|
||||
|
||||
if (videoPlayer.State == MediaState.Stopped)
|
||||
{
|
||||
videoPlayer.Dispose();
|
||||
videoPlayer = null;
|
||||
|
||||
splashScreenVideo.Dispose();
|
||||
splashScreenVideo = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
videoTexture = videoPlayer.GetTexture();
|
||||
|
||||
spriteBatch.Begin();
|
||||
spriteBatch.Draw(videoTexture, new Rectangle(0, 0, GameMain.GraphicsWidth, GameMain.GraphicsHeight), Color.White);
|
||||
spriteBatch.End();
|
||||
|
||||
if (PlayerInput.KeyHit(Keys.Space) || PlayerInput.KeyHit(Keys.Enter) || PlayerInput.LeftButtonDown())
|
||||
{
|
||||
videoPlayer.Stop();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
bool drawn;
|
||||
public IEnumerable<object> DoLoading(IEnumerable<object> loader)
|
||||
{
|
||||
drawn = false;
|
||||
LoadState = null;
|
||||
|
||||
while (!drawn)
|
||||
{
|
||||
yield return CoroutineStatus.Running;
|
||||
}
|
||||
|
||||
CoroutineManager.StartCoroutine(loader);
|
||||
|
||||
yield return CoroutineStatus.Running;
|
||||
|
||||
while (CoroutineManager.IsCoroutineRunning(loader.ToString()))
|
||||
{
|
||||
yield return CoroutineStatus.Running;
|
||||
}
|
||||
|
||||
loadState = 100.0f;
|
||||
|
||||
yield return CoroutineStatus.Success;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,423 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Reflection;
|
||||
using FarseerPhysics;
|
||||
using FarseerPhysics.Dynamics;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using Barotrauma.Networking;
|
||||
using Barotrauma.Particles;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.Xna.Framework.Input;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class GameMain : Game
|
||||
{
|
||||
public static bool DebugDraw;
|
||||
|
||||
public static FrameCounter FrameCounter;
|
||||
|
||||
public static readonly Version Version = Assembly.GetEntryAssembly().GetName().Version;
|
||||
|
||||
public static GameScreen GameScreen;
|
||||
public static MainMenuScreen MainMenuScreen;
|
||||
public static LobbyScreen LobbyScreen;
|
||||
|
||||
public static NetLobbyScreen NetLobbyScreen;
|
||||
public static ServerListScreen ServerListScreen;
|
||||
|
||||
public static EditMapScreen EditMapScreen;
|
||||
public static EditCharacterScreen EditCharacterScreen;
|
||||
|
||||
public static Lights.LightManager LightManager;
|
||||
|
||||
public static ContentPackage SelectedPackage
|
||||
{
|
||||
get { return Config.SelectedContentPackage; }
|
||||
}
|
||||
|
||||
public static GameSession GameSession;
|
||||
|
||||
public static NetworkMember NetworkMember;
|
||||
|
||||
public static ParticleManager ParticleManager;
|
||||
|
||||
public static World World;
|
||||
|
||||
public static LoadingScreen TitleScreen;
|
||||
private bool loadingScreenOpen;
|
||||
|
||||
public static GameSettings Config;
|
||||
|
||||
private CoroutineHandle loadingCoroutine;
|
||||
private bool hasLoaded;
|
||||
|
||||
private GameTime fixedTime;
|
||||
|
||||
private static SpriteBatch spriteBatch;
|
||||
|
||||
public static GameMain Instance
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public static GraphicsDeviceManager GraphicsDeviceManager
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public static int GraphicsWidth
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public static int GraphicsHeight
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public static bool WindowActive
|
||||
{
|
||||
get { return Instance == null || Instance.IsActive; }
|
||||
}
|
||||
|
||||
public static GameServer Server
|
||||
{
|
||||
get { return NetworkMember as GameServer; }
|
||||
}
|
||||
|
||||
public static GameClient Client
|
||||
{
|
||||
get { return NetworkMember as GameClient; }
|
||||
}
|
||||
|
||||
public static RasterizerState ScissorTestEnable
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public GameMain()
|
||||
{
|
||||
GraphicsDeviceManager = new GraphicsDeviceManager(this);
|
||||
Window.Title = "Barotrauma";
|
||||
|
||||
Instance = this;
|
||||
|
||||
Config = new GameSettings("config.xml");
|
||||
if (Config.WasGameUpdated)
|
||||
{
|
||||
UpdaterUtil.CleanOldFiles();
|
||||
Config.WasGameUpdated = false;
|
||||
Config.Save("config.xml");
|
||||
}
|
||||
|
||||
ApplyGraphicsSettings();
|
||||
|
||||
Content.RootDirectory = "Content";
|
||||
|
||||
FrameCounter = new FrameCounter();
|
||||
|
||||
IsFixedTimeStep = false;
|
||||
|
||||
Timing.Accumulator = 0.0f;
|
||||
fixedTime = new GameTime();
|
||||
|
||||
World = new World(new Vector2(0, -9.82f));
|
||||
FarseerPhysics.Settings.AllowSleep = true;
|
||||
FarseerPhysics.Settings.ContinuousPhysics = false;
|
||||
FarseerPhysics.Settings.VelocityIterations = 1;
|
||||
FarseerPhysics.Settings.PositionIterations = 1;
|
||||
}
|
||||
|
||||
public void ApplyGraphicsSettings()
|
||||
{
|
||||
GraphicsWidth = Config.GraphicsWidth;
|
||||
GraphicsHeight = Config.GraphicsHeight;
|
||||
GraphicsDeviceManager.SynchronizeWithVerticalRetrace = Config.VSyncEnabled;
|
||||
|
||||
GraphicsDeviceManager.HardwareModeSwitch = Config.WindowMode != WindowMode.BorderlessWindowed;
|
||||
|
||||
GraphicsDeviceManager.IsFullScreen = Config.WindowMode == WindowMode.Fullscreen || Config.WindowMode == WindowMode.BorderlessWindowed;
|
||||
GraphicsDeviceManager.PreferredBackBufferWidth = GraphicsWidth;
|
||||
GraphicsDeviceManager.PreferredBackBufferHeight = GraphicsHeight;
|
||||
|
||||
GraphicsDeviceManager.ApplyChanges();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Allows the game to perform any initialization it needs to before starting to run.
|
||||
/// This is where it can query for any required services and load any non-graphic
|
||||
/// related content. Calling base.Initialize will enumerate through any components
|
||||
/// and initialize them as well.
|
||||
/// </summary>
|
||||
protected override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
ScissorTestEnable = new RasterizerState() { ScissorTestEnable = true };
|
||||
|
||||
Hyper.ComponentModel.HyperTypeDescriptionProvider.Add(typeof(Character));
|
||||
Hyper.ComponentModel.HyperTypeDescriptionProvider.Add(typeof(Item));
|
||||
Hyper.ComponentModel.HyperTypeDescriptionProvider.Add(typeof(Items.Components.ItemComponent));
|
||||
Hyper.ComponentModel.HyperTypeDescriptionProvider.Add(typeof(Hull));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// LoadContent will be called once per game and is the place to load
|
||||
/// all of your content.
|
||||
/// </summary>
|
||||
protected override void LoadContent()
|
||||
{
|
||||
GraphicsWidth = GraphicsDevice.Viewport.Width;
|
||||
GraphicsHeight = GraphicsDevice.Viewport.Height;
|
||||
|
||||
Sound.Init();
|
||||
|
||||
ConvertUnits.SetDisplayUnitToSimUnitRatio(Physics.DisplayToSimRation);
|
||||
|
||||
spriteBatch = new SpriteBatch(GraphicsDevice);
|
||||
TextureLoader.Init(GraphicsDevice);
|
||||
|
||||
loadingScreenOpen = true;
|
||||
TitleScreen = new LoadingScreen(GraphicsDevice);
|
||||
|
||||
loadingCoroutine = CoroutineManager.StartCoroutine(Load());
|
||||
}
|
||||
|
||||
private IEnumerable<object> Load()
|
||||
{
|
||||
GUI.GraphicsDevice = base.GraphicsDevice;
|
||||
GUI.Init(Content);
|
||||
|
||||
GUIComponent.Init(Window);
|
||||
DebugConsole.Init(Window);
|
||||
DebugConsole.Log(SelectedPackage == null ? "No content package selected" : "Content package \"" + SelectedPackage.Name + "\" selected");
|
||||
yield return CoroutineStatus.Running;
|
||||
|
||||
LightManager = new Lights.LightManager(base.GraphicsDevice);
|
||||
|
||||
Hull.renderer = new WaterRenderer(base.GraphicsDevice, Content);
|
||||
TitleScreen.LoadState = 1.0f;
|
||||
yield return CoroutineStatus.Running;
|
||||
|
||||
GUI.LoadContent();
|
||||
TitleScreen.LoadState = 2.0f;
|
||||
yield return CoroutineStatus.Running;
|
||||
|
||||
Mission.Init();
|
||||
MapEntityPrefab.Init();
|
||||
LevelGenerationParams.LoadPresets();
|
||||
TitleScreen.LoadState = 10.0f;
|
||||
yield return CoroutineStatus.Running;
|
||||
|
||||
JobPrefab.LoadAll(SelectedPackage.GetFilesOfType(ContentType.Jobs));
|
||||
// Add any missing jobs from the prefab into Config.JobNamePreferences.
|
||||
foreach (JobPrefab job in JobPrefab.List)
|
||||
{
|
||||
if (!Config.JobNamePreferences.Contains(job.Name)) { Config.JobNamePreferences.Add(job.Name); }
|
||||
}
|
||||
StructurePrefab.LoadAll(SelectedPackage.GetFilesOfType(ContentType.Structure));
|
||||
TitleScreen.LoadState = 20.0f;
|
||||
yield return CoroutineStatus.Running;
|
||||
|
||||
ItemPrefab.LoadAll(SelectedPackage.GetFilesOfType(ContentType.Item));
|
||||
TitleScreen.LoadState = 30.0f;
|
||||
yield return CoroutineStatus.Running;
|
||||
|
||||
Debug.WriteLine("sounds");
|
||||
CoroutineManager.StartCoroutine(SoundPlayer.Init());
|
||||
|
||||
int i = 0;
|
||||
while (!SoundPlayer.Initialized)
|
||||
{
|
||||
i++;
|
||||
TitleScreen.LoadState = SoundPlayer.SoundCount == 0 ?
|
||||
30.0f :
|
||||
Math.Min(30.0f + 40.0f * i / Math.Max(SoundPlayer.SoundCount, 1), 70.0f);
|
||||
yield return CoroutineStatus.Running;
|
||||
}
|
||||
|
||||
TitleScreen.LoadState = 70.0f;
|
||||
yield return CoroutineStatus.Running;
|
||||
|
||||
GameModePreset.Init();
|
||||
|
||||
Submarine.RefreshSavedSubs();
|
||||
TitleScreen.LoadState = 80.0f;
|
||||
yield return CoroutineStatus.Running;
|
||||
|
||||
GameScreen = new GameScreen(GraphicsDeviceManager.GraphicsDevice, Content);
|
||||
TitleScreen.LoadState = 90.0f;
|
||||
yield return CoroutineStatus.Running;
|
||||
|
||||
MainMenuScreen = new MainMenuScreen(this);
|
||||
LobbyScreen = new LobbyScreen();
|
||||
|
||||
ServerListScreen = new ServerListScreen();
|
||||
|
||||
EditMapScreen = new EditMapScreen();
|
||||
EditCharacterScreen = new EditCharacterScreen();
|
||||
|
||||
yield return CoroutineStatus.Running;
|
||||
|
||||
ParticleManager = new ParticleManager("Content/Particles/ParticlePrefabs.xml", GameScreen.Cam);
|
||||
yield return CoroutineStatus.Running;
|
||||
|
||||
LocationType.Init();
|
||||
MainMenuScreen.Select();
|
||||
|
||||
TitleScreen.LoadState = 100.0f;
|
||||
hasLoaded = true;
|
||||
yield return CoroutineStatus.Success;
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// UnloadContent will be called once per game and is the place to unload
|
||||
/// all content.
|
||||
/// </summary>
|
||||
protected override void UnloadContent()
|
||||
{
|
||||
Sound.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Allows the game to run logic such as updating the world,
|
||||
/// checking for collisions, gathering input, and playing audio.
|
||||
/// </summary>
|
||||
/// <param name="gameTime">Provides a snapshot of timing values.</param>
|
||||
protected override void Update(GameTime gameTime)
|
||||
{
|
||||
Timing.TotalTime = gameTime.TotalGameTime.TotalSeconds;
|
||||
Timing.Accumulator += gameTime.ElapsedGameTime.TotalSeconds;
|
||||
PlayerInput.UpdateVariable();
|
||||
|
||||
bool paused = true;
|
||||
|
||||
while (Timing.Accumulator >= Timing.Step)
|
||||
{
|
||||
fixedTime.IsRunningSlowly = gameTime.IsRunningSlowly;
|
||||
TimeSpan addTime = new TimeSpan(0, 0, 0, 0, 16);
|
||||
fixedTime.ElapsedGameTime = addTime;
|
||||
fixedTime.TotalGameTime.Add(addTime);
|
||||
base.Update(fixedTime);
|
||||
|
||||
PlayerInput.Update(Timing.Step);
|
||||
|
||||
if (loadingScreenOpen)
|
||||
{
|
||||
//reset accumulator if loading
|
||||
// -> less choppy loading screens because the screen is rendered after each update
|
||||
// -> no pause caused by leftover time in the accumulator when starting a new shift
|
||||
Timing.Accumulator = 0.0f;
|
||||
|
||||
if (TitleScreen.LoadState >= 100.0f &&
|
||||
(!waitForKeyHit || PlayerInput.GetKeyboardState.GetPressedKeys().Length>0 || PlayerInput.LeftButtonClicked()))
|
||||
{
|
||||
loadingScreenOpen = false;
|
||||
}
|
||||
|
||||
if (!hasLoaded && !CoroutineManager.IsCoroutineRunning(loadingCoroutine))
|
||||
{
|
||||
throw new Exception("Loading was interrupted due to an error");
|
||||
}
|
||||
}
|
||||
else if (hasLoaded)
|
||||
{
|
||||
SoundPlayer.Update();
|
||||
|
||||
if (PlayerInput.KeyHit(Keys.Escape)) GUI.TogglePauseMenu();
|
||||
|
||||
GUIComponent.ClearUpdateList();
|
||||
paused = (DebugConsole.IsOpen || GUI.PauseMenuOpen || GUI.SettingsMenuOpen) &&
|
||||
(NetworkMember == null || !NetworkMember.GameStarted);
|
||||
|
||||
if (!paused)
|
||||
{
|
||||
Screen.Selected.AddToGUIUpdateList();
|
||||
}
|
||||
|
||||
if (NetworkMember != null)
|
||||
{
|
||||
NetworkMember.AddToGUIUpdateList();
|
||||
}
|
||||
|
||||
GUI.AddToGUIUpdateList();
|
||||
DebugConsole.AddToGUIUpdateList();
|
||||
GUIComponent.UpdateMouseOn();
|
||||
|
||||
DebugConsole.Update(this, (float)Timing.Step);
|
||||
|
||||
if (!paused)
|
||||
{
|
||||
Screen.Selected.Update(Timing.Step);
|
||||
}
|
||||
|
||||
if (NetworkMember != null)
|
||||
{
|
||||
NetworkMember.Update((float)Timing.Step);
|
||||
}
|
||||
|
||||
GUI.Update((float)Timing.Step);
|
||||
}
|
||||
|
||||
CoroutineManager.Update((float)Timing.Step, paused ? 0.0f : (float)Timing.Step);
|
||||
|
||||
Timing.Accumulator -= Timing.Step;
|
||||
}
|
||||
|
||||
if (!paused) Timing.Alpha = Timing.Accumulator / Timing.Step;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// This is called when the game should draw itself.
|
||||
/// </summary>
|
||||
protected override void Draw(GameTime gameTime)
|
||||
{
|
||||
double deltaTime = gameTime.ElapsedGameTime.TotalSeconds;
|
||||
|
||||
FrameCounter.Update(deltaTime);
|
||||
|
||||
if (loadingScreenOpen)
|
||||
{
|
||||
TitleScreen.Draw(spriteBatch, base.GraphicsDevice, (float)deltaTime);
|
||||
}
|
||||
else if (hasLoaded)
|
||||
{
|
||||
Screen.Selected.Draw(deltaTime, base.GraphicsDevice, spriteBatch);
|
||||
}
|
||||
|
||||
if (!DebugDraw) return;
|
||||
if (GUIComponent.MouseOn!=null)
|
||||
{
|
||||
spriteBatch.Begin();
|
||||
GUI.DrawRectangle(spriteBatch, GUIComponent.MouseOn.MouseRect, Color.Lime);
|
||||
spriteBatch.End();
|
||||
}
|
||||
}
|
||||
|
||||
static bool waitForKeyHit = true;
|
||||
public CoroutineHandle ShowLoading(IEnumerable<object> loader, bool waitKeyHit = true)
|
||||
{
|
||||
waitForKeyHit = waitKeyHit;
|
||||
loadingScreenOpen = true;
|
||||
TitleScreen.LoadState = null;
|
||||
return CoroutineManager.StartCoroutine(TitleScreen.DoLoading(loader));
|
||||
}
|
||||
|
||||
protected override void OnExiting(object sender, EventArgs args)
|
||||
{
|
||||
if (NetworkMember != null) NetworkMember.Disconnect();
|
||||
|
||||
base.OnExiting(sender, args);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class CargoManager
|
||||
{
|
||||
private List<ItemPrefab> purchasedItems;
|
||||
|
||||
public CargoManager()
|
||||
{
|
||||
purchasedItems = new List<ItemPrefab>();
|
||||
}
|
||||
|
||||
public void AddItem(ItemPrefab item)
|
||||
{
|
||||
purchasedItems.Add(item);
|
||||
}
|
||||
|
||||
public void CreateItems()
|
||||
{
|
||||
WayPoint wp = WayPoint.GetRandom(SpawnType.Cargo, null, Submarine.MainSub);
|
||||
|
||||
if (wp==null)
|
||||
{
|
||||
DebugConsole.ThrowError("The submarine must have a waypoint marked as Cargo for bought items to be placed correctly!");
|
||||
return;
|
||||
}
|
||||
|
||||
Hull cargoRoom = Hull.FindHull(wp.WorldPosition);
|
||||
|
||||
if (cargoRoom == null)
|
||||
{
|
||||
DebugConsole.ThrowError("A waypoint marked as Cargo must be placed inside a room!");
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (ItemPrefab prefab in purchasedItems)
|
||||
{
|
||||
Vector2 position = new Vector2(
|
||||
Rand.Range(cargoRoom.Rect.X + 20, cargoRoom.Rect.Right - 20),
|
||||
cargoRoom.Rect.Y - cargoRoom.Rect.Height + prefab.Size.Y/2);
|
||||
|
||||
new Item(prefab, position, wp.Submarine);
|
||||
}
|
||||
|
||||
purchasedItems.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,359 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.IO;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class CrewManager
|
||||
{
|
||||
public List<Character> characters;
|
||||
public List<CharacterInfo> characterInfos;
|
||||
|
||||
public int WinningTeam = 1;
|
||||
|
||||
private int money;
|
||||
|
||||
private GUIFrame guiFrame;
|
||||
private GUIListBox listBox, orderListBox;
|
||||
|
||||
private CrewCommander commander;
|
||||
|
||||
public CrewCommander CrewCommander
|
||||
{
|
||||
get { return commander; }
|
||||
}
|
||||
|
||||
public int Money
|
||||
{
|
||||
get { return money; }
|
||||
set { money = (int)Math.Max(value, 0.0f); }
|
||||
}
|
||||
|
||||
public CrewManager()
|
||||
{
|
||||
characters = new List<Character>();
|
||||
characterInfos = new List<CharacterInfo>();
|
||||
|
||||
guiFrame = new GUIFrame(new Rectangle(0, 50, 150, 450), Color.Transparent);
|
||||
guiFrame.Padding = Vector4.One * 5.0f;
|
||||
|
||||
listBox = new GUIListBox(new Rectangle(45, 30, 150, 0), Color.Transparent, null, guiFrame);
|
||||
listBox.ScrollBarEnabled = false;
|
||||
listBox.OnSelected = SelectCharacter;
|
||||
|
||||
orderListBox = new GUIListBox(new Rectangle(5, 30, 30, 0), Color.Transparent, null, guiFrame);
|
||||
orderListBox.ScrollBarEnabled = false;
|
||||
orderListBox.OnSelected = SelectCharacterOrder;
|
||||
|
||||
commander = new CrewCommander(this);
|
||||
|
||||
money = 10000;
|
||||
}
|
||||
|
||||
public CrewManager(XElement element)
|
||||
: this()
|
||||
{
|
||||
money = ToolBox.GetAttributeInt(element, "money", 0);
|
||||
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
if (subElement.Name.ToString().ToLowerInvariant() != "character") continue;
|
||||
|
||||
characterInfos.Add(new CharacterInfo(subElement));
|
||||
}
|
||||
}
|
||||
|
||||
public bool SelectCharacter(GUIComponent component, object selection)
|
||||
{
|
||||
//listBox.Select(selection);
|
||||
Character character = selection as Character;
|
||||
|
||||
if (character == null || character.IsDead || character.IsUnconscious) return false;
|
||||
|
||||
if (characters.Contains(character))
|
||||
{
|
||||
Character.Controlled = character;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void SetCharacterOrder(Character character, Order order)
|
||||
{
|
||||
if (order == null) return;
|
||||
|
||||
var characterFrame = listBox.FindChild(character);
|
||||
|
||||
if (characterFrame == null) return;
|
||||
|
||||
int characterIndex = listBox.children.IndexOf(characterFrame);
|
||||
|
||||
orderListBox.children[characterIndex].ClearChildren();
|
||||
|
||||
var img = new GUIImage(new Rectangle(0, 0, 30, 30), order.SymbolSprite, Alignment.Center, orderListBox.children[characterIndex]);
|
||||
img.Scale = 30.0f / img.SourceRect.Width;
|
||||
img.Color = order.Color;
|
||||
img.CanBeFocused = false;
|
||||
|
||||
orderListBox.children[characterIndex].ToolTip = "Order: " + order.Name;
|
||||
}
|
||||
|
||||
public bool SelectCharacterOrder(GUIComponent component, object selection)
|
||||
{
|
||||
commander.ToggleGUIFrame();
|
||||
|
||||
int orderIndex = orderListBox.children.IndexOf(component);
|
||||
if (orderIndex < 0 || orderIndex >= listBox.children.Count) return false;
|
||||
|
||||
var characterFrame = listBox.children[orderIndex];
|
||||
if (characterFrame == null) return false;
|
||||
|
||||
commander.SelectCharacter(characterFrame.UserData as Character);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void AddCharacter(Character character)
|
||||
{
|
||||
characters.Add(character);
|
||||
if (!characterInfos.Contains(character.Info))
|
||||
{
|
||||
characterInfos.Add(character.Info);
|
||||
}
|
||||
|
||||
if (character is AICharacter)
|
||||
{
|
||||
commander.UpdateCharacters();
|
||||
}
|
||||
|
||||
character.Info.CreateCharacterFrame(listBox, character.Info.Name.Replace(' ', '\n'), character);
|
||||
|
||||
GUIFrame orderFrame = new GUIFrame(new Rectangle(0, 0, 40, 40), Color.Transparent, "ListBoxElement", orderListBox);
|
||||
orderFrame.UserData = character;
|
||||
|
||||
var ai = character.AIController as HumanAIController;
|
||||
if (ai == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Error in crewmanager - attempted to give orders to a character with no HumanAIController");
|
||||
return;
|
||||
}
|
||||
SetCharacterOrder(character, ai.CurrentOrder);
|
||||
}
|
||||
|
||||
public void AddToGUIUpdateList()
|
||||
{
|
||||
guiFrame.AddToGUIUpdateList();
|
||||
if (commander.Frame != null) commander.Frame.AddToGUIUpdateList();
|
||||
}
|
||||
|
||||
public void Update(float deltaTime)
|
||||
{
|
||||
guiFrame.Update(deltaTime);
|
||||
|
||||
//TODO: implement AI commands in multiplayer?
|
||||
if (GameMain.NetworkMember == null &&
|
||||
GameMain.Config.KeyBind(InputType.CrewOrders).IsHit())
|
||||
{
|
||||
//deselect construction unless it's the ladders the character is climbing
|
||||
if (!commander.IsOpen && Character.Controlled != null &&
|
||||
Character.Controlled.SelectedConstruction != null &&
|
||||
Character.Controlled.SelectedConstruction.GetComponent<Items.Components.Ladder>() == null)
|
||||
{
|
||||
Character.Controlled.SelectedConstruction = null;
|
||||
}
|
||||
|
||||
//only allow opening the command UI if there are AICharacters in the crew
|
||||
if (commander.IsOpen || characters.Any(c => c is AICharacter)) commander.ToggleGUIFrame();
|
||||
}
|
||||
|
||||
if (commander.Frame != null) commander.Frame.Update(deltaTime);
|
||||
}
|
||||
|
||||
public void ReviveCharacter(Character revivedCharacter)
|
||||
{
|
||||
GUIComponent characterBlock = listBox.GetChild(revivedCharacter) as GUIComponent;
|
||||
if (characterBlock != null) characterBlock.Color = Color.Transparent;
|
||||
|
||||
if (revivedCharacter is AICharacter)
|
||||
{
|
||||
commander.UpdateCharacters();
|
||||
}
|
||||
}
|
||||
|
||||
public void KillCharacter(Character killedCharacter)
|
||||
{
|
||||
GUIComponent characterBlock = listBox.GetChild(killedCharacter) as GUIComponent;
|
||||
if (characterBlock != null) characterBlock.Color = Color.DarkRed * 0.5f;
|
||||
|
||||
if (killedCharacter is AICharacter)
|
||||
{
|
||||
commander.UpdateCharacters();
|
||||
}
|
||||
//if (characters.Find(c => !c.IsDead)==null)
|
||||
//{
|
||||
// Game1.GameSession.EndShift(null, null);
|
||||
//}
|
||||
}
|
||||
|
||||
public void CreateCrewFrame(List<Character> crew, GUIFrame crewFrame)
|
||||
{
|
||||
List<byte> teamIDs = crew.Select(c => c.TeamID).Distinct().ToList();
|
||||
|
||||
if (!teamIDs.Any()) teamIDs.Add(0);
|
||||
|
||||
int listBoxHeight = 300 / teamIDs.Count;
|
||||
|
||||
int y = 20;
|
||||
for (int i = 0; i < teamIDs.Count; i++)
|
||||
{
|
||||
if (teamIDs.Count > 1)
|
||||
{
|
||||
new GUITextBlock(new Rectangle(0, y - 20, 100, 20), CombatMission.GetTeamName(teamIDs[i]), "", crewFrame);
|
||||
}
|
||||
|
||||
GUIListBox crewList = new GUIListBox(new Rectangle(0, y, 280, listBoxHeight), Color.White * 0.7f, "", crewFrame);
|
||||
crewList.Padding = new Vector4(10.0f, 10.0f, 10.0f, 10.0f);
|
||||
crewList.OnSelected = (component, obj) =>
|
||||
{
|
||||
SelectCrewCharacter(component.UserData as Character, crewList);
|
||||
return true;
|
||||
};
|
||||
|
||||
foreach (Character character in crew.FindAll(c => c.TeamID == teamIDs[i]))
|
||||
{
|
||||
GUIFrame frame = new GUIFrame(new Rectangle(0, 0, 0, 40), Color.Transparent, "ListBoxElement", crewList);
|
||||
frame.UserData = character;
|
||||
frame.Padding = new Vector4(5.0f, 5.0f, 5.0f, 5.0f);
|
||||
frame.Color = (GameMain.NetworkMember != null && GameMain.NetworkMember.Character == character) ? Color.Gold * 0.2f : Color.Transparent;
|
||||
|
||||
GUITextBlock textBlock = new GUITextBlock(
|
||||
new Rectangle(40, 0, 0, 25),
|
||||
ToolBox.LimitString(character.Info.Name + " (" + character.Info.Job.Name + ")", GUI.Font, frame.Rect.Width-20),
|
||||
null,null,
|
||||
Alignment.Left, Alignment.Left,
|
||||
"", frame);
|
||||
textBlock.Padding = new Vector4(5.0f, 0.0f, 5.0f, 0.0f);
|
||||
|
||||
new GUIImage(new Rectangle(-10, 0, 0, 0), character.AnimController.Limbs[0].sprite, Alignment.Left, frame);
|
||||
}
|
||||
|
||||
y += crewList.Rect.Height + 30;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
protected virtual bool SelectCrewCharacter(Character character, GUIComponent crewList)
|
||||
{
|
||||
if (character == null) return false;
|
||||
|
||||
GUIComponent existingFrame = crewList.Parent.FindChild("selectedcharacter");
|
||||
if (existingFrame != null) crewList.Parent.RemoveChild(existingFrame);
|
||||
|
||||
var previewPlayer = new GUIFrame(
|
||||
new Rectangle(0, 0, 230, 300),
|
||||
new Color(0.0f, 0.0f, 0.0f, 0.8f), Alignment.TopRight, "", crewList.Parent);
|
||||
previewPlayer.Padding = new Vector4(5.0f, 5.0f, 5.0f, 5.0f);
|
||||
previewPlayer.UserData = "selectedcharacter";
|
||||
|
||||
character.Info.CreateInfoFrame(previewPlayer);
|
||||
|
||||
if (GameMain.NetworkMember != null) GameMain.NetworkMember.SelectCrewCharacter(character, crewList);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//private bool ToggleCrewFrame(GUIButton button, object obj)
|
||||
//{
|
||||
// if (crewFrame == null) CreateCrewFrame(characters);
|
||||
|
||||
// crewFrameOpen = !crewFrameOpen;
|
||||
// return true;
|
||||
//}
|
||||
|
||||
public void StartShift()
|
||||
{
|
||||
listBox.ClearChildren();
|
||||
characters.Clear();
|
||||
|
||||
WayPoint[] waypoints = WayPoint.SelectCrewSpawnPoints(characterInfos, Submarine.MainSub);
|
||||
|
||||
for (int i = 0; i < waypoints.Length; i++)
|
||||
{
|
||||
Character character;
|
||||
|
||||
if (characterInfos[i].HullID != null)
|
||||
{
|
||||
var hull = Entity.FindEntityByID((ushort)characterInfos[i].HullID) as Hull;
|
||||
if (hull == null) continue;
|
||||
character = Character.Create(characterInfos[i], hull.WorldPosition);
|
||||
}
|
||||
else
|
||||
{
|
||||
character = Character.Create(characterInfos[i], waypoints[i].WorldPosition);
|
||||
Character.Controlled = character;
|
||||
|
||||
if (character.Info != null && !character.Info.StartItemsGiven)
|
||||
{
|
||||
character.GiveJobItems(waypoints[i]);
|
||||
character.Info.StartItemsGiven = true;
|
||||
}
|
||||
}
|
||||
|
||||
AddCharacter(character);
|
||||
}
|
||||
|
||||
if (characters.Any()) listBox.Select(0);// SelectCharacter(null, characters[0]);
|
||||
}
|
||||
|
||||
public void EndShift()
|
||||
{
|
||||
foreach (Character c in characters)
|
||||
{
|
||||
if (!c.IsDead)
|
||||
{
|
||||
c.Info.UpdateCharacterItems();
|
||||
continue;
|
||||
}
|
||||
|
||||
CharacterInfo deadInfo = characterInfos.Find(x => c.Info == x);
|
||||
if (deadInfo != null) characterInfos.Remove(deadInfo);
|
||||
}
|
||||
|
||||
characters.Clear();
|
||||
listBox.ClearChildren();
|
||||
orderListBox.ClearChildren();
|
||||
}
|
||||
|
||||
public void Draw(SpriteBatch spriteBatch)
|
||||
{
|
||||
if (commander.IsOpen)
|
||||
{
|
||||
commander.Draw(spriteBatch);
|
||||
}
|
||||
else
|
||||
{
|
||||
guiFrame.Draw(spriteBatch);
|
||||
}
|
||||
}
|
||||
|
||||
public void Save(XElement parentElement)
|
||||
{
|
||||
XElement element = new XElement("crew");
|
||||
|
||||
element.Add(new XAttribute("money", money));
|
||||
|
||||
foreach (CharacterInfo ci in characterInfos)
|
||||
{
|
||||
ci.Save(element);
|
||||
}
|
||||
|
||||
parentElement.Add(element);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class GameMode
|
||||
{
|
||||
public static List<GameModePreset> PresetList = new List<GameModePreset>();
|
||||
|
||||
protected DateTime startTime;
|
||||
|
||||
//public readonly bool IsSinglePlayer;
|
||||
|
||||
protected bool isRunning;
|
||||
|
||||
//protected string name;
|
||||
|
||||
protected GameModePreset preset;
|
||||
|
||||
private string endMessage;
|
||||
|
||||
public virtual Mission Mission
|
||||
{
|
||||
get { return null; }
|
||||
}
|
||||
|
||||
public bool IsRunning
|
||||
{
|
||||
get { return isRunning; }
|
||||
}
|
||||
|
||||
public bool IsSinglePlayer
|
||||
{
|
||||
get { return preset.IsSinglePlayer; }
|
||||
}
|
||||
|
||||
public string Name
|
||||
{
|
||||
get { return preset.Name; }
|
||||
}
|
||||
|
||||
public string EndMessage
|
||||
{
|
||||
get { return endMessage; }
|
||||
}
|
||||
|
||||
public GameModePreset Preset
|
||||
{
|
||||
get { return preset; }
|
||||
}
|
||||
|
||||
public GameMode(GameModePreset preset, object param)
|
||||
{
|
||||
this.preset = preset;
|
||||
}
|
||||
|
||||
public virtual void Draw(SpriteBatch spriteBatch)
|
||||
{
|
||||
}
|
||||
|
||||
public virtual void Start()
|
||||
{
|
||||
startTime = DateTime.Now;
|
||||
//if (duration!=TimeSpan.Zero)
|
||||
//{
|
||||
// endTime = startTime + duration;
|
||||
// this.duration = duration;
|
||||
|
||||
// timerBar = new GUIProgressBar(new Rectangle(GameMain.GraphicsWidth - 120, 20, 100, 25), Color.Gold, 0.0f, null);
|
||||
//}
|
||||
|
||||
endMessage = "The round has ended!";
|
||||
|
||||
isRunning = true;
|
||||
}
|
||||
|
||||
public virtual void MsgBox() { }
|
||||
|
||||
public virtual void AddToGUIUpdateList() { }
|
||||
|
||||
public virtual void Update(float deltaTime)
|
||||
{
|
||||
//if (!isRunning) return;
|
||||
|
||||
//if (duration != TimeSpan.Zero)
|
||||
//{
|
||||
// double elapsedTime = (DateTime.Now - startTime).TotalSeconds;
|
||||
// timerBar.BarSize = (float)(elapsedTime / duration.TotalSeconds);
|
||||
//}
|
||||
//if (DateTime.Now >= endTime)
|
||||
//{
|
||||
// End(endMessage);
|
||||
//}
|
||||
}
|
||||
|
||||
public virtual void End(string endMessage = "")
|
||||
{
|
||||
isRunning = false;
|
||||
|
||||
if (endMessage != "" || this.endMessage == null) this.endMessage = endMessage;
|
||||
|
||||
GameMain.GameSession.EndShift(endMessage);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class GameModePreset
|
||||
{
|
||||
public static List<GameModePreset> list = new List<GameModePreset>();
|
||||
|
||||
public ConstructorInfo Constructor;
|
||||
public string Name;
|
||||
public bool IsSinglePlayer;
|
||||
|
||||
public string Description;
|
||||
|
||||
public GameModePreset(string name, Type type, bool isSinglePlayer = false)
|
||||
{
|
||||
this.Name = name;
|
||||
|
||||
Constructor = type.GetConstructor(new Type[] { typeof(GameModePreset), typeof(object) });
|
||||
|
||||
IsSinglePlayer = isSinglePlayer;
|
||||
|
||||
list.Add(this);
|
||||
}
|
||||
|
||||
public GameMode Instantiate(object param)
|
||||
{
|
||||
object[] lobject = new object[] { this, param };
|
||||
return (GameMode)Constructor.Invoke(lobject);
|
||||
}
|
||||
|
||||
public static void Init()
|
||||
{
|
||||
new GameModePreset("Single Player", typeof(SinglePlayerMode), true);
|
||||
new GameModePreset("Tutorial", typeof(TutorialMode), true);
|
||||
|
||||
var mode = new GameModePreset("SandBox", typeof(GameMode), false);
|
||||
mode.Description = "A game mode with no specific objectives.";
|
||||
|
||||
//mode = new GameModePreset("Traitor", typeof(TraitorMode), false);
|
||||
//mode.Description = "One of the players is selected as a traitor and given a secret objective. "
|
||||
// + "The rest of the crew will win if they reach the end of the level or kill the traitor "
|
||||
// + "before the objective is completed.";
|
||||
|
||||
mode = new GameModePreset("Mission", typeof(MissionMode), false);
|
||||
mode.Description = "The crew must work together to complete a specific task, such as retrieving "
|
||||
+ "an alien artifact or killing a creature that's terrorizing nearby outposts. The game ends "
|
||||
+ "when the task is completed or everyone in the crew has died.";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class MissionMode : GameMode
|
||||
{
|
||||
private Mission mission;
|
||||
|
||||
public override Mission Mission
|
||||
{
|
||||
get
|
||||
{
|
||||
return mission;
|
||||
}
|
||||
}
|
||||
|
||||
public MissionMode(GameModePreset preset, object param)
|
||||
: base(preset, param)
|
||||
{
|
||||
Location[] locations = { GameMain.GameSession.StartLocation, GameMain.GameSession.EndLocation };
|
||||
|
||||
MTRandom rand = new MTRandom(ToolBox.StringToInt(GameMain.NetLobbyScreen.LevelSeed));
|
||||
mission = Mission.LoadRandom(locations, rand, param as string);
|
||||
}
|
||||
|
||||
public override void MsgBox()
|
||||
{
|
||||
if (mission == null) return;
|
||||
|
||||
var missionMsg = new GUIMessageBox(mission.Name, mission.Description, 400, 400);
|
||||
missionMsg.UserData = "missionstartmessage";
|
||||
|
||||
Networking.GameServer.Log("Mission: " + mission.Name, Networking.ServerLog.MessageType.ServerMessage);
|
||||
Networking.GameServer.Log(mission.Description, Networking.ServerLog.MessageType.ServerMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,411 @@
|
||||
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 SinglePlayerMode : GameMode
|
||||
{
|
||||
//private const int StartCharacterAmount = 3;
|
||||
|
||||
//public readonly CrewManager CrewManager;
|
||||
//public readonly HireManager hireManager;
|
||||
|
||||
private GUIButton endShiftButton;
|
||||
|
||||
public readonly CargoManager CargoManager;
|
||||
|
||||
public Map Map;
|
||||
|
||||
private bool crewDead;
|
||||
private float endTimer;
|
||||
|
||||
private bool savedOnStart;
|
||||
|
||||
private List<Submarine> subsToLeaveBehind;
|
||||
|
||||
private Submarine leavingSub;
|
||||
private bool atEndPosition;
|
||||
|
||||
public override Mission Mission
|
||||
{
|
||||
get
|
||||
{
|
||||
return Map.SelectedConnection.Mission;
|
||||
}
|
||||
}
|
||||
|
||||
public int Money
|
||||
{
|
||||
get { return GameMain.GameSession.CrewManager.Money; }
|
||||
set { GameMain.GameSession.CrewManager.Money = value; }
|
||||
}
|
||||
|
||||
private CrewManager CrewManager
|
||||
{
|
||||
get { return GameMain.GameSession.CrewManager; }
|
||||
}
|
||||
|
||||
public SinglePlayerMode(GameModePreset preset, object param)
|
||||
: base(preset, param)
|
||||
{
|
||||
|
||||
CargoManager = new CargoManager();
|
||||
|
||||
endShiftButton = new GUIButton(new Rectangle(GameMain.GraphicsWidth - 220, 20, 200, 25), "End shift", null, Alignment.TopLeft, Alignment.Center, "");
|
||||
endShiftButton.Font = GUI.SmallFont;
|
||||
endShiftButton.OnClicked = TryEndShift;
|
||||
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
JobPrefab jobPrefab = null;
|
||||
switch (i)
|
||||
{
|
||||
case 0:
|
||||
jobPrefab = JobPrefab.List.Find(jp => jp.Name == "Captain");
|
||||
break;
|
||||
case 1:
|
||||
jobPrefab = JobPrefab.List.Find(jp => jp.Name == "Engineer");
|
||||
break;
|
||||
case 2:
|
||||
jobPrefab = JobPrefab.List.Find(jp => jp.Name == "Mechanic");
|
||||
break;
|
||||
}
|
||||
|
||||
CharacterInfo characterInfo =
|
||||
new CharacterInfo(Character.HumanConfigFile, "", Gender.None, jobPrefab);
|
||||
CrewManager.characterInfos.Add(characterInfo);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public SinglePlayerMode(XElement element)
|
||||
: this(GameModePreset.list.Find(gm => gm.Name == "Single Player"), null)
|
||||
{
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "crew":
|
||||
GameMain.GameSession.CrewManager = new CrewManager(subElement);
|
||||
break;
|
||||
case "map":
|
||||
Map = Map.Load(subElement);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//backwards compatibility with older save files
|
||||
if (Map==null)
|
||||
{
|
||||
string mapSeed = ToolBox.GetAttributeString(element, "mapseed", "a");
|
||||
|
||||
GenerateMap(mapSeed);
|
||||
|
||||
Map.SetLocation(ToolBox.GetAttributeInt(element, "currentlocation", 0));
|
||||
}
|
||||
|
||||
savedOnStart = true;
|
||||
}
|
||||
|
||||
public void GenerateMap(string seed)
|
||||
{
|
||||
Map = new Map(seed, 500);
|
||||
}
|
||||
|
||||
public override void Start()
|
||||
{
|
||||
CargoManager.CreateItems();
|
||||
|
||||
if (!savedOnStart)
|
||||
{
|
||||
SaveUtil.SaveGame(GameMain.GameSession.SaveFile);
|
||||
savedOnStart = true;
|
||||
}
|
||||
|
||||
endTimer = 5.0f;
|
||||
|
||||
isRunning = true;
|
||||
|
||||
CrewManager.StartShift();
|
||||
}
|
||||
|
||||
public bool TryHireCharacter(HireManager hireManager, CharacterInfo characterInfo)
|
||||
{
|
||||
if (CrewManager.Money < characterInfo.Salary) return false;
|
||||
|
||||
hireManager.availableCharacters.Remove(characterInfo);
|
||||
CrewManager.characterInfos.Add(characterInfo);
|
||||
|
||||
CrewManager.Money -= characterInfo.Salary;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public string GetMoney()
|
||||
{
|
||||
return "Money: " + CrewManager.Money;
|
||||
}
|
||||
|
||||
|
||||
private Submarine GetLeavingSub()
|
||||
{
|
||||
if (Character.Controlled != null && Character.Controlled.Submarine != null)
|
||||
{
|
||||
if (Character.Controlled.Submarine.AtEndPosition || Character.Controlled.Submarine.AtStartPosition)
|
||||
{
|
||||
return Character.Controlled.Submarine;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Submarine closestSub = Submarine.FindClosest(GameMain.GameScreen.Cam.WorldViewCenter);
|
||||
if (closestSub != null && (closestSub.AtEndPosition || closestSub.AtStartPosition))
|
||||
{
|
||||
return closestSub.DockedTo.Contains(Submarine.MainSub) ? Submarine.MainSub : closestSub;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private List<Submarine> GetSubsToLeaveBehind(Submarine leavingSub)
|
||||
{
|
||||
//leave subs behind if they're not docked to the leaving sub and not at the same exit
|
||||
return Submarine.Loaded.FindAll(s =>
|
||||
s != leavingSub &&
|
||||
!leavingSub.DockedTo.Contains(s) &&
|
||||
(s.AtEndPosition != leavingSub.AtEndPosition || s.AtStartPosition != leavingSub.AtStartPosition));
|
||||
}
|
||||
|
||||
public override void Draw(SpriteBatch spriteBatch)
|
||||
{
|
||||
if (!isRunning|| GUI.DisableHUD) return;
|
||||
|
||||
CrewManager.Draw(spriteBatch);
|
||||
|
||||
if (Submarine.MainSub == null) return;
|
||||
|
||||
Submarine leavingSub = GetLeavingSub();
|
||||
|
||||
if (leavingSub == null)
|
||||
{
|
||||
endShiftButton.Visible = false;
|
||||
}
|
||||
else if (leavingSub.AtEndPosition)
|
||||
{
|
||||
endShiftButton.Text = ToolBox.LimitString("Enter " + Map.SelectedLocation.Name, endShiftButton.Font, endShiftButton.Rect.Width - 5);
|
||||
endShiftButton.UserData = leavingSub;
|
||||
endShiftButton.Visible = true;
|
||||
}
|
||||
else if (leavingSub.AtStartPosition)
|
||||
{
|
||||
endShiftButton.Text = ToolBox.LimitString("Enter " + Map.CurrentLocation.Name, endShiftButton.Font, endShiftButton.Rect.Width - 5);
|
||||
endShiftButton.UserData = leavingSub;
|
||||
endShiftButton.Visible = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
endShiftButton.Visible = false;
|
||||
}
|
||||
|
||||
endShiftButton.Draw(spriteBatch);
|
||||
}
|
||||
|
||||
public override void AddToGUIUpdateList()
|
||||
{
|
||||
if (!isRunning) return;
|
||||
|
||||
base.AddToGUIUpdateList();
|
||||
|
||||
CrewManager.AddToGUIUpdateList();
|
||||
|
||||
endShiftButton.AddToGUIUpdateList();
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
if (!isRunning || GUI.DisableHUD) return;
|
||||
|
||||
base.Update(deltaTime);
|
||||
|
||||
CrewManager.Update(deltaTime);
|
||||
|
||||
endShiftButton.Update(deltaTime);
|
||||
|
||||
if (!crewDead)
|
||||
{
|
||||
if (!CrewManager.characters.Any(c => !c.IsDead)) crewDead = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
endTimer -= deltaTime;
|
||||
|
||||
if (endTimer <= 0.0f) EndShift(null, null);
|
||||
}
|
||||
}
|
||||
|
||||
public override void End(string endMessage = "")
|
||||
{
|
||||
isRunning = false;
|
||||
|
||||
bool success = CrewManager.characters.Any(c => !c.IsDead);
|
||||
|
||||
if (success)
|
||||
{
|
||||
if (subsToLeaveBehind == null || leavingSub == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Leaving submarine not selected -> selecting the closest one");
|
||||
|
||||
leavingSub = GetLeavingSub();
|
||||
|
||||
subsToLeaveBehind = GetSubsToLeaveBehind(leavingSub);
|
||||
}
|
||||
}
|
||||
|
||||
GameMain.GameSession.EndShift("");
|
||||
|
||||
if (success)
|
||||
{
|
||||
if (leavingSub != Submarine.MainSub && !leavingSub.DockedTo.Contains(Submarine.MainSub))
|
||||
{
|
||||
Submarine.MainSub = leavingSub;
|
||||
|
||||
GameMain.GameSession.Submarine = leavingSub;
|
||||
|
||||
foreach (Submarine sub in subsToLeaveBehind)
|
||||
{
|
||||
MapEntity.mapEntityList.RemoveAll(e => e.Submarine == sub && e is LinkedSubmarine);
|
||||
LinkedSubmarine.CreateDummy(leavingSub, sub);
|
||||
}
|
||||
}
|
||||
|
||||
if (atEndPosition)
|
||||
{
|
||||
Map.MoveToNextLocation();
|
||||
}
|
||||
|
||||
SaveUtil.SaveGame(GameMain.GameSession.SaveFile);
|
||||
}
|
||||
|
||||
|
||||
if (!success)
|
||||
{
|
||||
var summaryScreen = GUIMessageBox.VisibleBox;
|
||||
|
||||
if (summaryScreen != null)
|
||||
{
|
||||
summaryScreen = summaryScreen.children[0];
|
||||
summaryScreen.RemoveChild(summaryScreen.children.Find(c => c is GUIButton));
|
||||
|
||||
var okButton = new GUIButton(new Rectangle(-120, 0, 100, 30), "Load game", Alignment.BottomRight, "", summaryScreen);
|
||||
okButton.OnClicked += GameMain.GameSession.LoadPrevious;
|
||||
okButton.OnClicked += (GUIButton button, object obj) => { GUIMessageBox.MessageBoxes.Remove(GUIMessageBox.VisibleBox); return true; };
|
||||
|
||||
var quitButton = new GUIButton(new Rectangle(0, 0, 100, 30), "Quit", Alignment.BottomRight, "", summaryScreen);
|
||||
quitButton.OnClicked += GameMain.LobbyScreen.QuitToMainMenu;
|
||||
quitButton.OnClicked += (GUIButton button, object obj) => { GUIMessageBox.MessageBoxes.Remove(GUIMessageBox.VisibleBox); return true; };
|
||||
}
|
||||
}
|
||||
|
||||
CrewManager.EndShift();
|
||||
for (int i = Character.CharacterList.Count - 1; i >= 0; i--)
|
||||
{
|
||||
Character.CharacterList[i].Remove();
|
||||
}
|
||||
|
||||
Submarine.Unload();
|
||||
}
|
||||
|
||||
private bool TryEndShift(GUIButton button, object obj)
|
||||
{
|
||||
leavingSub = obj as Submarine;
|
||||
if (leavingSub != null)
|
||||
{
|
||||
subsToLeaveBehind = GetSubsToLeaveBehind(leavingSub);
|
||||
}
|
||||
|
||||
atEndPosition = leavingSub.AtEndPosition;
|
||||
|
||||
if (subsToLeaveBehind.Any())
|
||||
{
|
||||
string msg = "";
|
||||
if (subsToLeaveBehind.Count == 1)
|
||||
{
|
||||
msg = "One of your vessels isn't at the exit yet. Do you want to leave it behind?";
|
||||
}
|
||||
else
|
||||
{
|
||||
msg = "Some of your vessels aren't at the exit yet. Do you want to leave them behind?";
|
||||
}
|
||||
|
||||
var msgBox = new GUIMessageBox("Warning", msg, new string[] {"Yes", "No"});
|
||||
msgBox.Buttons[0].OnClicked += EndShift;
|
||||
msgBox.Buttons[0].OnClicked += msgBox.Close;
|
||||
msgBox.Buttons[0].UserData = Submarine.Loaded.FindAll(s => !subsToLeaveBehind.Contains(s));
|
||||
|
||||
msgBox.Buttons[1].OnClicked += msgBox.Close;
|
||||
}
|
||||
else
|
||||
{
|
||||
EndShift(button, obj);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool EndShift(GUIButton button, object obj)
|
||||
{
|
||||
isRunning = false;
|
||||
|
||||
List<Submarine> leavingSubs = obj as List<Submarine>;
|
||||
if (leavingSubs == null) leavingSubs = new List<Submarine>() { GetLeavingSub() };
|
||||
|
||||
var cinematic = new TransitionCinematic(leavingSubs, GameMain.GameScreen.Cam, 5.0f);
|
||||
|
||||
SoundPlayer.OverrideMusicType = CrewManager.characters.Any(c => !c.IsDead) ? "endshift" : "crewdead";
|
||||
|
||||
CoroutineManager.StartCoroutine(EndCinematic(cinematic),"EndCinematic");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private IEnumerable<object> EndCinematic(TransitionCinematic cinematic)
|
||||
{
|
||||
while (cinematic.Running)
|
||||
{
|
||||
if (Submarine.MainSub == null) yield return CoroutineStatus.Success;
|
||||
|
||||
yield return CoroutineStatus.Running;
|
||||
}
|
||||
|
||||
if (Submarine.MainSub == null) yield return CoroutineStatus.Success;
|
||||
|
||||
End("");
|
||||
|
||||
yield return new WaitForSeconds(18.0f);
|
||||
|
||||
SoundPlayer.OverrideMusicType = null;
|
||||
|
||||
yield return CoroutineStatus.Success;
|
||||
}
|
||||
|
||||
public void Save(XElement element)
|
||||
{
|
||||
//element.Add(new XAttribute("day", day));
|
||||
XElement modeElement = new XElement("gamemode");
|
||||
|
||||
//modeElement.Add(new XAttribute("currentlocation", Map.CurrentLocationIndex));
|
||||
//modeElement.Add(new XAttribute("mapseed", Map.Seed));
|
||||
|
||||
|
||||
CrewManager.Save(modeElement);
|
||||
Map.Save(modeElement);
|
||||
|
||||
element.Add(modeElement);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
using Barotrauma.Networking;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class TraitorManager
|
||||
{
|
||||
public Character TraitorCharacter
|
||||
{
|
||||
get { return traitorCharacter; }
|
||||
}
|
||||
|
||||
public Character TargetCharacter
|
||||
{
|
||||
get { return targetCharacter; }
|
||||
}
|
||||
|
||||
private Character traitorCharacter, targetCharacter;
|
||||
|
||||
public TraitorManager(GameServer server)
|
||||
{
|
||||
Start(server);
|
||||
}
|
||||
|
||||
private void Start(GameServer server)
|
||||
{
|
||||
if (server == null) return;
|
||||
|
||||
List<Character> characters = new List<Character>();
|
||||
foreach (Client client in server.ConnectedClients)
|
||||
{
|
||||
if (client.Character != null)
|
||||
characters.Add(client.Character);
|
||||
}
|
||||
|
||||
if (server.Character!= null) characters.Add(server.Character);
|
||||
|
||||
if (characters.Count < 2)
|
||||
{
|
||||
traitorCharacter = null;
|
||||
targetCharacter = null;
|
||||
return;
|
||||
}
|
||||
|
||||
int traitorIndex = Rand.Range(0, characters.Count);
|
||||
|
||||
int targetIndex = Rand.Range(0, characters.Count);
|
||||
while (targetIndex == traitorIndex)
|
||||
{
|
||||
targetIndex = Rand.Range(0, characters.Count);
|
||||
}
|
||||
|
||||
traitorCharacter = characters[traitorIndex];
|
||||
targetCharacter = characters[targetIndex];
|
||||
|
||||
if (server.Character == null)
|
||||
{
|
||||
new GUIMessageBox("New traitor", traitorCharacter.Name + " is the traitor and the target is " + targetCharacter.Name+".");
|
||||
}
|
||||
else if (server.Character == traitorCharacter)
|
||||
{
|
||||
CreateStartPopUp(traitorCharacter.Name);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
public static void CreateStartPopUp(string targetName)
|
||||
{
|
||||
new GUIMessageBox("You are the Traitor!",
|
||||
"Your secret task is to assassinate " + targetName + "! Discretion is an utmost concern; sinking the submarine and killing the entire crew "
|
||||
+ "will arouse suspicion amongst the Fleet. If possible, make the death look like an accident.", 400, 350);
|
||||
}
|
||||
|
||||
public string GetEndMessage()
|
||||
{
|
||||
if (GameMain.Server == null || traitorCharacter == null || targetCharacter == null) return "";
|
||||
|
||||
string endMessage = "";
|
||||
|
||||
if (targetCharacter.IsDead && !traitorCharacter.IsDead)
|
||||
{
|
||||
endMessage = traitorCharacter.Name + " was a traitor! ";
|
||||
endMessage += (traitorCharacter.Info.Gender == Gender.Male) ? "His" : "Her";
|
||||
endMessage += " task was to assassinate " + targetCharacter.Name + ". The task was successful.";
|
||||
}
|
||||
else if (targetCharacter.IsDead && traitorCharacter.IsDead)
|
||||
{
|
||||
endMessage = traitorCharacter.Name + " was a traitor! ";
|
||||
endMessage += (traitorCharacter.Info.Gender == Gender.Male) ? "His" : "Her";
|
||||
endMessage += " task was to assassinate " + targetCharacter.Name + ". The task was successful, but luckily the bastard didn't make it out alive either.";
|
||||
}
|
||||
else if (traitorCharacter.IsDead)
|
||||
{
|
||||
endMessage = traitorCharacter.Name + " was a traitor! ";
|
||||
endMessage += (traitorCharacter.Info.Gender == Gender.Male) ? "His" : "Her";
|
||||
endMessage += " task was to assassinate " + targetCharacter.Name + ", but ";
|
||||
endMessage += (traitorCharacter.Info.Gender == Gender.Male) ? "he" : "she";
|
||||
endMessage += " got " + ((traitorCharacter.Info.Gender == Gender.Male) ? "himself" : "herself");
|
||||
endMessage += " killed before completing it.";
|
||||
}
|
||||
else
|
||||
{
|
||||
endMessage = traitorCharacter.Name + " was a traitor! ";
|
||||
endMessage += (traitorCharacter.Info.Gender == Gender.Male) ? "His" : "Her";
|
||||
endMessage += " task was to assassinate " + targetCharacter.Name + ". ";
|
||||
endMessage += (Submarine.MainSub.AtEndPosition) ?
|
||||
"The task was unsuccessful - the submarine has reached its destination." :
|
||||
"The task was unsuccessful.";
|
||||
}
|
||||
|
||||
return endMessage;
|
||||
}
|
||||
|
||||
//public void CharacterLeft(Character character)
|
||||
//{
|
||||
// if (character != traitorCharacter && character != targetCharacter) return;
|
||||
|
||||
// if (character == traitorCharacter)
|
||||
// {
|
||||
// string endMessage = "The traitor has disconnected from the server.";
|
||||
// End(endMessage);
|
||||
// }
|
||||
// else if (character == targetCharacter)
|
||||
// {
|
||||
// string endMessage = "The traitor's target has disconnected from the server.";
|
||||
// End(endMessage);
|
||||
// }
|
||||
//}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,674 @@
|
||||
using Barotrauma.Items.Components;
|
||||
using FarseerPhysics;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Barotrauma.Tutorials
|
||||
{
|
||||
class BasicTutorial : TutorialType
|
||||
{
|
||||
public BasicTutorial(string name)
|
||||
:base (name)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public override IEnumerable<object> UpdateState()
|
||||
{
|
||||
//spawn some fish next to the player
|
||||
GameMain.GameScreen.BackgroundCreatureManager.SpawnSprites(2,
|
||||
Submarine.MainSub.Position + Character.Controlled.Position);
|
||||
|
||||
foreach (Item item in Item.ItemList)
|
||||
{
|
||||
var wire = item.GetComponent<Wire>();
|
||||
if (wire != null && wire.Connections.Any(c => c != null))
|
||||
{
|
||||
wire.Locked = true;
|
||||
}
|
||||
}
|
||||
|
||||
yield return new WaitForSeconds(4.0f);
|
||||
|
||||
infoBox = CreateInfoFrame("Use WASD to move and the mouse to look around");
|
||||
|
||||
yield return new WaitForSeconds(5.0f);
|
||||
|
||||
//-----------------------------------
|
||||
|
||||
infoBox = CreateInfoFrame("Open the door at your right side by highlighting the button next to it with your cursor and pressing E");
|
||||
|
||||
Door tutorialDoor = Item.ItemList.Find(i => i.HasTag("tutorialdoor")).GetComponent<Door>();
|
||||
|
||||
while (!tutorialDoor.IsOpen && Character.Controlled.WorldPosition.X < tutorialDoor.Item.WorldPosition.X)
|
||||
{
|
||||
yield return CoroutineStatus.Running;
|
||||
}
|
||||
|
||||
yield return new WaitForSeconds(2.0f);
|
||||
|
||||
//-----------------------------------
|
||||
|
||||
infoBox = CreateInfoFrame("Hold W or S to walk up or down stairs. Use shift to run.", true);
|
||||
|
||||
while (infoBox != null)
|
||||
{
|
||||
yield return CoroutineStatus.Running;
|
||||
}
|
||||
|
||||
//-----------------------------------
|
||||
|
||||
infoBox = CreateInfoFrame("At the moment the submarine has no power, which means that crucial systems such as the oxygen generator or the engine aren't running. Let's fix this: go to the upper left corner of the submarine, where you'll find a nuclear reactor.");
|
||||
|
||||
Reactor reactor = Item.ItemList.Find(i => i.HasTag("tutorialreactor")).GetComponent<Reactor>();
|
||||
reactor.MeltDownTemp = 20000.0f;
|
||||
|
||||
while (Vector2.Distance(Character.Controlled.Position, reactor.Item.Position) > 200.0f)
|
||||
{
|
||||
yield return CoroutineStatus.Running;
|
||||
}
|
||||
|
||||
infoBox = CreateInfoFrame("The reactor requires fuel rods to generate power. You can grab one from the steel cabinet by walking next to it and pressing E.");
|
||||
|
||||
while (Character.Controlled.SelectedConstruction == null || Character.Controlled.SelectedConstruction.Name != "Steel Cabinet")
|
||||
{
|
||||
yield return CoroutineStatus.Running;
|
||||
}
|
||||
|
||||
infoBox = CreateInfoFrame("Pick up one of the fuel rods either by double-clicking or dragging and dropping it into your inventory.");
|
||||
|
||||
while (!HasItem("Fuel Rod"))
|
||||
{
|
||||
yield return CoroutineStatus.Running;
|
||||
}
|
||||
|
||||
infoBox = CreateInfoFrame("Select the reactor by walking next to it and pressing E.");
|
||||
|
||||
while (Character.Controlled.SelectedConstruction != reactor.Item)
|
||||
{
|
||||
yield return CoroutineStatus.Running;
|
||||
}
|
||||
yield return new WaitForSeconds(0.5f);
|
||||
|
||||
infoBox = CreateInfoFrame("Load the fuel rod into the reactor by dropping it into any of the 5 slots.");
|
||||
|
||||
while (reactor.AvailableFuel <= 0.0f)
|
||||
{
|
||||
yield return CoroutineStatus.Running;
|
||||
}
|
||||
|
||||
infoBox = CreateInfoFrame("The reactor is now fueled up. Try turning it on by increasing the fission rate.");
|
||||
|
||||
while (reactor.FissionRate <= 0.0f)
|
||||
{
|
||||
yield return CoroutineStatus.Running;
|
||||
}
|
||||
yield return new WaitForSeconds(0.5f);
|
||||
|
||||
infoBox = CreateInfoFrame("The reactor core has started generating heat, which in turn generates power for the submarine. The power generation is very low at the moment,"
|
||||
+ " because the reactor is set to shut itself down when the temperature rises above 500 degrees Celsius. You can adjust the temperature limit by changing the \"Shutdown Temperature\" in the control panel.", true);
|
||||
|
||||
while (infoBox != null)
|
||||
{
|
||||
reactor.ShutDownTemp = Math.Min(reactor.ShutDownTemp, 5000.0f);
|
||||
yield return CoroutineStatus.Running;
|
||||
}
|
||||
yield return new WaitForSeconds(0.5f);
|
||||
|
||||
infoBox = CreateInfoFrame("The amount of power generated by the reactor should be kept close to the amount of power consumed by the devices in the submarine. "
|
||||
+ "If there isn't enough power, devices won't function properly (or at all), and if there's too much power, some devices may be damaged."
|
||||
+ " Try to raise the temperature of the reactor close to 3000 degrees by adjusting the fission and cooling rates.", true);
|
||||
|
||||
while (Math.Abs(reactor.Temperature - 3000.0f) > 100.0f)
|
||||
{
|
||||
reactor.AutoTemp = false;
|
||||
reactor.ShutDownTemp = Math.Min(reactor.ShutDownTemp, 5000.0f);
|
||||
yield return CoroutineStatus.Running;
|
||||
}
|
||||
yield return new WaitForSeconds(0.5f);
|
||||
|
||||
infoBox = CreateInfoFrame("Looks like we're up and running! Now you should turn on the \"Automatic temperature control\", which will make the reactor "
|
||||
+ "automatically adjust the temperature to a suitable level. Even though it's an easy way to keep the reactor up and running most of the time, "
|
||||
+ "you should keep in mind that it changes the temperature very slowly and carefully, which may cause issues if there are sudden changes in grid load.");
|
||||
|
||||
while (!reactor.AutoTemp)
|
||||
{
|
||||
yield return CoroutineStatus.Running;
|
||||
}
|
||||
yield return new WaitForSeconds(0.5f);
|
||||
|
||||
infoBox = CreateInfoFrame("That's the basics of operating the reactor! Now that there's power available for the engines, it's time to get the submarine moving. "
|
||||
+ "Deselect the reactor by pressing E and head to the command room at the right edge of the vessel.");
|
||||
|
||||
Steering steering = Item.ItemList.Find(i => i.HasTag("tutorialsteering")).GetComponent<Steering>();
|
||||
Radar radar = steering.Item.GetComponent<Radar>();
|
||||
|
||||
while (Vector2.Distance(Character.Controlled.Position, steering.Item.Position) > 150.0f)
|
||||
{
|
||||
yield return CoroutineStatus.Running;
|
||||
}
|
||||
|
||||
CoroutineManager.StartCoroutine(KeepReactorRunning(reactor));
|
||||
|
||||
infoBox = CreateInfoFrame("Select the navigation terminal by walking next to it and pressing E.");
|
||||
|
||||
while (Character.Controlled.SelectedConstruction != steering.Item)
|
||||
{
|
||||
yield return CoroutineStatus.Running;
|
||||
}
|
||||
yield return new WaitForSeconds(0.5f);
|
||||
|
||||
infoBox = CreateInfoFrame("There seems to be something wrong with the navigation terminal." +
|
||||
" There's nothing on the monitor, so it's probably out of power. The reactor must still be"
|
||||
+ " running or the lights would've gone out, so it's most likely a problem with the wiring."
|
||||
+ " Deselect the terminal by pressing E to start checking the wiring.");
|
||||
|
||||
while (Character.Controlled.SelectedConstruction == steering.Item)
|
||||
{
|
||||
yield return CoroutineStatus.Running;
|
||||
}
|
||||
yield return new WaitForSeconds(1.0f);
|
||||
|
||||
infoBox = CreateInfoFrame("You need a screwdriver to check the wiring of the terminal."
|
||||
+ " Equip a screwdriver by pulling it to either of the slots with a hand symbol, and then use it on the terminal by left clicking.");
|
||||
|
||||
while (Character.Controlled.SelectedConstruction != steering.Item ||
|
||||
Character.Controlled.SelectedItems.FirstOrDefault(i => i != null && i.Name == "Screwdriver") == null)
|
||||
{
|
||||
yield return CoroutineStatus.Running;
|
||||
}
|
||||
|
||||
|
||||
infoBox = CreateInfoFrame("Here you can see all the wires connected to the terminal. Apparently there's no wire"
|
||||
+ " going into the to the power connection - that's why the monitor isn't working."
|
||||
+ " You should find a piece of wire to connect it. Try searching some of the cabinets scattered around the sub.");
|
||||
|
||||
while (!HasItem("Wire"))
|
||||
{
|
||||
yield return CoroutineStatus.Running;
|
||||
}
|
||||
|
||||
infoBox = CreateInfoFrame("Head back to the navigation terminal to fix the wiring.");
|
||||
|
||||
PowerTransfer junctionBox = Item.ItemList.Find(i => i != null && i.HasTag("tutorialjunctionbox")).GetComponent<PowerTransfer>();
|
||||
|
||||
while ((Character.Controlled.SelectedConstruction != junctionBox.Item &&
|
||||
Character.Controlled.SelectedConstruction != steering.Item) ||
|
||||
Character.Controlled.SelectedItems.FirstOrDefault(i => i != null && i.Name == "Screwdriver") == null)
|
||||
{
|
||||
yield return CoroutineStatus.Running;
|
||||
}
|
||||
|
||||
if (Character.Controlled.SelectedItems.FirstOrDefault(i => i != null && i.GetComponent<Wire>() != null) == null)
|
||||
{
|
||||
infoBox = CreateInfoFrame("Equip the wire by dragging it to one of the slots with a hand symbol.");
|
||||
|
||||
while (Character.Controlled.SelectedItems.FirstOrDefault(i => i != null && i.GetComponent<Wire>() != null) == null)
|
||||
{
|
||||
yield return CoroutineStatus.Running;
|
||||
}
|
||||
}
|
||||
|
||||
infoBox = CreateInfoFrame("You can see the equipped wire at the middle of the connection panel. Drag it to the power connector.");
|
||||
|
||||
var steeringConnection = steering.Item.Connections.Find(c => c.Name.Contains("power"));
|
||||
|
||||
while (steeringConnection.Wires.FirstOrDefault(w => w != null) == null)
|
||||
{
|
||||
yield return CoroutineStatus.Running;
|
||||
|
||||
}
|
||||
|
||||
infoBox = CreateInfoFrame("Now you have to connect the other end of the wire to a power source. "
|
||||
+ "The junction box in the room just below the command room should do.");
|
||||
|
||||
while (Character.Controlled.SelectedConstruction != null)
|
||||
{
|
||||
yield return CoroutineStatus.Running;
|
||||
}
|
||||
|
||||
yield return new WaitForSeconds(2.0f);
|
||||
|
||||
infoBox = CreateInfoFrame("You can now move the other end of the wire around, and attach it on the wall by left clicking or "
|
||||
+ "remove the previous attachment by right clicking. Or if you don't care for neatly laid out wiring, you can just "
|
||||
+ "run it straight to the junction box.");
|
||||
|
||||
while (Character.Controlled.SelectedConstruction == null || Character.Controlled.SelectedConstruction.GetComponent<PowerTransfer>() == null)
|
||||
{
|
||||
yield return CoroutineStatus.Running;
|
||||
}
|
||||
|
||||
infoBox = CreateInfoFrame("Connect the wire to the junction box by pulling it to the power connection, the same way you did with the navigation terminal.");
|
||||
|
||||
while (radar.Voltage < 0.1f)
|
||||
{
|
||||
yield return CoroutineStatus.Running;
|
||||
}
|
||||
|
||||
infoBox = CreateInfoFrame("Great! Now we should be able to get moving.");
|
||||
|
||||
|
||||
while (Character.Controlled.SelectedConstruction != steering.Item)
|
||||
{
|
||||
yield return CoroutineStatus.Running;
|
||||
}
|
||||
|
||||
infoBox = CreateInfoFrame("You can take a look at the area around the sub by selecting the \"Sonar\" checkbox.");
|
||||
|
||||
while (!radar.IsActive)
|
||||
{
|
||||
yield return CoroutineStatus.Running;
|
||||
}
|
||||
yield return new WaitForSeconds(0.5f);
|
||||
|
||||
infoBox = CreateInfoFrame("The green rectangle in the middle is the submarine, and the flickering shapes outside it are the walls of an underwater cavern. "
|
||||
+ "Try moving the submarine by clicking somewhere on the monitor and dragging the pointer to the direction you want to go to.");
|
||||
|
||||
while (steering.TargetVelocity == Vector2.Zero && steering.TargetVelocity.Length() < 50.0f)
|
||||
{
|
||||
yield return CoroutineStatus.Running;
|
||||
}
|
||||
yield return new WaitForSeconds(4.0f);
|
||||
|
||||
infoBox = CreateInfoFrame("The submarine moves up and down by pumping water in and out of the two ballast tanks at the bottom of the submarine. "
|
||||
+ "The engine at the back of the sub moves it forwards and backwards.", true);
|
||||
|
||||
while (infoBox != null)
|
||||
{
|
||||
yield return CoroutineStatus.Running;
|
||||
}
|
||||
|
||||
infoBox = CreateInfoFrame("Steer the submarine downwards, heading further into the cavern.");
|
||||
|
||||
while (Submarine.MainSub.WorldPosition.Y > 40000.0f)
|
||||
{
|
||||
yield return CoroutineStatus.Running;
|
||||
}
|
||||
yield return new WaitForSeconds(1.0f);
|
||||
|
||||
var moloch = Character.Create(
|
||||
"Content/Characters/Moloch/moloch.xml",
|
||||
steering.Item.WorldPosition + new Vector2(3000.0f, -500.0f));
|
||||
|
||||
moloch.PlaySound(CharacterSound.SoundType.Attack);
|
||||
|
||||
yield return new WaitForSeconds(1.0f);
|
||||
|
||||
infoBox = CreateInfoFrame("Uh-oh... Something enormous just appeared on the radar.");
|
||||
|
||||
List<Structure> windows = new List<Structure>();
|
||||
foreach (Structure s in Structure.WallList)
|
||||
{
|
||||
if (s.CastShadow || !s.HasBody) continue;
|
||||
|
||||
if (s.Rect.Right > steering.Item.CurrentHull.Rect.Right) windows.Add(s);
|
||||
}
|
||||
|
||||
float slowdownTimer = 1.0f;
|
||||
bool broken = false;
|
||||
do
|
||||
{
|
||||
steering.TargetVelocity = Vector2.Zero;
|
||||
|
||||
slowdownTimer = Math.Max(0.0f, slowdownTimer - CoroutineManager.DeltaTime*0.3f);
|
||||
Submarine.MainSub.Velocity *= slowdownTimer;
|
||||
|
||||
moloch.AIController.SelectTarget(steering.Item.CurrentHull.AiTarget);
|
||||
Vector2 steeringDir = windows[0].WorldPosition - moloch.WorldPosition;
|
||||
if (steeringDir != Vector2.Zero) steeringDir = Vector2.Normalize(steeringDir);
|
||||
|
||||
moloch.AIController.SteeringManager.SteeringManual(CoroutineManager.DeltaTime, steeringDir*100.0f);
|
||||
|
||||
foreach (Structure window in windows)
|
||||
{
|
||||
for (int i = 0; i < window.SectionCount; i++)
|
||||
{
|
||||
if (!window.SectionIsLeaking(i)) continue;
|
||||
broken = true;
|
||||
break;
|
||||
}
|
||||
if (broken) break;
|
||||
}
|
||||
if (broken) break;
|
||||
|
||||
yield return CoroutineStatus.Running;
|
||||
} while (!broken);
|
||||
|
||||
//fix everything except the command windows
|
||||
foreach (Structure w in Structure.WallList)
|
||||
{
|
||||
bool isWindow = windows.Contains(w);
|
||||
|
||||
for (int i = 0; i < w.SectionCount; i++)
|
||||
{
|
||||
if (!w.SectionIsLeaking(i)) continue;
|
||||
|
||||
if (isWindow)
|
||||
{
|
||||
//decrease window damage to slow down the leaking
|
||||
w.AddDamage(i, -w.SectionDamage(i) * 0.48f);
|
||||
}
|
||||
else
|
||||
{
|
||||
w.AddDamage(i, -100000.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Submarine.MainSub.GodMode = true;
|
||||
|
||||
var capacitor1 = Item.ItemList.Find(i => i.HasTag("capacitor1")).GetComponent<PowerContainer>();
|
||||
var capacitor2 = Item.ItemList.Find(i => i.HasTag("capacitor1")).GetComponent<PowerContainer>();
|
||||
CoroutineManager.StartCoroutine(KeepEnemyAway(moloch, new PowerContainer[] { capacitor1, capacitor2 }));
|
||||
|
||||
infoBox = CreateInfoFrame("The hull has been breached! Close all the doors to the command room to stop the water from flooding the entire sub!");
|
||||
|
||||
Door commandDoor1 = Item.ItemList.Find(i => i.HasTag("commanddoor1")).GetComponent<Door>();
|
||||
Door commandDoor2 = Item.ItemList.Find(i => i.HasTag("commanddoor2")).GetComponent<Door>();
|
||||
Door commandDoor3 = Item.ItemList.Find(i => i.HasTag("commanddoor3")).GetComponent<Door>();
|
||||
|
||||
//wait until the player is out of the room and the doors are closed
|
||||
while (Character.Controlled.WorldPosition.X > commandDoor1.Item.WorldPosition.X ||
|
||||
(commandDoor1.IsOpen || (commandDoor2.IsOpen || commandDoor3.IsOpen)))
|
||||
{
|
||||
//prevent the hull from filling up completely and crushing the player
|
||||
steering.Item.CurrentHull.Volume = Math.Min(steering.Item.CurrentHull.Volume, steering.Item.CurrentHull.FullVolume * 0.9f);
|
||||
yield return CoroutineStatus.Running;
|
||||
}
|
||||
|
||||
|
||||
infoBox = CreateInfoFrame("You should quickly find yourself a diving mask or a diving suit. " +
|
||||
"There are some in the room next to the airlock.");
|
||||
|
||||
bool divingMaskSelected = false;
|
||||
|
||||
while (!HasItem("Diving Mask") && !HasItem("Diving Suit"))
|
||||
{
|
||||
if (!divingMaskSelected &&
|
||||
Character.Controlled.ClosestItem != null && Character.Controlled.ClosestItem.Name == "Diving Suit")
|
||||
{
|
||||
infoBox = CreateInfoFrame("There can only be one item in each inventory slot, so you need to take off "
|
||||
+ "the jumpsuit if you wish to wear a diving suit.");
|
||||
|
||||
divingMaskSelected = true;
|
||||
}
|
||||
|
||||
yield return CoroutineStatus.Running;
|
||||
}
|
||||
|
||||
if (HasItem("Diving Mask"))
|
||||
{
|
||||
infoBox = CreateInfoFrame("The diving mask will let you breathe underwater, but it won't protect from the water pressure outside the sub. " +
|
||||
"It should be fine for the situation at hand, but you still need to find an oxygen tank and drag it into the same slot as the mask." +
|
||||
"You should grab one or two from one of the cabinets.");
|
||||
}
|
||||
else if (HasItem("Diving Suit"))
|
||||
{
|
||||
infoBox = CreateInfoFrame("In addition to letting you breathe underwater, the suit will protect you from the water pressure outside the sub " +
|
||||
"(unlike the diving mask). However, you still need to drag an oxygen tank into the same slot as the suit to supply oxygen. " +
|
||||
"You should grab one or two from one of the cabinets.");
|
||||
}
|
||||
|
||||
while (!HasItem("Oxygen Tank"))
|
||||
{
|
||||
yield return CoroutineStatus.Running;
|
||||
}
|
||||
|
||||
yield return new WaitForSeconds(5.0f);
|
||||
|
||||
infoBox = CreateInfoFrame("Now you should stop the creature attacking the submarine before it does any more damage. Head to the railgun room at the upper right corner of the sub.");
|
||||
|
||||
var railGun = Item.ItemList.Find(i => i.GetComponent<Turret>() != null);
|
||||
|
||||
while (Vector2.Distance(Character.Controlled.Position, railGun.Position) > 500)
|
||||
{
|
||||
yield return new WaitForSeconds(1.0f);
|
||||
}
|
||||
|
||||
infoBox = CreateInfoFrame("The railgun requires a large power surge to fire. The reactor can't provide a surge large enough, so we need to use the "
|
||||
+ " supercapacitors in the railgun room. The capacitors need to be charged first; select them and crank up the recharge rate.");
|
||||
|
||||
while (capacitor1.RechargeSpeed < 0.5f && capacitor2.RechargeSpeed < 0.5f)
|
||||
{
|
||||
yield return new WaitForSeconds(1.0f);
|
||||
}
|
||||
|
||||
infoBox = CreateInfoFrame("The capacitors take some time to recharge, so now is a good " +
|
||||
"time to head to the room below and load some shells for the railgun.");
|
||||
|
||||
|
||||
var loader = Item.ItemList.Find(i => i.Name == "Railgun Loader").GetComponent<ItemContainer>();
|
||||
|
||||
while (Math.Abs(Character.Controlled.Position.Y - loader.Item.Position.Y) > 80)
|
||||
{
|
||||
yield return CoroutineStatus.Running;
|
||||
}
|
||||
|
||||
infoBox = CreateInfoFrame("Grab one of the shells. You can load it by selecting the railgun loader and dragging the shell to. "
|
||||
+ "one of the free slots. You need two hands to carry a shell, so make sure you don't have anything else in either hand.");
|
||||
|
||||
while (loader.Item.ContainedItems.FirstOrDefault(i => i != null && i.Name == "Railgun Shell") == null)
|
||||
{
|
||||
moloch.Health = 50.0f;
|
||||
|
||||
capacitor1.Charge += 5.0f;
|
||||
capacitor2.Charge += 5.0f;
|
||||
yield return CoroutineStatus.Running;
|
||||
}
|
||||
|
||||
infoBox = CreateInfoFrame("Now we're ready to shoot! Select the railgun controller.");
|
||||
|
||||
while (Character.Controlled.SelectedConstruction == null || Character.Controlled.SelectedConstruction.Name != "Railgun Controller")
|
||||
{
|
||||
yield return CoroutineStatus.Running;
|
||||
}
|
||||
|
||||
moloch.AnimController.SetPosition(ConvertUnits.ToSimUnits(Character.Controlled.WorldPosition + Vector2.UnitY * 600.0f));
|
||||
|
||||
infoBox = CreateInfoFrame("Use the right mouse button to aim and wait for the creature to come closer. When you're ready to shoot, "
|
||||
+ "press the left mouse button.");
|
||||
|
||||
while (!moloch.IsDead)
|
||||
{
|
||||
if (moloch.WorldPosition.Y > Character.Controlled.WorldPosition.Y + 600.0f)
|
||||
{
|
||||
moloch.AIController.SteeringManager.SteeringManual(CoroutineManager.DeltaTime, Character.Controlled.WorldPosition - moloch.WorldPosition);
|
||||
}
|
||||
|
||||
moloch.AIController.SelectTarget(Character.Controlled.AiTarget);
|
||||
yield return CoroutineStatus.Running;
|
||||
}
|
||||
|
||||
Submarine.MainSub.GodMode = false;
|
||||
|
||||
infoBox = CreateInfoFrame("The creature has died. Now you should fix the damages in the control room: " +
|
||||
"Grab a welding tool from the closet in the railgun room.");
|
||||
|
||||
while (!HasItem("Welding Tool"))
|
||||
{
|
||||
yield return CoroutineStatus.Running;
|
||||
}
|
||||
|
||||
infoBox = CreateInfoFrame("The welding tool requires fuel to work. Grab a welding fuel tank and attach it to the tool " +
|
||||
"by dragging it into the same slot.");
|
||||
|
||||
do
|
||||
{
|
||||
var weldingTool = Character.Controlled.Inventory.Items.FirstOrDefault(i => i != null && i.Name == "Welding Tool");
|
||||
if (weldingTool != null &&
|
||||
weldingTool.ContainedItems.FirstOrDefault(contained => contained != null && contained.Name == "Welding Fuel Tank") != null) break;
|
||||
|
||||
yield return CoroutineStatus.Running;
|
||||
} while (true);
|
||||
|
||||
|
||||
infoBox = CreateInfoFrame("You can aim with the tool using the right mouse button and weld using the left button. " +
|
||||
"Head to the command room to fix the leaks there.");
|
||||
|
||||
do
|
||||
{
|
||||
broken = false;
|
||||
foreach (Structure window in windows)
|
||||
{
|
||||
for (int i = 0; i < window.SectionCount; i++)
|
||||
{
|
||||
if (!window.SectionIsLeaking(i)) continue;
|
||||
broken = true;
|
||||
break;
|
||||
}
|
||||
if (broken) break;
|
||||
}
|
||||
|
||||
yield return new WaitForSeconds(1.0f);
|
||||
} while (broken);
|
||||
|
||||
infoBox = CreateInfoFrame("The hull is fixed now, but there's still quite a bit of water inside the sub. It should be pumped out "
|
||||
+ "using the bilge pump in the room at the bottom of the submarine.");
|
||||
|
||||
Pump pump = Item.ItemList.Find(i => i.HasTag("tutorialpump")).GetComponent<Pump>();
|
||||
|
||||
while (Vector2.Distance(Character.Controlled.Position, pump.Item.Position) > 100.0f)
|
||||
{
|
||||
yield return CoroutineStatus.Running;
|
||||
}
|
||||
|
||||
infoBox = CreateInfoFrame("The two pumps inside the ballast tanks "
|
||||
+ "are connected straight to the navigation terminal and can't be manually controlled unless you mess with their wiring, " +
|
||||
"so you should only use the pump in the middle room to pump out the water. Select it, turn it on and adjust the pumping speed " +
|
||||
"to start pumping water out.", true);
|
||||
|
||||
while (infoBox != null)
|
||||
{
|
||||
yield return CoroutineStatus.Running;
|
||||
}
|
||||
|
||||
|
||||
bool brokenMsgShown = false;
|
||||
|
||||
Item brokenBox = null;
|
||||
|
||||
while (pump.FlowPercentage > 0.0f || pump.CurrFlow <= 0.0f || !pump.IsActive)
|
||||
{
|
||||
if (!brokenMsgShown && pump.Voltage < pump.MinVoltage && Character.Controlled.SelectedConstruction == pump.Item)
|
||||
{
|
||||
brokenMsgShown = true;
|
||||
|
||||
infoBox = CreateInfoFrame("Looks like the pump isn't getting any power. The water must have short-circuited some of the junction "
|
||||
+"boxes. You can check which boxes are broken by selecting them.");
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (Character.Controlled.SelectedConstruction!=null &&
|
||||
Character.Controlled.SelectedConstruction.GetComponent<PowerTransfer>() != null &&
|
||||
Character.Controlled.SelectedConstruction.Condition == 0.0f)
|
||||
{
|
||||
brokenBox = Character.Controlled.SelectedConstruction;
|
||||
|
||||
infoBox = CreateInfoFrame("Here's our problem: this junction box is broken. Luckily engineers are adept at fixing electrical devices - "
|
||||
+"you just need to find a spare wire and click the \"Fix\"-button to repair the box.");
|
||||
break;
|
||||
}
|
||||
|
||||
if (pump.Voltage > pump.MinVoltage) break;
|
||||
|
||||
yield return CoroutineStatus.Running;
|
||||
}
|
||||
}
|
||||
|
||||
if (brokenBox != null && brokenBox.Condition > 50.0f && pump.Voltage < pump.MinVoltage)
|
||||
{
|
||||
yield return new WaitForSeconds(1.0f);
|
||||
|
||||
if (pump.Voltage < pump.MinVoltage)
|
||||
{
|
||||
infoBox = CreateInfoFrame("The pump is still not running. Check if there are more broken junction boxes between the pump and the reactor.");
|
||||
}
|
||||
brokenBox = null;
|
||||
}
|
||||
|
||||
yield return CoroutineStatus.Running;
|
||||
}
|
||||
|
||||
infoBox = CreateInfoFrame("The pump is up and running. Wait for the water to be drained out.");
|
||||
|
||||
while (pump.Item.CurrentHull.Volume > 1000.0f)
|
||||
{
|
||||
yield return CoroutineStatus.Running;
|
||||
}
|
||||
|
||||
infoBox = CreateInfoFrame("That was all there is to this tutorial! Now you should be able to handle " +
|
||||
"most of the basic tasks on board the submarine.");
|
||||
|
||||
yield return new WaitForSeconds(4.0f);
|
||||
|
||||
Character.Controlled = null;
|
||||
GameMain.GameScreen.Cam.TargetPos = Vector2.Zero;
|
||||
GameMain.LightManager.LosEnabled = false;
|
||||
|
||||
var cinematic = new TransitionCinematic(Submarine.MainSub, GameMain.GameScreen.Cam, 5.0f);
|
||||
|
||||
while (cinematic.Running)
|
||||
{
|
||||
yield return CoroutineStatus.Running;
|
||||
}
|
||||
|
||||
Submarine.Unload();
|
||||
GameMain.MainMenuScreen.Select();
|
||||
|
||||
yield return CoroutineStatus.Success;
|
||||
}
|
||||
|
||||
private bool HasItem(string itemName)
|
||||
{
|
||||
if (Character.Controlled == null) return false;
|
||||
|
||||
return Character.Controlled.Inventory.FindItem(itemName) != null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
protected IEnumerable<object> KeepReactorRunning(Reactor reactor)
|
||||
{
|
||||
do
|
||||
{
|
||||
reactor.AutoTemp = true;
|
||||
reactor.ShutDownTemp = 5000.0f;
|
||||
|
||||
yield return CoroutineStatus.Running;
|
||||
} while (Item.ItemList.Contains(reactor.Item));
|
||||
|
||||
yield return CoroutineStatus.Success;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// keeps the enemy away from the sub until the capacitors are loaded
|
||||
/// </summary>
|
||||
private IEnumerable<object> KeepEnemyAway(Character enemy, PowerContainer[] capacitors)
|
||||
{
|
||||
do
|
||||
{
|
||||
if (enemy == null || Character.Controlled == null) break;
|
||||
|
||||
enemy.Health = 50.0f;
|
||||
|
||||
enemy.AIController.State = AIController.AiState.None;
|
||||
|
||||
Vector2 targetPos = Character.Controlled.WorldPosition + new Vector2(0.0f, 3000.0f);
|
||||
|
||||
Vector2 steering = targetPos - enemy.WorldPosition;
|
||||
if (steering != Vector2.Zero) steering = Vector2.Normalize(steering);
|
||||
|
||||
enemy.AIController.Steering = steering * 2.0f;
|
||||
|
||||
yield return CoroutineStatus.Running;
|
||||
} while (capacitors.FirstOrDefault(c => c.Charge > 0.4f) == null);
|
||||
|
||||
yield return CoroutineStatus.Success;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Barotrauma.Tutorials
|
||||
{
|
||||
class EditorTutorial : TutorialType
|
||||
{
|
||||
public EditorTutorial(string name)
|
||||
: base (name)
|
||||
{
|
||||
}
|
||||
|
||||
public override IEnumerable<object> UpdateState()
|
||||
{
|
||||
infoBox = CreateInfoFrame("Use the mouse wheel to zoom in and out, and WASD to move the camera around.", true);
|
||||
|
||||
while (infoBox!=null)
|
||||
{
|
||||
yield return CoroutineStatus.Running;
|
||||
}
|
||||
|
||||
infoBox = CreateInfoFrame("Press \"Structure\" at the left side of the screen to start placing some walls.");
|
||||
|
||||
while (GameMain.EditMapScreen.SelectedTab != (int)MapEntityCategory.Structure)
|
||||
{
|
||||
yield return CoroutineStatus.Running;
|
||||
}
|
||||
|
||||
infoBox = CreateInfoFrame("Select \"topwall\" from the list.", true);
|
||||
|
||||
while (MapEntityPrefab.Selected == null || MapEntityPrefab.Selected.Name != "topwall")
|
||||
{
|
||||
yield return CoroutineStatus.Running;
|
||||
}
|
||||
|
||||
infoBox = CreateInfoFrame("You can now create a horizontal wall by clicking and dragging. When you're done, right click to stop creating walls.");
|
||||
|
||||
|
||||
|
||||
|
||||
yield return CoroutineStatus.Success;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using Barotrauma.Tutorials;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class TutorialMode : GameMode
|
||||
{
|
||||
public TutorialType tutorialType;
|
||||
|
||||
public static void StartTutorial(TutorialType tutorialType)
|
||||
{
|
||||
Submarine.MainSub = Submarine.Load("Content/Map/TutorialSub.sub", "", true);
|
||||
|
||||
tutorialType.Initialize();
|
||||
}
|
||||
|
||||
public TutorialMode(GameModePreset preset, object param)
|
||||
: base(preset, param)
|
||||
{
|
||||
}
|
||||
|
||||
public override void Start()
|
||||
{
|
||||
base.Start();
|
||||
|
||||
tutorialType.Start();
|
||||
|
||||
}
|
||||
|
||||
public override void AddToGUIUpdateList()
|
||||
{
|
||||
tutorialType.AddToGUIUpdateList();
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
base.Update(deltaTime);
|
||||
|
||||
tutorialType.Update(deltaTime);
|
||||
|
||||
}
|
||||
|
||||
|
||||
public override void Draw(SpriteBatch spriteBatch)
|
||||
{
|
||||
base.Draw(spriteBatch);
|
||||
|
||||
//CrewManager.Draw(spriteBatch);
|
||||
tutorialType.Draw(spriteBatch);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Barotrauma.Tutorials
|
||||
{
|
||||
class TutorialType
|
||||
{
|
||||
|
||||
public static List<TutorialType> TutorialTypes;
|
||||
|
||||
protected GUIComponent infoBox;
|
||||
|
||||
Character character;
|
||||
|
||||
|
||||
public string Name
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
static TutorialType()
|
||||
{
|
||||
TutorialTypes = new List<TutorialType>();
|
||||
|
||||
TutorialTypes.Add(new BasicTutorial("Basic tutorial"));
|
||||
|
||||
}
|
||||
|
||||
public TutorialType(string name)
|
||||
{
|
||||
this.Name = name;
|
||||
}
|
||||
|
||||
public virtual void Initialize()
|
||||
{
|
||||
|
||||
GameMain.GameSession = new GameSession(Submarine.MainSub, "", GameModePreset.list.Find(gm => gm.Name.ToLowerInvariant() == "tutorial"));
|
||||
(GameMain.GameSession.gameMode as TutorialMode).tutorialType = this;
|
||||
|
||||
GameMain.GameSession.StartShift("tuto");
|
||||
|
||||
GameMain.GameSession.TaskManager.Tasks.Clear();
|
||||
|
||||
GameMain.GameScreen.Select();
|
||||
}
|
||||
|
||||
public virtual void Start()
|
||||
{
|
||||
|
||||
WayPoint wayPoint = WayPoint.GetRandom(SpawnType.Cargo, null);
|
||||
if (wayPoint == null)
|
||||
{
|
||||
DebugConsole.ThrowError("A waypoint with the spawntype \"cargo\" is required for the tutorial event");
|
||||
return;
|
||||
}
|
||||
|
||||
CharacterInfo charInfo = new CharacterInfo(Character.HumanConfigFile, "", Gender.None, JobPrefab.List.Find(jp => jp.Name == "Engineer"));
|
||||
|
||||
character = Character.Create(charInfo, wayPoint.WorldPosition, false, false);
|
||||
Character.Controlled = character;
|
||||
character.GiveJobItems(null);
|
||||
|
||||
var idCard = character.Inventory.FindItem("ID Card");
|
||||
if (idCard == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Item prefab \"ID Card\" not found!");
|
||||
return;
|
||||
}
|
||||
idCard.AddTag("com");
|
||||
idCard.AddTag("eng");
|
||||
|
||||
//CoroutineManager.StartCoroutine(QuitChecker());
|
||||
CoroutineManager.StartCoroutine(UpdateState());
|
||||
}
|
||||
|
||||
public virtual void AddToGUIUpdateList()
|
||||
{
|
||||
if (infoBox != null) infoBox.AddToGUIUpdateList();
|
||||
}
|
||||
|
||||
public virtual void Update(float deltaTime)
|
||||
{
|
||||
if (character!=null)
|
||||
{
|
||||
if (Character.Controlled==null)
|
||||
{
|
||||
CoroutineManager.StopCoroutines("TutorialMode.UpdateState");
|
||||
infoBox = null;
|
||||
}
|
||||
else if (Character.Controlled.IsDead)
|
||||
{
|
||||
Character.Controlled = null;
|
||||
|
||||
CoroutineManager.StopCoroutines("TutorialMode.UpdateState");
|
||||
infoBox = null;
|
||||
CoroutineManager.StartCoroutine(Dead());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//CrewManager.Update(deltaTime);
|
||||
|
||||
if (infoBox != null) infoBox.Update(deltaTime);
|
||||
}
|
||||
|
||||
public virtual IEnumerable<object> UpdateState()
|
||||
{
|
||||
yield return CoroutineStatus.Success;
|
||||
}
|
||||
|
||||
public virtual void Draw(SpriteBatch spriteBatch)
|
||||
{
|
||||
|
||||
if (infoBox != null) infoBox.Draw(spriteBatch);
|
||||
}
|
||||
|
||||
|
||||
|
||||
private IEnumerable<object> Dead()
|
||||
{
|
||||
yield return new WaitForSeconds(3.0f);
|
||||
|
||||
var messageBox = new GUIMessageBox("You have died", "Do you want to try again?", new string[] { "Yes", "No" });
|
||||
|
||||
messageBox.Buttons[0].OnClicked += Restart;
|
||||
messageBox.Buttons[0].OnClicked += messageBox.Close;
|
||||
|
||||
|
||||
messageBox.Buttons[1].OnClicked = GameMain.MainMenuScreen.SelectTab;
|
||||
messageBox.Buttons[1].OnClicked += messageBox.Close;
|
||||
|
||||
yield return CoroutineStatus.Success;
|
||||
}
|
||||
|
||||
|
||||
protected bool CloseInfoFrame(GUIButton button, object userData)
|
||||
{
|
||||
infoBox = null;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected GUIComponent CreateInfoFrame(string text, bool hasButton = false)
|
||||
{
|
||||
int width = 300;
|
||||
int height = hasButton ? 110 : 80;
|
||||
|
||||
string wrappedText = ToolBox.WrapText(text, width, GUI.Font);
|
||||
|
||||
height += wrappedText.Split('\n').Length * 25;
|
||||
|
||||
var infoBlock = new GUIFrame(new Rectangle(-20, 20, width, height), null, Alignment.TopRight, "");
|
||||
//infoBlock.Color = infoBlock.Color * 0.8f;
|
||||
infoBlock.Padding = new Vector4(10.0f, 10.0f, 10.0f, 10.0f);
|
||||
infoBlock.Flash(Color.Green);
|
||||
|
||||
var textBlock = new GUITextBlock(new Rectangle(10, 10, width - 40, height), text, "", infoBlock, true);
|
||||
|
||||
if (hasButton)
|
||||
{
|
||||
var okButton = new GUIButton(new Rectangle(0, -40, 80, 25), "OK", Alignment.BottomCenter, "", textBlock);
|
||||
okButton.OnClicked = CloseInfoFrame;
|
||||
}
|
||||
|
||||
|
||||
GUI.PlayUISound(GUISoundType.Message);
|
||||
|
||||
return infoBlock;
|
||||
}
|
||||
|
||||
|
||||
private bool Restart(GUIButton button, object obj)
|
||||
{
|
||||
TutorialMode.StartTutorial(this);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user