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

@@ -6,10 +6,10 @@ using Content.Server.Items;
using Content.Server.Power.Components;
using Content.Server.UserInterface;
using Content.Shared.ActionBlocker;
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.Solution;
using Content.Shared.Interaction;
using Content.Shared.Notification.Managers;
using Content.Shared.Random.Helpers;
@@ -35,19 +35,31 @@ namespace Content.Server.Chemistry.Components
[RegisterComponent]
[ComponentReference(typeof(IActivate))]
[ComponentReference(typeof(IInteractUsing))]
public class ChemMasterComponent : SharedChemMasterComponent, IActivate, IInteractUsing, ISolutionChange
public class ChemMasterComponent : SharedChemMasterComponent, IActivate, IInteractUsing
{
[ViewVariables] private ContainerSlot _beakerContainer = default!;
[ViewVariables] private bool HasBeaker => _beakerContainer.ContainedEntity != null;
[ViewVariables] private bool _bufferModeTransfer = true;
[ViewVariables]
private ContainerSlot _beakerContainer = default!;
[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>
/// Called once per instance of this component. Gets references to any other components needed
@@ -65,8 +77,7 @@ namespace Content.Server.Chemistry.Components
_beakerContainer =
ContainerHelpers.EnsureContainer<ContainerSlot>(Owner, $"{Name}-reagentContainerContainer");
//BufferSolution = Owner.BufferSolution
BufferSolution.RemoveAllSolution();
_bufferSolution = EntitySystem.Get<SolutionContainerSystem>().EnsureSolution(Owner, SolutionName);
UpdateUserInterface();
}
@@ -76,13 +87,13 @@ namespace Content.Server.Chemistry.Components
base.HandleMessage(message, component);
switch (message)
{
case PowerChangedMessage powerChanged:
OnPowerChanged(powerChanged);
case PowerChangedMessage:
OnPowerChanged();
break;
}
}
private void OnPowerChanged(PowerChangedMessage e)
private void OnPowerChanged()
{
UpdateUserInterface();
}
@@ -140,6 +151,7 @@ namespace Content.Server.Chemistry.Components
/// Checks whether the player entity is able to use the chem master.
/// </summary>
/// <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>
private bool PlayerCanUseChemMaster(IEntity? playerEntity, bool needsPower = true)
{
@@ -166,25 +178,30 @@ namespace Content.Server.Chemistry.Components
private ChemMasterBoundUserInterfaceState GetUserInterfaceState()
{
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),
"", 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,
beaker.Name, Owner.Name, solution.ReagentList, BufferSolution.Contents, _bufferModeTransfer, BufferSolution.TotalVolume);
return new ChemMasterBoundUserInterfaceState(Powered, true, beakerSolution.CurrentVolume,
beakerSolution.MaxVolume,
beaker.Name, Owner.Name, beakerSolution.Contents, BufferSolution.Contents, _bufferModeTransfer,
BufferSolution.TotalVolume);
}
private void UpdateUserInterface()
public void UpdateUserInterface()
{
var state = GetUserInterfaceState();
UserInterface?.SetState(state);
}
/// <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.
/// </summary>
private void TryEject(IEntity user)
@@ -194,13 +211,14 @@ namespace Content.Server.Chemistry.Components
var beaker = _beakerContainer.ContainedEntity;
if(beaker is null)
if (beaker is null)
return;
_beakerContainer.Remove(beaker);
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;
if (hands.CanPutInHand(item))
hands.PutInHand(item);
@@ -211,10 +229,12 @@ namespace Content.Server.Chemistry.Components
if (!HasBeaker && _bufferModeTransfer) return;
var beaker = _beakerContainer.ContainedEntity;
if(beaker is null)
if (beaker is null)
return;
if (!EntitySystem.Get<SolutionContainerSystem>().TryGetSolution(beaker, SolutionName, out var beakerSolution))
return;
var beakerSolution = beaker.GetComponent<SolutionContainerComponent>();
if (isBuffer)
{
foreach (var reagent in BufferSolution.Contents)
@@ -222,30 +242,33 @@ namespace Content.Server.Chemistry.Components
if (reagent.ReagentId == id)
{
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
{
actualAmount = ReagentUnit.Min(reagent.Quantity, amount, beakerSolution.EmptyVolume);
actualAmount = ReagentUnit.Min(reagent.Quantity, amount, beakerSolution.AvailableVolume);
}
BufferSolution.RemoveReagent(id, actualAmount);
if (_bufferModeTransfer)
{
beakerSolution.TryAddReagent(id, actualAmount, out var _);
EntitySystem.Get<SolutionContainerSystem>()
.TryAddReagent(beaker.Uid, beakerSolution, id, actualAmount, out var _);
// beakerSolution.Solution.AddReagent(id, actualAmount);
}
break;
}
}
}
else
{
foreach (var reagent in beakerSolution.Solution.Contents)
foreach (var reagent in beakerSolution.Contents)
{
if (reagent.ReagentId == id)
{
@@ -258,7 +281,8 @@ namespace Content.Server.Chemistry.Components
{
actualAmount = ReagentUnit.Min(reagent.Quantity, amount);
}
beakerSolution.TryRemoveReagent(id, actualAmount);
EntitySystem.Get<SolutionContainerSystem>().TryRemoveReagent(beaker.Uid, beakerSolution, id, actualAmount);
BufferSolution.AddReagent(id, actualAmount);
break;
}
@@ -285,9 +309,9 @@ namespace Content.Server.Chemistry.Components
var bottle = Owner.EntityManager.SpawnEntity("ChemistryEmptyBottle01", Owner.Transform.Coordinates);
var bufferSolution = BufferSolution.SplitSolution(actualVolume);
var bottleSolution = EntitySystem.Get<SolutionContainerSystem>().EnsureSolution(bottle, "bottle");
bottle.TryGetComponent<SolutionContainerComponent>(out var bottleSolution);
bottleSolution?.TryAddSolution(bufferSolution);
EntitySystem.Get<SolutionContainerSystem>().TryAddSolution(bottle.Uid, bottleSolution, bufferSolution);
//Try to give them the bottle
if (user.TryGetComponent<HandsComponent>(out var hands) &&
@@ -305,7 +329,6 @@ namespace Content.Server.Chemistry.Components
//Give it an offset
bottle.RandomOffset(0.2f);
}
}
else //Pills
{
@@ -320,8 +343,8 @@ namespace Content.Server.Chemistry.Components
var bufferSolution = BufferSolution.SplitSolution(actualVolume);
pill.TryGetComponent<SolutionContainerComponent>(out var pillSolution);
pillSolution?.TryAddSolution(bufferSolution);
var pillSolution = EntitySystem.Get<SolutionContainerSystem>().EnsureSolution(pill, "pill");
EntitySystem.Get<SolutionContainerSystem>().TryAddSolution(pill.Uid, pillSolution, bufferSolution);
//Try to give them the bottle
if (user.TryGetComponent<HandsComponent>(out var hands) &&
@@ -332,7 +355,6 @@ namespace Content.Server.Chemistry.Components
hands.PutInHand(item);
continue;
}
}
//Put it on the floor
@@ -371,7 +393,7 @@ namespace Content.Server.Chemistry.Components
/// <summary>
/// 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.
/// </summary>
/// <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;
if (activeHandEntity.TryGetComponent<SolutionContainerComponent>(out var solution))
if (activeHandEntity.HasComponent<SolutionContainerManagerComponent>())
{
if (HasBeaker)
{
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.
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
{
@@ -410,14 +434,13 @@ namespace Content.Server.Chemistry.Components
}
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;
}
void ISolutionChange.SolutionChanged(SolutionChangeEventArgs eventArgs) => UpdateUserInterface();
private void ClickSound()
{
SoundSystem.Play(Filter.Pvs(Owner), _clickSound.GetSound(), Owner, AudioParams.Default.WithVolume(-2f));
@@ -446,6 +469,5 @@ namespace Content.Server.Chemistry.Components
component.TryEject(user);
}
}
}
}

