38f1ddb...178a853: v0.8.9.1, removed content folder
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
@@ -146,7 +147,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
limb.Disabled = true;
|
||||
|
||||
Vector2 worldPosition = lb.position + new Vector2(item.WorldRect.X, item.WorldRect.Y);
|
||||
Vector2 worldPosition = new Vector2(item.WorldRect.X, item.WorldRect.Y) + lb.position * item.Scale;
|
||||
Vector2 diff = worldPosition - limb.WorldPosition;
|
||||
|
||||
limb.PullJointEnabled = true;
|
||||
@@ -225,7 +226,7 @@ namespace Barotrauma.Items.Components
|
||||
Turret turret = targetItem.GetComponent<Turret>();
|
||||
if (turret != null)
|
||||
{
|
||||
centerPos = new Vector2(targetItem.WorldRect.X + turret.BarrelPos.X, targetItem.WorldRect.Y - turret.BarrelPos.Y);
|
||||
centerPos = new Vector2(targetItem.WorldRect.X + turret.TransformedBarrelPos.X, targetItem.WorldRect.Y - turret.TransformedBarrelPos.Y);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -240,7 +241,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
private Item GetFocusTarget()
|
||||
{
|
||||
item.SendSignal(0, targetRotation.ToString(), "position_out", character);
|
||||
item.SendSignal(0, MathHelper.ToDegrees(targetRotation).ToString("G", CultureInfo.InvariantCulture), "position_out", character);
|
||||
|
||||
for (int i = item.LastSentSignalRecipients.Count - 1; i >= 0; i--)
|
||||
{
|
||||
@@ -259,7 +260,7 @@ namespace Barotrauma.Items.Components
|
||||
item.SendSignal(0, "1", "signal_out", picker);
|
||||
|
||||
#if CLIENT
|
||||
PlaySound(ActionType.OnUse, item.WorldPosition);
|
||||
PlaySound(ActionType.OnUse, item.WorldPosition, picker);
|
||||
#endif
|
||||
|
||||
return true;
|
||||
@@ -308,7 +309,7 @@ namespace Barotrauma.Items.Components
|
||||
return true;
|
||||
}
|
||||
|
||||
public override void FlipX()
|
||||
public override void FlipX(bool relativeToSub)
|
||||
{
|
||||
if (dir != Direction.None)
|
||||
{
|
||||
@@ -319,16 +320,32 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
for (int i = 0; i < limbPositions.Count; i++)
|
||||
{
|
||||
float diff = (item.Rect.X + limbPositions[i].position.X) - item.Rect.Center.X;
|
||||
float diff = (item.Rect.X + limbPositions[i].position.X * item.Scale) - item.Rect.Center.X;
|
||||
|
||||
Vector2 flippedPos =
|
||||
new Vector2(
|
||||
item.Rect.Center.X - diff - item.Rect.X,
|
||||
(item.Rect.Center.X - diff - item.Rect.X) / item.Scale,
|
||||
limbPositions[i].position.Y);
|
||||
|
||||
limbPositions[i] = new LimbPos(limbPositions[i].limbType, flippedPos);
|
||||
}
|
||||
}
|
||||
|
||||
public override void FlipY(bool relativeToSub)
|
||||
{
|
||||
userPos.Y = -UserPos.Y;
|
||||
|
||||
for (int i = 0; i < limbPositions.Count; i++)
|
||||
{
|
||||
float diff = (item.Rect.Y + limbPositions[i].position.Y) - item.Rect.Center.Y;
|
||||
|
||||
Vector2 flippedPos =
|
||||
new Vector2(
|
||||
limbPositions[i].position.X,
|
||||
item.Rect.Center.Y - diff - item.Rect.Y);
|
||||
|
||||
limbPositions[i] = new LimbPos(limbPositions[i].limbType, flippedPos);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using Barotrauma.Networking;
|
||||
using Lidgren.Network;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
@@ -9,98 +8,148 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
partial class Deconstructor : Powered, IServerSerializable, IClientSerializable
|
||||
{
|
||||
float progressTimer;
|
||||
private float progressTimer;
|
||||
private float progressState;
|
||||
|
||||
ItemContainer container;
|
||||
private ItemContainer inputContainer, outputContainer;
|
||||
|
||||
public ItemContainer OutputContainer
|
||||
{
|
||||
get { return outputContainer; }
|
||||
}
|
||||
|
||||
public Deconstructor(Item item, XElement element)
|
||||
: base(item, element)
|
||||
{
|
||||
#if CLIENT
|
||||
progressBar = new GUIProgressBar(new Rectangle(0,0,200,20), Color.Green, "", 0.0f, Alignment.BottomCenter, GuiFrame);
|
||||
|
||||
activateButton = new GUIButton(new Rectangle(0, 0, 200, 20), "Deconstruct", Alignment.TopCenter, "", GuiFrame);
|
||||
activateButton.OnClicked = ToggleActive;
|
||||
#endif
|
||||
InitProjSpecific(element);
|
||||
}
|
||||
|
||||
partial void InitProjSpecific(XElement element);
|
||||
|
||||
public override void OnItemLoaded()
|
||||
{
|
||||
var containers = item.GetComponents<ItemContainer>().ToList();
|
||||
if (containers.Count < 2)
|
||||
{
|
||||
DebugConsole.ThrowError("Error in item \"" + item.Name + "\": Deconstructors must have two ItemContainer components!");
|
||||
return;
|
||||
}
|
||||
|
||||
inputContainer = containers[0];
|
||||
outputContainer = containers[1];
|
||||
|
||||
OnItemLoadedProjSpecific();
|
||||
}
|
||||
|
||||
partial void OnItemLoadedProjSpecific();
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
if (container == null || container.Inventory.Items.All(i => i == null))
|
||||
MoveInputQueue();
|
||||
|
||||
if (inputContainer == null || inputContainer.Inventory.Items.All(i => i == null))
|
||||
{
|
||||
SetActive(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (voltage < minVoltage) return;
|
||||
|
||||
|
||||
ApplyStatusEffects(ActionType.OnActive, deltaTime, null);
|
||||
|
||||
if (powerConsumption == 0.0f) voltage = 1.0f;
|
||||
|
||||
progressTimer += deltaTime*voltage;
|
||||
progressTimer += deltaTime * voltage;
|
||||
Voltage -= deltaTime * 10.0f;
|
||||
|
||||
var targetItem = container.Inventory.Items.FirstOrDefault(i => i != null);
|
||||
#if CLIENT
|
||||
progressBar.BarSize = Math.Min(progressTimer / targetItem.Prefab.DeconstructTime, 1.0f);
|
||||
#endif
|
||||
if (progressTimer>targetItem.Prefab.DeconstructTime)
|
||||
var targetItem = inputContainer.Inventory.Items.LastOrDefault(i => i != null);
|
||||
if (targetItem == null) { return; }
|
||||
|
||||
progressState = Math.Min(progressTimer / targetItem.Prefab.DeconstructTime, 1.0f);
|
||||
if (progressTimer > targetItem.Prefab.DeconstructTime)
|
||||
{
|
||||
var containers = item.GetComponents<ItemContainer>();
|
||||
if (containers.Count < 2)
|
||||
{
|
||||
DebugConsole.ThrowError("Error in Deconstructor.Update: Deconstructors must have two ItemContainer components!");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (DeconstructItem deconstructProduct in targetItem.Prefab.DeconstructItems)
|
||||
{
|
||||
float percentageHealth = targetItem.Condition / targetItem.Prefab.Health;
|
||||
if (percentageHealth <= deconstructProduct.MinCondition || percentageHealth > deconstructProduct.MaxCondition) continue;
|
||||
|
||||
var itemPrefab = MapEntityPrefab.Find(deconstructProduct.ItemPrefabName) as ItemPrefab;
|
||||
var itemPrefab = MapEntityPrefab.Find(null, deconstructProduct.ItemIdentifier) as ItemPrefab;
|
||||
if (itemPrefab == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Tried to deconstruct item \"" + targetItem.Name + "\" but couldn't find item prefab \"" + deconstructProduct + "\"!");
|
||||
DebugConsole.ThrowError("Tried to deconstruct item \"" + targetItem.Name + "\" but couldn't find item prefab \"" + deconstructProduct.ItemIdentifier + "\"!");
|
||||
continue;
|
||||
}
|
||||
|
||||
float condition = deconstructProduct.CopyCondition ?
|
||||
percentageHealth * itemPrefab.Health :
|
||||
itemPrefab.Health * deconstructProduct.OutCondition;
|
||||
|
||||
//container full, drop the items outside the deconstructor
|
||||
if (containers[1].Inventory.Items.All(i => i != null))
|
||||
if (outputContainer.Inventory.Items.All(i => i != null))
|
||||
{
|
||||
Entity.Spawner.AddToSpawnQueue(itemPrefab, item.Position, item.Submarine, itemPrefab.Health * deconstructProduct.OutCondition);
|
||||
Entity.Spawner.AddToSpawnQueue(itemPrefab, item.Position, item.Submarine, condition);
|
||||
}
|
||||
else
|
||||
{
|
||||
Entity.Spawner.AddToSpawnQueue(itemPrefab, containers[1].Inventory, itemPrefab.Health * deconstructProduct.OutCondition);
|
||||
Entity.Spawner.AddToSpawnQueue(itemPrefab, outputContainer.Inventory, condition);
|
||||
}
|
||||
}
|
||||
|
||||
container.Inventory.RemoveItem(targetItem);
|
||||
inputContainer.Inventory.RemoveItem(targetItem);
|
||||
Entity.Spawner.AddToRemoveQueue(targetItem);
|
||||
MoveInputQueue();
|
||||
PutItemsToLinkedContainer();
|
||||
|
||||
if (container.Inventory.Items.Any(i => i != null))
|
||||
if (inputContainer.Inventory.Items.Any(i => i != null))
|
||||
{
|
||||
progressTimer = 0.0f;
|
||||
#if CLIENT
|
||||
progressBar.BarSize = 0.0f;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void PutItemsToLinkedContainer()
|
||||
{
|
||||
if (GameMain.Client != null) { return; }
|
||||
if (outputContainer.Inventory.Items.All(it => it == null)) return;
|
||||
|
||||
foreach (MapEntity linkedTo in item.linkedTo)
|
||||
{
|
||||
if (linkedTo is Item linkedItem)
|
||||
{
|
||||
var fabricator = linkedItem.GetComponent<Fabricator>();
|
||||
if (fabricator != null) { continue; }
|
||||
var itemContainer = linkedItem.GetComponent<ItemContainer>();
|
||||
if (itemContainer == null) { continue; }
|
||||
|
||||
foreach (Item containedItem in outputContainer.Inventory.Items)
|
||||
{
|
||||
if (containedItem == null) { continue; }
|
||||
if (itemContainer.Inventory.Items.All(it => it != null)) { break; }
|
||||
itemContainer.Inventory.TryPutItem(containedItem, user: null, createNetworkEvent: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Move items towards the last slot in the inventory if there's free slots
|
||||
/// </summary>
|
||||
private void MoveInputQueue()
|
||||
{
|
||||
for (int i = inputContainer.Inventory.Capacity - 2; i >= 0; i--)
|
||||
{
|
||||
if (inputContainer.Inventory.Items[i] != null && inputContainer.Inventory.Items[i + 1] == null)
|
||||
{
|
||||
inputContainer.Inventory.TryPutItem(inputContainer.Inventory.Items[i], i + 1, allowSwapping: false, allowCombine: false, user: null, createNetworkEvent: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SetActive(bool active, Character user = null)
|
||||
{
|
||||
container = item.GetComponent<ItemContainer>();
|
||||
if (container == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Error in Deconstructor.Activate: Deconstructors must have two ItemContainer components");
|
||||
return;
|
||||
}
|
||||
PutItemsToLinkedContainer();
|
||||
|
||||
if (container.Inventory.Items.All(i => i == null)) active = false;
|
||||
if (inputContainer.Inventory.Items.All(i => i == null)) { active = false; }
|
||||
|
||||
IsActive = active;
|
||||
|
||||
@@ -109,22 +158,21 @@ namespace Barotrauma.Items.Components
|
||||
GameServer.Log(user.LogName + (IsActive ? " activated " : " deactivated ") + item.Name, ServerLog.MessageType.ItemInteraction);
|
||||
}
|
||||
|
||||
if (!IsActive) { progressState = 0.0f; }
|
||||
|
||||
#if CLIENT
|
||||
if (!IsActive)
|
||||
{
|
||||
progressBar.BarSize = 0.0f;
|
||||
progressTimer = 0.0f;
|
||||
|
||||
activateButton.Text = "Deconstruct";
|
||||
activateButton.Text = TextManager.Get("DeconstructorDeconstruct");
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
activateButton.Text = "Cancel";
|
||||
activateButton.Text = TextManager.Get("DeconstructorCancel");
|
||||
}
|
||||
#endif
|
||||
|
||||
container.Inventory.Locked = IsActive;
|
||||
inputContainer.Inventory.Locked = IsActive;
|
||||
}
|
||||
|
||||
public void ServerRead(ClientNetObject type, NetBuffer msg, Client c)
|
||||
|
||||
@@ -2,10 +2,12 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.Networking;
|
||||
using Lidgren.Network;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
partial class Engine : Powered
|
||||
partial class Engine : Powered, IServerSerializable, IClientSerializable
|
||||
{
|
||||
private float force;
|
||||
|
||||
@@ -13,6 +15,14 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
private float maxForce;
|
||||
|
||||
private Attack propellerDamage;
|
||||
|
||||
private float damageTimer;
|
||||
|
||||
private bool hasPower;
|
||||
|
||||
private float prevVoltage;
|
||||
|
||||
[Editable(0.0f, 10000000.0f, ToolTip = "The amount of force exerted on the submarine when the engine is operating at full power."),
|
||||
Serialize(2000.0f, true)]
|
||||
public float MaxForce
|
||||
@@ -24,56 +34,70 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
[Editable, Serialize("0.0,0.0", true)]
|
||||
public Vector2 PropellerPos
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public float Force
|
||||
{
|
||||
get { return force;}
|
||||
set { force = MathHelper.Clamp(value, -100.0f, 100.0f); }
|
||||
}
|
||||
|
||||
public float CurrentVolume
|
||||
{
|
||||
get { return Math.Abs((force / 100.0f) * (minVoltage <= 0.0f ? 1.0f : Math.Min(prevVoltage / minVoltage, 1.0f))); }
|
||||
}
|
||||
|
||||
public Engine(Item item, XElement element)
|
||||
: base(item, element)
|
||||
{
|
||||
IsActive = true;
|
||||
|
||||
#if CLIENT
|
||||
var button = new GUIButton(new Rectangle(160, 50, 30, 30), "-", "", GuiFrame);
|
||||
button.OnClicked = (GUIButton btn, object obj) =>
|
||||
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
targetForce -= 1.0f;
|
||||
|
||||
return true;
|
||||
};
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "propellerdamage":
|
||||
propellerDamage = new Attack(subElement, item.Name + ", Engine");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
button = new GUIButton(new Rectangle(200, 50, 30, 30), "+", "", GuiFrame);
|
||||
button.OnClicked = (GUIButton btn, object obj) =>
|
||||
{
|
||||
targetForce += 1.0f;
|
||||
|
||||
return true;
|
||||
};
|
||||
#endif
|
||||
}
|
||||
|
||||
public float CurrentVolume
|
||||
{
|
||||
get { return Math.Abs((force / 100.0f) * Math.Min(voltage / minVoltage, 1.0f)); }
|
||||
InitProjSpecific(element);
|
||||
}
|
||||
|
||||
partial void InitProjSpecific(XElement element);
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
UpdateOnActiveEffects(deltaTime);
|
||||
|
||||
UpdateAnimation(deltaTime);
|
||||
|
||||
currPowerConsumption = Math.Abs(targetForce) / 100.0f * powerConsumption;
|
||||
//pumps consume more power when in a bad condition
|
||||
currPowerConsumption *= MathHelper.Lerp(2.0f, 1.0f, item.Condition / 100.0f);
|
||||
|
||||
if (powerConsumption == 0.0f) voltage = 1.0f;
|
||||
|
||||
prevVoltage = voltage;
|
||||
hasPower = voltage > minVoltage;
|
||||
|
||||
Force = MathHelper.Lerp(force, (voltage < minVoltage) ? 0.0f : targetForce, 0.1f);
|
||||
if (Math.Abs(Force) > 1.0f)
|
||||
{
|
||||
Vector2 currForce = new Vector2((force / 100.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 / 100.0f);
|
||||
|
||||
item.Submarine.ApplyForce(currForce);
|
||||
|
||||
UpdatePropellerDamage(deltaTime);
|
||||
|
||||
if (item.CurrentHull != null)
|
||||
{
|
||||
item.CurrentHull.AiTarget.SoundRange = Math.Max(currForce.Length(), item.CurrentHull.AiTarget.SoundRange);
|
||||
@@ -82,7 +106,7 @@ namespace Barotrauma.Items.Components
|
||||
#if CLIENT
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
GameMain.ParticleManager.CreateParticle("bubbles", item.WorldPosition - (Vector2.UnitX * item.Rect.Width/2),
|
||||
GameMain.ParticleManager.CreateParticle("bubbles", item.WorldPosition + PropellerPos,
|
||||
-currForce / 5.0f + new Vector2(Rand.Range(-100.0f, 100.0f), Rand.Range(-50f, 50f)),
|
||||
0.0f, item.CurrentHull);
|
||||
}
|
||||
@@ -91,24 +115,69 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
voltage = 0.0f;
|
||||
}
|
||||
|
||||
private void UpdatePropellerDamage(float deltaTime)
|
||||
{
|
||||
damageTimer += deltaTime;
|
||||
if (damageTimer < 0.5f) return;
|
||||
damageTimer = 0.1f;
|
||||
|
||||
if (propellerDamage == null) return;
|
||||
Vector2 propellerWorldPos = item.WorldPosition + PropellerPos;
|
||||
foreach (Character character in Character.CharacterList)
|
||||
{
|
||||
if (character.Submarine != null || !character.Enabled || character.Removed) continue;
|
||||
|
||||
float dist = Vector2.DistanceSquared(character.WorldPosition, propellerWorldPos);
|
||||
if (dist > propellerDamage.DamageRange * propellerDamage.DamageRange) continue;
|
||||
|
||||
character.LastDamageSource = item;
|
||||
propellerDamage.DoDamage(null, character, propellerWorldPos, 1.0f, true);
|
||||
}
|
||||
}
|
||||
|
||||
partial void UpdateAnimation(float deltaTime);
|
||||
|
||||
public override void UpdateBroken(float deltaTime, Camera cam)
|
||||
{
|
||||
force = MathHelper.Lerp(force, 0.0f, 0.1f);
|
||||
}
|
||||
|
||||
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power=0.0f)
|
||||
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power = 0.0f, float signalStrength = 1.0f)
|
||||
{
|
||||
base.ReceiveSignal(stepsTaken, signal, connection, source, sender, power);
|
||||
base.ReceiveSignal(stepsTaken, signal, connection, source, sender, power, signalStrength);
|
||||
|
||||
if (connection.Name == "set_force")
|
||||
{
|
||||
float tempForce;
|
||||
if (float.TryParse(signal, NumberStyles.Float, CultureInfo.InvariantCulture, out tempForce))
|
||||
if (float.TryParse(signal, NumberStyles.Float, CultureInfo.InvariantCulture, out float tempForce))
|
||||
{
|
||||
targetForce = MathHelper.Clamp(tempForce, -100.0f, 100.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ServerWrite(NetBuffer msg, Client c, object[] extraData = null)
|
||||
{
|
||||
//force can only be adjusted at 10% intervals -> no need for more accuracy than this
|
||||
msg.WriteRangedInteger(-10, 10, (int)(targetForce / 10.0f));
|
||||
}
|
||||
|
||||
public void ServerRead(ClientNetObject type, NetBuffer msg, Client c)
|
||||
{
|
||||
float newTargetForce = msg.ReadRangedInteger(-10, 10) * 10.0f;
|
||||
|
||||
if (item.CanClientAccess(c))
|
||||
{
|
||||
if (Math.Abs(newTargetForce - targetForce) > 0.01f)
|
||||
{
|
||||
GameServer.Log(c.Character.LogName + " set the force of " + item.Name + " to " + (int)(newTargetForce) + " %", ServerLog.MessageType.ItemInteraction);
|
||||
}
|
||||
|
||||
targetForce = newTargetForce;
|
||||
}
|
||||
|
||||
//notify all clients of the changed state
|
||||
item.CreateServerEvent(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,91 +10,113 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
class FabricableItem
|
||||
{
|
||||
public readonly ItemPrefab TargetItem;
|
||||
public class RequiredItem
|
||||
{
|
||||
public readonly ItemPrefab ItemPrefab;
|
||||
public int Amount;
|
||||
public readonly float MinCondition;
|
||||
public readonly bool UseCondition;
|
||||
|
||||
//TODO: refactor this (maybe make it a struct)
|
||||
public readonly List<Tuple<ItemPrefab, int, float, bool>> RequiredItems;
|
||||
public RequiredItem(ItemPrefab itemPrefab, int amount, float minCondition, bool useCondition)
|
||||
{
|
||||
ItemPrefab = itemPrefab;
|
||||
Amount = amount;
|
||||
MinCondition = minCondition;
|
||||
UseCondition = useCondition;
|
||||
}
|
||||
}
|
||||
|
||||
public readonly ItemPrefab TargetItem;
|
||||
|
||||
public readonly string DisplayName;
|
||||
|
||||
public readonly List<RequiredItem> RequiredItems;
|
||||
|
||||
public readonly float RequiredTime;
|
||||
|
||||
public readonly float OutCondition; //Percentage-based from 0 to 1
|
||||
|
||||
public readonly List<Skill> RequiredSkills;
|
||||
|
||||
|
||||
public FabricableItem(XElement element)
|
||||
{
|
||||
string name = element.GetAttributeString("name", "");
|
||||
|
||||
TargetItem = MapEntityPrefab.Find(name) as ItemPrefab;
|
||||
|
||||
if (TargetItem == null)
|
||||
if (element.Attribute("name") != null)
|
||||
{
|
||||
return;
|
||||
string name = element.Attribute("name").Value;
|
||||
DebugConsole.ThrowError("Error in fabricable item config (" + name + ") - use item identifiers instead of names");
|
||||
TargetItem = MapEntityPrefab.Find(name) as ItemPrefab;
|
||||
if (TargetItem == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Error in fabricable item config - item prefab \"" + name + "\" not found.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
string identifier = element.GetAttributeString("identifier", "");
|
||||
TargetItem = MapEntityPrefab.Find(null, identifier) as ItemPrefab;
|
||||
if (TargetItem == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Error in fabricable item config - item prefab \"" + identifier + "\" not found.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
string displayName = element.GetAttributeString("displayname", "");
|
||||
DisplayName = string.IsNullOrEmpty(displayName) ? TargetItem.Name : TextManager.Get(displayName);
|
||||
|
||||
RequiredSkills = new List<Skill>();
|
||||
RequiredTime = element.GetAttributeFloat("requiredtime", 1.0f);
|
||||
OutCondition = element.GetAttributeFloat("outcondition", 1.0f);
|
||||
RequiredItems = new List<Tuple<ItemPrefab, int, float, bool>>();
|
||||
//Backwards compatibility for string lists
|
||||
string[] requiredItemNames = element.GetAttributeString("requireditems", "").Split(',');
|
||||
foreach (string requiredItemName in requiredItemNames)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(requiredItemName)) continue;
|
||||
|
||||
ItemPrefab requiredItem = MapEntityPrefab.Find(requiredItemName.Trim()) as ItemPrefab;
|
||||
if (requiredItem == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Error in fabricable item " + name + "! Required item \"" + requiredItemName + "\" not found.");
|
||||
continue;
|
||||
}
|
||||
|
||||
var existing = RequiredItems.Find(r => r.Item1 == requiredItem);
|
||||
if (existing == null)
|
||||
{
|
||||
RequiredItems.Add(new Tuple<ItemPrefab, int, float, bool>(requiredItem, 1, 1.0f, false));
|
||||
}
|
||||
else
|
||||
{
|
||||
RequiredItems.Remove(existing);
|
||||
RequiredItems.Add(new Tuple<ItemPrefab, int, float, bool>(requiredItem, existing.Item2 + 1, 1.0f, false));
|
||||
}
|
||||
}
|
||||
RequiredItems = new List<RequiredItem>();
|
||||
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "requiredskill":
|
||||
if (subElement.Attribute("name") != null)
|
||||
{
|
||||
DebugConsole.ThrowError("Error in fabricable item " + TargetItem.Name + "! Use skill identifiers instead of names.");
|
||||
continue;
|
||||
}
|
||||
|
||||
RequiredSkills.Add(new Skill(
|
||||
subElement.GetAttributeString("name", ""),
|
||||
subElement.GetAttributeString("identifier", ""),
|
||||
subElement.GetAttributeInt("level", 0)));
|
||||
break;
|
||||
case "item": //New system allowing for setting minimal item condition
|
||||
string requiredItemName = subElement.GetAttributeString("name", "");
|
||||
case "item":
|
||||
case "requireditem":
|
||||
string requiredItemIdentifier = subElement.GetAttributeString("identifier", "");
|
||||
if (string.IsNullOrWhiteSpace(requiredItemIdentifier))
|
||||
{
|
||||
DebugConsole.ThrowError("Error in fabricable item " + TargetItem.Name + "! One of the required items has no identifier.");
|
||||
continue;
|
||||
}
|
||||
|
||||
float minCondition = subElement.GetAttributeFloat("mincondition", 1.0f);
|
||||
//Substract mincondition from required item's condition or delete it regardless?
|
||||
bool useCondition = subElement.GetAttributeBool("usecondition", true);
|
||||
int count = subElement.GetAttributeInt("count", 1);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(requiredItemName)) continue;
|
||||
|
||||
ItemPrefab requiredItem = MapEntityPrefab.Find(requiredItemName.Trim()) as ItemPrefab;
|
||||
ItemPrefab requiredItem = MapEntityPrefab.Find(null, requiredItemIdentifier.Trim()) as ItemPrefab;
|
||||
if (requiredItem == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Error in fabricable item " + name + "! Required item \"" + requiredItemName + "\" not found.");
|
||||
DebugConsole.ThrowError("Error in fabricable item " + TargetItem.Name + "! Required item \"" + requiredItemIdentifier + "\" not found.");
|
||||
continue;
|
||||
}
|
||||
|
||||
var existing = RequiredItems.Find(r => r.Item1 == requiredItem);
|
||||
var existing = RequiredItems.Find(r => r.ItemPrefab == requiredItem);
|
||||
if (existing == null)
|
||||
{
|
||||
RequiredItems.Add(new Tuple<ItemPrefab, int, float, bool>(requiredItem, count, minCondition, useCondition));
|
||||
RequiredItems.Add(new RequiredItem(requiredItem, count, minCondition, useCondition));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
RequiredItems.Remove(existing);
|
||||
RequiredItems.Add(new Tuple<ItemPrefab, int, float, bool>(requiredItem, existing.Item2 + count, minCondition, useCondition));
|
||||
RequiredItems.Add(new RequiredItem(requiredItem, existing.Amount + count, minCondition, useCondition));
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -106,15 +128,20 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
partial class Fabricator : Powered, IServerSerializable, IClientSerializable
|
||||
{
|
||||
public const float SkillIncreaseMultiplier = 0.5f;
|
||||
|
||||
private List<FabricableItem> fabricableItems;
|
||||
|
||||
private FabricableItem fabricatedItem;
|
||||
private float timeUntilReady;
|
||||
|
||||
//used for checking if contained items have changed
|
||||
//(in which case we need to recheck which items can be fabricated)
|
||||
private Item[] prevContainedItems;
|
||||
private float requiredTime;
|
||||
|
||||
private Character user;
|
||||
|
||||
private ItemContainer inputContainer, outputContainer;
|
||||
|
||||
private float progressState;
|
||||
|
||||
public Fabricator(Item item, XElement element)
|
||||
: base(item, element)
|
||||
{
|
||||
@@ -138,89 +165,88 @@ namespace Barotrauma.Items.Components
|
||||
InitProjSpecific();
|
||||
}
|
||||
|
||||
public override void OnItemLoaded()
|
||||
{
|
||||
var containers = item.GetComponents<ItemContainer>().ToList();
|
||||
if (containers.Count < 2)
|
||||
{
|
||||
DebugConsole.ThrowError("Error in item \"" + item.Name + "\": Fabricators must have two ItemContainer components!");
|
||||
return;
|
||||
}
|
||||
|
||||
inputContainer = containers[0];
|
||||
outputContainer = containers[1];
|
||||
|
||||
foreach (FabricableItem fabricableItem in fabricableItems)
|
||||
{
|
||||
int ingredientCount = fabricableItem.RequiredItems.Sum(it => it.Amount);
|
||||
if (ingredientCount > inputContainer.Capacity)
|
||||
{
|
||||
DebugConsole.ThrowError("Error in item \"" + item.Name + "\": There's not enough room in the input inventory for the ingredients of \"" + fabricableItem.TargetItem.Name + "\"!");
|
||||
}
|
||||
}
|
||||
|
||||
OnItemLoadedProjSpecific();
|
||||
}
|
||||
|
||||
partial void OnItemLoadedProjSpecific();
|
||||
|
||||
|
||||
partial void InitProjSpecific();
|
||||
|
||||
public override bool Select(Character character)
|
||||
{
|
||||
CheckFabricableItems(character);
|
||||
#if CLIENT
|
||||
if (itemList.Selected != null)
|
||||
{
|
||||
SelectItem(itemList.Selected, itemList.Selected.UserData);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
SelectProjSpecific(character);
|
||||
return base.Select(character);
|
||||
}
|
||||
|
||||
partial void SelectProjSpecific(Character character);
|
||||
|
||||
public override bool Pick(Character picker)
|
||||
{
|
||||
return (picker != null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// check which of the items can be fabricated by the character
|
||||
/// and update the text colors of the item list accordingly
|
||||
/// </summary>
|
||||
private void CheckFabricableItems(Character character)
|
||||
{
|
||||
#if CLIENT
|
||||
foreach (GUIComponent child in itemList.children)
|
||||
{
|
||||
var itemPrefab = child.UserData as FabricableItem;
|
||||
if (itemPrefab == null) continue;
|
||||
|
||||
bool canBeFabricated = CanBeFabricated(itemPrefab, character);
|
||||
|
||||
|
||||
child.GetChild<GUITextBlock>().TextColor = Color.White * (canBeFabricated ? 1.0f : 0.5f);
|
||||
child.GetChild<GUIImage>().Color = itemPrefab.TargetItem.SpriteColor * (canBeFabricated ? 1.0f : 0.5f);
|
||||
|
||||
}
|
||||
#endif
|
||||
|
||||
var itemContainer = item.GetComponent<ItemContainer>();
|
||||
prevContainedItems = new Item[itemContainer.Inventory.Items.Length];
|
||||
itemContainer.Inventory.Items.CopyTo(prevContainedItems, 0);
|
||||
}
|
||||
|
||||
private void StartFabricating(FabricableItem selectedItem, Character user = null)
|
||||
|
||||
private void StartFabricating(FabricableItem selectedItem, Character user)
|
||||
{
|
||||
if (selectedItem == null) return;
|
||||
|
||||
if (user != null)
|
||||
{
|
||||
GameServer.Log(user.LogName + " started fabricating " + selectedItem.TargetItem.Name + " in " + item.Name, ServerLog.MessageType.ItemInteraction);
|
||||
GameServer.Log(user.LogName + " started fabricating " + selectedItem.DisplayName + " in " + item.Name, ServerLog.MessageType.ItemInteraction);
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
itemList.Enabled = false;
|
||||
|
||||
activateButton.Text = "Cancel";
|
||||
activateButton.Text = TextManager.Get("FabricatorCancel");
|
||||
#endif
|
||||
|
||||
MoveIngredientsToInputContainer(selectedItem);
|
||||
|
||||
fabricatedItem = selectedItem;
|
||||
IsActive = true;
|
||||
|
||||
timeUntilReady = fabricatedItem.RequiredTime;
|
||||
|
||||
var containers = item.GetComponents<ItemContainer>();
|
||||
containers[0].Inventory.Locked = true;
|
||||
containers[1].Inventory.Locked = true;
|
||||
this.user = user;
|
||||
|
||||
requiredTime = GetRequiredTime(fabricatedItem, user);
|
||||
timeUntilReady = requiredTime;
|
||||
|
||||
inputContainer.Inventory.Locked = true;
|
||||
outputContainer.Inventory.Locked = true;
|
||||
|
||||
currPowerConsumption = powerConsumption;
|
||||
currPowerConsumption *= MathHelper.Lerp(2.0f, 1.0f, item.Condition / 100.0f);
|
||||
}
|
||||
|
||||
private void CancelFabricating(Character user = null)
|
||||
{
|
||||
if (fabricatedItem != null && user != null)
|
||||
{
|
||||
GameServer.Log(user.LogName + " cancelled the fabrication of " + fabricatedItem.TargetItem.Name + " in " + item.Name, ServerLog.MessageType.ItemInteraction);
|
||||
GameServer.Log(user.LogName + " cancelled the fabrication of " + fabricatedItem.DisplayName + " in " + item.Name, ServerLog.MessageType.ItemInteraction);
|
||||
}
|
||||
|
||||
IsActive = false;
|
||||
fabricatedItem = null;
|
||||
this.user = null;
|
||||
|
||||
currPowerConsumption = 0.0f;
|
||||
|
||||
@@ -228,100 +254,178 @@ namespace Barotrauma.Items.Components
|
||||
itemList.Enabled = true;
|
||||
if (activateButton != null)
|
||||
{
|
||||
activateButton.Text = "Create";
|
||||
activateButton.Text = TextManager.Get("FabricatorCreate");
|
||||
}
|
||||
if (progressBar != null) progressBar.BarSize = 0.0f;
|
||||
#endif
|
||||
progressState = 0.0f;
|
||||
|
||||
timeUntilReady = 0.0f;
|
||||
|
||||
var containers = item.GetComponents<ItemContainer>();
|
||||
containers[0].Inventory.Locked = false;
|
||||
containers[1].Inventory.Locked = false;
|
||||
inputContainer.Inventory.Locked = false;
|
||||
outputContainer.Inventory.Locked = false;
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
if (fabricatedItem == null)
|
||||
if (fabricatedItem == null || !CanBeFabricated(fabricatedItem))
|
||||
{
|
||||
CancelFabricating();
|
||||
return;
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
if (progressBar != null)
|
||||
{
|
||||
progressBar.BarSize = fabricatedItem == null ? 0.0f : (fabricatedItem.RequiredTime - timeUntilReady) / fabricatedItem.RequiredTime;
|
||||
}
|
||||
#endif
|
||||
progressState = fabricatedItem == null ? 0.0f : (requiredTime - timeUntilReady) / requiredTime;
|
||||
|
||||
if (voltage < minVoltage) return;
|
||||
if (voltage < minVoltage) { return; }
|
||||
|
||||
ApplyStatusEffects(ActionType.OnActive, deltaTime, null);
|
||||
|
||||
if (powerConsumption == 0) voltage = 1.0f;
|
||||
|
||||
timeUntilReady -= deltaTime*voltage;
|
||||
if (powerConsumption <= 0) { voltage = 1.0f; }
|
||||
|
||||
timeUntilReady -= deltaTime * voltage;
|
||||
voltage -= deltaTime * 10.0f;
|
||||
|
||||
if (timeUntilReady > 0.0f) return;
|
||||
if (timeUntilReady > 0.0f) { return; }
|
||||
|
||||
var containers = item.GetComponents<ItemContainer>();
|
||||
if (containers.Count < 2)
|
||||
var availableIngredients = GetAvailableIngredients();
|
||||
foreach (FabricableItem.RequiredItem ingredient in fabricatedItem.RequiredItems)
|
||||
{
|
||||
DebugConsole.ThrowError("Error while fabricating a new item: fabricators must have two ItemContainer components");
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (Tuple<ItemPrefab, int, float, bool> ip in fabricatedItem.RequiredItems)
|
||||
{
|
||||
for (int i = 0; i < ip.Item2; i++)
|
||||
for (int i = 0; i < ingredient.Amount; i++)
|
||||
{
|
||||
var requiredItem = containers[0].Inventory.Items.FirstOrDefault(it => it != null && it.Prefab == ip.Item1 && it.Condition >= ip.Item1.Health * ip.Item3);
|
||||
var requiredItem = inputContainer.Inventory.Items.FirstOrDefault(it => it != null && it.Prefab == ingredient.ItemPrefab && it.Condition >= ingredient.ItemPrefab.Health * ingredient.MinCondition);
|
||||
if (requiredItem == null) continue;
|
||||
|
||||
//Item4 = use condition bool
|
||||
if (ip.Item4 && requiredItem.Condition - ip.Item1.Health * ip.Item3 > 0.0f) //Leave it behind with reduced condition if it has enough to stay above 0
|
||||
if (ingredient.UseCondition && requiredItem.Condition - ingredient.ItemPrefab.Health * ingredient.MinCondition > 0.0f) //Leave it behind with reduced condition if it has enough to stay above 0
|
||||
{
|
||||
requiredItem.Condition -= ip.Item1.Health * ip.Item3;
|
||||
requiredItem.Condition -= ingredient.ItemPrefab.Health * ingredient.MinCondition;
|
||||
continue;
|
||||
}
|
||||
Entity.Spawner.AddToRemoveQueue(requiredItem);
|
||||
containers[0].Inventory.RemoveItem(requiredItem);
|
||||
inputContainer.Inventory.RemoveItem(requiredItem);
|
||||
}
|
||||
}
|
||||
|
||||
if (containers[1].Inventory.Items.All(i => i != null))
|
||||
if (outputContainer.Inventory.Items.All(i => i != null))
|
||||
{
|
||||
Entity.Spawner.AddToSpawnQueue(fabricatedItem.TargetItem, item.Position, item.Submarine, fabricatedItem.TargetItem.Health * fabricatedItem.OutCondition);
|
||||
}
|
||||
else
|
||||
{
|
||||
Entity.Spawner.AddToSpawnQueue(fabricatedItem.TargetItem, containers[1].Inventory, fabricatedItem.TargetItem.Health * fabricatedItem.OutCondition);
|
||||
Entity.Spawner.AddToSpawnQueue(fabricatedItem.TargetItem, outputContainer.Inventory, fabricatedItem.TargetItem.Health * fabricatedItem.OutCondition);
|
||||
}
|
||||
|
||||
if (GameMain.Client == null && user != null)
|
||||
{
|
||||
foreach (Skill skill in fabricatedItem.RequiredSkills)
|
||||
{
|
||||
user.Info.IncreaseSkillLevel(skill.Identifier, skill.Level / 100.0f * SkillIncreaseMultiplier, user.WorldPosition + Vector2.UnitY * 150.0f);
|
||||
}
|
||||
}
|
||||
|
||||
CancelFabricating(null);
|
||||
}
|
||||
|
||||
private bool CanBeFabricated(FabricableItem fabricableItem, Character user)
|
||||
private bool CanBeFabricated(FabricableItem fabricableItem)
|
||||
{
|
||||
if (fabricableItem == null) return false;
|
||||
if (fabricableItem == null) { return false; }
|
||||
List<Item> availableIngredients = GetAvailableIngredients();
|
||||
return CanBeFabricated(fabricableItem, availableIngredients);
|
||||
}
|
||||
|
||||
if (user != null &&
|
||||
fabricableItem.RequiredSkills.Any(skill => user.GetSkillLevel(skill.Name) < skill.Level))
|
||||
private bool CanBeFabricated(FabricableItem fabricableItem, IEnumerable<Item> availableIngredients)
|
||||
{
|
||||
if (fabricableItem == null) { return false; }
|
||||
foreach (FabricableItem.RequiredItem requiredItem in fabricableItem.RequiredItems)
|
||||
{
|
||||
return false;
|
||||
if (availableIngredients.Count(it => IsItemValidIngredient(it, requiredItem)) < requiredItem.Amount)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
ItemContainer container = item.GetComponent<ItemContainer>();
|
||||
foreach (Tuple<ItemPrefab, int, float, bool> ip in fabricableItem.RequiredItems)
|
||||
{
|
||||
if (Array.FindAll(container.Inventory.Items, it => it != null && it.Prefab == ip.Item1 && it.Condition >= ip.Item1.Health * ip.Item3).Length < ip.Item2) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private float GetRequiredTime(FabricableItem fabricableItem, Character user)
|
||||
{
|
||||
float degreeOfSuccess = DegreeOfSuccess(user, fabricableItem.RequiredSkills);
|
||||
|
||||
float t = degreeOfSuccess < 0.5f ? degreeOfSuccess * degreeOfSuccess : degreeOfSuccess * 2;
|
||||
|
||||
//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 / MathHelper.Clamp(t, 0.01f, 2.0f);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a list of all items available in the input container and linked containers
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private List<Item> GetAvailableIngredients()
|
||||
{
|
||||
List<Item> availableIngredients = new List<Item>();
|
||||
availableIngredients.AddRange(inputContainer.Inventory.Items.Where(it => it != null));
|
||||
foreach (MapEntity linkedTo in item.linkedTo)
|
||||
{
|
||||
if (linkedTo is Item linkedItem)
|
||||
{
|
||||
var itemContainer = linkedItem.GetComponent<ItemContainer>();
|
||||
if (itemContainer == null) { continue; }
|
||||
|
||||
var deconstructor = linkedItem.GetComponent<Deconstructor>();
|
||||
if (deconstructor != null)
|
||||
{
|
||||
itemContainer = deconstructor.OutputContainer;
|
||||
}
|
||||
|
||||
availableIngredients.AddRange(itemContainer.Inventory.Items.Where(it => it != null));
|
||||
}
|
||||
}
|
||||
return availableIngredients;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Move the items required for fabrication into the input container.
|
||||
/// The method assumes that all the required ingredients are available either in the input container or linked containers.
|
||||
/// </summary>
|
||||
private void MoveIngredientsToInputContainer(FabricableItem targetItem)
|
||||
{
|
||||
//required ingredients that are already present in the input container
|
||||
List<Item> usedItems = new List<Item>();
|
||||
|
||||
var availableIngredients = GetAvailableIngredients();
|
||||
foreach (var requiredItem in targetItem.RequiredItems)
|
||||
{
|
||||
for (int i = 0; i < requiredItem.Amount; i++)
|
||||
{
|
||||
var matchingItem = availableIngredients.Find(it => !usedItems.Contains(it) && IsItemValidIngredient(it, requiredItem));
|
||||
if (matchingItem == null) { continue; }
|
||||
|
||||
if (matchingItem.ParentInventory == inputContainer.Inventory)
|
||||
{
|
||||
//already in input container, all good
|
||||
usedItems.Add(matchingItem);
|
||||
}
|
||||
else //in another inventory, we need to move the item
|
||||
{
|
||||
if (inputContainer.Inventory.Items.All(it => it != null))
|
||||
{
|
||||
var unneededItem = inputContainer.Inventory.Items.FirstOrDefault(it => !usedItems.Contains(it));
|
||||
unneededItem?.Drop();
|
||||
}
|
||||
inputContainer.Inventory.TryPutItem(matchingItem, user: null, createNetworkEvent: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsItemValidIngredient(Item item, FabricableItem.RequiredItem requiredItem)
|
||||
{
|
||||
return
|
||||
item != null &&
|
||||
item.prefab == requiredItem.ItemPrefab &&
|
||||
item.Condition / item.Prefab.Health >= requiredItem.MinCondition;
|
||||
}
|
||||
|
||||
public void ServerRead(ClientNetObject type, NetBuffer msg, Client c)
|
||||
{
|
||||
int itemIndex = msg.ReadRangedInteger(-1, fabricableItems.Count - 1);
|
||||
@@ -351,6 +455,8 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
int itemIndex = fabricatedItem == null ? -1 : fabricableItems.IndexOf(fabricatedItem);
|
||||
msg.WriteRangedInteger(-1, fabricableItems.Count - 1, itemIndex);
|
||||
UInt16 userID = fabricatedItem == null || user == null ? (UInt16)0 : user.ID;
|
||||
msg.Write(userID);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
|
||||
@@ -10,11 +11,16 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
public float? Oxygen;
|
||||
public float? Water;
|
||||
|
||||
public bool Distort;
|
||||
public float DistortionTimer;
|
||||
}
|
||||
|
||||
private DateTime resetDataTime;
|
||||
|
||||
bool hasPower;
|
||||
private bool hasPower;
|
||||
|
||||
private Dictionary<Hull, HullData> hullDatas;
|
||||
|
||||
[Editable(ToolTip = "Does the machine require inputs from water detectors in order to show the water levels inside rooms."), Serialize(false, true)]
|
||||
public bool RequireWaterDetectors
|
||||
@@ -30,35 +36,42 @@ namespace Barotrauma.Items.Components
|
||||
set;
|
||||
}
|
||||
|
||||
[Editable(ToolTip = "Should damaged walls be displayed by the machine."), Serialize(false, true)]
|
||||
[Editable(ToolTip = "Should damaged walls be displayed by the machine."), Serialize(true, true)]
|
||||
public bool ShowHullIntegrity
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
|
||||
private Dictionary<Hull, HullData> hullDatas;
|
||||
|
||||
public MiniMap(Item item, XElement element)
|
||||
: base(item, element)
|
||||
{
|
||||
IsActive = true;
|
||||
|
||||
hullDatas = new Dictionary<Hull, HullData>();
|
||||
InitProjSpecific(element);
|
||||
}
|
||||
|
||||
|
||||
partial void InitProjSpecific(XElement element);
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
//periodically reset all hull data
|
||||
//(so that outdated hull info won't be shown if detectors stop sending signals)
|
||||
if (DateTime.Now > resetDataTime)
|
||||
{
|
||||
hullDatas.Clear();
|
||||
foreach (HullData hullData in hullDatas.Values)
|
||||
{
|
||||
if (!hullData.Distort)
|
||||
{
|
||||
hullData.Oxygen = null;
|
||||
hullData.Water = null;
|
||||
}
|
||||
}
|
||||
resetDataTime = DateTime.Now + new TimeSpan(0, 0, 1);
|
||||
}
|
||||
|
||||
currPowerConsumption = powerConsumption;
|
||||
currPowerConsumption *= MathHelper.Lerp(2.0f, 1.0f, item.Condition / 100.0f);
|
||||
|
||||
hasPower = voltage > minVoltage;
|
||||
if (hasPower)
|
||||
@@ -71,28 +84,24 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public override bool Pick(Character picker)
|
||||
{
|
||||
if (picker == null) return false;
|
||||
|
||||
//picker.SelectedConstruction = item;
|
||||
|
||||
return true;
|
||||
return picker != null;
|
||||
}
|
||||
|
||||
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power = 0)
|
||||
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power = 0, float signalStrength = 1.0f)
|
||||
{
|
||||
base.ReceiveSignal(stepsTaken, signal, connection, source, sender, power);
|
||||
base.ReceiveSignal(stepsTaken, signal, connection, source, sender, power, signalStrength);
|
||||
|
||||
if (sender == null || sender.CurrentHull == null) return;
|
||||
if (source == null || source.CurrentHull == null) return;
|
||||
|
||||
Hull senderHull = sender.CurrentHull;
|
||||
|
||||
HullData hullData;
|
||||
if (!hullDatas.TryGetValue(senderHull, out hullData))
|
||||
Hull sourceHull = source.CurrentHull;
|
||||
if (!hullDatas.TryGetValue(sourceHull, out HullData hullData))
|
||||
{
|
||||
hullData = new HullData();
|
||||
hullDatas.Add(senderHull, hullData);
|
||||
hullDatas.Add(sourceHull, hullData);
|
||||
}
|
||||
|
||||
if (hullData.Distort) return;
|
||||
|
||||
switch (connection.Name)
|
||||
{
|
||||
case "water_data_in":
|
||||
@@ -103,7 +112,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
else
|
||||
{
|
||||
hullData.Water = Math.Min(senderHull.WaterVolume / senderHull.Volume, 1.0f);
|
||||
hullData.Water = Math.Min(sourceHull.WaterVolume / sourceHull.Volume, 1.0f);
|
||||
}
|
||||
break;
|
||||
case "oxygen_data_in":
|
||||
|
||||
@@ -9,9 +9,7 @@ namespace Barotrauma.Items.Components
|
||||
class OxygenGenerator : Powered
|
||||
{
|
||||
private float powerDownTimer;
|
||||
|
||||
private bool running;
|
||||
|
||||
|
||||
private float generatedAmount;
|
||||
|
||||
private List<Vent> ventList;
|
||||
@@ -43,24 +41,29 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
CurrFlow = 0.0f;
|
||||
currPowerConsumption = powerConsumption;
|
||||
//consume more power when in a bad condition
|
||||
currPowerConsumption *= MathHelper.Lerp(2.0f, 1.0f, item.Condition / 100.0f);
|
||||
|
||||
if (powerConsumption <= 0.0f)
|
||||
{
|
||||
voltage = 1.0f;
|
||||
}
|
||||
|
||||
if (item.CurrentHull == null) return;
|
||||
|
||||
if (voltage < minVoltage)
|
||||
{
|
||||
powerDownTimer += deltaTime;
|
||||
running = false;
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
powerDownTimer = 0.0f;
|
||||
}
|
||||
|
||||
running = true;
|
||||
|
||||
|
||||
CurrFlow = Math.Min(voltage, 1.0f) * generatedAmount * 100.0f;
|
||||
//item.CurrentHull.Oxygen += CurrFlow * deltaTime;
|
||||
//less effective when in bad condition
|
||||
CurrFlow *= MathHelper.Lerp(0.5f, 1.0f, item.Condition / 100.0f);
|
||||
|
||||
UpdateVents(CurrFlow);
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
private float? targetLevel;
|
||||
|
||||
public Hull hull1;
|
||||
private bool hasPower;
|
||||
|
||||
[Serialize(0.0f, true)]
|
||||
public float FlowPercentage
|
||||
@@ -34,7 +34,7 @@ namespace Barotrauma.Items.Components
|
||||
set { maxFlow = value; }
|
||||
}
|
||||
|
||||
float currFlow;
|
||||
private float currFlow;
|
||||
public float CurrFlow
|
||||
{
|
||||
get
|
||||
@@ -43,85 +43,59 @@ namespace Barotrauma.Items.Components
|
||||
return Math.Abs(currFlow);
|
||||
}
|
||||
}
|
||||
|
||||
public override bool IsActive
|
||||
{
|
||||
get
|
||||
{
|
||||
return base.IsActive;
|
||||
}
|
||||
set
|
||||
{
|
||||
base.IsActive = value;
|
||||
|
||||
#if CLIENT
|
||||
if (isActiveTickBox != null) isActiveTickBox.Selected = value;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public Pump(Item item, XElement element)
|
||||
: base(item, element)
|
||||
{
|
||||
GetHull();
|
||||
|
||||
InitProjSpecific();
|
||||
}
|
||||
|
||||
partial void InitProjSpecific();
|
||||
|
||||
public override void Move(Vector2 amount)
|
||||
{
|
||||
base.Move(amount);
|
||||
|
||||
GetHull();
|
||||
}
|
||||
|
||||
public override void OnMapLoaded()
|
||||
{
|
||||
GetHull();
|
||||
InitProjSpecific(element);
|
||||
}
|
||||
|
||||
partial void InitProjSpecific(XElement element);
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
currFlow = 0.0f;
|
||||
hasPower = false;
|
||||
|
||||
if (targetLevel != null)
|
||||
{
|
||||
float hullPercentage = 0.0f;
|
||||
if (hull1 != null) hullPercentage = (hull1.WaterVolume / hull1.Volume) * 100.0f;
|
||||
if (item.CurrentHull != null) hullPercentage = (item.CurrentHull.WaterVolume / item.CurrentHull.Volume) * 100.0f;
|
||||
FlowPercentage = ((float)targetLevel - hullPercentage) * 10.0f;
|
||||
}
|
||||
|
||||
currPowerConsumption = powerConsumption * Math.Abs(flowPercentage / 100.0f);
|
||||
//pumps consume more power when in a bad condition
|
||||
currPowerConsumption *= MathHelper.Lerp(2.0f, 1.0f, item.Condition / 100.0f);
|
||||
|
||||
if (voltage < minVoltage) return;
|
||||
|
||||
UpdateProjSpecific(deltaTime);
|
||||
|
||||
hasPower = true;
|
||||
|
||||
ApplyStatusEffects(ActionType.OnActive, deltaTime, null);
|
||||
|
||||
//check the hull if the item is movable
|
||||
if (item.body != null) GetHull();
|
||||
if (hull1 == null) return;
|
||||
if (item.CurrentHull == null) { return; }
|
||||
|
||||
float powerFactor = currPowerConsumption <= 0.0f ? 1.0f : voltage;
|
||||
|
||||
currFlow = flowPercentage / 100.0f * maxFlow * powerFactor;
|
||||
//less effective when in a bad condition
|
||||
currFlow *= MathHelper.Lerp(0.5f, 1.0f, item.Condition / 100.0f);
|
||||
|
||||
hull1.WaterVolume += currFlow;
|
||||
if (hull1.WaterVolume > hull1.Volume) hull1.Pressure += 0.5f;
|
||||
item.CurrentHull.WaterVolume += currFlow;
|
||||
if (item.CurrentHull.WaterVolume > item.CurrentHull.Volume) { item.CurrentHull.Pressure += 0.5f; }
|
||||
|
||||
voltage = 0.0f;
|
||||
}
|
||||
|
||||
private void GetHull()
|
||||
{
|
||||
hull1 = Hull.FindHull(item.WorldPosition, item.CurrentHull);
|
||||
}
|
||||
partial void UpdateProjSpecific(float deltaTime);
|
||||
|
||||
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power=0.0f)
|
||||
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power = 0.0f, float signalStrength = 1.0f)
|
||||
{
|
||||
base.ReceiveSignal(stepsTaken, signal, connection, source, sender, power);
|
||||
|
||||
base.ReceiveSignal(stepsTaken, signal, connection, source, sender, power, signalStrength);
|
||||
|
||||
if (connection.Name == "toggle")
|
||||
{
|
||||
IsActive = !IsActive;
|
||||
@@ -132,24 +106,43 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
else if (connection.Name == "set_speed")
|
||||
{
|
||||
float tempSpeed;
|
||||
if (float.TryParse(signal, NumberStyles.Any, CultureInfo.InvariantCulture, out tempSpeed))
|
||||
if (float.TryParse(signal, NumberStyles.Any, CultureInfo.InvariantCulture, out float tempSpeed))
|
||||
{
|
||||
flowPercentage = MathHelper.Clamp(tempSpeed, -100.0f, 100.0f);
|
||||
}
|
||||
}
|
||||
else if (connection.Name == "set_targetlevel")
|
||||
{
|
||||
float tempTarget;
|
||||
if (float.TryParse(signal, NumberStyles.Any, CultureInfo.InvariantCulture, out tempTarget))
|
||||
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 + 100.0f) / 2.0f, 0.0f, 100.0f);
|
||||
}
|
||||
}
|
||||
|
||||
if (!IsActive) currPowerConsumption = 0.0f;
|
||||
}
|
||||
|
||||
public override bool AIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective)
|
||||
{
|
||||
if (GameMain.Client != null) return false;
|
||||
|
||||
if (objective.Option.ToLowerInvariant() == "stoppumping")
|
||||
{
|
||||
if (FlowPercentage > 0.0f) item.CreateServerEvent(this);
|
||||
FlowPercentage = 0.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!IsActive || FlowPercentage > -100.0f)
|
||||
{
|
||||
item.CreateServerEvent(this);
|
||||
}
|
||||
IsActive = true;
|
||||
FlowPercentage = -100.0f;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void ServerRead(ClientNetObject type, Lidgren.Network.NetBuffer msg, Client c)
|
||||
{
|
||||
float newFlowPercentage = msg.ReadRangedInteger(-10, 10) * 10.0f;
|
||||
|
||||
@@ -1,162 +0,0 @@
|
||||
using Barotrauma.Networking;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
partial class Radar : Powered, IServerSerializable, IClientSerializable
|
||||
{
|
||||
private float range;
|
||||
|
||||
private float pingState;
|
||||
|
||||
private readonly Sprite pingCircle, screenOverlay;
|
||||
|
||||
private readonly Sprite radarBlip;
|
||||
|
||||
private float prevPingRadius;
|
||||
|
||||
float prevPassivePingRadius;
|
||||
|
||||
private Vector2 center;
|
||||
private float displayRadius;
|
||||
private float displayScale;
|
||||
|
||||
private float displayBorderSize;
|
||||
|
||||
[Serialize(10000.0f, false)]
|
||||
public float Range
|
||||
{
|
||||
get { return range; }
|
||||
set { range = MathHelper.Clamp(value, 0.0f, 100000.0f); }
|
||||
}
|
||||
|
||||
[Serialize(false, false)]
|
||||
public bool DetectSubmarineWalls
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public override bool IsActive
|
||||
{
|
||||
get
|
||||
{
|
||||
return base.IsActive;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
base.IsActive = value;
|
||||
#if CLIENT
|
||||
if (isActiveTickBox != null) isActiveTickBox.Selected = value;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
public Radar(Item item, XElement element)
|
||||
: base(item, element)
|
||||
{
|
||||
#if CLIENT
|
||||
radarBlips = new List<RadarBlip>();
|
||||
#endif
|
||||
|
||||
displayBorderSize = element.GetAttributeFloat("displaybordersize", 0.0f);
|
||||
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "pingcircle":
|
||||
pingCircle = new Sprite(subElement);
|
||||
break;
|
||||
case "screenoverlay":
|
||||
screenOverlay = new Sprite(subElement);
|
||||
break;
|
||||
case "blip":
|
||||
radarBlip = new Sprite(subElement);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
isActiveTickBox = new GUITickBox(new Rectangle(0, 0, 20, 20), "Active Sonar", Alignment.TopLeft, GuiFrame);
|
||||
isActiveTickBox.OnSelected = (GUITickBox box) =>
|
||||
{
|
||||
if (GameMain.Server != null)
|
||||
{
|
||||
item.CreateServerEvent(this);
|
||||
}
|
||||
else if (GameMain.Client != null)
|
||||
{
|
||||
item.CreateClientEvent(this);
|
||||
correctionTimer = CorrectionDelay;
|
||||
}
|
||||
IsActive = box.Selected;
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
GuiFrame.CanBeFocused = false;
|
||||
#endif
|
||||
|
||||
IsActive = false;
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
currPowerConsumption = powerConsumption;
|
||||
|
||||
UpdateOnActiveEffects(deltaTime);
|
||||
|
||||
if (voltage >= minVoltage || powerConsumption <= 0.0f)
|
||||
{
|
||||
pingState = pingState + deltaTime * 0.5f;
|
||||
if (pingState > 1.0f)
|
||||
{
|
||||
if (item.CurrentHull != null) item.CurrentHull.AiTarget.SoundRange = Math.Max(Range * pingState, item.CurrentHull.AiTarget.SoundRange);
|
||||
item.Use(deltaTime);
|
||||
pingState = 0.0f;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
pingState = 0.0f;
|
||||
}
|
||||
|
||||
Voltage -= deltaTime;
|
||||
}
|
||||
|
||||
public override bool Use(float deltaTime, Character character = null)
|
||||
{
|
||||
return pingState > 1.0f;
|
||||
}
|
||||
|
||||
protected override void RemoveComponentSpecific()
|
||||
{
|
||||
if (pingCircle!=null) pingCircle.Remove();
|
||||
if (screenOverlay != null) screenOverlay.Remove();
|
||||
}
|
||||
|
||||
public void ServerRead(ClientNetObject type, Lidgren.Network.NetBuffer msg, Barotrauma.Networking.Client c)
|
||||
{
|
||||
bool isActive = msg.ReadBoolean();
|
||||
|
||||
if (!item.CanClientAccess(c)) return;
|
||||
|
||||
IsActive = isActive;
|
||||
#if CLIENT
|
||||
isActiveTickBox.Selected = IsActive;
|
||||
#endif
|
||||
|
||||
item.CreateServerEvent(this);
|
||||
}
|
||||
|
||||
public void ServerWrite(Lidgren.Network.NetBuffer msg, Barotrauma.Networking.Client c, object[] extraData = null)
|
||||
{
|
||||
msg.Write(IsActive);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,80 +8,99 @@ using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
partial class Reactor : Powered, IDrawableComponent, IServerSerializable, IClientSerializable
|
||||
partial class Reactor : Powered, IServerSerializable, IClientSerializable
|
||||
{
|
||||
const float NetworkUpdateInterval = 0.5f;
|
||||
|
||||
//the rate at which the reactor is being run un
|
||||
//higher rates generate more power (and heat)
|
||||
//the rate at which the reactor is being run on (higher rate -> higher temperature)
|
||||
private float fissionRate;
|
||||
|
||||
//the rate at which the heat is being dissipated
|
||||
private float coolingRate;
|
||||
|
||||
|
||||
//how much of the generated steam is used to spin the turbines and generate power
|
||||
private float turbineOutput;
|
||||
|
||||
private float temperature;
|
||||
|
||||
private Client BlameOnBroken;
|
||||
|
||||
|
||||
//is automatic temperature control on
|
||||
//(adjusts the cooling rate automatically to keep the
|
||||
//(adjusts the fission rate and turbine output automatically to keep the
|
||||
//amount of power generated balanced with the load)
|
||||
private bool autoTemp;
|
||||
|
||||
//the temperature after which fissionrate is automatically
|
||||
//turned down and cooling increased
|
||||
private float shutDownTemp;
|
||||
private Client BlameOnBroken;
|
||||
|
||||
private float fireTemp, meltDownTemp, meltDownDelay;
|
||||
//automatical adjustment to the power output when
|
||||
//turbine output and temperature are in the optimal range
|
||||
private float autoAdjustAmount;
|
||||
|
||||
private float fuelConsumptionRate;
|
||||
|
||||
private float meltDownTimer;
|
||||
private float meltDownTimer, meltDownDelay;
|
||||
private float fireTimer, fireDelay;
|
||||
|
||||
//how much power is provided to the grid per 1 temperature unit
|
||||
private float powerPerTemp;
|
||||
private float maxPowerOutput;
|
||||
|
||||
private float load;
|
||||
|
||||
private bool unsentChanges;
|
||||
private float sendUpdateTimer;
|
||||
|
||||
private Character lastUser;
|
||||
private float degreeOfSuccess;
|
||||
|
||||
private float? nextServerLogWriteTime;
|
||||
private float lastServerLogWriteTime;
|
||||
|
||||
[Editable(ToolTip = "The temperature at which the reactor melts down."), Serialize(9500.0f, true)]
|
||||
public float MeltDownTemp
|
||||
private Vector2 optimalTemperature, allowedTemperature;
|
||||
private Vector2 optimalFissionRate, allowedFissionRate;
|
||||
private Vector2 optimalTurbineOutput, allowedTurbineOutput;
|
||||
|
||||
private bool shutDown;
|
||||
|
||||
const float AIUpdateInterval = 1.0f;
|
||||
private float aiUpdateTimer;
|
||||
|
||||
private Character lastUser;
|
||||
private Character LastUser
|
||||
{
|
||||
get { return meltDownTemp; }
|
||||
set
|
||||
get { return lastUser; }
|
||||
set
|
||||
{
|
||||
meltDownTemp = Math.Max(0.0f, value);
|
||||
if (lastUser == value) return;
|
||||
lastUser = value;
|
||||
degreeOfSuccess = lastUser == null ? 0.0f : DegreeOfSuccess(lastUser);
|
||||
}
|
||||
}
|
||||
|
||||
[Serialize(30.0f, true)]
|
||||
|
||||
[Editable(0.0f, float.MaxValue, ToolTip = "How much power (kW) the reactor generates when operating at full capacity."), Serialize(10000.0f, true)]
|
||||
public float MaxPowerOutput
|
||||
{
|
||||
get { return maxPowerOutput; }
|
||||
set
|
||||
{
|
||||
maxPowerOutput = Math.Max(0.0f, value);
|
||||
}
|
||||
}
|
||||
|
||||
[Editable(0.0f, float.MaxValue, ToolTip = "How long the temperature has to stay critical until a meltdown occurs."), Serialize(30.0f, true)]
|
||||
public float MeltdownDelay
|
||||
{
|
||||
get { return meltDownDelay; }
|
||||
set { meltDownDelay = Math.Max(value, 0.0f); }
|
||||
}
|
||||
|
||||
[Editable(ToolTip = "The temperature at which the reactor catches fire."), Serialize(9000.0f, true)]
|
||||
public float FireTemp
|
||||
[Editable(0.0f, float.MaxValue, ToolTip = "How long the temperature has to stay critical until the reactor catches fire."), Serialize(10.0f, true)]
|
||||
public float FireDelay
|
||||
{
|
||||
get { return fireTemp; }
|
||||
set
|
||||
{
|
||||
fireTemp = Math.Max(0.0f, value);
|
||||
}
|
||||
get { return fireDelay; }
|
||||
set { fireDelay = Math.Max(value, 0.0f); }
|
||||
}
|
||||
|
||||
[Editable(0.0f, float.MaxValue, ToolTip = "How much power (kW) the reactor generates relative to it's operating temperature (kW per one degree Celsius)."), Serialize(1.0f, true)]
|
||||
public float PowerPerTemp
|
||||
[Serialize(0.0f, true)]
|
||||
public float Temperature
|
||||
{
|
||||
get { return powerPerTemp; }
|
||||
get { return temperature; }
|
||||
set
|
||||
{
|
||||
powerPerTemp = Math.Max(0.0f, value);
|
||||
if (!MathUtils.IsValid(value)) return;
|
||||
temperature = MathHelper.Clamp(value, 0.0f, 100.0f);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,32 +116,32 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
|
||||
[Serialize(0.0f, true)]
|
||||
public float CoolingRate
|
||||
public float TurbineOutput
|
||||
{
|
||||
get { return coolingRate; }
|
||||
get { return turbineOutput; }
|
||||
set
|
||||
{
|
||||
if (!MathUtils.IsValid(value)) return;
|
||||
coolingRate = MathHelper.Clamp(value, 0.0f, 100.0f);
|
||||
turbineOutput = MathHelper.Clamp(value, 0.0f, 100.0f);
|
||||
}
|
||||
}
|
||||
|
||||
[Serialize(0.0f, true)]
|
||||
public float Temperature
|
||||
|
||||
[Serialize(0.2f, true), Editable(0.0f, 1000.0f, ToolTip = "How fast the condition of the contained fuel rods deteriorates.")]
|
||||
public float FuelConsumptionRate
|
||||
{
|
||||
get { return temperature; }
|
||||
set
|
||||
get { return fuelConsumptionRate; }
|
||||
set
|
||||
{
|
||||
if (!MathUtils.IsValid(value)) return;
|
||||
temperature = MathHelper.Clamp(value, 0.0f, 10000.0f);
|
||||
fuelConsumptionRate = Math.Max(value, 0.0f);
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsRunning()
|
||||
{
|
||||
return (temperature > 0.0f);
|
||||
}
|
||||
private float correctTurbineOutput;
|
||||
|
||||
private float targetFissionRate;
|
||||
private float targetTurbineOutput;
|
||||
|
||||
[Serialize(false, true)]
|
||||
public bool AutoTemp
|
||||
{
|
||||
@@ -131,109 +150,108 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
autoTemp = value;
|
||||
#if CLIENT
|
||||
if (autoTempTickBox!=null) autoTempTickBox.Selected = value;
|
||||
if (autoTempSlider != null)
|
||||
{
|
||||
autoTempSlider.BarScroll = value ?
|
||||
Math.Min(0.45f, autoTempSlider.BarScroll) :
|
||||
Math.Max(0.55f, autoTempSlider.BarScroll);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
public float ExtraCooling { get; set; }
|
||||
|
||||
|
||||
private float prevAvailableFuel;
|
||||
public float AvailableFuel { get; set; }
|
||||
|
||||
private float availableHeat, availableCooling;
|
||||
private float prevTemperature, temperatureChange;
|
||||
|
||||
[Serialize(500.0f, true)]
|
||||
public float ShutDownTemp
|
||||
{
|
||||
get { return shutDownTemp; }
|
||||
set { shutDownTemp = MathHelper.Clamp(value, 0.0f, 10000.0f); }
|
||||
}
|
||||
|
||||
|
||||
public Reactor(Item item, XElement element)
|
||||
: base(item, element)
|
||||
{
|
||||
shutDownTemp = 500.0f;
|
||||
powerPerTemp = 1.0f;
|
||||
{
|
||||
IsActive = true;
|
||||
InitProjSpecific();
|
||||
InitProjSpecific(element);
|
||||
}
|
||||
|
||||
partial void InitProjSpecific();
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
partial void InitProjSpecific(XElement element);
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
if (GameMain.Server != null && nextServerLogWriteTime != null)
|
||||
{
|
||||
if (Timing.TotalTime >= (float)nextServerLogWriteTime)
|
||||
{
|
||||
GameServer.Log(lastUser.LogName + " adjusted reactor settings: " +
|
||||
"Temperature: " + (int)temperature +
|
||||
", Fission rate: " + (int)fissionRate +
|
||||
", Cooling rate: " + (int)coolingRate +
|
||||
", Cooling rate: " + coolingRate +
|
||||
", Shutdown temp: " + shutDownTemp +
|
||||
"Temperature: " + (int)(temperature * 100.0f) +
|
||||
", Fission rate: " + (int)targetFissionRate +
|
||||
", Turbine output: " + (int)targetTurbineOutput +
|
||||
(autoTemp ? ", Autotemp ON" : ", Autotemp OFF"),
|
||||
ServerLog.MessageType.ItemInteraction);
|
||||
|
||||
|
||||
nextServerLogWriteTime = null;
|
||||
lastServerLogWriteTime = (float)Timing.TotalTime;
|
||||
}
|
||||
}
|
||||
|
||||
prevAvailableFuel = AvailableFuel;
|
||||
ApplyStatusEffects(ActionType.OnActive, deltaTime, null);
|
||||
|
||||
fissionRate = Math.Min(fissionRate, AvailableFuel);
|
||||
//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
|
||||
correctTurbineOutput += MathHelper.Clamp((load / MaxPowerOutput * 100.0f) - correctTurbineOutput, -10.0f, 10.0f) * deltaTime;
|
||||
|
||||
//the amount of cooling is always non-zero, so that the reactor always needs
|
||||
//to generate some amount of heat to prevent the temperature from dropping
|
||||
availableCooling = Math.Max(ExtraCooling, 5.0f);
|
||||
availableHeat = 80 * (AvailableFuel / 2000.0f);
|
||||
//calculate tolerances of the meters based on the skills of the user
|
||||
//more skilled characters have larger "sweet spots", making it easier to keep the power output at a suitable level
|
||||
float tolerance = MathHelper.Lerp(2.5f, 10.0f, degreeOfSuccess);
|
||||
optimalTurbineOutput = new Vector2(correctTurbineOutput - tolerance, correctTurbineOutput + tolerance);
|
||||
tolerance = MathHelper.Lerp(5.0f, 20.0f, degreeOfSuccess);
|
||||
allowedTurbineOutput = new Vector2(correctTurbineOutput - tolerance, correctTurbineOutput + tolerance);
|
||||
|
||||
float temperatureTolerance = MathHelper.Lerp(10.0f, 20.0f, degreeOfSuccess);
|
||||
optimalTemperature = Vector2.Lerp(new Vector2(40.0f, 60.0f), new Vector2(30.0f, 70.0f), degreeOfSuccess);
|
||||
allowedTemperature = Vector2.Lerp(new Vector2(30.0f, 70.0f), new Vector2(10.0f, 90.0f), degreeOfSuccess);
|
||||
|
||||
float heat = availableHeat * fissionRate;
|
||||
float heatDissipation = 50 * coolingRate + availableCooling;
|
||||
float fissionRateTolerance = MathHelper.Lerp(10.0f, 20.0f, degreeOfSuccess);
|
||||
optimalFissionRate = Vector2.Lerp(new Vector2(40.0f, 70.0f), new Vector2(30.0f, 85.0f), degreeOfSuccess);
|
||||
allowedFissionRate = Vector2.Lerp(new Vector2(30.0f, 85.0f), new Vector2(20.0f, 98.0f), degreeOfSuccess);
|
||||
|
||||
float deltaTemp = (((heat - heatDissipation) * 5) - temperature) / 10000.0f;
|
||||
Temperature = temperature + deltaTemp;
|
||||
float heatAmount = fissionRate * (AvailableFuel / 100.0f) * 2.0f;
|
||||
float temperatureDiff = (heatAmount - turbineOutput) - Temperature;
|
||||
Temperature += MathHelper.Clamp(Math.Sign(temperatureDiff) * 10.0f * deltaTime, -Math.Abs(temperatureDiff), Math.Abs(temperatureDiff));
|
||||
if (item.InWater && AvailableFuel < 100.0f) Temperature -= 12.0f * deltaTime;
|
||||
|
||||
temperatureChange = Temperature - prevTemperature;
|
||||
prevTemperature = temperature;
|
||||
FissionRate = MathHelper.Lerp(fissionRate, Math.Min(targetFissionRate, AvailableFuel), deltaTime);
|
||||
TurbineOutput = MathHelper.Lerp(turbineOutput, targetTurbineOutput, deltaTime);
|
||||
|
||||
if (temperature > fireTemp && temperature - deltaTemp < fireTemp)
|
||||
float temperatureFactor = Math.Min(temperature / 50.0f, 1.0f);
|
||||
currPowerConsumption = -MaxPowerOutput * Math.Min(turbineOutput / 100.0f, temperatureFactor);
|
||||
|
||||
//if the turbine output and coolant flow are the optimal range,
|
||||
//make the generated power slightly adjust according to the load
|
||||
// (-> the reactor can automatically handle small changes in load as long as the values are roughly correct)
|
||||
if (turbineOutput > optimalTurbineOutput.X && turbineOutput < optimalTurbineOutput.Y &&
|
||||
temperature > optimalTemperature.X && temperature < optimalTemperature.Y)
|
||||
{
|
||||
#if CLIENT
|
||||
Vector2 baseVel = Rand.Vector(300.0f);
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
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
|
||||
|
||||
new FireSource(item.WorldPosition);
|
||||
}
|
||||
|
||||
if (temperature > meltDownTemp)
|
||||
{
|
||||
item.SendSignal(0, "1", "meltdown_warning", null);
|
||||
meltDownTimer += deltaTime;
|
||||
|
||||
if (meltDownTimer > MeltdownDelay)
|
||||
{
|
||||
MeltDown();
|
||||
return;
|
||||
}
|
||||
float maxAutoAdjust = maxPowerOutput * 0.1f;
|
||||
autoAdjustAmount = MathHelper.Lerp(
|
||||
autoAdjustAmount,
|
||||
MathHelper.Clamp(-load - currPowerConsumption, -maxAutoAdjust, maxAutoAdjust),
|
||||
deltaTime * 10.0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
item.SendSignal(0, "0", "meltdown_warning", null);
|
||||
meltDownTimer = Math.Max(0.0f, meltDownTimer - deltaTime);
|
||||
autoAdjustAmount = MathHelper.Lerp(autoAdjustAmount, 0.0f, deltaTime * 10.0f);
|
||||
}
|
||||
currPowerConsumption += autoAdjustAmount;
|
||||
|
||||
if (shutDown)
|
||||
{
|
||||
targetFissionRate = 0.0f;
|
||||
targetTurbineOutput = 0.0f;
|
||||
}
|
||||
else if (autoTemp)
|
||||
{
|
||||
UpdateAutoTemp(2.0f, deltaTime);
|
||||
}
|
||||
|
||||
load = 0.0f;
|
||||
|
||||
List<Connection> connections = item.Connections;
|
||||
if (connections != null && connections.Count > 0)
|
||||
{
|
||||
@@ -247,50 +265,40 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
PowerTransfer pt = it.GetComponent<PowerTransfer>();
|
||||
if (pt == null) continue;
|
||||
|
||||
load = Math.Max(load,pt.PowerLoad);
|
||||
|
||||
load = Math.Max(load, pt.PowerLoad);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//item.Condition -= temperature * deltaTime * 0.00005f;
|
||||
|
||||
if (temperature > shutDownTemp)
|
||||
if (fissionRate > 0.0f)
|
||||
{
|
||||
CoolingRate += 0.5f;
|
||||
FissionRate -= 0.5f;
|
||||
}
|
||||
else if (autoTemp)
|
||||
{
|
||||
//take deltaTemp into account to slow down the change in temperature when getting closer to the desired value
|
||||
float target = temperature + deltaTemp * 100.0f;
|
||||
foreach (Item item in item.ContainedItems)
|
||||
{
|
||||
if (!item.HasTag("reactorfuel")) continue;
|
||||
item.Condition -= fissionRate / 100.0f * fuelConsumptionRate * deltaTime;
|
||||
}
|
||||
|
||||
//-1.0f in order to gradually turn down both rates when the target temperature is reached
|
||||
FissionRate += (MathHelper.Clamp(load - target, -10.0f, 10.0f) - 1.0f) * deltaTime;
|
||||
CoolingRate += (MathHelper.Clamp(target - load, -5.0f, 5.0f) - 1.0f) * deltaTime;
|
||||
}
|
||||
|
||||
//the power generated by the reactor is equal to the temperature
|
||||
currPowerConsumption = -temperature*powerPerTemp;
|
||||
|
||||
if (item.CurrentHull != null)
|
||||
{
|
||||
//the sound can be heard from 20 000 display units away when running at full power
|
||||
item.CurrentHull.SoundRange = Math.Max(temperature * 2, item.CurrentHull.AiTarget.SoundRange);
|
||||
if (item.CurrentHull != null)
|
||||
{
|
||||
//the sound can be heard from 20 000 display units away when running at full power
|
||||
item.CurrentHull.SoundRange = Math.Max(
|
||||
(-currPowerConsumption / MaxPowerOutput) * 20000.0f,
|
||||
item.CurrentHull.AiTarget.SoundRange);
|
||||
}
|
||||
}
|
||||
|
||||
item.SendSignal(0, ((int)(temperature * 100.0f)).ToString(), "temperature_out", null);
|
||||
|
||||
UpdateFailures(deltaTime);
|
||||
#if CLIENT
|
||||
UpdateGraph(deltaTime);
|
||||
#endif
|
||||
|
||||
ExtraCooling = 0.0f;
|
||||
AvailableFuel = 0.0f;
|
||||
|
||||
item.SendSignal(0, ((int)temperature).ToString(), "temperature_out", null);
|
||||
|
||||
sendUpdateTimer = Math.Max(sendUpdateTimer - deltaTime, 0.0f);
|
||||
|
||||
if (unsentChanges && sendUpdateTimer<= 0.0f)
|
||||
if (unsentChanges && sendUpdateTimer <= 0.0f)
|
||||
{
|
||||
if (GameMain.Server != null)
|
||||
{
|
||||
@@ -302,27 +310,78 @@ namespace Barotrauma.Items.Components
|
||||
item.CreateClientEvent(this);
|
||||
}
|
||||
#endif
|
||||
|
||||
sendUpdateTimer = NetworkUpdateInterval;
|
||||
unsentChanges = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateFailures(float deltaTime)
|
||||
{
|
||||
if (temperature > allowedTemperature.Y)
|
||||
{
|
||||
item.SendSignal(0, "1", "meltdown_warning", null);
|
||||
//faster meltdown if the item is in a bad condition
|
||||
meltDownTimer += MathHelper.Lerp(deltaTime * 2.0f, deltaTime, item.Condition / 100.0f);
|
||||
|
||||
if (meltDownTimer > MeltdownDelay)
|
||||
{
|
||||
MeltDown();
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
item.SendSignal(0, "0", "meltdown_warning", null);
|
||||
meltDownTimer = Math.Max(0.0f, meltDownTimer - deltaTime);
|
||||
}
|
||||
|
||||
if (temperature > optimalTemperature.Y)
|
||||
{
|
||||
float prevFireTimer = fireTimer;
|
||||
fireTimer += MathHelper.Lerp(deltaTime * 2.0f, deltaTime, item.Condition / 100.0f);
|
||||
|
||||
if (fireTimer >= FireDelay && prevFireTimer < fireDelay)
|
||||
{
|
||||
new FireSource(item.WorldPosition);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
fireTimer = Math.Max(0.0f, fireTimer - deltaTime);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateAutoTemp(float speed, float deltaTime)
|
||||
{
|
||||
float desiredTurbineOutput = (optimalTurbineOutput.X + optimalTurbineOutput.Y) / 2.0f;
|
||||
targetTurbineOutput += MathHelper.Clamp(desiredTurbineOutput - targetTurbineOutput, -speed, speed) * deltaTime;
|
||||
|
||||
float desiredFissionRate = (optimalFissionRate.X + optimalFissionRate.Y) / 2.0f;
|
||||
targetFissionRate += MathHelper.Clamp(desiredFissionRate - targetFissionRate, -speed, speed) * deltaTime;
|
||||
|
||||
if (temperature > (optimalTemperature.X + optimalTemperature.Y) / 2.0f)
|
||||
{
|
||||
targetFissionRate = Math.Min(targetFissionRate - speed * 2 * deltaTime, allowedFissionRate.Y);
|
||||
}
|
||||
else if (-currPowerConsumption < load)
|
||||
{
|
||||
targetFissionRate = Math.Min(targetFissionRate + speed * 2 * deltaTime, allowedFissionRate.Y);
|
||||
}
|
||||
}
|
||||
|
||||
public override void UpdateBroken(float deltaTime, Camera cam)
|
||||
{
|
||||
base.UpdateBroken(deltaTime, cam);
|
||||
|
||||
currPowerConsumption = 0.0f;
|
||||
Temperature -= deltaTime * 1000.0f;
|
||||
FissionRate -= deltaTime * 10.0f;
|
||||
CoolingRate -= deltaTime * 10.0f;
|
||||
|
||||
currPowerConsumption = -temperature;
|
||||
|
||||
targetFissionRate = Math.Max(targetFissionRate - deltaTime * 10.0f, 0.0f);
|
||||
targetTurbineOutput = Math.Max(targetTurbineOutput - deltaTime * 10.0f, 0.0f);
|
||||
#if CLIENT
|
||||
fissionRateScrollBar.BarScroll = 1.0f - FissionRate / 100.0f;
|
||||
turbineOutputScrollBar.BarScroll = 1.0f - TurbineOutput / 100.0f;
|
||||
UpdateGraph(deltaTime);
|
||||
#endif
|
||||
|
||||
ExtraCooling = 0.0f;
|
||||
}
|
||||
|
||||
private void MeltDown()
|
||||
@@ -332,6 +391,8 @@ namespace Barotrauma.Items.Components
|
||||
GameServer.Log("Reactor meltdown!", ServerLog.MessageType.ItemInteraction);
|
||||
|
||||
item.Condition = 0.0f;
|
||||
fireTimer = 0.0f;
|
||||
meltDownTimer = 0.0f;
|
||||
|
||||
var containedItems = item.ContainedItems;
|
||||
if (containedItems != null)
|
||||
@@ -356,6 +417,8 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public override bool AIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective)
|
||||
{
|
||||
if (GameMain.Client != null) return false;
|
||||
|
||||
float degreeOfSuccess = DegreeOfSuccess(character);
|
||||
|
||||
//characters with insufficient skill levels don't refuel the reactor
|
||||
@@ -371,65 +434,97 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
//the temperature is too low and not increasing even though the fission rate is high and cooling low
|
||||
// -> we need more fuel
|
||||
if (temperature < load * 0.5f && temperatureChange <= 0.0f && fissionRate > 0.9f && coolingRate < 0.1f)
|
||||
//we need more fuel
|
||||
if (-currPowerConsumption < load * 0.5f && prevAvailableFuel <= 0.0f)
|
||||
{
|
||||
var containFuelObjective = new AIObjectiveContainItem(character, new string[] { "Fuel Rod", "reactorfuel" }, item.GetComponent<ItemContainer>());
|
||||
containFuelObjective.MinContainedAmount = containedItems.Count(i => i != null && i.Prefab.NameMatches("Fuel Rod") || i.HasTag("reactorfuel")) + 1;
|
||||
containFuelObjective.GetItemPriority = (Item fuelItem) =>
|
||||
var containFuelObjective = new AIObjectiveContainItem(character, new string[] { "fuelrod", "reactorfuel" }, item.GetComponent<ItemContainer>())
|
||||
{
|
||||
if (fuelItem.ParentInventory?.Owner is Item)
|
||||
MinContainedAmount = containedItems.Count(i => i != null && i.Prefab.Identifier == "fuelrod" || i.HasTag("reactorfuel")) + 1,
|
||||
GetItemPriority = (Item fuelItem) =>
|
||||
{
|
||||
//don't take fuel from other reactors
|
||||
if (((Item)fuelItem.ParentInventory.Owner).GetComponent<Reactor>() != null) return 0.0f;
|
||||
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;
|
||||
}
|
||||
return 1.0f;
|
||||
};
|
||||
objective.AddSubObjective(containFuelObjective);
|
||||
|
||||
character?.Speak(TextManager.Get("DialogReactorFuel"), null, 0.0f, "reactorfuel", 30.0f);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (aiUpdateTimer > 0.0f)
|
||||
{
|
||||
aiUpdateTimer -= deltaTime;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (lastUser != character && lastUser != null && lastUser.SelectedConstruction == item)
|
||||
{
|
||||
character.Speak(TextManager.Get("DialogReactorTaken"), null, 0.0f, "reactortaken", 10.0f);
|
||||
}
|
||||
|
||||
LastUser = character;
|
||||
|
||||
switch (objective.Option.ToLowerInvariant())
|
||||
{
|
||||
case "power up":
|
||||
float tempDiff = load - temperature;
|
||||
|
||||
shutDownTemp = Math.Min(load + 1000.0f, 7500.0f);
|
||||
|
||||
case "powerup":
|
||||
shutDown = false;
|
||||
//characters with insufficient skill levels simply set the autotemp on instead of trying to adjust the temperature manually
|
||||
if (Math.Abs(tempDiff) < 500.0f || degreeOfSuccess < 0.5f)
|
||||
if (degreeOfSuccess < 0.5f)
|
||||
{
|
||||
if (!autoTemp) unsentChanges = true;
|
||||
AutoTemp = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
AutoTemp = false;
|
||||
//higher skill levels make the character adjust the temperature faster
|
||||
FissionRate += deltaTime * 100.0f * Math.Sign(tempDiff) * degreeOfSuccess;
|
||||
CoolingRate -= deltaTime * 100.0f * Math.Sign(tempDiff) * degreeOfSuccess;
|
||||
}
|
||||
unsentChanges = true;
|
||||
UpdateAutoTemp(2.0f + degreeOfSuccess * 5.0f, 1.0f);
|
||||
|
||||
}
|
||||
#if CLIENT
|
||||
onOffSwitch.BarScroll = 0.0f;
|
||||
fissionRateScrollBar.BarScroll = FissionRate / 100.0f;
|
||||
turbineOutputScrollBar.BarScroll = TurbineOutput / 100.0f;
|
||||
#endif
|
||||
break;
|
||||
case "shutdown":
|
||||
shutDownTemp = 0.0f;
|
||||
#if CLIENT
|
||||
onOffSwitch.BarScroll = 1.0f;
|
||||
#endif
|
||||
AutoTemp = false;
|
||||
shutDown = true;
|
||||
targetFissionRate = 0.0f;
|
||||
targetTurbineOutput = 0.0f;
|
||||
break;
|
||||
}
|
||||
|
||||
aiUpdateTimer = AIUpdateInterval;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power)
|
||||
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power, float signalStrength = 1.0f)
|
||||
{
|
||||
switch (connection.Name)
|
||||
{
|
||||
case "shutdown":
|
||||
if (shutDownTemp > 0.0f)
|
||||
if (targetFissionRate > 0.0f || targetTurbineOutput > 0.0f)
|
||||
{
|
||||
shutDown = true;
|
||||
AutoTemp = false;
|
||||
targetFissionRate = 0.0f;
|
||||
targetTurbineOutput = 0.0f;
|
||||
unsentChanges = true;
|
||||
shutDownTemp = 0.0f;
|
||||
#if CLIENT
|
||||
onOffSwitch.BarScroll = 1.0f;
|
||||
#endif
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -438,41 +533,46 @@ namespace Barotrauma.Items.Components
|
||||
public void ServerRead(ClientNetObject type, NetBuffer msg, Client c)
|
||||
{
|
||||
bool autoTemp = msg.ReadBoolean();
|
||||
float shutDownTemp = msg.ReadRangedSingle(0.0f, 10000.0f, 15);
|
||||
float coolingRate = msg.ReadRangedSingle(0.0f, 100.0f, 8);
|
||||
bool shutDown = msg.ReadBoolean();
|
||||
float fissionRate = msg.ReadRangedSingle(0.0f, 100.0f, 8);
|
||||
float turbineOutput = msg.ReadRangedSingle(0.0f, 100.0f, 8);
|
||||
|
||||
if (!item.CanClientAccess(c)) return;
|
||||
|
||||
if (!autoTemp && AutoTemp) BlameOnBroken = c;
|
||||
if (shutDownTemp > ShutDownTemp) BlameOnBroken = c;
|
||||
if (fissionRate > FissionRate) BlameOnBroken = c;
|
||||
if (turbineOutput < targetTurbineOutput) BlameOnBroken = c;
|
||||
if (fissionRate > targetFissionRate) BlameOnBroken = c;
|
||||
if (!this.shutDown && shutDown) BlameOnBroken = c;
|
||||
|
||||
AutoTemp = autoTemp;
|
||||
ShutDownTemp = shutDownTemp;
|
||||
this.shutDown = shutDown;
|
||||
targetFissionRate = fissionRate;
|
||||
targetTurbineOutput = turbineOutput;
|
||||
|
||||
CoolingRate = coolingRate;
|
||||
FissionRate = fissionRate;
|
||||
|
||||
lastUser = c.Character;
|
||||
LastUser = c.Character;
|
||||
if (nextServerLogWriteTime == null)
|
||||
{
|
||||
nextServerLogWriteTime = Math.Max(lastServerLogWriteTime + 1.0f, (float)Timing.TotalTime);
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
fissionRateScrollBar.BarScroll = 1.0f - targetFissionRate / 100.0f;
|
||||
turbineOutputScrollBar.BarScroll = 1.0f - targetTurbineOutput / 100.0f;
|
||||
onOffSwitch.BarScroll = shutDown ? Math.Max(onOffSwitch.BarScroll, 0.55f) : Math.Min(onOffSwitch.BarScroll, 0.45f);
|
||||
#endif
|
||||
|
||||
//need to create a server event to notify all clients of the changed state
|
||||
unsentChanges = true;
|
||||
}
|
||||
|
||||
public void ServerWrite(NetBuffer msg, Client c, object[] extraData = null)
|
||||
{
|
||||
msg.WriteRangedSingle(temperature, 0.0f, 10000.0f, 16);
|
||||
|
||||
msg.Write(autoTemp);
|
||||
msg.WriteRangedSingle(shutDownTemp, 0.0f, 10000.0f, 15);
|
||||
|
||||
msg.WriteRangedSingle(coolingRate, 0.0f, 100.0f, 8);
|
||||
msg.WriteRangedSingle(fissionRate, 0.0f, 100.0f, 8);
|
||||
msg.Write(shutDown);
|
||||
msg.WriteRangedSingle(temperature, 0.0f, 100.0f, 8);
|
||||
msg.WriteRangedSingle(targetFissionRate, 0.0f, 100.0f, 8);
|
||||
msg.WriteRangedSingle(targetTurbineOutput, 0.0f, 100.0f, 8);
|
||||
msg.WriteRangedSingle(degreeOfSuccess, 0.0f, 1.0f, 8);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,353 @@
|
||||
using Barotrauma.Networking;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
partial class Sonar : Powered, IServerSerializable, IClientSerializable
|
||||
{
|
||||
public const float DefaultSonarRange = 10000.0f;
|
||||
|
||||
class ConnectedTransducer
|
||||
{
|
||||
public readonly SonarTransducer Transducer;
|
||||
public float SignalStrength;
|
||||
public float DisconnectTimer;
|
||||
|
||||
public ConnectedTransducer(SonarTransducer transducer, float signalStrength, float disconnectTimer)
|
||||
{
|
||||
Transducer = transducer;
|
||||
SignalStrength = signalStrength;
|
||||
DisconnectTimer = disconnectTimer;
|
||||
}
|
||||
}
|
||||
|
||||
private const float DirectionalPingSector = 30.0f;
|
||||
private static readonly float DirectionalPingDotProduct;
|
||||
|
||||
static Sonar()
|
||||
{
|
||||
DirectionalPingDotProduct = (float)Math.Cos(MathHelper.ToRadians(DirectionalPingSector) * 0.5f);
|
||||
}
|
||||
|
||||
private float range;
|
||||
|
||||
private float pingState;
|
||||
|
||||
private const float MinZoom = 1.0f, MaxZoom = 4.0f;
|
||||
private float zoom = 1.0f;
|
||||
|
||||
private bool useDirectionalPing = false;
|
||||
private Vector2 lastPingDirection = new Vector2(1.0f, 0.0f);
|
||||
private Vector2 pingDirection = new Vector2(1.0f, 0.0f);
|
||||
|
||||
//was the last ping sent with directional pinging
|
||||
private bool isLastPingDirectional;
|
||||
|
||||
private readonly Sprite pingCircle, directionalPingCircle, screenOverlay, screenBackground;
|
||||
private readonly Sprite sonarBlip;
|
||||
|
||||
private bool aiPingCheckPending;
|
||||
|
||||
//the float value is a timer used for disconnecting the transducer if no signal is received from it for 1 second
|
||||
private List<ConnectedTransducer> connectedTransducers;
|
||||
|
||||
public IEnumerable<SonarTransducer> ConnectedTransducers
|
||||
{
|
||||
get { return connectedTransducers.Select(t => t.Transducer); }
|
||||
}
|
||||
|
||||
[Serialize(DefaultSonarRange, false)]
|
||||
public float Range
|
||||
{
|
||||
get { return range; }
|
||||
set { range = MathHelper.Clamp(value, 0.0f, 100000.0f); }
|
||||
}
|
||||
|
||||
[Serialize(false, false)]
|
||||
public bool DetectSubmarineWalls
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[Serialize(false, false), Editable(ToolTip = "Does the sonar have to be connected to external transducers to work.")]
|
||||
public bool UseTransducers
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public float Zoom
|
||||
{
|
||||
get { return zoom; }
|
||||
}
|
||||
|
||||
public override bool IsActive
|
||||
{
|
||||
get
|
||||
{
|
||||
return base.IsActive;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
base.IsActive = value;
|
||||
if (!value && item.CurrentHull != null)
|
||||
{
|
||||
item.CurrentHull.AiTarget.SectorDegrees = 360.0f;
|
||||
}
|
||||
#if CLIENT
|
||||
if (activeTickBox != null) activeTickBox.Selected = value;
|
||||
if (passiveTickBox != null) passiveTickBox.Selected = !value;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
public Sonar(Item item, XElement element)
|
||||
: base(item, element)
|
||||
{
|
||||
connectedTransducers = new List<ConnectedTransducer>();
|
||||
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "pingcircle":
|
||||
pingCircle = new Sprite(subElement);
|
||||
break;
|
||||
case "directionalpingcircle":
|
||||
directionalPingCircle = new Sprite(subElement);
|
||||
break;
|
||||
case "screenoverlay":
|
||||
screenOverlay = new Sprite(subElement);
|
||||
break;
|
||||
case "screenbackground":
|
||||
screenBackground = new Sprite(subElement);
|
||||
break;
|
||||
case "blip":
|
||||
sonarBlip = new Sprite(subElement);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
IsActive = false;
|
||||
InitProjSpecific(element);
|
||||
}
|
||||
|
||||
partial void InitProjSpecific(XElement element);
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
currPowerConsumption = powerConsumption;
|
||||
|
||||
UpdateOnActiveEffects(deltaTime);
|
||||
|
||||
if (UseTransducers)
|
||||
{
|
||||
foreach (ConnectedTransducer transducer in connectedTransducers)
|
||||
{
|
||||
transducer.DisconnectTimer -= deltaTime;
|
||||
}
|
||||
connectedTransducers.RemoveAll(t => t.DisconnectTimer <= 0.0f);
|
||||
}
|
||||
|
||||
if ((voltage >= minVoltage || powerConsumption <= 0.0f) &&
|
||||
(!UseTransducers || connectedTransducers.Count > 0))
|
||||
{
|
||||
pingState = pingState + deltaTime * 0.5f;
|
||||
if (pingState > 1.0f)
|
||||
{
|
||||
if (item.CurrentHull != null)
|
||||
{
|
||||
item.CurrentHull.AiTarget.SoundRange = Math.Max(Range * pingState / zoom, item.CurrentHull.AiTarget.SoundRange);
|
||||
item.CurrentHull.AiTarget.SectorDegrees = isLastPingDirectional ? DirectionalPingSector : 360.0f;
|
||||
item.CurrentHull.AiTarget.SectorDir = new Vector2(pingDirection.X, -pingDirection.Y);
|
||||
}
|
||||
if (item.AiTarget != null)
|
||||
{
|
||||
item.AiTarget.SoundRange = Math.Max(Range * pingState / zoom, item.AiTarget.SoundRange);
|
||||
item.AiTarget.SectorDegrees = isLastPingDirectional ? DirectionalPingSector : 360.0f;
|
||||
item.AiTarget.SectorDir = new Vector2(pingDirection.X, -pingDirection.Y);
|
||||
}
|
||||
aiPingCheckPending = true;
|
||||
isLastPingDirectional = useDirectionalPing;
|
||||
lastPingDirection = pingDirection;
|
||||
item.Use(deltaTime);
|
||||
pingState = 0.0f;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (item.CurrentHull != null)
|
||||
{
|
||||
item.CurrentHull.AiTarget.SectorDegrees = 360.0f;
|
||||
}
|
||||
aiPingCheckPending = false;
|
||||
pingState = 0.0f;
|
||||
}
|
||||
|
||||
Voltage -= deltaTime;
|
||||
}
|
||||
|
||||
public override bool Use(float deltaTime, Character character = null)
|
||||
{
|
||||
return pingState > 1.0f;
|
||||
}
|
||||
|
||||
protected override void RemoveComponentSpecific()
|
||||
{
|
||||
sonarBlip?.Remove();
|
||||
pingCircle?.Remove();
|
||||
directionalPingCircle?.Remove();
|
||||
screenOverlay?.Remove();
|
||||
screenBackground?.Remove();
|
||||
}
|
||||
|
||||
public override bool AIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective)
|
||||
{
|
||||
if (!IsActive || !aiPingCheckPending) return false;
|
||||
|
||||
Dictionary<string, List<Character>> targetGroups = new Dictionary<string, List<Character>>();
|
||||
|
||||
foreach (Character c in Character.CharacterList)
|
||||
{
|
||||
if (c.AnimController.CurrentHull != null || !c.Enabled) continue;
|
||||
if (DetectSubmarineWalls && c.AnimController.CurrentHull == null && item.CurrentHull != null) continue;
|
||||
if (Vector2.DistanceSquared(c.WorldPosition, item.WorldPosition) > range * range) continue;
|
||||
|
||||
string directionName = GetDirectionName(c.WorldPosition - item.WorldPosition);
|
||||
|
||||
if (!targetGroups.ContainsKey(directionName))
|
||||
{
|
||||
targetGroups.Add(directionName, new List<Character>());
|
||||
}
|
||||
targetGroups[directionName].Add(c);
|
||||
}
|
||||
|
||||
foreach (KeyValuePair<string, List<Character>> targetGroup in targetGroups)
|
||||
{
|
||||
string dialogTag = "DialogSonarTarget";
|
||||
if (targetGroup.Value.Count > 1)
|
||||
{
|
||||
dialogTag = "DialogSonarTargetMultiple";
|
||||
}
|
||||
else if (targetGroup.Value[0].Mass > 100.0f)
|
||||
{
|
||||
dialogTag = "DialogSonarTargetLarge";
|
||||
}
|
||||
character.Speak(TextManager.Get(dialogTag).Replace("[direction]", targetGroup.Key).Replace("[count]", targetGroup.Value.Count.ToString()),
|
||||
null, 0, "sonartarget" + targetGroup.Value[0].ID, 30);
|
||||
|
||||
//prevent the character from reporting other targets in the group
|
||||
for (int i = 1; i < targetGroup.Value.Count; i++)
|
||||
{
|
||||
character.DisableLine("sonartarget" + targetGroup.Value[i].ID);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private string GetDirectionName(Vector2 dir)
|
||||
{
|
||||
float angle = MathUtils.WrapAngleTwoPi((float)-Math.Atan2(dir.Y, dir.X) + MathHelper.PiOver2);
|
||||
|
||||
int clockDir = (int)Math.Round((angle / MathHelper.TwoPi) * 12);
|
||||
if (clockDir == 0) clockDir = 12;
|
||||
|
||||
return TextManager.Get("SubDirOClock").Replace("[dir]", clockDir.ToString());
|
||||
}
|
||||
|
||||
private Vector2 GetTransducerCenter()
|
||||
{
|
||||
if (!UseTransducers || connectedTransducers.Count == 0) return Vector2.Zero;
|
||||
Vector2 transducerPosSum = Vector2.Zero;
|
||||
foreach (ConnectedTransducer transducer in connectedTransducers)
|
||||
{
|
||||
transducerPosSum += transducer.Transducer.Item.WorldPosition;
|
||||
}
|
||||
return transducerPosSum / connectedTransducers.Count;
|
||||
}
|
||||
|
||||
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power = 0, float signalStrength = 1.0f)
|
||||
{
|
||||
base.ReceiveSignal(stepsTaken, signal, connection, source, sender, power, signalStrength);
|
||||
|
||||
if (connection.Name == "transducer_in")
|
||||
{
|
||||
var transducer = source.GetComponent<SonarTransducer>();
|
||||
if (transducer == null) return;
|
||||
|
||||
var connectedTransducer = connectedTransducers.Find(t => t.Transducer == transducer);
|
||||
if (connectedTransducer == null)
|
||||
{
|
||||
connectedTransducers.Add(new ConnectedTransducer(transducer, signalStrength, 1.0f));
|
||||
}
|
||||
else
|
||||
{
|
||||
connectedTransducer.SignalStrength = signalStrength;
|
||||
connectedTransducer.DisconnectTimer = 1.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ServerRead(ClientNetObject type, Lidgren.Network.NetBuffer msg, Client c)
|
||||
{
|
||||
bool isActive = msg.ReadBoolean();
|
||||
bool directionalPing = useDirectionalPing;
|
||||
float zoomT = zoom, pingDirectionT = 0.0f;
|
||||
if (isActive)
|
||||
{
|
||||
zoomT = msg.ReadRangedSingle(0.0f, 1.0f, 8);
|
||||
directionalPing = msg.ReadBoolean();
|
||||
if (directionalPing)
|
||||
{
|
||||
pingDirectionT = msg.ReadRangedSingle(0.0f, 1.0f, 8);
|
||||
}
|
||||
}
|
||||
|
||||
if (!item.CanClientAccess(c)) return;
|
||||
|
||||
IsActive = isActive;
|
||||
#if CLIENT
|
||||
activeTickBox.Selected = IsActive;
|
||||
#endif
|
||||
if (isActive)
|
||||
{
|
||||
zoom = MathHelper.Lerp(MinZoom, MaxZoom, zoomT);
|
||||
useDirectionalPing = directionalPing;
|
||||
if (useDirectionalPing)
|
||||
{
|
||||
float pingAngle = MathHelper.Lerp(0.0f, MathHelper.TwoPi, pingDirectionT);
|
||||
pingDirection = new Vector2((float)Math.Cos(pingAngle), (float)Math.Sin(pingAngle));
|
||||
}
|
||||
#if CLIENT
|
||||
zoomSlider.BarScroll = zoomT;
|
||||
directionalTickBox.Selected = useDirectionalPing;
|
||||
directionalSlider.BarScroll = pingDirectionT;
|
||||
#endif
|
||||
}
|
||||
|
||||
item.CreateServerEvent(this);
|
||||
}
|
||||
|
||||
public void ServerWrite(Lidgren.Network.NetBuffer msg, Client c, object[] extraData = null)
|
||||
{
|
||||
msg.Write(IsActive);
|
||||
if (IsActive)
|
||||
{
|
||||
msg.WriteRangedSingle(zoom, MinZoom, MaxZoom, 8);
|
||||
msg.Write(useDirectionalPing);
|
||||
if (useDirectionalPing)
|
||||
{
|
||||
float pingAngle = MathUtils.WrapAngleTwoPi(MathUtils.VectorToAngle(pingDirection));
|
||||
msg.WriteRangedSingle(MathUtils.InverseLerp(0.0f, MathHelper.TwoPi, pingAngle), 0.0f, 1.0f, 8);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
class SonarTransducer : Powered
|
||||
{
|
||||
const float SendSignalInterval = 0.5f;
|
||||
|
||||
private float sendSignalTimer;
|
||||
|
||||
public SonarTransducer(Item item, XElement element) : base(item, element)
|
||||
{
|
||||
IsActive = true;
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
UpdateOnActiveEffects(deltaTime);
|
||||
|
||||
if (voltage >= minVoltage || PowerConsumption <= 0.0f)
|
||||
{
|
||||
sendSignalTimer += deltaTime;
|
||||
if (sendSignalTimer > SendSignalInterval)
|
||||
{
|
||||
item.SendSignal(0, "0101101101101011010", "data_out", sender: null);
|
||||
sendSignalTimer = SendSignalInterval;
|
||||
}
|
||||
}
|
||||
|
||||
voltage = 0.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,9 @@
|
||||
using FarseerPhysics;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using Voronoi2;
|
||||
|
||||
@@ -11,10 +13,13 @@ namespace Barotrauma.Items.Components
|
||||
partial class Steering : Powered, IServerSerializable, IClientSerializable
|
||||
{
|
||||
private const float AutopilotRayCastInterval = 0.5f;
|
||||
private const float RecalculatePathInterval = 10.0f;
|
||||
|
||||
private Vector2 currVelocity;
|
||||
private Vector2 targetVelocity;
|
||||
|
||||
private Vector2 steeringInput;
|
||||
|
||||
private bool autoPilot;
|
||||
|
||||
private Vector2? posToMaintain;
|
||||
@@ -27,10 +32,19 @@ namespace Barotrauma.Items.Components
|
||||
private bool unsentChanges;
|
||||
|
||||
private float autopilotRayCastTimer;
|
||||
private float autopilotRecalculatePathTimer;
|
||||
|
||||
private Vector2 avoidStrength;
|
||||
|
||||
private float neutralBallastLevel;
|
||||
|
||||
private float steeringAdjustSpeed = 1.0f;
|
||||
|
||||
private Character user;
|
||||
|
||||
private Sonar sonar;
|
||||
|
||||
private Submarine controlledSub;
|
||||
|
||||
public bool AutoPilot
|
||||
{
|
||||
@@ -38,11 +52,10 @@ namespace Barotrauma.Items.Components
|
||||
set
|
||||
{
|
||||
if (value == autoPilot) return;
|
||||
|
||||
autoPilot = value;
|
||||
#if CLIENT
|
||||
autopilotTickBox.Selected = value;
|
||||
|
||||
autopilotTickBox.Selected = autoPilot;
|
||||
manualTickBox.Selected = !autoPilot;
|
||||
maintainPosTickBox.Enabled = autoPilot;
|
||||
levelEndTickBox.Enabled = autoPilot;
|
||||
levelStartTickBox.Enabled = autoPilot;
|
||||
@@ -50,24 +63,19 @@ namespace Barotrauma.Items.Components
|
||||
if (autoPilot)
|
||||
{
|
||||
if (pathFinder == null) pathFinder = new PathFinder(WayPoint.WayPointList, false);
|
||||
#if CLIENT
|
||||
ToggleMaintainPosition(maintainPosTickBox);
|
||||
#endif
|
||||
MaintainPos = true;
|
||||
}
|
||||
#if CLIENT
|
||||
else
|
||||
{
|
||||
maintainPosTickBox.Selected = false;
|
||||
levelEndTickBox.Selected = false;
|
||||
levelStartTickBox.Selected = false;
|
||||
|
||||
posToMaintain = null;
|
||||
PosToMaintain = null;
|
||||
MaintainPos = false;
|
||||
LevelEndSelected = false;
|
||||
LevelStartSelected = false;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
[Editable(0.0f, 1.0f, ToolTip = "How full the ballast tanks should be when the submarine is not being steered upwards/downwards."
|
||||
[Editable(0.0f, 1.0f, decimals: 3, ToolTip = "How full the ballast tanks should be when the submarine is not being steered upwards/downwards."
|
||||
+" Can be used to compensate if the ballast tanks are too large/small relative to the size of the submarine."), Serialize(0.5f, true)]
|
||||
public float NeutralBallastLevel
|
||||
{
|
||||
@@ -78,6 +86,13 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
[Serialize(1000.0f, true)]
|
||||
public float DockingAssistThreshold
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public Vector2 TargetVelocity
|
||||
{
|
||||
get { return targetVelocity;}
|
||||
@@ -88,12 +103,53 @@ namespace Barotrauma.Items.Components
|
||||
targetVelocity.Y = MathHelper.Clamp(value.Y, -100.0f, 100.0f);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public Vector2 SteeringInput
|
||||
{
|
||||
get { return steeringInput; }
|
||||
set
|
||||
{
|
||||
if (!MathUtils.IsValid(value)) return;
|
||||
steeringInput.X = MathHelper.Clamp(value.X, -100.0f, 100.0f);
|
||||
steeringInput.Y = MathHelper.Clamp(value.Y, -100.0f, 100.0f);
|
||||
}
|
||||
}
|
||||
|
||||
public SteeringPath SteeringPath
|
||||
{
|
||||
get { return steeringPath; }
|
||||
}
|
||||
|
||||
public Vector2? PosToMaintain
|
||||
{
|
||||
get { return posToMaintain; }
|
||||
set { posToMaintain = value; }
|
||||
}
|
||||
|
||||
struct ObstacleDebugInfo
|
||||
{
|
||||
public Vector2 Point1;
|
||||
public Vector2 Point2;
|
||||
|
||||
public Vector2? Intersection;
|
||||
|
||||
public float Dot;
|
||||
|
||||
public Vector2 AvoidStrength;
|
||||
|
||||
public ObstacleDebugInfo(GraphEdge edge, Vector2? intersection, float dot, Vector2 avoidStrength)
|
||||
{
|
||||
Point1 = edge.Point1;
|
||||
Point2 = edge.Point2;
|
||||
Intersection = intersection;
|
||||
Dot = dot;
|
||||
AvoidStrength = avoidStrength;
|
||||
}
|
||||
}
|
||||
|
||||
//edge point 1, edge point 2, avoid strength
|
||||
private List<ObstacleDebugInfo> debugDrawObstacles = new List<ObstacleDebugInfo>();
|
||||
|
||||
public Steering(Item item, XElement element)
|
||||
: base(item, element)
|
||||
{
|
||||
@@ -103,11 +159,24 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
partial void InitProjSpecific();
|
||||
|
||||
public override void OnItemLoaded()
|
||||
{
|
||||
sonar = item.GetComponent<Sonar>();
|
||||
}
|
||||
|
||||
public override bool Select(Character character)
|
||||
{
|
||||
if (!CanBeSelected) return false;
|
||||
|
||||
user = character;
|
||||
return true;
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime, Camera cam)
|
||||
{
|
||||
networkUpdateTimer -= deltaTime;
|
||||
if (unsentChanges)
|
||||
{
|
||||
networkUpdateTimer -= deltaTime;
|
||||
if (networkUpdateTimer <= 0.0f)
|
||||
{
|
||||
#if CLIENT
|
||||
@@ -123,14 +192,21 @@ namespace Barotrauma.Items.Components
|
||||
item.CreateServerEvent(this);
|
||||
}
|
||||
|
||||
networkUpdateTimer = 0.5f;
|
||||
networkUpdateTimer = 0.1f;
|
||||
unsentChanges = false;
|
||||
}
|
||||
}
|
||||
|
||||
controlledSub = item.Submarine;
|
||||
var sonar = item.GetComponent<Sonar>();
|
||||
if (sonar != null && sonar.UseTransducers)
|
||||
{
|
||||
controlledSub = sonar.ConnectedTransducers.Any() ? sonar.ConnectedTransducers.First().Item.Submarine : null;
|
||||
}
|
||||
|
||||
currPowerConsumption = powerConsumption;
|
||||
|
||||
if (voltage < minVoltage && currPowerConsumption > 0.0f) return;
|
||||
if (voltage < minVoltage && currPowerConsumption > 0.0f) { return; }
|
||||
|
||||
ApplyStatusEffects(ActionType.OnActive, deltaTime, null);
|
||||
|
||||
@@ -138,6 +214,30 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
UpdateAutoPilot(deltaTime);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (user != null && user.Info != null && user.SelectedConstruction == item)
|
||||
{
|
||||
user.Info.IncreaseSkillLevel("helm", 0.005f * deltaTime, user.WorldPosition + Vector2.UnitY * 150.0f);
|
||||
}
|
||||
|
||||
Vector2 velocityDiff = steeringInput - targetVelocity;
|
||||
if (velocityDiff != Vector2.Zero)
|
||||
{
|
||||
if (steeringAdjustSpeed >= 0.99f)
|
||||
{
|
||||
TargetVelocity = steeringInput;
|
||||
}
|
||||
else
|
||||
{
|
||||
float steeringChange = 1.0f / (1.0f - steeringAdjustSpeed);
|
||||
steeringChange *= steeringChange * 10.0f;
|
||||
|
||||
TargetVelocity += Vector2.Normalize(velocityDiff) *
|
||||
Math.Min(steeringChange * deltaTime, velocityDiff.Length());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
item.SendSignal(0, targetVelocity.X.ToString(CultureInfo.InvariantCulture), "velocity_x_out", null);
|
||||
|
||||
@@ -151,6 +251,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
private void UpdateAutoPilot(float deltaTime)
|
||||
{
|
||||
if (controlledSub == null) return;
|
||||
if (posToMaintain != null)
|
||||
{
|
||||
SteerTowardsPosition((Vector2)posToMaintain);
|
||||
@@ -158,34 +259,53 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
|
||||
autopilotRayCastTimer -= deltaTime;
|
||||
autopilotRecalculatePathTimer -= deltaTime;
|
||||
if (autopilotRecalculatePathTimer <= 0.0f)
|
||||
{
|
||||
//periodically recalculate the path in case the sub ends up to a position
|
||||
//where it can't keep traversing the initially calculated path
|
||||
UpdatePath();
|
||||
autopilotRecalculatePathTimer = RecalculatePathInterval;
|
||||
}
|
||||
|
||||
steeringPath.CheckProgress(ConvertUnits.ToSimUnits(item.Submarine.WorldPosition), 10.0f);
|
||||
steeringPath.CheckProgress(ConvertUnits.ToSimUnits(controlledSub.WorldPosition), 10.0f);
|
||||
|
||||
if (autopilotRayCastTimer <= 0.0f && steeringPath.NextNode != null)
|
||||
{
|
||||
Vector2 diff = Vector2.Normalize(ConvertUnits.ToSimUnits(steeringPath.NextNode.Position - item.Submarine.WorldPosition));
|
||||
Vector2 diff = ConvertUnits.ToSimUnits(steeringPath.NextNode.Position - controlledSub.WorldPosition);
|
||||
|
||||
bool nextVisible = true;
|
||||
for (int x = -1; x < 2; x += 2)
|
||||
//if the node is close enough, check if it's visible
|
||||
float lengthSqr = diff.LengthSquared();
|
||||
if (lengthSqr > 0.001f && lengthSqr < 500.0f)
|
||||
{
|
||||
for (int y = -1; y < 2; y += 2)
|
||||
diff = Vector2.Normalize(diff);
|
||||
|
||||
//check if the next waypoint is visible from all corners of the sub
|
||||
//(i.e. if we can navigate directly towards it or if there's obstacles in the way)
|
||||
bool nextVisible = true;
|
||||
for (int x = -1; x < 2; x += 2)
|
||||
{
|
||||
Vector2 cornerPos =
|
||||
new Vector2(item.Submarine.Borders.Width * x, item.Submarine.Borders.Height * y) / 2.0f;
|
||||
for (int y = -1; y < 2; y += 2)
|
||||
{
|
||||
Vector2 cornerPos =
|
||||
new Vector2(controlledSub.Borders.Width * x, controlledSub.Borders.Height * y) / 2.0f;
|
||||
|
||||
cornerPos = ConvertUnits.ToSimUnits(cornerPos * 1.2f + item.Submarine.WorldPosition);
|
||||
cornerPos = ConvertUnits.ToSimUnits(cornerPos * 1.2f + controlledSub.WorldPosition);
|
||||
|
||||
float dist = Vector2.Distance(cornerPos, steeringPath.NextNode.SimPosition);
|
||||
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;
|
||||
y = 2;
|
||||
nextVisible = false;
|
||||
x = 2;
|
||||
y = 2;
|
||||
}
|
||||
}
|
||||
|
||||
if (nextVisible) steeringPath.SkipToNextNode();
|
||||
}
|
||||
|
||||
if (nextVisible) steeringPath.SkipToNextNode();
|
||||
|
||||
|
||||
autopilotRayCastTimer = AutopilotRayCastInterval;
|
||||
}
|
||||
@@ -195,35 +315,48 @@ namespace Barotrauma.Items.Components
|
||||
SteerTowardsPosition(steeringPath.CurrentNode.WorldPosition);
|
||||
}
|
||||
|
||||
float avoidRadius = Math.Max(item.Submarine.Borders.Width, item.Submarine.Borders.Height) * 2.0f;
|
||||
avoidRadius = Math.Max(avoidRadius, 2000.0f);
|
||||
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));
|
||||
|
||||
float avoidRadius = avoidDist.Length();
|
||||
|
||||
Vector2 newAvoidStrength = Vector2.Zero;
|
||||
|
||||
debugDrawObstacles.Clear();
|
||||
|
||||
//steer away from nearby walls
|
||||
var closeCells = Level.Loaded.GetCells(item.Submarine.WorldPosition, 4);
|
||||
var closeCells = Level.Loaded.GetCells(controlledSub.WorldPosition, 4);
|
||||
foreach (VoronoiCell cell in closeCells)
|
||||
{
|
||||
foreach (GraphEdge edge in cell.edges)
|
||||
foreach (GraphEdge edge in cell.Edges)
|
||||
{
|
||||
var intersection = MathUtils.GetLineIntersection(edge.point1, edge.point2, item.Submarine.WorldPosition, cell.Center);
|
||||
if (intersection != null)
|
||||
if (MathUtils.GetLineIntersection(edge.Point1, edge.Point2, controlledSub.WorldPosition, cell.Center, out Vector2 intersection))
|
||||
{
|
||||
Vector2 diff = item.Submarine.WorldPosition - (Vector2)intersection;
|
||||
Vector2 diff = controlledSub.WorldPosition - intersection;
|
||||
|
||||
float dist = diff.Length();
|
||||
//far enough or too close to normalize the diff -> ignore
|
||||
if (dist > avoidRadius || dist < 0.00001f) continue;
|
||||
//far enough -> ignore
|
||||
if (Math.Abs(diff.X) > avoidDist.X && Math.Abs(diff.Y) > avoidDist.Y)
|
||||
{
|
||||
debugDrawObstacles.Add(new ObstacleDebugInfo(edge, intersection, 0.0f, Vector2.Zero));
|
||||
continue;
|
||||
}
|
||||
if (diff.LengthSquared() < 1.0f) diff = Vector2.UnitY;
|
||||
|
||||
float dot = item.Submarine.Velocity == Vector2.Zero ?
|
||||
0.0f : Vector2.Dot(item.Submarine.Velocity, -Vector2.Normalize(diff));
|
||||
Vector2 normalizedDiff = Vector2.Normalize(diff);
|
||||
float dot = controlledSub.Velocity == Vector2.Zero ?
|
||||
0.0f : Vector2.Dot(controlledSub.Velocity, -normalizedDiff);
|
||||
|
||||
//not heading towards the wall -> ignore
|
||||
if (dot < 0.5) continue;
|
||||
|
||||
Vector2 change = (Vector2.Normalize(diff) * Math.Max((avoidRadius - diff.Length()), 0.0f)) / avoidRadius;
|
||||
|
||||
if (dot < 0.5)
|
||||
{
|
||||
debugDrawObstacles.Add(new ObstacleDebugInfo(edge, intersection, dot, Vector2.Zero));
|
||||
continue;
|
||||
}
|
||||
|
||||
Vector2 change = (normalizedDiff * Math.Max((avoidRadius - diff.Length()), 0.0f)) / avoidRadius;
|
||||
newAvoidStrength += change * dot;
|
||||
debugDrawObstacles.Add(new ObstacleDebugInfo(edge, intersection, dot, change * dot));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -235,22 +368,21 @@ namespace Barotrauma.Items.Components
|
||||
//steer away from other subs
|
||||
foreach (Submarine sub in Submarine.Loaded)
|
||||
{
|
||||
if (sub == item.Submarine) continue;
|
||||
if (item.Submarine.DockedTo.Contains(sub)) continue;
|
||||
if (sub == controlledSub) continue;
|
||||
if (controlledSub.DockedTo.Contains(sub)) continue;
|
||||
|
||||
float thisSize = Math.Max(item.Submarine.Borders.Width, item.Submarine.Borders.Height);
|
||||
float thisSize = Math.Max(controlledSub.Borders.Width, controlledSub.Borders.Height);
|
||||
float otherSize = Math.Max(sub.Borders.Width, sub.Borders.Height);
|
||||
|
||||
Vector2 diff = item.Submarine.WorldPosition - sub.WorldPosition;
|
||||
Vector2 diff = controlledSub.WorldPosition - sub.WorldPosition;
|
||||
float dist = diff == Vector2.Zero ? 0.0f : diff.Length();
|
||||
|
||||
//far enough -> ignore
|
||||
if (dist > thisSize + otherSize) continue;
|
||||
|
||||
Vector2 dir = dist <= 0.0001f ? Vector2.UnitY : diff / dist;
|
||||
|
||||
float dot = item.Submarine.Velocity == Vector2.Zero ?
|
||||
0.0f : Vector2.Dot(Vector2.Normalize(item.Submarine.Velocity), -dir);
|
||||
float dot = controlledSub.Velocity == Vector2.Zero ?
|
||||
0.0f : Vector2.Dot(Vector2.Normalize(controlledSub.Velocity), -dir);
|
||||
|
||||
//heading away -> ignore
|
||||
if (dot < 0.0f) continue;
|
||||
@@ -264,7 +396,6 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
targetVelocity *= 100.0f / velMagnitude;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void UpdatePath()
|
||||
@@ -280,20 +411,15 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
target = ConvertUnits.ToSimUnits(Level.Loaded.StartPosition);
|
||||
}
|
||||
|
||||
|
||||
steeringPath = pathFinder.FindPath(ConvertUnits.ToSimUnits(item.WorldPosition), target);
|
||||
steeringPath = pathFinder.FindPath(ConvertUnits.ToSimUnits(controlledSub == null ? item.WorldPosition : controlledSub.WorldPosition), target, "(Autopilot, target: " + target + ")");
|
||||
}
|
||||
|
||||
public void SetDestinationLevelStart()
|
||||
{
|
||||
AutoPilot = true;
|
||||
|
||||
MaintainPos = false;
|
||||
posToMaintain = null;
|
||||
|
||||
LevelEndSelected = false;
|
||||
|
||||
if (!LevelStartSelected)
|
||||
{
|
||||
LevelStartSelected = true;
|
||||
@@ -303,13 +429,10 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
public void SetDestinationLevelEnd()
|
||||
{
|
||||
AutoPilot = false;
|
||||
|
||||
AutoPilot = true;
|
||||
MaintainPos = false;
|
||||
posToMaintain = null;
|
||||
|
||||
LevelStartSelected = false;
|
||||
|
||||
if (!LevelEndSelected)
|
||||
{
|
||||
LevelEndSelected = true;
|
||||
@@ -320,10 +443,10 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
float prediction = 10.0f;
|
||||
|
||||
Vector2 futurePosition = ConvertUnits.ToDisplayUnits(item.Submarine.Velocity) * prediction;
|
||||
Vector2 targetSpeed = ((worldPosition - item.Submarine.WorldPosition) - futurePosition);
|
||||
Vector2 futurePosition = ConvertUnits.ToDisplayUnits(controlledSub.Velocity) * prediction;
|
||||
Vector2 targetSpeed = ((worldPosition - controlledSub.WorldPosition) - futurePosition);
|
||||
|
||||
if (targetSpeed.Length()>500.0f)
|
||||
if (targetSpeed.Length() > 500.0f)
|
||||
{
|
||||
targetSpeed = Vector2.Normalize(targetSpeed);
|
||||
TargetVelocity = targetSpeed * 100.0f;
|
||||
@@ -333,8 +456,52 @@ namespace Barotrauma.Items.Components
|
||||
TargetVelocity = targetSpeed / 5.0f;
|
||||
}
|
||||
}
|
||||
|
||||
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power=0.0f)
|
||||
|
||||
public override bool AIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective)
|
||||
{
|
||||
if (user != character && user != null && user.SelectedConstruction == item)
|
||||
{
|
||||
character.Speak(TextManager.Get("DialogSteeringTaken"), null, 0.0f, "steeringtaken", 10.0f);
|
||||
}
|
||||
|
||||
user = character;
|
||||
|
||||
switch (objective.Option.ToLowerInvariant())
|
||||
{
|
||||
case "maintainposition":
|
||||
if (!posToMaintain.HasValue)
|
||||
{
|
||||
unsentChanges = true;
|
||||
posToMaintain = controlledSub == null ? item.WorldPosition : controlledSub.WorldPosition;
|
||||
}
|
||||
|
||||
if (!AutoPilot || !MaintainPos) unsentChanges = true;
|
||||
|
||||
AutoPilot = true;
|
||||
MaintainPos = true;
|
||||
break;
|
||||
case "navigateback":
|
||||
if (!AutoPilot || MaintainPos || LevelEndSelected || !LevelStartSelected)
|
||||
{
|
||||
unsentChanges = true;
|
||||
}
|
||||
SetDestinationLevelStart();
|
||||
break;
|
||||
case "navigatetodestination":
|
||||
if (!AutoPilot || MaintainPos || !LevelEndSelected || LevelStartSelected)
|
||||
{
|
||||
unsentChanges = true;
|
||||
}
|
||||
SetDestinationLevelEnd();
|
||||
break;
|
||||
}
|
||||
|
||||
sonar?.AIOperate(deltaTime, character, objective);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power = 0.0f, float signalStrength = 1.0f)
|
||||
{
|
||||
if (connection.Name == "velocity_in")
|
||||
{
|
||||
@@ -342,14 +509,14 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
else
|
||||
{
|
||||
base.ReceiveSignal(stepsTaken, signal, connection, source, sender, power);
|
||||
base.ReceiveSignal(stepsTaken, signal, connection, source, sender, power, signalStrength);
|
||||
}
|
||||
}
|
||||
|
||||
public void ServerRead(ClientNetObject type, Lidgren.Network.NetBuffer msg, Barotrauma.Networking.Client c)
|
||||
{
|
||||
bool autoPilot = msg.ReadBoolean();
|
||||
Vector2 newTargetVelocity = targetVelocity;
|
||||
Vector2 newSteeringInput = targetVelocity;
|
||||
bool maintainPos = false;
|
||||
Vector2? newPosToMaintain = null;
|
||||
bool headingToStart = false;
|
||||
@@ -370,20 +537,22 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
else
|
||||
{
|
||||
newTargetVelocity = new Vector2(msg.ReadFloat(), msg.ReadFloat());
|
||||
newSteeringInput = new Vector2(msg.ReadFloat(), msg.ReadFloat());
|
||||
}
|
||||
|
||||
if (!item.CanClientAccess(c)) return;
|
||||
if (!item.CanClientAccess(c)) return;
|
||||
|
||||
user = c.Character;
|
||||
|
||||
AutoPilot = autoPilot;
|
||||
|
||||
if (!AutoPilot)
|
||||
{
|
||||
targetVelocity = newTargetVelocity;
|
||||
steeringInput = newSteeringInput;
|
||||
steeringAdjustSpeed = MathHelper.Lerp(0.2f, 1.0f, c.Character.GetSkillLevel("helm") / 100.0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
MaintainPos = newPosToMaintain != null;
|
||||
posToMaintain = newPosToMaintain;
|
||||
|
||||
@@ -411,8 +580,11 @@ namespace Barotrauma.Items.Components
|
||||
if (!autoPilot)
|
||||
{
|
||||
//no need to write steering info if autopilot is controlling
|
||||
msg.Write(steeringInput.X);
|
||||
msg.Write(steeringInput.Y);
|
||||
msg.Write(targetVelocity.X);
|
||||
msg.Write(targetVelocity.Y);
|
||||
msg.Write(steeringAdjustSpeed);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user