Fix Broken Solution Enumerators (#43789)

* burge

* bruegr

* YAML

* two misc fixes

* snoutta here

* add tests

---------

Co-authored-by: Princess Cheeseballs <66055347+Pronana@users.noreply.github.com>
Co-authored-by: Janet Blackquill <uhhadd@gmail.com>
This commit is contained in:
Princess Cheeseballs
2026-05-01 14:20:03 -07:00
committed by GitHub
parent 12e45305de
commit 4d1325bccf
29 changed files with 292 additions and 193 deletions
@@ -0,0 +1,48 @@
using Content.IntegrationTests.Fixtures;
using Content.IntegrationTests.Fixtures.Attributes;
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.FixedPoint;
using Robust.Shared.GameObjects;
using Robust.Shared.Prototypes;
namespace Content.IntegrationTests.Tests.Chemistry;
[TestFixture]
[TestOf(typeof(SolutionRegenerationSystem))]
[TestOf(typeof(SolutionPurgeSystem))]
public sealed class SolutionPurgeRegenerationTests : GameTest
{
private static readonly EntProtoId AdvancedMop = "AdvMopItem";
private static readonly ProtoId<ReagentPrototype> Water = "Water";
private static readonly ProtoId<ReagentPrototype> NotWater = "DexalinPlus";
[SidedDependency(Side.Server)] private readonly SharedSolutionContainerSystem _solutionContainer = default!;
[Test]
public async Task TestMop()
{
var testMap = await Pair.CreateTestMap();
EntityUid mop = default!;
Entity<SolutionComponent> solution = default!;
await Server.WaitPost(() =>
{
mop = SSpawnAtPosition(AdvancedMop, testMap.GridCoords);
var generated = SComp<SolutionRegenerationComponent>(mop).Generated;
var purge = SComp<SolutionPurgeComponent>(mop);
Assume.That(generated.ContainsPrototype(Water));
Assume.That(purge.Preserve, Does.Not.Contain(NotWater));
Assert.That(_solutionContainer.TryGetSolution(mop, "absorbed", out var mopSolution, out _));
solution = mopSolution!.Value;
Assert.That(_solutionContainer.AddSolution(solution, new Solution(NotWater, 50)), Is.EqualTo(FixedPoint2.New(50)));
});
await PoolManager.WaitUntil(Server, () => !solution.Comp.Solution.ContainsPrototype(NotWater));
await PoolManager.WaitUntil(Server, () => solution.Comp.Solution.Volume == solution.Comp.Solution.MaxVolume);
}
}
@@ -0,0 +1,80 @@
using System.Numerics;
using Content.IntegrationTests.Fixtures;
using Content.IntegrationTests.Fixtures.Attributes;
using Content.IntegrationTests.NUnit.Constraints;
using Content.Server.Chemistry.Components;
using Content.Server.Chemistry.EntitySystems;
using Content.Server.Decals;
using Content.Server.Fluids.EntitySystems;
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.Decals;
using Content.Shared.Fluids.Components;
using Content.Shared.Fluids.EntitySystems;
using Robust.Shared.GameObjects;
using Robust.Shared.Prototypes;
namespace Content.IntegrationTests.Tests.Chemistry;
[TestFixture]
[TestOf(typeof(SharedSpraySystem))]
[TestOf(typeof(VaporSystem))]
public sealed class SprayVaporTests : GameTest
{
private static readonly ProtoId<ReagentPrototype> Blood = "Blood";
private static readonly EntProtoId SprayBottleSpaceCleaner = "SprayBottleSpaceCleaner";
private const string BloodPuddle = "SprayVaporTestBloodPuddle";
private const int BloodVolume = 5;
[TestPrototypes]
private static readonly string Prototypes = @$"
- type: entity
parent: Puddle
id: {BloodPuddle}
suffix: Blood
components:
- type: Solution
id: puddle
solution:
maxVol: 1000
reagents:
- ReagentId: {Blood}
Quantity: {BloodVolume}
";
[SidedDependency(Side.Server)] private readonly SpraySystem _spray = default!;
[SidedDependency(Side.Server)] private readonly SolutionContainerSystem _solutionContainer = default!;
[SidedDependency(Side.Server)] private readonly SharedTransformSystem _transform = default!;
[Test]
public async Task TestSprayingSpaceCleaner()
{
var testMap = await Pair.CreateTestMap();
Entity<SolutionComponent> puddle = default!;
await Server.WaitAssertion(() =>
{
var sprayCleaner = SSpawnAtPosition(SprayBottleSpaceCleaner, testMap.GridCoords);
Assume.That(sprayCleaner, Has.Comp<SprayComponent>(Server));
_transform.SetLocalPositionNoLerp(sprayCleaner, SComp<TransformComponent>(sprayCleaner).LocalPosition + new Vector2(1, 1));
var puddleUid = SSpawnAtPosition(BloodPuddle, testMap.GridCoords);
Assume.That(puddleUid, Has.Comp<PuddleComponent>(Server));
Assume.That(_solutionContainer.TryGetSolution(puddleUid, "puddle", out var puddleSolution, out _));
puddle = puddleSolution!.Value;
Assume.That(puddle.Comp.Solution.ContainsPrototype(Blood));
_spray.Spray((sprayCleaner, SComp<SprayComponent>(sprayCleaner)), _transform.GetMapCoordinates(puddleUid));
var vaporEnum = SEntMan.EntityQueryEnumerator<VaporComponent>();
Assume.That(vaporEnum.MoveNext(out _));
});
await PoolManager.WaitUntil(Server, () => !SEntMan.EntityQueryEnumerator<VaporComponent>().MoveNext(out _));
await Server.WaitAssertion(() =>
{
Assert.That(!puddle.Comp.Solution.ContainsPrototype(Blood));
});
}
}
@@ -0,0 +1,59 @@
using Content.IntegrationTests.Fixtures;
using Content.IntegrationTests.Fixtures.Attributes;
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Item.ItemToggle.Components;
using Content.Shared.Tools.Components;
using Content.Shared.Tools.Systems;
using Robust.Shared.GameObjects;
using Robust.Shared.Prototypes;
namespace Content.IntegrationTests.Tests.Tools;
[TestFixture]
[TestOf(typeof(SharedToolSystem))]
public sealed class WelderTests : GameTest
{
private const string Welder = "TestTinyWelder";
[TestPrototypes]
private const string Prototypes = $@"
- type: entity
parent: [SolutionToolWelderMiniEmergency, Welder]
id: {Welder}
components:
- type: Solution
solution:
maxVol: 5
reagents:
- ReagentId: WeldingFuel
Quantity: 5
";
[SidedDependency(Side.Server)] private readonly SharedToolSystem _tool = default!;
[SidedDependency(Side.Server)] private readonly SharedSolutionContainerSystem _solutionContainer = default!;
[Test]
public async Task FuelDepletion()
{
Entity<WelderComponent> welder = default!;
Entity<SolutionComponent> fuel = default!;
await Server.WaitPost(() =>
{
var uid = SSpawn(Welder);
welder = (uid, SComp<WelderComponent>(uid));
Assume.That(_solutionContainer.TryGetSolution(uid, welder.Comp.FuelSolutionName, out var solutionEnt, out _));
fuel = solutionEnt!.Value;
_tool.TurnOn(welder, null);
});
await PoolManager.WaitUntil(Server, () => fuel.Comp.Solution.Volume <= 0);
await Server.WaitPost(() =>
{
Assert.That(SComp<ItemToggleComponent>(welder).Activated, Is.False);
});
}
}
@@ -5,8 +5,6 @@ namespace Content.Server.Chemistry.Components
[RegisterComponent]
public sealed partial class VaporComponent : Component
{
public const string SolutionName = "vapor";
/// <summary>
/// Stores data on the previously reacted tile. We only want to do reaction checks once per tile.
/// </summary>
@@ -1,7 +1,6 @@
using Content.Server.Chemistry.Components;
using Content.Shared.Chemistry;
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.Components.SolutionManager;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.FixedPoint;
using Content.Shared.Physics;
@@ -16,6 +15,7 @@ using Robust.Shared.Physics.Systems;
using Robust.Shared.Prototypes;
using Robust.Shared.Spawners;
using System.Numerics;
using Content.Shared.Vapor;
namespace Content.Server.Chemistry.EntitySystems
{
@@ -23,11 +23,12 @@ namespace Content.Server.Chemistry.EntitySystems
internal sealed class VaporSystem : EntitySystem
{
[Dependency] private readonly IPrototypeManager _protoManager = default!;
[Dependency] private readonly ReactiveSystem _reactive = default!;
[Dependency] private readonly ThrowingSystem _throwing = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly SharedMapSystem _map = default!;
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
[Dependency] private readonly SharedSolutionContainerSystem _solutionContainerSystem = default!;
[Dependency] private readonly ThrowingSystem _throwing = default!;
[Dependency] private readonly ReactiveSystem _reactive = default!;
[Dependency] private readonly SharedSolutionContainerSystem _solutionContainer = default!;
[Dependency] private readonly SharedTransformSystem _transformSystem = default!;
public override void Initialize()
@@ -39,11 +40,8 @@ namespace Content.Server.Chemistry.EntitySystems
private void HandleCollide(Entity<VaporComponent> entity, ref StartCollideEvent args)
{
foreach (var (_, soln) in _solutionContainerSystem.EnumerateSolutions(entity.Owner))
{
var solution = soln.Comp.Solution;
_reactive.DoEntityReaction(args.OtherEntity, solution, ReactionMethod.Touch);
}
var solution = Comp<SolutionComponent>(entity).Solution;
_reactive.DoEntityReaction(args.OtherEntity, solution, ReactionMethod.Touch);
// Check for collision with a impassable object (e.g. wall) and stop
if ((args.OtherFixture.CollisionLayer & (int)CollisionGroup.Impassable) != 0 && args.OtherFixture.Hard)
@@ -52,7 +50,7 @@ namespace Content.Server.Chemistry.EntitySystems
}
}
public void Start(Entity<VaporComponent> vapor,
public void Start(Entity<VaporComponent?> vapor,
TransformComponent vaporXform,
Vector2 dir,
float speed,
@@ -60,6 +58,9 @@ namespace Content.Server.Chemistry.EntitySystems
float aliveTime,
EntityUid? user = null)
{
if (!Resolve(vapor, ref vapor.Comp))
return;
vapor.Comp.Active = true;
var despawn = EnsureComp<TimedDespawnComponent>(vapor);
despawn.Lifetime = aliveTime;
@@ -78,21 +79,20 @@ namespace Content.Server.Chemistry.EntitySystems
}
}
internal bool TryAddSolution(Entity<VaporComponent> vapor, Solution solution)
internal bool TryAddSolution(Entity<SolutionComponent?> vapor, Entity<SolutionComponent> solution, FixedPoint2 split)
{
if (solution.Volume == 0)
{
if (solution.Comp.Solution.Volume <= 0 || split <= 0 || !Resolve(vapor, ref vapor.Comp))
return false;
var newSolution = _solutionContainer.SplitSolution(solution, split);
if (TryComp<AppearanceComponent>(vapor, out var appearance))
{
_appearance.SetData(vapor, VaporVisuals.Color, newSolution.GetColor(_protoManager).WithAlpha(1f), appearance);
_appearance.SetData(vapor, VaporVisuals.State, true, appearance);
}
if (!_solutionContainerSystem.TryGetSolution(vapor.Owner,
VaporComponent.SolutionName,
out var vaporSolution))
{
return false;
}
return _solutionContainerSystem.TryAddSolution(vaporSolution.Value, solution);
return _solutionContainer.TryAddSolution((vapor, vapor.Comp), newSolution);
}
public override void Update(float frameTime)
@@ -101,8 +101,8 @@ namespace Content.Server.Chemistry.EntitySystems
// Enumerate over all VaporComponents
// TODO: Vapor should just use SolutionComponent and not be capable of having multiple solutions.
var query = EntityQueryEnumerator<VaporComponent, SolutionManagerComponent, TransformComponent>();
while (query.MoveNext(out var uid, out var vaporComp, out var container, out var xform))
var query = EntityQueryEnumerator<VaporComponent, SolutionComponent, TransformComponent>();
while (query.MoveNext(out var uid, out var vaporComp, out var solution, out var xform))
{
// Return early if we're not active
if (!vaporComp.Active)
@@ -118,47 +118,43 @@ namespace Content.Server.Chemistry.EntitySystems
if (vaporComp.PreviousTileRef != null && tile == vaporComp.PreviousTileRef)
continue;
// Enumerate over all the reagents in the vapor entity solution
foreach (var (_, soln) in _solutionContainerSystem.EnumerateSolutions((uid, container)))
// Iterate over the reagents in the solution
// Reason: Each reagent in our solution may have a unique TileReaction
// In this instance, we check individually for each reagent's TileReaction
// This is not doing chemical reactions!
var contents = solution.Solution;
foreach (var reagentQuantity in contents.Contents.ToArray())
{
// Iterate over the reagents in the solution
// Reason: Each reagent in our solution may have a unique TileReaction
// In this instance, we check individually for each reagent's TileReaction
// This is not doing chemical reactions!
var contents = soln.Comp.Solution;
foreach (var reagentQuantity in contents.Contents.ToArray())
{
// Check if the reagent is empty
if (reagentQuantity.Quantity == FixedPoint2.Zero)
continue;
// Check if the reagent is empty
if (reagentQuantity.Quantity == FixedPoint2.Zero)
continue;
var reagent = _protoManager.Index<ReagentPrototype>(reagentQuantity.Reagent.Prototype);
var reagent = _protoManager.Index<ReagentPrototype>(reagentQuantity.Reagent.Prototype);
// Limit the reaction amount to a minimum value to ensure no floating point funnies.
// Ex: A solution with a low percentage transfer amount will slowly approach 0.01... and never get deleted
var clampedAmount = Math.Max(
(float)reagentQuantity.Quantity * vaporComp.TransferAmountPercentage,
vaporComp.MinimumTransferAmount);
// Limit the reaction amount to a minimum value to ensure no floating point funnies.
// Ex: A solution with a low percentage transfer amount will slowly approach 0.01... and never get deleted
var clampedAmount = Math.Max(
(float)reagentQuantity.Quantity * vaporComp.TransferAmountPercentage,
vaporComp.MinimumTransferAmount);
// Preform the reagent's TileReaction
var reaction =
reagent.ReactionTile(tile,
clampedAmount,
EntityManager,
reagentQuantity.Reagent.Data);
// Preform the reagent's TileReaction
var reaction =
reagent.ReactionTile(tile,
clampedAmount,
EntityManager,
reagentQuantity.Reagent.Data);
if (reaction > reagentQuantity.Quantity)
reaction = reagentQuantity.Quantity;
_solutionContainerSystem.RemoveReagent(soln, reagentQuantity.Reagent, reaction);
}
// Delete the vapor entity if it has no contents
if (contents.Volume == 0)
QueueDel(uid);
if (reaction > reagentQuantity.Quantity)
reaction = reagentQuantity.Quantity;
_solutionContainer.RemoveReagent((uid, solution), reagentQuantity.Reagent, reaction);
}
// Delete the vapor entity if it has no contents
if (contents.Volume == 0)
QueueDel(uid);
// Set the previous tile reference to the current tile
vaporComp.PreviousTileRef = tile;
}
@@ -1,19 +1,15 @@
using Content.Server.Chemistry.Components;
using Content.Server.Chemistry.EntitySystems;
using Content.Server.Gravity;
using Content.Server.Popups;
using Content.Shared.CCVar;
using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.FixedPoint;
using Content.Shared.Fluids;
using Content.Shared.Interaction;
using Content.Shared.Timing;
using Content.Shared.Vapor;
using Robust.Server.GameObjects;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Configuration;
using Robust.Shared.Physics.Components;
using Robust.Shared.Prototypes;
using System.Numerics;
using Content.Shared.Fluids.EntitySystems;
using Content.Shared.Fluids.Components;
@@ -24,7 +20,6 @@ namespace Content.Server.Fluids.EntitySystems;
public sealed class SpraySystem : SharedSpraySystem
{
[Dependency] private readonly IPrototypeManager _proto = default!;
[Dependency] private readonly GravitySystem _gravity = default!;
[Dependency] private readonly PhysicsSystem _physics = default!;
[Dependency] private readonly UseDelaySystem _useDelay = default!;
@@ -32,7 +27,6 @@ public sealed class SpraySystem : SharedSpraySystem
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly SharedSolutionContainerSystem _solutionContainer = default!;
[Dependency] private readonly VaporSystem _vapor = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!;
[Dependency] private readonly ContainerSystem _container = default!;
@@ -151,10 +145,6 @@ public sealed class SpraySystem : SharedSpraySystem
target = sprayerMapPos.Offset(diffNorm * entity.Comp.SprayDistance);
var adjustedSolutionAmount = entity.Comp.TransferAmount / entity.Comp.VaporAmount;
var newSolution = _solutionContainer.SplitSolution(soln.Value, adjustedSolutionAmount);
if (newSolution.Volume <= FixedPoint2.Zero)
break;
// Spawn the vapor cloud onto the grid/map the user is present on. Offset the start position based on how far the target destination is.
var vaporPos = sprayerMapPos.Offset(distance < 1 ? quarter : threeQuarters);
@@ -163,22 +153,13 @@ public sealed class SpraySystem : SharedSpraySystem
_transform.SetWorldRotation(vaporXform, rotation);
if (TryComp(vapor, out AppearanceComponent? appearance))
{
_appearance.SetData(vapor, VaporVisuals.Color, solution.GetColor(_proto).WithAlpha(1f), appearance);
_appearance.SetData(vapor, VaporVisuals.State, true, appearance);
}
// Add the solution to the vapor and actually send the thing
var vaporComponent = Comp<VaporComponent>(vapor);
var ent = (vapor, vaporComponent);
_vapor.TryAddSolution(ent, newSolution);
_vapor.TryAddSolution(vapor, soln.Value, adjustedSolutionAmount);
// impulse direction is defined in world-coordinates, not local coordinates
var impulseDirection = rotation.ToVec();
var time = diffLength / entity.Comp.SprayVelocity;
_vapor.Start(ent, vaporXform, impulseDirection * diffLength, entity.Comp.SprayVelocity, target, time, user);
_vapor.Start(vapor, vaporXform, impulseDirection * diffLength, entity.Comp.SprayVelocity, target, time, user);
var thingGettingPushed = entity.Owner;
if (_container.TryGetOuterContainer(entity, sprayerXform, out var container))
@@ -1,4 +1,5 @@
using Content.Server.Chemistry.Components;
using Content.Server.Chemistry.EntitySystems;
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.FixedPoint;
@@ -12,6 +13,7 @@ namespace Content.Server.Weapons.Ranged.Systems;
public sealed partial class GunSystem
{
[Dependency] private readonly SharedSolutionContainerSystem _solutionContainer = default!;
[Dependency] private readonly VaporSystem _vapor = default!;
protected override void InitializeSolution()
{
@@ -61,25 +63,10 @@ public sealed partial class GunSystem
{
var (shot, shootable) = base.GetSolutionShot(ent, position);
if (!_solutionContainer.TryGetSolution(ent.Owner, ent.Comp.SolutionId, out var solution, out _))
if (!_solutionContainer.TryGetSolution(ent.Owner, ent.Comp.SolutionId, out var solution))
return (shot, shootable);
var newSolution = _solutionContainer.SplitSolution(solution.Value, ent.Comp.FireCost);
if (newSolution.Volume <= FixedPoint2.Zero)
return (shot, shootable);
if (TryComp<AppearanceComponent>(shot, out var appearance))
{
Appearance.SetData(shot, VaporVisuals.Color, newSolution.GetColor(ProtoManager).WithAlpha(1f), appearance);
Appearance.SetData(shot, VaporVisuals.State, true, appearance);
}
// Add the solution to the vapor and actually send the thing
if (_solutionContainer.TryGetSolution(shot, VaporComponent.SolutionName, out var vaporSolution, out _))
{
_solutionContainer.TryAddSolution(vaporSolution.Value, newSolution);
}
_vapor.TryAddSolution(shot, solution.Value, ent.Comp.FireCost);
return (shot, shootable);
}
}
@@ -15,12 +15,6 @@ namespace Content.Shared.Chemistry.Components;
[Access(typeof(SolutionPurgeSystem))]
public sealed partial class SolutionPurgeComponent : Component
{
/// <summary>
/// The name of the solution to detract from.
/// </summary>
[DataField(required: true)]
public string Solution = string.Empty;
/// <summary>
/// The reagent(s) to be ignored when purging the solution
/// </summary>
@@ -11,18 +11,6 @@ namespace Content.Shared.Chemistry.Components;
[Access(typeof(SolutionRegenerationSystem))]
public sealed partial class SolutionRegenerationComponent : Component
{
/// <summary>
/// The name of the solution to add to.
/// </summary>
[DataField("solution", required: true)]
public string SolutionName = string.Empty;
/// <summary>
/// The solution to add reagents to.
/// </summary>
[ViewVariables]
public Entity<SolutionComponent>? SolutionRef = null;
/// <summary>
/// The reagent(s) to be regenerated in the solution.
/// </summary>
@@ -656,21 +656,18 @@ public abstract partial class SharedSolutionContainerSystem : EntitySystem
/// <summary>
/// Adds a solution to the container, if it can fully fit.
/// </summary>
/// <param name="targetUid">entity holding targetSolution</param>
/// <param name="targetSolution">entity holding targetSolution</param>
/// <param name="solution">Solution we are adding to</param>
/// <param name="toAdd">solution being added</param>
/// <returns>If the solution could be added.</returns>
public bool TryAddSolution(Entity<SolutionComponent> soln, Solution toAdd)
public bool TryAddSolution(Entity<SolutionComponent> solution, Solution toAdd)
{
var (uid, comp) = soln;
var solution = comp.Solution;
if (toAdd.Volume == FixedPoint2.Zero)
return true;
if (toAdd.Volume > solution.AvailableVolume)
if (toAdd.Volume > solution.Comp.Solution.AvailableVolume)
return false;
ForceAddSolution(soln, toAdd);
ForceAddSolution((solution, solution.Comp), toAdd);
return true;
}
@@ -27,9 +27,8 @@ public sealed class SolutionPurgeSystem : EntitySystem
{
base.Update(frameTime);
// TODO: SolutionPurgeComponent on Solution Entities!
var query = EntityQueryEnumerator<SolutionPurgeComponent, SolutionManagerComponent>();
while (query.MoveNext(out var uid, out var purge, out var manager))
var query = EntityQueryEnumerator<SolutionPurgeComponent, SolutionComponent>();
while (query.MoveNext(out var uid, out var purge, out var solution))
{
if (_timing.CurTime < purge.NextPurgeTime)
continue;
@@ -39,12 +38,9 @@ public sealed class SolutionPurgeSystem : EntitySystem
// Needs to be networked and dirtied so that the client can reroll it during prediction
Dirty(uid, purge);
if (_solutionContainer.TryGetSolution((uid, manager), purge.Solution, out var solution))
{
_solutionContainer.SplitSolutionWithout(solution.Value,
purge.Quantity,
purge.Preserve.Select(proto => proto.Id).ToArray());
}
_solutionContainer.SplitSolutionWithout((uid, solution),
purge.Quantity,
purge.Preserve.ToArray());
}
}
}
@@ -16,7 +16,6 @@ public sealed class SolutionRegenerationSystem : EntitySystem
base.Initialize();
SubscribeLocalEvent<SolutionRegenerationComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<SolutionRegenerationComponent, EntRemovedFromContainerMessage>(OnEntRemoved);
}
private void OnMapInit(Entity<SolutionRegenerationComponent> ent, ref MapInitEvent args)
@@ -26,21 +25,13 @@ public sealed class SolutionRegenerationSystem : EntitySystem
Dirty(ent);
}
// Workaround for https://github.com/space-wizards/space-station-14/pull/35314
private void OnEntRemoved(Entity<SolutionRegenerationComponent> ent, ref EntRemovedFromContainerMessage args)
{
// Make sure the removed entity was our contained solution and clear our cached reference
if (args.Entity == ent.Comp.SolutionRef?.Owner)
ent.Comp.SolutionRef = null;
}
public override void Update(float frameTime)
{
base.Update(frameTime);
// TODO: SolutionRegenerationComponent on Solution Entities!
var query = EntityQueryEnumerator<SolutionRegenerationComponent, SolutionManagerComponent>();
while (query.MoveNext(out var uid, out var regen, out var manager))
var query = EntityQueryEnumerator<SolutionRegenerationComponent, SolutionComponent>();
while (query.MoveNext(out var uid, out var regen, out var solution))
{
if (_timing.CurTime < regen.NextRegenTime)
continue;
@@ -49,13 +40,7 @@ public sealed class SolutionRegenerationSystem : EntitySystem
regen.NextRegenTime += regen.Duration;
// Needs to be networked and dirtied so that the client can reroll it during prediction
Dirty(uid, regen);
if (!_solutionContainer.ResolveSolution((uid, manager),
regen.SolutionName,
ref regen.SolutionRef,
out var solution))
continue;
var amount = FixedPoint2.Min(solution.AvailableVolume, regen.Generated.Volume);
var amount = FixedPoint2.Min(solution.Solution.AvailableVolume, regen.Generated.Volume);
if (amount <= FixedPoint2.Zero)
continue;
@@ -64,7 +49,7 @@ public sealed class SolutionRegenerationSystem : EntitySystem
? regen.Generated
: regen.Generated.Clone().SplitSolution(amount);
_solutionContainer.TryAddSolution(regen.SolutionRef.Value, generated);
_solutionContainer.TryAddSolution((uid, solution), generated);
}
}
}
@@ -1,4 +1,5 @@
using Content.Shared.Chemistry.Components.SolutionManager;
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.Components.SolutionManager;
using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Chemistry.Reagent;
using Robust.Shared.Prototypes;
@@ -13,6 +14,12 @@ namespace Content.Shared.EntityEffects.Effects.Solution;
/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
public sealed class AddReagentToSolutionEntityEffectSystem : EntityEffectSystem<SolutionManagerComponent, AddReagentToSolution>
{
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<SolutionComponent, EntityEffectEvent<AddReagentToSolution>>(Effect);
}
[Dependency] private readonly SharedSolutionContainerSystem _solutionContainer = default!;
protected override void Effect(Entity<SolutionManagerComponent> entity, ref EntityEffectEvent<AddReagentToSolution> args)
@@ -25,6 +32,14 @@ public sealed class AddReagentToSolutionEntityEffectSystem : EntityEffectSystem<
_solutionContainer.TryAddReagent(solutionContainer.Value, reagent, args.Scale * args.Effect.StrengthModifier);
}
private void Effect(Entity<SolutionComponent> entity, ref EntityEffectEvent<AddReagentToSolution> args)
{
if (entity.Comp.Id != args.Effect.Solution)
return;
_solutionContainer.TryAddReagent(entity, args.Effect.Reagent, args.Scale * args.Effect.StrengthModifier);
}
}
/// <inheritdoc cref="EntityEffect"/>
+1 -1
View File
@@ -30,7 +30,7 @@ public sealed partial class AbsorbentComponent : Component
/// How much solution we can transfer in one interaction.
/// </summary>
[DataField]
public FixedPoint2 PickupAmount = FixedPoint2.New(100);
public FixedPoint2 PickupAmount = FixedPoint2.New(120);
/// <summary>
/// The effect spawned when the puddle fully evaporates.
@@ -117,13 +117,13 @@ public abstract partial class SharedToolSystem
if (TryComp(target, out ReagentTankComponent? tank)
&& tank.TankType == ReagentTankType.Fuel
&& SolutionContainerSystem.TryGetDrainableSolution(target, out var targetSoln, out var targetSolution)
&& SolutionContainerSystem.TryGetSolution(entity.Owner, entity.Comp.FuelSolutionName, out var solutionComp, out var welderSolution))
&& SolutionContainerSystem.TryGetSolution(entity.Owner, entity.Comp.FuelSolutionName, out var solution, out var welderSolution))
{
var trans = FixedPoint2.Min(welderSolution.AvailableVolume, targetSolution.Volume);
if (trans > 0)
{
var drained = SolutionContainerSystem.Drain(target, targetSoln.Value, trans);
SolutionContainerSystem.TryAddSolution(solutionComp.Value, drained);
SolutionContainerSystem.TryAddSolution(solution.Value, drained);
_audioSystem.PlayPredicted(entity.Comp.WelderRefill, entity, user: args.User);
_popup.PopupClient(Loc.GetString("welder-component-after-interact-refueled-message"), entity, args.User);
}
@@ -210,9 +210,10 @@ public abstract partial class SharedToolSystem
private void UpdateWelders()
{
// TODO: Same as the other EntityQueryEnumerators...
var query = EntityQueryEnumerator<WelderComponent, SolutionManagerComponent>();
// TODO: ActiveWelderComponent
var query = EntityQueryEnumerator<WelderComponent>();
var curTime = _timing.CurTime;
while (query.MoveNext(out var uid, out var welder, out var solutionContainer))
while (query.MoveNext(out var uid, out var welder))
{
if (curTime < welder.NextUpdate)
continue;
@@ -223,7 +224,8 @@ public abstract partial class SharedToolSystem
if (!welder.Enabled)
continue;
if (!SolutionContainerSystem.TryGetSolution((uid, solutionContainer), welder.FuelSolutionName, out var solutionComp, out var solution))
// TODO: Relations
if (!SolutionContainerSystem.TryGetSolution(uid, welder.FuelSolutionName, out var solutionComp, out var solution))
continue;
SolutionContainerSystem.RemoveReagent(solutionComp.Value, welder.FuelReagent, welder.FuelConsumption * welder.WelderUpdateTimer.TotalSeconds);
@@ -2523,7 +2523,6 @@
solutions:
- SolutionVenomSpider
- type: SolutionRegeneration
solution: melee
generated:
reagents:
- ReagentId: Mechanotoxin
@@ -30,13 +30,11 @@
- type: UseDelay
delay: 0.5 # quick feet
- type: SolutionRegeneration
solution: absorbed
generated:
reagents:
- ReagentId: Water
Quantity: 10
- type: SolutionPurge
solution: absorbed
preserve:
- Water
quantity: 10
@@ -127,7 +127,6 @@
rootTask:
task: FirebotCompound
- type: SolutionRegeneration
solution: spray
generated:
reagents:
- ReagentId: Water
@@ -273,13 +272,11 @@
- type: UseDelay
delay: 2
- type: SolutionRegeneration
solution: absorbed
generated:
reagents:
- ReagentId: Water
Quantity: 10
- type: SolutionPurge
solution: absorbed
preserve:
- Water
quantity: 10
@@ -63,7 +63,6 @@
- ReagentId: SpaceLube
Quantity: 960
- type: SolutionRegeneration
solution: beaker
generated:
reagents:
- ReagentId: SpaceLube
@@ -107,7 +107,6 @@
heldOnly: true
exactVolume: true
- type: SolutionRegeneration
solution: welder
generated:
reagents:
- ReagentId: WeldingFuel
@@ -165,7 +165,6 @@
description: It extinguishes fires. it slowly refills with water.
components:
- type: SolutionRegeneration
solution: spray
generated:
reagents:
- ReagentId: Water
@@ -89,13 +89,11 @@
- type: Absorbent
pickupAmount: 100
- type: SolutionRegeneration
solution: absorbed
generated:
reagents:
- ReagentId: Water
Quantity: 5
- type: SolutionPurge
solution: absorbed
preserve:
- Water
quantity: 10
@@ -149,7 +149,6 @@
- type: SolutionTransfer
canSend: false # No giving away infinite space cleaner!
- type: SolutionRegeneration
solution: drink
generated:
reagents:
- ReagentId: SpaceCleaner
@@ -11,7 +11,6 @@
- ReagentId: Nocturine
Quantity: 12
- type: SolutionRegeneration
solution: hypospray
generated:
reagents:
- ReagentId: Nocturine
@@ -397,7 +397,7 @@
Quantity: 15
- type: entity
parent: [SolutionGinormous, BaseBrandedLighter, BaseCentcommContraband]
parent: [SolutionToolWelderExperimental, BaseBrandedLighter, BaseCentcommContraband]
id: CentCommFlippo
name: Gilded CentComm Flippo
description: "An Ornate, jade embossed and gilded flippo frame containing a bluespace powered jet. The latch is secured by a miniature access reader that only responds to CentComm officials. The nicest lighter known to man."
@@ -421,21 +421,10 @@
color: Gold
- type: RefillableSolution
solution: Welder
- type: Solution
solution:
reagents:
- ReagentId: Plasma
Quantity: 480
- type: Tool
useSound:
collection: Welder
qualities: Welding
- type: SolutionRegeneration
solution: Welder
generated:
reagents:
- ReagentId: Plasma
Quantity: 0.1
- type: Lock
- type: AccessReader
access: [["CentralCommand"]]
@@ -168,12 +168,8 @@
enabled: false
radius: 1.5
color: lightblue
- type: SolutionRegeneration
solution: welder
generated:
reagents:
- ReagentId: WeldingFuel
Quantity: 1
- type: Welder
fuelReagent: Plasma
- type: RequiresEyeProtection
statusEffectTime: 5 # less harmful; sunglasses can block it
@@ -185,8 +181,13 @@
- type: Solution
solution:
reagents:
- ReagentId: WeldingFuel
- ReagentId: Plasma
Quantity: 480 # Literally infinite so this number basically doesn't matter, also this should be a battery tbqh.
- type: SolutionRegeneration
generated:
reagents:
- ReagentId: Plasma
Quantity: 1
- type: entity
name: emergency welding tool
@@ -243,7 +244,6 @@
fuelConsumption: 2
fuelLitCost: 1
- type: SolutionRegeneration
solution: welder
generated:
reagents:
- ReagentId: WeldingFuel
@@ -25,7 +25,6 @@
solutions:
- SolutionDrainNormal
- type: SolutionRegeneration
solution: tank
generated:
reagents:
- ReagentId: Water
@@ -88,7 +88,6 @@
solutions:
- SolutionDrainNormal
- type: SolutionRegeneration
solution: tank
generated:
reagents:
- ReagentId: Water
@@ -25,7 +25,6 @@
- ReagentId: Water
Quantity: 7680
- type: SolutionRegeneration
solution: pool
generated:
reagents:
- ReagentId: Water