v0.10.5.1
This commit is contained in:
@@ -209,15 +209,17 @@ namespace Barotrauma.Items.Components
|
||||
#if SERVER
|
||||
if (GameMain.Server != null && (!item.Submarine?.Loading ?? true))
|
||||
{
|
||||
originalDockingTargetID = DockingTarget.item.ID;
|
||||
item.CreateServerEvent(this);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
public void Lock(bool isNetworkMessage, bool forcePosition = false)
|
||||
{
|
||||
#if CLIENT
|
||||
if (GameMain.Client != null && !isNetworkMessage) return;
|
||||
if (GameMain.Client != null && !isNetworkMessage) { return; }
|
||||
#endif
|
||||
|
||||
if (DockingTarget == null)
|
||||
@@ -251,6 +253,7 @@ namespace Barotrauma.Items.Components
|
||||
#if SERVER
|
||||
if (GameMain.Server != null && (!item.Submarine?.Loading ?? true))
|
||||
{
|
||||
originalDockingTargetID = DockingTarget.item.ID;
|
||||
item.CreateServerEvent(this);
|
||||
}
|
||||
#else
|
||||
@@ -332,20 +335,45 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (DockingDir != 0) { return DockingDir; }
|
||||
|
||||
if (Door != null)
|
||||
if (Door != null && Door.LinkedGap.linkedTo.Count > 0)
|
||||
{
|
||||
if (Door.LinkedGap.linkedTo.Count == 1)
|
||||
Hull refHull = null;
|
||||
float largestHullSize = 0.0f;
|
||||
foreach (MapEntity linked in Door.LinkedGap.linkedTo)
|
||||
{
|
||||
if (!(linked is Hull hull)) { continue; }
|
||||
if (hull.Volume > largestHullSize)
|
||||
{
|
||||
refHull = hull;
|
||||
largestHullSize = hull.Volume;
|
||||
}
|
||||
}
|
||||
if (refHull != null)
|
||||
{
|
||||
return IsHorizontal ?
|
||||
Math.Sign(Door.Item.WorldPosition.X - Door.LinkedGap.linkedTo[0].WorldPosition.X) :
|
||||
Math.Sign(Door.Item.WorldPosition.Y - Door.LinkedGap.linkedTo[0].WorldPosition.Y);
|
||||
Math.Sign(Door.Item.WorldPosition.X - refHull.WorldPosition.X) :
|
||||
Math.Sign(Door.Item.WorldPosition.Y - refHull.WorldPosition.Y);
|
||||
}
|
||||
else if (dockingTarget?.Door?.LinkedGap != null && dockingTarget.Door.LinkedGap.linkedTo.Count == 1)
|
||||
}
|
||||
if (dockingTarget?.Door?.LinkedGap != null && dockingTarget.Door.LinkedGap.linkedTo.Count > 0)
|
||||
{
|
||||
Hull refHull = null;
|
||||
float largestHullSize = 0.0f;
|
||||
foreach (MapEntity linked in dockingTarget.Door.LinkedGap.linkedTo)
|
||||
{
|
||||
if (!(linked is Hull hull)) { continue; }
|
||||
if (hull.Volume > largestHullSize)
|
||||
{
|
||||
refHull = hull;
|
||||
largestHullSize = hull.Volume;
|
||||
}
|
||||
}
|
||||
if (refHull != null)
|
||||
{
|
||||
return IsHorizontal ?
|
||||
Math.Sign(dockingTarget.Door.LinkedGap.linkedTo[0].WorldPosition.X - dockingTarget.Door.Item.WorldPosition.X) :
|
||||
Math.Sign(dockingTarget.Door.LinkedGap.linkedTo[0].WorldPosition.Y - dockingTarget.Door.Item.WorldPosition.Y);
|
||||
}
|
||||
Math.Sign(refHull.WorldPosition.X - dockingTarget.Door.Item.WorldPosition.X) :
|
||||
Math.Sign(refHull.WorldPosition.Y - dockingTarget.Door.Item.WorldPosition.Y);
|
||||
}
|
||||
}
|
||||
if (dockingTarget != null)
|
||||
{
|
||||
@@ -838,6 +866,7 @@ namespace Barotrauma.Items.Components
|
||||
#if SERVER
|
||||
if (GameMain.Server != null && (!item.Submarine?.Loading ?? true))
|
||||
{
|
||||
originalDockingTargetID = Entity.NullEntityID;
|
||||
item.CreateServerEvent(this);
|
||||
}
|
||||
#endif
|
||||
@@ -1010,9 +1039,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 CLIENT
|
||||
if (GameMain.Client != null) return;
|
||||
#endif
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
|
||||
|
||||
bool wasDocked = docked;
|
||||
DockingPort prevDockingTarget = DockingTarget;
|
||||
@@ -1020,7 +1047,10 @@ namespace Barotrauma.Items.Components
|
||||
switch (connection.Name)
|
||||
{
|
||||
case "toggle":
|
||||
Docked = !docked;
|
||||
if (signal != "0")
|
||||
{
|
||||
Docked = !docked;
|
||||
}
|
||||
break;
|
||||
case "set_active":
|
||||
case "set_state":
|
||||
@@ -1044,16 +1074,5 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
public void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null)
|
||||
{
|
||||
msg.Write(docked);
|
||||
|
||||
if (docked)
|
||||
{
|
||||
msg.Write(DockingTarget.item.ID);
|
||||
msg.Write(hulls != null && hulls[0] != null && hulls[1] != null && gap != null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,8 +90,10 @@ namespace Barotrauma.Items.Components
|
||||
get { return stuck; }
|
||||
set
|
||||
{
|
||||
if (isOpen || isBroken || !CanBeWelded) return;
|
||||
if (isOpen || isBroken || !CanBeWelded) { return; }
|
||||
stuck = MathHelper.Clamp(value, 0.0f, 100.0f);
|
||||
//don't allow clients to make the door stuck unless the server says so (handled in ClientRead)
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
|
||||
if (stuck <= 0.0f) { IsStuck = false; }
|
||||
if (stuck >= 99.0f) { IsStuck = true; }
|
||||
}
|
||||
@@ -366,12 +368,24 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
else
|
||||
{
|
||||
Body.Enabled = Impassable || openState < 1.0f;
|
||||
bool wasEnabled = Body.Enabled;
|
||||
Body.Enabled = Impassable || openState < 1.0f;
|
||||
if (wasEnabled && !Body.Enabled && IsHorizontal)
|
||||
{
|
||||
//when opening a hatch, force characters above it to refresh the floor position
|
||||
//(otherwise the character won't fall through the hatch until it moves)
|
||||
foreach (Character c in Character.CharacterList)
|
||||
{
|
||||
if (c.WorldPosition.Y < item.WorldPosition.Y) { continue; }
|
||||
if (c.WorldPosition.X < item.WorldRect.X || c.WorldPosition.X > item.WorldRect.Right) { continue; }
|
||||
c.AnimController?.ForceRefreshFloorY();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//don't use the predicted state here, because it might set
|
||||
//other items to an incorrect state if the prediction is wrong
|
||||
item.SendSignal(0, (isOpen) ? "1" : "0", "state_out", null);
|
||||
item.SendSignal(0, isOpen ? "1" : "0", "state_out", null);
|
||||
}
|
||||
|
||||
partial void UpdateProjSpecific(float deltaTime);
|
||||
@@ -616,12 +630,13 @@ 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) { return; }
|
||||
|
||||
bool wasOpen = PredictedState == null ? isOpen : PredictedState.Value;
|
||||
|
||||
if (connection.Name == "toggle")
|
||||
{
|
||||
if (signal == "0") { return; }
|
||||
if (toggleCooldownTimer > 0.0f && sender != lastUser) { OnFailedToOpen(); return; }
|
||||
if (IsStuck) { toggleCooldownTimer = 1.0f; OnFailedToOpen(); return; }
|
||||
toggleCooldownTimer = ToggleCoolDown;
|
||||
|
||||
@@ -0,0 +1,832 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Networking;
|
||||
using FarseerPhysics;
|
||||
using FarseerPhysics.Dynamics;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Vector2 = Microsoft.Xna.Framework.Vector2;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
internal class ProducedItem
|
||||
{
|
||||
[Serialize(0f, true)]
|
||||
public float Probability { get; set; }
|
||||
|
||||
public readonly List<StatusEffect> StatusEffects = new List<StatusEffect>();
|
||||
|
||||
public readonly ItemPrefab? Prefab;
|
||||
|
||||
public ProducedItem(ItemPrefab prefab, float probability)
|
||||
{
|
||||
Prefab = prefab;
|
||||
Probability = probability;
|
||||
}
|
||||
|
||||
public ProducedItem(XElement element)
|
||||
{
|
||||
SerializableProperty.DeserializeProperties(this, element);
|
||||
|
||||
string itemIdentifier = element.GetAttributeString("identifier", string.Empty);
|
||||
if (!string.IsNullOrWhiteSpace(itemIdentifier))
|
||||
{
|
||||
Prefab = ItemPrefab.Find(null, itemIdentifier);
|
||||
}
|
||||
|
||||
LoadSubElements(element);
|
||||
}
|
||||
|
||||
private void LoadSubElements(XElement element)
|
||||
{
|
||||
if (!element.HasElements) { return; }
|
||||
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "statuseffect":
|
||||
{
|
||||
StatusEffect effect = StatusEffect.Load(subElement, Prefab?.Name);
|
||||
if (effect.type != ActionType.OnProduceSpawned)
|
||||
{
|
||||
DebugConsole.ThrowError("Only OnProduceSpawned type can be used in <ProducedItem>.");
|
||||
continue;
|
||||
}
|
||||
|
||||
StatusEffects.Add(effect);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ReSharper disable UnusedMember.Global
|
||||
internal enum VineTileType
|
||||
{
|
||||
Stem = 0b0000,
|
||||
CrossJunction = 0b1111,
|
||||
VerticalLane = 0b1010,
|
||||
HorizontalLane = 0b0101,
|
||||
TurnTopRight = 0b1001,
|
||||
TurnTopLeft = 0b0011,
|
||||
TurnBottomLeft = 0b0110,
|
||||
TurnBottomRight = 0b1100,
|
||||
TSectionTop = 0b1011,
|
||||
TSectionLeft = 0b0111,
|
||||
TSectionBottom = 0b1110,
|
||||
TSectionRight = 0b1101,
|
||||
StumpTop = 0b0001,
|
||||
StumpLeft = 0b0010,
|
||||
StumpBottom = 0b0100,
|
||||
StumpRight = 0b1000
|
||||
}
|
||||
|
||||
[Flags]
|
||||
internal enum TileSide
|
||||
{
|
||||
None = 0,
|
||||
Top = 1 << 0,
|
||||
Left = 1 << 1,
|
||||
Bottom = 1 << 2,
|
||||
Right = 1 << 3
|
||||
}
|
||||
|
||||
internal struct FoliageConfig
|
||||
{
|
||||
public static FoliageConfig EmptyConfig = new FoliageConfig { Variant = -1, Rotation = 0f, Scale = 1.0f };
|
||||
public static readonly int EmptyConfigValue = EmptyConfig.Serialize();
|
||||
|
||||
public int Variant;
|
||||
public float Rotation;
|
||||
public float Scale;
|
||||
|
||||
public readonly int Serialize()
|
||||
{
|
||||
int variant = Math.Min(Variant + 1, 15);
|
||||
int scale = (int) (Scale * 10f);
|
||||
int rotation = (int) (Rotation / MathHelper.TwoPi * 10f);
|
||||
|
||||
return variant | (scale << 4) | (rotation << 8);
|
||||
}
|
||||
|
||||
public static FoliageConfig Deserialize(int value)
|
||||
{
|
||||
int variant = value & 0x00F;
|
||||
int scale = (value & 0x0F0) >> 4;
|
||||
int rotation = (value & 0xF00) >> 8;
|
||||
|
||||
return new FoliageConfig { Variant = variant - 1, Scale = scale / 10f, Rotation = rotation / 10f * MathHelper.TwoPi };
|
||||
}
|
||||
|
||||
public static FoliageConfig CreateRandomConfig(int maxVariants, float minScale, float maxScale, Random? random = null)
|
||||
{
|
||||
int flowerVariant = Growable.RandomInt(0, maxVariants, random);
|
||||
float flowerScale = (float) Growable.RandomDouble(minScale, maxScale, random);
|
||||
float flowerRotation = (float) Growable.RandomDouble(0, MathHelper.TwoPi, random);
|
||||
return new FoliageConfig { Variant = flowerVariant, Scale = flowerScale, Rotation = flowerRotation };
|
||||
}
|
||||
}
|
||||
|
||||
internal partial class VineTile
|
||||
{
|
||||
public TileSide Sides = TileSide.None;
|
||||
public TileSide BlockedSides = TileSide.None;
|
||||
|
||||
public readonly FoliageConfig FlowerConfig;
|
||||
public readonly 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 float growthStep;
|
||||
|
||||
public float GrowthStep
|
||||
{
|
||||
get => growthStep;
|
||||
set
|
||||
{
|
||||
const float limit = 1.0f;
|
||||
growthStep = value;
|
||||
VineStep = Math.Min((float) Math.Pow(value, 2), limit);
|
||||
if (value > limit)
|
||||
{
|
||||
FlowerStep = Math.Min((float) Math.Pow(value - limit, 2), limit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private readonly float diameter;
|
||||
private Vector2 offset;
|
||||
|
||||
private readonly Growable Parent;
|
||||
public VineTileType Type;
|
||||
|
||||
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)
|
||||
{
|
||||
FlowerConfig = flowerConfig ?? FoliageConfig.EmptyConfig;
|
||||
LeafConfig = leafConfig ?? FoliageConfig.EmptyConfig;
|
||||
Position = position;
|
||||
Rect = rect ?? CreatePlantRect(position);
|
||||
Parent = parent;
|
||||
Type = type;
|
||||
diameter = Rect.Width / 2.0f;
|
||||
|
||||
AdjacentPositions = new Dictionary<TileSide, Vector2>
|
||||
{
|
||||
{ TileSide.Top, new Vector2(Position.X, Position.Y + Rect.Height) },
|
||||
{ TileSide.Bottom, new Vector2(Position.X, Position.Y - Rect.Height) },
|
||||
{ TileSide.Left, new Vector2(Position.X - Rect.Width, Position.Y) },
|
||||
{ TileSide.Right, new Vector2(Position.X + Rect.Width, Position.Y) }
|
||||
};
|
||||
}
|
||||
|
||||
public void UpdateScale(float deltaTime)
|
||||
{
|
||||
if (Parent.Decayed && GrowthStep > 1.0f)
|
||||
{
|
||||
if (DecayDelay > 0)
|
||||
{
|
||||
DecayDelay -= deltaTime;
|
||||
}
|
||||
else
|
||||
{
|
||||
GrowthStep -= 0.25f * deltaTime;
|
||||
}
|
||||
}
|
||||
|
||||
if (GrowthStep >= 2.0f || Parent.Decayed) { return; }
|
||||
|
||||
GrowthStep += deltaTime;
|
||||
|
||||
if (GrowthStep < 1.0f)
|
||||
{
|
||||
// I don't know how or why this works
|
||||
float offsetAmount = diameter * VineStep - diameter;
|
||||
switch (Type)
|
||||
{
|
||||
case VineTileType.StumpLeft:
|
||||
offset.X = offsetAmount;
|
||||
break;
|
||||
case VineTileType.StumpRight:
|
||||
offset.X = -offsetAmount;
|
||||
break;
|
||||
case VineTileType.StumpTop:
|
||||
offset.Y = offsetAmount;
|
||||
break;
|
||||
case VineTileType.Stem:
|
||||
case VineTileType.StumpBottom:
|
||||
offset.Y = -offsetAmount;
|
||||
break;
|
||||
default:
|
||||
offset = Vector2.Zero;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
offset = Vector2.Zero;
|
||||
}
|
||||
}
|
||||
|
||||
public Vector2 GetWorldPosition(Planter planter, Vector2 slotOffset)
|
||||
{
|
||||
return planter.Item.WorldPosition + slotOffset + Position;
|
||||
}
|
||||
|
||||
public void UpdateType()
|
||||
{
|
||||
if (Type == VineTileType.Stem) { return; }
|
||||
|
||||
Type = (VineTileType) Sides;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a random side that is not occupied.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// There is probably a much better way of doing this than allocating memory with an array
|
||||
/// but this felt like the most reliable approach I could come up with.
|
||||
/// </remarks>
|
||||
/// <returns></returns>
|
||||
public TileSide GetRandomFreeSide(Random? random = null)
|
||||
{
|
||||
const int maxSides = 4;
|
||||
TileSide occupiedSides = Sides | BlockedSides;
|
||||
int setBits = occupiedSides.Count();
|
||||
if (setBits >= maxSides) { return TileSide.None; }
|
||||
|
||||
int possible = maxSides - setBits;
|
||||
int[] pool = new int[possible];
|
||||
|
||||
for (int i = 0, j = 0; i < maxSides; i++)
|
||||
{
|
||||
if (!occupiedSides.IsBitSet((TileSide) (1 << i)))
|
||||
{
|
||||
pool[j] = i;
|
||||
j++;
|
||||
}
|
||||
}
|
||||
|
||||
int value = pool[Growable.RandomInt(0, possible, random)];
|
||||
|
||||
return (TileSide) (1 << value);
|
||||
}
|
||||
|
||||
public bool CanGrowMore() => (Sides | BlockedSides).Count() < 4;
|
||||
|
||||
public static Rectangle CreatePlantRect(Vector2 pos) => new Rectangle((int) pos.X - Size / 2, (int) pos.Y + Size / 2, Size, Size);
|
||||
}
|
||||
|
||||
internal static class GrowthSideExtension
|
||||
{
|
||||
// Enum.HasFlag() sucks
|
||||
public static bool IsBitSet(this TileSide side, TileSide bit)
|
||||
{
|
||||
return ((int) side & (int) bit) != 0;
|
||||
}
|
||||
|
||||
// K&R algorithm for counting how many bits are set in a bit field
|
||||
public static int Count(this TileSide side)
|
||||
{
|
||||
int n = (int) side;
|
||||
int count = 0;
|
||||
while (n != 0)
|
||||
{
|
||||
count += n & 1;
|
||||
n >>= 1;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
}
|
||||
|
||||
internal partial class Growable : ItemComponent, IServerSerializable
|
||||
{
|
||||
// used for debugging where a vine failed to grow
|
||||
public readonly HashSet<Rectangle> FailedRectangles = new HashSet<Rectangle>();
|
||||
|
||||
[Serialize(1f, true, "How fast the plant grows.")]
|
||||
public float GrowthSpeed { get; set; }
|
||||
|
||||
[Serialize(100f, true, "How long the plant can go without watering.")]
|
||||
public float MaxHealth { get; set; }
|
||||
|
||||
[Serialize(1f, true, "How much damage the plant takes while in water.")]
|
||||
public float FloodTolerance { get; set; }
|
||||
|
||||
[Serialize(1f, true, "How much damage the plant takes while growing.")]
|
||||
public float Hardiness { get; set; }
|
||||
|
||||
[Serialize(0.01f, true, "How often a seed is produced.")]
|
||||
public float SeedRate { get; set; }
|
||||
|
||||
[Serialize(0.01f, true, "How often a product item is produced.")]
|
||||
public float ProductRate { get; set; }
|
||||
|
||||
[Serialize(0.5f, true, "Probability of an attribute being randomly modified in a newly produced seed.")]
|
||||
public float MutationProbability { get; set; }
|
||||
|
||||
[Serialize("1.0,1.0,1.0,1.0", true, "Color of the flowers.")]
|
||||
public Color FlowerTint { get; set; }
|
||||
|
||||
[Serialize(3, true, "Number of flowers drawn when fully grown")]
|
||||
public int FlowerQuantity { get; set; }
|
||||
|
||||
[Serialize(0.25f, true, "Size of the flower sprites.")]
|
||||
public float BaseFlowerScale { get; set; }
|
||||
|
||||
[Serialize(0.5f, true, "Size of the leaf sprites.")]
|
||||
public float BaseLeafScale { get; set; }
|
||||
|
||||
[Serialize("1.0,1.0,1.0,1.0", true, "Color of the leaves.")]
|
||||
public Color LeafTint { get; set; }
|
||||
|
||||
[Serialize(0.33f, true, "Chance of a leaf appearing behind a branch.")]
|
||||
public float LeafProbability { get; set; }
|
||||
|
||||
[Serialize("1.0,1.0,1.0,1.0", true, "Color of the vines.")]
|
||||
public Color VineTint { get; set; }
|
||||
|
||||
[Serialize(32, true, "Maximum number of vine tiles the plant can grow.")]
|
||||
public int MaximumVines { get; set; }
|
||||
|
||||
[Serialize(0.25f, true, "Size of the vine sprites.")]
|
||||
public float VineScale { get; set; }
|
||||
|
||||
[Serialize("0.26,0.27,0.29,1.0", true, "Tint of a dead plant.")]
|
||||
public Color DeadTint { get; set; }
|
||||
|
||||
private const float increasedDeathSpeed = 10f;
|
||||
private bool accelerateDeath;
|
||||
private float health;
|
||||
private int flowerVariants;
|
||||
private int leafVariants;
|
||||
private int[] flowerTiles;
|
||||
|
||||
public float Health
|
||||
{
|
||||
get => health;
|
||||
set => health = Math.Clamp(value, 0, MaxHealth);
|
||||
}
|
||||
|
||||
public bool Decayed;
|
||||
public bool FullyGrown;
|
||||
|
||||
private const int maxProductDelay = 10,
|
||||
maxVineGrowthDelay = 10;
|
||||
|
||||
private int productDelay;
|
||||
private int vineDelay;
|
||||
|
||||
public readonly List<ProducedItem> ProducedItems = new List<ProducedItem>();
|
||||
public readonly List<VineTile> Vines = new List<VineTile>();
|
||||
private readonly ProducedItem ProducedSeed;
|
||||
|
||||
private static float MinFlowerScale = 0.5f, MaxFlowerScale = 1.0f, MinLeafScale = 0.5f, MaxLeafScale = 1.0f;
|
||||
private const int VineChunkSize = 32;
|
||||
|
||||
public Growable(Item item, XElement element) : base(item, element)
|
||||
{
|
||||
SerializableProperty.DeserializeProperties(this, element);
|
||||
|
||||
Health = MaxHealth;
|
||||
|
||||
if (element.HasElements)
|
||||
{
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "produceditem":
|
||||
ProducedItems.Add(new ProducedItem(subElement));
|
||||
break;
|
||||
case "vinesprites":
|
||||
LoadVines(subElement);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ProducedSeed = new ProducedItem(this.item.Prefab, 1.0f);
|
||||
flowerTiles = new int[FlowerQuantity];
|
||||
}
|
||||
|
||||
public override void OnItemLoaded()
|
||||
{
|
||||
base.OnItemLoaded();
|
||||
if (flowerTiles.All(i => i == 0))
|
||||
{
|
||||
GenerateFlowerTiles();
|
||||
}
|
||||
}
|
||||
|
||||
private void GenerateFlowerTiles(Random? random = null)
|
||||
{
|
||||
flowerTiles = new int[FlowerQuantity];
|
||||
List<int> pool = new List<int>();
|
||||
for (int i = 0; i < MaximumVines - 1; i++) { pool.Add(i); }
|
||||
|
||||
for (int i = 0; i < flowerTiles.Length; i++)
|
||||
{
|
||||
int index = RandomInt(0, pool.Count, random);
|
||||
flowerTiles[i] = pool[index];
|
||||
pool.RemoveAt(index);
|
||||
}
|
||||
}
|
||||
|
||||
partial void LoadVines(XElement element);
|
||||
|
||||
public void OnGrowthTick(Planter planter, PlantSlot slot)
|
||||
{
|
||||
if (Decayed) { return; }
|
||||
|
||||
if (FullyGrown)
|
||||
{
|
||||
TryGenerateProduct(planter, slot);
|
||||
}
|
||||
|
||||
if (Health > 0)
|
||||
{
|
||||
GrowVines(planter, slot);
|
||||
Health -= accelerateDeath ? Hardiness * increasedDeathSpeed : Hardiness;
|
||||
|
||||
if (planter.Item.InWater)
|
||||
{
|
||||
Health -= FloodTolerance;
|
||||
}
|
||||
}
|
||||
|
||||
CheckPlantState();
|
||||
|
||||
#if CLIENT
|
||||
UpdateBranchHealth();
|
||||
#endif
|
||||
}
|
||||
|
||||
private void UpdateBranchHealth()
|
||||
{
|
||||
Color healthColor = Color.White * (1.0f - Health / MaxHealth);
|
||||
foreach (VineTile vine in Vines)
|
||||
{
|
||||
vine.HealthColor = healthColor;
|
||||
}
|
||||
}
|
||||
|
||||
private void TryGenerateProduct(Planter planter, PlantSlot slot)
|
||||
{
|
||||
productDelay++;
|
||||
if (productDelay <= maxProductDelay) { return; }
|
||||
|
||||
productDelay = 0;
|
||||
|
||||
bool spawnProduct = Rand.Range(0f, 1f, Rand.RandSync.Unsynced) < ProductRate,
|
||||
spawnSeed = Rand.Range(0f, 1f, Rand.RandSync.Unsynced) < SeedRate;
|
||||
|
||||
Vector2 spawnPos;
|
||||
|
||||
if (spawnProduct || spawnSeed)
|
||||
{
|
||||
VineTile vine = Vines.GetRandom();
|
||||
spawnPos = vine.GetWorldPosition(planter, slot.Offset);
|
||||
}
|
||||
else
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (spawnProduct && ProducedItems.Any())
|
||||
{
|
||||
SpawnItem(ProducedItems.RandomElementByWeight(it => it.Probability), spawnPos);
|
||||
return;
|
||||
}
|
||||
|
||||
if (spawnSeed)
|
||||
{
|
||||
SpawnItem(ProducedSeed, spawnPos);
|
||||
}
|
||||
|
||||
static void SpawnItem(ProducedItem producedItem, Vector2 pos)
|
||||
{
|
||||
if (producedItem.Prefab == null) { return; }
|
||||
|
||||
Entity.Spawner?.AddToSpawnQueue(producedItem.Prefab, pos, onSpawned: it =>
|
||||
{
|
||||
foreach (StatusEffect effect in producedItem.StatusEffects)
|
||||
{
|
||||
it.ApplyStatusEffect(effect, ActionType.OnProduceSpawned, 1.0f, isNetworkEvent: true);
|
||||
}
|
||||
|
||||
it.ApplyStatusEffects(ActionType.OnProduceSpawned, 1.0f, isNetworkEvent: true);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates plant's state to fully grown or dead depending on its conditions.
|
||||
/// </summary>
|
||||
/// <returns>True if the plant has finished growing.</returns>
|
||||
private bool CheckPlantState()
|
||||
{
|
||||
if (Decayed) { return true; }
|
||||
|
||||
if (0 >= Health)
|
||||
{
|
||||
Decayed = true;
|
||||
#if CLIENT
|
||||
foreach (VineTile vine in Vines)
|
||||
{
|
||||
vine.DecayDelay = (float) RandomDouble(0f, 30f);
|
||||
}
|
||||
#endif
|
||||
#if SERVER
|
||||
item.CreateServerEvent(this);
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
if (Vines.Count >= MaximumVines && !FullyGrown)
|
||||
{
|
||||
FullyGrown = true;
|
||||
#if SERVER
|
||||
item.CreateServerEvent(this);
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!FullyGrown && !accelerateDeath && Vines.Any() && Vines.All(tile => !tile.CanGrowMore()))
|
||||
{
|
||||
accelerateDeath = true;
|
||||
}
|
||||
|
||||
// if the player somehow finds a way to extract the seed out of a planter kill the plant
|
||||
if (item.ParentInventory is CharacterInventory)
|
||||
{
|
||||
Decayed = true;
|
||||
#if SERVER
|
||||
item.CreateServerEvent(this);
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
base.Update(deltaTime, cam);
|
||||
|
||||
#if CLIENT
|
||||
foreach (VineTile vine in Vines)
|
||||
{
|
||||
vine.UpdateScale(deltaTime);
|
||||
}
|
||||
#endif
|
||||
|
||||
CheckPlantState();
|
||||
}
|
||||
|
||||
private void GrowVines(Planter planter, PlantSlot slot)
|
||||
{
|
||||
if (FullyGrown) { return; }
|
||||
|
||||
vineDelay++;
|
||||
if (vineDelay <= maxVineGrowthDelay / GrowthSpeed) { return; }
|
||||
|
||||
vineDelay = 0;
|
||||
|
||||
if (!Vines.Any())
|
||||
{
|
||||
// generate first stem
|
||||
GenerateStem();
|
||||
return;
|
||||
}
|
||||
|
||||
int count = Vines.Count;
|
||||
|
||||
TryGenerateBranches(planter, slot);
|
||||
|
||||
if (Vines.Count > count)
|
||||
{
|
||||
#if SERVER
|
||||
for (int i = 0; i < Vines.Count; i += VineChunkSize)
|
||||
{
|
||||
GameMain.Server.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.ComponentState, item.GetComponentIndex(this), i });
|
||||
}
|
||||
#elif CLIENT
|
||||
ResetPlanterSize();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
private void GenerateStem()
|
||||
{
|
||||
VineTile stem = new VineTile(this, Vector2.Zero, VineTileType.Stem) { BlockedSides = TileSide.Bottom | TileSide.Left | TileSide.Right };
|
||||
Vines.Add(stem);
|
||||
}
|
||||
|
||||
private void TryGenerateBranches(Planter planter, PlantSlot slot, Random? random = null, Random? flowerRandom = null)
|
||||
{
|
||||
List<VineTile> newList = new List<VineTile>(Vines);
|
||||
foreach (VineTile oldVines in newList)
|
||||
{
|
||||
if (oldVines.FailedGrowthAttempts > 8 || !oldVines.CanGrowMore()) { continue; }
|
||||
|
||||
if (RandomInt(0, Vines.Count(tile => tile.CanGrowMore()), random) != 0) { continue; }
|
||||
|
||||
TileSide side = oldVines.GetRandomFreeSide(random);
|
||||
|
||||
if (side == TileSide.None) { continue; }
|
||||
|
||||
Vector2 pos = oldVines.AdjacentPositions[side];
|
||||
Rectangle rect = VineTile.CreatePlantRect(pos);
|
||||
|
||||
if (CollidesWithWorld(rect, planter, slot))
|
||||
{
|
||||
oldVines.BlockedSides |= side;
|
||||
oldVines.FailedGrowthAttempts++;
|
||||
continue;
|
||||
}
|
||||
|
||||
FoliageConfig flowerConfig = FoliageConfig.EmptyConfig;
|
||||
FoliageConfig leafConfig = FoliageConfig.EmptyConfig;
|
||||
|
||||
if (flowerTiles.Any(i => Vines.Count == i))
|
||||
{
|
||||
flowerConfig = FoliageConfig.CreateRandomConfig(flowerVariants, MinFlowerScale, MaxFlowerScale, flowerRandom);
|
||||
}
|
||||
|
||||
if (LeafProbability >= RandomDouble(0d, 1.0d, flowerRandom) && leafVariants > 0)
|
||||
{
|
||||
leafConfig = FoliageConfig.CreateRandomConfig(leafVariants, MinLeafScale, MaxLeafScale, flowerRandom);
|
||||
}
|
||||
|
||||
VineTile newVine = new VineTile(this, pos, VineTileType.CrossJunction, flowerConfig, leafConfig, rect);
|
||||
|
||||
foreach (VineTile otherVine in Vines)
|
||||
{
|
||||
var (distX, distY) = pos - otherVine.Position;
|
||||
int absDistX = (int) Math.Abs(distX), absDistY = (int) Math.Abs(distY);
|
||||
|
||||
// check if the tile is within the with or height distance from us but ignore diagonals
|
||||
if (absDistX > newVine.Rect.Width || absDistY > newVine.Rect.Height || absDistX > 0 && absDistY > 0) { continue; }
|
||||
|
||||
// determines what side the tile is relative to the new tile by comparing the X/Y distance values
|
||||
// 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);
|
||||
|
||||
if (otherVine.BlockedSides.IsBitSet(connectingSide))
|
||||
{
|
||||
newVine.BlockedSides |= oppositeSide;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (otherVine != oldVines)
|
||||
{
|
||||
otherVine.BlockedSides |= connectingSide;
|
||||
newVine.BlockedSides |= oppositeSide;
|
||||
}
|
||||
else
|
||||
{
|
||||
otherVine.Sides |= connectingSide;
|
||||
newVine.Sides |= oppositeSide;
|
||||
}
|
||||
}
|
||||
|
||||
Vines.Add(newVine);
|
||||
|
||||
foreach (VineTile vine in Vines)
|
||||
{
|
||||
vine.UpdateType();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool CollidesWithWorld(Rectangle rect, Planter planter, PlantSlot slot)
|
||||
{
|
||||
if (Vines.Any(g => g.Rect.Contains(rect))) { return true; }
|
||||
|
||||
Rectangle worldRect = rect;
|
||||
worldRect.Location = planter.Item.WorldPosition.ToPoint() + slot.Offset.ToPoint() + worldRect.Location;
|
||||
worldRect.Y -= worldRect.Height;
|
||||
|
||||
Rectangle planterRect = planter.Item.WorldRect;
|
||||
planterRect.Y -= planterRect.Height;
|
||||
|
||||
if (planterRect.Intersects(worldRect))
|
||||
{
|
||||
#if DEBUG
|
||||
if (!FailedRectangles.Contains(worldRect))
|
||||
{
|
||||
FailedRectangles.Add(worldRect);
|
||||
}
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
Vector2 topLeft = ConvertUnits.ToSimUnits(new Vector2(worldRect.Left, worldRect.Top)),
|
||||
topRight = ConvertUnits.ToSimUnits(new Vector2(worldRect.Right, worldRect.Top)),
|
||||
bottomLeft = ConvertUnits.ToSimUnits(new Vector2(worldRect.Left, worldRect.Bottom)),
|
||||
bottomRight = ConvertUnits.ToSimUnits(new Vector2(worldRect.Right, worldRect.Bottom));
|
||||
|
||||
// ray casting a cross on the corners didn't seem to work so we are ray casting along the perimeter
|
||||
bool hasCollision = planterRect.Intersects(worldRect) || LineCollides(topLeft, topRight) || LineCollides(topRight, bottomRight) || LineCollides(bottomRight, bottomLeft) || LineCollides(bottomLeft, topLeft);
|
||||
|
||||
#if DEBUG
|
||||
if (hasCollision)
|
||||
{
|
||||
if (!FailedRectangles.Contains(worldRect))
|
||||
{
|
||||
FailedRectangles.Add(worldRect);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return hasCollision;
|
||||
|
||||
static bool LineCollides(Vector2 point1, Vector2 point2)
|
||||
{
|
||||
const Category category = Physics.CollisionWall | Physics.CollisionCharacter | Physics.CollisionItem | Physics.CollisionLevel;
|
||||
return Submarine.PickBody(point1, point2, collisionCategory: category, customPredicate: f => !(f.UserData is Hull) && f.CollidesWith.HasFlag(Physics.CollisionItem)) != null;
|
||||
}
|
||||
}
|
||||
|
||||
public override XElement Save(XElement parentElement)
|
||||
{
|
||||
XElement element = base.Save(parentElement);
|
||||
element.Add(new XAttribute("flowertiles", string.Join(",", flowerTiles)));
|
||||
element.Add(new XAttribute("decayed", Decayed));
|
||||
foreach (VineTile vine in Vines)
|
||||
{
|
||||
XElement vineElement = new XElement("Vine");
|
||||
vineElement.Add(new XAttribute("sides", (int) vine.Sides));
|
||||
vineElement.Add(new XAttribute("blockedsides", (int) vine.BlockedSides));
|
||||
vineElement.Add(new XAttribute("pos", XMLExtensions.Vector2ToString(vine.Position)));
|
||||
vineElement.Add(new XAttribute("tile", (int) vine.Type));
|
||||
vineElement.Add(new XAttribute("failedattempts", vine.FailedGrowthAttempts));
|
||||
#if SERVER
|
||||
vineElement.Add(new XAttribute("growthscale", Decayed ? 1.0f : 2.0f));
|
||||
#else
|
||||
vineElement.Add(new XAttribute("growthscale", vine.GrowthStep));
|
||||
#endif
|
||||
vineElement.Add(new XAttribute("flowerconfig", vine.FlowerConfig.Serialize()));
|
||||
vineElement.Add(new XAttribute("leafconfig", vine.LeafConfig.Serialize()));
|
||||
|
||||
element.Add(vineElement);
|
||||
}
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
public override void Load(XElement componentElement, bool usePrefabValues)
|
||||
{
|
||||
base.Load(componentElement, usePrefabValues);
|
||||
flowerTiles = componentElement.GetAttributeIntArray("flowertiles", new int[0]);
|
||||
Decayed = componentElement.GetAttributeBool("decayed", false);
|
||||
|
||||
Vines.Clear();
|
||||
foreach (XElement element in componentElement.Elements())
|
||||
{
|
||||
if (element.Name.ToString().Equals("vine", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
VineTileType type = (VineTileType) element.GetAttributeInt("tile", 0);
|
||||
Vector2 pos = element.GetAttributeVector2("pos", Vector2.Zero);
|
||||
TileSide sides = (TileSide) element.GetAttributeInt("sides", 0);
|
||||
TileSide blockedSides = (TileSide) element.GetAttributeInt("blockedsides", 0);
|
||||
int failedAttempts = element.GetAttributeInt("failedattempts", 0);
|
||||
float growthscale = element.GetAttributeFloat("growthscale", 0f);
|
||||
int flowerConfig = element.GetAttributeInt("flowerconfig", FoliageConfig.EmptyConfigValue);
|
||||
int leafConfig = element.GetAttributeInt("leafconfig", FoliageConfig.EmptyConfigValue);
|
||||
|
||||
VineTile tile = new VineTile(this, pos, type, FoliageConfig.Deserialize(flowerConfig), FoliageConfig.Deserialize(leafConfig))
|
||||
{
|
||||
Sides = sides, BlockedSides = blockedSides, FailedGrowthAttempts = failedAttempts, GrowthStep = growthscale
|
||||
};
|
||||
|
||||
Vines.Add(tile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool CanGrowMore() => Vines.Any(tile => tile.CanGrowMore());
|
||||
|
||||
public static int RandomInt(int min, int max, Random? random = null) => random?.Next(min, max) ?? Rand.Range(min, max);
|
||||
public static double RandomDouble(double min, double max, Random? random = null) => random?.NextDouble() * (max - min) + min ?? Rand.Range(min, max);
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ using FarseerPhysics.Dynamics.Contacts;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
@@ -392,6 +393,8 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
if (item.GetComponent<LevelResource>() != null) { return true; }
|
||||
|
||||
if (item.GetComponent<Planter>() is { } planter && planter.GrowableSeeds.Any(seed => seed != null)) { return false; }
|
||||
|
||||
//if the item has a connection panel and rewiring is disabled, don't allow deattaching
|
||||
var connectionPanel = item.GetComponent<ConnectionPanel>();
|
||||
if (connectionPanel != null && (connectionPanel.Locked || !(GameMain.NetworkMember?.ServerSettings?.AllowRewiring ?? true)))
|
||||
@@ -476,12 +479,13 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
var containedItems = item.ContainedItems;
|
||||
var containedItems = item.OwnInventory?.Items;
|
||||
if (containedItems != null)
|
||||
{
|
||||
foreach (Item contained in containedItems)
|
||||
{
|
||||
if (contained.body == null) continue;
|
||||
if (contained == null) { continue; }
|
||||
if (contained.body == null) { continue; }
|
||||
contained.SetTransform(item.SimPosition, contained.body.Rotation);
|
||||
}
|
||||
}
|
||||
@@ -573,7 +577,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
if (item.body == null || !item.body.Enabled) return;
|
||||
if (item.body == null || !item.body.Enabled) { return; }
|
||||
if (picker == null || !picker.HasEquippedItem(item))
|
||||
{
|
||||
if (Pusher != null) { Pusher.Enabled = false; }
|
||||
@@ -598,7 +602,10 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
ApplyStatusEffects(ActionType.OnActive, deltaTime, picker);
|
||||
|
||||
if (item.body.Dir != picker.AnimController.Dir) Flip();
|
||||
if (item.body.Dir != picker.AnimController.Dir)
|
||||
{
|
||||
item.FlipX(relativeToSub: false);
|
||||
}
|
||||
|
||||
item.Submarine = picker.Submarine;
|
||||
|
||||
@@ -635,11 +642,14 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
public void Flip()
|
||||
public override void FlipX(bool relativeToSub)
|
||||
{
|
||||
handlePos[0].X = -handlePos[0].X;
|
||||
handlePos[1].X = -handlePos[1].X;
|
||||
item.body.Dir = -item.body.Dir;
|
||||
if (item.body != null)
|
||||
{
|
||||
item.body.Dir = -item.body.Dir;
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnItemLoaded()
|
||||
|
||||
@@ -63,6 +63,13 @@ namespace Barotrauma.Items.Components
|
||||
item.RequireAimToUse = true;
|
||||
}
|
||||
|
||||
public override void Equip(Character character)
|
||||
{
|
||||
base.Equip(character);
|
||||
reloadTimer = Math.Min(reload, 1.0f);
|
||||
IsActive = true;
|
||||
}
|
||||
|
||||
public override bool Use(float deltaTime, Character character = null)
|
||||
{
|
||||
if (character == null || reloadTimer > 0.0f) { return false; }
|
||||
@@ -151,7 +158,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
ApplyStatusEffects(ActionType.OnActive, deltaTime, picker);
|
||||
|
||||
if (item.body.Dir != picker.AnimController.Dir) { Flip(); }
|
||||
if (item.body.Dir != picker.AnimController.Dir) { item.FlipX(relativeToSub: false); }
|
||||
|
||||
AnimController ac = picker.AnimController;
|
||||
|
||||
@@ -366,13 +373,15 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
|
||||
|
||||
bool success = Rand.Range(0.0f, 0.5f) < DegreeOfSuccess(User);
|
||||
|
||||
#if SERVER
|
||||
if (GameMain.Server != null && targetCharacter != null) //TODO: Log structure hits
|
||||
{
|
||||
GameMain.Server.CreateEntityEvent(item, new object[]
|
||||
{
|
||||
Networking.NetEntityEvent.Type.ApplyStatusEffect,
|
||||
ActionType.OnUse,
|
||||
success ? ActionType.OnUse : ActionType.OnFailure,
|
||||
null, //itemcomponent
|
||||
targetCharacter.ID, targetLimb
|
||||
});
|
||||
@@ -389,7 +398,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
if (targetCharacter != null) //TODO: Allow OnUse to happen on structures too maybe??
|
||||
{
|
||||
ApplyStatusEffects(ActionType.OnUse, 1.0f, targetCharacter, targetLimb, user: User);
|
||||
ApplyStatusEffects(success ? ActionType.OnUse : ActionType.OnFailure, 1.0f, targetCharacter, targetLimb, user: User);
|
||||
}
|
||||
|
||||
if (DeleteOnUse)
|
||||
|
||||
@@ -61,7 +61,7 @@ namespace Barotrauma.Items.Components
|
||||
allowedSlots.Add(allowedSlot);
|
||||
}
|
||||
|
||||
canBePicked = true;
|
||||
canBePicked = true;
|
||||
}
|
||||
|
||||
public override bool Pick(Character picker)
|
||||
@@ -142,7 +142,8 @@ namespace Barotrauma.Items.Components
|
||||
this,
|
||||
item.WorldPosition,
|
||||
pickTimer / requiredTime,
|
||||
GUI.Style.Red, GUI.Style.Green);
|
||||
GUI.Style.Red, GUI.Style.Green,
|
||||
!string.IsNullOrWhiteSpace(PickingMsg) ? PickingMsg : this is Door ? "progressbar.opening" : "progressbar.deattaching");
|
||||
#endif
|
||||
|
||||
picker.AnimController.UpdateUseItem(true, item.WorldPosition + new Vector2(0.0f, 100.0f) * ((pickTimer / 10.0f) % 0.1f));
|
||||
|
||||
@@ -72,6 +72,12 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
partial void InitProjSpecific(XElement element);
|
||||
|
||||
public override void Equip(Character character)
|
||||
{
|
||||
reloadTimer = Math.Min(reload, 1.0f);
|
||||
IsActive = true;
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
reloadTimer -= deltaTime;
|
||||
@@ -180,22 +186,25 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public Projectile FindProjectile(bool triggerOnUseOnContainers = false)
|
||||
{
|
||||
var containedItems = item.ContainedItems;
|
||||
var containedItems = item.OwnInventory?.Items;
|
||||
if (containedItems == null) { return null; }
|
||||
|
||||
foreach (Item item in containedItems)
|
||||
{
|
||||
if (item == null) { continue; }
|
||||
Projectile projectile = item.GetComponent<Projectile>();
|
||||
if (projectile != null) { return projectile; }
|
||||
}
|
||||
|
||||
//projectile not found, see if one of the contained items contains projectiles
|
||||
foreach (Item item in containedItems)
|
||||
foreach (Item it in containedItems)
|
||||
{
|
||||
var containedSubItems = item.ContainedItems;
|
||||
if (it == null) { continue; }
|
||||
var containedSubItems = it.OwnInventory?.Items;
|
||||
if (containedSubItems == null) { continue; }
|
||||
foreach (Item subItem in containedSubItems)
|
||||
{
|
||||
if (subItem == null) { continue; }
|
||||
Projectile 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...)
|
||||
|
||||
@@ -52,12 +52,16 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
[Serialize(0.0f, false, description: "How much the item decreases the size of fires per second.")]
|
||||
public float ExtinguishAmount
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
[Serialize(0.0f, false, description: "How much water the item provides to planters per second.")]
|
||||
public float WaterAmount { get; set; }
|
||||
|
||||
[Serialize("0.0,0.0", false, description: "The position of the barrel as an offset from the item's center (in pixels).")]
|
||||
public Vector2 BarrelPos { get; set; }
|
||||
|
||||
@@ -82,13 +86,19 @@ namespace Barotrauma.Items.Components
|
||||
[Serialize(0.0f, false, description: "Force applied to the entity the ray hits.")]
|
||||
public float TargetForce { get; set; }
|
||||
|
||||
[Serialize(0.0f, false, description: "Rotation of the barrel in degrees."), Editable(MinValueFloat = 0, MaxValueFloat = 360, VectorComponentLabels = new string[] { "editable.minvalue", "editable.maxvalue" })]
|
||||
public float BarrelRotation
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public Vector2 TransformedBarrelPos
|
||||
{
|
||||
get
|
||||
{
|
||||
Matrix bodyTransform = Matrix.CreateRotationZ(item.body.Rotation);
|
||||
Matrix bodyTransform = Matrix.CreateRotationZ(item.body.Rotation + MathHelper.ToRadians(BarrelRotation));
|
||||
Vector2 flippedPos = BarrelPos;
|
||||
if (item.body.Dir < 0.0f) flippedPos.X = -flippedPos.X;
|
||||
if (item.body.Dir < 0.0f) { flippedPos.X = -flippedPos.X; }
|
||||
return (Vector2.Transform(flippedPos, bodyTransform));
|
||||
}
|
||||
}
|
||||
@@ -188,7 +198,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
|
||||
float spread = MathHelper.ToRadians(MathHelper.Lerp(UnskilledSpread, Spread, degreeOfSuccess));
|
||||
float angle = item.body.Rotation + spread * Rand.Range(-0.5f, 0.5f);
|
||||
float angle = item.body.Rotation + MathHelper.ToRadians(BarrelRotation) + spread * Rand.Range(-0.5f, 0.5f);
|
||||
Vector2 rayEnd = rayStart +
|
||||
ConvertUnits.ToSimUnits(new Vector2(
|
||||
(float)Math.Cos(angle),
|
||||
@@ -276,7 +286,7 @@ namespace Barotrauma.Items.Components
|
||||
ignoreSensors: false,
|
||||
customPredicate: (Fixture f) =>
|
||||
{
|
||||
if (RepairThroughHoles && f.IsSensor && f.Body?.UserData is Structure) { return false; }
|
||||
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; }
|
||||
return true;
|
||||
},
|
||||
@@ -373,6 +383,42 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
if (WaterAmount > 0.0f && item.CurrentHull?.Submarine != null)
|
||||
{
|
||||
Vector2 pos = ConvertUnits.ToDisplayUnits(rayStart + item.Submarine.SimPosition);
|
||||
|
||||
// Could probably be done much efficiently here
|
||||
foreach (Item it in Item.ItemList)
|
||||
{
|
||||
if (it.Submarine == item.Submarine && it.GetComponent<Planter>() is { } planter)
|
||||
{
|
||||
if (it.GetComponent<Holdable>() is { } holdable && holdable.Attachable && !holdable.Attached) { continue; }
|
||||
|
||||
Rectangle collisionRect = it.WorldRect;
|
||||
collisionRect.Y -= collisionRect.Height;
|
||||
if (collisionRect.Left < pos.X && collisionRect.Right > pos.X && collisionRect.Bottom < pos.Y)
|
||||
{
|
||||
Body collision = Submarine.PickBody(rayStart, it.SimPosition, ignoredBodies, collisionCategories);
|
||||
if (collision == null)
|
||||
{
|
||||
for (var i = 0; i < planter.GrowableSeeds.Length; i++)
|
||||
{
|
||||
Growable seed = planter.GrowableSeeds[i];
|
||||
if (seed == null || seed.Decayed) { continue; }
|
||||
|
||||
seed.Health += WaterAmount * deltaTime;
|
||||
|
||||
#if CLIENT
|
||||
float barOffset = 10f * GUI.Scale;
|
||||
Vector2 offset = planter.PlantSlots.ContainsKey(i) ? planter.PlantSlots[i].Offset : Vector2.Zero;
|
||||
user.UpdateHUDProgressBar(planter, planter.Item.DrawPosition + new Vector2(barOffset, 0) + offset, seed.Health / seed.MaxHealth, GUI.Style.Blue, GUI.Style.Blue, "progressbar.watering");
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer)
|
||||
{
|
||||
@@ -464,7 +510,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
else if (targetBody.UserData is Item targetItem)
|
||||
{
|
||||
if (!HitItems) { return false; }
|
||||
if (!HitItems || targetItem.NonInteractable) { return false; }
|
||||
|
||||
var levelResource = targetItem.GetComponent<LevelResource>();
|
||||
if (levelResource != null && levelResource.Attached &&
|
||||
@@ -477,8 +523,9 @@ namespace Barotrauma.Items.Components
|
||||
this,
|
||||
targetItem.WorldPosition,
|
||||
levelResource.DeattachTimer / levelResource.DeattachDuration,
|
||||
GUI.Style.Red, GUI.Style.Green);
|
||||
GUI.Style.Red, GUI.Style.Green, "progressbar.deattaching");
|
||||
#endif
|
||||
FixItemProjSpecific(user, deltaTime, targetItem);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -571,34 +618,31 @@ namespace Barotrauma.Items.Components
|
||||
character.AIController.SteeringManager.SteeringSeek(standPos);
|
||||
}
|
||||
}
|
||||
else
|
||||
if (dist < reach / 2)
|
||||
{
|
||||
if (dist < reach / 2)
|
||||
// Too close -> steer away
|
||||
character.AIController.SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(character.SimPosition - leak.SimPosition));
|
||||
}
|
||||
else if (dist < reach * 2)
|
||||
{
|
||||
// In or almost in range
|
||||
character.CursorPosition = leak.Position;
|
||||
character.CursorPosition += VectorExtensions.Forward(Item.body.TransformedRotation + (float)Math.Sin(sinTime) / 2, dist / 2);
|
||||
if (character.AnimController.InWater)
|
||||
{
|
||||
// Too close -> steer away
|
||||
character.AIController.SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(character.SimPosition - leak.SimPosition));
|
||||
}
|
||||
else if (dist <= reach)
|
||||
{
|
||||
// In range
|
||||
character.CursorPosition = leak.Position;
|
||||
character.CursorPosition += VectorExtensions.Forward(Item.body.TransformedRotation + (float)Math.Sin(sinTime) / 2, dist / 2);
|
||||
if (character.AnimController.InWater)
|
||||
{
|
||||
var torso = character.AnimController.GetLimb(LimbType.Torso);
|
||||
// Turn facing the target when not moving (handled in the animcontroller if not moving)
|
||||
Vector2 mousePos = ConvertUnits.ToSimUnits(character.CursorPosition);
|
||||
Vector2 diff = (mousePos - torso.SimPosition) * character.AnimController.Dir;
|
||||
float newRotation = MathUtils.VectorToAngle(diff);
|
||||
character.AnimController.Collider.SmoothRotate(newRotation, 5.0f);
|
||||
var torso = character.AnimController.GetLimb(LimbType.Torso);
|
||||
// Turn facing the target when not moving (handled in the animcontroller if not moving)
|
||||
Vector2 mousePos = ConvertUnits.ToSimUnits(character.CursorPosition);
|
||||
Vector2 diff = (mousePos - torso.SimPosition) * character.AnimController.Dir;
|
||||
float newRotation = MathUtils.VectorToAngle(diff);
|
||||
character.AnimController.Collider.SmoothRotate(newRotation, 5.0f);
|
||||
|
||||
if (VectorExtensions.Angle(VectorExtensions.Forward(torso.body.TransformedRotation), fromCharacterToLeak) < MathHelper.PiOver4)
|
||||
{
|
||||
// Swim past
|
||||
Vector2 moveDir = leak.IsHorizontal ? Vector2.UnitY : Vector2.UnitX;
|
||||
moveDir *= character.AnimController.Dir;
|
||||
character.AIController.SteeringManager.SteeringManual(deltaTime, moveDir);
|
||||
}
|
||||
if (VectorExtensions.Angle(VectorExtensions.Forward(torso.body.TransformedRotation), fromCharacterToLeak) < MathHelper.PiOver4)
|
||||
{
|
||||
// Swim past
|
||||
Vector2 moveDir = leak.IsHorizontal ? Vector2.UnitY : Vector2.UnitX;
|
||||
moveDir *= character.AnimController.Dir;
|
||||
character.AIController.SteeringManager.SteeringManual(deltaTime, moveDir);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -674,9 +718,8 @@ namespace Barotrauma.Items.Components
|
||||
// A general purpose system could be better, but it would most likely require changes in the way we define the status effects in xml.
|
||||
foreach (ISerializableEntity target in targets)
|
||||
{
|
||||
if (!(target is Door door)) { continue; }
|
||||
|
||||
if (!door.CanBeWelded) { continue; }
|
||||
if (!(target is Door door)) { continue; }
|
||||
if (!door.CanBeWelded || door.Item.NonInteractable) { continue; }
|
||||
for (int i = 0; i < effect.propertyNames.Length; i++)
|
||||
{
|
||||
string propertyName = effect.propertyNames[i];
|
||||
@@ -685,7 +728,7 @@ 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);
|
||||
var progressBar = user.UpdateHUDProgressBar(door, door.Item.WorldPosition, door.Stuck / 100, Color.DarkGray * 0.5f, Color.White, "progressbar.welding");
|
||||
if (progressBar != null) { progressBar.Size = new Vector2(60.0f, 20.0f); }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
partial class Sprayer : RangedWeapon
|
||||
{
|
||||
[Serialize(0.0f, false, description: "The distance at which the item can spray walls.")]
|
||||
public float Range { get; set; }
|
||||
|
||||
[Serialize(1.0f, false, description: "How fast the item changes the color of the walls.")]
|
||||
public float SprayStrength { get; set; }
|
||||
|
||||
private readonly Dictionary<string, Color> liquidColors;
|
||||
private ItemContainer liquidContainer;
|
||||
|
||||
public Sprayer(Item item, XElement element) : base(item, element)
|
||||
{
|
||||
item.IsShootable = true;
|
||||
item.RequireAimToUse = true;
|
||||
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "paintcolors":
|
||||
{
|
||||
liquidColors = new Dictionary<string, Color>();
|
||||
foreach (XElement paintElement in subElement.Elements())
|
||||
{
|
||||
string paintName = paintElement.GetAttributeString("paintitem", string.Empty);
|
||||
Color paintColor = paintElement.GetAttributeColor("color", Color.Transparent);
|
||||
|
||||
if (paintName != string.Empty)
|
||||
{
|
||||
liquidColors.Add(paintName, paintColor);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
InitProjSpecific(element);
|
||||
}
|
||||
|
||||
public override void OnItemLoaded()
|
||||
{
|
||||
liquidContainer = item.GetComponent<ItemContainer>();
|
||||
}
|
||||
|
||||
partial void InitProjSpecific(XElement element);
|
||||
|
||||
#if SERVER
|
||||
public override bool Use(float deltaTime, Character character = null)
|
||||
{
|
||||
return character != null || character.Removed;
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
}
|
||||
@@ -84,7 +84,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
ApplyStatusEffects(ActionType.OnActive, deltaTime, picker);
|
||||
|
||||
if (item.body.Dir != picker.AnimController.Dir) { Flip(); }
|
||||
if (item.body.Dir != picker.AnimController.Dir) { item.FlipX(relativeToSub: false); }
|
||||
|
||||
AnimController ac = picker.AnimController;
|
||||
|
||||
|
||||
@@ -76,6 +76,13 @@ namespace Barotrauma.Items.Components
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize("", false, description: "What to display on the progress bar when this item is being picked.")]
|
||||
public string PickingMsg
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public Dictionary<string, SerializableProperty> SerializableProperties { get; protected set; }
|
||||
|
||||
public Action<bool> OnActiveStateChanged;
|
||||
|
||||
@@ -44,6 +44,13 @@ namespace Barotrauma.Items.Components
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(true, false, "Allow dragging and dropping items to deposit items into this inventory.")]
|
||||
public bool AllowDragAndDrop
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
|
||||
[Serialize(false, false, description: "If set to true, interacting with this item will make the character interact with the contained item(s), automatically picking them up if they can be picked up.")]
|
||||
public bool AutoInteractWithContained
|
||||
@@ -166,17 +173,21 @@ namespace Barotrauma.Items.Components
|
||||
public bool CanBeContained(Item item)
|
||||
{
|
||||
if (ContainableItems.Count == 0) { return true; }
|
||||
return (ContainableItems.Find(c => c.MatchesItem(item)) != null);
|
||||
return ContainableItems.Find(c => c.MatchesItem(item)) != null;
|
||||
}
|
||||
public bool CanBeContained(ItemPrefab itemPrefab)
|
||||
{
|
||||
if (ContainableItems.Count == 0) { return true; }
|
||||
return (ContainableItems.Find(c => c.MatchesItem(itemPrefab)) != null);
|
||||
return ContainableItems.Find(c => c.MatchesItem(itemPrefab)) != null;
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
if (item.body != null &&
|
||||
if (item.ParentInventory is CharacterInventory)
|
||||
{
|
||||
item.SetContainedItemPositions();
|
||||
}
|
||||
else if (item.body != null &&
|
||||
item.body.Enabled &&
|
||||
item.body.FarseerBody.Awake)
|
||||
{
|
||||
@@ -209,22 +220,6 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnItemLoaded()
|
||||
{
|
||||
base.OnItemLoaded();
|
||||
if (SpawnWithId.Length > 0)
|
||||
{
|
||||
ItemPrefab prefab = ItemPrefab.Prefabs.Find(m => m.Identifier == SpawnWithId);
|
||||
if (prefab != null)
|
||||
{
|
||||
if (Inventory != null && Inventory.Items.Any(it => it == null))
|
||||
{
|
||||
Entity.Spawner?.AddToSpawnQueue(prefab, Inventory);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override bool HasRequiredItems(Character character, bool addMessage, string msg = null)
|
||||
{
|
||||
return (!AccessOnlyWhenBroken || Item.Condition <= 0) && base.HasRequiredItems(character, addMessage, msg);
|
||||
@@ -284,6 +279,8 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public override bool Combine(Item item, Character user)
|
||||
{
|
||||
if (!AllowDragAndDrop && user != null) { return false; }
|
||||
|
||||
if (!ContainableItems.Any(x => x.MatchesItem(item))) { return false; }
|
||||
if (user != null && !user.CanAccessInventory(Inventory)) { return false; }
|
||||
|
||||
@@ -354,16 +351,28 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public override void OnMapLoaded()
|
||||
{
|
||||
if (itemIds == null) { return; }
|
||||
|
||||
for (ushort i = 0; i < itemIds.Length; i++)
|
||||
{
|
||||
if (!(Entity.FindEntityByID(itemIds[i]) is Item item)) { continue; }
|
||||
if (i >= Inventory.Capacity) { continue; }
|
||||
Inventory.TryPutItem(item, i, false, false, null, false);
|
||||
if (itemIds != null)
|
||||
{
|
||||
for (ushort i = 0; i < itemIds.Length; i++)
|
||||
{
|
||||
if (!(Entity.FindEntityByID(itemIds[i]) is Item item)) { continue; }
|
||||
if (i >= Inventory.Capacity) { continue; }
|
||||
Inventory.TryPutItem(item, i, false, false, null, false);
|
||||
}
|
||||
itemIds = null;
|
||||
}
|
||||
|
||||
itemIds = null;
|
||||
if (SpawnWithId.Length > 0)
|
||||
{
|
||||
ItemPrefab prefab = ItemPrefab.Prefabs.Find(m => m.Identifier == SpawnWithId);
|
||||
if (prefab != null)
|
||||
{
|
||||
if (Inventory != null && Inventory.Items.Any(it => it == null))
|
||||
{
|
||||
Entity.Spawner?.AddToSpawnQueue(prefab, Inventory);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void ShallowRemoveComponentSpecific()
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using Barotrauma.Networking;
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
partial class ItemLabel : ItemComponent, IDrawableComponent
|
||||
partial class ItemLabel : ItemComponent, IDrawableComponent, IServerSerializable
|
||||
{
|
||||
public Vector2 DrawSize
|
||||
{
|
||||
@@ -10,12 +11,16 @@ namespace Barotrauma.Items.Components
|
||||
get { return Vector2.Zero; }
|
||||
}
|
||||
|
||||
partial void OnStateChanged();
|
||||
|
||||
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":
|
||||
if (Text == signal) { return; }
|
||||
Text = signal;
|
||||
OnStateChanged();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -151,6 +151,7 @@ namespace Barotrauma.Items.Components
|
||||
if (user == null
|
||||
|| user.Removed
|
||||
|| user.SelectedConstruction != item
|
||||
|| item.ParentInventory != null
|
||||
|| !user.CanInteractWith(item)
|
||||
|| (UsableIn == UseEnvironment.Water && !user.AnimController.InWater)
|
||||
|| (UsableIn == UseEnvironment.Air && user.AnimController.InWater))
|
||||
@@ -221,7 +222,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
user.AnimController.ResetPullJoints();
|
||||
|
||||
if (dir != 0) user.AnimController.TargetDir = dir;
|
||||
if (dir != 0) { user.AnimController.TargetDir = dir; }
|
||||
|
||||
foreach (LimbPos lb in limbPositions)
|
||||
{
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
private float maxForce;
|
||||
|
||||
private Attack propellerDamage;
|
||||
private readonly Attack propellerDamage;
|
||||
|
||||
private float damageTimer;
|
||||
|
||||
@@ -24,6 +24,8 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
private float controlLockTimer;
|
||||
|
||||
public Character User;
|
||||
|
||||
[Editable(0.0f, 10000000.0f),
|
||||
Serialize(2000.0f, true, description: "The amount of force exerted on the submarine when the engine is operating at full power.")]
|
||||
public float MaxForce
|
||||
@@ -106,6 +108,11 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
//arbitrary multiplier that was added to changes in submarine mass without having to readjust all engines
|
||||
float forceMultiplier = 0.1f;
|
||||
if (User != null)
|
||||
{
|
||||
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);
|
||||
Vector2 currForce = new Vector2(force * maxForce * forceMultiplier * voltageFactor, 0.0f);
|
||||
//less effective when in a bad condition
|
||||
@@ -193,6 +200,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
controlLockTimer = 0.1f;
|
||||
targetForce = MathHelper.Clamp(tempForce, -100.0f, 100.0f);
|
||||
User = sender;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
@@ -15,6 +16,9 @@ namespace Barotrauma.Items.Components
|
||||
private float timeUntilReady;
|
||||
private float requiredTime;
|
||||
|
||||
private string savedFabricatedItem;
|
||||
private float savedTimeUntilReady, savedRequiredTime;
|
||||
|
||||
private bool hasPower;
|
||||
|
||||
private Character user;
|
||||
@@ -46,6 +50,7 @@ namespace Barotrauma.Items.Components
|
||||
if (state == value) { return; }
|
||||
state = value;
|
||||
#if SERVER
|
||||
serverEventId++;
|
||||
item.CreateServerEvent(this);
|
||||
#endif
|
||||
}
|
||||
@@ -158,8 +163,8 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
private void StartFabricating(FabricationRecipe selectedItem, Character user)
|
||||
{
|
||||
if (selectedItem == null) return;
|
||||
if (!outputContainer.Inventory.IsEmpty()) return;
|
||||
if (selectedItem == null) { return; }
|
||||
if (!outputContainer.Inventory.IsEmpty()) { return; }
|
||||
|
||||
#if CLIENT
|
||||
itemList.Enabled = false;
|
||||
@@ -194,14 +199,23 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
private void CancelFabricating(Character user = null)
|
||||
{
|
||||
if (fabricatedItem == null) { return; }
|
||||
|
||||
IsActive = false;
|
||||
fabricatedItem = null;
|
||||
this.user = null;
|
||||
|
||||
currPowerConsumption = 0.0f;
|
||||
|
||||
progressState = 0.0f;
|
||||
timeUntilReady = 0.0f;
|
||||
inputContainer.Inventory.Locked = false;
|
||||
outputContainer.Inventory.Locked = false;
|
||||
|
||||
if (GameMain.NetworkMember?.IsServer ?? true)
|
||||
{
|
||||
State = FabricatorState.Stopped;
|
||||
}
|
||||
|
||||
if (fabricatedItem == null) { return; }
|
||||
fabricatedItem = null;
|
||||
|
||||
#if CLIENT
|
||||
itemList.Enabled = true;
|
||||
if (activateButton != null)
|
||||
@@ -209,17 +223,6 @@ namespace Barotrauma.Items.Components
|
||||
activateButton.Text = TextManager.Get("FabricatorCreate");
|
||||
}
|
||||
#endif
|
||||
progressState = 0.0f;
|
||||
|
||||
timeUntilReady = 0.0f;
|
||||
|
||||
inputContainer.Inventory.Locked = false;
|
||||
outputContainer.Inventory.Locked = false;
|
||||
|
||||
if (GameMain.NetworkMember?.IsServer ?? true)
|
||||
{
|
||||
State = FabricatorState.Stopped;
|
||||
}
|
||||
#if SERVER
|
||||
if (user != null)
|
||||
{
|
||||
@@ -279,13 +282,13 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
for (int i = 0; i < ingredient.Amount; i++)
|
||||
{
|
||||
var availableItem = availableIngredients.FirstOrDefault(it => it != null && it.Prefab == ingredient.ItemPrefab && it.Condition >= ingredient.ItemPrefab.Health * ingredient.MinCondition);
|
||||
var availableItem = availableIngredients.FirstOrDefault(it => it != null && ingredient.ItemPrefabs.Contains(it.Prefab) && it.ConditionPercentage >= ingredient.MinCondition * 100.0f);
|
||||
if (availableItem == null) { continue; }
|
||||
|
||||
//Item4 = use condition bool
|
||||
if (ingredient.UseCondition && availableItem.Condition - ingredient.ItemPrefab.Health * ingredient.MinCondition > 0.0f) //Leave it behind with reduced condition if it has enough to stay above 0
|
||||
if (ingredient.UseCondition && availableItem.ConditionPercentage - ingredient.MinCondition * 100 > 0.0f) //Leave it behind with reduced condition if it has enough to stay above 0
|
||||
{
|
||||
availableItem.Condition -= ingredient.ItemPrefab.Health * ingredient.MinCondition;
|
||||
availableItem.Condition -= availableItem.Prefab.Health * ingredient.MinCondition;
|
||||
continue;
|
||||
}
|
||||
availableIngredients.Remove(availableItem);
|
||||
@@ -366,7 +369,8 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public float FabricationDegreeOfSuccess(Character character, List<Skill> skills)
|
||||
{
|
||||
if (skills.Count == 0) return 1.0f;
|
||||
if (skills.Count == 0) { return 1.0f; }
|
||||
if (character == null) { return 0.0f; }
|
||||
|
||||
float skillSum = (from t in skills let characterLevel = character.GetSkillLevel(t.Identifier) select (characterLevel - (t.Level * SkillRequirementMultiplier))).Sum();
|
||||
float average = skillSum / skills.Count;
|
||||
@@ -461,8 +465,53 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
return
|
||||
item != null &&
|
||||
item.prefab == requiredItem.ItemPrefab &&
|
||||
requiredItem.ItemPrefabs.Contains(item.prefab) &&
|
||||
item.Condition / item.Prefab.Health >= requiredItem.MinCondition;
|
||||
}
|
||||
|
||||
public override XElement Save(XElement parentElement)
|
||||
{
|
||||
var componentElement = base.Save(parentElement);
|
||||
if (fabricatedItem != null)
|
||||
{
|
||||
componentElement.Add(new XAttribute("fabricateditemidentifier", fabricatedItem.TargetItem.Identifier));
|
||||
componentElement.Add(new XAttribute("savedtimeuntilready", timeUntilReady.ToString("G", CultureInfo.InvariantCulture)));
|
||||
componentElement.Add(new XAttribute("savedrequiredtime", requiredTime.ToString("G", CultureInfo.InvariantCulture)));
|
||||
|
||||
}
|
||||
return componentElement;
|
||||
}
|
||||
|
||||
public override void Load(XElement componentElement, bool usePrefabValues)
|
||||
{
|
||||
base.Load(componentElement, usePrefabValues);
|
||||
savedFabricatedItem = componentElement.GetAttributeString("fabricateditemidentifier", "");
|
||||
savedTimeUntilReady = componentElement.GetAttributeFloat("savedtimeuntilready", 0.0f);
|
||||
savedRequiredTime = componentElement.GetAttributeFloat("savedrequiredtime", 0.0f);
|
||||
}
|
||||
|
||||
public override void OnMapLoaded()
|
||||
{
|
||||
if (string.IsNullOrEmpty(savedFabricatedItem)) { return; }
|
||||
|
||||
inputContainer?.OnMapLoaded();
|
||||
outputContainer?.OnMapLoaded();
|
||||
|
||||
var recipe = fabricationRecipes.Find(r => r.TargetItem.Identifier == savedFabricatedItem);
|
||||
if (recipe == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Error while loading a fabricator. Can't continue fabricating \"" + savedFabricatedItem + "\" (matching recipe not found).");
|
||||
}
|
||||
else
|
||||
{
|
||||
#if CLIENT
|
||||
SelectItem(null, recipe, savedRequiredTime);
|
||||
#endif
|
||||
StartFabricating(recipe, user: null);
|
||||
timeUntilReady = savedTimeUntilReady;
|
||||
requiredTime = savedRequiredTime;
|
||||
}
|
||||
savedFabricatedItem = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,8 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
partial class Reactor : Powered, IServerSerializable, IClientSerializable
|
||||
{
|
||||
const float NetworkUpdateInterval = 0.5f;
|
||||
const float NetworkUpdateIntervalHigh = 0.5f;
|
||||
const float NetworkUpdateIntervalLow = 10.0f;
|
||||
|
||||
//the rate at which the reactor is being run on (higher rate -> higher temperature)
|
||||
private float fissionRate;
|
||||
@@ -64,17 +65,18 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
private Character lastAIUser;
|
||||
public Character LastAIUser { get; private set; }
|
||||
|
||||
private Character lastUser;
|
||||
private Character LastUser
|
||||
public Character LastUser
|
||||
{
|
||||
get { return lastUser; }
|
||||
set
|
||||
private set
|
||||
{
|
||||
if (lastUser == value) return;
|
||||
if (lastUser == value) { return; }
|
||||
lastUser = value;
|
||||
degreeOfSuccess = lastUser == null ? 0.0f : DegreeOfSuccess(lastUser);
|
||||
LastUserWasPlayer = lastUser.IsPlayer;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,6 +178,8 @@ namespace Barotrauma.Items.Components
|
||||
[Serialize(0.0f, true)]
|
||||
public float AvailableFuel { get; set; }
|
||||
|
||||
public bool LastUserWasPlayer { get; private set; }
|
||||
|
||||
public Reactor(Item item, XElement element)
|
||||
: base(item, element)
|
||||
{
|
||||
@@ -207,13 +211,13 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
//if an AI character was using the item on the previous frame but not anymore, turn autotemp on
|
||||
// (= bots turn autotemp back on when leaving the reactor)
|
||||
if (lastAIUser != null)
|
||||
if (LastAIUser != null)
|
||||
{
|
||||
if (lastAIUser.SelectedConstruction != item && lastAIUser.CanInteractWith(item))
|
||||
if (LastAIUser.SelectedConstruction != item && LastAIUser.CanInteractWith(item))
|
||||
{
|
||||
AutoTemp = true;
|
||||
unsentChanges = true;
|
||||
lastAIUser = null;
|
||||
LastAIUser = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -309,10 +313,15 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
if (fissionRate > 0.0f)
|
||||
{
|
||||
foreach (Item item in item.ContainedItems)
|
||||
var containedItems = item.OwnInventory?.Items;
|
||||
if (containedItems != null)
|
||||
{
|
||||
if (!item.HasTag("reactorfuel")) continue;
|
||||
item.Condition -= fissionRate / 100.0f * fuelConsumptionRate * deltaTime;
|
||||
foreach (Item item in containedItems)
|
||||
{
|
||||
if (item == null) { continue; }
|
||||
if (!item.HasTag("reactorfuel")) { continue; }
|
||||
item.Condition -= fissionRate / 100.0f * fuelConsumptionRate * deltaTime;
|
||||
}
|
||||
}
|
||||
|
||||
if (item.CurrentHull != null)
|
||||
@@ -337,6 +346,7 @@ namespace Barotrauma.Items.Components
|
||||
item.SendSignal(0, ((int)(temperature * 100.0f)).ToString(), "temperature_out", null);
|
||||
item.SendSignal(0, ((int)-CurrPowerConsumption).ToString(), "power_value_out", null);
|
||||
item.SendSignal(0, ((int)load).ToString(), "load_value_out", null);
|
||||
item.SendSignal(0, ((int)AvailableFuel).ToString(), "fuel_out", null);
|
||||
|
||||
UpdateFailures(deltaTime);
|
||||
#if CLIENT
|
||||
@@ -344,9 +354,13 @@ namespace Barotrauma.Items.Components
|
||||
#endif
|
||||
AvailableFuel = 0.0f;
|
||||
|
||||
sendUpdateTimer = Math.Max(sendUpdateTimer - deltaTime, 0.0f);
|
||||
|
||||
sendUpdateTimer -= deltaTime;
|
||||
#if CLIENT
|
||||
if (unsentChanges && sendUpdateTimer <= 0.0f)
|
||||
#else
|
||||
if (sendUpdateTimer < -NetworkUpdateIntervalLow || (unsentChanges && sendUpdateTimer <= 0.0f))
|
||||
#endif
|
||||
{
|
||||
#if SERVER
|
||||
if (GameMain.Server != null)
|
||||
@@ -360,7 +374,7 @@ namespace Barotrauma.Items.Components
|
||||
item.CreateClientEvent(this);
|
||||
}
|
||||
#endif
|
||||
sendUpdateTimer = NetworkUpdateInterval;
|
||||
sendUpdateTimer = NetworkUpdateIntervalHigh;
|
||||
unsentChanges = false;
|
||||
}
|
||||
}
|
||||
@@ -398,8 +412,8 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
private bool TooMuchFuel()
|
||||
{
|
||||
var containedItems = item.ContainedItems;
|
||||
if (containedItems != null && containedItems.Count() <= 1) { return false; }
|
||||
var containedItems = item.OwnInventory?.Items;
|
||||
if (containedItems != null && containedItems.Count(i => i != null) <= 1) { return false; }
|
||||
|
||||
//get the amount of heat we'd generate if the fission rate was at the low end of the optimal range
|
||||
float minimumHeat = GetGeneratedHeat(optimalFissionRate.X);
|
||||
@@ -516,12 +530,12 @@ namespace Barotrauma.Items.Components
|
||||
fireTimer = 0.0f;
|
||||
meltDownTimer = 0.0f;
|
||||
|
||||
var containedItems = item.ContainedItems;
|
||||
var containedItems = item.OwnInventory?.Items;
|
||||
if (containedItems != null)
|
||||
{
|
||||
foreach (Item containedItem in containedItems)
|
||||
{
|
||||
if (containedItem == null) continue;
|
||||
if (containedItem == null) { continue; }
|
||||
containedItem.Condition = 0.0f;
|
||||
}
|
||||
}
|
||||
@@ -583,15 +597,19 @@ namespace Barotrauma.Items.Components
|
||||
else if (TooMuchFuel())
|
||||
{
|
||||
var container = item.GetComponent<ItemContainer>();
|
||||
foreach (Item item in item.ContainedItems)
|
||||
var containedItems = item.OwnInventory?.Items;
|
||||
if (containedItems != null)
|
||||
{
|
||||
if (item != null && container.ContainableItems.Any(ri => ri.MatchesItem(item)))
|
||||
foreach (Item item in containedItems)
|
||||
{
|
||||
if (!character.Inventory.TryPutItem(item, character, allowedSlots: item.AllowedSlots))
|
||||
if (item != null && container.ContainableItems.Any(ri => ri.MatchesItem(item)))
|
||||
{
|
||||
item.Drop(character);
|
||||
if (!character.Inventory.TryPutItem(item, character, allowedSlots: item.AllowedSlots))
|
||||
{
|
||||
item.Drop(character);
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -599,7 +617,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
if (objective.Override)
|
||||
{
|
||||
if (lastUser != null && lastUser != character && lastUser != lastAIUser)
|
||||
if (lastUser != null && lastUser != character && lastUser != LastAIUser)
|
||||
{
|
||||
if (lastUser.SelectedConstruction == item)
|
||||
{
|
||||
@@ -607,8 +625,12 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (LastUserWasPlayer)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
LastUser = lastAIUser = character;
|
||||
LastUser = LastAIUser = character;
|
||||
|
||||
bool prevAutoTemp = autoTemp;
|
||||
bool prevPowerOn = _powerOn;
|
||||
|
||||
@@ -286,7 +286,8 @@ namespace Barotrauma.Items.Components
|
||||
ApplyStatusEffects(ActionType.OnActive, deltaTime, null);
|
||||
|
||||
float userSkill = 0.0f;
|
||||
if (user != null && (user.SelectedConstruction == item || item.linkedTo.Contains(user.SelectedConstruction)))
|
||||
if (user != null && controlledSub != null &&
|
||||
(user.SelectedConstruction == item || item.linkedTo.Contains(user.SelectedConstruction)))
|
||||
{
|
||||
userSkill = user.GetSkillLevel("helm") / 100.0f;
|
||||
}
|
||||
@@ -298,7 +299,9 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
else
|
||||
{
|
||||
if (user != null && user.Info != null && user.SelectedConstruction == item)
|
||||
if (user != null && user.Info != null &&
|
||||
user.SelectedConstruction == item &&
|
||||
controlledSub != null && controlledSub.Velocity.LengthSquared() > 0.01f)
|
||||
{
|
||||
IncreaseSkillLevel(user, deltaTime);
|
||||
}
|
||||
@@ -320,13 +323,13 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
item.SendSignal(0, targetVelocity.X.ToString(CultureInfo.InvariantCulture), "velocity_x_out", null);
|
||||
|
||||
item.SendSignal(0, targetVelocity.X.ToString(CultureInfo.InvariantCulture), "velocity_x_out", user);
|
||||
|
||||
float targetLevel = -targetVelocity.Y;
|
||||
targetLevel += (neutralBallastLevel - 0.5f) * 100.0f;
|
||||
|
||||
item.SendSignal(0, targetLevel.ToString(CultureInfo.InvariantCulture), "velocity_y_out", null);
|
||||
item.SendSignal(0, targetLevel.ToString(CultureInfo.InvariantCulture), "velocity_y_out", user);
|
||||
}
|
||||
|
||||
private void IncreaseSkillLevel(Character user, float deltaTime)
|
||||
@@ -335,12 +338,11 @@ namespace Barotrauma.Items.Components
|
||||
// Do not increase the helm skill when "steering" the sub in an outpost level
|
||||
if (GameMain.GameSession?.Campaign != null && Level.IsLoadedOutpost) { return; }
|
||||
|
||||
float userSkill = user.GetSkillLevel("helm") / 100.0f;
|
||||
float userSkill = Math.Max(user.GetSkillLevel("helm"), 1.0f) / 100.0f;
|
||||
user.Info.IncreaseSkillLevel(
|
||||
"helm",
|
||||
SkillSettings.Current.SkillIncreasePerSecondWhenSteering / Math.Max(userSkill, 1.0f) * deltaTime,
|
||||
SkillSettings.Current.SkillIncreasePerSecondWhenSteering / userSkill * deltaTime,
|
||||
user.WorldPosition + Vector2.UnitY * 150.0f);
|
||||
|
||||
}
|
||||
|
||||
private void UpdateAutoPilot(float deltaTime)
|
||||
@@ -599,6 +601,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
break;
|
||||
case "navigateback":
|
||||
if (Level.IsLoadedOutpost) { break; }
|
||||
if (DockingSources.Any(d => d.Docked))
|
||||
{
|
||||
item.SendSignal(0, "1", "toggle_docking", sender: null);
|
||||
@@ -613,6 +616,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
break;
|
||||
case "navigatetodestination":
|
||||
if (Level.IsLoadedOutpost) { break; }
|
||||
if (DockingSources.Any(d => d.Docked))
|
||||
{
|
||||
item.SendSignal(0, "1", "toggle_docking", sender: null);
|
||||
|
||||
@@ -0,0 +1,314 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
internal enum PlantItemType
|
||||
{
|
||||
Seed,
|
||||
Fertilizer
|
||||
}
|
||||
|
||||
internal readonly struct SuitablePlantItem
|
||||
{
|
||||
public readonly Item? Item;
|
||||
public readonly PlantItemType Type;
|
||||
public readonly string ProgressBarMessage;
|
||||
|
||||
public SuitablePlantItem(Item item, PlantItemType type, string progressBarMessage)
|
||||
{
|
||||
Item = item;
|
||||
Type = type;
|
||||
ProgressBarMessage = progressBarMessage;
|
||||
}
|
||||
|
||||
public bool IsNull() => Item == null;
|
||||
}
|
||||
|
||||
internal struct PlantSlot
|
||||
{
|
||||
public Vector2 Offset;
|
||||
public float Size;
|
||||
|
||||
public PlantSlot(XElement element)
|
||||
{
|
||||
Offset = element.GetAttributeVector2("offset", Vector2.Zero);
|
||||
Size = element.GetAttributeFloat("size", 0.5f);
|
||||
}
|
||||
|
||||
public PlantSlot(Vector2 offset, float size)
|
||||
{
|
||||
Offset = offset;
|
||||
Size = size;
|
||||
}
|
||||
}
|
||||
|
||||
internal partial class Planter : Pickable, IDrawableComponent
|
||||
{
|
||||
public static readonly PlantSlot NullSlot = new PlantSlot();
|
||||
public readonly Dictionary<int, PlantSlot> PlantSlots = new Dictionary<int, PlantSlot>();
|
||||
|
||||
private static readonly SuitablePlantItem NullItem = new SuitablePlantItem();
|
||||
private const string MsgFertilizer = "ItemMsgAddFertilizer";
|
||||
private const string MsgSeed = "ItemMsgPlantSeed";
|
||||
private const string MsgHarvest = "ItemMsgHarvest";
|
||||
private const string MsgUprooting = "progressbar.uprooting";
|
||||
private const string MsgFertilizing = "progressbar.fertilizing";
|
||||
private const string MsgPlanting = "progressbar.planting";
|
||||
public static float GrowthTickDelay = 1f; // 1 second
|
||||
|
||||
private float fertilizer;
|
||||
|
||||
[Serialize(0f, true, "How much fertilizer the planter has.")]
|
||||
public float Fertilizer
|
||||
{
|
||||
get => fertilizer;
|
||||
set => fertilizer = Math.Clamp(value, 0, FertilizerCapacity);
|
||||
}
|
||||
|
||||
[Serialize(100f, true, "How much fertilizer can the planter hold.")]
|
||||
public float FertilizerCapacity { get; set; }
|
||||
|
||||
public Growable?[] GrowableSeeds = new Growable?[0];
|
||||
|
||||
private readonly List<RelatedItem> SuitableFertilizer = new List<RelatedItem>();
|
||||
private readonly List<RelatedItem> SuitableSeeds = new List<RelatedItem>();
|
||||
private ItemContainer? container;
|
||||
private float growthTickTimer;
|
||||
|
||||
public Planter(Item item, XElement element) : base(item, element)
|
||||
{
|
||||
canBePicked = true;
|
||||
SerializableProperty.DeserializeProperties(this, element);
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "plantslot":
|
||||
PlantSlots.Add(subElement.GetAttributeInt("slot", 0), new PlantSlot(subElement));
|
||||
break;
|
||||
case "suitablefertilizer":
|
||||
SuitableFertilizer.Add(RelatedItem.Load(subElement, true, item.Name));
|
||||
break;
|
||||
case "suitableseed":
|
||||
SuitableSeeds.Add(RelatedItem.Load(subElement, true, item.Name));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnItemLoaded()
|
||||
{
|
||||
base.OnItemLoaded();
|
||||
IsActive = true;
|
||||
#if CLIENT
|
||||
lightComponent = item.GetComponent<LightComponent>();
|
||||
if (lightComponent != null)
|
||||
{
|
||||
lightComponent.Light.Enabled = false;
|
||||
}
|
||||
#endif
|
||||
container = item.GetComponent<ItemContainer>();
|
||||
GrowableSeeds = new Growable[container.Capacity];
|
||||
}
|
||||
|
||||
public override bool HasRequiredItems(Character character, bool addMessage, string msg = null)
|
||||
{
|
||||
if (container?.Inventory == null) { return false; }
|
||||
|
||||
SuitablePlantItem plantItem = GetSuitableItem(character);
|
||||
|
||||
if (!plantItem.IsNull())
|
||||
{
|
||||
Msg = plantItem.Type switch
|
||||
{
|
||||
PlantItemType.Seed => MsgSeed,
|
||||
PlantItemType.Fertilizer => MsgFertilizer,
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
ParseMsg();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (HasAnyFinishedGrowing())
|
||||
{
|
||||
Msg = MsgHarvest;
|
||||
ParseMsg();
|
||||
return true;
|
||||
}
|
||||
|
||||
Msg = string.Empty;
|
||||
ParseMsg();
|
||||
return false;
|
||||
}
|
||||
|
||||
public override bool Pick(Character character)
|
||||
{
|
||||
SuitablePlantItem plantItem = GetSuitableItem(character);
|
||||
PickingMsg = plantItem.IsNull() ? MsgUprooting : plantItem.ProgressBarMessage;
|
||||
|
||||
return base.Pick(character);
|
||||
}
|
||||
|
||||
public override bool OnPicked(Character character)
|
||||
{
|
||||
if (container?.Inventory == null) { return false; }
|
||||
|
||||
SuitablePlantItem plantItem = GetSuitableItem(character);
|
||||
if (plantItem.IsNull())
|
||||
{
|
||||
return TryHarvest(character);
|
||||
}
|
||||
|
||||
switch (plantItem.Type)
|
||||
{
|
||||
case PlantItemType.Seed:
|
||||
return container.Inventory.TryPutItem(plantItem.Item, character, new List<InvSlotType> { InvSlotType.Any });
|
||||
case PlantItemType.Fertilizer when plantItem.Item != null:
|
||||
float canAdd = FertilizerCapacity - Fertilizer;
|
||||
float maxAvailable = plantItem.Item.Condition;
|
||||
float toAdd = Math.Min(canAdd, maxAvailable);
|
||||
plantItem.Item.Condition -= toAdd;
|
||||
fertilizer += toAdd;
|
||||
#if CLIENT
|
||||
character.UpdateHUDProgressBar(this, Item.DrawPosition, Fertilizer / FertilizerCapacity, Color.SaddleBrown, Color.SaddleBrown, "entityname.fertilizer");
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to harvest a fully grown plant or removes a decayed plant if any
|
||||
/// </summary>
|
||||
/// <param name="character">The character who gets the produce or null if they should drop on the floor.</param>
|
||||
/// <returns></returns>
|
||||
private bool TryHarvest(Character? character)
|
||||
{
|
||||
Debug.Assert(container != null, "Tried to harvest a planter without an item container.");
|
||||
|
||||
for (var i = 0; i < GrowableSeeds.Length; i++)
|
||||
{
|
||||
Growable? seed = GrowableSeeds[i];
|
||||
if (seed == null) { continue; }
|
||||
|
||||
if (seed.Decayed || seed.FullyGrown)
|
||||
{
|
||||
container?.Inventory.RemoveItem(seed.Item);
|
||||
GrowableSeeds[i] = null;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
base.Update(deltaTime, cam);
|
||||
|
||||
#if CLIENT
|
||||
if (lightComponent != null)
|
||||
{
|
||||
bool hasSeed = false;
|
||||
foreach (Growable? seed in GrowableSeeds) { hasSeed |= seed != null; }
|
||||
|
||||
lightComponent.Light.Enabled = hasSeed;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (container?.Inventory == null) { return; }
|
||||
|
||||
for (var i = 0; i < container.Inventory.Items.Length; i++)
|
||||
{
|
||||
if (i < 0 || GrowableSeeds.Length <= i) { continue; }
|
||||
|
||||
Item containedItem = container.Inventory.Items[i];
|
||||
|
||||
Growable? growable = containedItem?.GetComponent<Growable>();
|
||||
|
||||
if (growable != null)
|
||||
{
|
||||
GrowableSeeds[i] = growable;
|
||||
growable.IsActive = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (GrowableSeeds[i] is { } oldGrowable)
|
||||
{
|
||||
// Kill the plant if it's somehow removed
|
||||
oldGrowable.Decayed = true;
|
||||
oldGrowable.IsActive = false;
|
||||
}
|
||||
|
||||
GrowableSeeds[i] = null;
|
||||
}
|
||||
}
|
||||
|
||||
// server handles this
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
|
||||
|
||||
float delay = GrowthTickDelay;
|
||||
if (Fertilizer > 0)
|
||||
{
|
||||
delay /= 2f;
|
||||
Fertilizer -= deltaTime / 10f;
|
||||
}
|
||||
|
||||
if (growthTickTimer > delay)
|
||||
{
|
||||
for (var i = 0; i < GrowableSeeds.Length; i++)
|
||||
{
|
||||
PlantSlot slot = PlantSlots.ContainsKey(i) ? PlantSlots[i] : NullSlot;
|
||||
Growable? seed = GrowableSeeds[i];
|
||||
seed?.OnGrowthTick(this, slot);
|
||||
}
|
||||
|
||||
growthTickTimer = 0;
|
||||
}
|
||||
else if (Item.ParentInventory == null)
|
||||
{
|
||||
if (item.GetComponent<Holdable>() is { } holdable)
|
||||
{
|
||||
if (holdable.Attachable && !holdable.Attached)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
growthTickTimer += deltaTime;
|
||||
}
|
||||
}
|
||||
|
||||
private SuitablePlantItem GetSuitableItem(Character character)
|
||||
{
|
||||
foreach (Item heldItem in character.SelectedItems)
|
||||
{
|
||||
if (heldItem == null) { continue; }
|
||||
|
||||
if (container?.Inventory != null && !container.Inventory.IsFull())
|
||||
{
|
||||
if (heldItem.GetComponent<Growable>() != null && SuitableSeeds.Any(ri => ri.MatchesItem(heldItem)))
|
||||
{
|
||||
return new SuitablePlantItem(heldItem, PlantItemType.Seed, MsgPlanting);
|
||||
}
|
||||
}
|
||||
|
||||
if (SuitableFertilizer.Any(ri => ri.MatchesItem(heldItem)))
|
||||
{
|
||||
return new SuitablePlantItem(heldItem, PlantItemType.Fertilizer, MsgFertilizing);
|
||||
}
|
||||
}
|
||||
|
||||
return NullItem;
|
||||
}
|
||||
|
||||
private bool HasAnyFinishedGrowing() => GrowableSeeds.Any(seed => seed != null && (seed.FullyGrown || seed.Decayed));
|
||||
}
|
||||
}
|
||||
@@ -121,7 +121,6 @@ namespace Barotrauma.Items.Components
|
||||
: base(item, element)
|
||||
{
|
||||
IsActive = true;
|
||||
|
||||
InitProjSpecific();
|
||||
}
|
||||
|
||||
@@ -184,23 +183,25 @@ namespace Barotrauma.Items.Components
|
||||
charge = 0.0f;
|
||||
return;
|
||||
}
|
||||
|
||||
//output starts dropping when the charge is less than 10%
|
||||
float maxOutputRatio = 1.0f;
|
||||
if (chargeRatio < 0.1f)
|
||||
else
|
||||
{
|
||||
maxOutputRatio = Math.Max(chargeRatio * 10.0f, 0.0f);
|
||||
//output starts dropping when the charge is less than 10%
|
||||
float maxOutputRatio = 1.0f;
|
||||
if (chargeRatio < 0.1f)
|
||||
{
|
||||
maxOutputRatio = Math.Max(chargeRatio * 10.0f, 0.0f);
|
||||
}
|
||||
|
||||
CurrPowerOutput += (gridLoad - gridPower) * deltaTime;
|
||||
|
||||
float maxOutput = Math.Min(MaxOutPut * maxOutputRatio, gridLoad);
|
||||
CurrPowerOutput = MathHelper.Clamp(CurrPowerOutput, 0.0f, maxOutput);
|
||||
Charge -= CurrPowerOutput / 3600.0f;
|
||||
}
|
||||
|
||||
CurrPowerOutput += (gridLoad - gridPower) * deltaTime;
|
||||
|
||||
float maxOutput = Math.Min(MaxOutPut * maxOutputRatio, gridLoad);
|
||||
CurrPowerOutput = MathHelper.Clamp(CurrPowerOutput, 0.0f, maxOutput);
|
||||
Charge -= CurrPowerOutput / 3600.0f;
|
||||
|
||||
item.SendSignal(0, ((int)Math.Round(Charge)).ToString(), "charge", null);
|
||||
item.SendSignal(0, ((int)Math.Round((Charge / capacity) * 100)).ToString(), "charge_%", null);
|
||||
item.SendSignal(0, ((int)Math.Round((RechargeSpeed / maxRechargeSpeed) * 100)).ToString(), "charge_rate", null);
|
||||
item.SendSignal(0, ((int)Math.Round(Charge / capacity * 100)).ToString(), "charge_%", null);
|
||||
item.SendSignal(0, ((int)Math.Round(RechargeSpeed / maxRechargeSpeed * 100)).ToString(), "charge_rate", null);
|
||||
}
|
||||
|
||||
public override bool AIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective)
|
||||
|
||||
@@ -113,6 +113,19 @@ namespace Barotrauma.Items.Components
|
||||
powerLoad = 0.0f;
|
||||
currPowerConsumption = 0.0f;
|
||||
SetAllConnectionsDirty();
|
||||
foreach (HashSet<Connection> recipientList in connectedRecipients.Values.ToList())
|
||||
{
|
||||
foreach (Connection c in recipientList)
|
||||
{
|
||||
if (c.Item == item) { continue; }
|
||||
var recipientPowerTransfer = c.Item.GetComponent<PowerTransfer>();
|
||||
if (recipientPowerTransfer != null)
|
||||
{
|
||||
recipientPowerTransfer.SetAllConnectionsDirty();
|
||||
recipientPowerTransfer.RefreshConnections();
|
||||
}
|
||||
}
|
||||
}
|
||||
RefreshConnections();
|
||||
isBroken = true;
|
||||
}
|
||||
@@ -185,29 +198,32 @@ namespace Barotrauma.Items.Components
|
||||
else if (!connectionDirty[c])
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
HashSet<Connection> connected = new HashSet<Connection>();
|
||||
if (!connectedRecipients.ContainsKey(c))
|
||||
{
|
||||
connectedRecipients.Add(c, connected);
|
||||
}
|
||||
else
|
||||
{
|
||||
//mark all previous recipients as dirty
|
||||
foreach (Connection recipient in connectedRecipients[c])
|
||||
{
|
||||
var pt = recipient.Item.GetComponent<PowerTransfer>();
|
||||
if (pt != null) pt.connectionDirty[recipient] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//find all connections that are connected to this one (directly or via another PowerTransfer)
|
||||
connected.Add(c);
|
||||
GetConnected(c, connected);
|
||||
HashSet<Connection> connected = new HashSet<Connection>();
|
||||
if (item.Condition > 0.0f)
|
||||
{
|
||||
if (!connectedRecipients.ContainsKey(c))
|
||||
{
|
||||
connectedRecipients.Add(c, connected);
|
||||
}
|
||||
else
|
||||
{
|
||||
//mark all previous recipients as dirty
|
||||
foreach (Connection recipient in connectedRecipients[c])
|
||||
{
|
||||
var pt = recipient.Item.GetComponent<PowerTransfer>();
|
||||
if (pt != null) pt.connectionDirty[recipient] = true;
|
||||
}
|
||||
}
|
||||
|
||||
connected.Add(c);
|
||||
GetConnected(c, connected);
|
||||
}
|
||||
connectedRecipients[c] = connected;
|
||||
|
||||
//go through all the PowerTransfers and we're connected to and set their connections to match the ones we just calculated
|
||||
//go through all the PowerTransfers that we're connected to and set their connections to match the ones we just calculated
|
||||
//(no need to go through the recursive GetConnected method again)
|
||||
foreach (Connection recipient in connected)
|
||||
{
|
||||
@@ -232,10 +248,10 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
foreach (Connection recipient in recipients)
|
||||
{
|
||||
if (recipient == null || connected.Contains(recipient)) continue;
|
||||
if (recipient == null || connected.Contains(recipient)) { continue; }
|
||||
|
||||
Item it = recipient.Item;
|
||||
if (it == null || it.Condition <= 0.0f) continue;
|
||||
if (it == null || it.Condition <= 0.0f) { continue; }
|
||||
|
||||
connected.Add(recipient);
|
||||
|
||||
|
||||
@@ -131,7 +131,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (!powerOnSoundPlayed && powerOnSound != null)
|
||||
{
|
||||
SoundPlayer.PlaySound(powerOnSound.Sound, item.WorldPosition, powerOnSound.Volume, powerOnSound.Range, item.CurrentHull);
|
||||
SoundPlayer.PlaySound(powerOnSound.Sound, item.WorldPosition, powerOnSound.Volume, powerOnSound.Range, hullGuess: item.CurrentHull);
|
||||
powerOnSoundPlayed = true;
|
||||
}
|
||||
}
|
||||
@@ -250,7 +250,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
if (powered is PowerContainer pc)
|
||||
{
|
||||
if (pc.CurrPowerOutput <= 0.0f) { continue; }
|
||||
if (pc.CurrPowerOutput <= 0.0f || pc.item.Condition <= 0.0f) { continue; }
|
||||
//providing power
|
||||
lastPowerProbeRecipients.Clear();
|
||||
powered.powerOut?.SendPowerProbeSignal(powered.item, pc.CurrPowerOutput);
|
||||
@@ -282,7 +282,7 @@ namespace Barotrauma.Items.Components
|
||||
continue;
|
||||
}
|
||||
var pc = powerSource.Item.GetComponent<PowerContainer>();
|
||||
if (pc != null)
|
||||
if (pc != null && pc.item.Condition > 0.0f)
|
||||
{
|
||||
float voltage = pc.CurrPowerOutput / Math.Max(powered.CurrPowerConsumption, 1.0f);
|
||||
powered.voltage += voltage;
|
||||
|
||||
@@ -407,6 +407,16 @@ namespace Barotrauma.Items.Components
|
||||
return hits;
|
||||
}
|
||||
|
||||
public override void Drop(Character dropper)
|
||||
{
|
||||
if (dropper != null)
|
||||
{
|
||||
Deactivate();
|
||||
Unstick();
|
||||
}
|
||||
base.Drop(dropper);
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
ApplyStatusEffects(ActionType.OnActive, deltaTime, null);
|
||||
@@ -422,10 +432,13 @@ namespace Barotrauma.Items.Components
|
||||
if (item.body.LinearVelocity.LengthSquared() < ContinuousCollisionThreshold * ContinuousCollisionThreshold)
|
||||
{
|
||||
item.body.FarseerBody.IsBullet = false;
|
||||
//projectiles with a stickjoint don't become inactive until the stickjoint is detached
|
||||
if (stickJoint == null) { IsActive = false; }
|
||||
}
|
||||
}
|
||||
//projectiles with a stickjoint don't become inactive until the stickjoint is detached
|
||||
if (stickJoint == null && !item.body.FarseerBody.IsBullet)
|
||||
{
|
||||
IsActive = false;
|
||||
}
|
||||
|
||||
if (stickJoint == null) { return; }
|
||||
|
||||
@@ -511,9 +524,10 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
hits.Add(target.Body);
|
||||
impactQueue.Enqueue(new Impact(target, contact.Manifold.LocalNormal, item.body.LinearVelocity));
|
||||
if (hits.Count() >= MaxTargetsToHit)
|
||||
IsActive = true;
|
||||
if (hits.Count() >= MaxTargetsToHit || target.Body.UserData is VoronoiCell)
|
||||
{
|
||||
item.body.FarseerBody.OnCollision -= OnProjectileCollision;
|
||||
Deactivate();
|
||||
return true;
|
||||
}
|
||||
else
|
||||
@@ -626,20 +640,9 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
target.Body.ApplyLinearImpulse(velocity * item.body.Mass);
|
||||
|
||||
if (hits.Count() >= MaxTargetsToHit)
|
||||
if (hits.Count() >= MaxTargetsToHit || hits.LastOrDefault()?.UserData is VoronoiCell)
|
||||
{
|
||||
item.body.FarseerBody.OnCollision -= OnProjectileCollision;
|
||||
if ((item.Prefab.DamagedByProjectiles || item.Prefab.DamagedByMeleeWeapons) && item.Condition > 0)
|
||||
{
|
||||
item.body.CollisionCategories = Physics.CollisionCharacter;
|
||||
item.body.CollidesWith = Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionPlatform | Physics.CollisionProjectile;
|
||||
}
|
||||
else
|
||||
{
|
||||
item.body.CollisionCategories = Physics.CollisionItem;
|
||||
item.body.CollidesWith = Physics.CollisionWall | Physics.CollisionLevel;
|
||||
}
|
||||
IgnoredBodies.Clear();
|
||||
Deactivate();
|
||||
}
|
||||
|
||||
if (attackResult.AppliedDamageModifiers != null &&
|
||||
@@ -684,11 +687,12 @@ namespace Barotrauma.Items.Components
|
||||
item.body.LinearVelocity *= 0.5f;
|
||||
}
|
||||
|
||||
var containedItems = item.ContainedItems;
|
||||
var containedItems = item.OwnInventory?.Items;
|
||||
if (containedItems != null)
|
||||
{
|
||||
foreach (Item contained in containedItems)
|
||||
{
|
||||
if (contained == null) { continue; }
|
||||
if (contained.body != null)
|
||||
{
|
||||
contained.SetTransform(item.SimPosition, contained.body.Rotation);
|
||||
@@ -704,6 +708,22 @@ namespace Barotrauma.Items.Components
|
||||
return true;
|
||||
}
|
||||
|
||||
private void Deactivate()
|
||||
{
|
||||
item.body.FarseerBody.OnCollision -= OnProjectileCollision;
|
||||
if ((item.Prefab.DamagedByProjectiles || item.Prefab.DamagedByMeleeWeapons) && item.Condition > 0)
|
||||
{
|
||||
item.body.CollisionCategories = Physics.CollisionCharacter;
|
||||
item.body.CollidesWith = Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionPlatform | Physics.CollisionProjectile;
|
||||
}
|
||||
else
|
||||
{
|
||||
item.body.CollisionCategories = Physics.CollisionItem;
|
||||
item.body.CollidesWith = Physics.CollisionWall | Physics.CollisionLevel;
|
||||
}
|
||||
IgnoredBodies.Clear();
|
||||
}
|
||||
|
||||
private void StickToTarget(Body targetBody, Vector2 axis)
|
||||
{
|
||||
if (stickJoint != null) { return; }
|
||||
|
||||
@@ -206,9 +206,12 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (!CheckCharacterSuccess(character))
|
||||
{
|
||||
GameServer.Log($"{GameServer.CharacterLogName(character)} failed to {(action == FixActions.Sabotage ? "sabotage" : "repair")} {item.Name}", ServerLog.MessageType.ItemInteraction);
|
||||
GameMain.Server?.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.ApplyStatusEffect, ActionType.OnFailure, this, character.ID });
|
||||
return false;
|
||||
}
|
||||
|
||||
GameServer.Log($"{GameServer.CharacterLogName(character)} started {(action == FixActions.Sabotage ? "sabotaging" : "repairing")} {item.Name}", ServerLog.MessageType.ItemInteraction);
|
||||
item.CreateServerEvent(this);
|
||||
}
|
||||
#else
|
||||
|
||||
+4
-1
@@ -62,7 +62,10 @@ namespace Barotrauma.Items.Components
|
||||
timeSinceReceived[i] += deltaTime;
|
||||
}
|
||||
float output = Calculate(receivedSignal[0], receivedSignal[1]);
|
||||
item.SendSignal(0, MathHelper.Clamp(output, ClampMin, ClampMax).ToString("G", CultureInfo.InvariantCulture), "signal_out", null);
|
||||
if (MathUtils.IsValid(output))
|
||||
{
|
||||
item.SendSignal(0, MathHelper.Clamp(output, ClampMin, ClampMax).ToString("G", CultureInfo.InvariantCulture), "signal_out", null);
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract float Calculate(float signal1, float signal2);
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
class ConcatComponent : StringComponent
|
||||
{
|
||||
public ConcatComponent(Item item, XElement element)
|
||||
: base(item, element)
|
||||
{
|
||||
}
|
||||
|
||||
protected override string Calculate(string signal1, string signal2)
|
||||
{
|
||||
return signal1 + signal2;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,4 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Xml.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
@@ -13,6 +11,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
protected override float Calculate(float signal1, float signal2)
|
||||
{
|
||||
if (MathUtils.NearlyEqual(signal2, 0)) { return float.NaN; }
|
||||
return signal1 / signal2;
|
||||
}
|
||||
}
|
||||
|
||||
+4
-2
@@ -56,8 +56,10 @@ namespace Barotrauma.Items.Components
|
||||
item.SendSignal(0, Math.Abs(value).ToString("G", CultureInfo.InvariantCulture), "signal_out", null);
|
||||
break;
|
||||
case FunctionType.SquareRoot:
|
||||
double square = value > 0 ? Math.Sqrt(value) : 0;
|
||||
item.SendSignal(0, square.ToString("G", CultureInfo.InvariantCulture), "signal_out", null);
|
||||
if (value > 0)
|
||||
{
|
||||
item.SendSignal(0, Math.Sqrt(value).ToString("G", CultureInfo.InvariantCulture), "signal_out", null);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new NotImplementedException($"Function {Function} has not been implemented.");
|
||||
|
||||
@@ -208,7 +208,8 @@ namespace Barotrauma.Items.Components
|
||||
else
|
||||
{
|
||||
#if CLIENT
|
||||
light.Rotation = -Rotation;
|
||||
light.Rotation = -Rotation - MathHelper.ToRadians(item.Rotation);
|
||||
light.LightSpriteEffect = item.SpriteEffects;
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -265,11 +266,14 @@ namespace Barotrauma.Items.Components
|
||||
switch (connection.Name)
|
||||
{
|
||||
case "toggle":
|
||||
if (!IgnoreContinuousToggle || lastToggleSignalTime < Timing.TotalTime - 0.1)
|
||||
if (signal != "0")
|
||||
{
|
||||
IsOn = !IsOn;
|
||||
if (!IgnoreContinuousToggle || lastToggleSignalTime < Timing.TotalTime - 0.1)
|
||||
{
|
||||
IsOn = !IsOn;
|
||||
}
|
||||
lastToggleSignalTime = Timing.TotalTime;
|
||||
}
|
||||
lastToggleSignalTime = Timing.TotalTime;
|
||||
break;
|
||||
case "set_state":
|
||||
IsOn = signal != "0";
|
||||
|
||||
+23
-6
@@ -1,14 +1,24 @@
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.Networking;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
class MemoryComponent : ItemComponent
|
||||
partial class MemoryComponent : ItemComponent, IServerSerializable
|
||||
{
|
||||
const int MaxValueLength = 256;
|
||||
|
||||
|
||||
private string value;
|
||||
|
||||
[InGameEditable, Serialize("", true, description: "The currently stored signal the item outputs.", alwaysUseInstanceValues: true)]
|
||||
public string Value
|
||||
{
|
||||
get;
|
||||
set;
|
||||
get { return value; }
|
||||
set
|
||||
{
|
||||
if (value == null) { return; }
|
||||
this.value = value.Length <= MaxValueLength ? value : value.Substring(0, MaxValueLength);
|
||||
}
|
||||
}
|
||||
|
||||
protected bool writeable = true;
|
||||
@@ -24,15 +34,22 @@ namespace Barotrauma.Items.Components
|
||||
item.SendSignal(0, Value, "signal_out", null);
|
||||
}
|
||||
|
||||
partial void OnStateChanged();
|
||||
|
||||
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 (writeable) { Value = signal; }
|
||||
if (writeable)
|
||||
{
|
||||
if (Value == signal) { return; }
|
||||
Value = signal;
|
||||
OnStateChanged();
|
||||
}
|
||||
break;
|
||||
case "signal_store":
|
||||
writeable = (signal == "1");
|
||||
writeable = signal == "1";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
abstract class StringComponent : ItemComponent
|
||||
{
|
||||
//an array to keep track of how long ago a signal was received on both inputs
|
||||
protected float[] timeSinceReceived;
|
||||
|
||||
protected string[] receivedSignal;
|
||||
|
||||
//the output is sent if both inputs have received a signal within the timeframe
|
||||
protected float timeFrame;
|
||||
|
||||
|
||||
[InGameEditable(DecimalCount = 2),
|
||||
Serialize(0.0f, true, description: "The item must have received signals to both inputs within this timeframe to output the result." +
|
||||
" If set to 0, the inputs must be received at the same time.", alwaysUseInstanceValues: true)]
|
||||
public float TimeFrame
|
||||
{
|
||||
get { return timeFrame; }
|
||||
set
|
||||
{
|
||||
timeFrame = Math.Max(0.0f, value);
|
||||
}
|
||||
}
|
||||
|
||||
public StringComponent(Item item, XElement element)
|
||||
: base(item, element)
|
||||
{
|
||||
timeSinceReceived = new float[] { Math.Max(timeFrame * 2.0f, 0.1f), Math.Max(timeFrame * 2.0f, 0.1f) };
|
||||
receivedSignal = new string[2];
|
||||
}
|
||||
|
||||
sealed public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
for (int i = 0; i < timeSinceReceived.Length; i++)
|
||||
{
|
||||
if (timeSinceReceived[i] > timeFrame)
|
||||
{
|
||||
IsActive = false;
|
||||
return;
|
||||
}
|
||||
timeSinceReceived[i] += deltaTime;
|
||||
}
|
||||
string output = Calculate(receivedSignal[0], receivedSignal[1]);
|
||||
item.SendSignal(0, output, "signal_out", null);
|
||||
}
|
||||
|
||||
protected abstract string Calculate(string signal1, string signal2);
|
||||
|
||||
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":
|
||||
receivedSignal[0] = signal;
|
||||
timeSinceReceived[0] = 0.0f;
|
||||
IsActive = true;
|
||||
break;
|
||||
case "signal_in2":
|
||||
receivedSignal[1] = signal;
|
||||
timeSinceReceived[1] = 0.0f;
|
||||
IsActive = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+9
-1
@@ -62,9 +62,15 @@ namespace Barotrauma.Items.Components
|
||||
break;
|
||||
case FunctionType.Tan:
|
||||
if (!UseRadians) { value = MathHelper.ToRadians(value); }
|
||||
item.SendSignal(0, ((float)Math.Tan(value)).ToString("G", CultureInfo.InvariantCulture), "signal_out", null);
|
||||
//tan is undefined if the value is (π / 2) + πk, where k is any integer
|
||||
if (!MathUtils.NearlyEqual(value % MathHelper.Pi, MathHelper.PiOver2))
|
||||
{
|
||||
item.SendSignal(0, ((float)Math.Tan(value)).ToString("G", CultureInfo.InvariantCulture), "signal_out", null);
|
||||
}
|
||||
break;
|
||||
case FunctionType.Asin:
|
||||
//asin is only defined in the range [-1,1]
|
||||
if (value >= -1.0f && value <= 1.0f)
|
||||
{
|
||||
float angle = (float)Math.Asin(value);
|
||||
if (!UseRadians) { angle = MathHelper.ToDegrees(angle); }
|
||||
@@ -72,6 +78,8 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
break;
|
||||
case FunctionType.Acos:
|
||||
//acos is only defined in the range [-1,1]
|
||||
if (value >= -1.0f && value <= 1.0f)
|
||||
{
|
||||
float angle = (float)Math.Acos(value);
|
||||
if (!UseRadians) { angle = MathHelper.ToDegrees(angle); }
|
||||
|
||||
@@ -12,6 +12,8 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
private static readonly List<WifiComponent> list = new List<WifiComponent>();
|
||||
|
||||
const int ChannelMemorySize = 10;
|
||||
|
||||
private float range;
|
||||
|
||||
private int channel;
|
||||
@@ -20,6 +22,8 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
private string prevSignal;
|
||||
|
||||
private int[] channelMemory = new int[ChannelMemorySize];
|
||||
|
||||
[Serialize(Character.TeamType.None, true, description: "WiFi components can only communicate with components that have the same Team ID.", alwaysUseInstanceValues: true)]
|
||||
public Character.TeamType TeamID { get; set; }
|
||||
|
||||
@@ -36,7 +40,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
[InGameEditable, Serialize(1, true, description: "WiFi components can only communicate with components that use the same channel.", alwaysUseInstanceValues: true)]
|
||||
[InGameEditable, Serialize(0, true, description: "WiFi components can only communicate with components that use the same channel.", alwaysUseInstanceValues: true)]
|
||||
public int Channel
|
||||
{
|
||||
get { return channel; }
|
||||
@@ -83,6 +87,18 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
list.Add(this);
|
||||
IsActive = true;
|
||||
channelMemory = element.GetAttributeIntArray("channelmemory", new int[ChannelMemorySize]);
|
||||
}
|
||||
|
||||
public override void OnItemLoaded()
|
||||
{
|
||||
if (channelMemory.All(m => m == 0))
|
||||
{
|
||||
for (int i = 0; i < channelMemory.Length; i++)
|
||||
{
|
||||
channelMemory[i] = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool CanTransmit()
|
||||
@@ -118,6 +134,24 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
public int GetChannelMemory(int index)
|
||||
{
|
||||
if (index < 0 || index >= ChannelMemorySize)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
return channelMemory[index];
|
||||
}
|
||||
|
||||
public void SetChannelMemory(int index, int value)
|
||||
{
|
||||
if (index < 0 || index >= ChannelMemorySize)
|
||||
{
|
||||
return;
|
||||
}
|
||||
channelMemory[index] = MathHelper.Clamp(value, 0, 10000);
|
||||
}
|
||||
|
||||
public void TransmitSignal(int stepsTaken, string signal, Item source, Character sender, bool sendToChat, float signalStrength = 1.0f)
|
||||
{
|
||||
var senderComponent = source?.GetComponent<WifiComponent>();
|
||||
@@ -220,5 +254,12 @@ namespace Barotrauma.Items.Components
|
||||
base.RemoveComponentSpecific();
|
||||
list.Remove(this);
|
||||
}
|
||||
|
||||
public override XElement Save(XElement parentElement)
|
||||
{
|
||||
var element = base.Save(parentElement);
|
||||
element.Add(new XAttribute("channelmemory", string.Join(',', channelMemory)));
|
||||
return element;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,7 +87,14 @@ namespace Barotrauma.Items.Components
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
|
||||
[Serialize(false, false, description: "If enabled, the wire will not be visible in connection panels outside the submarine editor.")]
|
||||
public bool HiddenInGame
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public Wire(Item item, XElement element)
|
||||
: base(item, element)
|
||||
{
|
||||
@@ -673,12 +680,13 @@ namespace Barotrauma.Items.Components
|
||||
closestDist = 0.0f;
|
||||
int closestIndex = -1;
|
||||
|
||||
maxDist *= maxDist;
|
||||
for (int i = 0; i < nodes.Count-1; i++)
|
||||
{
|
||||
if ((Math.Abs(nodes[i].X - nodes[i + 1].X)<5 || Math.Sign(mousePos.X - nodes[i].X) != Math.Sign(mousePos.X - nodes[i + 1].X)) &&
|
||||
(Math.Abs(nodes[i].Y - nodes[i + 1].Y)<5 || Math.Sign(mousePos.Y - nodes[i].Y) != Math.Sign(mousePos.Y - nodes[i + 1].Y)))
|
||||
{
|
||||
float dist = MathUtils.LineToPointDistance(nodes[i], nodes[i + 1], mousePos);
|
||||
float dist = MathUtils.LineToPointDistanceSquared(nodes[i], nodes[i + 1], mousePos);
|
||||
if (dist > maxDist) continue;
|
||||
|
||||
if (closestIndex == -1 || dist < closestDist)
|
||||
@@ -688,12 +696,15 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
}
|
||||
closestDist = (float)Math.Sqrt(closestDist);
|
||||
|
||||
return closestIndex;
|
||||
}
|
||||
|
||||
public override void FlipX(bool relativeToSub)
|
||||
{
|
||||
if (item.ParentInventory != null) { return; }
|
||||
|
||||
Vector2 refPos = item.Submarine == null ?
|
||||
Vector2.Zero :
|
||||
item.Position - item.Submarine.HiddenSubPosition;
|
||||
|
||||
@@ -198,14 +198,14 @@ namespace Barotrauma.Items.Components
|
||||
private set;
|
||||
}
|
||||
|
||||
private float baseRotationRad;
|
||||
[Editable(0.0f, 360.0f), Serialize(0.0f, true, description: "The angle of the turret's base in degrees.", alwaysUseInstanceValues: true)]
|
||||
float prevBaseRotation;
|
||||
[Serialize(0.0f, true, description: "The angle of the turret's base in degrees.", alwaysUseInstanceValues: true)]
|
||||
public float BaseRotation
|
||||
{
|
||||
get { return MathHelper.ToDegrees(baseRotationRad); }
|
||||
get { return item.Rotation; }
|
||||
set
|
||||
{
|
||||
baseRotationRad = MathHelper.ToRadians(value);
|
||||
item.Rotation = value;
|
||||
UpdateTransformedBarrelPos();
|
||||
}
|
||||
}
|
||||
@@ -250,14 +250,15 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
private void UpdateTransformedBarrelPos()
|
||||
{
|
||||
float flippedRotation = BaseRotation;
|
||||
if (item.FlippedX) flippedRotation = -flippedRotation;
|
||||
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);
|
||||
#if CLIENT
|
||||
item.ResetCachedVisibleSize();
|
||||
item.SpriteRotation = MathHelper.ToRadians(flippedRotation);
|
||||
#endif
|
||||
item.Rotation = flippedRotation;
|
||||
prevBaseRotation = item.Rotation;
|
||||
}
|
||||
|
||||
public override void OnItemLoaded()
|
||||
@@ -271,7 +272,7 @@ namespace Barotrauma.Items.Components
|
||||
if (lightComponent != null)
|
||||
{
|
||||
lightComponent.Parent = null;
|
||||
lightComponent.Rotation = rotation;
|
||||
lightComponent.Rotation = Rotation - MathHelper.ToRadians(item.Rotation);
|
||||
lightComponent.Light.Rotation = -rotation;
|
||||
}
|
||||
#endif
|
||||
@@ -283,6 +284,10 @@ namespace Barotrauma.Items.Components
|
||||
this.cam = cam;
|
||||
|
||||
if (reload > 0.0f) { reload -= deltaTime; }
|
||||
if (!MathUtils.NearlyEqual(item.Rotation, prevBaseRotation))
|
||||
{
|
||||
UpdateTransformedBarrelPos();
|
||||
}
|
||||
|
||||
if (user != null && user.Removed)
|
||||
{
|
||||
@@ -344,7 +349,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
if (lightComponent != null)
|
||||
{
|
||||
lightComponent.Rotation = rotation;
|
||||
lightComponent.Rotation = Rotation - MathHelper.ToRadians(item.Rotation);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -504,6 +509,15 @@ namespace Barotrauma.Items.Components
|
||||
projectileComponent.Use((float)Timing.Step);
|
||||
projectile.GetComponent<Rope>()?.Attach(item, projectile);
|
||||
projectileComponent.User = user;
|
||||
|
||||
if (item.Submarine != null && projectile.body != null)
|
||||
{
|
||||
Vector2 velocitySum = item.Submarine.PhysicsBody.LinearVelocity + projectile.body.LinearVelocity;
|
||||
if (velocitySum.LengthSquared() < NetConfig.MaxPhysicsBodyVelocity * NetConfig.MaxPhysicsBodyVelocity * 0.9f)
|
||||
{
|
||||
projectile.body.LinearVelocity = velocitySum;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (projectile.Container != null) { projectile.Container.RemoveContained(projectile); }
|
||||
@@ -983,26 +997,8 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public override void FlipY(bool relativeToSub)
|
||||
{
|
||||
baseRotationRad = MathUtils.WrapAngleTwoPi(baseRotationRad - MathHelper.Pi);
|
||||
BaseRotation = MathHelper.ToDegrees(MathUtils.WrapAngleTwoPi(MathHelper.ToRadians(BaseRotation - 180)));
|
||||
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)
|
||||
@@ -1012,6 +1008,7 @@ namespace Barotrauma.Items.Components
|
||||
case "position_in":
|
||||
if (float.TryParse(signal, NumberStyles.Float, CultureInfo.InvariantCulture, out float newRotation))
|
||||
{
|
||||
if (!MathUtils.IsValid(newRotation)) { return; }
|
||||
targetRotation = MathHelper.ToRadians(newRotation);
|
||||
IsActive = true;
|
||||
}
|
||||
@@ -1030,11 +1027,17 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
break;
|
||||
case "toggle_light":
|
||||
if (lightComponent != null)
|
||||
if (lightComponent != null && signal != "0")
|
||||
{
|
||||
lightComponent.IsOn = !lightComponent.IsOn;
|
||||
}
|
||||
break;
|
||||
case "set_light":
|
||||
if (lightComponent != null)
|
||||
{
|
||||
lightComponent.IsOn = signal != "0";
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user