3 using Microsoft.Xna.Framework;
5 using System.Globalization;
12 const float NetworkUpdateIntervalHigh = 0.5f;
14 const float TemperatureBoostAmount = 25;
17 private float fissionRate;
20 private float turbineOutput;
22 private float temperature;
27 private bool autoTemp;
29 private float fuelConsumptionRate;
31 private float meltDownTimer, meltDownDelay;
32 private float fireTimer, fireDelay;
34 private float maxPowerOutput;
35 private float minUpdatePowerOut;
36 private float maxUpdatePowerOut;
38 private bool unsentChanges;
39 private float sendUpdateTimer;
41 private float degreeOfSuccess;
43 private Vector2 optimalTemperature, allowedTemperature;
44 private Vector2 optimalFissionRate, allowedFissionRate;
45 private Vector2 optimalTurbineOutput, allowedTurbineOutput;
47 private float? signalControlledTargetFissionRate, signalControlledTargetTurbineOutput;
48 private double lastReceivedFissionRateSignalTime, lastReceivedTurbineOutputSignalTime;
50 private float temperatureBoost;
54 private bool _powerOn;
59 get {
return _powerOn; }
64 UpdateUIElementStates();
79 get {
return lastUser; }
82 if (lastUser == value) {
return; }
86 degreeOfSuccess = 0.0f;
97 [
Editable(0.0f,
float.MaxValue),
Serialize(10000.0f,
IsPropertySaveable.Yes, description:
"How much power (kW) the reactor generates when operating at full capacity.", alwaysUseInstanceValues:
true)]
100 get => maxPowerOutput;
101 set => maxPowerOutput = Math.Max(0.0f, value);
107 get {
return meltDownDelay; }
108 set { meltDownDelay = Math.Max(value, 0.0f); }
114 get {
return fireDelay; }
115 set { fireDelay = Math.Max(value, 0.0f); }
118 [
Serialize(0.0f,
IsPropertySaveable.Yes, description:
"Current temperature of the reactor (0% - 100%). Indended to be used by StatusEffect conditionals.")]
121 get {
return temperature; }
124 if (!MathUtils.IsValid(value))
return;
125 temperature = MathHelper.Clamp(value, 0.0f, 100.0f);
129 [
Serialize(0.0f,
IsPropertySaveable.Yes, description:
"Current fission rate of the reactor (0% - 100%). Intended to be used by StatusEffect conditionals (setting the value from XML is not recommended).")]
132 get {
return fissionRate; }
135 if (!MathUtils.IsValid(value))
return;
136 fissionRate = MathHelper.Clamp(value, 0.0f, 100.0f);
140 [
Serialize(0.0f,
IsPropertySaveable.Yes, description:
"Current turbine output of the reactor (0% - 100%). Intended to be used by StatusEffect conditionals (setting the value from XML is not recommended).")]
143 get {
return turbineOutput; }
146 if (!MathUtils.IsValid(value))
return;
147 turbineOutput = MathHelper.Clamp(value, 0.0f, 100.0f);
154 get => fuelConsumptionRate;
157 if (!MathUtils.IsValid(value))
return;
158 fuelConsumptionRate = Math.Max(value, 0.0f);
162 [
Serialize(
false,
IsPropertySaveable.Yes, description:
"Is the temperature currently critical. Intended to be used by StatusEffect conditionals (setting the value from XML has no effect).")]
165 get {
return temperature > allowedTemperature.Y; }
169 [
Serialize(
false,
IsPropertySaveable.Yes, description:
"Is the automatic temperature control currently on. Indended to be used by StatusEffect conditionals (setting the value from XML is not recommended).")]
172 get {
return autoTemp; }
177 UpdateUIElementStates();
182 private float prevAvailableFuel;
188 public new float Load {
get;
private set; }
209 : base(
item, element)
212 InitProjSpecific(element);
220 if (
GameMain.Server !=
null && nextServerLogWriteTime !=
null)
222 if (Timing.TotalTime >= (
float)nextServerLogWriteTime)
224 GameServer.Log(GameServer.CharacterLogName(lastUser) +
" adjusted reactor settings: " +
225 "Temperature: " + (
int)(temperature * 100.0f) +
228 (autoTemp ?
", Autotemp ON" :
", Autotemp OFF"),
231 nextServerLogWriteTime =
null;
232 lastServerLogWriteTime = (float)Timing.TotalTime;
252 HintManager.OnReactorOutOfFuel(
this);
256 float maxPowerOut = GetMaxOutput();
258 if (signalControlledTargetFissionRate.HasValue && lastReceivedFissionRateSignalTime > Timing.TotalTime - 1)
267 signalControlledTargetFissionRate =
null;
269 if (signalControlledTargetTurbineOutput.HasValue && lastReceivedTurbineOutputSignalTime > Timing.TotalTime - 1)
278 signalControlledTargetTurbineOutput =
null;
281 static float adjustValueWithoutOverShooting(
float current,
float target,
float speed)
283 return target < current ? Math.Max(target, current - speed) : Math.Min(target, current + speed);
291 if (!MathUtils.NearlyEqual(maxPowerOut, 0.0f))
298 float tolerance = MathHelper.Lerp(2.5f, 10.0f, degreeOfSuccess);
300 tolerance = MathHelper.Lerp(5.0f, 20.0f, degreeOfSuccess);
303 optimalTemperature = Vector2.Lerp(
new Vector2(40.0f, 60.0f),
new Vector2(30.0f, 70.0f), degreeOfSuccess);
304 allowedTemperature = Vector2.Lerp(
new Vector2(30.0f, 70.0f),
new Vector2(10.0f, 90.0f), degreeOfSuccess);
307 optimalFissionRate.X = Math.Min(optimalFissionRate.X, optimalFissionRate.Y - 10);
309 allowedFissionRate.X = Math.Min(allowedFissionRate.X, allowedFissionRate.Y - 10);
311 float heatAmount = GetGeneratedHeat(fissionRate);
313 float temperatureDiff = (heatAmount - turbineOutput) -
Temperature;
314 Temperature += MathHelper.Clamp(Math.Sign(temperatureDiff) * 10.0f * deltaTime, -Math.Abs(temperatureDiff), Math.Abs(temperatureDiff));
315 temperatureBoost = adjustValueWithoutOverShooting(temperatureBoost, 0.0f, deltaTime);
324 float temperatureFactor = Math.Min(temperature / 50.0f, 1.0f);
337 float fuelLeft = 0.0f;
339 if (containedItems !=
null)
341 foreach (
Item item in containedItems)
344 if (fissionRate > 0.0f)
350 if (!isConnectedToFriendlyOutpost)
352 item.
Condition -= fissionRate / 100.0f * GetFuelConsumption() * deltaTime;
359 if (fissionRate > 0.0f)
365 aiTarget.
SoundRange = MathHelper.Lerp(aiTarget.MinSoundRange, aiTarget.MaxSoundRange, range);
369 if (hullAITarget !=
null)
371 hullAITarget.
SoundRange = Math.Max(hullAITarget.SoundRange, aiTarget.SoundRange);
377 item.
SendSignal(((
int)(temperature * 100.0f)).ToString(),
"temperature_out");
381 item.
SendSignal(((
int)fuelLeft).ToString(),
"fuel_percentage_left");
383 UpdateFailures(deltaTime);
385 UpdateGraph(deltaTime);
390 sendUpdateTimer -= deltaTime;
392 if (unsentChanges && sendUpdateTimer <= 0.0f)
394 if (sendUpdateTimer < -NetworkUpdateIntervalLow || (unsentChanges && sendUpdateTimer <= 0.0f))
400 item.CreateServerEvent(
this);
405 item.CreateClientEvent(
this);
408 sendUpdateTimer = NetworkUpdateIntervalHigh;
409 unsentChanges =
false;
418 return connection !=
null && connection.IsPower && connection.IsOutput ? -1 : 0;
426 float tolerance = 1f;
429 if (turbineOutput > optimalTurbineOutput.X && turbineOutput < optimalTurbineOutput.Y &&
430 temperature > optimalTemperature.X && temperature < optimalTemperature.Y)
435 float maxPowerOut = GetMaxOutput();
437 float temperatureFactor = Math.Min(temperature / 50.0f, 1.0f);
438 float minOutput = maxPowerOut * Math.Clamp(Math.Min((turbineOutput - tolerance) / 100.0f, temperatureFactor), 0, 1);
439 float maxOutput = maxPowerOut * Math.Min((turbineOutput + tolerance) / 100.0f, temperatureFactor);
441 minUpdatePowerOut = minOutput;
442 maxUpdatePowerOut = maxOutput;
444 float reactorMax =
PowerOn ? maxPowerOut : maxUpdatePowerOut;
446 return new PowerRange(minOutput, maxOutput, reactorMax);
455 float loadLeft = MathHelper.Max(load - power,0);
456 float expectedPower = MathHelper.Clamp(loadLeft, minMaxPower.
Min, minMaxPower.
Max);
459 float ratio = MathHelper.Max((loadLeft - minMaxPower.
Min) / (minMaxPower.
Max - minMaxPower.
Min), 0);
460 if (
float.IsInfinity(ratio))
465 float output = MathHelper.Clamp(ratio * (maxUpdatePowerOut - minUpdatePowerOut) + minUpdatePowerOut, minUpdatePowerOut, maxUpdatePowerOut);
466 float newLoad = loadLeft;
468 float maxOutput = GetMaxOutput();
474 float loadAdjust = MathHelper.Clamp((ratio - 0.5f) * 25 + idealLoad - (turbineOutput / 100 * maxOutput), -maxOutput / 100, maxOutput / 100);
475 newLoad = MathHelper.Clamp(loadLeft - (expectedPower - output) + loadAdjust, 0, loadLeft);
478 if (
float.IsNegative(newLoad))
488 private float GetGeneratedHeat(
float fissionRate)
490 return fissionRate * (prevAvailableFuel / 100.0f) * 2.0f + temperatureBoost;
498 private bool NeedMoreFuel(
float minimumOutputRatio,
float minCondition = 0)
501 if (remainingFuel <= minCondition && Load > 0.0f)
507 float maxFissionRate = Math.Min(prevAvailableFuel, 100.0f);
508 if (maxFissionRate >= 100.0f) {
return false; }
510 float maxTurbineOutput = 100.0f;
513 float theoreticalMaxHeat = GetGeneratedHeat(fissionRate: maxFissionRate);
514 float temperatureFactor = Math.Min(theoreticalMaxHeat / 50.0f, 1.0f);
515 float theoreticalMaxOutput = Math.Min(maxTurbineOutput / 100.0f, temperatureFactor) * GetMaxOutput();
518 return theoreticalMaxOutput <
Load * minimumOutputRatio;
521 private bool TooMuchFuel()
524 if (containedItems !=
null && containedItems.Count() <= 1) {
return false; }
527 float minimumHeat = GetGeneratedHeat(optimalFissionRate.X);
533 private void UpdateFailures(
float deltaTime)
535 if (temperature > allowedTemperature.Y)
552 meltDownTimer = Math.Max(0.0f, meltDownTimer - deltaTime);
555 if (temperature > optimalTemperature.Y)
559 if (fireTimer > Math.Min(5.0f,
FireDelay / 2) && blameOnBroken?.Character?.SelectedItem ==
item)
561 GameMain.Server.KarmaManager.OnReactorOverHeating(
item, blameOnBroken.Character, deltaTime);
572 fireTimer = Math.Max(0.0f, fireTimer - deltaTime);
578 float desiredTurbineOutput = (optimalTurbineOutput.X + optimalTurbineOutput.Y) / 2.0f;
582 float desiredFissionRate = (optimalFissionRate.X + optimalFissionRate.Y) / 2.0f;
585 if (temperature > (optimalTemperature.X + optimalTemperature.Y) / 2.0f)
606 for (
int i = 0; i < 100; i++)
608 Update((
float)(Timing.Step * 10.0f), cam:
null);
616 base.UpdateBroken(deltaTime, cam);
618 item.
SendSignal(((
int)(temperature * 100.0f)).ToString(),
"temperature_out");
627 UpdateGraph(deltaTime);
631 private void MeltDown()
640 foreach (
Explosion explosion
in statusEffect.Explosions)
652 meltDownTimer = 0.0f;
656 if (containedItems !=
null)
658 foreach (
Item containedItem
in containedItems)
660 containedItem.Condition = 0.0f;
665 if (GameMain.Server !=
null)
667 GameMain.Server.KarmaManager.OnReactorMeltdown(
item, blameOnBroken?.Character);
674 return picker !=
null;
681 bool shutDown = objective.
Option ==
"shutdown";
688 float refuelLimit = 0.3f;
690 if (degreeOfSuccess > refuelLimit)
700 float minCondition = GetFuelConsumption() * MathUtils.Pow2((degreeOfSuccess - refuelLimit) * 2);
701 if (NeedMoreFuel(minimumOutputRatio: 0.5f, minCondition: minCondition))
703 bool outOfFuel =
false;
707 var containObjective = AIContainItems<Reactor>(container, character, objective, itemCount: 1, equip:
true, removeEmpty:
true, spawnItemIfNotFound: !character.
IsOnPlayerTeam, dropItemOnDeselected:
true);
708 containObjective.Completed += ReportFuelRodCount;
709 containObjective.Abandoned += ReportFuelRodCount;
710 character.
Speak(TextManager.Get(
"DialogReactorFuel").Value,
null, 0.0f, Tags.Fuel, 30.0f);
712 void ReportFuelRodCount()
717 if (remainingFuelRods == 0)
719 character.
Speak(TextManager.Get(
"DialogOutOfFuelRods").Value,
null, 0.0f,
"outoffuelrods".ToIdentifier(), 30.0f);
722 else if (remainingFuelRods < 3)
724 character.
Speak(TextManager.Get(
"DialogLowOnFuelRods").Value,
null, 0.0f,
"lowonfuelrods".ToIdentifier(), 30.0f);
734 if (
Item.
Repairables.Average(r => r.DegreeOfSuccess(character)) > 0.4f)
741 character.
Speak(TextManager.Get(
"DialogReactorIsBroken").Value, identifier:
"reactorisbroken".ToIdentifier(), minDurationBetweenSimilar: 30.0f);
746 DropFuel(minCondition: 0.1f, maxCondition: 100);
750 DropFuel(minCondition: 0, maxCondition: 0);
758 if (lastUser !=
null && lastUser != character && lastUser !=
LastAIUser)
762 character.
Speak(TextManager.Get(
"DialogReactorTaken").Value,
null, 0.0f,
"reactortaken".ToIdentifier(), 10.0f);
773 bool prevAutoTemp = autoTemp;
774 bool prevPowerOn = _powerOn;
783 unsentChanges =
true;
789 if (objective.
Override || !autoTemp)
792 if (degreeOfSuccess < 0.5f)
799 UpdateAutoTemp(MathHelper.Lerp(0.5f, 2.0f, degreeOfSuccess), 1.0f);
806 if (autoTemp != prevAutoTemp ||
807 prevPowerOn != _powerOn ||
811 unsentChanges =
true;
818 void DropFuel(
float minCondition,
float maxCondition)
842 switch (connection.
Name)
851 registerUnsentChanges();
854 case "set_fissionrate":
855 if (
PowerOn &&
float.TryParse(signal.
value, NumberStyles.Float, CultureInfo.InvariantCulture, out
float newFissionRate))
857 signalControlledTargetFissionRate = MathHelper.Clamp(newFissionRate, 0.0f, 100.0f);
858 lastReceivedFissionRateSignalTime = Timing.TotalTime;
859 registerUnsentChanges();
862 case "set_turbineoutput":
863 if (
PowerOn &&
float.TryParse(signal.
value, NumberStyles.Float, CultureInfo.InvariantCulture, out
float newTurbineOutput))
865 signalControlledTargetTurbineOutput = MathHelper.Clamp(newTurbineOutput, 0.0f, 100.0f);
866 lastReceivedTurbineOutputSignalTime = Timing.TotalTime;
867 registerUnsentChanges();
872 void registerUnsentChanges()
879 private float GetFuelConsumption() =>
item.
StatManager.GetAdjustedValueMultiplicative(ItemTalentStats.ReactorFuelConsumption, fuelConsumptionRate);
SteeringManager SteeringManager
IEnumerable< AIObjective > SubObjectives
void AddSubObjective(AIObjective objective, bool addFirst=false)
readonly Identifier Option
readonly AIObjectiveManager objectiveManager
override bool IsValidTarget(Item item)
void Speak(string message, ChatMessageType? messageType=null, float delay=0.0f, Identifier identifier=default, float minDurationBetweenSimilar=0.0f)
bool CanInteractWith(Character c, float maxDist=200.0f, bool checkVisibility=true, bool skipDistanceCheck=false)
virtual AIController AIController
Item????????? SelectedItem
The primary selected item. It can be any device that character interacts with. This excludes items li...
Explosions are area of effect attacks that can damage characters, items and structures.
readonly HashSet< Submarine > IgnoredSubmarines
static NetworkMember NetworkMember
virtual IEnumerable< Item > AllItems
All items contained in the inventory. Stacked items are returned as individual instances....
IEnumerable< Item > AllItemsMod
All items contained in the inventory. Allows modifying the contents of the inventory while being enum...
void Drop(Character dropper, bool createNetworkEvent=true, bool setTransform=true)
ItemInventory OwnInventory
bool InvulnerableToDamage
ItemStatManager StatManager
List< Repairable > Repairables
bool HasTag(Identifier tag)
IEnumerable< Item > ContainedItems
float ConditionPercentage
void SendSignal(string signal, string connectionName)
void ApplyStatusEffects(ActionType type, float deltaTime, Character character=null, Limb targetLimb=null, Entity useTarget=null, Character user=null, Vector2? worldPosition=null, float afflictionMultiplier=1.0f)
readonly Dictionary< ActionType, List< StatusEffect > > statusEffectLists
const float AIUpdateInterval
float DegreeOfSuccess(Character character)
Returns 0.0f-1.0f based on how well the Character can use the itemcomponent
float CurrPowerConsumption
float currPowerConsumption
The amount of power currently consumed by the item. Negative values mean that the item is providing p...
float CorrectTurbineOutput
override PowerRange MinMaxPowerOut(Connection conn, float load)
Min and Max power output of the reactor based on tolerance
override void Update(float deltaTime, Camera cam)
bool ExplosionDamagesOtherSubs
override PowerPriority Priority
override void UpdateBroken(float deltaTime, Camera cam)
Reactor(Item item, ContentXElement element)
override bool Pick(Character picker)
a Character has picked the item
bool AllowTemperatureBoost
GUIScrollBar TurbineOutputScrollBar
override float GetConnectionPowerOut(Connection conn, float power, PowerRange minMaxPower, float load)
Determine how much power to output based on the load. The load is divided between reactors according ...
void UpdateAutoTemp(float speed, float deltaTime)
override float GetCurrentPowerConsumption(Connection connection=null)
Returns a negative value (indicating the reactor generates power) when querying the power output conn...
override bool CrewAIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective)
true if the operation was completed
float TargetTurbineOutput
GUIScrollBar FissionRateScrollBar
override void OnMapLoaded()
Called when all items have been loaded. Use to initialize connections between items.
void PowerUpImmediately()
override void ReceiveSignal(Signal signal, Connection connection)
float FuelConsumptionRate
static bool IsLoadedOutpost
Is there a loaded level set and is it an outpost?
List< Item > GetItems(bool alsoFromConnectedSubs)
IEnumerable< Submarine > GetConnectedSubs()
Returns a list of all submarines that are connected to this one via docking ports,...
static List< Submarine > Loaded
Interface for entities that the clients can send events to the server
Interface for entities that the server can send events to the clients
PowerPriority
Order in which power sources will provide to a grid, lower number is higher priority
ActionType
ActionTypes define when a StatusEffect is executed.
readonly float ReactorMaxOutput
Used by reactors to communicate their maximum output to each other so they can divide the grid load b...