(5a377a8ee) Unstable v0.9.1000.0

This commit is contained in:
Juan Pablo Arce
2020-05-13 12:55:42 -03:00
parent b143329701
commit a1ca41aa5d
426 changed files with 14384 additions and 5708 deletions
@@ -5,7 +5,7 @@ using FarseerPhysics.Dynamics.Joints;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.IO;
using Barotrauma.IO;
using System.Linq;
using System.Xml.Linq;
@@ -29,6 +29,7 @@ namespace Barotrauma.Items.Components
private Door door;
private Body[] bodies;
private Fixture outsideBlocker;
private Body doorBody;
private bool docked;
@@ -58,7 +59,7 @@ 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).")]
[Editable, Serialize(false, true, description: "If set to true, this docking port is used when spawning the submarine docked to an outpost (if possible).")]
public bool MainDockingPort
{
get;
@@ -113,6 +114,12 @@ namespace Barotrauma.Items.Components
{
if (DockingTarget != null)
{
if (IsHorizontal)
{
DockingDir = 0;
DockingDir = GetDir(DockingTarget);
DockingTarget.DockingDir = -DockingDir;
}
if (joint != null)
{
CreateJoint(joint is WeldJoint);
@@ -259,6 +266,12 @@ namespace Barotrauma.Items.Components
{
item.CreateServerEvent(this);
}
#else
if (GameMain.Client != null && GameMain.Client.MidRoundSyncing &&
(item.Submarine == Submarine.MainSub || DockingTarget.item.Submarine == Submarine.MainSub))
{
Screen.Selected.Cam.Position = Submarine.MainSub.WorldPosition;
}
#endif
}
@@ -461,6 +474,12 @@ namespace Barotrauma.Items.Components
}
}
if (leftSubRightSide == int.MinValue || rightSubLeftSide == int.MaxValue)
{
DebugConsole.NewMessage("Creating hulls between docking ports failed. Could not find a hull next to the docking port.");
return;
}
//expand left hull to the rightmost hull of the sub at the left side
//(unless the difference is more than 100 units - if the distance is very large
//there's something wrong with the positioning of the docking ports or submarine hulls)
@@ -469,7 +488,8 @@ namespace Barotrauma.Items.Components
{
if (leftHullDiff > 100)
{
DebugConsole.ThrowError("Creating hulls between docking ports failed. The leftmost docking port seems to be very far from any hulls in the left-side submarine.");
DebugConsole.NewMessage("Creating hulls between docking ports failed. The leftmost docking port seems to be very far from any hulls in the left-side submarine.");
return;
}
else
{
@@ -483,7 +503,8 @@ namespace Barotrauma.Items.Components
{
if (rightHullDiff > 100)
{
DebugConsole.ThrowError("Creating hulls between docking ports failed. The rightmost docking port seems to be very far from any hulls in the right-side submarine.");
DebugConsole.NewMessage("Creating hulls between docking ports failed. The rightmost docking port seems to be very far from any hulls in the right-side submarine.");
return;
}
else
{
@@ -506,6 +527,16 @@ namespace Barotrauma.Items.Components
}
}
if (rightHullDiff <= 100 && hulls[0].Submarine != null)
{
outsideBlocker = hulls[0].Submarine.PhysicsBody.FarseerBody.CreateRectangle(
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));
outsideBlocker.UserData = this;
}
gap = new Gap(new Rectangle(hullRects[0].Right - 2, hullRects[0].Y, 4, hullRects[0].Height), true, subs[0]);
}
else
@@ -540,6 +571,12 @@ namespace Barotrauma.Items.Components
}
}
if (upperSubBottom == int.MaxValue || lowerSubTop == int.MinValue)
{
DebugConsole.NewMessage("Creating hulls between docking ports failed. Could not find a hull next to the docking port.");
return;
}
//expand lower hull to the topmost hull of the lower sub
//(unless the difference is more than 100 units - if the distance is very large
//there's something wrong with the positioning of the docking ports or submarine hulls)
@@ -548,7 +585,8 @@ namespace Barotrauma.Items.Components
{
if (lowerHullDiff > 100)
{
DebugConsole.ThrowError("Creating hulls between docking ports failed. The lower docking port seems to be very far from any hulls in the lower submarine.");
DebugConsole.NewMessage("Creating hulls between docking ports failed. The lower docking port seems to be very far from any hulls in the lower submarine.");
return;
}
else
{
@@ -561,7 +599,8 @@ namespace Barotrauma.Items.Components
{
if (upperHullDiff > 100)
{
DebugConsole.ThrowError("Creating hulls between docking ports failed. The upper docking port seems to be very far from any hulls in the upper submarine.");
DebugConsole.NewMessage("Creating hulls between docking ports failed. The upper docking port seems to be very far from any hulls in the upper submarine.");
return;
}
else
{
@@ -575,7 +614,8 @@ namespace Barotrauma.Items.Components
int midHullDiff = ((hullRects[1].Y - hullRects[1].Height) - hullRects[0].Y) + 2;
if (midHullDiff > 100)
{
DebugConsole.ThrowError("Creating hulls between docking ports failed. The upper hull seems to be very far from the lower hull.");
DebugConsole.NewMessage("Creating hulls between docking ports failed. The upper hull seems to be very far from the lower hull.");
return;
}
else if (midHullDiff > 0)
{
@@ -584,15 +624,33 @@ namespace Barotrauma.Items.Components
hullRects[1].Height += midHullDiff / 2 + 1;
}
for (int i = 0; i < 2; i++)
{
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].AddToGrid(subs[i]);
hulls[i].FreeID();
for (int j = 0; j < 2; j++)
{
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)));
}
}
gap = new Gap(new Rectangle(hullRects[0].X, hullRects[0].Y+2, hullRects[0].Width, 4), false, subs[0]);
if (midHullDiff <= 100 && hulls[0].Submarine != null)
{
outsideBlocker = hulls[0].Submarine.PhysicsBody.FarseerBody.CreateRectangle(
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));
outsideBlocker.UserData = this;
}
gap = new Gap(new Rectangle(hullRects[0].X, hullRects[0].Y + 2, hullRects[0].Width, 4), false, subs[0]);
}
LinkHullsToGaps();
@@ -609,7 +667,7 @@ namespace Barotrauma.Items.Components
foreach (Body body in bodies)
{
if (body == null) continue;
if (body == null) { continue; }
body.BodyType = BodyType.Static;
body.Friction = 0.5f;
@@ -769,6 +827,9 @@ namespace Barotrauma.Items.Components
bodies = null;
}
outsideBlocker?.Body.Remove(outsideBlocker);
outsideBlocker = null;
Item.Submarine.EnableObstructedWaypoints();
obstructedWayPointsDisabled = false;
@@ -880,12 +941,12 @@ namespace Barotrauma.Items.Components
float closestDist = 30.0f * 30.0f;
foreach (Item it in Item.ItemList)
{
if (it.Submarine != item.Submarine) continue;
if (it.Submarine != item.Submarine) { continue; }
var doorComponent = it.GetComponent<Door>();
if (doorComponent == null) continue;
if (doorComponent == null || doorComponent.IsHorizontal == IsHorizontal) { continue; }
float distSqr = Vector2.Distance(item.Position, it.Position);
float distSqr = Vector2.DistanceSquared(item.Position, it.Position);
if (distSqr < closestDist)
{
door = doorComponent;
@@ -958,12 +1019,12 @@ namespace Barotrauma.Items.Components
if (docked)
{
if (item.Submarine != null && DockingTarget?.item?.Submarine != null)
GameServer.Log(sender.LogName + " docked " + item.Submarine.Info.Name + " to " + DockingTarget.item.Submarine.Info.Name, ServerLog.MessageType.ItemInteraction);
GameServer.Log(GameServer.CharacterLogName(sender) + " 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.Info.Name + " from " + prevDockingTarget.item.Submarine.Info.Name, ServerLog.MessageType.ItemInteraction);
GameServer.Log(GameServer.CharacterLogName(sender) + " undocked " + item.Submarine.Info.Name + " from " + prevDockingTarget.item.Submarine.Info.Name, ServerLog.MessageType.ItemInteraction);
}
}
#endif
@@ -4,7 +4,7 @@ using FarseerPhysics.Dynamics;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.IO;
using Barotrauma.IO;
using System.Linq;
using System.Xml.Linq;
#if CLIENT
@@ -38,12 +38,17 @@ namespace Barotrauma.Items.Components
}
}
//how much "less stuck" partially doors get when opened
const float StuckReductionOnOpen = 30.0f;
private float resetPredictionTimer;
private float toggleCooldownTimer;
private Character lastUser;
private float damageSoundCooldown;
private double lastBrokenTime;
private Rectangle doorRect;
private bool isBroken;
@@ -53,7 +58,7 @@ namespace Barotrauma.Items.Components
get { return isBroken; }
set
{
if (isBroken == value) return;
if (isBroken == value) { return; }
isBroken = value;
if (isBroken)
{
@@ -63,6 +68,9 @@ namespace Barotrauma.Items.Components
{
EnableBody();
}
#if SERVER
item.CreateServerEvent(this);
#endif
}
}
@@ -85,7 +93,7 @@ namespace Barotrauma.Items.Components
if (isOpen || isBroken || !CanBeWelded) return;
stuck = MathHelper.Clamp(value, 0.0f, 100.0f);
if (stuck <= 0.0f) { IsStuck = false; }
if (stuck >= 100.0f) { IsStuck = true; }
if (stuck >= 99.0f) { IsStuck = true; }
}
}
@@ -203,10 +211,16 @@ namespace Barotrauma.Items.Components
break;
}
}
IsActive = true;
}
public override void OnItemLoaded()
{
//do this here because the scale of the item might not be set to the final value yet in the constructor
doorRect = new Rectangle(
item.Rect.Center.X - (int)(doorSprite.size.X / 2 * item.Scale),
item.Rect.Y - item.Rect.Height/2 + (int)(doorSprite.size.Y / 2.0f * item.Scale),
item.Rect.Y - item.Rect.Height / 2 + (int)(doorSprite.size.Y / 2.0f * item.Scale),
(int)(doorSprite.size.X * item.Scale),
(int)(doorSprite.size.Y * item.Scale));
@@ -224,8 +238,6 @@ namespace Barotrauma.Items.Components
Body.SetTransformIgnoreContacts(
ConvertUnits.ToSimUnits(new Vector2(doorRect.Center.X, doorRect.Y - doorRect.Height / 2)),
0.0f);
IsActive = true;
}
public override void Move(Vector2 amount)
@@ -295,6 +307,7 @@ namespace Barotrauma.Items.Components
PickingTime = 0;
ToggleState(ActionType.OnUse, character);
PickingTime = originalPickingTime;
StopPicking(picker);
}
#if CLIENT
else if (hasRequiredItems && character != null && character == Character.Controlled)
@@ -313,8 +326,9 @@ namespace Barotrauma.Items.Components
if (isBroken)
{
lastBrokenTime = Timing.TotalTime;
//the door has to be restored to 50% health before collision detection on the body is re-enabled
if (item.ConditionPercentage > 50.0f)
if (item.ConditionPercentage > 50.0f && (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer))
{
IsBroken = false;
}
@@ -363,7 +377,10 @@ namespace Barotrauma.Items.Components
public override void UpdateBroken(float deltaTime, Camera cam)
{
base.UpdateBroken(deltaTime, cam);
IsBroken = true;
if (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer)
{
IsBroken = true;
}
}
private void EnableBody()
@@ -502,6 +519,7 @@ namespace Barotrauma.Items.Components
foreach (Limb limb in c.AnimController.Limbs)
{
if (limb.IsSevered) { continue; }
if (PushBodyOutOfDoorway(c, limb.body, dir, simPos, simSize) && damageSoundCooldown <= 0.0f)
{
#if CLIENT
@@ -564,7 +582,12 @@ namespace Barotrauma.Items.Components
body.ApplyLinearImpulse(new Vector2(dir * 2.0f, isOpen ? 0.0f : -1.0f), maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
}
c.SetStun(0.2f);
//don't stun if the door was broken a moment ago
//otherwise enabling the door's collider and pushing the character away will interrupt repairing
if (lastBrokenTime < Timing.TotalTime - 1.0f)
{
c.SetStun(0.2f);
}
return true;
}
@@ -594,7 +617,7 @@ namespace Barotrauma.Items.Components
#if SERVER
if (sender != null && wasOpen != isOpen)
{
GameServer.Log(sender.LogName + (isOpen ? " opened " : " closed ") + item.Name, ServerLog.MessageType.ItemInteraction);
GameServer.Log(GameServer.CharacterLogName(sender) + (isOpen ? " opened " : " closed ") + item.Name, ServerLog.MessageType.ItemInteraction);
}
#endif
}
@@ -1,13 +1,13 @@
using Microsoft.Xna.Framework;
using Barotrauma.Networking;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
namespace Barotrauma.Items.Components
{
partial class ElectricalDischarger : Powered
partial class ElectricalDischarger : Powered, IServerSerializable
{
private static readonly List<ElectricalDischarger> list = new List<ElectricalDischarger>();
public static IEnumerable<ElectricalDischarger> List
@@ -48,14 +48,14 @@ namespace Barotrauma.Items.Components
}
}
[Serialize(500.0f, true, description: "How far the discharge can travel from the item."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 5000.0f)]
[Serialize(500.0f, true, description: "How far the discharge can travel from the item.", alwaysUseInstanceValues: true), Editable(MinValueFloat = 0.0f, MaxValueFloat = 5000.0f)]
public float Range
{
get;
set;
}
[Serialize(25.0f, true, description: "How much further can the discharge be carried when moving across walls."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 1000.0f)]
[Serialize(25.0f, true, description: "How much further can the discharge be carried when moving across walls.", alwaysUseInstanceValues: true), Editable(MinValueFloat = 0.0f, MaxValueFloat = 1000.0f)]
public float RangeMultiplierInWalls
{
get;
@@ -115,10 +115,15 @@ namespace Barotrauma.Items.Components
//already active, do nothing
if (IsActive) { return false; }
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return false; }
CurrPowerConsumption = powerConsumption;
charging = true;
timer = Duration;
IsActive = true;
#if SERVER
if (GameMain.Server != null) { item.CreateServerEvent(this); }
#endif
return false;
}
@@ -150,14 +155,12 @@ namespace Barotrauma.Items.Components
neededPower -= takePower;
battery.Charge -= takePower / 3600.0f;
#if SERVER
if (GameMain.Server != null)
{
battery.Item.CreateServerEvent(battery);
}
if (GameMain.Server != null) { battery.Item.CreateServerEvent(battery); }
#endif
}
}
Discharge();
}
else if (Voltage > MinVoltage)
{
@@ -478,5 +481,10 @@ namespace Barotrauma.Items.Components
base.RemoveComponentSpecific();
list.Remove(this);
}
public void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null)
{
//no further data needed, the event just triggers the discharge
}
}
}
@@ -3,6 +3,7 @@ using FarseerPhysics;
using FarseerPhysics.Dynamics;
using FarseerPhysics.Dynamics.Contacts;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Xml.Linq;
@@ -309,8 +310,13 @@ namespace Barotrauma.Items.Components
{
picker = character;
if (character != null) item.Submarine = character.Submarine;
if (item.Removed)
{
DebugConsole.ThrowError($"Attempted to equip a removed item ({item.Name})\n" + Environment.StackTrace);
return;
}
if (character != null) { item.Submarine = character.Submarine; }
if (item.body == null)
{
if (body != null)
@@ -344,7 +350,7 @@ namespace Barotrauma.Items.Components
IsActive = true;
#if SERVER
if (!alreadyEquipped) GameServer.Log(character.LogName + " equipped " + item.Name, ServerLog.MessageType.ItemInteraction);
if (!alreadyEquipped) GameServer.Log(GameServer.CharacterLogName(character) + " equipped " + item.Name, ServerLog.MessageType.ItemInteraction);
#endif
}
}
@@ -355,7 +361,7 @@ namespace Barotrauma.Items.Components
picker.DeselectItem(item);
#if SERVER
GameServer.Log(character.LogName + " unequipped " + item.Name, ServerLog.MessageType.ItemInteraction);
GameServer.Log(GameServer.CharacterLogName(character) + " unequipped " + item.Name, ServerLog.MessageType.ItemInteraction);
#endif
item.body.PhysEnabled = true;
@@ -365,23 +371,30 @@ namespace Barotrauma.Items.Components
public bool CanBeAttached()
{
if (!attachable || !Reattachable) return false;
if (!attachable || !Reattachable) { return false; }
//can be attached anywhere in sub editor
if (Screen.Selected == GameMain.SubEditorScreen) return true;
if (Screen.Selected == GameMain.SubEditorScreen) { return true; }
//can be attached anywhere inside hulls
if (item.CurrentHull != null) return true;
if (item.CurrentHull != null) { return true; }
return Structure.GetAttachTarget(item.WorldPosition) != null;
}
public bool CanBeDeattached()
{
if (!attachable || !attached) return true;
if (!attachable || !attached) { return true; }
//allow deattaching everywhere in sub editor
if (Screen.Selected == GameMain.SubEditorScreen) return true;
if (Screen.Selected == GameMain.SubEditorScreen) { return true; }
//if the item has a connection panel and rewiring is disabled, don't allow deattaching
var connectionPanel = item.GetComponent<ConnectionPanel>();
if (connectionPanel != null && (connectionPanel.Locked || !(GameMain.NetworkMember?.ServerSettings?.AllowRewiring ?? true)))
{
return false;
}
//don't allow deattaching if part of a sub and outside hulls
return item.Submarine == null || item.CurrentHull != null;
@@ -389,12 +402,18 @@ namespace Barotrauma.Items.Components
public override bool Pick(Character picker)
{
if (item.Removed)
{
DebugConsole.ThrowError($"Attempted to pick up a removed item ({item.Name})\n" + Environment.StackTrace);
return false;
}
if (!attachable)
{
return base.Pick(picker);
}
if (!CanBeDeattached()) return false;
if (!CanBeDeattached()) { return false; }
if (Attached)
{
@@ -419,7 +438,7 @@ namespace Barotrauma.Items.Components
item.CreateServerEvent(this);
if (picker != null)
{
GameServer.Log(picker.LogName + " detached " + item.Name + " from a wall", ServerLog.MessageType.ItemInteraction);
GameServer.Log(GameServer.CharacterLogName(picker) + " detached " + item.Name + " from a wall", ServerLog.MessageType.ItemInteraction);
}
}
#endif
@@ -1,4 +1,5 @@
using FarseerPhysics;
using Barotrauma.Networking;
using FarseerPhysics;
using FarseerPhysics.Dynamics;
using FarseerPhysics.Dynamics.Contacts;
using Microsoft.Xna.Framework;
@@ -94,6 +95,7 @@ namespace Barotrauma.Items.Components
{
foreach (Limb l in character.AnimController.Limbs)
{
if (l.IsSevered) { continue; }
if (l.type == LimbType.LeftFoot || l.type == LimbType.LeftThigh || l.type == LimbType.LeftLeg) { continue; }
if (l.type == LimbType.Head || l.type == LimbType.Torso)
{
@@ -310,70 +312,7 @@ namespace Barotrauma.Items.Components
return false;
}
if (attack != null)
{
if (targetLimb == null && targetCharacter == null && targetStructure == null && (targetItem == null || ! targetItem.Prefab.DamagedByMeleeWeapons))
{
return false;
}
if (targetLimb != null)
{
targetLimb.character.LastDamageSource = item;
attack.DoDamageToLimb(User, targetLimb, item.WorldPosition, 1.0f);
}
else if (targetCharacter != null)
{
targetCharacter.LastDamageSource = item;
attack.DoDamage(User, targetCharacter, item.WorldPosition, 1.0f);
}
else if (targetStructure != null)
{
attack.DoDamage(User, targetStructure, item.WorldPosition, 1.0f);
}
else if (targetItem != null && targetItem.Prefab.DamagedByMeleeWeapons)
{
attack.DoDamage(User, targetItem, item.WorldPosition, 1.0f);
}
else
{
return false;
}
}
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return true; }
#if SERVER
if (GameMain.Server != null && targetCharacter != null) //TODO: Log structure hits
{
GameMain.Server.CreateEntityEvent(item, new object[]
{
Networking.NetEntityEvent.Type.ApplyStatusEffect,
ActionType.OnUse,
null, //itemcomponent
targetCharacter.ID, targetLimb
});
string logStr = picker?.LogName + " used " + item.Name;
if (item.ContainedItems != null && item.ContainedItems.Any())
{
logStr += " (" + string.Join(", ", item.ContainedItems.Select(i => i?.Name)) + ")";
}
logStr += " on " + targetCharacter.LogName + ".";
Networking.GameServer.Log(logStr, Networking.ServerLog.MessageType.Attack);
}
#endif
if (targetCharacter != null) //TODO: Allow OnUse to happen on structures too maybe??
{
ApplyStatusEffects(ActionType.OnUse, 1.0f, targetCharacter, targetLimb, user: User);
}
if (DeleteOnUse)
{
Entity.Spawner.AddToRemoveQueue(item);
}
impactQueue.Enqueue(f2);
return true;
}
@@ -414,7 +353,7 @@ namespace Barotrauma.Items.Components
if (targetStructure.Removed) { return; }
attack.DoDamage(User, targetStructure, item.WorldPosition, 1.0f);
}
else if (targetItem != null && targetItem.Prefab.DamagedByMeleeWeapons)
else if (targetItem != null && targetItem.Prefab.DamagedByMeleeWeapons && targetItem.Condition > 0)
{
if (targetItem.Removed) { return; }
attack.DoDamage(User, targetItem, item.WorldPosition, 1.0f);
@@ -65,7 +65,7 @@ namespace Barotrauma.Items.Components
if (PickingTime > 0.0f)
{
if (picker.PickingItem == null && PickingTime <= float.MaxValue)
if ((picker.PickingItem == null || picker.PickingItem == item) && PickingTime <= float.MaxValue)
{
#if SERVER
item.CreateServerEvent(this);
@@ -65,7 +65,7 @@ namespace Barotrauma.Items.Components
foreach (Limb limb in character.AnimController.Limbs)
{
if (limb.WearingItems.Find(w => w.WearableComponent.Item == this.item) == null) continue;
if (limb.WearingItems.Find(w => w.WearableComponent.Item == item) == null) { continue; }
limb.body.ApplyForce(propulsion, maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
}
@@ -90,6 +90,7 @@ namespace Barotrauma.Items.Components
return MathHelper.ToRadians(MathHelper.Lerp(Spread, UnskilledSpread, degreeOfFailure));
}
private readonly List<Body> limbBodies = new List<Body>();
public override bool Use(float deltaTime, Character character = null)
{
if (character == null || character.Removed) { return false; }
@@ -104,9 +105,10 @@ namespace Barotrauma.Items.Components
item.AiTarget.SightRange = item.AiTarget.MaxSightRange;
}
List<Body> limbBodies = new List<Body>();
limbBodies.Clear();
foreach (Limb l in character.AnimController.Limbs)
{
if (l.IsSevered) { continue; }
limbBodies.Add(l.body.FarseerBody);
}
@@ -22,6 +22,8 @@ namespace Barotrauma.Items.Components
private Vector2 debugRayStartPos, debugRayEndPos;
private readonly List<Body> ignoredBodies = new List<Body>();
[Serialize("Both", false, description: "Can the item be used in air, water or both.")]
public UseEnvironment UsableIn
{
@@ -114,8 +116,7 @@ namespace Barotrauma.Items.Components
}
}
item.IsShootable = true;
// TODO: should define this in xml if we have repair tools that don't require aim to use
item.RequireAimToUse = true;
item.RequireAimToUse = element.Parent.GetAttributeBool("requireaimtouse", true);
InitProjSpecific(element);
}
@@ -124,16 +125,17 @@ namespace Barotrauma.Items.Components
public override void Update(float deltaTime, Camera cam)
{
activeTimer -= deltaTime;
if (activeTimer <= 0.0f) IsActive = false;
if (activeTimer <= 0.0f) { IsActive = false; }
}
private List<Body> ignoredBodies = new List<Body>();
public override bool Use(float deltaTime, Character character = null)
{
if (character == null || character.Removed) return false;
if (item.RequireAimToUse && !character.IsKeyDown(InputType.Aim)) return false;
if (character != null)
{
if (item.RequireAimToUse && !character.IsKeyDown(InputType.Aim)) { return false; }
}
float degreeOfSuccess = DegreeOfSuccess(character);
float degreeOfSuccess = character == null ? 0.5f : DegreeOfSuccess(character);
if (Rand.Range(0.0f, 0.5f) > degreeOfSuccess)
{
@@ -187,12 +189,15 @@ namespace Barotrauma.Items.Components
(float)Math.Sin(angle)) * Range * item.body.Dir);
ignoredBodies.Clear();
foreach (Limb limb in character.AnimController.Limbs)
if (character != null)
{
if (Rand.Range(0.0f, 0.5f) > degreeOfSuccess) continue;
ignoredBodies.Add(limb.body.FarseerBody);
foreach (Limb limb in character.AnimController.Limbs)
{
if (Rand.Range(0.0f, 0.5f) > degreeOfSuccess) continue;
ignoredBodies.Add(limb.body.FarseerBody);
}
ignoredBodies.Add(character.AnimController.Collider.FarseerBody);
}
ignoredBodies.Add(character.AnimController.Collider.FarseerBody);
IsActive = true;
activeTimer = 0.1f;
@@ -200,7 +205,8 @@ namespace Barotrauma.Items.Components
debugRayStartPos = ConvertUnits.ToDisplayUnits(rayStart);
debugRayEndPos = ConvertUnits.ToDisplayUnits(rayEnd);
if (character.Submarine == null)
Submarine parentSub = character?.Submarine ?? item.Submarine;
if (parentSub == null)
{
foreach (Submarine sub in Submarine.Loaded)
{
@@ -216,7 +222,7 @@ namespace Barotrauma.Items.Components
}
else
{
Repair(rayStart - character.Submarine.SimPosition, rayEnd - character.Submarine.SimPosition, deltaTime, character, degreeOfSuccess, ignoredBodies);
Repair(rayStart - parentSub.SimPosition, rayEnd - parentSub.SimPosition, deltaTime, character, degreeOfSuccess, ignoredBodies);
}
UseProjSpecific(deltaTime, rayStart);
@@ -439,18 +445,7 @@ namespace Barotrauma.Items.Components
return true;
}
else if (targetBody.UserData is Item targetItem)
{
targetItem.IsHighlighted = true;
ApplyStatusEffectsOnTarget(user, deltaTime, ActionType.OnUse, targetItem.AllPropertyObjects);
if (targetItem.body != null && !MathUtils.NearlyEqual(TargetForce, 0.0f))
{
Vector2 dir = targetItem.WorldPosition - item.WorldPosition;
dir = dir.LengthSquared() < 0.0001f ? Vector2.UnitY : Vector2.Normalize(dir);
targetItem.body.ApplyForce(dir * TargetForce, maxVelocity: 10.0f);
}
{
var levelResource = targetItem.GetComponent<LevelResource>();
if (levelResource != null && levelResource.Attached &&
levelResource.requiredItems.Any() &&
@@ -464,7 +459,23 @@ namespace Barotrauma.Items.Components
levelResource.DeattachTimer / levelResource.DeattachDuration,
GUI.Style.Red, GUI.Style.Green);
#endif
return true;
}
if (!targetItem.Prefab.DamagedByRepairTools) { return false; }
if (item.GetComponent<Door>() == null && item.Condition <= 0) { return false; }
targetItem.IsHighlighted = true;
ApplyStatusEffectsOnTarget(user, deltaTime, ActionType.OnUse, targetItem.AllPropertyObjects);
if (targetItem.body != null && !MathUtils.NearlyEqual(TargetForce, 0.0f))
{
Vector2 dir = targetItem.WorldPosition - item.WorldPosition;
dir = dir.LengthSquared() < 0.0001f ? Vector2.UnitY : Vector2.Normalize(dir);
targetItem.body.ApplyForce(dir * TargetForce, maxVelocity: 10.0f);
}
FixItemProjSpecific(user, deltaTime, targetItem);
return true;
}
@@ -642,26 +653,26 @@ namespace Barotrauma.Items.Components
}
#if CLIENT
if (user == null) { return; }
// Hard-coded progress bars for welding doors stuck.
// A general purpose system could be better, but it would most likely require changes in the way we define the status effects in xml.
foreach (ISerializableEntity target in targets)
{
if (target is Door door)
if (!(target is Door door)) { continue; }
if (!door.CanBeWelded) { continue; }
for (int i = 0; i < effect.propertyNames.Length; i++)
{
if (!door.CanBeWelded) continue;
for (int i = 0; i < effect.propertyNames.Length; i++)
string propertyName = effect.propertyNames[i];
if (propertyName != "stuck") { continue; }
if (door.SerializableProperties == null || !door.SerializableProperties.TryGetValue(propertyName, out SerializableProperty property)) { continue; }
object value = property.GetValue(target);
if (door.Stuck > 0)
{
string propertyName = effect.propertyNames[i];
if (propertyName != "stuck") { continue; }
if (door.SerializableProperties == null || !door.SerializableProperties.TryGetValue(propertyName, out SerializableProperty property)) { continue; }
object value = property.GetValue(target);
if (door.Stuck > 0)
{
var progressBar = user.UpdateHUDProgressBar(door, door.Item.WorldPosition, door.Stuck / 100, Color.DarkGray * 0.5f, Color.White);
if (progressBar != null) { progressBar.Size = new Vector2(60.0f, 20.0f); }
}
var progressBar = user.UpdateHUDProgressBar(door, door.Item.WorldPosition, door.Stuck / 100, Color.DarkGray * 0.5f, Color.White);
if (progressBar != null) { progressBar.Size = new Vector2(60.0f, 20.0f); }
}
}
}
}
#endif
}
@@ -115,7 +115,7 @@ namespace Barotrauma.Items.Components
if (!MathUtils.IsValid(throwVector)) { throwVector = Vector2.UnitY; }
#if SERVER
GameServer.Log(picker.LogName + " threw " + item.Name, ServerLog.MessageType.ItemInteraction);
GameServer.Log(GameServer.CharacterLogName(picker) + " threw " + item.Name, ServerLog.MessageType.ItemInteraction);
#endif
Character thrower = picker;
item.Drop(thrower, createNetworkEvent: GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer);
@@ -421,7 +421,10 @@ namespace Barotrauma.Items.Components
case "activate":
case "use":
case "trigger_in":
item.Use(1.0f, sender);
if (signal != "0")
{
item.Use(1.0f, sender);
}
break;
case "toggle":
if (signal != "0")
@@ -734,12 +737,15 @@ namespace Barotrauma.Items.Components
public virtual void Load(XElement componentElement, bool usePrefabValues)
{
if (componentElement != null && !usePrefabValues)
if (componentElement != null)
{
foreach (XAttribute attribute in componentElement.Attributes())
{
if (!SerializableProperties.TryGetValue(attribute.Name.ToString().ToLowerInvariant(), out SerializableProperty property)) { continue; }
property.TrySetValue(this, attribute.Value);
if (property.OverridePrefabValues || !usePrefabValues)
{
property.TrySetValue(this, attribute.Value);
}
}
ParseMsg();
OverrideRequiredItems(componentElement);
@@ -908,49 +914,56 @@ namespace Barotrauma.Items.Components
#region AI related
protected const float AIUpdateInterval = 0.2f;
protected float aiUpdateTimer;
private int itemIndex;
private List<Item> ignoredContainers = new List<Item>();
private Character previousUser;
protected bool FindSuitableContainer(Character character, Func<Item, float> priority, out Item suitableContainer)
{
if (previousUser != character)
{
ignoredContainers.Clear();
previousUser = character;
}
suitableContainer = null;
if (character.FindItem(ref itemIndex, out Item targetContainer, ignoredItems: ignoredContainers, customPriorityFunction: priority))
if (character.AIController is HumanAIController aiController)
{
suitableContainer = targetContainer;
return true;
if (previousUser != character)
{
previousUser = character;
itemIndex = 0;
}
if (character.FindItem(ref itemIndex, out Item targetContainer, ignoredItems: aiController.IgnoredItems, customPriorityFunction: priority))
{
suitableContainer = targetContainer;
return true;
}
}
return false;
}
protected AIObjectiveContainItem AIContainItems<T>(ItemContainer container, Character character, AIObjective objective, int itemCount, bool equip, bool removeEmpty) where T : ItemComponent
{
var containObjective = new AIObjectiveContainItem(character, container.GetContainableItemIdentifiers.ToArray(), container, objective.objectiveManager)
AIObjectiveContainItem containObjective = null;
if (character.AIController is HumanAIController aiController)
{
targetItemCount = itemCount,
Equip = equip,
RemoveEmpty = removeEmpty,
GetItemPriority = i =>
containObjective = new AIObjectiveContainItem(character, container.GetContainableItemIdentifiers.ToArray(), container, objective.objectiveManager)
{
if (i.ParentInventory?.Owner is Item)
targetItemCount = itemCount,
Equip = equip,
RemoveEmpty = removeEmpty,
GetItemPriority = i =>
{
//don't take items from other items of the same type
if (((Item)i.ParentInventory.Owner).GetComponent<T>() != null)
if (i.ParentInventory?.Owner is Item)
{
return 0.0f;
//don't take items from other items of the same type
if (((Item)i.ParentInventory.Owner).GetComponent<T>() != null)
{
return 0.0f;
}
}
return 1.0f;
}
return 1.0f;
}
};
// TODO: are we sure that we want to abandon the objective here?
containObjective.Abandoned += () => objective.Abandon = true;
objective.AddSubObjective(containObjective);
};
containObjective.Abandoned += () =>
{
aiController.IgnoredItems.Add(container.Item);
};
objective.AddSubObjective(containObjective);
}
return containObjective;
}
@@ -959,68 +972,71 @@ namespace Barotrauma.Items.Components
/// </summary>
protected bool AIDecontainEmptyItems(Character character, AIObjective objective, bool equip, ItemContainer sourceContainer = null)
{
ItemContainer sourceC = sourceContainer ?? (item.OwnInventory?.Owner is Item it ? it.GetComponent<ItemContainer>() : null);
var containedItems = sourceContainer != null ? sourceContainer.Inventory.Items : item.OwnInventory.Items;
foreach (Item containedItem in containedItems)
if (character.AIController is HumanAIController aiController)
{
if (containedItem != null && containedItem.Condition <= 0.0f)
ItemContainer sourceC = sourceContainer ?? (item.OwnInventory?.Owner is Item it ? it.GetComponent<ItemContainer>() : null);
var containedItems = sourceContainer != null ? sourceContainer.Inventory.Items : item.OwnInventory.Items;
foreach (Item containedItem in containedItems)
{
if (FindSuitableContainer(character,
i =>
{
var container = i.GetComponent<ItemContainer>();
if (container == null) { return 0; }
if (container.Inventory.IsFull()) { return 0; }
if (containedItem != null && containedItem.Condition <= 0.0f)
{
if (FindSuitableContainer(character,
i =>
{
var container = i.GetComponent<ItemContainer>();
if (container == null) { return 0; }
if (container.Inventory.IsFull()) { return 0; }
// Ignore containers that are identical to the source container
if (sourceC != null && container.Item.Prefab == sourceC.Item.Prefab) { return 0; }
if (container.ShouldBeContained(containedItem, out bool isRestrictionsDefined))
{
if (isRestrictionsDefined)
if (container.ShouldBeContained(containedItem, out bool isRestrictionsDefined))
{
return 4;
}
else
{
if (containedItem.Prefab.IsContainerPreferred(container, out bool isPreferencesDefined, out bool isSecondary))
if (isRestrictionsDefined)
{
return isPreferencesDefined ? isSecondary ? 2 : 3 : 1;
return 4;
}
else
{
return isPreferencesDefined ? 0 : 1;
if (containedItem.Prefab.IsContainerPreferred(container, out bool isPreferencesDefined, out bool isSecondary))
{
return isPreferencesDefined ? isSecondary ? 2 : 3 : 1;
}
else
{
return isPreferencesDefined ? 0 : 1;
}
}
}
}
else
else
{
return 0;
}
}, out Item targetContainer))
{
var decontainObjective = new AIObjectiveDecontainItem(character, containedItem, objective.objectiveManager, sourceC, targetContainer?.GetComponent<ItemContainer>())
{
return 0;
}
}, out Item targetContainer))
{
var decontainObjective = new AIObjectiveDecontainItem(character, containedItem, objective.objectiveManager, sourceC, targetContainer?.GetComponent<ItemContainer>())
{
Equip = equip
};
decontainObjective.Abandoned += () =>
{
itemIndex = 0;
if (targetContainer != null)
{
ignoredContainers.Add(targetContainer);
}
};
decontainObjective.Completed += () =>
{
if (targetContainer == null)
Equip = equip
};
decontainObjective.Abandoned += () =>
{
itemIndex = 0;
}
};
objective.AddSubObjectiveInQueue(decontainObjective);
}
else
{
return false;
if (targetContainer != null)
{
aiController.IgnoredItems.Add(targetContainer);
}
};
decontainObjective.Completed += () =>
{
if (targetContainer == null)
{
itemIndex = 0;
}
};
objective.AddSubObjectiveInQueue(decontainObjective);
}
else
{
return false;
}
}
}
}
@@ -80,6 +80,13 @@ namespace Barotrauma.Items.Components
set { itemRotation = MathHelper.ToRadians(value); }
}
[Serialize("", false, description: "Specify an item for the container to spawn with.")]
public string SpawnWithId
{
get;
set;
}
public bool ShouldBeContained(string[] identifiersOrTags, out bool isRestrictionsDefined)
{
isRestrictionsDefined = containableRestrictions.Any();
@@ -143,7 +150,7 @@ namespace Barotrauma.Items.Components
}
//no need to Update() if this item has no statuseffects and no physics body
IsActive = itemsWithStatusEffects.Count > 0 || containedItem.body != null;
IsActive = itemsWithStatusEffects.Count > 0 || Inventory.Items.Any(it => it?.body != null);
}
public void OnItemRemoved(Item containedItem)
@@ -151,7 +158,7 @@ namespace Barotrauma.Items.Components
itemsWithStatusEffects.RemoveAll(i => i.First == containedItem);
//deactivate if the inventory is empty
IsActive = itemsWithStatusEffects.Count > 0 || containedItem.body != null;
IsActive = itemsWithStatusEffects.Count > 0 || Inventory.Items.Any(it => it?.body != null);
}
public bool CanBeContained(Item item)
@@ -200,6 +207,22 @@ namespace Barotrauma.Items.Components
}
}
public override void OnItemLoaded()
{
base.OnItemLoaded();
if (SpawnWithId.Length > 0)
{
ItemPrefab prefab = ItemPrefab.Prefabs.Find(m => m.Identifier == SpawnWithId);
if (prefab != null)
{
if (Inventory != null && Inventory.Items.Any(it => it == null))
{
Entity.Spawner?.AddToSpawnQueue(prefab, Inventory);
}
}
}
}
public override bool HasRequiredItems(Character character, bool addMessage, string msg = null)
{
return (!AccessOnlyWhenBroken || Item.Condition <= 0) && base.HasRequiredItems(character, addMessage, msg);
@@ -295,7 +318,7 @@ namespace Barotrauma.Items.Components
foreach (Item contained in Inventory.Items)
{
if (contained == null) continue;
if (contained == null) { continue; }
if (contained.body != null)
{
try
@@ -311,6 +334,7 @@ namespace Barotrauma.Items.Components
GameAnalyticsSDK.Net.EGAErrorSeverity.Error,
"SetTransformIgnoreContacts threw an exception in SetContainedItemPositions (" + e.Message + ")\n" + e.StackTrace);
}
contained.body.Submarine = item.Submarine;
}
contained.Rect =
@@ -39,8 +39,6 @@ namespace Barotrauma.Items.Components
private Item focusTarget;
private float targetRotation;
private bool state;
public Vector2 UserPos
{
get { return userPos; }
@@ -61,6 +59,18 @@ namespace Barotrauma.Items.Components
set;
}
[Editable, Serialize(false, false, description: "Whether the item is toggled on/off. Only valid if IsToggle is set to true.")]
public bool State
{
get;
set;
}
public bool ControlCharacterPose
{
get { return limbPositions.Count > 0; }
}
public Controller(Item item, XElement element)
: base(item, element)
{
@@ -99,7 +109,7 @@ namespace Barotrauma.Items.Components
if (IsToggle)
{
item.SendSignal(0, state ? "1" : "0", "signal_out", sender: null);
item.SendSignal(0, State ? "1" : "0", "signal_out", sender: null);
}
if (user == null
@@ -272,7 +282,7 @@ namespace Barotrauma.Items.Components
return true;
}
private Item GetFocusTarget()
public Item GetFocusTarget()
{
item.SendSignal(0, MathHelper.ToDegrees(targetRotation).ToString("G", CultureInfo.InvariantCulture), "position_out", user);
@@ -294,7 +304,7 @@ namespace Barotrauma.Items.Components
{
if (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer)
{
state = !state;
State = !State;
#if SERVER
item.CreateServerEvent(this);
#endif
@@ -113,7 +113,7 @@ namespace Barotrauma.Items.Components
if (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer)
{
if (targetItem.Prefab.DeconstructItems.Any())
if (targetItem.Prefab.AllowDeconstruct)
{
//drop all items that are inside the deconstructed item
foreach (ItemContainer ic in targetItem.GetComponents<ItemContainer>())
@@ -200,7 +200,7 @@ namespace Barotrauma.Items.Components
#if SERVER
if (user != null)
{
GameServer.Log(user.LogName + (IsActive ? " activated " : " deactivated ") + item.Name, ServerLog.MessageType.ItemInteraction);
GameServer.Log(GameServer.CharacterLogName(user) + (IsActive ? " activated " : " deactivated ") + item.Name, ServerLog.MessageType.ItemInteraction);
}
#endif
@@ -23,7 +23,7 @@ namespace Barotrauma.Items.Components
private float prevVoltage;
private float controlLockTimer;
[Editable(0.0f, 10000000.0f),
Serialize(2000.0f, true, description: "The amount of force exerted on the submarine when the engine is operating at full power.")]
public float MaxForce
@@ -119,12 +119,15 @@ namespace Barotrauma.Items.Components
float max = 1 + maxChangeSpeed;
UpdateAITargets(Math.Clamp(noise, min, max), deltaTime);
#if CLIENT
for (int i = 0; i < 5; i++)
particleTimer -= deltaTime;
if (particleTimer <= 0.0f)
{
Vector2 particleVel = -currForce.ClampLength(5000.0f) / 5.0f;
GameMain.ParticleManager.CreateParticle("bubbles", item.WorldPosition + PropellerPos,
-currForce / 5.0f + new Vector2(Rand.Range(-100.0f, 100.0f), Rand.Range(-50f, 50f)),
particleVel * Rand.Range(0.9f, 1.1f),
0.0f, item.CurrentHull);
}
particleTimer = 1.0f / particlesPerSec;
}
#endif
}
}
@@ -180,7 +180,7 @@ namespace Barotrauma.Items.Components
#if SERVER
if (user != null)
{
GameServer.Log(user.LogName + " started fabricating " + selectedItem.DisplayName + " in " + item.Name, ServerLog.MessageType.ItemInteraction);
GameServer.Log(GameServer.CharacterLogName(user) + " started fabricating " + selectedItem.DisplayName + " in " + item.Name, ServerLog.MessageType.ItemInteraction);
}
#endif
}
@@ -216,7 +216,7 @@ namespace Barotrauma.Items.Components
#if SERVER
if (user != null)
{
GameServer.Log(user.LogName + " cancelled the fabrication of " + fabricatedItem.DisplayName + " in " + item.Name, ServerLog.MessageType.ItemInteraction);
GameServer.Log(GameServer.CharacterLogName(user) + " cancelled the fabrication of " + fabricatedItem.DisplayName + " in " + item.Name, ServerLog.MessageType.ItemInteraction);
}
#endif
}
@@ -22,7 +22,7 @@ namespace Barotrauma.Items.Components
private set;
}
[Editable, Serialize(400.0f, true, description: "How much oxygen the machine generates when operating at full power.")]
[Editable, Serialize(400.0f, true, description: "How much oxygen the machine generates when operating at full power.", alwaysUseInstanceValues: true)]
public float GeneratedAmount
{
get { return generatedAmount; }
@@ -27,7 +27,7 @@ namespace Barotrauma.Items.Components
}
}
[Editable, Serialize(80.0f, false, description: "How fast the item pumps water in/out when operating at 100%.")]
[Editable, Serialize(80.0f, false, description: "How fast the item pumps water in/out when operating at 100%.", alwaysUseInstanceValues: true)]
public float MaxFlow
{
get { return maxFlow; }
@@ -45,6 +45,7 @@ namespace Barotrauma.Items.Components
}
public bool HasPower => IsActive && Voltage >= MinVoltage;
public bool IsAutoControlled => pumpSpeedLockTimer > 0.0f || isActiveLockTimer > 0.0f;
public Pump(Item item, XElement element)
: base(item, element)
@@ -130,7 +131,7 @@ namespace Barotrauma.Items.Components
if (objective.Option.Equals("stoppumping", StringComparison.OrdinalIgnoreCase))
{
#if SERVER
if (FlowPercentage > 0.0f)
if (objective.Override || FlowPercentage > 0.0f)
{
item.CreateServerEvent(this);
}
@@ -141,7 +142,7 @@ namespace Barotrauma.Items.Components
else
{
#if SERVER
if (!IsActive || FlowPercentage > -100.0f)
if (objective.Override || !IsActive || FlowPercentage > -100.0f)
{
item.CreateServerEvent(this);
}
@@ -78,7 +78,7 @@ namespace Barotrauma.Items.Components
}
}
[Editable(0.0f, float.MaxValue), Serialize(10000.0f, true, description: "How much power (kW) the reactor generates when operating at full capacity.")]
[Editable(0.0f, float.MaxValue), Serialize(10000.0f, true, description: "How much power (kW) the reactor generates when operating at full capacity.", alwaysUseInstanceValues: true)]
public float MaxPowerOutput
{
get { return maxPowerOutput; }
@@ -190,7 +190,7 @@ namespace Barotrauma.Items.Components
{
if (Timing.TotalTime >= (float)nextServerLogWriteTime)
{
GameServer.Log(lastUser.LogName + " adjusted reactor settings: " +
GameServer.Log(GameServer.CharacterLogName(lastUser) + " adjusted reactor settings: " +
"Temperature: " + (int)(temperature * 100.0f) +
", Fission rate: " + (int)targetFissionRate +
", Turbine output: " + (int)targetTurbineOutput +
@@ -330,6 +330,8 @@ namespace Barotrauma.Items.Components
}
item.SendSignal(0, ((int)(temperature * 100.0f)).ToString(), "temperature_out", null);
item.SendSignal(0, ((int)-CurrPowerConsumption).ToString(), "power_value_out", null);
item.SendSignal(0, ((int)load).ToString(), "load_value_out", null);
UpdateFailures(deltaTime);
#if CLIENT
@@ -622,7 +624,8 @@ namespace Barotrauma.Items.Components
AutoTemp = false;
targetFissionRate = 0.0f;
targetTurbineOutput = 0.0f;
break;
unsentChanges = true;
return true;
}
if (autoTemp != prevAutoTemp ||
@@ -653,17 +656,23 @@ namespace Barotrauma.Items.Components
}
break;
case "set_fissionrate":
if (float.TryParse(signal, NumberStyles.Float, CultureInfo.InvariantCulture, out float newFissionRate))
if (PowerOn && float.TryParse(signal, NumberStyles.Float, CultureInfo.InvariantCulture, out float newFissionRate))
{
FissionRate = newFissionRate;
targetFissionRate = newFissionRate;
unsentChanges = true;
#if CLIENT
FissionRateScrollBar.BarScroll = targetFissionRate / 100.0f;
#endif
}
break;
case "set_turbineoutput":
if (float.TryParse(signal, NumberStyles.Float, CultureInfo.InvariantCulture, out float newTurbineOutput))
if (PowerOn && float.TryParse(signal, NumberStyles.Float, CultureInfo.InvariantCulture, out float newTurbineOutput))
{
TurbineOutput = newTurbineOutput;
targetTurbineOutput = newTurbineOutput;
unsentChanges = true;
#if CLIENT
TurbineOutputScrollBar.BarScroll = targetTurbineOutput / 100.0f;
#endif
}
break;
}
@@ -108,10 +108,17 @@ namespace Barotrauma.Items.Components
public Vector2 TargetVelocity
{
get { return targetVelocity;}
set
get { return targetVelocity; }
set
{
if (!MathUtils.IsValid(value)) return;
if (!MathUtils.IsValid(value))
{
if (!MathUtils.IsValid(targetVelocity))
{
targetVelocity = Vector2.Zero;
}
return;
}
targetVelocity.X = MathHelper.Clamp(value.X, -100.0f, 100.0f);
targetVelocity.Y = MathHelper.Clamp(value.Y, -100.0f, 100.0f);
}
@@ -285,7 +292,7 @@ namespace Barotrauma.Items.Components
if (AutoPilot)
{
UpdateAutoPilot(deltaTime);
targetVelocity = targetVelocity.ClampLength(MathHelper.Lerp(AutoPilotMaxSpeed, AIPilotMaxSpeed, userSkill) * 100.0f);
TargetVelocity = TargetVelocity.ClampLength(MathHelper.Lerp(AutoPilotMaxSpeed, AIPilotMaxSpeed, userSkill) * 100.0f);
}
else
{
@@ -443,41 +450,46 @@ namespace Barotrauma.Items.Components
//steer away from other subs
foreach (Submarine sub in Submarine.Loaded)
{
if (sub == controlledSub) continue;
if (controlledSub.DockedTo.Contains(sub)) continue;
float thisSize = Math.Max(controlledSub.Borders.Width, controlledSub.Borders.Height);
float otherSize = Math.Max(sub.Borders.Width, sub.Borders.Height);
if (sub == controlledSub) { continue; }
if (controlledSub.DockedTo.Contains(sub)) { continue; }
Point sizeSum = controlledSub.Borders.Size + sub.Borders.Size;
Vector2 minDist = sizeSum.ToVector2() / 2;
Vector2 diff = controlledSub.WorldPosition - sub.WorldPosition;
float dist = diff == Vector2.Zero ? 0.0f : diff.Length();
//far enough -> ignore
if (dist > thisSize + otherSize) continue;
Vector2 dir = dist <= 0.0001f ? Vector2.UnitY : diff / dist;
float dot = controlledSub.Velocity == Vector2.Zero ?
0.0f : Vector2.Dot(Vector2.Normalize(controlledSub.Velocity), -dir);
//heading away -> ignore
if (dot < 0.0f) continue;
targetVelocity += diff * 200.0f;
float xDist = Math.Abs(diff.X);
float yDist = Math.Abs(diff.Y);
Vector2 maxAvoidDistance = minDist * 2;
if (xDist > maxAvoidDistance.X || yDist > maxAvoidDistance.Y)
{
//far enough -> ignore
continue;
}
float dot = controlledSub.Velocity == Vector2.Zero ? 0.0f : Vector2.Dot(Vector2.Normalize(controlledSub.Velocity), -diff);
if (dot < 0.0f)
{
//heading away -> ignore
continue;
}
float distanceFactor = MathHelper.Lerp(0, 1, MathUtils.InverseLerp(maxAvoidDistance.X + maxAvoidDistance.Y, minDist.X + minDist.Y, xDist + yDist));
float velocityFactor = MathHelper.Lerp(0, 1, MathUtils.InverseLerp(0, 3, controlledSub.Velocity.Length()));
TargetVelocity += 100 * Vector2.Normalize(diff) * distanceFactor * velocityFactor;
}
//clamp velocity magnitude to 100.0f
float velMagnitude = targetVelocity.Length();
//clamp velocity magnitude to 100.0f (Is this required? The X and Y components are clamped in the property setter)
float velMagnitude = TargetVelocity.Length();
if (velMagnitude > 100.0f)
{
targetVelocity *= 100.0f / velMagnitude;
TargetVelocity *= 100.0f / velMagnitude;
}
}
private void UpdatePath()
{
if (Level.Loaded == null) { return; }
if (pathFinder == null) pathFinder = new PathFinder(WayPoint.WayPointList, false);
if (pathFinder == null)
{
pathFinder = new PathFinder(WayPoint.WayPointList, false);
}
Vector2 target;
if (LevelEndSelected)
@@ -316,6 +316,8 @@ namespace Barotrauma.Items.Components
{
if (recipient.Item == item || recipient.Item == source) { continue; }
source?.LastSentSignalRecipients.Add(recipient.Item);
foreach (ItemComponent ic in recipient.Item.Components)
{
//other junction boxes don't need to receive the signal in the pass-through signal connections
@@ -140,6 +140,7 @@ namespace Barotrauma.Items.Components
public override void Update(float deltaTime, Camera cam)
{
currPowerConsumption = powerConsumption;
UpdateOnActiveEffects(deltaTime);
}
@@ -241,7 +241,6 @@ namespace Barotrauma.Items.Components
private void Launch(Vector2 impulse)
{
hits.Clear();
MaxTargetsToHit = 2;
if (item.AiTarget != null)
{
@@ -294,6 +293,12 @@ namespace Barotrauma.Items.Components
{
//shooting indoors, do a hitscan outside as well
hits.AddRange(DoRayCast(rayStart + item.Submarine.SimPosition, rayEnd + item.Submarine.SimPosition));
//also in the coordinate space of docked subs
foreach (Submarine dockedSub in item.Submarine.DockedTo)
{
if (dockedSub == item.Submarine) { continue; }
hits.AddRange(DoRayCast(rayStart + item.Submarine.SimPosition - dockedSub.SimPosition, rayEnd + item.Submarine.SimPosition - dockedSub.SimPosition));
}
}
else
{
@@ -354,7 +359,7 @@ namespace Barotrauma.Items.Components
{
//ignore sensors and items
if (fixture?.Body == null || fixture.IsSensor) { return true; }
if (fixture.Body.UserData is Item item && item.GetComponent<Door>() == null && !item.Prefab.DamagedByProjectiles) { return true; }
if (fixture.Body.UserData is Item item && (item.GetComponent<Door>() == null && !item.Prefab.DamagedByProjectiles || item.Condition <= 0)) { return true; }
if (fixture.Body?.UserData as string == "ruinroom") { return true; }
//ignore everything else than characters, sub walls and level walls
@@ -374,7 +379,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 && !item.Prefab.DamagedByProjectiles) { return -1; }
if (fixture.Body.UserData is Item item && (item.GetComponent<Door>() == null && !item.Prefab.DamagedByProjectiles || item.Condition <= 0)) { return -1; }
if (fixture.Body?.UserData as string == "ruinroom") { return -1; }
//ignore everything else than characters, sub walls and level walls
@@ -410,9 +415,9 @@ namespace Barotrauma.Items.Components
}
}
if (stickJoint == null || StickPermanently) { return; }
if (stickJoint == null) { return; }
if (persistentStickJointTimer > 0.0f)
if (persistentStickJointTimer > 0.0f && !StickPermanently)
{
persistentStickJointTimer -= deltaTime;
return;
@@ -420,20 +425,25 @@ namespace Barotrauma.Items.Components
if (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer)
{
if (stickJoint.JointTranslation < stickJoint.LowerLimit * 0.9f ||
stickJoint.JointTranslation > stickJoint.UpperLimit * 0.9f)
if (StickTargetRemoved() ||
(!StickPermanently && (stickJoint.JointTranslation < stickJoint.LowerLimit * 0.9f || stickJoint.JointTranslation > stickJoint.UpperLimit * 0.9f)))
{
Unstick();
}
#if SERVER
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
{
item.CreateServerEvent(this);
}
item.CreateServerEvent(this);
#endif
}
}
}
private bool StickTargetRemoved()
{
if (StickTarget == null) { return true; }
if (StickTarget.UserData is Limb limb) { return limb.character.Removed; }
if (StickTarget.UserData is Entity entity) { return entity.Removed; }
return false;
}
private bool OnProjectileCollision(Fixture f1, Fixture target, Contact contact)
{
@@ -529,7 +539,7 @@ namespace Barotrauma.Items.Components
}
else if (target.Body.UserData is Item targetItem)
{
if (attack != null && targetItem.Prefab.DamagedByProjectiles)
if (attack != null && targetItem.Prefab.DamagedByProjectiles && targetItem.Condition > 0)
{
attackResult = attack.DoDamage(User, targetItem, item.WorldPosition, 1.0f);
}
@@ -604,7 +614,7 @@ namespace Barotrauma.Items.Components
if (hits.Count() >= MaxTargetsToHit)
{
item.body.FarseerBody.OnCollision -= OnProjectileCollision;
if (item.Prefab.DamagedByProjectiles || item.Prefab.DamagedByMeleeWeapons)
if ((item.Prefab.DamagedByProjectiles || item.Prefab.DamagedByMeleeWeapons) && item.Condition > 0)
{
item.body.CollisionCategories = Physics.CollisionCharacter;
item.body.CollidesWith = Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionPlatform | Physics.CollisionProjectile;
@@ -623,6 +633,7 @@ namespace Barotrauma.Items.Components
item.body.LinearVelocity *= 0.1f;
}
else if (Vector2.Dot(velocity, collisionNormal) < 0.0f && hits.Count() >= MaxTargetsToHit &&
target.Body.Mass > item.body.Mass * 0.5f &&
(DoesStick ||
(StickToCharacters && target.Body.UserData is Limb) ||
(StickToStructures && target.Body.UserData is Structure) ||
@@ -692,6 +703,7 @@ namespace Barotrauma.Items.Components
if (StickPermanently)
{
stickJoint.LowerLimit = stickJoint.UpperLimit = 0.0f;
item.body.ResetDynamics();
}
else if (item.Sprite != null)
{
@@ -273,7 +273,7 @@ namespace Barotrauma.Items.Components
float successFactor = requiredSkills.Count == 0 ? 1.0f : DegreeOfSuccess(CurrentFixer, requiredSkills);
//item must have been below the repair threshold for the player to get an achievement or XP for repairing it
if (!item.IsFullCondition)
if (item.ConditionPercentage < AIRepairThreshold)
{
wasBroken = true;
}
@@ -120,6 +120,10 @@ namespace Barotrauma.Items.Components
return;
}
#if CLIENT
item.ResetCachedVisibleSize();
#endif
if (SnapOnCollision)
{
raycastTimer += deltaTime;
@@ -13,7 +13,7 @@ namespace Barotrauma.Items.Components
//the output is sent if both inputs have received a signal within the timeframe
protected float timeFrame;
[InGameEditable(DecimalCount = 2), Serialize(0.0f, true, description: "The item sends the output if both inputs have received a non-zero signal within the timeframe. If set to 0, the inputs must receive a signal at the same time.")]
[InGameEditable(DecimalCount = 2), Serialize(0.0f, true, description: "The item sends the output if both inputs have received a non-zero signal within the timeframe. If set to 0, the inputs must receive a signal at the same time.", alwaysUseInstanceValues: true)]
public float TimeFrame
{
get { return timeFrame; }
@@ -23,14 +23,14 @@ namespace Barotrauma.Items.Components
}
}
[InGameEditable, Serialize("1", true, description: "The signal sent when both inputs have received a non-zero signal.")]
[InGameEditable, Serialize("1", true, description: "The signal sent when the condition is met.", alwaysUseInstanceValues: true)]
public string Output
{
get { return output; }
set { output = value; }
}
[InGameEditable, Serialize("", true, description: "The signal sent when both inputs have not received a non-zero signal (if empty, no signal is sent).")]
[InGameEditable, Serialize("", true, description: "The signal sent when the condition is met (if empty, no signal is sent).", alwaysUseInstanceValues: true)]
public string FalseOutput
{
get { return falseOutput; }
@@ -15,7 +15,7 @@ namespace Barotrauma.Items.Components
//the output is sent if both inputs have received a signal within the timeframe
protected float timeFrame;
[Serialize(999999.0f, true, description: "The output of the item is restricted below this value."),
[Serialize(999999.0f, true, description: "The output of the item is restricted below this value.", alwaysUseInstanceValues: true),
InGameEditable(MinValueFloat = -999999.0f, MaxValueFloat = 999999.0f)]
public float ClampMax
{
@@ -23,7 +23,7 @@ namespace Barotrauma.Items.Components
set;
}
[Serialize(-999999.0f, true, description: "The output of the item is restricted above this value."),
[Serialize(-999999.0f, true, description: "The output of the item is restricted above this value.", alwaysUseInstanceValues: true),
InGameEditable(MinValueFloat = -999999.0f, MaxValueFloat = 999999.0f)]
public float ClampMin
{
@@ -32,8 +32,8 @@ namespace Barotrauma.Items.Components
}
[InGameEditable(DecimalCount = 2),
Serialize(0.0f, true, description: "The item must have received signals to both inputs within this timeframe to output the sum of the signals." +
" If set to 0, the inputs must be received at the same time.")]
Serialize(0.0f, true, description: "The item must have received signals to both inputs within this timeframe to output the result." +
" If set to 0, the inputs must be received at the same time.", alwaysUseInstanceValues: true)]
public float TimeFrame
{
get { return timeFrame; }
@@ -21,7 +21,7 @@ namespace Barotrauma.Items.Components
private List<ushort> disconnectedWireIds;
[Editable, Serialize(false, true, description: "Locked connection panels cannot be rewired in-game.")]
[Editable, Serialize(false, true, description: "Locked connection panels cannot be rewired in-game.", alwaysUseInstanceValues: true)]
public bool Locked
{
get;
@@ -192,6 +192,8 @@ namespace Barotrauma.Items.Components
public bool CheckCharacterSuccess(Character character)
{
if (character == null) { return false; }
//no electrocution in sub editor
if (Screen.Selected == GameMain.SubEditorScreen) { return true; }
var powered = item.GetComponent<Powered>();
if (powered != null)
@@ -262,7 +264,18 @@ namespace Barotrauma.Items.Components
{
if (wire.OtherConnection(null) == null) //wire not connected to anything else
{
#if CLIENT
if (SubEditorScreen.IsSubEditor())
{
wire.Item.Remove();
}
else
{
wire.Item.Drop(null);
}
#else
wire.Item.Drop(null);
#endif
}
}
@@ -275,7 +288,18 @@ namespace Barotrauma.Items.Components
if (wire.OtherConnection(c) == null) //wire not connected to anything else
{
#if CLIENT
if (SubEditorScreen.IsSubEditor())
{
wire.Item.Remove();
}
else
{
wire.Item.Drop(null);
}
#else
wire.Item.Drop(null);
#endif
}
else
{
@@ -30,7 +30,7 @@ namespace Barotrauma.Items.Components
private DelayedSignal prevQueuedSignal;
private float delay;
[InGameEditable(MinValueFloat = 0.0f, MaxValueFloat = 60.0f, DecimalCount = 2), Serialize(1.0f, true, description: "How long the item delays the signals (in seconds).")]
[InGameEditable(MinValueFloat = 0.0f, MaxValueFloat = 60.0f, DecimalCount = 2), Serialize(1.0f, true, description: "How long the item delays the signals (in seconds).", alwaysUseInstanceValues: true)]
public float Delay
{
get { return delay; }
@@ -43,14 +43,14 @@ namespace Barotrauma.Items.Components
}
}
[InGameEditable, Serialize(false, true, description: "Should the component discard previously received signals when a new one is received.")]
[InGameEditable, Serialize(false, true, description: "Should the component discard previously received signals when a new one is received.", alwaysUseInstanceValues: true)]
public bool ResetWhenSignalReceived
{
get;
set;
}
[InGameEditable, Serialize(false, true, description: "Should the component discard previously received signals when the incoming signal changes.")]
[InGameEditable, Serialize(false, true, description: "Should the component discard previously received signals when the incoming signal changes.", alwaysUseInstanceValues: true)]
public bool ResetWhenDifferentSignalReceived
{
get;
@@ -15,21 +15,21 @@ namespace Barotrauma.Items.Components
//the output is sent if both inputs have received a signal within the timeframe
protected float timeFrame;
[InGameEditable, Serialize("1", true, description: "The signal this item outputs when the received signals are equal.")]
[InGameEditable, Serialize("1", true, description: "The signal this item outputs when the condition is met.", alwaysUseInstanceValues: true)]
public string Output
{
get { return output; }
set { output = value; }
}
[InGameEditable, Serialize("", true, description: "The signal this item outputs when the received signals are not equal.")]
[InGameEditable, Serialize("", true, description: "The signal this item outputs when the condition is not met.", alwaysUseInstanceValues: true)]
public string FalseOutput
{
get { return falseOutput; }
set { falseOutput = value; }
}
[InGameEditable(DecimalCount = 2), Serialize(0.0f, true, description: "The maximum amount of time between the received signals. If set to 0, the signals must be received at the same time.")]
[InGameEditable(DecimalCount = 2), Serialize(0.0f, true, description: "The maximum amount of time between the received signals. If set to 0, the signals must be received at the same time.", alwaysUseInstanceValues: true)]
public float TimeFrame
{
get { return timeFrame; }
@@ -6,7 +6,7 @@ namespace Barotrauma.Items.Components
class ExponentiationComponent : ItemComponent
{
private float exponent;
[InGameEditable, Serialize(1.0f, false, description: "The exponent of the operation.")]
[InGameEditable, Serialize(1.0f, false, description: "The exponent of the operation.", alwaysUseInstanceValues: true)]
public float Exponent
{
get
@@ -16,7 +16,7 @@ namespace Barotrauma.Items.Components
SquareRoot
}
[Serialize(FunctionType.Round, false, description: "Which kind of function to run the input through.")]
[Serialize(FunctionType.Round, false, description: "Which kind of function to run the input through.", alwaysUseInstanceValues: true)]
public FunctionType Function
{
get; set;
@@ -15,7 +15,7 @@ namespace Barotrauma.Items.Components
private float lightBrightness;
private float blinkFrequency;
private float range;
private float flicker;
private float flicker, flickerState;
private bool castShadows;
private bool drawBehindSubs;
@@ -25,7 +25,7 @@ namespace Barotrauma.Items.Components
public PhysicsBody ParentBody;
[Serialize(100.0f, true, description: "The range of the emitted light. Higher values are more performance-intensive."),
[Serialize(100.0f, true, description: "The range of the emitted light. Higher values are more performance-intensive.", alwaysUseInstanceValues: true),
Editable(MinValueFloat = 0.0f, MaxValueFloat = 2048.0f)]
public float Range
{
@@ -34,6 +34,7 @@ namespace Barotrauma.Items.Components
{
range = MathHelper.Clamp(value, 0.0f, 4096.0f);
#if CLIENT
item.ResetCachedVisibleSize();
if (light != null) { light.Range = range; }
#endif
}
@@ -42,7 +43,7 @@ namespace Barotrauma.Items.Components
public float Rotation;
[Editable, Serialize(true, true, description: "Should structures cast shadows when light from this light source hits them. " +
"Disabling shadows increases the performance of the game, and is recommended for lights with a short range.")]
"Disabling shadows increases the performance of the game, and is recommended for lights with a short range.", alwaysUseInstanceValues: true)]
public bool CastShadows
{
get { return castShadows; }
@@ -56,7 +57,7 @@ namespace Barotrauma.Items.Components
}
[Editable, Serialize(false, true, description: "Lights drawn behind submarines don't cast any shadows and are much faster to draw than shadow-casting lights. " +
"It's recommended to enable this on decorative lights outside the submarine's hull.")]
"It's recommended to enable this on decorative lights outside the submarine's hull.", alwaysUseInstanceValues: true)]
public bool DrawBehindSubs
{
get { return drawBehindSubs; }
@@ -69,7 +70,7 @@ namespace Barotrauma.Items.Components
}
}
[Editable, Serialize(false, true, description: "Is the light currently on.")]
[Editable, Serialize(false, true, description: "Is the light currently on.", alwaysUseInstanceValues: true)]
public bool IsOn
{
get { return IsActive; }
@@ -82,7 +83,7 @@ namespace Barotrauma.Items.Components
}
}
[Serialize(0.0f, false, description: "How heavily the light flickers. 0 = no flickering, 1 = the light will alternate between completely dark and full brightness.")]
[Editable, Serialize(0.0f, false, description: "How heavily the light flickers. 0 = no flickering, 1 = the light will alternate between completely dark and full brightness.")]
public float Flicker
{
get { return flicker; }
@@ -92,6 +93,13 @@ namespace Barotrauma.Items.Components
}
}
[Editable, Serialize(1.0f, false, description: "How fast the light flickers.")]
public float FlickerSpeed
{
get;
set;
}
[Editable, Serialize(0.0f, true, description: "How rapidly the light blinks on and off (in Hz). 0 = no blinking.")]
public float BlinkFrequency
{
@@ -102,7 +110,7 @@ namespace Barotrauma.Items.Components
}
}
[InGameEditable, Serialize("255,255,255,255", true, description: "The color of the emitted light (R,G,B,A).")]
[InGameEditable, Serialize("255,255,255,255", true, description: "The color of the emitted light (R,G,B,A).", alwaysUseInstanceValues: true)]
public Color LightColor
{
get { return lightColor; }
@@ -217,7 +225,7 @@ namespace Barotrauma.Items.Components
}
else
{
lightBrightness = MathHelper.Lerp(lightBrightness, Math.Min(Voltage, 1.0f), 0.1f);
lightBrightness = MathHelper.Lerp(lightBrightness, powerConsumption <= 0.0f ? 1.0f : Math.Min(Voltage, 1.0f), 0.1f);
}
if (blinkFrequency > 0.0f)
@@ -231,7 +239,10 @@ namespace Barotrauma.Items.Components
}
else
{
SetLightSourceState(true, lightBrightness * (1.0f - Rand.Range(0.0f, flicker)));
flickerState += deltaTime * FlickerSpeed;
flickerState %= 255;
float noise = PerlinNoise.GetPerlin(flickerState, flickerState * 0.5f) * flicker;
SetLightSourceState(true, lightBrightness * (1.0f - noise));
}
if (powerIn == null && powerConsumption > 0.0f) { Voltage -= deltaTime; }
@@ -254,7 +265,7 @@ namespace Barotrauma.Items.Components
switch (connection.Name)
{
case "toggle":
if (IgnoreContinuousToggle && lastToggleSignalTime < Timing.TotalTime - 0.1)
if (!IgnoreContinuousToggle || lastToggleSignalTime < Timing.TotalTime - 0.1)
{
IsOn = !IsOn;
}
@@ -4,7 +4,7 @@ namespace Barotrauma.Items.Components
{
class MemoryComponent : ItemComponent
{
[InGameEditable, Serialize("", true, description: "The currently stored signal the item outputs.")]
[InGameEditable, Serialize("", true, description: "The currently stored signal the item outputs.", alwaysUseInstanceValues: true)]
public string Value
{
get;
@@ -6,7 +6,7 @@ namespace Barotrauma.Items.Components
class ModuloComponent : ItemComponent
{
private float modulus;
[InGameEditable, Serialize(1.0f, false, description: "The modulus of the operation. Must be non-zero.")]
[InGameEditable, Serialize(1.0f, false, description: "The modulus of the operation. Must be non-zero.", alwaysUseInstanceValues: true)]
public float Modulus
{
get { return modulus; }
@@ -18,14 +18,14 @@ namespace Barotrauma.Items.Components
[Serialize(false, false, description: "Has the item currently detected movement. Intended to be used by StatusEffect conditionals (setting this value in XML has no effect).")]
public bool MotionDetected { get; set; }
[Editable, Serialize(false, true, description: "Should the sensor only detect the movement of humans?")]
[Editable, Serialize(false, true, description: "Should the sensor only detect the movement of humans?", alwaysUseInstanceValues: true)]
public bool OnlyHumans
{
get;
set;
}
[Editable, Serialize(false, true, description: "Should the sensor ignore the bodies of dead characters?")]
[Editable, Serialize(false, true, description: "Should the sensor ignore the bodies of dead characters?", alwaysUseInstanceValues: true)]
public bool IgnoreDead
{
get;
@@ -33,16 +33,19 @@ namespace Barotrauma.Items.Components
}
[InGameEditable, Serialize(0.0f, true, description: "Horizontal detection range.")]
[InGameEditable, Serialize(0.0f, true, description: "Horizontal detection range.", alwaysUseInstanceValues: true)]
public float RangeX
{
get { return rangeX; }
set
{
rangeX = MathHelper.Clamp(value, 0.0f, 1000.0f);
#if CLIENT
item.ResetCachedVisibleSize();
#endif
}
}
[InGameEditable, Serialize(0.0f, true, description: "Vertical movement detection range.")]
[InGameEditable, Serialize(0.0f, true, description: "Vertical movement detection range.", alwaysUseInstanceValues: true)]
public float RangeY
{
get { return rangeY; }
@@ -64,13 +67,13 @@ namespace Barotrauma.Items.Components
}
}
[InGameEditable, Serialize("1", true, description: "The signal the item outputs when it has detected movement.")]
[InGameEditable, Serialize("1", true, description: "The signal the item outputs when it has detected movement.", alwaysUseInstanceValues: true)]
public string Output { get; set; }
[InGameEditable, Serialize("", true, description: "The signal the item outputs when it has not detected movement.")]
[InGameEditable, Serialize("", true, description: "The signal the item outputs when it has not detected movement.", alwaysUseInstanceValues: true)]
public string FalseOutput { get; set; }
[Editable(DecimalCount = 3), Serialize(0.01f, true, description: "How fast the objects within the detector's range have to be moving (in m/s).")]
[Editable(DecimalCount = 3), Serialize(0.01f, true, description: "How fast the objects within the detector's range have to be moving (in m/s).", alwaysUseInstanceValues: true)]
public float MinimumVelocity
{
get;
@@ -129,7 +132,8 @@ namespace Barotrauma.Items.Components
foreach (Limb limb in c.AnimController.Limbs)
{
if (limb.LinearVelocity.LengthSquared() <= MinimumVelocity * MinimumVelocity) continue;
if (limb.IsSevered) { continue; }
if (limb.LinearVelocity.LengthSquared() <= MinimumVelocity * MinimumVelocity) { continue; }
if (MathUtils.CircleIntersectsRectangle(limb.WorldPosition, ConvertUnits.ToDisplayUnits(limb.body.GetMaxExtent()), detectRect))
{
MotionDetected = true;
@@ -23,14 +23,14 @@ namespace Barotrauma.Items.Components
[InGameEditable, Serialize(WaveType.Pulse, true, description: "What kind of a signal the item outputs." +
" Pulse: periodically sends out a signal of 1." +
" Sine: sends out a sine wave oscillating between -1 and 1." +
" Square: sends out a signal that alternates between 0 and 1.")]
" Square: sends out a signal that alternates between 0 and 1.", alwaysUseInstanceValues: true)]
public WaveType OutputType
{
get;
set;
}
[InGameEditable(DecimalCount = 2), Serialize(1.0f, true, description: "How fast the signal oscillates, or how fast the pulses are sent (in Hz).")]
[InGameEditable(DecimalCount = 2), Serialize(1.0f, true, description: "How fast the signal oscillates, or how fast the pulses are sent (in Hz).", alwaysUseInstanceValues: true)]
public float Frequency
{
get { return frequency; }
@@ -16,16 +16,16 @@ namespace Barotrauma.Items.Components
private bool nonContinuousOutputSent;
[InGameEditable, Serialize("1", true, description: "The signal this item outputs when the received signal matches the regular expression.")]
[InGameEditable, Serialize("1", true, description: "The signal this item outputs when the received signal matches the regular expression.", alwaysUseInstanceValues: true)]
public string Output { get; set; }
[Serialize("0", true, description: "The signal this item outputs when the received signal does not match the regular expression.")]
[Serialize("0", true, description: "The signal this item outputs when the received signal does not match the regular expression.", alwaysUseInstanceValues: true)]
public string FalseOutput { get; set; }
[InGameEditable, Serialize(true, true, description: "Should the component keep sending the output even after it stops receiving a signal, or only send an output when it receives a signal.")]
[InGameEditable, Serialize(true, true, description: "Should the component keep sending the output even after it stops receiving a signal, or only send an output when it receives a signal.", alwaysUseInstanceValues: true)]
public bool ContinuousOutput { get; set; }
[InGameEditable, Serialize("", true, description: "The regular expression used to check the incoming signals.")]
[InGameEditable, Serialize("", true, description: "The regular expression used to check the incoming signals.", alwaysUseInstanceValues: true)]
public string Expression
{
get { return expression; }
@@ -37,7 +37,7 @@ namespace Barotrauma.Items.Components
}
}
[Editable, Serialize(false, true, description: "Can the relay currently pass power and signals through it.")]
[Editable, Serialize(false, true, description: "Can the relay currently pass power and signals through it.", alwaysUseInstanceValues: true)]
public bool IsOn
{
get
@@ -4,12 +4,12 @@ namespace Barotrauma.Items.Components
{
class SignalCheckComponent : ItemComponent
{
[InGameEditable, Serialize("1", true, description: "The signal this item outputs when the received signal matches the target signal.")]
[InGameEditable, Serialize("1", true, description: "The signal this item outputs when the received signal matches the target signal.", alwaysUseInstanceValues: true)]
public string Output { get; set; }
[InGameEditable, Serialize("0", true, description: "The signal this item outputs when the received signal does not match the target signal.")]
[InGameEditable, Serialize("0", true, description: "The signal this item outputs when the received signal does not match the target signal.", alwaysUseInstanceValues: true)]
public string FalseOutput { get; set; }
[InGameEditable, Serialize("", true, description: "The value to compare the received signals against.")]
[InGameEditable, Serialize("", true, description: "The value to compare the received signals against.", alwaysUseInstanceValues: true)]
public string TargetSignal { get; set; }
public SignalCheckComponent(Item item, XElement element)
@@ -1,25 +1,51 @@
using System.Linq;
using System.Xml.Linq;
using System.Xml.Linq;
namespace Barotrauma.Items.Components
{
class SmokeDetector : ItemComponent
{
[Serialize(50.0f, false, description: "How large the fire has to be for the detector to react to it.")]
public float FireSizeThreshold
{
get; set;
}
const float FireCheckInterval = 1.0f;
private float fireCheckTimer;
private bool fireInRange;
[InGameEditable, Serialize("1", true, description: "The signal the item outputs when it has detected movement.", alwaysUseInstanceValues: true)]
public string Output { get; set; }
[InGameEditable, Serialize("0", true, description: "The signal the item outputs when it has not detected movement.", alwaysUseInstanceValues: true)]
public string FalseOutput { get; set; }
public SmokeDetector(Item item, XElement element)
: base (item, element)
: base(item, element)
{
IsActive = true;
}
private bool IsFireInRange()
{
if (item.CurrentHull == null || item.InWater) { return false; }
var connectedHulls = item.CurrentHull.GetConnectedHulls(includingThis: true, searchDepth: 10, ignoreClosedGaps: true);
foreach (Hull hull in connectedHulls)
{
foreach (FireSource fireSource in hull.FireSources)
{
if (fireSource.IsInDamageRange(item.WorldPosition, fireSource.DamageRange * 2.0f)) { return true; }
}
}
return false;
}
public override void Update(float deltaTime, Camera cam)
{
item.SendSignal(0, item.CurrentHull != null && item.CurrentHull.FireSources.Any(fs => fs.Size.X > FireSizeThreshold) ? "1" : "0", "signal_out", null);
fireCheckTimer -= deltaTime;
if (fireCheckTimer <= 0.0f)
{
fireInRange = IsFireInRange();
fireCheckTimer = FireCheckInterval;
}
item.SendSignal(0, fireInRange ? "1" : "0", "signal_out", null);
}
}
}
@@ -13,7 +13,7 @@ namespace Barotrauma.Items.Components
}
private string welcomeMessage;
[InGameEditable, Serialize("", true, "Message to be displayed on the terminal display when it is first opened.", translationTextTag = "terminalwelcomemsg.")]
[InGameEditable, Serialize("", true, "Message to be displayed on the terminal display when it is first opened.", translationTextTag = "terminalwelcomemsg.", AlwaysUseInstanceValues = true)]
public string WelcomeMessage
{
get { return welcomeMessage; }
@@ -19,14 +19,14 @@ namespace Barotrauma.Items.Components
protected float[] receivedSignal = new float[2];
[Serialize(FunctionType.Sin, false, description: "Which kind of function to run the input through.")]
[Serialize(FunctionType.Sin, false, description: "Which kind of function to run the input through.", alwaysUseInstanceValues: true)]
public FunctionType Function
{
get; set;
}
[InGameEditable, Serialize(false, true, description: "If set to true, the trigonometric function uses radians instead of degrees.")]
[InGameEditable, Serialize(false, true, description: "If set to true, the trigonometric function uses radians instead of degrees.", alwaysUseInstanceValues: true)]
public bool UseRadians
{
get; set;
@@ -10,10 +10,10 @@ namespace Barotrauma.Items.Components
private bool isInWater;
private float stateSwitchDelay;
[InGameEditable, Serialize("1", true, description: "The signal the item sends out when it's underwater.")]
[InGameEditable, Serialize("1", true, description: "The signal the item sends out when it's underwater.", alwaysUseInstanceValues: true)]
public string Output { get; set; }
[InGameEditable, Serialize("0", true, description: "The signal the item sends out when it's not underwater.")]
[InGameEditable, Serialize("0", true, description: "The signal the item sends out when it's not underwater.", alwaysUseInstanceValues: true)]
public string FalseOutput { get; set; }
public WaterDetector(Item item, XElement element)
@@ -9,7 +9,7 @@ namespace Barotrauma.Items.Components
{
partial class WifiComponent : ItemComponent
{
private static List<WifiComponent> list = new List<WifiComponent>();
private static readonly List<WifiComponent> list = new List<WifiComponent>();
private float range;
@@ -19,17 +19,23 @@ namespace Barotrauma.Items.Components
private string prevSignal;
[Serialize(Character.TeamType.None, true, description: "WiFi components can only communicate with components that have the same Team ID.")]
[Serialize(Character.TeamType.None, true, description: "WiFi components can only communicate with components that have the same Team ID.", alwaysUseInstanceValues: true)]
public Character.TeamType TeamID { get; set; }
[Editable, Serialize(20000.0f, false, description: "How close the recipient has to be to receive a signal from this WiFi component.")]
[Editable, Serialize(20000.0f, false, description: "How close the recipient has to be to receive a signal from this WiFi component.", alwaysUseInstanceValues: true)]
public float Range
{
get { return range; }
set { range = Math.Max(value, 0.0f); }
set
{
range = Math.Max(value, 0.0f);
#if CLIENT
item.ResetCachedVisibleSize();
#endif
}
}
[InGameEditable, Serialize(1, true, description: "WiFi components can only communicate with components that use the same channel.")]
[InGameEditable, Serialize(1, true, description: "WiFi components can only communicate with components that use the same channel.", alwaysUseInstanceValues: true)]
public int Channel
{
get { return channel; }
@@ -40,7 +46,7 @@ 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.")]
[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.", alwaysUseInstanceValues: true)]
public bool AllowCrossTeamCommunication
{
get;
@@ -48,7 +54,7 @@ namespace Barotrauma.Items.Components
}
[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.")]
"as chat messages in the chatbox of the player holding the item.", alwaysUseInstanceValues: true)]
public bool LinkToChat
{
get;
@@ -105,12 +111,16 @@ namespace Barotrauma.Items.Components
public override void Update(float deltaTime, Camera cam)
{
chatMsgCooldown -= deltaTime;
if (chatMsgCooldown <= 0.0f)
{
IsActive = false;
}
}
public void TransmitSignal(int stepsTaken, string signal, Item source, Character sender, bool sendToChat, float signalStrength = 1.0f)
{
var senderComponent = source?.GetComponent<WifiComponent>();
if (senderComponent != null && !CanReceive(senderComponent)) return;
if (senderComponent != null && !CanReceive(senderComponent)) { return; }
bool chatMsgSent = false;
@@ -138,33 +148,32 @@ namespace Barotrauma.Items.Components
if (LinkToChat && wifiComp.LinkToChat && chatMsgCooldown <= 0.0f && sendToChat)
{
if (wifiComp.item.ParentInventory != null &&
wifiComp.item.ParentInventory.Owner != null &&
GameMain.NetworkMember != null)
wifiComp.item.ParentInventory.Owner != null)
{
string chatMsg = signal;
if (senderComponent != null)
{
chatMsg = ChatMessage.ApplyDistanceEffect(chatMsg, 1.0f - sentSignalStrength);
}
if (chatMsg.Length > ChatMessage.MaxLength) chatMsg = chatMsg.Substring(0, ChatMessage.MaxLength);
if (string.IsNullOrEmpty(chatMsg)) continue;
if (chatMsg.Length > ChatMessage.MaxLength) { chatMsg = chatMsg.Substring(0, ChatMessage.MaxLength); }
if (string.IsNullOrEmpty(chatMsg)) { continue; }
#if CLIENT
if (wifiComp.item.ParentInventory.Owner == Character.Controlled)
{
if (GameMain.Client == null)
GameMain.NetworkMember.AddChatMessage(signal, ChatMessageType.Radio, source == null ? "" : source.Name);
{
GameMain.GameSession?.CrewManager?.AddSinglePlayerChatMessage(source?.Name ?? "", signal, ChatMessageType.Radio, sender: null);
}
}
#endif
#if SERVER
#elif SERVER
if (GameMain.Server != null)
{
Client recipientClient = GameMain.Server.ConnectedClients.Find(c => c.Character == wifiComp.item.ParentInventory.Owner);
if (recipientClient != null)
{
GameMain.Server.SendDirectChatMessage(
ChatMessage.Create(source == null ? "" : source.Name, chatMsg, ChatMessageType.Radio, null), recipientClient);
ChatMessage.Create(source?.Name ?? "", chatMsg, ChatMessageType.Radio, null), recipientClient);
}
}
#endif
@@ -172,15 +181,31 @@ namespace Barotrauma.Items.Components
}
}
}
if (chatMsgSent) chatMsgCooldown = MinChatMessageInterval;
if (chatMsgSent)
{
chatMsgCooldown = MinChatMessageInterval;
IsActive = true;
}
prevSignal = signal;
}
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power = 0.0f, float signalStrength = 1.0f)
{
if (connection == null || connection.Name != "signal_in") return;
TransmitSignal(stepsTaken, signal, source, sender, true, signalStrength);
if (connection == null) { return; }
switch (connection.Name)
{
case "signal_in":
TransmitSignal(stepsTaken, signal, source, sender, true, signalStrength);
break;
case "set_channel":
if (int.TryParse(signal, out int newChannel))
{
Channel = newChannel;
}
break;
}
}
protected override void RemoveComponentSpecific()
@@ -5,6 +5,9 @@ using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Xml.Linq;
#if CLIENT
using Microsoft.Xna.Framework.Input;
#endif
namespace Barotrauma.Items.Components
{
@@ -37,9 +40,11 @@ namespace Barotrauma.Items.Components
}
}
private bool shouldClearConnections = true;
const float MaxAttachDistance = 150.0f;
const float MinNodeDistance = 15.0f;
const float MinNodeDistance = 7.0f;
const int MaxNodeCount = 255;
const int MaxNodesPerNetworkEvent = 30;
@@ -177,6 +182,8 @@ namespace Barotrauma.Items.Components
newConnection.Item.Position :
newConnection.Item.Position - refSub.HiddenSubPosition;
nodePos = RoundNode(nodePos);
if (nodes.Count > 0 && nodes[0] == nodePos) { break; }
if (nodes.Count > 1 && nodes[nodes.Count - 1] == nodePos) { break; }
@@ -241,7 +248,7 @@ namespace Barotrauma.Items.Components
public override void Equip(Character character)
{
ClearConnections(character);
if (shouldClearConnections) { ClearConnections(character); }
IsActive = true;
}
@@ -253,7 +260,7 @@ namespace Barotrauma.Items.Components
public override void Drop(Character dropper)
{
ClearConnections(dropper);
if (shouldClearConnections) { ClearConnections(dropper); }
IsActive = false;
}
@@ -334,7 +341,12 @@ namespace Barotrauma.Items.Components
}
else
{
#if CLIENT
bool disableGrid = SubEditorScreen.IsSubEditor() && PlayerInput.IsShiftDown();
newNodePos = disableGrid ? item.Position : RoundNode(item.Position);
#else
newNodePos = RoundNode(item.Position);
#endif
if (sub != null) { newNodePos -= sub.HiddenSubPosition; }
canPlaceNode = true;
}
@@ -497,6 +509,9 @@ namespace Barotrauma.Items.Components
sectionExtents.Y = Math.Max(Math.Abs(nodes[i].Y - item.Position.Y), sectionExtents.Y);
}
}
#if CLIENT
item.ResetCachedVisibleSize();
#endif
}
public void ClearConnections(Character user = null)
@@ -507,7 +522,7 @@ namespace Barotrauma.Items.Components
foreach (Item item in Item.ItemList)
{
var connectionPanel = item.GetComponent<ConnectionPanel>();
if (connectionPanel != null && connectionPanel.DisconnectedWires.Contains(this))
if (connectionPanel != null && connectionPanel.DisconnectedWires.Contains(this) && !item.Removed)
{
#if SERVER
item.CreateServerEvent(connectionPanel);
@@ -526,18 +541,18 @@ namespace Barotrauma.Items.Components
if (connections[0] != null && connections[1] != null)
{
GameServer.Log(user.LogName + " disconnected a wire from " +
GameServer.Log(GameServer.CharacterLogName(user) + " disconnected a wire from " +
connections[0].Item.Name + " (" + connections[0].Name + ") to "+
connections[1].Item.Name + " (" + connections[1].Name + ")", ServerLog.MessageType.ItemInteraction);
}
else if (connections[0] != null)
{
GameServer.Log(user.LogName + " disconnected a wire from " +
GameServer.Log(GameServer.CharacterLogName(user) + " disconnected a wire from " +
connections[0].Item.Name + " (" + connections[0].Name + ")", ServerLog.MessageType.ItemInteraction);
}
else if (connections[1] != null)
{
GameServer.Log(user.LogName + " disconnected a wire from " +
GameServer.Log(GameServer.CharacterLogName(user) + " disconnected a wire from " +
connections[1].Item.Name + " (" + connections[1].Name + ")", ServerLog.MessageType.ItemInteraction);
}
}
@@ -4,10 +4,11 @@ using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using Barotrauma.IO;
using System.Linq;
using System.Xml.Linq;
using Barotrauma.Extensions;
using FarseerPhysics.Dynamics;
namespace Barotrauma.Items.Components
{
@@ -35,8 +36,16 @@ namespace Barotrauma.Items.Components
private int failedLaunchAttempts;
private readonly List<Item> activeProjectiles = new List<Item>();
public IEnumerable<Item> ActiveProjectiles => activeProjectiles;
private Character user;
private float resetUserTimer;
public float Rotation
{
get { return rotation; }
}
[Serialize("0,0", false, description: "The position of the barrel relative to the upper left corner of the base sprite (in pixels).")]
public Vector2 BarrelPos
@@ -74,7 +83,28 @@ namespace Barotrauma.Items.Components
set { reloadTime = value; }
}
[Serialize(1, false, description: "How projectiles the weapon launches when fired once.")]
[Editable(0.1f, 10f), Serialize(1.0f, false, description: "Modifies the duration of retraction of the barrell after recoil to get back to the original position after shooting. Reload time affects this too.")]
public float RetractionDurationMultiplier
{
get;
set;
}
[Editable(0.1f, 10f), Serialize(0.1f, false, description: "How quickly the recoil moves the barrel after launching.")]
public float RecoilTime
{
get;
set;
}
[Editable(0f, 1000f), Serialize(0f, false, description: "How long the barrell stays in place after the recoil and before retracting back to the original position.")]
public float RetractionDelay
{
get;
set;
}
[Serialize(1, false, description: "How many projectiles the weapon launches when fired once.")]
public int ProjectileCount
{
get;
@@ -88,7 +118,8 @@ namespace Barotrauma.Items.Components
set;
}
[Editable, Serialize("0.0,0.0", true, description: "The range at which the barrel can rotate. TODO")]
[Editable(VectorComponentLabels = new string[] { "editable.minvalue", "editable.maxvalue" }),
Serialize("0.0,0.0", true, description: "The range at which the barrel can rotate.", alwaysUseInstanceValues: true)]
public Vector2 RotationLimits
{
get
@@ -168,7 +199,7 @@ namespace Barotrauma.Items.Components
}
private float baseRotationRad;
[Editable(0.0f, 360.0f), Serialize(0.0f, true, description: "The angle of the turret's base in degrees.")]
[Editable(0.0f, 360.0f), Serialize(0.0f, true, description: "The angle of the turret's base in degrees.", alwaysUseInstanceValues: true)]
public float BaseRotation
{
get { return MathHelper.ToDegrees(baseRotationRad); }
@@ -224,6 +255,7 @@ namespace Barotrauma.Items.Components
//if (item.FlippedY) flippedRotation = 180.0f - flippedRotation;
transformedBarrelPos = MathUtils.RotatePointAroundTarget(barrelPos * item.Scale, new Vector2(item.Rect.Width / 2, item.Rect.Height / 2), flippedRotation);
#if CLIENT
item.ResetCachedVisibleSize();
item.SpriteRotation = MathHelper.ToRadians(flippedRotation);
#endif
}
@@ -252,6 +284,16 @@ namespace Barotrauma.Items.Components
if (reload > 0.0f) { reload -= deltaTime; }
if (user != null && user.Removed)
{
user = null;
}
else
{
resetUserTimer -= deltaTime;
if (resetUserTimer <= 0.0f) { user = null; }
}
ApplyStatusEffects(ActionType.OnActive, deltaTime, null);
UpdateProjSpecific(deltaTime);
@@ -356,7 +398,7 @@ namespace Barotrauma.Items.Components
{
linkedItem.Use(deltaTime, null);
var repairable = linkedItem.GetComponent<Repairable>();
if (repairable != null)
if (repairable != null && failedLaunchAttempts < 2)
{
repairable.LastActiveTime = (float)Timing.TotalTime + 1.0f;
}
@@ -411,7 +453,7 @@ namespace Barotrauma.Items.Components
#if SERVER
if (character != null && launchedProjectile != null)
{
string msg = character.LogName + " launched " + item.Name + " (projectile: " + launchedProjectile.Item.Name;
string msg = GameServer.CharacterLogName(character) + " launched " + item.Name + " (projectile: " + launchedProjectile.Item.Name;
var containedItems = launchedProjectile.Item.ContainedItems;
if (containedItems == null || !containedItems.Any())
{
@@ -428,7 +470,7 @@ namespace Barotrauma.Items.Components
return true;
}
private void Launch(Item projectile, Character user = null)
private void Launch(Item projectile, Character user = null, float? launchRotation = null)
{
reload = reloadTime;
@@ -444,7 +486,9 @@ namespace Barotrauma.Items.Components
}
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.SetTransform(
ConvertUnits.ToSimUnits(new Vector2(item.WorldRect.X + transformedBarrelPos.X, item.WorldRect.Y - transformedBarrelPos.Y)),
-(launchRotation ?? rotation) + spread);
projectile.UpdateTransform();
projectile.Submarine = projectile.body?.Submarine;
@@ -456,12 +500,11 @@ namespace Barotrauma.Items.Components
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 (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 });
}
ApplyStatusEffects(ActionType.OnUse, 1.0f, user: user);
@@ -476,8 +519,10 @@ namespace Barotrauma.Items.Components
private float prevTargetRotation;
private float updateTimer;
private bool updatePending;
public void ThalamusOperate(float deltaTime, bool targetHumans, bool targetOtherCreatures, bool targetSubmarines, bool ignoreDelay)
public void ThalamusOperate(WreckAI ai, float deltaTime, bool targetHumans, bool targetOtherCreatures, bool targetSubmarines, bool ignoreDelay)
{
if (ai == null) { return; }
IsActive = true;
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient)
@@ -513,8 +558,8 @@ namespace Barotrauma.Items.Components
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 (character.Params.Group.Equals(ai.Config.Entity, StringComparison.OrdinalIgnoreCase)) { continue; }
bool isHuman = character.IsHuman || character.Params.Group.Equals(CharacterPrefab.HumanSpeciesName, StringComparison.OrdinalIgnoreCase);
if (isHuman)
{
if (!targetHumans)
@@ -619,7 +664,8 @@ namespace Barotrauma.Items.Components
end -= target.Submarine.SimPosition;
}
var collisionCategories = Physics.CollisionWall | Physics.CollisionCharacter | Physics.CollisionItem | Physics.CollisionLevel;
var pickedBody = Submarine.PickBody(start, end, null, collisionCategories);
var pickedBody = Submarine.PickBody(start, end, null, collisionCategories, allowInsideFixture: true,
customPredicate: (Fixture f) => { return !item.StaticFixtures.Contains(f); });
if (pickedBody == null) { return; }
Character targetCharacter = null;
if (pickedBody.UserData is Character c)
@@ -632,16 +678,28 @@ namespace Barotrauma.Items.Components
}
if (targetCharacter != null)
{
if (targetCharacter.Params.Group.Equals("thalamus", StringComparison.OrdinalIgnoreCase))
if (targetCharacter.Params.Group.Equals(ai.Config.Entity, StringComparison.OrdinalIgnoreCase))
{
// Don't shoot friendly characters
return;
}
}
else if (!(pickedBody.UserData is Structure) && !(pickedBody.UserData is Item))
else
{
// Hit something else than a wall or an item (probably a level wall)
return;
if (pickedBody.UserData is ISpatialEntity e)
{
Submarine sub = e.Submarine;
if (sub == null) { return; }
if (!targetSubmarines) { return; }
if (sub == Item.Submarine) { return; }
// Don't shoot non-player submarines, i.e. wrecks or outposts.
if (!sub.Info.IsPlayer) { return; }
}
else
{
// Hit something else, probably a level wall
return;
}
}
TryLaunch(deltaTime, ignorePower: true);
}
@@ -663,6 +721,7 @@ namespace Barotrauma.Items.Components
PowerContainer batteryToLoad = null;
foreach (PowerContainer battery in batteries)
{
if (battery.Item.NonInteractable) { continue; }
if (batteryToLoad == null || battery.Charge < lowestCharge)
{
batteryToLoad = battery;
@@ -683,20 +742,22 @@ namespace Barotrauma.Items.Components
int maxProjectileCount = 0;
foreach (MapEntity e in item.linkedTo)
{
if (!(e is Item projectileContainer)) continue;
var containedItems = projectileContainer.ContainedItems;
if (containedItems != null)
if (item.NonInteractable) { continue; }
if (e is Item projectileContainer)
{
var container = projectileContainer.GetComponent<ItemContainer>();
maxProjectileCount += container.Capacity;
var containedItems = projectileContainer.ContainedItems;
if (containedItems != null)
{
var container = projectileContainer.GetComponent<ItemContainer>();
maxProjectileCount += container.Capacity;
int projectiles = containedItems.Count(it => it.Condition > 0.0f);
usableProjectileCount += projectiles;
int projectiles = containedItems.Count(it => it.Condition > 0.0f);
usableProjectileCount += projectiles;
}
}
}
if (usableProjectileCount == 0 || (usableProjectileCount < maxProjectileCount && objective.Option.Equals("fireatwill", StringComparison.OrdinalIgnoreCase)))
if (usableProjectileCount == 0)
{
ItemContainer container = null;
Item containerItem = null;
@@ -704,11 +765,16 @@ namespace Barotrauma.Items.Components
{
containerItem = e as Item;
if (containerItem == null) { continue; }
if (containerItem.NonInteractable) { continue; }
if (character.AIController is HumanAIController aiController && aiController.IgnoredItems.Contains(containerItem)) { continue; }
container = containerItem.GetComponent<ItemContainer>();
if (container != null) { break; }
}
if (container == null || container.ContainableItems.Count == 0) { return true; }
if (container == null || container.ContainableItems.Count == 0)
{
character.Speak(TextManager.GetWithVariable("DialogCannotLoadTurret", "[itemname]", item.Name, true), null, 0.0f, "cannotloadturret", 30.0f);
return true;
}
if (objective.SubObjectives.None())
{
if (!AIDecontainEmptyItems(character, objective, equip: true, sourceContainer: container))
@@ -719,10 +785,25 @@ namespace Barotrauma.Items.Components
if (objective.SubObjectives.None())
{
var loadItemsObjective = AIContainItems<Turret>(container, character, objective, usableProjectileCount + 1, equip: true, removeEmpty: true);
loadItemsObjective.ignoredContainerIdentifiers = new string[] { containerItem.prefab.Identifier };
character.Speak(TextManager.GetWithVariable("DialogLoadTurret", "[itemname]", item.Name, true), null, 0.0f, "loadturret", 30.0f);
if (loadItemsObjective == null)
{
if (usableProjectileCount == 0)
{
character.Speak(TextManager.GetWithVariable("DialogCannotLoadTurret", "[itemname]", item.Name, true), null, 0.0f, "cannotloadturret", 30.0f);
return true;
}
}
else
{
loadItemsObjective.ignoredContainerIdentifiers = new string[] { containerItem.prefab.Identifier };
character.Speak(TextManager.GetWithVariable("DialogLoadTurret", "[itemname]", item.Name, true), null, 0.0f, "loadturret", 30.0f);
return false;
}
}
if (objective.SubObjectives.Any())
{
return false;
}
return false;
}
//enough shells and power
@@ -753,7 +834,10 @@ namespace Barotrauma.Items.Components
character.AIController.SelectTarget(closestEnemy.AiTarget);
character.CursorPosition = closestEnemy.WorldPosition;
if (item.Submarine != null) { character.CursorPosition -= item.Submarine.Position; }
if (character.Submarine != null)
{
character.CursorPosition -= character.Submarine.Position;
}
float enemyAngle = MathUtils.VectorToAngle(closestEnemy.WorldPosition - item.WorldPosition);
float turretAngle = -rotation;
@@ -769,7 +853,8 @@ namespace Barotrauma.Items.Components
end -= closestEnemy.Submarine.SimPosition;
}
var collisionCategories = Physics.CollisionWall | Physics.CollisionCharacter | Physics.CollisionItem | Physics.CollisionLevel;
var pickedBody = Submarine.PickBody(start, end, null, collisionCategories);
var pickedBody = Submarine.PickBody(start, end, null, collisionCategories, allowInsideFixture: true,
customPredicate: (Fixture f) => { return !item.StaticFixtures.Contains(f); });
if (pickedBody == null) { return false; }
Character targetCharacter = null;
if (pickedBody.UserData is Character c)
@@ -780,23 +865,34 @@ namespace Barotrauma.Items.Components
{
targetCharacter = limb.character;
}
if (targetCharacter != null && HumanAIController.IsFriendly(character, targetCharacter))
if (targetCharacter != null)
{
// Don't shoot friendly characters
return false;
if (HumanAIController.IsFriendly(character, targetCharacter))
{
// Don't shoot friendly characters
return false;
}
}
else if (targetCharacter == null && !(pickedBody.UserData is Structure) && !(pickedBody.UserData is Item))
else
{
// Hit something else than a wall or an item (probably a level wall)
return false;
if (pickedBody.UserData is ISpatialEntity e)
{
Submarine sub = e.Submarine;
if (sub == null) { return false; }
if (sub == Item.Submarine) { return false; }
// Don't shoot non-player submarines, i.e. wrecks or outposts.
if (!sub.Info.IsPlayer) { return false; }
// Don't shoot friendly submarines.
if (sub.TeamID == Item.Submarine.TeamID) { return false; }
}
else
{
// Hit something else, probably a level wall
return false;
}
}
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);
}
character?.Speak(TextManager.GetWithVariable("DialogFireTurret", "[itemname]", item.Name, true), null, 0.0f, "fireturret", 5.0f);
character.SetInput(InputType.Shoot, true, true);
return false;
}
@@ -914,10 +1010,12 @@ namespace Barotrauma.Items.Components
IsActive = true;
}
user = sender;
resetUserTimer = 10.0f;
break;
case "trigger_in":
item.Use((float)Timing.Step, sender);
user = sender;
resetUserTimer = 10.0f;
//triggering the Use method through item.Use will fail if the item is not characterusable and the signal was sent by a character
//so lets do it manually
if (!characterUsable && sender != null)
@@ -936,9 +1034,25 @@ namespace Barotrauma.Items.Components
public void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null)
{
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);
if (extraData.Length > 2)
{
msg.Write(!(extraData[2] is Item item) || item.Removed ? ushort.MaxValue : item.ID);
msg.WriteRangedSingle(MathHelper.Clamp(rotation, minRotation, maxRotation), minRotation, maxRotation, 16);
}
else
{
msg.Write((ushort)0);
float wrappedTargetRotation = targetRotation;
while (wrappedTargetRotation < minRotation && MathUtils.IsValid(wrappedTargetRotation))
{
wrappedTargetRotation += MathHelper.TwoPi;
}
while (wrappedTargetRotation > maxRotation && MathUtils.IsValid(wrappedTargetRotation))
{
wrappedTargetRotation -= MathHelper.TwoPi;
}
msg.WriteRangedSingle(MathHelper.Clamp(wrappedTargetRotation, minRotation, maxRotation), minRotation, maxRotation, 16);
}
}
}
}
@@ -1,7 +1,7 @@
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.IO;
using Barotrauma.IO;
using System.Linq;
using System.Xml.Linq;
using Barotrauma.Items.Components;
@@ -221,11 +221,11 @@ namespace Barotrauma.Items.Components
get { return variant; }
set
{
#if SERVER
if (variant == value) { return; }
#if SERVER
variant = value;
item.CreateServerEvent(this);
#elif CLIENT
if (variant == value) { return; }
Character character = picker;
if (character != null)
@@ -370,11 +370,12 @@ namespace Barotrauma.Items.Components
public override void Unequip(Character character)
{
if (picker == null) return;
if (character == null || character.Removed) { return; }
if (picker == null) { return; }
for (int i = 0; i < wearableSprites.Length; i++)
{
Limb equipLimb = character.AnimController.GetLimb(limbType[i]);
if (equipLimb == null) continue;
if (equipLimb == null) { continue; }
if (wearableSprites[i].LightComponent != null)
{
@@ -385,7 +386,6 @@ namespace Barotrauma.Items.Components
#if CLIENT
equipLimb.UpdateWearableTypesToHide();
#endif
limb[i] = null;
}
@@ -419,9 +419,14 @@ namespace Barotrauma.Items.Components
{
base.RemoveComponentSpecific();
Unequip(picker);
foreach (WearableSprite wearableSprite in wearableSprites)
{
if (wearableSprite != null && wearableSprite.Sprite != null) wearableSprite.Sprite.Remove();
if (wearableSprite != null && wearableSprite.Sprite != null)
{
wearableSprite.Sprite.Remove();
}
}
}