Merge remote-tracking branch 'origin/master' into dev

# Conflicts:
#	Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs
#	Barotrauma/BarotraumaClient/ClientSource/Events/EventManager.cs
#	Barotrauma/BarotraumaClient/ClientSource/GUI/CrewManagement.cs
#	Barotrauma/BarotraumaClient/ClientSource/GUI/MedicalClinicUI.cs
#	Barotrauma/BarotraumaClient/ClientSource/GUI/Store.cs
#	Barotrauma/BarotraumaClient/ClientSource/GUI/SubmarineSelection.cs
#	Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/CampaignMode.cs
#	Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/MultiPlayerCampaign.cs
#	Barotrauma/BarotraumaClient/ClientSource/GameSession/RoundSummary.cs
#	Barotrauma/BarotraumaClient/ClientSource/Items/Components/Door.cs
#	Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Fabricator.cs
#	Barotrauma/BarotraumaClient/ClientSource/Items/Components/Projectile.cs
#	Barotrauma/BarotraumaClient/ClientSource/Map/Lights/ConvexHull.cs
#	Barotrauma/BarotraumaClient/ClientSource/Map/Lights/LightManager.cs
#	Barotrauma/BarotraumaClient/ClientSource/Map/Lights/LightSource.cs
#	Barotrauma/BarotraumaClient/ClientSource/Map/Structure.cs
#	Barotrauma/BarotraumaClient/ClientSource/Networking/GameClient.cs
#	Barotrauma/BarotraumaClient/ClientSource/Networking/Voip/VoipClient.cs
#	Barotrauma/BarotraumaClient/ClientSource/Networking/Voting.cs
#	Barotrauma/BarotraumaClient/ClientSource/Screens/ServerListScreen/ServerListScreen.cs
#	Barotrauma/BarotraumaClient/LinuxClient.csproj
#	Barotrauma/BarotraumaClient/MacClient.csproj
#	Barotrauma/BarotraumaClient/WindowsClient.csproj
#	Barotrauma/BarotraumaServer/LinuxServer.csproj
#	Barotrauma/BarotraumaServer/MacServer.csproj
#	Barotrauma/BarotraumaServer/ServerSource/DebugConsole.cs
#	Barotrauma/BarotraumaServer/ServerSource/GameSession/MedicalClinic.cs
#	Barotrauma/BarotraumaServer/ServerSource/Items/Inventory.cs
#	Barotrauma/BarotraumaServer/ServerSource/Networking/GameServer.cs
#	Barotrauma/BarotraumaServer/ServerSource/Utils/DoSProtection.cs
#	Barotrauma/BarotraumaServer/WindowsServer.csproj
#	Barotrauma/BarotraumaShared/SharedSource/Characters/AI/HumanAIController.cs
#	Barotrauma/BarotraumaShared/SharedSource/Characters/AI/IndoorsSteeringManager.cs
#	Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveContainItem.cs
#	Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveFindSafety.cs
#	Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveGetItem.cs
#	Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveGoTo.cs
#	Barotrauma/BarotraumaShared/SharedSource/Characters/AI/Objectives/AIObjectiveIdle.cs
#	Barotrauma/BarotraumaShared/SharedSource/Characters/AI/ShipCommand/ShipIssueWorkerOperateWeapons.cs
#	Barotrauma/BarotraumaShared/SharedSource/Characters/Attack.cs
#	Barotrauma/BarotraumaShared/SharedSource/Characters/Character.cs
#	Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/Affliction.cs
#	Barotrauma/BarotraumaShared/SharedSource/Characters/Health/Afflictions/AfflictionPrefab.cs
#	Barotrauma/BarotraumaShared/SharedSource/Characters/Health/CharacterHealth.cs
#	Barotrauma/BarotraumaShared/SharedSource/Characters/Params/CharacterParams.cs
#	Barotrauma/BarotraumaShared/SharedSource/ContentManagement/ContentPackageManager.cs
#	Barotrauma/BarotraumaShared/SharedSource/Enums.cs
#	Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/MissionAction.cs
#	Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/NPCChangeTeamAction.cs
#	Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/NPCFollowAction.cs
#	Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/NPCWaitAction.cs
#	Barotrauma/BarotraumaShared/SharedSource/Events/EventManager.cs
#	Barotrauma/BarotraumaShared/SharedSource/Events/Missions/Mission.cs
#	Barotrauma/BarotraumaShared/SharedSource/Events/Missions/PirateMission.cs
#	Barotrauma/BarotraumaShared/SharedSource/Events/Missions/SalvageMission.cs
#	Barotrauma/BarotraumaShared/SharedSource/GameSession/CargoManager.cs
#	Barotrauma/BarotraumaShared/SharedSource/GameSession/Data/Reputation.cs
#	Barotrauma/BarotraumaShared/SharedSource/GameSession/GameModes/CampaignMode.cs
#	Barotrauma/BarotraumaShared/SharedSource/GameSession/GameSession.cs
#	Barotrauma/BarotraumaShared/SharedSource/Items/Components/Door.cs
#	Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/LevelResource.cs
#	Barotrauma/BarotraumaShared/SharedSource/Items/Components/Holdable/RangedWeapon.cs
#	Barotrauma/BarotraumaShared/SharedSource/Items/Components/ItemContainer.cs
#	Barotrauma/BarotraumaShared/SharedSource/Items/Components/Projectile.cs
#	Barotrauma/BarotraumaShared/SharedSource/Items/Components/Signal/LightComponent.cs
#	Barotrauma/BarotraumaShared/SharedSource/Items/Components/Turret.cs
#	Barotrauma/BarotraumaShared/SharedSource/Items/Item.cs
#	Barotrauma/BarotraumaShared/SharedSource/Items/ItemPrefab.cs
#	Barotrauma/BarotraumaShared/SharedSource/Map/Gap.cs
#	Barotrauma/BarotraumaShared/SharedSource/Map/Levels/Level.cs
#	Barotrauma/BarotraumaShared/SharedSource/Map/Map/Location.cs
#	Barotrauma/BarotraumaShared/SharedSource/Map/Map/Map.cs
#	Barotrauma/BarotraumaShared/SharedSource/Map/Outposts/OutpostGenerator.cs
#	Barotrauma/BarotraumaShared/SharedSource/Map/Submarine.cs
#	Barotrauma/BarotraumaShared/SharedSource/Map/SubmarineBody.cs
#	Barotrauma/BarotraumaShared/SharedSource/Networking/ServerSettings.cs
#	Barotrauma/BarotraumaShared/SharedSource/Settings/GameSettings.cs
#	Barotrauma/BarotraumaShared/SharedSource/StatusEffects/PropertyConditional.cs
#	Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs
#	Barotrauma/BarotraumaShared/changelog.txt
This commit is contained in:
Regalis11
2023-06-15 16:55:39 +03:00
238 changed files with 5395 additions and 3045 deletions
@@ -1,13 +1,15 @@
using Barotrauma.Networking;
using Barotrauma.IO;
using Barotrauma.Networking;
using FarseerPhysics;
using FarseerPhysics.Dynamics;
using FarseerPhysics.Dynamics.Joints;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using Barotrauma.IO;
using System.Linq;
using System.Xml.Linq;
#if CLIENT
using Barotrauma.Lights;
#endif
namespace Barotrauma.Items.Components
{
@@ -243,10 +245,12 @@ namespace Barotrauma.Items.Components
if (!target.item.Submarine.DockedTo.Contains(item.Submarine))
{
target.item.Submarine.ConnectedDockingPorts.Add(item.Submarine, target);
target.item.Submarine.RefreshConnectedSubs();
}
if (!item.Submarine.DockedTo.Contains(target.item.Submarine))
{
item.Submarine.ConnectedDockingPorts.Add(target.item.Submarine, this);
item.Submarine.RefreshConnectedSubs();
}
DockingTarget = target;
@@ -558,6 +562,7 @@ namespace Barotrauma.Items.Components
var subs = new Submarine[] { item.Submarine, DockingTarget.item.Submarine };
bodies = new Body[4];
RemoveConvexHulls();
if (DockingTarget.Door != null)
{
@@ -648,8 +653,10 @@ namespace Barotrauma.Items.Components
hullRects[i].X -= expand;
hullRects[i].Width += expand * 2;
hullRects[i].Location -= MathUtils.ToPoint(subs[i].WorldPosition - subs[i].HiddenSubPosition);
hulls[i] = new Hull(hullRects[i], subs[i]);
hulls[i].RoomName = IsHorizontal ? "entityname.dockingport" : "entityname.dockinghatch";
hulls[i] = new Hull(hullRects[i], subs[i])
{
RoomName = IsHorizontal ? "entityname.dockingport" : "entityname.dockinghatch"
};
hulls[i].AddToGrid(subs[i]);
hulls[i].FreeID();
@@ -661,6 +668,15 @@ namespace Barotrauma.Items.Components
BodyType.Static);
}
}
#if CLIENT
for (int i = 0; i < 2; i++)
{
convexHulls[i] =
new ConvexHull(new Rectangle(
new Point((int)item.Position.X, item.Rect.Y - item.Rect.Height * i),
new Point((int)(DockingTarget.item.WorldPosition.X - item.WorldPosition.X), 0)), IsHorizontal, item);
}
#endif
if (rightHullDiff <= 100 && hulls[0].Submarine != null)
{
@@ -764,15 +780,17 @@ namespace Barotrauma.Items.Components
hullRects[1].Height += midHullDiff / 2 + 1;
}
int expand = 5;
for (int i = 0; i < 2; i++)
{
hullRects[i].Y += expand;
hullRects[i].Height += expand * 2;
hullRects[i].Location -= MathUtils.ToPoint(subs[i].WorldPosition - subs[i].HiddenSubPosition);
hulls[i] = new Hull(hullRects[i], subs[i]);
hulls[i].RoomName = IsHorizontal ? "entityname.dockingport" : "entityname.dockinghatch";
hulls[i] = new Hull(hullRects[i], subs[i])
{
RoomName = IsHorizontal ? "entityname.dockingport" : "entityname.dockinghatch",
AvoidStaying = true
};
hulls[i].AddToGrid(subs[i]);
hulls[i].FreeID();
@@ -784,6 +802,15 @@ namespace Barotrauma.Items.Components
BodyType.Static);
}
}
#if CLIENT
for (int i = 0; i < 2; i++)
{
convexHulls[i] =
new ConvexHull(new Rectangle(
new Point(item.Rect.X + item.Rect.Width * i, (int)item.Position.Y),
new Point(0, (int)(DockingTarget.item.WorldPosition.Y - item.WorldPosition.Y))), IsHorizontal, item);
}
#endif
if (midHullDiff <= 100 && hulls[0].Submarine != null)
{
@@ -822,6 +849,8 @@ namespace Barotrauma.Items.Components
}
}
partial void RemoveConvexHulls();
private void LinkHullsToGaps()
{
if (gap == null || hulls == null || hulls[0] == null || hulls[1] == null)
@@ -916,7 +945,9 @@ namespace Barotrauma.Items.Components
}
DockingTarget.item.Submarine.ConnectedDockingPorts.Remove(item.Submarine);
DockingTarget.item.Submarine.RefreshConnectedSubs();
item.Submarine.ConnectedDockingPorts.Remove(DockingTarget.item.Submarine);
item.Submarine.RefreshConnectedSubs();
if (Door != null && DockingTarget.Door != null)
{
@@ -976,6 +1007,8 @@ namespace Barotrauma.Items.Components
hulls[0]?.Remove(); hulls[0] = null;
hulls[1]?.Remove(); hulls[1] = null;
RemoveConvexHulls();
if (gap != null)
{
gap.Remove();
@@ -1091,6 +1124,7 @@ namespace Barotrauma.Items.Components
hulls[0]?.Remove(); hulls[0] = null;
hulls[1]?.Remove(); hulls[1] = null;
gap?.Remove(); gap = null;
RemoveConvexHulls();
overlaySprite?.Remove();
overlaySprite = null;
@@ -69,7 +69,7 @@ namespace Barotrauma.Items.Components
private bool isBroken;
public bool CanBeTraversed => (IsOpen || IsBroken) && !IsJammed && !IsStuck && !Impassable;
public bool CanBeTraversed => !Impassable && (IsBroken || IsOpen);
public bool IsBroken
{
@@ -186,13 +186,19 @@ namespace Barotrauma.Items.Components
{
get { return openState; }
set
{
{
openState = MathHelper.Clamp(value, 0.0f, 1.0f);
#if CLIENT
float size = IsHorizontal ? item.Rect.Width : item.Rect.Height;
if (Math.Abs(lastConvexHullState - openState) * size < 5.0f) { return; }
UpdateConvexHulls();
lastConvexHullState = openState;
//refresh convex hulls if the body of the door has moved by 5 pixels,
//or if it becomes fully closed or fully open
if (Math.Abs(lastConvexHullState - openState) * size > 5.0f ||
(openState <= 0.0f && lastConvexHullState > 0.0f) ||
(openState >= 1.0f && lastConvexHullState < 1.0f))
{
UpdateConvexHulls();
lastConvexHullState = openState;
}
#endif
}
}
@@ -383,6 +389,8 @@ namespace Barotrauma.Items.Components
return;
}
bool isClosing = false;
if ((!IsStuck && !IsJammed) || !isOpen)
{
@@ -521,8 +529,11 @@ namespace Barotrauma.Items.Components
{
RefreshLinkedGap();
#if CLIENT
convexHull = new ConvexHull(Rectangle.Empty, !IsHorizontal, item);
if (Window != Rectangle.Empty) { convexHull2 = new ConvexHull(Rectangle.Empty, !IsHorizontal, item); }
convexHull = new ConvexHull(doorRect, IsHorizontal, item);
if (Window != Rectangle.Empty)
{
convexHull2 = new ConvexHull(doorRect, IsHorizontal, item);
}
UpdateConvexHulls();
#endif
}
@@ -556,6 +567,12 @@ namespace Barotrauma.Items.Components
gap.ConnectedDoor = null;
}
}
if (OutsideSubmarineFixture != null)
{
OutsideSubmarineFixture.Body.Remove(OutsideSubmarineFixture);
OutsideSubmarineFixture = null;
}
//no need to remove the gap if we're unloading the whole submarine
//otherwise the gap will be removed twice and cause console warnings
@@ -1,10 +1,9 @@
using System;
using System.Collections.Generic;
using System.Xml.Linq;
using System.Linq;
using Barotrauma.Extensions;
using Barotrauma.Extensions;
using Barotrauma.Networking;
using Microsoft.Xna.Framework;
using System;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Items.Components
{
@@ -96,11 +95,16 @@ namespace Barotrauma.Items.Components
if (selectedEffect != null)
{
targetCharacter = character;
ApplyStatusEffects(ActionType.OnWearing, 1.0f);
ApplyStatusEffects(ActionType.OnWearing, 1.0f, targetCharacter);
float selectedEffectStrength = GetCombinedEffectStrength();
character.CharacterHealth.ApplyAffliction(null, selectedEffect.Instantiate(selectedEffectStrength));
var affliction = character.CharacterHealth.GetAllAfflictions().FirstOrDefault(a => a.Prefab == selectedEffect);
if (affliction != null) { affliction.Strength = selectedEffectStrength; }
if (affliction != null)
{
affliction.Strength = selectedEffectStrength;
//force strength to the correct value to bypass any clamping e.g. AfflictionHusk might be doing
affliction.SetStrength(selectedEffectStrength);
}
#if SERVER
item.CreateServerEvent(this);
#endif
@@ -110,7 +114,12 @@ namespace Barotrauma.Items.Components
float selectedTaintedEffectStrength = GetCombinedTaintedEffectStrength();
character.CharacterHealth.ApplyAffliction(null, selectedTaintedEffect.Instantiate(selectedTaintedEffectStrength));
var affliction = character.CharacterHealth.GetAllAfflictions().FirstOrDefault(a => a.Prefab == selectedTaintedEffect);
if (affliction != null) { affliction.Strength = selectedTaintedEffectStrength; }
if (affliction != null)
{
affliction.Strength = selectedTaintedEffectStrength;
//force strength to the correct value to bypass any clamping e.g. AfflictionHusk might be doing
affliction.SetStrength(selectedTaintedEffectStrength);
}
targetCharacter = character;
#if SERVER
item.CreateServerEvent(this);
@@ -127,7 +136,7 @@ namespace Barotrauma.Items.Components
base.Update(deltaTime, cam);
if (targetCharacter != null)
{
var rootContainer = item.GetRootContainer();
var rootContainer = item.RootContainer;
if (!targetCharacter.HasEquippedItem(item) &&
(rootContainer == null || !targetCharacter.HasEquippedItem(rootContainer) || !targetCharacter.Inventory.IsInLimbSlot(rootContainer, InvSlotType.HealthInterface)))
{
@@ -220,7 +229,7 @@ namespace Barotrauma.Items.Components
return MathHelper.Clamp(probability, 0.0f, 1.0f);
}
private float GetTaintedProbabilityOnCombine(Character user)
private static float GetTaintedProbabilityOnCombine(Character user)
{
if (user == null) { return 1.0f; }
float probability = 1.0f - user.GetStatValue(StatTypes.GeneticMaterialTaintedProbabilityReductionOnCombine);
@@ -409,6 +409,7 @@ namespace Barotrauma.Items.Components
private int leafVariants;
private int[] flowerTiles;
[Serialize(100.0f, IsPropertySaveable.Yes)]
public float Health
{
get => health;
@@ -321,12 +321,12 @@ namespace Barotrauma.Items.Components
}
}
public override void Drop(Character dropper)
public override void Drop(Character dropper, bool setTransform = true)
{
Drop(true, dropper);
Drop(true, dropper, setTransform);
}
private void Drop(bool dropConnectedWires, Character dropper)
private void Drop(bool dropConnectedWires, Character dropper, bool setTransform = true)
{
GetRope()?.Snap();
if (dropConnectedWires)
@@ -343,8 +343,11 @@ namespace Barotrauma.Items.Components
DeattachFromWall();
}
if (Pusher != null) { Pusher.Enabled = false; }
if (item.body != null) { item.body.Enabled = true; }
if (setTransform)
{
if (Pusher != null) { Pusher.Enabled = false; }
if (item.body != null) { item.body.Enabled = true; }
}
IsActive = false;
attachTargetCell = null;
@@ -357,7 +360,7 @@ namespace Barotrauma.Items.Components
item.Submarine = picker.Submarine;
if (item.body != null)
if (item.body != null && setTransform)
{
if (item.body.Removed)
{
@@ -599,6 +602,10 @@ namespace Barotrauma.Items.Components
throw new InvalidOperationException($"Tried to attach an item with no physics body to a wall ({item.Prefab.Identifier}).");
}
body.Enabled = false;
body.SetTransformIgnoreContacts(body.SimPosition, rotation: 0.0f);
item.body = null;
//outside hulls/subs -> we need to check if the item is being attached on a structure outside the sub
if (item.CurrentHull == null && item.Submarine == null)
{
@@ -638,9 +645,6 @@ namespace Barotrauma.Items.Components
}
}
body.Enabled = false;
item.body = null;
DisplayMsg = prevMsg;
PickKey = prevPickKey;
requiredItems = new Dictionary<RelatedItem.RelationType, List<RelatedItem>>(prevRequiredItems);
@@ -700,7 +704,8 @@ namespace Barotrauma.Items.Components
}
Vector2 attachPos = GetAttachPosition(character, useWorldCoordinates: true);
Submarine attachSubmarine = Structure.GetAttachTarget(attachPos)?.Submarine ?? item.Submarine;
int maxAttachableCount = (int)character.Info.GetSavedStatValue(StatTypes.MaxAttachableCount, item.Prefab.Identifier);
int maxAttachableCount = (int)character.Info.GetSavedStatValueWithBotsInMp(StatTypes.MaxAttachableCount, item.Prefab.Identifier);
int currentlyAttachedCount = Item.ItemList.Count(
i => i.Submarine == attachSubmarine && i.GetComponent<Holdable>() is Holdable holdable && holdable.Attached && i.Prefab.Identifier == item.Prefab.Identifier);
if (maxAttachableCount == 0)
@@ -811,7 +816,7 @@ namespace Barotrauma.Items.Components
foreach (var edge in cell.Edges)
{
if (!edge.IsSolid) { continue; }
if (MathUtils.GetLineIntersection(edge.Point1, edge.Point2, user.WorldPosition, attachPos, out Vector2 intersection))
if (MathUtils.GetLineSegmentIntersection(edge.Point1, edge.Point2, user.WorldPosition, attachPos, out Vector2 intersection))
{
attachPos = intersection;
edgeFound = true;
@@ -97,11 +97,18 @@ namespace Barotrauma.Items.Components
{
if (holdable != null && !holdable.Attached)
{
trigger.Enabled = false;
if (trigger != null)
{
trigger.Enabled = false;
}
IsActive = false;
}
else
{
if (trigger == null)
{
CreateTriggerBody();
}
if (trigger != null && Vector2.DistanceSquared(item.SimPosition, trigger.SimPosition) > 0.01f)
{
trigger.SetTransform(item.SimPosition, 0.0f);
@@ -123,12 +130,15 @@ namespace Barotrauma.Items.Components
{
holdable.PickingTime = float.MaxValue;
}
}
private void CreateTriggerBody()
{
System.Diagnostics.Debug.Assert(trigger == null, "LevelResource trigger already created!");
var body = item.body ?? holdable.Body;
if (body != null)
if (body != null && Attached)
{
trigger = new PhysicsBody(body.Width, body.Height, body.Radius,
trigger = new PhysicsBody(body.Width, body.Height, body.Radius,
body.Density,
BodyType.Static,
Physics.CollisionWall,
@@ -143,7 +153,6 @@ namespace Barotrauma.Items.Components
protected override void RemoveComponentSpecific()
{
base.RemoveComponentSpecific();
if (trigger != null)
{
trigger.Remove();
@@ -170,9 +170,9 @@ namespace Barotrauma.Items.Components
return characterUsable || character == null;
}
public override void Drop(Character dropper)
public override void Drop(Character dropper, bool setTransform = true)
{
base.Drop(dropper);
base.Drop(dropper, setTransform);
hitting = false;
hitPos = 0.0f;
}
@@ -241,12 +241,9 @@ namespace Barotrauma.Items.Components
}
}
public override void Drop(Character dropper)
public override void Drop(Character dropper, bool setTransform = true)
{
if (picker == null)
{
picker = dropper;
}
picker ??= dropper;
Vector2 bodyDropPos = Vector2.Zero;
@@ -255,8 +252,7 @@ namespace Barotrauma.Items.Components
if (item.ParentInventory != null && item.ParentInventory.Owner != null && !item.ParentInventory.Owner.Removed)
{
bodyDropPos = item.ParentInventory.Owner.SimPosition;
if (item.body != null) item.body.ResetDynamics();
item.body?.ResetDynamics();
}
}
else if (!picker.Removed)
@@ -270,7 +266,7 @@ namespace Barotrauma.Items.Components
picker = null;
}
if (item.body != null && !item.body.Enabled)
if (item.body != null && !item.body.Enabled && setTransform)
{
if (item.body.Removed)
{
@@ -260,7 +260,7 @@ namespace Barotrauma.Items.Components
{
Vector2 barrelPos = TransformedBarrelPos + item.body.SimPosition;
float rotation = (Item.body.Dir == 1.0f) ? Item.body.Rotation : Item.body.Rotation - MathHelper.Pi;
float spread = GetSpread(character) * Projectile.GetSpreadFromPool();
float spread = GetSpread(character) * projectile.GetSpreadFromPool();
var lastProjectile = LastProjectile;
if (lastProjectile != projectile)
@@ -277,7 +277,7 @@ namespace Barotrauma.Items.Components
{
Item.body.ApplyLinearImpulse(new Vector2((float)Math.Cos(projectile.Item.body.Rotation), (float)Math.Sin(projectile.Item.body.Rotation)) * Item.body.Mass * -50.0f, maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
}
projectile.Item.body.ApplyTorque(projectile.Item.body.Mass * degreeOfFailure * 20.0f * Projectile.GetSpreadFromPool());
projectile.Item.body.ApplyTorque(projectile.Item.body.Mass * degreeOfFailure * 20.0f * projectile.GetSpreadFromPool());
}
Item.RemoveContained(projectile.Item);
}
@@ -84,6 +84,9 @@ namespace Barotrauma.Items.Components
[Serialize(false, IsPropertySaveable.No, description: "Can the item repair multiple things at once, or will it only affect the first thing the ray from the barrel hits.")]
public bool RepairMultiple { get; set; }
[Serialize(true, IsPropertySaveable.No, description: "Can the item repair multiple walls at once? Only relevant if RepairMultiple is true.")]
public bool RepairMultipleWalls { get; set; }
[Serialize(false, IsPropertySaveable.No, description: "Can the item repair things through holes in walls.")]
public bool RepairThroughHoles { get; set; }
@@ -383,6 +386,7 @@ namespace Barotrauma.Items.Components
//stop the ray if it already hit a door/wall and is now about to hit some other type of entity
if (lastHitType == typeof(Item) || lastHitType == typeof(Structure)) { break; }
}
if (!RepairMultipleWalls && (bodyType == typeof(Structure) || (body.UserData as Item)?.GetComponent<Door>() != null)) { break; }
Character hitCharacter = null;
if (body.UserData is Limb limb)
@@ -56,9 +56,9 @@ namespace Barotrauma.Items.Components
return false;
}
public override void Drop(Character dropper)
public override void Drop(Character dropper, bool setTransform = true)
{
base.Drop(dropper);
base.Drop(dropper, setTransform);
throwState = ThrowState.None;
throwAngle = ThrowAngleStart;
Item.ResetWaterDragCoefficient();
@@ -237,7 +237,7 @@ namespace Barotrauma.Items.Components
set;
}
[Serialize(0f, IsPropertySaveable.No, description: "How useful the item is in combat? Used by AI to decide which item it should use as a weapon. For the sake of clarity, use a value between 0 and 100 (not enforced). Note that there's also a generic BotPriority for all item prefabs.")]
[Serialize(0f, IsPropertySaveable.No, description: "How useful the item is in combat? Used by AI to decide which item it should use as a weapon. For the sake of clarity, use a value between 0 and 100 (not forced). Note that there's also a generic BotPriority for all item prefabs.")]
public float CombatPriority { get; private set; }
/// <summary>
@@ -435,7 +435,7 @@ namespace Barotrauma.Items.Components
}
/// <summary>a Character has dropped the item</summary>
public virtual void Drop(Character dropper) { }
public virtual void Drop(Character dropper, bool setTransform = true) { }
/// <returns>true if the operation was completed</returns>
public virtual bool CrewAIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective)
@@ -682,9 +682,10 @@ namespace Barotrauma.Items.Components
public virtual void FlipY(bool relativeToSub) { }
public bool IsNotEmpty(Character user, bool checkContainedItems = true) =>
HasRequiredContainedItems(user, addMessage: false) &&
(!checkContainedItems || Item.OwnInventory == null || Item.OwnInventory.AllItems.Any(i => i.Condition > 0));
/// <summary>
/// Shorthand for !HasRequiredContainedItems()
/// </summary>
public bool IsEmpty(Character user) => !HasRequiredContainedItems(user, addMessage: false);
public bool HasRequiredContainedItems(Character user, bool addMessage, LocalizedString msg = null)
{
@@ -716,20 +717,50 @@ namespace Barotrauma.Items.Components
{
if (character.IsBot && item.IgnoreByAI(character)) { return false; }
if (!item.IsInteractable(character)) { return false; }
if (requiredItems.None()) { return true; }
if (character.Inventory != null)
if (requiredItems.Count == 0) { return true; }
if (character.Inventory != null && requiredItems.TryGetValue(RelatedItem.RelationType.Picked, out List<RelatedItem> relatedItems))
{
foreach (Item item in character.Inventory.AllItems)
foreach (RelatedItem relatedItem in relatedItems)
{
if (requiredItems.Any(ri => ri.Value.Any(r => r.Type == RelatedItem.RelationType.Picked && r.MatchesItem(item))))
foreach (Item otherItem in character.Inventory.AllItems)
{
return true;
}
if (relatedItem.MatchesItem(otherItem))
{
if (otherItem.GetComponent<IdCard>() is IdCard idCard)
{
if (!CheckIdCardAccess(relatedItem, idCard))
{
continue;
}
}
return true;
}
}
}
}
return false;
}
/// <summary>
/// Presumes that matching is already checked.
/// </summary>
private bool CheckIdCardAccess(RelatedItem relatedItem, IdCard idCard)
{
if (item.Submarine != null)
{
//id cards don't work in enemy subs (except on items that only require the default "idcard" tag)
if (idCard.TeamID != CharacterTeamType.None && idCard.TeamID != item.Submarine.TeamID && relatedItem.Identifiers.Any(id => id != "idcard"))
{
return false;
}
else if (idCard.SubmarineSpecificID != 0 && item.Submarine.SubmarineSpecificIDTag != idCard.SubmarineSpecificID)
{
return false;
}
}
return true;
}
public virtual bool HasRequiredItems(Character character, bool addMessage, LocalizedString msg = null)
{
if (requiredItems.None()) { return true; }
@@ -765,23 +796,14 @@ namespace Barotrauma.Items.Components
bool CheckItems(RelatedItem relatedItem, IEnumerable<Item> itemList)
{
bool Predicate(Item it)
bool Predicate(Item it)
{
if (it == null || it.Condition <= 0.0f || !relatedItem.MatchesItem(it)) { return false; }
if (item.Submarine != null)
if (it.GetComponent<IdCard>() is IdCard idCard)
{
var idCard = it.GetComponent<IdCard>();
if (idCard != null)
if (!CheckIdCardAccess(relatedItem, idCard))
{
//id cards don't work in enemy subs (except on items that only require the default "idcard" tag)
if (idCard.TeamID != CharacterTeamType.None && idCard.TeamID != item.Submarine.TeamID && relatedItem.Identifiers.Any(id => id != "idcard"))
{
return false;
}
else if (idCard.SubmarineSpecificID != 0 && item.Submarine.SubmarineSpecificIDTag != idCard.SubmarineSpecificID)
{
return false;
}
return false;
}
}
return true;
@@ -20,11 +20,13 @@ namespace Barotrauma.Items.Components
{
public readonly int MaxStackSize;
public List<RelatedItem> ContainableItems;
public readonly bool AutoInject;
public SlotRestrictions(int maxStackSize, List<RelatedItem> containableItems)
public SlotRestrictions(int maxStackSize, List<RelatedItem> containableItems, bool autoInject)
{
MaxStackSize = maxStackSize;
ContainableItems = containableItems;
AutoInject = autoInject;
}
public bool MatchesItem(Item item)
@@ -269,7 +271,7 @@ namespace Barotrauma.Items.Components
List<SlotRestrictions> newSlotRestrictions = new List<SlotRestrictions>(totalCapacity);
for (int i = 0; i < capacity; i++)
{
newSlotRestrictions.Add(new SlotRestrictions(maxStackSize, ContainableItems));
newSlotRestrictions.Add(new SlotRestrictions(maxStackSize, ContainableItems, autoInject: false));
}
int subContainerIndex = capacity;
@@ -279,6 +281,7 @@ namespace Barotrauma.Items.Components
int subCapacity = subElement.GetAttributeInt("capacity", 1);
int subMaxStackSize = subElement.GetAttributeInt("maxstacksize", maxStackSize);
bool autoInject = subElement.GetAttributeBool("autoinject", false);
var subContainableItems = new List<RelatedItem>();
foreach (var subSubElement in subElement.Elements())
@@ -298,7 +301,7 @@ namespace Barotrauma.Items.Components
for (int i = subContainerIndex; i < subContainerIndex + subCapacity; i++)
{
newSlotRestrictions.Add(new SlotRestrictions(subMaxStackSize, subContainableItems));
newSlotRestrictions.Add(new SlotRestrictions(subMaxStackSize, subContainableItems, autoInject));
}
subContainerIndex += subCapacity;
}
@@ -466,7 +469,7 @@ namespace Barotrauma.Items.Components
prevContainedItemPositions = item.Position;
}
if (AutoInject)
if (AutoInject || slotRestrictions.Any(s => s.AutoInject))
{
//normally autoinjection should delete the (medical) item, so it only gets applied once
//but in multiplayer clients aren't allowed to remove items themselves, so they may be able to trigger this dozens of times
@@ -480,7 +483,21 @@ namespace Barotrauma.Items.Components
ownerCharacter.HealthPercentage / 100f <= AutoInjectThreshold &&
ownerCharacter.HasEquippedItem(item))
{
foreach (Item item in Inventory.AllItemsMod)
if (AutoInject)
{
Inventory.AllItemsMod.ForEach(i => Inject(i));
}
else
{
for (int i = 0; i < slotRestrictions.Length; i++)
{
if (slotRestrictions[i].AutoInject)
{
Inventory.GetItemsAt(i).ForEachMod(i => Inject(i));
}
}
}
void Inject(Item item)
{
item.ApplyStatusEffects(ActionType.OnSuccess, 1.0f, ownerCharacter, useTarget: ownerCharacter);
item.ApplyStatusEffects(ActionType.OnUse, 1.0f, ownerCharacter, useTarget: ownerCharacter);
@@ -511,25 +528,28 @@ namespace Barotrauma.Items.Components
if (activeContainedItem.ExcludeFullCondition && contained.IsFullCondition) { continue; }
StatusEffect effect = activeContainedItem.StatusEffect;
targets.Clear();
bool wearing = item.GetComponent<Wearable>() is Wearable { IsActive: true };
if (effect.HasTargetType(StatusEffect.TargetType.This))
{
effect.Apply(ActionType.OnContaining, deltaTime, item, item.AllPropertyObjects);
targets.AddRange(item.AllPropertyObjects);
}
if (effect.HasTargetType(StatusEffect.TargetType.Contained))
{
effect.Apply(ActionType.OnContaining, deltaTime, item, contained.AllPropertyObjects);
targets.AddRange(contained.AllPropertyObjects);
}
if (effect.HasTargetType(StatusEffect.TargetType.Character) && item.ParentInventory?.Owner is Character character)
{
effect.Apply(ActionType.OnContaining, deltaTime, item, character);
targets.Add(character);
}
if (effect.HasTargetType(StatusEffect.TargetType.NearbyItems) ||
effect.HasTargetType(StatusEffect.TargetType.NearbyCharacters))
{
targets.Clear();
effect.AddNearbyTargets(item.WorldPosition, targets);
effect.Apply(ActionType.OnActive, deltaTime, item, targets);
}
effect.Apply(ActionType.OnActive, deltaTime, item, targets);
effect.Apply(ActionType.OnContaining, deltaTime, item, targets);
if (wearing) { effect.Apply(ActionType.OnWearing, deltaTime, item, targets); }
}
}
@@ -629,7 +649,7 @@ namespace Barotrauma.Items.Components
return false;
}
public override void Drop(Character dropper)
public override void Drop(Character dropper, bool setTransform = true)
{
IsActive = true;
SetContainedActive(false);
@@ -98,6 +98,20 @@ namespace Barotrauma.Items.Components
set;
}
[Serialize(true, IsPropertySaveable.No, description: "Can another character select this controller when another character has already selected it?")]
public bool AllowSelectingWhenSelectedByOther
{
get;
set;
}
[Serialize(true, IsPropertySaveable.No, description: "Can another character select this controller when a bot has already selected it?")]
public bool AllowSelectingWhenSelectedByBot
{
get;
set;
}
public bool ControlCharacterPose
{
get { return limbPositions.Count > 0; }
@@ -466,8 +480,18 @@ namespace Barotrauma.Items.Components
IsActive = false;
CancelUsing(user);
user = null;
return false;
}
else if (user.IsBot && !activator.IsBot)
{
if (AllowSelectingWhenSelectedByBot)
{
CancelUsing(user);
user = activator;
IsActive = true;
return true;
}
}
return AllowSelectingWhenSelectedByOther;
}
else
{
@@ -10,6 +10,18 @@ namespace Barotrauma.Items.Components
{
private float force;
/// <summary>
/// Latest signal the set_force connection received, used to set <see cref="targetForce"/> in the Update method.
/// We use a separate variable, because otherwise specific item update orders and sending multiple signals to set_force would lead to bugs:
/// targetForce could be set to 0, then a power grid might update as if the engine was off and mark the voltage of the grid as 1,
/// then another item could set the targetForce to 100 and make it run without power.
/// </summary>
private float? lastReceivedTargetForce;
/// <summary>
/// The amount of force the engine is aiming for (the actual force may be less than this,
/// depending on the amount of power, the condition of the engine or boosts from talents)
/// </summary>
private float targetForce;
private float maxForce;
@@ -58,7 +70,7 @@ namespace Barotrauma.Items.Components
public float CurrentVolume
{
get { return Math.Abs((force / 100.0f) * (MinVoltage <= 0.0f ? 1.0f : Math.Min(prevVoltage / MinVoltage, 1.0f))); }
get { return Math.Abs((force / 100.0f) * (MinVoltage <= 0.0f ? 1.0f : Math.Min(prevVoltage, 1.0f))); }
}
public float CurrentBrokenVolume
@@ -110,7 +122,10 @@ namespace Barotrauma.Items.Components
hasPower = Voltage > MinVoltage;
}
if (lastReceivedTargetForce.HasValue)
{
targetForce = lastReceivedTargetForce.Value;
}
Force = MathHelper.Lerp(force, (Voltage < MinVoltage) ? 0.0f : targetForce, deltaTime * 10.0f);
if (Math.Abs(Force) > 1.0f)
{
@@ -254,7 +269,7 @@ namespace Barotrauma.Items.Components
if (float.TryParse(signal.value, NumberStyles.Float, CultureInfo.InvariantCulture, out float tempForce))
{
controlLockTimer = 0.1f;
targetForce = MathHelper.Clamp(tempForce, -100.0f, 100.0f);
lastReceivedTargetForce = MathHelper.Clamp(tempForce, -100.0f, 100.0f);
User = signal.sender;
}
}
@@ -784,8 +784,10 @@ namespace Barotrauma.Items.Components
}
else
{
float condition1 = MathUtils.IsValid(item1.Condition) ? item1.Condition : 0;
float condition2 = MathUtils.IsValid(item2.Condition) ? item2.Condition : 0;
//prefer items in worse condition
return Math.Sign(item2.Condition - item1.Condition);
return Math.Sign(condition2 - condition1);
}
}
@@ -775,7 +775,6 @@ namespace Barotrauma.Items.Components
if (shutDown)
{
PowerOn = false;
AutoTemp = false;
TargetFissionRate = 0.0f;
TargetTurbineOutput = 0.0f;
unsentChanges = true;
@@ -68,7 +68,6 @@ namespace Barotrauma.Items.Components
public bool UseDirectionalPing => useDirectionalPing;
private bool useDirectionalPing = false;
private Vector2 pingDirection = new Vector2(1.0f, 0.0f);
private bool useMineralScanner;
private bool aiPingCheckPending;
@@ -133,6 +132,9 @@ namespace Barotrauma.Items.Components
}
}
[Serialize(true, IsPropertySaveable.Yes, alwaysUseInstanceValues: true)]
public bool UseMineralScanner { get; set; }
public float Zoom
{
get { return zoom; }
@@ -366,7 +368,7 @@ namespace Barotrauma.Items.Components
bool isActive = msg.ReadBoolean();
bool directionalPing = useDirectionalPing;
float zoomT = zoom, pingDirectionT = 0.0f;
bool mineralScanner = useMineralScanner;
bool mineralScanner = UseMineralScanner;
if (isActive)
{
zoomT = msg.ReadRangedSingle(0.0f, 1.0f, 8);
@@ -391,13 +393,13 @@ namespace Barotrauma.Items.Components
float pingAngle = MathHelper.Lerp(0.0f, MathHelper.TwoPi, pingDirectionT);
pingDirection = new Vector2((float)Math.Cos(pingAngle), (float)Math.Sin(pingAngle));
}
useMineralScanner = mineralScanner;
UseMineralScanner = mineralScanner;
#if CLIENT
zoomSlider.BarScroll = zoomT;
directionalModeSwitch.Selected = useDirectionalPing;
if (mineralScannerSwitch != null)
{
mineralScannerSwitch.Selected = useMineralScanner;
mineralScannerSwitch.Selected = UseMineralScanner;
}
#endif
}
@@ -418,7 +420,7 @@ namespace Barotrauma.Items.Components
float pingAngle = MathUtils.WrapAngleTwoPi(MathUtils.VectorToAngle(pingDirection));
msg.WriteRangedSingle(MathUtils.InverseLerp(0.0f, MathHelper.TwoPi, pingAngle), 0.0f, 1.0f, 8);
}
msg.WriteBoolean(useMineralScanner);
msg.WriteBoolean(UseMineralScanner);
}
}
}
@@ -444,7 +444,7 @@ namespace Barotrauma.Items.Components
if (connectedSubUpdateTimer <= 0.0f)
{
connectedSubs.Clear();
connectedSubs = controlledSub?.GetConnectedSubs();
connectedSubs.AddRange(controlledSub.GetConnectedSubs());
connectedSubUpdateTimer = ConnectedSubUpdateInterval;
}
@@ -535,7 +535,7 @@ namespace Barotrauma.Items.Components
foreach (GraphEdge edge in cell.Edges)
{
if (MathUtils.GetLineIntersection(edge.Point1 + cell.Translation, edge.Point2 + cell.Translation, controlledSub.WorldPosition, cell.Center, out Vector2 intersection))
if (MathUtils.GetLineSegmentIntersection(edge.Point1 + cell.Translation, edge.Point2 + cell.Translation, controlledSub.WorldPosition, cell.Center, out Vector2 intersection))
{
Vector2 diff = controlledSub.WorldPosition - intersection;
//far enough -> ignore
@@ -435,7 +435,7 @@ namespace Barotrauma.Items.Components
{
//other junction boxes don't need to receive the signal in the pass-through signal connections
//because we relay it straight to the connected items without going through the whole chain of junction boxes
if (ic is PowerTransfer && !(ic is RelayComponent)) { continue; }
if (ic is PowerTransfer && ic is not RelayComponent) { continue; }
ic.ReceiveSignal(signal, recipient);
}
@@ -14,18 +14,18 @@ namespace Barotrauma.Items.Components
{
partial class Projectile : ItemComponent, IServerSerializable
{
const int SpreadCounterWrapAround = 256;
private static readonly ImmutableArray<float> spreadPool;
static Projectile()
{
MTRandom random = new MTRandom(0);
spreadPool = Enumerable.Range(0, SpreadCounterWrapAround).Select(f => (float)random.NextDouble() - 0.5f).ToImmutableArray();
spreadPool = Enumerable.Range(0, byte.MaxValue + 1).Select(f => (float)random.NextDouble() - 0.5f).ToImmutableArray();
}
public static float GetSpreadFromPool()
public static byte SpreadCounter { get; private set; }
public static void ResetSpreadCounter()
{
return spreadPool[SpreadCounter];
SpreadCounter = 0;
}
struct HitscanResult
@@ -62,7 +62,7 @@ namespace Barotrauma.Items.Components
private bool removePending;
public static byte SpreadCounter { get; private set; }
private byte spreadIndex;
//continuous collision detection is used while the projectile is moving faster than this
const float ContinuousCollisionThreshold = 5.0f;
@@ -299,7 +299,8 @@ namespace Barotrauma.Items.Components
return;
}
SpreadCounter = (byte)(item.ID % SpreadCounterWrapAround);
spreadIndex = SpreadCounter;
SpreadCounter++;
InitProjSpecific(element);
}
@@ -328,6 +329,12 @@ namespace Barotrauma.Items.Components
originalCollisionTargets = item.body.CollidesWith;
}
public float GetSpreadFromPool()
{
spreadIndex = (byte)MathUtils.PositiveModulo(spreadIndex, spreadPool.Length);
return spreadPool[spreadIndex];
}
private void Launch(Character user, Vector2 simPosition, float rotation, float damageMultiplier = 1f, float launchImpulseModifier = 0f)
{
if (Item.body == null) { return; }
@@ -380,7 +387,7 @@ namespace Barotrauma.Items.Components
{
#if SERVER
launchRot = rotation;
Item.CreateServerEvent(this, new EventData(launch: true, spreadCounter: (byte)(SpreadCounter - 1)));
Item.CreateServerEvent(this, new EventData(launch: true, spreadCounter: (byte)(spreadIndex - 1)));
#endif
}
}
@@ -396,7 +403,6 @@ namespace Barotrauma.Items.Components
for (int i = 0; i < HitScanCount; i++)
{
float launchAngle;
if (StaticSpread)
{
launchAngle = initialRotation + MathHelper.ToRadians(i - ((float)(HitScanCount - 1) / 2)) * Spread;
@@ -405,7 +411,7 @@ namespace Barotrauma.Items.Components
{
launchAngle = initialRotation + MathHelper.ToRadians(Spread * GetSpreadFromPool());
}
SpreadCounter++;
spreadIndex++;
Vector2 launchDir = new Vector2((float)Math.Cos(launchAngle), (float)Math.Sin(launchAngle));
if (Hitscan)
@@ -703,7 +709,7 @@ namespace Barotrauma.Items.Components
return hits;
}
public override void Drop(Character dropper)
public override void Drop(Character dropper, bool setTransform = true)
{
Item.ResetWaterDragCoefficient();
if (dropper != null)
@@ -711,7 +717,7 @@ namespace Barotrauma.Items.Components
DisableProjectileCollisions();
Unstick();
}
base.Drop(dropper);
base.Drop(dropper, setTransform);
}
public override void Update(float deltaTime, Camera cam)
@@ -885,17 +891,8 @@ namespace Barotrauma.Items.Components
return false;
}
Vector2 normalizedVel;
Vector2 dir;
if (item.body.LinearVelocity.LengthSquared() < 0.001f)
{
normalizedVel = Vector2.Zero;
dir = contact.Manifold.LocalNormal;
}
else
{
normalizedVel = dir = Vector2.Normalize(item.body.LinearVelocity);
}
Vector2 dir = item.body.LinearVelocity.LengthSquared() < 0.001f ?
contact.Manifold.LocalNormal : Vector2.Normalize(item.body.LinearVelocity);
//do a raycast in the sub's coordinate space to see if it hit a structure
var wallBody = Submarine.PickBody(
@@ -904,7 +901,7 @@ namespace Barotrauma.Items.Components
collisionCategory: Physics.CollisionWall);
if (wallBody?.FixtureList?.First() != null && (wallBody.UserData is Structure || wallBody.UserData is Item) &&
//ignore the hit if it's behind the position the item was launched from, and the projectile is travelling in the opposite direction
Vector2.Dot((item.body.SimPosition + normalizedVel) - launchPos, dir) > 0)
Vector2.Dot(item.body.SimPosition - launchPos, dir) > 0)
{
target = wallBody.FixtureList.First();
if (hits.Contains(target.Body))
@@ -942,7 +939,7 @@ namespace Barotrauma.Items.Components
Character character = null;
if (target.Body.UserData is Submarine submarine && target.UserData is not Barotrauma.Item)
{
item.Move(-submarine.Position);
item.Move(-submarine.Position, ignoreContacts: false);
item.Submarine = submarine;
item.body.Submarine = submarine;
return !Hitscan;
@@ -339,18 +339,19 @@ namespace Barotrauma.Items.Components
if (Math.Abs(TargetPullForce) > 0.001f)
{
var targetBody = GetBodyToPull(target);
if (user != null && targetCharacter != null && !user.AnimController.InWater)
bool lerpForces = LerpForces;
if (!lerpForces && user != null && targetCharacter != null && !user.AnimController.InWater)
{
// Prevents rubberbanding horizontally when dragging a corpse.
if ((forceDir.X < 0) != (user.AnimController.Dir < 0))
{
forceDir.X = Math.Clamp(forceDir.X, -0.1f, 0.1f);
// Prevents rubberbanding horizontally when dragging a corpse.
lerpForces = true;
}
}
float force = LerpForces ? MathHelper.Lerp(0, TargetPullForce, MathUtils.InverseLerp(0, MaxLength / 3, distance - 50)) : TargetPullForce;
float force = lerpForces ? MathHelper.Lerp(0, TargetPullForce, MathUtils.InverseLerp(0, MaxLength / 3, distance - 50)) : TargetPullForce;
targetBody?.ApplyForce(-forceDir * force);
var targetRagdoll = targetCharacter?.AnimController;
if (targetRagdoll != null && (targetRagdoll.InWater || targetRagdoll.OnGround))
if (targetRagdoll?.Collider != null && (targetRagdoll.InWater || targetRagdoll.OnGround))
{
targetRagdoll.Collider.ApplyForce(-forceDir * force * 3);
}
@@ -29,7 +29,7 @@ namespace Barotrauma.Items.Components
private readonly Item item;
public readonly bool IsOutput;
public readonly List<StatusEffect> Effects;
public readonly List<(ushort wireId, int? connectionIndex)> LoadedWires;
@@ -40,6 +40,9 @@ namespace Barotrauma.Items.Components
//Priority in which power output will be handled - load is unaffected
public PowerPriority Priority = PowerPriority.Default;
public Signal LastSentSignal { get; private set; }
public Signal LastReceivedSignal {get; private set;}
public bool IsPower
{
get;
@@ -292,6 +295,7 @@ namespace Barotrauma.Items.Components
public void SendSignal(Signal signal)
{
LastSentSignal = signal;
enumeratingWires = true;
foreach (var wire in wires)
{
@@ -302,6 +306,10 @@ namespace Barotrauma.Items.Components
signal.source?.LastSentSignalRecipients.Add(recipient);
Connection connection = recipient;
connection.LastReceivedSignal = signal;
#if CLIENT
wire.RegisterSignal(signal, source: this);
#endif
foreach (ItemComponent ic in recipient.item.Components)
{
@@ -82,8 +82,10 @@ namespace Barotrauma.Items.Components
signalOut.SendDuration -= 1;
item.SendSignal(new Signal(signalOut.Signal.value, sender: signalOut.Signal.sender, strength: signalOut.Signal.strength), "signal_out");
if (signalOut.SendDuration <= 0)
{
signalQueue.Dequeue();
{
//check the queue isn't empty again, because sending the signal may empty it
//if this component is set to reset when it receives a signal and the signal is routed back to this component
signalQueue.TryDequeue(out _);
}
else
{
@@ -173,6 +173,8 @@ namespace Barotrauma.Items.Components
set
{
lightColor = value;
//reset previously received signal to force updating the color if we receive a set_color signal after the color has been modified manually
prevColorSignal = string.Empty;
#if CLIENT
if (Light != null)
{
@@ -249,6 +251,11 @@ namespace Barotrauma.Items.Components
base.OnItemLoaded();
SetLightSourceState(IsActive, lightBrightness);
turret = item.GetComponent<Turret>();
if (item.body != null)
{
item.body.FarseerBody.OnEnabled += CheckIfNeedsUpdate;
item.body.FarseerBody.OnDisabled += CheckIfNeedsUpdate;
}
#if CLIENT
Drawable = AlphaBlend && Light.LightSprite != null;
if (Screen.Selected.IsEditor)
@@ -277,15 +284,24 @@ namespace Barotrauma.Items.Components
return;
}
if (item.body == null && powerConsumption <= 0.0f && Parent == null && turret == null &&
if ((item.body == null || !item.body.Enabled) &&
powerConsumption <= 0.0f && Parent == null && turret == null &&
(statusEffectLists == null || !statusEffectLists.ContainsKey(ActionType.OnActive)) &&
(IsActiveConditionals == null || IsActiveConditionals.Count == 0))
{
lightBrightness = 1.0f;
SetLightSourceState(true, lightBrightness);
if (item.body != null && !item.body.Enabled)
{
lightBrightness = 0.0f;
SetLightSourceState(false, 0.0f);
}
else
{
lightBrightness = 1.0f;
SetLightSourceState(true, lightBrightness);
}
isOn = true;
SetLightSourceTransformProjSpecific();
base.IsActive = false;
isOn = true;
#if CLIENT
Light.ParentSub = item.Submarine;
#endif
@@ -222,10 +222,10 @@ namespace Barotrauma.Items.Components
{
Vector2 e1 = edge.Point1 + cell.Translation;
Vector2 e2 = edge.Point2 + cell.Translation;
if (MathUtils.LinesIntersect(e1, e2, new Vector2(detectRect.X, detectRect.Y), new Vector2(detectRect.Right, detectRect.Y)) ||
MathUtils.LinesIntersect(e1, e2, new Vector2(detectRect.X, detectRect.Bottom), new Vector2(detectRect.Right, detectRect.Bottom)) ||
MathUtils.LinesIntersect(e1, e2, new Vector2(detectRect.X, detectRect.Y), new Vector2(detectRect.X, detectRect.Bottom)) ||
MathUtils.LinesIntersect(e1, e2, new Vector2(detectRect.Right, detectRect.Y), new Vector2(detectRect.Right, detectRect.Bottom)))
if (MathUtils.LineSegmentsIntersect(e1, e2, new Vector2(detectRect.X, detectRect.Y), new Vector2(detectRect.Right, detectRect.Y)) ||
MathUtils.LineSegmentsIntersect(e1, e2, new Vector2(detectRect.X, detectRect.Bottom), new Vector2(detectRect.Right, detectRect.Bottom)) ||
MathUtils.LineSegmentsIntersect(e1, e2, new Vector2(detectRect.X, detectRect.Y), new Vector2(detectRect.X, detectRect.Bottom)) ||
MathUtils.LineSegmentsIntersect(e1, e2, new Vector2(detectRect.Right, detectRect.Y), new Vector2(detectRect.Right, detectRect.Bottom)))
{
MotionDetected = true;
return;
@@ -8,6 +8,9 @@ namespace Barotrauma.Items.Components
internal Item source;
internal float power;
internal float strength;
public readonly double CreationTime;
public double TimeSinceCreated => Timing.TotalTimeUnpaused - CreationTime;
internal Signal(string value, int stepsTaken = 0, Character sender = null,
Item source = null, float power = 0.0f, float strength = 1.0f)
@@ -18,6 +21,7 @@ namespace Barotrauma.Items.Components
this.source = source;
this.power = power;
this.strength = strength;
CreationTime = Timing.TotalTimeUnpaused;
}
internal Signal WithStepsTaken(int stepsTaken)
@@ -64,7 +64,7 @@ namespace Barotrauma.Items.Components
set;
}
[ConditionallyEditable(ConditionallyEditable.ConditionType.AllowLinkingWifiToChat)]
[ConditionallyEditable(ConditionallyEditable.ConditionType.AllowLinkingWifiToChat, onlyInEditors: false)]
[Serialize(false, IsPropertySaveable.No, 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.", alwaysUseInstanceValues: true)]
public bool LinkToChat
@@ -19,7 +19,7 @@ namespace Barotrauma.Items.Components
private Vector2 end;
private readonly float angle;
private readonly float length;
public readonly float Length;
public Vector2 Start
{
@@ -36,7 +36,7 @@ namespace Barotrauma.Items.Components
this.end = end;
angle = MathUtils.VectorToAngle(end - start);
length = Vector2.Distance(start, end);
Length = Vector2.Distance(start, end);
}
}
@@ -52,7 +52,7 @@ namespace Barotrauma.Items.Components
private List<Vector2> nodes;
private readonly List<WireSection> sections;
private Connection[] connections;
private readonly Connection[] connections;
private bool canPlaceNode;
private Vector2 newNodePos;
@@ -81,6 +81,8 @@ namespace Barotrauma.Items.Components
get { return connections; }
}
public float Length { get; private set; }
[Serialize(5000.0f, IsPropertySaveable.No, description: "The maximum distance the wire can extend (in pixels).")]
public float MaxLength
{
@@ -333,7 +335,7 @@ namespace Barotrauma.Items.Components
IsActive = false;
}
public override void Drop(Character dropper)
public override void Drop(Character dropper, bool setTransform = true)
{
if (shouldClearConnections) { ClearConnections(dropper); }
IsActive = false;
@@ -563,6 +565,7 @@ namespace Barotrauma.Items.Components
sections.Add(new WireSection(nodes[i], nodes[i + 1]));
}
Drawable = IsActive || sections.Count > 0;
Length = sections.Count > 0 ? sections.Sum(s => s.Length) : 0;
CalculateExtents();
}
@@ -56,11 +56,9 @@ namespace Barotrauma.Items.Components
private float resetUserTimer;
private float aiTargetingGraceTimer;
private float aiFindTargetTimer;
private ISpatialEntity currentTarget;
private const float CrewAiFindTargetMaxInterval = 3.0f;
private const float CrewAiFindTargetMaxInterval = 1.0f;
private const float CrewAIFindTargetMinInverval = 0.2f;
private int currentLoaderIndex;
@@ -299,7 +297,7 @@ namespace Barotrauma.Items.Components
}
}
[Serialize(3000.0f, IsPropertySaveable.Yes, description: "How close to a target the turret has to be for an AI character to fire it.")]
[Serialize(3500.0f, IsPropertySaveable.Yes, description: "How close to a target the turret has to be for an AI character to fire it.")]
public float AIRange
{
get;
@@ -422,10 +420,7 @@ namespace Barotrauma.Items.Components
// Only make the Turret control the LightComponents that are it's children. So it'd be possible to for example have some extra lights on the turret that don't rotate with it.
if (lc?.Parent == this)
{
if (lightComponents == null)
{
lightComponents = new List<LightComponent>();
}
lightComponents ??= new List<LightComponent>();
lightComponents.Add(lc);
}
}
@@ -439,6 +434,8 @@ namespace Barotrauma.Items.Components
light.Parent = null;
light.Rotation = Rotation - item.RotationRad;
light.Light.Rotation = -rotation;
//turret lights are high-prio (don't want the lights to disappear when you're fighting something)
light.Light.PriorityMultiplier *= 10.0f;
}
}
#endif
@@ -590,10 +587,6 @@ namespace Barotrauma.Items.Components
angularVelocity *= -0.5f;
}
if (aiTargetingGraceTimer > 0f)
{
aiTargetingGraceTimer -= deltaTime;
}
if (aiFindTargetTimer > 0.0f)
{
aiFindTargetTimer -= deltaTime;
@@ -620,10 +613,18 @@ namespace Barotrauma.Items.Components
partial void UpdateProjSpecific(float deltaTime);
private bool isUseBeingCalled;
public override bool Use(float deltaTime, Character character = null)
{
if (!characterUsable && character != null) { return false; }
return TryLaunch(deltaTime, character);
//prevent an infinite loop if launching triggers a StatusEffect that Uses this item
if (isUseBeingCalled) { return false; }
isUseBeingCalled = true;
bool wasSuccessful = TryLaunch(deltaTime, character);
isUseBeingCalled = false;
return wasSuccessful;
}
public float GetPowerRequiredToShoot()
@@ -844,8 +845,13 @@ namespace Barotrauma.Items.Components
{
public readonly Item Projectile;
public EventData(Item projectile)
public EventData(Item projectile, Turret turret)
{
System.Diagnostics.Debug.Assert(projectile != null, $"Tried to create Turret {nameof(EventData)} with no projectile.");
GameAnalyticsManager.AddErrorEventOnce(
"Turret.EventData:entitynull"+ turret.Item.Prefab.Identifier,
GameAnalyticsManager.ErrorSeverity.Error,
$"Turret \"{turret.Item.Prefab.Identifier}\" tried to create {nameof(EventData)} with no projectile.");
Projectile = projectile;
}
}
@@ -885,21 +891,9 @@ namespace Barotrauma.Items.Components
}
float spread = MathHelper.ToRadians(Spread) * Rand.Range(-0.5f, 0.5f);
Vector2 launchPos = ConvertUnits.ToSimUnits(GetRelativeFiringPosition());
//check if there's some other sub between the turret's origin and the launch pos,
//and if so, launch at the intersection of the turret and the sub to prevent the projectile from spawning inside the other sub
Body pickedBody = Submarine.PickBody(ConvertUnits.ToSimUnits(item.WorldPosition), launchPos, null, Physics.CollisionWall, allowInsideFixture: true,
customPredicate: (Fixture f) =>
{
return f.Body.UserData is not Submarine sub || sub != item.Submarine;
});
if (pickedBody != null)
{
launchPos = Submarine.LastPickedPosition;
}
projectile.SetTransform(launchPos, -(launchRotation ?? rotation) + spread);
projectile.SetTransform(
ConvertUnits.ToSimUnits(GetRelativeFiringPosition()),
-(launchRotation ?? rotation) + spread);
projectile.UpdateTransform();
projectile.Submarine = projectile.body?.Submarine;
@@ -929,7 +923,7 @@ namespace Barotrauma.Items.Components
projectile.Container?.RemoveContained(projectile);
}
#if SERVER
item.CreateServerEvent(this, new EventData(projectile));
item.CreateServerEvent(this, new EventData(projectile, this));
#endif
ApplyStatusEffects(ActionType.OnUse, 1.0f, user: user);
@@ -1027,6 +1021,7 @@ namespace Barotrauma.Items.Components
float dist = Vector2.DistanceSquared(item.WorldPosition, targetItem.WorldPosition);
if (dist > closestDist) { continue; }
if (dist > shootDistance * shootDistance) { continue; }
if (!IsTargetItemCloseEnough(targetItem, dist)) { continue; }
if (!CheckTurretAngle(targetItem.WorldPosition)) { continue; }
target = targetItem;
closestDist = dist / priority;
@@ -1142,9 +1137,12 @@ namespace Barotrauma.Items.Components
{
if (character.AIController.SelectedAiTarget?.Entity is Character previousTarget && previousTarget.IsDead)
{
character.Speak(TextManager.Get("DialogTurretTargetDead").Value,
identifier: $"killedtarget{previousTarget.ID}".ToIdentifier(),
minDurationBetweenSimilar: 10.0f);
if (previousTarget.LastAttacker == null || previousTarget.LastAttacker == character)
{
character.Speak(TextManager.Get("DialogTurretTargetDead").Value,
identifier: $"killedtarget{previousTarget.ID}".ToIdentifier(),
minDurationBetweenSimilar: 5.0f);
}
character.AIController.SelectTarget(null);
}
@@ -1277,18 +1275,27 @@ namespace Barotrauma.Items.Components
Vector2? targetPos = null;
float maxDistance = 10000;
float shootDistance = AIRange * item.OffsetOnSelectedMultiplier;
// use full range only if we're actively firing
if (aiTargetingGraceTimer <= 0f)
{
shootDistance *= 0.75f;
}
float closestDistance = maxDistance * maxDistance;
bool hadCurrentTarget = currentTarget != null;
if (hadCurrentTarget)
{
if (!IsValidTarget(currentTarget))
bool isValidTarget = IsValidTarget(currentTarget);
if (isValidTarget)
{
float dist = Vector2.DistanceSquared(item.WorldPosition, currentTarget.WorldPosition);
if (dist > closestDistance)
{
isValidTarget = false;
}
else if (currentTarget is Item targetItem)
{
if (!IsTargetItemCloseEnough(targetItem, dist))
{
isValidTarget = false;
}
}
}
if (!isValidTarget)
{
currentTarget = null;
aiFindTargetTimer = CrewAIFindTargetMinInverval;
@@ -1304,11 +1311,17 @@ namespace Barotrauma.Items.Components
if (character.Submarine != null)
{
if (enemy.Submarine == character.Submarine) { continue; }
if (enemy.Submarine != null && enemy.Submarine.TeamID == character.Submarine.TeamID) { continue; }
if (enemy.Submarine != null)
{
if (enemy.Submarine.TeamID == character.Submarine.TeamID) { continue; }
if (enemy.Submarine.Info.IsOutpost) { continue; }
}
}
// Don't aim monsters that are inside any submarine.
if (!enemy.IsHuman && enemy.CurrentHull != null) { continue; }
if (HumanAIController.IsFriendly(character, enemy)) { continue; }
if (HumanAIController.IsFriendly(character, enemy)) { continue; }
// Don't shoot at captured enemies.
if (enemy.LockHands) { continue; }
float dist = Vector2.DistanceSquared(enemy.WorldPosition, item.WorldPosition);
if (dist > closestDistance) { continue; }
if (dist < shootDistance * shootDistance)
@@ -1330,6 +1343,7 @@ namespace Barotrauma.Items.Components
float dist = Vector2.DistanceSquared(item.WorldPosition, targetItem.WorldPosition);
if (dist > closestDistance) { continue; }
if (dist > shootDistance * shootDistance) { continue; }
if (!IsTargetItemCloseEnough(targetItem, dist)) { continue; }
if (!CheckTurretAngle(targetItem.WorldPosition)) { continue; }
targetPos = targetItem.WorldPosition;
closestDistance = dist / priority;
@@ -1337,14 +1351,7 @@ namespace Barotrauma.Items.Components
closestEnemy = null;
currentTarget = targetItem;
}
if (currentTarget == null)
{
aiFindTargetTimer = CrewAIFindTargetMinInverval;
}
else
{
aiFindTargetTimer = CrewAiFindTargetMaxInterval;
}
aiFindTargetTimer = currentTarget == null ? CrewAiFindTargetMaxInterval : CrewAIFindTargetMinInverval;
}
else if (currentTarget != null)
{
@@ -1358,6 +1365,10 @@ namespace Barotrauma.Items.Components
if (targetCharacter.Submarine != null && targetCharacter.CurrentHull != null && targetCharacter.Submarine != item.Submarine && !targetCharacter.CanSeeTarget(Item))
{
targetPos = targetCharacter.CurrentHull.WorldPosition;
if (closestDistance > maxDistance * maxDistance)
{
ResetTarget();
}
}
else
{
@@ -1377,12 +1388,17 @@ namespace Barotrauma.Items.Components
}
if (closestDist > shootDistance * shootDistance)
{
// Not close enough to shoot.
currentTarget = null;
closestEnemy = null;
targetPos = null;
aiFindTargetTimer = CrewAIFindTargetMinInverval;
ResetTarget();
}
}
void ResetTarget()
{
// Not close enough to shoot.
currentTarget = null;
closestEnemy = null;
targetPos = null;
}
}
else if (targetPos == null && item.Submarine != null && Level.Loaded != null)
{
@@ -1404,7 +1420,7 @@ namespace Barotrauma.Items.Components
{
// The closest point can't be targeted -> get a point directly in front of the turret
Vector2 barrelDir = new Vector2((float)Math.Cos(rotation), -(float)Math.Sin(rotation));
if (MathUtils.GetLineIntersection(p1, p2, item.WorldPosition, item.WorldPosition + barrelDir * shootDistance, out Vector2 intersection))
if (MathUtils.GetLineSegmentIntersection(p1, p2, item.WorldPosition, item.WorldPosition + barrelDir * shootDistance, out Vector2 intersection))
{
closestPoint = intersection;
if (!CheckTurretAngle(closestPoint)) { continue; }
@@ -1532,10 +1548,11 @@ namespace Barotrauma.Items.Components
}
character.SetInput(InputType.Shoot, true, true);
}
aiTargetingGraceTimer = 5f;
return false;
}
private bool IsTargetItemCloseEnough(Item target, float sqrDist) => float.IsPositiveInfinity(target.Prefab.AITurretTargetingMaxDistance) || sqrDist < MathUtils.Pow2(target.Prefab.AITurretTargetingMaxDistance);
/// <summary>
/// Turret doesn't consume grid power, directly takes from the batteries on its grid instead.
/// </summary>
@@ -1565,6 +1582,10 @@ namespace Barotrauma.Items.Components
{
return false;
}
if (targetItem.ParentInventory != null)
{
return false;
}
}
return true;
}
@@ -1580,6 +1601,7 @@ namespace Barotrauma.Items.Components
{
if (item.Submarine != null)
{
if (item.Submarine.Info.IsOutpost) { return false; }
// Check that the target is not in the friendly team, e.g. pirate or a hostile player sub (PvP).
return !target.IsOnFriendlyTeam(item.Submarine.TeamID) && TargetHumans;
}
@@ -1672,7 +1694,7 @@ namespace Barotrauma.Items.Components
return angle >= minRotation && angle <= maxRotation;
}
private bool CheckTurretAngle(Vector2 target) => CheckTurretAngle(-MathUtils.VectorToAngle(target - item.WorldPosition));
public bool CheckTurretAngle(Vector2 target) => CheckTurretAngle(-MathUtils.VectorToAngle(target - item.WorldPosition));
protected override void RemoveComponentSpecific()
{
@@ -1874,22 +1896,27 @@ namespace Barotrauma.Items.Components
{
if (TryExtractEventData(extraData, out EventData eventData))
{
msg.WriteUInt16(eventData.Projectile.ID);
msg.WriteRangedSingle(MathHelper.Clamp(rotation, minRotation, maxRotation), minRotation, maxRotation, 16);
msg.WriteUInt16(eventData.Projectile?.ID ?? Entity.NullEntityID);
msg.WriteRangedSingle(MathHelper.Clamp(wrapAngle(rotation), minRotation, maxRotation), minRotation, maxRotation, 16);
}
else
{
msg.WriteUInt16((ushort)0);
float wrappedTargetRotation = targetRotation;
while (wrappedTargetRotation < minRotation && MathUtils.IsValid(wrappedTargetRotation))
msg.WriteRangedSingle(MathHelper.Clamp(wrapAngle(targetRotation), minRotation, maxRotation), minRotation, maxRotation, 16);
}
float wrapAngle(float angle)
{
float wrappedAngle = angle;
while (wrappedAngle < minRotation && MathUtils.IsValid(wrappedAngle))
{
wrappedTargetRotation += MathHelper.TwoPi;
wrappedAngle += MathHelper.TwoPi;
}
while (wrappedTargetRotation > maxRotation && MathUtils.IsValid(wrappedTargetRotation))
while (wrappedAngle > maxRotation && MathUtils.IsValid(wrappedAngle))
{
wrappedTargetRotation -= MathHelper.TwoPi;
wrappedAngle -= MathHelper.TwoPi;
}
msg.WriteRangedSingle(MathHelper.Clamp(wrappedTargetRotation, minRotation, maxRotation), minRotation, maxRotation, 16);
return wrappedAngle;
}
}
}
@@ -482,11 +482,11 @@ namespace Barotrauma.Items.Components
character.OnWearablesChanged();
}
public override void Drop(Character dropper)
public override void Drop(Character dropper, bool setTransform = true)
{
Character previousPicker = picker;
Unequip(picker);
base.Drop(dropper);
base.Drop(dropper, setTransform);
previousPicker?.OnWearablesChanged();
picker = null;
IsActive = false;