View File

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

View File

@@ -1,8 +1,8 @@
using Content.Server.Interaction.Components;
using Content.Server.MobState.States;
using Content.Server.Weapon.Melee;
using Content.Shared.Chemistry;
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.Notification.Managers;
using Content.Shared.Sound;
@@ -18,7 +18,7 @@ using Robust.Shared.ViewVariables;
namespace Content.Server.Chemistry.Components
{
[RegisterComponent]
public sealed class HyposprayComponent : SharedHyposprayComponent, ISolutionChange
public sealed class HyposprayComponent : SharedHyposprayComponent
{
[DataField("ClumsyFailChance")]
[ViewVariables(VVAccess.ReadWrite)]
@@ -31,8 +31,6 @@ namespace Content.Server.Chemistry.Components
[DataField("InjectSound")]
private SoundSpecifier _injectSound = new SoundPathSpecifier("/Audio/Items/hypospray.ogg");
[ComponentDependency] private readonly SolutionContainerComponent? _solution = default!;
protected override void Initialize()
{
base.Initialize();
@@ -57,13 +55,24 @@ namespace Content.Server.Chemistry.Components
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"));
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)
{
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);
var targetSolution = target.GetComponent<SolutionContainerComponent>();
// 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)
{
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;
}
// 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))
{
@@ -95,29 +106,26 @@ namespace Content.Server.Chemistry.Components
removedSolution.DoEntityReaction(target, ReactionMethod.Injection);
targetSolution.TryAddSolution(removedSolution);
EntitySystem.Get<SolutionContainerSystem>().TryAddSolution(target.Uid, targetSolution, removedSolution);
static bool EligibleEntity(IEntity entity)
{
// 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.
return entity.HasComponent<SolutionContainerComponent>() && entity.HasComponent<MobStateComponent>();
return entity.HasComponent<SharedChemMasterComponent>()
&& entity.HasComponent<MobStateComponent>();
}
return true;
}
void ISolutionChange.SolutionChanged(SolutionChangeEventArgs eventArgs)
{
Dirty();
}
public override ComponentState GetComponentState(ICommonSession player)
{
if (_solution == null)
return new HyposprayComponentState(ReagentUnit.Zero, ReagentUnit.Zero);
return new HyposprayComponentState(_solution.CurrentVolume, _solution.MaxVolume);
var solutionSys = Owner.EntityManager.EntitySysManager.GetEntitySystem<SolutionContainerSystem>();
return solutionSys.TryGetSolution(Owner, SolutionName, out var solution)
? new HyposprayComponentState(solution.CurrentVolume, solution.MaxVolume)
: new HyposprayComponentState(ReagentUnit.Zero, ReagentUnit.Zero);
}
}
}

