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:
@@ -24,7 +24,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
|
||||
public static readonly List<InvSlotType> anySlot = new List<InvSlotType>() { InvSlotType.Any };
|
||||
public static readonly List<InvSlotType> AnySlot = new List<InvSlotType>() { InvSlotType.Any };
|
||||
|
||||
protected bool[] IsEquipped;
|
||||
|
||||
@@ -85,6 +85,22 @@ namespace Barotrauma
|
||||
|
||||
InitProjSpecific(element);
|
||||
|
||||
var itemElements = element.Elements().Where(e => e.Name.ToString().Equals("item", StringComparison.OrdinalIgnoreCase));
|
||||
int itemCount = itemElements.Count();
|
||||
if (itemCount > capacity)
|
||||
{
|
||||
DebugConsole.ThrowError($"Character \"{character.SpeciesName}\" is configured to spawn with more items than it has inventory capacity for.");
|
||||
}
|
||||
#if DEBUG
|
||||
else if (itemCount > capacity - 2)
|
||||
{
|
||||
DebugConsole.ThrowError(
|
||||
$"Character \"{character.SpeciesName}\" is configured to spawn with so many items it will have less than 2 free inventory slots. " +
|
||||
"This can cause issues with talents that spawn extra loot in monsters' inventories."
|
||||
+ " Consider increasing the inventory size.");
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!spawnInitialItems) { return; }
|
||||
|
||||
#if CLIENT
|
||||
@@ -92,10 +108,8 @@ namespace Barotrauma
|
||||
if (GameMain.Client != null) { return; }
|
||||
#endif
|
||||
|
||||
foreach (var subElement in element.Elements())
|
||||
{
|
||||
if (!subElement.Name.ToString().Equals("item", StringComparison.OrdinalIgnoreCase)) { continue; }
|
||||
|
||||
foreach (var subElement in itemElements)
|
||||
{
|
||||
string itemIdentifier = subElement.GetAttributeString("identifier", "");
|
||||
if (!ItemPrefab.Prefabs.TryGet(itemIdentifier, out var itemPrefab))
|
||||
{
|
||||
@@ -226,7 +240,7 @@ namespace Barotrauma
|
||||
if (item.AllowedSlots.Contains(InvSlotType.Any))
|
||||
{
|
||||
var wearable = item.GetComponent<Wearable>();
|
||||
if (wearable != null && !wearable.AutoEquipWhenFull && CheckIfAnySlotAvailable(item, false) == -1)
|
||||
if (wearable != null && !wearable.AutoEquipWhenFull && !IsAnySlotAvailable(item))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -336,7 +350,7 @@ namespace Barotrauma
|
||||
//try to place the item in a LimbSlot.Any slot if that's allowed
|
||||
if (allowedSlots.Contains(InvSlotType.Any) && item.AllowedSlots.Contains(InvSlotType.Any))
|
||||
{
|
||||
int freeIndex = CheckIfAnySlotAvailable(item, inWrongSlot);
|
||||
int freeIndex = GetFreeAnySlot(item, inWrongSlot);
|
||||
if (freeIndex > -1)
|
||||
{
|
||||
PutItem(item, freeIndex, user, true, createNetworkEvent);
|
||||
@@ -393,7 +407,9 @@ namespace Barotrauma
|
||||
return placedInSlot > -1;
|
||||
}
|
||||
|
||||
public int CheckIfAnySlotAvailable(Item item, bool inWrongSlot)
|
||||
public bool IsAnySlotAvailable(Item item) => GetFreeAnySlot(item, inWrongSlot: false) > -1;
|
||||
|
||||
private int GetFreeAnySlot(Item item, bool inWrongSlot)
|
||||
{
|
||||
//attempt to stack first
|
||||
for (int i = 0; i < capacity; i++)
|
||||
|
||||
@@ -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;
|
||||
|
||||
+14
-5
@@ -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;
|
||||
|
||||
@@ -575,8 +575,8 @@ namespace Barotrauma
|
||||
|
||||
if (removeItem)
|
||||
{
|
||||
item.Drop(user);
|
||||
if (item.ParentInventory != null) { item.ParentInventory.RemoveItem(item); }
|
||||
item.Drop(user, setTransform: false);
|
||||
item.ParentInventory?.RemoveItem(item);
|
||||
}
|
||||
|
||||
slots[i].Add(item);
|
||||
@@ -742,13 +742,13 @@ namespace Barotrauma
|
||||
stackedItems.Distinct().All(stackedItem => TryPutItem(stackedItem, index, false, false, user, createNetworkEvent))
|
||||
&&
|
||||
(existingItems.All(existingItem => otherInventory.TryPutItem(existingItem, otherIndex, false, false, user, createNetworkEvent)) ||
|
||||
existingItems.Count == 1 && otherInventory.TryPutItem(existingItems.First(), user, CharacterInventory.anySlot, createNetworkEvent));
|
||||
existingItems.Count == 1 && otherInventory.TryPutItem(existingItems.First(), user, CharacterInventory.AnySlot, createNetworkEvent));
|
||||
}
|
||||
else
|
||||
{
|
||||
swapSuccessful =
|
||||
(existingItems.All(existingItem => otherInventory.TryPutItem(existingItem, otherIndex, false, false, user, createNetworkEvent)) ||
|
||||
existingItems.Count == 1 && otherInventory.TryPutItem(existingItems.First(), user, CharacterInventory.anySlot, createNetworkEvent))
|
||||
existingItems.Count == 1 && otherInventory.TryPutItem(existingItems.First(), user, CharacterInventory.AnySlot, createNetworkEvent))
|
||||
&&
|
||||
stackedItems.Distinct().All(stackedItem => TryPutItem(stackedItem, index, false, false, user, createNetworkEvent));
|
||||
|
||||
@@ -835,13 +835,31 @@ namespace Barotrauma
|
||||
|
||||
if (otherIsEquipped)
|
||||
{
|
||||
existingItems.ForEach(existingItem => TryPutItem(existingItem, index, false, false, user, createNetworkEvent, ignoreCondition: true));
|
||||
stackedItems.ForEach(stackedItem => otherInventory.TryPutItem(stackedItem, otherIndex, false, false, user, createNetworkEvent, ignoreCondition: true));
|
||||
TryPutAndForce(existingItems, this, index);
|
||||
TryPutAndForce(stackedItems, otherInventory, otherIndex);
|
||||
}
|
||||
else
|
||||
{
|
||||
stackedItems.ForEach(stackedItem => otherInventory.TryPutItem(stackedItem, otherIndex, false, false, user, createNetworkEvent, ignoreCondition: true));
|
||||
existingItems.ForEach(existingItem => TryPutItem(existingItem, index, false, false, user, createNetworkEvent, ignoreCondition: true));
|
||||
TryPutAndForce(stackedItems, otherInventory, otherIndex);
|
||||
TryPutAndForce(existingItems, this, index);
|
||||
}
|
||||
|
||||
void TryPutAndForce(IEnumerable<Item> items, Inventory inventory, int slotIndex)
|
||||
{
|
||||
foreach (var item in items)
|
||||
{
|
||||
if (!inventory.TryPutItem(item, slotIndex, false, false, user, createNetworkEvent, ignoreCondition: true) &&
|
||||
!inventory.GetItemsAt(slotIndex).Contains(item))
|
||||
{
|
||||
inventory.ForceToSlot(item, slotIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (createNetworkEvent)
|
||||
{
|
||||
CreateNetworkEvent();
|
||||
otherInventory.CreateNetworkEvent();
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
|
||||
@@ -43,6 +43,13 @@ namespace Barotrauma
|
||||
/// </summary>
|
||||
public static IReadOnlyCollection<Item> CleanableItems => cleanableItems;
|
||||
|
||||
private static readonly List<Item> sonarVisibleItems = new List<Item>();
|
||||
|
||||
/// <summary>
|
||||
/// Items whose <see cref="ItemPrefab.SonarSize"/> is larger than 0
|
||||
/// </summary>
|
||||
public static IReadOnlyCollection<Item> SonarVisibleItems => sonarVisibleItems;
|
||||
|
||||
public new ItemPrefab Prefab => base.Prefab as ItemPrefab;
|
||||
|
||||
public static bool ShowLinks = true;
|
||||
@@ -126,7 +133,8 @@ namespace Barotrauma
|
||||
private float condition;
|
||||
|
||||
private bool inWater;
|
||||
private readonly bool hasWaterStatusEffects;
|
||||
private readonly bool hasInWaterStatusEffects;
|
||||
private readonly bool hasNotInWaterStatusEffects;
|
||||
|
||||
private Inventory parentInventory;
|
||||
private readonly ItemInventory ownInventory;
|
||||
@@ -206,6 +214,10 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public Item RootContainer { get; private set; }
|
||||
|
||||
private bool inWaterProofContainer;
|
||||
|
||||
private Item container;
|
||||
public Item Container
|
||||
{
|
||||
@@ -217,6 +229,8 @@ namespace Barotrauma
|
||||
container = value;
|
||||
CheckCleanable();
|
||||
SetActiveSprite();
|
||||
|
||||
RefreshRootContainer();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -408,14 +422,15 @@ namespace Barotrauma
|
||||
set { spriteColor = value; }
|
||||
}
|
||||
|
||||
[Serialize("1.0,1.0,1.0,1.0", IsPropertySaveable.Yes), Editable]
|
||||
[Serialize("1.0,1.0,1.0,1.0", IsPropertySaveable.Yes), ConditionallyEditable(ConditionallyEditable.ConditionType.Pickable)]
|
||||
public Color InventoryIconColor
|
||||
{
|
||||
get;
|
||||
protected set;
|
||||
}
|
||||
|
||||
[Editable, Serialize("1.0,1.0,1.0,1.0", IsPropertySaveable.Yes, description: "Changes the color of the item this item is contained inside. Only has an effect if either of the UseContainedSpriteColor or UseContainedInventoryIconColor property of the container is set to true.")]
|
||||
[Serialize("1.0,1.0,1.0,1.0", IsPropertySaveable.Yes, description: "Changes the color of the item this item is contained inside. Only has an effect if either of the UseContainedSpriteColor or UseContainedInventoryIconColor property of the container is set to true."),
|
||||
ConditionallyEditable(ConditionallyEditable.ConditionType.Pickable)]
|
||||
public Color ContainerColor
|
||||
{
|
||||
get;
|
||||
@@ -699,14 +714,26 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
[Serialize(false, IsPropertySaveable.No)]
|
||||
public bool FireProof
|
||||
{
|
||||
get { return Prefab.FireProof; }
|
||||
get; private set;
|
||||
}
|
||||
|
||||
private bool waterProof;
|
||||
[Serialize(false, IsPropertySaveable.No)]
|
||||
public bool WaterProof
|
||||
{
|
||||
get { return Prefab.WaterProof; }
|
||||
get { return waterProof; }
|
||||
private set
|
||||
{
|
||||
if (waterProof == value) { return; }
|
||||
waterProof = value;
|
||||
foreach (Item containedItem in ContainedItems)
|
||||
{
|
||||
containedItem.RefreshInWaterProofContainer();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool UseInHealthInterface
|
||||
@@ -735,7 +762,7 @@ namespace Barotrauma
|
||||
{
|
||||
//if the item has an active physics body, inWater is updated in the Update method
|
||||
if (body != null && body.Enabled) { return inWater; }
|
||||
if (hasWaterStatusEffects) { return inWater; }
|
||||
if (hasInWaterStatusEffects) { return inWater; }
|
||||
|
||||
//if not, we'll just have to check
|
||||
return IsInWater();
|
||||
@@ -1067,7 +1094,8 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
hasWaterStatusEffects = hasStatusEffectsOfType[(int)ActionType.InWater] || hasStatusEffectsOfType[(int)ActionType.NotInWater];
|
||||
hasInWaterStatusEffects = hasStatusEffectsOfType[(int)ActionType.InWater];
|
||||
hasNotInWaterStatusEffects = hasStatusEffectsOfType[(int)ActionType.NotInWater];
|
||||
|
||||
if (body != null)
|
||||
{
|
||||
@@ -1123,6 +1151,7 @@ namespace Barotrauma
|
||||
ItemList.Add(this);
|
||||
if (Prefab.IsDangerous) { dangerousItems.Add(this); }
|
||||
if (Repairables.Any()) { repairableItems.Add(this); }
|
||||
if (Prefab.SonarSize > 0.0f) { sonarVisibleItems.Add(this); }
|
||||
CheckCleanable();
|
||||
|
||||
DebugConsole.Log("Created " + Name + " (" + ID + ")");
|
||||
@@ -1416,7 +1445,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public override void Move(Vector2 amount, bool ignoreContacts = false)
|
||||
public override void Move(Vector2 amount, bool ignoreContacts = true)
|
||||
{
|
||||
if (!MathUtils.IsValid(amount))
|
||||
{
|
||||
@@ -1424,7 +1453,7 @@ namespace Barotrauma
|
||||
return;
|
||||
}
|
||||
|
||||
base.Move(amount);
|
||||
base.Move(amount, ignoreContacts);
|
||||
|
||||
if (ItemList != null && body != null)
|
||||
{
|
||||
@@ -1508,17 +1537,51 @@ namespace Barotrauma
|
||||
return CurrentHull;
|
||||
}
|
||||
|
||||
public Item GetRootContainer()
|
||||
private void RefreshRootContainer()
|
||||
{
|
||||
if (Container == null) { return null; }
|
||||
Item rootContainer = Container;
|
||||
while (rootContainer.Container != null)
|
||||
Item newRootContainer = null;
|
||||
inWaterProofContainer = false;
|
||||
if (Container != null)
|
||||
{
|
||||
rootContainer = rootContainer.Container;
|
||||
Item rootContainer = Container;
|
||||
inWaterProofContainer |= Container.WaterProof;
|
||||
|
||||
while (rootContainer.Container != null)
|
||||
{
|
||||
rootContainer = rootContainer.Container;
|
||||
inWaterProofContainer |= rootContainer.WaterProof;
|
||||
}
|
||||
newRootContainer = rootContainer;
|
||||
}
|
||||
if (newRootContainer != RootContainer)
|
||||
{
|
||||
RootContainer = newRootContainer;
|
||||
isActive = true;
|
||||
foreach (Item containedItem in ContainedItems)
|
||||
{
|
||||
containedItem.RefreshRootContainer();
|
||||
}
|
||||
}
|
||||
return rootContainer;
|
||||
}
|
||||
|
||||
|
||||
private void RefreshInWaterProofContainer()
|
||||
{
|
||||
inWaterProofContainer = false;
|
||||
if (container == null) { return; }
|
||||
if (container.WaterProof || container.inWaterProofContainer)
|
||||
{
|
||||
inWaterProofContainer = true;
|
||||
}
|
||||
foreach (Item containedItem in ContainedItems)
|
||||
{
|
||||
containedItem.RefreshInWaterProofContainer();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used by the AI to check whether they can (in principle) and are allowed (in practice) to interact with an object or not.
|
||||
/// Unlike CanInteractWith(), this method doesn't check the distance, the triggers, or anything like that.
|
||||
/// </summary>
|
||||
public bool HasAccess(Character character)
|
||||
{
|
||||
if (character.IsBot && IgnoreByAI(character)) { return false; }
|
||||
@@ -1526,6 +1589,7 @@ namespace Barotrauma
|
||||
var itemContainer = GetComponent<ItemContainer>();
|
||||
if (itemContainer != null && !itemContainer.HasAccess(character)) { return false; }
|
||||
if (Container != null && !Container.HasAccess(character)) { return false; }
|
||||
if (GetComponent<Pickable>() is { CanBePicked: false }) { return false; }
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1535,9 +1599,8 @@ namespace Barotrauma
|
||||
{
|
||||
if (ParentInventory == null) { return this; }
|
||||
if (ParentInventory.Owner is Character) { return ParentInventory.Owner; }
|
||||
var rootContainer = GetRootContainer();
|
||||
if (rootContainer?.ParentInventory?.Owner is Character) { return rootContainer.ParentInventory.Owner; }
|
||||
return rootContainer ?? this;
|
||||
if (RootContainer?.ParentInventory?.Owner is Character) { return RootContainer.ParentInventory.Owner; }
|
||||
return RootContainer ?? this;
|
||||
}
|
||||
|
||||
public Inventory FindParentInventory(Func<Inventory, bool> predicate)
|
||||
@@ -1587,6 +1650,17 @@ namespace Barotrauma
|
||||
return tags.Contains(tag) || base.Prefab.Tags.Contains(tag);
|
||||
}
|
||||
|
||||
public bool HasIdentifierOrTags(IEnumerable<Identifier> identifiersOrTags)
|
||||
{
|
||||
if (identifiersOrTags == null) { return false; }
|
||||
if (identifiersOrTags.Contains(Prefab.Identifier)) { return true; }
|
||||
foreach (Identifier tag in identifiersOrTags)
|
||||
{
|
||||
if (HasTag(tag)) { return true; }
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void ReplaceTag(string tag, string newTag)
|
||||
{
|
||||
ReplaceTag(tag.ToIdentifier(), newTag.ToIdentifier());
|
||||
@@ -1616,7 +1690,7 @@ namespace Barotrauma
|
||||
|
||||
public bool ConditionalMatches(PropertyConditional conditional)
|
||||
{
|
||||
if (string.IsNullOrEmpty(conditional.TargetItemComponent))
|
||||
if (string.IsNullOrEmpty(conditional.TargetItemComponentName))
|
||||
{
|
||||
if (!conditional.Matches(this)) { return false; }
|
||||
}
|
||||
@@ -1624,7 +1698,7 @@ namespace Barotrauma
|
||||
{
|
||||
foreach (ItemComponent component in components)
|
||||
{
|
||||
if (component.Name != conditional.TargetItemComponent) { continue; }
|
||||
if (component.Name != conditional.TargetItemComponentName) { continue; }
|
||||
if (!conditional.Matches(component)) { return false; }
|
||||
}
|
||||
}
|
||||
@@ -1770,8 +1844,23 @@ namespace Barotrauma
|
||||
|
||||
bool wasInFullCondition = IsFullCondition;
|
||||
|
||||
float diff = value - condition;
|
||||
if (GetComponent<Door>() is Door door && door.IsStuck && diff < 0)
|
||||
{
|
||||
float dmg = -diff;
|
||||
// When the door is fully welded shut, reduce the welded state instead of the condition.
|
||||
float prevStuck = door.Stuck;
|
||||
door.Stuck -= dmg;
|
||||
if (door.IsStuck) { return; }
|
||||
// Reduce the damage by the amount we just adjusted the welded state by.
|
||||
float damageReduction = dmg - prevStuck;
|
||||
if (damageReduction < 0) { return; }
|
||||
value -= damageReduction;
|
||||
}
|
||||
|
||||
condition = MathHelper.Clamp(value, 0.0f, MaxCondition);
|
||||
if (MathUtils.NearlyEqual(prevCondition, condition, epsilon: 0.000001f)) { return; }
|
||||
|
||||
if (MathUtils.NearlyEqual(prevCondition, value, epsilon: 0.000001f)) { return; }
|
||||
|
||||
RecalculateConditionValues();
|
||||
|
||||
@@ -1930,7 +2019,7 @@ namespace Barotrauma
|
||||
|
||||
if (ic.IsActiveConditionals != null)
|
||||
{
|
||||
if (ic.IsActiveConditionalComparison == PropertyConditional.LogicalOperatorType.And)
|
||||
if (ic.IsActiveConditionalComparison == PropertyConditional.Comparison.And)
|
||||
{
|
||||
bool shouldBeActive = true;
|
||||
foreach (var conditional in ic.IsActiveConditionals)
|
||||
@@ -1994,7 +2083,7 @@ namespace Barotrauma
|
||||
|
||||
if (Removed) { return; }
|
||||
|
||||
bool needsWaterCheck = hasWaterStatusEffects;
|
||||
bool needsWaterCheck = hasInWaterStatusEffects || hasNotInWaterStatusEffects;
|
||||
if (body != null && body.Enabled)
|
||||
{
|
||||
System.Diagnostics.Debug.Assert(body.FarseerBody.FixtureList != null);
|
||||
@@ -2023,7 +2112,7 @@ namespace Barotrauma
|
||||
if (needsWaterCheck)
|
||||
{
|
||||
bool wasInWater = inWater;
|
||||
inWater = IsInWater() && !WaterProof;
|
||||
inWater = !inWaterProofContainer && IsInWater() && !WaterProof;
|
||||
if (inWater)
|
||||
{
|
||||
//the item has gone through the surface of the water
|
||||
@@ -2036,36 +2125,29 @@ namespace Barotrauma
|
||||
body.LinearVelocity *= 0.2f;
|
||||
}
|
||||
}
|
||||
|
||||
Item container = this.Container;
|
||||
while (container != null)
|
||||
{
|
||||
if (container.WaterProof)
|
||||
{
|
||||
inWater = false;
|
||||
break;
|
||||
}
|
||||
container = container.Container;
|
||||
}
|
||||
}
|
||||
if (hasWaterStatusEffects && condition > 0.0f)
|
||||
if ((hasInWaterStatusEffects || hasNotInWaterStatusEffects) && condition > 0.0f)
|
||||
{
|
||||
ApplyStatusEffects(inWater ? ActionType.InWater : ActionType.NotInWater, deltaTime);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (updateableComponents.Count == 0 &&
|
||||
(aiTarget == null || !aiTarget.NeedsUpdate) &&
|
||||
!hasStatusEffectsOfType[(int)ActionType.Always] &&
|
||||
(body == null || !body.Enabled))
|
||||
if (inWaterProofContainer && !hasNotInWaterStatusEffects)
|
||||
{
|
||||
#if CLIENT
|
||||
positionBuffer.Clear();
|
||||
#endif
|
||||
isActive = false;
|
||||
needsWaterCheck = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!needsWaterCheck &&
|
||||
updateableComponents.Count == 0 &&
|
||||
(aiTarget == null || !aiTarget.NeedsUpdate) &&
|
||||
!hasStatusEffectsOfType[(int)ActionType.Always] &&
|
||||
(body == null || !body.Enabled))
|
||||
{
|
||||
#if CLIENT
|
||||
positionBuffer.Clear();
|
||||
#endif
|
||||
isActive = false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
partial void Splash();
|
||||
@@ -2854,7 +2936,7 @@ namespace Barotrauma
|
||||
|
||||
if (user != null)
|
||||
{
|
||||
var abilityItem = new AbilityApplyTreatment(user, character, this);
|
||||
var abilityItem = new AbilityApplyTreatment(user, character, this, targetLimb);
|
||||
user.CheckTalents(AbilityEffectType.OnApplyTreatment, abilityItem);
|
||||
}
|
||||
|
||||
@@ -2915,7 +2997,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
foreach (ItemComponent ic in components) { ic.Drop(dropper); }
|
||||
foreach (ItemComponent ic in components) { ic.Drop(dropper, setTransform); }
|
||||
|
||||
if (Container != null)
|
||||
{
|
||||
@@ -3550,7 +3632,7 @@ namespace Barotrauma
|
||||
element.Add(new XAttribute("healthmultiplier", HealthMultiplier.ToString("G", CultureInfo.InvariantCulture)));
|
||||
}
|
||||
|
||||
Item rootContainer = GetRootContainer() ?? this;
|
||||
Item rootContainer = RootContainer ?? this;
|
||||
System.Diagnostics.Debug.Assert(Submarine != null || rootContainer.ParentInventory?.Owner is Character);
|
||||
|
||||
Vector2 subPosition = Submarine == null ? Vector2.Zero : Submarine.HiddenSubPosition;
|
||||
@@ -3727,6 +3809,7 @@ namespace Barotrauma
|
||||
ItemList.Remove(this);
|
||||
dangerousItems.Remove(this);
|
||||
repairableItems.Remove(this);
|
||||
sonarVisibleItems.Remove(this);
|
||||
cleanableItems.Remove(this);
|
||||
}
|
||||
|
||||
@@ -3750,12 +3833,14 @@ namespace Barotrauma
|
||||
public Character Character { get; set; }
|
||||
public Character User { get; set; }
|
||||
public Item Item { get; set; }
|
||||
public Limb TargetLimb { get; set; }
|
||||
|
||||
public AbilityApplyTreatment(Character user, Character target, Item item)
|
||||
public AbilityApplyTreatment(Character user, Character target, Item item, Limb limb)
|
||||
{
|
||||
Character = target;
|
||||
User = user;
|
||||
Item = item;
|
||||
TargetLimb = limb;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,9 +71,9 @@ namespace Barotrauma
|
||||
{
|
||||
public EventType EventType => EventType.ItemStat;
|
||||
|
||||
public readonly Dictionary<ItemStatManager.TalentStatIdentifier, float> Stats;
|
||||
public readonly Dictionary<TalentStatIdentifier, float> Stats;
|
||||
|
||||
public SetItemStatEventData(Dictionary<ItemStatManager.TalentStatIdentifier, float> stats)
|
||||
public SetItemStatEventData(Dictionary<TalentStatIdentifier, float> stats)
|
||||
{
|
||||
Stats = stats;
|
||||
}
|
||||
|
||||
@@ -88,12 +88,15 @@ namespace Barotrauma
|
||||
|
||||
public abstract ItemPrefab FirstMatchingPrefab { get; }
|
||||
|
||||
public RequiredItem(int amount, float minCondition, float maxCondition, bool useCondition)
|
||||
public LocalizedString OverrideDescription { get; }
|
||||
|
||||
public RequiredItem(int amount, float minCondition, float maxCondition, bool useCondition, LocalizedString overrideDescription)
|
||||
{
|
||||
Amount = amount;
|
||||
MinCondition = minCondition;
|
||||
MaxCondition = maxCondition;
|
||||
UseCondition = useCondition;
|
||||
OverrideDescription = overrideDescription;
|
||||
}
|
||||
public readonly int Amount;
|
||||
public readonly float MinCondition;
|
||||
@@ -129,12 +132,14 @@ namespace Barotrauma
|
||||
|
||||
public override ItemPrefab FirstMatchingPrefab => ItemPrefab;
|
||||
|
||||
|
||||
public override bool MatchesItem(Item item)
|
||||
{
|
||||
return item?.Prefab.Identifier == ItemPrefabIdentifier;
|
||||
}
|
||||
|
||||
public RequiredItemByIdentifier(Identifier itemPrefab, int amount, float minCondition, float maxCondition, bool useCondition) : base(amount, minCondition, maxCondition, useCondition)
|
||||
public RequiredItemByIdentifier(Identifier itemPrefab, int amount, float minCondition, float maxCondition, bool useCondition, LocalizedString overrideDescription) :
|
||||
base(amount, minCondition, maxCondition, useCondition, overrideDescription)
|
||||
{
|
||||
ItemPrefabIdentifier = itemPrefab;
|
||||
using MD5 md5 = MD5.Create();
|
||||
@@ -163,7 +168,8 @@ namespace Barotrauma
|
||||
return item.HasTag(Tag);
|
||||
}
|
||||
|
||||
public RequiredItemByTag(Identifier tag, int amount, float minCondition, float maxCondition, bool useCondition) : base(amount, minCondition, maxCondition, useCondition)
|
||||
public RequiredItemByTag(Identifier tag, int amount, float minCondition, float maxCondition, bool useCondition, LocalizedString overrideDescription)
|
||||
: base(amount, minCondition, maxCondition, useCondition, overrideDescription)
|
||||
{
|
||||
Tag = tag;
|
||||
using MD5 md5 = MD5.Create();
|
||||
@@ -260,6 +266,12 @@ namespace Barotrauma
|
||||
bool useCondition = subElement.GetAttributeBool("usecondition", true);
|
||||
int amount = subElement.GetAttributeInt("count", subElement.GetAttributeInt("amount", 1));
|
||||
|
||||
LocalizedString description = string.Empty;
|
||||
if (subElement.GetAttributeString("description", string.Empty) is string texTag && !texTag.IsNullOrEmpty())
|
||||
{
|
||||
description = TextManager.Get(texTag);
|
||||
}
|
||||
|
||||
if (requiredItemIdentifier != Identifier.Empty)
|
||||
{
|
||||
var existing = requiredItems.FindIndex(r =>
|
||||
@@ -272,7 +284,7 @@ namespace Barotrauma
|
||||
amount += requiredItems[existing].Amount;
|
||||
requiredItems.RemoveAt(existing);
|
||||
}
|
||||
requiredItems.Add(new RequiredItemByIdentifier(requiredItemIdentifier, amount, minCondition, maxCondition, useCondition));
|
||||
requiredItems.Add(new RequiredItemByIdentifier(requiredItemIdentifier, amount, minCondition, maxCondition, useCondition, description));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -286,7 +298,7 @@ namespace Barotrauma
|
||||
amount += requiredItems[existing].Amount;
|
||||
requiredItems.RemoveAt(existing);
|
||||
}
|
||||
requiredItems.Add(new RequiredItemByTag(requiredItemTag, amount, minCondition, maxCondition, useCondition));
|
||||
requiredItems.Add(new RequiredItemByTag(requiredItemTag, amount, minCondition, maxCondition, useCondition, description));
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -669,8 +681,19 @@ namespace Barotrauma
|
||||
[Serialize(0.0f, IsPropertySaveable.No)]
|
||||
public float OffsetOnSelected { get; private set; }
|
||||
|
||||
private float health;
|
||||
|
||||
[Serialize(100.0f, IsPropertySaveable.No)]
|
||||
public float Health { get; private set; }
|
||||
public float Health
|
||||
{
|
||||
get { return health; }
|
||||
private set
|
||||
{
|
||||
//don't allow health values higher than this, because they lead to various issues:
|
||||
//e.g. integer overflows when we're casting to int to display a health value, value being set to float.Infinity if it's high enough
|
||||
health = Math.Min(value, 1000000.0f);
|
||||
}
|
||||
}
|
||||
|
||||
[Serialize(false, IsPropertySaveable.No)]
|
||||
public bool AllowSellingWhenBroken { get; private set; }
|
||||
@@ -702,12 +725,6 @@ namespace Barotrauma
|
||||
[Serialize(false, IsPropertySaveable.No)]
|
||||
public bool DamagedByMonsters { get; private set; }
|
||||
|
||||
[Serialize(false, IsPropertySaveable.No)]
|
||||
public bool FireProof { get; private set; }
|
||||
|
||||
[Serialize(false, IsPropertySaveable.No)]
|
||||
public bool WaterProof { get; private set; }
|
||||
|
||||
private float impactTolerance;
|
||||
[Serialize(0.0f, IsPropertySaveable.No)]
|
||||
public float ImpactTolerance
|
||||
@@ -813,6 +830,12 @@ namespace Barotrauma
|
||||
|
||||
[Serialize(1.0f, IsPropertySaveable.No, description: "How much the bots prioritize shooting this item with slow turrets, like railguns? Defaults to 1. Not used if AITurretPriority is 0. Distance to the target affects the decision making.")]
|
||||
public float AISlowTurretPriority { get; private set; }
|
||||
|
||||
[Serialize(float.PositiveInfinity, IsPropertySaveable.No, description: "The max distance at which the bots are allowed to target the items. Defaults to infinity.")]
|
||||
public float AITurretTargetingMaxDistance { get; private set; }
|
||||
|
||||
[Serialize(false, IsPropertySaveable.Yes, description: "If enabled, taking items from this container is never considered stealing.")]
|
||||
public bool AllowStealingContainedItems { get; private set; }
|
||||
|
||||
protected override Identifier DetermineIdentifier(XElement element)
|
||||
{
|
||||
@@ -1434,6 +1457,22 @@ namespace Barotrauma
|
||||
"Specify the amount in the variant to fix this.");
|
||||
}
|
||||
}
|
||||
if (originalElement?.Name.ToIdentifier() == "Deconstruct" &&
|
||||
variantElement?.Name.ToIdentifier() == "Deconstruct")
|
||||
{
|
||||
if (originalElement.Elements().Any(e => e.Name.ToIdentifier() == "Item") &&
|
||||
variantElement.Elements().Any(e => e.Name.ToIdentifier() == "RequiredItem"))
|
||||
{
|
||||
DebugConsole.AddWarning($"Potential error in item variant \"{Identifier}\": " +
|
||||
$"the item defines deconstruction recipes using 'RequiredItem' instead of 'Item'. Overriding the base recipe may not work correctly.");
|
||||
}
|
||||
if (variantElement.Elements().Any(e => e.Name.ToIdentifier() == "Item") &&
|
||||
originalElement.Elements().Any(e => e.Name.ToIdentifier() == "RequiredItem"))
|
||||
{
|
||||
DebugConsole.AddWarning($"Potential error in item \"{parent.Identifier}\": " +
|
||||
$"the item defines deconstruction recipes using 'RequiredItem' instead of 'Item'. The item variant \"{Identifier}\" may not override the base recipe correctly.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,29 +5,48 @@ using System.Collections.Generic;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
[NetworkSerialize]
|
||||
internal readonly record struct TalentStatIdentifier(ItemTalentStats Stat, Identifier TalentIdentifier, Option<UInt32> UniqueCharacterId) : INetSerializableStruct
|
||||
{
|
||||
/// <summary>
|
||||
/// Stackable identifiers feature a unique ID to allow multiple stats applied by the same talent from different characters to coexist.
|
||||
/// </summary>
|
||||
public static TalentStatIdentifier CreateStackable(ItemTalentStats stat, Identifier talentIdentifier, UInt32 characterId)
|
||||
=> new(stat, talentIdentifier, Option<UInt32>.Some(characterId));
|
||||
|
||||
/// <summary>
|
||||
/// Unstackable identifiers do not have a unique ID causing them to be identical to other stats applied by the same talent from different characters and thus only one of them will be applied.
|
||||
/// <see cref="ItemStatManager.ApplyStat"/> will always use the highest value for unstackable stats.
|
||||
/// </summary>
|
||||
public static TalentStatIdentifier CreateUnstackable(ItemTalentStats stat, Identifier talentIdentifier)
|
||||
=> new(stat, talentIdentifier, Option.None);
|
||||
}
|
||||
|
||||
internal sealed class ItemStatManager
|
||||
{
|
||||
private Item item;
|
||||
|
||||
public ItemStatManager(Item item)
|
||||
{
|
||||
this.item = item;
|
||||
}
|
||||
|
||||
[NetworkSerialize]
|
||||
public readonly record struct TalentStatIdentifier(ItemTalentStats Stat, Identifier TalentIdentifier, UInt32 CharacterID) : INetSerializableStruct;
|
||||
|
||||
private readonly Dictionary<TalentStatIdentifier, float> talentStats = new();
|
||||
private readonly Item item;
|
||||
|
||||
public void ApplyStat(ItemTalentStats stat, float value, CharacterTalent talent)
|
||||
public ItemStatManager(Item item) => this.item = item;
|
||||
|
||||
public void ApplyStat(ItemTalentStats stat, bool stackable, float value, CharacterTalent talent)
|
||||
{
|
||||
if (talent.Character?.ID is not { } characterId ||
|
||||
talent.Prefab?.Identifier is not { } talentIdentifier)
|
||||
talent.Prefab?.Identifier is not { } talentIdentifier) { return; }
|
||||
|
||||
var identifier = stackable
|
||||
? TalentStatIdentifier.CreateStackable(stat, talentIdentifier, characterId)
|
||||
: TalentStatIdentifier.CreateUnstackable(stat, talentIdentifier);
|
||||
|
||||
if (!stackable)
|
||||
{
|
||||
return;
|
||||
if (talentStats.TryGetValue(identifier, out float existingValue))
|
||||
{
|
||||
// Always use the highest value for non-stackable stats
|
||||
if (existingValue > value) { return; }
|
||||
}
|
||||
}
|
||||
|
||||
TalentStatIdentifier identifier = new TalentStatIdentifier(stat, talentIdentifier, characterId);
|
||||
talentStats[identifier] = value;
|
||||
|
||||
#if SERVER
|
||||
@@ -38,21 +57,19 @@ namespace Barotrauma
|
||||
#endif
|
||||
}
|
||||
|
||||
// Used for getting the value value from network packet
|
||||
public void ApplyStat(TalentStatIdentifier identifier, float value)
|
||||
{
|
||||
talentStats[identifier] = value;
|
||||
}
|
||||
/// <summary>
|
||||
/// Used for setting the value value from network packet; bypassing all validity checks.
|
||||
/// </summary>
|
||||
public void ApplyStatDirect(TalentStatIdentifier identifier, float value) => talentStats[identifier] = value;
|
||||
|
||||
public float GetAdjustedValue(ItemTalentStats stat, float originalValue)
|
||||
{
|
||||
float total = originalValue;
|
||||
|
||||
foreach (var (key, value) in talentStats)
|
||||
{
|
||||
if (key.Stat == stat)
|
||||
{
|
||||
total *= value;
|
||||
}
|
||||
if (key.Stat != stat) { continue; }
|
||||
total *= value;
|
||||
}
|
||||
|
||||
return total;
|
||||
|
||||
Reference in New Issue
Block a user