Renamed project folders from Subsurface to Barotrauma

This commit is contained in:
Regalis
2017-06-04 15:00:53 +03:00
parent ad03c8bf0d
commit 94c6a8ea1b
697 changed files with 157 additions and 211 deletions
@@ -0,0 +1,835 @@
using Barotrauma.Networking;
using FarseerPhysics;
using FarseerPhysics.Dynamics;
using FarseerPhysics.Dynamics.Joints;
using FarseerPhysics.Factories;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
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.Items.Components
{
class DockingPort : ItemComponent, IDrawableComponent, IServerSerializable
{
public static List<DockingPort> list = new List<DockingPort>();
private Sprite overlaySprite;
private Vector2 distanceTolerance;
private DockingPort dockingTarget;
private float dockingState;
private int dockingDir;
private Joint joint;
private Hull[] hulls;
private ushort?[] hullIds;
private Door door;
private Body[] bodies;
private Body doorBody;
private Gap gap;
private ushort? gapId;
private bool docked;
public int DockingDir
{
get { return dockingDir; }
set { dockingDir = value; }
}
[HasDefaultValue("32.0,32.0", false)]
public string DistanceTolerance
{
get { return ToolBox.Vector2ToString(distanceTolerance); }
set { distanceTolerance = ToolBox.ParseToVector2(value); }
}
[HasDefaultValue(32.0f, false)]
public float DockedDistance
{
get;
set;
}
[HasDefaultValue(true, false)]
public bool IsHorizontal
{
get;
set;
}
public DockingPort DockingTarget
{
get { return dockingTarget; }
set { dockingTarget = value; }
}
public bool Docked
{
get
{
return docked;
}
set
{
if (!docked && value)
{
if (dockingTarget == null) AttemptDock();
if (dockingTarget == null) return;
docked = true;
}
else if (docked && !value)
{
Undock();
}
//base.IsActive = value;
}
}
public DockingPort(Item item, XElement element)
: base(item, element)
{
// isOpen = false;
foreach (XElement subElement in element.Elements())
{
string texturePath = ToolBox.GetAttributeString(subElement, "texture", "");
switch (subElement.Name.ToString().ToLowerInvariant())
{
case "sprite":
overlaySprite = new Sprite(subElement, texturePath.Contains("/") ? "" : Path.GetDirectoryName(item.Prefab.ConfigFile));
break;
}
}
IsActive = true;
hullIds = new ushort?[2];
list.Add(this);
}
public override void FlipX()
{
base.FlipX();
if (dockingTarget != null)
{
if (joint != null)
{
CreateJoint(joint is WeldJoint);
LinkHullsToGap();
}
else if (dockingTarget.joint != null)
{
if (!GameMain.World.BodyList.Contains(dockingTarget.joint.BodyA) ||
!GameMain.World.BodyList.Contains(dockingTarget.joint.BodyB))
{
dockingTarget.CreateJoint(dockingTarget.joint is WeldJoint);
}
dockingTarget.LinkHullsToGap();
}
}
}
private DockingPort FindAdjacentPort()
{
foreach (DockingPort port in list)
{
if (port == this || port.item.Submarine == item.Submarine) continue;
if (Math.Abs(port.item.WorldPosition.X - item.WorldPosition.X) > distanceTolerance.X) continue;
if (Math.Abs(port.item.WorldPosition.Y - item.WorldPosition.Y) > distanceTolerance.Y) continue;
return port;
}
return null;
}
private void AttemptDock()
{
var adjacentPort = FindAdjacentPort();
if (adjacentPort != null) Dock(adjacentPort);
}
public void Dock(DockingPort target)
{
if (item.Submarine.DockedTo.Contains(target.item.Submarine)) return;
if (dockingTarget != null)
{
Undock();
}
if (target.item.Submarine == item.Submarine)
{
DebugConsole.ThrowError("Error - tried to dock a submarine to itself");
dockingTarget = null;
return;
}
PlaySound(ActionType.OnUse, item.WorldPosition);
if (!item.linkedTo.Contains(target.item)) item.linkedTo.Add(target.item);
if (!target.item.linkedTo.Contains(item)) target.item.linkedTo.Add(item);
if (!target.item.Submarine.DockedTo.Contains(item.Submarine)) target.item.Submarine.DockedTo.Add(item.Submarine);
if (!item.Submarine.DockedTo.Contains(target.item.Submarine)) item.Submarine.DockedTo.Add(target.item.Submarine);
dockingTarget = target;
dockingTarget.dockingTarget = this;
docked = true;
dockingTarget.Docked = true;
if (Character.Controlled != null &&
(Character.Controlled.Submarine == dockingTarget.item.Submarine || Character.Controlled.Submarine == item.Submarine))
{
GameMain.GameScreen.Cam.Shake = Vector2.Distance(dockingTarget.item.Submarine.Velocity, item.Submarine.Velocity);
}
dockingDir = IsHorizontal ?
Math.Sign(dockingTarget.item.WorldPosition.X - item.WorldPosition.X) :
Math.Sign(dockingTarget.item.WorldPosition.Y - item.WorldPosition.Y);
dockingTarget.dockingDir = -dockingDir;
foreach (WayPoint wp in WayPoint.WayPointList)
{
if (wp.Submarine != item.Submarine || wp.SpawnType != SpawnType.Path) continue;
if (!Submarine.RectContains(item.Rect, wp.Position)) continue;
foreach (WayPoint wp2 in WayPoint.WayPointList)
{
if (wp2.Submarine != dockingTarget.item.Submarine || wp2.SpawnType != SpawnType.Path) continue;
if (!Submarine.RectContains(dockingTarget.item.Rect, wp2.Position)) continue;
wp.linkedTo.Add(wp2);
wp2.linkedTo.Add(wp);
}
}
CreateJoint(false);
if (GameMain.Server != null)
{
item.CreateServerEvent(this);
}
}
public void Lock(bool isNetworkMessage)
{
if (GameMain.Client != null && !isNetworkMessage) return;
if (dockingTarget==null)
{
DebugConsole.ThrowError("Error - attempted to lock a docking port that's not connected to anything");
return;
}
else if (joint is WeldJoint)
{
//DebugConsole.ThrowError("Error - attempted to lock a docking port that's already locked");
return;
}
dockingDir = IsHorizontal ?
Math.Sign(dockingTarget.item.WorldPosition.X - item.WorldPosition.X) :
Math.Sign(dockingTarget.item.WorldPosition.Y - item.WorldPosition.Y);
dockingTarget.dockingDir = -dockingDir;
PlaySound(ActionType.OnSecondaryUse, item.WorldPosition);
ConnectWireBetweenPorts();
CreateJoint(true);
if (GameMain.Server != null)
{
item.CreateServerEvent(this);
}
if (!item.linkedTo.Any(e => e is Hull) && !dockingTarget.item.linkedTo.Any(e => e is Hull))
{
CreateHull();
}
}
private void CreateJoint(bool useWeldJoint)
{
if (joint != null)
{
GameMain.World.RemoveJoint(joint);
joint = null;
}
Vector2 offset = (IsHorizontal ?
Vector2.UnitX * dockingDir :
Vector2.UnitY * dockingDir);
offset *= DockedDistance * 0.5f;
Vector2 pos1 = item.WorldPosition + offset;
Vector2 pos2 = dockingTarget.item.WorldPosition - offset;
if (useWeldJoint)
{
joint = JointFactory.CreateWeldJoint(GameMain.World,
item.Submarine.PhysicsBody.FarseerBody, dockingTarget.item.Submarine.PhysicsBody.FarseerBody,
ConvertUnits.ToSimUnits(pos1), FarseerPhysics.ConvertUnits.ToSimUnits(pos2), true);
((WeldJoint)joint).FrequencyHz = 1.0f;
}
else
{
var distanceJoint = JointFactory.CreateDistanceJoint(GameMain.World,
item.Submarine.PhysicsBody.FarseerBody, dockingTarget.item.Submarine.PhysicsBody.FarseerBody,
ConvertUnits.ToSimUnits(pos1), FarseerPhysics.ConvertUnits.ToSimUnits(pos2), true);
distanceJoint.Length = 0.01f;
distanceJoint.Frequency = 1.0f;
distanceJoint.DampingRatio = 0.8f;
joint = distanceJoint;
}
joint.CollideConnected = true;
}
private void ConnectWireBetweenPorts()
{
Wire wire = item.GetComponent<Wire>();
if (wire == null) return;
wire.Hidden = true;
wire.Locked = true;
if (Item.Connections == null) return;
var powerConnection = Item.Connections.Find(c => c.IsPower);
if (powerConnection == null) return;
if (dockingTarget == null || dockingTarget.item.Connections == null) return;
var recipient = dockingTarget.item.Connections.Find(c => c.IsPower);
if (recipient == null) return;
wire.RemoveConnection(item);
wire.RemoveConnection(dockingTarget.item);
powerConnection.TryAddLink(wire);
wire.Connect(powerConnection, false, false);
recipient.TryAddLink(wire);
wire.Connect(recipient, false, false);
}
private void CreateDoorBody()
{
doorBody = BodyFactory.CreateRectangle(GameMain.World,
dockingTarget.door.Body.width,
dockingTarget.door.Body.height,
1.0f,
dockingTarget.door);
doorBody.CollisionCategories = Physics.CollisionWall;
doorBody.BodyType = BodyType.Static;
doorBody.SetTransform(
ConvertUnits.ToSimUnits(item.Position + (dockingTarget.door.Item.WorldPosition - item.WorldPosition)),
0.0f);
}
private void CreateHull()
{
var hullRects = new Rectangle[] { item.WorldRect, dockingTarget.item.WorldRect };
var subs = new Submarine[] { item.Submarine, dockingTarget.item.Submarine };
hulls = new Hull[2];
bodies = new Body[4];
if (dockingTarget.door != null)
{
CreateDoorBody();
}
if (door != null)
{
dockingTarget.CreateDoorBody();
}
if (IsHorizontal)
{
if (hullRects[0].Center.X > hullRects[1].Center.X)
{
hullRects = new Rectangle[] { dockingTarget.item.WorldRect, item.WorldRect };
subs = new Submarine[] { dockingTarget.item.Submarine,item.Submarine };
}
hullRects[0] = new Rectangle(hullRects[0].Center.X, hullRects[0].Y, ((int)DockedDistance / 2), hullRects[0].Height);
hullRects[1] = new Rectangle(hullRects[1].Center.X - ((int)DockedDistance / 2), hullRects[1].Y, ((int)DockedDistance / 2), hullRects[1].Height);
for (int i = 0; i < 2; i++)
{
hullRects[i].Location -= (subs[i].WorldPosition - subs[i].HiddenSubPosition).ToPoint();
hulls[i] = new Hull(MapEntityPrefab.list.Find(m => m.Name == "Hull"), hullRects[i], subs[i]);
hulls[i].AddToGrid(subs[i]);
for (int j = 0; j < 2; j++)
{
bodies[i + j * 2] = BodyFactory.CreateEdge(GameMain.World,
ConvertUnits.ToSimUnits(new Vector2(hullRects[i].X, hullRects[i].Y - hullRects[i].Height * j)),
ConvertUnits.ToSimUnits(new Vector2(hullRects[i].Right, hullRects[i].Y - hullRects[i].Height * j)));
}
}
gap = new Gap(new Rectangle(hullRects[0].Right - 2, hullRects[0].Y, 4, hullRects[0].Height), true, subs[0]);
if (gapId != null) gap.ID = (ushort)gapId;
LinkHullsToGap();
}
else
{
if (hullRects[0].Center.Y > hullRects[1].Center.Y)
{
hullRects = new Rectangle[] { dockingTarget.item.WorldRect, item.WorldRect };
subs = new Submarine[] { dockingTarget.item.Submarine, item.Submarine };
}
hullRects[0] = new Rectangle(hullRects[0].X, hullRects[0].Y + (int)(-hullRects[0].Height+DockedDistance)/2, hullRects[0].Width, ((int)DockedDistance / 2));
hullRects[1] = new Rectangle(hullRects[1].X, hullRects[1].Y - hullRects[1].Height/2, hullRects[1].Width, ((int)DockedDistance / 2));
for (int i = 0; i < 2; i++)
{
hullRects[i].Location -= (subs[i].WorldPosition - subs[i].HiddenSubPosition).ToPoint();
hulls[i] = new Hull(MapEntityPrefab.list.Find(m => m.Name == "Hull"), hullRects[i], subs[i]);
hulls[i].AddToGrid(subs[i]);
if (hullIds[i] != null) hulls[i].ID = (ushort)hullIds[i];
}
gap = new Gap(new Rectangle(hullRects[0].X, hullRects[0].Y+2, hullRects[0].Width, 4), false, subs[0]);
if (gapId != null) gap.ID = (ushort)gapId;
LinkHullsToGap();
}
item.linkedTo.Add(hulls[0]);
item.linkedTo.Add(hulls[1]);
hullIds[0] = hulls[0].ID;
hullIds[1] = hulls[1].ID;
gap.DisableHullRechecks = true;
gapId = gap.ID;
item.linkedTo.Add(gap);
foreach (Body body in bodies)
{
if (body == null) continue;
body.BodyType = BodyType.Static;
body.Friction = 0.5f;
body.CollisionCategories = Physics.CollisionWall;
}
}
private void LinkHullsToGap()
{
if (gap == null || hulls == null || hulls[0] == null || hulls[1] == null)
{
#if DEBUG
DebugConsole.ThrowError("Failed to link dockingport hulls to gap");
#endif
return;
}
gap.linkedTo.Clear();
if (IsHorizontal)
{
if (hulls[0].WorldRect.X < hulls[1].WorldRect.X)
{
gap.linkedTo.Add(hulls[0]);
gap.linkedTo.Add(hulls[1]);
}
else
{
gap.linkedTo.Add(hulls[1]);
gap.linkedTo.Add(hulls[0]);
}
}
else
{
if (hulls[0].WorldRect.Y > hulls[1].WorldRect.Y)
{
gap.linkedTo.Add(hulls[0]);
gap.linkedTo.Add(hulls[1]);
}
else
{
gap.linkedTo.Add(hulls[1]);
gap.linkedTo.Add(hulls[0]);
}
}
}
public void Undock()
{
if (dockingTarget == null || !docked) return;
PlaySound(ActionType.OnUse, item.WorldPosition);
dockingTarget.item.Submarine.DockedTo.Remove(item.Submarine);
item.Submarine.DockedTo.Remove(dockingTarget.item.Submarine);
//remove all waypoint links between this sub and the dockingtarget
foreach (WayPoint wp in WayPoint.WayPointList)
{
if (wp.Submarine != item.Submarine || wp.SpawnType != SpawnType.Path) continue;
for (int i = wp.linkedTo.Count - 1; i >= 0; i--)
{
var wp2 = wp.linkedTo[i] as WayPoint;
if (wp2 == null) continue;
if (wp.Submarine == dockingTarget.item.Submarine)
{
wp.linkedTo.RemoveAt(i);
wp2.linkedTo.Remove(wp);
}
}
}
item.linkedTo.Clear();
docked = false;
dockingTarget.Undock();
dockingTarget = null;
if (doorBody != null)
{
GameMain.World.RemoveBody(doorBody);
doorBody = null;
}
var wire = item.GetComponent<Wire>();
if (wire != null)
{
wire.Drop(null);
}
if (joint != null)
{
GameMain.World.RemoveJoint(joint);
joint = null;
}
if (hulls != null)
{
hulls[0].Remove();
hulls[1].Remove();
hulls = null;
}
if (gap != null)
{
gap.Remove();
gap = null;
}
hullIds[0] = null;
hullIds[1] = null;
gapId = null;
if (bodies!=null)
{
foreach (Body body in bodies)
{
if (body == null) continue;
GameMain.World.RemoveBody(body);
}
bodies = null;
}
if (GameMain.Server != null)
{
item.CreateServerEvent(this);
}
}
public override void Update(float deltaTime, Camera cam)
{
if (dockingTarget == null)
{
dockingState = MathHelper.Lerp(dockingState, 0.0f, deltaTime * 10.0f);
if (dockingState < 0.01f) docked = false;
item.SendSignal(0, "0", "state_out", null);
item.SendSignal(0, (FindAdjacentPort() != null) ? "1" : "0", "proximity_sensor", null);
}
else
{
if (!docked)
{
Dock(dockingTarget);
if (dockingTarget == null) return;
}
if (joint is DistanceJoint)
{
item.SendSignal(0, "0", "state_out", null);
dockingState = MathHelper.Lerp(dockingState, 0.5f, deltaTime * 10.0f);
if (Vector2.Distance(joint.WorldAnchorA, joint.WorldAnchorB) < 0.05f)
{
Lock(false);
}
}
else
{
if (dockingTarget.door != null && doorBody != null)
{
doorBody.Enabled = dockingTarget.door.Body.Enabled;
}
item.SendSignal(0, "1", "state_out", null);
dockingState = MathHelper.Lerp(dockingState, 1.0f, deltaTime * 10.0f);
}
}
}
public void Draw(SpriteBatch spriteBatch, bool editing)
{
if (dockingState == 0.0f) return;
Vector2 drawPos = item.DrawPosition;
drawPos.Y = -drawPos.Y;
var rect = overlaySprite.SourceRect;
if (IsHorizontal)
{
drawPos.Y -= rect.Height / 2;
if (dockingDir == 1)
{
spriteBatch.Draw(overlaySprite.Texture,
drawPos,
new Rectangle(
rect.Center.X + (int)(rect.Width / 2 * (1.0f - dockingState)), rect.Y,
(int)(rect.Width / 2 * dockingState), rect.Height), Color.White);
}
else
{
spriteBatch.Draw(overlaySprite.Texture,
drawPos - Vector2.UnitX * (rect.Width / 2 * dockingState),
new Rectangle(
rect.X, rect.Y,
(int)(rect.Width / 2 * dockingState), rect.Height), Color.White);
}
}
else
{
drawPos.X -= rect.Width / 2;
if (dockingDir == 1)
{
spriteBatch.Draw(overlaySprite.Texture,
drawPos - Vector2.UnitY * (rect.Height / 2 * dockingState),
new Rectangle(
rect.X, rect.Y,
rect.Width, (int)(rect.Height / 2 * dockingState)), Color.White);
}
else
{
spriteBatch.Draw(overlaySprite.Texture,
drawPos,
new Rectangle(
rect.X, rect.Y + rect.Height / 2 + (int)(rect.Height / 2 * (1.0f - dockingState)),
rect.Width, (int)(rect.Height / 2 * dockingState)), Color.White);
}
}
}
protected override void RemoveComponentSpecific()
{
list.Remove(this);
}
public override void OnMapLoaded()
{
foreach (Item it in Item.ItemList)
{
if (it.Submarine != item.Submarine) continue;
var doorComponent = it.GetComponent<Door>();
if (doorComponent == null) continue;
if (Vector2.Distance(item.Position, doorComponent.Item.Position) < Submarine.GridSize.X)
{
this.door = doorComponent;
break;
}
}
if (!item.linkedTo.Any()) return;
List<MapEntity> linked = new List<MapEntity>(item.linkedTo);
foreach (MapEntity entity in linked)
{
var hull = entity as Hull;
if (hull != null)
{
hull.Remove();
item.linkedTo.Remove(hull);
continue;
}
var gap = entity as Gap;
if (gap != null)
{
gap.Remove();
continue;
}
Item linkedItem = entity as Item;
if (linkedItem == null) continue;
var dockingPort = linkedItem.GetComponent<DockingPort>();
if (dockingPort != null)
{
Dock(dockingPort);
}
}
}
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power = 0.0f)
{
if (GameMain.Client != null) return;
bool wasDocked = docked;
DockingPort prevDockingTarget = dockingTarget;
switch (connection.Name)
{
case "toggle":
Docked = !docked;
break;
case "set_active":
case "set_state":
Docked = signal != "0";
break;
}
if (sender != null && docked != wasDocked)
{
if (docked)
{
if (item.Submarine != null && dockingTarget?.item?.Submarine != null)
GameServer.Log(sender.Name + " docked " + item.Submarine.Name + " to " + dockingTarget.item.Submarine.Name, ServerLog.MessageType.ItemInteraction);
}
else
{
if (item.Submarine != null && prevDockingTarget?.item?.Submarine != null)
GameServer.Log(sender.Name + " undocked " + item.Submarine.Name + " from " + prevDockingTarget.item.Submarine.Name, ServerLog.MessageType.ItemInteraction);
}
}
}
public void ServerWrite(Lidgren.Network.NetBuffer msg, Client c, object[] extraData = null)
{
msg.Write(docked);
if (docked)
{
msg.Write(dockingTarget.item.ID);
if (hullIds[0] != null && hullIds[1] != null && gapId != null)
{
msg.Write(true);
msg.Write((ushort)hullIds[0]);
msg.Write((ushort)hullIds[1]);
msg.Write((ushort)gapId);
}
else
{
msg.Write(false);
}
}
}
public void ClientRead(ServerNetObject type, Lidgren.Network.NetBuffer msg, float sendingTime)
{
bool isDocked = msg.ReadBoolean();
if (isDocked)
{
ushort dockingTargetID = msg.ReadUInt16();
bool isLocked = msg.ReadBoolean();
if (isLocked)
{
hullIds[0] = msg.ReadUInt16();
hullIds[1] = msg.ReadUInt16();
gapId = msg.ReadUInt16();
}
Entity targetEntity = Entity.FindEntityByID(dockingTargetID);
if (targetEntity == null || !(targetEntity is Item))
{
DebugConsole.ThrowError("Invalid docking port network event (can't dock to " + targetEntity.ToString() + ")");
return;
}
dockingTarget = (targetEntity as Item).GetComponent<DockingPort>();
if (dockingTarget == null)
{
DebugConsole.ThrowError("Invalid docking port network event (" + targetEntity + " doesn't have a docking port component)");
return;
}
Dock(dockingTarget);
if (isLocked)
{
Lock(true);
hulls[0].ID = (ushort)hullIds[0];
hulls[1].ID = (ushort)hullIds[1];
gap.ID = (ushort)gapId;
}
}
else
{
Undock();
}
}
}
}
+554
View File
@@ -0,0 +1,554 @@
using Barotrauma.Lights;
using Barotrauma.Networking;
using FarseerPhysics;
using FarseerPhysics.Dynamics;
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.Items.Components
{
class Door : ItemComponent, IDrawableComponent, IServerSerializable
{
private Gap linkedGap;
private Rectangle window;
private ConvexHull convexHull;
private ConvexHull convexHull2;
private bool isOpen;
private float openState;
private PhysicsBody body;
private Sprite doorSprite, weldedSprite;
private bool isHorizontal;
private bool isStuck;
private bool? predictedState;
private float resetPredictionTimer;
public PhysicsBody Body
{
get { return body; }
}
private float stuck;
public float Stuck
{
get { return stuck; }
set
{
if (isOpen) return;
stuck = MathHelper.Clamp(value, 0.0f, 100.0f);
if (stuck == 0.0f) isStuck = false;
if (stuck == 100.0f) isStuck = true;
}
}
public Gap LinkedGap
{
get
{
if (linkedGap != null) return linkedGap;
foreach (MapEntity e in item.linkedTo)
{
linkedGap = e as Gap;
if (linkedGap != null)
{
linkedGap.PassAmbientLight = window != Rectangle.Empty;
return linkedGap;
}
}
Rectangle rect = item.Rect;
if (isHorizontal)
{
rect.Y += 5;
rect.Height += 10;
}
else
{
rect.X -= 5;
rect.Width += 10;
}
linkedGap = new Gap(rect, Item.Submarine);
linkedGap.Submarine = item.Submarine;
linkedGap.PassAmbientLight = window != Rectangle.Empty;
linkedGap.Open = openState;
item.linkedTo.Add(linkedGap);
return linkedGap;
}
}
[HasDefaultValue("0.0,0.0,0.0,0.0", false)]
public string Window
{
get { return ToolBox.Vector4ToString(new Vector4(window.X, window.Y, window.Width, window.Height)); }
set
{
Vector4 vector = ToolBox.ParseToVector4(value);
if (vector.Z!=0.0f || vector.W !=0.0f)
{
window = new Rectangle((int)vector.X, (int)vector.Y, (int)vector.Z, (int)vector.W);
}
}
}
public Rectangle WindowRect
{
get { return window; }
}
[Editable, HasDefaultValue(false, true)]
public bool IsOpen
{
get { return isOpen; }
set
{
isOpen = value;
OpenState = (isOpen) ? 1.0f : 0.0f;
}
}
private Rectangle doorRect;
public float OpenState
{
get { return openState; }
set
{
float prevValue = openState;
openState = MathHelper.Clamp(value, 0.0f, 1.0f);
if (openState == prevValue) return;
UpdateConvexHulls();
}
}
public Door(Item item, XElement element)
: base(item, element)
{
//Vector2 position = new Vector2(newRect.X, newRect.Y);
isHorizontal = ToolBox.GetAttributeBool(element, "horizontal", false);
// isOpen = false;
foreach (XElement subElement in element.Elements())
{
string texturePath = ToolBox.GetAttributeString(subElement, "texture", "");
switch (subElement.Name.ToString().ToLowerInvariant())
{
case "sprite":
doorSprite = new Sprite(subElement, texturePath.Contains("/") ? "" : Path.GetDirectoryName(item.Prefab.ConfigFile));
break;
case "weldedsprite":
weldedSprite = new Sprite(subElement, texturePath.Contains("/") ? "" : Path.GetDirectoryName(item.Prefab.ConfigFile));
break;
}
}
doorRect = new Rectangle(
item.Rect.Center.X - (int)(doorSprite.size.X / 2),
item.Rect.Y - item.Rect.Height/2 + (int)(doorSprite.size.Y / 2.0f),
(int)doorSprite.size.X,
(int)doorSprite.size.Y);
body = new PhysicsBody(
ConvertUnits.ToSimUnits(Math.Max(doorRect.Width, 1)),
ConvertUnits.ToSimUnits(Math.Max(doorRect.Height, 1)),
0.0f,
1.5f);
body.UserData = item;
body.CollisionCategories = Physics.CollisionWall;
body.BodyType = BodyType.Static;
body.SetTransform(
ConvertUnits.ToSimUnits(new Vector2(doorRect.Center.X, doorRect.Y - doorRect.Height / 2)),
0.0f);
body.Friction = 0.5f;
//string spritePath = Path.GetDirectoryName(item.Prefab.ConfigFile) + "\\"+ ToolBox.GetAttributeString(element, "sprite", "");
IsActive = true;
}
private void UpdateConvexHulls()
{
doorRect = new Rectangle(
item.Rect.Center.X - (int)(doorSprite.size.X / 2),
item.Rect.Y - item.Rect.Height / 2 + (int)(doorSprite.size.Y / 2.0f),
(int)doorSprite.size.X,
(int)doorSprite.size.Y);
Rectangle rect = doorRect;
if (isHorizontal)
{
rect.Width = (int)(rect.Width * (1.0f - openState));
}
else
{
rect.Height = (int)(rect.Height * (1.0f - openState));
}
if (window.Height > 0 && window.Width > 0)
{
rect.Height = -window.Y;
rect.Y += (int)(doorRect.Height * openState);
rect.Height = Math.Max(rect.Height - (rect.Y - doorRect.Y), 0);
rect.Y = Math.Min(doorRect.Y, rect.Y);
if (convexHull2 != null)
{
Rectangle rect2 = doorRect;
rect2.Y = rect2.Y + window.Y - window.Height;
rect2.Y += (int)(doorRect.Height * openState);
rect2.Y = Math.Min(doorRect.Y, rect2.Y);
rect2.Height = rect2.Y - (doorRect.Y - (int)(doorRect.Height * (1.0f - openState)));
//convexHull2.SetVertices(GetConvexHullCorners(rect2));
if (rect2.Height == 0)
{
convexHull2.Enabled = false;
}
else
{
convexHull2.Enabled = true;
convexHull2.SetVertices(GetConvexHullCorners(rect2));
}
}
}
if (convexHull == null) return;
if (rect.Height == 0 || rect.Width == 0)
{
convexHull.Enabled = false;
}
else
{
convexHull.Enabled = true;
convexHull.SetVertices(GetConvexHullCorners(rect));
}
}
private Vector2[] GetConvexHullCorners(Rectangle rect)
{
Vector2[] corners = new Vector2[4];
corners[0] = new Vector2(rect.X, rect.Y - rect.Height);
corners[1] = new Vector2(rect.X, rect.Y);
corners[2] = new Vector2(rect.Right, rect.Y);
corners[3] = new Vector2(rect.Right, rect.Y - rect.Height);
return corners;
}
public override void Move(Vector2 amount)
{
base.Move(amount);
//LinkedGap.Move(amount);
body.SetTransform(body.SimPosition + ConvertUnits.ToSimUnits(amount), 0.0f);
UpdateConvexHulls();
//convexHull.Move(amount);
//if (convexHull2 != null) convexHull2.Move(amount);
}
public override bool Pick(Character picker)
{
isOpen = !isOpen;
return true;
}
public override bool Select(Character character)
{
//can only be selected if the item is broken
return item.Condition <= 0.0f;
}
public override void Update(float deltaTime, Camera cam)
{
bool isClosing = false;
if (!isStuck)
{
if (predictedState == null)
{
OpenState += deltaTime * ((isOpen) ? 2.0f : -2.0f);
isClosing = openState > 0.0f && openState < 1.0f && !isOpen;
}
else
{
OpenState += deltaTime * (((bool)predictedState) ? 2.0f : -2.0f);
isClosing = openState > 0.0f && openState < 1.0f && !(bool)predictedState;
resetPredictionTimer -= deltaTime;
if (resetPredictionTimer <= 0.0f)
{
predictedState = null;
}
}
LinkedGap.Open = openState;
}
if (isClosing)
{
PushCharactersAway();
}
else
{
body.Enabled = openState < 1.0f;
}
//don't use the predicted state here, because it might set
//other items to an incorrect state if the prediction is wrong
item.SendSignal(0, (isOpen) ? "1" : "0", "state_out", null);
}
public override void UpdateBroken(float deltaTime, Camera cam)
{
body.Enabled = false;
linkedGap.Open = 1.0f;
}
public void Draw(SpriteBatch spriteBatch, bool editing)
{
Color color = (item.IsSelected) ? Color.Green : Color.White;
color = color * (item.Condition / 100.0f);
color.A = 255;
//prefab.sprite.Draw(spriteBatch, new Vector2(rect.X, -rect.Y), new Vector2(rect.Width, rect.Height), color);
if (stuck>0.0f && weldedSprite!=null)
{
Vector2 weldSpritePos = new Vector2(item.Rect.Center.X, item.Rect.Y-item.Rect.Height/2.0f);
if (item.Submarine != null) weldSpritePos += item.Submarine.Position;
weldSpritePos.Y = -weldSpritePos.Y;
weldedSprite.Draw(spriteBatch,
weldSpritePos, Color.White*(stuck/100.0f), 0.0f, 1.0f);
}
if (openState == 1.0f)
{
body.Enabled = false;
return;
}
if (isHorizontal)
{
Vector2 pos = new Vector2(item.Rect.X, item.Rect.Y - item.Rect.Height/2);
if (item.Submarine != null) pos += item.Submarine.DrawPosition;
pos.Y = -pos.Y;
spriteBatch.Draw(doorSprite.Texture, pos,
new Rectangle((int)(doorSprite.SourceRect.X + doorSprite.size.X * openState), (int)doorSprite.SourceRect.Y,
(int)(doorSprite.size.X * (1.0f - openState)),(int)doorSprite.size.Y),
color, 0.0f, doorSprite.Origin, 1.0f, SpriteEffects.None, doorSprite.Depth);
}
else
{
Vector2 pos = new Vector2(item.Rect.Center.X, item.Rect.Y);
if (item.Submarine != null) pos += item.Submarine.DrawPosition;
pos.Y = -pos.Y;
spriteBatch.Draw(doorSprite.Texture, pos,
new Rectangle(doorSprite.SourceRect.X, (int)(doorSprite.SourceRect.Y + doorSprite.size.Y * openState),
(int)doorSprite.size.X, (int)(doorSprite.size.Y * (1.0f - openState))),
color, 0.0f, doorSprite.Origin, 1.0f, SpriteEffects.None, doorSprite.Depth);
}
}
public override void OnMapLoaded()
{
LinkedGap.ConnectedDoor = this;
LinkedGap.Open = openState;
Vector2[] corners = GetConvexHullCorners(Rectangle.Empty);
convexHull = new ConvexHull(corners, Color.Black, item);
if (window != Rectangle.Empty) convexHull2 = new ConvexHull(corners, Color.Black, item);
UpdateConvexHulls();
}
protected override void RemoveComponentSpecific()
{
base.RemoveComponentSpecific();
if (body != null)
{
body.Remove();
body = null;
}
if (linkedGap!=null) linkedGap.Remove();
doorSprite.Remove();
if (weldedSprite != null) weldedSprite.Remove();
if (convexHull!=null) convexHull.Remove();
if (convexHull2 != null) convexHull2.Remove();
}
private void PushCharactersAway()
{
//push characters out of the doorway when the door is closing/opening
Vector2 simPos = ConvertUnits.ToSimUnits(new Vector2(item.Rect.X, item.Rect.Y));
Vector2 currSize = isHorizontal ?
new Vector2(item.Rect.Width * (1.0f - openState), doorSprite.size.Y) :
new Vector2(doorSprite.size.X, item.Rect.Height * (1.0f - openState));
Vector2 simSize = ConvertUnits.ToSimUnits(currSize);
foreach (Character c in Character.CharacterList)
{
int dir = isHorizontal ? Math.Sign(c.SimPosition.Y - item.SimPosition.Y) : Math.Sign(c.SimPosition.X - item.SimPosition.X);
List<PhysicsBody> bodies = c.AnimController.Limbs.Select(l => l.body).ToList();
bodies.Add(c.AnimController.Collider);
foreach (PhysicsBody body in bodies)
{
float diff = 0.0f;
if (isHorizontal)
{
if (body.SimPosition.X < simPos.X || body.SimPosition.X > simPos.X + simSize.X) continue;
diff = body.SimPosition.Y - item.SimPosition.Y;
}
else
{
if (body.SimPosition.Y > simPos.Y || body.SimPosition.Y < simPos.Y - simSize.Y) continue;
diff = body.SimPosition.X - item.SimPosition.X;
}
if (Math.Sign(diff) != dir)
{
SoundPlayer.PlayDamageSound(DamageSoundType.LimbBlunt, 1.0f, body);
if (isHorizontal)
{
body.SetTransform(new Vector2(body.SimPosition.X, item.SimPosition.Y + dir * simSize.Y * 2.0f), body.Rotation);
body.ApplyLinearImpulse(new Vector2(isOpen ? 0.0f : 1.0f, dir * 2.0f));
}
else
{
body.SetTransform(new Vector2(item.SimPosition.X + dir * simSize.X * 1.2f, body.SimPosition.Y), body.Rotation);
body.ApplyLinearImpulse(new Vector2(dir * 0.5f, isOpen ? 0.0f : -1.0f));
}
}
if (isHorizontal)
{
if (Math.Abs(body.SimPosition.Y - item.SimPosition.Y) > simSize.Y * 0.5f) continue;
body.ApplyLinearImpulse(new Vector2(isOpen ? 0.0f : 1.0f, dir * 0.5f));
}
else
{
if (Math.Abs(body.SimPosition.X - item.SimPosition.X) > simSize.X * 0.5f) continue;
body.ApplyLinearImpulse(new Vector2(dir * 0.5f, isOpen ? 0.0f : -1.0f));
}
c.SetStun(0.2f);
}
}
}
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power = 0.0f)
{
if (isStuck) return;
bool wasOpen = predictedState == null ? isOpen : predictedState.Value;
if (connection.Name == "toggle")
{
SetState(!wasOpen, false, true);
}
else if (connection.Name == "set_state")
{
SetState(signal != "0", false, true);
}
bool newState = predictedState == null ? isOpen : predictedState.Value;
if (sender != null && wasOpen != newState)
{
GameServer.Log(sender.Name + (newState ? " opened " : " closed ") + item.Name, ServerLog.MessageType.ItemInteraction);
}
}
public void SetState(bool open, bool isNetworkMessage, bool sendNetworkMessage = false)
{
if (isStuck || (predictedState == null && isOpen == open) || (predictedState != null && isOpen == predictedState.Value)) return;
if (GameMain.Client != null && !isNetworkMessage)
{
//clients can "predict" that the door opens/closes when a signal is received
//the prediction will be reset after 1 second, setting the door to a state
//sent by the server, or reverting it back to its old state if no msg from server was received
if (open != predictedState) PlaySound(ActionType.OnUse, item.WorldPosition);
predictedState = open;
resetPredictionTimer = CorrectionDelay;
}
else
{
if (!isNetworkMessage || open != predictedState) PlaySound(ActionType.OnUse, item.WorldPosition);
isOpen = open;
}
//opening a partially stuck door makes it less stuck
if (isOpen) stuck = MathHelper.Clamp(stuck - 30.0f, 0.0f, 100.0f);
if (sendNetworkMessage)
{
item.CreateServerEvent(this);
}
}
public void ServerWrite(Lidgren.Network.NetBuffer msg, Barotrauma.Networking.Client c, object[] extraData = null)
{
msg.Write(isOpen);
msg.WriteRangedSingle(stuck, 0.0f, 100.0f, 8);
}
public void ClientRead(ServerNetObject type, Lidgren.Network.NetBuffer msg, float sendingTime)
{
SetState(msg.ReadBoolean(), true);
Stuck = msg.ReadRangedSingle(0.0f, 100.0f, 8);
predictedState = null;
}
}
}
@@ -0,0 +1,292 @@
using System.Xml.Linq;
using FarseerPhysics;
using Microsoft.Xna.Framework;
using System.Collections.Generic;
namespace Barotrauma.Items.Components
{
class Holdable : Pickable
{
//the position(s) in the item that the Character grabs
protected Vector2[] handlePos;
private List<RelatedItem> prevRequiredItems;
string prevMsg;
//the distance from the holding characters elbow to center of the physics body of the item
protected Vector2 holdPos;
protected Vector2 aimPos;
//protected bool aimable;
private bool attachable, attached, attachedByDefault;
private PhysicsBody body;
//the angle in which the Character holds the item
protected float holdAngle;
[HasDefaultValue(false, true)]
public bool Attached
{
get { return attached && item.ParentInventory == null; }
set { attached = value; }
}
[HasDefaultValue(false, false)]
public bool ControlPose
{
get;
set;
}
[HasDefaultValue(false, false)]
public bool Attachable
{
get { return attachable; }
set { attachable = value; }
}
[HasDefaultValue(false, false)]
public bool AttachedByDefault
{
get { return attachedByDefault; }
set { attachedByDefault = value; }
}
[HasDefaultValue("0.0,0.0", false)]
public string HoldPos
{
get { return ToolBox.Vector2ToString(ConvertUnits.ToDisplayUnits(holdPos)); }
set { holdPos = ConvertUnits.ToSimUnits(ToolBox.ParseToVector2(value)); }
}
[HasDefaultValue("0.0,0.0", false)]
public string AimPos
{
get { return ToolBox.Vector2ToString(ConvertUnits.ToDisplayUnits(aimPos)); }
set { aimPos = ConvertUnits.ToSimUnits(ToolBox.ParseToVector2(value)); }
}
[HasDefaultValue(0.0f, false)]
public float HoldAngle
{
get { return MathHelper.ToDegrees(holdAngle); }
set { holdAngle = MathHelper.ToRadians(value); }
}
public Holdable(Item item, XElement element)
: base(item, element)
{
body = item.body;
handlePos = new Vector2[2];
for (int i = 1; i < 3; i++)
{
handlePos[i - 1] = ToolBox.GetAttributeVector2(element, "handle" + i, Vector2.Zero);
handlePos[i - 1] = ConvertUnits.ToSimUnits(handlePos[i - 1]);
}
canBePicked = true;
if (attachable)
{
prevRequiredItems = new List<RelatedItem>(requiredItems);
prevMsg = Msg;
requiredItems.Clear();
Msg = "";
}
if (attachedByDefault || (Screen.Selected == GameMain.EditMapScreen)) Use(1.0f);
//holdAngle = ToolBox.GetAttributeFloat(element, "holdangle", 0.0f);
//holdAngle = MathHelper.ToRadians(holdAngle);
}
public override void Drop(Character dropper)
{
DropConnectedWires(dropper);
if (body != null) item.body = body;
if (item.body != null) item.body.Enabled = true;
IsActive = false;
if (picker == null)
{
if (dropper == null) return;
picker = dropper;
}
if (picker.Inventory == null) return;
item.Submarine = picker.Submarine;
if (item.body != null)
{
item.body.ResetDynamics();
item.SetTransform(picker.SimPosition, 0.0f);
}
picker.DeselectItem(item);
picker.Inventory.RemoveItem(item);
picker = null;
}
public override void Equip(Character character)
{
picker = character;
if (character != null) item.Submarine = character.Submarine;
if (item.body == null)
{
if (body != null)
{
item.body = body;
}
else
{
return;
}
}
if (!item.body.Enabled)
{
Limb rightHand = picker.AnimController.GetLimb(LimbType.RightHand);
item.SetTransform(rightHand.SimPosition, 0.0f);
}
bool alreadySelected = character.HasSelectedItem(item);
if (picker.TrySelectItem(item))
{
item.body.Enabled = true;
IsActive = true;
if (!alreadySelected) Networking.GameServer.Log(character.Name + " equipped " + item.Name, Networking.ServerLog.MessageType.ItemInteraction);
}
}
public override void Unequip(Character character)
{
if (picker == null) return;
picker.DeselectItem(item);
Networking.GameServer.Log(character.Name + " unequipped " + item.Name, Networking.ServerLog.MessageType.ItemInteraction);
item.body.Enabled = false;
IsActive = false;
}
public override bool Pick(Character picker)
{
if (!attachable)
{
return base.Pick(picker);
}
if (!base.Pick(picker))
{
return false;
}
else
{
requiredItems.Clear();
Msg = "";
}
attached = false;
if (body != null) item.body = body;
//item.body.Enabled = true;
return true;
}
public override bool Use(float deltaTime, Character character = null)
{
if (!attachable || item.body == null) return true;
if (character != null && !character.IsKeyDown(InputType.Aim)) return false;
item.Drop();
var containedItems = item.ContainedItems;
if (containedItems != null)
{
foreach (Item contained in containedItems)
{
if (contained.body == null) continue;
contained.SetTransform(item.SimPosition, contained.body.Rotation);
}
}
item.body.Enabled = false;
item.body = null;
requiredItems = new List<RelatedItem>(prevRequiredItems);
Msg = prevMsg;
attached = true;
return true;
}
public override void UpdateBroken(float deltaTime, Camera cam)
{
Update(deltaTime, cam);
}
public override void Update(float deltaTime, Camera cam)
{
if (item.body == null || !item.body.Enabled) return;
if (!picker.HasSelectedItem(item)) IsActive = false;
ApplyStatusEffects(ActionType.OnActive, deltaTime, picker);
if (item.body.Dir != picker.AnimController.Dir) Flip(item);
item.Submarine = picker.Submarine;
picker.AnimController.HoldItem(deltaTime, item, handlePos, holdPos, aimPos, picker.IsKeyDown(InputType.Aim), holdAngle);
}
protected void Flip(Item item)
{
handlePos[0].X = -handlePos[0].X;
handlePos[1].X = -handlePos[1].X;
item.body.Dir = -item.body.Dir;
}
public override void OnMapLoaded()
{
//prevRequiredItems = new List<RelatedItem>(requiredItems);
if (!attachable) return;
if (Attached)
{
Use(1.0f);
}
else
{
if (item.ParentInventory != null)
{
if (body != null)
{
item.body = body;
body.Enabled = false;
}
attached = false;
}
requiredItems.Clear();
Msg = "";
}
}
}
}
@@ -0,0 +1,261 @@
using FarseerPhysics;
using FarseerPhysics.Dynamics;
using FarseerPhysics.Dynamics.Contacts;
using Microsoft.Xna.Framework;
using System;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Items.Components
{
class MeleeWeapon : Holdable
{
private float hitPos;
private bool hitting;
private Attack attack;
private float range;
private Character user;
private float reload;
private float reloadTimer;
[HasDefaultValue(0.0f, false)]
public float Range
{
get { return ConvertUnits.ToDisplayUnits(range); }
set { range = ConvertUnits.ToSimUnits(value); }
}
[HasDefaultValue(0.5f, false)]
public float Reload
{
get { return reload; }
set { reload = Math.Max(0.0f, value); }
}
public MeleeWeapon(Item item, XElement element)
: base(item, element)
{
//throwForce = ToolBox.GetAttributeFloat(element, "throwforce", 1.0f);
foreach (XElement subElement in element.Elements())
{
if (subElement.Name.ToString().ToLowerInvariant() != "attack") continue;
attack = new Attack(subElement);
}
}
public override bool Use(float deltaTime, Character character = null)
{
if (character == null || reloadTimer>0.0f) return false;
if (!character.IsKeyDown(InputType.Aim) || hitting) return false;
//don't allow hitting if the character is already hitting with another weapon
for (int i = 0; i < 2; i++ )
{
if (character.SelectedItems[i] == null || character.SelectedItems[i] == Item) continue;
var otherWeapon = character.SelectedItems[i].GetComponent<MeleeWeapon>();
if (otherWeapon == null) continue;
if (otherWeapon.hitting) return false;
}
SetUser(character);
if (hitPos < MathHelper.Pi * 0.69f) return false;
reloadTimer = reload;
item.body.FarseerBody.CollisionCategories = Physics.CollisionProjectile;
item.body.FarseerBody.CollidesWith = Physics.CollisionCharacter | Physics.CollisionWall;
item.body.FarseerBody.OnCollision += OnCollision;
foreach (Limb l in character.AnimController.Limbs)
{
//item.body.FarseerBody.IgnoreCollisionWith(l.body.FarseerBody);
if (character.AnimController.InWater) continue;
if (l.type == LimbType.LeftFoot || l.type == LimbType.LeftThigh || l.type == LimbType.LeftLeg) continue;
if (l.type == LimbType.Head || l.type == LimbType.Torso)
{
l.body.ApplyLinearImpulse(new Vector2(character.AnimController.Dir * 7.0f, -4.0f));
}
else
{
l.body.ApplyLinearImpulse(new Vector2(character.AnimController.Dir * 5.0f, -2.0f));
}
}
hitting = true;
IsActive = true;
return false;
}
public override void Drop(Character dropper)
{
base.Drop(dropper);
hitting = false;
hitPos = 0.0f;
}
public override void UpdateBroken(float deltaTime, Camera cam)
{
Update(deltaTime, cam);
}
public override void Update(float deltaTime, Camera cam)
{
if (!item.body.Enabled) return;
if (!picker.HasSelectedItem(item)) IsActive = false;
reloadTimer -= deltaTime;
if (!picker.IsKeyDown(InputType.Aim) && !hitting) hitPos = 0.0f;
ApplyStatusEffects(ActionType.OnActive, deltaTime, picker);
if (item.body.Dir != picker.AnimController.Dir) Flip(item);
AnimController ac = picker.AnimController;
if (!hitting)
{
if (picker.IsKeyDown(InputType.Aim))
{
hitPos = Math.Min(hitPos+deltaTime*5.0f, MathHelper.Pi*0.7f);
ac.HoldItem(deltaTime, item, handlePos, new Vector2(0.6f, -0.1f), new Vector2(-0.3f, 0.2f), false, hitPos);
}
else
{
ac.HoldItem(deltaTime, item, handlePos, new Vector2(hitPos, 0.0f), aimPos, false, holdAngle);
}
}
else
{
//Vector2 diff = Vector2.Normalize(picker.CursorPosition - ac.RefLimb.Position);
//diff.X = diff.X * ac.Dir;
hitPos -= deltaTime*15.0f;
//angl = -hitPos * 2.0f;
// System.Diagnostics.Debug.WriteLine("<1.0f "+hitPos);
ac.HoldItem(deltaTime, item, handlePos, new Vector2(0.6f, -0.1f), new Vector2(-0.3f, 0.2f), false, hitPos);
//}
//else
//{
// System.Diagnostics.Debug.WriteLine(">1.0f " + hitPos);
// ac.HoldItem(deltaTime, item, handlePos, new Vector2(0.5f, 0.2f), new Vector2(1.0f, 0.2f), false, 0.0f);
//}
if (hitPos < -MathHelper.PiOver4*1.2f)
{
RestoreCollision();
hitting = false;
}
}
}
private void SetUser(Character character)
{
if (user == character) return;
if (user != null)
{
foreach (Limb limb in user.AnimController.Limbs)
{
try
{
item.body.FarseerBody.RestoreCollisionWith(limb.body.FarseerBody);
}
catch
{
continue;
}
}
}
foreach (Limb limb in character.AnimController.Limbs)
{
item.body.FarseerBody.IgnoreCollisionWith(limb.body.FarseerBody);
}
user = character;
}
private void RestoreCollision()
{
item.body.FarseerBody.OnCollision -= OnCollision;
item.body.CollisionCategories = Physics.CollisionItem;
item.body.CollidesWith = Physics.CollisionWall;
//foreach (Limb l in picker.AnimController.Limbs)
//{
// item.body.FarseerBody.RestoreCollisionWith(l.body.FarseerBody);
//}
}
private bool OnCollision(Fixture f1, Fixture f2, Contact contact)
{
Character target = null;
Limb limb = f2.Body.UserData as Limb;
if (limb != null)
{
if (limb.character == picker) return false;
target = limb.character;
}
else
{
return false;
}
if (target == null)
{
target = f2.Body.UserData as Character;
}
if (target == null) return false;
if (attack != null) attack.DoDamage(user, target, item.WorldPosition, 1.0f);
RestoreCollision();
hitting = false;
if (GameMain.Client != null) return true;
if (GameMain.Server != null)
{
GameMain.Server.CreateEntityEvent(item, new object[] { Networking.NetEntityEvent.Type.ApplyStatusEffect, ActionType.OnUse, target.ID });
string logStr = picker?.Name + " used " + item.Name;
if (item.ContainedItems != null && item.ContainedItems.Length > 0)
{
logStr += "(" + string.Join(", ", item.ContainedItems.Select(i => i?.Name)) + ")";
}
logStr += " on " + target + ".";
Networking.GameServer.Log(logStr, Networking.ServerLog.MessageType.Attack);
}
ApplyStatusEffects(ActionType.OnUse, 1.0f, limb.character);
return true;
}
}
}
@@ -0,0 +1,209 @@
using Barotrauma.Networking;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
using System.Xml.Linq;
namespace Barotrauma.Items.Components
{
class Pickable : ItemComponent
{
protected Character picker;
protected List<InvSlotType> allowedSlots;
private float pickTimer;
public List<InvSlotType> AllowedSlots
{
get { return allowedSlots; }
}
public Character Picker
{
get { return picker; }
}
public Pickable(Item item, XElement element)
: base(item, element)
{
allowedSlots = new List<InvSlotType>();
string slotString = ToolBox.GetAttributeString(element, "slots", "Any");
string[] slotCombinations = slotString.Split(',');
foreach (string slotCombination in slotCombinations)
{
string[] slots = slotCombination.Split('+');
InvSlotType allowedSlot = InvSlotType.None;
foreach (string slot in slots)
{
if (slot.ToLowerInvariant() == "bothhands")
{
allowedSlot = InvSlotType.LeftHand | InvSlotType.RightHand;
}
else
{
allowedSlot = allowedSlot | (InvSlotType)Enum.Parse(typeof(InvSlotType), slot.Trim());
}
}
allowedSlots.Add(allowedSlot);
}
canBePicked = true;
}
public override bool Pick(Character picker)
{
//return if someone is already trying to pick the item
if (pickTimer>0.0f) return false;
if (picker == null || picker.Inventory == null) return false;
if (PickingTime>0.0f)
{
CoroutineManager.StartCoroutine(WaitForPick(picker, PickingTime));
return false;
}
else
{
return OnPicked(picker);
}
}
private bool OnPicked(Character picker)
{
if (picker.Inventory.TryPutItem(item, allowedSlots))
{
if (!picker.HasSelectedItem(item) && item.body != null) item.body.Enabled = false;
this.picker = picker;
for (int i = item.linkedTo.Count - 1; i >= 0; i--)
{
item.linkedTo[i].RemoveLinked(item);
}
item.linkedTo.Clear();
DropConnectedWires(picker);
ApplyStatusEffects(ActionType.OnPicked, 1.0f, picker);
return true;
}
return false;
}
private IEnumerable<object> WaitForPick(Character picker, float requiredTime)
{
var leftHand = picker.AnimController.GetLimb(LimbType.LeftHand);
var rightHand = picker.AnimController.GetLimb(LimbType.RightHand);
pickTimer = 0.0f;
while (pickTimer < requiredTime && Screen.Selected != GameMain.EditMapScreen)
{
if (picker.IsKeyDown(InputType.Aim) ||
!item.IsInPickRange(picker.WorldPosition) ||
picker.Stun > 0.0f || picker.IsDead)
{
StopPicking(picker);
yield return CoroutineStatus.Success;
}
picker.UpdateHUDProgressBar(
this,
item.WorldPosition,
pickTimer / requiredTime,
Color.Red, Color.Green);
picker.AnimController.Anim = AnimController.Animation.UsingConstruction;
picker.AnimController.TargetMovement = Vector2.Zero;
leftHand.Disabled = true;
leftHand.pullJoint.Enabled = true;
leftHand.pullJoint.WorldAnchorB = item.SimPosition + Vector2.UnitY * ((pickTimer / 10.0f) % 0.1f);
rightHand.Disabled = true;
rightHand.pullJoint.Enabled = true;
rightHand.pullJoint.WorldAnchorB = item.SimPosition + Vector2.UnitY * ((pickTimer / 10.0f) % 0.1f);
pickTimer += CoroutineManager.DeltaTime;
yield return CoroutineStatus.Running;
}
StopPicking(picker);
if (!picker.IsRemotePlayer) OnPicked(picker);
yield return CoroutineStatus.Success;
}
private void StopPicking(Character picker)
{
picker.AnimController.Anim = AnimController.Animation.None;
pickTimer = 0.0f;
}
protected void DropConnectedWires(Character character)
{
Vector2 pos = character == null ? item.SimPosition : character.SimPosition;
var connectionPanel = item.GetComponent<ConnectionPanel>();
if (connectionPanel == null) return;
foreach (Connection c in connectionPanel.Connections)
{
foreach (Wire w in c.Wires)
{
if (w == null) continue;
w.Item.Drop(character);
w.Item.SetTransform(pos, 0.0f);
}
}
}
public override void Drop(Character dropper)
{
if (picker == null)
{
picker = dropper;
}
Vector2 bodyDropPos = Vector2.Zero;
if (picker == null || picker.Inventory == null)
{
if (item.ParentInventory != null && item.ParentInventory.Owner != null)
{
bodyDropPos = item.ParentInventory.Owner.SimPosition;
if (item.body != null) item.body.ResetDynamics();
}
}
else
{
DropConnectedWires(picker);
item.Submarine = picker.Submarine;
bodyDropPos = picker.SimPosition;
picker.Inventory.RemoveItem(item);
picker = null;
}
if (item.body != null && !item.body.Enabled)
{
item.body.ResetDynamics();
item.SetTransform(bodyDropPos, 0.0f);
item.body.Enabled = true;
}
}
}
}
@@ -0,0 +1,104 @@
using System.Xml.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Barotrauma.Particles;
namespace Barotrauma.Items.Components
{
class Propulsion : ItemComponent
{
private float force;
private string particles;
private float useState;
private ParticlePrefab.DrawTargetType usableIn;
[HasDefaultValue(0.0f, false)]
public float Force
{
get { return force; }
set { force = value; }
}
[HasDefaultValue("", false)]
public string Particles
{
get { return particles; }
set { particles = value; }
}
public Propulsion(Item item, XElement element)
: base(item,element)
{
switch (ToolBox.GetAttributeString(element, "usablein", "both").ToLowerInvariant())
{
case "air":
usableIn = ParticlePrefab.DrawTargetType.Air;
break;
case "water":
usableIn = ParticlePrefab.DrawTargetType.Water;
break;
case "both":
default:
usableIn = ParticlePrefab.DrawTargetType.Both;
break;
}
}
public override bool Use(float deltaTime, Character character = null)
{
if (character == null) return false;
if (!character.IsKeyDown(InputType.Aim) || character.Stun>0.0f) return false;
IsActive = true;
useState = 0.1f;
if (character.AnimController.InWater)
{
if (usableIn == ParticlePrefab.DrawTargetType.Air) return true;
}
else
{
if (usableIn == ParticlePrefab.DrawTargetType.Water) return true;
}
Vector2 dir = Vector2.Normalize(character.CursorPosition - character.Position);
Vector2 propulsion = dir * force;
if (character.AnimController.InWater) character.AnimController.TargetMovement = dir;
foreach (Limb limb in character.AnimController.Limbs)
{
if (limb.WearingItems.Find(w => w.WearableComponent.Item == this.item)==null) continue;
limb.body.ApplyForce(propulsion);
}
character.AnimController.Collider.ApplyForce(propulsion);
if (character.SelectedItems[0] == item) character.AnimController.GetLimb(LimbType.RightHand).body.ApplyForce(propulsion);
if (character.SelectedItems[1] == item) character.AnimController.GetLimb(LimbType.LeftHand).body.ApplyForce(propulsion);
if (!string.IsNullOrWhiteSpace(particles))
{
GameMain.ParticleManager.CreateParticle(particles, item.WorldPosition,
item.body.Rotation + ((item.body.Dir > 0.0f) ? 0.0f : MathHelper.Pi), 0.0f, item.CurrentHull);
}
return true;
}
public override void Update(float deltaTime, Camera cam)
{
useState -= deltaTime;
if (useState <= 0.0f) IsActive = false;
}
}
}
@@ -0,0 +1,126 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using FarseerPhysics;
using FarseerPhysics.Dynamics;
using Microsoft.Xna.Framework;
namespace Barotrauma.Items.Components
{
class RangedWeapon : ItemComponent
{
private float reload, reloadTimer;
private Vector2 barrelPos;
[HasDefaultValue("0.0,0.0", false)]
public string BarrelPos
{
get { return ToolBox.Vector2ToString(ConvertUnits.ToDisplayUnits(barrelPos)); }
set { barrelPos = ConvertUnits.ToSimUnits(ToolBox.ParseToVector2(value)); }
}
[HasDefaultValue(1.0f, false)]
public float Reload
{
get { return reload; }
set { reload = Math.Max(value, 0.0f); }
}
public Vector2 TransformedBarrelPos
{
get
{
Matrix bodyTransform = Matrix.CreateRotationZ(item.body.Rotation);
Vector2 flippedPos = barrelPos;
if (item.body.Dir < 0.0f) flippedPos.X = -flippedPos.X;
return (Vector2.Transform(flippedPos, bodyTransform) + item.body.SimPosition);
}
}
public RangedWeapon(Item item, XElement element)
: base(item, element)
{
//barrelPos = ToolBox.GetAttributeVector2(element, "barrelpos", Vector2.Zero);
//barrelPos = ConvertUnits.ToSimUnits(barrelPos);
}
public override void Update(float deltaTime, Camera cam)
{
reloadTimer -= deltaTime;
if (reloadTimer < 0.0f)
{
reloadTimer = 0.0f;
IsActive = false;
}
}
public override bool Use(float deltaTime, Character character = null)
{
if (character == null) return false;
if (!character.IsKeyDown(InputType.Aim) || reloadTimer > 0.0f) return false;
IsActive = true;
reloadTimer = reload;
List<Body> limbBodies = new List<Body>();
foreach (Limb l in character.AnimController.Limbs)
{
limbBodies.Add(l.body.FarseerBody);
}
float degreeOfFailure = (100.0f - DegreeOfSuccess(character))/100.0f;
degreeOfFailure *= degreeOfFailure;
if (degreeOfFailure > Rand.Range(0.0f, 1.0f))
{
ApplyStatusEffects(ActionType.OnFailure, 1.0f, character);
}
Item[] containedItems = item.ContainedItems;
if (containedItems != null)
{
foreach (Item projectile in containedItems)
{
if (projectile == null) continue;
//find the projectile-itemcomponent of the projectile,
//and add the limbs of the shooter to the list of bodies to be ignored
//so that the player can't shoot himself
Projectile projectileComponent= projectile.GetComponent<Projectile>();
if (projectileComponent == null) continue;
projectile.body.ResetDynamics();
projectile.SetTransform(TransformedBarrelPos,
((item.body.Dir == 1.0f) ? item.body.Rotation : item.body.Rotation - MathHelper.Pi)
+ Rand.Range(-degreeOfFailure, degreeOfFailure));
projectile.Use(deltaTime);
projectileComponent.User = character;
projectile.body.ApplyTorque(projectile.body.Mass * degreeOfFailure * Rand.Range(-10.0f, 10.0f));
//recoil
item.body.ApplyLinearImpulse(
new Vector2((float)Math.Cos(projectile.body.Rotation), (float)Math.Sin(projectile.body.Rotation)) * item.body.Mass * -50.0f);
projectileComponent.IgnoredBodies = limbBodies;
item.RemoveContained(projectile);
Rope rope = item.GetComponent<Rope>();
if (rope != null) rope.Attach(projectile);
return true;
}
}
return true;
}
}
}
@@ -0,0 +1,271 @@
using System;
using System.Collections.Generic;
using System.Xml.Linq;
using FarseerPhysics;
using FarseerPhysics.Dynamics;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace Barotrauma.Items.Components
{
class RepairTool : ItemComponent
{
private readonly List<string> fixableEntities;
private float range;
private Vector2 pickedPosition;
private Vector2 barrelPos;
private string particles;
private float activeTimer;
[HasDefaultValue(0.0f, false)]
public float Range
{
get { return range; }
set { range = value; }
}
[HasDefaultValue(0.0f, false)]
public float StructureFixAmount
{
get; set;
}
[HasDefaultValue(0.0f, false)]
public float LimbFixAmount
{
get; set;
}
[HasDefaultValue(0.0f, false)]
public float ExtinquishAmount
{
get; set;
}
[HasDefaultValue("", false)]
public string Particles
{
get { return particles; }
set { particles = value; }
}
[HasDefaultValue(0.0f, false)]
public float ParticleSpeed
{
get; set;
}
[HasDefaultValue("0.0,0.0", false)]
public string BarrelPos
{
get { return ToolBox.Vector2ToString(barrelPos); }
set { barrelPos = ToolBox.ParseToVector2(value); }
}
public Vector2 TransformedBarrelPos
{
get
{
Matrix bodyTransform = Matrix.CreateRotationZ(item.body.Rotation);
Vector2 flippedPos = barrelPos;
if (item.body.Dir < 0.0f) flippedPos.X = -flippedPos.X;
return (Vector2.Transform(flippedPos, bodyTransform));
}
}
public RepairTool(Item item, XElement element)
: base(item, element)
{
this.item = item;
fixableEntities = new List<string>();
foreach (XElement subElement in element.Elements())
{
switch (subElement.Name.ToString().ToLowerInvariant())
{
case "fixable":
fixableEntities.Add(subElement.Attribute("name").Value);
break;
}
}
}
public override void Update(float deltaTime, Camera cam)
{
activeTimer -= deltaTime;
if (activeTimer <= 0.0f) IsActive = false;
}
public override bool Use(float deltaTime, Character character = null)
{
if (character == null) return false;
if (!character.IsKeyDown(InputType.Aim)) return false;
//if (DoesUseFail(Character)) return false;
//targetPosition = targetPosition.X, -targetPosition.Y);
float degreeOfSuccess = DegreeOfSuccess(character)/100.0f;
if (Rand.Range(0.0f, 0.5f) > degreeOfSuccess)
{
ApplyStatusEffects(ActionType.OnFailure, deltaTime, character);
return false;
}
Vector2 targetPosition = item.WorldPosition;
targetPosition += new Vector2(
(float)Math.Cos(item.body.Rotation),
(float)Math.Sin(item.body.Rotation)) * range * item.body.Dir;
List<Body> ignoredBodies = new List<Body>();
foreach (Limb limb in character.AnimController.Limbs)
{
if (Rand.Range(0.0f, 0.5f) > degreeOfSuccess) continue;
ignoredBodies.Add(limb.body.FarseerBody);
}
IsActive = true;
activeTimer = 0.1f;
Vector2 rayStart = ConvertUnits.ToSimUnits(item.WorldPosition);
Vector2 rayEnd = ConvertUnits.ToSimUnits(targetPosition);
if (character.Submarine == null)
{
foreach (Submarine sub in Submarine.Loaded)
{
Repair(rayStart - sub.SimPosition, rayEnd - sub.SimPosition, deltaTime, character, degreeOfSuccess, ignoredBodies);
}
Repair(rayStart, rayEnd, deltaTime, character, degreeOfSuccess, ignoredBodies);
}
else
{
Repair(rayStart - character.Submarine.SimPosition, rayEnd - character.Submarine.SimPosition, deltaTime, character, degreeOfSuccess, ignoredBodies);
}
GameMain.ParticleManager.CreateParticle(particles, item.WorldPosition + TransformedBarrelPos,
-item.body.Rotation + ((item.body.Dir > 0.0f) ? 0.0f : MathHelper.Pi), ParticleSpeed);
return true;
}
private void Repair(Vector2 rayStart, Vector2 rayEnd, float deltaTime, Character user, float degreeOfSuccess, List<Body> ignoredBodies)
{
if (ExtinquishAmount > 0.0f && item.CurrentHull != null)
{
Vector2 displayPos = ConvertUnits.ToDisplayUnits(rayStart + (rayEnd - rayStart) * Submarine.LastPickedFraction * 0.9f);
displayPos += item.CurrentHull.Submarine.Position;
Hull hull = Hull.FindHull(displayPos, item.CurrentHull);
if (hull != null)
{
hull.Extinquish(deltaTime, ExtinquishAmount, displayPos);
if (hull != item.CurrentHull)
{
item.CurrentHull.Extinquish(deltaTime, ExtinquishAmount, displayPos);
}
}
}
Body targetBody = Submarine.PickBody(rayStart, rayEnd, ignoredBodies);
if (targetBody == null || targetBody.UserData == null) return;
pickedPosition = Submarine.LastPickedPosition;
Structure targetStructure;
Limb targetLimb;
Item targetItem;
if ((targetStructure = (targetBody.UserData as Structure)) != null)
{
if (!fixableEntities.Contains("structure") && !fixableEntities.Contains(targetStructure.Name)) return;
if (targetStructure.IsPlatform) return;
int sectionIndex = targetStructure.FindSectionIndex(ConvertUnits.ToDisplayUnits(pickedPosition));
if (sectionIndex < 0) return;
Vector2 progressBarPos = targetStructure.SectionPosition(sectionIndex);
if (targetStructure.Submarine != null)
{
progressBarPos += targetStructure.Submarine.DrawPosition;
}
var progressBar = user.UpdateHUDProgressBar(
targetStructure,
progressBarPos,
1.0f - targetStructure.SectionDamage(sectionIndex) / targetStructure.Health,
Color.Red, Color.Green);
if (progressBar != null) progressBar.Size = new Vector2(60.0f, 20.0f);
targetStructure.AddDamage(sectionIndex, -StructureFixAmount * degreeOfSuccess);
//if the next section is small enough, apply the effect to it as well
//(to make it easier to fix a small "left-over" section)
for (int i = -1; i < 2; i += 2)
{
int nextSectionLength = targetStructure.SectionLength(sectionIndex + i);
if ((sectionIndex == 1 && i == -1) ||
(sectionIndex == targetStructure.SectionCount - 2 && i == 1) ||
(nextSectionLength > 0 && nextSectionLength < Structure.wallSectionSize * 0.3f))
{
//targetStructure.HighLightSection(sectionIndex + i);
targetStructure.AddDamage(sectionIndex + i, -StructureFixAmount * degreeOfSuccess);
}
}
}
else if ((targetLimb = (targetBody.UserData as Limb)) != null)
{
targetLimb.character.AddDamage(CauseOfDeath.Damage, -LimbFixAmount * degreeOfSuccess, user);
}
else if ((targetItem = (targetBody.UserData as Item)) != null)
{
targetItem.IsHighlighted = true;
ApplyStatusEffects(ActionType.OnUse, targetItem.AllPropertyObjects, deltaTime);
}
}
public override bool AIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective)
{
Gap leak = objective.OperateTarget as Gap;
if (leak == null) return true;
float dist = Vector2.Distance(leak.WorldPosition, item.WorldPosition);
//too far away -> consider this done and hope the AI is smart enough to move closer
if (dist > range * 5.0f) return true;
//steer closer if almost in range
if (dist > range)
{
Vector2 standPos = leak.isHorizontal ?
new Vector2(Math.Sign(item.WorldPosition.X - leak.WorldPosition.X), 0.0f)
: new Vector2(0.0f, Math.Sign(item.WorldPosition.Y - leak.WorldPosition.Y));
standPos = leak.WorldPosition + standPos * range;
character.AIController.SteeringManager.SteeringManual(deltaTime, (standPos - character.WorldPosition) / 1000.0f);
}
else
{
//close enough -> stop moving
character.AIController.SteeringManager.Reset();
}
character.CursorPosition = leak.Position;
character.SetInput(InputType.Aim, false, true);
Use(deltaTime, character);
return leak.Open <= 0.0f;
}
}
}
@@ -0,0 +1,143 @@
using Microsoft.Xna.Framework;
using System.Xml.Linq;
namespace Barotrauma.Items.Components
{
class Throwable : Holdable
{
float throwForce;
float throwPos;
bool throwing;
[HasDefaultValue(1.0f, false)]
public float ThrowForce
{
get { return throwForce; }
set { throwForce = value; }
}
public Throwable(Item item, XElement element)
: base(item, element)
{
//throwForce = ToolBox.GetAttributeFloat(element, "throwforce", 1.0f);
}
public override bool Use(float deltaTime, Character character = null)
{
if (character == null) return false;
if (!character.IsKeyDown(InputType.Aim) || throwing) return false;
//Vector2 diff = Vector2.Normalize(Character.CursorPosition - Character.AnimController.RefLimb.Position);
//if (Character.SelectedItems[1]==item)
//{
// Limb leftHand = Character.AnimController.GetLimb(LimbType.LeftHand);
// leftHand.body.ApplyLinearImpulse(diff * 20.0f);
// leftHand.Disabled = true;
//}
//if (Character.SelectedItems[0] == item)
//{
// Limb rightHand = Character.AnimController.GetLimb(LimbType.RightHand);
// rightHand.body.ApplyLinearImpulse(diff * 20.0f);
// rightHand.Disabled = true;
//}
throwing = true;
IsActive = true;
return true;
}
public override void SecondaryUse(float deltaTime, Character character = null)
{
if (throwing) return;
}
public override void Drop(Character dropper)
{
base.Drop(dropper);
throwing = false;
throwPos = 0.0f;
}
public override void UpdateBroken(float deltaTime, Camera cam)
{
Update(deltaTime, cam);
}
public override void Update(float deltaTime, Camera cam)
{
if (!item.body.Enabled) return;
if (!picker.HasSelectedItem(item)) IsActive = false;
if (!picker.IsKeyDown(InputType.Aim) && !throwing) throwPos = 0.0f;
ApplyStatusEffects(ActionType.OnActive, deltaTime, picker);
if (item.body.Dir != picker.AnimController.Dir) Flip(item);
AnimController ac = picker.AnimController;
item.Submarine = picker.Submarine;
if (!throwing)
{
if (picker.IsKeyDown(InputType.Aim))
{
throwPos = (float)System.Math.Min(throwPos+deltaTime*5.0f, MathHelper.Pi*0.7f);
ac.HoldItem(deltaTime, item, handlePos, new Vector2(0.6f, -0.0f), new Vector2(-0.3f, 0.2f), false, throwPos);
}
else
{
ac.HoldItem(deltaTime, item, handlePos, new Vector2(throwPos, 0.0f), aimPos, false, 0.0f);
}
}
else
{
//Vector2 diff = Vector2.Normalize(picker.CursorPosition - ac.RefLimb.Position);
//diff.X = diff.X * ac.Dir;
throwPos -= deltaTime*15.0f;
//angl = -hitPos * 2.0f;
// System.Diagnostics.Debug.WriteLine("<1.0f "+hitPos);
ac.HoldItem(deltaTime, item, handlePos, new Vector2(0.6f, 0.0f), new Vector2(-0.3f, 0.2f), false, throwPos);
//}
//else
//{
// System.Diagnostics.Debug.WriteLine(">1.0f " + hitPos);
// ac.HoldItem(deltaTime, item, handlePos, new Vector2(0.5f, 0.2f), new Vector2(1.0f, 0.2f), false, 0.0f);
//}
if (throwPos < -0.0)
{
Vector2 throwVector = picker.CursorWorldPosition - picker.WorldPosition;
throwVector = Vector2.Normalize(throwVector);
item.Drop();
item.body.ApplyLinearImpulse(throwVector * throwForce * item.body.Mass * 3.0f);
ac.GetLimb(LimbType.Head).body.ApplyLinearImpulse(throwVector*10.0f);
ac.GetLimb(LimbType.Torso).body.ApplyLinearImpulse(throwVector * 10.0f);
Limb rightHand = ac.GetLimb(LimbType.RightHand);
item.body.AngularVelocity = rightHand.body.AngularVelocity;
throwing = false;
}
}
}
}
}
@@ -0,0 +1,811 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Xml.Linq;
using Lidgren.Network;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Barotrauma.Networking;
using System.IO;
namespace Barotrauma.Items.Components
{
interface IDrawableComponent
{
void Draw(SpriteBatch spriteBatch, bool editing);
}
class ItemSound
{
public readonly Sound Sound;
public readonly ActionType Type;
public string VolumeProperty;
public float VolumeMultiplier;
public readonly float Range;
public readonly bool Loop;
public ItemSound(Sound sound, ActionType type, float range, bool loop = false)
{
this.Sound = sound;
this.Type = type;
this.Range = range;
this.Loop = loop;
}
}
/// <summary>
/// The base class for components holding the different functionalities of the item
/// </summary>
class ItemComponent : IPropertyObject
{
protected Item item;
protected string name;
private bool isActive;
protected bool characterUsable;
protected bool canBePicked;
protected bool canBeSelected;
public bool WasUsed;
public readonly Dictionary<ActionType, List<StatusEffect>> statusEffectLists;
public List<RelatedItem> requiredItems;
public List<Skill> requiredSkills;
private Dictionary<ActionType,List<ItemSound>> sounds;
private GUIFrame guiFrame;
public ItemComponent Parent;
protected const float CorrectionDelay = 1.0f;
protected CoroutineHandle delayedCorrectionCoroutine;
protected float correctionTimer;
private string msg;
[HasDefaultValue(0.0f, false)]
public float PickingTime
{
get;
private set;
}
public readonly Dictionary<string, ObjectProperty> properties;
public Dictionary<string, ObjectProperty> ObjectProperties
{
get { return properties; }
}
public virtual bool IsActive
{
get { return isActive; }
set
{
if (!value && isActive)
{
StopSounds(ActionType.OnActive);
}
isActive = value;
}
}
private bool drawable = true;
public bool Drawable
{
get { return drawable; }
set
{
if (value == drawable) return;
if (!(this is IDrawableComponent))
{
DebugConsole.ThrowError("Couldn't make \""+this+"\" drawable (the component doesn't implement the IDrawableComponent interface)");
return;
}
drawable = value;
if (drawable)
{
if (!item.drawableComponents.Contains((IDrawableComponent)this))
item.drawableComponents.Add((IDrawableComponent)this);
}
else
{
item.drawableComponents.Remove((IDrawableComponent)this);
}
}
}
[HasDefaultValue(false, false)]
public bool CanBePicked
{
get { return canBePicked; }
set { canBePicked = value; }
}
[HasDefaultValue(false, false)]
public bool DrawHudWhenEquipped
{
get;
private set;
}
[HasDefaultValue(false, false)]
public bool CanBeSelected
{
get { return canBeSelected; }
set { canBeSelected = value; }
}
public InputType PickKey
{
get;
private set;
}
public InputType SelectKey
{
get;
private set;
}
[HasDefaultValue(false, false)]
public bool DeleteOnUse
{
get;
set;
}
public Item Item
{
get { return item; }
}
public string Name
{
get { return name; }
}
protected GUIFrame GuiFrame
{
get
{
if (guiFrame==null)
{
DebugConsole.ThrowError("Error: the component "+name+" in "+item.Name+" doesn't have a GuiFrame component");
guiFrame = new GUIFrame(new Rectangle(0, 0, 100, 100), Color.Black);
}
return guiFrame;
}
}
[HasDefaultValue("", false)]
public string Msg
{
get { return msg; }
set { msg = value; }
}
public ItemComponent(Item item, XElement element)
{
this.item = item;
properties = ObjectProperty.GetProperties(this);
//canBePicked = ToolBox.GetAttributeBool(element, "canbepicked", false);
//canBeSelected = ToolBox.GetAttributeBool(element, "canbeselected", false);
//msg = ToolBox.GetAttributeString(element, "msg", "");
requiredItems = new List<RelatedItem>();
requiredSkills = new List<Skill>();
sounds = new Dictionary<ActionType, List<ItemSound>>();
SelectKey = InputType.Select;
try
{
string selectKeyStr = ToolBox.GetAttributeString(element, "selectkey", "Select");
selectKeyStr = ToolBox.ConvertInputType(selectKeyStr);
SelectKey = (InputType)Enum.Parse(typeof(InputType), selectKeyStr, true);
}
catch (Exception e)
{
DebugConsole.ThrowError("Invalid select key in " + element + "!", e);
}
PickKey = InputType.Select;
try
{
string pickKeyStr = ToolBox.GetAttributeString(element, "selectkey", "Select");
pickKeyStr = ToolBox.ConvertInputType(pickKeyStr);
PickKey = (InputType)Enum.Parse(typeof(InputType),pickKeyStr, true);
}
catch (Exception e)
{
DebugConsole.ThrowError("Invalid pick key in " + element + "!", e);
}
properties = ObjectProperty.InitProperties(this, element);
foreach (XElement subElement in element.Elements())
{
switch (subElement.Name.ToString().ToLowerInvariant())
{
case "requireditem":
case "requireditems":
RelatedItem ri = RelatedItem.Load(subElement);
if (ri != null) requiredItems.Add(ri);
break;
case "requiredskill":
case "requiredskills":
string skillName = ToolBox.GetAttributeString(subElement, "name", "");
requiredSkills.Add(new Skill(skillName, ToolBox.GetAttributeInt(subElement, "level", 0)));
break;
case "statuseffect":
var statusEffect = StatusEffect.Load(subElement);
if (statusEffectLists == null) statusEffectLists = new Dictionary<ActionType, List<StatusEffect>>();
List<StatusEffect> effectList;
if (!statusEffectLists.TryGetValue(statusEffect.type, out effectList))
{
effectList = new List<StatusEffect>();
statusEffectLists.Add(statusEffect.type, effectList);
}
effectList.Add(statusEffect);
break;
case "guiframe":
string rectStr = ToolBox.GetAttributeString(subElement, "rect", "0.0,0.0,0.5,0.5");
string[] components = rectStr.Split(',');
if (components.Length < 4) continue;
Vector4 rect = ToolBox.GetAttributeVector4(subElement, "rect", Vector4.One);
if (components[0].Contains(".")) rect.X *= GameMain.GraphicsWidth;
if (components[1].Contains(".")) rect.Y *= GameMain.GraphicsHeight;
if (components[2].Contains(".")) rect.Z *= GameMain.GraphicsWidth;
if (components[3].Contains(".")) rect.W *= GameMain.GraphicsHeight;
string style = ToolBox.GetAttributeString(subElement, "style", "");
Vector4 color = ToolBox.GetAttributeVector4(subElement, "color", Vector4.One);
Alignment alignment = Alignment.Center;
try
{
alignment = (Alignment)Enum.Parse(typeof(Alignment),
ToolBox.GetAttributeString(subElement, "alignment", "Center"), true);
}
catch
{
DebugConsole.ThrowError("Error in " + element + "! \"" + element.Attribute("type").Value + "\" is not a valid alignment");
}
guiFrame = new GUIFrame(
new Rectangle((int)rect.X, (int)rect.Y, (int)rect.Z, (int)rect.W),
new Color(color.X, color.Y, color.Z) * color.W,
alignment, style);
break;
case "sound":
string filePath = ToolBox.GetAttributeString(subElement, "file", "");
if (filePath == "") filePath = ToolBox.GetAttributeString(subElement, "sound", "");
if (filePath == "")
{
DebugConsole.ThrowError("Error when instantiating item \""+item.Name+"\" - sound with no file path set");
continue;
}
if (!filePath.Contains("/") && !filePath.Contains("\\") && !filePath.Contains(Path.DirectorySeparatorChar))
{
filePath = Path.Combine(Path.GetDirectoryName(item.Prefab.ConfigFile), filePath);
}
ActionType type;
try
{
type = (ActionType)Enum.Parse(typeof(ActionType), ToolBox.GetAttributeString(subElement, "type", ""), true);
}
catch (Exception e)
{
DebugConsole.ThrowError("Invalid sound type in "+subElement+"!", e);
break;
}
Sound sound = Sound.Load(filePath);
float range = ToolBox.GetAttributeFloat(subElement, "range", 800.0f);
bool loop = ToolBox.GetAttributeBool(subElement, "loop", false);
ItemSound itemSound = new ItemSound(sound, type, range, loop);
itemSound.VolumeProperty = ToolBox.GetAttributeString(subElement, "volume", "");
itemSound.VolumeMultiplier = ToolBox.GetAttributeFloat(subElement, "volumemultiplier", 1.0f);
List<ItemSound> soundList = null;
if (!sounds.TryGetValue(itemSound.Type, out soundList))
{
soundList = new List<ItemSound>();
sounds.Add(itemSound.Type, soundList);
}
soundList.Add(itemSound);
break;
default:
ItemComponent ic = Load(subElement, item, item.ConfigFile, false);
if (ic == null) break;
ic.Parent = this;
item.components.Add(ic);
break;
}
}
}
private ItemSound loopingSound;
private int loopingSoundIndex;
public void PlaySound(ActionType type, Vector2 position)
{
if (loopingSound != null)
{
loopingSoundIndex = loopingSound.Sound.Loop(loopingSoundIndex, GetSoundVolume(loopingSound), position, loopingSound.Range);
return;
}
List<ItemSound> matchingSounds;
if (!sounds.TryGetValue(type, out matchingSounds)) return;
ItemSound itemSound = null;
if (!Sounds.SoundManager.IsPlaying(loopingSoundIndex))
{
int index = Rand.Int(matchingSounds.Count);
itemSound = matchingSounds[index];
}
if (itemSound == null) return;
if (itemSound.Loop)
{
loopingSound = itemSound;
loopingSoundIndex = loopingSound.Sound.Loop(loopingSoundIndex, GetSoundVolume(loopingSound), position, loopingSound.Range);
}
else
{
float volume = GetSoundVolume(itemSound);
if (volume == 0.0f) return;
itemSound.Sound.Play(volume, itemSound.Range, position);
}
}
public void StopSounds(ActionType type)
{
if (loopingSoundIndex <= 0) return;
if (loopingSound == null) return;
if (loopingSound.Type != type) return;
if (Sounds.SoundManager.IsPlaying(loopingSoundIndex))
{
Sounds.SoundManager.Stop(loopingSoundIndex);
loopingSound = null;
loopingSoundIndex = -1;
}
}
private float GetSoundVolume(ItemSound sound)
{
if (sound == null) return 0.0f;
if (sound.VolumeProperty == "") return 1.0f;
ObjectProperty op = null;
if (properties.TryGetValue(sound.VolumeProperty.ToLowerInvariant(), out op))
{
float newVolume = 0.0f;
try
{
newVolume = (float)op.GetValue();
}
catch
{
return 0.0f;
}
newVolume *= sound.VolumeMultiplier;
return MathHelper.Clamp(newVolume, 0.0f, 1.0f);
}
return 0.0f;
}
public virtual void Move(Vector2 amount) { }
/// <summary>a Character has picked the item</summary>
public virtual bool Pick(Character picker)
{
return false;
}
public virtual bool Select(Character character)
{
return CanBeSelected;
}
/// <summary>a Character has dropped the item</summary>
public virtual void Drop(Character dropper) { }
//public virtual void Draw(SpriteBatch spriteBatch, bool editing = false)
//{
// item.drawableComponents = Array.FindAll(item.drawableComponents, i => i != this);
//}
public virtual void DrawHUD(SpriteBatch spriteBatch, Character character) { }
public virtual void AddToGUIUpdateList() { }
public virtual void UpdateHUD(Character character) { }
/// <returns>true if the operation was completed</returns>
public virtual bool AIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective)
{
return false;
}
//called when isActive is true and condition > 0.0f
public virtual void Update(float deltaTime, Camera cam) { }
//called when isActive is true and condition == 0.0f
public virtual void UpdateBroken(float deltaTime, Camera cam)
{
StopSounds(ActionType.OnActive);
}
//called when the item is equipped and left mouse button is pressed
//returns true if the item was used succesfully (not out of ammo, reloading, etc)
public virtual bool Use(float deltaTime, Character character = null)
{
return false;
}
//called when the item is equipped and right mouse button is pressed
public virtual void SecondaryUse(float deltaTime, Character character = null) { }
//called when the item is placed in a "limbslot"
public virtual void Equip(Character character) { }
//called then the item is dropped or dragged out of a "limbslot"
public virtual void Unequip(Character character) { }
public virtual void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power = 0.0f)
{
switch (connection.Name)
{
case "activate":
case "use":
item.Use(1.0f);
break;
case "toggle":
IsActive = !isActive;
break;
case "set_active":
case "set_state":
IsActive = signal != "0";
break;
}
}
public virtual bool Combine(Item item)
{
return false;
}
public void Remove()
{
if (loopingSound != null)
{
Sounds.SoundManager.Stop(loopingSoundIndex);
}
if (delayedCorrectionCoroutine != null)
{
CoroutineManager.StopCoroutines(delayedCorrectionCoroutine);
delayedCorrectionCoroutine = null;
}
RemoveComponentSpecific();
}
/// <summary>
/// Remove the component so that it doesn't appear to exist in the game world (stop sounds, remove bodies etc)
/// but don't reset anything that's required for cloning the item
/// </summary>
public void ShallowRemove()
{
if (loopingSound != null)
{
Sounds.SoundManager.Stop(loopingSoundIndex);
}
ShallowRemoveComponentSpecific();
}
protected virtual void ShallowRemoveComponentSpecific()
{
RemoveComponentSpecific();
}
protected virtual void RemoveComponentSpecific()
{ }
public bool HasRequiredSkills(Character character)
{
Skill temp;
return HasRequiredSkills(character, out temp);
}
public bool HasRequiredSkills(Character character, out Skill insufficientSkill)
{
foreach (Skill skill in requiredSkills)
{
int characterLevel = character.GetSkillLevel(skill.Name);
if (characterLevel < skill.Level)
{
insufficientSkill = skill;
return false;
}
}
insufficientSkill = null;
return true;
}
/// <summary>
/// Returns 0.0f-1.0f based on how well the Character can use the itemcomponent
/// </summary>
/// <returns>0.5f if all the skills meet the skill requirements exactly, 1.0f if they're way above and 0.0f if way less</returns>
protected float DegreeOfSuccess(Character character)
{
if (requiredSkills.Count == 0) return 100.0f;
float[] skillSuccess = new float[requiredSkills.Count];
for (int i = 0; i < requiredSkills.Count; i++ )
{
int characterLevel = character.GetSkillLevel(requiredSkills[i].Name);
skillSuccess[i] = (characterLevel - requiredSkills[i].Level);
}
float average = skillSuccess.Average();
return (average+100.0f)/2.0f;
}
public virtual void FlipX() { }
public bool HasRequiredContainedItems(bool addMessage)
{
List<RelatedItem> requiredContained = requiredItems.FindAll(ri=> ri.Type == RelatedItem.RelationType.Contained);
if (!requiredContained.Any()) return true;
Item[] containedItems = item.ContainedItems;
if (containedItems == null || !containedItems.Any()) return false;
foreach (RelatedItem ri in requiredContained)
{
Item containedItem = Array.Find(containedItems, x => x != null && x.Condition > 0.0f && ri.MatchesItem(x));
if (containedItem == null)
{
if (addMessage && !string.IsNullOrEmpty(ri.Msg)) GUI.AddMessage(ri.Msg, Color.Red);
return false;
}
}
return true;
}
public bool HasRequiredItems(Character character, bool addMessage)
{
if (!requiredItems.Any()) return true;
foreach (RelatedItem ri in requiredItems)
{
if (!ri.Type.HasFlag(RelatedItem.RelationType.Equipped) && !ri.Type.HasFlag(RelatedItem.RelationType.Picked)) continue;
bool hasItem = false;
if (ri.Type.HasFlag(RelatedItem.RelationType.Equipped))
{
if (character.SelectedItems.FirstOrDefault(it => it != null && it.Condition > 0.0f && ri.MatchesItem(it)) != null) hasItem = true;
}
if (!hasItem && ri.Type.HasFlag(RelatedItem.RelationType.Picked))
{
if (character.Inventory.Items.FirstOrDefault(x => x!=null && x.Condition>0.0f && ri.MatchesItem(x))!=null) hasItem = true;
}
if (!hasItem)
{
if (addMessage && !string.IsNullOrEmpty(ri.Msg)) GUI.AddMessage(ri.Msg, Color.Red);
return false;
}
}
return true;
}
public void ApplyStatusEffects(ActionType type, float deltaTime, Character character = null)
{
if (statusEffectLists == null) return;
List<StatusEffect> statusEffects;
if (!statusEffectLists.TryGetValue(type, out statusEffects)) return;
foreach (StatusEffect effect in statusEffects)
{
item.ApplyStatusEffect(effect, type, deltaTime, character);
}
}
public void ApplyStatusEffects(ActionType type, List<IPropertyObject> targets, float deltaTime)
{
if (statusEffectLists == null) return;
List<StatusEffect> statusEffects;
if (!statusEffectLists.TryGetValue(type, out statusEffects)) return;
foreach (StatusEffect effect in statusEffects)
{
effect.Apply(type, deltaTime, item, targets);
}
}
//Starts a coroutine that will read the correct state of the component from the NetBuffer when correctionTimer reaches zero.
protected void StartDelayedCorrection(ServerNetObject type, NetBuffer buffer, float sendingTime)
{
if (delayedCorrectionCoroutine != null) CoroutineManager.StopCoroutines(delayedCorrectionCoroutine);
delayedCorrectionCoroutine = CoroutineManager.StartCoroutine(DoDelayedCorrection(type, buffer, sendingTime));
}
private IEnumerable<object> DoDelayedCorrection(ServerNetObject type, NetBuffer buffer, float sendingTime)
{
while (correctionTimer > 0.0f)
{
correctionTimer -= CoroutineManager.DeltaTime;
yield return CoroutineStatus.Running;
}
((IServerSerializable)this).ClientRead(type, buffer, sendingTime);
correctionTimer = 0.0f;
delayedCorrectionCoroutine = null;
yield return CoroutineStatus.Success;
}
public virtual XElement Save(XElement parentElement)
{
XElement componentElement = new XElement(name);
foreach (RelatedItem ri in requiredItems)
{
XElement newElement = new XElement("requireditem");
ri.Save(newElement);
componentElement.Add(newElement);
}
ObjectProperty.SaveProperties(this, componentElement);
parentElement.Add(componentElement);
return componentElement;
}
public virtual void Load(XElement componentElement)
{
if (componentElement == null) return;
foreach (XAttribute attribute in componentElement.Attributes())
{
ObjectProperty property = null;
if (!properties.TryGetValue(attribute.Name.ToString().ToLowerInvariant(), out property)) continue;
property.TrySetValue(attribute.Value);
}
List<RelatedItem> prevRequiredItems = new List<RelatedItem>(requiredItems);
requiredItems.Clear();
foreach (XElement subElement in componentElement.Elements())
{
switch (subElement.Name.ToString().ToLowerInvariant())
{
case "requireditem":
RelatedItem newRequiredItem = RelatedItem.Load(subElement);
if (newRequiredItem == null) continue;
var prevRequiredItem = prevRequiredItems.Find(ri => ri.JoinedNames == newRequiredItem.JoinedNames);
if (prevRequiredItem!=null)
{
newRequiredItem.statusEffects = prevRequiredItem.statusEffects;
newRequiredItem.Msg = prevRequiredItem.Msg;
}
requiredItems.Add(newRequiredItem);
break;
}
}
}
public virtual void OnMapLoaded() { }
public static ItemComponent Load(XElement element, Item item, string file, bool errorMessages = true)
{
Type t;
string type = element.Name.ToString().ToLowerInvariant();
try
{
// Get the type of a specified class.
t = Type.GetType("Barotrauma.Items.Components." + type + "", false, true);
if (t == null)
{
if (errorMessages) DebugConsole.ThrowError("Could not find the component \"" + type + "\" (" + file + ")");
return null;
}
}
catch (Exception e)
{
if (errorMessages) DebugConsole.ThrowError("Could not find the component \"" + type + "\" (" + file + ")", e);
return null;
}
ConstructorInfo constructor;
try
{
if (t != typeof(ItemComponent) && !t.IsSubclassOf(typeof(ItemComponent))) return null;
constructor = t.GetConstructor(new Type[] { typeof(Item), typeof(XElement) });
if (constructor == null)
{
DebugConsole.ThrowError("Could not find the constructor of the component \"" + type + "\" (" + file + ")");
return null;
}
}
catch (Exception e)
{
DebugConsole.ThrowError("Could not find the constructor of the component \"" + type + "\" (" + file + ")", e);
return null;
}
object[] lobject = new object[] { item, element };
object component = constructor.Invoke(lobject);
ItemComponent ic = (ItemComponent)component;
ic.name = element.Name.ToString();
return ic;
}
}
}
@@ -0,0 +1,320 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using FarseerPhysics;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace Barotrauma.Items.Components
{
class ItemContainer : ItemComponent, IDrawableComponent
{
public const int MaxInventoryCount = 4;
List<RelatedItem> containableItems;
public ItemInventory Inventory;
private List<Pair<Item, StatusEffect>> itemsWithStatusEffects;
//how many items can be contained
[HasDefaultValue(5, false)]
public int Capacity
{
get { return capacity; }
set { capacity = Math.Max(value, 1); }
}
private int capacity;
[HasDefaultValue(true, false)]
public bool HideItems
{
get { return hideItems; }
set
{
hideItems = value;
Drawable = !hideItems;
}
}
private bool hideItems;
[HasDefaultValue(false, false)]
public bool DrawInventory
{
get { return drawInventory; }
set { drawInventory = value; }
}
private bool drawInventory;
//the position of the first item in the container
[HasDefaultValue("0.0,0.0", false)]
public string ItemPos
{
get { return ToolBox.Vector2ToString(itemPos); }
set { itemPos = ToolBox.ParseToVector2(value); }
}
private Vector2 itemPos;
//item[i].Pos = itemPos + itemInterval*i
[HasDefaultValue("0.0,0.0", false)]
public string ItemInterval
{
get { return ToolBox.Vector2ToString(itemInterval); }
set { itemInterval = ToolBox.ParseToVector2(value); }
}
private Vector2 itemInterval;
[HasDefaultValue(0.0f, false)]
public float ItemRotation
{
get { return MathHelper.ToDegrees(itemRotation); }
set { itemRotation = MathHelper.ToRadians(value); }
}
private float itemRotation;
[HasDefaultValue("0.5,0.9", false)]
public string HudPos
{
get { return ToolBox.Vector2ToString(hudPos); }
set
{
hudPos = ToolBox.ParseToVector2(value);
//inventory.CenterPos = hudPos;
}
}
private Vector2 hudPos;
[HasDefaultValue(5, false)]
public int SlotsPerRow
{
get { return slotsPerRow; }
set { slotsPerRow = value; }
}
private int slotsPerRow;
public List<RelatedItem> ContainableItems
{
get { return containableItems; }
}
public ItemContainer(Item item, XElement element)
: base (item, element)
{
Inventory = new ItemInventory(item, this, capacity, hudPos, slotsPerRow);
containableItems = new List<RelatedItem>();
foreach (XElement subElement in element.Elements())
{
switch (subElement.Name.ToString().ToLowerInvariant())
{
case "containable":
RelatedItem containable = RelatedItem.Load(subElement);
if (containable == null) continue;
containableItems.Add(containable);
break;
}
}
itemsWithStatusEffects = new List<Pair<Item, StatusEffect>>();
}
public void OnItemContained(Item containedItem)
{
item.SetContainedItemPositions();
RelatedItem ri = containableItems.Find(x => x.MatchesItem(containedItem));
if (ri != null)
{
foreach (StatusEffect effect in ri.statusEffects)
{
itemsWithStatusEffects.Add(Pair<Item, StatusEffect>.Create(containedItem, effect));
}
}
//no need to Update() if this item has no statuseffects and no physics body
IsActive = itemsWithStatusEffects.Count > 0 || containedItem.body != null;
}
public void OnItemRemoved(Item item)
{
itemsWithStatusEffects.RemoveAll(i => i.First == item);
//deactivate if the inventory is empty
IsActive = itemsWithStatusEffects.Count > 0 || item.body != null;
}
public bool CanBeContained(Item item)
{
if (containableItems.Count == 0) return true;
return (containableItems.Find(x => x.MatchesItem(item)) != null);
}
public override void Update(float deltaTime, Camera cam)
{
if (item.body != null &&
item.body.Enabled &&
item.body.FarseerBody.Awake)
{
item.SetContainedItemPositions();
}
foreach (Pair<Item, StatusEffect> itemAndEffect in itemsWithStatusEffects)
{
Item contained = itemAndEffect.First;
if (contained.Condition < 0.0f) continue;
StatusEffect effect = itemAndEffect.Second;
if (effect.Targets.HasFlag(StatusEffect.TargetType.This))
effect.Apply(ActionType.OnContaining, deltaTime, item, item.AllPropertyObjects);
if (effect.Targets.HasFlag(StatusEffect.TargetType.Contained))
effect.Apply(ActionType.OnContaining, deltaTime, item, contained.AllPropertyObjects);
}
}
public void Draw(SpriteBatch spriteBatch, bool editing = false)
{
if (hideItems || (item.body != null && !item.body.Enabled)) return;
Vector2 transformedItemPos = itemPos;
Vector2 transformedItemInterval = itemInterval;
float currentRotation = itemRotation;
if (item.body == null)
{
transformedItemPos = new Vector2(item.Rect.X, item.Rect.Y);
if (item.Submarine != null) transformedItemPos += item.Submarine.DrawPosition;
transformedItemPos = transformedItemPos + itemPos;
}
else
{
//item.body.Enabled = true;
Matrix transform = Matrix.CreateRotationZ(item.body.Rotation);
if (item.body.Dir==-1.0f)
{
transformedItemPos.X = -transformedItemPos.X;
transformedItemInterval.X = -transformedItemInterval.X;
}
transformedItemPos = Vector2.Transform(transformedItemPos, transform);
transformedItemInterval = Vector2.Transform(transformedItemInterval, transform);
transformedItemPos += item.DrawPosition;
currentRotation += item.body.Rotation;
}
foreach (Item containedItem in Inventory.Items)
{
if (containedItem == null) continue;
containedItem.Sprite.Draw(
spriteBatch,
new Vector2(transformedItemPos.X, -transformedItemPos.Y),
-currentRotation,
1.0f,
(item.body != null && item.body.Dir == -1) ? SpriteEffects.FlipHorizontally : SpriteEffects.None);
transformedItemPos += transformedItemInterval;
}
}
public override void UpdateHUD(Character character)
{
Inventory.Update((float)Timing.Step);
}
public override void DrawHUD(SpriteBatch spriteBatch, Character character)
{
Inventory.Draw(spriteBatch);
}
public override bool Pick(Character picker)
{
return (picker != null);
}
public override bool Combine(Item item)
{
if (!containableItems.Any(x => x.MatchesItem(item))) return false;
if (Inventory.TryPutItem(item))
{
IsActive = true;
if (hideItems && item.body != null) item.body.Enabled = false;
return true;
}
return false;
}
public override void OnMapLoaded()
{
if (itemIds == null) return;
for (ushort i = 0; i < itemIds.Length; i++)
{
Item item = MapEntity.FindEntityByID(itemIds[i]) as Item;
if (item == null) continue;
Inventory.TryPutItem(item, i, false, false);
}
itemIds = null;
}
protected override void ShallowRemoveComponentSpecific()
{
}
protected override void RemoveComponentSpecific()
{
foreach (Item item in Inventory.Items)
{
if (item == null) continue;
item.Remove();
}
}
public override void Load(XElement componentElement)
{
base.Load(componentElement);
string containedString = ToolBox.GetAttributeString(componentElement, "contained", "");
string[] itemIdStrings = containedString.Split(',');
itemIds = new ushort[itemIdStrings.Length];
for (int i = 0; i < itemIdStrings.Length; i++)
{
ushort id = 0;
if (!ushort.TryParse(itemIdStrings[i], out id)) continue;
itemIds[i] = id;
}
}
ushort[] itemIds;
public override XElement Save(XElement parentElement)
{
XElement componentElement = base.Save(parentElement);
string[] itemIdStrings = new string[Inventory.Items.Length];
for (int i = 0; i < Inventory.Items.Length; i++)
{
itemIdStrings[i] = (Inventory.Items[i]==null) ? "0" : Inventory.Items[i].ID.ToString();
}
componentElement.Add(new XAttribute("contained", string.Join(",",itemIdStrings)));
return componentElement;
}
}
}
@@ -0,0 +1,87 @@
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System.Xml.Linq;
namespace Barotrauma.Items.Components
{
class ItemLabel : ItemComponent, IDrawableComponent
{
private GUITextBlock textBlock;
[HasDefaultValue("", true), Editable(100)]
public string Text
{
get { return textBlock.Text.Replace("\n", ""); }
set
{
if (value == TextBlock.Text || item.Rect.Width < 5) return;
if (textBlock.Rect.Width != item.Rect.Width || textBlock.Rect.Height != item.Rect.Height)
{
textBlock = null;
}
TextBlock.Text = value;
}
}
private Color textColor;
[Editable, HasDefaultValue("0.0,0.0,0.0,1.0", true)]
public string TextColor
{
get { return ToolBox.Vector4ToString(textColor.ToVector4()); }
set
{
textColor = new Color(ToolBox.ParseToVector4(value));
if (textBlock != null) textBlock.TextColor = textColor;
}
}
[Editable, HasDefaultValue(1.0f, true)]
public float TextScale
{
get { return textBlock == null ? 1.0f : textBlock.TextScale; }
set
{
if (textBlock != null) textBlock.TextScale = MathHelper.Clamp(value, 0.1f, 10.0f);
}
}
private GUITextBlock TextBlock
{
get
{
if (textBlock == null)
{
textBlock = new GUITextBlock(new Rectangle(item.Rect.X,-item.Rect.Y,item.Rect.Width, item.Rect.Height), "",
Color.Transparent, textColor,
Alignment.TopLeft, Alignment.Center,
null, null, true);
textBlock.Font = GUI.SmallFont;
textBlock.Padding = new Vector4(5.0f, 5.0f, 5.0f, 5.0f);
textBlock.TextDepth = item.Sprite.Depth - 0.0001f;
textBlock.TextScale = TextScale;
}
return textBlock;
}
}
public override void Move(Vector2 amount)
{
textBlock.Rect = new Rectangle(item.Rect.X, -item.Rect.Y, item.Rect.Width, item.Rect.Height);
}
public ItemLabel(Item item, XElement element)
: base(item, element)
{
}
public void Draw(SpriteBatch spriteBatch, bool editing = false)
{
var drawPos = new Vector2(
item.DrawPosition.X - item.Rect.Width/2.0f,
-(item.DrawPosition.Y + item.Rect.Height/2.0f));
textBlock.Draw(spriteBatch, drawPos - textBlock.Rect.Location.ToVector2());
}
}
}
@@ -0,0 +1,23 @@
using System.Xml.Linq;
namespace Barotrauma.Items.Components
{
class Ladder : ItemComponent
{
public Ladder(Item item, XElement element)
: base(item, element)
{
}
public override bool Select(Character character)
{
if (character == null) return false;
character.AnimController.Anim = AnimController.Animation.Climbing;
//picker.SelectedConstruction = item;
return true;
}
}
}
@@ -0,0 +1,289 @@
using System;
using System.Collections.Generic;
using System.Xml.Linq;
using FarseerPhysics;
using FarseerPhysics.Dynamics.Joints;
using Microsoft.Xna.Framework;
namespace Barotrauma.Items.Components
{
struct LimbPos
{
public LimbType limbType;
public Vector2 position;
public LimbPos(LimbType limbType, Vector2 position)
{
this.limbType = limbType;
this.position = position;
}
}
class Controller : ItemComponent
{
//where the limbs of the user should be positioned when using the controller
private List<LimbPos> limbPositions;
private Direction dir;
//the position where the user walks to when using the controller
//(relative to the position of the item)
private Vector2 userPos;
private Camera cam;
private Character character;
public Vector2 UserPos
{
get { return userPos; }
set { userPos = value; }
}
public Controller(Item item, XElement element)
: base(item, element)
{
limbPositions = new List<LimbPos>();
userPos = ToolBox.GetAttributeVector2(element, "UserPos", Vector2.Zero);
Enum.TryParse<Direction>(ToolBox.GetAttributeString(element, "direction", "None"), out dir);
foreach (XElement el in element.Elements())
{
if (el.Name != "limbposition") continue;
LimbPos lp = new LimbPos();
try
{
lp.limbType = (LimbType)Enum.Parse(typeof(LimbType), el.Attribute("limb").Value, true);
}
catch (Exception e)
{
DebugConsole.ThrowError("Error in " + element + ": " + e.Message, e);
}
lp.position = ToolBox.GetAttributeVector2(el, "position", Vector2.Zero);
limbPositions.Add(lp);
}
IsActive = true;
}
public override void Update(float deltaTime, Camera cam)
{
this.cam = cam;
if (character == null
|| character.IsDead
|| character.Stun > 0.0f
|| character.SelectedConstruction != item
|| Vector2.Distance(character.Position, item.Position) > item.PickDistance * 2.0f)
{
if (character != null)
{
CancelUsing(character);
character = null;
}
IsActive = false;
return;
}
character.AnimController.Anim = AnimController.Animation.UsingConstruction;
if (userPos != Vector2.Zero)
{
Vector2 diff = (item.WorldPosition + userPos) - character.WorldPosition;
if (character.AnimController.InWater)
{
if (diff.Length() > 30.0f)
{
character.AnimController.TargetMovement = Vector2.Clamp(diff*0.01f, -Vector2.One, Vector2.One);
character.AnimController.TargetDir = diff.X > 0.0f ? Direction.Right : Direction.Left;
}
else
{
character.AnimController.TargetMovement = Vector2.Zero;
}
}
else
{
diff.Y = 0.0f;
if (diff != Vector2.Zero && diff.Length() > 10.0f)
{
character.AnimController.TargetMovement = Vector2.Normalize(diff);
character.AnimController.TargetDir = diff.X > 0.0f ? Direction.Right : Direction.Left;
return;
}
character.AnimController.TargetMovement = Vector2.Zero;
}
}
ApplyStatusEffects(ActionType.OnActive, deltaTime, character);
if (limbPositions.Count == 0) return;
character.AnimController.Anim = AnimController.Animation.UsingConstruction;
character.AnimController.ResetPullJoints();
if (dir != 0) character.AnimController.TargetDir = dir;
foreach (LimbPos lb in limbPositions)
{
Limb limb = character.AnimController.GetLimb(lb.limbType);
if (limb == null) continue;
limb.Disabled = true;
if (limb.pullJoint == null) continue;
Vector2 position = ConvertUnits.ToSimUnits(lb.position + new Vector2(item.Rect.X, item.Rect.Y));
limb.pullJoint.Enabled = true;
limb.pullJoint.WorldAnchorB = position;
}
}
public override bool Use(float deltaTime, Character activator = null)
{
if (character == null || activator != character || character.SelectedConstruction != item)
{
character = null;
return false;
}
item.SendSignal(0, "1", "trigger_out", character);
ApplyStatusEffects(ActionType.OnUse, 1.0f, activator);
return true;
}
public override void SecondaryUse(float deltaTime, Character character = null)
{
if (this.character == null || this.character != character || this.character.SelectedConstruction != item)
{
character = null;
return;
}
Entity focusTarget = null;
if (character == null) return;
foreach (Connection c in item.Connections)
{
if (c.Name != "position_out") continue;
foreach (Connection c2 in c.Recipients)
{
if (c2 == null || c2.Item == null || !c2.Item.Prefab.FocusOnSelected) continue;
focusTarget = c2.Item;
break;
}
}
if (focusTarget == null)
{
item.SendSignal(0, ToolBox.Vector2ToString(character.CursorWorldPosition), "position_out", character);
return;
}
character.ViewTarget = focusTarget;
if (character == Character.Controlled && cam != null)
{
Lights.LightManager.ViewTarget = focusTarget;
cam.TargetPos = focusTarget.WorldPosition;
cam.OffsetAmount = MathHelper.Lerp(cam.OffsetAmount, (focusTarget as Item).Prefab.OffsetOnSelected, deltaTime*10.0f);
}
if (!character.IsRemotePlayer || character.ViewTarget == focusTarget)
{
item.SendSignal(0, ToolBox.Vector2ToString(character.CursorWorldPosition), "position_out", character);
}
}
public override bool Pick(Character picker)
{
item.SendSignal(0, "1", "signal_out", picker);
PlaySound(ActionType.OnUse, item.WorldPosition);
return true;
}
private void CancelUsing(Character character)
{
foreach (LimbPos lb in limbPositions)
{
Limb limb = character.AnimController.GetLimb(lb.limbType);
if (limb == null) continue;
limb.Disabled = false;
limb.pullJoint.Enabled = false;
}
if (character.SelectedConstruction == this.item) character.SelectedConstruction = null;
character.AnimController.Anim = AnimController.Animation.None;
}
public override bool Select(Character activator)
{
if (activator == null) return false;
//someone already using the item
if (character != null)
{
if (character == activator)
{
IsActive = false;
CancelUsing(character);
character = null;
return false;
}
}
else
{
character = activator;
IsActive = true;
}
item.SendSignal(0, "1", "signal_out", character);
return true;
}
public override void FlipX()
{
if (dir != Direction.None)
{
dir = dir == Direction.Left ? Direction.Right : Direction.Left;
}
userPos.X = -UserPos.X;
for (int i = 0; i < limbPositions.Count; i++)
{
float diff = (item.Rect.X + limbPositions[i].position.X) - item.Rect.Center.X;
Vector2 flippedPos =
new Vector2(
item.Rect.Center.X - diff - item.Rect.X,
limbPositions[i].position.Y);
limbPositions[i] = new LimbPos(limbPositions[i].limbType, flippedPos);
}
}
}
}
@@ -0,0 +1,187 @@
using Barotrauma.Networking;
using Lidgren.Network;
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.Items.Components
{
class Deconstructor : Powered, IServerSerializable, IClientSerializable
{
GUIProgressBar progressBar;
GUIButton activateButton;
float progressTimer;
ItemContainer container;
public Deconstructor(Item item, XElement element)
: base(item, element)
{
progressBar = new GUIProgressBar(new Rectangle(0,0,200,20), Color.Green, "", 0.0f, Alignment.BottomCenter, GuiFrame);
activateButton = new GUIButton(new Rectangle(0, 0, 200, 20), "Deconstruct", Alignment.TopCenter, "", GuiFrame);
activateButton.OnClicked = ToggleActive;
}
public override void Update(float deltaTime, Camera cam)
{
if (container == null || container.Inventory.Items.All(i => i == null))
{
SetActive(false);
return;
}
if (voltage < minVoltage) return;
if (powerConsumption == 0.0f) voltage = 1.0f;
progressTimer += deltaTime*voltage;
Voltage -= deltaTime * 10.0f;
var targetItem = container.Inventory.Items.FirstOrDefault(i => i != null);
progressBar.BarSize = Math.Min(progressTimer / targetItem.Prefab.DeconstructTime, 1.0f);
if (progressTimer>targetItem.Prefab.DeconstructTime)
{
var containers = item.GetComponents<ItemContainer>();
if (containers.Count < 2)
{
DebugConsole.ThrowError("Error in Deconstructor.Update: Deconstructors must have two ItemContainer components!");
return;
}
foreach (DeconstructItem deconstructProduct in targetItem.Prefab.DeconstructItems)
{
if (deconstructProduct.RequireFullCondition && targetItem.Condition < 100.0f) continue;
var itemPrefab = MapEntityPrefab.list.FirstOrDefault(ip => ip.Name.ToLowerInvariant() == deconstructProduct.ItemPrefabName.ToLowerInvariant()) as ItemPrefab;
if (itemPrefab == null)
{
DebugConsole.ThrowError("Tried to deconstruct item \"" + targetItem.Name + "\" but couldn't find item prefab \"" + deconstructProduct + "\"!");
continue;
}
//container full, drop the items outside the deconstructor
if (containers[1].Inventory.Items.All(i => i != null))
{
Entity.Spawner.AddToSpawnQueue(itemPrefab, item.Position, item.Submarine);
}
else
{
Entity.Spawner.AddToSpawnQueue(itemPrefab, containers[1].Inventory);
}
}
container.Inventory.RemoveItem(targetItem);
Entity.Spawner.AddToRemoveQueue(targetItem);
if (container.Inventory.Items.Any(i => i != null))
{
progressTimer = 0.0f;
progressBar.BarSize = 0.0f;
}
}
}
public override void DrawHUD(SpriteBatch spriteBatch, Character character)
{
GuiFrame.Draw(spriteBatch);
}
public override void AddToGUIUpdateList()
{
GuiFrame.AddToGUIUpdateList();
}
public override void UpdateHUD(Character character)
{
GuiFrame.Update((float)Timing.Step);
}
private bool ToggleActive(GUIButton button, object obj)
{
SetActive(!IsActive, Character.Controlled);
currPowerConsumption = IsActive ? powerConsumption : 0.0f;
if (GameMain.Server != null)
{
item.CreateServerEvent(this);
}
else if (GameMain.Client != null)
{
item.CreateClientEvent(this);
}
return true;
}
private void SetActive(bool active, Character user = null)
{
container = item.GetComponent<ItemContainer>();
if (container == null)
{
DebugConsole.ThrowError("Error in Deconstructor.Activate: Deconstructors must have two ItemContainer components");
return;
}
if (container.Inventory.Items.All(i => i == null)) active = false;
IsActive = active;
if (user != null)
{
GameServer.Log(user.Name + (IsActive ? " activated " : " deactivated ") + item.Name, ServerLog.MessageType.ItemInteraction);
}
if (!IsActive)
{
progressBar.BarSize = 0.0f;
progressTimer = 0.0f;
activateButton.Text = "Deconstruct";
}
else
{
activateButton.Text = "Cancel";
}
container.Inventory.Locked = IsActive;
}
public void ClientWrite(NetBuffer msg, object[] extraData = null)
{
msg.Write(IsActive);
}
public void ServerRead(ClientNetObject type, NetBuffer msg, Client c)
{
bool active = msg.ReadBoolean();
item.CreateServerEvent(this);
if (item.CanClientAccess(c))
{
SetActive(active, c.Character);
}
}
public void ServerWrite(NetBuffer msg, Client c, object[] extraData = null)
{
msg.Write(IsActive);
}
public void ClientRead(ServerNetObject type, NetBuffer msg, float sendingTime)
{
SetActive(msg.ReadBoolean());
}
}
}
@@ -0,0 +1,148 @@
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Xml.Linq;
namespace Barotrauma.Items.Components
{
class Engine : Powered
{
private float force;
private float targetForce;
private float maxForce;
//[Editable, HasDefaultValue(1.0f, true)]
//public float PowerPerForce
//{
// get { return powerPerForce; }
// set
// {
// powerPerForce = Math.Max(0.0f, value);
// }
//}
[Editable, HasDefaultValue(2000.0f, true)]
public float MaxForce
{
get { return maxForce; }
set
{
maxForce = Math.Max(0.0f, value);
}
}
public float Force
{
get { return force;}
set { force = MathHelper.Clamp(value, -100.0f, 100.0f); }
}
public Engine(Item item, XElement element)
: base(item, element)
{
IsActive = true;
var button = new GUIButton(new Rectangle(160, 50, 30, 30), "-", "", GuiFrame);
button.OnClicked = (GUIButton btn, object obj) =>
{
targetForce -= 1.0f;
return true;
};
button = new GUIButton(new Rectangle(200, 50, 30, 30), "+", "", GuiFrame);
button.OnClicked = (GUIButton btn, object obj) =>
{
targetForce += 1.0f;
return true;
};
}
public float CurrentVolume
{
get { return Math.Abs((force / 100.0f) * (voltage / minVoltage)); }
}
public override void Update(float deltaTime, Camera cam)
{
base.Update(deltaTime, cam);
currPowerConsumption = Math.Abs(targetForce)/100.0f * powerConsumption;
if (powerConsumption == 0.0f) voltage = 1.0f;
Force = MathHelper.Lerp(force, (voltage < minVoltage) ? 0.0f : targetForce, 0.1f);
if (Math.Abs(Force) > 1.0f)
{
Vector2 currForce = new Vector2((force / 100.0f) * maxForce * (voltage / minVoltage), 0.0f);
item.Submarine.ApplyForce(currForce);
if (item.CurrentHull != null)
{
item.CurrentHull.AiTarget.SoundRange = Math.Max(currForce.Length(), item.CurrentHull.AiTarget.SoundRange);
}
for (int i = 0; i < 5; i++)
{
GameMain.ParticleManager.CreateParticle("bubbles", item.WorldPosition - (Vector2.UnitX * item.Rect.Width/2),
-currForce / 5.0f + new Vector2(Rand.Range(-100.0f, 100.0f), Rand.Range(-50f, 50f)),
0.0f, item.CurrentHull);
}
}
voltage = 0.0f;
}
public override void DrawHUD(SpriteBatch spriteBatch, Character character)
{
//isActive = true;
GuiFrame.Draw(spriteBatch);
//int width = 300, height = 300;
//int x = Game1.GraphicsWidth / 2 - width / 2;
//int y = Game1.GraphicsHeight / 2 - height / 2 - 50;
//GUI.DrawRectangle(spriteBatch, new Rectangle(x, y, width, height), Color.Black, true);
GUI.Font.DrawString(spriteBatch, "Force: " + (int)(targetForce) + " %", new Vector2(GuiFrame.Rect.X + 30, GuiFrame.Rect.Y + 30), Color.White);
}
public override void AddToGUIUpdateList()
{
GuiFrame.AddToGUIUpdateList();
}
public override void UpdateHUD(Character character)
{
GuiFrame.Update(1.0f / 60.0f);
}
public override void UpdateBroken(float deltaTime, Camera cam)
{
force = MathHelper.Lerp(force, 0.0f, 0.1f);
}
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power=0.0f)
{
base.ReceiveSignal(stepsTaken, signal, connection, source, sender, power);
if (connection.Name == "set_force")
{
float tempForce;
if (float.TryParse(signal, NumberStyles.Float, CultureInfo.InvariantCulture, out tempForce))
{
targetForce = MathHelper.Clamp(tempForce, -100.0f, 100.0f);
}
}
}
}
}
@@ -0,0 +1,522 @@
using Barotrauma.Networking;
using Lidgren.Network;
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.Items.Components
{
class FabricableItem
{
public readonly ItemPrefab TargetItem;
public readonly List<Tuple<ItemPrefab, int>> RequiredItems;
public readonly float RequiredTime;
public readonly List<Skill> RequiredSkills;
public FabricableItem(XElement element)
{
string name = ToolBox.GetAttributeString(element, "name", "");
TargetItem = ItemPrefab.list.Find(ip => ip.Name.ToLowerInvariant() == name.ToLowerInvariant()) as ItemPrefab;
if (TargetItem == null)
{
DebugConsole.ThrowError("Error in fabricable item "+name+"! Item \"" + element.Name + "\" not found.");
return;
}
RequiredSkills = new List<Skill>();
RequiredTime = ToolBox.GetAttributeFloat(element, "requiredtime", 1.0f);
RequiredItems = new List<Tuple<ItemPrefab, int>>();
string[] requiredItemNames = ToolBox.GetAttributeString(element, "requireditems", "").Split(',');
foreach (string requiredItemName in requiredItemNames)
{
if (string.IsNullOrWhiteSpace(requiredItemName)) continue;
ItemPrefab requiredItem = ItemPrefab.list.Find(ip => ip.Name.ToLowerInvariant() == requiredItemName.Trim().ToLowerInvariant()) as ItemPrefab;
if (requiredItem == null)
{
DebugConsole.ThrowError("Error in fabricable item " + name + "! Required item \"" + requiredItemName + "\" not found.");
continue;
}
var existing = RequiredItems.Find(r => r.Item1 == requiredItem);
if (existing == null)
{
RequiredItems.Add(new Tuple<ItemPrefab, int>(requiredItem, 1));
}
else
{
RequiredItems.Remove(existing);
RequiredItems.Add(new Tuple<ItemPrefab, int>(requiredItem, existing.Item2+1));
}
}
foreach (XElement subElement in element.Elements())
{
switch (subElement.Name.ToString().ToLowerInvariant())
{
case "requiredskill":
RequiredSkills.Add(new Skill(
ToolBox.GetAttributeString(subElement, "name", ""),
ToolBox.GetAttributeInt(subElement, "level", 0)));
break;
}
}
}
}
class Fabricator : Powered, IServerSerializable, IClientSerializable
{
private List<FabricableItem> fabricableItems;
private GUIListBox itemList;
private GUIFrame selectedItemFrame;
private GUIProgressBar progressBar;
private GUIButton activateButton;
private FabricableItem fabricatedItem;
private float timeUntilReady;
//used for checking if contained items have changed
//(in which case we need to recheck which items can be fabricated)
private Item[] prevContainedItems;
public Fabricator(Item item, XElement element)
: base(item, element)
{
fabricableItems = new List<FabricableItem>();
foreach (XElement subElement in element.Elements())
{
if (subElement.Name.ToString() != "fabricableitem") continue;
FabricableItem fabricableItem = new FabricableItem(subElement);
if (fabricableItem.TargetItem != null) fabricableItems.Add(fabricableItem);
}
GuiFrame.Padding = new Vector4(20.0f, 20.0f, 20.0f, 20.0f);
itemList = new GUIListBox(new Rectangle(0,0,GuiFrame.Rect.Width/2-20,0), "", GuiFrame);
itemList.OnSelected = SelectItem;
foreach (FabricableItem fi in fabricableItems)
{
GUIFrame frame = new GUIFrame(new Rectangle(0, 0, 0, 50), Color.Transparent, null, itemList)
{
UserData = fi,
Padding = new Vector4(5.0f, 5.0f, 5.0f, 5.0f),
HoverColor = Color.Gold * 0.2f,
SelectedColor = Color.Gold * 0.5f,
ToolTip = fi.TargetItem.Description
};
GUITextBlock textBlock = new GUITextBlock(
new Rectangle(40, 0, 0, 25),
fi.TargetItem.Name,
Color.Transparent, Color.White,
Alignment.Left, Alignment.Left,
null, frame);
textBlock.ToolTip = fi.TargetItem.Description;
textBlock.Padding = new Vector4(5.0f, 0.0f, 5.0f, 0.0f);
if (fi.TargetItem.sprite != null)
{
GUIImage img = new GUIImage(new Rectangle(0, 0, 40, 40), fi.TargetItem.sprite, Alignment.Left, frame);
img.Scale = Math.Min(Math.Min(40.0f / img.SourceRect.Width, 40.0f / img.SourceRect.Height), 1.0f);
img.Color = fi.TargetItem.SpriteColor;
img.ToolTip = fi.TargetItem.Description;
}
}
}
private bool SelectItem(GUIComponent component, object obj)
{
FabricableItem targetItem = obj as FabricableItem;
if (targetItem == null) return false;
if (selectedItemFrame != null) GuiFrame.RemoveChild(selectedItemFrame);
//int width = 200, height = 150;
selectedItemFrame = new GUIFrame(new Rectangle(0, 0, (int)(GuiFrame.Rect.Width * 0.4f), 300), Color.Black * 0.8f, Alignment.CenterY | Alignment.Right, null, GuiFrame);
selectedItemFrame.Padding = new Vector4(10.0f, 10.0f, 10.0f, 10.0f);
progressBar = new GUIProgressBar(new Rectangle(0, 0, 0, 20), Color.Green, "", 0.0f, Alignment.BottomCenter, selectedItemFrame);
progressBar.IsHorizontal = true;
if (targetItem.TargetItem.sprite != null)
{
int y = 0;
GUIImage img = new GUIImage(new Rectangle(10, 0, 40, 40), targetItem.TargetItem.sprite, Alignment.TopLeft, selectedItemFrame);
img.Scale = Math.Min(Math.Min(40.0f / img.SourceRect.Width, 40.0f / img.SourceRect.Height), 1.0f);
img.Color = targetItem.TargetItem.SpriteColor;
new GUITextBlock(
new Rectangle(60, 0, 0, 25),
targetItem.TargetItem.Name,
Color.Transparent, Color.White,
Alignment.TopLeft,
Alignment.TopLeft, null,
selectedItemFrame, true);
y += 40;
if (!string.IsNullOrWhiteSpace(targetItem.TargetItem.Description))
{
var description = new GUITextBlock(
new Rectangle(0, y, 0, 0),
targetItem.TargetItem.Description,
"", Alignment.TopLeft, Alignment.TopLeft,
selectedItemFrame, true, GUI.SmallFont);
y += description.Rect.Height + 10;
}
List<Skill> inadequateSkills = new List<Skill>();
if (Character.Controlled != null)
{
inadequateSkills = targetItem.RequiredSkills.FindAll(skill => Character.Controlled.GetSkillLevel(skill.Name) < skill.Level);
}
Color textColor = Color.White;
string text;
if (!inadequateSkills.Any())
{
text = "Required items:\n";
foreach (Tuple<ItemPrefab, int> ip in targetItem.RequiredItems)
{
text += " - " + ip.Item1.Name + " x"+ip.Item2+"\n";
}
text += "Required time: " + targetItem.RequiredTime + " s";
}
else
{
text = "Skills required to calibrate:\n";
foreach (Skill skill in inadequateSkills)
{
text += " - " + skill.Name + " lvl " + skill.Level + "\n";
}
textColor = Color.Red;
}
new GUITextBlock(
new Rectangle(0, y, 0, 25),
text,
Color.Transparent, textColor,
Alignment.TopLeft,
Alignment.TopLeft, null,
selectedItemFrame);
activateButton = new GUIButton(new Rectangle(0, -30, 100, 20), "Create", Color.White, Alignment.CenterX | Alignment.Bottom, "", selectedItemFrame);
activateButton.OnClicked = StartButtonClicked;
activateButton.UserData = targetItem;
activateButton.Enabled = false;
}
return true;
}
public override bool Select(Character character)
{
CheckFabricableItems(character);
if (itemList.Selected != null)
{
SelectItem(itemList.Selected, itemList.Selected.UserData);
}
return base.Select(character);
}
public override bool Pick(Character picker)
{
return (picker != null);
}
/// <summary>
/// check which of the items can be fabricated by the character
/// and update the text colors of the item list accordingly
/// </summary>
private void CheckFabricableItems(Character character)
{
foreach (GUIComponent child in itemList.children)
{
var itemPrefab = child.UserData as FabricableItem;
if (itemPrefab == null) continue;
bool canBeFabricated = CanBeFabricated(itemPrefab, character);
child.GetChild<GUITextBlock>().TextColor = Color.White * (canBeFabricated ? 1.0f : 0.5f);
child.GetChild<GUIImage>().Color = itemPrefab.TargetItem.SpriteColor * (canBeFabricated ? 1.0f : 0.5f);
}
var itemContainer = item.GetComponent<ItemContainer>();
prevContainedItems = new Item[itemContainer.Inventory.Items.Length];
itemContainer.Inventory.Items.CopyTo(prevContainedItems, 0);
}
private bool StartButtonClicked(GUIButton button, object obj)
{
if (fabricatedItem == null)
{
StartFabricating(obj as FabricableItem, Character.Controlled);
}
else
{
CancelFabricating(Character.Controlled);
}
if (GameMain.Server != null)
{
item.CreateServerEvent(this);
}
else if (GameMain.Client != null)
{
item.CreateClientEvent(this);
}
return true;
}
private void StartFabricating(FabricableItem selectedItem, Character user = null)
{
if (selectedItem == null) return;
if (user != null)
{
GameServer.Log(user.Name + " started fabricating " + selectedItem.TargetItem.Name + " in " + item.Name, ServerLog.MessageType.ItemInteraction);
}
itemList.Enabled = false;
activateButton.Text = "Cancel";
fabricatedItem = selectedItem;
IsActive = true;
timeUntilReady = fabricatedItem.RequiredTime;
var containers = item.GetComponents<ItemContainer>();
containers[0].Inventory.Locked = true;
containers[1].Inventory.Locked = true;
currPowerConsumption = powerConsumption;
}
private void CancelFabricating(Character user = null)
{
if (fabricatedItem != null && user != null)
{
GameServer.Log(user.Name + " cancelled the fabrication of " + fabricatedItem.TargetItem.Name + " in " + item.Name, ServerLog.MessageType.ItemInteraction);
}
itemList.Enabled = true;
IsActive = false;
fabricatedItem = null;
currPowerConsumption = 0.0f;
if (activateButton != null)
{
activateButton.Text = "Create";
}
if (progressBar != null) progressBar.BarSize = 0.0f;
timeUntilReady = 0.0f;
var containers = item.GetComponents<ItemContainer>();
containers[0].Inventory.Locked = false;
containers[1].Inventory.Locked = false;
}
public override void Update(float deltaTime, Camera cam)
{
if (progressBar!=null)
{
progressBar.BarSize = fabricatedItem == null ? 0.0f : (fabricatedItem.RequiredTime - timeUntilReady) / fabricatedItem.RequiredTime;
}
if (voltage < minVoltage) return;
if (powerConsumption == 0) voltage = 1.0f;
timeUntilReady -= deltaTime*voltage;
voltage -= deltaTime * 10.0f;
if (timeUntilReady > 0.0f) return;
var containers = item.GetComponents<ItemContainer>();
if (containers.Count < 2)
{
DebugConsole.ThrowError("Error while fabricating a new item: fabricators must have two ItemContainer components");
return;
}
foreach (Tuple<ItemPrefab, int> ip in fabricatedItem.RequiredItems)
{
for (int i = 0; i < ip.Item2; i++)
{
var requiredItem = containers[0].Inventory.Items.FirstOrDefault(it => it != null && it.Prefab == ip.Item1);
if (requiredItem == null) continue;
Entity.Spawner.AddToRemoveQueue(requiredItem);
containers[0].Inventory.RemoveItem(requiredItem);
}
}
if (containers[1].Inventory.Items.All(i => i != null))
{
Entity.Spawner.AddToSpawnQueue(fabricatedItem.TargetItem, item.Position, item.Submarine);
}
else
{
Entity.Spawner.AddToSpawnQueue(fabricatedItem.TargetItem, containers[1].Inventory);
}
CancelFabricating(null);
}
public override void DrawHUD(SpriteBatch spriteBatch, Character character)
{
GuiFrame.Draw(spriteBatch);
}
public override void AddToGUIUpdateList()
{
GuiFrame.AddToGUIUpdateList();
}
public override void UpdateHUD(Character character)
{
FabricableItem targetItem = itemList.SelectedData as FabricableItem;
if (targetItem != null)
{
activateButton.Enabled = CanBeFabricated(targetItem, character);
}
if (character != null)
{
bool itemsChanged = false;
if (prevContainedItems == null)
{
itemsChanged = true;
}
else
{
var itemContainer = item.GetComponent<ItemContainer>();
for (int i = 0; i < itemContainer.Inventory.Items.Length; i++)
{
if (prevContainedItems[i] != itemContainer.Inventory.Items[i])
{
itemsChanged = true;
break;
}
}
}
if (itemsChanged) CheckFabricableItems(character);
}
GuiFrame.Update((float)Timing.Step);
}
private bool CanBeFabricated(FabricableItem fabricableItem, Character user)
{
if (fabricableItem == null) return false;
if (user != null &&
fabricableItem.RequiredSkills.Any(skill => user.GetSkillLevel(skill.Name) < skill.Level))
{
return false;
}
ItemContainer container = item.GetComponent<ItemContainer>();
foreach (Tuple<ItemPrefab, int> ip in fabricableItem.RequiredItems)
{
if (Array.FindAll(container.Inventory.Items, it => it != null && it.Prefab == ip.Item1).Length < ip.Item2) return false;
}
return true;
}
public void ClientWrite(NetBuffer msg, object[] extraData = null)
{
int itemIndex = fabricatedItem == null ? -1 : fabricableItems.IndexOf(fabricatedItem);
msg.WriteRangedInteger(-1, fabricableItems.Count - 1, itemIndex);
}
public void ServerRead(ClientNetObject type, NetBuffer msg, Client c)
{
int itemIndex = msg.ReadRangedInteger(-1, fabricableItems.Count - 1);
item.CreateServerEvent(this);
if (!item.CanClientAccess(c)) return;
if (itemIndex == -1)
{
CancelFabricating(c.Character);
}
else
{
//if already fabricating the selected item, return
if (fabricatedItem != null && fabricableItems.IndexOf(fabricatedItem) == itemIndex) return;
if (itemIndex < 0 || itemIndex >= fabricableItems.Count) return;
SelectItem(null, fabricableItems[itemIndex]);
StartFabricating(fabricableItems[itemIndex], c.Character);
}
}
public void ServerWrite(NetBuffer msg, Client c, object[] extraData = null)
{
int itemIndex = fabricatedItem == null ? -1 : fabricableItems.IndexOf(fabricatedItem);
msg.WriteRangedInteger(-1, fabricableItems.Count - 1, itemIndex);
}
public void ClientRead(ServerNetObject type, NetBuffer msg, float sendingTime)
{
int itemIndex = msg.ReadRangedInteger(-1, fabricableItems.Count - 1);
if (itemIndex == -1)
{
CancelFabricating();
}
else
{
//if already fabricating the selected item, return
if (fabricatedItem != null && fabricableItems.IndexOf(fabricatedItem) == itemIndex) return;
if (itemIndex < 0 || itemIndex >= fabricableItems.Count) return;
SelectItem(null, fabricableItems[itemIndex]);
StartFabricating(fabricableItems[itemIndex]);
}
}
}
}
@@ -0,0 +1,239 @@
using System;
using System.Xml.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma.Items.Components
{
class MiniMap : Powered
{
class HullData
{
public float? Oxygen;
public float? Water;
}
private DateTime resetDataTime;
bool hasPower;
[Editable, HasDefaultValue(false, true)]
public bool RequireWaterDetectors
{
get;
set;
}
[Editable, HasDefaultValue(true, true)]
public bool RequireOxygenDetectors
{
get;
set;
}
[Editable, HasDefaultValue(false, true)]
public bool ShowHullIntegrity
{
get;
set;
}
private Dictionary<Hull, HullData> hullDatas;
public MiniMap(Item item, XElement element)
: base(item, element)
{
IsActive = true;
hullDatas = new Dictionary<Hull, HullData>();
}
public override void Update(float deltaTime, Camera cam)
{
//periodically reset all hull data
//(so that outdated hull info won't be shown if detectors stop sending signals)
if (DateTime.Now > resetDataTime)
{
hullDatas.Clear();
resetDataTime = DateTime.Now + new TimeSpan(0, 0, 1);
}
currPowerConsumption = powerConsumption;
hasPower = voltage > minVoltage;
voltage = 0.0f;
}
public override bool Pick(Character picker)
{
if (picker == null) return false;
//picker.SelectedConstruction = item;
return true;
}
public override void DrawHUD(SpriteBatch spriteBatch, Character character)
{
if (item.Submarine == null) return;
int width = GuiFrame.Rect.Width, height = GuiFrame.Rect.Height;
int x = GuiFrame.Rect.X;
int y = GuiFrame.Rect.Y;
GuiFrame.Draw(spriteBatch);
if (!hasPower) return;
Rectangle miniMap = new Rectangle(x + 20, y + 40, width - 40, height - 60);
float size = Math.Min((float)miniMap.Width / (float)item.Submarine.Borders.Width, (float)miniMap.Height / (float)item.Submarine.Borders.Height);
foreach (Hull hull in Hull.hullList)
{
Point topLeft = new Point(
miniMap.X + (int)((hull.Rect.X - item.Submarine.HiddenSubPosition.X - item.Submarine.Borders.X) * size),
miniMap.Y - (int)((hull.Rect.Y - item.Submarine.HiddenSubPosition.Y - item.Submarine.Borders.Y) * size));
Point bottomRight = new Point(
topLeft.X + (int)(hull.Rect.Width * size),
topLeft.Y + (int)(hull.Rect.Height * size));
topLeft.X = (int)MathUtils.RoundTowardsClosest(topLeft.X, 4);
topLeft.Y = (int)MathUtils.RoundTowardsClosest(topLeft.Y, 4);
bottomRight.X = (int)MathUtils.RoundTowardsClosest(bottomRight.X, 4);
bottomRight.Y = (int)MathUtils.RoundTowardsClosest(bottomRight.Y, 4);
Rectangle hullRect = new Rectangle(
topLeft, bottomRight - topLeft);
HullData hullData;
hullDatas.TryGetValue(hull, out hullData);
Color borderColor = Color.Green;
//hull integrity -----------------------------------
float? gapOpenSum = 0.0f;
if (ShowHullIntegrity)
{
gapOpenSum = hull.ConnectedGaps.Where(g => !g.IsRoomToRoom).Sum(g => g.Open);
borderColor = Color.Lerp(borderColor, Color.Red, Math.Min((float)gapOpenSum, 1.0f));
}
//oxygen -----------------------------------
float? oxygenAmount = null;
if (RequireOxygenDetectors && (hullData == null || hullData.Oxygen == null))
{
borderColor *= 0.5f;
}
else
{
oxygenAmount = hullData != null && hullData.Oxygen != null ? (float)hullData.Oxygen : hull.OxygenPercentage;
GUI.DrawRectangle(spriteBatch, hullRect, Color.Lerp(Color.Red * 0.5f, Color.Green * 0.3f, (float)oxygenAmount / 100.0f), true);
}
//water -----------------------------------
float? waterAmount = null;
if (RequireWaterDetectors && (hullData == null || hullData.Water == null))
{
borderColor *= 0.5f;
}
else
{
waterAmount = hullData != null && hullData.Water != null ?
(float)hullData.Water :
Math.Min(hull.Volume / hull.FullVolume, 1.0f);
if (hullRect.Height * waterAmount > 3.0f)
{
Rectangle waterRect = new Rectangle(
hullRect.X,
(int)(hullRect.Y + hullRect.Height * (1.0f - waterAmount)),
hullRect.Width,
(int)(hullRect.Height * waterAmount));
waterRect.Inflate(-3, -3);
GUI.DrawRectangle(spriteBatch, waterRect, Color.DarkBlue, true);
GUI.DrawLine(spriteBatch, new Vector2(waterRect.X, waterRect.Y), new Vector2(waterRect.Right, waterRect.Y), Color.LightBlue);
}
}
if (hullRect.Contains(PlayerInput.MousePosition))
{
borderColor = Color.White;
if (gapOpenSum > 0.1f)
{
GUI.DrawString(spriteBatch,
new Vector2(x + 10, y + height - 60),
"Hull breach", Color.Red, Color.Black * 0.5f, 2, GUI.SmallFont);
}
GUI.DrawString(spriteBatch,
new Vector2(x + 10, y + height - 60),
oxygenAmount == null ? "Air quality data not available" : "Air quality: " + (int)oxygenAmount + " %",
oxygenAmount == null ? Color.Red : Color.Lerp(Color.Red, Color.LightGreen, (float)oxygenAmount / 100.0f),
Color.Black * 0.5f, 2, GUI.SmallFont);
GUI.DrawString(spriteBatch,
new Vector2(x + 10, y + height - 40),
waterAmount == null ? "Water level data not available" : "Water level: " + (int)(waterAmount * 100.0f) + " %",
waterAmount == null ? Color.Red : Color.Lerp(Color.LightGreen, Color.Red, (float)waterAmount),
Color.Black * 0.5f, 2, GUI.SmallFont);
}
GUI.DrawRectangle(spriteBatch, hullRect, borderColor, false, 0.0f, 2);
}
}
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power = 0)
{
base.ReceiveSignal(stepsTaken, signal, connection, source, sender, power);
if (sender == null || sender.CurrentHull == null) return;
Hull senderHull = sender.CurrentHull;
HullData hullData;
if (!hullDatas.TryGetValue(senderHull, out hullData))
{
hullData = new HullData();
hullDatas.Add(senderHull, hullData);
}
switch (connection.Name)
{
case "water_data_in":
//cheating a bit because water detectors don't actually send the water level
if (source.GetComponent<WaterDetector>() == null)
{
hullData.Water = Rand.Range(0.0f, 1.0f);
}
else
{
hullData.Water = Math.Min(senderHull.Volume / senderHull.FullVolume, 1.0f);
}
break;
case "oxygen_data_in":
float oxy;
if (!float.TryParse(signal, System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out oxy))
{
oxy = Rand.Range(0.0f, 100.0f);
}
hullData.Oxygen = oxy;
break;
}
}
}
}
@@ -0,0 +1,131 @@
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Xml.Linq;
using System.Linq;
namespace Barotrauma.Items.Components
{
class OxygenGenerator : Powered
{
PropertyTask powerUpTask;
float powerDownTimer;
bool running;
private float generatedAmount;
List<Vent> ventList;
private float totalHullVolume;
public bool IsRunning()
{
return (running && item.Condition>0.0f);
}
public float CurrFlow
{
get;
private set;
}
[Editable, HasDefaultValue(100.0f, true)]
public float GeneratedAmount
{
get { return generatedAmount; }
set { generatedAmount = MathHelper.Clamp(value, -10000.0f, 10000.0f); }
}
public OxygenGenerator(Item item, XElement element)
: base(item, element)
{
IsActive = true;
//item.linkedTo.CollectionChanged += delegate { GetVents(); };
}
public override void Update(float deltaTime, Camera cam)
{
base.Update(deltaTime, cam);
CurrFlow = 0.0f;
currPowerConsumption = powerConsumption;
if (item.CurrentHull == null) return;
if (voltage < minVoltage)
{
powerDownTimer += deltaTime;
running = false;
if ((powerUpTask==null || powerUpTask.IsFinished) && powerDownTimer>5.0f)
{
powerUpTask = new PropertyTask(item, IsRunning, 50.0f, "Turn on the oxygen generator");
}
return;
}
else
{
powerDownTimer = 0.0f;
}
ApplyStatusEffects(ActionType.OnActive, deltaTime, null);
running = true;
CurrFlow = Math.Min(voltage, 1.0f) * generatedAmount*100.0f;
//item.CurrentHull.Oxygen += CurrFlow * deltaTime;
UpdateVents(CurrFlow);
voltage -= deltaTime;
}
public override void UpdateBroken(float deltaTime, Camera cam)
{
powerDownTimer += deltaTime;
}
private void GetVents()
{
ventList.Clear();
foreach (MapEntity entity in item.linkedTo)
{
Item linkedItem = entity as Item;
if (linkedItem == null) continue;
Vent vent = linkedItem.GetComponent<Vent>();
if (vent == null) continue;
ventList.Add(vent);
if (linkedItem.CurrentHull!=null) totalHullVolume += linkedItem.CurrentHull.FullVolume;
}
}
//public override void OnMapLoaded()
//{
// GetVents();
//}
private void UpdateVents(float deltaOxygen)
{
if (ventList == null)
{
ventList = new List<Vent>();
GetVents();
}
if (!ventList.Any() || totalHullVolume == 0.0f) return;
foreach (Vent v in ventList)
{
if (v.Item.CurrentHull == null) continue;
v.OxygenFlow = deltaOxygen * (v.Item.CurrentHull.FullVolume / totalHullVolume);
v.IsActive = true;
}
}
}
}
@@ -0,0 +1,287 @@
using Barotrauma.Networking;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Specialized;
using System.Globalization;
using System.Xml.Linq;
namespace Barotrauma.Items.Components
{
class Pump : Powered, IServerSerializable, IClientSerializable
{
private float flowPercentage;
private float maxFlow;
private float? targetLevel;
public Hull hull1;
private GUITickBox isActiveTickBox;
[HasDefaultValue(0.0f, true)]
public float FlowPercentage
{
get { return flowPercentage; }
set
{
if (!MathUtils.IsValid(flowPercentage)) return;
flowPercentage = MathHelper.Clamp(value,-100.0f,100.0f);
flowPercentage = MathUtils.Round(flowPercentage, 1.0f);
}
}
[HasDefaultValue(80.0f, false)]
public float MaxFlow
{
get { return maxFlow; }
set { maxFlow = value; }
}
float currFlow;
public float CurrFlow
{
get
{
if (!IsActive) return 0.0f;
return Math.Abs(currFlow);
}
}
public override bool IsActive
{
get
{
return base.IsActive;
}
set
{
base.IsActive = value;
if (isActiveTickBox != null) isActiveTickBox.Selected = value;
}
}
public Pump(Item item, XElement element)
: base(item, element)
{
GetHull();
isActiveTickBox = new GUITickBox(new Rectangle(0, 0, 20, 20), "Running", Alignment.TopLeft, GuiFrame);
isActiveTickBox.OnSelected = (GUITickBox box) =>
{
targetLevel = null;
IsActive = !IsActive;
if (!IsActive) currPowerConsumption = 0.0f;
if (GameMain.Server != null)
{
item.CreateServerEvent(this);
GameServer.Log(Character.Controlled + (IsActive ? " turned on " : " turned off ") + item.Name, ServerLog.MessageType.ItemInteraction);
}
else if (GameMain.Client != null)
{
correctionTimer = CorrectionDelay;
item.CreateClientEvent(this);
}
return true;
};
var button = new GUIButton(new Rectangle(160, 40, 35, 30), "OUT", "", GuiFrame);
button.OnClicked = (GUIButton btn, object obj) =>
{
FlowPercentage -= 10.0f;
if (GameMain.Server != null)
{
item.CreateServerEvent(this);
GameServer.Log(Character.Controlled + " set the pumping speed of " + item.Name + " to " + (int)(flowPercentage) + " %", ServerLog.MessageType.ItemInteraction);
}
else if (GameMain.Client != null)
{
correctionTimer = CorrectionDelay;
item.CreateClientEvent(this);
}
return true;
};
button = new GUIButton(new Rectangle(210, 40, 35, 30), "IN", "", GuiFrame);
button.OnClicked = (GUIButton btn, object obj) =>
{
FlowPercentage += 10.0f;
if (GameMain.Server != null)
{
item.CreateServerEvent(this);
GameServer.Log(Character.Controlled + " set the pumping speed of " + item.Name + " to " + (int)(flowPercentage) + " %", ServerLog.MessageType.ItemInteraction);
}
else if (GameMain.Client != null)
{
correctionTimer = CorrectionDelay;
item.CreateClientEvent(this);
}
return true;
};
}
public override void Move(Vector2 amount)
{
base.Move(amount);
GetHull();
}
public override void OnMapLoaded()
{
GetHull();
}
public override void Update(float deltaTime, Camera cam)
{
currFlow = 0.0f;
if (targetLevel != null)
{
float hullPercentage = 0.0f;
if (hull1 != null) hullPercentage = (hull1.Volume / hull1.FullVolume) * 100.0f;
FlowPercentage = ((float)targetLevel - hullPercentage) * 10.0f;
}
currPowerConsumption = powerConsumption * Math.Abs(flowPercentage / 100.0f);
if (voltage < minVoltage) return;
ApplyStatusEffects(ActionType.OnActive, deltaTime, null);
if (hull1 == null) return;
float powerFactor = (currPowerConsumption==0.0f) ? 1.0f : voltage;
//flowPercentage = maxFlow * powerFactor;
currFlow = (flowPercentage / 100.0f) * maxFlow * powerFactor;
hull1.Volume += currFlow;
if (hull1.Volume > hull1.FullVolume) hull1.Pressure += 0.5f;
//if (hull2 != null)
//{
// hull2.Volume -= currFlow;
// if (hull2.Volume > hull1.FullVolume) hull2.Pressure += 0.5f;
//}
voltage = 0.0f;
}
private void GetHull()
{
hull1 = Hull.FindHull(item.WorldPosition, item.CurrentHull);
}
public override void DrawHUD(SpriteBatch spriteBatch, Character character)
{
int x = GuiFrame.Rect.X;
int y = GuiFrame.Rect.Y;
GuiFrame.Draw(spriteBatch);
GUI.Font.DrawString(spriteBatch, "Pumping speed: " + (int)flowPercentage + " %", new Vector2(x + 40, y + 85), Color.White);
}
public override void AddToGUIUpdateList()
{
GuiFrame.AddToGUIUpdateList();
}
public override void UpdateHUD(Character character)
{
GuiFrame.Update(1.0f / 60.0f);
}
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power=0.0f)
{
base.ReceiveSignal(stepsTaken, signal, connection, source, sender, power);
if (connection.Name == "toggle")
{
IsActive = !IsActive;
}
else if (connection.Name == "set_active")
{
IsActive = (signal != "0");
}
else if (connection.Name == "set_speed")
{
float tempSpeed;
if (float.TryParse(signal, NumberStyles.Any, CultureInfo.InvariantCulture, out tempSpeed))
{
flowPercentage = MathHelper.Clamp(tempSpeed, -100.0f, 100.0f);
}
}
else if (connection.Name == "set_targetlevel")
{
float tempTarget;
if (float.TryParse(signal, NumberStyles.Any, CultureInfo.InvariantCulture, out tempTarget))
{
targetLevel = MathHelper.Clamp((tempTarget+100.0f)/2.0f, 0.0f, 100.0f);
}
}
if (!IsActive) currPowerConsumption = 0.0f;
}
public void ClientWrite(Lidgren.Network.NetBuffer msg, object[] extraData = null)
{
//flowpercentage can only be adjusted at 10% intervals -> no need for more accuracy than this
msg.WriteRangedInteger(-10, 10, (int)(flowPercentage / 10.0f));
msg.Write(IsActive);
}
public void ServerRead(ClientNetObject type, Lidgren.Network.NetBuffer msg, Client c)
{
float newFlowPercentage = msg.ReadRangedInteger(-10, 10) * 10.0f;
bool newIsActive = msg.ReadBoolean();
if (item.CanClientAccess(c))
{
if (newFlowPercentage != FlowPercentage)
{
GameServer.Log(c.Character + " set the pumping speed of " + item.Name + " to " + (int)(newFlowPercentage) + " %", ServerLog.MessageType.ItemInteraction);
}
if (newIsActive != IsActive)
{
GameServer.Log(c.Character + (newIsActive ? " turned on " : " turned off ") + item.Name, ServerLog.MessageType.ItemInteraction);
}
FlowPercentage = newFlowPercentage;
IsActive = newIsActive;
}
//notify all clients of the changed state
item.CreateServerEvent(this);
}
public void ServerWrite(Lidgren.Network.NetBuffer msg, Client c, object[] extraData = null)
{
//flowpercentage can only be adjusted at 10% intervals -> no need for more accuracy than this
msg.WriteRangedInteger(-10, 10, (int)(flowPercentage / 10.0f));
msg.Write(IsActive);
}
public void ClientRead(ServerNetObject type, Lidgren.Network.NetBuffer msg, float sendingTime)
{
if (correctionTimer > 0.0f)
{
StartDelayedCorrection(type, msg.ExtractBits(5 + 1), sendingTime);
return;
}
FlowPercentage = msg.ReadRangedInteger(-10, 10) * 10.0f;
IsActive = msg.ReadBoolean();
}
}
}
@@ -0,0 +1,584 @@
using Barotrauma.Networking;
using FarseerPhysics;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
using System.Xml.Linq;
using Voronoi2;
namespace Barotrauma.Items.Components
{
class Radar : Powered, IServerSerializable, IClientSerializable
{
private float range;
private float pingState;
private readonly Sprite pingCircle, screenOverlay;
private readonly Sprite radarBlip;
private GUITickBox isActiveTickBox;
private List<RadarBlip> radarBlips;
private float prevPingRadius;
float prevPassivePingRadius;
private Vector2 center;
private float displayRadius;
private float displayScale;
private float displayBorderSize;
[HasDefaultValue(10000.0f, false)]
public float Range
{
get { return range; }
set { range = MathHelper.Clamp(value, 0.0f, 100000.0f); }
}
[HasDefaultValue(false, false)]
public bool DetectSubmarineWalls
{
get;
set;
}
public override bool IsActive
{
get
{
return base.IsActive;
}
set
{
base.IsActive = value;
if (isActiveTickBox != null) isActiveTickBox.Selected = value;
}
}
public Radar(Item item, XElement element)
: base(item, element)
{
radarBlips = new List<RadarBlip>();
displayBorderSize = ToolBox.GetAttributeFloat(element, "displaybordersize", 0.0f);
foreach (XElement subElement in element.Elements())
{
switch (subElement.Name.ToString().ToLowerInvariant())
{
case "pingcircle":
pingCircle = new Sprite(subElement);
break;
case "screenoverlay":
screenOverlay = new Sprite(subElement);
break;
case "blip":
radarBlip = new Sprite(subElement);
break;
}
}
isActiveTickBox = new GUITickBox(new Rectangle(0, 0, 20, 20), "Active Sonar", Alignment.TopLeft, GuiFrame);
isActiveTickBox.OnSelected = (GUITickBox box) =>
{
if (GameMain.Server != null)
{
item.CreateServerEvent(this);
}
else if (GameMain.Client != null)
{
item.CreateClientEvent(this);
correctionTimer = CorrectionDelay;
}
IsActive = box.Selected;
return true;
};
GuiFrame.CanBeFocused = false;
IsActive = false;
}
public override void Update(float deltaTime, Camera cam)
{
currPowerConsumption = powerConsumption;
base.Update(deltaTime, cam);
if (voltage >= minVoltage || powerConsumption <= 0.0f)
{
pingState = pingState + deltaTime * 0.5f;
if (pingState > 1.0f)
{
if (item.CurrentHull != null) item.CurrentHull.AiTarget.SoundRange = Math.Max(Range * pingState, item.CurrentHull.AiTarget.SoundRange);
item.Use(deltaTime);
pingState = 0.0f;
}
}
else
{
pingState = 0.0f;
}
Voltage -= deltaTime;
}
public override bool Use(float deltaTime, Character character = null)
{
return pingState > 1.0f;
}
public override void AddToGUIUpdateList()
{
GuiFrame.AddToGUIUpdateList();
}
public override void UpdateHUD(Character character)
{
GuiFrame.Update((float)Timing.Step);
for (int i = radarBlips.Count - 1; i >= 0; i--)
{
radarBlips[i].FadeTimer -= (float)Timing.Step * 0.5f;
if (radarBlips[i].FadeTimer <= 0.0f) radarBlips.RemoveAt(i);
}
if (IsActive)
{
float pingRadius = displayRadius * pingState;
Ping(item.WorldPosition, pingRadius, prevPingRadius, displayScale, range, 2.0f);
prevPingRadius = pingRadius;
}
float passivePingRadius = (float)Math.Sin(Timing.TotalTime * 10);
if (passivePingRadius > 0.0f)
{
foreach (AITarget t in AITarget.List)
{
if (t.SoundRange <= 0.0f) continue;
if (Vector2.Distance(t.WorldPosition, item.WorldPosition) < t.SoundRange)
{
Ping(t.WorldPosition, t.SoundRange * passivePingRadius * 0.2f, t.SoundRange * prevPassivePingRadius * 0.2f, displayScale, t.SoundRange, 0.5f);
radarBlips.Add(new RadarBlip(t.WorldPosition, 1.0f));
}
}
}
prevPassivePingRadius = passivePingRadius;
}
public override void DrawHUD(SpriteBatch spriteBatch, Character character)
{
GuiFrame.Draw(spriteBatch);
int radius = GuiFrame.Rect.Height / 2 - 10;
DrawRadar(spriteBatch, new Rectangle((int)GuiFrame.Center.X - radius, (int)GuiFrame.Center.Y - radius, radius * 2, radius * 2));
}
private void DrawRadar(SpriteBatch spriteBatch, Rectangle rect)
{
center = new Vector2(rect.X + rect.Width * 0.5f, rect.Center.Y);
displayRadius = (rect.Width / 2.0f) * (1.0f - displayBorderSize);
displayScale = displayRadius / range;
if (IsActive)
{
pingCircle.Draw(spriteBatch, center, Color.White * (1.0f - pingState), 0.0f, (displayRadius*2 / pingCircle.size.X) * pingState);
}
if (item.Submarine != null && !DetectSubmarineWalls)
{
float simScale = displayScale * Physics.DisplayToSimRation;
foreach (Submarine submarine in Submarine.Loaded)
{
if (submarine != item.Submarine && !submarine.DockedTo.Contains(item.Submarine)) continue;
Vector2 offset = ConvertUnits.ToSimUnits(submarine.WorldPosition - item.WorldPosition);
for (int i = 0; i < submarine.HullVertices.Count; i++)
{
Vector2 start = (submarine.HullVertices[i] + offset) * simScale;
start.Y = -start.Y;
Vector2 end = (submarine.HullVertices[(i + 1) % submarine.HullVertices.Count] + offset) * simScale;
end.Y = -end.Y;
GUI.DrawLine(spriteBatch, center + start, center + end, Color.LightBlue);
}
}
}
if (radarBlips.Count > 0)
{
spriteBatch.End();
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.Additive);
foreach (RadarBlip radarBlip in radarBlips)
{
DrawBlip(spriteBatch, radarBlip, center, radarBlip.FadeTimer / 2.0f);
}
spriteBatch.End();
spriteBatch.Begin(SpriteSortMode.Immediate, null, null, null, GameMain.ScissorTestEnable);
}
if (GameMain.DebugDraw)
{
GUI.DrawString(spriteBatch, rect.Location.ToVector2(), radarBlips.Count.ToString(), Color.White);
}
if (screenOverlay != null)
{
screenOverlay.Draw(spriteBatch, center, 0.0f, rect.Width / screenOverlay.size.X);
}
if (GameMain.GameSession == null) return;
DrawMarker(spriteBatch,
GameMain.GameSession.StartLocation.Name,
(Level.Loaded.StartPosition - item.WorldPosition), displayScale, center, (rect.Width * 0.5f));
DrawMarker(spriteBatch,
GameMain.GameSession.EndLocation.Name,
(Level.Loaded.EndPosition - item.WorldPosition), displayScale, center, (rect.Width * 0.5f));
if (GameMain.GameSession.Mission != null)
{
var mission = GameMain.GameSession.Mission;
if (!string.IsNullOrWhiteSpace(mission.RadarLabel) && mission.RadarPosition != Vector2.Zero)
{
DrawMarker(spriteBatch,
mission.RadarLabel,
mission.RadarPosition - item.WorldPosition, displayScale, center, (rect.Width * 0.55f));
}
}
foreach (Submarine sub in Submarine.Loaded)
{
if (!sub.OnRadar) continue;
if (item.Submarine == sub || sub.DockedTo.Contains(item.Submarine)) continue;
if (sub.WorldPosition.Y > Level.Loaded.Size.Y) continue;
DrawMarker(spriteBatch, sub.Name, sub.WorldPosition - item.WorldPosition, displayScale, center, (rect.Width * 0.45f));
}
if (!GameMain.DebugDraw) return;
var steering = item.GetComponent<Steering>();
if (steering == null || steering.SteeringPath == null) return;
Vector2 prevPos = Vector2.Zero;
foreach (WayPoint wp in steering.SteeringPath.Nodes)
{
Vector2 pos = (wp.Position - item.WorldPosition) * displayScale;
if (pos.Length() > displayRadius) continue;
pos.Y = -pos.Y;
pos += center;
GUI.DrawRectangle(spriteBatch, new Rectangle((int)pos.X - 3 / 2, (int)pos.Y - 3, 6, 6), (steering.SteeringPath.CurrentNode == wp) ? Color.LightGreen : Color.Green, false);
if (prevPos != Vector2.Zero)
{
GUI.DrawLine(spriteBatch, pos, prevPos, Color.Green);
}
prevPos = pos;
}
}
private void Ping(Vector2 pingSource, float pingRadius, float prevPingRadius, float displayScale, float range, float pingStrength = 1.0f)
{
foreach (Submarine submarine in Submarine.Loaded)
{
if (item.Submarine == submarine && !DetectSubmarineWalls) continue;
if (item.Submarine != null && item.Submarine.DockedTo.Contains(submarine)) continue;
if (submarine.HullVertices == null) continue;
for (int i = 0; i < submarine.HullVertices.Count; i++)
{
Vector2 start = ConvertUnits.ToDisplayUnits(submarine.HullVertices[i]);
Vector2 end = ConvertUnits.ToDisplayUnits(submarine.HullVertices[(i + 1) % submarine.HullVertices.Count]);
if (item.Submarine == submarine)
{
start += Rand.Vector(500.0f);
end += Rand.Vector(500.0f);
}
CreateBlipsForLine(
start + submarine.WorldPosition,
end + submarine.WorldPosition,
pingRadius, prevPingRadius,
200.0f, 2.0f, range, 1.0f);
}
}
if (Level.Loaded != null && (item.CurrentHull == null || !DetectSubmarineWalls))
{
if (Level.Loaded.Size.Y - pingSource.Y < range)
{
CreateBlipsForLine(
new Vector2(pingSource.X - range, Level.Loaded.Size.Y),
new Vector2(pingSource.X + range, Level.Loaded.Size.Y),
pingRadius, prevPingRadius,
250.0f, 150.0f, range, pingStrength);
}
List<VoronoiCell> cells = Level.Loaded.GetCells(pingSource, 7);
foreach (VoronoiCell cell in cells)
{
foreach (GraphEdge edge in cell.edges)
{
if (!edge.isSolid) continue;
float cellDot = Vector2.Dot(cell.Center - pingSource, (edge.Center + cell.Translation) - cell.Center);
if (cellDot > 0) continue;
float facingDot = Vector2.Dot(
Vector2.Normalize(edge.point1 - edge.point2),
Vector2.Normalize(cell.Center - pingSource));
CreateBlipsForLine(
edge.point1 + cell.Translation,
edge.point2 + cell.Translation,
pingRadius, prevPingRadius,
350.0f, 3.0f * (Math.Abs(facingDot) + 1.0f), range, pingStrength);
}
}
foreach (RuinGeneration.Ruin ruin in Level.Loaded.Ruins)
{
if (!MathUtils.CircleIntersectsRectangle(pingSource, range, ruin.Area)) continue;
foreach (var ruinShape in ruin.RuinShapes)
{
foreach (RuinGeneration.Line wall in ruinShape.Walls)
{
float cellDot = Vector2.Dot(
Vector2.Normalize(ruinShape.Center - pingSource),
Vector2.Normalize((wall.A + wall.B) / 2.0f - ruinShape.Center));
if (cellDot > 0) continue;
CreateBlipsForLine(
wall.A, wall.B,
pingRadius, prevPingRadius,
100.0f, 1000.0f, range, pingStrength);
}
}
}
}
foreach (Character c in Character.CharacterList)
{
if (c.AnimController.CurrentHull != null || !c.Enabled) continue;
if (DetectSubmarineWalls && c.AnimController.CurrentHull == null && item.CurrentHull != null) continue;
foreach (Limb limb in c.AnimController.Limbs)
{
float pointDist = (limb.WorldPosition - pingSource).Length() * displayScale;
if (limb.SimPosition == Vector2.Zero || pointDist > displayRadius) continue;
if (pointDist > prevPingRadius && pointDist < pingRadius)
{
for (int i = 0; i <= limb.Mass / 100.0f; i++)
{
var blip = new RadarBlip(limb.WorldPosition + Rand.Vector(limb.Mass / 10.0f), MathHelper.Clamp(limb.Mass, 0.1f, pingStrength));
radarBlips.Add(blip);
}
}
}
}
}
private void CreateBlipsForLine(Vector2 point1, Vector2 point2, float pingRadius, float prevPingRadius,
float lineStep, float zStep, float range, float pingStrength)
{
float length = (point1 - point2).Length();
Vector2 lineDir = (point2 - point1) / length;
range *= displayScale;
for (float x = 0; x < length; x += lineStep*Rand.Range(0.8f,1.2f))
{
Vector2 point = point1 + lineDir * x;
//point += cell.Translation;
float pointDist = Vector2.Distance(item.WorldPosition, point) * displayScale;
if (pointDist > displayRadius) continue;
if (pointDist < prevPingRadius || pointDist > pingRadius) continue;
float alpha = pingStrength * Rand.Range(1.5f, 2.0f);
for (float z = 0; z < displayRadius - pointDist * displayScale; z += zStep)
{
Vector2 pos = point + Rand.Vector(150.0f) + Vector2.Normalize(point - item.WorldPosition) * z / displayScale;
float fadeTimer = alpha * (1.0f - pointDist / range);
int minDist = 200;
radarBlips.RemoveAll(b => b.FadeTimer < fadeTimer && Math.Abs(pos.X - b.Position.X) < minDist && Math.Abs(pos.Y - b.Position.Y) < minDist);
var blip = new RadarBlip(pos, fadeTimer);
radarBlips.Add(blip);
zStep += 0.5f;
if (z == 0)
{
alpha = Math.Min(alpha - 0.5f, 1.5f);
}
else
{
alpha -= 0.1f;
}
if (alpha < 0) break;
}
}
}
private void DrawBlip(SpriteBatch spriteBatch, RadarBlip blip, Vector2 center, float strength)
{
strength = MathHelper.Clamp(strength, 0.0f, 1.0f);
Color[] colors = new Color[] {
Color.TransparentBlack,
new Color(0, 50, 160),
new Color(0, 133, 166),
new Color(2, 159, 30),
new Color(255, 255, 255) };
float scaledT = strength * (colors.Length - 1);
Color color = Color.Lerp(colors[(int)scaledT], colors[(int)Math.Min(scaledT+1, colors.Length-1)], (scaledT - (int)scaledT));
Vector2 pos = (blip.Position - item.WorldPosition) * displayScale;
pos.Y = -pos.Y;
if (pos.Length() > displayRadius)
{
blip.FadeTimer = 0.0f;
return;
}
float posDist = pos.Length();
Vector2 dir = pos / posDist;
float distFactor = (posDist / displayRadius);
Vector2 normal = new Vector2(dir.Y, -dir.X);
float scale = (strength + 3.0f) * Math.Max(distFactor * 3.0f, 1.0f);
if (radarBlip == null)
{
GUI.DrawRectangle(spriteBatch, center + pos, Vector2.One * 4, Color.Magenta, true);
return;
}
radarBlip.Draw(spriteBatch, center + pos, color, radarBlip.Origin, MathUtils.VectorToAngle(pos),
new Vector2(scale * 0.3f, scale) * 0.04f, SpriteEffects.None, 0);
pos += Rand.Range(0.0f, 1.0f) * dir + Rand.Range(-scale, scale) * normal;
radarBlip.Draw(spriteBatch, center + pos, color * 0.5f, radarBlip.Origin, MathUtils.VectorToAngle(pos),
new Vector2(scale * 0.3f, scale) * 0.08f, SpriteEffects.None, 0);
}
private void DrawMarker(SpriteBatch spriteBatch, string label, Vector2 position, float scale, Vector2 center, float radius)
{
//position += Level.Loaded.Position;
float dist = position.Length();
position *= scale;
position.Y = -position.Y;
float textAlpha = MathHelper.Clamp(1.5f - dist / 50000.0f, 0.5f, 1.0f);
Vector2 dir = Vector2.Normalize(position);
Vector2 markerPos = (dist*scale>radius) ? dir * radius : position;
markerPos += center;
markerPos.X = (int)markerPos.X;
markerPos.Y = (int)markerPos.Y;
GUI.DrawRectangle(spriteBatch, new Rectangle((int)markerPos.X, (int)markerPos.Y, 5, 5), Color.LightBlue);
if (dir.X < 0.0f) markerPos.X -= GUI.SmallFont.MeasureString(label).X+10;
string wrappedLabel = ToolBox.WrapText(label, 150, GUI.SmallFont);
wrappedLabel += "\n"+((int)(dist * Physics.DisplayToRealWorldRatio) + " m");
GUI.DrawString(spriteBatch,
new Vector2(markerPos.X + 10, markerPos.Y),
wrappedLabel,
Color.LightBlue * textAlpha, Color.Black * textAlpha * 0.5f,
2, GUI.SmallFont);
}
protected override void RemoveComponentSpecific()
{
if (pingCircle!=null) pingCircle.Remove();
if (screenOverlay != null) screenOverlay.Remove();
}
public void ClientWrite(Lidgren.Network.NetBuffer msg, object[] extraData = null)
{
msg.Write(IsActive);
}
public void ServerRead(ClientNetObject type, Lidgren.Network.NetBuffer msg, Barotrauma.Networking.Client c)
{
bool isActive = msg.ReadBoolean();
if (!item.CanClientAccess(c)) return;
IsActive = isActive;
isActiveTickBox.Selected = IsActive;
item.CreateServerEvent(this);
}
public void ServerWrite(Lidgren.Network.NetBuffer msg, Barotrauma.Networking.Client c, object[] extraData = null)
{
msg.Write(IsActive);
}
public void ClientRead(ServerNetObject type, Lidgren.Network.NetBuffer msg, float sendingTime)
{
if (correctionTimer > 0.0f)
{
StartDelayedCorrection(type, msg.ExtractBits(1), sendingTime);
return;
}
IsActive = msg.ReadBoolean();
isActiveTickBox.Selected = IsActive;
}
}
class RadarBlip
{
public float FadeTimer;
public Vector2 Position;
public RadarBlip(Vector2 pos, float fadeTimer)
{
Position = pos;
FadeTimer = Math.Max(fadeTimer, 0.0f);
}
}
}
@@ -0,0 +1,666 @@
using System;
using System.Collections.Generic;
using System.Xml.Linq;
using Lidgren.Network;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Barotrauma.Networking;
namespace Barotrauma.Items.Components
{
class Reactor : Powered, IDrawableComponent, IServerSerializable, IClientSerializable
{
const float NetworkUpdateInterval = 0.5f;
//the rate at which the reactor is being run un
//higher rates generate more power (and heat)
private float fissionRate;
//the rate at which the heat is being dissipated
private float coolingRate;
private float temperature;
//is automatic temperature control on
//(adjusts the cooling rate automatically to keep the
//amount of power generated balanced with the load)
private bool autoTemp;
//the temperature after which fissionrate is automatically
//turned down and cooling increased
private float shutDownTemp;
private float fireTemp, meltDownTemp;
//how much power is provided to the grid per 1 temperature unit
private float powerPerTemp;
private int graphSize = 25;
private float graphTimer;
private int updateGraphInterval = 500;
private float[] fissionRateGraph;
private float[] coolingRateGraph;
private float[] tempGraph;
private float[] loadGraph;
private float load;
private PropertyTask powerUpTask;
private GUITickBox autoTempTickBox;
private bool unsentChanges;
private float sendUpdateTimer;
private Character lastUser;
private float? nextServerLogWriteTime;
private float lastServerLogWriteTime;
[Editable, HasDefaultValue(9500.0f, true)]
public float MeltDownTemp
{
get { return meltDownTemp; }
set
{
meltDownTemp = Math.Max(0.0f, value);
}
}
[Editable, HasDefaultValue(9000.0f, true)]
public float FireTemp
{
get { return fireTemp; }
set
{
fireTemp = Math.Max(0.0f, value);
}
}
[Editable, HasDefaultValue(1.0f, true)]
public float PowerPerTemp
{
get { return powerPerTemp; }
set
{
powerPerTemp = Math.Max(0.0f, value);
}
}
[HasDefaultValue(0.0f, true)]
public float FissionRate
{
get { return fissionRate; }
set
{
if (!MathUtils.IsValid(value)) return;
fissionRate = MathHelper.Clamp(value, 0.0f, 100.0f);
}
}
[HasDefaultValue(0.0f, true)]
public float CoolingRate
{
get { return coolingRate; }
set
{
if (!MathUtils.IsValid(value)) return;
coolingRate = MathHelper.Clamp(value, 0.0f, 100.0f);
}
}
[HasDefaultValue(0.0f, true)]
public float Temperature
{
get { return temperature; }
set
{
if (!MathUtils.IsValid(value)) return;
temperature = MathHelper.Clamp(value, 0.0f, 10000.0f);
}
}
public bool IsRunning()
{
return (temperature > 0.0f);
}
[HasDefaultValue(false, true)]
public bool AutoTemp
{
get { return autoTemp; }
set
{
autoTemp = value;
if (autoTempTickBox!=null) autoTempTickBox.Selected = value;
}
}
public float ExtraCooling { get; set; }
public float AvailableFuel { get; set; }
[HasDefaultValue(500.0f, true)]
public float ShutDownTemp
{
get { return shutDownTemp; }
set { shutDownTemp = MathHelper.Clamp(value, 0.0f, 10000.0f); }
}
public Reactor(Item item, XElement element)
: base(item, element)
{
fissionRateGraph = new float[graphSize];
coolingRateGraph = new float[graphSize];
tempGraph = new float[graphSize];
loadGraph = new float[graphSize];
shutDownTemp = 500.0f;
powerPerTemp = 1.0f;
IsActive = true;
var button = new GUIButton(new Rectangle(410, 70, 40, 40), "-", "", GuiFrame);
button.OnPressed = () =>
{
lastUser = Character.Controlled;
if (nextServerLogWriteTime == null)
{
nextServerLogWriteTime = Math.Max(lastServerLogWriteTime + 1.0f, (float)Timing.TotalTime);
}
unsentChanges = true;
ShutDownTemp -= 100.0f;
return false;
};
button = new GUIButton(new Rectangle(460, 70, 40,40), "+", "", GuiFrame);
button.OnPressed = () =>
{
lastUser = Character.Controlled;
if (nextServerLogWriteTime == null)
{
nextServerLogWriteTime = Math.Max(lastServerLogWriteTime + 1.0f, (float)Timing.TotalTime);
}
unsentChanges = true;
ShutDownTemp += 100.0f;
return false;
};
autoTempTickBox = new GUITickBox(new Rectangle(410, 170, 20, 20), "Automatic temperature control", Alignment.TopLeft, GuiFrame);
autoTempTickBox.OnSelected = ToggleAutoTemp;
button = new GUIButton(new Rectangle(210, 290, 40, 40), "+", "", GuiFrame);
button.OnPressed = () =>
{
lastUser = Character.Controlled;
if (nextServerLogWriteTime == null)
{
nextServerLogWriteTime = Math.Max(lastServerLogWriteTime + 1.0f, (float)Timing.TotalTime);
}
unsentChanges = true;
FissionRate += 1.0f;
return false;
};
button = new GUIButton(new Rectangle(210, 340, 40, 40), "-", "", GuiFrame);
button.OnPressed = () =>
{
lastUser = Character.Controlled;
if (nextServerLogWriteTime == null)
{
nextServerLogWriteTime = Math.Max(lastServerLogWriteTime + 1.0f, (float)Timing.TotalTime);
}
unsentChanges = true;
FissionRate -= 1.0f;
return false;
};
button = new GUIButton(new Rectangle(500, 290, 40, 40), "+", "", GuiFrame);
button.OnPressed = () =>
{
lastUser = Character.Controlled;
if (nextServerLogWriteTime == null)
{
nextServerLogWriteTime = Math.Max(lastServerLogWriteTime + 1.0f, (float)Timing.TotalTime);
}
unsentChanges = true;
CoolingRate += 1.0f;
return false;
};
button = new GUIButton(new Rectangle(500, 340, 40, 40), "-", "", GuiFrame);
button.OnPressed = () =>
{
lastUser = Character.Controlled;
if (nextServerLogWriteTime == null)
{
nextServerLogWriteTime = Math.Max(lastServerLogWriteTime + 1.0f, (float)Timing.TotalTime);
}
unsentChanges = true;
CoolingRate -= 1.0f;
return false;
};
}
public override void Update(float deltaTime, Camera cam)
{
if (GameMain.Server != null && nextServerLogWriteTime != null)
{
if (Timing.TotalTime >= (float)nextServerLogWriteTime)
{
GameServer.Log(lastUser + " adjusted reactor settings: " +
"Temperature: " + (int)temperature +
", Fission rate: " + (int)fissionRate +
", Cooling rate: " + (int)coolingRate +
", Cooling rate: " + coolingRate +
", Shutdown temp: " + shutDownTemp +
(autoTemp ? ", Autotemp ON" : ", Autotemp OFF"),
ServerLog.MessageType.ItemInteraction);
nextServerLogWriteTime = null;
lastServerLogWriteTime = (float)Timing.TotalTime;
}
}
ApplyStatusEffects(ActionType.OnActive, deltaTime, null);
fissionRate = Math.Min(fissionRate, AvailableFuel);
float heat = 80 * fissionRate * (AvailableFuel/2000.0f);
float heatDissipation = 50 * coolingRate + Math.Max(ExtraCooling, 5.0f);
float deltaTemp = (((heat - heatDissipation) * 5) - temperature) / 10000.0f;
Temperature = temperature + deltaTemp;
if (temperature>fireTemp && temperature-deltaTemp<fireTemp)
{
Vector2 baseVel = Rand.Vector(300.0f);
for (int i = 0; i < 10; i++)
{
var particle = GameMain.ParticleManager.CreateParticle("spark", item.WorldPosition,
baseVel + Rand.Vector(100.0f), 0.0f, item.CurrentHull);
if (particle != null) particle.Size *= Rand.Range(0.5f, 1.0f);
}
new FireSource(item.WorldPosition);
}
if (temperature > meltDownTemp)
{
MeltDown();
return;
}
else if (temperature == 0.0f)
{
if (powerUpTask == null || powerUpTask.IsFinished)
{
powerUpTask = new PropertyTask(item, IsRunning, 50.0f, "Power up the reactor");
}
}
load = 0.0f;
List<Connection> connections = item.Connections;
if (connections != null && connections.Count > 0)
{
foreach (Connection connection in connections)
{
if (!connection.IsPower) continue;
foreach (Connection recipient in connection.Recipients)
{
Item it = recipient.Item as Item;
if (it == null) continue;
PowerTransfer pt = it.GetComponent<PowerTransfer>();
if (pt == null) continue;
load = Math.Max(load,pt.PowerLoad);
}
}
}
//item.Condition -= temperature * deltaTime * 0.00005f;
if (temperature > shutDownTemp)
{
CoolingRate += 0.5f;
FissionRate -= 0.5f;
}
else if (autoTemp)
{
//take deltaTemp into account to slow down the change in temperature when getting closer to the desired value
float target = temperature + deltaTemp * 100.0f;
//-1.0f in order to gradually turn down both rates when the target temperature is reached
FissionRate += (MathHelper.Clamp(load - target, -10.0f, 10.0f) - 1.0f) * deltaTime;
CoolingRate += (MathHelper.Clamp(target - load, -5.0f, 5.0f) - 1.0f) * deltaTime;
}
//the power generated by the reactor is equal to the temperature
currPowerConsumption = -temperature*powerPerTemp;
if (item.CurrentHull != null)
{
//the sound can be heard from 20 000 display units away when running at full power
item.CurrentHull.SoundRange = Math.Max(temperature * 2, item.CurrentHull.AiTarget.SoundRange);
}
UpdateGraph(deltaTime);
ExtraCooling = 0.0f;
AvailableFuel = 0.0f;
item.SendSignal(0, ((int)temperature).ToString(), "temperature_out", null);
sendUpdateTimer = Math.Max(sendUpdateTimer - deltaTime, 0.0f);
if (unsentChanges && sendUpdateTimer<= 0.0f)
{
if (GameMain.Server != null)
{
item.CreateServerEvent(this);
}
else if (GameMain.Client != null)
{
item.CreateClientEvent(this);
}
sendUpdateTimer = NetworkUpdateInterval;
unsentChanges = false;
}
}
public override void UpdateBroken(float deltaTime, Camera cam)
{
base.UpdateBroken(deltaTime, cam);
Temperature -= deltaTime * 1000.0f;
FissionRate -= deltaTime * 10.0f;
CoolingRate -= deltaTime * 10.0f;
currPowerConsumption = -temperature;
UpdateGraph(deltaTime);
ExtraCooling = 0.0f;
}
private void UpdateGraph(float deltaTime)
{
graphTimer += deltaTime * 1000.0f;
if (graphTimer > updateGraphInterval)
{
UpdateGraph(fissionRateGraph, fissionRate);
UpdateGraph(coolingRateGraph, coolingRate);
UpdateGraph(tempGraph, temperature);
UpdateGraph(loadGraph, load);
graphTimer = 0.0f;
}
}
private void MeltDown()
{
if (item.Condition <= 0.0f) return;
GameServer.Log("Reactor meltdown!", ServerLog.MessageType.ItemInteraction);
new RepairTask(item, 60.0f, "Reactor meltdown!");
item.Condition = 0.0f;
var containedItems = item.ContainedItems;
if (containedItems == null) return;
foreach (Item containedItem in item.ContainedItems)
{
if (containedItem == null) continue;
containedItem.Condition = 0.0f;
}
}
public override bool Pick(Character picker)
{
return picker != null;
}
public void Draw(SpriteBatch spriteBatch, bool editing = false)
{
GUI.DrawRectangle(spriteBatch,
new Vector2(item.Rect.X + item.Rect.Width / 2 - 6, -item.Rect.Y + 29),
new Vector2(12, 42), Color.Black);
if (temperature > 0)
GUI.DrawRectangle(spriteBatch,
new Vector2(item.Rect.X + item.Rect.Width / 2 - 5, -item.Rect.Y + 30 + (40.0f * (1.0f - temperature / 10000.0f))),
new Vector2(10, 40 * (temperature / 10000.0f)), new Color(temperature / 10000.0f, 1.0f - (temperature / 10000.0f), 0.0f, 1.0f), true);
}
public override bool AIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective)
{
switch (objective.Option.ToLowerInvariant())
{
case "power up":
float tempDiff = load - temperature;
shutDownTemp = Math.Min(load + 1000.0f, 7500.0f);
//temperature too high/low
if (Math.Abs(tempDiff)>500.0f)
{
AutoTemp = false;
FissionRate += deltaTime * 100.0f * Math.Sign(tempDiff);
CoolingRate -= deltaTime * 100.0f * Math.Sign(tempDiff);
}
//temperature OK
else
{
AutoTemp = true;
}
break;
case "shutdown":
shutDownTemp = 0.0f;
break;
}
return false;
}
public override void DrawHUD(SpriteBatch spriteBatch, Character character)
{
IsActive = true;
int x = GuiFrame.Rect.X;
int y = GuiFrame.Rect.Y;
GuiFrame.Draw(spriteBatch);
float xOffset = graphTimer / updateGraphInterval;
//GUI.DrawRectangle(spriteBatch, new Rectangle(x, y, width, height), Color.Black, true);
GUI.Font.DrawString(spriteBatch, "Output: " + (int)temperature + " kW",
new Vector2(x + 450, y + 30), Color.Red);
GUI.Font.DrawString(spriteBatch, "Grid load: " + (int)load + " kW",
new Vector2(x + 600, y + 30), Color.Yellow);
float maxLoad = 0.0f;
foreach (float loadVal in loadGraph)
{
maxLoad = Math.Max(maxLoad, loadVal);
}
DrawGraph(tempGraph, spriteBatch,
new Rectangle(x + 30, y + 30, 400, 250), Math.Max(10000.0f, maxLoad), xOffset, Color.Red);
DrawGraph(loadGraph, spriteBatch,
new Rectangle(x + 30, y + 30, 400, 250), Math.Max(10000.0f, maxLoad), xOffset, Color.Yellow);
GUI.Font.DrawString(spriteBatch, "Shutdown Temperature: " + (int)shutDownTemp, new Vector2(x + 450, y + 80), Color.White);
//GUI.Font.DrawString(spriteBatch, "Automatic Temperature Control: " + ((autoTemp) ? "ON" : "OFF"), new Vector2(x + 450, y + 180), Color.White);
y += 300;
GUI.Font.DrawString(spriteBatch, "Fission rate: " + (int)fissionRate + " %", new Vector2(x + 30, y), Color.White);
DrawGraph(fissionRateGraph, spriteBatch,
new Rectangle(x + 30, y + 30, 200, 100), 100.0f, xOffset, Color.Orange);
GUI.Font.DrawString(spriteBatch, "Cooling rate: " + (int)coolingRate + " %", new Vector2(x + 320, y), Color.White);
DrawGraph(coolingRateGraph, spriteBatch,
new Rectangle(x + 320, y + 30, 200, 100), 100.0f, xOffset, Color.LightBlue);
//y = y - 260;
}
public override void AddToGUIUpdateList()
{
GuiFrame.AddToGUIUpdateList();
}
public override void UpdateHUD(Character character)
{
GuiFrame.Update(1.0f / 60.0f);
}
private bool ToggleAutoTemp(GUITickBox tickBox)
{
unsentChanges = true;
autoTemp = tickBox.Selected;
return true;
}
static void UpdateGraph<T>(IList<T> graph, T newValue)
{
for (int i = graph.Count - 1; i > 0; i--)
{
graph[i] = graph[i - 1];
}
graph[0] = newValue;
}
static void DrawGraph(IList<float> graph, SpriteBatch spriteBatch, Rectangle rect, float maxVal, float xOffset, Color color)
{
float lineWidth = (float)rect.Width / (float)(graph.Count - 2);
float yScale = (float)rect.Height / maxVal;
GUI.DrawRectangle(spriteBatch, rect, Color.White);
Vector2 prevPoint = new Vector2(rect.Right, rect.Bottom - (graph[1] + (graph[0] - graph[1]) * xOffset) * yScale);
float currX = rect.Right - ((xOffset - 1.0f) * lineWidth);
for (int i = 1; i < graph.Count - 1; i++)
{
currX -= lineWidth;
Vector2 newPoint = new Vector2(currX, rect.Bottom - graph[i] * yScale);
GUI.DrawLine(spriteBatch, prevPoint, newPoint - new Vector2(1.0f, 0), color);
prevPoint = newPoint;
}
Vector2 lastPoint = new Vector2(rect.X,
rect.Bottom - (graph[graph.Count - 1] + (graph[graph.Count - 2] - graph[graph.Count - 1]) * xOffset) * yScale);
GUI.DrawLine(spriteBatch, prevPoint, lastPoint, color);
}
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power)
{
switch (connection.Name)
{
case "shutdown":
if (shutDownTemp > 0.0f)
{
unsentChanges = true;
shutDownTemp = 0.0f;
}
break;
}
}
public void ClientWrite(NetBuffer msg, object[] extraData = null)
{
msg.Write(autoTemp);
msg.WriteRangedSingle(shutDownTemp, 0.0f, 10000.0f, 15);
msg.WriteRangedSingle(coolingRate, 0.0f, 100.0f, 8);
msg.WriteRangedSingle(fissionRate, 0.0f, 100.0f, 8);
correctionTimer = CorrectionDelay;
}
public void ServerRead(ClientNetObject type, NetBuffer msg, Client c)
{
bool autoTemp = msg.ReadBoolean();
float shutDownTemp = msg.ReadRangedSingle(0.0f, 10000.0f, 15);
float coolingRate = msg.ReadRangedSingle(0.0f, 100.0f, 8);
float fissionRate = msg.ReadRangedSingle(0.0f, 100.0f, 8);
if (!item.CanClientAccess(c)) return;
AutoTemp = autoTemp;
ShutDownTemp = shutDownTemp;
CoolingRate = coolingRate;
FissionRate = fissionRate;
lastUser = c.Character;
if (nextServerLogWriteTime == null)
{
nextServerLogWriteTime = Math.Max(lastServerLogWriteTime + 1.0f, (float)Timing.TotalTime);
}
//need to create a server event to notify all clients of the changed state
unsentChanges = true;
}
public void ServerWrite(NetBuffer msg, Client c, object[] extraData = null)
{
msg.WriteRangedSingle(temperature, 0.0f, 10000.0f, 16);
msg.Write(autoTemp);
msg.WriteRangedSingle(shutDownTemp, 0.0f, 10000.0f, 15);
msg.WriteRangedSingle(coolingRate, 0.0f, 100.0f, 8);
msg.WriteRangedSingle(fissionRate, 0.0f, 100.0f, 8);
}
public void ClientRead(ServerNetObject type, NetBuffer msg, float sendingTime)
{
if (correctionTimer > 0.0f)
{
StartDelayedCorrection(type, msg.ExtractBits(16 + 1 + 15 + 8 + 8), sendingTime);
return;
}
Temperature = msg.ReadRangedSingle(0.0f, 10000.0f, 16);
AutoTemp = msg.ReadBoolean();
ShutDownTemp = msg.ReadRangedSingle(0.0f, 10000.0f, 15);
CoolingRate = msg.ReadRangedSingle(0.0f, 100.0f, 8);
FissionRate = msg.ReadRangedSingle(0.0f, 100.0f, 8);
}
}
}
@@ -0,0 +1,662 @@
using Barotrauma.Networking;
using FarseerPhysics;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using Voronoi2;
namespace Barotrauma.Items.Components
{
class Steering : Powered, IServerSerializable, IClientSerializable
{
private const float AutopilotRayCastInterval = 0.5f;
private Vector2 currVelocity;
private Vector2 targetVelocity;
private GUITickBox autopilotTickBox, maintainPosTickBox;
private GUITickBox levelEndTickBox, levelStartTickBox;
private bool autoPilot;
private Vector2? posToMaintain;
private SteeringPath steeringPath;
private PathFinder pathFinder;
private float networkUpdateTimer;
private bool unsentChanges;
private float autopilotRayCastTimer;
private Vector2 avoidStrength;
private float neutralBallastLevel;
public bool AutoPilot
{
get { return autoPilot; }
set
{
if (value == autoPilot) return;
autoPilot = value;
autopilotTickBox.Selected = value;
maintainPosTickBox.Enabled = autoPilot;
levelEndTickBox.Enabled = autoPilot;
levelStartTickBox.Enabled = autoPilot;
if (autoPilot)
{
if (pathFinder == null) pathFinder = new PathFinder(WayPoint.WayPointList, false);
ToggleMaintainPosition(maintainPosTickBox);
}
else
{
maintainPosTickBox.Selected = false;
levelEndTickBox.Selected = false;
levelStartTickBox.Selected = false;
posToMaintain = null;
}
}
}
public bool MaintainPos
{
get { return maintainPosTickBox.Selected; }
set { maintainPosTickBox.Selected = value; }
}
[Editable, HasDefaultValue(0.5f, true)]
public float NeutralBallastLevel
{
get { return neutralBallastLevel; }
set
{
neutralBallastLevel = MathHelper.Clamp(value, 0.0f, 1.0f);
}
}
public Vector2 TargetVelocity
{
get { return targetVelocity;}
set
{
if (!MathUtils.IsValid(value)) return;
targetVelocity.X = MathHelper.Clamp(value.X, -100.0f, 100.0f);
targetVelocity.Y = MathHelper.Clamp(value.Y, -100.0f, 100.0f);
}
}
public SteeringPath SteeringPath
{
get { return steeringPath; }
}
public Steering(Item item, XElement element)
: base(item, element)
{
IsActive = true;
autopilotTickBox = new GUITickBox(new Rectangle(0,25,20,20), "Autopilot", Alignment.TopLeft, GuiFrame);
autopilotTickBox.OnSelected = (GUITickBox box) =>
{
AutoPilot = box.Selected;
unsentChanges = true;
return true;
};
maintainPosTickBox = new GUITickBox(new Rectangle(5, 50, 15, 15), "Maintain position", Alignment.TopLeft, GUI.SmallFont, GuiFrame);
maintainPosTickBox.Enabled = false;
maintainPosTickBox.OnSelected = ToggleMaintainPosition;
levelStartTickBox = new GUITickBox(
new Rectangle(5, 70, 15, 15),
GameMain.GameSession == null ? "" : ToolBox.LimitString(GameMain.GameSession.StartLocation.Name, 20),
Alignment.TopLeft, GUI.SmallFont, GuiFrame);
levelStartTickBox.Enabled = false;
levelStartTickBox.OnSelected = SelectDestination;
levelEndTickBox = new GUITickBox(
new Rectangle(5, 90, 15, 15),
GameMain.GameSession == null ? "" : ToolBox.LimitString(GameMain.GameSession.EndLocation.Name, 20),
Alignment.TopLeft, GUI.SmallFont, GuiFrame);
levelEndTickBox.Enabled = false;
levelEndTickBox.OnSelected = SelectDestination;
}
public override void Update(float deltaTime, Camera cam)
{
if (unsentChanges)
{
networkUpdateTimer -= deltaTime;
if (networkUpdateTimer <= 0.0f)
{
if (GameMain.Client != null)
{
item.CreateClientEvent(this);
correctionTimer = CorrectionDelay;
}
else if (GameMain.Server != null)
{
item.CreateServerEvent(this);
}
networkUpdateTimer = 0.5f;
unsentChanges = false;
}
}
if (voltage < minVoltage && powerConsumption > 0.0f) return;
if (autoPilot)
{
UpdateAutoPilot(deltaTime);
}
item.SendSignal(0, targetVelocity.X.ToString(CultureInfo.InvariantCulture), "velocity_x_out", null);
float targetLevel = -targetVelocity.Y;
targetLevel += (neutralBallastLevel - 0.5f) * 100.0f;
item.SendSignal(0, targetLevel.ToString(CultureInfo.InvariantCulture), "velocity_y_out", null);
voltage -= deltaTime;
}
public override void DrawHUD(SpriteBatch spriteBatch, Character character)
{
//if (voltage < minVoltage) return;
int width = GuiFrame.Rect.Width, height = GuiFrame.Rect.Height;
int x = GuiFrame.Rect.X;
int y = GuiFrame.Rect.Y;
GuiFrame.Draw(spriteBatch);
if (voltage < minVoltage && powerConsumption > 0.0f) return;
Rectangle velRect = new Rectangle(x + 20, y + 20, width - 40, height - 40);
//GUI.DrawRectangle(spriteBatch, velRect, Color.White, false);
if (item.Submarine != null && Level.Loaded != null)
{
Vector2 realWorldVelocity = ConvertUnits.ToDisplayUnits(item.Submarine.Velocity * Physics.DisplayToRealWorldRatio) * 3.6f;
float realWorldDepth = Math.Abs(item.Submarine.Position.Y - Level.Loaded.Size.Y) * Physics.DisplayToRealWorldRatio;
GUI.DrawString(spriteBatch, new Vector2(x + 20, y + height - 65),
"Velocity: " + (int)realWorldVelocity.X + " km/h", Color.LightGreen, null, 0, GUI.SmallFont);
GUI.DrawString(spriteBatch, new Vector2(x + 20, y + height - 50),
"Descent velocity: " + -(int)realWorldVelocity.Y + " km/h", Color.LightGreen, null, 0, GUI.SmallFont);
GUI.DrawString(spriteBatch, new Vector2(x + 20, y + height - 30),
"Depth: " + (int)realWorldDepth + " m", Color.LightGreen, null, 0, GUI.SmallFont);
}
GUI.DrawLine(spriteBatch,
new Vector2(velRect.Center.X,velRect.Center.Y),
new Vector2(velRect.Center.X + currVelocity.X, velRect.Center.Y - currVelocity.Y),
Color.Gray);
Vector2 targetVelPos = new Vector2(velRect.Center.X + targetVelocity.X, velRect.Center.Y - targetVelocity.Y);
GUI.DrawLine(spriteBatch,
new Vector2(velRect.Center.X, velRect.Center.Y),
targetVelPos,
Color.LightGray);
GUI.DrawRectangle(spriteBatch, new Rectangle((int)targetVelPos.X - 5, (int)targetVelPos.Y - 5, 10, 10), Color.White);
if (Vector2.Distance(PlayerInput.MousePosition, new Vector2(velRect.Center.X, velRect.Center.Y)) < 200.0f)
{
GUI.DrawRectangle(spriteBatch, new Rectangle((int)targetVelPos.X -10, (int)targetVelPos.Y - 10, 20, 20), Color.Red);
}
}
public override void AddToGUIUpdateList()
{
GuiFrame.AddToGUIUpdateList();
}
public override void UpdateHUD(Character character)
{
GuiFrame.Update(1.0f / 60.0f);
if (Vector2.Distance(PlayerInput.MousePosition, new Vector2(GuiFrame.Rect.Center.X, GuiFrame.Rect.Center.Y)) < 200.0f)
{
if (PlayerInput.LeftButtonHeld())
{
TargetVelocity = PlayerInput.MousePosition - new Vector2(GuiFrame.Rect.Center.X, GuiFrame.Rect.Center.Y);
targetVelocity.Y = -targetVelocity.Y;
unsentChanges = true;
}
}
}
private void UpdateAutoPilot(float deltaTime)
{
if (posToMaintain != null)
{
SteerTowardsPosition((Vector2)posToMaintain);
return;
}
autopilotRayCastTimer -= deltaTime;
steeringPath.CheckProgress(ConvertUnits.ToSimUnits(item.Submarine.WorldPosition), 10.0f);
if (autopilotRayCastTimer <= 0.0f && steeringPath.NextNode != null)
{
Vector2 diff = Vector2.Normalize(ConvertUnits.ToSimUnits(steeringPath.NextNode.Position - item.Submarine.WorldPosition));
bool nextVisible = true;
for (int x = -1; x < 2; x += 2)
{
for (int y = -1; y < 2; y += 2)
{
Vector2 cornerPos =
new Vector2(item.Submarine.Borders.Width * x, item.Submarine.Borders.Height * y) / 2.0f;
cornerPos = ConvertUnits.ToSimUnits(cornerPos * 1.2f + item.Submarine.WorldPosition);
float dist = Vector2.Distance(cornerPos, steeringPath.NextNode.SimPosition);
if (Submarine.PickBody(cornerPos, cornerPos + diff * dist, null, Physics.CollisionLevel) == null) continue;
nextVisible = false;
x = 2;
y = 2;
}
}
if (nextVisible) steeringPath.SkipToNextNode();
autopilotRayCastTimer = AutopilotRayCastInterval;
}
if (steeringPath.CurrentNode != null)
{
SteerTowardsPosition(steeringPath.CurrentNode.WorldPosition);
}
float avoidRadius = Math.Max(item.Submarine.Borders.Width, item.Submarine.Borders.Height) * 2.0f;
avoidRadius = Math.Max(avoidRadius, 2000.0f);
Vector2 newAvoidStrength = Vector2.Zero;
//steer away from nearby walls
var closeCells = Level.Loaded.GetCells(item.Submarine.WorldPosition, 4);
foreach (VoronoiCell cell in closeCells)
{
foreach (GraphEdge edge in cell.edges)
{
var intersection = MathUtils.GetLineIntersection(edge.point1, edge.point2, item.Submarine.WorldPosition, cell.Center);
if (intersection != null)
{
Vector2 diff = item.Submarine.WorldPosition - (Vector2)intersection;
//far enough -> ignore
if (diff.Length() > avoidRadius) continue;
float dot = item.Submarine.Velocity == Vector2.Zero ?
0.0f : Vector2.Dot(item.Submarine.Velocity, -Vector2.Normalize(diff));
//not heading towards the wall -> ignore
if (dot < 0.5) continue;
Vector2 change = (Vector2.Normalize(diff) * Math.Max((avoidRadius - diff.Length()), 0.0f)) / avoidRadius;
newAvoidStrength += change * dot;
}
}
}
avoidStrength = Vector2.Lerp(avoidStrength, newAvoidStrength, deltaTime * 10.0f);
targetVelocity += avoidStrength * 100.0f;
//steer away from other subs
foreach (Submarine sub in Submarine.Loaded)
{
if (sub == item.Submarine) continue;
if (item.Submarine.DockedTo.Contains(sub)) continue;
float thisSize = Math.Max(item.Submarine.Borders.Width, item.Submarine.Borders.Height);
float otherSize = Math.Max(sub.Borders.Width, sub.Borders.Height);
Vector2 diff = item.Submarine.WorldPosition - sub.WorldPosition;
float dist = diff == Vector2.Zero ? 0.0f : diff.Length();
//far enough -> ignore
if (dist > thisSize + otherSize) continue;
diff = Vector2.Normalize(diff);
float dot = item.Submarine.Velocity == Vector2.Zero ?
0.0f : Vector2.Dot(Vector2.Normalize(item.Submarine.Velocity), -Vector2.Normalize(diff));
//heading away -> ignore
if (dot < 0.0f) continue;
targetVelocity += diff * 200.0f;
}
//clamp velocity magnitude to 100.0f
float velMagnitude = targetVelocity.Length();
if (velMagnitude > 100.0f)
{
targetVelocity *= 100.0f / velMagnitude;
}
}
private void UpdatePath()
{
if (pathFinder == null) pathFinder = new PathFinder(WayPoint.WayPointList, false);
Vector2 target;
if (levelEndTickBox.Selected)
{
target = ConvertUnits.ToSimUnits(Level.Loaded.EndPosition);
}
else
{
target = ConvertUnits.ToSimUnits(Level.Loaded.StartPosition);
}
steeringPath = pathFinder.FindPath(ConvertUnits.ToSimUnits(item.WorldPosition), target);
}
private void SteerTowardsPosition(Vector2 worldPosition)
{
float prediction = 10.0f;
Vector2 futurePosition = ConvertUnits.ToDisplayUnits(item.Submarine.Velocity) * prediction;
Vector2 targetSpeed = ((worldPosition - item.Submarine.WorldPosition) - futurePosition);
if (targetSpeed.Length()>500.0f)
{
targetSpeed = Vector2.Normalize(targetSpeed);
TargetVelocity = targetSpeed * 100.0f;
}
else
{
TargetVelocity = targetSpeed / 5.0f;
}
}
private bool ToggleMaintainPosition(GUITickBox tickBox)
{
unsentChanges = true;
levelStartTickBox.Selected = false;
levelEndTickBox.Selected = false;
if (item.Submarine == null)
{
posToMaintain = null;
}
else
{
posToMaintain = item.Submarine.WorldPosition;
}
tickBox.Selected = true;
return true;
}
public void SetDestinationLevelStart()
{
AutoPilot = true;
MaintainPos = false;
posToMaintain = null;
levelEndTickBox.Selected = false;
if (!levelStartTickBox.Selected)
{
levelStartTickBox.Selected = true;
UpdatePath();
}
}
public void SetDestinationLevelEnd()
{
AutoPilot = false;
MaintainPos = false;
posToMaintain = null;
levelStartTickBox.Selected = false;
if (!levelEndTickBox.Selected)
{
levelEndTickBox.Selected = true;
UpdatePath();
}
}
private bool SelectDestination(GUITickBox tickBox)
{
unsentChanges = true;
if (tickBox == levelStartTickBox)
{
levelEndTickBox.Selected = false;
}
else
{
levelStartTickBox.Selected = false;
}
maintainPosTickBox.Selected = false;
posToMaintain = null;
tickBox.Selected = true;
UpdatePath();
return true;
}
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power=0.0f)
{
if (connection.Name == "velocity_in")
{
currVelocity = ToolBox.ParseToVector2(signal, false);
}
else
{
base.ReceiveSignal(stepsTaken, signal, connection, source, sender, power);
}
}
public void ClientWrite(Lidgren.Network.NetBuffer msg, object[] extraData = null)
{
msg.Write(autoPilot);
if (!autoPilot)
{
//no need to write steering info if autopilot is controlling
msg.Write(targetVelocity.X);
msg.Write(targetVelocity.Y);
}
else
{
msg.Write(posToMaintain != null);
if (posToMaintain != null)
{
msg.Write(((Vector2)posToMaintain).X);
msg.Write(((Vector2)posToMaintain).Y);
}
else
{
msg.Write(levelStartTickBox.Selected);
}
}
}
public void ServerRead(ClientNetObject type, Lidgren.Network.NetBuffer msg, Barotrauma.Networking.Client c)
{
bool autoPilot = msg.ReadBoolean();
Vector2 newTargetVelocity = targetVelocity;
bool maintainPos = false;
Vector2? newPosToMaintain = null;
bool headingToStart = false;
if (autoPilot)
{
maintainPos = msg.ReadBoolean();
if (maintainPos)
{
newPosToMaintain = new Vector2(
msg.ReadFloat(),
msg.ReadFloat());
}
else
{
headingToStart = msg.ReadBoolean();
}
}
else
{
newTargetVelocity = new Vector2(msg.ReadFloat(), msg.ReadFloat());
}
if (!item.CanClientAccess(c)) return;
AutoPilot = autoPilot;
if (!AutoPilot)
{
targetVelocity = newTargetVelocity;
}
else
{
maintainPosTickBox.Selected = newPosToMaintain != null;
posToMaintain = newPosToMaintain;
if (posToMaintain == null)
{
levelStartTickBox.Selected = headingToStart;
levelEndTickBox.Selected = !headingToStart;
UpdatePath();
}
else
{
levelStartTickBox.Selected = false;
levelEndTickBox.Selected = false;
}
}
//notify all clients of the changed state
unsentChanges = true;
}
public void ServerWrite(Lidgren.Network.NetBuffer msg, Barotrauma.Networking.Client c, object[] extraData = null)
{
msg.Write(autoPilot);
if (!autoPilot)
{
//no need to write steering info if autopilot is controlling
msg.Write(targetVelocity.X);
msg.Write(targetVelocity.Y);
}
else
{
msg.Write(posToMaintain != null);
if (posToMaintain != null)
{
msg.Write(((Vector2)posToMaintain).X);
msg.Write(((Vector2)posToMaintain).Y);
}
else
{
msg.Write(levelStartTickBox.Selected);
}
}
}
public void ClientRead(ServerNetObject type, Lidgren.Network.NetBuffer msg, float sendingTime)
{
long msgStartPos = msg.Position;
bool autoPilot = msg.ReadBoolean();
Vector2 newTargetVelocity = targetVelocity;
bool maintainPos = false;
Vector2? newPosToMaintain = null;
bool headingToStart = false;
if (autoPilot)
{
maintainPos = msg.ReadBoolean();
if (maintainPos)
{
newPosToMaintain = new Vector2(
msg.ReadFloat(),
msg.ReadFloat());
}
else
{
headingToStart = msg.ReadBoolean();
}
}
else
{
newTargetVelocity = new Vector2(msg.ReadFloat(), msg.ReadFloat());
}
if (correctionTimer > 0.0f)
{
int msgLength = (int)(msg.Position - msgStartPos);
msg.Position = msgStartPos;
StartDelayedCorrection(type, msg.ExtractBits(msgLength), sendingTime);
return;
}
AutoPilot = autoPilot;
if (!AutoPilot)
{
targetVelocity = newTargetVelocity;
}
else
{
maintainPosTickBox.Selected = newPosToMaintain != null;
posToMaintain = newPosToMaintain;
if (posToMaintain == null)
{
levelStartTickBox.Selected = headingToStart;
levelEndTickBox.Selected = !headingToStart;
UpdatePath();
}
else
{
levelStartTickBox.Selected = false;
levelEndTickBox.Selected = false;
}
}
}
}
}
@@ -0,0 +1,32 @@
using System;
using System.Xml.Linq;
namespace Barotrauma.Items.Components
{
class Vent : ItemComponent
{
private float oxygenFlow;
public float OxygenFlow
{
get { return oxygenFlow; }
set { oxygenFlow = Math.Max(value, 0.0f); }
}
public Vent (Item item, XElement element)
: base(item, element)
{
}
public override void Update(float deltaTime, Camera cam)
{
if (item.CurrentHull == null) return;
if (item.InWater) return;
item.CurrentHull.Oxygen += oxygenFlow * deltaTime;
OxygenFlow -= deltaTime * 1000.0f;
}
}
}
@@ -0,0 +1,318 @@
using System;
using System.Xml.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Barotrauma.Networking;
using Lidgren.Network;
namespace Barotrauma.Items.Components
{
class PowerContainer : Powered, IDrawableComponent, IServerSerializable, IClientSerializable
{
//[power/min]
private float capacity;
private float charge;
private float rechargeVoltage, outputVoltage;
//how fast the battery can be recharged
private float maxRechargeSpeed;
//how fast it's currently being recharged (can be changed, so that
//charging can be slowed down or disabled if there's a shortage of power)
private float rechargeSpeed;
private float maxOutput;
private float lastSentCharge;
public float CurrPowerOutput
{
get;
private set;
}
[Editable, HasDefaultValue(10.0f, true)]
public float MaxOutPut
{
set { maxOutput = value; }
get { return maxOutput; }
}
[HasDefaultValue(10.0f, true), Editable]
public float Capacity
{
get { return capacity; }
set { capacity = Math.Max(value, 1.0f); }
}
[Editable, HasDefaultValue(0.0f, true)]
public float Charge
{
get { return charge; }
set
{
if (!MathUtils.IsValid(value)) return;
charge = MathHelper.Clamp(value, 0.0f, capacity);
if (Math.Abs(charge - lastSentCharge) / capacity > 1.0f)
{
if (GameMain.Server != null) item.CreateServerEvent(this);
lastSentCharge = charge;
}
}
}
[HasDefaultValue(10.0f, true), Editable]
public float RechargeSpeed
{
get { return rechargeSpeed; }
set
{
if (!MathUtils.IsValid(value)) return;
rechargeSpeed = MathHelper.Clamp(value, 0.0f, maxRechargeSpeed);
rechargeSpeed = MathUtils.RoundTowardsClosest(rechargeSpeed, Math.Max(maxRechargeSpeed * 0.1f, 1.0f));
}
}
[HasDefaultValue(10.0f, false), Editable]
public float MaxRechargeSpeed
{
get { return maxRechargeSpeed; }
set { maxRechargeSpeed = Math.Max(value, 1.0f); }
}
public PowerContainer(Item item, XElement element)
: base(item, element)
{
//capacity = ToolBox.GetAttributeFloat(element, "capacity", 10.0f);
//maxRechargeSpeed = ToolBox.GetAttributeFloat(element, "maxinput", 10.0f);
//maxOutput = ToolBox.GetAttributeFloat(element, "maxoutput", 10.0f);
IsActive = true;
if (canBeSelected)
{
var button = new GUIButton(new Rectangle(160, 50, 30,30), "-", "", GuiFrame);
button.OnClicked = (GUIButton btn, object obj) =>
{
RechargeSpeed = rechargeSpeed - maxRechargeSpeed * 0.1f;
if (GameMain.Server != null)
{
item.CreateServerEvent(this);
GameServer.Log(Character.Controlled + " set the recharge speed of " + item.Name + " to " + (int)((rechargeSpeed / maxRechargeSpeed) * 100.0f) + " %", ServerLog.MessageType.ItemInteraction);
}
else if (GameMain.Client != null)
{
item.CreateClientEvent(this);
correctionTimer = CorrectionDelay;
}
return true;
};
button = new GUIButton(new Rectangle(200, 50, 30, 30), "+", "", GuiFrame);
button.OnClicked = (GUIButton btn, object obj) =>
{
RechargeSpeed = rechargeSpeed + maxRechargeSpeed * 0.1f;
if (GameMain.Server != null)
{
item.CreateServerEvent(this);
GameServer.Log(Character.Controlled + " set the recharge speed of " + item.Name + " to " + (int)((rechargeSpeed / maxRechargeSpeed) * 100.0f) + " %", ServerLog.MessageType.ItemInteraction);
}
else if (GameMain.Client != null)
{
item.CreateClientEvent(this);
correctionTimer = CorrectionDelay;
}
return true;
};
}
}
public override bool Pick(Character picker)
{
if (picker == null) return false;
//picker.SelectedConstruction = (picker.SelectedConstruction == item) ? null : item;
return true;
}
public override void Update(float deltaTime, Camera cam)
{
float chargeRatio = (float)(Math.Sqrt(charge / capacity));
float gridPower = 0.0f;
float gridLoad = 0.0f;
//if (item.linkedTo.Count == 0) return;
foreach (Connection c in item.Connections)
{
if (c.Name == "power_in") continue;
foreach (Connection c2 in c.Recipients)
{
PowerTransfer pt = c2.Item.GetComponent<PowerTransfer>();
if (pt == null || !pt.IsActive) continue;
gridLoad += pt.PowerLoad;
gridPower -= pt.CurrPowerConsumption;
}
}
//float gridRate = voltage;
if (chargeRatio > 0.0f)
{
ApplyStatusEffects(ActionType.OnActive, deltaTime, null);
}
//recharge
//if (gridRate >= chargeRate)
//{
if (charge >= capacity)
{
rechargeVoltage = 0.0f;
charge = capacity;
CurrPowerConsumption = 0.0f;
}
else
{
currPowerConsumption = MathHelper.Lerp(currPowerConsumption, rechargeSpeed, 0.05f);
Charge += currPowerConsumption * rechargeVoltage / 3600.0f;
}
//}
//provide power to the grid
if (gridLoad > 0.0f)
{
if (charge <= 0.0f)
{
CurrPowerOutput = 0.0f;
charge = 0.0f;
return;
}
if (gridPower < gridLoad)
{
CurrPowerOutput = MathHelper.Lerp(
CurrPowerOutput,
Math.Min(maxOutput * chargeRatio, gridLoad),
deltaTime);
}
else
{
CurrPowerOutput = MathHelper.Lerp(CurrPowerOutput, 0.0f, deltaTime);
}
Charge -= CurrPowerOutput / 3600.0f;
}
rechargeVoltage = 0.0f;
outputVoltage = 0.0f;
}
public override bool AIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective)
{
RechargeSpeed = maxRechargeSpeed * 0.5f;
return true;
}
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power)
{
if (!connection.IsPower) return;
if (connection.Name == "power_in")
{
rechargeVoltage = power;
}
else
{
outputVoltage = power;
}
}
public void Draw(SpriteBatch spriteBatch, bool editing = false)
{
GUI.DrawRectangle(spriteBatch,
new Vector2(item.DrawPosition.X- 4, -item.DrawPosition.Y),
new Vector2(8, 22), Color.Black);
if (charge > 0)
GUI.DrawRectangle(spriteBatch,
new Vector2(item.DrawPosition.X - 3, -item.DrawPosition.Y + 1 + (20.0f * (1.0f - charge / capacity))),
new Vector2(6, 20 * (charge / capacity)), Color.Green, true);
}
public override void DrawHUD(SpriteBatch spriteBatch, Character character)
{
GuiFrame.Draw(spriteBatch);
int x = GuiFrame.Rect.X;
int y = GuiFrame.Rect.Y;
//GUI.DrawRectangle(spriteBatch, new Rectangle(x, y, width, height), Color.Black, true);
GUI.Font.DrawString(spriteBatch,
"Charge: " + (int)charge + "/" + (int)capacity + " kWm (" + (int)((charge / capacity) * 100.0f) + " %)",
new Vector2(x + 30, y + 30), Color.White);
GUI.Font.DrawString(spriteBatch, "Recharge rate: " + (int)((rechargeSpeed / maxRechargeSpeed) * 100.0f) + " %", new Vector2(x + 30, y + 95), Color.White);
}
public override void AddToGUIUpdateList()
{
GuiFrame.AddToGUIUpdateList();
}
public override void UpdateHUD(Character character)
{
GuiFrame.Update(1.0f / 60.0f);
}
public void ClientWrite(NetBuffer msg, object[] extraData)
{
msg.WriteRangedInteger(0, 10, (int)(rechargeSpeed / MaxRechargeSpeed * 10));
}
public void ServerRead(ClientNetObject type, NetBuffer msg, Client c)
{
float newRechargeSpeed = msg.ReadRangedInteger(0,10) / 10.0f * maxRechargeSpeed;
if (item.CanClientAccess(c))
{
RechargeSpeed = newRechargeSpeed;
GameServer.Log(c.Character + " set the recharge speed of "+item.Name+" to "+ (int)((rechargeSpeed / maxRechargeSpeed) * 100.0f) + " %", ServerLog.MessageType.ItemInteraction);
}
item.CreateServerEvent(this);
}
public void ServerWrite(NetBuffer msg, Client c, object[] extraData = null)
{
msg.WriteRangedInteger(0, 10, (int)(rechargeSpeed / MaxRechargeSpeed * 10));
float chargeRatio = MathHelper.Clamp(charge / capacity, 0.0f, 1.0f);
msg.WriteRangedSingle(chargeRatio, 0.0f, 1.0f, 8);
}
public void ClientRead(ServerNetObject type, NetBuffer msg, float sendingTime)
{
if (correctionTimer > 0.0f)
{
StartDelayedCorrection(type, msg.ExtractBits(4 + 8), sendingTime);
return;
}
RechargeSpeed = msg.ReadRangedInteger(0, 10) / 10.0f * maxRechargeSpeed;
Charge = msg.ReadRangedSingle(0.0f, 1.0f, 8) * capacity;
}
}
}
@@ -0,0 +1,223 @@
using System.Collections.Generic;
using System.Xml.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Globalization;
using System.Linq;
namespace Barotrauma.Items.Components
{
class PowerTransfer : Powered
{
static float fullPower;
static float fullLoad;
//private bool updated;
private int updateTimer;
const float FireProbability = 0.15f;
//affects how fast changes in power/load are carried over the grid
static float inertia = 5.0f;
static HashSet<Powered> connectedList = new HashSet<Powered>();
private List<Connection> powerConnections;
private float powerLoad;
public float PowerLoad
{
get { return powerLoad; }
}
public PowerTransfer(Item item, XElement element)
: base(item, element)
{
IsActive = true;
powerConnections = new List<Connection>();
}
public override void Update(float deltaTime, Camera cam)
{
if (updateTimer > 0)
{
//this junction box has already been updated this frame
updateTimer--;
return;
}
//reset and recalculate the power generated/consumed
//by the constructions connected to the grid
fullPower = 0.0f;
fullLoad = 0.0f;
connectedList.Clear();
CheckJunctions(deltaTime);
updateTimer = 0;
foreach (Powered p in connectedList)
{
PowerTransfer pt = p as PowerTransfer;
if (pt == null) continue;
pt.powerLoad += (fullLoad - pt.powerLoad) / inertia;
pt.currPowerConsumption += (-fullPower - pt.currPowerConsumption) / inertia;
pt.Item.SendSignal(0, "", "power", null, fullPower / Math.Max(fullLoad, 1.0f));
pt.Item.SendSignal(0, "", "power_out", null, fullPower / Math.Max(fullLoad, 1.0f));
//damage the item if voltage is too high
//(except if running as a client)
if (GameMain.Client != null) continue;
if (-pt.currPowerConsumption < Math.Max(pt.powerLoad * Rand.Range(1.9f,2.1f), 200.0f)) continue;
float prevCondition = pt.item.Condition;
pt.item.Condition -= deltaTime * 10.0f;
if (pt.item.Condition <= 0.0f && prevCondition > 0.0f)
{
sparkSounds[Rand.Int(sparkSounds.Length)].Play(1.0f, 600.0f, pt.item.WorldPosition);
Vector2 baseVel = Rand.Vector(300.0f);
for (int i = 0; i < 10; i++)
{
var particle = GameMain.ParticleManager.CreateParticle("spark", pt.item.WorldPosition,
baseVel + Rand.Vector(100.0f), 0.0f, item.CurrentHull);
if (particle != null) particle.Size *= Rand.Range(0.5f, 1.0f);
}
if (FireProbability > 0.0f && Rand.Int((int)(1.0f / FireProbability)) == 1)
{
new FireSource(pt.item.WorldPosition);
}
}
}
}
public override bool Pick(Character picker)
{
return picker != null;
}
//a recursive function that goes through all the junctions and adds up
//all the generated/consumed power of the constructions connected to the grid
private void CheckJunctions(float deltaTime)
{
updateTimer = 1;
connectedList.Add(this);
ApplyStatusEffects(ActionType.OnActive, deltaTime, null);
foreach (Connection c in powerConnections)
{
var recipients = c.Recipients;
foreach (Connection recipient in recipients)
{
if (recipient == null) continue;
Item it = recipient.Item;
if (it == null) continue;
if (it.Condition <= 0.0f) continue;
foreach (Powered powered in it.GetComponents<Powered>())
{
if (powered == null || !powered.IsActive) continue;
if (connectedList.Contains(powered)) continue;
PowerTransfer powerTransfer = powered as PowerTransfer;
if (powerTransfer != null)
{
powerTransfer.CheckJunctions(deltaTime);
continue;
}
PowerContainer powerContainer = powered as PowerContainer;
if (powerContainer != null)
{
if (recipient.Name == "power_in")
{
fullLoad += powerContainer.CurrPowerConsumption;
}
else
{
fullPower += powerContainer.CurrPowerOutput;
}
}
else
{
connectedList.Add(powered);
//positive power consumption = the construction requires power -> increase load
if (powered.CurrPowerConsumption > 0.0f)
{
fullLoad += powered.CurrPowerConsumption;
}
else if (powered.CurrPowerConsumption < 0.0f)
//negative power consumption = the construction is a
//generator/battery or another junction box
{
fullPower -= powered.CurrPowerConsumption;
}
}
}
}
}
}
public override void DrawHUD(SpriteBatch spriteBatch, Character character)
{
if (!canBeSelected) return;
int x = GuiFrame.Rect.X;
int y = GuiFrame.Rect.Y;
GuiFrame.Draw(spriteBatch);
GUI.Font.DrawString(spriteBatch, "Power: " + (int)(-currPowerConsumption) + " kW", new Vector2(x + 30, y + 30), Color.White);
GUI.Font.DrawString(spriteBatch, "Load: " + (int)powerLoad + " kW", new Vector2(x + 30, y + 100), Color.White);
}
public override void AddToGUIUpdateList()
{
GuiFrame.AddToGUIUpdateList();
}
public override void UpdateHUD(Character character)
{
GuiFrame.Update(1.0f / 60.0f);
}
public override void OnMapLoaded()
{
var connections = item.Connections;
if (connections == null)
{
IsActive = false;
return;
}
powerConnections = connections.FindAll(c => c.IsPower);
if (powerConnections.Count == 0) IsActive = false;
}
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power)
{
base.ReceiveSignal(stepsTaken, signal, connection, source, sender, power);
if (connection.Name.Length > 5 && connection.Name.Substring(0, 6).ToLowerInvariant() == "signal")
{
connection.SendSignal(stepsTaken, signal, source, sender, 0.0f);
}
}
}
}
@@ -0,0 +1,111 @@
using System;
using System.Xml.Linq;
namespace Barotrauma.Items.Components
{
class Powered : ItemComponent
{
protected static Sound[] sparkSounds;
//the amount of power CURRENTLY consumed by the item
//negative values mean that the item is providing power to connected items
protected float currPowerConsumption;
//current voltage of the item (load / power)
protected float voltage;
//the minimum voltage required for the item to work
protected float minVoltage;
//the maximum amount of power the item can draw from connected items
protected float powerConsumption;
private bool powerOnSoundPlayed;
private static Sound powerOnSound;
[Editable, HasDefaultValue(0.5f, true)]
public float MinVoltage
{
get { return minVoltage; }
set { minVoltage = value; }
}
[Editable, HasDefaultValue(0.0f, true)]
public float PowerConsumption
{
get { return powerConsumption; }
set { powerConsumption = value; }
}
[HasDefaultValue(false,true)]
public override bool IsActive
{
get { return base.IsActive; }
set
{
base.IsActive = value;
if (!value) currPowerConsumption = 0.0f;
}
}
[HasDefaultValue(0.0f, true)]
public float CurrPowerConsumption
{
get {return currPowerConsumption; }
set { currPowerConsumption = value; }
}
[HasDefaultValue(0.0f, true)]
public float Voltage
{
get { return voltage; }
set { voltage = Math.Max(0.0f, value); }
}
public Powered(Item item, XElement element)
: base(item, element)
{
if (powerOnSound == null)
{
powerOnSound = Sound.Load("Content/Items/Electricity/powerOn.ogg", false);
}
if (sparkSounds == null)
{
sparkSounds = new Sound[4];
for (int i = 0; i < 4; i++)
{
sparkSounds[i] = Sound.Load("Content/Items/Electricity/zap" + (i + 1) + ".ogg", false);
}
}
}
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power = 0)
{
if (currPowerConsumption == 0.0f) voltage = 0.0f;
if (connection.IsPower) voltage = power;
}
public override void Update(float deltaTime, Camera cam)
{
if (currPowerConsumption == 0.0f) return;
if (voltage > minVoltage)
{
if (!powerOnSoundPlayed)
{
powerOnSound.Play(1.0f, 600.0f, item.WorldPosition);
powerOnSoundPlayed = true;
}
}
else if (voltage < 0.1f)
{
powerOnSoundPlayed = false;
}
}
}
}
@@ -0,0 +1,244 @@
using System;
using System.Collections.Generic;
using System.Xml.Linq;
using FarseerPhysics;
using FarseerPhysics.Dynamics;
using FarseerPhysics.Dynamics.Contacts;
using FarseerPhysics.Dynamics.Joints;
using Microsoft.Xna.Framework;
namespace Barotrauma.Items.Components
{
class Projectile : ItemComponent
{
private float launchImpulse;
private bool doesStick;
private PrismaticJoint stickJoint;
private Body stickTarget;
private Attack attack;
public List<Body> IgnoredBodies;
public Character User;
[HasDefaultValue(10.0f, false)]
public float LaunchImpulse
{
get { return launchImpulse; }
set { launchImpulse = value; }
}
[HasDefaultValue(false, false)]
public bool CharacterUsable
{
get { return characterUsable; }
set { characterUsable = value; }
}
[HasDefaultValue(false, false)]
public bool DoesStick
{
get { return doesStick; }
set { doesStick = value; }
}
public Projectile(Item item, XElement element)
: base (item, element)
{
IgnoredBodies = new List<Body>();
foreach (XElement subElement in element.Elements())
{
if (subElement.Name.ToString().ToLowerInvariant() != "attack") continue;
attack = new Attack(subElement);
}
}
public override bool Use(float deltaTime, Character character = null)
{
if (character != null && !characterUsable) return false;
Launch(new Vector2(
(float)Math.Cos(item.body.Rotation),
(float)Math.Sin(item.body.Rotation)) * launchImpulse * item.body.Mass);
return true;
}
private void Launch(Vector2 impulse)
{
item.Drop();
item.body.Enabled = true;
item.body.ApplyLinearImpulse(impulse);
item.body.FarseerBody.OnCollision += OnProjectileCollision;
item.body.FarseerBody.IsBullet = true;
item.body.CollisionCategories = Physics.CollisionProjectile;
item.body.CollidesWith = Physics.CollisionCharacter | Physics.CollisionWall | Physics.CollisionLevel;
IsActive = true;
if (stickJoint == null || !doesStick) return;
if (stickTarget != null)
{
try
{
item.body.FarseerBody.RestoreCollisionWith(stickTarget);
}
catch (Exception e)
{
#if DEBUG
DebugConsole.ThrowError("Failed to restore collision with stickTarget", e);
#endif
}
stickTarget = null;
}
GameMain.World.RemoveJoint(stickJoint);
stickJoint = null;
}
public override void Update(float deltaTime, Camera cam)
{
ApplyStatusEffects(ActionType.OnActive, deltaTime, null);
if (stickJoint != null && stickJoint.JointTranslation < 0.01f)
{
if (stickTarget != null)
{
try
{
item.body.FarseerBody.RestoreCollisionWith(stickTarget);
}
catch
{
//the body that the projectile was stuck to has been removed
}
stickTarget = null;
}
try
{
GameMain.World.RemoveJoint(stickJoint);
}
catch
{
//the body that the projectile was stuck to has been removed
}
stickJoint = null;
IsActive = false;
}
}
private bool OnProjectileCollision(Fixture f1, Fixture f2, Contact contact)
{
if (IgnoredBodies.Contains(f2.Body)) return false;
if (f2.CollisionCategories == Physics.CollisionCharacter && !(f2.Body.UserData is Limb))
{
return false;
}
AttackResult attackResult = new AttackResult(0.0f, 0.0f);
if (attack != null)
{
var submarine = f2.Body.UserData as Submarine;
if (submarine != null)
{
item.Move(-submarine.Position);
item.Submarine = submarine;
item.body.Submarine = submarine;
//item.FindHull();
return true;
}
Limb limb;
Structure structure;
if ((limb = (f2.Body.UserData as Limb)) != null)
{
attackResult = attack.DoDamage(User, limb.character, item.WorldPosition, 1.0f);
}
else if ((structure = (f2.Body.UserData as Structure)) != null)
{
attackResult = attack.DoDamage(User, structure, item.WorldPosition, 1.0f);
}
}
ApplyStatusEffects(ActionType.OnUse, 1.0f);
ApplyStatusEffects(ActionType.OnImpact, 1.0f);
IsActive = false;
item.body.FarseerBody.OnCollision -= OnProjectileCollision;
item.body.FarseerBody.IsBullet = false;
item.body.CollisionCategories = Physics.CollisionItem;
item.body.CollidesWith = Physics.CollisionWall | Physics.CollisionLevel;
IgnoredBodies.Clear();
f2.Body.ApplyLinearImpulse(item.body.LinearVelocity * item.body.Mass);
if (attackResult.HitArmor)
{
item.body.LinearVelocity *= 0.1f;
}
else if (doesStick)
{
Vector2 normal = contact.Manifold.LocalNormal;
Vector2 dir = new Vector2(
(float)Math.Cos(item.body.Rotation),
(float)Math.Sin(item.body.Rotation));
if (Vector2.Dot(f1.Body.LinearVelocity, normal) < 0.0f) return StickToTarget(f2.Body, dir);
}
else
{
item.body.LinearVelocity *= 0.5f;
}
var containedItems = item.ContainedItems;
if (containedItems != null)
{
foreach (Item contained in containedItems)
{
if (contained.body != null)
{
contained.SetTransform(item.SimPosition, contained.body.Rotation);
}
contained.Condition = 0.0f;
}
}
return f2.CollisionCategories != Physics.CollisionCharacter;
}
private bool StickToTarget(Body targetBody, Vector2 axis)
{
if (stickJoint != null) return false;
stickJoint = new PrismaticJoint(targetBody, item.body.FarseerBody, item.body.SimPosition, axis, true);
stickJoint.MotorEnabled = true;
stickJoint.MaxMotorForce = 30.0f;
stickJoint.LimitEnabled = true;
stickJoint.UpperLimit = ConvertUnits.ToSimUnits(item.Sprite.size.X*0.7f);
item.body.FarseerBody.IgnoreCollisionWith(targetBody);
stickTarget = targetBody;
GameMain.World.AddJoint(stickJoint);
IsActive = true;
return false;
}
}
}
+333
View File
@@ -0,0 +1,333 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using FarseerPhysics;
using FarseerPhysics.Collision.Shapes;
using FarseerPhysics.Common;
using FarseerPhysics.Dynamics;
using FarseerPhysics.Dynamics.Joints;
using FarseerPhysics.Factories;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace Barotrauma.Items.Components
{
class Rope : ItemComponent, IDrawableComponent
{
PhysicsBody[] ropeBodies;
RevoluteJoint[] ropeJoints;
DistanceJoint gunJoint;
float pullForce;
Sprite sprite;
float reload;
float prevDir;
float sectionLength;
Item projectile;
Vector2 projectileAnchor;
private Vector2 BarrelPos
{
get
{
Vector2 barrelPos = Vector2.Zero;
//RangedWeapon weapon = item.GetComponent<RangedWeapon>();
//if (weapon != null) barrelPos = weapon.barrelPos;
return barrelPos;
}
}
private Vector2 TransformedBarrelPos
{
get
{
Vector2 barrelPos = Vector2.Zero;
RangedWeapon weapon = item.GetComponent<RangedWeapon>();
if (weapon != null) barrelPos = weapon.TransformedBarrelPos;
return barrelPos;
}
}
public Rope(Item item, XElement element)
: base(item, element)
{
string spritePath = ToolBox.GetAttributeString(element, "sprite", "");
if (spritePath == "") DebugConsole.ThrowError("Sprite "+spritePath+" in "+element+" not found!");
float length = ConvertUnits.ToSimUnits(ToolBox.GetAttributeFloat(element, "length", 200.0f));
pullForce = ToolBox.GetAttributeFloat(element, "pullforce", 10.0f);
projectileAnchor = Vector2.Zero;
projectileAnchor.X = ToolBox.GetAttributeFloat(element, "projectileanchorx", 0.0f);
projectileAnchor.Y = ToolBox.GetAttributeFloat(element, "projectileanchory", 0.0f);
projectileAnchor = ConvertUnits.ToSimUnits(projectileAnchor);
characterUsable = ToolBox.GetAttributeBool(element, "characterusable", false);
sprite = new Sprite(spritePath, new Vector2(0.5f,0.5f));
sectionLength = ConvertUnits.ToSimUnits(sprite.size.X);
Path ropePath = new Path();
ropePath.Add(item.body.SimPosition);
ropePath.Add(item.body.SimPosition + new Vector2(length, 0.0f));
ropePath.Closed = false;
Vertices box = PolygonTools.CreateRectangle(sectionLength, 0.05f);
PolygonShape shape = new PolygonShape(box, 5);
List<Body>ropeList = PathManager.EvenlyDistributeShapesAlongPath(GameMain.World, ropePath, shape, BodyType.Dynamic, (int)(length/sectionLength));
ropeBodies = new PhysicsBody[ropeList.Count];
for (int i = 0; i<ropeBodies.Length; i++)
{
ropeList[i].Mass = 0.01f;
ropeList[i].Enabled = false;
//only collide with the map
ropeList[i].CollisionCategories = Physics.CollisionItem;
ropeList[i].CollidesWith = Physics.CollisionWall;
//ropeBodies[i] = new PhysicsBody(ropeList[i]);
}
List<RevoluteJoint> joints = PathManager.AttachBodiesWithRevoluteJoint(GameMain.World, ropeList,
new Vector2(-sectionLength/2, 0.0f), new Vector2(sectionLength/2, 0.0f), false, false);
ropeJoints = new RevoluteJoint[joints.Count+1];
//ropeJoints[0] = JointFactory.CreateRevoluteJoint(Game1.world, item.body, ropeList[0], new Vector2(0f, -0.0f));
for (int i = 0; i < joints.Count; i++)
{
var distanceJoint = JointFactory.CreateDistanceJoint(GameMain.World, ropeList[i], ropeList[i + 1]);
distanceJoint.Length = sectionLength;
distanceJoint.DampingRatio = 1.0f;
ropeJoints[i] = joints[i];
}
}
public override void SecondaryUse(float deltaTime, Character character = null)
{
if (reload > 0.0f) return;
bool first = true;
for (int i = 0; i < ropeBodies.Length - 1; i++)
{
if (ropeBodies[i].UserData == null || (bool)ropeBodies[i].UserData) continue;
if (first)
{
Vector2 dist = gunJoint.WorldAnchorA - ropeJoints[i].WorldAnchorA;
float length = dist.Length();
if (gunJoint.Length < 0.011 && length*0.5f<sectionLength)
{
NextSection(i);
}
else
{
gunJoint.Length = Math.Max(gunJoint.Length-0.01f,0.01f);
gunJoint.Frequency = 30;
gunJoint.DampingRatio = 0.05f;
//gunJoint.MotorEnabled = true;
//gunJoint.MotorSpeed = -150.0f;
//ropeBodies[i + 1].ApplyForce(dist / length * pullForce);
//ropeJoints[0].LocalAnchorA = new Vector2(ropeJoints[0].LocalAnchorA.X-0.05f,ropeJoints[0].LocalAnchorA.Y);
//ropeBodies[i].SmoothRotate(item.body.Rotation);
}
first = false;
}
else
{
//Vector2 dist = ropeBodies[i].Position - ropeBodies[i + 1].Position;
//float length = dist.Length();
//ropeBodies[i + 1].ApplyForce(dist / length * pullForce * 0.1f);
}
}
}
private void NextSection(int i)
{
gunJoint.Length = sectionLength;
ropeBodies[i].UserData = true;
ropeBodies[i].Enabled = false;
//if (ropeJoints[0] != null) Game1.world.RemoveJoint(ropeJoints[0]);
//ropeJoints[0] = JointFactory.CreateRevoluteJoint(Game1.world,
// item.body.FarseerBody, ropeBodies[i + 1].FarseerBody,
// BarrelPos, new Vector2(-sectionLength / 2, 0.0f));
AttachGunJoint(ropeBodies[i + 1].FarseerBody);
if (i == ropeBodies.Length - 2)
{
item.Combine(projectile);
ropeBodies[ropeBodies.Length - 1].Enabled = false;
IsActive = false;
}
}
public override void Update(float deltaTime, Camera cam)
{
if (reload>0.0f) reload -= deltaTime;
//for (int i = 0; i < ropeBodies.Length - 1; i++)
//{
// if (ropeBodies[i].UserData == null || (bool)ropeBodies[i].UserData == true) continue;
// ropeBodies[i].SmoothRotate(item.body.Rotation);
//}
int len = 1;
for (int i = 0; i < ropeBodies.Length - 1; i++)
{
if (ropeBodies[i].UserData == null || (bool)ropeBodies[i].UserData) continue;
len++;
}
if (Vector2.Distance(TransformedBarrelPos, projectile.SimPosition)>len*sectionLength)
{
Vector2 stopForce = projectile.SimPosition - ropeBodies[ropeBodies.Length-1].SimPosition;
stopForce = Vector2.Normalize(stopForce);
float dotProduct = Vector2.Dot(stopForce, Vector2.Normalize(projectile.body.LinearVelocity));
if (dotProduct<0)
projectile.body.ApplyLinearImpulse(-stopForce*dotProduct * projectile.body.LinearVelocity.Length() * projectile.body.Mass);
}
if (item.body.Dir!=prevDir)
{
gunJoint.LocalAnchorA =
new Vector2(
-gunJoint.LocalAnchorA.X,
BarrelPos.Y);
prevDir = -prevDir;
}
if (!projectile.body.Enabled || !item.body.Enabled)
{
//attempt to recontain the projectile in the launcher
//eq automatically reload a spear into a speargun when picking the spear up
if (!projectile.body.Enabled) item.Combine(projectile);
foreach (PhysicsBody b in ropeBodies)
{
b.Enabled = false;
}
foreach (var joint in ropeJoints)
{
if (joint != null) joint.Enabled = false;
}
IsActive = false;
}
}
public void Draw(SpriteBatch spriteBatch, bool editing = false)
{
if (!IsActive) return;
RevoluteJoint firstJoint = null;
for (int i = 0; i<ropeBodies.Length-1; i++)
{
if (!ropeBodies[i].Enabled) continue;
if (firstJoint==null) firstJoint = ropeJoints[i];
DrawSection(spriteBatch, ropeJoints[i].WorldAnchorA, ropeJoints[i+1].WorldAnchorA, i);
}
if (gunJoint == null || firstJoint==null) return;
DrawSection(spriteBatch, gunJoint.WorldAnchorA, firstJoint.WorldAnchorA, 0);
}
private void DrawSection(SpriteBatch spriteBatch, Vector2 start, Vector2 end, int i)
{
start.Y = -start.Y;
end.Y = -end.Y;
spriteBatch.Draw(sprite.Texture,
ConvertUnits.ToDisplayUnits(start), null, Color.White,
MathUtils.VectorToAngle(end - start),
new Vector2(0.0f, sprite.size.Y / 2.0f),
new Vector2((ConvertUnits.ToDisplayUnits(Vector2.Distance(start, end))) / sprite.Texture.Width, 1.0f),
SpriteEffects.None,
sprite.Depth + i*0.00001f);
}
public void Attach(Item projectile)
{
reload = 0.5f;
IsActive = true;
this.projectile = projectile;
//Projectile projectileComponent = projectile.GetComponent<Projectile>();
foreach (PhysicsBody b in ropeBodies)
{
b.SetTransform(item.body.SimPosition, 0.0f);
b.UserData = false;
b.Enabled = true;
}
foreach (var joint in ropeJoints)
{
if (joint!=null) joint.Enabled = true;
}
ropeBodies[ropeBodies.Length - 1].SetTransform(projectile.body.SimPosition, projectile.body.Rotation);
//attach projectile to the last section of the rope
if (ropeJoints[ropeJoints.Length-1] != null) GameMain.World.RemoveJoint(ropeJoints[ropeJoints.Length-1]);
ropeJoints[ropeJoints.Length - 1] = JointFactory.CreateRevoluteJoint(GameMain.World,
projectile.body.FarseerBody, ropeBodies[ropeBodies.Length - 1].FarseerBody,
projectileAnchor, new Vector2(sectionLength / 2, 0.0f));
AttachGunJoint(ropeBodies[0].FarseerBody);
prevDir = item.body.Dir;
}
private void AttachGunJoint(Body body)
{
float rotation = (item.body.Dir == -1.0f) ? item.body.Rotation - MathHelper.Pi : item.body.Rotation;
body.SetTransform(TransformedBarrelPos, rotation);
//Vector2 axis = new Vector2((float)Math.Cos(rotation), (float)Math.Sin(rotation));
if (gunJoint != null) GameMain.World.RemoveJoint(gunJoint);
gunJoint = new DistanceJoint(item.body.FarseerBody, body, BarrelPos,
new Vector2(sectionLength / 2, 0.0f));
gunJoint.Length = sectionLength;
//gunJoint.LocalAnchorA = BarrelPos;
//gunJoint.LocalAnchorB = new Vector2(sectionLength / 2, 0.0f);
//gunJoint.UpperLimit = sectionLength;
//gunJoint.LowerLimit = 0.0f;
//gunJoint.LimitEnabled = true;
//gunJoint.ReferenceAngle = 0.0f;
GameMain.World.AddJoint(gunJoint);
}
}
}
@@ -0,0 +1,83 @@
using System;
using System.Xml.Linq;
namespace Barotrauma.Items.Components
{
class AndComponent : ItemComponent
{
protected string output, falseOutput;
//an array to keep track of how long ago a non-zero signal was received on both inputs
protected float[] timeSinceReceived;
//the output is sent if both inputs have received a signal within the timeframe
protected float timeFrame;
[InGameEditable, HasDefaultValue(0.0f, true)]
public float TimeFrame
{
get { return timeFrame; }
set
{
timeFrame = Math.Max(0.0f, value);
}
}
[InGameEditable, HasDefaultValue("1", true)]
public string Output
{
get { return output; }
set { output = value; }
}
[InGameEditable, HasDefaultValue("", true)]
public string FalseOutput
{
get { return falseOutput; }
set { falseOutput = value; }
}
public AndComponent(Item item, XElement element)
: base (item, element)
{
timeSinceReceived = new float[] { Math.Max(timeFrame*2.0f,0.1f), Math.Max(timeFrame*2.0f, 0.1f) };
//output = "1";
}
public override void Update(float deltaTime, Camera cam)
{
bool sendOutput = true;
for (int i = 0; i<timeSinceReceived.Length; i++)
{
if (timeSinceReceived[i] > timeFrame) sendOutput = false;
timeSinceReceived[i] += deltaTime;
}
string signalOut = sendOutput ? output : falseOutput;
if (string.IsNullOrEmpty(signalOut)) return;
item.SendSignal(0, signalOut, "signal_out", null);
}
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power=0.0f)
{
switch (connection.Name)
{
case "signal_in1":
if (signal == "0") return;
timeSinceReceived[0] = 0.0f;
IsActive = true;
break;
case "signal_in2":
if (signal == "0") return;
timeSinceReceived[1] = 0.0f;
IsActive = true;
break;
case "set_output":
output = signal;
break;
}
}
}
}
@@ -0,0 +1,486 @@
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Items.Components
{
class Connection
{
private static Texture2D panelTexture;
private static Sprite connector;
private static Sprite wireVertical;
//how many wires can be linked to a single connector
public const int MaxLinked = 5;
public readonly string Name;
public Wire[] Wires;
private Item item;
public readonly bool IsOutput;
private static Wire draggingConnected;
private List<StatusEffect> effects;
public readonly ushort[] wireId;
public bool IsPower
{
get;
private set;
}
public List<Connection> Recipients
{
get
{
List<Connection> recipients = new List<Connection>();
for (int i = 0; i < MaxLinked; i++)
{
if (Wires[i] == null) continue;
Connection recipient = Wires[i].OtherConnection(this);
if (recipient != null) recipients.Add(recipient);
}
return recipients;
}
}
public Item Item
{
get { return item; }
}
public Connection(XElement element, Item item)
{
if (connector == null)
{
panelTexture = Sprite.LoadTexture("Content/Items/connectionpanel.png");
connector = new Sprite(panelTexture, new Rectangle(470, 102, 19, 43), Vector2.Zero, 0.0f);
connector.Origin = new Vector2(9.5f, 10.0f);
wireVertical = new Sprite(panelTexture, new Rectangle(408, 1, 11, 102), Vector2.Zero, 0.0f);
}
this.item = item;
//recipient = new Connection[MaxLinked];
Wires = new Wire[MaxLinked];
IsOutput = (element.Name.ToString() == "output");
Name = ToolBox.GetAttributeString(element, "name", (IsOutput) ? "output" : "input");
IsPower = Name == "power_in" || Name == "power" || Name == "power_out";
effects = new List<StatusEffect>();
wireId = new ushort[MaxLinked];
foreach (XElement subElement in element.Elements())
{
switch (subElement.Name.ToString().ToLowerInvariant())
{
case "link":
int index = -1;
for (int i = 0; i < MaxLinked; i++)
{
if (wireId[i] < 1) index = i;
}
if (index == -1) break;
int id = ToolBox.GetAttributeInt(subElement, "w", 0);
if (id < 0) id = 0;
wireId[index] = (ushort)id;
break;
case "statuseffect":
effects.Add(StatusEffect.Load(subElement));
break;
}
}
}
public int FindEmptyIndex()
{
for (int i = 0; i < MaxLinked; i++)
{
if (Wires[i] == null) return i;
}
return -1;
}
//public int FindLinkIndex(Item item)
//{
// for (int i = 0; i < MaxLinked; i++)
// {
// if (item == null && recipient[i] == null) return i;
// if (recipient[i]!=null && recipient[i].item == item) return i;
// }
// return -1;
//}
public int FindWireIndex(Item wireItem)
{
for (int i = 0; i < MaxLinked; i++)
{
if (Wires[i] == null && wireItem == null) return i;
if (Wires[i] != null && Wires[i].Item == wireItem) return i;
}
return -1;
}
public void TryAddLink(Wire wire)
{
for (int i = 0; i < MaxLinked; i++)
{
if (Wires[i] == null)
{
Wires[i] = wire;
return;
}
}
}
public void AddLink(int index, Wire wire)
{
Wires[index] = wire;
}
public void SendSignal(int stepsTaken, string signal, Item source, Character sender, float power)
{
for (int i = 0; i < MaxLinked; i++)
{
if (Wires[i] == null) continue;
Connection recipient = Wires[i].OtherConnection(this);
if (recipient == null) continue;
if (recipient.item == this.item || recipient.item == source) continue;
foreach (ItemComponent ic in recipient.item.components)
{
ic.ReceiveSignal(stepsTaken, signal, recipient, item, sender, power);
}
foreach (StatusEffect effect in recipient.effects)
{
//effect.Apply(ActionType.OnUse, 1.0f, recipient.item, recipient.item);
recipient.item.ApplyStatusEffect(effect, ActionType.OnUse, 1.0f);
}
}
}
public void ClearConnections()
{
for (int i = 0; i < MaxLinked; i++)
{
if (Wires[i] == null) continue;
Wires[i].RemoveConnection(this);
Wires[i] = null;
}
}
public static void DrawConnections(SpriteBatch spriteBatch, ConnectionPanel panel, Character character)
{
int width = 400, height = 200;
int x = GameMain.GraphicsWidth / 2 - width / 2, y = GameMain.GraphicsHeight - height;
Rectangle panelRect = new Rectangle(x, y, width, height);
spriteBatch.Draw(panelTexture, panelRect, new Rectangle(0, 512 - height, width, height), Color.White);
//GUI.DrawRectangle(spriteBatch, panelRect, Color.Black, true);
bool mouseInRect = panelRect.Contains(PlayerInput.MousePosition);
int totalWireCount = 0;
foreach (Connection c in panel.Connections)
{
totalWireCount += c.Wires.Count(w => w != null);
}
Wire equippedWire = null;
//if the Character using the panel has a wire item equipped
//and the wire hasn't been connected yet, draw it on the panel
for (int i = 0; i < character.SelectedItems.Length; i++)
{
Item selectedItem = character.SelectedItems[i];
if (selectedItem == null) continue;
Wire wireComponent = selectedItem.GetComponent<Wire>();
if (wireComponent != null) equippedWire = wireComponent;
}
Vector2 rightPos = new Vector2(x + width - 130, y + 50);
Vector2 leftPos = new Vector2(x + 130, y + 50);
Vector2 rightWirePos = new Vector2(x + width - 5, y + 30);
Vector2 leftWirePos = new Vector2(x + 5, y + 30);
int wireInterval = (height - 20) / Math.Max(totalWireCount, 1);
foreach (Connection c in panel.Connections)
{
//if dragging a wire, let the Inventory know so that the wire can be
//dropped or dragged from the panel to the players inventory
if (draggingConnected != null)
{
int linkIndex = c.FindWireIndex(draggingConnected.Item);
if (linkIndex > -1)
{
Inventory.draggingItem = c.Wires[linkIndex].Item;
}
}
//outputs are drawn at the right side of the panel, inputs at the left
if (c.IsOutput)
{
c.Draw(spriteBatch, panel.Item, rightPos,
new Vector2(rightPos.X - GUI.SmallFont.MeasureString(c.Name).X - 20, rightPos.Y + 3),
rightWirePos,
mouseInRect, equippedWire,
wireInterval);
rightPos.Y += 30;
rightWirePos.Y += c.Wires.Count(w => w != null) * wireInterval;
}
else
{
c.Draw(spriteBatch, panel.Item, leftPos,
new Vector2(leftPos.X + 20, leftPos.Y - 12),
leftWirePos,
mouseInRect, equippedWire,
wireInterval);
leftPos.Y += 30;
leftWirePos.Y += c.Wires.Count(w => w != null) * wireInterval;
//leftWireX -= wireInterval;
}
}
if (draggingConnected != null)
{
DrawWire(spriteBatch, draggingConnected, draggingConnected.Item, PlayerInput.MousePosition, new Vector2(x + width / 2, y + height), mouseInRect, null);
if (!PlayerInput.LeftButtonHeld())
{
if (GameMain.Client != null)
{
panel.Item.CreateClientEvent<ConnectionPanel>(panel);
}
else if (GameMain.Server != null)
{
panel.Item.CreateServerEvent<ConnectionPanel>(panel);
}
draggingConnected = null;
}
}
//if the Character using the panel has a wire item equipped
//and the wire hasn't been connected yet, draw it on the panel
if (equippedWire != null)
{
if (panel.Connections.Find(c => c.Wires.Contains(equippedWire)) == null)
{
DrawWire(spriteBatch, equippedWire, equippedWire.Item,
new Vector2(x + width / 2, y + height - 100),
new Vector2(x + width / 2, y + height), mouseInRect, null);
if (draggingConnected == equippedWire) Inventory.draggingItem = equippedWire.Item;
}
}
//stop dragging a wire item if cursor is outside the panel
if (mouseInRect) Inventory.draggingItem = null;
spriteBatch.Draw(panelTexture, panelRect, new Rectangle(0, 0, width, height), Color.White);
}
private void Draw(SpriteBatch spriteBatch, Item item, Vector2 position, Vector2 labelPos, Vector2 wirePosition, bool mouseIn, Wire equippedWire, float wireInterval)
{
//spriteBatch.DrawString(GUI.SmallFont, Name, new Vector2(labelPos.X, labelPos.Y-10), Color.White);
GUI.DrawString(spriteBatch, labelPos, Name, IsPower ? Color.Red : Color.White, Color.Black, 0, GUI.SmallFont);
GUI.DrawRectangle(spriteBatch, new Rectangle((int)position.X - 10, (int)position.Y - 10, 20, 20), Color.White);
spriteBatch.Draw(panelTexture, position - new Vector2(16.0f, 16.0f), new Rectangle(64, 256, 32, 32), Color.White);
for (int i = 0; i < MaxLinked; i++)
{
if (Wires[i] == null || Wires[i].Hidden || draggingConnected == Wires[i]) continue;
Connection recipient = Wires[i].OtherConnection(this);
DrawWire(spriteBatch, Wires[i], (recipient == null) ? Wires[i].Item : recipient.item, position, wirePosition, mouseIn, equippedWire);
wirePosition.Y += wireInterval;
}
if (draggingConnected != null && Vector2.Distance(position, PlayerInput.MousePosition) < 13.0f)
{
spriteBatch.Draw(panelTexture, position - new Vector2(21.5f, 21.5f), new Rectangle(106, 250, 43, 43), Color.White);
if (!PlayerInput.LeftButtonHeld())
{
//find an empty cell for the new connection
int index = FindWireIndex(null);
if (index > -1 && !Wires.Contains(draggingConnected))
{
bool alreadyConnected = draggingConnected.IsConnectedTo(item);
draggingConnected.RemoveConnection(item);
if (draggingConnected.Connect(this, !alreadyConnected, true)) Wires[index] = draggingConnected;
}
}
}
int screwIndex = (position.Y % 60 < 30) ? 0 : 1;
if (Wires.Any(w => w != null && w != draggingConnected))
{
spriteBatch.Draw(panelTexture, position - new Vector2(16.0f, 16.0f), new Rectangle(screwIndex * 32, 256, 32, 32), Color.White);
}
}
private static void DrawWire(SpriteBatch spriteBatch, Wire wire, Item item, Vector2 end, Vector2 start, bool mouseIn, Wire equippedWire)
{
if (draggingConnected == wire)
{
if (!mouseIn) return;
end = PlayerInput.MousePosition;
start.X = (start.X + end.X) / 2.0f;
}
int textX = (int)start.X;
if (start.X < end.X)
textX -= 10;
else
textX += 10;
bool canDrag = equippedWire == null || equippedWire == wire;
float alpha = canDrag ? 1.0f : 0.5f;
bool mouseOn =
canDrag &&
((PlayerInput.MousePosition.X > Math.Min(start.X, end.X) &&
PlayerInput.MousePosition.X < Math.Max(start.X, end.X) &&
MathUtils.LineToPointDistance(start, end, PlayerInput.MousePosition) < 6) ||
Vector2.Distance(end, PlayerInput.MousePosition) < 20.0f ||
new Rectangle((start.X < end.X) ? textX - 100 : textX, (int)start.Y - 5, 100, 14).Contains(PlayerInput.MousePosition));
string label = wire.Locked ? item.Name + "\n(Locked)" : item.Name;
GUI.DrawString(spriteBatch,
new Vector2(start.X < end.X ? textX - GUI.SmallFont.MeasureString(label).X : textX, start.Y - 5.0f),
label,
(mouseOn ? Color.Gold : Color.White) * (wire.Locked ? 0.6f : 1.0f), Color.Black * 0.8f,
3, GUI.SmallFont);
var wireEnd = end + Vector2.Normalize(start - end) * 30.0f;
float dist = Vector2.Distance(start, wireEnd);
if (mouseOn)
{
spriteBatch.Draw(wireVertical.Texture, new Rectangle(wireEnd.ToPoint(), new Point(18, (int)dist)), wireVertical.SourceRect,
Color.Gold,
MathUtils.VectorToAngle(end - start) + MathHelper.PiOver2, //angle of line (calulated above)
new Vector2(6, 0), // point in line about which to rotate
SpriteEffects.None,
0.0f);
}
spriteBatch.Draw(wireVertical.Texture, new Rectangle(wireEnd.ToPoint(), new Point(12, (int)dist)), wireVertical.SourceRect,
wire.Item.Color * alpha,
MathUtils.VectorToAngle(end - start) + MathHelper.PiOver2, //angle of line (calulated above)
new Vector2(6, 0), // point in line about which to rotate
SpriteEffects.None,
0.0f);
connector.Draw(spriteBatch, end, Color.White, new Vector2(10.0f, 10.0f), MathUtils.VectorToAngle(end - start) + MathHelper.PiOver2);
if (draggingConnected == null && canDrag)
{
if (mouseOn)
{
ConnectionPanel.HighlightedWire = wire;
if (!wire.Locked)
{
//start dragging the wire
if (PlayerInput.LeftButtonHeld()) draggingConnected = wire;
}
}
}
}
public void Save(XElement parentElement)
{
XElement newElement = new XElement(IsOutput ? "output" : "input", new XAttribute("name", Name));
Array.Sort(Wires, delegate(Wire wire1, Wire wire2)
{
if (wire1 == null) return 1;
if (wire2 == null) return -1;
return wire1.Item.ID.CompareTo(wire2.Item.ID);
});
for (int i = 0; i < MaxLinked; i++)
{
if (Wires[i] == null) continue;
//Connection recipient = wires[i].OtherConnection(this);
//int connectionIndex = recipient.item.Connections.FindIndex(x => x == recipient);
newElement.Add(new XElement("link",
new XAttribute("w", Wires[i].Item.ID.ToString())));
}
parentElement.Add(newElement);
}
public void ConnectLinked()
{
if (wireId == null) return;
for (int i = 0; i < MaxLinked; i++)
{
if (wireId[i] == 0) continue;
Item wireItem = MapEntity.FindEntityByID(wireId[i]) as Item;
if (wireItem == null) continue;
Wires[i] = wireItem.GetComponent<Wire>();
if (Wires[i] != null)
{
if (Wires[i].Item.body != null) Wires[i].Item.body.Enabled = false;
Wires[i].Connect(this, false, false);
}
}
}
}
}
@@ -0,0 +1,269 @@
using Barotrauma.Networking;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Linq;
using System.Collections.Generic;
using System.Xml.Linq;
using Lidgren.Network;
namespace Barotrauma.Items.Components
{
class ConnectionPanel : ItemComponent, IServerSerializable, IClientSerializable
{
public static Wire HighlightedWire;
public List<Connection> Connections;
Character user;
public ConnectionPanel(Item item, XElement element)
: base(item, element)
{
Connections = new List<Connection>();
foreach (XElement subElement in element.Elements())
{
switch (subElement.Name.ToString())
{
case "input":
Connections.Add(new Connection(subElement, item));
break;
case "output":
Connections.Add(new Connection(subElement, item));
break;
}
}
IsActive = true;
}
public override void UpdateHUD(Character character)
{
if (character != Character.Controlled || character != user) return;
if (Screen.Selected != GameMain.EditMapScreen &&
character.IsKeyHit(InputType.Select) &&
character.SelectedConstruction == this.item) character.SelectedConstruction = null;
if (HighlightedWire != null)
{
HighlightedWire.Item.IsHighlighted = true;
if (HighlightedWire.Connections[0] != null && HighlightedWire.Connections[0].Item != null) HighlightedWire.Connections[0].Item.IsHighlighted = true;
if (HighlightedWire.Connections[1] != null && HighlightedWire.Connections[1].Item != null) HighlightedWire.Connections[1].Item.IsHighlighted = true;
}
}
public override void DrawHUD(SpriteBatch spriteBatch, Character character)
{
if (character != Character.Controlled || character != user) return;
HighlightedWire = null;
Connection.DrawConnections(spriteBatch, this, character);
}
public override XElement Save(XElement parentElement)
{
XElement componentElement = base.Save(parentElement);
foreach (Connection c in Connections)
{
c.Save(componentElement);
}
return componentElement;
}
public override void OnMapLoaded()
{
foreach (Connection c in Connections)
{
c.ConnectLinked();
}
}
public override void Update(float deltaTime, Camera cam)
{
if (user != null && user.SelectedConstruction != item) user = null;
}
public override bool Select(Character picker)
{
user = picker;
IsActive = true;
return true;
}
public override bool Use(float deltaTime, Character character = null)
{
if (character == null || character!=user) return false;
var powered = item.GetComponent<Powered>();
if (powered != null)
{
if (powered.Voltage < 0.1f) return false;
}
float degreeOfSuccess = DegreeOfSuccess(character);
if (Rand.Range(0.0f, 50.0f) < degreeOfSuccess) return false;
character.SetStun(5.0f);
item.ApplyStatusEffects(ActionType.OnFailure, 1.0f, character);
return true;
}
public override void Load(XElement element)
{
base.Load(element);
List<Connection> loadedConnections = new List<Connection>();
foreach (XElement subElement in element.Elements())
{
switch (subElement.Name.ToString())
{
case "input":
loadedConnections.Add(new Connection(subElement, item));
break;
case "output":
loadedConnections.Add(new Connection(subElement, item));
break;
}
}
for (int i = 0; i<loadedConnections.Count && i<Connections.Count; i++)
{
loadedConnections[i].wireId.CopyTo(Connections[i].wireId, 0);
}
}
protected override void RemoveComponentSpecific()
{
foreach (Connection c in Connections)
{
foreach (Wire wire in c.Wires)
{
if (wire == null) continue;
if (wire.OtherConnection(c) == null) //wire not connected to anything else
{
wire.Item.Drop(null);
}
else
{
wire.RemoveConnection(item);
}
}
}
}
public void ClientWrite(NetBuffer msg, object[] extraData = null)
{
foreach (Connection connection in Connections)
{
Wire[] wires = Array.FindAll(connection.Wires, w => w != null);
msg.WriteRangedInteger(0, Connection.MaxLinked, wires.Length);
for (int i = 0; i < wires.Length; i++)
{
msg.Write(wires[i].Item.ID);
}
}
}
public void ServerRead(ClientNetObject type, NetBuffer msg, Client c)
{
int[] wireCounts = new int[Connections.Count];
Wire[,] wires = new Wire[Connections.Count, Connection.MaxLinked];
//read wire IDs for each connection
for (int i = 0; i < Connections.Count; i++)
{
wireCounts[i] = msg.ReadRangedInteger(0, Connection.MaxLinked);
for (int j = 0; j < wireCounts[i]; j++)
{
ushort wireId = msg.ReadUInt16();
Item wireItem = MapEntity.FindEntityByID(wireId) as Item;
if (wireItem == null) continue;
Wire wireComponent = wireItem.GetComponent<Wire>();
if (wireComponent != null)
{
wires[i, j] = wireComponent;
}
}
}
item.CreateServerEvent<ConnectionPanel>(this);
//check if the character can access this connectionpanel
//and all the wires they're trying to connect
if (!item.CanClientAccess(c)) return;
foreach (Wire wire in wires)
{
if (wire != null)
{
//wire not found in any of the connections yet (client is trying to connect a new wire)
// -> we need to check if the client has access to it
if (!Connections.Any(connection => connection.Wires.Contains(wire)))
{
if (!wire.Item.CanClientAccess(c)) return;
}
}
}
Networking.GameServer.Log(item.Name + " rewired by " + c.Character.Name, ServerLog.MessageType.ItemInteraction);
//update the connections
for (int i = 0; i < Connections.Count; i++)
{
Connections[i].ClearConnections();
for (int j = 0; j < wireCounts[i]; j++)
{
if (wires[i, j] == null) continue;
Connections[i].Wires[j] = wires[i,j];
wires[i, j].Connect(Connections[i], true);
var otherConnection = Connections[i].Wires[j].OtherConnection(Connections[i]);
Networking.GameServer.Log(
item.Name + " (" + Connections[i].Name + ") -> " +
(otherConnection == null ? "none" : otherConnection.Item.Name + " (" + (otherConnection.Name) + ")"), ServerLog.MessageType.ItemInteraction);
}
}
}
public void ServerWrite(NetBuffer msg, Client c, object[] extraData = null)
{
ClientWrite(msg, extraData);
}
public void ClientRead(ServerNetObject type, NetBuffer msg, float sendingTime)
{
foreach (Connection connection in Connections)
{
connection.ClearConnections();
int wireCount = msg.ReadRangedInteger(0, Connection.MaxLinked);
for (int i = 0; i < wireCount; i++)
{
ushort wireId = msg.ReadUInt16();
Item wireItem = MapEntity.FindEntityByID(wireId) as Item;
if (wireItem == null) continue;
Wire wireComponent = wireItem.GetComponent<Wire>();
if (wireComponent == null) continue;
connection.Wires[i] = wireComponent;
wireComponent.Connect(connection, false);
}
}
}
}
}
@@ -0,0 +1,61 @@
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
namespace Barotrauma.Items.Components
{
class DelayComponent : ItemComponent
{
const int SignalQueueSize = 500;
//the output is sent if both inputs have received a signal within the timeframe
private TimeSpan delay;
private Queue<Tuple<string, DateTime>> signalQueue;
[InGameEditable, HasDefaultValue(1.0f, true)]
public float Delay
{
get { return (float)delay.TotalSeconds; }
set
{
float seconds = MathHelper.Clamp(value, 0.0f, 60.0f);
delay = new TimeSpan(0,0,0,0, (int)(seconds*1000.0f));
}
}
public DelayComponent(Item item, XElement element)
: base (item, element)
{
signalQueue = new Queue<Tuple<string, DateTime>>();
IsActive = true;
}
public override void Update(float deltaTime, Camera cam)
{
while (signalQueue.Any() && signalQueue.Peek().Item2 + delay <= DateTime.Now)
{
var signalOut = signalQueue.Dequeue();
item.SendSignal(0, signalOut.Item1, "signal_out", null);
}
}
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power=0.0f)
{
switch (connection.Name)
{
case "signal_in":
if (signalQueue.Count >= SignalQueueSize) return;
signalQueue.Enqueue(new Tuple<string, DateTime>(signal, DateTime.Now));
break;
}
}
}
}
@@ -0,0 +1,224 @@
using Microsoft.Xna.Framework;
using Barotrauma.Lights;
using System;
using System.Xml.Linq;
using Barotrauma.Networking;
using Lidgren.Network;
namespace Barotrauma.Items.Components
{
class LightComponent : Powered, IServerSerializable, IDrawableComponent
{
private Color lightColor;
private LightSource light;
private float range;
private float lightBrightness;
private float flicker;
private bool castShadows;
[Editable, HasDefaultValue(100.0f, true)]
public float Range
{
get { return range; }
set
{
range = MathHelper.Clamp(value, 0.0f, 2048.0f);
}
}
[Editable, HasDefaultValue(true, true)]
public bool CastShadows
{
get { return castShadows; }
set
{
castShadows = value;
if (light != null) light.CastShadows = value;
}
}
[Editable, HasDefaultValue(false, true)]
public bool IsOn
{
get { return IsActive; }
set
{
if (IsActive == value) return;
IsActive = value;
if (GameMain.Server != null) item.CreateServerEvent(this);
}
}
[HasDefaultValue(0.0f, false)]
public float Flicker
{
get { return flicker; }
set
{
flicker = MathHelper.Clamp(value, 0.0f, 1.0f);
}
}
[InGameEditable, HasDefaultValue("1.0,1.0,1.0,1.0", true)]
public string LightColor
{
get { return ToolBox.Vector4ToString(lightColor.ToVector4(), "0.00"); }
set
{
Vector4 newColor = ToolBox.ParseToVector4(value, false);
newColor.X = MathHelper.Clamp(newColor.X, 0.0f, 1.0f);
newColor.Y = MathHelper.Clamp(newColor.Y, 0.0f, 1.0f);
newColor.Z = MathHelper.Clamp(newColor.Z, 0.0f, 1.0f);
newColor.W = MathHelper.Clamp(newColor.W, 0.0f, 1.0f);
lightColor = new Color(newColor);
}
}
public override void Move(Vector2 amount)
{
light.Position += amount;
}
public override bool IsActive
{
get
{
return base.IsActive;
}
set
{
base.IsActive = value;
if (light == null) return;
light.Color = value ? lightColor : Color.Transparent;
if (!value) lightBrightness = 0.0f;
}
}
public LightComponent(Item item, XElement element)
: base (item, element)
{
light = new LightSource(element);
light.ParentSub = item.CurrentHull == null ? null : item.CurrentHull.Submarine;
light.Position = item.Position;
light.CastShadows = castShadows;
IsActive = IsOn;
//foreach (XElement subElement in element.Elements())
//{
// if (subElement.Name.ToString().ToLowerInvariant() != "sprite") continue;
// light.LightSprite = new Sprite(subElement);
// break;
//}
}
public override void Update(float deltaTime, Camera cam)
{
base.Update(deltaTime, cam);
light.ParentSub = item.Submarine;
ApplyStatusEffects(ActionType.OnActive, deltaTime);
if (item.Container != null)
{
light.Color = Color.Transparent;
return;
}
if (item.body != null)
{
light.Position = item.Position;
light.Rotation = item.body.Dir > 0.0f ? item.body.Rotation : item.body.Rotation - MathHelper.Pi;
if (!item.body.Enabled)
{
light.Color = Color.Transparent;
return;
}
}
if (powerConsumption == 0.0f)
{
voltage = 1.0f;
}
else
{
currPowerConsumption = powerConsumption;
}
if (Rand.Range(0.0f, 1.0f) < 0.05f && voltage < Rand.Range(0.0f, minVoltage))
{
if (voltage > 0.1f) sparkSounds[Rand.Int(sparkSounds.Length)].Play(1.0f, 400.0f, item.WorldPosition);
lightBrightness = 0.0f;
}
else
{
lightBrightness = MathHelper.Lerp(lightBrightness, Math.Min(voltage, 1.0f), 0.1f);
}
light.Color = lightColor * lightBrightness * (1.0f-Rand.Range(0.0f,Flicker));
light.Range = range * (float)Math.Sqrt(lightBrightness);
voltage = 0.0f;
}
public override bool Use(float deltaTime, Character character = null)
{
return true;
}
public void Draw(Microsoft.Xna.Framework.Graphics.SpriteBatch spriteBatch, bool editing = false)
{
if (light.LightSprite != null && (item.body == null || item.body.Enabled))
{
light.LightSprite.Draw(spriteBatch, new Vector2(item.DrawPosition.X, -item.DrawPosition.Y), lightColor * lightBrightness, 0.0f, 1.0f, Microsoft.Xna.Framework.Graphics.SpriteEffects.None, item.Sprite.Depth - 0.0001f);
}
}
protected override void RemoveComponentSpecific()
{
base.RemoveComponentSpecific();
light.Remove();
}
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power=0.0f)
{
base.ReceiveSignal(stepsTaken, signal, connection, source, sender, power);
switch (connection.Name)
{
case "toggle":
IsActive = !IsActive;
break;
case "set_state":
IsActive = (signal != "0");
break;
case "set_color":
LightColor = signal;
break;
}
}
public void ServerWrite(NetBuffer msg, Client c, object[] extraData = null)
{
msg.Write(IsOn);
}
public void ClientRead(ServerNetObject type, NetBuffer msg, float sendingTime)
{
IsOn = msg.ReadBoolean();
}
}
}
@@ -0,0 +1,91 @@
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Linq;
namespace Barotrauma.Items.Components
{
class MotionSensor : ItemComponent
{
private const float UpdateInterval = 0.1f;
private string output, falseOutput;
private bool motionDetected;
private float range;
private float updateTimer;
[InGameEditable, HasDefaultValue(0.0f, true)]
public float Range
{
get { return range; }
set
{
range = MathHelper.Clamp(value, 0.0f, 500.0f);
}
}
[InGameEditable, HasDefaultValue("1", true)]
public string Output
{
get { return output; }
set { output = value; }
}
[InGameEditable, HasDefaultValue("", true)]
public string FalseOutput
{
get { return falseOutput; }
set { falseOutput = value; }
}
public MotionSensor(Item item, XElement element)
: base (item, element)
{
IsActive = true;
}
public override void Update(float deltaTime, Camera cam)
{
if (motionDetected)
{
item.SendSignal(1, output, "state_out", null);
}
else if (!string.IsNullOrWhiteSpace(falseOutput))
{
item.SendSignal(1, falseOutput, "state_out", null);
}
updateTimer -= deltaTime;
if (updateTimer > 0.0f) return;
motionDetected = false;
updateTimer = UpdateInterval;
if (item.body != null && item.body.Enabled)
{
if (Math.Abs(item.body.LinearVelocity.X) > 0.01f || Math.Abs(item.body.LinearVelocity.Y) > 0.1f)
{
motionDetected = true;
}
}
foreach (Character c in Character.CharacterList)
{
if (Math.Abs(c.WorldPosition.X - item.WorldPosition.X) < range &&
Math.Abs(c.WorldPosition.Y - item.WorldPosition.Y) < range)
{
if (!c.AnimController.Limbs.Any(l => l.body.FarseerBody.Awake)) continue;
motionDetected = true;
break;
}
}
}
}
}
@@ -0,0 +1,19 @@
using System.Xml.Linq;
namespace Barotrauma.Items.Components
{
class NotComponent : ItemComponent
{
public NotComponent(Item item, XElement element)
: base (item, element)
{
}
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power=0.0f)
{
if (connection.Name != "signal_in") return;
item.SendSignal(stepsTaken, signal=="0" ? "1" : "0", "signal_out", sender);
}
}
}
@@ -0,0 +1,27 @@
using System.Xml.Linq;
namespace Barotrauma.Items.Components
{
class OrComponent : AndComponent
{
public OrComponent(Item item, XElement element)
: base (item, element)
{
}
public override void Update(float deltaTime, Camera cam)
{
bool sendOutput = false;
for (int i = 0; i<timeSinceReceived.Length; i++)
{
if (timeSinceReceived[i] <= timeFrame) sendOutput = true;
timeSinceReceived[i] += deltaTime;
}
string signalOut = sendOutput ? output : falseOutput;
if (string.IsNullOrEmpty(signalOut)) return;
item.SendSignal(0, signalOut, "signal_out", null);
}
}
}
@@ -0,0 +1,21 @@
using System.Xml.Linq;
namespace Barotrauma.Items.Components
{
class OxygenDetector : ItemComponent
{
public OxygenDetector(Item item, XElement element)
: base (item, element)
{
IsActive = true;
}
public override void Update(float deltaTime, Camera cam)
{
if (item.CurrentHull == null) return;
item.SendSignal(0, ((int)item.CurrentHull.OxygenPercentage).ToString(), "signal_out", null);
}
}
}
@@ -0,0 +1,93 @@
using System.Xml.Linq;
using System.Text.RegularExpressions;
namespace Barotrauma.Items.Components
{
class RegExFindComponent : ItemComponent
{
private string output;
private string expression;
private string receivedSignal;
private string previousReceivedSignal;
bool previousResult;
private Regex regex;
[InGameEditable, HasDefaultValue("1", true)]
public string Output
{
get { return output; }
set { output = value; }
}
[InGameEditable, HasDefaultValue("", true)]
public string Expression
{
get { return expression; }
set
{
if (expression == value) return;
expression = value;
previousReceivedSignal = "";
try
{
regex = new Regex(@expression);
}
catch
{
item.SendSignal(0, "ERROR", "signal_out", null);
return;
}
}
}
public RegExFindComponent(Item item, XElement element)
: base(item, element)
{
IsActive = true;
}
public override void Update(float deltaTime, Camera cam)
{
if (string.IsNullOrWhiteSpace(expression) || regex==null) return;
if (receivedSignal != previousReceivedSignal)
{
try
{
Match match = regex.Match(receivedSignal);
previousResult = match.Success;
previousReceivedSignal = receivedSignal;
}
catch
{
item.SendSignal(0, "ERROR", "signal_out", null);
previousResult = false;
return;
}
}
item.SendSignal(0, previousResult ? output : "0", "signal_out", null);
}
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power = 0.0f)
{
switch (connection.Name)
{
case "signal_in":
receivedSignal = signal;
break;
case "set_output":
output = signal;
break;
}
}
}
}
@@ -0,0 +1,108 @@
using Barotrauma.Networking;
using Lidgren.Network;
using System;
using System.Xml.Linq;
namespace Barotrauma.Items.Components
{
class RelayComponent : PowerTransfer, IServerSerializable
{
private float maxPower;
[Editable, HasDefaultValue(1000.0f, true)]
public float MaxPower
{
get { return maxPower; }
set
{
maxPower = Math.Max(0.0f, value);
}
}
private bool isOn;
[Editable, HasDefaultValue(false, true)]
public bool IsOn
{
get
{
return IsActive;
}
set
{
isOn = value;
IsActive = value;
if (!IsActive)
{
currPowerConsumption = 0.0f;
}
}
}
public RelayComponent(Item item, XElement element)
: base (item, element)
{
IsActive = isOn;
}
public override void Update(float deltaTime, Camera cam)
{
base.Update(deltaTime, cam);
item.SendSignal(0, IsOn ? "1" : "0", "state_out", null);
}
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power=0.0f)
{
if (connection.IsPower && connection.Name.Contains("_out")) return;
if (item.Condition <= 0.0f) return;
if (power > maxPower) item.Condition = 0.0f;
if (connection.Name.Contains("_in"))
{
if (!IsOn) return;
string outConnection = connection.Name.Contains("power_in") ? "power_out" : "signal_out";
int connectionNumber = -1;
int.TryParse(connection.Name.Substring(connection.Name.Length - 1, 1), out connectionNumber);
if (connectionNumber > 0) outConnection += connectionNumber;
item.SendSignal(stepsTaken, signal, outConnection, sender, power);
}
else if (connection.Name == "toggle")
{
SetState(!IsOn, false);
}
else if (connection.Name == "set_state")
{
SetState(signal != "0", false);
}
}
public void SetState(bool on, bool isNetworkMessage)
{
if (GameMain.Client != null && !isNetworkMessage) return;
if (on != IsOn && GameMain.Server != null)
{
item.CreateServerEvent(this);
}
IsOn = on;
}
public void ServerWrite(NetBuffer msg, Client c, object[] extraData = null)
{
msg.Write(isOn);
}
public void ClientRead(ServerNetObject type, NetBuffer msg, float sendingTime)
{
SetState(msg.ReadBoolean(), true);
}
}
}
@@ -0,0 +1,56 @@
using System.Xml.Linq;
namespace Barotrauma.Items.Components
{
class SignalCheckComponent : ItemComponent
{
private string output, falseOutput;
private string targetSignal;
[InGameEditable, HasDefaultValue("1", true)]
public string Output
{
get { return output; }
set { output = value; }
}
[InGameEditable, HasDefaultValue("0", true)]
public string FalseOutput
{
get { return falseOutput; }
set { falseOutput = value; }
}
[InGameEditable, HasDefaultValue("", true)]
public string TargetSignal
{
get { return targetSignal; }
set { targetSignal = value; }
}
public SignalCheckComponent(Item item, XElement element)
: base(item, element)
{
}
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power=0.0f)
{
switch (connection.Name)
{
case "signal_in":
string signalOut = (signal == targetSignal) ? output : falseOutput;
if (string.IsNullOrWhiteSpace(signalOut)) return;
item.SendSignal(stepsTaken, signalOut, "signal_out", sender);
break;
case "set_output":
output = signal;
break;
case "set_targetsignal":
targetSignal = signal;
break;
}
}
}
}
@@ -0,0 +1,18 @@
using System.Xml.Linq;
namespace Barotrauma.Items.Components
{
class WaterDetector : ItemComponent
{
public WaterDetector(Item item, XElement element)
: base (item, element)
{
IsActive = true;
}
public override void Update(float deltaTime, Camera cam)
{
item.SendSignal(0, item.InWater ? "1" : "0", "signal_out", null);
}
}
}
@@ -0,0 +1,123 @@
using Barotrauma.Networking;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Xml.Linq;
namespace Barotrauma.Items.Components
{
class WifiComponent : ItemComponent
{
private static List<WifiComponent> list = new List<WifiComponent>();
private float range;
private int channel;
[HasDefaultValue(20000.0f, false)]
public float Range
{
get { return range; }
set { range = Math.Max(value, 0.0f); }
}
[InGameEditable, HasDefaultValue(1, true)]
public int Channel
{
get { return channel; }
set
{
channel = MathHelper.Clamp(value, 0, 10000);
}
}
[Editable, HasDefaultValue(false, false)]
public bool LinkToChat
{
get;
set;
}
public WifiComponent(Item item, XElement element)
: base (item, element)
{
list.Add(this);
}
public bool CanTransmit()
{
return HasRequiredContainedItems(true);
}
/*public void Transmit(string signal)
{
if (!CanTransmit()) return;
var receivers = GetReceiversInRange();
foreach (WifiComponent w in receivers)
{
var connections = w.item.Connections;
w.ReceiveSignal(1, signal, connections == null ? null : connections.Find(c => c.Name == "signal_in"), item, null);
}
}*/
private List<WifiComponent> GetReceiversInRange()
{
return list.FindAll(w => w != this && w.CanReceive(this));
}
public bool CanReceive(WifiComponent sender)
{
if (!HasRequiredContainedItems(false)) return false;
if (sender == null || sender.channel != channel) return false;
return Vector2.Distance(item.WorldPosition, sender.item.WorldPosition) <= sender.Range;
}
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power=0.0f)
{
var senderComponent = source.GetComponent<WifiComponent>();
if (senderComponent != null && !CanReceive(senderComponent)) return;
if (LinkToChat)
{
if (item.ParentInventory != null &&
item.ParentInventory.Owner != null &&
item.ParentInventory.Owner == Character.Controlled &&
GameMain.NetworkMember != null)
{
if (senderComponent != null)
{
signal = ChatMessage.ApplyDistanceEffect(item, sender, signal, senderComponent.range);
}
GameMain.NetworkMember.AddChatMessage(signal, ChatMessageType.Radio);
}
}
if (connection == null) return;
switch (connection.Name)
{
case "signal_in":
var receivers = GetReceiversInRange();
foreach (WifiComponent wifiComp in receivers)
{
wifiComp.item.SendSignal(stepsTaken, signal, "signal_out", sender);
}
break;
}
}
protected override void RemoveComponentSpecific()
{
base.RemoveComponentSpecific();
list.Remove(this);
}
}
}
@@ -0,0 +1,746 @@
using Barotrauma.Networking;
using Lidgren.Network;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Items.Components
{
class Wire : ItemComponent, IDrawableComponent, IServerSerializable
{
class WireSection
{
private Vector2 start;
private float angle;
private float length;
public WireSection(Vector2 start, Vector2 end)
{
this.start = start;
angle = MathUtils.VectorToAngle(end - start);
length = Vector2.Distance(start, end);
}
public void Draw(SpriteBatch spriteBatch, Color color, Vector2 offset, float depth, float width = 0.3f)
{
spriteBatch.Draw(wireSprite.Texture,
new Vector2(start.X+offset.X, -(start.Y+offset.Y)), null, color,
-angle,
new Vector2(0.0f, wireSprite.size.Y / 2.0f),
new Vector2(length / wireSprite.Texture.Width, width),
SpriteEffects.None,
depth);
}
public static void Draw(SpriteBatch spriteBatch, Vector2 start, Vector2 end, Color color, float depth, float width = 0.3f)
{
start.Y = -start.Y;
end.Y = -end.Y;
spriteBatch.Draw(wireSprite.Texture,
start, null, color,
MathUtils.VectorToAngle(end - start),
new Vector2(0.0f, wireSprite.size.Y / 2.0f),
new Vector2((Vector2.Distance(start, end)) / wireSprite.Texture.Width, width),
SpriteEffects.None,
depth);
}
}
const float nodeDistance = 32.0f;
const float heightFromFloor = 128.0f;
static Sprite wireSprite;
private List<Vector2> nodes;
private List<WireSection> sections;
Connection[] connections;
private Vector2 newNodePos;
private static Wire draggingWire;
private static int? selectedNodeIndex;
private static int? highlightedNodeIndex;
public bool Hidden, Locked;
public Connection[] Connections
{
get { return connections; }
}
public Wire(Item item, XElement element)
: base(item, element)
{
if (wireSprite == null)
{
wireSprite = new Sprite("Content/Items/wireHorizontal.png", new Vector2(0.5f, 0.5f));
wireSprite.Depth = 0.85f;
}
nodes = new List<Vector2>();
sections = new List<WireSection>();
connections = new Connection[2];
IsActive = false;
}
public Connection OtherConnection(Connection connection)
{
if (connection == null) return null;
if (connection == connections[0]) return connections[1];
if (connection == connections[1]) return connections[0];
return null;
}
public bool IsConnectedTo(Item item)
{
if (connections[0] != null && connections[0].Item == item) return true;
return (connections[1] != null && connections[1].Item == item);
}
public void RemoveConnection(Item item)
{
for (int i = 0; i<2; i++)
{
if (connections[i]==null || connections[i].Item!=item) continue;
for (int n = 0; n< connections[i].Wires.Length; n++)
{
if (connections[i].Wires[n] != this) continue;
connections[i].Wires[n] = null;
}
connections[i] = null;
}
}
public void RemoveConnection(Connection connection)
{
if (connection == connections[0]) connections[0] = null;
if (connection == connections[1]) connections[1] = null;
}
public bool Connect(Connection newConnection, bool addNode = true, bool sendNetworkEvent = false)
{
for (int i = 0; i < 2; i++)
{
if (connections[i] == newConnection) return false;
}
if (!connections.Any(c => c == null)) return false;
for (int i = 0; i < 2; i++)
{
if (connections[i] != null && connections[i].Item == newConnection.Item)
{
addNode = false;
break;
}
}
if (item.body != null) item.Submarine = newConnection.Item.Submarine;
for (int i = 0; i < 2; i++)
{
if (connections[i] != null) continue;
connections[i] = newConnection;
if (!addNode) break;
if (newConnection.Item.Submarine == null) continue;
if (nodes.Count > 0 && nodes[0] == newConnection.Item.Position - newConnection.Item.Submarine.HiddenSubPosition) break;
if (nodes.Count > 1 && nodes[nodes.Count-1] == newConnection.Item.Position - newConnection.Item.Submarine.HiddenSubPosition) break;
if (i == 0)
{
nodes.Insert(0, newConnection.Item.Position - newConnection.Item.Submarine.HiddenSubPosition);
}
else
{
nodes.Add(newConnection.Item.Position - newConnection.Item.Submarine.HiddenSubPosition);
}
break;
}
if (connections[0] != null && connections[1] != null)
{
foreach (ItemComponent ic in item.components)
{
if (ic == this) continue;
ic.Drop(null);
}
if (item.Container != null) item.Container.RemoveContained(this.item);
if (item.body != null) item.body.Enabled = false;
IsActive = false;
CleanNodes();
}
if (sendNetworkEvent)
{
if (GameMain.Server != null)
{
item.CreateServerEvent(this);
}
//the wire is active if only one end has been connected
IsActive = connections[0] == null ^ connections[1] == null;
}
Drawable = IsActive || nodes.Any();
UpdateSections();
return true;
}
public override void Equip(Character character)
{
ClearConnections();
IsActive = true;
}
public override void Unequip(Character character)
{
ClearConnections();
IsActive = false;
}
public override void Drop(Character dropper)
{
ClearConnections();
IsActive = false;
}
public override void Update(float deltaTime, Camera cam)
{
if (nodes.Count == 0) return;
Submarine sub = null;
if (connections[0] != null && connections[0].Item.Submarine != null) sub = connections[0].Item.Submarine;
if (connections[1] != null && connections[1].Item.Submarine != null) sub = connections[1].Item.Submarine;
if ((item.Submarine != sub || sub == null) && Screen.Selected != GameMain.EditMapScreen)
{
ClearConnections();
return;
}
newNodePos = RoundNode(item.Position, item.CurrentHull) - sub.HiddenSubPosition;
}
public override bool Use(float deltaTime, Character character = null)
{
if (character == Character.Controlled && character.SelectedConstruction != null) return false;
if (newNodePos!= Vector2.Zero && nodes.Count>0 && Vector2.Distance(newNodePos, nodes[nodes.Count - 1]) > nodeDistance)
{
nodes.Add(newNodePos);
UpdateSections();
Drawable = true;
newNodePos = Vector2.Zero;
}
return true;
}
public override void SecondaryUse(float deltaTime, Character character = null)
{
if (nodes.Count > 1)
{
nodes.RemoveAt(nodes.Count - 1);
UpdateSections();
}
Drawable = IsActive || sections.Count > 0;
}
public override bool Pick(Character picker)
{
ClearConnections();
return true;
}
public override void Move(Vector2 amount)
{
if (item.IsSelected) MoveNodes(amount);
}
public List<Vector2> GetNodes()
{
return new List<Vector2>(nodes);
}
public void SetNodes(List<Vector2> nodes)
{
this.nodes = new List<Vector2>(nodes);
UpdateSections();
}
public void MoveNodes(Vector2 amount)
{
for (int i = 0; i < nodes.Count; i++)
{
nodes[i] += amount;
}
UpdateSections();
}
public void UpdateSections()
{
sections.Clear();
for (int i = 0; i < nodes.Count-1; i++)
{
sections.Add(new WireSection(nodes[i], nodes[i + 1]));
}
Drawable = IsActive || sections.Count > 0;
}
private void ClearConnections()
{
nodes.Clear();
sections.Clear();
for (int i = 0; i < 2; i++)
{
if (connections[i] == null) continue;
int wireIndex = connections[i].FindWireIndex(item);
if (wireIndex == -1) continue;
connections[i].AddLink(wireIndex, null);
connections[i] = null;
}
Drawable = sections.Count > 0;
}
private Vector2 RoundNode(Vector2 position, Hull hull)
{
if (Screen.Selected == GameMain.EditMapScreen)
{
position.X = MathUtils.Round(position.X, Submarine.GridSize.X / 2.0f);
position.Y = MathUtils.Round(position.Y, Submarine.GridSize.Y / 2.0f);
}
else
{
position.X = MathUtils.Round(position.X, nodeDistance);
if (hull == null)
{
position.Y = MathUtils.Round(position.Y, nodeDistance);
}
else
{
position.Y -= hull.Rect.Y - hull.Rect.Height;
position.Y = Math.Max(MathUtils.Round(position.Y, nodeDistance), heightFromFloor);
position.Y += hull.Rect.Y -hull.Rect.Height;
}
}
return position;
}
private void CleanNodes()
{
for (int i = nodes.Count - 2; i > 0; i--)
{
if ((nodes[i - 1].X == nodes[i].X || nodes[i - 1].Y == nodes[i].Y) &&
(nodes[i + 1].X == nodes[i].X || nodes[i + 1].Y == nodes[i].Y))
{
if (Vector2.Distance(nodes[i - 1], nodes[i]) == Vector2.Distance(nodes[i + 1], nodes[i]))
{
nodes.RemoveAt(i);
}
}
}
bool removed;
do
{
removed = false;
for (int i = nodes.Count - 2; i > 0; i--)
{
if ((nodes[i - 1].X == nodes[i].X && nodes[i + 1].X == nodes[i].X)
|| (nodes[i - 1].Y == nodes[i].Y && nodes[i + 1].Y == nodes[i].Y))
{
nodes.RemoveAt(i);
removed = true;
}
}
} while (removed);
}
public void Draw(SpriteBatch spriteBatch, bool editing)
{
if (sections.Count == 0 && !IsActive)
{
Drawable = false;
return;
}
Vector2 drawOffset = Vector2.Zero;
if (item.Submarine != null)
{
drawOffset = item.Submarine.DrawPosition + item.Submarine.HiddenSubPosition;
}
float depth = item.IsSelected ? 0.0f : wireSprite.Depth + ((item.ID % 100) * 0.00001f);
if (item.IsHighlighted)
{
foreach (WireSection section in sections)
{
section.Draw(spriteBatch, Color.Gold, drawOffset, depth + 0.00001f, 0.7f);
}
}
else if (item.IsSelected)
{
foreach (WireSection section in sections)
{
section.Draw(spriteBatch, Color.Red, drawOffset, depth + 0.00001f, 0.7f);
}
}
foreach (WireSection section in sections)
{
section.Draw(spriteBatch, item.Color, drawOffset, depth, 0.3f);
}
if (IsActive && nodes.Count > 0 && Vector2.Distance(newNodePos, nodes[nodes.Count - 1]) > nodeDistance)
{
WireSection.Draw(
spriteBatch,
new Vector2(nodes[nodes.Count - 1].X, nodes[nodes.Count - 1].Y) + drawOffset,
new Vector2(newNodePos.X, newNodePos.Y) + drawOffset,
item.Color * 0.5f,
depth,
0.3f);
}
if (!editing || !GameMain.EditMapScreen.WiringMode) return;
for (int i = 0; i < nodes.Count; i++)
{
Vector2 drawPos = nodes[i];
if (item.Submarine != null) drawPos += item.Submarine.Position + item.Submarine.HiddenSubPosition;
drawPos.Y = -drawPos.Y;
if (item.IsSelected)
{
GUI.DrawRectangle(spriteBatch, drawPos + new Vector2(-5, -5), new Vector2(10, 10), item.Color, true, 0.0f);
if (highlightedNodeIndex == i)
{
GUI.DrawRectangle(spriteBatch, drawPos + new Vector2(-10, -10), new Vector2(20, 20), Color.Red, false, 0.0f);
}
}
else
{
GUI.DrawRectangle(spriteBatch, drawPos + new Vector2(-3, -3), new Vector2(6, 6), item.Color, true, 0.0f);
}
}
}
public static void UpdateEditing(List<Wire> wires)
{
//dragging a node of some wire
if (draggingWire != null)
{
//cancel dragging
if (!PlayerInput.LeftButtonHeld())
{
draggingWire = null;
selectedNodeIndex = null;
}
//update dragging
else
{
MapEntity.DisableSelect = true;
Submarine sub = null;
if (draggingWire.connections[0] != null && draggingWire.connections[0].Item.Submarine != null) sub = draggingWire.connections[0].Item.Submarine;
if (draggingWire.connections[1] != null && draggingWire.connections[1].Item.Submarine != null) sub = draggingWire.connections[1].Item.Submarine;
Vector2 nodeWorldPos = GameMain.EditMapScreen.Cam.ScreenToWorld(PlayerInput.MousePosition) - sub.HiddenSubPosition - sub.Position;// Nodes[(int)selectedNodeIndex];
nodeWorldPos.X = MathUtils.Round(nodeWorldPos.X, Submarine.GridSize.X / 2.0f);
nodeWorldPos.Y = MathUtils.Round(nodeWorldPos.Y, Submarine.GridSize.Y / 2.0f);
draggingWire.nodes[(int)selectedNodeIndex] = nodeWorldPos;
draggingWire.UpdateSections();
MapEntity.SelectEntity(draggingWire.item);
}
return;
}
//a wire has been selected -> check if we should start dragging one of the nodes
float nodeSelectDist = 10, sectionSelectDist = 5;
highlightedNodeIndex = null;
if (MapEntity.SelectedList.Count == 1 && MapEntity.SelectedList[0] is Item)
{
Wire selectedWire = ((Item)MapEntity.SelectedList[0]).GetComponent<Wire>();
if (selectedWire != null)
{
Vector2 mousePos = GameMain.EditMapScreen.Cam.ScreenToWorld(PlayerInput.MousePosition);
if (selectedWire.item.Submarine != null) mousePos -= (selectedWire.item.Submarine.Position + selectedWire.item.Submarine.HiddenSubPosition);
//left click while holding ctrl -> check if the cursor is on a wire section,
//and add a new node if it is
if (PlayerInput.KeyDown(Keys.RightControl) || PlayerInput.KeyDown(Keys.LeftControl))
{
if (PlayerInput.LeftButtonClicked())
{
float temp = 0.0f;
int closestSectionIndex = selectedWire.GetClosestSectionIndex(mousePos, sectionSelectDist, out temp);
if (closestSectionIndex > -1)
{
selectedWire.nodes.Insert(closestSectionIndex + 1, mousePos);
selectedWire.UpdateSections();
}
}
}
else
{
//check if close enough to a node
float temp = 0.0f;
int closestIndex = selectedWire.GetClosestNodeIndex(mousePos, nodeSelectDist, out temp);
if (closestIndex > -1)
{
highlightedNodeIndex = closestIndex;
//start dragging the node
if (PlayerInput.LeftButtonHeld())
{
draggingWire = selectedWire;
selectedNodeIndex = closestIndex;
}
//remove the node
else if (PlayerInput.RightButtonClicked() && closestIndex > 0 && closestIndex < selectedWire.nodes.Count - 1)
{
selectedWire.nodes.RemoveAt(closestIndex);
selectedWire.UpdateSections();
}
}
}
}
}
//check which wire is highlighted with the cursor
Wire highlighted = null;
float closestDist = 0.0f;
foreach (Wire w in wires)
{
Vector2 mousePos = GameMain.EditMapScreen.Cam.ScreenToWorld(PlayerInput.MousePosition);
if (w.item.Submarine != null) mousePos -= (w.item.Submarine.Position + w.item.Submarine.HiddenSubPosition);
float dist = 0.0f;
if (w.GetClosestNodeIndex(mousePos, highlighted == null ? nodeSelectDist : closestDist, out dist) > -1)
{
highlighted = w;
closestDist = dist;
}
if (w.GetClosestSectionIndex(mousePos, highlighted == null ? sectionSelectDist : closestDist, out dist) > -1)
{
highlighted = w;
closestDist = dist;
}
}
if (highlighted != null)
{
highlighted.item.IsHighlighted = true;
if (PlayerInput.LeftButtonClicked())
{
MapEntity.DisableSelect = true;
MapEntity.SelectEntity(highlighted.item);
}
}
}
private int GetClosestNodeIndex(Vector2 pos, float maxDist, out float closestDist)
{
closestDist = 0.0f;
int closestIndex = -1;
for (int i = 0; i < nodes.Count; i++)
{
float dist = Vector2.Distance(nodes[i], pos);
if (dist > maxDist) continue;
if (closestIndex == -1 || dist < closestDist)
{
closestIndex = i;
closestDist = dist;
}
}
return closestIndex;
}
private int GetClosestSectionIndex(Vector2 mousePos, float maxDist, out float closestDist)
{
closestDist = 0.0f;
int closestIndex = -1;
for (int i = 0; i < nodes.Count-1; i++)
{
if ((Math.Abs(nodes[i].X - nodes[i + 1].X)<5 || Math.Sign(mousePos.X - nodes[i].X) != Math.Sign(mousePos.X - nodes[i + 1].X)) &&
(Math.Abs(nodes[i].Y - nodes[i + 1].Y)<5 || Math.Sign(mousePos.Y - nodes[i].Y) != Math.Sign(mousePos.Y - nodes[i + 1].Y)))
{
float dist = MathUtils.LineToPointDistance(nodes[i], nodes[i + 1], mousePos);
if (dist > maxDist) continue;
if (closestIndex == -1 || dist < closestDist)
{
closestIndex = i;
closestDist = dist;
}
}
}
return closestIndex;
}
public override void FlipX()
{
for (int i = 0; i < nodes.Count; i++)
{
nodes[i] = new Vector2(-nodes[i].X, nodes[i].Y);
}
UpdateSections();
}
public override XElement Save(XElement parentElement)
{
XElement componentElement = base.Save(parentElement);
if (nodes == null || nodes.Count == 0) return componentElement;
string[] nodeCoords = new string[nodes.Count * 2];
for (int i = 0; i < nodes.Count; i++)
{
nodeCoords[i * 2] = nodes[i].X.ToString(CultureInfo.InvariantCulture);
nodeCoords[i * 2 + 1] = nodes[i].Y.ToString(CultureInfo.InvariantCulture);
}
componentElement.Add(new XAttribute("nodes", string.Join(";", nodeCoords)));
return componentElement;
}
public override void Load(XElement componentElement)
{
base.Load(componentElement);
string nodeString = ToolBox.GetAttributeString(componentElement, "nodes", "");
if (nodeString == "") return;
string[] nodeCoords = nodeString.Split(';');
for (int i = 0; i < nodeCoords.Length / 2; i++)
{
float x = 0.0f, y = 0.0f;
try
{
x = float.Parse(nodeCoords[i * 2], CultureInfo.InvariantCulture);
}
catch { x = 0.0f; }
try
{
y = float.Parse(nodeCoords[i * 2 + 1], CultureInfo.InvariantCulture);
}
catch { y = 0.0f; }
nodes.Add(new Vector2(x, y));
}
Drawable = nodes.Any();
}
protected override void ShallowRemoveComponentSpecific()
{
for (int i = 0; i < 2; i++)
{
if (connections[i] == null) continue;
int wireIndex = connections[i].FindWireIndex(item);
if (wireIndex > -1)
{
connections[i].AddLink(wireIndex, null);
}
}
}
protected override void RemoveComponentSpecific()
{
ClearConnections();
base.RemoveComponentSpecific();
}
public void ServerWrite(NetBuffer msg, Client c, object[] extraData = null)
{
msg.Write((byte)Math.Min(nodes.Count, 255));
for (int i = 0; i < Math.Min(nodes.Count, 255); i++)
{
msg.Write(nodes[i].X);
msg.Write(nodes[i].Y);
}
}
public void ClientRead(ServerNetObject type, NetBuffer msg, float sendingTime)
{
nodes.Clear();
int nodeCount = msg.ReadByte();
Vector2[] nodePositions = new Vector2[nodeCount];
for (int i = 0; i < nodeCount; i++)
{
nodePositions[i] = new Vector2(msg.ReadFloat(), msg.ReadFloat());
}
if (nodePositions.Any(n => !MathUtils.IsValid(n))) return;
nodes = nodePositions.ToList();
UpdateSections();
Drawable = nodes.Any();
}
}
}
@@ -0,0 +1,75 @@
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using Microsoft.Xna.Framework;
namespace Barotrauma.Items.Components
{
class StatusHUD : ItemComponent
{
private static readonly string[] BleedingTexts = {"Minor bleeding", "Bleeding", "Bleeding heavily", "Catastrophic Bleeding"};
private static readonly string[] HealthTexts = { "No visible injuries", "Minor injuries", "Injured", "Major injuries", "Critically injured" };
private static readonly string[] OxygenTexts = { "Oxygen level normal", "Gasping for air", "Signs of oxygen deprivation", "Not breathing" };
public StatusHUD(Item item, XElement element)
: base(item, element)
{
}
public override void DrawHUD(SpriteBatch spriteBatch, Character character)
{
if (character == null) return;
GUI.DrawRectangle(spriteBatch, new Rectangle(0, 0, GameMain.GraphicsWidth, GameMain.GraphicsHeight),
Color.Green * 0.1f, true);
if (character.ClosestCharacter == null) return;
var target = character.ClosestCharacter;
Vector2 hudPos = GameMain.GameScreen.Cam.WorldToScreen(target.WorldPosition);
hudPos += Vector2.UnitX * 50.0f;
List<string> texts = new List<string>();
texts.Add(target.Name);
if (target.IsDead)
{
texts.Add("Deceased");
}
else
{
if (target.IsUnconscious) texts.Add("Unconscious");
if (target.Stun > 0.01f) texts.Add("Stunned");
int healthTextIndex = target.Health > 95.0f ? 0 :
MathHelper.Clamp((int)Math.Ceiling((1.0f - (target.Health / 200.0f + 0.5f)) * HealthTexts.Length), 0, HealthTexts.Length - 1);
texts.Add(HealthTexts[healthTextIndex]);
int oxygenTextIndex = MathHelper.Clamp((int)Math.Floor((1.0f - (target.Oxygen / 200.0f + 0.5f)) * OxygenTexts.Length), 0, OxygenTexts.Length - 1);
texts.Add(OxygenTexts[oxygenTextIndex]);
if (target.Bleeding > 0.0f)
{
int bleedingTextIndex = MathHelper.Clamp((int)Math.Floor(target.Bleeding / 4.0f) * BleedingTexts.Length, 0, BleedingTexts.Length - 1);
texts.Add(BleedingTexts[bleedingTextIndex]);
}
}
foreach (string text in texts)
{
GUI.DrawString(spriteBatch, hudPos, text, Color.LightGreen, Color.Black * 0.7f, 2);
hudPos.Y += 24.0f;
}
}
}
}
@@ -0,0 +1,418 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Xml.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using FarseerPhysics;
using Barotrauma.Networking;
using Lidgren.Network;
namespace Barotrauma.Items.Components
{
class Turret : Powered, IDrawableComponent, IServerSerializable
{
Sprite barrelSprite;
Vector2 barrelPos;
float rotation, targetRotation;
float reload, reloadTime;
float minRotation, maxRotation;
float launchImpulse;
Camera cam;
[HasDefaultValue("0,0", false)]
public string BarrelPos
{
get
{
return ToolBox.Vector2ToString(barrelPos);
}
set
{
barrelPos = ToolBox.ParseToVector2(value);
}
}
[HasDefaultValue(0.0f, false)]
public float LaunchImpulse
{
get { return launchImpulse; }
set { launchImpulse = value; }
}
[HasDefaultValue(5.0f, false)]
public float Reload
{
get { return reloadTime; }
set { reloadTime = value; }
}
[HasDefaultValue("0.0,0.0", true), Editable]
public string RotationLimits
{
get
{
Vector2 limits = new Vector2(minRotation, maxRotation);
limits.X = MathHelper.ToDegrees(limits.X);
limits.Y = MathHelper.ToDegrees(limits.Y);
return ToolBox.Vector2ToString(limits);
}
set
{
Vector2 vector = ToolBox.ParseToVector2(value);
minRotation = MathHelper.ToRadians(vector.X);
maxRotation = MathHelper.ToRadians(vector.Y);
rotation = (minRotation + maxRotation) / 2;
}
}
public Turret(Item item, XElement element)
: base(item, element)
{
IsActive = true;
string barrelSpritePath = ToolBox.GetAttributeString(element, "barrelsprite", "");
if (!string.IsNullOrWhiteSpace(barrelSpritePath))
{
if (!barrelSpritePath.Contains("/"))
{
barrelSpritePath = Path.Combine(Path.GetDirectoryName(item.Prefab.ConfigFile), barrelSpritePath);
}
barrelSprite = new Sprite(
barrelSpritePath,
ToolBox.GetAttributeVector2(element, "origin", Vector2.Zero));
}
}
public void Draw(SpriteBatch spriteBatch, bool editing = false)
{
Vector2 drawPos = new Vector2(item.Rect.X, item.Rect.Y);
if (item.Submarine != null) drawPos += item.Submarine.DrawPosition;
drawPos.Y = -drawPos.Y;
if (barrelSprite!=null)
{
barrelSprite.Draw(spriteBatch,
drawPos + barrelPos, Color.White,
rotation + MathHelper.PiOver2, 1.0f,
SpriteEffects.None, item.Sprite.Depth+0.01f);
}
if (!editing) return;
GUI.DrawLine(spriteBatch,
drawPos + barrelPos,
drawPos + barrelPos + new Vector2((float)Math.Cos(minRotation), (float)Math.Sin(minRotation))*60.0f,
Color.Green);
GUI.DrawLine(spriteBatch,
drawPos + barrelPos,
drawPos + barrelPos + new Vector2((float)Math.Cos(maxRotation), (float)Math.Sin(maxRotation)) * 60.0f,
Color.Green);
GUI.DrawLine(spriteBatch,
drawPos + barrelPos,
drawPos + barrelPos + new Vector2((float)Math.Cos((maxRotation + minRotation) / 2), (float)Math.Sin((maxRotation + minRotation) / 2)) * 60.0f,
Color.LightGreen);
}
public override void Update(float deltaTime, Camera cam)
{
this.cam = cam;
if (reload > 0.0f) reload -= deltaTime;
ApplyStatusEffects(ActionType.OnActive, deltaTime, null);
if (minRotation == maxRotation) return;
float targetMidDiff = MathHelper.WrapAngle(targetRotation - (minRotation + maxRotation) / 2.0f);
float maxDist = (maxRotation - minRotation) / 2.0f;
if (Math.Abs(targetMidDiff) > maxDist)
{
targetRotation = (targetMidDiff < 0.0f) ? minRotation : maxRotation;
}
float deltaRotation = MathHelper.WrapAngle(targetRotation-rotation);
deltaRotation = MathHelper.Clamp(deltaRotation, -0.5f, 0.5f) * 5.0f;
rotation += deltaRotation * deltaTime;
float rotMidDiff = MathHelper.WrapAngle(rotation - (minRotation + maxRotation) / 2.0f);
if (rotMidDiff < -maxDist)
{
rotation = minRotation;
}
else if (rotMidDiff > maxDist)
{
rotation = maxRotation;
}
}
public override bool Use(float deltaTime, Character character = null)
{
if (GameMain.Client != null) return false;
if (reload > 0.0f) return false;
var projectiles = GetLoadedProjectiles(true);
if (projectiles.Count == 0) return false;
if (GetAvailablePower() < powerConsumption) return false;
var batteries = item.GetConnectedComponents<PowerContainer>();
float availablePower = 0.0f;
foreach (PowerContainer battery in batteries)
{
float batteryPower = Math.Min(battery.Charge*3600.0f, battery.MaxOutPut);
float takePower = Math.Min(powerConsumption - availablePower, batteryPower);
battery.Charge -= takePower/3600.0f;
if (GameMain.Server != null)
{
battery.Item.CreateServerEvent(battery);
}
}
Launch(projectiles[0].Item);
if (character != null)
{
GameServer.Log(character.Name + " launched " + item.Name, ServerLog.MessageType.ItemInteraction);
}
return true;
}
private void Launch(Item projectile)
{
reload = reloadTime;
projectile.Drop();
projectile.body.Dir = 1.0f;
projectile.body.ResetDynamics();
projectile.body.Enabled = true;
projectile.SetTransform(ConvertUnits.ToSimUnits(new Vector2(item.WorldRect.X + barrelPos.X, item.WorldRect.Y - barrelPos.Y)), -rotation);
projectile.FindHull();
projectile.Submarine = projectile.body.Submarine;
projectile.Use((float)Timing.Step);
if (projectile.Container != null) projectile.Container.RemoveContained(projectile);
if (GameMain.Server != null)
{
GameMain.Server.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.ComponentState, item.components.IndexOf(this), projectile });
}
}
public override bool AIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective)
{
var projectiles = GetLoadedProjectiles();
if (projectiles.Count == 0 || (projectiles.Count == 1 && objective.Option.ToLowerInvariant() != "fire at will"))
{
ItemContainer container = null;
foreach (MapEntity e in item.linkedTo)
{
var containerItem = e as Item;
if (containerItem == null) continue;
container = containerItem.GetComponent<ItemContainer>();
if (container != null) break;
}
if (container == null || container.ContainableItems.Count==0) return true;
var containShellObjective = new AIObjectiveContainItem(character, container.ContainableItems[0].Names[0], container);
containShellObjective.IgnoreAlreadyContainedItems = true;
objective.AddSubObjective(containShellObjective);
return false;
}
else if (GetAvailablePower() < powerConsumption)
{
var batteries = item.GetConnectedComponents<PowerContainer>();
float lowestCharge = 0.0f;
PowerContainer batteryToLoad = null;
foreach (PowerContainer battery in batteries)
{
if (batteryToLoad == null || battery.Charge < lowestCharge)
{
batteryToLoad = battery;
lowestCharge = battery.Charge;
}
}
if (batteryToLoad == null) return true;
if (batteryToLoad.RechargeSpeed < batteryToLoad.MaxRechargeSpeed*0.4f)
{
objective.AddSubObjective(new AIObjectiveOperateItem(batteryToLoad, character, ""));
return false;
}
}
//enough shells and power
Character closestEnemy = null;
float closestDist = 3000.0f;
foreach (Character enemy in Character.CharacterList)
{
//ignore humans and characters that are inside the sub
if (enemy.IsDead || enemy.SpeciesName == "human" || enemy.AnimController.CurrentHull != null) continue;
float dist = Vector2.Distance(enemy.WorldPosition, item.WorldPosition);
if (dist < closestDist)
{
closestEnemy = enemy;
closestDist = dist;
}
}
if (closestEnemy == null) return false;
character.CursorPosition = closestEnemy.WorldPosition;
if (item.Submarine!=null) character.CursorPosition -= item.Submarine.Position;
character.SetInput(InputType.Aim, false, true);
float enemyAngle = MathUtils.VectorToAngle(closestEnemy.WorldPosition-item.WorldPosition);
float turretAngle = -rotation;
if (Math.Abs(MathUtils.GetShortestAngle(enemyAngle, turretAngle)) > 0.01f) return false;
var pickedBody = Submarine.PickBody(ConvertUnits.ToSimUnits(item.WorldPosition), closestEnemy.SimPosition, null);
if (pickedBody != null && !(pickedBody.UserData is Limb)) return false;
if (objective.Option.ToLowerInvariant() == "fire at will") Use(deltaTime, character);
return false;
}
private float GetAvailablePower()
{
var batteries = item.GetConnectedComponents<PowerContainer>();
float availablePower = 0.0f;
foreach (PowerContainer battery in batteries)
{
float batteryPower = Math.Min(battery.Charge*3600.0f, battery.MaxOutPut);
availablePower += batteryPower;
}
return availablePower;
}
protected override void RemoveComponentSpecific()
{
base.RemoveComponentSpecific();
if (barrelSprite != null) barrelSprite.Remove();
}
private List<Projectile> GetLoadedProjectiles(bool returnFirst = false)
{
List<Projectile> projectiles = new List<Projectile>();
foreach (MapEntity e in item.linkedTo)
{
var projectileContainer = e as Item;
if (projectileContainer == null) continue;
var containedItems = projectileContainer.ContainedItems;
if (containedItems == null) continue;
for (int i = 0; i < containedItems.Length; i++)
{
var projectileComponent = containedItems[i].GetComponent<Projectile>();
if (projectileComponent != null)
{
projectiles.Add(projectileComponent);
if (returnFirst) return projectiles;
}
}
}
return projectiles;
}
public override void FlipX()
{
minRotation = (float)Math.PI - minRotation;
maxRotation = (float)Math.PI - maxRotation;
var temp = minRotation;
minRotation = maxRotation;
maxRotation = temp;
while (minRotation < 0)
{
minRotation += MathHelper.TwoPi;
maxRotation += MathHelper.TwoPi;
}
}
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power)
{
switch (connection.Name)
{
case "position_in":
Vector2 receivedPos = ToolBox.ParseToVector2(signal, false);
Vector2 centerPos = new Vector2(item.WorldRect.X + barrelPos.X, item.WorldRect.Y - barrelPos.Y);
Vector2 offset = receivedPos - centerPos;
offset.Y = -offset.Y;
targetRotation = MathUtils.WrapAngleTwoPi(MathUtils.VectorToAngle(offset));
IsActive = true;
break;
case "trigger_in":
item.Use((float)Timing.Step, sender);
break;
}
}
public void ServerWrite(NetBuffer msg, Client c, object[] extraData = null)
{
//ID of the launched projectile
msg.Write(((Item)extraData[2]).ID);
}
public void ClientRead(ServerNetObject type, NetBuffer msg, float sendingTime)
{
UInt16 projectileID = msg.ReadUInt16();
Item projectile = Entity.FindEntityByID(projectileID) as Item;
if (projectile == null)
{
DebugConsole.ThrowError("Failed to launch a projectile - item with the ID \""+projectileID+" not found");
return;
}
Launch(projectile);
PlaySound(ActionType.OnUse, item.WorldPosition);
}
}
}
@@ -0,0 +1,190 @@
using Microsoft.Xna.Framework;
using System;
using System.IO;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Items.Components
{
class WearableSprite
{
public Sprite Sprite;
public bool HideLimb;
public LimbType DepthLimb;
public Wearable WearableComponent;
public WearableSprite(Wearable item, Sprite sprite, bool hideLimb, LimbType depthLimb = LimbType.None)
{
WearableComponent = item;
Sprite = sprite;
HideLimb = hideLimb;
DepthLimb = depthLimb;
}
}
class Wearable : Pickable
{
WearableSprite[] wearableSprites;
LimbType[] limbType;
Limb[] limb;
private float armorValue;
private Vector2 armorSector;
[HasDefaultValue(0.0f, false)]
public float ArmorValue
{
get { return armorValue; }
set { armorValue = MathHelper.Clamp(value, 0.0f, 100.0f); }
}
[HasDefaultValue("0.0,360.0", false)]
public string ArmorSector
{
get { return ToolBox.Vector2ToString(armorSector); }
set
{
armorSector = ToolBox.ParseToVector2(value);
armorSector.X = MathHelper.ToRadians(armorSector.X);
armorSector.Y = MathHelper.ToRadians(armorSector.Y);
}
}
public Vector2 ArmorSectorLimits
{
get { return armorSector; }
}
public Wearable (Item item, XElement element)
: base(item, element)
{
this.item = item;
var sprites = element.Elements().Where(x => x.Name.ToString() == "sprite").ToList();
int spriteCount = sprites.Count;
wearableSprites = new WearableSprite[spriteCount];
limbType = new LimbType[spriteCount];
limb = new Limb[spriteCount];
int i = 0;
foreach (XElement subElement in sprites)
{
//Rectangle sourceRect = new Rectangle(
// ToolBox.GetAttributeInt(subElement, "sourcex", 1),
// ToolBox.GetAttributeInt(subElement, "sourcey", 1),
// ToolBox.GetAttributeInt(subElement, "sourcewidth", 1),
// ToolBox.GetAttributeInt(subElement, "sourceheight", 1));
if (subElement.Attribute("texture") == null)
{
DebugConsole.ThrowError("Item \"" + item.Name + "\" doesn't have a texture specified!");
return;
}
string spritePath = subElement.Attribute("texture").Value;
spritePath = Path.GetDirectoryName( item.Prefab.ConfigFile)+"/"+spritePath;
var sprite = new Sprite(subElement, "", spritePath);
wearableSprites[i] = new WearableSprite(this, sprite, ToolBox.GetAttributeBool(subElement, "hidelimb", false),
(LimbType)Enum.Parse(typeof(LimbType),
ToolBox.GetAttributeString(subElement, "depthlimb", "None"), true));
limbType[i] = (LimbType)Enum.Parse(typeof(LimbType),
ToolBox.GetAttributeString(subElement, "limb", "Head"), true);
i++;
}
}
public override void Equip(Character character)
{
picker = character;
for (int i = 0; i < wearableSprites.Length; i++ )
{
Limb equipLimb = character.AnimController.GetLimb(limbType[i]);
if (equipLimb == null) continue;
//something is already on the limb -> unequip it
//if (equipLimb.WearingItem != null && equipLimb.WearingItem != this)
//{
// equipLimb.WearingItem.Unequip(character);
//}
//sprite[i].Depth = equipLimb.sprite.Depth - 0.001f;
item.body.Enabled = false;
IsActive = true;
limb[i] = equipLimb;
equipLimb.WearingItems.Add(wearableSprites[i]);
}
}
public override void Drop(Character dropper)
{
Unequip(picker);
base.Drop(dropper);
picker = null;
IsActive = false;
}
public override void Unequip(Character character)
{
if (picker == null) return;
for (int i = 0; i < wearableSprites.Length; i++)
{
Limb equipLimb = character.AnimController.GetLimb(limbType[i]);
if (equipLimb == null) continue;
//foreach (WearableSprite wearable in equipLimb.WearingItems)
//{
// if (wearable != wearableSprites[i]) continue;
// equipLimb.WearingItems.Remove(wearableSprites[i]);
//}
equipLimb.WearingItems.RemoveAll(w=> w!=null && w==wearableSprites[i]);
//if (equipLimb.WearingItem != this) continue;
limb[i] = null;
//equipLimb.WearingItem = null;
//equipLimb.WearingItemSprite = null;
}
IsActive = false;
}
public override void UpdateBroken(float deltaTime, Camera cam)
{
Update(deltaTime, cam);
}
public override void Update(float deltaTime, Camera cam)
{
item.SetTransform(picker.SimPosition, 0.0f);
item.SetContainedItemPositions();
ApplyStatusEffects(ActionType.OnWearing, deltaTime, picker);
PlaySound(ActionType.OnWearing, picker.WorldPosition);
}
protected override void RemoveComponentSpecific()
{
base.RemoveComponentSpecific();
foreach (WearableSprite wearableSprite in wearableSprites)
{
if (wearableSprite != null && wearableSprite.Sprite != null) wearableSprite.Sprite.Remove();
}
}
}
}