Unstable 1.8.4.0
This commit is contained in:
@@ -335,7 +335,7 @@ namespace Barotrauma.Items.Components
|
||||
item.CreateServerEvent(this);
|
||||
}
|
||||
#else
|
||||
if (GameMain.Client != null && GameMain.Client.MidRoundSyncing &&
|
||||
if (GameMain.Client != null && GameMain.Client.MidRoundSyncing && Submarine.MainSub != null &&
|
||||
(item.Submarine == Submarine.MainSub || DockingTarget.item.Submarine == Submarine.MainSub))
|
||||
{
|
||||
Screen.Selected.Cam.Position = Submarine.MainSub.WorldPosition;
|
||||
@@ -1224,11 +1224,10 @@ namespace Barotrauma.Items.Components
|
||||
#if CLIENT
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (GameMain.GameSession?.Campaign != null && !CampaignMode.AllowedToManageCampaign(ClientPermissions.ManageMap))
|
||||
{
|
||||
return;
|
||||
if (GameMain.GameSession?.Campaign != null && !CampaignMode.AllowedToManageCampaign(ClientPermissions.ManageMap))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1254,9 +1253,12 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
if (newDockedState != wasDocked)
|
||||
{
|
||||
bool tryingToToggleOutpostDocking = docked ?
|
||||
DockingTarget?.Item?.Submarine?.Info?.IsOutpost ?? false :
|
||||
FindAdjacentPort()?.Item?.Submarine?.Info?.IsOutpost ?? false;
|
||||
var targetPort = docked ? DockingTarget : FindAdjacentPort();
|
||||
bool tryingToToggleOutpostDocking =
|
||||
item.Submarine is { Info.IsOutpost: false } &&
|
||||
//check that the "parent submarine of the submarine" is not an outpost (that this isn't a shuttle/elevator that's part of an outpost)
|
||||
item.Submarine?.Submarine is not { Info.IsOutpost: true } &&
|
||||
targetPort is { item.Submarine.Info.IsOutpost: true };
|
||||
//trying to dock/undock from an outpost and the signal was sent by some automated system instead of a character
|
||||
// -> ask if the player really wants to dock/undock to prevent a softlock if someone's wired the docking port
|
||||
// in a way that makes always makes it dock/undock immediately at the start of the roun
|
||||
|
||||
@@ -21,7 +21,7 @@ namespace Barotrauma.Items.Components
|
||||
private Gap linkedGap;
|
||||
private bool isOpen;
|
||||
|
||||
private float openState;
|
||||
private float openState, lastOpenState;
|
||||
private readonly Sprite doorSprite, weldedSprite, brokenSprite;
|
||||
private readonly bool scaleBrokenSprite, fadeBrokenSprite;
|
||||
private readonly bool autoOrientGap;
|
||||
@@ -59,6 +59,8 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
public bool IgnoreSignals { get; private set; }
|
||||
|
||||
//how much "less stuck" partially doors get when opened
|
||||
const float StuckReductionOnOpen = 30.0f;
|
||||
|
||||
@@ -216,6 +218,7 @@ namespace Barotrauma.Items.Components
|
||||
get { return openState; }
|
||||
set
|
||||
{
|
||||
lastOpenState = openState;
|
||||
openState = MathHelper.Clamp(value, 0.0f, 1.0f);
|
||||
#if CLIENT
|
||||
float size = IsHorizontal ? item.Rect.Width : item.Rect.Height;
|
||||
@@ -327,13 +330,24 @@ namespace Barotrauma.Items.Components
|
||||
private readonly LocalizedString cannotOpenText = TextManager.Get("DoorMsgCannotOpen");
|
||||
public override bool HasRequiredItems(Character character, bool addMessage, LocalizedString msg = null)
|
||||
{
|
||||
Msg = HasAccess(character) ? "ItemMsgOpen" : "ItemMsgForceOpenCrowbar";
|
||||
if (IsBroken)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (isOpen)
|
||||
{
|
||||
Msg = HasAccess(character) ? "ItemMsgClose" : "ItemMsgForceCloseCrowbar";
|
||||
}
|
||||
else
|
||||
{
|
||||
Msg = HasAccess(character) ? "ItemMsgOpen" : "ItemMsgForceOpenCrowbar";
|
||||
}
|
||||
ParseMsg();
|
||||
if (addMessage)
|
||||
{
|
||||
msg = msg ?? (HasIntegratedButtons ? accessDeniedTxt : cannotOpenText).Value;
|
||||
msg ??= (HasIntegratedButtons ? accessDeniedTxt : cannotOpenText).Value;
|
||||
}
|
||||
return isBroken || base.HasRequiredItems(character, addMessage, msg);
|
||||
return base.HasRequiredItems(character, addMessage, msg);
|
||||
}
|
||||
|
||||
public override bool Pick(Character picker)
|
||||
@@ -350,6 +364,7 @@ namespace Barotrauma.Items.Components
|
||||
if (!HasAccess(picker))
|
||||
{
|
||||
ToggleState(ActionType.OnPicked, picker);
|
||||
ApplyStatusEffects(ActionType.OnPicked, 1.0f, picker);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -458,12 +473,12 @@ namespace Barotrauma.Items.Components
|
||||
if (PredictedState == null)
|
||||
{
|
||||
OpenState += deltaTime * (isOpen ? OpeningSpeed : -ClosingSpeed);
|
||||
isClosing = openState > 0.0f && openState < 1.0f && !isOpen;
|
||||
isClosing = openState is > 0.0f and < 1.0f && !isOpen;
|
||||
}
|
||||
else
|
||||
{
|
||||
OpenState += deltaTime * ((bool)PredictedState ? OpeningSpeed : -ClosingSpeed);
|
||||
isClosing = openState > 0.0f && openState < 1.0f && !(bool)PredictedState;
|
||||
OpenState += deltaTime * (PredictedState.Value ? OpeningSpeed : -ClosingSpeed);
|
||||
isClosing = openState is > 0.0f and < 1.0f && !PredictedState.Value;
|
||||
|
||||
resetPredictionTimer -= deltaTime;
|
||||
if (resetPredictionTimer <= 0.0f)
|
||||
@@ -476,7 +491,11 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
if (isClosing)
|
||||
{
|
||||
if (OpenState < 0.9f) { PushCharactersAway(); }
|
||||
//server gives the clients more leeway on moving through closing doors
|
||||
//latency can often otherwise make a client get blocked by a closing door server-side even if it seemed like they made it through client-side
|
||||
float pushCharactersAwayThreshold = GameMain.NetworkMember is { IsServer: true } ? 0.1f : 0.9f;
|
||||
|
||||
if (OpenState < pushCharactersAwayThreshold) { PushCharactersAway(); }
|
||||
if (CheckSubmarinesInDoorWay())
|
||||
{
|
||||
PredictedState = null;
|
||||
@@ -707,6 +726,7 @@ namespace Barotrauma.Items.Components
|
||||
foreach (Character c in Character.CharacterList)
|
||||
{
|
||||
if (!c.Enabled) { continue; }
|
||||
if (c.SelectedItem?.GetComponent<Controller>() is { } controller && controller.IsAttachedUser(c)) { continue; }
|
||||
if (!MathUtils.IsValid(c.SimPosition))
|
||||
{
|
||||
if (!characterPosErrorShown.Contains(c))
|
||||
@@ -767,11 +787,11 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (IsHorizontal)
|
||||
{
|
||||
body.SetTransform(new Vector2(body.SimPosition.X, item.SimPosition.Y + dir * doorRectSimSize.Y * 2.0f), body.Rotation);
|
||||
body.SetTransformIgnoreContacts(new Vector2(body.SimPosition.X, item.SimPosition.Y + dir * doorRectSimSize.Y * 2.0f), body.Rotation);
|
||||
}
|
||||
else
|
||||
{
|
||||
body.SetTransform(new Vector2(item.SimPosition.X + dir * doorRectSimSize.X * 1.2f, body.SimPosition.Y), body.Rotation);
|
||||
body.SetTransformIgnoreContacts(new Vector2(item.SimPosition.X + dir * doorRectSimSize.X * 1.2f, body.SimPosition.Y), body.Rotation);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -797,23 +817,20 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
|
||||
partial void OnFailedToOpen();
|
||||
|
||||
|
||||
public override bool HasAccess(Character character)
|
||||
{
|
||||
if (!item.IsInteractable(character)) { return false; }
|
||||
if (HasIntegratedButtons)
|
||||
{
|
||||
return base.HasAccess(character);
|
||||
}
|
||||
else
|
||||
{
|
||||
return base.HasAccess(character) && Item.GetConnectedComponents<Controller>(true).Any(b => b.HasAccess(character));
|
||||
}
|
||||
if (!base.HasAccess(character)) { return false; }
|
||||
if (HasIntegratedButtons) { return true; }
|
||||
var buttons = Item.GetConnectedComponents<Controller>(recursive: true);
|
||||
// If there are no buttons, and we can access the door, treat it accessible. Might be controlled by some mechanism, such as motion sensor.
|
||||
return buttons.None() || buttons.Any(b => b.HasAccess(character));
|
||||
}
|
||||
|
||||
public override void ReceiveSignal(Signal signal, Connection connection)
|
||||
{
|
||||
if (IsStuck || IsJammed) { return; }
|
||||
if (IsStuck || IsJammed || IgnoreSignals) { return; }
|
||||
|
||||
bool wasOpen = PredictedState == null ? isOpen : PredictedState.Value;
|
||||
|
||||
|
||||
@@ -172,7 +172,7 @@ namespace Barotrauma.Items.Components
|
||||
if (item.Connections == null)
|
||||
{
|
||||
//no connections and can't be wired = must be powered by something like batteries
|
||||
hasPower = Voltage > MinVoltage;
|
||||
hasPower = HasPower;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
+33
-11
@@ -1,4 +1,4 @@
|
||||
#nullable enable
|
||||
#nullable enable
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
@@ -70,6 +70,19 @@ namespace Barotrauma.Items.Components
|
||||
[Editable, Serialize(false, IsPropertySaveable.Yes, "")]
|
||||
public bool PreloadCharacter { get; set; }
|
||||
|
||||
[Editable, Serialize(false, IsPropertySaveable.Yes, "Should the \"spawn monsters\" setting affect this item in the PvP mode?")]
|
||||
public bool AffectedByPvPSpawnMonstersSetting { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Implemented as a property and checked on the fly instead of disabling the component,
|
||||
/// because the signals sent by the component might be necessary even if it can't spawn anything.
|
||||
/// </summary>
|
||||
private bool DisabledByByPvPSpawnMonstersSetting =>
|
||||
!SpeciesName.IsNullOrEmpty() &&
|
||||
AffectedByPvPSpawnMonstersSetting &&
|
||||
GameMain.GameSession?.GameMode is PvPMode &&
|
||||
GameMain.NetworkMember is { ServerSettings.PvPSpawnMonsters: false };
|
||||
|
||||
private float spawnTimer;
|
||||
private float? spawnTimerGoal;
|
||||
|
||||
@@ -114,15 +127,24 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
if (PreloadCharacter && !Screen.Selected.IsEditor && !preloadInitiated)
|
||||
if (DisabledByByPvPSpawnMonstersSetting)
|
||||
{
|
||||
SpawnCharacter(Vector2.Zero, onSpawn: (Character c) =>
|
||||
CanSpawn = false;
|
||||
//in most cases we could probably just disable the component here and return,
|
||||
//but the state_out signal might be needed for something even if the spawning is disabled
|
||||
}
|
||||
else
|
||||
{
|
||||
if (PreloadCharacter && !Screen.Selected.IsEditor && !preloadInitiated)
|
||||
{
|
||||
preloadedCharacter = c;
|
||||
c.DisabledByEvent = true;
|
||||
});
|
||||
preloadInitiated = true;
|
||||
return;
|
||||
SpawnCharacter(Vector2.Zero, onSpawn: (Character c) =>
|
||||
{
|
||||
preloadedCharacter = c;
|
||||
c.DisabledByEvent = true;
|
||||
});
|
||||
preloadInitiated = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
base.Update(deltaTime, cam);
|
||||
@@ -182,7 +204,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
private bool CanSpawnMore()
|
||||
{
|
||||
if (!CanSpawn) { return false; }
|
||||
if (!CanSpawn || DisabledByByPvPSpawnMonstersSetting) { return false; }
|
||||
if (MaximumAmount > 0 && spawnedAmount >= MaximumAmount) { return false; }
|
||||
|
||||
if (OnlySpawnWhenCrewInRange)
|
||||
@@ -300,7 +322,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
else if (!string.IsNullOrWhiteSpace(ItemIdentifier))
|
||||
{
|
||||
Identifier[] allItems = ItemIdentifier.Split(',').Select(s => s.Trim()).ToIdentifiers().ToArray();
|
||||
Identifier[] allItems = ItemIdentifier.ToIdentifiers().ToArray();
|
||||
Identifier itemIdentifier = allItems.GetRandomUnsynced();
|
||||
ItemPrefab? prefab = ItemPrefab.Find(null, itemIdentifier);
|
||||
if (prefab is null) { return; }
|
||||
@@ -320,7 +342,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(SpeciesName))
|
||||
{
|
||||
Identifier[] allSpecies = SpeciesName.Split(',').Select(s => s.Trim()).ToIdentifiers().ToArray();
|
||||
Identifier[] allSpecies = SpeciesName.ToIdentifiers().ToArray();
|
||||
Identifier species = allSpecies.GetRandomUnsynced();
|
||||
Entity.Spawner?.AddCharacterToSpawnQueue(species, pos, onSpawn);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using Barotrauma.Networking;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
@@ -15,47 +16,57 @@ namespace Barotrauma.Items.Components
|
||||
private AfflictionPrefab selectedEffect, selectedTaintedEffect;
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public string Effect
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
public string Effect { get; set; }
|
||||
|
||||
[Serialize("geneticmaterialdebuff", IsPropertySaveable.Yes)]
|
||||
public Identifier TaintedEffect
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
[Serialize("geneticmaterialdebuff", IsPropertySaveable.Yes, description: "Either the identifier or the type for the tainted effect prefab")]
|
||||
public Identifier TaintedEffect { get; set; }
|
||||
|
||||
private bool tainted;
|
||||
|
||||
[Serialize(false, IsPropertySaveable.Yes)]
|
||||
public bool Tainted
|
||||
{
|
||||
get { return tainted; }
|
||||
private set
|
||||
set
|
||||
{
|
||||
if (!value) { return; }
|
||||
tainted = true;
|
||||
item.AllowDeconstruct = false;
|
||||
if (!TaintedEffect.IsEmpty)
|
||||
tainted = value;
|
||||
if (tainted)
|
||||
{
|
||||
selectedTaintedEffect = AfflictionPrefab.Prefabs.Where(a =>
|
||||
a.Identifier == TaintedEffect ||
|
||||
a.AfflictionType == TaintedEffect).GetRandomUnsynced();
|
||||
if (!TaintedEffect.IsEmpty)
|
||||
{
|
||||
selectedTaintedEffect = AfflictionPrefab.Prefabs.Where(a =>
|
||||
a.Identifier == TaintedEffect ||
|
||||
a.AfflictionType == TaintedEffect).GetRandomUnsynced();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (targetCharacter != null)
|
||||
{
|
||||
var affliction = targetCharacter.CharacterHealth.GetAllAfflictions().FirstOrDefault(a => a.Prefab == selectedEffect);
|
||||
if (affliction != null)
|
||||
{
|
||||
affliction.Strength = 0;
|
||||
}
|
||||
}
|
||||
|
||||
selectedTaintedEffect = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Serialize(false, IsPropertySaveable.Yes)]
|
||||
public bool SetTaintedOnDeath { get; private set; }
|
||||
|
||||
[Serialize(false, IsPropertySaveable.Yes)]
|
||||
public bool CanBeUntainted { get; private set; }
|
||||
|
||||
//only for saving the selected tainted effect
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public Identifier SelectedTaintedEffect
|
||||
{
|
||||
get { return selectedTaintedEffect?.Identifier ?? Identifier.Empty; }
|
||||
private set
|
||||
{
|
||||
selectedTaintedEffect = !value.IsEmpty ? AfflictionPrefab.Prefabs.Find(a => a.Identifier == value) : null;
|
||||
}
|
||||
private set { selectedTaintedEffect = !value.IsEmpty ? AfflictionPrefab.Prefabs.Find(a => a.Identifier == value) : null; }
|
||||
}
|
||||
|
||||
public GeneticMaterial(Item item, ContentXElement element)
|
||||
@@ -66,6 +77,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
materialName = TextManager.Get(nameId);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(Effect))
|
||||
{
|
||||
selectedEffect = AfflictionPrefab.Prefabs.Where(a =>
|
||||
@@ -74,20 +86,103 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
[Serialize(3.0f, IsPropertySaveable.No)]
|
||||
public float ConditionIncreaseOnCombineMin { get; set; }
|
||||
[Serialize(0.0f, IsPropertySaveable.No)]
|
||||
public float ConditionIncreaseOnCombineMin { get; set; }
|
||||
|
||||
[Serialize(8.0f, IsPropertySaveable.No)]
|
||||
[Serialize(0.0f, IsPropertySaveable.No)]
|
||||
public float ConditionIncreaseOnCombineMax { get; set; }
|
||||
|
||||
[Serialize(3.0f, IsPropertySaveable.No, description: "When refining, min value for condition increase bonus based on the quality of the worse gene.")]
|
||||
public float ConditionIncreaseOnLowQualityCombine { get; set; }
|
||||
|
||||
[Serialize(25.0f, IsPropertySaveable.No, description: "When refining, max value for condition increase bonus based on the quality of the worse gene.")]
|
||||
public float ConditionIncreaseOnHighQualityCombine { get; set; }
|
||||
|
||||
private bool SharesTypeWith(GeneticMaterial otherGeneticMaterial)
|
||||
{
|
||||
return GetSharedTypeOrDefault(otherGeneticMaterial) != null;
|
||||
}
|
||||
|
||||
private ItemPrefab GetSharedTypeOrDefault(GeneticMaterial otherGeneticMaterial)
|
||||
{
|
||||
if (otherGeneticMaterial == null) { return default; }
|
||||
|
||||
return AllMaterialTypes.FirstOrDefault(materialType => otherGeneticMaterial.AllMaterialTypes.Contains(materialType));
|
||||
}
|
||||
|
||||
private IEnumerable<ItemPrefab> AllMaterialTypes
|
||||
{
|
||||
get
|
||||
{
|
||||
yield return item.Prefab;
|
||||
|
||||
if (IsCombined) { yield return NestedMaterial.item.Prefab; }
|
||||
}
|
||||
}
|
||||
|
||||
private GeneticMaterial NestedMaterial
|
||||
{
|
||||
get
|
||||
{
|
||||
if (item == null || item.OwnInventory == null) { return null; }
|
||||
|
||||
var nestedItemWithGeneticMaterial = item.OwnInventory.AllItems.FirstOrDefault(it => it.GetComponent<GeneticMaterial>() != null);
|
||||
if (nestedItemWithGeneticMaterial == null) { return null; }
|
||||
|
||||
return nestedItemWithGeneticMaterial.GetComponent<GeneticMaterial>();
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsCombined
|
||||
{
|
||||
get
|
||||
{
|
||||
if (NestedMaterial != null) { return true; }
|
||||
|
||||
// check if this is the nested material
|
||||
if (item.ParentInventory != null &&
|
||||
item.ParentInventory.Owner is Item parentItem &&
|
||||
parentItem.GetComponent<GeneticMaterial>()?.NestedMaterial == this)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private CombineResult GetCombineRefineResult(GeneticMaterial otherGeneticMaterial)
|
||||
{
|
||||
if (otherGeneticMaterial == null)
|
||||
{
|
||||
return CombineResult.None;
|
||||
}
|
||||
|
||||
// both are combined, no more processing is possible
|
||||
if (IsCombined && otherGeneticMaterial.IsCombined)
|
||||
{
|
||||
return CombineResult.None;
|
||||
}
|
||||
|
||||
// neither is combined, can be either refined or combined
|
||||
if (!IsCombined && !otherGeneticMaterial.IsCombined)
|
||||
{
|
||||
return SharesTypeWith(otherGeneticMaterial) ? CombineResult.Refined : CombineResult.Combined;
|
||||
}
|
||||
|
||||
// one is combined, if they share a type, they can be refined
|
||||
return SharesTypeWith(otherGeneticMaterial) ? CombineResult.Refined : CombineResult.None;
|
||||
}
|
||||
|
||||
public bool CanBeCombinedWith(GeneticMaterial otherGeneticMaterial)
|
||||
{
|
||||
return !tainted && otherGeneticMaterial != null && !otherGeneticMaterial.tainted && item.AllowDeconstruct && otherGeneticMaterial.item.AllowDeconstruct;
|
||||
return GetCombineRefineResult(otherGeneticMaterial) != CombineResult.None;
|
||||
}
|
||||
|
||||
public override void Equip(Character character)
|
||||
{
|
||||
if (character == null) { return; }
|
||||
|
||||
IsActive = true;
|
||||
|
||||
if (targetCharacter != null) { return; }
|
||||
@@ -99,7 +194,7 @@ namespace Barotrauma.Items.Components
|
||||
float selectedEffectStrength = GetCombinedEffectStrength();
|
||||
character.CharacterHealth.ApplyAffliction(null, selectedEffect.Instantiate(selectedEffectStrength));
|
||||
var affliction = character.CharacterHealth.GetAllAfflictions().FirstOrDefault(a => a.Prefab == selectedEffect);
|
||||
if (affliction != null)
|
||||
if (affliction != null)
|
||||
{
|
||||
affliction.Strength = selectedEffectStrength;
|
||||
//force strength to the correct value to bypass any clamping e.g. AfflictionHusk might be doing
|
||||
@@ -107,24 +202,27 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
#if SERVER
|
||||
item.CreateServerEvent(this);
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
if (tainted && selectedTaintedEffect != null)
|
||||
{
|
||||
float selectedTaintedEffectStrength = GetCombinedTaintedEffectStrength();
|
||||
character.CharacterHealth.ApplyAffliction(null, selectedTaintedEffect.Instantiate(selectedTaintedEffectStrength));
|
||||
var affliction = character.CharacterHealth.GetAllAfflictions().FirstOrDefault(a => a.Prefab == selectedTaintedEffect);
|
||||
if (affliction != null)
|
||||
{
|
||||
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);
|
||||
#endif
|
||||
}
|
||||
|
||||
foreach (Item containedItem in item.ContainedItems)
|
||||
{
|
||||
containedItem.GetComponent<GeneticMaterial>()?.Equip(character);
|
||||
@@ -136,23 +234,60 @@ namespace Barotrauma.Items.Components
|
||||
base.Update(deltaTime, cam);
|
||||
if (targetCharacter != null)
|
||||
{
|
||||
if (SetTaintedOnDeath && !tainted &&
|
||||
targetCharacter.IsDead && targetCharacter.CauseOfDeath is not { Type: CauseOfDeathType.Disconnected })
|
||||
{
|
||||
SetTainted(true);
|
||||
}
|
||||
|
||||
var rootContainer = item.RootContainer;
|
||||
if (!targetCharacter.HasEquippedItem(item) &&
|
||||
if (!targetCharacter.HasEquippedItem(item) &&
|
||||
(rootContainer == null || !targetCharacter.HasEquippedItem(rootContainer) || !targetCharacter.Inventory.IsInLimbSlot(rootContainer, InvSlotType.HealthInterface)))
|
||||
{
|
||||
item.ApplyStatusEffects(ActionType.OnSevered, 1.0f, targetCharacter);
|
||||
IsActive = false;
|
||||
Character prevTargetCharacter = targetCharacter;
|
||||
|
||||
var affliction = targetCharacter.CharacterHealth.GetAllAfflictions().FirstOrDefault(a => a.Prefab == selectedEffect);
|
||||
if (affliction != null) { affliction.Strength = GetCombinedEffectStrength(); }
|
||||
var taintedAffliction = targetCharacter.CharacterHealth.GetAllAfflictions().FirstOrDefault(a => a.Prefab == selectedTaintedEffect);
|
||||
if (taintedAffliction != null) { taintedAffliction.Strength = GetCombinedTaintedEffectStrength(); }
|
||||
//deactivate so the material is no longer updated or considered to be "in effect" in GetCombinedEffectStrength
|
||||
Deactivate();
|
||||
if (rootContainer != null)
|
||||
{
|
||||
foreach (var otherItem in rootContainer.ContainedItems)
|
||||
{
|
||||
if (otherItem != item && otherItem.GetComponent<GeneticMaterial>() is { IsActive: true } otherGeneticMaterial)
|
||||
{
|
||||
//we need to deactivate other genetic materials in the container too at this point,
|
||||
//otherwise their effects might get triggered by the damage done by removing the gene
|
||||
otherGeneticMaterial.Deactivate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
targetCharacter = null;
|
||||
//do this after nullifying the effects, otherwise the damage from removing the genes could trigger the gene's own effects
|
||||
item.ApplyStatusEffects(ActionType.OnSevered, 1.0f, prevTargetCharacter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void Deactivate()
|
||||
{
|
||||
IsActive = false;
|
||||
if (targetCharacter != null)
|
||||
{
|
||||
var affliction = targetCharacter.CharacterHealth.GetAllAfflictions().FirstOrDefault(a => a.Prefab == selectedEffect);
|
||||
if (affliction != null)
|
||||
{
|
||||
affliction.Strength = GetCombinedEffectStrength();
|
||||
}
|
||||
|
||||
var taintedAffliction = targetCharacter.CharacterHealth.GetAllAfflictions().FirstOrDefault(a => a.Prefab == selectedTaintedEffect);
|
||||
if (taintedAffliction != null)
|
||||
{
|
||||
taintedAffliction.Strength = GetCombinedTaintedEffectStrength();
|
||||
}
|
||||
}
|
||||
NestedMaterial?.Deactivate();
|
||||
targetCharacter = null;
|
||||
}
|
||||
|
||||
public enum CombineResult
|
||||
{
|
||||
None,
|
||||
@@ -160,35 +295,85 @@ namespace Barotrauma.Items.Components
|
||||
Combined
|
||||
}
|
||||
|
||||
public CombineResult Combine(GeneticMaterial otherGeneticMaterial, Character user)
|
||||
public CombineResult Combine(GeneticMaterial otherGeneticMaterial, Character user, out Item itemToDestroy)
|
||||
{
|
||||
if (!CanBeCombinedWith(otherGeneticMaterial)) { return CombineResult.None; }
|
||||
var combineRefineResult = GetCombineRefineResult(otherGeneticMaterial);
|
||||
|
||||
float conditionIncrease = Rand.Range(ConditionIncreaseOnCombineMin, ConditionIncreaseOnCombineMax);
|
||||
conditionIncrease += user?.GetStatValue(StatTypes.GeneticMaterialRefineBonus) ?? 0.0f;
|
||||
if (item.Prefab == otherGeneticMaterial.item.Prefab)
|
||||
float randomQualityIncrease = Rand.Range(ConditionIncreaseOnCombineMin, ConditionIncreaseOnCombineMax);
|
||||
float talentIncrease = user?.GetStatValue(StatTypes.GeneticMaterialRefineBonus) ?? 0.0f;
|
||||
|
||||
bool perfectQuality = item.IsFullCondition || otherGeneticMaterial.item.IsFullCondition;
|
||||
|
||||
itemToDestroy = otherGeneticMaterial.item;
|
||||
|
||||
if (combineRefineResult == CombineResult.Refined)
|
||||
{
|
||||
float taintedProbability = GetTaintedProbabilityOnRefine(otherGeneticMaterial, user);
|
||||
item.Condition = Math.Max(item.Condition, otherGeneticMaterial.item.Condition) + conditionIncrease;
|
||||
if (taintedProbability >= Rand.Range(0.0f, 1.0f))
|
||||
float maxQuality = Math.Max(item.Condition, otherGeneticMaterial.item.Condition);
|
||||
float minQuality = Math.Min(item.Condition, otherGeneticMaterial.item.Condition);
|
||||
bool oneIsCombined = IsCombined || otherGeneticMaterial.IsCombined;
|
||||
|
||||
float minQualityProportionalIncreaseBonus = MathHelper.Lerp(ConditionIncreaseOnLowQualityCombine, ConditionIncreaseOnHighQualityCombine, Math.Clamp(minQuality / 80.0f, 0f, 1f));
|
||||
float totalQualityIncrease = minQualityProportionalIncreaseBonus + randomQualityIncrease + talentIncrease;
|
||||
if (oneIsCombined) { totalQualityIncrease /= 2f; }
|
||||
|
||||
float newQuality = maxQuality + totalQualityIncrease;
|
||||
|
||||
// the deconstructor wants to remove and delete the item for otherGeneticMaterial,
|
||||
// we want to keep the type that's not being refined here, so we move around the items
|
||||
if (!IsCombined && otherGeneticMaterial.IsCombined)
|
||||
{
|
||||
MakeTainted();
|
||||
if (item.Prefab == otherGeneticMaterial.item.Prefab)
|
||||
{
|
||||
// main items share type, nest the non-shared item
|
||||
item.OwnInventory?.TryPutItem(otherGeneticMaterial.NestedMaterial.item, user: null);
|
||||
}
|
||||
else
|
||||
{
|
||||
// the non-shared item is the main item in otherGeneticMaterial,
|
||||
// we need to nest it...
|
||||
item.OwnInventory?.TryPutItem(otherGeneticMaterial.item, user: null);
|
||||
|
||||
// ...and get rid of the now triple-nested item inside it
|
||||
var otherNestedItem = otherGeneticMaterial.NestedMaterial.item;
|
||||
otherGeneticMaterial.item.OwnInventory?.RemoveItem(otherNestedItem);
|
||||
itemToDestroy = otherNestedItem;
|
||||
}
|
||||
}
|
||||
|
||||
// note: the condition of combined items represents the quality of both materials,
|
||||
// and the condition of the nested item should equal that of the housing item
|
||||
item.Condition = newQuality;
|
||||
// this can become combined as a result of the item shuffling above
|
||||
if (IsCombined) { NestedMaterial.item.Condition = newQuality; }
|
||||
|
||||
// if one item is 100% condition, remove taint when refining
|
||||
if (CanBeUntainted && perfectQuality)
|
||||
{
|
||||
SetTainted(false, affectsNestedGene: true);
|
||||
}
|
||||
else if (GetTaintedProbabilityOnRefine(otherGeneticMaterial, user) >= Rand.Range(0.0f, 1.0f))
|
||||
{
|
||||
SetTainted(true);
|
||||
}
|
||||
return CombineResult.Refined;
|
||||
}
|
||||
else
|
||||
else if (combineRefineResult == CombineResult.Combined)
|
||||
{
|
||||
item.Condition = otherGeneticMaterial.Item.Condition =
|
||||
(item.Condition + otherGeneticMaterial.Item.Condition) / 2.0f + conditionIncrease;
|
||||
float averageQuality = (item.Condition + otherGeneticMaterial.Item.Condition) / 2.0f;
|
||||
item.Condition = otherGeneticMaterial.Item.Condition = averageQuality + randomQualityIncrease + talentIncrease;
|
||||
item.OwnInventory?.TryPutItem(otherGeneticMaterial.Item, user: null);
|
||||
item.AllowDeconstruct = false;
|
||||
otherGeneticMaterial.Item.AllowDeconstruct = false;
|
||||
if (GetTaintedProbabilityOnCombine(user) >= Rand.Range(0.0f, 1.0f))
|
||||
|
||||
// if one item is 100% condition, remove taint when combining
|
||||
if (CanBeUntainted && perfectQuality)
|
||||
{
|
||||
MakeTainted();
|
||||
SetTainted(false, affectsNestedGene: true);
|
||||
}
|
||||
else if (GetTaintedProbabilityOnCombine(user) >= Rand.Range(0.0f, 1.0f))
|
||||
{
|
||||
SetTainted(true);
|
||||
}
|
||||
return CombineResult.Combined;
|
||||
}
|
||||
|
||||
return combineRefineResult;
|
||||
}
|
||||
|
||||
private float GetCombinedEffectStrength()
|
||||
@@ -198,11 +383,13 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
var geneticMaterial = otherItem.GetComponent<GeneticMaterial>();
|
||||
if (geneticMaterial == null || !geneticMaterial.IsActive) { continue; }
|
||||
|
||||
if (geneticMaterial.selectedEffect == selectedEffect)
|
||||
{
|
||||
effectStrength += otherItem.ConditionPercentage / 100.0f * selectedEffect.MaxStrength;
|
||||
}
|
||||
}
|
||||
|
||||
return effectStrength;
|
||||
}
|
||||
|
||||
@@ -213,17 +400,20 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
var geneticMaterial = otherItem.GetComponent<GeneticMaterial>();
|
||||
if (geneticMaterial == null || !geneticMaterial.IsActive) { continue; }
|
||||
|
||||
if (selectedTaintedEffect != null && geneticMaterial.selectedTaintedEffect == selectedTaintedEffect)
|
||||
{
|
||||
taintedEffectStrength += otherItem.ConditionPercentage / 100.0f * selectedTaintedEffect.MaxStrength;
|
||||
}
|
||||
}
|
||||
|
||||
return taintedEffectStrength;
|
||||
}
|
||||
|
||||
private float GetTaintedProbabilityOnRefine(GeneticMaterial otherGeneticMaterial, Character user)
|
||||
{
|
||||
if (user == null) { return 1.0f; }
|
||||
|
||||
float probability = MathHelper.Lerp(0.0f, 0.99f, Math.Max(item.Condition, otherGeneticMaterial.Item.Condition) / 100.0f);
|
||||
probability *= MathHelper.Lerp(1.0f, 0.25f, DegreeOfSuccess(user));
|
||||
return MathHelper.Clamp(probability, 0.0f, 1.0f);
|
||||
@@ -232,17 +422,24 @@ namespace Barotrauma.Items.Components
|
||||
private static float GetTaintedProbabilityOnCombine(Character user)
|
||||
{
|
||||
if (user == null) { return 1.0f; }
|
||||
|
||||
float probability = 1.0f - user.GetStatValue(StatTypes.GeneticMaterialTaintedProbabilityReductionOnCombine);
|
||||
return MathHelper.Clamp(probability, 0.0f, 1.0f);
|
||||
}
|
||||
|
||||
private void MakeTainted()
|
||||
public void SetTainted(bool newValue, bool affectsNestedGene = false)
|
||||
{
|
||||
if (GameMain.NetworkMember?.IsClient ?? false) { return; }
|
||||
Tainted = true;
|
||||
|
||||
Tainted = newValue;
|
||||
#if SERVER
|
||||
item.CreateServerEvent(this);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
if (affectsNestedGene && NestedMaterial != null)
|
||||
{
|
||||
NestedMaterial.SetTainted(newValue);
|
||||
}
|
||||
}
|
||||
|
||||
public static LocalizedString TryCreateName(ItemPrefab prefab, XElement element)
|
||||
@@ -258,7 +455,8 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return prefab.Name;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -556,7 +556,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
if (spawnProduct || spawnSeed)
|
||||
{
|
||||
VineTile vine = Vines.GetRandomUnsynced();
|
||||
VineTile vine = Vines.GetRandomUnsynced()!;
|
||||
spawnPos = vine.GetWorldPosition(planter, slot.Offset);
|
||||
}
|
||||
else
|
||||
|
||||
@@ -14,13 +14,15 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
partial class Holdable : Pickable, IServerSerializable, IClientSerializable
|
||||
{
|
||||
private readonly struct EventData : IEventData
|
||||
private readonly struct AttachEventData : IEventData
|
||||
{
|
||||
public readonly Vector2 AttachPos;
|
||||
|
||||
public EventData(Vector2 attachPos)
|
||||
public readonly Character Attacher;
|
||||
|
||||
public AttachEventData(Vector2 attachPos, Character attacher)
|
||||
{
|
||||
AttachPos = attachPos;
|
||||
Attacher = attacher;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -225,6 +227,51 @@ namespace Barotrauma.Items.Components
|
||||
set;
|
||||
}
|
||||
|
||||
[Editable, Serialize("", IsPropertySaveable.Yes, translationTextTag: "ItemMsg", description: "A text displayed next to the item when it's been dropped on the floor (not attached to a wall).")]
|
||||
public string MsgWhenDropped
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// For setting the handle positions using status effects
|
||||
/// </summary>
|
||||
public Vector2 Handle1
|
||||
{
|
||||
get { return ConvertUnits.ToDisplayUnits(handlePos[0]); }
|
||||
set
|
||||
{
|
||||
handlePos[0] = ConvertUnits.ToSimUnits(value);
|
||||
if (item.FlippedX)
|
||||
{
|
||||
handlePos[0].X = -handlePos[0].X;
|
||||
}
|
||||
if (!secondHandlePosDefined)
|
||||
{
|
||||
Handle2 = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// For setting the handle positions using status effects
|
||||
/// </summary>
|
||||
public Vector2 Handle2
|
||||
{
|
||||
get { return ConvertUnits.ToDisplayUnits(handlePos[1]); }
|
||||
set
|
||||
{
|
||||
handlePos[1] = ConvertUnits.ToSimUnits(value);
|
||||
if (item.FlippedX)
|
||||
{
|
||||
handlePos[1].X = -handlePos[1].X;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool secondHandlePosDefined;
|
||||
|
||||
public Holdable(Item item, ContentXElement element)
|
||||
: base(item, element)
|
||||
{
|
||||
@@ -254,9 +301,14 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
int index = i - 1;
|
||||
string attributeName = "handle" + i;
|
||||
var attribute = element.GetAttribute(attributeName);
|
||||
// If no value is defind for handle2, use the value of handle1.
|
||||
var value = attribute != null ? ConvertUnits.ToSimUnits(XMLExtensions.ParseVector2(attribute.Value)) : previousValue;
|
||||
Vector2 value = previousValue;
|
||||
var attribute = element.GetAttribute(attributeName);
|
||||
if (attribute != null)
|
||||
{
|
||||
secondHandlePosDefined = i > 1;
|
||||
value = ConvertUnits.ToSimUnits(XMLExtensions.ParseVector2(attribute.Value));
|
||||
}
|
||||
handlePos[index] = value;
|
||||
previousValue = value;
|
||||
}
|
||||
@@ -688,7 +740,19 @@ namespace Barotrauma.Items.Components
|
||||
#endif
|
||||
//make the item pickable with the default pick key and with no specific tools/items when it's deattached
|
||||
RequiredItems.Clear();
|
||||
DisplayMsg = "";
|
||||
if (MsgWhenDropped.IsNullOrEmpty())
|
||||
{
|
||||
DisplayMsg = "";
|
||||
}
|
||||
else
|
||||
{
|
||||
DisplayMsg = TextManager.Get(MsgWhenDropped);
|
||||
DisplayMsg =
|
||||
DisplayMsg.Loaded ?
|
||||
TextManager.ParseInputTypes(DisplayMsg) :
|
||||
MsgWhenDropped;
|
||||
}
|
||||
|
||||
PickKey = InputType.Select;
|
||||
#if CLIENT
|
||||
item.DrawDepthOffset = SpriteDepthWhenDropped - item.SpriteDepth;
|
||||
@@ -755,21 +819,14 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
if (GameMain.NetworkMember != null)
|
||||
{
|
||||
if (character != Character.Controlled)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else if (GameMain.NetworkMember.IsServer)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
#if CLIENT
|
||||
if (character == Character.Controlled)
|
||||
{
|
||||
Vector2 attachPos = ConvertUnits.ToSimUnits(GetAttachPosition(character));
|
||||
item.CreateClientEvent(this, new EventData(attachPos));
|
||||
#endif
|
||||
item.CreateClientEvent(this, new AttachEventData(attachPos, character));
|
||||
}
|
||||
#endif
|
||||
//don't attach at this point in MP: instead rely on the network events created above
|
||||
return false;
|
||||
}
|
||||
else
|
||||
@@ -824,9 +881,13 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
if (user.Submarine != null)
|
||||
{
|
||||
//we must add some "padding" to the raycast to ensure it reaches all the way to a wall
|
||||
//otherwise the cursor might be outside a wall, but the grid cell it's in might be partially inside
|
||||
Vector2 padding = Submarine.GridSize * new Vector2(Math.Sign(mouseDiff.X), Math.Sign(mouseDiff.Y));
|
||||
|
||||
if (Submarine.PickBody(
|
||||
ConvertUnits.ToSimUnits(user.Position),
|
||||
ConvertUnits.ToSimUnits(user.Position + mouseDiff), collisionCategory: Physics.CollisionWall) != null)
|
||||
ConvertUnits.ToSimUnits(user.Position + mouseDiff + padding), collisionCategory: Physics.CollisionWall) != null)
|
||||
{
|
||||
attachPos = userPos + mouseDiff * Submarine.LastPickedFraction + offset;
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Collections.Immutable;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
@@ -23,7 +23,7 @@ namespace Barotrauma.Items.Components
|
||||
public string OwnerTags
|
||||
{
|
||||
get => string.Join(',', OwnerTagSet);
|
||||
set => OwnerTagSet = value.Split(',').ToIdentifiers().ToImmutableHashSet();
|
||||
set => OwnerTagSet = value.ToIdentifiers().ToImmutableHashSet();
|
||||
}
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes, alwaysUseInstanceValues: true)]
|
||||
@@ -95,6 +95,10 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
item.AddTag(s);
|
||||
}
|
||||
if (GameMain.GameSession?.GameMode is PvPMode)
|
||||
{
|
||||
item.AddTag($"id_{character.TeamID}".ToIdentifier());
|
||||
}
|
||||
if (!string.IsNullOrWhiteSpace(spawnPoint.IdCardDesc))
|
||||
{
|
||||
item.Description = Description = spawnPoint.IdCardDesc;
|
||||
|
||||
@@ -52,7 +52,11 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (holdable.Attached)
|
||||
{
|
||||
GameAnalyticsManager.AddDesignEvent("ResourceCollected:" + (GameMain.GameSession?.GameMode?.Preset.Identifier.Value ?? "none") + ":" + item.Prefab.Identifier);
|
||||
//we don't need info of every collected resource, we can get a good sample size just by logging a small sample
|
||||
if (GameAnalyticsManager.ShouldLogRandomSample())
|
||||
{
|
||||
GameAnalyticsManager.AddDesignEvent("ResourceCollected:" + (GameMain.GameSession?.GameMode?.Preset.Identifier.Value ?? "none") + ":" + item.Prefab.Identifier);
|
||||
}
|
||||
holdable.DeattachFromWall();
|
||||
}
|
||||
trigger.Enabled = false;
|
||||
|
||||
@@ -174,9 +174,11 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public override void Drop(Character dropper, bool setTransform = true)
|
||||
{
|
||||
//end hit first (which sets the weapon to the "held" state, with disabled physics and no special collision detection)
|
||||
EndHit();
|
||||
//ensure the physics body is enabled
|
||||
item.body.PhysEnabled = true;
|
||||
base.Drop(dropper, setTransform);
|
||||
hitting = false;
|
||||
hitPos = 0.0f;
|
||||
}
|
||||
|
||||
public override void UpdateBroken(float deltaTime, Camera cam)
|
||||
@@ -251,10 +253,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
if (hitPos < -MathHelper.Pi)
|
||||
{
|
||||
RestoreCollision();
|
||||
hitting = false;
|
||||
hitTargets.Clear();
|
||||
hitPos = 0;
|
||||
EndHit();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -291,6 +290,14 @@ namespace Barotrauma.Items.Components
|
||||
User = character;
|
||||
}
|
||||
|
||||
private void EndHit()
|
||||
{
|
||||
RestoreCollision();
|
||||
hitting = false;
|
||||
hitTargets.Clear();
|
||||
hitPos = 0;
|
||||
}
|
||||
|
||||
private void RestoreCollision()
|
||||
{
|
||||
impactQueue.Clear();
|
||||
@@ -327,6 +334,7 @@ namespace Barotrauma.Items.Components
|
||||
if (targetLimb.character.IgnoreMeleeWeapons) { return false; }
|
||||
var targetCharacter = targetLimb.character;
|
||||
if (targetCharacter == picker) { return false; }
|
||||
if (HitFriendlyTarget(targetCharacter)) { return false; }
|
||||
if (AllowHitMultiple)
|
||||
{
|
||||
if (hitTargets.Contains(targetCharacter)) { return false; }
|
||||
@@ -341,7 +349,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (targetCharacter == picker || targetCharacter == User) { return false; }
|
||||
if (targetCharacter.IgnoreMeleeWeapons) { return false; }
|
||||
targetLimb = targetCharacter.AnimController.GetLimb(LimbType.Torso); //Otherwise armor can be bypassed in strange ways
|
||||
if (HitFriendlyTarget(targetCharacter)) { return false; }
|
||||
if (AllowHitMultiple)
|
||||
{
|
||||
if (hitTargets.Contains(targetCharacter)) { return false; }
|
||||
@@ -380,6 +388,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
else if (f2.Body.UserData is Holdable holdable && holdable.CanPush)
|
||||
{
|
||||
if (holdable.Item.GetRootInventoryOwner() == User) { return false; }
|
||||
hitTargets.Add(holdable.Item);
|
||||
}
|
||||
}
|
||||
@@ -387,10 +396,22 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
impactQueue.Enqueue(f2);
|
||||
|
||||
return true;
|
||||
|
||||
// Prevent bots from hitting friendly targets.
|
||||
bool HitFriendlyTarget(Character target)
|
||||
{
|
||||
if (User.IsPlayer) { return false; }
|
||||
if (User.AIController is HumanAIController { Enabled: true } humanAI)
|
||||
{
|
||||
if (humanAI.ObjectiveManager.CurrentObjective is AIObjectiveCombat combat && combat.Enemy != target)
|
||||
{
|
||||
if (humanAI.IsFriendly(target, onlySameTeam: true)) { return true; }
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private System.Text.StringBuilder serverLogger;
|
||||
@@ -412,7 +433,7 @@ namespace Barotrauma.Items.Components
|
||||
Limb targetLimb = target.UserData as Limb;
|
||||
Character targetCharacter = targetLimb?.character ?? target.UserData as Character;
|
||||
Structure targetStructure = target.UserData as Structure ?? targetFixture.UserData as Structure;
|
||||
Item targetItem = target.UserData as Item ?? targetFixture.UserData as Item;
|
||||
Item targetItem = target.UserData is Holdable h ? h.Item : target.UserData as Item ?? targetFixture.UserData as Item;
|
||||
Entity targetEntity = targetCharacter ?? targetStructure ?? targetItem ?? target.UserData as Entity;
|
||||
if (Attack != null)
|
||||
{
|
||||
@@ -452,10 +473,9 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
#endif
|
||||
}
|
||||
else if (target.UserData is Holdable holdable && holdable.CanPush)
|
||||
else if (target.UserData is Holdable { CanPush: true } holdable)
|
||||
{
|
||||
if (holdable.Item.Removed) { return; }
|
||||
Attack.DoDamage(user, holdable.Item, item.WorldPosition, 1.0f);
|
||||
RestoreCollision();
|
||||
hitting = false;
|
||||
User = null;
|
||||
@@ -475,8 +495,8 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
if (GameMain.NetworkMember is { IsServer: true } server && targetEntity != null)
|
||||
{
|
||||
server.CreateEntityEvent(item, new Item.ApplyStatusEffectEventData(conditionalActionType, targetItemComponent: null, targetCharacter, targetLimb, useTarget: targetEntity));
|
||||
server.CreateEntityEvent(item, new Item.ApplyStatusEffectEventData(ActionType.OnUse, targetItemComponent: null, targetCharacter, targetLimb, useTarget: targetEntity));
|
||||
server.CreateEntityEvent(item, new Item.ApplyStatusEffectEventData(conditionalActionType, targetItemComponent: this, targetCharacter, targetLimb, useTarget: targetEntity));
|
||||
server.CreateEntityEvent(item, new Item.ApplyStatusEffectEventData(ActionType.OnUse, targetItemComponent: this, targetCharacter, targetLimb, useTarget: targetEntity));
|
||||
serverLogger ??= new System.Text.StringBuilder();
|
||||
serverLogger.Clear();
|
||||
serverLogger.Append($"{picker?.LogName} used {item.Name}");
|
||||
|
||||
@@ -202,12 +202,26 @@ namespace Barotrauma.Items.Components
|
||||
#if CLIENT
|
||||
if (requiredTime < float.MaxValue && picker == Character.Controlled)
|
||||
{
|
||||
string text = string.Empty;
|
||||
if (!string.IsNullOrWhiteSpace(PickingMsg))
|
||||
{
|
||||
text = PickingMsg;
|
||||
}
|
||||
else if (this is Door door)
|
||||
{
|
||||
text = door.IsClosed ? "progressbar.opening" : "progressbar.closing";
|
||||
}
|
||||
else
|
||||
{
|
||||
text = "progressbar.deattaching";
|
||||
}
|
||||
|
||||
Character.Controlled?.UpdateHUDProgressBar(
|
||||
this,
|
||||
item.WorldPosition,
|
||||
pickTimer / requiredTime,
|
||||
GUIStyle.Red, GUIStyle.Green,
|
||||
!string.IsNullOrWhiteSpace(PickingMsg) ? PickingMsg : this is Door ? "progressbar.opening" : "progressbar.deattaching");
|
||||
text);
|
||||
}
|
||||
#endif
|
||||
picker.AnimController.UpdateUseItem(!picker.IsClimbing, item.WorldPosition + new Vector2(0.0f, 100.0f) * ((pickTimer / 10.0f) % 0.1f));
|
||||
|
||||
@@ -243,7 +243,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
IsActive = true;
|
||||
float baseReloadTime = reload;
|
||||
float weaponSkill = character.GetSkillLevel("weapons");
|
||||
float weaponSkill = character.GetSkillLevel(Tags.WeaponsSkill);
|
||||
|
||||
bool applyReloadFailure = ReloadSkillRequirement > 0 && ReloadNoSkill > reload && weaponSkill < ReloadSkillRequirement;
|
||||
if (applyReloadFailure)
|
||||
@@ -291,9 +291,14 @@ namespace Barotrauma.Items.Components
|
||||
var lastProjectile = LastProjectile;
|
||||
if (lastProjectile != projectile)
|
||||
{
|
||||
//Note that we always snap the rope here, unlike when firing a rope from a turret.
|
||||
//That's because handheld RangedWeapons have some special logic for handling the rope,
|
||||
//which doesn't support multiple attached ropes (see Holdable.GetRope and the references to it)
|
||||
lastProjectile?.Item.GetComponent<Rope>()?.Snap();
|
||||
}
|
||||
float damageMultiplier = (1f + item.GetQualityModifier(Quality.StatType.FirepowerMultiplier)) * WeaponDamageModifier;
|
||||
|
||||
float rangedAttackMultiplier = character?.GetStatValue(StatTypes.RangedAttackMultiplier) ?? 0;
|
||||
float damageMultiplier = (1f + item.GetQualityModifier(Quality.StatType.FirepowerMultiplier) + rangedAttackMultiplier) * WeaponDamageModifier;
|
||||
projectile.Launcher = item;
|
||||
|
||||
ignoredBodies.Clear();
|
||||
@@ -303,6 +308,9 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (l.IsSevered) { continue; }
|
||||
ignoredBodies.Add(l.body.FarseerBody);
|
||||
#if SERVER
|
||||
ignoredBodies.Add(l.LagCompensatedBody.FarseerBody);
|
||||
#endif
|
||||
}
|
||||
|
||||
foreach (Item heldItem in character.HeldItems)
|
||||
@@ -314,6 +322,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
}
|
||||
projectile.Item.body.Dir = Item.body.Dir;
|
||||
projectile.Shoot(character, character.AnimController.AimSourceSimPos, barrelPos, rotation + spread, ignoredBodies: ignoredBodies.ToList(), createNetworkEvent: false, damageMultiplier, LaunchImpulse);
|
||||
projectile.Item.GetComponent<Rope>()?.Attach(Item, projectile.Item);
|
||||
if (projectile.Item.body != null)
|
||||
|
||||
@@ -248,7 +248,7 @@ namespace Barotrauma.Items.Components
|
||||
var barrelHull = Hull.FindHull(ConvertUnits.ToDisplayUnits(rayStartWorld), item.CurrentHull, useWorldCoordinates: true);
|
||||
if (barrelHull != null && barrelHull != item.CurrentHull)
|
||||
{
|
||||
if (MathUtils.GetLineRectangleIntersection(ConvertUnits.ToDisplayUnits(sourcePos), ConvertUnits.ToDisplayUnits(rayStart), item.CurrentHull.Rect, out Vector2 hullIntersection))
|
||||
if (MathUtils.GetLineWorldRectangleIntersection(ConvertUnits.ToDisplayUnits(sourcePos), ConvertUnits.ToDisplayUnits(rayStart), item.CurrentHull.Rect, out Vector2 hullIntersection))
|
||||
{
|
||||
if (!item.CurrentHull.ConnectedGaps.Any(g => g.Open > 0.0f && Submarine.RectContains(g.Rect, hullIntersection)))
|
||||
{
|
||||
@@ -320,7 +320,7 @@ namespace Barotrauma.Items.Components
|
||||
private readonly List<FireSource> fireSourcesInRange = new List<FireSource>();
|
||||
private void Repair(Vector2 rayStart, Vector2 rayEnd, float deltaTime, Character user, float degreeOfSuccess, List<Body> ignoredBodies)
|
||||
{
|
||||
var collisionCategories = Physics.CollisionWall | Physics.CollisionItem | Physics.CollisionLevel | Physics.CollisionRepairableWall;
|
||||
var collisionCategories = Physics.CollisionWall | Physics.CollisionItem | Physics.CollisionLevel | Physics.CollisionRepairableWall | Physics.CollisionItemBlocking;
|
||||
if (!IgnoreCharacters)
|
||||
{
|
||||
collisionCategories |= Physics.CollisionCharacter;
|
||||
@@ -654,8 +654,9 @@ namespace Barotrauma.Items.Components
|
||||
FixCharacterProjSpecific(user, deltaTime, targetLimb.character);
|
||||
return true;
|
||||
}
|
||||
else if (targetBody.UserData is Item targetItem)
|
||||
else if (targetBody.UserData is Barotrauma.Item or Holdable)
|
||||
{
|
||||
Item targetItem = targetBody.UserData is Holdable holdable ? holdable.Item : (Item)targetBody.UserData;
|
||||
if (!HitItems || !targetItem.IsInteractable(user)) { return false; }
|
||||
|
||||
var levelResource = targetItem.GetComponent<LevelResource>();
|
||||
@@ -764,7 +765,7 @@ namespace Barotrauma.Items.Components
|
||||
if (!character.AnimController.InWater && character.AnimController is HumanoidAnimController humanAnim &&
|
||||
Math.Abs(fromCharacterToLeak.X) < 100.0f && fromCharacterToLeak.Y < 0.0f && fromCharacterToLeak.Y > -150.0f)
|
||||
{
|
||||
humanAnim.Crouching = true;
|
||||
humanAnim.Crouch();
|
||||
}
|
||||
}
|
||||
if (!character.IsClimbing)
|
||||
|
||||
@@ -75,6 +75,9 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public readonly ContentXElement originalElement;
|
||||
|
||||
/// <summary>
|
||||
/// The default delay for delayed client-side corrections (see <see cref="StartDelayedCorrection"/>.
|
||||
/// </summary>
|
||||
protected const float CorrectionDelay = 1.0f;
|
||||
protected CoroutineHandle delayedCorrectionCoroutine;
|
||||
|
||||
@@ -661,10 +664,13 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
protected virtual void RemoveComponentSpecific()
|
||||
{
|
||||
#if CLIENT
|
||||
HUDOverlay?.Remove();
|
||||
HUDOverlay = null;
|
||||
#endif
|
||||
}
|
||||
|
||||
protected string GetTextureDirectory(ContentXElement subElement)
|
||||
=> subElement.DoesAttributeReferenceFileNameAlone("texture") ? Path.GetDirectoryName(item.Prefab.FilePath) : string.Empty;
|
||||
protected string GetTextureDirectory(ContentXElement subElement) => item.Prefab.GetTexturePath(subElement, item.Prefab.ParentPrefab);
|
||||
|
||||
public bool HasRequiredSkills(Character character)
|
||||
{
|
||||
@@ -794,12 +800,25 @@ namespace Barotrauma.Items.Components
|
||||
/// </summary>
|
||||
private bool CheckIdCardAccess(RelatedItem relatedItem, IdCard idCard)
|
||||
{
|
||||
if (item.Submarine != null && item.Submarine != GameMain.NetworkMember?.RespawnManager?.RespawnShuttle)
|
||||
if (item.Submarine is { IsRespawnShuttle: false })
|
||||
{
|
||||
//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;
|
||||
if (GameMain.GameSession?.GameMode is PvPMode)
|
||||
{
|
||||
if (item.Submarine.TeamID != CharacterTeamType.FriendlyNPC && item.Submarine.TeamID != CharacterTeamType.None)
|
||||
{
|
||||
// In PvP, always allow access also to FriendlyNPC and None -> restrict access only to the enemy sub.
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (idCard.SubmarineSpecificID != 0 && item.Submarine.SubmarineSpecificIDTag != idCard.SubmarineSpecificID)
|
||||
{
|
||||
|
||||
@@ -108,7 +108,10 @@ namespace Barotrauma.Items.Components
|
||||
[Serialize(100, IsPropertySaveable.No, description: "How many items are placed in a row before starting a new row.")]
|
||||
public int ItemsPerRow { get; set; }
|
||||
|
||||
[Serialize(true, IsPropertySaveable.No, description: "Should the inventory of this item be visible when the item is selected.")]
|
||||
[Serialize(false, IsPropertySaveable.No, description: "Should items be drawn based on their position within the inventory?")]
|
||||
public bool ItemsUseInventoryPlacement { get; set; }
|
||||
|
||||
[Serialize(true, IsPropertySaveable.No, description: "Should the inventory of this item be visible when the item is selected. Note that this does not prevent dragging and dropping items to the item.")]
|
||||
public bool DrawInventory
|
||||
{
|
||||
get;
|
||||
@@ -139,13 +142,24 @@ namespace Barotrauma.Items.Components
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(true, IsPropertySaveable.No)]
|
||||
private ImmutableHashSet<Identifier> autoInteractWithContainedTags = ImmutableHashSet<Identifier>.Empty;
|
||||
[Serialize("", IsPropertySaveable.Yes, description: $"Interacting with this container will autointeract with contained items that have one of these tags. Only valid if {nameof(AutoInteractWithContained)} is set to true.")]
|
||||
public string AutoInteractWithContainedTags
|
||||
{
|
||||
get { return autoInteractWithContainedTags.ConvertToString(); }
|
||||
set
|
||||
{
|
||||
autoInteractWithContainedTags = value.ToIdentifiers().ToImmutableHashSet();
|
||||
}
|
||||
}
|
||||
|
||||
[Serialize(true, IsPropertySaveable.No, description: "Is the container accessible in general.")]
|
||||
public bool AllowAccess { get; set; }
|
||||
|
||||
[Serialize(false, IsPropertySaveable.No)]
|
||||
[Serialize(false, IsPropertySaveable.No, description: "Is the container only accessible when it's broken. Doesn't apply to editors.")]
|
||||
public bool AccessOnlyWhenBroken { get; set; }
|
||||
|
||||
[Serialize(true, IsPropertySaveable.No)]
|
||||
[Serialize(true, IsPropertySaveable.No, description: "Is the container accessible when dropped.")]
|
||||
public bool AllowAccessWhenDropped { get; set; }
|
||||
|
||||
[Serialize(5, IsPropertySaveable.No, description: "How many inventory slots the inventory has per row.")]
|
||||
@@ -421,12 +435,11 @@ namespace Barotrauma.Items.Components
|
||||
relatedItem ??= containableItem;
|
||||
foreach (StatusEffect effect in containableItem.StatusEffects)
|
||||
{
|
||||
activeContainedItems.Add(new ActiveContainedItem(
|
||||
containedItem,
|
||||
effect,
|
||||
containableItem.ExcludeBroken,
|
||||
containableItem.ExcludeFullCondition,
|
||||
containableItem.BlameEquipperForDeath));
|
||||
ActiveContainedItem activeContainedItem = new(containedItem, effect, containableItem.ExcludeBroken, containableItem.ExcludeFullCondition, containableItem.BlameEquipperForDeath);
|
||||
activeContainedItems.Add(activeContainedItem);
|
||||
|
||||
if (!ShouldApplyEffects(activeContainedItem)) { continue; }
|
||||
activeContainedItem.StatusEffect.Apply(ActionType.OnInserted, deltaTime: 1, item, targets);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -492,6 +505,12 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public void OnItemRemoved(Item containedItem)
|
||||
{
|
||||
foreach (ActiveContainedItem activeContainedItem in activeContainedItems)
|
||||
{
|
||||
if (activeContainedItem.Item != containedItem || !ShouldApplyEffects(activeContainedItem)) { continue; }
|
||||
activeContainedItem.StatusEffect.Apply(ActionType.OnRemoved, deltaTime: 1, item, targets);
|
||||
}
|
||||
|
||||
activeContainedItems.RemoveAll(i => i.Item == containedItem);
|
||||
containedItems.RemoveAll(i => i.Item == containedItem);
|
||||
item.SetContainedItemPositions();
|
||||
@@ -657,39 +676,47 @@ namespace Barotrauma.Items.Components
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var activeContainedItem in activeContainedItems)
|
||||
foreach (ActiveContainedItem activeContainedItem in activeContainedItems)
|
||||
{
|
||||
Item contained = activeContainedItem.Item;
|
||||
if (!ShouldApplyEffects(activeContainedItem)) continue;
|
||||
|
||||
if (activeContainedItem.ExcludeBroken && contained.Condition <= 0.0f) { continue; }
|
||||
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))
|
||||
{
|
||||
targets.AddRange(item.AllPropertyObjects);
|
||||
}
|
||||
if (effect.HasTargetType(StatusEffect.TargetType.Contained))
|
||||
{
|
||||
targets.AddRange(contained.AllPropertyObjects);
|
||||
}
|
||||
if (effect.HasTargetType(StatusEffect.TargetType.Character) && item.ParentInventory?.Owner is Character character)
|
||||
{
|
||||
targets.Add(character);
|
||||
}
|
||||
if (effect.HasTargetType(StatusEffect.TargetType.NearbyItems) ||
|
||||
effect.HasTargetType(StatusEffect.TargetType.NearbyCharacters))
|
||||
{
|
||||
effect.AddNearbyTargets(item.WorldPosition, targets);
|
||||
}
|
||||
effect.Apply(ActionType.OnActive, deltaTime, item, targets);
|
||||
effect.Apply(ActionType.OnContaining, deltaTime, item, targets);
|
||||
if (wearing) { effect.Apply(ActionType.OnWearing, deltaTime, item, targets); }
|
||||
if (item.GetComponent<Wearable>() is Wearable { IsActive: true })
|
||||
{
|
||||
effect.Apply(ActionType.OnWearing, deltaTime, item, targets);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool ShouldApplyEffects(ActiveContainedItem activeContainedItem)
|
||||
{
|
||||
Item contained = activeContainedItem.Item;
|
||||
if (activeContainedItem.ExcludeBroken && contained.Condition <= 0) { return false; }
|
||||
if (activeContainedItem.ExcludeFullCondition && contained.IsFullCondition) { return false; }
|
||||
StatusEffect effect = activeContainedItem.StatusEffect;
|
||||
|
||||
targets.Clear();
|
||||
if (effect.HasTargetType(StatusEffect.TargetType.This))
|
||||
{
|
||||
targets.AddRange(item.AllPropertyObjects);
|
||||
}
|
||||
if (effect.HasTargetType(StatusEffect.TargetType.Contained))
|
||||
{
|
||||
targets.AddRange(contained.AllPropertyObjects);
|
||||
}
|
||||
if (effect.HasTargetType(StatusEffect.TargetType.Character) && item.ParentInventory?.Owner is Character character)
|
||||
{
|
||||
targets.Add(character);
|
||||
}
|
||||
if (effect.HasTargetType(StatusEffect.TargetType.NearbyItems) || effect.HasTargetType(StatusEffect.TargetType.NearbyCharacters))
|
||||
{
|
||||
effect.AddNearbyTargets(item.WorldPosition, targets);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the positions of the contained items if this item has moved/rotated enough
|
||||
/// </summary>
|
||||
@@ -715,25 +742,37 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public override bool HasRequiredItems(Character character, bool addMessage, LocalizedString msg = null)
|
||||
{
|
||||
return AllowAccess && (!AccessOnlyWhenBroken || Item.Condition <= 0) && base.HasRequiredItems(character, addMessage, msg);
|
||||
return IsAccessible() && base.HasRequiredItems(character, addMessage, msg);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Is the container currently accessible. Use this method for checking the accessibility logic, instead of using custom logic on the properties.
|
||||
/// Use <see cref="HasRequiredItems"/> instead to do a transitive check, where the items of the character are also checked.
|
||||
/// </summary>
|
||||
public bool IsAccessible()
|
||||
{
|
||||
if (!AllowAccess) { return false; }
|
||||
if (AccessOnlyWhenBroken)
|
||||
{
|
||||
if (Screen.Selected is { IsEditor: true })
|
||||
{
|
||||
// AccessOnlyWhenBroken doesn't apply to editors.
|
||||
return true;
|
||||
}
|
||||
return item.Condition <= 0;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public override bool Select(Character character)
|
||||
{
|
||||
if (!AllowAccess) { return false; }
|
||||
if (item.Container != null) { return false; }
|
||||
if (AccessOnlyWhenBroken)
|
||||
{
|
||||
if (item.Condition > 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!IsAccessible()) { return false; }
|
||||
if (AutoInteractWithContained && character.SelectedItem == null && Screen.Selected is not { IsEditor: true })
|
||||
{
|
||||
foreach (Item contained in Inventory.AllItems)
|
||||
{
|
||||
if (contained.TryInteract(character))
|
||||
if (CanAutoInteractWithContained(contained) && contained.TryInteract(character))
|
||||
{
|
||||
character.FocusedItem = contained;
|
||||
return false;
|
||||
@@ -756,19 +795,12 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public override bool Pick(Character picker)
|
||||
{
|
||||
if (!AllowAccess) { return false; }
|
||||
if (AccessOnlyWhenBroken)
|
||||
{
|
||||
if (item.Condition > 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!IsAccessible()) { return false; }
|
||||
if (AutoInteractWithContained && Screen.Selected is not { IsEditor: true })
|
||||
{
|
||||
foreach (Item contained in Inventory.AllItems)
|
||||
{
|
||||
if (contained.TryInteract(picker))
|
||||
if (CanAutoInteractWithContained(contained) && contained.TryInteract(picker))
|
||||
{
|
||||
picker.FocusedItem = contained;
|
||||
return true;
|
||||
@@ -819,6 +851,12 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
private bool CanAutoInteractWithContained(Item containedItem)
|
||||
{
|
||||
return AutoInteractWithContained &&
|
||||
(autoInteractWithContainedTags.None() || autoInteractWithContainedTags.Any(t => containedItem.HasTag(t)));
|
||||
}
|
||||
|
||||
private void SetContainedActive(bool active)
|
||||
{
|
||||
if ((ContainableItems == null || !ContainableItems.Any(c => c.SetActive)) &&
|
||||
@@ -888,58 +926,16 @@ namespace Barotrauma.Items.Components
|
||||
#warning There's some code duplication here and in DrawContainedItems() method, but it's not straightforward to get rid of it, because of slightly different logic and the usage of draw positions vs. positions etc. Should probably be splitted into smaller methods.
|
||||
public void SetContainedItemPositions()
|
||||
{
|
||||
Vector2 transformedItemPos = ItemPos * item.Scale;
|
||||
Vector2 transformedItemInterval = ItemInterval * item.Scale;
|
||||
Vector2 transformedItemIntervalHorizontal = new Vector2(transformedItemInterval.X, 0.0f);
|
||||
Vector2 transformedItemIntervalVertical = new Vector2(0.0f, transformedItemInterval.Y);
|
||||
if (containedItems.Count == 0) { return; }
|
||||
|
||||
if (ItemPos == Vector2.Zero && ItemInterval == Vector2.Zero)
|
||||
{
|
||||
transformedItemPos = item.Position;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (item.body == null)
|
||||
{
|
||||
if (item.FlippedX)
|
||||
{
|
||||
transformedItemPos.X = -transformedItemPos.X;
|
||||
transformedItemPos.X += item.Rect.Width;
|
||||
transformedItemInterval.X = -transformedItemInterval.X;
|
||||
transformedItemIntervalHorizontal.X = -transformedItemIntervalHorizontal.X;
|
||||
}
|
||||
if (item.FlippedY)
|
||||
{
|
||||
transformedItemPos.Y = -transformedItemPos.Y;
|
||||
transformedItemPos.Y -= item.Rect.Height;
|
||||
transformedItemInterval.Y = -transformedItemInterval.Y;
|
||||
transformedItemIntervalVertical.Y = -transformedItemIntervalVertical.Y;
|
||||
}
|
||||
transformedItemPos += new Vector2(item.Rect.X, item.Rect.Y);
|
||||
if (Math.Abs(item.Rotation) > 0.01f)
|
||||
{
|
||||
Matrix transform = Matrix.CreateRotationZ(-item.RotationRad);
|
||||
transformedItemPos = Vector2.Transform(transformedItemPos - item.Position, transform) + item.Position;
|
||||
transformedItemInterval = Vector2.Transform(transformedItemInterval, transform);
|
||||
transformedItemIntervalHorizontal = Vector2.Transform(transformedItemIntervalHorizontal, transform);
|
||||
transformedItemIntervalVertical = Vector2.Transform(transformedItemIntervalVertical, transform);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Matrix transform = Matrix.CreateRotationZ(item.body.Rotation);
|
||||
if (item.body.Dir == -1.0f)
|
||||
{
|
||||
transformedItemPos.X = -transformedItemPos.X;
|
||||
transformedItemInterval.X = -transformedItemInterval.X;
|
||||
transformedItemIntervalHorizontal.X = -transformedItemIntervalHorizontal.X;
|
||||
}
|
||||
transformedItemPos = Vector2.Transform(transformedItemPos, transform);
|
||||
transformedItemInterval = Vector2.Transform(transformedItemInterval, transform);
|
||||
transformedItemIntervalHorizontal = Vector2.Transform(transformedItemIntervalHorizontal, transform);
|
||||
transformedItemPos += item.Position;
|
||||
}
|
||||
}
|
||||
var rootBody = item.RootContainer?.body ?? item.body;
|
||||
|
||||
Vector2 transformedItemPos = GetContainedPosition(
|
||||
drawPosition: false,
|
||||
out Vector2 transformedItemIntervalHorizontal,
|
||||
out Vector2 transformedItemIntervalVertical,
|
||||
out bool flippedX,
|
||||
out bool flippedY);
|
||||
|
||||
int i = 0;
|
||||
Vector2 currentItemPos = transformedItemPos;
|
||||
@@ -952,19 +948,19 @@ namespace Barotrauma.Items.Components
|
||||
if (item.body != null)
|
||||
{
|
||||
Matrix transform = Matrix.CreateRotationZ(item.body.Rotation);
|
||||
pos.X *= item.body.Dir;
|
||||
pos.X *= rootBody.Dir;
|
||||
itemPos = Vector2.Transform(pos, transform) + item.body.Position;
|
||||
}
|
||||
else
|
||||
{
|
||||
itemPos = pos;
|
||||
// This code is aped based on above. Not tested.
|
||||
if (item.FlippedX)
|
||||
if (flippedX)
|
||||
{
|
||||
itemPos.X = -itemPos.X;
|
||||
itemPos.X += item.Rect.Width;
|
||||
}
|
||||
if (item.FlippedY)
|
||||
if (flippedY)
|
||||
{
|
||||
itemPos.Y = -itemPos.Y;
|
||||
itemPos.Y -= item.Rect.Height;
|
||||
@@ -990,7 +986,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
if (item.body != null)
|
||||
{
|
||||
rotation *= item.body.Dir;
|
||||
rotation *= rootBody.Dir;
|
||||
rotation += item.body.Rotation;
|
||||
}
|
||||
else
|
||||
@@ -998,8 +994,7 @@ namespace Barotrauma.Items.Components
|
||||
rotation += -item.RotationRad;
|
||||
}
|
||||
contained.Item.body.FarseerBody.SetTransformIgnoreContacts(ref simPos, rotation);
|
||||
contained.Item.body.SetPrevTransform(contained.Item.body.SimPosition, contained.Item.body.Rotation);
|
||||
contained.Item.body.UpdateDrawPosition();
|
||||
contained.Item.body.UpdateDrawPosition(interpolate: false);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -1021,6 +1016,11 @@ namespace Barotrauma.Items.Components
|
||||
contained.Item.CurrentHull = item.CurrentHull;
|
||||
contained.Item.SetContainedItemPositions();
|
||||
|
||||
foreach (var lightComponent in contained.Item.GetComponents<LightComponent>())
|
||||
{
|
||||
lightComponent.SetLightSourceTransform();
|
||||
}
|
||||
|
||||
i++;
|
||||
if (Math.Abs(ItemInterval.X) > 0.001f && Math.Abs(ItemInterval.Y) > 0.001f)
|
||||
{
|
||||
@@ -1034,11 +1034,82 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
else
|
||||
{
|
||||
currentItemPos += transformedItemInterval;
|
||||
currentItemPos += transformedItemIntervalHorizontal + transformedItemIntervalVertical;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Vector2 GetContainedPosition(bool drawPosition,
|
||||
out Vector2 transformedItemIntervalHorizontal, out Vector2 transformedItemIntervalVertical,
|
||||
out bool flippedX, out bool flippedY)
|
||||
{
|
||||
Vector2 transformedItemPos = ItemPos * item.Scale;
|
||||
Vector2 transformedItemInterval = ItemInterval * item.Scale;
|
||||
transformedItemIntervalHorizontal = new Vector2(transformedItemInterval.X, 0.0f);
|
||||
transformedItemIntervalVertical = new Vector2(0.0f, transformedItemInterval.Y);
|
||||
|
||||
flippedX = item.RootContainer?.FlippedX ?? (item.FlippedX && item.Prefab.CanSpriteFlipX);
|
||||
flippedY = item.RootContainer?.FlippedY ?? (item.FlippedY && item.Prefab.CanSpriteFlipY);
|
||||
var rootBody = item.RootContainer?.body ?? item.body;
|
||||
bool bodyFlipped = rootBody is { Dir: -1 };
|
||||
|
||||
if (ItemPos == Vector2.Zero && ItemInterval == Vector2.Zero && !drawPosition)
|
||||
{
|
||||
transformedItemPos = item.Position;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (item.body == null)
|
||||
{
|
||||
if (flippedX)
|
||||
{
|
||||
transformedItemPos.X = -transformedItemPos.X;
|
||||
transformedItemPos.X += item.Rect.Width;
|
||||
transformedItemInterval.X = -transformedItemInterval.X;
|
||||
transformedItemIntervalHorizontal.X = -transformedItemIntervalHorizontal.X;
|
||||
}
|
||||
if (flippedY)
|
||||
{
|
||||
transformedItemPos.Y = -transformedItemPos.Y;
|
||||
transformedItemPos.Y -= item.Rect.Height;
|
||||
transformedItemInterval.Y = -transformedItemInterval.Y;
|
||||
transformedItemIntervalVertical.Y = -transformedItemIntervalVertical.Y;
|
||||
}
|
||||
transformedItemPos += new Vector2(item.Rect.X, item.Rect.Y);
|
||||
if (drawPosition)
|
||||
{
|
||||
if (item.Submarine != null) { transformedItemPos += item.Submarine.DrawPosition; }
|
||||
}
|
||||
if (Math.Abs(item.RotationRad) > 0.01f)
|
||||
{
|
||||
Matrix transform = Matrix.CreateRotationZ(-item.RotationRad);
|
||||
transformedItemPos =
|
||||
drawPosition ?
|
||||
Vector2.Transform(transformedItemPos - item.DrawPosition, transform) + item.DrawPosition :
|
||||
Vector2.Transform(transformedItemPos - item.Position, transform) + item.Position;
|
||||
transformedItemIntervalVertical = Vector2.Transform(transformedItemIntervalVertical, transform);
|
||||
transformedItemIntervalHorizontal = Vector2.Transform(transformedItemIntervalHorizontal, transform);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Matrix transform = Matrix.CreateRotationZ(drawPosition ? item.body.DrawRotation : item.body.Rotation);
|
||||
if (bodyFlipped)
|
||||
{
|
||||
transformedItemPos.X = -transformedItemPos.X;
|
||||
transformedItemInterval.X = -transformedItemInterval.X;
|
||||
transformedItemIntervalHorizontal.X = -transformedItemIntervalHorizontal.X;
|
||||
}
|
||||
|
||||
transformedItemPos = Vector2.Transform(transformedItemPos, transform);
|
||||
transformedItemIntervalVertical = Vector2.Transform(transformedItemIntervalVertical, transform);
|
||||
transformedItemIntervalHorizontal = Vector2.Transform(transformedItemIntervalHorizontal, transform);
|
||||
transformedItemPos += drawPosition ? item.body.DrawPosition : item.body.Position;
|
||||
}
|
||||
}
|
||||
return transformedItemPos;
|
||||
}
|
||||
|
||||
public override void OnItemLoaded()
|
||||
{
|
||||
Inventory.AllowSwappingContainedItems = AllowSwappingContainedItems;
|
||||
@@ -1086,6 +1157,8 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
SpawnAlwaysContainedItems();
|
||||
}
|
||||
|
||||
SetContainedItemPositions();
|
||||
}
|
||||
|
||||
private void SpawnAlwaysContainedItems()
|
||||
@@ -1096,7 +1169,7 @@ namespace Barotrauma.Items.Components
|
||||
foreach (string id in splitIds)
|
||||
{
|
||||
ItemPrefab prefab = ItemPrefab.Prefabs.Find(m => m.Identifier == id);
|
||||
if (prefab != null && Inventory != null && Inventory.CanBePut(prefab))
|
||||
if (prefab != null && Inventory != null && Inventory.CanProbablyBePut(prefab))
|
||||
{
|
||||
bool isEditor = false;
|
||||
#if CLIENT
|
||||
|
||||
@@ -18,7 +18,8 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public override bool Select(Character character)
|
||||
{
|
||||
if (character == null || character.LockHands || character.Removed || !(character.AnimController is HumanoidAnimController)) return false;
|
||||
if (character == null || character.LockHands || character.Removed ) { return false; }
|
||||
if (!character.CanClimb) { return false; }
|
||||
character.AnimController.StartClimbing();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -183,6 +183,13 @@ namespace Barotrauma.Items.Components
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(false, IsPropertySaveable.No, description: "Does the Controller require power to function (= to send signals and move the camera focus to a connected item)?")]
|
||||
public bool RequirePower
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(false, IsPropertySaveable.No, description: "If true, other items can be used simultaneously.")]
|
||||
public bool IsSecondaryItem
|
||||
{
|
||||
@@ -190,6 +197,13 @@ namespace Barotrauma.Items.Components
|
||||
private set;
|
||||
}
|
||||
|
||||
[Serialize(false, IsPropertySaveable.No, description: "If enabled, the user sticks to the position of this item even if the item moves.")]
|
||||
public bool ForceUserToStayAttached
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public Controller(Item item, ContentXElement element)
|
||||
: base(item, element)
|
||||
{
|
||||
@@ -199,25 +213,44 @@ namespace Barotrauma.Items.Components
|
||||
IsActive = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Hack for allowing characters to interact with a loader to get inside a boarding pod.
|
||||
/// Doing that simply by autointeracting with the contained pod is difficult, because interacting with the loader selects it
|
||||
/// _after_ the Select method of the pod is called by the autointeract logic, and the character only goes inside the pod if it's the selected item.
|
||||
/// </summary>
|
||||
private bool forceSelectNextFrame;
|
||||
|
||||
private float userCanInteractCheckTimer;
|
||||
|
||||
private const float UserCanInteractCheckInterval = 1.0f;
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
this.cam = cam;
|
||||
UserInCorrectPosition = false;
|
||||
if (!ForceUserToStayAttached) { UserInCorrectPosition = false; }
|
||||
|
||||
string signal = IsToggle && State ? output : falseOutput;
|
||||
if (item.Connections != null && IsToggle && !string.IsNullOrEmpty(signal))
|
||||
if (item.Connections != null && IsToggle && !string.IsNullOrEmpty(signal) && !IsOutOfPower())
|
||||
{
|
||||
item.SendSignal(signal, "signal_out");
|
||||
item.SendSignal(signal, "trigger_out");
|
||||
}
|
||||
|
||||
if (forceSelectNextFrame && user != null)
|
||||
{
|
||||
user.SelectedItem = item;
|
||||
}
|
||||
forceSelectNextFrame = false;
|
||||
|
||||
userCanInteractCheckTimer -= deltaTime;
|
||||
|
||||
if (user == null
|
||||
|| user.Removed
|
||||
|| !user.IsAnySelectedItem(item)
|
||||
|| item.ParentInventory != null
|
||||
|| !user.CanInteractWith(item)
|
||||
|| (item.ParentInventory != null && !IsAttachedUser(user))
|
||||
|| (UsableIn == UseEnvironment.Water && !user.AnimController.InWater)
|
||||
|| (UsableIn == UseEnvironment.Air && user.AnimController.InWater))
|
||||
|| (UsableIn == UseEnvironment.Air && user.AnimController.InWater)
|
||||
|| !CheckUserCanInteract())
|
||||
{
|
||||
if (user != null)
|
||||
{
|
||||
@@ -228,6 +261,17 @@ namespace Barotrauma.Items.Components
|
||||
return;
|
||||
}
|
||||
|
||||
if (ForceUserToStayAttached && Vector2.DistanceSquared(item.WorldPosition, user.WorldPosition) > 0.1f)
|
||||
{
|
||||
user.TeleportTo(item.WorldPosition);
|
||||
user.AnimController.Collider.ResetDynamics();
|
||||
foreach (var limb in user.AnimController.Limbs)
|
||||
{
|
||||
if (limb.Removed || limb.IsSevered) { continue; }
|
||||
limb.body?.ResetDynamics();
|
||||
}
|
||||
}
|
||||
|
||||
user.AnimController.StartUsingItem();
|
||||
|
||||
if (userPos != Vector2.Zero)
|
||||
@@ -330,6 +374,22 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
private bool CheckUserCanInteract()
|
||||
{
|
||||
//optimization: CanInteractWith is relatively heavy (can involve visibility checks for example), let's not do it every frame
|
||||
if (user != null)
|
||||
{
|
||||
if (userCanInteractCheckTimer <= 0.0f)
|
||||
{
|
||||
userCanInteractCheckTimer = UserCanInteractCheckInterval;
|
||||
return user.CanInteractWith(item);
|
||||
}
|
||||
}
|
||||
//we only do the actual check every UserCanInteractCheckInterval seconds
|
||||
//can mean the component can stay selected for <1s after the user no longer has access to it
|
||||
return true;
|
||||
}
|
||||
|
||||
private double lastUsed;
|
||||
|
||||
public override bool Use(float deltaTime, Character activator = null)
|
||||
@@ -344,6 +404,8 @@ namespace Barotrauma.Items.Components
|
||||
return false;
|
||||
}
|
||||
|
||||
if (IsOutOfPower()) { return false; }
|
||||
|
||||
if (IsToggle && (activator == null || lastUsed < Timing.TotalTime - 0.1))
|
||||
{
|
||||
if (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer)
|
||||
@@ -380,6 +442,8 @@ namespace Barotrauma.Items.Components
|
||||
return false;
|
||||
}
|
||||
|
||||
if (IsOutOfPower()) { return false; }
|
||||
|
||||
focusTarget = GetFocusTarget();
|
||||
|
||||
if (focusTarget == null)
|
||||
@@ -417,11 +481,20 @@ namespace Barotrauma.Items.Components
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool IsOutOfPower()
|
||||
{
|
||||
if (!RequirePower) { return false; }
|
||||
var powered = item.GetComponent<Powered>();
|
||||
return powered == null || powered.Voltage < powered.MinVoltage;
|
||||
}
|
||||
|
||||
public Item GetFocusTarget()
|
||||
{
|
||||
var positionOut = item.Connections?.Find(c => c.Name == "position_out");
|
||||
if (positionOut == null) { return null; }
|
||||
|
||||
if (IsOutOfPower()) { return null; }
|
||||
|
||||
item.SendSignal(new Signal(MathHelper.ToDegrees(targetRotation).ToString("G", CultureInfo.InvariantCulture), sender: user), positionOut);
|
||||
|
||||
for (int i = item.LastSentSignalRecipients.Count - 1; i >= 0; i--)
|
||||
@@ -447,6 +520,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public override bool Pick(Character picker)
|
||||
{
|
||||
if (IsOutOfPower()) { return false; }
|
||||
#if CLIENT
|
||||
if (Screen.Selected == GameMain.SubEditorScreen) { return false; }
|
||||
#endif
|
||||
@@ -539,7 +613,16 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
user = activator;
|
||||
IsActive = true;
|
||||
if (ForceUserToStayAttached && item.Container != null)
|
||||
{
|
||||
forceSelectNextFrame = true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
//allow the selection logic above to run when out of power, but allow sending signals
|
||||
if (IsOutOfPower()) { return false; }
|
||||
|
||||
#if SERVER
|
||||
item.CreateServerEvent(this);
|
||||
#endif
|
||||
@@ -550,6 +633,14 @@ namespace Barotrauma.Items.Components
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// "Attached user" sticks to this item. Can be used for things such as clown crates and boarding pods.
|
||||
/// </summary>
|
||||
public bool IsAttachedUser(Character character)
|
||||
{
|
||||
return character != null && character == user && ForceUserToStayAttached;
|
||||
}
|
||||
|
||||
public override void FlipX(bool relativeToSub)
|
||||
{
|
||||
if (dir != Direction.None)
|
||||
|
||||
+22
-17
@@ -14,8 +14,6 @@ namespace Barotrauma.Items.Components
|
||||
private float progressTimer;
|
||||
private float progressState;
|
||||
|
||||
private bool hasPower;
|
||||
|
||||
private Character user;
|
||||
|
||||
private float userDeconstructorSpeedMultiplier = 1.0f;
|
||||
@@ -88,9 +86,8 @@ namespace Barotrauma.Items.Components
|
||||
SetActive(false);
|
||||
return;
|
||||
}
|
||||
|
||||
hasPower = Voltage >= MinVoltage;
|
||||
if (!hasPower) { return; }
|
||||
|
||||
if (!HasPower) { return; }
|
||||
|
||||
var repairable = item.GetComponent<Repairable>();
|
||||
if (repairable != null)
|
||||
@@ -243,16 +240,16 @@ namespace Barotrauma.Items.Components
|
||||
if (targetItem == otherItem) { continue; }
|
||||
if (deconstructProduct.RequiredOtherItem.Any(r => otherItem.HasTag(r) || r == otherItem.Prefab.Identifier))
|
||||
{
|
||||
var geneticMaterial1 = targetItem.GetComponent<GeneticMaterial>();
|
||||
var geneticMaterial2 = otherItem.GetComponent<GeneticMaterial>();
|
||||
if (geneticMaterial1 != null && geneticMaterial2 != null)
|
||||
var targetGeneticMaterial = targetItem.GetComponent<GeneticMaterial>();
|
||||
var otherGeneticMaterial = otherItem.GetComponent<GeneticMaterial>();
|
||||
if (targetGeneticMaterial != null && otherGeneticMaterial != null)
|
||||
{
|
||||
var result = geneticMaterial1.Combine(geneticMaterial2, user);
|
||||
var result = targetGeneticMaterial.Combine(otherGeneticMaterial, user, out Item itemToDestroy);
|
||||
if (result == GeneticMaterial.CombineResult.Refined)
|
||||
{
|
||||
inputContainer.Inventory.RemoveItem(otherItem);
|
||||
OutputContainer.Inventory.RemoveItem(otherItem);
|
||||
Entity.Spawner.AddItemToRemoveQueue(otherItem);
|
||||
Entity.Spawner.AddItemToRemoveQueue(itemToDestroy);
|
||||
}
|
||||
if (result != GeneticMaterial.CombineResult.None)
|
||||
{
|
||||
@@ -425,31 +422,39 @@ namespace Barotrauma.Items.Components
|
||||
foreach (Item inputItem in items)
|
||||
{
|
||||
if (!inputItem.AllowDeconstruct) { continue; }
|
||||
|
||||
foreach (var deconstructItem in inputItem.Prefab.DeconstructItems)
|
||||
{
|
||||
// check for deconstructor compatibility (for example, 'geneticresearchstation' tag)
|
||||
if (deconstructItem.RequiredDeconstructor.Length > 0)
|
||||
{
|
||||
if (!deconstructItem.RequiredDeconstructor.Any(r => item.HasTag(r) || item.Prefab.Identifier == r)) { continue; }
|
||||
if (deconstructItem.RequiredDeconstructor.None(requiredId => item.HasTag(requiredId) || item.Prefab.Identifier == requiredId)) { continue; }
|
||||
}
|
||||
|
||||
// check for other required items in the same deconstructor (for example, 'geneticmaterial')
|
||||
if (deconstructItem.RequiredOtherItem.Length > 0 && checkRequiredOtherItems)
|
||||
{
|
||||
if (!deconstructItem.RequiredOtherItem.Any(r => items.Any(it => it.HasTag(r) || it.Prefab.Identifier == r))) { continue; }
|
||||
// no matching item with the required id, skip
|
||||
if (deconstructItem.RequiredOtherItem.None(requiredId => items.Any(it => it.HasTag(requiredId) || it.Prefab.Identifier == requiredId))) { continue; }
|
||||
|
||||
bool validOtherItemFound = false;
|
||||
foreach (Item otherInputItem in items)
|
||||
{
|
||||
if (otherInputItem == inputItem) { continue; }
|
||||
if (!deconstructItem.RequiredOtherItem.Any(r => otherInputItem.HasTag(r) || otherInputItem.Prefab.Identifier == r)) { continue; }
|
||||
if (deconstructItem.RequiredOtherItem.None(requiredId => otherInputItem.HasTag(requiredId) || otherInputItem.Prefab.Identifier == requiredId)) { continue; }
|
||||
|
||||
var geneticMaterial1 = inputItem.GetComponent<GeneticMaterial>();
|
||||
var geneticMaterial2 = otherInputItem.GetComponent<GeneticMaterial>();
|
||||
if (geneticMaterial1 != null && geneticMaterial2 != null)
|
||||
// skip if genetic materials cannot be combined (or refined)
|
||||
var geneticMaterial = inputItem.GetComponent<GeneticMaterial>();
|
||||
var otherGeneticMaterial = otherInputItem.GetComponent<GeneticMaterial>();
|
||||
if (geneticMaterial != null && otherGeneticMaterial != null)
|
||||
{
|
||||
if (!geneticMaterial1.CanBeCombinedWith(geneticMaterial2)) { continue; }
|
||||
if (!geneticMaterial.CanBeCombinedWith(otherGeneticMaterial)) { continue; }
|
||||
}
|
||||
validOtherItemFound = true;
|
||||
}
|
||||
if (!validOtherItemFound) { continue; }
|
||||
}
|
||||
|
||||
yield return (inputItem, deconstructItem);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,7 +126,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
else
|
||||
{
|
||||
hasPower = Voltage > MinVoltage;
|
||||
hasPower = HasPower;
|
||||
}
|
||||
|
||||
if (lastReceivedTargetForce.HasValue)
|
||||
@@ -146,7 +146,7 @@ namespace Barotrauma.Items.Components
|
||||
float forceMultiplier = 0.1f;
|
||||
if (User != null)
|
||||
{
|
||||
forceMultiplier *= MathHelper.Lerp(0.5f, 2.0f, (float)Math.Sqrt(User.GetSkillLevel("helm") / 100));
|
||||
forceMultiplier *= MathHelper.Lerp(0.5f, 2.0f, (float)Math.Sqrt(User.GetSkillLevel(Tags.HelmSkill) / 100));
|
||||
}
|
||||
currForce *= item.StatManager.GetAdjustedValueMultiplicative(ItemTalentStats.EngineMaxSpeed, MaxForce) * forceMultiplier;
|
||||
if (item.GetComponent<Repairable>() is { IsTinkering: true } repairable)
|
||||
|
||||
@@ -206,7 +206,7 @@ namespace Barotrauma.Items.Components
|
||||
private void StartFabricating(FabricationRecipe selectedItem, Character user, bool addToServerLog = true)
|
||||
{
|
||||
if (selectedItem == null) { return; }
|
||||
if (!outputContainer.Inventory.CanBePut(selectedItem.TargetItem, selectedItem.OutCondition * selectedItem.TargetItem.Health)) { return; }
|
||||
if (!outputContainer.Inventory.CanProbablyBePut(selectedItem.TargetItem, selectedItem.OutCondition * selectedItem.TargetItem.Health)) { return; }
|
||||
|
||||
IsActive = true;
|
||||
this.user = user;
|
||||
@@ -319,7 +319,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
else
|
||||
{
|
||||
hasPower = Voltage >= MinVoltage;
|
||||
hasPower = HasPower;
|
||||
|
||||
if (!hasPower)
|
||||
{
|
||||
@@ -499,12 +499,13 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
GameAnalyticsManager.AddDesignEvent("ItemFabricated:" + (GameMain.GameSession?.GameMode?.Preset.Identifier.Value ?? "none") + ":" + fabricatedItem.TargetItem.Identifier);
|
||||
}
|
||||
InvSlotType invSlot = fabricatedItem.MoveToSlot;
|
||||
if (i < amountFittingContainer)
|
||||
{
|
||||
Entity.Spawner.AddItemToSpawnQueue(fabricatedItem.TargetItem, outputContainer.Inventory, fabricatedItem.TargetItem.Health * outCondition, quality,
|
||||
onSpawned: (Item spawnedItem) =>
|
||||
{
|
||||
onItemSpawned(spawnedItem, tempUser);
|
||||
onItemSpawned(spawnedItem, tempUser, invSlot);
|
||||
spawnedItem.Quality = quality;
|
||||
spawnedItem.StolenDuringRound = ingredientsStolen;
|
||||
spawnedItem.AllowStealing = ingredientsAllowStealing;
|
||||
@@ -517,7 +518,7 @@ namespace Barotrauma.Items.Components
|
||||
Entity.Spawner.AddItemToSpawnQueue(fabricatedItem.TargetItem, item.Position, item.Submarine, fabricatedItem.TargetItem.Health * outCondition, quality,
|
||||
onSpawned: (Item spawnedItem) =>
|
||||
{
|
||||
onItemSpawned(spawnedItem, tempUser);
|
||||
onItemSpawned(spawnedItem, tempUser, invSlot);
|
||||
spawnedItem.Quality = quality;
|
||||
spawnedItem.StolenDuringRound = ingredientsStolen;
|
||||
spawnedItem.AllowStealing = ingredientsAllowStealing;
|
||||
@@ -527,15 +528,28 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
void onItemSpawned(Item spawnedItem, Character user)
|
||||
void onItemSpawned(Item spawnedItem, Character user, InvSlotType slot)
|
||||
{
|
||||
if (user != null && user.TeamID != CharacterTeamType.None)
|
||||
CharacterTeamType teamID = CharacterTeamType.None;
|
||||
if (user != null)
|
||||
{
|
||||
teamID = user.TeamID;
|
||||
}
|
||||
else if (item.Submarine != null)
|
||||
{
|
||||
teamID = item.Submarine.TeamID;
|
||||
}
|
||||
if (teamID != CharacterTeamType.None)
|
||||
{
|
||||
foreach (WifiComponent wifiComponent in spawnedItem.GetComponents<WifiComponent>())
|
||||
{
|
||||
wifiComponent.TeamID = user.TeamID;
|
||||
wifiComponent.TeamID = teamID;
|
||||
}
|
||||
}
|
||||
if (slot != InvSlotType.None)
|
||||
{
|
||||
user?.Inventory.TryPutItem(spawnedItem, user, slot.ToEnumerable());
|
||||
}
|
||||
OnItemFabricated?.Invoke(spawnedItem, user);
|
||||
}
|
||||
if (user?.Info != null && !user.Removed)
|
||||
@@ -562,7 +576,6 @@ namespace Barotrauma.Items.Components
|
||||
StartFabricating(prevFabricatedItem, prevUser, addToServerLog: false);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -720,17 +733,24 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
private readonly HashSet<Item> usedIngredients = new HashSet<Item>();
|
||||
|
||||
private bool CanBeFabricated(FabricationRecipe fabricableItem, IReadOnlyDictionary<Identifier, List<Item>> availableIngredients, Character character)
|
||||
public bool MissingRequiredRecipe(FabricationRecipe fabricableItem, Character character)
|
||||
{
|
||||
if (fabricableItem == null) { return false; }
|
||||
if (fabricableItem.RequiresRecipe)
|
||||
if (fabricableItem.RequiresRecipe)
|
||||
{
|
||||
if (character == null) { return false; }
|
||||
if (!AnyOneHasRecipeForItem(character, fabricableItem.TargetItem))
|
||||
{
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool CanBeFabricated(FabricationRecipe fabricableItem, IReadOnlyDictionary<Identifier, List<Item>> availableIngredients, Character character)
|
||||
{
|
||||
if (fabricableItem == null) { return false; }
|
||||
|
||||
if (MissingRequiredRecipe(fabricableItem, character)) { return false; }
|
||||
|
||||
if (fabricableItem.HideForNonTraitors)
|
||||
{
|
||||
|
||||
@@ -80,7 +80,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
hasPower = Voltage > MinVoltage;
|
||||
hasPower = HasPower;
|
||||
if (hasPower)
|
||||
{
|
||||
ApplyStatusEffects(ActionType.OnActive, deltaTime);
|
||||
|
||||
+1
-1
@@ -46,7 +46,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
if (item.CurrentHull == null) { return; }
|
||||
|
||||
if (Voltage < MinVoltage && PowerConsumption > 0)
|
||||
if (!HasPower && PowerConsumption > 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -78,7 +78,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasPower => IsActive && Voltage >= MinVoltage;
|
||||
public override bool HasPower => IsActive && Voltage >= MinVoltage;
|
||||
public bool IsAutoControlled => pumpSpeedLockTimer > 0.0f || isActiveLockTimer > 0.0f;
|
||||
|
||||
private const float TinkeringSpeedIncrease = 4.0f;
|
||||
@@ -140,13 +140,11 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
float powerFactor = Math.Min(currPowerConsumption <= 0.0f || MinVoltage <= 0.0f ? 1.0f : Voltage, MaxOverVoltageFactor);
|
||||
|
||||
currFlow = flowPercentage / 100.0f * item.StatManager.GetAdjustedValueMultiplicative(ItemTalentStats.PumpMaxFlow, MaxFlow) * powerFactor;
|
||||
|
||||
currFlow = flowPercentage / 100.0f * MaxFlow * powerFactor;
|
||||
if (item.GetComponent<Repairable>() is { IsTinkering: true } repairable)
|
||||
{
|
||||
currFlow *= 1f + repairable.TinkeringStrength * TinkeringSpeedIncrease;
|
||||
}
|
||||
|
||||
currFlow = item.StatManager.GetAdjustedValueMultiplicative(ItemTalentStats.PumpSpeed, currFlow);
|
||||
|
||||
//less effective when in a bad condition
|
||||
|
||||
@@ -80,6 +80,7 @@ namespace Barotrauma.Items.Components
|
||||
private set
|
||||
{
|
||||
if (lastUser == value) { return; }
|
||||
if (Screen.Selected.IsEditor) { return; }
|
||||
lastUser = value;
|
||||
if (lastUser == null)
|
||||
{
|
||||
@@ -246,6 +247,13 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
//rapidly adjust the reactor in the first few seconds of the round to prevent overvoltages if the load changed between rounds
|
||||
//(unless the reactor is being operated by a player)
|
||||
if (GameMain.GameSession is { RoundDuration: <5 } && lastUser is not { IsPlayer: true })
|
||||
{
|
||||
UpdateAutoTemp(100.0f, (float)(Timing.Step * 10.0f));
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
if (PowerOn && AvailableFuel < 1)
|
||||
{
|
||||
@@ -340,7 +348,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
foreach (Item item in containedItems)
|
||||
{
|
||||
if (!item.HasTag(Tags.Fuel)) { continue; }
|
||||
if (!item.HasTag(Tags.ReactorFuel)) { continue; }
|
||||
if (fissionRate > 0.0f)
|
||||
{
|
||||
bool isConnectedToFriendlyOutpost = Level.IsLoadedOutpost &&
|
||||
@@ -707,13 +715,13 @@ namespace Barotrauma.Items.Components
|
||||
var containObjective = AIContainItems<Reactor>(container, character, objective, itemCount: 1, equip: true, removeEmpty: true, spawnItemIfNotFound: !character.IsOnPlayerTeam, dropItemOnDeselected: true);
|
||||
containObjective.Completed += ReportFuelRodCount;
|
||||
containObjective.Abandoned += ReportFuelRodCount;
|
||||
character.Speak(TextManager.Get("DialogReactorFuel").Value, null, 0.0f, Tags.Fuel, 30.0f);
|
||||
character.Speak(TextManager.Get("DialogReactorFuel").Value, null, 0.0f, Tags.ReactorFuel, 30.0f);
|
||||
|
||||
void ReportFuelRodCount()
|
||||
{
|
||||
if (!character.IsOnPlayerTeam) { return; }
|
||||
if (character.Submarine != Submarine.MainSub) { return; }
|
||||
int remainingFuelRods = Submarine.MainSub.GetItems(false).Count(i => i.HasTag(Tags.Fuel) && i.Condition > 1);
|
||||
int remainingFuelRods = Submarine.MainSub.GetItems(false).Count(i => i.HasTag(Tags.ReactorFuel) && i.Condition > 1);
|
||||
if (remainingFuelRods == 0)
|
||||
{
|
||||
character.Speak(TextManager.Get("DialogOutOfFuelRods").Value, null, 0.0f, "outoffuelrods".ToIdentifier(), 30.0f);
|
||||
|
||||
@@ -8,6 +8,8 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
partial class Sonar : Powered, IServerSerializable, IClientSerializable
|
||||
{
|
||||
public static List<Sonar> SonarList = new List<Sonar>();
|
||||
|
||||
public enum Mode
|
||||
{
|
||||
Active,
|
||||
@@ -167,6 +169,7 @@ namespace Barotrauma.Items.Components
|
||||
IsActive = true;
|
||||
InitProjSpecific(element);
|
||||
CurrentMode = Mode.Passive;
|
||||
SonarList.Add(this);
|
||||
}
|
||||
|
||||
partial void InitProjSpecific(ContentXElement element);
|
||||
@@ -191,8 +194,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
if (currentMode == Mode.Active)
|
||||
{
|
||||
if ((Voltage >= MinVoltage) &&
|
||||
(!UseTransducers || connectedTransducers.Count > 0))
|
||||
if (HasPower && (!UseTransducers || connectedTransducers.Count > 0))
|
||||
{
|
||||
if (currentPingIndex != -1)
|
||||
{
|
||||
@@ -380,6 +382,29 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
protected override void RemoveComponentSpecific()
|
||||
{
|
||||
base.RemoveComponentSpecific();
|
||||
#if CLIENT
|
||||
sonarBlip?.Remove();
|
||||
pingCircle?.Remove();
|
||||
directionalPingCircle?.Remove();
|
||||
screenOverlay?.Remove();
|
||||
screenBackground?.Remove();
|
||||
lineSprite?.Remove();
|
||||
|
||||
foreach (var t in targetIcons.Values)
|
||||
{
|
||||
t.Item1.Remove();
|
||||
}
|
||||
targetIcons.Clear();
|
||||
|
||||
MineralClusters = null;
|
||||
#endif
|
||||
SonarList.Remove(this);
|
||||
}
|
||||
|
||||
|
||||
public void ServerEventRead(IReadMessage msg, Client c)
|
||||
{
|
||||
bool isActive = msg.ReadBoolean();
|
||||
|
||||
+1
-1
@@ -19,7 +19,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
UpdateOnActiveEffects(deltaTime);
|
||||
|
||||
if (Voltage >= MinVoltage)
|
||||
if (HasPower)
|
||||
{
|
||||
sendSignalTimer += deltaTime;
|
||||
if (sendSignalTimer > SendSignalInterval)
|
||||
|
||||
@@ -61,6 +61,7 @@ namespace Barotrauma.Items.Components
|
||||
private Sonar sonar;
|
||||
|
||||
private Submarine controlledSub;
|
||||
public Submarine ControlledSub => controlledSub;
|
||||
|
||||
// AI interfacing
|
||||
public Vector2 AITacticalTarget { get; set; }
|
||||
@@ -75,6 +76,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
private double lastReceivedSteeringSignalTime;
|
||||
|
||||
[Serialize(defaultValue: false, isSaveable: IsPropertySaveable.Yes, AlwaysUseInstanceValues = true)]
|
||||
public bool AutoPilot
|
||||
{
|
||||
get { return autoPilot; }
|
||||
@@ -298,7 +300,7 @@ namespace Barotrauma.Items.Components
|
||||
controlledSub = sonar.ConnectedTransducers.Any() ? sonar.ConnectedTransducers.First().Item.Submarine : null;
|
||||
}
|
||||
|
||||
if (Voltage < MinVoltage) { return; }
|
||||
if (!HasPower) { return; }
|
||||
|
||||
if (user != null && user.Removed)
|
||||
{
|
||||
@@ -311,7 +313,7 @@ namespace Barotrauma.Items.Components
|
||||
if (user != null && controlledSub != null &&
|
||||
(user.SelectedItem == item || item.linkedTo.Contains(user.SelectedItem)))
|
||||
{
|
||||
userSkill = user.GetSkillLevel("helm") / 100.0f;
|
||||
userSkill = user.GetSkillLevel(Tags.HelmSkill) / 100.0f;
|
||||
}
|
||||
|
||||
// override autopilot pathing while the AI rams, and go full speed ahead
|
||||
|
||||
@@ -131,7 +131,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
SuitablePlantItem plantItem = GetSuitableItem(character);
|
||||
|
||||
if (!plantItem.IsNull())
|
||||
if (!plantItem.IsNull() && item.GetComponent<Holdable>() is not { Attachable: true, Attached: false })
|
||||
{
|
||||
Msg = plantItem.Type switch
|
||||
{
|
||||
@@ -159,7 +159,6 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
SuitablePlantItem plantItem = GetSuitableItem(character);
|
||||
PickingMsg = plantItem.IsNull() ? MsgUprooting : plantItem.ProgressBarMessage;
|
||||
|
||||
return base.Pick(character);
|
||||
}
|
||||
|
||||
|
||||
@@ -87,7 +87,7 @@ namespace Barotrauma.Items.Components
|
||||
if (Math.Abs(charge - lastSentCharge) / adjustedCapacity > 0.05f)
|
||||
{
|
||||
#if SERVER
|
||||
if (GameMain.Server != null && (!item.Submarine?.Loading ?? true)) { item.CreateServerEvent(this); }
|
||||
if (GameMain.Server != null && item.FullyInitialized) { item.CreateServerEvent(this); }
|
||||
#endif
|
||||
lastSentCharge = charge;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using Barotrauma.Extensions;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
@@ -231,7 +232,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
float maxOverVoltage = Math.Max(OverloadVoltage, 1.0f);
|
||||
|
||||
Overload = Voltage > maxOverVoltage;
|
||||
Overload = Voltage > maxOverVoltage && GameMain.GameSession is not { RoundDuration: < 5 };
|
||||
|
||||
if (Overload && (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer))
|
||||
{
|
||||
@@ -378,14 +379,30 @@ namespace Barotrauma.Items.Components
|
||||
foreach (Connection c in item.Connections)
|
||||
{
|
||||
connectionDirty[c] = true;
|
||||
if (c.IsPower)
|
||||
{
|
||||
ChangedConnections.Add(c);
|
||||
if (connectedRecipients.TryGetValue(c, out var recipients))
|
||||
{
|
||||
recipients.Where(c => c.IsPower).ForEach(c => ChangedConnections.Add(c));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void SetConnectionDirty(Connection connection)
|
||||
{
|
||||
var connections = item.Connections;
|
||||
if (connections == null || !connections.Contains(connection)) return;
|
||||
if (connections == null || !connections.Contains(connection)) { return; }
|
||||
connectionDirty[connection] = true;
|
||||
if (connection.IsPower)
|
||||
{
|
||||
ChangedConnections.Add(connection);
|
||||
if (connectedRecipients.TryGetValue(connection, out var recipients))
|
||||
{
|
||||
recipients.Where(c => c.IsPower).ForEach(c => ChangedConnections.Add(c));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnItemLoaded()
|
||||
|
||||
@@ -157,6 +157,13 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (powerOut?.Grid != null) { return powerOut.Grid.Voltage; }
|
||||
}
|
||||
|
||||
if (this is PowerTransfer && item.Condition <= 0.0f)
|
||||
{
|
||||
//if the junction box or other power transfer device is broken,
|
||||
//it cannot be supplying any power (voltage = 0)
|
||||
return 0.0f;
|
||||
}
|
||||
return PowerConsumption <= 0.0f ? 1.0f : voltage;
|
||||
}
|
||||
set
|
||||
@@ -170,6 +177,8 @@ namespace Barotrauma.Items.Components
|
||||
/// Can be used by status effects or sounds to check if the item has enough power to run
|
||||
/// </summary>
|
||||
public float RelativeVoltage => minVoltage <= 0.0f ? 1.0f : MathHelper.Clamp(Voltage / minVoltage, 0.0f, 1.0f);
|
||||
|
||||
public virtual bool HasPower => Voltage >= MinVoltage;
|
||||
|
||||
public bool PoweredByTinkering { get; set; }
|
||||
|
||||
@@ -208,7 +217,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (!powerOnSoundPlayed && powerOnSound != null)
|
||||
{
|
||||
SoundPlayer.PlaySound(powerOnSound.Sound, item.WorldPosition, powerOnSound.Volume, powerOnSound.Range, hullGuess: item.CurrentHull, ignoreMuffling: powerOnSound.IgnoreMuffling, freqMult: powerOnSound.GetRandomFrequencyMultiplier());
|
||||
SoundPlayer.PlaySound(powerOnSound, item.WorldPosition, hullGuess: item.CurrentHull);
|
||||
powerOnSoundPlayed = true;
|
||||
}
|
||||
}
|
||||
@@ -668,6 +677,8 @@ namespace Barotrauma.Items.Components
|
||||
return
|
||||
conn1.IsPower && conn2.IsPower &&
|
||||
conn1.Item.Condition > 0.0f && conn2.Item.Condition > 0.0f &&
|
||||
conn1.Item.GetComponent<PowerTransfer>() is not { CanTransfer: false } &&
|
||||
conn2.Item.GetComponent<PowerTransfer>() is not { CanTransfer: false } &&
|
||||
(conn1.Item.HasTag(Tags.JunctionBox) || conn2.Item.HasTag(Tags.JunctionBox) || conn1.Item.HasTag(Tags.DockingPort) || conn2.Item.HasTag(Tags.DockingPort) || conn1.IsOutput != conn2.IsOutput);
|
||||
}
|
||||
|
||||
|
||||
@@ -28,20 +28,24 @@ namespace Barotrauma.Items.Components
|
||||
SpreadCounter = 0;
|
||||
}
|
||||
|
||||
struct HitscanResult
|
||||
readonly struct HitscanResult
|
||||
{
|
||||
public Fixture Fixture;
|
||||
public Vector2 Point;
|
||||
public Vector2 Normal;
|
||||
public float Fraction;
|
||||
public HitscanResult(Fixture fixture, Vector2 point, Vector2 normal, float fraction)
|
||||
public readonly Fixture Fixture;
|
||||
public readonly Vector2 Point;
|
||||
public readonly Vector2 Normal;
|
||||
public readonly float Fraction;
|
||||
public readonly Submarine Submarine;
|
||||
|
||||
public HitscanResult(Fixture fixture, Vector2 point, Vector2 normal, float fraction, Submarine sub)
|
||||
{
|
||||
Fixture = fixture;
|
||||
Point = point;
|
||||
Normal = normal;
|
||||
Fraction = fraction;
|
||||
Submarine = sub;
|
||||
}
|
||||
}
|
||||
|
||||
struct Impact
|
||||
{
|
||||
public Fixture Fixture;
|
||||
@@ -184,6 +188,20 @@ namespace Barotrauma.Items.Components
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(false, IsPropertySaveable.No, description: "")]
|
||||
public bool GoThroughLightTargets
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(-1f, IsPropertySaveable.No, description: $"Minimum mass of targets to stick to when {nameof(StickToLightTargets)} is disabled. Defaults to half of the projectile's mass.")]
|
||||
public float LightTargetMassThreshold
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(false, IsPropertySaveable.No, description: "Hitscan projectiles cast a ray forwards and immediately hit whatever the ray hits. "+
|
||||
"It is recommended to use hitscans for very fast-moving projectiles such as bullets, because using extremely fast launch velocities may cause physics glitches.")]
|
||||
public bool Hitscan
|
||||
@@ -261,6 +279,13 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
private float maxJointTranslationInSimUnits = -1;
|
||||
|
||||
[Serialize(1000.0f, IsPropertySaveable.No)]
|
||||
public float JointBreakPoint
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(true, IsPropertySaveable.No)]
|
||||
public bool Prismatic
|
||||
{
|
||||
@@ -310,7 +335,7 @@ namespace Barotrauma.Items.Components
|
||||
if (!subElement.Name.ToString().Equals("attack", StringComparison.OrdinalIgnoreCase)) { continue; }
|
||||
Attack = new Attack(subElement, item.Name + ", Projectile", item);
|
||||
}
|
||||
|
||||
|
||||
if (item.body == null)
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in projectile definition ({item.Name}): No body defined!",
|
||||
@@ -372,8 +397,6 @@ namespace Barotrauma.Items.Components
|
||||
if (Item.Removed) { return; }
|
||||
launchPos = simPosition;
|
||||
LaunchSub = item.Submarine;
|
||||
//set the rotation of the projectile again because dropping the projectile resets the rotation
|
||||
Item.SetTransform(simPosition, rotation + (Item.body.Dir * LaunchRotationRadians), findNewHull: false);
|
||||
if (DeactivationTime > 0)
|
||||
{
|
||||
deactivationTimer = DeactivationTime;
|
||||
@@ -419,6 +442,14 @@ namespace Barotrauma.Items.Components
|
||||
//can't launch if already launched
|
||||
if (StickTarget != null || IsActive) { return false; }
|
||||
|
||||
#if SERVER
|
||||
var owner = GameMain.Server.ConnectedClients.FirstOrDefault(c => c.Character == User);
|
||||
if (owner != null)
|
||||
{
|
||||
Limb.SetLagCompensatedBodyPositions(owner);
|
||||
}
|
||||
#endif
|
||||
|
||||
float initialRotation = item.body.Rotation;
|
||||
//if the item is being launched from an inventory, assume it's being fired by a gun that handles setting the rotation correctly
|
||||
//but if the item is e.g. being thrown by a character, we need to take the direction into account
|
||||
@@ -440,10 +471,10 @@ namespace Barotrauma.Items.Components
|
||||
spreadIndex++;
|
||||
|
||||
Vector2 launchDir = new Vector2((float)Math.Cos(launchAngle), (float)Math.Sin(launchAngle));
|
||||
Vector2 prevSimpos = item.SimPosition;
|
||||
item.body.SetTransformIgnoreContacts(item.body.SimPosition, launchAngle);
|
||||
if (Hitscan)
|
||||
{
|
||||
Vector2 prevSimpos = item.SimPosition;
|
||||
item.body.SetTransformIgnoreContacts(item.body.SimPosition, launchAngle);
|
||||
DoHitscan(launchDir);
|
||||
if (i < HitScanCount - 1)
|
||||
{
|
||||
@@ -452,9 +483,15 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
else
|
||||
{
|
||||
item.body.SetTransform(item.body.SimPosition, launchAngle);
|
||||
float modifiedLaunchImpulse = (LaunchImpulse + launchImpulseModifier) * (1 + Rand.Range(-ImpulseSpread, ImpulseSpread));
|
||||
DoLaunch(launchDir * modifiedLaunchImpulse);
|
||||
//needs to be set after DoLaunch, because dropping the item resets the rotation and dir
|
||||
float afterLaunchAngle = launchAngle + (item.body.Dir * LaunchRotationRadians);
|
||||
if (item.body.Dir < 0)
|
||||
{
|
||||
afterLaunchAngle -= MathHelper.Pi;
|
||||
}
|
||||
item.SetTransform(item.body.SimPosition, afterLaunchAngle, findNewHull: false);
|
||||
}
|
||||
}
|
||||
User = character;
|
||||
@@ -579,7 +616,8 @@ namespace Barotrauma.Items.Components
|
||||
inSubHits[i].Fixture,
|
||||
inSubHits[i].Point + submarine.SimPosition,
|
||||
inSubHits[i].Normal,
|
||||
inSubHits[i].Fraction);
|
||||
inSubHits[i].Fraction,
|
||||
sub: null);
|
||||
}
|
||||
hits.AddRange(inSubHits);
|
||||
}
|
||||
@@ -592,6 +630,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
var h = hits[i];
|
||||
item.SetTransform(h.Point, rotation);
|
||||
item.Submarine = h.Submarine;
|
||||
item.UpdateTransform();
|
||||
if (HandleProjectileCollision(h.Fixture, h.Normal, Vector2.Zero))
|
||||
{
|
||||
@@ -649,8 +688,6 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
if (fixture.Body.UserData is VineTile) { return true; }
|
||||
if (fixture.CollidesWith == Category.None) { return true; }
|
||||
//only collides with characters = probably an "outsideCollisionBlocker" created by a gap
|
||||
if (fixture.CollidesWith == Physics.CollisionCharacter) { return true; }
|
||||
|
||||
if (fixture.Body.UserData as string == "ruinroom" || fixture.Body.UserData is Hull || fixture.UserData is Hull) { return true; }
|
||||
|
||||
@@ -669,6 +706,11 @@ namespace Barotrauma.Items.Components
|
||||
if (item.Condition <= 0) { return true; }
|
||||
if (!item.Prefab.DamagedByProjectiles && item.GetComponent<Door>() == null) { return true; }
|
||||
}
|
||||
else if (fixture.Body.UserData is Gap)
|
||||
{
|
||||
//an "outsideCollisionBlocker" created by a gap, should never collide
|
||||
return true;
|
||||
}
|
||||
else if (fixture.Body.UserData is Holdable { CanPush: false })
|
||||
{
|
||||
// Ignore holdables that can't push -> shouldn't block
|
||||
@@ -686,7 +728,7 @@ namespace Barotrauma.Items.Components
|
||||
fixture.Body.GetTransform(out FarseerPhysics.Common.Transform transform);
|
||||
if (!fixture.Shape.TestPoint(ref transform, ref rayStart)) { return true; }
|
||||
|
||||
hits.Add(new HitscanResult(fixture, rayStart, -dir, 0.0f));
|
||||
hits.Add(new HitscanResult(fixture, rayStart, -dir, 0.0f, submarine));
|
||||
return true;
|
||||
}, ref aabb);
|
||||
|
||||
@@ -703,14 +745,17 @@ namespace Barotrauma.Items.Components
|
||||
return -1;
|
||||
}
|
||||
if (fixture.Body.UserData is VineTile) { return -1; }
|
||||
if (fixture.CollidesWith == Category.None) { return -1; }
|
||||
//only collides with characters = probably an "outsideCollisionBlocker" created by a gap
|
||||
if (fixture.CollidesWith == Physics.CollisionCharacter) { return -1; }
|
||||
if (fixture.CollidesWith == Category.None && fixture.CollisionCategories != Physics.CollisionLagCompensationBody) { return -1; }
|
||||
if (fixture.Body.UserData is Item item)
|
||||
{
|
||||
if (item.Condition <= 0) { return -1; }
|
||||
if (!item.Prefab.DamagedByProjectiles && item.GetComponent<Door>() == null) { return -1; }
|
||||
}
|
||||
else if (fixture.Body.UserData is Gap)
|
||||
{
|
||||
//an "outsideCollisionBlocker" created by a gap, should never collide
|
||||
return -1;
|
||||
}
|
||||
if (fixture.Body.UserData as string == "ruinroom" || fixture.Body?.UserData is Hull || fixture.UserData is Hull) { return -1; }
|
||||
|
||||
//if doing the raycast in a submarine's coordinate space, ignore anything that's not in that sub
|
||||
@@ -755,10 +800,10 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
hits.Add(new HitscanResult(fixture, point, normal, fraction));
|
||||
hits.Add(new HitscanResult(fixture, point, normal, fraction, submarine));
|
||||
|
||||
return 1;
|
||||
}, rayStart, rayEnd, Physics.CollisionCharacter | Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionItemBlocking | Physics.CollisionProjectile);
|
||||
}, rayStart, rayEnd, Physics.CollisionCharacter | Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionItemBlocking | Physics.CollisionProjectile | Physics.CollisionLagCompensationBody);
|
||||
|
||||
return hits;
|
||||
}
|
||||
@@ -830,7 +875,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
if (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer)
|
||||
{
|
||||
if (StickTargetRemoved() || stickJoint is PrismaticJoint pJoint && Math.Abs(pJoint.JointTranslation) > maxJointTranslationInSimUnits)
|
||||
if (StickTargetRemoved() || stickJoint is PrismaticJoint pJoint && Math.Abs(pJoint.JointTranslation) > maxJointTranslationInSimUnits || !stickJoint.Enabled)
|
||||
{
|
||||
Unstick();
|
||||
#if SERVER
|
||||
@@ -858,6 +903,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (GoThroughLightTargets && target.Body.Mass < GetLightTargetMassThreshold()) { return false; }
|
||||
if (target.IsSensor) { return false; }
|
||||
if (hits.Contains(target.Body)) { return false; }
|
||||
if (target.Body.UserData is Submarine)
|
||||
@@ -872,10 +918,7 @@ namespace Barotrauma.Items.Components
|
||||
limb.body?.ApplyLinearImpulse(item.body.LinearVelocity * item.body.Mass * 0.1f, item.SimPosition);
|
||||
return false;
|
||||
}
|
||||
if (!FriendlyFire && User != null && limb.character.IsFriendly(User))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (ShouldIgnoreCharacterCollision(limb.character)) { return false; }
|
||||
}
|
||||
else if (target.Body.UserData is Item item)
|
||||
{
|
||||
@@ -918,6 +961,20 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
private bool ShouldIgnoreCharacterCollision(Character character)
|
||||
{
|
||||
//don't hit characters "attached" to the projectile (e.g. inside a boarding pod)
|
||||
if (item.GetComponent<Controller>() is { } controller && controller.User == character && controller.IsAttachedUser(controller.User))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (!FriendlyFire && User != null && character.IsFriendly(User))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Should the collision with the target submarine be ignored (e.g. did the projectile collide with the wall behind the turret when being launched)
|
||||
/// </summary>
|
||||
@@ -961,7 +1018,8 @@ namespace Barotrauma.Items.Components
|
||||
var wallBody = Submarine.PickBody(
|
||||
item.body.SimPosition - ConvertUnits.ToSimUnits(sub.Position) - dir,
|
||||
item.body.SimPosition - ConvertUnits.ToSimUnits(sub.Position) + dir,
|
||||
collisionCategory: Physics.CollisionWall);
|
||||
collisionCategory: Physics.CollisionWall,
|
||||
customPredicate: (Fixture f) => IgnoredBodies == null || !IgnoredBodies.Contains(f.Body));
|
||||
|
||||
Vector2 launchPosInCurrentCoordinateSpace = launchPos;
|
||||
if (item.body.Submarine == null && LaunchSub != null)
|
||||
@@ -1015,10 +1073,6 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
else if (target.Body.UserData is Limb limb)
|
||||
{
|
||||
if (!FriendlyFire && User != null && limb.character.IsFriendly(User))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// when hitting limbs with piercing ammo, don't lose as much speed
|
||||
if (MaxTargetsToHit > 1)
|
||||
{
|
||||
@@ -1026,6 +1080,7 @@ namespace Barotrauma.Items.Components
|
||||
deflectedSpeedMultiplier = 0.8f;
|
||||
}
|
||||
if (limb.IsSevered || limb.character == null || limb.character.Removed) { return false; }
|
||||
if (ShouldIgnoreCharacterCollision(limb.character)) { return false; }
|
||||
|
||||
limb.character.LastDamageSource = item;
|
||||
if (Attack != null) { attackResult = Attack.DoDamageToLimb(User ?? Attacker, limb, item.WorldPosition, 1.0f); }
|
||||
@@ -1057,11 +1112,11 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (Attack != null)
|
||||
{
|
||||
Vector2 pos = item.WorldPosition;
|
||||
if (item.Submarine == null && damageable is Structure structure && structure.Submarine != null && Vector2.DistanceSquared(item.WorldPosition, structure.WorldPosition) > 10000.0f * 10000.0f)
|
||||
{
|
||||
item.Submarine = structure.Submarine;
|
||||
}
|
||||
Vector2 pos = item.WorldPosition;
|
||||
attackResult = Attack.DoDamage(User ?? Attacker, damageable, pos, 1.0f);
|
||||
}
|
||||
}
|
||||
@@ -1154,7 +1209,7 @@ namespace Barotrauma.Items.Components
|
||||
else if ( remainingHits <= 0 &&
|
||||
stickJoint == null && StickTarget == null &&
|
||||
StickToStructures && target.Body.UserData is Structure ||
|
||||
((StickToLightTargets || target.Body.Mass > item.body.Mass * 0.5f) &&
|
||||
((StickToLightTargets || target.Body.Mass >= GetLightTargetMassThreshold()) &&
|
||||
(DoesStick ||
|
||||
(StickToCharacters && (target.Body.UserData is Limb || target.Body.UserData is Character)) ||
|
||||
(target.Body.UserData is Item i && (i.GetComponent<Door>() != null ? StickToDoors : StickToItems)))))
|
||||
@@ -1218,6 +1273,11 @@ namespace Barotrauma.Items.Components
|
||||
return true;
|
||||
}
|
||||
|
||||
private float GetLightTargetMassThreshold()
|
||||
{
|
||||
return LightTargetMassThreshold < 0 ? item.body.Mass * 0.5f : LightTargetMassThreshold;
|
||||
}
|
||||
|
||||
private void EnableProjectileCollisions()
|
||||
{
|
||||
if (item.body.CollisionCategories != Category.None)
|
||||
@@ -1274,7 +1334,7 @@ namespace Barotrauma.Items.Components
|
||||
MotorEnabled = true,
|
||||
MaxMotorForce = 30.0f,
|
||||
LimitEnabled = true,
|
||||
Breakpoint = 1000.0f
|
||||
Breakpoint = JointBreakPoint,
|
||||
};
|
||||
|
||||
if (maxJointTranslationInSimUnits == -1)
|
||||
|
||||
@@ -752,7 +752,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
else if (ic is Powered powered && powered is not LightComponent)
|
||||
{
|
||||
if (powered.Voltage >= powered.MinVoltage) { return true; }
|
||||
if (powered.HasPower) { return true; }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ using FarseerPhysics;
|
||||
using FarseerPhysics.Dynamics;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
@@ -102,6 +103,20 @@ namespace Barotrauma.Items.Components
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(true, IsPropertySaveable.No, description: "Should the rope snap when the weapon it was fired from is fired again? I.e. can there be multiple ropes coming from the weapon at the same time?")]
|
||||
public bool SnapWhenWeaponFiredAgain
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(0.9f, IsPropertySaveable.No, description: "Multiplier for the length of the barrel when determining where the rope should start from.")]
|
||||
public float BarrelLengthMultiplier
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(30.0f, IsPropertySaveable.No, description: "How much mass is required for the target to pull the source towards it. Static and kinematic targets are always treated heavy enough.")]
|
||||
public float TargetMinMass
|
||||
{
|
||||
@@ -115,7 +130,14 @@ namespace Barotrauma.Items.Components
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
|
||||
[Serialize(true, IsPropertySaveable.No, description: "Should the force be dynamically adjusted to make it more difficult for targets to escape the pull?")]
|
||||
public bool IncreaseForceForEscapingTargets
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
private bool isReelingIn;
|
||||
private bool snapped;
|
||||
public bool Snapped
|
||||
@@ -314,7 +336,9 @@ namespace Barotrauma.Items.Components
|
||||
// Currently can only apply pull forces to the source, when it's a character, not e.g. when the item would be auto-operated by an AI. Might have to change this.
|
||||
if (user != null)
|
||||
{
|
||||
if (!snapped)
|
||||
if (!snapped &&
|
||||
//user can only hold on to the rope if it was launched from a holdable item, or by something else than an item (limb?)
|
||||
(projectile.Launcher == null || projectile.Launcher.GetComponent<Holdable>() != null))
|
||||
{
|
||||
user.AnimController.HoldToRope();
|
||||
if (targetCharacter != null)
|
||||
@@ -332,61 +356,70 @@ namespace Barotrauma.Items.Components
|
||||
var sourceBody = GetBodyToPull(source);
|
||||
if (sourceBody != null)
|
||||
{
|
||||
isReelingIn = user.InWater && user.IsRagdolled || !user.InWater && targetCharacter is { IsIncapacitated: false };
|
||||
if (isReelingIn)
|
||||
PhysicsBody targetBody = GetBodyToPull(target);
|
||||
if (sourceBody.UserData is Character)
|
||||
{
|
||||
float pullForce = SourcePullForce;
|
||||
if (!user.InWater)
|
||||
isReelingIn = user.InWater && user.IsRagdolled || !user.InWater && targetCharacter is { IsIncapacitated: false };
|
||||
if (isReelingIn)
|
||||
{
|
||||
// Apply a tiny amount to the character holding the rope, so that the connection "feels" more real.
|
||||
pullForce *= 0.1f;
|
||||
}
|
||||
float lengthFactor = MathUtils.InverseLerp(0, MaxLength / 2, currentRopeLength);
|
||||
float force = LerpForces ? MathHelper.Lerp(0, pullForce, lengthFactor) : pullForce;
|
||||
sourceBody.ApplyForce(forceDir * force);
|
||||
// Take the target velocity into account.
|
||||
PhysicsBody targetBody = GetBodyToPull(target);
|
||||
if (targetBody != null)
|
||||
{
|
||||
if (targetCharacter != null)
|
||||
float pullForce = SourcePullForce;
|
||||
if (!user.InWater)
|
||||
{
|
||||
if (targetBody.LinearVelocity != Vector2.Zero && sourceBody.LinearVelocity != Vector2.Zero)
|
||||
// Apply a tiny amount to the character holding the rope, so that the connection "feels" more real.
|
||||
pullForce *= 0.1f;
|
||||
}
|
||||
float lengthFactor = MathUtils.InverseLerp(0, MaxLength / 2, currentRopeLength);
|
||||
float force = LerpForces ? MathHelper.Lerp(0, pullForce, lengthFactor) : pullForce;
|
||||
sourceBody.ApplyForce(forceDir * force);
|
||||
// Take the target velocity into account.
|
||||
if (targetBody != null)
|
||||
{
|
||||
if (targetCharacter != null)
|
||||
{
|
||||
Vector2 targetDir = Vector2.Normalize(targetBody.LinearVelocity);
|
||||
float movementDot = Vector2.Dot(Vector2.Normalize(sourceBody.LinearVelocity), targetDir);
|
||||
if (movementDot < 0)
|
||||
if (targetBody.LinearVelocity != Vector2.Zero && sourceBody.LinearVelocity != Vector2.Zero)
|
||||
{
|
||||
// Pushing to a different dir -> add some counter force
|
||||
const float multiplier = 5;
|
||||
float inverseLengthFactor = MathHelper.Lerp(1, 0, lengthFactor);
|
||||
sourceBody.ApplyForce(targetBody.LinearVelocity * Math.Min(targetBody.Mass * multiplier, 250) * sourceBody.Mass * -movementDot * inverseLengthFactor);
|
||||
}
|
||||
float forceDot = Vector2.Dot(forceDir, targetDir);
|
||||
if (forceDot > 0)
|
||||
{
|
||||
// Pulling to the same dir -> add extra force
|
||||
float targetSpeed = targetBody.LinearVelocity.Length();
|
||||
const float multiplier = 25;
|
||||
sourceBody.ApplyForce(forceDir * targetSpeed * sourceBody.Mass * multiplier * forceDot * lengthFactor);
|
||||
}
|
||||
float colliderMainLimbDistance = Vector2.Distance(sourceBody.SimPosition, user.AnimController.MainLimb.SimPosition);
|
||||
const float minDist = 1;
|
||||
const float maxDist = 10;
|
||||
if (colliderMainLimbDistance > minDist)
|
||||
{
|
||||
// Move the ragdoll closer to the collider, if it's too far (the correction force in HumanAnimController is not enough -> the ragdoll would lag behind and get teleported).
|
||||
float correctionForce = MathHelper.Lerp(10.0f, NetConfig.MaxPhysicsBodyVelocity, MathUtils.InverseLerp(minDist, maxDist, colliderMainLimbDistance));
|
||||
Vector2 targetPos = sourceBody.SimPosition + new Vector2((float)Math.Sin(-sourceBody.Rotation), (float)Math.Cos(-sourceBody.Rotation)) * 0.4f;
|
||||
user.AnimController.MainLimb.MoveToPos(targetPos, correctionForce);
|
||||
Vector2 targetDir = Vector2.Normalize(targetBody.LinearVelocity);
|
||||
float movementDot = Vector2.Dot(Vector2.Normalize(sourceBody.LinearVelocity), targetDir);
|
||||
if (movementDot < 0)
|
||||
{
|
||||
// Pushing to a different dir -> add some counter force
|
||||
const float multiplier = 5;
|
||||
float inverseLengthFactor = MathHelper.Lerp(1, 0, lengthFactor);
|
||||
sourceBody.ApplyForce(targetBody.LinearVelocity * Math.Min(targetBody.Mass * multiplier, 250) * sourceBody.Mass * -movementDot * inverseLengthFactor);
|
||||
}
|
||||
float forceDot = Vector2.Dot(forceDir, targetDir);
|
||||
if (forceDot > 0)
|
||||
{
|
||||
// Pulling to the same dir -> add extra force
|
||||
float targetSpeed = targetBody.LinearVelocity.Length();
|
||||
const float multiplier = 25;
|
||||
sourceBody.ApplyForce(forceDir * targetSpeed * sourceBody.Mass * multiplier * forceDot * lengthFactor);
|
||||
}
|
||||
float colliderMainLimbDistance = Vector2.Distance(sourceBody.SimPosition, user.AnimController.MainLimb.SimPosition);
|
||||
const float minDist = 1;
|
||||
const float maxDist = 10;
|
||||
if (colliderMainLimbDistance > minDist && sourceBody.UserData is not Submarine)
|
||||
{
|
||||
// Move the ragdoll closer to the collider, if it's too far (the correction force in HumanAnimController is not enough -> the ragdoll would lag behind and get teleported).
|
||||
float correctionForce = MathHelper.Lerp(10.0f, NetConfig.MaxPhysicsBodyVelocity, MathUtils.InverseLerp(minDist, maxDist, colliderMainLimbDistance));
|
||||
Vector2 targetPos = sourceBody.SimPosition + new Vector2((float)Math.Sin(-sourceBody.Rotation), (float)Math.Cos(-sourceBody.Rotation)) * 0.4f;
|
||||
user.AnimController.MainLimb.MoveToPos(targetPos, correctionForce);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
sourceBody.ApplyForce(targetBody.LinearVelocity * sourceBody.Mass);
|
||||
else
|
||||
{
|
||||
sourceBody.ApplyForce(targetBody.LinearVelocity * sourceBody.Mass);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
float distance = Vector2.Distance(source.WorldPosition, target.WorldPosition);
|
||||
float force = LerpForces ? MathHelper.Lerp(0, SourcePullForce, MathUtils.InverseLerp(0, MaxLength / 2, distance)) : SourcePullForce;
|
||||
sourceBody.ApplyForce(forceDir * force);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -433,7 +466,7 @@ namespace Barotrauma.Items.Components
|
||||
if (targetRagdoll.InWater || targetRagdoll.OnGround)
|
||||
{
|
||||
float forceMultiplier = 1;
|
||||
if (!targetCharacter.IsRagdolled && !targetCharacter.IsIncapacitated)
|
||||
if (!targetCharacter.IsRagdolled && !targetCharacter.IsIncapacitated && IncreaseForceForEscapingTargets)
|
||||
{
|
||||
// Pulling the main collider requires higher forces when the target is trying to move away.
|
||||
Vector2 targetMovement = targetCharacter.AnimController.TargetMovement;
|
||||
@@ -523,6 +556,7 @@ namespace Barotrauma.Items.Components
|
||||
};
|
||||
}
|
||||
if (targetItem.body != null) { return targetItem.body; }
|
||||
if (targetItem.StaticFixtures.Any() && targetItem.Submarine != null) { return targetItem.Submarine.PhysicsBody; }
|
||||
}
|
||||
else if (target is Limb targetLimb)
|
||||
{
|
||||
|
||||
+111
-66
@@ -1,9 +1,9 @@
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Networking;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
@@ -11,24 +11,32 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
[Editable, Serialize(new string[0], IsPropertySaveable.Yes, description: "Signals sent when the corresponding buttons are pressed.", alwaysUseInstanceValues: true)]
|
||||
public string[] Signals { get; set; }
|
||||
|
||||
[Editable, Serialize("", IsPropertySaveable.Yes, description: "Identifiers or tags of items that, when contained, allow the terminal buttons to be used. Multiple ones should be separated by commas.", alwaysUseInstanceValues: true)]
|
||||
public string ActivatingItems { get; set; }
|
||||
|
||||
private int RequiredSignalCount { get; set; }
|
||||
|
||||
private readonly int requiredSignalCount;
|
||||
private ItemContainer Container { get; set; }
|
||||
private HashSet<ItemPrefab> ActivatingItemPrefabs { get; set; } = new HashSet<ItemPrefab>();
|
||||
|
||||
|
||||
private bool AllowUsingButtons => ActivatingItemPrefabs.None() || (Container != null && Container.Inventory.AllItems.Any(i => i != null && ActivatingItemPrefabs.Any(p => p == i.Prefab)));
|
||||
private bool IsActivated => ActivatingItemPrefabs.None() || (Container != null && Container.Inventory.AllItems.Any(i => i != null && ActivatingItemPrefabs.Any(p => p == i.Prefab)));
|
||||
|
||||
private readonly IReadOnlyList<string> buttonSignalDefinitions;
|
||||
|
||||
public ButtonTerminal(Item item, ContentXElement element) : base(item, element)
|
||||
{
|
||||
RequiredSignalCount = element.GetChildElements("TerminalButton").Count(c => c.GetAttribute("style") != null);
|
||||
if (RequiredSignalCount < 1)
|
||||
var buttons = element.GetChildElements("TerminalButton").Where(c => c.GetAttribute("style") != null);
|
||||
if (buttons.None())
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in item \"{item.Name}\": no TerminalButton elements defined for the ButtonTerminal component!",
|
||||
contentPackage: element.ContentPackage);
|
||||
DebugConsole.ThrowError($"Error in item \"{item.Name}\": no TerminalButton elements with a style defined for the ButtonTerminal component!", contentPackage: element.ContentPackage);
|
||||
}
|
||||
requiredSignalCount = buttons.Count();
|
||||
List<string> buttonSignals = new ();
|
||||
foreach (ContentXElement button in buttons)
|
||||
{
|
||||
buttonSignals.Add(button.GetAttributeString("signal", null));
|
||||
}
|
||||
buttonSignalDefinitions = buttonSignals.ToImmutableList();
|
||||
InitProjSpecific(element);
|
||||
}
|
||||
|
||||
@@ -37,57 +45,10 @@ namespace Barotrauma.Items.Components
|
||||
public override void OnItemLoaded()
|
||||
{
|
||||
base.OnItemLoaded();
|
||||
|
||||
if (Signals == null)
|
||||
{
|
||||
Signals = new string[RequiredSignalCount];
|
||||
for (int i = 0; i < RequiredSignalCount; i++)
|
||||
{
|
||||
Signals[i] = string.Empty;
|
||||
}
|
||||
}
|
||||
else if (Signals.Length != RequiredSignalCount)
|
||||
{
|
||||
string[] newSignals = new string[RequiredSignalCount];
|
||||
if (Signals.Length < RequiredSignalCount)
|
||||
{
|
||||
Signals.CopyTo(newSignals, 0);
|
||||
for (int i = Signals.Length; i < RequiredSignalCount; i++)
|
||||
{
|
||||
newSignals[i] = string.Empty;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < RequiredSignalCount; i++)
|
||||
{
|
||||
newSignals[i] = Signals[i];
|
||||
}
|
||||
}
|
||||
Signals = newSignals;
|
||||
}
|
||||
|
||||
ActivatingItemPrefabs.Clear();
|
||||
if (!string.IsNullOrEmpty(ActivatingItems))
|
||||
{
|
||||
foreach (var activatingItem in ActivatingItems.Split(','))
|
||||
{
|
||||
if (MapEntityPrefab.Find(null, identifier: activatingItem, showErrorMessages: false) is ItemPrefab prefab)
|
||||
{
|
||||
ActivatingItemPrefabs.Add(prefab);
|
||||
}
|
||||
else
|
||||
{
|
||||
ItemPrefab.Prefabs.Where(p => p.Tags.Any(t => t == activatingItem))
|
||||
.ForEach(p => ActivatingItemPrefabs.Add(p));
|
||||
}
|
||||
}
|
||||
if (ActivatingItemPrefabs.None())
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in item \"{item.Name}\": no activating item prefabs found with identifiers or tags \"{ActivatingItems}\"");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
LoadSignals();
|
||||
LoadActivatingItems();
|
||||
|
||||
var containers = item.GetComponents<ItemContainer>();
|
||||
if (containers.Count() != 1)
|
||||
{
|
||||
@@ -97,16 +58,100 @@ namespace Barotrauma.Items.Components
|
||||
Container = containers.FirstOrDefault();
|
||||
|
||||
OnItemLoadedProjSpecific();
|
||||
// Set active so that update loop is active and we can send the state_out signal.
|
||||
IsActive = true;
|
||||
}
|
||||
|
||||
partial void OnItemLoadedProjSpecific();
|
||||
|
||||
private bool SendSignal(int signalIndex, Character sender, bool isServerMessage = false)
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
if (!isServerMessage && !AllowUsingButtons) { return false; }
|
||||
string signal = Signals[signalIndex];
|
||||
base.Update(deltaTime, cam);
|
||||
item.SendSignal(IsActivated ? "1" : "0", "state_out");
|
||||
}
|
||||
|
||||
private void LoadSignals()
|
||||
{
|
||||
if (Signals == null || Signals.None())
|
||||
{
|
||||
Signals = new string[requiredSignalCount];
|
||||
for (int i = 0; i < requiredSignalCount; i++)
|
||||
{
|
||||
Signals[i] = string.Empty;
|
||||
}
|
||||
// Load signals from the button elements, if defined.
|
||||
for (int i = 0; i < buttonSignalDefinitions.Count; i++)
|
||||
{
|
||||
Debug.Assert(Signals.Length > i);
|
||||
string overrideDefinition = buttonSignalDefinitions[i];
|
||||
if (overrideDefinition != null)
|
||||
{
|
||||
Signals[i] = overrideDefinition;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (Signals.Length != requiredSignalCount)
|
||||
{
|
||||
string[] newSignals = new string[requiredSignalCount];
|
||||
if (Signals.Length < requiredSignalCount)
|
||||
{
|
||||
Signals.CopyTo(newSignals, 0);
|
||||
for (int i = Signals.Length; i < requiredSignalCount; i++)
|
||||
{
|
||||
newSignals[i] = string.Empty;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < requiredSignalCount; i++)
|
||||
{
|
||||
newSignals[i] = Signals[i];
|
||||
}
|
||||
}
|
||||
Signals = newSignals;
|
||||
}
|
||||
}
|
||||
|
||||
private void LoadActivatingItems()
|
||||
{
|
||||
ActivatingItemPrefabs.Clear();
|
||||
if (!string.IsNullOrEmpty(ActivatingItems))
|
||||
{
|
||||
foreach (string activatingItem in ActivatingItems.Split(','))
|
||||
{
|
||||
Identifier itemIdentifier = activatingItem.ToIdentifier();
|
||||
if (MapEntityPrefab.FindByIdentifier(itemIdentifier) is ItemPrefab prefab)
|
||||
{
|
||||
ActivatingItemPrefabs.Add(prefab);
|
||||
}
|
||||
else
|
||||
{
|
||||
ItemPrefab.Prefabs.Where(p => p.Tags.Any(t => t == itemIdentifier))
|
||||
.ForEach(p => ActivatingItemPrefabs.Add(p));
|
||||
}
|
||||
}
|
||||
if (ActivatingItemPrefabs.None())
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in item \"{item.Name}\": no activating item prefabs found with identifiers or tags \"{ActivatingItems}\"");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void Reset()
|
||||
{
|
||||
base.Reset();
|
||||
Signals = null;
|
||||
LoadSignals();
|
||||
LoadActivatingItems();
|
||||
}
|
||||
|
||||
private bool SendSignal(int signalIndex, Character sender, bool ignoreState = false, string overrideSignal = null)
|
||||
{
|
||||
if (!ignoreState && !IsActivated) { return false; }
|
||||
string signal = overrideSignal ?? Signals[signalIndex];
|
||||
string connectionName = $"signal_out{signalIndex + 1}";
|
||||
item.SendSignal(new Signal(signal, sender: sender), connectionName);
|
||||
AchievementManager.OnButtonTerminalSignal(item, sender);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#nullable enable
|
||||
#nullable enable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -82,8 +82,13 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public bool IsFull => ComponentContainer?.Inventory is { } inventory && inventory.IsFull(true);
|
||||
|
||||
/// <summary>
|
||||
/// Works the same way as the Locked property, but isn't persistent.
|
||||
/// </summary>
|
||||
public bool TemporarilyLocked;
|
||||
|
||||
[Editable, Serialize(false, IsPropertySaveable.Yes, description: "Locked circuit boxes can only be viewed and not interacted with.")]
|
||||
public bool Locked { get; set; }
|
||||
public bool Locked { get; private set; }
|
||||
|
||||
public CircuitBox(Item item, ContentXElement element) : base(item, element)
|
||||
{
|
||||
@@ -635,6 +640,18 @@ namespace Barotrauma.Items.Components
|
||||
OnViewUpdateProjSpecific();
|
||||
}
|
||||
|
||||
public void RemoveWire(Wire wireItem)
|
||||
{
|
||||
foreach (CircuitBoxWire wire in Wires.ToImmutableArray())
|
||||
{
|
||||
if (wire.BackingWire.TryUnwrap(out var backingWire) && backingWire == wireItem.Item)
|
||||
{
|
||||
RemoveWireCollectionUnsafe(wire);
|
||||
}
|
||||
}
|
||||
OnViewUpdateProjSpecific();
|
||||
}
|
||||
|
||||
private void RemoveWireCollectionUnsafe(CircuitBoxWire wire)
|
||||
{
|
||||
foreach (CircuitBoxOutputConnection output in Outputs)
|
||||
@@ -756,6 +773,8 @@ namespace Barotrauma.Items.Components
|
||||
_ => true
|
||||
};
|
||||
|
||||
public bool IsLocked() => Locked || TemporarilyLocked;
|
||||
|
||||
public static Option<Item> GetApplicableResourcePlayerHas(ItemPrefab prefab, Character? character)
|
||||
{
|
||||
if (character is null) { return Option.None; }
|
||||
|
||||
@@ -155,7 +155,7 @@ namespace Barotrauma.Items.Components
|
||||
if (DisplayName.IsNullOrEmpty())
|
||||
{
|
||||
#if DEBUG
|
||||
DebugConsole.ThrowError("Missing display name in connection " + item.Name + ": " + Name);
|
||||
DebugConsole.ThrowError($"Could not find a display name for the connection {Name} in the item {item.Name} (submarine: {item.Submarine?.Info?.Name ?? "none"})");
|
||||
#endif
|
||||
DisplayName = Name;
|
||||
}
|
||||
|
||||
+122
@@ -0,0 +1,122 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma.Items.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Base class for signal components that can select between input/output connections (e.g. multiplexer and demultiplexer components)
|
||||
/// </summary>
|
||||
abstract class ConnectionSelectorComponent : ItemComponent
|
||||
{
|
||||
protected int selectedConnectionIndex;
|
||||
protected string selectedConnectionIndexStr;
|
||||
protected string selectedConnectionName;
|
||||
|
||||
private int connectionCount = -1;
|
||||
|
||||
[InGameEditable,
|
||||
Serialize(0, IsPropertySaveable.Yes, description: "The index of the selected connection.", alwaysUseInstanceValues: true)]
|
||||
public int SelectedConnection
|
||||
{
|
||||
get { return selectedConnectionIndex; }
|
||||
set
|
||||
{
|
||||
selectedConnectionIndex = Math.Max(0, value);
|
||||
//don't clamp until we've determined how many connections the item has
|
||||
//(can't be done until the connection panel component has been loaded too)
|
||||
if (connectionCount > -1)
|
||||
{
|
||||
selectedConnectionIndex = Math.Min(selectedConnectionIndex, connectionCount - 1);
|
||||
}
|
||||
selectedConnectionName = GetConnectionName(selectedConnectionIndex);
|
||||
selectedConnectionIndexStr = selectedConnectionIndex.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
[InGameEditable,
|
||||
Serialize(true, IsPropertySaveable.Yes, description: "Should the selected connection go back to the first one when moving past the last one?", alwaysUseInstanceValues: true)]
|
||||
public bool WrapAround
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[InGameEditable,
|
||||
Serialize(true, IsPropertySaveable.Yes, description: "Should empty connections (connections with no wires in them) be skipped over when moving the selection?", alwaysUseInstanceValues: true)]
|
||||
public bool SkipEmptyConnections
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public ConnectionSelectorComponent(Item item, ContentXElement element)
|
||||
: base(item, element)
|
||||
{
|
||||
}
|
||||
|
||||
protected abstract string GetConnectionName(int connectionIndex);
|
||||
|
||||
/// <summary>
|
||||
/// Name of the input connection that sets the selected connection.
|
||||
/// </summary>
|
||||
protected abstract string InputNameSetConnection { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Name of the input connection that moves the selected connection.
|
||||
/// </summary>
|
||||
protected abstract string InputNameMoveInput { get; }
|
||||
|
||||
protected abstract IEnumerable<Connection> GetConnections();
|
||||
|
||||
public override void OnItemLoaded()
|
||||
{
|
||||
connectionCount = GetConnections().Count();
|
||||
}
|
||||
|
||||
public override void ReceiveSignal(Signal signal, Connection connection)
|
||||
{
|
||||
if (connection.Name == InputNameSetConnection)
|
||||
{
|
||||
if (int.TryParse(signal.value, out int newInput))
|
||||
{
|
||||
SelectedConnection = newInput;
|
||||
}
|
||||
|
||||
}
|
||||
else if (connection.Name == InputNameMoveInput)
|
||||
{
|
||||
if (int.TryParse(signal.value, out int moveAmount))
|
||||
{
|
||||
if (SkipEmptyConnections)
|
||||
{
|
||||
for (int i = 0; i < connectionCount; i++)
|
||||
{
|
||||
moveInput(moveAmount);
|
||||
if (item.Connections.Any(c =>
|
||||
c.Name == selectedConnectionName &&
|
||||
(c.Wires.Any() || c.CircuitBoxConnections.Any())))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
moveInput(moveAmount);
|
||||
}
|
||||
}
|
||||
}
|
||||
void moveInput(int moveAmount)
|
||||
{
|
||||
if (WrapAround)
|
||||
{
|
||||
SelectedConnection = MathUtils.PositiveModulo(selectedConnectionIndex + moveAmount, connectionCount);
|
||||
}
|
||||
else
|
||||
{
|
||||
SelectedConnection += moveAmount;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+124
-92
@@ -21,6 +21,14 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
class CustomInterfaceElement : ISerializableEntity
|
||||
{
|
||||
public enum InputTypeOption
|
||||
{
|
||||
Number,
|
||||
Text,
|
||||
Button,
|
||||
TickBox
|
||||
}
|
||||
|
||||
public bool ContinuousSignal;
|
||||
public bool State;
|
||||
public string ConnectionName;
|
||||
@@ -33,6 +41,7 @@ namespace Barotrauma.Items.Components
|
||||
public string Signal { get; set; }
|
||||
|
||||
public Identifier PropertyName { get; }
|
||||
public Identifier TargetItemComponent { get; }
|
||||
public bool TargetOnlyParentProperty { get; }
|
||||
|
||||
public string NumberInputMin { get; }
|
||||
@@ -44,11 +53,21 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public const string DefaultNumberInputMin = "0", DefaultNumberInputMax = "99", DefaultNumberInputStep = "1";
|
||||
public const int DefaultNumberInputDecimalPlaces = 0;
|
||||
public bool IsNumberInput { get; }
|
||||
public InputTypeOption InputType { get; }
|
||||
public NumberType? NumberType { get; }
|
||||
public bool HasPropertyName { get; }
|
||||
public bool ShouldSetProperty { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// By default, the elements in the interface only set values of the item or send signals.
|
||||
/// This can be used to make them additionally work the other way around, periodically getting the current value of the property from the item and refreshing the UI.
|
||||
/// </summary>
|
||||
public float GetValueInterval { get; set; } = -1.0f;
|
||||
|
||||
#if CLIENT
|
||||
public float GetValueTimer;
|
||||
#endif
|
||||
|
||||
public string Name => "CustomInterfaceElement";
|
||||
|
||||
public Dictionary<Identifier, SerializableProperty> SerializableProperties { get; set; }
|
||||
@@ -59,24 +78,26 @@ namespace Barotrauma.Items.Components
|
||||
/// Pass the parent component to the constructor to access the serializable properties
|
||||
/// for elements which change property values.
|
||||
/// </summary>
|
||||
public CustomInterfaceElement(Item item, ContentXElement element, CustomInterface parent)
|
||||
public CustomInterfaceElement(Item item, ContentXElement element, CustomInterface parent, InputTypeOption inputType)
|
||||
{
|
||||
Label = element.GetAttributeString("text", "");
|
||||
ConnectionName = element.GetAttributeString("connection", "");
|
||||
PropertyName = element.GetAttributeIdentifier("propertyname", "");
|
||||
PropertyName = element.GetAttributeIdentifier("propertyname", Identifier.Empty);
|
||||
TargetItemComponent = element.GetAttributeIdentifier("targetitemcomponent", Identifier.Empty);
|
||||
TargetOnlyParentProperty = element.GetAttributeBool("targetonlyparentproperty", false);
|
||||
NumberInputMin = element.GetAttributeString("min", DefaultNumberInputMin);
|
||||
NumberInputMax = element.GetAttributeString("max", DefaultNumberInputMax);
|
||||
NumberInputStep = element.GetAttributeString("step", DefaultNumberInputStep);
|
||||
NumberInputDecimalPlaces = element.GetAttributeInt("decimalplaces", DefaultNumberInputDecimalPlaces);
|
||||
MaxTextLength = element.GetAttributeInt("maxtextlength", int.MaxValue);
|
||||
GetValueInterval = element.GetAttributeFloat(nameof(GetValueInterval), -1.0f);
|
||||
|
||||
InputType = inputType;
|
||||
|
||||
HasPropertyName = !PropertyName.IsEmpty;
|
||||
if (HasPropertyName)
|
||||
{
|
||||
string elementName = element.Name.ToString().ToLowerInvariant();
|
||||
IsNumberInput = elementName == "numberinput" || elementName == "integerinput"; // backwards compatibility
|
||||
if (IsNumberInput)
|
||||
if (inputType == InputTypeOption.Number)
|
||||
{
|
||||
string numberType = element.GetAttributeString("numbertype", string.Empty);
|
||||
switch (numberType)
|
||||
@@ -101,22 +122,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
else if (HasPropertyName && parent != null)
|
||||
{
|
||||
if (TargetOnlyParentProperty)
|
||||
{
|
||||
if (parent.SerializableProperties.ContainsKey(PropertyName))
|
||||
{
|
||||
Signal = parent.SerializableProperties[PropertyName].GetValue(parent) as string;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (ISerializableEntity e in parent.item.AllPropertyObjects)
|
||||
{
|
||||
if (!e.SerializableProperties.ContainsKey(PropertyName)) { continue; }
|
||||
Signal = e.SerializableProperties[PropertyName].GetValue(e) as string;
|
||||
break;
|
||||
}
|
||||
}
|
||||
parent.SetSignalToPropertyValue(this);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -125,7 +131,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
foreach (var subElement in element.Elements())
|
||||
{
|
||||
if (subElement.Name.ToString().Equals("statuseffect", System.StringComparison.OrdinalIgnoreCase))
|
||||
if (subElement.Name.ToString().Equals("statuseffect", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
StatusEffects.Add(StatusEffect.Load(subElement, parentDebugName: "custom interface element (label " + Label + ")"));
|
||||
}
|
||||
@@ -193,6 +199,13 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
[Serialize(false, IsPropertySaveable.Yes)]
|
||||
public bool ShowInsufficientPowerWarning
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
private readonly List<CustomInterfaceElement> customInterfaceElementList = new List<CustomInterfaceElement>();
|
||||
|
||||
public CustomInterface(Item item, ContentXElement element)
|
||||
@@ -200,36 +213,44 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
foreach (var subElement in element.Elements())
|
||||
{
|
||||
bool continuousSignalByDefault = false;
|
||||
CustomInterfaceElement.InputTypeOption inputType = CustomInterfaceElement.InputTypeOption.Number;
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "button":
|
||||
inputType = CustomInterfaceElement.InputTypeOption.Button;
|
||||
continuousSignalByDefault = false;
|
||||
break;
|
||||
case "textbox":
|
||||
inputType = CustomInterfaceElement.InputTypeOption.Text;
|
||||
continuousSignalByDefault = false;
|
||||
break;
|
||||
case "integerinput": // backwards compatibility
|
||||
case "numberinput":
|
||||
var button = new CustomInterfaceElement(item, subElement, this)
|
||||
{
|
||||
ContinuousSignal = false
|
||||
};
|
||||
if (string.IsNullOrEmpty(button.Label))
|
||||
{
|
||||
button.Label = "Signal out " + customInterfaceElementList.Count(e => !e.ContinuousSignal);
|
||||
}
|
||||
customInterfaceElementList.Add(button);
|
||||
inputType = CustomInterfaceElement.InputTypeOption.Number;
|
||||
continuousSignalByDefault = false;
|
||||
break;
|
||||
case "tickbox":
|
||||
var tickBox = new CustomInterfaceElement(item, subElement, this)
|
||||
{
|
||||
ContinuousSignal = true
|
||||
};
|
||||
if (string.IsNullOrEmpty(tickBox.Label))
|
||||
{
|
||||
tickBox.Label = "Signal out " + customInterfaceElementList.Count(e => e.ContinuousSignal);
|
||||
}
|
||||
customInterfaceElementList.Add(tickBox);
|
||||
inputType = CustomInterfaceElement.InputTypeOption.TickBox;
|
||||
//the default behavior of tickboxes is different for mainly backwards compatibility reasons
|
||||
//(e.g. keeps sending a true/false signal depending on the state of the tickbox, while the others send a signal when the value changes)
|
||||
continuousSignalByDefault = true;
|
||||
break;
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
var ciElement = new CustomInterfaceElement(item, subElement, this, inputType)
|
||||
{
|
||||
ContinuousSignal = subElement.GetAttributeBool(nameof(CustomInterfaceElement.ContinuousSignal), def: continuousSignalByDefault)
|
||||
};
|
||||
if (string.IsNullOrEmpty(ciElement.Label))
|
||||
{
|
||||
ciElement.Label = "Signal out " + customInterfaceElementList.Count(e => e.ContinuousSignal == ciElement.ContinuousSignal);
|
||||
}
|
||||
customInterfaceElementList.Add(ciElement);
|
||||
IsActive |= ciElement.ContinuousSignal;
|
||||
}
|
||||
IsActive = true;
|
||||
|
||||
InitProjSpecific();
|
||||
//load these here to ensure the UI elements (created in InitProjSpecific) are up-to-date
|
||||
Labels = element.GetAttributeString("labels", "");
|
||||
@@ -268,27 +289,55 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
if (element.HasPropertyName && element.ShouldSetProperty)
|
||||
{
|
||||
if (element.TargetOnlyParentProperty)
|
||||
{
|
||||
if (SerializableProperties.ContainsKey(element.PropertyName))
|
||||
{
|
||||
SerializableProperties[element.PropertyName].TrySetValue(this, element.Signal);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var po in item.AllPropertyObjects)
|
||||
{
|
||||
if (!po.SerializableProperties.ContainsKey(element.PropertyName)) { continue; }
|
||||
po.SerializableProperties[element.PropertyName].TrySetValue(po, element.Signal);
|
||||
}
|
||||
}
|
||||
SetPropertyValueToSignal(element);
|
||||
customInterfaceElementList[i].ShouldSetProperty = false;
|
||||
}
|
||||
}
|
||||
UpdateSignalsProjSpecific();
|
||||
}
|
||||
|
||||
private void SetPropertyValueToSignal(CustomInterfaceElement element)
|
||||
{
|
||||
if (element.TargetOnlyParentProperty)
|
||||
{
|
||||
if (SerializableProperties.ContainsKey(element.PropertyName))
|
||||
{
|
||||
SerializableProperties[element.PropertyName].TrySetValue(this, element.Signal);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var po in item.AllPropertyObjects)
|
||||
{
|
||||
if (!po.SerializableProperties.ContainsKey(element.PropertyName)) { continue; }
|
||||
if (!element.TargetItemComponent.IsEmpty && po.Name != element.TargetItemComponent) { continue; }
|
||||
po.SerializableProperties[element.PropertyName].TrySetValue(po, element.Signal);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SetSignalToPropertyValue(CustomInterfaceElement element)
|
||||
{
|
||||
if (element.TargetOnlyParentProperty)
|
||||
{
|
||||
if (SerializableProperties.ContainsKey(element.PropertyName))
|
||||
{
|
||||
element.Signal = SerializableProperties[element.PropertyName].GetValue(this)?.ToString();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (ISerializableEntity e in item.AllPropertyObjects)
|
||||
{
|
||||
if (!e.SerializableProperties.ContainsKey(element.PropertyName)) { continue; }
|
||||
if (!element.TargetItemComponent.IsEmpty && e.Name != element.TargetItemComponent) { continue; }
|
||||
element.Signal = e.SerializableProperties[element.PropertyName].GetValue(e)?.ToString();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public override void OnItemLoaded()
|
||||
{
|
||||
foreach (CustomInterfaceElement ciElement in customInterfaceElementList)
|
||||
@@ -333,41 +382,28 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (tickBoxElement == null) { return; }
|
||||
tickBoxElement.State = state;
|
||||
tickBoxElement.Signal = state.ToString();
|
||||
if (!tickBoxElement.ContinuousSignal)
|
||||
{
|
||||
SetPropertyValueToSignal(tickBoxElement);
|
||||
}
|
||||
}
|
||||
|
||||
private void TextChanged(CustomInterfaceElement textElement, string text)
|
||||
{
|
||||
if (textElement == null) { return; }
|
||||
textElement.Signal = text;
|
||||
if (!textElement.TargetOnlyParentProperty)
|
||||
{
|
||||
foreach (ISerializableEntity e in item.AllPropertyObjects)
|
||||
{
|
||||
if (!e.SerializableProperties.ContainsKey(textElement.PropertyName)) { continue; }
|
||||
e.SerializableProperties[textElement.PropertyName].TrySetValue(e, text);
|
||||
}
|
||||
}
|
||||
else if (SerializableProperties.ContainsKey(textElement.PropertyName))
|
||||
{
|
||||
SerializableProperties[textElement.PropertyName].TrySetValue(this, text);
|
||||
}
|
||||
SetPropertyValueToSignal(textElement);
|
||||
}
|
||||
|
||||
private void ValueChanged(CustomInterfaceElement numberInputElement, int value)
|
||||
{
|
||||
if (numberInputElement == null) { return; }
|
||||
numberInputElement.Signal = value.ToString();
|
||||
if (!numberInputElement.TargetOnlyParentProperty)
|
||||
SetPropertyValueToSignal(numberInputElement);
|
||||
foreach (StatusEffect effect in numberInputElement.StatusEffects)
|
||||
{
|
||||
foreach (ISerializableEntity e in item.AllPropertyObjects)
|
||||
{
|
||||
if (!e.SerializableProperties.ContainsKey(numberInputElement.PropertyName)) { continue; }
|
||||
e.SerializableProperties[numberInputElement.PropertyName].TrySetValue(e, value);
|
||||
}
|
||||
}
|
||||
else if (SerializableProperties.ContainsKey(numberInputElement.PropertyName))
|
||||
{
|
||||
SerializableProperties[numberInputElement.PropertyName].TrySetValue(this, value);
|
||||
item.ApplyStatusEffect(effect, ActionType.OnUse, 1.0f, character: item.ParentInventory?.Owner as Character);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -375,25 +411,14 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (numberInputElement == null) { return; }
|
||||
numberInputElement.Signal = value.ToString();
|
||||
if (!numberInputElement.TargetOnlyParentProperty)
|
||||
{
|
||||
foreach (ISerializableEntity e in item.AllPropertyObjects)
|
||||
{
|
||||
if (!e.SerializableProperties.ContainsKey(numberInputElement.PropertyName)) { continue; }
|
||||
e.SerializableProperties[numberInputElement.PropertyName].TrySetValue(e, value);
|
||||
}
|
||||
}
|
||||
else if (SerializableProperties.ContainsKey(numberInputElement.PropertyName))
|
||||
{
|
||||
SerializableProperties[numberInputElement.PropertyName].TrySetValue(this, value);
|
||||
}
|
||||
SetPropertyValueToSignal(numberInputElement);
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
foreach (CustomInterfaceElement ciElement in customInterfaceElementList)
|
||||
{
|
||||
if (!ciElement.ContinuousSignal) { continue; }
|
||||
if (!ciElement.ContinuousSignal && ciElement.PropertyName != "Voltage") { continue; }
|
||||
//TODO: allow changing output when a tickbox is not selected
|
||||
if (!string.IsNullOrEmpty(ciElement.Signal) && ciElement.Connection != null)
|
||||
{
|
||||
@@ -407,6 +432,13 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
public override void UpdateBroken(float deltaTime, Camera cam)
|
||||
{
|
||||
//CustomInterface works even when broken (it should be possible to tick the checkboxes and change values,
|
||||
//it's up to the other components to work or not work depending on whether the item is broken)
|
||||
Update(deltaTime, cam);
|
||||
}
|
||||
|
||||
public override XElement Save(XElement parentElement)
|
||||
{
|
||||
labels = customInterfaceElementList.Select(ci => ci.Label).ToArray();
|
||||
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma.Items.Components;
|
||||
|
||||
/// <summary>
|
||||
/// A component with one input and multiple outputs. Can be used to choose which output the signal should be passed to.
|
||||
/// </summary>
|
||||
sealed class DemultiplexerComponent : ConnectionSelectorComponent
|
||||
{
|
||||
public DemultiplexerComponent(Item item, ContentXElement element)
|
||||
: base(item, element)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
protected override string InputNameSetConnection => "set_output";
|
||||
|
||||
protected override string InputNameMoveInput => "move_output";
|
||||
|
||||
public override void OnItemLoaded()
|
||||
{
|
||||
base.OnItemLoaded();
|
||||
IsActive = item.Connections != null && item.Connections.Any(c => c.Name == "selected_output_out");
|
||||
}
|
||||
|
||||
public override void ReceiveSignal(Signal signal, Connection connection)
|
||||
{
|
||||
if (connection.Name == "signal_in")
|
||||
{
|
||||
item.SendSignal(signal, selectedConnectionName);
|
||||
}
|
||||
else
|
||||
{
|
||||
base.ReceiveSignal(signal, connection);
|
||||
}
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
item.SendSignal(selectedConnectionIndexStr, "selected_output_out");
|
||||
}
|
||||
|
||||
protected override string GetConnectionName(int connectionIndex)
|
||||
{
|
||||
return "signal_out" + connectionIndex;
|
||||
}
|
||||
|
||||
protected override IEnumerable<Connection> GetConnections()
|
||||
{
|
||||
if (item.GetComponent<ConnectionPanel>() is { } connectionPanel)
|
||||
{
|
||||
return connectionPanel.Connections.Where(c => c.IsOutput && c.Name.StartsWith("signal_out"));
|
||||
}
|
||||
return Enumerable.Empty<Connection>();
|
||||
}
|
||||
}
|
||||
+18
-11
@@ -94,9 +94,9 @@ namespace Barotrauma.Items.Components
|
||||
set
|
||||
{
|
||||
if (isOn == value && IsActive == value) { return; }
|
||||
|
||||
IsActive = isOn = value;
|
||||
SetLightSourceState(value, value ? lightBrightness : 0.0f);
|
||||
bool isLightOn = isOn && item.Condition > 0;
|
||||
SetLightSourceState(isLightOn, isLightOn ? lightBrightness : 0.0f);
|
||||
OnStateChanged();
|
||||
}
|
||||
}
|
||||
@@ -198,6 +198,13 @@ namespace Barotrauma.Items.Components
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize("0,0", IsPropertySaveable.No, description: "Offset of the light from the position of the item (in pixels).")]
|
||||
public Vector2 LightOffset
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the red component of the light is twice as bright as the blue and green. Can be used by StatusEffects.
|
||||
/// </summary>
|
||||
@@ -259,7 +266,6 @@ namespace Barotrauma.Items.Components
|
||||
#endif
|
||||
|
||||
IsActive = IsOn;
|
||||
item.AddTag("light");
|
||||
}
|
||||
|
||||
public override void OnItemLoaded()
|
||||
@@ -305,17 +311,18 @@ namespace Barotrauma.Items.Components
|
||||
(statusEffectLists == null || !statusEffectLists.ContainsKey(ActionType.OnActive)) &&
|
||||
(IsActiveConditionals == null || IsActiveConditionals.Count == 0))
|
||||
{
|
||||
if (item.body == null || item.body.Enabled ||
|
||||
(item.ParentInventory is ItemInventory itemInventory && !itemInventory.Container.HideItems))
|
||||
{
|
||||
lightBrightness = 1.0f;
|
||||
SetLightSourceState(true, lightBrightness);
|
||||
}
|
||||
else
|
||||
PhysicsBody body = ParentBody ?? item.body;
|
||||
if ((body == null || !body.Enabled) &&
|
||||
(item.FindParentInventory(static it => it is ItemInventory { Container.HideItems: true }) != null))
|
||||
{
|
||||
lightBrightness = 0.0f;
|
||||
SetLightSourceState(false, 0.0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
lightBrightness = 1.0f;
|
||||
SetLightSourceState(true, lightBrightness);
|
||||
}
|
||||
isOn = true;
|
||||
SetLightSourceTransformProjSpecific();
|
||||
base.IsActive = false;
|
||||
@@ -367,7 +374,7 @@ namespace Barotrauma.Items.Components
|
||||
SetLightSourceTransformProjSpecific();
|
||||
|
||||
PhysicsBody body = ParentBody ?? item.body;
|
||||
if (body != null && !body.Enabled && !visibleInContainer)
|
||||
if ((body == null || !body.Enabled) && !visibleInContainer)
|
||||
{
|
||||
lightBrightness = 0.0f;
|
||||
SetLightSourceState(false, 0.0f);
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
using FarseerPhysics;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.Extensions;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
@@ -12,6 +15,9 @@ namespace Barotrauma.Items.Components
|
||||
private Vector2 detectOffset;
|
||||
|
||||
private float updateTimer;
|
||||
|
||||
[Serialize(false, IsPropertySaveable.No, description: "Has the item currently detected movement. Intended to be used by StatusEffect conditionals (setting this value in XML has no effect).")]
|
||||
public bool MotionDetected { get; set; }
|
||||
|
||||
[Flags]
|
||||
public enum TargetType
|
||||
@@ -23,15 +29,34 @@ namespace Barotrauma.Items.Components
|
||||
Any = Human | Monster | Wall | Pet,
|
||||
}
|
||||
|
||||
[Serialize(false, IsPropertySaveable.No, description: "Has the item currently detected movement. Intended to be used by StatusEffect conditionals (setting this value in XML has no effect).")]
|
||||
public bool MotionDetected { get; set; }
|
||||
|
||||
private bool triggerFromHumans = true;
|
||||
private bool triggerFromPets = true;
|
||||
private bool triggerFromMonsters = true;
|
||||
private TargetType _target;
|
||||
|
||||
[InGameEditable, Serialize(TargetType.Any, IsPropertySaveable.Yes, description: "Which kind of targets can trigger the sensor?", alwaysUseInstanceValues: true)]
|
||||
public TargetType Target
|
||||
{
|
||||
get;
|
||||
set;
|
||||
get => _target;
|
||||
set
|
||||
{
|
||||
if (_target != value)
|
||||
{
|
||||
_target = value;
|
||||
triggerFromHumans = Target.HasFlag(TargetType.Human);
|
||||
triggerFromPets = Target.HasFlag(TargetType.Pet);
|
||||
triggerFromMonsters = Target.HasFlag(TargetType.Monster);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Editable, Serialize("", IsPropertySaveable.Yes, description: "Does the sensor react only to certain characters (species names, groups or tags)? Doesn't have an effect, if the Target Type is incorrect.", alwaysUseInstanceValues: true)]
|
||||
public string TargetCharacters
|
||||
{
|
||||
get => targetCharacters.ConvertToString();
|
||||
set => targetCharacters = value.ToIdentifiers().ToHashSet();
|
||||
}
|
||||
private HashSet<Identifier> targetCharacters;
|
||||
|
||||
[InGameEditable, Serialize(false, IsPropertySaveable.Yes, description: "Should the sensor ignore the bodies of dead characters?", alwaysUseInstanceValues: true)]
|
||||
public bool IgnoreDead
|
||||
@@ -40,7 +65,6 @@ namespace Barotrauma.Items.Components
|
||||
set;
|
||||
}
|
||||
|
||||
|
||||
[InGameEditable, Serialize(0.0f, IsPropertySaveable.Yes, description: "Horizontal detection range.", alwaysUseInstanceValues: true)]
|
||||
public float RangeX
|
||||
{
|
||||
@@ -248,51 +272,37 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
if (worldBorders.Intersects(detectRect))
|
||||
{
|
||||
MotionDetected = true;
|
||||
return;
|
||||
foreach (Structure wall in Structure.WallList)
|
||||
{
|
||||
if (wall.Submarine == sub &&
|
||||
wall.WorldRect.Intersects(detectRect))
|
||||
{
|
||||
MotionDetected = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool triggerFromHumans = Target.HasFlag(TargetType.Human);
|
||||
bool triggerFromPets = Target.HasFlag(TargetType.Pet);
|
||||
bool triggerFromMonsters = Target.HasFlag(TargetType.Monster);
|
||||
|
||||
bool hasTriggers = triggerFromHumans || triggerFromPets || triggerFromMonsters;
|
||||
if (!hasTriggers) { return; }
|
||||
foreach (Character c in Character.CharacterList)
|
||||
foreach (Character character in Character.CharacterList)
|
||||
{
|
||||
if (IgnoreDead && c.IsDead) { continue; }
|
||||
|
||||
//ignore characters that have spawned a second or less ago
|
||||
//makes it possible to detect when a spawned character moves without triggering the detector immediately as the ragdoll spawns and drops to the ground
|
||||
if (c.SpawnTime > Timing.TotalTime - 1.0) { continue; }
|
||||
if (c.IsHuman)
|
||||
{
|
||||
if (!triggerFromHumans) { continue; }
|
||||
}
|
||||
else if (c.IsPet)
|
||||
{
|
||||
if (!triggerFromPets) { continue; }
|
||||
}
|
||||
else
|
||||
{
|
||||
// Not a human or a pet -> monster?
|
||||
if (!triggerFromMonsters) { continue; }
|
||||
if (CharacterParams.CompareGroup(c.Group, CharacterPrefab.HumanGroup))
|
||||
{
|
||||
//characters in the "human" group aren't considered monsters (even if they were something like a friendly mudraptor)
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (character.SpawnTime > Timing.TotalTime - 1.0) { continue; }
|
||||
|
||||
if (!TriggersOn(character)) { continue; }
|
||||
|
||||
//do a rough check based on the position of the character's collider first
|
||||
//before the more accurate limb-based check
|
||||
if (Math.Abs(c.WorldPosition.X - detectPos.X) > broadRangeX || Math.Abs(c.WorldPosition.Y - detectPos.Y) > broadRangeY)
|
||||
if (Math.Abs(character.WorldPosition.X - detectPos.X) > broadRangeX || Math.Abs(character.WorldPosition.Y - detectPos.Y) > broadRangeY)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (Limb limb in c.AnimController.Limbs)
|
||||
foreach (Limb limb in character.AnimController.Limbs)
|
||||
{
|
||||
if (limb.IsSevered) { continue; }
|
||||
if (limb.LinearVelocity.LengthSquared() < MinimumVelocity * MinimumVelocity) { continue; }
|
||||
@@ -304,7 +314,53 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public bool TriggersOn(Character character)
|
||||
{
|
||||
bool hasTriggers = triggerFromHumans || triggerFromPets || triggerFromMonsters;
|
||||
if (!hasTriggers) { return false; }
|
||||
return TriggersOn(character, triggerFromHumans, triggerFromPets, triggerFromMonsters);
|
||||
}
|
||||
|
||||
private bool TriggersOn(Character character, bool triggerFromHumans, bool triggerFromPets, bool triggerFromMonsters)
|
||||
{
|
||||
if (IgnoreDead && character.IsDead) { return false; }
|
||||
if (character.IsHuman)
|
||||
{
|
||||
if (!triggerFromHumans) { return false; }
|
||||
}
|
||||
else if (character.IsPet)
|
||||
{
|
||||
if (!triggerFromPets) { return false; }
|
||||
}
|
||||
else
|
||||
{
|
||||
// Not a human or a pet -> monster?
|
||||
if (!triggerFromMonsters) { return false; }
|
||||
if (CharacterParams.CompareGroup(character.Group, CharacterPrefab.HumanGroup))
|
||||
{
|
||||
//characters in the "human" group aren't considered monsters (even if they were something like a friendly mudraptor)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// Check matching character, if defined.
|
||||
if (targetCharacters.Any())
|
||||
{
|
||||
// Performance critical code -> using a foreach loop to avoid having to capture variables in lambdas.
|
||||
bool matchFound = false;
|
||||
foreach (Identifier target in targetCharacters)
|
||||
{
|
||||
if (character.MatchesSpeciesNameOrGroup(target) || character.Params.HasTag(target))
|
||||
{
|
||||
matchFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!matchFound) { return false; }
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public override XElement Save(XElement parentElement)
|
||||
{
|
||||
Vector2 prevDetectOffset = detectOffset;
|
||||
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma.Items.Components;
|
||||
|
||||
/// <summary>
|
||||
/// A component with multiple inputs and one output. Can be used to choose which input the component passes signals to the output from.
|
||||
/// </summary>
|
||||
sealed class MultiplexerComponent : ConnectionSelectorComponent
|
||||
{
|
||||
public MultiplexerComponent(Item item, ContentXElement element)
|
||||
: base(item, element)
|
||||
{
|
||||
}
|
||||
|
||||
protected override string InputNameSetConnection => "set_input";
|
||||
|
||||
protected override string InputNameMoveInput => "move_input";
|
||||
|
||||
public override void OnItemLoaded()
|
||||
{
|
||||
base.OnItemLoaded();
|
||||
IsActive = item.Connections != null && item.Connections.Any(c => c.Name == "selected_input_out");
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
item.SendSignal(selectedConnectionIndexStr, "selected_input_out");
|
||||
}
|
||||
public override void ReceiveSignal(Signal signal, Connection connection)
|
||||
{
|
||||
if (connection.Name.StartsWith("signal_in"))
|
||||
{
|
||||
if (connection.Name == selectedConnectionName)
|
||||
{
|
||||
item.SendSignal(signal, "signal_out");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
base.ReceiveSignal(signal, connection);
|
||||
}
|
||||
}
|
||||
|
||||
protected override string GetConnectionName(int connectionIndex)
|
||||
{
|
||||
return "signal_in" + connectionIndex;
|
||||
}
|
||||
|
||||
protected override IEnumerable<Connection> GetConnections()
|
||||
{
|
||||
if (item.GetComponent<ConnectionPanel>() is { } connectionPanel)
|
||||
{
|
||||
return connectionPanel.Connections.Where(c => !c.IsOutput && c.Name.StartsWith("signal_in"));
|
||||
}
|
||||
return Enumerable.Empty<Connection>();
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,11 @@
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
class OxygenDetector : ItemComponent
|
||||
{
|
||||
public const int LowOxygenPercentage = 35;
|
||||
|
||||
private int prevSentOxygenValue;
|
||||
private string oxygenSignal;
|
||||
public string OxygenSignal { get; private set; }
|
||||
|
||||
public OxygenDetector(Item item, ContentXElement element)
|
||||
: base (item, element)
|
||||
@@ -20,13 +18,13 @@ namespace Barotrauma.Items.Components
|
||||
if (item.CurrentHull == null) { return; }
|
||||
|
||||
int currOxygenPercentage = (int)item.CurrentHull.OxygenPercentage;
|
||||
if (prevSentOxygenValue != currOxygenPercentage || oxygenSignal == null)
|
||||
if (prevSentOxygenValue != currOxygenPercentage || OxygenSignal == null)
|
||||
{
|
||||
prevSentOxygenValue = currOxygenPercentage;
|
||||
oxygenSignal = prevSentOxygenValue.ToString();
|
||||
OxygenSignal = prevSentOxygenValue.ToString();
|
||||
}
|
||||
|
||||
item.SendSignal(oxygenSignal, "signal_out");
|
||||
item.SendSignal(OxygenSignal, "signal_out");
|
||||
item.SendSignal(currOxygenPercentage <= LowOxygenPercentage ? "1" : "0", "low_oxygen");
|
||||
}
|
||||
|
||||
|
||||
@@ -80,7 +80,6 @@ namespace Barotrauma.Items.Components
|
||||
set
|
||||
{
|
||||
isOn = value;
|
||||
CanTransfer = value;
|
||||
if (!isOn)
|
||||
{
|
||||
currPowerConsumption = 0.0f;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
@@ -8,7 +7,7 @@ namespace Barotrauma.Items.Components
|
||||
const float FireCheckInterval = 1.0f;
|
||||
private float fireCheckTimer;
|
||||
|
||||
private bool fireInRange;
|
||||
public bool FireInRange { get; private set; }
|
||||
|
||||
private int maxOutputLength;
|
||||
[Editable, Serialize(200, IsPropertySaveable.No, description: "The maximum length of the output strings. Warning: Large values can lead to large memory usage or networking issues.")]
|
||||
@@ -80,10 +79,10 @@ namespace Barotrauma.Items.Components
|
||||
fireCheckTimer -= deltaTime;
|
||||
if (fireCheckTimer <= 0.0f)
|
||||
{
|
||||
fireInRange = IsFireInRange();
|
||||
FireInRange = IsFireInRange();
|
||||
fireCheckTimer = FireCheckInterval;
|
||||
}
|
||||
string signalOut = fireInRange ? Output : FalseOutput;
|
||||
string signalOut = FireInRange ? Output : FalseOutput;
|
||||
if (!string.IsNullOrEmpty(signalOut)) { item.SendSignal(signalOut, "signal_out"); }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,8 +96,20 @@ namespace Barotrauma.Items.Components
|
||||
[Editable, Serialize("> ", IsPropertySaveable.Yes)]
|
||||
public string LineStartSymbol { get; set; }
|
||||
|
||||
[Editable, Serialize(false, IsPropertySaveable.No)]
|
||||
public bool Readonly { get; set; }
|
||||
private bool _readonly;
|
||||
|
||||
[Editable, Serialize(false, IsPropertySaveable.Yes)]
|
||||
public bool Readonly
|
||||
{
|
||||
get => _readonly;
|
||||
set
|
||||
{
|
||||
_readonly = value;
|
||||
#if CLIENT
|
||||
RefreshInputElements();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
[Serialize(true, IsPropertySaveable.No)]
|
||||
public bool AutoScrollToBottom { get; set; }
|
||||
|
||||
@@ -8,7 +8,7 @@ using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
partial class WifiComponent : ItemComponent, IServerSerializable
|
||||
partial class WifiComponent : ItemComponent, IServerSerializable, IClientSerializable
|
||||
{
|
||||
private static readonly List<WifiComponent> list = new List<WifiComponent>();
|
||||
|
||||
@@ -56,7 +56,6 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[Editable, Serialize(false, IsPropertySaveable.Yes, description: "Can the component communicate with wifi components in another team's submarine (e.g. enemy sub in Combat missions, respawn shuttle). Needs to be enabled on both the component transmitting the signal and the component receiving it.", alwaysUseInstanceValues: true)]
|
||||
public bool AllowCrossTeamCommunication
|
||||
{
|
||||
@@ -229,14 +228,20 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public void TransmitSignal(Signal signal, bool sentFromChat)
|
||||
{
|
||||
if (sentFromChat)
|
||||
{
|
||||
item.LastSentSignalRecipients.Clear();
|
||||
}
|
||||
|
||||
bool chatMsgSent = false;
|
||||
|
||||
var receivers = GetReceiversInRange();
|
||||
if (sentFromChat)
|
||||
{
|
||||
//if sent from chat, we need to reset the "signal chain" at this point
|
||||
//so we can correctly detect which components the signal has already passed through to avoid infinite loops
|
||||
//only relevant for signals originating from the chat - normally this is handled in Item.SendSignal
|
||||
item.LastSentSignalRecipients.Clear();
|
||||
foreach (WifiComponent receiver in receivers)
|
||||
{
|
||||
receiver.item.LastSentSignalRecipients.Clear();
|
||||
}
|
||||
}
|
||||
foreach (WifiComponent wifiComp in receivers)
|
||||
{
|
||||
if (sentFromChat && !wifiComp.LinkToChat) { continue; }
|
||||
@@ -366,5 +371,25 @@ namespace Barotrauma.Items.Components
|
||||
element.Add(new XAttribute("channelmemory", string.Join(',', channelMemory)));
|
||||
return element;
|
||||
}
|
||||
|
||||
protected void SharedEventWrite(IWriteMessage msg)
|
||||
{
|
||||
msg.WriteRangedInteger(Channel, MinChannel, MaxChannel);
|
||||
|
||||
for (int i = 0; i < ChannelMemorySize; i++)
|
||||
{
|
||||
msg.WriteRangedInteger(channelMemory[i], MinChannel, MaxChannel);
|
||||
}
|
||||
}
|
||||
|
||||
protected void SharedEventRead(IReadMessage msg)
|
||||
{
|
||||
Channel = msg.ReadRangedInteger(MinChannel, MaxChannel);
|
||||
|
||||
for (int i = 0; i < ChannelMemorySize; i++)
|
||||
{
|
||||
channelMemory[i] = msg.ReadRangedInteger(MinChannel, MaxChannel);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,6 +66,7 @@ namespace Barotrauma.Items.Components
|
||||
private float editNodeDelay;
|
||||
|
||||
private bool locked;
|
||||
|
||||
public bool Locked
|
||||
{
|
||||
get
|
||||
@@ -83,6 +84,13 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public float Length { get; private set; }
|
||||
|
||||
[Serialize(0.3f, IsPropertySaveable.No), Editable(MinValueFloat = 0.01f, MaxValueFloat = 10.0f, DecimalCount = 2)]
|
||||
public float Width
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(5000.0f, IsPropertySaveable.No, description: "The maximum distance the wire can extend (in pixels).")]
|
||||
public float MaxLength
|
||||
{
|
||||
@@ -884,8 +892,13 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
protected override void RemoveComponentSpecific()
|
||||
{
|
||||
if (item.Container?.GetComponent<CircuitBox>() is { } circuitBox)
|
||||
{
|
||||
circuitBox.RemoveWire(this);
|
||||
}
|
||||
ClearConnections();
|
||||
base.RemoveComponentSpecific();
|
||||
|
||||
#if CLIENT
|
||||
if (DraggingWire == this) { draggingWire = null; }
|
||||
overrideSprite?.Remove();
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
using FarseerPhysics;
|
||||
using FarseerPhysics;
|
||||
using FarseerPhysics.Dynamics;
|
||||
using FarseerPhysics.Dynamics.Contacts;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using Barotrauma.Extensions;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
@@ -17,6 +19,7 @@ namespace Barotrauma.Items.Components
|
||||
public bool DistanceBasedForce { get; set; }
|
||||
[Editable, Serialize(false, IsPropertySaveable.Yes, description: "Determines if the force fluctuates over time or if it stays constant.", alwaysUseInstanceValues: true)]
|
||||
public bool ForceFluctuation { get; set; }
|
||||
|
||||
[Serialize(1.0f, IsPropertySaveable.Yes, description: "How much the fluctuation affects the force. 1 is the maximum fluctuation, 0 is no fluctuation.", alwaysUseInstanceValues: true)]
|
||||
private float ForceFluctuationStrength
|
||||
{
|
||||
@@ -55,7 +58,65 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
|
||||
public PhysicsBody PhysicsBody { get; private set; }
|
||||
private float Radius { get; set; }
|
||||
|
||||
private float radius;
|
||||
[Editable, Serialize(0.0f, IsPropertySaveable.Yes)]
|
||||
public float Radius
|
||||
{
|
||||
get => radius;
|
||||
|
||||
set
|
||||
{
|
||||
if (radius == value) { return; }
|
||||
radius = value;
|
||||
if (PhysicsBody != null) { RefreshPhysicsBodySize(); }
|
||||
}
|
||||
}
|
||||
|
||||
private float width;
|
||||
[Editable, Serialize(0.0f, IsPropertySaveable.Yes)]
|
||||
public float Width
|
||||
{
|
||||
get => width;
|
||||
|
||||
set
|
||||
{
|
||||
if (width == value) { return; }
|
||||
width = value;
|
||||
if (PhysicsBody != null) { RefreshPhysicsBodySize(); }
|
||||
}
|
||||
}
|
||||
|
||||
private float height;
|
||||
[Editable, Serialize(0.0f, IsPropertySaveable.Yes)]
|
||||
public float Height
|
||||
{
|
||||
get => height;
|
||||
|
||||
set
|
||||
{
|
||||
if (height == value) { return; }
|
||||
height = value;
|
||||
if (PhysicsBody != null) { RefreshPhysicsBodySize(); }
|
||||
}
|
||||
}
|
||||
|
||||
private float currentRadius, currentWidth, currentHeight;
|
||||
|
||||
private Vector2 bodyOffset;
|
||||
[Editable, Serialize("0,0", IsPropertySaveable.Yes)]
|
||||
public Vector2 BodyOffset
|
||||
{
|
||||
get => bodyOffset;
|
||||
|
||||
set
|
||||
{
|
||||
if (bodyOffset == value) { return; }
|
||||
bodyOffset = value;
|
||||
if (PhysicsBody != null) { SetPhysicsBodyPosition(); }
|
||||
}
|
||||
}
|
||||
|
||||
private float RadiusInDisplayUnits { get; set; }
|
||||
private bool TriggeredOnce { get; set; }
|
||||
private float CurrentForceFluctuation { get; set; } = 1.0f;
|
||||
@@ -75,7 +136,24 @@ namespace Barotrauma.Items.Components
|
||||
[Serialize(false, IsPropertySaveable.Yes, alwaysUseInstanceValues: true)]
|
||||
public bool MoveOutsideSub { get; set; }
|
||||
|
||||
public override bool IsActive
|
||||
{
|
||||
get => base.IsActive;
|
||||
set
|
||||
{
|
||||
base.IsActive = value;
|
||||
if (!IsActive)
|
||||
{
|
||||
TriggerActive = false;
|
||||
triggerers.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private readonly LevelTrigger.TriggererType triggeredBy;
|
||||
private readonly Identifier triggerSpeciesOrGroup;
|
||||
private readonly PropertyConditional.LogicalComparison conditionals;
|
||||
|
||||
private readonly HashSet<Entity> triggerers = new HashSet<Entity>();
|
||||
private readonly bool triggerOnce;
|
||||
private readonly List<ISerializableEntity> statusEffectTargets = new List<ISerializableEntity>();
|
||||
@@ -94,11 +172,20 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public TriggerComponent(Item item, ContentXElement element) : base(item, element)
|
||||
{
|
||||
string triggeredByAttribute = element.GetAttributeString("triggeredby", "Character");
|
||||
if (!Enum.TryParse(triggeredByAttribute, out triggeredBy))
|
||||
string triggeredByString = element.GetAttributeString("triggeredby", "Character");
|
||||
if (!Enum.TryParse(triggeredByString, out triggeredBy))
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in ForceComponent config: \"{triggeredByAttribute}\" is not a valid triggerer type.",
|
||||
contentPackage: element.ContentPackage);
|
||||
Identifier speciesOrGroup = triggeredByString.ToIdentifier();
|
||||
if (CharacterPrefab.Prefabs.Any(p => p.MatchesSpeciesNameOrGroup(speciesOrGroup)))
|
||||
{
|
||||
triggerSpeciesOrGroup = speciesOrGroup;
|
||||
triggeredBy = LevelTrigger.TriggererType.Character;
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in ForceComponent config: \"{triggeredByString}\" is not a valid triggerer type.",
|
||||
contentPackage: element.ContentPackage);
|
||||
}
|
||||
}
|
||||
triggerOnce = element.GetAttributeBool("triggeronce", false);
|
||||
string parentDebugName = $"TriggerComponent in {item.Name}";
|
||||
@@ -115,36 +202,93 @@ namespace Barotrauma.Items.Components
|
||||
break;
|
||||
}
|
||||
}
|
||||
conditionals = PropertyConditional.LoadConditionals(element);
|
||||
IsActive = true;
|
||||
}
|
||||
|
||||
public override void OnItemLoaded()
|
||||
{
|
||||
base.OnItemLoaded();
|
||||
float radiusAttribute = originalElement.GetAttributeFloat("radius", 10.0f);
|
||||
Radius = ConvertUnits.ToSimUnits(radiusAttribute * item.Scale);
|
||||
PhysicsBody = new PhysicsBody(0.0f, 0.0f, Radius, 1.5f, BodyType.Static, Physics.CollisionWall, LevelTrigger.GetCollisionCategories(triggeredBy))
|
||||
RefreshPhysicsBodySize();
|
||||
}
|
||||
|
||||
private void RefreshPhysicsBodySize()
|
||||
{
|
||||
PhysicsBody?.Remove();
|
||||
|
||||
currentWidth = ConvertUnits.ToSimUnits(Width * item.Scale);
|
||||
currentHeight = ConvertUnits.ToSimUnits(Height * item.Scale);
|
||||
if (currentWidth > 0 && currentHeight > 0)
|
||||
{
|
||||
UserData = item
|
||||
};
|
||||
PhysicsBody.SetTransformIgnoreContacts(item.SimPosition, 0.0f);
|
||||
PhysicsBody.FarseerBody.SetIsSensor(true);
|
||||
PhysicsBody = new PhysicsBody(currentWidth, currentHeight, radius: 0.0f, density: 1.5f, BodyType.Static, Physics.CollisionWall, LevelTrigger.GetCollisionCategories(triggeredBy))
|
||||
{
|
||||
UserData = item
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
currentRadius = Math.Max(ConvertUnits.ToSimUnits(Radius * item.Scale), 0.01f);
|
||||
PhysicsBody = new PhysicsBody(width: 0.0f, height: 0.0f, radius: currentRadius, density: 1.5f, BodyType.Static, Physics.CollisionWall, LevelTrigger.GetCollisionCategories(triggeredBy))
|
||||
{
|
||||
UserData = item
|
||||
};
|
||||
}
|
||||
|
||||
SetPhysicsBodyPosition();
|
||||
PhysicsBody.FarseerBody.SetIsSensor(originalElement.GetAttributeBool("sensor", true));
|
||||
PhysicsBody.FarseerBody.OnCollision += OnCollision;
|
||||
PhysicsBody.FarseerBody.OnSeparation += OnSeparation;
|
||||
RadiusInDisplayUnits = ConvertUnits.ToDisplayUnits(PhysicsBody.Radius);
|
||||
}
|
||||
|
||||
public void SetPhysicsBodyPosition(bool ignoreContacts = true)
|
||||
{
|
||||
if (PhysicsBody == null) { return; }
|
||||
|
||||
Vector2 offset = ConvertUnits.ToSimUnits(BodyOffset * item.Scale);
|
||||
if (item.FlippedX)
|
||||
{
|
||||
offset.X = -offset.X;
|
||||
}
|
||||
if (item.FlippedY)
|
||||
{
|
||||
offset.Y = -offset.Y;
|
||||
}
|
||||
if (!MathUtils.NearlyEqual(item.RotationRad, 0))
|
||||
{
|
||||
Matrix transform = Matrix.CreateRotationZ(-item.RotationRad);
|
||||
offset = Vector2.Transform(offset, transform);
|
||||
}
|
||||
if (ignoreContacts)
|
||||
{
|
||||
PhysicsBody.SetTransformIgnoreContacts(item.SimPosition + offset, -item.RotationRad);
|
||||
}
|
||||
else
|
||||
{
|
||||
PhysicsBody.SetTransform(item.SimPosition + offset, -item.RotationRad);
|
||||
}
|
||||
PhysicsBody.UpdateDrawPosition();
|
||||
}
|
||||
|
||||
public override void FlipX(bool relativeToSub)
|
||||
{
|
||||
SetPhysicsBodyPosition();
|
||||
}
|
||||
public override void FlipY(bool relativeToSub)
|
||||
{
|
||||
SetPhysicsBodyPosition();
|
||||
}
|
||||
|
||||
public override void OnMapLoaded()
|
||||
{
|
||||
base.OnMapLoaded();
|
||||
PhysicsBody.SetTransformIgnoreContacts(item.SimPosition, 0.0f);
|
||||
SetPhysicsBodyPosition(true);
|
||||
PhysicsBody.Submarine = item.Submarine;
|
||||
}
|
||||
|
||||
private bool OnCollision(Fixture sender, Fixture other, Contact contact)
|
||||
{
|
||||
if (LevelTrigger.GetEntity(other) is not Entity entity) { return false; }
|
||||
if (!LevelTrigger.IsTriggeredByEntity(entity, triggeredBy, mustBeOnSpecificSub: (!MoveOutsideSub, item.Submarine))) { return false; }
|
||||
if (!LevelTrigger.IsTriggeredByEntity(entity, triggeredBy, triggerSpeciesOrGroup, conditionals, mustBeOnSpecificSub: (!MoveOutsideSub, item.Submarine))) { return false; }
|
||||
triggerers.Add(entity);
|
||||
return true;
|
||||
}
|
||||
@@ -174,7 +318,12 @@ namespace Barotrauma.Items.Components
|
||||
item.SetTransform(ConvertUnits.ToSimUnits(item.WorldPosition), item.Rotation);
|
||||
item.CurrentHull = null;
|
||||
item.Submarine = null;
|
||||
PhysicsBody.SetTransformIgnoreContacts(item.SimPosition, 0.0f);
|
||||
SetPhysicsBodyPosition();
|
||||
PhysicsBody.Submarine = item.Submarine;
|
||||
}
|
||||
else if (item.body is { BodyType: BodyType.Dynamic })
|
||||
{
|
||||
SetPhysicsBodyPosition();
|
||||
PhysicsBody.Submarine = item.Submarine;
|
||||
}
|
||||
|
||||
@@ -187,11 +336,34 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
TriggeredOnce = true;
|
||||
IsActive = false;
|
||||
triggerers.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
TriggerActive = triggerers.Any();
|
||||
if (TriggerActive && conditionals != null)
|
||||
{
|
||||
switch (conditionals.LogicalOperator)
|
||||
{
|
||||
case PropertyConditional.LogicalOperatorType.And:
|
||||
{
|
||||
if (triggerers.Any(t => !PropertyConditional.CheckConditionals((ISerializableEntity)t, conditionals.Conditionals, conditionals.LogicalOperator)))
|
||||
{
|
||||
// Some of the conditionals doesn't match
|
||||
IsActive = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case PropertyConditional.LogicalOperatorType.Or:
|
||||
{
|
||||
if (triggerers.None(t => !PropertyConditional.CheckConditionals((ISerializableEntity)t, conditionals.Conditionals, conditionals.LogicalOperator)))
|
||||
{
|
||||
// None of the conditionals match
|
||||
IsActive = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ForceFluctuation && TriggerActive && (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer))
|
||||
{
|
||||
@@ -208,7 +380,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
foreach (Entity triggerer in triggerers)
|
||||
{
|
||||
LevelTrigger.ApplyStatusEffects(statusEffects, item.WorldPosition, triggerer, deltaTime, statusEffectTargets);
|
||||
LevelTrigger.ApplyStatusEffects(statusEffects, item.WorldPosition, triggerer, deltaTime, statusEffectTargets, targetItem: Item);
|
||||
|
||||
if (triggerer is IDamageable damageable)
|
||||
{
|
||||
@@ -260,12 +432,17 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
private void ApplyForce(PhysicsBody body, float multiplier = 1.0f)
|
||||
{
|
||||
Vector2 diff = ConvertUnits.ToDisplayUnits(PhysicsBody.SimPosition - body.SimPosition);
|
||||
Vector2 diff = ConvertUnits.ToDisplayUnits(item.SimPosition - body.SimPosition);
|
||||
if (diff.LengthSquared() < 0.0001f) { return; }
|
||||
float distanceFactor = DistanceBasedForce ? LevelTrigger.GetDistanceFactor(body, PhysicsBody, RadiusInDisplayUnits) : 1.0f;
|
||||
if (distanceFactor <= 0.0f) { return; }
|
||||
Vector2 force = distanceFactor * (CurrentForceFluctuation * Force) * Vector2.Normalize(diff) * multiplier;
|
||||
if (force.LengthSquared() < 0.01f) { return; }
|
||||
if (body.Mass < 1)
|
||||
{
|
||||
//restrict the force if the body is very light, otherwise it can end up moving at a speed that breaks physics
|
||||
force *= body.Mass;
|
||||
}
|
||||
body.ApplyForce(force);
|
||||
}
|
||||
|
||||
@@ -273,14 +450,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (PhysicsBody != null)
|
||||
{
|
||||
if (ignoreContacts)
|
||||
{
|
||||
PhysicsBody.SetTransformIgnoreContacts(PhysicsBody.SimPosition + ConvertUnits.ToSimUnits(amount), 0.0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
PhysicsBody.SetTransform(PhysicsBody.SimPosition + ConvertUnits.ToSimUnits(amount), 0.0f);
|
||||
}
|
||||
SetPhysicsBodyPosition(ignoreContacts);
|
||||
PhysicsBody.Submarine = item.Submarine;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ namespace Barotrauma.Items.Components
|
||||
partial class Turret : Powered, IDrawableComponent, IServerSerializable
|
||||
{
|
||||
private Sprite barrelSprite, railSprite;
|
||||
private Sprite barrelSpriteBroken, railSpriteBroken;
|
||||
private readonly List<(Sprite sprite, Vector2 position)> chargeSprites = new List<(Sprite sprite, Vector2 position)>();
|
||||
private readonly List<Sprite> spinningBarrelSprites = new List<Sprite>();
|
||||
|
||||
@@ -89,6 +90,8 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
private List<LightComponent> lightComponents;
|
||||
|
||||
private Projectile lastProjectile;
|
||||
|
||||
private readonly bool isSlowTurret;
|
||||
|
||||
public float Rotation { get; private set; }
|
||||
@@ -320,9 +323,22 @@ namespace Barotrauma.Items.Components
|
||||
[Serialize("", IsPropertySaveable.Yes, description: "[Auto Operate] Group or SpeciesName that the AI ignores when the turret is operated automatically."),
|
||||
Editable(TransferToSwappedItem = true)]
|
||||
public Identifier FriendlyTag { get; private set; }
|
||||
|
||||
|
||||
[Serialize("OwnSub", IsPropertySaveable.Yes, description: "[Auto Operate] Team that the turret considers friendly."),
|
||||
Editable(TransferToSwappedItem = true)]
|
||||
public TeamType FriendlyTeamType { get; private set; }
|
||||
|
||||
public enum TeamType
|
||||
{
|
||||
OwnSub,
|
||||
Team1,
|
||||
Team2,
|
||||
FriendlyNPC,
|
||||
NoneTeam
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
private const string SetAutoOperateConnection = "set_auto_operate";
|
||||
private const string ToggleAutoOperateConnection = "toggle_auto_operate";
|
||||
|
||||
@@ -330,7 +346,7 @@ namespace Barotrauma.Items.Components
|
||||
: base(item, element)
|
||||
{
|
||||
IsActive = true;
|
||||
|
||||
|
||||
foreach (var subElement in element.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
@@ -341,6 +357,12 @@ namespace Barotrauma.Items.Components
|
||||
case "railsprite":
|
||||
railSprite = new Sprite(subElement);
|
||||
break;
|
||||
case "barrelspritebroken":
|
||||
barrelSpriteBroken = new Sprite(subElement);
|
||||
break;
|
||||
case "railspritebroken":
|
||||
railSpriteBroken = new Sprite(subElement);
|
||||
break;
|
||||
case "chargesprite":
|
||||
chargeSprites.Add((new Sprite(subElement), subElement.GetAttributeVector2("chargetarget", Vector2.Zero)));
|
||||
break;
|
||||
@@ -376,6 +398,7 @@ namespace Barotrauma.Items.Components
|
||||
base.OnMapLoaded();
|
||||
if (loadedRotationLimits.HasValue) { RotationLimits = loadedRotationLimits.Value; }
|
||||
if (loadedBaseRotation.HasValue) { BaseRotation = loadedBaseRotation.Value; }
|
||||
if (loadedFriendlyTeamType.HasValue) { FriendlyTeamType = loadedFriendlyTeamType.Value; }
|
||||
targetRotation = Rotation;
|
||||
UpdateTransformedBarrelPos();
|
||||
if (!AllowAutoOperateWithWiring &&
|
||||
@@ -779,6 +802,12 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
if (launchedProjectile != null || LaunchWithoutProjectile)
|
||||
{
|
||||
if (launchedProjectile?.Item.GetComponent<Rope>() != null &&
|
||||
lastProjectile?.Item.GetComponent<Rope>() is { SnapWhenWeaponFiredAgain: true } rope)
|
||||
{
|
||||
rope.Snap();
|
||||
}
|
||||
|
||||
if (projectiles.Any())
|
||||
{
|
||||
foreach (Projectile projectile in projectiles)
|
||||
@@ -806,6 +835,8 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
lastProjectile = launchedProjectile;
|
||||
|
||||
#if SERVER
|
||||
if (character != null && launchedProjectile != null)
|
||||
{
|
||||
@@ -900,6 +931,10 @@ namespace Barotrauma.Items.Components
|
||||
projectileComponent.Attack.DamageMultiplier = (1f * DamageMultiplier) + (TinkeringDamageIncrease * tinkeringStrength);
|
||||
}
|
||||
projectileComponent.Use(null, LaunchImpulse);
|
||||
if (item.GetComponent<TriggerComponent>() is { } trigger)
|
||||
{
|
||||
projectileComponent.IgnoredBodies.Add(trigger.PhysicsBody.FarseerBody);
|
||||
}
|
||||
projectile.GetComponent<Rope>()?.Attach(item, projectile);
|
||||
projectileComponent.User = user;
|
||||
|
||||
@@ -1027,7 +1062,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
if (TargetItems)
|
||||
{
|
||||
foreach (Item targetItem in Item.ItemList)
|
||||
foreach (Item targetItem in Item.TurretTargetItems)
|
||||
{
|
||||
if (!IsValidTarget(targetItem)) { continue; }
|
||||
float priority = isSlowTurret ? targetItem.Prefab.AISlowTurretPriority : targetItem.Prefab.AITurretPriority;
|
||||
@@ -1117,7 +1152,7 @@ namespace Barotrauma.Items.Components
|
||||
if (target is Hull targetHull)
|
||||
{
|
||||
Vector2 barrelDir = GetBarrelDir();
|
||||
if (!MathUtils.GetLineRectangleIntersection(item.WorldPosition, item.WorldPosition + barrelDir * AIRange, targetHull.WorldRect, out _))
|
||||
if (!MathUtils.GetLineWorldRectangleIntersection(item.WorldPosition, item.WorldPosition + barrelDir * AIRange, targetHull.WorldRect, out _))
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -1351,7 +1386,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
// 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, ignoreHuskDisguising: true)) { continue; }
|
||||
// Don't shoot at captured enemies.
|
||||
if (enemy.LockHands) { continue; }
|
||||
float dist = Vector2.DistanceSquared(enemy.WorldPosition, item.WorldPosition);
|
||||
@@ -1371,7 +1406,7 @@ namespace Barotrauma.Items.Components
|
||||
closestDistance = dist / priority;
|
||||
currentTarget = closestEnemy;
|
||||
}
|
||||
foreach (Item targetItem in Item.ItemList)
|
||||
foreach (Item targetItem in Item.TurretTargetItems)
|
||||
{
|
||||
if (!IsValidTarget(targetItem)) { continue; }
|
||||
float priority = isSlowTurret ? targetItem.Prefab.AISlowTurretPriority : targetItem.Prefab.AITurretPriority;
|
||||
@@ -1675,22 +1710,34 @@ namespace Barotrauma.Items.Components
|
||||
return true;
|
||||
}
|
||||
|
||||
private CharacterTeamType GetFriendlyTeam()
|
||||
{
|
||||
return FriendlyTeamType switch
|
||||
{
|
||||
TeamType.Team1 => CharacterTeamType.Team1,
|
||||
TeamType.Team2 => CharacterTeamType.Team2,
|
||||
TeamType.FriendlyNPC => CharacterTeamType.FriendlyNPC,
|
||||
TeamType.NoneTeam => CharacterTeamType.None,
|
||||
TeamType.OwnSub => item.Submarine?.TeamID ?? CharacterTeamType.None,
|
||||
_ => throw new NotImplementedException(),
|
||||
};
|
||||
}
|
||||
|
||||
private bool IsValidTargetForAutoOperate(Character target, Identifier friendlyTag)
|
||||
{
|
||||
if (!friendlyTag.IsEmpty)
|
||||
{
|
||||
if (target.SpeciesName.Equals(friendlyTag) || target.Group.Equals(friendlyTag)) { return false; }
|
||||
}
|
||||
|
||||
CharacterTeamType friendlyTeam = GetFriendlyTeam();
|
||||
|
||||
if (target.TeamID == friendlyTeam) { return false; }
|
||||
|
||||
bool isHuman = target.IsHuman || target.Group == CharacterPrefab.HumanSpeciesName;
|
||||
if (isHuman)
|
||||
{
|
||||
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;
|
||||
}
|
||||
return TargetHumans;
|
||||
return !target.IsOnFriendlyTeam(friendlyTeam) && TargetHumans;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1719,7 +1766,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (user != null)
|
||||
{
|
||||
if (HumanAIController.IsFriendly(user, targetCharacter))
|
||||
if (HumanAIController.IsFriendly(user, targetCharacter, ignoreHuskDisguising: true))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -1739,8 +1786,15 @@ namespace Barotrauma.Items.Components
|
||||
Submarine sub = e.Submarine ?? e as Submarine;
|
||||
if (sub == null) { return true; }
|
||||
if (sub == Item.Submarine) { return false; }
|
||||
if (sub.Info.IsOutpost || sub.Info.IsWreck || sub.Info.IsBeacon) { return false; }
|
||||
if (sub.TeamID == Item.Submarine.TeamID) { return false; }
|
||||
if (sub.Info.IsOutpost || sub.Info.IsWreck || sub.Info.IsBeacon || sub.Info.IsRuin) { return false; }
|
||||
if (item.Submarine == null)
|
||||
{
|
||||
if (sub.TeamID == GetFriendlyTeam()) { return false; }
|
||||
}
|
||||
else
|
||||
{
|
||||
if (sub.TeamID == Item.Submarine.TeamID) { return false; }
|
||||
}
|
||||
}
|
||||
else if (targetBody.UserData is not Voronoi2.VoronoiCell { IsDestructible: true })
|
||||
{
|
||||
@@ -1758,6 +1812,8 @@ namespace Barotrauma.Items.Components
|
||||
customPredicate: (Fixture f) =>
|
||||
{
|
||||
if (f.UserData is Item i && i.GetComponent<Turret>() != null) { return false; }
|
||||
if (f.CollidesWith == Physics.CollisionNone) { return false; }
|
||||
if (f.Body.UserData == item) { return false; }
|
||||
if (f.UserData is Hull) { return false; }
|
||||
return !item.StaticFixtures.Contains(f);
|
||||
});
|
||||
@@ -1792,6 +1848,8 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
barrelSprite?.Remove(); barrelSprite = null;
|
||||
railSprite?.Remove(); railSprite = null;
|
||||
barrelSpriteBroken?.Remove(); barrelSpriteBroken = null;
|
||||
railSpriteBroken?.Remove(); railSpriteBroken = null;
|
||||
|
||||
#if CLIENT
|
||||
crosshairSprite?.Remove(); crosshairSprite = null;
|
||||
@@ -1974,11 +2032,27 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
private Vector2? loadedRotationLimits;
|
||||
private float? loadedBaseRotation;
|
||||
private TeamType? loadedFriendlyTeamType;
|
||||
|
||||
public override void Load(ContentXElement componentElement, bool usePrefabValues, IdRemap idRemap, bool isItemSwap)
|
||||
{
|
||||
base.Load(componentElement, usePrefabValues, idRemap, isItemSwap);
|
||||
loadedRotationLimits = componentElement.GetAttributeVector2("rotationlimits", RotationLimits);
|
||||
loadedBaseRotation = componentElement.GetAttributeFloat("baserotation", componentElement.Parent.GetAttributeFloat("rotation", BaseRotation));
|
||||
|
||||
//backwards compatibility: previously None made the turret consider the team the submarine/outpost belongs as the friendly team
|
||||
if (componentElement.GetAttribute("FriendlyTeam") is { } friendlyTeamAttribute)
|
||||
{
|
||||
CharacterTeamType friendlyTeam = XMLExtensions.ParseEnumValue(friendlyTeamAttribute.Value, defaultValue: CharacterTeamType.None, friendlyTeamAttribute);
|
||||
loadedFriendlyTeamType = friendlyTeam switch
|
||||
{
|
||||
CharacterTeamType.None => TeamType.OwnSub,
|
||||
CharacterTeamType.Team1 => TeamType.Team1,
|
||||
CharacterTeamType.Team2 => TeamType.Team2,
|
||||
CharacterTeamType.FriendlyNPC => TeamType.FriendlyNPC,
|
||||
_ => throw new NotImplementedException()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnItemLoaded()
|
||||
@@ -1991,6 +2065,8 @@ namespace Barotrauma.Items.Components
|
||||
if (item.FlippedX) { FlipX(relativeToSub: false); }
|
||||
if (item.FlippedY) { FlipY(relativeToSub: false); }
|
||||
}
|
||||
UpdateTransformedBarrelPos();
|
||||
UpdateLightComponents();
|
||||
}
|
||||
|
||||
public void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData = null)
|
||||
|
||||
@@ -165,10 +165,11 @@ namespace Barotrauma
|
||||
{
|
||||
if (element.DoesAttributeReferenceFileNameAlone("texture"))
|
||||
{
|
||||
var basePrefab = WearableComponent.Item.Prefab.ParentPrefab ?? WearableComponent.Item.Prefab;
|
||||
string textureName = element.GetAttributeString("texture", "");
|
||||
return ContentPath.FromRaw(
|
||||
element.ContentPackage,
|
||||
$"{Path.GetDirectoryName(WearableComponent.Item.Prefab.FilePath)}/{textureName}");
|
||||
$"{Path.GetDirectoryName(basePrefab.FilePath)}/{textureName}");
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -269,6 +270,13 @@ namespace Barotrauma
|
||||
|
||||
IsInitialized = true;
|
||||
}
|
||||
|
||||
public void Remove()
|
||||
{
|
||||
Sprite?.Remove();
|
||||
//don't use the Picker setter, because it causes the sprite to be re-initialized for "no character"
|
||||
_picker = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -350,7 +358,7 @@ namespace Barotrauma.Items.Components
|
||||
damageModifiers = new List<DamageModifier>();
|
||||
SkillModifiers = new Dictionary<Identifier, float>();
|
||||
|
||||
int spriteCount = element.Elements().Count(x => x.Name.ToString() == "sprite");
|
||||
int spriteCount = element.Elements().Count(x => x.Name.ToString().ToLowerInvariant() == "sprite");
|
||||
Variants = element.GetAttributeInt("variants", 0);
|
||||
variant = Rand.Range(1, Variants + 1, Rand.RandSync.ServerAndClient);
|
||||
wearableSprites = new WearableSprite[spriteCount];
|
||||
@@ -569,8 +577,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
foreach (WearableSprite wearableSprite in wearableSprites)
|
||||
{
|
||||
wearableSprite?.Sprite?.Remove();
|
||||
wearableSprite.Picker = null;
|
||||
wearableSprite.Remove();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user