Solutions Refactor Part 1 - Solution Prototypes (#43412)

* Everything except the YAML slop...

* She solution on my manager till I sajfslkahfsjakfhaskjfshajksafhfsakhfasjfas

* another 1000 lines

* fix chem dispenser size

* go my shnelf

* Implicit ass

* rider is being mean for some reason

* dasdas

* borger

* better formatting go!

* clothes/bloodstream

* Cartons

* cups and bottles

* mmm soder

* bar drinks

* Spray bottles and some size tweaks with hindsight

* 99 bottles of beer on the wall I hate YAML

* push that shit

* mmm burger

* Sneed

* sheets

* condiments

* mmm yummy

* fridge yummyfood

* meat...

* sub 300

* burger...

* bready

* let them eat cake

* does she know how to make a grilled cheese?

* pizza pie!

* misc shit

* soup or salad

* Food and drinks, vanquished

* the cubes!!!

* Almost free from YAML...

* fix test fails and some yaml issues

* fix prediction, almost done

* fix all test fails

* remove master merge artifacts and undo autonetworking

* review and compatibility

* graaah

* unfuck master merge I hate github

* merg conflicts

* sfsa

* ehoop

* sadas

* afsafsaasf

* merge conflicts

* fucked up the merge conflicts

* merge conflict is fucked I might need to completely redo this branch

* test fail

* no calcium????

---------

Co-authored-by: Princess Cheeseballs <66055347+Pronana@users.noreply.github.com>
This commit is contained in:
Princess Cheeseballs
2026-04-24 19:00:34 -07:00
committed by GitHub
parent d74f891288
commit ef3a0ecc2a
246 changed files with 9297 additions and 11888 deletions
@@ -1,7 +1,8 @@
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.Components.SolutionManager;
using Content.Shared.Chemistry.EntitySystems;
using Robust.Shared.GameStates;
namespace Content.Client.Chemistry.Containers.EntitySystems;
public sealed partial class SolutionContainerSystem : SharedSolutionContainerSystem
{
}
public sealed partial class SolutionContainerSystem : SharedSolutionContainerSystem;
@@ -117,8 +117,7 @@ public sealed class ChemistryGuideDataSystem : SharedChemistryGuideDataSystem
if (extractableComponent.GrindableSolutionName is { } grindableSolutionId &&
entProto.TryGetComponent<SolutionContainerManagerComponent>(out var manager, EntityManager.ComponentFactory) &&
_solutionContainer.TryGetSolution(manager, grindableSolutionId, out var grindableSolution))
_solutionContainer.TryGetSolution(entProto, grindableSolutionId, out var grindableSolution))
{
var data = new ReagentEntitySourceData(
new() { DefaultGrindCategory },
@@ -23,15 +23,15 @@ public sealed class DrainTest : InteractionTest
- type: entity
parent: Puddle
id: PuddleBloodTest
suffix: Blood (30u)
suffix: Blood
components:
- type: SolutionContainerManager
solutions:
puddle:
maxVol: 1000
reagents:
- ReagentId: {BloodReagent}
Quantity: {PuddleVolume}
- type: Solution
id: puddle
solution:
maxVol: 1000
reagents:
- ReagentId: {BloodReagent}
Quantity: {PuddleVolume}
";
@@ -21,10 +21,10 @@ public sealed class SolutionRoundingTest : GameTest
- type: entity
id: SolutionRoundingTestContainer
components:
- type: SolutionContainerManager
solutions:
beaker:
maxVol: 100
- type: Solution
id: beaker
solution:
maxVol: 100
# This is the Chloral Hydrate recipe fyi.
- type: reagent
@@ -20,10 +20,10 @@ public sealed class SolutionSystemTests : GameTest
- type: entity
id: SolutionTarget
components:
- type: SolutionContainerManager
solutions:
beaker:
maxVol: 50
- type: Solution
id: beaker
solution:
maxVol: 50
- type: reagent
id: TestReagentA
@@ -20,11 +20,10 @@ namespace Content.IntegrationTests.Tests.Chemistry
- type: entity
id: TestSolutionContainer
components:
- type: SolutionContainerManager
solutions:
beaker:
maxVol: 50
canMix: true";
- type: Solution
id: beaker
solution:
maxVol: 120";
private static string[] _reactions = GameDataScrounger.PrototypesOfKind<ReactionPrototype>();
@@ -35,19 +35,19 @@ public sealed class AbsorbentTest : GameTest
components:
- type: Absorbent
useAbsorberSolution: true
- type: SolutionContainerManager
solutions:
absorbed:
maxVol: 100
- type: Solution
id: absorbed
solution:
maxVol: 100
- type: entity
name: {RefillableDummyId}
id: {RefillableDummyId}
components:
- type: SolutionContainerManager
solutions:
refillable:
maxVol: 200
- type: Solution
id: refillable
solution:
maxVol: 200
- type: RefillableSolution
solution: refillable
@@ -55,10 +55,10 @@ public sealed class AbsorbentTest : GameTest
name: {SmallRefillableDummyId}
id: {SmallRefillableDummyId}
components:
- type: SolutionContainerManager
solutions:
refillable:
maxVol: 20
- type: Solution
id: refillable
solution:
maxVol: 20
- type: RefillableSolution
solution: refillable
";
@@ -36,16 +36,17 @@ namespace Content.Server.Administration.Commands
return;
}
if (!_entManager.TryGetComponent(uid, out SolutionContainerManagerComponent? man))
{
shell.WriteLine($"Entity does not have any solutions.");
return;
}
var solutionContainerSystem = _entManager.System<SharedSolutionContainerSystem>();
if (!solutionContainerSystem.TryGetSolution((uid.Value, man), args[1], out var solution))
if (!solutionContainerSystem.TryGetSolution(uid.Value, args[1], out var solution))
{
var validSolutions = string.Join(", ", solutionContainerSystem.EnumerateSolutions((uid.Value, man)).Select(s => s.Name));
var solutions = solutionContainerSystem.EnumerateSolutions(uid.Value).ToArray();
if (!solutions.Any())
{
shell.WriteLine("Entity does not have any solutions!");
return;
}
var validSolutions = string.Join(", ", solutions.Select(s => s.Name));
shell.WriteLine($"Entity does not have a \"{args[1]}\" solution. Valid solutions are:\n{validSolutions}");
return;
}
@@ -24,22 +24,23 @@ namespace Content.Server.Administration.Commands
return;
}
if (!NetEntity.TryParse(args[0], out var uidNet))
if (!NetEntity.TryParse(args[0], out var uidNet) || !_entManager.TryGetEntity(uidNet, out var uid))
{
shell.WriteLine($"Invalid entity id.");
return;
}
if (!_entManager.TryGetEntity(uidNet, out var uid) || !_entManager.TryGetComponent(uid, out SolutionContainerManagerComponent? man))
{
shell.WriteLine($"Entity does not have any solutions.");
return;
}
var solutionContainerSystem = _entManager.System<SharedSolutionContainerSystem>();
if (!solutionContainerSystem.TryGetSolution((uid.Value, man), args[1], out var solution))
if (!solutionContainerSystem.TryGetSolution(uid.Value, args[1], out var solution))
{
var validSolutions = string.Join(", ", solutionContainerSystem.EnumerateSolutions((uid.Value, man)).Select(s => s.Name));
var solutions = solutionContainerSystem.EnumerateSolutions(uid.Value).ToArray();
if (!solutions.Any())
{
shell.WriteLine("Entity does not have any solutions!");
return;
}
var validSolutions = string.Join(", ", solutions.Select(s => s.Name));
shell.WriteLine($"Entity does not have a \"{args[1]}\" solution. Valid solutions are:\n{validSolutions}");
return;
}
@@ -29,16 +29,17 @@ namespace Content.Server.Administration.Commands
return;
}
if (!_entManager.TryGetComponent(uid, out SolutionContainerManagerComponent? man))
{
shell.WriteLine($"Entity does not have any solutions.");
return;
}
var solutionContainerSystem = _entManager.System<SharedSolutionContainerSystem>();
if (!solutionContainerSystem.TryGetSolution((uid.Value, man), args[1], out var solution))
if (!solutionContainerSystem.TryGetSolution(uid.Value, args[1], out var solution))
{
var validSolutions = string.Join(", ", solutionContainerSystem.EnumerateSolutions((uid.Value, man)).Select(s => s.Name));
var solutions = solutionContainerSystem.EnumerateSolutions(uid.Value).ToArray();
if (!solutions.Any())
{
shell.WriteLine("Entity does not have any solutions!");
return;
}
var validSolutions = string.Join(", ", solutions.Select(s => s.Name));
shell.WriteLine($"Entity does not have a \"{args[1]}\" solution. Valid solutions are:\n{validSolutions}");
return;
}
@@ -29,16 +29,17 @@ namespace Content.Server.Administration.Commands
return;
}
if (!_entManager.TryGetComponent(uid, out SolutionContainerManagerComponent? man))
{
shell.WriteLine($"Entity does not have any solutions.");
return;
}
var solutionContainerSystem = _entManager.System<SharedSolutionContainerSystem>();
if (!solutionContainerSystem.TryGetSolution((uid.Value, man), args[1], out var solutionEnt, out var solution))
if (!solutionContainerSystem.TryGetSolution(uid.Value, args[1], out var solutionEnt, out var solution))
{
var validSolutions = string.Join(", ", solutionContainerSystem.EnumerateSolutions((uid.Value, man)).Select(s => s.Name));
var solutions = solutionContainerSystem.EnumerateSolutions(uid.Value).ToArray();
if (!solutions.Any())
{
shell.WriteLine("Entity does not have any solutions!");
return;
}
var validSolutions = string.Join(", ", solutions.Select(s => s.Name));
shell.WriteLine($"Entity does not have a \"{args[1]}\" solution. Valid solutions are:\n{validSolutions}");
return;
}
@@ -35,6 +35,7 @@ using Robust.Shared.Timing;
using Robust.Shared.Toolshed;
using Robust.Shared.Utility;
using System.Linq;
using Content.Shared.Chemistry.Components;
using static Content.Shared.Configurable.ConfigurationComponent;
namespace Content.Server.Administration.Systems
@@ -73,7 +74,10 @@ namespace Content.Server.Administration.Systems
{
SubscribeLocalEvent<GetVerbsEvent<Verb>>(GetVerbs);
SubscribeLocalEvent<RoundRestartCleanupEvent>(Reset);
SubscribeLocalEvent<SolutionContainerManagerComponent, SolutionContainerChangedEvent>(OnSolutionChanged);
// TODO: This is genuinely terrible, solutions are already networked and we shouldn't need to update the BUI like this.
SubscribeLocalEvent<SolutionComponent, SolutionChangedEvent>((x, ref _) => OnSolutionChanged(x.Owner));
SubscribeLocalEvent<SolutionManagerComponent, SolutionChangedEvent>((x, ref _) => OnSolutionChanged(x.Owner));
}
private void GetVerbs(GetVerbsEvent<Verb> ev)
@@ -445,7 +449,7 @@ namespace Content.Server.Administration.Systems
}
// Control mob verb
if ((_toolshed.ActivePermissionController?.CheckInvokable(new CommandSpec(_toolshed.DefaultEnvironment.GetCommand("mind"), "control"), player, out _) ?? false) &&
if (_toolshed.ActivePermissionController?.CheckInvokable(new CommandSpec(_toolshed.DefaultEnvironment.GetCommand("mind"), "control"), player, out _) ?? false &&
args.User != args.Target)
{
Verb verb = new()
@@ -573,7 +577,7 @@ namespace Content.Server.Administration.Systems
// Add verb to open Solution Editor
if (_groupController.CanCommand(player, "addreagent") &&
HasComp<SolutionContainerManagerComponent>(args.Target))
(HasComp<SolutionManagerComponent>(args.Target) || HasComp<SolutionComponent>(args.Target)))
{
Verb verb = new()
{
@@ -588,13 +592,13 @@ namespace Content.Server.Administration.Systems
}
#region SolutionsEui
private void OnSolutionChanged(Entity<SolutionContainerManagerComponent> entity, ref SolutionContainerChangedEvent args)
private void OnSolutionChanged(EntityUid uid)
{
foreach (var list in _openSolutionUis.Values)
{
foreach (var eui in list)
{
if (eui.Target == entity.Owner)
if (eui.Target == uid)
eui.StateDirty();
}
}
@@ -1,12 +1,9 @@
using Content.Server.Chemistry.Containers.EntitySystems;
using Content.Shared.Administration;
using Content.Shared.Administration;
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.FixedPoint;
using Content.Shared.Chemistry.EntitySystems;
using Robust.Shared.Toolshed;
using Robust.Shared.Toolshed.Syntax;
using Robust.Shared.Toolshed.TypeParsers;
using System.Linq;
using Robust.Shared.Prototypes;
@@ -1,5 +1,4 @@
using Content.Server.Administration.Systems;
using Content.Server.Chemistry.Containers.EntitySystems;
using Content.Server.EUI;
using Content.Shared.Administration;
using Content.Shared.Chemistry.Components.SolutionManager;
@@ -42,21 +41,15 @@ namespace Content.Server.Administration.UI
public override EuiStateBase GetNewState()
{
List<(string Name, NetEntity Solution)>? netSolutions;
List<(string Name, NetEntity Solution)>? netSolutions = new();
if (_entityManager.TryGetComponent(Target, out SolutionContainerManagerComponent? container) && container.Containers.Count > 0)
foreach (var (name, solution) in _solutionContainerSystem.EnumerateSolutions(Target))
{
netSolutions = new();
foreach (var (name, solution) in _solutionContainerSystem.EnumerateSolutions((Target, container)))
{
if (name is null || !_entityManager.TryGetNetEntity(solution, out var netSolution))
continue;
if (name is null || !_entityManager.TryGetNetEntity(solution, out var netSolution))
continue;
netSolutions.Add((name, netSolution.Value));
}
netSolutions.Add((name, netSolution.Value));
}
else
netSolutions = null;
return new EditSolutionsEuiState(_entityManager.GetNetEntity(Target), netSolutions, _gameTiming.CurTick);
}
@@ -11,38 +11,9 @@ public sealed class BloodstreamSystem : SharedBloodstreamSystem
{
base.Initialize();
SubscribeLocalEvent<BloodstreamComponent, ComponentInit>(OnComponentInit);
SubscribeLocalEvent<BloodstreamComponent, GenerateDnaEvent>(OnDnaGenerated);
}
// not sure if we can move this to shared or not
// it would certainly help if SolutionContainer was documented
// but since we usually don't add the component dynamically to entities we can keep this unpredicted for now
private void OnComponentInit(Entity<BloodstreamComponent> entity, ref ComponentInit args)
{
if (!SolutionContainer.EnsureSolution(entity.Owner,
entity.Comp.BloodSolutionName,
out var bloodSolution) ||
!SolutionContainer.EnsureSolution(entity.Owner,
entity.Comp.BloodTemporarySolutionName,
out var tempSolution) ||
!SolutionContainer.EnsureSolution(entity.Owner,
entity.Comp.MetabolitesSolutionName,
out var metabolitesSolution))
return;
bloodSolution.MaxVolume = entity.Comp.BloodReferenceSolution.Volume * entity.Comp.MaxVolumeModifier;
metabolitesSolution.MaxVolume = bloodSolution.MaxVolume;
tempSolution.MaxVolume = entity.Comp.BleedPuddleThreshold * 4; // give some leeway, for chemstream as well
entity.Comp.BloodReferenceSolution.SetReagentData(GetEntityBloodData((entity, entity.Comp)));
// Fill blood solution with BLOOD
// The DNA string might not be initialized yet, but the reagent data gets updated in the GenerateDnaEvent subscription
var solution = entity.Comp.BloodReferenceSolution.Clone();
solution.ScaleTo(entity.Comp.BloodReferenceSolution.Volume - bloodSolution.Volume);
bloodSolution.AddSolution(solution, PrototypeManager);
}
// forensics is not predicted yet
private void OnDnaGenerated(Entity<BloodstreamComponent> entity, ref GenerateDnaEvent args)
{
@@ -20,21 +20,17 @@ public sealed partial class BotanySystem
_entityEffects.TryApplyEffect(uid, mutation.Effect);
}
if (!_solutionContainerSystem.EnsureSolution(uid,
produce.SolutionName,
out var solutionContainer,
FixedPoint2.Zero))
return;
_solutionContainerSystem.EnsureSolution(uid, produce.SolutionName, out var solution);
solutionContainer.RemoveAllSolution();
solution.Comp.Solution.RemoveAllSolution();
foreach (var (chem, quantity) in seed.Chemicals)
{
var amount = quantity.Min;
if (quantity.PotencyDivisor > 0 && seed.Potency > 0)
amount += seed.Potency / quantity.PotencyDivisor;
amount = FixedPoint2.Clamp(amount, quantity.Min, quantity.Max);
solutionContainer.MaxVolume += amount;
solutionContainer.AddReagent(chem, amount);
solution.Comp.Solution.MaxVolume += amount;
solution.Comp.Solution.AddReagent(chem, amount);
}
}
+29 -30
View File
@@ -130,14 +130,11 @@ public sealed class PricingSystem : EntitySystem
args.Price += entity.Comp.RandomPrice ?? 0;
}
private double GetSolutionPrice(Entity<SolutionContainerManagerComponent> entity)
private double GetSolutionPrice(EntityUid entity)
{
if (Comp<MetaDataComponent>(entity).EntityLifeStage < EntityLifeStage.MapInitialized)
return GetSolutionPrice(entity.Comp);
var price = 0.0;
foreach (var (_, soln) in _solutionContainerSystem.EnumerateSolutions((entity.Owner, entity.Comp)))
foreach (var (_, soln) in _solutionContainerSystem.EnumerateSolutions(entity))
{
var solution = soln.Comp.Solution;
foreach (var (reagent, quantity) in solution.Contents)
@@ -153,25 +150,6 @@ public sealed class PricingSystem : EntitySystem
return price;
}
private double GetSolutionPrice(SolutionContainerManagerComponent component)
{
var price = 0.0;
foreach (var (_, prototype) in _solutionContainerSystem.EnumerateSolutions(component))
{
foreach (var (reagent, quantity) in prototype.Contents)
{
if (!_prototypeManager.TryIndex<ReagentPrototype>(reagent.Prototype, out var reagentProto))
continue;
// TODO check ReagentData for price information?
price += (float) quantity * reagentProto.PricePerUnit;
}
}
return price;
}
private double GetMaterialPrice(PhysicalCompositionComponent component)
{
double price = 0;
@@ -319,22 +297,43 @@ public sealed class PricingSystem : EntitySystem
{
var price = 0.0;
if (TryComp<SolutionContainerManagerComponent>(uid, out var solComp))
var meta = MetaData(uid);
if (meta.EntityLifeStage < EntityLifeStage.MapInitialized)
return GetSolutionsPrice(meta.EntityPrototype);
foreach (var (_, soln) in _solutionContainerSystem.EnumerateSolutions(uid))
{
price += GetSolutionPrice((uid, solComp));
var solution = soln.Comp.Solution;
foreach (var (reagent, quantity) in solution.Contents)
{
if (!_prototypeManager.TryIndex<ReagentPrototype>(reagent.Prototype, out var reagentProto))
continue;
// TODO check ReagentData for price information?
price += (float) quantity * reagentProto.PricePerUnit;
}
}
return price;
}
private double GetSolutionsPrice(EntityPrototype prototype)
private double GetSolutionsPrice(EntityPrototype? prototype)
{
var price = 0.0;
if (prototype.Components.TryGetValue(Factory.GetComponentName<SolutionContainerManagerComponent>(), out var solManager))
if (prototype == null)
return price;
foreach (var (_, solution) in _solutionContainerSystem.EnumerateSolutions(prototype))
{
var solComp = (SolutionContainerManagerComponent) solManager.Component;
price += GetSolutionPrice(solComp);
foreach (var (reagent, quantity) in solution.Contents)
{
if (!_prototypeManager.TryIndex<ReagentPrototype>(reagent.Prototype, out var reagentProto))
continue;
// TODO check ReagentData for price information?
price += (float) quantity * reagentProto.PricePerUnit;
}
}
return price;
@@ -1,45 +0,0 @@
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.Components.SolutionManager;
using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.FixedPoint;
using Robust.Shared.Containers;
using Robust.Shared.Map;
using Robust.Shared.Utility;
using System.Numerics;
namespace Content.Server.Chemistry.Containers.EntitySystems;
[Obsolete("This is being depreciated. Use SharedSolutionContainerSystem instead!")]
public sealed partial class SolutionContainerSystem : SharedSolutionContainerSystem
{
[Obsolete("This is being depreciated. Use the ensure methods in SharedSolutionContainerSystem instead!")]
public Solution EnsureSolution(Entity<MetaDataComponent?> entity, string name)
=> EnsureSolution(entity, name, out _);
[Obsolete("This is being depreciated. Use the ensure methods in SharedSolutionContainerSystem instead!")]
public Solution EnsureSolution(Entity<MetaDataComponent?> entity, string name, out bool existed)
=> EnsureSolution(entity, name, FixedPoint2.Zero, out existed);
[Obsolete("This is being depreciated. Use the ensure methods in SharedSolutionContainerSystem instead!")]
public Solution EnsureSolution(Entity<MetaDataComponent?> entity, string name, FixedPoint2 maxVol, out bool existed)
=> EnsureSolution(entity, name, maxVol, null, out existed);
[Obsolete("This is being depreciated. Use the ensure methods in SharedSolutionContainerSystem instead!")]
public Solution EnsureSolution(Entity<MetaDataComponent?> entity, string name, FixedPoint2 maxVol, Solution? prototype, out bool existed)
{
EnsureSolution(entity, name, maxVol, prototype, out existed, out var solution);
return solution!;//solution is only ever null on the client, so we can suppress this
}
[Obsolete("This is being depreciated. Use the ensure methods in SharedSolutionContainerSystem instead!")]
public Entity<SolutionComponent> EnsureSolutionEntity(
Entity<SolutionContainerManagerComponent?> entity,
string name,
FixedPoint2 maxVol,
Solution? prototype,
out bool existed)
{
EnsureSolutionEntity(entity, name, out existed, out var solEnt, maxVol, prototype);
return solEnt!.Value;//solEnt is only ever null on the client, so we can suppress this
}
}
@@ -47,7 +47,7 @@ namespace Content.Server.Chemistry.EntitySystems
base.Initialize();
SubscribeLocalEvent<ChemMasterComponent, ComponentStartup>(SubscribeUpdateUiState);
SubscribeLocalEvent<ChemMasterComponent, SolutionContainerChangedEvent>(SubscribeUpdateUiState);
SubscribeLocalEvent<ChemMasterComponent, SolutionChangedEvent>(SubscribeUpdateUiState);
SubscribeLocalEvent<ChemMasterComponent, EntInsertedIntoContainerMessage>(SubscribeUpdateUiState);
SubscribeLocalEvent<ChemMasterComponent, EntRemovedFromContainerMessage>(SubscribeUpdateUiState);
// Subscribing to DragDropTargetEvent is a quick fix to ensure the UI updates when fluids are dragged and dropped into the ChemMaster, since Shared.Fluids.EntitySystems.SolutionDumpingSystem.cs bypasses UpdateChemicals().
@@ -235,22 +235,17 @@ namespace Content.Server.Chemistry.EntitySystems
_storageSystem.Insert(container, item, out _, user: user, storage);
_labelSystem.Label(item, message.Label);
_solutionContainerSystem.EnsureSolutionEntity(item,
SharedChemMaster.PillSolutionName,
out var itemSolution,
message.Dosage);
if (!itemSolution.HasValue)
return;
_solutionContainerSystem.EnsureSolution(item, SharedChemMaster.PillSolutionName, out var itemSolution);
itemSolution.Comp.Solution.MaxVolume = message.Dosage;
_solutionContainerSystem.TryAddSolution(itemSolution.Value, withdrawal.SplitSolution(message.Dosage));
_solutionContainerSystem.TryAddSolution(itemSolution, withdrawal.SplitSolution(message.Dosage));
var pill = EnsureComp<PillComponent>(item);
pill.PillType = chemMaster.Comp.PillType;
Dirty(item, pill);
// Log pill creation by a user
_adminLogger.Add(LogType.Action, LogImpact.Low,
$"{ToPrettyString(user):user} printed {ToPrettyString(item):pill} {SharedSolutionContainerSystem.ToPrettyString(itemSolution.Value.Comp.Solution)}");
_adminLogger.Add(LogType.Action, LogImpact.Low, $"{ToPrettyString(user):user} printed {ToPrettyString(item):pill} {SharedSolutionContainerSystem.ToPrettyString(itemSolution.Comp.Solution)}");
}
UpdateUiState(chemMaster);
@@ -12,7 +12,7 @@ namespace Content.Server.Chemistry.EntitySystems.DeleteOnSolutionEmptySystem
{
base.Initialize();
SubscribeLocalEvent<DeleteOnSolutionEmptyComponent, ComponentStartup>(OnStartup);
SubscribeLocalEvent<DeleteOnSolutionEmptyComponent, SolutionContainerChangedEvent>(OnSolutionChange);
SubscribeLocalEvent<DeleteOnSolutionEmptyComponent, SolutionChangedEvent>(OnSolutionChange);
}
public void OnStartup(Entity<DeleteOnSolutionEmptyComponent> entity, ref ComponentStartup args)
@@ -20,19 +20,23 @@ namespace Content.Server.Chemistry.EntitySystems.DeleteOnSolutionEmptySystem
CheckSolutions(entity);
}
public void OnSolutionChange(Entity<DeleteOnSolutionEmptyComponent> entity, ref SolutionContainerChangedEvent args)
public void OnSolutionChange(Entity<DeleteOnSolutionEmptyComponent> entity, ref SolutionChangedEvent args)
{
CheckSolutions(entity);
var solution = args.Solution.Comp.Solution;
if (args.Solution.Comp.Id != entity.Comp.Solution)
return;
if (solution.Volume <= 0)
QueueDel(entity);
}
public void CheckSolutions(Entity<DeleteOnSolutionEmptyComponent> entity)
{
if (!TryComp(entity, out SolutionContainerManagerComponent? solutions))
if (!_solutionContainerSystem.TryGetSolution(entity.Owner, entity.Comp.Solution, out _, out var solution))
return;
if (_solutionContainerSystem.TryGetSolution((entity.Owner, solutions), entity.Comp.Solution, out _, out var solution))
if (solution.Volume <= 0)
QueueDel(entity);
if (solution.Volume <= 0)
QueueDel(entity);
}
}
}
@@ -1,6 +1,5 @@
using System.Linq;
using Content.Server.Chemistry.Components;
using Content.Server.Chemistry.Containers.EntitySystems;
using Content.Shared.Chemistry;
using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Containers.ItemSlots;
@@ -40,7 +39,7 @@ namespace Content.Server.Chemistry.EntitySystems
base.Initialize();
SubscribeLocalEvent<ReagentDispenserComponent, ComponentStartup>(SubscribeUpdateUiState);
SubscribeLocalEvent<ReagentDispenserComponent, SolutionContainerChangedEvent>(SubscribeUpdateUiState);
SubscribeLocalEvent<ReagentDispenserComponent, SolutionChangedEvent>(SubscribeUpdateUiState);
SubscribeLocalEvent<ReagentDispenserComponent, EntInsertedIntoContainerMessage>(SubscribeUpdateUiState, after: [typeof(SharedStorageSystem)]);
SubscribeLocalEvent<ReagentDispenserComponent, EntRemovedFromContainerMessage>(SubscribeUpdateUiState, after: [typeof(SharedStorageSystem)]);
SubscribeLocalEvent<ReagentDispenserComponent, BoundUIOpenedEvent>(SubscribeUpdateUiState);
@@ -0,0 +1,5 @@
using Content.Shared.Chemistry.EntitySystems;
namespace Content.Server.Chemistry.EntitySystems;
public sealed class SolutionContainerSystem : SharedSolutionContainerSystem;
@@ -81,13 +81,10 @@ public sealed class SolutionHeaterSystem : EntitySystem
var query = EntityQueryEnumerator<ActiveSolutionHeaterComponent, SolutionHeaterComponent, ItemPlacerComponent>();
while (query.MoveNext(out _, out _, out var heater, out var placer))
{
var energy = heater.HeatPerSecond * frameTime;
foreach (var heatingEntity in placer.PlacedEntities)
{
if (!TryComp<SolutionContainerManagerComponent>(heatingEntity, out var container))
continue;
var energy = heater.HeatPerSecond * frameTime;
foreach (var (_, soln) in _solutionContainer.EnumerateSolutions((heatingEntity, container)))
foreach (var (_, soln) in _solutionContainer.EnumerateSolutions(heatingEntity))
{
_solutionContainer.AddThermalEnergy(soln, energy);
}
@@ -3,6 +3,7 @@ using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.Random;
using Content.Shared.Random.Helpers;
using Content.Shared.Storage.Components;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
@@ -37,8 +38,10 @@ public sealed class SolutionRandomFillSystem : EntitySystem
return;
}
_solutionsSystem.EnsureSolutionEntity(entity.Owner, entity.Comp.Solution, out var target , pick.quantity);
if(target.HasValue)
_solutionsSystem.TryAddReagent(target.Value, reagent, quantity);
_solutionsSystem.EnsureSolution(entity.Owner, entity.Comp.Solution, out var target);
if (target.Comp.Solution.AvailableVolume < quantity)
Log.Error($"A random solution fill {entity.Comp.WeightedRandomId} tried to put {pick.quantity} of {pick.reagent} into {ToPrettyString(target)} but there was not enough space!");
_solutionsSystem.TryAddReagent(target, reagent, quantity);
}
}
@@ -18,7 +18,7 @@ public sealed class TransformableContainerSystem : EntitySystem
base.Initialize();
SubscribeLocalEvent<TransformableContainerComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<TransformableContainerComponent, SolutionContainerChangedEvent>(OnSolutionChange);
SubscribeLocalEvent<TransformableContainerComponent, SolutionChangedEvent>(OnSolutionChange);
SubscribeLocalEvent<TransformableContainerComponent, RefreshNameModifiersEvent>(OnRefreshNameModifiers);
}
@@ -31,7 +31,7 @@ public sealed class TransformableContainerSystem : EntitySystem
}
}
private void OnSolutionChange(Entity<TransformableContainerComponent> entity, ref SolutionContainerChangedEvent args)
private void OnSolutionChange(Entity<TransformableContainerComponent> entity, ref SolutionChangedEvent args)
{
if (!_solutionsSystem.TryGetFitsInDispenser(entity.Owner, out _, out var solution))
return;
@@ -39,9 +39,7 @@ namespace Content.Server.Chemistry.EntitySystems
private void HandleCollide(Entity<VaporComponent> entity, ref StartCollideEvent args)
{
if (!TryComp(entity.Owner, out SolutionContainerManagerComponent? contents)) return;
foreach (var (_, soln) in _solutionContainerSystem.EnumerateSolutions((entity.Owner, contents)))
foreach (var (_, soln) in _solutionContainerSystem.EnumerateSolutions(entity.Owner))
{
var solution = soln.Comp.Solution;
_reactive.DoEntityReaction(args.OtherEntity, solution, ReactionMethod.Touch);
@@ -102,7 +100,8 @@ namespace Content.Server.Chemistry.EntitySystems
base.Update(frameTime);
// Enumerate over all VaporComponents
var query = EntityQueryEnumerator<VaporComponent, SolutionContainerManagerComponent, TransformComponent>();
// 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))
{
// Return early if we're not active
@@ -30,7 +30,8 @@ public sealed partial class SpillBehavior : IThresholdBehavior
var coordinates = system.EntityManager.GetComponent<TransformComponent>(owner).Coordinates;
// Spill the solution that was drained/split
if (solutionContainer.TryGetSolution(owner, Solution, out _, out var solution))
// TODO: ??? Top 10 reasons for solution entity prototypes right here bruh.
if (Solution != null && solutionContainer.TryGetSolution(owner, Solution, out _, out var solution))
puddleSystem.TrySplashSpillAt(owner, coordinates, solution, out _, false, cause);
else
puddleSystem.TrySplashSpillAt(owner, coordinates, out _, out _, false, cause);
@@ -294,20 +294,13 @@ public sealed partial class PuddleSystem : SharedPuddleSystem
Solution addedSolution,
bool sound = true,
bool checkForOverflow = true,
PuddleComponent? puddleComponent = null,
SolutionContainerManagerComponent? sol = null)
PuddleComponent? puddleComponent = null)
{
if (!Resolve(puddleUid, ref puddleComponent, ref sol))
if (!Resolve(puddleUid, ref puddleComponent))
return false;
_solutionContainerSystem.EnsureAllSolutions((puddleUid, sol));
if (addedSolution.Volume == 0 ||
!_solutionContainerSystem.ResolveSolution(puddleUid, puddleComponent.SolutionName,
ref puddleComponent.Solution))
{
if (addedSolution.Volume == 0 || !_solutionContainerSystem.ResolveSolution(puddleUid, puddleComponent.SolutionName, ref puddleComponent.Solution))
return false;
}
_solutionContainerSystem.AddSolution(puddleComponent.Solution.Value, addedSolution);
@@ -204,7 +204,7 @@ public sealed class SmokeSystem : EntitySystem
private void OnReactionAttempt(Entity<SmokeComponent> entity, ref SolutionRelayEvent<ReactionAttemptEvent> args)
{
if (args.Name == SmokeComponent.SolutionName)
if (args.Solution.Comp.Id == SmokeComponent.SolutionName)
OnReactionAttempt(entity, ref args.Event);
}
@@ -13,9 +13,7 @@ using Content.Shared.Tag;
using Robust.Shared.Audio.Systems;
using Robust.Server.GameObjects;
using Robust.Shared.Audio;
using Robust.Shared.Player;
using Robust.Shared.Timing;
using Content.Server.Chemistry.Containers.EntitySystems;
using Robust.Shared.Prototypes;
// todo: remove this stinky LINQy
@@ -47,11 +47,11 @@ namespace Content.Server.Forensics
SubscribeLocalEvent<CleansForensicsComponent, AfterInteractEvent>(OnAfterInteract, after: new[] { typeof(AbsorbentSystem) });
SubscribeLocalEvent<ForensicsComponent, CleanForensicsDoAfterEvent>(OnCleanForensicsDoAfter);
SubscribeLocalEvent<DnaComponent, TransferDnaEvent>(OnTransferDnaEvent);
SubscribeLocalEvent<DnaSubstanceTraceComponent, SolutionContainerChangedEvent>(OnSolutionChanged);
SubscribeLocalEvent<DnaSubstanceTraceComponent, SolutionChangedEvent>(OnSolutionChanged);
SubscribeLocalEvent<CleansForensicsComponent, GetVerbsEvent<UtilityVerb>>(OnUtilityVerb);
}
private void OnSolutionChanged(Entity<DnaSubstanceTraceComponent> ent, ref SolutionContainerChangedEvent ev)
private void OnSolutionChanged(Entity<DnaSubstanceTraceComponent> ent, ref SolutionChangedEvent ev)
{
var soln = GetSolutionsDNA(ev.Solution);
if (soln.Count > 0)
@@ -152,12 +152,9 @@ namespace Content.Server.Forensics
public List<string> GetSolutionsDNA(EntityUid uid)
{
List<string> list = new();
if (TryComp<SolutionContainerManagerComponent>(uid, out var comp))
foreach (var (_, soln) in _solutionContainerSystem.EnumerateSolutions(uid))
{
foreach (var (_, soln) in _solutionContainerSystem.EnumerateSolutions((uid, comp)))
{
list.AddRange(GetSolutionsDNA(soln.Comp.Solution));
}
list.AddRange(GetSolutionsDNA(soln.Comp.Solution));
}
return list;
}
@@ -78,7 +78,7 @@ namespace Content.Server.Kitchen.EntitySystems
SubscribeLocalEvent<MicrowaveComponent, ComponentInit>(OnInit);
SubscribeLocalEvent<MicrowaveComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<MicrowaveComponent, SolutionContainerChangedEvent>(OnSolutionChange);
SubscribeLocalEvent<MicrowaveComponent, SolutionChangedEvent>(OnSolutionChange);
SubscribeLocalEvent<MicrowaveComponent, EntInsertedIntoContainerMessage>(OnContentUpdate);
SubscribeLocalEvent<MicrowaveComponent, EntRemovedFromContainerMessage>(OnContentUpdate);
SubscribeLocalEvent<MicrowaveComponent, InteractUsingEvent>(OnInteractUsing, after: new[] { typeof(AnchorableSystem) });
@@ -181,9 +181,7 @@ namespace Content.Server.Kitchen.EntitySystems
if (TryComp<TemperatureComponent>(entity, out var tempComp))
_temperature.ChangeHeat(entity, heatToAdd * component.ObjectHeatMultiplier, false, tempComp);
if (!TryComp<SolutionContainerManagerComponent>(entity, out var solutions))
continue;
foreach (var (_, soln) in _solutionContainer.EnumerateSolutions((entity, solutions)))
foreach (var (_, soln) in _solutionContainer.EnumerateSolutions(entity))
{
var solution = soln.Comp.Solution;
if (solution.Temperature > component.TemperatureUpperThreshold)
@@ -319,7 +317,7 @@ namespace Content.Server.Kitchen.EntitySystems
args.Handled = true;
}
private void OnSolutionChange(Entity<MicrowaveComponent> ent, ref SolutionContainerChangedEvent args)
private void OnSolutionChange(Entity<MicrowaveComponent> ent, ref SolutionChangedEvent args)
{
UpdateUserInterfaceState(ent, ent.Comp);
}
@@ -69,20 +69,19 @@ public sealed class MaterialReclaimerSystem : SharedMaterialReclaimerSystem
private void OnInteractUsing(Entity<MaterialReclaimerComponent> entity, ref InteractUsingEvent args)
{
if (args.Handled)
if (args.Handled || entity.Comp.SolutionContainerId == null)
return;
// if we're trying to get a solution out of the reclaimer, don't destroy it
if (_solutionContainer.TryGetSolution(entity.Owner, entity.Comp.SolutionContainerId, out _, out var outputSolution) && outputSolution.Contents.Any())
{
if (TryComp<SolutionContainerManagerComponent>(args.Used, out var managerComponent) &&
_solutionContainer.EnumerateSolutions((args.Used, managerComponent)).Any(s => s.Solution.Comp.Solution.AvailableVolume > 0))
if (_solutionContainer.EnumerateSolutions(args.Used).Any(s => s.Solution.Comp.Solution.AvailableVolume > 0))
{
if (_openable.IsClosed(args.Used))
return;
if (TryComp<SolutionTransferComponent>(args.Used, out var transfer) &&
transfer.CanReceive)
transfer.CanSend)
return;
}
}
@@ -250,7 +249,7 @@ public sealed class MaterialReclaimerSystem : SharedMaterialReclaimerSystem
TransformComponent? xform = null,
PhysicalCompositionComponent? composition = null)
{
if (!Resolve(reclaimer, ref reclaimerComponent, ref xform))
if (!Resolve(reclaimer, ref reclaimerComponent, ref xform) || reclaimerComponent.SolutionContainerId == null)
return;
efficiency *= reclaimerComponent.Efficiency;
@@ -34,7 +34,7 @@ public sealed class SliceableFoodSystem : EntitySystem
SubscribeLocalEvent<SliceableFoodComponent, InteractUsingEvent>(OnInteractUsing);
SubscribeLocalEvent<SliceableFoodComponent, SliceFoodDoAfterEvent>(OnSlicedoAfter);
SubscribeLocalEvent<SliceableFoodComponent, ComponentStartup>(OnComponentStartup);
SubscribeLocalEvent<SliceableFoodComponent, MapInitEvent>(OnMapInit);
}
private void OnInteractUsing(Entity<SliceableFoodComponent> entity, ref InteractUsingEvent args)
@@ -156,10 +156,9 @@ public sealed class SliceableFoodSystem : EntitySystem
_solutionContainer.TryAddSolution(itsSoln.Value, lostSolutionPart);
}
private void OnComponentStartup(Entity<SliceableFoodComponent> entity, ref ComponentStartup args)
private void OnMapInit(Entity<SliceableFoodComponent> entity, ref MapInitEvent args)
{
// TODO: When Food Component is fully kill delete this awful method
// This exists just to make tests fail I guess, awesome!
// This exists just to make tests fail!
// If you're here because your test just failed, make sure that:
// Your food has the edible component
// The solution listed in the edible component exists
@@ -81,11 +81,10 @@ namespace Content.Server.Nutrition.EntitySystems
EntityUid contents = entity.Comp.BowlSlot.Item.Value;
if (!TryComp<SolutionContainerManagerComponent>(contents, out var reagents) ||
!_solutionContainerSystem.TryGetSolution(smokable.Owner, smokable.Comp.Solution, out var pipeSolution, out _))
if (!_solutionContainerSystem.TryGetSolution(smokable.Owner, smokable.Comp.Solution, out var pipeSolution, out _))
return false;
foreach (var (_, soln) in _solutionContainerSystem.EnumerateSolutions((contents, reagents)))
foreach (var (_, soln) in _solutionContainerSystem.EnumerateSolutions(contents))
{
var reagentSolution = soln.Comp.Solution;
_solutionContainerSystem.TryAddSolution(pipeSolution.Value, reagentSolution);
@@ -18,7 +18,7 @@ namespace Content.Server.Nutrition.EntitySystems
{
base.Initialize();
SubscribeLocalEvent<TrashOnSolutionEmptyComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<TrashOnSolutionEmptyComponent, SolutionContainerChangedEvent>(OnSolutionChange);
SubscribeLocalEvent<TrashOnSolutionEmptyComponent, SolutionChangedEvent>(OnSolutionChange);
}
public void OnMapInit(Entity<TrashOnSolutionEmptyComponent> entity, ref MapInitEvent args)
@@ -26,16 +26,13 @@ namespace Content.Server.Nutrition.EntitySystems
CheckSolutions(entity);
}
public void OnSolutionChange(Entity<TrashOnSolutionEmptyComponent> entity, ref SolutionContainerChangedEvent args)
public void OnSolutionChange(Entity<TrashOnSolutionEmptyComponent> entity, ref SolutionChangedEvent args)
{
CheckSolutions(entity);
}
public void CheckSolutions(Entity<TrashOnSolutionEmptyComponent> entity)
{
if (!HasComp<SolutionContainerManagerComponent>(entity))
return;
if (_solutionContainerSystem.TryGetSolution(entity.Owner, entity.Comp.Solution, out _, out var solution))
UpdateTags(entity, solution);
}
@@ -25,7 +25,7 @@ public sealed class RiggableSystem : EntitySystem
base.Initialize();
SubscribeLocalEvent<RiggableComponent, RejuvenateEvent>(OnRejuvenate);
SubscribeLocalEvent<RiggableComponent, BeingMicrowavedEvent>(OnMicrowaved);
SubscribeLocalEvent<RiggableComponent, SolutionContainerChangedEvent>(OnSolutionChanged);
SubscribeLocalEvent<RiggableComponent, SolutionChangedEvent>(OnSolutionChanged);
SubscribeLocalEvent<RiggableComponent, ChargeChangedEvent>(OnChargeChanged);
}
@@ -47,13 +47,14 @@ public sealed class RiggableSystem : EntitySystem
}
}
private void OnSolutionChanged(Entity<RiggableComponent> entity, ref SolutionContainerChangedEvent args)
private void OnSolutionChanged(Entity<RiggableComponent> entity, ref SolutionChangedEvent args)
{
if (args.SolutionId != entity.Comp.Solution)
if (args.Solution.Comp.Id != entity.Comp.Solution)
return;
var wasRigged = entity.Comp.IsRigged;
var quantity = args.Solution.GetReagentQuantity(entity.Comp.RequiredQuantity.Reagent);
var solution = args.Solution.Comp.Solution;
var quantity = solution.GetReagentQuantity(entity.Comp.RequiredQuantity.Reagent);
entity.Comp.IsRigged = quantity >= entity.Comp.RequiredQuantity.Quantity;
if (entity.Comp.IsRigged && !wasRigged)
@@ -27,7 +27,7 @@ public sealed partial class ChemicalFuelGeneratorAdapterComponent : Component
public string SolutionName = "tank";
/// <summary>
/// The solution on the <see cref="SolutionContainerManagerComponent"/> to use.
/// The solution to use.
/// </summary>
[ViewVariables]
public Entity<SolutionComponent>? Solution = null;
@@ -26,7 +26,7 @@ namespace Content.Server.Stunnable.Systems
base.Initialize();
SubscribeLocalEvent<StunbatonComponent, ExaminedEvent>(OnExamined);
SubscribeLocalEvent<StunbatonComponent, SolutionContainerChangedEvent>(OnSolutionChange);
SubscribeLocalEvent<StunbatonComponent, SolutionChangedEvent>(OnSolutionChange);
SubscribeLocalEvent<StunbatonComponent, StaminaDamageOnHitAttemptEvent>(OnStaminaHitAttempt);
SubscribeLocalEvent<StunbatonComponent, ChargeChangedEvent>(OnChargeChanged);
}
@@ -75,7 +75,7 @@ namespace Content.Server.Stunnable.Systems
}
// https://github.com/space-wizards/space-station-14/pull/17288#discussion_r1241213341
private void OnSolutionChange(Entity<StunbatonComponent> entity, ref SolutionContainerChangedEvent args)
private void OnSolutionChange(Entity<StunbatonComponent> entity, ref SolutionChangedEvent args)
{
// Explode if baton is activated and rigged.
if (!TryComp<RiggableComponent>(entity, out var riggable) ||
@@ -18,7 +18,7 @@ public sealed partial class GunSystem
base.InitializeSolution();
SubscribeLocalEvent<SolutionAmmoProviderComponent, MapInitEvent>(OnSolutionMapInit);
SubscribeLocalEvent<SolutionAmmoProviderComponent, SolutionContainerChangedEvent>(OnSolutionChanged);
SubscribeLocalEvent<SolutionAmmoProviderComponent, SolutionChangedEvent>(OnSolutionChanged);
}
private void OnSolutionMapInit(Entity<SolutionAmmoProviderComponent> entity, ref MapInitEvent args)
@@ -26,10 +26,10 @@ public sealed partial class GunSystem
UpdateSolutionShots(entity);
}
private void OnSolutionChanged(Entity<SolutionAmmoProviderComponent> entity, ref SolutionContainerChangedEvent args)
private void OnSolutionChanged(Entity<SolutionAmmoProviderComponent> entity, ref SolutionChangedEvent args)
{
if (args.Solution.Name == entity.Comp.SolutionId)
UpdateSolutionShots(entity, args.Solution);
if (args.Solution.Comp.Id == entity.Comp.SolutionId)
UpdateSolutionShots(entity, args.Solution.Comp.Solution);
}
protected override void UpdateSolutionShots(Entity<SolutionAmmoProviderComponent> ent, Solution? solution = null)
@@ -11,7 +11,7 @@ namespace Content.Shared.Administration
public readonly List<(string, NetEntity)>? Solutions;
public readonly GameTick Tick;
public EditSolutionsEuiState(NetEntity target, List<(string, NetEntity)>? solutions, GameTick tick)
public EditSolutionsEuiState(NetEntity target, List<(string, NetEntity)> solutions, GameTick tick)
{
Target = target;
Solutions = solutions;
@@ -148,10 +148,10 @@ public sealed partial class BloodstreamComponent : Component
/// Defines which reagents are considered as 'blood' and how much of it is normal.
/// </summary>
/// <remarks>
/// Slime-people might use slime as their blood or something like that.
/// Default is human blood at 5 liters (600u) of blood.
/// </remarks>
[DataField, AutoNetworkedField]
public Solution BloodReferenceSolution = new([new("Blood", 300)]);
public Solution BloodReferenceSolution = new([new("Blood", 600)]);
/// <summary>
/// Caches the blood data of an entity.
+6 -7
View File
@@ -20,7 +20,7 @@ public sealed class LungSystem : EntitySystem
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<LungComponent, ComponentInit>(OnComponentInit);
SubscribeLocalEvent<LungComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<BreathToolComponent, GotEquippedEvent>(OnGotEquipped);
SubscribeLocalEvent<BreathToolComponent, GotUnequippedEvent>(OnGotUnequipped);
}
@@ -44,13 +44,12 @@ public sealed class LungSystem : EntitySystem
}
}
private void OnComponentInit(Entity<LungComponent> entity, ref ComponentInit args)
private void OnMapInit(Entity<LungComponent> entity, ref MapInitEvent args)
{
if (_solutionContainerSystem.EnsureSolution(entity.Owner, entity.Comp.SolutionName, out var solution))
{
solution.MaxVolume = 100.0f;
solution.CanReact = false; // No dexalin lungs
}
_solutionContainerSystem.EnsureSolution(entity.Owner, entity.Comp.SolutionName, out var solution);
solution.Comp.Solution.MaxVolume = 100.0f;
solution.Comp.Solution.CanReact = false; // No dexalin lungs
}
// TODO: JUST METABOLIZE GASES DIRECTLY DON'T CONVERT TO REAGENTS!!! (Needs Metabolism refactor :B)
@@ -110,10 +110,25 @@ public abstract class SharedBloodstreamSystem : EntitySystem
}
}
private void OnMapInit(Entity<BloodstreamComponent> ent, ref MapInitEvent args)
private void OnMapInit(Entity<BloodstreamComponent> entity, ref MapInitEvent args)
{
ent.Comp.NextUpdate = _timing.CurTime + ent.Comp.AdjustedUpdateInterval;
DirtyField(ent, ent.Comp, nameof(BloodstreamComponent.NextUpdate));
entity.Comp.NextUpdate = _timing.CurTime + entity.Comp.AdjustedUpdateInterval;
DirtyField(entity, entity.Comp, nameof(BloodstreamComponent.NextUpdate));
SolutionContainer.EnsureSolution(entity.Owner, entity.Comp.BloodSolutionName, out var bloodSolution);
SolutionContainer.EnsureSolution(entity.Owner, entity.Comp.BloodTemporarySolutionName, out var tempSolution);
SolutionContainer.EnsureSolution(entity.Owner, entity.Comp.MetabolitesSolutionName, out var metabolitesSolution);
bloodSolution.Comp.Solution.MaxVolume = entity.Comp.BloodReferenceSolution.Volume * entity.Comp.MaxVolumeModifier;
metabolitesSolution.Comp.Solution.MaxVolume = bloodSolution.Comp.Solution.MaxVolume;
tempSolution.Comp.Solution.MaxVolume = entity.Comp.BleedPuddleThreshold * 4; // give some leeway, for chemstream as well
entity.Comp.BloodReferenceSolution.SetReagentData(GetEntityBloodData((entity, entity.Comp)));
// Fill blood solution with BLOOD
// The DNA string might not be initialized yet, but the reagent data gets updated in the GenerateDnaEvent subscription
var solution = entity.Comp.BloodReferenceSolution.Clone();
solution.ScaleTo(entity.Comp.BloodReferenceSolution.Volume - bloodSolution.Comp.Solution.Volume);
bloodSolution.Comp.Solution.AddSolution(solution, PrototypeManager);
}
// prevent the infamous UdderSystem debug assert, see https://github.com/space-wizards/space-station-14/pull/35314
@@ -157,8 +172,8 @@ public abstract class SharedBloodstreamSystem : EntitySystem
private void OnReactionAttempt(Entity<BloodstreamComponent> ent, ref SolutionRelayEvent<ReactionAttemptEvent> args)
{
if (args.Name != ent.Comp.BloodSolutionName
&& args.Name != ent.Comp.BloodTemporarySolutionName)
if (args.Solution.Comp.Id != ent.Comp.BloodSolutionName
&& args.Solution.Comp.Id != ent.Comp.BloodTemporarySolutionName)
{
return;
}
+8 -20
View File
@@ -2,7 +2,6 @@ using Content.Shared.Body.Components;
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.Components.SolutionManager;
using Content.Shared.Chemistry.EntitySystems;
using Robust.Shared.Utility;
namespace Content.Shared.Body.Systems;
@@ -12,33 +11,22 @@ public sealed class StomachSystem : EntitySystem
public const string DefaultSolutionName = "stomach";
public bool CanTransferSolution(
EntityUid uid,
Solution solution,
StomachComponent? stomach = null,
SolutionContainerManagerComponent? solutions = null)
public bool CanTransferSolution(Entity<StomachComponent?, SolutionManagerComponent?> entity, Solution solution)
{
return Resolve(uid, ref stomach, ref solutions, logMissing: false)
&& _solutionContainerSystem.ResolveSolution((uid, solutions), DefaultSolutionName, ref stomach.Solution, out var stomachSolution)
return Resolve(entity, ref entity.Comp1, logMissing: false)
&& _solutionContainerSystem.ResolveSolution((entity, entity.Comp2), DefaultSolutionName, ref entity.Comp1.Solution, out var stomachSolution)
// TODO: For now no partial transfers. Potentially change by design
&& stomachSolution.CanAddSolution(solution);
}
public bool TryTransferSolution(
EntityUid uid,
Solution solution,
StomachComponent? stomach = null,
SolutionContainerManagerComponent? solutions = null)
public bool TryTransferSolution(Entity<StomachComponent?, SolutionManagerComponent?> entity, Solution solution)
{
if (!Resolve(uid, ref stomach, ref solutions, logMissing: false)
|| !_solutionContainerSystem.ResolveSolution((uid, solutions), DefaultSolutionName, ref stomach.Solution)
|| !CanTransferSolution(uid, solution, stomach, solutions))
{
if (!Resolve(entity, ref entity.Comp1, logMissing: false)
|| !_solutionContainerSystem.ResolveSolution((entity, entity.Comp2), DefaultSolutionName, ref entity.Comp1.Solution)
|| !CanTransferSolution(entity, solution))
return false;
}
_solutionContainerSystem.TryAddSolution(stomach.Solution.Value, solution);
_solutionContainerSystem.TryAddSolution(entity.Comp1.Solution.Value, solution);
return true;
}
}
@@ -1,6 +1,5 @@
using System.Collections;
using System.Linq;
using Content.Shared.Chemistry.Components.SolutionManager;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.FixedPoint;
using JetBrains.Annotations;
@@ -59,12 +58,6 @@ namespace Content.Shared.Chemistry.Components
[DataField]
public float Temperature { get; set; } = 293.15f;
/// <summary>
/// The name of this solution, if it is contained in some <see cref="SolutionContainerManagerComponent"/>
/// </summary>
[DataField]
public string? Name;
/// <summary>
/// Checks if a solution can fit into the container.
/// </summary>
@@ -205,7 +198,7 @@ namespace Content.Shared.Chemistry.Components
DebugTools.Assert(!Contents.Any(x => x.Quantity <= FixedPoint2.Zero));
// No duplicate reagents iDs
DebugTools.Assert(Contents.Select(x => x.Reagent).ToHashSet().Count == Contents.Count);
DebugTools.Assert(Contents.Select(x => x.Reagent).ToHashSet().Count == Contents.Count, $"Solution: {this}, contained duplcate contents {Contents}");
// If it isn't flagged as dirty, check heat capacity is correct.
if (!_heatCapacityDirty)
@@ -1,6 +1,7 @@
using Content.Shared.Chemistry.Components.SolutionManager;
using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Materials;
using Content.Shared.Temperature.Components;
using Robust.Shared.GameStates;
using Robust.Shared.Serialization;
@@ -8,23 +9,33 @@ namespace Content.Shared.Chemistry.Components;
/// <summary>
/// <para>Holds the composition of an entity made from reagents and its reagent temperature.</para>
/// <para>If the entity is used to represent a collection of reagents inside of a container such as a beaker, syringe, bloodstream, food, or similar the entity is tracked by a <see cref="SolutionContainerManagerComponent"/> on the container and has a <see cref="ContainedSolutionComponent"/> tracking which container it's in.</para>
/// <para>If the entity is used to represent a collection of reagents inside of a container such as a beaker, syringe, bloodstream, food, or similar the entity is tracked by a <see cref="SolutionManagerComponent"/> on the container and has a <see cref="ContainedSolutionComponent"/> tracking which container it's in.</para>
/// </summary>
/// <remarks>
/// <para>Once reagents and materials have been merged this component should be depricated in favor of using a combination of <see cref="PhysicalCompositionComponent"/> and <see cref="Content.Server.Temperature.Components.TemperatureComponent"/>. May require minor reworks to both.</para>
/// <para>Once reagents and materials have been merged this component should be depricated in favor of using a combination of <see cref="PhysicalCompositionComponent"/> and <see cref="TemperatureComponent"/>. May require minor reworks to both.</para>
/// </remarks>
[RegisterComponent, NetworkedComponent]
[Access(typeof(SharedSolutionContainerSystem))]
public sealed partial class SolutionComponent : Component
{
public const string DefaultSolutionId = "solution";
/// <summary>
/// The name of this solution. This value should *never* change once the solution is initialized.
/// </summary>
[DataField]
[Access(typeof(SharedSolutionContainerSystem))]
public string Id = DefaultSolutionId;
/// <summary>
/// <para>The reagents the entity is composed of and their temperature.</para>
/// </summary>
[DataField]
[DataField, AlwaysPushInheritance]
public Solution Solution = new();
}
/// <remarks>
/// We manually network the component state as it raises one less event and therefore is better performance wise.
/// </remarks>
[Serializable, NetSerializable]
public sealed class SolutionComponentState(Solution solution) : ComponentState
{
@@ -12,6 +12,7 @@ namespace Content.Shared.Chemistry.Components.SolutionManager;
/// The <see cref="Solution.MaxVolume"/> field should then be extracted out into this component.
/// Solution entities would just become an apporpriately composed entity hanging out in the container.
/// Will probably require entities in components being given a relation to associate themselves with their container.
/// TODO: Proper relations system so this can be initialized with a SolutionComponent attached to it :)
/// </remarks>
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
[Access(typeof(SharedSolutionContainerSystem))]
@@ -20,12 +21,6 @@ public sealed partial class ContainedSolutionComponent : Component
/// <summary>
/// The entity that the solution is contained in.
/// </summary>
[DataField(required: true), AutoNetworkedField]
[DataField, AutoNetworkedField]
public EntityUid Container;
/// <summary>
/// The name/key of the container the solution is located in.
/// </summary>
[DataField(required: true), AutoNetworkedField]
public string ContainerName = default!;
}
@@ -4,11 +4,14 @@ using Robust.Shared.GameStates;
namespace Content.Shared.Chemistry.Components.SolutionManager;
/// <summary>
/// <para>A map of the solution entities contained within this entity.</para>
/// <para>Every solution entity this maps should have a <see cref="SolutionComponent"/> to track its state and a <see cref="ContainedSolutionComponent"/> to track its container.</para>
/// </summary>
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
/// <remarks>
/// Exists for simple backwards compatibility.
/// On <see cref="ComponentInit"/> this component will transfer all its data where it can to a <see cref="SolutionManagerComponent"/>
/// Then it will delete itself.
/// This component will be deleted in the indeterminate future.
/// </remarks>
[Obsolete]
[RegisterComponent]
[Access(typeof(SharedSolutionContainerSystem))]
public sealed partial class SolutionContainerManagerComponent : Component
{
@@ -22,7 +25,7 @@ public sealed partial class SolutionContainerManagerComponent : Component
/// The names of each solution container attached to this entity.
/// Actually accessing them must be done via <see cref="ContainerManagerComponent"/>.
/// </summary>
[DataField, AutoNetworkedField]
[DataField]
public HashSet<string> Containers = new(DefaultCapacity);
/// <summary>
@@ -31,6 +34,6 @@ public sealed partial class SolutionContainerManagerComponent : Component
/// <remarks>
/// Should be null after mapinit.
/// </remarks>
[DataField, AutoNetworkedField]
public Dictionary<string, Solution>? Solutions = null;
[DataField]
public Dictionary<string, Solution>? Solutions;
}
@@ -0,0 +1,37 @@
using Content.Shared.Chemistry.EntitySystems;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
namespace Content.Shared.Chemistry.Components.SolutionManager;
/// <summary>
/// <para>Allows for an entity to have and manage multiple solutions.</para>
/// <para>Spawns additional solutions from their prototypes, and stores them in a container.</para>
/// <para>Also used in the case another component spawns a solution for this entity.</para>
/// <para>Every solution entity this maps should have a <see cref="SolutionComponent"/> to track its state and a <see cref="ContainedSolutionComponent"/> to track its container.</para>
/// </summary>
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
[Access(typeof(SharedSolutionContainerSystem))]
public sealed partial class SolutionManagerComponent : Component
{
public static readonly string DefaultContainerId = "solutions";
/// <summary>
/// The names of the container for solutions attached to this entity.
/// </summary>
[DataField, AutoNetworkedField]
public string Container = DefaultContainerId;
/// <summary>
/// A cache of solutions currently attached to this entity.
/// </summary>
[ViewVariables]
[Access(typeof(SharedSolutionContainerSystem), Other = AccessPermissions.None)]
public Dictionary<string, Entity<SolutionComponent>> Solutions = new ();
/// <summary>
/// A list of solution entities to spawn when this component starts up.
/// </summary>
[DataField("solutions", readOnly: true)]
public List<EntProtoId> SolutionEnts = new ();
}
@@ -25,7 +25,7 @@ public sealed partial class SolutionTransferComponent : Component
/// The maximum amount of solution that can be transferred at once from this solution.
/// </summary>
[DataField("maxTransferAmount"), AutoNetworkedField]
public FixedPoint2 MaximumTransferAmount = FixedPoint2.New(100);
public FixedPoint2 MaximumTransferAmount = FixedPoint2.New(120);
/// <summary>
/// Can this entity take reagent from reagent tanks?
@@ -17,7 +17,7 @@ public sealed class ReactiveContainerSystem : EntitySystem
base.Initialize();
SubscribeLocalEvent<ReactiveContainerComponent, EntInsertedIntoContainerMessage>(OnInserted);
SubscribeLocalEvent<ReactiveContainerComponent, SolutionContainerChangedEvent>(OnSolutionChange);
SubscribeLocalEvent<ReactiveContainerComponent, SolutionChangedEvent>(OnSolutionChange);
}
private void OnInserted(EntityUid uid, ReactiveContainerComponent comp, EntInsertedIntoContainerMessage args)
@@ -34,7 +34,7 @@ public sealed class ReactiveContainerSystem : EntitySystem
_reactiveSystem.DoEntityReaction(args.Entity, solution, ReactionMethod.Touch);
}
private void OnSolutionChange(EntityUid uid, ReactiveContainerComponent comp, SolutionContainerChangedEvent args)
private void OnSolutionChange(EntityUid uid, ReactiveContainerComponent comp, SolutionChangedEvent args)
{
// The changes are already networked as part of the same game state.
if (_timing.ApplyingState)
@@ -42,10 +42,10 @@ public sealed class ReactiveContainerSystem : EntitySystem
if (!_solutionContainerSystem.TryGetSolution(uid, comp.Solution, out _, out var solution))
return;
if (solution.Volume == 0)
return;
if (!TryComp<ContainerManagerComponent>(uid, out var manager))
return;
if (!_containerSystem.TryGetContainer(uid, comp.Container, out var container))
return;
@@ -23,17 +23,17 @@ public sealed class RehydratableSystem : EntitySystem
{
base.Initialize();
SubscribeLocalEvent<RehydratableComponent, SolutionContainerChangedEvent>(OnSolutionChange);
SubscribeLocalEvent<RehydratableComponent, SolutionChangedEvent>(OnSolutionChange);
}
private void OnSolutionChange(Entity<RehydratableComponent> ent, ref SolutionContainerChangedEvent args)
private void OnSolutionChange(Entity<RehydratableComponent> ent, ref SolutionChangedEvent args)
{
// The changes are already networked as part of the same game state.
if (_timing.ApplyingState)
return;
var quantity = _solutions.GetTotalPrototypeQuantity(ent, ent.Comp.CatalystPrototype);
_adminLogger.Add(LogType.Action, LogImpact.Medium, $"{ToPrettyString(ent.Owner)} was hydrated, now contains a solution of: {SharedSolutionContainerSystem.ToPrettyString(args.Solution)}.");
var quantity = _solutions.GetTotalPrototypeQuantity(ent.Owner, ent.Comp.CatalystPrototype);
_adminLogger.Add(LogType.Action, LogImpact.Medium, $"{ToPrettyString(ent.Owner)} was hydrated, now contains a solution of: {SharedSolutionContainerSystem.ToPrettyString(args.Solution.Comp.Solution)}.");
if (quantity != FixedPoint2.Zero && quantity >= ent.Comp.CatalystMinimum)
{
Expand(ent);
@@ -11,7 +11,7 @@ public abstract partial class SharedSolutionContainerSystem
{
#region Solution Accessors
public bool TryGetRefillableSolution(Entity<RefillableSolutionComponent?, SolutionContainerManagerComponent?> entity, [NotNullWhen(true)] out Entity<SolutionComponent>? soln, [NotNullWhen(true)] out Solution? solution)
public bool TryGetRefillableSolution(Entity<RefillableSolutionComponent?, SolutionManagerComponent?> entity, [NotNullWhen(true)] out Entity<SolutionComponent>? soln, [NotNullWhen(true)] out Solution? solution)
{
if (!Resolve(entity, ref entity.Comp1, logMissing: false))
{
@@ -22,7 +22,7 @@ public abstract partial class SharedSolutionContainerSystem
return TryGetSolution((entity.Owner, entity.Comp2), entity.Comp1.Solution, out soln, out solution);
}
public bool TryGetDrainableSolution(Entity<DrainableSolutionComponent?, SolutionContainerManagerComponent?> entity, [NotNullWhen(true)] out Entity<SolutionComponent>? soln, [NotNullWhen(true)] out Solution? solution)
public bool TryGetDrainableSolution(Entity<DrainableSolutionComponent?, SolutionManagerComponent?> entity, [NotNullWhen(true)] out Entity<SolutionComponent>? soln, [NotNullWhen(true)] out Solution? solution)
{
if (!Resolve(entity, ref entity.Comp1, logMissing: false))
{
@@ -33,9 +33,9 @@ public abstract partial class SharedSolutionContainerSystem
return TryGetSolution((entity.Owner, entity.Comp2), entity.Comp1.Solution, out soln, out solution);
}
public bool TryGetExtractableSolution(Entity<ExtractableComponent?, SolutionContainerManagerComponent?> entity, [NotNullWhen(true)] out Entity<SolutionComponent>? soln, [NotNullWhen(true)] out Solution? solution)
public bool TryGetExtractableSolution(Entity<ExtractableComponent?, SolutionManagerComponent?> entity, [NotNullWhen(true)] out Entity<SolutionComponent>? soln, [NotNullWhen(true)] out Solution? solution)
{
if (!Resolve(entity, ref entity.Comp1, logMissing: false))
if (!Resolve(entity, ref entity.Comp1, logMissing: false) || entity.Comp1.GrindableSolutionName == null)
{
(soln, solution) = (default!, null);
return false;
@@ -44,7 +44,7 @@ public abstract partial class SharedSolutionContainerSystem
return TryGetSolution((entity.Owner, entity.Comp2), entity.Comp1.GrindableSolutionName, out soln, out solution);
}
public bool TryGetDumpableSolution(Entity<DumpableSolutionComponent?, SolutionContainerManagerComponent?> entity, [NotNullWhen(true)] out Entity<SolutionComponent>? soln, [NotNullWhen(true)] out Solution? solution)
public bool TryGetDumpableSolution(Entity<DumpableSolutionComponent?, SolutionManagerComponent?> entity, [NotNullWhen(true)] out Entity<SolutionComponent>? soln, [NotNullWhen(true)] out Solution? solution)
{
if (!Resolve(entity, ref entity.Comp1, logMissing: false))
{
@@ -55,7 +55,7 @@ public abstract partial class SharedSolutionContainerSystem
return TryGetSolution((entity.Owner, entity.Comp2), entity.Comp1.Solution, out soln, out solution);
}
public bool TryGetDrawableSolution(Entity<DrawableSolutionComponent?, SolutionContainerManagerComponent?> entity, [NotNullWhen(true)] out Entity<SolutionComponent>? soln, [NotNullWhen(true)] out Solution? solution)
public bool TryGetDrawableSolution(Entity<DrawableSolutionComponent?, SolutionManagerComponent?> entity, [NotNullWhen(true)] out Entity<SolutionComponent>? soln, [NotNullWhen(true)] out Solution? solution)
{
if (!Resolve(entity, ref entity.Comp1, logMissing: false))
{
@@ -66,7 +66,7 @@ public abstract partial class SharedSolutionContainerSystem
return TryGetSolution((entity.Owner, entity.Comp2), entity.Comp1.Solution, out soln, out solution);
}
public bool TryGetInjectableSolution(Entity<InjectableSolutionComponent?, SolutionContainerManagerComponent?> entity, [NotNullWhen(true)] out Entity<SolutionComponent>? soln, [NotNullWhen(true)] out Solution? solution)
public bool TryGetInjectableSolution(Entity<InjectableSolutionComponent?, SolutionManagerComponent?> entity, [NotNullWhen(true)] out Entity<SolutionComponent>? soln, [NotNullWhen(true)] out Solution? solution)
{
if (!Resolve(entity, ref entity.Comp1, logMissing: false))
{
@@ -77,7 +77,7 @@ public abstract partial class SharedSolutionContainerSystem
return TryGetSolution((entity.Owner, entity.Comp2), entity.Comp1.Solution, out soln, out solution);
}
public bool TryGetFitsInDispenser(Entity<FitsInDispenserComponent?, SolutionContainerManagerComponent?> entity, [NotNullWhen(true)] out Entity<SolutionComponent>? soln, [NotNullWhen(true)] out Solution? solution)
public bool TryGetFitsInDispenser(Entity<FitsInDispenserComponent?, SolutionManagerComponent?> entity, [NotNullWhen(true)] out Entity<SolutionComponent>? soln, [NotNullWhen(true)] out Solution? solution)
{
if (!Resolve(entity, ref entity.Comp1, logMissing: false))
{
@@ -88,7 +88,7 @@ public abstract partial class SharedSolutionContainerSystem
return TryGetSolution((entity.Owner, entity.Comp2), entity.Comp1.Solution, out soln, out solution);
}
public bool TryGetMixableSolution(Entity<MixableSolutionComponent?, SolutionContainerManagerComponent?> entity, [NotNullWhen(true)] out Entity<SolutionComponent>? soln, [NotNullWhen(true)] out Solution? solution)
public bool TryGetMixableSolution(Entity<MixableSolutionComponent?, SolutionManagerComponent?> entity, [NotNullWhen(true)] out Entity<SolutionComponent>? soln, [NotNullWhen(true)] out Solution? solution)
{
if (!Resolve(entity, ref entity.Comp1, logMissing: false))
{
@@ -148,13 +148,17 @@ public abstract partial class SharedSolutionContainerSystem
#region Static Methods
public static string ToPrettyString(SolutionComponent solution)
{
var sb = new StringBuilder($"{solution.Id}:");
sb.Append(ToPrettyString(solution.Solution));
return sb.ToString();
}
public static string ToPrettyString(Solution solution)
{
var sb = new StringBuilder();
if (solution.Name == null)
sb.Append("[");
else
sb.Append($"{solution.Name}:[");
sb.Append("[");
var first = true;
foreach (var (id, quantity) in solution.Contents)
{
@@ -0,0 +1,79 @@
using Content.Shared.Chemistry.Components.SolutionManager;
using Robust.Shared.Containers;
namespace Content.Shared.Chemistry.EntitySystems;
/// <summary>
/// This exists so that entity prototypes and maps with <see cref="SolutionContainerManagerComponent"/> can load their solutions.
/// This system is extremely simple and will not have compatibility for new features that come as a result of future refactors.
/// This compatibility layer will degrade over time and eventually break as more solution logic relies on well-defined prototypes.
/// This is only here to give you more time to port your solutions to the new system. It is going to be deleted eventually.
/// You have been warned.
/// </summary>
public abstract partial class SharedSolutionContainerSystem
{
public void InitializeContainerManager()
{
SubscribeLocalEvent<SolutionContainerManagerComponent, MapInitEvent>(OnSolutionContainerInit);
}
private void OnSolutionContainerInit(Entity<SolutionContainerManagerComponent> container, ref MapInitEvent args)
{
// Create the manager, this should also create a container, so we ensure it exists.
EnsureComp<SolutionManagerComponent>(container, out var manager);
var solutionContainer = ContainerSystem.EnsureContainer<Container>(container, manager.Container);
// First, if this entity was saved with an entity in a container, try to put it in the SolutionManager
foreach (var name in container.Comp.Containers)
{
if (ContainerSystem.GetContainer(container, $"solution@{name}") is not ContainerSlot slot || slot.ContainedEntity is not { } solutionUid)
continue;
if (!SolutionQuery.TryComp(solutionUid, out var solution))
continue;
if (TryGetSolution(container.Owner, name, out var solutionEnt))
{
// Only a warning so tests don't fail. If you're using this to find maps/prototypes which need porting, change this to Log.Error so tests fail.
Log.Warning($"Attempted to port a solution id: {name} entity: {ToPrettyString(solutionUid)} " +
$"from a {nameof(SolutionContainerManagerComponent)} on {ToPrettyString(container)}, {MetaData(container).EntityPrototype}, " +
$"but the entity already had a solution with that id.");
solutionEnt.Value.Comp.Solution = solution.Solution;
}
else
{
ContainerSystem.Insert(solutionUid, solutionContainer, force: true);
}
// We don't need it anymore
ContainerSystem.ShutdownContainer(slot);
}
if (container.Comp.Solutions == null)
{
RemCompDeferred<SolutionManagerComponent>(container);
return;
}
// Next, if this entity was never initialized, create its solutions.
foreach (var (name, solution) in container.Comp.Solutions)
{
// Solution already exists so we ignore it.
if (EnsureSolution(container.Owner, name, out var solutionEnt))
{
// Only a warning so tests don't fail. If you're using this to find maps/prototypes which need porting, change this to Log.Error so tests fail.
Log.Warning($"Attempted to port a solution id: {name} " +
$"from a {nameof(SolutionContainerManagerComponent)} on {ToPrettyString(container)}, {MetaData(container).EntityPrototype}, " +
$"but the entity already had a solution with that id.");
}
// Clone the solution to the component.
solutionEnt.Comp.Solution = solution;
}
// Clear its data
container.Comp.Solutions = null;
RemCompDeferred<SolutionManagerComponent>(container);
}
}
@@ -7,23 +7,6 @@ namespace Content.Shared.Chemistry.EntitySystems;
#region Events
/// <summary>
/// Raised on the container of the solution entity when the contained solution is changed.
/// If you want to subscribe with the solution entity itself
/// then use <see cref="SolutionChangedEvent"/> instead.
/// </summary>
/// <remarks>
/// This is always raised on the client when handling the component state so that we can update UIs accordingly.
/// You might need an IGameTiming.ApplyingState guard to prevent mispredicts if the changes from your subscription are
/// networked with the same game state.
/// </remarks>
[ByRefEvent]
public record struct SolutionContainerChangedEvent(Solution Solution, string SolutionId)
{
public readonly Solution Solution = Solution;
public readonly string SolutionId = SolutionId;
}
/// <summary>
/// An event raised when more reagents are added to a (managed) solution than it can hold.
/// </summary>
@@ -47,13 +30,11 @@ public record struct SolutionContainerOverflowEvent(EntityUid SolutionEnt, Solut
/// </summary>
/// <typeparam name="TEvent"></typeparam>
/// <param name="Event">The event that is being relayed.</param>
/// <param name="ContainerEnt">The container entity that the event is being relayed to.</param>
/// <param name="Name">The name of the solution entity that the event is being relayed from.</param>
/// <param name="Solution">The container entity that the event is being relayed to.</param>
[ByRefEvent]
public record struct SolutionRelayEvent<TEvent>(TEvent Event, EntityUid ContainerEnt, string Name)
public record struct SolutionRelayEvent<TEvent>(TEvent Event, Entity<SolutionComponent> Solution)
{
public readonly EntityUid ContainerEnt = ContainerEnt;
public readonly string Name = Name;
public readonly Entity<SolutionComponent> Solution = Solution;
public TEvent Event = Event;
}
@@ -78,28 +59,12 @@ public abstract partial class SharedSolutionContainerSystem
{
protected void InitializeRelays()
{
SubscribeLocalEvent<ContainedSolutionComponent, SolutionChangedEvent>(OnSolutionChanged);
SubscribeLocalEvent<ContainedSolutionComponent, SolutionOverflowEvent>(OnSolutionOverflow);
SubscribeLocalEvent<ContainedSolutionComponent, ReactionAttemptEvent>(RelaySolutionRefEvent);
}
#region Event Handlers
protected virtual void OnSolutionChanged(Entity<ContainedSolutionComponent> entity, ref SolutionChangedEvent args)
{
var (solutionId, solutionComp) = args.Solution;
var solution = solutionComp.Solution;
var relayEvent = new SolutionContainerChangedEvent(solution, entity.Comp.ContainerName);
RaiseLocalEvent(entity.Comp.Container, ref relayEvent);
// The appearance changes are already networked as part of the same game state.
if (_timing.ApplyingState)
return;
UpdateAppearance(entity.Comp.Container, (solutionId, solutionComp, entity.Comp));
}
protected virtual void OnSolutionOverflow(Entity<ContainedSolutionComponent> entity, ref SolutionOverflowEvent args)
{
var solution = args.Solution.Comp.Solution;
@@ -117,18 +82,20 @@ public abstract partial class SharedSolutionContainerSystem
private void RelaySolutionValEvent<TEvent>(EntityUid uid, ContainedSolutionComponent comp, TEvent @event)
{
var relayEvent = new SolutionRelayEvent<TEvent>(@event, uid, comp.ContainerName);
var solution = Comp<SolutionComponent>(uid);
var relayEvent = new SolutionRelayEvent<TEvent>(@event, (uid, solution));
RaiseLocalEvent(comp.Container, ref relayEvent);
}
private void RelaySolutionRefEvent<TEvent>(Entity<ContainedSolutionComponent> entity, ref TEvent @event)
{
var relayEvent = new SolutionRelayEvent<TEvent>(@event, entity.Owner, entity.Comp.ContainerName);
var solution = Comp<SolutionComponent>(entity);
var relayEvent = new SolutionRelayEvent<TEvent>(@event, (entity, solution));
RaiseLocalEvent(entity.Comp.Container, ref relayEvent);
@event = relayEvent.Event;
}
private void RelaySolutionContainerEvent<TEvent>(EntityUid uid, SolutionContainerManagerComponent comp, TEvent @event)
private void RelaySolutionContainerEvent<TEvent>(EntityUid uid, SolutionManagerComponent comp, TEvent @event)
{
foreach (var (name, soln) in EnumerateSolutions((uid, comp)))
{
@@ -137,7 +104,7 @@ public abstract partial class SharedSolutionContainerSystem
}
}
private void RelaySolutionContainerEvent<TEvent>(Entity<SolutionContainerManagerComponent> entity, ref TEvent @event)
private void RelaySolutionContainerEvent<TEvent>(Entity<SolutionManagerComponent> entity, ref TEvent @event)
{
foreach (var (name, soln) in EnumerateSolutions((entity.Owner, entity.Comp)))
{
@@ -1,6 +1,5 @@
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Numerics;
using System.Runtime.CompilerServices;
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.Components.SolutionManager;
@@ -16,7 +15,6 @@ using Content.Shared.Verbs;
using JetBrains.Annotations;
using Robust.Shared.Containers;
using Robust.Shared.GameStates;
using Robust.Shared.Map;
using Robust.Shared.Network;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
@@ -27,9 +25,8 @@ namespace Content.Shared.Chemistry.EntitySystems;
/// <summary>
/// The event raised whenever a solution entity is modified.
/// Raised on the solution entity itself.
/// If you want to subscribe with the entity containing the solution entity
/// then use <see cref="SolutionContainerChangedEvent"/> instead.
/// This event is raised on the owner of the solution.
/// If the changed solution is contained in a <see cref="SolutionManagerComponent"/>, it will be raised on the owner of that component.
/// </summary>
/// <remarks>
/// Raised after chemcial reactions and <see cref="SolutionOverflowEvent"/> are handled.
@@ -69,38 +66,42 @@ public partial record struct SolutionAccessAttemptEvent(string SolutionName)
[UsedImplicitly]
public abstract partial class SharedSolutionContainerSystem : EntitySystem
{
public static readonly EntProtoId DefaultSolution = "Solution";
[Dependency] protected readonly IGameTiming Timing = default!;
[Dependency] protected readonly INetManager Net = default!;
[Dependency] protected readonly IPrototypeManager PrototypeManager = default!;
[Dependency] protected readonly ChemicalReactionSystem ChemicalReactionSystem = default!;
[Dependency] protected readonly ExamineSystemShared ExamineSystem = default!;
[Dependency] protected readonly OpenableSystem Openable = default!;
[Dependency] protected readonly SharedAppearanceSystem AppearanceSystem = default!;
[Dependency] protected readonly SharedHandsSystem Hands = default!;
[Dependency] protected readonly SharedContainerSystem ContainerSystem = default!;
[Dependency] protected readonly MetaDataSystem MetaDataSys = default!;
[Dependency] protected readonly INetManager NetManager = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] protected readonly SharedHandsSystem Hands = default!;
[Dependency] protected readonly EntityQuery<ContainedSolutionComponent> ContainedQuery = default!;
[Dependency] protected readonly EntityQuery<SolutionComponent> SolutionQuery = default!;
[Dependency] protected readonly EntityQuery<SolutionManagerComponent> SolutionManagerQuery = default!;
public override void Initialize()
{
base.Initialize();
InitializeRelays();
InitializeContainerManager();
SubscribeLocalEvent<SolutionComponent, ComponentGetState>(OnSolutionGetState);
SubscribeLocalEvent<SolutionComponent, ComponentHandleState>(OnSolutionHandleState);
SubscribeLocalEvent<SolutionComponent, ComponentInit>(OnComponentInit);
SubscribeLocalEvent<SolutionComponent, ComponentStartup>(OnSolutionStartup);
SubscribeLocalEvent<SolutionComponent, MapInitEvent>(OnSolutionInit);
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);
}
SubscribeLocalEvent<SolutionManagerComponent, MapInitEvent>(OnManagerInit);
SubscribeLocalEvent<SolutionManagerComponent, ComponentShutdown>(OnManagerShutdown);
SubscribeLocalEvent<SolutionManagerComponent, EntInsertedIntoContainerMessage>(OnSolutionAdded);
SubscribeLocalEvent<SolutionManagerComponent, EntRemovedFromContainerMessage>(OnSolutionRemoved);
}
private void OnSolutionGetState(Entity<SolutionComponent> ent, ref ComponentGetState args)
@@ -118,32 +119,37 @@ public abstract partial class SharedSolutionContainerSystem : EntitySystem
// Always raise the event on the client so that we can update UIs accordingly.
var changedEv = new SolutionChangedEvent(ent);
RaiseLocalEvent(ent, ref changedEv);
if (!ContainedQuery.TryComp(ent, out var contained) || !SolutionManagerQuery.TryComp(contained.Container, out var manager))
return;
manager.Solutions[ent.Comp.Id] = ent;
}
/// <summary>
/// Attempts to resolve a solution associated with an entity.
/// </summary>
/// <param name="container">The entity that holdes the container the solution entity is in.</param>
/// <param name="entity">The entity that holdes the container the solution entity is in.</param>
/// <param name="name">The name of the solution entities container.</param>
/// <param name="entity">A reference to a solution entity to load the associated solution entity into. Will be unchanged if not null.</param>
/// <param name="solutionEnt">A reference to a solution entity to load the associated solution entity into. Will be unchanged if not null.</param>
/// <param name="solution">Returns the solution state of the solution entity.</param>
/// <returns>Whether the solution was successfully resolved.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool ResolveSolution(Entity<SolutionContainerManagerComponent?> container, string? name, [NotNullWhen(true)] ref Entity<SolutionComponent>? entity, [NotNullWhen(true)] out Solution? solution)
public bool ResolveSolution(Entity<SolutionManagerComponent?> entity, string name, [NotNullWhen(true)] ref Entity<SolutionComponent>? solutionEnt, [NotNullWhen(true)] out Solution? solution)
{
if (!ResolveSolution(container, name, ref entity))
if (!ResolveSolution(entity, name, ref solutionEnt))
{
solution = null;
return false;
}
solution = entity.Value.Comp.Solution;
solution = solutionEnt.Value.Comp.Solution;
return true;
}
/// <inheritdoc cref="ResolveSolution"/>
/// <inheritdoc cref="ResolveSolution(Entity{SolutionManagerComponent?}, string, ref Entity{SolutionComponent}?, out Solution?)"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool ResolveSolution(Entity<SolutionContainerManagerComponent?> container, string? name, [NotNullWhen(true)] ref Entity<SolutionComponent>? entity)
public bool ResolveSolution(Entity<SolutionManagerComponent?> container, string name, [NotNullWhen(true)] ref Entity<SolutionComponent>? entity)
{
if (entity is not null)
{
@@ -159,171 +165,217 @@ public abstract partial class SharedSolutionContainerSystem : EntitySystem
/// Attempts to fetch a solution entity associated with an entity.
/// </summary>
/// <remarks>
/// If the solution entity will be frequently accessed please use the equivalent <see cref="ResolveSolution"/> method and cache the result.
/// If the solution entity will be frequently accessed please use the equivalent
/// <see cref="ResolveSolution(Entity{SolutionManagerComponent?}, string, ref Entity{SolutionComponent}?, out Solution?)"/>
/// method and cache the result.
/// </remarks>
/// <param name="container">The entity the solution entity should be associated with.</param>
/// <param name="entity">The entity the solution entity should be associated with.</param>
/// <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="solutionEnt">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,
Entity<SolutionManagerComponent?> entity,
string name,
[NotNullWhen(true)] out Entity<SolutionComponent>? solutionEnt,
[NotNullWhen(true)] out Solution? solution,
bool errorOnMissing = false)
{
if (!TryGetSolution(container, name, out entity, errorOnMissing: errorOnMissing))
if (!TryGetSolution(entity, name, out solutionEnt, errorOnMissing: errorOnMissing))
{
solution = null;
return false;
}
solution = entity.Value.Comp.Solution;
solution = solutionEnt.Value.Comp.Solution;
return true;
}
/// <inheritdoc cref="TryGetSolution"/>
/// <inheritdoc cref="TryGetSolution(Entity{SolutionManagerComponent?},string,out Entity{SolutionComponent}?, out Solution?, bool)"/>
public bool TryGetSolution(
Entity<SolutionContainerManagerComponent?> container,
string? name,
[NotNullWhen(true)] out Entity<SolutionComponent>? entity,
Entity<SolutionManagerComponent?> entity,
string name,
[NotNullWhen(true)] out Entity<SolutionComponent>? solutionEnt,
bool errorOnMissing = false)
{
// use connected container instead of entity from arguments, if it exists.
var ev = new GetConnectedContainerEvent();
RaiseLocalEvent(container, ref ev);
if (ev.ContainerEntity.HasValue)
container = ev.ContainerEntity.Value;
solutionEnt = null;
EntityUid uid;
if (name is null)
uid = container;
else if (
ContainerSystem.TryGetContainer(container, $"solution@{name}", out var solutionContainer) &&
solutionContainer is ContainerSlot solutionSlot &&
solutionSlot.ContainedEntity is { } containedSolution
)
var ev = new GetConnectedContainerEvent();
RaiseLocalEvent(entity, ref ev);
if (ev.ContainerEntity.HasValue)
entity = ev.ContainerEntity.Value;
if (SolutionQuery.TryComp(entity, out var comp) && comp.Id == name)
{
solutionEnt = (entity.Owner, comp);
return true;
}
if (!SolutionManagerQuery.Resolve(entity, ref entity.Comp, errorOnMissing))
return false;
if (entity.Comp.Solutions.TryGetValue(name, out var solution))
{
var attemptEv = new SolutionAccessAttemptEvent(name);
RaiseLocalEvent(container, ref attemptEv);
RaiseLocalEvent(entity, ref attemptEv);
if (attemptEv.Cancelled)
{
entity = null;
return false;
}
uid = containedSolution;
}
else
{
entity = null;
if (!errorOnMissing)
return false;
Log.Error($"{ToPrettyString(container)} does not have a solution with ID: {name}");
return false;
solutionEnt = solution;
return true;
}
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;
}
if (errorOnMissing)
Log.Error($"{ToPrettyString(entity)} does not have a solution with ID: {name}");
entity = (uid, comp);
return true;
return false;
}
/// <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,
/// Used for prototypes.
/// </summary>
public bool TryGetSolution(EntProtoId entProtoId,
string name,
[NotNullWhen(true)] out Solution? solution,
bool errorOnMissing = false)
{
solution = null;
if (container.Solutions != null)
return container.Solutions.TryGetValue(name, out solution);
if (!errorOnMissing)
if (!PrototypeManager.Resolve(entProtoId, out var proto))
return false;
Log.Error($"{container} does not have a solution with ID: {name}");
return TryGetSolution(proto, name, out solution, errorOnMissing);
}
public bool TryGetSolution(EntityPrototype entProto,
string name,
[NotNullWhen(true)] out Solution? solution,
bool errorOnMissing = false)
{
solution = null;
if (!TryGetSolutionFill(entProto, out var solutions))
return false;
foreach (var protoId in solutions)
{
if (!PrototypeManager.Resolve(protoId, out var proto))
continue;
if (!proto.TryGetComponent<SolutionComponent>(out var sol, Factory))
{
Log.Error($"Entity prototype {proto}, tried to spawn in a solution container in prototype {entProto.ID}, but had no {nameof(SolutionComponent)}");
continue;
}
if (sol.Id != name)
continue;
solution = sol.Solution;
return true;
}
if (errorOnMissing)
Log.Error($"{entProto.ID} does not have a solution with ID: {name}");
return false;
}
public IEnumerable<(string? Name, Entity<SolutionComponent> Solution)> EnumerateSolutions(Entity<SolutionContainerManagerComponent?> container, bool includeSelf = true)
public IEnumerable<(string? Name, Entity<SolutionComponent> Solution)> EnumerateSolutions(Entity<SolutionManagerComponent?> entity, bool includeSelf = true)
{
if (includeSelf && TryComp(container, out SolutionComponent? solutionComp))
yield return (null, (container.Owner, solutionComp));
if (includeSelf && SolutionQuery.TryComp(entity, out var solutionComp))
yield return (solutionComp.Id, (entity.Owner, solutionComp));
if (!Resolve(container, ref container.Comp, logMissing: false))
if (!SolutionManagerQuery.Resolve(entity, ref entity.Comp, logMissing: false))
yield break;
foreach (var name in container.Comp.Containers)
foreach (var (id, solution) in entity.Comp.Solutions)
{
var attemptEv = new SolutionAccessAttemptEvent(name);
RaiseLocalEvent(container, ref attemptEv);
var attemptEv = new SolutionAccessAttemptEvent(id);
RaiseLocalEvent(entity, ref attemptEv);
if (attemptEv.Cancelled)
continue;
if (ContainerSystem.GetContainer(container, $"solution@{name}") is ContainerSlot slot && slot.ContainedEntity is { } solutionId)
yield return (name, (solutionId, Comp<SolutionComponent>(solutionId)));
yield return (id, solution);
}
}
public IEnumerable<(string Name, Solution Solution)> EnumerateSolutions(SolutionContainerManagerComponent container)
public IEnumerable<(string Id, Solution Solution)> EnumerateSolutions(EntityPrototype entProto)
{
if (container.Solutions is not { Count: > 0 } solutions)
if (!TryGetSolutionFill(entProto, out var solutions))
yield break;
foreach (var (name, solution) in solutions)
foreach (var protoId in solutions)
{
yield return (name, solution);
if (!PrototypeManager.Resolve(protoId, out var proto))
continue;
if (!proto.TryGetComponent<SolutionComponent>(out var sol, Factory))
{
Log.Error($"Entity prototype {proto}, tried to spawn in a solution container in prototype {entProto.ID}, but had no {nameof(SolutionComponent)}");
continue;
}
yield return (sol.Id, sol.Solution);
}
}
private bool TryGetSolutionFill(Entity<SolutionManagerComponent?> entity, [NotNullWhen(true)] out List<EntProtoId>? fill)
{
fill = null;
if (!SolutionManagerQuery.Resolve(entity, ref entity.Comp))
return false;
protected void UpdateAppearance(Entity<AppearanceComponent?> container, Entity<SolutionComponent, ContainedSolutionComponent> soln)
fill = entity.Comp.SolutionEnts;
return true;
}
private bool TryGetSolutionFill(EntityPrototype entProto, [NotNullWhen(true)] out List<EntProtoId>? fill)
{
fill = null;
if (!entProto.TryGetComponent<SolutionManagerComponent>(out var manager, Factory))
return false;
fill = manager.SolutionEnts;
return true;
}
protected void UpdateAppearance(Entity<AppearanceComponent?> container, Entity<SolutionComponent> soln)
{
var (uid, appearanceComponent) = container;
if (!HasComp<SolutionContainerVisualsComponent>(uid) || !Resolve(uid, ref appearanceComponent, logMissing: false))
return;
var (_, comp, relation) = soln;
var solution = comp.Solution;
var solution = soln.Comp.Solution;
AppearanceSystem.SetData(uid, SolutionContainerVisuals.FillFraction, solution.FillFraction, appearanceComponent);
AppearanceSystem.SetData(uid, SolutionContainerVisuals.Color, solution.GetColor(PrototypeManager), appearanceComponent);
AppearanceSystem.SetData(uid, SolutionContainerVisuals.SolutionName, relation.ContainerName, appearanceComponent);
AppearanceSystem.SetData(uid, SolutionContainerVisuals.SolutionName, soln.Comp.Id, appearanceComponent);
if (solution.GetPrimaryReagentId() is { } reagent)
AppearanceSystem.SetData(uid, SolutionContainerVisuals.BaseOverride, reagent.ToString(), appearanceComponent);
}
public FixedPoint2 GetTotalPrototypeQuantity(EntityUid owner, string reagentId)
public FixedPoint2 GetTotalPrototypeQuantity(Entity<SolutionManagerComponent?> owner, string reagentId)
{
var reagentQuantity = FixedPoint2.New(0);
if (Exists(owner)
&& TryComp(owner, out SolutionContainerManagerComponent? managerComponent))
if (Exists(owner))
{
foreach (var (_, soln) in EnumerateSolutions((owner, managerComponent)))
foreach (var (_, solution) in EnumerateSolutions(owner))
{
var solution = soln.Comp.Solution;
reagentQuantity += solution.GetTotalPrototypeQuantity(reagentId);
reagentQuantity += solution.Comp.Solution.GetTotalPrototypeQuantity(reagentId);
}
}
return reagentQuantity;
}
/// <summary>
/// Dirties a solution entity that has been modified and prompts updates to chemical reactions and overflow state.
/// Should be invoked whenever a solution entity is modified.
@@ -331,46 +383,37 @@ public abstract partial class SharedSolutionContainerSystem : EntitySystem
/// <remarks>
/// 90% of this system is ensuring that this proc is invoked whenever a solution entity is changed. The other 10% <i>is</i> this proc.
/// </remarks>
/// <param name="soln"></param>
/// <param name="solution"></param>
/// <param name="needsReactionsProcessing"></param>
/// <param name="mixerComponent"></param>
public void UpdateChemicals(Entity<SolutionComponent> soln, bool needsReactionsProcessing = true, ReactionMixerComponent? mixerComponent = null)
public void UpdateChemicals(Entity<SolutionComponent> solution, bool needsReactionsProcessing = true, ReactionMixerComponent? mixerComponent = null)
{
Dirty(soln);
var (uid, comp) = soln;
var solution = comp.Solution;
// Process reactions
if (needsReactionsProcessing && solution.CanReact)
ChemicalReactionSystem.FullyReactSolution(soln, mixerComponent);
if (needsReactionsProcessing && solution.Comp.Solution.CanReact)
ChemicalReactionSystem.FullyReactSolution(solution, mixerComponent);
var overflow = solution.Volume - solution.MaxVolume;
var overflow = solution.Comp.Solution.Volume - solution.Comp.Solution.MaxVolume;
if (overflow > FixedPoint2.Zero)
{
var overflowEv = new SolutionOverflowEvent(soln, overflow);
RaiseLocalEvent(uid, ref overflowEv);
var overflowEv = new SolutionOverflowEvent(solution, overflow);
RaiseLocalEvent(solution, ref overflowEv);
}
UpdateAppearance((uid, comp, null));
var owner = GetSolutionOwner(solution);
var changedEv = new SolutionChangedEvent(soln);
RaiseLocalEvent(uid, ref changedEv);
}
var changedEv = new SolutionChangedEvent(solution);
RaiseLocalEvent(owner, ref changedEv);
Dirty(solution);
public void UpdateAppearance(Entity<SolutionComponent, AppearanceComponent?> soln)
{
var (uid, comp, appearanceComponent) = soln;
var solution = comp.Solution;
if (!Exists(uid) || !Resolve(uid, ref appearanceComponent, false))
if (Timing.ApplyingState)
return;
AppearanceSystem.SetData(uid, SolutionContainerVisuals.FillFraction, solution.FillFraction, appearanceComponent);
AppearanceSystem.SetData(uid, SolutionContainerVisuals.Color, solution.GetColor(PrototypeManager), appearanceComponent);
UpdateAppearance(owner, solution);
}
if (solution.GetPrimaryReagentId() is { } reagent)
AppearanceSystem.SetData(uid, SolutionContainerVisuals.BaseOverride, reagent.ToString(), appearanceComponent);
public EntityUid GetSolutionOwner(Entity<SolutionComponent> entity)
{
return ContainedQuery.CompOrNull(entity)?.Container ?? entity.Owner;
}
/// <summary>
@@ -830,27 +873,16 @@ public abstract partial class SharedSolutionContainerSystem : EntitySystem
entity.Comp.Solution.ValidateSolution();
}
private void OnSolutionStartup(Entity<SolutionComponent> entity, ref ComponentStartup args)
private void OnSolutionInit(Entity<SolutionComponent> entity, ref MapInitEvent args)
{
UpdateChemicals(entity);
}
private void OnSolutionShutdown(Entity<SolutionComponent> entity, ref ComponentShutdown args)
{
RemoveAllSolution(entity);
}
private void OnContainerManagerInit(Entity<SolutionContainerManagerComponent> entity, ref ComponentInit args)
{
if (entity.Comp.Containers is not { Count: > 0 } containers)
return;
var containerManager = EnsureComp<ContainerManagerComponent>(entity);
foreach (var name in containers)
{
// The actual solution entity should be directly held within the corresponding slot.
ContainerSystem.EnsureContainer<ContainerSlot>(entity.Owner, $"solution@{name}", containerManager);
}
// If we are contained within another entity, update that entity. Otherwise, don't update if we're being deleted.
if (ContainedQuery.HasComp(entity) || !Terminating(entity))
RemoveAllSolution(entity);
}
/// <summary>
@@ -1049,211 +1081,132 @@ public abstract partial class SharedSolutionContainerSystem : EntitySystem
return true;
}
private void OnMapInit(Entity<SolutionContainerManagerComponent> entity, ref MapInitEvent args)
/// <remarks>
/// We want all our solutions spawned before MapInit.
/// They should only ever be attached to this entity so spawning them before MapInit should be fine.
/// </remarks>
private void OnManagerInit(Entity<SolutionManagerComponent> entity, ref MapInitEvent args)
{
EnsureAllSolutions(entity);
var container = ContainerSystem.EnsureContainer<Container>(entity.Owner, entity.Comp.Container);
foreach (var solution in entity.Comp.SolutionEnts)
{
CreateSolution(solution, container);
}
}
private void OnContainerManagerShutdown(Entity<SolutionContainerManagerComponent> entity, ref ComponentShutdown args)
private void OnManagerShutdown(Entity<SolutionManagerComponent> 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))
if (ContainerSystem.TryGetContainer(entity, entity.Comp.Container, out var solutionContainer))
ContainerSystem.ShutdownContainer(solutionContainer);
}
private void OnSolutionAdded(Entity<SolutionManagerComponent> entity, ref EntInsertedIntoContainerMessage args)
{
// Container networking boilerplate
if (args.Container.ID != entity.Comp.Container || !SolutionQuery.TryComp(args.Entity, out var solution))
return;
// Don't add a solution entity with the same id as this entity's solution if it exists!
DebugTools.Assert(!TryComp<SolutionComponent>(entity, out var sol) || sol.Id != solution.Id, $"Tried to add a solution {MetaData(args.Entity).EntityPrototype} {solution.Id} to {ToPrettyString(entity)} but it itself was a solution with a matching id!");
EnsureComp<ContainedSolutionComponent>(args.Entity, out var contained);
contained.Container = entity.Owner;
// Throw if we already have a solution with the same ID. Only throw on server to avoid prediction causing issues.
if (!entity.Comp.Solutions.TryAdd(solution.Id, (args.Entity, solution)) && Net.IsServer)
Log.Error($"Solution {ToPrettyString(entity)}, tried to add a solution with a duplicate id: {solution.Id}");
}
private void OnSolutionRemoved(Entity<SolutionManagerComponent> entity, ref EntRemovedFromContainerMessage args)
{
// Container networking jank
if (args.Container.ID != entity.Comp.Container || !SolutionQuery.TryComp(args.Entity, out var solution))
return;
RemComp<ContainedSolutionComponent>(args.Entity);
entity.Comp.Solutions.Remove(solution.Id);
}
#endregion Event Handlers
/// <summary>
/// A method which ensures a solution with a given ID exists.
/// </summary>
/// <param name="entity">Entity we're trying to attach a new solution to.</param>
/// <param name="name">Name of the new solution.</param>
/// <param name="solutionEntity">Solution entity found or created.</param>
/// <returns>Returns true if the solution already existed, and false if it had to create a new solution.</returns>
/// <remarks>
/// Only run this after the entity is already initialized.
/// If you're running this when your entity is created, it is recommended to run on <see cref="MapInitEvent"/>
/// Deviance from these instructions may prevent your game from building. YOU HAVE BEEN WARNED.
/// </remarks>
public bool EnsureSolution(
Entity<MetaDataComponent?> entity,
Entity<SolutionManagerComponent?> entity,
string name,
[NotNullWhen(true)] out Solution? solution,
FixedPoint2 maxVol = default)
out Entity<SolutionComponent> solutionEntity)
{
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)
if (SolutionQuery.TryComp(entity, out var comp) && comp.Id == name)
{
EnsureSolutionEntity((uid, manager), name, out existed,
out var solEnt, maxVol, prototype);
solution = solEnt!.Value.Comp.Solution;
solutionEntity = (entity.Owner, comp);
return true;
}
solution = EnsureSolutionPrototype((uid, manager), name, maxVol, prototype, out existed);
return true;
}
public void EnsureAllSolutions(Entity<SolutionContainerManagerComponent> entity)
{
if (NetManager.IsClient)
return;
// Ensure we have a SolutionManagerComponent
// EnsureComp should ensure a container and fill that container with default spawns!
if (entity.Comp == null)
EnsureComp<SolutionManagerComponent>(entity, out entity.Comp);
if (entity.Comp.Solutions is not { } prototypes)
return;
foreach (var (name, prototype) in prototypes)
// Check the cache first, even if the component didn't exist before, creating one may have spawned and cached solutions!
if (entity.Comp.Solutions.TryGetValue(name, out var solution))
{
EnsureSolutionEntity((entity.Owner, entity.Comp), name, out _, out _, prototype.MaxVolume, prototype);
solutionEntity = solution;
return true;
}
entity.Comp.Solutions = null;
Dirty(entity);
// Create a default entity if one doesn't already exist!
solutionEntity = CreateDefaultSolution((entity, entity.Comp), name);
return false;
}
public bool EnsureSolutionEntity(
Entity<SolutionContainerManagerComponent?> entity,
/// <remarks>This is private since you should really be specifying a solution prototype to create.</remarks>
private Entity<SolutionComponent> CreateDefaultSolution(
Entity<SolutionManagerComponent> entity,
string name)
{
var container = ContainerSystem.EnsureContainer<Container>(entity.Owner, entity.Comp.Container);
return CreateDefaultSolution(name, container);
}
private Entity<SolutionComponent> CreateDefaultSolution(
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
)
Container container)
{
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);
var solution = SpawnSolutionUninitialized(DefaultSolution);
solution.Comp.Id = name;
ContainerSystem.Insert(solution.Owner, container, force: true);
EntityManager.InitializeAndStartEntity(solution);
return solution;
}
private Entity<SolutionComponent, ContainedSolutionComponent> SpawnSolutionUninitialized(ContainerSlot container, string name, FixedPoint2 maxVol, Solution prototype)
public Entity<SolutionComponent> CreateSolution(
EntProtoId proto,
Container container)
{
var coords = new EntityCoordinates(container.Owner, Vector2.Zero);
var uid = EntityManager.CreateEntityUninitialized(null, coords, null);
// TODO: Replace this with an engine bound method when e#6192 is merged.
var solution = SpawnSolutionUninitialized(proto);
ContainerSystem.Insert(solution.Owner, container, force: true);
EntityManager.InitializeAndStartEntity(solution);
return solution;
}
var solution = new SolutionComponent() { Solution = prototype };
AddComp(uid, solution);
private Entity<SolutionComponent> SpawnSolutionUninitialized(EntProtoId solution)
{
var uid = EntityManager.CreateEntityUninitialized(solution);
var relation = new ContainedSolutionComponent() { Container = container.Owner, ContainerName = name };
AddComp(uid, relation);
MetaDataSys.SetEntityName(uid, $"solution - {name}", raiseEvents: false);
ContainerSystem.Insert(uid, container, force: true);
return (uid, solution, relation);
// If you pass in a ProtoId without a SolutionComponent that's your own damn fault!
var comp = SolutionQuery.Comp(uid);
return (uid, comp);
}
public void AdjustDissolvedReagent(
@@ -27,7 +27,8 @@ public sealed class SolutionPurgeSystem : EntitySystem
{
base.Update(frameTime);
var query = EntityQueryEnumerator<SolutionPurgeComponent, SolutionContainerManagerComponent>();
// TODO: SolutionPurgeComponent on Solution Entities!
var query = EntityQueryEnumerator<SolutionPurgeComponent, SolutionManagerComponent>();
while (query.MoveNext(out var uid, out var purge, out var manager))
{
if (_timing.CurTime < purge.NextPurgeTime)
@@ -38,7 +38,8 @@ public sealed class SolutionRegenerationSystem : EntitySystem
{
base.Update(frameTime);
var query = EntityQueryEnumerator<SolutionRegenerationComponent, SolutionContainerManagerComponent>();
// TODO: SolutionRegenerationComponent on Solution Entities!
var query = EntityQueryEnumerator<SolutionRegenerationComponent, SolutionManagerComponent>();
while (query.MoveNext(out var uid, out var regen, out var manager))
{
if (_timing.CurTime < regen.NextRegenTime)
@@ -26,7 +26,7 @@ public sealed class SolutionSpikerSystem : EntitySystem
private void OnInteractUsing(Entity<RefillableSolutionComponent> entity, ref InteractUsingEvent args)
{
if (TrySpike(args.Used, args.Target, args.User, entity.Comp))
if (TrySpike(args.Used, (args.Target, entity.Comp), args.User))
args.Handled = true;
}
@@ -37,31 +37,29 @@ public sealed class SolutionSpikerSystem : EntitySystem
/// <param name="source">Source of the solution.</param>
/// <param name="target">Target to spike with the solution from source.</param>
/// <param name="user">User spiking the target solution.</param>
private bool TrySpike(EntityUid source, EntityUid target, EntityUid user, RefillableSolutionComponent? spikableTarget = null,
SolutionSpikerComponent? spikableSource = null,
SolutionContainerManagerComponent? managerSource = null,
SolutionContainerManagerComponent? managerTarget = null)
private bool TrySpike(Entity<SolutionSpikerComponent?> source,
Entity<RefillableSolutionComponent?> target,
EntityUid user)
{
if (!Resolve(source, ref spikableSource, ref managerSource, false)
|| !Resolve(target, ref spikableTarget, ref managerTarget, false)
|| !_solution.TryGetRefillableSolution((target, spikableTarget, managerTarget), out var targetSoln, out var targetSolution)
|| !_solution.TryGetSolution((source, managerSource), spikableSource.SourceSolution, out _, out var sourceSolution))
if (!Resolve(source, ref source.Comp, false)
|| !_solution.TryGetRefillableSolution((target, target.Comp), out var targetSoln, out var targetSolution)
|| !_solution.TryGetSolution(source.Owner, source.Comp.SourceSolution, out _, out var sourceSolution))
{
return false;
}
if (targetSolution.Volume == 0 && !spikableSource.IgnoreEmpty)
if (targetSolution.Volume == 0 && !source.Comp.IgnoreEmpty)
{
_popup.PopupClient(Loc.GetString(spikableSource.PopupEmpty, ("spiked-entity", target), ("spike-entity", source)), user, user);
_popup.PopupClient(Loc.GetString(source.Comp.PopupEmpty, ("spiked-entity", target), ("spike-entity", source)), user, user);
return false;
}
if (!_solution.ForceAddSolution(targetSoln.Value, sourceSolution))
return false;
_popup.PopupClient(Loc.GetString(spikableSource.Popup, ("spiked-entity", target), ("spike-entity", source)), user, user);
_popup.PopupClient(Loc.GetString(source.Comp.Popup, ("spiked-entity", target), ("spike-entity", source)), user, user);
sourceSolution.RemoveAllSolution();
if (spikableSource.Delete)
if (source.Comp.Delete)
QueueDel(source);
return true;
@@ -29,7 +29,8 @@ public sealed class SolutionTransferSystem : EntitySystem
/// <summary>
/// Default transfer amounts for the set-transfer verb.
/// </summary>
public static readonly FixedPoint2[] DefaultTransferAmounts = new FixedPoint2[] { 1, 5, 10, 25, 50, 100, 250, 500, 1000 };
/// TODO: Turn this into a prototype just like with injectors.
public static readonly FixedPoint2[] DefaultTransferAmounts = new FixedPoint2[] { 1, 5, 10, 15, 30, 60, 120, 240, 480, 960 };
public override void Initialize()
{
@@ -11,11 +11,11 @@ namespace Content.Shared.EntityEffects.Effects.Solution;
/// Quantity is modified by scale.
/// </summary>
/// <inheritdoc cref="EntityEffectSystem{T,TEffect}"/>
public sealed class AddReagentToSolutionEntityEffectSystem : EntityEffectSystem<SolutionContainerManagerComponent, AddReagentToSolution>
public sealed class AddReagentToSolutionEntityEffectSystem : EntityEffectSystem<SolutionManagerComponent, AddReagentToSolution>
{
[Dependency] private readonly SharedSolutionContainerSystem _solutionContainer = default!;
protected override void Effect(Entity<SolutionContainerManagerComponent> entity, ref EntityEffectEvent<AddReagentToSolution> args)
protected override void Effect(Entity<SolutionManagerComponent> entity, ref EntityEffectEvent<AddReagentToSolution> args)
{
var solution = args.Effect.Solution;
var reagent = args.Effect.Reagent;
@@ -40,7 +40,7 @@ public sealed partial class AddReagentToSolution : EntityEffectBase<AddReagentTo
/// Solution we're looking for
/// </summary>
[DataField(required: true)]
public string? Solution = "reagents";
public string Solution = "reagents";
///<summary>
/// A modifier for how much reagent we're creating.
@@ -1,5 +1,4 @@
using Content.Shared.Audio;
using Content.Shared.Chemistry.Components.SolutionManager;
using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Database;
using Content.Shared.DoAfter;
@@ -23,6 +22,7 @@ namespace Content.Shared.Fluids.EntitySystems;
/// <summary>
/// Handles the draining of solutions from containers into drains.
/// TODO: This system is very bad, and needs to be rewritten.
/// </summary>
public sealed class DrainSystem : EntitySystem
{
@@ -130,9 +130,9 @@ public sealed class DrainSystem : EntitySystem
{
base.Update(frameTime);
var query = EntityQueryEnumerator<DrainComponent, SolutionContainerManagerComponent>();
var query = EntityQueryEnumerator<DrainComponent>();
var curTime = _timing.CurTime;
while (query.MoveNext(out var uid, out var drain, out var manager))
while (query.MoveNext(out var uid, out var drain))
{
if (curTime < drain.NextUpdate)
continue;
@@ -141,7 +141,7 @@ public sealed class DrainSystem : EntitySystem
Dirty(uid, drain);
// Best to do this one every second rather than once every tick...
if (!_solutionContainerSystem.ResolveSolution((uid, manager), DrainComponent.SolutionName, ref drain.Solution, out var drainSolution))
if (!_solutionContainerSystem.ResolveSolution(uid, DrainComponent.SolutionName, ref drain.Solution, out var drainSolution))
continue;
if (drainSolution.Volume <= 0 && !drain.AutoDrain)
@@ -201,12 +201,9 @@ public sealed class DrainSystem : EntitySystem
private void OnExamined(Entity<DrainComponent> ent, ref ExaminedEvent args)
{
if (!args.IsInDetailsRange ||
!HasComp<SolutionContainerManagerComponent>(ent) ||
!_solutionContainerSystem.ResolveSolution(ent.Owner, DrainComponent.SolutionName, ref ent.Comp.Solution, out var drainSolution))
{
if (!args.IsInDetailsRange
|| !_solutionContainerSystem.ResolveSolution(ent.Owner, DrainComponent.SolutionName, ref ent.Comp.Solution, out var drainSolution))
return;
}
var text = drainSolution.AvailableVolume != 0
? Loc.GetString("drain-component-examine-volume", ("volume", drainSolution.AvailableVolume))
@@ -38,7 +38,7 @@ public abstract class SharedAbsorbentSystem : EntitySystem
SubscribeLocalEvent<AbsorbentComponent, AfterInteractEvent>(OnAfterInteract);
SubscribeLocalEvent<AbsorbentComponent, UserActivateInWorldEvent>(OnActivateInWorld);
SubscribeLocalEvent<AbsorbentComponent, SolutionContainerChangedEvent>(OnAbsorbentSolutionChange);
SubscribeLocalEvent<AbsorbentComponent, SolutionChangedEvent>(OnAbsorbentSolutionChange);
}
private void OnActivateInWorld(Entity<AbsorbentComponent> ent, ref UserActivateInWorldEvent args)
@@ -59,7 +59,7 @@ public abstract class SharedAbsorbentSystem : EntitySystem
args.Handled = true;
}
private void OnAbsorbentSolutionChange(Entity<AbsorbentComponent> ent, ref SolutionContainerChangedEvent args)
private void OnAbsorbentSolutionChange(Entity<AbsorbentComponent> ent, ref SolutionChangedEvent args)
{
// The changes are already networked as part of the same game state.
if (_timing.ApplyingState)
+7 -7
View File
@@ -66,7 +66,7 @@ public abstract partial class SharedPuddleSystem : EntitySystem
base.Initialize();
// Shouldn't need re-anchoring.
SubscribeLocalEvent<PuddleComponent, AnchorStateChangedEvent>(OnAnchorChanged);
SubscribeLocalEvent<PuddleComponent, SolutionContainerChangedEvent>(OnSolutionUpdate);
SubscribeLocalEvent<PuddleComponent, SolutionChangedEvent>(OnSolutionUpdate);
SubscribeLocalEvent<PuddleComponent, GetFootstepSoundEvent>(OnGetFootstepSound);
SubscribeLocalEvent<PuddleComponent, ExaminedEvent>(HandlePuddleExamined);
SubscribeLocalEvent<PuddleComponent, EntRemovedFromContainerMessage>(OnEntRemoved);
@@ -110,25 +110,25 @@ public abstract partial class SharedPuddleSystem : EntitySystem
_standoutReagents = [.. _prototypeManager.EnumeratePrototypes<ReagentPrototype>().Where(x => x.Standsout).Select(x => x.ID)];
}
private void OnSolutionUpdate(Entity<PuddleComponent> entity, ref SolutionContainerChangedEvent args)
private void OnSolutionUpdate(Entity<PuddleComponent> entity, ref SolutionChangedEvent args)
{
// The changes are already networked as part of the same game state.
if (_timing.ApplyingState)
return;
if (args.SolutionId != entity.Comp.SolutionName)
if (args.Solution.Comp.Id != entity.Comp.SolutionName)
return;
if (args.Solution.Volume <= 0)
if (args.Solution.Comp.Solution.Volume <= 0)
{
_deletionQueue.Add(entity);
return;
}
_deletionQueue.Remove(entity);
UpdateSlip((entity, entity.Comp), args.Solution);
UpdateSlow(entity, args.Solution);
UpdateEvaporation(entity, args.Solution);
UpdateSlip((entity, entity.Comp), args.Solution.Comp.Solution);
UpdateSlow(entity, args.Solution.Comp.Solution);
UpdateEvaporation(entity, args.Solution.Comp.Solution);
UpdateAppearance((entity, entity.Comp));
}
@@ -41,7 +41,7 @@ public abstract class SharedReagentGrinderSystem : EntitySystem
{
base.Initialize();
SubscribeLocalEvent<InsideReagentGrinderComponent, SolutionContainerChangedEvent>(OnBeakerSolutionContainerChanged);
SubscribeLocalEvent<InsideReagentGrinderComponent, SolutionChangedEvent>(OnBeakerSolutionContainerChanged);
SubscribeLocalEvent<ReagentGrinderComponent, ComponentStartup>(OnGrinderStartup);
SubscribeLocalEvent<ReagentGrinderComponent, ContainerIsRemovingAttemptEvent>(OnEntRemovingAttempt);
@@ -56,7 +56,7 @@ public abstract class SharedReagentGrinderSystem : EntitySystem
SubscribeLocalEvent<ReagentGrinderComponent, ReagentGrinderEjectChamberContentMessage>(OnEjectChamberContentMessage);
}
private void OnBeakerSolutionContainerChanged(Entity<InsideReagentGrinderComponent> ent, ref SolutionContainerChangedEvent args)
private void OnBeakerSolutionContainerChanged(Entity<InsideReagentGrinderComponent> ent, ref SolutionChangedEvent args)
{
// Update the UI if the reagents inside the beaker are changed.
// This is needed in case the component state for the container is applied before that of the solution container
@@ -356,10 +356,11 @@ public abstract class SharedReagentGrinderSystem : EntitySystem
switch (program)
{
case GrinderProgram.Grind:
if (_solutionContainersSystem.TryGetSolution(ent.Owner, ent.Comp.GrindableSolutionName, out _, out var solution))
{
if (ent.Comp.GrindableSolutionName is not { } solutionId)
return null;
if (_solutionContainersSystem.TryGetSolution(ent.Owner, solutionId, out _, out var solution))
return solution;
}
break;
case GrinderProgram.Juice:
return ent.Comp.JuiceSolution;
@@ -5,7 +5,6 @@ using Content.Shared.Body.Components;
using Content.Shared.Body.Systems;
using Content.Shared.Chemistry;
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.Components.SolutionManager;
using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.Climbing.Systems;
@@ -57,8 +56,6 @@ public abstract partial class SharedCryoPodSystem : EntitySystem
[Dependency] private readonly EntityQuery<BloodstreamComponent> _bloodstreamQuery = default!;
[Dependency] private readonly EntityQuery<ItemSlotsComponent> _itemSlotsQuery = default!;
[Dependency] private readonly EntityQuery<FitsInDispenserComponent> _dispenserQuery = default!;
[Dependency] private readonly EntityQuery<SolutionContainerManagerComponent> _solutionContainerQuery = default!;
public override void Initialize()
{
@@ -111,9 +108,8 @@ public abstract partial class SharedCryoPodSystem : EntitySystem
var patient = entity.Comp.BodyContainer.ContainedEntity;
if (patient == null
|| !_solutionContainerQuery.TryComp(entity, out var podSolutionManager)
|| !_solutionContainer.TryGetSolution(
(entity.Owner, podSolutionManager),
entity.Owner,
CryoPodComponent.InjectionBufferSolutionName,
out var injectingSolution,
out _)
@@ -352,14 +348,12 @@ public abstract partial class SharedCryoPodSystem : EntitySystem
if (beaker == null
|| !beaker.Value.Valid
|| !_dispenserQuery.TryComp(beaker, out var fitsInDispenserComponent)
|| !_solutionContainerQuery.TryComp(beaker, out var beakerSolutionManager)
|| !_solutionContainerQuery.TryComp(cryoPod, out var podSolutionManager)
|| !_solutionContainer.TryGetFitsInDispenser(
(beaker.Value, fitsInDispenserComponent, beakerSolutionManager),
(beaker.Value, fitsInDispenserComponent),
out var beakerSolution,
out _)
|| !_solutionContainer.TryGetSolution(
(cryoPod.Owner, podSolutionManager),
cryoPod.Owner,
CryoPodComponent.InjectionBufferSolutionName,
out var injectionSolutionComp,
out var injectionSolution))
@@ -377,9 +371,8 @@ public abstract partial class SharedCryoPodSystem : EntitySystem
public void ClearInjectionBuffer(Entity<CryoPodComponent> cryoPod)
{
if (_solutionContainerQuery.TryComp(cryoPod, out var podSolutionManager)
&& _solutionContainer.TryGetSolution(
(cryoPod.Owner, podSolutionManager),
if (_solutionContainer.TryGetSolution(
cryoPod.Owner,
CryoPodComponent.InjectionBufferSolutionName,
out var injectingSolution,
out _))
@@ -402,9 +395,8 @@ public abstract partial class SharedCryoPodSystem : EntitySystem
if (beaker == null
|| !beaker.Value.Valid
|| !_dispenserQuery.TryComp(beaker, out var fitsInDispenserComponent)
|| !_solutionContainerQuery.TryComp(beaker, out var solutionContainerManagerComponent)
|| !_solutionContainer.TryGetFitsInDispenser(
(beaker.Value, fitsInDispenserComponent, solutionContainerManagerComponent),
(beaker.Value, fitsInDispenserComponent),
out var containerSolution,
out _))
return (null, null);
@@ -419,9 +411,8 @@ public abstract partial class SharedCryoPodSystem : EntitySystem
protected List<ReagentQuantity>? GetInjectingReagents(Entity<CryoPodComponent> entity)
{
if (!_solutionContainerQuery.TryComp(entity, out var solutionManager)
|| !_solutionContainer.TryGetSolution(
(entity.Owner, solutionManager),
if (!_solutionContainer.TryGetSolution(
entity.Owner,
CryoPodComponent.InjectionBufferSolutionName,
out var injectingSolution,
out _))
+16 -19
View File
@@ -32,7 +32,7 @@ public sealed class MetabolizerSystem : EntitySystem
[Dependency] private readonly SharedSolutionContainerSystem _solutionContainerSystem = default!;
[Dependency] private readonly EntityQuery<OrganComponent> _organQuery = default!;
[Dependency] private readonly EntityQuery<SolutionContainerManagerComponent> _solutionQuery = default!;
[Dependency] private readonly EntityQuery<SolutionManagerComponent> _solutionQuery = default!;
public override void Initialize()
{
@@ -87,7 +87,7 @@ public sealed class MetabolizerSystem : EntitySystem
}
private bool LookupSolution(
Entity<MetabolizerComponent, OrganComponent?, SolutionContainerManagerComponent?> ent,
Entity<MetabolizerComponent, OrganComponent?, SolutionManagerComponent?> ent,
MetabolismSolutionEntry solutionData,
bool lookupTransfer,
[NotNullWhen(true)] out Solution? solution,
@@ -106,28 +106,24 @@ public sealed class MetabolizerSystem : EntitySystem
if (lookupTransfer ? solutionData.TransferSolutionOnBody : solutionData.SolutionOnBody)
{
if (ent.Comp2?.Body is { } body)
{
if (!_solutionQuery.TryComp(body, out var bodySolution))
return false;
solutionOwner = body;
return _solutionContainerSystem.TryGetSolution((body, bodySolution), solutionName, out solutionEntity, out solution);
}
}
else
{
if (!_solutionQuery.Resolve(ent, ref ent.Comp3, logMissing: false))
if (ent.Comp2?.Body is not { } body)
return false;
solutionOwner = ent;
return _solutionContainerSystem.TryGetSolution((ent, ent), solutionName, out solutionEntity, out solution);
if (!_solutionContainerSystem.TryGetSolution(body, solutionName, out solutionEntity, out solution))
return false;
solutionOwner = body;
return true;
}
return false;
if (!_solutionContainerSystem.TryGetSolution((ent, ent.Comp3), solutionName, out solutionEntity, out solution))
return false;
solutionOwner = ent;
return true;
}
private void TryMetabolizeStage(Entity<MetabolizerComponent, OrganComponent?, SolutionContainerManagerComponent?> ent, ProtoId<MetabolismStagePrototype> stage)
private void TryMetabolizeStage(Entity<MetabolizerComponent, OrganComponent?, SolutionManagerComponent?> ent, ProtoId<MetabolismStagePrototype> stage)
{
if (!ent.Comp1.Solutions.TryGetValue(stage, out var solutionData))
return;
@@ -261,9 +257,10 @@ public sealed class MetabolizerSystem : EntitySystem
}
}
private void TryMetabolize(Entity<MetabolizerComponent, OrganComponent?, SolutionContainerManagerComponent?> ent)
private void TryMetabolize(Entity<MetabolizerComponent, OrganComponent?, SolutionManagerComponent?> ent)
{
_organQuery.Resolve(ent, ref ent.Comp2, logMissing: false);
_solutionQuery.Resolve(ent, ref ent.Comp3, logMissing: false);
foreach (var stage in ent.Comp1.Stages)
{
@@ -298,7 +298,7 @@ public sealed partial class IngestionSystem
/// <param name="user">The entity trying to make the ingestion happening, not necessarily the one eating</param>
/// <param name="solution">Solution we're returning</param>
/// <param name="time">The time it takes us to eat this entity</param>
public bool CanAccessSolution(Entity<SolutionContainerManagerComponent?> ingested,
public bool CanAccessSolution(EntityUid ingested,
EntityUid user,
[NotNullWhen(true)] out Entity<SolutionComponent>? solution,
out TimeSpan? time)
@@ -306,19 +306,20 @@ public sealed partial class IngestionSystem
solution = null;
time = null;
if (!Resolve(ingested, ref ingested.Comp))
{
_popup.PopupClient(Loc.GetString("ingestion-try-use-is-empty", ("entity", ingested)), ingested, user);
return false;
}
// TODO: Relay this event to solutions using solution relay
var ev = new EdibleEvent(user);
RaiseLocalEvent(ingested, ref ev);
solution = ev.Solution;
time = ev.Time;
return !ev.Cancelled && solution != null;
if (solution == null)
{
_popup.PopupClient(Loc.GetString("ingestion-try-use-is-empty", ("entity", ingested)), ingested, user);
return false;
}
return !ev.Cancelled;
}
/// <summary>
@@ -23,6 +23,7 @@ using Content.Shared.UserInterface;
using Content.Shared.Verbs;
using Content.Shared.Whitelist;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Network;
using Robust.Shared.Prototypes;
namespace Content.Shared.Nutrition.EntitySystems;
@@ -66,7 +67,7 @@ public sealed partial class IngestionSystem : EntitySystem
{
base.Initialize();
SubscribeLocalEvent<EdibleComponent, ComponentInit>(OnEdibleInit);
SubscribeLocalEvent<EdibleComponent, MapInitEvent>(OnEdibleInit);
// Interactions
SubscribeLocalEvent<EdibleComponent, UseInHandEvent>(OnUseEdibleInHand, after: [typeof(OpenableSystem), typeof(InventorySystem), typeof(ActivatableUISystem)]);
@@ -83,7 +84,7 @@ public sealed partial class IngestionSystem : EntitySystem
// Verbs
SubscribeLocalEvent<EdibleComponent, GetVerbsEvent<AlternativeVerb>>(AddEdibleVerbs);
SubscribeLocalEvent<EdibleComponent, SolutionContainerChangedEvent>(OnSolutionContainerChanged);
SubscribeLocalEvent<EdibleComponent, SolutionChangedEvent>(OnSolutionContainerChanged);
// Misc
SubscribeLocalEvent<EdibleComponent, AttemptShakeEvent>(OnAttemptShake);
@@ -135,7 +136,7 @@ public sealed partial class IngestionSystem : EntitySystem
return ingestionEv.Handled;
}
private void OnEdibleInit(Entity<EdibleComponent> entity, ref ComponentInit args)
private void OnEdibleInit(Entity<EdibleComponent> entity, ref MapInitEvent args)
{
// Beakers, Soap and other items have drainable, and we should be able to eat that solution.
// This ensures that tests fail when you configured the yaml from and EdibleComponent uses the wrong solution,
@@ -161,7 +162,7 @@ public sealed partial class IngestionSystem : EntitySystem
_appearance.SetData(entity, FoodVisuals.Visual, drainAvailable.Float(), entity.Comp2);
}
private void OnSolutionContainerChanged(Entity<EdibleComponent> entity, ref SolutionContainerChangedEvent args)
private void OnSolutionContainerChanged(Entity<EdibleComponent> entity, ref SolutionChangedEvent args)
{
// The changes are already networked as part of the same game state.
if (_timing.ApplyingState)
@@ -381,7 +382,7 @@ public sealed partial class IngestionSystem : EntitySystem
var afterEv = new IngestedEvent(args.User, entity, split, forceFed, beforeEv.Transfer >= beforeEv.Max);
RaiseLocalEvent(food, ref afterEv);
_stomach.TryTransferSolution(stomachToUse.Value.Owner, split, stomachToUse);
_stomach.TryTransferSolution((stomachToUse.Value, stomachToUse.Value.Comp), split);
if (!afterEv.Destroy)
{
@@ -30,11 +30,10 @@ public sealed partial class PressurizedSolutionSystem : EntitySystem
{
base.Initialize();
SubscribeLocalEvent<PressurizedSolutionComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<PressurizedSolutionComponent, ShakeEvent>(OnShake);
SubscribeLocalEvent<PressurizedSolutionComponent, OpenableOpenedEvent>(OnOpened);
SubscribeLocalEvent<PressurizedSolutionComponent, LandEvent>(OnLand);
SubscribeLocalEvent<PressurizedSolutionComponent, SolutionContainerChangedEvent>(OnSolutionUpdate);
SubscribeLocalEvent<PressurizedSolutionComponent, SolutionChangedEvent>(OnSolutionUpdate);
}
/// <summary>
@@ -242,10 +241,6 @@ public sealed partial class PressurizedSolutionSystem : EntitySystem
#endregion
#region Event Handlers
private void OnMapInit(Entity<PressurizedSolutionComponent> entity, ref MapInitEvent args)
{
RollSprayThreshold(entity);
}
private void OnOpened(Entity<PressurizedSolutionComponent> entity, ref OpenableOpenedEvent args)
{
@@ -265,13 +260,13 @@ public sealed partial class PressurizedSolutionSystem : EntitySystem
SprayOrAddFizziness(entity, entity.Comp.SprayChanceModOnLand, entity.Comp.FizzinessAddedOnLand);
}
private void OnSolutionUpdate(Entity<PressurizedSolutionComponent> entity, ref SolutionContainerChangedEvent args)
private void OnSolutionUpdate(Entity<PressurizedSolutionComponent> entity, ref SolutionChangedEvent args)
{
// The changes are already networked as part of the same game state.
if (_timing.ApplyingState)
return;
if (args.SolutionId != entity.Comp.Solution)
if (args.Solution.Comp.Id != entity.Comp.Solution)
return;
// If the solution is no longer capable of being fizzy, clear any built up fizziness
@@ -136,9 +136,6 @@ public sealed partial class FoodHasReagent : FoodMetamorphRule
public override bool Check(IPrototypeManager protoMan, EntityManager entMan, EntityUid food, List<FoodSequenceVisualLayer> ingredients)
{
if (!entMan.TryGetComponent<SolutionContainerManagerComponent>(food, out var solMan))
return false;
var solutionMan = entMan.System<SharedSolutionContainerSystem>();
if (!solutionMan.TryGetSolution(food, Solution, out var foodSoln, out var foodSolution))
@@ -41,7 +41,7 @@ public sealed partial class WelderComponent : Component
/// Name of the fuel solution.
/// </summary>
[DataField]
public string FuelSolutionName = "Welder";
public string FuelSolutionName = "welder";
/// <summary>
/// Reagent that will be used as fuel for welding.
@@ -57,9 +57,9 @@ public abstract partial class SharedToolSystem
Dirty(entity, entity.Comp);
}
public (FixedPoint2 fuel, FixedPoint2 capacity) GetWelderFuelAndCapacity(EntityUid uid, WelderComponent? welder = null, SolutionContainerManagerComponent? solutionContainer = null)
public (FixedPoint2 fuel, FixedPoint2 capacity) GetWelderFuelAndCapacity(EntityUid uid, WelderComponent? welder = null, SolutionManagerComponent? solutionContainer = null)
{
if (!Resolve(uid, ref welder, ref solutionContainer))
if (!Resolve(uid, ref welder))
return default;
if (!SolutionContainerSystem.TryGetSolution(
@@ -209,7 +209,8 @@ public abstract partial class SharedToolSystem
private void UpdateWelders()
{
var query = EntityQueryEnumerator<WelderComponent, SolutionContainerManagerComponent>();
// TODO: Same as the other EntityQueryEnumerators...
var query = EntityQueryEnumerator<WelderComponent, SolutionManagerComponent>();
var curTime = _timing.CurTime;
while (query.MoveNext(out var uid, out var welder, out var solutionContainer))
{
+2 -2
View File
@@ -10,8 +10,8 @@ reagent-desc-buzzochloric-bees = Liquid bees. Oh god it's LIQUID BEES NO-
reagent-name-ground-bee = ground Bee
reagent-desc-ground-bee = Bee grounds. Gross.
reagent-name-saxoite = saxoite
reagent-desc-saxoite = Smells like jazz.
reagent-name-brass = brass
reagent-desc-brass = Smells like clockwork.
reagent-name-licoxide = licoxide
reagent-desc-licoxide = A synthetic battery acid. It looks... electrifying.
+11 -3
View File
@@ -9,10 +9,18 @@
- Ruminant
- Wheat
- BananaPeel
- type: SolutionContainerManager
- type: SolutionManager
solutions:
stomach:
maxVol: 80
- SolutionStomachRuminant
- type: entity
parent: SolutionStomach
id: SolutionStomachRuminant
categories: [ HideSpawnMenu ]
components:
- type: Solution
solution:
maxVol: 240
- type: entity
id: BaseMobRuminant
@@ -112,7 +112,7 @@
bloodReferenceSolution:
reagents:
- ReagentId: CopperBlood
Quantity: 300
Quantity: 600
- type: MeleeWeapon
animation: WeaponArcBite
soundHit:
+1 -1
View File
@@ -101,7 +101,7 @@
bloodReferenceSolution:
reagents:
- ReagentId: Sap
Quantity: 300
Quantity: 600
- type: Reactive
groups:
Flammable: [ Touch ]
@@ -60,9 +60,9 @@
bloodReferenceSolution:
reagents:
- ReagentId: Sugar
Quantity: 100
- ReagentId: Butter
Quantity: 200
- ReagentId: Butter
Quantity: 400
- type: Fixtures
fixtures:
fix1:
+1 -1
View File
@@ -157,7 +157,7 @@
bloodReferenceSolution:
reagents:
- ReagentId: InsectBlood
Quantity: 300
Quantity: 600
- type: DamageVisuals
damageOverlayGroups:
Brute:
+3 -4
View File
@@ -134,7 +134,7 @@
bloodReferenceSolution: # TODO Color slime blood based on their slime color or smth
reagents:
- ReagentId: Slime
Quantity: 300
Quantity: 600
- type: Barotrauma
damage:
types:
@@ -255,10 +255,9 @@
id: OrganSlimePersonCore
name: sentient slime core
components:
- type: SolutionContainerManager
- type: SolutionManager
solutions:
stomach:
maxVol: 50.0
- SolutionStomach
- type: Stomach
- type: Metabolizer
maxReagents: 6
+1 -1
View File
@@ -247,7 +247,7 @@
bloodReferenceSolution:
reagents:
- ReagentId: AmmoniaBlood
Quantity: 300
Quantity: 600
- type: MeleeWeapon
soundHit:
collection: AlienClaw
@@ -204,7 +204,7 @@
bloodReferenceSolution:
reagents:
- ReagentId: SulfurBlood
Quantity: 300
Quantity: 600
- type: CreamPied
sprite:
sprite: Effects/creampie.rsi
+25 -7
View File
@@ -334,11 +334,9 @@
- type: Lung
- type: Metabolizer
stages: [ Respiration ]
- type: SolutionContainerManager
- type: SolutionManager
solutions:
Lung:
maxVol: 100.0
canReact: false
- SolutionLungGas
- type: Sprite
layers:
- state: lung-l
@@ -346,6 +344,17 @@
- type: Item
heldPrefix: lungs
- type: entity
parent: Solution
id: SolutionLungGas # TODO: Metabolize directly from gasses :(
categories: [ HideSpawnMenu ]
components:
- type: Solution
id: Lung
solution:
maxVol: 100.0
canReact: false
- type: entity
parent: OrganBase
id: OrganBaseHeart
@@ -372,10 +381,9 @@
- type: Organ
category: Stomach
- type: Stomach
- type: SolutionContainerManager
- type: SolutionManager
solutions:
stomach:
maxVol: 50
- SolutionStomach
- type: Metabolizer
maxReagents: 3
stages: [ Digestion ]
@@ -385,6 +393,16 @@
- type: Item
heldPrefix: stomach
- type: entity
parent: Solution
id: SolutionStomach
categories: [ HideSpawnMenu ]
components:
- type: Solution
id: stomach
solution:
maxVol: 150 # 1.25 liters which is average for a human
- type: entity
parent: OrganBase
id: OrganBaseLiver
@@ -45,13 +45,9 @@
storagebase: !type:AllSelector
children:
- id: JugBicaridine
- id: JugPuncturase
- id: JugDermaline
- id: JugDylovene
- id: JugHyronalin
- id: JugSaline
- id: JugDexalinPlus
- id: JugTranexamicAcid
- id: JugPunctTranex
- id: JugPyraDerma
- id: JugDexPlusSaline
- type: entity
parent: ClothingBackpackDuffelSyndicateBundle
@@ -48,7 +48,8 @@
- 5
- 10
- 15
- 50
- 30
- 60
- type: injectorMode
parent: [ BaseSyringeMode, BaseInjectMode ]
@@ -66,14 +67,6 @@
parent: [ BaseBluespaceSyringeMode, BaseDrawMode ]
id: BluespaceSyringeDrawMode
- type: injectorMode
parent: [ BaseCryostasisSyringeMode, BaseInjectMode ]
id: CryostasisSyringeInjectMode
- type: injectorMode
parent: [ BaseCryostasisSyringeMode, BaseDrawMode ]
id: CryostasisSyringeDrawMode
## Dropper
- type: injectorMode
abstract: true
@@ -20,7 +20,7 @@
type: ChameleonBoundUserInterface
- type: entity
parent: [Clothing, ClothingSlotBase]
parent: [SolutionTank, SolutionGinormous, Clothing, ClothingSlotBase]
id: ClothingBackpackWaterTank
name: backpack water tank
description: Holds a large amount of fluids. Supplies to spray nozzles in your hands, and has a slot on the side for said spray nozzles.
@@ -54,10 +54,6 @@
- type: SolutionAmmoProvider
solutionId: tank
proto: BulletWaterShot
- type: SolutionContainerManager
solutions:
tank:
maxVol: 1000 #much water
- type: SolutionTransfer
transferAmount: 50
maxTransferAmount: 100
@@ -107,8 +107,8 @@
- WhitelistChameleon
- type: entity
parent: ClothingEyesHudBase
id: ClothingEyesHudFriedOnion
parent: [SolutionFood, SolutionSmall, ClothingEyesHudBase]
id: ClothingEyesHudFriedOnion # It's been too long since I've had good onion rings...
name: fried onion goggles
description: Filler
components:
@@ -118,13 +118,11 @@
sprite: Clothing/Eyes/Hud/friedonion.rsi
- type: ShowHungerIcons
- type: Edible
- type: SolutionContainerManager
solutions:
food:
maxVol: 3
reagents:
- ReagentId: Nutriment
Quantity: 3
- type: Solution
solution:
reagents:
- ReagentId: Nutriment
Quantity: 30
- type: FlavorProfile
flavors:
- onion
@@ -1,6 +1,6 @@
- type: entity
abstract: true
parent: Clothing
parent: [SolutionFood, SolutionToolSmall, Clothing]
id: ClothingHandsBase
components:
- type: Sprite
@@ -12,13 +12,11 @@
- type: Item
size: Small
storedRotation: -90
- type: SolutionContainerManager
solutions:
food:
maxVol: 10
reagents:
- ReagentId: Fiber
Quantity: 10
- type: Solution
solution:
reagents:
- ReagentId: Fiber
Quantity: 15
- type: Tag
tags:
- ClothMade
@@ -1,6 +1,6 @@
- type: entity
abstract: true
parent: Clothing
parent: [SolutionFood, SolutionToolSmall, Clothing]
id: ClothingHeadBase
components:
- type: Clothing
@@ -13,13 +13,11 @@
storedRotation: -90
- type: Edible
requiresSpecialDigestion: true
- type: SolutionContainerManager
solutions:
food:
maxVol: 10
reagents:
- ReagentId: Fiber
Quantity: 10
- type: Solution
solution:
reagents:
- ReagentId: Fiber
Quantity: 15
- type: Tag
tags:
- ClothMade
@@ -61,7 +59,7 @@
equippedPrefix: off
- type: Item
heldPrefix: off
size: Normal
size: Normal # I'm not updating the solution because these probably shouldn't be edible...
- type: ToggleableVisuals
spriteLayer: light
- type: ItemTogglePointLight
@@ -302,7 +302,7 @@
- FacialHair
- type: entity
parent: Clothing
parent: [SolutionDrink, Clothing]
id: WaterDropletHat
name: water droplet
description: Makes 8-eyed friends 8 times more adorable!
@@ -328,13 +328,12 @@
- water
- type: DrainableSolution
solution: drink
- type: SolutionContainerManager
solutions:
drink:
maxVol: 2
reagents:
- ReagentId: Water
Quantity: 2
- type: Solution
solution:
maxVol: 2 # Droplets are pretty small.
reagents:
- ReagentId: Water
Quantity: 2
- type: DamageOnHighSpeedImpact
minimumSpeed: 0.1
damage:
@@ -32,9 +32,9 @@
event: !type:ToggleMaskEvent
- type: entity
id: ClothingMaskBaseButcherable
parent: ClothingMaskBase
abstract: true
parent: [SolutionFood, SolutionToolSmall, ClothingMaskBase]
id: ClothingMaskBaseButcherable
components:
- type: Butcherable
butcheringType: Knife
@@ -46,13 +46,11 @@
Cloth: 50
- type: Edible
requiresSpecialDigestion: true
- type: SolutionContainerManager
solutions:
food:
maxVol: 10
reagents:
- ReagentId: Fiber
Quantity: 10
- type: Solution
solution:
reagents:
- ReagentId: Fiber
Quantity: 15
- type: Tag
tags:
- ClothMade
@@ -1,9 +1,9 @@
- type: entity
abstract: true
parent: [UnsensoredClothingUniformBase, ClothingHeadBase, ClothingBeltBase]
id: BaseTowel
name: base towel
abstract: true
description: If you want to survive out here, you gotta know where your towel is.
parent: [ UnsensoredClothingUniformBase, ClothingHeadBase, ClothingBeltBase ]
components:
- type: Sprite
sprite: Clothing/Multiple/towel.rsi
@@ -21,15 +21,9 @@
spillWhenThrown: false
- type: Absorbent
pickupAmount: 15
- type: SolutionContainerManager
- type: SolutionManager
solutions:
food:
maxVol: 30
reagents:
- ReagentId: Fiber
Quantity: 30
absorbed:
maxVol: 30
- SolutionMopSmall
- type: UseDelay
delay: 1
- type: Fiber
@@ -1,6 +1,6 @@
- type: entity
abstract: true
parent: Clothing
parent: [SolutionFood, SolutionToolSmall, Clothing]
id: ClothingNeckBase
components:
- type: Item
@@ -21,13 +21,11 @@
Cloth: 100
- type: Edible
requiresSpecialDigestion: true
- type: SolutionContainerManager
solutions:
food:
maxVol: 10
reagents:
- ReagentId: Fiber
Quantity: 10
- type: Solution
solution:
reagents:
- ReagentId: Fiber
Quantity: 15
- type: Tag
tags:
- ClothMade
@@ -263,7 +263,7 @@
equippedPrefix: goldautism
- type: entity
parent: BaseItem
parent: [SolutionSpray, SolutionTiny, BaseItem]
id: SprayFlowerPin
name: flower pin
description: A cute flower pin. Something seems off with it...
@@ -281,13 +281,11 @@
- neck
- type: EquipSpray
verbLocId: equip-spray-verb-press
- type: SolutionContainerManager
solutions:
spray:
maxVol: 30
reagents:
- ReagentId: Water
Quantity: 30
- type: Solution
solution:
reagents:
- ReagentId: Water
Quantity: 30
- type: RefillableSolution
solution: spray
- type: DrainableSolution
@@ -48,6 +48,19 @@
- type: StaticPrice
price: 70
- type: entity
abstract: true
parent: [SolutionFood, SolutionNormal, ClothingOuterStorageBase]
id: ClothingOuterStorageEdible
components:
- type: Edible
requiresSpecialDigestion: true
- type: Solution
solution:
reagents:
- ReagentId: Fiber
Quantity: 60
- type: entity
abstract: true
parent: [ClothingOuterStorageBase, BaseFoldable]
@@ -123,7 +123,7 @@
id: SyndieHandyFlag
- type: entity
parent: ClothingOuterStorageBase
parent: ClothingOuterStorageEdible
id: ClothingOuterCoatTrench
name: trench coat
description: A comfy trench coat.
@@ -139,15 +139,6 @@
modifiers:
coefficients:
Slash: 0.95
- type: Edible
requiresSpecialDigestion: true
- type: SolutionContainerManager
solutions:
food:
maxVol: 20
reagents:
- ReagentId: Fiber
Quantity: 20
- type: entity
parent: ClothingOuterStorageFoldableBase
@@ -1,5 +1,5 @@
- type: entity
parent: ClothingOuterStorageBase
parent: ClothingOuterStorageEdible
id: ClothingOuterWinterCoat
name: winter coat
description: A heavy jacket made from 'synthetic' animal furs.
@@ -21,15 +21,6 @@
priceMultiplier: 0
- type: ZombificationResistance
zombificationResistanceCoefficient: 0.55
- type: Edible
requiresSpecialDigestion: true
- type: SolutionContainerManager
solutions:
food:
maxVol: 30
reagents:
- ReagentId: Fiber
Quantity: 30
- type: Tag
tags:
- ClothMade
@@ -875,13 +866,11 @@
- cobwebs
ignoreReagents:
- Fiber
- type: SolutionContainerManager
solutions: # 15 (3 (fiber count of web) * 5 (to craft)) + 5 (magical crafting bonus)
food:
maxVol: 20
reagents:
- ReagentId: Fiber
Quantity: 20
- type: Solution
solution:
reagents:
- ReagentId: Fiber
Quantity: 50 # 50 (10 (fiber count of web) * 5 (to craft))
- type: ToggleableClothing
clothingPrototype: ClothingHeadHatHoodWinterWeb
@@ -1,6 +1,6 @@
- type: entity
abstract: true
parent: Clothing
parent: [SolutionFood, SolutionToolNormal, Clothing]
id: ClothingShoesBase
components:
- type: Clothing
@@ -12,13 +12,11 @@
size: Normal
- type: Edible
requiresSpecialDigestion: true
- type: SolutionContainerManager
solutions:
food:
maxVol: 10
reagents:
- ReagentId: Fiber
Quantity: 10
- type: Solution
solution:
reagents:
- ReagentId: Fiber
Quantity: 30
- type: Tag
tags:
- ClothMade
@@ -190,13 +190,11 @@
- cobwebs
ignoreReagents:
- Fiber
- type: SolutionContainerManager
solutions: # 6 (3 (fiber count of web) * 2 (to craft)) + 4 (magical crafting bonus)
food:
maxVol: 10
reagents:
- ReagentId: Fiber
Quantity: 10
- type: Solution
solution: # 20 (10 (fiber count of web) * 2 (to craft))
reagents:
- ReagentId: Fiber
Quantity: 20
- type: Butcherable
spawned:
- id: MaterialWebSilk1

Some files were not shown because too many files have changed in this diff Show More