This commit is contained in:
Evil Factory
2022-06-15 13:26:49 -03:00
410 changed files with 11140 additions and 5815 deletions
@@ -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);