Moved Serverside solution container code to shared (yes that includes ensureSolution!) (#27478)

* Added warning to tryGetSolution, moved SolutionContainer code to shared
- Added an optional warning (false by default) to print an error if a solution is missing when using tryGetSolution methods
- Moved ensuring solution containers to shared, left the old method stubs for compatability and marked them as obsolete.

* Update SharedSolutionContainerSystem.cs

* Update SharedSolutionContainerSystem.cs

* Update SolutionContainerSystem.cs

* Update SharedSolutionContainerSystem.cs

* Fixing ensuring chem solutions always returning false on client
- ensuring chem solutions will only return false on the client if it is waiting for a server solutionEntity to be synced

* Added concentration helpers

* fix whitespace
This commit is contained in:
Jezithyr
2024-05-06 17:27:36 -07:00
committed by GitHub
parent 008f6ef94a
commit 6685146a1e
2 changed files with 328 additions and 173 deletions

View File

@@ -11,10 +11,13 @@ using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Text;
using Content.Shared.Hands.Components;
using Content.Shared.Hands.EntitySystems;
using Robust.Shared.Map;
using Robust.Shared.Network;
using Dependency = Robust.Shared.IoC.DependencyAttribute;
namespace Content.Shared.Chemistry.EntitySystems;
@@ -58,6 +61,7 @@ public abstract partial class SharedSolutionContainerSystem : EntitySystem
[Dependency] protected readonly SharedHandsSystem Hands = default!;
[Dependency] protected readonly SharedContainerSystem ContainerSystem = default!;
[Dependency] protected readonly MetaDataSystem MetaData = default!;
[Dependency] protected readonly INetManager NetManager = default!;
public override void Initialize()
{
@@ -66,13 +70,18 @@ public abstract partial class SharedSolutionContainerSystem : EntitySystem
InitializeRelays();
SubscribeLocalEvent<SolutionComponent, ComponentInit>(OnComponentInit);
SubscribeLocalEvent<SolutionComponent, ComponentStartup>(OnComponentStartup);
SubscribeLocalEvent<SolutionComponent, ComponentShutdown>(OnComponentShutdown);
SubscribeLocalEvent<SolutionContainerManagerComponent, ComponentInit>(OnComponentInit);
SubscribeLocalEvent<SolutionComponent, ComponentStartup>(OnSolutionStartup);
SubscribeLocalEvent<SolutionComponent, ComponentShutdown>(OnSolutionShutdown);
SubscribeLocalEvent<SolutionContainerManagerComponent, ComponentInit>(OnContainerManagerInit);
SubscribeLocalEvent<ExaminableSolutionComponent, ExaminedEvent>(OnExamineSolution);
SubscribeLocalEvent<ExaminableSolutionComponent, GetVerbsEvent<ExamineVerb>>(OnSolutionExaminableVerb);
SubscribeLocalEvent<SolutionContainerManagerComponent, MapInitEvent>(OnMapInit);
if (NetManager.IsServer)
{
SubscribeLocalEvent<SolutionContainerManagerComponent, ComponentShutdown>(OnContainerManagerShutdown);
SubscribeLocalEvent<ContainedSolutionComponent, ComponentShutdown>(OnContainedSolutionShutdown);
}
}
@@ -121,8 +130,14 @@ public abstract partial class SharedSolutionContainerSystem : EntitySystem
/// <param name="name">The name of the solution entity to fetch.</param>
/// <param name="entity">Returns the solution entity that was fetched.</param>
/// <param name="solution">Returns the solution state of the solution entity that was fetched.</param>
/// /// <param name="errorOnMissing">Should we print an error if the solution specified by name is missing</param>
/// <returns></returns>
public bool TryGetSolution(Entity<SolutionContainerManagerComponent?> container, string? name, [NotNullWhen(true)] out Entity<SolutionComponent>? entity, [NotNullWhen(true)] out Solution? solution)
public bool TryGetSolution(
Entity<SolutionContainerManagerComponent?> container,
string? name,
[NotNullWhen(true)] out Entity<SolutionComponent>? entity,
[NotNullWhen(true)] out Solution? solution,
bool errorOnMissing = false)
{
if (!TryGetSolution(container, name, out entity))
{
@@ -135,7 +150,11 @@ public abstract partial class SharedSolutionContainerSystem : EntitySystem
}
/// <inheritdoc cref="TryGetSolution"/>
public bool TryGetSolution(Entity<SolutionContainerManagerComponent?> container, string? name, [NotNullWhen(true)] out Entity<SolutionComponent>? entity)
public bool TryGetSolution(
Entity<SolutionContainerManagerComponent?> container,
string? name,
[NotNullWhen(true)] out Entity<SolutionComponent>? entity,
bool errorOnMissing = false)
{
if (TryComp(container, out BlockSolutionAccessComponent? blocker))
{
@@ -155,12 +174,18 @@ public abstract partial class SharedSolutionContainerSystem : EntitySystem
else
{
entity = null;
if (!errorOnMissing)
return false;
Log.Error($"{ToPrettyString(container)} does not have a solution with ID: {name}");
return false;
}
if (!TryComp(uid, out SolutionComponent? comp))
{
entity = null;
if (!errorOnMissing)
return false;
Log.Error($"{ToPrettyString(container)} does not have a solution with ID: {name}");
return false;
}
@@ -171,13 +196,18 @@ public abstract partial class SharedSolutionContainerSystem : EntitySystem
/// <summary>
/// Version of TryGetSolution that doesn't take or return an entity.
/// Used for prototypes and with old code parity.
public bool TryGetSolution(SolutionContainerManagerComponent container, string name, [NotNullWhen(true)] out Solution? solution)
public bool TryGetSolution(SolutionContainerManagerComponent container,
string name,
[NotNullWhen(true)] out Solution? solution,
bool errorOnMissing = false)
{
solution = null;
if (container.Solutions == null)
if (container.Solutions != null)
return container.Solutions.TryGetValue(name, out solution);
if (!errorOnMissing)
return false;
return container.Solutions.TryGetValue(name, out solution);
Log.Error($"{container} does not have a solution with ID: {name}");
return false;
}
public IEnumerable<(string? Name, Entity<SolutionComponent> Solution)> EnumerateSolutions(Entity<SolutionContainerManagerComponent?> container, bool includeSelf = true)
@@ -703,17 +733,17 @@ public abstract partial class SharedSolutionContainerSystem : EntitySystem
entity.Comp.Solution.ValidateSolution();
}
private void OnComponentStartup(Entity<SolutionComponent> entity, ref ComponentStartup args)
private void OnSolutionStartup(Entity<SolutionComponent> entity, ref ComponentStartup args)
{
UpdateChemicals(entity);
}
private void OnComponentShutdown(Entity<SolutionComponent> entity, ref ComponentShutdown args)
private void OnSolutionShutdown(Entity<SolutionComponent> entity, ref ComponentShutdown args)
{
RemoveAllSolution(entity);
}
private void OnComponentInit(Entity<SolutionContainerManagerComponent> entity, ref ComponentInit args)
private void OnContainerManagerInit(Entity<SolutionContainerManagerComponent> entity, ref ComponentInit args)
{
if (entity.Comp.Containers is not { Count: > 0 } containers)
return;
@@ -904,5 +934,273 @@ public abstract partial class SharedSolutionContainerSystem : EntitySystem
return true;
}
private void OnMapInit(Entity<SolutionContainerManagerComponent> entity, ref MapInitEvent args)
{
EnsureAllSolutions(entity);
}
private void OnContainerManagerShutdown(Entity<SolutionContainerManagerComponent> entity, ref ComponentShutdown args)
{
foreach (var name in entity.Comp.Containers)
{
if (ContainerSystem.TryGetContainer(entity, $"solution@{name}", out var solutionContainer))
ContainerSystem.ShutdownContainer(solutionContainer);
}
entity.Comp.Containers.Clear();
}
private void OnContainedSolutionShutdown(Entity<ContainedSolutionComponent> entity, ref ComponentShutdown args)
{
if (TryComp(entity.Comp.Container, out SolutionContainerManagerComponent? container))
{
container.Containers.Remove(entity.Comp.ContainerName);
Dirty(entity.Comp.Container, container);
}
if (ContainerSystem.TryGetContainer(entity, $"solution@{entity.Comp.ContainerName}", out var solutionContainer))
ContainerSystem.ShutdownContainer(solutionContainer);
}
#endregion Event Handlers
public bool EnsureSolution(
Entity<MetaDataComponent?> entity,
string name,
[NotNullWhen(true)]out Solution? solution,
FixedPoint2 maxVol = default)
{
return EnsureSolution(entity, name, maxVol, null, out _, out solution);
}
public bool EnsureSolution(
Entity<MetaDataComponent?> entity,
string name,
out bool existed,
[NotNullWhen(true)]out Solution? solution,
FixedPoint2 maxVol = default)
{
return EnsureSolution(entity, name, maxVol, null, out existed, out solution);
}
public bool EnsureSolution(
Entity<MetaDataComponent?> entity,
string name,
FixedPoint2 maxVol,
Solution? prototype,
out bool existed,
[NotNullWhen(true)] out Solution? solution)
{
solution = null;
existed = false;
var (uid, meta) = entity;
if (!Resolve(uid, ref meta))
throw new InvalidOperationException("Attempted to ensure solution on invalid entity.");
var manager = EnsureComp<SolutionContainerManagerComponent>(uid);
if (meta.EntityLifeStage >= EntityLifeStage.MapInitialized)
{
EnsureSolutionEntity((uid, manager), name, out existed,
out var solEnt, maxVol, prototype);
solution = solEnt!.Value.Comp.Solution;
return true;
}
solution = EnsureSolutionPrototype((uid, manager), name, maxVol, prototype, out existed);
return true;
}
public void EnsureAllSolutions(Entity<SolutionContainerManagerComponent> entity)
{
if (NetManager.IsClient)
return;
if (entity.Comp.Solutions is not { } prototypes)
return;
foreach (var (name, prototype) in prototypes)
{
EnsureSolutionEntity((entity.Owner, entity.Comp), name, out _, out _, prototype.MaxVolume, prototype);
}
entity.Comp.Solutions = null;
Dirty(entity);
}
public bool EnsureSolutionEntity(
Entity<SolutionContainerManagerComponent?> entity,
string name,
[NotNullWhen(true)] out Entity<SolutionComponent>? solutionEntity,
FixedPoint2 maxVol = default) =>
EnsureSolutionEntity(entity, name, out _, out solutionEntity, maxVol);
public bool EnsureSolutionEntity(
Entity<SolutionContainerManagerComponent?> entity,
string name,
out bool existed,
[NotNullWhen(true)] out Entity<SolutionComponent>? solutionEntity,
FixedPoint2 maxVol = default,
Solution? prototype = null
)
{
existed = true;
solutionEntity = null;
var (uid, container) = entity;
var solutionSlot = ContainerSystem.EnsureContainer<ContainerSlot>(uid, $"solution@{name}", out existed);
if (!Resolve(uid, ref container, logMissing: false))
{
existed = false;
container = AddComp<SolutionContainerManagerComponent>(uid);
container.Containers.Add(name);
if (NetManager.IsClient)
return false;
}
else if (!existed)
{
container.Containers.Add(name);
Dirty(uid, container);
}
var needsInit = false;
SolutionComponent solutionComp;
if (solutionSlot.ContainedEntity is not { } solutionId)
{
if (NetManager.IsClient)
return false;
prototype ??= new() { MaxVolume = maxVol };
prototype.Name = name;
(solutionId, solutionComp, _) = SpawnSolutionUninitialized(solutionSlot, name, maxVol, prototype);
existed = false;
needsInit = true;
Dirty(uid, container);
}
else
{
solutionComp = Comp<SolutionComponent>(solutionId);
DebugTools.Assert(TryComp(solutionId, out ContainedSolutionComponent? relation) && relation.Container == uid && relation.ContainerName == name);
DebugTools.Assert(solutionComp.Solution.Name == name);
var solution = solutionComp.Solution;
solution.MaxVolume = FixedPoint2.Max(solution.MaxVolume, maxVol);
// Depending on MapInitEvent order some systems can ensure solution empty solutions and conflict with the prototype solutions.
// We want the reagents from the prototype to exist even if something else already created the solution.
if (prototype is { Volume.Value: > 0 })
solution.AddSolution(prototype, PrototypeManager);
Dirty(solutionId, solutionComp);
}
if (needsInit)
EntityManager.InitializeAndStartEntity(solutionId, Transform(solutionId).MapID);
solutionEntity = (solutionId, solutionComp);
return true;
}
private Solution EnsureSolutionPrototype(Entity<SolutionContainerManagerComponent?> entity, string name, FixedPoint2 maxVol, Solution? prototype, out bool existed)
{
existed = true;
var (uid, container) = entity;
if (!Resolve(uid, ref container, logMissing: false))
{
container = AddComp<SolutionContainerManagerComponent>(uid);
existed = false;
}
if (container.Solutions is null)
container.Solutions = new(SolutionContainerManagerComponent.DefaultCapacity);
if (!container.Solutions.TryGetValue(name, out var solution))
{
solution = prototype ?? new() { Name = name, MaxVolume = maxVol };
container.Solutions.Add(name, solution);
existed = false;
}
else
solution.MaxVolume = FixedPoint2.Max(solution.MaxVolume, maxVol);
Dirty(uid, container);
return solution;
}
private Entity<SolutionComponent, ContainedSolutionComponent> SpawnSolutionUninitialized(ContainerSlot container, string name, FixedPoint2 maxVol, Solution prototype)
{
var coords = new EntityCoordinates(container.Owner, Vector2.Zero);
var uid = EntityManager.CreateEntityUninitialized(null, coords, null);
var solution = new SolutionComponent() { Solution = prototype };
AddComp(uid, solution);
var relation = new ContainedSolutionComponent() { Container = container.Owner, ContainerName = name };
AddComp(uid, relation);
MetaData.SetEntityName(uid, $"solution - {name}");
ContainerSystem.Insert(uid, container, force: true);
return (uid, solution, relation);
}
public void AdjustDissolvedReagent(
Entity<SolutionComponent> dissolvedSolution,
FixedPoint2 volume,
ReagentId reagent,
float concentrationChange)
{
if (concentrationChange == 0)
return;
var dissolvedSol = dissolvedSolution.Comp.Solution;
var amtChange =
GetReagentQuantityFromConcentration(dissolvedSolution, volume, MathF.Abs(concentrationChange));
if (concentrationChange > 0)
{
dissolvedSol.AddReagent(reagent, amtChange);
}
else
{
dissolvedSol.RemoveReagent(reagent,amtChange);
}
UpdateChemicals(dissolvedSolution);
}
public FixedPoint2 GetReagentQuantityFromConcentration(Entity<SolutionComponent> dissolvedSolution,
FixedPoint2 volume,float concentration)
{
var dissolvedSol = dissolvedSolution.Comp.Solution;
if (volume == 0
|| dissolvedSol.Volume == 0)
return 0;
return concentration * volume;
}
public float GetReagentConcentration(Entity<SolutionComponent> dissolvedSolution,
FixedPoint2 volume, ReagentId dissolvedReagent)
{
var dissolvedSol = dissolvedSolution.Comp.Solution;
if (volume == 0
|| dissolvedSol.Volume == 0
|| !dissolvedSol.TryGetReagentQuantity(dissolvedReagent, out var dissolvedVol))
return 0;
return (float)dissolvedVol / volume.Float();
}
public FixedPoint2 ClampReagentAmountByConcentration(
Entity<SolutionComponent> dissolvedSolution,
FixedPoint2 volume,
ReagentId dissolvedReagent,
FixedPoint2 dissolvedReagentAmount,
float maxConcentration = 1f)
{
var dissolvedSol = dissolvedSolution.Comp.Solution;
if (volume == 0
|| dissolvedSol.Volume == 0
|| !dissolvedSol.TryGetReagentQuantity(dissolvedReagent, out var dissolvedVol))
return 0;
volume *= maxConcentration;
dissolvedVol += dissolvedReagentAmount;
var overflow = volume - dissolvedVol;
if (overflow < 0)
dissolvedReagentAmount += overflow;
return dissolvedReagentAmount;
}
}