This commit is contained in:
Evil Factory
2022-06-15 13:26:49 -03:00
410 changed files with 11140 additions and 5815 deletions
@@ -360,9 +360,6 @@ namespace Barotrauma
{
if (allowedSlot.HasFlag(SlotTypes[i]) && item.AllowedSlots.Any(s => s.HasFlag(SlotTypes[i])) && slots[i].Items.Any(it => it != item))
{
#if CLIENT
if (PersonalSlots.HasFlag(SlotTypes[i])) { hidePersonalSlots = false; }
#endif
if (!slots[i].First().AllowedSlots.Contains(InvSlotType.Any) || !TryPutItem(slots[i].FirstOrDefault(), character, new List<InvSlotType> { InvSlotType.Any }, true, ignoreCondition))
{
free = false;
@@ -382,9 +379,6 @@ namespace Barotrauma
{
if (allowedSlot.HasFlag(SlotTypes[i]) && item.GetComponents<Pickable>().Any(p => p.AllowedSlots.Any(s => s.HasFlag(SlotTypes[i]))) && slots[i].Empty())
{
#if CLIENT
if (PersonalSlots.HasFlag(SlotTypes[i])) { hidePersonalSlots = false; }
#endif
bool removeFromOtherSlots = item.ParentInventory != this;
if (placedInSlot == -1 && inWrongSlot)
{
@@ -454,9 +448,6 @@ namespace Barotrauma
GameAnalyticsManager.AddErrorEventOnce("CharacterInventory.TryPutItem:IndexOutOfRange", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
return false;
}
#if CLIENT
if (PersonalSlots.HasFlag(SlotTypes[index])) { hidePersonalSlots = false; }
#endif
//there's already an item in the slot
if (slots[index].Any())
{
@@ -480,9 +471,6 @@ namespace Barotrauma
foreach (InvSlotType allowedSlot in pickable.AllowedSlots)
{
if (!allowedSlot.HasFlag(SlotTypes[index])) { continue; }
#if CLIENT
if (PersonalSlots.HasFlag(allowedSlot)) { hidePersonalSlots = false; }
#endif
for (int i = 0; i < capacity; i++)
{
if (allowedSlot.HasFlag(SlotTypes[i]) && slots[i].Any() && !slots[i].Contains(item))
@@ -99,8 +99,8 @@ namespace Barotrauma.Items.Components
{
if (!docked && value)
{
if (DockingTarget == null) AttemptDock();
if (DockingTarget == null) return;
if (DockingTarget == null) { AttemptDock(); }
if (DockingTarget == null) { return; }
docked = true;
}
@@ -126,6 +126,14 @@ namespace Barotrauma.Items.Components
/// </summary>
public event Action OnUnDocked;
private bool outpostAutoDockingPromptShown;
enum AllowOutpostAutoDocking
{
Ask, Yes, No
}
private AllowOutpostAutoDocking allowOutpostAutoDocking = AllowOutpostAutoDocking.Ask;
public DockingPort(Item item, ContentXElement element)
: base(item, element)
{
@@ -613,7 +621,7 @@ namespace Barotrauma.Items.Components
hullRects[i].X -= expand;
hullRects[i].Width += expand * 2;
hullRects[i].Location -= MathUtils.ToPoint(subs[i].WorldPosition - subs[i].HiddenSubPosition);
hulls[i] = new Hull(MapEntityPrefab.Find(null, "hull"), hullRects[i], subs[i]);
hulls[i] = new Hull(hullRects[i], subs[i]);
hulls[i].RoomName = IsHorizontal ? "entityname.dockingport" : "entityname.dockinghatch";
hulls[i].AddToGrid(subs[i]);
hulls[i].FreeID();
@@ -622,7 +630,8 @@ namespace Barotrauma.Items.Components
{
bodies[i + j * 2] = GameMain.World.CreateEdge(
ConvertUnits.ToSimUnits(new Vector2(hullRects[i].X, hullRects[i].Y - hullRects[i].Height * j)),
ConvertUnits.ToSimUnits(new Vector2(hullRects[i].Right, hullRects[i].Y - hullRects[i].Height * j)));
ConvertUnits.ToSimUnits(new Vector2(hullRects[i].Right, hullRects[i].Y - hullRects[i].Height * j)),
BodyType.Static);
}
}
@@ -632,7 +641,9 @@ namespace Barotrauma.Items.Components
ConvertUnits.ToSimUnits(hullRects[0].Width + hullRects[1].Width),
ConvertUnits.ToSimUnits(hullRects[0].Height),
density: 0.0f,
offset: ConvertUnits.ToSimUnits(new Vector2(hullRects[0].Right, hullRects[0].Y - hullRects[0].Height / 2) - hulls[0].Submarine.HiddenSubPosition));
offset: ConvertUnits.ToSimUnits(new Vector2(hullRects[0].Right, hullRects[0].Y - hullRects[0].Height / 2) - hulls[0].Submarine.HiddenSubPosition),
Physics.CollisionWall,
Physics.CollisionCharacter | Physics.CollisionItem | Physics.CollisionCharacter | Physics.CollisionItemBlocking | Physics.CollisionProjectile);
outsideBlocker.UserData = this;
}
@@ -733,7 +744,7 @@ namespace Barotrauma.Items.Components
hullRects[i].Y += expand;
hullRects[i].Height += expand * 2;
hullRects[i].Location -= MathUtils.ToPoint(subs[i].WorldPosition - subs[i].HiddenSubPosition);
hulls[i] = new Hull(MapEntityPrefab.Find(null, "hull"), hullRects[i], subs[i]);
hulls[i] = new Hull(hullRects[i], subs[i]);
hulls[i].RoomName = IsHorizontal ? "entityname.dockingport" : "entityname.dockinghatch";
hulls[i].AddToGrid(subs[i]);
hulls[i].FreeID();
@@ -742,7 +753,8 @@ namespace Barotrauma.Items.Components
{
bodies[i + j * 2] = GameMain.World.CreateEdge(
ConvertUnits.ToSimUnits(new Vector2(hullRects[i].X + hullRects[i].Width * j, hullRects[i].Y)),
ConvertUnits.ToSimUnits(new Vector2(hullRects[i].X + hullRects[i].Width * j, hullRects[i].Y - hullRects[i].Height)));
ConvertUnits.ToSimUnits(new Vector2(hullRects[i].X + hullRects[i].Width * j, hullRects[i].Y - hullRects[i].Height)),
BodyType.Static);
}
}
@@ -752,7 +764,9 @@ namespace Barotrauma.Items.Components
ConvertUnits.ToSimUnits(hullRects[0].Width),
ConvertUnits.ToSimUnits(hullRects[0].Height + hullRects[1].Height),
density: 0.0f,
offset: ConvertUnits.ToSimUnits(new Vector2(hullRects[0].Center.X, hullRects[0].Y) - hulls[0].Submarine.HiddenSubPosition));
offset: ConvertUnits.ToSimUnits(new Vector2(hullRects[0].Center.X, hullRects[0].Y) - hulls[0].Submarine.HiddenSubPosition),
Physics.CollisionWall,
Physics.CollisionCharacter | Physics.CollisionItem | Physics.CollisionCharacter | Physics.CollisionItemBlocking | Physics.CollisionProjectile);
outsideBlocker.UserData = this;
}
@@ -778,8 +792,6 @@ namespace Barotrauma.Items.Components
if (body == null) { continue; }
body.BodyType = BodyType.Static;
body.Friction = 0.5f;
body.CollisionCategories = Physics.CollisionWall;
}
}
@@ -947,7 +959,7 @@ namespace Barotrauma.Items.Components
{
foreach (Body body in bodies)
{
if (body == null) continue;
if (body == null) { continue; }
GameMain.World.Remove(body);
}
bodies = null;
@@ -961,6 +973,9 @@ namespace Barotrauma.Items.Components
{
item.CreateServerEvent(this);
}
#elif CLIENT
autodockingVerification?.Close();
autodockingVerification = null;
#endif
OnUnDocked?.Invoke();
OnUnDocked = null;
@@ -1140,27 +1155,86 @@ namespace Barotrauma.Items.Components
public override void ReceiveSignal(Signal signal, Connection connection)
{
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
#if CLIENT
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient &&
!(GameMain.GameSession?.Campaign?.AllowedToManageCampaign(ClientPermissions.ManageMap) ?? false))
{
return;
}
#endif
if (dockingCooldown > 0.0f) { return; }
bool wasDocked = docked;
DockingPort prevDockingTarget = DockingTarget;
bool newDockedState = wasDocked;
switch (connection.Name)
{
case "toggle":
if (signal.value != "0")
{
Docked = !docked;
newDockedState = !docked;
}
break;
case "set_active":
case "set_state":
Docked = signal.value != "0";
newDockedState = signal.value != "0";
break;
}
if (newDockedState != wasDocked)
{
bool tryingToToggleOutpostDocking = docked ?
DockingTarget?.Item?.Submarine?.Info?.IsOutpost ?? false :
FindAdjacentPort()?.Item?.Submarine?.Info?.IsOutpost ?? false;
//trying to dock/undock from an outpost and the signal was sent by some automated system instead of a character
// -> ask if the player really wants to dock/undock to prevent a softlock if someone's wired the docking port
// in a way that makes always makes it dock/undock immediately at the start of the roun
if (tryingToToggleOutpostDocking && signal.sender == null)
{
if (allowOutpostAutoDocking == AllowOutpostAutoDocking.Ask)
{
#if CLIENT
if (!outpostAutoDockingPromptShown)
{
autodockingVerification = new GUIMessageBox(string.Empty,
TextManager.Get(newDockedState ? "autodockverification" : "autoundockverification"),
new LocalizedString[] { TextManager.Get("Yes"), TextManager.Get("No") });
autodockingVerification.Buttons[0].OnClicked += (btn, userdata) =>
{
autodockingVerification?.Close();
autodockingVerification = null;
if (item.Removed || GameMain.Client == null) { return false; }
allowOutpostAutoDocking = AllowOutpostAutoDocking.Yes;
item.CreateClientEvent(this);
return true;
};
autodockingVerification.Buttons[1].OnClicked += (btn, userdata) =>
{
autodockingVerification?.Close();
autodockingVerification = null;
if (item.Removed || GameMain.Client == null) { return false; }
allowOutpostAutoDocking = AllowOutpostAutoDocking.No;
item.CreateClientEvent(this);
return true;
};
}
#endif
outpostAutoDockingPromptShown = true;
return;
}
else if (allowOutpostAutoDocking == AllowOutpostAutoDocking.No)
{
return;
}
}
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
Docked = newDockedState;
}
#if SERVER
if (signal.sender != null && docked != wasDocked)
{
@@ -241,12 +241,14 @@ namespace Barotrauma.Items.Components
Body = new PhysicsBody(
ConvertUnits.ToSimUnits(Math.Max(doorRect.Width, 1)),
ConvertUnits.ToSimUnits(Math.Max(doorRect.Height, 1)),
0.0f,
1.5f)
radius: 0.0f,
density: 1.5f,
BodyType.Static,
Physics.CollisionWall,
Physics.CollisionCharacter | Physics.CollisionItem | Physics.CollisionCharacter | Physics.CollisionItemBlocking | Physics.CollisionProjectile,
findNewContacts: false)
{
UserData = item,
CollisionCategories = Physics.CollisionWall,
BodyType = BodyType.Static,
Friction = 0.5f
};
Body.SetTransformIgnoreContacts(
@@ -258,12 +260,16 @@ namespace Barotrauma.Items.Components
}
}
public override void Move(Vector2 amount)
public override void Move(Vector2 amount, bool ignoreContacts = false)
{
base.Move(amount);
Body?.SetTransform(Body.SimPosition + ConvertUnits.ToSimUnits(amount), 0.0f);
if (ignoreContacts)
{
Body?.SetTransformIgnoreContacts(Body.SimPosition + ConvertUnits.ToSimUnits(amount), 0.0f);
}
else
{
Body?.SetTransform(Body.SimPosition + ConvertUnits.ToSimUnits(amount), 0.0f);
}
#if CLIENT
UpdateConvexHulls();
#endif
@@ -144,9 +144,16 @@ namespace Barotrauma.Items.Components
}
}
public bool Combine(GeneticMaterial otherGeneticMaterial, Character user)
public enum CombineResult
{
if (!CanBeCombinedWith(otherGeneticMaterial)) { return false; }
None,
Refined,
Combined
}
public CombineResult Combine(GeneticMaterial otherGeneticMaterial, Character user)
{
if (!CanBeCombinedWith(otherGeneticMaterial)) { return CombineResult.None; }
float conditionIncrease = Rand.Range(ConditionIncreaseOnCombineMin, ConditionIncreaseOnCombineMax);
conditionIncrease += user?.GetStatValue(StatTypes.GeneticMaterialRefineBonus) ?? 0.0f;
@@ -158,7 +165,7 @@ namespace Barotrauma.Items.Components
{
MakeTainted();
}
return true;
return CombineResult.Refined;
}
else
{
@@ -171,7 +178,7 @@ namespace Barotrauma.Items.Components
{
MakeTainted();
}
return false;
return CombineResult.Combined;
}
}
@@ -41,6 +41,8 @@ namespace Barotrauma.Items.Components
private Character prevEquipper;
public override bool IsAttached => Attached;
private bool attachable, attached, attachedByDefault;
private Voronoi2.VoronoiCell attachTargetCell;
private PhysicsBody body;
@@ -71,6 +73,7 @@ namespace Barotrauma.Items.Components
set
{
attached = value;
item.CheckCleanable();
item.SetActiveSprite();
}
}
@@ -164,6 +167,8 @@ namespace Barotrauma.Items.Components
public bool SwingWhenAiming { get; set; }
[Editable, Serialize(false, IsPropertySaveable.No, description: "Should the item swing around when it's being used (for example, when firing a weapon or a welding tool).")]
public bool SwingWhenUsing { get; set; }
[Editable, Serialize(false, IsPropertySaveable.No)]
public bool DisableHeadRotation { get; set; }
[ConditionallyEditable(ConditionallyEditable.ConditionType.Attachable, MinValueFloat = 0.0f, MaxValueFloat = 0.999f, DecimalCount = 3), Serialize(0.55f, IsPropertySaveable.No, description: "Sprite depth that's used when the item is NOT attached to a wall.")]
public float SpriteDepthWhenDropped
@@ -180,11 +185,12 @@ namespace Barotrauma.Items.Components
Pusher = null;
if (element.GetAttributeBool("blocksplayers", false))
{
Pusher = new PhysicsBody(item.body.width, item.body.height, item.body.radius, item.body.Density)
Pusher = new PhysicsBody(item.body.width, item.body.height, item.body.radius,
item.body.Density,
BodyType.Dynamic,
Physics.CollisionItemBlocking,
Physics.CollisionCharacter | Physics.CollisionProjectile)
{
BodyType = BodyType.Dynamic,
CollidesWith = Physics.CollisionCharacter | Physics.CollisionProjectile,
CollisionCategories = Physics.CollisionItemBlocking,
Enabled = false,
UserData = this
};
@@ -3,7 +3,6 @@ using FarseerPhysics;
using Microsoft.Xna.Framework;
using System;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Items.Components
{
@@ -79,11 +78,18 @@ namespace Barotrauma.Items.Components
IsActive = true;
}
public override void Move(Vector2 amount)
public override void Move(Vector2 amount, bool ignoreContacts = false)
{
if (trigger != null && amount.LengthSquared() > 0.00001f)
{
trigger.SetTransform(item.SimPosition, 0.0f);
if (ignoreContacts)
{
trigger.SetTransformIgnoreContacts(item.SimPosition, 0.0f);
}
else
{
trigger.SetTransform(item.SimPosition, 0.0f);
}
}
}
@@ -119,17 +125,19 @@ namespace Barotrauma.Items.Components
}
var body = item.body ?? holdable.Body;
if (body != null)
{
trigger = new PhysicsBody(body.width, body.height, body.radius, body.Density)
trigger = new PhysicsBody(body.width, body.height, body.radius,
body.Density,
BodyType.Static,
Physics.CollisionWall,
Physics.CollisionNone,
findNewContacts: false)
{
UserData = item
};
trigger.FarseerBody.SetIsSensor(true);
trigger.FarseerBody.BodyType = BodyType.Static;
trigger.FarseerBody.CollisionCategories = Physics.CollisionWall;
trigger.FarseerBody.CollidesWith = Physics.CollisionNone;
}
}
@@ -111,8 +111,9 @@ namespace Barotrauma.Items.Components
ActivateNearbySleepingCharacters();
reloadTimer = reload;
reloadTimer /= (1f + character.GetStatValue(StatTypes.MeleeAttackSpeed));
reloadTimer /= (1f + item.GetQualityModifier(Quality.StatType.StrikingSpeedMultiplier));
reloadTimer /= 1f + character.GetStatValue(StatTypes.MeleeAttackSpeed);
reloadTimer /= 1f + item.GetQualityModifier(Quality.StatType.StrikingSpeedMultiplier);
character.AnimController.LockFlippingUntil = (float)Timing.TotalTime + reloadTimer;
item.body.FarseerBody.CollisionCategories = Physics.CollisionProjectile;
item.body.FarseerBody.CollidesWith = Physics.CollisionCharacter | Physics.CollisionWall | Physics.CollisionItemBlocking;
@@ -216,6 +217,10 @@ namespace Barotrauma.Items.Components
{
hitPos = MathUtils.WrapAnglePi(Math.Min(hitPos + deltaTime * 3f, MathHelper.PiOver4));
ac.HoldItem(deltaTime, item, handlePos, aimPos, Vector2.Zero, aim: false, hitPos, holdAngle + hitPos, aimMelee: true);
if (ac.InWater)
{
ac.LockFlippingUntil = (float)Timing.TotalTime + Reload;
}
}
else
{
@@ -4,7 +4,6 @@ using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Items.Components
{
@@ -20,6 +19,8 @@ namespace Barotrauma.Items.Components
private CoroutineHandle pickingCoroutine;
public virtual bool IsAttached => false;
public List<InvSlotType> AllowedSlots
{
get { return allowedSlots; }
@@ -71,6 +72,7 @@ namespace Barotrauma.Items.Components
//return if someone is already trying to pick the item
if (pickTimer > 0.0f) { return false; }
if (picker == null || picker.Inventory == null) { return false; }
if (!picker.Inventory.AccessibleWhenAlive && !picker.Inventory.AccessibleByOwner) { return false; }
if (PickingTime > 0.0f)
{
@@ -226,7 +228,7 @@ namespace Barotrauma.Items.Components
{
foreach (Connection c in connectionPanel.Connections)
{
foreach (Wire w in c.Wires)
foreach (Wire w in c.Wires.ToArray())
{
if (w == null) continue;
w.Item.Drop(character);
@@ -40,7 +40,7 @@ namespace Barotrauma.Items.Components
public override bool Use(float deltaTime, Character character = null)
{
if (character == null || character.Removed) return false;
if (character == null || character.Removed) { return false; }
if (!character.IsKeyDown(InputType.Aim) || character.Stun > 0.0f) { return false; }
IsActive = true;
@@ -55,12 +55,11 @@ namespace Barotrauma.Items.Components
if (UsableIn == UseEnvironment.Water) { return true; }
}
Vector2 dir = Vector2.Normalize(character.CursorPosition - character.Position);
//move upwards if the cursor is at the position of the character
if (!MathUtils.IsValid(dir)) dir = Vector2.UnitY;
Vector2 dir = character.CursorPosition - character.Position;
if (!MathUtils.IsValid(dir)) { return true; }
float length = 200;
dir = dir.ClampLength(length) / length;
Vector2 propulsion = dir * Force * character.PropulsionSpeedMultiplier;
if (character.AnimController.InWater && Force > 0.0f) { character.AnimController.TargetMovement = dir; }
foreach (Limb limb in character.AnimController.Limbs)
@@ -79,7 +79,7 @@ namespace Barotrauma.Items.Components
{
get
{
Matrix bodyTransform = Matrix.CreateRotationZ(item.body == null ? MathHelper.ToRadians(item.Rotation) : item.body.Rotation);
Matrix bodyTransform = Matrix.CreateRotationZ(item.body == null ? item.RotationRad : item.body.Rotation);
Vector2 flippedPos = barrelPos;
if (item.body != null && item.body.Dir < 0.0f) { flippedPos.X = -flippedPos.X; }
return Vector2.Transform(flippedPos, bodyTransform) * item.Scale;
@@ -94,7 +94,7 @@ namespace Barotrauma.Items.Components
get; set;
}
[Serialize(true, IsPropertySaveable.No, description: "Can the item hit broken doors.")]
[Serialize(true, IsPropertySaveable.No, description: "Can the item hit doors.")]
public bool HitItems { get; set; }
[Serialize(false, IsPropertySaveable.No, description: "Can the item hit broken doors.")]
@@ -697,14 +697,17 @@ namespace Barotrauma.Items.Components
Vector2 fromCharacterToLeak = leak.WorldPosition - character.AnimController.AimSourceWorldPos;
float dist = fromCharacterToLeak.Length();
float reach = AIObjectiveFixLeak.CalculateReach(this, character);
if (dist > reach * 3)
if (dist > reach * 2)
{
// Too far away -> consider this done and hope the AI is smart enough to move closer
Reset();
return true;
}
character.AIController.SteeringManager.Reset();
if (character.AIController.SteeringManager is IndoorsSteeringManager pathSteering)
{
pathSteering.ResetPath();
}
if (!character.AnimController.InWater)
{
// TODO: use the collider size?
@@ -714,34 +717,25 @@ namespace Barotrauma.Items.Components
humanAnim.Crouching = true;
}
}
if (dist > reach * 0.8f || dist > reach * 0.5f && character.AnimController.Limbs.Any(l => l.InWater))
if (!character.IsClimbing)
{
// Steer closer
if (character.AIController.SteeringManager is IndoorsSteeringManager indoorSteering)
if (dist > reach * 0.8f || dist > reach * 0.5f && character.AnimController.Limbs.Any(l => l.InWater))
{
// Swimming inside the sub
if (indoorSteering.CurrentPath != null && !indoorSteering.IsPathDirty && (indoorSteering.CurrentPath.Unreachable || indoorSteering.CurrentPath.Finished))
// Steer closer
Vector2 dir = Vector2.Normalize(fromCharacterToLeak);
if (!character.InWater)
{
Vector2 dir = Vector2.Normalize(fromCharacterToLeak);
character.AIController.SteeringManager.SteeringManual(deltaTime, dir);
}
else
{
character.AIController.SteeringManager.SteeringSeek(character.GetRelativeSimPosition(leak));
dir.Y = 0;
}
character.AIController.SteeringManager.SteeringManual(deltaTime, dir);
}
else
else if (dist < reach * 0.25f && !character.IsClimbing)
{
// Swimming outside the sub
character.AIController.SteeringManager.SteeringSeek(character.GetRelativeSimPosition(leak));
// Too close -> steer away
character.AIController.SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(character.SimPosition - leak.SimPosition));
}
}
else if (dist < reach * 0.25f)
{
// Too close -> steer away
character.AIController.SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(character.SimPosition - leak.SimPosition));
}
if (dist <= reach)
if (dist <= reach || character.IsClimbing)
{
// In range
character.CursorPosition = leak.WorldPosition;
@@ -815,7 +809,7 @@ namespace Barotrauma.Items.Components
}
bool leakFixed = (leak.Open <= 0.0f || leak.Removed) &&
(leak.ConnectedWall == null || leak.ConnectedWall.Sections.Average(s => s.damage) < 1);
(leak.ConnectedWall == null || leak.ConnectedWall.Sections.Max(s => s.damage) < 0.1f);
if (leakFixed && leak.FlowTargetHull?.DisplayName != null && character.IsOnPlayerTeam)
{
@@ -390,7 +390,7 @@ namespace Barotrauma.Items.Components
IsActive = isActive;
}
public void SetRequiredItems(ContentXElement element)
public void SetRequiredItems(ContentXElement element, bool allowEmpty = false)
{
bool returnEmpty = false;
#if CLIENT
@@ -412,13 +412,13 @@ namespace Barotrauma.Items.Components
requiredItems[ri.Type].Add(ri);
}
}
else
else if (!allowEmpty)
{
DebugConsole.ThrowError("Error in item config \"" + item.ConfigFilePath + "\" - component " + GetType().ToString() + " requires an item with no identifiers.");
}
}
public virtual void Move(Vector2 amount) { }
public virtual void Move(Vector2 amount, bool ignoreContacts = false) { }
/// <summary>a Character has picked the item</summary>
public virtual bool Pick(Character picker)
@@ -17,18 +17,20 @@ namespace Barotrauma.Items.Components
public readonly Item Item;
public readonly StatusEffect StatusEffect;
public readonly bool ExcludeBroken;
public ActiveContainedItem(Item item, StatusEffect statusEffect, bool excludeBroken)
public readonly bool ExcludeFullCondition;
public ActiveContainedItem(Item item, StatusEffect statusEffect, bool excludeBroken, bool excludeFullCondition)
{
Item = item;
StatusEffect = statusEffect;
ExcludeBroken = excludeBroken;
ExcludeFullCondition = excludeFullCondition;
}
}
class SlotRestrictions
{
public readonly int MaxStackSize;
public readonly List<RelatedItem> ContainableItems;
public List<RelatedItem> ContainableItems;
public SlotRestrictions(int maxStackSize, List<RelatedItem> containableItems)
{
@@ -185,7 +187,12 @@ namespace Barotrauma.Items.Components
[Serialize(false, IsPropertySaveable.No)]
public bool RemoveContainedItemsOnDeconstruct { get; set; }
private SlotRestrictions[] slotRestrictions;
private readonly ImmutableArray<SlotRestrictions> slotRestrictions;
readonly List<ISerializableEntity> targets = new List<ISerializableEntity>();
private Vector2 prevContainedItemPositions;
public bool ShouldBeContained(string[] identifiersOrTags, out bool isRestrictionsDefined)
{
@@ -235,10 +242,11 @@ namespace Barotrauma.Items.Components
}
}
Inventory = new ItemInventory(item, this, totalCapacity, SlotsPerRow);
slotRestrictions = new SlotRestrictions[totalCapacity];
List<SlotRestrictions> newSlotRestrictions = new List<SlotRestrictions>(totalCapacity);
for (int i = 0; i < capacity; i++)
{
slotRestrictions[i] = new SlotRestrictions(maxStackSize, ContainableItems);
newSlotRestrictions.Add(new SlotRestrictions(maxStackSize, ContainableItems));
}
int subContainerIndex = capacity;
@@ -266,14 +274,37 @@ namespace Barotrauma.Items.Components
for (int i = subContainerIndex; i < subContainerIndex + subCapacity; i++)
{
slotRestrictions[i] = new SlotRestrictions(subMaxStackSize, subContainableItems);
newSlotRestrictions.Add(new SlotRestrictions(subMaxStackSize, subContainableItems));
}
subContainerIndex += subCapacity;
}
capacity = totalCapacity;
slotRestrictions = newSlotRestrictions.ToImmutableArray();
System.Diagnostics.Debug.Assert(totalCapacity == slotRestrictions.Length);
InitProjSpecific(element);
}
public void ReloadContainableRestrictions(ContentXElement element)
{
int containableIndex = 0;
foreach (var subElement in element.GetChildElements("containable"))
{
RelatedItem containable = RelatedItem.Load(subElement, returnEmpty: false, parentDebugName: item.Name);
if (containable == null)
{
DebugConsole.ThrowError("Error when loading containable restrictions for \"" + item.Name + "\" - containable with no identifiers.");
continue;
}
ContainableItems[containableIndex] = containable;
containableIndex++;
if (containableIndex >= ContainableItems.Count) { break; }
}
for (int i = 0; i < capacity; i++)
{
slotRestrictions[i].ContainableItems = ContainableItems;
}
}
public int GetMaxStackSize(int slotIndex)
{
if (slotIndex < 0 || slotIndex >= capacity)
@@ -300,7 +331,7 @@ namespace Barotrauma.Items.Components
if (!containableItem.MatchesItem(containedItem)) { continue; }
foreach (StatusEffect effect in containableItem.statusEffects)
{
activeContainedItems.Add(new ActiveContainedItem(containedItem, effect, containableItem.ExcludeBroken));
activeContainedItems.Add(new ActiveContainedItem(containedItem, effect, containableItem.ExcludeBroken, containableItem.ExcludeFullCondition));
}
}
}
@@ -315,7 +346,7 @@ namespace Barotrauma.Items.Components
IsActive = activeContainedItems.Count > 0 || Inventory.AllItems.Any(it => it.body != null);
}
public override void Move(Vector2 amount)
public override void Move(Vector2 amount, bool ignoreContacts = false)
{
SetContainedItemPositions();
}
@@ -363,18 +394,21 @@ namespace Barotrauma.Items.Components
return false;
}
readonly List<ISerializableEntity> targets = new List<ISerializableEntity>();
public override void Update(float deltaTime, Camera cam)
{
if (!string.IsNullOrEmpty(SpawnWithId) && !alwaysContainedItemsSpawned)
{
SpawnAlwaysContainedItems();
alwaysContainedItemsSpawned = true;
}
if (item.ParentInventory is CharacterInventory ownerInventory)
{
item.SetContainedItemPositions();
if (Vector2.DistanceSquared(prevContainedItemPositions, item.Position) > 10.0f)
{
SetContainedItemPositions();
prevContainedItemPositions = item.Position;
}
if (AutoInject)
{
@@ -395,7 +429,7 @@ namespace Barotrauma.Items.Components
item.body.Enabled &&
item.body.FarseerBody.Awake)
{
item.SetContainedItemPositions();
SetContainedItemPositions();
}
else if (activeContainedItems.Count == 0)
{
@@ -408,6 +442,7 @@ namespace Barotrauma.Items.Components
Item contained = activeContainedItem.Item;
if (activeContainedItem.ExcludeBroken && contained.Condition <= 0.0f) { continue; }
if (activeContainedItem.ExcludeFullCondition && contained.IsFullCondition) { continue; }
StatusEffect effect = activeContainedItem.StatusEffect;
if (effect.HasTargetType(StatusEffect.TargetType.This))
@@ -569,7 +604,7 @@ namespace Barotrauma.Items.Components
transformedItemPos += new Vector2(item.Rect.X, item.Rect.Y);
if (Math.Abs(item.Rotation) > 0.01f)
{
Matrix transform = Matrix.CreateRotationZ(MathHelper.ToRadians(-item.Rotation));
Matrix transform = Matrix.CreateRotationZ(-item.RotationRad);
transformedItemPos = Vector2.Transform(transformedItemPos - item.Position, transform) + item.Position;
transformedItemInterval = Vector2.Transform(transformedItemInterval, transform);
transformedItemIntervalHorizontal = Vector2.Transform(transformedItemIntervalHorizontal, transform);
@@ -600,7 +635,7 @@ namespace Barotrauma.Items.Components
}
else
{
currentRotation += MathHelper.ToRadians(-item.Rotation);
currentRotation += -item.RotationRad;
}
int i = 0;
@@ -751,7 +786,11 @@ namespace Barotrauma.Items.Components
return;
}
#endif
Inventory.AllItemsMod.ForEach(it => it.Drop(null));
//if we're unloading the whole sub, no need to drop anything (everything's going to be removed anyway)
if (!Submarine.Unloading)
{
Inventory.AllItemsMod.ForEach(it => it.Drop(null));
}
}
public override void Load(ContentXElement componentElement, bool usePrefabValues, IdRemap idRemap)
@@ -114,6 +114,20 @@ namespace Barotrauma.Items.Components
private set;
} = true;
[Serialize(false, IsPropertySaveable.No)]
public bool NonInteractableWhenFlippedX
{
get;
set;
}
[Serialize(false, IsPropertySaveable.No)]
public bool NonInteractableWhenFlippedY
{
get;
set;
}
public Controller(Item item, ContentXElement element)
: base(item, element)
{
@@ -147,7 +161,7 @@ namespace Barotrauma.Items.Components
CancelUsing(user);
user = null;
}
if (!IsToggle) { IsActive = false; }
if (!IsToggle || item.Connections == null) { IsActive = false; }
return;
}
@@ -462,19 +476,8 @@ namespace Barotrauma.Items.Components
{
dir = dir == Direction.Left ? Direction.Right : Direction.Left;
}
userPos.X = -UserPos.X;
for (int i = 0; i < limbPositions.Count; i++)
{
float diff = (item.Rect.X + limbPositions[i].Position.X * item.Scale) - item.Rect.Center.X;
Vector2 flippedPos =
new Vector2(
(item.Rect.Center.X - diff - item.Rect.X) / item.Scale,
limbPositions[i].Position.Y);
limbPositions[i] = new LimbPos(limbPositions[i].LimbType, flippedPos, limbPositions[i].AllowUsingLimb);
}
userPos.X = -UserPos.X;
FlipLimbPositions();
}
public override void FlipY(bool relativeToSub)
@@ -519,6 +522,11 @@ namespace Barotrauma.Items.Components
{
if (Screen.Selected == GameMain.SubEditorScreen)
{
if (item.FlippedX)
{
FlipLimbPositions();
}
// Don't save flipped positions.
foreach (var limbPos in limbPositions)
{
element.Add(new XElement("limbposition",
@@ -526,6 +534,10 @@ namespace Barotrauma.Items.Components
new XAttribute("position", XMLExtensions.Vector2ToString(limbPos.Position)),
new XAttribute("allowusinglimb", limbPos.AllowUsingLimb)));
}
if (item.FlippedX)
{
FlipLimbPositions();
}
}
return element;
}
@@ -558,5 +570,41 @@ namespace Barotrauma.Items.Components
}
}
}
private void FlipLimbPositions()
{
for (int i = 0; i < limbPositions.Count; i++)
{
float diff = (item.Rect.X + limbPositions[i].Position.X * item.Scale) - item.Rect.Center.X;
Vector2 flippedPos =
new Vector2(
(item.Rect.Center.X - diff - item.Rect.X) / item.Scale,
limbPositions[i].Position.Y);
limbPositions[i] = new LimbPos(limbPositions[i].LimbType, flippedPos, limbPositions[i].AllowUsingLimb);
}
}
public override void OnItemLoaded()
{
if (item.FlippedX && NonInteractableWhenFlippedX)
{
item.NonInteractable = true;
}
else if (item.FlippedY && NonInteractableWhenFlippedY)
{
item.NonInteractable = true;
}
}
public override void Reset()
{
base.Reset();
LoadLimbPositions(originalElement);
if (item.FlippedX)
{
FlipLimbPositions();
}
}
}
}
@@ -231,28 +231,40 @@ namespace Barotrauma.Items.Components
if (targetItem == otherItem) { continue; }
if (deconstructProduct.RequiredOtherItem.Any(r => otherItem.HasTag(r) || r == otherItem.Prefab.Identifier))
{
user?.CheckTalents(AbilityEffectType.OnGeneticMaterialCombinedOrRefined);
foreach (Character character in Character.GetFriendlyCrew(user))
{
character.CheckTalents(AbilityEffectType.OnCrewGeneticMaterialCombinedOrRefined);
}
var geneticMaterial1 = targetItem.GetComponent<GeneticMaterial>();
var geneticMaterial2 = otherItem.GetComponent<GeneticMaterial>();
if (geneticMaterial1 != null && geneticMaterial2 != null)
{
if (geneticMaterial1.Combine(geneticMaterial2, user))
var result = geneticMaterial1.Combine(geneticMaterial2, user);
if (result == GeneticMaterial.CombineResult.Refined)
{
inputContainer.Inventory.RemoveItem(otherItem);
OutputContainer.Inventory.RemoveItem(otherItem);
Entity.Spawner.AddItemToRemoveQueue(otherItem);
}
if (result != GeneticMaterial.CombineResult.None)
{
OnCombinedOrRefined();
}
allowRemove = false;
return;
}
inputContainer.Inventory.RemoveItem(otherItem);
OutputContainer.Inventory.RemoveItem(otherItem);
Entity.Spawner.AddItemToRemoveQueue(otherItem);
else
{
inputContainer.Inventory.RemoveItem(otherItem);
OutputContainer.Inventory.RemoveItem(otherItem);
Entity.Spawner.AddItemToRemoveQueue(otherItem);
OnCombinedOrRefined();
}
}
}
void OnCombinedOrRefined()
{
user?.CheckTalents(AbilityEffectType.OnGeneticMaterialCombinedOrRefined);
foreach (Character character in Character.GetFriendlyCrew(user))
{
character.CheckTalents(AbilityEffectType.OnCrewGeneticMaterialCombinedOrRefined);
}
}
}
@@ -161,7 +161,7 @@ namespace Barotrauma.Items.Components
hullData.ReceivedWaterAmount = null;
if (fromWaterDetector)
{
hullData.ReceivedWaterAmount = Math.Min(sourceHull.WaterVolume / sourceHull.Volume, 1.0f);
hullData.ReceivedWaterAmount = WaterDetector.GetWaterPercentage(sourceHull);
}
foreach (var linked in sourceHull.linkedTo)
{
@@ -174,7 +174,7 @@ namespace Barotrauma.Items.Components
linkedHullData.ReceivedWaterAmount = null;
if (fromWaterDetector)
{
linkedHullData.ReceivedWaterAmount = Math.Min(linkedHull.WaterVolume / linkedHull.Volume, 1.0f);
linkedHullData.ReceivedWaterAmount = WaterDetector.GetWaterPercentage(linkedHull);
}
}
break;
@@ -1,10 +1,9 @@
using Barotrauma.Networking;
using Barotrauma.MapCreatures.Behavior;
using Barotrauma.Networking;
using Microsoft.Xna.Framework;
using System;
using System.Globalization;
using System.Linq;
using System.Xml.Linq;
using Barotrauma.MapCreatures.Behavior;
namespace Barotrauma.Items.Components
{
@@ -24,7 +23,10 @@ namespace Barotrauma.Items.Components
if (value == hijacked) { return; }
hijacked = value;
#if SERVER
item.CreateServerEvent(this);
if (!Submarine.Unloading)
{
item.CreateServerEvent(this);
}
#endif
}
}
@@ -68,6 +68,8 @@ namespace Barotrauma.Items.Components
private const float ConnectedSubUpdateInterval = 1.0f;
float connectedSubUpdateTimer;
private double lastReceivedSteeringSignalTime;
public bool AutoPilot
{
get { return autoPilot; }
@@ -312,16 +314,20 @@ namespace Barotrauma.Items.Components
}
else if (AutoPilot)
{
UpdateAutoPilot(deltaTime);
float throttle = 1.0f;
if (controlledSub != null)
//signals override autopilot for a duration of one second
if (lastReceivedSteeringSignalTime < Timing.TotalTime - 1)
{
//if the sub is heading in the correct direction, throttle the speed according to the user's skill
//if it's e.g. sinking due to extra water, don't throttle, but allow emptying up the ballast completely
throttle = MathHelper.Clamp(Vector2.Dot(controlledSub.Velocity, TargetVelocity) / 100.0f, 0.0f, 1.0f);
UpdateAutoPilot(deltaTime);
float throttle = 1.0f;
if (controlledSub != null)
{
//if the sub is heading in the correct direction, throttle the speed according to the user's skill
//if it's e.g. sinking due to extra water, don't throttle, but allow emptying up the ballast completely
throttle = MathHelper.Clamp(Vector2.Dot(controlledSub.Velocity, TargetVelocity) / 100.0f, 0.0f, 1.0f);
}
float maxSpeed = MathHelper.Lerp(AutoPilotMaxSpeed, AIPilotMaxSpeed, userSkill) * 100.0f;
TargetVelocity = TargetVelocity.ClampLength(MathHelper.Lerp(100.0f, maxSpeed, throttle));
}
float maxSpeed = MathHelper.Lerp(AutoPilotMaxSpeed, AIPilotMaxSpeed, userSkill) * 100.0f;
TargetVelocity = TargetVelocity.ClampLength(MathHelper.Lerp(100.0f, maxSpeed, throttle));
}
else
{
@@ -394,8 +400,8 @@ namespace Barotrauma.Items.Components
private void IncreaseSkillLevel(Character user, float deltaTime)
{
if (user?.Info == null) { return; }
// Do not increase the helm skill when "steering" the sub in an outpost level
if (GameMain.GameSession?.Campaign != null && Level.IsLoadedOutpost) { return; }
// Do not increase the helm skill when "steering" the sub while docked into something static (e.g. outpost or wreck)
if (GameMain.GameSession?.Campaign != null && controlledSub != null && controlledSub.DockedTo.Any(d => d.PhysicsBody.BodyType == BodyType.Static)) { return; }
float userSkill = Math.Max(user.GetSkillLevel("helm"), 1.0f) / 100.0f;
user.Info.IncreaseSkillLevel(
@@ -821,6 +827,7 @@ namespace Barotrauma.Items.Components
steeringInput.X = MathHelper.Clamp(steeringInput.X, -100.0f, 100.0f);
steeringInput.Y = MathHelper.Clamp(-steeringInput.Y, -100.0f, 100.0f);
TargetVelocity = steeringInput;
lastReceivedSteeringSignalTime = Timing.TotalTime;
}
else
{
@@ -3,7 +3,6 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Xml.Linq;
using Microsoft.Xna.Framework;
namespace Barotrauma.Items.Components
@@ -81,6 +80,8 @@ namespace Barotrauma.Items.Components
private ItemContainer? container;
private float growthTickTimer;
private List<LightComponent>? lightComponents;
public Planter(Item item, ContentXElement element) : base(item, element)
{
canBePicked = true;
@@ -107,10 +108,14 @@ namespace Barotrauma.Items.Components
base.OnItemLoaded();
IsActive = true;
#if CLIENT
lightComponent = item.GetComponent<LightComponent>();
if (lightComponent != null)
var lights = item.GetComponents<LightComponent>();
if (lights.Any())
{
lightComponent.Light.Enabled = false;
lightComponents = lights.ToList();
foreach (var light in lightComponents)
{
light.Light.Enabled = false;
}
}
#endif
container = item.GetComponent<ItemContainer>();
@@ -227,12 +232,17 @@ namespace Barotrauma.Items.Components
base.Update(deltaTime, cam);
#if CLIENT
if (lightComponent != null)
if (lightComponents != null && lightComponents.Count > 0)
{
bool hasSeed = false;
foreach (Growable? seed in GrowableSeeds) { hasSeed |= seed != null; }
lightComponent.Light.Enabled = hasSeed;
foreach (Growable? seed in GrowableSeeds)
{
hasSeed |= seed != null;
}
foreach (var light in lightComponents)
{
light.Light.Enabled = hasSeed;
}
}
#endif
@@ -68,7 +68,7 @@ namespace Barotrauma.Items.Components
get { return poweredList; }
}
public static readonly List<Connection> ChangedConnections = new List<Connection>();
public static readonly HashSet<Connection> ChangedConnections = new HashSet<Connection>();
public readonly static Dictionary<int, GridInfo> Grids = new Dictionary<int, GridInfo>();
@@ -150,7 +150,7 @@ namespace Barotrauma.Items.Components
{
if (powerOut?.Grid != null) { return powerOut.Grid.Voltage; }
}
return voltage;
return PowerConsumption <= 0.0f ? 1.0f : voltage;
}
set
{
@@ -158,21 +158,13 @@ namespace Barotrauma.Items.Components
}
}
public bool PoweredByTinkering
{
get
{
if (this is PowerContainer) { return false; }
foreach (Repairable repairable in Item.Repairables)
{
if (repairable.IsTinkering && repairable.TinkeringPowersDevices)
{
return true;
}
}
return false;
}
}
/// <summary>
/// Essentially Voltage / MinVoltage (= how much of the minimum required voltage has been satisfied), clamped between 0 and 1.
/// Can be used by status effects or sounds to check if the item has enough power to run
/// </summary>
public float RelativeVoltage => minVoltage <= 0.0f ? 1.0f : MathHelper.Clamp(Voltage / minVoltage, 0.0f, 1.0f);
public bool PoweredByTinkering { get; set; }
[Editable, Serialize(true, IsPropertySaveable.Yes, description: "Can the item be damaged by electomagnetic pulses.")]
public bool VulnerableToEMP
@@ -449,7 +441,7 @@ namespace Barotrauma.Items.Components
#if CLIENT
sw.Stop();
GameMain.PerformanceCounter.AddElapsedTicks("GridUpdate", sw.ElapsedTicks);
GameMain.PerformanceCounter.AddElapsedTicks("Update:Power", sw.ElapsedTicks);
sw.Restart();
#endif
@@ -606,7 +598,7 @@ namespace Barotrauma.Items.Components
#if CLIENT
sw.Stop();
GameMain.PerformanceCounter.AddElapsedTicks("PowerUpdate", sw.ElapsedTicks);
GameMain.PerformanceCounter.AddElapsedTicks("Update:Power", sw.ElapsedTicks);
#endif
}
@@ -231,6 +231,13 @@ namespace Barotrauma.Items.Components
set;
}
[Serialize(false, IsPropertySaveable.No, description:"Enable only if you want to make the projectile ignore collisions with other projectiles when it's shot. Doesn't have any effect, if the item is not set to be damaged by projectiles.")]
public bool IgnoreProjectilesWhileActive
{
get;
set;
}
public Body StickTarget
{
get;
@@ -405,6 +412,10 @@ namespace Barotrauma.Items.Components
item.body.CollisionCategories = Physics.CollisionProjectile;
item.body.CollidesWith = Physics.CollisionCharacter | Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionItemBlocking;
if (item.Prefab.DamagedByProjectiles && !IgnoreProjectilesWhileActive)
{
item.body.CollidesWith |= Physics.CollisionProjectile;
}
IsActive = true;
@@ -835,6 +846,10 @@ namespace Barotrauma.Items.Components
}
else if (target.Body.UserData is Limb limb)
{
if (!FriendlyFire && User != null && limb.character.IsFriendly(User))
{
return false;
}
// when hitting limbs with piercing ammo, don't lose as much speed
if (MaxTargetsToHit > 1)
{
@@ -954,7 +969,8 @@ namespace Barotrauma.Items.Components
{
item.body.LinearVelocity *= deflectedSpeedMultiplier;
}
else if ( stickJoint == null && StickTarget == null &&
else if ( remainingHits <= 0 &&
stickJoint == null && StickTarget == null &&
StickToStructures && target.Body.UserData is Structure ||
((StickToLightTargets || target.Body.Mass > item.body.Mass * 0.5f) &&
(DoesStick ||
@@ -56,7 +56,8 @@ namespace Barotrauma.Items.Components
if (value == qualityLevel) { return; }
bool wasInFullCondition = item.IsFullCondition;
qualityLevel = MathHelper.Clamp(value, 0, MaxQuality);
qualityLevel = MathHelper.Clamp(value, 0, MaxQuality);
item.RecalculateConditionValues();
//set the condition to the new max condition
if (wasInFullCondition && statValues.ContainsKey(StatType.Condition))
{
@@ -106,7 +106,25 @@ namespace Barotrauma.Items.Components
}
}
public bool IsTinkering { get; private set; } = false;
private bool isTinkering;
public bool IsTinkering
{
get { return isTinkering; }
private set
{
if (isTinkering == value) { return; }
isTinkering = value;
if (tinkeringPowersDevices)
{
foreach (Powered powered in item.GetComponents<Powered>())
{
if (powered is PowerContainer) { continue; }
powered.PoweredByTinkering = isTinkering;
}
}
}
}
public Character CurrentFixer { get; private set; }
private Item currentRepairItem;
@@ -584,7 +602,7 @@ namespace Barotrauma.Items.Components
private bool ShouldDeteriorate()
{
if (Level.IsLoadedOutpost) { return false; }
if (Level.IsLoadedFriendlyOutpost) { return false; }
if (LastActiveTime > Timing.TotalTime) { return true; }
foreach (ItemComponent ic in item.Components)
@@ -160,7 +160,8 @@ namespace Barotrauma.Items.Components
public override void Update(float deltaTime, Camera cam)
{
if (source == null || target == null || target.Removed ||
(source is Entity sourceEntity && sourceEntity.Removed))
(source is Entity sourceEntity && sourceEntity.Removed) ||
(source is Limb limb && limb.Removed))
{
ResetSource();
target = null;
@@ -0,0 +1,10 @@
namespace Barotrauma.Items.Components
{
sealed class AndComponent : BooleanOperatorComponent
{
public AndComponent(Item item, ContentXElement element)
: base(item, element) { }
protected override bool GetOutput(int numTrueInputs) => numTrueInputs >= 2;
}
}
@@ -3,7 +3,7 @@ using System.Xml.Linq;
namespace Barotrauma.Items.Components
{
class AndComponent : ItemComponent
abstract class BooleanOperatorComponent : ItemComponent
{
protected string output, falseOutput;
@@ -70,22 +70,25 @@ namespace Barotrauma.Items.Components
}
}
public AndComponent(Item item, ContentXElement element)
public BooleanOperatorComponent(Item item, ContentXElement element)
: base(item, element)
{
timeSinceReceived = new float[] { Math.Max(timeFrame * 2.0f, 0.1f), Math.Max(timeFrame * 2.0f, 0.1f) };
IsActive = true;
}
public override void Update(float deltaTime, Camera cam)
protected abstract bool GetOutput(int numTrueInputs);
public sealed override void Update(float deltaTime, Camera cam)
{
bool state = true;
int receivedInputs = 0;
for (int i = 0; i < timeSinceReceived.Length; i++)
{
if (timeSinceReceived[i] > timeFrame) { state = false; }
if (timeSinceReceived[i] <= timeFrame) { receivedInputs += 1; }
timeSinceReceived[i] += deltaTime;
}
bool state = GetOutput(receivedInputs);
string signalOut = state ? output : falseOutput;
if (string.IsNullOrEmpty(signalOut))
{
@@ -0,0 +1,10 @@
namespace Barotrauma.Items.Components
{
sealed class OrComponent : BooleanOperatorComponent
{
public OrComponent(Item item, ContentXElement element)
: base(item, element) { }
protected override bool GetOutput(int numTrueInputs) => numTrueInputs > 0;
}
}
@@ -0,0 +1,10 @@
namespace Barotrauma.Items.Components
{
sealed class XorComponent : BooleanOperatorComponent
{
public XorComponent(Item item, ContentXElement element)
: base(item, element) { }
protected override bool GetOutput(int numTrueInputs) => numTrueInputs == 1;
}
}
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Xml.Linq;
@@ -19,11 +20,8 @@ namespace Barotrauma.Items.Components
public readonly string Name;
public readonly LocalizedString DisplayName;
private readonly Wire[] wires;
public IEnumerable<Wire> Wires
{
get { return wires; }
}
private readonly HashSet<Wire> wires;
public IReadOnlyCollection<Wire> Wires => wires;
private readonly Item item;
@@ -31,7 +29,7 @@ namespace Barotrauma.Items.Components
public readonly List<StatusEffect> Effects;
public readonly ushort[] wireId;
public readonly List<ushort> LoadedWireIds;
//The grid the connection is a part of
public GridInfo Grid;
@@ -92,7 +90,7 @@ namespace Barotrauma.Items.Components
MaxWires = Math.Max(element.Elements().Count(e => e.Name.ToString().Equals("link", StringComparison.OrdinalIgnoreCase)), MaxWires);
MaxPlayerConnectableWires = element.GetAttributeInt("maxplayerconnectablewires", MaxWires);
wires = new Wire[MaxWires];
wires = new HashSet<Wire>();
IsOutput = element.Name.ToString() == "output";
Name = element.GetAttributeString("name", IsOutput ? "output" : "input");
@@ -150,23 +148,15 @@ namespace Barotrauma.Items.Components
IsPower = Name == "power_in" || Name == "power" || Name == "power_out";
wireId = new ushort[MaxWires];
LoadedWireIds = new List<ushort>();
foreach (var subElement in element.Elements())
{
switch (subElement.Name.ToString().ToLowerInvariant())
{
case "link":
int index = -1;
for (int i = 0; i < MaxWires; i++)
{
if (wireId[i] < 1) { index = i; }
}
if (index == -1) { break; }
int id = subElement.GetAttributeInt("w", 0);
if (id < 0) { id = 0; }
wireId[index] = idRemap.GetOffsetId(id);
if (LoadedWireIds.Count < MaxWires) { LoadedWireIds.Add(idRemap.GetOffsetId(id)); }
break;
case "statuseffect":
@@ -180,143 +170,117 @@ namespace Barotrauma.Items.Components
public void SetRecipientsDirty()
{
recipientsDirty = true;
if (IsPower) { Powered.ChangedConnections.Add(this); }
}
private void RefreshRecipients()
{
recipients.Clear();
for (int i = 0; i < MaxWires; i++)
foreach (var wire in wires)
{
if (wires[i] == null) continue;
Connection recipient = wires[i].OtherConnection(this);
if (recipient != null) recipients.Add(recipient);
Connection recipient = wire.OtherConnection(this);
if (recipient != null) { recipients.Add(recipient); }
}
recipientsDirty = false;
}
public int FindEmptyIndex()
{
for (int i = 0; i < MaxWires; i++)
{
if (wires[i] == null) return i;
}
return -1;
}
public int FindWireIndex(Wire wire)
{
for (int i = 0; i < MaxWires; i++)
{
if (wires[i] == wire) return i;
}
return -1;
}
public int FindWireIndex(Item wireItem)
{
for (int i = 0; i < MaxWires; i++)
{
if (wires[i] == null && wireItem == null) return i;
if (wires[i] != null && wires[i].Item == wireItem) return i;
}
return -1;
}
public Wire FindWireByItem(Item it)
=> Wires.FirstOrDefault(w => w.Item == it);
public bool WireSlotsAvailable()
=> wires.Count < MaxWires;
public bool TryAddLink(Wire wire)
{
for (int i = 0; i < MaxWires; i++)
if (wire is null
|| wires.Contains(wire)
|| !WireSlotsAvailable())
{
if (wires[i] == null)
{
SetWire(i, wire);
return true;
}
return false;
}
return false;
wires.Add(wire);
return true;
}
public void SetWire(int index, Wire wire)
public void DisconnectWire(Wire wire)
{
Wire previousWire = wires[index];
if (wire != previousWire && previousWire != null)
{
var otherConnection = previousWire.OtherConnection(this);
if (otherConnection != null)
{
//Change the connection grids or flag them for updating
if (IsPower && otherConnection.IsPower && Grid != null)
{
//Check if both connections belong to a larger grid
if (otherConnection.recipients.Count > 1 && recipients.Count > 1)
{
Powered.ChangedConnections.Add(otherConnection);
Powered.ChangedConnections.Add(this);
}
else if (recipients.Count > 1)
{
//This wire was the only one at the other grid
otherConnection.Grid?.RemoveConnection(otherConnection);
otherConnection.Grid = null;
}
else if (otherConnection.recipients.Count > 1)
{
Grid?.RemoveConnection(this);
Grid = null;
}
else if (Grid.Connections.Count == 2)
{
//Delete the grid as these were the only 2 devices
Powered.Grids.Remove(Grid.ID);
Grid = null;
otherConnection.Grid = null;
}
}
otherConnection.recipientsDirty = true;
}
}
if (wire == null || !wires.Contains(wire)) { return; }
wires[index] = wire;
var prevOtherConnection = wire.OtherConnection(this);
if (prevOtherConnection != null)
{
//Change the connection grids or flag them for updating
if (IsPower && prevOtherConnection.IsPower && Grid != null)
{
//Check if both connections belong to a larger grid
if (prevOtherConnection.recipients.Count > 1 && recipients.Count > 1)
{
Powered.ChangedConnections.Add(prevOtherConnection);
Powered.ChangedConnections.Add(this);
}
else if (recipients.Count > 1)
{
//This wire was the only one at the other grid
prevOtherConnection.Grid?.RemoveConnection(prevOtherConnection);
prevOtherConnection.Grid = null;
}
else if (prevOtherConnection.recipients.Count > 1)
{
Grid?.RemoveConnection(this);
Grid = null;
}
else if (Grid.Connections.Count == 2)
{
//Delete the grid as these were the only 2 devices
Powered.Grids.Remove(Grid.ID);
Grid = null;
prevOtherConnection.Grid = null;
}
}
prevOtherConnection.recipientsDirty = true;
}
wires.Remove(wire);
recipientsDirty = true;
if (wire != null)
}
public void ConnectWire(Wire wire)
{
if (wire == null || !TryAddLink(wire)) { return; }
ConnectionPanel.DisconnectedWires.Remove(wire);
var otherConnection = wire.OtherConnection(this);
if (otherConnection != null)
{
ConnectionPanel.DisconnectedWires.Remove(wire);
var otherConnection = wire.OtherConnection(this);
if (otherConnection != null)
//Set the other connection grid if a grid exists already
if (Powered.ValidPowerConnection(this, otherConnection))
{
//Set the other connection grid if a grid exists already
if (Powered.ValidPowerConnection(this, otherConnection))
if (Grid == null && otherConnection.Grid != null)
{
if (Grid == null && otherConnection.Grid != null)
{
otherConnection.Grid.AddConnection(this);
Grid = otherConnection.Grid;
}
else if (Grid != null && otherConnection.Grid == null)
{
Grid.AddConnection(otherConnection);
otherConnection.Grid = Grid;
}
else
{
//Flag change so that proper grids can be formed
Powered.ChangedConnections.Add(this);
Powered.ChangedConnections.Add(otherConnection);
}
otherConnection.Grid.AddConnection(this);
Grid = otherConnection.Grid;
}
else if (Grid != null && otherConnection.Grid == null)
{
Grid.AddConnection(otherConnection);
otherConnection.Grid = Grid;
}
else
{
//Flag change so that proper grids can be formed
Powered.ChangedConnections.Add(this);
Powered.ChangedConnections.Add(otherConnection);
}
otherConnection.recipientsDirty = true;
}
otherConnection.recipientsDirty = true;
}
recipientsDirty = true;
}
public void SendSignal(Signal signal)
{
for (int i = 0; i < MaxWires; i++)
foreach (var wire in wires)
{
if (wires[i] == null) { continue; }
Connection recipient = wires[i].OtherConnection(this);
Connection recipient = wire.OtherConnection(this);
if (recipient == null) { continue; }
if (recipient.item == this.item || signal.source?.LastSentSignalRecipients.LastOrDefault() == recipient) { continue; }
@@ -354,35 +318,32 @@ namespace Barotrauma.Items.Components
}
}
for (int i = 0; i < MaxWires; i++)
foreach (var wire in wires)
{
if (wires[i] == null) continue;
wires[i].RemoveConnection(this);
wires[i] = null;
wire.RemoveConnection(this);
recipientsDirty = true;
}
wires.Clear();
}
public void ConnectLinked()
public void InitializeFromLoaded()
{
if (wireId == null) return;
if (LoadedWireIds.Count == 0) { return; }
for (int i = 0; i < MaxWires; i++)
for (int i = 0; i < LoadedWireIds.Count; i++)
{
if (wireId[i] == 0) { continue; }
if (!(Entity.FindEntityByID(LoadedWireIds[i]) is Item wireItem)) { continue; }
if (!(Entity.FindEntityByID(wireId[i]) is Item wireItem)) { continue; }
wires[i] = wireItem.GetComponent<Wire>();
recipientsDirty = true;
if (wires[i] != null)
var wire = wireItem.GetComponent<Wire>();
if (wire != null && TryAddLink(wire))
{
if (wires[i].Item.body != null) wires[i].Item.body.Enabled = false;
wires[i].Connect(this, false, false);
wires[i].FixNodeEnds();
if (wire.Item.body != null) wire.Item.body.Enabled = false;
wire.Connect(this, false, false);
wire.FixNodeEnds();
recipientsDirty = true;
}
}
LoadedWireIds.Clear();
}
@@ -390,19 +351,10 @@ namespace Barotrauma.Items.Components
{
XElement newElement = new XElement(IsOutput ? "output" : "input", new XAttribute("name", Name));
Array.Sort(wires, delegate (Wire wire1, Wire wire2)
foreach (var wire in wires.OrderBy(w => w.Item.ID))
{
if (wire1 == null) return 1;
if (wire2 == null) return -1;
return wire1.Item.ID.CompareTo(wire2.Item.ID);
});
for (int i = 0; i < MaxWires; i++)
{
if (wires[i] == null) continue;
newElement.Add(new XElement("link",
new XAttribute("w", wires[i].Item.ID.ToString())));
new XAttribute("w", wire.Item.ID.ToString())));
}
parentElement.Add(newElement);
@@ -49,7 +49,7 @@ namespace Barotrauma.Items.Components
public bool TemporarilyLocked
{
get { return Level.IsLoadedOutpost && item.GetComponent<DockingPort>() != null; }
get { return Level.IsLoadedOutpost && (item.GetComponent<DockingPort>()?.Docked ?? false); }
}
//connection panels can't be deactivated externally (by signals or status effects)
@@ -99,7 +99,7 @@ namespace Barotrauma.Items.Components
{
foreach (Connection c in Connections)
{
c.ConnectLinked();
c.InitializeFromLoaded();
}
if (disconnectedWireIds != null)
@@ -286,25 +286,8 @@ namespace Barotrauma.Items.Components
for (int i = 0; i < loadedConnections.Count && i < Connections.Count; i++)
{
if (loadedConnections[i].wireId.Length == Connections[i].wireId.Length)
{
loadedConnections[i].wireId.CopyTo(Connections[i].wireId, 0);
}
else
{
//backwards compatibility when maximum number of wires has changed
foreach (ushort id in loadedConnections[i].wireId)
{
for (int j = 0; j < Connections[i].wireId.Length; j++)
{
if (Connections[i].wireId[j] == 0)
{
Connections[i].wireId[j] = id;
break;
}
}
}
}
Connections[i].LoadedWireIds.Clear();
Connections[i].LoadedWireIds.AddRange(loadedConnections[i].LoadedWireIds);
}
disconnectedWireIds = element.GetAttributeUshortArray("disconnectedwires", Array.Empty<ushort>()).ToList();
@@ -361,10 +344,8 @@ namespace Barotrauma.Items.Components
DisconnectedWires.Clear();
foreach (Connection c in Connections)
{
foreach (Wire wire in c.Wires)
foreach (Wire wire in c.Wires.ToArray())
{
if (wire == null) { continue; }
if (wire.OtherConnection(c) == null) //wire not connected to anything else
{
#if CLIENT
@@ -408,13 +389,14 @@ namespace Barotrauma.Items.Components
foreach (Connection connection in Connections)
{
msg.WriteVariableUInt32((uint)connection.Wires.Count);
foreach (Wire wire in connection.Wires)
{
msg.Write(wire?.Item == null ? (ushort)0 : wire.Item.ID);
}
}
msg.Write((ushort)DisconnectedWires.Count());
msg.Write((ushort)DisconnectedWires.Count);
foreach (Wire disconnectedWire in DisconnectedWires)
{
msg.Write(disconnectedWire.Item.ID);
@@ -3,6 +3,7 @@ using Barotrauma.Networking;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using System.Globalization;
namespace Barotrauma.Items.Components
{
@@ -34,13 +35,17 @@ namespace Barotrauma.Items.Components
public Identifier PropertyName { get; }
public bool TargetOnlyParentProperty { get; }
public int NumberInputMin { get; }
public int NumberInputMax { get; }
public string NumberInputMin { get; }
public string NumberInputMax { get; }
public string NumberInputStep { get; }
public int NumberInputDecimalPlaces { get; }
public int MaxTextLength { get; }
public const int DefaultNumberInputMin = 0, DefaultNumberInputMax = 99;
public bool IsIntegerInput { get; }
public const string DefaultNumberInputMin = "0", DefaultNumberInputMax = "99", DefaultNumberInputStep = "1";
public const int DefaultNumberInputDecimalPlaces = 0;
public bool IsNumberInput { get; }
public NumberType? NumberType { get; }
public bool HasPropertyName { get; }
public bool ShouldSetProperty { get; set; }
@@ -60,11 +65,34 @@ namespace Barotrauma.Items.Components
ConnectionName = element.GetAttributeString("connection", "");
PropertyName = element.GetAttributeIdentifier("propertyname", "");
TargetOnlyParentProperty = element.GetAttributeBool("targetonlyparentproperty", false);
NumberInputMin = element.GetAttributeInt("min", DefaultNumberInputMin);
NumberInputMax = element.GetAttributeInt("max", DefaultNumberInputMax);
NumberInputMin = element.GetAttributeString("min", DefaultNumberInputMin);
NumberInputMax = element.GetAttributeString("max", DefaultNumberInputMax);
NumberInputStep = element.GetAttributeString("step", DefaultNumberInputStep);
NumberInputDecimalPlaces = element.GetAttributeInt("decimalplaces", DefaultNumberInputDecimalPlaces);
MaxTextLength = element.GetAttributeInt("maxtextlength", int.MaxValue);
HasPropertyName = !PropertyName.IsEmpty;
IsIntegerInput = HasPropertyName && element.Name.ToString().ToLowerInvariant() == "integerinput";
if (HasPropertyName)
{
string elementName = element.Name.ToString().ToLowerInvariant();
IsNumberInput = elementName == "numberinput" || elementName == "integerinput"; // backwards compatibility
if (IsNumberInput)
{
string numberType = element.GetAttributeString("numbertype", string.Empty);
switch (numberType)
{
case "f":
case "float":
NumberType = Barotrauma.NumberType.Float;
break;
case "int":
case "integer":
default: // backwards compatibility
NumberType = Barotrauma.NumberType.Int;
break;
}
}
}
if (element.GetAttribute("signal") is XAttribute attribute)
{
@@ -152,7 +180,8 @@ namespace Barotrauma.Items.Components
{
case "button":
case "textbox":
case "integerinput":
case "integerinput": // backwards compatibility
case "numberinput":
var button = new CustomInterfaceElement(item, subElement, this)
{
ContinuousSignal = false
@@ -317,6 +346,24 @@ namespace Barotrauma.Items.Components
}
}
private void ValueChanged(CustomInterfaceElement numberInputElement, float value)
{
if (numberInputElement == null) { return; }
numberInputElement.Signal = value.ToString();
if (!numberInputElement.TargetOnlyParentProperty)
{
foreach (ISerializableEntity e in item.AllPropertyObjects)
{
if (!e.SerializableProperties.ContainsKey(numberInputElement.PropertyName)) { continue; }
e.SerializableProperties[numberInputElement.PropertyName].TrySetValue(e, value);
}
}
else if (SerializableProperties.ContainsKey(numberInputElement.PropertyName))
{
SerializableProperties[numberInputElement.PropertyName].TrySetValue(this, value);
}
}
public override void Update(float deltaTime, Camera cam)
{
foreach (CustomInterfaceElement ciElement in customInterfaceElementList)
@@ -341,5 +388,10 @@ namespace Barotrauma.Items.Components
signals = customInterfaceElementList.Select(ci => ci.Signal).ToArray();
return base.Save(parentElement);
}
private static bool TryParseFloatInvariantCulture(string s, out float f)
{
return float.TryParse(s, NumberStyles.Any, CultureInfo.InvariantCulture, out f);
}
}
}
@@ -187,7 +187,7 @@ namespace Barotrauma.Items.Components
set;
}
public override void Move(Vector2 amount)
public override void Move(Vector2 amount, bool ignoreContacts = false)
{
#if CLIENT
Light.Position += amount;
@@ -13,12 +13,13 @@ namespace Barotrauma.Items.Components
private float updateTimer;
[Flags]
public enum TargetType
{
Any,
Human,
Monster,
Wall
Human = 1,
Monster = 2,
Wall = 4,
Any = Human | Monster | Wall,
}
[Serialize(false, IsPropertySaveable.No, description: "Has the item currently detected movement. Intended to be used by StatusEffect conditionals (setting this value in XML has no effect).")]
@@ -179,6 +180,11 @@ namespace Barotrauma.Items.Components
if (!string.IsNullOrEmpty(signalOut)) { item.SendSignal(new Signal(signalOut, 1), "state_out"); }
if (MotionDetected)
{
ApplyStatusEffects(ActionType.OnUse, deltaTime);
}
updateTimer -= deltaTime;
if (updateTimer > 0.0f) { return; }
@@ -199,8 +205,7 @@ namespace Barotrauma.Items.Components
float broadRangeX = Math.Max(rangeX * 2, 500);
float broadRangeY = Math.Max(rangeY * 2, 500);
if (item.CurrentHull == null && item.Submarine != null &&
(Target == TargetType.Wall || Target == TargetType.Any))
if (item.CurrentHull == null && item.Submarine != null && Target.HasFlag(TargetType.Wall))
{
if (Level.Loaded != null && (Math.Abs(item.Submarine.Velocity.X) > MinimumVelocity || Math.Abs(item.Submarine.Velocity.Y) > MinimumVelocity))
{
@@ -248,7 +253,7 @@ namespace Barotrauma.Items.Components
}
}
if (Target != TargetType.Wall)
if (Target.HasFlag(TargetType.Human) || Target.HasFlag(TargetType.Monster))
{
foreach (Character c in Character.CharacterList)
{
@@ -258,14 +263,13 @@ namespace Barotrauma.Items.Components
//makes it possible to detect when a spawned character moves without triggering the detector immediately as the ragdoll spawns and drops to the ground
if (c.SpawnTime > Timing.TotalTime - 1.0) { continue; }
switch (Target)
if (c.IsHuman)
{
case TargetType.Human:
if (!c.IsHuman) { continue; }
break;
case TargetType.Monster:
if (c.IsHuman || c.IsPet) { continue; }
break;
if (!Target.HasFlag(TargetType.Human)) { continue; }
}
else if (!c.IsPet)
{
if (!Target.HasFlag(TargetType.Monster)) { continue; }
}
//do a rough check based on the position of the character's collider first
@@ -1,33 +0,0 @@
using System.Xml.Linq;
namespace Barotrauma.Items.Components
{
class OrComponent : AndComponent
{
public OrComponent(Item item, ContentXElement element)
: base(item, element)
{
IsActive = true;
}
public override void Update(float deltaTime, Camera cam)
{
bool state = false;
for (int i = 0; i < timeSinceReceived.Length; i++)
{
if (timeSinceReceived[i] <= timeFrame) { state = true; }
timeSinceReceived[i] += deltaTime;
}
string signalOut = state ? output : falseOutput;
if (string.IsNullOrEmpty(signalOut))
{
//deactivate the component if state is false and there's no false output (will be woken up by non-zero signals in ReceiveSignal)
if (!state) { IsActive = false; }
return;
}
item.SendSignal(new Signal(signalOut, sender: signalSender[0] ?? signalSender[1]), "signal_out");
}
}
}
@@ -64,6 +64,11 @@ namespace Barotrauma.Items.Components
IsActive = true;
}
public static int GetWaterPercentage(Hull hull)
{
return hull.WaterVolume > 1.0f ? MathHelper.Clamp((int)Math.Ceiling(hull.WaterPercentage), 0, 100) : 0;
}
public override void Update(float deltaTime, Camera cam)
{
if (stateSwitchDelay > 0.0f)
@@ -103,12 +108,7 @@ namespace Barotrauma.Items.Components
if (item.CurrentHull != null)
{
int waterPercentage = 0;
//ignore minuscule amounts of water
if (item.CurrentHull.WaterVolume > 1.0f)
{
waterPercentage = MathHelper.Clamp((int)Math.Ceiling(item.CurrentHull.WaterPercentage), 0, 100);
}
int waterPercentage = GetWaterPercentage(item.CurrentHull);
if (prevSentWaterPercentageValue != waterPercentage || waterPercentageSignal == null)
{
prevSentWaterPercentageValue = waterPercentage;
@@ -150,7 +150,7 @@ namespace Barotrauma.Items.Components
// = no point in receiving
if (!LinkToChat)
{
if (signalOutConnection == null || !signalOutConnection.Wires.Any(w => w != null))
if (signalOutConnection == null || signalOutConnection.Wires.Count <= 0)
{
return false;
}
@@ -143,12 +143,11 @@ namespace Barotrauma.Items.Components
{
if (connections[i] == null || connections[i].Item != item) { continue; }
foreach (Wire wire in connections[i].Wires)
if (connections[i].Wires.Contains(this))
{
if (wire != this) continue;
SetConnectedDirty();
connections[i].SetWire(connections[i].FindWireIndex(wire), null);
connections[i].DisconnectWire(this);
}
connections[i] = null;
@@ -597,15 +596,16 @@ namespace Barotrauma.Items.Components
for (int i = 0; i < 2; i++)
{
if (connections[i] == null) { continue; }
int wireIndex = connections[i].FindWireIndex(item);
if (wireIndex == -1) { continue; }
var wire = connections[i].FindWireByItem(item);
if (wire is null) { continue; }
#if SERVER
if (!connections[i].Item.Removed && (!connections[i].Item.Submarine?.Loading ?? true) && (!Level.Loaded?.Generating ?? true))
{
connections[i].Item.CreateServerEvent(connections[i].Item.GetComponent<ConnectionPanel>());
}
#endif
connections[i].SetWire(wireIndex, null);
connections[i].DisconnectWire(wire);
connections[i] = null;
}
@@ -1,34 +0,0 @@
using System.Xml.Linq;
namespace Barotrauma.Items.Components
{
class XorComponent : AndComponent
{
public XorComponent(Item item, ContentXElement element)
: base(item, element)
{
IsActive = true;
}
public override void Update(float deltaTime, Camera cam)
{
int receivedInputs = 0;
for (int i = 0; i < timeSinceReceived.Length; i++)
{
if (timeSinceReceived[i] <= timeFrame) { receivedInputs += 1; }
timeSinceReceived[i] += deltaTime;
}
bool state = receivedInputs == 1;
string signalOut = state ? output : falseOutput;
if (string.IsNullOrEmpty(signalOut))
{
//deactivate the component if state is false and there's no false output (will be woken up by non-zero signals in ReceiveSignal)
if (!state) { IsActive = false; }
return;
}
item.SendSignal(new Signal(signalOut, sender: signalSender[0] ?? signalSender[1]), "signal_out");
}
}
}
@@ -3,9 +3,9 @@ using FarseerPhysics.Dynamics;
using FarseerPhysics.Dynamics.Contacts;
using Microsoft.Xna.Framework;
using System;
using System.Linq;
using System.Collections.Generic;
using System.Xml.Linq;
using System.Globalization;
using System.Linq;
namespace Barotrauma.Items.Components
{
@@ -13,6 +13,46 @@ namespace Barotrauma.Items.Components
{
[Editable, Serialize(0.0f, IsPropertySaveable.Yes, description: "The maximum amount of force applied to the triggering entitites.", alwaysUseInstanceValues: true)]
public float Force { get; set; }
[Editable, Serialize(false, IsPropertySaveable.Yes, description: "Determines if the force gets higher the closer the triggerer is to the center of the trigger.", alwaysUseInstanceValues: true)]
public bool DistanceBasedForce { get; set; }
[Editable, Serialize(false, IsPropertySaveable.Yes, description: "Determines if the force fluctuates over time or if it stays constant.", alwaysUseInstanceValues: true)]
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
{
get
{
return forceFluctuationStrength;
}
set
{
forceFluctuationStrength = Math.Clamp(value, 0.0f, 1.0f);
}
}
[Serialize(1.0f, IsPropertySaveable.Yes, description: "How fast (cycles per second) the force fluctuates.", alwaysUseInstanceValues: true)]
private float ForceFluctuationFrequency
{
get
{
return forceFluctuationFrequency;
}
set
{
forceFluctuationFrequency = Math.Max(value, 0.01f);
}
}
[Serialize(0.01f, IsPropertySaveable.Yes, description: "How often (in seconds) the force fluctuation is calculated.", alwaysUseInstanceValues: true)]
private float ForceFluctuationInterval
{
get
{
return forceFluctuationInterval;
}
set
{
forceFluctuationInterval = Math.Max(value, 0.01f);
}
}
public PhysicsBody PhysicsBody { get; private set; }
private float Radius { get; set; }
@@ -39,11 +79,6 @@ namespace Barotrauma.Items.Components
private readonly LevelTrigger.TriggererType triggeredBy;
private readonly HashSet<Entity> triggerers = new HashSet<Entity>();
private readonly bool triggerOnce;
private readonly bool distanceBasedForce;
private readonly bool forceFluctuation;
private readonly float forceFluctuationStrength;
private readonly float forceFluctuationFrequency;
private readonly float forceFluctuationInterval;
private readonly List<ISerializableEntity> statusEffectTargets = new List<ISerializableEntity>();
/// <summary>
/// Effects applied to entities inside the trigger
@@ -54,6 +89,10 @@ namespace Barotrauma.Items.Components
/// </summary>
private readonly List<Attack> attacks = new List<Attack>();
private float forceFluctuationStrength;
private float forceFluctuationFrequency;
private float forceFluctuationInterval;
public TriggerComponent(Item item, ContentXElement element) : base(item, element)
{
string triggeredByAttribute = element.GetAttributeString("triggeredby", "Character");
@@ -62,15 +101,6 @@ namespace Barotrauma.Items.Components
DebugConsole.ThrowError($"Error in ForceComponent config: \"{triggeredByAttribute}\" is not a valid triggerer type.");
}
triggerOnce = element.GetAttributeBool("triggeronce", false);
distanceBasedForce = element.GetAttributeBool("distancebasedforce", false);
forceFluctuation = element.GetAttributeBool("forcefluctuation", false);
forceFluctuationStrength = element.GetAttributeFloat("forcefluctuationstrength", 1.0f);
forceFluctuationStrength = Math.Clamp(forceFluctuationStrength, 0.0f, 1.0f);
forceFluctuationFrequency = element.GetAttributeFloat("fluctuationfrequency", 1.0f);
forceFluctuationFrequency = Math.Max(forceFluctuationFrequency, 0.01f);
forceFluctuationInterval = element.GetAttributeFloat("fluctuationinterval", 0.01f);
forceFluctuationInterval = Math.Max(forceFluctuationInterval, 0.01f);
string parentDebugName = $"TriggerComponent in {item.Name}";
foreach (var subElement in element.Elements())
{
@@ -93,13 +123,11 @@ namespace Barotrauma.Items.Components
base.OnItemLoaded();
float radiusAttribute = originalElement.GetAttributeFloat("radius", 10.0f);
Radius = ConvertUnits.ToSimUnits(radiusAttribute * item.Scale);
PhysicsBody = new PhysicsBody(0.0f, 0.0f, Radius, 1.5f)
PhysicsBody = new PhysicsBody(0.0f, 0.0f, Radius, 1.5f, BodyType.Static, Physics.CollisionWall, LevelTrigger.GetCollisionCategories(triggeredBy))
{
BodyType = BodyType.Static,
CollidesWith = LevelTrigger.GetCollisionCategories(triggeredBy),
CollisionCategories = Physics.CollisionWall,
UserData = item
};
PhysicsBody.SetTransformIgnoreContacts(item.SimPosition, 0.0f);
PhysicsBody.FarseerBody.SetIsSensor(true);
PhysicsBody.FarseerBody.OnCollision += OnCollision;
PhysicsBody.FarseerBody.OnSeparation += OnSeparation;
@@ -156,14 +184,14 @@ namespace Barotrauma.Items.Components
TriggerActive = triggerers.Any();
if (forceFluctuation && TriggerActive && (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer))
if (ForceFluctuation && TriggerActive && (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer))
{
ForceFluctuationTimer += deltaTime;
if (ForceFluctuationTimer >= forceFluctuationInterval)
if (ForceFluctuationTimer >= ForceFluctuationInterval)
{
float v = MathF.Sin(2 * MathF.PI * forceFluctuationFrequency * TimeInLevel);
float v = MathF.Sin(2 * MathF.PI * ForceFluctuationFrequency * TimeInLevel);
float amount = MathUtils.InverseLerp(-1.0f, 1.0f, v);
CurrentForceFluctuation = MathHelper.Lerp(1.0f - forceFluctuationStrength, 1.0f, amount);
CurrentForceFluctuation = MathHelper.Lerp(1.0f - ForceFluctuationStrength, 1.0f, amount);
ForceFluctuationTimer = 0.0f;
GameMain.NetworkMember?.CreateEntityEvent(this);
}
@@ -182,7 +210,7 @@ namespace Barotrauma.Items.Components
LevelTrigger.ApplyAttacks(attacks, item.WorldPosition, deltaTime);
}
if (Force < 0.01f)
if (Math.Abs(Force) < 0.01f)
{
// Just ignore very minimal forces
continue;
@@ -208,21 +236,65 @@ namespace Barotrauma.Items.Components
{
Vector2 diff = ConvertUnits.ToDisplayUnits(PhysicsBody.SimPosition - body.SimPosition);
if (diff.LengthSquared() < 0.0001f) { return; }
float distanceFactor = distanceBasedForce ? LevelTrigger.GetDistanceFactor(body, PhysicsBody, RadiusInDisplayUnits) : 1.0f;
float distanceFactor = DistanceBasedForce ? LevelTrigger.GetDistanceFactor(body, PhysicsBody, RadiusInDisplayUnits) : 1.0f;
if (distanceFactor <= 0.0f) { return; }
Vector2 force = distanceFactor * (CurrentForceFluctuation * Force) * Vector2.Normalize(diff);
if (force.LengthSquared() < 0.01f) { return; }
body.ApplyForce(force);
}
public override void Move(Vector2 amount)
public override void Move(Vector2 amount, bool ignoreContacts = false)
{
base.Move(amount);
if (PhysicsBody != null)
{
PhysicsBody.SetTransform(PhysicsBody.SimPosition + ConvertUnits.ToSimUnits(amount), 0.0f);
if (ignoreContacts)
{
PhysicsBody.SetTransformIgnoreContacts(PhysicsBody.SimPosition + ConvertUnits.ToSimUnits(amount), 0.0f);
}
else
{
PhysicsBody.SetTransform(PhysicsBody.SimPosition + ConvertUnits.ToSimUnits(amount), 0.0f);
}
PhysicsBody.Submarine = item.Submarine;
}
}
public override void ReceiveSignal(Signal signal, Connection connection)
{
base.ReceiveSignal(signal, connection);
switch (connection.Name)
{
case "set_force":
if (!FloatTryParse(signal, out float force)) { break; }
Force = force;
break;
case "set_distancebasedforce":
if (!bool.TryParse(signal.value, out bool distanceBasedForce)) { break; }
DistanceBasedForce = distanceBasedForce;
break;
case "set_forcefluctuation":
if (!bool.TryParse(signal.value, out bool forceFluctuation)) { break; }
ForceFluctuation = forceFluctuation;
break;
case "set_forcefluctuationstrength":
if (!FloatTryParse(signal, out float forceFluctuationStrength)) { break; }
ForceFluctuationStrength = forceFluctuationStrength;
break;
case "set_forcefluctuationfrequency":
if (!FloatTryParse(signal, out float forceFluctuationFrequency)) { break; }
ForceFluctuationFrequency = forceFluctuationFrequency;
break;
case "set_forcefluctuationinterval":
if (!FloatTryParse(signal, out float forceFluctuationInterval)) { break; }
ForceFluctuationInterval = forceFluctuationInterval;
break;
}
static bool FloatTryParse(Signal signal, out float value)
{
return float.TryParse(signal.value, NumberStyles.Any, CultureInfo.InvariantCulture, out value);
}
}
}
}
}
@@ -4,9 +4,7 @@ using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Globalization;
using Barotrauma.IO;
using System.Linq;
using System.Xml.Linq;
using Barotrauma.Extensions;
using FarseerPhysics.Dynamics;
@@ -20,8 +18,6 @@ namespace Barotrauma.Items.Components
private Vector2 barrelPos;
private Vector2 transformedBarrelPos;
private LightComponent lightComponent;
private float rotation, targetRotation;
@@ -71,6 +67,8 @@ namespace Barotrauma.Items.Components
public Character ActiveUser;
private float resetActiveUserTimer;
private List<LightComponent> lightComponents;
public float Rotation
{
get { return rotation; }
@@ -168,10 +166,13 @@ namespace Barotrauma.Items.Components
rotation = (minRotation + maxRotation) / 2;
#if CLIENT
if (lightComponent != null)
if (lightComponents != null)
{
lightComponent.Rotation = rotation;
lightComponent.Light.Rotation = -rotation;
foreach (var light in lightComponents)
{
light.Rotation = rotation;
light.Light.Rotation = -rotation;
}
}
#endif
}
@@ -331,27 +332,39 @@ namespace Barotrauma.Items.Components
if (loadedRotationLimits.HasValue) { RotationLimits = loadedRotationLimits.Value; }
if (loadedBaseRotation.HasValue) { BaseRotation = loadedBaseRotation.Value; }
targetRotation = rotation;
FindLightComponent();
UpdateTransformedBarrelPos();
}
private void FindLightComponent()
private void FindLightComponents()
{
if (lightComponents != null)
{
// Can't run again, because of reparenting.
return;
}
foreach (LightComponent lc in item.GetComponents<LightComponent>())
{
// Only make the Turret control the LightComponents that are it's children. So it'd be possible to for example have some extra lights on the turret that don't rotate with it.
if (lc?.Parent == this)
{
lightComponent = lc;
break;
if (lightComponents == null)
{
lightComponents = new List<LightComponent>();
}
lightComponents.Add(lc);
}
}
#if CLIENT
if (lightComponent != null)
if (lightComponents != null)
{
lightComponent.Parent = null;
lightComponent.Rotation = Rotation - MathHelper.ToRadians(item.Rotation);
lightComponent.Light.Rotation = -rotation;
foreach (var light in lightComponents)
{
// We want the turret to control the state of the LightComponent, not tie it's state to the state of the Turret (the light can be inactive even if the turret is active)
light.Parent = null;
light.Rotation = Rotation - item.RotationRad;
light.Light.Rotation = -rotation;
}
}
#endif
}
@@ -428,7 +441,7 @@ namespace Barotrauma.Items.Components
if (MathUtils.NearlyEqual(minRotation, maxRotation))
{
UpdateLightComponent();
UpdateLightComponents();
return;
}
@@ -452,7 +465,7 @@ namespace Barotrauma.Items.Components
}
// Do not increase the weapons skill when operating a turret in an outpost level
if (user?.Info != null && (GameMain.GameSession?.Campaign == null || !Level.IsLoadedOutpost))
if (user?.Info != null && (GameMain.GameSession?.Campaign == null || !Level.IsLoadedFriendlyOutpost))
{
user.Info.IncreaseSkillLevel("weapons".ToIdentifier(),
SkillSettings.Current.SkillIncreasePerSecondWhenOperatingTurret * deltaTime / Math.Max(user.GetSkillLevel("weapons"), 1.0f));
@@ -509,14 +522,17 @@ namespace Barotrauma.Items.Components
aiFindTargetTimer -= deltaTime;
}
UpdateLightComponent();
UpdateLightComponents();
}
private void UpdateLightComponent()
private void UpdateLightComponents()
{
if (lightComponent != null)
if (lightComponents != null)
{
lightComponent.Rotation = Rotation - MathHelper.ToRadians(item.Rotation);
foreach (var light in lightComponents)
{
light.Rotation = Rotation - item.RotationRad;
}
}
}
@@ -661,6 +677,7 @@ namespace Barotrauma.Items.Components
while (neededPower > 0.0001f && batteries.Count > 0)
{
batteries.RemoveAll(b => b.Charge <= 0.0001f || b.MaxOutPut <= 0.0001f);
if (!batteries.Any()) { break; }
float takePower = neededPower / batteries.Count;
takePower = Math.Min(takePower, batteries.Min(b => Math.Min(b.Charge * 3600.0f, b.MaxOutPut)));
foreach (PowerContainer battery in batteries)
@@ -1151,8 +1168,12 @@ namespace Barotrauma.Items.Components
foreach (Character enemy in Character.CharacterList)
{
// Ignore dead, friendly, and those that are inside the same sub
if (enemy.IsDead || !enemy.Enabled || enemy.Submarine == character.Submarine) { continue; }
if (enemy.Submarine != null && enemy.Submarine.TeamID == character.Submarine.TeamID) { continue; }
if (enemy.IsDead || !enemy.Enabled) { continue; }
if (character.Submarine != null)
{
if (enemy.Submarine == character.Submarine) { continue; }
if (enemy.Submarine != null && enemy.Submarine.TeamID == character.Submarine.TeamID) { continue; }
}
// Don't aim monsters that are inside any submarine.
if (!enemy.IsHuman && enemy.CurrentHull != null) { continue; }
if (HumanAIController.IsFriendly(character, enemy)) { continue; }
@@ -1596,21 +1617,24 @@ namespace Barotrauma.Items.Components
}
break;
case "toggle_light":
if (lightComponent != null && signal.value != "0")
if (lightComponents != null && signal.value != "0")
{
lightComponent.IsOn = !lightComponent.IsOn;
UpdateLightComponent();
foreach (var light in lightComponents)
{
light.IsOn = !light.IsOn;
}
UpdateLightComponents();
}
break;
case "set_light":
if (lightComponent != null)
if (lightComponents != null)
{
bool shouldBeOn = signal.value != "0";
if (shouldBeOn != lightComponent.IsOn)
foreach (var light in lightComponents)
{
lightComponent.IsOn = shouldBeOn;
UpdateLightComponent();
light.IsOn = shouldBeOn;
}
UpdateLightComponents();
}
break;
}
@@ -1628,7 +1652,7 @@ namespace Barotrauma.Items.Components
public override void OnItemLoaded()
{
base.OnItemLoaded();
FindLightComponent();
FindLightComponents();
targetRotation = rotation;
if (!loadedBaseRotation.HasValue)
{
@@ -6,7 +6,6 @@ using System.Linq;
using System.Xml.Linq;
using Barotrauma.Items.Components;
using Barotrauma.Networking;
using System.Collections.Immutable;
using Barotrauma.Abilities;
namespace Barotrauma
@@ -18,7 +17,6 @@ namespace Barotrauma
Beard,
Moustache,
FaceAttachment,
JobIndicator,
Husk,
Herpes
}
@@ -82,7 +80,20 @@ namespace Barotrauma
public string Sound { get; private set; }
public Point? SheetIndex { get; private set; }
public LightComponent LightComponent { get; set; }
public LightComponent LightComponent => LightComponents?.FirstOrDefault();
public List<LightComponent> LightComponents
{
get
{
if (_lightComponents == null)
{
_lightComponents = new List<LightComponent>();
}
return _lightComponents;
}
}
private List<LightComponent> _lightComponents;
public int Variant { get; set; }
@@ -116,7 +127,6 @@ namespace Barotrauma
case WearableType.Beard:
case WearableType.Moustache:
case WearableType.FaceAttachment:
case WearableType.JobIndicator:
case WearableType.Husk:
case WearableType.Herpes:
Limb = LimbType.Head;
@@ -338,11 +348,14 @@ namespace Barotrauma.Items.Components
foreach (var lightElement in subElement.Elements())
{
if (!lightElement.Name.ToString().Equals("lightcomponent", StringComparison.OrdinalIgnoreCase)) { continue; }
wearableSprites[i].LightComponent = new LightComponent(item, lightElement)
wearableSprites[i].LightComponents.Add(new LightComponent(item, lightElement)
{
Parent = this
};
item.AddComponent(wearableSprites[i].LightComponent);
});
foreach (var light in wearableSprites[i].LightComponents)
{
item.AddComponent(light);
}
}
i++;
@@ -413,7 +426,10 @@ namespace Barotrauma.Items.Components
IsActive = true;
if (wearableSprite.LightComponent != null)
{
wearableSprite.LightComponent.ParentBody = equipLimb.body;
foreach (var light in wearableSprite.LightComponents)
{
light.ParentBody = equipLimb.body;
}
}
limb[i] = equipLimb;
@@ -467,7 +483,10 @@ namespace Barotrauma.Items.Components
if (wearableSprites[i].LightComponent != null)
{
wearableSprites[i].LightComponent.ParentBody = null;
foreach (var light in wearableSprites[i].LightComponents)
{
light.ParentBody = null;
}
}
equipLimb.WearingItems.RemoveAll(w => w != null && w == wearableSprites[i]);
@@ -494,7 +513,6 @@ namespace Barotrauma.Items.Components
}
item.SetTransform(picker.SimPosition, 0.0f);
item.SetContainedItemPositions();
item.ApplyStatusEffects(ActionType.OnWearing, deltaTime, picker);
@@ -25,13 +25,32 @@ namespace Barotrauma
partial class Item : MapEntity, IDamageable, IIgnorable, ISerializableEntity, IServerPositionSync, IClientSerializable
{
public static List<Item> ItemList = new List<Item>();
private static readonly HashSet<Item> dangerousItems = new HashSet<Item>();
public static IReadOnlyCollection<Item> DangerousItems { get { return dangerousItems; } }
private static readonly List<Item> repairableItems = new List<Item>();
/// <summary>
/// Items that have one more more Repairable component
/// </summary>
public static IReadOnlyCollection<Item> RepairableItems => repairableItems;
private static readonly List<Item> cleanableItems = new List<Item>();
/// <summary>
/// Items that may potentially need to be cleaned up (pickable, not attached to a wall, and not inside a valid container)
/// </summary>
public static IReadOnlyCollection<Item> CleanableItems => cleanableItems;
public new ItemPrefab Prefab => base.Prefab as ItemPrefab;
public static bool ShowLinks = true;
private readonly HashSet<Identifier> tags;
private bool isWire, isLogic;
private readonly bool isWire, isLogic;
private Hull currentHull;
public Hull CurrentHull
@@ -116,7 +135,7 @@ namespace Barotrauma
private readonly Quality qualityComponent;
private readonly ConcurrentQueue<float> impactQueue = new ConcurrentQueue<float>();
private ConcurrentQueue<float> impactQueue;
//a dictionary containing lists of the status effects in all the components of the item
private readonly bool[] hasStatusEffectsOfType;
@@ -182,6 +201,7 @@ namespace Barotrauma
if (value != container)
{
container = value;
CheckCleanable();
SetActiveSprite();
}
}
@@ -263,24 +283,27 @@ namespace Barotrauma
}
}
private float rotationRad;
public float RotationRad { get; private set; }
[ConditionallyEditable(ConditionallyEditable.ConditionType.AllowRotating, MinValueFloat = 0.0f, MaxValueFloat = 360.0f, DecimalCount = 1, ValueStep = 1f), Serialize(0.0f, IsPropertySaveable.Yes)]
public float Rotation
{
get
{
return MathHelper.ToDegrees(rotationRad);
return MathHelper.ToDegrees(RotationRad);
}
set
{
if (!Prefab.AllowRotatingInEditor) { return; }
rotationRad = MathHelper.ToRadians(value);
RotationRad = MathHelper.ToRadians(value);
#if CLIENT
if (Screen.Selected == GameMain.SubEditorScreen)
{
SetContainedItemPositions();
GetComponent<LightComponent>()?.SetLightSourceTransform();
foreach (var light in GetComponents<LightComponent>())
{
light.SetLightSourceTransform();
}
}
#endif
}
@@ -473,9 +496,9 @@ namespace Barotrauma
get { return spriteColor; }
}
public bool IsFullCondition => MathUtils.NearlyEqual(Condition, MaxCondition);
public float MaxCondition => Prefab.Health * healthMultiplier * maxRepairConditionMultiplier * (1.0f + GetQualityModifier(Items.Components.Quality.StatType.Condition));
public float ConditionPercentage => MathUtils.Percentage(Condition, MaxCondition);
public bool IsFullCondition { get; private set; }
public float MaxCondition { get; private set; }
public float ConditionPercentage { get; private set; }
private float offsetOnSelectedMultiplier = 1.0f;
@@ -496,7 +519,8 @@ namespace Barotrauma
{
float prevConditionPercentage = ConditionPercentage;
healthMultiplier = MathHelper.Clamp(value, 0.0f, float.PositiveInfinity);
Condition = MaxCondition * prevConditionPercentage / 100.0f;
condition = MaxCondition * prevConditionPercentage / 100.0f;
RecalculateConditionValues();
}
}
@@ -506,7 +530,11 @@ namespace Barotrauma
public float MaxRepairConditionMultiplier
{
get => maxRepairConditionMultiplier;
set { maxRepairConditionMultiplier = MathHelper.Clamp(value, 0.0f, float.PositiveInfinity); }
set
{
maxRepairConditionMultiplier = MathHelper.Clamp(value, 0.0f, float.PositiveInfinity);
RecalculateConditionValues();
}
}
//the default value should be Prefab.Health, but because we can't use it in the attribute,
@@ -807,7 +835,9 @@ namespace Barotrauma
defaultRect = newRect;
rect = newRect;
condition = MaxCondition;
condition = MaxCondition = Prefab.Health;
ConditionPercentage = 100.0f;
lastSentCondition = condition;
AllowDeconstruct = itemPrefab.AllowDeconstruct;
@@ -836,33 +866,35 @@ namespace Barotrauma
var rand = new Random(ID);
density = MathHelper.Lerp(minDensity, maxDensity, (float)rand.NextDouble());
}
body = new PhysicsBody(subElement, ConvertUnits.ToSimUnits(Position), Scale, density);
string collisionCategory = subElement.GetAttributeString("collisioncategory", null);
string collisionCategoryStr = subElement.GetAttributeString("collisioncategory", null);
Category collisionCategory = Physics.CollisionItem;
Category collidesWith = Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionPlatform;
if ((Prefab.DamagedByProjectiles || Prefab.DamagedByMeleeWeapons) && Condition > 0)
{
//force collision category to Character to allow projectiles and weapons to hit
//(we could also do this by making the projectiles and weapons hit CollisionItem
//and check if the collision should be ignored in the OnCollision callback, but
//that'd make the hit detection more expensive because every item would be included)
body.CollisionCategories = Physics.CollisionCharacter;
body.CollidesWith = Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionPlatform | Physics.CollisionProjectile;
collisionCategory = Physics.CollisionCharacter;
}
if (collisionCategory != null)
if (collisionCategoryStr != null)
{
if (!Physics.TryParseCollisionCategory(collisionCategory, out Category cat))
if (!Physics.TryParseCollisionCategory(collisionCategoryStr, out Category cat))
{
DebugConsole.ThrowError("Invalid collision category in item \"" + Name+"\" (" + collisionCategory + ")");
DebugConsole.ThrowError("Invalid collision category in item \"" + Name+"\" (" + collisionCategoryStr + ")");
}
else
{
body.CollisionCategories = cat;
collisionCategory = cat;
if (cat.HasFlag(Physics.CollisionCharacter))
{
body.CollidesWith = Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionPlatform | Physics.CollisionProjectile;
collisionCategory |= Physics.CollisionProjectile;
}
}
}
body = new PhysicsBody(subElement, ConvertUnits.ToSimUnits(Position), Scale, density, collisionCategory, collidesWith, findNewContacts: false);
body.FarseerBody.AngularDamping = subElement.GetAttributeFloat("angulardamping", 0.2f);
body.FarseerBody.LinearDamping = subElement.GetAttributeFloat("lineardamping", 0.1f);
body.UserData = this;
@@ -993,6 +1025,9 @@ namespace Barotrauma
InsertToList();
ItemList.Add(this);
if (Prefab.IsDangerous) { dangerousItems.Add(this); }
if (Repairables.Any()) { repairableItems.Add(this); }
CheckCleanable();
DebugConsole.Log("Created " + Name + " (" + ID + ")");
@@ -1003,6 +1038,10 @@ namespace Barotrauma
ApplyStatusEffects(ActionType.OnSpawn, 1.0f);
Components.ForEach(c => c.ApplyStatusEffects(ActionType.OnSpawn, 1.0f));
RecalculateConditionValues();
#if CLIENT
Submarine.ForceVisibilityRecheck();
#endif
}
partial void InitProjSpecific();
@@ -1085,17 +1124,16 @@ namespace Barotrauma
component.OnActiveStateChanged += (bool isActive) =>
{
bool hasSounds = false;
bool needsSoundUpdate = false;
#if CLIENT
hasSounds = component.HasSounds;
needsSoundUpdate = component.NeedsSoundUpdate();
#endif
//component doesn't need to be updated if it isn't active, doesn't have a parent that could activate it,
//nor status effects, sounds or conditionals that would need to run
//nor sounds or conditionals that would need to run
if (!isActive && !component.UpdateWhenInactive &&
!hasSounds &&
!needsSoundUpdate &&
component.Parent == null &&
(component.IsActiveConditionals == null || !component.IsActiveConditionals.Any()) &&
(component.statusEffectLists == null || !component.statusEffectLists.Any()))
(component.IsActiveConditionals == null || !component.IsActiveConditionals.Any()))
{
if (updateableComponents.Contains(component)) { updateableComponents.Remove(component); }
}
@@ -1132,6 +1170,7 @@ namespace Barotrauma
drawableComponents.Add(drawable);
hasComponentsToDraw = true;
#if CLIENT
Submarine.ForceVisibilityRecheck();
cachedVisibleExtents = null;
#endif
}
@@ -1185,7 +1224,6 @@ namespace Barotrauma
public void RemoveContained(Item contained)
{
ownInventory?.RemoveItem(contained);
contained.Container = null;
}
@@ -1264,12 +1302,28 @@ namespace Barotrauma
partial void SetActiveSpriteProjSpecific();
public override void Move(Vector2 amount)
/// <summary>
/// Recheck if the item needs to be included in the list of cleanable items
/// </summary>
public void CheckCleanable()
{
Move(amount, ignoreContacts: false);
var pickable = GetComponent<Pickable>();
if (pickable != null && !pickable.IsAttached &&
Prefab.PreferredContainers.Any() &&
(container == null || container.HasTag("allowcleanup")))
{
if (!cleanableItems.Contains(this))
{
cleanableItems.Add(this);
}
}
else
{
cleanableItems.Remove(this);
}
}
public void Move(Vector2 amount, bool ignoreContacts)
public override void Move(Vector2 amount, bool ignoreContacts = false)
{
if (!MathUtils.IsValid(amount))
{
@@ -1292,7 +1346,7 @@ namespace Barotrauma
}
foreach (ItemComponent ic in components)
{
ic.Move(amount);
ic.Move(amount, ignoreContacts);
}
if (body != null && (Submarine == null || !Submarine.Loading)) { FindHull(); }
@@ -1498,6 +1552,11 @@ namespace Barotrauma
public void ApplyStatusEffect(StatusEffect effect, ActionType type, float deltaTime, Character character = null, Limb limb = null, Entity useTarget = null, bool isNetworkEvent = false, bool checkCondition = true, Vector2? worldPosition = null)
{
if (effect.intervalTimer > 0.0f)
{
effect.intervalTimer -= deltaTime;
return;
}
if (!isNetworkEvent && checkCondition)
{
if (condition == 0.0f && !effect.AllowWhenBroken && effect.type != ActionType.OnBroken) { return; }
@@ -1617,6 +1676,10 @@ namespace Barotrauma
bool wasInFullCondition = IsFullCondition;
condition = MathHelper.Clamp(value, 0.0f, MaxCondition);
if (MathUtils.NearlyEqual(prev, condition, epsilon: 0.000001f)) { return; }
RecalculateConditionValues();
if (condition == 0.0f && prev > 0.0f)
{
//Flag connections to be updated as device is broken
@@ -1625,6 +1688,7 @@ namespace Barotrauma
foreach (ItemComponent ic in components)
{
ic.PlaySound(ActionType.OnBroken);
ic.StopSounds(ActionType.OnActive);
}
if (Screen.Selected == GameMain.SubEditorScreen) { return; }
#endif
@@ -1678,6 +1742,17 @@ namespace Barotrauma
}
}
/// <summary>
/// Recalculates the item's maximum condition, condition percentage and whether it's in full condition.
/// You generally never need to call this manually - done automatically when any of the factors that affect the values change.
/// </summary>
public void RecalculateConditionValues()
{
MaxCondition = Prefab.Health * healthMultiplier * maxRepairConditionMultiplier * (1.0f + GetQualityModifier(Items.Components.Quality.StatType.Condition));
IsFullCondition = MathUtils.NearlyEqual(Condition, MaxCondition);
ConditionPercentage = MathUtils.Percentage(Condition, MaxCondition);
}
private bool IsInWater()
{
if (CurrentHull == null) { return true; }
@@ -1706,12 +1781,8 @@ namespace Barotrauma
public override void Update(float deltaTime, Camera cam)
{
while (impactQueue.TryDequeue(out float impact))
{
HandleCollision(impact);
}
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer && (!Submarine?.Loading ?? true))
#if SERVER
if (!(Submarine is { Loading: true }))
{
sendConditionUpdateTimer -= deltaTime;
if (conditionUpdatePending && sendConditionUpdateTimer <= 0.0f)
@@ -1719,14 +1790,23 @@ namespace Barotrauma
SendPendingNetworkUpdates();
}
}
#endif
if (aiTarget != null)
if (!isActive) { return; }
if (impactQueue != null)
{
while (impactQueue.TryDequeue(out float impact))
{
HandleCollision(impact);
}
}
if (aiTarget != null && aiTarget.NeedsUpdate)
{
aiTarget.Update(deltaTime);
}
if (!isActive) { return; }
ApplyStatusEffects(ActionType.Always, deltaTime, character: (parentInventory as CharacterInventory)?.Owner as Character);
ApplyStatusEffects(parentInventory == null ? ActionType.OnNotContained : ActionType.OnContained, deltaTime, character: (parentInventory as CharacterInventory)?.Owner as Character);
@@ -1827,7 +1907,10 @@ namespace Barotrauma
}
else
{
if (updateableComponents.Count == 0 && !hasStatusEffectsOfType[(int)ActionType.Always] && (body == null || !body.Enabled))
if (updateableComponents.Count == 0 &&
(aiTarget == null || !aiTarget.NeedsUpdate) &&
!hasStatusEffectsOfType[(int)ActionType.Always] &&
(body == null || !body.Enabled))
{
#if CLIENT
positionBuffer.Clear();
@@ -1962,7 +2045,9 @@ namespace Barotrauma
if (contact.FixtureA.Body == f1.Body) { normal = -normal; }
float impact = Vector2.Dot(f1.Body.LinearVelocity, -normal);
impactQueue ??= new ConcurrentQueue<float>();
impactQueue.Enqueue(impact);
isActive = true;
return true;
}
@@ -2001,7 +2086,7 @@ namespace Barotrauma
if (Prefab.AllowRotatingInEditor)
{
rotationRad = MathUtils.WrapAngleTwoPi(-rotationRad);
RotationRad = MathUtils.WrapAngleTwoPi(-RotationRad);
}
#if CLIENT
if (Prefab.CanSpriteFlipX)
@@ -2486,11 +2571,9 @@ namespace Barotrauma
if (ic.Use(deltaTime, character))
{
ic.WasUsed = true;
#if CLIENT
ic.PlaySound(ActionType.OnUse, character);
ic.PlaySound(ActionType.OnUse, character);
#endif
ic.ApplyStatusEffects(ActionType.OnUse, deltaTime, character, targetLimb);
if (ic.DeleteOnUse) { remove = true; }
@@ -2524,11 +2607,9 @@ namespace Barotrauma
if (ic.SecondaryUse(deltaTime, character))
{
ic.WasSecondaryUsed = true;
#if CLIENT
ic.PlaySound(ActionType.OnSecondaryUse, character);
#endif
ic.ApplyStatusEffects(ActionType.OnSecondaryUse, deltaTime, character);
if (ic.DeleteOnUse) { remove = true; }
@@ -2675,6 +2756,9 @@ namespace Barotrauma
}
SetContainedItemPositions();
#if CLIENT
Submarine.ForceVisibilityRecheck();
#endif
}
public void Equip(Character character)
@@ -2693,21 +2777,21 @@ namespace Barotrauma
foreach (ItemComponent ic in components) { ic.Unequip(character); }
}
public List<Pair<object, SerializableProperty>> GetProperties<T>()
public List<(object obj, SerializableProperty property)> GetProperties<T>()
{
List<Pair<object, SerializableProperty>> allProperties = new List<Pair<object, SerializableProperty>>();
List<(object obj, SerializableProperty property)> allProperties = new List<(object obj, SerializableProperty property)>();
List<SerializableProperty> itemProperties = SerializableProperty.GetProperties<T>(this);
foreach (var itemProperty in itemProperties)
{
allProperties.Add(new Pair<object, SerializableProperty>(this, itemProperty));
allProperties.Add((this, itemProperty));
}
foreach (ItemComponent ic in components)
{
List<SerializableProperty> componentProperties = SerializableProperty.GetProperties<T>(ic);
foreach (var componentProperty in componentProperties)
{
allProperties.Add(new Pair<object, SerializableProperty>(ic, componentProperty));
allProperties.Add((ic, componentProperty));
}
}
return allProperties;
@@ -2721,13 +2805,13 @@ namespace Barotrauma
SerializableProperty property = extraData.SerializableProperty;
if (property != null)
{
var propertyOwner = allProperties.Find(p => p.Second == property);
var propertyOwner = allProperties.Find(p => p.property == property);
if (allProperties.Count > 1)
{
msg.Write((byte)allProperties.FindIndex(p => p.Second == property));
msg.Write((byte)allProperties.FindIndex(p => p.property == property));
}
object value = property.GetValue(propertyOwner.First);
object value = property.GetValue(propertyOwner.obj);
if (value is string stringVal)
{
msg.Write(stringVal);
@@ -2808,7 +2892,7 @@ namespace Barotrauma
}
}
private List<Pair<object, SerializableProperty>> GetInGameEditableProperties(bool ignoreConditions = false)
private List<(object obj, SerializableProperty property)> GetInGameEditableProperties(bool ignoreConditions = false)
{
if (ignoreConditions)
{
@@ -2817,7 +2901,7 @@ namespace Barotrauma
else
{
return GetProperties<ConditionallyEditable>()
.Where(ce => ce.Second.GetAttribute<ConditionallyEditable>().IsEditable(this))
.Where(ce => ce.property.GetAttribute<ConditionallyEditable>().IsEditable(this))
.Union(GetProperties<InGameEditable>()).ToList();
}
}
@@ -2836,8 +2920,8 @@ namespace Barotrauma
}
bool allowEditing = true;
object parentObject = allProperties[propertyIndex].First;
SerializableProperty property = allProperties[propertyIndex].Second;
object parentObject = allProperties[propertyIndex].obj;
SerializableProperty property = allProperties[propertyIndex].property;
if (inGameEditableOnly && parentObject is ItemComponent ic)
{
if (!ic.AllowInGameEditing) { allowEditing = false; }
@@ -3170,12 +3254,12 @@ namespace Barotrauma
{
Vector2 oldRelativeOrigin = (oldPrefab.SwappableItem.SwapOrigin - oldPrefab.Size / 2) * element.GetAttributeFloat(item.scale, "scale", "Scale");
oldRelativeOrigin.Y = -oldRelativeOrigin.Y;
oldRelativeOrigin = MathUtils.RotatePoint(oldRelativeOrigin, -item.rotationRad);
oldRelativeOrigin = MathUtils.RotatePoint(oldRelativeOrigin, -item.RotationRad);
Vector2 oldOrigin = centerPos + oldRelativeOrigin;
Vector2 relativeOrigin = (prefab.SwappableItem.SwapOrigin - prefab.Size / 2) * item.Scale;
relativeOrigin.Y = -relativeOrigin.Y;
relativeOrigin = MathUtils.RotatePoint(relativeOrigin, -item.rotationRad);
relativeOrigin = MathUtils.RotatePoint(relativeOrigin, -item.RotationRad);
Vector2 origin = new Vector2(rect.X + rect.Width / 2, rect.Y - rect.Height / 2) + relativeOrigin;
item.rect.Location -= (origin - oldOrigin).ToPoint();
@@ -3211,11 +3295,20 @@ namespace Barotrauma
item.condition = MathHelper.Clamp(condition, 0, item.MaxCondition);
item.lastSentCondition = item.condition;
item.RecalculateConditionValues();
item.SetActiveSprite();
if (submarine?.Info.GameVersion != null)
Version savedVersion = submarine?.Info.GameVersion;
if (element.Document?.Root != null && element.Document.Root.Name.ToString().Equals("gamesession", StringComparison.OrdinalIgnoreCase))
{
SerializableProperty.UpgradeGameVersion(item, item.Prefab.ConfigElement, submarine.Info.GameVersion);
//character inventories are loaded from the game session file - use the version number of the saved game session instead of the sub
//(the sub may have already been saved and up-to-date, even though the character inventories aren't)
savedVersion = new Version(element.Document.Root.GetAttributeString("version", "0.0.0.0"));
}
if (savedVersion != null)
{
SerializableProperty.UpgradeGameVersion(item, item.Prefab.ConfigElement, savedVersion);
}
foreach (ItemComponent component in item.components)
@@ -3335,7 +3428,7 @@ namespace Barotrauma
{
ic.ShallowRemove();
}
ItemList.Remove(this);
RemoveFromLists();
if (body != null)
{
@@ -3395,7 +3488,8 @@ namespace Barotrauma
ic.GuiFrame = null;
#endif
}
ItemList.Remove(this);
RemoveFromLists();
if (body != null)
{
@@ -3430,6 +3524,14 @@ namespace Barotrauma
GameMain.LuaCs.Hook.Call("item.removed", this);
}
private void RemoveFromLists()
{
ItemList.Remove(this);
dangerousItems.Remove(this);
repairableItems.Remove(this);
cleanableItems.Remove(this);
}
partial void RemoveProjSpecific();
public static void RemoveByPrefab(ItemPrefab prefab)
@@ -253,6 +253,12 @@ namespace Barotrauma
public readonly float MinCondition;
public readonly int MinAmount;
public readonly int MaxAmount;
// Overrides min and max, if defined.
public readonly int Amount;
public readonly bool CampaignOnly;
public readonly bool NotCampaign;
public readonly bool TransferOnlyOnePerContainer;
public readonly bool AllowTransfersHere = true;
public PreferredContainer(XElement element)
{
@@ -261,21 +267,26 @@ namespace Barotrauma
SpawnProbability = element.GetAttributeFloat("spawnprobability", 0.0f);
MinAmount = element.GetAttributeInt("minamount", 0);
MaxAmount = Math.Max(MinAmount, element.GetAttributeInt("maxamount", 0));
Amount = element.GetAttributeInt("amount", 0);
MaxCondition = element.GetAttributeFloat("maxcondition", 100f);
MinCondition = element.GetAttributeFloat("mincondition", 0f);
CampaignOnly = element.GetAttributeBool("campaignonly", CampaignOnly);
NotCampaign = element.GetAttributeBool("notcampaign", NotCampaign);
TransferOnlyOnePerContainer = element.GetAttributeBool("TransferOnlyOnePerContainer", TransferOnlyOnePerContainer);
AllowTransfersHere = element.GetAttributeBool("AllowTransfersHere", AllowTransfersHere);
if (element.Attribute("spawnprobability") == null)
if (element.GetAttribute("spawnprobability") == null)
{
//if spawn probability is not defined but amount is, assume the probability is 1
if (MaxAmount > 0)
if (MaxAmount > 0 || Amount > 0)
{
SpawnProbability = 1.0f;
}
}
else if (element.Attribute("minamount") == null && element.Attribute("maxamount") == null)
else if (element.GetAttribute("minamount") == null && element.GetAttribute("maxamount") == null && element.GetAttribute("amount") == null)
{
//spawn probability defined but amount isn't, assume amount is 1
MinAmount = MaxAmount = 1;
MinAmount = MaxAmount = Amount = 1;
SpawnProbability = element.GetAttributeFloat("spawnprobability", 0.0f);
}
}
@@ -600,6 +611,9 @@ namespace Barotrauma
public ImmutableHashSet<Identifier> AllowDroppingOnSwapWith { get; private set; }
[Serialize(false, IsPropertySaveable.No)]
public bool DontTransferBetweenSubs { get; private set; }
protected override Identifier DetermineIdentifier(XElement element)
{
Identifier identifier = base.DetermineIdentifier(element);
@@ -1084,7 +1098,7 @@ namespace Barotrauma
//legacy support
identifier = GenerateLegacyIdentifier(name);
}
prefab = Find(p => p is ItemPrefab && p.Identifier == identifier) as ItemPrefab;
Prefabs.TryGet(identifier, out prefab);
//not found, see if we can find a prefab with a matching alias
if (prefab == null && !string.IsNullOrEmpty(name))
@@ -1104,12 +1118,13 @@ namespace Barotrauma
return prefab;
}
public bool IsContainerPreferred(Item item, ItemContainer targetContainer, out bool isPreferencesDefined, out bool isSecondary, bool requireConditionRequirement = false)
public bool IsContainerPreferred(Item item, ItemContainer targetContainer, out bool isPreferencesDefined, out bool isSecondary, bool requireConditionRequirement = false, bool checkTransferConditions = false)
{
isPreferencesDefined = PreferredContainers.Any();
isSecondary = false;
if (!isPreferencesDefined) { return true; }
if (PreferredContainers.Any(pc => (!requireConditionRequirement || HasConditionRequirement(pc)) && IsItemConditionAcceptable(item, pc) && IsContainerPreferred(pc.Primary, targetContainer)))
if (PreferredContainers.Any(pc => (!requireConditionRequirement || HasConditionRequirement(pc)) && IsItemConditionAcceptable(item, pc) &&
IsContainerPreferred(pc.Primary, targetContainer) && (!checkTransferConditions || CanBeTransferred(item.Prefab.Identifier, pc, targetContainer))))
{
return true;
}
@@ -1132,6 +1147,8 @@ namespace Barotrauma
}
private bool IsItemConditionAcceptable(Item item, PreferredContainer pc) => item.ConditionPercentage >= pc.MinCondition && item.ConditionPercentage <= pc.MaxCondition;
private bool CanBeTransferred(Identifier item, PreferredContainer pc, ItemContainer targetContainer) =>
pc.AllowTransfersHere && (!pc.TransferOnlyOnePerContainer || targetContainer.Inventory.AllItems.None(i => i.Prefab.Identifier == item));
public static bool IsContainerPreferred(IEnumerable<Identifier> preferences, ItemContainer c) => preferences.Any(id => c.Item.Prefab.Identifier == id || c.Item.HasTag(id));
public static bool IsContainerPreferred(IEnumerable<Identifier> preferences, IEnumerable<Identifier> ids) => ids.Any(id => preferences.Contains(id));
@@ -36,6 +36,11 @@ namespace Barotrauma
/// </summary>
public bool ExcludeBroken { get; private set; }
/// <summary>
/// Should full condition (100%) items be excluded
/// </summary>
public bool ExcludeFullCondition { get; private set; }
public bool AllowVariants { get; private set; } = true;
public RelationType Type
@@ -102,14 +107,14 @@ namespace Barotrauma
return CheckContained(parentItem);
case RelationType.Container:
if (parentItem == null || parentItem.Container == null) { return MatchOnEmpty; }
return (!ExcludeBroken || parentItem.Container.Condition > 0.0f) && MatchesItem(parentItem.Container);
return (!ExcludeBroken || parentItem.Container.Condition > 0.0f) && (!ExcludeFullCondition || !parentItem.Container.IsFullCondition) && MatchesItem(parentItem.Container);
case RelationType.Equipped:
if (character == null) { return false; }
if (MatchOnEmpty && !character.HeldItems.Any()) { return true; }
foreach (Item equippedItem in character.HeldItems)
{
if (equippedItem == null) { continue; }
if ((!ExcludeBroken || equippedItem.Condition > 0.0f) && MatchesItem(equippedItem)) { return true; }
if ((!ExcludeBroken || equippedItem.Condition > 0.0f) && (!ExcludeFullCondition || !equippedItem.IsFullCondition) && MatchesItem(equippedItem)) { return true; }
}
break;
case RelationType.Picked:
@@ -138,8 +143,7 @@ namespace Barotrauma
foreach (Item contained in parentItem.ContainedItems)
{
if (TargetSlot > -1 && parentItem.OwnInventory.FindIndex(contained) != TargetSlot) { continue; }
if ((!ExcludeBroken || contained.Condition > 0.0f) && MatchesItem(contained)) { return true; }
if ((!ExcludeBroken || contained.Condition > 0.0f) && (!ExcludeFullCondition || !contained.IsFullCondition) && MatchesItem(contained)) { return true; }
if (CheckContained(contained)) { return true; }
}
return false;
@@ -153,6 +157,7 @@ namespace Barotrauma
new XAttribute("optional", IsOptional),
new XAttribute("ignoreineditor", IgnoreInEditor),
new XAttribute("excludebroken", ExcludeBroken),
new XAttribute("excludefullcondition", ExcludeFullCondition),
new XAttribute("targetslot", TargetSlot),
new XAttribute("allowvariants", AllowVariants));
@@ -212,12 +217,12 @@ namespace Barotrauma
}
}
if (identifiers.Length == 0 && excludedIdentifiers.Length == 0 && !returnEmpty) { return null; }
RelatedItem ri = new RelatedItem(identifiers, excludedIdentifiers)
{
ExcludeBroken = element.GetAttributeBool("excludebroken", true),
ExcludeFullCondition = element.GetAttributeBool("excludefullcondition", false),
AllowVariants = element.GetAttributeBool("allowvariants", true)
};
string typeStr = element.GetAttributeString("type", "");
@@ -0,0 +1,42 @@
#nullable enable
using System.Collections.Immutable;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma
{
internal class StartItem
{
public Identifier Item;
public int Amount;
public StartItem(XElement element)
{
Item = element.GetAttributeIdentifier("identifier", Identifier.Empty);
Amount = element.GetAttributeInt("amount", 1);
}
}
/// <summary>
/// Additive sets of items spawned only at the start of the game.
/// </summary>
internal class StartItemSet : PrefabWithUintIdentifier
{
public readonly static PrefabCollection<StartItemSet> Sets = new PrefabCollection<StartItemSet>();
public readonly ImmutableArray<StartItem> Items;
/// <summary>
/// The order in which the sets are displayed in menus
/// </summary>
public readonly int Order;
public StartItemSet(ContentXElement element, StartItemsFile file) : base(file, element.GetAttributeIdentifier("identifier", Identifier.Empty))
{
Items = element.Elements().Select(e => new StartItem(e!)).ToImmutableArray();
Order = element.GetAttributeInt("order", 0);
}
public override void Dispose() { }
}
}