Merge remote-tracking branch 'upstream/master' into develop

This commit is contained in:
EvilFactory
2024-04-24 12:20:11 -03:00
397 changed files with 15250 additions and 6473 deletions
@@ -32,11 +32,6 @@ namespace Barotrauma.Items.Components
private LocalizedString prevMsg;
private Dictionary<RelatedItem.RelationType, List<RelatedItem>> prevRequiredItems;
//the distance from the holding characters elbow to center of the physics body of the item
protected Vector2 holdPos;
protected Vector2 aimPos;
private float swingState;
private Character prevEquipper;
@@ -131,6 +126,9 @@ namespace Barotrauma.Items.Components
get { return ConvertUnits.ToDisplayUnits(holdPos); }
set { holdPos = ConvertUnits.ToSimUnits(value); }
}
//the distance from the holding characters elbow to center of the physics body of the item
protected Vector2 holdPos;
[Serialize("0.0,0.0", IsPropertySaveable.No, description: "The position the character holds the item at when aiming (in pixels, as an offset from the character's shoulder)."+
" Works similarly as HoldPos, except that the position is rotated according to the direction the player is aiming at. For example, a value of 10,-100 would make the character hold the item 100 pixels below the shoulder and 10 pixels forwards when aiming directly to the right.")]
@@ -139,6 +137,7 @@ namespace Barotrauma.Items.Components
get { return ConvertUnits.ToDisplayUnits(aimPos); }
set { aimPos = ConvertUnits.ToSimUnits(value); }
}
protected Vector2 aimPos;
protected float holdAngle;
#if DEBUG
@@ -258,12 +257,12 @@ namespace Barotrauma.Items.Components
}
canBePicked = true;
prevRequiredItems = new Dictionary<RelatedItem.RelationType, List<RelatedItem>>(RequiredItems);
if (attachable)
{
prevMsg = DisplayMsg;
prevPickKey = PickKey;
prevRequiredItems = new Dictionary<RelatedItem.RelationType, List<RelatedItem>>(requiredItems);
if (item.Submarine != null)
{
@@ -320,7 +319,7 @@ namespace Barotrauma.Items.Components
if (attachable)
{
prevMsg = DisplayMsg;
prevRequiredItems = new Dictionary<RelatedItem.RelationType, List<RelatedItem>>(requiredItems);
prevRequiredItems = new Dictionary<RelatedItem.RelationType, List<RelatedItem>>(RequiredItems);
}
}
@@ -649,7 +648,7 @@ namespace Barotrauma.Items.Components
DisplayMsg = prevMsg;
PickKey = prevPickKey;
requiredItems = new Dictionary<RelatedItem.RelationType, List<RelatedItem>>(prevRequiredItems);
RequiredItems = new Dictionary<RelatedItem.RelationType, List<RelatedItem>>(prevRequiredItems);
Attached = true;
#if CLIENT
@@ -667,7 +666,7 @@ namespace Barotrauma.Items.Components
item.DrawDepthOffset = 0.0f;
#endif
//make the item pickable with the default pick key and with no specific tools/items when it's deattached
requiredItems.Clear();
RequiredItems.Clear();
DisplayMsg = "";
PickKey = InputType.Select;
#if CLIENT
@@ -916,15 +915,25 @@ namespace Barotrauma.Items.Components
bool aim = picker.IsKeyDown(InputType.Aim) && aimPos != Vector2.Zero && picker.CanAim && !UsageDisabledByRangedWeapon(picker);
if (aim)
{
picker.AnimController.HoldItem(deltaTime, item, scaledHandlePos, holdPos + swingPos, aimPos + swingPos, aim, holdAngle, aimAngle);
if (picker.AnimController.IsHoldingToRope && GetRope() is { Snapped: false } rope)
{
Vector2 targetPos = Submarine.GetRelativeSimPosition(picker, rope.Item);
picker.AnimController.HoldItem(deltaTime, item, scaledHandlePos, itemPos: aimPos, aim: true, holdAngle, aimAngle, targetPos: targetPos);
}
else
{
picker.AnimController.HoldItem(deltaTime, item, scaledHandlePos, itemPos: aimPos + swingPos, aim: true, 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)
picker.AnimController.HoldItem(deltaTime, item, scaledHandlePos, itemPos: holdPos + swingPos, aim: false, holdAngle);
if (GetRope() is { SnapWhenNotAimed: true } rope)
{
rope.Snap();
if (rope.Item.ParentInventory == null)
{
rope.Snap();
}
}
}
}
@@ -1054,15 +1063,15 @@ namespace Barotrauma.Items.Components
}
var tempMsg = DisplayMsg;
var tempRequiredItems = requiredItems;
var tempRequiredItems = RequiredItems;
DisplayMsg = prevMsg;
requiredItems = prevRequiredItems;
RequiredItems = prevRequiredItems;
XElement saveElement = base.Save(parentElement);
DisplayMsg = tempMsg;
requiredItems = tempRequiredItems;
RequiredItems = tempRequiredItems;
return saveElement;
}
@@ -126,7 +126,7 @@ namespace Barotrauma.Items.Components
return;
}
holdable.Reattachable = false;
if (requiredItems.Any())
if (RequiredItems.Any())
{
holdable.PickingTime = float.MaxValue;
}
@@ -91,7 +91,9 @@ namespace Barotrauma.Items.Components
public override void Equip(Character character)
{
base.Equip(character);
reloadTimer = Math.Min(reload, 1.0f);
//force a wait of at least 1 second when equipping the weapon, so you can't "rapid-fire" by swapping between weapons
const float forcedDelayOnEquip = 1.0f;
reloadTimer = Math.Max(Math.Min(reload, forcedDelayOnEquip), reloadTimer);
IsActive = true;
}
@@ -218,13 +220,12 @@ namespace Barotrauma.Items.Components
AnimController ac = picker.AnimController;
if (!hitting)
{
bool aim = item.RequireAimToUse && picker.AllowInput && picker.IsKeyDown(InputType.Aim) && reloadTimer <= 0 && picker.CanAim &&
!UsageDisabledByRangedWeapon(picker);
bool aim = item.RequireAimToUse && picker.AllowInput && picker.IsKeyDown(InputType.Aim) && reloadTimer <= 0 && picker.CanAim && !UsageDisabledByRangedWeapon(picker);
if (aim)
{
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 + aimAngle, aimMelee: true);
ac.HoldItem(deltaTime, item, handlePos, itemPos: aimPos + swingPos, aim: false, hitPos, holdAngle + hitPos + aimAngle, aimMelee: true);
if (ac.InWater)
{
ac.LockFlipping();
@@ -233,7 +234,7 @@ namespace Barotrauma.Items.Components
else
{
hitPos = 0;
ac.HoldItem(deltaTime, item, handlePos, holdPos, Vector2.Zero, aim: false, holdAngle);
ac.HoldItem(deltaTime, item, handlePos, itemPos: holdPos, aim: false, holdAngle);
}
}
else
@@ -242,11 +243,11 @@ namespace Barotrauma.Items.Components
hitPos -= deltaTime * 15f;
if (Swing)
{
ac.HoldItem(deltaTime, item, handlePos, SwingPos, Vector2.Zero, aim: false, hitPos, holdAngle);
ac.HoldItem(deltaTime, item, handlePos, itemPos: SwingPos, aim: false, hitPos, holdAngle);
}
else
{
ac.HoldItem(deltaTime, item, handlePos, holdPos, Vector2.Zero, aim: false, holdAngle);
ac.HoldItem(deltaTime, item, handlePos, itemPos: holdPos, aim: false, holdAngle);
}
if (hitPos < -MathHelper.Pi)
{
@@ -82,9 +82,9 @@ namespace Barotrauma.Items.Components
var abilityPickingTime = new AbilityItemPickingTime(PickingTime, item.Prefab);
picker.CheckTalents(AbilityEffectType.OnItemPicked, abilityPickingTime);
if (requiredItems.ContainsKey(RelatedItem.RelationType.Equipped))
if (RequiredItems.ContainsKey(RelatedItem.RelationType.Equipped))
{
foreach (RelatedItem ri in requiredItems[RelatedItem.RelationType.Equipped])
foreach (RelatedItem ri in RequiredItems[RelatedItem.RelationType.Equipped])
{
foreach (var heldItem in picker.HeldItems)
{
@@ -95,6 +95,20 @@ namespace Barotrauma.Items.Components
get;
private set;
}
[Serialize(defaultValue: 1f, IsPropertySaveable.Yes, description: "Penalty multiplier to reload time when dual-wielding.")]
public float DualWieldReloadTimePenaltyMultiplier
{
get;
private set;
}
[Serialize(defaultValue: 0f, IsPropertySaveable.Yes, description: "Additive penalty to accuracy (spread angle) when dual-wielding.")]
public float DualWieldAccuracyPenalty
{
get;
private set;
}
private readonly IReadOnlySet<Identifier> suitableProjectiles;
@@ -128,8 +142,11 @@ namespace Barotrauma.Items.Components
: base(item, element)
{
item.IsShootable = true;
// TODO: should define this in xml if we have ranged weapons that don't require aim to use
item.RequireAimToUse = true;
if (element.Parent is { } parent)
{
item.RequireAimToUse = parent.GetAttributeBool(nameof(item.RequireAimToUse), true);
}
characterUsable = true;
suitableProjectiles = element.GetAttributeIdentifierArray(nameof(suitableProjectiles), Array.Empty<Identifier>()).ToHashSet();
if (ReloadSkillRequirement > 0 && ReloadNoSkill <= reload)
@@ -140,7 +157,7 @@ namespace Barotrauma.Items.Components
InitProjSpecific(element);
}
partial void InitProjSpecific(ContentXElement element);
partial void InitProjSpecific(ContentXElement rangedWeaponElement);
public override void Equip(Character character)
{
@@ -194,9 +211,28 @@ namespace Barotrauma.Items.Components
float degreeOfFailure = MathHelper.Clamp(1.0f - DegreeOfSuccess(user), 0.0f, 1.0f);
degreeOfFailure *= degreeOfFailure;
float spread = MathHelper.Lerp(Spread, UnskilledSpread, degreeOfFailure) / (1f + user.GetStatValue(StatTypes.RangedSpreadReduction));
if (user.IsDualWieldingRangedWeapons())
{
spread += Math.Max(0f, ApplyDualWieldPenaltyReduction(user, DualWieldAccuracyPenalty, neutralValue: 0f));
}
return MathHelper.ToRadians(spread);
}
/// <summary>
/// Lerps between the original penalty and a neutral value, which should be 1 for multipliers and 0 for additive penalties.
/// </summary>
/// <param name="character">The character to get stat values from</param>
/// <param name="originalPenalty">The original penalty value</param>
/// <param name="neutralValue">Neutral value to lerp towards. Should be 1 for multipliers and 0 for additives.</param>
/// <returns></returns>
private static float ApplyDualWieldPenaltyReduction(Character character, float originalPenalty, float neutralValue)
{
float statAdjustmentPrc = character.GetStatValue(StatTypes.DualWieldingPenaltyReduction);
statAdjustmentPrc = MathHelper.Clamp(statAdjustmentPrc, 0f, 1f);
float reducedPenaltyMultiplier = MathHelper.Lerp(originalPenalty, neutralValue, statAdjustmentPrc);
return reducedPenaltyMultiplier;
}
private readonly List<Body> ignoredBodies = new List<Body>();
public override bool Use(float deltaTime, Character character = null)
{
@@ -208,22 +244,27 @@ namespace Barotrauma.Items.Components
IsActive = true;
float baseReloadTime = reload;
float weaponSkill = character.GetSkillLevel("weapons");
if (ReloadSkillRequirement > 0 && ReloadNoSkill > reload && weaponSkill < ReloadSkillRequirement)
bool applyReloadFailure = ReloadSkillRequirement > 0 && ReloadNoSkill > reload && weaponSkill < ReloadSkillRequirement;
if (applyReloadFailure)
{
//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);
}
if (character.IsDualWieldingRangedWeapons())
{
baseReloadTime *= Math.Max(1f, ApplyDualWieldPenaltyReduction(character, DualWieldReloadTimePenaltyMultiplier, neutralValue: 1f));
}
ReloadTimer = baseReloadTime / (1 + character?.GetStatValue(StatTypes.RangedAttackSpeed) ?? 0f);
ReloadTimer /= 1f + item.GetQualityModifier(Quality.StatType.FiringRateMultiplier);
currentChargeTime = 0f;
if (character != null)
{
var abilityRangedWeapon = new AbilityRangedWeapon(item);
character.CheckTalents(AbilityEffectType.OnUseRangedWeapon, abilityRangedWeapon);
}
var abilityRangedWeapon = new AbilityRangedWeapon(item);
character.CheckTalents(AbilityEffectType.OnUseRangedWeapon, abilityRangedWeapon);
if (item.AiTarget != null)
{
@@ -90,12 +90,11 @@ namespace Barotrauma.Items.Components
[Serialize(false, IsPropertySaveable.No, description: "Can the item repair things through holes in walls.")]
public bool RepairThroughHoles { get; set; }
[Serialize(100.0f, IsPropertySaveable.No, description: "How far two walls need to not be considered overlapping and to stop the ray.")]
public float MaxOverlappingWallDist
{
get; set;
}
public float MaxOverlappingWallDist { get; set; }
[Serialize(1.0f, IsPropertySaveable.No, description: "How fast the tool detaches level resources (e.g. minerals). Acts as a multiplier on the speed: with a value of 2, detaching an item whose DeattachDuration is set to 30 seconds would take 15 seconds.")]
public float DeattachSpeed { get; set; }
[Serialize(true, IsPropertySaveable.No, description: "Can the item hit doors.")]
public bool HitItems { get; set; }
@@ -171,7 +170,7 @@ namespace Barotrauma.Items.Components
}
}
item.IsShootable = true;
item.RequireAimToUse = element.Parent.GetAttributeBool("requireaimtouse", true);
item.RequireAimToUse = element.Parent.GetAttributeBool(nameof(item.RequireAimToUse), true);
InitProjSpecific(element);
}
@@ -321,7 +320,7 @@ namespace Barotrauma.Items.Components
private readonly List<FireSource> fireSourcesInRange = new List<FireSource>();
private void Repair(Vector2 rayStart, Vector2 rayEnd, float deltaTime, Character user, float degreeOfSuccess, List<Body> ignoredBodies)
{
var collisionCategories = Physics.CollisionWall | Physics.CollisionItem | Physics.CollisionLevel | Physics.CollisionRepair;
var collisionCategories = Physics.CollisionWall | Physics.CollisionItem | Physics.CollisionLevel | Physics.CollisionRepairableWall;
if (!IgnoreCharacters)
{
collisionCategories |= Physics.CollisionCharacter;
@@ -414,7 +413,7 @@ namespace Barotrauma.Items.Components
break;
}
pickedPosition = rayStart + (rayEnd - rayStart) * thisBodyFraction;
if (FixBody(user, deltaTime, degreeOfSuccess, body))
if (FixBody(user, pickedPosition, deltaTime, degreeOfSuccess, body))
{
lastPickedFraction = thisBodyFraction;
if (bodyType != null) { lastHitType = bodyType; }
@@ -452,7 +451,7 @@ namespace Barotrauma.Items.Components
},
allowInsideFixture: true);
pickedPosition = Submarine.LastPickedPosition;
FixBody(user, deltaTime, degreeOfSuccess, pickedBody);
FixBody(user, pickedPosition, deltaTime, degreeOfSuccess, pickedBody);
lastPickedFraction = Submarine.LastPickedFraction;
}
@@ -543,7 +542,7 @@ namespace Barotrauma.Items.Components
}
}
private bool FixBody(Character user, float deltaTime, float degreeOfSuccess, Body targetBody)
private bool FixBody(Character user, Vector2 hitPosition, float deltaTime, float degreeOfSuccess, Body targetBody)
{
if (targetBody?.UserData == null) { return false; }
@@ -600,7 +599,7 @@ namespace Barotrauma.Items.Components
{
if (Level.Loaded?.ExtraWalls.Find(w => w.Body == cell.Body) is DestructibleLevelWall levelWall)
{
levelWall.AddDamage(-LevelWallFixAmount * deltaTime, item.WorldPosition);
levelWall.AddDamage(-LevelWallFixAmount * deltaTime, ConvertUnits.ToDisplayUnits(hitPosition));
}
return true;
}
@@ -661,10 +660,13 @@ namespace Barotrauma.Items.Components
var levelResource = targetItem.GetComponent<LevelResource>();
if (levelResource != null && levelResource.Attached &&
levelResource.requiredItems.Any() &&
levelResource.RequiredItems.Any() &&
levelResource.HasRequiredItems(user, addMessage: false))
{
float addedDetachTime = deltaTime * (1f + user.GetStatValue(StatTypes.RepairToolDeattachTimeMultiplier)) * (1f + item.GetQualityModifier(Quality.StatType.RepairToolDeattachTimeMultiplier));
float addedDetachTime = deltaTime *
DeattachSpeed *
(1f + user.GetStatValue(StatTypes.RepairToolDeattachTimeMultiplier)) *
(1f + item.GetQualityModifier(Quality.StatType.RepairToolDeattachTimeMultiplier));
levelResource.DeattachTimer += addedDetachTime;
#if CLIENT
if (targetItem.Prefab.ShowHealthBar && Character.Controlled != null &&
@@ -149,7 +149,7 @@ namespace Barotrauma.Items.Components
if (aim || throwState == ThrowState.Initiated)
{
throwAngle = System.Math.Min(throwAngle + deltaTime * 8.0f, ThrowAngleEnd);
ac.HoldItem(deltaTime, item, handlePos, aimPos, Vector2.Zero, aim: false, throwAngle);
ac.HoldItem(deltaTime, item, handlePos, itemPos: aimPos, aim: false, throwAngle);
if (throwAngle >= ThrowAngleEnd && throwState == ThrowState.Initiated)
{
throwState = ThrowState.Throwing;
@@ -158,13 +158,13 @@ namespace Barotrauma.Items.Components
else
{
throwAngle = ThrowAngleStart;
ac.HoldItem(deltaTime, item, handlePos, holdPos, Vector2.Zero, aim: false, holdAngle);
ac.HoldItem(deltaTime, item, handlePos, itemPos: aimPos, aim: false, holdAngle);
}
}
else
{
throwAngle = MathUtils.WrapAnglePi(throwAngle - deltaTime * 15.0f);
ac.HoldItem(deltaTime, item, handlePos, aimPos, Vector2.Zero, aim: false, throwAngle);
ac.HoldItem(deltaTime, item, handlePos, itemPos: aimPos, aim: false, throwAngle);
if (throwAngle < 0)
{