Unstable 0.17.4.0
This commit is contained in:
@@ -74,7 +74,7 @@ namespace Barotrauma.Items.Components
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(true, IsPropertySaveable.No, description: "Should the OnUse StatusEffects trigger when docking (on vanilla docking ports these effects emit particles and play a sound).)")]
|
||||
[Editable, Serialize(true, IsPropertySaveable.No, description: "Should the OnUse StatusEffects trigger when docking (on vanilla docking ports these effects emit particles and play a sound).)")]
|
||||
public bool ApplyEffectsOnDocking
|
||||
{
|
||||
get;
|
||||
|
||||
@@ -200,7 +200,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
int index = i - 1;
|
||||
string attributeName = "handle" + i;
|
||||
var attribute = element.Attribute(attributeName);
|
||||
var attribute = element.GetAttribute(attributeName);
|
||||
// If no value is defind for handle2, use the value of handle1.
|
||||
var value = attribute != null ? ConvertUnits.ToSimUnits(XMLExtensions.ParseVector2(attribute.Value)) : previousValue;
|
||||
handlePos[index] = value;
|
||||
|
||||
@@ -129,7 +129,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
this.item = item;
|
||||
|
||||
if (element.Attribute("limbfixamount") != null)
|
||||
if (element.GetAttribute("limbfixamount") != null)
|
||||
{
|
||||
DebugConsole.ThrowError("Error in item \"" + item.Name + "\" - RepairTool damage should be configured using a StatusEffect with Afflictions, not the limbfixamount attribute.");
|
||||
}
|
||||
@@ -140,10 +140,10 @@ namespace Barotrauma.Items.Components
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "fixable":
|
||||
if (subElement.Attribute("name") != null)
|
||||
if (subElement.GetAttribute("name") != null)
|
||||
{
|
||||
DebugConsole.ThrowError("Error in RepairTool " + item.Name + " - use identifiers instead of names to configure fixable entities.");
|
||||
fixableEntities.Add(subElement.Attribute("name").Value.ToIdentifier());
|
||||
fixableEntities.Add(subElement.GetAttribute("name").Value.ToIdentifier());
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -344,7 +344,7 @@ namespace Barotrauma.Items.Components
|
||||
break;
|
||||
case "requiredskill":
|
||||
case "requiredskills":
|
||||
if (subElement.Attribute("name") != null)
|
||||
if (subElement.GetAttribute("name") != null)
|
||||
{
|
||||
DebugConsole.ThrowError("Error in item config \"" + item.ConfigFilePath + "\" - skill requirement in component " + GetType().ToString() + " should use a skill identifier instead of the name of the skill.");
|
||||
continue;
|
||||
|
||||
@@ -424,6 +424,15 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
public override void UpdateBroken(float deltaTime, Camera cam)
|
||||
{
|
||||
//update when the item is broken too to get OnContaining effects to execute and contained item positions to update
|
||||
if (IsActive)
|
||||
{
|
||||
Update(deltaTime, cam);
|
||||
}
|
||||
}
|
||||
|
||||
public override bool HasRequiredItems(Character character, bool addMessage, LocalizedString msg = null)
|
||||
{
|
||||
return AllowAccess && (!AccessOnlyWhenBroken || Item.Condition <= 0) && base.HasRequiredItems(character, addMessage, msg);
|
||||
|
||||
@@ -127,7 +127,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (subElement.Name != "limbposition") { continue; }
|
||||
string limbStr = subElement.GetAttributeString("limb", "");
|
||||
if (!Enum.TryParse(subElement.Attribute("limb").Value, out LimbType limbType))
|
||||
if (!Enum.TryParse(subElement.GetAttribute("limb").Value, out LimbType limbType))
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in item \"{item.Name}\" - {limbStr} is not a valid limb type.");
|
||||
}
|
||||
|
||||
@@ -186,8 +186,19 @@ namespace Barotrauma.Items.Components
|
||||
if (!isClient)
|
||||
{
|
||||
MoveIngredientsToInputContainer(selectedItem);
|
||||
if (selectedItem.RequiredMoney > 0)
|
||||
{
|
||||
if (GameMain.GameSession?.GameMode is MultiPlayerCampaign)
|
||||
{
|
||||
user.Wallet.Deduct(selectedItem.RequiredMoney);
|
||||
}
|
||||
else if (GameMain.GameSession?.GameMode is CampaignMode campaign)
|
||||
{
|
||||
campaign.Bank.Deduct(selectedItem.RequiredMoney);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
requiredTime = GetRequiredTime(fabricatedItem, user);
|
||||
timeUntilReady = requiredTime;
|
||||
|
||||
@@ -508,6 +519,22 @@ namespace Barotrauma.Items.Components
|
||||
if (fabricableItem == null) { return false; }
|
||||
if (fabricableItem.RequiresRecipe && (character == null || !character.HasRecipeForItem(fabricableItem.TargetItem.Identifier))) { return false; }
|
||||
|
||||
if (fabricableItem.RequiredMoney > 0)
|
||||
{
|
||||
if (GameMain.GameSession?.GameMode is MultiPlayerCampaign)
|
||||
{
|
||||
if (character?.Wallet == null || character.Wallet.Balance < fabricableItem.RequiredMoney) { return false; }
|
||||
}
|
||||
else if (GameMain.GameSession?.GameMode is CampaignMode campaign)
|
||||
{
|
||||
if (campaign.Bank.Balance < fabricableItem.RequiredMoney) { return false; }
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return fabricableItem.RequiredItems.All(requiredItem =>
|
||||
{
|
||||
int availablePrefabsAmount = 0;
|
||||
|
||||
@@ -169,7 +169,7 @@ namespace Barotrauma.Items.Components
|
||||
hull.BallastFlora = new BallastFloraBehavior(hull, ballastFloraPrefab, offset, firstGrowth: true);
|
||||
|
||||
#if SERVER
|
||||
hull.BallastFlora.SendNetworkMessage(new BallastFloraBehavior.SpawnEventData());
|
||||
hull.BallastFlora.CreateNetworkMessage(new BallastFloraBehavior.SpawnEventData());
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Xml.Linq;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Collections.Generic;
|
||||
#if CLIENT
|
||||
@@ -151,20 +150,6 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
set
|
||||
{
|
||||
if (powerIn != null)
|
||||
{
|
||||
if (powerIn.Grid != null)
|
||||
{
|
||||
powerIn.Grid.Voltage = Math.Max(0.0f, value);
|
||||
}
|
||||
}
|
||||
else if (powerOut != null)
|
||||
{
|
||||
if (powerOut.Grid != null)
|
||||
{
|
||||
powerOut.Grid.Voltage = Math.Max(0.0f, value);
|
||||
}
|
||||
}
|
||||
voltage = Math.Max(0.0f, value);
|
||||
}
|
||||
}
|
||||
@@ -213,11 +198,6 @@ namespace Barotrauma.Items.Components
|
||||
powerOnSoundPlayed = false;
|
||||
}
|
||||
#endif
|
||||
if (powerIn == null)
|
||||
{
|
||||
//power down the device here if it has no power connection (= receives power from contained battery cells instead of the "normal" power logic)
|
||||
Voltage -= deltaTime;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
@@ -470,6 +450,11 @@ namespace Barotrauma.Items.Components
|
||||
//Determine if devices are adding a load or providing power, also resolve solo nodes
|
||||
foreach (Powered powered in poweredList)
|
||||
{
|
||||
//Make voltage decay to ensure the device powers down.
|
||||
//This only effects devices with no power input (whose voltage is set by other means, e.g. status effects from a contained battery)
|
||||
//or devices that have been disconnected from the power grid - other devices use the voltage of the grid instead.
|
||||
powered.Voltage -= deltaTime;
|
||||
|
||||
//Handle the device if it's got a power connection
|
||||
if (powered.powerIn != null && powered.powerOut != powered.powerIn)
|
||||
{
|
||||
@@ -500,7 +485,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
else
|
||||
{
|
||||
powered.CurrPowerConsumption = powered.GetConnectionPowerOut(powered.powerIn, 0, powered.MinMaxPowerOut(powered.powerIn, 0), 0);
|
||||
powered.CurrPowerConsumption = -powered.GetConnectionPowerOut(powered.powerIn, 0, powered.MinMaxPowerOut(powered.powerIn, 0), 0);
|
||||
powered.GridResolved(powered.powerIn);
|
||||
}
|
||||
}
|
||||
@@ -541,7 +526,7 @@ namespace Barotrauma.Items.Components
|
||||
else
|
||||
{
|
||||
//Perform power calculations for the singular connection
|
||||
float loadOut = powered.GetConnectionPowerOut(powered.powerOut, 0, powered.MinMaxPowerOut(powered.powerOut, 0), 0);
|
||||
float loadOut = -powered.GetConnectionPowerOut(powered.powerOut, 0, powered.MinMaxPowerOut(powered.powerOut, 0), 0);
|
||||
if (powered is PowerTransfer pt2)
|
||||
{
|
||||
pt2.PowerLoad = loadOut;
|
||||
@@ -667,7 +652,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public static bool ValidPowerConnection(Connection conn1, Connection conn2)
|
||||
{
|
||||
return conn1.IsPower && conn2.IsPower && (conn1.Item.HasTag("junctionbox") || conn2.Item.HasTag("junctionbox") || conn1.IsOutput != conn2.IsOutput || (conn1.Item.HasTag("dock") && conn2.Item.HasTag("dock")));
|
||||
return conn1.IsPower && conn2.IsPower && (conn1.Item.HasTag("junctionbox") || conn2.Item.HasTag("junctionbox") || conn1.Item.HasTag("dock") || conn2.Item.HasTag("dock") || conn1.IsOutput != conn2.IsOutput);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -7,7 +7,6 @@ using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using Voronoi2;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
@@ -49,9 +48,8 @@ namespace Barotrauma.Items.Components
|
||||
//continuous collision detection is used while the projectile is moving faster than this
|
||||
const float ContinuousCollisionThreshold = 5.0f;
|
||||
|
||||
//a duration during which the projectile won't drop from the body it's stuck to
|
||||
private const float PersistentStickJointDuration = 1.0f;
|
||||
private PrismaticJoint stickJoint;
|
||||
private Joint stickJoint;
|
||||
private Vector2 jointAxis;
|
||||
|
||||
public Attack Attack { get; private set; }
|
||||
|
||||
@@ -86,8 +84,6 @@ namespace Barotrauma.Items.Components
|
||||
get { return hits; }
|
||||
}
|
||||
|
||||
private float persistentStickJointTimer;
|
||||
|
||||
[Serialize(10.0f, IsPropertySaveable.No, description: "The impulse applied to the physics body of the item when it's launched. Higher values make the projectile faster.")]
|
||||
public float LaunchImpulse { get; set; }
|
||||
|
||||
@@ -116,13 +112,6 @@ namespace Barotrauma.Items.Components
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(false, IsPropertySaveable.No, description: "When set to true, the item won't fall of a target it's stuck to unless removed.")]
|
||||
public bool StickPermanently
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(false, IsPropertySaveable.No, description: "Can the item stick to the character it hits.")]
|
||||
public bool StickToCharacters
|
||||
{
|
||||
@@ -151,6 +140,13 @@ namespace Barotrauma.Items.Components
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(false, IsPropertySaveable.No, description: "")]
|
||||
public bool StickToLightTargets
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(false, IsPropertySaveable.No, description: "Hitscan projectiles cast a ray forwards and immediately hit whatever the ray hits. "+
|
||||
"It is recommended to use hitscans for very fast-moving projectiles such as bullets, because using extremely fast launch velocities may cause physics glitches.")]
|
||||
public bool Hitscan
|
||||
@@ -212,16 +208,36 @@ namespace Barotrauma.Items.Components
|
||||
set;
|
||||
}
|
||||
|
||||
private float stickTimer;
|
||||
[Serialize(0f, IsPropertySaveable.No)]
|
||||
public float StickDuration
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(-1f, IsPropertySaveable.No)]
|
||||
public float MaxJointTranslation
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
private float maxJointTranslationInSimUnits = -1;
|
||||
|
||||
[Serialize(true, IsPropertySaveable.No)]
|
||||
public bool Prismatic
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public Body StickTarget
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public bool IsStuckToTarget
|
||||
{
|
||||
get { return StickTarget != null; }
|
||||
}
|
||||
public bool IsStuckToTarget => StickTarget != null;
|
||||
|
||||
private Category originalCollisionCategories;
|
||||
private Category originalCollisionTargets;
|
||||
@@ -660,23 +676,22 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
if (stickJoint == null) { return; }
|
||||
|
||||
if (persistentStickJointTimer > 0.0f && !StickPermanently)
|
||||
if (StickDuration > 0 && stickTimer > 0)
|
||||
{
|
||||
persistentStickJointTimer -= deltaTime;
|
||||
stickTimer -= deltaTime;
|
||||
return;
|
||||
}
|
||||
|
||||
float absoluteMaxTranslation = 100;
|
||||
// Update the item's transform to make sure it's inside the same sub as the target (or outside)
|
||||
if (StickTarget?.UserData is Limb target && target.Submarine != item.Submarine || Math.Abs(stickJoint.JointTranslation) > 100.0f)
|
||||
if (StickTarget?.UserData is Limb target && target.Submarine != item.Submarine || stickJoint is PrismaticJoint prismaticJoint && Math.Abs(prismaticJoint.JointTranslation) > absoluteMaxTranslation)
|
||||
{
|
||||
item.UpdateTransform();
|
||||
}
|
||||
|
||||
if (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer)
|
||||
{
|
||||
if (StickTargetRemoved() ||
|
||||
(!StickPermanently && (stickJoint.JointTranslation < stickJoint.LowerLimit * 0.9f || stickJoint.JointTranslation > stickJoint.UpperLimit * 0.9f)) ||
|
||||
Math.Abs(stickJoint.JointTranslation) > 100.0f) //failsafe unstick if the target is still extremely far
|
||||
if (StickTargetRemoved() || stickJoint is PrismaticJoint pJoint && Math.Abs(pJoint.JointTranslation) > maxJointTranslationInSimUnits)
|
||||
{
|
||||
Unstick();
|
||||
#if SERVER
|
||||
@@ -706,7 +721,7 @@ namespace Barotrauma.Items.Components
|
||||
if (hits.Contains(target.Body)) { return false; }
|
||||
if (target.Body.UserData is Submarine)
|
||||
{
|
||||
if (ShouldIgnoreSubmarineCollision(ref target, contact)) { return false; }
|
||||
if (ShouldIgnoreSubmarineCollision(ref target, contact)) { return false; }
|
||||
}
|
||||
else if (target.Body.UserData is Limb limb)
|
||||
{
|
||||
@@ -778,7 +793,10 @@ namespace Barotrauma.Items.Components
|
||||
Vector2.Dot(item.body.SimPosition - launchPos, dir) > 0)
|
||||
{
|
||||
target = wallBody.FixtureList.First();
|
||||
if (hits.Contains(target.Body)) { return true; }
|
||||
if (hits.Contains(target.Body))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -936,14 +954,12 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
item.body.LinearVelocity *= deflectedSpeedMultiplier;
|
||||
}
|
||||
else if ( // When hitting characters the collision normal seems to sometimes point into wrong direction, resulting in a failed attempt to stick
|
||||
//Vector2.Dot(Vector2.Normalize(velocity), collisionNormal) < 0.0f &&
|
||||
hits.Count() >= MaxTargetsToHit &&
|
||||
target.Body.Mass > item.body.Mass * 0.5f &&
|
||||
else if ( stickJoint == null && StickTarget == null &&
|
||||
StickToStructures && target.Body.UserData is Structure ||
|
||||
((StickToLightTargets || target.Body.Mass > item.body.Mass * 0.5f) &&
|
||||
(DoesStick ||
|
||||
(StickToCharacters && (target.Body.UserData is Limb || target.Body.UserData is Character)) ||
|
||||
(StickToStructures && target.Body.UserData is Structure) ||
|
||||
(StickToItems && target.Body.UserData is Item)))
|
||||
(StickToItems && target.Body.UserData is Item))))
|
||||
{
|
||||
Vector2 dir = new Vector2(
|
||||
(float)Math.Cos(item.body.Rotation),
|
||||
@@ -1025,30 +1041,39 @@ namespace Barotrauma.Items.Components
|
||||
private void StickToTarget(Body targetBody, Vector2 axis)
|
||||
{
|
||||
if (stickJoint != null) { return; }
|
||||
|
||||
stickJoint = new PrismaticJoint(targetBody, item.body.FarseerBody, item.body.SimPosition, axis, true)
|
||||
jointAxis = axis;
|
||||
item.body.ResetDynamics();
|
||||
if (Prismatic)
|
||||
{
|
||||
MotorEnabled = true,
|
||||
MaxMotorForce = 30.0f,
|
||||
LimitEnabled = true,
|
||||
Breakpoint = 1000.0f
|
||||
};
|
||||
stickJoint = new PrismaticJoint(targetBody, item.body.FarseerBody, item.body.SimPosition, axis, useWorldCoordinates: true)
|
||||
{
|
||||
MotorEnabled = true,
|
||||
MaxMotorForce = 30.0f,
|
||||
LimitEnabled = true,
|
||||
Breakpoint = 1000.0f
|
||||
};
|
||||
|
||||
if (StickPermanently)
|
||||
{
|
||||
stickJoint.LowerLimit = stickJoint.UpperLimit = 0.0f;
|
||||
item.body.ResetDynamics();
|
||||
if (maxJointTranslationInSimUnits == -1)
|
||||
{
|
||||
if (item.Sprite != null && MaxJointTranslation < 0)
|
||||
{
|
||||
MaxJointTranslation = item.Sprite.size.X / 2 * item.Scale;
|
||||
}
|
||||
MaxJointTranslation = Math.Min(MaxJointTranslation, 1000);
|
||||
maxJointTranslationInSimUnits = ConvertUnits.ToSimUnits(MaxJointTranslation);
|
||||
}
|
||||
}
|
||||
else if (item.Sprite != null)
|
||||
else
|
||||
{
|
||||
stickJoint.LowerLimit = ConvertUnits.ToSimUnits(item.Sprite.size.X * -0.3f * item.Scale);
|
||||
stickJoint.UpperLimit = ConvertUnits.ToSimUnits(item.Sprite.size.X * 0.3f * item.Scale);
|
||||
stickJoint = new WeldJoint(targetBody, item.body.FarseerBody, item.body.SimPosition, item.body.SimPosition, useWorldCoordinates: true)
|
||||
{
|
||||
FrequencyHz = 10.0f,
|
||||
DampingRatio = 0.5f
|
||||
};
|
||||
}
|
||||
|
||||
persistentStickJointTimer = PersistentStickJointDuration;
|
||||
stickTimer = StickDuration;
|
||||
StickTarget = targetBody;
|
||||
GameMain.World.Add(stickJoint);
|
||||
|
||||
IsActive = true;
|
||||
if (targetBody.UserData is Limb limb)
|
||||
{
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
using Barotrauma.Networking;
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Networking;
|
||||
using FarseerPhysics;
|
||||
using FarseerPhysics.Dynamics;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
@@ -12,8 +12,36 @@ namespace Barotrauma.Items.Components
|
||||
private ISpatialEntity source;
|
||||
private Item target;
|
||||
|
||||
private Vector2? launchDir;
|
||||
|
||||
private void SetSource(ISpatialEntity source)
|
||||
{
|
||||
this.source = source;
|
||||
if (source is Limb sourceLimb)
|
||||
{
|
||||
sourceLimb.AttachedRope = this;
|
||||
float offset = sourceLimb.Params.GetSpriteOrientation() - MathHelper.PiOver2;
|
||||
launchDir = VectorExtensions.Forward(sourceLimb.body.TransformedRotation - offset * sourceLimb.character.AnimController.Dir);
|
||||
}
|
||||
}
|
||||
|
||||
private void ResetSource()
|
||||
{
|
||||
if (source is Limb sourceLimb && sourceLimb.AttachedRope == this)
|
||||
{
|
||||
sourceLimb.AttachedRope = null;
|
||||
}
|
||||
source = null;
|
||||
}
|
||||
|
||||
private float snapTimer;
|
||||
private const float SnapAnimDuration = 1.0f;
|
||||
|
||||
[Serialize(1.0f, IsPropertySaveable.No, description: "")]
|
||||
public float SnapAnimDuration
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
private float raycastTimer;
|
||||
private const float RayCastInterval = 0.2f;
|
||||
@@ -46,6 +74,13 @@ namespace Barotrauma.Items.Components
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(360.0f, IsPropertySaveable.No, description: "The maximum angle from the source to the target until the rope breaks.")]
|
||||
public float MaxAngle
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(true, IsPropertySaveable.No, description: "Should the rope snap when it collides with a structure/submarine (if not, it will just go through it).")]
|
||||
public bool SnapOnCollision
|
||||
{
|
||||
@@ -115,8 +150,8 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
System.Diagnostics.Debug.Assert(source != null);
|
||||
System.Diagnostics.Debug.Assert(target != null);
|
||||
this.source = source;
|
||||
this.target = target;
|
||||
SetSource(source);
|
||||
Snapped = false;
|
||||
ApplyStatusEffects(ActionType.OnUse, 1.0f, worldPosition: item.WorldPosition);
|
||||
IsActive = true;
|
||||
@@ -127,7 +162,7 @@ namespace Barotrauma.Items.Components
|
||||
if (source == null || target == null || target.Removed ||
|
||||
(source is Entity sourceEntity && sourceEntity.Removed))
|
||||
{
|
||||
source = null;
|
||||
ResetSource();
|
||||
target = null;
|
||||
IsActive = false;
|
||||
return;
|
||||
@@ -144,12 +179,27 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
|
||||
Vector2 diff = target.WorldPosition - source.WorldPosition;
|
||||
if (diff.LengthSquared() > MaxLength * MaxLength)
|
||||
float lengthSqr = diff.LengthSquared();
|
||||
if (lengthSqr > MaxLength * MaxLength)
|
||||
{
|
||||
Snap();
|
||||
return;
|
||||
}
|
||||
|
||||
if (MaxAngle < 180 && lengthSqr > 2500)
|
||||
{
|
||||
if (launchDir == null)
|
||||
{
|
||||
launchDir = diff;
|
||||
}
|
||||
float angle = MathHelper.ToDegrees(VectorExtensions.Angle(launchDir.Value, diff));
|
||||
if (angle > MaxAngle)
|
||||
{
|
||||
Snap();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
item.ResetCachedVisibleSize();
|
||||
#endif
|
||||
|
||||
@@ -99,7 +99,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
string displayNameTag = "", fallbackTag = "";
|
||||
//if displayname is not present, attempt to find it from the prefab
|
||||
if (element.Attribute("displayname") == null)
|
||||
if (element.GetAttribute("displayname") == null)
|
||||
{
|
||||
foreach (var subElement in item.Prefab.ConfigElement.Elements())
|
||||
{
|
||||
|
||||
@@ -66,7 +66,7 @@ namespace Barotrauma.Items.Components
|
||||
HasPropertyName = !PropertyName.IsEmpty;
|
||||
IsIntegerInput = HasPropertyName && element.Name.ToString().ToLowerInvariant() == "integerinput";
|
||||
|
||||
if (element.Attribute("signal") is XAttribute attribute)
|
||||
if (element.GetAttribute("signal") is XAttribute attribute)
|
||||
{
|
||||
Signal = attribute.Value;
|
||||
ShouldSetProperty = HasPropertyName;
|
||||
|
||||
@@ -154,7 +154,7 @@ namespace Barotrauma.Items.Components
|
||||
IsActive = true;
|
||||
|
||||
//backwards compatibility
|
||||
if (element.Attribute("range") != null)
|
||||
if (element.GetAttribute("range") != null)
|
||||
{
|
||||
rangeX = rangeY = element.GetAttributeFloat("range", 0.0f);
|
||||
}
|
||||
|
||||
@@ -323,7 +323,7 @@ namespace Barotrauma.Items.Components
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "sprite":
|
||||
if (subElement.Attribute("texture") == null)
|
||||
if (subElement.GetAttribute("texture") == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Item \"" + item.Name + "\" doesn't have a texture specified!");
|
||||
return;
|
||||
|
||||
@@ -1389,7 +1389,10 @@ namespace Barotrauma
|
||||
/// </summary>
|
||||
public static void UpdateHulls()
|
||||
{
|
||||
foreach (Item item in ItemList) item.FindHull();
|
||||
foreach (Item item in ItemList)
|
||||
{
|
||||
item.FindHull();
|
||||
}
|
||||
}
|
||||
|
||||
public Hull FindHull()
|
||||
@@ -1677,12 +1680,17 @@ namespace Barotrauma
|
||||
if (!(GameMain.NetworkMember is { IsServer: true })) { return; }
|
||||
if (!conditionUpdatePending) { return; }
|
||||
|
||||
GameMain.NetworkMember.CreateEntityEvent(this, new StatusEventData());
|
||||
CreateStatusEvent();
|
||||
lastSentCondition = condition;
|
||||
sendConditionUpdateTimer = NetConfig.ItemConditionUpdateInterval;
|
||||
conditionUpdatePending = false;
|
||||
}
|
||||
|
||||
public void CreateStatusEvent()
|
||||
{
|
||||
GameMain.NetworkMember.CreateEntityEvent(this, new ItemStatusEventData());
|
||||
}
|
||||
|
||||
private bool isActive = true;
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
@@ -2955,7 +2963,7 @@ namespace Barotrauma
|
||||
/// <returns></returns>
|
||||
public static Item Load(ContentXElement element, Submarine submarine, bool createNetworkEvent, IdRemap idRemap)
|
||||
{
|
||||
string name = element.Attribute("name").Value;
|
||||
string name = element.GetAttribute("name").Value;
|
||||
Identifier identifier = element.GetAttributeIdentifier("identifier", Identifier.Empty);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(name) && identifier.IsEmpty)
|
||||
|
||||
@@ -63,7 +63,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private readonly struct StatusEventData : IEventData
|
||||
private readonly struct ItemStatusEventData : IEventData
|
||||
{
|
||||
public EventType EventType => EventType.Status;
|
||||
}
|
||||
|
||||
@@ -106,12 +106,13 @@ namespace Barotrauma
|
||||
public readonly Identifier TargetItemPrefabIdentifier;
|
||||
public ItemPrefab TargetItem => ItemPrefab.Prefabs[TargetItemPrefabIdentifier];
|
||||
|
||||
private Lazy<LocalizedString> displayName;
|
||||
private readonly Lazy<LocalizedString> displayName;
|
||||
public LocalizedString DisplayName
|
||||
=> ItemPrefab.Prefabs.ContainsKey(TargetItemPrefabIdentifier) ? displayName.Value : "";
|
||||
public readonly ImmutableArray<RequiredItem> RequiredItems;
|
||||
public readonly ImmutableArray<Identifier> SuitableFabricatorIdentifiers;
|
||||
public readonly float RequiredTime;
|
||||
public readonly int RequiredMoney;
|
||||
public readonly bool RequiresRecipe;
|
||||
public readonly float OutCondition; //Percentage-based from 0 to 1
|
||||
public readonly ImmutableArray<Skill> RequiredSkills;
|
||||
@@ -130,6 +131,7 @@ namespace Barotrauma
|
||||
|
||||
var requiredSkills = new List<Skill>();
|
||||
RequiredTime = element.GetAttributeFloat("requiredtime", 1.0f);
|
||||
RequiredMoney = element.GetAttributeInt("requiredmoney", 0);
|
||||
OutCondition = element.GetAttributeFloat("outcondition", 1.0f);
|
||||
if (OutCondition > 1.0f)
|
||||
{
|
||||
@@ -322,8 +324,13 @@ namespace Barotrauma
|
||||
|
||||
private PriceInfo defaultPrice;
|
||||
public PriceInfo DefaultPrice => defaultPrice;
|
||||
|
||||
private ImmutableDictionary<Identifier, PriceInfo> locationPrices;
|
||||
private ImmutableDictionary<Identifier, PriceInfo> StorePrices { get; set; }
|
||||
public bool CanBeBought => (DefaultPrice != null && DefaultPrice.CanBeBought) ||
|
||||
(StorePrices != null && StorePrices.Any(p => p.Value.CanBeBought));
|
||||
/// <summary>
|
||||
/// Any item with a Price element in the definition can be sold everywhere.
|
||||
/// </summary>
|
||||
public bool CanBeSold => DefaultPrice != null;
|
||||
|
||||
/// <summary>
|
||||
/// Defines areas where the item can be interacted with. If RequireBodyInsideTrigger is set to true, the character
|
||||
@@ -393,13 +400,6 @@ namespace Barotrauma
|
||||
/// </summary>
|
||||
public bool? AllowAsExtraCargo { get; private set; }
|
||||
|
||||
public bool CanBeBought => (DefaultPrice != null && DefaultPrice.CanBeBought) || (locationPrices != null && locationPrices.Any(p => p.Value.CanBeBought));
|
||||
|
||||
/// <summary>
|
||||
/// Any item with a Price element in the definition can be sold everywhere.
|
||||
/// </summary>
|
||||
public bool CanBeSold => DefaultPrice != null;
|
||||
|
||||
public bool RandomDeconstructionOutput { get; private set; }
|
||||
|
||||
public int RandomDeconstructionOutputAmount { get; private set; }
|
||||
@@ -675,11 +675,11 @@ namespace Barotrauma
|
||||
var deconstructItems = new List<DeconstructItem>();
|
||||
var fabricationRecipes = new Dictionary<uint, FabricationRecipe>();
|
||||
var treatmentSuitability = new Dictionary<Identifier, float>();
|
||||
var locationPrices = new Dictionary<Identifier, PriceInfo>();
|
||||
var storePrices = new Dictionary<Identifier, PriceInfo>();
|
||||
var preferredContainers = new List<PreferredContainer>();
|
||||
DeconstructTime = 1.0f;
|
||||
|
||||
if (ConfigElement.Attribute("allowasextracargo") != null)
|
||||
if (ConfigElement.GetAttribute("allowasextracargo") != null)
|
||||
{
|
||||
AllowAsExtraCargo = ConfigElement.GetAttributeBool("allowasextracargo", false);
|
||||
}
|
||||
@@ -690,7 +690,7 @@ namespace Barotrauma
|
||||
this.tags = ConfigElement.GetAttributeIdentifierArray("Tags", Array.Empty<Identifier>()).ToImmutableHashSet();
|
||||
}
|
||||
|
||||
if (ConfigElement.Attribute("cargocontainername") != null)
|
||||
if (ConfigElement.GetAttribute("cargocontainername") != null)
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in item prefab \"{ToString()}\" - cargo container should be configured using the item's identifier, not the name.");
|
||||
}
|
||||
@@ -731,39 +731,46 @@ namespace Barotrauma
|
||||
CanSpriteFlipY = subElement.GetAttributeBool("canflipy", true);
|
||||
|
||||
sprite = new Sprite(subElement, spriteFolder, lazyLoad: true);
|
||||
if (subElement.Attribute("sourcerect") == null &&
|
||||
subElement.Attribute("sheetindex") == null)
|
||||
if (subElement.GetAttribute("sourcerect") == null &&
|
||||
subElement.GetAttribute("sheetindex") == null)
|
||||
{
|
||||
DebugConsole.ThrowError($"Warning - sprite sourcerect not configured for item \"{ToString()}\"!");
|
||||
}
|
||||
Size = Sprite.size;
|
||||
|
||||
if (subElement.Attribute("name") == null && !Name.IsNullOrWhiteSpace())
|
||||
if (subElement.GetAttribute("name") == null && !Name.IsNullOrWhiteSpace())
|
||||
{
|
||||
Sprite.Name = Name.Value;
|
||||
}
|
||||
Sprite.EntityIdentifier = Identifier;
|
||||
break;
|
||||
case "price":
|
||||
if (subElement.Attribute("baseprice") != null)
|
||||
if (subElement.GetAttribute("baseprice") != null)
|
||||
{
|
||||
foreach (Tuple<Identifier, PriceInfo> priceInfo in PriceInfo.CreatePriceInfos(subElement, out this.defaultPrice))
|
||||
foreach (var priceInfo in PriceInfo.CreatePriceInfos(subElement, out defaultPrice))
|
||||
{
|
||||
if (priceInfo == null) { continue; }
|
||||
locationPrices.Add(priceInfo.Item1, priceInfo.Item2);
|
||||
if (priceInfo.StoreIdentifier.IsEmpty) { continue; }
|
||||
if (storePrices.ContainsKey(priceInfo.StoreIdentifier))
|
||||
{
|
||||
DebugConsole.AddWarning($"Error in item prefab \"{this}\": price for the store \"{priceInfo.StoreIdentifier}\" defined more than once.");
|
||||
storePrices[priceInfo.StoreIdentifier] = priceInfo;
|
||||
}
|
||||
else
|
||||
{
|
||||
storePrices.Add(priceInfo.StoreIdentifier, priceInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (subElement.Attribute("buyprice") != null)
|
||||
else if (subElement.GetAttribute("buyprice") != null && subElement.GetAttributeIdentifier("locationtype", "") is { IsEmpty: false } locationType) // Backwards compatibility
|
||||
{
|
||||
Identifier locationType = subElement.GetAttributeIdentifier("locationtype", "");
|
||||
if (locationPrices.ContainsKey(locationType))
|
||||
if (storePrices.ContainsKey(locationType))
|
||||
{
|
||||
DebugConsole.AddWarning($"Error in item prefab \"{ToString()}\": price for the location type \"{locationType}\" defined more than once.");
|
||||
locationPrices[locationType] = new PriceInfo(subElement);
|
||||
DebugConsole.AddWarning($"Error in item prefab \"{this}\": price for the location type \"{locationType}\" defined more than once.");
|
||||
storePrices[locationType] = new PriceInfo(subElement);
|
||||
}
|
||||
else
|
||||
{
|
||||
locationPrices.Add(locationType, new PriceInfo(subElement));
|
||||
storePrices.Add(locationType, new PriceInfo(subElement));
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -851,7 +858,7 @@ namespace Barotrauma
|
||||
}
|
||||
break;
|
||||
case "suitabletreatment":
|
||||
if (subElement.Attribute("name") != null)
|
||||
if (subElement.GetAttribute("name") != null)
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in item prefab \"{ToString()}\" - suitable treatments should be defined using item identifiers, not item names.");
|
||||
}
|
||||
@@ -870,15 +877,15 @@ namespace Barotrauma
|
||||
this.DeconstructItems = deconstructItems.ToImmutableArray();
|
||||
this.FabricationRecipes = fabricationRecipes.ToImmutableDictionary();
|
||||
this.treatmentSuitability = treatmentSuitability.ToImmutableDictionary();
|
||||
this.locationPrices = locationPrices.ToImmutableDictionary();
|
||||
StorePrices = storePrices.ToImmutableDictionary();
|
||||
this.PreferredContainers = preferredContainers.ToImmutableArray();
|
||||
this.LevelCommonness = levelCommonness.ToImmutableDictionary();
|
||||
this.LevelQuantity = levelQuantity.ToImmutableDictionary();
|
||||
|
||||
// Backwards compatibility
|
||||
if (locationPrices != null && locationPrices.Any())
|
||||
if (storePrices.Any())
|
||||
{
|
||||
this.defaultPrice ??= new PriceInfo(GetMinPrice() ?? 0, false);
|
||||
defaultPrice ??= new PriceInfo(GetMinPrice() ?? 0, false);
|
||||
}
|
||||
|
||||
HideConditionInTooltip = ConfigElement.GetAttributeBool("hideconditionintooltip", HideConditionBar);
|
||||
@@ -930,31 +937,109 @@ namespace Barotrauma
|
||||
return treatmentSuitability.TryGetValue(treatmentIdentifier, out float suitability) ? suitability : 0.0f;
|
||||
}
|
||||
|
||||
public PriceInfo GetPriceInfo(Location location)
|
||||
#region Pricing
|
||||
|
||||
public PriceInfo GetPriceInfo(Location.StoreInfo store)
|
||||
{
|
||||
if (location?.Type == null) { return null; }
|
||||
var locationTypeId = location.Type.Identifier;
|
||||
if (locationPrices != null && locationPrices.ContainsKey(locationTypeId))
|
||||
if (!store.Identifier.IsEmpty && StorePrices != null && StorePrices.TryGetValue(store.Identifier, out var storePriceInfo))
|
||||
{
|
||||
return locationPrices[locationTypeId];
|
||||
return storePriceInfo;
|
||||
}
|
||||
else
|
||||
{
|
||||
return DefaultPrice;
|
||||
}
|
||||
}
|
||||
|
||||
public bool CanBeBoughtAtLocation(Location location, out PriceInfo priceInfo)
|
||||
|
||||
public bool CanBeBoughtFrom(Location.StoreInfo store, out PriceInfo priceInfo)
|
||||
{
|
||||
priceInfo = null;
|
||||
if (location?.Type == null) { return false; }
|
||||
priceInfo = GetPriceInfo(location);
|
||||
return
|
||||
priceInfo != null &&
|
||||
priceInfo.CanBeBought &&
|
||||
(location.LevelData?.Difficulty ?? 0) >= priceInfo.MinLevelDifficulty;
|
||||
priceInfo = GetPriceInfo(store);
|
||||
return priceInfo != null && priceInfo.CanBeBought && (store.Location?.LevelData?.Difficulty ?? 0) >= priceInfo.MinLevelDifficulty;
|
||||
}
|
||||
|
||||
public bool CanBeBoughtFrom(Location location)
|
||||
{
|
||||
if (location?.Stores == null) { return false; }
|
||||
foreach (var store in location.Stores)
|
||||
{
|
||||
var priceInfo = GetPriceInfo(store.Value);
|
||||
if (priceInfo == null) { continue; }
|
||||
if (!priceInfo.CanBeBought) { continue; }
|
||||
if ((location.LevelData?.Difficulty ?? 0) < priceInfo.MinLevelDifficulty) { continue; }
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public int? GetMinPrice()
|
||||
{
|
||||
int? minPrice = StorePrices.Values.Min(p => p.Price);
|
||||
if (minPrice.HasValue)
|
||||
{
|
||||
if (DefaultPrice != null)
|
||||
{
|
||||
return minPrice < DefaultPrice.Price ? minPrice : DefaultPrice.Price;
|
||||
}
|
||||
else
|
||||
{
|
||||
return minPrice.Value;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return DefaultPrice?.Price;
|
||||
}
|
||||
}
|
||||
|
||||
public ImmutableDictionary<Identifier, PriceInfo> GetBuyPricesUnder(int maxCost = 0)
|
||||
{
|
||||
var prices = new Dictionary<Identifier, PriceInfo>();
|
||||
if (StorePrices != null)
|
||||
{
|
||||
foreach (var storePrice in StorePrices)
|
||||
{
|
||||
var priceInfo = storePrice.Value;
|
||||
if (priceInfo == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (!priceInfo.CanBeBought)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (priceInfo.Price < maxCost || maxCost == 0)
|
||||
{
|
||||
prices.Add(storePrice.Key, priceInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
return prices.ToImmutableDictionary();
|
||||
}
|
||||
|
||||
public ImmutableDictionary<Identifier, PriceInfo> GetSellPricesOver(int minCost = 0, bool sellingImportant = true)
|
||||
{
|
||||
var prices = new Dictionary<Identifier, PriceInfo>();
|
||||
if (!CanBeSold && sellingImportant)
|
||||
{
|
||||
return prices.ToImmutableDictionary();
|
||||
}
|
||||
foreach (var storePrice in StorePrices)
|
||||
{
|
||||
var priceInfo = storePrice.Value;
|
||||
if (priceInfo == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (priceInfo.Price > minCost)
|
||||
{
|
||||
prices.Add(storePrice.Key, priceInfo);
|
||||
}
|
||||
}
|
||||
return prices.ToImmutableDictionary();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public static ItemPrefab Find(string name, Identifier identifier)
|
||||
{
|
||||
if (string.IsNullOrEmpty(name) && identifier.IsEmpty)
|
||||
@@ -988,77 +1073,6 @@ namespace Barotrauma
|
||||
return prefab;
|
||||
}
|
||||
|
||||
public int? GetMinPrice()
|
||||
{
|
||||
int? minPrice = locationPrices != null && locationPrices.Values.Any() ? locationPrices?.Values.Min(p => p.Price) : null;
|
||||
if (minPrice.HasValue)
|
||||
{
|
||||
if (DefaultPrice != null)
|
||||
{
|
||||
return minPrice < DefaultPrice.Price ? minPrice : DefaultPrice.Price;
|
||||
}
|
||||
else
|
||||
{
|
||||
return minPrice.Value;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return DefaultPrice?.Price;
|
||||
}
|
||||
}
|
||||
|
||||
public ImmutableDictionary<Identifier, PriceInfo> GetBuyPricesUnder(int maxCost = 0)
|
||||
{
|
||||
Dictionary<Identifier, PriceInfo> priceLocations = new Dictionary<Identifier, PriceInfo>();
|
||||
if (locationPrices != null)
|
||||
{
|
||||
foreach (KeyValuePair<Identifier, PriceInfo> locationPrice in locationPrices)
|
||||
{
|
||||
PriceInfo priceInfo = locationPrice.Value;
|
||||
|
||||
if (priceInfo == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (!priceInfo.CanBeBought)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (priceInfo.Price < maxCost || maxCost == 0)
|
||||
{
|
||||
priceLocations.Add(locationPrice.Key, priceInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
return priceLocations.ToImmutableDictionary();
|
||||
}
|
||||
|
||||
public ImmutableDictionary<Identifier, PriceInfo> GetSellPricesOver(int minCost = 0, bool sellingImportant = true)
|
||||
{
|
||||
Dictionary<Identifier, PriceInfo> priceLocations = new Dictionary<Identifier, PriceInfo>();
|
||||
|
||||
if (!CanBeSold && sellingImportant)
|
||||
{
|
||||
return priceLocations.ToImmutableDictionary();
|
||||
}
|
||||
|
||||
foreach (KeyValuePair<Identifier, PriceInfo> locationPrice in locationPrices)
|
||||
{
|
||||
PriceInfo priceInfo = locationPrice.Value;
|
||||
|
||||
if (priceInfo == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (priceInfo.Price > minCost)
|
||||
{
|
||||
priceLocations.Add(locationPrice.Key, priceInfo);
|
||||
}
|
||||
}
|
||||
return priceLocations.ToImmutableDictionary();
|
||||
}
|
||||
|
||||
public bool IsContainerPreferred(Item item, ItemContainer targetContainer, out bool isPreferencesDefined, out bool isSecondary, bool requireConditionRequirement = false)
|
||||
{
|
||||
isPreferencesDefined = PreferredContainers.Any();
|
||||
|
||||
@@ -167,7 +167,7 @@ namespace Barotrauma
|
||||
public static RelatedItem Load(ContentXElement element, bool returnEmpty, string parentDebugName)
|
||||
{
|
||||
Identifier[] identifiers;
|
||||
if (element.Attribute("name") != null)
|
||||
if (element.GetAttribute("name") != null)
|
||||
{
|
||||
//backwards compatibility + a console warning
|
||||
DebugConsole.ThrowError("Error in RelatedItem config (" + (string.IsNullOrEmpty(parentDebugName) ? element.ToString() : parentDebugName) + ") - use item tags or identifiers instead of names.");
|
||||
|
||||
Reference in New Issue
Block a user