(3dc4135ce) v0.9.5.1

This commit is contained in:
Regalis
2019-11-21 18:22:25 +01:00
parent b39922a074
commit 5c95c53118
287 changed files with 12655 additions and 5048 deletions
@@ -20,9 +20,9 @@ namespace Barotrauma.Items.Components
private bool isOpen;
private float openState;
private Sprite doorSprite, weldedSprite, brokenSprite;
private bool scaleBrokenSprite, fadeBrokenSprite;
private bool autoOrientGap;
private readonly Sprite doorSprite, weldedSprite, brokenSprite;
private readonly bool scaleBrokenSprite, fadeBrokenSprite;
private readonly bool autoOrientGap;
private bool isStuck;
public bool IsStuck => isStuck;
@@ -221,8 +221,8 @@ namespace Barotrauma.Items.Components
#endif
}
private string accessDeniedTxt = TextManager.Get("AccessDenied");
private string cannotOpenText = TextManager.Get("DoorMsgCannotOpen");
private readonly string accessDeniedTxt = TextManager.Get("AccessDenied");
private readonly string cannotOpenText = TextManager.Get("DoorMsgCannotOpen");
private bool hasValidIdCard;
public override bool HasRequiredItems(Character character, bool addMessage, string msg = null)
{
@@ -272,14 +272,15 @@ namespace Barotrauma.Items.Components
ToggleState(ActionType.OnUse);
PickingTime = originalPickingTime;
}
else if (hasRequiredItems)
{
#if CLIENT
else if (hasRequiredItems && character != null && character == Character.Controlled)
{
GUI.AddMessage(accessDeniedTxt, Color.Red);
#endif
}
#endif
}
return item.Condition <= RepairThreshold;
return false;
}
public override void Update(float deltaTime, Camera cam)
@@ -340,6 +341,13 @@ namespace Barotrauma.Items.Components
if (!Impassable)
{
Body.FarseerBody.IsSensor = false;
var ce = Body.FarseerBody.ContactList;
while (ce != null && ce.Contact != null)
{
ce.Contact.Enabled = false;
ce = ce.Next;
}
PushCharactersAway();
}
#if CLIENT
UpdateConvexHulls();
@@ -354,6 +362,12 @@ namespace Barotrauma.Items.Components
if (!Impassable)
{
Body.FarseerBody.IsSensor = true;
var ce = Body.FarseerBody.ContactList;
while (ce != null && ce.Contact != null)
{
ce.Contact.Enabled = false;
ce = ce.Next;
}
}
linkedGap.Open = 1.0f;
IsOpen = false;
@@ -413,15 +427,14 @@ namespace Barotrauma.Items.Components
//otherwise the gap will be removed twice and cause console warnings
if (!Submarine.Unloading)
{
if (linkedGap != null) linkedGap.Remove();
linkedGap?.Remove();
}
doorSprite.Remove();
if (weldedSprite != null) weldedSprite.Remove();
doorSprite?.Remove();
weldedSprite?.Remove();
#if CLIENT
if (convexHull != null) convexHull.Remove();
if (convexHull2 != null) convexHull2.Remove();
convexHull?.Remove();
convexHull2?.Remove();
#endif
}
@@ -474,7 +487,6 @@ namespace Barotrauma.Items.Components
private bool PushBodyOutOfDoorway(Character c, PhysicsBody body, int dir, Vector2 doorRectSimPos, Vector2 doorRectSimSize)
{
float diff = 0.0f;
if (!MathUtils.IsValid(body.SimPosition))
{
DebugConsole.ThrowError("Failed to push a limb out of a doorway - position of the body (character \"" + c.Name + "\") is not valid (" + body.SimPosition + ")");
@@ -484,7 +496,8 @@ namespace Barotrauma.Items.Components
" Remoteplayer: " + c.IsRemotePlayer);
return false;
}
float diff;
if (IsHorizontal)
{
if (body.SimPosition.X < doorRectSimPos.X || body.SimPosition.X > doorRectSimPos.X + doorRectSimSize.X) { return false; }
@@ -131,7 +131,7 @@ namespace Barotrauma.Items.Components
{
if (charging)
{
if (voltage > minVoltage || powerConsumption <= 0.0f)
if (Voltage > MinVoltage)
{
Discharge();
}
@@ -142,8 +142,6 @@ namespace Barotrauma.Items.Components
{
IsActive = false;
}
voltage = 0.0f;
}
public override void UpdateBroken(float deltaTime, Camera cam)
@@ -455,6 +453,7 @@ namespace Barotrauma.Items.Components
protected override void RemoveComponentSpecific()
{
base.RemoveComponentSpecific();
list.Remove(this);
}
}
@@ -242,9 +242,19 @@ namespace Barotrauma.Items.Components
User = null;
}
//ignore collision if there's a wall between the user and the weapon to prevent hitting through walls
if (Submarine.PickBody(User.AnimController.AimSourceSimPos,
item.SimPosition,
collisionCategory: Physics.CollisionWall | Physics.CollisionLevel | Physics.CollisionItemBlocking,
allowInsideFixture: true) != null)
{
return false;
}
Character targetCharacter = null;
Limb targetLimb = null;
Structure targetStructure = null;
Item targetItem = null;
attack?.SetUser(User);
@@ -292,6 +302,19 @@ namespace Barotrauma.Items.Components
}
hitTargets.Add(targetStructure);
}
else if (f2.Body.UserData is Item)
{
targetItem = (Item)f2.Body.UserData;
if (AllowHitMultiple)
{
if (hitTargets.Contains(targetItem)) { return true; }
}
else
{
if (hitTargets.Any(t => t is Item)) { return true; }
}
hitTargets.Add(targetItem);
}
else
{
return false;
@@ -313,6 +336,10 @@ namespace Barotrauma.Items.Components
{
attack.DoDamage(User, targetStructure, item.WorldPosition, 1.0f);
}
else if (targetItem != null && targetItem.Prefab.DamagedByMeleeWeapons)
{
attack.DoDamage(User, targetItem, item.WorldPosition, 1.0f);
}
else
{
return false;
@@ -127,6 +127,7 @@ namespace Barotrauma.Items.Components
if (activeTimer <= 0.0f) IsActive = false;
}
private List<Body> ignoredBodies = new List<Body>();
public override bool Use(float deltaTime, Character character = null)
{
if (character == null || character.Removed) return false;
@@ -185,7 +186,7 @@ namespace Barotrauma.Items.Components
(float)Math.Cos(angle),
(float)Math.Sin(angle)) * Range * item.body.Dir);
List<Body> ignoredBodies = new List<Body>();
ignoredBodies.Clear();
foreach (Limb limb in character.AnimController.Limbs)
{
if (Rand.Range(0.0f, 0.5f) > degreeOfSuccess) continue;
@@ -438,27 +439,48 @@ namespace Barotrauma.Items.Components
private float sinTime;
public override bool AIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective)
{
if (!(objective.OperateTarget is Gap leak)) return true;
Vector2 fromItemToLeak = leak.WorldPosition - item.WorldPosition;
float dist = fromItemToLeak.Length();
if (!(objective.OperateTarget is Gap leak)) { return true; }
if (leak.Submarine == null) { return true; }
Vector2 fromCharacterToLeak = leak.WorldPosition - character.WorldPosition;
float dist = fromCharacterToLeak.Length();
float reach = Range + ConvertUnits.ToDisplayUnits(((HumanoidAnimController)character.AnimController).ArmLength);
//too far away -> consider this done and hope the AI is smart enough to move closer
if (dist > Range * 3.0f) { return true; }
// TODO: use the collider size?
if (!character.AnimController.InWater && character.AnimController is HumanoidAnimController &&
Math.Abs(fromItemToLeak.X) < 100.0f && fromItemToLeak.Y < 0.0f && fromItemToLeak.Y > -150.0f)
{
((HumanoidAnimController)character.AnimController).Crouching = true;
}
if (dist > reach * 2) { return true; }
character.AIController.SteeringManager.Reset();
//steer closer if almost in range
if (dist > Range)
if (dist > reach)
{
Vector2 standPos = new Vector2(Math.Sign(-fromItemToLeak.X), Math.Sign(-fromItemToLeak.Y)) / 2;
if (!character.AnimController.InWater)
if (character.AnimController.InWater)
{
if (character.AIController.SteeringManager is IndoorsSteeringManager indoorSteering)
{
// Swimming inside the sub
if (indoorSteering.CurrentPath != null && !indoorSteering.IsPathDirty && indoorSteering.CurrentPath.Unreachable)
{
Vector2 dir = Vector2.Normalize(fromCharacterToLeak);
character.AIController.SteeringManager.SteeringManual(deltaTime, dir);
}
else
{
character.AIController.SteeringManager.SteeringSeek(character.GetRelativeSimPosition(leak));
}
}
else
{
// Swimming outside the sub
character.AIController.SteeringManager.SteeringSeek(character.GetRelativeSimPosition(leak));
}
}
else
{
// TODO: use the collider size?
if (!character.AnimController.InWater && character.AnimController is HumanoidAnimController &&
Math.Abs(fromCharacterToLeak.X) < 100.0f && fromCharacterToLeak.Y < 0.0f && fromCharacterToLeak.Y > -150.0f)
{
((HumanoidAnimController)character.AnimController).Crouching = true;
}
Vector2 standPos = new Vector2(Math.Sign(-fromCharacterToLeak.X), Math.Sign(-fromCharacterToLeak.Y)) / 2;
if (leak.IsHorizontal)
{
standPos.X *= 2;
@@ -468,43 +490,40 @@ namespace Barotrauma.Items.Components
{
standPos.X = 0;
}
}
if (character.AIController.SteeringManager is IndoorsSteeringManager indoorSteering)
{
if (indoorSteering.CurrentPath != null && !indoorSteering.IsPathDirty && indoorSteering.CurrentPath.Unreachable)
{
Vector2 dir = Vector2.Normalize(standPos - character.WorldPosition);
character.AIController.SteeringManager.SteeringManual(deltaTime, dir / 2);
}
else
{
character.AIController.SteeringManager.SteeringSeek(standPos);
}
}
else
{
character.AIController.SteeringManager.SteeringSeek(standPos);
}
}
else
{
if (dist < Range / 2)
if (dist < reach / 2)
{
// Too close -> steer away
character.AIController.SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(character.SimPosition - leak.SimPosition) / 2);
character.AIController.SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(character.SimPosition - leak.SimPosition));
}
else if (dist <= Range)
else if (dist <= reach)
{
// In range
character.AIController.SteeringManager.Reset();
}
else
{
return false;
character.CursorPosition = leak.Position;
character.CursorPosition += VectorExtensions.Forward(Item.body.TransformedRotation + (float)Math.Sin(sinTime) / 2, dist / 2);
if (character.AnimController.InWater)
{
var torso = character.AnimController.GetLimb(LimbType.Torso);
// Turn facing the target when not moving (handled in the animcontroller if not moving)
Vector2 mousePos = ConvertUnits.ToSimUnits(character.CursorPosition);
Vector2 diff = (mousePos - torso.SimPosition) * character.AnimController.Dir;
float newRotation = MathUtils.VectorToAngle(diff);
character.AnimController.Collider.SmoothRotate(newRotation, 5.0f);
if (VectorExtensions.Angle(VectorExtensions.Forward(torso.body.TransformedRotation), fromCharacterToLeak) < MathHelper.PiOver4)
{
// Swim past
Vector2 moveDir = leak.IsHorizontal ? Vector2.UnitY : Vector2.UnitX;
moveDir *= character.AnimController.Dir;
character.AIController.SteeringManager.SteeringManual(deltaTime, moveDir);
}
}
}
}
sinTime += deltaTime;
character.CursorPosition = leak.Position + VectorExtensions.Forward(Item.body.TransformedRotation + (float)Math.Sin(sinTime), dist);
if (item.RequireAimToUse)
{
bool isOperatingButtons = false;
@@ -520,13 +539,33 @@ namespace Barotrauma.Items.Components
{
character.SetInput(InputType.Aim, false, true);
}
bool isAiming = false;
var holdable = item.GetComponent<Holdable>();
if (holdable != null)
{
isAiming = holdable.ControlPose;
}
sinTime = isAiming ? sinTime + deltaTime * 5 : 0;
}
// Press the trigger only when the tool is approximately facing the target.
Vector2 fromItemToLeak = leak.WorldPosition - item.WorldPosition;
var angle = VectorExtensions.Angle(VectorExtensions.Forward(item.body.TransformedRotation), fromItemToLeak);
if (angle < MathHelper.PiOver4)
{
character.SetInput(InputType.Shoot, false, true);
Use(deltaTime, character);
// Check that we don't hit any friendlies
if (Submarine.PickBodies(item.SimPosition, leak.SimPosition, collisionCategory: Physics.CollisionCharacter).None(hit =>
{
if (hit.UserData is Character c)
{
if (c == character) { return false; }
return HumanAIController.IsFriendly(character, c);
}
return false;
}))
{
character.SetInput(InputType.Shoot, false, true);
Use(deltaTime, character);
}
}
bool leakFixed = (leak.Open <= 0.0f || leak.Removed) &&
@@ -534,7 +573,6 @@ namespace Barotrauma.Items.Components
if (leakFixed && leak.FlowTargetHull != null)
{
sinTime = 0;
if (!leak.FlowTargetHull.ConnectedGaps.Any(g => !g.IsRoomToRoom && g.Open > 0.0f))
{
@@ -44,8 +44,9 @@ namespace Barotrauma.Items.Components
public bool WasUsed;
public readonly Dictionary<ActionType, List<StatusEffect>> statusEffectLists;
public Dictionary<RelatedItem.RelationType, List<RelatedItem>> requiredItems;
public readonly List<RelatedItem> DisabledRequiredItems = new List<RelatedItem>();
public List<Skill> requiredSkills;
@@ -271,19 +272,7 @@ namespace Barotrauma.Items.Components
break;
case "requireditem":
case "requireditems":
RelatedItem ri = RelatedItem.Load(subElement, item.Name);
if (ri != null)
{
if (!requiredItems.ContainsKey(ri.Type))
{
requiredItems.Add(ri.Type, new List<RelatedItem>());
}
requiredItems[ri.Type].Add(ri);
}
else
{
DebugConsole.ThrowError("Error in item config \"" + item.ConfigFile + "\" - component " + GetType().ToString() + " requires an item with no identifiers.");
}
SetRequiredItems(subElement);
break;
case "requiredskill":
case "requiredskills":
@@ -323,6 +312,34 @@ namespace Barotrauma.Items.Components
}
}
public void SetRequiredItems(XElement element)
{
bool returnEmpty = false;
#if CLIENT
returnEmpty = Screen.Selected == GameMain.SubEditorScreen;
#endif
RelatedItem ri = RelatedItem.Load(element, returnEmpty, item.Name);
if (ri != null)
{
if (ri.Identifiers.Length == 0)
{
DisabledRequiredItems.Add(ri);
}
else
{
if (!requiredItems.ContainsKey(ri.Type))
{
requiredItems.Add(ri.Type, new List<RelatedItem>());
}
requiredItems[ri.Type].Add(ri);
}
}
else
{
DebugConsole.ThrowError("Error in item config \"" + item.ConfigFile + "\" - component " + GetType().ToString() + " requires an item with no identifiers.");
}
}
public virtual void Move(Vector2 amount) { }
/// <summary>a Character has picked the item</summary>
@@ -762,6 +779,12 @@ namespace Barotrauma.Items.Components
componentElement.Add(newElement);
}
}
foreach (RelatedItem ri in DisabledRequiredItems)
{
XElement newElement = new XElement("requireditem");
ri.Save(newElement);
componentElement.Add(newElement);
}
SerializableProperty.SerializeProperties(this, componentElement);
@@ -783,12 +806,16 @@ namespace Barotrauma.Items.Components
var prevRequiredItems = new Dictionary<RelatedItem.RelationType, List<RelatedItem>>(requiredItems);
requiredItems.Clear();
bool returnEmptyRequirements = false;
#if CLIENT
returnEmptyRequirements = Screen.Selected == GameMain.SubEditorScreen;
#endif
foreach (XElement subElement in element.Elements())
{
switch (subElement.Name.ToString().ToLowerInvariant())
{
case "requireditem":
RelatedItem newRequiredItem = RelatedItem.Load(subElement, item.Name);
RelatedItem newRequiredItem = RelatedItem.Load(subElement, returnEmptyRequirements, item.Name);
if (newRequiredItem == null) continue;
var prevRequiredItem = prevRequiredItems.ContainsKey(newRequiredItem.Type) ?
@@ -3,6 +3,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using Barotrauma.Extensions;
namespace Barotrauma.Items.Components
{
@@ -54,20 +55,44 @@ namespace Barotrauma.Items.Components
[Serialize(5, false, description: "How many inventory slots the inventory has per row.")]
public int SlotsPerRow { get; set; }
public List<RelatedItem> ContainableItems { get; private set; }
private HashSet<string> containableRestrictions = new HashSet<string>();
[Editable, Serialize("", true, description: "Define items (by identifiers or tags) that bots should place inside this container. If empty, no restrictions are applied.")]
public string ContainableRestrictions
{
get { return string.Join(",", containableRestrictions); }
set
{
StringFormatter.ParseCommaSeparatedStringToCollection(value, containableRestrictions);
}
}
public bool ShouldBeContained(string[] identifiersOrTags, out bool isRestrictionsDefined)
{
isRestrictionsDefined = containableRestrictions.Any();
if (!isRestrictionsDefined) { return true; }
return identifiersOrTags.Any(id => containableRestrictions.Any(r => r == id));
}
public bool ShouldBeContained(Item item, out bool isRestrictionsDefined)
{
isRestrictionsDefined = containableRestrictions.Any();
if (!isRestrictionsDefined) { return true; }
return containableRestrictions.Any(id => item.Prefab.Identifier == id || item.HasTag(id));
}
public List<RelatedItem> ContainableItems { get; private set; } = new List<RelatedItem>();
public ItemContainer(Item item, XElement element)
: base (item, element)
{
Inventory = new ItemInventory(item, this, capacity, SlotsPerRow);
ContainableItems = new List<RelatedItem>();
Inventory = new ItemInventory(item, this, capacity, SlotsPerRow);
foreach (XElement subElement in element.Elements())
{
switch (subElement.Name.ToString().ToLowerInvariant())
{
case "containable":
RelatedItem containable = RelatedItem.Load(subElement, item.Name);
RelatedItem containable = RelatedItem.Load(subElement, returnEmpty: false, parentDebugName: item.Name);
if (containable == null)
{
DebugConsole.ThrowError("Error in item config \"" + item.ConfigFile + "\" - containable with no identifiers.");
@@ -34,6 +34,7 @@ namespace Barotrauma.Items.Components
public override void OnItemLoaded()
{
base.OnItemLoaded();
var containers = item.GetComponents<ItemContainer>().ToList();
if (containers.Count < 2)
{
@@ -59,7 +60,7 @@ namespace Barotrauma.Items.Components
return;
}
hasPower = voltage >= minVoltage;
hasPower = Voltage >= MinVoltage;
if (!hasPower) { return; }
var repairable = item.GetComponent<Repairable>();
@@ -70,10 +71,8 @@ namespace Barotrauma.Items.Components
ApplyStatusEffects(ActionType.OnActive, deltaTime, null);
if (powerConsumption == 0.0f) { voltage = 1.0f; }
progressTimer += deltaTime * voltage;
Voltage -= deltaTime * 10.0f;
if (powerConsumption <= 0.0f) { Voltage = 1.0f; }
progressTimer += deltaTime * Voltage;
var targetItem = inputContainer.Inventory.Items.LastOrDefault(i => i != null);
if (targetItem == null) { return; }
@@ -99,7 +98,7 @@ namespace Barotrauma.Items.Components
float condition = deconstructProduct.CopyCondition ?
percentageHealth * itemPrefab.Health :
itemPrefab.Health * deconstructProduct.OutCondition;
//container full, drop the items outside the deconstructor
if (emptySlots <= 0)
{
@@ -111,7 +110,7 @@ namespace Barotrauma.Items.Components
emptySlots--;
}
}
if (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer)
{
if (targetItem.Prefab.DeconstructItems.Any())
@@ -149,8 +148,6 @@ namespace Barotrauma.Items.Components
progressState = 0.0f;
}
}
voltage -= deltaTime * 10.0f;
}
private void PutItemsToLinkedContainer()
@@ -50,7 +50,7 @@ namespace Barotrauma.Items.Components
public float CurrentVolume
{
get { return Math.Abs((force / 100.0f) * (minVoltage <= 0.0f ? 1.0f : Math.Min(prevVoltage / minVoltage, 1.0f))); }
get { return Math.Abs((force / 100.0f) * (MinVoltage <= 0.0f ? 1.0f : Math.Min(prevVoltage / MinVoltage, 1.0f))); }
}
public Engine(Item item, XElement element)
@@ -83,15 +83,15 @@ namespace Barotrauma.Items.Components
//pumps consume more power when in a bad condition
currPowerConsumption *= MathHelper.Lerp(2.0f, 1.0f, item.Condition / item.MaxCondition);
if (powerConsumption == 0.0f) voltage = 1.0f;
if (powerConsumption == 0.0f) { Voltage = 1.0f; }
prevVoltage = voltage;
hasPower = voltage > minVoltage;
prevVoltage = Voltage;
hasPower = Voltage > MinVoltage;
Force = MathHelper.Lerp(force, (voltage < minVoltage) ? 0.0f : targetForce, 0.1f);
Force = MathHelper.Lerp(force, (Voltage < MinVoltage) ? 0.0f : targetForce, 0.1f);
if (Math.Abs(Force) > 1.0f)
{
Vector2 currForce = new Vector2((force / 10.0f) * maxForce * Math.Min(voltage / minVoltage, 1.0f), 0.0f);
Vector2 currForce = new Vector2((force / 10.0f) * maxForce * Math.Min(Voltage / MinVoltage, 1.0f), 0.0f);
//less effective when in a bad condition
currForce *= MathHelper.Lerp(0.5f, 2.0f, item.Condition / item.MaxCondition);
@@ -119,8 +119,6 @@ namespace Barotrauma.Items.Components
}
#endif
}
voltage -= deltaTime;
}
private void UpdatePropellerDamage(float deltaTime)
@@ -172,5 +170,16 @@ namespace Barotrauma.Items.Components
}
}
}
public override XElement Save(XElement parentElement)
{
Vector2 prevPropellerPos = PropellerPos;
//undo flipping before saving
if (item.FlippedX) { PropellerPos = new Vector2(-PropellerPos.X, PropellerPos.Y); }
if (item.FlippedY) { PropellerPos = new Vector2(PropellerPos.X, -PropellerPos.Y); }
XElement element = base.Save(parentElement);
PropellerPos = prevPropellerPos;
return element;
}
}
}
@@ -70,6 +70,7 @@ namespace Barotrauma.Items.Components
public override void OnItemLoaded()
{
base.OnItemLoaded();
var containers = item.GetComponents<ItemContainer>().ToList();
if (containers.Count < 2)
{
@@ -199,7 +200,7 @@ namespace Barotrauma.Items.Components
progressState = fabricatedItem == null ? 0.0f : (requiredTime - timeUntilReady) / requiredTime;
hasPower = voltage >= minVoltage;
hasPower = Voltage >= MinVoltage;
if (!hasPower) { return; }
var repairable = item.GetComponent<Repairable>();
@@ -210,10 +211,9 @@ namespace Barotrauma.Items.Components
ApplyStatusEffects(ActionType.OnActive, deltaTime, null);
if (powerConsumption <= 0) { voltage = 1.0f; }
if (powerConsumption <= 0) { Voltage = 1.0f; }
timeUntilReady -= deltaTime * voltage;
voltage -= deltaTime * 10.0f;
timeUntilReady -= deltaTime * Voltage;
if (timeUntilReady > 0.0f) { return; }
@@ -75,13 +75,11 @@ namespace Barotrauma.Items.Components
currPowerConsumption = powerConsumption;
currPowerConsumption *= MathHelper.Lerp(2.0f, 1.0f, item.Condition / item.MaxCondition);
hasPower = voltage > minVoltage;
hasPower = Voltage > MinVoltage;
if (hasPower)
{
ApplyStatusEffects(ActionType.OnActive, deltaTime, null);
}
voltage -= deltaTime;
}
public override bool Pick(Character picker)
@@ -46,12 +46,12 @@ namespace Barotrauma.Items.Components
if (powerConsumption <= 0.0f)
{
voltage = 1.0f;
Voltage = 1.0f;
}
if (item.CurrentHull == null) return;
if (voltage < minVoltage)
if (Voltage < MinVoltage)
{
powerDownTimer += deltaTime;
return;
@@ -61,7 +61,7 @@ namespace Barotrauma.Items.Components
powerDownTimer = 0.0f;
}
CurrFlow = Math.Min(voltage, 1.0f) * generatedAmount * 100.0f;
CurrFlow = Math.Min(Voltage, 1.0f) * generatedAmount * 100.0f;
//less effective when in bad condition
float conditionMult = item.Condition / item.MaxCondition;
@@ -71,8 +71,6 @@ namespace Barotrauma.Items.Components
CurrFlow *= conditionMult * conditionMult;
UpdateVents(CurrFlow);
voltage -= deltaTime;
}
public override void UpdateBroken(float deltaTime, Camera cam)
@@ -76,7 +76,7 @@ namespace Barotrauma.Items.Components
//pumps consume more power when in a bad condition
currPowerConsumption *= MathHelper.Lerp(2.0f, 1.0f, item.Condition / item.MaxCondition);
if (voltage < minVoltage) { return; }
if (Voltage < MinVoltage) { return; }
UpdateProjSpecific(deltaTime);
@@ -86,7 +86,7 @@ namespace Barotrauma.Items.Components
if (item.CurrentHull == null) { return; }
float powerFactor = currPowerConsumption <= 0.0f ? 1.0f : voltage;
float powerFactor = currPowerConsumption <= 0.0f ? 1.0f : Voltage;
currFlow = flowPercentage / 100.0f * maxFlow * powerFactor;
//less effective when in a bad condition
@@ -94,8 +94,6 @@ namespace Barotrauma.Items.Components
item.CurrentHull.WaterVolume += currFlow;
if (item.CurrentHull.WaterVolume > item.CurrentHull.Volume) { item.CurrentHull.Pressure += 0.5f; }
voltage -= deltaTime;
}
partial void UpdateProjSpecific(float deltaTime);
@@ -124,7 +122,7 @@ namespace Barotrauma.Items.Components
{
if (float.TryParse(signal, NumberStyles.Any, CultureInfo.InvariantCulture, out float tempTarget))
{
targetLevel = MathHelper.Clamp((tempTarget + 100.0f) / 2.0f, 0.0f, 100.0f);
targetLevel = MathHelper.Clamp(tempTarget + 50.0f, 0.0f, 100.0f);
controlLockTimer = 0.1f;
}
}
@@ -4,6 +4,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using Barotrauma.Extensions;
namespace Barotrauma.Items.Components
{
@@ -35,6 +36,7 @@ namespace Barotrauma.Items.Components
private float maxPowerOutput;
private Queue<float> loadQueue = new Queue<float>();
private float load;
private bool unsentChanges;
@@ -165,7 +167,10 @@ namespace Barotrauma.Items.Components
private float prevAvailableFuel;
public float AvailableFuel { get; set; }
private readonly string[] fuelTags = new string[1] { "reactorfuel" };
public Reactor(Item item, XElement element)
: base(item, element)
{
@@ -267,8 +272,7 @@ namespace Barotrauma.Items.Components
{
UpdateAutoTemp(2.0f, deltaTime);
}
load = 0.0f;
float currentLoad = 0.0f;
List<Connection> connections = item.Connections;
if (connections != null && connections.Count > 0)
{
@@ -284,13 +288,20 @@ namespace Barotrauma.Items.Components
//calculate how much external power there is in the grid
//(power coming from somewhere else than this reactor, e.g. batteries)
float externalPower = Math.Max(CurrPowerConsumption - pt.CurrPowerConsumption, 0);
float externalPower = Math.Max(CurrPowerConsumption - pt.CurrPowerConsumption, 0) * 0.95f;
//reduce the external power from the load to prevent overloading the grid
load = Math.Max(load, pt.PowerLoad - externalPower);
currentLoad = Math.Max(currentLoad, pt.PowerLoad - externalPower);
}
}
}
loadQueue.Enqueue(currentLoad);
while (loadQueue.Count() > 60.0f)
{
load = loadQueue.Average();
loadQueue.Dequeue();
}
if (fissionRate > 0.0f)
{
foreach (Item item in item.ContainedItems)
@@ -505,6 +516,19 @@ namespace Barotrauma.Items.Components
return picker != null;
}
private int itemIndex;
private List<Item> ignoredContainers = new List<Item>();
private bool FindSuitableContainer(Character character, Func<Item, float> priority, out Item suitableContainer)
{
suitableContainer = null;
if (character.FindItem(ref itemIndex, out Item targetContainer, ignoredItems: ignoredContainers, customPriorityFunction: priority))
{
suitableContainer = targetContainer;
return true;
}
return false;
}
public override bool AIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective)
{
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return false; }
@@ -516,13 +540,56 @@ namespace Barotrauma.Items.Components
//characters with insufficient skill levels don't refuel the reactor
if (degreeOfSuccess > 0.2f)
{
//remove used-up fuel from the reactor
var containedItems = item.ContainedItems;
foreach (Item item in containedItems)
if (objective.SubObjectives.None())
{
if (item != null && item.Condition <= 0.0f)
var containedItems = item.ContainedItems;
foreach (Item fuelRod in containedItems)
{
item.Drop(character);
if (fuelRod != null && fuelRod.Condition <= 0.0f)
{
if (!FindSuitableContainer(character,
i =>
{
var container = i.GetComponent<ItemContainer>();
if (container == null) { return 0; }
if (container.Inventory.IsFull()) { return 0; }
if (container.ShouldBeContained(fuelRod, out bool isRestrictionsDefined))
{
if (isRestrictionsDefined)
{
return 3;
}
else
{
if (fuelRod.Prefab.IsContainerPreferred(container, out bool isPreferencesDefined))
{
return isPreferencesDefined ? 2 : 1;
}
else
{
return isPreferencesDefined ? 0 : 1;
}
}
}
else
{
return 0;
}
}, out Item targetContainer))
{
return false;
}
var decontainObjective = new AIObjectiveDecontainItem(character, fuelRod, item.GetComponent<ItemContainer>(), objective.objectiveManager, targetContainer?.GetComponent<ItemContainer>());
decontainObjective.Abandoned += () =>
{
itemIndex = 0;
if (targetContainer != null)
{
ignoredContainers.Add(targetContainer);
}
};
objective.AddSubObjectiveInQueue(decontainObjective);
}
}
}
@@ -535,31 +602,33 @@ namespace Barotrauma.Items.Components
//load more fuel if the current maximum output is only 50% of the current load
if (NeedMoreFuel(minimumOutputRatio: 0.5f))
{
var containFuelObjective = new AIObjectiveContainItem(character, new string[] { "fuelrod", "reactorfuel" }, item.GetComponent<ItemContainer>(), objective.objectiveManager)
{
targetItemCount = item.ContainedItems.Count(i => i != null && i.Prefab.Identifier == "fuelrod" || i.HasTag("reactorfuel")) + 1,
GetItemPriority = (Item fuelItem) =>
{
if (fuelItem.ParentInventory?.Owner is Item)
{
//don't take fuel from other reactors
if (((Item)fuelItem.ParentInventory.Owner).GetComponent<Reactor>() != null) return 0.0f;
}
return 1.0f;
}
};
objective.AddSubObjective(containFuelObjective);
character?.Speak(TextManager.Get("DialogReactorFuel"), null, 0.0f, "reactorfuel", 30.0f);
aiUpdateTimer = AIUpdateInterval;
if (objective.SubObjectives.None())
{
var containFuelObjective = new AIObjectiveContainItem(character, fuelTags, item.GetComponent<ItemContainer>(), objective.objectiveManager)
{
targetItemCount = item.ContainedItems.Count(i => i != null && fuelTags.Any(t => i.Prefab.Identifier == t || i.HasTag(t))) + 1,
GetItemPriority = (Item fuelItem) =>
{
if (fuelItem.ParentInventory?.Owner is Item)
{
//don't take fuel from other reactors
if (((Item)fuelItem.ParentInventory.Owner).GetComponent<Reactor>() != null) return 0.0f;
}
return 1.0f;
}
};
containFuelObjective.Abandoned += () => objective.Abandon = true;
objective.AddSubObjective(containFuelObjective);
character?.Speak(TextManager.Get("DialogReactorFuel"), null, 0.0f, "reactorfuel", 30.0f);
}
return false;
}
else if (TooMuchFuel())
{
foreach (Item item in item.ContainedItems)
{
if (item != null && item.HasTag("reactorfuel"))
if (item != null && fuelTags.Any(t => item.Prefab.Identifier == t || item.HasTag(t)))
{
if (!character.Inventory.TryPutItem(item, character, allowedSlots: item.AllowedSlots))
{
@@ -577,22 +646,28 @@ namespace Barotrauma.Items.Components
}
LastUser = lastAIUser = character;
bool prevAutoTemp = autoTemp;
bool prevShutDown = shutDown;
float prevFissionRate = targetFissionRate;
float prevTurbineOutput = targetTurbineOutput;
switch (objective.Option.ToLowerInvariant())
{
case "powerup":
shutDown = false;
//characters with insufficient skill levels simply set the autotemp on instead of trying to adjust the temperature manually
if (degreeOfSuccess < 0.5f)
if (objective.Override || !autoTemp)
{
if (!autoTemp) unsentChanges = true;
AutoTemp = true;
}
else
{
AutoTemp = false;
unsentChanges = true;
UpdateAutoTemp(MathHelper.Lerp(0.5f, 2.0f, degreeOfSuccess), 1.0f);
//characters with insufficient skill levels simply set the autotemp on instead of trying to adjust the temperature manually
if (degreeOfSuccess < 0.5f)
{
AutoTemp = true;
}
else
{
AutoTemp = false;
UpdateAutoTemp(MathHelper.Lerp(0.5f, 2.0f, degreeOfSuccess), 1.0f);
}
}
#if CLIENT
onOffSwitch.BarScroll = 0.0f;
@@ -604,11 +679,6 @@ namespace Barotrauma.Items.Components
#if CLIENT
onOffSwitch.BarScroll = 1.0f;
#endif
if (AutoTemp || !shutDown || targetFissionRate > 0.0f || targetTurbineOutput > 0.0f)
{
unsentChanges = true;
}
AutoTemp = false;
shutDown = true;
targetFissionRate = 0.0f;
@@ -616,6 +686,14 @@ namespace Barotrauma.Items.Components
break;
}
if (autoTemp != prevAutoTemp ||
prevShutDown != shutDown ||
Math.Abs(prevFissionRate - targetFissionRate) > 1.0f ||
Math.Abs(prevTurbineOutput - targetTurbineOutput) > 1.0f)
{
unsentChanges = true;
}
aiUpdateTimer = AIUpdateInterval;
return false;
@@ -162,7 +162,7 @@ namespace Barotrauma.Items.Components
if (currentMode == Mode.Active)
{
if ((voltage >= minVoltage || powerConsumption <= 0.0f) &&
if ((Voltage >= MinVoltage) &&
(!UseTransducers || connectedTransducers.Count > 0))
{
if (currentPingIndex != -1)
@@ -201,7 +201,6 @@ namespace Barotrauma.Items.Components
{
item.AiTarget.SectorDegrees = 360.0f;
}
currentPingIndex = -1;
aiPingCheckPending = false;
}
}
@@ -235,6 +234,7 @@ namespace Barotrauma.Items.Components
protected override void RemoveComponentSpecific()
{
base.RemoveComponentSpecific();
sonarBlip?.Remove();
pingCircle?.Remove();
directionalPingCircle?.Remove();
@@ -247,6 +247,7 @@ namespace Barotrauma.Items.Components
{
if (currentMode == Mode.Passive || !aiPingCheckPending) return false;
// TODO: Don't create new collections here
Dictionary<string, List<Character>> targetGroups = new Dictionary<string, List<Character>>();
foreach (Character c in Character.CharacterList)
@@ -20,7 +20,7 @@ namespace Barotrauma.Items.Components
{
UpdateOnActiveEffects(deltaTime);
if (voltage >= minVoltage || PowerConsumption <= 0.0f)
if (Voltage >= MinVoltage)
{
sendSignalTimer += deltaTime;
if (sendSignalTimer > SendSignalInterval)
@@ -29,8 +29,6 @@ namespace Barotrauma.Items.Components
sendSignalTimer = SendSignalInterval;
}
}
voltage = 0.0f;
}
}
}
@@ -15,6 +15,13 @@ namespace Barotrauma.Items.Components
private const float AutopilotRayCastInterval = 0.5f;
private const float RecalculatePathInterval = 5.0f;
private const float AutopilotMinDistToPathNode = 30.0f;
private const float AutoPilotSteeringLerp = 0.1f;
private const float AutoPilotMaxSpeed = 0.5f;
private const float AIPilotMaxSpeed = 1.0f;
private Vector2 currVelocity;
private Vector2 targetVelocity;
@@ -162,6 +169,7 @@ namespace Barotrauma.Items.Components
public override void OnItemLoaded()
{
base.OnItemLoaded();
sonar = item.GetComponent<Sonar>();
}
@@ -209,7 +217,7 @@ namespace Barotrauma.Items.Components
currPowerConsumption = powerConsumption;
if (voltage < minVoltage && currPowerConsumption > 0.0f) { return; }
if (Voltage < MinVoltage) { return; }
if (user != null && user.Removed)
{
@@ -221,6 +229,12 @@ namespace Barotrauma.Items.Components
if (autoPilot)
{
UpdateAutoPilot(deltaTime);
float userSkill = 0.0f;
if (user != null && (user.SelectedConstruction == item || item.linkedTo.Contains(user.SelectedConstruction)))
{
userSkill = user.GetSkillLevel("helm") / 100.0f;
}
targetVelocity = targetVelocity.ClampLength(MathHelper.Lerp(AutoPilotMaxSpeed, AIPilotMaxSpeed, userSkill) * 100.0f);
}
else
{
@@ -253,8 +267,6 @@ namespace Barotrauma.Items.Components
targetLevel += (neutralBallastLevel - 0.5f) * 100.0f;
item.SendSignal(0, targetLevel.ToString(CultureInfo.InvariantCulture), "velocity_y_out", null);
voltage -= deltaTime;
}
private void UpdateAutoPilot(float deltaTime)
@@ -262,7 +274,8 @@ namespace Barotrauma.Items.Components
if (controlledSub == null) return;
if (posToMaintain != null)
{
SteerTowardsPosition((Vector2)posToMaintain);
Vector2 steeringVel = GetSteeringVelocity((Vector2)posToMaintain);
TargetVelocity = Vector2.Lerp(TargetVelocity, steeringVel, AutoPilotSteeringLerp);
return;
}
@@ -284,7 +297,7 @@ namespace Barotrauma.Items.Components
//if the node is close enough, check if it's visible
float lengthSqr = diff.LengthSquared();
if (lengthSqr > 0.001f && lengthSqr < 500.0f)
if (lengthSqr > 0.001f && lengthSqr < AutopilotMinDistToPathNode * AutopilotMinDistToPathNode)
{
diff = Vector2.Normalize(diff);
@@ -298,11 +311,11 @@ namespace Barotrauma.Items.Components
Vector2 cornerPos =
new Vector2(controlledSub.Borders.Width * x, controlledSub.Borders.Height * y) / 2.0f;
cornerPos = ConvertUnits.ToSimUnits(cornerPos * 1.2f + controlledSub.WorldPosition);
cornerPos = ConvertUnits.ToSimUnits(cornerPos * 1.1f + controlledSub.WorldPosition);
float dist = Vector2.Distance(cornerPos, steeringPath.NextNode.SimPosition);
if (Submarine.PickBody(cornerPos, cornerPos + diff * dist, null, Physics.CollisionLevel) == null) continue;
if (Submarine.PickBody(cornerPos, cornerPos + diff * dist, null, Physics.CollisionLevel) == null) { continue; }
nextVisible = false;
x = 2;
@@ -313,19 +326,18 @@ namespace Barotrauma.Items.Components
if (nextVisible) steeringPath.SkipToNextNode();
}
autopilotRayCastTimer = AutopilotRayCastInterval;
}
Vector2 newVelocity = Vector2.Zero;
if (steeringPath.CurrentNode != null)
{
SteerTowardsPosition(steeringPath.CurrentNode.WorldPosition);
newVelocity = GetSteeringVelocity(steeringPath.CurrentNode.WorldPosition);
}
Vector2 avoidDist = new Vector2(
Math.Max(1000.0f * Math.Abs(controlledSub.Velocity.X), controlledSub.Borders.Width * 1.5f),
Math.Max(1000.0f * Math.Abs(controlledSub.Velocity.Y), controlledSub.Borders.Height * 1.5f));
Math.Max(1000.0f * Math.Abs(controlledSub.Velocity.X), controlledSub.Borders.Width * 0.75f),
Math.Max(1000.0f * Math.Abs(controlledSub.Velocity.Y), controlledSub.Borders.Height * 0.75f));
float avoidRadius = avoidDist.Length();
@@ -356,22 +368,22 @@ namespace Barotrauma.Items.Components
0.0f : Vector2.Dot(controlledSub.Velocity, -normalizedDiff);
//not heading towards the wall -> ignore
if (dot < 0.5)
if (dot < 1.0)
{
debugDrawObstacles.Add(new ObstacleDebugInfo(edge, intersection, dot, Vector2.Zero, cell.Translation));
continue;
}
Vector2 change = (normalizedDiff * Math.Max((avoidRadius - diff.Length()), 0.0f)) / avoidRadius;
newAvoidStrength += change * dot;
debugDrawObstacles.Add(new ObstacleDebugInfo(edge, intersection, dot, change * dot, cell.Translation));
Vector2 change = (normalizedDiff * Math.Max((avoidRadius - diff.Length()), 0.0f)) / avoidRadius;
if (change.LengthSquared() < 0.001f) { continue; }
newAvoidStrength += change * (dot - 1.0f);
debugDrawObstacles.Add(new ObstacleDebugInfo(edge, intersection, dot - 1.0f, change * (dot - 1.0f), cell.Translation));
}
}
}
avoidStrength = Vector2.Lerp(avoidStrength, newAvoidStrength, deltaTime * 10.0f);
targetVelocity += avoidStrength * 100.0f;
TargetVelocity = Vector2.Lerp(TargetVelocity, newVelocity + avoidStrength * 100.0f, AutoPilotSteeringLerp);
//steer away from other subs
foreach (Submarine sub in Submarine.Loaded)
@@ -447,21 +459,21 @@ namespace Barotrauma.Items.Components
UpdatePath();
}
}
private void SteerTowardsPosition(Vector2 worldPosition)
private Vector2 GetSteeringVelocity(Vector2 worldPosition)
{
float prediction = 10.0f;
float prediction = 2.0f;
Vector2 futurePosition = ConvertUnits.ToDisplayUnits(controlledSub.Velocity) * prediction;
Vector2 targetSpeed = ((worldPosition - controlledSub.WorldPosition) - futurePosition);
if (targetSpeed.Length() > 500.0f)
if (targetSpeed.LengthSquared() > 500.0f * 500.0f)
{
targetSpeed = Vector2.Normalize(targetSpeed);
TargetVelocity = targetSpeed * 100.0f;
return Vector2.Normalize(targetSpeed) * 100.0f;
}
else
{
TargetVelocity = targetSpeed / 5.0f;
return targetSpeed / 5.0f;
}
}
@@ -471,43 +483,53 @@ namespace Barotrauma.Items.Components
{
character.Speak(TextManager.Get("DialogSteeringTaken"), null, 0.0f, "steeringtaken", 10.0f);
}
user = character;
if (!AutoPilot)
{
unsentChanges = true;
AutoPilot = true;
}
switch (objective.Option.ToLowerInvariant())
{
case "maintainposition":
if (!posToMaintain.HasValue)
if (objective.Override)
{
unsentChanges = true;
posToMaintain = controlledSub != null ?
controlledSub.WorldPosition :
item.Submarine == null ? item.WorldPosition : item.Submarine.WorldPosition;
if (!MaintainPos)
{
unsentChanges = true;
MaintainPos = true;
}
if (!posToMaintain.HasValue)
{
unsentChanges = true;
posToMaintain = controlledSub != null ?
controlledSub.WorldPosition :
item.Submarine == null ? item.WorldPosition : item.Submarine.WorldPosition;
}
}
if (!AutoPilot || !MaintainPos) unsentChanges = true;
AutoPilot = true;
MaintainPos = true;
break;
case "navigateback":
if (!AutoPilot || MaintainPos || LevelEndSelected || !LevelStartSelected)
if (objective.Override)
{
unsentChanges = true;
if (MaintainPos || LevelEndSelected || !LevelStartSelected)
{
unsentChanges = true;
}
SetDestinationLevelStart();
}
SetDestinationLevelStart();
break;
case "navigatetodestination":
if (!AutoPilot || MaintainPos || !LevelEndSelected || LevelStartSelected)
if (objective.Override)
{
unsentChanges = true;
if (MaintainPos || !LevelEndSelected || LevelStartSelected)
{
unsentChanges = true;
}
SetDestinationLevelEnd();
}
SetDestinationLevelEnd();
break;
}
sonar?.AIOperate(deltaTime, character, objective);
return false;
}
@@ -14,7 +14,7 @@ namespace Barotrauma.Items.Components
private float charge;
private float rechargeVoltage;
//private float rechargeVoltage;
//how fast the battery can be recharged
private float maxRechargeSpeed;
@@ -28,10 +28,7 @@ namespace Barotrauma.Items.Components
protected Vector2 indicatorPosition, indicatorSize;
protected bool isHorizontal;
//a list of powered devices connected directly to this item
private readonly List<Pair<Powered, Connection>> directlyConnected = new List<Pair<Powered, Connection>>(10);
public float CurrPowerOutput
{
get;
@@ -107,12 +104,18 @@ namespace Barotrauma.Items.Components
if (!MathUtils.IsValid(value)) return;
rechargeSpeed = MathHelper.Clamp(value, 0.0f, maxRechargeSpeed);
rechargeSpeed = MathUtils.RoundTowardsClosest(rechargeSpeed, Math.Max(maxRechargeSpeed * 0.1f, 1.0f));
if (isRunning)
{
HasBeenTuned = true;
}
}
}
public float RechargeRatio => RechargeSpeed / MaxRechargeSpeed;
public const float aiRechargeTargetRatio = 0.5f;
private bool isRunning;
public bool HasBeenTuned { get; private set; }
public PowerContainer(Item item, XElement element)
: base(item, element)
@@ -131,14 +134,13 @@ namespace Barotrauma.Items.Components
public override void Update(float deltaTime, Camera cam)
{
isRunning = true;
float chargeRatio = charge / capacity;
float gridPower = 0.0f;
float gridLoad = 0.0f;
directlyConnected.Clear();
foreach (Connection c in item.Connections)
{
if (c.Name == "power_in") continue;
if (!c.IsPower || !c.IsOutput) { continue; }
foreach (Connection c2 in c.Recipients)
{
if (c2.Item.Condition <= 0.0f) { continue; }
@@ -149,15 +151,13 @@ namespace Barotrauma.Items.Components
foreach (Powered powered in c2.Item.GetComponents<Powered>())
{
if (!powered.IsActive) continue;
directlyConnected.Add(new Pair<Powered, Connection>(powered, c2));
gridLoad += powered.CurrPowerConsumption;
}
continue;
}
if (!pt.IsActive || !pt.CanTransfer) { continue; }
gridLoad += pt.PowerLoad;
gridPower -= pt.CurrPowerConsumption;
gridLoad += pt.PowerLoad;
}
}
@@ -168,66 +168,51 @@ namespace Barotrauma.Items.Components
if (charge >= capacity)
{
rechargeVoltage = 0.0f;
//rechargeVoltage = 0.0f;
charge = capacity;
CurrPowerConsumption = 0.0f;
}
else
{
currPowerConsumption = MathHelper.Lerp(currPowerConsumption, rechargeSpeed, 0.05f);
Charge += currPowerConsumption * rechargeVoltage / 3600.0f;
Charge += currPowerConsumption * Voltage / 3600.0f;
}
//provide power to the grid
if (gridLoad > 0.0f)
if (charge <= 0.0f)
{
if (charge <= 0.0f)
{
CurrPowerOutput = 0.0f;
charge = 0.0f;
return;
}
if (gridPower < gridLoad)
{
//output starts dropping when the charge is less than 10%
float maxOutputRatio = 1.0f;
if (chargeRatio < 0.1f)
{
maxOutputRatio = Math.Max(chargeRatio * 10.0f, 0.0f);
}
CurrPowerOutput = MathHelper.Lerp(
CurrPowerOutput,
Math.Min(MaxOutPut * maxOutputRatio, gridLoad),
deltaTime * 10.0f);
}
else
{
CurrPowerOutput = MathHelper.Lerp(CurrPowerOutput, 0.0f, deltaTime * 10.0f);
}
Charge -= CurrPowerOutput / 3600.0f;
CurrPowerOutput = 0.0f;
charge = 0.0f;
return;
}
item.SendSignal(0, ((int)Charge).ToString(), "charge", null);
item.SendSignal(0, ((int)((Charge / capacity) * 100)).ToString(), "charge_%", null);
item.SendSignal(0, ((int)((RechargeSpeed / maxRechargeSpeed) * 100)).ToString(), "charge_rate", null);
foreach (Pair<Powered, Connection> connected in directlyConnected)
//output starts dropping when the charge is less than 10%
float maxOutputRatio = 1.0f;
if (chargeRatio < 0.1f)
{
connected.First.ReceiveSignal(0, "", connected.Second, source: item, sender: null,
power: gridLoad <= 0.0f ? 1.0f : CurrPowerOutput / gridLoad);
maxOutputRatio = Math.Max(chargeRatio * 10.0f, 0.0f);
}
rechargeVoltage = 0.0f;
CurrPowerOutput += (gridLoad - gridPower) * deltaTime;
float maxOutput = Math.Min(MaxOutPut * maxOutputRatio, gridLoad);
CurrPowerOutput = MathHelper.Clamp(CurrPowerOutput, 0.0f, maxOutput);
Charge -= CurrPowerOutput / 3600.0f;
item.SendSignal(0, ((int)Math.Round(Charge)).ToString(), "charge", null);
item.SendSignal(0, ((int)Math.Round((Charge / capacity) * 100)).ToString(), "charge_%", null);
item.SendSignal(0, ((int)Math.Round((RechargeSpeed / maxRechargeSpeed) * 100)).ToString(), "charge_rate", null);
}
public override bool AIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective)
{
#if CLIENT
if (GameMain.Client != null) return false;
#endif
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return false; }
if (objective.Override)
{
HasBeenTuned = false;
}
if (HasBeenTuned) { return true; }
if (string.IsNullOrEmpty(objective.Option) || objective.Option.ToLowerInvariant() == "charge")
{
@@ -274,6 +259,8 @@ namespace Barotrauma.Items.Components
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power, float signalStrength = 1.0f)
{
if (connection.IsPower) { return; }
if (connection.Name == "set_rate")
{
if (float.TryParse(signal, NumberStyles.Any, CultureInfo.InvariantCulture, out float tempSpeed))
@@ -290,12 +277,6 @@ namespace Barotrauma.Items.Components
#endif
}
}
if (!connection.IsPower) { return; }
if (connection.Name == "power_in")
{
rechargeVoltage = Math.Min(power, 1.0f);
}
}
}
}
@@ -8,37 +8,21 @@ namespace Barotrauma.Items.Components
{
partial class PowerTransfer : Powered
{
private static float fullPower;
private static float fullLoad;
public List<Connection> PowerConnections { get; private set; }
private int updateCount;
//affects how fast changes in power/load are carried over the grid
static float inertia = 5.0f;
private static HashSet<Powered> connectedList = new HashSet<Powered>();
private List<Connection> powerConnections;
public List<Connection> PowerConnections
{
get
{
return powerConnections;
}
}
private Dictionary<Connection, bool> connectionDirty = new Dictionary<Connection, bool>();
private readonly Dictionary<Connection, bool> connectionDirty = new Dictionary<Connection, bool>();
//a list of connections a given connection is connected to, either directly or via other power transfer components
private Dictionary<Connection, HashSet<Connection>> connectedRecipients = new Dictionary<Connection, HashSet<Connection>>();
private readonly Dictionary<Connection, HashSet<Connection>> connectedRecipients = new Dictionary<Connection, HashSet<Connection>>();
private float powerLoad;
protected float powerLoad;
private bool isBroken;
protected bool isBroken;
public float PowerLoad
{
get { return powerLoad; }
set { powerLoad = value; }
}
[Editable, Serialize(true, true, description: "Can the item be damaged if too much power is supplied to the power grid.")]
@@ -145,97 +129,43 @@ namespace Barotrauma.Items.Components
SetAllConnectionsDirty();
isBroken = false;
}
if (updateCount > 0)
ApplyStatusEffects(ActionType.OnActive, deltaTime, null);
//if the item can't be fixed, don't allow it to break
if (!item.Repairables.Any() || !CanBeOverloaded) { return; }
float maxOverVoltage = Math.Max(OverloadVoltage, 1.0f);
Overload = -currPowerConsumption > Math.Max(powerLoad, 200.0f) * maxOverVoltage;
if (Overload && (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer))
{
//this junction box has already been updated this frame
updateCount--;
return;
}
//damage the item if voltage is too high (except if running as a client)
float prevCondition = item.Condition;
item.Condition -= deltaTime * 10.0f;
Overload = false;
//reset and recalculate the power generated/consumed
//by the constructions connected to the grid
fullPower = 0.0f;
fullLoad = 0.0f;
connectedList.Clear();
updateCount = 0;
CheckJunctions(deltaTime);
foreach (Powered p in connectedList)
{
PowerTransfer pt = p as PowerTransfer;
if (pt == null || pt.updateCount == 0) { continue; }
if (pt is RelayComponent != this is RelayComponent) { continue; }
pt.Overload = false;
pt.powerLoad += (fullLoad - pt.powerLoad) / inertia;
pt.currPowerConsumption += (-fullPower - pt.currPowerConsumption) / inertia;
float voltage = fullPower / Math.Max(fullLoad, 1.0f);
if (this is RelayComponent)
{
pt.currPowerConsumption = Math.Max(-fullLoad, pt.currPowerConsumption);
voltage = Math.Min(voltage, 1.0f);
}
pt.Item.SendSignal(0, "", "power", null, voltage);
pt.Item.SendSignal(0, "", "power_out", null, voltage);
//items in a bad condition are more sensitive to overvoltage
float maxOverVoltage = MathHelper.Lerp(OverloadVoltage * 0.75f, OverloadVoltage, pt.item.Condition / pt.item.MaxCondition);
maxOverVoltage = Math.Max(OverloadVoltage, 1.0f);
//if the item can't be fixed, don't allow it to break
if (!pt.item.Repairables.Any() || !pt.CanBeOverloaded) { continue; }
//relays don't blow up if the power is higher than load, only if the output is high enough
//(i.e. enough power passing through the relay)
if (pt is RelayComponent) { continue; }
if (-pt.currPowerConsumption < Math.Max(pt.powerLoad, 200.0f) * maxOverVoltage) { continue; }
pt.Overload = true;
#if CLIENT
//damage the item if voltage is too high
//(except if running as a client)
if (GameMain.Client != null) { continue; }
#endif
float prevCondition = pt.item.Condition;
pt.item.Condition -= deltaTime * 10.0f;
if (pt.item.Condition <= 0.0f && prevCondition > 0.0f)
if (item.Condition <= 0.0f && prevCondition > 0.0f)
{
#if CLIENT
SoundPlayer.PlaySound("zap", item.WorldPosition, hullGuess: pt.item.CurrentHull);
SoundPlayer.PlaySound("zap", item.WorldPosition, hullGuess: item.CurrentHull);
Vector2 baseVel = Rand.Vector(300.0f);
for (int i = 0; i < 10; i++)
{
var particle = GameMain.ParticleManager.CreateParticle("spark", pt.item.WorldPosition,
baseVel + Rand.Vector(100.0f), 0.0f, pt.item.CurrentHull);
var particle = GameMain.ParticleManager.CreateParticle("spark", item.WorldPosition,
baseVel + Rand.Vector(100.0f), 0.0f, item.CurrentHull);
if (particle != null) particle.Size *= Rand.Range(0.5f, 1.0f);
}
#endif
float currentIntensity = GameMain.GameSession?.EventManager != null ?
float currentIntensity = GameMain.GameSession?.EventManager != null ?
GameMain.GameSession.EventManager.CurrentIntensity : 0.5f;
//higher probability for fires if the current intensity is low
if (pt.FireProbability > 0.0f &&
Rand.Range(0.0f, 1.0f) < MathHelper.Lerp(pt.FireProbability, pt.FireProbability * 0.1f, currentIntensity))
if (FireProbability > 0.0f &&
Rand.Range(0.0f, 1.0f) < MathHelper.Lerp(FireProbability, FireProbability * 0.1f, currentIntensity))
{
new FireSource(pt.item.WorldPosition);
new FireSource(item.WorldPosition);
}
}
}
updateCount = 0;
}
public override bool Pick(Character picker)
@@ -243,7 +173,7 @@ namespace Barotrauma.Items.Components
return picker != null;
}
private void RefreshConnections()
protected void RefreshConnections()
{
var connections = item.Connections;
foreach (Connection c in connections)
@@ -317,102 +247,6 @@ namespace Barotrauma.Items.Components
}
}
//a recursive function that goes through all the junctions and adds up
//all the generated/consumed power of the constructions connected to the grid
private void CheckJunctions(float deltaTime, bool increaseUpdateCount = true, float clampPower = float.MaxValue, float clampLoad = float.MaxValue)
{
if (increaseUpdateCount)
{
updateCount = 1;
}
connectedList.Add(this);
ApplyStatusEffects(ActionType.OnActive, deltaTime, null);
//float maxPower = this is RelayComponent relayComponent ? relayComponent.MaxPower : float.PositiveInfinity;
RelayComponent thisRelayComponent = this as RelayComponent;
if (thisRelayComponent != null)
{
clampPower = Math.Min(Math.Min(clampPower, thisRelayComponent.MaxPower), powerLoad);
clampLoad = Math.Min(clampLoad, thisRelayComponent.MaxPower);
}
foreach (Connection c in PowerConnections)
{
var recipients = c.Recipients;
foreach (Connection recipient in recipients)
{
if (recipient?.Item == null || !recipient.IsPower) { continue; }
Item it = recipient.Item;
if (it.Condition <= 0.0f) { continue; }
foreach (ItemComponent ic in it.Components)
{
if (!(ic is Powered powered) || !powered.IsActive) { continue; }
if (connectedList.Contains(powered)) { continue; }
if (powered is PowerTransfer powerTransfer)
{
RelayComponent otherRelayComponent = powerTransfer as RelayComponent;
if ((thisRelayComponent == null) == (otherRelayComponent == null))
{
if (!powerTransfer.CanTransfer) { continue; }
powerTransfer.CheckJunctions(deltaTime, increaseUpdateCount, clampPower, clampLoad);
}
else
{
if (!powerTransfer.CanTransfer) continue;
float maxPowerIn = (thisRelayComponent != null && c.IsOutput) ? 0.0f : clampPower;
float maxPowerOut = (thisRelayComponent != null && !c.IsOutput) ? 0.0f : clampLoad;
if (maxPowerIn > 0.0f || maxPowerOut > 0.0f)
{
powerTransfer.CheckJunctions(deltaTime, false, maxPowerIn, maxPowerOut);
}
}
continue;
}
float addLoad = 0.0f;
float addPower = 0.0f;
if (powered is PowerContainer powerContainer)
{
if (recipient.Name == "power_in")
{
addLoad = powerContainer.CurrPowerConsumption;
}
else
{
addPower = powerContainer.CurrPowerOutput;
}
}
else
{
connectedList.Add(powered);
//positive power consumption = the construction requires power -> increase load
if (powered.CurrPowerConsumption > 0.0f)
{
addLoad = powered.CurrPowerConsumption;
}
else if (powered.CurrPowerConsumption < 0.0f)
//negative power consumption = the construction is a
//generator/battery or another junction box
{
addPower -= powered.CurrPowerConsumption;
}
}
if (addPower + fullPower > clampPower) { addPower -= (addPower + fullPower) - clampPower; };
if (addPower > 0) { fullPower += addPower; }
if (addLoad + fullLoad > clampLoad) { addLoad -= (addLoad + fullLoad) - clampLoad; };
if (addLoad > 0) { fullLoad += addLoad; }
}
}
}
}
public void SetAllConnectionsDirty()
{
if (item.Connections == null) return;
@@ -431,8 +265,9 @@ namespace Barotrauma.Items.Components
public override void OnItemLoaded()
{
base.OnItemLoaded();
var connections = Item.Connections;
powerConnections = connections == null ? new List<Connection>() : connections.FindAll(c => c.IsPower);
PowerConnections = connections == null ? new List<Connection>() : connections.FindAll(c => c.IsPower);
if (connections == null)
{
IsActive = false;
@@ -440,33 +275,45 @@ namespace Barotrauma.Items.Components
}
SetAllConnectionsDirty();
}
public override void ReceivePowerProbeSignal(Connection connection, Item source, float power)
{
//we've already received this signal
if (lastPowerProbeRecipients.Contains(this)) { return; }
lastPowerProbeRecipients.Add(this);
if (power < 0.0f)
{
powerLoad -= power;
}
else
{
currPowerConsumption -= power;
}
powerOut?.SendPowerProbeSignal(source, power);
}
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power, float signalStrength = 1.0f)
{
if (connection.IsPower) return;
base.ReceiveSignal(stepsTaken, signal, connection, source, sender, power);
if (!connectedRecipients.ContainsKey(connection)) return;
if (item.Condition <= 0.0f || connection.IsPower) { return; }
if (!connectedRecipients.ContainsKey(connection)) { return; }
if (connection.Name.Length > 5 && connection.Name.Substring(0, 6) == "signal")
{
foreach (Connection recipient in connectedRecipients[connection])
{
if (recipient.Item == item || recipient.Item == source) continue;
if (recipient.Item == item || recipient.Item == source) { continue; }
foreach (ItemComponent ic in recipient.Item.Components)
{
//powertransfer components don't need to receive the signal in the pass-through signal connections
//because we relay it straight to the connected items without going through the whole chain of junction boxes
if (ic is PowerTransfer && connection.Name.Contains("signal")) continue;
if (ic is PowerTransfer && connection.Name.Contains("signal")) { continue; }
ic.ReceiveSignal(stepsTaken, signal, recipient, source, sender, 0.0f, signalStrength);
}
bool broken = recipient.Item.Condition <= 0.0f;
foreach (StatusEffect effect in recipient.Effects)
{
if (broken && effect.type != ActionType.OnBroken) continue;
recipient.Item.ApplyStatusEffect(effect, ActionType.OnUse, 1.0f, null, null, false, false);
}
}
@@ -1,6 +1,7 @@
using System;
using System.Xml.Linq;
using Microsoft.Xna.Framework;
using System.Collections.Generic;
#if CLIENT
using Barotrauma.Sounds;
#endif
@@ -9,25 +10,47 @@ namespace Barotrauma.Items.Components
{
partial class Powered : ItemComponent
{
//the amount of power CURRENTLY consumed by the item
//negative values mean that the item is providing power to connected items
private static float updateTimer;
protected static float UpdateInterval = 0.2f;
/// <summary>
/// List of all powered ItemComponents
/// </summary>
private static readonly List<Powered> poweredList = new List<Powered>();
/// <summary>
/// Items that have already received the "probe signal" that's used to distribute power and load across the grid
/// </summary>
protected static HashSet<PowerTransfer> lastPowerProbeRecipients = new HashSet<PowerTransfer>();
/// <summary>
/// The amount of power currently consumed by the item. Negative values mean that the item is providing power to connected items
/// </summary>
protected float currPowerConsumption;
//current voltage of the item (load / power)
protected float voltage;
/// <summary>
/// Current voltage of the item (load / power)
/// </summary>
private float voltage;
//the minimum voltage required for the item to work
protected float minVoltage;
/// <summary>
/// The minimum voltage required for the item to work
/// </summary>
private float minVoltage;
//the maximum amount of power the item can draw from connected items
/// <summary>
/// The maximum amount of power the item can draw from connected items
/// </summary>
protected float powerConsumption;
protected Connection powerIn, powerOut;
[Editable, Serialize(0.5f, true, description: "The minimum voltage required for the device to function. " +
"The voltage is calculated as power / powerconsumption, meaning that a device " +
"with a power consumption of 1000 kW would need at least 500 kW of power to work if the minimum voltage is set to 0.5.")]
public float MinVoltage
{
get { return minVoltage; }
get { return powerConsumption <= 0.0f ? 0.0f : minVoltage; }
set { minVoltage = value; }
}
@@ -76,34 +99,32 @@ namespace Barotrauma.Items.Components
public Powered(Item item, XElement element)
: base(item, element)
{
poweredList.Add(this);
InitProjectSpecific(element);
}
partial void InitProjectSpecific(XElement element);
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power = 0, float signalStrength = 1.0f)
{
if (currPowerConsumption == 0.0f) voltage = 0.0f;
if (connection.IsPower) voltage = Math.Max(0.0f, power);
}
protected void UpdateOnActiveEffects(float deltaTime)
{
if (currPowerConsumption == 0.0f)
if (currPowerConsumption <= 0.0f)
{
//if the item consumes no power, ignore the voltage requirement and
//apply OnActive statuseffects as long as this component is active
if (powerConsumption == 0.0f)
if (powerConsumption <= 0.0f)
{
ApplyStatusEffects(ActionType.OnActive, deltaTime, null);
}
return;
}
#if CLIENT
if (voltage > minVoltage)
{
ApplyStatusEffects(ActionType.OnActive, deltaTime, null);
}
#if CLIENT
if (voltage > minVoltage)
{
if (!powerOnSoundPlayed && powerOnSound != null)
{
SoundPlayer.PlaySound(powerOnSound.Sound, item.WorldPosition, powerOnSound.Volume, powerOnSound.Range, item.CurrentHull);
@@ -114,21 +135,160 @@ namespace Barotrauma.Items.Components
{
powerOnSoundPlayed = false;
}
#else
if (voltage > minVoltage)
{
ApplyStatusEffects(ActionType.OnActive, deltaTime, null);
}
#endif
}
public override void Update(float deltaTime, Camera cam)
{
UpdateOnActiveEffects(deltaTime);
voltage = 0.0f;
}
public override void OnItemLoaded()
{
if (item.Connections == null) { return; }
foreach (Connection c in item.Connections)
{
if (!c.IsPower) { continue; }
if (this is PowerTransfer pt)
{
if (c.Name == "power_in")
{
powerIn = c;
}
else if (c.Name == "power_out")
{
powerOut = c;
}
else if (c.Name == "power")
{
powerIn = powerOut = c;
}
}
else
{
if (c.IsOutput)
{
if (c.Name == "power_in")
{
#if DEBUG
DebugConsole.ThrowError($"Item \"{item.Name}\" has a power output connection called power_in. If the item is supposed to receive power through the connection, change it to an input connection.");
#else
DebugConsole.NewMessage($"Item \"{item.Name}\" has a power output connection called power_in. If the item is supposed to receive power through the connection, change it to an input connection.", Color.Orange);
#endif
}
powerOut = c;
}
else
{
if (c.Name == "power_out")
{
#if DEBUG
DebugConsole.ThrowError($"Item \"{item.Name}\" has a power input connection called power_out. If the item is supposed to output power through the connection, change it to an output connection.");
#else
DebugConsole.NewMessage($"Item \"{item.Name}\" has a power input connection called power_out. If the item is supposed to output power through the connection, change it to an output connection.", Color.Orange);
#endif
}
powerIn = c;
}
}
}
}
public virtual void ReceivePowerProbeSignal(Connection connection, Item source, float power) { }
public static void UpdatePower(float deltaTime)
{
if (updateTimer > 0.0f)
{
updateTimer -= deltaTime;
return;
}
updateTimer = UpdateInterval;
//reset power first
foreach (Powered powered in poweredList)
{
if (powered is PowerTransfer pt)
{
powered.CurrPowerConsumption = 0.0f;
pt.PowerLoad = 0.0f;
if (pt is RelayComponent relay)
{
relay.DisplayLoad = 0.0f;
}
}
//only reset voltage if the item has a power connector
//(other items, such as handheld devices, get power through other means and shouldn't be updated here)
if (powered.powerIn != null || powered.powerOut != null) { powered.voltage = 0.0f; }
}
//go through all the devices that are consuming/providing power
//and send out a "probe signal" which the PowerTransfer components use to add up the grid power/load
foreach (Powered powered in poweredList)
{
if (powered is PowerTransfer) { continue; }
if (powered.currPowerConsumption > 0.0f)
{
//consuming power
lastPowerProbeRecipients.Clear();
powered.powerIn?.SendPowerProbeSignal(powered.item, -powered.currPowerConsumption);
}
}
foreach (Powered powered in poweredList)
{
if (powered is PowerTransfer) { continue; }
else if (powered.currPowerConsumption < 0.0f)
{
//providing power
lastPowerProbeRecipients.Clear();
powered.powerOut?.SendPowerProbeSignal(powered.item, -powered.currPowerConsumption);
}
if (powered is PowerContainer pc)
{
if (pc.CurrPowerOutput <= 0.0f) { continue; }
//providing power
lastPowerProbeRecipients.Clear();
powered.powerOut?.SendPowerProbeSignal(powered.item, pc.CurrPowerOutput);
}
}
//go through powered items and calculate their current voltage
foreach (Powered powered in poweredList)
{
if (powered is PowerTransfer pt1 || (pt1 = powered.Item.GetComponent<PowerTransfer>()) != null)
{
powered.voltage = -pt1.CurrPowerConsumption / Math.Max(pt1.PowerLoad, 1.0f);
continue;
}
if (powered.powerConsumption <= 0.0f && !(powered is PowerContainer))
{
powered.voltage = 1.0f;
continue;
}
if (powered.powerIn == null) { continue; }
foreach (Connection powerSource in powered.powerIn.Recipients)
{
if (!powerSource.IsPower || !powerSource.IsOutput) { continue; }
var pt = powerSource.Item.GetComponent<PowerTransfer>();
if (pt != null)
{
float voltage = -pt.CurrPowerConsumption / Math.Max(pt.PowerLoad, 1.0f);
powered.voltage = Math.Max(powered.voltage, voltage);
continue;
}
var pc = powerSource.Item.GetComponent<PowerContainer>();
if (pc != null)
{
float voltage = -pc.CurrPowerOutput / Math.Max(powered.CurrPowerConsumption, 1.0f);
powered.voltage += voltage;
}
}
}
}
protected override void RemoveComponentSpecific()
{
poweredList.Remove(this);
}
}
}
@@ -416,9 +416,8 @@ namespace Barotrauma.Items.Components
if (IgnoredBodies.Contains(target.Body)) { return false; }
if (target.UserData is Item) { return false; }
if (target.CollisionCategories == Physics.CollisionCharacter && !(target.Body.UserData is Limb))
//ignore character colliders (the projectile only hits limbs)
if (target.CollisionCategories == Physics.CollisionCharacter && target.Body.UserData is Character)
{
return false;
}
@@ -445,17 +444,52 @@ namespace Barotrauma.Items.Components
if (attack != null) { attackResult = attack.DoDamageToLimb(User, limb, item.WorldPosition, 1.0f); }
if (limb.character != null) { character = limb.character; }
}
else if (target.Body.UserData is Structure structure)
else if (target.Body.UserData is Item targetItem)
{
if (attack != null) { attackResult = attack.DoDamage(User, structure, item.WorldPosition, 1.0f); }
if (attack != null && targetItem.Prefab.DamagedByProjectiles)
{
attackResult = attack.DoDamage(User, targetItem, item.WorldPosition, 1.0f);
}
}
else if (target.Body.UserData is IDamageable damageable)
{
if (attack != null) { attackResult = attack.DoDamage(User, damageable, item.WorldPosition, 1.0f); }
}
if (character != null) { character.LastDamageSource = item; }
if (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer)
{
ApplyStatusEffects(ActionType.OnUse, 1.0f, character, target.Body.UserData as Limb, user: user);
ApplyStatusEffects(ActionType.OnImpact, 1.0f, character, target.Body.UserData as Limb, user: user);
if (target.Body.UserData is Limb targetLimb)
{
ApplyStatusEffects(ActionType.OnUse, 1.0f, character, targetLimb, user: user);
ApplyStatusEffects(ActionType.OnImpact, 1.0f, character, targetLimb, user: user);
var attack = targetLimb.attack;
if (attack != null)
{
// Apply the status effects defined in the limb's attack that was hit
foreach (var effect in attack.StatusEffects)
{
if (effect.type == ActionType.OnImpact)
{
//effect.Apply(effect.type, 1.0f, targetLimb.character, targetLimb.character, targetLimb.WorldPosition);
if (effect.HasTargetType(StatusEffect.TargetType.This))
{
effect.Apply(effect.type, 1.0f, targetLimb.character, targetLimb.character, targetLimb.WorldPosition);
}
if (effect.HasTargetType(StatusEffect.TargetType.NearbyItems) ||
effect.HasTargetType(StatusEffect.TargetType.NearbyCharacters))
{
var targets = new List<ISerializableEntity>();
effect.GetNearbyTargets(targetLimb.WorldPosition, targets);
effect.Apply(ActionType.OnActive, 1.0f, targetLimb.character, targets);
}
}
}
}
}
#if SERVER
if (GameMain.NetworkMember.IsServer)
{
@@ -92,9 +92,7 @@ namespace Barotrauma.Items.Components
foreach (XElement connectionElement in subElement.Elements())
{
if (connectionElement.Name.ToString() != element.Name.ToString()) { continue; }
string prefabConnectionName = element.GetAttributeString("name", IsOutput ? "output" : "input");
string prefabConnectionName = element.GetAttributeString("name", null);
if (prefabConnectionName == Name)
{
displayNameTag = connectionElement.GetAttributeString("displayname", "");
@@ -245,31 +243,38 @@ namespace Barotrauma.Items.Components
{
for (int i = 0; i < MaxLinked; i++)
{
if (wires[i] == null) continue;
if (wires[i] == null) { continue; }
Connection recipient = wires[i].OtherConnection(this);
if (recipient == null) continue;
if (recipient.item == this.item || recipient.item == source) continue;
if (recipient == null) { continue; }
if (recipient.item == this.item || recipient.item == source) { continue; }
if (source != null && !source.LastSentSignalRecipients.Contains(recipient.item))
{
source.LastSentSignalRecipients.Add(recipient.item);
}
source?.LastSentSignalRecipients.Add(recipient.item);
foreach (ItemComponent ic in recipient.item.Components)
{
ic.ReceiveSignal(stepsTaken, signal, recipient, source, sender, power, signalStrength);
}
bool broken = recipient.Item.Condition <= 0.0f;
foreach (StatusEffect effect in recipient.Effects)
{
if (broken && effect.type != ActionType.OnBroken) continue;
recipient.Item.ApplyStatusEffect(effect, ActionType.OnUse, (float)Timing.Step, null, null, false, false);
}
}
}
public void SendPowerProbeSignal(Item source, float power)
{
for (int i = 0; i < MaxLinked; i++)
{
if (wires[i] == null) { continue; }
Connection recipient = wires[i].OtherConnection(this);
if (recipient == null) { continue; }
recipient.item.GetComponent<Powered>()?.ReceivePowerProbeSignal(recipient, source, power);
}
}
public void ClearConnections()
{
for (int i = 0; i < MaxLinked; i++)
@@ -0,0 +1,57 @@
using System;
using System.Globalization;
using System.Xml.Linq;
namespace Barotrauma.Items.Components
{
class FunctionComponent : ItemComponent
{
public enum FunctionType
{
Round,
Ceil,
Floor,
Factorial
}
[Serialize(FunctionType.Round, false, description: "Which kind of function to run the input through.")]
public FunctionType Function
{
get; set;
}
public FunctionComponent(Item item, XElement element)
: base(item, element)
{
IsActive = true;
}
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power = 0, float signalStrength = 1)
{
float.TryParse(signal, out float value);
switch (Function)
{
case FunctionType.Round:
item.SendSignal(0, Math.Round(value).ToString("G", CultureInfo.InvariantCulture), "signal_out", null);
break;
case FunctionType.Ceil:
item.SendSignal(0, Math.Ceiling(value).ToString("G", CultureInfo.InvariantCulture), "signal_out", null);
break;
case FunctionType.Floor:
item.SendSignal(0, Math.Floor(value).ToString("G", CultureInfo.InvariantCulture), "signal_out", null);
break;
case FunctionType.Factorial:
int intVal = (int)Math.Min(value, 20);
ulong factorial = 1;
for (int i = intVal; i > 0; i--)
{
factorial *= (ulong)i;
}
item.SendSignal(0, factorial.ToString(), "signal_out", null);
break;
default:
throw new NotImplementedException($"Function {Function} has not been implemented.");
}
}
}
}
@@ -216,19 +216,12 @@ namespace Barotrauma.Items.Components
#endif
}
if (powerConsumption == 0.0f)
{
voltage = 1.0f;
}
else
{
currPowerConsumption = powerConsumption;
}
currPowerConsumption = powerConsumption;
if (Rand.Range(0.0f, 1.0f) < 0.05f && voltage < Rand.Range(0.0f, minVoltage))
if (Rand.Range(0.0f, 1.0f) < 0.05f && Voltage < Rand.Range(0.0f, MinVoltage))
{
#if CLIENT
if (voltage > 0.1f)
if (Voltage > 0.1f)
{
SoundPlayer.PlaySound("zap", item.WorldPosition, hullGuess: item.CurrentHull);
}
@@ -237,7 +230,7 @@ namespace Barotrauma.Items.Components
}
else
{
lightBrightness = MathHelper.Lerp(lightBrightness, Math.Min(voltage, 1.0f), 0.1f);
lightBrightness = MathHelper.Lerp(lightBrightness, Math.Min(Voltage, 1.0f), 0.1f);
}
if (blinkFrequency > 0.0f)
@@ -262,8 +255,6 @@ namespace Barotrauma.Items.Components
{
UpdateAITarget(item.AiTarget);
}
voltage -= deltaTime;
}
#if CLIENT
@@ -0,0 +1,41 @@
using System.Globalization;
using System.Xml.Linq;
namespace Barotrauma.Items.Components
{
class ModuloComponent : ItemComponent
{
private float modulus;
[InGameEditable, Serialize(1.0f, false, description: "The modulus of the operation. Must be non-zero.")]
public float Modulus
{
get { return modulus; }
set
{
modulus = MathUtils.NearlyEqual(value, 0.0f) ? 1.0f : value;
}
}
public ModuloComponent(Item item, XElement element) : base(item, element)
{
IsActive = true;
}
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power = 0, float signalStrength = 1)
{
switch (connection.Name)
{
case "set_modulus":
case "modulus":
float.TryParse(signal, out float newModulus);
Modulus = newModulus;
break;
case "signal_in":
float.TryParse(signal, out float value);
item.SendSignal(0, (value % modulus).ToString("G", CultureInfo.InvariantCulture), "signal_out", null);
break;
}
}
}
}
@@ -25,6 +25,14 @@ namespace Barotrauma.Items.Components
set;
}
[Editable, Serialize(false, true, description: "Should the sensor ignore the bodies of dead characters?")]
public bool IgnoreDead
{
get;
set;
}
[InGameEditable, Serialize(0.0f, true, description: "Horizontal detection range.")]
public float RangeX
{
@@ -109,6 +117,7 @@ namespace Barotrauma.Items.Components
foreach (Character c in Character.CharacterList)
{
if (IgnoreDead && c.IsDead) { continue; }
if (OnlyHumans && !c.IsHuman) { continue; }
//do a rough check based on the position of the character's collider first
@@ -138,5 +147,15 @@ namespace Barotrauma.Items.Components
{
detectOffset.Y = -detectOffset.Y;
}
public override XElement Save(XElement parentElement)
{
Vector2 prevDetectOffset = detectOffset;
//undo flipping before saving
if (item.FlippedX) { detectOffset.X = -detectOffset.X; }
if (item.FlippedY) { detectOffset.Y = -detectOffset.Y; }
XElement element = base.Save(parentElement);
detectOffset = prevDetectOffset;
return element;
}
}
}
@@ -1,4 +1,5 @@
using Barotrauma.Networking;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Xml.Linq;
@@ -8,9 +9,11 @@ namespace Barotrauma.Items.Components
class RelayComponent : PowerTransfer, IServerSerializable
{
private float maxPower;
private bool isOn;
private float throttlePowerOutput;
private static readonly Dictionary<string, string> connectionPairs = new Dictionary<string, string>
{
{ "power_in", "power_out"},
@@ -21,6 +24,7 @@ namespace Barotrauma.Items.Components
{ "signal_in4", "signal_out4" },
{ "signal_in5", "signal_out5" }
};
public float DisplayLoad { get; set; }
[Editable, Serialize(1000.0f, true, description: "The maximum amount of power that can pass through the item.")]
public float MaxPower
@@ -31,7 +35,7 @@ namespace Barotrauma.Items.Components
maxPower = Math.Max(0.0f, value);
}
}
[Editable, Serialize(false, true, description: "Can the relay currently pass power and signals through it.")]
public bool IsOn
{
@@ -49,18 +53,46 @@ namespace Barotrauma.Items.Components
}
}
}
public RelayComponent(Item item, XElement element)
: base (item, element)
: base(item, element)
{
IsActive = true;
}
throttlePowerOutput = MaxPower;
}
public override void Update(float deltaTime, Camera cam)
{
base.Update(deltaTime, cam);
RefreshConnections();
item.SendSignal(0, IsOn ? "1" : "0", "state_out", null);
if (!CanTransfer) { Voltage = 0.0f; return; }
if (isBroken)
{
SetAllConnectionsDirty();
isBroken = false;
}
ApplyStatusEffects(ActionType.OnActive, deltaTime, null);
if (powerOut != null)
{
bool overloaded = false;
foreach (Connection recipient in powerOut.Recipients)
{
var pt = recipient.Item.GetComponent<PowerTransfer>();
if (pt != null)
{
float overload = -pt.CurrPowerConsumption - pt.PowerLoad;
throttlePowerOutput += overload * deltaTime * 0.5f;
overloaded = overload > 1.0f;
}
}
throttlePowerOutput = overloaded ?
MathHelper.Clamp(throttlePowerOutput, 0.0f, MaxPower):
Math.Max(throttlePowerOutput - MaxPower * 0.1f * deltaTime, 0.0f);
}
if (Math.Min(-currPowerConsumption, PowerLoad) > maxPower && CanBeOverloaded)
{
@@ -68,9 +100,56 @@ namespace Barotrauma.Items.Components
}
}
public override void ReceivePowerProbeSignal(Connection connection, Item source, float power)
{
if (!IsOn) { return; }
//we've already received this signal
if (lastPowerProbeRecipients.Contains(this)) { return; }
lastPowerProbeRecipients.Add(this);
if (power < 0.0f)
{
if (!connection.IsOutput || powerIn == null) { return; }
//power being drawn from the power_out connection
DisplayLoad -= Math.Min(power, 0.0f);
powerLoad -= Math.Min(power + throttlePowerOutput, 0.0f);
//pass the load to items connected to the input
powerIn.SendPowerProbeSignal(source, Math.Max(power, -MaxPower));
}
else
{
if (connection.IsOutput || powerOut == null) { return; }
//power being supplied to the power_in connection
if (currPowerConsumption - power < -MaxPower)
{
power += MaxPower + (currPowerConsumption - power);
}
currPowerConsumption -= power;
foreach (Connection recipient in powerOut.Recipients)
{
if (!recipient.IsPower) { continue; }
var powered = recipient.Item.GetComponent<Powered>();
if (powered == null) { continue; }
float load = powered.CurrPowerConsumption;
var powerTransfer = powered as PowerTransfer;
if (powerTransfer != null) { load = powerTransfer.PowerLoad; }
float powerOut = power * (load / Math.Max(powerLoad + throttlePowerOutput, 0.01f));
powered.ReceivePowerProbeSignal(recipient, source, Math.Min(powerOut, power));
}
}
}
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power = 0.0f, float signalStrength = 1.0f)
{
if (connection.IsPower || item.Condition <= 0.0f) { return; }
if (item.Condition <= 0.0f || connection.IsPower) { return; }
if (connectionPairs.TryGetValue(connection.Name, out string outConnection))
{
@@ -0,0 +1,108 @@
using Microsoft.Xna.Framework;
using System;
using System.Globalization;
using System.Xml.Linq;
namespace Barotrauma.Items.Components
{
class TrigonometricFunctionComponent : ItemComponent
{
public enum FunctionType
{
Sin,
Cos,
Tan,
Asin,
Acos,
Atan,
}
protected float[] receivedSignal = new float[2];
[Serialize(FunctionType.Sin, false, description: "Which kind of function to run the input through.")]
public FunctionType Function
{
get; set;
}
[InGameEditable, Serialize(false, true, description: "If set to true, the trigonometric function uses radians instead of degrees.")]
public bool UseRadians
{
get; set;
}
public TrigonometricFunctionComponent(Item item, XElement element)
: base(item, element)
{
IsActive = true;
}
public override void Update(float deltaTime, Camera cam)
{
//reset received signals
receivedSignal[0] = float.NaN;
receivedSignal[1] = float.NaN;
}
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power = 0, float signalStrength = 1)
{
float.TryParse(signal, out float value);
switch (Function)
{
case FunctionType.Sin:
if (!UseRadians) { value = MathHelper.ToRadians(value); }
item.SendSignal(0, ((float)Math.Sin(value)).ToString("G", CultureInfo.InvariantCulture), "signal_out", null);
break;
case FunctionType.Cos:
if (!UseRadians) { value = MathHelper.ToRadians(value); }
item.SendSignal(0, ((float)Math.Cos(value)).ToString("G", CultureInfo.InvariantCulture), "signal_out", null);
break;
case FunctionType.Tan:
if (!UseRadians) { value = MathHelper.ToRadians(value); }
item.SendSignal(0, ((float)Math.Tan(value)).ToString("G", CultureInfo.InvariantCulture), "signal_out", null);
break;
case FunctionType.Asin:
{
float angle = (float)Math.Asin(value);
if (!UseRadians) { angle = MathHelper.ToDegrees(angle); }
item.SendSignal(0, angle.ToString("G", CultureInfo.InvariantCulture), "signal_out", null);
}
break;
case FunctionType.Acos:
{
float angle = (float)Math.Acos(value);
if (!UseRadians) { angle = MathHelper.ToDegrees(angle); }
item.SendSignal(0, angle.ToString("G", CultureInfo.InvariantCulture), "signal_out", null);
}
break;
case FunctionType.Atan:
if (connection.Name == "signal_in_x")
{
float.TryParse(signal, NumberStyles.Float, CultureInfo.InvariantCulture, out receivedSignal[0]);
}
else if (connection.Name == "signal_in_y")
{
float.TryParse(signal, NumberStyles.Float, CultureInfo.InvariantCulture, out receivedSignal[1]);
if (!float.IsNaN(receivedSignal[0]) && !float.IsNaN(receivedSignal[1]))
{
float angle = (float)Math.Atan2(receivedSignal[1], receivedSignal[0]);
if (!UseRadians) { angle = MathHelper.ToDegrees(angle); }
item.SendSignal(0, angle.ToString("G", CultureInfo.InvariantCulture), "signal_out", null);
}
}
else
{
float angle = (float)Math.Atan(value);
if (!UseRadians) { angle = MathHelper.ToDegrees(angle); }
item.SendSignal(0, angle.ToString("G", CultureInfo.InvariantCulture), "signal_out", null);
}
break;
default:
throw new NotImplementedException($"Function {Function} has not been implemented.");
}
}
}
}
@@ -22,7 +22,7 @@ namespace Barotrauma.Items.Components
[Serialize(Character.TeamType.None, false, description: "WiFi components can only communicate with components that have the same Team ID.")]
public Character.TeamType TeamID { get; set; }
[Serialize(20000.0f, false, description: "How close the recipient has to be to receive a signal from this WiFi component.")]
[Editable, Serialize(20000.0f, false, description: "How close the recipient has to be to receive a signal from this WiFi component.")]
public float Range
{
get { return range; }
@@ -174,8 +174,6 @@ namespace Barotrauma.Items.Components
protected override void RemoveComponentSpecific()
{
base.RemoveComponentSpecific();
list.Remove(this);
}
}
@@ -83,22 +83,16 @@ namespace Barotrauma.Items.Components
public Wire(Item item, XElement element)
: base(item, element)
{
#if CLIENT
if (wireSprite == null)
{
wireSprite = new Sprite("Content/Items/wireHorizontal.png", new Vector2(0.5f, 0.5f))
{
Depth = 0.85f
};
}
#endif
nodes = new List<Vector2>();
sections = new List<WireSection>();
connections = new Connection[2];
IsActive = false;
InitProjSpecific(element);
}
partial void InitProjSpecific(XElement element);
public Connection OtherConnection(Connection connection)
{
if (connection == connections[0]) { return connections[1]; }
@@ -728,6 +722,11 @@ namespace Barotrauma.Items.Components
{
ClearConnections();
base.RemoveComponentSpecific();
#if CLIENT
overrideSprite?.Remove();
overrideSprite = null;
wireSprite = null;
#endif
}
public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
@@ -192,6 +192,7 @@ namespace Barotrauma.Items.Components
public override void OnItemLoaded()
{
base.OnItemLoaded();
var lightComponents = item.GetComponents<LightComponent>();
if (lightComponents != null && lightComponents.Count() > 0)
{
@@ -325,20 +326,24 @@ namespace Barotrauma.Items.Components
failedLaunchAttempts = 0;
var batteries = item.GetConnectedComponents<PowerContainer>();
float availablePower = 0.0f;
foreach (PowerContainer battery in batteries)
float neededPower = powerConsumption;
while (neededPower > 0.0001f && batteries.Count > 0)
{
float batteryPower = Math.Min(battery.Charge * 3600.0f, battery.MaxOutPut);
float takePower = Math.Min(powerConsumption - availablePower, batteryPower);
battery.Charge -= takePower / 3600.0f;
#if SERVER
if (GameMain.Server != null)
batteries.RemoveAll(b => b.Charge <= 0.0001f || b.MaxOutPut <= 0.0001f);
float takePower = neededPower / batteries.Count;
takePower = Math.Min(takePower, batteries.Min(b => Math.Min(b.Charge * 3600.0f, b.MaxOutPut)));
foreach (PowerContainer battery in batteries)
{
battery.Item.CreateServerEvent(battery);
}
neededPower -= takePower;
battery.Charge -= takePower / 3600.0f;
#if SERVER
if (GameMain.Server != null)
{
battery.Item.CreateServerEvent(battery);
}
#endif
}
}
Launch(projectiles[0].Item, character);
@@ -477,12 +482,12 @@ namespace Barotrauma.Items.Components
//enough shells and power
Character closestEnemy = null;
float closestDist = 10000.0f * 10000.0f;
float closestDist = 3000 * 3000;
foreach (Character enemy in Character.CharacterList)
{
//ignore humans and characters that are inside the sub
if (enemy.IsDead|| enemy.AnimController.CurrentHull != null || !enemy.Enabled) { continue; }
if (enemy.SpeciesName == character.SpeciesName && enemy.TeamID == character.TeamID) { continue; }
// Ignore friendly and those that are inside the sub
if (enemy.IsDead || enemy.AnimController.CurrentHull != null || !enemy.Enabled) { continue; }
if (HumanAIController.IsFriendly(character, enemy)) { continue; }
float dist = Vector2.DistanceSquared(enemy.WorldPosition, item.WorldPosition);
if (dist > closestDist) { continue; }
@@ -510,8 +515,21 @@ namespace Barotrauma.Items.Components
if (Math.Abs(MathUtils.GetShortestAngle(enemyAngle, turretAngle)) > 0.15f) { return false; }
var pickedBody = Submarine.PickBody(ConvertUnits.ToSimUnits(item.WorldPosition), closestEnemy.SimPosition, null);
if (pickedBody != null && !(pickedBody.UserData is Limb)) { return false; }
var pickedBody = Submarine.PickBody(ConvertUnits.ToSimUnits(item.WorldPosition), closestEnemy.SimPosition);
if (pickedBody == null) { return false; }
Character target = null;
if (pickedBody.UserData is Character c)
{
target = c;
}
else if (pickedBody.UserData is Limb limb)
{
target = limb.character;
}
if (target == null || HumanAIController.IsFriendly(character, target))
{
return false;
}
if (objective.Option.ToLowerInvariant() == "fireatwill")
{
@@ -554,8 +572,8 @@ namespace Barotrauma.Items.Components
{
base.RemoveComponentSpecific();
if (barrelSprite != null) barrelSprite.Remove();
if (railSprite != null) railSprite.Remove();
barrelSprite?.Remove(); barrelSprite = null;
railSprite?.Remove(); railSprite = null;
#if CLIENT
moveSoundChannel?.Dispose(); moveSoundChannel = null;
@@ -6,6 +6,7 @@ using System.Linq;
using System.Xml.Linq;
using Barotrauma.Items.Components;
using Barotrauma.Extensions;
using Barotrauma.Networking;
namespace Barotrauma
{
@@ -23,6 +24,7 @@ namespace Barotrauma
class WearableSprite
{
public string UnassignedSpritePath { get; private set; }
public string SpritePath { get; private set; }
public XElement SourceElement { get; private set; }
@@ -83,7 +85,7 @@ namespace Barotrauma
if (value == _gender) { return; }
_gender = value;
IsInitialized = false;
SpritePath = ParseSpritePath(SourceElement.GetAttributeString("texture", string.Empty));
UnassignedSpritePath = ParseSpritePath(SourceElement.GetAttributeString("texture", string.Empty));
Init(_gender);
}
}
@@ -92,7 +94,7 @@ namespace Barotrauma
{
Type = type;
SourceElement = subElement;
SpritePath = subElement.GetAttributeString("texture", string.Empty);
UnassignedSpritePath = subElement.GetAttributeString("texture", string.Empty);
Init();
switch (type)
{
@@ -122,42 +124,24 @@ namespace Barotrauma
Type = WearableType.Item;
WearableComponent = wearable;
Variant = Math.Max(variant, 0);
SpritePath = ParseSpritePath(subElement.GetAttributeString("texture", string.Empty));
UnassignedSpritePath = ParseSpritePath(subElement.GetAttributeString("texture", string.Empty));
SourceElement = subElement;
}
private string ParseSpritePath(string texturePath) => texturePath.Contains("/") ? texturePath : $"{Path.GetDirectoryName(WearableComponent.Item.Prefab.ConfigFile)}/{texturePath}";
public void RefreshPath()
{
if (Variant > 0)
{
// Restore the tag so that we can parse it again.
ReplaceNumbersWith("[VARIANT]");
}
ParsePath(true);
}
private void ReplaceNumbersWith(string replacement)
{
var fileName = Path.GetFileName(SpritePath);
var path = Path.GetDirectoryName(SpritePath);
fileName = fileName.Replace(replacement, c => char.IsNumber(c));
SpritePath = Path.Combine(path, fileName);
}
private void ParsePath(bool parseSpritePath)
public void ParsePath(bool parseSpritePath)
{
string tempPath = UnassignedSpritePath;
if (_gender != Gender.None)
{
SpritePath = SpritePath.Replace("[GENDER]", (_gender == Gender.Female) ? "female" : "male");
tempPath = tempPath.Replace("[GENDER]", (_gender == Gender.Female) ? "female" : "male");
}
SpritePath = SpritePath.Replace("[VARIANT]", Variant.ToString());
SpritePath = tempPath.Replace("[VARIANT]", Variant.ToString());
if (!File.Exists(SpritePath))
{
// If the variant does not exist, parse the path so that it uses first variant.
Variant = 1;
ReplaceNumbersWith(Variant.ToString());
SpritePath = tempPath.Replace("[VARIANT]", "1");
}
if (parseSpritePath)
{
@@ -169,13 +153,13 @@ namespace Barotrauma
public void Init(Gender gender = Gender.None)
{
if (IsInitialized) { return; }
_gender = SpritePath.Contains("[GENDER]") ? gender : Gender.None;
_gender = UnassignedSpritePath.Contains("[GENDER]") ? gender : Gender.None;
ParsePath(false);
if (Sprite != null)
{
Sprite.Remove();
}
Sprite = new Sprite(SourceElement, file: SpritePath);
Sprite = new Sprite(SourceElement, file: SpritePath, preMultiplyAlpha: true);
Limb = (LimbType)Enum.Parse(typeof(LimbType), SourceElement.GetAttributeString("limb", "Head"), true);
HideLimb = SourceElement.GetAttributeBool("hidelimb", false);
HideOtherWearables = SourceElement.GetAttributeBool("hideotherwearables", false);
@@ -197,25 +181,60 @@ namespace Barotrauma
namespace Barotrauma.Items.Components
{
class Wearable : Pickable
class Wearable : Pickable, IServerSerializable
{
private WearableSprite[] wearableSprites;
private LimbType[] limbType;
private Limb[] limb;
private readonly XElement[] wearableElements;
private readonly WearableSprite[] wearableSprites;
private readonly LimbType[] limbType;
private readonly Limb[] limb;
private List<DamageModifier> damageModifiers;
private readonly List<DamageModifier> damageModifiers;
public List<DamageModifier> DamageModifiers
public IEnumerable<DamageModifier> DamageModifiers
{
get { return damageModifiers; }
}
private bool autoEquipWhenFull;
public bool AutoEquipWhenFull
public bool AutoEquipWhenFull { get; private set; }
public readonly int Variants;
private int variant;
public int Variant
{
get { return autoEquipWhenFull; }
}
get { return variant; }
set
{
#if SERVER
variant = value;
item.CreateServerEvent(this);
#elif CLIENT
if (variant == value) { return; }
Character character = picker;
if (character != null)
{
Unequip(character);
}
for (int i = 0; i < wearableSprites.Length; i++)
{
var subElement = wearableElements[i];
wearableSprites[i]?.Sprite?.Remove();
wearableSprites[i] = new WearableSprite(subElement, this, value);
}
if (character != null)
{
Equip(character);
}
variant = value;
#endif
}
}
public Wearable(Item item, XElement element) : base(item, element)
{
this.item = item;
@@ -223,12 +242,13 @@ namespace Barotrauma.Items.Components
damageModifiers = new List<DamageModifier>();
int spriteCount = element.Elements().Count(x => x.Name.ToString() == "sprite");
int variants = element.GetAttributeInt("variants", 0);
int variant = variants > 0 ? Rand.Range(1, variants + 1, Rand.RandSync.Server) : 1;
Variants = element.GetAttributeInt("variants", 0);
variant = Rand.Range(1, Variants + 1, Rand.RandSync.Server);
wearableSprites = new WearableSprite[spriteCount];
wearableElements = new XElement[spriteCount];
limbType = new LimbType[spriteCount];
limb = new Limb[spriteCount];
autoEquipWhenFull = element.GetAttributeBool("autoequipwhenfull", true);
AutoEquipWhenFull = element.GetAttributeBool("autoequipwhenfull", true);
int i = 0;
foreach (XElement subElement in element.Elements())
{
@@ -245,6 +265,7 @@ namespace Barotrauma.Items.Components
subElement.GetAttributeString("limb", "Head"), true);
wearableSprites[i] = new WearableSprite(subElement, this, variant);
wearableElements[i] = subElement;
foreach (XElement lightElement in subElement.Elements())
{
@@ -380,5 +401,39 @@ namespace Barotrauma.Items.Components
}
}
public override XElement Save(XElement parentElement)
{
XElement componentElement = base.Save(parentElement);
componentElement.Add(new XAttribute("variant", variant));
return componentElement;
}
private int loadedVariant = -1;
public override void Load(XElement componentElement, bool usePrefabValues)
{
base.Load(componentElement, usePrefabValues);
loadedVariant = componentElement.GetAttributeInt("variant", -1);
}
public override void OnItemLoaded()
{
base.OnItemLoaded();
//do this here to prevent creating a network event before the item has been fully initialized
if (loadedVariant > 0 && loadedVariant < Variants + 1)
{
Variant = loadedVariant;
}
}
public override void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null)
{
msg.Write((byte)Variant);
base.ServerWrite(msg, c, extraData);
}
public override void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
{
Variant = (int)msg.ReadByte();
base.ClientRead(type, msg, sendingTime);
}
}
}