View File

@@ -1,10 +1,11 @@
using System;
using System.Threading.Tasks;
using Content.Server.Body.Circulatory;
using Content.Shared.Chemistry;
using Content.Shared.Body.Networks;
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.Solution.Components;
using Content.Shared.Interaction;
using Content.Shared.Interaction.Helpers;
using Content.Shared.Notification.Managers;
@@ -22,8 +23,10 @@ namespace Content.Server.Chemistry.Components
/// containers, and can directly inject into a mobs bloodstream.
/// </summary>
[RegisterComponent]
public class InjectorComponent : SharedInjectorComponent, IAfterInteract, IUse, ISolutionChange
public class InjectorComponent : SharedInjectorComponent, IAfterInteract, IUse
{
public const string SolutionName = "injector";
/// <summary>
/// Whether or not the injector is able to draw from containers or if it's a single use
/// device that can only inject.
@@ -109,47 +112,46 @@ namespace Content.Server.Chemistry.Components
if (!eventArgs.InRangeUnobstructed(ignoreInsideBlocker: true, popup: true))
return false;
var solutionsSys = EntitySystem.Get<SolutionContainerSystem>();
//Make sure we have the attacking entity
if (eventArgs.Target == null || !Owner.HasComponent<SolutionContainerComponent>())
if (eventArgs.Target == null || !Owner.HasComponent<SolutionContainerManagerComponent>())
{
return false;
}
var targetEntity = eventArgs.Target;
// 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(targetSolution, eventArgs.User);
}
else
{
eventArgs.User.PopupMessage(eventArgs.User,
Loc.GetString("injector-component-cannot-transfer-message", ("owner", targetSolution.Owner)));
}
TryInject(targetEntity, injectableSolution, eventArgs.User);
}
else if (ToggleState == InjectorToggleMode.Draw)
else if (targetEntity.TryGetComponent(out BloodstreamComponent? bloodstream))
{
if (targetSolution.CanDraw)
{
TryDraw(targetSolution, eventArgs.User);
}
else
{
eventArgs.User.PopupMessage(eventArgs.User,
Loc.GetString("injector-component-cannot-draw-message", ("owner", targetSolution.Owner)));
}
TryInjectIntoBloodstream(bloodstream, eventArgs.User);
}
else
{
eventArgs.User.PopupMessage(eventArgs.User,
Loc.GetString("injector-component-cannot-transfer-message",
("owner", eventArgs.User)));
}
}
// Handle injecting into bloodstream
else if (targetEntity.TryGetComponent(out BloodstreamComponent? bloodstream) &&
ToggleState == InjectorToggleMode.Inject)
else if (ToggleState == InjectorToggleMode.Draw)
{
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;
@@ -168,10 +170,10 @@ namespace Content.Server.Chemistry.Components
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;
}
// Get transfer amount. May be smaller than _transferAmount if not enough room
var realTransferAmount = ReagentUnit.Min(_transferAmount, targetBloodstream.EmptyVolume);
@@ -179,61 +181,65 @@ namespace Content.Server.Chemistry.Components
if (realTransferAmount <= 0)
{
Owner.PopupMessage(user,
Loc.GetString("injector-component-cannot-inject-message",("owner", targetBloodstream.Owner)));
Loc.GetString("injector-component-cannot-inject-message", ("owner", targetBloodstream.Owner)));
return;
}
// 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;
}
// TODO: Account for partial transfer.
var bloodsStreamEntity = Owner.EntityManager.GetEntity(user.Uid);
removedSolution.DoEntityReaction(bloodsStreamEntity, ReactionMethod.Injection);
removedSolution.DoEntityReaction(solution.Owner, ReactionMethod.Injection);
solution.TryAddSolution(removedSolution);
EntitySystem.Get<SolutionContainerSystem>().TryAddSolution(user.Uid, bloodstream, removedSolution);
removedSolution.DoEntityReaction(targetBloodstream.Owner, ReactionMethod.Injection);
Owner.PopupMessage(user,
Loc.GetString("injector-component-inject-success-message",
("amount", removedSolution.TotalVolume),
("target", targetBloodstream.Owner)));
("amount", removedSolution.TotalVolume),
("target", targetBloodstream.Owner)));
Dirty();
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;
}
// 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)
{
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;
}
// 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,
Loc.GetString("injector-component-transfer-success-message",
("amount", removedSolution.TotalVolume),
("target", targetSolution.Owner)));
("amount", removedSolution.TotalVolume),
("target", targetEntity)));
Dirty();
AfterInject();
}
@@ -241,15 +247,27 @@ namespace Content.Server.Chemistry.Components
private void AfterInject()
{
// 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;
}
}
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;
}
@@ -259,43 +277,33 @@ namespace Content.Server.Chemistry.Components
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;
}
// 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;
}
Owner.PopupMessage(user,
Loc.GetString("injector-component-draw-success-message",
("amount", removedSolution.TotalVolume),
("target", targetSolution.Owner)));
("amount", removedSolution.TotalVolume),
("target", targetEntity)));
Dirty();
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)
{
Owner.TryGetComponent(out SolutionContainerComponent? solution);
Owner.EntityManager.EntitySysManager.GetEntitySystem<SolutionContainerSystem>()
.TryGetSolution(Owner, SolutionName, out var solution);
var currentVolume = solution?.CurrentVolume ?? 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.UserInterface;
using Content.Shared.ActionBlocker;
using Content.Shared.Chemistry;
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.Dispenser;
using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.Chemistry.Solution;
using Content.Shared.Interaction;
using Content.Shared.Notification.Managers;
using Content.Shared.Sound;
@@ -38,9 +38,10 @@ namespace Content.Server.Chemistry.Components
[RegisterComponent]
[ComponentReference(typeof(IActivate))]
[ComponentReference(typeof(IInteractUsing))]
public class ReagentDispenserComponent : SharedReagentDispenserComponent, IActivate, IInteractUsing, ISolutionChange
public class ReagentDispenserComponent : SharedReagentDispenserComponent, IActivate, IInteractUsing
{
private static ReagentInventoryComparer _comparer = new();
public static string SolutionName = "reagent";
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
@@ -52,9 +53,20 @@ namespace Content.Server.Chemistry.Components
[ViewVariables] private bool HasBeaker => _beakerContainer.ContainedEntity != null;
[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);
@@ -134,7 +146,7 @@ namespace Content.Server.Chemistry.Components
_ => true,
};
if(!PlayerCanUseDispenser(obj.Session.AttachedEntity, needsPower))
if (!PlayerCanUseDispenser(obj.Session.AttachedEntity, needsPower))
return;
switch (msg.Button)
@@ -216,25 +228,27 @@ namespace Content.Server.Chemistry.Components
private ReagentDispenserBoundUserInterfaceState GetUserInterfaceState()
{
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);
}
var solution = beaker.GetComponent<SolutionContainerComponent>();
return new ReagentDispenserBoundUserInterfaceState(Powered, true, solution.CurrentVolume, solution.MaxVolume,
beaker.Name, Inventory, Owner.Name, solution.ReagentList.ToList(), _dispenseAmount);
return new ReagentDispenserBoundUserInterfaceState(Powered, true, solution.CurrentVolume,
solution.MaxVolume,
beaker.Name, Inventory, Owner.Name, solution.Contents.ToList(), _dispenseAmount);
}
private void UpdateUserInterface()
public void UpdateUserInterface()
{
var state = GetUserInterfaceState();
UserInterface?.SetState(state);
}
/// <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.
/// </summary>
private void TryEject(IEntity user)
@@ -243,46 +257,48 @@ namespace Content.Server.Chemistry.Components
return;
var beaker = _beakerContainer.ContainedEntity;
if(beaker is null)
if (beaker is null)
return;
_beakerContainer.Remove(beaker);
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;
if (hands.CanPutInHand(item))
hands.PutInHand(item);
}
/// <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>
private void TryClear()
{
if (!HasBeaker) return;
var solution = _beakerContainer.ContainedEntity?.GetComponent<SolutionContainerComponent>();
if(solution is null)
if (!HasBeaker ||
!EntitySystem.Get<SolutionContainerSystem>()
.TryGetSolution(_beakerContainer.ContainedEntity, "beaker", out var solution))
return;
solution.RemoveAllSolution();
EntitySystem.Get<SolutionContainerSystem>().RemoveAllSolution(_beakerContainer.ContainedEntity!.Uid, solution);
UpdateUserInterface();
}
/// <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>
/// <param name="dispenseIndex">The index of the reagent in <c>Inventory</c>.</param>
private void TryDispense(int dispenseIndex)
{
if (!HasBeaker) return;
var solution = _beakerContainer.ContainedEntity?.GetComponent<SolutionContainerComponent>();
if (solution is null)
return;
if (_beakerContainer.ContainedEntity == null
|| !EntitySystem.Get<SolutionContainerSystem>()
.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();
}
@@ -313,7 +329,7 @@ namespace Content.Server.Chemistry.Components
/// <summary>
/// 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.
/// </summary>
/// <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)
{
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;
}
var solutionSys = EntitySystem.Get<SolutionContainerSystem>();
var activeHandEntity = hands.GetActiveHand.Owner;
if (activeHandEntity.TryGetComponent<SolutionContainerComponent>(out var solution))
if (solutionSys.TryGetSolution(activeHandEntity, "beaker", out _))
{
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.
Owner.PopupMessage(args.User, Loc.GetString("reagent-dispenser-component-cannot-fit-message"));
@@ -352,14 +371,14 @@ namespace Content.Server.Chemistry.Components
}
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;
}
void ISolutionChange.SolutionChanged(SolutionChangeEventArgs eventArgs) => UpdateUserInterface();
private void ClickSound()
{
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.Localization;
using Robust.Shared.Serialization.Manager.Attributes;
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.
/// </summary>
[RegisterComponent]
[ComponentReference(typeof(ISolutionChange))]
public class RehydratableComponent : Component, ISolutionChange
public class RehydratableComponent : Component
{
public override string Name => "Rehydratable";
[ViewVariables]
[DataField("catalyst")]
private string _catalystPrototype = "Water";
internal string CatalystPrototype = "Water";
[ViewVariables]
[DataField("target")]
private string? _targetPrototype = default!;
internal string? TargetPrototype = default!;
private 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();
}
internal bool Expanding;
}
}

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.Shared.Chemistry;
using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.Smoking;
using Robust.Server.GameObjects;
@@ -14,19 +14,20 @@ namespace Content.Server.Chemistry.Components
public class SmokeSolutionAreaEffectComponent : SolutionAreaEffectComponent
{
public override string Name => "SmokeSolutionAreaEffect";
public const string SolutionName = "smoke";
protected override void UpdateVisuals()
{
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)
{
if (SolutionContainerComponent == null)
if (!EntitySystem.Get<SolutionContainerSystem>().TryGetSolution(Owner, SolutionName, out var solution))
return;
if (!entity.TryGetComponent(out BloodstreamComponent? bloodstream))
@@ -37,7 +38,7 @@ namespace Content.Server.Chemistry.Components
return;
var chemistry = EntitySystem.Get<ChemistrySystem>();
var cloneSolution = SolutionContainerComponent.Solution.Clone();
var cloneSolution = solution.Clone();
var transferAmount = ReagentUnit.Min(cloneSolution.TotalVolume * solutionFraction, bloodstream.EmptyVolume);
var transferSolution = cloneSolution.SplitSolution(transferAmount);

View File

@@ -3,8 +3,9 @@ using System.Linq;
using Content.Server.Atmos.Components;
using Content.Server.Coordinates.Helpers;
using Content.Shared.Chemistry;
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.Chemistry.Solution;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Log;
@@ -20,10 +21,10 @@ namespace Content.Server.Chemistry.Components
/// </summary>
public abstract class SolutionAreaEffectComponent : Component
{
public const string SolutionName = "solutionArea";
[Dependency] protected readonly IMapManager MapManager = default!;
[Dependency] protected readonly IPrototypeManager PrototypeManager = default!;
[ComponentDependency] protected readonly SolutionContainerComponent? SolutionContainerComponent = default!;
public int Amount { get; set; }
public SolutionAreaEffectInceptionComponent? Inception { get; set; }
@@ -67,10 +68,12 @@ namespace Content.Server.Chemistry.Components
var coords = Owner.Transform.Coordinates;
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;
if (Owner.EntityManager.ComponentManager.TryGetComponent(neighbor, out AirtightComponent? airtight) && airtight.AirBlocked)
if (Owner.EntityManager.ComponentManager.TryGetComponent(neighbor,
out AirtightComponent? airtight) && airtight.AirBlocked)
return;
}
@@ -82,9 +85,9 @@ namespace Content.Server.Chemistry.Components
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;
@@ -95,7 +98,6 @@ namespace Content.Server.Chemistry.Components
SpreadToDir(Direction.East);
SpreadToDir(Direction.South);
SpreadToDir(Direction.West);
}
/// <summary>
@@ -120,7 +122,7 @@ namespace Content.Server.Chemistry.Components
/// with the other area effects from the inception.</param>
public void React(float averageExposures)
{
if (SolutionContainerComponent == null)
if (!EntitySystem.Get<SolutionContainerSystem>().TryGetSolution(Owner, SolutionName, out var solution))
return;
var chemistry = EntitySystem.Get<ChemistrySystem>();
@@ -129,7 +131,7 @@ namespace Content.Server.Chemistry.Components
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;
var reagent = PrototypeManager.Index<ReagentPrototype>(reagentQuantity.ReagentId);
@@ -140,7 +142,8 @@ namespace Content.Server.Chemistry.Components
// Touch every entity on the tile
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)
return;
if (SolutionContainerComponent == null)
if (!EntitySystem.Get<SolutionContainerSystem>().TryGetSolution(Owner, SolutionName, out var solutionArea))
return;
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();
}

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.Shared.ActionBlocker;
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.Solution.Components;
using Content.Shared.Interaction;
using Content.Shared.Interaction.Helpers;
using Content.Shared.Notification.Managers;
using Content.Shared.Verbs;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Robust.Server.GameObjects;
using Robust.Shared.GameObjects;
using Robust.Shared.Localization;
@@ -108,7 +111,8 @@ namespace Content.Server.Chemistry.Components
var amount = Math.Clamp(sval, MinimumTransferAmount.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));
break;
}
@@ -116,50 +120,57 @@ namespace Content.Server.Chemistry.Components
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;
}
async Task<bool> IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs)
{
var solutionsSys = EntitySystem.Get<SolutionContainerSystem>();
if (!eventArgs.InRangeUnobstructed() || eventArgs.Target == null)
return false;
if (!Owner.TryGetComponent(out ISolutionInteractionsComponent? ownerSolution))
if (!Owner.HasComponent<SolutionContainerManagerComponent>())
return false;
var target = eventArgs.Target;
if (!target.TryGetComponent(out ISolutionInteractionsComponent? targetSolution))
var target = eventArgs.Target!;
if (!target.HasComponent<SolutionContainerManagerComponent>())
{
return false;
}
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)
{
var toTheBrim = ownerSolution.RefillSpaceAvailable == 0;
var toTheBrim = ownerRefill.AvailableVolume == 0;
var msg = toTheBrim
? "comp-solution-transfer-fill-fully"
: "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;
}
}
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)
{
Owner.PopupMessage(eventArgs.User,
Loc.GetString("comp-solution-transfer-transfer-solution",
("amount",transferred),
("target",target)));
Loc.GetString("comp-solution-transfer-transfer-solution",
("amount", transferred),
("target", target)));
return true;
}
@@ -169,29 +180,33 @@ namespace Content.Server.Chemistry.Components
}
/// <returns>The actual amount transferred.</returns>
private static ReagentUnit DoTransfer(
ISolutionInteractionsComponent source,
ISolutionInteractionsComponent target,
ReagentUnit amount,
IEntity user)
private static ReagentUnit DoTransfer(IEntity user,
IEntity sourceEntity,
Solution source,
IEntity targetEntity,
Solution target,
ReagentUnit amount)
{
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;
}
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;
}
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);
target.Refill(solution);
var solution = EntitySystem.Get<SolutionContainerSystem>().Drain(sourceEntity.Uid, source, actualAmount);
EntitySystem.Get<SolutionContainerSystem>().Refill(targetEntity.Uid, target, solution);
return actualAmount;
}
@@ -251,7 +266,8 @@ namespace Content.Server.Chemistry.Components
protected override void Activate(IEntity user, SolutionTransferComponent component)
{
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)
{
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;
}
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.Solution;
using Robust.Server.GameObjects;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
namespace Content.Server.Chemistry.Components
{
[RegisterComponent]
public class TransformableContainerComponent : Component, ISolutionChange
public class TransformableContainerComponent : Component
{
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
public override string Name => "TransformableContainer";
private SpriteSpecifier? _initialSprite;
private string _initialName = default!;
private string _initialDescription = default!;
private ReagentPrototype? _currentReagent;
public SpriteSpecifier? InitialSprite;
public string InitialName = default!;
public string InitialDescription = default!;
public ReagentPrototype? CurrentReagent;
public bool Transformed { get; private set; }
public bool Transformed { get; internal set; }
protected override void Initialize()
{
@@ -30,72 +25,19 @@ namespace Content.Server.Chemistry.Components
if (Owner.TryGetComponent(out SpriteComponent? sprite) &&
sprite.BaseRSIPath != null)
{
_initialSprite = new SpriteSpecifier.Rsi(new ResourcePath(sprite.BaseRSIPath), "icon");
InitialSprite = new SpriteSpecifier.Rsi(new ResourcePath(sprite.BaseRSIPath), "icon");
}
_initialName = Owner.Name;
_initialDescription = Owner.Description;
InitialName = Owner.Name;
InitialDescription = Owner.Description;
}
protected override void Startup()
{
base.Startup();
Owner.EnsureComponentWarn(out SolutionContainerComponent solution);
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;
}
Owner.EnsureComponentWarn<SolutionContainerManagerComponent>();
Owner.EnsureComponentWarn<FitsInDispenserComponent>();
}
}
}