Files
LuaCsForBarotraumaEP/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Reactor.cs
2019-03-27 20:52:47 +02:00

546 lines
20 KiB
C#

using Barotrauma.Networking;
using Lidgren.Network;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Items.Components
{
partial class Reactor : Powered, IServerSerializable, IClientSerializable
{
const float NetworkUpdateInterval = 0.5f;
//the rate at which the reactor is being run on (higher rate -> higher temperature)
private float fissionRate;
//how much of the generated steam is used to spin the turbines and generate power
private float turbineOutput;
private float temperature;
//is automatic temperature control on
//(adjusts the fission rate and turbine output automatically to keep the
//amount of power generated balanced with the load)
private bool autoTemp;
//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, meltDownDelay;
private float fireTimer, fireDelay;
private float maxPowerOutput;
private float load;
private bool unsentChanges;
private float sendUpdateTimer;
private float degreeOfSuccess;
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 lastUser; }
set
{
if (lastUser == value) return;
lastUser = value;
degreeOfSuccess = lastUser == null ? 0.0f : DegreeOfSuccess(lastUser);
}
}
[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(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 fireDelay; }
set { fireDelay = Math.Max(value, 0.0f); }
}
[Serialize(0.0f, true)]
public float Temperature
{
get { return temperature; }
set
{
if (!MathUtils.IsValid(value)) return;
temperature = MathHelper.Clamp(value, 0.0f, 100.0f);
}
}
[Serialize(0.0f, true)]
public float FissionRate
{
get { return fissionRate; }
set
{
if (!MathUtils.IsValid(value)) return;
fissionRate = MathHelper.Clamp(value, 0.0f, 100.0f);
}
}
[Serialize(0.0f, true)]
public float TurbineOutput
{
get { return turbineOutput; }
set
{
if (!MathUtils.IsValid(value)) return;
turbineOutput = MathHelper.Clamp(value, 0.0f, 100.0f);
}
}
[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 fuelConsumptionRate; }
set
{
if (!MathUtils.IsValid(value)) return;
fuelConsumptionRate = Math.Max(value, 0.0f);
}
}
private float correctTurbineOutput;
private float targetFissionRate;
private float targetTurbineOutput;
[Serialize(false, true)]
public bool AutoTemp
{
get { return autoTemp; }
set
{
autoTemp = value;
#if CLIENT
if (autoTempSlider != null)
{
autoTempSlider.BarScroll = value ?
Math.Min(0.45f, autoTempSlider.BarScroll) :
Math.Max(0.55f, autoTempSlider.BarScroll);
}
#endif
}
}
private float prevAvailableFuel;
public float AvailableFuel { get; set; }
public Reactor(Item item, XElement element)
: base(item, element)
{
IsActive = true;
InitProjSpecific(element);
}
partial void InitProjSpecific(XElement element);
public override void Update(float deltaTime, Camera cam)
{
#if SERVER
if (GameMain.Server != null && nextServerLogWriteTime != null)
{
if (Timing.TotalTime >= (float)nextServerLogWriteTime)
{
GameServer.Log(lastUser.LogName + " adjusted reactor settings: " +
"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;
}
}
#endif
prevAvailableFuel = AvailableFuel;
ApplyStatusEffects(ActionType.OnActive, deltaTime, null);
//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;
//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);
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);
optimalFissionRate = Vector2.Lerp(new Vector2(30, AvailableFuel - 20), new Vector2(20, AvailableFuel - 10), degreeOfSuccess);
optimalFissionRate.X = Math.Min(optimalFissionRate.X, optimalFissionRate.Y - 10);
allowedFissionRate = Vector2.Lerp(new Vector2(20, AvailableFuel), new Vector2(10, AvailableFuel), degreeOfSuccess);
allowedFissionRate.X = Math.Min(allowedFissionRate.X, allowedFissionRate.Y - 10);
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;
FissionRate = MathHelper.Lerp(fissionRate, Math.Min(targetFissionRate, AvailableFuel), deltaTime);
TurbineOutput = MathHelper.Lerp(turbineOutput, targetTurbineOutput, deltaTime);
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)
{
float maxAutoAdjust = maxPowerOutput * 0.1f;
autoAdjustAmount = MathHelper.Lerp(
autoAdjustAmount,
MathHelper.Clamp(-load - currPowerConsumption, -maxAutoAdjust, maxAutoAdjust),
deltaTime * 10.0f);
}
else
{
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)
{
foreach (Connection connection in connections)
{
if (!connection.IsPower) continue;
foreach (Connection recipient in connection.Recipients)
{
if (!(recipient.Item is Item it)) continue;
PowerTransfer pt = it.GetComponent<PowerTransfer>();
if (pt == null) continue;
load = Math.Max(load, pt.PowerLoad);
}
}
}
if (fissionRate > 0.0f)
{
foreach (Item item in item.ContainedItems)
{
if (!item.HasTag("reactorfuel")) continue;
item.Condition -= fissionRate / 100.0f * fuelConsumptionRate * deltaTime;
}
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
AvailableFuel = 0.0f;
sendUpdateTimer = Math.Max(sendUpdateTimer - deltaTime, 0.0f);
if (unsentChanges && sendUpdateTimer <= 0.0f)
{
#if SERVER
if (GameMain.Server != null)
{
item.CreateServerEvent(this);
}
#endif
#if CLIENT
if (GameMain.Client != null)
{
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 / item.MaxCondition);
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 / item.MaxCondition);
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;
targetTurbineOutput = MathHelper.Clamp(targetTurbineOutput, 0.0f, 100.0f);
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, 100.0f);
}
targetFissionRate = MathHelper.Clamp(targetFissionRate, 0.0f, 100.0f);
}
public override void UpdateBroken(float deltaTime, Camera cam)
{
base.UpdateBroken(deltaTime, cam);
currPowerConsumption = 0.0f;
Temperature -= deltaTime * 1000.0f;
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
}
private void MeltDown()
{
if (item.Condition <= 0.0f) return;
#if CLIENT
if (GameMain.Client != null) return;
#endif
#if SERVER
GameServer.Log("Reactor meltdown!", ServerLog.MessageType.ItemInteraction);
#endif
item.Condition = 0.0f;
fireTimer = 0.0f;
meltDownTimer = 0.0f;
var containedItems = item.ContainedItems;
if (containedItems != null)
{
foreach (Item containedItem in containedItems)
{
if (containedItem == null) continue;
containedItem.Condition = 0.0f;
}
}
#if SERVER
if (GameMain.Server != null && GameMain.Server.ConnectedClients.Contains(blameOnBroken))
{
blameOnBroken.Karma = 0.0f;
}
#endif
}
public override bool Pick(Character picker)
{
return picker != null;
}
public override bool AIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective)
{
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return false; }
float degreeOfSuccess = DegreeOfSuccess(character);
//characters with insufficient skill levels don't refuel the reactor
if (degreeOfSuccess > 0.2f)
{
//remove used-up fuel from the reactor
var containedItems = item.ContainedItems;
foreach (Item item in containedItems)
{
if (item != null && item.Condition <= 0.0f)
{
item.Drop(character);
}
}
//we need more fuel
if (-currPowerConsumption < load * 0.5f && prevAvailableFuel <= 0.0f)
{
var containFuelObjective = new AIObjectiveContainItem(character, new string[] { "fuelrod", "reactorfuel" }, item.GetComponent<ItemContainer>())
{
MinContainedAmount = containedItems.Count(i => i != null && i.Prefab.Identifier == "fuelrod" || i.HasTag("reactorfuel")) + 1,
GetItemPriority = (Item fuelItem) =>
{
if (fuelItem.ParentInventory?.Owner is Item)
{
//don't take fuel from other reactors
if (((Item)fuelItem.ParentInventory.Owner).GetComponent<Reactor>() != null) return 0.0f;
}
return 1.0f;
}
};
objective.AddSubObjective(containFuelObjective);
character?.Speak(TextManager.Get("DialogReactorFuel"), null, 0.0f, "reactorfuel", 30.0f);
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 "powerup":
shutDown = false;
//characters with insufficient skill levels simply set the autotemp on instead of trying to adjust the temperature manually
if (degreeOfSuccess < 0.5f)
{
if (!autoTemp) unsentChanges = true;
AutoTemp = true;
}
else
{
AutoTemp = false;
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":
#if CLIENT
onOffSwitch.BarScroll = 1.0f;
#endif
if (AutoTemp || !shutDown || targetFissionRate > 0.0f || targetTurbineOutput > 0.0f)
{
unsentChanges = true;
}
AutoTemp = false;
shutDown = true;
targetFissionRate = 0.0f;
targetTurbineOutput = 0.0f;
break;
}
aiUpdateTimer = AIUpdateInterval;
return false;
}
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 (targetFissionRate > 0.0f || targetTurbineOutput > 0.0f)
{
shutDown = true;
AutoTemp = false;
targetFissionRate = 0.0f;
targetTurbineOutput = 0.0f;
unsentChanges = true;
#if CLIENT
onOffSwitch.BarScroll = 1.0f;
#endif
}
break;
}
}
}
}