38f1ddb...178a853: v0.8.9.1, removed content folder

This commit is contained in:
Joonas Rikkonen
2019-03-18 19:46:58 +02:00
parent 38f1ddb6fe
commit 6c0679c297
1054 changed files with 151673 additions and 144931 deletions
@@ -14,17 +14,14 @@ namespace Barotrauma.Items.Components
{
partial class DockingPort : ItemComponent, IDrawableComponent, IServerSerializable
{
public static List<DockingPort> list = new List<DockingPort>();
private static List<DockingPort> list = new List<DockingPort>();
public static IEnumerable<DockingPort> List
{
get { return list; }
}
private Sprite overlaySprite;
private Vector2 distanceTolerance;
private DockingPort dockingTarget;
private float dockingState;
private int dockingDir;
private Joint joint;
private readonly Hull[] hulls = new Hull[2];
@@ -37,18 +34,15 @@ namespace Barotrauma.Items.Components
private bool docked;
public int DockingDir
{
get { return dockingDir; }
set { dockingDir = value; }
}
private float forceLockTimer;
//if the submarine isn't in the correct position to lock within this time after docking has been activated,
//force the sub to the correct position
const float ForceLockDelay = 1.0f;
public int DockingDir { get; private set; }
[Serialize("32.0,32.0", false)]
public Vector2 DistanceTolerance
{
get { return distanceTolerance; }
set { distanceTolerance = value; }
}
public Vector2 DistanceTolerance { get; set; }
[Serialize(32.0f, false)]
public float DockedDistance
@@ -64,11 +58,7 @@ namespace Barotrauma.Items.Components
set;
}
public DockingPort DockingTarget
{
get { return dockingTarget; }
set { dockingTarget = value; }
}
public DockingPort DockingTarget { get; private set; }
public bool Docked
{
@@ -80,8 +70,8 @@ namespace Barotrauma.Items.Components
{
if (!docked && value)
{
if (dockingTarget == null) AttemptDock();
if (dockingTarget == null) return;
if (DockingTarget == null) AttemptDock();
if (DockingTarget == null) return;
docked = true;
}
@@ -89,11 +79,9 @@ namespace Barotrauma.Items.Components
{
Undock();
}
//base.IsActive = value;
}
}
public DockingPort(Item item, XElement element)
: base(item, element)
{
@@ -114,37 +102,40 @@ namespace Barotrauma.Items.Components
list.Add(this);
}
public override void FlipX()
public override void FlipX(bool relativeToSub)
{
base.FlipX();
if (dockingTarget != null)
if (DockingTarget != null)
{
if (joint != null)
{
CreateJoint(joint is WeldJoint);
LinkHullsToGaps();
}
else if (dockingTarget.joint != null)
else if (DockingTarget.joint != null)
{
if (!GameMain.World.BodyList.Contains(dockingTarget.joint.BodyA) ||
!GameMain.World.BodyList.Contains(dockingTarget.joint.BodyB))
if (!GameMain.World.BodyList.Contains(DockingTarget.joint.BodyA) ||
!GameMain.World.BodyList.Contains(DockingTarget.joint.BodyB))
{
dockingTarget.CreateJoint(dockingTarget.joint is WeldJoint);
DockingTarget.CreateJoint(DockingTarget.joint is WeldJoint);
}
dockingTarget.LinkHullsToGaps();
DockingTarget.LinkHullsToGaps();
}
}
}
public override void FlipY(bool relativeToSub)
{
FlipX(relativeToSub);
}
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;
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;
}
@@ -162,8 +153,10 @@ namespace Barotrauma.Items.Components
public void Dock(DockingPort target)
{
if (item.Submarine.DockedTo.Contains(target.item.Submarine)) return;
if (dockingTarget != null)
forceLockTimer = 0.0f;
if (DockingTarget != null)
{
Undock();
}
@@ -171,7 +164,7 @@ namespace Barotrauma.Items.Components
if (target.item.Submarine == item.Submarine)
{
DebugConsole.ThrowError("Error - tried to dock a submarine to itself");
dockingTarget = null;
DockingTarget = null;
return;
}
@@ -185,27 +178,27 @@ namespace Barotrauma.Items.Components
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;
DockingTarget = target;
DockingTarget.DockingTarget = this;
docked = true;
dockingTarget.Docked = true;
DockingTarget.Docked = true;
if (Character.Controlled != null &&
(Character.Controlled.Submarine == dockingTarget.item.Submarine || Character.Controlled.Submarine == item.Submarine))
(Character.Controlled.Submarine == DockingTarget.item.Submarine || Character.Controlled.Submarine == item.Submarine))
{
GameMain.GameScreen.Cam.Shake = Vector2.Distance(dockingTarget.item.Submarine.Velocity, item.Submarine.Velocity);
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;
DockingDir = IsHorizontal ?
Math.Sign(DockingTarget.item.WorldPosition.X - item.WorldPosition.X) :
Math.Sign(DockingTarget.item.WorldPosition.Y - item.WorldPosition.Y);
DockingTarget.DockingDir = -DockingDir;
if (door != null && dockingTarget.door != null)
if (door != null && DockingTarget.door != null)
{
WayPoint myWayPoint = WayPoint.WayPointList.Find(wp => door.LinkedGap == wp.ConnectedGap);
WayPoint targetWayPoint = WayPoint.WayPointList.Find(wp => dockingTarget.door.LinkedGap == wp.ConnectedGap);
WayPoint targetWayPoint = WayPoint.WayPointList.Find(wp => DockingTarget.door.LinkedGap == wp.ConnectedGap);
if (myWayPoint != null && targetWayPoint != null)
{
@@ -222,11 +215,11 @@ namespace Barotrauma.Items.Components
}
}
public void Lock(bool isNetworkMessage)
public void Lock(bool isNetworkMessage, bool forcePosition = false)
{
if (GameMain.Client != null && !isNetworkMessage) return;
if (dockingTarget == null)
if (DockingTarget == null)
{
DebugConsole.ThrowError("Error - attempted to lock a docking port that's not connected to anything");
return;
@@ -234,15 +227,24 @@ namespace Barotrauma.Items.Components
if (!(joint is WeldJoint))
{
dockingDir = IsHorizontal ?
Math.Sign(dockingTarget.item.WorldPosition.X - item.WorldPosition.X) :
Math.Sign(dockingTarget.item.WorldPosition.Y - item.WorldPosition.Y);
dockingTarget.dockingDir = -dockingDir;
DockingDir = IsHorizontal ?
Math.Sign(DockingTarget.item.WorldPosition.X - item.WorldPosition.X) :
Math.Sign(DockingTarget.item.WorldPosition.Y - item.WorldPosition.Y);
DockingTarget.DockingDir = -DockingDir;
#if CLIENT
PlaySound(ActionType.OnSecondaryUse, item.WorldPosition);
#endif
Vector2 jointDiff = joint.WorldAnchorB - joint.WorldAnchorA;
if (item.Submarine.PhysicsBody.Mass < DockingTarget.item.Submarine.PhysicsBody.Mass ||
DockingTarget.item.Submarine.IsOutpost)
{
item.Submarine.SubBody.SetPosition(item.Submarine.SubBody.Position + ConvertUnits.ToDisplayUnits(jointDiff));
}
else if (DockingTarget.item.Submarine.PhysicsBody.Mass < item.Submarine.PhysicsBody.Mass ||
item.Submarine.IsOutpost)
{
DockingTarget.item.Submarine.SubBody.SetPosition(item.Submarine.SubBody.Position - ConvertUnits.ToDisplayUnits(jointDiff));
}
ConnectWireBetweenPorts();
CreateJoint(true);
@@ -257,7 +259,7 @@ namespace Barotrauma.Items.Components
List<MapEntity> removedEntities = item.linkedTo.Where(e => e.Removed).ToList();
foreach (MapEntity removed in removedEntities) item.linkedTo.Remove(removed);
if (!item.linkedTo.Any(e => e is Hull) && !dockingTarget.item.linkedTo.Any(e => e is Hull))
if (!item.linkedTo.Any(e => e is Hull) && !DockingTarget.item.linkedTo.Any(e => e is Hull))
{
CreateHulls();
}
@@ -273,18 +275,18 @@ namespace Barotrauma.Items.Components
}
Vector2 offset = (IsHorizontal ?
Vector2.UnitX * dockingDir :
Vector2.UnitY * dockingDir);
Vector2.UnitX * DockingDir :
Vector2.UnitY * DockingDir);
offset *= DockedDistance * 0.5f;
Vector2 pos1 = item.WorldPosition + offset;
Vector2 pos2 = dockingTarget.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,
item.Submarine.PhysicsBody.FarseerBody, DockingTarget.item.Submarine.PhysicsBody.FarseerBody,
ConvertUnits.ToSimUnits(pos1), FarseerPhysics.ConvertUnits.ToSimUnits(pos2), true);
((WeldJoint)joint).FrequencyHz = 1.0f;
@@ -292,7 +294,7 @@ namespace Barotrauma.Items.Components
else
{
var distanceJoint = JointFactory.CreateDistanceJoint(GameMain.World,
item.Submarine.PhysicsBody.FarseerBody, dockingTarget.item.Submarine.PhysicsBody.FarseerBody,
item.Submarine.PhysicsBody.FarseerBody, DockingTarget.item.Submarine.PhysicsBody.FarseerBody,
ConvertUnits.ToSimUnits(pos1), FarseerPhysics.ConvertUnits.ToSimUnits(pos2), true);
distanceJoint.Length = 0.01f;
@@ -302,7 +304,6 @@ namespace Barotrauma.Items.Components
joint = distanceJoint;
}
joint.CollideConnected = true;
}
@@ -319,12 +320,12 @@ namespace Barotrauma.Items.Components
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 (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);
wire.RemoveConnection(DockingTarget.item);
powerConnection.TryAddLink(wire);
@@ -341,7 +342,7 @@ namespace Barotrauma.Items.Components
doorBody = null;
}
Vector2 position = ConvertUnits.ToSimUnits(item.Position + (dockingTarget.door.Item.WorldPosition - item.WorldPosition));
Vector2 position = ConvertUnits.ToSimUnits(item.Position + (DockingTarget.door.Item.WorldPosition - item.WorldPosition));
if (!MathUtils.IsValid(position))
{
string errorMsg =
@@ -360,11 +361,11 @@ namespace Barotrauma.Items.Components
System.Diagnostics.Debug.Assert(doorBody == null);
doorBody = BodyFactory.CreateRectangle(GameMain.World,
dockingTarget.door.Body.width,
dockingTarget.door.Body.height,
DockingTarget.door.Body.width,
DockingTarget.door.Body.height,
1.0f,
position,
dockingTarget.door);
DockingTarget.door);
doorBody.CollisionCategories = Physics.CollisionWall;
doorBody.BodyType = BodyType.Static;
@@ -372,33 +373,32 @@ namespace Barotrauma.Items.Components
private void CreateHulls()
{
var hullRects = new Rectangle[] { item.WorldRect, dockingTarget.item.WorldRect };
var subs = new Submarine[] { item.Submarine, dockingTarget.item.Submarine };
var hullRects = new Rectangle[] { item.WorldRect, DockingTarget.item.WorldRect };
var subs = new Submarine[] { item.Submarine, DockingTarget.item.Submarine };
bodies = new Body[4];
if (dockingTarget.door != null)
if (DockingTarget.door != null)
{
CreateDoorBody();
}
if (door != null)
{
dockingTarget.CreateDoorBody();
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 = 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);
//expand hulls if needed, so there's no empty space between the sub's hulls and docking port hulls
int leftSubRightSide = int.MinValue, rightSubLeftSide = int.MaxValue;
foreach (Hull hull in Hull.hullList)
@@ -420,11 +420,10 @@ namespace Barotrauma.Items.Components
}
}
//expand left hull to the rightmost hull of the sub at the left side
//(unless the difference is more than 100 units - if the distance is very large
//there's something wrong with the positioning of the docking ports or submarine hulls)
int leftHullDiff = hullRects[0].X - leftSubRightSide;
int leftHullDiff = (hullRects[0].X - leftSubRightSide) + 5;
if (leftHullDiff > 0)
{
if (leftHullDiff > 100)
@@ -438,7 +437,7 @@ namespace Barotrauma.Items.Components
}
}
int rightHullDiff = rightSubLeftSide - hullRects[1].Right;
int rightHullDiff = (rightSubLeftSide - hullRects[1].Right) + 5;
if (rightHullDiff > 0)
{
if (rightHullDiff > 100)
@@ -455,7 +454,7 @@ namespace Barotrauma.Items.Components
for (int i = 0; i < 2; i++)
{
hullRects[i].Location -= MathUtils.ToPoint((subs[i].WorldPosition - subs[i].HiddenSubPosition));
hulls[i] = new Hull(MapEntityPrefab.Find("Hull"), hullRects[i], subs[i]);
hulls[i] = new Hull(MapEntityPrefab.Find(null, "Hull"), hullRects[i], subs[i]);
hulls[i].AddToGrid(subs[i]);
hulls[i].FreeID();
@@ -473,8 +472,8 @@ namespace Barotrauma.Items.Components
{
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 = 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));
@@ -504,7 +503,7 @@ namespace Barotrauma.Items.Components
//expand lower hull to the topmost hull of the lower sub
//(unless the difference is more than 100 units - if the distance is very large
//there's something wrong with the positioning of the docking ports or submarine hulls)
int lowerHullDiff = (hullRects[0].Y - hullRects[0].Height) - lowerSubTop;
int lowerHullDiff = ((hullRects[0].Y - hullRects[0].Height) - lowerSubTop) + 5;
if (lowerHullDiff > 0)
{
if (lowerHullDiff > 100)
@@ -517,7 +516,7 @@ namespace Barotrauma.Items.Components
}
}
int upperHullDiff = upperSubBottom - hullRects[1].Y;
int upperHullDiff = (upperSubBottom - hullRects[1].Y) + 5;
if (upperHullDiff > 0)
{
if (upperHullDiff > 100)
@@ -531,10 +530,24 @@ namespace Barotrauma.Items.Components
}
}
//difference between the edges of the hulls (to avoid a gap between the hulls)
//0 is lower
int midHullDiff = ((hullRects[1].Y - hullRects[1].Height) - hullRects[0].Y) + 2;
if (midHullDiff > 100)
{
DebugConsole.ThrowError("Creating hulls between docking ports failed. The upper hull seems to be very far from the lower hull.");
}
else if (midHullDiff > 0)
{
hullRects[0].Height += midHullDiff / 2 + 1;
hullRects[1].Y -= midHullDiff / 2 + 1;
hullRects[1].Height += midHullDiff / 2 + 1;
}
for (int i = 0; i < 2; i++)
{
hullRects[i].Location -= MathUtils.ToPoint((subs[i].WorldPosition - subs[i].HiddenSubPosition));
hulls[i] = new Hull(MapEntityPrefab.Find("Hull"), hullRects[i], subs[i]);
hulls[i] = new Hull(MapEntityPrefab.Find(null, "hull"), hullRects[i], subs[i]);
hulls[i].AddToGrid(subs[i]);
hulls[i].FreeID();
}
@@ -605,14 +618,14 @@ namespace Barotrauma.Items.Components
for (int i = 0; i < 2; i++)
{
Gap doorGap = i == 0 ? door?.LinkedGap : dockingTarget?.door?.LinkedGap;
Gap doorGap = i == 0 ? door?.LinkedGap : DockingTarget?.door?.LinkedGap;
if (doorGap == null) continue;
doorGap.DisableHullRechecks = true;
if (doorGap.linkedTo.Count >= 2) continue;
if (IsHorizontal)
{
if (item.WorldPosition.X < dockingTarget.item.WorldPosition.X)
if (item.WorldPosition.X < DockingTarget.item.WorldPosition.X)
{
if (!doorGap.linkedTo.Contains(hulls[0])) doorGap.linkedTo.Add(hulls[0]);
}
@@ -620,10 +633,17 @@ namespace Barotrauma.Items.Components
{
if (!doorGap.linkedTo.Contains(hulls[1])) doorGap.linkedTo.Add(hulls[1]);
}
//make sure the left hull is linked to the gap first (gap logic assumes that the first hull is the one to the left)
if (doorGap.linkedTo[0].Rect.X > doorGap.linkedTo[1].Rect.X)
{
var temp = doorGap.linkedTo[0];
doorGap.linkedTo[0] = doorGap.linkedTo[1];
doorGap.linkedTo[1] = temp;
}
}
else
{
if (item.WorldPosition.Y < dockingTarget.item.WorldPosition.Y)
if (item.WorldPosition.Y < DockingTarget.item.WorldPosition.Y)
{
if (!doorGap.linkedTo.Contains(hulls[0])) doorGap.linkedTo.Add(hulls[0]);
}
@@ -631,25 +651,34 @@ namespace Barotrauma.Items.Components
{
if (!doorGap.linkedTo.Contains(hulls[1])) doorGap.linkedTo.Add(hulls[1]);
}
//make sure the upper hull is linked to the gap first (gap logic assumes that the first hull is above the second one)
if (doorGap.linkedTo[0].Rect.Y < doorGap.linkedTo[1].Rect.Y)
{
var temp = doorGap.linkedTo[0];
doorGap.linkedTo[0] = doorGap.linkedTo[1];
doorGap.linkedTo[1] = temp;
}
}
}
}
public void Undock()
{
if (dockingTarget == null || !docked) return;
if (DockingTarget == null || !docked) return;
forceLockTimer = 0.0f;
#if CLIENT
PlaySound(ActionType.OnUse, item.WorldPosition);
#endif
dockingTarget.item.Submarine.DockedTo.Remove(item.Submarine);
item.Submarine.DockedTo.Remove(dockingTarget.item.Submarine);
DockingTarget.item.Submarine.DockedTo.Remove(item.Submarine);
item.Submarine.DockedTo.Remove(DockingTarget.item.Submarine);
if (door != null && dockingTarget.door != null)
if (door != null && DockingTarget.door != null)
{
WayPoint myWayPoint = WayPoint.WayPointList.Find(wp => door.LinkedGap == wp.ConnectedGap);
WayPoint targetWayPoint = WayPoint.WayPointList.Find(wp => dockingTarget.door.LinkedGap == wp.ConnectedGap);
WayPoint targetWayPoint = WayPoint.WayPointList.Find(wp => DockingTarget.door.LinkedGap == wp.ConnectedGap);
if (myWayPoint != null && targetWayPoint != null)
{
@@ -662,8 +691,8 @@ namespace Barotrauma.Items.Components
docked = false;
dockingTarget.Undock();
dockingTarget = null;
DockingTarget.Undock();
DockingTarget = null;
if (doorBody != null)
{
@@ -710,7 +739,7 @@ namespace Barotrauma.Items.Components
public override void Update(float deltaTime, Camera cam)
{
if (dockingTarget == null)
if (DockingTarget == null)
{
dockingState = MathHelper.Lerp(dockingState, 0.0f, deltaTime * 10.0f);
if (dockingState < 0.01f) docked = false;
@@ -723,8 +752,8 @@ namespace Barotrauma.Items.Components
{
if (!docked)
{
Dock(dockingTarget);
if (dockingTarget == null) return;
Dock(DockingTarget);
if (DockingTarget == null) { return; }
}
if (joint is DistanceJoint)
@@ -732,16 +761,48 @@ namespace Barotrauma.Items.Components
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)
forceLockTimer += deltaTime;
Vector2 jointDiff = joint.WorldAnchorB - joint.WorldAnchorA;
if (jointDiff.LengthSquared() > 0.04f * 0.04f && forceLockTimer < ForceLockDelay)
{
Lock(false);
float totalMass = item.Submarine.PhysicsBody.Mass + DockingTarget.item.Submarine.PhysicsBody.Mass;
float massRatio1 = 1.0f;
float massRatio2 = 1.0f;
if (item.Submarine.PhysicsBody.BodyType != BodyType.Dynamic)
{
massRatio1 = 0.0f;
massRatio2 = 1.0f;
}
else if (DockingTarget.item.Submarine.PhysicsBody.BodyType != BodyType.Dynamic)
{
massRatio1 = 1.0f;
massRatio2 = 0.0f;
}
else
{
massRatio1 = DockingTarget.item.Submarine.PhysicsBody.Mass / totalMass;
massRatio2 = item.Submarine.PhysicsBody.Mass / totalMass;
}
Vector2 relativeVelocity = DockingTarget.item.Submarine.Velocity - item.Submarine.Velocity;
Vector2 desiredRelativeVelocity = Vector2.Normalize(jointDiff);
item.Submarine.Velocity += (relativeVelocity + desiredRelativeVelocity) * massRatio1;
DockingTarget.item.Submarine.Velocity += (-relativeVelocity - desiredRelativeVelocity) * massRatio2;
}
else
{
Lock(isNetworkMessage: false, forcePosition: true);
}
}
else
{
if (dockingTarget.door != null && doorBody != null)
if (DockingTarget.door != null && doorBody != null)
{
doorBody.Enabled = dockingTarget.door.Body.Enabled;
doorBody.Enabled = DockingTarget.door.Body.Enabled;
}
item.SendSignal(0, "1", "state_out", null);
@@ -780,16 +841,14 @@ namespace Barotrauma.Items.Components
List<MapEntity> linked = new List<MapEntity>(item.linkedTo);
foreach (MapEntity entity in linked)
{
var hull = entity as Hull;
if (hull != null)
if (entity is Hull hull)
{
hull.Remove();
item.linkedTo.Remove(hull);
continue;
}
var gap = entity as Gap;
if (gap != null)
if (entity is Gap gap)
{
gap.Remove();
continue;
@@ -806,12 +865,12 @@ namespace Barotrauma.Items.Components
}
}
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power = 0.0f)
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power = 0.0f, float signalStrength = 1.0f)
{
if (GameMain.Client != null) return;
bool wasDocked = docked;
DockingPort prevDockingTarget = dockingTarget;
DockingPort prevDockingTarget = DockingTarget;
switch (connection.Name)
{
@@ -828,8 +887,8 @@ namespace Barotrauma.Items.Components
{
if (docked)
{
if (item.Submarine != null && dockingTarget?.item?.Submarine != null)
GameServer.Log(sender.LogName + " docked " + item.Submarine.Name + " to " + dockingTarget.item.Submarine.Name, ServerLog.MessageType.ItemInteraction);
if (item.Submarine != null && DockingTarget?.item?.Submarine != null)
GameServer.Log(sender.LogName + " docked " + item.Submarine.Name + " to " + DockingTarget.item.Submarine.Name, ServerLog.MessageType.ItemInteraction);
}
else
{
@@ -845,7 +904,7 @@ namespace Barotrauma.Items.Components
if (docked)
{
msg.Write(dockingTarget.item.ID);
msg.Write(DockingTarget.item.ID);
msg.Write(hulls != null && hulls[0] != null && hulls[1] != null && gap != null);
}
}
@@ -882,18 +941,18 @@ namespace Barotrauma.Items.Components
return;
}
dockingTarget = (targetEntity as Item).GetComponent<DockingPort>();
if (dockingTarget == null)
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);
Dock(DockingTarget);
if (isLocked)
{
Lock(true);
Lock(isNetworkMessage: true, forcePosition: true);
}
}
else
@@ -30,6 +30,9 @@ namespace Barotrauma.Items.Components
private bool isHorizontal;
private bool createdNewGap;
private bool autoOrientGap;
private bool isStuck;
private bool? predictedState;
@@ -39,6 +42,9 @@ namespace Barotrauma.Items.Components
private bool isBroken;
//openState when the vertices of the convex hull were last calculated
private float lastConvexHullState;
public bool IsBroken
{
get { return isBroken; }
@@ -63,6 +69,7 @@ namespace Barotrauma.Items.Components
}
private float stuck;
[Serialize(0.0f, false)]
public float Stuck
{
get { return stuck; }
@@ -70,11 +77,16 @@ namespace Barotrauma.Items.Components
{
if (isOpen || isBroken) return;
stuck = MathHelper.Clamp(value, 0.0f, 100.0f);
if (stuck == 0.0f) isStuck = false;
if (stuck == 100.0f) isStuck = true;
if (stuck <= 0.0f) isStuck = false;
if (stuck >= 100.0f) isStuck = true;
}
}
public bool? PredictedState
{
get { return predictedState; }
}
public Gap LinkedGap
{
get
@@ -102,14 +114,22 @@ namespace Barotrauma.Items.Components
rect.Width += 10;
}
linkedGap = new Gap(rect, Item.Submarine);
linkedGap.Submarine = item.Submarine;
linkedGap.PassAmbientLight = window != Rectangle.Empty;
linkedGap.Open = openState;
linkedGap = new Gap(rect, !isHorizontal, Item.Submarine)
{
Submarine = item.Submarine,
PassAmbientLight = window != Rectangle.Empty,
Open = openState
};
item.linkedTo.Add(linkedGap);
createdNewGap = true;
return linkedGap;
}
}
public bool IsHorizontal
{
get { return isHorizontal; }
}
[Serialize("0.0,0.0,0.0,0.0", false)]
public Rectangle Window
@@ -133,23 +153,30 @@ namespace Barotrauma.Items.Components
{
get { return openState; }
set
{
float prevValue = openState;
{
openState = MathHelper.Clamp(value, 0.0f, 1.0f);
if (openState == prevValue) return;
#if CLIENT
float size = isHorizontal ? item.Rect.Width : item.Rect.Height;
if (Math.Abs(lastConvexHullState - openState) * size < 5.0f) { return; }
UpdateConvexHulls();
lastConvexHullState = openState;
#endif
}
}
[Serialize(false, false)]
public bool Impassable
{
get;
set;
}
public Door(Item item, XElement element)
: base(item, element)
{
isHorizontal = element.GetAttributeBool("horizontal", false);
canBePicked = element.GetAttributeBool("canbepicked", false);
autoOrientGap = element.GetAttributeBool("autoorientgap", false);
foreach (XElement subElement in element.Elements())
{
@@ -171,24 +198,25 @@ namespace Barotrauma.Items.Components
}
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);
item.Rect.Center.X - (int)(doorSprite.size.X / 2 * item.Scale),
item.Rect.Y - item.Rect.Height/2 + (int)(doorSprite.size.Y / 2.0f * item.Scale),
(int)(doorSprite.size.X * item.Scale),
(int)(doorSprite.size.Y * item.Scale));
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;
1.5f)
{
UserData = item,
CollisionCategories = Physics.CollisionWall,
BodyType = BodyType.Static,
Friction = 0.5f
};
body.SetTransform(
ConvertUnits.ToSimUnits(new Vector2(doorRect.Center.X, doorRect.Y - doorRect.Height / 2)),
0.0f);
body.Friction = 0.5f;
IsActive = true;
}
@@ -197,7 +225,7 @@ namespace Barotrauma.Items.Components
{
base.Move(amount);
body.SetTransform(body.SimPosition + ConvertUnits.ToSimUnits(amount), 0.0f);
body?.SetTransform(body.SimPosition + ConvertUnits.ToSimUnits(amount), 0.0f);
#if CLIENT
UpdateConvexHulls();
@@ -223,7 +251,7 @@ namespace Barotrauma.Items.Components
SetState(predictedState == null ? !isOpen : !predictedState.Value, false, true); //crowbar function
#if CLIENT
PlaySound(ActionType.OnPicked, item.WorldPosition);
PlaySound(ActionType.OnPicked, item.WorldPosition, picker);
#endif
return false;
}
@@ -275,7 +303,7 @@ namespace Barotrauma.Items.Components
}
else
{
body.Enabled = openState < 1.0f;
body.Enabled = Impassable || openState < 1.0f;
}
//don't use the predicted state here, because it might set
@@ -290,7 +318,10 @@ namespace Barotrauma.Items.Components
private void EnableBody()
{
body.FarseerBody.IsSensor = false;
if (!Impassable)
{
body.FarseerBody.IsSensor = false;
}
#if CLIENT
UpdateConvexHulls();
#endif
@@ -301,7 +332,10 @@ namespace Barotrauma.Items.Components
{
//change the body to a sensor instead of disabling it completely,
//because otherwise repairtool raycasts won't hit it
body.FarseerBody.IsSensor = true;
if (!Impassable)
{
body.FarseerBody.IsSensor = true;
}
linkedGap.Open = 1.0f;
IsOpen = false;
#if CLIENT
@@ -314,6 +348,7 @@ namespace Barotrauma.Items.Components
{
LinkedGap.ConnectedDoor = this;
LinkedGap.Open = openState;
if (createdNewGap && autoOrientGap) linkedGap.AutoOrient();
#if CLIENT
Vector2[] corners = GetConvexHullCorners(Rectangle.Empty);
@@ -365,8 +400,8 @@ namespace Barotrauma.Items.Components
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));
new Vector2(item.Rect.Width * (1.0f - openState), doorSprite.size.Y * item.Scale) :
new Vector2(doorSprite.size.X * item.Scale, item.Rect.Height * (1.0f - openState));
Vector2 simSize = ConvertUnits.ToSimUnits(currSize);
@@ -447,7 +482,7 @@ namespace Barotrauma.Items.Components
}
}
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power = 0.0f)
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power = 0.0f, float signalStrength = 1.0f)
{
if (isStuck) return;
@@ -477,26 +512,24 @@ namespace Barotrauma.Items.Components
if (GameMain.Client != null && !isNetworkMessage)
{
//clients can "predict" that the door opens/closes when a signal is received
bool stateChanged = open != predictedState;
//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 CLIENT
if (open != predictedState) PlaySound(ActionType.OnUse, item.WorldPosition);
#endif
predictedState = open;
resetPredictionTimer = CorrectionDelay;
#if CLIENT
if (stateChanged) PlaySound(ActionType.OnUse, item.WorldPosition);
#endif
}
else
{
isOpen = open;
#if CLIENT
if (!isNetworkMessage || open != predictedState) PlaySound(ActionType.OnUse, item.WorldPosition);
#endif
isOpen = open;
}
//opening a partially stuck door makes it less stuck
@@ -508,14 +541,18 @@ namespace Barotrauma.Items.Components
}
}
public void ServerWrite(Lidgren.Network.NetBuffer msg, Barotrauma.Networking.Client c, object[] extraData = null)
public override void ServerWrite(Lidgren.Network.NetBuffer msg, Client c, object[] extraData = null)
{
base.ServerWrite(msg, c, extraData);
msg.Write(isOpen);
msg.WriteRangedSingle(stuck, 0.0f, 100.0f, 8);
}
public void ClientRead(ServerNetObject type, Lidgren.Network.NetBuffer msg, float sendingTime)
public override void ClientRead(ServerNetObject type, Lidgren.Network.NetBuffer msg, float sendingTime)
{
base.ClientRead(type, msg, sendingTime);
SetState(msg.ReadBoolean(), true);
Stuck = msg.ReadRangedSingle(0.0f, 100.0f, 8);
@@ -0,0 +1,442 @@
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
namespace Barotrauma.Items.Components
{
partial class ElectricalDischarger : Powered
{
private static List<ElectricalDischarger> list = new List<ElectricalDischarger>();
public static IEnumerable<ElectricalDischarger> List
{
get { return list; }
}
const int MaxNodes = 100;
const float MaxNodeDistance = 150.0f;
public struct Node
{
public Vector2 WorldPosition;
public int ParentIndex;
public float Length;
public float Angle;
public Node(Vector2 worldPosition, int parentIndex, float length = 0.0f, float angle = 0.0f)
{
WorldPosition = worldPosition;
ParentIndex = parentIndex;
Length = length;
Angle = angle;
}
}
[Serialize(100.0f, true), Editable(MinValueFloat = 0.0f, MaxValueFloat = 5000.0f)]
public float Range
{
get;
set;
}
[Serialize(10.0f, true), Editable(MinValueFloat = 0.0f, MaxValueFloat = 1000.0f, ToolTip = "How much further can the discharge be carried when moving across walls.")]
public float RangeMultiplierInWalls
{
get;
set;
}
[Serialize(0.25f, true), Editable(MinValueFloat = 0.0f, MaxValueFloat = 1.0f)]
public float Duration
{
get;
set;
}
[Serialize(false, true), Editable()]
public bool OutdoorsOnly
{
get;
set;
}
private readonly List<Node> nodes = new List<Node>();
public IEnumerable<Node> Nodes
{
get { return nodes; }
}
private readonly List<Pair<Character,Node>> charactersInRange = new List<Pair<Character, Node>>();
private bool charging;
private float timer;
private Attack attack;
public ElectricalDischarger(Item item, XElement element) :
base(item, element)
{
list.Add(this);
foreach (XElement subElement in element.Elements())
{
switch (subElement.Name.ToString().ToLowerInvariant())
{
case "attack":
attack = new Attack(subElement, item.Name);
break;
}
}
InitProjSpecific();
}
partial void InitProjSpecific();
public override bool Use(float deltaTime, Character character = null)
{
//already active, do nothing
if (IsActive) { return false; }
CurrPowerConsumption = powerConsumption;
charging = true;
timer = Duration;
IsActive = true;
return false;
}
public override void Update(float deltaTime, Camera cam)
{
#if CLIENT
frameOffset = Rand.Int(electricitySprite.FrameCount);
#endif
if (timer > 0.0f)
{
if (charging)
{
if (voltage > minVoltage || powerConsumption <= 0.0f)
{
Discharge();
}
}
timer -= deltaTime;
}
else
{
nodes.Clear();
charactersInRange.Clear();
IsActive = false;
}
voltage = 0.0f;
}
private void Discharge()
{
ApplyStatusEffects(ActionType.OnUse, 1.0f);
FindNodes(item.WorldPosition, Range);
if (attack != null)
{
foreach (Pair<Character, Node> characterInRange in charactersInRange)
{
characterInRange.First.ApplyAttack(null, characterInRange.Second.WorldPosition, attack, 1.0f);
}
}
DischargeProjSpecific();
charging = false;
}
partial void DischargeProjSpecific();
private void FindNodes(Vector2 worldPosition, float range)
{
//see which submarines are within range so we can skip structures that are in far-away subs
List<Submarine> submarinesInRange = new List<Submarine>();
foreach (Submarine sub in Submarine.Loaded)
{
if (item.Submarine == sub)
{
submarinesInRange.Add(sub);
}
else
{
Rectangle subBorders = new Rectangle(
sub.Borders.X - (int)range, sub.Borders.Y + (int)range,
sub.Borders.Width + (int)(range * 2), sub.Borders.Height + (int)(range * 2));
subBorders.Location += MathUtils.ToPoint(sub.SubBody.Position);
if (Submarine.RectContains(subBorders, worldPosition))
{
submarinesInRange.Add(sub);
}
}
}
//get all walls within range
List<Entity> entitiesInRange = new List<Entity>(100);
foreach (Structure structure in Structure.WallList)
{
if (!structure.HasBody || structure.IsPlatform) { continue; }
if (structure.Submarine != null&& !submarinesInRange.Contains(structure.Submarine)) { continue; }
var structureWorldRect = structure.WorldRect;
if (worldPosition.X < structureWorldRect.X - range) continue;
if (worldPosition.X > structureWorldRect.Right + range) continue;
if (worldPosition.Y > structureWorldRect.Y + range) continue;
if (worldPosition.Y < structureWorldRect.Y -structureWorldRect.Height - range) continue;
if (structure.Submarine != null)
{
if (!submarinesInRange.Contains(structure.Submarine)) { continue; }
if (OutdoorsOnly)
{
//check if the structure is within a hull
//add a small offset away from the sub's center so structures right at the edge of a hull are still valid
Vector2 offset = Vector2.Normalize(structure.WorldPosition - structure.Submarine.WorldPosition);
if (Hull.FindHull(structure.Position + offset * Submarine.GridSize, useWorldCoordinates: false) != null) { continue; }
}
}
entitiesInRange.Add(structure);
}
foreach (Character character in Character.CharacterList)
{
if (!character.Enabled) continue;
if (OutdoorsOnly && character.Submarine != null) continue;
if (character.Submarine != null && !submarinesInRange.Contains(character.Submarine)) continue;
if (Vector2.DistanceSquared(character.WorldPosition, worldPosition) < range * range * RangeMultiplierInWalls)
{
entitiesInRange.Add(character);
}
}
nodes.Clear();
nodes.Add(new Node(worldPosition, -1));
FindNodes(entitiesInRange, worldPosition, 0, range);
//construct final nodes (w/ lengths and angles so they don't have to be recalculated when rendering the discharge)
for (int i = 0; i < nodes.Count; i++)
{
if (nodes[i].ParentIndex < 0) continue;
Node parentNode = nodes[nodes[i].ParentIndex];
float length = Vector2.Distance(nodes[i].WorldPosition, parentNode.WorldPosition) * Rand.Range(1.0f, 1.25f);
float angle = MathUtils.VectorToAngle(parentNode.WorldPosition - nodes[i].WorldPosition);
nodes[i] = new Node(nodes[i].WorldPosition, nodes[i].ParentIndex, length, angle);
}
}
private void FindNodes(List<Entity> entitiesInRange, Vector2 currPos, int parentNodeIndex, float currentRange)
{
if (currentRange <= 0.0f || nodes.Count >= MaxNodes) return;
//find the closest structure
int closestIndex = -1;
float closestDist = float.MaxValue;
for (int i = 0; i < entitiesInRange.Count; i++)
{
float dist = float.MaxValue;
if (entitiesInRange[i] is Structure structure)
{
if (structure.IsHorizontal)
{
dist = Math.Abs(structure.WorldPosition.Y - currPos.Y);
if (currPos.X < structure.WorldRect.X)
dist += structure.WorldRect.X - currPos.X;
else if (currPos.X > structure.WorldRect.Right)
dist += currPos.X - structure.WorldRect.Right;
}
else
{
dist = Math.Abs(structure.WorldPosition.X - currPos.X);
if (currPos.Y < structure.WorldRect.Y - structure.Rect.Height)
dist += (structure.WorldRect.Y - structure.Rect.Height) - currPos.Y;
else if (currPos.Y > structure.WorldRect.Y)
dist += currPos.Y - structure.WorldRect.Y;
}
}
else if (entitiesInRange[i] is Character character)
{
dist = Vector2.Distance(character.WorldPosition, currPos);
}
if (dist < closestDist)
{
closestIndex = i;
closestDist = dist;
}
}
if (closestIndex == -1 || closestDist > currentRange)
{
int originalParentNodeIndex = parentNodeIndex;
//nothing in range, create some arcs to random directions
for (int i = 0; i < Rand.Int(4); i++)
{
Vector2 targetPos = currPos + Rand.Vector(MaxNodeDistance * Rand.Range(0.5f, 1.5f));
nodes.Add(new Node(targetPos, parentNodeIndex));
}
return;
}
currentRange -= closestDist;
if (entitiesInRange[closestIndex] is Structure targetStructure)
{
if (targetStructure.IsHorizontal)
{
//which side of the structure to add the nodes to
//if outside the sub, use the sides that's furthers from the sub's center position
//otherwise the side that's closer to the previous node
int yDir = OutdoorsOnly && targetStructure.Submarine != null ?
Math.Sign(targetStructure.WorldPosition.Y - targetStructure.Submarine.WorldPosition.Y) :
Math.Sign(currPos.Y - targetStructure.WorldPosition.Y);
int sectionIndex = targetStructure.FindSectionIndex(currPos, world: true, clamp: true);
if (sectionIndex == -1) { return; }
Vector2 sectionPos = targetStructure.SectionPosition(sectionIndex, world: true);
Vector2 targetPos =
new Vector2(
MathHelper.Clamp(sectionPos.X, targetStructure.WorldRect.X, targetStructure.WorldRect.Right),
sectionPos.Y + targetStructure.BodyHeight / 2 * yDir);
//create nodes from the current position to the closest point on the structure
AddNodesBetweenPoints(currPos, targetPos, 0.25f, ref parentNodeIndex);
//add a node at the closest point
nodes.Add(new Node(targetPos, parentNodeIndex));
int nodeIndex = nodes.Count - 1;
entitiesInRange.RemoveAt(closestIndex);
float newRange = currentRange - (targetStructure.Rect.Width / 2) * (1.0f / RangeMultiplierInWalls);
//continue the discharge to the left edge of the structure and extend from there
int leftNodeIndex = nodeIndex;
Vector2 leftPos = targetStructure.SectionPosition(0, world: true);
leftPos.Y += targetStructure.BodyHeight / 2 * yDir;
AddNodesBetweenPoints(targetPos, leftPos, 0.05f, ref leftNodeIndex);
nodes.Add(new Node(leftPos, leftNodeIndex));
FindNodes(entitiesInRange, leftPos, nodes.Count - 1, newRange);
//continue the discharge to the right edge of the structure and extend from there
int rightNodeIndex = nodeIndex;
Vector2 rightPos = targetStructure.SectionPosition(targetStructure.SectionCount - 1, world: true);
leftPos.Y += targetStructure.BodyHeight / 2 * yDir;
AddNodesBetweenPoints(targetPos, rightPos, 0.05f, ref rightNodeIndex);
nodes.Add(new Node(rightPos, rightNodeIndex));
FindNodes(entitiesInRange, rightPos, nodes.Count - 1, newRange);
}
else
{
int xDir = OutdoorsOnly && targetStructure.Submarine != null ?
Math.Sign(targetStructure.WorldPosition.X - targetStructure.Submarine.WorldPosition.X) :
Math.Sign(currPos.X - targetStructure.WorldPosition.X);
int sectionIndex = targetStructure.FindSectionIndex(currPos, world: true, clamp: true);
if (sectionIndex == -1) { return; }
Vector2 sectionPos = targetStructure.SectionPosition(sectionIndex, world: true);
Vector2 targetPos = new Vector2(
sectionPos.X + targetStructure.BodyWidth / 2 * xDir,
MathHelper.Clamp(sectionPos.Y, targetStructure.WorldRect.Y - targetStructure.Rect.Height, targetStructure.WorldRect.Y));
//create nodes from the current position to the closest point on the structure
AddNodesBetweenPoints(currPos, targetPos, 0.25f, ref parentNodeIndex);
//add a node at the closest point
nodes.Add(new Node(targetPos, parentNodeIndex));
int nodeIndex = nodes.Count - 1;
entitiesInRange.RemoveAt(closestIndex);
float newRange = currentRange - (targetStructure.Rect.Height / 2) * (1.0f / RangeMultiplierInWalls);
//continue the discharge to the top edge of the structure and extend from there
int topNodeIndex = nodeIndex;
Vector2 topPos = targetStructure.SectionPosition(0, world: true);
topPos.X += targetStructure.BodyWidth / 2 * xDir;
AddNodesBetweenPoints(targetPos, topPos, 0.05f, ref topNodeIndex);
nodes.Add(new Node(topPos, topNodeIndex));
FindNodes(entitiesInRange, topPos, nodes.Count - 1, newRange);
//continue the discharge to the bottom edge of the structure and extend from there
int bottomNodeIndex = nodeIndex;
Vector2 bottomBos = targetStructure.SectionPosition(targetStructure.SectionCount - 1, world: true);
bottomBos.X += targetStructure.BodyWidth / 2 * xDir;
AddNodesBetweenPoints(targetPos, bottomBos, 0.05f, ref bottomNodeIndex);
nodes.Add(new Node(bottomBos, bottomNodeIndex));
FindNodes(entitiesInRange, bottomBos, nodes.Count - 1, newRange);
}
//check if any character is close to this structure
for (int j = 0; j < entitiesInRange.Count; j++)
{
var otherEntity = entitiesInRange[j];
if (!(otherEntity is Character character)) continue;
if (OutdoorsOnly && character.Submarine != null) continue;
if (targetStructure.IsHorizontal)
{
if (otherEntity.WorldPosition.X < targetStructure.WorldRect.X) continue;
if (otherEntity.WorldPosition.X > targetStructure.WorldRect.Right) continue;
if (Math.Abs(otherEntity.WorldPosition.Y - targetStructure.WorldPosition.Y) > currentRange) continue;
}
else
{
if (otherEntity.WorldPosition.Y < targetStructure.WorldRect.Y - targetStructure.Rect.Height) continue;
if (otherEntity.WorldPosition.Y > targetStructure.WorldRect.Y) continue;
if (Math.Abs(otherEntity.WorldPosition.X - targetStructure.WorldPosition.X) > currentRange) continue;
}
float closestNodeDistSqr = float.MaxValue;
int closestNodeIndex = -1;
for (int i = 0; i < nodes.Count; i++)
{
float distSqr = Vector2.DistanceSquared(character.WorldPosition, nodes[i].WorldPosition);
if (distSqr < closestNodeDistSqr)
{
closestNodeDistSqr = distSqr;
closestNodeIndex = i;
}
}
if (closestNodeIndex > -1)
{
FindNodes(entitiesInRange, nodes[closestNodeIndex].WorldPosition, closestNodeIndex, currentRange - (float)Math.Sqrt(closestNodeDistSqr));
}
}
}
else if (entitiesInRange[closestIndex] is Character character)
{
Vector2 targetPos = character.WorldPosition;
//create nodes from the current position to the closest point on the character
AddNodesBetweenPoints(currPos, targetPos, 0.25f, ref parentNodeIndex);
nodes.Add(new Node(targetPos, parentNodeIndex));
entitiesInRange.RemoveAt(closestIndex);
charactersInRange.Add(new Pair<Character, Node>(character, nodes[parentNodeIndex]));
FindNodes(entitiesInRange, targetPos, nodes.Count - 1, currentRange);
}
}
private void AddNodesBetweenPoints(Vector2 currPos, Vector2 targetPos, float variance, ref int parentNodeIndex)
{
Vector2 diff = targetPos - currPos;
float dist = diff.Length();
Vector2 normal = new Vector2(-diff.Y, diff.X) / dist;
for (float x = MaxNodeDistance; x < dist - MaxNodeDistance; x += MaxNodeDistance * Rand.Range(0.5f, 1.5f))
{
//0 at the edges, 1 at the center
float normalOffset = (0.5f - Math.Abs(x / dist - 0.5f)) * 2.0f;
normalOffset *= variance * dist * Rand.Range(-1.0f, 1.0f);
nodes.Add(new Node(currPos + (diff / dist) * x + normal * normalOffset, parentNodeIndex));
parentNodeIndex = nodes.Count - 1;
}
}
protected override void RemoveComponentSpecific()
{
list.Remove(this);
}
}
}
@@ -11,24 +11,35 @@ namespace Barotrauma.Items.Components
{
//the position(s) in the item that the Character grabs
protected Vector2[] handlePos;
private Vector2[] scaledHandlePos;
private InputType prevPickKey;
private string prevMsg;
private List<RelatedItem> prevRequiredItems;
private Dictionary<RelatedItem.RelationType, List<RelatedItem>> prevRequiredItems;
//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 float swingState;
private bool attachable, attached, attachedByDefault;
private PhysicsBody body;
public PhysicsBody Pusher
{
get;
private set;
}
//the angle in which the Character holds the item
protected float holdAngle;
public PhysicsBody Body
{
get { return item.body ?? body; }
}
[Serialize(false, true)]
public bool Attached
{
@@ -36,6 +47,13 @@ namespace Barotrauma.Items.Components
set { attached = value; }
}
[Serialize(true, true)]
public bool Aimable
{
get;
set;
}
[Serialize(false, false)]
public bool ControlPose
{
@@ -50,6 +68,13 @@ namespace Barotrauma.Items.Components
set { attachable = value; }
}
[Serialize(true, false)]
public bool Reattachable
{
get;
set;
}
[Serialize(false, false)]
public bool AttachedByDefault
{
@@ -57,7 +82,7 @@ namespace Barotrauma.Items.Components
set { attachedByDefault = value; }
}
[Serialize("0.0,0.0", false)]
[Serialize("0.0,0.0", false),Editable]
public Vector2 HoldPos
{
get { return ConvertUnits.ToDisplayUnits(holdPos); }
@@ -71,25 +96,62 @@ namespace Barotrauma.Items.Components
set { aimPos = ConvertUnits.ToSimUnits(value); }
}
[Serialize(0.0f, false)]
[Serialize(0.0f, false), Editable]
public float HoldAngle
{
get { return MathHelper.ToDegrees(holdAngle); }
set { holdAngle = MathHelper.ToRadians(value); }
}
private Vector2 swingAmount;
[Serialize("0.0,0.0", false), Editable]
public Vector2 SwingAmount
{
get { return ConvertUnits.ToDisplayUnits(swingAmount); }
set { swingAmount = ConvertUnits.ToSimUnits(value); }
}
[Serialize(0.0f, false), Editable]
public float SwingSpeed { get; set; }
[Serialize(false, false), Editable]
public bool SwingWhenHolding { get; set; }
[Serialize(false, false), Editable]
public bool SwingWhenAiming { get; set; }
[Serialize(false, false), Editable]
public bool SwingWhenUsing { get; set; }
public Holdable(Item item, XElement element)
: base(item, element)
{
body = item.body;
handlePos = new Vector2[2];
Pusher = null;
if (element.GetAttributeBool("blocksplayers", false))
{
Pusher = new PhysicsBody(item.body.width, item.body.height, item.body.radius, item.body.Density)
{
BodyType = FarseerPhysics.Dynamics.BodyType.Dynamic,
CollidesWith = Physics.CollisionCharacter,
CollisionCategories = Physics.CollisionItemBlocking,
Enabled = false
};
Pusher.FarseerBody.FixedRotation = false;
Pusher.FarseerBody.GravityScale = 0.0f;
}
handlePos = new Vector2[2];
scaledHandlePos = new Vector2[2];
Vector2 previousValue = Vector2.Zero;
for (int i = 1; i < 3; i++)
{
handlePos[i - 1] = element.GetAttributeVector2("handle" + i, Vector2.Zero);
handlePos[i - 1] = ConvertUnits.ToSimUnits(handlePos[i - 1]);
int index = i - 1;
string attributeName = "handle" + i;
var attribute = element.Attribute(attributeName);
// If no value is defind for handle2, use the value of handle1.
var value = attribute != null ? ConvertUnits.ToSimUnits(XMLExtensions.ParseVector2(attribute.Value)) : previousValue;
handlePos[index] = value;
previousValue = value;
}
canBePicked = true;
@@ -98,7 +160,7 @@ namespace Barotrauma.Items.Components
{
prevMsg = Msg;
prevPickKey = PickKey;
prevRequiredItems = new List<RelatedItem>(requiredItems);
prevRequiredItems = new Dictionary<RelatedItem.RelationType, List<RelatedItem>>(requiredItems);
if (item.Submarine != null)
{
@@ -131,7 +193,7 @@ namespace Barotrauma.Items.Components
{
prevMsg = Msg;
prevPickKey = PickKey;
prevRequiredItems = new List<RelatedItem>(requiredItems);
prevRequiredItems = new Dictionary<RelatedItem.RelationType, List<RelatedItem>>(requiredItems);
}
}
@@ -156,7 +218,8 @@ namespace Barotrauma.Items.Components
item.body = body;
}
}
if (Pusher != null) Pusher.Enabled = false;
if (item.body != null) item.body.Enabled = true;
IsActive = false;
@@ -177,18 +240,17 @@ namespace Barotrauma.Items.Components
{
heldHand = picker.AnimController.GetLimb(LimbType.LeftHand);
arm = picker.AnimController.GetLimb(LimbType.LeftArm);
}
else
{
heldHand = picker.AnimController.GetLimb(LimbType.RightHand);
arm = picker.AnimController.GetLimb(LimbType.RightArm);
}
float xDif = (heldHand.SimPosition.X - arm.SimPosition.X) / 2f;
float yDif = (heldHand.SimPosition.Y - arm.SimPosition.Y) / 2.5f;
//hand simPosition is actually in the wrist so need to move the item out from it slightly
item.SetTransform(heldHand.SimPosition + new Vector2(xDif,yDif), 0.0f);
item.SetTransform(heldHand.SimPosition + new Vector2(xDif, yDif), 0.0f);
}
picker.DeselectItem(item);
@@ -242,12 +304,28 @@ namespace Barotrauma.Items.Components
IsActive = false;
}
public bool CanBeAttached()
{
if (!attachable || !Reattachable) return false;
//can be attached anywhere in sub editor
if (Screen.Selected == GameMain.SubEditorScreen) return true;
//can be attached anywhere inside hulls
if (item.CurrentHull != null) return true;
return Structure.GetAttachTarget(item.WorldPosition) != null;
}
public bool CanBeDeattached()
{
if (!attachable || !attached) return true;
//don't allow deattaching if outside hulls and not in sub editor
return item.CurrentHull != null || Screen.Selected == GameMain.SubEditorScreen;
//allow deattaching everywhere in sub editor
if (Screen.Selected == GameMain.SubEditorScreen) return true;
//don't allow deattaching if part of a sub and outside hulls
return item.Submarine == null || item.CurrentHull != null;
}
public override bool Pick(Character picker)
@@ -281,7 +359,7 @@ namespace Barotrauma.Items.Components
item.CreateServerEvent(this);
if (picker != null)
{
Networking.GameServer.Log(picker.LogName + " detached " + item.Name + " from a wall", ServerLog.MessageType.ItemInteraction);
GameServer.Log(picker.LogName + " detached " + item.Name + " from a wall", ServerLog.MessageType.ItemInteraction);
}
}
return true;
@@ -290,10 +368,25 @@ namespace Barotrauma.Items.Components
return false;
}
private void AttachToWall()
public void AttachToWall()
{
if (!attachable) return;
//outside hulls/subs -> we need to check if the item is being attached on a structure outside the sub
if (item.CurrentHull == null && item.Submarine == null)
{
Structure attachTarget = Structure.GetAttachTarget(item.WorldPosition);
if (attachTarget != null)
{
if (attachTarget.Submarine != null)
{
//set to submarine-relative position
item.SetTransform(ConvertUnits.ToSimUnits(item.WorldPosition - attachTarget.Submarine.Position), 0.0f, false);
}
item.Submarine = attachTarget.Submarine;
}
}
var containedItems = item.ContainedItems;
if (containedItems != null)
{
@@ -309,12 +402,12 @@ namespace Barotrauma.Items.Components
Msg = prevMsg;
PickKey = prevPickKey;
requiredItems = new List<RelatedItem>(prevRequiredItems);
requiredItems = new Dictionary<RelatedItem.RelationType, List<RelatedItem>>(prevRequiredItems);
attached = true;
}
private void DeattachFromWall()
public void DeattachFromWall()
{
if (!attachable) return;
@@ -328,15 +421,15 @@ namespace Barotrauma.Items.Components
public override bool Use(float deltaTime, Character character = null)
{
if (!attachable || item.body == null) return true;
if (!attachable || item.body == null) return (character == null || character.IsKeyDown(InputType.Aim));
if (character != null)
{
if (!character.IsKeyDown(InputType.Aim)) return false;
if (character.CurrentHull == null) return false;
if (!CanBeAttached()) return false;
if (GameMain.Server != null)
{
item.CreateServerEvent(this);
GameServer.Log(character.LogName + " attached " + item.Name+" to a wall", ServerLog.MessageType.ItemInteraction);
GameServer.Log(character.LogName + " attached " + item.Name + " to a wall", ServerLog.MessageType.ItemInteraction);
}
item.Drop();
}
@@ -356,42 +449,57 @@ namespace Barotrauma.Items.Components
if (item.body == null || !item.body.Enabled) return;
if (picker == null || !picker.HasEquippedItem(item))
{
if (Pusher != null) Pusher.Enabled = false;
IsActive = false;
return;
}
Vector2 swing = Vector2.Zero;
if (swingAmount != Vector2.Zero)
{
swingState += deltaTime;
swingState %= 1.0f;
if (SwingWhenHolding ||
(SwingWhenAiming && picker.IsKeyDown(InputType.Aim)) ||
(SwingWhenUsing && picker.IsKeyDown(InputType.Aim) && picker.IsKeyDown(InputType.Use)))
{
swing = swingAmount * new Vector2(
PerlinNoise.GetPerlin(swingState * SwingSpeed * 0.1f, swingState * SwingSpeed * 0.1f) - 0.5f,
PerlinNoise.GetPerlin(swingState * SwingSpeed * 0.1f + 0.5f, swingState * SwingSpeed * 0.1f + 0.5f) - 0.5f);
}
}
ApplyStatusEffects(ActionType.OnActive, deltaTime, picker);
if (item.body.Dir != picker.AnimController.Dir) Flip(item);
item.Submarine = picker.Submarine;
if (picker.HasSelectedItem(item))
{
picker.AnimController.HoldItem(deltaTime, item, handlePos, holdPos, aimPos, picker.IsKeyDown(InputType.Aim) && aimPos != Vector2.Zero, holdAngle);
scaledHandlePos[0] = handlePos[0] * item.Scale;
scaledHandlePos[1] = handlePos[1] * item.Scale;
picker.AnimController.HoldItem(deltaTime, item, scaledHandlePos, holdPos + swing, aimPos + swing, picker.IsKeyDown(InputType.Aim) && aimPos != Vector2.Zero, holdAngle);
}
else
{
Limb equipLimb = null;
if (picker.Inventory.IsInLimbSlot(item, InvSlotType.Face) || picker.Inventory.IsInLimbSlot(item, InvSlotType.Head))
if (picker.Inventory.IsInLimbSlot(item, InvSlotType.Headset) || picker.Inventory.IsInLimbSlot(item, InvSlotType.Head))
{
equipLimb = picker.AnimController.GetLimb(LimbType.Head);
}
else if (picker.Inventory.IsInLimbSlot(item, InvSlotType.Torso))
else if (picker.Inventory.IsInLimbSlot(item, InvSlotType.InnerClothes) ||
picker.Inventory.IsInLimbSlot(item, InvSlotType.OuterClothes))
{
equipLimb = picker.AnimController.GetLimb(LimbType.Torso);
}
else if (picker.Inventory.IsInLimbSlot(item, InvSlotType.Legs))
{
equipLimb = picker.AnimController.GetLimb(LimbType.Waist);
}
if (equipLimb != null)
{
float itemAngle = (equipLimb.Rotation + holdAngle * picker.AnimController.Dir);
Matrix itemTransfrom = Matrix.CreateRotationZ(equipLimb.Rotation);
Vector2 transformedHandlePos = Vector2.Transform(handlePos[0], itemTransfrom);
Vector2 transformedHandlePos = Vector2.Transform(handlePos[0] * item.Scale, itemTransfrom);
item.body.ResetDynamics();
item.SetTransform(equipLimb.SimPosition - transformedHandlePos, itemAngle);
@@ -434,20 +542,19 @@ namespace Barotrauma.Items.Components
}
}
public void ServerWrite(NetBuffer msg, Client c, object[] extraData = null)
public override void ServerWrite(NetBuffer msg, Client c, object[] extraData = null)
{
if (!attachable || body == null)
{
DebugConsole.ThrowError("Sent an attachment event for an item that's not attachable.");
}
base.ServerWrite(msg, c, extraData);
if (!attachable || body == null) return;
msg.Write(Attached);
msg.Write(body.SimPosition.X);
msg.Write(body.SimPosition.Y);
}
public void ClientRead(ServerNetObject type, NetBuffer msg, float sendingTime)
public override void ClientRead(ServerNetObject type, NetBuffer msg, float sendingTime)
{
base.ClientRead(type, msg, sendingTime);
bool shouldBeAttached = msg.ReadBoolean();
Vector2 simPosition = new Vector2(msg.ReadFloat(), msg.ReadFloat());
@@ -459,9 +566,12 @@ namespace Barotrauma.Items.Components
if (shouldBeAttached)
{
Drop(false, null);
item.SetTransform(simPosition, 0.0f);
AttachToWall();
if (!attached)
{
Drop(false, null);
item.SetTransform(simPosition, 0.0f);
AttachToWall();
}
}
else
{
@@ -0,0 +1,100 @@
using Barotrauma.Networking;
using Lidgren.Network;
using Microsoft.Xna.Framework;
using System;
using System.Xml.Linq;
namespace Barotrauma.Items.Components
{
partial class LevelResource : ItemComponent, IServerSerializable
{
[Serialize(1.0f, false)]
public float DeattachDuration
{
get;
set;
}
[Serialize(0.0f, false)]
public float DeattachTimer
{
get { return deattachTimer; }
set
{
deattachTimer = Math.Max(0.0f, value);
//clients don't deattach the item until the server says so (handled in ClientRead)
if (GameMain.Client == null && deattachTimer >= DeattachDuration)
{
holdable.DeattachFromWall();
}
}
}
private PhysicsBody trigger;
private Holdable holdable;
private float deattachTimer;
public LevelResource(Item item, XElement element) : base(item, element)
{
IsActive = true;
}
public override void Update(float deltaTime, Camera cam)
{
if (!holdable.Attached)
{
trigger.Enabled = false;
IsActive = false;
}
else
{
if (Vector2.DistanceSquared(item.SimPosition, trigger.SimPosition) > 0.01f)
{
trigger.SetTransform(item.SimPosition, 0.0f);
}
}
}
public override void OnItemLoaded()
{
holdable = item.GetComponent<Holdable>();
if (holdable == null)
{
DebugConsole.ThrowError("Error while initializing item \"" + item.Name + "\". Level resources require a Holdable component.");
IsActive = false;
return;
}
holdable.Reattachable = false;
holdable.PickingTime = float.MaxValue;
var body = item.body ?? holdable.Body;
if (body != null)
{
trigger = new PhysicsBody(body.width, body.height, body.radius, body.Density)
{
UserData = item
};
trigger.FarseerBody.IsSensor = true;
trigger.FarseerBody.IsStatic = true;
trigger.FarseerBody.CollisionCategories = Physics.CollisionWall;
}
}
protected override void RemoveComponentSpecific()
{
if (trigger != null)
{
trigger.Remove();
trigger = null;
}
}
public void ServerWrite(NetBuffer msg, Client c, object[] extraData = null)
{
msg.Write(deattachTimer);
}
}
}
@@ -27,6 +27,11 @@ namespace Barotrauma.Items.Components
private HashSet<Entity> hitTargets = new HashSet<Entity>();
public Character User
{
get { return user; }
}
[Serialize(0.0f, false)]
public float Range
{
@@ -56,7 +61,7 @@ namespace Barotrauma.Items.Components
foreach (XElement subElement in element.Elements())
{
if (subElement.Name.ToString().ToLowerInvariant() != "attack") continue;
attack = new Attack(subElement);
attack = new Attack(subElement, item.Name + ", MeleeWeapon");
}
}
@@ -78,7 +83,7 @@ namespace Barotrauma.Items.Components
SetUser(character);
if (hitPos < MathHelper.Pi * 0.69f) return false;
if (hitPos < MathHelper.PiOver4) return false;
reloadTimer = reload;
@@ -129,6 +134,7 @@ namespace Barotrauma.Items.Components
if (!picker.HasSelectedItem(item)) IsActive = false;
reloadTimer -= deltaTime;
if (reloadTimer < 0) { reloadTimer = 0; }
if (!picker.IsKeyDown(InputType.Aim) && !hitting) hitPos = 0.0f;
@@ -138,32 +144,34 @@ namespace Barotrauma.Items.Components
AnimController ac = picker.AnimController;
//TODO: refactor the hitting logic (get rid of the magic numbers, make it possible to use different kinds of animations for different items)
if (!hitting)
{
if (picker.IsKeyDown(InputType.Aim))
if (picker.IsKeyDown(InputType.Aim) && reloadTimer <= 0)
{
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);
hitPos = MathUtils.WrapAnglePi(Math.Min(hitPos + deltaTime * 5f, MathHelper.PiOver4));
ac.HoldItem(deltaTime, item, handlePos, aimPos, Vector2.Zero, false, hitPos, holdAngle + hitPos);
}
else
{
ac.HoldItem(deltaTime, item, handlePos, holdPos, aimPos, false, holdAngle);
hitPos = 0;
ac.HoldItem(deltaTime, item, handlePos, holdPos, Vector2.Zero, false, holdAngle);
}
}
else
{
hitPos -= deltaTime * 15.0f;
ac.HoldItem(deltaTime, item, handlePos, new Vector2(0.6f, -0.1f), new Vector2(-0.3f, 0.2f), false, hitPos);
hitPos = MathUtils.WrapAnglePi(hitPos - deltaTime * 15f);
ac.HoldItem(deltaTime, item, handlePos, new Vector2(2, 0), Vector2.Zero, false, hitPos, holdAngle + hitPos); // aimPos not used -> zero (new Vector2(-0.3f, 0.2f)), holdPos new Vector2(0.6f, -0.1f)
if (hitPos < -MathHelper.PiOver4 * 1.2f)
{
RestoreCollision();
hitting = false;
hitTargets.Clear();
hitPos = 0;
}
}
}
private void SetUser(Character character)
{
if (user == character) return;
@@ -173,14 +181,12 @@ namespace Barotrauma.Items.Components
{
foreach (Limb limb in user.AnimController.Limbs)
{
try
if (limb.body.FarseerBody != null)
{
item.body.FarseerBody.RestoreCollisionWith(limb.body.FarseerBody);
}
catch
{
continue;
if (GameMain.World.BodyList.Contains(limb.body.FarseerBody))
{
item.body.FarseerBody.RestoreCollisionWith(limb.body.FarseerBody);
}
}
}
}
@@ -199,11 +205,6 @@ namespace Barotrauma.Items.Components
item.body.CollisionCategories = Physics.CollisionItem;
item.body.CollidesWith = Physics.CollisionWall;
//foreach (Limb l in picker.AnimController.Limbs)
//{
// item.body.FarseerBody.RestoreCollisionWith(l.body.FarseerBody);
//}
}
@@ -220,6 +221,8 @@ namespace Barotrauma.Items.Components
Limb targetLimb = null;
Structure targetStructure = null;
attack?.SetUser(user);
if (f2.Body.UserData is Limb)
{
targetLimb = (Limb)f2.Body.UserData;
@@ -273,10 +276,12 @@ namespace Barotrauma.Items.Components
{
if (targetLimb != null)
{
targetLimb.character.LastDamageSource = item;
attack.DoDamageToLimb(user, targetLimb, item.WorldPosition, 1.0f);
}
else if (targetCharacter != null)
{
targetCharacter.LastDamageSource = item;
attack.DoDamage(user, targetCharacter, item.WorldPosition, 1.0f);
}
else if (targetStructure != null)
@@ -293,7 +298,14 @@ namespace Barotrauma.Items.Components
if (GameMain.Server != null && targetCharacter != null) //TODO: Log structure hits
{
GameMain.Server.CreateEntityEvent(item, new object[] { Networking.NetEntityEvent.Type.ApplyStatusEffect, ActionType.OnUse, targetCharacter.ID });
GameMain.Server.CreateEntityEvent(item, new object[]
{
Networking.NetEntityEvent.Type.ApplyStatusEffect,
ActionType.OnUse,
null, //itemcomponent
targetCharacter.ID, targetLimb
});
string logStr = picker?.LogName + " used " + item.Name;
if (item.ContainedItems != null && item.ContainedItems.Length > 0)
@@ -303,9 +315,11 @@ namespace Barotrauma.Items.Components
logStr += " on " + targetCharacter.LogName + ".";
Networking.GameServer.Log(logStr, Networking.ServerLog.MessageType.Attack);
}
if (targetCharacter != null) //TODO: Allow OnUse to happen on structures too maybe??
ApplyStatusEffects(ActionType.OnUse, 1.0f, targetCharacter);
{
ApplyStatusEffects(ActionType.OnUse, 1.0f, targetCharacter, targetLimb, user: user);
}
if (DeleteOnUse)
{
@@ -1,11 +1,13 @@
using Microsoft.Xna.Framework;
using Barotrauma.Networking;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Xml.Linq;
using Lidgren.Network;
namespace Barotrauma.Items.Components
{
class Pickable : ItemComponent
class Pickable : ItemComponent, IServerSerializable
{
protected Character picker;
@@ -13,6 +15,8 @@ namespace Barotrauma.Items.Components
private float pickTimer;
private Character activePicker;
public List<InvSlotType> AllowedSlots
{
get { return allowedSlots; }
@@ -36,13 +40,14 @@ namespace Barotrauma.Items.Components
InvSlotType allowedSlot = InvSlotType.None;
foreach (string slot in slots)
{
if (slot.ToLowerInvariant() == "bothhands")
switch (slot.ToLowerInvariant())
{
allowedSlot = InvSlotType.LeftHand | InvSlotType.RightHand;
}
else
{
allowedSlot = allowedSlot | (InvSlotType)Enum.Parse(typeof(InvSlotType), slot.Trim());
case "bothhands":
allowedSlot = InvSlotType.LeftHand | InvSlotType.RightHand;
break;
default:
allowedSlot = allowedSlot | (InvSlotType)Enum.Parse(typeof(InvSlotType), slot.Trim());
break;
}
}
allowedSlots.Add(allowedSlot);
@@ -59,8 +64,9 @@ namespace Barotrauma.Items.Components
if (PickingTime > 0.0f)
{
if (picker.PickingItem == null)
if (picker.PickingItem == null && PickingTime <= float.MaxValue)
{
item.CreateServerEvent(this);
CoroutineManager.StartCoroutine(WaitForPick(picker, PickingTime));
}
return false;
@@ -90,7 +96,7 @@ namespace Barotrauma.Items.Components
#if CLIENT
if (!GameMain.Instance.LoadingScreenOpen && picker == Character.Controlled) GUI.PlayUISound(GUISoundType.PickItem);
PlaySound(ActionType.OnPicked, item.WorldPosition);
PlaySound(ActionType.OnPicked, item.WorldPosition, picker);
#endif
return true;
@@ -105,6 +111,7 @@ namespace Barotrauma.Items.Components
private IEnumerable<object> WaitForPick(Character picker, float requiredTime)
{
activePicker = picker;
picker.PickingItem = item;
var leftHand = picker.AnimController.GetLimb(LimbType.LeftHand);
@@ -127,7 +134,7 @@ namespace Barotrauma.Items.Components
Color.Red, Color.Green);
#endif
picker.AnimController.UpdateUseItem(true, item.SimPosition + Vector2.UnitY * ((pickTimer / 10.0f) % 0.1f));
picker.AnimController.UpdateUseItem(true, item.WorldPosition + new Vector2(0.0f, 100.0f) * ((pickTimer / 10.0f) % 0.1f));
pickTimer += CoroutineManager.DeltaTime;
@@ -143,28 +150,31 @@ namespace Barotrauma.Items.Components
private void StopPicking(Character picker)
{
picker.AnimController.Anim = AnimController.Animation.None;
picker.PickingItem = null;
if (picker != null)
{
picker.AnimController.Anim = AnimController.Animation.None;
picker.PickingItem = null;
}
activePicker = null;
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 (ConnectionPanel connectionPanel in item.GetComponents<ConnectionPanel>())
{
foreach (Wire w in c.Wires)
foreach (Connection c in connectionPanel.Connections)
{
if (w == null) continue;
w.Item.Drop(character);
w.Item.SetTransform(pos, 0.0f);
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)
@@ -204,5 +214,22 @@ namespace Barotrauma.Items.Components
}
}
public virtual void ServerWrite(NetBuffer msg, Client c, object[] extraData = null)
{
msg.Write(activePicker == null ? (ushort)0 : activePicker.ID);
}
public virtual void ClientRead(ServerNetObject type, NetBuffer msg, float sendingTime)
{
ushort pickerID = msg.ReadUInt16();
if (pickerID == 0)
{
StopPicking(activePicker);
}
else
{
Pick(Entity.FindEntityByID(pickerID) as Character);
}
}
}
}
@@ -11,18 +11,16 @@ namespace Barotrauma.Items.Components
{
enum UsableIn
{
Air,Water,Both
Air, Water, Both
};
private float force;
private string particles;
private float useState;
private UsableIn usableIn;
[Serialize(0.0f, false)]
[Serialize(0.0f, false), Editable(MinValueFloat = 0.0f, MaxValueFloat = 1000.0f)]
public float Force
{
get { return force; }
@@ -30,6 +28,7 @@ namespace Barotrauma.Items.Components
}
#if CLIENT
private string particles;
[Serialize("", false)]
public string Particles
{
@@ -1,4 +1,5 @@
using FarseerPhysics;
using FarseerPhysics.Collision;
using FarseerPhysics.Dynamics;
using Microsoft.Xna.Framework;
using System;
@@ -81,7 +82,7 @@ namespace Barotrauma.Items.Components
limbBodies.Add(l.body.FarseerBody);
}
float degreeOfFailure = (100.0f - DegreeOfSuccess(character)) / 100.0f;
float degreeOfFailure = 1.0f - DegreeOfSuccess(character);
degreeOfFailure *= degreeOfFailure;
@@ -90,50 +91,80 @@ namespace Barotrauma.Items.Components
ApplyStatusEffects(ActionType.OnFailure, 1.0f, character);
}
Projectile projectile = null;
Item[] containedItems = item.ContainedItems;
if (containedItems != null)
if (containedItems == null) return true;
foreach (Item item in containedItems)
{
foreach (Item projectile in containedItems)
projectile = item.GetComponent<Projectile>();
if (projectile != null) break;
}
//projectile not found, see if one of the contained items contains projectiles
if (projectile == null)
{
foreach (Item item 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;
float spread = MathHelper.ToRadians(MathHelper.Lerp(Spread, UnskilledSpread, degreeOfFailure));
float rotation = (item.body.Dir == 1.0f) ? item.body.Rotation : item.body.Rotation - MathHelper.Pi;
rotation += spread * Rand.Range(-0.5f, 0.5f);
projectile.body.ResetDynamics();
projectile.SetTransform(TransformedBarrelPos, rotation);
projectileComponent.User = character;
projectileComponent.IgnoredBodies = new List<Body>(limbBodies);
projectile.Use(deltaTime);
projectileComponent.User = character;
projectile.body.ApplyTorque(projectile.body.Mass * degreeOfFailure * Rand.Range(-10.0f, 10.0f));
//set the rotation of the projectile again because dropping the projectile resets the rotation
projectile.SetTransform(projectile.SimPosition, rotation);
//recoil
item.body.ApplyLinearImpulse(
new Vector2((float)Math.Cos(projectile.body.Rotation), (float)Math.Sin(projectile.body.Rotation)) * item.body.Mass * -50.0f);
item.RemoveContained(projectile);
Rope rope = item.GetComponent<Rope>();
if (rope != null) rope.Attach(projectile);
return true;
Item[] containedSubItems = item.ContainedItems;
foreach (Item subItem in containedSubItems)
{
projectile = subItem.GetComponent<Projectile>();
//apply OnUse statuseffects to the container in case it has to react to it somehow
//(play a sound, spawn more projectiles, reduce condition...)
subItem.GetComponent<ItemContainer>()?.Item.ApplyStatusEffects(ActionType.OnUse, deltaTime);
if (projectile != null) break;
}
}
}
return true;
}
if (projectile == null) return true;
float spread = MathHelper.ToRadians(MathHelper.Lerp(Spread, UnskilledSpread, degreeOfFailure));
float rotation = (item.body.Dir == 1.0f) ? item.body.Rotation : item.body.Rotation - MathHelper.Pi;
rotation += spread * Rand.Range(-0.5f, 0.5f);
projectile.User = character;
//add the limbs of the shooter to the list of bodies to be ignored
//so that the player can't shoot himself
projectile.IgnoredBodies = new List<Body>(limbBodies);
Vector2 projectilePos = item.SimPosition;
Vector2 sourcePos = character?.AnimController == null ? item.SimPosition : character.AnimController.AimSourceSimPos;
Vector2 barrelPos = TransformedBarrelPos;
//make sure there's no obstacles between the base of the weapon (or the shoulder of the character) and the end of the barrel
if (Submarine.PickBody(sourcePos, barrelPos, projectile.IgnoredBodies) == null)
{
//no obstacles -> we can spawn the projectile at the barrel
projectilePos = barrelPos;
}
else if ((sourcePos - barrelPos).LengthSquared() > 0.0001f)
{
//spawn the projectile body.GetMaxExtent() away from the position where the raycast hit the obstacle
projectilePos = sourcePos - Vector2.Normalize(barrelPos - projectilePos) * Math.Max(projectile.Item.body.GetMaxExtent(), 0.1f);
}
projectile.Item.body.ResetDynamics();
projectile.Item.SetTransform(projectilePos, rotation);
projectile.Use(deltaTime);
projectile.User = character;
projectile.Item.body.ApplyTorque(projectile.Item.body.Mass * degreeOfFailure * Rand.Range(-10.0f, 10.0f));
//set the rotation of the projectile again because dropping the projectile resets the rotation
projectile.Item.SetTransform(projectilePos,
rotation + ((item.body.Dir == 1.0f) ? projectile.LaunchRotationRadians : projectile.LaunchRotationRadians - MathHelper.Pi));
//recoil
item.body.ApplyLinearImpulse(
new Vector2((float)Math.Cos(projectile.Item.body.Rotation), (float)Math.Sin(projectile.Item.body.Rotation)) * item.body.Mass * -50.0f);
item.RemoveContained(projectile.Item);
Rope rope = item.GetComponent<Rope>();
if (rope != null) rope.Attach(projectile.Item);
return true;
}
}
}
@@ -3,15 +3,15 @@ using FarseerPhysics.Dynamics;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
#if CLIENT
using Barotrauma.Particles;
#endif
namespace Barotrauma.Items.Components
{
class RepairTool : ItemComponent
partial class RepairTool : ItemComponent
{
private readonly List<string> fixableEntities;
@@ -20,9 +20,7 @@ namespace Barotrauma.Items.Components
private Vector2 pickedPosition;
private Vector2 barrelPos;
private string particles;
private float activeTimer;
[Serialize(0.0f, false)]
@@ -44,25 +42,11 @@ namespace Barotrauma.Items.Components
get; set;
}
[Serialize(0.0f, false)]
public float ExtinquishAmount
public float ExtinguishAmount
{
get; set;
}
#if CLIENT
public ParticleEmitter ParticleEmitter
{
get;
private set;
}
private List<ParticleEmitter> ParticleEmitterHitStructure = new List<ParticleEmitter>();
private List<ParticleEmitter> ParticleEmitterHitItem = new List<ParticleEmitter>();
private List<ParticleEmitter> ParticleEmitterHitCharacter = new List<ParticleEmitter>();
#endif
[Serialize("0.0,0.0", false)]
public Vector2 BarrelPos
{
@@ -92,26 +76,24 @@ namespace Barotrauma.Items.Components
switch (subElement.Name.ToString().ToLowerInvariant())
{
case "fixable":
fixableEntities.Add(subElement.Attribute("name").Value);
if (subElement.Attribute("name") != null)
{
DebugConsole.ThrowError("Error in RepairTool " + item.Name + " - use identifiers instead of names to configure fixable entities.");
fixableEntities.Add(subElement.Attribute("name").Value);
}
else
{
fixableEntities.Add(subElement.GetAttributeString("identifier", ""));
}
break;
#if CLIENT
case "particleemitter":
ParticleEmitter = new ParticleEmitter(subElement);
break;
case "particleemitterhititem":
ParticleEmitterHitItem.Add(new ParticleEmitter(subElement));
break;
case "particleemitterhitstructure":
ParticleEmitterHitStructure.Add(new ParticleEmitter(subElement));
break;
case "particleemitterhitcharacter":
ParticleEmitterHitCharacter.Add(new ParticleEmitter(subElement));
break;
#endif
}
}
InitProjSpecific(element);
}
partial void InitProjSpecific(XElement element);
public override void Update(float deltaTime, Camera cam)
{
activeTimer -= deltaTime;
@@ -123,7 +105,7 @@ namespace Barotrauma.Items.Components
if (character == null || character.Removed) return false;
if (!character.IsKeyDown(InputType.Aim)) return false;
float degreeOfSuccess = DegreeOfSuccess(character)/100.0f;
float degreeOfSuccess = DegreeOfSuccess(character);
if (Rand.Range(0.0f, 0.5f) > degreeOfSuccess)
{
@@ -163,32 +145,26 @@ namespace Barotrauma.Items.Components
Repair(rayStart - character.Submarine.SimPosition, rayEnd - character.Submarine.SimPosition, deltaTime, character, degreeOfSuccess, ignoredBodies);
}
#if CLIENT
if (ParticleEmitter != null)
{
float particleAngle = item.body.Rotation + ((item.body.Dir > 0.0f) ? 0.0f : MathHelper.Pi);
ParticleEmitter.Emit(
deltaTime, item.WorldPosition + TransformedBarrelPos,
item.CurrentHull, particleAngle, -particleAngle);
}
#endif
UseProjSpecific(deltaTime);
return true;
}
partial void UseProjSpecific(float deltaTime);
private void Repair(Vector2 rayStart, Vector2 rayEnd, float deltaTime, Character user, float degreeOfSuccess, List<Body> ignoredBodies)
{
Body targetBody = Submarine.PickBody(rayStart, rayEnd, ignoredBodies,
Body targetBody = Submarine.PickBody(rayStart, rayEnd, ignoredBodies,
Physics.CollisionWall | Physics.CollisionCharacter | Physics.CollisionItem | Physics.CollisionLevel | Physics.CollisionRepair, false);
if (ExtinquishAmount > 0.0f && item.CurrentHull != null)
if (ExtinguishAmount > 0.0f && item.CurrentHull != null)
{
List<FireSource> fireSourcesInRange = new List<FireSource>();
//step along the ray in 10% intervals, collecting all fire sources in the range
for (float x = 0.0f; x <= Submarine.LastPickedFraction; x += 0.1f)
{
Vector2 displayPos = ConvertUnits.ToDisplayUnits(rayStart + (rayEnd - rayStart) * x);
displayPos += item.CurrentHull.Submarine.Position;
if (item.CurrentHull.Submarine != null) { displayPos += item.CurrentHull.Submarine.Position; }
Hull hull = Hull.FindHull(displayPos, item.CurrentHull);
if (hull == null) continue;
@@ -203,52 +179,25 @@ namespace Barotrauma.Items.Components
foreach (FireSource fs in fireSourcesInRange)
{
fs.Extinguish(deltaTime, ExtinquishAmount);
fs.Extinguish(deltaTime, ExtinguishAmount);
}
}
if (targetBody == null || targetBody.UserData == null) return;
pickedPosition = Submarine.LastPickedPosition;
Structure targetStructure;
Character targetCharacter;
Limb targetLimb;
Item targetItem;
if ((targetStructure = (targetBody.UserData as Structure)) != null)
if (targetBody.UserData is Structure targetStructure)
{
if (!fixableEntities.Contains("structure") && !fixableEntities.Contains(targetStructure.Name)) return;
if (!fixableEntities.Contains("structure") && !fixableEntities.Contains(targetStructure.Prefab.Identifier)) return;
if (targetStructure.IsPlatform) return;
int sectionIndex = targetStructure.FindSectionIndex(ConvertUnits.ToDisplayUnits(pickedPosition));
if (sectionIndex < 0) return;
#if CLIENT
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);
Vector2 particlePos = ConvertUnits.ToDisplayUnits(pickedPosition);
if (targetStructure.Submarine != null) particlePos += targetStructure.Submarine.DrawPosition;
foreach (var emitter in ParticleEmitterHitStructure)
{
float particleAngle = item.body.Rotation + ((item.body.Dir > 0.0f) ? 0.0f : MathHelper.Pi);
emitter.Emit(deltaTime, particlePos, item.CurrentHull, particleAngle + MathHelper.Pi, -particleAngle + MathHelper.Pi);
}
#endif
FixStructureProjSpecific(user, deltaTime, targetStructure, sectionIndex);
targetStructure.AddDamage(sectionIndex, -StructureFixAmount * degreeOfSuccess, user);
//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)
@@ -263,34 +212,25 @@ namespace Barotrauma.Items.Components
}
}
}
else if ((targetCharacter = (targetBody.UserData as Character)) != null)
else if (targetBody.UserData is Character targetCharacter)
{
targetCharacter.AddDamage(CauseOfDeath.Damage, -LimbFixAmount * degreeOfSuccess, user);
#if CLIENT
Vector2 particlePos = ConvertUnits.ToDisplayUnits(pickedPosition);
if (targetCharacter.Submarine != null) particlePos += targetCharacter.Submarine.DrawPosition;
foreach (var emitter in ParticleEmitterHitCharacter)
{
float particleAngle = item.body.Rotation + ((item.body.Dir > 0.0f) ? 0.0f : MathHelper.Pi);
emitter.Emit(deltaTime, particlePos, item.CurrentHull, particleAngle + MathHelper.Pi, -particleAngle + MathHelper.Pi);
}
#endif
}
else if ((targetLimb = (targetBody.UserData as Limb)) != null)
{
targetLimb.character.AddDamage(CauseOfDeath.Damage, -LimbFixAmount * degreeOfSuccess, user);
Vector2 hitPos = ConvertUnits.ToDisplayUnits(pickedPosition);
if (targetCharacter.Submarine != null) hitPos += targetCharacter.Submarine.Position;
#if CLIENT
Vector2 particlePos = ConvertUnits.ToDisplayUnits(pickedPosition);
if (targetLimb.character.Submarine != null) particlePos += targetLimb.character.Submarine.DrawPosition;
foreach (var emitter in ParticleEmitterHitCharacter)
{
float particleAngle = item.body.Rotation + ((item.body.Dir > 0.0f) ? 0.0f : MathHelper.Pi);
emitter.Emit(deltaTime, particlePos, item.CurrentHull, particleAngle + MathHelper.Pi, -particleAngle + MathHelper.Pi);
}
#endif
targetCharacter.LastDamageSource = item;
targetCharacter.AddDamage(hitPos,
new List<Affliction>() { AfflictionPrefab.Burn.Instantiate(-LimbFixAmount * degreeOfSuccess, user) }, 0.0f, false, 0.0f, user);
FixCharacterProjSpecific(user, deltaTime, targetCharacter);
}
else if ((targetItem = (targetBody.UserData as Item)) != null)
else if (targetBody.UserData is Limb targetLimb)
{
targetLimb.character.LastDamageSource = item;
targetLimb.character.DamageLimb(targetLimb.WorldPosition, targetLimb,
new List<Affliction>() { AfflictionPrefab.Burn.Instantiate(-LimbFixAmount * degreeOfSuccess, user) }, 0.0f, false, 0.0f, user);
FixCharacterProjSpecific(user, deltaTime, targetLimb.character);
}
else if (targetBody.UserData is Item targetItem)
{
targetItem.IsHighlighted = true;
@@ -298,31 +238,27 @@ namespace Barotrauma.Items.Components
ApplyStatusEffectsOnTarget(deltaTime, ActionType.OnUse, targetItem.AllPropertyObjects);
#if CLIENT
if (item.Condition != prevCondition)
var levelResource = targetItem.GetComponent<LevelResource>();
if (levelResource != null && levelResource.IsActive &&
levelResource.HasRequiredItems(user, addMessage: false))
{
Vector2 progressBarPos = targetItem.DrawPosition;
var progressBar = user.UpdateHUDProgressBar(
targetItem,
progressBarPos,
targetItem.Condition / 100.0f,
levelResource.DeattachTimer += deltaTime;
#if CLIENT
Character.Controlled?.UpdateHUDProgressBar(
this,
targetItem.WorldPosition,
levelResource.DeattachTimer / levelResource.DeattachDuration,
Color.Red, Color.Green);
if (progressBar != null) progressBar.Size = new Vector2(60.0f, 20.0f);
Vector2 particlePos = ConvertUnits.ToDisplayUnits(pickedPosition);
if (targetItem.Submarine != null) particlePos += targetItem.Submarine.DrawPosition;
foreach (var emitter in ParticleEmitterHitItem)
{
float particleAngle = item.body.Rotation + ((item.body.Dir > 0.0f) ? 0.0f : MathHelper.Pi);
emitter.Emit(deltaTime, particlePos, item.CurrentHull, particleAngle + MathHelper.Pi, -particleAngle + MathHelper.Pi);
}
#endif
}
#endif
FixItemProjSpecific(user, deltaTime, targetItem, prevCondition);
}
}
partial void FixStructureProjSpecific(Character user, float deltaTime, Structure targetStructure, int sectionIndex);
partial void FixCharacterProjSpecific(Character user, float deltaTime, Character targetCharacter);
partial void FixItemProjSpecific(Character user, float deltaTime, Item targetItem, float prevCondition);
public override bool AIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective)
{
Gap leak = objective.OperateTarget as Gap;
@@ -332,17 +268,24 @@ namespace Barotrauma.Items.Components
//too far away -> consider this done and hope the AI is smart enough to move closer
if (dist > range * 5.0f) return true;
Vector2 gapDiff = leak.WorldPosition - item.WorldPosition;
if (!character.AnimController.InWater && character.AnimController is HumanoidAnimController &&
Math.Abs(gapDiff.X) < 100.0f && gapDiff.Y < 0.0f && gapDiff.Y > -150.0f)
{
((HumanoidAnimController)character.AnimController).Crouching = true;
}
//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));
new Vector2(Math.Sign(-gapDiff.X), 0.0f)
: new Vector2(0.0f, Math.Sign(-gapDiff.Y) * 0.5f);
standPos = leak.WorldPosition + standPos * range;
character.AIController.SteeringManager.SteeringManual(deltaTime, (standPos - character.WorldPosition) / 1000.0f);
character.AIController.SteeringManager.SteeringManual(deltaTime, (standPos - character.WorldPosition) / 1000.0f);
}
else
{
@@ -355,7 +298,21 @@ namespace Barotrauma.Items.Components
Use(deltaTime, character);
return leak.Open <= 0.0f;
bool leakFixed = leak.Open <= 0.0f || leak.Removed;
if (leakFixed && leak.FlowTargetHull != null)
{
if (!leak.FlowTargetHull.ConnectedGaps.Any(g => !g.IsRoomToRoom && g.Open > 0.0f))
{
character.Speak(TextManager.Get("DialogLeaksFixed").Replace("[roomname]", leak.FlowTargetHull.RoomName), null, 0.0f, "leaksfixed", 10.0f);
}
else
{
character.Speak(TextManager.Get("DialogLeakFixed").Replace("[roomname]", leak.FlowTargetHull.RoomName), null, 0.0f, "leakfixed", 10.0f);
}
}
return leakFixed;
}
private void ApplyStatusEffectsOnTarget(float deltaTime, ActionType actionType, List<ISerializableEntity> targets)
@@ -367,7 +324,7 @@ namespace Barotrauma.Items.Components
foreach (StatusEffect effect in statusEffects)
{
if (effect.Targets.HasFlag(StatusEffect.TargetType.UseTarget))
if (effect.HasTargetType(StatusEffect.TargetType.UseTarget))
{
effect.Apply(actionType, deltaTime, item, targets);
}
@@ -24,6 +24,10 @@ namespace Barotrauma.Items.Components
: base(item, element)
{
//throwForce = ToolBox.GetAttributeFloat(element, "throwforce", 1.0f);
if (aimPos == Vector2.Zero)
{
aimPos = new Vector2(0.6f, 0.1f);
}
}
public override bool Use(float deltaTime, Character character = null)
@@ -77,22 +81,21 @@ namespace Barotrauma.Items.Components
{
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);
throwPos = MathUtils.WrapAnglePi(System.Math.Min(throwPos + deltaTime * 5.0f, MathHelper.PiOver2));
ac.HoldItem(deltaTime, item, handlePos, aimPos, Vector2.Zero, false, throwPos);
}
else
{
ac.HoldItem(deltaTime, item, handlePos, holdPos, aimPos, false, holdAngle);
throwPos = 0;
ac.HoldItem(deltaTime, item, handlePos, holdPos, Vector2.Zero, false, holdAngle);
}
}
else
{
throwPos -= deltaTime * 15.0f;
throwPos = MathUtils.WrapAnglePi(throwPos - deltaTime * 15.0f);
ac.HoldItem(deltaTime, item, handlePos, aimPos, Vector2.Zero, false, throwPos);
ac.HoldItem(deltaTime, item, handlePos, new Vector2(0.6f, 0.0f), new Vector2(-0.3f, 0.2f), false, throwPos);
if (throwPos < -0.0)
if (throwPos < 0)
{
Vector2 throwVector = Vector2.Normalize(picker.CursorWorldPosition - picker.WorldPosition);
//throw upwards if cursor is at the position of the character
@@ -108,6 +111,7 @@ namespace Barotrauma.Items.Components
Limb rightHand = ac.GetLimb(LimbType.RightHand);
item.body.AngularVelocity = rightHand.body.AngularVelocity;
throwPos = 0;
throwDone = true;
ApplyStatusEffects(ActionType.OnSecondaryUse, deltaTime, picker); //Stun grenades, flares, etc. all have their throw-related things handled in "onSecondaryUse"
throwing = false;
@@ -5,6 +5,9 @@ using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Xml.Linq;
#if CLIENT
using Barotrauma.Sounds;
#endif
namespace Barotrauma.Items.Components
{
@@ -37,7 +40,7 @@ namespace Barotrauma.Items.Components
public readonly Dictionary<ActionType, List<StatusEffect>> statusEffectLists;
public List<RelatedItem> requiredItems;
public Dictionary<RelatedItem.RelationType, List<RelatedItem>> requiredItems;
public List<Skill> requiredSkills;
@@ -53,7 +56,7 @@ namespace Barotrauma.Items.Components
public float PickingTime
{
get;
private set;
set;
}
public readonly Dictionary<string, SerializableProperty> properties;
@@ -73,7 +76,7 @@ namespace Barotrauma.Items.Components
StopSounds(ActionType.OnActive);
}
#endif
if (AITarget != null) AITarget.Enabled = value;
isActive = value;
}
}
@@ -88,7 +91,7 @@ namespace Barotrauma.Items.Components
if (value == drawable) return;
if (!(this is IDrawableComponent))
{
DebugConsole.ThrowError("Couldn't make \""+this+"\" drawable (the component doesn't implement the IDrawableComponent interface)");
DebugConsole.ThrowError("Couldn't make \"" + this + "\" drawable (the component doesn't implement the IDrawableComponent interface)");
return;
}
@@ -150,6 +153,14 @@ namespace Barotrauma.Items.Components
set { characterUsable = value; }
}
//Remove item if combination results in 0 condition
[Serialize(true, false), Editable(ToolTip = "Can the properties of the component be edited in-game (only applicable if the component has in-game editable properties).")]
public bool AllowInGameEditing
{
get;
set;
}
public InputType PickKey
{
get;
@@ -185,22 +196,19 @@ namespace Barotrauma.Items.Components
get { return msg; }
set { msg = value; }
}
public AITarget AITarget
{
get;
private set;
}
public ItemComponent(Item item, XElement element)
{
this.item = item;
name = element.Name.ToString();
properties = SerializableProperty.GetProperties(this);
//canBePicked = ToolBox.GetAttributeBool(element, "canbepicked", false);
//canBeSelected = ToolBox.GetAttributeBool(element, "canbeselected", false);
//msg = ToolBox.GetAttributeString(element, "msg", "");
requiredItems = new List<RelatedItem>();
properties = SerializableProperty.GetProperties(this);
requiredItems = new Dictionary<RelatedItem.RelationType, List<RelatedItem>>();
requiredSkills = new List<Skill>();
#if CLIENT
@@ -234,24 +242,50 @@ namespace Barotrauma.Items.Components
}
properties = SerializableProperty.DeserializeProperties(this, element);
#if CLIENT
string msg = TextManager.Get(Msg, true);
if (msg != null)
{
foreach (InputType inputType in Enum.GetValues(typeof(InputType)))
{
msg = msg.Replace("[" + inputType.ToString().ToLowerInvariant() + "]", GameMain.Config.KeyBind(inputType).ToString());
}
Msg = msg;
}
#endif
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);
RelatedItem ri = RelatedItem.Load(subElement, item.Name);
if (ri != null)
{
if (!requiredItems.ContainsKey(ri.Type))
{
requiredItems.Add(ri.Type, new List<RelatedItem>());
}
requiredItems[ri.Type].Add(ri);
}
else
{
DebugConsole.ThrowError("Error in item config \"" + item.ConfigFile + "\" - component " + GetType().ToString() + " requires an item with no identifiers.");
}
break;
case "requiredskill":
case "requiredskills":
string skillName = subElement.GetAttributeString("name", "");
requiredSkills.Add(new Skill(skillName, subElement.GetAttributeInt("level", 0)));
if (subElement.Attribute("name") != null)
{
DebugConsole.ThrowError("Error in item config \"" + item.ConfigFile + "\" - skill requirement in component " + GetType().ToString() + " should use a skill identifier instead of the name of the skill.");
continue;
}
string skillIdentifier = subElement.GetAttributeString("identifier", "");
requiredSkills.Add(new Skill(skillIdentifier, subElement.GetAttributeInt("level", 0)));
break;
case "statuseffect":
var statusEffect = StatusEffect.Load(subElement);
var statusEffect = StatusEffect.Load(subElement, item.Name);
if (statusEffectLists == null) statusEffectLists = new Dictionary<ActionType, List<StatusEffect>>();
@@ -264,6 +298,12 @@ namespace Barotrauma.Items.Components
effectList.Add(statusEffect);
break;
case "aitarget":
AITarget = new AITarget(item, subElement)
{
Enabled = isActive
};
break;
default:
if (LoadElemProjSpecific(subElement)) break;
@@ -329,14 +369,14 @@ namespace Barotrauma.Items.Components
//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)
{
public virtual void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power = 0.0f, float signalStrength = 1.0f)
{
switch (connection.Name)
{
case "activate":
case "use":
case "trigger_in":
item.Use(1.0f);
break;
case "toggle":
@@ -394,10 +434,12 @@ namespace Barotrauma.Items.Components
public void Remove()
{
#if CLIENT
if (loopingSound != null)
if (loopingSoundChannel != null)
{
Sounds.SoundManager.Stop(loopingSoundIndex);
}
loopingSoundChannel.Dispose();
loopingSoundChannel = null;
}
if (GuiFrame != null) GUI.RemoveFromUpdateList(GuiFrame, true);
#endif
if (delayedCorrectionCoroutine != null)
@@ -406,6 +448,12 @@ namespace Barotrauma.Items.Components
delayedCorrectionCoroutine = null;
}
if (AITarget != null)
{
AITarget.Remove();
AITarget = null;
}
RemoveComponentSpecific();
}
@@ -416,11 +464,17 @@ namespace Barotrauma.Items.Components
public void ShallowRemove()
{
#if CLIENT
if (loopingSound != null)
if (loopingSoundChannel != null)
{
Sounds.SoundManager.Stop(loopingSoundIndex);
loopingSoundChannel.Dispose();
loopingSoundChannel = null;
}
#endif
if (AITarget != null)
{
AITarget.Remove();
AITarget = null;
}
ShallowRemoveComponentSpecific();
}
@@ -435,15 +489,14 @@ namespace Barotrauma.Items.Components
public bool HasRequiredSkills(Character character)
{
Skill temp;
return HasRequiredSkills(character, out temp);
return HasRequiredSkills(character, out Skill temp);
}
public bool HasRequiredSkills(Character character, out Skill insufficientSkill)
{
foreach (Skill skill in requiredSkills)
{
int characterLevel = character.GetSkillLevel(skill.Name);
float characterLevel = character.GetSkillLevel(skill.Identifier);
if (characterLevel < skill.Level)
{
insufficientSkill = skill;
@@ -458,39 +511,50 @@ namespace Barotrauma.Items.Components
/// 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)
public 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;
return DegreeOfSuccess(character, requiredSkills);
}
public virtual void FlipX() { }
/// <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>
public float DegreeOfSuccess(Character character, List<Skill> requiredSkills)
{
if (requiredSkills.Count == 0) return 1.0f;
if (character == null)
{
string errorMsg = "ItemComponent.DegreeOfSuccess failed (character was null).\n" + Environment.StackTrace;
DebugConsole.ThrowError(errorMsg);
GameAnalyticsManager.AddErrorEventOnce("ItemComponent.DegreeOfSuccess:CharacterNull", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
return 0.0f;
}
float skillSuccessSum = 0.0f;
for (int i = 0; i < requiredSkills.Count; i++)
{
float characterLevel = character.GetSkillLevel(requiredSkills[i].Identifier);
skillSuccessSum += (characterLevel - requiredSkills[i].Level);
}
float average = skillSuccessSum / requiredSkills.Count;
return ((average + 100.0f) / 2.0f) / 100.0f;
}
public virtual void FlipX(bool relativeToSub) { }
public virtual void FlipY(bool relativeToSub) { }
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)
if (!requiredItems.ContainsKey(RelatedItem.RelationType.Contained)) return true;
if (item.OwnInventory == null) return false;
foreach (RelatedItem ri in requiredItems[RelatedItem.RelationType.Contained])
{
Item containedItem = Array.Find(containedItems, x => x != null && x.Condition > 0.0f && ri.MatchesItem(x));
if (containedItem == null)
if (!item.OwnInventory.Items.Any(it => it != null && it.Condition > 0.0f && ri.MatchesItem(it)))
{
#if CLIENT
if (addMessage && !string.IsNullOrEmpty(ri.Msg)) GUI.AddMessage(ri.Msg, Color.Red);
@@ -507,57 +571,72 @@ namespace Barotrauma.Items.Components
if (!requiredItems.Any()) return true;
if (character.Inventory == null) return false;
foreach (RelatedItem ri in requiredItems)
if (requiredItems.ContainsKey(RelatedItem.RelationType.Equipped))
{
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)
foreach (RelatedItem ri in requiredItems[RelatedItem.RelationType.Equipped])
{
if (character.SelectedItems.FirstOrDefault(it => it != null && it.Condition > 0.0f && ri.MatchesItem(it)) == null)
{
#if CLIENT
if (addMessage && !string.IsNullOrEmpty(ri.Msg)) GUI.AddMessage(ri.Msg, Color.Red);
#endif
return false;
return false;
}
}
}
if (requiredItems.ContainsKey(RelatedItem.RelationType.Picked))
{
foreach (RelatedItem ri in requiredItems[RelatedItem.RelationType.Picked])
{
if (character.Inventory.Items.FirstOrDefault(it => it != null && it.Condition > 0.0f && ri.MatchesItem(it)) == null)
{
#if CLIENT
if (addMessage && !string.IsNullOrEmpty(ri.Msg)) GUI.AddMessage(ri.Msg, Color.Red);
#endif
return false;
}
}
}
return true;
}
public void ApplyStatusEffects(ActionType type, float deltaTime, Character character = null)
public void ApplyStatusEffects(ActionType type, float deltaTime, Character character = null, Limb targetLimb = null, Character user = null)
{
if (statusEffectLists == null) return;
List<StatusEffect> statusEffects;
if (!statusEffectLists.TryGetValue(type, out statusEffects)) return;
if (!statusEffectLists.TryGetValue(type, out List<StatusEffect> statusEffects)) return;
bool broken = item.Condition <= 0.0f;
foreach (StatusEffect effect in statusEffects)
{
item.ApplyStatusEffect(effect, type, deltaTime, character);
if (broken && effect.type != ActionType.OnBroken) { continue; }
if (user != null) { effect.SetUser(user); }
item.ApplyStatusEffect(effect, type, deltaTime, character, targetLimb, false, false);
}
}
public virtual void Load(XElement componentElement)
{
if (componentElement == null) return;
if (componentElement == null) return;
foreach (XAttribute attribute in componentElement.Attributes())
{
SerializableProperty property = null;
if (!properties.TryGetValue(attribute.Name.ToString().ToLowerInvariant(), out property)) continue;
if (!properties.TryGetValue(attribute.Name.ToString().ToLowerInvariant(), out SerializableProperty property)) continue;
property.TrySetValue(attribute.Value);
}
List<RelatedItem> prevRequiredItems = new List<RelatedItem>(requiredItems);
#if CLIENT
string msg = TextManager.Get(Msg, true);
if (msg != null)
{
foreach (InputType inputType in Enum.GetValues(typeof(InputType)))
{
msg = msg.Replace("[" + inputType.ToString().ToLowerInvariant() + "]", GameMain.Config.KeyBind(inputType).ToString());
}
Msg = msg;
}
#endif
var prevRequiredItems = new Dictionary<RelatedItem.RelationType, List<RelatedItem>>(requiredItems);
bool overrideRequiredItems = false;
foreach (XElement subElement in componentElement.Elements())
@@ -568,18 +647,22 @@ namespace Barotrauma.Items.Components
if (!overrideRequiredItems) requiredItems.Clear();
overrideRequiredItems = true;
RelatedItem newRequiredItem = RelatedItem.Load(subElement);
RelatedItem newRequiredItem = RelatedItem.Load(subElement, item.Name);
if (newRequiredItem == null) continue;
var prevRequiredItem = prevRequiredItems.Find(ri => ri.JoinedNames == newRequiredItem.JoinedNames);
if (prevRequiredItem!=null)
var prevRequiredItem = prevRequiredItems.ContainsKey(newRequiredItem.Type) ?
prevRequiredItems[newRequiredItem.Type].Find(ri => ri.JoinedIdentifiers == newRequiredItem.JoinedIdentifiers) : null;
if (prevRequiredItem != null)
{
newRequiredItem.statusEffects = prevRequiredItem.statusEffects;
newRequiredItem.Msg = prevRequiredItem.Msg;
}
requiredItems.Add(newRequiredItem);
if (!requiredItems.ContainsKey(newRequiredItem.Type))
{
requiredItems[newRequiredItem.Type] = new List<RelatedItem>();
}
requiredItems[newRequiredItem.Type].Add(newRequiredItem);
break;
}
}
@@ -594,7 +677,10 @@ namespace Barotrauma.Items.Components
/// Called when all the components of the item have been loaded. Use to initialize connections between components and such.
/// </summary>
public virtual void OnItemLoaded() { }
// TODO: Consider using generics, interfaces, or inheritance instead of reflection -> would be easier to debug when something changes/goes wrong.
// For example, currently we can edit the constructors but they will fail in runtime because the parameters are not changed here.
// It's also painful to find where the constructors are used, because the references exist only at runtime.
public static ItemComponent Load(XElement element, Item item, string file, bool errorMessages = true)
{
Type t;
@@ -604,7 +690,7 @@ namespace Barotrauma.Items.Components
// 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;
}
@@ -631,13 +717,11 @@ namespace Barotrauma.Items.Components
DebugConsole.ThrowError("Could not find the constructor of the component \"" + type + "\" (" + file + ")", e);
return null;
}
ItemComponent ic = null;
try
{
object[] lobject = new object[] { item, element };
object component = constructor.Invoke(lobject);
ic = (ItemComponent)component;
ic.name = element.Name.ToString();
}
@@ -656,13 +740,17 @@ namespace Barotrauma.Items.Components
{
XElement componentElement = new XElement(name);
foreach (RelatedItem ri in requiredItems)
foreach (var kvp in requiredItems)
{
XElement newElement = new XElement("requireditem");
ri.Save(newElement);
componentElement.Add(newElement);
foreach (RelatedItem ri in kvp.Value)
{
XElement newElement = new XElement("requireditem");
ri.Save(newElement);
componentElement.Add(newElement);
}
}
SerializableProperty.SerializeProperties(this, componentElement);
parentElement.Add(componentElement);
@@ -8,8 +8,6 @@ namespace Barotrauma.Items.Components
{
partial class ItemContainer : ItemComponent, IDrawableComponent
{
public const int MaxInventoryCount = 4;
private List<RelatedItem> containableItems;
public ItemInventory Inventory;
@@ -18,14 +16,15 @@ namespace Barotrauma.Items.Components
private ushort[] itemIds;
//how many items can be contained
private int capacity;
[Serialize(5, false)]
public int Capacity
{
get { return capacity; }
set { capacity = Math.Max(value, 1); }
}
private int capacity;
private bool hideItems;
[Serialize(true, false)]
public bool HideItems
{
@@ -36,61 +35,26 @@ namespace Barotrauma.Items.Components
Drawable = !hideItems;
}
}
private bool hideItems;
[Serialize(false, false)]
[Serialize(true, false)]
public bool DrawInventory
{
get { return drawInventory; }
set { drawInventory = value; }
get;
set;
}
private bool drawInventory;
//the position of the first item in the container
[Serialize("0.0,0.0", false)]
public Vector2 ItemPos
[Serialize(false, false)]
public bool AutoInteractWithContained
{
get { return itemPos; }
set { itemPos = value; }
get;
set;
}
private Vector2 itemPos;
//item[i].Pos = itemPos + itemInterval*i
[Serialize("0.0,0.0", false)]
public Vector2 ItemInterval
{
get { return itemInterval; }
set { itemInterval = value; }
}
private Vector2 itemInterval;
[Serialize(0.0f, false)]
public float ItemRotation
{
get { return MathHelper.ToDegrees(itemRotation); }
set { itemRotation = MathHelper.ToRadians(value); }
}
private float itemRotation;
[Serialize("0.5,0.9", false)]
public Vector2 HudPos
{
get { return hudPos; }
set
{
hudPos = value;
}
}
private Vector2 hudPos;
[Serialize("0.5,0.5", false)]
public Vector2 HudPos { get; set; }
[Serialize(5, false)]
public int SlotsPerRow
{
get { return slotsPerRow; }
set { slotsPerRow = value; }
}
private int slotsPerRow;
public int SlotsPerRow { get; set; }
public List<RelatedItem> ContainableItems
{
@@ -100,7 +64,7 @@ namespace Barotrauma.Items.Components
public ItemContainer(Item item, XElement element)
: base (item, element)
{
Inventory = new ItemInventory(item, this, capacity, hudPos, slotsPerRow);
Inventory = new ItemInventory(item, this, capacity, HudPos, SlotsPerRow);
containableItems = new List<RelatedItem>();
foreach (XElement subElement in element.Elements())
@@ -108,18 +72,24 @@ namespace Barotrauma.Items.Components
switch (subElement.Name.ToString().ToLowerInvariant())
{
case "containable":
RelatedItem containable = RelatedItem.Load(subElement);
if (containable == null) continue;
RelatedItem containable = RelatedItem.Load(subElement, item.Name);
if (containable == null)
{
DebugConsole.ThrowError("Error in item config \"" + item.ConfigFile + "\" - containable with no identifiers.");
continue;
}
containableItems.Add(containable);
break;
}
}
InitProjSpecific(element);
itemsWithStatusEffects = new List<Pair<Item, StatusEffect>>();
}
partial void InitProjSpecific(XElement element);
public void OnItemContained(Item containedItem)
{
item.SetContainedItemPositions();
@@ -129,7 +99,7 @@ namespace Barotrauma.Items.Components
{
foreach (StatusEffect effect in ri.statusEffects)
{
itemsWithStatusEffects.Add(Pair<Item, StatusEffect>.Create(containedItem, effect));
itemsWithStatusEffects.Add(new Pair<Item, StatusEffect>(containedItem, effect));
}
}
@@ -167,15 +137,43 @@ namespace Barotrauma.Items.Components
StatusEffect effect = itemAndEffect.Second;
if (effect.Targets.HasFlag(StatusEffect.TargetType.This))
if (effect.HasTargetType(StatusEffect.TargetType.This))
effect.Apply(ActionType.OnContaining, deltaTime, item, item.AllPropertyObjects);
if (effect.Targets.HasFlag(StatusEffect.TargetType.Contained))
if (effect.HasTargetType(StatusEffect.TargetType.Contained))
effect.Apply(ActionType.OnContaining, deltaTime, item, contained.AllPropertyObjects);
}
}
public override bool Select(Character character)
{
if (AutoInteractWithContained)
{
foreach (Item contained in Inventory.Items)
{
if (contained == null) continue;
if (contained.TryInteract(character))
{
return false;
}
}
}
return base.Select(character);
}
public override bool Pick(Character picker)
{
if (AutoInteractWithContained)
{
foreach (Item contained in Inventory.Items)
{
if (contained == null) continue;
if (contained.TryInteract(picker))
{
return true;
}
}
}
return (picker != null);
}
@@ -239,6 +237,11 @@ namespace Barotrauma.Items.Components
Item item = Entity.FindEntityByID(itemIds[i]) as Item;
if (item == null) continue;
if (i >= Inventory.Capacity)
{
continue;
}
Inventory.TryPutItem(item, i, false, false, null, false);
}
@@ -251,12 +254,53 @@ namespace Barotrauma.Items.Components
protected override void RemoveComponentSpecific()
{
#if CLIENT
inventoryTopSprite?.Remove();
inventoryBackSprite?.Remove();
inventoryBottomSprite?.Remove();
ContainedStateIndicator?.Remove();
if (Screen.Selected == GameMain.SubEditorScreen && !Submarine.Unloading)
{
string itemNames = string.Empty;
foreach (Item item in Inventory.Items)
{
if (item == null) continue;
itemNames += item.Name + "\n";
}
if (itemNames.Length > 0)
{
var msgBox = new GUIMessageBox(Item.Name, TextManager.Get("DeletingContainerWithItems") + itemNames, new string[] { TextManager.Get("Yes"), TextManager.Get("No") });
msgBox.Buttons[0].OnClicked = (btn, userdata) =>
{
Inventory.DeleteAllItems();
msgBox.Close();
return true;
};
msgBox.Buttons[1].OnClicked = (btn, userdata) =>
{
foreach (Item item in Inventory.Items)
{
if (item == null) continue;
item.Drop();
}
msgBox.Close();
return true;
};
}
return;
}
#endif
foreach (Item item in Inventory.Items)
{
if (item == null) continue;
item.Drop();
}
}
}
}
public override void Load(XElement componentElement)
{
@@ -2,5 +2,14 @@
{
partial class ItemLabel : ItemComponent, IDrawableComponent
{
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power = 0, float signalStrength = 1)
{
switch (connection.Name)
{
case "set_text":
Text = signal;
break;
}
}
}
}
@@ -19,10 +19,9 @@ namespace Barotrauma.Items.Components
public override bool Select(Character character)
{
if (character == null || character.LockHands || character.Removed) return false;
if (character == null || character.LockHands || character.Removed || !(character.AnimController is HumanoidAnimController)) return false;
character.AnimController.Anim = AnimController.Animation.Climbing;
return true;
}
@@ -2,6 +2,7 @@
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Xml.Linq;
namespace Barotrauma.Items.Components
@@ -146,7 +147,7 @@ namespace Barotrauma.Items.Components
limb.Disabled = true;
Vector2 worldPosition = lb.position + new Vector2(item.WorldRect.X, item.WorldRect.Y);
Vector2 worldPosition = new Vector2(item.WorldRect.X, item.WorldRect.Y) + lb.position * item.Scale;
Vector2 diff = worldPosition - limb.WorldPosition;
limb.PullJointEnabled = true;
@@ -225,7 +226,7 @@ namespace Barotrauma.Items.Components
Turret turret = targetItem.GetComponent<Turret>();
if (turret != null)
{
centerPos = new Vector2(targetItem.WorldRect.X + turret.BarrelPos.X, targetItem.WorldRect.Y - turret.BarrelPos.Y);
centerPos = new Vector2(targetItem.WorldRect.X + turret.TransformedBarrelPos.X, targetItem.WorldRect.Y - turret.TransformedBarrelPos.Y);
}
}
@@ -240,7 +241,7 @@ namespace Barotrauma.Items.Components
private Item GetFocusTarget()
{
item.SendSignal(0, targetRotation.ToString(), "position_out", character);
item.SendSignal(0, MathHelper.ToDegrees(targetRotation).ToString("G", CultureInfo.InvariantCulture), "position_out", character);
for (int i = item.LastSentSignalRecipients.Count - 1; i >= 0; i--)
{
@@ -259,7 +260,7 @@ namespace Barotrauma.Items.Components
item.SendSignal(0, "1", "signal_out", picker);
#if CLIENT
PlaySound(ActionType.OnUse, item.WorldPosition);
PlaySound(ActionType.OnUse, item.WorldPosition, picker);
#endif
return true;
@@ -308,7 +309,7 @@ namespace Barotrauma.Items.Components
return true;
}
public override void FlipX()
public override void FlipX(bool relativeToSub)
{
if (dir != Direction.None)
{
@@ -319,16 +320,32 @@ namespace Barotrauma.Items.Components
for (int i = 0; i < limbPositions.Count; i++)
{
float diff = (item.Rect.X + limbPositions[i].position.X) - item.Rect.Center.X;
float diff = (item.Rect.X + limbPositions[i].position.X * item.Scale) - item.Rect.Center.X;
Vector2 flippedPos =
new Vector2(
item.Rect.Center.X - diff - item.Rect.X,
(item.Rect.Center.X - diff - item.Rect.X) / item.Scale,
limbPositions[i].position.Y);
limbPositions[i] = new LimbPos(limbPositions[i].limbType, flippedPos);
}
}
public override void FlipY(bool relativeToSub)
{
userPos.Y = -UserPos.Y;
for (int i = 0; i < limbPositions.Count; i++)
{
float diff = (item.Rect.Y + limbPositions[i].position.Y) - item.Rect.Center.Y;
Vector2 flippedPos =
new Vector2(
limbPositions[i].position.X,
item.Rect.Center.Y - diff - item.Rect.Y);
limbPositions[i] = new LimbPos(limbPositions[i].limbType, flippedPos);
}
}
}
}
@@ -1,6 +1,5 @@
using Barotrauma.Networking;
using Lidgren.Network;
using Microsoft.Xna.Framework;
using System;
using System.Linq;
using System.Xml.Linq;
@@ -9,98 +8,148 @@ namespace Barotrauma.Items.Components
{
partial class Deconstructor : Powered, IServerSerializable, IClientSerializable
{
float progressTimer;
private float progressTimer;
private float progressState;
ItemContainer container;
private ItemContainer inputContainer, outputContainer;
public ItemContainer OutputContainer
{
get { return outputContainer; }
}
public Deconstructor(Item item, XElement element)
: base(item, element)
{
#if CLIENT
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;
#endif
InitProjSpecific(element);
}
partial void InitProjSpecific(XElement element);
public override void OnItemLoaded()
{
var containers = item.GetComponents<ItemContainer>().ToList();
if (containers.Count < 2)
{
DebugConsole.ThrowError("Error in item \"" + item.Name + "\": Deconstructors must have two ItemContainer components!");
return;
}
inputContainer = containers[0];
outputContainer = containers[1];
OnItemLoadedProjSpecific();
}
partial void OnItemLoadedProjSpecific();
public override void Update(float deltaTime, Camera cam)
{
if (container == null || container.Inventory.Items.All(i => i == null))
MoveInputQueue();
if (inputContainer == null || inputContainer.Inventory.Items.All(i => i == null))
{
SetActive(false);
return;
}
if (voltage < minVoltage) return;
ApplyStatusEffects(ActionType.OnActive, deltaTime, null);
if (powerConsumption == 0.0f) voltage = 1.0f;
progressTimer += deltaTime*voltage;
progressTimer += deltaTime * voltage;
Voltage -= deltaTime * 10.0f;
var targetItem = container.Inventory.Items.FirstOrDefault(i => i != null);
#if CLIENT
progressBar.BarSize = Math.Min(progressTimer / targetItem.Prefab.DeconstructTime, 1.0f);
#endif
if (progressTimer>targetItem.Prefab.DeconstructTime)
var targetItem = inputContainer.Inventory.Items.LastOrDefault(i => i != null);
if (targetItem == null) { return; }
progressState = 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)
{
float percentageHealth = targetItem.Condition / targetItem.Prefab.Health;
if (percentageHealth <= deconstructProduct.MinCondition || percentageHealth > deconstructProduct.MaxCondition) continue;
var itemPrefab = MapEntityPrefab.Find(deconstructProduct.ItemPrefabName) as ItemPrefab;
var itemPrefab = MapEntityPrefab.Find(null, deconstructProduct.ItemIdentifier) as ItemPrefab;
if (itemPrefab == null)
{
DebugConsole.ThrowError("Tried to deconstruct item \"" + targetItem.Name + "\" but couldn't find item prefab \"" + deconstructProduct + "\"!");
DebugConsole.ThrowError("Tried to deconstruct item \"" + targetItem.Name + "\" but couldn't find item prefab \"" + deconstructProduct.ItemIdentifier + "\"!");
continue;
}
float condition = deconstructProduct.CopyCondition ?
percentageHealth * itemPrefab.Health :
itemPrefab.Health * deconstructProduct.OutCondition;
//container full, drop the items outside the deconstructor
if (containers[1].Inventory.Items.All(i => i != null))
if (outputContainer.Inventory.Items.All(i => i != null))
{
Entity.Spawner.AddToSpawnQueue(itemPrefab, item.Position, item.Submarine, itemPrefab.Health * deconstructProduct.OutCondition);
Entity.Spawner.AddToSpawnQueue(itemPrefab, item.Position, item.Submarine, condition);
}
else
{
Entity.Spawner.AddToSpawnQueue(itemPrefab, containers[1].Inventory, itemPrefab.Health * deconstructProduct.OutCondition);
Entity.Spawner.AddToSpawnQueue(itemPrefab, outputContainer.Inventory, condition);
}
}
container.Inventory.RemoveItem(targetItem);
inputContainer.Inventory.RemoveItem(targetItem);
Entity.Spawner.AddToRemoveQueue(targetItem);
MoveInputQueue();
PutItemsToLinkedContainer();
if (container.Inventory.Items.Any(i => i != null))
if (inputContainer.Inventory.Items.Any(i => i != null))
{
progressTimer = 0.0f;
#if CLIENT
progressBar.BarSize = 0.0f;
#endif
}
}
}
private void PutItemsToLinkedContainer()
{
if (GameMain.Client != null) { return; }
if (outputContainer.Inventory.Items.All(it => it == null)) return;
foreach (MapEntity linkedTo in item.linkedTo)
{
if (linkedTo is Item linkedItem)
{
var fabricator = linkedItem.GetComponent<Fabricator>();
if (fabricator != null) { continue; }
var itemContainer = linkedItem.GetComponent<ItemContainer>();
if (itemContainer == null) { continue; }
foreach (Item containedItem in outputContainer.Inventory.Items)
{
if (containedItem == null) { continue; }
if (itemContainer.Inventory.Items.All(it => it != null)) { break; }
itemContainer.Inventory.TryPutItem(containedItem, user: null, createNetworkEvent: true);
}
}
}
}
/// <summary>
/// Move items towards the last slot in the inventory if there's free slots
/// </summary>
private void MoveInputQueue()
{
for (int i = inputContainer.Inventory.Capacity - 2; i >= 0; i--)
{
if (inputContainer.Inventory.Items[i] != null && inputContainer.Inventory.Items[i + 1] == null)
{
inputContainer.Inventory.TryPutItem(inputContainer.Inventory.Items[i], i + 1, allowSwapping: false, allowCombine: false, user: null, createNetworkEvent: 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;
}
PutItemsToLinkedContainer();
if (container.Inventory.Items.All(i => i == null)) active = false;
if (inputContainer.Inventory.Items.All(i => i == null)) { active = false; }
IsActive = active;
@@ -109,22 +158,21 @@ namespace Barotrauma.Items.Components
GameServer.Log(user.LogName + (IsActive ? " activated " : " deactivated ") + item.Name, ServerLog.MessageType.ItemInteraction);
}
if (!IsActive) { progressState = 0.0f; }
#if CLIENT
if (!IsActive)
{
progressBar.BarSize = 0.0f;
progressTimer = 0.0f;
activateButton.Text = "Deconstruct";
activateButton.Text = TextManager.Get("DeconstructorDeconstruct");
}
else
{
activateButton.Text = "Cancel";
activateButton.Text = TextManager.Get("DeconstructorCancel");
}
#endif
container.Inventory.Locked = IsActive;
inputContainer.Inventory.Locked = IsActive;
}
public void ServerRead(ClientNetObject type, NetBuffer msg, Client c)
@@ -2,10 +2,12 @@
using System;
using System.Globalization;
using System.Xml.Linq;
using Barotrauma.Networking;
using Lidgren.Network;
namespace Barotrauma.Items.Components
{
partial class Engine : Powered
partial class Engine : Powered, IServerSerializable, IClientSerializable
{
private float force;
@@ -13,6 +15,14 @@ namespace Barotrauma.Items.Components
private float maxForce;
private Attack propellerDamage;
private float damageTimer;
private bool hasPower;
private float prevVoltage;
[Editable(0.0f, 10000000.0f, ToolTip = "The amount of force exerted on the submarine when the engine is operating at full power."),
Serialize(2000.0f, true)]
public float MaxForce
@@ -24,56 +34,70 @@ namespace Barotrauma.Items.Components
}
}
[Editable, Serialize("0.0,0.0", true)]
public Vector2 PropellerPos
{
get;
set;
}
public float Force
{
get { return force;}
set { force = MathHelper.Clamp(value, -100.0f, 100.0f); }
}
public float CurrentVolume
{
get { return Math.Abs((force / 100.0f) * (minVoltage <= 0.0f ? 1.0f : Math.Min(prevVoltage / minVoltage, 1.0f))); }
}
public Engine(Item item, XElement element)
: base(item, element)
{
IsActive = true;
#if CLIENT
var button = new GUIButton(new Rectangle(160, 50, 30, 30), "-", "", GuiFrame);
button.OnClicked = (GUIButton btn, object obj) =>
foreach (XElement subElement in element.Elements())
{
targetForce -= 1.0f;
return true;
};
switch (subElement.Name.ToString().ToLowerInvariant())
{
case "propellerdamage":
propellerDamage = new Attack(subElement, item.Name + ", Engine");
break;
}
}
button = new GUIButton(new Rectangle(200, 50, 30, 30), "+", "", GuiFrame);
button.OnClicked = (GUIButton btn, object obj) =>
{
targetForce += 1.0f;
return true;
};
#endif
}
public float CurrentVolume
{
get { return Math.Abs((force / 100.0f) * Math.Min(voltage / minVoltage, 1.0f)); }
InitProjSpecific(element);
}
partial void InitProjSpecific(XElement element);
public override void Update(float deltaTime, Camera cam)
{
UpdateOnActiveEffects(deltaTime);
UpdateAnimation(deltaTime);
currPowerConsumption = Math.Abs(targetForce) / 100.0f * powerConsumption;
//pumps consume more power when in a bad condition
currPowerConsumption *= MathHelper.Lerp(2.0f, 1.0f, item.Condition / 100.0f);
if (powerConsumption == 0.0f) voltage = 1.0f;
prevVoltage = voltage;
hasPower = voltage > minVoltage;
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 * Math.Min(voltage / minVoltage, 1.0f), 0.0f);
//less effective when in a bad condition
currForce *= MathHelper.Lerp(0.5f, 2.0f, item.Condition / 100.0f);
item.Submarine.ApplyForce(currForce);
UpdatePropellerDamage(deltaTime);
if (item.CurrentHull != null)
{
item.CurrentHull.AiTarget.SoundRange = Math.Max(currForce.Length(), item.CurrentHull.AiTarget.SoundRange);
@@ -82,7 +106,7 @@ namespace Barotrauma.Items.Components
#if CLIENT
for (int i = 0; i < 5; i++)
{
GameMain.ParticleManager.CreateParticle("bubbles", item.WorldPosition - (Vector2.UnitX * item.Rect.Width/2),
GameMain.ParticleManager.CreateParticle("bubbles", item.WorldPosition + PropellerPos,
-currForce / 5.0f + new Vector2(Rand.Range(-100.0f, 100.0f), Rand.Range(-50f, 50f)),
0.0f, item.CurrentHull);
}
@@ -91,24 +115,69 @@ namespace Barotrauma.Items.Components
voltage = 0.0f;
}
private void UpdatePropellerDamage(float deltaTime)
{
damageTimer += deltaTime;
if (damageTimer < 0.5f) return;
damageTimer = 0.1f;
if (propellerDamage == null) return;
Vector2 propellerWorldPos = item.WorldPosition + PropellerPos;
foreach (Character character in Character.CharacterList)
{
if (character.Submarine != null || !character.Enabled || character.Removed) continue;
float dist = Vector2.DistanceSquared(character.WorldPosition, propellerWorldPos);
if (dist > propellerDamage.DamageRange * propellerDamage.DamageRange) continue;
character.LastDamageSource = item;
propellerDamage.DoDamage(null, character, propellerWorldPos, 1.0f, true);
}
}
partial void UpdateAnimation(float deltaTime);
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)
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power = 0.0f, float signalStrength = 1.0f)
{
base.ReceiveSignal(stepsTaken, signal, connection, source, sender, power);
base.ReceiveSignal(stepsTaken, signal, connection, source, sender, power, signalStrength);
if (connection.Name == "set_force")
{
float tempForce;
if (float.TryParse(signal, NumberStyles.Float, CultureInfo.InvariantCulture, out tempForce))
if (float.TryParse(signal, NumberStyles.Float, CultureInfo.InvariantCulture, out float tempForce))
{
targetForce = MathHelper.Clamp(tempForce, -100.0f, 100.0f);
}
}
}
public void ServerWrite(NetBuffer msg, Client c, object[] extraData = null)
{
//force can only be adjusted at 10% intervals -> no need for more accuracy than this
msg.WriteRangedInteger(-10, 10, (int)(targetForce / 10.0f));
}
public void ServerRead(ClientNetObject type, NetBuffer msg, Client c)
{
float newTargetForce = msg.ReadRangedInteger(-10, 10) * 10.0f;
if (item.CanClientAccess(c))
{
if (Math.Abs(newTargetForce - targetForce) > 0.01f)
{
GameServer.Log(c.Character.LogName + " set the force of " + item.Name + " to " + (int)(newTargetForce) + " %", ServerLog.MessageType.ItemInteraction);
}
targetForce = newTargetForce;
}
//notify all clients of the changed state
item.CreateServerEvent(this);
}
}
}
@@ -10,91 +10,113 @@ namespace Barotrauma.Items.Components
{
class FabricableItem
{
public readonly ItemPrefab TargetItem;
public class RequiredItem
{
public readonly ItemPrefab ItemPrefab;
public int Amount;
public readonly float MinCondition;
public readonly bool UseCondition;
//TODO: refactor this (maybe make it a struct)
public readonly List<Tuple<ItemPrefab, int, float, bool>> RequiredItems;
public RequiredItem(ItemPrefab itemPrefab, int amount, float minCondition, bool useCondition)
{
ItemPrefab = itemPrefab;
Amount = amount;
MinCondition = minCondition;
UseCondition = useCondition;
}
}
public readonly ItemPrefab TargetItem;
public readonly string DisplayName;
public readonly List<RequiredItem> RequiredItems;
public readonly float RequiredTime;
public readonly float OutCondition; //Percentage-based from 0 to 1
public readonly List<Skill> RequiredSkills;
public FabricableItem(XElement element)
{
string name = element.GetAttributeString("name", "");
TargetItem = MapEntityPrefab.Find(name) as ItemPrefab;
if (TargetItem == null)
if (element.Attribute("name") != null)
{
return;
string name = element.Attribute("name").Value;
DebugConsole.ThrowError("Error in fabricable item config (" + name + ") - use item identifiers instead of names");
TargetItem = MapEntityPrefab.Find(name) as ItemPrefab;
if (TargetItem == null)
{
DebugConsole.ThrowError("Error in fabricable item config - item prefab \"" + name + "\" not found.");
return;
}
}
else
{
string identifier = element.GetAttributeString("identifier", "");
TargetItem = MapEntityPrefab.Find(null, identifier) as ItemPrefab;
if (TargetItem == null)
{
DebugConsole.ThrowError("Error in fabricable item config - item prefab \"" + identifier + "\" not found.");
return;
}
}
string displayName = element.GetAttributeString("displayname", "");
DisplayName = string.IsNullOrEmpty(displayName) ? TargetItem.Name : TextManager.Get(displayName);
RequiredSkills = new List<Skill>();
RequiredTime = element.GetAttributeFloat("requiredtime", 1.0f);
OutCondition = element.GetAttributeFloat("outcondition", 1.0f);
RequiredItems = new List<Tuple<ItemPrefab, int, float, bool>>();
//Backwards compatibility for string lists
string[] requiredItemNames = element.GetAttributeString("requireditems", "").Split(',');
foreach (string requiredItemName in requiredItemNames)
{
if (string.IsNullOrWhiteSpace(requiredItemName)) continue;
ItemPrefab requiredItem = MapEntityPrefab.Find(requiredItemName.Trim()) 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, float, bool>(requiredItem, 1, 1.0f, false));
}
else
{
RequiredItems.Remove(existing);
RequiredItems.Add(new Tuple<ItemPrefab, int, float, bool>(requiredItem, existing.Item2 + 1, 1.0f, false));
}
}
RequiredItems = new List<RequiredItem>();
foreach (XElement subElement in element.Elements())
{
switch (subElement.Name.ToString().ToLowerInvariant())
{
case "requiredskill":
if (subElement.Attribute("name") != null)
{
DebugConsole.ThrowError("Error in fabricable item " + TargetItem.Name + "! Use skill identifiers instead of names.");
continue;
}
RequiredSkills.Add(new Skill(
subElement.GetAttributeString("name", ""),
subElement.GetAttributeString("identifier", ""),
subElement.GetAttributeInt("level", 0)));
break;
case "item": //New system allowing for setting minimal item condition
string requiredItemName = subElement.GetAttributeString("name", "");
case "item":
case "requireditem":
string requiredItemIdentifier = subElement.GetAttributeString("identifier", "");
if (string.IsNullOrWhiteSpace(requiredItemIdentifier))
{
DebugConsole.ThrowError("Error in fabricable item " + TargetItem.Name + "! One of the required items has no identifier.");
continue;
}
float minCondition = subElement.GetAttributeFloat("mincondition", 1.0f);
//Substract mincondition from required item's condition or delete it regardless?
bool useCondition = subElement.GetAttributeBool("usecondition", true);
int count = subElement.GetAttributeInt("count", 1);
if (string.IsNullOrWhiteSpace(requiredItemName)) continue;
ItemPrefab requiredItem = MapEntityPrefab.Find(requiredItemName.Trim()) as ItemPrefab;
ItemPrefab requiredItem = MapEntityPrefab.Find(null, requiredItemIdentifier.Trim()) as ItemPrefab;
if (requiredItem == null)
{
DebugConsole.ThrowError("Error in fabricable item " + name + "! Required item \"" + requiredItemName + "\" not found.");
DebugConsole.ThrowError("Error in fabricable item " + TargetItem.Name + "! Required item \"" + requiredItemIdentifier + "\" not found.");
continue;
}
var existing = RequiredItems.Find(r => r.Item1 == requiredItem);
var existing = RequiredItems.Find(r => r.ItemPrefab == requiredItem);
if (existing == null)
{
RequiredItems.Add(new Tuple<ItemPrefab, int, float, bool>(requiredItem, count, minCondition, useCondition));
RequiredItems.Add(new RequiredItem(requiredItem, count, minCondition, useCondition));
}
else
{
RequiredItems.Remove(existing);
RequiredItems.Add(new Tuple<ItemPrefab, int, float, bool>(requiredItem, existing.Item2 + count, minCondition, useCondition));
RequiredItems.Add(new RequiredItem(requiredItem, existing.Amount + count, minCondition, useCondition));
}
break;
@@ -106,15 +128,20 @@ namespace Barotrauma.Items.Components
partial class Fabricator : Powered, IServerSerializable, IClientSerializable
{
public const float SkillIncreaseMultiplier = 0.5f;
private List<FabricableItem> fabricableItems;
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;
private float requiredTime;
private Character user;
private ItemContainer inputContainer, outputContainer;
private float progressState;
public Fabricator(Item item, XElement element)
: base(item, element)
{
@@ -138,89 +165,88 @@ namespace Barotrauma.Items.Components
InitProjSpecific();
}
public override void OnItemLoaded()
{
var containers = item.GetComponents<ItemContainer>().ToList();
if (containers.Count < 2)
{
DebugConsole.ThrowError("Error in item \"" + item.Name + "\": Fabricators must have two ItemContainer components!");
return;
}
inputContainer = containers[0];
outputContainer = containers[1];
foreach (FabricableItem fabricableItem in fabricableItems)
{
int ingredientCount = fabricableItem.RequiredItems.Sum(it => it.Amount);
if (ingredientCount > inputContainer.Capacity)
{
DebugConsole.ThrowError("Error in item \"" + item.Name + "\": There's not enough room in the input inventory for the ingredients of \"" + fabricableItem.TargetItem.Name + "\"!");
}
}
OnItemLoadedProjSpecific();
}
partial void OnItemLoadedProjSpecific();
partial void InitProjSpecific();
public override bool Select(Character character)
{
CheckFabricableItems(character);
#if CLIENT
if (itemList.Selected != null)
{
SelectItem(itemList.Selected, itemList.Selected.UserData);
}
#endif
SelectProjSpecific(character);
return base.Select(character);
}
partial void SelectProjSpecific(Character 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)
{
#if CLIENT
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);
}
#endif
var itemContainer = item.GetComponent<ItemContainer>();
prevContainedItems = new Item[itemContainer.Inventory.Items.Length];
itemContainer.Inventory.Items.CopyTo(prevContainedItems, 0);
}
private void StartFabricating(FabricableItem selectedItem, Character user = null)
private void StartFabricating(FabricableItem selectedItem, Character user)
{
if (selectedItem == null) return;
if (user != null)
{
GameServer.Log(user.LogName + " started fabricating " + selectedItem.TargetItem.Name + " in " + item.Name, ServerLog.MessageType.ItemInteraction);
GameServer.Log(user.LogName + " started fabricating " + selectedItem.DisplayName + " in " + item.Name, ServerLog.MessageType.ItemInteraction);
}
#if CLIENT
itemList.Enabled = false;
activateButton.Text = "Cancel";
activateButton.Text = TextManager.Get("FabricatorCancel");
#endif
MoveIngredientsToInputContainer(selectedItem);
fabricatedItem = selectedItem;
IsActive = true;
timeUntilReady = fabricatedItem.RequiredTime;
var containers = item.GetComponents<ItemContainer>();
containers[0].Inventory.Locked = true;
containers[1].Inventory.Locked = true;
this.user = user;
requiredTime = GetRequiredTime(fabricatedItem, user);
timeUntilReady = requiredTime;
inputContainer.Inventory.Locked = true;
outputContainer.Inventory.Locked = true;
currPowerConsumption = powerConsumption;
currPowerConsumption *= MathHelper.Lerp(2.0f, 1.0f, item.Condition / 100.0f);
}
private void CancelFabricating(Character user = null)
{
if (fabricatedItem != null && user != null)
{
GameServer.Log(user.LogName + " cancelled the fabrication of " + fabricatedItem.TargetItem.Name + " in " + item.Name, ServerLog.MessageType.ItemInteraction);
GameServer.Log(user.LogName + " cancelled the fabrication of " + fabricatedItem.DisplayName + " in " + item.Name, ServerLog.MessageType.ItemInteraction);
}
IsActive = false;
fabricatedItem = null;
this.user = null;
currPowerConsumption = 0.0f;
@@ -228,100 +254,178 @@ namespace Barotrauma.Items.Components
itemList.Enabled = true;
if (activateButton != null)
{
activateButton.Text = "Create";
activateButton.Text = TextManager.Get("FabricatorCreate");
}
if (progressBar != null) progressBar.BarSize = 0.0f;
#endif
progressState = 0.0f;
timeUntilReady = 0.0f;
var containers = item.GetComponents<ItemContainer>();
containers[0].Inventory.Locked = false;
containers[1].Inventory.Locked = false;
inputContainer.Inventory.Locked = false;
outputContainer.Inventory.Locked = false;
}
public override void Update(float deltaTime, Camera cam)
{
if (fabricatedItem == null)
if (fabricatedItem == null || !CanBeFabricated(fabricatedItem))
{
CancelFabricating();
return;
}
#if CLIENT
if (progressBar != null)
{
progressBar.BarSize = fabricatedItem == null ? 0.0f : (fabricatedItem.RequiredTime - timeUntilReady) / fabricatedItem.RequiredTime;
}
#endif
progressState = fabricatedItem == null ? 0.0f : (requiredTime - timeUntilReady) / requiredTime;
if (voltage < minVoltage) return;
if (voltage < minVoltage) { return; }
ApplyStatusEffects(ActionType.OnActive, deltaTime, null);
if (powerConsumption == 0) voltage = 1.0f;
timeUntilReady -= deltaTime*voltage;
if (powerConsumption <= 0) { voltage = 1.0f; }
timeUntilReady -= deltaTime * voltage;
voltage -= deltaTime * 10.0f;
if (timeUntilReady > 0.0f) return;
if (timeUntilReady > 0.0f) { return; }
var containers = item.GetComponents<ItemContainer>();
if (containers.Count < 2)
var availableIngredients = GetAvailableIngredients();
foreach (FabricableItem.RequiredItem ingredient in fabricatedItem.RequiredItems)
{
DebugConsole.ThrowError("Error while fabricating a new item: fabricators must have two ItemContainer components");
return;
}
foreach (Tuple<ItemPrefab, int, float, bool> ip in fabricatedItem.RequiredItems)
{
for (int i = 0; i < ip.Item2; i++)
for (int i = 0; i < ingredient.Amount; i++)
{
var requiredItem = containers[0].Inventory.Items.FirstOrDefault(it => it != null && it.Prefab == ip.Item1 && it.Condition >= ip.Item1.Health * ip.Item3);
var requiredItem = inputContainer.Inventory.Items.FirstOrDefault(it => it != null && it.Prefab == ingredient.ItemPrefab && it.Condition >= ingredient.ItemPrefab.Health * ingredient.MinCondition);
if (requiredItem == null) continue;
//Item4 = use condition bool
if (ip.Item4 && requiredItem.Condition - ip.Item1.Health * ip.Item3 > 0.0f) //Leave it behind with reduced condition if it has enough to stay above 0
if (ingredient.UseCondition && requiredItem.Condition - ingredient.ItemPrefab.Health * ingredient.MinCondition > 0.0f) //Leave it behind with reduced condition if it has enough to stay above 0
{
requiredItem.Condition -= ip.Item1.Health * ip.Item3;
requiredItem.Condition -= ingredient.ItemPrefab.Health * ingredient.MinCondition;
continue;
}
Entity.Spawner.AddToRemoveQueue(requiredItem);
containers[0].Inventory.RemoveItem(requiredItem);
inputContainer.Inventory.RemoveItem(requiredItem);
}
}
if (containers[1].Inventory.Items.All(i => i != null))
if (outputContainer.Inventory.Items.All(i => i != null))
{
Entity.Spawner.AddToSpawnQueue(fabricatedItem.TargetItem, item.Position, item.Submarine, fabricatedItem.TargetItem.Health * fabricatedItem.OutCondition);
}
else
{
Entity.Spawner.AddToSpawnQueue(fabricatedItem.TargetItem, containers[1].Inventory, fabricatedItem.TargetItem.Health * fabricatedItem.OutCondition);
Entity.Spawner.AddToSpawnQueue(fabricatedItem.TargetItem, outputContainer.Inventory, fabricatedItem.TargetItem.Health * fabricatedItem.OutCondition);
}
if (GameMain.Client == null && user != null)
{
foreach (Skill skill in fabricatedItem.RequiredSkills)
{
user.Info.IncreaseSkillLevel(skill.Identifier, skill.Level / 100.0f * SkillIncreaseMultiplier, user.WorldPosition + Vector2.UnitY * 150.0f);
}
}
CancelFabricating(null);
}
private bool CanBeFabricated(FabricableItem fabricableItem, Character user)
private bool CanBeFabricated(FabricableItem fabricableItem)
{
if (fabricableItem == null) return false;
if (fabricableItem == null) { return false; }
List<Item> availableIngredients = GetAvailableIngredients();
return CanBeFabricated(fabricableItem, availableIngredients);
}
if (user != null &&
fabricableItem.RequiredSkills.Any(skill => user.GetSkillLevel(skill.Name) < skill.Level))
private bool CanBeFabricated(FabricableItem fabricableItem, IEnumerable<Item> availableIngredients)
{
if (fabricableItem == null) { return false; }
foreach (FabricableItem.RequiredItem requiredItem in fabricableItem.RequiredItems)
{
return false;
if (availableIngredients.Count(it => IsItemValidIngredient(it, requiredItem)) < requiredItem.Amount)
{
return false;
}
}
ItemContainer container = item.GetComponent<ItemContainer>();
foreach (Tuple<ItemPrefab, int, float, bool> ip in fabricableItem.RequiredItems)
{
if (Array.FindAll(container.Inventory.Items, it => it != null && it.Prefab == ip.Item1 && it.Condition >= ip.Item1.Health * ip.Item3).Length < ip.Item2) return false;
}
return true;
}
private float GetRequiredTime(FabricableItem fabricableItem, Character user)
{
float degreeOfSuccess = DegreeOfSuccess(user, fabricableItem.RequiredSkills);
float t = degreeOfSuccess < 0.5f ? degreeOfSuccess * degreeOfSuccess : degreeOfSuccess * 2;
//fabricating takes 100 times longer if degree of success is close to 0
//characters with a higher skill than required can fabricate up to 100% faster
return fabricableItem.RequiredTime / MathHelper.Clamp(t, 0.01f, 2.0f);
}
/// <summary>
/// Get a list of all items available in the input container and linked containers
/// </summary>
/// <returns></returns>
private List<Item> GetAvailableIngredients()
{
List<Item> availableIngredients = new List<Item>();
availableIngredients.AddRange(inputContainer.Inventory.Items.Where(it => it != null));
foreach (MapEntity linkedTo in item.linkedTo)
{
if (linkedTo is Item linkedItem)
{
var itemContainer = linkedItem.GetComponent<ItemContainer>();
if (itemContainer == null) { continue; }
var deconstructor = linkedItem.GetComponent<Deconstructor>();
if (deconstructor != null)
{
itemContainer = deconstructor.OutputContainer;
}
availableIngredients.AddRange(itemContainer.Inventory.Items.Where(it => it != null));
}
}
return availableIngredients;
}
/// <summary>
/// Move the items required for fabrication into the input container.
/// The method assumes that all the required ingredients are available either in the input container or linked containers.
/// </summary>
private void MoveIngredientsToInputContainer(FabricableItem targetItem)
{
//required ingredients that are already present in the input container
List<Item> usedItems = new List<Item>();
var availableIngredients = GetAvailableIngredients();
foreach (var requiredItem in targetItem.RequiredItems)
{
for (int i = 0; i < requiredItem.Amount; i++)
{
var matchingItem = availableIngredients.Find(it => !usedItems.Contains(it) && IsItemValidIngredient(it, requiredItem));
if (matchingItem == null) { continue; }
if (matchingItem.ParentInventory == inputContainer.Inventory)
{
//already in input container, all good
usedItems.Add(matchingItem);
}
else //in another inventory, we need to move the item
{
if (inputContainer.Inventory.Items.All(it => it != null))
{
var unneededItem = inputContainer.Inventory.Items.FirstOrDefault(it => !usedItems.Contains(it));
unneededItem?.Drop();
}
inputContainer.Inventory.TryPutItem(matchingItem, user: null, createNetworkEvent: true);
}
}
}
}
private bool IsItemValidIngredient(Item item, FabricableItem.RequiredItem requiredItem)
{
return
item != null &&
item.prefab == requiredItem.ItemPrefab &&
item.Condition / item.Prefab.Health >= requiredItem.MinCondition;
}
public void ServerRead(ClientNetObject type, NetBuffer msg, Client c)
{
int itemIndex = msg.ReadRangedInteger(-1, fabricableItems.Count - 1);
@@ -351,6 +455,8 @@ namespace Barotrauma.Items.Components
{
int itemIndex = fabricatedItem == null ? -1 : fabricableItems.IndexOf(fabricatedItem);
msg.WriteRangedInteger(-1, fabricableItems.Count - 1, itemIndex);
UInt16 userID = fabricatedItem == null || user == null ? (UInt16)0 : user.ID;
msg.Write(userID);
}
}
@@ -1,4 +1,5 @@
using System;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Xml.Linq;
@@ -10,11 +11,16 @@ namespace Barotrauma.Items.Components
{
public float? Oxygen;
public float? Water;
public bool Distort;
public float DistortionTimer;
}
private DateTime resetDataTime;
bool hasPower;
private bool hasPower;
private Dictionary<Hull, HullData> hullDatas;
[Editable(ToolTip = "Does the machine require inputs from water detectors in order to show the water levels inside rooms."), Serialize(false, true)]
public bool RequireWaterDetectors
@@ -30,35 +36,42 @@ namespace Barotrauma.Items.Components
set;
}
[Editable(ToolTip = "Should damaged walls be displayed by the machine."), Serialize(false, true)]
[Editable(ToolTip = "Should damaged walls be displayed by the machine."), Serialize(true, 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>();
InitProjSpecific(element);
}
partial void InitProjSpecific(XElement element);
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();
foreach (HullData hullData in hullDatas.Values)
{
if (!hullData.Distort)
{
hullData.Oxygen = null;
hullData.Water = null;
}
}
resetDataTime = DateTime.Now + new TimeSpan(0, 0, 1);
}
currPowerConsumption = powerConsumption;
currPowerConsumption *= MathHelper.Lerp(2.0f, 1.0f, item.Condition / 100.0f);
hasPower = voltage > minVoltage;
if (hasPower)
@@ -71,28 +84,24 @@ namespace Barotrauma.Items.Components
public override bool Pick(Character picker)
{
if (picker == null) return false;
//picker.SelectedConstruction = item;
return true;
return picker != null;
}
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power = 0)
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power = 0, float signalStrength = 1.0f)
{
base.ReceiveSignal(stepsTaken, signal, connection, source, sender, power);
base.ReceiveSignal(stepsTaken, signal, connection, source, sender, power, signalStrength);
if (sender == null || sender.CurrentHull == null) return;
if (source == null || source.CurrentHull == null) return;
Hull senderHull = sender.CurrentHull;
HullData hullData;
if (!hullDatas.TryGetValue(senderHull, out hullData))
Hull sourceHull = source.CurrentHull;
if (!hullDatas.TryGetValue(sourceHull, out HullData hullData))
{
hullData = new HullData();
hullDatas.Add(senderHull, hullData);
hullDatas.Add(sourceHull, hullData);
}
if (hullData.Distort) return;
switch (connection.Name)
{
case "water_data_in":
@@ -103,7 +112,7 @@ namespace Barotrauma.Items.Components
}
else
{
hullData.Water = Math.Min(senderHull.WaterVolume / senderHull.Volume, 1.0f);
hullData.Water = Math.Min(sourceHull.WaterVolume / sourceHull.Volume, 1.0f);
}
break;
case "oxygen_data_in":
@@ -9,9 +9,7 @@ namespace Barotrauma.Items.Components
class OxygenGenerator : Powered
{
private float powerDownTimer;
private bool running;
private float generatedAmount;
private List<Vent> ventList;
@@ -43,24 +41,29 @@ namespace Barotrauma.Items.Components
CurrFlow = 0.0f;
currPowerConsumption = powerConsumption;
//consume more power when in a bad condition
currPowerConsumption *= MathHelper.Lerp(2.0f, 1.0f, item.Condition / 100.0f);
if (powerConsumption <= 0.0f)
{
voltage = 1.0f;
}
if (item.CurrentHull == null) return;
if (voltage < minVoltage)
{
powerDownTimer += deltaTime;
running = false;
return;
}
else
{
powerDownTimer = 0.0f;
}
running = true;
CurrFlow = Math.Min(voltage, 1.0f) * generatedAmount * 100.0f;
//item.CurrentHull.Oxygen += CurrFlow * deltaTime;
//less effective when in bad condition
CurrFlow *= MathHelper.Lerp(0.5f, 1.0f, item.Condition / 100.0f);
UpdateVents(CurrFlow);
@@ -13,7 +13,7 @@ namespace Barotrauma.Items.Components
private float? targetLevel;
public Hull hull1;
private bool hasPower;
[Serialize(0.0f, true)]
public float FlowPercentage
@@ -34,7 +34,7 @@ namespace Barotrauma.Items.Components
set { maxFlow = value; }
}
float currFlow;
private float currFlow;
public float CurrFlow
{
get
@@ -43,85 +43,59 @@ namespace Barotrauma.Items.Components
return Math.Abs(currFlow);
}
}
public override bool IsActive
{
get
{
return base.IsActive;
}
set
{
base.IsActive = value;
#if CLIENT
if (isActiveTickBox != null) isActiveTickBox.Selected = value;
#endif
}
}
public Pump(Item item, XElement element)
: base(item, element)
{
GetHull();
InitProjSpecific();
}
partial void InitProjSpecific();
public override void Move(Vector2 amount)
{
base.Move(amount);
GetHull();
}
public override void OnMapLoaded()
{
GetHull();
InitProjSpecific(element);
}
partial void InitProjSpecific(XElement element);
public override void Update(float deltaTime, Camera cam)
{
currFlow = 0.0f;
hasPower = false;
if (targetLevel != null)
{
float hullPercentage = 0.0f;
if (hull1 != null) hullPercentage = (hull1.WaterVolume / hull1.Volume) * 100.0f;
if (item.CurrentHull != null) hullPercentage = (item.CurrentHull.WaterVolume / item.CurrentHull.Volume) * 100.0f;
FlowPercentage = ((float)targetLevel - hullPercentage) * 10.0f;
}
currPowerConsumption = powerConsumption * Math.Abs(flowPercentage / 100.0f);
//pumps consume more power when in a bad condition
currPowerConsumption *= MathHelper.Lerp(2.0f, 1.0f, item.Condition / 100.0f);
if (voltage < minVoltage) return;
UpdateProjSpecific(deltaTime);
hasPower = true;
ApplyStatusEffects(ActionType.OnActive, deltaTime, null);
//check the hull if the item is movable
if (item.body != null) GetHull();
if (hull1 == null) return;
if (item.CurrentHull == null) { return; }
float powerFactor = currPowerConsumption <= 0.0f ? 1.0f : voltage;
currFlow = flowPercentage / 100.0f * maxFlow * powerFactor;
//less effective when in a bad condition
currFlow *= MathHelper.Lerp(0.5f, 1.0f, item.Condition / 100.0f);
hull1.WaterVolume += currFlow;
if (hull1.WaterVolume > hull1.Volume) hull1.Pressure += 0.5f;
item.CurrentHull.WaterVolume += currFlow;
if (item.CurrentHull.WaterVolume > item.CurrentHull.Volume) { item.CurrentHull.Pressure += 0.5f; }
voltage = 0.0f;
}
private void GetHull()
{
hull1 = Hull.FindHull(item.WorldPosition, item.CurrentHull);
}
partial void UpdateProjSpecific(float deltaTime);
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power=0.0f)
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power = 0.0f, float signalStrength = 1.0f)
{
base.ReceiveSignal(stepsTaken, signal, connection, source, sender, power);
base.ReceiveSignal(stepsTaken, signal, connection, source, sender, power, signalStrength);
if (connection.Name == "toggle")
{
IsActive = !IsActive;
@@ -132,24 +106,43 @@ namespace Barotrauma.Items.Components
}
else if (connection.Name == "set_speed")
{
float tempSpeed;
if (float.TryParse(signal, NumberStyles.Any, CultureInfo.InvariantCulture, out tempSpeed))
if (float.TryParse(signal, NumberStyles.Any, CultureInfo.InvariantCulture, out float 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))
if (float.TryParse(signal, NumberStyles.Any, CultureInfo.InvariantCulture, out float tempTarget))
{
targetLevel = MathHelper.Clamp((tempTarget+100.0f)/2.0f, 0.0f, 100.0f);
targetLevel = MathHelper.Clamp((tempTarget + 100.0f) / 2.0f, 0.0f, 100.0f);
}
}
if (!IsActive) currPowerConsumption = 0.0f;
}
public override bool AIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective)
{
if (GameMain.Client != null) return false;
if (objective.Option.ToLowerInvariant() == "stoppumping")
{
if (FlowPercentage > 0.0f) item.CreateServerEvent(this);
FlowPercentage = 0.0f;
}
else
{
if (!IsActive || FlowPercentage > -100.0f)
{
item.CreateServerEvent(this);
}
IsActive = true;
FlowPercentage = -100.0f;
}
return true;
}
public void ServerRead(ClientNetObject type, Lidgren.Network.NetBuffer msg, Client c)
{
float newFlowPercentage = msg.ReadRangedInteger(-10, 10) * 10.0f;
@@ -1,162 +0,0 @@
using Barotrauma.Networking;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Xml.Linq;
namespace Barotrauma.Items.Components
{
partial class Radar : Powered, IServerSerializable, IClientSerializable
{
private float range;
private float pingState;
private readonly Sprite pingCircle, screenOverlay;
private readonly Sprite radarBlip;
private float prevPingRadius;
float prevPassivePingRadius;
private Vector2 center;
private float displayRadius;
private float displayScale;
private float displayBorderSize;
[Serialize(10000.0f, false)]
public float Range
{
get { return range; }
set { range = MathHelper.Clamp(value, 0.0f, 100000.0f); }
}
[Serialize(false, false)]
public bool DetectSubmarineWalls
{
get;
set;
}
public override bool IsActive
{
get
{
return base.IsActive;
}
set
{
base.IsActive = value;
#if CLIENT
if (isActiveTickBox != null) isActiveTickBox.Selected = value;
#endif
}
}
public Radar(Item item, XElement element)
: base(item, element)
{
#if CLIENT
radarBlips = new List<RadarBlip>();
#endif
displayBorderSize = element.GetAttributeFloat("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;
}
}
#if CLIENT
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;
#endif
IsActive = false;
}
public override void Update(float deltaTime, Camera cam)
{
currPowerConsumption = powerConsumption;
UpdateOnActiveEffects(deltaTime);
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;
}
protected override void RemoveComponentSpecific()
{
if (pingCircle!=null) pingCircle.Remove();
if (screenOverlay != null) screenOverlay.Remove();
}
public void ServerRead(ClientNetObject type, Lidgren.Network.NetBuffer msg, Barotrauma.Networking.Client c)
{
bool isActive = msg.ReadBoolean();
if (!item.CanClientAccess(c)) return;
IsActive = isActive;
#if CLIENT
isActiveTickBox.Selected = IsActive;
#endif
item.CreateServerEvent(this);
}
public void ServerWrite(Lidgren.Network.NetBuffer msg, Barotrauma.Networking.Client c, object[] extraData = null)
{
msg.Write(IsActive);
}
}
}
@@ -8,80 +8,99 @@ using System.Xml.Linq;
namespace Barotrauma.Items.Components
{
partial class Reactor : Powered, IDrawableComponent, IServerSerializable, IClientSerializable
partial class Reactor : Powered, IServerSerializable, IClientSerializable
{
const float NetworkUpdateInterval = 0.5f;
//the rate at which the reactor is being run un
//higher rates generate more power (and heat)
//the rate at which the reactor is being run on (higher rate -> higher temperature)
private float fissionRate;
//the rate at which the heat is being dissipated
private float coolingRate;
//how much of the generated steam is used to spin the turbines and generate power
private float turbineOutput;
private float temperature;
private Client BlameOnBroken;
//is automatic temperature control on
//(adjusts the cooling rate automatically to keep the
//(adjusts the fission rate and turbine output 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 Client BlameOnBroken;
private float fireTemp, meltDownTemp, meltDownDelay;
//automatical adjustment to the power output when
//turbine output and temperature are in the optimal range
private float autoAdjustAmount;
private float fuelConsumptionRate;
private float meltDownTimer;
private float meltDownTimer, meltDownDelay;
private float fireTimer, fireDelay;
//how much power is provided to the grid per 1 temperature unit
private float powerPerTemp;
private float maxPowerOutput;
private float load;
private bool unsentChanges;
private float sendUpdateTimer;
private Character lastUser;
private float degreeOfSuccess;
private float? nextServerLogWriteTime;
private float lastServerLogWriteTime;
[Editable(ToolTip = "The temperature at which the reactor melts down."), Serialize(9500.0f, true)]
public float MeltDownTemp
private Vector2 optimalTemperature, allowedTemperature;
private Vector2 optimalFissionRate, allowedFissionRate;
private Vector2 optimalTurbineOutput, allowedTurbineOutput;
private bool shutDown;
const float AIUpdateInterval = 1.0f;
private float aiUpdateTimer;
private Character lastUser;
private Character LastUser
{
get { return meltDownTemp; }
set
get { return lastUser; }
set
{
meltDownTemp = Math.Max(0.0f, value);
if (lastUser == value) return;
lastUser = value;
degreeOfSuccess = lastUser == null ? 0.0f : DegreeOfSuccess(lastUser);
}
}
[Serialize(30.0f, true)]
[Editable(0.0f, float.MaxValue, ToolTip = "How much power (kW) the reactor generates when operating at full capacity."), Serialize(10000.0f, true)]
public float MaxPowerOutput
{
get { return maxPowerOutput; }
set
{
maxPowerOutput = Math.Max(0.0f, value);
}
}
[Editable(0.0f, float.MaxValue, ToolTip = "How long the temperature has to stay critical until a meltdown occurs."), Serialize(30.0f, true)]
public float MeltdownDelay
{
get { return meltDownDelay; }
set { meltDownDelay = Math.Max(value, 0.0f); }
}
[Editable(ToolTip = "The temperature at which the reactor catches fire."), Serialize(9000.0f, true)]
public float FireTemp
[Editable(0.0f, float.MaxValue, ToolTip = "How long the temperature has to stay critical until the reactor catches fire."), Serialize(10.0f, true)]
public float FireDelay
{
get { return fireTemp; }
set
{
fireTemp = Math.Max(0.0f, value);
}
get { return fireDelay; }
set { fireDelay = Math.Max(value, 0.0f); }
}
[Editable(0.0f, float.MaxValue, ToolTip = "How much power (kW) the reactor generates relative to it's operating temperature (kW per one degree Celsius)."), Serialize(1.0f, true)]
public float PowerPerTemp
[Serialize(0.0f, true)]
public float Temperature
{
get { return powerPerTemp; }
get { return temperature; }
set
{
powerPerTemp = Math.Max(0.0f, value);
if (!MathUtils.IsValid(value)) return;
temperature = MathHelper.Clamp(value, 0.0f, 100.0f);
}
}
@@ -97,32 +116,32 @@ namespace Barotrauma.Items.Components
}
[Serialize(0.0f, true)]
public float CoolingRate
public float TurbineOutput
{
get { return coolingRate; }
get { return turbineOutput; }
set
{
if (!MathUtils.IsValid(value)) return;
coolingRate = MathHelper.Clamp(value, 0.0f, 100.0f);
turbineOutput = MathHelper.Clamp(value, 0.0f, 100.0f);
}
}
[Serialize(0.0f, true)]
public float Temperature
[Serialize(0.2f, true), Editable(0.0f, 1000.0f, ToolTip = "How fast the condition of the contained fuel rods deteriorates.")]
public float FuelConsumptionRate
{
get { return temperature; }
set
get { return fuelConsumptionRate; }
set
{
if (!MathUtils.IsValid(value)) return;
temperature = MathHelper.Clamp(value, 0.0f, 10000.0f);
fuelConsumptionRate = Math.Max(value, 0.0f);
}
}
public bool IsRunning()
{
return (temperature > 0.0f);
}
private float correctTurbineOutput;
private float targetFissionRate;
private float targetTurbineOutput;
[Serialize(false, true)]
public bool AutoTemp
{
@@ -131,109 +150,108 @@ namespace Barotrauma.Items.Components
{
autoTemp = value;
#if CLIENT
if (autoTempTickBox!=null) autoTempTickBox.Selected = value;
if (autoTempSlider != null)
{
autoTempSlider.BarScroll = value ?
Math.Min(0.45f, autoTempSlider.BarScroll) :
Math.Max(0.55f, autoTempSlider.BarScroll);
}
#endif
}
}
public float ExtraCooling { get; set; }
private float prevAvailableFuel;
public float AvailableFuel { get; set; }
private float availableHeat, availableCooling;
private float prevTemperature, temperatureChange;
[Serialize(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)
{
shutDownTemp = 500.0f;
powerPerTemp = 1.0f;
{
IsActive = true;
InitProjSpecific();
InitProjSpecific(element);
}
partial void InitProjSpecific();
public override void Update(float deltaTime, Camera cam)
partial void InitProjSpecific(XElement element);
public override void Update(float deltaTime, Camera cam)
{
if (GameMain.Server != null && nextServerLogWriteTime != null)
{
if (Timing.TotalTime >= (float)nextServerLogWriteTime)
{
GameServer.Log(lastUser.LogName + " adjusted reactor settings: " +
"Temperature: " + (int)temperature +
", Fission rate: " + (int)fissionRate +
", Cooling rate: " + (int)coolingRate +
", Cooling rate: " + coolingRate +
", Shutdown temp: " + shutDownTemp +
"Temperature: " + (int)(temperature * 100.0f) +
", Fission rate: " + (int)targetFissionRate +
", Turbine output: " + (int)targetTurbineOutput +
(autoTemp ? ", Autotemp ON" : ", Autotemp OFF"),
ServerLog.MessageType.ItemInteraction);
nextServerLogWriteTime = null;
lastServerLogWriteTime = (float)Timing.TotalTime;
}
}
prevAvailableFuel = AvailableFuel;
ApplyStatusEffects(ActionType.OnActive, deltaTime, null);
fissionRate = Math.Min(fissionRate, AvailableFuel);
//use a smoothed "correct output" instead of the actual correct output based on the load
//so the player doesn't have to keep adjusting the rate impossibly fast when the load fluctuates heavily
correctTurbineOutput += MathHelper.Clamp((load / MaxPowerOutput * 100.0f) - correctTurbineOutput, -10.0f, 10.0f) * deltaTime;
//the amount of cooling is always non-zero, so that the reactor always needs
//to generate some amount of heat to prevent the temperature from dropping
availableCooling = Math.Max(ExtraCooling, 5.0f);
availableHeat = 80 * (AvailableFuel / 2000.0f);
//calculate tolerances of the meters based on the skills of the user
//more skilled characters have larger "sweet spots", making it easier to keep the power output at a suitable level
float tolerance = MathHelper.Lerp(2.5f, 10.0f, degreeOfSuccess);
optimalTurbineOutput = new Vector2(correctTurbineOutput - tolerance, correctTurbineOutput + tolerance);
tolerance = MathHelper.Lerp(5.0f, 20.0f, degreeOfSuccess);
allowedTurbineOutput = new Vector2(correctTurbineOutput - tolerance, correctTurbineOutput + tolerance);
float temperatureTolerance = MathHelper.Lerp(10.0f, 20.0f, degreeOfSuccess);
optimalTemperature = Vector2.Lerp(new Vector2(40.0f, 60.0f), new Vector2(30.0f, 70.0f), degreeOfSuccess);
allowedTemperature = Vector2.Lerp(new Vector2(30.0f, 70.0f), new Vector2(10.0f, 90.0f), degreeOfSuccess);
float heat = availableHeat * fissionRate;
float heatDissipation = 50 * coolingRate + availableCooling;
float fissionRateTolerance = MathHelper.Lerp(10.0f, 20.0f, degreeOfSuccess);
optimalFissionRate = Vector2.Lerp(new Vector2(40.0f, 70.0f), new Vector2(30.0f, 85.0f), degreeOfSuccess);
allowedFissionRate = Vector2.Lerp(new Vector2(30.0f, 85.0f), new Vector2(20.0f, 98.0f), degreeOfSuccess);
float deltaTemp = (((heat - heatDissipation) * 5) - temperature) / 10000.0f;
Temperature = temperature + deltaTemp;
float heatAmount = fissionRate * (AvailableFuel / 100.0f) * 2.0f;
float temperatureDiff = (heatAmount - turbineOutput) - Temperature;
Temperature += MathHelper.Clamp(Math.Sign(temperatureDiff) * 10.0f * deltaTime, -Math.Abs(temperatureDiff), Math.Abs(temperatureDiff));
if (item.InWater && AvailableFuel < 100.0f) Temperature -= 12.0f * deltaTime;
temperatureChange = Temperature - prevTemperature;
prevTemperature = temperature;
FissionRate = MathHelper.Lerp(fissionRate, Math.Min(targetFissionRate, AvailableFuel), deltaTime);
TurbineOutput = MathHelper.Lerp(turbineOutput, targetTurbineOutput, deltaTime);
if (temperature > fireTemp && temperature - deltaTemp < fireTemp)
float temperatureFactor = Math.Min(temperature / 50.0f, 1.0f);
currPowerConsumption = -MaxPowerOutput * Math.Min(turbineOutput / 100.0f, temperatureFactor);
//if the turbine output and coolant flow are the optimal range,
//make the generated power slightly adjust according to the load
// (-> the reactor can automatically handle small changes in load as long as the values are roughly correct)
if (turbineOutput > optimalTurbineOutput.X && turbineOutput < optimalTurbineOutput.Y &&
temperature > optimalTemperature.X && temperature < optimalTemperature.Y)
{
#if CLIENT
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);
}
#endif
new FireSource(item.WorldPosition);
}
if (temperature > meltDownTemp)
{
item.SendSignal(0, "1", "meltdown_warning", null);
meltDownTimer += deltaTime;
if (meltDownTimer > MeltdownDelay)
{
MeltDown();
return;
}
float maxAutoAdjust = maxPowerOutput * 0.1f;
autoAdjustAmount = MathHelper.Lerp(
autoAdjustAmount,
MathHelper.Clamp(-load - currPowerConsumption, -maxAutoAdjust, maxAutoAdjust),
deltaTime * 10.0f);
}
else
{
item.SendSignal(0, "0", "meltdown_warning", null);
meltDownTimer = Math.Max(0.0f, meltDownTimer - deltaTime);
autoAdjustAmount = MathHelper.Lerp(autoAdjustAmount, 0.0f, deltaTime * 10.0f);
}
currPowerConsumption += autoAdjustAmount;
if (shutDown)
{
targetFissionRate = 0.0f;
targetTurbineOutput = 0.0f;
}
else if (autoTemp)
{
UpdateAutoTemp(2.0f, deltaTime);
}
load = 0.0f;
List<Connection> connections = item.Connections;
if (connections != null && connections.Count > 0)
{
@@ -247,50 +265,40 @@ namespace Barotrauma.Items.Components
PowerTransfer pt = it.GetComponent<PowerTransfer>();
if (pt == null) continue;
load = Math.Max(load,pt.PowerLoad);
load = Math.Max(load, pt.PowerLoad);
}
}
}
//item.Condition -= temperature * deltaTime * 0.00005f;
if (temperature > shutDownTemp)
if (fissionRate > 0.0f)
{
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;
foreach (Item item in item.ContainedItems)
{
if (!item.HasTag("reactorfuel")) continue;
item.Condition -= fissionRate / 100.0f * fuelConsumptionRate * deltaTime;
}
//-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);
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(
(-currPowerConsumption / MaxPowerOutput) * 20000.0f,
item.CurrentHull.AiTarget.SoundRange);
}
}
item.SendSignal(0, ((int)(temperature * 100.0f)).ToString(), "temperature_out", null);
UpdateFailures(deltaTime);
#if CLIENT
UpdateGraph(deltaTime);
#endif
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 (unsentChanges && sendUpdateTimer <= 0.0f)
{
if (GameMain.Server != null)
{
@@ -302,27 +310,78 @@ namespace Barotrauma.Items.Components
item.CreateClientEvent(this);
}
#endif
sendUpdateTimer = NetworkUpdateInterval;
unsentChanges = false;
}
}
}
private void UpdateFailures(float deltaTime)
{
if (temperature > allowedTemperature.Y)
{
item.SendSignal(0, "1", "meltdown_warning", null);
//faster meltdown if the item is in a bad condition
meltDownTimer += MathHelper.Lerp(deltaTime * 2.0f, deltaTime, item.Condition / 100.0f);
if (meltDownTimer > MeltdownDelay)
{
MeltDown();
return;
}
}
else
{
item.SendSignal(0, "0", "meltdown_warning", null);
meltDownTimer = Math.Max(0.0f, meltDownTimer - deltaTime);
}
if (temperature > optimalTemperature.Y)
{
float prevFireTimer = fireTimer;
fireTimer += MathHelper.Lerp(deltaTime * 2.0f, deltaTime, item.Condition / 100.0f);
if (fireTimer >= FireDelay && prevFireTimer < fireDelay)
{
new FireSource(item.WorldPosition);
}
}
else
{
fireTimer = Math.Max(0.0f, fireTimer - deltaTime);
}
}
private void UpdateAutoTemp(float speed, float deltaTime)
{
float desiredTurbineOutput = (optimalTurbineOutput.X + optimalTurbineOutput.Y) / 2.0f;
targetTurbineOutput += MathHelper.Clamp(desiredTurbineOutput - targetTurbineOutput, -speed, speed) * deltaTime;
float desiredFissionRate = (optimalFissionRate.X + optimalFissionRate.Y) / 2.0f;
targetFissionRate += MathHelper.Clamp(desiredFissionRate - targetFissionRate, -speed, speed) * deltaTime;
if (temperature > (optimalTemperature.X + optimalTemperature.Y) / 2.0f)
{
targetFissionRate = Math.Min(targetFissionRate - speed * 2 * deltaTime, allowedFissionRate.Y);
}
else if (-currPowerConsumption < load)
{
targetFissionRate = Math.Min(targetFissionRate + speed * 2 * deltaTime, allowedFissionRate.Y);
}
}
public override void UpdateBroken(float deltaTime, Camera cam)
{
base.UpdateBroken(deltaTime, cam);
currPowerConsumption = 0.0f;
Temperature -= deltaTime * 1000.0f;
FissionRate -= deltaTime * 10.0f;
CoolingRate -= deltaTime * 10.0f;
currPowerConsumption = -temperature;
targetFissionRate = Math.Max(targetFissionRate - deltaTime * 10.0f, 0.0f);
targetTurbineOutput = Math.Max(targetTurbineOutput - deltaTime * 10.0f, 0.0f);
#if CLIENT
fissionRateScrollBar.BarScroll = 1.0f - FissionRate / 100.0f;
turbineOutputScrollBar.BarScroll = 1.0f - TurbineOutput / 100.0f;
UpdateGraph(deltaTime);
#endif
ExtraCooling = 0.0f;
}
private void MeltDown()
@@ -332,6 +391,8 @@ namespace Barotrauma.Items.Components
GameServer.Log("Reactor meltdown!", ServerLog.MessageType.ItemInteraction);
item.Condition = 0.0f;
fireTimer = 0.0f;
meltDownTimer = 0.0f;
var containedItems = item.ContainedItems;
if (containedItems != null)
@@ -356,6 +417,8 @@ namespace Barotrauma.Items.Components
public override bool AIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective)
{
if (GameMain.Client != null) return false;
float degreeOfSuccess = DegreeOfSuccess(character);
//characters with insufficient skill levels don't refuel the reactor
@@ -371,65 +434,97 @@ namespace Barotrauma.Items.Components
}
}
//the temperature is too low and not increasing even though the fission rate is high and cooling low
// -> we need more fuel
if (temperature < load * 0.5f && temperatureChange <= 0.0f && fissionRate > 0.9f && coolingRate < 0.1f)
//we need more fuel
if (-currPowerConsumption < load * 0.5f && prevAvailableFuel <= 0.0f)
{
var containFuelObjective = new AIObjectiveContainItem(character, new string[] { "Fuel Rod", "reactorfuel" }, item.GetComponent<ItemContainer>());
containFuelObjective.MinContainedAmount = containedItems.Count(i => i != null && i.Prefab.NameMatches("Fuel Rod") || i.HasTag("reactorfuel")) + 1;
containFuelObjective.GetItemPriority = (Item fuelItem) =>
var containFuelObjective = new AIObjectiveContainItem(character, new string[] { "fuelrod", "reactorfuel" }, item.GetComponent<ItemContainer>())
{
if (fuelItem.ParentInventory?.Owner is Item)
MinContainedAmount = containedItems.Count(i => i != null && i.Prefab.Identifier == "fuelrod" || i.HasTag("reactorfuel")) + 1,
GetItemPriority = (Item fuelItem) =>
{
//don't take fuel from other reactors
if (((Item)fuelItem.ParentInventory.Owner).GetComponent<Reactor>() != null) return 0.0f;
if (fuelItem.ParentInventory?.Owner is Item)
{
//don't take fuel from other reactors
if (((Item)fuelItem.ParentInventory.Owner).GetComponent<Reactor>() != null) return 0.0f;
}
return 1.0f;
}
return 1.0f;
};
objective.AddSubObjective(containFuelObjective);
character?.Speak(TextManager.Get("DialogReactorFuel"), null, 0.0f, "reactorfuel", 30.0f);
return false;
}
}
if (aiUpdateTimer > 0.0f)
{
aiUpdateTimer -= deltaTime;
return false;
}
if (lastUser != character && lastUser != null && lastUser.SelectedConstruction == item)
{
character.Speak(TextManager.Get("DialogReactorTaken"), null, 0.0f, "reactortaken", 10.0f);
}
LastUser = character;
switch (objective.Option.ToLowerInvariant())
{
case "power up":
float tempDiff = load - temperature;
shutDownTemp = Math.Min(load + 1000.0f, 7500.0f);
case "powerup":
shutDown = false;
//characters with insufficient skill levels simply set the autotemp on instead of trying to adjust the temperature manually
if (Math.Abs(tempDiff) < 500.0f || degreeOfSuccess < 0.5f)
if (degreeOfSuccess < 0.5f)
{
if (!autoTemp) unsentChanges = true;
AutoTemp = true;
}
else
{
AutoTemp = false;
//higher skill levels make the character adjust the temperature faster
FissionRate += deltaTime * 100.0f * Math.Sign(tempDiff) * degreeOfSuccess;
CoolingRate -= deltaTime * 100.0f * Math.Sign(tempDiff) * degreeOfSuccess;
}
unsentChanges = true;
UpdateAutoTemp(2.0f + degreeOfSuccess * 5.0f, 1.0f);
}
#if CLIENT
onOffSwitch.BarScroll = 0.0f;
fissionRateScrollBar.BarScroll = FissionRate / 100.0f;
turbineOutputScrollBar.BarScroll = TurbineOutput / 100.0f;
#endif
break;
case "shutdown":
shutDownTemp = 0.0f;
#if CLIENT
onOffSwitch.BarScroll = 1.0f;
#endif
AutoTemp = false;
shutDown = true;
targetFissionRate = 0.0f;
targetTurbineOutput = 0.0f;
break;
}
aiUpdateTimer = AIUpdateInterval;
return false;
}
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power)
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power, float signalStrength = 1.0f)
{
switch (connection.Name)
{
case "shutdown":
if (shutDownTemp > 0.0f)
if (targetFissionRate > 0.0f || targetTurbineOutput > 0.0f)
{
shutDown = true;
AutoTemp = false;
targetFissionRate = 0.0f;
targetTurbineOutput = 0.0f;
unsentChanges = true;
shutDownTemp = 0.0f;
#if CLIENT
onOffSwitch.BarScroll = 1.0f;
#endif
}
break;
}
@@ -438,41 +533,46 @@ namespace Barotrauma.Items.Components
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);
bool shutDown = msg.ReadBoolean();
float fissionRate = msg.ReadRangedSingle(0.0f, 100.0f, 8);
float turbineOutput = msg.ReadRangedSingle(0.0f, 100.0f, 8);
if (!item.CanClientAccess(c)) return;
if (!autoTemp && AutoTemp) BlameOnBroken = c;
if (shutDownTemp > ShutDownTemp) BlameOnBroken = c;
if (fissionRate > FissionRate) BlameOnBroken = c;
if (turbineOutput < targetTurbineOutput) BlameOnBroken = c;
if (fissionRate > targetFissionRate) BlameOnBroken = c;
if (!this.shutDown && shutDown) BlameOnBroken = c;
AutoTemp = autoTemp;
ShutDownTemp = shutDownTemp;
this.shutDown = shutDown;
targetFissionRate = fissionRate;
targetTurbineOutput = turbineOutput;
CoolingRate = coolingRate;
FissionRate = fissionRate;
lastUser = c.Character;
LastUser = c.Character;
if (nextServerLogWriteTime == null)
{
nextServerLogWriteTime = Math.Max(lastServerLogWriteTime + 1.0f, (float)Timing.TotalTime);
}
#if CLIENT
fissionRateScrollBar.BarScroll = 1.0f - targetFissionRate / 100.0f;
turbineOutputScrollBar.BarScroll = 1.0f - targetTurbineOutput / 100.0f;
onOffSwitch.BarScroll = shutDown ? Math.Max(onOffSwitch.BarScroll, 0.55f) : Math.Min(onOffSwitch.BarScroll, 0.45f);
#endif
//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);
msg.Write(shutDown);
msg.WriteRangedSingle(temperature, 0.0f, 100.0f, 8);
msg.WriteRangedSingle(targetFissionRate, 0.0f, 100.0f, 8);
msg.WriteRangedSingle(targetTurbineOutput, 0.0f, 100.0f, 8);
msg.WriteRangedSingle(degreeOfSuccess, 0.0f, 1.0f, 8);
}
}
}
@@ -0,0 +1,353 @@
using Barotrauma.Networking;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Items.Components
{
partial class Sonar : Powered, IServerSerializable, IClientSerializable
{
public const float DefaultSonarRange = 10000.0f;
class ConnectedTransducer
{
public readonly SonarTransducer Transducer;
public float SignalStrength;
public float DisconnectTimer;
public ConnectedTransducer(SonarTransducer transducer, float signalStrength, float disconnectTimer)
{
Transducer = transducer;
SignalStrength = signalStrength;
DisconnectTimer = disconnectTimer;
}
}
private const float DirectionalPingSector = 30.0f;
private static readonly float DirectionalPingDotProduct;
static Sonar()
{
DirectionalPingDotProduct = (float)Math.Cos(MathHelper.ToRadians(DirectionalPingSector) * 0.5f);
}
private float range;
private float pingState;
private const float MinZoom = 1.0f, MaxZoom = 4.0f;
private float zoom = 1.0f;
private bool useDirectionalPing = false;
private Vector2 lastPingDirection = new Vector2(1.0f, 0.0f);
private Vector2 pingDirection = new Vector2(1.0f, 0.0f);
//was the last ping sent with directional pinging
private bool isLastPingDirectional;
private readonly Sprite pingCircle, directionalPingCircle, screenOverlay, screenBackground;
private readonly Sprite sonarBlip;
private bool aiPingCheckPending;
//the float value is a timer used for disconnecting the transducer if no signal is received from it for 1 second
private List<ConnectedTransducer> connectedTransducers;
public IEnumerable<SonarTransducer> ConnectedTransducers
{
get { return connectedTransducers.Select(t => t.Transducer); }
}
[Serialize(DefaultSonarRange, false)]
public float Range
{
get { return range; }
set { range = MathHelper.Clamp(value, 0.0f, 100000.0f); }
}
[Serialize(false, false)]
public bool DetectSubmarineWalls
{
get;
set;
}
[Serialize(false, false), Editable(ToolTip = "Does the sonar have to be connected to external transducers to work.")]
public bool UseTransducers
{
get;
set;
}
public float Zoom
{
get { return zoom; }
}
public override bool IsActive
{
get
{
return base.IsActive;
}
set
{
base.IsActive = value;
if (!value && item.CurrentHull != null)
{
item.CurrentHull.AiTarget.SectorDegrees = 360.0f;
}
#if CLIENT
if (activeTickBox != null) activeTickBox.Selected = value;
if (passiveTickBox != null) passiveTickBox.Selected = !value;
#endif
}
}
public Sonar(Item item, XElement element)
: base(item, element)
{
connectedTransducers = new List<ConnectedTransducer>();
foreach (XElement subElement in element.Elements())
{
switch (subElement.Name.ToString().ToLowerInvariant())
{
case "pingcircle":
pingCircle = new Sprite(subElement);
break;
case "directionalpingcircle":
directionalPingCircle = new Sprite(subElement);
break;
case "screenoverlay":
screenOverlay = new Sprite(subElement);
break;
case "screenbackground":
screenBackground = new Sprite(subElement);
break;
case "blip":
sonarBlip = new Sprite(subElement);
break;
}
}
IsActive = false;
InitProjSpecific(element);
}
partial void InitProjSpecific(XElement element);
public override void Update(float deltaTime, Camera cam)
{
currPowerConsumption = powerConsumption;
UpdateOnActiveEffects(deltaTime);
if (UseTransducers)
{
foreach (ConnectedTransducer transducer in connectedTransducers)
{
transducer.DisconnectTimer -= deltaTime;
}
connectedTransducers.RemoveAll(t => t.DisconnectTimer <= 0.0f);
}
if ((voltage >= minVoltage || powerConsumption <= 0.0f) &&
(!UseTransducers || connectedTransducers.Count > 0))
{
pingState = pingState + deltaTime * 0.5f;
if (pingState > 1.0f)
{
if (item.CurrentHull != null)
{
item.CurrentHull.AiTarget.SoundRange = Math.Max(Range * pingState / zoom, item.CurrentHull.AiTarget.SoundRange);
item.CurrentHull.AiTarget.SectorDegrees = isLastPingDirectional ? DirectionalPingSector : 360.0f;
item.CurrentHull.AiTarget.SectorDir = new Vector2(pingDirection.X, -pingDirection.Y);
}
if (item.AiTarget != null)
{
item.AiTarget.SoundRange = Math.Max(Range * pingState / zoom, item.AiTarget.SoundRange);
item.AiTarget.SectorDegrees = isLastPingDirectional ? DirectionalPingSector : 360.0f;
item.AiTarget.SectorDir = new Vector2(pingDirection.X, -pingDirection.Y);
}
aiPingCheckPending = true;
isLastPingDirectional = useDirectionalPing;
lastPingDirection = pingDirection;
item.Use(deltaTime);
pingState = 0.0f;
}
}
else
{
if (item.CurrentHull != null)
{
item.CurrentHull.AiTarget.SectorDegrees = 360.0f;
}
aiPingCheckPending = false;
pingState = 0.0f;
}
Voltage -= deltaTime;
}
public override bool Use(float deltaTime, Character character = null)
{
return pingState > 1.0f;
}
protected override void RemoveComponentSpecific()
{
sonarBlip?.Remove();
pingCircle?.Remove();
directionalPingCircle?.Remove();
screenOverlay?.Remove();
screenBackground?.Remove();
}
public override bool AIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective)
{
if (!IsActive || !aiPingCheckPending) return false;
Dictionary<string, List<Character>> targetGroups = new Dictionary<string, List<Character>>();
foreach (Character c in Character.CharacterList)
{
if (c.AnimController.CurrentHull != null || !c.Enabled) continue;
if (DetectSubmarineWalls && c.AnimController.CurrentHull == null && item.CurrentHull != null) continue;
if (Vector2.DistanceSquared(c.WorldPosition, item.WorldPosition) > range * range) continue;
string directionName = GetDirectionName(c.WorldPosition - item.WorldPosition);
if (!targetGroups.ContainsKey(directionName))
{
targetGroups.Add(directionName, new List<Character>());
}
targetGroups[directionName].Add(c);
}
foreach (KeyValuePair<string, List<Character>> targetGroup in targetGroups)
{
string dialogTag = "DialogSonarTarget";
if (targetGroup.Value.Count > 1)
{
dialogTag = "DialogSonarTargetMultiple";
}
else if (targetGroup.Value[0].Mass > 100.0f)
{
dialogTag = "DialogSonarTargetLarge";
}
character.Speak(TextManager.Get(dialogTag).Replace("[direction]", targetGroup.Key).Replace("[count]", targetGroup.Value.Count.ToString()),
null, 0, "sonartarget" + targetGroup.Value[0].ID, 30);
//prevent the character from reporting other targets in the group
for (int i = 1; i < targetGroup.Value.Count; i++)
{
character.DisableLine("sonartarget" + targetGroup.Value[i].ID);
}
}
return true;
}
private string GetDirectionName(Vector2 dir)
{
float angle = MathUtils.WrapAngleTwoPi((float)-Math.Atan2(dir.Y, dir.X) + MathHelper.PiOver2);
int clockDir = (int)Math.Round((angle / MathHelper.TwoPi) * 12);
if (clockDir == 0) clockDir = 12;
return TextManager.Get("SubDirOClock").Replace("[dir]", clockDir.ToString());
}
private Vector2 GetTransducerCenter()
{
if (!UseTransducers || connectedTransducers.Count == 0) return Vector2.Zero;
Vector2 transducerPosSum = Vector2.Zero;
foreach (ConnectedTransducer transducer in connectedTransducers)
{
transducerPosSum += transducer.Transducer.Item.WorldPosition;
}
return transducerPosSum / connectedTransducers.Count;
}
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power = 0, float signalStrength = 1.0f)
{
base.ReceiveSignal(stepsTaken, signal, connection, source, sender, power, signalStrength);
if (connection.Name == "transducer_in")
{
var transducer = source.GetComponent<SonarTransducer>();
if (transducer == null) return;
var connectedTransducer = connectedTransducers.Find(t => t.Transducer == transducer);
if (connectedTransducer == null)
{
connectedTransducers.Add(new ConnectedTransducer(transducer, signalStrength, 1.0f));
}
else
{
connectedTransducer.SignalStrength = signalStrength;
connectedTransducer.DisconnectTimer = 1.0f;
}
}
}
public void ServerRead(ClientNetObject type, Lidgren.Network.NetBuffer msg, Client c)
{
bool isActive = msg.ReadBoolean();
bool directionalPing = useDirectionalPing;
float zoomT = zoom, pingDirectionT = 0.0f;
if (isActive)
{
zoomT = msg.ReadRangedSingle(0.0f, 1.0f, 8);
directionalPing = msg.ReadBoolean();
if (directionalPing)
{
pingDirectionT = msg.ReadRangedSingle(0.0f, 1.0f, 8);
}
}
if (!item.CanClientAccess(c)) return;
IsActive = isActive;
#if CLIENT
activeTickBox.Selected = IsActive;
#endif
if (isActive)
{
zoom = MathHelper.Lerp(MinZoom, MaxZoom, zoomT);
useDirectionalPing = directionalPing;
if (useDirectionalPing)
{
float pingAngle = MathHelper.Lerp(0.0f, MathHelper.TwoPi, pingDirectionT);
pingDirection = new Vector2((float)Math.Cos(pingAngle), (float)Math.Sin(pingAngle));
}
#if CLIENT
zoomSlider.BarScroll = zoomT;
directionalTickBox.Selected = useDirectionalPing;
directionalSlider.BarScroll = pingDirectionT;
#endif
}
item.CreateServerEvent(this);
}
public void ServerWrite(Lidgren.Network.NetBuffer msg, Client c, object[] extraData = null)
{
msg.Write(IsActive);
if (IsActive)
{
msg.WriteRangedSingle(zoom, MinZoom, MaxZoom, 8);
msg.Write(useDirectionalPing);
if (useDirectionalPing)
{
float pingAngle = MathUtils.WrapAngleTwoPi(MathUtils.VectorToAngle(pingDirection));
msg.WriteRangedSingle(MathUtils.InverseLerp(0.0f, MathHelper.TwoPi, pingAngle), 0.0f, 1.0f, 8);
}
}
}
}
}
@@ -0,0 +1,36 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Xml.Linq;
namespace Barotrauma.Items.Components
{
class SonarTransducer : Powered
{
const float SendSignalInterval = 0.5f;
private float sendSignalTimer;
public SonarTransducer(Item item, XElement element) : base(item, element)
{
IsActive = true;
}
public override void Update(float deltaTime, Camera cam)
{
UpdateOnActiveEffects(deltaTime);
if (voltage >= minVoltage || PowerConsumption <= 0.0f)
{
sendSignalTimer += deltaTime;
if (sendSignalTimer > SendSignalInterval)
{
item.SendSignal(0, "0101101101101011010", "data_out", sender: null);
sendSignalTimer = SendSignalInterval;
}
}
voltage = 0.0f;
}
}
}
@@ -2,7 +2,9 @@
using FarseerPhysics;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Xml.Linq;
using Voronoi2;
@@ -11,10 +13,13 @@ namespace Barotrauma.Items.Components
partial class Steering : Powered, IServerSerializable, IClientSerializable
{
private const float AutopilotRayCastInterval = 0.5f;
private const float RecalculatePathInterval = 10.0f;
private Vector2 currVelocity;
private Vector2 targetVelocity;
private Vector2 steeringInput;
private bool autoPilot;
private Vector2? posToMaintain;
@@ -27,10 +32,19 @@ namespace Barotrauma.Items.Components
private bool unsentChanges;
private float autopilotRayCastTimer;
private float autopilotRecalculatePathTimer;
private Vector2 avoidStrength;
private float neutralBallastLevel;
private float steeringAdjustSpeed = 1.0f;
private Character user;
private Sonar sonar;
private Submarine controlledSub;
public bool AutoPilot
{
@@ -38,11 +52,10 @@ namespace Barotrauma.Items.Components
set
{
if (value == autoPilot) return;
autoPilot = value;
#if CLIENT
autopilotTickBox.Selected = value;
autopilotTickBox.Selected = autoPilot;
manualTickBox.Selected = !autoPilot;
maintainPosTickBox.Enabled = autoPilot;
levelEndTickBox.Enabled = autoPilot;
levelStartTickBox.Enabled = autoPilot;
@@ -50,24 +63,19 @@ namespace Barotrauma.Items.Components
if (autoPilot)
{
if (pathFinder == null) pathFinder = new PathFinder(WayPoint.WayPointList, false);
#if CLIENT
ToggleMaintainPosition(maintainPosTickBox);
#endif
MaintainPos = true;
}
#if CLIENT
else
{
maintainPosTickBox.Selected = false;
levelEndTickBox.Selected = false;
levelStartTickBox.Selected = false;
posToMaintain = null;
PosToMaintain = null;
MaintainPos = false;
LevelEndSelected = false;
LevelStartSelected = false;
}
#endif
}
}
[Editable(0.0f, 1.0f, ToolTip = "How full the ballast tanks should be when the submarine is not being steered upwards/downwards."
[Editable(0.0f, 1.0f, decimals: 3, ToolTip = "How full the ballast tanks should be when the submarine is not being steered upwards/downwards."
+" Can be used to compensate if the ballast tanks are too large/small relative to the size of the submarine."), Serialize(0.5f, true)]
public float NeutralBallastLevel
{
@@ -78,6 +86,13 @@ namespace Barotrauma.Items.Components
}
}
[Serialize(1000.0f, true)]
public float DockingAssistThreshold
{
get;
set;
}
public Vector2 TargetVelocity
{
get { return targetVelocity;}
@@ -88,12 +103,53 @@ namespace Barotrauma.Items.Components
targetVelocity.Y = MathHelper.Clamp(value.Y, -100.0f, 100.0f);
}
}
public Vector2 SteeringInput
{
get { return steeringInput; }
set
{
if (!MathUtils.IsValid(value)) return;
steeringInput.X = MathHelper.Clamp(value.X, -100.0f, 100.0f);
steeringInput.Y = MathHelper.Clamp(value.Y, -100.0f, 100.0f);
}
}
public SteeringPath SteeringPath
{
get { return steeringPath; }
}
public Vector2? PosToMaintain
{
get { return posToMaintain; }
set { posToMaintain = value; }
}
struct ObstacleDebugInfo
{
public Vector2 Point1;
public Vector2 Point2;
public Vector2? Intersection;
public float Dot;
public Vector2 AvoidStrength;
public ObstacleDebugInfo(GraphEdge edge, Vector2? intersection, float dot, Vector2 avoidStrength)
{
Point1 = edge.Point1;
Point2 = edge.Point2;
Intersection = intersection;
Dot = dot;
AvoidStrength = avoidStrength;
}
}
//edge point 1, edge point 2, avoid strength
private List<ObstacleDebugInfo> debugDrawObstacles = new List<ObstacleDebugInfo>();
public Steering(Item item, XElement element)
: base(item, element)
{
@@ -103,11 +159,24 @@ namespace Barotrauma.Items.Components
partial void InitProjSpecific();
public override void OnItemLoaded()
{
sonar = item.GetComponent<Sonar>();
}
public override bool Select(Character character)
{
if (!CanBeSelected) return false;
user = character;
return true;
}
public override void Update(float deltaTime, Camera cam)
{
networkUpdateTimer -= deltaTime;
if (unsentChanges)
{
networkUpdateTimer -= deltaTime;
if (networkUpdateTimer <= 0.0f)
{
#if CLIENT
@@ -123,14 +192,21 @@ namespace Barotrauma.Items.Components
item.CreateServerEvent(this);
}
networkUpdateTimer = 0.5f;
networkUpdateTimer = 0.1f;
unsentChanges = false;
}
}
controlledSub = item.Submarine;
var sonar = item.GetComponent<Sonar>();
if (sonar != null && sonar.UseTransducers)
{
controlledSub = sonar.ConnectedTransducers.Any() ? sonar.ConnectedTransducers.First().Item.Submarine : null;
}
currPowerConsumption = powerConsumption;
if (voltage < minVoltage && currPowerConsumption > 0.0f) return;
if (voltage < minVoltage && currPowerConsumption > 0.0f) { return; }
ApplyStatusEffects(ActionType.OnActive, deltaTime, null);
@@ -138,6 +214,30 @@ namespace Barotrauma.Items.Components
{
UpdateAutoPilot(deltaTime);
}
else
{
if (user != null && user.Info != null && user.SelectedConstruction == item)
{
user.Info.IncreaseSkillLevel("helm", 0.005f * deltaTime, user.WorldPosition + Vector2.UnitY * 150.0f);
}
Vector2 velocityDiff = steeringInput - targetVelocity;
if (velocityDiff != Vector2.Zero)
{
if (steeringAdjustSpeed >= 0.99f)
{
TargetVelocity = steeringInput;
}
else
{
float steeringChange = 1.0f / (1.0f - steeringAdjustSpeed);
steeringChange *= steeringChange * 10.0f;
TargetVelocity += Vector2.Normalize(velocityDiff) *
Math.Min(steeringChange * deltaTime, velocityDiff.Length());
}
}
}
item.SendSignal(0, targetVelocity.X.ToString(CultureInfo.InvariantCulture), "velocity_x_out", null);
@@ -151,6 +251,7 @@ namespace Barotrauma.Items.Components
private void UpdateAutoPilot(float deltaTime)
{
if (controlledSub == null) return;
if (posToMaintain != null)
{
SteerTowardsPosition((Vector2)posToMaintain);
@@ -158,34 +259,53 @@ namespace Barotrauma.Items.Components
}
autopilotRayCastTimer -= deltaTime;
autopilotRecalculatePathTimer -= deltaTime;
if (autopilotRecalculatePathTimer <= 0.0f)
{
//periodically recalculate the path in case the sub ends up to a position
//where it can't keep traversing the initially calculated path
UpdatePath();
autopilotRecalculatePathTimer = RecalculatePathInterval;
}
steeringPath.CheckProgress(ConvertUnits.ToSimUnits(item.Submarine.WorldPosition), 10.0f);
steeringPath.CheckProgress(ConvertUnits.ToSimUnits(controlledSub.WorldPosition), 10.0f);
if (autopilotRayCastTimer <= 0.0f && steeringPath.NextNode != null)
{
Vector2 diff = Vector2.Normalize(ConvertUnits.ToSimUnits(steeringPath.NextNode.Position - item.Submarine.WorldPosition));
Vector2 diff = ConvertUnits.ToSimUnits(steeringPath.NextNode.Position - controlledSub.WorldPosition);
bool nextVisible = true;
for (int x = -1; x < 2; x += 2)
//if the node is close enough, check if it's visible
float lengthSqr = diff.LengthSquared();
if (lengthSqr > 0.001f && lengthSqr < 500.0f)
{
for (int y = -1; y < 2; y += 2)
diff = Vector2.Normalize(diff);
//check if the next waypoint is visible from all corners of the sub
//(i.e. if we can navigate directly towards it or if there's obstacles in the way)
bool nextVisible = true;
for (int x = -1; x < 2; x += 2)
{
Vector2 cornerPos =
new Vector2(item.Submarine.Borders.Width * x, item.Submarine.Borders.Height * y) / 2.0f;
for (int y = -1; y < 2; y += 2)
{
Vector2 cornerPos =
new Vector2(controlledSub.Borders.Width * x, controlledSub.Borders.Height * y) / 2.0f;
cornerPos = ConvertUnits.ToSimUnits(cornerPos * 1.2f + item.Submarine.WorldPosition);
cornerPos = ConvertUnits.ToSimUnits(cornerPos * 1.2f + controlledSub.WorldPosition);
float dist = Vector2.Distance(cornerPos, steeringPath.NextNode.SimPosition);
float dist = Vector2.Distance(cornerPos, steeringPath.NextNode.SimPosition);
if (Submarine.PickBody(cornerPos, cornerPos + diff * dist, null, Physics.CollisionLevel) == null) continue;
if (Submarine.PickBody(cornerPos, cornerPos + diff * dist, null, Physics.CollisionLevel) == null) continue;
nextVisible = false;
x = 2;
y = 2;
nextVisible = false;
x = 2;
y = 2;
}
}
if (nextVisible) steeringPath.SkipToNextNode();
}
if (nextVisible) steeringPath.SkipToNextNode();
autopilotRayCastTimer = AutopilotRayCastInterval;
}
@@ -195,35 +315,48 @@ namespace Barotrauma.Items.Components
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 avoidDist = new Vector2(
Math.Max(1000.0f * Math.Abs(controlledSub.Velocity.X), controlledSub.Borders.Width * 1.5f),
Math.Max(1000.0f * Math.Abs(controlledSub.Velocity.Y), controlledSub.Borders.Height * 1.5f));
float avoidRadius = avoidDist.Length();
Vector2 newAvoidStrength = Vector2.Zero;
debugDrawObstacles.Clear();
//steer away from nearby walls
var closeCells = Level.Loaded.GetCells(item.Submarine.WorldPosition, 4);
var closeCells = Level.Loaded.GetCells(controlledSub.WorldPosition, 4);
foreach (VoronoiCell cell in closeCells)
{
foreach (GraphEdge edge in cell.edges)
foreach (GraphEdge edge in cell.Edges)
{
var intersection = MathUtils.GetLineIntersection(edge.point1, edge.point2, item.Submarine.WorldPosition, cell.Center);
if (intersection != null)
if (MathUtils.GetLineIntersection(edge.Point1, edge.Point2, controlledSub.WorldPosition, cell.Center, out Vector2 intersection))
{
Vector2 diff = item.Submarine.WorldPosition - (Vector2)intersection;
Vector2 diff = controlledSub.WorldPosition - intersection;
float dist = diff.Length();
//far enough or too close to normalize the diff -> ignore
if (dist > avoidRadius || dist < 0.00001f) continue;
//far enough -> ignore
if (Math.Abs(diff.X) > avoidDist.X && Math.Abs(diff.Y) > avoidDist.Y)
{
debugDrawObstacles.Add(new ObstacleDebugInfo(edge, intersection, 0.0f, Vector2.Zero));
continue;
}
if (diff.LengthSquared() < 1.0f) diff = Vector2.UnitY;
float dot = item.Submarine.Velocity == Vector2.Zero ?
0.0f : Vector2.Dot(item.Submarine.Velocity, -Vector2.Normalize(diff));
Vector2 normalizedDiff = Vector2.Normalize(diff);
float dot = controlledSub.Velocity == Vector2.Zero ?
0.0f : Vector2.Dot(controlledSub.Velocity, -normalizedDiff);
//not heading towards the wall -> ignore
if (dot < 0.5) continue;
Vector2 change = (Vector2.Normalize(diff) * Math.Max((avoidRadius - diff.Length()), 0.0f)) / avoidRadius;
if (dot < 0.5)
{
debugDrawObstacles.Add(new ObstacleDebugInfo(edge, intersection, dot, Vector2.Zero));
continue;
}
Vector2 change = (normalizedDiff * Math.Max((avoidRadius - diff.Length()), 0.0f)) / avoidRadius;
newAvoidStrength += change * dot;
debugDrawObstacles.Add(new ObstacleDebugInfo(edge, intersection, dot, change * dot));
}
}
}
@@ -235,22 +368,21 @@ namespace Barotrauma.Items.Components
//steer away from other subs
foreach (Submarine sub in Submarine.Loaded)
{
if (sub == item.Submarine) continue;
if (item.Submarine.DockedTo.Contains(sub)) continue;
if (sub == controlledSub) continue;
if (controlledSub.DockedTo.Contains(sub)) continue;
float thisSize = Math.Max(item.Submarine.Borders.Width, item.Submarine.Borders.Height);
float thisSize = Math.Max(controlledSub.Borders.Width, controlledSub.Borders.Height);
float otherSize = Math.Max(sub.Borders.Width, sub.Borders.Height);
Vector2 diff = item.Submarine.WorldPosition - sub.WorldPosition;
Vector2 diff = controlledSub.WorldPosition - sub.WorldPosition;
float dist = diff == Vector2.Zero ? 0.0f : diff.Length();
//far enough -> ignore
if (dist > thisSize + otherSize) continue;
Vector2 dir = dist <= 0.0001f ? Vector2.UnitY : diff / dist;
float dot = item.Submarine.Velocity == Vector2.Zero ?
0.0f : Vector2.Dot(Vector2.Normalize(item.Submarine.Velocity), -dir);
float dot = controlledSub.Velocity == Vector2.Zero ?
0.0f : Vector2.Dot(Vector2.Normalize(controlledSub.Velocity), -dir);
//heading away -> ignore
if (dot < 0.0f) continue;
@@ -264,7 +396,6 @@ namespace Barotrauma.Items.Components
{
targetVelocity *= 100.0f / velMagnitude;
}
}
private void UpdatePath()
@@ -280,20 +411,15 @@ namespace Barotrauma.Items.Components
{
target = ConvertUnits.ToSimUnits(Level.Loaded.StartPosition);
}
steeringPath = pathFinder.FindPath(ConvertUnits.ToSimUnits(item.WorldPosition), target);
steeringPath = pathFinder.FindPath(ConvertUnits.ToSimUnits(controlledSub == null ? item.WorldPosition : controlledSub.WorldPosition), target, "(Autopilot, target: " + target + ")");
}
public void SetDestinationLevelStart()
{
AutoPilot = true;
MaintainPos = false;
posToMaintain = null;
LevelEndSelected = false;
if (!LevelStartSelected)
{
LevelStartSelected = true;
@@ -303,13 +429,10 @@ namespace Barotrauma.Items.Components
public void SetDestinationLevelEnd()
{
AutoPilot = false;
AutoPilot = true;
MaintainPos = false;
posToMaintain = null;
LevelStartSelected = false;
if (!LevelEndSelected)
{
LevelEndSelected = true;
@@ -320,10 +443,10 @@ namespace Barotrauma.Items.Components
{
float prediction = 10.0f;
Vector2 futurePosition = ConvertUnits.ToDisplayUnits(item.Submarine.Velocity) * prediction;
Vector2 targetSpeed = ((worldPosition - item.Submarine.WorldPosition) - futurePosition);
Vector2 futurePosition = ConvertUnits.ToDisplayUnits(controlledSub.Velocity) * prediction;
Vector2 targetSpeed = ((worldPosition - controlledSub.WorldPosition) - futurePosition);
if (targetSpeed.Length()>500.0f)
if (targetSpeed.Length() > 500.0f)
{
targetSpeed = Vector2.Normalize(targetSpeed);
TargetVelocity = targetSpeed * 100.0f;
@@ -333,8 +456,52 @@ namespace Barotrauma.Items.Components
TargetVelocity = targetSpeed / 5.0f;
}
}
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power=0.0f)
public override bool AIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective)
{
if (user != character && user != null && user.SelectedConstruction == item)
{
character.Speak(TextManager.Get("DialogSteeringTaken"), null, 0.0f, "steeringtaken", 10.0f);
}
user = character;
switch (objective.Option.ToLowerInvariant())
{
case "maintainposition":
if (!posToMaintain.HasValue)
{
unsentChanges = true;
posToMaintain = controlledSub == null ? item.WorldPosition : controlledSub.WorldPosition;
}
if (!AutoPilot || !MaintainPos) unsentChanges = true;
AutoPilot = true;
MaintainPos = true;
break;
case "navigateback":
if (!AutoPilot || MaintainPos || LevelEndSelected || !LevelStartSelected)
{
unsentChanges = true;
}
SetDestinationLevelStart();
break;
case "navigatetodestination":
if (!AutoPilot || MaintainPos || !LevelEndSelected || LevelStartSelected)
{
unsentChanges = true;
}
SetDestinationLevelEnd();
break;
}
sonar?.AIOperate(deltaTime, character, objective);
return false;
}
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power = 0.0f, float signalStrength = 1.0f)
{
if (connection.Name == "velocity_in")
{
@@ -342,14 +509,14 @@ namespace Barotrauma.Items.Components
}
else
{
base.ReceiveSignal(stepsTaken, signal, connection, source, sender, power);
base.ReceiveSignal(stepsTaken, signal, connection, source, sender, power, signalStrength);
}
}
public void ServerRead(ClientNetObject type, Lidgren.Network.NetBuffer msg, Barotrauma.Networking.Client c)
{
bool autoPilot = msg.ReadBoolean();
Vector2 newTargetVelocity = targetVelocity;
Vector2 newSteeringInput = targetVelocity;
bool maintainPos = false;
Vector2? newPosToMaintain = null;
bool headingToStart = false;
@@ -370,20 +537,22 @@ namespace Barotrauma.Items.Components
}
else
{
newTargetVelocity = new Vector2(msg.ReadFloat(), msg.ReadFloat());
newSteeringInput = new Vector2(msg.ReadFloat(), msg.ReadFloat());
}
if (!item.CanClientAccess(c)) return;
if (!item.CanClientAccess(c)) return;
user = c.Character;
AutoPilot = autoPilot;
if (!AutoPilot)
{
targetVelocity = newTargetVelocity;
steeringInput = newSteeringInput;
steeringAdjustSpeed = MathHelper.Lerp(0.2f, 1.0f, c.Character.GetSkillLevel("helm") / 100.0f);
}
else
{
MaintainPos = newPosToMaintain != null;
posToMaintain = newPosToMaintain;
@@ -411,8 +580,11 @@ namespace Barotrauma.Items.Components
if (!autoPilot)
{
//no need to write steering info if autopilot is controlling
msg.Write(steeringInput.X);
msg.Write(steeringInput.Y);
msg.Write(targetVelocity.X);
msg.Write(targetVelocity.Y);
msg.Write(steeringAdjustSpeed);
}
else
{
@@ -2,6 +2,7 @@
using Lidgren.Network;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Xml.Linq;
namespace Barotrauma.Items.Components
@@ -26,12 +27,38 @@ namespace Barotrauma.Items.Components
private float lastSentCharge;
//charge indicator description
protected Vector2 indicatorPosition, indicatorSize;
protected bool isHorizontal;
public float CurrPowerOutput
{
get;
private set;
}
[Serialize("0,0", true)]
public Vector2 IndicatorPosition
{
get { return indicatorPosition; }
set { indicatorPosition = value; }
}
[Serialize("0,0", true)]
public Vector2 IndicatorSize
{
get { return indicatorSize; }
set { indicatorSize = value; }
}
[Serialize(false, true)]
public bool IsHorizontal
{
get { return isHorizontal; }
set { isHorizontal = value; }
}
[Editable(ToolTip = "Maximum output of the device when fully charged (kW)."), Serialize(10.0f, true)]
public float MaxOutPut
{
@@ -87,10 +114,6 @@ namespace Barotrauma.Items.Components
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;
InitProjSpecific();
@@ -100,11 +123,7 @@ namespace Barotrauma.Items.Components
public override bool Pick(Character picker)
{
if (picker == null) return false;
//picker.SelectedConstruction = (picker.SelectedConstruction == item) ? null : item;
return true;
return picker != null;
}
public override void Update(float deltaTime, Camera cam)
@@ -112,14 +131,25 @@ namespace Barotrauma.Items.Components
float chargeRatio = (float)(Math.Sqrt(charge / capacity));
float gridPower = 0.0f;
float gridLoad = 0.0f;
List<Pair<Powered, Connection>> directlyConnected = new List<Pair<Powered, Connection>>();
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;
if (pt == null)
{
foreach (Powered powered in c2.Item.GetComponents<Powered>())
{
if (!powered.IsActive) continue;
directlyConnected.Add(new Pair<Powered, Connection>(powered, c2));
gridLoad += powered.CurrPowerConsumption;
}
continue;
}
if (!pt.IsActive) { continue; }
gridLoad += pt.PowerLoad;
gridPower -= pt.CurrPowerConsumption;
@@ -143,7 +173,7 @@ namespace Barotrauma.Items.Components
currPowerConsumption = MathHelper.Lerp(currPowerConsumption, rechargeSpeed, 0.05f);
Charge += currPowerConsumption * rechargeVoltage / 3600.0f;
}
//provide power to the grid
if (gridLoad > 0.0f)
{
@@ -159,28 +189,57 @@ namespace Barotrauma.Items.Components
CurrPowerOutput = MathHelper.Lerp(
CurrPowerOutput,
Math.Min(maxOutput * chargeRatio, gridLoad),
deltaTime);
deltaTime * 10.0f);
}
else
{
CurrPowerOutput = MathHelper.Lerp(CurrPowerOutput, 0.0f, deltaTime);
CurrPowerOutput = MathHelper.Lerp(CurrPowerOutput, 0.0f, deltaTime * 10.0f);
}
Charge -= CurrPowerOutput / 3600.0f;
}
foreach (Pair<Powered, Connection> connected in directlyConnected)
{
connected.First.ReceiveSignal(0, "", connected.Second, source: item, sender: null,
power: gridLoad <= 0.0f ? 1.0f : CurrPowerOutput / gridLoad);
}
rechargeVoltage = 0.0f;
outputVoltage = 0.0f;
}
public override bool AIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective)
{
RechargeSpeed = maxRechargeSpeed * 0.5f;
if (GameMain.Client != null) return false;
if (string.IsNullOrEmpty(objective.Option) || objective.Option.ToLowerInvariant() == "charge")
{
if (Math.Abs(rechargeSpeed - maxRechargeSpeed * 0.5f) > 0.05f)
{
item.CreateServerEvent(this);
RechargeSpeed = maxRechargeSpeed * 0.5f;
character.Speak(TextManager.Get("DialogChargeBatteries")
.Replace("[itemname]", item.Name)
.Replace("[rate]", ((int)(rechargeSpeed / maxRechargeSpeed * 100.0f)).ToString()), null, 1.0f, "chargebattery", 10.0f);
}
}
else
{
if (rechargeSpeed > 0.0f)
{
item.CreateServerEvent(this);
RechargeSpeed = 0.0f;
character.Speak(TextManager.Get("DialogStopChargingBatteries")
.Replace("[itemname]", item.Name)
.Replace("[rate]", ((int)(rechargeSpeed / maxRechargeSpeed * 100.0f)).ToString()), null, 1.0f, "chargebattery", 10.0f);
}
}
return true;
}
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power)
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power, float signalStrength = 1.0f)
{
if (!connection.IsPower) return;
@@ -193,15 +252,15 @@ namespace Barotrauma.Items.Components
outputVoltage = power;
}
}
public void ServerRead(ClientNetObject type, NetBuffer msg, Client c)
{
float newRechargeSpeed = msg.ReadRangedInteger(0,10) / 10.0f * maxRechargeSpeed;
float newRechargeSpeed = msg.ReadRangedInteger(0, 10) / 10.0f * maxRechargeSpeed;
if (item.CanClientAccess(c))
{
RechargeSpeed = newRechargeSpeed;
GameServer.Log(c.Character.LogName + " set the recharge speed of "+item.Name+" to "+ (int)((rechargeSpeed / maxRechargeSpeed) * 100.0f) + " %", ServerLog.MessageType.ItemInteraction);
GameServer.Log(c.Character.LogName + " set the recharge speed of " + item.Name + " to " + (int)((rechargeSpeed / maxRechargeSpeed) * 100.0f) + " %", ServerLog.MessageType.ItemInteraction);
}
item.CreateServerEvent(this);
@@ -1,17 +1,18 @@
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Items.Components
{
partial class PowerTransfer : Powered
{
static float fullPower;
static float fullLoad;
private static float fullPower;
private static float fullLoad;
private int updateCount;
//affects how fast changes in power/load are carried over the grid
static float inertia = 5.0f;
@@ -104,7 +105,11 @@ namespace Barotrauma.Items.Components
{
IsActive = true;
canTransfer = true;
InitProjectSpecific(element);
}
partial void InitProjectSpecific(XElement element);
public override void UpdateBroken(float deltaTime, Camera cam)
{
@@ -168,13 +173,17 @@ namespace Barotrauma.Items.Components
//(except if running as a client)
if (GameMain.Client != null) continue;
//items in a bad condition are more sensitive to overvoltage
float maxOverVoltage = MathHelper.Lerp(Math.Min(OverloadVoltage, 1.0f), OverloadVoltage, item.Condition / 100.0f);
//if the item can't be fixed, don't allow it to break
if (item.FixRequirements.Count == 0 || !CanBeOverloaded) continue;
if (!item.Repairables.Any() || !CanBeOverloaded) continue;
//relays don't blow up if the power is higher than load, only if the output is high enough
//(i.e. enough power passing through the relay)
if (this is RelayComponent) continue;
if (-pt.currPowerConsumption < Math.Max(pt.powerLoad, 200.0f) * OverloadVoltage) continue;
if (-pt.currPowerConsumption < Math.Max(pt.powerLoad, 200.0f) * maxOverVoltage) continue;
float prevCondition = pt.item.Condition;
pt.item.Condition -= deltaTime * 10.0f;
@@ -182,7 +191,11 @@ namespace Barotrauma.Items.Components
if (pt.item.Condition <= 0.0f && prevCondition > 0.0f)
{
#if CLIENT
sparkSounds[Rand.Int(sparkSounds.Length)].Play(1.0f, 600.0f, pt.item.WorldPosition);
if (sparkSounds.Count > 0)
{
var sparkSound = sparkSounds[Rand.Int(sparkSounds.Count)];
SoundPlayer.PlaySound(sparkSound.Sound, sparkSound.Volume, sparkSound.Range, pt.item.WorldPosition, pt.item.CurrentHull);
}
Vector2 baseVel = Rand.Vector(300.0f);
for (int i = 0; i < 10; i++)
@@ -194,7 +207,12 @@ namespace Barotrauma.Items.Components
}
#endif
if (FireProbability > 0.0f && FireProbability < Rand.Range(0.0f, 1.0f))
float currentIntensity = GameMain.GameSession?.EventManager != null ?
GameMain.GameSession.EventManager.CurrentIntensity : 0.5f;
//higher probability for fires if the current intensity is low
if (FireProbability > 0.0f &&
Rand.Range(0.0f, 1.0f) < MathHelper.Lerp(FireProbability, FireProbability * 0.1f, currentIntensity))
{
new FireSource(pt.item.WorldPosition);
}
@@ -298,7 +316,6 @@ namespace Barotrauma.Items.Components
foreach (Connection c in PowerConnections)
{
var recipients = c.Recipients;
foreach (Connection recipient in recipients)
{
if (recipient == null) continue;
@@ -308,8 +325,9 @@ namespace Barotrauma.Items.Components
if (it.Condition <= 0.0f) continue;
foreach (Powered powered in it.GetComponents<Powered>())
foreach (ItemComponent ic in it.components)
{
Powered powered = ic as Powered;
if (powered == null || !powered.IsActive) continue;
if (connectedList.Contains(powered)) continue;
@@ -357,8 +375,7 @@ namespace Barotrauma.Items.Components
fullPower -= powered.CurrPowerConsumption;
}
}
}
}
}
}
}
@@ -391,7 +408,7 @@ namespace Barotrauma.Items.Components
SetAllConnectionsDirty();
}
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power)
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power, float signalStrength = 1.0f)
{
if (connection.IsPower) return;
@@ -399,7 +416,7 @@ namespace Barotrauma.Items.Components
if (!connectedRecipients.ContainsKey(connection)) return;
if (connection.Name.Length > 5 && connection.Name.Substring(0, 6).ToLowerInvariant() == "signal")
if (connection.Name.Length > 5 && connection.Name.Substring(0, 6) == "signal")
{
foreach (Connection recipient in connectedRecipients[connection])
{
@@ -410,16 +427,17 @@ namespace Barotrauma.Items.Components
//powertransfer components don't need to receive the signal in the pass-through signal connections
//because we relay it straight to the connected items without going through the whole chain of junction boxes
if (ic is PowerTransfer && connection.Name.Contains("signal")) continue;
ic.ReceiveSignal(stepsTaken, signal, recipient, source, sender, 0.0f);
ic.ReceiveSignal(stepsTaken, signal, recipient, source, sender, 0.0f, signalStrength);
}
bool broken = recipient.Item.Condition <= 0.0f;
foreach (StatusEffect effect in recipient.effects)
{
recipient.Item.ApplyStatusEffect(effect, ActionType.OnUse, 1.0f);
if (broken && effect.type != ActionType.OnBroken) continue;
recipient.Item.ApplyStatusEffect(effect, ActionType.OnUse, 1.0f, null, null, false, false);
}
}
}
}
}
}
@@ -1,5 +1,9 @@
using System;
using System.Xml.Linq;
using Microsoft.Xna.Framework;
#if CLIENT
using Barotrauma.Sounds;
#endif
namespace Barotrauma.Items.Components
{
@@ -69,27 +73,15 @@ namespace Barotrauma.Items.Components
public Powered(Item item, XElement element)
: base(item, element)
{
#if CLIENT
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);
}
}
#endif
InitProjectSpecific(element);
}
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power = 0)
partial void InitProjectSpecific(XElement element);
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power = 0, float signalStrength = 1.0f)
{
if (currPowerConsumption == 0.0f) voltage = 0.0f;
if (connection.IsPower) voltage = power;
if (connection.IsPower) voltage = Math.Max(0.0f, power);
}
protected void UpdateOnActiveEffects(float deltaTime)
@@ -109,9 +101,9 @@ namespace Barotrauma.Items.Components
if (voltage > minVoltage)
{
ApplyStatusEffects(ActionType.OnActive, deltaTime, null);
if (!powerOnSoundPlayed)
if (!powerOnSoundPlayed && powerOnSound != null)
{
powerOnSound.Play(1.0f, 600.0f, item.WorldPosition);
SoundPlayer.PlaySound(powerOnSound.Sound, powerOnSound.Volume, powerOnSound.Range, item.WorldPosition, item.CurrentHull);
powerOnSoundPlayed = true;
}
}
@@ -43,7 +43,16 @@ namespace Barotrauma.Items.Components
public List<Body> IgnoredBodies;
public Character User;
private Character user;
public Character User
{
get { return user; }
set
{
user = value;
attack?.SetUser(user);
}
}
private float persistentStickJointTimer;
@@ -53,7 +62,20 @@ namespace Barotrauma.Items.Components
get { return launchImpulse; }
set { launchImpulse = value; }
}
[Serialize(0.0f, false)]
public float LaunchRotation
{
get { return MathHelper.ToDegrees(LaunchRotationRadians); }
set { LaunchRotationRadians = MathHelper.ToRadians(value); }
}
public float LaunchRotationRadians
{
get;
private set;
}
[Serialize(false, false)]
//backwards compatibility, can stick to anything
public bool DoesStick
@@ -105,7 +127,7 @@ namespace Barotrauma.Items.Components
foreach (XElement subElement in element.Elements())
{
if (subElement.Name.ToString().ToLowerInvariant() != "attack") continue;
attack = new Attack(subElement);
attack = new Attack(subElement, item.Name + ", Projectile");
}
}
@@ -168,16 +190,18 @@ namespace Barotrauma.Items.Components
if (stickTarget != null)
{
#if DEBUG
try
{
#endif
item.body.FarseerBody.RestoreCollisionWith(stickTarget);
#if DEBUG
}
catch (Exception e)
{
#if DEBUG
DebugConsole.ThrowError("Failed to restore collision with stickTarget", e);
#endif
}
#endif
stickTarget = null;
}
@@ -188,6 +212,7 @@ namespace Barotrauma.Items.Components
private void DoHitscan(Vector2 dir)
{
float rotation = item.body.Rotation;
Vector2 simPositon = item.SimPosition;
item.Drop();
item.body.Enabled = true;
@@ -196,8 +221,8 @@ namespace Barotrauma.Items.Components
item.body.LinearVelocity = dir;
IsActive = true;
Vector2 rayStart = item.SimPosition;
Vector2 rayEnd = item.SimPosition + dir * 1000.0f;
Vector2 rayStart = simPositon;
Vector2 rayEnd = simPositon + dir * 1000.0f;
List<HitscanResult> hits = new List<HitscanResult>();
@@ -294,29 +319,24 @@ namespace Barotrauma.Items.Components
{
if (stickTarget != null)
{
try
if (GameMain.World.BodyList.Contains(stickTarget))
{
item.body.FarseerBody.RestoreCollisionWith(stickTarget);
}
catch
{
//the body that the projectile was stuck to has been removed
}
stickTarget = null;
}
try
if (stickJoint != null)
{
GameMain.World.RemoveJoint(stickJoint);
}
catch
{
//the body that the projectile was stuck to has been removed
}
if (GameMain.World.JointList.Contains(stickJoint))
{
GameMain.World.RemoveJoint(stickJoint);
}
stickJoint = null;
stickJoint = null;
}
if (!item.body.FarseerBody.IsBullet) IsActive = false;
}
}
@@ -350,22 +370,28 @@ namespace Barotrauma.Items.Components
item.body.Submarine = submarine;
return !Hitscan;
}
Structure structure;
if (target.Body.UserData is Limb limb)
else if (target.Body.UserData is Limb limb)
{
//severed limbs don't deactivate the projectile (but may still slow it down enough to make it inactive)
if (limb.IsSevered)
{
target.Body.ApplyLinearImpulse(item.body.LinearVelocity * item.body.Mass);
return true;
}
limb.character.LastDamageSource = item;
attackResult = attack.DoDamageToLimb(User, limb, item.WorldPosition, 1.0f);
if (limb.character != null)
character = limb.character;
if (limb.character != null) character = limb.character;
}
else if ((structure = (target.Body.UserData as Structure)) != null)
else if (target.Body.UserData is Structure structure)
{
attackResult = attack.DoDamage(User, structure, item.WorldPosition, 1.0f);
}
}
ApplyStatusEffects(ActionType.OnUse, 1.0f, character);
ApplyStatusEffects(ActionType.OnImpact, 1.0f, character);
if (character != null) character.LastDamageSource = item;
ApplyStatusEffects(ActionType.OnUse, 1.0f, character, target.Body.UserData as Limb, user: user);
ApplyStatusEffects(ActionType.OnImpact, 1.0f, character, target.Body.UserData as Limb, user: user);
item.body.FarseerBody.OnCollision -= OnProjectileCollision;
@@ -425,11 +451,12 @@ namespace Barotrauma.Items.Components
{
if (stickJoint != null) return;
stickJoint = new PrismaticJoint(targetBody, item.body.FarseerBody, item.body.SimPosition, axis, true);
stickJoint.MotorEnabled = true;
stickJoint.MaxMotorForce = 30.0f;
stickJoint.LimitEnabled = true;
stickJoint = new PrismaticJoint(targetBody, item.body.FarseerBody, item.body.SimPosition, axis, true)
{
MotorEnabled = true,
MaxMotorForce = 30.0f,
LimitEnabled = true
};
if (item.Sprite != null)
{
stickJoint.LowerLimit = ConvertUnits.ToSimUnits(item.Sprite.size.X * -0.3f);
@@ -0,0 +1,204 @@
using Barotrauma.Networking;
using Lidgren.Network;
using Microsoft.Xna.Framework;
using System;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Items.Components
{
partial class Repairable : ItemComponent, IServerSerializable, IClientSerializable
{
public static float SkillIncreaseMultiplier = 0.4f;
private string header;
private float lastSentProgress;
private float fixDurationLowSkill, fixDurationHighSkill;
private float deteriorationTimer;
[Serialize(0.0f, true), Editable(MinValueFloat = 0.0f, MaxValueFloat = 100.0f, DecimalCount = 2, ToolTip = "How fast the condition of the item deteriorates per second.")]
public float DeteriorationSpeed
{
get;
set;
}
[Serialize(0.0f, true), Editable(MinValueFloat = 0.0f, MaxValueFloat = 1000.0f, DecimalCount = 2, ToolTip = "Minimum initial delay before the item starts to deteriorate.")]
public float MinDeteriorationDelay
{
get;
set;
}
[Serialize(0.0f, true), Editable(MinValueFloat = 0.0f, MaxValueFloat = 1000.0f, DecimalCount = 2, ToolTip = "Maximum initial delay before the item starts to deteriorate.")]
public float MaxDeteriorationDelay
{
get;
set;
}
[Serialize(50.0f, true), Editable(MinValueFloat = 0.0f, MaxValueFloat = 100.0f, ToolTip = "The item won't deteriorate spontaneously if the condition is below this value. For example, if set to 10, the condition will spontaneously drop to 10 and then stop dropping (unless the item is damaged further by external factors).")]
public float MinDeteriorationCondition
{
get;
set;
}
[Serialize(80.0f, true), Editable(MinValueFloat = 0.0f, MaxValueFloat = 100.0f, ToolTip = "The condition of the item has to be below this before the repair UI becomes usable.")]
public float ShowRepairUIThreshold
{
get;
set;
}
/*private float repairProgress;
public float RepairProgress
{
get { return repairProgress; }
set
{
repairProgress = MathHelper.Clamp(value, 0.0f, 1.0f);
if (repairProgress >= 1.0f && currentFixer != null) currentFixer.AnimController.Anim = AnimController.Animation.None;
}
}*/
private Character currentFixer;
public Character CurrentFixer
{
get { return currentFixer; }
set
{
if (currentFixer == value || item.Condition >= 100.0f) return;
if (currentFixer != null) currentFixer.AnimController.Anim = AnimController.Animation.None;
currentFixer = value;
}
}
public Repairable(Item item, XElement element)
: base(item, element)
{
IsActive = true;
canBeSelected = true;
this.item = item;
header = element.GetAttributeString("name", "");
fixDurationLowSkill = element.GetAttributeFloat("fixdurationlowskill", 100.0f);
fixDurationHighSkill = element.GetAttributeFloat("fixdurationhighskill", 5.0f);
InitProjSpecific(element);
}
public override void OnItemLoaded()
{
deteriorationTimer = Rand.Range(MinDeteriorationDelay, MaxDeteriorationDelay);
//let the clients know the initial deterioration delay
item.CreateServerEvent(this);
}
partial void InitProjSpecific(XElement element);
public void StartRepairing(Character character)
{
CurrentFixer = character;
}
public override void UpdateBroken(float deltaTime, Camera cam)
{
Update(deltaTime, cam);
}
public override void Update(float deltaTime, Camera cam)
{
UpdateProjSpecific(deltaTime);
if (CurrentFixer == null)
{
if (item.Condition > 0.0f)
{
if (deteriorationTimer > 0.0f)
{
if (GameMain.Client == null)
{
deteriorationTimer -= deltaTime;
if (deteriorationTimer <= 0.0f) { item.CreateServerEvent(this); }
}
return;
}
if (item.Condition > MinDeteriorationCondition)
{
item.Condition -= DeteriorationSpeed * deltaTime;
}
}
return;
}
if (CurrentFixer.SelectedConstruction != item || !currentFixer.CanInteractWith(item))
{
currentFixer.AnimController.Anim = AnimController.Animation.None;
currentFixer = null;
return;
}
UpdateFixAnimation(CurrentFixer);
if (GameMain.Client != null) return;
float successFactor = requiredSkills.Count == 0 ? 1.0f : 0.0f;
foreach (Skill skill in requiredSkills)
{
float characterSkillLevel = CurrentFixer.GetSkillLevel(skill.Identifier);
if (characterSkillLevel >= skill.Level) successFactor += 1.0f / requiredSkills.Count;
CurrentFixer.Info.IncreaseSkillLevel(skill.Identifier,
SkillIncreaseMultiplier * deltaTime / Math.Max(characterSkillLevel, 1.0f),
CurrentFixer.WorldPosition + Vector2.UnitY * 100.0f);
}
bool wasBroken = item.Condition < item.Prefab.Health;
float fixDuration = MathHelper.Lerp(fixDurationLowSkill, fixDurationHighSkill, successFactor);
if (fixDuration <= 0.0f)
{
item.Condition = item.Prefab.Health;
}
else
{
item.Condition += deltaTime / (fixDuration / item.Prefab.Health);
}
if (wasBroken && item.Condition >= item.Prefab.Health)
{
SteamAchievementManager.OnItemRepaired(item, currentFixer);
}
}
partial void UpdateProjSpecific(float deltaTime);
private void UpdateFixAnimation(Character character)
{
character.AnimController.UpdateUseItem(false, item.WorldPosition + new Vector2(0.0f, 100.0f) * ((item.Condition / 100.0f) % 0.1f));
}
public void ServerWrite(NetBuffer msg, Client c, object[] extraData = null)
{
msg.Write(deteriorationTimer);
}
public void ClientRead(ServerNetObject type, NetBuffer msg, float sendingTime)
{
deteriorationTimer = msg.ReadSingle();
}
public void ClientWrite(NetBuffer msg, object[] extraData = null)
{
//no need to write anything, just letting the server know we started repairing
}
public void ServerRead(ClientNetObject type, NetBuffer msg, Client c)
{
if (c.Character == null) return;
StartRepairing(c.Character);
}
}
}
@@ -1,4 +1,5 @@
using System;
using System.Globalization;
using System.Xml.Linq;
namespace Barotrauma.Items.Components
@@ -14,7 +15,7 @@ namespace Barotrauma.Items.Components
//the output is sent if both inputs have received a signal within the timeframe
protected float timeFrame;
[InGameEditable, Serialize(0.0f, true)]
[InGameEditable(DecimalCount = 2), Serialize(0.0f, true)]
public float TimeFrame
{
get { return timeFrame; }
@@ -46,16 +47,16 @@ namespace Barotrauma.Items.Components
}
}
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power=0.0f)
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power = 0.0f, float signalStrength = 1.0f)
{
switch (connection.Name)
{
case "signal_in1":
float.TryParse(signal, out receivedSignal[0]);
float.TryParse(signal, NumberStyles.Float, CultureInfo.InvariantCulture, out receivedSignal[0]);
timeSinceReceived[0] = 0.0f;
break;
case "signal_in2":
float.TryParse(signal, out receivedSignal[1]);
float.TryParse(signal, NumberStyles.Float, CultureInfo.InvariantCulture, out receivedSignal[1]);
timeSinceReceived[1] = 0.0f;
break;
}
@@ -13,7 +13,7 @@ namespace Barotrauma.Items.Components
//the output is sent if both inputs have received a signal within the timeframe
protected float timeFrame;
[InGameEditable, Serialize(0.0f, true)]
[InGameEditable(DecimalCount = 2), Serialize(0.0f, true)]
public float TimeFrame
{
get { return timeFrame; }
@@ -59,7 +59,7 @@ namespace Barotrauma.Items.Components
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)
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power = 0.0f, float signalStrength = 1.0f)
{
switch (connection.Name)
{
@@ -1,6 +1,7 @@
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Items.Components
@@ -12,14 +13,16 @@ namespace Barotrauma.Items.Components
public readonly string Name;
public Wire[] Wires;
private Wire[] wires;
public IEnumerable<Wire> Wires
{
get { return wires; }
}
private Item item;
public readonly bool IsOutput;
private static Wire draggingConnected;
public readonly List<StatusEffect> effects;
public readonly ushort[] wireId;
@@ -30,45 +33,45 @@ namespace Barotrauma.Items.Components
private set;
}
private bool recipientsDirty = true;
private List<Connection> recipients = new List<Connection>();
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);
}
if (recipientsDirty) RefreshRecipients();
return recipients;
}
}
public Item Item
{
get { return item; }
}
public Connection(XElement element, Item item)
public ConnectionPanel ConnectionPanel
{
get;
private set;
}
public Connection(XElement element, ConnectionPanel connectionPanel)
{
#if CLIENT
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);
}
connector = GUI.Style.GetComponentStyle("ConnectionPanelConnector").Sprites[GUIComponent.ComponentState.None][0].Sprite;
wireVertical = GUI.Style.GetComponentStyle("ConnectionPanelWire").Sprites[GUIComponent.ComponentState.None][0].Sprite;
connectionSprite = GUI.Style.GetComponentStyle("ConnectionPanelConnection").Sprites[GUIComponent.ComponentState.None][0].Sprite;
connectionSpriteHighlight = GUI.Style.GetComponentStyle("ConnectionPanelConnection").Sprites[GUIComponent.ComponentState.Hover][0].Sprite;
screwSprites = GUI.Style.GetComponentStyle("ConnectionPanelScrew").Sprites[GUIComponent.ComponentState.None].Select(s => s.Sprite).ToList();
}
#endif
ConnectionPanel = connectionPanel;
item = connectionPanel.Item;
this.item = item;
//recipient = new Connection[MaxLinked];
Wires = new Wire[MaxLinked];
wires = new Wire[MaxLinked];
IsOutput = (element.Name.ToString() == "output");
Name = element.GetAttributeString("name", (IsOutput) ? "output" : "input");
@@ -98,37 +101,48 @@ namespace Barotrauma.Items.Components
break;
case "statuseffect":
effects.Add(StatusEffect.Load(subElement));
effects.Add(StatusEffect.Load(subElement, item.Name + ", connection " + Name));
break;
}
}
}
private void RefreshRecipients()
{
recipients.Clear();
for (int i = 0; i < MaxLinked; i++)
{
if (wires[i] == null) continue;
Connection recipient = wires[i].OtherConnection(this);
if (recipient != null) recipients.Add(recipient);
}
recipientsDirty = false;
}
public int FindEmptyIndex()
{
for (int i = 0; i < MaxLinked; i++)
{
if (Wires[i] == null) return 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(Wire wire)
{
for (int i = 0; i < MaxLinked; i++)
{
if (wires[i] == wire) 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;
if (wires[i] == null && wireItem == null) return i;
if (wires[i] != null && wires[i].Item == wireItem) return i;
}
return -1;
}
@@ -137,26 +151,27 @@ namespace Barotrauma.Items.Components
{
for (int i = 0; i < MaxLinked; i++)
{
if (Wires[i] == null)
if (wires[i] == null)
{
Wires[i] = wire;
SetWire(i, wire);
return;
}
}
}
public void AddLink(int index, Wire wire)
public void SetWire(int index, Wire wire)
{
Wires[index] = wire;
wires[index] = wire;
recipientsDirty = true;
}
public void SendSignal(int stepsTaken, string signal, Item source, Character sender, float power)
public void SendSignal(int stepsTaken, string signal, Item source, Character sender, float power, float signalStrength = 1.0f)
{
for (int i = 0; i < MaxLinked; i++)
{
if (Wires[i] == null) continue;
if (wires[i] == null) continue;
Connection recipient = Wires[i].OtherConnection(this);
Connection recipient = wires[i].OtherConnection(this);
if (recipient == null) continue;
if (recipient.item == this.item || recipient.item == source) continue;
@@ -167,12 +182,14 @@ namespace Barotrauma.Items.Components
foreach (ItemComponent ic in recipient.item.components)
{
ic.ReceiveSignal(stepsTaken, signal, recipient, item, sender, power);
ic.ReceiveSignal(stepsTaken, signal, recipient, source, sender, power, signalStrength);
}
bool broken = recipient.Item.Condition <= 0.0f;
foreach (StatusEffect effect in recipient.effects)
{
recipient.item.ApplyStatusEffect(effect, ActionType.OnUse, 1.0f);
if (broken && effect.type != ActionType.OnBroken) continue;
recipient.Item.ApplyStatusEffect(effect, ActionType.OnUse, 1.0f, null, null, false, false);
}
}
}
@@ -181,10 +198,11 @@ namespace Barotrauma.Items.Components
{
for (int i = 0; i < MaxLinked; i++)
{
if (Wires[i] == null) continue;
if (wires[i] == null) continue;
Wires[i].RemoveConnection(this);
Wires[i] = null;
wires[i].RemoveConnection(this);
wires[i] = null;
recipientsDirty = true;
}
}
@@ -196,15 +214,16 @@ namespace Barotrauma.Items.Components
{
if (wireId[i] == 0) continue;
Item wireItem = MapEntity.FindEntityByID(wireId[i]) as Item;
Item wireItem = Entity.FindEntityByID(wireId[i]) as Item;
if (wireItem == null) continue;
Wires[i] = wireItem.GetComponent<Wire>();
wires[i] = wireItem.GetComponent<Wire>();
recipientsDirty = true;
if (Wires[i] != null)
if (wires[i] != null)
{
if (Wires[i].Item.body != null) Wires[i].Item.body.Enabled = false;
Wires[i].Connect(this, false, false);
if (wires[i].Item.body != null) wires[i].Item.body.Enabled = false;
wires[i].Connect(this, false, false);
}
}
}
@@ -214,7 +233,7 @@ namespace Barotrauma.Items.Components
{
XElement newElement = new XElement(IsOutput ? "output" : "input", new XAttribute("name", Name));
Array.Sort(Wires, delegate (Wire wire1, Wire wire2)
Array.Sort(wires, delegate (Wire wire1, Wire wire2)
{
if (wire1 == null) return 1;
if (wire2 == null) return -1;
@@ -223,10 +242,10 @@ namespace Barotrauma.Items.Components
for (int i = 0; i < MaxLinked; i++)
{
if (Wires[i] == null) continue;
if (wires[i] == null) continue;
newElement.Add(new XElement("link",
new XAttribute("w", Wires[i].Item.ID.ToString())));
new XAttribute("w", wires[i].Item.ID.ToString())));
}
parentElement.Add(newElement);
@@ -1,6 +1,7 @@
using Barotrauma.Networking;
using FarseerPhysics;
using Lidgren.Network;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -10,8 +11,6 @@ namespace Barotrauma.Items.Components
{
partial class ConnectionPanel : ItemComponent, IServerSerializable, IClientSerializable
{
public static Wire HighlightedWire;
public List<Connection> Connections;
private Character user;
@@ -23,6 +22,13 @@ namespace Barotrauma.Items.Components
set;
}
//connection panels can't be deactivated
public override bool IsActive
{
get { return true; }
set { /*do nothing*/ }
}
public ConnectionPanel(Item item, XElement element)
: base(item, element)
{
@@ -33,17 +39,20 @@ namespace Barotrauma.Items.Components
switch (subElement.Name.ToString())
{
case "input":
Connections.Add(new Connection(subElement, item));
Connections.Add(new Connection(subElement, this));
break;
case "output":
Connections.Add(new Connection(subElement, item));
Connections.Add(new Connection(subElement, this));
break;
}
}
IsActive = true;
InitProjSpecific(element);
}
partial void InitProjSpecific(XElement element);
public override void OnMapLoaded()
{
foreach (Connection c in Connections)
@@ -52,9 +61,57 @@ namespace Barotrauma.Items.Components
}
}
public override void OnItemLoaded()
{
if (item.body != null)
{
var holdable = item.GetComponent<Holdable>();
if (holdable == null || !holdable.Attachable)
{
DebugConsole.ThrowError("Item \"" + item.Name + "\" has a ConnectionPanel component," +
" but cannot be wired because it has an active physics body that cannot be attached to a wall." +
" Remove the physics body or add a Holdable component with the Attachable attribute set to true.");
}
}
}
public void MoveConnectedWires(Vector2 amount)
{
Vector2 wireNodeOffset = item.Submarine == null ? Vector2.Zero : item.Submarine.HiddenSubPosition + amount;
foreach (Connection c in Connections)
{
foreach (Wire wire in c.Wires)
{
if (wire == null) continue;
#if CLIENT
if (wire.Item.IsSelected) continue;
#endif
var wireNodes = wire.GetNodes();
if (wireNodes.Count == 0) continue;
if (Submarine.RectContains(item.Rect, wireNodes[0] + wireNodeOffset))
{
wire.MoveNode(0, amount);
}
else if (Submarine.RectContains(item.Rect, wireNodes[wireNodes.Count - 1] + wireNodeOffset))
{
wire.MoveNode(wireNodes.Count - 1, amount);
}
}
}
}
public override void Update(float deltaTime, Camera cam)
{
if (user != null && user.SelectedConstruction != item) user = null;
if (user == null || user.SelectedConstruction != item)
{
user = null;
return;
}
if (!user.Enabled || !HasRequiredItems(user, addMessage: false)) { return; }
user.AnimController.UpdateUseItem(true, item.WorldPosition + new Vector2(0.0f, 100.0f) * (((float)Timing.TotalTime / 10.0f) % 0.1f));
}
public override bool Select(Character picker)
@@ -70,10 +127,10 @@ namespace Barotrauma.Items.Components
IsActive = true;
return true;
}
public override bool Use(float deltaTime, Character character = null)
{
if (character == null || character!=user) return false;
if (character == null || character != user) return false;
var powered = item.GetComponent<Powered>();
if (powered != null)
@@ -82,7 +139,7 @@ namespace Barotrauma.Items.Components
}
float degreeOfSuccess = DegreeOfSuccess(character);
if (Rand.Range(0.0f, 50.0f) < degreeOfSuccess) return false;
if (Rand.Range(0.0f, 0.5f) < degreeOfSuccess) return false;
character.SetStun(5.0f);
@@ -94,7 +151,7 @@ namespace Barotrauma.Items.Components
public override void Load(XElement element)
{
base.Load(element);
List<Connection> loadedConnections = new List<Connection>();
foreach (XElement subElement in element.Elements())
@@ -102,15 +159,15 @@ namespace Barotrauma.Items.Components
switch (subElement.Name.ToString())
{
case "input":
loadedConnections.Add(new Connection(subElement, item));
loadedConnections.Add(new Connection(subElement, this));
break;
case "output":
loadedConnections.Add(new Connection(subElement, item));
loadedConnections.Add(new Connection(subElement, this));
break;
}
}
for (int i = 0; i<loadedConnections.Count && i<Connections.Count; i++)
for (int i = 0; i < loadedConnections.Count && i < Connections.Count; i++)
{
loadedConnections[i].wireId.CopyTo(Connections[i].wireId, 0);
}
@@ -157,9 +214,9 @@ namespace Barotrauma.Items.Components
{
foreach (Connection connection in Connections)
{
for (int i = 0; i < Connection.MaxLinked; i++)
foreach (Wire wire in connection.Wires)
{
msg.Write(connection.Wires[i]?.Item == null ? (ushort)0 : connection.Wires[i].Item.ID);
msg.Write(wire?.Item == null ? (ushort)0 : wire.Item.ID);
}
}
}
@@ -171,8 +228,7 @@ namespace Barotrauma.Items.Components
//read wire IDs for each connection
for (int i = 0; i < Connections.Count; i++)
{
wires[i] = new List<Wire>();
wires[i] = new List<Wire>();
for (int j = 0; j < Connection.MaxLinked; j++)
{
ushort wireId = msg.ReadUInt16();
@@ -212,9 +268,10 @@ namespace Barotrauma.Items.Components
//go through existing wire links
for (int i = 0; i < Connections.Count; i++)
{
for (int j = 0; j < Connection.MaxLinked; j++)
int j = -1;
foreach (Wire existingWire in Connections[i].Wires)
{
Wire existingWire = Connections[i].Wires[j];
j++;
if (existingWire == null) continue;
//existing wire not in the list of new wires -> disconnect it
@@ -263,9 +320,8 @@ namespace Barotrauma.Items.Components
}
}
Connections[i].Wires[j] = null;
}
Connections[i].SetWire(j, null);
}
}
}
@@ -302,6 +358,6 @@ namespace Barotrauma.Items.Components
public void ServerWrite(NetBuffer msg, Client c, object[] extraData = null)
{
ClientWrite(msg, extraData);
}
}
}
}
@@ -0,0 +1,193 @@
using Barotrauma.Networking;
using Lidgren.Network;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Items.Components
{
partial class CustomInterface : ItemComponent, IClientSerializable, IServerSerializable
{
class CustomInterfaceElement
{
public bool ContinuousSignal;
public bool State;
public string Label, Connection, Signal;
public CustomInterfaceElement(XElement element)
{
Label = element.GetAttributeString("text", "");
Connection = element.GetAttributeString("connection", "");
Signal = element.GetAttributeString("signal", "1");
}
}
private string[] labels;
[Serialize("", true), Editable()]
public string Labels
{
get { return string.Join(",", labels); }
set
{
if (value == null) { return; }
string[] splitValues = value == "" ? new string[0] : value.Split(',');
if (customInterfaceElementList.Count > 0)
{
UpdateLabels(splitValues);
}
}
}
private string[] signals;
[Serialize("", true), Editable()]
public string Signals
{
//use semicolon as a separator because comma may be needed in the signals (for color or vector values for example)
//kind of hacky, we should probably add support for (string) arrays to SerializableEntityEditor so this wouldn't be needed
get { return signals == null ? "" : string.Join(";", signals); }
set
{
if (value == null) { return; }
string[] splitValues = value == "" ? new string[0] : value.Split(';');
if (customInterfaceElementList.Count > 0)
{
signals = new string[customInterfaceElementList.Count];
for (int i = 0; i < customInterfaceElementList.Count; i++)
{
signals[i] = i < splitValues.Length ? splitValues[i] : customInterfaceElementList[i].Signal;
customInterfaceElementList[i].Signal = signals[i];
}
}
}
}
private List<CustomInterfaceElement> customInterfaceElementList = new List<CustomInterfaceElement>();
public CustomInterface(Item item, XElement element)
: base(item, element)
{
int i = 0;
foreach (XElement subElement in element.Elements())
{
switch (subElement.Name.ToString().ToLowerInvariant())
{
case "button":
var button = new CustomInterfaceElement(subElement)
{
ContinuousSignal = false
};
if (string.IsNullOrEmpty(button.Label))
{
button.Label = "Signal out " + customInterfaceElementList.Count(e => !e.ContinuousSignal);
}
customInterfaceElementList.Add(button);
break;
case "tickbox":
var tickBox = new CustomInterfaceElement(subElement)
{
ContinuousSignal = true
};
if (string.IsNullOrEmpty(tickBox.Label))
{
tickBox.Label = "Signal out " + customInterfaceElementList.Count(e => !e.ContinuousSignal);
}
customInterfaceElementList.Add(tickBox);
break;
}
i++;
}
IsActive = true;
InitProjSpecific(element);
Labels = element.GetAttributeString("labels", "");
Signals = element.GetAttributeString("signals", "");
}
private void UpdateLabels(string[] newLabels)
{
labels = new string[customInterfaceElementList.Count];
for (int i = 0; i < labels.Length; i++)
{
labels[i] = i < newLabels.Length ? newLabels[i] : customInterfaceElementList[i].Label;
customInterfaceElementList[i].Label = labels[i];
}
UpdateLabelsProjSpecific();
}
partial void UpdateLabelsProjSpecific();
partial void InitProjSpecific(XElement element);
private void ButtonClicked(CustomInterfaceElement btnElement)
{
if (btnElement == null) return;
item.SendSignal(0, btnElement.Signal, btnElement.Connection, sender: null, source: item);
}
private void TickBoxToggled(CustomInterfaceElement tickBoxElement, bool state)
{
if (tickBoxElement == null) return;
tickBoxElement.State = state;
}
public override void Update(float deltaTime, Camera cam)
{
foreach (CustomInterfaceElement ciElement in customInterfaceElementList)
{
if (!ciElement.ContinuousSignal) { continue; }
//TODO: allow changing output when a tickbox is not selected
item.SendSignal(0, ciElement.State ? ciElement.Signal : "0", ciElement.Connection, sender: null, source: item);
}
}
public void ServerRead(ClientNetObject type, NetBuffer msg, Client c)
{
bool[] elementStates = new bool[customInterfaceElementList.Count];
for (int i = 0; i < customInterfaceElementList.Count; i++)
{
elementStates[i] = msg.ReadBoolean();
}
CustomInterfaceElement clickedButton = null;
if (item.CanClientAccess(c))
{
for (int i = 0; i < customInterfaceElementList.Count; i++)
{
if (customInterfaceElementList[i].ContinuousSignal)
{
TickBoxToggled(customInterfaceElementList[i], elementStates[i]);
}
else if (elementStates[i])
{
clickedButton = customInterfaceElementList[i];
ButtonClicked(customInterfaceElementList[i]);
}
}
}
//notify all clients of the new state
GameMain.Server.CreateEntityEvent(item, new object[]
{
NetEntityEvent.Type.ComponentState,
item.components.IndexOf(this),
clickedButton
});
item.CreateServerEvent(this);
}
public void ServerWrite(NetBuffer msg, Client c, object[] extraData = null)
{
//extradata contains an array of buttons clicked by a client (or nothing if nothing was clicked)
for (int i = 0; i < customInterfaceElementList.Count; i++)
{
if (customInterfaceElementList[i].ContinuousSignal)
{
msg.Write(customInterfaceElementList[i].State);
}
else
{
msg.Write(extraData != null && extraData.Any(d => d as CustomInterfaceElement == customInterfaceElementList[i]));
}
}
}
}
}
@@ -1,18 +1,29 @@
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Collections.Generic;
using System.Xml.Linq;
namespace Barotrauma.Items.Components
{
class DelayComponent : ItemComponent
{
class DelayedSignal
{
public readonly string Signal;
public readonly float SignalStrength;
public float SendTimer;
public DelayedSignal(string signal, float signalStrength, float sendTimer)
{
Signal = signal;
SignalStrength = signalStrength;
SendTimer = sendTimer;
}
}
const int SignalQueueSize = 500;
private Queue<Pair<string, float>> signalQueue;
private Queue<DelayedSignal> signalQueue;
[InGameEditable(MinValueFloat = 0.0f, MaxValueFloat = 60.0f), Serialize(1.0f, true)]
[InGameEditable(MinValueFloat = 0.0f, MaxValueFloat = 60.0f, DecimalCount = 2), Serialize(1.0f, true)]
public float Delay
{
get;
@@ -26,10 +37,17 @@ namespace Barotrauma.Items.Components
set;
}
[InGameEditable(ToolTip = "Should the component discard previously received signals when the incoming signal changes."), Serialize(false, true)]
public bool ResetWhenDifferentSignalReceived
{
get;
set;
}
public DelayComponent(Item item, XElement element)
: base (item, element)
{
signalQueue = new Queue<Pair<string, float>>();
signalQueue = new Queue<DelayedSignal>();
IsActive = true;
}
@@ -37,24 +55,28 @@ namespace Barotrauma.Items.Components
{
foreach (var val in signalQueue)
{
val.Second -= deltaTime;
val.SendTimer -= deltaTime;
}
while (signalQueue.Count > 0 && signalQueue.Peek().Second <= 0.0f)
while (signalQueue.Count > 0 && signalQueue.Peek().SendTimer <= 0.0f)
{
var signalOut = signalQueue.Dequeue();
item.SendSignal(0, signalOut.First, "signal_out", null);
item.SendSignal(0, signalOut.Signal, "signal_out", null, signalStrength: signalOut.SignalStrength);
}
}
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power = 0.0f)
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power = 0.0f, float signalStrength = 1.0f)
{
switch (connection.Name)
{
case "signal_in":
if (signalQueue.Count >= SignalQueueSize) return;
if (ResetWhenSignalReceived) signalQueue.Clear();
signalQueue.Enqueue(Pair<string, float>.Create(signal, Delay));
if (ResetWhenDifferentSignalReceived && signalQueue.Count > 0 && signalQueue.Peek().Signal != signal)
{
signalQueue.Clear();
}
signalQueue.Enqueue(new DelayedSignal(signal, signalStrength, Delay));
break;
}
}
@@ -13,24 +13,24 @@ namespace Barotrauma.Items.Components
partial class LightComponent : Powered, IServerSerializable, IDrawableComponent
{
private Color lightColor;
private float range;
private float lightBrightness;
private float blinkFrequency;
private float range;
private float flicker;
private bool castShadows;
private bool drawBehindSubs;
private float blinkTimer;
public PhysicsBody ParentBody;
[Editable(0.0f, 2048.0f), Serialize(100.0f, true)]
[Editable(MinValueFloat = 0.0f, MaxValueFloat = 2048.0f), Serialize(100.0f, true)]
public float Range
{
get { return range; }
set
{
range = MathHelper.Clamp(value, 0.0f, 2048.0f);
range = MathHelper.Clamp(value, 0.0f, 4096.0f);
#if CLIENT
if (light != null) light.Range = range;
#endif
@@ -53,6 +53,20 @@ namespace Barotrauma.Items.Components
}
}
[Editable(ToolTip = "Lights drawn behind submarines don't cast any shadows and are much faster to draw than shadow-casting lights. "+
"It's recommended to enable this on decorative lights outside the submarine's hull."), Serialize(false, true)]
public bool DrawBehindSubs
{
get { return drawBehindSubs; }
set
{
drawBehindSubs = value;
#if CLIENT
if (light != null) light.IsBackground = drawBehindSubs;
#endif
}
}
[Editable, Serialize(false, true)]
public bool IsOn
{
@@ -76,6 +90,16 @@ namespace Barotrauma.Items.Components
}
}
[Editable, Serialize(0.0f, true)]
public float BlinkFrequency
{
get { return blinkFrequency; }
set
{
blinkFrequency = MathHelper.Clamp(value, 0.0f, 60.0f);
}
}
[InGameEditable, Serialize("1.0,1.0,1.0,1.0", true)]
public Color LightColor
{
@@ -84,7 +108,7 @@ namespace Barotrauma.Items.Components
{
lightColor = value;
#if CLIENT
if (light != null) light.Color = lightColor;
if (light != null) light.Color = IsActive ? lightColor : Color.Transparent;
#endif
}
}
@@ -118,10 +142,13 @@ namespace Barotrauma.Items.Components
: base (item, element)
{
#if CLIENT
light = new LightSource(element);
light.ParentSub = item.CurrentHull == null ? null : item.CurrentHull.Submarine;
light.Position = item.Position;
light.CastShadows = castShadows;
light = new LightSource(element)
{
ParentSub = item.CurrentHull?.Submarine,
Position = item.Position,
CastShadows = castShadows,
IsBackground = drawBehindSubs
};
#endif
IsActive = IsOn;
@@ -130,6 +157,7 @@ namespace Barotrauma.Items.Components
public override void Update(float deltaTime, Camera cam)
{
UpdateOnActiveEffects(deltaTime);
if (AITarget != null) AITarget.Enabled = voltage > minVoltage || powerConsumption <= 0.0f;
#if CLIENT
light.ParentSub = item.Submarine;
@@ -176,7 +204,11 @@ namespace Barotrauma.Items.Components
if (Rand.Range(0.0f, 1.0f) < 0.05f && voltage < Rand.Range(0.0f, minVoltage))
{
#if CLIENT
if (voltage > 0.1f) sparkSounds[Rand.Int(sparkSounds.Length)].Play(1.0f, 400.0f, item.WorldPosition);
if (voltage > 0.1f && sparkSounds.Count > 0)
{
var sparkSound = sparkSounds[Rand.Int(sparkSounds.Count)];
SoundPlayer.PlaySound(sparkSound.Sound, sparkSound.Volume, sparkSound.Range, item.WorldPosition, item.CurrentHull);
}
#endif
lightBrightness = 0.0f;
}
@@ -185,11 +217,26 @@ namespace Barotrauma.Items.Components
lightBrightness = MathHelper.Lerp(lightBrightness, Math.Min(voltage, 1.0f), 0.1f);
}
#if CLIENT
light.Color = lightColor * lightBrightness * (1.0f-Rand.Range(0.0f,Flicker));
light.Range = range * (float)Math.Sqrt(lightBrightness);
#endif
if (blinkFrequency > 0.0f)
{
blinkTimer = (blinkTimer + deltaTime * blinkFrequency) % 1.0f;
}
if (blinkTimer > 0.5f)
{
#if CLIENT
light.Color = Color.Transparent;
#endif
}
else
{
#if CLIENT
light.Color = lightColor * lightBrightness * (1.0f - Rand.Range(0.0f, Flicker));
light.Range = range;
#endif
item.SightRange = Math.Max(range * (float)Math.Sqrt(lightBrightness), item.SightRange);
}
voltage = 0.0f;
}
@@ -210,9 +257,9 @@ namespace Barotrauma.Items.Components
return true;
}
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power=0.0f)
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power = 0.0f, float signalStrength = 1.0f)
{
base.ReceiveSignal(stepsTaken, signal, connection, source, sender, power);
base.ReceiveSignal(stepsTaken, signal, connection, source, sender, power, signalStrength);
switch (connection.Name)
{
@@ -1,11 +1,12 @@
using Microsoft.Xna.Framework;
using FarseerPhysics;
using Microsoft.Xna.Framework;
using System;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Items.Components
{
class MotionSensor : ItemComponent
partial class MotionSensor : ItemComponent
{
private const float UpdateInterval = 0.1f;
@@ -13,17 +14,54 @@ namespace Barotrauma.Items.Components
private bool motionDetected;
private float range;
private float rangeX, rangeY;
private Vector2 detectOffset;
private float updateTimer;
[InGameEditable, Serialize(0.0f, true)]
public float Range
[Serialize(false, false)]
public bool MotionDetected
{
get { return range; }
get { return motionDetected; }
set { motionDetected = value; }
}
[Serialize(false, true), Editable]
public bool OnlyHumans
{
get;
set;
}
[InGameEditable, Serialize(0.0f, true)]
public float RangeX
{
get { return rangeX; }
set
{
range = MathHelper.Clamp(value, 0.0f, 500.0f);
rangeX = MathHelper.Clamp(value, 0.0f, 1000.0f);
}
}
[InGameEditable, Serialize(0.0f, true)]
public float RangeY
{
get { return rangeY; }
set
{
rangeY = MathHelper.Clamp(value, 0.0f, 1000.0f);
}
}
[Serialize("0,0", true), Editable(ToolTip = "The position to detect the movement at relative to the item. For example, 0,100 would detect movement 100 units above the item.")]
public Vector2 DetectOffset
{
get { return detectOffset; }
set
{
detectOffset = value;
detectOffset.X = MathHelper.Clamp(value.X, -rangeX, rangeX);
detectOffset.Y = MathHelper.Clamp(value.Y, -rangeY, rangeY);
}
}
@@ -41,10 +79,17 @@ namespace Barotrauma.Items.Components
set { falseOutput = value; }
}
public MotionSensor(Item item, XElement element)
: base (item, element)
{
IsActive = true;
//backwards compatibility
if (element.Attribute("range") != null)
{
rangeX = rangeY = element.GetAttributeFloat("range", 0.0f);
}
}
public override void Update(float deltaTime, Camera cam)
@@ -67,16 +112,31 @@ namespace Barotrauma.Items.Components
}
}
Vector2 detectPos = item.WorldPosition + detectOffset;
Rectangle detectRect = new Rectangle((int)(detectPos.X - rangeX), (int)(detectPos.Y - rangeY), (int)(rangeX * 2), (int)(rangeY * 2));
float broadRangeX = Math.Max(rangeX * 2, 500);
float broadRangeY = Math.Max(rangeY * 2, 500);
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;
if (OnlyHumans && c.ConfigPath != Character.HumanConfigFile) { continue; }
motionDetected = true;
break;
}
//do a rough check based on the position of the character's collider first
//before the more accurate limb-based check
if (Math.Abs(c.WorldPosition.X - detectPos.X) > broadRangeX || Math.Abs(c.WorldPosition.Y - detectPos.Y) > broadRangeY)
{
continue;
}
foreach (Limb limb in c.AnimController.Limbs)
{
if (limb.LinearVelocity.LengthSquared() <= 0.001f) continue;
if (MathUtils.CircleIntersectsRectangle(limb.WorldPosition, ConvertUnits.ToDisplayUnits(limb.body.GetMaxExtent()), detectRect))
{
motionDetected = true;
break;
}
}
}
}
}
@@ -8,12 +8,12 @@ namespace Barotrauma.Items.Components
: base (item, element)
{
}
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power=0.0f)
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power = 0.0f, float signalStrength = 1.0f)
{
if (connection.Name != "signal_in") return;
item.SendSignal(stepsTaken, signal=="0" ? "1" : "0", "signal_out", sender);
item.SendSignal(stepsTaken, signal == "0" ? "1" : "0", "signal_out", sender, 0.0f, source, signalStrength);
}
}
}
@@ -27,7 +27,7 @@ namespace Barotrauma.Items.Components
set;
}
[InGameEditable, Serialize(1.0f, true)]
[InGameEditable(DecimalCount = 2), Serialize(1.0f, true)]
public float Frequency
{
get { return frequency; }
@@ -71,14 +71,14 @@ namespace Barotrauma.Items.Components
}
}
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power = 0.0f)
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power = 0.0f, float signalStrength = 1.0f)
{
switch (connection.Name)
{
case "set_frequency":
case "frequency_in":
float newFrequency;
if (float.TryParse(signal, out newFrequency))
if (float.TryParse(signal, NumberStyles.Float, CultureInfo.InvariantCulture, out newFrequency))
{
Frequency = newFrequency;
}
@@ -75,18 +75,19 @@ namespace Barotrauma.Items.Components
}
}
string signalOut = previousResult ? Output : FalseOutput;
if (ContinuousOutput)
{
item.SendSignal(0, previousResult ? Output : FalseOutput, "signal_out", null);
if (!string.IsNullOrEmpty(signalOut)) { item.SendSignal(0, signalOut, "signal_out", null); }
}
else if (!nonContinuousOutputSent)
{
item.SendSignal(0, previousResult ? Output : FalseOutput, "signal_out", null);
if (!string.IsNullOrEmpty(signalOut)) { item.SendSignal(0, signalOut, "signal_out", null); }
nonContinuousOutputSent = true;
}
}
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power = 0.0f)
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power = 0.0f, float signalStrength = 1.0f)
{
switch (connection.Name)
{
@@ -53,13 +53,11 @@ namespace Barotrauma.Items.Components
if (Math.Min(-currPowerConsumption, PowerLoad) > maxPower) item.Condition = 0.0f;
}
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power=0.0f)
{
if (connection.IsPower) return;
if (item.Condition <= 0.0f) return;
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power = 0.0f, float signalStrength = 1.0f)
{
if (connection.IsPower || item.Condition <= 0.0f) return;
if (connection.Name.Contains("_in"))
{
if (!IsOn) return;
@@ -68,10 +66,8 @@ namespace Barotrauma.Items.Components
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);
item.SendSignal(stepsTaken, signal, outConnection, sender, power, source, signalStrength);
}
else if (connection.Name == "toggle")
{
@@ -33,7 +33,7 @@ namespace Barotrauma.Items.Components
{
}
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power=0.0f)
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power = 0.0f, float signalStrength = 1.0f)
{
switch (connection.Name)
{
@@ -41,7 +41,7 @@ namespace Barotrauma.Items.Components
string signalOut = (signal == targetSignal) ? output : falseOutput;
if (string.IsNullOrWhiteSpace(signalOut)) return;
item.SendSignal(stepsTaken, signalOut, "signal_out", sender);
item.SendSignal(stepsTaken, signalOut, "signal_out", sender, signalStrength);
break;
case "set_output":
@@ -0,0 +1,25 @@
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Items.Components
{
class SmokeDetector : ItemComponent
{
[Serialize(50.0f, false)]
public float FireSizeThreshold
{
get; set;
}
public SmokeDetector(Item item, XElement element)
: base (item, element)
{
IsActive = true;
}
public override void Update(float deltaTime, Camera cam)
{
item.SendSignal(0, item.CurrentHull != null && item.CurrentHull.FireSources.Any(fs => fs.Size.X > FireSizeThreshold) ? "1" : "0", "signal_out", null);
}
}
}
@@ -4,15 +4,50 @@ namespace Barotrauma.Items.Components
{
class WaterDetector : ItemComponent
{
private string output, falseOutput;
[InGameEditable, Serialize("1", true)]
public string Output
{
get { return output; }
set { output = value; }
}
[InGameEditable, Serialize("0", true)]
public string FalseOutput
{
get { return falseOutput; }
set { falseOutput = value; }
}
public WaterDetector(Item item, XElement element)
: base (item, element)
: base(item, element)
{
IsActive = true;
}
public override void Update(float deltaTime, Camera cam)
{
item.SendSignal(0, item.InWater ? "1" : "0", "signal_out", null);
string signalOut = falseOutput;
if (item.InWater)
{
//item in water -> we definitely want to send the True output
signalOut = Output;
}
else if (item.CurrentHull != null)
{
//item in not water -> check if there's water anywhere within the rect of the item
if (item.CurrentHull.Surface > item.CurrentHull.Rect.Y - item.CurrentHull.Rect.Height + 1 &&
item.CurrentHull.Surface > item.Rect.Y - item.Rect.Height)
{
signalOut = output;
}
}
if (!string.IsNullOrEmpty(signalOut))
{
item.SendSignal(0, signalOut, "signal_out", null);
}
}
}
}
@@ -6,13 +6,17 @@ using System.Xml.Linq;
namespace Barotrauma.Items.Components
{
class WifiComponent : ItemComponent
partial class WifiComponent : ItemComponent
{
private static List<WifiComponent> list = new List<WifiComponent>();
private float range;
private int channel;
private float chatMsgCooldown;
private string prevSignal;
public byte TeamID;
@@ -33,18 +37,36 @@ namespace Barotrauma.Items.Components
}
}
[Editable(ToolTip = "If enabled, any signals received by the item are displayed as chat messages in the chatbox of the player holding the item."), Serialize(false, false)]
[Editable(ToolTip =
"If enabled, any signals received from another chat-linked wifi component are displayed "+
"as chat messages in the chatbox of the player holding the item."), Serialize(false, false)]
public bool LinkToChat
{
get;
set;
}
[Editable(ToolTip = "How many seconds have to pass between signals for a message to be displayed in the chatbox. "+
"Setting this to a very low value is not recommended, because it may cause an excessive amount of chat messages to be created "+
"if there are chat-linked wifi components that transmit a continuous signal."), Serialize(1.0f, true)]
public float MinChatMessageInterval
{
get;
set;
}
[Editable(ToolTip = "If set to true, the component will only create chat messages when the received signal changes."), Serialize(false, true)]
public bool DiscardDuplicateChatMessages
{
get;
set;
}
public WifiComponent(Item item, XElement element)
: base (item, element)
{
list.Add(this);
IsActive = true;
}
public bool CanTransmit()
@@ -59,57 +81,86 @@ namespace Barotrauma.Items.Components
public bool CanReceive(WifiComponent sender)
{
if (!HasRequiredContainedItems(false)) return false;
if (sender == null || sender.channel != channel || sender.TeamID != TeamID) return false;
if (Vector2.DistanceSquared(item.WorldPosition, sender.item.WorldPosition) > sender.range * sender.range) return false;
return Vector2.Distance(item.WorldPosition, sender.item.WorldPosition) <= sender.Range;
return HasRequiredContainedItems(false);
}
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power=0.0f)
public override void Update(float deltaTime, Camera cam)
{
var senderComponent = source.GetComponent<WifiComponent>();
chatMsgCooldown -= deltaTime;
}
public void TransmitSignal(int stepsTaken, string signal, Item source, Character sender, bool sendToChat, float signalStrength = 1.0f)
{
var senderComponent = source?.GetComponent<WifiComponent>();
if (senderComponent != null && !CanReceive(senderComponent)) return;
if (LinkToChat)
bool chatMsgSent = false;
var receivers = GetReceiversInRange();
foreach (WifiComponent wifiComp in receivers)
{
if (item.ParentInventory != null &&
item.ParentInventory.Owner != null &&
item.ParentInventory.Owner == Character.Controlled &&
GameMain.NetworkMember != null)
//signal strength diminishes by distance
float sentSignalStrength = signalStrength *
MathHelper.Clamp(1.0f - (Vector2.Distance(item.WorldPosition, wifiComp.item.WorldPosition) / wifiComp.range), 0.0f, 1.0f);
wifiComp.item.SendSignal(stepsTaken, signal, "signal_out", sender, 0, source, sentSignalStrength);
if (source != null)
{
if (senderComponent != null)
foreach (Item receiverItem in wifiComp.item.LastSentSignalRecipients)
{
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);
if (source != null)
if (!source.LastSentSignalRecipients.Contains(receiverItem))
{
foreach (Item receiverItem in wifiComp.item.LastSentSignalRecipients)
{
if (!source.LastSentSignalRecipients.Contains(receiverItem))
{
source.LastSentSignalRecipients.Add(receiverItem);
}
}
source.LastSentSignalRecipients.Add(receiverItem);
}
}
break;
}
if (DiscardDuplicateChatMessages && signal == prevSignal) continue;
if (LinkToChat && wifiComp.LinkToChat && chatMsgCooldown <= 0.0f && sendToChat)
{
if (wifiComp.item.ParentInventory != null &&
wifiComp.item.ParentInventory.Owner != null &&
GameMain.NetworkMember != null)
{
string chatMsg = signal;
if (senderComponent != null)
{
chatMsg = ChatMessage.ApplyDistanceEffect(chatMsg, 1.0f - sentSignalStrength);
}
if (chatMsg.Length > ChatMessage.MaxLength) chatMsg = chatMsg.Substring(0, ChatMessage.MaxLength);
if (string.IsNullOrEmpty(chatMsg)) continue;
if (wifiComp.item.ParentInventory.Owner == Character.Controlled)
{
if (GameMain.Client == null)
GameMain.NetworkMember.AddChatMessage(signal, ChatMessageType.Radio, source == null ? "" : source.Name);
}
else if (GameMain.Server != null)
{
Client recipientClient = GameMain.Server.ConnectedClients.Find(c => c.Character == wifiComp.item.ParentInventory.Owner);
if (recipientClient != null)
{
GameMain.Server.SendDirectChatMessage(
ChatMessage.Create(source == null ? "" : source.Name, chatMsg, ChatMessageType.Radio, null), recipientClient);
}
}
chatMsgSent = true;
}
}
}
if (chatMsgSent) chatMsgCooldown = MinChatMessageInterval;
prevSignal = signal;
}
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power = 0.0f, float signalStrength = 1.0f)
{
if (connection == null || connection.Name != "signal_in") return;
TransmitSignal(stepsTaken, signal, source, sender, true, signalStrength);
}
protected override void RemoveComponentSpecific()
@@ -40,8 +40,18 @@ namespace Barotrauma.Items.Components
private bool canPlaceNode;
private Vector2 newNodePos;
public bool Hidden, Locked;
public bool Hidden;
private bool locked;
public bool Locked
{
get
{
return locked || connections.Any(c => c != null && c.ConnectionPanel.Locked);
}
set { locked = value; }
}
public Connection[] Connections
{
@@ -95,13 +105,14 @@ namespace Barotrauma.Items.Components
{
if (connections[i] == null || connections[i].Item != item) continue;
for (int n = 0; n < connections[i].Wires.Length; n++)
foreach (Wire wire in connections[i].Wires)
{
if (connections[i].Wires[n] != this) continue;
if (wire != this) continue;
SetConnectedDirty();
connections[i].Wires[n] = null;
connections[i].SetWire(connections[i].FindWireIndex(wire), null);
}
connections[i] = null;
}
}
@@ -142,18 +153,29 @@ namespace Barotrauma.Items.Components
if (!addNode) break;
if (newConnection.Item.Submarine == null) continue;
Submarine refSub = newConnection.Item.Submarine;
if (refSub == null)
{
Structure attachTarget = Structure.GetAttachTarget(newConnection.Item.WorldPosition);
if (attachTarget == null) continue;
refSub = attachTarget.Submarine;
}
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;
Vector2 nodePos = refSub == null ?
newConnection.Item.Position :
newConnection.Item.Position - refSub.HiddenSubPosition;
if (nodes.Count > 0 && nodes[0] == nodePos) break;
if (nodes.Count > 1 && nodes[nodes.Count - 1] == nodePos) break;
if (i == 0)
{
nodes.Insert(0, newConnection.Item.Position - newConnection.Item.Submarine.HiddenSubPosition);
nodes.Insert(0, nodePos);
}
else
{
nodes.Add(newConnection.Item.Position - newConnection.Item.Submarine.HiddenSubPosition);
nodes.Add(nodePos);
}
break;
@@ -226,7 +248,7 @@ namespace Barotrauma.Items.Components
if (Screen.Selected != GameMain.SubEditorScreen)
{
//cannot run wires from sub to another
if (sub == null || (item.Submarine != sub && sub != null && item.Submarine != null))
if (item.Submarine != sub && sub != null && item.Submarine != null)
{
ClearConnections();
return;
@@ -234,12 +256,18 @@ namespace Barotrauma.Items.Components
if (item.CurrentHull == null)
{
newNodePos = item.WorldPosition - sub.Position - sub.HiddenSubPosition;
canPlaceNode = false;
Structure attachTarget = Structure.GetAttachTarget(item.WorldPosition);
canPlaceNode = attachTarget != null;
sub = attachTarget?.Submarine;
newNodePos = sub == null ?
item.WorldPosition :
item.WorldPosition - sub.Position - sub.HiddenSubPosition;
}
else
{
newNodePos = RoundNode(item.Position, item.CurrentHull) - sub.HiddenSubPosition;
newNodePos = RoundNode(item.Position, item.CurrentHull);
if (sub != null) { newNodePos -= sub.HiddenSubPosition; }
canPlaceNode = true;
}
@@ -250,7 +278,7 @@ namespace Barotrauma.Items.Components
if (user == null) return;
Vector2 prevNodePos = nodes[nodes.Count - 1];
prevNodePos += sub.HiddenSubPosition;
if (sub != null) { prevNodePos += sub.HiddenSubPosition; }
float currLength = 0.0f;
for (int i = 0; i < nodes.Count - 1; i++)
@@ -263,8 +291,9 @@ namespace Barotrauma.Items.Components
{
Vector2 diff = nodes[nodes.Count - 1] - newNodePos;
Vector2 pullBackDir = diff == Vector2.Zero ? Vector2.Zero : Vector2.Normalize(diff);
user.AnimController.Collider.ApplyForce(pullBackDir * user.Mass * 50.0f);
user.AnimController.UpdateUseItem(true, user.SimPosition + pullBackDir * 2.0f);
user.AnimController.UpdateUseItem(true, user.WorldPosition + pullBackDir * 200.0f);
if (currLength > MaxLength * 1.5f && GameMain.Client == null)
{
ClearConnections();
@@ -344,6 +373,13 @@ namespace Barotrauma.Items.Components
UpdateSections();
}
public void MoveNode(int index, Vector2 amount)
{
if (index < 0 || index >= nodes.Count) return;
nodes[index] += amount;
UpdateSections();
}
public void MoveNodes(Vector2 amount)
{
for (int i = 0; i < nodes.Count; i++)
@@ -397,7 +433,7 @@ namespace Barotrauma.Items.Components
int wireIndex = connections[i].FindWireIndex(item);
if (wireIndex == -1) continue;
connections[i].AddLink(wireIndex, null);
connections[i].SetWire(wireIndex, null);
connections[i] = null;
}
@@ -512,7 +548,7 @@ namespace Barotrauma.Items.Components
return closestIndex;
}
public override void FlipX()
public override void FlipX(bool relativeToSub)
{
for (int i = 0; i < nodes.Count; i++)
{
@@ -521,6 +557,15 @@ namespace Barotrauma.Items.Components
UpdateSections();
}
public override void FlipY(bool relativeToSub)
{
for (int i = 0; i < nodes.Count; i++)
{
nodes[i] = new Vector2(nodes[i].X, -nodes[i].Y);
}
UpdateSections();
}
public override void Load(XElement componentElement)
{
base.Load(componentElement);
@@ -533,17 +578,9 @@ namespace Barotrauma.Items.Components
{
float x = 0.0f, y = 0.0f;
try
{
x = float.Parse(nodeCoords[i * 2], CultureInfo.InvariantCulture);
}
catch { x = 0.0f; }
float.TryParse(nodeCoords[i * 2], NumberStyles.Float, CultureInfo.InvariantCulture, out x);
try
{
y = float.Parse(nodeCoords[i * 2 + 1], CultureInfo.InvariantCulture);
}
catch { y = 0.0f; }
float.TryParse(nodeCoords[i * 2 + 1], NumberStyles.Float, CultureInfo.InvariantCulture, out y);
nodes.Add(new Vector2(x, y));
}
@@ -571,7 +608,7 @@ namespace Barotrauma.Items.Components
protected override void ShallowRemoveComponentSpecific()
{
for (int i = 0; i < 2; i++)
/*for (int i = 0; i < 2; i++)
{
if (connections[i] == null) continue;
int wireIndex = connections[i].FindWireIndex(item);
@@ -580,7 +617,7 @@ namespace Barotrauma.Items.Components
{
connections[i].AddLink(wireIndex, null);
}
}
}*/
}
protected override void RemoveComponentSpecific()
@@ -4,6 +4,7 @@ using Lidgren.Network;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Xml.Linq;
@@ -12,23 +13,29 @@ namespace Barotrauma.Items.Components
{
partial class Turret : Powered, IDrawableComponent, IServerSerializable
{
Sprite barrelSprite;
private Sprite barrelSprite, railSprite;
Vector2 barrelPos;
private Vector2 barrelPos;
private Vector2 transformedBarrelPos;
bool? hasLight;
LightComponent lightComponent;
private LightComponent lightComponent;
private float rotation, targetRotation;
float rotation, targetRotation;
private float reload, reloadTime;
float reload, reloadTime;
private float minRotation, maxRotation;
float minRotation, maxRotation;
private float launchImpulse;
float launchImpulse;
private Camera cam;
Camera cam;
private float angularVelocity;
private int failedLaunchAttempts;
private Character user;
[Serialize("0,0", false)]
public Vector2 BarrelPos
{
@@ -36,9 +43,18 @@ namespace Barotrauma.Items.Components
{
return barrelPos;
}
set
set
{
barrelPos = value;
UpdateTransformedBarrelPos();
}
}
public Vector2 TransformedBarrelPos
{
get
{
return transformedBarrelPos;
}
}
@@ -49,7 +65,7 @@ namespace Barotrauma.Items.Components
set { launchImpulse = value; }
}
[Serialize(5.0f, false)]
[Serialize(5.0f, false), Editable(0.0f, 1000.0f)]
public float Reload
{
get { return reloadTime; }
@@ -69,6 +85,64 @@ namespace Barotrauma.Items.Components
maxRotation = MathHelper.ToRadians(Math.Max(value.X, value.Y));
rotation = (minRotation + maxRotation) / 2;
#if CLIENT
if (lightComponent != null)
{
lightComponent.Rotation = rotation;
lightComponent.Light.Rotation = -rotation;
}
#endif
}
}
[Serialize(5.0f, false), Editable(0.0f, 1000.0f, DecimalCount = 2)]
public float SpringStiffnessLowSkill
{
get;
private set;
}
[Serialize(2.0f, false), Editable(0.0f, 1000.0f, DecimalCount = 2)]
public float SpringStiffnessHighSkill
{
get;
private set;
}
[Serialize(50.0f, false), Editable(0.0f, 1000.0f, DecimalCount = 2)]
public float SpringDampingLowSkill
{
get;
private set;
}
[Serialize(10.0f, false), Editable(0.0f, 1000.0f, DecimalCount = 2)]
public float SpringDampingHighSkill
{
get;
private set;
}
[Serialize(1.0f, false), Editable(0.0f, 100.0f, DecimalCount = 2)]
public float RotationSpeedLowSkill
{
get;
private set;
}
[Serialize(5.0f, false), Editable(0.0f, 100.0f, DecimalCount = 2)]
public float RotationSpeedHighSkill
{
get;
private set;
}
private float baseRotationRad;
[Serialize(0.0f, true), Editable(0.0f, 360.0f)]
public float BaseRotation
{
get { return MathHelper.ToDegrees(baseRotationRad); }
set
{
baseRotationRad = MathHelper.ToRadians(value);
UpdateTransformedBarrelPos();
}
}
@@ -76,51 +150,62 @@ namespace Barotrauma.Items.Components
: base(item, element)
{
IsActive = true;
string barrelSpritePath = element.GetAttributeString("barrelsprite", "");
if (!string.IsNullOrWhiteSpace(barrelSpritePath))
foreach (XElement subElement in element.Elements())
{
if (!barrelSpritePath.Contains("/"))
switch (subElement.Name.ToString().ToLowerInvariant())
{
barrelSpritePath = Path.Combine(Path.GetDirectoryName(item.Prefab.ConfigFile), barrelSpritePath);
case "barrelsprite":
barrelSprite = new Sprite(subElement);
break;
case "railsprite":
railSprite = new Sprite(subElement);
break;
}
barrelSprite = new Sprite(
barrelSpritePath,
element.GetAttributeVector2("origin", Vector2.Zero));
}
hasLight = null;
InitProjSpecific(element);
}
partial void InitProjSpecific(XElement element);
private void UpdateTransformedBarrelPos()
{
float flippedRotation = BaseRotation;
if (item.FlippedX) flippedRotation = -flippedRotation;
//if (item.FlippedY) flippedRotation = 180.0f - flippedRotation;
transformedBarrelPos = MathUtils.RotatePointAroundTarget(barrelPos * item.Scale, new Vector2(item.Rect.Width / 2, item.Rect.Height / 2), flippedRotation);
#if CLIENT
item.SpriteRotation = MathHelper.ToRadians(flippedRotation);
#endif
}
public override void OnItemLoaded()
{
var lightComponents = item.GetComponents<LightComponent>();
if (lightComponents != null && lightComponents.Count() > 0)
{
lightComponent = lightComponents.FirstOrDefault(lc => lc.Parent == this);
#if CLIENT
if (lightComponent != null)
{
lightComponent.Rotation = rotation;
lightComponent.Light.Rotation = -rotation;
}
#endif
}
}
public override void Update(float deltaTime, Camera cam)
{
if (hasLight == null)
{
List<LightComponent> lightComponents = item.GetComponents<LightComponent>();
if (lightComponents != null && lightComponents.Count>0)
{
lightComponent = lightComponents.Find(lc => lc.Parent == this);
hasLight = (lightComponent != null);
}
else
{
hasLight = false;
}
}
this.cam = cam;
if (reload > 0.0f) reload -= deltaTime;
ApplyStatusEffects(ActionType.OnActive, deltaTime, null);
UpdateProjSpecific(deltaTime);
if (minRotation == maxRotation) return;
float targetMidDiff = MathHelper.WrapAngle(targetRotation - (minRotation + maxRotation) / 2.0f);
@@ -132,47 +217,94 @@ namespace Barotrauma.Items.Components
targetRotation = (targetMidDiff < 0.0f) ? minRotation : maxRotation;
}
float deltaRotation = MathHelper.WrapAngle(targetRotation-rotation);
deltaRotation = MathHelper.Clamp(deltaRotation, -0.5f, 0.5f) * 5.0f;
float degreeOfSuccess = user == null ? 0.5f : DegreeOfSuccess(user);
if (degreeOfSuccess < 0.5f) degreeOfSuccess *= degreeOfSuccess; //the ease of aiming drops quickly with insufficient skill levels
float springStiffness = MathHelper.Lerp(SpringStiffnessLowSkill, SpringStiffnessHighSkill, degreeOfSuccess);
float springDamping = MathHelper.Lerp(SpringDampingLowSkill, SpringDampingHighSkill, degreeOfSuccess);
float rotationSpeed = MathHelper.Lerp(RotationSpeedLowSkill, RotationSpeedHighSkill, degreeOfSuccess);
rotation += deltaRotation * deltaTime;
angularVelocity +=
(MathHelper.WrapAngle(targetRotation - rotation) * springStiffness - angularVelocity * springDamping) * deltaTime;
angularVelocity = MathHelper.Clamp(angularVelocity, -rotationSpeed, rotationSpeed);
rotation += angularVelocity * deltaTime;
float rotMidDiff = MathHelper.WrapAngle(rotation - (minRotation + maxRotation) / 2.0f);
if (rotMidDiff < -maxDist)
{
rotation = minRotation;
angularVelocity *= -0.5f;
}
else if (rotMidDiff > maxDist)
{
rotation = maxRotation;
angularVelocity *= -0.5f;
}
if ((bool)hasLight)
if (lightComponent != null)
{
lightComponent.Rotation = rotation;
}
}
partial void UpdateProjSpecific(float deltaTime);
public override bool Use(float deltaTime, Character character = null)
{
if (!characterUsable && character != null) return false;
return TryLaunch(character);
return TryLaunch(deltaTime, character);
}
private bool TryLaunch(Character character = null)
private bool TryLaunch(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)
{
#if CLIENT
if (!flashLowPower && character != null && character == Character.Controlled)
{
flashLowPower = true;
GUI.PlayUISound(GUISoundType.PickItemFail);
}
#endif
return false;
}
foreach (MapEntity e in item.linkedTo)
{
//use linked projectile containers in case they have to react to the turret being launched somehow
//(play a sound, spawn more projectiles)
Item linkedItem = e as Item;
if (linkedItem == null) continue;
ItemContainer projectileContainer = linkedItem.GetComponent<ItemContainer>();
if (projectileContainer != null) linkedItem.Use(deltaTime, null);
}
if (GetAvailablePower() < powerConsumption) return false;
var projectiles = GetLoadedProjectiles(true);
if (projectiles.Count == 0)
{
//coilguns spawns ammo in the ammo boxes with the OnUse statuseffect when the turret is launched,
//causing a one frame delay before the gun can be launched (or more in multiplayer where there may be a longer delay)
// -> attempt to launch the gun multiple times before showing the "no ammo" flash
failedLaunchAttempts++;
#if CLIENT
if (!flashNoAmmo && character != null && character == Character.Controlled && failedLaunchAttempts > 20)
{
flashNoAmmo = true;
failedLaunchAttempts = 0;
GUI.PlayUISound(GUISoundType.PickItemFail);
}
#endif
return false;
}
failedLaunchAttempts = 0;
var batteries = item.GetConnectedComponents<PowerContainer>();
float availablePower = 0.0f;
foreach (PowerContainer battery in batteries)
{
@@ -215,14 +347,10 @@ namespace Barotrauma.Items.Components
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.SetTransform(ConvertUnits.ToSimUnits(new Vector2(item.WorldRect.X + transformedBarrelPos.X, item.WorldRect.Y - transformedBarrelPos.Y)), -rotation);
projectile.FindHull();
projectile.Submarine = projectile.body.Submarine;
LaunchProjSpecific();
ApplyStatusEffects(ActionType.OnUse, 1.0f, user);
Projectile projectileComponent = projectile.GetComponent<Projectile>();
if (projectileComponent != null)
{
@@ -236,34 +364,23 @@ namespace Barotrauma.Items.Components
{
GameMain.Server.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.ComponentState, item.components.IndexOf(this), projectile });
}
ApplyStatusEffects(ActionType.OnUse, 1.0f, user: user);
LaunchProjSpecific();
}
partial void LaunchProjSpecific();
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"))
if (character.AIController.SelectedAiTarget?.Entity is Character previousTarget &&
previousTarget.IsDead)
{
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;
character?.Speak(TextManager.Get("DialogTurretTargetDead"), null, 0.0f, "killedtarget" + previousTarget.ID, 30.0f);
character.AIController.SelectTarget(null);
}
else if (GetAvailablePower() < powerConsumption)
if (GetAvailablePower() < powerConsumption)
{
var batteries = item.GetConnectedComponents<PowerContainer>();
@@ -275,27 +392,65 @@ namespace Barotrauma.Items.Components
{
batteryToLoad = battery;
lowestCharge = battery.Charge;
}
}
}
if (batteryToLoad == null) return true;
if (batteryToLoad.RechargeSpeed < batteryToLoad.MaxRechargeSpeed * 0.4f)
{
objective.AddSubObjective(new AIObjectiveOperateItem(batteryToLoad, character, "", false));
objective.AddSubObjective(new AIObjectiveOperateItem(batteryToLoad, character, "", false));
return false;
}
}
int projectileCount = 0;
int maxProjectileCount = 0;
foreach (MapEntity e in item.linkedTo)
{
var projectileContainer = e as Item;
if (projectileContainer == null) continue;
var containedItems = projectileContainer.ContainedItems;
if (containedItems != null)
{
var container = projectileContainer.GetComponent<ItemContainer>();
if (containedItems != null) maxProjectileCount += container.Capacity;
projectileCount += containedItems.Length;
}
}
if (projectileCount == 0 || (projectileCount < maxProjectileCount && objective.Option.ToLowerInvariant() != "fireatwill"))
{
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].Identifiers[0], container);
character?.Speak(TextManager.Get("DialogLoadTurret").Replace("[itemname]", item.Name), null, 0.0f, "loadturret", 30.0f);
containShellObjective.MinContainedAmount = projectileCount + 1;
containShellObjective.IgnoreAlreadyContainedItems = true;
objective.AddSubObjective(containShellObjective);
return false;
}
//enough shells and power
Character closestEnemy = null;
float closestDist = 3000.0f;
float closestDist = 10000.0f * 10000.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);
float dist = Vector2.DistanceSquared(enemy.WorldPosition, item.WorldPosition);
if (dist < closestDist)
{
closestEnemy = enemy;
@@ -304,20 +459,26 @@ namespace Barotrauma.Items.Components
}
if (closestEnemy == null) return false;
character.AIController.SelectTarget(closestEnemy.AiTarget);
character.CursorPosition = closestEnemy.WorldPosition;
if (item.Submarine!=null) character.CursorPosition -= item.Submarine.Position;
if (item.Submarine != null) character.CursorPosition -= item.Submarine.Position;
character.SetInput(InputType.Aim, false, true);
float enemyAngle = MathUtils.VectorToAngle(closestEnemy.WorldPosition-item.WorldPosition);
float enemyAngle = MathUtils.VectorToAngle(closestEnemy.WorldPosition - item.WorldPosition);
float turretAngle = -rotation;
if (Math.Abs(MathUtils.GetShortestAngle(enemyAngle, turretAngle)) > 0.01f) return false;
if (Math.Abs(MathUtils.GetShortestAngle(enemyAngle, turretAngle)) > 0.1f) 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);
if (objective.Option.ToLowerInvariant() == "fireatwill")
{
character?.Speak(TextManager.Get("DialogFireTurret").Replace("[itemname]", item.Name), null, 0.0f, "fireturret", 5.0f);
character.SetInput(InputType.Use, true, true);
}
return false;
}
@@ -355,81 +516,132 @@ namespace Barotrauma.Items.Components
base.RemoveComponentSpecific();
if (barrelSprite != null) barrelSprite.Remove();
if (railSprite != null) railSprite.Remove();
#if CLIENT
moveSoundChannel?.Dispose(); moveSoundChannel = null;
#endif
}
private List<Projectile> GetLoadedProjectiles(bool returnFirst = false, bool returnNull = false)
private List<Projectile> GetLoadedProjectiles(bool returnFirst = false)
{
List<Projectile> projectiles = new List<Projectile>();
//check the item itself first
CheckProjectileContainer(item, projectiles, returnFirst);
foreach (MapEntity e in item.linkedTo)
{
var projectileContainer = e as Item;
if (projectileContainer == null) continue;
if (returnNull)
{
var itemContainer = projectileContainer.GetComponent<ItemContainer>();
if (itemContainer == null) continue;
if (itemContainer.Inventory == null) continue;
if (itemContainer.Inventory.Items == null) continue;
for (int i = 0; i < itemContainer.Inventory.Items.Length; i++)
{
projectiles.Add(itemContainer.Inventory.Items[i]?.GetComponent<Projectile>());
}
}
else
{
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;
}
}
}
if (e is Item projectileContainer) { CheckProjectileContainer(projectileContainer, projectiles, returnFirst); }
if (returnFirst && projectiles.Any()) return projectiles;
}
return projectiles;
}
public override void FlipX()
private void CheckProjectileContainer(Item projectileContainer, List<Projectile> projectiles, bool returnFirst)
{
minRotation = (float)Math.PI - minRotation;
maxRotation = (float)Math.PI - maxRotation;
var containedItems = projectileContainer.ContainedItems;
if (containedItems == null) return;
for (int i = 0; i < containedItems.Length; i++)
{
var projectileComponent = containedItems[i].GetComponent<Projectile>();
if (projectileComponent != null)
{
projectiles.Add(projectileComponent);
if (returnFirst) return;
}
else
{
//check if the contained item is another itemcontainer with projectiles inside it
if (containedItems[i].ContainedItems == null) continue;
for (int j = 0; j < containedItems[i].ContainedItems.Length; j++)
{
projectileComponent = containedItems[i].ContainedItems[j].GetComponent<Projectile>();
if (projectileComponent != null)
{
projectiles.Add(projectileComponent);
if (returnFirst) return;
}
}
}
}
}
public override void FlipX(bool relativeToSub)
{
minRotation = MathHelper.Pi - minRotation;
maxRotation = MathHelper.Pi - maxRotation;
var temp = minRotation;
minRotation = maxRotation;
maxRotation = temp;
barrelPos.X = item.Rect.Width / item.Scale - barrelPos.X;
while (minRotation < 0)
{
minRotation += MathHelper.TwoPi;
maxRotation += MathHelper.TwoPi;
}
rotation = (minRotation + maxRotation) / 2;
UpdateTransformedBarrelPos();
}
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power)
public override void FlipY(bool relativeToSub)
{
baseRotationRad = MathUtils.WrapAngleTwoPi(baseRotationRad - MathHelper.Pi);
UpdateTransformedBarrelPos();
/*minRotation = -minRotation;
maxRotation = -maxRotation;
var temp = minRotation;
minRotation = maxRotation;
maxRotation = temp;
barrelPos.Y = item.Rect.Height / item.Scale - barrelPos.Y;
while (minRotation < 0)
{
minRotation += MathHelper.TwoPi;
maxRotation += MathHelper.TwoPi;
}
rotation = (minRotation + maxRotation) / 2;
UpdateTransformedBarrelPos();*/
}
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power, float signalStrength = 1.0f)
{
switch (connection.Name)
{
case "position_in":
float.TryParse(signal, out targetRotation);
IsActive = true;
if (float.TryParse(signal, NumberStyles.Float, CultureInfo.InvariantCulture, out float newRotation))
{
targetRotation = MathHelper.ToRadians(newRotation);
IsActive = true;
}
user = sender;
break;
case "trigger_in":
item.Use((float)Timing.Step, sender);
user = sender;
//triggering the Use method through item.Use will fail if the item is not characterusable and the signal was sent by a character
//so lets do it manually
if (!characterUsable && sender != null)
{
TryLaunch(sender);
TryLaunch((float)Timing.Step, sender);
}
break;
case "toggle":
case "toggle_light":
foreach (ItemComponent component in item.components)
{
if (component.Parent == this && component is LightComponent lightComponent)
{
lightComponent.IsOn = !lightComponent.IsOn;
}
}
break;
}
@@ -4,34 +4,155 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Xml.Linq;
using Barotrauma.Items.Components;
namespace Barotrauma
{
public enum WearableType
{
Item,
Hair,
Beard,
Moustache,
FaceAttachment,
JobIndicator
}
class WearableSprite
{
public string SpritePath { get; private set; }
public XElement SourceElement { get; private set; }
public WearableType Type { get; private set; }
private Sprite _sprite;
public Sprite Sprite
{
get { return _sprite; }
set
{
if (value == _sprite) { return; }
if (_sprite != null)
{
_sprite.Remove();
}
_sprite = value;
}
}
public LimbType Limb { get; private set; }
public bool HideLimb { get; private set; }
public bool HideOtherWearables { get; private set; }
public bool InheritLimbDepth { get; private set; }
public bool InheritTextureScale { get; private set; }
public bool InheritOrigin { get; private set; }
public bool InheritSourceRect { get; private set; }
public LimbType DepthLimb { get; private set; }
private Wearable _wearableComponent;
public Wearable WearableComponent
{
get { return _wearableComponent; }
set
{
if (value == _wearableComponent) { return; }
if (_wearableComponent != null)
{
_wearableComponent.Remove();
}
_wearableComponent = value;
}
}
public string Sound { get; private set; }
public Point? SheetIndex { get; private set; }
public LightComponent LightComponent { get; set; }
private Gender _gender;
/// <summary>
/// None = Any/Not Defined -> no effect.
/// Changing the gender forces re-initialization, because the textures can be different for male and female characters.
/// </summary>
public Gender Gender
{
get { return _gender; }
set
{
if (value == _gender) { return; }
_gender = value;
IsInitialized = false;
Init(_gender);
}
}
public WearableSprite(XElement subElement, WearableType type)
{
Type = type;
SourceElement = subElement;
SpritePath = subElement.Attribute("texture").Value;
Init();
switch (type)
{
case WearableType.Hair:
case WearableType.Beard:
case WearableType.Moustache:
case WearableType.FaceAttachment:
case WearableType.JobIndicator:
Limb = LimbType.Head;
HideLimb = false;
HideOtherWearables = false;
InheritLimbDepth = true;
InheritTextureScale = true;
InheritOrigin = true;
InheritSourceRect = true;
break;
}
}
/// <summary>
/// Note: this constructor cannot initialize automatically, because the gender is unknown at this point. We only know it when the item is equipped.
/// </summary>
public WearableSprite(XElement subElement, Wearable item)
{
Type = WearableType.Item;
WearableComponent = item;
string texturePath = subElement.GetAttributeString("texture", string.Empty);
SpritePath = texturePath.Contains("/") ? texturePath : $"{Path.GetDirectoryName(item.Item.Prefab.ConfigFile)}/{texturePath}";
SourceElement = subElement;
}
public bool IsInitialized { get; private set; }
public void Init(Gender gender = Gender.None)
{
if (IsInitialized) { return; }
_gender = SpritePath.Contains("[GENDER]") ? gender : Gender.None;
if (_gender != Gender.None)
{
SpritePath = SpritePath.Replace("[GENDER]", (_gender == Gender.Female) ? "female" : "male");
}
if (Sprite != null)
{
Sprite.Remove();
}
Sprite = new Sprite(SourceElement, "", SpritePath);
Limb = (LimbType)Enum.Parse(typeof(LimbType), SourceElement.GetAttributeString("limb", "Head"), true);
HideLimb = SourceElement.GetAttributeBool("hidelimb", false);
HideOtherWearables = SourceElement.GetAttributeBool("hideotherwearables", false);
InheritLimbDepth = SourceElement.GetAttributeBool("inheritlimbdepth", true);
InheritTextureScale = SourceElement.GetAttributeBool("inherittexturescale", false);
InheritOrigin = SourceElement.GetAttributeBool("inheritorigin", false);
InheritSourceRect = SourceElement.GetAttributeBool("inheritsourcerect", false);
DepthLimb = (LimbType)Enum.Parse(typeof(LimbType), SourceElement.GetAttributeString("depthlimb", "None"), true);
Sound = SourceElement.GetAttributeString("sound", "");
var index = SourceElement.GetAttributePoint("sheetindex", new Point(-1, -1));
if (index.X > -1 && index.Y > -1)
{
SheetIndex = index;
}
IsInitialized = true;
}
}
}
namespace Barotrauma.Items.Components
{
class WearableSprite
{
public readonly Sprite Sprite;
public readonly LimbType Limb;
public readonly bool HideLimb;
public readonly bool InheritLimbDepth;
public readonly LimbType DepthLimb;
public LightComponent LightComponent;
public readonly Wearable WearableComponent;
public readonly string Sound;
public WearableSprite(Wearable item, Sprite sprite, LimbType limb, bool hideLimb, bool inheritLimbDepth = true, LimbType depthLimb = LimbType.None, string sound = null)
{
WearableComponent = item;
Sprite = sprite;
Limb = limb;
HideLimb = hideLimb;
InheritLimbDepth = inheritLimbDepth;
DepthLimb = depthLimb;
Sound = sound;
}
}
class Wearable : Pickable
{
private WearableSprite[] wearableSprites;
@@ -45,8 +166,7 @@ namespace Barotrauma.Items.Components
get { return damageModifiers; }
}
public Wearable (Item item, XElement element)
: base(item, element)
public Wearable (Item item, XElement element) : base(item, element)
{
this.item = item;
@@ -69,18 +189,10 @@ namespace Barotrauma.Items.Components
return;
}
string spritePath = subElement.Attribute("texture").Value;
spritePath = Path.GetDirectoryName(item.Prefab.ConfigFile) + "/" + spritePath;
var sound = subElement.GetAttributeString("sound", "");
var sprite = new Sprite(subElement, "", spritePath);
limbType[i] = (LimbType)Enum.Parse(typeof(LimbType),
subElement.GetAttributeString("limb", "Head"), true);
wearableSprites[i] = new WearableSprite(this, sprite, limbType[i],
subElement.GetAttributeBool("hidelimb", false),
subElement.GetAttributeBool("inheritlimbdepth", true),
(LimbType)Enum.Parse(typeof(LimbType), subElement.GetAttributeString("depthlimb", "None"), true), sound);
wearableSprites[i] = new WearableSprite(subElement, this);
foreach (XElement lightElement in subElement.Elements())
{
@@ -93,7 +205,7 @@ namespace Barotrauma.Items.Components
i++;
break;
case "damagemodifier":
damageModifiers.Add(new DamageModifier(subElement));
damageModifiers.Add(new DamageModifier(subElement, item.Name + ", Wearable"));
break;
}
}
@@ -104,20 +216,28 @@ namespace Barotrauma.Items.Components
picker = character;
for (int i = 0; i < wearableSprites.Length; i++ )
{
var wearableSprite = wearableSprites[i];
if (!wearableSprite.IsInitialized) { wearableSprite.Init(picker.Info?.Gender ?? Gender.None); }
if (picker.Info?.Gender != Gender.None && (wearableSprite.Gender != Gender.None))
{
// If the item is gender specific (it has a different textures for male and female), we have to change the gender here so that the texture is updated.
wearableSprite.Gender = picker.Info.Gender;
}
Limb equipLimb = character.AnimController.GetLimb(limbType[i]);
if (equipLimb == null) continue;
item.body.Enabled = false;
IsActive = true;
if (wearableSprites[i].LightComponent != null)
if (wearableSprite.LightComponent != null)
{
wearableSprites[i].LightComponent.ParentBody = equipLimb.body;
wearableSprite.LightComponent.ParentBody = equipLimb.body;
}
limb[i] = equipLimb;
if (!equipLimb.WearingItems.Contains(wearableSprites[i]))
if (!equipLimb.WearingItems.Contains(wearableSprite))
{
equipLimb.WearingItems.Add(wearableSprites[i]);
equipLimb.WearingItems.Add(wearableSprite);
}
}
}
@@ -160,13 +280,19 @@ namespace Barotrauma.Items.Components
public override void Update(float deltaTime, Camera cam)
{
if (picker.Removed)
{
IsActive = false;
return;
}
item.SetTransform(picker.SimPosition, 0.0f);
item.SetContainedItemPositions();
item.ApplyStatusEffects(ActionType.OnWearing, deltaTime, picker);
#if CLIENT
PlaySound(ActionType.OnWearing, picker.WorldPosition);
PlaySound(ActionType.OnWearing, picker.WorldPosition, picker);
#endif
}