(bcb06cc5c) Unstable v0.9.9.0

This commit is contained in:
Juan Pablo Arce
2020-03-27 15:22:59 -03:00
parent c81486a993
commit b143329701
326 changed files with 9692 additions and 4364 deletions
@@ -58,6 +58,13 @@ namespace Barotrauma.Items.Components
set;
}
[Serialize(false, false, description: "If set to true, this docking port is used when spawning the submarine docked to an outpost (if possible).")]
public bool MainDockingPort
{
get;
set;
}
public DockingPort DockingTarget { get; private set; }
public bool Docked
@@ -173,8 +180,8 @@ namespace Barotrauma.Items.Components
if (!item.linkedTo.Contains(target.item)) item.linkedTo.Add(target.item);
if (!target.item.linkedTo.Contains(item)) target.item.linkedTo.Add(item);
if (!target.item.Submarine.DockedTo.Contains(item.Submarine)) target.item.Submarine.DockedTo.Add(item.Submarine);
if (!item.Submarine.DockedTo.Contains(target.item.Submarine)) item.Submarine.DockedTo.Add(target.item.Submarine);
if (!target.item.Submarine.DockedTo.Contains(item.Submarine)) target.item.Submarine.ConnectedDockingPorts.Add(item.Submarine, target);
if (!item.Submarine.DockedTo.Contains(target.item.Submarine)) item.Submarine.ConnectedDockingPorts.Add(target.item.Submarine, this);
DockingTarget = target;
DockingTarget.DockingTarget = this;
@@ -234,12 +241,12 @@ namespace Barotrauma.Items.Components
Vector2 jointDiff = joint.WorldAnchorB - joint.WorldAnchorA;
if (item.Submarine.PhysicsBody.Mass < DockingTarget.item.Submarine.PhysicsBody.Mass ||
DockingTarget.item.Submarine.IsOutpost)
DockingTarget.item.Submarine.Info.IsOutpost)
{
item.Submarine.SubBody.SetPosition(item.Submarine.SubBody.Position + ConvertUnits.ToDisplayUnits(jointDiff));
}
else if (DockingTarget.item.Submarine.PhysicsBody.Mass < item.Submarine.PhysicsBody.Mass ||
item.Submarine.IsOutpost)
item.Submarine.Info.IsOutpost)
{
DockingTarget.item.Submarine.SubBody.SetPosition(DockingTarget.item.Submarine.SubBody.Position - ConvertUnits.ToDisplayUnits(jointDiff));
}
@@ -703,8 +710,8 @@ namespace Barotrauma.Items.Components
ApplyStatusEffects(ActionType.OnSecondaryUse, 1.0f);
DockingTarget.item.Submarine.DockedTo.Remove(item.Submarine);
item.Submarine.DockedTo.Remove(DockingTarget.item.Submarine);
DockingTarget.item.Submarine.ConnectedDockingPorts.Remove(item.Submarine);
item.Submarine.ConnectedDockingPorts.Remove(DockingTarget.item.Submarine);
if (door != null && DockingTarget.door != null)
{
@@ -951,12 +958,12 @@ namespace Barotrauma.Items.Components
if (docked)
{
if (item.Submarine != null && DockingTarget?.item?.Submarine != null)
GameServer.Log(sender.LogName + " docked " + item.Submarine.Name + " to " + DockingTarget.item.Submarine.Name, ServerLog.MessageType.ItemInteraction);
GameServer.Log(sender.LogName + " docked " + item.Submarine.Info.Name + " to " + DockingTarget.item.Submarine.Info.Name, ServerLog.MessageType.ItemInteraction);
}
else
{
if (item.Submarine != null && prevDockingTarget?.item?.Submarine != null)
GameServer.Log(sender.LogName + " undocked " + item.Submarine.Name + " from " + prevDockingTarget.item.Submarine.Name, ServerLog.MessageType.ItemInteraction);
GameServer.Log(sender.LogName + " undocked " + item.Submarine.Info.Name + " from " + prevDockingTarget.item.Submarine.Info.Name, ServerLog.MessageType.ItemInteraction);
}
}
#endif
@@ -287,24 +287,21 @@ namespace Barotrauma.Items.Components
public override bool Select(Character character)
{
if (!isBroken)
if (isBroken) { return true; }
bool hasRequiredItems = HasRequiredItems(character, false);
if (HasAccess(character))
{
bool hasRequiredItems = HasRequiredItems(character, false);
if (HasAccess(character))
{
float originalPickingTime = PickingTime;
PickingTime = 0;
ToggleState(ActionType.OnUse, character);
PickingTime = originalPickingTime;
}
#if CLIENT
else if (hasRequiredItems && character != null && character == Character.Controlled)
{
GUI.AddMessage(accessDeniedTxt, GUI.Style.Red);
}
#endif
float originalPickingTime = PickingTime;
PickingTime = 0;
ToggleState(ActionType.OnUse, character);
PickingTime = originalPickingTime;
}
#if CLIENT
else if (hasRequiredItems && character != null && character == Character.Controlled)
{
GUI.AddMessage(accessDeniedTxt, GUI.Style.Red);
}
#endif
return false;
}
@@ -54,7 +54,7 @@ namespace Barotrauma.Items.Components
{
foreach (XElement subElement in element.Elements())
{
if (subElement.Name.ToString().ToLowerInvariant() != "attack") { continue; }
if (!subElement.Name.ToString().Equals("attack", StringComparison.OrdinalIgnoreCase)) { continue; }
attack = new Attack(subElement, item.Name + ", MeleeWeapon");
}
item.IsShootable = true;
@@ -29,6 +29,13 @@ namespace Barotrauma.Items.Components
set { reload = Math.Max(value, 0.0f); }
}
[Serialize(1, false, description: "How projectiles the weapon launches when fired once.")]
public int ProjectileCount
{
get;
set;
}
[Serialize(0.0f, false, description: "Random spread applied to the firing angle of the projectiles when used by a character with sufficient skills to use the weapon (in degrees).")]
public float Spread
{
@@ -110,55 +117,62 @@ namespace Barotrauma.Items.Components
ApplyStatusEffects(ActionType.OnFailure, 1.0f, character);
}
Projectile projectile = FindProjectile(triggerOnUseOnContainers: true);
if (projectile == null) { return true; }
float spread = GetSpread(character);
float rotation = (item.body.Dir == 1.0f) ? item.body.Rotation : item.body.Rotation - MathHelper.Pi;
rotation += spread * Rand.Range(-0.5f, 0.5f);
projectile.User = character;
//add the limbs of the shooter to the list of bodies to be ignored
//so that the player can't shoot himself
projectile.IgnoredBodies = new List<Body>(limbBodies);
Vector2 projectilePos = item.SimPosition;
Vector2 sourcePos = character?.AnimController == null ? item.SimPosition : character.AnimController.AimSourceSimPos;
Vector2 barrelPos = TransformedBarrelPos + item.body.SimPosition;
//make sure there's no obstacles between the base of the weapon (or the shoulder of the character) and the end of the barrel
if (Submarine.PickBody(sourcePos, barrelPos, projectile.IgnoredBodies, Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionItemBlocking) == null)
for (int i = 0; i < ProjectileCount; i++)
{
//no obstacles -> we can spawn the projectile at the barrel
projectilePos = barrelPos;
Projectile projectile = FindProjectile(triggerOnUseOnContainers: true);
if (projectile == null) { return true; }
float spread = GetSpread(character);
float rotation = (item.body.Dir == 1.0f) ? item.body.Rotation : item.body.Rotation - MathHelper.Pi;
rotation += spread * Rand.Range(-0.5f, 0.5f);
projectile.User = character;
//add the limbs of the shooter to the list of bodies to be ignored
//so that the player can't shoot himself
projectile.IgnoredBodies = new List<Body>(limbBodies);
Vector2 projectilePos = item.SimPosition;
Vector2 sourcePos = character?.AnimController == null ? item.SimPosition : character.AnimController.AimSourceSimPos;
Vector2 barrelPos = TransformedBarrelPos + item.body.SimPosition;
//make sure there's no obstacles between the base of the weapon (or the shoulder of the character) and the end of the barrel
if (Submarine.PickBody(sourcePos, barrelPos, projectile.IgnoredBodies, Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionItemBlocking) == null)
{
//no obstacles -> we can spawn the projectile at the barrel
projectilePos = barrelPos;
}
else if ((sourcePos - barrelPos).LengthSquared() > 0.0001f)
{
//spawn the projectile body.GetMaxExtent() away from the position where the raycast hit the obstacle
projectilePos = sourcePos - Vector2.Normalize(barrelPos - projectilePos) * Math.Max(projectile.Item.body.GetMaxExtent(), 0.1f);
}
projectile.Item.body.ResetDynamics();
projectile.Item.SetTransform(projectilePos, rotation);
projectile.Use(deltaTime);
projectile.Item.GetComponent<Rope>()?.Attach(item, projectile.Item);
if (projectile.Item.Removed) { continue; }
projectile.User = character;
projectile.Item.body.ApplyTorque(projectile.Item.body.Mass * degreeOfFailure * Rand.Range(-10.0f, 10.0f));
//set the rotation of the projectile again because dropping the projectile resets the rotation
projectile.Item.SetTransform(projectilePos,
rotation + (projectile.Item.body.Dir * projectile.LaunchRotationRadians));
item.RemoveContained(projectile.Item);
if (i == 0)
{
//recoil
item.body.ApplyLinearImpulse(
new Vector2((float)Math.Cos(projectile.Item.body.Rotation), (float)Math.Sin(projectile.Item.body.Rotation)) * item.body.Mass * -50.0f,
maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
}
}
else if ((sourcePos - barrelPos).LengthSquared() > 0.0001f)
{
//spawn the projectile body.GetMaxExtent() away from the position where the raycast hit the obstacle
projectilePos = sourcePos - Vector2.Normalize(barrelPos - projectilePos) * Math.Max(projectile.Item.body.GetMaxExtent(), 0.1f);
}
projectile.Item.body.ResetDynamics();
projectile.Item.SetTransform(projectilePos, rotation);
projectile.Use(deltaTime);
if (projectile.Item.Removed) { return true; }
projectile.User = character;
projectile.Item.body.ApplyTorque(projectile.Item.body.Mass * degreeOfFailure * Rand.Range(-10.0f, 10.0f));
//set the rotation of the projectile again because dropping the projectile resets the rotation
projectile.Item.SetTransform(projectilePos,
rotation + (projectile.Item.body.Dir * projectile.LaunchRotationRadians));
//recoil
item.body.ApplyLinearImpulse(
new Vector2((float)Math.Cos(projectile.Item.body.Rotation), (float)Math.Sin(projectile.Item.body.Rotation)) * item.body.Mass * -50.0f,
maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
LaunchProjSpecific();
item.RemoveContained(projectile.Item);
return true;
}
@@ -578,13 +578,7 @@ namespace Barotrauma.Items.Components
{
character.SetInput(InputType.Aim, false, true);
}
bool isAiming = false;
var holdable = item.GetComponent<Holdable>();
if (holdable != null)
{
isAiming = holdable.ControlPose;
}
sinTime = isAiming ? sinTime + deltaTime * 5 : 0;
sinTime += deltaTime * 5;
}
// Press the trigger only when the tool is approximately facing the target.
Vector2 fromItemToLeak = leak.WorldPosition - item.WorldPosition;
@@ -76,7 +76,7 @@ namespace Barotrauma.Items.Components
if (!picker.IsKeyDown(InputType.Aim) && !throwing) { throwPos = 0.0f; }
bool aim = picker.IsKeyDown(InputType.Aim) && (picker.SelectedConstruction == null || picker.SelectedConstruction.GetComponent<Ladder>() != null);
if (picker.IsUnconscious || picker.IsDead || !picker.AllowInput)
if (picker.IsDead || !picker.AllowInput)
{
throwing = false;
aim = false;
@@ -68,7 +68,6 @@ namespace Barotrauma.Items.Components
protected const float CorrectionDelay = 1.0f;
protected CoroutineHandle delayedCorrectionCoroutine;
protected float correctionTimer;
[Editable, Serialize(0.0f, false, description: "How long it takes to pick up the item (in seconds).")]
public float PickingTime
@@ -81,7 +80,6 @@ namespace Barotrauma.Items.Components
public Action<bool> OnActiveStateChanged;
public float IsActiveTimer;
public virtual bool IsActive
{
get { return isActive; }
@@ -222,7 +220,6 @@ namespace Barotrauma.Items.Components
set;
}
/// <summary>
/// How useful the item is in combat? Used by AI to decide which item it should use as a weapon. For the sake of clarity, use a value between 0 and 100 (not enforced).
/// </summary>
@@ -632,7 +629,7 @@ namespace Barotrauma.Items.Components
}
/// <summary>
/// Only checks the id card(s). Much simpler and a bit different than HasRequiredItems.
/// Only checks if any of the Picked requirements are matched (used for checking id card(s)). Much simpler and a bit different than HasRequiredItems.
/// </summary>
public bool HasAccess(Character character)
{
@@ -641,7 +638,7 @@ namespace Barotrauma.Items.Components
foreach (Item item in character.Inventory.Items)
{
if (item?.Prefab.Identifier == "idcard" && requiredItems.Any(ri => ri.Value.Any(r => r.MatchesItem(item))))
if (requiredItems.Any(ri => ri.Value.Any(r => r.Type == RelatedItem.RelationType.Picked && r.MatchesItem(item))))
{
return true;
}
@@ -741,14 +738,14 @@ namespace Barotrauma.Items.Components
{
foreach (XAttribute attribute in componentElement.Attributes())
{
if (!SerializableProperties.TryGetValue(attribute.Name.ToString().ToLowerInvariant(), out SerializableProperty property)) continue;
if (!SerializableProperties.TryGetValue(attribute.Name.ToString().ToLowerInvariant(), out SerializableProperty property)) { continue; }
property.TrySetValue(this, attribute.Value);
}
ParseMsg();
OverrideRequiredItems(componentElement);
}
if (item.Submarine != null) { SerializableProperty.UpgradeGameVersion(this, originalElement, item.Submarine.GameVersion); }
if (item.Submarine != null) { SerializableProperty.UpgradeGameVersion(this, originalElement, item.Submarine.Info.GameVersion); }
}
/// <summary>
@@ -870,6 +867,7 @@ namespace Barotrauma.Items.Components
switch (subElement.Name.ToString().ToLowerInvariant())
{
case "requireditem":
case "requireditems":
RelatedItem newRequiredItem = RelatedItem.Load(subElement, returnEmptyRequirements, item.Name);
if (newRequiredItem == null) continue;
@@ -52,6 +52,9 @@ namespace Barotrauma.Items.Components
set;
}
[Serialize(false, false)]
public bool AccessOnlyWhenBroken { get; set; }
[Serialize(5, false, description: "How many inventory slots the inventory has per row.")]
public int SlotsPerRow { get; set; }
@@ -197,10 +200,21 @@ namespace Barotrauma.Items.Components
}
}
public override bool HasRequiredItems(Character character, bool addMessage, string msg = null)
{
return (!AccessOnlyWhenBroken || Item.Condition <= 0) && base.HasRequiredItems(character, addMessage, msg);
}
public override bool Select(Character character)
{
if (item.Container != null) { return false; }
if (AccessOnlyWhenBroken)
{
if (item.Condition > 0)
{
return false;
}
}
if (AutoInteractWithContained && character.SelectedConstruction == null)
{
foreach (Item contained in Inventory.Items)
@@ -218,6 +232,13 @@ namespace Barotrauma.Items.Components
public override bool Pick(Character picker)
{
if (AccessOnlyWhenBroken)
{
if (item.Condition > 0)
{
return false;
}
}
if (AutoInteractWithContained)
{
foreach (Item contained in Inventory.Items)
@@ -5,6 +5,7 @@ using System;
using System.Collections.Generic;
using System.Globalization;
using System.Xml.Linq;
using Barotrauma.Extensions;
namespace Barotrauma.Items.Components
{
@@ -209,7 +210,7 @@ namespace Barotrauma.Items.Components
return true;
}
public override bool SecondaryUse(float deltaTime, Character character = null)
{
if (this.user != character)
@@ -55,6 +55,15 @@ namespace Barotrauma.Items.Components
get { return Math.Abs((force / 100.0f) * (MinVoltage <= 0.0f ? 1.0f : Math.Min(prevVoltage / MinVoltage, 1.0f))); }
}
public float CurrentBrokenVolume
{
get
{
if (item.ConditionPercentage > 10.0f) { return 0.0f; }
return Math.Abs(targetForce / 100.0f) * (1.0f - item.ConditionPercentage / 10.0f);
}
}
public Engine(Item item, XElement element)
: base(item, element)
{
@@ -22,6 +22,30 @@ namespace Barotrauma.Items.Components
private ItemContainer inputContainer, outputContainer;
private enum FabricatorState
{
Active = 1,
Paused = 2,
Stopped = 0
}
private FabricatorState state;
private FabricatorState State
{
get
{
return state;
}
set
{
if (state == value) { return; }
state = value;
#if SERVER
item.CreateServerEvent(this);
#endif
}
}
public ItemContainer InputContainer
{
get { return inputContainer; }
@@ -39,7 +63,7 @@ namespace Barotrauma.Items.Components
{
foreach (XElement subElement in element.Elements())
{
if (subElement.Name.ToString().ToLowerInvariant() == "fabricableitem")
if (subElement.Name.ToString().Equals("fabricableitem", StringComparison.OrdinalIgnoreCase))
{
DebugConsole.ThrowError("Error in item " + item.Name + "! Fabrication recipes should be defined in the craftable item's xml, not in the fabricator.");
break;
@@ -61,6 +85,8 @@ namespace Barotrauma.Items.Components
}
}
state = FabricatorState.Stopped;
InitProjSpecific();
}
@@ -147,12 +173,15 @@ namespace Barotrauma.Items.Components
currPowerConsumption = powerConsumption;
currPowerConsumption *= MathHelper.Lerp(1.5f, 1.0f, item.Condition / item.MaxCondition);
if (GameMain.NetworkMember?.IsServer ?? true)
{
State = FabricatorState.Active;
}
#if SERVER
if (user != null)
{
GameServer.Log(user.LogName + " started fabricating " + selectedItem.DisplayName + " in " + item.Name, ServerLog.MessageType.ItemInteraction);
}
item.CreateServerEvent(this);
#endif
}
@@ -180,12 +209,15 @@ namespace Barotrauma.Items.Components
inputContainer.Inventory.Locked = false;
outputContainer.Inventory.Locked = false;
if (GameMain.NetworkMember?.IsServer ?? true)
{
State = FabricatorState.Stopped;
}
#if SERVER
if (user != null)
{
GameServer.Log(user.LogName + " cancelled the fabrication of " + fabricatedItem.DisplayName + " in " + item.Name, ServerLog.MessageType.ItemInteraction);
}
item.CreateServerEvent(this);
#endif
}
@@ -199,8 +231,25 @@ namespace Barotrauma.Items.Components
progressState = fabricatedItem == null ? 0.0f : (requiredTime - timeUntilReady) / requiredTime;
hasPower = Voltage >= MinVoltage;
if (!hasPower) { return; }
if (GameMain.NetworkMember?.IsClient ?? false)
{
hasPower = State != FabricatorState.Paused;
if (!hasPower)
{
return;
}
}
else
{
hasPower = Voltage >= MinVoltage;
if (!hasPower)
{
State = FabricatorState.Paused;
return;
}
State = FabricatorState.Active;
}
var repairable = item.GetComponent<Repairable>();
if (repairable != null)
@@ -37,23 +37,10 @@ namespace Barotrauma.Items.Components
private float currFlow;
public float CurrFlow
{
get
get
{
if (!IsActive) { return 0.0f; }
return Math.Abs(currFlow);
}
}
public override bool IsActive
{
get => base.IsActive;
set
{
base.IsActive = value;
if (!IsActive)
{
powerConsumption = 0;
}
return Math.Abs(currFlow);
}
}
@@ -77,11 +64,6 @@ namespace Barotrauma.Items.Components
float hullPercentage = 0.0f;
if (item.CurrentHull != null) { hullPercentage = (item.CurrentHull.WaterVolume / item.CurrentHull.Volume) * 100.0f; }
FlowPercentage = ((float)targetLevel - hullPercentage) * 10.0f;
if (pumpSpeedLockTimer <= 0.0f)
{
targetLevel = null;
}
}
currPowerConsumption = powerConsumption * Math.Abs(flowPercentage / 100.0f);
@@ -125,6 +107,7 @@ namespace Barotrauma.Items.Components
if (float.TryParse(signal, NumberStyles.Any, CultureInfo.InvariantCulture, out float tempSpeed))
{
flowPercentage = MathHelper.Clamp(tempSpeed, -100.0f, 100.0f);
targetLevel = null;
pumpSpeedLockTimer = 0.1f;
}
}
@@ -144,7 +127,7 @@ namespace Barotrauma.Items.Components
if (GameMain.Client != null) { return false; }
#endif
if (objective.Option.ToLowerInvariant() == "stoppumping")
if (objective.Option.Equals("stoppumping", StringComparison.OrdinalIgnoreCase))
{
#if SERVER
if (FlowPercentage > 0.0f)
@@ -5,6 +5,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using Barotrauma.Extensions;
using System.Globalization;
namespace Barotrauma.Items.Components
{
@@ -651,6 +652,20 @@ namespace Barotrauma.Items.Components
unsentChanges = true;
}
break;
case "set_fissionrate":
if (float.TryParse(signal, NumberStyles.Float, CultureInfo.InvariantCulture, out float newFissionRate))
{
FissionRate = newFissionRate;
unsentChanges = true;
}
break;
case "set_turbineoutput":
if (float.TryParse(signal, NumberStyles.Float, CultureInfo.InvariantCulture, out float newTurbineOutput))
{
TurbineOutput = newTurbineOutput;
unsentChanges = true;
}
break;
}
}
@@ -165,7 +165,6 @@ namespace Barotrauma.Items.Components
#region Docking
public List<DockingPort> DockingSources = new List<DockingPort>();
public DockingPort ActiveDockingSource, DockingTarget;
private bool searchedConnectedDockingPort;
private bool dockingModeEnabled;
@@ -200,7 +199,7 @@ namespace Barotrauma.Items.Components
if (dockingConnection != null)
{
var connectedPorts = item.GetConnectedComponentsRecursive<DockingPort>(dockingConnection);
DockingSources.AddRange(connectedPorts.Where(p => p.Item.Submarine != null && !p.Item.Submarine.IsOutpost));
DockingSources.AddRange(connectedPorts.Where(p => p.Item.Submarine != null && !p.Item.Submarine.Info.IsOutpost));
}
}
#endregion
@@ -344,6 +343,7 @@ namespace Barotrauma.Items.Components
autopilotRecalculatePathTimer = RecalculatePathInterval;
}
if (steeringPath == null) { return; }
steeringPath.CheckProgress(ConvertUnits.ToSimUnits(controlledSub.WorldPosition), 10.0f);
if (autopilotRayCastTimer <= 0.0f && steeringPath.NextNode != null)
@@ -475,6 +475,8 @@ namespace Barotrauma.Items.Components
private void UpdatePath()
{
if (Level.Loaded == null) { return; }
if (pathFinder == null) pathFinder = new PathFinder(WayPoint.WayPointList, false);
Vector2 target;
@@ -536,7 +538,6 @@ namespace Barotrauma.Items.Components
}
}
private bool aiDockingToggled;
public override bool AIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective)
{
if (objective.Override)
@@ -572,7 +573,7 @@ namespace Barotrauma.Items.Components
}
break;
case "navigateback":
if (!aiDockingToggled && DockingSources.Any(d => d.Docked))
if (DockingSources.Any(d => d.Docked))
{
item.SendSignal(0, "1", "toggle_docking", sender: null);
}
@@ -586,7 +587,7 @@ namespace Barotrauma.Items.Components
}
break;
case "navigatetodestination":
if (!aiDockingToggled && DockingSources.Any(d => d.Docked))
if (DockingSources.Any(d => d.Docked))
{
item.SendSignal(0, "1", "toggle_docking", sender: null);
}
@@ -213,7 +213,7 @@ namespace Barotrauma.Items.Components
}
if (HasBeenTuned) { return true; }
if (string.IsNullOrEmpty(objective.Option) || objective.Option.ToLowerInvariant() == "charge")
if (string.IsNullOrEmpty(objective.Option) || objective.Option.Equals("charge", StringComparison.OrdinalIgnoreCase))
{
if (Math.Abs(rechargeSpeed - maxRechargeSpeed * aiRechargeTargetRatio) > 0.05f)
{
@@ -11,7 +11,7 @@ using System.Xml.Linq;
namespace Barotrauma.Items.Components
{
class Projectile : ItemComponent
partial class Projectile : ItemComponent, IServerSerializable
{
struct HitscanResult
{
@@ -32,11 +32,13 @@ namespace Barotrauma.Items.Components
{
public Fixture Fixture;
public Vector2 Normal;
public Vector2 LinearVelocity;
public Impact(Fixture fixture, Vector2 normal)
public Impact(Fixture fixture, Vector2 normal, Vector2 velocity)
{
Fixture = fixture;
Normal = normal;
LinearVelocity = velocity;
}
}
@@ -47,13 +49,13 @@ namespace Barotrauma.Items.Components
//a duration during which the projectile won't drop from the body it's stuck to
private const float PersistentStickJointDuration = 1.0f;
private float launchImpulse;
private PrismaticJoint stickJoint;
private Body stickTarget;
private Attack attack;
private readonly Attack attack;
private Vector2 launchPos;
private readonly HashSet<Body> hits = new HashSet<Body>();
public List<Body> IgnoredBodies;
@@ -68,14 +70,15 @@ namespace Barotrauma.Items.Components
}
}
public IEnumerable<Body> Hits
{
get { return hits; }
}
private float persistentStickJointTimer;
[Serialize(10.0f, false, description: "The impulse applied to the physics body of the item when it's launched. Higher values make the projectile faster.")]
public float LaunchImpulse
{
get { return launchImpulse; }
set { launchImpulse = value; }
}
public float LaunchImpulse { get; set; }
[Serialize(0.0f, false, description: "The rotation of the item relative to the rotation of the weapon when launched (in degrees).")]
public float LaunchRotation
@@ -98,6 +101,13 @@ namespace Barotrauma.Items.Components
set;
}
[Serialize(false, false, description: "When set to true, the item won't fall of a target it's stuck to unless removed.")]
public bool StickPermanently
{
get;
set;
}
[Serialize(false, false, description: "Can the item stick to the character it hits.")]
public bool StickToCharacters
{
@@ -136,6 +146,13 @@ namespace Barotrauma.Items.Components
set;
}
[Serialize(1, false, description: "How many targets the projectile can hit before it stops.")]
public int MaxTargetsToHit
{
get;
set;
}
[Serialize(false, false, description: "Should the item be deleted when it hits something.")]
public bool RemoveOnHit
{
@@ -150,6 +167,17 @@ namespace Barotrauma.Items.Components
set;
}
public Body StickTarget
{
get;
private set;
}
public bool IsStuckToTarget
{
get { return StickTarget != null; }
}
public Projectile(Item item, XElement element)
: base (item, element)
{
@@ -157,7 +185,7 @@ namespace Barotrauma.Items.Components
foreach (XElement subElement in element.Elements())
{
if (subElement.Name.ToString().ToLowerInvariant() != "attack") continue;
if (!subElement.Name.ToString().Equals("attack", StringComparison.OrdinalIgnoreCase)) { continue; }
attack = new Attack(subElement, item.Name + ", Projectile");
}
}
@@ -201,7 +229,7 @@ namespace Barotrauma.Items.Components
}
else
{
Launch(launchDir * launchImpulse * item.body.Mass);
Launch(launchDir * LaunchImpulse * item.body.Mass);
}
}
@@ -212,6 +240,9 @@ namespace Barotrauma.Items.Components
private void Launch(Vector2 impulse)
{
hits.Clear();
MaxTargetsToHit = 2;
if (item.AiTarget != null)
{
item.AiTarget.SightRange = item.AiTarget.MaxSightRange;
@@ -220,6 +251,8 @@ namespace Barotrauma.Items.Components
item.Drop(null);
launchPos = item.SimPosition;
item.body.Enabled = true;
item.body.ApplyLinearImpulse(impulse, maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
@@ -233,7 +266,7 @@ namespace Barotrauma.Items.Components
if (stickJoint == null) { return; }
stickTarget = null;
StickTarget = null;
GameMain.World.Remove(stickJoint);
stickJoint = null;
}
@@ -287,7 +320,7 @@ namespace Barotrauma.Items.Components
foreach (HitscanResult h in hits)
{
item.body.SetTransform(h.Point, rotation);
if (HandleProjectileCollision(h.Fixture, h.Normal))
if (HandleProjectileCollision(h.Fixture, h.Normal, Vector2.Zero))
{
hitSomething = true;
break;
@@ -321,7 +354,7 @@ namespace Barotrauma.Items.Components
{
//ignore sensors and items
if (fixture?.Body == null || fixture.IsSensor) { return true; }
if (fixture.Body.UserData is Item) { return true; }
if (fixture.Body.UserData is Item item && item.GetComponent<Door>() == null && !item.Prefab.DamagedByProjectiles) { return true; }
if (fixture.Body?.UserData as string == "ruinroom") { return true; }
//ignore everything else than characters, sub walls and level walls
@@ -341,7 +374,7 @@ namespace Barotrauma.Items.Components
//ignore sensors and items
if (fixture?.Body == null || fixture.IsSensor) { return -1; }
if (fixture.Body.UserData is Item item && item.GetComponent<Door>() == null) { return -1; }
if (fixture.Body.UserData is Item item && item.GetComponent<Door>() == null && !item.Prefab.DamagedByProjectiles) { return -1; }
if (fixture.Body?.UserData as string == "ruinroom") { return -1; }
//ignore everything else than characters, sub walls and level walls
@@ -364,7 +397,7 @@ namespace Barotrauma.Items.Components
while (impactQueue.Count > 0)
{
var impact = impactQueue.Dequeue();
HandleProjectileCollision(impact.Fixture, impact.Normal);
HandleProjectileCollision(impact.Fixture, impact.Normal, impact.LinearVelocity);
}
if (item.body != null && item.body.FarseerBody.IsBullet)
@@ -377,7 +410,7 @@ namespace Barotrauma.Items.Components
}
}
if (stickJoint == null) { return; }
if (stickJoint == null || StickPermanently) { return; }
if (persistentStickJointTimer > 0.0f)
{
@@ -385,19 +418,20 @@ namespace Barotrauma.Items.Components
return;
}
if (stickJoint.JointTranslation < stickJoint.LowerLimit * 0.9f || stickJoint.JointTranslation > stickJoint.UpperLimit * 0.9f)
if (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer)
{
stickTarget = null;
if (stickJoint != null)
if (stickJoint.JointTranslation < stickJoint.LowerLimit * 0.9f ||
stickJoint.JointTranslation > stickJoint.UpperLimit * 0.9f)
{
if (GameMain.World.JointList.Contains(stickJoint))
{
GameMain.World.Remove(stickJoint);
}
stickJoint = null;
Unstick();
}
if (!item.body.FarseerBody.IsBullet) { IsActive = false; }
}
#if SERVER
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
{
item.CreateServerEvent(this);
}
#endif
}
}
@@ -405,6 +439,12 @@ namespace Barotrauma.Items.Components
{
if (User != null && User.Removed) { User = null; return false; }
if (IgnoredBodies.Contains(target.Body)) { return false; }
//ignore character colliders (the projectile only hits limbs)
if (target.CollisionCategories == Physics.CollisionCharacter && target.Body.UserData is Character)
{
return false;
}
if (hits.Contains(target.Body)) { return false; }
if (target.Body.UserData is Submarine sub)
{
Vector2 dir = item.body.LinearVelocity.LengthSquared() < 0.001f ?
@@ -415,9 +455,12 @@ namespace Barotrauma.Items.Components
item.body.SimPosition - ConvertUnits.ToSimUnits(sub.Position) - dir,
item.body.SimPosition - ConvertUnits.ToSimUnits(sub.Position) + dir,
collisionCategory: Physics.CollisionWall);
if (wallBody?.FixtureList?.First() != null && wallBody.UserData is Structure structure)
if (wallBody?.FixtureList?.First() != null && wallBody.UserData is Structure structure &&
//ignore the hit if it's behind the position the item was launched from, and the projectile is travelling in the opposite direction
Vector2.Dot(item.body.SimPosition - launchPos, dir) > 0)
{
target = wallBody.FixtureList.First();
if (hits.Contains(target.Body)) { return false; }
}
else
{
@@ -440,18 +483,23 @@ namespace Barotrauma.Items.Components
return false;
}
impactQueue.Enqueue(new Impact(target, contact.Manifold.LocalNormal));
item.body.FarseerBody.OnCollision -= OnProjectileCollision;
return true;
hits.Add(target.Body);
impactQueue.Enqueue(new Impact(target, contact.Manifold.LocalNormal, item.body.LinearVelocity));
if (hits.Count() >= MaxTargetsToHit)
{
item.body.FarseerBody.OnCollision -= OnProjectileCollision;
return true;
}
else
{
return false;
}
}
private bool HandleProjectileCollision(Fixture target, Vector2 collisionNormal)
private bool HandleProjectileCollision(Fixture target, Vector2 collisionNormal, Vector2 velocity)
{
if (User != null && User.Removed) { User = null; }
if (IgnoredBodies.Contains(target.Body)) { return false; }
//ignore character colliders (the projectile only hits limbs)
if (target.CollisionCategories == Physics.CollisionCharacter && target.Body.UserData is Character)
{
@@ -472,7 +520,6 @@ namespace Barotrauma.Items.Components
//severed limbs don't deactivate the projectile (but may still slow it down enough to make it inactive)
if (limb.IsSevered)
{
target.Body.ApplyLinearImpulse(item.body.LinearVelocity * item.body.Mass);
return true;
}
@@ -552,21 +599,30 @@ namespace Barotrauma.Items.Components
}
}
item.body.FarseerBody.OnCollision -= OnProjectileCollision;
target.Body.ApplyLinearImpulse(velocity * item.body.Mass);
item.body.CollisionCategories = Physics.CollisionItem;
item.body.CollidesWith = Physics.CollisionWall | Physics.CollisionLevel;
IgnoredBodies.Clear();
target.Body.ApplyLinearImpulse(item.body.LinearVelocity * item.body.Mass);
if (hits.Count() >= MaxTargetsToHit)
{
item.body.FarseerBody.OnCollision -= OnProjectileCollision;
if (item.Prefab.DamagedByProjectiles || item.Prefab.DamagedByMeleeWeapons)
{
item.body.CollisionCategories = Physics.CollisionCharacter;
item.body.CollidesWith = Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionPlatform | Physics.CollisionProjectile;
}
else
{
item.body.CollisionCategories = Physics.CollisionItem;
item.body.CollidesWith = Physics.CollisionWall | Physics.CollisionLevel;
}
IgnoredBodies.Clear();
}
if (attackResult.AppliedDamageModifiers != null &&
attackResult.AppliedDamageModifiers.Any(dm => dm.DeflectProjectiles))
{
item.body.LinearVelocity *= 0.1f;
}
else if (Vector2.Dot(item.body.LinearVelocity, collisionNormal) < 0.0f &&
else if (Vector2.Dot(velocity, collisionNormal) < 0.0f && hits.Count() >= MaxTargetsToHit &&
(DoesStick ||
(StickToCharacters && target.Body.UserData is Limb) ||
(StickToStructures && target.Body.UserData is Structure) ||
@@ -575,8 +631,24 @@ namespace Barotrauma.Items.Components
Vector2 dir = new Vector2(
(float)Math.Cos(item.body.Rotation),
(float)Math.Sin(item.body.Rotation));
StickToTarget(target.Body, dir);
if (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer)
{
if (target.Body.UserData is Structure structure && structure.Submarine != item.Submarine && structure.Submarine != null)
{
StickToTarget(structure.Submarine.PhysicsBody.FarseerBody, dir);
}
else
{
StickToTarget(target.Body, dir);
}
}
#if SERVER
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
{
item.CreateServerEvent(this);
}
#endif
item.body.LinearVelocity *= 0.5f;
return Hitscan;
@@ -608,7 +680,7 @@ namespace Barotrauma.Items.Components
private void StickToTarget(Body targetBody, Vector2 axis)
{
if (stickJoint != null) return;
if (stickJoint != null) { return; }
stickJoint = new PrismaticJoint(targetBody, item.body.FarseerBody, item.body.SimPosition, axis, true)
{
@@ -616,19 +688,38 @@ namespace Barotrauma.Items.Components
MaxMotorForce = 30.0f,
LimitEnabled = true
};
if (item.Sprite != null)
if (StickPermanently)
{
stickJoint.LowerLimit = ConvertUnits.ToSimUnits(item.Sprite.size.X * -0.3f);
stickJoint.UpperLimit = ConvertUnits.ToSimUnits(item.Sprite.size.X * 0.3f);
stickJoint.LowerLimit = stickJoint.UpperLimit = 0.0f;
}
else if (item.Sprite != null)
{
stickJoint.LowerLimit = ConvertUnits.ToSimUnits(item.Sprite.size.X * -0.3f * item.Scale);
stickJoint.UpperLimit = ConvertUnits.ToSimUnits(item.Sprite.size.X * 0.3f * item.Scale);
}
persistentStickJointTimer = PersistentStickJointDuration;
stickTarget = targetBody;
StickTarget = targetBody;
GameMain.World.Add(stickJoint);
IsActive = true;
}
private void Unstick()
{
StickTarget = null;
if (stickJoint != null)
{
if (GameMain.World.JointList.Contains(stickJoint))
{
GameMain.World.Remove(stickJoint);
}
stickJoint = null;
}
if (!item.body.FarseerBody.IsBullet) { IsActive = false; }
}
protected override void RemoveComponentSpecific()
{
if (stickJoint != null)
@@ -1,4 +1,5 @@
using Barotrauma.Networking;
using Barotrauma.Extensions;
using Barotrauma.Networking;
using Microsoft.Xna.Framework;
using System;
using System.Globalization;
@@ -111,7 +112,7 @@ namespace Barotrauma.Items.Components
element.GetAttributeString("name", "");
//backwards compatibility
var showRepairUIAttribute = element.Attributes().FirstOrDefault(a => a.Name.ToString().ToLowerInvariant() == "showrepairuithreshold");
var showRepairUIAttribute = element.Attributes().FirstOrDefault(a => a.Name.ToString().Equals("showrepairuithreshold", StringComparison.OrdinalIgnoreCase));
if (showRepairUIAttribute != null)
{
float repairThreshold;
@@ -130,7 +131,26 @@ namespace Barotrauma.Items.Components
}
partial void InitProjSpecific(XElement element);
/// <summary>
/// Check if the character manages to succesfully repair the item
/// </summary>
public bool CheckCharacterSuccess(Character character)
{
if (character == null) { return false; }
if (statusEffectLists == null || statusEffectLists.None(s => s.Key == ActionType.OnFailure)) { return true; }
// unpowered (electrical) items can be repaired without a risk of electrical shock
if (requiredSkills.Any(s => s != null && s.Identifier.Equals("electrical", StringComparison.OrdinalIgnoreCase)) &&
item.GetComponent<Powered>() is Powered powered && powered.Voltage < 0.1f) { return true; }
if (Rand.Range(0.0f, 0.5f) < DegreeOfSuccess(character)) { return true; }
ApplyStatusEffects(ActionType.OnFailure, 1.0f, character);
return false;
}
public bool StartRepairing(Character character, FixActions action)
{
if (character == null || character.IsDead || action == FixActions.None)
@@ -143,8 +163,15 @@ namespace Barotrauma.Items.Components
#if SERVER
if (CurrentFixer != character || currentFixerAction != action)
{
if (!CheckCharacterSuccess(character))
{
GameMain.Server?.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.ApplyStatusEffect, ActionType.OnFailure, this, character.ID });
return false;
}
item.CreateServerEvent(this);
}
#else
if (GameMain.Client == null && (CurrentFixer != character || currentFixerAction != action) && !CheckCharacterSuccess(character)) { return false; }
#endif
CurrentFixer = character;
CurrentFixerAction = action;
@@ -0,0 +1,252 @@
using Barotrauma.Networking;
using FarseerPhysics;
using FarseerPhysics.Dynamics;
using Microsoft.Xna.Framework;
using System;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Items.Components
{
partial class Rope : ItemComponent, IServerSerializable
{
private Item source, target;
private float snapTimer;
private const float SnapAnimDuration = 1.0f;
private float raycastTimer;
private const float RayCastInterval = 0.2f;
[Serialize(0.0f, false, description: "How much force is applied to pull the projectile the rope is attached to.")]
public float ProjectilePullForce
{
get;
set;
}
[Serialize(0.0f, false, description: "How much force is applied to pull the target the rope is attached to.")]
public float TargetPullForce
{
get;
set;
}
[Serialize(0.0f, false, description: "How much force is applied to pull the source the rope is attached to.")]
public float SourcePullForce
{
get;
set;
}
[Serialize(1000.0f, false, description: "How far the source item can be from the projectile until the rope breaks.")]
public float MaxLength
{
get;
set;
}
[Serialize(true, false, description: "Should the rope snap when it collides with a structure/submarine (if not, it will just go through it).")]
public bool SnapOnCollision
{
get;
set;
}
private bool snapped;
public bool Snapped
{
get { return snapped; }
set
{
if (snapped == value) { return; }
if (GameMain.NetworkMember != null)
{
if (GameMain.NetworkMember.IsClient)
{
return;
}
else
{
#if SERVER
item.CreateServerEvent(this);
#endif
}
}
snapped = value;
}
}
public Rope(Item item, XElement element) : base(item, element)
{
InitProjSpecific(element);
}
partial void InitProjSpecific(XElement element);
public void Attach(Item source, Item target)
{
System.Diagnostics.Debug.Assert(source != null);
System.Diagnostics.Debug.Assert(target != null);
this.source = source;
this.target = target;
ApplyStatusEffects(ActionType.OnUse, 1.0f, worldPosition: item.WorldPosition);
IsActive = true;
}
public override void Update(float deltaTime, Camera cam)
{
if (source == null || source.Removed || target == null || target.Removed)
{
IsActive = false;
return;
}
if (Snapped)
{
snapTimer += deltaTime;
if (snapTimer >= SnapAnimDuration)
{
IsActive = false;
}
return;
}
Vector2 diff = target.WorldPosition - source.WorldPosition;
if (diff.LengthSquared() > MaxLength * MaxLength)
{
Snapped = true;
return;
}
if (SnapOnCollision)
{
raycastTimer += deltaTime;
if (raycastTimer > RayCastInterval)
{
if (Submarine.PickBody(ConvertUnits.ToSimUnits(source.WorldPosition), ConvertUnits.ToSimUnits(target.WorldPosition),
collisionCategory: Physics.CollisionLevel | Physics.CollisionWall,
customPredicate: (Fixture f) =>
{
var projectile = target?.GetComponent<Projectile>();
if (projectile != null)
{
foreach (Body body in projectile.Hits)
{
Submarine alreadyHitSub = null;
if (body.UserData is Structure hitStructure)
{
alreadyHitSub = hitStructure.Submarine;
}
else if (body.UserData is Submarine hitSub)
{
alreadyHitSub = hitSub;
}
if (alreadyHitSub != null)
{
if (f.Body?.UserData is MapEntity me && me.Submarine == alreadyHitSub) { return false; }
if (f.Body?.UserData as Submarine == alreadyHitSub) { return false; }
}
}
}
Submarine targetSub = target?.GetComponent<Projectile>()?.StickTarget?.UserData as Submarine ?? target.Submarine;
if (f.Body?.UserData is MapEntity mapEntity && mapEntity.Submarine != null)
{
if (mapEntity.Submarine == targetSub || mapEntity.Submarine == source.Submarine)
{
return false;
}
}
else if (f.Body?.UserData is Submarine sub)
{
if (sub == targetSub || sub == source.Submarine)
{
return false;
}
}
return true;
}) != null)
{
Snapped = true;
return;
}
raycastTimer = 0.0f;
}
}
Vector2 forceDir = diff;
if (forceDir.LengthSquared() > 0.01f)
{
forceDir = Vector2.Normalize(forceDir);
}
if (Math.Abs(ProjectilePullForce) > 0.001f)
{
var projectile = target.GetComponent<Projectile>();
projectile?.Item?.body?.ApplyForce(-forceDir * ProjectilePullForce);
}
if (Math.Abs(SourcePullForce) > 0.001f)
{
var sourceBody = GetBodyToPull(source);
if (sourceBody != null)
{
sourceBody.ApplyForce(forceDir * SourcePullForce);
}
}
if (Math.Abs(TargetPullForce) > 0.001f)
{
var targetBody = GetBodyToPull(target);
if (targetBody != null)
{
targetBody.ApplyForce(-forceDir * TargetPullForce);
}
}
}
public override void UpdateBroken(float deltaTime, Camera cam)
{
base.UpdateBroken(deltaTime, cam);
if (Snapped)
{
snapTimer += deltaTime;
if (snapTimer >= SnapAnimDuration)
{
IsActive = false;
}
}
}
private PhysicsBody GetBodyToPull(Item target)
{
if (target.ParentInventory is CharacterInventory characterInventory &&
characterInventory.Owner is Character ownerCharacter)
{
if (ownerCharacter.Removed) { return null; }
return ownerCharacter.AnimController.Collider;
}
var projectile = target.GetComponent<Projectile>();
if (projectile != null)
{
if (projectile.StickTarget?.UserData is Structure structure)
{
return structure.Submarine?.PhysicsBody;
}
else if (projectile.StickTarget?.UserData is Submarine sub)
{
return sub?.PhysicsBody;
}
else if (projectile.StickTarget?.UserData is Character character)
{
return character.AnimController.Collider;
}
return null;
}
if (target.body != null) { return target.body; }
return null;
}
}
}
@@ -88,7 +88,7 @@ namespace Barotrauma.Items.Components
{
foreach (XElement subElement in item.Prefab.ConfigElement.Elements())
{
if (subElement.Name.ToString().ToLowerInvariant() != "connectionpanel") { continue; }
if (!subElement.Name.ToString().Equals("connectionpanel", StringComparison.OrdinalIgnoreCase)) { continue; }
foreach (XElement connectionElement in subElement.Elements())
{
@@ -79,6 +79,12 @@ namespace Barotrauma.Items.Components
Wire wire = wireItem.GetComponent<Wire>();
if (wire != null)
{
if (Item.ItemList.Any(it => it != item && (it.GetComponent<ConnectionPanel>()?.DisconnectedWires.Contains(wire) ?? false)))
{
if (wire.Item.body != null) { wire.Item.body.Enabled = false; }
wire.IsActive = false;
wire.UpdateSections();
}
DisconnectedWires.Add(wire);
base.IsActive = true;
}
@@ -197,7 +203,7 @@ namespace Barotrauma.Items.Components
float degreeOfSuccess = DegreeOfSuccess(character);
if (Rand.Range(0.0f, 0.5f) < degreeOfSuccess) { return true; }
item.ApplyStatusEffects(ActionType.OnFailure, 1.0f, character);
ApplyStatusEffects(ActionType.OnFailure, 1.0f, character);
return false;
}
@@ -12,6 +12,7 @@ namespace Barotrauma.Items.Components
public bool ContinuousSignal;
public bool State;
public string ConnectionName;
public string PropertyName;
public Connection Connection;
[Serialize("", false, translationTextTag: "Label.", description: "The text displayed on this button/tickbox."), Editable]
public string Label { get; set; }
@@ -28,11 +29,12 @@ namespace Barotrauma.Items.Components
{
Label = element.GetAttributeString("text", "");
ConnectionName = element.GetAttributeString("connection", "");
PropertyName = element.GetAttributeString("propertyname", "").ToLowerInvariant();
Signal = element.GetAttributeString("signal", "1");
foreach (XElement subElement in element.Elements())
{
if (subElement.Name.ToString().ToLowerInvariant() == "statuseffect")
if (subElement.Name.ToString().Equals("statuseffect", System.StringComparison.OrdinalIgnoreCase))
{
StatusEffects.Add(StatusEffect.Load(subElement, parentDebugName: "custom interface element (label " + Label + ")"));
}
@@ -89,6 +91,7 @@ namespace Barotrauma.Items.Components
switch (subElement.Name.ToString().ToLowerInvariant())
{
case "button":
case "textbox":
var button = new CustomInterfaceElement(subElement)
{
ContinuousSignal = false
@@ -106,7 +109,7 @@ namespace Barotrauma.Items.Components
};
if (string.IsNullOrEmpty(tickBox.Label))
{
tickBox.Label = "Signal out " + customInterfaceElementList.Count(e => !e.ContinuousSignal);
tickBox.Label = "Signal out " + customInterfaceElementList.Count(e => e.ContinuousSignal);
}
customInterfaceElementList.Add(tickBox);
break;
@@ -168,6 +171,18 @@ namespace Barotrauma.Items.Components
tickBoxElement.State = state;
}
private void TextChanged(CustomInterfaceElement textElement, string text)
{
textElement.Signal = text;
foreach (ISerializableEntity e in item.AllPropertyObjects)
{
if (e.SerializableProperties.ContainsKey(textElement.PropertyName))
{
e.SerializableProperties[textElement.PropertyName].TrySetValue(e, text);
}
}
}
public override void Update(float deltaTime, Camera cam)
{
UpdateProjSpecific();
@@ -21,7 +21,7 @@ namespace Barotrauma.Items.Components
private float blinkTimer;
private bool itemLoaded;
private double lastToggleSignalTime;
public PhysicsBody ParentBody;
@@ -78,9 +78,7 @@ namespace Barotrauma.Items.Components
if (IsActive == value) { return; }
IsActive = value;
#if SERVER
if (GameMain.Server != null && itemLoaded) { item.CreateServerEvent(this); }
#endif
OnStateChanged();
}
}
@@ -117,6 +115,13 @@ namespace Barotrauma.Items.Components
}
}
[Serialize(false, false, description: "If enabled, the component will ignore continuous signals received in the toggle input (i.e. a continuous signal will only toggle it once).")]
public bool IgnoreContinuousToggle
{
get;
set;
}
public override void Move(Vector2 amount)
{
#if CLIENT
@@ -158,14 +163,7 @@ namespace Barotrauma.Items.Components
IsActive = IsOn;
item.AddTag("light");
}
public override void OnItemLoaded()
{
base.OnItemLoaded();
itemLoaded = true;
SetLightSourceState(IsActive, lightBrightness);
}
public override void Update(float deltaTime, Camera cam)
{
if (item.AiTarget != null)
@@ -249,15 +247,21 @@ namespace Barotrauma.Items.Components
return true;
}
partial void OnStateChanged();
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power = 0.0f, float signalStrength = 1.0f)
{
switch (connection.Name)
{
case "toggle":
IsActive = !IsActive;
if (IgnoreContinuousToggle && lastToggleSignalTime < Timing.TotalTime - 0.1)
{
IsOn = !IsOn;
}
lastToggleSignalTime = Timing.TotalTime;
break;
case "set_state":
IsActive = (signal != "0");
IsOn = signal != "0";
break;
case "set_color":
LightColor = XMLExtensions.ParseColor(signal, false);
@@ -265,20 +269,14 @@ namespace Barotrauma.Items.Components
}
}
public void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null)
{
msg.Write(IsOn);
}
private void UpdateAITarget(AITarget target)
{
target.Enabled = IsActive;
if (!IsActive) { return; }
if (target.MaxSightRange <= 0)
{
target.MaxSightRange = Range * 5;
}
target.SightRange = target.MaxSightRange * lightBrightness;
target.SightRange = Math.Max(target.SightRange, target.MaxSightRange * lightBrightness);
}
partial void SetLightSourceState(bool enabled, float brightness);
@@ -39,6 +39,14 @@ namespace Barotrauma.Items.Components
}
}
[Serialize(false, false, description: "Can the component communicate with wifi components in another team's submarine (e.g. enemy sub in Combat missions, respawn shuttle). Needs to be enabled on both the component transmitting the signal and the component receiving it.")]
public bool AllowCrossTeamCommunication
{
get;
set;
}
[Editable, Serialize(false, false, description: "If enabled, any signals received from another chat-linked wifi component are displayed " +
"as chat messages in the chatbox of the player holding the item.")]
public bool LinkToChat
@@ -84,7 +92,7 @@ namespace Barotrauma.Items.Components
{
if (sender == null || sender.channel != channel) { return false; }
if (sender.TeamID != TeamID)
if (sender.TeamID != TeamID && !AllowCrossTeamCommunication)
{
return false;
}
@@ -34,6 +34,8 @@ namespace Barotrauma.Items.Components
private int failedLaunchAttempts;
private readonly List<Item> activeProjectiles = new List<Item>();
private Character user;
[Serialize("0,0", false, description: "The position of the barrel relative to the upper left corner of the base sprite (in pixels).")]
@@ -72,6 +74,20 @@ namespace Barotrauma.Items.Components
set { reloadTime = value; }
}
[Serialize(1, false, description: "How projectiles the weapon launches when fired once.")]
public int ProjectileCount
{
get;
set;
}
[Serialize(false, false, description: "Can the turret be fired without projectiles (causing it just to execute the OnUse effects and the firing animation without actually firing anything).")]
public bool LaunchWithoutProjectile
{
get;
set;
}
[Editable, Serialize("0.0,0.0", true, description: "The range at which the barrel can rotate. TODO")]
public Vector2 RotationLimits
{
@@ -95,6 +111,13 @@ namespace Barotrauma.Items.Components
}
}
[Serialize(0.0f, false, description: "Random spread applied to the firing angle of the projectiles (in degrees).")]
public float Spread
{
get;
set;
}
[Editable(0.0f, 1000.0f, DecimalCount = 2),
Serialize(5.0f, false, description: "How much torque is applied to rotate the barrel when the item is used by a character"
+ " with insufficient skills to operate it. Higher values make the barrel rotate faster.")]
@@ -155,7 +178,21 @@ namespace Barotrauma.Items.Components
UpdateTransformedBarrelPos();
}
}
[Serialize(3000.0f, true, description: "How close to a target the turret has to be for an AI character to fire it.")]
public float AIRange
{
get;
set;
}
[Serialize(-1, true, description: "The turret won't fire additional projectiles if the number of previously fired, still active projectiles reaches this limit. If set to -1, there is no limit to the number of projectiles.")]
public int MaxActiveProjectiles
{
get;
set;
}
public Turret(Item item, XElement element)
: base(item, element)
{
@@ -213,13 +250,13 @@ namespace Barotrauma.Items.Components
{
this.cam = cam;
if (reload > 0.0f) reload -= deltaTime;
if (reload > 0.0f) { reload -= deltaTime; }
ApplyStatusEffects(ActionType.OnActive, deltaTime, null);
UpdateProjSpecific(deltaTime);
if (minRotation == maxRotation) return;
if (minRotation == maxRotation) { return; }
float targetMidDiff = MathHelper.WrapAngle(targetRotation - (minRotation + maxRotation) / 2.0f);
@@ -230,12 +267,19 @@ namespace Barotrauma.Items.Components
targetRotation = (targetMidDiff < 0.0f) ? minRotation : maxRotation;
}
float degreeOfSuccess = user == null ? 0.5f : DegreeOfSuccess(user);
if (degreeOfSuccess < 0.5f) degreeOfSuccess *= degreeOfSuccess; //the ease of aiming drops quickly with insufficient skill levels
float degreeOfSuccess = user == null ? 0.5f : DegreeOfSuccess(user);
if (degreeOfSuccess < 0.5f) { degreeOfSuccess *= degreeOfSuccess; } //the ease of aiming drops quickly with insufficient skill levels
float springStiffness = MathHelper.Lerp(SpringStiffnessLowSkill, SpringStiffnessHighSkill, degreeOfSuccess);
float springDamping = MathHelper.Lerp(SpringDampingLowSkill, SpringDampingHighSkill, degreeOfSuccess);
float rotationSpeed = MathHelper.Lerp(RotationSpeedLowSkill, RotationSpeedHighSkill, degreeOfSuccess);
if (user?.Info != null)
{
user.Info.IncreaseSkillLevel("weapons",
SkillSettings.Current.SkillIncreasePerSecondWhenOperatingTurret * deltaTime / Math.Max(user.GetSkillLevel("weapons"), 1.0f),
user.WorldPosition + Vector2.UnitY * 150.0f);
}
angularVelocity +=
(MathHelper.WrapAngle(targetRotation - rotation) * springStiffness - angularVelocity * springDamping) * deltaTime;
angularVelocity = MathHelper.Clamp(angularVelocity, -rotationSpeed, rotationSpeed);
@@ -265,95 +309,110 @@ namespace Barotrauma.Items.Components
public override bool Use(float deltaTime, Character character = null)
{
if (!characterUsable && character != null) return false;
if (!characterUsable && character != null) { return false; }
return TryLaunch(deltaTime, character);
}
private bool TryLaunch(float deltaTime, Character character = null)
private bool TryLaunch(float deltaTime, Character character = null, bool ignorePower = false)
{
#if CLIENT
if (GameMain.Client != null) return false;
#endif
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return false; }
if (reload > 0.0f) return false;
if (reload > 0.0f) { return false; }
if (GetAvailableBatteryPower() < powerConsumption)
if (MaxActiveProjectiles >= 0)
{
#if CLIENT
if (!flashLowPower && character != null && character == Character.Controlled)
activeProjectiles.RemoveAll(it => it.Removed);
if (activeProjectiles.Count >= MaxActiveProjectiles)
{
flashLowPower = true;
GUI.PlayUISound(GUISoundType.PickItemFail);
return false;
}
#endif
return false;
}
foreach (MapEntity e in item.linkedTo)
if (!ignorePower)
{
//use linked projectile containers in case they have to react to the turret being launched somehow
//(play a sound, spawn more projectiles)
if (!(e is Item linkedItem)) continue;
ItemContainer projectileContainer = linkedItem.GetComponent<ItemContainer>();
if (projectileContainer != null)
if (GetAvailableBatteryPower() < powerConsumption)
{
linkedItem.Use(deltaTime, null);
var repairable = linkedItem.GetComponent<Repairable>();
if (repairable != null)
{
repairable.LastActiveTime = (float)Timing.TotalTime + 1.0f;
}
}
}
var projectiles = GetLoadedProjectiles(true);
if (projectiles.Count == 0)
{
//coilguns spawns ammo in the ammo boxes with the OnUse statuseffect when the turret is launched,
//causing a one frame delay before the gun can be launched (or more in multiplayer where there may be a longer delay)
// -> attempt to launch the gun multiple times before showing the "no ammo" flash
failedLaunchAttempts++;
#if CLIENT
if (!flashNoAmmo && character != null && character == Character.Controlled && failedLaunchAttempts > 20)
{
flashNoAmmo = true;
failedLaunchAttempts = 0;
GUI.PlayUISound(GUISoundType.PickItemFail);
}
#endif
return false;
}
failedLaunchAttempts = 0;
var batteries = item.GetConnectedComponents<PowerContainer>();
float neededPower = powerConsumption;
while (neededPower > 0.0001f && batteries.Count > 0)
{
batteries.RemoveAll(b => b.Charge <= 0.0001f || b.MaxOutPut <= 0.0001f);
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)
{
neededPower -= takePower;
battery.Charge -= takePower / 3600.0f;
#if SERVER
if (GameMain.Server != null)
if (!flashLowPower && character != null && character == Character.Controlled)
{
battery.Item.CreateServerEvent(battery);
flashLowPower = true;
GUI.PlayUISound(GUISoundType.PickItemFail);
}
#endif
return false;
}
}
Launch(projectiles[0].Item, character);
Projectile launchedProjectile = null;
for (int i = 0; i < ProjectileCount; i++)
{
foreach (MapEntity e in item.linkedTo)
{
//use linked projectile containers in case they have to react to the turret being launched somehow
//(play a sound, spawn more projectiles)
if (!(e is Item linkedItem)) { continue; }
ItemContainer projectileContainer = linkedItem.GetComponent<ItemContainer>();
if (projectileContainer != null)
{
linkedItem.Use(deltaTime, null);
var repairable = linkedItem.GetComponent<Repairable>();
if (repairable != null)
{
repairable.LastActiveTime = (float)Timing.TotalTime + 1.0f;
}
}
}
var projectiles = GetLoadedProjectiles(true);
if (projectiles.Count == 0 && !LaunchWithoutProjectile)
{
//coilguns spawns ammo in the ammo boxes with the OnUse statuseffect when the turret is launched,
//causing a one frame delay before the gun can be launched (or more in multiplayer where there may be a longer delay)
// -> attempt to launch the gun multiple times before showing the "no ammo" flash
failedLaunchAttempts++;
#if CLIENT
if (!flashNoAmmo && character != null && character == Character.Controlled && failedLaunchAttempts > 20)
{
flashNoAmmo = true;
failedLaunchAttempts = 0;
GUI.PlayUISound(GUISoundType.PickItemFail);
}
#endif
return false;
}
failedLaunchAttempts = 0;
launchedProjectile = projectiles.FirstOrDefault();
if (!ignorePower)
{
var batteries = item.GetConnectedComponents<PowerContainer>();
float neededPower = powerConsumption;
while (neededPower > 0.0001f && batteries.Count > 0)
{
batteries.RemoveAll(b => b.Charge <= 0.0001f || b.MaxOutPut <= 0.0001f);
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)
{
neededPower -= takePower;
battery.Charge -= takePower / 3600.0f;
#if SERVER
battery.Item.CreateServerEvent(battery);
#endif
}
}
}
if (launchedProjectile != null || LaunchWithoutProjectile)
{
Launch(launchedProjectile?.Item, character);
}
}
#if SERVER
if (character != null)
if (character != null && launchedProjectile != null)
{
string msg = character.LogName + " launched " + item.Name + " (projectile: " + projectiles[0].Item.Name;
var containedItems = projectiles[0].Item.ContainedItems;
string msg = character.LogName + " launched " + item.Name + " (projectile: " + launchedProjectile.Item.Name;
var containedItems = launchedProjectile.Item.ContainedItems;
if (containedItems == null || !containedItems.Any())
{
msg += ")";
@@ -373,27 +432,36 @@ namespace Barotrauma.Items.Components
{
reload = reloadTime;
projectile.Drop(null);
projectile.body.Dir = 1.0f;
projectile.body.ResetDynamics();
projectile.body.Enabled = true;
projectile.SetTransform(ConvertUnits.ToSimUnits(new Vector2(item.WorldRect.X + transformedBarrelPos.X, item.WorldRect.Y - transformedBarrelPos.Y)), -rotation);
projectile.UpdateTransform();
projectile.Submarine = projectile.body.Submarine;
Projectile projectileComponent = projectile.GetComponent<Projectile>();
if (projectileComponent != null)
if (projectile != null)
{
projectileComponent.Use((float)Timing.Step);
projectileComponent.User = user;
}
activeProjectiles.Add(projectile);
projectile.Drop(null);
if (projectile.body != null)
{
projectile.body.Dir = 1.0f;
projectile.body.ResetDynamics();
projectile.body.Enabled = true;
}
if (projectile.Container != null) projectile.Container.RemoveContained(projectile);
float spread = MathHelper.ToRadians(Spread) * Rand.Range(-0.5f, 0.5f);
projectile.SetTransform(ConvertUnits.ToSimUnits(new Vector2(item.WorldRect.X + transformedBarrelPos.X, item.WorldRect.Y - transformedBarrelPos.Y)), -rotation + spread);
projectile.UpdateTransform();
projectile.Submarine = projectile.body?.Submarine;
Projectile projectileComponent = projectile.GetComponent<Projectile>();
if (projectileComponent != null)
{
projectileComponent.Use((float)Timing.Step);
projectile.GetComponent<Rope>()?.Attach(item, projectile);
projectileComponent.User = user;
}
if (projectile.Container != null) { projectile.Container.RemoveContained(projectile); }
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
{
GameMain.NetworkMember.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.ComponentState, item.GetComponentIndex(this), projectile });
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
{
GameMain.NetworkMember.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.ComponentState, item.GetComponentIndex(this), projectile });
}
}
ApplyStatusEffects(ActionType.OnUse, 1.0f, user: user);
@@ -402,6 +470,182 @@ namespace Barotrauma.Items.Components
partial void LaunchProjSpecific();
private float waitTimer;
private float disorderTimer;
private float prevTargetRotation;
private float updateTimer;
private bool updatePending;
public void ThalamusOperate(float deltaTime, bool targetHumans, bool targetOtherCreatures, bool targetSubmarines, bool ignoreDelay)
{
IsActive = true;
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient)
{
return;
}
if (updatePending)
{
if (updateTimer < 0.0f)
{
#if SERVER
item.CreateServerEvent(this);
#endif
prevTargetRotation = targetRotation;
updateTimer = 0.25f;
}
updateTimer -= deltaTime;
}
if (!ignoreDelay && waitTimer > 0)
{
waitTimer -= deltaTime;
return;
}
Submarine closestSub = null;
float maxDistance = 10000.0f;
float shootDistance = AIRange;
ISpatialEntity target = null;
float closestDist = shootDistance * shootDistance;
if (targetHumans || targetOtherCreatures)
{
foreach (var character in Character.CharacterList)
{
if (character == null || character.Removed || character.IsDead) { continue; }
if (character.Params.Group.Equals("thalamus", StringComparison.OrdinalIgnoreCase)) { continue; }
bool isHuman = character.IsHuman || character.Params.Group.Equals("human", StringComparison.OrdinalIgnoreCase);
if (isHuman)
{
if (!targetHumans)
{
// Don't target humans if not defined to.
continue;
}
}
else if (!targetOtherCreatures)
{
// Don't target other creatures if not defined to.
continue;
}
float dist = Vector2.DistanceSquared(character.WorldPosition, item.WorldPosition);
if (dist > closestDist) { continue; }
target = character;
closestDist = dist;
}
}
if (targetSubmarines)
{
if (target == null || target.Submarine != null)
{
closestDist = maxDistance * maxDistance;
foreach (Submarine sub in Submarine.Loaded)
{
if (sub.Info.Type != SubmarineInfo.SubmarineType.Player) { continue; }
float dist = Vector2.DistanceSquared(sub.WorldPosition, item.WorldPosition);
if (dist > closestDist) { continue; }
closestSub = sub;
closestDist = dist;
}
closestDist = shootDistance * shootDistance;
if (closestSub != null)
{
foreach (var hull in Hull.hullList)
{
if (!closestSub.IsEntityFoundOnThisSub(hull, true)) { continue; }
float dist = Vector2.DistanceSquared(hull.WorldPosition, item.WorldPosition);
if (dist > closestDist) { continue; }
target = hull;
closestDist = dist;
}
}
}
}
if (!ignoreDelay)
{
if (target == null)
{
// Random movement
waitTimer = Rand.Value(Rand.RandSync.Unsynced) < 0.98f ? 0f : Rand.Range(5f, 20f);
targetRotation = Rand.Range(minRotation, maxRotation);
updatePending = true;
return;
}
if (disorderTimer < 0)
{
// Random disorder
disorderTimer = Rand.Range(0f, 3f);
waitTimer = Rand.Range(0.25f, 1f);
targetRotation = MathUtils.WrapAngleTwoPi(targetRotation += Rand.Range(-1f, 1f));
updatePending = true;
return;
}
else
{
disorderTimer -= deltaTime;
}
}
if (target == null) { return; }
float angle = -MathUtils.VectorToAngle(target.WorldPosition - item.WorldPosition);
targetRotation = MathUtils.WrapAngleTwoPi(angle);
if (Math.Abs(targetRotation - prevTargetRotation) > 0.1f) { updatePending = true; }
if (target is Hull targetHull)
{
Vector2 barrelDir = new Vector2((float)Math.Cos(rotation), -(float)Math.Sin(rotation));
if (!MathUtils.GetLineRectangleIntersection(item.WorldPosition, item.WorldPosition + barrelDir * AIRange, targetHull.WorldRect, out _))
{
return;
}
}
else
{
float midRotation = (minRotation + maxRotation) / 2.0f;
while (midRotation - angle < -MathHelper.Pi) { angle -= MathHelper.TwoPi; }
while (midRotation - angle > MathHelper.Pi) { angle += MathHelper.TwoPi; }
if (angle < minRotation || angle > maxRotation) { return; }
float enemyAngle = MathUtils.VectorToAngle(target.WorldPosition - item.WorldPosition);
float turretAngle = -rotation;
if (Math.Abs(MathUtils.GetShortestAngle(enemyAngle, turretAngle)) > 0.15f) { return; }
}
Vector2 start = ConvertUnits.ToSimUnits(item.WorldPosition);
Vector2 end = ConvertUnits.ToSimUnits(target.WorldPosition);
if (target.Submarine != null)
{
start -= target.Submarine.SimPosition;
end -= target.Submarine.SimPosition;
}
var collisionCategories = Physics.CollisionWall | Physics.CollisionCharacter | Physics.CollisionItem | Physics.CollisionLevel;
var pickedBody = Submarine.PickBody(start, end, null, collisionCategories);
if (pickedBody == null) { return; }
Character targetCharacter = null;
if (pickedBody.UserData is Character c)
{
targetCharacter = c;
}
else if (pickedBody.UserData is Limb limb)
{
targetCharacter = limb.character;
}
if (targetCharacter != null)
{
if (targetCharacter.Params.Group.Equals("thalamus", StringComparison.OrdinalIgnoreCase))
{
// Don't shoot friendly characters
return;
}
}
else if (!(pickedBody.UserData is Structure) && !(pickedBody.UserData is Item))
{
// Hit something else than a wall or an item (probably a level wall)
return;
}
TryLaunch(deltaTime, ignorePower: true);
}
public override bool AIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective)
{
if (character.AIController.SelectedAiTarget?.Entity is Character previousTarget &&
@@ -483,7 +727,7 @@ namespace Barotrauma.Items.Components
//enough shells and power
Character closestEnemy = null;
float closestDist = 3000 * 3000;
float closestDist = AIRange * AIRange;
foreach (Character enemy in Character.CharacterList)
{
// Ignore dead, friendly, and those that are inside the same sub
@@ -547,7 +791,7 @@ namespace Barotrauma.Items.Components
return false;
}
if (objective.Option.ToLowerInvariant() == "fireatwill")
if (objective.Option.Equals("fireatwill", StringComparison.OrdinalIgnoreCase))
{
character?.Speak(TextManager.GetWithVariable("DialogFireTurret", "[itemname]", item.Name, true), null, 0.0f, "fireturret", 5.0f);
character.SetInput(InputType.Shoot, true, true);
@@ -556,19 +800,6 @@ namespace Barotrauma.Items.Components
return false;
}
private void GetAvailablePower(out float availableCharge, out float availableCapacity)
{
var batteries = item.GetConnectedComponents<PowerContainer>();
availableCharge = 0.0f;
availableCapacity = 0.0f;
foreach (PowerContainer battery in batteries)
{
availableCharge += battery.Charge;
availableCapacity += battery.Capacity;
}
}
protected override void RemoveComponentSpecific()
{
base.RemoveComponentSpecific();
@@ -591,7 +822,7 @@ namespace Barotrauma.Items.Components
foreach (MapEntity e in item.linkedTo)
{
if (e is Item projectileContainer) { CheckProjectileContainer(projectileContainer, projectiles, returnFirst); }
if (returnFirst && projectiles.Any()) return projectiles;
if (returnFirst && projectiles.Any()) { return projectiles; }
}
return projectiles;
@@ -600,27 +831,27 @@ namespace Barotrauma.Items.Components
private void CheckProjectileContainer(Item projectileContainer, List<Projectile> projectiles, bool returnFirst)
{
var containedItems = projectileContainer.ContainedItems;
if (containedItems == null) return;
if (containedItems == null) { return; }
foreach (Item containedItem in containedItems)
{
var projectileComponent = containedItem.GetComponent<Projectile>();
if (projectileComponent != null)
if (projectileComponent != null && projectileComponent.Item.body != null)
{
projectiles.Add(projectileComponent);
if (returnFirst) return;
if (returnFirst) { return; }
}
else
{
//check if the contained item is another itemcontainer with projectiles inside it
if (containedItem.ContainedItems == null) continue;
if (containedItem.ContainedItems == null) { continue; }
foreach (Item subContainedItem in containedItem.ContainedItems)
{
projectileComponent = subContainedItem.GetComponent<Projectile>();
if (projectileComponent != null)
if (projectileComponent != null && projectileComponent.Item.body != null)
{
projectiles.Add(projectileComponent);
if (returnFirst) return;
if (returnFirst) { return; }
}
}
}
@@ -694,7 +925,6 @@ namespace Barotrauma.Items.Components
TryLaunch((float)Timing.Step, sender);
}
break;
case "toggle":
case "toggle_light":
if (lightComponent != null)
{
@@ -706,8 +936,9 @@ namespace Barotrauma.Items.Components
public void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null)
{
Item item = (Item)extraData[2];
msg.Write(item.Removed ? (ushort)0 : item.ID);
Item item = extraData.Length > 2 ? (Item)extraData[2] : null;
msg.Write(item == null || item.Removed ? (ushort)0 : item.ID);
msg.WriteRangedSingle(MathHelper.Clamp(targetRotation, minRotation, maxRotation), minRotation, maxRotation, 8);
}
}
}
@@ -211,6 +211,7 @@ namespace Barotrauma.Items.Components
}
public bool AutoEquipWhenFull { get; private set; }
public bool DisplayContainedStatus { get; private set; }
public readonly int Variants;
@@ -264,6 +265,7 @@ namespace Barotrauma.Items.Components
limbType = new LimbType[spriteCount];
limb = new Limb[spriteCount];
AutoEquipWhenFull = element.GetAttributeBool("autoequipwhenfull", true);
DisplayContainedStatus = element.GetAttributeBool("displaycontainedstatus", false);
int i = 0;
foreach (XElement subElement in element.Elements())
{
@@ -284,7 +286,7 @@ namespace Barotrauma.Items.Components
foreach (XElement lightElement in subElement.Elements())
{
if (lightElement.Name.ToString().ToLowerInvariant() != "lightcomponent") continue;
if (!lightElement.Name.ToString().Equals("lightcomponent", StringComparison.OrdinalIgnoreCase)) { continue; }
wearableSprites[i].LightComponent = new LightComponent(item, lightElement)
{
Parent = this