38f1ddb...178a853: v0.8.9.1, removed content folder
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user