Merge remote-tracking branch 'upstream/master' into develop
This commit is contained in:
@@ -326,6 +326,8 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
ConnectWireBetweenPorts();
|
||||
CreateJoint(true);
|
||||
item.SendSignal("1", "on_dock");
|
||||
DockingTarget.Item.SendSignal("1", "on_dock");
|
||||
|
||||
#if SERVER
|
||||
if (GameMain.Server != null && (!item.Submarine?.Loading ?? true))
|
||||
@@ -655,7 +657,8 @@ namespace Barotrauma.Items.Components
|
||||
hullRects[i].Location -= MathUtils.ToPoint(subs[i].WorldPosition - subs[i].HiddenSubPosition);
|
||||
hulls[i] = new Hull(hullRects[i], subs[i])
|
||||
{
|
||||
RoomName = IsHorizontal ? "entityname.dockingport" : "entityname.dockinghatch"
|
||||
RoomName = IsHorizontal ? "entityname.dockingport" : "entityname.dockinghatch",
|
||||
AvoidStaying = true
|
||||
};
|
||||
hulls[i].AddToGrid(subs[i]);
|
||||
hulls[i].FreeID();
|
||||
@@ -974,6 +977,7 @@ namespace Barotrauma.Items.Components
|
||||
item.linkedTo.Clear();
|
||||
|
||||
docked = false;
|
||||
item.SendSignal("1", "on_undock");
|
||||
|
||||
Item.Submarine.RefreshOutdoorNodes();
|
||||
Item.Submarine.EnableObstructedWaypoints(DockingTarget.Item.Submarine);
|
||||
|
||||
@@ -165,6 +165,8 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public bool IsHorizontal { get; private set; }
|
||||
|
||||
public bool IsConvexHullHorizontal => autoOrientGap && linkedGap != null ? !linkedGap.IsHorizontal : IsHorizontal;
|
||||
|
||||
[Serialize("0.0,0.0,0.0,0.0", IsPropertySaveable.No, description: "Position and size of the window on the door. The upper left corner is 0,0. Set the width and height to 0 if you don't want the door to have a window.")]
|
||||
public Rectangle Window { get; set; }
|
||||
|
||||
@@ -321,7 +323,7 @@ namespace Barotrauma.Items.Components
|
||||
public override bool Pick(Character picker)
|
||||
{
|
||||
if (item.Condition < RepairThreshold && item.GetComponent<Repairable>().HasRequiredItems(picker, addMessage: false)) { return true; }
|
||||
if (requiredItems.None()) { return false; }
|
||||
if (RequiredItems.None()) { return false; }
|
||||
if (HasAccess(picker) && HasRequiredItems(picker, false)) { return false; }
|
||||
return base.Pick(picker);
|
||||
}
|
||||
@@ -559,6 +561,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public void RefreshLinkedGap()
|
||||
{
|
||||
LinkedGap.Layer = item.Layer;
|
||||
LinkedGap.ConnectedDoor = this;
|
||||
if (autoOrientGap)
|
||||
{
|
||||
@@ -572,10 +575,10 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
RefreshLinkedGap();
|
||||
#if CLIENT
|
||||
convexHull = new ConvexHull(doorRect, IsHorizontal, item);
|
||||
convexHull = new ConvexHull(doorRect, IsConvexHullHorizontal, item);
|
||||
if (Window != Rectangle.Empty)
|
||||
{
|
||||
convexHull2 = new ConvexHull(doorRect, IsHorizontal, item);
|
||||
convexHull2 = new ConvexHull(doorRect, IsConvexHullHorizontal, item);
|
||||
}
|
||||
UpdateConvexHulls();
|
||||
#endif
|
||||
|
||||
@@ -77,6 +77,9 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
Stem = 0b0000,
|
||||
CrossJunction = 0b1111,
|
||||
HorizontalLine = 0b1010,
|
||||
VerticalLine = 0b0101,
|
||||
/*backwards compatibility, the vertical and horizontal "lane" used to be backwards*/
|
||||
VerticalLane = 0b1010,
|
||||
HorizontalLane = 0b0101,
|
||||
TurnTopRight = 0b1001,
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -50,10 +50,10 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public readonly Dictionary<ActionType, List<StatusEffect>> statusEffectLists;
|
||||
|
||||
public Dictionary<RelatedItem.RelationType, List<RelatedItem>> requiredItems;
|
||||
public Dictionary<RelatedItem.RelationType, List<RelatedItem>> RequiredItems;
|
||||
public readonly List<RelatedItem> DisabledRequiredItems = new List<RelatedItem>();
|
||||
|
||||
public List<Skill> requiredSkills;
|
||||
public readonly List<Skill> RequiredSkills = new List<Skill>();
|
||||
|
||||
private ItemComponent parent;
|
||||
public ItemComponent Parent
|
||||
@@ -288,9 +288,7 @@ namespace Barotrauma.Items.Components
|
||||
originalElement = element;
|
||||
name = element.Name.ToString();
|
||||
SerializableProperties = SerializableProperty.GetProperties(this);
|
||||
requiredItems = new Dictionary<RelatedItem.RelationType, List<RelatedItem>>();
|
||||
requiredSkills = new List<Skill>();
|
||||
|
||||
RequiredItems = new Dictionary<RelatedItem.RelationType, List<RelatedItem>>();
|
||||
#if CLIENT
|
||||
hasSoundsOfType = new bool[Enum.GetValues(typeof(ActionType)).Length];
|
||||
sounds = new Dictionary<ActionType, List<ItemSound>>();
|
||||
@@ -338,7 +336,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
else
|
||||
{
|
||||
requiredSkills = component.requiredSkills;
|
||||
RequiredSkills = component.RequiredSkills;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -391,7 +389,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
|
||||
Identifier skillIdentifier = subElement.GetAttributeIdentifier("identifier", "");
|
||||
requiredSkills.Add(new Skill(skillIdentifier, subElement.GetAttributeInt("level", 0)));
|
||||
RequiredSkills.Add(new Skill(skillIdentifier, subElement.GetAttributeInt("level", 0)));
|
||||
break;
|
||||
case "statuseffect":
|
||||
statusEffectLists ??= new Dictionary<ActionType, List<StatusEffect>>();
|
||||
@@ -416,7 +414,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
void LoadStatusEffect(ContentXElement subElement)
|
||||
{
|
||||
var statusEffect = StatusEffect.Load(subElement, item.Name);
|
||||
var statusEffect = StatusEffect.Load(subElement, item.Name + ", " + GetType().Name);
|
||||
if (!statusEffectLists.TryGetValue(statusEffect.type, out List<StatusEffect> effectList))
|
||||
{
|
||||
effectList = new List<StatusEffect>();
|
||||
@@ -446,11 +444,11 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!requiredItems.ContainsKey(ri.Type))
|
||||
if (!RequiredItems.ContainsKey(ri.Type))
|
||||
{
|
||||
requiredItems.Add(ri.Type, new List<RelatedItem>());
|
||||
RequiredItems.Add(ri.Type, new List<RelatedItem>());
|
||||
}
|
||||
requiredItems[ri.Type].Add(ri);
|
||||
RequiredItems[ri.Type].Add(ri);
|
||||
}
|
||||
}
|
||||
else if (!allowEmpty)
|
||||
@@ -677,7 +675,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public bool HasRequiredSkills(Character character, out Skill insufficientSkill)
|
||||
{
|
||||
foreach (Skill skill in requiredSkills)
|
||||
foreach (Skill skill in RequiredSkills)
|
||||
{
|
||||
float characterLevel = character.GetSkillLevel(skill.Identifier);
|
||||
if (characterLevel < skill.Level * GetSkillMultiplier())
|
||||
@@ -698,7 +696,7 @@ namespace Barotrauma.Items.Components
|
||||
/// <returns>0.5f if all the skills meet the skill requirements exactly, 1.0f if they're way above and 0.0f if way less</returns>
|
||||
public float DegreeOfSuccess(Character character)
|
||||
{
|
||||
return DegreeOfSuccess(character, requiredSkills);
|
||||
return DegreeOfSuccess(character, RequiredSkills);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -733,16 +731,18 @@ namespace Barotrauma.Items.Components
|
||||
public virtual void FlipY(bool relativeToSub) { }
|
||||
|
||||
/// <summary>
|
||||
/// Shorthand for !HasRequiredContainedItems()
|
||||
/// Returns true if the item is lacking required contained items, or if there's nothing with a non-zero condition inside.
|
||||
/// </summary>
|
||||
public bool IsEmpty(Character user) => !HasRequiredContainedItems(user, addMessage: false);
|
||||
public bool IsEmpty(Character user) =>
|
||||
!HasRequiredContainedItems(user, addMessage: false) ||
|
||||
(Item.OwnInventory != null && !Item.OwnInventory.AllItems.Any(i => i.Condition > 0));
|
||||
|
||||
public bool HasRequiredContainedItems(Character user, bool addMessage, LocalizedString msg = null)
|
||||
{
|
||||
if (!requiredItems.ContainsKey(RelatedItem.RelationType.Contained)) { return true; }
|
||||
if (!RequiredItems.ContainsKey(RelatedItem.RelationType.Contained)) { return true; }
|
||||
if (item.OwnInventory == null) { return false; }
|
||||
|
||||
foreach (RelatedItem ri in requiredItems[RelatedItem.RelationType.Contained])
|
||||
foreach (RelatedItem ri in RequiredItems[RelatedItem.RelationType.Contained])
|
||||
{
|
||||
if (!ri.CheckRequirements(user, item))
|
||||
{
|
||||
@@ -767,8 +767,8 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (character.IsBot && item.IgnoreByAI(character)) { return false; }
|
||||
if (!item.IsInteractable(character)) { return false; }
|
||||
if (requiredItems.Count == 0) { return true; }
|
||||
if (character.Inventory != null && requiredItems.TryGetValue(RelatedItem.RelationType.Picked, out List<RelatedItem> relatedItems))
|
||||
if (RequiredItems.Count == 0) { return true; }
|
||||
if (character.Inventory != null && RequiredItems.TryGetValue(RelatedItem.RelationType.Picked, out List<RelatedItem> relatedItems))
|
||||
{
|
||||
foreach (RelatedItem relatedItem in relatedItems)
|
||||
{
|
||||
@@ -813,13 +813,13 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public virtual bool HasRequiredItems(Character character, bool addMessage, LocalizedString msg = null)
|
||||
{
|
||||
if (requiredItems.None()) { return true; }
|
||||
if (RequiredItems.None()) { return true; }
|
||||
if (character.Inventory == null) { return false; }
|
||||
bool hasRequiredItems = false;
|
||||
bool canContinue = true;
|
||||
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])
|
||||
{
|
||||
canContinue = CheckItems(ri, character.HeldItems);
|
||||
if (!canContinue) { break; }
|
||||
@@ -827,9 +827,9 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
if (canContinue)
|
||||
{
|
||||
if (requiredItems.ContainsKey(RelatedItem.RelationType.Picked))
|
||||
if (RequiredItems.ContainsKey(RelatedItem.RelationType.Picked))
|
||||
{
|
||||
foreach (RelatedItem ri in requiredItems[RelatedItem.RelationType.Picked])
|
||||
foreach (RelatedItem ri in RequiredItems[RelatedItem.RelationType.Picked])
|
||||
{
|
||||
if (!CheckItems(ri, character.Inventory.AllItems)) { break; }
|
||||
}
|
||||
@@ -1058,7 +1058,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
XElement componentElement = new XElement(name);
|
||||
|
||||
foreach (var kvp in requiredItems)
|
||||
foreach (var kvp in RequiredItems)
|
||||
{
|
||||
foreach (RelatedItem ri in kvp.Value)
|
||||
{
|
||||
@@ -1091,8 +1091,8 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
private void OverrideRequiredItems(ContentXElement element)
|
||||
{
|
||||
var prevRequiredItems = new Dictionary<RelatedItem.RelationType, List<RelatedItem>>(requiredItems);
|
||||
requiredItems.Clear();
|
||||
var prevRequiredItems = new Dictionary<RelatedItem.RelationType, List<RelatedItem>>(RequiredItems);
|
||||
RequiredItems.Clear();
|
||||
|
||||
bool returnEmptyRequirements = false;
|
||||
#if CLIENT
|
||||
@@ -1117,11 +1117,11 @@ namespace Barotrauma.Items.Components
|
||||
newRequiredItem.IgnoreInEditor = prevRequiredItem.IgnoreInEditor;
|
||||
}
|
||||
|
||||
if (!requiredItems.ContainsKey(newRequiredItem.Type))
|
||||
if (!RequiredItems.ContainsKey(newRequiredItem.Type))
|
||||
{
|
||||
requiredItems[newRequiredItem.Type] = new List<RelatedItem>();
|
||||
RequiredItems[newRequiredItem.Type] = new List<RelatedItem>();
|
||||
}
|
||||
requiredItems[newRequiredItem.Type].Add(newRequiredItem);
|
||||
RequiredItems[newRequiredItem.Type].Add(newRequiredItem);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -673,7 +673,7 @@ namespace Barotrauma.Items.Components
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (AutoInteractWithContained && character.SelectedItem == null)
|
||||
if (AutoInteractWithContained && character.SelectedItem == null && Screen.Selected is not { IsEditor: true })
|
||||
{
|
||||
foreach (Item contained in Inventory.AllItems)
|
||||
{
|
||||
@@ -708,7 +708,7 @@ namespace Barotrauma.Items.Components
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (AutoInteractWithContained)
|
||||
if (AutoInteractWithContained && Screen.Selected is not { IsEditor: true })
|
||||
{
|
||||
foreach (Item contained in Inventory.AllItems)
|
||||
{
|
||||
|
||||
+32
-4
@@ -34,10 +34,16 @@ namespace Barotrauma.Items.Components
|
||||
get { return outputContainer; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Should the output items left in the deconstructor be automatically moved to the main sub at the end of the round
|
||||
/// if the deconstructor is not in the main sub?
|
||||
/// </summary>
|
||||
public bool RelocateOutputToMainSub;
|
||||
|
||||
[Serialize(false, IsPropertySaveable.Yes)]
|
||||
public bool DeconstructItemsSimultaneously { get; set; }
|
||||
|
||||
[Editable, Serialize(1.0f, IsPropertySaveable.Yes)]
|
||||
[Editable(MinValueFloat = 0.1f, MaxValueFloat = 1000), Serialize(1.0f, IsPropertySaveable.Yes)]
|
||||
public float DeconstructionSpeed { get; set; }
|
||||
|
||||
public Deconstructor(Item item, ContentXElement element)
|
||||
@@ -290,6 +296,10 @@ namespace Barotrauma.Items.Components
|
||||
spawnedItem.AllowStealing = targetItem.AllowStealing;
|
||||
spawnedItem.OriginalOutpost = targetItem.OriginalOutpost;
|
||||
spawnedItem.SpawnedInCurrentOutpost = targetItem.SpawnedInCurrentOutpost;
|
||||
if (RelocateOutputToMainSub && user is { AIController: HumanAIController humanAi })
|
||||
{
|
||||
humanAi.HandleRelocation(spawnedItem);
|
||||
}
|
||||
for (int i = 0; i < outputContainer.Capacity; i++)
|
||||
{
|
||||
var containedItem = outputContainer.Inventory.GetItemAt(i);
|
||||
@@ -318,7 +328,12 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
GameAnalyticsManager.AddDesignEvent("ItemDeconstructed:" + (GameMain.GameSession?.GameMode?.Preset.Identifier.Value ?? "none") + ":" + targetItem.Prefab.Identifier);
|
||||
if (targetItem.Prefab.ContentPackage == ContentPackageManager.VanillaCorePackage &&
|
||||
/* we don't need info of every item, we can get a good sample size just by logging 5% */
|
||||
Rand.Range(0.0f, 1.0f) < 0.05f)
|
||||
{
|
||||
GameAnalyticsManager.AddDesignEvent("ItemDeconstructed:" + (GameMain.GameSession?.GameMode?.Preset.Identifier.Value ?? "none") + ":" + targetItem.Prefab.Identifier);
|
||||
}
|
||||
|
||||
bool? result = GameMain.LuaCs.Hook.Call<bool?>("item.deconstructed", targetItem, this, user, allowRemove);
|
||||
if (result == true) { return; }
|
||||
@@ -332,6 +347,10 @@ namespace Barotrauma.Items.Components
|
||||
foreach (Item outputItem in ic.Inventory.AllItemsMod)
|
||||
{
|
||||
tryPutInOutputSlots(outputItem);
|
||||
if (RelocateOutputToMainSub && user != null && user.AIController is HumanAIController humanAi)
|
||||
{
|
||||
humanAi.HandleRelocation(outputItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
inputContainer.Inventory.RemoveItem(targetItem);
|
||||
@@ -439,11 +458,12 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
private void SetActive(bool active, Character user = null)
|
||||
public void SetActive(bool active, Character user = null, bool createNetworkEvent = false)
|
||||
{
|
||||
PutItemsToLinkedContainer();
|
||||
|
||||
this.user = user;
|
||||
RelocateOutputToMainSub = false;
|
||||
|
||||
if (inputContainer.Inventory.IsEmpty()) { active = false; }
|
||||
|
||||
@@ -456,6 +476,10 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
GameServer.Log(GameServer.CharacterLogName(user) + (IsActive ? " activated " : " deactivated ") + item.Name, ServerLog.MessageType.ItemInteraction);
|
||||
}
|
||||
if (createNetworkEvent)
|
||||
{
|
||||
item.CreateServerEvent(this);
|
||||
}
|
||||
#endif
|
||||
if (!IsActive)
|
||||
{
|
||||
@@ -465,7 +489,11 @@ namespace Barotrauma.Items.Components
|
||||
#if CLIENT
|
||||
else
|
||||
{
|
||||
HintManager.OnStartDeconstructing(user, this);
|
||||
HintManager.OnStartDeconstructing(user, this);
|
||||
if (Item.Submarine is { Info.IsOutpost: true } && user is { IsBot: true })
|
||||
{
|
||||
HintManager.OnItemMarkedForRelocation();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
private ItemContainer inputContainer, outputContainer;
|
||||
|
||||
[Serialize(1.0f, IsPropertySaveable.Yes)]
|
||||
[Editable(MinValueFloat = 0.1f, MaxValueFloat = 1000), Serialize(1.0f, IsPropertySaveable.Yes)]
|
||||
public float FabricationSpeed { get; set; }
|
||||
|
||||
[Serialize(1.0f, IsPropertySaveable.Yes)]
|
||||
@@ -469,7 +469,10 @@ namespace Barotrauma.Items.Components
|
||||
character.CheckTalents(AbilityEffectType.OnAllyItemFabricatedAmount, fabricationitemAmount);
|
||||
}
|
||||
user.CheckTalents(AbilityEffectType.OnItemFabricatedAmount, fabricationitemAmount);
|
||||
quality = GetFabricatedItemQuality(fabricatedItem, user).RollQuality();
|
||||
quality =
|
||||
fabricatedItem.TargetItem.MaxStackSize > 1 ?
|
||||
GetFabricatedItemQuality(fabricatedItem, user).Quality :
|
||||
GetFabricatedItemQuality(fabricatedItem, user).RollQuality();
|
||||
}
|
||||
|
||||
int amount = (int)fabricationitemAmount.Value;
|
||||
@@ -490,7 +493,12 @@ namespace Barotrauma.Items.Components
|
||||
for (int i = 0; i < amount; i++)
|
||||
{
|
||||
float outCondition = fabricatedItem.OutCondition;
|
||||
GameAnalyticsManager.AddDesignEvent("ItemFabricated:" + (GameMain.GameSession?.GameMode?.Preset.Identifier.Value ?? "none") + ":" + fabricatedItem.TargetItem.Identifier);
|
||||
if (fabricatedItem.TargetItem.ContentPackage == ContentPackageManager.VanillaCorePackage &&
|
||||
/* we don't need info of every fabricated item, we can get a good sample size just by logging 5% */
|
||||
Rand.Range(0.0f, 1.0f) < 0.05f)
|
||||
{
|
||||
GameAnalyticsManager.AddDesignEvent("ItemFabricated:" + (GameMain.GameSession?.GameMode?.Preset.Identifier.Value ?? "none") + ":" + fabricatedItem.TargetItem.Identifier);
|
||||
}
|
||||
if (i < amountFittingContainer)
|
||||
{
|
||||
Entity.Spawner.AddItemToSpawnQueue(fabricatedItem.TargetItem, outputContainer.Inventory, fabricatedItem.TargetItem.Health * outCondition, quality,
|
||||
@@ -577,11 +585,11 @@ namespace Barotrauma.Items.Components
|
||||
public static float CalculateBonusRollPercentage(float skillLevel, float target)
|
||||
=> Math.Clamp((skillLevel - target) / (100f - target) * 100f, min: 0, max: 100);
|
||||
|
||||
public readonly record struct QualityResult(int Quality, float PlusOnePercentage, float PlusTwoPercentage)
|
||||
public readonly record struct QualityResult(int Quality, bool HasRandomQuality, float PlusOnePercentage, float PlusTwoPercentage)
|
||||
{
|
||||
public static readonly QualityResult Empty = new QualityResult(0, 0, 0);
|
||||
public static readonly QualityResult Empty = new QualityResult(0, true, 0, 0);
|
||||
|
||||
public bool HasRandomQualityRollChance => PlusOnePercentage > 0f || PlusTwoPercentage > 0f;
|
||||
public bool HasRandomQualityRollChance => HasRandomQuality && (PlusOnePercentage > 0f || PlusTwoPercentage > 0f);
|
||||
|
||||
// The total real world percentage for a roll to succeed, taking into account that +1 needs to succeed for +2 to be attempted and
|
||||
// that the chance for only +1 goes down as +2 increases since some of the +1's will turn into +2s
|
||||
@@ -676,9 +684,23 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
bool hasRandomQuality = !(fabricatedItem.TargetItem.MaxStackSize > 1); //don't randomise items with a stacksize > 1
|
||||
float PlusOnePercentage = plusOne.Match(some: static f => f, none: static () => 0f);
|
||||
float PlusTwoPercentage = plusTwo.Match(some: static f => f, none: static () => 0f);
|
||||
|
||||
if (!hasRandomQuality && PlusOnePercentage > 0)
|
||||
{
|
||||
quality++;
|
||||
if (PlusTwoPercentage > 0)
|
||||
{
|
||||
quality++;
|
||||
}
|
||||
}
|
||||
|
||||
return new QualityResult(quality,
|
||||
PlusOnePercentage: plusOne.Match(some: static f => f, none: static () => 0f),
|
||||
PlusTwoPercentage: plusTwo.Match(some: static f => f, none: static () => 0f));
|
||||
hasRandomQuality,
|
||||
PlusOnePercentage,
|
||||
PlusTwoPercentage);
|
||||
}
|
||||
|
||||
partial void UpdateRequiredTimeProjSpecific();
|
||||
@@ -690,6 +712,8 @@ namespace Barotrauma.Items.Components
|
||||
GameSession.GetSessionCrewCharacters(CharacterType.Bot).Any(c => c.HasRecipeForItem(item.Identifier));
|
||||
}
|
||||
|
||||
private readonly HashSet<Item> usedIngredients = new HashSet<Item>();
|
||||
|
||||
private bool CanBeFabricated(FabricationRecipe fabricableItem, IReadOnlyDictionary<Identifier, List<Item>> availableIngredients, Character character)
|
||||
{
|
||||
if (fabricableItem == null) { return false; }
|
||||
@@ -733,22 +757,26 @@ namespace Barotrauma.Items.Components
|
||||
return false;
|
||||
}
|
||||
|
||||
//maintain a list of used ingredients so we don't end up considering the same item a suitable for multiple required ingredients
|
||||
usedIngredients.Clear();
|
||||
|
||||
return fabricableItem.RequiredItems.All(requiredItem =>
|
||||
{
|
||||
int availablePrefabsAmount = 0;
|
||||
int availableItemsAmount = 0;
|
||||
foreach (ItemPrefab requiredPrefab in requiredItem.ItemPrefabs)
|
||||
{
|
||||
if (!availableIngredients.ContainsKey(requiredPrefab.Identifier)) { continue; }
|
||||
if (!availableIngredients.TryGetValue(requiredPrefab.Identifier, out var availableItems)) { continue; }
|
||||
|
||||
var availablePrefabs = availableIngredients[requiredPrefab.Identifier];
|
||||
foreach (Item availablePrefab in availablePrefabs)
|
||||
foreach (Item availableItem in availableItems)
|
||||
{
|
||||
if (requiredItem.IsConditionSuitable(availablePrefab.ConditionPercentage))
|
||||
if (usedIngredients.Contains(availableItem)) { continue; }
|
||||
if (requiredItem.IsConditionSuitable(availableItem.ConditionPercentage))
|
||||
{
|
||||
availablePrefabsAmount++;
|
||||
usedIngredients.Add(availableItem);
|
||||
availableItemsAmount++;
|
||||
}
|
||||
|
||||
if (availablePrefabsAmount >= requiredItem.Amount)
|
||||
if (availableItemsAmount >= requiredItem.Amount)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
+15
-1
@@ -91,7 +91,7 @@ namespace Barotrauma.Items.Components
|
||||
ventList.Clear();
|
||||
foreach (MapEntity entity in item.linkedTo)
|
||||
{
|
||||
if (!(entity is Item linkedItem)) { continue; }
|
||||
if (entity is not Item linkedItem) { continue; }
|
||||
|
||||
Vent vent = linkedItem.GetComponent<Vent>();
|
||||
if (vent?.Item.CurrentHull == null) { continue; }
|
||||
@@ -132,5 +132,19 @@ namespace Barotrauma.Items.Components
|
||||
vent.IsActive = true;
|
||||
}
|
||||
}
|
||||
|
||||
public float GetVentOxygenFlow(Vent targetVent)
|
||||
{
|
||||
if (ventList == null)
|
||||
{
|
||||
GetVents();
|
||||
}
|
||||
foreach ((Vent vent, float hullVolume) in ventList)
|
||||
{
|
||||
if (vent != targetVent) { continue; }
|
||||
return generatedAmount * 100.0f * (hullVolume / totalHullVolume);
|
||||
}
|
||||
return 0.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
@@ -215,10 +214,10 @@ namespace Barotrauma.Items.Components
|
||||
activePings[currentPingIndex].Direction = pingDirection;
|
||||
activePings[currentPingIndex].State = 0.0f;
|
||||
activePings[currentPingIndex].PrevPingRadius = 0.0f;
|
||||
if (item.AiTarget != null)
|
||||
foreach (AITarget aiTarget in GetAITargets())
|
||||
{
|
||||
item.AiTarget.SectorDegrees = useDirectionalPing ? DirectionalPingSector : 360.0f;
|
||||
item.AiTarget.SectorDir = new Vector2(pingDirection.X, -pingDirection.Y);
|
||||
aiTarget.SectorDegrees = useDirectionalPing ? DirectionalPingSector : 360.0f;
|
||||
aiTarget.SectorDir = new Vector2(pingDirection.X, -pingDirection.Y);
|
||||
}
|
||||
item.Use(deltaTime);
|
||||
}
|
||||
@@ -231,10 +230,10 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
for (var pingIndex = 0; pingIndex < activePingsCount;)
|
||||
{
|
||||
if (item.AiTarget != null)
|
||||
foreach (AITarget aiTarget in GetAITargets())
|
||||
{
|
||||
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));
|
||||
float range = MathUtils.InverseLerp(aiTarget.MinSoundRange, aiTarget.MaxSoundRange, Range * activePings[pingIndex].State / zoom);
|
||||
aiTarget.SoundRange = Math.Max(aiTarget.SoundRange, MathHelper.Lerp(aiTarget.MinSoundRange, aiTarget.MaxSoundRange, range));
|
||||
}
|
||||
if (activePings[pingIndex].State > 1.0f)
|
||||
{
|
||||
@@ -254,6 +253,24 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<AITarget> GetAITargets()
|
||||
{
|
||||
if (!UseTransducers)
|
||||
{
|
||||
if (item.AiTarget != null) { yield return item.AiTarget; }
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var transducer in connectedTransducers)
|
||||
{
|
||||
if (transducer.Transducer.Item.AiTarget != null)
|
||||
{
|
||||
yield return transducer.Transducer.Item.AiTarget;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Power consumption of the sonar. Only consume power when active and adjust the consumption based on the sonar mode.
|
||||
/// </summary>
|
||||
|
||||
@@ -22,6 +22,11 @@ namespace Barotrauma.Items.Components
|
||||
private const float AutoPilotMaxSpeed = 0.5f;
|
||||
private const float AIPilotMaxSpeed = 1.0f;
|
||||
|
||||
/// <summary>
|
||||
/// How many units before crush depth the pressure warning is shown
|
||||
/// </summary>
|
||||
public const float PressureWarningThreshold = 500.0f;
|
||||
|
||||
/// <summary>
|
||||
/// How fast the steering vector adjusts when the nav terminal is operated by something else than a character (= signals)
|
||||
/// </summary>
|
||||
|
||||
@@ -636,6 +636,8 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
if (fixture.Body.UserData is VineTile) { return true; }
|
||||
if (fixture.CollidesWith == Category.None) { return true; }
|
||||
//only collides with characters = probably an "outsideCollisionBlocker" created by a gap
|
||||
if (fixture.CollidesWith == Physics.CollisionCharacter) { return true; }
|
||||
|
||||
if (fixture.Body.UserData as string == "ruinroom" || fixture.Body.UserData is Hull || fixture.UserData is Hull) { return true; }
|
||||
|
||||
@@ -689,6 +691,8 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
if (fixture.Body.UserData is VineTile) { return -1; }
|
||||
if (fixture.CollidesWith == Category.None) { return -1; }
|
||||
//only collides with characters = probably an "outsideCollisionBlocker" created by a gap
|
||||
if (fixture.CollidesWith == Physics.CollisionCharacter) { return -1; }
|
||||
if (fixture.Body.UserData is Item item)
|
||||
{
|
||||
if (item.Condition <= 0) { return -1; }
|
||||
@@ -945,9 +949,15 @@ namespace Barotrauma.Items.Components
|
||||
item.body.SimPosition - ConvertUnits.ToSimUnits(sub.Position) - dir,
|
||||
item.body.SimPosition - ConvertUnits.ToSimUnits(sub.Position) + dir,
|
||||
collisionCategory: Physics.CollisionWall);
|
||||
|
||||
Vector2 launchPosInCurrentCoordinateSpace = launchPos;
|
||||
if (item.body.Submarine == null && LaunchSub != null)
|
||||
{
|
||||
launchPosInCurrentCoordinateSpace += ConvertUnits.ToSimUnits(LaunchSub.Position);
|
||||
}
|
||||
if (wallBody?.FixtureList?.First() != null && (wallBody.UserData is Structure || wallBody.UserData is Item) &&
|
||||
//ignore the hit if it's behind the position the item was launched from, and the projectile is travelling in the opposite direction
|
||||
Vector2.Dot((item.body.SimPosition + normalizedVel) - launchPos, dir) > 0)
|
||||
Vector2.Dot((item.body.SimPosition + normalizedVel) - launchPosInCurrentCoordinateSpace, dir) > 0)
|
||||
{
|
||||
target = wallBody.FixtureList.First();
|
||||
if (hits.Contains(target.Body))
|
||||
|
||||
@@ -144,8 +144,10 @@ namespace Barotrauma.Items.Components
|
||||
private bool tinkeringPowersDevices;
|
||||
public bool TinkeringPowersDevices => tinkeringPowersDevices;
|
||||
|
||||
public bool IsBelowRepairThreshold => item.ConditionPercentage <= RepairThreshold;
|
||||
public bool IsBelowRepairIconThreshold => item.ConditionPercentage <= RepairThreshold / 2;
|
||||
public bool IsBelowRepairThreshold => item.ConditionPercentageRelativeToDefaultMaxCondition < RepairThreshold;
|
||||
|
||||
public bool IsBelowRepairIconThreshold => item.ConditionPercentageRelativeToDefaultMaxCondition < RepairThreshold / 2;
|
||||
|
||||
|
||||
public enum FixActions : int
|
||||
{
|
||||
@@ -186,6 +188,21 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
// Modify damage (not stun) caused by repair failure based on campaign settings
|
||||
if (GameMain.GameSession?.Campaign is CampaignMode campaign
|
||||
&& statusEffectLists != null
|
||||
&& statusEffectLists.TryGetValue(ActionType.OnFailure, out var onFailureEffects))
|
||||
{
|
||||
foreach (var effect in onFailureEffects)
|
||||
{
|
||||
foreach (Affliction affliction in effect.Afflictions)
|
||||
{
|
||||
if (affliction.Prefab.AfflictionType == Tags.Stun) { continue; }
|
||||
affliction.Strength *= campaign.Settings.RepairFailMultiplier;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
InitProjSpecific(element);
|
||||
}
|
||||
|
||||
@@ -203,12 +220,12 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (character == null) { return false; }
|
||||
|
||||
if (statusEffectLists == null || statusEffectLists.None(s => s.Key == ActionType.OnFailure)) { return true; }
|
||||
if (statusEffectLists == null) { return true; }
|
||||
|
||||
if (bestRepairItem != null && bestRepairItem.Prefab.CannotRepairFail) { return true; }
|
||||
|
||||
// unpowered (electrical) items can be repaired without a risk of electrical shock
|
||||
if (requiredSkills.Any(s => s != null && s.Identifier == "electrical"))
|
||||
if (RequiredSkills.Any(s => s != null && s.Identifier == "electrical"))
|
||||
{
|
||||
if (item.GetComponent<Reactor>() is Reactor reactor)
|
||||
{
|
||||
@@ -216,18 +233,29 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
else if (item.GetComponent<Powered>() is Powered powered && powered.Voltage < 0.1f)
|
||||
{
|
||||
return true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (Rand.Range(0.0f, 0.5f) < RepairDegreeOfSuccess(character, requiredSkills)) { return true; }
|
||||
bool success = Rand.Range(0.0f, 0.5f) < RepairDegreeOfSuccess(character, RequiredSkills);
|
||||
ActionType actionType = success ? ActionType.OnSuccess : ActionType.OnFailure;
|
||||
|
||||
ApplyStatusEffects(ActionType.OnFailure, 1.0f, character);
|
||||
if (bestRepairItem != null && bestRepairItem.GetComponent<Holdable>() is Holdable h)
|
||||
ApplyStatusEffectsAndCreateEntityEvent(this, actionType, character);
|
||||
ApplyStatusEffectsAndCreateEntityEvent(this, ActionType.OnUse, character);
|
||||
if (bestRepairItem != null && bestRepairItem.GetComponent<Holdable>() is Holdable holdable)
|
||||
{
|
||||
h.ApplyStatusEffects(ActionType.OnFailure, 1.0f, character);
|
||||
ApplyStatusEffectsAndCreateEntityEvent(holdable, actionType, character);
|
||||
ApplyStatusEffectsAndCreateEntityEvent(holdable, ActionType.OnUse, character);
|
||||
}
|
||||
return false;
|
||||
static void ApplyStatusEffectsAndCreateEntityEvent(ItemComponent ic, ActionType actionType, Character character)
|
||||
{
|
||||
ic.ApplyStatusEffects(actionType, 1.0f, character);
|
||||
if (GameMain.NetworkMember is { IsServer: true } && ic.statusEffectLists != null && ic.statusEffectLists.ContainsKey(actionType))
|
||||
{
|
||||
GameMain.NetworkMember.CreateEntityEvent(ic.Item, new Item.ApplyStatusEffectEventData(actionType, ic, character));
|
||||
}
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
public override float GetSkillMultiplier()
|
||||
@@ -251,9 +279,9 @@ namespace Barotrauma.Items.Components
|
||||
if (CurrentFixer == null) { return; }
|
||||
if (qteSuccess)
|
||||
{
|
||||
item.Condition += RepairDegreeOfSuccess(CurrentFixer, requiredSkills) * 3 * (currentFixerAction == FixActions.Repair ? 1.0f : -1.0f);
|
||||
item.Condition += RepairDegreeOfSuccess(CurrentFixer, RequiredSkills) * 3 * (currentFixerAction == FixActions.Repair ? 1.0f : -1.0f);
|
||||
}
|
||||
else if (Rand.Range(0.0f, 2.0f) > RepairDegreeOfSuccess(CurrentFixer, requiredSkills))
|
||||
else if (Rand.Range(0.0f, 2.0f) > RepairDegreeOfSuccess(CurrentFixer, RequiredSkills))
|
||||
{
|
||||
ApplyStatusEffects(ActionType.OnFailure, 1.0f, CurrentFixer);
|
||||
#if SERVER
|
||||
@@ -283,12 +311,6 @@ namespace Barotrauma.Items.Components
|
||||
if (!CheckCharacterSuccess(character, bestRepairItem))
|
||||
{
|
||||
GameServer.Log($"{GameServer.CharacterLogName(character)} failed to {(action == FixActions.Sabotage ? "sabotage" : "repair")} {item.Name}", ServerLog.MessageType.ItemInteraction);
|
||||
GameMain.Server?.CreateEntityEvent(item, new Item.ApplyStatusEffectEventData(ActionType.OnFailure, this, character));
|
||||
if (bestRepairItem != null && bestRepairItem.GetComponent<Holdable>() is Holdable h)
|
||||
{
|
||||
GameMain.Server?.CreateEntityEvent(bestRepairItem, new Item.ApplyStatusEffectEventData(ActionType.OnFailure, h, character));
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -449,7 +471,7 @@ namespace Barotrauma.Items.Components
|
||||
return;
|
||||
}
|
||||
|
||||
float successFactor = requiredSkills.Count == 0 ? 1.0f : RepairDegreeOfSuccess(CurrentFixer, requiredSkills);
|
||||
float successFactor = RequiredSkills.Count == 0 ? 1.0f : RepairDegreeOfSuccess(CurrentFixer, RequiredSkills);
|
||||
|
||||
//item must have been below the repair threshold for the player to get an achievement or XP for repairing it
|
||||
if (IsBelowRepairThreshold)
|
||||
@@ -462,9 +484,16 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
|
||||
float talentMultiplier = CurrentFixer.GetStatValue(StatTypes.RepairSpeed);
|
||||
if (requiredSkills.Any(static skill => skill.Identifier == "mechanical"))
|
||||
foreach (Skill skill in RequiredSkills)
|
||||
{
|
||||
talentMultiplier += CurrentFixer.GetStatValue(StatTypes.MechanicalRepairSpeed);
|
||||
if (skill.Identifier == "mechanical")
|
||||
{
|
||||
talentMultiplier += CurrentFixer.GetStatValue(StatTypes.MechanicalRepairSpeed);
|
||||
}
|
||||
else if (skill.Identifier == "electrical")
|
||||
{
|
||||
talentMultiplier += CurrentFixer.GetStatValue(StatTypes.ElectricalRepairSpeed);
|
||||
}
|
||||
}
|
||||
|
||||
float fixDuration = MathHelper.Lerp(FixDurationLowSkill, FixDurationHighSkill, successFactor);
|
||||
@@ -493,7 +522,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (wasBroken)
|
||||
{
|
||||
foreach (Skill skill in requiredSkills)
|
||||
foreach (Skill skill in RequiredSkills)
|
||||
{
|
||||
CurrentFixer.Info?.ApplySkillGain(skill.Identifier, SkillSettings.Current.SkillIncreasePerRepair);
|
||||
}
|
||||
@@ -527,7 +556,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (wasGoodCondition)
|
||||
{
|
||||
foreach (Skill skill in requiredSkills)
|
||||
foreach (Skill skill in RequiredSkills)
|
||||
{
|
||||
float characterSkillLevel = CurrentFixer.GetSkillLevel(skill.Identifier);
|
||||
CurrentFixer.Info?.IncreaseSkillLevel(skill.Identifier,
|
||||
@@ -578,11 +607,11 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (character == null) { return 1.0f; }
|
||||
// kind of rough to keep this in update, but seems most robust
|
||||
if (requiredSkills.Any(s => s != null && s.Identifier == "mechanical"))
|
||||
if (RequiredSkills.Any(s => s != null && s.Identifier == "mechanical"))
|
||||
{
|
||||
return 1 + character.GetStatValue(StatTypes.MaxRepairConditionMultiplierMechanical);
|
||||
}
|
||||
if (requiredSkills.Any(s => s != null && s.Identifier == "electrical"))
|
||||
if (RequiredSkills.Any(s => s != null && s.Identifier == "electrical"))
|
||||
{
|
||||
return 1 + character.GetStatValue(StatTypes.MaxRepairConditionMultiplierElectrical);
|
||||
}
|
||||
|
||||
@@ -11,8 +11,8 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
private ISpatialEntity source;
|
||||
private Item target;
|
||||
|
||||
private Vector2? launchDir;
|
||||
private float currentRopeLength;
|
||||
|
||||
private void SetSource(ISpatialEntity source)
|
||||
{
|
||||
@@ -73,6 +73,13 @@ namespace Barotrauma.Items.Components
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(200.0f, IsPropertySaveable.No, description: "At which distance the user stops pulling the target?")]
|
||||
public float MinPullDistance
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(360.0f, IsPropertySaveable.No, description: "The maximum angle from the source to the target until the rope breaks.")]
|
||||
public float MaxAngle
|
||||
@@ -108,7 +115,8 @@ namespace Barotrauma.Items.Components
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
|
||||
private bool isReelingIn;
|
||||
private bool snapped;
|
||||
public bool Snapped
|
||||
{
|
||||
@@ -134,6 +142,15 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
snapTimer = 0;
|
||||
}
|
||||
else if (target != null && source != null && target != source)
|
||||
{
|
||||
#if CLIENT
|
||||
// Play a sound at both ends. Initially tested playing the sound in the middle when the rope snaps in the middle,
|
||||
// but I think it's more important to ensure that the players hear the sound.
|
||||
PlaySound(snapSound, source.WorldPosition);
|
||||
PlaySound(snapSound, target.WorldPosition);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -156,14 +173,17 @@ namespace Barotrauma.Items.Components
|
||||
ApplyStatusEffects(ActionType.OnUse, 1.0f, worldPosition: item.WorldPosition);
|
||||
IsActive = true;
|
||||
}
|
||||
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
var user = item.GetComponent<Projectile>()?.User;
|
||||
UpdateProjSpecific();
|
||||
isReelingIn = false;
|
||||
Character user = item.GetComponent<Projectile>()?.User;
|
||||
if (source == null || target == null || target.Removed ||
|
||||
(source is Entity sourceEntity && sourceEntity.Removed) ||
|
||||
(source is Limb limb && limb.Removed) ||
|
||||
(user != null && user.Removed))
|
||||
source is Entity { Removed: true } ||
|
||||
source is Limb { Removed: true } ||
|
||||
user is null ||
|
||||
user is { Removed: true })
|
||||
{
|
||||
ResetSource();
|
||||
target = null;
|
||||
@@ -191,11 +211,8 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
if (MaxAngle < 180 && lengthSqr > 2500)
|
||||
{
|
||||
if (launchDir == null)
|
||||
{
|
||||
launchDir = diff;
|
||||
}
|
||||
float angle = MathHelper.ToDegrees(VectorExtensions.Angle(launchDir.Value, diff));
|
||||
launchDir ??= diff;
|
||||
float angle = MathHelper.ToDegrees(launchDir.Value.Angle(diff));
|
||||
if (angle > MaxAngle)
|
||||
{
|
||||
Snap();
|
||||
@@ -262,8 +279,8 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
|
||||
Vector2 forceDir = diff;
|
||||
float distance = diff.Length();
|
||||
if (distance > 0.001f)
|
||||
currentRopeLength = diff.Length();
|
||||
if (currentRopeLength > 0.001f)
|
||||
{
|
||||
forceDir = Vector2.Normalize(forceDir);
|
||||
}
|
||||
@@ -277,87 +294,162 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
float targetMass = float.MaxValue;
|
||||
Character targetCharacter = null;
|
||||
if (projectile.StickTarget.UserData is Limb targetLimb)
|
||||
switch (projectile.StickTarget.UserData)
|
||||
{
|
||||
targetCharacter = targetLimb.character;
|
||||
targetMass = targetLimb.ragdoll.Mass;
|
||||
}
|
||||
else if (projectile.StickTarget.UserData is Character character)
|
||||
{
|
||||
targetCharacter = character;
|
||||
targetMass = character.Mass;
|
||||
}
|
||||
else if (projectile.StickTarget.UserData is Item item)
|
||||
{
|
||||
targetMass = projectile.StickTarget.Mass;
|
||||
case Limb targetLimb:
|
||||
targetCharacter = targetLimb.character;
|
||||
targetMass = targetLimb.ragdoll.Mass;
|
||||
break;
|
||||
case Character character:
|
||||
targetCharacter = character;
|
||||
targetMass = character.Mass;
|
||||
break;
|
||||
case Item _:
|
||||
targetMass = projectile.StickTarget.Mass;
|
||||
break;
|
||||
}
|
||||
if (projectile.StickTarget.BodyType != BodyType.Dynamic)
|
||||
{
|
||||
targetMass = float.MaxValue;
|
||||
}
|
||||
if (targetMass > TargetMinMass)
|
||||
if (!snapped)
|
||||
{
|
||||
if (Math.Abs(SourcePullForce) > 0.001f)
|
||||
user.AnimController.HoldToRope();
|
||||
if (targetCharacter != null)
|
||||
{
|
||||
var sourceBody = GetBodyToPull(source);
|
||||
if (sourceBody != null)
|
||||
targetCharacter.AnimController.DragWithRope();
|
||||
}
|
||||
if (user.InWater)
|
||||
{
|
||||
user.AnimController.HangWithRope();
|
||||
}
|
||||
}
|
||||
if (Math.Abs(SourcePullForce) > 0.001f && targetMass > TargetMinMass)
|
||||
{
|
||||
// This should be the main collider.
|
||||
var sourceBody = GetBodyToPull(source);
|
||||
if (sourceBody != null)
|
||||
{
|
||||
isReelingIn = user.InWater && user.IsRagdolled || !user.InWater && targetCharacter is { IsIncapacitated: false };
|
||||
if (isReelingIn)
|
||||
{
|
||||
if (user != null && user.InWater)
|
||||
float pullForce = SourcePullForce;
|
||||
if (!user.InWater)
|
||||
{
|
||||
// Apply a tiny amount to the character holding the rope, so that the connection "feels" more real.
|
||||
pullForce *= 0.1f;
|
||||
}
|
||||
float lengthFactor = MathUtils.InverseLerp(0, MaxLength / 2, currentRopeLength);
|
||||
float force = LerpForces ? MathHelper.Lerp(0, pullForce, lengthFactor) : pullForce;
|
||||
sourceBody.ApplyForce(forceDir * force);
|
||||
// Take the target velocity into account.
|
||||
PhysicsBody targetBody = GetBodyToPull(target);
|
||||
if (targetBody != null)
|
||||
{
|
||||
if (user.IsRagdolled)
|
||||
{
|
||||
// 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;
|
||||
var targetCollider = targetCharacter.AnimController.Collider;
|
||||
if (myCollider.LinearVelocity != Vector2.Zero && targetCollider.LinearVelocity != Vector2.Zero)
|
||||
if (targetBody.LinearVelocity != Vector2.Zero && sourceBody.LinearVelocity != Vector2.Zero)
|
||||
{
|
||||
if (Vector2.Dot(Vector2.Normalize(myCollider.LinearVelocity), Vector2.Normalize(targetCollider.LinearVelocity)) < 0)
|
||||
Vector2 targetDir = Vector2.Normalize(targetBody.LinearVelocity);
|
||||
float movementDot = Vector2.Dot(Vector2.Normalize(sourceBody.LinearVelocity), targetDir);
|
||||
if (movementDot < 0)
|
||||
{
|
||||
myCollider.ApplyForce(targetCollider.LinearVelocity * targetCollider.Mass);
|
||||
// Pushing to a different dir -> add some counter force
|
||||
const float multiplier = 5;
|
||||
float inverseLengthFactor = MathHelper.Lerp(1, 0, lengthFactor);
|
||||
sourceBody.ApplyForce(targetBody.LinearVelocity * Math.Min(targetBody.Mass * multiplier, 250) * sourceBody.Mass * -movementDot * inverseLengthFactor);
|
||||
}
|
||||
float forceDot = Vector2.Dot(forceDir, targetDir);
|
||||
if (forceDot > 0)
|
||||
{
|
||||
// Pulling to the same dir -> add extra force
|
||||
float targetSpeed = targetBody.LinearVelocity.Length();
|
||||
const float multiplier = 25;
|
||||
sourceBody.ApplyForce(forceDir * targetSpeed * sourceBody.Mass * multiplier * forceDot * lengthFactor);
|
||||
}
|
||||
float colliderMainLimbDistance = Vector2.Distance(sourceBody.SimPosition, user.AnimController.MainLimb.SimPosition);
|
||||
const float minDist = 1;
|
||||
const float maxDist = 10;
|
||||
if (colliderMainLimbDistance > minDist)
|
||||
{
|
||||
// Move the ragdoll closer to the collider, if it's too far (the correction force in HumanAnimController is not enough -> the ragdoll would lag behind and get teleported).
|
||||
float correctionForce = MathHelper.Lerp(10.0f, NetConfig.MaxPhysicsBodyVelocity, MathUtils.InverseLerp(minDist, maxDist, colliderMainLimbDistance));
|
||||
Vector2 targetPos = sourceBody.SimPosition + new Vector2((float)Math.Sin(-sourceBody.Rotation), (float)Math.Cos(-sourceBody.Rotation)) * 0.4f;
|
||||
user.AnimController.MainLimb.MoveToPos(targetPos, correctionForce);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var targetBody = GetBodyToPull(target);
|
||||
if (targetBody != null)
|
||||
{
|
||||
sourceBody.ApplyForce(targetBody.LinearVelocity * sourceBody.Mass);
|
||||
}
|
||||
sourceBody.ApplyForce(targetBody.LinearVelocity * sourceBody.Mass);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (Math.Abs(TargetPullForce) > 0.001f)
|
||||
if (Math.Abs(TargetPullForce) > 0.001f && !user.IsRagdolled)
|
||||
{
|
||||
var targetBody = GetBodyToPull(target);
|
||||
PhysicsBody targetBody = GetBodyToPull(target);
|
||||
if (targetBody == null) { return; }
|
||||
bool lerpForces = LerpForces;
|
||||
if (!lerpForces && user != null && targetCharacter != null && !user.AnimController.InWater)
|
||||
float maxVelocity = NetConfig.MaxPhysicsBodyVelocity * 0.25f;
|
||||
// The distance where we start pulling with max force.
|
||||
float maxPullDistance = MaxLength / 3;
|
||||
float minPullDistance = MinPullDistance;
|
||||
const float absoluteMinPullDistance = 50;
|
||||
if (targetCharacter != null)
|
||||
{
|
||||
if ((forceDir.X < 0) != (user.AnimController.Dir < 0))
|
||||
if (targetCharacter.IsRagdolled || targetCharacter.IsUnconscious)
|
||||
{
|
||||
// Prevents rubberbanding horizontally when dragging a corpse.
|
||||
lerpForces = true;
|
||||
if (!targetCharacter.InWater)
|
||||
{
|
||||
// Limits the velocity of ragdolled characters on ground/air, because otherwise they tend to move with too high forces.
|
||||
maxVelocity = NetConfig.MaxPhysicsBodyVelocity * 0.075f;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Target alive and kicking -> Use the absolute min pull distance and full forces to pull.
|
||||
// Keep some lerping, because it results into smoothing when the target is close by.
|
||||
minPullDistance = absoluteMinPullDistance;
|
||||
maxPullDistance = 200;
|
||||
}
|
||||
}
|
||||
float force = lerpForces ? MathHelper.Lerp(0, TargetPullForce, MathUtils.InverseLerp(0, MaxLength / 3, distance - 50)) : TargetPullForce;
|
||||
targetBody?.ApplyForce(-forceDir * force);
|
||||
var targetRagdoll = targetCharacter?.AnimController;
|
||||
if (targetRagdoll?.Collider != null && (targetRagdoll.InWater || targetRagdoll.OnGround))
|
||||
minPullDistance = MathHelper.Max(minPullDistance, absoluteMinPullDistance);
|
||||
if (currentRopeLength < minPullDistance) { return; }
|
||||
maxPullDistance = MathHelper.Max(minPullDistance * 2, maxPullDistance);
|
||||
float force = lerpForces
|
||||
? MathHelper.Lerp(0, TargetPullForce, MathUtils.InverseLerp(minPullDistance, maxPullDistance, currentRopeLength))
|
||||
: TargetPullForce;
|
||||
targetBody.ApplyForce(-forceDir * force, maxVelocity);
|
||||
AnimController targetRagdoll = targetCharacter?.AnimController;
|
||||
if (targetRagdoll?.Collider != null)
|
||||
{
|
||||
targetRagdoll.Collider.ApplyForce(-forceDir * force * 3);
|
||||
isReelingIn = true;
|
||||
if (targetRagdoll.InWater || targetRagdoll.OnGround)
|
||||
{
|
||||
float forceMultiplier = 1;
|
||||
if (!targetCharacter.IsRagdolled && !targetCharacter.IsIncapacitated)
|
||||
{
|
||||
// Pulling the main collider requires higher forces when the target is trying to move away.
|
||||
Vector2 targetMovement = targetCharacter.AnimController.TargetMovement;
|
||||
float dot = Vector2.Dot(Vector2.Normalize(targetMovement), forceDir);
|
||||
if (dot > 0)
|
||||
{
|
||||
const float constMultiplier = 2.5f;
|
||||
float targetVelocity = targetMovement.Length();
|
||||
float massFactor = Math.Max((float)Math.Log(targetCharacter.Mass / 10), 1);
|
||||
forceMultiplier = Math.Max(targetVelocity * massFactor * constMultiplier * dot, 1);
|
||||
}
|
||||
}
|
||||
targetRagdoll.Collider.ApplyForce(-forceDir * force * forceMultiplier, maxVelocity);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
partial void UpdateProjSpecific();
|
||||
|
||||
public override void UpdateBroken(float deltaTime, Camera cam)
|
||||
{
|
||||
@@ -409,32 +501,22 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (target is Item targetItem)
|
||||
{
|
||||
if (targetItem.ParentInventory is CharacterInventory characterInventory &&
|
||||
characterInventory.Owner is Character ownerCharacter)
|
||||
if (targetItem.ParentInventory is CharacterInventory { Owner: Character ownerCharacter })
|
||||
{
|
||||
if (ownerCharacter.Removed) { return null; }
|
||||
return ownerCharacter.AnimController.Collider;
|
||||
}
|
||||
var projectile = targetItem.GetComponent<Projectile>();
|
||||
if (projectile != null && projectile.StickTarget != null)
|
||||
if (projectile is { StickTarget: not null })
|
||||
{
|
||||
if (projectile.StickTarget.UserData is Structure structure)
|
||||
return projectile.StickTarget.UserData switch
|
||||
{
|
||||
return structure.Submarine?.PhysicsBody;
|
||||
}
|
||||
else if (projectile.StickTarget.UserData is Submarine sub)
|
||||
{
|
||||
return sub.PhysicsBody;
|
||||
}
|
||||
else if (projectile.StickTarget.UserData is Item item)
|
||||
{
|
||||
return item.body;
|
||||
}
|
||||
else if (projectile.StickTarget.UserData is Limb limb)
|
||||
{
|
||||
return limb.body;
|
||||
}
|
||||
return null;
|
||||
Structure structure => structure.Submarine?.PhysicsBody,
|
||||
Submarine sub => sub.PhysicsBody,
|
||||
Item item => item.body,
|
||||
Limb limb => limb.body,
|
||||
_ => null
|
||||
};
|
||||
}
|
||||
if (targetItem.body != null) { return targetItem.body; }
|
||||
}
|
||||
|
||||
@@ -22,6 +22,8 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public readonly List<CircuitBoxInputOutputNode> InputOutputNodes = new();
|
||||
|
||||
public readonly List<CircuitBoxLabelNode> Labels = new();
|
||||
|
||||
public readonly List<CircuitBoxWire> Wires = new List<CircuitBoxWire>();
|
||||
|
||||
public override bool IsActive => true;
|
||||
@@ -80,6 +82,9 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public bool IsFull => ComponentContainer?.Inventory is { } inventory && inventory.IsFull(true);
|
||||
|
||||
[Editable, Serialize(false, IsPropertySaveable.Yes, description: "Locked circuit boxes can only be viewed and not interacted with.")]
|
||||
public bool Locked { get; set; }
|
||||
|
||||
public CircuitBox(Item item, ContentXElement element) : base(item, element)
|
||||
{
|
||||
containers = item.GetComponents<ItemContainer>().ToArray();
|
||||
@@ -176,6 +181,9 @@ namespace Barotrauma.Items.Components
|
||||
case "outputnode":
|
||||
LoadFor(CircuitBoxInputOutputNode.Type.Output, subElement);
|
||||
break;
|
||||
case "label":
|
||||
Labels.Add(CircuitBoxLabelNode.LoadFromXML(subElement, this));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -204,11 +212,14 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
Components.Clear();
|
||||
Wires.Clear();
|
||||
Labels.Clear();
|
||||
|
||||
foreach (var origComp in original.Components)
|
||||
foreach (var label in original.Labels)
|
||||
{
|
||||
var newComponent = new CircuitBoxComponent(origComp.ID, clonedContainedItems[origComp.Item.ID], origComp.Position, this, origComp.UsedResource);
|
||||
Components.Add(newComponent);
|
||||
var newLabel = new CircuitBoxLabelNode(label.ID, label.Color, label.Position, this);
|
||||
newLabel.EditText(label.HeaderText, label.BodyText);
|
||||
newLabel.ApplyResize(label.Size, label.Position);
|
||||
Labels.Add(newLabel);
|
||||
}
|
||||
|
||||
for (int ioIndex = 0; ioIndex < original.InputOutputNodes.Count; ioIndex++)
|
||||
@@ -219,10 +230,19 @@ namespace Barotrauma.Items.Components
|
||||
cloneNode.Position = origNode.Position;
|
||||
}
|
||||
|
||||
if (!clonedContainedItems.Any()) { return; }
|
||||
|
||||
foreach (var origComp in original.Components)
|
||||
{
|
||||
if (!clonedContainedItems.TryGetValue(origComp.Item.ID, out var clonedItem)) { continue; }
|
||||
var newComponent = new CircuitBoxComponent(origComp.ID, clonedItem, origComp.Position, this, origComp.UsedResource);
|
||||
Components.Add(newComponent);
|
||||
}
|
||||
|
||||
foreach (var origWire in original.Wires)
|
||||
{
|
||||
Option<CircuitBoxConnection> to = CircuitBoxConnectorIdentifier.FromConnection(origWire.To).FindConnection(this),
|
||||
from = CircuitBoxConnectorIdentifier.FromConnection(origWire.From).FindConnection(this);
|
||||
from = CircuitBoxConnectorIdentifier.FromConnection(origWire.From).FindConnection(this);
|
||||
|
||||
if (!to.TryUnwrap(out var toConn) || !from.TryUnwrap(out var fromConn))
|
||||
{
|
||||
@@ -230,7 +250,8 @@ namespace Barotrauma.Items.Components
|
||||
continue;
|
||||
}
|
||||
|
||||
var newWire = new CircuitBoxWire(this, origWire.ID, origWire.BackingWire.Select(w => clonedContainedItems[w.ID]), fromConn, toConn, origWire.UsedItemPrefab);
|
||||
var wireItem = origWire.BackingWire.Select(w => clonedContainedItems[w.ID]);
|
||||
var newWire = new CircuitBoxWire(this, origWire.ID, wireItem, fromConn, toConn, origWire.UsedItemPrefab);
|
||||
Wires.Add(newWire);
|
||||
}
|
||||
}
|
||||
@@ -254,6 +275,11 @@ namespace Barotrauma.Items.Components
|
||||
componentElement.Add(wire.Save());
|
||||
}
|
||||
|
||||
foreach (var label in Labels)
|
||||
{
|
||||
componentElement.Add(label.Save());
|
||||
}
|
||||
|
||||
return componentElement;
|
||||
}
|
||||
|
||||
@@ -324,6 +350,36 @@ namespace Barotrauma.Items.Components
|
||||
return true;
|
||||
}
|
||||
|
||||
private void AddLabelInternal(ushort id, Color color, Vector2 pos, NetLimitedString header, NetLimitedString body)
|
||||
{
|
||||
var newLabel = new CircuitBoxLabelNode(id, color, pos, this);
|
||||
newLabel.EditText(header, body);
|
||||
Labels.Add(newLabel);
|
||||
OnViewUpdateProjSpecific();
|
||||
}
|
||||
|
||||
private void RemoveLabelInternal(IReadOnlyCollection<ushort> ids)
|
||||
{
|
||||
foreach (CircuitBoxLabelNode node in Labels.ToImmutableArray())
|
||||
{
|
||||
if (!ids.Contains(node.ID)) { continue; }
|
||||
Labels.Remove(node);
|
||||
}
|
||||
OnViewUpdateProjSpecific();
|
||||
}
|
||||
|
||||
private void ResizeLabelInternal(ushort id, Vector2 pos, Vector2 size)
|
||||
{
|
||||
size = Vector2.Max(size, CircuitBoxLabelNode.MinSize);
|
||||
foreach (CircuitBoxLabelNode node in Labels)
|
||||
{
|
||||
if (node.ID != id) { continue; }
|
||||
node.ApplyResize(size, pos);
|
||||
break;
|
||||
}
|
||||
OnViewUpdateProjSpecific();
|
||||
}
|
||||
|
||||
private static bool IsExternalConnection(CircuitBoxConnection conn) => conn is (CircuitBoxInputConnection or CircuitBoxOutputConnection);
|
||||
|
||||
private void CreateWireWithoutItem(CircuitBoxConnection one, CircuitBoxConnection two, ushort id, ItemPrefab prefab)
|
||||
@@ -380,6 +436,18 @@ namespace Barotrauma.Items.Components
|
||||
private void AddWireDirect(ushort id, ItemPrefab prefab, Option<Item> backingItem, CircuitBoxConnection one, CircuitBoxConnection two)
|
||||
=> Wires.Add(new CircuitBoxWire(this, id, backingItem, one, two, prefab));
|
||||
|
||||
private void RenameLabelInternal(ushort id, Color color, NetLimitedString header, NetLimitedString body)
|
||||
{
|
||||
foreach (CircuitBoxLabelNode node in Labels)
|
||||
{
|
||||
if (node.ID != id) { continue; }
|
||||
|
||||
node.EditText(header, body);
|
||||
node.Color = color;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private bool AddComponentInternal(ushort id, ItemPrefab prefab, ItemPrefab usedResource, Vector2 pos, Character? user, Action<Item>? onItemSpawned)
|
||||
{
|
||||
if (id is ICircuitBoxIdentifiable.NullComponentID)
|
||||
@@ -426,6 +494,21 @@ namespace Barotrauma.Items.Components
|
||||
ClearSelectionFor(characterId, Components);
|
||||
ClearSelectionFor(characterId, InputOutputNodes);
|
||||
ClearSelectionFor(characterId, Wires);
|
||||
ClearSelectionFor(characterId, Labels);
|
||||
}
|
||||
|
||||
private void SelectLabelsInternal(IReadOnlyCollection<ushort> ids, ushort characterId, bool overwrite)
|
||||
{
|
||||
if (overwrite) { ClearSelectionFor(characterId, Labels); }
|
||||
|
||||
if (!ids.Any()) { return; }
|
||||
|
||||
foreach (CircuitBoxLabelNode node in Labels)
|
||||
{
|
||||
if (!ids.Contains(node.ID)) { continue; }
|
||||
|
||||
node.SetSelected(Option.Some(characterId));
|
||||
}
|
||||
}
|
||||
|
||||
private void SelectComponentsInternal(IReadOnlyCollection<ushort> ids, ushort characterId, bool overwrite)
|
||||
@@ -444,7 +527,8 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
private void UpdateSelections(ImmutableDictionary<ushort, Option<ushort>> nodeIds,
|
||||
ImmutableDictionary<ushort, Option<ushort>> wireIds,
|
||||
ImmutableDictionary<CircuitBoxInputOutputNode.Type, Option<ushort>> inputOutputs)
|
||||
ImmutableDictionary<CircuitBoxInputOutputNode.Type, Option<ushort>> inputOutputs,
|
||||
ImmutableDictionary<ushort, Option<ushort>> labels)
|
||||
{
|
||||
foreach (var wire in Wires)
|
||||
{
|
||||
@@ -474,6 +558,13 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
node.SetSelected(selectedBy);
|
||||
}
|
||||
|
||||
foreach (var node in Labels)
|
||||
{
|
||||
if (!labels.TryGetValue(node.ID, out var selectedBy)) { continue; }
|
||||
|
||||
node.SetSelected(selectedBy);
|
||||
}
|
||||
}
|
||||
|
||||
private void SelectWiresInternal(IReadOnlyCollection<ushort> ids, ushort characterId, bool overwrite)
|
||||
@@ -555,6 +646,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
private void MoveNodesInternal(IReadOnlyCollection<ushort> ids,
|
||||
IReadOnlyCollection<CircuitBoxInputOutputNode.Type> ios,
|
||||
IReadOnlyCollection<ushort> labels,
|
||||
Vector2 moveAmount)
|
||||
{
|
||||
IEnumerable<CircuitBoxComponent> nodes = Components.Where(node => ids.Contains(node.ID));
|
||||
@@ -563,6 +655,11 @@ namespace Barotrauma.Items.Components
|
||||
node.Position += moveAmount;
|
||||
}
|
||||
|
||||
foreach (var label in Labels.Where(n => labels.Contains(n.ID)))
|
||||
{
|
||||
label.Position += moveAmount;
|
||||
}
|
||||
|
||||
|
||||
foreach (var io in InputOutputNodes)
|
||||
{
|
||||
@@ -635,7 +732,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
public static ImmutableArray<Item> GetSortedCircuitBoxSortedItemsFromPlayer(Character? character)
|
||||
public static ImmutableArray<Item> GetSortedCircuitBoxItemsFromPlayer(Character? character)
|
||||
=> character?.Inventory?.FindAllItems(predicate: CanItemBeAccessed, recursive: true)
|
||||
.OrderBy(static i => i.Prefab.Identifier == Tags.FPGACircuit)
|
||||
.ToImmutableArray() ?? ImmutableArray<Item>.Empty;
|
||||
@@ -651,7 +748,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (character is null) { return Option.None; }
|
||||
|
||||
return GetApplicableResourcePlayerHas(prefab, GetSortedCircuitBoxSortedItemsFromPlayer(character));
|
||||
return GetApplicableResourcePlayerHas(prefab, GetSortedCircuitBoxItemsFromPlayer(character));
|
||||
}
|
||||
|
||||
public static Option<Item> GetApplicableResourcePlayerHas(ItemPrefab prefab, ImmutableArray<Item> playerItems)
|
||||
|
||||
@@ -292,9 +292,8 @@ namespace Barotrauma.Items.Components
|
||||
refSub = attachTarget?.Submarine;
|
||||
}
|
||||
|
||||
Vector2 nodePos = refSub == null ?
|
||||
newConnection.Item.Position :
|
||||
newConnection.Item.Position - refSub.HiddenSubPosition;
|
||||
Vector2 nodePos = RoundNode(newConnection.Item.Position);
|
||||
if (refSub != null) { nodePos -= refSub.HiddenSubPosition; }
|
||||
|
||||
if (nodes.Count > 0 && nodes[0] == nodePos) { return; }
|
||||
if (nodes.Count > 1 && nodes[nodes.Count - 1] == nodePos) { return; }
|
||||
@@ -469,9 +468,7 @@ namespace Barotrauma.Items.Components
|
||||
Vector2 mouseDiff = user.CursorWorldPosition - user.WorldPosition;
|
||||
mouseDiff = mouseDiff.ClampLength(MaxAttachDistance);
|
||||
|
||||
return new Vector2(
|
||||
MathUtils.RoundTowardsClosest(user.Position.X + mouseDiff.X, Submarine.GridSize.X),
|
||||
MathUtils.RoundTowardsClosest(user.Position.Y + mouseDiff.Y, Submarine.GridSize.Y));
|
||||
return RoundNode(user.Position + mouseDiff);
|
||||
}
|
||||
|
||||
public override bool Use(float deltaTime, Character character = null)
|
||||
@@ -662,11 +659,14 @@ namespace Barotrauma.Items.Components
|
||||
Drawable = sections.Count > 0;
|
||||
}
|
||||
|
||||
private Vector2 RoundNode(Vector2 position)
|
||||
private static Vector2 RoundNode(Vector2 position)
|
||||
{
|
||||
position.X = MathUtils.Round(position.X, Submarine.GridSize.X / 2.0f);
|
||||
position.Y = MathUtils.Round(position.Y, Submarine.GridSize.Y / 2.0f);
|
||||
return position;
|
||||
Vector2 halfGrid = Submarine.GridSize / 2;
|
||||
|
||||
position += halfGrid;
|
||||
position.X = MathUtils.RoundTowardsClosest(position.X, Submarine.GridSize.X / 2.0f);
|
||||
position.Y = MathUtils.RoundTowardsClosest(position.Y, Submarine.GridSize.Y / 2.0f);
|
||||
return position - halfGrid;
|
||||
}
|
||||
|
||||
public void SetConnectedDirty()
|
||||
|
||||
@@ -143,7 +143,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
private bool OnCollision(Fixture sender, Fixture other, Contact contact)
|
||||
{
|
||||
if (!(LevelTrigger.GetEntity(other) is Entity entity)) { return false; }
|
||||
if (LevelTrigger.GetEntity(other) is not Entity entity) { return false; }
|
||||
if (!LevelTrigger.IsTriggeredByEntity(entity, triggeredBy, mustBeOnSpecificSub: (!MoveOutsideSub, item.Submarine))) { return false; }
|
||||
triggerers.Add(entity);
|
||||
return true;
|
||||
@@ -151,7 +151,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
private void OnSeparation(Fixture sender, Fixture other, Contact contact)
|
||||
{
|
||||
if (!(LevelTrigger.GetEntity(other) is Entity entity))
|
||||
if (LevelTrigger.GetEntity(other) is not Entity entity)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -233,7 +233,17 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
else if (triggerer is Character c)
|
||||
{
|
||||
ApplyForce(c.AnimController.Collider);
|
||||
if (c.AnimController.Collider.BodyType == BodyType.Dynamic)
|
||||
{
|
||||
if (c.AnimController.Collider.Enabled)
|
||||
{
|
||||
ApplyForce(c.AnimController.Collider);
|
||||
}
|
||||
foreach (var limb in c.AnimController.Limbs)
|
||||
{
|
||||
ApplyForce(limb.body, multiplier: limb.Mass * c.AnimController.Collider.Mass / c.AnimController.Mass);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (triggerer is Submarine s)
|
||||
{
|
||||
@@ -248,13 +258,13 @@ namespace Barotrauma.Items.Components
|
||||
item.SendSignal(IsActive ? "1" : "0", "state_out");
|
||||
}
|
||||
|
||||
private void ApplyForce(PhysicsBody body)
|
||||
private void ApplyForce(PhysicsBody body, float multiplier = 1.0f)
|
||||
{
|
||||
Vector2 diff = ConvertUnits.ToDisplayUnits(PhysicsBody.SimPosition - body.SimPosition);
|
||||
if (diff.LengthSquared() < 0.0001f) { return; }
|
||||
float distanceFactor = DistanceBasedForce ? LevelTrigger.GetDistanceFactor(body, PhysicsBody, RadiusInDisplayUnits) : 1.0f;
|
||||
if (distanceFactor <= 0.0f) { return; }
|
||||
Vector2 force = distanceFactor * (CurrentForceFluctuation * Force) * Vector2.Normalize(diff);
|
||||
Vector2 force = distanceFactor * (CurrentForceFluctuation * Force) * Vector2.Normalize(diff) * multiplier;
|
||||
if (force.LengthSquared() < 0.01f) { return; }
|
||||
body.ApplyForce(force);
|
||||
}
|
||||
|
||||
@@ -338,6 +338,11 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// How deep down does the item protect from pressure? Determined by status effects.
|
||||
/// </summary>
|
||||
public readonly float PressureProtection;
|
||||
|
||||
public Wearable(Item item, ContentXElement element) : base(item, element)
|
||||
{
|
||||
this.item = item;
|
||||
@@ -415,6 +420,12 @@ namespace Barotrauma.Items.Components
|
||||
WearableStatValues.TryAdd(statType, statValue);
|
||||
}
|
||||
break;
|
||||
case "statuseffect":
|
||||
if (subElement.GetAttributeString("Target", string.Empty).ToLowerInvariant().Contains("character"))
|
||||
{
|
||||
PressureProtection = Math.Max(subElement.GetAttributeFloat(nameof(PressureProtection), 0), PressureProtection);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user