v0.11.0.9

This commit is contained in:
Joonas Rikkonen
2020-12-09 16:34:16 +02:00
parent bbf06f0984
commit f433a7ba10
325 changed files with 13947 additions and 3652 deletions
@@ -89,6 +89,16 @@ namespace Barotrauma.Items.Components
}
}
/// <summary>
/// Automatically cleared after docking -> no need to unregister
/// </summary>
public event Action OnDocked;
/// <summary>
/// Automatically cleared after undocking -> no need to unregister
/// </summary>
public event Action OnUnDocked;
public DockingPort(Item item, XElement element)
: base(item, element)
{
@@ -213,6 +223,9 @@ namespace Barotrauma.Items.Components
item.CreateServerEvent(this);
}
#endif
OnDocked?.Invoke();
OnDocked = null;
}
@@ -817,6 +830,9 @@ namespace Barotrauma.Items.Components
docked = false;
Item.Submarine.EnableObstructedWaypoints(DockingTarget.Item.Submarine);
obstructedWayPointsDisabled = false;
DockingTarget.Undock();
DockingTarget = null;
@@ -860,9 +876,6 @@ namespace Barotrauma.Items.Components
outsideBlocker?.Body.Remove(outsideBlocker);
outsideBlocker = null;
Item.Submarine.EnableObstructedWaypoints();
obstructedWayPointsDisabled = false;
#if SERVER
if (GameMain.Server != null && (!item.Submarine?.Loading ?? true))
{
@@ -870,6 +883,8 @@ namespace Barotrauma.Items.Components
item.CreateServerEvent(this);
}
#endif
OnUnDocked?.Invoke();
OnUnDocked = null;
}
public override void Update(float deltaTime, Camera cam)
@@ -1034,7 +1049,6 @@ namespace Barotrauma.Items.Components
Dock(dockingPort);
}
}
}
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power = 0.0f, float signalStrength = 1.0f)
@@ -23,6 +23,20 @@ namespace Barotrauma.Items.Components
private readonly Sprite doorSprite, weldedSprite, brokenSprite;
private readonly bool scaleBrokenSprite, fadeBrokenSprite;
private readonly bool autoOrientGap;
private bool isJammed;
public bool IsJammed
{
get { return isJammed; }
set
{
if (isJammed == value) { return; }
isJammed = value;
#if SERVER
item.CreateServerEvent(this);
#endif
}
}
private bool isStuck;
public bool IsStuck
@@ -297,7 +311,7 @@ namespace Barotrauma.Items.Components
{
if (toggleCooldownTimer > 0.0f && user != lastUser) { OnFailedToOpen(); return; }
toggleCooldownTimer = ToggleCoolDown;
if (IsStuck) { toggleCooldownTimer = 1.0f; OnFailedToOpen(); return; }
if (IsStuck || IsJammed) { toggleCooldownTimer = 1.0f; OnFailedToOpen(); return; }
lastUser = user;
SetState(PredictedState == null ? !isOpen : !PredictedState.Value, false, true, forcedOpen: actionType == ActionType.OnPicked);
}
@@ -341,7 +355,7 @@ namespace Barotrauma.Items.Components
}
bool isClosing = false;
if (!IsStuck)
if ((!IsStuck && !IsJammed) || !isOpen)
{
if (PredictedState == null)
{
@@ -630,7 +644,7 @@ namespace Barotrauma.Items.Components
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; }
if (IsStuck || IsJammed) { return; }
bool wasOpen = PredictedState == null ? isOpen : PredictedState.Value;
@@ -62,7 +62,7 @@ namespace Barotrauma.Items.Components
set;
}
[Serialize(0.25f, true, description: "The duration of an individual discharge (in seconds)."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 1.0f)]
[Serialize(0.25f, true, description: "The duration of an individual discharge (in seconds)."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 60.0f, ValueStep = 0.1f, DecimalCount = 2)]
public float Duration
{
get;
@@ -193,7 +193,7 @@ namespace Barotrauma.Items.Components
partial void DischargeProjSpecific();
private void FindNodes(Vector2 worldPosition, float range)
public 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>();
@@ -2,7 +2,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Xml.Linq;
using Barotrauma.Extensions;
using Barotrauma.Networking;
@@ -10,6 +9,7 @@ using FarseerPhysics;
using FarseerPhysics.Dynamics;
using Microsoft.Xna.Framework;
using Vector2 = Microsoft.Xna.Framework.Vector2;
using Vector4 = Microsoft.Xna.Framework.Vector4;
namespace Barotrauma.Items.Components
{
@@ -138,19 +138,25 @@ namespace Barotrauma.Items.Components
public TileSide Sides = TileSide.None;
public TileSide BlockedSides = TileSide.None;
public readonly FoliageConfig FlowerConfig;
public readonly FoliageConfig LeafConfig;
public FoliageConfig FlowerConfig;
public FoliageConfig LeafConfig;
public int FailedGrowthAttempts;
public Rectangle Rect;
public Vector2 Position;
public Color HealthColor = Color.Transparent;
public float DecayDelay;
private float VineStep;
private float FlowerStep;
private readonly float diameter;
public Vector2 offset;
public VineTileType Type;
public readonly Dictionary<TileSide, Vector2> AdjacentPositions;
public static int Size = 32;
public float VineStep;
public float FlowerStep;
private float growthStep;
public float GrowthStep
{
get => growthStep;
@@ -166,17 +172,12 @@ namespace Barotrauma.Items.Components
}
}
private readonly float diameter;
private Vector2 offset;
public Color HealthColor = Color.Transparent;
public float DecayDelay;
private readonly Growable Parent;
public VineTileType Type;
private readonly Growable? Parent;
public readonly Dictionary<TileSide, Vector2> AdjacentPositions;
public static int Size = 32;
public VineTile(Growable parent, Vector2 position, VineTileType type, FoliageConfig? flowerConfig = null, FoliageConfig? leafConfig = null, Rectangle? rect = null)
public VineTile(Growable? parent, Vector2 position, VineTileType type, FoliageConfig? flowerConfig = null, FoliageConfig? leafConfig = null, Rectangle? rect = null)
{
FlowerConfig = flowerConfig ?? FoliageConfig.EmptyConfig;
LeafConfig = leafConfig ?? FoliageConfig.EmptyConfig;
@@ -197,7 +198,9 @@ namespace Barotrauma.Items.Components
public void UpdateScale(float deltaTime)
{
if (Parent.Decayed && GrowthStep > 1.0f)
bool decayed = Parent?.Decayed ?? false;
if (decayed && GrowthStep > 1.0f)
{
if (DecayDelay > 0)
{
@@ -209,7 +212,7 @@ namespace Barotrauma.Items.Components
}
}
if (GrowthStep >= 2.0f || Parent.Decayed) { return; }
if (GrowthStep >= 2.0f || decayed) { return; }
GrowthStep += deltaTime;
@@ -282,13 +285,26 @@ namespace Barotrauma.Items.Components
}
}
int value = pool[Growable.RandomInt(0, possible, random)];
int value;
if (Parent == null)
{
value = pool[Growable.RandomInt(0, possible, random)];
}
else
{
var (x, y, z, w) = Parent.GrowthWeights;
float[] weights = { x, y, z, w };
value = pool.RandomElementByWeight(i => weights[i]);
}
return (TileSide) (1 << value);
}
public bool CanGrowMore() => (Sides | BlockedSides).Count() < 4;
public bool IsSideBlocked(TileSide side) => BlockedSides.IsBitSet(side) || Sides.IsBitSet(side);
public static Rectangle CreatePlantRect(Vector2 pos) => new Rectangle((int) pos.X - Size / 2, (int) pos.Y + Size / 2, Size, Size);
}
@@ -313,6 +329,11 @@ namespace Barotrauma.Items.Components
return count;
}
public static TileSide GetOppositeSide(this TileSide side)
{
return (TileSide) (1 << ((int) Math.Log2((int) side) + 2) % 4);
}
}
internal partial class Growable : ItemComponent, IServerSerializable
@@ -371,6 +392,9 @@ namespace Barotrauma.Items.Components
[Serialize("0.26,0.27,0.29,1.0", true, "Tint of a dead plant.")]
public Color DeadTint { get; set; }
[Serialize("1,1,1,1", true, "Probability for the plant to grow in a direction.")]
public Vector4 GrowthWeights { get; set; }
private const float increasedDeathSpeed = 10f;
private bool accelerateDeath;
private float health;
@@ -666,7 +690,23 @@ namespace Barotrauma.Items.Components
TileSide side = oldVines.GetRandomFreeSide(random);
if (side == TileSide.None) { continue; }
if (side == TileSide.None)
{
oldVines.FailedGrowthAttempts++;
continue;
}
if (GrowthWeights != Vector4.One)
{
var (x, y, z, w) = GrowthWeights;
float[] weights = { x, y, z, w };
int index = (int) Math.Log2((int) side);
if (MathUtils.NearlyEqual(weights[index], 0f))
{
oldVines.FailedGrowthAttempts++;
continue;
}
}
Vector2 pos = oldVines.AdjacentPositions[side];
Rectangle rect = VineTile.CreatePlantRect(pos);
@@ -705,8 +745,7 @@ namespace Barotrauma.Items.Components
// if the X value is bigger than Y it's to the left or right of us and then check if X is negative or positive to determine if it's right or left
TileSide connectingSide = absDistX > absDistY ? distX > 0 ? TileSide.Right : TileSide.Left : distY > 0 ? TileSide.Top : TileSide.Bottom;
// We use log2 to find the index and offset that index by 2 since the opposite side is always 2 offsets away
TileSide oppositeSide = (TileSide) (1 << ((int) Math.Log2((int) connectingSide) + 2) % 4);
TileSide oppositeSide = connectingSide.GetOppositeSide();
if (otherVine.BlockedSides.IsBitSet(connectingSide))
{
@@ -810,9 +849,9 @@ namespace Barotrauma.Items.Components
return element;
}
public override void Load(XElement componentElement, bool usePrefabValues)
public override void Load(XElement componentElement, bool usePrefabValues, IdRemap idRemap)
{
base.Load(componentElement, usePrefabValues);
base.Load(componentElement, usePrefabValues, idRemap);
flowerTiles = componentElement.GetAttributeIntArray("flowertiles", new int[0]);
Decayed = componentElement.GetAttributeBool("decayed", false);
@@ -30,6 +30,7 @@ namespace Barotrauma.Items.Components
private float swingState;
private bool attachable, attached, attachedByDefault;
private Voronoi2.VoronoiCell attachTargetCell;
private readonly PhysicsBody body;
public PhysicsBody Pusher
{
@@ -213,9 +214,9 @@ namespace Barotrauma.Items.Components
}
}
public override void Load(XElement componentElement, bool usePrefabValues)
public override void Load(XElement componentElement, bool usePrefabValues, IdRemap idRemap)
{
base.Load(componentElement, usePrefabValues);
base.Load(componentElement, usePrefabValues, idRemap);
if (usePrefabValues)
{
@@ -255,6 +256,7 @@ namespace Barotrauma.Items.Components
if (Pusher != null) { Pusher.Enabled = false; }
if (item.body != null) { item.body.Enabled = true; }
IsActive = false;
attachTargetCell = null;
if (picker == null || picker.Removed)
{
@@ -359,7 +361,7 @@ namespace Barotrauma.Items.Components
public override void Unequip(Character character)
{
if (picker == null) return;
if (picker == null) { return; }
picker.DeselectItem(item);
#if SERVER
@@ -383,9 +385,9 @@ namespace Barotrauma.Items.Components
//can be attached anywhere inside hulls
if (item.CurrentHull != null && Submarine.RectContains(item.CurrentHull.WorldRect, attachPos)) { return true; }
return Structure.GetAttachTarget(attachPos) != null;
return Structure.GetAttachTarget(attachPos) != null || GetAttachTargetCell(100.0f) != null;
}
public bool CanBeDeattached()
{
if (!attachable || !attached) { return true; }
@@ -406,7 +408,7 @@ namespace Barotrauma.Items.Components
if (item.CurrentHull == null)
{
return Structure.GetAttachTarget(item.WorldPosition) != null;
return attachTargetCell != null && Structure.GetAttachTarget(item.WorldPosition) != null;
}
else
{
@@ -464,7 +466,7 @@ namespace Barotrauma.Items.Components
public void AttachToWall()
{
if (!attachable) return;
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)
@@ -479,6 +481,11 @@ namespace Barotrauma.Items.Components
}
item.Submarine = attachTarget.Submarine;
}
else
{
attachTargetCell = GetAttachTargetCell(150.0f);
if (attachTargetCell != null) { IsActive = true; }
}
}
var containedItems = item.OwnInventory?.Items;
@@ -507,6 +514,7 @@ namespace Barotrauma.Items.Components
if (!attachable) return;
Attached = false;
attachTargetCell = null;
//make the item pickable with the default pick key and with no specific tools/items when it's deattached
requiredItems.Clear();
@@ -568,9 +576,47 @@ namespace Barotrauma.Items.Components
Vector2 userPos = useWorldCoordinates ? user.WorldPosition : user.Position;
return new Vector2(
MathUtils.RoundTowardsClosest(userPos.X + mouseDiff.X, Submarine.GridSize.X),
MathUtils.RoundTowardsClosest(userPos.Y + mouseDiff.Y, Submarine.GridSize.Y));
Vector2 attachPos = userPos + mouseDiff;
if (user.Submarine == null)
{
bool edgeFound = false;
foreach (var cell in Level.Loaded.GetCells(attachPos))
{
if (cell.CellType != Voronoi2.CellType.Solid) { continue; }
foreach (var edge in cell.Edges)
{
if (!edge.IsSolid) { continue; }
if (MathUtils.GetLineIntersection(edge.Point1, edge.Point2, user.WorldPosition, attachPos, out Vector2 intersection))
{
attachPos = intersection;
edgeFound = true;
break;
}
}
if (edgeFound) { break; }
}
}
return
new Vector2(
MathUtils.RoundTowardsClosest(attachPos.X, Submarine.GridSize.X),
MathUtils.RoundTowardsClosest(attachPos.Y, Submarine.GridSize.Y));
}
private Voronoi2.VoronoiCell GetAttachTargetCell(float maxDist)
{
foreach (var cell in Level.Loaded.GetCells(item.WorldPosition, searchDepth: 1))
{
if (cell.CellType != Voronoi2.CellType.Solid) { continue; }
Vector2 diff = cell.Center - item.WorldPosition;
if (diff.LengthSquared() > 0.0001f) { diff = Vector2.Normalize(diff); }
if (cell.IsPointInside(item.WorldPosition + diff * maxDist))
{
return cell;
}
}
return null;
}
public override void UpdateBroken(float deltaTime, Camera cam)
@@ -580,14 +626,28 @@ namespace Barotrauma.Items.Components
public override void Update(float deltaTime, Camera cam)
{
if (attachTargetCell != null)
{
if (attachTargetCell.CellType != Voronoi2.CellType.Solid)
{
Drop(dropConnectedWires: true, dropper: null);
}
return;
}
if (item.body == null || !item.body.Enabled) { return; }
if (picker == null || !picker.HasEquippedItem(item))
{
if (Pusher != null) { Pusher.Enabled = false; }
IsActive = false;
if (attachTargetCell == null) { IsActive = false; }
return;
}
if (picker == Character.Controlled && picker.IsKeyDown(InputType.Aim) && CanBeAttached(picker))
{
Drawable = true;
}
Vector2 swing = Vector2.Zero;
if (swingAmount != Vector2.Zero && !picker.IsUnconscious && picker.Stun <= 0.0f)
{
@@ -0,0 +1,54 @@
using Microsoft.Xna.Framework;
using System.Collections.Generic;
using System.Xml.Linq;
namespace Barotrauma.Items.Components
{
partial class IdCard : Pickable
{
public IdCard(Item item, XElement element) : base(item, element)
{
}
public void Initialize(CharacterInfo info)
{
if (info == null) return;
if (info.Job?.Prefab != null)
{
item.AddTag("jobid:" + info.Job.Prefab.Identifier);
}
var head = info.Head;
if (info != null && head != null)
{
item.AddTag("gender:" + head.gender.ToString().ToLowerInvariant());
item.AddTag("race:" + head.race.ToString());
item.AddTag("headspriteid:" + info.HeadSpriteId.ToString());
item.AddTag("hairindex:" + head.HairIndex);
item.AddTag("beardindex:" + head.BeardIndex);
item.AddTag("moustacheindex:" + head.MoustacheIndex);
item.AddTag("faceattachmentindex:" + head.FaceAttachmentIndex);
if (head.SheetIndex != null)
{
item.AddTag("sheetindex:" + head.SheetIndex.Value.X + ";" + head.SheetIndex.Value.Y);
}
}
}
public override void Equip(Character character)
{
base.Equip(character);
character.Info.CheckDisguiseStatus(true, this);
}
public override void Unequip(Character character)
{
base.Unequip(character);
character.Info.CheckDisguiseStatus(true, this);
}
}
}
@@ -33,6 +33,9 @@ namespace Barotrauma.Items.Components
{
return;
}
if (holdable == null) { return; }
deattachTimer = Math.Max(0.0f, value);
#if SERVER
if (deattachTimer >= DeattachDuration)
@@ -57,7 +60,7 @@ namespace Barotrauma.Items.Components
public bool Attached
{
get { return holdable == null ? false : holdable.Attached; }
get { return holdable != null && holdable.Attached; }
}
public LevelResource(Item item, XElement element) : base(item, element)
@@ -67,14 +70,14 @@ namespace Barotrauma.Items.Components
public override void Update(float deltaTime, Camera cam)
{
if (!holdable.Attached)
if (holdable != null && !holdable.Attached)
{
trigger.Enabled = false;
IsActive = false;
}
else
{
if (Vector2.DistanceSquared(item.SimPosition, trigger.SimPosition) > 0.01f)
if (trigger != null && Vector2.DistanceSquared(item.SimPosition, trigger.SimPosition) > 0.01f)
{
trigger.SetTransform(item.SimPosition, 0.0f);
}
@@ -87,7 +90,6 @@ namespace Barotrauma.Items.Components
holdable = item.GetComponent<Holdable>();
if (holdable == null)
{
DebugConsole.ThrowError("Error while initializing item \"" + item.Name + "\". Level resources require a Holdable component.");
IsActive = false;
return;
}
@@ -143,13 +143,15 @@ namespace Barotrauma.Items.Components
public override void Update(float deltaTime, Camera cam)
{
if (!item.body.Enabled) { impactQueue.Clear(); return; }
if (!picker.HasSelectedItem(item)) { impactQueue.Clear(); IsActive = false; }
if (picker == null && !picker.HasSelectedItem(item)) { impactQueue.Clear(); IsActive = false; }
while (impactQueue.Count > 0)
{
var impact = impactQueue.Dequeue();
HandleImpact(impact.Body);
}
//in case handling the impact does something to the picker
if (picker == null) { return; }
reloadTimer -= deltaTime;
if (reloadTimer < 0) { reloadTimer = 0; }
@@ -5,6 +5,7 @@ using FarseerPhysics.Dynamics;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Items.Components
@@ -128,54 +129,19 @@ namespace Barotrauma.Items.Components
for (int i = 0; i < ProjectileCount; i++)
{
Projectile projectile = FindProjectile(triggerOnUseOnContainers: true);
if (projectile == null) { return true; }
float spread = GetSpread(character);
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 + item.body.SimPosition;
//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, Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionItemBlocking) == null)
if (projectile != 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.Item.GetComponent<Rope>()?.Attach(item, projectile.Item);
if (projectile.Item.Removed) { continue; }
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 + (projectile.Item.body.Dir * projectile.LaunchRotationRadians));
item.RemoveContained(projectile.Item);
if (i == 0)
{
//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,
maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
Vector2 barrelPos = TransformedBarrelPos + item.body.SimPosition;
float rotation = (Item.body.Dir == 1.0f) ? Item.body.Rotation : Item.body.Rotation - MathHelper.Pi;
float spread = GetSpread(character) * Rand.Range(-0.5f, 0.5f);
projectile.Shoot(character, character.AnimController.AimSourceSimPos, barrelPos, rotation + spread, ignoredBodies: limbBodies.ToList(), createNetworkEvent: false);
projectile.Item.GetComponent<Rope>()?.Attach(Item, projectile.Item);
if (i == 0)
{
Item.body.ApplyLinearImpulse(new Vector2((float)Math.Cos(projectile.Item.body.Rotation), (float)Math.Sin(projectile.Item.body.Rotation)) * Item.body.Mass * -50.0f, maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
}
projectile.Item.body.ApplyTorque(projectile.Item.body.Mass * degreeOfFailure * Rand.Range(-10.0f, 10.0f));
Item.RemoveContained(projectile.Item);
}
}
@@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using Barotrauma.Extensions;
using Barotrauma.MapCreatures.Behavior;
namespace Barotrauma.Items.Components
{
@@ -53,6 +54,18 @@ namespace Barotrauma.Items.Components
get; set;
}
[Serialize(0.0f, false, description: "How much damage is applied to ballast flora.")]
public float FireDamage
{
get; set;
}
[Serialize(0.0f, false, description: "How many units of damage the item removes from destructible level walls per second.")]
public float LevelWallFixAmount
{
get; set;
}
[Serialize(0.0f, false, description: "How much the item decreases the size of fires per second.")]
public float ExtinguishAmount
{
@@ -183,23 +196,40 @@ namespace Barotrauma.Items.Components
}
Vector2 rayStart;
Vector2 rayStartWorld;
Vector2 sourcePos = character?.AnimController == null ? item.SimPosition : character.AnimController.AimSourceSimPos;
Vector2 barrelPos = item.SimPosition + ConvertUnits.ToSimUnits(TransformedBarrelPos);
//make sure there's no obstacles between the base of the item (or the shoulder of the character) and the end of the barrel
if (Submarine.PickBody(sourcePos, barrelPos, collisionCategory: Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionItemBlocking) == null)
{
//no obstacles -> we start the raycast at the end of the barrel
rayStart = ConvertUnits.ToSimUnits(item.WorldPosition + TransformedBarrelPos);
rayStart = ConvertUnits.ToSimUnits(item.Position + TransformedBarrelPos);
rayStartWorld = ConvertUnits.ToSimUnits(item.WorldPosition + TransformedBarrelPos);
}
else
{
rayStart = Submarine.LastPickedPosition + Submarine.LastPickedNormal * 0.1f;
if (item.Submarine != null) { rayStart += item.Submarine.SimPosition; }
rayStart = rayStartWorld = Submarine.LastPickedPosition + Submarine.LastPickedNormal * 0.1f;
if (item.Submarine != null) { rayStartWorld += item.Submarine.SimPosition; }
}
//if the calculated barrel pos is in another hull, use the origin of the item to make sure the particles don't end up in an incorrect hull
if (item.CurrentHull != null)
{
var barrelHull = Hull.FindHull(ConvertUnits.ToDisplayUnits(rayStartWorld), item.CurrentHull, useWorldCoordinates: true);
if (barrelHull != null && barrelHull != item.CurrentHull)
{
if (MathUtils.GetLineRectangleIntersection(ConvertUnits.ToDisplayUnits(sourcePos), ConvertUnits.ToDisplayUnits(rayStart), item.CurrentHull.Rect, out Vector2 hullIntersection))
{
Vector2 rayDir = rayStart.NearlyEquals(sourcePos) ? Vector2.Zero : Vector2.Normalize(rayStart - sourcePos);
rayStartWorld = ConvertUnits.ToSimUnits(hullIntersection - rayDir * 5.0f);
if (item.Submarine != null) { rayStartWorld += item.Submarine.SimPosition; }
}
}
}
float spread = MathHelper.ToRadians(MathHelper.Lerp(UnskilledSpread, Spread, degreeOfSuccess));
float angle = item.body.Rotation + MathHelper.ToRadians(BarrelRotation) + spread * Rand.Range(-0.5f, 0.5f);
Vector2 rayEnd = rayStart +
Vector2 rayEnd = rayStartWorld +
ConvertUnits.ToSimUnits(new Vector2(
(float)Math.Cos(angle),
(float)Math.Sin(angle)) * Range * item.body.Dir);
@@ -218,7 +248,7 @@ namespace Barotrauma.Items.Components
IsActive = true;
activeTimer = 0.1f;
debugRayStartPos = ConvertUnits.ToDisplayUnits(rayStart);
debugRayStartPos = ConvertUnits.ToDisplayUnits(rayStartWorld);
debugRayEndPos = ConvertUnits.ToDisplayUnits(rayEnd);
Submarine parentSub = character?.Submarine ?? item.Submarine;
@@ -232,16 +262,16 @@ namespace Barotrauma.Items.Components
{
continue;
}
Repair(rayStart - sub.SimPosition, rayEnd - sub.SimPosition, deltaTime, character, degreeOfSuccess, ignoredBodies);
Repair(rayStartWorld - sub.SimPosition, rayEnd - sub.SimPosition, deltaTime, character, degreeOfSuccess, ignoredBodies);
}
Repair(rayStart, rayEnd, deltaTime, character, degreeOfSuccess, ignoredBodies);
Repair(rayStartWorld, rayEnd, deltaTime, character, degreeOfSuccess, ignoredBodies);
}
else
{
Repair(rayStart - parentSub.SimPosition, rayEnd - parentSub.SimPosition, deltaTime, character, degreeOfSuccess, ignoredBodies);
Repair(rayStartWorld - parentSub.SimPosition, rayEnd - parentSub.SimPosition, deltaTime, character, degreeOfSuccess, ignoredBodies);
}
UseProjSpecific(deltaTime, rayStart);
UseProjSpecific(deltaTime, rayStartWorld);
return true;
}
@@ -289,6 +319,7 @@ namespace Barotrauma.Items.Components
{
if (RepairThroughHoles && f.IsSensor && f.Body?.UserData is Structure || (f.Body?.UserData is Item it && it.GetComponent<Planter>() != null)) { return false; }
if (f.Body?.UserData as string == "ruinroom") { return false; }
if (f.Body?.UserData is VineTile && !(FireDamage > 0)) { return false; }
return true;
},
allowInsideFixture: true);
@@ -324,9 +355,16 @@ namespace Barotrauma.Items.Components
hitCharacters.Add(hitCharacter);
}
//if repairing through walls is not allowed and the next wall is more than 100 pixels away from the previous one, stop here
//(= repairing multiple overlapping walls is allowed as long as the edges of the walls are less than 100 pixels apart)
float thisBodyFraction = Submarine.LastPickedBodyDist(body);
if (!RepairThroughWalls && lastHitType == typeof(Structure) && Range * (thisBodyFraction - lastPickedFraction) > 100.0f)
{
break;
}
if (FixBody(user, deltaTime, degreeOfSuccess, body))
{
lastPickedFraction = Submarine.LastPickedBodyDist(body);
lastPickedFraction = thisBodyFraction;
if (bodyType != null) { lastHitType = bodyType; }
}
}
@@ -341,6 +379,8 @@ namespace Barotrauma.Items.Components
{
if (RepairThroughHoles && f.IsSensor && f.Body?.UserData is Structure) { return false; }
if (f.Body?.UserData as string == "ruinroom") { return false; }
if (f.Body?.UserData is VineTile && !(FireDamage > 0)) { return false; }
if (f.Body?.UserData is Item targetItem)
{
if (!HitItems) { return false; }
@@ -479,6 +519,15 @@ namespace Barotrauma.Items.Components
}
return true;
}
else if (targetBody.UserData is Voronoi2.VoronoiCell cell && cell.IsDestructible)
{
var levelWall = Level.Loaded?.ExtraWalls.Find(w => w.Body == cell.Body) as DestructibleLevelWall;
if (levelWall != null)
{
levelWall.AddDamage(-LevelWallFixAmount * deltaTime, item.WorldPosition);
}
return true;
}
else if (targetBody.UserData is Character targetCharacter)
{
if (targetCharacter.Removed) { return false; }
@@ -569,6 +618,13 @@ namespace Barotrauma.Items.Components
FixItemProjSpecific(user, deltaTime, targetItem);
return true;
}
else if (targetBody.UserData is BallastFloraBranch branch)
{
if (branch.ParentBallastFlora is { } ballastFlora)
{
ballastFlora.DamageBranch(branch, FireDamage * deltaTime, BallastFloraBehavior.AttackType.Fire, user);
}
}
return false;
}
@@ -769,7 +825,8 @@ namespace Barotrauma.Items.Components
object value = property.GetValue(target);
if (door.Stuck > 0)
{
var progressBar = user.UpdateHUDProgressBar(door, door.Item.WorldPosition, door.Stuck / 100, Color.DarkGray * 0.5f, Color.White, "progressbar.welding");
var progressBar = user.UpdateHUDProgressBar(door, door.Item.WorldPosition, door.Stuck / 100, Color.DarkGray * 0.5f, Color.White,
effect.propertyEffects[i].GetType() == typeof(float) && (float)effect.propertyEffects[i] < 0 ? "progressbar.cutting" : "progressbar.welding");
if (progressBar != null) { progressBar.Size = new Vector2(60.0f, 20.0f); }
}
}
@@ -394,7 +394,10 @@ namespace Barotrauma.Items.Components
}
//called when isActive is true and condition > 0.0f
public virtual void Update(float deltaTime, Camera cam) { }
public virtual void Update(float deltaTime, Camera cam)
{
ApplyStatusEffects(ActionType.OnActive, deltaTime);
}
//called when isActive is true and condition == 0.0f
public virtual void UpdateBroken(float deltaTime, Camera cam)
@@ -763,7 +766,7 @@ namespace Barotrauma.Items.Components
}
}
public virtual void Load(XElement componentElement, bool usePrefabValues)
public virtual void Load(XElement componentElement, bool usePrefabValues, IdRemap idRemap)
{
if (componentElement != null)
{
@@ -963,12 +966,12 @@ namespace Barotrauma.Items.Components
return false;
}
protected AIObjectiveContainItem AIContainItems<T>(ItemContainer container, Character character, AIObjective objective, int itemCount, bool equip, bool removeEmpty, bool spawnItemIfNotFound = false) where T : ItemComponent
protected AIObjectiveContainItem AIContainItems<T>(ItemContainer container, Character character, AIObjective currentObjective, int itemCount, bool equip, bool removeEmpty, bool spawnItemIfNotFound = false, bool dropItemOnDeselected = false) where T : ItemComponent
{
AIObjectiveContainItem containObjective = null;
if (character.AIController is HumanAIController aiController)
{
containObjective = new AIObjectiveContainItem(character, container.GetContainableItemIdentifiers.ToArray(), container, objective.objectiveManager, spawnItemIfNotFound: spawnItemIfNotFound)
containObjective = new AIObjectiveContainItem(character, container.GetContainableItemIdentifiers.ToArray(), container, currentObjective.objectiveManager, spawnItemIfNotFound: spawnItemIfNotFound)
{
targetItemCount = itemCount,
Equip = equip,
@@ -986,11 +989,21 @@ namespace Barotrauma.Items.Components
return 1.0f;
}
};
containObjective.Abandoned += () =>
containObjective.Abandoned += () => aiController.IgnoredItems.Add(container.Item);
if (dropItemOnDeselected)
{
aiController.IgnoredItems.Add(container.Item);
};
objective.AddSubObjective(containObjective);
currentObjective.Deselected += () =>
{
if (containObjective == null) { return; }
if (containObjective.IsCompleted) { return; }
Item item = containObjective.ItemToContain;
if (item != null && character.CanInteractWith(item, checkLinked: false))
{
item.Drop(character);
}
};
}
currentObjective.AddSubObjective(containObjective);
}
return containObjective;
}
@@ -1011,6 +1024,7 @@ namespace Barotrauma.Items.Components
if (FindSuitableContainer(character,
i =>
{
if (i.IsThisOrAnyContainerIgnoredByAI()) { return 0; }
var container = i.GetComponent<ItemContainer>();
if (container == null) { return 0; }
if (container.Inventory.IsFull()) { return 0; }
@@ -94,6 +94,9 @@ namespace Barotrauma.Items.Components
set;
}
[Serialize(false, false)]
public bool RemoveContainedItemsOnDeconstruct { get; set; }
public bool ShouldBeContained(string[] identifiersOrTags, out bool isRestrictionsDefined)
{
isRestrictionsDefined = containableRestrictions.Any();
@@ -377,12 +380,9 @@ namespace Barotrauma.Items.Components
if (SpawnWithId.Length > 0)
{
ItemPrefab prefab = ItemPrefab.Prefabs.Find(m => m.Identifier == SpawnWithId);
if (prefab != null)
if (prefab != null && Inventory != null && Inventory.Items.Any(it => it == null))
{
if (Inventory != null && Inventory.Items.Any(it => it == null))
{
Entity.Spawner?.AddToSpawnQueue(prefab, Inventory);
}
Entity.Spawner?.AddToSpawnQueue(prefab, Inventory, spawnIfInventoryFull: false);
}
}
}
@@ -415,17 +415,17 @@ namespace Barotrauma.Items.Components
}
}
public override void Load(XElement componentElement, bool usePrefabValues)
public override void Load(XElement componentElement, bool usePrefabValues, IdRemap idRemap)
{
base.Load(componentElement, usePrefabValues);
base.Load(componentElement, usePrefabValues, idRemap);
string containedString = componentElement.GetAttributeString("contained", "");
string[] itemIdStrings = containedString.Split(',');
itemIds = new ushort[itemIdStrings.Length];
for (int i = 0; i < itemIdStrings.Length; i++)
{
if (!ushort.TryParse(itemIdStrings[i], out ushort id)) { continue; }
itemIds[i] = id;
if (!int.TryParse(itemIdStrings[i], out int id)) { continue; }
itemIds[i] = idRemap.GetOffsetId(id);
}
}
@@ -318,16 +318,12 @@ namespace Barotrauma.Items.Components
if (!character.IsRemotePlayer || character.ViewTarget == focusTarget)
{
Vector2 centerPos = new Vector2(item.WorldRect.Center.X, item.WorldRect.Center.Y);
Vector2 centerPos = new Vector2(focusTarget.WorldRect.Center.X, focusTarget.WorldRect.Center.Y);
Item targetItem = focusTarget as Item;
if (targetItem != null)
Turret turret = focusTarget.GetComponent<Turret>();
if (turret != null)
{
Turret turret = targetItem.GetComponent<Turret>();
if (turret != null)
{
centerPos = new Vector2(targetItem.WorldRect.X + turret.TransformedBarrelPos.X, targetItem.WorldRect.Y - turret.TransformedBarrelPos.Y);
}
centerPos = new Vector2(focusTarget.WorldRect.X + turret.TransformedBarrelPos.X, focusTarget.WorldRect.Y - turret.TransformedBarrelPos.Y);
}
Vector2 offset = character.CursorWorldPosition - centerPos;
@@ -356,6 +352,9 @@ namespace Barotrauma.Items.Components
public override bool Pick(Character picker)
{
#if CLIENT
if (Screen.Selected == GameMain.SubEditorScreen) { return false; }
#endif
if (IsToggle)
{
if (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer)
@@ -123,7 +123,7 @@ namespace Barotrauma.Items.Components
//drop all items that are inside the deconstructed item
foreach (ItemContainer ic in targetItem.GetComponents<ItemContainer>())
{
if (ic?.Inventory?.Items == null) { continue; }
if (ic?.Inventory?.Items == null || ic.RemoveContainedItemsOnDeconstruct) { continue; }
foreach (Item containedItem in ic.Inventory.Items)
{
containedItem?.Drop(dropper: null, createNetworkEvent: true);
@@ -113,7 +113,7 @@ namespace Barotrauma.Items.Components
forceMultiplier *= MathHelper.Lerp(0.5f, 2.0f, (float)Math.Sqrt(User.GetSkillLevel("helm") / 100));
}
float voltageFactor = MinVoltage <= 0.0f ? 1.0f : Math.Min(Voltage / MinVoltage, 1.0f);
float voltageFactor = MinVoltage <= 0.0f ? 1.0f : Math.Min(Voltage, 1.0f);
Vector2 currForce = new Vector2(force * maxForce * forceMultiplier * voltageFactor, 0.0f);
//less effective when in a bad condition
currForce *= MathHelper.Lerp(0.5f, 2.0f, item.Condition / item.MaxCondition);
@@ -121,7 +121,7 @@ namespace Barotrauma.Items.Components
UpdatePropellerDamage(deltaTime);
float maxChangeSpeed = 0.5f;
float modifier = 2;
float noise = currForce.Length() * forceMultiplier * modifier / maxForce;
float noise = MathUtils.NearlyEqual(0.0f, maxForce) ? 0.0f : currForce.Length() * forceMultiplier * modifier / maxForce;
float min = Math.Max(1 - maxChangeSpeed, 0);
float max = 1 + maxChangeSpeed;
UpdateAITargets(Math.Clamp(noise, min, max), deltaTime);
@@ -482,9 +482,9 @@ namespace Barotrauma.Items.Components
return componentElement;
}
public override void Load(XElement componentElement, bool usePrefabValues)
public override void Load(XElement componentElement, bool usePrefabValues, IdRemap idRemap)
{
base.Load(componentElement, usePrefabValues);
base.Load(componentElement, usePrefabValues, idRemap);
savedFabricatedItem = componentElement.GetAttributeString("fabricateditemidentifier", "");
savedTimeUntilReady = componentElement.GetAttributeFloat("savedtimeuntilready", 0.0f);
savedRequiredTime = componentElement.GetAttributeFloat("savedrequiredtime", 0.0f);
@@ -8,11 +8,10 @@ namespace Barotrauma.Items.Components
{
class OxygenGenerator : Powered
{
private float powerDownTimer;
private float generatedAmount;
private List<Vent> ventList;
//key = vent, float = total volume of the hull the vent is in and the hulls connected to it
private Dictionary<Vent, float> ventList;
private float totalHullVolume;
@@ -49,17 +48,12 @@ namespace Barotrauma.Items.Components
Voltage = 1.0f;
}
if (item.CurrentHull == null) return;
if (item.CurrentHull == null) { return; }
if (Voltage < MinVoltage)
{
powerDownTimer += deltaTime;
return;
}
else
{
powerDownTimer = 0.0f;
}
CurrFlow = Math.Min(Voltage, 1.0f) * generatedAmount * 100.0f;
@@ -76,24 +70,25 @@ namespace Barotrauma.Items.Components
public override void UpdateBroken(float deltaTime, Camera cam)
{
base.UpdateBroken(deltaTime, cam);
powerDownTimer += deltaTime;
CurrFlow = 0.0f;
}
private void GetVents()
{
ventList.Clear();
ventList = new Dictionary<Vent, float>();
foreach (MapEntity entity in item.linkedTo)
{
Item linkedItem = entity as Item;
if (linkedItem == null) continue;
if (!(entity is Item linkedItem)) { continue; }
Vent vent = linkedItem.GetComponent<Vent>();
if (vent == null) continue;
if (vent?.Item.CurrentHull == null) { continue; }
ventList.Add(vent);
if (linkedItem.CurrentHull != null) totalHullVolume += linkedItem.CurrentHull.Volume;
ventList.Add(vent, 0.0f);
foreach (Hull connectedHull in vent.Item.CurrentHull.GetConnectedHulls(includingThis: true, searchDepth: 10, ignoreClosedGaps: true))
{
totalHullVolume += connectedHull.Volume;
ventList[vent] += connectedHull.Volume;
}
}
}
@@ -101,18 +96,17 @@ namespace Barotrauma.Items.Components
{
if (ventList == null)
{
ventList = new List<Vent>();
GetVents();
}
if (!ventList.Any() || totalHullVolume <= 0.0f) return;
if (!ventList.Any() || totalHullVolume <= 0.0f) { return; }
foreach (Vent v in ventList)
foreach (KeyValuePair<Vent, float> v in ventList)
{
if (v.Item.CurrentHull == null) continue;
if (v.Key?.Item.CurrentHull == null) { continue; }
v.OxygenFlow = deltaOxygen * (v.Item.CurrentHull.Volume / totalHullVolume);
v.IsActive = true;
v.Key.OxygenFlow = deltaOxygen * (v.Value / totalHullVolume);
v.Key.IsActive = true;
}
}
}
@@ -2,7 +2,9 @@
using Microsoft.Xna.Framework;
using System;
using System.Globalization;
using System.Linq;
using System.Xml.Linq;
using Barotrauma.MapCreatures.Behavior;
namespace Barotrauma.Items.Components
{
@@ -11,10 +13,38 @@ namespace Barotrauma.Items.Components
private float flowPercentage;
private float maxFlow;
private float? targetLevel;
public float? TargetLevel;
private bool hijacked;
public bool Hijacked
{
get { return hijacked; }
set
{
if (value == hijacked) { return; }
hijacked = value;
#if SERVER
item.CreateServerEvent(this);
#endif
}
}
private float pumpSpeedLockTimer, isActiveLockTimer;
private bool infected;
[Serialize(false, true, description: "Whether or not the pump is infected with ballast flora spores.")]
public bool Infected
{
get => infected;
set
{
infected = value;
}
}
public string InfectIdentifier;
[Serialize(0.0f, true, description: "How fast the item is currently pumping water (-100 = full speed out, 100 = full speed in). Intended to be used by StatusEffect conditionals (setting this value in XML has no effect).")]
public float FlowPercentage
{
@@ -66,12 +96,12 @@ namespace Barotrauma.Items.Components
{
currFlow = 0.0f;
if (targetLevel != null)
if (TargetLevel != null)
{
pumpSpeedLockTimer -= deltaTime;
float hullPercentage = 0.0f;
if (item.CurrentHull != null) { hullPercentage = (item.CurrentHull.WaterVolume / item.CurrentHull.Volume) * 100.0f; }
FlowPercentage = ((float)targetLevel - hullPercentage) * 10.0f;
FlowPercentage = ((float)TargetLevel - hullPercentage) * 10.0f;
}
currPowerConsumption = powerConsumption * Math.Abs(flowPercentage / 100.0f);
@@ -92,14 +122,41 @@ namespace Barotrauma.Items.Components
//less effective when in a bad condition
currFlow *= MathHelper.Lerp(0.5f, 1.0f, item.Condition / item.MaxCondition);
if (currFlow < 0 && Infected)
{
InfectBallast(InfectIdentifier);
}
Infected = false;
item.CurrentHull.WaterVolume += currFlow;
if (item.CurrentHull.WaterVolume > item.CurrentHull.Volume) { item.CurrentHull.Pressure += 0.5f; }
}
public void InfectBallast(string identifier)
{
Hull hull = item.CurrentHull;
if (hull == null) { return; }
// if the ship is already infected then do nothing
if (Hull.hullList.Where(h => h.Submarine == hull.Submarine).Any(h => h.BallastFlora != null)) { return; }
if (hull.BallastFlora != null) { return; }
Vector2 offset = item.WorldPosition - hull.WorldPosition;
hull.BallastFlora = new BallastFloraBehavior(hull, BallastFloraPrefab.Find(identifier), offset, firstGrowth: true);
#if SERVER
hull.BallastFlora.SendNetworkMessage(hull.BallastFlora, BallastFloraBehavior.NetworkHeader.Spawn);
#endif
}
partial void UpdateProjSpecific(float deltaTime);
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power = 0.0f, float signalStrength = 1.0f)
{
if (Hijacked) { return; }
if (connection.Name == "toggle")
{
IsActive = !IsActive;
@@ -115,7 +172,7 @@ namespace Barotrauma.Items.Components
if (float.TryParse(signal, NumberStyles.Any, CultureInfo.InvariantCulture, out float tempSpeed))
{
flowPercentage = MathHelper.Clamp(tempSpeed, -100.0f, 100.0f);
targetLevel = null;
TargetLevel = null;
pumpSpeedLockTimer = 0.1f;
}
}
@@ -123,7 +180,7 @@ namespace Barotrauma.Items.Components
{
if (float.TryParse(signal, NumberStyles.Any, CultureInfo.InvariantCulture, out float tempTarget))
{
targetLevel = MathHelper.Clamp(tempTarget + 50.0f, 0.0f, 100.0f);
TargetLevel = MathHelper.Clamp(tempTarget + 50.0f, 0.0f, 100.0f);
pumpSpeedLockTimer = 0.1f;
}
}
@@ -137,7 +137,7 @@ namespace Barotrauma.Items.Components
}
}
[Serialize(0.2f, true, description: "How fast the condition of the contained fuel rods deteriorates per second."), Editable(0.0f, 1000.0f)]
[Serialize(0.2f, true, description: "How fast the condition of the contained fuel rods deteriorates per second."), Editable(0.0f, 1000.0f, decimals: 3)]
public float FuelConsumptionRate
{
get { return fuelConsumptionRate; }
@@ -399,6 +399,8 @@ namespace Barotrauma.Items.Components
//fission rate is clamped to the amount of available fuel
float maxFissionRate = Math.Min(prevAvailableFuel, 100.0f);
if (maxFissionRate >= 100.0f) { return false; }
float maxTurbineOutput = 100.0f;
//calculate the maximum output if the fission rate is cranked as high as it goes and turbine output is at max
@@ -589,7 +591,7 @@ namespace Barotrauma.Items.Components
if (objective.SubObjectives.None())
{
int itemCount = item.ContainedItems.Count(i => i != null && container.ContainableItems.Any(ri => ri.MatchesItem(i))) + 1;
AIContainItems<Reactor>(container, character, objective, itemCount, equip: false, removeEmpty: true, spawnItemIfNotFound: character.TeamID == Character.TeamType.FriendlyNPC);
AIContainItems<Reactor>(container, character, objective, itemCount, equip: false, removeEmpty: true, spawnItemIfNotFound: character.TeamID == Character.TeamType.FriendlyNPC, dropItemOnDeselected: true);
character.Speak(TextManager.Get("DialogReactorFuel"), null, 0.0f, "reactorfuel", 30.0f);
}
return false;
@@ -604,10 +606,7 @@ namespace Barotrauma.Items.Components
{
if (item != null && container.ContainableItems.Any(ri => ri.MatchesItem(item)))
{
if (!character.Inventory.TryPutItem(item, character, allowedSlots: item.AllowedSlots))
{
item.Drop(character);
}
item.Drop(character);
break;
}
}
@@ -64,6 +64,7 @@ namespace Barotrauma.Items.Components
private bool useDirectionalPing = false;
private Vector2 pingDirection = new Vector2(1.0f, 0.0f);
private bool useMineralScanner;
private bool aiPingCheckPending;
@@ -103,6 +104,10 @@ namespace Barotrauma.Items.Components
set;
}
[Editable, Serialize(false, false, description: "Does the sonar have mineral scanning mode. " +
"Only available in-game when the Item has no Steering component.")]
public bool HasMineralScanner { get; set; }
public float Zoom
{
get { return zoom; }
@@ -343,6 +348,7 @@ namespace Barotrauma.Items.Components
bool isActive = msg.ReadBoolean();
bool directionalPing = useDirectionalPing;
float zoomT = zoom, pingDirectionT = 0.0f;
bool mineralScanner = useMineralScanner;
if (isActive)
{
zoomT = msg.ReadRangedSingle(0.0f, 1.0f, 8);
@@ -351,6 +357,7 @@ namespace Barotrauma.Items.Components
{
pingDirectionT = msg.ReadRangedSingle(0.0f, 1.0f, 8);
}
mineralScanner = msg.ReadBoolean();
}
if (!item.CanClientAccess(c)) { return; }
@@ -366,9 +373,14 @@ namespace Barotrauma.Items.Components
float pingAngle = MathHelper.Lerp(0.0f, MathHelper.TwoPi, pingDirectionT);
pingDirection = new Vector2((float)Math.Cos(pingAngle), (float)Math.Sin(pingAngle));
}
useMineralScanner = mineralScanner;
#if CLIENT
zoomSlider.BarScroll = zoomT;
directionalModeSwitch.Selected = useDirectionalPing;
if (mineralScannerSwitch != null)
{
mineralScannerSwitch.Selected = useMineralScanner;
}
#endif
}
#if SERVER
@@ -388,6 +400,7 @@ namespace Barotrauma.Items.Components
float pingAngle = MathUtils.WrapAngleTwoPi(MathUtils.VectorToAngle(pingDirection));
msg.WriteRangedSingle(MathUtils.InverseLerp(0.0f, MathHelper.TwoPi, pingAngle), 0.0f, 1.0f, 8);
}
msg.Write(useMineralScanner);
}
}
}
@@ -67,7 +67,10 @@ namespace Barotrauma.Items.Components
{
if (pathFinder == null)
{
pathFinder = new PathFinder(WayPoint.WayPointList, false);
pathFinder = new PathFinder(WayPoint.WayPointList, false)
{
GetNodePenalty = GetNodePenalty
};
}
MaintainPos = true;
if (posToMaintain == null)
@@ -87,7 +90,7 @@ namespace Barotrauma.Items.Components
}
}
[Editable(0.0f, 1.0f, decimals: 3),
[Editable(0.0f, 1.0f, decimals: 4),
Serialize(0.5f, true, description: "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.")]
public float NeutralBallastLevel
@@ -417,6 +420,7 @@ namespace Barotrauma.Items.Components
Math.Max(1000.0f * Math.Abs(controlledSub.Velocity.Y), controlledSub.Borders.Height * 0.75f));
float avoidRadius = avoidDist.Length();
float damagingWallAvoidRadius = avoidRadius * 1.5f;
Vector2 newAvoidStrength = Vector2.Zero;
@@ -426,12 +430,26 @@ namespace Barotrauma.Items.Components
var closeCells = Level.Loaded.GetCells(controlledSub.WorldPosition, 4);
foreach (VoronoiCell cell in closeCells)
{
if (cell.DoesDamage)
{
foreach (GraphEdge edge in cell.Edges)
{
Vector2 closestPoint = MathUtils.GetClosestPointOnLineSegment(edge.Point1 + cell.Translation, edge.Point2 + cell.Translation, controlledSub.WorldPosition);
float dist = Vector2.Distance(closestPoint, controlledSub.WorldPosition);
if (dist > damagingWallAvoidRadius) { continue; }
Vector2 diff = controlledSub.WorldPosition - cell.Center;
Vector2 avoid = Vector2.Normalize(diff) * (damagingWallAvoidRadius - dist) / damagingWallAvoidRadius;
newAvoidStrength += avoid;
debugDrawObstacles.Add(new ObstacleDebugInfo(edge, edge.Center, 1.0f, avoid, cell.Translation));
}
continue;
}
foreach (GraphEdge edge in cell.Edges)
{
if (MathUtils.GetLineIntersection(edge.Point1 + cell.Translation, edge.Point2 + cell.Translation, controlledSub.WorldPosition, cell.Center, out Vector2 intersection))
{
Vector2 diff = controlledSub.WorldPosition - intersection;
//far enough -> ignore
if (Math.Abs(diff.X) > avoidDist.X && Math.Abs(diff.Y) > avoidDist.Y)
{
@@ -497,6 +515,15 @@ namespace Barotrauma.Items.Components
}
}
private float? GetNodePenalty(PathNode node, PathNode nextNode)
{
if (node.Waypoint?.Tunnel == null || controlledSub == null || node.Waypoint.Tunnel.Type == Level.TunnelType.MainPath) { return 0.0f; }
//never navigate from the main path to another type of path
if (node.Waypoint.Tunnel.Type == Level.TunnelType.MainPath && nextNode.Waypoint?.Tunnel?.Type != Level.TunnelType.MainPath) { return null; }
//higher cost for side paths (= autopilot prefers the main path, but can still navigate side paths if it ends up on one)
return 1000.0f;
}
private void UpdatePath()
{
if (Level.Loaded == null) { return; }
@@ -13,11 +13,7 @@ namespace Barotrauma.Items.Components
set { oxygenFlow = Math.Max(value, 0.0f); }
}
public Vent (Item item, XElement element)
: base(item, element)
{
}
public Vent (Item item, XElement element) : base(item, element) { }
public override void Update(float deltaTime, Camera cam)
{
@@ -1,8 +1,4 @@
using Barotrauma.Networking;
using System.Xml.Linq;
#if CLIENT
using Microsoft.Xna.Framework.Graphics;
#endif
namespace Barotrauma.Items.Components
{
@@ -55,6 +55,22 @@ namespace Barotrauma.Items.Components
set;
}
private float extraLoad;
private float extraLoadSetTime;
/// <summary>
/// Additional load coming from somewhere else than the devices connected to the junction box (e.g. ballast flora or piezo crystals).
/// Goes back to zero automatically if you stop setting the value.
/// </summary>
public float ExtraLoad
{
get { return extraLoad; }
set
{
extraLoad = Math.Max(value, 0.0f);
extraLoadSetTime = (float)Timing.TotalTime;
}
}
//can the component transfer power
private bool canTransfer;
public bool CanTransfer
@@ -135,6 +151,11 @@ namespace Barotrauma.Items.Components
{
RefreshConnections();
if (Timing.TotalTime > extraLoadSetTime + 1.0)
{
extraLoad = Math.Max(extraLoad - 1000.0f * deltaTime, 0);
}
if (!CanTransfer) { return; }
if (isBroken)
@@ -231,8 +231,16 @@ namespace Barotrauma.Items.Components
//and send out a "probe signal" which the PowerTransfer components use to add up the grid power/load
foreach (Powered powered in poweredList)
{
if (powered is PowerTransfer) { continue; }
if (powered.currPowerConsumption > 0.0f)
if (powered is PowerTransfer pt)
{
if (pt.ExtraLoad > 0.0f)
{
lastPowerProbeRecipients.Clear();
powered.powerIn?.SendPowerProbeSignal(powered.item, -pt.ExtraLoad);
}
continue;
}
else if (powered.currPowerConsumption > 0.0f)
{
//consuming power
lastPowerProbeRecipients.Clear();
@@ -60,14 +60,14 @@ namespace Barotrauma.Items.Components
public List<Body> IgnoredBodies;
private Character user;
private Character _user;
public Character User
{
get { return user; }
get { return _user; }
set
{
user = value;
Attack?.SetUser(user);
_user = value;
Attack?.SetUser(_user);
}
}
@@ -211,7 +211,54 @@ namespace Barotrauma.Items.Components
}
}
public override bool Use(float deltaTime, Character character = null)
private void Launch(Character user, Vector2 simPosition, float rotation)
{
Item.body.ResetDynamics();
Item.SetTransform(simPosition, rotation);
// Set user for hitscan projectiles to work properly.
User = user;
// Need to set null for non-characterusable items.
Use(character: null);
// Set user for normal projectiles to work properly.
User = user;
if (Item.Removed) { return; }
launchPos = simPosition;
//set the rotation of the projectile again because dropping the projectile resets the rotation
Item.SetTransform(simPosition, rotation + (Item.body.Dir * LaunchRotationRadians));
}
public void Shoot(Character user, Vector2 weaponPos, Vector2 spawnPos, float rotation, List<Body> ignoredBodies, bool createNetworkEvent)
{
//add the limbs of the shooter to the list of bodies to be ignored
//so that the player can't shoot himself
IgnoredBodies = ignoredBodies;
Vector2 projectilePos = weaponPos;
//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(weaponPos, spawnPos, IgnoredBodies, Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionItemBlocking) == null)
{
//no obstacles -> we can spawn the projectile at the barrel
projectilePos = spawnPos;
}
else if ((weaponPos - spawnPos).LengthSquared() > 0.0001f)
{
//spawn the projectile body.GetMaxExtent() away from the position where the raycast hit the obstacle
Vector2 newPos = weaponPos - Vector2.Normalize(spawnPos - projectilePos) * Math.Max(Item.body.GetMaxExtent(), 0.1f);
if (MathUtils.IsValid(newPos))
{
projectilePos = newPos;
}
}
Launch(user, projectilePos, rotation);
if (createNetworkEvent && !Item.Removed && GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
{
#if SERVER
launchRot = rotation;
Item.CreateServerEvent(this, new object[] { true }); //true = indicate that this is a launch event
#endif
}
}
public bool Use(Character character = null)
{
if (character != null && !characterUsable) { return false; }
@@ -230,16 +277,16 @@ namespace Barotrauma.Items.Components
}
else
{
Launch(launchDir * LaunchImpulse * item.body.Mass);
DoLaunch(launchDir * LaunchImpulse * item.body.Mass);
}
}
User = character;
return true;
}
private void Launch(Vector2 impulse)
public override bool Use(float deltaTime, Character character = null) => Use(character);
private void DoLaunch(Vector2 impulse)
{
hits.Clear();
@@ -342,7 +389,15 @@ namespace Barotrauma.Items.Components
}
else
{
Entity.Spawner.AddToRemoveQueue(item);
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient)
{
//clients aren't allowed to remove items by themselves, so lets hide the projectile until the server tells us to remove it
item.HiddenInGame = Hitscan;
}
else
{
Entity.Spawner.AddToRemoveQueue(item);
}
}
}
}
@@ -360,6 +415,7 @@ namespace Barotrauma.Items.Components
{
//ignore sensors and items
if (fixture?.Body == null || fixture.IsSensor) { return true; }
if (fixture.Body.UserData is VineTile) { return true; }
if (fixture.Body.UserData is Item item && (item.GetComponent<Door>() == null && !item.Prefab.DamagedByProjectiles || item.Condition <= 0)) { return true; }
if (fixture.Body?.UserData as string == "ruinroom") { return true; }
@@ -381,6 +437,7 @@ namespace Barotrauma.Items.Components
{
//ignore sensors and items
if (fixture?.Body == null || fixture.IsSensor) { return -1; }
if (fixture.Body.UserData is VineTile) { return -1; }
if (fixture.Body.UserData is Item item && (item.GetComponent<Door>() == null && !item.Prefab.DamagedByProjectiles || item.Condition <= 0)) { return -1; }
if (fixture.Body?.UserData as string == "ruinroom") { return -1; }
@@ -577,20 +634,27 @@ namespace Barotrauma.Items.Components
{
if (Attack != null) { attackResult = Attack.DoDamage(User, damageable, item.WorldPosition, 1.0f); }
}
else if (target.Body.UserData is VoronoiCell voronoiCell && voronoiCell.IsDestructible && Attack != null && Math.Abs(Attack.StructureDamage) > 0.0f)
{
if (Level.Loaded?.ExtraWalls.Find(w => w.Body == target.Body) is DestructibleLevelWall destructibleWall)
{
attackResult = Attack.DoDamage(User, destructibleWall, item.WorldPosition, 1.0f);
}
}
if (character != null) { character.LastDamageSource = item; }
#if CLIENT
PlaySound(ActionType.OnUse, user: user);
PlaySound(ActionType.OnImpact, user: user);
PlaySound(ActionType.OnUse, user: _user);
PlaySound(ActionType.OnImpact, user: _user);
#endif
if (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer)
{
if (target.Body.UserData is Limb targetLimb)
{
ApplyStatusEffects(ActionType.OnUse, 1.0f, character, targetLimb, user: user);
ApplyStatusEffects(ActionType.OnImpact, 1.0f, character, targetLimb, user: user);
ApplyStatusEffects(ActionType.OnUse, 1.0f, character, targetLimb, user: _user);
ApplyStatusEffects(ActionType.OnImpact, 1.0f, character, targetLimb, user: _user);
var attack = targetLimb.attack;
if (attack != null)
{
@@ -626,8 +690,8 @@ namespace Barotrauma.Items.Components
}
else
{
ApplyStatusEffects(ActionType.OnUse, 1.0f, useTarget: target.Body.UserData as Entity, user: user);
ApplyStatusEffects(ActionType.OnImpact, 1.0f, useTarget: target.Body.UserData as Entity, user: user);
ApplyStatusEffects(ActionType.OnUse, 1.0f, useTarget: target.Body.UserData as Entity, user: _user);
ApplyStatusEffects(ActionType.OnImpact, 1.0f, useTarget: target.Body.UserData as Entity, user: _user);
#if SERVER
if (GameMain.NetworkMember.IsServer)
{
@@ -703,7 +767,15 @@ namespace Barotrauma.Items.Components
if (RemoveOnHit)
{
Entity.Spawner.AddToRemoveQueue(item);
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient)
{
//clients aren't allowed to remove items by themselves, so lets hide the projectile until the server tells us to remove it
item.HiddenInGame = Hitscan;
}
else
{
Entity.Spawner?.AddToRemoveQueue(item);
}
}
return true;
@@ -446,7 +446,7 @@ namespace Barotrauma.Items.Components
//oxygen generators don't deteriorate if they're not running
if (oxyGenerator.CurrFlow > 0.1f) { return true; }
}
else if (ic is Powered powered)
else if (ic is Powered powered && !(powered is LightComponent))
{
if (powered.Voltage >= powered.MinVoltage) { return true; }
}
@@ -477,6 +477,7 @@ namespace Barotrauma.Items.Components
private void UpdateFixAnimation(Character character)
{
if (character == null || character.IsDead || character.IsIncapacitated) { return; }
character.AnimController.UpdateUseItem(false, item.WorldPosition + new Vector2(0.0f, 100.0f) * ((item.Condition / item.MaxCondition) % 0.1f));
}
@@ -40,7 +40,7 @@ namespace Barotrauma.Items.Components
{
get
{
if (recipientsDirty) RefreshRecipients();
if (recipientsDirty) { RefreshRecipients(); }
return recipients;
}
}
@@ -61,7 +61,7 @@ namespace Barotrauma.Items.Components
return "Connection (" + item.Name + ", " + Name + ")";
}
public Connection(XElement element, ConnectionPanel connectionPanel)
public Connection(XElement element, ConnectionPanel connectionPanel, IdRemap idRemap)
{
#if CLIENT
@@ -150,8 +150,11 @@ namespace Barotrauma.Items.Components
if (index == -1) break;
int id = subElement.GetAttributeInt("w", 0);
if (id < 0) id = 0;
wireId[index] = (ushort)id;
if (id < 0)
{
id = 0;
}
wireId[index] = idRemap.GetOffsetId(id);
break;
@@ -162,6 +165,11 @@ namespace Barotrauma.Items.Components
}
}
public void SetRecipientsDirty()
{
recipientsDirty = true;
}
private void RefreshRecipients()
{
recipients.Clear();
@@ -304,6 +312,7 @@ namespace Barotrauma.Items.Components
{
if (wires[i].Item.body != null) wires[i].Item.body.Enabled = false;
wires[i].Connect(this, false, false);
wires[i].FixNodeEnds();
}
}
}
@@ -50,10 +50,10 @@ namespace Barotrauma.Items.Components
switch (subElement.Name.ToString())
{
case "input":
Connections.Add(new Connection(subElement, this));
Connections.Add(new Connection(subElement, this, IdRemap.DiscardId));
break;
case "output":
Connections.Add(new Connection(subElement, this));
Connections.Add(new Connection(subElement, this, IdRemap.DiscardId));
break;
}
}
@@ -218,9 +218,9 @@ namespace Barotrauma.Items.Components
return false;
}
public override void Load(XElement element, bool usePrefabValues)
public override void Load(XElement element, bool usePrefabValues, IdRemap idRemap)
{
base.Load(element, usePrefabValues);
base.Load(element, usePrefabValues, idRemap);
List<Connection> loadedConnections = new List<Connection>();
@@ -229,10 +229,10 @@ namespace Barotrauma.Items.Components
switch (subElement.Name.ToString())
{
case "input":
loadedConnections.Add(new Connection(subElement, this));
loadedConnections.Add(new Connection(subElement, this, idRemap));
break;
case "output":
loadedConnections.Add(new Connection(subElement, this));
loadedConnections.Add(new Connection(subElement, this, idRemap));
break;
}
}
@@ -14,12 +14,12 @@ namespace Barotrauma.Items.Components
private Color lightColor;
private float lightBrightness;
private float blinkFrequency;
private float pulseFrequency, pulseAmount;
private float range;
private float flicker, flickerState;
private float flicker, flickerSpeed;
private bool castShadows;
private bool drawBehindSubs;
private float blinkTimer;
private double lastToggleSignalTime;
@@ -90,14 +90,49 @@ namespace Barotrauma.Items.Components
set
{
flicker = MathHelper.Clamp(value, 0.0f, 1.0f);
#if CLIENT
if (light != null) { light.LightSourceParams.Flicker = flicker; }
#endif
}
}
[Editable, Serialize(1.0f, false, description: "How fast the light flickers.")]
public float FlickerSpeed
{
get;
set;
get { return flickerSpeed; }
set
{
flickerSpeed = value;
#if CLIENT
if (light != null) { light.LightSourceParams.FlickerSpeed = flickerSpeed; }
#endif
}
}
[Editable, Serialize(0.0f, true, description: "How rapidly the light pulsates (in Hz). 0 = no blinking.")]
public float PulseFrequency
{
get { return pulseFrequency; }
set
{
pulseFrequency = MathHelper.Clamp(value, 0.0f, 60.0f);
#if CLIENT
if (light != null) { light.LightSourceParams.PulseFrequency = pulseFrequency; }
#endif
}
}
[Editable(MinValueFloat = 0.0f, MaxValueFloat = 1.0f, DecimalCount = 2), Serialize(0.0f, true, description: "How much light pulsates (in Hz). 0 = not at all, 1 = alternates between full brightness and off.")]
public float PulseAmount
{
get { return pulseAmount; }
set
{
pulseAmount = MathHelper.Clamp(value, 0.0f, 1.0f);
#if CLIENT
if (light != null) { light.LightSourceParams.PulseAmount = pulseAmount; }
#endif
}
}
[Editable, Serialize(0.0f, true, description: "How rapidly the light blinks on and off (in Hz). 0 = no blinking.")]
@@ -107,6 +142,9 @@ namespace Barotrauma.Items.Components
set
{
blinkFrequency = MathHelper.Clamp(value, 0.0f, 60.0f);
#if CLIENT
if (light != null) { light.LightSourceParams.BlinkFrequency = blinkFrequency; }
#endif
}
}
@@ -161,11 +199,16 @@ namespace Barotrauma.Items.Components
{
ParentSub = item.CurrentHull?.Submarine,
Position = item.Position,
CastShadows = castShadows,
CastShadows = castShadows,
IsBackground = drawBehindSubs,
SpriteScale = Vector2.One * item.Scale,
Range = range
};
light.LightSourceParams.Flicker = flicker;
light.LightSourceParams.FlickerSpeed = FlickerSpeed;
light.LightSourceParams.PulseAmount = pulseAmount;
light.LightSourceParams.PulseFrequency = pulseFrequency;
light.LightSourceParams.BlinkFrequency = blinkFrequency;
#endif
IsActive = IsOn;
@@ -229,22 +272,7 @@ namespace Barotrauma.Items.Components
lightBrightness = MathHelper.Lerp(lightBrightness, powerConsumption <= 0.0f ? 1.0f : Math.Min(Voltage, 1.0f), 0.1f);
}
if (blinkFrequency > 0.0f)
{
blinkTimer = (blinkTimer + deltaTime * blinkFrequency) % 1.0f;
}
if (blinkTimer > 0.5f)
{
SetLightSourceState(false, lightBrightness);
}
else
{
flickerState += deltaTime * FlickerSpeed;
flickerState %= 255;
float noise = PerlinNoise.GetPerlin(flickerState, flickerState * 0.5f) * flicker;
SetLightSourceState(true, lightBrightness * (1.0f - noise));
}
SetLightSourceState(true, lightBrightness);
if (powerIn == null && powerConsumption > 0.0f) { Voltage -= deltaTime; }
}
@@ -1,4 +1,5 @@
using Barotrauma.Networking;
using System.Collections.Generic;
using System.Xml.Linq;
namespace Barotrauma.Items.Components
@@ -7,6 +8,10 @@ namespace Barotrauma.Items.Components
{
private const int MaxMessageLength = ChatMessage.MaxLength;
private const int MaxMessages = 60;
private List<string> messageHistory = new List<string>(MaxMessages);
public string DisplayedWelcomeMessage
{
get;
@@ -50,5 +55,41 @@ namespace Barotrauma.Items.Components
string inputSignal = signal.Replace("\\n", "\n");
ShowOnDisplay(inputSignal);
}
public override void OnItemLoaded()
{
base.OnItemLoaded();
if (!string.IsNullOrEmpty(DisplayedWelcomeMessage))
{
ShowOnDisplay(DisplayedWelcomeMessage);
DisplayedWelcomeMessage = "";
//remove welcome message if a game session is running so it doesn't reappear on successive rounds
if (GameMain.GameSession != null)
{
welcomeMessage = null;
}
}
}
public override XElement Save(XElement parentElement)
{
var componentElement = base.Save(parentElement);
for (int i = 0; i < messageHistory.Count; i++)
{
componentElement.Add(new XAttribute("msg" + i, messageHistory[i]));
}
return componentElement;
}
public override void Load(XElement componentElement, bool usePrefabValues, IdRemap idRemap)
{
base.Load(componentElement, usePrefabValues, idRemap);
for (int i = 0; i < MaxMessages; i++)
{
string msg = componentElement.GetAttributeString("msg" + i, null);
if (msg == null) { break; }
ShowOnDisplay(msg);
}
}
}
}
@@ -17,7 +17,8 @@ namespace Barotrauma.Items.Components
Atan,
}
protected float[] receivedSignal = new float[2];
private float[] receivedSignal = new float[2];
private float[] timeSinceReceived = new float[2];
[Serialize(FunctionType.Sin, false, description: "Which kind of function to run the input through.", alwaysUseInstanceValues: true)]
public FunctionType Function
@@ -41,12 +42,25 @@ namespace Barotrauma.Items.Components
public override void Update(float deltaTime, Camera cam)
{
//reset received signals
receivedSignal[0] = float.NaN;
receivedSignal[1] = float.NaN;
if (Function == FunctionType.Atan)
{
for (int i = 0; i < 2; i++)
{
timeSinceReceived[i] += deltaTime;
if (timeSinceReceived[i] > 0.1f)
{
receivedSignal[i] = float.NaN;
}
}
if (!float.IsNaN(receivedSignal[0]) && !float.IsNaN(receivedSignal[1]))
{
float angle = (float)Math.Atan2(receivedSignal[1], receivedSignal[0]);
if (!UseRadians) { angle = MathHelper.ToDegrees(angle); }
item.SendSignal(0, angle.ToString("G", CultureInfo.InvariantCulture), "signal_out", null);
}
}
}
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power = 0, float signalStrength = 1)
{
float.TryParse(signal, NumberStyles.Float, CultureInfo.InvariantCulture, out float value);
@@ -89,17 +103,13 @@ namespace Barotrauma.Items.Components
case FunctionType.Atan:
if (connection.Name == "signal_in_x")
{
timeSinceReceived[0] = 0.0f;
float.TryParse(signal, NumberStyles.Float, CultureInfo.InvariantCulture, out receivedSignal[0]);
}
else if (connection.Name == "signal_in_y")
{
float.TryParse(signal, NumberStyles.Float, CultureInfo.InvariantCulture, out receivedSignal[1]);
if (!float.IsNaN(receivedSignal[0]) && !float.IsNaN(receivedSignal[1]))
{
float angle = (float)Math.Atan2(receivedSignal[1], receivedSignal[0]);
if (!UseRadians) { angle = MathHelper.ToDegrees(angle); }
item.SendSignal(0, angle.ToString("G", CultureInfo.InvariantCulture), "signal_out", null);
}
timeSinceReceived[1] = 0.0f;
float.TryParse(signal, NumberStyles.Float, CultureInfo.InvariantCulture, out receivedSignal[1]);
}
else
{
@@ -1,4 +1,6 @@
using System.Xml.Linq;
using Microsoft.Xna.Framework;
using System;
using System.Xml.Linq;
namespace Barotrauma.Items.Components
{
@@ -59,6 +61,12 @@ namespace Barotrauma.Items.Components
{
item.SendSignal(0, signalOut, "signal_out", null);
}
if (item.CurrentHull != null)
{
int waterPercentage = MathHelper.Clamp((int)Math.Round(item.CurrentHull.WaterPercentage), 0, 100);
item.SendSignal(0, waterPercentage.ToString(), "water_%", null);
}
}
}
}
@@ -152,7 +152,7 @@ namespace Barotrauma.Items.Components
channelMemory[index] = MathHelper.Clamp(value, 0, 10000);
}
public void TransmitSignal(int stepsTaken, string signal, Item source, Character sender, bool sendToChat, float signalStrength = 1.0f)
public void TransmitSignal(int stepsTaken, string signal, Item source, Character sender, bool sentFromChat, float signalStrength = 1.0f)
{
var senderComponent = source?.GetComponent<WifiComponent>();
if (senderComponent != null && !CanReceive(senderComponent)) { return; }
@@ -162,6 +162,8 @@ namespace Barotrauma.Items.Components
var receivers = GetReceiversInRange();
foreach (WifiComponent wifiComp in receivers)
{
if (sentFromChat && !wifiComp.LinkToChat) { continue; }
//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);
@@ -176,11 +178,12 @@ namespace Barotrauma.Items.Components
source.LastSentSignalRecipients.Add(receiverItem);
}
}
}
}
if (DiscardDuplicateChatMessages && signal == prevSignal) continue;
if (DiscardDuplicateChatMessages && signal == prevSignal) { continue; }
if (LinkToChat && wifiComp.LinkToChat && chatMsgCooldown <= 0.0f && sendToChat)
//create a chat message
if (LinkToChat && wifiComp.LinkToChat && chatMsgCooldown <= 0.0f && !sentFromChat)
{
if (wifiComp.item.ParentInventory != null &&
wifiComp.item.ParentInventory.Owner != null)
@@ -232,7 +235,7 @@ namespace Barotrauma.Items.Components
switch (connection.Name)
{
case "signal_in":
TransmitSignal(stepsTaken, signal, source, sender, true, signalStrength);
TransmitSignal(stepsTaken, signal, source, sender, false, signalStrength);
break;
case "set_channel":
if (int.TryParse(signal, out int newChannel))
@@ -37,6 +37,11 @@ namespace Barotrauma.Items.Components
angle = MathUtils.VectorToAngle(end - start);
length = Vector2.Distance(start, end);
if (length > 5000.0f)
{
int akjsdnfkjsadf = 1;
}
}
}
@@ -183,8 +188,12 @@ namespace Barotrauma.Items.Components
if (refSub == null)
{
Structure attachTarget = Structure.GetAttachTarget(newConnection.Item.WorldPosition);
if (attachTarget == null) { continue; }
refSub = attachTarget.Submarine;
if (attachTarget == null && !(newConnection.Item.GetComponent<Holdable>()?.Attached ?? false))
{
connections[i] = null;
continue;
}
refSub = attachTarget?.Submarine;
}
Vector2 nodePos = refSub == null ?
@@ -200,14 +209,16 @@ namespace Barotrauma.Items.Components
{
if (connections[0] != null && connections[0] != newConnection)
{
if (Vector2.DistanceSquared(nodes[0], connections[0].Item.Position - (refSub?.HiddenSubPosition ?? Vector2.Zero)) < Vector2.DistanceSquared(nodes[nodes.Count - 1], nodePos))
if (Vector2.DistanceSquared(nodes[0], connections[0].Item.Position - (refSub?.HiddenSubPosition ?? Vector2.Zero)) <
Vector2.DistanceSquared(nodes[nodes.Count - 1], connections[0].Item.Position - (refSub?.HiddenSubPosition ?? Vector2.Zero)))
{
newNodeIndex = nodes.Count;
}
}
else if (connections[1] != null && connections[1] != newConnection)
{
if (Vector2.DistanceSquared(nodes[0], connections[1].Item.Position - (refSub?.HiddenSubPosition ?? Vector2.Zero)) < Vector2.DistanceSquared(nodes[nodes.Count - 1], nodePos))
if (Vector2.DistanceSquared(nodes[0], connections[1].Item.Position - (refSub?.HiddenSubPosition ?? Vector2.Zero)) <
Vector2.DistanceSquared(nodes[nodes.Count - 1], connections[1].Item.Position - (refSub?.HiddenSubPosition ?? Vector2.Zero)))
{
newNodeIndex = nodes.Count;
}
@@ -236,18 +247,18 @@ namespace Barotrauma.Items.Components
{
foreach (ItemComponent ic in item.Components)
{
if (ic == this) continue;
if (ic == this) { continue; }
ic.Drop(null);
}
if (item.Container != null) item.Container.RemoveContained(this.item);
if (item.body != null) item.body.Enabled = false;
if (item.Container != null) { item.Container.RemoveContained(this.item); }
if (item.body != null) { item.body.Enabled = false; }
IsActive = false;
CleanNodes();
}
if (item.body != null) item.Submarine = newConnection.Item.Submarine;
if (item.body != null) { item.Submarine = newConnection.Item.Submarine; }
if (sendNetworkEvent)
{
@@ -620,8 +631,8 @@ namespace Barotrauma.Items.Components
{
if (connections[i]?.Item != null)
{
var pt = connections[i].Item.GetComponent<PowerTransfer>();
if (pt != null) pt.SetConnectionDirty(connections[i]);
connections[i].Item.GetComponent<PowerTransfer>()?.SetConnectionDirty(connections[i]);
connections[i].SetRecipientsDirty();
}
}
}
@@ -651,17 +662,29 @@ namespace Barotrauma.Items.Components
} while (removed);
}
private void FixNodeEnds()
public void FixNodeEnds()
{
if (connections[0] == null || connections[1] == null || nodes.Count == 0) { return; }
Item item0 = connections[0]?.Item;
Item item1 = connections[1]?.Item;
if (item0 == null && item1 != null)
{
item0 = Item.ItemList.Find(it => it.GetComponent<ConnectionPanel>()?.DisconnectedWires.Contains(this) ?? false);
}
else if (item0 != null && item1 == null)
{
item1 = Item.ItemList.Find(it => it.GetComponent<ConnectionPanel>()?.DisconnectedWires.Contains(this) ?? false);
}
if (item0 == null || item1 == null || nodes.Count == 0) { return; }
Vector2 nodePos = nodes[0];
Submarine refSub = connections[0].Item.Submarine ?? connections[1].Item.Submarine;
Submarine refSub = item0.Submarine ?? item1.Submarine;
if (refSub != null) { nodePos += refSub.HiddenSubPosition; }
float dist1 = Vector2.DistanceSquared(connections[0].Item.Position, nodePos);
float dist2 = Vector2.DistanceSquared(connections[1].Item.Position, nodePos);
float dist1 = Vector2.DistanceSquared(item0.Position, nodePos);
float dist2 = Vector2.DistanceSquared(item1.Position, nodePos);
//first node is closer to the second item
//= the nodes are "backwards", need to reverse them
@@ -721,6 +744,11 @@ namespace Barotrauma.Items.Components
public override void FlipX(bool relativeToSub)
{
if (item.ParentInventory != null) { return; }
#if CLIENT
if (!relativeToSub && Screen.Selected != GameMain.SubEditorScreen) { return; }
#else
if (!relativeToSub) { return; }
#endif
Vector2 refPos = item.Submarine == null ?
Vector2.Zero :
@@ -750,9 +778,9 @@ namespace Barotrauma.Items.Components
UpdateSections();
}
public override void Load(XElement componentElement, bool usePrefabValues)
public override void Load(XElement componentElement, bool usePrefabValues, IdRemap idRemap)
{
base.Load(componentElement, usePrefabValues);
base.Load(componentElement, usePrefabValues, idRemap);
string nodeString = componentElement.GetAttributeString("nodes", "");
if (nodeString == "") return;
@@ -76,7 +76,7 @@ namespace Barotrauma.Items.Components
set { launchImpulse = value; }
}
[Editable(0.0f, 1000.0f), Serialize(5.0f, false, description: "The period of time the user has to wait between shots.")]
[Editable(0.0f, 1000.0f, decimals: 3), Serialize(5.0f, false, description: "The period of time the user has to wait between shots.")]
public float Reload
{
get { return reloadTime; }
@@ -198,6 +198,7 @@ namespace Barotrauma.Items.Components
private set;
}
private float prevScale;
float prevBaseRotation;
[Serialize(0.0f, true, description: "The angle of the turret's base in degrees.", alwaysUseInstanceValues: true)]
public float BaseRotation
@@ -250,20 +251,17 @@ namespace Barotrauma.Items.Components
private void UpdateTransformedBarrelPos()
{
float flippedRotation = item.Rotation;
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);
transformedBarrelPos = MathUtils.RotatePointAroundTarget(barrelPos * item.Scale, new Vector2(item.Rect.Width / 2, item.Rect.Height / 2), item.Rotation);
#if CLIENT
item.ResetCachedVisibleSize();
#endif
item.Rotation = flippedRotation;
prevBaseRotation = item.Rotation;
prevScale = item.Scale;
}
public override void OnItemLoaded()
public override void OnMapLoaded()
{
base.OnItemLoaded();
base.OnMapLoaded();
var lightComponents = item.GetComponents<LightComponent>();
if (lightComponents != null && lightComponents.Count() > 0)
{
@@ -277,6 +275,9 @@ namespace Barotrauma.Items.Components
}
#endif
}
if (loadedRotationLimits.HasValue) { RotationLimits = loadedRotationLimits.Value; }
if (loadedBaseRotation.HasValue) { BaseRotation = loadedBaseRotation.Value; }
UpdateTransformedBarrelPos();
}
public override void Update(float deltaTime, Camera cam)
@@ -284,7 +285,7 @@ namespace Barotrauma.Items.Components
this.cam = cam;
if (reload > 0.0f) { reload -= deltaTime; }
if (!MathUtils.NearlyEqual(item.Rotation, prevBaseRotation))
if (!MathUtils.NearlyEqual(item.Rotation, prevBaseRotation) || !MathUtils.NearlyEqual(item.Scale, prevScale))
{
UpdateTransformedBarrelPos();
}
@@ -328,19 +329,42 @@ namespace Barotrauma.Items.Components
user.WorldPosition + Vector2.UnitY * 150.0f);
}
float rotMidDiff = MathHelper.WrapAngle(rotation - (minRotation + maxRotation) / 2.0f);
float targetRotationDiff = MathHelper.WrapAngle(targetRotation - rotation);
if ((maxRotation - minRotation) < MathHelper.TwoPi)
{
float targetRotationMaxDiff = MathHelper.WrapAngle(targetRotation - maxRotation);
float targetRotationMinDiff = MathHelper.WrapAngle(targetRotation - minRotation);
if (Math.Abs(targetRotationMaxDiff) < Math.Abs(targetRotationMinDiff) &&
rotMidDiff < 0.0f &&
targetRotationDiff < 0.0f)
{
targetRotationDiff += MathHelper.TwoPi;
}
else if (Math.Abs(targetRotationMaxDiff) > Math.Abs(targetRotationMinDiff) &&
rotMidDiff > 0.0f &&
targetRotationDiff > 0.0f)
{
targetRotationDiff -= MathHelper.TwoPi;
}
}
angularVelocity +=
(MathHelper.WrapAngle(targetRotation - rotation) * springStiffness - angularVelocity * springDamping) * deltaTime;
(targetRotationDiff * springStiffness - angularVelocity * springDamping) * deltaTime;
angularVelocity = MathHelper.Clamp(angularVelocity, -rotationSpeed, rotationSpeed);
rotation += angularVelocity * deltaTime;
float rotMidDiff = MathHelper.WrapAngle(rotation - (minRotation + maxRotation) / 2.0f);
rotMidDiff = MathHelper.WrapAngle(rotation - (minRotation + maxRotation) / 2.0f);
if (rotMidDiff < -maxDist)
{
rotation = minRotation;
angularVelocity *= -0.5f;
}
}
else if (rotMidDiff > maxDist)
{
rotation = maxRotation;
@@ -804,7 +828,7 @@ namespace Barotrauma.Items.Components
}
if (objective.SubObjectives.None())
{
var loadItemsObjective = AIContainItems<Turret>(container, character, objective, usableProjectileCount + 1, equip: true, removeEmpty: true);
var loadItemsObjective = AIContainItems<Turret>(container, character, objective, usableProjectileCount + 1, equip: true, removeEmpty: true, dropItemOnDeselected: true);
if (loadItemsObjective == null)
{
if (usableProjectileCount == 0)
@@ -976,6 +1000,8 @@ namespace Barotrauma.Items.Components
public override void FlipX(bool relativeToSub)
{
BaseRotation = MathHelper.ToDegrees(MathUtils.WrapAngleTwoPi(MathHelper.ToRadians(-BaseRotation)));
minRotation = MathHelper.Pi - minRotation;
maxRotation = MathHelper.Pi - maxRotation;
@@ -997,7 +1023,22 @@ namespace Barotrauma.Items.Components
public override void FlipY(bool relativeToSub)
{
BaseRotation = MathHelper.ToDegrees(MathUtils.WrapAngleTwoPi(MathHelper.ToRadians(BaseRotation - 180)));
BaseRotation = MathHelper.ToDegrees(MathUtils.WrapAngleTwoPi(MathHelper.ToRadians(180 - BaseRotation)));
minRotation = -minRotation;
maxRotation = -maxRotation;
var temp = minRotation;
minRotation = maxRotation;
maxRotation = temp;
while (minRotation < 0)
{
minRotation += MathHelper.TwoPi;
maxRotation += MathHelper.TwoPi;
}
rotation = (minRotation + maxRotation) / 2;
UpdateTransformedBarrelPos();
}
@@ -1041,6 +1082,25 @@ namespace Barotrauma.Items.Components
}
}
private Vector2? loadedRotationLimits;
private float? loadedBaseRotation;
public override void Load(XElement componentElement, bool usePrefabValues, IdRemap idRemap)
{
base.Load(componentElement, usePrefabValues, idRemap);
loadedRotationLimits = componentElement.GetAttributeVector2("rotationlimits", RotationLimits);
loadedBaseRotation = componentElement.GetAttributeFloat("baserotation", componentElement.Parent.GetAttributeFloat("rotation", BaseRotation));
}
public override void OnItemLoaded()
{
base.OnItemLoaded();
if (!loadedBaseRotation.HasValue)
{
if (item.FlippedX) { FlipX(relativeToSub: false); }
if (item.FlippedY) { FlipY(relativeToSub: false); }
}
}
public void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null)
{
if (extraData.Length > 2)
@@ -441,9 +441,9 @@ namespace Barotrauma.Items.Components
}
private int loadedVariant = -1;
public override void Load(XElement componentElement, bool usePrefabValues)
public override void Load(XElement componentElement, bool usePrefabValues, IdRemap idRemap)
{
base.Load(componentElement, usePrefabValues);
base.Load(componentElement, usePrefabValues, idRemap);
loadedVariant = componentElement.GetAttributeInt("variant", -1);
}
public override void OnItemLoaded()