Merge branch 'dev' of https://github.com/Regalis11/Barotrauma into unstable

This commit is contained in:
EvilFactory
2022-09-29 12:13:55 -03:00
602 changed files with 19759 additions and 16312 deletions
@@ -46,7 +46,7 @@ namespace Barotrauma.Items.Components
private float forceLockTimer;
//if the submarine isn't in the correct position to lock within this time after docking has been activated,
//force the sub to the correct position
const float ForceLockDelay = 1.0f;
const float ForceLockDelay = 1.0f;
public int DockingDir { get; set; }
@@ -81,12 +81,18 @@ namespace Barotrauma.Items.Components
set;
}
[Editable, Serialize(DirectionType.None, IsPropertySaveable.No, description: "Which direction the port is allowed to dock in. For example, \"Top\" would mean the port can dock to another port above it.\n"+
[Editable, Serialize(DirectionType.None, IsPropertySaveable.No, description: "Which direction the port is allowed to dock in. For example, \"Top\" would mean the port can dock to another port above it.\n" +
"Normally there's no need to touch this setting, but if you notice the docking position is incorrect (for example due to some unusual docking port configuration without hulls or doors), you can use this to enforce the direction.")]
public DirectionType ForceDockingDirection { get; set; }
public DockingPort DockingTarget { get; private set; }
/// <summary>
/// Can be used by status effects
/// </summary>
public bool AtStartExit => Item.Submarine is { AtStartExit: true};
public bool AtEndExit => Item.Submarine is { AtEndExit: true };
public Door Door { get; private set; }
public bool Docked
@@ -116,6 +122,8 @@ namespace Barotrauma.Items.Components
get { return joint is WeldJoint || DockingTarget?.joint is WeldJoint; }
}
public bool AnotherPortInProximity => FindAdjacentPort() != null;
/// <summary>
/// Automatically cleared after docking -> no need to unregister
/// </summary>
@@ -989,7 +997,7 @@ namespace Barotrauma.Items.Components
dockingState = MathHelper.Lerp(dockingState, 0.0f, deltaTime * 10.0f);
if (dockingState < 0.01f) { docked = false; }
item.SendSignal("0", "state_out");
item.SendSignal((FindAdjacentPort() != null) ? "1" : "0", "proximity_sensor");
item.SendSignal(AnotherPortInProximity ? "1" : "0", "proximity_sensor");
}
else
{
@@ -1191,7 +1199,7 @@ namespace Barotrauma.Items.Components
//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
if (tryingToToggleOutpostDocking && signal.sender == null)
if (GameMain.NetworkMember != null && tryingToToggleOutpostDocking && signal.sender == null)
{
if (allowOutpostAutoDocking == AllowOutpostAutoDocking.Ask)
{
@@ -153,7 +153,7 @@ namespace Barotrauma.Items.Components
{
if (GetAvailableInstantaneousBatteryPower() >= PowerConsumption)
{
List<PowerContainer> batteries = GetConnectedBatteries();
List<PowerContainer> batteries = GetDirectlyConnectedBatteries();
float neededPower = PowerConsumption;
while (neededPower > 0.0001f && batteries.Count > 0)
{
@@ -203,7 +203,7 @@ namespace Barotrauma.Items.Components
foreach ((Character character, Node node) in charactersInRange)
{
if (character == null || character.Removed) { continue; }
character.ApplyAttack(null, node.WorldPosition, attack, 1.0f);
character.ApplyAttack(null, node.WorldPosition, attack, MathHelper.Clamp(Voltage, 1.0f, MaxOverVoltageFactor));
}
}
DischargeProjSpecific();
@@ -132,8 +132,8 @@ namespace Barotrauma.Items.Components
public static FoliageConfig CreateRandomConfig(int maxVariants, float minScale, float maxScale, Random? random = null)
{
int flowerVariant = Growable.RandomInt(0, maxVariants, random);
float flowerScale = (float) Growable.RandomDouble(minScale, maxScale, random);
float flowerRotation = (float) Growable.RandomDouble(0, MathHelper.TwoPi, random);
float flowerScale = (float)Growable.RandomDouble(minScale, maxScale, random);
float flowerRotation = (float)Growable.RandomDouble(0, MathHelper.TwoPi, random);
return new FoliageConfig { Variant = flowerVariant, Scale = flowerScale, Rotation = flowerRotation };
}
}
@@ -169,10 +169,10 @@ namespace Barotrauma.Items.Components
{
const float limit = 1.0f;
growthStep = value;
VineStep = Math.Min((float) Math.Pow(value, 2), limit);
VineStep = Math.Min((float)Math.Pow(value, 2), limit);
if (value > limit)
{
FlowerStep = Math.Min((float) Math.Pow(value - limit, 2), limit);
FlowerStep = Math.Min((float)Math.Pow(value - limit, 2), limit);
}
}
}
@@ -260,7 +260,7 @@ namespace Barotrauma.Items.Components
{
if (Type == VineTileType.Stem) { return; }
Type = (VineTileType) Sides;
Type = (VineTileType)Sides;
}
/// <summary>
@@ -310,7 +310,7 @@ namespace Barotrauma.Items.Components
public bool IsSideBlocked(TileSide side) => BlockedSides.HasFlag(side) || Sides.HasFlag(side);
public static Rectangle CreatePlantRect(Vector2 pos) => new Rectangle((int) pos.X - Size / 2, (int) pos.Y + Size / 2, Size, Size);
public static Rectangle CreatePlantRect(Vector2 pos) => new Rectangle((int)pos.X - Size / 2, (int)pos.Y + Size / 2, Size, Size);
}
internal static class GrowthSideExtension
@@ -318,7 +318,7 @@ namespace Barotrauma.Items.Components
// K&R algorithm for counting how many bits are set in a bit field
public static int Count(this TileSide side)
{
int n = (int) side;
int n = (int)side;
int count = 0;
while (n != 0)
{
@@ -607,7 +607,7 @@ namespace Barotrauma.Items.Components
#if CLIENT
foreach (VineTile vine in Vines)
{
vine.DecayDelay = (float) RandomDouble(0f, 30f);
vine.DecayDelay = (float)RandomDouble(0f, 30f);
}
#endif
#if SERVER
@@ -742,7 +742,7 @@ namespace Barotrauma.Items.Components
{
var (x, y, z, w) = GrowthWeights;
float[] weights = { x, y, z, w };
int index = (int) Math.Log2((int) side);
int index = (int)Math.Log2((int)side);
if (MathUtils.NearlyEqual(weights[index], 0f))
{
oldVines.FailedGrowthAttempts++;
@@ -778,7 +778,7 @@ namespace Barotrauma.Items.Components
foreach (VineTile otherVine in Vines)
{
var (distX, distY) = pos - otherVine.Position;
int absDistX = (int) Math.Abs(distX), absDistY = (int) Math.Abs(distY);
int absDistX = (int)Math.Abs(distX), absDistY = (int)Math.Abs(distY);
// check if the tile is within the with or height distance from us but ignore diagonals
if (absDistX > newVine.Rect.Width || absDistY > newVine.Rect.Height || absDistX > 0 && absDistY > 0) { continue; }
@@ -872,10 +872,10 @@ namespace Barotrauma.Items.Components
foreach (VineTile vine in Vines)
{
XElement vineElement = new XElement("Vine");
vineElement.Add(new XAttribute("sides", (int) vine.Sides));
vineElement.Add(new XAttribute("blockedsides", (int) vine.BlockedSides));
vineElement.Add(new XAttribute("sides", (int)vine.Sides));
vineElement.Add(new XAttribute("blockedsides", (int)vine.BlockedSides));
vineElement.Add(new XAttribute("pos", XMLExtensions.Vector2ToString(vine.Position)));
vineElement.Add(new XAttribute("tile", (int) vine.Type));
vineElement.Add(new XAttribute("tile", (int)vine.Type));
vineElement.Add(new XAttribute("failedattempts", vine.FailedGrowthAttempts));
#if SERVER
vineElement.Add(new XAttribute("growthscale", Decayed ? 1.0f : 2.0f));
@@ -902,10 +902,10 @@ namespace Barotrauma.Items.Components
{
if (element.Name.ToString().Equals("vine", StringComparison.OrdinalIgnoreCase))
{
VineTileType type = (VineTileType) element.GetAttributeInt("tile", 0);
VineTileType type = (VineTileType)element.GetAttributeInt("tile", 0);
Vector2 pos = element.GetAttributeVector2("pos", Vector2.Zero);
TileSide sides = (TileSide) element.GetAttributeInt("sides", 0);
TileSide blockedSides = (TileSide) element.GetAttributeInt("blockedsides", 0);
TileSide sides = (TileSide)element.GetAttributeInt("sides", 0);
TileSide blockedSides = (TileSide)element.GetAttributeInt("blockedsides", 0);
int failedAttempts = element.GetAttributeInt("failedattempts", 0);
float growthscale = element.GetAttributeFloat("growthscale", 0f);
int flowerConfig = element.GetAttributeInt("flowerconfig", FoliageConfig.EmptyConfigValue);
@@ -295,12 +295,11 @@ namespace Barotrauma.Items.Components
if (attachable)
{
DeattachFromWall();
if (body != null)
{
item.body = body;
}
DeattachFromWall();
}
if (Pusher != null) { Pusher.Enabled = false; }
@@ -619,6 +618,10 @@ namespace Barotrauma.Items.Components
#if CLIENT
item.DrawDepthOffset = SpriteDepthWhenDropped - item.SpriteDepth;
#endif
foreach (LightComponent light in item.GetComponents<LightComponent>())
{
light.CheckIfNeedsUpdate();
}
}
public override void ParseMsg()
@@ -691,12 +694,30 @@ namespace Barotrauma.Items.Components
{
item.Drop(character);
item.SetTransform(ConvertUnits.ToSimUnits(GetAttachPosition(character)), 0.0f, findNewHull: false);
//the light source won't get properly updated if lighting is disabled (even though the light sprite is still drawn when lighting is disabled)
//so let's ensure the light source is up-to-date
RefreshLightSources(item);
}
AttachToWall();
}
return true;
static void RefreshLightSources(Item item)
{
item.body?.UpdateDrawPosition();
foreach (var light in item.GetComponents<LightComponent>())
{
light.SetLightSourceTransform();
}
item.GetComponent<ItemContainer>()?.SetContainedItemPositions();
foreach (var containedItem in item.ContainedItems)
{
RefreshLightSources(containedItem);
}
}
}
public override bool SecondaryUse(float deltaTime, Character character = null)
{
return true;
@@ -1,12 +1,11 @@
using Barotrauma.Networking;
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.Linq;
using System.Xml.Linq;
namespace Barotrauma.Items.Components
{
@@ -64,7 +63,7 @@ namespace Barotrauma.Items.Components
/// <summary>
/// Defines items that boost the weapon functionality, like battery cell for stun batons.
/// </summary>
public readonly Identifier[] PreferredContainedItems;
public readonly ImmutableHashSet<Identifier> PreferredContainedItems;
public MeleeWeapon(Item item, ContentXElement element)
: base(item, element)
@@ -79,7 +78,7 @@ namespace Barotrauma.Items.Components
}
item.IsShootable = true;
item.RequireAimToUse = element.Parent.GetAttributeBool("requireaimtouse", true);
PreferredContainedItems = element.GetAttributeIdentifierArray("preferredcontaineditems", Array.Empty<Identifier>());
PreferredContainedItems = element.GetAttributeIdentifierArray("preferredcontaineditems", Array.Empty<Identifier>()).ToImmutableHashSet();
}
public override void Equip(Character character)
@@ -292,7 +291,6 @@ namespace Barotrauma.Items.Components
item.body.PhysEnabled = false;
}
private bool OnCollision(Fixture f1, Fixture f2, Contact contact)
{
if (User == null || User.Removed)
@@ -390,15 +388,17 @@ namespace Barotrauma.Items.Components
User = null;
return;
}
float damageMultiplier = 1 + User.GetStatValue(StatTypes.MeleeAttackMultiplier);
damageMultiplier *= 1.0f + item.GetQualityModifier(Quality.StatType.StrikingPowerMultiplier);
Limb targetLimb = target.UserData as Limb;
Character targetCharacter = targetLimb?.character ?? target.UserData as Character;
GameMain.LuaCs.Hook.Call("meleeWeapon.handleImpact", this, target);
if (Attack != null)
{
Attack.SetUser(User);
Attack.DamageMultiplier = 1 + User.GetStatValue(StatTypes.MeleeAttackMultiplier);
Attack.DamageMultiplier *= 1.0f + item.GetQualityModifier(Quality.StatType.StrikingPowerMultiplier);
Attack.DamageMultiplier = damageMultiplier;
if (targetLimb != null)
{
@@ -420,7 +420,18 @@ namespace Barotrauma.Items.Components
else if (target.UserData is Item targetItem && targetItem.Prefab.DamagedByMeleeWeapons && targetItem.Condition > 0)
{
if (targetItem.Removed) { return; }
Attack.DoDamage(User, targetItem, item.WorldPosition, 1.0f);
var attackResult = Attack.DoDamage(User, targetItem, item.WorldPosition, 1.0f);
#if CLIENT
if (attackResult.Damage > 0.0f)
{
Character.Controlled?.UpdateHUDProgressBar(targetItem,
targetItem.WorldPosition,
targetItem.Condition / targetItem.MaxCondition,
emptyColor: GUIStyle.HealthBarColorLow,
fullColor: GUIStyle.HealthBarColorHigh,
textTag: targetItem.Name);
}
#endif
}
else if (target.UserData is Holdable holdable && holdable.CanPush)
{
@@ -460,7 +471,7 @@ namespace Barotrauma.Items.Components
if (targetCharacter != null) //TODO: Allow OnUse to happen on structures too maybe??
{
ApplyStatusEffects(success ? ActionType.OnUse : ActionType.OnFailure, 1.0f, targetCharacter, targetLimb, user: User);
ApplyStatusEffects(success ? ActionType.OnUse : ActionType.OnFailure, 1.0f, targetCharacter, targetLimb, user: User, afflictionMultiplier: damageMultiplier);
}
if (DeleteOnUse)
@@ -165,31 +165,32 @@ namespace Barotrauma.Items.Components
pickTimer = 0.0f;
while (pickTimer < requiredTime && Screen.Selected != GameMain.SubEditorScreen)
{
//cancel if the item is currently selected
//attempting to pick does not select the item, so if it is selected at this point, another ItemComponent
//must have been selected and we should not keep deattaching (happens when for example interacting with
//an electrical component while holding both a screwdriver and a wrench).
if (picker.SelectedConstruction == item ||
picker.IsKeyDown(InputType.Aim) ||
!picker.CanInteractWith(item) ||
item.Removed || item.ParentInventory != null)
if (!CoroutineManager.Paused)
{
StopPicking(picker);
yield return CoroutineStatus.Success;
}
//cancel if the item is currently selected
//attempting to pick does not select the item, so if it is selected at this point, another ItemComponent
//must have been selected and we should not keep deattaching (happens when for example interacting with
//an electrical component while holding both a screwdriver and a wrench).
if (picker.IsAnySelectedItem(item) ||
picker.IsKeyDown(InputType.Aim) ||
!picker.CanInteractWith(item) ||
item.Removed || item.ParentInventory != null)
{
StopPicking(picker);
yield return CoroutineStatus.Success;
}
#if CLIENT
Character.Controlled?.UpdateHUDProgressBar(
this,
item.WorldPosition,
pickTimer / requiredTime,
GUIStyle.Red, GUIStyle.Green,
!string.IsNullOrWhiteSpace(PickingMsg) ? PickingMsg : this is Door ? "progressbar.opening" : "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");
#endif
picker.AnimController.UpdateUseItem(true, item.WorldPosition + new Vector2(0.0f, 100.0f) * ((pickTimer / 10.0f) % 0.1f));
pickTimer += CoroutineManager.DeltaTime;
picker.AnimController.UpdateUseItem(!picker.IsClimbing, item.WorldPosition + new Vector2(0.0f, 100.0f) * ((pickTimer / 10.0f) % 0.1f));
pickTimer += CoroutineManager.DeltaTime;
}
yield return CoroutineStatus.Running;
}
@@ -208,7 +209,7 @@ namespace Barotrauma.Items.Components
{
if (picker != null)
{
picker.AnimController.Anim = AnimController.Animation.None;
picker.AnimController.StopUsingItem();
picker.PickingItem = null;
}
if (pickingCoroutine != null)
@@ -286,7 +287,7 @@ namespace Barotrauma.Items.Components
public virtual void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData = null)
{
msg.Write(activePicker?.ID ?? (ushort)0);
msg.WriteUInt16(activePicker?.ID ?? (ushort)0);
}
public virtual void ClientEventRead(IReadMessage msg, float sendingTime)
@@ -18,6 +18,7 @@ namespace Barotrauma.Items.Components
};
private readonly HashSet<Identifier> fixableEntities;
private readonly HashSet<Identifier> nonFixableEntities;
private Vector2 pickedPosition;
private float activeTimer;
@@ -135,6 +136,7 @@ namespace Barotrauma.Items.Components
}
fixableEntities = new HashSet<Identifier>();
nonFixableEntities = new HashSet<Identifier>();
foreach (var subElement in element.Elements())
{
switch (subElement.Name.ToString().ToLowerInvariant())
@@ -147,7 +149,16 @@ namespace Barotrauma.Items.Components
}
else
{
fixableEntities.Add(subElement.GetAttributeIdentifier("identifier", ""));
foreach (Identifier id in subElement.GetAttributeIdentifierArray("identifier", Array.Empty<Identifier>()))
{
fixableEntities.Add(id);
}
}
break;
case "nonfixable":
foreach (Identifier id in subElement.GetAttributeIdentifierArray("identifier", Array.Empty<Identifier>()))
{
nonFixableEntities.Add(id);
}
break;
}
@@ -523,6 +534,7 @@ namespace Barotrauma.Items.Components
if (sectionIndex < 0) { return false; }
if (!fixableEntities.Contains("structure") && !fixableEntities.Contains(targetStructure.Prefab.Identifier)) { return true; }
if (nonFixableEntities.Contains(targetStructure.Prefab.Identifier) || nonFixableEntities.Any(t => targetStructure.Tags.Contains(t))) { return false; }
ApplyStatusEffectsOnTarget(user, deltaTime, ActionType.OnUse, structure: targetStructure);
FixStructureProjSpecific(user, deltaTime, targetStructure, sectionIndex);
@@ -111,6 +111,12 @@ namespace Barotrauma.Items.Components
}
ApplyStatusEffects(ActionType.OnActive, deltaTime, picker);
//return if the status effect got rid of the picker somehow
if (picker == null || picker.Removed || !picker.HeldItems.Contains(item))
{
IsActive = false;
return;
}
if (item.body.Dir != picker.AnimController.Dir) { item.FlipX(relativeToSub: false); }
@@ -231,8 +231,6 @@ namespace Barotrauma.Items.Components
set;
}
public virtual bool RecreateGUIOnResolutionChange => false;
/// <summary>
/// How useful the item is in combat? Used by AI to decide which item it should use as a weapon. For the sake of clarity, use a value between 0 and 100 (not enforced).
/// </summary>
@@ -399,7 +397,7 @@ namespace Barotrauma.Items.Components
RelatedItem ri = RelatedItem.Load(element, returnEmpty, item.Name);
if (ri != null)
{
if (ri.Identifiers.Length == 0)
if (ri.Identifiers.Count == 0)
{
DisabledRequiredItems.Add(ri);
}
@@ -818,7 +816,7 @@ namespace Barotrauma.Items.Components
}
}
public void ApplyStatusEffects(ActionType type, float deltaTime, Character character = null, Limb targetLimb = null, Entity useTarget = null, Character user = null, Vector2? worldPosition = null, float applyOnUserFraction = 0.0f)
public void ApplyStatusEffects(ActionType type, float deltaTime, Character character = null, Limb targetLimb = null, Entity useTarget = null, Character user = null, Vector2? worldPosition = null, float afflictionMultiplier = 1.0f, float applyOnUserFraction = 0.0f)
{
if (statusEffectLists == null) { return; }
@@ -830,13 +828,14 @@ namespace Barotrauma.Items.Components
{
if (broken && !effect.AllowWhenBroken && effect.type != ActionType.OnBroken) { continue; }
if (user != null) { effect.SetUser(user); }
effect.AfflictionMultiplier = afflictionMultiplier;
item.ApplyStatusEffect(effect, type, deltaTime, character, targetLimb, useTarget, isNetworkEvent: false, checkCondition: false, worldPosition);
if (user != null && applyOnUserFraction > 0.0f && effect.HasTargetType(StatusEffect.TargetType.Character))
{
effect.AfflictionMultiplier = applyOnUserFraction;
item.ApplyStatusEffect(effect, type, deltaTime, user, targetLimb == null ? null : user.AnimController.GetLimb(targetLimb.type), useTarget, false, false, worldPosition);
effect.AfflictionMultiplier = 1.0f;
}
effect.AfflictionMultiplier = 1.0f;
reducesCondition |= effect.ReducesItemCondition();
}
//if any of the effects reduce the item's condition, set the user for OnBroken effects as well
@@ -1072,7 +1071,7 @@ namespace Barotrauma.Items.Components
AIObjectiveContainItem containObjective = null;
if (character.AIController is HumanAIController aiController)
{
containObjective = new AIObjectiveContainItem(character, container.ContainableItemIdentifiers.ToArray(), container, currentObjective.objectiveManager, spawnItemIfNotFound: spawnItemIfNotFound)
containObjective = new AIObjectiveContainItem(character, container.ContainableItemIdentifiers, container, currentObjective.objectiveManager, spawnItemIfNotFound: spawnItemIfNotFound)
{
ItemCount = itemCount,
Equip = equip,
@@ -49,9 +49,11 @@ namespace Barotrauma.Items.Components
}
}
public readonly NamedEvent<ItemContainer> OnContainedItemsChanged = new NamedEvent<ItemContainer>();
private bool alwaysContainedItemsSpawned;
public ItemInventory Inventory;
public readonly ItemInventory Inventory;
private readonly List<ActiveContainedItem> activeContainedItems = new List<ActiveContainedItem>();
@@ -187,6 +189,16 @@ namespace Barotrauma.Items.Components
[Serialize(false, IsPropertySaveable.No)]
public bool RemoveContainedItemsOnDeconstruct { get; set; }
/// <summary>
/// Can be used by status effects to lock the inventory
/// </summary>
public bool Locked
{
get { return Inventory.Locked; }
set { Inventory.Locked = value; }
}
private readonly ImmutableArray<SlotRestrictions> slotRestrictions;
readonly List<ISerializableEntity> targets = new List<ISerializableEntity>();
@@ -214,9 +226,7 @@ namespace Barotrauma.Items.Components
}
private ImmutableHashSet<Identifier> containableItemIdentifiers;
public IEnumerable<Identifier> ContainableItemIdentifiers => containableItemIdentifiers;
public override bool RecreateGUIOnResolutionChange => true;
public ImmutableHashSet<Identifier> ContainableItemIdentifiers => containableItemIdentifiers;
public List<RelatedItem> ContainableItems { get; }
@@ -347,6 +357,7 @@ namespace Barotrauma.Items.Components
//no need to Update() if this item has no statuseffects and no physics body
IsActive = activeContainedItems.Count > 0 || Inventory.AllItems.Any(it => it.body != null);
OnContainedItemsChanged.Invoke(this);
}
public override void Move(Vector2 amount, bool ignoreContacts = false)
@@ -360,6 +371,7 @@ namespace Barotrauma.Items.Components
//deactivate if the inventory is empty
IsActive = activeContainedItems.Count > 0 || Inventory.AllItems.Any(it => it.body != null);
OnContainedItemsChanged.Invoke(this);
}
public bool CanBeContained(Item item)
@@ -496,7 +508,7 @@ namespace Barotrauma.Items.Components
return false;
}
}
if (AutoInteractWithContained && character.SelectedConstruction == null)
if (AutoInteractWithContained && character.SelectedItem == null)
{
foreach (Item contained in Inventory.AllItems)
{
@@ -510,7 +522,15 @@ namespace Barotrauma.Items.Components
var abilityItem = new AbilityItemContainer(item);
character.CheckTalents(AbilityEffectType.OnOpenItemContainer, abilityItem);
return base.Select(character);
if (item.ParentInventory?.Owner == character)
{
//can't select ItemContainers in the character's inventory (the inventory is drawn by hovering the cursor over the inventory slot, not as a GUIFrame)
return false;
}
else
{
return base.Select(character);
}
}
public override bool Pick(Character picker)
@@ -19,8 +19,7 @@ namespace Barotrauma.Items.Components
public override bool Select(Character character)
{
if (character == null || character.LockHands || character.Removed || !(character.AnimController is HumanoidAnimController)) return false;
character.AnimController.Anim = AnimController.Animation.Climbing;
character.AnimController.StartClimbing();
return true;
}
@@ -36,6 +36,7 @@ namespace Barotrauma.Items.Components
private readonly List<LimbPos> limbPositions = new List<LimbPos>();
private Direction dir;
public Direction Direction => dir;
//the position where the user walks to when using the controller
//(relative to the position of the item)
@@ -128,6 +129,13 @@ namespace Barotrauma.Items.Components
set;
}
[Serialize(false, IsPropertySaveable.No, description: "If true, other items can be used simultaneously.")]
public bool IsSecondaryItem
{
get;
private set;
}
public Controller(Item item, ContentXElement element)
: base(item, element)
{
@@ -150,7 +158,7 @@ namespace Barotrauma.Items.Components
if (user == null
|| user.Removed
|| user.SelectedConstruction != item
|| !user.IsAnySelectedItem(item)
|| item.ParentInventory != null
|| !user.CanInteractWith(item)
|| (UsableIn == UseEnvironment.Water && !user.AnimController.InWater)
@@ -165,7 +173,7 @@ namespace Barotrauma.Items.Components
return;
}
user.AnimController.Anim = AnimController.Animation.UsingConstruction;
user.AnimController.StartUsingItem();
if (userPos != Vector2.Zero)
{
@@ -186,32 +194,34 @@ namespace Barotrauma.Items.Components
}
else
{
diff.Y = 0.0f;
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient && user != Character.Controlled)
// Secondary items (like ladders or chairs) will control the character position over primary items
// Only control the character position if the character doesn't have another secondary item already controlling it
if (!user.HasSelectedAnotherSecondaryItem(Item))
{
if (Math.Abs(diff.X) > 20.0f)
diff.Y = 0.0f;
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient && user != Character.Controlled)
{
//wait for the character to walk to the correct position
return;
if (Math.Abs(diff.X) > 20.0f)
{
//wait for the character to walk to the correct position
return;
}
else if (Math.Abs(diff.X) > 0.1f)
{
//aim to keep the collider at the correct position once close enough
user.AnimController.Collider.LinearVelocity = new Vector2(
diff.X * 0.1f,
user.AnimController.Collider.LinearVelocity.Y);
}
}
else if (Math.Abs(diff.X) > 0.1f)
{
//aim to keep the collider at the correct position once close enough
user.AnimController.Collider.LinearVelocity = new Vector2(
diff.X * 0.1f,
user.AnimController.Collider.LinearVelocity.Y);
}
}
else
{
if (Math.Abs(diff.X) > 10.0f)
else if (Math.Abs(diff.X) > 10.0f)
{
user.AnimController.TargetMovement = Vector2.Normalize(diff);
user.AnimController.TargetDir = diff.X > 0.0f ? Direction.Right : Direction.Left;
return;
}
user.AnimController.TargetMovement = Vector2.Zero;
}
user.AnimController.TargetMovement = Vector2.Zero;
UserInCorrectPosition = true;
}
}
@@ -220,9 +230,16 @@ namespace Barotrauma.Items.Components
if (limbPositions.Count == 0) { return; }
user.AnimController.Anim = AnimController.Animation.UsingConstruction;
user.AnimController.StartUsingItem();
user.AnimController.ResetPullJoints();
if (user.SelectedItem != null)
{
user.AnimController.ResetPullJoints(l => l.IsLowerBody);
}
else
{
user.AnimController.ResetPullJoints();
}
if (dir != 0) { user.AnimController.TargetDir = dir; }
@@ -230,7 +247,10 @@ namespace Barotrauma.Items.Components
{
Limb limb = user.AnimController.GetLimb(lb.LimbType);
if (limb == null || !limb.body.Enabled) { continue; }
// Don't move lower body limbs if there's another selected secondary item that should control them
if (limb.IsLowerBody && user.HasSelectedAnotherSecondaryItem(Item)) { continue; }
// Don't move hands if there's a selected primary item that should control them
if (!limb.IsLowerBody && Item == user.SelectedSecondaryItem && user.SelectedItem != null) { continue; }
if (lb.AllowUsingLimb)
{
switch (lb.LimbType)
@@ -247,12 +267,9 @@ namespace Barotrauma.Items.Components
break;
}
}
limb.Disabled = true;
Vector2 worldPosition = new Vector2(item.WorldRect.X, item.WorldRect.Y) + lb.Position * item.Scale;
Vector2 diff = worldPosition - limb.WorldPosition;
limb.PullJointEnabled = true;
limb.PullJointWorldAnchorB = limb.SimPosition + ConvertUnits.ToSimUnits(diff);
}
@@ -266,9 +283,7 @@ namespace Barotrauma.Items.Components
{
return false;
}
if (user == null || user.Removed ||
user.SelectedConstruction != item || !user.CanInteractWith(item))
if (user == null || user.Removed || !user.IsAnySelectedItem(item) || !user.CanInteractWith(item))
{
user = null;
return false;
@@ -290,46 +305,44 @@ namespace Barotrauma.Items.Components
}
lastUsed = Timing.TotalTime;
ApplyStatusEffects(ActionType.OnUse, 1.0f, activator);
ApplyStatusEffects(ActionType.OnUse, 1.0f, activator);
return true;
}
public override bool SecondaryUse(float deltaTime, Character character = null)
{
if (this.user != character)
if (user != character)
{
return false;
}
if (this.user == null || character.Removed ||
this.user.SelectedConstruction != item || !character.CanInteractWith(item))
if (user == null || character.Removed || !user.IsAnySelectedItem(item) || !character.CanInteractWith(item))
{
user = null;
return false;
}
if (character == null)
{
this.user = null;
return false;
}
if (character == null) return false;
focusTarget = GetFocusTarget();
if (focusTarget == null)
{
Vector2 centerPos = new Vector2(item.WorldRect.Center.X, item.WorldRect.Center.Y);
Vector2 offset = character.CursorWorldPosition - centerPos;
offset.Y = -offset.Y;
targetRotation = MathUtils.WrapAngleTwoPi(MathUtils.VectorToAngle(offset));
return false;
}
character.ViewTarget = focusTarget;
#if CLIENT
if (character == Character.Controlled && cam != null)
{
Lights.LightManager.ViewTarget = focusTarget;
cam.TargetPos = focusTarget.WorldPosition;
cam.OffsetAmount = MathHelper.Lerp(cam.OffsetAmount, (focusTarget as Item).Prefab.OffsetOnSelected * focusTarget.OffsetOnSelectedMultiplier, deltaTime * 10.0f);
HideHUDs(true);
}
@@ -338,16 +351,12 @@ namespace Barotrauma.Items.Components
if (!character.IsRemotePlayer || character.ViewTarget == focusTarget)
{
Vector2 centerPos = new Vector2(focusTarget.WorldRect.Center.X, focusTarget.WorldRect.Center.Y);
Turret turret = focusTarget.GetComponent<Turret>();
if (turret != null)
if (focusTarget.GetComponent<Turret>() is { } turret)
{
centerPos = new Vector2(focusTarget.WorldRect.X + turret.TransformedBarrelPos.X, focusTarget.WorldRect.Y - turret.TransformedBarrelPos.Y);
}
Vector2 offset = character.CursorWorldPosition - centerPos;
offset.Y = -offset.Y;
targetRotation = MathUtils.WrapAngleTwoPi(MathUtils.VectorToAngle(offset));
}
return true;
@@ -425,9 +434,10 @@ namespace Barotrauma.Items.Components
humanoidAnim.LockFlippingUntil = (float)Timing.TotalTime + 0.5f;
}
if (character.SelectedConstruction == this.item) { character.SelectedConstruction = null; }
if (character.SelectedItem == item) { character.SelectedItem = null; }
if (character.SelectedSecondaryItem == item) { character.SelectedSecondaryItem = null; }
character.AnimController.Anim = AnimController.Animation.None;
character.AnimController.StopUsingItem();
if (character == Character.Controlled)
{
HideHUDs(false);
@@ -3,6 +3,7 @@ using Barotrauma.Extensions;
using Barotrauma.Networking;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Xml.Linq;
@@ -39,8 +40,6 @@ namespace Barotrauma.Items.Components
[Editable, Serialize(1.0f, IsPropertySaveable.Yes)]
public float DeconstructionSpeed { get; set; }
public override bool RecreateGUIOnResolutionChange => true;
public Deconstructor(Item item, ContentXElement element)
: base(item, element)
{
@@ -62,11 +61,18 @@ namespace Barotrauma.Items.Components
inputContainer = containers[0];
outputContainer = containers[1];
#if CLIENT
Identifier eventIdentifier = new Identifier(nameof(Deconstructor));
inputContainer.OnContainedItemsChanged.RegisterOverwriteExisting(eventIdentifier, OnItemSlotsChanged);
#endif
OnItemLoadedProjSpecific();
}
partial void OnItemLoadedProjSpecific();
partial void OnItemSlotsChanged(ItemContainer container);
public override void Update(float deltaTime, Camera cam)
{
MoveInputQueue();
@@ -88,7 +94,7 @@ namespace Barotrauma.Items.Components
ApplyStatusEffects(ActionType.OnActive, deltaTime, null);
progressTimer += deltaTime * Math.Min(powerConsumption <= 0.0f ? 1 : Voltage, 1.0f);
progressTimer += deltaTime * Math.Min(powerConsumption <= 0.0f ? 1 : Voltage, MaxOverVoltageFactor);
float tinkeringStrength = 0f;
if (repairable.IsTinkering)
@@ -114,7 +120,7 @@ namespace Barotrauma.Items.Components
{
if ((Entity.Spawner?.IsInRemoveQueue(targetItem) ?? false) || !inputContainer.Inventory.AllItems.Contains(targetItem)) { continue; }
var validDeconstructItems = targetItem.Prefab.DeconstructItems.Where(it =>
(it.RequiredDeconstructor.Length == 0 || it.RequiredDeconstructor.Any(r => item.HasTag(r) || item.Prefab.Identifier == r)) &&
it.IsValidDeconstructor(item) &&
(it.RequiredOtherItem.Length == 0 || it.RequiredOtherItem.Any(r => items.Any(it => it != targetItem && (it.HasTag(r) || it.Prefab.Identifier == r))))).ToList();
ProcessItem(targetItem, items, validDeconstructItems, allowRemove: validDeconstructItems.Any() || !targetItem.Prefab.DeconstructItems.Any());
@@ -132,9 +138,7 @@ namespace Barotrauma.Items.Components
var targetItem = inputContainer.Inventory.LastOrDefault();
if (targetItem == null) { return; }
var validDeconstructItems = targetItem.Prefab.DeconstructItems.Where(it =>
it.RequiredDeconstructor.Length == 0 || it.RequiredDeconstructor.Any(r => item.HasTag(r) || item.Prefab.Identifier == r)).ToList();
var validDeconstructItems = targetItem.Prefab.DeconstructItems.Where(it => it.IsValidDeconstructor(item)).ToList();
float deconstructTime = validDeconstructItems.Any() ? targetItem.Prefab.DeconstructTime / (DeconstructionSpeed * deconstructionSpeedModifier) : 1.0f;
progressState = Math.Min(progressTimer / deconstructTime, 1.0f);
@@ -197,18 +201,18 @@ namespace Barotrauma.Items.Components
foreach (DeconstructItem deconstructProduct in products)
{
CreateDeconstructProduct(deconstructProduct, inputItems, amountMultiplier);
CreateDeconstructProduct(deconstructProduct, inputItems, (int)(amountMultiplier * deconstructProduct.Amount));
}
}
else
{
foreach (DeconstructItem deconstructProduct in validDeconstructItems)
{
CreateDeconstructProduct(deconstructProduct, inputItems, amountMultiplier);
CreateDeconstructProduct(deconstructProduct, inputItems, (int)(amountMultiplier * deconstructProduct.Amount));
}
}
void CreateDeconstructProduct(DeconstructItem deconstructProduct, IEnumerable<Item> inputItems, float amountMultiplier)
void CreateDeconstructProduct(DeconstructItem deconstructProduct, IEnumerable<Item> inputItems, int amount)
{
float percentageHealth = targetItem.Condition / targetItem.MaxCondition;
@@ -276,11 +280,11 @@ namespace Barotrauma.Items.Components
user.CheckTalents(AbilityEffectType.OnItemDeconstructedInventory, itemDeconstructedInventory);
}
int amount = (int)amountMultiplier;
for (int i = 0; i < amount; i++)
{
Entity.Spawner.AddItemToSpawnQueue(itemPrefab, outputContainer.Inventory, condition, onSpawned: (Item spawnedItem) =>
{
spawnedItem.SpawnedInCurrentOutpost = item.SpawnedInCurrentOutpost;
spawnedItem.StolenDuringRound = targetItem.StolenDuringRound;
spawnedItem.AllowStealing = targetItem.AllowStealing;
for (int i = 0; i < outputContainer.Capacity; i++)
@@ -117,7 +117,7 @@ namespace Barotrauma.Items.Components
Force = MathHelper.Lerp(force, (Voltage < MinVoltage) ? 0.0f : targetForce, deltaTime * 10.0f);
if (Math.Abs(Force) > 1.0f)
{
float voltageFactor = MinVoltage <= 0.0f ? 1.0f : Math.Min(Voltage, 1.0f);
float voltageFactor = MinVoltage <= 0.0f ? 1.0f : Math.Min(Voltage, MaxOverVoltageFactor);
float currForce = force * voltageFactor;
float condition = item.Condition / item.MaxCondition;
// Broken engine makes more noise.
@@ -76,8 +76,6 @@ namespace Barotrauma.Items.Components
get { return outputContainer; }
}
public override bool RecreateGUIOnResolutionChange => true;
private float progressState;
private readonly Dictionary<uint, int> fabricationLimits = new Dictionary<uint, int>();
@@ -305,7 +303,7 @@ namespace Barotrauma.Items.Components
float fabricationSpeedIncrease = 1f + tinkeringStrength * TinkeringSpeedIncrease;
timeUntilReady -= deltaTime * fabricationSpeedIncrease * Math.Min(powerConsumption <= 0 ? 1 : Voltage, 1.0f);
timeUntilReady -= deltaTime * fabricationSpeedIncrease * Math.Min(powerConsumption <= 0 ? 1 : Voltage, MaxOverVoltageFactor);
UpdateRequiredTimeProjSpecific();
@@ -371,8 +369,7 @@ namespace Barotrauma.Items.Components
var availableItems = availableIngredients[requiredPrefab.Identifier];
var availableItem = availableItems.FirstOrDefault(potentialPrefab =>
{
return potentialPrefab.ConditionPercentage >= requiredItem.MinCondition * 100.0f &&
potentialPrefab.ConditionPercentage <= requiredItem.MaxCondition * 100.0f;
return requiredItem.IsConditionSuitable(potentialPrefab.ConditionPercentage);
});
if (availableItem == null) { continue; }
@@ -556,8 +553,20 @@ namespace Barotrauma.Items.Components
const int MaxCraftingSkill = 100;
//having a higher-than-100 skill (e.g. due to talents) gives +1 quality
quality += fabricatedItem.RequiredSkills.All(s => user.GetSkillLevel(s.Identifier) >= MaxCraftingSkill) ? 1 : 0;
quality += FabricationDegreeOfSuccess(user, fabricatedItem.RequiredSkills) >= 0.5f ? 1 : 0;
foreach (var skill in fabricatedItem.RequiredSkills)
{
//+1 quality if the character's skill level is >20% from the min requirement towards max skill
//e.g. if the skill requirement is 10 -> 28
//40 -> 52
//90 -> 92
float skillRequirement = MathHelper.Lerp(skill.Level, MaxCraftingSkill, 0.2f);
if (user.GetSkillLevel(skill.Identifier) > skillRequirement)
{
quality += 1;
}
}
return quality;
}
@@ -604,8 +613,7 @@ namespace Barotrauma.Items.Components
var availablePrefabs = availableIngredients[requiredPrefab.Identifier];
foreach (Item availablePrefab in availablePrefabs)
{
if (availablePrefab.ConditionPercentage / 100.0f >= requiredItem.MinCondition &&
availablePrefab.ConditionPercentage / 100.0f <= requiredItem.MaxCondition)
if (requiredItem.IsConditionSuitable(availablePrefab.ConditionPercentage))
{
availablePrefabsAmount++;
}
@@ -637,10 +645,13 @@ namespace Barotrauma.Items.Components
if (skills.Length == 0) { return 1.0f; }
if (character == null) { return 0.0f; }
float skillSum = (from t in skills let characterLevel = character.GetSkillLevel(t.Identifier) select (characterLevel - (t.Level * SkillRequirementMultiplier))).Sum();
float average = skillSum / skills.Length;
return (average + 100.0f) / 2.0f / 100.0f;
float minDegreeOfSuccess = 1.0f;
foreach (var skill in skills)
{
float characterLevel = character.GetSkillLevel(skill.Identifier);
minDegreeOfSuccess = Math.Min(minDegreeOfSuccess, (characterLevel - (skill.Level * SkillRequirementMultiplier) + 100.0f) / 2.0f / 100.0f);
}
return minDegreeOfSuccess;
}
public override float GetSkillMultiplier()
@@ -648,13 +659,16 @@ namespace Barotrauma.Items.Components
return SkillRequirementMultiplier;
}
private readonly HashSet<Inventory> linkedInventories = new HashSet<Inventory>();
private void RefreshAvailableIngredients()
{
Character user = this.user;
#if CLIENT
user ??= Character.Controlled;
#endif
linkedInventories.Clear();
List<Item> itemList = new List<Item>();
itemList.AddRange(inputContainer.Inventory.AllItems);
foreach (MapEntity linkedTo in item.linkedTo)
@@ -674,6 +688,7 @@ namespace Barotrauma.Items.Components
itemContainer = deconstructor.OutputContainer;
}
linkedInventories.Add(itemContainer.Inventory);
itemList.AddRange(itemContainer.Inventory.AllItems);
}
}
@@ -688,6 +703,7 @@ namespace Barotrauma.Items.Components
if (user?.Inventory != null)
{
itemList.AddRange(user.Inventory.AllItems);
linkedInventories.Add(user.Inventory);
}
availableIngredients.Clear();
foreach (Item item in itemList)
@@ -720,9 +736,7 @@ namespace Barotrauma.Items.Components
var availablePrefabs = availableIngredients[requiredPrefab.Identifier];
var availablePrefab = availablePrefabs.FirstOrDefault(potentialPrefab =>
{
return !usedItems.Contains(potentialPrefab) &&
potentialPrefab.ConditionPercentage >= requiredItem.MinCondition * 100.0f &&
potentialPrefab.ConditionPercentage <= requiredItem.MaxCondition * 100.0f;
return !usedItems.Contains(potentialPrefab) && requiredItem.IsConditionSuitable(potentialPrefab.ConditionPercentage);
});
if (availablePrefab == null) { continue; }
@@ -2,7 +2,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Items.Components
{
@@ -52,7 +51,7 @@ namespace Barotrauma.Items.Components
return;
}
CurrFlow = Math.Min(PowerConsumption > 0 ? Voltage : 1.0f, 1.0f) * generatedAmount * 100.0f;
CurrFlow = Math.Min(PowerConsumption > 0 ? Voltage : 1.0f, MaxOverVoltageFactor) * generatedAmount * 100.0f;
float conditionMult = item.Condition / item.MaxCondition;
//100% condition = 100% oxygen
//50% condition = 25% oxygen
@@ -130,7 +130,7 @@ namespace Barotrauma.Items.Components
if (item.CurrentHull == null) { return; }
float powerFactor = Math.Min(currPowerConsumption <= 0.0f || MinVoltage <= 0.0f ? 1.0f : Voltage, 1.0f);
float powerFactor = Math.Min(currPowerConsumption <= 0.0f || MinVoltage <= 0.0f ? 1.0f : Voltage, MaxOverVoltageFactor);
currFlow = flowPercentage / 100.0f * maxFlow * powerFactor;
@@ -11,6 +11,8 @@ namespace Barotrauma.Items.Components
{
const float NetworkUpdateIntervalHigh = 0.5f;
const float TemperatureBoostAmount = 20;
//the rate at which the reactor is being run on (higher rate -> higher temperature)
private float fissionRate;
@@ -46,6 +48,11 @@ namespace Barotrauma.Items.Components
private Vector2 optimalFissionRate, allowedFissionRate;
private Vector2 optimalTurbineOutput, allowedTurbineOutput;
private float? signalControlledTargetFissionRate, signalControlledTargetTurbineOutput;
private double lastReceivedFissionRateSignalTime, lastReceivedTurbineOutputSignalTime;
private float temperatureBoost;
private bool _powerOn;
[Serialize(defaultValue: false, isSaveable: IsPropertySaveable.Yes)]
@@ -226,7 +233,7 @@ namespace Barotrauma.Items.Components
// (= bots turn autotemp back on when leaving the reactor)
if (LastAIUser != null)
{
if (LastAIUser.SelectedConstruction != item && LastAIUser.CanInteractWith(item))
if (LastAIUser.SelectedItem != item && LastAIUser.CanInteractWith(item))
{
AutoTemp = true;
if (GameMain.NetworkMember?.IsServer ?? false) { unsentChanges = true; }
@@ -241,6 +248,34 @@ namespace Barotrauma.Items.Components
}
#endif
if (signalControlledTargetFissionRate.HasValue && lastReceivedFissionRateSignalTime > Timing.TotalTime - 1)
{
TargetFissionRate = adjustValueWithoutOverShooting(TargetFissionRate, signalControlledTargetFissionRate.Value, deltaTime * 5.0f);
#if CLIENT
FissionRateScrollBar.BarScroll = TargetFissionRate / 100.0f;
#endif
}
else
{
signalControlledTargetFissionRate = null;
}
if (signalControlledTargetTurbineOutput.HasValue && lastReceivedTurbineOutputSignalTime > Timing.TotalTime - 1)
{
TargetTurbineOutput = adjustValueWithoutOverShooting(TargetTurbineOutput, signalControlledTargetTurbineOutput.Value, deltaTime * 5.0f);
#if CLIENT
TurbineOutputScrollBar.BarScroll = TargetTurbineOutput / 100.0f;
#endif
}
else
{
signalControlledTargetTurbineOutput = null;
}
static float adjustValueWithoutOverShooting(float current, float target, float speed)
{
return target < current ? Math.Max(target, current - speed) : Math.Min(target, current + speed);
}
prevAvailableFuel = AvailableFuel;
ApplyStatusEffects(ActionType.OnActive, deltaTime, null);
@@ -270,7 +305,10 @@ namespace Barotrauma.Items.Components
float temperatureDiff = (heatAmount - turbineOutput) - Temperature;
Temperature += MathHelper.Clamp(Math.Sign(temperatureDiff) * 10.0f * deltaTime, -Math.Abs(temperatureDiff), Math.Abs(temperatureDiff));
//if (item.InWater && AvailableFuel < 100.0f) Temperature -= 12.0f * deltaTime;
temperatureBoost = adjustValueWithoutOverShooting(temperatureBoost, 0.0f, deltaTime);
#if CLIENT
temperatureBoostUpButton.Enabled = temperatureBoostDownButton.Enabled = Math.Abs(temperatureBoost) < TemperatureBoostAmount * 0.9f;
#endif
FissionRate = MathHelper.Lerp(fissionRate, Math.Min(TargetFissionRate, AvailableFuel), deltaTime);
@@ -438,7 +476,7 @@ namespace Barotrauma.Items.Components
private float GetGeneratedHeat(float fissionRate)
{
return fissionRate * (prevAvailableFuel / 100.0f) * 2.0f;
return fissionRate * (prevAvailableFuel / 100.0f) * 2.0f + temperatureBoost;
}
/// <summary>
@@ -486,13 +524,15 @@ namespace Barotrauma.Items.Components
if (temperature > allowedTemperature.Y)
{
item.SendSignal("1", "meltdown_warning");
//faster meltdown if the item is in a bad condition
meltDownTimer += MathHelper.Lerp(deltaTime * 2.0f, deltaTime, item.Condition / item.MaxCondition);
if (meltDownTimer > MeltdownDelay)
if (!item.InvulnerableToDamage)
{
MeltDown();
return;
//faster meltdown if the item is in a bad condition
meltDownTimer += MathHelper.Lerp(deltaTime * 2.0f, deltaTime, item.Condition / item.MaxCondition);
if (meltDownTimer > MeltdownDelay)
{
MeltDown();
return;
}
}
}
else
@@ -505,7 +545,7 @@ namespace Barotrauma.Items.Components
{
fireTimer += MathHelper.Lerp(deltaTime * 2.0f, deltaTime, item.Condition / item.MaxCondition);
#if SERVER
if (fireTimer > Math.Min(5.0f, FireDelay / 2) && blameOnBroken?.Character?.SelectedConstruction == item)
if (fireTimer > Math.Min(5.0f, FireDelay / 2) && blameOnBroken?.Character?.SelectedItem == item)
{
GameMain.Server.KarmaManager.OnReactorOverHeating(item, blameOnBroken.Character, deltaTime);
}
@@ -705,7 +745,7 @@ namespace Barotrauma.Items.Components
{
if (lastUser != null && lastUser != character && lastUser != LastAIUser)
{
if (lastUser.SelectedConstruction == item && character.IsOnPlayerTeam)
if (lastUser.SelectedItem == item && character.IsOnPlayerTeam)
{
character.Speak(TextManager.Get("DialogReactorTaken").Value, null, 0.0f, "reactortaken".ToIdentifier(), 10.0f);
}
@@ -797,30 +837,31 @@ namespace Barotrauma.Items.Components
AutoTemp = false;
TargetFissionRate = 0.0f;
TargetTurbineOutput = 0.0f;
if (GameMain.NetworkMember?.IsServer ?? false) { unsentChanges = true; }
registerUnsentChanges();
}
break;
case "set_fissionrate":
if (PowerOn && float.TryParse(signal.value, NumberStyles.Float, CultureInfo.InvariantCulture, out float newFissionRate))
{
TargetFissionRate = MathHelper.Clamp(newFissionRate, 0.0f, 100.0f);
if (GameMain.NetworkMember?.IsServer ?? false) { unsentChanges = true; }
#if CLIENT
FissionRateScrollBar.BarScroll = TargetFissionRate / 100.0f;
#endif
signalControlledTargetFissionRate = MathHelper.Clamp(newFissionRate, 0.0f, 100.0f);
lastReceivedFissionRateSignalTime = Timing.TotalTime;
registerUnsentChanges();
}
break;
case "set_turbineoutput":
if (PowerOn && float.TryParse(signal.value, NumberStyles.Float, CultureInfo.InvariantCulture, out float newTurbineOutput))
{
TargetTurbineOutput = MathHelper.Clamp(newTurbineOutput, 0.0f, 100.0f);
if (GameMain.NetworkMember?.IsServer ?? false) { unsentChanges = true; }
#if CLIENT
TurbineOutputScrollBar.BarScroll = TargetTurbineOutput / 100.0f;
#endif
signalControlledTargetTurbineOutput = MathHelper.Clamp(newTurbineOutput, 0.0f, 100.0f);
lastReceivedTurbineOutputSignalTime = Timing.TotalTime;
registerUnsentChanges();
}
break;
}
void registerUnsentChanges()
{
if (GameMain.NetworkMember is { IsServer: true }) { unsentChanges = true; }
}
}
}
}
@@ -64,6 +64,8 @@ namespace Barotrauma.Items.Components
private const float MinZoom = 1.0f, MaxZoom = 4.0f;
private float zoom = 1.0f;
/// <remarks>Accessed through event actions. Do not remove even if there are no references in code.</remarks>
public bool UseDirectionalPing => useDirectionalPing;
private bool useDirectionalPing = false;
private Vector2 pingDirection = new Vector2(1.0f, 0.0f);
private bool useMineralScanner;
@@ -113,13 +115,34 @@ namespace Barotrauma.Items.Components
set;
}
[Editable, Serialize(false, IsPropertySaveable.No, description: "Does the sonar have mineral scanning mode. " +
"Only available in-game when the Item has no Steering component.")]
public bool HasMineralScanner { get; set; }
private bool hasMineralScanner;
[Editable, Serialize(false, IsPropertySaveable.No, description: "Does the sonar have mineral scanning mode. ")]
public bool HasMineralScanner
{
get => hasMineralScanner;
set
{
#if CLIENT
if (controlContainer != null && !hasMineralScanner && value)
{
AddMineralScannerSwitchToGUI();
}
#endif
hasMineralScanner = value;
}
}
public float Zoom
{
get { return zoom; }
set
{
zoom = MathHelper.Clamp(value, MinZoom, MaxZoom);
#if CLIENT
zoomSlider.BarScroll = MathUtils.InverseLerp(MinZoom, MaxZoom, zoom);
#endif
}
}
public Mode CurrentMode
@@ -144,8 +167,6 @@ namespace Barotrauma.Items.Components
}
}
public override bool RecreateGUIOnResolutionChange => true;
public Sonar(Item item, ContentXElement element)
: base(item, element)
{
@@ -396,17 +417,17 @@ namespace Barotrauma.Items.Components
public void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData = null)
{
msg.Write(currentMode == Mode.Active);
msg.WriteBoolean(currentMode == Mode.Active);
if (currentMode == Mode.Active)
{
msg.WriteRangedSingle(zoom, MinZoom, MaxZoom, 8);
msg.Write(useDirectionalPing);
msg.WriteBoolean(useDirectionalPing);
if (useDirectionalPing)
{
float pingAngle = MathUtils.WrapAngleTwoPi(MathUtils.VectorToAngle(pingDirection));
msg.WriteRangedSingle(MathUtils.InverseLerp(0.0f, MathHelper.TwoPi, pingAngle), 0.0f, 1.0f, 8);
}
msg.Write(useMineralScanner);
msg.WriteBoolean(useMineralScanner);
}
}
}
@@ -166,8 +166,6 @@ namespace Barotrauma.Items.Components
set { posToMaintain = value; }
}
public override bool RecreateGUIOnResolutionChange => true;
struct ObstacleDebugInfo
{
public Vector2 Point1;
@@ -301,7 +299,7 @@ namespace Barotrauma.Items.Components
float userSkill = 0.0f;
if (user != null && controlledSub != null &&
(user.SelectedConstruction == item || item.linkedTo.Contains(user.SelectedConstruction)))
(user.SelectedItem == item || item.linkedTo.Contains(user.SelectedItem)))
{
userSkill = user.GetSkillLevel("helm") / 100.0f;
}
@@ -333,7 +331,7 @@ namespace Barotrauma.Items.Components
{
showIceSpireWarning = false;
if (user != null && user.Info != null &&
user.SelectedConstruction == item &&
user.SelectedItem == item &&
controlledSub != null && controlledSub.Velocity.LengthSquared() > 0.01f)
{
IncreaseSkillLevel(user, deltaTime);
@@ -389,7 +387,7 @@ namespace Barotrauma.Items.Components
}
// if our tactical AI pilot has left, revert back to maintaining position
if (navigateTactically && (user == null || user.SelectedConstruction != item))
if (navigateTactically && (user == null || user.SelectedItem != item))
{
navigateTactically = false;
AIRamTimer = 0f;
@@ -722,7 +720,7 @@ namespace Barotrauma.Items.Components
character.AIController.SteeringManager.Reset();
if (objective.Override)
{
if (user != character && user != null && user.SelectedConstruction == item && character.IsOnPlayerTeam)
if (user != character && user != null && user.SelectedItem == item && character.IsOnPlayerTeam)
{
character.Speak(TextManager.Get("DialogSteeringTaken").Value, null, 0.0f, "steeringtaken".ToIdentifier(), 10.0f);
}
@@ -98,7 +98,7 @@ namespace Barotrauma.Items.Components
set { maxRechargeSpeed = Math.Max(value, 1.0f); }
}
[Editable, Serialize(10.0f, IsPropertySaveable.Yes, description: "The current recharge speed of the device.")]
[Editable, Serialize(0.0f, IsPropertySaveable.Yes, description: "The current recharge speed of the device.")]
public float RechargeSpeed
{
get { return rechargeSpeed; }
@@ -117,9 +117,6 @@ namespace Barotrauma.Items.Components
[Serialize(false, IsPropertySaveable.Yes, description: "If true, the recharge speed (and power consumption) of the device goes up exponentially as the recharge rate is increased.")]
public bool ExponentialRechargeSpeed { get; set; }
[Editable(minValue: 0.0f, maxValue: 10.0f, decimals: 2), Serialize(0.5f, IsPropertySaveable.Yes)]
public float RechargeAdjustSpeed { get; set; }
private float efficiency;
[Editable(minValue: 0.0f, maxValue: 1.0f, decimals: 2), Serialize(0.95f, IsPropertySaveable.Yes, description: "The amount of power you can get out of a item relative to the amount of power that's put into it.")]
public float Efficiency
@@ -243,8 +243,13 @@ namespace Barotrauma.Items.Components
//damage the item if voltage is too high (except if running as a client)
float prevCondition = item.Condition;
item.Condition -= deltaTime * 10.0f;
//some randomness to prevent all junction boxes from breaking at the same time
if (Rand.Range(0.0f, 1.0f) < 0.01f)
{
//damaged boxes are more sensitive to overvoltage (also preventing all boxes from breaking at the same time)
float conditionFactor = MathHelper.Lerp(5.0f, 1.0f, item.Condition / item.MaxCondition);
item.Condition -= deltaTime * Rand.Range(10.0f, 500.0f) * conditionFactor;
}
if (item.Condition <= 0.0f && prevCondition > 0.0f)
{
overloadCooldownTimer = OverloadCooldown;
@@ -273,7 +278,8 @@ namespace Barotrauma.Items.Components
public override float GetConnectionPowerOut(Connection conn, float power, PowerRange minMaxPower, float load)
{
return conn == powerOut ? PowerConsumption + ExtraLoad : 0;
//not used in the vanilla game (junction boxes or relays don't output power)
return conn == powerOut ? MathHelper.Max(-(PowerConsumption + ExtraLoad), 0) : 0;
}
public override bool Pick(Character picker)
@@ -94,6 +94,11 @@ namespace Barotrauma.Items.Components
protected Connection powerIn, powerOut;
/// <summary>
/// Maximum voltage factor when the device is being overvolted. I.e. how many times more effectively the device can function when it's being overvolted
/// </summary>
protected const float MaxOverVoltageFactor = 2.0f;
protected virtual PowerPriority Priority { get { return PowerPriority.Default; } }
[Editable, Serialize(0.5f, IsPropertySaveable.Yes, description: "The minimum voltage required for the device to function. " +
@@ -685,43 +690,21 @@ namespace Barotrauma.Items.Components
}
/// <summary>
/// Efficient method to retrieve the batteries connected to the device
/// Returns a list of batteries directly connected to the item
/// </summary>
/// <returns>All connected PowerContainers</returns>
protected List<PowerContainer> GetConnectedBatteries(bool outputOnly = true)
protected List<PowerContainer> GetDirectlyConnectedBatteries()
{
List<PowerContainer> batteries = new List<PowerContainer>();
GridInfo supplyingGrid = null;
//Determine supplying grid, prefer PowerIn connection
if (powerIn != null)
if (item.Connections == null || powerIn == null) { return batteries; }
foreach (Connection recipient in powerIn.Recipients)
{
if (powerIn.Grid != null)
if (!recipient.IsPower || !recipient.IsOutput) { continue; }
var battery = recipient.Item?.GetComponent<PowerContainer>();
if (battery != null)
{
supplyingGrid = powerIn.Grid;
batteries.Add(battery);
}
}
else if (powerOut != null)
{
if (powerOut.Grid != null)
{
supplyingGrid = powerOut.Grid;
}
}
if (supplyingGrid != null)
{
//Iterate through all connections to fine powerContainers
foreach (Connection c in supplyingGrid.Connections)
{
PowerContainer pc = c.Item.GetComponent<PowerContainer>();
if (pc != null && (!outputOnly || pc.powerOut == c))
{
batteries.Add(pc);
}
}
}
return batteries;
}
@@ -244,6 +244,13 @@ namespace Barotrauma.Items.Components
private set;
}
[Serialize(false, IsPropertySaveable.No)]
public bool DamageDoors
{
get;
set;
}
public bool IsStuckToTarget => StickTarget != null;
private Category originalCollisionCategories;
@@ -288,7 +295,7 @@ namespace Barotrauma.Items.Components
}
}
private void Launch(Character user, Vector2 simPosition, float rotation, float damageMultiplier = 1f)
private void Launch(Character user, Vector2 simPosition, float rotation, float damageMultiplier = 1f, float launchImpulseModifier = 0f)
{
Item.body.ResetDynamics();
Item.SetTransform(simPosition, rotation);
@@ -299,7 +306,7 @@ namespace Barotrauma.Items.Components
// Set user for hitscan projectiles to work properly.
User = user;
// Need to set null for non-characterusable items.
Use(character: null);
Use(character: null, launchImpulseModifier);
// Set user for normal projectiles to work properly.
User = user;
if (Item.Removed) { return; }
@@ -312,7 +319,7 @@ namespace Barotrauma.Items.Components
}
}
public void Shoot(Character user, Vector2 weaponPos, Vector2 spawnPos, float rotation, List<Body> ignoredBodies, bool createNetworkEvent, float damageMultiplier = 1f)
public void Shoot(Character user, Vector2 weaponPos, Vector2 spawnPos, float rotation, List<Body> ignoredBodies, bool createNetworkEvent, float damageMultiplier = 1f, float launchImpulseModifier = 0f)
{
//add the limbs of the shooter to the list of bodies to be ignored
//so that the player can't shoot himself
@@ -320,7 +327,7 @@ namespace Barotrauma.Items.Components
Vector2 projectilePos = weaponPos;
//make sure there's no obstacles between the base of the weapon (or the shoulder of the character) and the end of the barrel
if (Submarine.PickBody(weaponPos, spawnPos, IgnoredBodies, Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionItemBlocking,
customPredicate: (Fixture f) => { return !IgnoredBodies.Contains(f.Body); }) == null)
customPredicate: (Fixture f) => { return IgnoredBodies == null || !IgnoredBodies.Contains(f.Body); }) == null)
{
//no obstacles -> we can spawn the projectile at the barrel
projectilePos = spawnPos;
@@ -334,7 +341,7 @@ namespace Barotrauma.Items.Components
projectilePos = newPos;
}
}
Launch(user, projectilePos, rotation, damageMultiplier);
Launch(user, projectilePos, rotation, damageMultiplier, launchImpulseModifier);
if (createNetworkEvent && !Item.Removed && GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
{
#if SERVER
@@ -344,7 +351,7 @@ namespace Barotrauma.Items.Components
}
}
public bool Use(Character character = null)
public bool Use(Character character = null, float launchImpulseModifier = 0f)
{
if (character != null && !characterUsable) { return false; }
@@ -379,7 +386,7 @@ namespace Barotrauma.Items.Components
else
{
item.body.SetTransform(item.body.SimPosition, launchAngle);
float modifiedLaunchImpulse = LaunchImpulse * (1 + Rand.Range(-ImpulseSpread, ImpulseSpread));
float modifiedLaunchImpulse = (LaunchImpulse + launchImpulseModifier) * (1 + Rand.Range(-ImpulseSpread, ImpulseSpread));
DoLaunch(launchDir * modifiedLaunchImpulse * item.body.Mass);
System.Diagnostics.Debug.WriteLine("launch: " + modifiedLaunchImpulse + " - " + item.body.LinearVelocity);
}
@@ -723,7 +730,7 @@ namespace Barotrauma.Items.Components
private bool OnProjectileCollision(Fixture f1, Fixture target, Contact contact)
{
if (User != null && User.Removed) { User = null; return false; }
if (IgnoredBodies.Contains(target.Body)) { return false; }
if (IgnoredBodies != null && IgnoredBodies.Contains(target.Body)) { return false; }
//ignore character colliders (the projectile only hits limbs)
if (target.CollisionCategories == Physics.CollisionCharacter && target.Body.UserData is Character)
{
@@ -828,7 +835,7 @@ namespace Barotrauma.Items.Components
private bool HandleProjectileCollision(Fixture target, Vector2 collisionNormal, Vector2 velocity)
{
if (User != null && User.Removed) { User = null; }
if (IgnoredBodies.Contains(target.Body)) { return false; }
if (IgnoredBodies != null && IgnoredBodies.Contains(target.Body)) { return false; }
//ignore character colliders (the projectile only hits limbs)
if (target.CollisionCategories == Physics.CollisionCharacter && target.Body.UserData is Character)
{
@@ -851,7 +858,7 @@ namespace Barotrauma.Items.Components
}
else if (target.Body.UserData is Limb limb)
{
if (!FriendlyFire && User != null && limb.character.IsFriendly(User))
if (!FriendlyFire && User != null && limb.character.IsFriendly(User) && HumanAIController.IsOnFriendlyTeam(limb.character, User))
{
return false;
}
@@ -870,9 +877,20 @@ namespace Barotrauma.Items.Components
else if ((target.Body.UserData as Item ?? (target.Body.UserData as ItemComponent)?.Item) is Item targetItem)
{
if (targetItem.Removed) { return false; }
if (Attack != null && targetItem.Prefab.DamagedByProjectiles && targetItem.Condition > 0)
if (Attack != null && (targetItem.Prefab.DamagedByProjectiles || DamageDoors && targetItem.GetComponent<Door>() != null) && targetItem.Condition > 0)
{
attackResult = Attack.DoDamage(User ?? Attacker, targetItem, item.WorldPosition, 1.0f);
attackResult = Attack.DoDamage(User ?? Attacker, targetItem, item.WorldPosition, 1.0f);
#if CLIENT
if (attackResult.Damage > 0.0f)
{
Character.Controlled?.UpdateHUDProgressBar(targetItem,
targetItem.WorldPosition,
targetItem.Condition / targetItem.MaxCondition,
emptyColor: GUIStyle.HealthBarColorLow,
fullColor: GUIStyle.HealthBarColorHigh,
textTag: targetItem.Name);
}
#endif
}
}
else if (target.Body.UserData is IDamageable damageable)
@@ -1056,7 +1074,7 @@ namespace Barotrauma.Items.Components
item.body.CollidesWith = Physics.CollisionWall | Physics.CollisionLevel;
}
}
IgnoredBodies.Clear();
IgnoredBodies?.Clear();
}
private void StickToTarget(Body targetBody, Vector2 axis)
@@ -29,25 +29,14 @@ namespace Barotrauma.Items.Components
FirepowerMultiplier,
StrikingPowerMultiplier,
StrikingSpeedMultiplier,
FiringRateMultiplier,
// unused as of now
AttackMultiplier,
// unused as of now
AttackSpeedMultiplier,
ForceDoorsOpenSpeedMultiplier,
RangedSpreadReduction,
ChargeSpeedMultiplier,
MovementSpeedMultiplier,
EffectivenessMultiplier,
PowerOutputMultiplier,
ConsumptionReductionMultiplier,
FiringRateMultiplier
}
private readonly Dictionary<StatType, float> statValues = new Dictionary<StatType, float>();
private int qualityLevel;
[Editable, Serialize(0, IsPropertySaveable.Yes)]
[Editable(MinValueInt = 0, MaxValueInt = MaxQuality), Serialize(0, IsPropertySaveable.Yes)]
public int QualityLevel
{
get { return qualityLevel; }
@@ -343,7 +343,7 @@ namespace Barotrauma.Items.Components
{
CurrentFixer.CheckTalents(AbilityEffectType.OnStopTinkering);
}
CurrentFixer.AnimController.Anim = AnimController.Animation.None;
CurrentFixer.AnimController.StopUsingItem();
CurrentFixer = null;
currentRepairItem = null;
currentFixerAction = FixActions.None;
@@ -430,7 +430,7 @@ namespace Barotrauma.Items.Components
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
if (CurrentFixer != null && (CurrentFixer.SelectedConstruction != item || !CurrentFixer.CanInteractWith(item) || CurrentFixer.IsDead))
if (CurrentFixer != null && (CurrentFixer.SelectedItem != item || !CurrentFixer.CanInteractWith(item) || CurrentFixer.IsDead))
{
StopRepairing(CurrentFixer);
return;
@@ -502,7 +502,7 @@ namespace Barotrauma.Items.Components
SteamAchievementManager.OnItemRepaired(item, CurrentFixer);
CurrentFixer.CheckTalents(AbilityEffectType.OnRepairComplete);
}
if (CurrentFixer?.SelectedConstruction == item) { CurrentFixer.SelectedConstruction = null; }
if (CurrentFixer?.SelectedItem == item) { CurrentFixer.SelectedItem = null; }
deteriorationTimer = Rand.Range(MinDeteriorationDelay, MaxDeteriorationDelay);
wasBroken = false;
StopRepairing(CurrentFixer);
@@ -603,6 +603,9 @@ namespace Barotrauma.Items.Components
private bool ShouldDeteriorate()
{
if (Level.IsLoadedFriendlyOutpost) { return false; }
#if CLIENT
if (GameMain.GameSession?.GameMode is TutorialMode) { return false; }
#endif
if (LastActiveTime > Timing.TotalTime) { return true; }
foreach (ItemComponent ic in item.Components)
@@ -48,6 +48,8 @@ namespace Barotrauma.Items.Components
{
if (value == null) { return; }
output = value;
//reactivate (we may not have been previously sending a signal, but might now)
IsActive = true;
if (output.Length > MaxOutputLength && (item.Submarine == null || !item.Submarine.Loading))
{
output = output.Substring(0, MaxOutputLength);
@@ -63,6 +65,8 @@ namespace Barotrauma.Items.Components
{
if (value == null) { return; }
falseOutput = value;
//reactivate (we may not have been previously sending a signal, but might now)
IsActive = true;
if (falseOutput.Length > MaxOutputLength && (item.Submarine == null || !item.Submarine.Loading))
{
falseOutput = falseOutput.Substring(0, MaxOutputLength);
@@ -82,9 +86,14 @@ namespace Barotrauma.Items.Components
public sealed override void Update(float deltaTime, Camera cam)
{
int receivedInputs = 0;
bool allInputsTimedOut = true;
for (int i = 0; i < timeSinceReceived.Length; i++)
{
if (timeSinceReceived[i] <= timeFrame) { receivedInputs += 1; }
if (timeSinceReceived[i] <= timeFrame)
{
allInputsTimedOut = false;
receivedInputs += 1;
}
timeSinceReceived[i] += deltaTime;
}
@@ -93,7 +102,7 @@ namespace Barotrauma.Items.Components
if (string.IsNullOrEmpty(signalOut))
{
//deactivate the component if state is false and there's no false output (will be woken up by non-zero signals in ReceiveSignal)
if (!state) { IsActive = false; }
if (!state && allInputsTimedOut) { IsActive = false; }
return;
}
@@ -23,6 +23,9 @@ namespace Barotrauma.Items.Components
private readonly HashSet<Wire> wires;
public IReadOnlyCollection<Wire> Wires => wires;
private bool enumeratingWires;
private readonly HashSet<Wire> removedWires = new HashSet<Wire>();
private readonly Item item;
public readonly bool IsOutput;
@@ -239,7 +242,14 @@ namespace Barotrauma.Items.Components
}
prevOtherConnection.recipientsDirty = true;
}
wires.Remove(wire);
if (enumeratingWires)
{
removedWires.Add(wire);
}
else
{
wires.Remove(wire);
}
recipientsDirty = true;
}
@@ -278,6 +288,7 @@ namespace Barotrauma.Items.Components
public void SendSignal(Signal signal)
{
enumeratingWires = true;
foreach (var wire in wires)
{
Connection recipient = wire.OtherConnection(this);
@@ -305,6 +316,12 @@ namespace Barotrauma.Items.Components
}
}
}
enumeratingWires = false;
foreach (var removedWire in removedWires)
{
wires.Remove(removedWire);
}
removedWires.Clear();
}
public void ClearConnections()
@@ -317,13 +334,23 @@ namespace Barotrauma.Items.Components
Powered.ChangedConnections.Add(c);
}
}
foreach (var wire in wires)
{
wire.RemoveConnection(this);
recipientsDirty = true;
}
wires.Clear();
if (enumeratingWires)
{
foreach (var wire in wires)
{
removedWires.Add(wire);
}
}
else
{
wires.Clear();
}
}
public void InitializeFromLoaded()
@@ -179,7 +179,7 @@ namespace Barotrauma.Items.Components
{
UpdateProjSpecific(deltaTime);
if (user == null || user.SelectedConstruction != item)
if (user == null || user.SelectedItem != item)
{
#if SERVER
if (user != null) { item.CreateServerEvent(this); }
@@ -196,7 +196,7 @@ namespace Barotrauma.Items.Components
return;
}
user.AnimController.UpdateUseItem(true, item.WorldPosition + new Vector2(0.0f, 100.0f) * (((float)Timing.TotalTime / 10.0f) % 0.1f));
user.AnimController.UpdateUseItem(!user.IsClimbing, item.WorldPosition + new Vector2(0.0f, 100.0f) * (((float)Timing.TotalTime / 10.0f) % 0.1f));
}
public override void UpdateBroken(float deltaTime, Camera cam)
@@ -206,7 +206,7 @@ namespace Barotrauma.Items.Components
partial void UpdateProjSpecific(float deltaTime);
public override bool Select(Character picker)
public bool CanRewire()
{
//attaching wires to items with a body is not allowed
//(signal items remove their bodies when attached to a wall)
@@ -214,6 +214,15 @@ namespace Barotrauma.Items.Components
{
return false;
}
return true;
}
public override bool Select(Character picker)
{
if (!CanRewire())
{
return false;
}
user = picker;
#if SERVER
@@ -392,14 +401,14 @@ namespace Barotrauma.Items.Components
msg.WriteVariableUInt32((uint)connection.Wires.Count);
foreach (Wire wire in connection.Wires)
{
msg.Write(wire?.Item == null ? (ushort)0 : wire.Item.ID);
msg.WriteUInt16(wire?.Item == null ? (ushort)0 : wire.Item.ID);
}
}
msg.Write((ushort)DisconnectedWires.Count);
msg.WriteUInt16((ushort)DisconnectedWires.Count);
foreach (Wire disconnectedWire in DisconnectedWires)
{
msg.Write(disconnectedWire.Item.ID);
msg.WriteUInt16(disconnectedWire.Item.ID);
}
}
}
@@ -155,7 +155,7 @@ namespace Barotrauma.Items.Components
{
//use semicolon as a separator because comma may be needed in the signals (for color or vector values for example)
//kind of hacky, we should probably add support for (string) arrays to SerializableEntityEditor so this wouldn't be needed
get { return signals == null ? "" : string.Join(";", signals); }
get { return signals == null ? string.Empty : string.Join(";", signals); }
set
{
if (value == null) { return; }
@@ -167,7 +167,31 @@ namespace Barotrauma.Items.Components
}
}
public override bool RecreateGUIOnResolutionChange => true;
private bool[] elementStates;
[Serialize("", IsPropertySaveable.Yes, description: "", alwaysUseInstanceValues: true)]
public string ElementStates
{
get { return elementStates == null ? string.Empty : string.Join(",", elementStates); }
set
{
if (value == null) { return; }
if (customInterfaceElementList.Count > 0)
{
string[] splitValues = value == "" ? Array.Empty<string>() : value.Split(',');
for (int i = 0; i < customInterfaceElementList.Count && i < splitValues.Length; i++)
{
if (!bool.TryParse(splitValues[i], out bool val)) { continue; }
customInterfaceElementList[i].State = val;
#if CLIENT
if (uiElements != null && i < uiElements.Count && uiElements[i] is GUITickBox tickBox)
{
tickBox.Selected = val;
}
#endif
}
}
}
}
private readonly List<CustomInterfaceElement> customInterfaceElementList = new List<CustomInterfaceElement>();
@@ -207,8 +231,10 @@ namespace Barotrauma.Items.Components
}
IsActive = true;
InitProjSpecific();
//load these here to ensure the UI elements (created in InitProjSpecific) are up-to-date
Labels = element.GetAttributeString("labels", "");
Signals = element.GetAttributeString("signals", "");
ElementStates = element.GetAttributeString("elementstates", "");
}
private void UpdateLabels(string[] newLabels)
@@ -386,6 +412,7 @@ namespace Barotrauma.Items.Components
{
labels = customInterfaceElementList.Select(ci => ci.Label).ToArray();
signals = customInterfaceElementList.Select(ci => ci.Signal).ToArray();
elementStates = customInterfaceElementList.Select(ci => ci.State).ToArray();
return base.Save(parentElement);
}
@@ -248,8 +248,19 @@ namespace Barotrauma.Items.Components
public override void OnMapLoaded()
{
if (item.body == null && powerConsumption <= 0.0f && Parent == null && turret == null && IsOn &&
(statusEffectLists == null || !statusEffectLists.ContainsKey(ActionType.OnActive)) &&
CheckIfNeedsUpdate();
}
public void CheckIfNeedsUpdate()
{
if (!IsOn)
{
base.IsActive = false;
return;
}
if (item.body == null && powerConsumption <= 0.0f && Parent == null && turret == null &&
(statusEffectLists == null || !statusEffectLists.ContainsKey(ActionType.OnActive)) &&
(IsActiveConditionals == null || IsActiveConditionals.Count == 0))
{
lightBrightness = 1.0f;
@@ -261,6 +272,10 @@ namespace Barotrauma.Items.Components
Light.ParentSub = item.Submarine;
#endif
}
else
{
base.IsActive = true;
}
}
public override void Update(float deltaTime, Camera cam)
@@ -19,7 +19,8 @@ namespace Barotrauma.Items.Components
Human = 1,
Monster = 2,
Wall = 4,
Any = Human | Monster | Wall,
Pet = 8,
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).")]
@@ -253,7 +254,7 @@ namespace Barotrauma.Items.Components
}
}
if (Target.HasFlag(TargetType.Human) || Target.HasFlag(TargetType.Monster))
if (Target.HasFlag(TargetType.Human) || Target.HasFlag(TargetType.Pet) || Target.HasFlag(TargetType.Monster))
{
foreach (Character c in Character.CharacterList)
{
@@ -267,7 +268,11 @@ namespace Barotrauma.Items.Components
{
if (!Target.HasFlag(TargetType.Human)) { continue; }
}
else if (!c.IsPet)
else if (c.IsPet)
{
if (!Target.HasFlag(TargetType.Pet)) { continue; }
}
else
{
if (!Target.HasFlag(TargetType.Monster)) { continue; }
}
@@ -374,7 +374,7 @@ namespace Barotrauma.Items.Components
public void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData = null)
{
msg.Write(isOn);
msg.WriteBoolean(isOn);
}
public void ClientEventRead(IReadMessage msg, float sendingTime)
@@ -106,11 +106,11 @@ namespace Barotrauma.Items.Components
{
case "set_text":
case "signal_in":
if (string.IsNullOrEmpty(signal.value)) { return; }
if (signal.value.Length > MaxMessageLength)
{
signal.value = signal.value.Substring(0, MaxMessageLength);
}
string inputSignal = signal.value.Replace("\\n", "\n");
ShowOnDisplay(inputSignal, addToHistory: true, TextColor);
break;
@@ -58,6 +58,9 @@ namespace Barotrauma.Items.Components
}
}
public bool WaterDetected => isInWater;
public int WaterPercentage => GetWaterPercentage(item.CurrentHull);
public WaterDetector(Item item, ContentXElement element)
: base(item, element)
{
@@ -309,7 +309,7 @@ namespace Barotrauma.Items.Components
if (nodes.Count == 0) { return; }
Character user = item.ParentInventory?.Owner as Character;
editNodeDelay = (user?.SelectedConstruction == null) ? editNodeDelay - deltaTime : 0.5f;
editNodeDelay = (user?.SelectedItem == null) ? editNodeDelay - deltaTime : 0.5f;
Submarine sub = item.Submarine;
if (connections[0] != null && connections[0].Item.Submarine != null) { sub = connections[0].Item.Submarine; }
@@ -369,7 +369,7 @@ namespace Barotrauma.Items.Components
user.AnimController.Collider.ApplyForce(forceDir * user.Mass * 50.0f, maxVelocity: NetConfig.MaxPhysicsBodyVelocity * 0.5f);
if (diff.LengthSquared() > 50.0f * 50.0f)
{
user.AnimController.UpdateUseItem(true, user.WorldPosition + pullBackDir * Math.Min(150.0f, diff.Length()));
user.AnimController.UpdateUseItem(!user.IsClimbing, user.WorldPosition + pullBackDir * Math.Min(150.0f, diff.Length()));
}
if (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer)
@@ -428,7 +428,7 @@ namespace Barotrauma.Items.Components
public override bool Use(float deltaTime, Character character = null)
{
if (character == null || character != Character.Controlled) { return false; }
if (character.SelectedConstruction != null) { return false; }
if (character.HasSelectedAnyItem) { return false; }
#if CLIENT
if (Screen.Selected == GameMain.SubEditorScreen && !PlayerInput.PrimaryMouseButtonClicked())
{
@@ -7,6 +7,7 @@ using System.Globalization;
using System.Linq;
using Barotrauma.Extensions;
using FarseerPhysics.Dynamics;
using System.Collections.Immutable;
namespace Barotrauma.Items.Components
{
@@ -21,12 +22,15 @@ namespace Barotrauma.Items.Components
private float rotation, targetRotation;
private float reload, reloadTime;
private float reload, reloadTime, delayBetweenBurst;
private int shotsPerBurst, shotCounter;
private float minRotation, maxRotation;
private float launchImpulse;
private float damageMultiplier;
private Camera cam;
private float angularVelocity;
@@ -94,6 +98,16 @@ namespace Barotrauma.Items.Components
get;
set;
}
public bool flipFiringOffset;
[Serialize(false, IsPropertySaveable.No, description: "If enabled, the firing offset will alternate from left to right (i.e. flipping the x-component of the offset each shot.)")]
public bool AlternatingFiringOffset
{
get;
set;
}
public Vector2 TransformedBarrelPos
{
get
@@ -116,6 +130,20 @@ namespace Barotrauma.Items.Components
set { reloadTime = value; }
}
[Editable(1, 100), Serialize(1, IsPropertySaveable.No, description: "How many projectiles needs to be shot before we add an extra break? Think of the double coilgun.")]
public int ShotsPerBurst
{
get { return shotsPerBurst; }
set { shotsPerBurst = value; }
}
[Editable(0.0f, 1000.0f, decimals: 3), Serialize(0.0f, IsPropertySaveable.No, description: "An extra delay between the bursts. Added to the reload.")]
public float DelayBetweenBursts
{
get { return delayBetweenBurst; }
set { delayBetweenBurst = value; }
}
[Editable(0.1f, 10f), Serialize(1.0f, IsPropertySaveable.No, description: "Modifies the duration of retraction of the barrell after recoil to get back to the original position after shooting. Reload time affects this too.")]
public float RetractionDurationMultiplier
{
@@ -137,6 +165,13 @@ namespace Barotrauma.Items.Components
set;
}
[Serialize(1.0f, IsPropertySaveable.No, description: "Multiplies the damage the turret deals by this amount.")]
public float DamageMultiplier
{
get { return damageMultiplier; }
set { damageMultiplier = value; }
}
[Serialize(1, IsPropertySaveable.No, description: "How many projectiles the weapon launches when fired once.")]
public int ProjectileCount
{
@@ -525,7 +560,7 @@ namespace Barotrauma.Items.Components
UpdateLightComponents();
}
private void UpdateLightComponents()
public void UpdateLightComponents()
{
if (lightComponents != null)
{
@@ -602,9 +637,9 @@ namespace Barotrauma.Items.Components
if (projectiles.Any())
{
ItemContainer projectileContainer = projectiles.First().Item.Container?.GetComponent<ItemContainer>();
if (projectileContainer != null && projectileContainer.Item != item)
{
projectileContainer?.Item.Use(deltaTime, null);
if (projectileContainer != null && projectileContainer.Item != item)
{
projectileContainer?.Item.Use(deltaTime, null);
}
}
else
@@ -667,7 +702,7 @@ namespace Barotrauma.Items.Components
if (!ignorePower)
{
List<PowerContainer> batteries = GetConnectedBatteries();
List<PowerContainer> batteries = GetDirectlyConnectedBatteries();
float neededPower = GetPowerRequiredToShoot();
// tinkering is currently not factored into the common method as it is checked only when shooting
@@ -764,6 +799,15 @@ namespace Barotrauma.Items.Components
private void Launch(Item projectile, Character user = null, float? launchRotation = null, float tinkeringStrength = 0f)
{
reload = reloadTime;
if (ShotsPerBurst > 1)
{
shotCounter++;
if (shotCounter >= ShotsPerBurst)
{
reload += DelayBetweenBursts;
shotCounter = 0;
}
}
reload /= 1f + (tinkeringStrength * TinkeringReloadDecrease);
if (user != null)
@@ -773,6 +817,10 @@ namespace Barotrauma.Items.Components
if (projectile != null)
{
if (AlternatingFiringOffset)
{
flipFiringOffset = !flipFiringOffset;
}
activeProjectiles.Add(projectile);
projectile.Drop(null, setTransform: false);
if (projectile.body != null)
@@ -796,9 +844,9 @@ namespace Barotrauma.Items.Components
projectileComponent.Attacker = projectileComponent.User = user;
if (projectileComponent.Attack != null)
{
projectileComponent.Attack.DamageMultiplier = 1f + (TinkeringDamageIncrease * tinkeringStrength);
projectileComponent.Attack.DamageMultiplier = (1f * DamageMultiplier) + (TinkeringDamageIncrease * tinkeringStrength);
}
projectileComponent.Use();
projectileComponent.Use(null, LaunchImpulse);
projectile.GetComponent<Rope>()?.Attach(item, projectile);
projectileComponent.User = user;
@@ -1018,7 +1066,7 @@ namespace Barotrauma.Items.Components
bool canShoot = true;
if (!HasPowerToShoot())
{
List<PowerContainer> batteries = GetConnectedBatteries();
List<PowerContainer> batteries = GetDirectlyConnectedBatteries();
float lowestCharge = 0.0f;
PowerContainer batteryToLoad = null;
foreach (PowerContainer battery in batteries)
@@ -1104,7 +1152,7 @@ namespace Barotrauma.Items.Components
if (objective.SubObjectives.None())
{
var loadItemsObjective = AIContainItems<Turret>(container, character, objective, usableProjectileCount + 1, equip: true, removeEmpty: true, dropItemOnDeselected: true);
loadItemsObjective.ignoredContainerIdentifiers = new Identifier[] { ((MapEntity)containerItem).Prefab.Identifier };
loadItemsObjective.ignoredContainerIdentifiers = ((MapEntity)containerItem).Prefab.Identifier.ToEnumerable().ToImmutableHashSet();
if (character.IsOnPlayerTeam)
{
character.Speak(TextManager.GetWithVariable("DialogLoadTurret", "[itemname]", item.Name, formatCapitals: FormatCapitals.Yes).Value,
@@ -1260,9 +1308,13 @@ namespace Barotrauma.Items.Components
}
}
float dist = Vector2.Distance(closestPoint, item.WorldPosition);
//add one px to make sure the visibility raycast doesn't miss the cell due to the end position being right at the edge of the cell
closestPoint += (closestPoint - item.WorldPosition) / Math.Max(dist, 1);
if (dist > AIRange + 1000) { continue; }
float dot = 0;
if (item.Submarine.Velocity != Vector2.Zero)
if (!MathUtils.NearlyEqual(item.Submarine.Velocity, Vector2.Zero))
{
dot = Vector2.Dot(Vector2.Normalize(item.Submarine.Velocity), Vector2.Normalize(closestPoint - item.Submarine.WorldPosition));
}
@@ -1293,7 +1345,7 @@ namespace Barotrauma.Items.Components
{
if (character.AIController.SelectedAiTarget == null && !hadCurrentTarget)
{
if (CreatureMetrics.Instance.RecentlyEncountered.Contains(closestEnemy.SpeciesName))
if (CreatureMetrics.Instance.RecentlyEncountered.Contains(closestEnemy.SpeciesName) || closestEnemy.IsHuman)
{
character.Speak(TextManager.Get("DialogNewTargetSpotted").Value,
identifier: "newtargetspotted".ToIdentifier(),
@@ -1452,7 +1504,9 @@ namespace Barotrauma.Items.Components
Vector2 transformedFiringOffset = Vector2.Zero;
if (useOffset)
{
transformedFiringOffset = MathUtils.RotatePoint(new Vector2(-FiringOffset.Y, -FiringOffset.X) * item.Scale, -rotation);
Vector2 currOffSet = FiringOffset;
if (flipFiringOffset) { currOffSet.X = -currOffSet.X; }
transformedFiringOffset = MathUtils.RotatePoint(new Vector2(-currOffSet.Y, -currOffSet.X) * item.Scale, -rotation);
}
return new Vector2(item.WorldRect.X + transformedBarrelPos.X + transformedFiringOffset.X, item.WorldRect.Y - transformedBarrelPos.Y + transformedFiringOffset.Y);
}
@@ -1562,6 +1616,7 @@ namespace Barotrauma.Items.Components
targetRotation = rotation = (minRotation + maxRotation) / 2;
UpdateTransformedBarrelPos();
UpdateLightComponents();
}
public override void FlipY(bool relativeToSub)
@@ -1583,6 +1638,7 @@ namespace Barotrauma.Items.Components
targetRotation = rotation = (minRotation + maxRotation) / 2;
UpdateTransformedBarrelPos();
UpdateLightComponents();
}
public override void ReceiveSignal(Signal signal, Connection connection)
@@ -1665,12 +1721,12 @@ namespace Barotrauma.Items.Components
{
if (TryExtractEventData(extraData, out EventData eventData))
{
msg.Write(eventData.Projectile.ID);
msg.WriteUInt16(eventData.Projectile.ID);
msg.WriteRangedSingle(MathHelper.Clamp(rotation, minRotation, maxRotation), minRotation, maxRotation, 16);
}
else
{
msg.Write((ushort)0);
msg.WriteUInt16((ushort)0);
float wrappedTargetRotation = targetRotation;
while (wrappedTargetRotation < minRotation && MathUtils.IsValid(wrappedTargetRotation))
{
@@ -560,7 +560,7 @@ namespace Barotrauma.Items.Components
}
public override void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData = null)
{
msg.Write((byte)Variant);
msg.WriteByte((byte)Variant);
base.ServerEventWrite(msg, c, extraData);
}