Files
tbd-station-14/Content.Server/Power/Generator/GeneratorSystem.cs
TemporalOroboros d75e743dd7 Solution Entities (#21916)
* Creates Content.Shared.Chemistry.Solutions
Copies Solution class to new namespace
Obsoletes old Solution class

* Switches over to the Solutions.Solution Solution

* Creates Content.Shared.Chemistry.Containers
Copies relevant components/systems to the new namespace
Obsoletes old versions

* Switches over to the Containers.XYZ namespace

* Creates SolutionSystem and obsoletes old SolutionContainerSystem methods

* Start using SolutionSystem for Solution manipulation

* EnumerateSolutions

* Move TryGetMixableSolution

* Move EnsureSolution to Server

* Create Solution Entities

* Stop using obsolete solution system methods

* Fix prototype component tests

* Add using ..Audio.Systems; back

* Wrap solution container slots in ContainerSlots

* Actually add the slot to the solution container map

* Dirty SolutionContainerComponent when ensuring solutions

* Revert namespace changes

* Remerge SolutionSystem and SolutionContainerSystem

* SolutionContainerManagerComponent refactor

* Avoid wrapping necessary code in DebugTools.Assert as it is removed when compiling for release

* Readd examine reagent sorting

* Fix errors

* Poke tests

* Fix solution names not being applied

* Fix WoolyComponent including statement

* Fix merge skew

* Fix compile errors

* Make reactions use solntities

* Reindent solution class namespace

* Field attribute changes

* AutoGenerateComponentState for SolutionContainerComponent

* SolutionContainerComponent -> ContainedSolutionComponent

* ref ReactionAttemptEvent

* Denetwork preinit solutions

* Misc 1

* Nullable TryGetSolution out vars

* Cache associated solutions

* Fix merge skew

* Use explicit regions in SharedSolutionContainerSystem.Capabilities

* Add debug assert

* Use explicit regions in SharedSolutionContainerSystem.Relay + ref SolutionContainerChangedEvent

* ContainedSolutionComponent.Name -> ContainedSolutionComponent.ContainerName

* SolutionComponent doc comments

* Implicit DataField names and property purge

* ReagentEffect DataField names

* Local variables for readability

* Sort using statements + Entity<T> event handlers

* Fix compile erros

* Fix compile errors

---------

Co-authored-by: ElectroJr <leonsfriedrich@gmail.com>
2023-12-28 17:58:14 -08:00

267 lines
10 KiB
C#

using Content.Server.Audio;
using Content.Server.Chemistry.Containers.EntitySystems;
using Content.Server.Fluids.EntitySystems;
using Content.Server.Materials;
using Content.Server.Popups;
using Content.Server.Power.Components;
using Content.Server.Power.EntitySystems;
using Content.Shared.FixedPoint;
using Content.Shared.Popups;
using Content.Shared.Power.Generator;
using Robust.Server.GameObjects;
namespace Content.Server.Power.Generator;
/// <inheritdoc/>
/// <seealso cref="FuelGeneratorComponent"/>
/// <seealso cref="ChemicalFuelGeneratorAdapterComponent"/>
/// <seealso cref="SolidFuelGeneratorAdapterComponent"/>
public sealed class GeneratorSystem : SharedGeneratorSystem
{
[Dependency] private readonly AppearanceSystem _appearance = default!;
[Dependency] private readonly AmbientSoundSystem _ambientSound = default!;
[Dependency] private readonly MaterialStorageSystem _materialStorage = default!;
[Dependency] private readonly SolutionContainerSystem _solutionContainer = default!;
[Dependency] private readonly PopupSystem _popup = default!;
[Dependency] private readonly PuddleSystem _puddle = default!;
private EntityQuery<UpgradePowerSupplierComponent> _upgradeQuery;
public override void Initialize()
{
_upgradeQuery = GetEntityQuery<UpgradePowerSupplierComponent>();
UpdatesBefore.Add(typeof(PowerNetSystem));
SubscribeLocalEvent<FuelGeneratorComponent, PortableGeneratorSetTargetPowerMessage>(OnTargetPowerSet);
SubscribeLocalEvent<FuelGeneratorComponent, PortableGeneratorEjectFuelMessage>(OnEjectFuel);
SubscribeLocalEvent<FuelGeneratorComponent, AnchorStateChangedEvent>(OnAnchorStateChanged);
SubscribeLocalEvent<SolidFuelGeneratorAdapterComponent, GeneratorGetFuelEvent>(SolidGetFuel);
SubscribeLocalEvent<SolidFuelGeneratorAdapterComponent, GeneratorUseFuel>(SolidUseFuel);
SubscribeLocalEvent<SolidFuelGeneratorAdapterComponent, GeneratorEmpty>(SolidEmpty);
SubscribeLocalEvent<ChemicalFuelGeneratorAdapterComponent, GeneratorGetFuelEvent>(ChemicalGetFuel);
SubscribeLocalEvent<ChemicalFuelGeneratorAdapterComponent, GeneratorUseFuel>(ChemicalUseFuel);
SubscribeLocalEvent<ChemicalFuelGeneratorAdapterComponent, GeneratorGetCloggedEvent>(ChemicalGetClogged);
SubscribeLocalEvent<ChemicalFuelGeneratorAdapterComponent, GeneratorEmpty>(ChemicalEmpty);
}
private void OnAnchorStateChanged(EntityUid uid, FuelGeneratorComponent component, ref AnchorStateChangedEvent args)
{
// Turn off generator if unanchored while running.
if (!component.On)
return;
SetFuelGeneratorOn(uid, false, component);
}
private void OnEjectFuel(EntityUid uid, FuelGeneratorComponent component, PortableGeneratorEjectFuelMessage args)
{
EmptyGenerator(uid);
}
private void SolidEmpty(EntityUid uid, SolidFuelGeneratorAdapterComponent component, GeneratorEmpty args)
{
_materialStorage.EjectAllMaterial(uid);
}
private void ChemicalEmpty(Entity<ChemicalFuelGeneratorAdapterComponent> entity, ref GeneratorEmpty args)
{
if (!_solutionContainer.ResolveSolution(entity.Owner, entity.Comp.SolutionName, ref entity.Comp.Solution, out var solution))
return;
var spillSolution = _solutionContainer.SplitSolution(entity.Comp.Solution.Value, solution.Volume);
_puddle.TrySpillAt(entity.Owner, spillSolution, out _);
}
private void ChemicalGetClogged(Entity<ChemicalFuelGeneratorAdapterComponent> entity, ref GeneratorGetCloggedEvent args)
{
if (!_solutionContainer.ResolveSolution(entity.Owner, entity.Comp.SolutionName, ref entity.Comp.Solution, out var solution))
return;
foreach (var reagentQuantity in solution)
{
if (reagentQuantity.Reagent.Prototype != entity.Comp.Reagent)
{
args.Clogged = true;
return;
}
}
}
private void ChemicalUseFuel(Entity<ChemicalFuelGeneratorAdapterComponent> entity, ref GeneratorUseFuel args)
{
if (!_solutionContainer.ResolveSolution(entity.Owner, entity.Comp.SolutionName, ref entity.Comp.Solution, out var solution))
return;
var availableReagent = solution.GetTotalPrototypeQuantity(entity.Comp.Reagent).Value;
var toRemove = RemoveFractionalFuel(
ref entity.Comp.FractionalReagent,
args.FuelUsed,
entity.Comp.Multiplier * FixedPoint2.Epsilon.Float(),
availableReagent);
_solutionContainer.RemoveReagent(entity.Comp.Solution.Value, entity.Comp.Reagent, FixedPoint2.FromCents(toRemove));
}
private void ChemicalGetFuel(Entity<ChemicalFuelGeneratorAdapterComponent> entity, ref GeneratorGetFuelEvent args)
{
if (!_solutionContainer.ResolveSolution(entity.Owner, entity.Comp.SolutionName, ref entity.Comp.Solution, out var solution))
return;
var availableReagent = solution.GetTotalPrototypeQuantity(entity.Comp.Reagent).Float();
var reagent = entity.Comp.FractionalReagent * FixedPoint2.Epsilon.Float() + availableReagent;
args.Fuel = reagent * entity.Comp.Multiplier;
}
private void SolidUseFuel(EntityUid uid, SolidFuelGeneratorAdapterComponent component, GeneratorUseFuel args)
{
var availableMaterial = _materialStorage.GetMaterialAmount(uid, component.FuelMaterial);
var toRemove = RemoveFractionalFuel(
ref component.FractionalMaterial,
args.FuelUsed,
component.Multiplier,
availableMaterial);
_materialStorage.TryChangeMaterialAmount(uid, component.FuelMaterial, -toRemove);
}
private int RemoveFractionalFuel(ref float fractional, float fuelUsed, float multiplier, int availableQuantity)
{
fractional -= fuelUsed / multiplier;
if (fractional >= 0)
return 0;
// worst (unrealistic) case: -5.5 -> -6.0 -> 6
var toRemove = -(int) MathF.Floor(fractional);
toRemove = Math.Min(availableQuantity, toRemove);
fractional = Math.Max(0, fractional + toRemove);
return toRemove;
}
private void SolidGetFuel(
EntityUid uid,
SolidFuelGeneratorAdapterComponent component,
ref GeneratorGetFuelEvent args)
{
var material = component.FractionalMaterial + _materialStorage.GetMaterialAmount(uid, component.FuelMaterial);
args.Fuel = material * component.Multiplier;
}
private void OnTargetPowerSet(EntityUid uid, FuelGeneratorComponent component,
PortableGeneratorSetTargetPowerMessage args)
{
component.TargetPower = Math.Clamp(
args.TargetPower,
component.MinTargetPower / 1000,
component.MaxTargetPower / 1000) * 1000;
}
public void SetFuelGeneratorOn(EntityUid uid, bool on, FuelGeneratorComponent? generator = null)
{
if (!Resolve(uid, ref generator))
return;
if (on && !Transform(uid).Anchored)
{
// Generator must be anchored to start.
return;
}
generator.On = on;
UpdateState(uid, generator);
}
public override void Update(float frameTime)
{
var query = EntityQueryEnumerator<FuelGeneratorComponent, PowerSupplierComponent>();
while (query.MoveNext(out var uid, out var gen, out var supplier))
{
if (!gen.On)
continue;
var fuel = GetFuel(uid);
if (fuel <= 0)
{
SetFuelGeneratorOn(uid, false, gen);
continue;
}
if (GetIsClogged(uid))
{
_popup.PopupEntity(Loc.GetString("generator-clogged", ("generator", uid)), uid, PopupType.SmallCaution);
SetFuelGeneratorOn(uid, false, gen);
continue;
}
supplier.Enabled = true;
var upgradeMultiplier = _upgradeQuery.CompOrNull(uid)?.ActualScalar ?? 1f;
supplier.MaxSupply = gen.TargetPower * upgradeMultiplier;
var eff = 1 / CalcFuelEfficiency(gen.TargetPower, gen.OptimalPower, gen);
var consumption = gen.OptimalBurnRate * frameTime * eff;
RaiseLocalEvent(uid, new GeneratorUseFuel(consumption));
}
}
public float GetFuel(EntityUid generator)
{
GeneratorGetFuelEvent getFuelEvent = default;
RaiseLocalEvent(generator, ref getFuelEvent);
return getFuelEvent.Fuel;
}
public bool GetIsClogged(EntityUid generator)
{
GeneratorGetCloggedEvent getCloggedEvent = default;
RaiseLocalEvent(generator, ref getCloggedEvent);
return getCloggedEvent.Clogged;
}
public void EmptyGenerator(EntityUid generator)
{
RaiseLocalEvent(generator, GeneratorEmpty.Instance);
}
private void UpdateState(EntityUid generator, FuelGeneratorComponent component)
{
_appearance.SetData(generator, GeneratorVisuals.Running, component.On);
_ambientSound.SetAmbience(generator, component.On);
if (!component.On)
Comp<PowerSupplierComponent>(generator).Enabled = false;
}
}
/// <summary>
/// Raised by <see cref="GeneratorSystem"/> to calculate the amount of remaining fuel in the generator.
/// </summary>
[ByRefEvent]
public record struct GeneratorGetFuelEvent(float Fuel);
/// <summary>
/// Raised by <see cref="GeneratorSystem"/> to check if a generator is "clogged".
/// For example there's bad chemicals in the fuel tank that prevent starting it.
/// </summary>
[ByRefEvent]
public record struct GeneratorGetCloggedEvent(bool Clogged);
/// <summary>
/// Raised by <see cref="GeneratorSystem"/> to draw fuel from its adapters.
/// </summary>
/// <remarks>
/// Implementations are expected to round fuel consumption up if the used fuel value is too small (e.g. reagent units).
/// </remarks>
public record struct GeneratorUseFuel(float FuelUsed);
/// <summary>
/// Raised by <see cref="GeneratorSystem"/> to empty a generator of its fuel contents.
/// </summary>
public sealed class GeneratorEmpty
{
public static readonly GeneratorEmpty Instance = new();
}