Build 0.20.0.0
This commit is contained in:
@@ -1164,11 +1164,14 @@ namespace Barotrauma.Items.Components
|
||||
public override void ReceiveSignal(Signal signal, Connection connection)
|
||||
{
|
||||
#if CLIENT
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient &&
|
||||
!(GameMain.GameSession?.Campaign?.AllowedToManageCampaign(ClientPermissions.ManageMap) ?? false))
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (GameMain.GameSession?.Campaign != null && !CampaignMode.AllowedToManageCampaign(ClientPermissions.ManageMap))
|
||||
{
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (dockingCooldown > 0.0f) { return; }
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Barotrauma.Networking;
|
||||
using FarseerPhysics;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -63,6 +64,9 @@ namespace Barotrauma.Items.Components
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(0.0f, IsPropertySaveable.No)]
|
||||
public float RaycastRange { get; set; }
|
||||
|
||||
[Serialize(0.25f, IsPropertySaveable.Yes, description: "The duration of an individual discharge (in seconds)."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 60.0f, ValueStep = 0.1f, DecimalCount = 2)]
|
||||
public float Duration
|
||||
{
|
||||
@@ -70,6 +74,13 @@ namespace Barotrauma.Items.Components
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(0.25f, IsPropertySaveable.Yes), Editable(MinValueFloat = 0.0f, MaxValueFloat = 60.0f, ValueStep = 0.1f, DecimalCount = 2)]
|
||||
public float Reload
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(false, IsPropertySaveable.Yes, "If set to true, the discharge cannot travel inside the submarine nor shock anyone inside."), Editable]
|
||||
public bool OutdoorsOnly
|
||||
{
|
||||
@@ -77,6 +88,13 @@ namespace Barotrauma.Items.Components
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(false, IsPropertySaveable.Yes)]
|
||||
public bool IgnoreUser
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
private readonly List<Node> nodes = new List<Node>();
|
||||
public IEnumerable<Node> Nodes
|
||||
{
|
||||
@@ -91,6 +109,10 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
private readonly Attack attack;
|
||||
|
||||
private Character user;
|
||||
|
||||
private float reloadTimer;
|
||||
|
||||
public ElectricalDischarger(Item item, ContentXElement element) :
|
||||
base(item, element)
|
||||
{
|
||||
@@ -125,6 +147,7 @@ namespace Barotrauma.Items.Components
|
||||
charging = true;
|
||||
timer = Duration;
|
||||
IsActive = true;
|
||||
user = character;
|
||||
#if SERVER
|
||||
if (GameMain.Server != null) { item.CreateServerEvent(this); }
|
||||
#endif
|
||||
@@ -144,6 +167,11 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
if (timer <= 0.0f)
|
||||
{
|
||||
if (reloadTimer > 0.0f)
|
||||
{
|
||||
reloadTimer -= deltaTime;
|
||||
return;
|
||||
}
|
||||
IsActive = false;
|
||||
return;
|
||||
}
|
||||
@@ -196,6 +224,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
private void Discharge()
|
||||
{
|
||||
reloadTimer = Reload;
|
||||
ApplyStatusEffects(ActionType.OnUse, 1.0f);
|
||||
FindNodes(item.WorldPosition, Range);
|
||||
if (attack != null)
|
||||
@@ -203,7 +232,7 @@ namespace Barotrauma.Items.Components
|
||||
foreach ((Character character, Node node) in charactersInRange)
|
||||
{
|
||||
if (character == null || character.Removed) { continue; }
|
||||
character.ApplyAttack(null, node.WorldPosition, attack, MathHelper.Clamp(Voltage, 1.0f, MaxOverVoltageFactor));
|
||||
character.ApplyAttack(user, node.WorldPosition, attack, MathHelper.Clamp(Voltage, 1.0f, MaxOverVoltageFactor));
|
||||
}
|
||||
}
|
||||
DischargeProjSpecific();
|
||||
@@ -214,6 +243,18 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public void FindNodes(Vector2 worldPosition, float range)
|
||||
{
|
||||
if (RaycastRange > 0.0f)
|
||||
{
|
||||
float angle = 0.0f;
|
||||
float dir = 1;
|
||||
if (item.body != null)
|
||||
{
|
||||
angle += item.body.Rotation;
|
||||
dir = item.body.Dir;
|
||||
}
|
||||
worldPosition += new Vector2((float)Math.Cos(angle), (float)Math.Sin(angle)) * RaycastRange * dir;
|
||||
}
|
||||
|
||||
//see which submarines are within range so we can skip structures that are in far-away subs
|
||||
List<Submarine> submarinesInRange = new List<Submarine>();
|
||||
foreach (Submarine sub in Submarine.Loaded)
|
||||
@@ -222,7 +263,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
submarinesInRange.Add(sub);
|
||||
}
|
||||
else
|
||||
else if (sub != null)
|
||||
{
|
||||
Rectangle subBorders = new Rectangle(
|
||||
sub.Borders.X - (int)range, sub.Borders.Y + (int)range,
|
||||
@@ -263,26 +304,41 @@ namespace Barotrauma.Items.Components
|
||||
entitiesInRange.Add(structure);
|
||||
}
|
||||
|
||||
|
||||
nodes.Clear();
|
||||
if (RaycastRange > 0.0f)
|
||||
{
|
||||
nodes.Add(new Node(item.WorldPosition, -1));
|
||||
int parentNodeIndex = 0;
|
||||
AddNodesBetweenPoints(item.WorldPosition, worldPosition, 0.5f, ref parentNodeIndex);
|
||||
}
|
||||
else
|
||||
{
|
||||
nodes.Add(new Node(worldPosition, -1));
|
||||
}
|
||||
|
||||
float totalRange = RaycastRange + range;
|
||||
foreach (Character character in Character.CharacterList)
|
||||
{
|
||||
if (!character.Enabled) continue;
|
||||
if (OutdoorsOnly && character.Submarine != null) continue;
|
||||
if (character.Submarine != null && !submarinesInRange.Contains(character.Submarine)) continue;
|
||||
if (!character.Enabled) { continue; }
|
||||
if (IgnoreUser && character == user) { continue; }
|
||||
if (OutdoorsOnly && character.Submarine != null) { continue; }
|
||||
if (character.Submarine != null && !submarinesInRange.Contains(character.Submarine)) { continue; }
|
||||
|
||||
if (Vector2.DistanceSquared(character.WorldPosition, worldPosition) < range * range * RangeMultiplierInWalls)
|
||||
if (Vector2.DistanceSquared(character.WorldPosition, worldPosition) < totalRange * totalRange * RangeMultiplierInWalls ||
|
||||
(RaycastRange > 0.0f && MathUtils.LineToPointDistanceSquared(worldPosition, item.WorldPosition, character.WorldPosition) < range * range * RangeMultiplierInWalls))
|
||||
{
|
||||
entitiesInRange.Add(character);
|
||||
charactersInRange.Add((character, nodes[0]));
|
||||
}
|
||||
}
|
||||
|
||||
nodes.Clear();
|
||||
nodes.Add(new Node(worldPosition, -1));
|
||||
FindNodes(entitiesInRange, worldPosition, 0, range);
|
||||
FindNodes(entitiesInRange, worldPosition, nodes.Count - 1, range);
|
||||
|
||||
//construct final nodes (w/ lengths and angles so they don't have to be recalculated when rendering the discharge)
|
||||
for (int i = 0; i < nodes.Count; i++)
|
||||
{
|
||||
if (nodes[i].ParentIndex < 0) continue;
|
||||
if (nodes[i].ParentIndex < 0) { continue; }
|
||||
Node parentNode = nodes[nodes[i].ParentIndex];
|
||||
float length = Vector2.Distance(nodes[i].WorldPosition, parentNode.WorldPosition) * Rand.Range(1.0f, 1.25f);
|
||||
float angle = MathUtils.VectorToAngle(parentNode.WorldPosition - nodes[i].WorldPosition);
|
||||
@@ -292,7 +348,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
private void FindNodes(List<Entity> entitiesInRange, Vector2 currPos, int parentNodeIndex, float currentRange)
|
||||
{
|
||||
if (currentRange <= 0.0f || nodes.Count >= MaxNodes) return;
|
||||
if (currentRange <= 0.0f || nodes.Count >= MaxNodes) { return; }
|
||||
|
||||
//find the closest structure
|
||||
int closestIndex = -1;
|
||||
@@ -434,20 +490,21 @@ namespace Barotrauma.Items.Components
|
||||
for (int j = 0; j < entitiesInRange.Count; j++)
|
||||
{
|
||||
var otherEntity = entitiesInRange[j];
|
||||
if (!(otherEntity is Character character)) continue;
|
||||
if (OutdoorsOnly && character.Submarine != null) continue;
|
||||
if (otherEntity is not Character character) { continue; }
|
||||
if (IgnoreUser && character == user) { continue; }
|
||||
if (OutdoorsOnly && character.Submarine != null) { continue; }
|
||||
|
||||
if (targetStructure.IsHorizontal)
|
||||
{
|
||||
if (otherEntity.WorldPosition.X < targetStructure.WorldRect.X) continue;
|
||||
if (otherEntity.WorldPosition.X > targetStructure.WorldRect.Right) continue;
|
||||
if (Math.Abs(otherEntity.WorldPosition.Y - targetStructure.WorldPosition.Y) > currentRange) continue;
|
||||
if (otherEntity.WorldPosition.X < targetStructure.WorldRect.X) { continue; }
|
||||
if (otherEntity.WorldPosition.X > targetStructure.WorldRect.Right) { continue; }
|
||||
if (Math.Abs(otherEntity.WorldPosition.Y - targetStructure.WorldPosition.Y) > currentRange) { continue; }
|
||||
}
|
||||
else
|
||||
{
|
||||
if (otherEntity.WorldPosition.Y < targetStructure.WorldRect.Y - targetStructure.Rect.Height) continue;
|
||||
if (otherEntity.WorldPosition.Y > targetStructure.WorldRect.Y) continue;
|
||||
if (Math.Abs(otherEntity.WorldPosition.X - targetStructure.WorldPosition.X) > currentRange) continue;
|
||||
if (otherEntity.WorldPosition.Y < targetStructure.WorldRect.Y - targetStructure.Rect.Height) { continue; }
|
||||
if (otherEntity.WorldPosition.Y > targetStructure.WorldRect.Y) { continue; }
|
||||
if (Math.Abs(otherEntity.WorldPosition.X - targetStructure.WorldPosition.X) > currentRange) { continue; }
|
||||
}
|
||||
float closestNodeDistSqr = float.MaxValue;
|
||||
int closestNodeIndex = -1;
|
||||
@@ -473,7 +530,10 @@ namespace Barotrauma.Items.Components
|
||||
AddNodesBetweenPoints(currPos, targetPos, 0.25f, ref parentNodeIndex);
|
||||
nodes.Add(new Node(targetPos, parentNodeIndex));
|
||||
entitiesInRange.RemoveAt(closestIndex);
|
||||
charactersInRange.Add((character, nodes[parentNodeIndex]));
|
||||
if (!charactersInRange.Any(c => c.character == character))
|
||||
{
|
||||
charactersInRange.Add((character, nodes[parentNodeIndex]));
|
||||
}
|
||||
FindNodes(entitiesInRange, targetPos, nodes.Count - 1, currentRange);
|
||||
}
|
||||
}
|
||||
@@ -483,7 +543,7 @@ namespace Barotrauma.Items.Components
|
||||
Vector2 diff = targetPos - currPos;
|
||||
float dist = diff.Length();
|
||||
Vector2 normal = new Vector2(-diff.Y, diff.X) / dist;
|
||||
for (float x = MaxNodeDistance; x < dist - MaxNodeDistance; x += MaxNodeDistance * Rand.Range(0.5f, 1.5f))
|
||||
for (float x = MaxNodeDistance; x < dist - MaxNodeDistance; x += MaxNodeDistance * Rand.Range(0.5f, 1.0f))
|
||||
{
|
||||
//0 at the edges, 1 at the center
|
||||
float normalOffset = (0.5f - Math.Abs(x / dist - 0.5f)) * 2.0f;
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
const float MaxAttachDistance = 150.0f;
|
||||
private const float MaxAttachDistance = ItemPrefab.DefaultInteractDistance * 0.95f;
|
||||
|
||||
//the position(s) in the item that the Character grabs
|
||||
protected Vector2[] handlePos;
|
||||
@@ -127,7 +127,7 @@ namespace Barotrauma.Items.Components
|
||||
set { attachedByDefault = value; }
|
||||
}
|
||||
|
||||
[Editable, Serialize("0.0,0.0", IsPropertySaveable.No, description: "The position the character holds the item at (in pixels, as an offset from the character's shoulder)."+
|
||||
[Serialize("0.0,0.0", IsPropertySaveable.No, description: "The position the character holds the item at (in pixels, as an offset from the character's shoulder)."+
|
||||
" For example, a value of 10,-100 would make the character hold the item 100 pixels below the shoulder and 10 pixels forwards.")]
|
||||
public Vector2 HoldPos
|
||||
{
|
||||
@@ -143,7 +143,11 @@ namespace Barotrauma.Items.Components
|
||||
set { aimPos = ConvertUnits.ToSimUnits(value); }
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
[Editable, Serialize(0.0f, IsPropertySaveable.No, description: "The rotation at which the character holds the item (in degrees, relative to the rotation of the character's hand).")]
|
||||
#else
|
||||
[Serialize(0.0f, IsPropertySaveable.No)]
|
||||
#endif
|
||||
public float HoldAngle
|
||||
{
|
||||
get { return MathHelper.ToDegrees(holdAngle); }
|
||||
@@ -151,23 +155,50 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
|
||||
private Vector2 swingAmount;
|
||||
#if DEBUG
|
||||
[Editable, Serialize("0.0,0.0", IsPropertySaveable.No, description: "How much the item swings around when aiming/holding it (in pixels, as an offset from AimPos/HoldPos).")]
|
||||
#else
|
||||
[Serialize("0.0,0.0", IsPropertySaveable.No)]
|
||||
#endif
|
||||
public Vector2 SwingAmount
|
||||
{
|
||||
get { return ConvertUnits.ToDisplayUnits(swingAmount); }
|
||||
set { swingAmount = ConvertUnits.ToSimUnits(value); }
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
[Editable, Serialize(0.0f, IsPropertySaveable.No, description: "How fast the item swings around when aiming/holding it (only valid if SwingAmount is set).")]
|
||||
#else
|
||||
[Serialize(0.0f, IsPropertySaveable.No)]
|
||||
#endif
|
||||
|
||||
public float SwingSpeed { get; set; }
|
||||
|
||||
#if DEBUG
|
||||
[Editable, Serialize(false, IsPropertySaveable.No, description: "Should the item swing around when it's being held.")]
|
||||
#else
|
||||
[Serialize(false, IsPropertySaveable.No)]
|
||||
#endif
|
||||
public bool SwingWhenHolding { get; set; }
|
||||
|
||||
#if DEBUG
|
||||
[Editable, Serialize(false, IsPropertySaveable.No, description: "Should the item swing around when it's being aimed.")]
|
||||
#else
|
||||
[Serialize(false, IsPropertySaveable.No)]
|
||||
#endif
|
||||
public bool SwingWhenAiming { get; set; }
|
||||
|
||||
#if DEBUG
|
||||
[Editable, Serialize(false, IsPropertySaveable.No, description: "Should the item swing around when it's being used (for example, when firing a weapon or a welding tool).")]
|
||||
#else
|
||||
[Serialize(false, IsPropertySaveable.No)]
|
||||
#endif
|
||||
public bool SwingWhenUsing { get; set; }
|
||||
|
||||
#if DEBUG
|
||||
[Editable, Serialize(false, IsPropertySaveable.No)]
|
||||
#else
|
||||
[Serialize(false, IsPropertySaveable.No)]
|
||||
#endif
|
||||
public bool DisableHeadRotation { get; set; }
|
||||
|
||||
[ConditionallyEditable(ConditionallyEditable.ConditionType.Attachable, MinValueFloat = 0.0f, MaxValueFloat = 0.999f, DecimalCount = 3), Serialize(0.55f, IsPropertySaveable.No, description: "Sprite depth that's used when the item is NOT attached to a wall.")]
|
||||
@@ -731,10 +762,24 @@ namespace Barotrauma.Items.Components
|
||||
mouseDiff = mouseDiff.ClampLength(MaxAttachDistance);
|
||||
|
||||
Vector2 userPos = useWorldCoordinates ? user.WorldPosition : user.Position;
|
||||
|
||||
Vector2 attachPos = userPos + mouseDiff;
|
||||
|
||||
if (user.Submarine == null && Level.Loaded != null)
|
||||
if (user.Submarine != null)
|
||||
{
|
||||
if (Submarine.PickBody(
|
||||
ConvertUnits.ToSimUnits(user.Position),
|
||||
ConvertUnits.ToSimUnits(user.Position + mouseDiff), collisionCategory: Physics.CollisionWall) != null)
|
||||
{
|
||||
attachPos = userPos + mouseDiff * Submarine.LastPickedFraction;
|
||||
|
||||
//round down if we're placing on the right side and vice versa: ensures we don't round the position inside a wall
|
||||
return
|
||||
new Vector2(
|
||||
mouseDiff.X > 0 ? (float)Math.Floor(attachPos.X / Submarine.GridSize.X) * Submarine.GridSize.X : (float)Math.Ceiling(attachPos.X / Submarine.GridSize.X) * Submarine.GridSize.X,
|
||||
mouseDiff.Y > 0 ? (float)Math.Floor(attachPos.Y / Submarine.GridSize.Y) * Submarine.GridSize.X : (float)Math.Ceiling(attachPos.Y / Submarine.GridSize.Y) * Submarine.GridSize.Y);
|
||||
}
|
||||
}
|
||||
else if (Level.Loaded != null)
|
||||
{
|
||||
bool edgeFound = false;
|
||||
foreach (var cell in Level.Loaded.GetCells(attachPos))
|
||||
|
||||
@@ -112,7 +112,7 @@ namespace Barotrauma.Items.Components
|
||||
reloadTimer = reload;
|
||||
reloadTimer /= 1f + character.GetStatValue(StatTypes.MeleeAttackSpeed);
|
||||
reloadTimer /= 1f + item.GetQualityModifier(Quality.StatType.StrikingSpeedMultiplier);
|
||||
character.AnimController.LockFlippingUntil = (float)Timing.TotalTime + reloadTimer;
|
||||
character.AnimController.LockFlippingUntil = (float)Timing.TotalTime + reloadTimer * 0.9f;
|
||||
|
||||
item.body.FarseerBody.CollisionCategories = Physics.CollisionProjectile;
|
||||
item.body.FarseerBody.CollidesWith = Physics.CollisionCharacter | Physics.CollisionWall | Physics.CollisionItemBlocking;
|
||||
@@ -421,7 +421,7 @@ namespace Barotrauma.Items.Components
|
||||
if (targetItem.Removed) { return; }
|
||||
var attackResult = Attack.DoDamage(User, targetItem, item.WorldPosition, 1.0f);
|
||||
#if CLIENT
|
||||
if (attackResult.Damage > 0.0f)
|
||||
if (attackResult.Damage > 0.0f && targetItem.Prefab.ShowHealthBar)
|
||||
{
|
||||
Character.Controlled?.UpdateHUDProgressBar(targetItem,
|
||||
targetItem.WorldPosition,
|
||||
|
||||
@@ -514,7 +514,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
if (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer)
|
||||
{
|
||||
if (Rand.Range(0.0f, 1.0f) < FireProbability * deltaTime)
|
||||
if (Rand.Range(0.0f, 1.0f) < FireProbability * deltaTime && item.CurrentHull != null)
|
||||
{
|
||||
Vector2 displayPos = ConvertUnits.ToDisplayUnits(rayStart + (rayEnd - rayStart) * lastPickedFraction * 0.9f);
|
||||
if (item.CurrentHull.Submarine != null) { displayPos += item.CurrentHull.Submarine.Position; }
|
||||
@@ -636,11 +636,14 @@ namespace Barotrauma.Items.Components
|
||||
float addedDetachTime = deltaTime * (1f + user.GetStatValue(StatTypes.RepairToolDeattachTimeMultiplier)) * (1f + item.GetQualityModifier(Quality.StatType.RepairToolDeattachTimeMultiplier));
|
||||
levelResource.DeattachTimer += addedDetachTime;
|
||||
#if CLIENT
|
||||
Character.Controlled?.UpdateHUDProgressBar(
|
||||
this,
|
||||
targetItem.WorldPosition,
|
||||
levelResource.DeattachTimer / levelResource.DeattachDuration,
|
||||
GUIStyle.Red, GUIStyle.Green, "progressbar.deattaching");
|
||||
if (targetItem.Prefab.ShowHealthBar)
|
||||
{
|
||||
Character.Controlled?.UpdateHUDProgressBar(
|
||||
this,
|
||||
targetItem.WorldPosition,
|
||||
levelResource.DeattachTimer / levelResource.DeattachDuration,
|
||||
GUIStyle.Red, GUIStyle.Green, "progressbar.deattaching");
|
||||
}
|
||||
#endif
|
||||
FixItemProjSpecific(user, deltaTime, targetItem, showProgressBar: false);
|
||||
return true;
|
||||
|
||||
@@ -111,6 +111,13 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
private bool drawable = true;
|
||||
|
||||
[Serialize(PropertyConditional.Comparison.And, IsPropertySaveable.No)]
|
||||
public PropertyConditional.Comparison IsActiveConditionalComparison
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public List<PropertyConditional> IsActiveConditionals;
|
||||
|
||||
public bool Drawable
|
||||
@@ -241,6 +248,18 @@ namespace Barotrauma.Items.Components
|
||||
[Serialize(0, IsPropertySaveable.Yes, alwaysUseInstanceValues: true)]
|
||||
public int ManuallySelectedSound { get; private set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Can be used by status effects or conditionals to the speed of the item
|
||||
/// </summary>
|
||||
public float Speed
|
||||
{
|
||||
get
|
||||
{
|
||||
return item.Speed;
|
||||
}
|
||||
}
|
||||
|
||||
public ItemComponent(Item item, ContentXElement element)
|
||||
{
|
||||
this.item = item;
|
||||
@@ -814,7 +833,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
public void ApplyStatusEffects(ActionType type, float deltaTime, Character character = null, Limb targetLimb = null, Entity useTarget = null, Character user = null, Vector2? worldPosition = null, float afflictionMultiplier = 1.0f, float applyOnUserFraction = 0.0f)
|
||||
public void ApplyStatusEffects(ActionType type, float deltaTime, Character character = null, Limb targetLimb = null, Entity useTarget = null, Character user = null, Vector2? worldPosition = null, float afflictionMultiplier = 1.0f)
|
||||
{
|
||||
if (statusEffectLists == null) { return; }
|
||||
|
||||
@@ -828,11 +847,6 @@ namespace Barotrauma.Items.Components
|
||||
if (user != null) { effect.SetUser(user); }
|
||||
effect.AfflictionMultiplier = afflictionMultiplier;
|
||||
item.ApplyStatusEffect(effect, type, deltaTime, character, targetLimb, useTarget, isNetworkEvent: false, checkCondition: false, worldPosition);
|
||||
if (user != null && applyOnUserFraction > 0.0f && effect.HasTargetType(StatusEffect.TargetType.Character))
|
||||
{
|
||||
effect.AfflictionMultiplier = applyOnUserFraction;
|
||||
item.ApplyStatusEffect(effect, type, deltaTime, user, targetLimb == null ? null : user.AnimController.GetLimb(targetLimb.type), useTarget, false, false, worldPosition);
|
||||
}
|
||||
effect.AfflictionMultiplier = 1.0f;
|
||||
reducesCondition |= effect.ReducesItemCondition();
|
||||
}
|
||||
|
||||
@@ -104,12 +104,14 @@ namespace Barotrauma.Items.Components
|
||||
// doesn't quite work properly, remaining time changes if tinkering stops
|
||||
float deconstructionSpeedModifier = userDeconstructorSpeedMultiplier * (1f + tinkeringStrength * TinkeringSpeedIncrease);
|
||||
|
||||
float deconstructionSpeed = item.StatManager.GetAdjustedValue(ItemTalentStats.DeconstructorSpeed, DeconstructionSpeed);
|
||||
|
||||
if (DeconstructItemsSimultaneously)
|
||||
{
|
||||
float deconstructTime = 0.0f;
|
||||
foreach (Item targetItem in inputContainer.Inventory.AllItems)
|
||||
{
|
||||
deconstructTime += targetItem.Prefab.DeconstructTime / (DeconstructionSpeed * deconstructionSpeedModifier);
|
||||
deconstructTime += targetItem.Prefab.DeconstructTime / (deconstructionSpeed * deconstructionSpeedModifier);
|
||||
}
|
||||
|
||||
progressState = Math.Min(progressTimer / deconstructTime, 1.0f);
|
||||
@@ -139,7 +141,7 @@ namespace Barotrauma.Items.Components
|
||||
if (targetItem == null) { return; }
|
||||
|
||||
var validDeconstructItems = targetItem.Prefab.DeconstructItems.Where(it => it.IsValidDeconstructor(item)).ToList();
|
||||
float deconstructTime = validDeconstructItems.Any() ? targetItem.Prefab.DeconstructTime / (DeconstructionSpeed * deconstructionSpeedModifier) : 1.0f;
|
||||
float deconstructTime = validDeconstructItems.Any() ? targetItem.Prefab.DeconstructTime / (deconstructionSpeed * deconstructionSpeedModifier) : 1.0f;
|
||||
|
||||
progressState = Math.Min(progressTimer / deconstructTime, 1.0f);
|
||||
if (progressTimer > deconstructTime)
|
||||
@@ -218,7 +220,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
if (percentageHealth < deconstructProduct.MinCondition || percentageHealth > deconstructProduct.MaxCondition) { return; }
|
||||
|
||||
if (!(MapEntityPrefab.Find(null, deconstructProduct.ItemIdentifier) is ItemPrefab itemPrefab))
|
||||
if (MapEntityPrefab.FindByIdentifier(deconstructProduct.ItemIdentifier) is not ItemPrefab itemPrefab)
|
||||
{
|
||||
DebugConsole.ThrowError("Tried to deconstruct item \"" + targetItem.Name + "\" but couldn't find item prefab \"" + deconstructProduct.ItemIdentifier + "\"!");
|
||||
return;
|
||||
@@ -284,9 +286,10 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
Entity.Spawner.AddItemToSpawnQueue(itemPrefab, outputContainer.Inventory, condition, onSpawned: (Item spawnedItem) =>
|
||||
{
|
||||
spawnedItem.SpawnedInCurrentOutpost = item.SpawnedInCurrentOutpost;
|
||||
spawnedItem.StolenDuringRound = targetItem.StolenDuringRound;
|
||||
spawnedItem.AllowStealing = targetItem.AllowStealing;
|
||||
spawnedItem.OriginalOutpost = targetItem.OriginalOutpost;
|
||||
spawnedItem.SpawnedInCurrentOutpost = targetItem.SpawnedInCurrentOutpost;
|
||||
for (int i = 0; i < outputContainer.Capacity; i++)
|
||||
{
|
||||
var containedItem = outputContainer.Inventory.GetItemAt(i);
|
||||
|
||||
@@ -30,11 +30,8 @@ namespace Barotrauma.Items.Components
|
||||
Serialize(500.0f, IsPropertySaveable.Yes, description: "The amount of force exerted on the submarine when the engine is operating at full power.")]
|
||||
public float MaxForce
|
||||
{
|
||||
get { return maxForce; }
|
||||
set
|
||||
{
|
||||
maxForce = Math.Max(0.0f, value);
|
||||
}
|
||||
get => maxForce;
|
||||
set => maxForce = Math.Max(0.0f, value);
|
||||
}
|
||||
|
||||
[Editable, Serialize("0.0,0.0", IsPropertySaveable.Yes,
|
||||
@@ -94,7 +91,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
|
||||
partial void InitProjSpecific(ContentXElement element);
|
||||
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
UpdateOnActiveEffects(deltaTime);
|
||||
@@ -129,12 +126,14 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
forceMultiplier *= MathHelper.Lerp(0.5f, 2.0f, (float)Math.Sqrt(User.GetSkillLevel("helm") / 100));
|
||||
}
|
||||
currForce *= maxForce * forceMultiplier;
|
||||
if (item.GetComponent<Repairable>() is Repairable repairable && repairable.IsTinkering)
|
||||
currForce *= item.StatManager.GetAdjustedValue(ItemTalentStats.EngineMaxSpeed, MaxForce) * forceMultiplier;
|
||||
if (item.GetComponent<Repairable>() is { IsTinkering: true } repairable)
|
||||
{
|
||||
currForce *= 1f + repairable.TinkeringStrength * TinkeringForceIncrease;
|
||||
}
|
||||
|
||||
currForce = item.StatManager.GetAdjustedValue(ItemTalentStats.EngineSpeed, currForce);
|
||||
|
||||
//less effective when in a bad condition
|
||||
currForce *= MathHelper.Lerp(0.5f, 2.0f, condition);
|
||||
if (item.Submarine.FlippedX) { currForce *= -1; }
|
||||
|
||||
@@ -89,7 +89,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
DebugConsole.ThrowError("Error in item " + item.Name + "! Fabrication recipes should be defined in the craftable item's xml, not in the fabricator.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var fabricationRecipes = new Dictionary<uint, FabricationRecipe>();
|
||||
@@ -104,6 +104,18 @@ namespace Barotrauma.Items.Components
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
bool recipeInvalid = false;
|
||||
foreach (var requiredItem in recipe.RequiredItems)
|
||||
{
|
||||
if (requiredItem.ItemPrefabs.None())
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in the fabrication recipe for \"{itemPrefab.Name}\". Could not find the ingredient \"{requiredItem}\".");
|
||||
recipeInvalid = true;
|
||||
}
|
||||
}
|
||||
if (recipeInvalid) { continue; }
|
||||
|
||||
fabricationRecipes.Add(recipe.RecipeHash, recipe);
|
||||
if (recipe.FabricationLimitMax >= 0)
|
||||
{
|
||||
@@ -356,9 +368,10 @@ namespace Barotrauma.Items.Components
|
||||
bool ingredientsStolen = false;
|
||||
bool ingredientsAllowStealing = true;
|
||||
|
||||
if (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer)
|
||||
if (GameMain.NetworkMember is null || GameMain.NetworkMember.IsServer)
|
||||
{
|
||||
fabricatedItem.RequiredItems.ForEach(requiredItem =>
|
||||
List<Item> foundAvailableItems = new List<Item>();
|
||||
foreach (FabricationRecipe.RequiredItem requiredItem in fabricatedItem.RequiredItems)
|
||||
{
|
||||
for (int usedPrefabsAmount = 0; usedPrefabsAmount < requiredItem.Amount; usedPrefabsAmount++)
|
||||
{
|
||||
@@ -367,10 +380,7 @@ namespace Barotrauma.Items.Components
|
||||
if (!availableIngredients.ContainsKey(requiredPrefab.Identifier)) { continue; }
|
||||
|
||||
var availableItems = availableIngredients[requiredPrefab.Identifier];
|
||||
var availableItem = availableItems.FirstOrDefault(potentialPrefab =>
|
||||
{
|
||||
return requiredItem.IsConditionSuitable(potentialPrefab.ConditionPercentage);
|
||||
});
|
||||
var availableItem = availableItems.FirstOrDefault(potentialPrefab => requiredItem.IsConditionSuitable(potentialPrefab.ConditionPercentage));
|
||||
|
||||
if (availableItem == null) { continue; }
|
||||
|
||||
@@ -401,13 +411,21 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
foundAvailableItems.Add(availableItem);
|
||||
availableItems.Remove(availableItem);
|
||||
Entity.Spawner.AddItemToRemoveQueue(availableItem);
|
||||
inputContainer.Inventory.RemoveItem(availableItem);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
var fabricationIngredients = new AbilityFabricationItemIngredients(foundAvailableItems);
|
||||
user.CheckTalents(AbilityEffectType.OnItemFabricatedIngredients, fabricationIngredients);
|
||||
|
||||
foreach (Item availableItem in fabricationIngredients.Items)
|
||||
{
|
||||
Entity.Spawner.AddItemToRemoveQueue(availableItem);
|
||||
inputContainer.Inventory.RemoveItem(availableItem);
|
||||
}
|
||||
|
||||
int amountFittingContainer = outputContainer.Inventory.HowManyCanBePut(fabricatedItem.TargetItem, fabricatedItem.OutCondition * fabricatedItem.TargetItem.Health);
|
||||
|
||||
@@ -535,12 +553,13 @@ namespace Barotrauma.Items.Components
|
||||
return currPowerConsumption;
|
||||
}
|
||||
|
||||
private int GetFabricatedItemQuality(FabricationRecipe fabricatedItem, Character user)
|
||||
private static int GetFabricatedItemQuality(FabricationRecipe fabricatedItem, Character user)
|
||||
{
|
||||
if (user == null) { return 0; }
|
||||
if (user?.Info == null) { return 0; }
|
||||
if (fabricatedItem.TargetItem.ConfigElement.GetChildElement("Quality") == null) { return 0; }
|
||||
int quality = 0;
|
||||
float floatQuality = 0.0f;
|
||||
floatQuality += user.GetStatValue(StatTypes.IncreaseFabricationQuality);
|
||||
foreach (var tag in fabricatedItem.TargetItem.Tags)
|
||||
{
|
||||
floatQuality += user.Info.GetSavedStatValue(StatTypes.IncreaseFabricationQuality, tag);
|
||||
@@ -637,9 +656,14 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
//fabricating takes 100 times longer if degree of success is close to 0
|
||||
//characters with a higher skill than required can fabricate up to 100% faster
|
||||
return fabricableItem.RequiredTime / FabricationSpeed / MathHelper.Clamp(t, 0.01f, 2.0f);
|
||||
float time = fabricableItem.RequiredTime / item.StatManager.GetAdjustedValue(ItemTalentStats.FabricationSpeed, FabricationSpeed) / MathHelper.Clamp(t, 0.01f, 2.0f);
|
||||
if (user is not null && fabricableItem.TargetItem is { } it && it.Tags.Contains("medical"))
|
||||
{
|
||||
time *= 1f + user.GetStatValue(StatTypes.FabricateMedicineSpeedMultiplier);
|
||||
}
|
||||
return time;
|
||||
}
|
||||
|
||||
|
||||
public float FabricationDegreeOfSuccess(Character character, ImmutableArray<Skill> skills)
|
||||
{
|
||||
if (skills.Length == 0) { return 1.0f; }
|
||||
@@ -713,7 +737,14 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
availableIngredients[itemIdentifier] = new List<Item>(itemList.Count);
|
||||
}
|
||||
availableIngredients[itemIdentifier].Add(item);
|
||||
//order by condition (prefer using worst-condition items)
|
||||
int index = 0;
|
||||
while (index < availableIngredients[itemIdentifier].Count &&
|
||||
availableIngredients[itemIdentifier][index].Condition < item.Condition)
|
||||
{
|
||||
index++;
|
||||
}
|
||||
availableIngredients[itemIdentifier].Insert(index, item);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -827,5 +858,15 @@ namespace Barotrauma.Items.Components
|
||||
public float Value { get; set; }
|
||||
public ItemPrefab ItemPrefab { get; set; }
|
||||
}
|
||||
|
||||
internal sealed class AbilityFabricationItemIngredients : AbilityObject
|
||||
{
|
||||
public List<Item> Items { get; set; }
|
||||
|
||||
public AbilityFabricationItemIngredients(List<Item> items)
|
||||
{
|
||||
Items = items;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,8 +57,8 @@ namespace Barotrauma.Items.Components
|
||||
[Editable, Serialize(80.0f, IsPropertySaveable.No, description: "How fast the item pumps water in/out when operating at 100%.", alwaysUseInstanceValues: true)]
|
||||
public float MaxFlow
|
||||
{
|
||||
get { return maxFlow; }
|
||||
set { maxFlow = value; }
|
||||
get => maxFlow;
|
||||
set => maxFlow = value;
|
||||
}
|
||||
|
||||
[Editable, Serialize(true, IsPropertySaveable.Yes, alwaysUseInstanceValues: true)]
|
||||
@@ -92,13 +92,16 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
|
||||
partial void InitProjSpecific(ContentXElement element);
|
||||
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
pumpSpeedLockTimer -= deltaTime;
|
||||
isActiveLockTimer -= deltaTime;
|
||||
|
||||
if (!IsActive) { return; }
|
||||
if (!IsActive)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
currFlow = 0.0f;
|
||||
|
||||
@@ -122,7 +125,10 @@ namespace Barotrauma.Items.Components
|
||||
FlowPercentage = ((float)TargetLevel - hullPercentage) * 10.0f;
|
||||
}
|
||||
|
||||
if (!HasPower) { return; }
|
||||
if (!HasPower)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
UpdateProjSpecific(deltaTime);
|
||||
|
||||
@@ -132,13 +138,15 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
float powerFactor = Math.Min(currPowerConsumption <= 0.0f || MinVoltage <= 0.0f ? 1.0f : Voltage, MaxOverVoltageFactor);
|
||||
|
||||
currFlow = flowPercentage / 100.0f * maxFlow * powerFactor;
|
||||
currFlow = flowPercentage / 100.0f * item.StatManager.GetAdjustedValue(ItemTalentStats.PumpMaxFlow, MaxFlow) * powerFactor;
|
||||
|
||||
if (item.GetComponent<Repairable>() is Repairable repairable && repairable.IsTinkering)
|
||||
if (item.GetComponent<Repairable>() is { IsTinkering: true } repairable)
|
||||
{
|
||||
currFlow *= 1f + repairable.TinkeringStrength * TinkeringSpeedIncrease;
|
||||
}
|
||||
|
||||
currFlow = item.StatManager.GetAdjustedValue(ItemTalentStats.PumpSpeed, currFlow);
|
||||
|
||||
//less effective when in a bad condition
|
||||
currFlow *= MathHelper.Lerp(0.5f, 1.0f, item.Condition / item.MaxCondition);
|
||||
|
||||
|
||||
@@ -83,19 +83,24 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (lastUser == value) { return; }
|
||||
lastUser = value;
|
||||
degreeOfSuccess = lastUser == null ? 0.0f : Math.Min(DegreeOfSuccess(lastUser), 1.0f);
|
||||
LastUserWasPlayer = lastUser.IsPlayer;
|
||||
if (lastUser == null)
|
||||
{
|
||||
degreeOfSuccess = 0.0f;
|
||||
LastUserWasPlayer = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
degreeOfSuccess = Math.Min(DegreeOfSuccess(lastUser), 1.0f);
|
||||
LastUserWasPlayer = lastUser.IsPlayer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[Editable(0.0f, float.MaxValue), Serialize(10000.0f, IsPropertySaveable.Yes, description: "How much power (kW) the reactor generates when operating at full capacity.", alwaysUseInstanceValues: true)]
|
||||
public float MaxPowerOutput
|
||||
{
|
||||
get { return maxPowerOutput; }
|
||||
set
|
||||
{
|
||||
maxPowerOutput = Math.Max(0.0f, value);
|
||||
}
|
||||
get => maxPowerOutput;
|
||||
set => maxPowerOutput = Math.Max(0.0f, value);
|
||||
}
|
||||
|
||||
[Editable(0.0f, float.MaxValue), Serialize(120.0f, IsPropertySaveable.Yes, description: "How long the temperature has to stay critical until a meltdown occurs.")]
|
||||
@@ -144,11 +149,11 @@ namespace Barotrauma.Items.Components
|
||||
turbineOutput = MathHelper.Clamp(value, 0.0f, 100.0f);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[Serialize(0.2f, IsPropertySaveable.Yes, description: "How fast the condition of the contained fuel rods deteriorates per second."), Editable(0.0f, 1000.0f, decimals: 3)]
|
||||
public float FuelConsumptionRate
|
||||
{
|
||||
get { return fuelConsumptionRate; }
|
||||
get => fuelConsumptionRate;
|
||||
set
|
||||
{
|
||||
if (!MathUtils.IsValid(value)) return;
|
||||
@@ -248,6 +253,8 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
#endif
|
||||
|
||||
float maxPowerOut = GetMaxOutput();
|
||||
|
||||
if (signalControlledTargetFissionRate.HasValue && lastReceivedFissionRateSignalTime > Timing.TotalTime - 1)
|
||||
{
|
||||
TargetFissionRate = adjustValueWithoutOverShooting(TargetFissionRate, signalControlledTargetFissionRate.Value, deltaTime * 5.0f);
|
||||
@@ -281,9 +288,9 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
//use a smoothed "correct output" instead of the actual correct output based on the load
|
||||
//so the player doesn't have to keep adjusting the rate impossibly fast when the load fluctuates heavily
|
||||
if (!MathUtils.NearlyEqual(MaxPowerOutput, 0.0f))
|
||||
if (!MathUtils.NearlyEqual(maxPowerOut, 0.0f))
|
||||
{
|
||||
CorrectTurbineOutput += MathHelper.Clamp((Load / MaxPowerOutput * 100.0f) - CorrectTurbineOutput, -20.0f, 20.0f) * deltaTime;
|
||||
CorrectTurbineOutput += MathHelper.Clamp((Load / maxPowerOut * 100.0f) - CorrectTurbineOutput, -20.0f, 20.0f) * deltaTime;
|
||||
}
|
||||
|
||||
//calculate tolerances of the meters based on the skills of the user
|
||||
@@ -342,7 +349,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
if (!isConnectedToFriendlyOutpost)
|
||||
{
|
||||
item.Condition -= fissionRate / 100.0f * fuelConsumptionRate * deltaTime;
|
||||
item.Condition -= fissionRate / 100.0f * GetFuelConsumption() * deltaTime;
|
||||
}
|
||||
}
|
||||
fuelLeft += item.ConditionPercentage;
|
||||
@@ -351,10 +358,10 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
if (fissionRate > 0.0f)
|
||||
{
|
||||
if (item.AiTarget != null && MaxPowerOutput > 0)
|
||||
if (item.AiTarget != null && maxPowerOut > 0)
|
||||
{
|
||||
var aiTarget = item.AiTarget;
|
||||
float range = Math.Abs(currPowerConsumption) / MaxPowerOutput;
|
||||
float range = Math.Abs(currPowerConsumption) / maxPowerOut;
|
||||
aiTarget.SoundRange = MathHelper.Lerp(aiTarget.MinSoundRange, aiTarget.MaxSoundRange, range);
|
||||
if (item.CurrentHull != null)
|
||||
{
|
||||
@@ -425,15 +432,17 @@ namespace Barotrauma.Items.Components
|
||||
tolerance = 3f;
|
||||
}
|
||||
|
||||
float maxPowerOut = GetMaxOutput();
|
||||
|
||||
float temperatureFactor = Math.Min(temperature / 50.0f, 1.0f);
|
||||
float minOutput = MaxPowerOutput * Math.Clamp(Math.Min((turbineOutput - tolerance) / 100.0f, temperatureFactor), 0, 1);
|
||||
float maxOutput = MaxPowerOutput * Math.Min((turbineOutput + tolerance) / 100.0f, temperatureFactor);
|
||||
float minOutput = maxPowerOut * Math.Clamp(Math.Min((turbineOutput - tolerance) / 100.0f, temperatureFactor), 0, 1);
|
||||
float maxOutput = maxPowerOut * Math.Min((turbineOutput + tolerance) / 100.0f, temperatureFactor);
|
||||
|
||||
minUpdatePowerOut = minOutput;
|
||||
maxUpdatePowerOut = maxOutput;
|
||||
|
||||
float reactorMax = PowerOn ? MaxPowerOutput : maxUpdatePowerOut;
|
||||
|
||||
float reactorMax = PowerOn ? maxPowerOut : maxUpdatePowerOut;
|
||||
|
||||
return new PowerRange(minOutput, maxOutput, reactorMax);
|
||||
}
|
||||
|
||||
@@ -456,11 +465,13 @@ namespace Barotrauma.Items.Components
|
||||
float output = MathHelper.Clamp(ratio * (maxUpdatePowerOut - minUpdatePowerOut) + minUpdatePowerOut, minUpdatePowerOut, maxUpdatePowerOut);
|
||||
float newLoad = loadLeft;
|
||||
|
||||
float maxOutput = GetMaxOutput();
|
||||
|
||||
//Adjust behaviour for multi reactor setup
|
||||
if (MaxPowerOutput != minMaxPower.ReactorMaxOutput)
|
||||
if (maxOutput != minMaxPower.ReactorMaxOutput)
|
||||
{
|
||||
float idealLoad = MaxPowerOutput / minMaxPower.ReactorMaxOutput * loadLeft;
|
||||
float loadAdjust = MathHelper.Clamp((ratio - 0.5f) * 25 + idealLoad - (turbineOutput / 100 * MaxPowerOutput), -MaxPowerOutput / 100, MaxPowerOutput / 100);
|
||||
float idealLoad = maxOutput / minMaxPower.ReactorMaxOutput * loadLeft;
|
||||
float loadAdjust = MathHelper.Clamp((ratio - 0.5f) * 25 + idealLoad - (turbineOutput / 100 * maxOutput), -maxOutput / 100, maxOutput / 100);
|
||||
newLoad = MathHelper.Clamp(loadLeft - (expectedPower - output) + loadAdjust, 0, loadLeft);
|
||||
}
|
||||
|
||||
@@ -501,7 +512,7 @@ namespace Barotrauma.Items.Components
|
||||
//calculate the maximum output if the fission rate is cranked as high as it goes and turbine output is at max
|
||||
float theoreticalMaxHeat = GetGeneratedHeat(fissionRate: maxFissionRate);
|
||||
float temperatureFactor = Math.Min(theoreticalMaxHeat / 50.0f, 1.0f);
|
||||
float theoreticalMaxOutput = Math.Min(maxTurbineOutput / 100.0f, temperatureFactor) * MaxPowerOutput;
|
||||
float theoreticalMaxOutput = Math.Min(maxTurbineOutput / 100.0f, temperatureFactor) * GetMaxOutput();
|
||||
|
||||
//maximum output not enough, we need more fuel
|
||||
return theoreticalMaxOutput < Load * minimumOutputRatio;
|
||||
@@ -685,7 +696,7 @@ namespace Barotrauma.Items.Components
|
||||
aiUpdateTimer = AIUpdateInterval;
|
||||
// load more fuel if the current maximum output is only 50% of the current load
|
||||
// or if the fuel rod is (almost) deplenished
|
||||
float minCondition = fuelConsumptionRate * MathUtils.Pow2((degreeOfSuccess - refuelLimit) * 2);
|
||||
float minCondition = GetFuelConsumption() * MathUtils.Pow2((degreeOfSuccess - refuelLimit) * 2);
|
||||
if (NeedMoreFuel(minimumOutputRatio: 0.5f, minCondition: minCondition))
|
||||
{
|
||||
bool outOfFuel = false;
|
||||
@@ -863,5 +874,8 @@ namespace Barotrauma.Items.Components
|
||||
if (GameMain.NetworkMember is { IsServer: true }) { unsentChanges = true; }
|
||||
}
|
||||
}
|
||||
|
||||
private float GetMaxOutput() => item.StatManager.GetAdjustedValue(ItemTalentStats.ReactorMaxOutput, MaxPowerOutput);
|
||||
private float GetFuelConsumption() => item.StatManager.GetAdjustedValue(ItemTalentStats.ReactorFuelEfficiency, fuelConsumptionRate);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,13 +153,6 @@ namespace Barotrauma.Items.Components
|
||||
bool changed = currentMode != value;
|
||||
|
||||
currentMode = value;
|
||||
if (value == Mode.Passive)
|
||||
{
|
||||
if (item.AiTarget != null)
|
||||
{
|
||||
item.AiTarget.SectorDegrees = 360.0f;
|
||||
}
|
||||
}
|
||||
#if CLIENT
|
||||
if (changed) { prevPassivePingRadius = float.MaxValue; }
|
||||
UpdateGUIElements();
|
||||
@@ -204,15 +197,13 @@ namespace Barotrauma.Items.Components
|
||||
if (currentPingIndex != -1)
|
||||
{
|
||||
var activePing = activePings[currentPingIndex];
|
||||
if (item.AiTarget != null)
|
||||
{
|
||||
float range = MathUtils.InverseLerp(item.AiTarget.MinSoundRange, item.AiTarget.MaxSoundRange, Range * activePing.State / zoom);
|
||||
item.AiTarget.SoundRange = MathHelper.Lerp(item.AiTarget.MinSoundRange, item.AiTarget.MaxSoundRange, range);
|
||||
}
|
||||
if (activePing.State > 1.0f)
|
||||
{
|
||||
if (item.AiTarget != null)
|
||||
{
|
||||
float range = MathUtils.InverseLerp(item.AiTarget.MinSoundRange, item.AiTarget.MaxSoundRange, Range * activePing.State / zoom);
|
||||
item.AiTarget.SoundRange = MathHelper.Lerp(item.AiTarget.MinSoundRange, item.AiTarget.MaxSoundRange, range);
|
||||
item.AiTarget.SectorDegrees = activePing.IsDirectional ? DirectionalPingSector : 360.0f;
|
||||
item.AiTarget.SectorDir = new Vector2(pingDirection.X, -pingDirection.Y);
|
||||
}
|
||||
aiPingCheckPending = true;
|
||||
currentPingIndex = -1;
|
||||
}
|
||||
@@ -228,15 +219,16 @@ namespace Barotrauma.Items.Components
|
||||
activePings[currentPingIndex].Direction = pingDirection;
|
||||
activePings[currentPingIndex].State = 0.0f;
|
||||
activePings[currentPingIndex].PrevPingRadius = 0.0f;
|
||||
if (item.AiTarget != null)
|
||||
{
|
||||
item.AiTarget.SectorDegrees = useDirectionalPing ? DirectionalPingSector : 360.0f;
|
||||
item.AiTarget.SectorDir = new Vector2(pingDirection.X, -pingDirection.Y);
|
||||
}
|
||||
item.Use(deltaTime);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (item.AiTarget != null)
|
||||
{
|
||||
item.AiTarget.SectorDegrees = 360.0f;
|
||||
}
|
||||
aiPingCheckPending = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ namespace Barotrauma.Items.Components
|
||||
[Editable, Serialize(10.0f, IsPropertySaveable.Yes, description: "The maximum capacity of the device (kW * min). For example, a value of 1000 means the device can output 100 kilowatts of power for 10 minutes, or 1000 kilowatts for 1 minute.")]
|
||||
public float Capacity
|
||||
{
|
||||
get { return capacity; }
|
||||
get => capacity;
|
||||
set { capacity = Math.Max(value, 1.0f); }
|
||||
}
|
||||
|
||||
@@ -89,7 +89,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
public float ChargePercentage => MathUtils.Percentage(Charge, Capacity);
|
||||
public float ChargePercentage => MathUtils.Percentage(Charge, GetCapacity());
|
||||
|
||||
[Editable, Serialize(10.0f, IsPropertySaveable.Yes, description: "How fast the device can be recharged. For example, a recharge speed of 100 kW and a capacity of 1000 kW*min would mean it takes 10 minutes to fully charge the device.")]
|
||||
public float MaxRechargeSpeed
|
||||
@@ -125,10 +125,19 @@ namespace Barotrauma.Items.Components
|
||||
set { efficiency = MathHelper.Clamp(value, 0.0f, 1.0f); }
|
||||
}
|
||||
|
||||
private bool flipIndicator;
|
||||
[Editable, Serialize(false, IsPropertySaveable.Yes, description: "Should the progress bar indicating the charge be flipped to fill from the other side.")]
|
||||
public bool FlipIndicator
|
||||
{
|
||||
get { return flipIndicator; }
|
||||
set { flipIndicator = value; }
|
||||
}
|
||||
|
||||
public float RechargeRatio => RechargeSpeed / MaxRechargeSpeed;
|
||||
|
||||
public const float aiRechargeTargetRatio = 0.5f;
|
||||
private bool isRunning;
|
||||
|
||||
public bool HasBeenTuned { get; private set; }
|
||||
|
||||
public PowerContainer(Item item, ContentXElement element)
|
||||
@@ -146,7 +155,7 @@ namespace Barotrauma.Items.Components
|
||||
return picker != null;
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
if (item.Connections == null)
|
||||
{
|
||||
@@ -283,7 +292,7 @@ namespace Barotrauma.Items.Components
|
||||
else
|
||||
{
|
||||
//Decrease charge based on how much power is leaving the device
|
||||
Charge = Math.Clamp(Charge - CurrPowerOutput / 60 * UpdateInterval, 0, Capacity);
|
||||
Charge = Math.Clamp(Charge - CurrPowerOutput / 60 * UpdateInterval, 0, GetCapacity());
|
||||
prevCharge = Charge;
|
||||
}
|
||||
}
|
||||
@@ -370,5 +379,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public float GetCapacity() => item.StatManager.GetAdjustedValue(ItemTalentStats.BatteryCapacity, Capacity);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -736,6 +736,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (target.IsSensor) { return false; }
|
||||
if (hits.Contains(target.Body)) { return false; }
|
||||
if (target.Body.UserData is Submarine)
|
||||
{
|
||||
@@ -881,7 +882,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
attackResult = Attack.DoDamage(User ?? Attacker, targetItem, item.WorldPosition, 1.0f);
|
||||
#if CLIENT
|
||||
if (attackResult.Damage > 0.0f)
|
||||
if (attackResult.Damage > 0.0f && targetItem.Prefab.ShowHealthBar)
|
||||
{
|
||||
Character.Controlled?.UpdateHUDProgressBar(targetItem,
|
||||
targetItem.WorldPosition,
|
||||
|
||||
@@ -6,6 +6,7 @@ using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.Abilities;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
@@ -420,7 +421,8 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
if (item.ConditionPercentage > MinDeteriorationCondition)
|
||||
{
|
||||
item.Condition -= DeteriorationSpeed * deltaTime;
|
||||
float deteriorationSpeed = item.StatManager.GetAdjustedValue(ItemTalentStats.DetoriationSpeed, DeteriorationSpeed);
|
||||
item.Condition -= deteriorationSpeed * deltaTime;
|
||||
}
|
||||
}
|
||||
return;
|
||||
@@ -467,8 +469,14 @@ namespace Barotrauma.Items.Components
|
||||
wasGoodCondition = true;
|
||||
}
|
||||
|
||||
float talentMultiplier = CurrentFixer.GetStatValue(StatTypes.RepairSpeed);
|
||||
if (requiredSkills.Any(static skill => skill.Identifier == "mechanical"))
|
||||
{
|
||||
talentMultiplier += CurrentFixer.GetStatValue(StatTypes.MechanicalRepairSpeed);
|
||||
}
|
||||
|
||||
float fixDuration = MathHelper.Lerp(FixDurationLowSkill, FixDurationHighSkill, successFactor);
|
||||
fixDuration /= 1 + CurrentFixer.GetStatValue(StatTypes.RepairSpeed) + currentRepairItem?.Prefab.AddedRepairSpeedMultiplier ?? 0f;
|
||||
fixDuration /= 1 + talentMultiplier + currentRepairItem?.Prefab.AddedRepairSpeedMultiplier ?? 0f;
|
||||
fixDuration /= 1 + item.GetQualityModifier(Quality.StatType.RepairSpeed);
|
||||
|
||||
item.MaxRepairConditionMultiplier = GetMaxRepairConditionMultiplier(CurrentFixer);
|
||||
@@ -500,7 +508,7 @@ namespace Barotrauma.Items.Components
|
||||
SkillSettings.Current.SkillIncreasePerRepair / Math.Max(characterSkillLevel, 1.0f));
|
||||
}
|
||||
SteamAchievementManager.OnItemRepaired(item, CurrentFixer);
|
||||
CurrentFixer.CheckTalents(AbilityEffectType.OnRepairComplete);
|
||||
CurrentFixer.CheckTalents(AbilityEffectType.OnRepairComplete, new AbilityRepairable(item));
|
||||
}
|
||||
if (CurrentFixer?.SelectedItem == item) { CurrentFixer.SelectedItem = null; }
|
||||
deteriorationTimer = Rand.Range(MinDeteriorationDelay, MaxDeteriorationDelay);
|
||||
@@ -687,4 +695,14 @@ namespace Barotrauma.Items.Components
|
||||
//where set_active/set_state signals can disable the component
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class AbilityRepairable : AbilityObject, IAbilityItem
|
||||
{
|
||||
public Item Item { get; set; }
|
||||
|
||||
public AbilityRepairable(Item item)
|
||||
{
|
||||
Item = item;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -270,6 +270,9 @@ namespace Barotrauma.Items.Components
|
||||
public bool AutoEquipWhenFull { get; private set; }
|
||||
public bool DisplayContainedStatus { get; private set; }
|
||||
|
||||
[Serialize(false, IsPropertySaveable.No, description: "Can the item be used (assuming it has components that are usable in some way) when worn."), Editable(MinValueFloat = -1000.0f, MaxValueFloat = 1000.0f)]
|
||||
public bool AllowUseWhenWorn { get; set; }
|
||||
|
||||
public readonly int Variants;
|
||||
|
||||
private int variant;
|
||||
|
||||
@@ -226,6 +226,14 @@ namespace Barotrauma
|
||||
{
|
||||
foreach (var item in slots[i].Items)
|
||||
{
|
||||
if (item == null)
|
||||
{
|
||||
#if DEBUG
|
||||
DebugConsole.ThrowError($"Null item in inventory {Owner.ToString() ?? "null"}, slot {i}!");
|
||||
#endif
|
||||
continue;
|
||||
}
|
||||
|
||||
bool duplicateFound = false;
|
||||
for (int j = 0; j < i; j++)
|
||||
{
|
||||
|
||||
@@ -424,6 +424,39 @@ namespace Barotrauma
|
||||
|
||||
public Color? HighlightColor;
|
||||
|
||||
/// <summary>
|
||||
/// Can be used by status effects or conditionals to check whether the item is contained inside something
|
||||
/// </summary>
|
||||
public bool IsContained
|
||||
{
|
||||
get
|
||||
{
|
||||
return parentInventory != null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Can be used by status effects or conditionals to the speed of the item
|
||||
/// </summary>
|
||||
public float Speed
|
||||
{
|
||||
get
|
||||
{
|
||||
if (body != null && body.PhysEnabled)
|
||||
{
|
||||
return body.LinearVelocity.Length();
|
||||
}
|
||||
else if (ParentInventory?.Owner is Character character)
|
||||
{
|
||||
return character.AnimController.MainLimb.LinearVelocity.Length();
|
||||
}
|
||||
else if (container != null)
|
||||
{
|
||||
return container.Speed;
|
||||
}
|
||||
return 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
|
||||
@@ -821,6 +854,16 @@ namespace Barotrauma
|
||||
|
||||
public bool IsSecondaryItem { get; }
|
||||
|
||||
private ItemStatManager statManager;
|
||||
public ItemStatManager StatManager
|
||||
{
|
||||
get
|
||||
{
|
||||
statManager ??= new ItemStatManager(this);
|
||||
return statManager;
|
||||
}
|
||||
}
|
||||
|
||||
public Item(ItemPrefab itemPrefab, Vector2 position, Submarine submarine, ushort id = Entity.NullEntityID, bool callOnItemLoaded = true)
|
||||
: this(new Rectangle(
|
||||
(int)(position.X - itemPrefab.Sprite.size.X / 2 * itemPrefab.Scale),
|
||||
@@ -1837,16 +1880,32 @@ namespace Barotrauma
|
||||
|
||||
if (ic.IsActiveConditionals != null)
|
||||
{
|
||||
bool shouldBeActive = true;
|
||||
foreach (var conditional in ic.IsActiveConditionals)
|
||||
if (ic.IsActiveConditionalComparison == PropertyConditional.Comparison.And)
|
||||
{
|
||||
if (!ConditionalMatches(conditional))
|
||||
bool shouldBeActive = true;
|
||||
foreach (var conditional in ic.IsActiveConditionals)
|
||||
{
|
||||
shouldBeActive = false;
|
||||
break;
|
||||
if (!ConditionalMatches(conditional))
|
||||
{
|
||||
shouldBeActive = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
ic.IsActive = shouldBeActive;
|
||||
}
|
||||
else
|
||||
{
|
||||
bool shouldBeActive = false;
|
||||
foreach (var conditional in ic.IsActiveConditionals)
|
||||
{
|
||||
if (ConditionalMatches(conditional))
|
||||
{
|
||||
shouldBeActive = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
ic.IsActive = shouldBeActive;
|
||||
}
|
||||
ic.IsActive = shouldBeActive;
|
||||
}
|
||||
#if CLIENT
|
||||
if (ic.HasSounds)
|
||||
@@ -2072,7 +2131,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
//no need to apply buoyancy if the item is still and not light enough to float
|
||||
if (moving || body.Density < 10.0f)
|
||||
if (moving || body.Density <= 10.0f)
|
||||
{
|
||||
Vector2 buoyancy = -GameMain.World.Gravity * forceFactor * volume * Physics.NeutralDensity;
|
||||
body.ApplyForce(buoyancy);
|
||||
@@ -2699,8 +2758,6 @@ namespace Barotrauma
|
||||
}
|
||||
#endif
|
||||
|
||||
float applyOnSelfFraction = user?.GetStatValue(StatTypes.ApplyTreatmentsOnSelfFraction) ?? 0.0f;
|
||||
|
||||
bool remove = false;
|
||||
foreach (ItemComponent ic in components)
|
||||
{
|
||||
@@ -2713,19 +2770,7 @@ namespace Barotrauma
|
||||
ic.PlaySound(actionType, user);
|
||||
#endif
|
||||
ic.WasUsed = true;
|
||||
ic.ApplyStatusEffects(actionType, 1.0f, character, targetLimb, user: user, applyOnUserFraction: applyOnSelfFraction);
|
||||
|
||||
if (applyOnSelfFraction > 0.0f)
|
||||
{
|
||||
//hacky af
|
||||
ic.statusEffectLists.TryGetValue(actionType, out var effectList);
|
||||
if (effectList != null)
|
||||
{
|
||||
effectList.ForEach(e => e.AfflictionMultiplier = applyOnSelfFraction);
|
||||
ic.ApplyStatusEffects(actionType, 1.0f, user, targetLimb == null ? null : user.AnimController.GetLimb(targetLimb.type), user: user);
|
||||
effectList.ForEach(e => e.AfflictionMultiplier = 1.0f);
|
||||
}
|
||||
}
|
||||
ic.ApplyStatusEffects(actionType, 1.0f, character, targetLimb, user: user);
|
||||
|
||||
if (GameMain.NetworkMember is { IsServer: true })
|
||||
{
|
||||
@@ -2866,15 +2911,20 @@ namespace Barotrauma
|
||||
//to ensure client/server doesn't get any properties mixed up if there's some conditions that can vary between the server and the clients
|
||||
var allProperties = inGameEditableOnly ? GetInGameEditableProperties(ignoreConditions: true) : GetProperties<Editable>();
|
||||
SerializableProperty property = extraData.SerializableProperty;
|
||||
ISerializableEntity entity = extraData.Entity;
|
||||
if (property != null)
|
||||
{
|
||||
var propertyOwner = allProperties.Find(p => p.property == property);
|
||||
if (allProperties.Count > 1)
|
||||
{
|
||||
msg.WriteByte((byte)allProperties.FindIndex(p => p.property == property));
|
||||
int propertyIndex = allProperties.FindIndex(p => p.property == property && p.obj == entity);
|
||||
if (propertyIndex < -1)
|
||||
{
|
||||
throw new Exception($"Could not find the property \"{property.Name}\" in \"{entity.Name ?? "null"}\"");
|
||||
}
|
||||
msg.WriteVariableUInt32((uint)propertyIndex);
|
||||
}
|
||||
|
||||
object value = property.GetValue(propertyOwner.obj);
|
||||
object value = property.GetValue(entity);
|
||||
if (value is string stringVal)
|
||||
{
|
||||
msg.WriteString(stringVal);
|
||||
@@ -2979,7 +3029,7 @@ namespace Barotrauma
|
||||
int propertyIndex = 0;
|
||||
if (allProperties.Count > 1)
|
||||
{
|
||||
propertyIndex = msg.ReadByte();
|
||||
propertyIndex = (int)msg.ReadVariableUInt32();
|
||||
}
|
||||
|
||||
bool allowEditing = true;
|
||||
@@ -3119,14 +3169,14 @@ namespace Barotrauma
|
||||
}
|
||||
logPropertyChangeCoroutine = CoroutineManager.Invoke(() =>
|
||||
{
|
||||
GameServer.Log($"{sender.Character.Name} set the value \"{property.Name}\" of the item \"{Name}\" to \"{logValue}\".", ServerLog.MessageType.ItemInteraction);
|
||||
GameServer.Log($"{sender.Character?.Name ?? sender.Name} set the value \"{property.Name}\" of the item \"{Name}\" to \"{logValue}\".", ServerLog.MessageType.ItemInteraction);
|
||||
}, delay: 1.0f);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (GameMain.NetworkMember is { IsServer: true })
|
||||
if (GameMain.NetworkMember is { IsServer: true } && parentObject is ISerializableEntity entity)
|
||||
{
|
||||
GameMain.NetworkMember.CreateEntityEvent(this, new ChangePropertyEventData(property));
|
||||
GameMain.NetworkMember.CreateEntityEvent(this, new ChangePropertyEventData(property, entity));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3230,7 +3280,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (!(property.GetValue(item)?.Equals(prevValue) ?? true))
|
||||
{
|
||||
GameMain.NetworkMember.CreateEntityEvent(item, new ChangePropertyEventData(property));
|
||||
GameMain.NetworkMember.CreateEntityEvent(item, new ChangePropertyEventData(property, item));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3349,8 +3399,24 @@ namespace Barotrauma
|
||||
item.PurchasedNewSwap = false;
|
||||
}
|
||||
|
||||
item.condition = element.GetAttributeFloat("condition", item.condition);
|
||||
item.condition = MathHelper.Clamp(item.condition, 0, item.MaxCondition);
|
||||
if (element.GetAttribute("conditionpercentage") != null)
|
||||
{
|
||||
item.condition = element.GetAttributeFloat("conditionpercentage", 100.0f) / 100.0f * item.MaxCondition;
|
||||
}
|
||||
else
|
||||
{
|
||||
//backwards compatibility
|
||||
item.condition = element.GetAttributeFloat("condition", item.condition);
|
||||
//if the item was in full condition considering the unmodified health
|
||||
//(not taking possible HealthMultipliers added by mods into account),
|
||||
//make sure it stays in full condition
|
||||
bool wasFullCondition = item.condition >= item.Prefab.Health;
|
||||
if (wasFullCondition)
|
||||
{
|
||||
item.condition = item.MaxCondition;
|
||||
}
|
||||
item.condition = MathHelper.Clamp(item.condition, 0, item.MaxCondition);
|
||||
}
|
||||
item.lastSentCondition = item.condition;
|
||||
item.RecalculateConditionValues();
|
||||
item.SetActiveSprite();
|
||||
@@ -3370,6 +3436,7 @@ namespace Barotrauma
|
||||
|
||||
foreach (ItemComponent component in item.components)
|
||||
{
|
||||
if (component.Parent != null) { component.IsActive = component.Parent.IsActive; }
|
||||
component.OnItemLoaded();
|
||||
}
|
||||
|
||||
@@ -3401,11 +3468,6 @@ namespace Barotrauma
|
||||
element.Add(new XAttribute("availableswaps", string.Join(',', AvailableSwaps.Select(s => s.Identifier))));
|
||||
}
|
||||
|
||||
if (condition < MaxCondition)
|
||||
{
|
||||
element.Add(new XAttribute("condition", condition.ToString("G", CultureInfo.InvariantCulture)));
|
||||
}
|
||||
|
||||
if (!MathUtils.NearlyEqual(healthMultiplier, 1.0f))
|
||||
{
|
||||
element.Add(new XAttribute("healthmultiplier", HealthMultiplier.ToString("G", CultureInfo.InvariantCulture)));
|
||||
@@ -3442,6 +3504,16 @@ namespace Barotrauma
|
||||
upgrade.Save(element);
|
||||
}
|
||||
|
||||
if (condition < MaxCondition)
|
||||
{
|
||||
element.Add(new XAttribute("conditionpercentage", ConditionPercentage.ToString("G", CultureInfo.InvariantCulture)));
|
||||
}
|
||||
else
|
||||
{
|
||||
var conditionAttribute = element.GetAttribute("condition");
|
||||
if (conditionAttribute != null) { conditionAttribute.Remove(); }
|
||||
}
|
||||
|
||||
parentElement.Add(element);
|
||||
|
||||
return element;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Barotrauma.Items.Components;
|
||||
using Barotrauma.Networking;
|
||||
using Microsoft.Xna.Framework;
|
||||
@@ -18,9 +19,10 @@ namespace Barotrauma
|
||||
AssignCampaignInteraction = 6,
|
||||
ApplyStatusEffect = 7,
|
||||
Upgrade = 8,
|
||||
ItemStat = 9,
|
||||
|
||||
MinValue = 0,
|
||||
MaxValue = 6
|
||||
MaxValue = 9
|
||||
}
|
||||
|
||||
public interface IEventData : NetEntityEvent.IData
|
||||
@@ -56,10 +58,24 @@ namespace Barotrauma
|
||||
{
|
||||
public EventType EventType => EventType.ChangeProperty;
|
||||
public readonly SerializableProperty SerializableProperty;
|
||||
public readonly ISerializableEntity Entity;
|
||||
|
||||
public ChangePropertyEventData(SerializableProperty serializableProperty)
|
||||
public ChangePropertyEventData(SerializableProperty serializableProperty, ISerializableEntity entity)
|
||||
{
|
||||
SerializableProperty = serializableProperty;
|
||||
Entity = entity;
|
||||
}
|
||||
}
|
||||
|
||||
public readonly struct SetItemStatEventData : IEventData
|
||||
{
|
||||
public EventType EventType => EventType.ItemStat;
|
||||
|
||||
public readonly Dictionary<ItemStatManager.TalentStatIdentifier, float> Stats;
|
||||
|
||||
public SetItemStatEventData(Dictionary<ItemStatManager.TalentStatIdentifier, float> stats)
|
||||
{
|
||||
Stats = stats;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -47,8 +47,8 @@ namespace Barotrauma
|
||||
CopyCondition = element.GetAttributeBool("copycondition", false);
|
||||
Commonness = element.GetAttributeFloat("commonness", 1.0f);
|
||||
RequiredDeconstructor = element.GetAttributeStringArray("requireddeconstructor",
|
||||
element.Parent?.GetAttributeStringArray("requireddeconstructor", new string[0]) ?? new string[0]);
|
||||
RequiredOtherItem = element.GetAttributeStringArray("requiredotheritem", new string[0]);
|
||||
element.Parent?.GetAttributeStringArray("requireddeconstructor", Array.Empty<string>()) ?? Array.Empty<string>());
|
||||
RequiredOtherItem = element.GetAttributeStringArray("requiredotheritem", Array.Empty<string>());
|
||||
ActivateButtonText = element.GetAttributeString("activatebuttontext", string.Empty);
|
||||
InfoText = element.GetAttributeString("infotext", string.Empty);
|
||||
InfoTextOnOtherItemMissing = element.GetAttributeString("infotextonotheritemmissing", string.Empty);
|
||||
@@ -102,12 +102,13 @@ namespace Barotrauma
|
||||
{
|
||||
public readonly Identifier ItemPrefabIdentifier;
|
||||
|
||||
public ItemPrefab ItemPrefab => ItemPrefab.Prefabs.TryGet(ItemPrefabIdentifier, out var prefab) ? prefab
|
||||
: MapEntityPrefab.FindByName(ItemPrefabIdentifier.Value) as ItemPrefab ?? throw new Exception($"No ItemPrefab with identifier or name \"{ItemPrefabIdentifier}\"");
|
||||
public ItemPrefab ItemPrefab =>
|
||||
ItemPrefab.Prefabs.TryGet(ItemPrefabIdentifier, out var prefab) ? prefab
|
||||
: MapEntityPrefab.FindByName(ItemPrefabIdentifier.Value) as ItemPrefab;
|
||||
|
||||
public override UInt32 UintIdentifier { get; }
|
||||
|
||||
public override IEnumerable<ItemPrefab> ItemPrefabs => ItemPrefab.ToEnumerable();
|
||||
public override IEnumerable<ItemPrefab> ItemPrefabs => ItemPrefab == null ? Enumerable.Empty<ItemPrefab>() : ItemPrefab.ToEnumerable();
|
||||
|
||||
public override ItemPrefab FirstMatchingPrefab => ItemPrefab;
|
||||
|
||||
@@ -122,6 +123,11 @@ namespace Barotrauma
|
||||
using MD5 md5 = MD5.Create();
|
||||
UintIdentifier = ToolBox.IdentifierToUint32Hash(itemPrefab, md5);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{base.ToString()} ({ItemPrefabIdentifier})";
|
||||
}
|
||||
}
|
||||
|
||||
public class RequiredItemByTag : RequiredItem
|
||||
@@ -146,6 +152,11 @@ namespace Barotrauma
|
||||
using MD5 md5 = MD5.Create();
|
||||
UintIdentifier = ToolBox.IdentifierToUint32Hash(tag, md5);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{base.ToString()} ({Tag})";
|
||||
}
|
||||
}
|
||||
|
||||
public readonly Identifier TargetItemPrefabIdentifier;
|
||||
@@ -390,6 +401,8 @@ namespace Barotrauma
|
||||
{
|
||||
public static readonly PrefabCollection<ItemPrefab> Prefabs = new PrefabCollection<ItemPrefab>();
|
||||
|
||||
public const float DefaultInteractDistance = 120.0f;
|
||||
|
||||
//default size
|
||||
public Vector2 Size { get; private set; }
|
||||
|
||||
@@ -410,7 +423,6 @@ namespace Barotrauma
|
||||
public ImmutableArray<Rectangle> Triggers { get; private set; }
|
||||
|
||||
private ImmutableDictionary<Identifier, float> treatmentSuitability;
|
||||
private readonly List<XElement> fabricationRecipeElements = new List<XElement>();
|
||||
|
||||
/// <summary>
|
||||
/// Is this prefab overriding a prefab in another content package
|
||||
@@ -590,7 +602,7 @@ namespace Barotrauma
|
||||
public override ImmutableHashSet<string> Aliases => aliases;
|
||||
|
||||
//how close the Character has to be to the item to pick it up
|
||||
[Serialize(120.0f, IsPropertySaveable.No)]
|
||||
[Serialize(DefaultInteractDistance, IsPropertySaveable.No)]
|
||||
public float InteractDistance { get; private set; }
|
||||
|
||||
// this can be used to allow items which are behind other items tp
|
||||
@@ -752,6 +764,9 @@ namespace Barotrauma
|
||||
[Serialize(false, IsPropertySaveable.No)]
|
||||
public bool DontTransferBetweenSubs { get; private set; }
|
||||
|
||||
[Serialize(true, IsPropertySaveable.No)]
|
||||
public bool ShowHealthBar { get; private set; }
|
||||
|
||||
protected override Identifier DetermineIdentifier(XElement element)
|
||||
{
|
||||
Identifier identifier = base.DetermineIdentifier(element);
|
||||
@@ -1143,7 +1158,7 @@ namespace Barotrauma
|
||||
public bool CanBeBoughtFrom(Location.StoreInfo store, out PriceInfo priceInfo)
|
||||
{
|
||||
priceInfo = GetPriceInfo(store);
|
||||
return priceInfo != null && priceInfo.CanBeBought && (store.Location?.LevelData?.Difficulty ?? 0) >= priceInfo.MinLevelDifficulty;
|
||||
return priceInfo is { CanBeBought: true } && (store.Location?.LevelData?.Difficulty ?? 0) >= priceInfo.MinLevelDifficulty;
|
||||
}
|
||||
|
||||
public bool CanBeBoughtFrom(Location location)
|
||||
@@ -1240,13 +1255,12 @@ namespace Barotrauma
|
||||
throw new ArgumentException("Both name and identifier cannot be null.");
|
||||
}
|
||||
|
||||
ItemPrefab prefab;
|
||||
if (identifier.IsEmpty)
|
||||
{
|
||||
//legacy support
|
||||
identifier = GenerateLegacyIdentifier(name);
|
||||
}
|
||||
Prefabs.TryGet(identifier, out prefab);
|
||||
Prefabs.TryGet(identifier, out ItemPrefab prefab);
|
||||
|
||||
//not found, see if we can find a prefab with a matching alias
|
||||
if (prefab == null && !string.IsNullOrEmpty(name))
|
||||
@@ -1294,8 +1308,8 @@ namespace Barotrauma
|
||||
return PreferredContainers.Any(pc => IsItemConditionAcceptable(item, pc) && IsContainerPreferred(pc.Secondary, identifiersOrTags));
|
||||
}
|
||||
|
||||
private bool IsItemConditionAcceptable(Item item, PreferredContainer pc) => item.ConditionPercentage >= pc.MinCondition && item.ConditionPercentage <= pc.MaxCondition;
|
||||
private bool CanBeTransferred(Identifier item, PreferredContainer pc, ItemContainer targetContainer) =>
|
||||
private static bool IsItemConditionAcceptable(Item item, PreferredContainer pc) => item.ConditionPercentage >= pc.MinCondition && item.ConditionPercentage <= pc.MaxCondition;
|
||||
private static bool CanBeTransferred(Identifier item, PreferredContainer pc, ItemContainer targetContainer) =>
|
||||
pc.AllowTransfersHere && (!pc.TransferOnlyOnePerContainer || targetContainer.Inventory.AllItems.None(i => i.Prefab.Identifier == item));
|
||||
|
||||
public static bool IsContainerPreferred(IEnumerable<Identifier> preferences, ItemContainer c) => preferences.Any(id => c.Item.Prefab.Identifier == id || c.Item.HasTag(id));
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
#nullable enable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
internal sealed class ItemStatManager
|
||||
{
|
||||
private Item item;
|
||||
|
||||
public ItemStatManager(Item item)
|
||||
{
|
||||
this.item = item;
|
||||
}
|
||||
|
||||
[NetworkSerialize]
|
||||
public readonly record struct TalentStatIdentifier(ItemTalentStats Stat, Identifier TalentIdentifier, UInt32 CharacterID) : INetSerializableStruct
|
||||
{
|
||||
public override int GetHashCode() => HashCode.Combine(TalentIdentifier, CharacterID, Stat);
|
||||
}
|
||||
|
||||
private readonly Dictionary<TalentStatIdentifier, float> talentStats = new();
|
||||
|
||||
public void ApplyStat(ItemTalentStats stat, float value, CharacterTalent talent)
|
||||
{
|
||||
if (talent.Character?.ID is not { } characterId ||
|
||||
talent.Prefab?.Identifier is not { } talentIdentifier)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
TalentStatIdentifier identifier = new TalentStatIdentifier(stat, talentIdentifier, characterId);
|
||||
talentStats[identifier] = value;
|
||||
|
||||
#if SERVER
|
||||
if (GameMain.NetworkMember is { IsServer: true } server)
|
||||
{
|
||||
server.CreateEntityEvent(item, new Item.SetItemStatEventData(talentStats));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// Used for getting the value value from network packet
|
||||
public void ApplyStat(TalentStatIdentifier identifier, float value)
|
||||
{
|
||||
talentStats[identifier] = value;
|
||||
}
|
||||
|
||||
public float GetAdjustedValue(ItemTalentStats stat, float originalValue)
|
||||
{
|
||||
float total = originalValue;
|
||||
foreach (var (key, value) in talentStats)
|
||||
{
|
||||
if (key.Stat == stat)
|
||||
{
|
||||
total *= value;
|
||||
}
|
||||
}
|
||||
|
||||
return total;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.Extensions;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -21,6 +22,8 @@ namespace Barotrauma
|
||||
|
||||
public bool MatchOnEmpty { get; set; }
|
||||
|
||||
public bool RequireEmpty { get; set; }
|
||||
|
||||
public bool IgnoreInEditor { get; set; }
|
||||
|
||||
private ImmutableHashSet<Identifier> excludedIdentifiers;
|
||||
@@ -133,40 +136,52 @@ namespace Barotrauma
|
||||
if (parentItem == null) { return false; }
|
||||
return CheckContained(parentItem);
|
||||
case RelationType.Container:
|
||||
if (parentItem == null || parentItem.Container == null) { return MatchOnEmpty; }
|
||||
return (!ExcludeBroken || parentItem.Container.Condition > 0.0f) && (!ExcludeFullCondition || !parentItem.Container.IsFullCondition) && MatchesItem(parentItem.Container);
|
||||
if (parentItem == null || parentItem.Container == null) { return MatchOnEmpty || RequireEmpty; }
|
||||
return CheckItem(parentItem.Container, this);
|
||||
case RelationType.Equipped:
|
||||
if (character == null) { return false; }
|
||||
if (MatchOnEmpty && !character.HeldItems.Any()) { return true; }
|
||||
foreach (Item equippedItem in character.HeldItems)
|
||||
var heldItems = character.HeldItems;
|
||||
if ((RequireEmpty || MatchOnEmpty) && heldItems.None()) { return true; }
|
||||
foreach (Item equippedItem in heldItems)
|
||||
{
|
||||
if (equippedItem == null) { continue; }
|
||||
if ((!ExcludeBroken || equippedItem.Condition > 0.0f) && (!ExcludeFullCondition || !equippedItem.IsFullCondition) && MatchesItem(equippedItem)) { return true; }
|
||||
if (CheckItem(equippedItem, this))
|
||||
{
|
||||
if (RequireEmpty && equippedItem.Condition > 0) { return false; }
|
||||
return true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case RelationType.Picked:
|
||||
if (character == null || character.Inventory == null) { return false; }
|
||||
foreach (Item pickedItem in character.Inventory.AllItems)
|
||||
if (character == null) { return false; }
|
||||
if (character.Inventory == null) { return MatchOnEmpty || RequireEmpty; }
|
||||
var allItems = character.Inventory.AllItems;
|
||||
if ((RequireEmpty || MatchOnEmpty) && allItems.None()) { return true; }
|
||||
foreach (Item pickedItem in allItems)
|
||||
{
|
||||
if (MatchesItem(pickedItem)) { return true; }
|
||||
if (pickedItem == null) { continue; }
|
||||
if (CheckItem(pickedItem, this))
|
||||
{
|
||||
if (RequireEmpty && pickedItem.Condition > 0) { return false; }
|
||||
return true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool CheckItem(Item i, RelatedItem ri) => (!ri.ExcludeBroken || ri.RequireEmpty || i.Condition > 0.0f) && (!ri.ExcludeFullCondition || !i.IsFullCondition) && ri.MatchesItem(i);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool CheckContained(Item parentItem)
|
||||
{
|
||||
if (parentItem.OwnInventory == null) { return false; }
|
||||
|
||||
if (MatchOnEmpty && parentItem.OwnInventory.IsEmpty())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool isEmpty = parentItem.OwnInventory.IsEmpty();
|
||||
if (RequireEmpty && !isEmpty) { return false; }
|
||||
if (MatchOnEmpty && isEmpty) { return true; }
|
||||
foreach (Item contained in parentItem.ContainedItems)
|
||||
{
|
||||
if (TargetSlot > -1 && parentItem.OwnInventory.FindIndex(contained) != TargetSlot) { continue; }
|
||||
@@ -184,6 +199,7 @@ namespace Barotrauma
|
||||
new XAttribute("optional", IsOptional),
|
||||
new XAttribute("ignoreineditor", IgnoreInEditor),
|
||||
new XAttribute("excludebroken", ExcludeBroken),
|
||||
new XAttribute("requireempty", RequireEmpty),
|
||||
new XAttribute("excludefullcondition", ExcludeFullCondition),
|
||||
new XAttribute("targetslot", TargetSlot),
|
||||
new XAttribute("allowvariants", AllowVariants));
|
||||
@@ -249,6 +265,7 @@ namespace Barotrauma
|
||||
RelatedItem ri = new RelatedItem(identifiers, excludedIdentifiers)
|
||||
{
|
||||
ExcludeBroken = element.GetAttributeBool("excludebroken", true),
|
||||
RequireEmpty = element.GetAttributeBool("requireempty", false),
|
||||
ExcludeFullCondition = element.GetAttributeBool("excludefullcondition", false),
|
||||
AllowVariants = element.GetAttributeBool("allowvariants", true)
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user