Build 0.20.4.0

This commit is contained in:
Markus Isberg
2022-11-11 17:57:23 +02:00
parent edaf4b09fe
commit 54712b5dc9
201 changed files with 7618 additions and 2020 deletions
@@ -843,10 +843,18 @@ namespace Barotrauma.Items.Components
public override void Update(float deltaTime, Camera cam)
{
if (item.body == null || !item.body.Enabled) { return; }
Character owner = picker ?? item.GetRootInventoryOwner() as Character;
if (owner != null)
{
ApplyStatusEffects(ActionType.OnActive, deltaTime, owner);
}
if (picker == null || !picker.HasEquippedItem(item))
{
if (Pusher != null) { Pusher.Enabled = false; }
if (attachTargetCell == null) { IsActive = false; }
if (attachTargetCell == null && owner == null) { IsActive = false; }
return;
}
@@ -855,23 +863,7 @@ namespace Barotrauma.Items.Components
Drawable = true;
}
Vector2 swing = Vector2.Zero;
if (swingAmount != Vector2.Zero && !picker.IsUnconscious && picker.Stun <= 0.0f)
{
swingState += deltaTime;
swingState %= 1.0f;
if (SwingWhenHolding ||
(SwingWhenAiming && picker.IsKeyDown(InputType.Aim)) ||
(SwingWhenUsing && picker.IsKeyDown(InputType.Aim) && picker.IsKeyDown(InputType.Shoot)))
{
swing = swingAmount * new Vector2(
PerlinNoise.GetPerlin(swingState * SwingSpeed * 0.1f, swingState * SwingSpeed * 0.1f) - 0.5f,
PerlinNoise.GetPerlin(swingState * SwingSpeed * 0.1f + 0.5f, swingState * SwingSpeed * 0.1f + 0.5f) - 0.5f);
}
}
ApplyStatusEffects(ActionType.OnActive, deltaTime, picker);
UpdateSwingPos(deltaTime, out Vector2 swingPos);
if (item.body.Dir != picker.AnimController.Dir)
{
item.FlipX(relativeToSub: false);
@@ -884,7 +876,7 @@ namespace Barotrauma.Items.Components
scaledHandlePos[0] = handlePos[0] * item.Scale;
scaledHandlePos[1] = handlePos[1] * item.Scale;
bool aim = picker.IsKeyDown(InputType.Aim) && aimPos != Vector2.Zero && picker.CanAim;
picker.AnimController.HoldItem(deltaTime, item, scaledHandlePos, holdPos + swing, aimPos + swing, aim, holdAngle);
picker.AnimController.HoldItem(deltaTime, item, scaledHandlePos, holdPos + swingPos, aimPos + swingPos, aim, holdAngle);
if (!aim)
{
var rope = GetRope();
@@ -921,6 +913,24 @@ namespace Barotrauma.Items.Components
}
}
public void UpdateSwingPos(float deltaTime, out Vector2 swingPos)
{
swingPos = Vector2.Zero;
if (swingAmount != Vector2.Zero && !picker.IsUnconscious && picker.Stun <= 0.0f)
{
swingState += deltaTime;
swingState %= 1.0f;
if (SwingWhenHolding ||
(SwingWhenAiming && picker.IsKeyDown(InputType.Aim)) ||
(SwingWhenUsing && picker.IsKeyDown(InputType.Aim) && picker.IsKeyDown(InputType.Shoot)))
{
swingPos = swingAmount * new Vector2(
PerlinNoise.GetPerlin(swingState * SwingSpeed * 0.1f, swingState * SwingSpeed * 0.1f) - 0.5f,
PerlinNoise.GetPerlin(swingState * SwingSpeed * 0.1f + 0.5f, swingState * SwingSpeed * 0.1f + 0.5f) - 0.5f);
}
}
}
public override void ReceiveSignal(Signal signal, Connection connection)
{
//do nothing
@@ -214,8 +214,9 @@ namespace Barotrauma.Items.Components
bool aim = item.RequireAimToUse && picker.AllowInput && picker.IsKeyDown(InputType.Aim) && reloadTimer <= 0 && picker.CanAim;
if (aim)
{
UpdateSwingPos(deltaTime, out Vector2 swingPos);
hitPos = MathUtils.WrapAnglePi(Math.Min(hitPos + deltaTime * 3f, MathHelper.PiOver4));
ac.HoldItem(deltaTime, item, handlePos, aimPos, Vector2.Zero, aim: false, hitPos, holdAngle + hitPos, aimMelee: true);
ac.HoldItem(deltaTime, item, handlePos, aimPos + swingPos, Vector2.Zero, aim: false, hitPos, holdAngle + hitPos, aimMelee: true);
if (ac.InWater)
{
ac.LockFlippingUntil = (float)Timing.TotalTime + Reload;
@@ -392,34 +393,35 @@ namespace Barotrauma.Items.Components
float damageMultiplier = 1 + User.GetStatValue(StatTypes.MeleeAttackMultiplier);
damageMultiplier *= 1.0f + item.GetQualityModifier(Quality.StatType.StrikingPowerMultiplier);
Character user = User;
Limb targetLimb = target.UserData as Limb;
Character targetCharacter = targetLimb?.character ?? target.UserData as Character;
if (Attack != null)
{
Attack.SetUser(User);
Attack.SetUser(user);
Attack.DamageMultiplier = damageMultiplier;
if (targetLimb != null)
{
if (targetLimb.character.Removed) { return; }
targetLimb.character.LastDamageSource = item;
Attack.DoDamageToLimb(User, targetLimb, item.WorldPosition, 1.0f);
Attack.DoDamageToLimb(user, targetLimb, item.WorldPosition, 1.0f);
}
else if (targetCharacter != null)
{
if (targetCharacter.Removed) { return; }
targetCharacter.LastDamageSource = item;
Attack.DoDamage(User, targetCharacter, item.WorldPosition, 1.0f);
Attack.DoDamage(user, targetCharacter, item.WorldPosition, 1.0f);
}
else if ((target.UserData as Structure ?? targetFixture.UserData as Structure) is Structure targetStructure)
{
if (targetStructure.Removed) { return; }
Attack.DoDamage(User, targetStructure, item.WorldPosition, 1.0f);
Attack.DoDamage(user, targetStructure, item.WorldPosition, 1.0f);
}
else if (target.UserData is Item targetItem && targetItem.Prefab.DamagedByMeleeWeapons && targetItem.Condition > 0)
{
if (targetItem.Removed) { return; }
var attackResult = 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 && targetItem.Prefab.ShowHealthBar)
{
@@ -435,7 +437,7 @@ namespace Barotrauma.Items.Components
else if (target.UserData is Holdable holdable && holdable.CanPush)
{
if (holdable.Item.Removed) { return; }
Attack.DoDamage(User, holdable.Item, item.WorldPosition, 1.0f);
Attack.DoDamage(user, holdable.Item, item.WorldPosition, 1.0f);
RestoreCollision();
hitting = false;
User = null;
@@ -448,29 +450,32 @@ namespace Barotrauma.Items.Components
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
bool success = Rand.Range(0.0f, 0.5f) < DegreeOfSuccess(User);
#if SERVER
if (GameMain.Server != null && targetCharacter != null) //TODO: Log structure hits
ActionType conditionalActionType = ActionType.OnSuccess;
if (user != null && Rand.Range(0.0f, 0.5f) > DegreeOfSuccess(user))
{
GameMain.Server.CreateEntityEvent(item, new Item.ApplyStatusEffectEventData(
success ? ActionType.OnUse : ActionType.OnFailure,
targetItemComponent: null,
targetCharacter, targetLimb));
string logStr = picker?.LogName + " used " + item.Name;
if (item.ContainedItems != null && item.ContainedItems.Any())
{
logStr += " (" + string.Join(", ", item.ContainedItems.Select(i => i?.Name)) + ")";
}
logStr += " on " + targetCharacter.LogName + ".";
Networking.GameServer.Log(logStr, Networking.ServerLog.MessageType.Attack);
conditionalActionType = ActionType.OnFailure;
}
if (GameMain.NetworkMember is { IsServer: true } server && targetCharacter != null)
{
server.CreateEntityEvent(item, new Item.ApplyStatusEffectEventData(conditionalActionType, targetItemComponent: null, targetCharacter, targetLimb));
server.CreateEntityEvent(item, new Item.ApplyStatusEffectEventData(ActionType.OnUse, targetItemComponent: null, targetCharacter, targetLimb));
#if SERVER
if (GameMain.Server != null) //TODO: Log structure hits
{
string logStr = picker?.LogName + " used " + item.Name;
if (item.ContainedItems != null && item.ContainedItems.Any())
{
logStr += " (" + string.Join(", ", item.ContainedItems.Select(i => i?.Name)) + ")";
}
logStr += " on " + targetCharacter.LogName + ".";
Networking.GameServer.Log(logStr, Networking.ServerLog.MessageType.Attack);
}
#endif
}
#endif
if (targetCharacter != null) //TODO: Allow OnUse to happen on structures too maybe??
{
ApplyStatusEffects(success ? ActionType.OnUse : ActionType.OnFailure, 1.0f, targetCharacter, targetLimb, user: User, afflictionMultiplier: damageMultiplier);
ApplyStatusEffects(conditionalActionType, 1.0f, targetCharacter, targetLimb, user: user, afflictionMultiplier: damageMultiplier);
ApplyStatusEffects(ActionType.OnUse, 1.0f, targetCharacter, targetLimb, user: user, afflictionMultiplier: damageMultiplier);
}
if (DeleteOnUse)
@@ -23,6 +23,8 @@ namespace Barotrauma.Items.Components
[Serialize(0.0f, IsPropertySaveable.No, description: "The force to apply to the user's body."), Editable(MinValueFloat = -1000.0f, MaxValueFloat = 1000.0f)]
public float Force { get; set; }
[Serialize(true, IsPropertySaveable.No, description: "If the item is held in RightHand or LeftHand, apply extra force there")]
public bool ApplyToHands { get; set; }
#if CLIENT
private string particles;
[Serialize("", IsPropertySaveable.No, description: "The name of the particle prefab the item emits when used.")]
@@ -70,13 +72,16 @@ namespace Barotrauma.Items.Components
character.AnimController.Collider.ApplyForce(propulsion);
if (character.Inventory.IsInLimbSlot(item, InvSlotType.RightHand))
{
character.AnimController.GetLimb(LimbType.RightHand)?.body.ApplyForce(propulsion);
}
if (character.Inventory.IsInLimbSlot(item, InvSlotType.LeftHand))
{
character.AnimController.GetLimb(LimbType.LeftHand)?.body.ApplyForce(propulsion);
if (ApplyToHands)
{
if (character.Inventory.IsInLimbSlot(item, InvSlotType.RightHand))
{
character.AnimController.GetLimb(LimbType.RightHand)?.body.ApplyForce(propulsion);
}
if (character.Inventory.IsInLimbSlot(item, InvSlotType.LeftHand))
{
character.AnimController.GetLimb(LimbType.LeftHand)?.body.ApplyForce(propulsion);
}
}
#if CLIENT
@@ -32,6 +32,20 @@ namespace Barotrauma.Items.Components
set { reload = Math.Max(value, 0.0f); }
}
[Serialize(0f, IsPropertySaveable.No, description: "Weapons skill requirement to reload at normal speed.")]
public float ReloadSkillRequirement
{
get;
set;
}
[Serialize(1.0f, IsPropertySaveable.No, description: "Reload time at 0 skill level. Reload time scales with skill level up to the Weapons skill requirement.")]
public float ReloadNoSkill
{
get;
set;
}
[Serialize(false, IsPropertySaveable.No, description: "Tells the AI to hold the trigger down when it uses this weapon")]
public bool HoldTrigger
{
@@ -39,7 +53,7 @@ namespace Barotrauma.Items.Components
set;
}
[Serialize(1, IsPropertySaveable.No, description: "How projectiles the weapon launches when fired once.")]
[Serialize(1, IsPropertySaveable.No, description: "How many projectiles the weapon launches when fired once.")]
public int ProjectileCount
{
get;
@@ -60,6 +74,23 @@ namespace Barotrauma.Items.Components
set;
}
[Serialize(0.0f, IsPropertySaveable.No, description: "The impulse applied to the physics body of the projectile (the higher the impulse, the faster the projectiles are launched). Sum of weapon + projectile.")]
public float LaunchImpulse
{
get;
set;
}
[Serialize(0.0f, IsPropertySaveable.Yes, description: "Percentage of damage mitigation ignored when hitting armored body parts (deflecting limbs). Sum of weapon + projectile."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 1f)]
public float Penetration { get; private set; }
[Serialize(1f, IsPropertySaveable.Yes, description: "Weapon's damage modifier")]
public float WeaponDamageModifier
{
get;
private set;
}
[Serialize(0f, IsPropertySaveable.Yes, description: "The time required for a charge-type turret to charge up before able to fire.")]
public float MaxChargeTime
{
@@ -99,6 +130,12 @@ namespace Barotrauma.Items.Components
// TODO: should define this in xml if we have ranged weapons that don't require aim to use
item.RequireAimToUse = true;
characterUsable = true;
if (ReloadSkillRequirement > 0 && ReloadNoSkill <= reload)
{
DebugConsole.AddWarning($"Invalid XML at {item.Name}: ReloadNoSkill is lower or equal than it's reload skill, despite having ReloadSkillRequirement.");
}
InitProjSpecific(element);
}
@@ -167,7 +204,15 @@ namespace Barotrauma.Items.Components
if (currentChargeTime < MaxChargeTime) { return false; }
IsActive = true;
ReloadTimer = reload / (1 + character?.GetStatValue(StatTypes.RangedAttackSpeed) ?? 0f);
float baseReloadTime = reload;
float weaponSkill = character.GetSkillLevel("weapons");
if (ReloadSkillRequirement > 0 && ReloadNoSkill > reload && weaponSkill < ReloadSkillRequirement)
{
//Examples, assuming 40 weapon skill required: 1 - 40/40 = 0 ... 1 - 0/40 = 1 ... 1 - 20 / 40 = 0.5
float reloadFailure = MathHelper.Clamp(1 - (weaponSkill / ReloadSkillRequirement), 0, 1);
baseReloadTime = MathHelper.Lerp(reload, ReloadNoSkill, reloadFailure);
}
ReloadTimer = baseReloadTime / (1 + character?.GetStatValue(StatTypes.RangedAttackSpeed) ?? 0f);
currentChargeTime = 0f;
if (character != null)
@@ -218,9 +263,9 @@ namespace Barotrauma.Items.Components
{
lastProjectile?.Item.GetComponent<Rope>()?.Snap();
}
float damageMultiplier = 1f + item.GetQualityModifier(Quality.StatType.FirepowerMultiplier);
float damageMultiplier = (1f + item.GetQualityModifier(Quality.StatType.FirepowerMultiplier)) * WeaponDamageModifier;
projectile.Launcher = item;
projectile.Shoot(character, character.AnimController.AimSourceSimPos, barrelPos, rotation + spread, ignoredBodies: ignoredBodies.ToList(), createNetworkEvent: false, damageMultiplier);
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 (i == 0)
{
@@ -1,17 +1,26 @@
using Barotrauma.Networking;
using Microsoft.Xna.Framework;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Items.Components
{
class Throwable : Holdable
{
private float throwPos;
private bool throwing, throwDone;
enum ThrowState
{
None,
Initiated,
Throwing
}
private const float ThrowAngleStart = -MathHelper.PiOver2, ThrowAngleEnd = MathHelper.PiOver2;
private float throwAngle = ThrowAngleStart;
private bool midAir;
private ThrowState throwState;
//continuous collision detection is used while the item is moving faster than this
const float ContinuousCollisionThreshold = 5.0f;
@@ -27,7 +36,6 @@ namespace Barotrauma.Items.Components
public Throwable(Item item, ContentXElement element)
: base(item, element)
{
//throwForce = ToolBox.GetAttributeFloat(element, "throwforce", 1.0f);
if (aimPos == Vector2.Zero)
{
aimPos = new Vector2(0.6f, 0.1f);
@@ -36,22 +44,21 @@ namespace Barotrauma.Items.Components
public override bool Use(float deltaTime, Character character = null)
{
return characterUsable || character == null; //We do the actual throwing in Aim because Use might be used by chems
//actual throwing logic is handled in Update
return characterUsable || character == null;
}
public override bool SecondaryUse(float deltaTime, Character character = null)
{
if (!throwDone) return false; //This should only be triggered in update
throwDone = false;
return true;
//actual throwing logic is handled in Update - SecondaryUse only triggers when the item is thrown
return false;
}
public override void Drop(Character dropper)
{
base.Drop(dropper);
throwing = false;
throwPos = 0.0f;
throwState = ThrowState.None;
throwAngle = ThrowAngleStart;
}
public override void UpdateBroken(float deltaTime, Camera cam)
@@ -100,13 +107,22 @@ namespace Barotrauma.Items.Components
return;
}
if (picker.IsKeyDown(InputType.Aim) && picker.IsKeyHit(InputType.Shoot)) { throwing = true; }
if (!picker.IsKeyDown(InputType.Aim) && !throwing) { throwPos = 0.0f; }
bool aim = picker.IsKeyDown(InputType.Aim) && picker.CanAim;
if (throwState != ThrowState.Throwing)
{
if (picker.IsKeyDown(InputType.Aim))
{
if (picker.IsKeyDown(InputType.Shoot)) { throwState = ThrowState.Initiated; }
}
else if (throwState != ThrowState.Initiated)
{
throwAngle = ThrowAngleStart;
}
}
bool aim = picker.IsKeyDown(InputType.Aim) && picker.CanAim;
if (picker.IsDead || !picker.AllowInput)
{
throwing = false;
throwState = ThrowState.None;
aim = false;
}
@@ -124,25 +140,29 @@ namespace Barotrauma.Items.Components
item.Submarine = picker.Submarine;
if (!throwing)
if (throwState != ThrowState.Throwing)
{
if (aim)
if (aim || throwState == ThrowState.Initiated)
{
throwPos = MathUtils.WrapAnglePi(System.Math.Min(throwPos + deltaTime * 5.0f, MathHelper.PiOver2));
ac.HoldItem(deltaTime, item, handlePos, aimPos, Vector2.Zero, aim: false, throwPos);
throwAngle = System.Math.Min(throwAngle + deltaTime * 8.0f, ThrowAngleEnd);
ac.HoldItem(deltaTime, item, handlePos, aimPos, Vector2.Zero, aim: false, throwAngle);
if (throwAngle >= ThrowAngleEnd && throwState == ThrowState.Initiated)
{
throwState = ThrowState.Throwing;
}
}
else
{
throwPos = 0;
throwAngle = ThrowAngleStart;
ac.HoldItem(deltaTime, item, handlePos, holdPos, Vector2.Zero, aim: false, holdAngle);
}
}
else
{
throwPos = MathUtils.WrapAnglePi(throwPos - deltaTime * 15.0f);
ac.HoldItem(deltaTime, item, handlePos, aimPos, Vector2.Zero, aim: false, throwPos);
throwAngle = MathUtils.WrapAnglePi(throwAngle - deltaTime * 15.0f);
ac.HoldItem(deltaTime, item, handlePos, aimPos, Vector2.Zero, aim: false, throwAngle);
if (throwPos < 0)
if (throwAngle < 0)
{
Vector2 throwVector = Vector2.Normalize(picker.CursorWorldPosition - picker.WorldPosition);
//throw upwards if cursor is at the position of the character
@@ -180,8 +200,7 @@ namespace Barotrauma.Items.Components
Limb rightHand = ac.GetLimb(LimbType.RightHand);
item.body.AngularVelocity = rightHand.body.AngularVelocity;
throwPos = 0;
throwDone = true;
throwAngle = ThrowAngleStart;
IsActive = true;
if (GameMain.NetworkMember is { IsServer: true })
@@ -193,7 +212,7 @@ namespace Barotrauma.Items.Components
//Stun grenades, flares, etc. all have their throw-related things handled in "onSecondaryUse"
ApplyStatusEffects(ActionType.OnSecondaryUse, deltaTime, CurrentThrower, user: CurrentThrower);
}
throwing = false;
throwState = ThrowState.None;
}
}
}
@@ -65,8 +65,16 @@ namespace Barotrauma.Items.Components
public int Capacity
{
get { return capacity; }
set { capacity = Math.Max(value, 0); }
private set
{
capacity = Math.Max(value, 0);
MainContainerCapacity = value;
}
}
/// <summary>
/// The capacity of the main container without taking the sub containers into account. Only differs when there's a sub container defined for the component.
/// </summary>
public int MainContainerCapacity { get; private set; }
//how many items can be contained
private int maxStackSize;
@@ -229,6 +237,9 @@ namespace Barotrauma.Items.Components
public ImmutableHashSet<Identifier> ContainableItemIdentifiers => containableItemIdentifiers;
public List<RelatedItem> ContainableItems { get; }
public List<RelatedItem> AllSubContainableItems { get; }
public readonly bool HasSubContainers;
public ItemContainer(Item item, ContentXElement element)
: base(item, element)
@@ -251,6 +262,7 @@ namespace Barotrauma.Items.Components
break;
case "subcontainer":
totalCapacity += subElement.GetAttributeInt("capacity", 1);
HasSubContainers = true;
break;
}
}
@@ -270,7 +282,7 @@ namespace Barotrauma.Items.Components
int subCapacity = subElement.GetAttributeInt("capacity", 1);
int subMaxStackSize = subElement.GetAttributeInt("maxstacksize", maxStackSize);
List<RelatedItem> subContainableItems = null;
var subContainableItems = new List<RelatedItem>();
foreach (var subSubElement in subElement.Elements())
{
if (subSubElement.Name.ToString().ToLowerInvariant() != "containable") { continue; }
@@ -281,8 +293,9 @@ namespace Barotrauma.Items.Components
DebugConsole.ThrowError("Error in item config \"" + item.ConfigFilePath + "\" - containable with no identifiers.");
continue;
}
subContainableItems ??= new List<RelatedItem>();
subContainableItems.Add(containable);
AllSubContainableItems ??= new List<RelatedItem>();
AllSubContainableItems.Add(containable);
}
for (int i = subContainerIndex; i < subContainerIndex + subCapacity; i++)
@@ -357,6 +370,14 @@ 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);
if (IsActive && item.GetRootInventoryOwner() is Character owner &&
owner.HasEquippedItem(item, predicate: slot => slot.HasFlag(InvSlotType.LeftHand) || slot.HasFlag(InvSlotType.RightHand)))
{
// Set the contained items active if there's an item inserted inside the container. Enables e.g. the rifle flashlight when it's attached to the rifle (put inside of it).
SetContainedActive(true);
}
OnContainedItemsChanged.Invoke(this);
}
@@ -409,6 +430,20 @@ namespace Barotrauma.Items.Components
return false;
}
public override void FlipX(bool relativeToSub)
{
base.FlipX(relativeToSub);
if (HideItems) { return; }
if (item.body == null) { return; }
foreach (Item containedItem in Inventory.AllItems)
{
if (containedItem.body != null && containedItem.body.Enabled && containedItem.body.Dir != item.body.Dir)
{
containedItem.FlipX(relativeToSub);
}
}
}
public override void Update(float deltaTime, Camera cam)
{
if (!string.IsNullOrEmpty(SpawnWithId) && !alwaysContainedItemsSpawned)
@@ -477,7 +512,7 @@ namespace Barotrauma.Items.Components
effect.HasTargetType(StatusEffect.TargetType.NearbyCharacters))
{
targets.Clear();
targets.AddRange(effect.GetNearbyTargets(item.WorldPosition, targets));
effect.AddNearbyTargets(item.WorldPosition, targets);
effect.Apply(ActionType.OnActive, deltaTime, item, targets);
}
}
@@ -582,11 +617,53 @@ namespace Barotrauma.Items.Components
public override void Drop(Character dropper)
{
IsActive = true;
SetContainedActive(false);
}
public override void Equip(Character character)
{
IsActive = true;
if (character != null && character.HasEquippedItem(item, predicate: slot => slot.HasFlag(InvSlotType.LeftHand) || slot.HasFlag(InvSlotType.RightHand)))
{
SetContainedActive(true);
}
}
private void SetContainedActive(bool active)
{
foreach (Item containedItem in Inventory.AllItems)
{
RelatedItem containableItem = FindContainableItem(containedItem);
if (containableItem != null && containableItem.SetActive)
{
foreach (var ic in containedItem.Components)
{
ic.IsActive = active;
}
if (containedItem.body != null)
{
containedItem.body.Enabled = active;
if (active)
{
containedItem.body.PhysEnabled = false;
}
}
}
}
if (active)
{
FlipX(false);
}
}
private RelatedItem FindContainableItem(Item item)
{
var relatedItem = ContainableItems?.FirstOrDefault(ci => ci.MatchesItem(item));
if (relatedItem == null && AllSubContainableItems != null)
{
relatedItem = AllSubContainableItems.FirstOrDefault(ci => ci.MatchesItem(item));
}
return relatedItem;
}
public override void ReceiveSignal(Signal signal, Connection connection)
@@ -604,6 +681,7 @@ 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;
@@ -657,29 +735,70 @@ namespace Barotrauma.Items.Components
transformedItemIntervalHorizontal = Vector2.Transform(transformedItemIntervalHorizontal, transform);
transformedItemPos += item.Position;
}
}
float currentRotation = itemRotation;
if (item.body != null)
{
currentRotation *= item.body.Dir;
currentRotation += item.body.Rotation;
}
else
{
currentRotation += -item.RotationRad;
}
int i = 0;
Vector2 currentItemPos = transformedItemPos;
foreach (Item contained in Inventory.AllItems)
{
Vector2 itemPos = currentItemPos;
var relatedItem = FindContainableItem(contained);
if (relatedItem != null)
{
if (relatedItem.Hide.HasValue && relatedItem.Hide.Value) { continue; }
if (relatedItem.ItemPos.HasValue)
{
Vector2 pos = relatedItem.ItemPos.Value;
if (item.body != null)
{
Matrix transform = Matrix.CreateRotationZ(item.body.Rotation);
pos.X *= item.body.Dir;
itemPos = Vector2.Transform(pos, transform) + item.body.Position;
}
else
{
itemPos = pos;
// This code is aped based on above. Not tested.
if (item.FlippedX)
{
itemPos.X = -itemPos.X;
itemPos.X += item.Rect.Width;
}
if (item.FlippedY)
{
itemPos.Y = -itemPos.Y;
itemPos.Y -= item.Rect.Height;
}
itemPos += new Vector2(item.Rect.X, item.Rect.Y);
if (Math.Abs(item.RotationRad) > 0.01f)
{
Matrix transform = Matrix.CreateRotationZ(item.RotationRad);
itemPos = Vector2.Transform(itemPos - item.Position, transform) + item.Position;
}
}
}
}
if (contained.body != null)
{
try
{
Vector2 simPos = ConvertUnits.ToSimUnits(currentItemPos);
contained.body.FarseerBody.SetTransformIgnoreContacts(ref simPos, currentRotation);
Vector2 simPos = ConvertUnits.ToSimUnits(itemPos);
float rotation = itemRotation;
if (relatedItem != null && relatedItem.Rotation != 0)
{
rotation = MathHelper.ToRadians(relatedItem.Rotation);
}
if (item.body != null)
{
rotation *= item.body.Dir;
rotation += item.body.Rotation;
}
else
{
rotation += -item.RotationRad;
}
contained.body.FarseerBody.SetTransformIgnoreContacts(ref simPos, rotation);
contained.body.SetPrevTransform(contained.body.SimPosition, contained.body.Rotation);
contained.body.UpdateDrawPosition();
}
@@ -695,8 +814,8 @@ namespace Barotrauma.Items.Components
contained.Rect =
new Rectangle(
(int)(currentItemPos.X - contained.Rect.Width / 2.0f),
(int)(currentItemPos.Y + contained.Rect.Height / 2.0f),
(int)(itemPos.X - contained.Rect.Width / 2.0f),
(int)(itemPos.Y + contained.Rect.Height / 2.0f),
contained.Rect.Width, contained.Rect.Height);
contained.Submarine = item.Submarine;
@@ -459,6 +459,12 @@ namespace Barotrauma.Items.Components
progressTimer = 0.0f;
progressState = 0.0f;
}
#if CLIENT
else
{
HintManager.OnStartDeconstructing(user, this);
}
#endif
inputContainer.Inventory.Locked = IsActive;
}
@@ -419,7 +419,7 @@ namespace Barotrauma.Items.Components
}
var fabricationIngredients = new AbilityFabricationItemIngredients(foundAvailableItems);
user.CheckTalents(AbilityEffectType.OnItemFabricatedIngredients, fabricationIngredients);
user?.CheckTalents(AbilityEffectType.OnItemFabricatedIngredients, fabricationIngredients);
foreach (Item availableItem in fabricationIngredients.Items)
{
@@ -559,7 +559,7 @@ namespace Barotrauma.Items.Components
if (fabricatedItem.TargetItem.ConfigElement.GetChildElement("Quality") == null) { return 0; }
int quality = 0;
float floatQuality = 0.0f;
floatQuality += user.GetStatValue(StatTypes.IncreaseFabricationQuality);
floatQuality += user.GetStatValue(StatTypes.IncreaseFabricationQuality, includeSaved: false);
foreach (var tag in fabricatedItem.TargetItem.Tags)
{
floatQuality += user.Info.GetSavedStatValue(StatTypes.IncreaseFabricationQuality, tag);
@@ -740,10 +740,27 @@ namespace Barotrauma.Items.Components
//order by condition (prefer using worst-condition items)
int index = 0;
while (index < availableIngredients[itemIdentifier].Count &&
availableIngredients[itemIdentifier][index].Condition < item.Condition)
compare(item, availableIngredients[itemIdentifier][index], inputContainer.Inventory) < 0)
{
index++;
}
static int compare(Item item1, Item item2, Inventory inputInventory)
{
bool item1InInputInventory = item1.ParentInventory == inputInventory;
bool item2InInputInventory = item2.ParentInventory == inputInventory;
//prefer items in the input inventory
if (item1InInputInventory != item2InInputInventory)
{
return item1InInputInventory ? 1 : -1;
}
else
{
//prefer items in worse condition
return Math.Sign(item2.Condition - item1.Condition);
}
}
availableIngredients[itemIdentifier].Insert(index, item);
}
}
@@ -15,6 +15,8 @@ namespace Barotrauma.Items.Components
public float? ReceivedOxygenAmount,
ReceivedWaterAmount;
public double LastOxygenDataTime, LastWaterDataTime;
public readonly HashSet<IdCard> Cards = new HashSet<IdCard>();
public bool Distort;
@@ -83,7 +85,7 @@ namespace Barotrauma.Items.Components
public override void Update(float deltaTime, Camera cam)
{
//periodically reset all hull data
//reset data if we haven't received anything in a while
//(so that outdated hull info won't be shown if detectors stop sending signals)
if (DateTime.Now > resetDataTime)
{
@@ -91,8 +93,8 @@ namespace Barotrauma.Items.Components
{
if (!hullData.Distort)
{
hullData.ReceivedOxygenAmount = null;
hullData.ReceivedWaterAmount = null;
if (Timing.TotalTime > hullData.LastOxygenDataTime + 1.0) { hullData.ReceivedOxygenAmount = null; }
if (Timing.TotalTime > hullData.LastWaterDataTime + 1.0) { hullData.ReceivedWaterAmount = null; }
}
}
resetDataTime = DateTime.Now + new TimeSpan(0, 0, 1);
@@ -159,6 +161,7 @@ namespace Barotrauma.Items.Components
//cheating a bit because water detectors don't actually send the water level
bool fromWaterDetector = source.GetComponent<WaterDetector>() != null;
hullData.ReceivedWaterAmount = null;
hullData.LastWaterDataTime = Timing.TotalTime;
if (fromWaterDetector)
{
hullData.ReceivedWaterAmount = WaterDetector.GetWaterPercentage(sourceHull);
@@ -184,9 +187,10 @@ namespace Barotrauma.Items.Components
oxy = Rand.Range(0.0f, 100.0f);
}
hullData.ReceivedOxygenAmount = oxy;
hullData.LastOxygenDataTime = Timing.TotalTime;
foreach (var linked in sourceHull.linkedTo)
{
if (!(linked is Hull linkedHull)) { continue; }
if (linked is not Hull linkedHull) { continue; }
if (!hullDatas.TryGetValue(linkedHull, out HullData linkedHullData))
{
linkedHullData = new HullData();
@@ -197,11 +197,6 @@ namespace Barotrauma.Items.Components
if (currentPingIndex != -1)
{
var activePing = activePings[currentPingIndex];
if (item.AiTarget != null)
{
float range = MathUtils.InverseLerp(item.AiTarget.MinSoundRange, item.AiTarget.MaxSoundRange, Range * activePing.State / zoom);
item.AiTarget.SoundRange = MathHelper.Lerp(item.AiTarget.MinSoundRange, item.AiTarget.MaxSoundRange, range);
}
if (activePing.State > 1.0f)
{
aiPingCheckPending = true;
@@ -235,6 +230,11 @@ namespace Barotrauma.Items.Components
for (var pingIndex = 0; pingIndex < activePingsCount;)
{
if (item.AiTarget != null)
{
float range = MathUtils.InverseLerp(item.AiTarget.MinSoundRange, item.AiTarget.MaxSoundRange, Range * activePings[pingIndex].State / zoom);
item.AiTarget.SoundRange = Math.Max(item.AiTarget.SoundRange, MathHelper.Lerp(item.AiTarget.MinSoundRange, item.AiTarget.MaxSoundRange, range));
}
if (activePings[pingIndex].State > 1.0f)
{
var lastIndex = --activePingsCount;
@@ -144,6 +144,11 @@ namespace Barotrauma.Items.Components
}
}
public float TargetVelocityLengthSquared
{
get => TargetVelocity.LengthSquared();
}
public Vector2 SteeringInput
{
get { return steeringInput; }
@@ -392,6 +392,7 @@ namespace Barotrauma.Items.Components
}
}
User = character;
ApplyStatusEffects(ActionType.OnUse, 1.0f, User, user: User);
return true;
}
@@ -916,23 +917,22 @@ namespace Barotrauma.Items.Components
if (character != null) { character.LastDamageSource = item; }
ActionType actionType = ActionType.OnUse;
if (_user != null && Rand.Range(0.0f, 0.5f) > DegreeOfSuccess(_user))
ActionType conditionalActionType = ActionType.OnSuccess;
if (User != null && Rand.Range(0.0f, 0.5f) > DegreeOfSuccess(User))
{
actionType = ActionType.OnFailure;
conditionalActionType = ActionType.OnFailure;
}
#if CLIENT
PlaySound(actionType, user: _user);
PlaySound(ActionType.OnImpact, user: _user);
PlaySound(conditionalActionType, user: User);
PlaySound(ActionType.OnImpact, user: User);
#endif
if (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer)
{
if (target.Body.UserData is Limb targetLimb)
{
ApplyStatusEffects(actionType, 1.0f, character, targetLimb, user: _user);
ApplyStatusEffects(ActionType.OnImpact, 1.0f, character, targetLimb, user: _user);
ApplyStatusEffects(conditionalActionType, 1.0f, character, targetLimb, user: User);
ApplyStatusEffects(ActionType.OnImpact, 1.0f, character, targetLimb, user: User);
var attack = targetLimb.attack;
if (attack != null)
{
@@ -941,8 +941,6 @@ namespace Barotrauma.Items.Components
{
if (effect.type == ActionType.OnImpact)
{
//effect.Apply(effect.type, 1.0f, targetLimb.character, targetLimb.character, targetLimb.WorldPosition);
if (effect.HasTargetType(StatusEffect.TargetType.This))
{
effect.Apply(effect.type, 1.0f, targetLimb.character, targetLimb.character, targetLimb.WorldPosition);
@@ -951,32 +949,27 @@ namespace Barotrauma.Items.Components
effect.HasTargetType(StatusEffect.TargetType.NearbyCharacters))
{
targets.Clear();
targets.AddRange(effect.GetNearbyTargets(targetLimb.WorldPosition, targets));
effect.AddNearbyTargets(targetLimb.WorldPosition, targets);
effect.Apply(ActionType.OnActive, 1.0f, targetLimb.character, targets);
}
}
}
}
#if SERVER
if (GameMain.NetworkMember.IsServer)
if (GameMain.NetworkMember is { IsServer: true } server)
{
GameMain.Server?.CreateEntityEvent(item, new Item.ApplyStatusEffectEventData(actionType, this, targetLimb.character, targetLimb, null, item.WorldPosition));
GameMain.Server?.CreateEntityEvent(item, new Item.ApplyStatusEffectEventData(ActionType.OnImpact, this, targetLimb.character, targetLimb, null, item.WorldPosition));
server.CreateEntityEvent(item, new Item.ApplyStatusEffectEventData(conditionalActionType, this, targetLimb.character, targetLimb, null, item.WorldPosition));
server.CreateEntityEvent(item, new Item.ApplyStatusEffectEventData(ActionType.OnImpact, this, targetLimb.character, targetLimb, null, item.WorldPosition));
}
#endif
}
else
{
ApplyStatusEffects(actionType, 1.0f, useTarget: target.Body.UserData as Entity, user: _user);
ApplyStatusEffects(ActionType.OnImpact, 1.0f, useTarget: target.Body.UserData as Entity, user: _user);
#if SERVER
if (GameMain.NetworkMember.IsServer)
ApplyStatusEffects(conditionalActionType, 1.0f, useTarget: target.Body.UserData as Entity, user: User);
ApplyStatusEffects(ActionType.OnImpact, 1.0f, useTarget: target.Body.UserData as Entity, user: User);
if (GameMain.NetworkMember is { IsServer: true } server)
{
GameMain.Server?.CreateEntityEvent(item, new Item.ApplyStatusEffectEventData(actionType, this, null, null, target.Body.UserData as Entity, item.WorldPosition));
GameMain.Server?.CreateEntityEvent(item, new Item.ApplyStatusEffectEventData(ActionType.OnImpact, this, null, null, target.Body.UserData as Entity, item.WorldPosition));
server.CreateEntityEvent(item, new Item.ApplyStatusEffectEventData(conditionalActionType, this, null, null, target.Body.UserData as Entity, item.WorldPosition));
server.CreateEntityEvent(item, new Item.ApplyStatusEffectEventData(ActionType.OnImpact, this, null, null, target.Body.UserData as Entity, item.WorldPosition));
}
#endif
}
}
@@ -289,7 +289,7 @@ namespace Barotrauma.Items.Components
#if CLIENT
Light.ParentSub = item.Submarine;
#endif
if (item.Container != null)
if (item.Container != null && !(item.GetRootInventoryOwner() is Character))
{
SetLightSourceState(false, 0.0f);
return;
@@ -301,7 +301,7 @@ namespace Barotrauma.Items.Components
if (body != null && !body.Enabled)
{
SetLightSourceState(false, 0.0f);
return;
return;
}
//currPowerConsumption = powerConsumption;