Release 1.9.7.0 - Summer Update 2025
This commit is contained in:
@@ -179,7 +179,8 @@ namespace Barotrauma.Items.Components
|
||||
var prevDockingTarget = DockingTarget;
|
||||
Undock(applyEffects: false);
|
||||
Dock(prevDockingTarget);
|
||||
Lock(isNetworkMessage: true, applyEffects: false);
|
||||
//don't move subs at this point, it will mess up the placement logic when flipping multi-part subs
|
||||
Lock(isNetworkMessage: true, applyEffects: false, moveSubs: false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -281,7 +282,7 @@ namespace Barotrauma.Items.Components
|
||||
OnDocked = null;
|
||||
}
|
||||
|
||||
public void Lock(bool isNetworkMessage, bool applyEffects = true)
|
||||
public void Lock(bool isNetworkMessage, bool applyEffects = true, bool moveSubs = true)
|
||||
{
|
||||
#if CLIENT
|
||||
if (GameMain.Client != null && !isNetworkMessage) { return; }
|
||||
@@ -312,16 +313,19 @@ namespace Barotrauma.Items.Components
|
||||
ApplyStatusEffects(ActionType.OnUse, 1.0f);
|
||||
}
|
||||
|
||||
Vector2 jointDiff = joint.WorldAnchorB - joint.WorldAnchorA;
|
||||
if (item.Submarine.PhysicsBody.Mass < DockingTarget.item.Submarine.PhysicsBody.Mass ||
|
||||
DockingTarget.item.Submarine.Info.IsOutpost)
|
||||
if (moveSubs)
|
||||
{
|
||||
item.Submarine.SubBody.SetPosition(item.Submarine.SubBody.Position + ConvertUnits.ToDisplayUnits(jointDiff));
|
||||
}
|
||||
else if (DockingTarget.item.Submarine.PhysicsBody.Mass < item.Submarine.PhysicsBody.Mass ||
|
||||
item.Submarine.Info.IsOutpost)
|
||||
{
|
||||
DockingTarget.item.Submarine.SubBody.SetPosition(DockingTarget.item.Submarine.SubBody.Position - ConvertUnits.ToDisplayUnits(jointDiff));
|
||||
Vector2 jointDiff = joint.WorldAnchorB - joint.WorldAnchorA;
|
||||
if (item.Submarine.PhysicsBody.Mass < DockingTarget.item.Submarine.PhysicsBody.Mass ||
|
||||
DockingTarget.item.Submarine.Info.IsOutpost)
|
||||
{
|
||||
item.Submarine.SubBody.SetPosition(item.Submarine.SubBody.Position + ConvertUnits.ToDisplayUnits(jointDiff));
|
||||
}
|
||||
else if (DockingTarget.item.Submarine.PhysicsBody.Mass < item.Submarine.PhysicsBody.Mass ||
|
||||
item.Submarine.Info.IsOutpost)
|
||||
{
|
||||
DockingTarget.item.Submarine.SubBody.SetPosition(DockingTarget.item.Submarine.SubBody.Position - ConvertUnits.ToDisplayUnits(jointDiff));
|
||||
}
|
||||
}
|
||||
|
||||
ConnectWireBetweenPorts();
|
||||
|
||||
@@ -349,31 +349,31 @@ namespace Barotrauma.Items.Components
|
||||
// used for debugging where a vine failed to grow
|
||||
public readonly HashSet<Rectangle> FailedRectangles = new HashSet<Rectangle>();
|
||||
|
||||
[Serialize(1f, IsPropertySaveable.Yes, "How fast the plant grows.")]
|
||||
[Serialize(1f, IsPropertySaveable.Yes, "How fast the plant grows. Value of 1 means a vine attempts to grow every 10 seconds while 2 and 0.5 mean every 5 and 20 seconds respectively.")]
|
||||
public float GrowthSpeed { get; set; }
|
||||
|
||||
[Serialize(100f, IsPropertySaveable.Yes, "How long the plant can go without watering.")]
|
||||
public float MaxHealth { get; set; }
|
||||
[Serialize(100f, IsPropertySaveable.Yes, "How much water the plant can hold. Affects how long the plant can survive without water.")]
|
||||
public float MaxWater { get; set; }
|
||||
|
||||
[Serialize(1f, IsPropertySaveable.Yes, "How much damage the plant takes while in water.")]
|
||||
public float FloodTolerance { get; set; }
|
||||
[Serialize(1f, IsPropertySaveable.Yes, "How much extra water the plant uses per second while it is submerged in a flooded hull.")]
|
||||
public float ExtraWaterUsedPerSecondWhileFlooded { get; set; }
|
||||
|
||||
[Serialize(1f, IsPropertySaveable.Yes, "How much damage the plant takes while growing.")]
|
||||
public float Hardiness { get; set; }
|
||||
[Serialize(1f, IsPropertySaveable.Yes, "How much water the plant consumes passively per second.")]
|
||||
public float WaterUsedPerSecond { get; set; }
|
||||
|
||||
[Serialize(0.01f, IsPropertySaveable.Yes, "How often a seed is produced.")]
|
||||
public float SeedRate { get; set; }
|
||||
[Serialize(0.01f, IsPropertySaveable.Yes, "Percentage chance of a seed item being produced on growth ticks (every 10 seconds without a multiplier). 0.01 means 1% chance. Not used in vanilla plants.")]
|
||||
public float SeedSpawnChance { get; set; }
|
||||
|
||||
[Serialize(0.01f, IsPropertySaveable.Yes, "How often a product item is produced.")]
|
||||
public float ProductRate { get; set; }
|
||||
[Serialize(0.01f, IsPropertySaveable.Yes, "How often a product item is produced on growth ticks (every 10 seconds without a multiplier). 0.01 means 1% chance.")]
|
||||
public float ProductSpawnChance { get; set; }
|
||||
|
||||
[Serialize(0.5f, IsPropertySaveable.Yes, "Probability of an attribute being randomly modified in a newly produced seed.")]
|
||||
[Serialize(0.5f, IsPropertySaveable.Yes, "Completely unused property that was added on the first design pass but due to the first pass being too complex was never used and now it is used by mods so it cannot be removed.")]
|
||||
public float MutationProbability { get; set; }
|
||||
|
||||
[Serialize("1.0,1.0,1.0,1.0", IsPropertySaveable.Yes, "Color of the flowers.")]
|
||||
public Color FlowerTint { get; set; }
|
||||
|
||||
[Serialize(3, IsPropertySaveable.Yes, "Number of flowers drawn when fully grown")]
|
||||
[Serialize(3, IsPropertySaveable.Yes, "Number of flowers drawn.")]
|
||||
public int FlowerQuantity { get; set; }
|
||||
|
||||
[Serialize(0.25f, IsPropertySaveable.Yes, "Size of the flower sprites.")]
|
||||
@@ -403,9 +403,12 @@ namespace Barotrauma.Items.Components
|
||||
[Serialize("1,1,1,1", IsPropertySaveable.Yes, "Probability for the plant to grow in a direction.")]
|
||||
public Vector4 GrowthWeights { get; set; }
|
||||
|
||||
[Serialize(0.0f, IsPropertySaveable.Yes, "How much damage is taken from fires.")]
|
||||
[Serialize(0.0f, IsPropertySaveable.Yes, "How much water is lost due to fires every 10 seconds.")]
|
||||
public float FireVulnerability { get; set; }
|
||||
|
||||
[Serialize("0.0, 0.0", IsPropertySaveable.Yes, "Modifier to the percentage of product and seed items produced before the plant is fully grown based on how many vines have been grown. 0 would mean no products or seeds are produced while 0.5 would mean half of the normal amount.")]
|
||||
public Vector2 LinearProductAndSeedMultiplierBeforeFullyGrown { get; set; }
|
||||
|
||||
private const float increasedDeathSpeed = 10f;
|
||||
private bool accelerateDeath;
|
||||
private float health;
|
||||
@@ -417,7 +420,7 @@ namespace Barotrauma.Items.Components
|
||||
public float Health
|
||||
{
|
||||
get => health;
|
||||
set => health = Math.Clamp(value, 0, MaxHealth);
|
||||
set => health = Math.Clamp(value, 0, MaxWater);
|
||||
}
|
||||
|
||||
public bool Decayed { get; set; }
|
||||
@@ -441,7 +444,14 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
SerializableProperty.DeserializeProperties(this, element);
|
||||
|
||||
Health = MaxHealth;
|
||||
// backwards compatibility
|
||||
MaxWater = element.GetAttributeFloat("maxhealth", MaxWater);
|
||||
WaterUsedPerSecond = element.GetAttributeFloat("hardiness", WaterUsedPerSecond);
|
||||
ExtraWaterUsedPerSecondWhileFlooded = element.GetAttributeFloat("floodtolerance", ExtraWaterUsedPerSecondWhileFlooded);
|
||||
ProductSpawnChance = element.GetAttributeFloat("productrate", ProductSpawnChance);
|
||||
SeedSpawnChance = element.GetAttributeFloat("seedrate", SeedSpawnChance);
|
||||
|
||||
Health = MaxWater;
|
||||
|
||||
if (element.HasElements)
|
||||
{
|
||||
@@ -492,10 +502,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (Decayed) { return; }
|
||||
|
||||
if (FullyGrown)
|
||||
{
|
||||
TryGenerateProduct(planter, slot);
|
||||
}
|
||||
TryGenerateProduct(planter, slot);
|
||||
|
||||
if (Health > 0)
|
||||
{
|
||||
@@ -504,11 +511,11 @@ namespace Barotrauma.Items.Components
|
||||
// fertilizer makes the plant tick faster, compensate by halving water requirement
|
||||
float multipler = planter.Fertilizer > 0 ? 0.5f : 1f;
|
||||
|
||||
Health -= (accelerateDeath ? Hardiness * increasedDeathSpeed : Hardiness) * multipler;
|
||||
Health -= (accelerateDeath ? WaterUsedPerSecond * increasedDeathSpeed : WaterUsedPerSecond) * multipler;
|
||||
|
||||
if (planter.Item.InWater)
|
||||
{
|
||||
Health -= FloodTolerance * multipler;
|
||||
Health -= ExtraWaterUsedPerSecondWhileFlooded * multipler;
|
||||
}
|
||||
#if SERVER
|
||||
if (FullyGrown)
|
||||
@@ -535,7 +542,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
private void UpdateBranchHealth()
|
||||
{
|
||||
Color healthColor = Color.White * (1.0f - Health / MaxHealth);
|
||||
Color healthColor = Color.White * (1.0f - Health / MaxWater);
|
||||
foreach (VineTile vine in Vines)
|
||||
{
|
||||
vine.HealthColor = healthColor;
|
||||
@@ -546,11 +553,24 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
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;
|
||||
float spawnChanceMultiplier = 1f;
|
||||
|
||||
if (!FullyGrown)
|
||||
{
|
||||
if (LinearProductAndSeedMultiplierBeforeFullyGrown.NearlyEquals(Vector2.Zero)) { return; }
|
||||
|
||||
float growthProgress = Vines.Count / (float)MaximumVines;
|
||||
|
||||
spawnChanceMultiplier = MathHelper.Lerp(LinearProductAndSeedMultiplierBeforeFullyGrown.X, LinearProductAndSeedMultiplierBeforeFullyGrown.Y, growthProgress);
|
||||
|
||||
if (MathUtils.NearlyEqual(spawnChanceMultiplier, 0f)) { return; }
|
||||
}
|
||||
|
||||
|
||||
bool spawnProduct = Rand.Range(0f, 1f) < (ProductSpawnChance * spawnChanceMultiplier),
|
||||
spawnSeed = Rand.Range(0f, 1f) < (SeedSpawnChance * spawnChanceMultiplier);
|
||||
|
||||
Vector2 spawnPos;
|
||||
|
||||
@@ -678,7 +698,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
fireCheckCooldown = 5f;
|
||||
fireCheckCooldown = 10f;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Barotrauma.Abilities;
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Networking;
|
||||
using FarseerPhysics;
|
||||
using FarseerPhysics.Dynamics;
|
||||
@@ -44,7 +45,12 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
private bool attachable, attached, attachedByDefault;
|
||||
private Voronoi2.VoronoiCell attachTargetCell;
|
||||
private PhysicsBody body;
|
||||
|
||||
/// <summary>
|
||||
/// The item's original physics body (if one exists). When the item is attached to a wall, it's <see cref="Item.body"/> gets set to null,
|
||||
/// and we use this field to keep track of the original body.
|
||||
/// </summary>
|
||||
private PhysicsBody originalBody;
|
||||
|
||||
public readonly ImmutableDictionary<StatTypes, float> HoldableStatValues;
|
||||
|
||||
@@ -62,7 +68,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public PhysicsBody Body
|
||||
{
|
||||
get { return item.body ?? body; }
|
||||
get { return item.body ?? originalBody; }
|
||||
}
|
||||
|
||||
[Serialize(false, IsPropertySaveable.Yes, description: "Is the item currently attached to a wall (only valid if Attachable is set to true).")]
|
||||
@@ -84,6 +90,9 @@ namespace Barotrauma.Items.Components
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(0f, IsPropertySaveable.Yes, description: "Camera offset to apply when aiming this item. Only valid if Aimable is set to true.")]
|
||||
public float CameraAimOffset { get; set; }
|
||||
|
||||
[Serialize(false, IsPropertySaveable.No, description: "Should the character adjust its pose when aiming with the item. Most noticeable underwater, where the character will rotate its entire body to face the direction the item is aimed at.")]
|
||||
public bool ControlPose
|
||||
{
|
||||
@@ -119,6 +128,32 @@ namespace Barotrauma.Items.Components
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(false, IsPropertySaveable.No, description: "When enabled, the item can only be attached to a position where it touches the floor.")]
|
||||
public bool AttachesToFloor
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(true, IsPropertySaveable.No, description: "Can the item be attached inside doors?")]
|
||||
public bool AllowAttachInsideDoors
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
private HashSet<Identifier> disallowAttachingOverTags = new HashSet<Identifier>();
|
||||
|
||||
[Editable, Serialize("", IsPropertySaveable.Yes)]
|
||||
public string DisallowAttachingOverTags
|
||||
{
|
||||
get => disallowAttachingOverTags.ConvertToString();
|
||||
set
|
||||
{
|
||||
disallowAttachingOverTags = value.ToIdentifiers().ToHashSet();
|
||||
}
|
||||
}
|
||||
|
||||
[Serialize(false, IsPropertySaveable.No, description: "Should the item be attached to a wall by default when it's placed in the submarine editor.")]
|
||||
public bool AttachedByDefault
|
||||
{
|
||||
@@ -275,7 +310,7 @@ namespace Barotrauma.Items.Components
|
||||
public Holdable(Item item, ContentXElement element)
|
||||
: base(item, element)
|
||||
{
|
||||
body = item.body;
|
||||
originalBody = item.body;
|
||||
|
||||
Pusher = null;
|
||||
if (element.GetAttributeBool("blocksplayers", false))
|
||||
@@ -411,9 +446,9 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
if (attachable)
|
||||
{
|
||||
if (body != null)
|
||||
if (originalBody != null)
|
||||
{
|
||||
item.body = body;
|
||||
item.body = originalBody;
|
||||
}
|
||||
DeattachFromWall();
|
||||
}
|
||||
@@ -514,9 +549,9 @@ namespace Barotrauma.Items.Components
|
||||
if (character != null) { item.Submarine = character.Submarine; }
|
||||
if (item.body == null)
|
||||
{
|
||||
if (body != null)
|
||||
if (originalBody != null)
|
||||
{
|
||||
item.body = body;
|
||||
item.body = originalBody;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -565,13 +600,45 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public bool CanBeAttached(Character user)
|
||||
{
|
||||
return CanBeAttached(user, out _);
|
||||
}
|
||||
|
||||
private static List<Item> tempOverlappingItems = new List<Item>();
|
||||
|
||||
private bool CanBeAttached(Character user, out IEnumerable<Item> overlappingItems)
|
||||
{
|
||||
tempOverlappingItems.Clear();
|
||||
overlappingItems = tempOverlappingItems;
|
||||
if (!attachable || !Reattachable) { return false; }
|
||||
|
||||
//can be attached anywhere in sub editor
|
||||
if (Screen.Selected == GameMain.SubEditorScreen) { return true; }
|
||||
|
||||
if (AttachesToFloor && item.CurrentHull == null) { return false; }
|
||||
|
||||
Vector2 attachPos = user == null ? item.WorldPosition : GetAttachPosition(user, useWorldCoordinates: true);
|
||||
|
||||
if (disallowAttachingOverTags.Any() || !AllowAttachInsideDoors)
|
||||
{
|
||||
var connectedHulls = item.CurrentHull?.GetConnectedHulls(includingThis: true, searchDepth: 5, ignoreClosedGaps: true);
|
||||
Vector2 size = item.Rect.Size.ToVector2() / 2;
|
||||
foreach (Item otherItem in Item.ItemList)
|
||||
{
|
||||
if (otherItem == item || otherItem.body is { BodyType: BodyType.Dynamic, Enabled: true }) { continue; }
|
||||
if (connectedHulls != null && !connectedHulls.Contains(otherItem.CurrentHull)) { continue; }
|
||||
if (disallowAttachingOverTags.None(tag => otherItem.HasTag(tag)) &&
|
||||
(otherItem.GetComponent<Door>() == null || AllowAttachInsideDoors))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
Rectangle worldRect = otherItem.WorldRect;
|
||||
if (attachPos.X + size.X < worldRect.X || attachPos.X - size.X > worldRect.Right) { continue; }
|
||||
if (attachPos.Y - size.Y > worldRect.Y || attachPos.Y + size.Y < worldRect.Y - worldRect.Height) { continue; }
|
||||
tempOverlappingItems.Add(otherItem);
|
||||
}
|
||||
if (tempOverlappingItems.Any()) { return false; }
|
||||
}
|
||||
|
||||
//can be attached anywhere inside hulls
|
||||
if (item.CurrentHull != null && Submarine.RectContains(item.CurrentHull.WorldRect, attachPos)) { return true; }
|
||||
|
||||
@@ -670,14 +737,19 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (!attachable) { return; }
|
||||
|
||||
if (body == null)
|
||||
if (originalBody == null)
|
||||
{
|
||||
throw new InvalidOperationException($"Tried to attach an item with no physics body to a wall ({item.Prefab.Identifier}).");
|
||||
}
|
||||
|
||||
body.Enabled = false;
|
||||
body.SetTransformIgnoreContacts(body.SimPosition, rotation: 0.0f);
|
||||
item.body = null;
|
||||
originalBody.Enabled = false;
|
||||
originalBody.SetTransformIgnoreContacts(originalBody.SimPosition, rotation: 0.0f);
|
||||
if (item.body != null)
|
||||
{
|
||||
item.body.Dir = 1;
|
||||
item.body = null;
|
||||
}
|
||||
item.GetComponents<LightComponent>().ForEach(static light => light.SetLightSourceTransform());
|
||||
|
||||
//outside hulls/subs -> we need to check if the item is being attached on a structure outside the sub
|
||||
if (item.CurrentHull == null && item.Submarine == null)
|
||||
@@ -689,7 +761,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
//set to submarine-relative position
|
||||
item.SetTransform(ConvertUnits.ToSimUnits(item.WorldPosition - attachTarget.Submarine.Position), 0.0f, false);
|
||||
body.SetTransformIgnoreContacts(item.SimPosition, 0.0f);
|
||||
originalBody.SetTransformIgnoreContacts(item.SimPosition, 0.0f);
|
||||
}
|
||||
item.Submarine = attachTarget.Submarine;
|
||||
}
|
||||
@@ -833,6 +905,9 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
item.Drop(character);
|
||||
item.SetTransform(ConvertUnits.ToSimUnits(GetAttachPosition(character)), 0.0f, findNewHull: false);
|
||||
//don't find the new hull in SetTransform, because that'd also potentially change the submarine (teleport the item outside if it's attached outside)
|
||||
//instead just find the hull, so the item is considered to be in the right hull
|
||||
item.CurrentHull = Hull.FindHull(item.WorldPosition, item.CurrentHull);
|
||||
//the light source won't get properly updated if lighting is disabled (even though the light sprite is still drawn when lighting is disabled)
|
||||
//so let's ensure the light source is up-to-date
|
||||
RefreshLightSources(item);
|
||||
@@ -869,34 +944,60 @@ namespace Barotrauma.Items.Components
|
||||
Vector2 mouseDiff = user.CursorWorldPosition - user.WorldPosition;
|
||||
mouseDiff = mouseDiff.ClampLength(MaxAttachDistance);
|
||||
|
||||
Vector2 submarinePos = useWorldCoordinates && user.Submarine != null ? user.Submarine.Position : Vector2.Zero;
|
||||
Vector2 userPos = useWorldCoordinates ? user.WorldPosition : user.Position;
|
||||
Vector2 attachPos = userPos + mouseDiff;
|
||||
|
||||
Vector2 halfSize = new Vector2(item.Rect.Width, item.Rect.Height) / 2;
|
||||
|
||||
//offset the position by half the size of the grid to get the item to adhere to the grid in the same way as in the sub editor
|
||||
//in the sub editor, we align the top-left corner of the item with the grid
|
||||
//but here the origin of the item is placed at the attach position, so we need to offset it
|
||||
Vector2 offset = new Vector2(
|
||||
-(item.Rect.Width / 2) % Submarine.GridSize.X,
|
||||
(item.Rect.Height / 2) % Submarine.GridSize.Y);
|
||||
-halfSize.X % Submarine.GridSize.X,
|
||||
halfSize.Y % Submarine.GridSize.Y);
|
||||
|
||||
if (user.Submarine != null)
|
||||
{
|
||||
//we must add some "padding" to the raycast to ensure it reaches all the way to a wall
|
||||
//otherwise the cursor might be outside a wall, but the grid cell it's in might be partially inside
|
||||
Vector2 padding = Submarine.GridSize * new Vector2(Math.Sign(mouseDiff.X), Math.Sign(mouseDiff.Y));
|
||||
Vector2 padding = halfSize * new Vector2(Math.Sign(mouseDiff.X), Math.Sign(mouseDiff.Y));
|
||||
|
||||
if (Submarine.PickBody(
|
||||
ConvertUnits.ToSimUnits(user.Position),
|
||||
ConvertUnits.ToSimUnits(user.Position + mouseDiff + padding), collisionCategory: Physics.CollisionWall) != null)
|
||||
ConvertUnits.ToSimUnits(user.Position + mouseDiff + padding), collisionCategory: Physics.CollisionWall,
|
||||
/*don't ignore sensors so the raycast can hit open doors or broken walls*/
|
||||
ignoreSensors: AllowAttachInsideDoors, customPredicate: (Fixture fixture) =>
|
||||
{
|
||||
if (fixture.UserData is Door) { return false; }
|
||||
return true;
|
||||
}) != null)
|
||||
{
|
||||
attachPos = userPos + mouseDiff * Submarine.LastPickedFraction + offset;
|
||||
|
||||
Vector2 pickedPos = userPos + mouseDiff * Submarine.LastPickedFraction + offset - submarinePos;
|
||||
//round down if we're placing on the right side and vice versa: ensures we don't round the position inside a wall
|
||||
return
|
||||
attachPos =
|
||||
new Vector2(
|
||||
(mouseDiff.X > 0 ? MathF.Floor(attachPos.X / Submarine.GridSize.X) : MathF.Ceiling(attachPos.X / Submarine.GridSize.X)) * Submarine.GridSize.X,
|
||||
(mouseDiff.Y > 0 ? MathF.Floor(attachPos.Y / Submarine.GridSize.Y) : MathF.Ceiling(attachPos.Y / Submarine.GridSize.Y)) * Submarine.GridSize.Y)
|
||||
- offset;
|
||||
RoundToGrid(pickedPos.X, Submarine.GridSize.X, roundingDir: -Math.Sign(mouseDiff.X)),
|
||||
RoundToGrid(pickedPos.Y, Submarine.GridSize.Y, roundingDir: -Math.Sign(mouseDiff.Y)))
|
||||
- offset + submarinePos;
|
||||
}
|
||||
|
||||
if (AttachesToFloor)
|
||||
{
|
||||
//if attaching to floor, do a raycast down and move the attach pos where it hits
|
||||
float size = item.Rect.Height / 2.0f;
|
||||
Vector2 rayStart = attachPos - submarinePos;
|
||||
Vector2 rayEnd = rayStart - Vector2.UnitY * MaxAttachDistance * 2;
|
||||
if (Submarine.PickBody(
|
||||
ConvertUnits.ToSimUnits(rayStart),
|
||||
ConvertUnits.ToSimUnits(rayEnd), collisionCategory: Physics.CollisionWall | Physics.CollisionPlatform) != null)
|
||||
{
|
||||
attachPos = ConvertUnits.ToDisplayUnits(Submarine.LastPickedPosition) + Vector2.UnitY * size + submarinePos;
|
||||
}
|
||||
else
|
||||
{
|
||||
return Vector2.Zero;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (Level.Loaded != null)
|
||||
@@ -919,9 +1020,30 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
return new Vector2(
|
||||
MathUtils.RoundTowardsClosest(attachPos.X + offset.X, Submarine.GridSize.X),
|
||||
MathUtils.RoundTowardsClosest(attachPos.Y + offset.Y, Submarine.GridSize.Y)) - offset;
|
||||
//subtract the submarine position so we're doing the rounding in the sub's
|
||||
//internal/local coordinate space regardless if we're using world coordinates
|
||||
//(otherwise the rounding would behave differently depending on the value of useWorldCoordinates)
|
||||
Vector2 offsetAttachPos = attachPos + offset - submarinePos;
|
||||
return
|
||||
new Vector2(
|
||||
RoundToGrid(offsetAttachPos.X, Submarine.GridSize.X),
|
||||
//don't round the vertical position if we're attaching to floor - we want the item to align with the floor, not the grid
|
||||
AttachesToFloor ? offsetAttachPos.Y : RoundToGrid(offsetAttachPos.Y, Submarine.GridSize.Y))
|
||||
- offset + submarinePos;
|
||||
|
||||
///<param name="roundingDir">If < 0, the method rounds down. If > 0, rounds up. If 0, rounds to the closest integer.</param>
|
||||
static float RoundToGrid(float position, float gridSize, int roundingDir = 0)
|
||||
{
|
||||
if (roundingDir < 0)
|
||||
{
|
||||
return MathF.Floor(position / gridSize) * gridSize;
|
||||
}
|
||||
else if (roundingDir > 0)
|
||||
{
|
||||
return MathF.Ceiling(position / gridSize) * gridSize;
|
||||
}
|
||||
return MathUtils.RoundTowardsClosest(position, gridSize);
|
||||
}
|
||||
}
|
||||
|
||||
private Voronoi2.VoronoiCell GetAttachTargetCell(float maxDist)
|
||||
@@ -977,7 +1099,7 @@ namespace Barotrauma.Items.Components
|
||||
return;
|
||||
}
|
||||
|
||||
if (picker == Character.Controlled && picker.IsKeyDown(InputType.Aim) && CanBeAttached(picker))
|
||||
if (picker == Character.Controlled && picker.IsKeyDown(InputType.Aim) && attachable && Reattachable)
|
||||
{
|
||||
Drawable = true;
|
||||
}
|
||||
@@ -1115,11 +1237,11 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
else
|
||||
{
|
||||
if (body != null)
|
||||
if (originalBody != null)
|
||||
{
|
||||
body.SetTransformIgnoreContacts(item.SimPosition, item.Rotation);
|
||||
item.body = body;
|
||||
body.Enabled = item.ParentInventory == null;
|
||||
originalBody.SetTransformIgnoreContacts(item.SimPosition, item.Rotation);
|
||||
item.body = originalBody;
|
||||
originalBody.Enabled = item.ParentInventory == null;
|
||||
}
|
||||
DeattachFromWall();
|
||||
}
|
||||
@@ -1134,7 +1256,7 @@ namespace Barotrauma.Items.Components
|
||||
Pusher.Remove();
|
||||
Pusher = null;
|
||||
}
|
||||
body = null;
|
||||
originalBody = null;
|
||||
}
|
||||
|
||||
public override XElement Save(XElement parentElement)
|
||||
|
||||
@@ -435,55 +435,65 @@ namespace Barotrauma.Items.Components
|
||||
Structure targetStructure = target.UserData as Structure ?? targetFixture.UserData as Structure;
|
||||
Item targetItem = target.UserData is Holdable h ? h.Item : target.UserData as Item ?? targetFixture.UserData as Item;
|
||||
Entity targetEntity = targetCharacter ?? targetStructure ?? targetItem ?? target.UserData as Entity;
|
||||
|
||||
if (Attack != null)
|
||||
{
|
||||
Attack.SetUser(user);
|
||||
Attack.DamageMultiplier = damageMultiplier;
|
||||
if (targetLimb != null)
|
||||
bool applyAttack = true;
|
||||
if (Attack.Conditionals.Any(c => !c.TargetSelf && !c.Matches(targetEntity as ISerializableEntity)) ||
|
||||
Attack.Conditionals.Any(c => c.TargetSelf && !c.Matches(user)))
|
||||
{
|
||||
if (targetLimb.character.Removed) { return; }
|
||||
targetLimb.character.LastDamageSource = item;
|
||||
Attack.DoDamageToLimb(user, targetLimb, item.WorldPosition, 1.0f);
|
||||
applyAttack = false;
|
||||
}
|
||||
else if (targetCharacter != null)
|
||||
if (applyAttack)
|
||||
{
|
||||
if (targetCharacter.Removed) { return; }
|
||||
targetCharacter.LastDamageSource = item;
|
||||
Attack.DoDamage(user, targetCharacter, item.WorldPosition, 1.0f);
|
||||
}
|
||||
else if (targetStructure != null)
|
||||
{
|
||||
if (targetStructure.Removed) { return; }
|
||||
Attack.DoDamage(user, targetStructure, item.WorldPosition, 1.0f);
|
||||
}
|
||||
else if (targetItem != null && targetItem.Prefab.DamagedByMeleeWeapons && targetItem.Condition > 0)
|
||||
{
|
||||
if (targetItem.Removed) { return; }
|
||||
var attackResult = Attack.DoDamage(user, targetItem, item.WorldPosition, 1.0f);
|
||||
#if CLIENT
|
||||
if (attackResult.Damage > 0.0f && targetItem.Prefab.ShowHealthBar && Character.Controlled != null &&
|
||||
(user == Character.Controlled || Character.Controlled.CanSeeTarget(item)))
|
||||
Attack.DamageMultiplier = damageMultiplier;
|
||||
if (targetLimb != null)
|
||||
{
|
||||
Character.Controlled.UpdateHUDProgressBar(targetItem,
|
||||
targetItem.WorldPosition,
|
||||
targetItem.Condition / targetItem.MaxCondition,
|
||||
emptyColor: GUIStyle.HealthBarColorLow,
|
||||
fullColor: GUIStyle.HealthBarColorHigh,
|
||||
textTag: targetItem.Prefab.ShowNameInHealthBar ? targetItem.Name : string.Empty);
|
||||
if (targetLimb.character.Removed) { return; }
|
||||
targetLimb.character.LastDamageSource = item;
|
||||
Attack.DoDamageToLimb(user, targetLimb, item.WorldPosition, 1.0f);
|
||||
}
|
||||
else if (targetCharacter != null)
|
||||
{
|
||||
if (targetCharacter.Removed) { return; }
|
||||
targetCharacter.LastDamageSource = item;
|
||||
Attack.DoDamage(user, targetCharacter, item.WorldPosition, 1.0f);
|
||||
}
|
||||
else if (targetStructure != null)
|
||||
{
|
||||
if (targetStructure.Removed) { return; }
|
||||
Attack.DoDamage(user, targetStructure, item.WorldPosition, 1.0f);
|
||||
}
|
||||
else if (targetItem != null && targetItem.Prefab.DamagedByMeleeWeapons && targetItem.Condition > 0)
|
||||
{
|
||||
if (targetItem.Removed) { return; }
|
||||
var attackResult = Attack.DoDamage(user, targetItem, item.WorldPosition, 1.0f);
|
||||
#if CLIENT
|
||||
if (attackResult.Damage > 0.0f && targetItem.Prefab.ShowHealthBar && Character.Controlled != null &&
|
||||
(user == Character.Controlled || Character.Controlled.CanSeeTarget(item)))
|
||||
{
|
||||
Character.Controlled.UpdateHUDProgressBar(targetItem,
|
||||
targetItem.WorldPosition,
|
||||
targetItem.Condition / targetItem.MaxCondition,
|
||||
emptyColor: GUIStyle.HealthBarColorLow,
|
||||
fullColor: GUIStyle.HealthBarColorHigh,
|
||||
textTag: targetItem.Prefab.ShowNameInHealthBar ? targetItem.Name : string.Empty);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
else if (target.UserData is Holdable { CanPush: true } holdable)
|
||||
{
|
||||
if (holdable.Item.Removed) { return; }
|
||||
RestoreCollision();
|
||||
hitting = false;
|
||||
User = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (target.UserData is Holdable { CanPush: true } holdable)
|
||||
{
|
||||
if (holdable.Item.Removed) { return; }
|
||||
RestoreCollision();
|
||||
hitting = false;
|
||||
User = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
|
||||
|
||||
@@ -522,7 +522,7 @@ namespace Barotrauma.Items.Components
|
||||
#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, GUIStyle.Blue, GUIStyle.Blue, "progressbar.watering");
|
||||
user?.UpdateHUDProgressBar(planter, planter.Item.DrawPosition + new Vector2(barOffset, 0) + offset, seed.Health / seed.MaxWater, GUIStyle.Blue, GUIStyle.Blue, "progressbar.watering");
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ namespace Barotrauma.Items.Components
|
||||
public override bool Use(float deltaTime, Character character = null)
|
||||
{
|
||||
//actual throwing logic is handled in Update
|
||||
return characterUsable || character == null;
|
||||
return (characterUsable && !UsageDisabledByRangedWeapon(character)) || character == null;
|
||||
}
|
||||
|
||||
public override bool SecondaryUse(float deltaTime, Character character = null)
|
||||
@@ -111,24 +111,28 @@ namespace Barotrauma.Items.Components
|
||||
return;
|
||||
}
|
||||
|
||||
if (throwState != ThrowState.Throwing)
|
||||
bool aim = false;
|
||||
if (!UsageDisabledByRangedWeapon(picker))
|
||||
{
|
||||
if (picker.IsKeyDown(InputType.Aim))
|
||||
if (throwState != ThrowState.Throwing)
|
||||
{
|
||||
if (picker.IsKeyDown(InputType.Shoot)) { throwState = ThrowState.Initiated; }
|
||||
if (picker.IsKeyDown(InputType.Aim))
|
||||
{
|
||||
if (picker.IsKeyDown(InputType.Shoot)) { throwState = ThrowState.Initiated; }
|
||||
}
|
||||
else if (throwState != ThrowState.Initiated)
|
||||
{
|
||||
throwAngle = ThrowAngleStart;
|
||||
}
|
||||
}
|
||||
else if (throwState != ThrowState.Initiated)
|
||||
{
|
||||
throwAngle = ThrowAngleStart;
|
||||
}
|
||||
}
|
||||
|
||||
bool aim = picker.IsKeyDown(InputType.Aim) && picker.CanAim;
|
||||
aim = picker.IsKeyDown(InputType.Aim) && picker.CanAim;
|
||||
}
|
||||
if (picker.IsDead || !picker.AllowInput)
|
||||
{
|
||||
throwState = ThrowState.None;
|
||||
aim = false;
|
||||
}
|
||||
}
|
||||
|
||||
ApplyStatusEffects(ActionType.OnActive, deltaTime, picker);
|
||||
//return if the status effect got rid of the picker somehow
|
||||
|
||||
@@ -998,6 +998,13 @@ namespace Barotrauma.Items.Components
|
||||
/// </summary>
|
||||
public virtual void OnItemLoaded() { }
|
||||
|
||||
/// <summary>
|
||||
/// Implement in a base class if the instances of the component contain some sort of data that isn't serialized using the normal serializable properties
|
||||
/// (i.e. some data that changes per-item and isn't loaded from the prefab, but that isn't a property marked with [Serialize] either),
|
||||
/// but that must be copied when cloning the item.
|
||||
/// </summary>
|
||||
public virtual void Clone(ItemComponent original) { }
|
||||
|
||||
public virtual void OnScaleChanged() { }
|
||||
|
||||
/// <summary>
|
||||
@@ -1093,7 +1100,6 @@ namespace Barotrauma.Items.Components
|
||||
componentElement.Add(newElement);
|
||||
}
|
||||
|
||||
|
||||
SerializableProperty.SerializeProperties(this, componentElement);
|
||||
|
||||
parentElement.Add(componentElement);
|
||||
|
||||
@@ -438,9 +438,14 @@ namespace Barotrauma.Items.Components
|
||||
ActiveContainedItem activeContainedItem = new(containedItem, effect, containableItem.ExcludeBroken, containableItem.ExcludeFullCondition, containableItem.BlameEquipperForDeath);
|
||||
activeContainedItems.Add(activeContainedItem);
|
||||
|
||||
if (!ShouldApplyEffects(activeContainedItem)) { continue; }
|
||||
if (!ShouldApplyEffects(activeContainedItem) || item.Submarine is { Loading: true} || initializingLoadedItems ||
|
||||
containedItem.OnInsertedEffectsApplied)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
activeContainedItem.StatusEffect.Apply(ActionType.OnInserted, deltaTime: 1, item, targets);
|
||||
}
|
||||
containedItem.OnInsertedEffectsApplied = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -511,6 +516,8 @@ namespace Barotrauma.Items.Components
|
||||
activeContainedItem.StatusEffect.Apply(ActionType.OnRemoved, deltaTime: 1, item, targets);
|
||||
}
|
||||
|
||||
containedItem.OnInsertedEffectsApplied = false;
|
||||
|
||||
activeContainedItems.RemoveAll(i => i.Item == containedItem);
|
||||
containedItems.RemoveAll(i => i.Item == containedItem);
|
||||
item.SetContainedItemPositions();
|
||||
@@ -680,7 +687,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
foreach (ActiveContainedItem activeContainedItem in activeContainedItems)
|
||||
{
|
||||
if (!ShouldApplyEffects(activeContainedItem)) continue;
|
||||
if (!ShouldApplyEffects(activeContainedItem)) { continue; }
|
||||
|
||||
StatusEffect effect = activeContainedItem.StatusEffect;
|
||||
effect.Apply(ActionType.OnActive, deltaTime, item, targets);
|
||||
@@ -993,6 +1000,8 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
else
|
||||
{
|
||||
//flip if flipped on one axis but not both (flipping on both axes is basically "double negative" and makes the rotation normal again)
|
||||
if (flippedX ^ flippedY) { rotation = -rotation; }
|
||||
rotation += -item.RotationRad;
|
||||
}
|
||||
contained.Item.body.FarseerBody.SetTransformIgnoreContacts(ref simPos, rotation);
|
||||
@@ -1050,8 +1059,17 @@ namespace Barotrauma.Items.Components
|
||||
transformedItemIntervalHorizontal = new Vector2(transformedItemInterval.X, 0.0f);
|
||||
transformedItemIntervalVertical = new Vector2(0.0f, transformedItemInterval.Y);
|
||||
|
||||
flippedX = item.RootContainer?.FlippedX ?? (item.FlippedX && item.Prefab.CanSpriteFlipX);
|
||||
flippedY = item.RootContainer?.FlippedY ?? (item.FlippedY && item.Prefab.CanSpriteFlipY);
|
||||
if (item.RootContainer != null)
|
||||
{
|
||||
flippedX = item.RootContainer.FlippedX && item.RootContainer.Prefab.CanSpriteFlipX;
|
||||
flippedY = item.RootContainer.FlippedY && item.RootContainer.Prefab.CanSpriteFlipY;
|
||||
}
|
||||
else
|
||||
{
|
||||
flippedX = item.FlippedX && item.Prefab.CanSpriteFlipX;
|
||||
flippedY = item.FlippedY && item.Prefab.CanSpriteFlipY;
|
||||
}
|
||||
|
||||
var rootBody = item.RootContainer?.body ?? item.body;
|
||||
bool bodyFlipped = rootBody is { Dir: -1 };
|
||||
|
||||
@@ -1123,10 +1141,13 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
private bool initializingLoadedItems;
|
||||
|
||||
public override void OnMapLoaded()
|
||||
{
|
||||
if (itemIds != null)
|
||||
{
|
||||
initializingLoadedItems = true;
|
||||
for (ushort i = 0; i < itemIds.Length; i++)
|
||||
{
|
||||
if (i >= Inventory.Capacity)
|
||||
@@ -1138,10 +1159,11 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
foreach (ushort id in itemIds[i])
|
||||
{
|
||||
if (!(Entity.FindEntityByID(id) is Item item)) { continue; }
|
||||
if (Entity.FindEntityByID(id) is not Item item) { continue; }
|
||||
Inventory.TryPutItem(item, i, false, false, null, createNetworkEvent: false, ignoreCondition: true);
|
||||
}
|
||||
}
|
||||
initializingLoadedItems = false;
|
||||
itemIds = null;
|
||||
}
|
||||
|
||||
|
||||
@@ -349,7 +349,7 @@ namespace Barotrauma.Items.Components
|
||||
// Don't move lower body limbs if there's another selected secondary item that should control them
|
||||
if (limb.IsLowerBody && user.HasSelectedAnotherSecondaryItem(Item)) { continue; }
|
||||
// Don't move hands if there's a selected primary item that should control them
|
||||
if (!limb.IsLowerBody && Item == user.SelectedSecondaryItem && user.SelectedItem != null) { continue; }
|
||||
if (limb.IsArm && Item == user.SelectedSecondaryItem && user.SelectedItem != null) { continue; }
|
||||
if (lb.AllowUsingLimb)
|
||||
{
|
||||
switch (lb.LimbType)
|
||||
|
||||
@@ -564,7 +564,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
fireTimer += MathHelper.Lerp(deltaTime * 2.0f, deltaTime, item.Condition / item.MaxCondition);
|
||||
#if SERVER
|
||||
if (fireTimer > Math.Min(5.0f, FireDelay / 2) && blameOnBroken?.Character?.SelectedItem == item)
|
||||
if (fireTimer > Math.Min(5.0f, FireDelay / 2) && blameOnBroken?.Character != null)
|
||||
{
|
||||
GameMain.Server.KarmaManager.OnReactorOverHeating(item, blameOnBroken.Character, deltaTime);
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
private double lastReceivedSteeringSignalTime;
|
||||
|
||||
[Serialize(defaultValue: false, isSaveable: IsPropertySaveable.Yes, AlwaysUseInstanceValues = true)]
|
||||
[Serialize(defaultValue: false, isSaveable: IsPropertySaveable.Yes, description: "Is autopilot currently on or not?", AlwaysUseInstanceValues = true)]
|
||||
public bool AutoPilot
|
||||
{
|
||||
get { return autoPilot; }
|
||||
|
||||
@@ -110,17 +110,15 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
base.OnItemLoaded();
|
||||
IsActive = true;
|
||||
#if CLIENT
|
||||
var lights = item.GetComponents<LightComponent>();
|
||||
if (lights.Any())
|
||||
{
|
||||
lightComponents = lights.ToList();
|
||||
foreach (var light in lightComponents)
|
||||
foreach (var light in item.GetComponents<LightComponent>())
|
||||
{
|
||||
light.Light.Enabled = false;
|
||||
light.IsOn = false;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
container = item.GetComponent<ItemContainer>();
|
||||
GrowableSeeds = new Growable[container.Capacity];
|
||||
}
|
||||
@@ -233,7 +231,6 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
base.Update(deltaTime, cam);
|
||||
|
||||
#if CLIENT
|
||||
if (lightComponents != null && lightComponents.Count > 0)
|
||||
{
|
||||
bool hasSeed = false;
|
||||
@@ -243,10 +240,9 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
foreach (var light in lightComponents)
|
||||
{
|
||||
light.Light.Enabled = hasSeed;
|
||||
light.IsOn = hasSeed;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (container?.Inventory == null) { return; }
|
||||
|
||||
|
||||
@@ -0,0 +1,230 @@
|
||||
#nullable enable
|
||||
using Barotrauma.Networking;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
internal partial class PowerDistributor : PowerTransfer
|
||||
{
|
||||
private const int MaxNameLength = 32;
|
||||
|
||||
private const int SupplyRatioSteps = 20;
|
||||
private const float SupplyRatioStep = 1f / SupplyRatioSteps;
|
||||
|
||||
private partial class PowerGroup
|
||||
{
|
||||
private readonly PowerDistributor distributor;
|
||||
public readonly Connection PowerOut;
|
||||
public readonly Connection? RatioInput, RatioOutput;
|
||||
|
||||
private string name;
|
||||
public string Name
|
||||
{
|
||||
get => name;
|
||||
set
|
||||
{
|
||||
name = value;
|
||||
DisplayName = TextManager.Get(name).Fallback(name);
|
||||
#if CLIENT
|
||||
UpdateNameBox();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
public LocalizedString? DisplayName { get; private set; }
|
||||
|
||||
private float supplyRatio = 1f;
|
||||
public float SupplyRatio
|
||||
{
|
||||
get => supplyRatio;
|
||||
set
|
||||
{
|
||||
if (!MathUtils.IsValid(value)) { return; }
|
||||
supplyRatio = MathUtils.RoundTowardsClosest(MathHelper.Clamp(value, 0f, 1f), SupplyRatioStep);
|
||||
#if CLIENT
|
||||
UpdateSlider();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
public float DisplayRatio
|
||||
{
|
||||
get => MathUtils.RoundToInt(supplyRatio * 100);
|
||||
set => SupplyRatio = value / 100f;
|
||||
}
|
||||
|
||||
public float Load;
|
||||
public float ModifiedLoad => Load * SupplyRatio;
|
||||
|
||||
public PowerGroup(PowerDistributor distributor, Connection power, XElement? element = null, Connection? ratioInput = null, Connection? ratioOutput = null)
|
||||
{
|
||||
this.distributor = distributor;
|
||||
PowerOut = power;
|
||||
|
||||
RatioInput = ratioInput;
|
||||
RatioOutput = ratioOutput;
|
||||
|
||||
distributor.powerGroups.Add(this);
|
||||
|
||||
name = TextManager.GetWithVariable("groupx", "[num]", distributor.powerGroups.Count.ToString()).Value;
|
||||
SupplyRatio = 1f;
|
||||
|
||||
if (element != null)
|
||||
{
|
||||
name = element.GetAttributeString("name", name);
|
||||
SupplyRatio = element.GetAttributeFloat("ratio", SupplyRatio);
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
CreateGUI();
|
||||
#endif
|
||||
}
|
||||
|
||||
#region Signals
|
||||
public void ReceiveRatioSignal(Signal signal)
|
||||
{
|
||||
if (!float.TryParse(signal.value, NumberStyles.Float, CultureInfo.InvariantCulture, out float receivedSignal) || !MathUtils.IsValid(receivedSignal)) { return; }
|
||||
DisplayRatio = receivedSignal;
|
||||
}
|
||||
|
||||
public void SendRatioSignal() => distributor.item.SendSignal(new Signal(DisplayRatio.ToString()), RatioOutput);
|
||||
#endregion
|
||||
}
|
||||
|
||||
private readonly List<PowerGroup> powerGroups = new List<PowerGroup>();
|
||||
|
||||
protected override PowerPriority Priority => PowerPriority.Relay;
|
||||
|
||||
public PowerDistributor(Item item, ContentXElement element) : base(item, element) { }
|
||||
|
||||
public override void OnItemLoaded()
|
||||
{
|
||||
base.OnItemLoaded();
|
||||
|
||||
IEnumerable<Connection> ratioInputs = Item.Connections.Where(static conn => !conn.IsOutput && conn.Name.StartsWith("set_supply_ratio"));
|
||||
IEnumerable<Connection> ratioOutputs = Item.Connections.Where(static conn => conn.IsOutput && conn.Name.StartsWith("supply_ratio_out"));
|
||||
|
||||
for (int i = 0; i < powerOuts.Count; i++)
|
||||
{
|
||||
new PowerGroup(this, powerOuts[i], cachedGroupData.ElementAtOrDefault(i), ratioInputs.ElementAtOrDefault(i), ratioOutputs.ElementAtOrDefault(i));
|
||||
}
|
||||
|
||||
cachedGroupData.Clear();
|
||||
}
|
||||
|
||||
public override void Clone(ItemComponent original)
|
||||
{
|
||||
if (original is not PowerDistributor originalPowerDistributor) { return; }
|
||||
for (int i = 0; i < powerOuts.Count; i++)
|
||||
{
|
||||
powerGroups[i].SupplyRatio = originalPowerDistributor.powerGroups[i].SupplyRatio;
|
||||
powerGroups[i].Name = originalPowerDistributor.powerGroups[i].Name;
|
||||
}
|
||||
}
|
||||
|
||||
#region Signals
|
||||
protected override void SendSignals()
|
||||
{
|
||||
item.SendSignal(MathUtils.RoundToInt(powerIn.Grid?.Power ?? 0f).ToString(), "power_value_out");
|
||||
item.SendSignal(MathUtils.RoundToInt(GetCurrentPowerConsumption(powerIn)).ToString(), "load_value_out");
|
||||
powerGroups.ForEach(static group => group.SendRatioSignal());
|
||||
}
|
||||
|
||||
public override void ReceiveSignal(Signal signal, Connection connection)
|
||||
{
|
||||
if (item.Condition <= 0f || connection.IsPower) { return; }
|
||||
if (connection.IsOutput) { return; }
|
||||
|
||||
powerGroups.FirstOrDefault(group => group.RatioInput == connection)?.ReceiveRatioSignal(signal);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Power Calculation
|
||||
private bool IsShortCircuited(Connection conn) => powerIn.Grid == conn.Grid;
|
||||
|
||||
public override float GetCurrentPowerConsumption(Connection? connection = null)
|
||||
{
|
||||
if (connection != powerIn) { return -1f; }
|
||||
if (isBroken) { return 0f; }
|
||||
return powerGroups.Sum(group => IsShortCircuited(group.PowerOut) ? 0f : group.ModifiedLoad) + ExtraLoad;
|
||||
}
|
||||
|
||||
private float CalculatePowerOut(PowerGroup group)
|
||||
{
|
||||
if (isBroken || powerIn.Grid == null || IsShortCircuited(group.PowerOut)) { return 0f; }
|
||||
return Math.Max(group.ModifiedLoad * Voltage, 0f);
|
||||
}
|
||||
|
||||
public override float GetConnectionPowerOut(Connection connection, float power, PowerRange minMaxPower, float load)
|
||||
{
|
||||
if (connection == powerIn) { return 0f; }
|
||||
PowerGroup group = powerGroups.First(group => group.PowerOut == connection);
|
||||
group.Load = load;
|
||||
return CalculatePowerOut(group);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Serialization
|
||||
private readonly List<XElement> cachedGroupData = new List<XElement>();
|
||||
|
||||
public override XElement Save(XElement parentElement)
|
||||
{
|
||||
XElement componentElement = base.Save(parentElement);
|
||||
foreach (PowerGroup powerGroup in powerGroups)
|
||||
{
|
||||
componentElement.Add(new XElement("PowerGroup",
|
||||
new XAttribute("name", powerGroup.Name),
|
||||
new XAttribute("ratio", powerGroup.SupplyRatio)));
|
||||
}
|
||||
return componentElement;
|
||||
}
|
||||
|
||||
public override void Load(ContentXElement componentElement, bool usePrefabValues, IdRemap idRemap, bool isItemSwap)
|
||||
{
|
||||
base.Load(componentElement, usePrefabValues, idRemap, isItemSwap);
|
||||
if (usePrefabValues) { return; }
|
||||
|
||||
foreach (XElement element in componentElement.Elements())
|
||||
{
|
||||
cachedGroupData.Add(element);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Networking
|
||||
private enum EventType { NameChange, RatioChange }
|
||||
|
||||
private void SharedEventWrite(IWriteMessage msg, NetEntityEvent.IData? extraData = null)
|
||||
{
|
||||
EventData data = ExtractEventData<EventData>(extraData);
|
||||
msg.WriteRangedInteger((int)data.EventType, 0, 1);
|
||||
msg.WriteRangedInteger(powerGroups.IndexOf(data.PowerGroup), 0, powerGroups.Count - 1);
|
||||
switch (data.EventType)
|
||||
{
|
||||
case EventType.NameChange:
|
||||
msg.WriteString(data.PowerGroup.Name);
|
||||
break;
|
||||
case EventType.RatioChange:
|
||||
msg.WriteRangedInteger(MathUtils.RoundToInt(data.PowerGroup.SupplyRatio / SupplyRatioStep), 0, SupplyRatioSteps);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void SharedEventRead(IReadMessage msg, out EventType eventType, out PowerGroup powerGroup, out string newName, out float newRatio)
|
||||
{
|
||||
eventType = (EventType)msg.ReadRangedInteger(0, 1);
|
||||
powerGroup = powerGroups[msg.ReadRangedInteger(0, powerGroups.Count - 1)];
|
||||
|
||||
newName = eventType == EventType.NameChange ? string.Concat(msg.ReadString().Take(MaxNameLength)) : powerGroup.Name;
|
||||
newRatio = eventType == EventType.RatioChange ? msg.ReadRangedInteger(0, SupplyRatioSteps) * SupplyRatioStep : powerGroup.SupplyRatio;
|
||||
}
|
||||
|
||||
private readonly record struct EventData(PowerGroup PowerGroup, EventType EventType) : IEventData;
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -177,18 +177,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
RefreshConnections();
|
||||
|
||||
if (Timing.TotalTime > extraLoadSetTime + 1.0)
|
||||
{
|
||||
//Decay the extra load to 0 from either positive or negative
|
||||
if (extraLoad > 0)
|
||||
{
|
||||
extraLoad = Math.Max(extraLoad - 1000.0f * deltaTime, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
extraLoad = Math.Min(extraLoad + 1000.0f * deltaTime, 0);
|
||||
}
|
||||
}
|
||||
UpdateExtraLoad(deltaTime);
|
||||
|
||||
if (!CanTransfer) { return; }
|
||||
|
||||
@@ -200,6 +189,28 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
ApplyStatusEffects(ActionType.OnActive, deltaTime);
|
||||
|
||||
SendSignals();
|
||||
|
||||
UpdateOvervoltage(deltaTime);
|
||||
}
|
||||
|
||||
protected virtual void UpdateExtraLoad(float deltaTime)
|
||||
{
|
||||
if (Timing.TotalTime <= extraLoadSetTime + 1.0) { return; }
|
||||
|
||||
//Decay the extra load to 0 from either positive or negative
|
||||
if (extraLoad > 0)
|
||||
{
|
||||
extraLoad = Math.Max(extraLoad - 1000.0f * deltaTime, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
extraLoad = Math.Min(extraLoad + 1000.0f * deltaTime, 0);
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void SendSignals()
|
||||
{
|
||||
float powerReadingOut = 0;
|
||||
float loadReadingOut = ExtraLoad;
|
||||
if (powerLoad < 0)
|
||||
@@ -226,7 +237,10 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
item.SendSignal(powerSignal, "power_value_out");
|
||||
item.SendSignal(loadSignal, "load_value_out");
|
||||
}
|
||||
|
||||
protected virtual void UpdateOvervoltage(float deltaTime)
|
||||
{
|
||||
//if the item can't be fixed, don't allow it to break
|
||||
if (!item.Repairables.Any() || !CanBeOverloaded) { return; }
|
||||
|
||||
@@ -234,46 +248,45 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
Overload = Voltage > maxOverVoltage && GameMain.GameSession is not { RoundDuration: < 5 };
|
||||
|
||||
if (Overload && (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer))
|
||||
if (!Overload || GameMain.NetworkMember is { IsClient: true }) { return; }
|
||||
|
||||
if (overloadCooldownTimer > 0.0f)
|
||||
{
|
||||
if (overloadCooldownTimer > 0.0f)
|
||||
{
|
||||
overloadCooldownTimer -= deltaTime;
|
||||
return;
|
||||
}
|
||||
overloadCooldownTimer -= deltaTime;
|
||||
return;
|
||||
}
|
||||
|
||||
//damage the item if voltage is too high (except if running as a client)
|
||||
float prevCondition = item.Condition;
|
||||
//some randomness to prevent all junction boxes from breaking at the same time
|
||||
if (Rand.Range(0.0f, 1.0f) < 0.01f)
|
||||
{
|
||||
//damaged boxes are more sensitive to overvoltage (also preventing all boxes from breaking at the same time)
|
||||
float conditionFactor = MathHelper.Lerp(5.0f, 1.0f, item.Condition / item.MaxCondition);
|
||||
item.Condition -= deltaTime * Rand.Range(10.0f, 500.0f) * conditionFactor;
|
||||
}
|
||||
if (item.Condition <= 0.0f && prevCondition > 0.0f)
|
||||
{
|
||||
overloadCooldownTimer = OverloadCooldown;
|
||||
//damage the item if voltage is too high (except if running as a client)
|
||||
float prevCondition = item.Condition;
|
||||
//some randomness to prevent all junction boxes from breaking at the same time
|
||||
if (Rand.Range(0.0f, 1.0f) < 0.01f)
|
||||
{
|
||||
//damaged boxes are more sensitive to overvoltage (also preventing all boxes from breaking at the same time)
|
||||
float conditionFactor = MathHelper.Lerp(5.0f, 1.0f, item.Condition / item.MaxCondition);
|
||||
item.Condition -= deltaTime * Rand.Range(10.0f, 500.0f) * conditionFactor;
|
||||
}
|
||||
|
||||
if (item.Condition > 0.0f || prevCondition <= 0.0f) { return; }
|
||||
|
||||
overloadCooldownTimer = OverloadCooldown;
|
||||
#if CLIENT
|
||||
SoundPlayer.PlaySound("zap", item.WorldPosition, hullGuess: item.CurrentHull);
|
||||
Vector2 baseVel = Rand.Vector(300.0f);
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
var particle = GameMain.ParticleManager.CreateParticle("spark", item.WorldPosition,
|
||||
baseVel + Rand.Vector(100.0f), 0.0f, item.CurrentHull);
|
||||
if (particle != null) particle.Size *= Rand.Range(0.5f, 1.0f);
|
||||
}
|
||||
SoundPlayer.PlaySound("zap", item.WorldPosition, hullGuess: item.CurrentHull);
|
||||
Vector2 baseVel = Rand.Vector(300.0f);
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
var particle = GameMain.ParticleManager.CreateParticle("spark", item.WorldPosition,
|
||||
baseVel + Rand.Vector(100.0f), 0.0f, item.CurrentHull);
|
||||
if (particle != null) particle.Size *= Rand.Range(0.5f, 1.0f);
|
||||
}
|
||||
#endif
|
||||
float currentIntensity = GameMain.GameSession?.EventManager != null ?
|
||||
GameMain.GameSession.EventManager.CurrentIntensity : 0.5f;
|
||||
float currentIntensity = GameMain.GameSession?.EventManager != null ?
|
||||
GameMain.GameSession.EventManager.CurrentIntensity : 0.5f;
|
||||
|
||||
//higher probability for fires if the current intensity is low
|
||||
if (FireProbability > 0.0f &&
|
||||
Rand.Range(0.0f, 1.0f) < MathHelper.Lerp(FireProbability, FireProbability * 0.1f, currentIntensity))
|
||||
{
|
||||
new FireSource(item.WorldPosition);
|
||||
}
|
||||
}
|
||||
//higher probability for fires if the current intensity is low
|
||||
if (FireProbability > 0.0f &&
|
||||
Rand.Range(0.0f, 1.0f) < MathHelper.Lerp(FireProbability, FireProbability * 0.1f, currentIntensity))
|
||||
{
|
||||
new FireSource(item.WorldPosition);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -424,7 +437,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
if (this is not RelayComponent)
|
||||
if (this is not RelayComponent and not PowerDistributor)
|
||||
{
|
||||
if (PowerConnections.Any(p => !p.IsOutput) && PowerConnections.Any(p => p.IsOutput))
|
||||
{
|
||||
@@ -452,7 +465,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
//other junction boxes don't need to receive the signal in the pass-through signal connections
|
||||
//because we relay it straight to the connected items without going through the whole chain of junction boxes
|
||||
if (ic is PowerTransfer && ic is not RelayComponent) { continue; }
|
||||
if (ic is PowerTransfer and not RelayComponent and not PowerDistributor) { continue; }
|
||||
ic.ReceiveSignal(signal, recipient);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Barotrauma.Extensions;
|
||||
#if CLIENT
|
||||
using Barotrauma.Sounds;
|
||||
#endif
|
||||
@@ -93,7 +94,22 @@ namespace Barotrauma.Items.Components
|
||||
/// </summary>
|
||||
protected float powerConsumption;
|
||||
|
||||
protected Connection powerIn, powerOut;
|
||||
protected Connection powerIn;
|
||||
protected List<Connection> powerOuts = new List<Connection>();
|
||||
/// <summary>
|
||||
/// Throws an error if there is more than one power out connection.<br/>
|
||||
/// Use <see cref="powerOuts"/> if a component should handle multiple outputs.
|
||||
/// </summary>
|
||||
protected Connection powerOut
|
||||
{
|
||||
get
|
||||
{
|
||||
if (powerOuts.Count > 1) { DebugConsole.ThrowErrorOnce($"{item.ID}.multiplePowerOut", $"Item {item.Name} ({item.Prefab.Identifier}) has multiple power outputs, but only supports one!"); }
|
||||
return powerOuts.FirstOrDefault();
|
||||
}
|
||||
}
|
||||
|
||||
protected bool powerInIsPowerOut => powerOuts.Contains(powerIn);
|
||||
|
||||
/// <summary>
|
||||
/// Maximum voltage factor when the device is being overvolted. I.e. how many times more effectively the device can function when it's being overvolted
|
||||
@@ -153,9 +169,10 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (powerIn?.Grid != null) { return powerIn.Grid.Voltage; }
|
||||
}
|
||||
else if (powerOut != null)
|
||||
else if (powerOuts.Any())
|
||||
{
|
||||
if (powerOut?.Grid != null) { return powerOut.Grid.Voltage; }
|
||||
IEnumerable<Connection> gridConnections = powerOuts.Where(static conn => conn.Grid != null);
|
||||
if (gridConnections.Any()) { return gridConnections.Average(static conn => conn.Grid.Voltage); }
|
||||
}
|
||||
|
||||
if (this is PowerTransfer && item.Condition <= 0.0f)
|
||||
@@ -245,18 +262,19 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
powerIn = c;
|
||||
}
|
||||
else if (c.Name == "power_out")
|
||||
{
|
||||
powerOut = c;
|
||||
// Connection takes the lowest priority
|
||||
if (Priority > powerOut.Priority)
|
||||
{
|
||||
powerOut.Priority = Priority;
|
||||
}
|
||||
}
|
||||
else if (c.Name == "power")
|
||||
{
|
||||
powerIn = powerOut = c;
|
||||
powerIn = c;
|
||||
powerOuts.Add(c);
|
||||
}
|
||||
else if (c.IsOutput)
|
||||
{
|
||||
powerOuts.Add(c);
|
||||
// Connection takes the lowest priority
|
||||
if (Priority > c.Priority)
|
||||
{
|
||||
c.Priority = Priority;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -271,11 +289,11 @@ namespace Barotrauma.Items.Components
|
||||
DebugConsole.NewMessage($"Item \"{item.Name}\" has a power output connection called power_in. If the item is supposed to receive power through the connection, change it to an input connection.", Color.Orange);
|
||||
#endif
|
||||
}
|
||||
powerOut = c;
|
||||
powerOuts.Add(c);
|
||||
// Connection takes the lowest priority
|
||||
if (Priority > powerOut.Priority)
|
||||
if (Priority > c.Priority)
|
||||
{
|
||||
powerOut.Priority = Priority;
|
||||
c.Priority = Priority;
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -335,9 +353,9 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
powered.powerIn.Grid = null;
|
||||
}
|
||||
if (powered.powerOut != null)
|
||||
foreach (Connection powerOut in powered.powerOuts)
|
||||
{
|
||||
powered.powerOut.Grid = null;
|
||||
powerOut.Grid = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -345,8 +363,10 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
foreach (Powered powered in poweredList)
|
||||
{
|
||||
if (powered.Item.Condition <= 0f) { continue; }
|
||||
|
||||
//Probe through all connections that don't have a gridID
|
||||
if (powered.powerIn != null && powered.powerIn.Grid == null && powered.powerIn != powered.powerOut && powered.Item.Condition > 0.0f)
|
||||
if (powered.powerIn != null && powered.powerIn.Grid == null && !powered.powerInIsPowerOut)
|
||||
{
|
||||
// Only create grids for networks with more than 1 device
|
||||
if (powered.powerIn.Recipients.Count > 0)
|
||||
@@ -356,13 +376,16 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
if (powered.powerOut != null && powered.powerOut.Grid == null && powered.Item.Condition > 0.0f)
|
||||
foreach (Connection powerOut in powered.powerOuts)
|
||||
{
|
||||
//Only create grids for networks with more than 1 device
|
||||
if (powered.powerOut.Recipients.Count > 0)
|
||||
if (powerOut != null && powerOut.Grid == null)
|
||||
{
|
||||
GridInfo grid = PropagateGrid(powered.powerOut);
|
||||
Grids[grid.ID] = grid;
|
||||
//Only create grids for networks with more than 1 device
|
||||
if (powerOut.Recipients.Count > 0)
|
||||
{
|
||||
GridInfo grid = PropagateGrid(powerOut);
|
||||
Grids[grid.ID] = grid;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -479,7 +502,7 @@ namespace Barotrauma.Items.Components
|
||||
powered.Voltage -= deltaTime;
|
||||
|
||||
//Handle the device if it's got a power connection
|
||||
if (powered.powerIn != null && powered.powerOut != powered.powerIn)
|
||||
if (powered.powerIn != null && !powered.powerInIsPowerOut)
|
||||
{
|
||||
//Get the new load for the connection
|
||||
float currLoad = powered.GetCurrentPowerConsumption(powered.powerIn);
|
||||
@@ -507,10 +530,10 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
|
||||
//Handle the device power depending on if its powerout
|
||||
if (powered.powerOut != null)
|
||||
foreach (Connection powerOut in powered.powerOuts)
|
||||
{
|
||||
//Get the connection's load
|
||||
float currLoad = powered.GetCurrentPowerConsumption(powered.powerOut);
|
||||
float currLoad = powered.GetCurrentPowerConsumption(powerOut);
|
||||
|
||||
//Update the device's output load to the correct variable
|
||||
if (powered is PowerTransfer pt)
|
||||
@@ -529,20 +552,20 @@ namespace Barotrauma.Items.Components
|
||||
if (currLoad >= 0)
|
||||
{
|
||||
//Add to the grid load if possible
|
||||
if (powered.powerOut.Grid != null)
|
||||
if (powerOut.Grid != null)
|
||||
{
|
||||
powered.powerOut.Grid.Load += currLoad;
|
||||
powerOut.Grid.Load += currLoad;
|
||||
}
|
||||
}
|
||||
else if (powered.powerOut.Grid != null)
|
||||
else if (powerOut.Grid != null)
|
||||
{
|
||||
//Add connection as a source to be processed
|
||||
powered.powerOut.Grid.AddSrc(powered.powerOut);
|
||||
powerOut.Grid.AddSrc(powerOut);
|
||||
}
|
||||
else
|
||||
{
|
||||
//Perform power calculations for the singular connection
|
||||
float loadOut = -powered.GetConnectionPowerOut(powered.powerOut, 0, powered.MinMaxPowerOut(powered.powerOut, 0), 0);
|
||||
float loadOut = -powered.GetConnectionPowerOut(powerOut, 0, powered.MinMaxPowerOut(powerOut, 0), 0);
|
||||
if (powered is PowerTransfer pt2)
|
||||
{
|
||||
pt2.PowerLoad = loadOut;
|
||||
@@ -557,7 +580,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
|
||||
//Indicate grid is resolved as it was the only device
|
||||
powered.GridResolved(powered.powerOut);
|
||||
powered.GridResolved(powerOut);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -625,7 +648,7 @@ namespace Barotrauma.Items.Components
|
||||
public virtual float GetCurrentPowerConsumption(Connection connection = null)
|
||||
{
|
||||
// If a handheld device there is no consumption
|
||||
if (powerIn == null && powerOut == null)
|
||||
if (powerIn == null && powerOuts.None())
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
@@ -664,7 +687,7 @@ namespace Barotrauma.Items.Components
|
||||
/// <returns>Power pushed to the grid</returns>
|
||||
public virtual float GetConnectionPowerOut(Connection conn, float power, PowerRange minMaxPower, float load)
|
||||
{
|
||||
return conn == powerOut ? MathHelper.Max(-CurrPowerConsumption, 0) : 0;
|
||||
return powerOuts.Contains(conn) ? MathHelper.Max(-CurrPowerConsumption, 0) : 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -233,6 +233,7 @@ namespace Barotrauma.Items.Components
|
||||
var cloneNode = InputOutputNodes[ioIndex];
|
||||
|
||||
cloneNode.Position = origNode.Position;
|
||||
cloneNode.ReplaceAllConnectionLabelOverrides(origNode.ConnectionLabelOverrides);
|
||||
}
|
||||
|
||||
if (!clonedContainedItems.Any()) { return; }
|
||||
|
||||
@@ -18,7 +18,14 @@ namespace Barotrauma.Items.Components
|
||||
public readonly int MaxWires = 5;
|
||||
|
||||
public readonly string Name;
|
||||
public readonly LocalizedString DisplayName;
|
||||
private readonly LocalizedString _displayName;
|
||||
public LocalizedString DisplayName
|
||||
{
|
||||
get => DisplayNameOverride ?? _displayName;
|
||||
private init => _displayName = value;
|
||||
}
|
||||
|
||||
public LocalizedString DisplayNameOverride;
|
||||
|
||||
private readonly HashSet<Wire> wires;
|
||||
public IReadOnlyCollection<Wire> Wires => wires;
|
||||
@@ -160,8 +167,7 @@ namespace Barotrauma.Items.Components
|
||||
DisplayName = Name;
|
||||
}
|
||||
|
||||
IsPower = Name == "power_in" || Name == "power" || Name == "power_out";
|
||||
|
||||
IsPower = element.GetAttributeBool("ispower", Name is "power_in" or "power" or "power_out");
|
||||
|
||||
LoadedWires = new List<(ushort wireId, int? connectionIndex)>();
|
||||
foreach (var subElement in element.Elements())
|
||||
|
||||
@@ -325,23 +325,18 @@ namespace Barotrauma.Items.Components
|
||||
private bool TriggersOn(Character character, bool triggerFromHumans, bool triggerFromPets, bool triggerFromMonsters)
|
||||
{
|
||||
if (IgnoreDead && character.IsDead) { return false; }
|
||||
if (character.IsHuman)
|
||||
{
|
||||
if (!triggerFromHumans) { return false; }
|
||||
}
|
||||
else if (character.IsPet)
|
||||
if (character.IsPet)
|
||||
{
|
||||
if (!triggerFromPets) { return false; }
|
||||
}
|
||||
else if (character.IsHuman || CharacterParams.CompareGroup(character.Group, CharacterPrefab.HumanGroup))
|
||||
{
|
||||
if (!triggerFromHumans) { return false; }
|
||||
}
|
||||
else
|
||||
{
|
||||
// Not a human or a pet -> monster?
|
||||
if (!triggerFromMonsters) { return false; }
|
||||
if (CharacterParams.CompareGroup(character.Group, CharacterPrefab.HumanGroup))
|
||||
{
|
||||
//characters in the "human" group aren't considered monsters (even if they were something like a friendly mudraptor)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// Check matching character, if defined.
|
||||
if (targetCharacters.Any())
|
||||
|
||||
@@ -553,9 +553,9 @@ namespace Barotrauma.Items.Components
|
||||
return new List<Vector2>(nodes);
|
||||
}
|
||||
|
||||
public void SetNodes(List<Vector2> nodes)
|
||||
public void SetNodes(IEnumerable<Vector2> nodes)
|
||||
{
|
||||
this.nodes = new List<Vector2>(nodes);
|
||||
this.nodes = nodes.ToList();
|
||||
UpdateSections();
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ namespace Barotrauma.Items.Components
|
||||
public bool ForceFluctuation { get; set; }
|
||||
|
||||
[Serialize(1.0f, IsPropertySaveable.Yes, description: "How much the fluctuation affects the force. 1 is the maximum fluctuation, 0 is no fluctuation.", alwaysUseInstanceValues: true)]
|
||||
private float ForceFluctuationStrength
|
||||
public float ForceFluctuationStrength
|
||||
{
|
||||
get
|
||||
{
|
||||
@@ -33,7 +33,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
[Serialize(1.0f, IsPropertySaveable.Yes, description: "How fast (cycles per second) the force fluctuates.", alwaysUseInstanceValues: true)]
|
||||
private float ForceFluctuationFrequency
|
||||
public float ForceFluctuationFrequency
|
||||
{
|
||||
get
|
||||
{
|
||||
@@ -45,7 +45,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
[Serialize(0.01f, IsPropertySaveable.Yes, description: "How often (in seconds) the force fluctuation is calculated.", alwaysUseInstanceValues: true)]
|
||||
private float ForceFluctuationInterval
|
||||
public float ForceFluctuationInterval
|
||||
{
|
||||
get
|
||||
{
|
||||
|
||||
@@ -542,6 +542,10 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
|
||||
IsActive = false;
|
||||
#if CLIENT
|
||||
//stop any sounds that may have been looping while wearing the item
|
||||
StopSounds(ActionType.OnWearing);
|
||||
#endif
|
||||
}
|
||||
|
||||
public override void UpdateBroken(float deltaTime, Camera cam)
|
||||
|
||||
Reference in New Issue
Block a user