Solution refactor (#4407)

* Rename SolutionContainerCaps -> Capability

* Move IExamine event to Chemistry System.

* ECS the ISolutionChange into SolutionChangeEvent

* Unify SolutionContainer into a single shared component

* Replace ISolutionInteraction with SolutionContainerComponent

* Move all methods from SolutionContainer to ChemistrySystem

* Refactor EntitySystem calls to Dependencies

* Refactor SolutionContainer to SolutionManager

* Fix yamls

* Fix test fails

* Fix post merge issues

* Fix various issues with SolutionManager

* More fixes

* Fix more components

* Fix events not being directed

* Fixes for Hypospray

* Separate removal and iteration on Metabolism

* Fix creampie problems

* Address some of sloth's issues

* Refactors for Systems

* Refactored solution location

* Fix tests

* Address more sloth issues

* Fix dependency

* Fix merge conflicts

* Add xmldocs for Capabilities components

* Remove HasSolution/TryGetDefaultSolution and Add/Remove Drainable/Refillable

* Replace Grindable/Juiceable with Extractable

* Refactor field names

* Fix Drainable

* Fix some issues with spillable and injector

* Fix issues with Grinder

* Fix Beaker having duplicate solutions

* Fix foaming

* Address some MGS issues

* Fix Uid issues

* Fix errors in solution Tranfer

* Fixed some extra values constant values

* Cola is drinkable now
This commit is contained in:
Ygg01
2021-09-06 15:49:44 +02:00
committed by GitHub
parent b8911d58ac
commit c209e3f29b
166 changed files with 4268 additions and 3278 deletions

View File

@@ -1,12 +0,0 @@
using Content.Shared.Chemistry.Solution.Components;
using Robust.Shared.GameObjects;
namespace Content.Client.Chemistry.Components
{
[RegisterComponent]
[ComponentReference(typeof(SharedSolutionContainerComponent))]
public class SolutionContainerComponent : SharedSolutionContainerComponent
{
}
}

View File

@@ -1,5 +1,5 @@
using System; using System;
using Content.Shared.Chemistry.Solution.Components; using Content.Shared.Chemistry.EntitySystems;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Client.GameObjects; using Robust.Client.GameObjects;
using Robust.Shared.Maths; using Robust.Shared.Maths;

View File

@@ -224,8 +224,7 @@ namespace Content.Client.Entry
"DebugEquip", "DebugEquip",
"InnateActions", "InnateActions",
"ReagentGrinder", "ReagentGrinder",
"Grindable", "Extractable",
"Juiceable",
"WelderRefinable", "WelderRefinable",
"ConveyorAssembly", "ConveyorAssembly",
"TwoWayLever", "TwoWayLever",

View File

@@ -1,6 +1,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.Reagent; using Content.Shared.Chemistry.Reagent;
using Content.Shared.Chemistry.Solution;
using Content.Shared.Kitchen.Components; using Content.Shared.Kitchen.Components;
using Robust.Client.AutoGenerated; using Robust.Client.AutoGenerated;
using Robust.Client.GameObjects; using Robust.Client.GameObjects;

View File

@@ -1,7 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.Reagent; using Content.Shared.Chemistry.Reagent;
using Content.Shared.Chemistry.Solution;
using Content.Shared.Kitchen.Components; using Content.Shared.Kitchen.Components;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Client.GameObjects; using Robust.Client.GameObjects;

View File

@@ -4,7 +4,7 @@ using Robust.Client.UserInterface.Controls;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.IoC; using Robust.Shared.IoC;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
using static Content.Shared.Chemistry.Solution.Solution; using static Content.Shared.Chemistry.Components.Solution;
namespace Content.Client.Kitchen.UI namespace Content.Client.Kitchen.UI
{ {

View File

@@ -28,6 +28,7 @@ namespace Content.IntegrationTests.Tests.Body
components: components:
- type: Bloodstream - type: Bloodstream
max_volume: 100 max_volume: 100
- type: SolutionContainerManager
- type: Body - type: Body
template: HumanoidTemplate template: HumanoidTemplate
preset: HumanPreset preset: HumanPreset

View File

@@ -1,17 +1,13 @@
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Content.Server.Chemistry.Components; using Content.Shared.Chemistry.Components;
using Content.Server.Fluids.Components; using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Chemistry.Reaction; using Content.Shared.Chemistry.Reaction;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.Coordinates;
using NUnit.Framework; using NUnit.Framework;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.Map; using Robust.Shared.Map;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
using Robust.Shared.Utility; using Robust.Shared.Utility;
namespace Content.IntegrationTests.Tests.Chemistry namespace Content.IntegrationTests.Tests.Chemistry
@@ -37,17 +33,19 @@ namespace Content.IntegrationTests.Tests.Chemistry
Console.WriteLine($"Testing {reactionPrototype.ID}"); Console.WriteLine($"Testing {reactionPrototype.ID}");
IEntity beaker; IEntity beaker;
SolutionContainerComponent component = null; Solution component = null;
server.Assert(() => server.Assert(() =>
{ {
mapManager.CreateNewMapEntity(MapId.Nullspace); mapManager.CreateNewMapEntity(MapId.Nullspace);
beaker = entityManager.SpawnEntity("BluespaceBeaker", MapCoordinates.Nullspace); beaker = entityManager.SpawnEntity("BluespaceBeaker", MapCoordinates.Nullspace);
Assert.That(beaker.TryGetComponent(out component)); Assert.That(EntitySystem.Get<SolutionContainerSystem>()
.TryGetSolution(beaker, "beaker", out component));
foreach (var (id, reactant) in reactionPrototype.Reactants) foreach (var (id, reactant) in reactionPrototype.Reactants)
{ {
Assert.That(component.TryAddReagent(id, reactant.Amount, out var quantity)); Assert.That(EntitySystem.Get<SolutionContainerSystem>()
.TryAddReagent(beaker.Uid, component, id, reactant.Amount, out var quantity));
Assert.That(reactant.Amount, Is.EqualTo(quantity)); Assert.That(reactant.Amount, Is.EqualTo(quantity));
} }
}); });
@@ -61,7 +59,7 @@ namespace Content.IntegrationTests.Tests.Chemistry
var foundProductsMap = reactionPrototype.Products var foundProductsMap = reactionPrototype.Products
.Concat(reactionPrototype.Reactants.Where(x => x.Value.Catalyst).ToDictionary(x => x.Key, x => x.Value.Amount)) .Concat(reactionPrototype.Reactants.Where(x => x.Value.Catalyst).ToDictionary(x => x.Key, x => x.Value.Amount))
.ToDictionary(x => x, x => false); .ToDictionary(x => x, x => false);
foreach (var reagent in component.Solution.Contents) foreach (var reagent in component.Contents)
{ {
Assert.That(foundProductsMap.TryFirstOrNull(x => x.Key.Key == reagent.ReagentId && x.Key.Value == reagent.Quantity, out var foundProduct)); Assert.That(foundProductsMap.TryFirstOrNull(x => x.Key.Key == reagent.ReagentId && x.Key.Value == reagent.Quantity, out var foundProduct));
foundProductsMap[foundProduct.Value.Key] = true; foundProductsMap[foundProduct.Value.Key] = true;

View File

@@ -1,9 +1,8 @@
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Content.Server.Fluids.Components; using Content.Server.Fluids.Components;
using Content.Shared.Chemistry; using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.Reagent; using Content.Shared.Chemistry.Reagent;
using Content.Shared.Chemistry.Solution;
using Content.Shared.Coordinates; using Content.Shared.Coordinates;
using NUnit.Framework; using NUnit.Framework;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;

View File

@@ -1,6 +1,8 @@
using Content.Server.AI.WorldState; using Content.Server.AI.WorldState;
using Content.Server.AI.WorldState.States; using Content.Server.AI.WorldState.States;
using Content.Server.Chemistry.Components; using Content.Server.Nutrition.Components;
using Content.Shared.Chemistry.EntitySystems;
using Robust.Shared.GameObjects;
namespace Content.Server.AI.Utility.Considerations.Nutrition.Drink namespace Content.Server.AI.Utility.Considerations.Nutrition.Drink
{ {
@@ -10,14 +12,16 @@ namespace Content.Server.AI.Utility.Considerations.Nutrition.Drink
{ {
var target = context.GetState<TargetEntityState>().GetValue(); var target = context.GetState<TargetEntityState>().GetValue();
if (target == null || target.Deleted || !target.TryGetComponent(out SolutionContainerComponent? drink)) if (target == null
|| target.Deleted
|| !EntitySystem.Get<SolutionContainerSystem>().TryGetSolution(target, DrinkComponent.DefaultSolutionName, out var drink))
{ {
return 0.0f; return 0.0f;
} }
var nutritionValue = 0; var nutritionValue = 0;
foreach (var reagent in drink.ReagentList) foreach (var reagent in drink.Contents)
{ {
// TODO // TODO
nutritionValue += (reagent.Quantity * 30).Int(); nutritionValue += (reagent.Quantity * 30).Int();

View File

@@ -1,6 +1,8 @@
using Content.Server.AI.WorldState; using Content.Server.AI.WorldState;
using Content.Server.AI.WorldState.States; using Content.Server.AI.WorldState.States;
using Content.Server.Chemistry.Components; using Content.Server.Nutrition.Components;
using Content.Shared.Chemistry.EntitySystems;
using Robust.Shared.GameObjects;
namespace Content.Server.AI.Utility.Considerations.Nutrition.Food namespace Content.Server.AI.Utility.Considerations.Nutrition.Food
{ {
@@ -10,14 +12,15 @@ namespace Content.Server.AI.Utility.Considerations.Nutrition.Food
{ {
var target = context.GetState<TargetEntityState>().GetValue(); var target = context.GetState<TargetEntityState>().GetValue();
if (target == null || target.Deleted || !target.TryGetComponent(out SolutionContainerComponent? food)) if (target == null || target.Deleted ||
!EntitySystem.Get<SolutionContainerSystem>().TryGetSolution(target, FoodComponent.SolutionName, out var food))
{ {
return 0.0f; return 0.0f;
} }
var nutritionValue = 0; var nutritionValue = 0;
foreach (var reagent in food.ReagentList) foreach (var reagent in food.Contents)
{ {
// TODO // TODO
nutritionValue += (reagent.Quantity * 30).Int(); nutritionValue += (reagent.Quantity * 30).Int();

View File

@@ -1,10 +1,10 @@
using Content.Server.Administration.Managers; using Content.Server.Administration.Managers;
using Content.Server.Chemistry.Components;
using Content.Server.EUI; using Content.Server.EUI;
using Content.Shared.Administration; using Content.Shared.Administration;
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.Components.SolutionManager;
using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Chemistry.Reagent; using Content.Shared.Chemistry.Reagent;
using Content.Shared.Chemistry.Solution;
using Content.Shared.Chemistry.Solution.Components;
using Content.Shared.Eui; using Content.Shared.Eui;
using Content.Shared.Verbs; using Content.Shared.Verbs;
using Robust.Server.GameObjects; using Robust.Server.GameObjects;
@@ -13,7 +13,6 @@ using Robust.Shared.GameObjects;
using Robust.Shared.IoC; using Robust.Shared.IoC;
using Robust.Shared.Localization; using Robust.Shared.Localization;
namespace Content.Server.Administration.Verbs namespace Content.Server.Administration.Verbs
{ {
[GlobalVerb] [GlobalVerb]
@@ -34,9 +33,8 @@ namespace Content.Server.Administration.Verbs
{ {
// ISolutionInteractionsComponent doesn't exactly have an interface for "admin tries to refill this", so... // ISolutionInteractionsComponent doesn't exactly have an interface for "admin tries to refill this", so...
// Still have a path for SolutionContainerComponent in case it doesn't allow direct refilling. // Still have a path for SolutionContainerComponent in case it doesn't allow direct refilling.
if (!target.HasComponent<SolutionContainerComponent>() if (!(target.HasComponent<SolutionContainerManagerComponent>()
&& !(target.TryGetComponent(out ISolutionInteractionsComponent? interactions) && target.HasComponent<InjectableSolutionComponent>()))
&& interactions.CanInject))
{ {
data.Visibility = VerbVisibility.Invisible; data.Visibility = VerbVisibility.Invisible;
return; return;
@@ -87,7 +85,8 @@ namespace Content.Server.Administration.Verbs
public override EuiStateBase GetNewState() public override EuiStateBase GetNewState()
{ {
if (_target.TryGetComponent(out SolutionContainerComponent? container)) if (EntitySystem.Get<SolutionContainerSystem>()
.TryGetSolution(_target, "default", out var container))
{ {
return new AdminAddReagentEuiState return new AdminAddReagentEuiState
{ {
@@ -96,16 +95,6 @@ namespace Content.Server.Administration.Verbs
}; };
} }
if (_target.TryGetComponent(out ISolutionInteractionsComponent? interactions))
{
return new AdminAddReagentEuiState
{
// We don't exactly have an absolute total volume so good enough.
CurVolume = ReagentUnit.Zero,
MaxVolume = interactions.InjectSpaceAvailable
};
}
return new AdminAddReagentEuiState return new AdminAddReagentEuiState
{ {
CurVolume = ReagentUnit.Zero, CurVolume = ReagentUnit.Zero,
@@ -131,15 +120,21 @@ namespace Content.Server.Administration.Verbs
var id = doAdd.ReagentId; var id = doAdd.ReagentId;
var amount = doAdd.Amount; var amount = doAdd.Amount;
var solutionsSys = EntitySystem.Get<SolutionContainerSystem>();
if (_target.TryGetComponent(out SolutionContainerComponent? container)) if (_target.TryGetComponent(out InjectableSolutionComponent? injectable)
{ && solutionsSys.TryGetSolution(_target, injectable.Name, out var targetSolution))
container.TryAddReagent(id, amount, out _);
}
else if (_target.TryGetComponent(out ISolutionInteractionsComponent? interactions))
{ {
var solution = new Solution(id, amount); var solution = new Solution(id, amount);
interactions.Inject(solution); solutionsSys.Inject(_target.Uid, targetSolution, solution);
}
else
{
//TODO decide how to find the solution
if (solutionsSys.TryGetSolution(_target, "default", out var solution))
{
solutionsSys.TryAddReagent(_target.Uid,solution, id, amount, out _);
}
} }
StateDirty(); StateDirty();

View File

@@ -1,10 +1,9 @@
using Content.Server.Atmos.EntitySystems; using Content.Server.Atmos.EntitySystems;
using Content.Server.Fluids.Components; using Content.Server.Fluids.Components;
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.Reagent; using Content.Shared.Chemistry.Reagent;
using Content.Shared.Chemistry.Solution;
using Content.Shared.Maps; using Content.Shared.Maps;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Server.GameObjects;
using Robust.Shared.Serialization.Manager.Attributes; using Robust.Shared.Serialization.Manager.Attributes;
namespace Content.Server.Atmos.Reactions namespace Content.Server.Atmos.Reactions

View File

@@ -1,11 +1,10 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Content.Server.Body.Circulatory; using Content.Server.Body.Circulatory;
using Content.Server.Chemistry.Components;
using Content.Shared.Body.Networks; using Content.Shared.Body.Networks;
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Chemistry.Reagent; using Content.Shared.Chemistry.Reagent;
using Content.Shared.Chemistry.Solution;
using Content.Shared.Chemistry.Solution.Components;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.Serialization.Manager.Attributes; using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables; using Robust.Shared.ViewVariables;
@@ -18,6 +17,7 @@ namespace Content.Server.Body.Behavior
/// </summary> /// </summary>
public class StomachBehavior : MechanismBehavior public class StomachBehavior : MechanismBehavior
{ {
private const string DefaultSolutionName = "stomach";
private float _accumulatedFrameTime; private float _accumulatedFrameTime;
/// <summary> /// <summary>
@@ -30,7 +30,6 @@ namespace Content.Server.Body.Behavior
/// </param> /// </param>
public override void Update(float frameTime) public override void Update(float frameTime)
{ {
// Do not metabolise if the organ does not have a body. // Do not metabolise if the organ does not have a body.
if (Body == null) if (Body == null)
{ {
@@ -49,8 +48,8 @@ namespace Content.Server.Body.Behavior
// Note that "Owner" should be the organ that has this behaviour/mechanism, and it should have a dedicated // Note that "Owner" should be the organ that has this behaviour/mechanism, and it should have a dedicated
// solution container. "Body.Owner" is something else, and may have more than one solution container. // solution container. "Body.Owner" is something else, and may have more than one solution container.
if (!Owner.TryGetComponent(out SolutionContainerComponent? solution) || if (!Body.Owner.TryGetComponent(out BloodstreamComponent? bloodstream)
!Body.Owner.TryGetComponent(out BloodstreamComponent? bloodstream)) || !EntitySystem.Get<SolutionContainerSystem>().TryGetSolution(Owner, SharedBloodstreamComponent.DefaultSolutionName, out var solution))
{ {
return; return;
} }
@@ -68,13 +67,15 @@ namespace Content.Server.Body.Behavior
// This reagent has been in the somach long enough, TRY to transfer it. // This reagent has been in the somach long enough, TRY to transfer it.
// But first, check if the reagent still exists, and how much is left. // But first, check if the reagent still exists, and how much is left.
// Some poor spessman may have washed down a potassium snack with some water. // Some poor spessman may have washed down a potassium snack with some water.
if (solution.Solution.ContainsReagent(delta.ReagentId, out ReagentUnit quantity)){ if (solution.ContainsReagent(delta.ReagentId, out ReagentUnit quantity))
{
if (quantity > delta.Quantity) { if (quantity > delta.Quantity)
{
quantity = delta.Quantity; quantity = delta.Quantity;
} }
solution.TryRemoveReagent(delta.ReagentId, quantity); EntitySystem.Get<SolutionContainerSystem>()
.TryRemoveReagent(Owner.Uid, solution, delta.ReagentId, quantity);
transferSolution.AddReagent(delta.ReagentId, quantity); transferSolution.AddReagent(delta.ReagentId, quantity);
} }
@@ -86,17 +87,28 @@ namespace Content.Server.Body.Behavior
bloodstream.TryTransferSolution(transferSolution); bloodstream.TryTransferSolution(transferSolution);
} }
public Solution? StomachSolution
{
get
{
EntitySystem.Get<SolutionContainerSystem>().TryGetSolution(Owner, DefaultSolutionName, out var solution);
return solution;
}
}
/// <summary> /// <summary>
/// Max volume of internal solution storage /// Max volume of internal solution storage
/// </summary> /// </summary>
public ReagentUnit MaxVolume public ReagentUnit MaxVolume
{ {
get => Owner.TryGetComponent(out SharedSolutionContainerComponent? solution) ? solution.MaxVolume : ReagentUnit.Zero; get =>
StomachSolution?.MaxVolume ?? ReagentUnit.Zero;
set set
{ {
if (Owner.TryGetComponent(out SharedSolutionContainerComponent? solution)) if (StomachSolution != null)
{ {
solution.MaxVolume = value; StomachSolution.MaxVolume = value;
} }
} }
} }
@@ -118,27 +130,25 @@ namespace Content.Server.Body.Behavior
/// <summary> /// <summary>
/// Used to track how long each reagent has been in the stomach /// Used to track how long each reagent has been in the stomach
/// </summary> /// </summary>
[ViewVariables] [ViewVariables] private readonly List<ReagentDelta> _reagentDeltas = new();
private readonly List<ReagentDelta> _reagentDeltas = new();
public override void Startup() public override void Startup()
{ {
base.Startup(); base.Startup();
Owner.EnsureComponentWarn(out SolutionContainerComponent solution); var solution = EntitySystem.Get<SolutionContainerSystem>().EnsureSolution(Owner, DefaultSolutionName);
solution.MaxVolume = InitialMaxVolume; solution.MaxVolume = InitialMaxVolume;
} }
public bool CanTransferSolution(Solution solution) public bool CanTransferSolution(Solution solution)
{ {
if (!Owner.TryGetComponent(out SharedSolutionContainerComponent? solutionComponent)) if (StomachSolution == null)
{ {
return false; return false;
} }
// TODO: For now no partial transfers. Potentially change by design // TODO: For now no partial transfers. Potentially change by design
if (!solutionComponent.CanAddSolution(solution)) if (!StomachSolution.CanAddSolution(solution))
{ {
return false; return false;
} }
@@ -151,13 +161,13 @@ namespace Content.Server.Body.Behavior
if (Owner == null || !CanTransferSolution(solution)) if (Owner == null || !CanTransferSolution(solution))
return false; return false;
if (!Owner.TryGetComponent(out SolutionContainerComponent? solutionComponent)) if (StomachSolution == null)
{ {
return false; return false;
} }
// Add solution to _stomachContents // Add solution to _stomachContents
solutionComponent.TryAddSolution(solution); EntitySystem.Get<SolutionContainerSystem>().TryAddSolution(Owner.Uid, StomachSolution, solution);
// Add each reagent to _reagentDeltas. Used to track how long each reagent has been in the stomach // Add each reagent to _reagentDeltas. Used to track how long each reagent has been in the stomach
foreach (var reagent in solution.Contents) foreach (var reagent in solution.Contents)
{ {

View File

@@ -1,13 +1,12 @@
using System; using System;
using System.Linq;
using Content.Server.Atmos; using Content.Server.Atmos;
using Content.Server.Atmos.EntitySystems; using Content.Server.Atmos.EntitySystems;
using Content.Server.Body.Respiratory; using Content.Server.Body.Respiratory;
using Content.Server.Chemistry.Components;
using Content.Shared.Atmos; using Content.Shared.Atmos;
using Content.Shared.Body.Networks; using Content.Shared.Body.Networks;
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Chemistry.Reagent; using Content.Shared.Chemistry.Reagent;
using Content.Shared.Chemistry.Solution;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.Serialization.Manager.Attributes; using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables; using Robust.Shared.ViewVariables;
@@ -23,31 +22,33 @@ namespace Content.Server.Body.Circulatory
/// <summary> /// <summary>
/// Max volume of internal solution storage /// Max volume of internal solution storage
/// </summary> /// </summary>
[DataField("maxVolume")] [DataField("maxVolume")] [ViewVariables]
[ViewVariables] private ReagentUnit _initialMaxVolume = ReagentUnit.New(250); private ReagentUnit _initialMaxVolume = ReagentUnit.New(250);
/// <summary> /// <summary>
/// Internal solution for reagent storage /// Internal solution for reagent storage
/// </summary> /// </summary>
[ViewVariables] private SolutionContainerComponent _internalSolution = default!; [ViewVariables] private Solution? _internalSolution;
/// <summary> /// <summary>
/// Empty volume of internal solution /// Empty volume of internal solution
/// </summary> /// </summary>
[ViewVariables] public ReagentUnit EmptyVolume => _internalSolution.EmptyVolume; [ViewVariables]
public ReagentUnit EmptyVolume => _internalSolution?.AvailableVolume ?? ReagentUnit.Zero;
[ViewVariables] [ViewVariables]
public GasMixture Air { get; set; } = new(6) public GasMixture Air { get; set; } = new(6)
{Temperature = Atmospherics.NormalBodyTemperature}; { Temperature = Atmospherics.NormalBodyTemperature };
[ViewVariables] public SolutionContainerComponent Solution => _internalSolution;
protected override void Initialize() protected override void Initialize()
{ {
base.Initialize(); base.Initialize();
_internalSolution = Owner.EnsureComponent<SolutionContainerComponent>(); _internalSolution = EntitySystem.Get<SolutionContainerSystem>().EnsureSolution(Owner, DefaultSolutionName);
_internalSolution.MaxVolume = _initialMaxVolume; if (_internalSolution != null)
{
_internalSolution.MaxVolume = _initialMaxVolume;
}
} }
/// <summary> /// <summary>
@@ -59,12 +60,14 @@ namespace Content.Server.Body.Circulatory
public override bool TryTransferSolution(Solution solution) public override bool TryTransferSolution(Solution solution)
{ {
// For now doesn't support partial transfers // For now doesn't support partial transfers
if (solution.TotalVolume + _internalSolution.CurrentVolume > _internalSolution.MaxVolume) var current = _internalSolution?.CurrentVolume ?? ReagentUnit.Zero;
var max = _internalSolution?.MaxVolume ?? ReagentUnit.Zero;
if (solution.TotalVolume + current > max)
{ {
return false; return false;
} }
_internalSolution.TryAddSolution(solution); EntitySystem.Get<SolutionContainerSystem>().TryAddSolution(Owner.Uid, _internalSolution, solution);
return true; return true;
} }

View File

@@ -1,5 +1,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using Content.Shared.Body.Networks;
using Content.Shared.Chemistry.Reagent; using Content.Shared.Chemistry.Reagent;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.Serialization.Manager.Attributes; using Robust.Shared.Serialization.Manager.Attributes;
@@ -25,11 +26,11 @@ namespace Content.Server.Body.Metabolism
public float UpdateFrequency = 1.0f; public float UpdateFrequency = 1.0f;
/// <summary> /// <summary>
/// Whether this metabolizer should attempt to metabolize chemicals in its parent bodies' bloodstream, /// From which solution will this metabolizer attempt to metabolize chemicals in its parent bodies' bloodstream,
/// as opposed to a solution container on the metabolizing entity itself. /// as opposed to a solution container on the metabolizing entity itself.
/// </summary> /// </summary>
[DataField("takeFromBloodstream")] [DataField("solution")]
public bool TakeFromBloodstream = true; public string SolutionName { get; set; } = SharedBloodstreamComponent.DefaultSolutionName;
/// <summary> /// <summary>
/// A dictionary mapping reagent string IDs to a list of effects & associated metabolism rate. /// A dictionary mapping reagent string IDs to a list of effects & associated metabolism rate.

View File

@@ -1,20 +1,36 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using Content.Server.Body.Circulatory; using Content.Server.Body.Circulatory;
using Content.Server.Chemistry.Components;
using Content.Shared.Body.Components; using Content.Shared.Body.Components;
using Content.Shared.Body.Mechanism; using Content.Shared.Body.Mechanism;
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Chemistry.Reagent; using Content.Shared.Chemistry.Reagent;
using Content.Shared.Chemistry.Solution; using JetBrains.Annotations;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.IoC; using Robust.Shared.IoC;
using Robust.Shared.Prototypes;
namespace Content.Server.Body.Metabolism namespace Content.Server.Body.Metabolism
{ {
// TODO mirror in the future working on mechanisms move updating here to BodySystem so it can be ordered? // TODO mirror in the future working on mechanisms move updating here to BodySystem so it can be ordered?
[UsedImplicitly]
public class MetabolizerSystem : EntitySystem public class MetabolizerSystem : EntitySystem
{ {
[Dependency]
private readonly SolutionContainerSystem _solutionContainerSystem = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<MetabolizerComponent, ComponentInit>(OnMetabolizerInit);
}
private void OnMetabolizerInit(EntityUid uid, MetabolizerComponent component, ComponentInit args)
{
_solutionContainerSystem.EnsureSolution(EntityManager.GetEntity(uid), component.SolutionName);
}
public override void Update(float frameTime) public override void Update(float frameTime)
{ {
base.Update(frameTime); base.Update(frameTime);
@@ -35,37 +51,34 @@ namespace Content.Server.Body.Metabolism
private void TryMetabolize(MetabolizerComponent comp) private void TryMetabolize(MetabolizerComponent comp)
{ {
var owner = comp.Owner; var owner = comp.Owner;
var reagentList = new List<Solution.ReagentQuantity>(); IReadOnlyList<Solution.ReagentQuantity> reagentList = new List<Solution.ReagentQuantity>();
SolutionContainerComponent? solution = null; Solution? solution = null;
SharedBodyComponent? body = null; SharedBodyComponent? body = null;
var solutionsSys = Get<SolutionContainerSystem>();
// if this field is passed we should try and take from the bloodstream over anything else // if this field is passed we should try and take from the bloodstream over anything else
if (comp.TakeFromBloodstream && owner.TryGetComponent<SharedMechanismComponent>(out var mech)) if (owner.TryGetComponent<SharedMechanismComponent>(out var mech))
{ {
body = mech.Body; body = mech.Body;
if (body != null) if (body != null)
{ {
if (body.Owner.TryGetComponent<BloodstreamComponent>(out var bloodstream) if (body.Owner.HasComponent<BloodstreamComponent>()
&& bloodstream.Solution.CurrentVolume >= ReagentUnit.Zero) && solutionsSys.TryGetSolution(body.Owner, comp.SolutionName, out solution)
&& solution.CurrentVolume >= ReagentUnit.Zero)
{ {
solution = bloodstream.Solution; reagentList = solution.Contents;
reagentList = bloodstream.Solution.ReagentList.ToList();
} }
} }
} }
else if (owner.TryGetComponent<SolutionContainerComponent>(out var sol))
{
// if we have no mechanism/body but a solution container instead,
// we'll just use that to metabolize from
solution = sol;
reagentList = sol.ReagentList.ToList();
}
if (solution == null || reagentList.Count == 0) if (solution == null || reagentList.Count == 0)
{ {
// We're all outta ideas on where to metabolize from // We're all outta ideas on where to metabolize from
return; return;
} }
List<Solution.ReagentQuantity> removeReagents = new(5);
// Run metabolism for each reagent, remove metabolized reagents // Run metabolism for each reagent, remove metabolized reagents
foreach (var reagent in reagentList) foreach (var reagent in reagentList)
{ {
@@ -100,8 +113,10 @@ namespace Content.Server.Body.Metabolism
effect.Metabolize(ent, reagent); effect.Metabolize(ent, reagent);
} }
solution.TryRemoveReagent(reagent.ReagentId, metabolism.MetabolismRate); removeReagents.Add(new Solution.ReagentQuantity(reagent.ReagentId, metabolism.MetabolismRate));
} }
solutionsSys.TryRemoveAllReagents(solution, removeReagents);
} }
} }
} }

View File

@@ -1,9 +1,7 @@
using System; using System;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Content.Server.Atmos; using Content.Server.Atmos;
using Content.Server.Atmos.EntitySystems; using Content.Server.Atmos.EntitySystems;
using Content.Server.Chemistry.Components;
using Content.Server.Fluids.Components; using Content.Server.Fluids.Components;
using Content.Server.Hands.Components; using Content.Server.Hands.Components;
using Content.Server.Notification; using Content.Server.Notification;
@@ -11,11 +9,10 @@ using Content.Server.Plants;
using Content.Shared.ActionBlocker; using Content.Shared.ActionBlocker;
using Content.Shared.Audio; using Content.Shared.Audio;
using Content.Shared.Botany; using Content.Shared.Botany;
using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Chemistry.Reagent; using Content.Shared.Chemistry.Reagent;
using Content.Shared.Chemistry.Solution.Components;
using Content.Shared.Examine; using Content.Shared.Examine;
using Content.Shared.Interaction; using Content.Shared.Interaction;
using Content.Shared.Interaction.Events;
using Content.Shared.Notification.Managers; using Content.Shared.Notification.Managers;
using Content.Shared.Random.Helpers; using Content.Shared.Random.Helpers;
using Content.Shared.Tag; using Content.Shared.Tag;
@@ -40,17 +37,23 @@ namespace Content.Server.Botany.Components
{ {
public const float HydroponicsSpeedMultiplier = 1f; public const float HydroponicsSpeedMultiplier = 1f;
public const float HydroponicsConsumptionMultiplier = 4f; public const float HydroponicsConsumptionMultiplier = 4f;
private const string SoilSolutionName = "soil";
[Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!; [Dependency] private readonly IGameTiming _gameTiming = default!;
[ComponentDependency] private readonly AppearanceComponent? _appearanceComponent = default!;
public override string Name => "PlantHolder"; public override string Name => "PlantHolder";
[ViewVariables] private int _lastProduce; [ViewVariables] private int _lastProduce;
[ViewVariables(VVAccess.ReadWrite)] private int _missingGas; [ViewVariables(VVAccess.ReadWrite)] private int _missingGas;
private readonly TimeSpan _cycleDelay = TimeSpan.FromSeconds(15f); private readonly TimeSpan _cycleDelay = TimeSpan.FromSeconds(15f);
[ViewVariables] private TimeSpan _lastCycle = TimeSpan.Zero; [ViewVariables] private TimeSpan _lastCycle = TimeSpan.Zero;
[ViewVariables(VVAccess.ReadWrite)] private bool _updateSpriteAfterUpdate; [ViewVariables(VVAccess.ReadWrite)] private bool _updateSpriteAfterUpdate;
[ViewVariables(VVAccess.ReadWrite)] [ViewVariables(VVAccess.ReadWrite)]
@@ -117,16 +120,6 @@ namespace Content.Server.Botany.Components
[ViewVariables(VVAccess.ReadWrite)] [ViewVariables(VVAccess.ReadWrite)]
public bool ForceUpdate { get; set; } public bool ForceUpdate { get; set; }
[ComponentDependency] private readonly SolutionContainerComponent? _solutionContainer = default!;
[ComponentDependency] private readonly AppearanceComponent? _appearanceComponent = default!;
protected override void Initialize()
{
base.Initialize();
Owner.EnsureComponentWarn<SolutionContainerComponent>();
}
public void WeedInvasion() public void WeedInvasion()
{ {
// TODO // TODO
@@ -142,7 +135,7 @@ namespace Content.Server.Botany.Components
ForceUpdate = false; ForceUpdate = false;
else if (curTime < (_lastCycle + _cycleDelay)) else if (curTime < (_lastCycle + _cycleDelay))
{ {
if(_updateSpriteAfterUpdate) if (_updateSpriteAfterUpdate)
UpdateSprite(); UpdateSprite();
return; return;
} }
@@ -190,8 +183,8 @@ namespace Content.Server.Botany.Components
SkipAging--; SkipAging--;
else else
{ {
if(_random.Prob(0.8f)) if (_random.Prob(0.8f))
Age += (int)(1 * HydroponicsSpeedMultiplier); Age += (int) (1 * HydroponicsSpeedMultiplier);
_updateSpriteAfterUpdate = true; _updateSpriteAfterUpdate = true;
} }
@@ -207,7 +200,8 @@ namespace Content.Server.Botany.Components
// Water consumption. // Water consumption.
if (Seed.WaterConsumption > 0 && WaterLevel > 0 && _random.Prob(0.75f)) if (Seed.WaterConsumption > 0 && WaterLevel > 0 && _random.Prob(0.75f))
{ {
WaterLevel -= MathF.Max(0f, Seed.NutrientConsumption * HydroponicsConsumptionMultiplier * HydroponicsSpeedMultiplier); WaterLevel -= MathF.Max(0f,
Seed.NutrientConsumption * HydroponicsConsumptionMultiplier * HydroponicsSpeedMultiplier);
if (DrawWarnings) if (DrawWarnings)
_updateSpriteAfterUpdate = true; _updateSpriteAfterUpdate = true;
} }
@@ -248,7 +242,8 @@ namespace Content.Server.Botany.Components
_updateSpriteAfterUpdate = true; _updateSpriteAfterUpdate = true;
} }
var environment = EntitySystem.Get<AtmosphereSystem>().GetTileMixture(Owner.Transform.Coordinates, true)?? GasMixture.SpaceGas; var environment = EntitySystem.Get<AtmosphereSystem>().GetTileMixture(Owner.Transform.Coordinates, true) ??
GasMixture.SpaceGas;
if (Seed.ConsumeGasses.Count > 0) if (Seed.ConsumeGasses.Count > 0)
{ {
@@ -306,7 +301,8 @@ namespace Content.Server.Botany.Components
{ {
foreach (var (gas, amount) in Seed.ExudeGasses) foreach (var (gas, amount) in Seed.ExudeGasses)
{ {
environment.AdjustMoles(gas, MathF.Max(1f, MathF.Round((amount * MathF.Round(Seed.Potency)) / exudeCount))); environment.AdjustMoles(gas,
MathF.Max(1f, MathF.Round((amount * MathF.Round(Seed.Potency)) / exudeCount)));
} }
} }
@@ -357,7 +353,7 @@ namespace Content.Server.Botany.Components
if (DrawWarnings) if (DrawWarnings)
_updateSpriteAfterUpdate = true; _updateSpriteAfterUpdate = true;
} }
else if(Age < 0) // Revert back to seed packet! else if (Age < 0) // Revert back to seed packet!
{ {
Seed.SpawnSeedPacket(Owner.Transform.Coordinates); Seed.SpawnSeedPacket(Owner.Transform.Coordinates);
RemovePlant(); RemovePlant();
@@ -393,7 +389,7 @@ namespace Content.Server.Botany.Components
CheckLevelSanity(); CheckLevelSanity();
if(_updateSpriteAfterUpdate) if (_updateSpriteAfterUpdate)
UpdateSprite(); UpdateSprite();
} }
@@ -428,8 +424,8 @@ namespace Content.Server.Botany.Components
{ {
if (!Seed.CheckHarvest(user, hands.GetActiveHand?.Owner)) if (!Seed.CheckHarvest(user, hands.GetActiveHand?.Owner))
return false; return false;
}
} else if (!Seed.CheckHarvest(user)) else if (!Seed.CheckHarvest(user))
{ {
return false; return false;
} }
@@ -460,7 +456,7 @@ namespace Content.Server.Botany.Components
Harvest = false; Harvest = false;
_lastProduce = Age; _lastProduce = Age;
if(Seed?.HarvestRepeat == HarvestType.NoRepeat) if (Seed?.HarvestRepeat == HarvestType.NoRepeat)
RemovePlant(); RemovePlant();
CheckLevelSanity(); CheckLevelSanity();
@@ -546,10 +542,11 @@ namespace Content.Server.Botany.Components
public void UpdateReagents() public void UpdateReagents()
{ {
if (_solutionContainer == null) var solutionSystem = EntitySystem.Get<SolutionContainerSystem>();
if (!solutionSystem.TryGetSolution(Owner, SoilSolutionName, out var solution))
return; return;
if (_solutionContainer.Solution.TotalVolume <= 0 || MutationLevel >= 25) if (solution.TotalVolume <= 0 || MutationLevel >= 25)
{ {
if (MutationLevel >= 0) if (MutationLevel >= 0)
{ {
@@ -560,12 +557,10 @@ namespace Content.Server.Botany.Components
else else
{ {
var one = ReagentUnit.New(1); var one = ReagentUnit.New(1);
foreach (var reagent in solutionSystem.RemoveEachReagent(solution, one))
foreach (var (reagent, amount) in _solutionContainer.ReagentList.ToArray())
{ {
var reagentProto = _prototypeManager.Index<ReagentPrototype>(reagent); var reagentProto = _prototypeManager.Index<ReagentPrototype>(reagent);
reagentProto.ReactionPlant(Owner); reagentProto.ReactionPlant(Owner);
_solutionContainer.Solution.RemoveReagent(reagent, amount < one ? amount : one);
} }
} }
@@ -586,26 +581,30 @@ namespace Content.Server.Botany.Components
if (Seed != null) if (Seed != null)
{ {
if(DrawWarnings) if (DrawWarnings)
_appearanceComponent.SetData(PlantHolderVisuals.HealthLight, Health <= (Seed.Endurance / 2f)); _appearanceComponent.SetData(PlantHolderVisuals.HealthLight, Health <= (Seed.Endurance / 2f));
if (Dead) if (Dead)
{ {
_appearanceComponent.SetData(PlantHolderVisuals.Plant, new SpriteSpecifier.Rsi(Seed.PlantRsi, "dead")); _appearanceComponent.SetData(PlantHolderVisuals.Plant,
new SpriteSpecifier.Rsi(Seed.PlantRsi, "dead"));
} }
else if (Harvest) else if (Harvest)
{ {
_appearanceComponent.SetData(PlantHolderVisuals.Plant, new SpriteSpecifier.Rsi(Seed.PlantRsi, "harvest")); _appearanceComponent.SetData(PlantHolderVisuals.Plant,
new SpriteSpecifier.Rsi(Seed.PlantRsi, "harvest"));
} }
else if (Age < Seed.Maturation) else if (Age < Seed.Maturation)
{ {
var growthStage = Math.Max(1, (int)((Age * Seed.GrowthStages) / Seed.Maturation)); var growthStage = Math.Max(1, (int) ((Age * Seed.GrowthStages) / Seed.Maturation));
_appearanceComponent.SetData(PlantHolderVisuals.Plant, new SpriteSpecifier.Rsi(Seed.PlantRsi,$"stage-{growthStage}")); _appearanceComponent.SetData(PlantHolderVisuals.Plant,
new SpriteSpecifier.Rsi(Seed.PlantRsi, $"stage-{growthStage}"));
_lastProduce = Age; _lastProduce = Age;
} }
else else
{ {
_appearanceComponent.SetData(PlantHolderVisuals.Plant, new SpriteSpecifier.Rsi(Seed.PlantRsi,$"stage-{Seed.GrowthStages}")); _appearanceComponent.SetData(PlantHolderVisuals.Plant,
new SpriteSpecifier.Rsi(Seed.PlantRsi, $"stage-{Seed.GrowthStages}"));
} }
} }
else else
@@ -617,7 +616,9 @@ namespace Content.Server.Botany.Components
if (!DrawWarnings) return; if (!DrawWarnings) return;
_appearanceComponent.SetData(PlantHolderVisuals.WaterLight, WaterLevel <= 10); _appearanceComponent.SetData(PlantHolderVisuals.WaterLight, WaterLevel <= 10);
_appearanceComponent.SetData(PlantHolderVisuals.NutritionLight, NutritionLevel <= 2); _appearanceComponent.SetData(PlantHolderVisuals.NutritionLight, NutritionLevel <= 2);
_appearanceComponent.SetData(PlantHolderVisuals.AlertLight, WeedLevel >= 5 || PestLevel >= 5 || Toxins >= 40 || ImproperHeat || ImproperLight || ImproperPressure || _missingGas > 0); _appearanceComponent.SetData(PlantHolderVisuals.AlertLight,
WeedLevel >= 5 || PestLevel >= 5 || Toxins >= 40 || ImproperHeat || ImproperLight || ImproperPressure ||
_missingGas > 0);
_appearanceComponent.SetData(PlantHolderVisuals.HarvestLight, Harvest); _appearanceComponent.SetData(PlantHolderVisuals.HarvestLight, Harvest);
} }
@@ -644,7 +645,7 @@ namespace Content.Server.Botany.Components
var user = eventArgs.User; var user = eventArgs.User;
var usingItem = eventArgs.Using; var usingItem = eventArgs.Using;
if (usingItem == null || usingItem.Deleted || !EntitySystem.Get<ActionBlockerSystem>().CanInteract(user)) if (usingItem.Deleted || !EntitySystem.Get<ActionBlockerSystem>().CanInteract(user))
return false; return false;
if (usingItem.TryGetComponent(out SeedComponent? seeds)) if (usingItem.TryGetComponent(out SeedComponent? seeds))
@@ -659,8 +660,8 @@ namespace Content.Server.Botany.Components
} }
user.PopupMessageCursor(Loc.GetString("plant-holder-component-plant-success-message", user.PopupMessageCursor(Loc.GetString("plant-holder-component-plant-success-message",
("seedName", seeds.Seed.SeedName), ("seedName", seeds.Seed.SeedName),
("seedNoun", seeds.Seed.SeedNoun))); ("seedNoun", seeds.Seed.SeedNoun)));
Seed = seeds.Seed; Seed = seeds.Seed;
Dead = false; Dead = false;
@@ -676,7 +677,8 @@ namespace Content.Server.Botany.Components
return true; return true;
} }
user.PopupMessageCursor(Loc.GetString("plant-holder-component-already-seeded-message", ("name", Owner.Name))); user.PopupMessageCursor(Loc.GetString("plant-holder-component-already-seeded-message",
("name", Owner.Name)));
return false; return false;
} }
@@ -684,8 +686,10 @@ namespace Content.Server.Botany.Components
{ {
if (WeedLevel > 0) if (WeedLevel > 0)
{ {
user.PopupMessageCursor(Loc.GetString("plant-holder-component-remove-weeds-message",("name", Owner.Name))); user.PopupMessageCursor(Loc.GetString("plant-holder-component-remove-weeds-message",
user.PopupMessageOtherClients(Loc.GetString("plant-holder-component-remove-weeds-others-message",("otherName", user.Name))); ("name", Owner.Name)));
user.PopupMessageOtherClients(Loc.GetString("plant-holder-component-remove-weeds-others-message",
("otherName", user.Name)));
WeedLevel = 0; WeedLevel = 0;
UpdateSprite(); UpdateSprite();
} }
@@ -701,8 +705,10 @@ namespace Content.Server.Botany.Components
{ {
if (Seed != null) if (Seed != null)
{ {
user.PopupMessageCursor(Loc.GetString("plant-holder-component-remove-plant-message",("name", Owner.Name))); user.PopupMessageCursor(Loc.GetString("plant-holder-component-remove-plant-message",
user.PopupMessageOtherClients(Loc.GetString("plant-holder-component-remove-plant-others-message",("name", user.Name))); ("name", Owner.Name)));
user.PopupMessageOtherClients(Loc.GetString("plant-holder-component-remove-plant-others-message",
("name", user.Name)));
RemovePlant(); RemovePlant();
} }
else else
@@ -713,31 +719,38 @@ namespace Content.Server.Botany.Components
return true; return true;
} }
if (usingItem.TryGetComponent(out ISolutionInteractionsComponent? solution) && solution.CanDrain) var solutionSystem = EntitySystem.Get<SolutionContainerSystem>();
if (solutionSystem.TryGetDrainableSolution(usingItem.Uid, out var solution)
&& solutionSystem.TryGetSolution(Owner, SoilSolutionName, out var targetSolution))
{ {
var amount = ReagentUnit.New(5); var amount = ReagentUnit.New(5);
var sprayed = false; var sprayed = false;
var targetEntity = Owner.Uid;
var solutionEntity = usingItem.Uid;
if (usingItem.TryGetComponent(out SprayComponent? spray)) if (usingItem.TryGetComponent(out SprayComponent? spray))
{ {
sprayed = true; sprayed = true;
amount = ReagentUnit.New(1); amount = ReagentUnit.New(1);
SoundSystem.Play(Filter.Pvs(usingItem), spray.SpraySound.GetSound(), usingItem, AudioHelpers.WithVariation(0.125f)); SoundSystem.Play(Filter.Pvs(usingItem), spray.SpraySound.GetSound(), usingItem,
AudioHelpers.WithVariation(0.125f));
} }
var split = solution.Drain(amount); var split = solutionSystem.Drain(solutionEntity, solution, amount);
if (split.TotalVolume == 0) if (split.TotalVolume == 0)
{ {
user.PopupMessageCursor(Loc.GetString("plant-holder-component-empty-message",("owner", usingItem))); user.PopupMessageCursor(Loc.GetString("plant-holder-component-empty-message",
("owner", usingItem)));
return true; return true;
} }
user.PopupMessageCursor(Loc.GetString(sprayed ? "plant-holder-component-spray-message" : "plant-holder-component-transfer-message", user.PopupMessageCursor(Loc.GetString(
("owner",Owner), sprayed ? "plant-holder-component-spray-message" : "plant-holder-component-transfer-message",
("amount",split.TotalVolume))); ("owner", Owner),
("amount", split.TotalVolume)));
_solutionContainer?.TryAddSolution(split); solutionSystem.TryAddSolution(targetEntity, targetSolution, split);
ForceUpdateByExternalCause(); ForceUpdateByExternalCause();
@@ -766,7 +779,8 @@ namespace Content.Server.Botany.Components
var seed = Seed.SpawnSeedPacket(user.Transform.Coordinates); var seed = Seed.SpawnSeedPacket(user.Transform.Coordinates);
seed.RandomOffset(0.25f); seed.RandomOffset(0.25f);
user.PopupMessageCursor(Loc.GetString("plant-holder-component-take-sample-message", ("seedName", Seed.DisplayName))); user.PopupMessageCursor(Loc.GetString("plant-holder-component-take-sample-message",
("seedName", Seed.DisplayName)));
Health -= (_random.Next(3, 5) * 10); Health -= (_random.Next(3, 5) * 10);
if (_random.Prob(0.3f)) if (_random.Prob(0.3f))
@@ -787,17 +801,18 @@ namespace Content.Server.Botany.Components
if (usingItem.HasComponent<ProduceComponent>()) if (usingItem.HasComponent<ProduceComponent>())
{ {
user.PopupMessageCursor(Loc.GetString("plant-holder-component-compost-message", user.PopupMessageCursor(Loc.GetString("plant-holder-component-compost-message",
("owner", Owner), ("owner", Owner),
("usingItem", usingItem))); ("usingItem", usingItem)));
user.PopupMessageOtherClients(Loc.GetString("plant-holder-component-compost-others-message", user.PopupMessageOtherClients(Loc.GetString("plant-holder-component-compost-others-message",
("user",user), ("user", user),
("usingItem", usingItem), ("usingItem", usingItem),
("owner", Owner))); ("owner", Owner)));
if (usingItem.TryGetComponent(out SolutionContainerComponent? solution2)) if (solutionSystem.TryGetSolution(usingItem, ProduceComponent.SolutionName, out var solution2))
{ {
// This deliberately discards overfill. // This deliberately discards overfill.
_solutionContainer?.TryAddSolution(solution2.SplitSolution(solution2.Solution.TotalVolume)); solutionSystem.TryAddSolution(usingItem.Uid, solution2,
solutionSystem.SplitSolution(usingItem.Uid, solution2, solution2.TotalVolume));
ForceUpdateByExternalCause(); ForceUpdateByExternalCause();
} }
@@ -834,14 +849,17 @@ namespace Content.Server.Botany.Components
else if (!Dead) else if (!Dead)
{ {
message.AddMarkup(Loc.GetString("plant-holder-component-something-already-growing-message", message.AddMarkup(Loc.GetString("plant-holder-component-something-already-growing-message",
("seedName", Seed.DisplayName), ("seedName", Seed.DisplayName),
("toBeForm", Seed.DisplayName.EndsWith('s') ? "are" : "is")) ("toBeForm", Seed.DisplayName.EndsWith('s') ? "are" : "is"))
+ "\n"); + "\n");
if(Health <= Seed.Endurance / 2) if (Health <= Seed.Endurance / 2)
message.AddMarkup(Loc.GetString("plant-holder-component-something-already-growing-low-health-message", message.AddMarkup(Loc.GetString(
("healthState", Loc.GetString(Age > Seed.Lifespan ? "plant-holder-component-plant-old-adjective" : "plant-holder-component-something-already-growing-low-health-message",
"plant-holder-component-plant-unhealthy-adjective"))) ("healthState",
Loc.GetString(Age > Seed.Lifespan
? "plant-holder-component-plant-old-adjective"
: "plant-holder-component-plant-unhealthy-adjective")))
+ "\n"); + "\n");
} }
else else
@@ -849,30 +867,32 @@ namespace Content.Server.Botany.Components
message.AddMarkup(Loc.GetString("plant-holder-component-dead-plant-matter-message") + "\n"); message.AddMarkup(Loc.GetString("plant-holder-component-dead-plant-matter-message") + "\n");
} }
if(WeedLevel >= 5) if (WeedLevel >= 5)
message.AddMarkup(Loc.GetString("plant-holder-component-weed-high-level-message") + "\n"); message.AddMarkup(Loc.GetString("plant-holder-component-weed-high-level-message") + "\n");
if(PestLevel >= 5) if (PestLevel >= 5)
message.AddMarkup(Loc.GetString("plant-holder-component-pest-high-level-message") + "\n"); message.AddMarkup(Loc.GetString("plant-holder-component-pest-high-level-message") + "\n");
message.AddMarkup(Loc.GetString($"plant-holder-component-water-level-message",("waterLevel", (int)WaterLevel)) + "\n"); message.AddMarkup(Loc.GetString($"plant-holder-component-water-level-message",
message.AddMarkup(Loc.GetString($"plant-holder-component-nutrient-level-message", ("nutritionLevel", (int)NutritionLevel)) + "\n"); ("waterLevel", (int) WaterLevel)) + "\n");
message.AddMarkup(Loc.GetString($"plant-holder-component-nutrient-level-message",
("nutritionLevel", (int) NutritionLevel)) + "\n");
if (DrawWarnings) if (DrawWarnings)
{ {
if(Toxins > 40f) if (Toxins > 40f)
message.AddMarkup(Loc.GetString("plant-holder-component-toxins-high-warning") + "\n"); message.AddMarkup(Loc.GetString("plant-holder-component-toxins-high-warning") + "\n");
if(ImproperLight) if (ImproperLight)
message.AddMarkup(Loc.GetString("plant-holder-component-light-improper-warning") + "\n"); message.AddMarkup(Loc.GetString("plant-holder-component-light-improper-warning") + "\n");
if(ImproperHeat) if (ImproperHeat)
message.AddMarkup(Loc.GetString("plant-holder-component-heat-improper-warning") + "\n"); message.AddMarkup(Loc.GetString("plant-holder-component-heat-improper-warning") + "\n");
if(ImproperPressure) if (ImproperPressure)
message.AddMarkup(Loc.GetString("plant-holder-component-pressure-improper-warning") + "\n"); message.AddMarkup(Loc.GetString("plant-holder-component-pressure-improper-warning") + "\n");
if(_missingGas > 0) if (_missingGas > 0)
message.AddMarkup(Loc.GetString("plant-holder-component-gas-missing-warning") + "\n"); message.AddMarkup(Loc.GetString("plant-holder-component-gas-missing-warning") + "\n");
} }
} }

View File

@@ -1,8 +1,9 @@
using Content.Server.Chemistry.Components; using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Chemistry.Reagent; using Content.Shared.Chemistry.Reagent;
using Robust.Server.GameObjects; using Robust.Server.GameObjects;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.IoC; using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Maths; using Robust.Shared.Maths;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
using Robust.Shared.Serialization; using Robust.Shared.Serialization;
@@ -15,9 +16,9 @@ namespace Content.Server.Botany.Components
public class ProduceComponent : Component, ISerializationHooks public class ProduceComponent : Component, ISerializationHooks
{ {
public override string Name => "Produce"; public override string Name => "Produce";
public const string SolutionName = "produce";
[DataField("seed")] [DataField("seed")] private string? _seedName;
private string? _seedName;
[ViewVariables] [ViewVariables]
public Seed? Seed public Seed? Seed
@@ -39,18 +40,22 @@ namespace Content.Server.Botany.Components
sprite.LayerSetState(0, Seed.PlantIconState); sprite.LayerSetState(0, Seed.PlantIconState);
} }
var solutionContainer = Owner.EnsureComponent<SolutionContainerComponent>(); EntitySystem.Get<SolutionContainerSystem>().RemoveAllSolution(Owner.Uid);
var solutionContainer = EntitySystem.Get<SolutionContainerSystem>().EnsureSolution(Owner, SolutionName);
solutionContainer.RemoveAllSolution(); if (solutionContainer == null)
{
Logger.Warning($"No solution container found in {nameof(ProduceComponent)}.");
return;
}
foreach (var (chem, quantity) in Seed.Chemicals) foreach (var (chem, quantity) in Seed.Chemicals)
{ {
var amount = ReagentUnit.New(quantity.Min); var amount = ReagentUnit.New(quantity.Min);
if(quantity.PotencyDivisor > 0 && Potency > 0) if (quantity.PotencyDivisor > 0 && Potency > 0)
amount += ReagentUnit.New(Potency/quantity.PotencyDivisor); amount += ReagentUnit.New(Potency / quantity.PotencyDivisor);
amount = ReagentUnit.New((int) MathHelper.Clamp(amount.Float(), quantity.Min, quantity.Max)); amount = ReagentUnit.New((int) MathHelper.Clamp(amount.Float(), quantity.Min, quantity.Max));
solutionContainer.MaxVolume += amount; solutionContainer.MaxVolume += amount;
solutionContainer.Solution.AddReagent(chem, amount); solutionContainer.AddReagent(chem, amount);
} }
} }
} }

View File

@@ -6,10 +6,10 @@ using Content.Server.Items;
using Content.Server.Power.Components; using Content.Server.Power.Components;
using Content.Server.UserInterface; using Content.Server.UserInterface;
using Content.Shared.ActionBlocker; using Content.Shared.ActionBlocker;
using Content.Shared.Chemistry;
using Content.Shared.Chemistry.Components; using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.Components.SolutionManager;
using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Chemistry.Reagent; using Content.Shared.Chemistry.Reagent;
using Content.Shared.Chemistry.Solution;
using Content.Shared.Interaction; using Content.Shared.Interaction;
using Content.Shared.Notification.Managers; using Content.Shared.Notification.Managers;
using Content.Shared.Random.Helpers; using Content.Shared.Random.Helpers;
@@ -35,19 +35,31 @@ namespace Content.Server.Chemistry.Components
[RegisterComponent] [RegisterComponent]
[ComponentReference(typeof(IActivate))] [ComponentReference(typeof(IActivate))]
[ComponentReference(typeof(IInteractUsing))] [ComponentReference(typeof(IInteractUsing))]
public class ChemMasterComponent : SharedChemMasterComponent, IActivate, IInteractUsing, ISolutionChange public class ChemMasterComponent : SharedChemMasterComponent, IActivate, IInteractUsing
{ {
[ViewVariables] private ContainerSlot _beakerContainer = default!; [ViewVariables]
[ViewVariables] private bool HasBeaker => _beakerContainer.ContainedEntity != null; private ContainerSlot _beakerContainer = default!;
[ViewVariables] private bool _bufferModeTransfer = true;
[ViewVariables] private bool Powered => !Owner.TryGetComponent(out ApcPowerReceiverComponent? receiver) || receiver.Powered; [ViewVariables]
private bool HasBeaker => _beakerContainer.ContainedEntity != null;
[ViewVariables] private readonly Solution BufferSolution = new(); [ViewVariables]
private bool _bufferModeTransfer = true;
[ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(ChemMasterUiKey.Key); [ViewVariables]
private bool Powered => !Owner.TryGetComponent(out ApcPowerReceiverComponent? receiver) || receiver.Powered;
[ViewVariables]
private Solution BufferSolution => _bufferSolution ??= EntitySystem.Get<SolutionContainerSystem>().EnsureSolution(Owner, SolutionName);
private Solution? _bufferSolution;
[ViewVariables]
private BoundUserInterface? UserInterface => Owner.GetUIOrNull(ChemMasterUiKey.Key);
[DataField("clickSound")]
private SoundSpecifier _clickSound = new SoundPathSpecifier("/Audio/Machines/machine_switch.ogg");
[DataField("clickSound")] private SoundSpecifier _clickSound = new SoundPathSpecifier("/Audio/Machines/machine_switch.ogg");
/// <summary> /// <summary>
/// Called once per instance of this component. Gets references to any other components needed /// Called once per instance of this component. Gets references to any other components needed
@@ -65,8 +77,7 @@ namespace Content.Server.Chemistry.Components
_beakerContainer = _beakerContainer =
ContainerHelpers.EnsureContainer<ContainerSlot>(Owner, $"{Name}-reagentContainerContainer"); ContainerHelpers.EnsureContainer<ContainerSlot>(Owner, $"{Name}-reagentContainerContainer");
//BufferSolution = Owner.BufferSolution _bufferSolution = EntitySystem.Get<SolutionContainerSystem>().EnsureSolution(Owner, SolutionName);
BufferSolution.RemoveAllSolution();
UpdateUserInterface(); UpdateUserInterface();
} }
@@ -76,13 +87,13 @@ namespace Content.Server.Chemistry.Components
base.HandleMessage(message, component); base.HandleMessage(message, component);
switch (message) switch (message)
{ {
case PowerChangedMessage powerChanged: case PowerChangedMessage:
OnPowerChanged(powerChanged); OnPowerChanged();
break; break;
} }
} }
private void OnPowerChanged(PowerChangedMessage e) private void OnPowerChanged()
{ {
UpdateUserInterface(); UpdateUserInterface();
} }
@@ -140,6 +151,7 @@ namespace Content.Server.Chemistry.Components
/// Checks whether the player entity is able to use the chem master. /// Checks whether the player entity is able to use the chem master.
/// </summary> /// </summary>
/// <param name="playerEntity">The player entity.</param> /// <param name="playerEntity">The player entity.</param>
/// <param name="needsPower">whether the device requires power</param>
/// <returns>Returns true if the entity can use the chem master, and false if it cannot.</returns> /// <returns>Returns true if the entity can use the chem master, and false if it cannot.</returns>
private bool PlayerCanUseChemMaster(IEntity? playerEntity, bool needsPower = true) private bool PlayerCanUseChemMaster(IEntity? playerEntity, bool needsPower = true)
{ {
@@ -166,25 +178,30 @@ namespace Content.Server.Chemistry.Components
private ChemMasterBoundUserInterfaceState GetUserInterfaceState() private ChemMasterBoundUserInterfaceState GetUserInterfaceState()
{ {
var beaker = _beakerContainer.ContainedEntity; var beaker = _beakerContainer.ContainedEntity;
if (beaker == null) EntitySystem.Get<SolutionContainerSystem>().TryGetSolution(beaker, SolutionName, out var beakerSolution);
// TODO this is just a guess
if (beaker == null || beakerSolution == null)
{ {
return new ChemMasterBoundUserInterfaceState(Powered, false, ReagentUnit.New(0), ReagentUnit.New(0), return new ChemMasterBoundUserInterfaceState(Powered, false, ReagentUnit.New(0), ReagentUnit.New(0),
"", Owner.Name, new List<Solution.ReagentQuantity>(), BufferSolution.Contents, _bufferModeTransfer, BufferSolution.TotalVolume); "", Owner.Name, new List<Solution.ReagentQuantity>(), BufferSolution.Contents, _bufferModeTransfer,
BufferSolution.TotalVolume);
} }
var solution = beaker.GetComponent<SolutionContainerComponent>();
return new ChemMasterBoundUserInterfaceState(Powered, true, solution.CurrentVolume, solution.MaxVolume, return new ChemMasterBoundUserInterfaceState(Powered, true, beakerSolution.CurrentVolume,
beaker.Name, Owner.Name, solution.ReagentList, BufferSolution.Contents, _bufferModeTransfer, BufferSolution.TotalVolume); beakerSolution.MaxVolume,
beaker.Name, Owner.Name, beakerSolution.Contents, BufferSolution.Contents, _bufferModeTransfer,
BufferSolution.TotalVolume);
} }
private void UpdateUserInterface() public void UpdateUserInterface()
{ {
var state = GetUserInterfaceState(); var state = GetUserInterfaceState();
UserInterface?.SetState(state); UserInterface?.SetState(state);
} }
/// <summary> /// <summary>
/// If this component contains an entity with a <see cref="SolutionContainerComponent"/>, eject it. /// If this component contains an entity with a <see cref="Solution"/>, eject it.
/// Tries to eject into user's hands first, then ejects onto chem master if both hands are full. /// Tries to eject into user's hands first, then ejects onto chem master if both hands are full.
/// </summary> /// </summary>
private void TryEject(IEntity user) private void TryEject(IEntity user)
@@ -194,13 +211,14 @@ namespace Content.Server.Chemistry.Components
var beaker = _beakerContainer.ContainedEntity; var beaker = _beakerContainer.ContainedEntity;
if(beaker is null) if (beaker is null)
return; return;
_beakerContainer.Remove(beaker); _beakerContainer.Remove(beaker);
UpdateUserInterface(); UpdateUserInterface();
if(!user.TryGetComponent<HandsComponent>(out var hands) || !beaker.TryGetComponent<ItemComponent>(out var item)) if (!user.TryGetComponent<HandsComponent>(out var hands) ||
!beaker.TryGetComponent<ItemComponent>(out var item))
return; return;
if (hands.CanPutInHand(item)) if (hands.CanPutInHand(item))
hands.PutInHand(item); hands.PutInHand(item);
@@ -211,10 +229,12 @@ namespace Content.Server.Chemistry.Components
if (!HasBeaker && _bufferModeTransfer) return; if (!HasBeaker && _bufferModeTransfer) return;
var beaker = _beakerContainer.ContainedEntity; var beaker = _beakerContainer.ContainedEntity;
if(beaker is null) if (beaker is null)
return;
if (!EntitySystem.Get<SolutionContainerSystem>().TryGetSolution(beaker, SolutionName, out var beakerSolution))
return; return;
var beakerSolution = beaker.GetComponent<SolutionContainerComponent>();
if (isBuffer) if (isBuffer)
{ {
foreach (var reagent in BufferSolution.Contents) foreach (var reagent in BufferSolution.Contents)
@@ -222,30 +242,33 @@ namespace Content.Server.Chemistry.Components
if (reagent.ReagentId == id) if (reagent.ReagentId == id)
{ {
ReagentUnit actualAmount; ReagentUnit actualAmount;
if (amount == ReagentUnit.New(-1)) //amount is ReagentUnit.New(-1) when the client sends a message requesting to remove all solution from the container if (
amount == ReagentUnit
.New(-1)) //amount is ReagentUnit.New(-1) when the client sends a message requesting to remove all solution from the container
{ {
actualAmount = ReagentUnit.Min(reagent.Quantity, beakerSolution.EmptyVolume); actualAmount = ReagentUnit.Min(reagent.Quantity, beakerSolution.AvailableVolume);
} }
else else
{ {
actualAmount = ReagentUnit.Min(reagent.Quantity, amount, beakerSolution.EmptyVolume); actualAmount = ReagentUnit.Min(reagent.Quantity, amount, beakerSolution.AvailableVolume);
} }
BufferSolution.RemoveReagent(id, actualAmount); BufferSolution.RemoveReagent(id, actualAmount);
if (_bufferModeTransfer) if (_bufferModeTransfer)
{ {
beakerSolution.TryAddReagent(id, actualAmount, out var _); EntitySystem.Get<SolutionContainerSystem>()
.TryAddReagent(beaker.Uid, beakerSolution, id, actualAmount, out var _);
// beakerSolution.Solution.AddReagent(id, actualAmount); // beakerSolution.Solution.AddReagent(id, actualAmount);
} }
break; break;
} }
} }
} }
else else
{ {
foreach (var reagent in beakerSolution.Solution.Contents) foreach (var reagent in beakerSolution.Contents)
{ {
if (reagent.ReagentId == id) if (reagent.ReagentId == id)
{ {
@@ -258,7 +281,8 @@ namespace Content.Server.Chemistry.Components
{ {
actualAmount = ReagentUnit.Min(reagent.Quantity, amount); actualAmount = ReagentUnit.Min(reagent.Quantity, amount);
} }
beakerSolution.TryRemoveReagent(id, actualAmount);
EntitySystem.Get<SolutionContainerSystem>().TryRemoveReagent(beaker.Uid, beakerSolution, id, actualAmount);
BufferSolution.AddReagent(id, actualAmount); BufferSolution.AddReagent(id, actualAmount);
break; break;
} }
@@ -285,9 +309,9 @@ namespace Content.Server.Chemistry.Components
var bottle = Owner.EntityManager.SpawnEntity("ChemistryEmptyBottle01", Owner.Transform.Coordinates); var bottle = Owner.EntityManager.SpawnEntity("ChemistryEmptyBottle01", Owner.Transform.Coordinates);
var bufferSolution = BufferSolution.SplitSolution(actualVolume); var bufferSolution = BufferSolution.SplitSolution(actualVolume);
var bottleSolution = EntitySystem.Get<SolutionContainerSystem>().EnsureSolution(bottle, "bottle");
bottle.TryGetComponent<SolutionContainerComponent>(out var bottleSolution); EntitySystem.Get<SolutionContainerSystem>().TryAddSolution(bottle.Uid, bottleSolution, bufferSolution);
bottleSolution?.TryAddSolution(bufferSolution);
//Try to give them the bottle //Try to give them the bottle
if (user.TryGetComponent<HandsComponent>(out var hands) && if (user.TryGetComponent<HandsComponent>(out var hands) &&
@@ -305,7 +329,6 @@ namespace Content.Server.Chemistry.Components
//Give it an offset //Give it an offset
bottle.RandomOffset(0.2f); bottle.RandomOffset(0.2f);
} }
} }
else //Pills else //Pills
{ {
@@ -320,8 +343,8 @@ namespace Content.Server.Chemistry.Components
var bufferSolution = BufferSolution.SplitSolution(actualVolume); var bufferSolution = BufferSolution.SplitSolution(actualVolume);
pill.TryGetComponent<SolutionContainerComponent>(out var pillSolution); var pillSolution = EntitySystem.Get<SolutionContainerSystem>().EnsureSolution(pill, "pill");
pillSolution?.TryAddSolution(bufferSolution); EntitySystem.Get<SolutionContainerSystem>().TryAddSolution(pill.Uid, pillSolution, bufferSolution);
//Try to give them the bottle //Try to give them the bottle
if (user.TryGetComponent<HandsComponent>(out var hands) && if (user.TryGetComponent<HandsComponent>(out var hands) &&
@@ -332,7 +355,6 @@ namespace Content.Server.Chemistry.Components
hands.PutInHand(item); hands.PutInHand(item);
continue; continue;
} }
} }
//Put it on the floor //Put it on the floor
@@ -371,7 +393,7 @@ namespace Content.Server.Chemistry.Components
/// <summary> /// <summary>
/// Called when you click the owner entity with something in your active hand. If the entity in your hand /// Called when you click the owner entity with something in your active hand. If the entity in your hand
/// contains a <see cref="SolutionContainerComponent"/>, if you have hands, and if the chem master doesn't already /// contains a <see cref="Solution"/>, if you have hands, and if the chem master doesn't already
/// hold a container, it will be added to the chem master. /// hold a container, it will be added to the chem master.
/// </summary> /// </summary>
/// <param name="args">Data relevant to the event such as the actor which triggered it.</param> /// <param name="args">Data relevant to the event such as the actor which triggered it.</param>
@@ -391,16 +413,18 @@ namespace Content.Server.Chemistry.Components
} }
var activeHandEntity = hands.GetActiveHand.Owner; var activeHandEntity = hands.GetActiveHand.Owner;
if (activeHandEntity.TryGetComponent<SolutionContainerComponent>(out var solution)) if (activeHandEntity.HasComponent<SolutionContainerManagerComponent>())
{ {
if (HasBeaker) if (HasBeaker)
{ {
Owner.PopupMessage(args.User, Loc.GetString("chem-master-component-has-beaker-already-message")); Owner.PopupMessage(args.User, Loc.GetString("chem-master-component-has-beaker-already-message"));
} }
else if (!solution.CanUseWithChemDispenser) else if (!activeHandEntity.HasComponent<FitsInDispenserComponent>())
{ {
//If it can't fit in the chem master, don't put it in. For example, buckets and mop buckets can't fit. //If it can't fit in the chem master, don't put it in. For example, buckets and mop buckets can't fit.
Owner.PopupMessage(args.User, Loc.GetString("chem-master-component-container-too-large-message",("container", activeHandEntity))); Owner.PopupMessage(args.User,
Loc.GetString("chem-master-component-container-too-large-message",
("container", activeHandEntity)));
} }
else else
{ {
@@ -410,14 +434,13 @@ namespace Content.Server.Chemistry.Components
} }
else else
{ {
Owner.PopupMessage(args.User, Loc.GetString("chem-master-component-cannot-put-entity-message", ("entity", activeHandEntity))); Owner.PopupMessage(args.User,
Loc.GetString("chem-master-component-cannot-put-entity-message", ("entity", activeHandEntity)));
} }
return true; return true;
} }
void ISolutionChange.SolutionChanged(SolutionChangeEventArgs eventArgs) => UpdateUserInterface();
private void ClickSound() private void ClickSound()
{ {
SoundSystem.Play(Filter.Pvs(Owner), _clickSound.GetSound(), Owner, AudioParams.Default.WithVolume(-2f)); SoundSystem.Play(Filter.Pvs(Owner), _clickSound.GetSound(), Owner, AudioParams.Default.WithVolume(-2f));
@@ -446,6 +469,5 @@ namespace Content.Server.Chemistry.Components
component.TryEject(user); component.TryEject(user);
} }
} }
} }
} }

View File

@@ -1,6 +1,8 @@
using Content.Server.Body.Circulatory; using Content.Server.Body.Circulatory;
using Content.Server.Inventory.Components; using Content.Server.Inventory.Components;
using Content.Server.Items; using Content.Server.Items;
using Content.Shared.Chemistry;
using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Chemistry.Reagent; using Content.Shared.Chemistry.Reagent;
using Content.Shared.Foam; using Content.Shared.Foam;
using Content.Shared.Inventory; using Content.Shared.Inventory;
@@ -15,21 +17,22 @@ namespace Content.Server.Chemistry.Components
public class FoamSolutionAreaEffectComponent : SolutionAreaEffectComponent public class FoamSolutionAreaEffectComponent : SolutionAreaEffectComponent
{ {
public override string Name => "FoamSolutionAreaEffect"; public override string Name => "FoamSolutionAreaEffect";
public static string SolutionName = "foam";
[DataField("foamedMetalPrototype")] private string? _foamedMetalPrototype; [DataField("foamedMetalPrototype")] private string? _foamedMetalPrototype;
protected override void UpdateVisuals() protected override void UpdateVisuals()
{ {
if (Owner.TryGetComponent(out AppearanceComponent? appearance) && if (Owner.TryGetComponent(out AppearanceComponent? appearance) &&
SolutionContainerComponent != null) EntitySystem.Get<SolutionContainerSystem>().TryGetSolution(Owner, SolutionName, out var solution))
{ {
appearance.SetData(FoamVisuals.Color, SolutionContainerComponent.Color.WithAlpha(0.80f)); appearance.SetData(FoamVisuals.Color, solution.Color.WithAlpha(0.80f));
} }
} }
protected override void ReactWithEntity(IEntity entity, double solutionFraction) protected override void ReactWithEntity(IEntity entity, double solutionFraction)
{ {
if (SolutionContainerComponent == null) if (!EntitySystem.Get<SolutionContainerSystem>().TryGetSolution(Owner, SolutionName, out var solution))
return; return;
if (!entity.TryGetComponent(out BloodstreamComponent? bloodstream)) if (!entity.TryGetComponent(out BloodstreamComponent? bloodstream))
@@ -53,8 +56,9 @@ namespace Content.Server.Chemistry.Components
} }
} }
var cloneSolution = SolutionContainerComponent.Solution.Clone(); var cloneSolution = solution.Clone();
var transferAmount = ReagentUnit.Min(cloneSolution.TotalVolume * solutionFraction * (1 - protection), bloodstream.EmptyVolume); var transferAmount = ReagentUnit.Min(cloneSolution.TotalVolume * solutionFraction * (1 - protection),
bloodstream.EmptyVolume);
var transferSolution = cloneSolution.SplitSolution(transferAmount); var transferSolution = cloneSolution.SplitSolution(transferAmount);
bloodstream.TryTransferSolution(transferSolution); bloodstream.TryTransferSolution(transferSolution);
@@ -68,12 +72,14 @@ namespace Content.Server.Chemistry.Components
{ {
appearance.SetData(FoamVisuals.State, true); appearance.SetData(FoamVisuals.State, true);
} }
Owner.SpawnTimer(600, () => Owner.SpawnTimer(600, () =>
{ {
if (!string.IsNullOrEmpty(_foamedMetalPrototype)) if (!string.IsNullOrEmpty(_foamedMetalPrototype))
{ {
Owner.EntityManager.SpawnEntity(_foamedMetalPrototype, Owner.Transform.Coordinates); Owner.EntityManager.SpawnEntity(_foamedMetalPrototype, Owner.Transform.Coordinates);
} }
Owner.QueueDelete(); Owner.QueueDelete();
}); });
} }

View File

@@ -1,8 +1,8 @@
using Content.Server.Interaction.Components; using Content.Server.Interaction.Components;
using Content.Server.MobState.States; using Content.Server.MobState.States;
using Content.Server.Weapon.Melee; using Content.Server.Weapon.Melee;
using Content.Shared.Chemistry;
using Content.Shared.Chemistry.Components; using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Chemistry.Reagent; using Content.Shared.Chemistry.Reagent;
using Content.Shared.Notification.Managers; using Content.Shared.Notification.Managers;
using Content.Shared.Sound; using Content.Shared.Sound;
@@ -18,7 +18,7 @@ using Robust.Shared.ViewVariables;
namespace Content.Server.Chemistry.Components namespace Content.Server.Chemistry.Components
{ {
[RegisterComponent] [RegisterComponent]
public sealed class HyposprayComponent : SharedHyposprayComponent, ISolutionChange public sealed class HyposprayComponent : SharedHyposprayComponent
{ {
[DataField("ClumsyFailChance")] [DataField("ClumsyFailChance")]
[ViewVariables(VVAccess.ReadWrite)] [ViewVariables(VVAccess.ReadWrite)]
@@ -31,8 +31,6 @@ namespace Content.Server.Chemistry.Components
[DataField("InjectSound")] [DataField("InjectSound")]
private SoundSpecifier _injectSound = new SoundPathSpecifier("/Audio/Items/hypospray.ogg"); private SoundSpecifier _injectSound = new SoundPathSpecifier("/Audio/Items/hypospray.ogg");
[ComponentDependency] private readonly SolutionContainerComponent? _solution = default!;
protected override void Initialize() protected override void Initialize()
{ {
base.Initialize(); base.Initialize();
@@ -57,13 +55,24 @@ namespace Content.Server.Chemistry.Components
target = user; target = user;
} }
if (_solution == null || _solution.CurrentVolume == 0) var solutionsSys = EntitySystem.Get<SolutionContainerSystem>();
solutionsSys.TryGetSolution(Owner, SolutionName, out var hypoSpraySolution);
if (hypoSpraySolution == null || hypoSpraySolution.CurrentVolume == 0)
{ {
user.PopupMessageCursor(Loc.GetString("hypospray-component-empty-message")); user.PopupMessageCursor(Loc.GetString("hypospray-component-empty-message"));
return true; return true;
} }
user.PopupMessage(Loc.GetString(msgFormat ?? "hypospray-component-inject-other-message",("other", target))); if (!solutionsSys.TryGetInjectableSolution(target.Uid, out var targetSolution))
{
user.PopupMessage(user,
Loc.GetString("hypospray-cant-inject", ("target", target)));
return false;
}
user.PopupMessage(Loc.GetString(msgFormat ?? "hypospray-component-inject-other-message",
("other", target)));
if (target != user) if (target != user)
{ {
target.PopupMessage(Loc.GetString("hypospray-component-feel-prick-message")); target.PopupMessage(Loc.GetString("hypospray-component-feel-prick-message"));
@@ -74,19 +83,21 @@ namespace Content.Server.Chemistry.Components
SoundSystem.Play(Filter.Pvs(user), _injectSound.GetSound(), user); SoundSystem.Play(Filter.Pvs(user), _injectSound.GetSound(), user);
var targetSolution = target.GetComponent<SolutionContainerComponent>();
// Get transfer amount. May be smaller than _transferAmount if not enough room // Get transfer amount. May be smaller than _transferAmount if not enough room
var realTransferAmount = ReagentUnit.Min(TransferAmount, targetSolution.EmptyVolume); var realTransferAmount = ReagentUnit.Min(TransferAmount, targetSolution.AvailableVolume);
if (realTransferAmount <= 0) if (realTransferAmount <= 0)
{ {
user.PopupMessage(user, Loc.GetString("hypospray-component-transfer-already-full-message ",("owner", targetSolution.Owner))); user.PopupMessage(user,
Loc.GetString("hypospray-component-transfer-already-full-message",
("owner", target)));
return true; return true;
} }
// Move units from attackSolution to targetSolution // Move units from attackSolution to targetSolution
var removedSolution = _solution.SplitSolution(realTransferAmount); var removedSolution =
EntitySystem.Get<SolutionContainerSystem>()
.SplitSolution(Owner.Uid, hypoSpraySolution, realTransferAmount);
if (!targetSolution.CanAddSolution(removedSolution)) if (!targetSolution.CanAddSolution(removedSolution))
{ {
@@ -95,29 +106,26 @@ namespace Content.Server.Chemistry.Components
removedSolution.DoEntityReaction(target, ReactionMethod.Injection); removedSolution.DoEntityReaction(target, ReactionMethod.Injection);
targetSolution.TryAddSolution(removedSolution); EntitySystem.Get<SolutionContainerSystem>().TryAddSolution(target.Uid, targetSolution, removedSolution);
static bool EligibleEntity(IEntity entity) static bool EligibleEntity(IEntity entity)
{ {
// TODO: Does checking for BodyComponent make sense as a "can be hypospray'd" tag? // TODO: Does checking for BodyComponent make sense as a "can be hypospray'd" tag?
// In SS13 the hypospray ONLY works on mobs, NOT beakers or anything else. // In SS13 the hypospray ONLY works on mobs, NOT beakers or anything else.
return entity.HasComponent<SolutionContainerComponent>() && entity.HasComponent<MobStateComponent>();
return entity.HasComponent<SharedChemMasterComponent>()
&& entity.HasComponent<MobStateComponent>();
} }
return true; return true;
} }
void ISolutionChange.SolutionChanged(SolutionChangeEventArgs eventArgs)
{
Dirty();
}
public override ComponentState GetComponentState(ICommonSession player) public override ComponentState GetComponentState(ICommonSession player)
{ {
if (_solution == null) var solutionSys = Owner.EntityManager.EntitySysManager.GetEntitySystem<SolutionContainerSystem>();
return new HyposprayComponentState(ReagentUnit.Zero, ReagentUnit.Zero); return solutionSys.TryGetSolution(Owner, SolutionName, out var solution)
? new HyposprayComponentState(solution.CurrentVolume, solution.MaxVolume)
return new HyposprayComponentState(_solution.CurrentVolume, _solution.MaxVolume); : new HyposprayComponentState(ReagentUnit.Zero, ReagentUnit.Zero);
} }
} }
} }

View File

@@ -1,10 +1,11 @@
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Content.Server.Body.Circulatory; using Content.Server.Body.Circulatory;
using Content.Shared.Chemistry; using Content.Shared.Body.Networks;
using Content.Shared.Chemistry.Components; using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.Components.SolutionManager;
using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Chemistry.Reagent; using Content.Shared.Chemistry.Reagent;
using Content.Shared.Chemistry.Solution.Components;
using Content.Shared.Interaction; using Content.Shared.Interaction;
using Content.Shared.Interaction.Helpers; using Content.Shared.Interaction.Helpers;
using Content.Shared.Notification.Managers; using Content.Shared.Notification.Managers;
@@ -22,8 +23,10 @@ namespace Content.Server.Chemistry.Components
/// containers, and can directly inject into a mobs bloodstream. /// containers, and can directly inject into a mobs bloodstream.
/// </summary> /// </summary>
[RegisterComponent] [RegisterComponent]
public class InjectorComponent : SharedInjectorComponent, IAfterInteract, IUse, ISolutionChange public class InjectorComponent : SharedInjectorComponent, IAfterInteract, IUse
{ {
public const string SolutionName = "injector";
/// <summary> /// <summary>
/// Whether or not the injector is able to draw from containers or if it's a single use /// Whether or not the injector is able to draw from containers or if it's a single use
/// device that can only inject. /// device that can only inject.
@@ -109,47 +112,46 @@ namespace Content.Server.Chemistry.Components
if (!eventArgs.InRangeUnobstructed(ignoreInsideBlocker: true, popup: true)) if (!eventArgs.InRangeUnobstructed(ignoreInsideBlocker: true, popup: true))
return false; return false;
var solutionsSys = EntitySystem.Get<SolutionContainerSystem>();
//Make sure we have the attacking entity //Make sure we have the attacking entity
if (eventArgs.Target == null || !Owner.HasComponent<SolutionContainerComponent>()) if (eventArgs.Target == null || !Owner.HasComponent<SolutionContainerManagerComponent>())
{ {
return false; return false;
} }
var targetEntity = eventArgs.Target; var targetEntity = eventArgs.Target;
// Handle injecting/drawing for solutions // Handle injecting/drawing for solutions
if (targetEntity.TryGetComponent<ISolutionInteractionsComponent>(out var targetSolution)) if (ToggleState == InjectorToggleMode.Inject)
{ {
if (ToggleState == InjectorToggleMode.Inject) if (solutionsSys.TryGetInjectableSolution(targetEntity.Uid, out var injectableSolution))
{ {
if (targetSolution.CanInject) TryInject(targetEntity, injectableSolution, eventArgs.User);
{
TryInject(targetSolution, eventArgs.User);
}
else
{
eventArgs.User.PopupMessage(eventArgs.User,
Loc.GetString("injector-component-cannot-transfer-message", ("owner", targetSolution.Owner)));
}
} }
else if (ToggleState == InjectorToggleMode.Draw) else if (targetEntity.TryGetComponent(out BloodstreamComponent? bloodstream))
{ {
if (targetSolution.CanDraw) TryInjectIntoBloodstream(bloodstream, eventArgs.User);
{ }
TryDraw(targetSolution, eventArgs.User); else
} {
else eventArgs.User.PopupMessage(eventArgs.User,
{ Loc.GetString("injector-component-cannot-transfer-message",
eventArgs.User.PopupMessage(eventArgs.User, ("owner", eventArgs.User)));
Loc.GetString("injector-component-cannot-draw-message", ("owner", targetSolution.Owner)));
}
} }
} }
// Handle injecting into bloodstream else if (ToggleState == InjectorToggleMode.Draw)
else if (targetEntity.TryGetComponent(out BloodstreamComponent? bloodstream) &&
ToggleState == InjectorToggleMode.Inject)
{ {
TryInjectIntoBloodstream(bloodstream, eventArgs.User); if (solutionsSys.TryGetDrawableSolution(targetEntity, out var drawableSolution))
{
TryDraw(targetEntity, drawableSolution, eventArgs.User);
}
else
{
eventArgs.User.PopupMessage(eventArgs.User,
Loc.GetString("injector-component-cannot-draw-message",
("owner", eventArgs.User)));
}
} }
return true; return true;
@@ -168,10 +170,10 @@ namespace Content.Server.Chemistry.Components
private void TryInjectIntoBloodstream(BloodstreamComponent targetBloodstream, IEntity user) private void TryInjectIntoBloodstream(BloodstreamComponent targetBloodstream, IEntity user)
{ {
if (!Owner.TryGetComponent(out SolutionContainerComponent? solution) || solution.CurrentVolume == 0) if (!EntitySystem.Get<SolutionContainerSystem>()
{ .TryGetSolution(user, SharedBloodstreamComponent.DefaultSolutionName, out var bloodstream)
|| bloodstream.CurrentVolume == 0)
return; return;
}
// Get transfer amount. May be smaller than _transferAmount if not enough room // Get transfer amount. May be smaller than _transferAmount if not enough room
var realTransferAmount = ReagentUnit.Min(_transferAmount, targetBloodstream.EmptyVolume); var realTransferAmount = ReagentUnit.Min(_transferAmount, targetBloodstream.EmptyVolume);
@@ -179,61 +181,65 @@ namespace Content.Server.Chemistry.Components
if (realTransferAmount <= 0) if (realTransferAmount <= 0)
{ {
Owner.PopupMessage(user, Owner.PopupMessage(user,
Loc.GetString("injector-component-cannot-inject-message",("owner", targetBloodstream.Owner))); Loc.GetString("injector-component-cannot-inject-message", ("owner", targetBloodstream.Owner)));
return; return;
} }
// Move units from attackSolution to targetSolution // Move units from attackSolution to targetSolution
var removedSolution = solution.SplitSolution(realTransferAmount); var removedSolution =
EntitySystem.Get<SolutionContainerSystem>().SplitSolution(user.Uid, bloodstream, realTransferAmount);
if (!solution.CanAddSolution(removedSolution)) if (!bloodstream.CanAddSolution(removedSolution))
{ {
return; return;
} }
// TODO: Account for partial transfer. // TODO: Account for partial transfer.
var bloodsStreamEntity = Owner.EntityManager.GetEntity(user.Uid);
removedSolution.DoEntityReaction(bloodsStreamEntity, ReactionMethod.Injection);
removedSolution.DoEntityReaction(solution.Owner, ReactionMethod.Injection); EntitySystem.Get<SolutionContainerSystem>().TryAddSolution(user.Uid, bloodstream, removedSolution);
solution.TryAddSolution(removedSolution);
removedSolution.DoEntityReaction(targetBloodstream.Owner, ReactionMethod.Injection); removedSolution.DoEntityReaction(targetBloodstream.Owner, ReactionMethod.Injection);
Owner.PopupMessage(user, Owner.PopupMessage(user,
Loc.GetString("injector-component-inject-success-message", Loc.GetString("injector-component-inject-success-message",
("amount", removedSolution.TotalVolume), ("amount", removedSolution.TotalVolume),
("target", targetBloodstream.Owner))); ("target", targetBloodstream.Owner)));
Dirty(); Dirty();
AfterInject(); AfterInject();
} }
private void TryInject(ISolutionInteractionsComponent targetSolution, IEntity user) private void TryInject(IEntity targetEntity, Solution targetSolution, IEntity user)
{ {
if (!Owner.TryGetComponent(out SolutionContainerComponent? solution) || solution.CurrentVolume == 0) if (!EntitySystem.Get<SolutionContainerSystem>().TryGetSolution(Owner, SolutionName, out var solution)
|| solution.CurrentVolume == 0)
{ {
return; return;
} }
// Get transfer amount. May be smaller than _transferAmount if not enough room // Get transfer amount. May be smaller than _transferAmount if not enough room
var realTransferAmount = ReagentUnit.Min(_transferAmount, targetSolution.InjectSpaceAvailable); var realTransferAmount = ReagentUnit.Min(_transferAmount, targetSolution.AvailableVolume);
if (realTransferAmount <= 0) if (realTransferAmount <= 0)
{ {
Owner.PopupMessage(user, Loc.GetString("injector-component-target-already-full-message", ("target", targetSolution.Owner))); Owner.PopupMessage(user,
Loc.GetString("injector-component-target-already-full-message", ("target", targetEntity)));
return; return;
} }
// Move units from attackSolution to targetSolution // Move units from attackSolution to targetSolution
var removedSolution = solution.SplitSolution(realTransferAmount); var removedSolution = EntitySystem.Get<SolutionContainerSystem>().SplitSolution(Owner.Uid, solution, realTransferAmount);
removedSolution.DoEntityReaction(targetSolution.Owner, ReactionMethod.Injection); removedSolution.DoEntityReaction(targetEntity, ReactionMethod.Injection);
targetSolution.Inject(removedSolution); EntitySystem.Get<SolutionContainerSystem>()
.Inject(targetEntity.Uid, targetSolution, removedSolution);
Owner.PopupMessage(user, Owner.PopupMessage(user,
Loc.GetString("injector-component-transfer-success-message", Loc.GetString("injector-component-transfer-success-message",
("amount", removedSolution.TotalVolume), ("amount", removedSolution.TotalVolume),
("target", targetSolution.Owner))); ("target", targetEntity)));
Dirty(); Dirty();
AfterInject(); AfterInject();
} }
@@ -241,15 +247,27 @@ namespace Content.Server.Chemistry.Components
private void AfterInject() private void AfterInject()
{ {
// Automatically set syringe to draw after completely draining it. // Automatically set syringe to draw after completely draining it.
if (Owner.GetComponent<SolutionContainerComponent>().CurrentVolume == 0) if (EntitySystem.Get<SolutionContainerSystem>().TryGetSolution(Owner, SolutionName, out var solution)
&& solution.CurrentVolume == 0)
{ {
ToggleState = InjectorToggleMode.Draw; ToggleState = InjectorToggleMode.Draw;
} }
} }
private void TryDraw(ISolutionInteractionsComponent targetSolution, IEntity user) private void AfterDraw()
{ {
if (!Owner.TryGetComponent(out SolutionContainerComponent? solution) || solution.EmptyVolume == 0) // Automatically set syringe to inject after completely filling it.
if (EntitySystem.Get<SolutionContainerSystem>().TryGetSolution(Owner, SolutionName, out var solution)
&& solution.AvailableVolume == 0)
{
ToggleState = InjectorToggleMode.Inject;
}
}
private void TryDraw(IEntity targetEntity, Solution targetSolution, IEntity user)
{
if (!EntitySystem.Get<SolutionContainerSystem>().TryGetSolution(Owner, SolutionName, out var solution)
|| solution.AvailableVolume == 0)
{ {
return; return;
} }
@@ -259,43 +277,33 @@ namespace Content.Server.Chemistry.Components
if (realTransferAmount <= 0) if (realTransferAmount <= 0)
{ {
Owner.PopupMessage(user, Loc.GetString("injector-component-target-is-empty-message",("target", targetSolution.Owner))); Owner.PopupMessage(user,
Loc.GetString("injector-component-target-is-empty-message", ("target", targetEntity)));
return; return;
} }
// Move units from attackSolution to targetSolution // Move units from attackSolution to targetSolution
var removedSolution = targetSolution.Draw(realTransferAmount); var removedSolution = EntitySystem.Get<SolutionContainerSystem>()
.Draw(targetEntity.Uid, targetSolution, realTransferAmount);
if (!solution.TryAddSolution(removedSolution)) if (!EntitySystem.Get<SolutionContainerSystem>().TryAddSolution(targetEntity.Uid, solution, removedSolution))
{ {
return; return;
} }
Owner.PopupMessage(user, Owner.PopupMessage(user,
Loc.GetString("injector-component-draw-success-message", Loc.GetString("injector-component-draw-success-message",
("amount", removedSolution.TotalVolume), ("amount", removedSolution.TotalVolume),
("target", targetSolution.Owner))); ("target", targetEntity)));
Dirty(); Dirty();
AfterDraw(); AfterDraw();
} }
private void AfterDraw()
{
// Automatically set syringe to inject after completely filling it.
if (Owner.GetComponent<SolutionContainerComponent>().EmptyVolume == 0)
{
ToggleState = InjectorToggleMode.Inject;
}
}
void ISolutionChange.SolutionChanged(SolutionChangeEventArgs eventArgs)
{
Dirty();
}
public override ComponentState GetComponentState(ICommonSession player) public override ComponentState GetComponentState(ICommonSession player)
{ {
Owner.TryGetComponent(out SolutionContainerComponent? solution); Owner.EntityManager.EntitySysManager.GetEntitySystem<SolutionContainerSystem>()
.TryGetSolution(Owner, SolutionName, out var solution);
var currentVolume = solution?.CurrentVolume ?? ReagentUnit.Zero; var currentVolume = solution?.CurrentVolume ?? ReagentUnit.Zero;
var maxVolume = solution?.MaxVolume ?? ReagentUnit.Zero; var maxVolume = solution?.MaxVolume ?? ReagentUnit.Zero;

View File

@@ -7,10 +7,10 @@ using Content.Server.Items;
using Content.Server.Power.Components; using Content.Server.Power.Components;
using Content.Server.UserInterface; using Content.Server.UserInterface;
using Content.Shared.ActionBlocker; using Content.Shared.ActionBlocker;
using Content.Shared.Chemistry; using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.Dispenser; using Content.Shared.Chemistry.Dispenser;
using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Chemistry.Reagent; using Content.Shared.Chemistry.Reagent;
using Content.Shared.Chemistry.Solution;
using Content.Shared.Interaction; using Content.Shared.Interaction;
using Content.Shared.Notification.Managers; using Content.Shared.Notification.Managers;
using Content.Shared.Sound; using Content.Shared.Sound;
@@ -38,9 +38,10 @@ namespace Content.Server.Chemistry.Components
[RegisterComponent] [RegisterComponent]
[ComponentReference(typeof(IActivate))] [ComponentReference(typeof(IActivate))]
[ComponentReference(typeof(IInteractUsing))] [ComponentReference(typeof(IInteractUsing))]
public class ReagentDispenserComponent : SharedReagentDispenserComponent, IActivate, IInteractUsing, ISolutionChange public class ReagentDispenserComponent : SharedReagentDispenserComponent, IActivate, IInteractUsing
{ {
private static ReagentInventoryComparer _comparer = new(); private static ReagentInventoryComparer _comparer = new();
public static string SolutionName = "reagent";
[Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
@@ -52,9 +53,20 @@ namespace Content.Server.Chemistry.Components
[ViewVariables] private bool HasBeaker => _beakerContainer.ContainedEntity != null; [ViewVariables] private bool HasBeaker => _beakerContainer.ContainedEntity != null;
[ViewVariables] private ReagentUnit _dispenseAmount = ReagentUnit.New(10); [ViewVariables] private ReagentUnit _dispenseAmount = ReagentUnit.New(10);
[UsedImplicitly] [ViewVariables] private SolutionContainerComponent? Solution => _beakerContainer.ContainedEntity?.GetComponent<SolutionContainerComponent>();
[ViewVariables] private bool Powered => !Owner.TryGetComponent(out ApcPowerReceiverComponent? receiver) || receiver.Powered; [UsedImplicitly]
[ViewVariables]
private Solution? Solution
{
get
{
EntitySystem.Get<SolutionContainerSystem>().TryGetSolution(Owner, SolutionName, out var solution);
return solution;
}
}
[ViewVariables]
private bool Powered => !Owner.TryGetComponent(out ApcPowerReceiverComponent? receiver) || receiver.Powered;
[ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(ReagentDispenserUiKey.Key); [ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(ReagentDispenserUiKey.Key);
@@ -134,7 +146,7 @@ namespace Content.Server.Chemistry.Components
_ => true, _ => true,
}; };
if(!PlayerCanUseDispenser(obj.Session.AttachedEntity, needsPower)) if (!PlayerCanUseDispenser(obj.Session.AttachedEntity, needsPower))
return; return;
switch (msg.Button) switch (msg.Button)
@@ -216,25 +228,27 @@ namespace Content.Server.Chemistry.Components
private ReagentDispenserBoundUserInterfaceState GetUserInterfaceState() private ReagentDispenserBoundUserInterfaceState GetUserInterfaceState()
{ {
var beaker = _beakerContainer.ContainedEntity; var beaker = _beakerContainer.ContainedEntity;
if (beaker == null) if (beaker == null ||
!EntitySystem.Get<SolutionContainerSystem>().TryGetSolution(beaker, "beaker", out var solution))
{ {
return new ReagentDispenserBoundUserInterfaceState(Powered, false, ReagentUnit.New(0), ReagentUnit.New(0), return new ReagentDispenserBoundUserInterfaceState(Powered, false, ReagentUnit.New(0),
ReagentUnit.New(0),
string.Empty, Inventory, Owner.Name, null, _dispenseAmount); string.Empty, Inventory, Owner.Name, null, _dispenseAmount);
} }
var solution = beaker.GetComponent<SolutionContainerComponent>(); return new ReagentDispenserBoundUserInterfaceState(Powered, true, solution.CurrentVolume,
return new ReagentDispenserBoundUserInterfaceState(Powered, true, solution.CurrentVolume, solution.MaxVolume, solution.MaxVolume,
beaker.Name, Inventory, Owner.Name, solution.ReagentList.ToList(), _dispenseAmount); beaker.Name, Inventory, Owner.Name, solution.Contents.ToList(), _dispenseAmount);
} }
private void UpdateUserInterface() public void UpdateUserInterface()
{ {
var state = GetUserInterfaceState(); var state = GetUserInterfaceState();
UserInterface?.SetState(state); UserInterface?.SetState(state);
} }
/// <summary> /// <summary>
/// If this component contains an entity with a <see cref="SolutionContainerComponent"/>, eject it. /// If this component contains an entity with a <see cref="SolutionHolder"/>, eject it.
/// Tries to eject into user's hands first, then ejects onto dispenser if both hands are full. /// Tries to eject into user's hands first, then ejects onto dispenser if both hands are full.
/// </summary> /// </summary>
private void TryEject(IEntity user) private void TryEject(IEntity user)
@@ -243,46 +257,48 @@ namespace Content.Server.Chemistry.Components
return; return;
var beaker = _beakerContainer.ContainedEntity; var beaker = _beakerContainer.ContainedEntity;
if(beaker is null) if (beaker is null)
return; return;
_beakerContainer.Remove(beaker); _beakerContainer.Remove(beaker);
UpdateUserInterface(); UpdateUserInterface();
if(!user.TryGetComponent<HandsComponent>(out var hands) || !beaker.TryGetComponent<ItemComponent>(out var item)) if (!user.TryGetComponent<HandsComponent>(out var hands) ||
!beaker.TryGetComponent<ItemComponent>(out var item))
return; return;
if (hands.CanPutInHand(item)) if (hands.CanPutInHand(item))
hands.PutInHand(item); hands.PutInHand(item);
} }
/// <summary> /// <summary>
/// If this component contains an entity with a <see cref="SolutionContainerComponent"/>, remove all of it's reagents / solutions. /// If this component contains an entity with a <see cref="SolutionHolder"/>, remove all of it's reagents / solutions.
/// </summary> /// </summary>
private void TryClear() private void TryClear()
{ {
if (!HasBeaker) return; if (!HasBeaker ||
var solution = _beakerContainer.ContainedEntity?.GetComponent<SolutionContainerComponent>(); !EntitySystem.Get<SolutionContainerSystem>()
if(solution is null) .TryGetSolution(_beakerContainer.ContainedEntity, "beaker", out var solution))
return; return;
solution.RemoveAllSolution(); EntitySystem.Get<SolutionContainerSystem>().RemoveAllSolution(_beakerContainer.ContainedEntity!.Uid, solution);
UpdateUserInterface(); UpdateUserInterface();
} }
/// <summary> /// <summary>
/// If this component contains an entity with a <see cref="SolutionContainerComponent"/>, attempt to dispense the specified reagent to it. /// If this component contains an entity with a <see cref="SolutionHolder"/>, attempt to dispense the specified reagent to it.
/// </summary> /// </summary>
/// <param name="dispenseIndex">The index of the reagent in <c>Inventory</c>.</param> /// <param name="dispenseIndex">The index of the reagent in <c>Inventory</c>.</param>
private void TryDispense(int dispenseIndex) private void TryDispense(int dispenseIndex)
{ {
if (!HasBeaker) return; if (!HasBeaker) return;
var solution = _beakerContainer.ContainedEntity?.GetComponent<SolutionContainerComponent>(); if (_beakerContainer.ContainedEntity == null
if (solution is null) || !EntitySystem.Get<SolutionContainerSystem>()
return; .TryGetSolution(_beakerContainer.ContainedEntity, "beaker", out var solution)) return;
solution.TryAddReagent(Inventory[dispenseIndex].ID, _dispenseAmount, out _); EntitySystem.Get<SolutionContainerSystem>()
.TryAddReagent(_beakerContainer.ContainedEntity.Uid, solution, Inventory[dispenseIndex].ID, _dispenseAmount, out _);
UpdateUserInterface(); UpdateUserInterface();
} }
@@ -313,7 +329,7 @@ namespace Content.Server.Chemistry.Components
/// <summary> /// <summary>
/// Called when you click the owner entity with something in your active hand. If the entity in your hand /// Called when you click the owner entity with something in your active hand. If the entity in your hand
/// contains a <see cref="SolutionContainerComponent"/>, if you have hands, and if the dispenser doesn't already /// contains a <see cref="SolutionHolder"/>, if you have hands, and if the dispenser doesn't already
/// hold a container, it will be added to the dispenser. /// hold a container, it will be added to the dispenser.
/// </summary> /// </summary>
/// <param name="args">Data relevant to the event such as the actor which triggered it.</param> /// <param name="args">Data relevant to the event such as the actor which triggered it.</param>
@@ -328,18 +344,21 @@ namespace Content.Server.Chemistry.Components
if (hands.GetActiveHand == null) if (hands.GetActiveHand == null)
{ {
Owner.PopupMessage(args.User, Loc.GetString("reagent-dispenser-component-interact-using-nothing-in-hands")); Owner.PopupMessage(args.User,
Loc.GetString("reagent-dispenser-component-interact-using-nothing-in-hands"));
return false; return false;
} }
var solutionSys = EntitySystem.Get<SolutionContainerSystem>();
var activeHandEntity = hands.GetActiveHand.Owner; var activeHandEntity = hands.GetActiveHand.Owner;
if (activeHandEntity.TryGetComponent<SolutionContainerComponent>(out var solution)) if (solutionSys.TryGetSolution(activeHandEntity, "beaker", out _))
{ {
if (HasBeaker) if (HasBeaker)
{ {
Owner.PopupMessage(args.User, Loc.GetString("reagent-dispenser-component-has-container-already-message")); Owner.PopupMessage(args.User,
Loc.GetString("reagent-dispenser-component-has-container-already-message"));
} }
else if ((solution.Capabilities & SolutionContainerCaps.FitsInDispenser) == 0) else if (!solutionSys.HasFitsInDispenser(activeHandEntity))
{ {
//If it can't fit in the dispenser, don't put it in. For example, buckets and mop buckets can't fit. //If it can't fit in the dispenser, don't put it in. For example, buckets and mop buckets can't fit.
Owner.PopupMessage(args.User, Loc.GetString("reagent-dispenser-component-cannot-fit-message")); Owner.PopupMessage(args.User, Loc.GetString("reagent-dispenser-component-cannot-fit-message"));
@@ -352,14 +371,14 @@ namespace Content.Server.Chemistry.Components
} }
else else
{ {
Owner.PopupMessage(args.User, Loc.GetString("reagent-dispenser-component-cannot-put-entity-message", ("entity", activeHandEntity))); Owner.PopupMessage(args.User,
Loc.GetString("reagent-dispenser-component-cannot-put-entity-message",
("entity", activeHandEntity)));
} }
return true; return true;
} }
void ISolutionChange.SolutionChanged(SolutionChangeEventArgs eventArgs) => UpdateUserInterface();
private void ClickSound() private void ClickSound()
{ {
SoundSystem.Play(Filter.Pvs(Owner), _clickSound.GetSound(), Owner, AudioParams.Default.WithVolume(-2f)); SoundSystem.Play(Filter.Pvs(Owner), _clickSound.GetSound(), Owner, AudioParams.Default.WithVolume(-2f));

View File

@@ -1,8 +1,4 @@
using Content.Server.Notification;
using Content.Shared.Chemistry;
using Content.Shared.Chemistry.Reagent;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.Localization;
using Robust.Shared.Serialization.Manager.Attributes; using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables; using Robust.Shared.ViewVariables;
@@ -13,44 +9,18 @@ namespace Content.Server.Chemistry.Components
/// But specifically, this component deletes the entity and spawns in a new entity when the entity is exposed to a given reagent. /// But specifically, this component deletes the entity and spawns in a new entity when the entity is exposed to a given reagent.
/// </summary> /// </summary>
[RegisterComponent] [RegisterComponent]
[ComponentReference(typeof(ISolutionChange))] public class RehydratableComponent : Component
public class RehydratableComponent : Component, ISolutionChange
{ {
public override string Name => "Rehydratable"; public override string Name => "Rehydratable";
[ViewVariables] [ViewVariables]
[DataField("catalyst")] [DataField("catalyst")]
private string _catalystPrototype = "Water"; internal string CatalystPrototype = "Water";
[ViewVariables] [ViewVariables]
[DataField("target")] [DataField("target")]
private string? _targetPrototype = default!; internal string? TargetPrototype = default!;
private bool _expanding; internal bool Expanding;
void ISolutionChange.SolutionChanged(SolutionChangeEventArgs eventArgs)
{
var solution = eventArgs.Owner.GetComponent<SolutionContainerComponent>();
if (solution.Solution.GetReagentQuantity(_catalystPrototype) > ReagentUnit.Zero)
{
Expand();
}
}
// Try not to make this public if you can help it.
private void Expand()
{
if (_expanding)
{
return;
}
_expanding = true;
Owner.PopupMessageEveryone(Loc.GetString("rehydratable-component-expands-message",("owner", Owner)));
if (!string.IsNullOrEmpty(_targetPrototype))
{
var ent = Owner.EntityManager.SpawnEntity(_targetPrototype, Owner.Transform.Coordinates);
ent.Transform.AttachToGridOrMap();
}
Owner.Delete();
}
} }
} }

View File

@@ -1,7 +1,7 @@
using System.Linq; using Content.Server.Body.Circulatory;
using Content.Server.Body.Circulatory;
using Content.Server.Body.Respiratory; using Content.Server.Body.Respiratory;
using Content.Shared.Chemistry; using Content.Shared.Chemistry;
using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Chemistry.Reagent; using Content.Shared.Chemistry.Reagent;
using Content.Shared.Smoking; using Content.Shared.Smoking;
using Robust.Server.GameObjects; using Robust.Server.GameObjects;
@@ -14,19 +14,20 @@ namespace Content.Server.Chemistry.Components
public class SmokeSolutionAreaEffectComponent : SolutionAreaEffectComponent public class SmokeSolutionAreaEffectComponent : SolutionAreaEffectComponent
{ {
public override string Name => "SmokeSolutionAreaEffect"; public override string Name => "SmokeSolutionAreaEffect";
public const string SolutionName = "smoke";
protected override void UpdateVisuals() protected override void UpdateVisuals()
{ {
if (Owner.TryGetComponent(out AppearanceComponent? appearance) && if (Owner.TryGetComponent(out AppearanceComponent? appearance) &&
SolutionContainerComponent != null) EntitySystem.Get<SolutionContainerSystem>().TryGetSolution(Owner, SolutionName, out var solution))
{ {
appearance.SetData(SmokeVisuals.Color, SolutionContainerComponent.Color); appearance.SetData(SmokeVisuals.Color, solution.Color);
} }
} }
protected override void ReactWithEntity(IEntity entity, double solutionFraction) protected override void ReactWithEntity(IEntity entity, double solutionFraction)
{ {
if (SolutionContainerComponent == null) if (!EntitySystem.Get<SolutionContainerSystem>().TryGetSolution(Owner, SolutionName, out var solution))
return; return;
if (!entity.TryGetComponent(out BloodstreamComponent? bloodstream)) if (!entity.TryGetComponent(out BloodstreamComponent? bloodstream))
@@ -37,7 +38,7 @@ namespace Content.Server.Chemistry.Components
return; return;
var chemistry = EntitySystem.Get<ChemistrySystem>(); var chemistry = EntitySystem.Get<ChemistrySystem>();
var cloneSolution = SolutionContainerComponent.Solution.Clone(); var cloneSolution = solution.Clone();
var transferAmount = ReagentUnit.Min(cloneSolution.TotalVolume * solutionFraction, bloodstream.EmptyVolume); var transferAmount = ReagentUnit.Min(cloneSolution.TotalVolume * solutionFraction, bloodstream.EmptyVolume);
var transferSolution = cloneSolution.SplitSolution(transferAmount); var transferSolution = cloneSolution.SplitSolution(transferAmount);

View File

@@ -3,8 +3,9 @@ using System.Linq;
using Content.Server.Atmos.Components; using Content.Server.Atmos.Components;
using Content.Server.Coordinates.Helpers; using Content.Server.Coordinates.Helpers;
using Content.Shared.Chemistry; using Content.Shared.Chemistry;
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Chemistry.Reagent; using Content.Shared.Chemistry.Reagent;
using Content.Shared.Chemistry.Solution;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.IoC; using Robust.Shared.IoC;
using Robust.Shared.Log; using Robust.Shared.Log;
@@ -20,10 +21,10 @@ namespace Content.Server.Chemistry.Components
/// </summary> /// </summary>
public abstract class SolutionAreaEffectComponent : Component public abstract class SolutionAreaEffectComponent : Component
{ {
public const string SolutionName = "solutionArea";
[Dependency] protected readonly IMapManager MapManager = default!; [Dependency] protected readonly IMapManager MapManager = default!;
[Dependency] protected readonly IPrototypeManager PrototypeManager = default!; [Dependency] protected readonly IPrototypeManager PrototypeManager = default!;
[ComponentDependency] protected readonly SolutionContainerComponent? SolutionContainerComponent = default!;
public int Amount { get; set; } public int Amount { get; set; }
public SolutionAreaEffectInceptionComponent? Inception { get; set; } public SolutionAreaEffectInceptionComponent? Inception { get; set; }
@@ -67,10 +68,12 @@ namespace Content.Server.Chemistry.Components
var coords = Owner.Transform.Coordinates; var coords = Owner.Transform.Coordinates;
foreach (var neighbor in grid.GetInDir(coords, dir)) foreach (var neighbor in grid.GetInDir(coords, dir))
{ {
if (Owner.EntityManager.ComponentManager.TryGetComponent(neighbor, out SolutionAreaEffectComponent? comp) && comp.Inception == Inception) if (Owner.EntityManager.ComponentManager.TryGetComponent(neighbor,
out SolutionAreaEffectComponent? comp) && comp.Inception == Inception)
return; return;
if (Owner.EntityManager.ComponentManager.TryGetComponent(neighbor, out AirtightComponent? airtight) && airtight.AirBlocked) if (Owner.EntityManager.ComponentManager.TryGetComponent(neighbor,
out AirtightComponent? airtight) && airtight.AirBlocked)
return; return;
} }
@@ -82,9 +85,9 @@ namespace Content.Server.Chemistry.Components
return; return;
} }
if (SolutionContainerComponent != null) if (EntitySystem.Get<SolutionContainerSystem>().TryGetSolution(Owner, SolutionName, out var solution))
{ {
effectComponent.TryAddSolution(SolutionContainerComponent.Solution.Clone()); effectComponent.TryAddSolution(solution.Clone());
} }
effectComponent.Amount = Amount - 1; effectComponent.Amount = Amount - 1;
@@ -95,7 +98,6 @@ namespace Content.Server.Chemistry.Components
SpreadToDir(Direction.East); SpreadToDir(Direction.East);
SpreadToDir(Direction.South); SpreadToDir(Direction.South);
SpreadToDir(Direction.West); SpreadToDir(Direction.West);
} }
/// <summary> /// <summary>
@@ -120,7 +122,7 @@ namespace Content.Server.Chemistry.Components
/// with the other area effects from the inception.</param> /// with the other area effects from the inception.</param>
public void React(float averageExposures) public void React(float averageExposures)
{ {
if (SolutionContainerComponent == null) if (!EntitySystem.Get<SolutionContainerSystem>().TryGetSolution(Owner, SolutionName, out var solution))
return; return;
var chemistry = EntitySystem.Get<ChemistrySystem>(); var chemistry = EntitySystem.Get<ChemistrySystem>();
@@ -129,7 +131,7 @@ namespace Content.Server.Chemistry.Components
var solutionFraction = 1 / Math.Floor(averageExposures); var solutionFraction = 1 / Math.Floor(averageExposures);
foreach (var reagentQuantity in SolutionContainerComponent.ReagentList.ToArray()) foreach (var reagentQuantity in solution.Contents)
{ {
if (reagentQuantity.Quantity == ReagentUnit.Zero) continue; if (reagentQuantity.Quantity == ReagentUnit.Zero) continue;
var reagent = PrototypeManager.Index<ReagentPrototype>(reagentQuantity.ReagentId); var reagent = PrototypeManager.Index<ReagentPrototype>(reagentQuantity.ReagentId);
@@ -140,7 +142,8 @@ namespace Content.Server.Chemistry.Components
// Touch every entity on the tile // Touch every entity on the tile
foreach (var entity in tile.GetEntitiesInTileFast().ToArray()) foreach (var entity in tile.GetEntitiesInTileFast().ToArray())
{ {
chemistry.ReactionEntity(entity, ReactionMethod.Touch, reagent, reagentQuantity.Quantity * solutionFraction, SolutionContainerComponent.Solution); chemistry.ReactionEntity(entity, ReactionMethod.Touch, reagent,
reagentQuantity.Quantity * solutionFraction, solution);
} }
} }
@@ -157,13 +160,13 @@ namespace Content.Server.Chemistry.Components
if (solution.TotalVolume == 0) if (solution.TotalVolume == 0)
return; return;
if (SolutionContainerComponent == null) if (!EntitySystem.Get<SolutionContainerSystem>().TryGetSolution(Owner, SolutionName, out var solutionArea))
return; return;
var addSolution = var addSolution =
solution.SplitSolution(ReagentUnit.Min(solution.TotalVolume, SolutionContainerComponent.EmptyVolume)); solution.SplitSolution(ReagentUnit.Min(solution.TotalVolume, solutionArea.AvailableVolume));
SolutionContainerComponent.TryAddSolution(addSolution); EntitySystem.Get<SolutionContainerSystem>().TryAddSolution(Owner.Uid, solutionArea, addSolution);
UpdateVisuals(); UpdateVisuals();
} }

View File

@@ -1,12 +0,0 @@
using Content.Shared.Chemistry.Solution.Components;
using Robust.Shared.GameObjects;
namespace Content.Server.Chemistry.Components
{
[RegisterComponent]
[ComponentReference(typeof(SharedSolutionContainerComponent))]
[ComponentReference(typeof(ISolutionInteractionsComponent))]
public class SolutionContainerComponent : SharedSolutionContainerComponent
{
}
}

View File

@@ -3,12 +3,15 @@ using System.Threading.Tasks;
using Content.Server.UserInterface; using Content.Server.UserInterface;
using Content.Shared.ActionBlocker; using Content.Shared.ActionBlocker;
using Content.Shared.Chemistry; 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.Chemistry.Reagent;
using Content.Shared.Chemistry.Solution.Components;
using Content.Shared.Interaction; using Content.Shared.Interaction;
using Content.Shared.Interaction.Helpers; using Content.Shared.Interaction.Helpers;
using Content.Shared.Notification.Managers; using Content.Shared.Notification.Managers;
using Content.Shared.Verbs; using Content.Shared.Verbs;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Robust.Server.GameObjects; using Robust.Server.GameObjects;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.Localization; using Robust.Shared.Localization;
@@ -108,7 +111,8 @@ namespace Content.Server.Chemistry.Components
var amount = Math.Clamp(sval, MinimumTransferAmount.Float(), var amount = Math.Clamp(sval, MinimumTransferAmount.Float(),
MaximumTransferAmount.Float()); MaximumTransferAmount.Float());
serverMsg.Session.AttachedEntity?.PopupMessage(Loc.GetString("comp-solution-transfer-set-amount", ("amount", amount))); serverMsg.Session.AttachedEntity?.PopupMessage(Loc.GetString("comp-solution-transfer-set-amount",
("amount", amount)));
SetTransferAmount(ReagentUnit.New(amount)); SetTransferAmount(ReagentUnit.New(amount));
break; break;
} }
@@ -116,50 +120,57 @@ namespace Content.Server.Chemistry.Components
public void SetTransferAmount(ReagentUnit amount) public void SetTransferAmount(ReagentUnit amount)
{ {
amount = ReagentUnit.New(Math.Clamp(amount.Int(), MinimumTransferAmount.Int(), MaximumTransferAmount.Int())); amount = ReagentUnit.New(Math.Clamp(amount.Int(), MinimumTransferAmount.Int(),
MaximumTransferAmount.Int()));
TransferAmount = amount; TransferAmount = amount;
} }
async Task<bool> IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs) async Task<bool> IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs)
{ {
var solutionsSys = EntitySystem.Get<SolutionContainerSystem>();
if (!eventArgs.InRangeUnobstructed() || eventArgs.Target == null) if (!eventArgs.InRangeUnobstructed() || eventArgs.Target == null)
return false; return false;
if (!Owner.TryGetComponent(out ISolutionInteractionsComponent? ownerSolution)) if (!Owner.HasComponent<SolutionContainerManagerComponent>())
return false; return false;
var target = eventArgs.Target; var target = eventArgs.Target!;
if (!target.TryGetComponent(out ISolutionInteractionsComponent? targetSolution)) if (!target.HasComponent<SolutionContainerManagerComponent>())
{ {
return false; return false;
} }
if (CanReceive && target.TryGetComponent(out ReagentTankComponent? tank) if (CanReceive && target.TryGetComponent(out ReagentTankComponent? tank)
&& ownerSolution.CanRefill && targetSolution.CanDrain) && solutionsSys.TryGetRefillableSolution(Owner.Uid, out var ownerRefill)
&& solutionsSys.TryGetDrainableSolution(eventArgs.Target.Uid, out var targetDrain))
{ {
var transferred = DoTransfer(targetSolution, ownerSolution, tank.TransferAmount, eventArgs.User); var transferred = DoTransfer(eventArgs.User, eventArgs.Target, targetDrain, Owner, ownerRefill, tank.TransferAmount);
if (transferred > 0) if (transferred > 0)
{ {
var toTheBrim = ownerSolution.RefillSpaceAvailable == 0; var toTheBrim = ownerRefill.AvailableVolume == 0;
var msg = toTheBrim var msg = toTheBrim
? "comp-solution-transfer-fill-fully" ? "comp-solution-transfer-fill-fully"
: "comp-solution-transfer-fill-normal"; : "comp-solution-transfer-fill-normal";
target.PopupMessage(eventArgs.User, Loc.GetString(msg,("owner", Owner),("amount", transferred),("target", target))); target.PopupMessage(eventArgs.User,
Loc.GetString(msg, ("owner", eventArgs.Target), ("amount", transferred), ("target", Owner)));
return true; return true;
} }
} }
if (CanSend && targetSolution.CanRefill && ownerSolution.CanDrain) if (CanSend && solutionsSys.TryGetRefillableSolution(eventArgs.Target.Uid, out var targetRefill)
&& solutionsSys.TryGetDrainableSolution(Owner.Uid, out var ownerDrain))
{ {
var transferred = DoTransfer(ownerSolution, targetSolution, TransferAmount, eventArgs.User); var transferred = DoTransfer(eventArgs.User, Owner, ownerDrain, target, targetRefill, TransferAmount);
if (transferred > 0) if (transferred > 0)
{ {
Owner.PopupMessage(eventArgs.User, Owner.PopupMessage(eventArgs.User,
Loc.GetString("comp-solution-transfer-transfer-solution", Loc.GetString("comp-solution-transfer-transfer-solution",
("amount",transferred), ("amount", transferred),
("target",target))); ("target", target)));
return true; return true;
} }
@@ -169,29 +180,33 @@ namespace Content.Server.Chemistry.Components
} }
/// <returns>The actual amount transferred.</returns> /// <returns>The actual amount transferred.</returns>
private static ReagentUnit DoTransfer( private static ReagentUnit DoTransfer(IEntity user,
ISolutionInteractionsComponent source, IEntity sourceEntity,
ISolutionInteractionsComponent target, Solution source,
ReagentUnit amount, IEntity targetEntity,
IEntity user) Solution target,
ReagentUnit amount)
{ {
if (source.DrainAvailable == 0) if (source.DrainAvailable == 0)
{ {
source.Owner.PopupMessage(user, Loc.GetString("comp-solution-transfer-is-empty", ("target", source.Owner))); sourceEntity.PopupMessage(user,
Loc.GetString("comp-solution-transfer-is-empty", ("target", sourceEntity)));
return ReagentUnit.Zero; return ReagentUnit.Zero;
} }
if (target.RefillSpaceAvailable == 0) if (target.AvailableVolume == 0)
{ {
target.Owner.PopupMessage(user, Loc.GetString("comp-solution-transfer-is-full", ("target", target.Owner))); targetEntity.PopupMessage(user,
Loc.GetString("comp-solution-transfer-is-full", ("target", targetEntity)));
return ReagentUnit.Zero; return ReagentUnit.Zero;
} }
var actualAmount = var actualAmount =
ReagentUnit.Min(amount, ReagentUnit.Min(source.DrainAvailable, target.RefillSpaceAvailable)); ReagentUnit.Min(amount, ReagentUnit.Min(source.DrainAvailable, target.AvailableVolume));
var solution = source.Drain(actualAmount); var solution = EntitySystem.Get<SolutionContainerSystem>().Drain(sourceEntity.Uid, source, actualAmount);
target.Refill(solution); EntitySystem.Get<SolutionContainerSystem>().Refill(targetEntity.Uid, target, solution);
return actualAmount; return actualAmount;
} }
@@ -251,7 +266,8 @@ namespace Content.Server.Chemistry.Components
protected override void Activate(IEntity user, SolutionTransferComponent component) protected override void Activate(IEntity user, SolutionTransferComponent component)
{ {
component.TransferAmount = component.SubjectiveBestTransferAmount(); component.TransferAmount = component.SubjectiveBestTransferAmount();
user.PopupMessage(Loc.GetString("comp-solution-transfer-set-amount", ("amount", component.TransferAmount.Int()))); user.PopupMessage(Loc.GetString("comp-solution-transfer-set-amount",
("amount", component.TransferAmount.Int())));
} }
} }
@@ -275,7 +291,8 @@ namespace Content.Server.Chemistry.Components
protected override void Activate(IEntity user, SolutionTransferComponent component) protected override void Activate(IEntity user, SolutionTransferComponent component)
{ {
component.TransferAmount = component.MaximumTransferAmount; component.TransferAmount = component.MaximumTransferAmount;
user.PopupMessage(Loc.GetString("comp-solution-transfer-set-amount", ("amount", component.TransferAmount.Int()))); user.PopupMessage(Loc.GetString("comp-solution-transfer-set-amount",
("amount", component.TransferAmount.Int())));
} }
} }
@@ -303,6 +320,7 @@ namespace Content.Server.Chemistry.Components
{ {
return; return;
} }
component.UserInterface?.Open(actor.PlayerSession); component.UserInterface?.Open(actor.PlayerSession);
} }
} }

View File

@@ -1,27 +1,22 @@
using Content.Shared.Chemistry; using Content.Shared.Chemistry.Components.SolutionManager;
using Content.Shared.Chemistry.Reagent; using Content.Shared.Chemistry.Reagent;
using Content.Shared.Chemistry.Solution;
using Robust.Server.GameObjects; using Robust.Server.GameObjects;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility; using Robust.Shared.Utility;
namespace Content.Server.Chemistry.Components namespace Content.Server.Chemistry.Components
{ {
[RegisterComponent] [RegisterComponent]
public class TransformableContainerComponent : Component, ISolutionChange public class TransformableContainerComponent : Component
{ {
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
public override string Name => "TransformableContainer"; public override string Name => "TransformableContainer";
private SpriteSpecifier? _initialSprite; public SpriteSpecifier? InitialSprite;
private string _initialName = default!; public string InitialName = default!;
private string _initialDescription = default!; public string InitialDescription = default!;
private ReagentPrototype? _currentReagent; public ReagentPrototype? CurrentReagent;
public bool Transformed { get; private set; } public bool Transformed { get; internal set; }
protected override void Initialize() protected override void Initialize()
{ {
@@ -30,72 +25,19 @@ namespace Content.Server.Chemistry.Components
if (Owner.TryGetComponent(out SpriteComponent? sprite) && if (Owner.TryGetComponent(out SpriteComponent? sprite) &&
sprite.BaseRSIPath != null) sprite.BaseRSIPath != null)
{ {
_initialSprite = new SpriteSpecifier.Rsi(new ResourcePath(sprite.BaseRSIPath), "icon"); InitialSprite = new SpriteSpecifier.Rsi(new ResourcePath(sprite.BaseRSIPath), "icon");
} }
_initialName = Owner.Name; InitialName = Owner.Name;
_initialDescription = Owner.Description; InitialDescription = Owner.Description;
} }
protected override void Startup() protected override void Startup()
{ {
base.Startup(); base.Startup();
Owner.EnsureComponentWarn(out SolutionContainerComponent solution); Owner.EnsureComponentWarn<SolutionContainerManagerComponent>();
Owner.EnsureComponentWarn<FitsInDispenserComponent>();
solution.Capabilities |= SolutionContainerCaps.FitsInDispenser;
}
public void CancelTransformation()
{
_currentReagent = null;
Transformed = false;
if (Owner.TryGetComponent(out SpriteComponent? sprite) &&
_initialSprite != null)
{
sprite.LayerSetSprite(0, _initialSprite);
}
Owner.Name = _initialName;
Owner.Description = _initialDescription;
}
void ISolutionChange.SolutionChanged(SolutionChangeEventArgs eventArgs)
{
var solution = eventArgs.Owner.GetComponent<SolutionContainerComponent>();
//Transform container into initial state when emptied
if (_currentReagent != null && solution.ReagentList.Count == 0)
{
CancelTransformation();
}
//the biggest reagent in the solution decides the appearance
var reagentId = solution.Solution.GetPrimaryReagentId();
//If biggest reagent didn't changed - don't change anything at all
if (_currentReagent != null && _currentReagent.ID == reagentId)
{
return;
}
//Only reagents with spritePath property can change appearance of transformable containers!
if (!string.IsNullOrWhiteSpace(reagentId) &&
_prototypeManager.TryIndex(reagentId, out ReagentPrototype? proto) &&
!string.IsNullOrWhiteSpace(proto.SpriteReplacementPath))
{
var spriteSpec = new SpriteSpecifier.Rsi(new ResourcePath("Objects/Consumable/Drinks/" + proto.SpriteReplacementPath),"icon");
if (Owner.TryGetComponent(out SpriteComponent? sprite))
{
sprite?.LayerSetSprite(0, spriteSpec);
}
Owner.Name = proto.Name + " glass";
Owner.Description = proto.Description;
_currentReagent = proto;
Transformed = true;
}
} }
} }
} }

View File

@@ -0,0 +1,24 @@
using Content.Server.Chemistry.Components;
using Content.Shared.Chemistry.EntitySystems;
using JetBrains.Annotations;
using Robust.Shared.GameObjects;
namespace Content.Server.Chemistry.EntitySystems
{
[UsedImplicitly]
public class ChemMasterSystem : EntitySystem
{
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<ChemMasterComponent, SolutionChangedEvent>(OnSolutionChange);
}
private void OnSolutionChange(EntityUid uid, ChemMasterComponent component,
SolutionChangedEvent solutionChanged)
{
component.UpdateUserInterface();
}
}
}

View File

@@ -1,3 +1,4 @@
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.Reaction; using Content.Shared.Chemistry.Reaction;
using Content.Shared.Chemistry.Reagent; using Content.Shared.Chemistry.Reagent;
using Robust.Shared.Audio; using Robust.Shared.Audio;
@@ -8,9 +9,9 @@ namespace Content.Server.Chemistry.EntitySystems
{ {
public class ChemicalReactionSystem : SharedChemicalReactionSystem public class ChemicalReactionSystem : SharedChemicalReactionSystem
{ {
protected override void OnReaction(ReactionPrototype reaction, IEntity owner, ReagentUnit unitReactions) protected override void OnReaction(Solution solution, ReactionPrototype reaction, IEntity owner, ReagentUnit unitReactions)
{ {
base.OnReaction(reaction, owner, unitReactions); base.OnReaction(solution, reaction, owner, unitReactions);
SoundSystem.Play(Filter.Pvs(owner), reaction.Sound.GetSound(), owner); SoundSystem.Play(Filter.Pvs(owner), reaction.Sound.GetSound(), owner);
} }

View File

@@ -1,10 +1,13 @@
using Content.Server.Chemistry.Components; using Content.Server.Chemistry.Components;
using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Interaction; using Content.Shared.Interaction;
using Content.Shared.Weapons.Melee; using Content.Shared.Weapons.Melee;
using JetBrains.Annotations;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
namespace Content.Server.Chemistry.EntitySystems namespace Content.Server.Chemistry.EntitySystems
{ {
[UsedImplicitly]
public class HypospraySystem : EntitySystem public class HypospraySystem : EntitySystem
{ {
public override void Initialize() public override void Initialize()
@@ -13,6 +16,12 @@ namespace Content.Server.Chemistry.EntitySystems
SubscribeLocalEvent<HyposprayComponent, AfterInteractEvent>(OnAfterInteract); SubscribeLocalEvent<HyposprayComponent, AfterInteractEvent>(OnAfterInteract);
SubscribeLocalEvent<HyposprayComponent, ClickAttackEvent>(OnClickAttack); SubscribeLocalEvent<HyposprayComponent, ClickAttackEvent>(OnClickAttack);
SubscribeLocalEvent<HyposprayComponent, SolutionChangedEvent>(OnSolutionChange);
}
private void OnSolutionChange(EntityUid uid, HyposprayComponent component, SolutionChangedEvent args)
{
component.Dirty();
} }
public void OnAfterInteract(EntityUid uid, HyposprayComponent comp, AfterInteractEvent args) public void OnAfterInteract(EntityUid uid, HyposprayComponent comp, AfterInteractEvent args)

View File

@@ -0,0 +1,23 @@
using Content.Server.Chemistry.Components;
using Content.Shared.Chemistry.EntitySystems;
using JetBrains.Annotations;
using Robust.Shared.GameObjects;
namespace Content.Server.Chemistry.EntitySystems
{
[UsedImplicitly]
public class InjectorSystem : EntitySystem
{
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<InjectorComponent, SolutionChangedEvent>(OnSolutionChange);
}
private void OnSolutionChange(EntityUid uid, InjectorComponent component, SolutionChangedEvent args)
{
component.Dirty();
}
}
}

View File

@@ -0,0 +1,23 @@
using Content.Server.Chemistry.Components;
using Content.Shared.Chemistry.EntitySystems;
using JetBrains.Annotations;
using Robust.Shared.GameObjects;
namespace Content.Server.Chemistry.EntitySystems
{
[UsedImplicitly]
public class ReagentDispenserSystem : EntitySystem
{
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<ReagentDispenserComponent, SolutionChangedEvent>(OnSolutionChange);
}
private void OnSolutionChange(EntityUid uid, ReagentDispenserComponent component, SolutionChangedEvent args)
{
component.UpdateUserInterface();
}
}
}

View File

@@ -0,0 +1,51 @@
using Content.Server.Chemistry.Components;
using Content.Server.Notification;
using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Chemistry.Reagent;
using JetBrains.Annotations;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
namespace Content.Server.Chemistry.EntitySystems
{
[UsedImplicitly]
public class RehydratableSystem : EntitySystem
{
[Dependency] private readonly SolutionContainerSystem _solutionsSystem = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<RehydratableComponent, SolutionChangedEvent>(OnSolutionChange);
}
private void OnSolutionChange(EntityUid uid, RehydratableComponent component, SolutionChangedEvent args)
{
if (_solutionsSystem.GetReagentQuantity(uid, component.CatalystPrototype) > ReagentUnit.Zero)
{
Expand(component, component.Owner);
}
}
// Try not to make this public if you can help it.
private void Expand(RehydratableComponent component, IEntity owner)
{
if (component.Expanding)
{
return;
}
component.Expanding = true;
owner.PopupMessageEveryone(Loc.GetString("rehydratable-component-expands-message", ("owner", owner)));
if (!string.IsNullOrEmpty(component.TargetPrototype))
{
var ent = component.Owner.EntityManager.SpawnEntity(component.TargetPrototype,
owner.Transform.Coordinates);
ent.Transform.AttachToGridOrMap();
}
owner.QueueDelete();
}
}
}

View File

@@ -1,7 +1,10 @@
using Content.Server.Body.Circulatory; using Content.Server.Body.Circulatory;
using Content.Server.Chemistry.Components; using Content.Server.Chemistry.Components;
using Content.Shared.Chemistry.Components.SolutionManager;
using Content.Shared.Chemistry.EntitySystems;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Physics.Dynamics; using Robust.Shared.Physics.Dynamics;
namespace Content.Server.Chemistry.EntitySystems namespace Content.Server.Chemistry.EntitySystems
@@ -9,6 +12,7 @@ namespace Content.Server.Chemistry.EntitySystems
[UsedImplicitly] [UsedImplicitly]
internal sealed class SolutionInjectOnCollideSystem : EntitySystem internal sealed class SolutionInjectOnCollideSystem : EntitySystem
{ {
[Dependency] private readonly SolutionContainerSystem _solutionsSystem = default!;
public override void Initialize() public override void Initialize()
{ {
base.Initialize(); base.Initialize();
@@ -18,15 +22,15 @@ namespace Content.Server.Chemistry.EntitySystems
private void HandleInit(EntityUid uid, SolutionInjectOnCollideComponent component, ComponentInit args) private void HandleInit(EntityUid uid, SolutionInjectOnCollideComponent component, ComponentInit args)
{ {
component.Owner.EnsureComponentWarn<SolutionContainerComponent>($"{nameof(SolutionInjectOnCollideComponent)} requires a SolutionContainer on {component.Owner}!"); component.Owner
.EnsureComponentWarn<SolutionContainerManagerComponent>($"{nameof(SolutionInjectOnCollideComponent)} requires a SolutionContainerManager on {component.Owner}!");
} }
private void HandleInjection(EntityUid uid, SolutionInjectOnCollideComponent component, StartCollideEvent args) private void HandleInjection(EntityUid uid, SolutionInjectOnCollideComponent component, StartCollideEvent args)
{ {
if (!args.OtherFixture.Body.Owner.TryGetComponent<BloodstreamComponent>(out var bloodstream) || if (!args.OtherFixture.Body.Owner.TryGetComponent<BloodstreamComponent>(out var bloodstream) ||
!ComponentManager.TryGetComponent(uid, out SolutionContainerComponent? solutionContainer)) return; !_solutionsSystem.TryGetInjectableSolution(component.Owner.Uid, out var solution)) return;
var solution = solutionContainer.Solution;
var solRemoved = solution.SplitSolution(component.TransferAmount); var solRemoved = solution.SplitSolution(component.TransferAmount);
var solRemovedVol = solRemoved.TotalVolume; var solRemovedVol = solRemoved.TotalVolume;

View File

@@ -0,0 +1,82 @@
using Content.Server.Chemistry.Components;
using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Chemistry.Reagent;
using JetBrains.Annotations;
using Robust.Server.GameObjects;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
namespace Content.Server.Chemistry.EntitySystems
{
[UsedImplicitly]
public class TransformableContainerSystem : EntitySystem
{
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly SolutionContainerSystem _solutionsSystem = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<TransformableContainerComponent, SolutionChangedEvent>(OnSolutionChange);
}
private void OnSolutionChange(EntityUid uid, TransformableContainerComponent component,
SolutionChangedEvent args)
{
if (!_solutionsSystem.TryGetFitsInDispenser(uid, out var solution))
return;
//Transform container into initial state when emptied
if (component.CurrentReagent != null && solution.Contents.Count == 0)
{
CancelTransformation(component);
}
//the biggest reagent in the solution decides the appearance
var reagentId = solution.GetPrimaryReagentId();
//If biggest reagent didn't changed - don't change anything at all
if (component.CurrentReagent != null && component.CurrentReagent.ID == reagentId)
{
return;
}
//Only reagents with spritePath property can change appearance of transformable containers!
if (!string.IsNullOrWhiteSpace(reagentId)
&& _prototypeManager.TryIndex(reagentId, out ReagentPrototype? proto)
&& !string.IsNullOrWhiteSpace(proto.SpriteReplacementPath))
{
var spriteSpec =
new SpriteSpecifier.Rsi(
new ResourcePath("Objects/Consumable/Drinks/" + proto.SpriteReplacementPath), "icon");
var ownerEntity = EntityManager.GetEntity(uid);
if (ownerEntity.TryGetComponent(out SpriteComponent? sprite))
{
sprite?.LayerSetSprite(0, spriteSpec);
}
ownerEntity.Name = proto.Name + " glass";
ownerEntity.Description = proto.Description;
component.CurrentReagent = proto;
component.Transformed = true;
}
}
private void CancelTransformation(TransformableContainerComponent component)
{
component.CurrentReagent = null;
component.Transformed = false;
if (component.Owner.TryGetComponent(out SpriteComponent? sprite) &&
component.InitialSprite != null)
{
sprite.LayerSetSprite(0, component.InitialSprite);
}
component.Owner.Name = component.InitialName;
component.Owner.Description = component.InitialDescription;
}
}
}

View File

@@ -1,8 +1,10 @@
using System.Linq; using Content.Server.Chemistry.Components;
using Content.Server.Chemistry.Components; using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.Components.SolutionManager;
using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Chemistry.Reagent; using Content.Shared.Chemistry.Reagent;
using Content.Shared.Chemistry.Solution;
using Content.Shared.Physics; using Content.Shared.Physics;
using Content.Shared.Vapor;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.IoC; using Robust.Shared.IoC;
@@ -18,6 +20,7 @@ namespace Content.Server.Chemistry.EntitySystems
{ {
[Dependency] private readonly IMapManager _mapManager = default!; [Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IPrototypeManager _protoManager = default!; [Dependency] private readonly IPrototypeManager _protoManager = default!;
[Dependency] private readonly SolutionContainerSystem _solutionContainerSystem = default!;
private const float ReactTime = 0.125f; private const float ReactTime = 0.125f;
@@ -29,14 +32,17 @@ namespace Content.Server.Chemistry.EntitySystems
private void HandleCollide(EntityUid uid, VaporComponent component, StartCollideEvent args) private void HandleCollide(EntityUid uid, VaporComponent component, StartCollideEvent args)
{ {
if (!ComponentManager.TryGetComponent(uid, out SolutionContainerComponent? contents)) return; if (!ComponentManager.TryGetComponent(uid, out SolutionContainerManagerComponent? contents)) return;
contents.Solution.DoEntityReaction(args.OtherFixture.Body.Owner, ReactionMethod.Touch); foreach (var (_, value) in contents.Solutions)
{
value.DoEntityReaction(args.OtherFixture.Body.Owner, ReactionMethod.Touch);
}
// Check for collision with a impassable object (e.g. wall) and stop // Check for collision with a impassable object (e.g. wall) and stop
if ((args.OtherFixture.CollisionLayer & (int) CollisionGroup.Impassable) != 0 && args.OtherFixture.Hard) if ((args.OtherFixture.CollisionLayer & (int) CollisionGroup.Impassable) != 0 && args.OtherFixture.Hard)
{ {
EntityManager.QueueDeleteEntity(uid); EntityManager.QueueDeleteEntity(uid);
} }
} }
@@ -60,14 +66,7 @@ namespace Content.Server.Chemistry.EntitySystems
return false; return false;
} }
if (!vapor.Owner.TryGetComponent(out SolutionContainerComponent? contents)) if (!_solutionContainerSystem.TryGetSolution(vapor.Owner, SharedVaporComponent.SolutionName, out _))
{
return false;
}
var result = contents.TryAddSolution(solution);
if (!result)
{ {
return false; return false;
} }
@@ -77,13 +76,17 @@ namespace Content.Server.Chemistry.EntitySystems
public override void Update(float frameTime) public override void Update(float frameTime)
{ {
foreach (var (vaporComp, solution) in ComponentManager.EntityQuery<VaporComponent, SolutionContainerComponent>(true)) foreach (var (vaporComp, solution) in ComponentManager
.EntityQuery<VaporComponent, SolutionContainerManagerComponent>(true))
{ {
Update(frameTime, vaporComp, solution); foreach (var (_, value) in solution.Solutions)
{
Update(frameTime, vaporComp, value);
}
} }
} }
private void Update(float frameTime, VaporComponent vapor, SolutionContainerComponent contents) private void Update(float frameTime, VaporComponent vapor, Solution contents)
{ {
if (!vapor.Active) if (!vapor.Active)
return; return;
@@ -99,16 +102,19 @@ namespace Content.Server.Chemistry.EntitySystems
var mapGrid = _mapManager.GetGrid(entity.Transform.GridID); var mapGrid = _mapManager.GetGrid(entity.Transform.GridID);
var tile = mapGrid.GetTileRef(entity.Transform.Coordinates.ToVector2i(EntityManager, _mapManager)); var tile = mapGrid.GetTileRef(entity.Transform.Coordinates.ToVector2i(EntityManager, _mapManager));
foreach (var reagentQuantity in contents.ReagentList.ToArray()) foreach (var reagentQuantity in contents.Contents.ToArray())
{ {
if (reagentQuantity.Quantity == ReagentUnit.Zero) continue; if (reagentQuantity.Quantity == ReagentUnit.Zero) continue;
var reagent = _protoManager.Index<ReagentPrototype>(reagentQuantity.ReagentId); var reagent = _protoManager.Index<ReagentPrototype>(reagentQuantity.ReagentId);
contents.TryRemoveReagent(reagentQuantity.ReagentId, reagent.ReactionTile(tile, (reagentQuantity.Quantity / vapor.TransferAmount) * 0.25f)); _solutionContainerSystem.TryRemoveReagent(vapor.Owner.Uid, contents, reagentQuantity.ReagentId,
reagent.ReactionTile(tile, (reagentQuantity.Quantity / vapor.TransferAmount) * 0.25f));
} }
} }
// Check if we've reached our target. // Check if we've reached our target.
if(!vapor.Reached && vapor.Target.TryDistance(EntityManager, entity.Transform.Coordinates, out var distance) && distance <= 0.5f) if (!vapor.Reached &&
vapor.Target.TryDistance(EntityManager, entity.Transform.Coordinates, out var distance) &&
distance <= 0.5f)
{ {
vapor.Reached = true; vapor.Reached = true;
} }

View File

@@ -1,7 +1,9 @@
using System; using System;
using Content.Server.Chemistry.Components; using Content.Server.Chemistry.Components;
using Content.Server.Coordinates.Helpers; using Content.Server.Coordinates.Helpers;
using Content.Shared.Audio; using Content.Shared.Audio;
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Chemistry.Reaction; using Content.Shared.Chemistry.Reaction;
using Content.Shared.Sound; using Content.Shared.Sound;
using JetBrains.Annotations; using JetBrains.Annotations;
@@ -87,12 +89,9 @@ namespace Content.Server.Chemistry.ReactionEffects
IoCManager.InjectDependencies(this); IoCManager.InjectDependencies(this);
} }
public void React(IEntity solutionEntity, double intensity) public void React(Solution solution, IEntity solutionEntity, double intensity)
{ {
if (!solutionEntity.TryGetComponent(out SolutionContainerComponent? contents)) var splitSolution = EntitySystem.Get<SolutionContainerSystem>().SplitSolution(solutionEntity.Uid, solution, solution.MaxVolume);
return;
var solution = contents.SplitSolution(contents.MaxVolume);
// We take the square root so it becomes harder to reach higher amount values // We take the square root so it becomes harder to reach higher amount values
var amount = (int) Math.Round(_rangeConstant + _rangeMultiplier*Math.Sqrt(intensity)); var amount = (int) Math.Round(_rangeConstant + _rangeMultiplier*Math.Sqrt(intensity));
amount = Math.Min(amount, _maxRange); amount = Math.Min(amount, _maxRange);
@@ -115,7 +114,7 @@ namespace Content.Server.Chemistry.ReactionEffects
solutionFraction = amount * (1 - _reagentMaxConcentrationFactor) / _reagentDilutionStart + solutionFraction = amount * (1 - _reagentMaxConcentrationFactor) / _reagentDilutionStart +
_reagentMaxConcentrationFactor; _reagentMaxConcentrationFactor;
} }
solution.RemoveSolution(solution.TotalVolume * solutionFraction); splitSolution.RemoveSolution(splitSolution.TotalVolume * solutionFraction);
} }
if (!_mapManager.TryFindGridAt(solutionEntity.Transform.MapPosition, out var grid)) return; if (!_mapManager.TryFindGridAt(solutionEntity.Transform.MapPosition, out var grid)) return;
@@ -133,7 +132,7 @@ namespace Content.Server.Chemistry.ReactionEffects
return; return;
} }
areaEffectComponent.TryAddSolution(solution); areaEffectComponent.TryAddSolution(splitSolution);
areaEffectComponent.Start(amount, _duration, _spreadDelay, _removeDelay); areaEffectComponent.Start(amount, _duration, _spreadDelay, _removeDelay);
SoundSystem.Play(Filter.Pvs(solutionEntity), _sound.GetSound(), solutionEntity, AudioHelpers.WithVariation(0.125f)); SoundSystem.Play(Filter.Pvs(solutionEntity), _sound.GetSound(), solutionEntity, AudioHelpers.WithVariation(0.125f));

View File

@@ -1,6 +1,8 @@
using System; using System;
using Content.Server.Chemistry.Components; using Content.Server.Chemistry.Components;
using Content.Server.Explosion; using Content.Server.Explosion;
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.Components.SolutionManager;
using Content.Shared.Chemistry.Reaction; using Content.Shared.Chemistry.Reaction;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.Serialization.Manager.Attributes; using Robust.Shared.Serialization.Manager.Attributes;
@@ -26,12 +28,11 @@ namespace Content.Server.Chemistry.ReactionEffects
/// </summary> /// </summary>
[DataField("maxScale")] private float _maxScale = 1; [DataField("maxScale")] private float _maxScale = 1;
public void React(IEntity? solutionEntity, double intensity) public void React(Solution solution, IEntity solutionEntity, double intensity)
{ {
var floatIntensity = (float) intensity; var floatIntensity = (float) intensity;
if (solutionEntity == null)
return; if (!solutionEntity.HasComponent<SolutionContainerManagerComponent>())
if (!solutionEntity.HasComponent<SolutionContainerComponent>())
return; return;
//Handle scaling //Handle scaling

View File

@@ -1,6 +1,5 @@
using Content.Shared.Body.Components; using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.Reagent; using Content.Shared.Chemistry.Reagent;
using Content.Shared.Chemistry.Solution;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.Serialization.Manager.Attributes; using Robust.Shared.Serialization.Manager.Attributes;

View File

@@ -1,6 +1,5 @@
using Content.Shared.Chemistry; using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.Reagent; using Content.Shared.Chemistry.Reagent;
using Content.Shared.Chemistry.Solution;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.Serialization.Manager.Attributes; using Robust.Shared.Serialization.Manager.Attributes;
using Content.Shared.Damage; using Content.Shared.Damage;

View File

@@ -1,12 +1,11 @@
using Content.Shared.Chemistry.Reagent;
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization.Manager.Attributes;
using Content.Shared.Movement.Components;
using Content.Shared.Chemistry.Components;
using Robust.Shared.Timing;
using Robust.Shared.IoC;
using System; using System;
using Content.Shared.Chemistry.Solution; using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.Movement.Components;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.Timing;
namespace Content.Server.Chemistry.ReagentEffects namespace Content.Server.Chemistry.ReagentEffects
{ {

View File

@@ -1,7 +1,6 @@
using Content.Server.Nutrition.Components; using Content.Server.Nutrition.Components;
using Content.Shared.Chemistry; using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.Reagent; using Content.Shared.Chemistry.Reagent;
using Content.Shared.Chemistry.Solution;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.Serialization.Manager.Attributes; using Robust.Shared.Serialization.Manager.Attributes;

View File

@@ -1,7 +1,6 @@
using Content.Server.Nutrition.Components; using Content.Server.Nutrition.Components;
using Content.Shared.Chemistry; using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.Reagent; using Content.Shared.Chemistry.Reagent;
using Content.Shared.Chemistry.Solution;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.Serialization.Manager.Attributes; using Robust.Shared.Serialization.Manager.Attributes;

View File

@@ -1,7 +1,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using Content.Server.Chemistry.Components; using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Chemistry.Reagent; using Content.Shared.Chemistry.Reagent;
using Content.Shared.Chemistry.Solution;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.Serialization.Manager.Attributes; using Robust.Shared.Serialization.Manager.Attributes;
@@ -12,15 +12,19 @@ namespace Content.Server.Chemistry.ReagentEntityReactions
[UsedImplicitly] [UsedImplicitly]
public class AddToSolutionReaction : ReagentEntityReaction public class AddToSolutionReaction : ReagentEntityReaction
{ {
[DataField("reagents", true, customTypeSerializer:typeof(PrototypeIdHashSetSerializer<ReagentPrototype>))] [DataField("reagents", true, customTypeSerializer: typeof(PrototypeIdHashSetSerializer<ReagentPrototype>))]
// ReSharper disable once CollectionNeverUpdated.Local // ReSharper disable once CollectionNeverUpdated.Local
private readonly HashSet<string> _reagents = new (); private readonly HashSet<string> _reagents = new();
protected override void React(IEntity entity, ReagentPrototype reagent, ReagentUnit volume, Solution? source) protected override void React(IEntity entity, ReagentPrototype reagent, ReagentUnit volume, Solution? source)
{ {
if (!entity.TryGetComponent(out SolutionContainerComponent? solutionContainer) || (_reagents.Count > 0 && !_reagents.Contains(reagent.ID))) return; // TODO see if this is correct
if (!EntitySystem.Get<SolutionContainerSystem>()
.TryGetSolution(entity, "reagents", out var solutionContainer)
|| (_reagents.Count > 0 && !_reagents.Contains(reagent.ID))) return;
if(solutionContainer.TryAddReagent(reagent.ID, volume, out var accepted)) if (EntitySystem.Get<SolutionContainerSystem>()
.TryAddReagent(entity.Uid, solutionContainer, reagent.ID, volume, out var accepted))
source?.RemoveReagent(reagent.ID, accepted); source?.RemoveReagent(reagent.ID, accepted);
} }
} }

View File

@@ -1,7 +1,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using Content.Server.Atmos.Components; using Content.Server.Atmos.Components;
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.Reagent; using Content.Shared.Chemistry.Reagent;
using Content.Shared.Chemistry.Solution;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.Serialization.Manager.Attributes; using Robust.Shared.Serialization.Manager.Attributes;

View File

@@ -1,7 +1,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using Content.Server.Atmos.Components; using Content.Server.Atmos.Components;
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.Reagent; using Content.Shared.Chemistry.Reagent;
using Content.Shared.Chemistry.Solution;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.Serialization.Manager.Attributes; using Robust.Shared.Serialization.Manager.Attributes;

View File

@@ -1,8 +1,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using Content.Server.Nutrition.Components;
using Content.Server.Nutrition.EntitySystems; using Content.Server.Nutrition.EntitySystems;
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.Reagent; using Content.Shared.Chemistry.Reagent;
using Content.Shared.Chemistry.Solution;
using Content.Shared.Nutrition.Components; using Content.Shared.Nutrition.Components;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;

View File

@@ -1,8 +1,7 @@
using Content.Server.Fluids.Components; using Content.Server.Fluids.Components;
using Content.Shared.Chemistry; using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.Reaction; using Content.Shared.Chemistry.Reaction;
using Content.Shared.Chemistry.Reagent; using Content.Shared.Chemistry.Reagent;
using Content.Shared.Chemistry.Solution;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Shared.Map; using Robust.Shared.Map;
using Robust.Shared.Serialization.Manager.Attributes; using Robust.Shared.Serialization.Manager.Attributes;

View File

@@ -1,8 +1,7 @@
using Content.Server.Fluids.Components; using Content.Server.Fluids.Components;
using Content.Shared.Chemistry; using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.Reaction; using Content.Shared.Chemistry.Reaction;
using Content.Shared.Chemistry.Reagent; using Content.Shared.Chemistry.Reagent;
using Content.Shared.Chemistry.Solution;
using Content.Shared.Slippery; using Content.Shared.Slippery;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Shared.Map; using Robust.Shared.Map;

View File

@@ -1,5 +1,5 @@
using Content.Server.Chemistry.Components;
using Content.Server.Fluids.Components; using Content.Server.Fluids.Components;
using Content.Shared.Chemistry.EntitySystems;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.Serialization.Manager.Attributes; using Robust.Shared.Serialization.Manager.Attributes;
@@ -12,10 +12,11 @@ namespace Content.Server.Destructible.Thresholds.Behaviors
{ {
public void Execute(IEntity owner, DestructibleSystem system) public void Execute(IEntity owner, DestructibleSystem system)
{ {
if (!owner.TryGetComponent(out SolutionContainerComponent? solutionContainer)) // TODO see if this is correct
if (!EntitySystem.Get<SolutionContainerSystem>().TryGetSolution(owner, SpillableComponent.SolutionName, out var solution))
return; return;
solutionContainer.Solution.SpillAt(owner.Transform.Coordinates, "PuddleSmear", false); solution.SpillAt(owner.Transform.Coordinates, "PuddleSmear", false);
} }
} }
} }

View File

@@ -1,9 +1,8 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using Content.Server.Chemistry.Components; using Content.Server.Chemistry.Components;
using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Chemistry.Reagent; using Content.Shared.Chemistry.Reagent;
using Content.Shared.Chemistry.Solution.Components;
using Content.Shared.Interaction; using Content.Shared.Interaction;
using Content.Shared.Notification;
using Content.Shared.Notification.Managers; using Content.Shared.Notification.Managers;
using Content.Shared.Sound; using Content.Shared.Sound;
using Robust.Shared.Audio; using Robust.Shared.Audio;
@@ -12,13 +11,13 @@ using Robust.Shared.Localization;
using Robust.Shared.Player; using Robust.Shared.Player;
using Robust.Shared.Serialization.Manager.Attributes; using Robust.Shared.Serialization.Manager.Attributes;
namespace Content.Server.Extinguisher namespace Content.Server.Extinguisher
{ {
[RegisterComponent] [RegisterComponent]
public class FireExtinguisherComponent : Component, IAfterInteract public class FireExtinguisherComponent : Component, IAfterInteract
{ {
public override string Name => "FireExtinguisher"; public override string Name => "FireExtinguisher";
private const string SolutionName = "fireExtinguisher";
[DataField("refillSound")] SoundSpecifier _refillSound = new SoundPathSpecifier("/Audio/Effects/refill.ogg"); [DataField("refillSound")] SoundSpecifier _refillSound = new SoundPathSpecifier("/Audio/Effects/refill.ogg");
@@ -27,24 +26,26 @@ namespace Content.Server.Extinguisher
async Task<bool> IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs) async Task<bool> IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs)
{ {
var solutionContainerSystem = EntitySystem.Get<SolutionContainerSystem>();
if (eventArgs.Target == null || !eventArgs.CanReach) if (eventArgs.Target == null || !eventArgs.CanReach)
{ {
return false; return false;
} }
if (eventArgs.Target.TryGetComponent(out ReagentTankComponent? tank) var targetEntity = eventArgs.Target;
&& eventArgs.Target.TryGetComponent(out ISolutionInteractionsComponent? targetSolution) if (eventArgs.Target.HasComponent<ReagentTankComponent>()
&& targetSolution.CanDrain && solutionContainerSystem.TryGetDrainableSolution(targetEntity.Uid, out var targetSolution)
&& Owner.TryGetComponent(out SolutionContainerComponent? container)) && solutionContainerSystem.TryGetSolution(Owner, SolutionName, out var container))
{ {
var trans = ReagentUnit.Min(container.EmptyVolume, targetSolution.DrainAvailable); var transfer = ReagentUnit.Min(container.AvailableVolume, targetSolution.DrainAvailable);
if (trans > 0) if (transfer > 0)
{ {
var drained = targetSolution.Drain(trans); var drained = solutionContainerSystem.Drain(targetEntity.Uid, targetSolution, transfer);
container.TryAddSolution(drained); solutionContainerSystem.TryAddSolution(Owner.Uid, container, drained);
SoundSystem.Play(Filter.Pvs(Owner), _refillSound.GetSound(), Owner); SoundSystem.Play(Filter.Pvs(Owner), _refillSound.GetSound(), Owner);
eventArgs.Target.PopupMessage(eventArgs.User, Loc.GetString("fire-extinguisher-component-after-interact-refilled-message",("owner", Owner))); eventArgs.Target.PopupMessage(eventArgs.User,
Loc.GetString("fire-extinguisher-component-after-interact-refilled-message", ("owner", Owner)));
} }
return true; return true;

View File

@@ -1,11 +1,10 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using Content.Server.Chemistry.Components;
using Content.Server.DoAfter; using Content.Server.DoAfter;
using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Chemistry.Reagent; using Content.Shared.Chemistry.Reagent;
using Content.Shared.Interaction; using Content.Shared.Interaction;
using Content.Shared.Interaction.Helpers; using Content.Shared.Interaction.Helpers;
using Content.Shared.Notification;
using Content.Shared.Notification.Managers; using Content.Shared.Notification.Managers;
using Content.Shared.Sound; using Content.Shared.Sound;
using Robust.Shared.Audio; using Robust.Shared.Audio;
@@ -23,38 +22,37 @@ namespace Content.Server.Fluids.Components
public class BucketComponent : Component, IInteractUsing public class BucketComponent : Component, IInteractUsing
{ {
public override string Name => "Bucket"; public override string Name => "Bucket";
public const string SolutionName = "bucket";
private List<EntityUid> _currentlyUsing = new(); private List<EntityUid> _currentlyUsing = new();
public ReagentUnit MaxVolume public ReagentUnit MaxVolume
{ {
get => Owner.TryGetComponent(out SolutionContainerComponent? solution) ? solution.MaxVolume : ReagentUnit.Zero; get =>
EntitySystem.Get<SolutionContainerSystem>().TryGetSolution(Owner, SolutionName, out var solution)
? solution.MaxVolume
: ReagentUnit.Zero;
set set
{ {
if (Owner.TryGetComponent(out SolutionContainerComponent? solution)) if (EntitySystem.Get<SolutionContainerSystem>().TryGetSolution(Owner, SolutionName, out var solution))
{ {
solution.MaxVolume = value; solution.MaxVolume = value;
} }
} }
} }
public ReagentUnit CurrentVolume => Owner.TryGetComponent(out SolutionContainerComponent? solution) public ReagentUnit CurrentVolume => EntitySystem.Get<SolutionContainerSystem>().TryGetSolution(Owner, SolutionName, out var solution)
? solution.CurrentVolume ? solution.CurrentVolume
: ReagentUnit.Zero; : ReagentUnit.Zero;
[DataField("sound")] [DataField("sound")]
private SoundSpecifier _sound = new SoundPathSpecifier("/Audio/Effects/Fluids/watersplash.ogg"); private SoundSpecifier _sound = new SoundPathSpecifier("/Audio/Effects/Fluids/watersplash.ogg");
/// <inheritdoc />
protected override void Initialize()
{
base.Initialize();
Owner.EnsureComponentWarn<SolutionContainerComponent>();
}
async Task<bool> IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs) async Task<bool> IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs)
{ {
if (!Owner.TryGetComponent(out SolutionContainerComponent? contents) || var solutionsSys = EntitySystem.Get<SolutionContainerSystem>();
if (!solutionsSys.TryGetSolution(Owner, SolutionName, out var contents) ||
_currentlyUsing.Contains(eventArgs.Using.Uid) || _currentlyUsing.Contains(eventArgs.Using.Uid) ||
!eventArgs.Using.TryGetComponent(out MopComponent? mopComponent) || !eventArgs.Using.TryGetComponent(out MopComponent? mopComponent) ||
mopComponent.Mopping) mopComponent.Mopping)
@@ -101,15 +99,15 @@ namespace Content.Server.Fluids.Components
return false; return false;
} }
var mopContents = mopComponent.Contents; var mopContents = mopComponent.MopSolution;
if (mopContents == null) if (mopContents == null)
{ {
return false; return false;
} }
var solution = contents.SplitSolution(transferAmount); var solution = solutionsSys.SplitSolution(Owner.Uid, contents, transferAmount);
if (!mopContents.TryAddSolution(solution)) if (!solutionsSys.TryAddSolution(mopComponent.Owner.Uid, mopContents, solution))
{ {
return false; return false;
} }

View File

@@ -1,10 +1,10 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using Content.Server.Chemistry.Components;
using Content.Server.DoAfter; using Content.Server.DoAfter;
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Chemistry.Reagent; using Content.Shared.Chemistry.Reagent;
using Content.Shared.Interaction; using Content.Shared.Interaction;
using Content.Shared.Interaction.Helpers; using Content.Shared.Interaction.Helpers;
using Content.Shared.Notification;
using Content.Shared.Notification.Managers; using Content.Shared.Notification.Managers;
using Content.Shared.Sound; using Content.Shared.Sound;
using Robust.Shared.Audio; using Robust.Shared.Audio;
@@ -23,28 +23,36 @@ namespace Content.Server.Fluids.Components
public class MopComponent : Component, IAfterInteract public class MopComponent : Component, IAfterInteract
{ {
public override string Name => "Mop"; public override string Name => "Mop";
public const string SolutionName = "mop";
/// <summary> /// <summary>
/// Used to prevent do_after spam if we're currently mopping. /// Used to prevent do_after spam if we're currently mopping.
/// </summary> /// </summary>
public bool Mopping { get; private set; } public bool Mopping { get; private set; }
public SolutionContainerComponent? Contents => Owner.GetComponentOrNull<SolutionContainerComponent>(); public Solution? MopSolution
{
get
{
EntitySystem.Get<SolutionContainerSystem>().TryGetSolution(Owner, SolutionName, out var solution);
return solution;
}
}
public ReagentUnit MaxVolume public ReagentUnit MaxVolume
{ {
get => Owner.GetComponentOrNull<SolutionContainerComponent>()?.MaxVolume ?? ReagentUnit.Zero; get => MopSolution?.MaxVolume ?? ReagentUnit.Zero;
set set
{ {
if (Owner.TryGetComponent(out SolutionContainerComponent? solution)) var solution = MopSolution;
if (solution != null)
{ {
solution.MaxVolume = value; solution.MaxVolume = value;
} }
} }
} }
public ReagentUnit CurrentVolume => public ReagentUnit CurrentVolume => MopSolution?.CurrentVolume ?? ReagentUnit.Zero;
Owner.GetComponentOrNull<SolutionContainerComponent>()?.CurrentVolume ?? ReagentUnit.Zero;
// Currently there's a separate amount for pickup and dropoff so // Currently there's a separate amount for pickup and dropoff so
// Picking up a puddle requires multiple clicks // Picking up a puddle requires multiple clicks
@@ -60,15 +68,7 @@ namespace Content.Server.Fluids.Components
/// Multiplier for the do_after delay for how fast the mop works. /// Multiplier for the do_after delay for how fast the mop works.
/// </summary> /// </summary>
[ViewVariables] [ViewVariables]
[DataField("speed")] [DataField("speed")] private float _mopSpeed = 1;
private float _mopSpeed = 1;
protected override void Initialize()
{
base.Initialize();
Owner.EnsureComponentWarn(out SolutionContainerComponent _);
}
async Task<bool> IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs) async Task<bool> IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs)
{ {
@@ -80,7 +80,7 @@ namespace Content.Server.Fluids.Components
* will spill some of the mop's solution onto the puddle which will evaporate eventually. * will spill some of the mop's solution onto the puddle which will evaporate eventually.
*/ */
if (!Owner.TryGetComponent(out SolutionContainerComponent? contents) || if (!EntitySystem.Get<SolutionContainerSystem>().TryGetSolution(Owner, SolutionName, out var contents ) ||
Mopping || Mopping ||
!eventArgs.InRangeUnobstructed(ignoreInsideBlocker: true, popup: true)) !eventArgs.InRangeUnobstructed(ignoreInsideBlocker: true, popup: true))
{ {
@@ -94,7 +94,8 @@ namespace Content.Server.Fluids.Components
if (currentVolume > 0) if (currentVolume > 0)
{ {
// Drop the liquid on the mop on to the ground // Drop the liquid on the mop on to the ground
contents.SplitSolution(CurrentVolume).SpillAt(eventArgs.ClickLocation, "PuddleSmear"); EntitySystem.Get<SolutionContainerSystem>().SplitSolution(Owner.Uid, contents, CurrentVolume)
.SpillAt(eventArgs.ClickLocation, "PuddleSmear");
return true; return true;
} }
@@ -117,7 +118,8 @@ namespace Content.Server.Fluids.Components
Mopping = true; Mopping = true;
// So if the puddle has 20 units we mop in 2 seconds. Don't just store CurrentVolume given it can change so need to re-calc it anyway. // So if the puddle has 20 units we mop in 2 seconds. Don't just store CurrentVolume given it can change so need to re-calc it anyway.
var doAfterArgs = new DoAfterEventArgs(eventArgs.User, _mopSpeed * puddleVolume.Float() / 10.0f, target: eventArgs.Target) var doAfterArgs = new DoAfterEventArgs(eventArgs.User, _mopSpeed * puddleVolume.Float() / 10.0f,
target: eventArgs.Target)
{ {
BreakOnUserMove = true, BreakOnUserMove = true,
BreakOnStun = true, BreakOnStun = true,
@@ -138,7 +140,9 @@ namespace Content.Server.Fluids.Components
if (transferAmount == 0) if (transferAmount == 0)
{ {
if (puddleComponent.EmptyHolder) //The puddle doesn't actually *have* reagents, for example vomit because there's no "vomit" reagent. if (
puddleComponent
.EmptyHolder) //The puddle doesn't actually *have* reagents, for example vomit because there's no "vomit" reagent.
{ {
puddleComponent.Owner.Delete(); puddleComponent.Owner.Delete();
transferAmount = ReagentUnit.Min(ReagentUnit.New(5), CurrentVolume); transferAmount = ReagentUnit.Min(ReagentUnit.New(5), CurrentVolume);
@@ -154,13 +158,15 @@ namespace Content.Server.Fluids.Components
puddleComponent.SplitSolution(transferAmount); puddleComponent.SplitSolution(transferAmount);
} }
if (puddleCleaned) //After cleaning the puddle, make a new puddle with solution from the mop as a "wet floor". Then evaporate it slowly. if (
puddleCleaned) //After cleaning the puddle, make a new puddle with solution from the mop as a "wet floor". Then evaporate it slowly.
{ {
contents.SplitSolution(transferAmount).SpillAt(eventArgs.ClickLocation, "PuddleSmear"); EntitySystem.Get<SolutionContainerSystem>().SplitSolution(Owner.Uid, contents, transferAmount)
.SpillAt(eventArgs.ClickLocation, "PuddleSmear");
} }
else else
{ {
contents.SplitSolution(transferAmount); EntitySystem.Get<SolutionContainerSystem>().SplitSolution(Owner.Uid, contents, transferAmount);
} }
SoundSystem.Play(Filter.Pvs(Owner), _pickupSound.GetSound(), Owner); SoundSystem.Play(Filter.Pvs(Owner), _pickupSound.GetSound(), Owner);

View File

@@ -3,9 +3,9 @@ using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using Content.Server.Chemistry.Components; using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Chemistry.Reagent; using Content.Shared.Chemistry.Reagent;
using Content.Shared.Chemistry.Solution;
using Content.Shared.Directions; using Content.Shared.Directions;
using Content.Shared.Examine; using Content.Shared.Examine;
using Content.Shared.Maps; using Content.Shared.Maps;
@@ -50,15 +50,22 @@ namespace Content.Server.Fluids.Components
[Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly IRobustRandom _random = default!;
public override string Name => "Puddle"; public override string Name => "Puddle";
public const string DefaultSolutionName = "puddle";
private CancellationTokenSource? _evaporationToken; private CancellationTokenSource? _evaporationToken;
[DataField("evaporate_threshold")]
private ReagentUnit _evaporateThreshold = ReagentUnit.New(20); // How few <Solution Quantity> we can hold prior to self-destructing [DataField("evaporate_threshold")] private ReagentUnit
_evaporateThreshold =
ReagentUnit.New(20); // How few <Solution Quantity> we can hold prior to self-destructing
public ReagentUnit EvaporateThreshold public ReagentUnit EvaporateThreshold
{ {
get => _evaporateThreshold; get => _evaporateThreshold;
set => _evaporateThreshold = value; set => _evaporateThreshold = value;
} }
private ReagentUnit _slipThreshold = ReagentUnit.New(3); private ReagentUnit _slipThreshold = ReagentUnit.New(3);
public ReagentUnit SlipThreshold public ReagentUnit SlipThreshold
{ {
get => _slipThreshold; get => _slipThreshold;
@@ -83,40 +90,44 @@ namespace Content.Server.Fluids.Components
public ReagentUnit MaxVolume public ReagentUnit MaxVolume
{ {
get => _contents.MaxVolume; get => PuddleSolution?.MaxVolume ?? ReagentUnit.Zero;
set => _contents.MaxVolume = value; set
{
if (PuddleSolution != null)
{
PuddleSolution.MaxVolume = value;
}
}
} }
[ViewVariables] [ViewVariables] public ReagentUnit CurrentVolume => PuddleSolution?.CurrentVolume ?? ReagentUnit.Zero;
public ReagentUnit CurrentVolume => _contents.CurrentVolume;
// Volume at which the fluid will try to spill to adjacent components // Volume at which the fluid will try to spill to adjacent components
// Currently a random number, potentially change // Currently a random number, potentially change
public ReagentUnit OverflowVolume => _overflowVolume; public ReagentUnit OverflowVolume => _overflowVolume;
[ViewVariables]
[DataField("overflow_volume")] [ViewVariables] [DataField("overflow_volume")]
private ReagentUnit _overflowVolume = ReagentUnit.New(20); private ReagentUnit _overflowVolume = ReagentUnit.New(20);
private ReagentUnit OverflowLeft => CurrentVolume - OverflowVolume; private ReagentUnit OverflowLeft => CurrentVolume - OverflowVolume;
private SolutionContainerComponent _contents = default!; public bool EmptyHolder => PuddleSolution?.Contents.Count == 0;
public bool EmptyHolder => _contents.ReagentList.Count == 0;
[DataField("variants")]
private int _spriteVariants = 1;
// Whether the underlying solution color should be used
[DataField("recolor")]
private bool _recolor = default;
[DataField("state")] [DataField("variants")] private int _spriteVariants = 1;
private string _spriteState = "puddle";
// Whether the underlying solution color should be used
[DataField("recolor")] private bool _recolor = default;
[DataField("state")] private string _spriteState = "puddle";
private bool Slippery => Owner.TryGetComponent(out SlipperyComponent? slippery) && slippery.Slippery; private bool Slippery => Owner.TryGetComponent(out SlipperyComponent? slippery) && slippery.Slippery;
private Solution? PuddleSolution => EntitySystem.Get<SolutionContainerSystem>().EnsureSolution(Owner, DefaultSolutionName);
protected override void Initialize() protected override void Initialize()
{ {
base.Initialize(); base.Initialize();
_contents = Owner.EnsureComponentWarn<SolutionContainerComponent>();
// Smaller than 1m^3 for now but realistically this shouldn't be hit // Smaller than 1m^3 for now but realistically this shouldn't be hit
MaxVolume = ReagentUnit.New(1000); MaxVolume = ReagentUnit.New(1000);
@@ -143,7 +154,7 @@ namespace Content.Server.Fluids.Components
void IExamine.Examine(FormattedMessage message, bool inDetailsRange) void IExamine.Examine(FormattedMessage message, bool inDetailsRange)
{ {
if(Slippery) if (Slippery)
{ {
message.AddText(Loc.GetString("puddle-component-examine-is-slipper-text")); message.AddText(Loc.GetString("puddle-component-examine-is-slipper-text"));
} }
@@ -160,13 +171,15 @@ namespace Content.Server.Fluids.Components
} }
// Flow rate should probably be controlled globally so this is it for now // Flow rate should probably be controlled globally so this is it for now
internal bool TryAddSolution(Solution solution, bool sound = true, bool checkForEvaporate = true, bool checkForOverflow = true) internal bool TryAddSolution(Solution solution, bool sound = true, bool checkForEvaporate = true,
bool checkForOverflow = true)
{ {
if (solution.TotalVolume == 0) if (solution.TotalVolume == 0)
{ {
return false; return false;
} }
var result = _contents.TryAddSolution(solution);
var result = EntitySystem.Get<SolutionContainerSystem>().TryAddSolution(Owner.Uid, PuddleSolution, solution);
if (!result) if (!result)
{ {
return false; return false;
@@ -194,12 +207,15 @@ namespace Content.Server.Fluids.Components
return true; return true;
} }
internal Solution SplitSolution(ReagentUnit quantity) internal void SplitSolution(ReagentUnit quantity)
{ {
var split = _contents.SplitSolution(quantity); if (PuddleSolution != null)
CheckEvaporate(); {
UpdateAppearance(); EntitySystem.Get<SolutionContainerSystem>().SplitSolution(Owner.Uid, PuddleSolution, quantity);
return split; CheckEvaporate();
UpdateAppearance();
}
} }
public void CheckEvaporate() public void CheckEvaporate()
@@ -212,7 +228,12 @@ namespace Content.Server.Fluids.Components
public void Evaporate() public void Evaporate()
{ {
_contents.SplitSolution(ReagentUnit.Min(ReagentUnit.New(1), _contents.CurrentVolume)); if (PuddleSolution != null)
{
EntitySystem.Get<SolutionContainerSystem>().SplitSolution(Owner.Uid, PuddleSolution,
ReagentUnit.Min(ReagentUnit.New(1), PuddleSolution.CurrentVolume));
}
if (CurrentVolume == 0) if (CurrentVolume == 0)
{ {
Owner.Delete(); Owner.Delete();
@@ -226,7 +247,7 @@ namespace Content.Server.Fluids.Components
public void UpdateStatus() public void UpdateStatus()
{ {
_evaporationToken?.Cancel(); _evaporationToken?.Cancel();
if(Owner.Deleted) return; if (Owner.Deleted) return;
UpdateAppearance(); UpdateAppearance();
UpdateSlip(); UpdateSlip();
@@ -262,15 +283,16 @@ namespace Content.Server.Fluids.Components
{ {
return; return;
} }
// Opacity based on level of fullness to overflow // Opacity based on level of fullness to overflow
// Hard-cap lower bound for visibility reasons // Hard-cap lower bound for visibility reasons
var volumeScale = (CurrentVolume.Float() / OverflowVolume.Float()) * 0.75f + 0.25f; var volumeScale = (CurrentVolume.Float() / OverflowVolume.Float()) * 0.75f + 0.25f;
var cappedScale = Math.Min(1.0f, volumeScale); var cappedScale = Math.Min(1.0f, volumeScale);
// Color based on the underlying solutioncomponent // Color based on the underlying solutioncomponent
Color newColor; Color newColor;
if (_recolor) if (_recolor && PuddleSolution != null)
{ {
newColor = _contents.Color.WithAlpha(cappedScale); newColor = PuddleSolution.Color.WithAlpha(cappedScale);
} }
else else
{ {
@@ -287,12 +309,10 @@ namespace Content.Server.Fluids.Components
/// </summary> /// </summary>
private void CheckOverflow() private void CheckOverflow()
{ {
if (CurrentVolume <= _overflowVolume || _overflown) if (PuddleSolution == null || CurrentVolume <= _overflowVolume || _overflown)
{
return; return;
}
var nextPuddles = new List<PuddleComponent>() {this}; var nextPuddles = new List<PuddleComponent>() { this };
var overflownPuddles = new List<PuddleComponent>(); var overflownPuddles = new List<PuddleComponent>();
while (OverflowLeft > ReagentUnit.Zero && nextPuddles.Count > 0) while (OverflowLeft > ReagentUnit.Zero && nextPuddles.Count > 0)
@@ -321,7 +341,7 @@ namespace Content.Server.Fluids.Components
{ {
var adjacentPuddle = adjacent(); var adjacentPuddle = adjacent();
var quantity = ReagentUnit.Min(overflowSplit, adjacentPuddle.OverflowVolume); var quantity = ReagentUnit.Min(overflowSplit, adjacentPuddle.OverflowVolume);
var spillAmount = _contents.SplitSolution(quantity); var spillAmount = EntitySystem.Get<SolutionContainerSystem>().SplitSolution(Owner.Uid, PuddleSolution, quantity);
adjacentPuddle.TryAddSolution(spillAmount, false, false, false); adjacentPuddle.TryAddSolution(spillAmount, false, false, false);
nextPuddles.Add(adjacentPuddle); nextPuddles.Add(adjacentPuddle);
@@ -390,7 +410,9 @@ namespace Content.Server.Fluids.Components
if (puddle == default) if (puddle == default)
{ {
puddle = () => Owner.EntityManager.SpawnEntity(Owner.Prototype?.ID, mapGrid.DirectionToGrid(coords, direction)).GetComponent<PuddleComponent>(); puddle = () =>
Owner.EntityManager.SpawnEntity(Owner.Prototype?.ID, mapGrid.DirectionToGrid(coords, direction))
.GetComponent<PuddleComponent>();
} }
return true; return true;

View File

@@ -1,9 +1,9 @@
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Linq; using System.Linq;
using Content.Server.Coordinates.Helpers; using Content.Server.Coordinates.Helpers;
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Chemistry.Reagent; using Content.Shared.Chemistry.Reagent;
using Content.Shared.Chemistry.Solution;
using Content.Shared.Chemistry.Solution.Components;
using Robust.Server.GameObjects; using Robust.Server.GameObjects;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.IoC; using Robust.Shared.IoC;
@@ -23,7 +23,8 @@ namespace Content.Server.Fluids.Components
/// <param name="prototype">The prototype to use.</param> /// <param name="prototype">The prototype to use.</param>
/// <param name="sound">Play the spill sound.</param> /// <param name="sound">Play the spill sound.</param>
/// <returns>The puddle if one was created, null otherwise.</returns> /// <returns>The puddle if one was created, null otherwise.</returns>
public static PuddleComponent? SpillAt(this Solution solution, IEntity entity, string prototype, bool sound = true) public static PuddleComponent? SpillAt(this Solution solution, IEntity entity, string prototype,
bool sound = true)
{ {
return solution.SpillAt(entity.Transform.Coordinates, prototype, sound); return solution.SpillAt(entity.Transform.Coordinates, prototype, sound);
} }
@@ -39,7 +40,8 @@ namespace Content.Server.Fluids.Components
/// <param name="puddle">The puddle if one was created, null otherwise.</param> /// <param name="puddle">The puddle if one was created, null otherwise.</param>
/// <param name="sound">Play the spill sound.</param> /// <param name="sound">Play the spill sound.</param>
/// <returns>True if a puddle was created, false otherwise.</returns> /// <returns>True if a puddle was created, false otherwise.</returns>
public static bool TrySpillAt(this Solution solution, IEntity entity, string prototype, [NotNullWhen(true)] out PuddleComponent? puddle, bool sound = true) public static bool TrySpillAt(this Solution solution, IEntity entity, string prototype,
[NotNullWhen(true)] out PuddleComponent? puddle, bool sound = true)
{ {
puddle = solution.SpillAt(entity, prototype, sound); puddle = solution.SpillAt(entity, prototype, sound);
return puddle != null; return puddle != null;
@@ -53,14 +55,16 @@ namespace Content.Server.Fluids.Components
/// <param name="prototype">The prototype to use.</param> /// <param name="prototype">The prototype to use.</param>
/// <param name="sound">Whether or not to play the spill sound.</param> /// <param name="sound">Whether or not to play the spill sound.</param>
/// <returns>The puddle if one was created, null otherwise.</returns> /// <returns>The puddle if one was created, null otherwise.</returns>
public static PuddleComponent? SpillAt(this Solution solution, EntityCoordinates coordinates, string prototype, bool overflow = true, bool sound = true) public static PuddleComponent? SpillAt(this Solution solution, EntityCoordinates coordinates, string prototype,
bool overflow = true, bool sound = true)
{ {
if (solution.TotalVolume == 0) return null; if (solution.TotalVolume == 0) return null;
var mapManager = IoCManager.Resolve<IMapManager>(); var mapManager = IoCManager.Resolve<IMapManager>();
var entityManager = IoCManager.Resolve<IEntityManager>(); var entityManager = IoCManager.Resolve<IEntityManager>();
if (!mapManager.TryGetGrid(coordinates.GetGridId(entityManager), out var mapGrid)) return null; // Let's not spill to space. if (!mapManager.TryGetGrid(coordinates.GetGridId(entityManager), out var mapGrid))
return null; // Let's not spill to space.
return SpillAt(mapGrid.GetTileRef(coordinates), solution, prototype, overflow, sound); return SpillAt(mapGrid.GetTileRef(coordinates), solution, prototype, overflow, sound);
} }
@@ -74,13 +78,15 @@ namespace Content.Server.Fluids.Components
/// <param name="puddle">The puddle if one was created, null otherwise.</param> /// <param name="puddle">The puddle if one was created, null otherwise.</param>
/// <param name="sound">Play the spill sound.</param> /// <param name="sound">Play the spill sound.</param>
/// <returns>True if a puddle was created, false otherwise.</returns> /// <returns>True if a puddle was created, false otherwise.</returns>
public static bool TrySpillAt(this Solution solution, EntityCoordinates coordinates, string prototype, [NotNullWhen(true)] out PuddleComponent? puddle, bool sound = true) public static bool TrySpillAt(this Solution solution, EntityCoordinates coordinates, string prototype,
[NotNullWhen(true)] out PuddleComponent? puddle, bool sound = true)
{ {
puddle = solution.SpillAt(coordinates, prototype, sound); puddle = solution.SpillAt(coordinates, prototype, sound);
return puddle != null; return puddle != null;
} }
public static bool TryGetPuddle(this TileRef tileRef, GridTileLookupSystem? gridTileLookupSystem, [NotNullWhen(true)] out PuddleComponent? puddle) public static bool TryGetPuddle(this TileRef tileRef, GridTileLookupSystem? gridTileLookupSystem,
[NotNullWhen(true)] out PuddleComponent? puddle)
{ {
foreach (var entity in tileRef.GetEntitiesInTileFast(gridTileLookupSystem)) foreach (var entity in tileRef.GetEntitiesInTileFast(gridTileLookupSystem))
{ {
@@ -95,7 +101,8 @@ namespace Content.Server.Fluids.Components
return false; return false;
} }
public static PuddleComponent? SpillAt(this TileRef tileRef, Solution solution, string prototype, bool overflow = true, bool sound = true) public static PuddleComponent? SpillAt(this TileRef tileRef, Solution solution, string prototype,
bool overflow = true, bool sound = true)
{ {
if (solution.TotalVolume <= 0) return null; if (solution.TotalVolume <= 0) return null;
@@ -116,15 +123,18 @@ namespace Content.Server.Fluids.Components
PuddleComponent? puddle = null; PuddleComponent? puddle = null;
var spilt = false; var spilt = false;
var spillEntities = IoCManager.Resolve<IEntityLookup>().GetEntitiesIntersecting(mapGrid.ParentMapId, spillGridCoords.Position).ToArray(); var spillEntities = IoCManager.Resolve<IEntityLookup>()
.GetEntitiesIntersecting(mapGrid.ParentMapId, spillGridCoords.Position).ToArray();
foreach (var spillEntity in spillEntities) foreach (var spillEntity in spillEntities)
{ {
if (spillEntity.TryGetComponent(out ISolutionInteractionsComponent? solutionContainerComponent) && if (EntitySystem.Get<SolutionContainerSystem>()
solutionContainerComponent.CanRefill) .TryGetRefillableSolution(spillEntity.Uid, out var solutionContainerComponent))
{ {
solutionContainerComponent.Refill( EntitySystem.Get<SolutionContainerSystem>().Refill(spillEntity.Uid, solutionContainerComponent,
solution.SplitSolution(ReagentUnit.Min(solutionContainerComponent.RefillSpaceAvailable, solutionContainerComponent.MaxSpillRefill)) solution.SplitSolution(ReagentUnit.Min(
); solutionContainerComponent.AvailableVolume,
solutionContainerComponent.MaxSpillRefill))
);
} }
} }

View File

@@ -1,9 +1,8 @@
using Content.Shared.ActionBlocker; using Content.Shared.ActionBlocker;
using Content.Shared.Chemistry.Components.SolutionManager;
using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Chemistry.Reagent; using Content.Shared.Chemistry.Reagent;
using Content.Shared.Chemistry.Solution.Components;
using Content.Shared.DragDrop;
using Content.Shared.Interaction; using Content.Shared.Interaction;
using Content.Shared.Interaction.Events;
using Content.Shared.Notification.Managers; using Content.Shared.Notification.Managers;
using Content.Shared.Verbs; using Content.Shared.Verbs;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
@@ -15,6 +14,7 @@ namespace Content.Server.Fluids.Components
public class SpillableComponent : Component, IDropped public class SpillableComponent : Component, IDropped
{ {
public override string Name => "Spillable"; public override string Name => "Spillable";
public const string SolutionName = "puddle";
/// <summary> /// <summary>
/// Transfers solution from the held container to the floor. /// Transfers solution from the held container to the floor.
@@ -25,8 +25,8 @@ namespace Content.Server.Fluids.Components
protected override void GetData(IEntity user, SpillableComponent component, VerbData data) protected override void GetData(IEntity user, SpillableComponent component, VerbData data)
{ {
if (!EntitySystem.Get<ActionBlockerSystem>().CanInteract(user) || if (!EntitySystem.Get<ActionBlockerSystem>().CanInteract(user) ||
!component.Owner.TryGetComponent(out ISolutionInteractionsComponent? solutionComponent) || !EntitySystem.Get<SolutionContainerSystem>()
!solutionComponent.CanDrain) .TryGetDrainableSolution(component.Owner.Uid, out var solutionComponent))
{ {
data.Visibility = VerbVisibility.Invisible; data.Visibility = VerbVisibility.Invisible;
return; return;
@@ -40,30 +40,40 @@ namespace Content.Server.Fluids.Components
protected override void Activate(IEntity user, SpillableComponent component) protected override void Activate(IEntity user, SpillableComponent component)
{ {
if (component.Owner.TryGetComponent<ISolutionInteractionsComponent>(out var solutionComponent)) var solutionsSys = EntitySystem.Get<SolutionContainerSystem>();
if (component.Owner.HasComponent<SolutionContainerManagerComponent>())
{ {
if (!solutionComponent.CanDrain) if (solutionsSys.TryGetDrainableSolution(component.Owner.Uid, out var solutionComponent))
{
if (solutionComponent.DrainAvailable <= 0)
{
user.PopupMessage(user,
Loc.GetString("spill-target-verb-activate-is-empty-message", ("owner", component.Owner)));
}
// Need this as when we split the component's owner may be deleted
EntitySystem.Get<SolutionContainerSystem>()
.Drain(component.Owner.Uid, solutionComponent, solutionComponent.DrainAvailable)
.SpillAt(component.Owner.Transform.Coordinates, "PuddleSmear");
}
else
{ {
user.PopupMessage(user, user.PopupMessage(user,
Loc.GetString("spill-target-verb-activate-cannot-drain-message",("owner", component.Owner))); Loc.GetString("spill-target-verb-activate-cannot-drain-message",
("owner", component.Owner)));
} }
if (solutionComponent.DrainAvailable <= 0)
{
user.PopupMessage(user, Loc.GetString("spill-target-verb-activate-is-empty-message",("owner", component.Owner)));
}
// Need this as when we split the component's owner may be deleted
solutionComponent.Drain(solutionComponent.DrainAvailable).SpillAt(component.Owner.Transform.Coordinates, "PuddleSmear");
} }
} }
} }
void IDropped.Dropped(DroppedEventArgs eventArgs) void IDropped.Dropped(DroppedEventArgs eventArgs)
{ {
if (!eventArgs.Intentional && Owner.TryGetComponent(out ISolutionInteractionsComponent? solutionComponent)) if (!eventArgs.Intentional
&& EntitySystem.Get<SolutionContainerSystem>().TryGetSolution(Owner, SolutionName, out var solutionComponent))
{ {
solutionComponent.Drain(solutionComponent.DrainAvailable).SpillAt(Owner.Transform.Coordinates, "PuddleSmear"); EntitySystem.Get<SolutionContainerSystem>()
.Drain(Owner.Uid, solutionComponent, solutionComponent.DrainAvailable)
.SpillAt(Owner.Transform.Coordinates, "PuddleSmear");
} }
} }
} }

View File

@@ -4,9 +4,9 @@ using Content.Server.Chemistry.Components;
using Content.Server.Chemistry.EntitySystems; using Content.Server.Chemistry.EntitySystems;
using Content.Shared.ActionBlocker; using Content.Shared.ActionBlocker;
using Content.Shared.Audio; using Content.Shared.Audio;
using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Chemistry.Reagent; using Content.Shared.Chemistry.Reagent;
using Content.Shared.Cooldown; using Content.Shared.Cooldown;
using Content.Shared.DragDrop;
using Content.Shared.Fluids; using Content.Shared.Fluids;
using Content.Shared.Interaction; using Content.Shared.Interaction;
using Content.Shared.Notification.Managers; using Content.Shared.Notification.Managers;
@@ -30,6 +30,7 @@ namespace Content.Server.Fluids.Components
internal sealed class SprayComponent : SharedSprayComponent, IAfterInteract, IUse, IActivate, IDropped internal sealed class SprayComponent : SharedSprayComponent, IAfterInteract, IUse, IActivate, IDropped
{ {
public const float SprayDistance = 3f; public const float SprayDistance = 3f;
public const string SolutionName = "spray";
[Dependency] private readonly IGameTiming _gameTiming = default!; [Dependency] private readonly IGameTiming _gameTiming = default!;
@@ -82,14 +83,19 @@ namespace Content.Server.Fluids.Components
[DataField("safetySound")] [DataField("safetySound")]
public SoundSpecifier SafetySound { get; } = new SoundPathSpecifier("/Audio/Machines/button.ogg"); public SoundSpecifier SafetySound { get; } = new SoundPathSpecifier("/Audio/Machines/button.ogg");
public ReagentUnit CurrentVolume => Owner.GetComponentOrNull<SolutionContainerComponent>()?.CurrentVolume ?? ReagentUnit.Zero;
public ReagentUnit CurrentVolume {
get
{
EntitySystem.Get<SolutionContainerSystem>().TryGetSolution(Owner, SolutionName, out var solution);
return solution?.CurrentVolume ?? ReagentUnit.Zero;
}
}
protected override void Initialize() protected override void Initialize()
{ {
base.Initialize(); base.Initialize();
Owner.EnsureComponentWarn(out SolutionContainerComponent _);
if (_hasSafety) if (_hasSafety)
{ {
SetSafety(Owner, _safety); SetSafety(Owner, _safety);
@@ -124,7 +130,7 @@ namespace Content.Server.Fluids.Components
if (eventArgs.ClickLocation.GetGridId(entManager) != playerPos.GetGridId(entManager)) if (eventArgs.ClickLocation.GetGridId(entManager) != playerPos.GetGridId(entManager))
return true; return true;
if (!Owner.TryGetComponent(out SolutionContainerComponent? contents)) if (!EntitySystem.Get<SolutionContainerSystem>().TryGetSolution(Owner, SolutionName, out var contents))
return true; return true;
var direction = (eventArgs.ClickLocation.Position - playerPos.Position).Normalized; var direction = (eventArgs.ClickLocation.Position - playerPos.Position).Normalized;
@@ -148,7 +154,7 @@ namespace Content.Server.Fluids.Components
if (target.TryDistance(Owner.EntityManager, playerPos, out var distance) && distance > SprayDistance) if (target.TryDistance(Owner.EntityManager, playerPos, out var distance) && distance > SprayDistance)
target = eventArgs.User.Transform.Coordinates.Offset(diffNorm * SprayDistance); target = eventArgs.User.Transform.Coordinates.Offset(diffNorm * SprayDistance);
var solution = contents.SplitSolution(_transferAmount); var solution = EntitySystem.Get<SolutionContainerSystem>().SplitSolution(Owner.Uid, contents, _transferAmount);
if (solution.TotalVolume <= ReagentUnit.Zero) if (solution.TotalVolume <= ReagentUnit.Zero)
break; break;

View File

@@ -0,0 +1,27 @@
using Content.Server.Kitchen.EntitySystems;
using Content.Shared.Chemistry.Components;
using Robust.Shared.Analyzers;
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
namespace Content.Server.Kitchen.Components
{
/// <summary>
/// Tag component that denotes an entity as Extractable
/// </summary>
[RegisterComponent]
[Friend(typeof(ReagentGrinderSystem))]
public class ExtractableComponent : Component
{
public override string Name => "Extractable";
[ViewVariables]
[DataField("result")]
public Solution ResultSolution = new();
[ViewVariables]
[DataField("extractableSolution")]
public string? GrindableSolution;
}
}

View File

@@ -1,17 +0,0 @@
using Content.Shared.Chemistry.Solution;
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
namespace Content.Server.Kitchen.Components
{
/// <summary>
/// Tag component that denotes an entity as Juiceable
/// </summary>
[RegisterComponent]
public class JuiceableComponent : Component
{
public override string Name => "Juiceable";
[ViewVariables] [DataField("result")] public Solution JuiceResultSolution = new();
}
}

View File

@@ -1,4 +1,3 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
@@ -13,14 +12,12 @@ using Content.Server.UserInterface;
using Content.Shared.Acts; using Content.Shared.Acts;
using Content.Shared.Body.Components; using Content.Shared.Body.Components;
using Content.Shared.Body.Part; using Content.Shared.Body.Part;
using Content.Shared.Chemistry; using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Chemistry.Reagent; using Content.Shared.Chemistry.Reagent;
using Content.Shared.Chemistry.Solution;
using Content.Shared.Chemistry.Solution.Components;
using Content.Shared.Interaction; using Content.Shared.Interaction;
using Content.Shared.Kitchen; using Content.Shared.Kitchen;
using Content.Shared.Kitchen.Components; using Content.Shared.Kitchen.Components;
using Content.Shared.Notification;
using Content.Shared.Notification.Managers; using Content.Shared.Notification.Managers;
using Content.Shared.Power; using Content.Shared.Power;
using Content.Shared.Sound; using Content.Shared.Sound;
@@ -38,27 +35,28 @@ namespace Content.Server.Kitchen.Components
{ {
[RegisterComponent] [RegisterComponent]
[ComponentReference(typeof(IActivate))] [ComponentReference(typeof(IActivate))]
public class MicrowaveComponent : SharedMicrowaveComponent, IActivate, IInteractUsing, ISolutionChange, ISuicideAct, IBreakAct public class MicrowaveComponent : SharedMicrowaveComponent, IActivate, IInteractUsing, ISuicideAct, IBreakAct
{ {
[Dependency] private readonly RecipeManager _recipeManager = default!; [Dependency] private readonly RecipeManager _recipeManager = default!;
#region YAMLSERIALIZE #region YAMLSERIALIZE
[DataField("cookTime")]
private uint _cookTimeDefault = 5; [DataField("cookTime")] private uint _cookTimeDefault = 5;
[DataField("cookTimeMultiplier")] [DataField("cookTimeMultiplier")] private int _cookTimeMultiplier = 1000; //For upgrades and stuff I guess?
private int _cookTimeMultiplier = 1000; //For upgrades and stuff I guess? [DataField("failureResult")] private string _badRecipeName = "FoodBadRecipe";
[DataField("failureResult")]
private string _badRecipeName = "FoodBadRecipe"; [DataField("beginCookingSound")] private SoundSpecifier _startCookingSound =
[DataField("beginCookingSound")] new SoundPathSpecifier("/Audio/Machines/microwave_start_beep.ogg");
private SoundSpecifier _startCookingSound = new SoundPathSpecifier("/Audio/Machines/microwave_start_beep.ogg");
[DataField("foodDoneSound")] [DataField("foodDoneSound")] private SoundSpecifier _cookingCompleteSound =
private SoundSpecifier _cookingCompleteSound = new SoundPathSpecifier("/Audio/Machines/microwave_done_beep.ogg"); new SoundPathSpecifier("/Audio/Machines/microwave_done_beep.ogg");
[DataField("clickSound")] [DataField("clickSound")]
private SoundSpecifier _clickSound = new SoundPathSpecifier("/Audio/Machines/machine_switch.ogg"); private SoundSpecifier _clickSound = new SoundPathSpecifier("/Audio/Machines/machine_switch.ogg");
#endregion YAMLSERIALIZE #endregion YAMLSERIALIZE
[ViewVariables] [ViewVariables] private bool _busy = false;
private bool _busy = false;
private bool _broken; private bool _broken;
/// <summary> /// <summary>
@@ -66,18 +64,25 @@ namespace Content.Server.Kitchen.Components
/// The cook times for all recipes should be divisible by 5,with a minimum of 1 second. /// The cook times for all recipes should be divisible by 5,with a minimum of 1 second.
/// For right now, I don't think any recipe cook time should be greater than 60 seconds. /// For right now, I don't think any recipe cook time should be greater than 60 seconds.
/// </summary> /// </summary>
[ViewVariables] [ViewVariables] private uint _currentCookTimerTime = 1;
private uint _currentCookTimerTime = 1;
private bool Powered => !Owner.TryGetComponent(out ApcPowerReceiverComponent? receiver) || receiver.Powered; private bool Powered => !Owner.TryGetComponent(out ApcPowerReceiverComponent? receiver) || receiver.Powered;
private bool _hasContents => Owner.TryGetComponent(out SolutionContainerComponent? solution) && (solution.ReagentList.Count > 0 || _storage.ContainedEntities.Count > 0);
private bool _uiDirty = true;
private bool _lostPower = false;
private int _currentCookTimeButtonIndex = 0;
void ISolutionChange.SolutionChanged(SolutionChangeEventArgs eventArgs) => _uiDirty = true; private bool HasContents => EntitySystem.Get<SolutionContainerSystem>()
private AudioSystem _audioSystem = default!; .TryGetSolution(Owner, SolutionName, out var solution) &&
(solution.Contents.Count > 0 || _storage.ContainedEntities.Count > 0);
private bool _uiDirty = true;
private bool _lostPower;
private int _currentCookTimeButtonIndex;
public void DirtyUi()
{
_uiDirty = true;
}
private Container _storage = default!; private Container _storage = default!;
private const string SolutionName = "microwave";
[ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(MicrowaveUiKey.Key); [ViewVariables] private BoundUserInterface? UserInterface => Owner.GetUIOrNull(MicrowaveUiKey.Key);
@@ -87,10 +92,10 @@ namespace Content.Server.Kitchen.Components
_currentCookTimerTime = _cookTimeDefault; _currentCookTimerTime = _cookTimeDefault;
Owner.EnsureComponent<SolutionContainerComponent>(); EntitySystem.Get<SolutionContainerSystem>().EnsureSolution(Owner, SolutionName);
_storage = ContainerHelpers.EnsureContainer<Container>(Owner, "microwave_entity_container", out var existed); _storage = ContainerHelpers.EnsureContainer<Container>(Owner, "microwave_entity_container",
_audioSystem = EntitySystem.Get<AudioSystem>(); out _);
if (UserInterface != null) if (UserInterface != null)
{ {
@@ -107,33 +112,36 @@ namespace Content.Server.Kitchen.Components
switch (message.Message) switch (message.Message)
{ {
case MicrowaveStartCookMessage msg: case MicrowaveStartCookMessage:
Wzhzhzh(); Wzhzhzh();
break; break;
case MicrowaveEjectMessage msg: case MicrowaveEjectMessage:
if (_hasContents) if (HasContents)
{ {
VaporizeReagents(); VaporizeReagents();
EjectSolids(); EjectSolids();
ClickSound(); ClickSound();
_uiDirty = true; _uiDirty = true;
} }
break; break;
case MicrowaveEjectSolidIndexedMessage msg: case MicrowaveEjectSolidIndexedMessage msg:
if (_hasContents) if (HasContents)
{ {
EjectSolid(msg.EntityID); EjectSolid(msg.EntityID);
ClickSound(); ClickSound();
_uiDirty = true; _uiDirty = true;
} }
break; break;
case MicrowaveVaporizeReagentIndexedMessage msg: case MicrowaveVaporizeReagentIndexedMessage msg:
if (_hasContents) if (HasContents)
{ {
VaporizeReagentQuantity(msg.ReagentQuantity); VaporizeReagentQuantity(msg.ReagentQuantity);
ClickSound(); ClickSound();
_uiDirty = true; _uiDirty = true;
} }
break; break;
case MicrowaveSelectCookTimeMessage msg: case MicrowaveSelectCookTimeMessage msg:
_currentCookTimeButtonIndex = msg.ButtonIndex; _currentCookTimeButtonIndex = msg.ButtonIndex;
@@ -142,12 +150,10 @@ namespace Content.Server.Kitchen.Components
_uiDirty = true; _uiDirty = true;
break; break;
} }
} }
public void OnUpdate() public void OnUpdate()
{ {
if (!Powered) if (!Powered)
{ {
//TODO:If someone cuts power currently, microwave magically keeps going. FIX IT! //TODO:If someone cuts power currently, microwave magically keeps going. FIX IT!
@@ -175,11 +181,12 @@ namespace Content.Server.Kitchen.Components
_uiDirty = true; _uiDirty = true;
} }
if (_uiDirty && Owner.TryGetComponent(out SolutionContainerComponent? solution)) if (_uiDirty && EntitySystem.Get<SolutionContainerSystem>()
.TryGetSolution(Owner, SolutionName, out var solution))
{ {
UserInterface?.SetState(new MicrowaveUpdateUserInterfaceState UserInterface?.SetState(new MicrowaveUpdateUserInterfaceState
( (
solution.Solution.Contents.ToArray(), solution.Contents.ToArray(),
_storage.ContainedEntities.Select(item => item.Uid).ToArray(), _storage.ContainedEntities.Select(item => item.Uid).ToArray(),
_busy, _busy,
_currentCookTimeButtonIndex, _currentCookTimeButtonIndex,
@@ -244,40 +251,41 @@ namespace Content.Server.Kitchen.Components
if (itemEntity.TryGetComponent<SolutionTransferComponent>(out var attackPourable)) if (itemEntity.TryGetComponent<SolutionTransferComponent>(out var attackPourable))
{ {
if (!itemEntity.TryGetComponent<ISolutionInteractionsComponent>(out var attackSolution) var solutionsSystem = EntitySystem.Get<SolutionContainerSystem>();
|| !attackSolution.CanDrain) if (!solutionsSystem.TryGetDrainableSolution(itemEntity.Uid, out var attackSolution))
{ {
return false; return false;
} }
if (!Owner.TryGetComponent(out SolutionContainerComponent? solution)) if (!solutionsSystem.TryGetSolution(Owner, SolutionName, out var solution))
{ {
return false; return false;
} }
//Get transfer amount. May be smaller than _transferAmount if not enough room //Get transfer amount. May be smaller than _transferAmount if not enough room
var realTransferAmount = ReagentUnit.Min(attackPourable.TransferAmount, solution.EmptyVolume); var realTransferAmount = ReagentUnit.Min(attackPourable.TransferAmount, solution.AvailableVolume);
if (realTransferAmount <= 0) //Special message if container is full if (realTransferAmount <= 0) //Special message if container is full
{ {
Owner.PopupMessage(eventArgs.User, Loc.GetString("microwave-component-interact-using-container-full")); Owner.PopupMessage(eventArgs.User,
Loc.GetString("microwave-component-interact-using-container-full"));
return false; return false;
} }
//Move units from attackSolution to targetSolution //Move units from attackSolution to targetSolution
var removedSolution = attackSolution.Drain(realTransferAmount); var removedSolution = EntitySystem.Get<SolutionContainerSystem>()
if (!solution.TryAddSolution(removedSolution)) .Drain(itemEntity.Uid, attackSolution, realTransferAmount);
if (!EntitySystem.Get<SolutionContainerSystem>().TryAddSolution(Owner.Uid, solution, removedSolution))
{ {
return false; return false;
} }
Owner.PopupMessage(eventArgs.User, Loc.GetString("microwave-component-interact-using-transfer-success", Owner.PopupMessage(eventArgs.User, Loc.GetString("microwave-component-interact-using-transfer-success",
("amount", removedSolution.TotalVolume))); ("amount", removedSolution.TotalVolume)));
return true; return true;
} }
if (!itemEntity.TryGetComponent(typeof(ItemComponent), out var food)) if (!itemEntity.TryGetComponent(typeof(ItemComponent), out var food))
{ {
Owner.PopupMessage(eventArgs.User, "microwave-component-interact-using-transfer-fail"); Owner.PopupMessage(eventArgs.User, "microwave-component-interact-using-transfer-fail");
return false; return false;
} }
@@ -292,7 +300,7 @@ namespace Content.Server.Kitchen.Components
// ReSharper disable once IdentifierTypo // ReSharper disable once IdentifierTypo
private void Wzhzhzh() private void Wzhzhzh()
{ {
if (!_hasContents) if (!HasContents)
{ {
return; return;
} }
@@ -331,14 +339,15 @@ namespace Content.Server.Kitchen.Components
// Check recipes // Check recipes
FoodRecipePrototype? recipeToCook = null; FoodRecipePrototype? recipeToCook = null;
foreach (var r in _recipeManager.Recipes.Where(r => CanSatisfyRecipe(r, solidsDict) == MicrowaveSuccessState.RecipePass)) foreach (var r in _recipeManager.Recipes.Where(r =>
CanSatisfyRecipe(r, solidsDict) == MicrowaveSuccessState.RecipePass))
{ {
recipeToCook = r; recipeToCook = r;
} }
SetAppearance(MicrowaveVisualState.Cooking); SetAppearance(MicrowaveVisualState.Cooking);
SoundSystem.Play(Filter.Pvs(Owner), _startCookingSound.GetSound(), Owner, AudioParams.Default); SoundSystem.Play(Filter.Pvs(Owner), _startCookingSound.GetSound(), Owner, AudioParams.Default);
Owner.SpawnTimer((int) (_currentCookTimerTime * _cookTimeMultiplier), (Action) (() => Owner.SpawnTimer((int) (_currentCookTimerTime * _cookTimeMultiplier), () =>
{ {
if (_lostPower) if (_lostPower)
{ {
@@ -372,24 +381,25 @@ namespace Content.Server.Kitchen.Components
_busy = false; _busy = false;
_uiDirty = true; _uiDirty = true;
})); });
_lostPower = false; _lostPower = false;
_uiDirty = true; _uiDirty = true;
} }
private void VaporizeReagents() private void VaporizeReagents()
{ {
if (Owner.TryGetComponent(out SolutionContainerComponent? solution)) if (EntitySystem.Get<SolutionContainerSystem>().TryGetSolution(Owner, SolutionName, out var solution))
{ {
solution.RemoveAllSolution(); EntitySystem.Get<SolutionContainerSystem>().RemoveAllSolution(Owner.Uid, solution);
} }
} }
private void VaporizeReagentQuantity(Solution.ReagentQuantity reagentQuantity) private void VaporizeReagentQuantity(Solution.ReagentQuantity reagentQuantity)
{ {
if (Owner.TryGetComponent(out SolutionContainerComponent? solution)) if (EntitySystem.Get<SolutionContainerSystem>().TryGetSolution(Owner, SolutionName, out var solution))
{ {
solution?.TryRemoveReagent(reagentQuantity.ReagentId, reagentQuantity.Quantity); EntitySystem.Get<SolutionContainerSystem>()
.TryRemoveReagent(Owner.Uid, solution, reagentQuantity.ReagentId, reagentQuantity.Quantity);
} }
} }
@@ -405,32 +415,32 @@ namespace Content.Server.Kitchen.Components
private void EjectSolids() private void EjectSolids()
{ {
for (var i = _storage.ContainedEntities.Count - 1; i >= 0; i--) for (var i = _storage.ContainedEntities.Count - 1; i >= 0; i--)
{ {
_storage.Remove(_storage.ContainedEntities.ElementAt(i)); _storage.Remove(_storage.ContainedEntities.ElementAt(i));
} }
} }
private void EjectSolid(EntityUid entityID) private void EjectSolid(EntityUid entityId)
{ {
if (Owner.EntityManager.EntityExists(entityID)) if (Owner.EntityManager.EntityExists(entityId))
{ {
_storage.Remove(Owner.EntityManager.GetEntity(entityID)); _storage.Remove(Owner.EntityManager.GetEntity(entityId));
} }
} }
private void SubtractContents(FoodRecipePrototype recipe) private void SubtractContents(FoodRecipePrototype recipe)
{ {
if (!Owner.TryGetComponent(out SolutionContainerComponent? solution)) var solutionUid = Owner.Uid;
if (!EntitySystem.Get<SolutionContainerSystem>().TryGetSolution(Owner, SolutionName, out var solution))
{ {
return; return;
} }
foreach (var recipeReagent in recipe.IngredientsReagents) foreach (var recipeReagent in recipe.IngredientsReagents)
{ {
solution?.TryRemoveReagent(recipeReagent.Key, ReagentUnit.New(recipeReagent.Value)); EntitySystem.Get<SolutionContainerSystem>()
.TryRemoveReagent(solutionUid, solution, recipeReagent.Key, ReagentUnit.New(recipeReagent.Value));
} }
foreach (var recipeSolid in recipe.IngredientsSolids) foreach (var recipeSolid in recipe.IngredientsSolids)
@@ -453,7 +463,6 @@ namespace Content.Server.Kitchen.Components
} }
} }
} }
} }
private MicrowaveSuccessState CanSatisfyRecipe(FoodRecipePrototype recipe, Dictionary<string, int> solids) private MicrowaveSuccessState CanSatisfyRecipe(FoodRecipePrototype recipe, Dictionary<string, int> solids)
@@ -463,14 +472,14 @@ namespace Content.Server.Kitchen.Components
return MicrowaveSuccessState.RecipeFail; return MicrowaveSuccessState.RecipeFail;
} }
if (!Owner.TryGetComponent(out SolutionContainerComponent? solution)) if (!EntitySystem.Get<SolutionContainerSystem>().TryGetSolution(Owner, SolutionName, out var solution))
{ {
return MicrowaveSuccessState.RecipeFail; return MicrowaveSuccessState.RecipeFail;
} }
foreach (var reagent in recipe.IngredientsReagents) foreach (var reagent in recipe.IngredientsReagents)
{ {
if (!solution.Solution.ContainsReagent(reagent.Key, out var amount)) if (!solution.ContainsReagent(reagent.Key, out var amount))
{ {
return MicrowaveSuccessState.RecipeFail; return MicrowaveSuccessState.RecipeFail;
} }

View File

@@ -1,5 +1,4 @@
using Content.Server.Chemistry.Components; using Content.Shared.Chemistry.Components;
using Content.Shared.Interaction;
using Content.Shared.Kitchen.Components; using Content.Shared.Kitchen.Components;
using Content.Shared.Sound; using Content.Shared.Sound;
using Robust.Shared.Containers; using Robust.Shared.Containers;
@@ -9,7 +8,6 @@ using Robust.Shared.ViewVariables;
namespace Content.Server.Kitchen.Components namespace Content.Server.Kitchen.Components
{ {
/// <summary> /// <summary>
/// The combo reagent grinder/juicer. The reason why grinding and juicing are seperate is simple, /// The combo reagent grinder/juicer. The reason why grinding and juicing are seperate is simple,
/// think of grinding as a utility to break an object down into its reagents. Think of juicing as /// think of grinding as a utility to break an object down into its reagents. Think of juicing as
@@ -24,7 +22,7 @@ namespace Content.Server.Kitchen.Components
/// <summary> /// <summary>
/// Can be null since we won't always have a beaker in the grinder. /// Can be null since we won't always have a beaker in the grinder.
/// </summary> /// </summary>
[ViewVariables] public SolutionContainerComponent? HeldBeaker = default!; [ViewVariables] public Solution? HeldBeaker = default!;
/// <summary> /// <summary>
/// Contains the things that are going to be ground or juiced. /// Contains the things that are going to be ground or juiced.

View File

@@ -1,4 +1,5 @@
using Content.Server.Kitchen.Components; using Content.Server.Kitchen.Components;
using Content.Shared.Chemistry.EntitySystems;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
@@ -7,6 +8,18 @@ namespace Content.Server.Kitchen.EntitySystems
[UsedImplicitly] [UsedImplicitly]
internal sealed class MicrowaveSystem : EntitySystem internal sealed class MicrowaveSystem : EntitySystem
{ {
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<MicrowaveComponent, SolutionChangedEvent>(OnSolutionChange);
}
private void OnSolutionChange(EntityUid uid, MicrowaveComponent component, SolutionChangedEvent args)
{
component.DirtyUi();
}
public override void Update(float frameTime) public override void Update(float frameTime)
{ {
base.Update(frameTime); base.Update(frameTime);

View File

@@ -1,20 +1,18 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Content.Server.Chemistry.Components;
using Content.Server.Hands.Components; using Content.Server.Hands.Components;
using Content.Server.Items; using Content.Server.Items;
using Content.Server.Kitchen.Components; using Content.Server.Kitchen.Components;
using Content.Server.Kitchen.Events;
using Content.Server.Power.Components; using Content.Server.Power.Components;
using Content.Server.Stack; using Content.Server.Stack;
using Content.Server.UserInterface; using Content.Server.UserInterface;
using Content.Server.Kitchen.Events; using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Chemistry.Solution;
using Content.Shared.Interaction; using Content.Shared.Interaction;
using Content.Shared.Kitchen.Components; using Content.Shared.Kitchen.Components;
using Content.Shared.Notification.Managers; using Content.Shared.Notification.Managers;
using Content.Shared.Random.Helpers; using Content.Shared.Random.Helpers;
using Content.Shared.Tag;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Server.GameObjects; using Robust.Server.GameObjects;
using Robust.Shared.Audio; using Robust.Shared.Audio;
@@ -30,7 +28,7 @@ namespace Content.Server.Kitchen.EntitySystems
[UsedImplicitly] [UsedImplicitly]
internal sealed class ReagentGrinderSystem : EntitySystem internal sealed class ReagentGrinderSystem : EntitySystem
{ {
[Dependency] private readonly IEntityManager _entityManager = default!; [Dependency] private readonly SolutionContainerSystem _solutionsSystem = default!;
private Queue<ReagentGrinderComponent> _uiUpdateQueue = new(); private Queue<ReagentGrinderComponent> _uiUpdateQueue = new();
@@ -39,13 +37,14 @@ namespace Content.Server.Kitchen.EntitySystems
base.Initialize(); base.Initialize();
SubscribeLocalEvent<ReagentGrinderComponent, ComponentInit>(OnComponentInit); SubscribeLocalEvent<ReagentGrinderComponent, ComponentInit>(OnComponentInit);
SubscribeLocalEvent<ReagentGrinderComponent, PowerChangedEvent>((_, component, _) => EnqueueUiUpdate(component)); SubscribeLocalEvent<ReagentGrinderComponent, PowerChangedEvent>((_, component, _) =>
EnqueueUiUpdate(component));
SubscribeLocalEvent<ReagentGrinderComponent, InteractHandEvent>(OnInteractHand); SubscribeLocalEvent<ReagentGrinderComponent, InteractHandEvent>(OnInteractHand);
SubscribeLocalEvent<ReagentGrinderComponent, InteractUsingEvent>(OnInteractUsing); SubscribeLocalEvent<ReagentGrinderComponent, InteractUsingEvent>(OnInteractUsing);
SubscribeLocalEvent<StackComponent, JuiceableScalingEvent>(JuiceableScaling); SubscribeLocalEvent<StackComponent, ExtractableScalingEvent>(ExtractableScaling);
} }
private void JuiceableScaling(EntityUid uid, StackComponent component, JuiceableScalingEvent args) private void ExtractableScaling(EntityUid uid, StackComponent component, ExtractableScalingEvent args)
{ {
args.Scalar *= component.Count; // multiply scalar by amount of items in stack args.Scalar *= component.Count; // multiply scalar by amount of items in stack
} }
@@ -54,19 +53,20 @@ namespace Content.Server.Kitchen.EntitySystems
{ {
if (args.Handled) return; if (args.Handled) return;
if (!args.User.TryGetComponent(out IHandsComponent? hands)) if (!args.User.HasComponent<IHandsComponent>())
{ {
component.Owner.PopupMessage(args.User, Loc.GetString("reagent-grinder-component-interact-using-no-hands")); component.Owner.PopupMessage(args.User,
Loc.GetString("reagent-grinder-component-interact-using-no-hands"));
args.Handled = true; args.Handled = true;
return; return;
} }
IEntity heldEnt = args.Used; IEntity heldEnt = args.Used;
//First, check if user is trying to insert a beaker. // First, check if user is trying to insert a beaker.
//No promise it will be a beaker right now, but whatever. // No promise it will be a beaker right now, but whatever.
//Maybe this should whitelist "beaker" in the prototype id of heldEnt? // Maybe this should whitelist "beaker" in the prototype id of heldEnt?
if (heldEnt.TryGetComponent(out SolutionContainerComponent? beaker) && beaker.Capabilities.HasFlag(SolutionContainerCaps.FitsInDispenser)) if (_solutionsSystem.TryGetFitsInDispenser(heldEnt.Uid, out var beaker))
{ {
component.BeakerContainer.Insert(heldEnt); component.BeakerContainer.Insert(heldEnt);
component.HeldBeaker = beaker; component.HeldBeaker = beaker;
@@ -74,15 +74,17 @@ namespace Content.Server.Kitchen.EntitySystems
//We are done, return. Insert the beaker and exit! //We are done, return. Insert the beaker and exit!
if (component.Owner.TryGetComponent(out AppearanceComponent? appearance)) if (component.Owner.TryGetComponent(out AppearanceComponent? appearance))
{ {
appearance.SetData(SharedReagentGrinderComponent.ReagentGrinderVisualState.BeakerAttached, component.BeakerContainer.ContainedEntity != null); appearance.SetData(SharedReagentGrinderComponent.ReagentGrinderVisualState.BeakerAttached,
component.BeakerContainer.ContainedEntity != null);
} }
ClickSound(component); ClickSound(component);
args.Handled = true; args.Handled = true;
return; return;
} }
//Next, see if the user is trying to insert something they want to be ground/juiced. //Next, see if the user is trying to insert something they want to be ground/juiced.
if (!heldEnt.HasTag("Grindable") && !heldEnt.TryGetComponent(out JuiceableComponent? juice)) if (!heldEnt.TryGetComponent(out ExtractableComponent? juice))
{ {
//Entity did NOT pass the whitelist for grind/juice. //Entity did NOT pass the whitelist for grind/juice.
//Wouldn't want the clown grinding up the Captain's ID card now would you? //Wouldn't want the clown grinding up the Captain's ID card now would you?
@@ -114,8 +116,10 @@ namespace Content.Server.Kitchen.EntitySystems
{ {
return; return;
} }
EnqueueUiUpdate(component); EnqueueUiUpdate(component);
component.Owner.GetUIOrNull(SharedReagentGrinderComponent.ReagentGrinderUiKey.Key)?.Toggle(actor.PlayerSession); component.Owner.GetUIOrNull(SharedReagentGrinderComponent.ReagentGrinderUiKey.Key)
?.Toggle(actor.PlayerSession);
args.Handled = true; args.Handled = true;
} }
@@ -130,11 +134,13 @@ namespace Content.Server.Kitchen.EntitySystems
//A slot for the beaker where the grounds/juices will go. //A slot for the beaker where the grounds/juices will go.
component.BeakerContainer = component.BeakerContainer =
ContainerHelpers.EnsureContainer<ContainerSlot>(component.Owner, $"{component.Name}-reagentContainerContainer"); ContainerHelpers.EnsureContainer<ContainerSlot>(component.Owner,
$"{component.Name}-reagentContainerContainer");
//A container for the things that WILL be ground/juiced. Useful for ejecting them instead of deleting them from the hands of the user. //A container for the things that WILL be ground/juiced. Useful for ejecting them instead of deleting them from the hands of the user.
component.Chamber = component.Chamber =
ContainerHelpers.EnsureContainer<Container>(component.Owner, $"{component.Name}-entityContainerContainer"); ContainerHelpers.EnsureContainer<Container>(component.Owner,
$"{component.Name}-entityContainerContainer");
var bui = component.Owner.GetUIOrNull(SharedReagentGrinderComponent.ReagentGrinderUiKey.Key); var bui = component.Owner.GetUIOrNull(SharedReagentGrinderComponent.ReagentGrinderUiKey.Key);
if (bui != null) if (bui != null)
@@ -154,15 +160,19 @@ namespace Content.Server.Kitchen.EntitySystems
switch (message.Message) switch (message.Message)
{ {
case SharedReagentGrinderComponent.ReagentGrinderGrindStartMessage msg: case SharedReagentGrinderComponent.ReagentGrinderGrindStartMessage msg:
if (!component.Owner.TryGetComponent(out ApcPowerReceiverComponent? receiver) || !receiver.Powered) break; if (!component.Owner.TryGetComponent(out ApcPowerReceiverComponent? receiver) ||
!receiver.Powered) break;
ClickSound(component); ClickSound(component);
DoWork(component, message.Session.AttachedEntity!, SharedReagentGrinderComponent.GrinderProgram.Grind); DoWork(component, message.Session.AttachedEntity!,
SharedReagentGrinderComponent.GrinderProgram.Grind);
break; break;
case SharedReagentGrinderComponent.ReagentGrinderJuiceStartMessage msg: case SharedReagentGrinderComponent.ReagentGrinderJuiceStartMessage msg:
if (!component.Owner.TryGetComponent(out ApcPowerReceiverComponent? receiver2) || !receiver2.Powered) break; if (!component.Owner.TryGetComponent(out ApcPowerReceiverComponent? receiver2) ||
!receiver2.Powered) break;
ClickSound(component); ClickSound(component);
DoWork(component, message.Session.AttachedEntity!, SharedReagentGrinderComponent.GrinderProgram.Juice); DoWork(component, message.Session.AttachedEntity!,
SharedReagentGrinderComponent.GrinderProgram.Juice);
break; break;
case SharedReagentGrinderComponent.ReagentGrinderEjectChamberAllMessage msg: case SharedReagentGrinderComponent.ReagentGrinderEjectChamberAllMessage msg:
@@ -175,8 +185,10 @@ namespace Content.Server.Kitchen.EntitySystems
component.Chamber.Remove(entity); component.Chamber.Remove(entity);
entity.RandomOffset(0.4f); entity.RandomOffset(0.4f);
} }
EnqueueUiUpdate(component); EnqueueUiUpdate(component);
} }
break; break;
case SharedReagentGrinderComponent.ReagentGrinderEjectChamberContentMessage msg: case SharedReagentGrinderComponent.ReagentGrinderEjectChamberContentMessage msg:
@@ -187,6 +199,7 @@ namespace Content.Server.Kitchen.EntitySystems
EnqueueUiUpdate(component); EnqueueUiUpdate(component);
ClickSound(component); ClickSound(component);
} }
break; break;
case SharedReagentGrinderComponent.ReagentGrinderEjectBeakerMessage msg: case SharedReagentGrinderComponent.ReagentGrinderEjectBeakerMessage msg:
@@ -203,7 +216,7 @@ namespace Content.Server.Kitchen.EntitySystems
while (_uiUpdateQueue.TryDequeue(out var comp)) while (_uiUpdateQueue.TryDequeue(out var comp))
{ {
if(comp.Deleted) if (comp.Deleted)
continue; continue;
bool canJuice = false; bool canJuice = false;
@@ -212,23 +225,25 @@ namespace Content.Server.Kitchen.EntitySystems
{ {
foreach (var entity in comp.Chamber.ContainedEntities) foreach (var entity in comp.Chamber.ContainedEntities)
{ {
if (!canJuice && entity.HasComponent<JuiceableComponent>()) canJuice = true; if (canJuice || !entity.TryGetComponent(out ExtractableComponent? component)) continue;
if (!canGrind && entity.HasTag("Grindable")) canGrind = true;
if (canJuice && canGrind) break; canJuice = component.GrindableSolution == null;
canGrind = component.GrindableSolution != null;
} }
} }
comp.Owner.GetUIOrNull(SharedReagentGrinderComponent.ReagentGrinderUiKey.Key)?.SetState(new ReagentGrinderInterfaceState comp.Owner.GetUIOrNull(SharedReagentGrinderComponent.ReagentGrinderUiKey.Key)?.SetState(
( new ReagentGrinderInterfaceState
comp.Busy, (
comp.BeakerContainer.ContainedEntity != null, comp.Busy,
comp.Owner.TryGetComponent(out ApcPowerReceiverComponent? receiver) && receiver.Powered, comp.BeakerContainer.ContainedEntity != null,
canJuice, comp.Owner.TryGetComponent(out ApcPowerReceiverComponent? receiver) && receiver.Powered,
canGrind, canJuice,
comp.Chamber.ContainedEntities.Select(item => item.Uid).ToArray(), canGrind,
//Remember the beaker can be null! comp.Chamber.ContainedEntities.Select(item => item.Uid).ToArray(),
comp.HeldBeaker?.Solution.Contents.ToArray() //Remember the beaker can be null!
)); comp.HeldBeaker?.Contents.ToArray()
));
} }
} }
@@ -247,7 +262,8 @@ namespace Content.Server.Kitchen.EntitySystems
component.BeakerContainer.Remove(beaker); component.BeakerContainer.Remove(beaker);
if (user == null || !user.TryGetComponent<HandsComponent>(out var hands) || !component.HeldBeaker.Owner.TryGetComponent<ItemComponent>(out var item)) if (user == null || !user.TryGetComponent<HandsComponent>(out var hands) ||
!beaker.TryGetComponent<ItemComponent>(out var item))
return; return;
hands.PutInHandOrDrop(item); hands.PutInHandOrDrop(item);
@@ -255,7 +271,8 @@ namespace Content.Server.Kitchen.EntitySystems
EnqueueUiUpdate(component); EnqueueUiUpdate(component);
if (component.Owner.TryGetComponent(out AppearanceComponent? appearance)) if (component.Owner.TryGetComponent(out AppearanceComponent? appearance))
{ {
appearance.SetData(SharedReagentGrinderComponent.ReagentGrinderVisualState.BeakerAttached, component.BeakerContainer.ContainedEntity != null); appearance.SetData(SharedReagentGrinderComponent.ReagentGrinderVisualState.BeakerAttached,
component.BeakerContainer.ContainedEntity != null);
} }
} }
@@ -263,10 +280,13 @@ namespace Content.Server.Kitchen.EntitySystems
/// The wzhzhzh of the grinder. Processes the contents of the grinder and puts the output in the beaker. /// The wzhzhzh of the grinder. Processes the contents of the grinder and puts the output in the beaker.
/// </summary> /// </summary>
/// <param name="isJuiceIntent">true for wanting to juice, false for wanting to grind.</param> /// <param name="isJuiceIntent">true for wanting to juice, false for wanting to grind.</param>
private void DoWork(ReagentGrinderComponent component, IEntity user, SharedReagentGrinderComponent.GrinderProgram program) private void DoWork(ReagentGrinderComponent component, IEntity user,
SharedReagentGrinderComponent.GrinderProgram program)
{ {
//Have power, are we busy, chamber has anything to grind, a beaker for the grounds to go? //Have power, are we busy, chamber has anything to grind, a beaker for the grounds to go?
if (!component.Owner.TryGetComponent(out ApcPowerReceiverComponent? receiver) || !receiver.Powered || component.Busy || component.Chamber.ContainedEntities.Count <= 0 || component.BeakerContainer.ContainedEntity == null || component.HeldBeaker == null) if (!component.Owner.TryGetComponent(out ApcPowerReceiverComponent? receiver) || !receiver.Powered ||
component.Busy || component.Chamber.ContainedEntities.Count <= 0 ||
component.BeakerContainer.ContainedEntity == null || component.HeldBeaker == null)
{ {
return; return;
} }
@@ -275,25 +295,31 @@ namespace Content.Server.Kitchen.EntitySystems
var bui = component.Owner.GetUIOrNull(SharedReagentGrinderComponent.ReagentGrinderUiKey.Key); var bui = component.Owner.GetUIOrNull(SharedReagentGrinderComponent.ReagentGrinderUiKey.Key);
bui?.SendMessage(new SharedReagentGrinderComponent.ReagentGrinderWorkStartedMessage(program)); bui?.SendMessage(new SharedReagentGrinderComponent.ReagentGrinderWorkStartedMessage(program));
var beakerEntity = component.BeakerContainer.ContainedEntity;
switch (program) switch (program)
{ {
case SharedReagentGrinderComponent.GrinderProgram.Grind: case SharedReagentGrinderComponent.GrinderProgram.Grind:
SoundSystem.Play(Filter.Pvs(component.Owner), component.GrindSound.GetSound(), component.Owner, AudioParams.Default); SoundSystem.Play(Filter.Pvs(component.Owner), component.GrindSound.GetSound(), component.Owner, AudioParams.Default);
//Get each item inside the chamber and get the reagents it contains. Transfer those reagents to the beaker, given we have one in. // Get each item inside the chamber and get the reagents it contains.
// Transfer those reagents to the beaker, given we have one in.
component.Owner.SpawnTimer(component.WorkTime, (Action) (() => component.Owner.SpawnTimer(component.WorkTime, (Action) (() =>
{ {
foreach (var item in component.Chamber.ContainedEntities.ToList()) foreach (var item in component.Chamber.ContainedEntities.ToList())
{ {
if (!item.HasTag("Grindable")) continue; if (!item.TryGetComponent(out ExtractableComponent? extract)
if (!item.TryGetComponent<SolutionContainerComponent>(out var solution)) continue; || extract.GrindableSolution == null
var juiceEvent = new JuiceableScalingEvent(); // default of scalar is always 1.0 || !_solutionsSystem.TryGetSolution(item, extract.GrindableSolution, out var solution)) continue;
RaiseLocalEvent<JuiceableScalingEvent>(item.Uid, juiceEvent, false);
if (component.HeldBeaker.CurrentVolume + solution.CurrentVolume * juiceEvent.Scalar > component.HeldBeaker.MaxVolume) continue; var juiceEvent = new ExtractableScalingEvent(); // default of scalar is always 1.0
solution.Solution.ScaleSolution(juiceEvent.Scalar); RaiseLocalEvent(item.Uid, juiceEvent, false);
component.HeldBeaker.TryAddSolution(solution.Solution); if (component.HeldBeaker.CurrentVolume + solution.CurrentVolume * juiceEvent.Scalar >
solution.RemoveAllSolution(); component.HeldBeaker.MaxVolume) continue;
solution.ScaleSolution(juiceEvent.Scalar);
_solutionsSystem.TryAddSolution(beakerEntity.Uid, component.HeldBeaker, solution);
_solutionsSystem.RemoveAllSolution(beakerEntity.Uid, solution);
item.Delete(); item.Delete();
} }
component.Busy = false; component.Busy = false;
EnqueueUiUpdate(component); EnqueueUiUpdate(component);
bui?.SendMessage(new SharedReagentGrinderComponent.ReagentGrinderWorkCompleteMessage()); bui?.SendMessage(new SharedReagentGrinderComponent.ReagentGrinderWorkCompleteMessage());
@@ -306,17 +332,21 @@ namespace Content.Server.Kitchen.EntitySystems
{ {
foreach (var item in component.Chamber.ContainedEntities.ToList()) foreach (var item in component.Chamber.ContainedEntities.ToList())
{ {
if (!item.TryGetComponent<JuiceableComponent>(out var juiceMe)) continue; if (!item.TryGetComponent<ExtractableComponent>(out var juiceMe)) continue;
var juiceEvent = new JuiceableScalingEvent(); // default of scalar is always 1.0 var juiceEvent = new ExtractableScalingEvent(); // default of scalar is always 1.0
if (item.HasComponent<StackComponent>()) if (item.HasComponent<StackComponent>())
{ {
RaiseLocalEvent<JuiceableScalingEvent>(item.Uid, juiceEvent); RaiseLocalEvent(item.Uid, juiceEvent);
} }
if (component.HeldBeaker.CurrentVolume + juiceMe.JuiceResultSolution.TotalVolume * juiceEvent.Scalar > component.HeldBeaker.MaxVolume) continue;
juiceMe.JuiceResultSolution.ScaleSolution(juiceEvent.Scalar); if (component.HeldBeaker.CurrentVolume +
component.HeldBeaker.TryAddSolution(juiceMe.JuiceResultSolution); juiceMe.ResultSolution.TotalVolume * juiceEvent.Scalar >
component.HeldBeaker.MaxVolume) continue;
juiceMe.ResultSolution.ScaleSolution(juiceEvent.Scalar);
_solutionsSystem.TryAddSolution(beakerEntity.Uid, component.HeldBeaker, juiceMe.ResultSolution);
item.Delete(); item.Delete();
} }
bui?.SendMessage(new SharedReagentGrinderComponent.ReagentGrinderWorkCompleteMessage()); bui?.SendMessage(new SharedReagentGrinderComponent.ReagentGrinderWorkCompleteMessage());
component.Busy = false; component.Busy = false;
EnqueueUiUpdate(component); EnqueueUiUpdate(component);

View File

@@ -5,10 +5,10 @@ namespace Content.Server.Kitchen.Events
/// <summary> /// <summary>
/// Used in scaling amount of solution to extract in juicing /// Used in scaling amount of solution to extract in juicing
/// </summary> /// </summary>
public class JuiceableScalingEvent : EntityEventArgs public class ExtractableScalingEvent : EntityEventArgs
{ {
public JuiceableScalingEvent() public ExtractableScalingEvent()
{ {
Scalar = 1f; Scalar = 1f;
} }

View File

@@ -2,16 +2,13 @@ using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Content.Server.Body.Behavior; using Content.Server.Body.Behavior;
using Content.Server.Fluids.Components; using Content.Server.Fluids.Components;
using Content.Shared.Audio;
using Content.Shared.Body.Components; using Content.Shared.Body.Components;
using Content.Shared.Chemistry; using Content.Shared.Chemistry.Components.SolutionManager;
using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Chemistry.Reagent; using Content.Shared.Chemistry.Reagent;
using Content.Shared.Chemistry.Solution;
using Content.Shared.Chemistry.Solution.Components;
using Content.Shared.Examine; using Content.Shared.Examine;
using Content.Shared.Interaction; using Content.Shared.Interaction;
using Content.Shared.Interaction.Helpers; using Content.Shared.Interaction.Helpers;
using Content.Shared.Notification;
using Content.Shared.Notification.Managers; using Content.Shared.Notification.Managers;
using Content.Shared.Nutrition.Components; using Content.Shared.Nutrition.Components;
using Content.Shared.Sound; using Content.Shared.Sound;
@@ -23,7 +20,6 @@ using Robust.Shared.GameObjects;
using Robust.Shared.IoC; using Robust.Shared.IoC;
using Robust.Shared.Localization; using Robust.Shared.Localization;
using Robust.Shared.Player; using Robust.Shared.Player;
using Robust.Shared.Prototypes;
using Robust.Shared.Random; using Robust.Shared.Random;
using Robust.Shared.Serialization.Manager.Attributes; using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.Utility; using Robust.Shared.Utility;
@@ -32,11 +28,14 @@ using Robust.Shared.ViewVariables;
namespace Content.Server.Nutrition.Components namespace Content.Server.Nutrition.Components
{ {
[RegisterComponent] [RegisterComponent]
public class DrinkComponent : Component, IUse, IAfterInteract, ISolutionChange, IExamine, ILand public class DrinkComponent : Component, IUse, IAfterInteract, IExamine, ILand
{ {
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly IRobustRandom _random = default!;
[DataField("solution")]
public string SolutionName { get; set; } = DefaultSolutionName;
public const string DefaultSolutionName = "drink";
public override string Name => "Drink"; public override string Name => "Drink";
int IAfterInteract.Priority => 10; int IAfterInteract.Priority => 10;
@@ -50,7 +49,7 @@ namespace Content.Server.Nutrition.Components
[ViewVariables] [ViewVariables]
[DataField("isOpen")] [DataField("isOpen")]
private bool _defaultToOpened; internal bool DefaultToOpened;
[ViewVariables(VVAccess.ReadWrite)] [ViewVariables(VVAccess.ReadWrite)]
public ReagentUnit TransferAmount { get; [UsedImplicitly] private set; } = ReagentUnit.New(5); public ReagentUnit TransferAmount { get; [UsedImplicitly] private set; } = ReagentUnit.New(5);
@@ -71,8 +70,14 @@ namespace Content.Server.Nutrition.Components
} }
} }
[ViewVariables] [ViewVariables] public bool Empty => IsEmpty();
public bool Empty => Owner.GetComponentOrNull<ISolutionInteractionsComponent>()?.DrainAvailable <= 0;
private bool IsEmpty()
{
var drainAvailable = EntitySystem.Get<SolutionContainerSystem>()
.DrainAvailable(Owner);
return drainAvailable <= 0;
}
[DataField("openSounds")] [DataField("openSounds")]
private SoundSpecifier _openSounds = new SoundCollectionSpecifier("canOpenSounds"); private SoundSpecifier _openSounds = new SoundCollectionSpecifier("canOpenSounds");
@@ -81,45 +86,39 @@ namespace Content.Server.Nutrition.Components
[DataField("burstSound")] [DataField("burstSound")]
private SoundSpecifier _burstSound = new SoundPathSpecifier("/Audio/Effects/flash_bang.ogg"); private SoundSpecifier _burstSound = new SoundPathSpecifier("/Audio/Effects/flash_bang.ogg");
protected override void Initialize()
{
base.Initialize();
Opened = _defaultToOpened;
UpdateAppearance();
}
private void OpenedChanged() private void OpenedChanged()
{ {
if (!Owner.TryGetComponent(out SharedSolutionContainerComponent? contents)) var solutionSys = EntitySystem.Get<SolutionContainerSystem>();
if (!solutionSys.TryGetSolution(Owner, SolutionName, out _))
{ {
return; return;
} }
if (Opened) if (Opened)
{ {
contents.Capabilities |= SolutionContainerCaps.Refillable | SolutionContainerCaps.Drainable; var refillable = Owner.EnsureComponent<RefillableSolutionComponent>();
refillable.Solution = SolutionName;
var drainable = Owner.EnsureComponent<DrainableSolutionComponent>();
drainable.Solution = SolutionName;
} }
else else
{ {
contents.Capabilities &= ~(SolutionContainerCaps.Refillable | SolutionContainerCaps.Drainable); Owner.RemoveComponent<RefillableSolutionComponent>();
Owner.RemoveComponent<DrainableSolutionComponent>();
} }
} }
void ISolutionChange.SolutionChanged(SolutionChangeEventArgs eventArgs) // TODO move to DrinkSystem
{ public void UpdateAppearance()
UpdateAppearance();
}
private void UpdateAppearance()
{ {
if (!Owner.TryGetComponent(out AppearanceComponent? appearance) || if (!Owner.TryGetComponent(out AppearanceComponent? appearance) ||
!Owner.TryGetComponent(out ISolutionInteractionsComponent? contents)) !Owner.HasComponent<SolutionContainerManagerComponent>())
{ {
return; return;
} }
appearance.SetData(SharedFoodComponent.FoodVisuals.Visual, contents.DrainAvailable.Float()); var drainAvailable = EntitySystem.Get<SolutionContainerSystem>().DrainAvailable(Owner);
appearance.SetData(SharedFoodComponent.FoodVisuals.Visual, drainAvailable.Float());
} }
bool IUse.UseEntity(UseEntityEventArgs args) bool IUse.UseEntity(UseEntityEventArgs args)
@@ -133,8 +132,8 @@ namespace Content.Server.Nutrition.Components
return false; return false;
} }
if (!Owner.TryGetComponent(out ISolutionInteractionsComponent? contents) || if (!Owner.HasComponent<SolutionContainerManagerComponent>() ||
contents.DrainAvailable <= 0) EntitySystem.Get<SolutionContainerSystem>().DrainAvailable(Owner) <= 0)
{ {
args.User.PopupMessage(Loc.GetString("drink-component-on-use-is-empty", ("owner", Owner))); args.User.PopupMessage(Loc.GetString("drink-component-on-use-is-empty", ("owner", Owner)));
return true; return true;
@@ -160,8 +159,10 @@ namespace Content.Server.Nutrition.Components
{ {
return; return;
} }
var color = Empty ? "gray" : "yellow"; var color = Empty ? "gray" : "yellow";
var openedText = Loc.GetString(Empty ? "drink-component-on-examine-is-empty" : "drink-component-on-examine-is-opened"); var openedText =
Loc.GetString(Empty ? "drink-component-on-examine-is-empty" : "drink-component-on-examine-is-opened");
message.AddMarkup(Loc.GetString("drink-component-on-examine-details-text", ("colorName", color), ("text", openedText))); message.AddMarkup(Loc.GetString("drink-component-on-examine-details-text", ("colorName", color), ("text", openedText)));
} }
@@ -173,8 +174,7 @@ namespace Content.Server.Nutrition.Components
return false; return false;
} }
if (!Owner.TryGetComponent(out ISolutionInteractionsComponent? interactions) || if (!EntitySystem.Get<SolutionContainerSystem>().TryGetDrainableSolution(Owner.Uid, out var interactions) ||
!interactions.CanDrain ||
interactions.DrainAvailable <= 0) interactions.DrainAvailable <= 0)
{ {
if (!forced) if (!forced)
@@ -199,8 +199,9 @@ namespace Content.Server.Nutrition.Components
return false; return false;
} }
var solutionContainerSystem = EntitySystem.Get<SolutionContainerSystem>();
var transferAmount = ReagentUnit.Min(TransferAmount, interactions.DrainAvailable); var transferAmount = ReagentUnit.Min(TransferAmount, interactions.DrainAvailable);
var drain = interactions.Drain(transferAmount); var drain = solutionContainerSystem.Drain(Owner.Uid, interactions, transferAmount);
var firstStomach = stomachs.FirstOrDefault(stomach => stomach.CanTransferSolution(drain)); var firstStomach = stomachs.FirstOrDefault(stomach => stomach.CanTransferSolution(drain));
// All stomach are full or can't handle whatever solution we have. // All stomach are full or can't handle whatever solution we have.
@@ -208,13 +209,14 @@ namespace Content.Server.Nutrition.Components
{ {
target.PopupMessage(Loc.GetString("drink-component-try-use-drink-had-enough", ("owner", Owner))); target.PopupMessage(Loc.GetString("drink-component-try-use-drink-had-enough", ("owner", Owner)));
if (!interactions.CanRefill) if (Owner.EntityManager.TryGetEntity(Owner.Uid, out var interactionEntity)
&& !interactionEntity.HasComponent<RefillableSolutionComponent>())
{ {
drain.SpillAt(target, "PuddleSmear"); drain.SpillAt(target, "PuddleSmear");
return false; return false;
} }
interactions.Refill(drain); solutionContainerSystem.Refill(Owner.Uid, interactions, drain);
return false; return false;
} }
@@ -237,16 +239,12 @@ namespace Content.Server.Nutrition.Components
if (_pressurized && if (_pressurized &&
!Opened && !Opened &&
_random.Prob(0.25f) && _random.Prob(0.25f) &&
Owner.TryGetComponent(out ISolutionInteractionsComponent? interactions)) EntitySystem.Get<SolutionContainerSystem>().TryGetDrainableSolution(Owner.Uid, out var interactions))
{ {
Opened = true; Opened = true;
if (!interactions.CanDrain) var solution = EntitySystem.Get<SolutionContainerSystem>()
{ .Drain(Owner.Uid, interactions, interactions.DrainAvailable);
return;
}
var solution = interactions.Drain(interactions.DrainAvailable);
solution.SpillAt(Owner, "PuddleSmear"); solution.SpillAt(Owner, "PuddleSmear");
SoundSystem.Play(Filter.Pvs(Owner), _burstSound.GetSound(), Owner, AudioParams.Default.WithVolume(-4)); SoundSystem.Play(Filter.Pvs(Owner), _burstSound.GetSound(), Owner, AudioParams.Default.WithVolume(-4));

View File

@@ -3,10 +3,10 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Content.Server.Body.Behavior; using Content.Server.Body.Behavior;
using Content.Server.Chemistry.Components;
using Content.Server.Hands.Components; using Content.Server.Hands.Components;
using Content.Server.Items; using Content.Server.Items;
using Content.Shared.Body.Components; using Content.Shared.Body.Components;
using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Chemistry.Reagent; using Content.Shared.Chemistry.Reagent;
using Content.Shared.Interaction; using Content.Shared.Interaction;
using Content.Shared.Interaction.Helpers; using Content.Shared.Interaction.Helpers;
@@ -28,6 +28,7 @@ namespace Content.Server.Nutrition.Components
public class FoodComponent : Component, IUse, IAfterInteract public class FoodComponent : Component, IUse, IAfterInteract
{ {
public override string Name => "Food"; public override string Name => "Food";
public static string SolutionName = "food";
[ViewVariables] [ViewVariables]
[DataField("useSound")] [DataField("useSound")]
@@ -52,7 +53,7 @@ namespace Content.Server.Nutrition.Components
{ {
get get
{ {
if (!Owner.TryGetComponent(out SolutionContainerComponent? solution)) if (!EntitySystem.Get<SolutionContainerSystem>().TryGetSolution(Owner, SolutionName, out var solution))
{ {
return 0; return 0;
} }
@@ -69,7 +70,7 @@ namespace Content.Server.Nutrition.Components
protected override void Initialize() protected override void Initialize()
{ {
base.Initialize(); base.Initialize();
Owner.EnsureComponentWarn<SolutionContainerComponent>(); // Owner.EnsureComponentWarn<SolutionContainerManager>();
} }
bool IUse.UseEntity(UseEntityEventArgs eventArgs) bool IUse.UseEntity(UseEntityEventArgs eventArgs)
@@ -97,7 +98,8 @@ namespace Content.Server.Nutrition.Components
public bool TryUseFood(IEntity? user, IEntity? target, UtensilComponent? utensilUsed = null) public bool TryUseFood(IEntity? user, IEntity? target, UtensilComponent? utensilUsed = null)
{ {
if (!Owner.TryGetComponent(out SolutionContainerComponent? solution)) var solutionContainerSys = EntitySystem.Get<SolutionContainerSystem>();
if (!solutionContainerSys.TryGetSolution(Owner, SolutionName, out var solution))
{ {
return false; return false;
} }
@@ -147,7 +149,8 @@ namespace Content.Server.Nutrition.Components
if (!types.HasFlag(_utensilsNeeded)) if (!types.HasFlag(_utensilsNeeded))
{ {
trueTarget.PopupMessage(user, Loc.GetString("food-you-need-to-hold-utensil", ("utensil", _utensilsNeeded))); trueTarget.PopupMessage(user,
Loc.GetString("food-you-need-to-hold-utensil", ("utensil", _utensilsNeeded)));
return false; return false;
} }
} }
@@ -158,12 +161,12 @@ namespace Content.Server.Nutrition.Components
} }
var transferAmount = TransferAmount != null ? ReagentUnit.Min((ReagentUnit)TransferAmount, solution.CurrentVolume) : solution.CurrentVolume; var transferAmount = TransferAmount != null ? ReagentUnit.Min((ReagentUnit)TransferAmount, solution.CurrentVolume) : solution.CurrentVolume;
var split = solution.SplitSolution(transferAmount); var split = solutionContainerSys.SplitSolution(Owner.Uid, solution, transferAmount);
var firstStomach = stomachs.FirstOrDefault(stomach => stomach.CanTransferSolution(split)); var firstStomach = stomachs.FirstOrDefault(stomach => stomach.CanTransferSolution(split));
if (firstStomach == null) if (firstStomach == null)
{ {
solution.TryAddSolution(split); solutionContainerSys.TryAddSolution(Owner.Uid, solution, split);
trueTarget.PopupMessage(user, Loc.GetString("food-you-cannot-eat-any-more")); trueTarget.PopupMessage(user, Loc.GetString("food-you-cannot-eat-any-more"));
return false; return false;
} }
@@ -203,6 +206,8 @@ namespace Content.Server.Nutrition.Components
return true; return true;
} }
private void DeleteAndSpawnTrash(IEntity user) private void DeleteAndSpawnTrash(IEntity user)
{ {
//We're empty. Become trash. //We're empty. Become trash.

View File

@@ -1,7 +1,8 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using Content.Server.Chemistry.Components;
using Content.Server.Hands.Components; using Content.Server.Hands.Components;
using Content.Server.Items; using Content.Server.Items;
using Content.Shared.Chemistry.Components.SolutionManager;
using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Chemistry.Reagent; using Content.Shared.Chemistry.Reagent;
using Content.Shared.Examine; using Content.Shared.Examine;
using Content.Shared.Interaction; using Content.Shared.Interaction;
@@ -36,14 +37,16 @@ namespace Content.Server.Nutrition.Components
[ViewVariables(VVAccess.ReadWrite)] [ViewVariables(VVAccess.ReadWrite)]
private ushort _totalCount = 5; private ushort _totalCount = 5;
[ViewVariables(VVAccess.ReadWrite)] public ushort Count; [ViewVariables(VVAccess.ReadWrite)]
public ushort Count;
protected override void Initialize() protected override void Initialize()
{ {
base.Initialize(); base.Initialize();
Count = _totalCount; Count = _totalCount;
Owner.EnsureComponent<FoodComponent>(); Owner.EnsureComponent<FoodComponent>();
Owner.EnsureComponent<SolutionContainerComponent>(); Owner.EnsureComponent<SolutionContainerManagerComponent>();
EntitySystem.Get<SolutionContainerSystem>().EnsureSolution(Owner, FoodComponent.SolutionName);
} }
async Task<bool> IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs) async Task<bool> IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs)
@@ -52,10 +55,12 @@ namespace Content.Server.Nutrition.Components
{ {
return false; return false;
} }
if (!Owner.TryGetComponent(out SolutionContainerComponent? solution))
if (!EntitySystem.Get<SolutionContainerSystem>().TryGetSolution(Owner, FoodComponent.SolutionName, out var solution))
{ {
return false; return false;
} }
if (!eventArgs.Using.TryGetComponent(out UtensilComponent? utensil) || !utensil.HasType(UtensilType.Knife)) if (!eventArgs.Using.TryGetComponent(out UtensilComponent? utensil) || !utensil.HasType(UtensilType.Knife))
{ {
return false; return false;
@@ -79,7 +84,9 @@ namespace Content.Server.Nutrition.Components
Owner.Delete(); Owner.Delete();
return true; return true;
} }
solution.TryRemoveReagent("Nutriment", solution.CurrentVolume / ReagentUnit.New(Count + 1));
EntitySystem.Get<SolutionContainerSystem>().TryRemoveReagent(Owner.Uid, solution, "Nutriment",
solution.CurrentVolume / ReagentUnit.New(Count + 1));
return true; return true;
} }

View File

@@ -1,7 +1,8 @@
using Content.Server.Chemistry.Components;
using Content.Server.Fluids.Components; using Content.Server.Fluids.Components;
using Content.Server.Notification; using Content.Server.Notification;
using Content.Server.Nutrition.Components;
using Content.Shared.Audio; using Content.Shared.Audio;
using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Notification.Managers; using Content.Shared.Notification.Managers;
using Content.Shared.Nutrition.Components; using Content.Shared.Nutrition.Components;
using Content.Shared.Nutrition.EntitySystems; using Content.Shared.Nutrition.EntitySystems;
@@ -9,6 +10,7 @@ using Content.Shared.Throwing;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Shared.Audio; using Robust.Shared.Audio;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Localization; using Robust.Shared.Localization;
using Robust.Shared.Player; using Robust.Shared.Player;
@@ -17,13 +19,15 @@ namespace Content.Server.Nutrition.EntitySystems
[UsedImplicitly] [UsedImplicitly]
public class CreamPieSystem : SharedCreamPieSystem public class CreamPieSystem : SharedCreamPieSystem
{ {
[Dependency] private readonly SolutionContainerSystem _solutionsSystem = default!;
protected override void SplattedCreamPie(EntityUid uid, CreamPieComponent creamPie) protected override void SplattedCreamPie(EntityUid uid, CreamPieComponent creamPie)
{ {
SoundSystem.Play(Filter.Pvs(creamPie.Owner), creamPie.Sound.GetSound(), creamPie.Owner, AudioHelpers.WithVariation(0.125f)); SoundSystem.Play(Filter.Pvs(creamPie.Owner), creamPie.Sound.GetSound(), creamPie.Owner, AudioHelpers.WithVariation(0.125f));
if (ComponentManager.TryGetComponent(uid, out SolutionContainerComponent? solution)) if (_solutionsSystem.TryGetSolution(creamPie.Owner, FoodComponent.SolutionName, out var solution))
{ {
solution.Solution.SpillAt(creamPie.Owner, "PuddleSmear", false); solution.SpillAt(creamPie.Owner, "PuddleSmear", false);
} }
} }

View File

@@ -0,0 +1,47 @@
using Content.Server.Nutrition.Components;
using Content.Shared.Chemistry.Components.SolutionManager;
using Content.Shared.Chemistry.EntitySystems;
using JetBrains.Annotations;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
namespace Content.Server.Nutrition.EntitySystems
{
[UsedImplicitly]
public class DrinkSystem : EntitySystem
{
[Dependency] private readonly SolutionContainerSystem _solutionContainerSystem = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<DrinkComponent, SolutionChangedEvent>(OnSolutionChange);
SubscribeLocalEvent<DrinkComponent, ComponentInit>(OnDrinkInit);
}
private void OnDrinkInit(EntityUid uid, DrinkComponent component, ComponentInit args)
{
component.Opened = component.DefaultToOpened;
var owner = EntityManager.GetEntity(uid);
if (owner.TryGetComponent(out DrainableSolutionComponent? existingDrainable))
{
// Beakers have Drink component but they should use the existing Drainable
component.SolutionName = existingDrainable.Solution;
}
else
{
_solutionContainerSystem.EnsureSolution(owner, component.SolutionName);
}
component.UpdateAppearance();
}
private void OnSolutionChange(EntityUid uid, DrinkComponent component, SolutionChangedEvent args)
{
component.UpdateAppearance();
}
}
}

View File

@@ -21,15 +21,16 @@ namespace Content.Server.PowerCell.Components
/// </summary> /// </summary>
[RegisterComponent] [RegisterComponent]
[ComponentReference(typeof(BatteryComponent))] [ComponentReference(typeof(BatteryComponent))]
public class PowerCellComponent : BatteryComponent, IExamine, ISolutionChange public class PowerCellComponent : BatteryComponent, IExamine
{ {
public override string Name => "PowerCell"; public override string Name => "PowerCell";
public const string SolutionName = "powerCell";
[ViewVariables] public PowerCellSize CellSize => _cellSize; [ViewVariables] public PowerCellSize CellSize => _cellSize;
[DataField("cellSize")] [DataField("cellSize")]
private PowerCellSize _cellSize = PowerCellSize.Small; private PowerCellSize _cellSize = PowerCellSize.Small;
[ViewVariables] public bool IsRigged { get; private set; } [ViewVariables] public bool IsRigged { get; set; }
protected override void Initialize() protected override void Initialize()
{ {
@@ -96,13 +97,6 @@ namespace Content.Server.PowerCell.Components
message.AddMarkup(Loc.GetString("power-cell-component-examine-details", ("currentCharge", $"{CurrentCharge / MaxCharge * 100:F0}"))); message.AddMarkup(Loc.GetString("power-cell-component-examine-details", ("currentCharge", $"{CurrentCharge / MaxCharge * 100:F0}")));
} }
} }
void ISolutionChange.SolutionChanged(SolutionChangeEventArgs eventArgs)
{
IsRigged = Owner.TryGetComponent(out SolutionContainerComponent? solution)
&& solution.Solution.ContainsReagent("Plasma", out var plasma)
&& plasma >= 5;
}
} }
public enum PowerCellSize public enum PowerCellSize

View File

@@ -0,0 +1,28 @@
using Content.Server.PowerCell.Components;
using Content.Shared.Chemistry.EntitySystems;
using JetBrains.Annotations;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
namespace Content.Server.PowerCell
{
[UsedImplicitly]
public class PowerCellSystem : EntitySystem
{
[Dependency] private readonly SolutionContainerSystem _solutionsSystem = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<PowerCellComponent, SolutionChangedEvent>(OnSolutionChange);
}
private void OnSolutionChange(EntityUid uid, PowerCellComponent component, SolutionChangedEvent args)
{
component.IsRigged = _solutionsSystem.TryGetSolution(uid, PowerCellComponent.SolutionName, out var solution)
&& solution.ContainsReagent("Plasma", out var plasma)
&& plasma >= 5;
}
}
}

View File

@@ -8,10 +8,10 @@ using Content.Server.Explosion;
using Content.Server.Items; using Content.Server.Items;
using Content.Server.Notification; using Content.Server.Notification;
using Content.Shared.Audio; using Content.Shared.Audio;
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.Chemistry.Reagent;
using Content.Shared.Chemistry.Solution.Components;
using Content.Shared.Examine;
using Content.Shared.Interaction; using Content.Shared.Interaction;
using Content.Shared.Notification.Managers; using Content.Shared.Notification.Managers;
using Content.Shared.Sound; using Content.Shared.Sound;
@@ -26,7 +26,6 @@ using Robust.Shared.Localization;
using Robust.Shared.Player; using Robust.Shared.Player;
using Robust.Shared.Players; using Robust.Shared.Players;
using Robust.Shared.Serialization.Manager.Attributes; using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.Utility;
using Robust.Shared.ViewVariables; using Robust.Shared.ViewVariables;
namespace Content.Server.Tools.Components namespace Content.Server.Tools.Components
@@ -36,12 +35,14 @@ namespace Content.Server.Tools.Components
[ComponentReference(typeof(IToolComponent))] [ComponentReference(typeof(IToolComponent))]
[ComponentReference(typeof(IHotItem))] [ComponentReference(typeof(IHotItem))]
[NetworkedComponent()] [NetworkedComponent()]
public class WelderComponent : ToolComponent, IExamine, IUse, ISuicideAct, ISolutionChange, IHotItem, IAfterInteract public class WelderComponent : ToolComponent, IUse, ISuicideAct, IHotItem, IAfterInteract
{ {
[Dependency] private readonly IEntitySystemManager _entitySystemManager = default!; [Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;
public override string Name => "Welder"; public override string Name => "Welder";
public const string SolutionName = "welder";
/// <summary> /// <summary>
/// Default Cost of using the welder fuel for an action /// Default Cost of using the welder fuel for an action
/// </summary> /// </summary>
@@ -55,7 +56,6 @@ namespace Content.Server.Tools.Components
private bool _welderLit; private bool _welderLit;
private WelderSystem _welderSystem = default!; private WelderSystem _welderSystem = default!;
private SpriteComponent? _spriteComponent; private SpriteComponent? _spriteComponent;
private SolutionContainerComponent? _solutionComponent;
private PointLightComponent? _pointLightComponent; private PointLightComponent? _pointLightComponent;
[DataField("weldSounds")] [DataField("weldSounds")]
@@ -70,11 +70,19 @@ namespace Content.Server.Tools.Components
[DataField("welderRefill")] [DataField("welderRefill")]
private SoundSpecifier WelderRefill { get; set; } = new SoundPathSpecifier("/Audio/Effects/refill.ogg"); private SoundSpecifier WelderRefill { get; set; } = new SoundPathSpecifier("/Audio/Effects/refill.ogg");
[ViewVariables] [ViewVariables] public float Fuel => WelderSolution?.GetReagentQuantity("WeldingFuel").Float() ?? 0f;
public float Fuel => _solutionComponent?.Solution?.GetReagentQuantity("WeldingFuel").Float() ?? 0f;
[ViewVariables] [ViewVariables] public float FuelCapacity => WelderSolution?.MaxVolume.Float() ?? 0f;
public float FuelCapacity => _solutionComponent?.MaxVolume.Float() ?? 0f;
private Solution? WelderSolution
{
get
{
Owner.EntityManager.EntitySysManager.GetEntitySystem<SolutionContainerSystem>()
.TryGetSolution(Owner, SolutionName, out var solution);
return solution;
}
}
/// <summary> /// <summary>
/// Status of welder, whether it is ignited /// Status of welder, whether it is ignited
@@ -103,9 +111,10 @@ namespace Content.Server.Tools.Components
_welderSystem = _entitySystemManager.GetEntitySystem<WelderSystem>(); _welderSystem = _entitySystemManager.GetEntitySystem<WelderSystem>();
Owner.TryGetComponent(out _solutionComponent); Owner.EnsureComponent<SolutionContainerManagerComponent>();
Owner.TryGetComponent(out _spriteComponent); Owner.TryGetComponent(out _spriteComponent);
Owner.TryGetComponent(out _pointLightComponent); Owner.TryGetComponent(out _pointLightComponent);
EntitySystem.Get<SolutionContainerSystem>().EnsureSolution(Owner, "welder");
} }
public override ComponentState GetComponentState(ICommonSession player) public override ComponentState GetComponentState(ICommonSession player)
@@ -113,7 +122,8 @@ namespace Content.Server.Tools.Components
return new WelderComponentState(FuelCapacity, Fuel, WelderLit); return new WelderComponentState(FuelCapacity, Fuel, WelderLit);
} }
public override async Task<bool> UseTool(IEntity user, IEntity? target, float doAfterDelay, ToolQuality toolQualityNeeded, Func<bool>? doAfterCheck = null) public override async Task<bool> UseTool(IEntity user, IEntity? target, float doAfterDelay,
ToolQuality toolQualityNeeded, Func<bool>? doAfterCheck = null)
{ {
bool ExtraCheck() bool ExtraCheck()
{ {
@@ -134,7 +144,8 @@ namespace Content.Server.Tools.Components
return toolQualityNeeded.HasFlag(ToolQuality.Welding) ? canUse && TryWeld(DefaultFuelCost, user) : canUse; return toolQualityNeeded.HasFlag(ToolQuality.Welding) ? canUse && TryWeld(DefaultFuelCost, user) : canUse;
} }
public async Task<bool> UseTool(IEntity user, IEntity target, float doAfterDelay, ToolQuality toolQualityNeeded, float fuelConsumed, Func<bool>? doAfterCheck = null) public async Task<bool> UseTool(IEntity user, IEntity target, float doAfterDelay, ToolQuality toolQualityNeeded,
float fuelConsumed, Func<bool>? doAfterCheck = null)
{ {
bool ExtraCheck() bool ExtraCheck()
{ {
@@ -143,7 +154,8 @@ namespace Content.Server.Tools.Components
return extraCheck && CanWeld(fuelConsumed); return extraCheck && CanWeld(fuelConsumed);
} }
return await base.UseTool(user, target, doAfterDelay, toolQualityNeeded, ExtraCheck) && TryWeld(fuelConsumed, user); return await base.UseTool(user, target, doAfterDelay, toolQualityNeeded, ExtraCheck) &&
TryWeld(fuelConsumed, user);
} }
private bool TryWeld(float value, IEntity? user = null, bool silent = false) private bool TryWeld(float value, IEntity? user = null, bool silent = false)
@@ -164,15 +176,17 @@ namespace Content.Server.Tools.Components
return false; return false;
} }
if (_solutionComponent == null) if (WelderSolution == null)
return false; return false;
var succeeded = _solutionComponent.TryRemoveReagent("WeldingFuel", ReagentUnit.New(value)); var succeeded = EntitySystem.Get<SolutionContainerSystem>()
.TryRemoveReagent(Owner.Uid, WelderSolution, "WeldingFuel", ReagentUnit.New(value));
if (succeeded && !silent) if (succeeded && !silent)
{ {
PlaySound(WeldSounds); PlaySound(WeldSounds);
} }
return succeeded; return succeeded;
} }
@@ -232,26 +246,6 @@ namespace Content.Server.Tools.Components
return ToggleWelderStatus(eventArgs.User); return ToggleWelderStatus(eventArgs.User);
} }
public void Examine(FormattedMessage message, bool inDetailsRange)
{
if (WelderLit)
{
message.AddMarkup(Loc.GetString("welder-component-on-examine-welder-lit-message") + "\n");
}
else
{
message.AddText(Loc.GetString("welder-component-on-examine-welder-not-lit-message") + "\n");
}
if (inDetailsRange)
{
message.AddMarkup(Loc.GetString("welder-component-on-examine-detailed-message",
("colorName", Fuel < FuelCapacity / 4f ? "darkorange" : "orange"),
("fuelLeft", Math.Round(Fuel)),
("fuelCapacity", FuelCapacity)));
}
}
protected override void Shutdown() protected override void Shutdown()
{ {
base.Shutdown(); base.Shutdown();
@@ -263,13 +257,13 @@ namespace Content.Server.Tools.Components
if (!HasQuality(ToolQuality.Welding) || !WelderLit || Owner.Deleted) if (!HasQuality(ToolQuality.Welding) || !WelderLit || Owner.Deleted)
return; return;
_solutionComponent?.TryRemoveReagent("WeldingFuel", ReagentUnit.New(FuelLossRate * frameTime)); EntitySystem.Get<SolutionContainerSystem>().TryRemoveReagent(Owner.Uid, WelderSolution, "WeldingFuel",
ReagentUnit.New(FuelLossRate * frameTime));
EntitySystem.Get<AtmosphereSystem>().HotspotExpose(Owner.Transform.Coordinates, 700, 50, true); EntitySystem.Get<AtmosphereSystem>().HotspotExpose(Owner.Transform.Coordinates, 700, 50, true);
if (Fuel == 0) if (Fuel == 0)
ToggleWelderStatus(); ToggleWelderStatus();
} }
SuicideKind ISuicideAct.Suicide(IEntity victim, IChatManager chat) SuicideKind ISuicideAct.Suicide(IEntity victim, IChatManager chat)
@@ -283,7 +277,7 @@ namespace Content.Server.Tools.Components
othersMessage = othersMessage =
Loc.GetString("welder-component-suicide-lit-others-message", Loc.GetString("welder-component-suicide-lit-others-message",
("victim",victim)); ("victim", victim));
victim.PopupMessageOtherClients(othersMessage); victim.PopupMessageOtherClients(othersMessage);
selfMessage = Loc.GetString("welder-component-suicide-lit-message"); selfMessage = Loc.GetString("welder-component-suicide-lit-message");
@@ -301,12 +295,6 @@ namespace Content.Server.Tools.Components
return SuicideKind.Blunt; return SuicideKind.Blunt;
} }
public void SolutionChanged(SolutionChangeEventArgs eventArgs)
{
Dirty();
}
async Task<bool> IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs) async Task<bool> IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs)
{ {
if (eventArgs.Target == null || !eventArgs.CanReach) if (eventArgs.Target == null || !eventArgs.CanReach)
@@ -316,9 +304,9 @@ namespace Content.Server.Tools.Components
if (eventArgs.Target.TryGetComponent(out ReagentTankComponent? tank) if (eventArgs.Target.TryGetComponent(out ReagentTankComponent? tank)
&& tank.TankType == ReagentTankType.Fuel && tank.TankType == ReagentTankType.Fuel
&& eventArgs.Target.TryGetComponent(out ISolutionInteractionsComponent? targetSolution) && EntitySystem.Get<SolutionContainerSystem>()
&& targetSolution.CanDrain .TryGetDrainableSolution(eventArgs.Target.Uid, out var targetSolution)
&& _solutionComponent != null) && WelderSolution != null)
{ {
if (WelderLit) if (WelderLit)
{ {
@@ -327,13 +315,14 @@ namespace Content.Server.Tools.Components
return true; return true;
} }
var trans = ReagentUnit.Min(_solutionComponent.EmptyVolume, targetSolution.DrainAvailable); var trans = ReagentUnit.Min(WelderSolution.AvailableVolume, targetSolution.DrainAvailable);
if (trans > 0) if (trans > 0)
{ {
var drained = targetSolution.Drain(trans); var drained = EntitySystem.Get<SolutionContainerSystem>().Drain(eventArgs.Target.Uid, targetSolution, trans);
_solutionComponent.TryAddSolution(drained); EntitySystem.Get<SolutionContainerSystem>().TryAddSolution(Owner.Uid, WelderSolution, drained);
SoundSystem.Play(Filter.Pvs(Owner), WelderRefill.GetSound(), Owner); SoundSystem.Play(Filter.Pvs(Owner), WelderRefill.GetSound(), Owner);
eventArgs.Target.PopupMessage(eventArgs.User, Loc.GetString("welder-component-after-interact-refueled-message")); eventArgs.Target.PopupMessage(eventArgs.User,
Loc.GetString("welder-component-after-interact-refueled-message"));
} }
} }

View File

@@ -1,7 +1,11 @@
using System.Collections.Generic; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using Content.Server.Tools.Components; using Content.Server.Tools.Components;
using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Examine;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.Localization;
namespace Content.Server.Tools namespace Content.Server.Tools
{ {
@@ -12,6 +16,39 @@ namespace Content.Server.Tools
{ {
private readonly HashSet<WelderComponent> _activeWelders = new(); private readonly HashSet<WelderComponent> _activeWelders = new();
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<WelderComponent, SolutionChangedEvent>(OnSolutionChange);
SubscribeLocalEvent<WelderComponent, ExaminedEvent>(OnExamine);
}
private void OnExamine(EntityUid uid, WelderComponent component, ExaminedEvent args)
{
if (component.WelderLit)
{
args.Message.AddMarkup(Loc.GetString("welder-component-on-examine-welder-lit-message") + "\n");
}
else
{
args.Message.AddText(Loc.GetString("welder-component-on-examine-welder-not-lit-message") + "\n");
}
if (args.IsInDetailsRange)
{
args.Message.AddMarkup(Loc.GetString("welder-component-on-examine-detailed-message",
("colorName", component.Fuel < component.FuelCapacity / 4f ? "darkorange" : "orange"),
("fuelLeft", Math.Round(component.Fuel)),
("fuelCapacity", component.FuelCapacity)));
}
}
private void OnSolutionChange(EntityUid uid, WelderComponent component, SolutionChangedEvent args)
{
component.Dirty();
}
public bool Subscribe(WelderComponent welder) public bool Subscribe(WelderComponent welder)
{ {
return _activeWelders.Add(welder); return _activeWelders.Add(welder);

View File

@@ -5,6 +5,7 @@ using Content.Server.Body.Circulatory;
using Content.Server.Chemistry.Components; using Content.Server.Chemistry.Components;
using Content.Server.Cooldown; using Content.Server.Cooldown;
using Content.Server.Weapon.Melee.Components; using Content.Server.Weapon.Melee.Components;
using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Damage.Components; using Content.Shared.Damage.Components;
using Content.Shared.Hands; using Content.Shared.Hands;
using Content.Shared.Interaction; using Content.Shared.Interaction;
@@ -16,7 +17,6 @@ using Robust.Shared.IoC;
using Robust.Shared.Map; using Robust.Shared.Map;
using Robust.Shared.Maths; using Robust.Shared.Maths;
using Robust.Shared.Physics; using Robust.Shared.Physics;
using Robust.Shared.Physics.Broadphase;
using Robust.Shared.Player; using Robust.Shared.Player;
using Robust.Shared.Timing; using Robust.Shared.Timing;
@@ -25,7 +25,7 @@ namespace Content.Server.Weapon.Melee
public sealed class MeleeWeaponSystem : EntitySystem public sealed class MeleeWeaponSystem : EntitySystem
{ {
[Dependency] private IGameTiming _gameTiming = default!; [Dependency] private IGameTiming _gameTiming = default!;
[Dependency] private SolutionContainerSystem _solutionsSystem = default!;
public override void Initialize() public override void Initialize()
{ {
@@ -219,7 +219,7 @@ namespace Content.Server.Weapon.Melee
for (var i = 0; i < increments; i++) for (var i = 0; i < increments; i++)
{ {
var castAngle = new Angle(baseAngle + increment * i); var castAngle = new Angle(baseAngle + increment * i);
var res = EntitySystem.Get<SharedBroadphaseSystem>().IntersectRay(mapId, var res = Get<SharedBroadphaseSystem>().IntersectRay(mapId,
new CollisionRay(position, castAngle.ToWorldVec(), new CollisionRay(position, castAngle.ToWorldVec(),
(int) (CollisionGroup.Impassable | CollisionGroup.MobImpassable)), range, ignore).ToList(); (int) (CollisionGroup.Impassable | CollisionGroup.MobImpassable)), range, ignore).ToList();
@@ -234,7 +234,8 @@ namespace Content.Server.Weapon.Melee
private void OnChemicalInjectorHit(EntityUid uid, MeleeChemicalInjectorComponent comp, MeleeHitEvent args) private void OnChemicalInjectorHit(EntityUid uid, MeleeChemicalInjectorComponent comp, MeleeHitEvent args)
{ {
if (!ComponentManager.TryGetComponent<SolutionContainerComponent>(uid, out var solutionContainer)) IEntity owner = EntityManager.GetEntity(uid);
if (!_solutionsSystem.TryGetInjectableSolution(owner.Uid, out var solutionContainer))
return; return;
var hitBloodstreams = new List<BloodstreamComponent>(); var hitBloodstreams = new List<BloodstreamComponent>();
@@ -250,7 +251,7 @@ namespace Content.Server.Weapon.Melee
if (hitBloodstreams.Count < 1) if (hitBloodstreams.Count < 1)
return; return;
var removedSolution = solutionContainer.Solution.SplitSolution(comp.TransferAmount * hitBloodstreams.Count); var removedSolution = solutionContainer.SplitSolution(comp.TransferAmount * hitBloodstreams.Count);
var removedVol = removedSolution.TotalVolume; var removedVol = removedSolution.TotalVolume;
var solutionToInject = removedSolution.SplitSolution(removedVol * comp.TransferEfficiency); var solutionToInject = removedSolution.SplitSolution(removedVol * comp.TransferEfficiency);
var volPerBloodstream = solutionToInject.TotalVolume * (1 / hitBloodstreams.Count); var volPerBloodstream = solutionToInject.TotalVolume * (1 / hitBloodstreams.Count);

View File

@@ -1,8 +1,10 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Content.Server.Chemistry.Components;
using Content.Server.Weapon.Ranged.Barrels.Components; using Content.Server.Weapon.Ranged.Barrels.Components;
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.EntitySystems;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.Serialization.Manager.Attributes;
namespace Content.Server.Weapon.Ranged.Ammunition.Components namespace Content.Server.Weapon.Ranged.Ammunition.Components
{ {
@@ -10,6 +12,10 @@ namespace Content.Server.Weapon.Ranged.Ammunition.Components
public class ChemicalAmmoComponent : Component public class ChemicalAmmoComponent : Component
{ {
public override string Name => "ChemicalAmmo"; public override string Name => "ChemicalAmmo";
public const string DefaultSolutionName = "ammo";
[DataField("solution")]
public string SolutionName { get; set; } = DefaultSolutionName;
public override void HandleMessage(ComponentMessage message, IComponent? component) public override void HandleMessage(ComponentMessage message, IComponent? component)
{ {
@@ -24,32 +30,34 @@ namespace Content.Server.Weapon.Ranged.Ammunition.Components
private void TransferSolution(BarrelFiredMessage barrelFired) private void TransferSolution(BarrelFiredMessage barrelFired)
{ {
if (!Owner.TryGetComponent<SolutionContainerComponent>(out var ammoSolutionContainer)) if (!EntitySystem.Get<SolutionContainerSystem>().TryGetSolution(Owner, SolutionName, out var ammoSolution))
return; return;
var projectiles = barrelFired.FiredProjectiles; var projectiles = barrelFired.FiredProjectiles;
var solutionContainerSystem = EntitySystem.Get<SolutionContainerSystem>();
var projectileSolutionContainers = new List<SolutionContainerComponent>(); var projectileSolutionContainers = new List<(EntityUid, Solution)>();
foreach (var projectile in projectiles) foreach (var projectile in projectiles)
{ {
if (projectile.TryGetComponent<SolutionContainerComponent>(out var projectileSolutionContainer)) if (EntitySystem.Get<SolutionContainerSystem>()
.TryGetSolution(projectile, SolutionName, out var projectileSolutionContainer))
{ {
projectileSolutionContainers.Add(projectileSolutionContainer); projectileSolutionContainers.Add((projectile.Uid, projectileSolutionContainer));
} }
} }
if (!projectileSolutionContainers.Any()) if (!projectileSolutionContainers.Any())
return; return;
var solutionPerProjectile = ammoSolutionContainer.CurrentVolume * (1 / projectileSolutionContainers.Count); var solutionPerProjectile = ammoSolution.CurrentVolume * (1 / projectileSolutionContainers.Count);
foreach (var projectileSolutionContainer in projectileSolutionContainers) foreach (var (projectileUid, projectileSolution) in projectileSolutionContainers)
{ {
var solutionToTransfer = ammoSolutionContainer.SplitSolution(solutionPerProjectile); var solutionToTransfer = solutionContainerSystem.SplitSolution(Owner.Uid, ammoSolution, solutionPerProjectile);
projectileSolutionContainer.TryAddSolution(solutionToTransfer); solutionContainerSystem.TryAddSolution(projectileUid, projectileSolution, solutionToTransfer);
} }
ammoSolutionContainer.RemoveAllSolution(); solutionContainerSystem.RemoveAllSolution(Owner.Uid, ammoSolution);
} }
} }
} }

View File

@@ -1,4 +1,4 @@
using Content.Shared.Chemistry.Solution; using Content.Shared.Chemistry.Components;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
namespace Content.Shared.Body.Networks namespace Content.Shared.Body.Networks
@@ -12,5 +12,7 @@ namespace Content.Shared.Body.Networks
/// <param name="solution">The solution to be transferred.</param> /// <param name="solution">The solution to be transferred.</param>
/// <returns>Whether or not transfer was successful.</returns> /// <returns>Whether or not transfer was successful.</returns>
public abstract bool TryTransferSolution(Solution solution); public abstract bool TryTransferSolution(Solution solution);
public const string DefaultSolutionName = "bloodstream";
} }
} }

View File

@@ -1,5 +1,3 @@
using System;
using System.Linq;
using Content.Shared.Chemistry.Reaction; using Content.Shared.Chemistry.Reaction;
using Content.Shared.Chemistry.Reagent; using Content.Shared.Chemistry.Reagent;
using JetBrains.Annotations; using JetBrains.Annotations;
@@ -9,51 +7,20 @@ using Robust.Shared.Prototypes;
namespace Content.Shared.Chemistry namespace Content.Shared.Chemistry
{ {
/// <summary>
/// This interface gives components behavior on whether entities solution (implying SolutionComponent is in place) is changed
/// </summary>
public interface ISolutionChange
{
/// <summary>
/// Called when solution is mixed with some other solution, or when some part of the solution is removed
/// </summary>
void SolutionChanged(SolutionChangeEventArgs eventArgs);
}
public class SolutionChangeEventArgs : EventArgs
{
public IEntity Owner { get; set; } = default!;
}
[UsedImplicitly] [UsedImplicitly]
public class ChemistrySystem : EntitySystem public partial class ChemistrySystem : EntitySystem
{ {
[Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
public void HandleSolutionChange(IEntity owner) public void ReactionEntity(IEntity? entity, ReactionMethod method, string reagentId, ReagentUnit reactVolume,
{ Components.Solution? source)
var eventArgs = new SolutionChangeEventArgs
{
Owner = owner,
};
var solutionChangeArgs = owner.GetAllComponents<ISolutionChange>().ToList();
foreach (var solutionChangeArg in solutionChangeArgs)
{
solutionChangeArg.SolutionChanged(eventArgs);
if (owner.Deleted)
return;
}
}
public void ReactionEntity(IEntity? entity, ReactionMethod method, string reagentId, ReagentUnit reactVolume, Solution.Solution? source)
{ {
// We throw if the reagent specified doesn't exist. // We throw if the reagent specified doesn't exist.
ReactionEntity(entity, method, _prototypeManager.Index<ReagentPrototype>(reagentId), reactVolume, source); ReactionEntity(entity, method, _prototypeManager.Index<ReagentPrototype>(reagentId), reactVolume, source);
} }
public void ReactionEntity(IEntity? entity, ReactionMethod method, ReagentPrototype reagent, ReagentUnit reactVolume, Solution.Solution? source) public void ReactionEntity(IEntity? entity, ReactionMethod method, ReagentPrototype reagent,
ReagentUnit reactVolume, Components.Solution? source)
{ {
if (entity == null || entity.Deleted || !entity.TryGetComponent(out ReactiveComponent? reactive)) if (entity == null || entity.Deleted || !entity.TryGetComponent(out ReactiveComponent? reactive))
return; return;

View File

@@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel;
using Content.Shared.Chemistry.Reagent; using Content.Shared.Chemistry.Reagent;
using Content.Shared.Cloning; using Content.Shared.Cloning;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
@@ -14,6 +15,7 @@ namespace Content.Shared.Chemistry.Components
public class SharedChemMasterComponent : Component public class SharedChemMasterComponent : Component
{ {
public override string Name => "ChemMaster"; public override string Name => "ChemMaster";
public const string SolutionName = "buffer";
[Serializable, NetSerializable] [Serializable, NetSerializable]
public class ChemMasterBoundUserInterfaceState : BoundUserInterfaceState public class ChemMasterBoundUserInterfaceState : BoundUserInterfaceState
@@ -27,11 +29,11 @@ namespace Content.Shared.Chemistry.Components
/// <summary> /// <summary>
/// A list of the reagents and their amounts within the beaker/reagent container, if applicable. /// A list of the reagents and their amounts within the beaker/reagent container, if applicable.
/// </summary> /// </summary>
public readonly IReadOnlyList<Solution.Solution.ReagentQuantity> ContainerReagents; public readonly IReadOnlyList<Solution.ReagentQuantity> ContainerReagents;
/// <summary> /// <summary>
/// A list of the reagents and their amounts within the buffer, if applicable. /// A list of the reagents and their amounts within the buffer, if applicable.
/// </summary> /// </summary>
public readonly IReadOnlyList<Solution.Solution.ReagentQuantity> BufferReagents; public readonly IReadOnlyList<Solution.ReagentQuantity> BufferReagents;
public readonly string DispenserName; public readonly string DispenserName;
public readonly bool BufferModeTransfer; public readonly bool BufferModeTransfer;
@@ -39,7 +41,7 @@ namespace Content.Shared.Chemistry.Components
public readonly ReagentUnit BufferCurrentVolume; public readonly ReagentUnit BufferCurrentVolume;
public ChemMasterBoundUserInterfaceState(bool hasPower, bool hasBeaker, ReagentUnit beakerCurrentVolume, ReagentUnit beakerMaxVolume, string containerName, public ChemMasterBoundUserInterfaceState(bool hasPower, bool hasBeaker, ReagentUnit beakerCurrentVolume, ReagentUnit beakerMaxVolume, string containerName,
string dispenserName, IReadOnlyList<Solution.Solution.ReagentQuantity> containerReagents, IReadOnlyList<Solution.Solution.ReagentQuantity> bufferReagents, bool bufferModeTransfer, ReagentUnit bufferCurrentVolume) string dispenserName, IReadOnlyList<Solution.ReagentQuantity> containerReagents, IReadOnlyList<Solution.ReagentQuantity> bufferReagents, bool bufferModeTransfer, ReagentUnit bufferCurrentVolume)
{ {
HasPower = hasPower; HasPower = hasPower;
HasBeaker = hasBeaker; HasBeaker = hasBeaker;

View File

@@ -10,6 +10,7 @@ namespace Content.Shared.Chemistry.Components
public abstract class SharedHyposprayComponent : Component public abstract class SharedHyposprayComponent : Component
{ {
public sealed override string Name => "Hypospray"; public sealed override string Name => "Hypospray";
public const string SolutionName = "hypospray";
[Serializable, NetSerializable] [Serializable, NetSerializable]
protected sealed class HyposprayComponentState : ComponentState protected sealed class HyposprayComponentState : ComponentState

View File

@@ -0,0 +1,50 @@
using Content.Shared.Chemistry.Reagent;
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
namespace Content.Shared.Chemistry.Components
{
public partial class Solution
{
/// <summary>
/// If reactions will be checked for when adding reagents to the container.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
[DataField("canReact")]
public bool CanReact { get; set; } = true;
/// <summary>
/// Volume needed to fill this container.
/// </summary>
[ViewVariables]
public ReagentUnit AvailableVolume => MaxVolume - CurrentVolume;
public ReagentUnit DrawAvailable => CurrentVolume;
public ReagentUnit DrainAvailable => CurrentVolume;
/// <summary>
/// Checks if a solution can fit into the container.
/// </summary>
/// <param name="solution">The solution that is trying to be added.</param>
/// <returns>If the solution can be fully added.</returns>
public bool CanAddSolution(Solution solution)
{
return solution.TotalVolume <= AvailableVolume;
}
[DataField("maxSpillRefill")]
public ReagentUnit MaxSpillRefill { get; set; }
[ViewVariables(VVAccess.ReadWrite)]
[DataField("maxVol")]
public ReagentUnit MaxVolume { get; set; } = ReagentUnit.Zero;
[ViewVariables]
public ReagentUnit CurrentVolume => TotalVolume;
// [ViewVariables]
// public EntityUid OwnerUid { get; set; }
}
}

View File

@@ -14,27 +14,25 @@ using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototy
using Robust.Shared.Utility; using Robust.Shared.Utility;
using Robust.Shared.ViewVariables; using Robust.Shared.ViewVariables;
namespace Content.Shared.Chemistry.Solution namespace Content.Shared.Chemistry.Components
{ {
/// <summary> /// <summary>
/// A solution of reagents. /// A solution of reagents.
/// </summary> /// </summary>
[Serializable, NetSerializable] [Serializable, NetSerializable]
[DataDefinition] [DataDefinition]
public class Solution : IEnumerable<Solution.ReagentQuantity>, ISerializationHooks public partial class Solution : IEnumerable<Solution.ReagentQuantity>, ISerializationHooks
{ {
// Most objects on the station hold only 1 or 2 reagents // Most objects on the station hold only 1 or 2 reagents
[ViewVariables] [ViewVariables]
[DataField("reagents")] [DataField("reagents")]
private List<ReagentQuantity> _contents = new(2); public List<ReagentQuantity> Contents = new(2);
public IReadOnlyList<ReagentQuantity> Contents => _contents;
/// <summary> /// <summary>
/// The calculated total volume of all reagents in the solution (ex. Total volume of liquid in beaker). /// The calculated total volume of all reagents in the solution (ex. Total volume of liquid in beaker).
/// </summary> /// </summary>
[ViewVariables] [ViewVariables]
public ReagentUnit TotalVolume { get; private set; } public ReagentUnit TotalVolume { get; set; }
public Color Color => GetColor(); public Color Color => GetColor();
@@ -56,7 +54,7 @@ namespace Content.Shared.Chemistry.Solution
void ISerializationHooks.AfterDeserialization() void ISerializationHooks.AfterDeserialization()
{ {
TotalVolume = ReagentUnit.Zero; TotalVolume = ReagentUnit.Zero;
_contents.ForEach(reagent => TotalVolume += reagent.Quantity); Contents.ForEach(reagent => TotalVolume += reagent.Quantity);
} }
public bool ContainsReagent(string reagentId) public bool ContainsReagent(string reagentId)
@@ -100,18 +98,18 @@ namespace Content.Shared.Chemistry.Solution
if (quantity <= 0) if (quantity <= 0)
return; return;
for (var i = 0; i < _contents.Count; i++) for (var i = 0; i < Contents.Count; i++)
{ {
var reagent = _contents[i]; var reagent = Contents[i];
if (reagent.ReagentId != reagentId) if (reagent.ReagentId != reagentId)
continue; continue;
_contents[i] = new ReagentQuantity(reagentId, reagent.Quantity + quantity); Contents[i] = new ReagentQuantity(reagentId, reagent.Quantity + quantity);
TotalVolume += quantity; TotalVolume += quantity;
return; return;
} }
_contents.Add(new ReagentQuantity(reagentId, quantity)); Contents.Add(new ReagentQuantity(reagentId, quantity));
TotalVolume += quantity; TotalVolume += quantity;
} }
@@ -122,7 +120,7 @@ namespace Content.Shared.Chemistry.Solution
public void ScaleSolution(float scale) public void ScaleSolution(float scale)
{ {
if (scale == 1) return; if (scale == 1) return;
var tempContents = new List<ReagentQuantity>(_contents); var tempContents = new List<ReagentQuantity>(Contents);
foreach(ReagentQuantity current in tempContents) foreach(ReagentQuantity current in tempContents)
{ {
if(scale > 1) if(scale > 1)
@@ -143,10 +141,10 @@ namespace Content.Shared.Chemistry.Solution
/// <returns>The quantity in milli-units.</returns> /// <returns>The quantity in milli-units.</returns>
public ReagentUnit GetReagentQuantity(string reagentId) public ReagentUnit GetReagentQuantity(string reagentId)
{ {
for (var i = 0; i < _contents.Count; i++) for (var i = 0; i < Contents.Count; i++)
{ {
if (_contents[i].ReagentId == reagentId) if (Contents[i].ReagentId == reagentId)
return _contents[i].Quantity; return Contents[i].Quantity;
} }
return ReagentUnit.New(0); return ReagentUnit.New(0);
@@ -157,9 +155,9 @@ namespace Content.Shared.Chemistry.Solution
if(quantity <= 0) if(quantity <= 0)
return; return;
for (var i = 0; i < _contents.Count; i++) for (var i = 0; i < Contents.Count; i++)
{ {
var reagent = _contents[i]; var reagent = Contents[i];
if(reagent.ReagentId != reagentId) if(reagent.ReagentId != reagentId)
continue; continue;
@@ -168,12 +166,12 @@ namespace Content.Shared.Chemistry.Solution
var newQuantity = curQuantity - quantity; var newQuantity = curQuantity - quantity;
if (newQuantity <= 0) if (newQuantity <= 0)
{ {
_contents.RemoveSwap(i); Contents.RemoveSwap(i);
TotalVolume -= curQuantity; TotalVolume -= curQuantity;
} }
else else
{ {
_contents[i] = new ReagentQuantity(reagentId, newQuantity); Contents[i] = new ReagentQuantity(reagentId, newQuantity);
TotalVolume -= quantity; TotalVolume -= quantity;
} }
@@ -198,16 +196,16 @@ namespace Content.Shared.Chemistry.Solution
return; return;
} }
for (var i = 0; i < _contents.Count; i++) for (var i = 0; i < Contents.Count; i++)
{ {
var reagent = _contents[i]; var reagent = Contents[i];
var oldQuantity = reagent.Quantity; var oldQuantity = reagent.Quantity;
// quantity taken is always a little greedy, so fractional quantities get rounded up to the nearest // quantity taken is always a little greedy, so fractional quantities get rounded up to the nearest
// whole unit. This should prevent little bits of chemical remaining because of float rounding errors. // whole unit. This should prevent little bits of chemical remaining because of float rounding errors.
var newQuantity = oldQuantity * ratio; var newQuantity = oldQuantity * ratio;
_contents[i] = new ReagentQuantity(reagent.ReagentId, newQuantity); Contents[i] = new ReagentQuantity(reagent.ReagentId, newQuantity);
} }
TotalVolume = TotalVolume * ratio; TotalVolume = TotalVolume * ratio;
@@ -215,7 +213,7 @@ namespace Content.Shared.Chemistry.Solution
public void RemoveAllSolution() public void RemoveAllSolution()
{ {
_contents.Clear(); Contents.Clear();
TotalVolume = ReagentUnit.New(0); TotalVolume = ReagentUnit.New(0);
} }
@@ -237,17 +235,17 @@ namespace Content.Shared.Chemistry.Solution
var newTotalVolume = ReagentUnit.New(0); var newTotalVolume = ReagentUnit.New(0);
var remainingVolume = TotalVolume; var remainingVolume = TotalVolume;
for (var i = 0; i < _contents.Count; i++) for (var i = 0; i < Contents.Count; i++)
{ {
var reagent = _contents[i]; var reagent = Contents[i];
var ratio = (remainingVolume - quantity).Double() / remainingVolume.Double(); var ratio = (remainingVolume - quantity).Double() / remainingVolume.Double();
remainingVolume -= reagent.Quantity; remainingVolume -= reagent.Quantity;
var newQuantity = reagent.Quantity * ratio; var newQuantity = reagent.Quantity * ratio;
var splitQuantity = reagent.Quantity - newQuantity; var splitQuantity = reagent.Quantity - newQuantity;
_contents[i] = new ReagentQuantity(reagent.ReagentId, newQuantity); Contents[i] = new ReagentQuantity(reagent.ReagentId, newQuantity);
newSolution._contents.Add(new ReagentQuantity(reagent.ReagentId, splitQuantity)); newSolution.Contents.Add(new ReagentQuantity(reagent.ReagentId, splitQuantity));
newTotalVolume += splitQuantity; newTotalVolume += splitQuantity;
quantity -= splitQuantity; quantity -= splitQuantity;
} }
@@ -260,25 +258,25 @@ namespace Content.Shared.Chemistry.Solution
public void AddSolution(Solution otherSolution) public void AddSolution(Solution otherSolution)
{ {
for (var i = 0; i < otherSolution._contents.Count; i++) for (var i = 0; i < otherSolution.Contents.Count; i++)
{ {
var otherReagent = otherSolution._contents[i]; var otherReagent = otherSolution.Contents[i];
var found = false; var found = false;
for (var j = 0; j < _contents.Count; j++) for (var j = 0; j < Contents.Count; j++)
{ {
var reagent = _contents[j]; var reagent = Contents[j];
if (reagent.ReagentId == otherReagent.ReagentId) if (reagent.ReagentId == otherReagent.ReagentId)
{ {
found = true; found = true;
_contents[j] = new ReagentQuantity(reagent.ReagentId, reagent.Quantity + otherReagent.Quantity); Contents[j] = new ReagentQuantity(reagent.ReagentId, reagent.Quantity + otherReagent.Quantity);
break; break;
} }
} }
if (!found) if (!found)
{ {
_contents.Add(new ReagentQuantity(otherReagent.ReagentId, otherReagent.Quantity)); Contents.Add(new ReagentQuantity(otherReagent.ReagentId, otherReagent.Quantity));
} }
} }
@@ -322,10 +320,10 @@ namespace Content.Shared.Chemistry.Solution
var volume = ReagentUnit.New(0); var volume = ReagentUnit.New(0);
var newSolution = new Solution(); var newSolution = new Solution();
for (var i = 0; i < _contents.Count; i++) for (var i = 0; i < Contents.Count; i++)
{ {
var reagent = _contents[i]; var reagent = Contents[i];
newSolution._contents.Add(reagent); newSolution.Contents.Add(reagent);
volume += reagent.Quantity; volume += reagent.Quantity;
} }
@@ -337,7 +335,7 @@ namespace Content.Shared.Chemistry.Solution
{ {
var chemistry = EntitySystem.Get<ChemistrySystem>(); var chemistry = EntitySystem.Get<ChemistrySystem>();
foreach (var (reagentId, quantity) in _contents.ToArray()) foreach (var (reagentId, quantity) in Contents.ToArray())
{ {
chemistry.ReactionEntity(entity, method, reagentId, quantity, this); chemistry.ReactionEntity(entity, method, reagentId, quantity, this);
} }
@@ -377,7 +375,7 @@ namespace Content.Shared.Chemistry.Solution
public IEnumerator<ReagentQuantity> GetEnumerator() public IEnumerator<ReagentQuantity> GetEnumerator()
{ {
return _contents.GetEnumerator(); return Contents.GetEnumerator();
} }
IEnumerator IEnumerable.GetEnumerator() IEnumerator IEnumerable.GetEnumerator()

View File

@@ -0,0 +1,23 @@
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
namespace Content.Shared.Chemistry.Components.SolutionManager
{
/// <summary>
/// Denotes the solution that can be easily removed through any reagent container.
/// Think pouring this or draining from a water tank.
/// </summary>
[RegisterComponent]
public class DrainableSolutionComponent : Component
{
public override string Name => "DrainableSolution";
/// <summary>
/// Solution name that can be drained.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
[DataField("solution")]
public string Solution { get; set; } = "default";
}
}

View File

@@ -0,0 +1,22 @@
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
namespace Content.Shared.Chemistry.Components.SolutionManager
{
/// <summary>
/// Denotes the solution that can removed be with syringes.
/// </summary>
[RegisterComponent]
public class DrawableSolutionComponent : Component
{
public override string Name => "DrawableSolution";
/// <summary>
/// Solution name that can be removed with syringes.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
[DataField("solution")]
public string Solution { get; set; } = "default";
}
}

View File

@@ -0,0 +1,16 @@
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
namespace Content.Shared.Chemistry.Components.SolutionManager
{
[RegisterComponent]
public class ExaminableSolutionComponent: Component
{
public override string Name => "ExaminableSolution";
[ViewVariables(VVAccess.ReadWrite)]
[DataField("solution")]
public string Solution { get; set; } = "default";
}
}

View File

@@ -0,0 +1,24 @@
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
namespace Content.Shared.Chemistry.Components.SolutionManager
{
/// Allows the entity with this component to be placed in a <c>SharedReagentDispenserComponent</c>.
/// <para>Otherwise it's considered to be too large or the improper shape to fit.</para>
/// <para>Allows us to have obscenely large containers that are harder to abuse in chem dispensers
/// since they can't be placed directly in them.</para>
/// <see cref="Content.Shared.Chemistry.Dispenser.SharedReagentDispenserComponent"/>
[RegisterComponent]
public class FitsInDispenserComponent : Component
{
public override string Name => "FitsInDispenser";
/// <summary>
/// Solution name that will interact with ReagentDispenserComponent.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
[DataField("solution")]
public string Solution { get; set; } = "default";
}
}

View File

@@ -0,0 +1,22 @@
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
namespace Content.Shared.Chemistry.Components.SolutionManager
{
/// <summary>
/// Denotes a solution which can be added with syringes.
/// </summary>
[RegisterComponent]
public class InjectableSolutionComponent : Component
{
public override string Name => "InjectableSolution";
/// <summary>
/// Solution name which can be added with syringes.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
[DataField("solution")]
public string Solution { get; set; } = "default";
}
}

View File

@@ -0,0 +1,25 @@
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
namespace Content.Shared.Chemistry.Components.SolutionManager
{
/// <summary>
/// Reagents that can be added easily. For example like
/// pouring something into another beaker, glass, or into the gas
/// tank of a car.
/// </summary>
[RegisterComponent]
public class RefillableSolutionComponent : Component
{
public override string Name => "RefillableSolution";
/// <summary>
/// Solution name that can added to easily.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
[DataField("solution")]
public string Solution { get; set; } = "default";
}
}

View File

@@ -0,0 +1,23 @@
using System.Collections.Generic;
using Content.Shared.Chemistry.EntitySystems;
using Robust.Shared.Analyzers;
using Robust.Shared.GameObjects;
using Robust.Shared.GameStates;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
namespace Content.Shared.Chemistry.Components.SolutionManager
{
[NetworkedComponent()]
[RegisterComponent]
[DataDefinition]
[Friend(typeof(SolutionContainerSystem))]
public class SolutionContainerManagerComponent : Component
{
public override string Name => "SolutionContainerManager";
[ViewVariables]
[DataField("solutions")]
public readonly Dictionary<string, Solution> Solutions = new();
}
}

View File

@@ -36,12 +36,12 @@ namespace Content.Shared.Chemistry.Dispenser
/// <summary> /// <summary>
/// A list of the reagents and their amounts within the beaker/reagent container, if applicable. /// A list of the reagents and their amounts within the beaker/reagent container, if applicable.
/// </summary> /// </summary>
public readonly List<Solution.Solution.ReagentQuantity>? ContainerReagents; public readonly List<Components.Solution.ReagentQuantity>? ContainerReagents;
public readonly string DispenserName; public readonly string DispenserName;
public readonly ReagentUnit SelectedDispenseAmount; public readonly ReagentUnit SelectedDispenseAmount;
public ReagentDispenserBoundUserInterfaceState(bool hasPower, bool hasBeaker, ReagentUnit beakerCurrentVolume, ReagentUnit beakerMaxVolume, string containerName, public ReagentDispenserBoundUserInterfaceState(bool hasPower, bool hasBeaker, ReagentUnit beakerCurrentVolume, ReagentUnit beakerMaxVolume, string containerName,
List<ReagentDispenserInventoryEntry> inventory, string dispenserName, List<Solution.Solution.ReagentQuantity>? containerReagents, ReagentUnit selectedDispenseAmount) List<ReagentDispenserInventoryEntry> inventory, string dispenserName, List<Components.Solution.ReagentQuantity>? containerReagents, ReagentUnit selectedDispenseAmount)
{ {
HasPower = hasPower; HasPower = hasPower;
HasBeaker = hasBeaker; HasBeaker = hasBeaker;

View File

@@ -0,0 +1,132 @@
using System.Diagnostics.CodeAnalysis;
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.Components.SolutionManager;
using Content.Shared.Chemistry.Reagent;
using Robust.Shared.GameObjects;
namespace Content.Shared.Chemistry.EntitySystems
{
public partial class SolutionContainerSystem
{
public void Refill(EntityUid targetUid, Solution targetSolution, Solution addedSolution)
{
if (!ComponentManager.HasComponent<RefillableSolutionComponent>(targetUid))
return;
TryAddSolution(targetUid, targetSolution, addedSolution);
}
public void Inject(EntityUid targetUid, Solution targetSolution, Solution addedSolution)
{
if (!ComponentManager.HasComponent<InjectableSolutionComponent>(targetUid))
return;
TryAddSolution(targetUid, targetSolution, addedSolution);
}
public Solution Draw(EntityUid targetUid, Solution solution, ReagentUnit amount)
{
if (!ComponentManager.HasComponent<DrawableSolutionComponent>(targetUid))
{
return new Solution();
}
return SplitSolution(targetUid, solution, amount);
}
public Solution Drain(EntityUid targetUid, Solution targetSolution, ReagentUnit amount)
{
if (!ComponentManager.HasComponent<DrainableSolutionComponent>(targetUid))
{
return new Solution();
}
return SplitSolution(targetUid, targetSolution, amount);
}
public bool TryGetInjectableSolution(EntityUid targetUid,
[NotNullWhen(true)] out Solution? solution)
{
if (ComponentManager.TryGetComponent(targetUid, out InjectableSolutionComponent? injectable) &&
ComponentManager.TryGetComponent(targetUid, out SolutionContainerManagerComponent? manager) &&
manager.Solutions.TryGetValue(injectable.Solution, out solution))
{
return true;
}
solution = null;
return false;
}
public bool TryGetRefillableSolution(EntityUid targetUid,
[NotNullWhen(true)] out Solution? solution)
{
if (ComponentManager.TryGetComponent(targetUid, out RefillableSolutionComponent? refillable) &&
ComponentManager.TryGetComponent(targetUid, out SolutionContainerManagerComponent? manager) &&
manager.Solutions.TryGetValue(refillable.Solution, out var refillableSolution))
{
solution = refillableSolution;
return true;
}
solution = null;
return false;
}
public bool TryGetDrainableSolution(EntityUid targetUid,
[NotNullWhen(true)] out Solution? solution)
{
if (ComponentManager.TryGetComponent(targetUid,out DrainableSolutionComponent? drainable) &&
ComponentManager.TryGetComponent(targetUid,out SolutionContainerManagerComponent? manager) &&
manager.Solutions.TryGetValue(drainable.Solution, out solution))
{
return true;
}
solution = null;
return false;
}
public bool TryGetDrawableSolution(IEntity owner,
[NotNullWhen(true)] out Solution? solution)
{
if (owner.TryGetComponent(out DrawableSolutionComponent? drawable) &&
owner.TryGetComponent(out SolutionContainerManagerComponent? manager) &&
manager.Solutions.TryGetValue(drawable.Solution, out solution))
{
return true;
}
solution = null;
return false;
}
public ReagentUnit DrainAvailable(IEntity? owner)
{
if (owner == null || !TryGetDrainableSolution(owner.Uid, out var solution))
return ReagentUnit.Zero;
return solution.CurrentVolume;
}
public bool HasFitsInDispenser(IEntity owner)
{
return !owner.Deleted && owner.HasComponent<FitsInDispenserComponent>();
}
public bool TryGetFitsInDispenser(EntityUid owner,
[NotNullWhen(true)] out Solution? solution)
{
if (EntityManager.TryGetEntity(owner, out var ownerEntity) &&
ownerEntity.TryGetComponent(out FitsInDispenserComponent? dispenserFits) &&
ownerEntity.TryGetComponent(out SolutionContainerManagerComponent? manager) &&
manager.Solutions.TryGetValue(dispenserFits.Solution, out solution))
{
return true;
}
solution = null;
return false;
}
}
}

View File

@@ -0,0 +1,342 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.Components.SolutionManager;
using Content.Shared.Chemistry.Reaction;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.Examine;
using JetBrains.Annotations;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Log;
using Robust.Shared.Maths;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
using Robust.Shared.Utility;
namespace Content.Shared.Chemistry.EntitySystems
{
/// <summary>
/// This event alerts system that the solution was changed
/// </summary>
public class SolutionChangedEvent : EntityEventArgs
{
}
/// <summary>
/// Part of Chemistry system deal with SolutionContainers
/// </summary>
[UsedImplicitly]
public partial class SolutionContainerSystem : EntitySystem
{
[Dependency]
private readonly SharedChemicalReactionSystem _chemistrySystem = default!;
[Dependency]
private readonly IPrototypeManager _prototypeManager = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<SolutionContainerManagerComponent, ComponentInit>(InitSolution);
SubscribeLocalEvent<ExaminableSolutionComponent, ExaminedEvent>(OnExamineSolution);
}
private void InitSolution(EntityUid uid, SolutionContainerManagerComponent component, ComponentInit args)
{
foreach (var keyValue in component.Solutions)
{
var solutionHolder = keyValue.Value;
// solutionHolder.OwnerUid = component.Owner.Uid;
if (solutionHolder.MaxVolume == ReagentUnit.Zero && solutionHolder.TotalVolume > solutionHolder.MaxVolume)
{
solutionHolder.MaxVolume = solutionHolder.TotalVolume;
}
UpdateAppearance(uid, solutionHolder);
}
}
private void OnExamineSolution(EntityUid uid, ExaminableSolutionComponent examinableComponent,
ExaminedEvent args)
{
if (!args.Examined.TryGetComponent(out SolutionContainerManagerComponent? solutionsManager)
|| !solutionsManager.Solutions.TryGetValue(examinableComponent.Solution, out var solutionHolder))
return;
if (solutionHolder.Contents.Count == 0)
{
args.Message.AddText(Loc.GetString("shared-solution-container-component-on-examine-empty-container"));
return;
}
var primaryReagent = solutionHolder.GetPrimaryReagentId();
if (!_prototypeManager.TryIndex(primaryReagent, out ReagentPrototype? proto))
{
Logger.Error(
$"{nameof(Solution)} could not find the prototype associated with {primaryReagent}.");
return;
}
var colorHex = solutionHolder.Color
.ToHexNoAlpha(); //TODO: If the chem has a dark color, the examine text becomes black on a black background, which is unreadable.
var messageString = "shared-solution-container-component-on-examine-main-text";
args.Message.AddMarkup(Loc.GetString(messageString,
("color", colorHex),
("wordedAmount", Loc.GetString(solutionHolder.Contents.Count == 1
? "shared-solution-container-component-on-examine-worded-amount-one-reagent"
: "shared-solution-container-component-on-examine-worded-amount-multiple-reagents")),
("desc", Loc.GetString(proto.PhysicalDescription))));
}
private void UpdateAppearance(EntityUid uid, Solution solution)
{
if (!EntityManager.TryGetEntity(uid, out var solutionEntity)
|| solutionEntity.Deleted
|| !solutionEntity.TryGetComponent<SharedAppearanceComponent>(out var appearance))
return;
var filledVolumeFraction = solution.CurrentVolume.Float() / solution.MaxVolume.Float();
appearance.SetData(SolutionContainerVisuals.VisualState, new SolutionContainerVisualState(solution.Color, filledVolumeFraction));
solutionEntity.Dirty();
}
/// <summary>
/// Removes part of the solution in the container.
/// </summary>
/// <param name="targetUid"></param>
/// <param name="solutionHolder"></param>
/// <param name="quantity">the volume of solution to remove.</param>
/// <returns>The solution that was removed.</returns>
public Solution SplitSolution(EntityUid targetUid, Solution solutionHolder, ReagentUnit quantity)
{
var splitSol = solutionHolder.SplitSolution(quantity);
UpdateChemicals(targetUid, solutionHolder);
return splitSol;
}
private void UpdateChemicals(EntityUid uid, Solution solutionHolder, bool needsReactionsProcessing = false)
{
// Process reactions
if (needsReactionsProcessing && solutionHolder.CanReact)
{
_chemistrySystem
.FullyReactSolution(solutionHolder, EntityManager.GetEntity(uid), solutionHolder.MaxVolume);
}
UpdateAppearance(uid, solutionHolder);
RaiseLocalEvent(uid, new SolutionChangedEvent());
}
public void RemoveAllSolution(EntityUid uid, Solution solutionHolder)
{
if (solutionHolder.CurrentVolume == 0)
return;
solutionHolder.RemoveAllSolution();
UpdateChemicals(uid, solutionHolder);
}
public void RemoveAllSolution(EntityUid uid)
{
if (!ComponentManager.TryGetComponent(uid, out SolutionContainerManagerComponent? solutionContainerManager))
return;
foreach (var solution in solutionContainerManager.Solutions.Values)
{
RemoveAllSolution(uid, solution);
}
}
/// <summary>
/// Adds reagent of an Id to the container.
/// </summary>
/// <param name="targetUid"></param>
/// <param name="targetSolution">Container to which we are adding reagent</param>
/// <param name="reagentId">The Id of the reagent to add.</param>
/// <param name="quantity">The amount of reagent to add.</param>
/// <param name="acceptedQuantity">The amount of reagent successfully added.</param>
/// <returns>If all the reagent could be added.</returns>
public bool TryAddReagent(EntityUid targetUid, Solution targetSolution, string reagentId, ReagentUnit quantity,
out ReagentUnit acceptedQuantity)
{
acceptedQuantity = targetSolution.AvailableVolume > quantity ? quantity : targetSolution.AvailableVolume;
targetSolution.AddReagent(reagentId, acceptedQuantity);
if (acceptedQuantity > 0)
UpdateChemicals(targetUid, targetSolution, true);
return acceptedQuantity == quantity;
}
/// <summary>
/// Removes reagent of an Id to the container.
/// </summary>
/// <param name="targetUid"></param>
/// <param name="container">Solution container from which we are removing reagent</param>
/// <param name="reagentId">The Id of the reagent to remove.</param>
/// <param name="quantity">The amount of reagent to remove.</param>
/// <returns>If the reagent to remove was found in the container.</returns>
public bool TryRemoveReagent(EntityUid targetUid, Solution? container, string reagentId, ReagentUnit quantity)
{
if (container == null || !container.ContainsReagent(reagentId))
return false;
container.RemoveReagent(reagentId, quantity);
UpdateChemicals(targetUid, container);
return true;
}
/// <summary>
/// Adds a solution to the container, if it can fully fit.
/// </summary>
/// <param name="targetUid"></param>
/// <param name="targetSolution">The container to which we try to add.</param>
/// <param name="solution">The solution to try to add.</param>
/// <returns>If the solution could be added.</returns>
public bool TryAddSolution(EntityUid targetUid, Solution? targetSolution, Solution solution)
{
if (targetSolution == null || !targetSolution.CanAddSolution(solution) || solution.TotalVolume == 0)
return false;
targetSolution.AddSolution(solution);
UpdateChemicals(targetUid, targetSolution, true);
return true;
}
public bool TryGetSolution(IEntity? target, string name,
[NotNullWhen(true)] out Solution? solution)
{
if (target == null || target.Deleted)
{
solution = null;
return false;
}
return TryGetSolution(target.Uid, name, out solution);
}
public bool TryGetSolution(EntityUid uid, string name,
[NotNullWhen(true)] out Solution? solution)
{
if (!ComponentManager.TryGetComponent(uid, out SolutionContainerManagerComponent? solutionsMgr))
{
solution = null;
return false;
}
return solutionsMgr.Solutions.TryGetValue(name, out solution);
}
/// <summary>
/// Will ensure a solution is added to given entity even if it's missing solutionContainerManager
/// </summary>
/// <param name="owner">Entity to which to add solution</param>
/// <param name="name">name for the solution</param>
/// <returns>solution</returns>
public Solution EnsureSolution(IEntity owner, string name)
{
var solutionsMgr = owner.EnsureComponent<SolutionContainerManagerComponent>();
if (!solutionsMgr.Solutions.ContainsKey(name))
{
var newSolution = new Solution();
solutionsMgr.Solutions.Add(name, newSolution);
}
return solutionsMgr.Solutions[name];
}
public string[] RemoveEachReagent(Solution solution, ReagentUnit quantity)
{
var removedReagent = new string[solution.Contents.Count];
if (quantity <= 0)
return Array.Empty<string>();
var pos = 0;
for (var i = 0; i < solution.Contents.Count; i++)
{
var (reagentId, curQuantity) = solution.Contents[i];
removedReagent[pos++] = reagentId;
var newQuantity = curQuantity - quantity;
if (newQuantity <= 0)
{
solution.Contents.RemoveSwap(i);
solution.TotalVolume -= curQuantity;
}
else
{
solution.Contents[i] = new Solution.ReagentQuantity(reagentId, newQuantity);
solution.TotalVolume -= quantity;
}
}
return removedReagent;
}
public void TryRemoveAllReagents(Solution solution, List<Solution.ReagentQuantity> removeReagents)
{
foreach (var reagent in removeReagents)
{
solution.RemoveReagent(reagent.ReagentId, reagent.Quantity);
}
}
public ReagentUnit GetReagentQuantity(EntityUid ownerUid, string reagentId)
{
var reagentQuantity = ReagentUnit.New(0);
if (EntityManager.TryGetEntity(ownerUid, out var owner)
&& owner.TryGetComponent(out SolutionContainerManagerComponent? managerComponent))
{
foreach (var solution in managerComponent.Solutions.Values)
{
reagentQuantity += solution.GetReagentQuantity(reagentId);
}
}
return reagentQuantity;
}
}
[Serializable, NetSerializable]
public enum SolutionContainerVisuals : byte
{
VisualState
}
[Serializable, NetSerializable]
public class SolutionContainerVisualState
{
public readonly Color Color;
/// <summary>
/// Represents how full the container is, as a fraction equivalent to <see cref="FilledVolumeFraction"/>/<see cref="byte.MaxValue"/>.
/// </summary>
public readonly byte FilledVolumeFraction;
// do we really need this just to save three bytes?
public float FilledVolumePercent => (float) FilledVolumeFraction / byte.MaxValue;
/// <summary>
/// Sets the solution state of a container.
/// </summary>
/// <param name="color"></param>
/// <param name="filledVolumeFraction">The fraction of the container's volume that is filled.</param>
public SolutionContainerVisualState(Color color, float filledVolumeFraction)
{
Color = color;
FilledVolumeFraction = (byte) (byte.MaxValue * filledVolumeFraction);
}
}
public enum SolutionContainerLayers : byte
{
Fill,
Base
}
}

View File

@@ -1,3 +1,4 @@
using Content.Shared.Chemistry.Components;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
namespace Content.Shared.Chemistry.Reaction namespace Content.Shared.Chemistry.Reaction
@@ -7,6 +8,6 @@ namespace Content.Shared.Chemistry.Reaction
/// </summary> /// </summary>
public interface IReactionEffect public interface IReactionEffect
{ {
void React(IEntity solutionEntity, double intensity); void React(Solution solution, IEntity solutionEntity, double intensity);
} }
} }

View File

@@ -1,4 +1,5 @@
using System.Collections.Generic; using System.Collections.Generic;
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.Reagent; using Content.Shared.Chemistry.Reagent;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.IoC; using Robust.Shared.IoC;
@@ -28,7 +29,7 @@ namespace Content.Shared.Chemistry.Reaction
/// <param name="reaction">The reaction to check.</param> /// <param name="reaction">The reaction to check.</param>
/// <param name="lowestUnitReactions">How many times this reaction can occur.</param> /// <param name="lowestUnitReactions">How many times this reaction can occur.</param>
/// <returns></returns> /// <returns></returns>
private static bool CanReact(Solution.Solution solution, ReactionPrototype reaction, out ReagentUnit lowestUnitReactions) private static bool CanReact(Solution solution, ReactionPrototype reaction, out ReagentUnit lowestUnitReactions)
{ {
lowestUnitReactions = ReagentUnit.MaxValue; lowestUnitReactions = ReagentUnit.MaxValue;
@@ -54,7 +55,7 @@ namespace Content.Shared.Chemistry.Reaction
/// Perform a reaction on a solution. This assumes all reaction criteria are met. /// Perform a reaction on a solution. This assumes all reaction criteria are met.
/// Removes the reactants from the solution, then returns a solution with all products. /// Removes the reactants from the solution, then returns a solution with all products.
/// </summary> /// </summary>
private Solution.Solution PerformReaction(Solution.Solution solution, IEntity owner, ReactionPrototype reaction, ReagentUnit unitReactions) private Solution PerformReaction(Solution solution, IEntity owner, ReactionPrototype reaction, ReagentUnit unitReactions)
{ {
//Remove reactants //Remove reactants
foreach (var reactant in reaction.Reactants) foreach (var reactant in reaction.Reactants)
@@ -67,23 +68,23 @@ namespace Content.Shared.Chemistry.Reaction
} }
//Create products //Create products
var products = new Solution.Solution(); var products = new Solution();
foreach (var product in reaction.Products) foreach (var product in reaction.Products)
{ {
products.AddReagent(product.Key, product.Value * unitReactions); products.AddReagent(product.Key, product.Value * unitReactions);
} }
// Trigger reaction effects // Trigger reaction effects
OnReaction(reaction, owner, unitReactions); OnReaction(solution, reaction, owner, unitReactions);
return products; return products;
} }
protected virtual void OnReaction(ReactionPrototype reaction, IEntity owner, ReagentUnit unitReactions) protected virtual void OnReaction(Solution solution, ReactionPrototype reaction, IEntity owner, ReagentUnit unitReactions)
{ {
foreach (var effect in reaction.Effects) foreach (var effect in reaction.Effects)
{ {
effect.React(owner, unitReactions.Double()); effect.React(solution, owner, unitReactions.Double());
} }
} }
@@ -92,10 +93,10 @@ namespace Content.Shared.Chemistry.Reaction
/// Removes the reactants from the solution, then returns a solution with all products. /// Removes the reactants from the solution, then returns a solution with all products.
/// WARNING: Does not trigger reactions between solution and new products. /// WARNING: Does not trigger reactions between solution and new products.
/// </summary> /// </summary>
private Solution.Solution ProcessReactions(Solution.Solution solution, IEntity owner) private Solution ProcessReactions(Solution solution, IEntity owner)
{ {
//TODO: make a hashmap at startup and then look up reagents in the contents for a reaction //TODO: make a hashmap at startup and then look up reagents in the contents for a reaction
var overallProducts = new Solution.Solution(); var overallProducts = new Solution();
foreach (var reaction in _reactions) foreach (var reaction in _reactions)
{ {
if (CanReact(solution, reaction, out var unitReactions)) if (CanReact(solution, reaction, out var unitReactions))
@@ -111,7 +112,7 @@ namespace Content.Shared.Chemistry.Reaction
/// <summary> /// <summary>
/// Continually react a solution until no more reactions occur. /// Continually react a solution until no more reactions occur.
/// </summary> /// </summary>
public void FullyReactSolution(Solution.Solution solution, IEntity owner) public void FullyReactSolution(Solution solution, IEntity owner)
{ {
for (var i = 0; i < MaxReactionIterations; i++) for (var i = 0; i < MaxReactionIterations; i++)
{ {
@@ -122,14 +123,14 @@ namespace Content.Shared.Chemistry.Reaction
solution.AddSolution(products); solution.AddSolution(products);
} }
Logger.Error($"{nameof(Solution.Solution)} on {owner} (Uid: {owner.Uid}) could not finish reacting in under {MaxReactionIterations} loops."); Logger.Error($"{nameof(Solution)} on {owner} (Uid: {owner.Uid}) could not finish reacting in under {MaxReactionIterations} loops.");
} }
/// <summary> /// <summary>
/// Continually react a solution until no more reactions occur, with a volume constraint. /// Continually react a solution until no more reactions occur, with a volume constraint.
/// If a reaction's products would exceed the max volume, some product is deleted. /// If a reaction's products would exceed the max volume, some product is deleted.
/// </summary> /// </summary>
public void FullyReactSolution(Solution.Solution solution, IEntity owner, ReagentUnit maxVolume) public void FullyReactSolution(Solution solution, IEntity owner, ReagentUnit maxVolume)
{ {
for (var i = 0; i < MaxReactionIterations; i++) for (var i = 0; i < MaxReactionIterations; i++)
{ {
@@ -148,7 +149,7 @@ namespace Content.Shared.Chemistry.Reaction
solution.AddSolution(products); solution.AddSolution(products);
} }
Logger.Error($"{nameof(Solution.Solution)} on {owner} (Uid: {owner.Uid}) could not finish reacting in under {MaxReactionIterations} loops."); Logger.Error($"{nameof(Solution)} on {owner} (Uid: {owner.Uid}) could not finish reacting in under {MaxReactionIterations} loops.");
} }
} }
} }

View File

@@ -18,6 +18,6 @@ namespace Content.Shared.Chemistry.Reagent
[DataField("conditions")] [DataField("conditions")]
public ReagentEffectCondition[]? Conditions; public ReagentEffectCondition[]? Conditions;
public abstract void Metabolize(IEntity solutionEntity, Solution.Solution.ReagentQuantity amount); public abstract void Metabolize(IEntity solutionEntity, Components.Solution.ReagentQuantity amount);
} }
} }

View File

@@ -1,5 +1,4 @@
using Content.Shared.Body.Components; using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.Solution;
using Robust.Shared.GameObjects; using Robust.Shared.GameObjects;
using Robust.Shared.Serialization.Manager.Attributes; using Robust.Shared.Serialization.Manager.Attributes;
@@ -8,6 +7,6 @@ namespace Content.Shared.Chemistry.Reagent
[ImplicitDataDefinitionForInheritors] [ImplicitDataDefinitionForInheritors]
public abstract class ReagentEffectCondition public abstract class ReagentEffectCondition
{ {
public abstract bool Condition(IEntity solutionEntity, Solution.Solution.ReagentQuantity reagent); public abstract bool Condition(IEntity solutionEntity, Solution.ReagentQuantity reagent);
} }
} }

View File

@@ -27,7 +27,7 @@ namespace Content.Shared.Chemistry.Reagent
[DataField("ingestion")] [DataField("ingestion")]
public bool Ingestion { get; } = false; public bool Ingestion { get; } = false;
public void React(ReactionMethod method, IEntity entity, ReagentPrototype reagent, ReagentUnit volume, Solution.Solution? source) public void React(ReactionMethod method, IEntity entity, ReagentPrototype reagent, ReagentUnit volume, Components.Solution? source)
{ {
switch (method) switch (method)
{ {
@@ -50,6 +50,6 @@ namespace Content.Shared.Chemistry.Reagent
React(entity, reagent, volume, source); React(entity, reagent, volume, source);
} }
protected abstract void React(IEntity entity, ReagentPrototype reagent, ReagentUnit volume, Solution.Solution? source); protected abstract void React(IEntity entity, ReagentPrototype reagent, ReagentUnit volume, Components.Solution? source);
} }
} }

View File

@@ -1,89 +0,0 @@
using Content.Shared.Chemistry.Reagent;
using Robust.Shared.GameObjects;
namespace Content.Shared.Chemistry.Solution.Components
{
/// <summary>
/// High-level solution transferring operations like "what happens when a syringe tries to inject this entity."
/// </summary>
/// <remarks>
/// This interface is most often implemented by using <see cref="SharedSolutionContainerComponent"/>
/// and setting the appropriate <see cref="SolutionContainerCaps"/>
/// </remarks>
public interface ISolutionInteractionsComponent : IComponent
{
//
// INJECTING
//
/// <summary>
/// Whether we CAN POTENTIALLY be injected with solutions by items like syringes.
/// </summary>
/// <remarks>
/// <para>
/// This should NOT change to communicate behavior like "the container is full".
/// Change <see cref="InjectSpaceAvailable"/> to 0 for that.
/// </para>
/// <para>
/// If refilling is allowed (<see cref="CanRefill"/>) you should also always allow injecting.
/// </para>
/// </remarks>
bool CanInject => false;
/// <summary>
/// The amount of solution space available for injecting.
/// </summary>
ReagentUnit InjectSpaceAvailable => ReagentUnit.Zero;
/// <summary>
/// Actually inject reagents.
/// </summary>
void Inject(Solution solution)
{
}
//
// DRAWING
//
bool CanDraw => false;
ReagentUnit DrawAvailable => ReagentUnit.Zero;
Solution Draw(ReagentUnit amount)
{
return new();
}
//
// REFILLING
//
bool CanRefill => false;
ReagentUnit RefillSpaceAvailable => ReagentUnit.Zero;
/// <summary>
/// The amount that will transfer if something is spilled on the container.
/// </summary>
ReagentUnit MaxSpillRefill => ReagentUnit.Zero;
void Refill(Solution solution)
{
}
//
// DRAINING
//
bool CanDrain => false;
ReagentUnit DrainAvailable => ReagentUnit.Zero;
Solution Drain(ReagentUnit amount)
{
return new();
}
}
}

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