Build 0.21.6.0 (1.0 pre-patch)

This commit is contained in:
Regalis11
2023-01-31 18:08:26 +02:00
parent e1c04bc31d
commit cf9ecd35b3
231 changed files with 4479 additions and 2276 deletions
@@ -188,16 +188,26 @@ namespace Barotrauma.Items.Components
private DockingPort FindAdjacentPort()
{
float closestDist = float.MaxValue;
DockingPort closestPort = null;
foreach (DockingPort port in list)
{
if (port == this || port.item.Submarine == item.Submarine || port.IsHorizontal != IsHorizontal) { continue; }
if (Math.Abs(port.item.WorldPosition.X - item.WorldPosition.X) > DistanceTolerance.X) { continue; }
if (Math.Abs(port.item.WorldPosition.Y - item.WorldPosition.Y) > DistanceTolerance.Y) { continue; }
float xDist = Math.Abs(port.item.WorldPosition.X - item.WorldPosition.X);
if (xDist > DistanceTolerance.X) { continue; }
float yDist = Math.Abs(port.item.WorldPosition.Y - item.WorldPosition.Y);
if (yDist > DistanceTolerance.Y) { continue; }
return port;
float dist = xDist + yDist;
//disfavor non-interactable ports
if (port.item.NonInteractable) { dist *= 2; }
if (dist < closestDist)
{
closestPort = port;
closestDist = dist;
}
}
return null;
return closestPort;
}
private void AttemptDock()
@@ -279,7 +289,16 @@ namespace Barotrauma.Items.Components
return;
}
if (!(joint is WeldJoint))
if (joint == null)
{
string errorMsg = "Error while locking a docking port (joint between submarines doesn't exist)." +
" Submarine: " + (item.Submarine?.Info.Name ?? "null") +
", target submarine: " + (DockingTarget.item.Submarine?.Info.Name ?? "null");
GameAnalyticsManager.AddErrorEventOnce("DockingPort.Lock:JointNotCreated", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
return;
}
if (joint is not WeldJoint)
{
DockingDir = GetDir(DockingTarget);
DockingTarget.DockingDir = -DockingDir;
@@ -1,12 +1,9 @@
using Barotrauma.Networking;
using FarseerPhysics;
using FarseerPhysics.Dynamics;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using Barotrauma.IO;
using System.Linq;
using System.Xml.Linq;
#if CLIENT
using Barotrauma.Lights;
#endif
@@ -206,6 +203,8 @@ namespace Barotrauma.Items.Components
IsHorizontal = element.GetAttributeBool("horizontal", false);
canBePicked = element.GetAttributeBool("canbepicked", false);
autoOrientGap = element.GetAttributeBool("autoorientgap", false);
allowedSlots.Clear();
foreach (var subElement in element.Elements())
{
@@ -359,7 +358,11 @@ namespace Barotrauma.Items.Components
{
lastBrokenTime = Timing.TotalTime;
//the door has to be restored to 50% health before collision detection on the body is re-enabled
if (item.ConditionPercentage > 50.0f && (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer))
//multiply by MaxRepairConditionMultiplier so the item gets repaired at 50% of the _default max condition_
//otherwise increasing the max condition is arguably harmful, as the door needs to be repaired further to re-enable the collider
if (item.ConditionPercentage * Math.Max(item.MaxRepairConditionMultiplier, 1.0f) > 50.0f &&
(GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer))
{
IsBroken = false;
}
@@ -459,7 +462,10 @@ namespace Barotrauma.Items.Components
ce = ce.Next;
}
}
linkedGap.Open = 1.0f;
if (linkedGap != null)
{
linkedGap.Open = 1.0f;
}
IsOpen = false;
#if CLIENT
if (convexHull != null) { convexHull.Enabled = false; }
@@ -58,9 +58,6 @@ namespace Barotrauma.Items.Components
set;
}
//the angle in which the Character holds the item
protected float holdAngle;
public PhysicsBody Body
{
get { return item.body ?? body; }
@@ -143,6 +140,7 @@ namespace Barotrauma.Items.Components
set { aimPos = ConvertUnits.ToSimUnits(value); }
}
protected float holdAngle;
#if DEBUG
[Editable, Serialize(0.0f, IsPropertySaveable.No, description: "The rotation at which the character holds the item (in degrees, relative to the rotation of the character's hand).")]
#else
@@ -154,6 +152,18 @@ namespace Barotrauma.Items.Components
set { holdAngle = MathHelper.ToRadians(value); }
}
protected float aimAngle;
#if DEBUG
[Editable, Serialize(0.0f, IsPropertySaveable.No, description: "The rotation at which the character holds the item while aiming (in degrees, relative to the rotation of the character's hand).")]
#else
[Serialize(0.0f, IsPropertySaveable.No)]
#endif
public float AimAngle
{
get { return MathHelper.ToDegrees(aimAngle); }
set { aimAngle = MathHelper.ToRadians(value); }
}
private Vector2 swingAmount;
#if DEBUG
[Editable, Serialize("0.0,0.0", IsPropertySaveable.No, description: "How much the item swings around when aiming/holding it (in pixels, as an offset from AimPos/HoldPos).")]
@@ -552,6 +562,7 @@ namespace Barotrauma.Items.Components
{
return false;
}
bool wasAttached = IsAttached;
if (base.OnPicked(picker))
{
DeattachFromWall();
@@ -560,7 +571,7 @@ namespace Barotrauma.Items.Components
if (GameMain.Server != null && attachable)
{
item.CreateServerEvent(this);
if (picker != null)
if (picker != null && wasAttached)
{
GameServer.Log(GameServer.CharacterLogName(picker) + " detached " + item.Name + " from a wall", ServerLog.MessageType.ItemInteraction);
}
@@ -688,16 +699,22 @@ namespace Barotrauma.Items.Components
if (maxAttachableCount == 0)
{
#if CLIENT
GUI.AddMessage(TextManager.Get("itemmsgrequiretraining"), Color.Red);
if (character == Character.Controlled)
{
GUI.AddMessage(TextManager.Get("itemmsgrequiretraining"), Color.Red);
}
#endif
return false;
}
else if (currentlyAttachedCount >= maxAttachableCount)
{
#if CLIENT
GUI.AddMessage($"{TextManager.Get("itemmsgtotalnumberlimited")} ({currentlyAttachedCount}/{maxAttachableCount})", Color.Red);
if (character == Character.Controlled)
{
GUI.AddMessage($"{TextManager.Get("itemmsgtotalnumberlimited")} ({currentlyAttachedCount}/{maxAttachableCount})", Color.Red);
}
#endif
return false;
return false;
}
}
@@ -875,9 +892,13 @@ 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 + swingPos, aimPos + swingPos, aim, holdAngle);
if (!aim)
if (aim)
{
picker.AnimController.HoldItem(deltaTime, item, scaledHandlePos, holdPos + swingPos, aimPos + swingPos, aim, holdAngle, aimAngle);
}
else
{
picker.AnimController.HoldItem(deltaTime, item, scaledHandlePos, holdPos + swingPos, aimPos + swingPos, aim, holdAngle);
var rope = GetRope();
if (rope != null && rope.SnapWhenNotAimed && rope.Item.ParentInventory == null)
{
@@ -49,6 +49,13 @@ namespace Barotrauma.Items.Components
set;
}
[Serialize(false, IsPropertySaveable.No, description: "Disable to make the weapon ignore all hit effects when it collides with walls, doors, or other items.")]
public bool HitOnlyCharacters
{
get;
set;
}
[Editable, Serialize(true, IsPropertySaveable.No)]
public bool Swing { get; set; }
@@ -112,7 +119,7 @@ namespace Barotrauma.Items.Components
reloadTimer = reload;
reloadTimer /= 1f + character.GetStatValue(StatTypes.MeleeAttackSpeed);
reloadTimer /= 1f + item.GetQualityModifier(Quality.StatType.StrikingSpeedMultiplier);
character.AnimController.LockFlippingUntil = (float)Timing.TotalTime + reloadTimer * 0.9f;
character.AnimController.LockFlipping();
item.body.FarseerBody.CollisionCategories = Physics.CollisionProjectile;
item.body.FarseerBody.CollidesWith = Physics.CollisionCharacter | Physics.CollisionWall | Physics.CollisionItemBlocking;
@@ -216,10 +223,10 @@ namespace Barotrauma.Items.Components
{
UpdateSwingPos(deltaTime, out Vector2 swingPos);
hitPos = MathUtils.WrapAnglePi(Math.Min(hitPos + deltaTime * 3f, MathHelper.PiOver4));
ac.HoldItem(deltaTime, item, handlePos, aimPos + swingPos, Vector2.Zero, aim: false, hitPos, holdAngle + hitPos, aimMelee: true);
ac.HoldItem(deltaTime, item, handlePos, aimPos + swingPos, Vector2.Zero, aim: false, hitPos, holdAngle + hitPos + aimAngle, aimMelee: true);
if (ac.InWater)
{
ac.LockFlippingUntil = (float)Timing.TotalTime + Reload;
ac.LockFlipping();
}
}
else
@@ -343,33 +350,36 @@ namespace Barotrauma.Items.Components
}
hitTargets.Add(targetCharacter);
}
else if ((f2.Body.UserData as Structure ?? f2.UserData as Structure) is Structure targetStructure)
else if (!HitOnlyCharacters)
{
if (AllowHitMultiple)
if ((f2.Body.UserData as Structure ?? f2.UserData as Structure) is Structure targetStructure)
{
if (hitTargets.Contains(targetStructure)) { return true; }
if (AllowHitMultiple)
{
if (hitTargets.Contains(targetStructure)) { return true; }
}
else
{
if (hitTargets.Any(t => t is Structure)) { return true; }
}
hitTargets.Add(targetStructure);
}
else
else if (f2.Body.UserData is Item targetItem)
{
if (hitTargets.Any(t => t is Structure)) { return true; }
if (AllowHitMultiple)
{
if (hitTargets.Contains(targetItem)) { return true; }
}
else
{
if (hitTargets.Any(t => t is Item)) { return true; }
}
hitTargets.Add(targetItem);
}
hitTargets.Add(targetStructure);
}
else if (f2.Body.UserData is Item targetItem)
{
if (AllowHitMultiple)
else if (f2.Body.UserData is Holdable holdable && holdable.CanPush)
{
if (hitTargets.Contains(targetItem)) { return true; }
hitTargets.Add(holdable.Item);
}
else
{
if (hitTargets.Any(t => t is Item)) { return true; }
}
hitTargets.Add(targetItem);
}
else if (f2.Body.UserData is Holdable holdable && holdable.CanPush)
{
hitTargets.Add(holdable.Item);
}
else
{
@@ -381,6 +391,7 @@ namespace Barotrauma.Items.Components
return true;
}
private System.Text.StringBuilder serverLogger;
private void HandleImpact(Fixture targetFixture)
{
var target = targetFixture.Body;
@@ -398,11 +409,13 @@ namespace Barotrauma.Items.Components
Character user = User;
Limb targetLimb = target.UserData as Limb;
Character targetCharacter = targetLimb?.character ?? target.UserData as Character;
Structure targetStructure = target.UserData as Structure ?? targetFixture.UserData as Structure;
Item targetItem = target.UserData as Item;
Entity targetEntity = targetCharacter ?? targetStructure ?? targetItem ?? target.UserData as Entity;
if (Attack != null)
{
Attack.SetUser(user);
Attack.DamageMultiplier = damageMultiplier;
if (targetLimb != null)
{
if (targetLimb.character.Removed) { return; }
@@ -415,12 +428,12 @@ namespace Barotrauma.Items.Components
targetCharacter.LastDamageSource = item;
Attack.DoDamage(user, targetCharacter, item.WorldPosition, 1.0f);
}
else if ((target.UserData as Structure ?? targetFixture.UserData as Structure) is Structure targetStructure)
else if (targetStructure != null)
{
if (targetStructure.Removed) { return; }
Attack.DoDamage(user, targetStructure, item.WorldPosition, 1.0f);
}
else if (target.UserData is Item targetItem && targetItem.Prefab.DamagedByMeleeWeapons && targetItem.Condition > 0)
else if (targetItem != null && targetItem.Prefab.DamagedByMeleeWeapons && targetItem.Condition > 0)
{
if (targetItem.Removed) { return; }
var attackResult = Attack.DoDamage(user, targetItem, item.WorldPosition, 1.0f);
@@ -457,27 +470,43 @@ namespace Barotrauma.Items.Components
{
conditionalActionType = ActionType.OnFailure;
}
if (GameMain.NetworkMember is { IsServer: true } server && targetCharacter != null)
if (GameMain.NetworkMember is { IsServer: true } server && targetEntity != 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
server.CreateEntityEvent(item, new Item.ApplyStatusEffectEventData(conditionalActionType, targetItemComponent: null, targetCharacter, targetLimb, useTarget: targetEntity));
server.CreateEntityEvent(item, new Item.ApplyStatusEffectEventData(ActionType.OnUse, targetItemComponent: null, targetCharacter, targetLimb, useTarget: targetEntity));
serverLogger ??= new System.Text.StringBuilder();
serverLogger.Clear();
serverLogger.Append($"{picker?.LogName} used {item.Name}");
if (item.ContainedItems != null && item.ContainedItems.Any())
{
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);
serverLogger.Append($"({string.Join(", ", item.ContainedItems.Select(i => i?.Name))})");
}
#endif
string targetName;
if (targetCharacter != null)
{
targetName = targetCharacter.LogName;
}
else if (targetItem != null)
{
targetName = targetItem.Name;
}
else if (targetStructure != null)
{
targetName = targetStructure.Name;
}
else
{
targetName = targetEntity.ToString();
}
serverLogger.Append($" on {targetName}.");
#if SERVER
Networking.GameServer.Log(serverLogger.ToString(), Networking.ServerLog.MessageType.Attack);
#endif
}
if (targetCharacter != null) //TODO: Allow OnUse to happen on structures too maybe??
if (targetEntity != null)
{
ApplyStatusEffects(conditionalActionType, 1.0f, targetCharacter, targetLimb, user: user, afflictionMultiplier: damageMultiplier);
ApplyStatusEffects(ActionType.OnUse, 1.0f, targetCharacter, targetLimb, user: user, afflictionMultiplier: damageMultiplier);
ApplyStatusEffects(conditionalActionType, 1.0f, targetCharacter, targetLimb, useTarget: targetEntity, user: user, afflictionMultiplier: damageMultiplier);
ApplyStatusEffects(ActionType.OnUse, 1.0f, targetCharacter, targetLimb, useTarget: targetEntity, user: user, afflictionMultiplier: damageMultiplier);
}
if (DeleteOnUse)
@@ -26,6 +26,8 @@ namespace Barotrauma.Items.Components
get { return allowedSlots; }
}
public bool PickingDone => pickTimer >= PickingTime;
public Character Picker
{
get
@@ -213,6 +213,8 @@ namespace Barotrauma.Items.Components
baseReloadTime = MathHelper.Lerp(reload, ReloadNoSkill, reloadFailure);
}
ReloadTimer = baseReloadTime / (1 + character?.GetStatValue(StatTypes.RangedAttackSpeed) ?? 0f);
ReloadTimer /= 1f + item.GetQualityModifier(Quality.StatType.FiringRateMultiplier);
currentChargeTime = 0f;
if (character != null)
@@ -4,7 +4,6 @@ using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using Barotrauma.Extensions;
using Barotrauma.MapCreatures.Behavior;
@@ -185,24 +184,23 @@ namespace Barotrauma.Items.Components
float degreeOfSuccess = character == null ? 0.5f : DegreeOfSuccess(character);
bool failed = false;
if (Rand.Range(0.0f, 0.5f) > degreeOfSuccess)
{
ApplyStatusEffects(ActionType.OnFailure, deltaTime, character);
return false;
failed = true;
}
if (UsableIn == UseEnvironment.None)
{
ApplyStatusEffects(ActionType.OnFailure, deltaTime, character);
return false;
failed = true;
}
if (item.InWater)
{
if (UsableIn == UseEnvironment.Air)
{
ApplyStatusEffects(ActionType.OnFailure, deltaTime, character);
return false;
failed = true;
}
}
else
@@ -210,9 +208,15 @@ namespace Barotrauma.Items.Components
if (UsableIn == UseEnvironment.Water)
{
ApplyStatusEffects(ActionType.OnFailure, deltaTime, character);
return false;
failed = true;
}
}
if (failed)
{
// Always apply ActionType.OnUse. If doesn't fail, the effect is called later.
ApplyStatusEffects(ActionType.OnUse, deltaTime, character);
return false;
}
Vector2 rayStart;
Vector2 rayStartWorld;
@@ -312,9 +316,12 @@ namespace Barotrauma.Items.Components
var collisionCategories = Physics.CollisionWall | Physics.CollisionCharacter | Physics.CollisionItem | Physics.CollisionLevel | Physics.CollisionRepair;
//if the item can cut off limbs, activate nearby bodies to allow the raycast to hit them
if (statusEffectLists != null && statusEffectLists.ContainsKey(ActionType.OnUse))
if (statusEffectLists != null)
{
if (statusEffectLists[ActionType.OnUse].Any(s => s.SeverLimbsProbability > 0.0f))
static bool CanSeverJoints(ActionType type, Dictionary<ActionType, List<StatusEffect>> effectList) =>
effectList.TryGetValue(type, out List<StatusEffect> effects) && effects.Any(e => e.SeverLimbsProbability > 0);
if (CanSeverJoints(ActionType.OnUse, statusEffectLists) || CanSeverJoints(ActionType.OnSuccess, statusEffectLists))
{
float rangeSqr = ConvertUnits.ToSimUnits(Range);
rangeSqr *= rangeSqr;
@@ -537,6 +544,7 @@ namespace Barotrauma.Items.Components
if (nonFixableEntities.Contains(targetStructure.Prefab.Identifier) || nonFixableEntities.Any(t => targetStructure.Tags.Contains(t))) { return false; }
ApplyStatusEffectsOnTarget(user, deltaTime, ActionType.OnUse, structure: targetStructure);
ApplyStatusEffectsOnTarget(user, deltaTime, ActionType.OnSuccess, structure: targetStructure);
FixStructureProjSpecific(user, deltaTime, targetStructure, sectionIndex);
float structureFixAmount = StructureFixAmount;
@@ -605,6 +613,7 @@ namespace Barotrauma.Items.Components
}
ApplyStatusEffectsOnTarget(user, deltaTime, ActionType.OnUse, character: targetCharacter, limb: closestLimb);
ApplyStatusEffectsOnTarget(user, deltaTime, ActionType.OnSuccess, character: targetCharacter, limb: closestLimb);
FixCharacterProjSpecific(user, deltaTime, targetCharacter);
return true;
}
@@ -621,6 +630,7 @@ namespace Barotrauma.Items.Components
targetLimb.character.LastDamageSource = item;
ApplyStatusEffectsOnTarget(user, deltaTime, ActionType.OnUse, character: targetLimb.character, limb: targetLimb);
ApplyStatusEffectsOnTarget(user, deltaTime, ActionType.OnSuccess, character: targetLimb.character, limb: targetLimb);
FixCharacterProjSpecific(user, deltaTime, targetLimb.character);
return true;
}
@@ -663,6 +673,7 @@ namespace Barotrauma.Items.Components
targetItem.IsHighlighted = true;
ApplyStatusEffectsOnTarget(user, deltaTime, ActionType.OnUse, targetItem);
ApplyStatusEffectsOnTarget(user, deltaTime, ActionType.OnSuccess, targetItem);
if (targetItem.body != null && !MathUtils.NearlyEqual(TargetForce, 0.0f))
{
@@ -875,7 +886,7 @@ namespace Barotrauma.Items.Components
}
else if (effect.HasTargetType(StatusEffect.TargetType.Character))
{
currentTargets.Add(character);
currentTargets.Add(user);
effect.Apply(actionType, deltaTime, item, currentTargets);
}
else if (effect.HasTargetType(StatusEffect.TargetType.Limb))
@@ -205,12 +205,12 @@ namespace Barotrauma.Items.Components
if (GameMain.NetworkMember is { IsServer: true })
{
GameMain.NetworkMember.CreateEntityEvent(item, new Item.ApplyStatusEffectEventData(ActionType.OnSecondaryUse, this, CurrentThrower));
GameMain.NetworkMember.CreateEntityEvent(item, new Item.ApplyStatusEffectEventData(ActionType.OnSecondaryUse, this, targetCharacter: CurrentThrower));
}
if (!(GameMain.NetworkMember is { IsClient: true }))
{
//Stun grenades, flares, etc. all have their throw-related things handled in "onSecondaryUse"
ApplyStatusEffects(ActionType.OnSecondaryUse, deltaTime, CurrentThrower, user: CurrentThrower);
ApplyStatusEffects(ActionType.OnSecondaryUse, deltaTime, character: CurrentThrower, user: CurrentThrower);
}
throwState = ThrowState.None;
}
@@ -125,8 +125,8 @@ namespace Barotrauma.Items.Components
get { return drawable; }
set
{
if (value == drawable) return;
if (!(this is IDrawableComponent))
if (value == drawable) { return; }
if (this is not IDrawableComponent)
{
DebugConsole.ThrowError("Couldn't make \"" + this + "\" drawable (the component doesn't implement the IDrawableComponent interface)");
return;
@@ -236,10 +236,7 @@ namespace Barotrauma.Items.Components
set;
}
/// <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>
[Serialize(0f, IsPropertySaveable.No, description: "How useful the item is in combat? Used by AI to decide which item it should use as a weapon. For the sake of clarity, use a value between 0 and 100 (not enforced).")]
[Serialize(0f, IsPropertySaveable.No, description: "How useful the item is in combat? Used by AI to decide which item it should use as a weapon. For the sake of clarity, use a value between 0 and 100 (not enforced). Note that there's also a generic BotPriority for all item prefabs.")]
public float CombatPriority { get; private set; }
/// <summary>
@@ -697,7 +694,7 @@ namespace Barotrauma.Items.Components
public virtual void FlipY(bool relativeToSub) { }
public bool IsLoaded(Character user, bool checkContainedItems = true) =>
public bool IsNotEmpty(Character user, bool checkContainedItems = true) =>
HasRequiredContainedItems(user, addMessage: false) &&
(!checkContainedItems || Item.OwnInventory == null || Item.OwnInventory.AllItems.Any(i => i.Condition > 0));
@@ -856,7 +853,13 @@ 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);
var c = character;
if (user != null && effect.HasTargetType(StatusEffect.TargetType.Character) && !effect.HasTargetType(StatusEffect.TargetType.UseTarget))
{
// A bit hacky, but fixes MeleeWeapons targeting the use target instead of the attacker. Also applies to Projectiles and Throwables, or other callers that passes the user.
c = user;
}
item.ApplyStatusEffect(effect, type, deltaTime, c, targetLimb, useTarget, isNetworkEvent: false, checkCondition: false, worldPosition);
effect.AfflictionMultiplier = 1.0f;
reducesCondition |= effect.ReducesItemCondition();
}
@@ -114,7 +114,7 @@ namespace Barotrauma.Items.Components
[Serialize(100, IsPropertySaveable.No, description: "How many items are placed in a row before starting a new row.")]
public int ItemsPerRow { get; set; }
[Serialize(true, IsPropertySaveable.No, description: "Should the contents in the item's inventory be visible? Disabled on items like magazines that spawn the contents as needed.")]
[Serialize(true, IsPropertySaveable.No, description: "Should the inventory of this item be visible when the item is selected.")]
public bool DrawInventory
{
get;
@@ -142,6 +142,9 @@ namespace Barotrauma.Items.Components
set;
}
[Serialize(true, IsPropertySaveable.No)]
public bool AllowAccess { get; set; }
[Serialize(false, IsPropertySaveable.No)]
public bool AccessOnlyWhenBroken { get; set; }
@@ -534,12 +537,12 @@ namespace Barotrauma.Items.Components
public override bool HasRequiredItems(Character character, bool addMessage, LocalizedString msg = null)
{
return DrawInventory && (!AccessOnlyWhenBroken || Item.Condition <= 0) && base.HasRequiredItems(character, addMessage, msg);
return AllowAccess && (!AccessOnlyWhenBroken || Item.Condition <= 0) && base.HasRequiredItems(character, addMessage, msg);
}
public override bool Select(Character character)
{
if (!DrawInventory) { return false; }
if (!AllowAccess) { return false; }
if (item.Container != null) { return false; }
if (AccessOnlyWhenBroken)
{
@@ -575,7 +578,7 @@ namespace Barotrauma.Items.Components
public override bool Pick(Character picker)
{
if (!DrawInventory) { return false; }
if (!AllowAccess) { return false; }
if (AccessOnlyWhenBroken)
{
if (item.Condition > 0)
@@ -431,7 +431,7 @@ namespace Barotrauma.Items.Components
//disable flipping for 0.5 seconds, because flipping the character when it's in a weird pose (e.g. lying in bed) can mess up the ragdoll
if (character.AnimController is HumanoidAnimController humanoidAnim)
{
humanoidAnim.LockFlippingUntil = (float)Timing.TotalTime + 0.5f;
humanoidAnim.LockFlipping(0.5f);
}
if (character.SelectedItem == item) { character.SelectedItem = null; }
@@ -112,27 +112,34 @@ namespace Barotrauma.Items.Components
set;
}
[Serialize(false, IsPropertySaveable.No, description: "Can the item stick to the character it hits.")]
[Serialize(false, IsPropertySaveable.No, description: "Can the projectile stick to characters.")]
public bool StickToCharacters
{
get;
set;
}
[Serialize(false, IsPropertySaveable.No, description: "Can the item stick to the structure it hits.")]
[Serialize(false, IsPropertySaveable.No, description: "Can the projectile stick to walls.")]
public bool StickToStructures
{
get;
set;
}
[Serialize(false, IsPropertySaveable.No, description: "Can the item stick to the item it hits.")]
[Serialize(false, IsPropertySaveable.No, description: "Can the projectile stick to items.")]
public bool StickToItems
{
get;
set;
}
[Serialize(false, IsPropertySaveable.No, description: "Can the projectile stick to doors. Caution: may cause issues.")]
public bool StickToDoors
{
get;
set;
}
[Serialize(false, IsPropertySaveable.No, description: "Can the item stick even to deflective targets.")]
public bool StickToDeflective
{
@@ -457,36 +464,36 @@ namespace Barotrauma.Items.Components
Vector2 rayEndWorld = rayStartWorld + dir * worldDist;
List<HitscanResult> hits = new List<HitscanResult>();
hits.AddRange(DoRayCast(rayStart, rayEnd, submarine: item.Submarine));
if (item.Submarine != null)
{
//shooting indoors, do a hitscan outside as well
hits.AddRange(DoRayCast(rayStart + item.Submarine.SimPosition, rayEnd + item.Submarine.SimPosition, submarine: null));
//also in the coordinate space of docked subs
foreach (Submarine dockedSub in item.Submarine.DockedTo)
{
if (dockedSub == item.Submarine) { continue; }
hits.AddRange(DoRayCast(rayStart + item.Submarine.SimPosition - dockedSub.SimPosition, rayEnd + item.Submarine.SimPosition - dockedSub.SimPosition, dockedSub));
}
//do a hitscan in other subs' coordinate spaces
RayCastInOtherSubs(rayStart + item.Submarine.SimPosition, rayEnd + item.Submarine.SimPosition);
}
else
{
RayCastInOtherSubs(rayStart, rayEnd);
}
void RayCastInOtherSubs(Vector2 rayStart, Vector2 rayEnd)
{
//shooting outdoors, see if we can hit anything inside a sub
foreach (Submarine submarine in Submarine.Loaded)
{
if (submarine == item.Submarine) { continue; }
var inSubHits = DoRayCast(rayStart - submarine.SimPosition, rayEnd - submarine.SimPosition, submarine);
//transform back to world coordinates
for (int i = 0; i < inSubHits.Count; i++)
{
inSubHits[i] = new HitscanResult(
inSubHits[i].Fixture,
inSubHits[i].Point + submarine.SimPosition,
inSubHits[i].Normal,
inSubHits[i].Fixture,
inSubHits[i].Point + submarine.SimPosition,
inSubHits[i].Normal,
inSubHits[i].Fraction);
}
hits.AddRange(inSubHits);
}
}
@@ -767,7 +774,7 @@ namespace Barotrauma.Items.Components
limb.body?.ApplyLinearImpulse(item.body.LinearVelocity * item.body.Mass * 0.1f, item.SimPosition);
return false;
}
if (!FriendlyFire && User != null && limb.character.IsFriendly(User) && HumanAIController.IsOnFriendlyTeam(limb.character, User))
if (!FriendlyFire && User != null && limb.character.IsFriendly(User))
{
return false;
}
@@ -888,7 +895,7 @@ namespace Barotrauma.Items.Components
}
else if (target.Body.UserData is Limb limb)
{
if (!FriendlyFire && User != null && limb.character.IsFriendly(User) && HumanAIController.IsOnFriendlyTeam(limb.character, User))
if (!FriendlyFire && User != null && limb.character.IsFriendly(User))
{
return false;
}
@@ -959,8 +966,8 @@ namespace Barotrauma.Items.Components
{
if (target.Body.UserData is Limb targetLimb)
{
ApplyStatusEffects(conditionalActionType, 1.0f, character, targetLimb, user: User);
ApplyStatusEffects(ActionType.OnImpact, 1.0f, character, targetLimb, user: User);
ApplyStatusEffects(conditionalActionType, 1.0f, character, targetLimb, useTarget: character, user: User);
ApplyStatusEffects(ActionType.OnImpact, 1.0f, character, targetLimb, useTarget: character, user: User);
var attack = targetLimb.attack;
if (attack != null)
{
@@ -971,22 +978,30 @@ namespace Barotrauma.Items.Components
{
if (effect.HasTargetType(StatusEffect.TargetType.This))
{
effect.Apply(effect.type, 1.0f, targetLimb.character, targetLimb.character, targetLimb.WorldPosition);
effect.Apply(effect.type, 1.0f, User, User);
}
if (effect.HasTargetType(StatusEffect.TargetType.Character) || effect.HasTargetType(StatusEffect.TargetType.UseTarget))
{
effect.Apply(effect.type, 1.0f, targetLimb.character, targetLimb.character);
}
if (effect.HasTargetType(StatusEffect.TargetType.Limb))
{
effect.Apply(effect.type, 1.0f, targetLimb.character, targetLimb);
}
if (effect.HasTargetType(StatusEffect.TargetType.NearbyItems) ||
effect.HasTargetType(StatusEffect.TargetType.NearbyCharacters))
{
targets.Clear();
effect.AddNearbyTargets(targetLimb.WorldPosition, targets);
effect.Apply(ActionType.OnActive, 1.0f, targetLimb.character, targets);
effect.Apply(effect.type, 1.0f, targetLimb.character, targets);
}
}
}
}
if (GameMain.NetworkMember is { IsServer: true } server)
{
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));
server.CreateEntityEvent(item, new Item.ApplyStatusEffectEventData(conditionalActionType, this, targetLimb.character, targetLimb, useTarget: targetLimb.character, item.WorldPosition));
server.CreateEntityEvent(item, new Item.ApplyStatusEffectEventData(ActionType.OnImpact, this, targetLimb.character, targetLimb, useTarget: targetLimb.character, item.WorldPosition));
}
}
else
@@ -995,8 +1010,8 @@ namespace Barotrauma.Items.Components
ApplyStatusEffects(ActionType.OnImpact, 1.0f, useTarget: target.Body.UserData as Entity, user: User);
if (GameMain.NetworkMember is { IsServer: true } server)
{
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));
server.CreateEntityEvent(item, new Item.ApplyStatusEffectEventData(conditionalActionType, this, useTarget: target.Body.UserData as Entity, worldPosition: item.WorldPosition));
server.CreateEntityEvent(item, new Item.ApplyStatusEffectEventData(ActionType.OnImpact, this, useTarget: target.Body.UserData as Entity, worldPosition: item.WorldPosition));
}
}
}
@@ -1004,13 +1019,12 @@ namespace Barotrauma.Items.Components
target.Body.ApplyLinearImpulse(velocity * item.body.Mass);
target.Body.LinearVelocity = target.Body.LinearVelocity.ClampLength(NetConfig.MaxPhysicsBodyVelocity * 0.5f);
if (hits.Count() >= MaxTargetsToHit || hits.LastOrDefault()?.UserData is VoronoiCell)
if (hits.Count >= MaxTargetsToHit || hits.LastOrDefault()?.UserData is VoronoiCell)
{
DisableProjectileCollisions();
}
if (attackResult.AppliedDamageModifiers != null &&
(attackResult.AppliedDamageModifiers.Any(dm => dm.DeflectProjectiles) && !StickToDeflective))
if (attackResult.AppliedDamageModifiers != null && attackResult.AppliedDamageModifiers.Any(dm => dm.DeflectProjectiles) && !StickToDeflective)
{
item.body.LinearVelocity *= deflectedSpeedMultiplier;
}
@@ -1020,7 +1034,7 @@ namespace Barotrauma.Items.Components
((StickToLightTargets || target.Body.Mass > item.body.Mass * 0.5f) &&
(DoesStick ||
(StickToCharacters && (target.Body.UserData is Limb || target.Body.UserData is Character)) ||
(StickToItems && target.Body.UserData is Item))))
(target.Body.UserData is Item i && (i.GetComponent<Door>() != null ? StickToDoors : StickToItems)))))
{
Vector2 dir = new Vector2(
(float)Math.Cos(item.body.Rotation),
@@ -83,7 +83,15 @@ namespace Barotrauma.Items.Components
{
if (submarine?.Info == null || level == null || submarine.Info.Type == SubmarineType.Player) { return 0; }
float difficultyFactor = MathHelper.Clamp(level.Difficulty, 0.0f, 1.0f);
float difficultyFactor = MathHelper.Clamp(level.Difficulty, 0.0f, level.LevelData.Biome.ActualMaxDifficulty / 100.0f);
if (level.Type == LevelData.LevelType.Outpost &&
level.StartLocation?.Type?.OutpostTeam == CharacterTeamType.FriendlyNPC)
{
//no high-quality spawns in friendly outposts
difficultyFactor = 0.0f;
}
return ToolBox.SelectWeightedRandom(Enumerable.Range(0, MaxQuality + 1), q => GetCommonness(q, difficultyFactor), randSync);
static float GetCommonness(int quality, float difficultyFactor)
@@ -159,9 +159,11 @@ namespace Barotrauma.Items.Components
public override void Update(float deltaTime, Camera cam)
{
var user = item.GetComponent<Projectile>()?.User;
if (source == null || target == null || target.Removed ||
(source is Entity sourceEntity && sourceEntity.Removed) ||
(source is Limb limb && limb.Removed))
(source is Limb limb && limb.Removed) ||
(user != null && user.Removed))
{
ResetSource();
target = null;
@@ -293,7 +295,6 @@ namespace Barotrauma.Items.Components
{
targetMass = float.MaxValue;
}
var user = item.GetComponent<Projectile>()?.User;
if (targetMass > TargetMinMass)
{
if (Math.Abs(SourcePullForce) > 0.001f)
@@ -301,33 +302,16 @@ namespace Barotrauma.Items.Components
var sourceBody = GetBodyToPull(source);
if (sourceBody != null)
{
var targetBody = GetBodyToPull(target);
if (targetBody != null && !(targetBody.UserData is Character))
if (user != null && user.InWater)
{
sourceBody.ApplyForce(targetBody.LinearVelocity * sourceBody.Mass);
}
float forceMultiplier = 1;
if (user != null)
{
user.AnimController.Hang();
if (user.InWater)
if (user.IsRagdolled)
{
if (user.IsRagdolled)
{
forceMultiplier = 0;
}
}
else
{
forceMultiplier = user.IsRagdolled ? 0.1f : 0.4f;
// Prevents too easy smashing to the walls
forceDir.X /= 4;
// Prevents rubberbanding up and down
if (forceDir.Y < 0)
{
forceDir.Y = 0;
}
// Reel in towards the target.
user.AnimController.Hang();
float force = LerpForces ? MathHelper.Lerp(0, SourcePullForce, MathUtils.InverseLerp(0, MaxLength / 2, distance)) : SourcePullForce;
sourceBody.ApplyForce(forceDir * force);
}
// Take the target velocity into account.
if (targetCharacter != null)
{
var myCollider = user.AnimController.Collider;
@@ -340,9 +324,15 @@ namespace Barotrauma.Items.Components
}
}
}
else
{
var targetBody = GetBodyToPull(target);
if (targetBody != null)
{
sourceBody.ApplyForce(targetBody.LinearVelocity * sourceBody.Mass);
}
}
}
float force = LerpForces ? MathHelper.Lerp(0, SourcePullForce, MathUtils.InverseLerp(0, MaxLength / 2, distance)) * forceMultiplier : SourcePullForce * forceMultiplier;
sourceBody.ApplyForce(forceDir * force);
}
}
}
@@ -94,7 +94,7 @@ namespace Barotrauma.Items.Components
if (isOn == value && IsActive == value) { return; }
IsActive = isOn = value;
SetLightSourceState(value, value ? lightBrightness : 0.0f);
SetLightSourceState(value);
OnStateChanged();
}
}
@@ -174,7 +174,7 @@ namespace Barotrauma.Items.Components
#if CLIENT
if (Light != null)
{
Light.Color = IsOn ? lightColor.Multiply(currentBrightness) : Color.Transparent;
Light.Color = IsOn ? lightColor.Multiply(lightBrightness) : Color.Transparent;
}
#endif
}
@@ -187,6 +187,15 @@ namespace Barotrauma.Items.Components
set;
}
[Serialize(false, IsPropertySaveable.No, description: "Should the light sprite be drawn on the item using alpha blending, in addition to being rendered in the light map? Can be used to make the light sprite stand out more.")]
public bool AlphaBlend
{
get;
set;
}
public float TemporaryFlickerTimer;
public override void Move(Vector2 amount, bool ignoreContacts = false)
{
#if CLIENT
@@ -205,7 +214,7 @@ namespace Barotrauma.Items.Components
{
if (base.IsActive == value) { return; }
base.IsActive = isOn = value;
SetLightSourceState(value, value ? lightBrightness : 0.0f);
SetLightSourceState(value);
}
}
@@ -236,9 +245,10 @@ namespace Barotrauma.Items.Components
public override void OnItemLoaded()
{
base.OnItemLoaded();
SetLightSourceState(IsActive, lightBrightness);
SetLightSourceState(IsActive);
turret = item.GetComponent<Turret>();
#if CLIENT
Drawable = AlphaBlend && Light.LightSprite != null;
if (Screen.Selected.IsEditor)
{
OnMapLoaded();
@@ -263,8 +273,7 @@ namespace Barotrauma.Items.Components
(statusEffectLists == null || !statusEffectLists.ContainsKey(ActionType.OnActive)) &&
(IsActiveConditionals == null || IsActiveConditionals.Count == 0))
{
lightBrightness = 1.0f;
SetLightSourceState(true, lightBrightness);
SetLightSourceState(true);
SetLightSourceTransformProjSpecific();
base.IsActive = false;
isOn = true;
@@ -285,13 +294,15 @@ namespace Barotrauma.Items.Components
UpdateAITarget(item.AiTarget);
}
UpdateOnActiveEffects(deltaTime);
//something in UpdateOnActiveEffects may deactivate the light -> return so we don't turn it back on
if (!IsActive) { return; }
#if CLIENT
Light.ParentSub = item.Submarine;
#endif
if (item.Container != null && !(item.GetRootInventoryOwner() is Character))
if (item.Container != null && item.GetRootInventoryOwner() is not Character)
{
SetLightSourceState(false, 0.0f);
SetLightSourceState(false);
return;
}
@@ -300,12 +311,14 @@ namespace Barotrauma.Items.Components
PhysicsBody body = ParentBody ?? item.body;
if (body != null && !body.Enabled)
{
SetLightSourceState(false, 0.0f);
SetLightSourceState(false);
return;
}
TemporaryFlickerTimer -= deltaTime;
//currPowerConsumption = powerConsumption;
if (Rand.Range(0.0f, 1.0f) < 0.05f && Voltage < Rand.Range(0.0f, MinVoltage))
if (Rand.Range(0.0f, 1.0f) < 0.05f && (Voltage < Rand.Range(0.0f, MinVoltage) || TemporaryFlickerTimer > 0.0f))
{
#if CLIENT
if (Voltage > 0.1f)
@@ -325,7 +338,7 @@ namespace Barotrauma.Items.Components
public override void UpdateBroken(float deltaTime, Camera cam)
{
SetLightSourceState(false, 0.0f);
SetLightSourceState(false);
}
public override bool Use(float deltaTime, Character character = null)
@@ -357,7 +370,7 @@ namespace Barotrauma.Items.Components
{
LightColor = XMLExtensions.ParseColor(signal.value, false);
#if CLIENT
SetLightSourceState(Light.Enabled, currentBrightness);
SetLightSourceState(Light.Enabled);
#endif
prevColorSignal = signal.value;
}
@@ -375,7 +388,7 @@ namespace Barotrauma.Items.Components
target.SightRange = Math.Max(target.SightRange, target.MaxSightRange * lightBrightness);
}
partial void SetLightSourceState(bool enabled, float brightness);
partial void SetLightSourceState(bool enabled, float? brightness = null);
public void SetLightSourceTransform()
{
@@ -25,7 +25,7 @@ namespace Barotrauma.Items.Components
private string prevSignal;
private readonly int[] channelMemory = new int[ChannelMemorySize];
private int[] channelMemory = new int[ChannelMemorySize];
private Connection signalInConnection;
private Connection signalOutConnection;
@@ -94,7 +94,17 @@ namespace Barotrauma.Items.Components
{
list.Add(this);
IsActive = true;
channelMemory = element.GetAttributeIntArray("channelmemory", new int[ChannelMemorySize]);
}
public override void Load(ContentXElement componentElement, bool usePrefabValues, IdRemap idRemap)
{
base.Load(componentElement, usePrefabValues, idRemap);
channelMemory = componentElement.GetAttributeIntArray("channelmemory", new int[ChannelMemorySize]);
if (channelMemory.Length != ChannelMemorySize)
{
DebugConsole.AddWarning($"Error when loading item {item.Prefab.Identifier}: the size of the channel memory doesn't match the default value of {ChannelMemorySize}. Resizing...");
Array.Resize(ref channelMemory, ChannelMemorySize);
}
}
public override void OnItemLoaded()
@@ -13,7 +13,7 @@ namespace Barotrauma.Items.Components
{
partial class Wire : ItemComponent, IDrawableComponent, IServerSerializable, IClientSerializable
{
partial class WireSection
public partial class WireSection
{
private Vector2 start;
private Vector2 end;
@@ -775,20 +775,25 @@ namespace Barotrauma.Items.Components
UpdateSections();
}
public override void Load(ContentXElement componentElement, bool usePrefabValues, IdRemap idRemap)
public static IEnumerable<Vector2> ExtractNodes(XElement element)
{
base.Load(componentElement, usePrefabValues, idRemap);
string nodeString = componentElement.GetAttributeString("nodes", "");
if (nodeString == "") return;
string nodeString = element.GetAttributeString("nodes", "");
if (nodeString.IsNullOrWhiteSpace()) { yield break; }
string[] nodeCoords = nodeString.Split(';');
for (int i = 0; i < nodeCoords.Length / 2; i++)
{
float.TryParse(nodeCoords[i * 2], NumberStyles.Float, CultureInfo.InvariantCulture, out float x);
float.TryParse(nodeCoords[i * 2 + 1], NumberStyles.Float, CultureInfo.InvariantCulture, out float y);
nodes.Add(new Vector2(x, y));
float.TryParse(nodeCoords[i * 2].Trim(), NumberStyles.Float, CultureInfo.InvariantCulture, out float x);
float.TryParse(nodeCoords[i * 2 + 1].Trim(), NumberStyles.Float, CultureInfo.InvariantCulture, out float y);
yield return new Vector2(x, y);
}
}
public override void Load(ContentXElement componentElement, bool usePrefabValues, IdRemap idRemap)
{
base.Load(componentElement, usePrefabValues, idRemap);
nodes.AddRange(ExtractNodes(componentElement));
Drawable = nodes.Any();
}
@@ -445,7 +445,9 @@ namespace Barotrauma.Items.Components
{
// single charged shot guns will decharge after firing
// for cosmetic reasons, this is done by lerping in half the reload time
currentChargeTime = Math.Max(0f, MaxChargeTime * (reload / reloadTime - 0.5f));
currentChargeTime = reloadTime > 0.0f ?
Math.Max(0f, MaxChargeTime * (reload / reloadTime - 0.5f)) :
0.0f;
}
else
{
@@ -44,7 +44,16 @@ namespace Barotrauma
}
public LimbType Limb { get; private set; }
public bool HideLimb { get; private set; }
public bool HideOtherWearables { get; private set; }
public enum ObscuringMode
{
None,
Hide,
AlphaClip
}
public ObscuringMode ObscureOtherWearables { get; private set; }
public bool HideOtherWearables => ObscureOtherWearables == ObscuringMode.Hide;
public bool AlphaClipOtherWearables => ObscureOtherWearables == ObscuringMode.AlphaClip;
public bool CanBeHiddenByOtherWearables { get; private set; }
public List<WearableType> HideWearablesOfType { get; private set; }
public bool InheritLimbDepth { get; private set; }
@@ -130,7 +139,7 @@ namespace Barotrauma
case WearableType.Husk:
case WearableType.Herpes:
Limb = LimbType.Head;
HideOtherWearables = false;
ObscureOtherWearables = ObscuringMode.None;
InheritLimbDepth = true;
InheritScale = true;
InheritOrigin = true;
@@ -202,7 +211,16 @@ namespace Barotrauma
Sprite = new Sprite(SourceElement, file: SpritePath);
Limb = (LimbType)Enum.Parse(typeof(LimbType), SourceElement.GetAttributeString("limb", "Head"), true);
HideLimb = SourceElement.GetAttributeBool("hidelimb", false);
HideOtherWearables = SourceElement.GetAttributeBool("hideotherwearables", false);
foreach (var mode in Enum.GetValues<ObscuringMode>())
{
if (mode == ObscuringMode.None) { continue; }
if (SourceElement.GetAttributeBool($"{mode}OtherWearables", false))
{
ObscureOtherWearables = mode;
}
}
CanBeHiddenByOtherWearables = SourceElement.GetAttributeBool("canbehiddenbyotherwearables", true);
InheritLimbDepth = SourceElement.GetAttributeBool("inheritlimbdepth", true);
var scale = SourceElement.GetAttribute("inheritscale");