A big hecking chemistry-related refactor. (#3055)
* A big hecking chemistry-related refactor. Changed SolutionContainerCaps. It now describes "stock" behavior for interacting with solutions that is pre-implemented by SolutionContainerComponent. As such things like syringes do not check it anymore (on themselves) to see "can we remove reagent from ourselves". That's assumed by it... being a syringe. SolutionContainerCaps now has different flags more accurately describing possible reagent interaction behaviors. ISolutionInteractionsComponent is the interface that describes the common behaviors like "what happens when injected with a syringe". This is implemented by SolutionContainerComponent but could be implemented by other classes. One notable example that drove me to making this interface was the /vg/station circuit imprinter which splits reagent poured in into its two reservoir beakers. Having this interface allows us to do this "proxying" behavior hack-free. (the hacks in /vg/ code were somewhat dirty...). PourableComponent has been replaced SolutionTransferComponent. It now describes both give-and-take behavior for the common reagent containers. This is in line with /vg/'s /obj/item/weapon/reagent_containers architecture. "Taking" in this context is ONLY from reagent tanks like fuel tanks. Oh, should I mention that fuel tanks and such have a proper component now? They do. Because of this behavioral change, reagent tanks DO NOT have Pourable anymore. Removing from reagent tanks is now in the hands of the item used on them. Welders and fire extinguishers now have code for removing from them. This sounds bad at first but remember that all have quite unique behavior related to this: Welders cause explosions if lit and can ONLY be fueled at fuel tanks. Extinguishers can be filled at any tank, etc... The code for this is also simpler due to ISolutionInteractionsComponent now so... IAfterInteract now works like IInteractUsing with the Priority levels and "return true to block further handlers" behavior. This was necessary to make extinguishers prioritize taking from tanks over spraying. Explicitly coded interactions like welders refueling also means they refuse instantly to full now, which they didn't before. And it plays the sound. Etc... Probably more stuff I'm forgetting. * Review improvements.
This commit is contained in:
committed by
GitHub
parent
b284c82668
commit
c40ac26ced
@@ -92,7 +92,7 @@ namespace Content.Shared.Chemistry
|
||||
return "";
|
||||
}
|
||||
|
||||
var majorReagent = Contents.OrderByDescending(reagent => reagent.Quantity).First(); ;
|
||||
var majorReagent = Contents.OrderByDescending(reagent => reagent.Quantity).First();
|
||||
return majorReagent.ReagentId;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
using System;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Chemistry
|
||||
{
|
||||
/// <summary>
|
||||
/// These are the defined capabilities of a container of a solution.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
[Serializable, NetSerializable]
|
||||
public enum SolutionContainerCaps
|
||||
{
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Can solutions be added into the container?
|
||||
/// </summary>
|
||||
AddTo = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Can solutions be removed from the container?
|
||||
/// </summary>
|
||||
RemoveFrom = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Allows the container to be placed in a <c>ReagentDispenserComponent</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>
|
||||
/// </summary>
|
||||
FitsInDispenser = 4,
|
||||
|
||||
/// <summary>
|
||||
/// Can people examine the solution in the container or is it impossible to see?
|
||||
/// </summary>
|
||||
CanExamine = 8,
|
||||
}
|
||||
|
||||
public static class SolutionContainerCapsHelpers
|
||||
{
|
||||
public static bool HasCap(this SolutionContainerCaps cap, SolutionContainerCaps other)
|
||||
{
|
||||
return (cap & other) == other;
|
||||
}
|
||||
}
|
||||
}
|
||||
62
Content.Shared/Chemistry/SolutionContainerCaps.cs
Normal file
62
Content.Shared/Chemistry/SolutionContainerCaps.cs
Normal file
@@ -0,0 +1,62 @@
|
||||
using System;
|
||||
using Content.Shared.GameObjects.Components.Chemistry;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Chemistry
|
||||
{
|
||||
/// <summary>
|
||||
/// Define common interaction behaviors for <see cref="SharedSolutionContainerComponent"/>
|
||||
/// </summary>
|
||||
/// <seealso cref="ISolutionInteractionsComponent"/>
|
||||
[Flags]
|
||||
[Serializable, NetSerializable]
|
||||
public enum SolutionContainerCaps : ushort
|
||||
{
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Reagents can be added with syringes.
|
||||
/// </summary>
|
||||
Injectable = 1 << 0,
|
||||
|
||||
/// <summary>
|
||||
/// Reagents can be removed with syringes.
|
||||
/// </summary>
|
||||
Drawable = 1 << 1,
|
||||
|
||||
/// <summary>
|
||||
/// Reagents can be easily added via all reagent containers.
|
||||
/// Think pouring something into another beaker or into the gas tank of a car.
|
||||
/// </summary>
|
||||
Refillable = 1 << 2,
|
||||
|
||||
/// <summary>
|
||||
/// Reagents can be easily removed through any reagent container.
|
||||
/// Think pouring this or draining from a water tank.
|
||||
/// </summary>
|
||||
Drainable = 1 << 3,
|
||||
|
||||
/// <summary>
|
||||
/// The contents of the solution can be examined directly.
|
||||
/// </summary>
|
||||
CanExamine = 1 << 4,
|
||||
|
||||
/// <summary>
|
||||
/// Allows the container to be placed in a <c>ReagentDispenserComponent</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>
|
||||
/// </summary>
|
||||
FitsInDispenser = 1 << 5,
|
||||
|
||||
OpenContainer = Refillable | Drainable | CanExamine
|
||||
}
|
||||
|
||||
public static class SolutionContainerCapsHelpers
|
||||
{
|
||||
public static bool HasCap(this SolutionContainerCaps cap, SolutionContainerCaps other)
|
||||
{
|
||||
return (cap & other) == other;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
using Content.Shared.Chemistry;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
|
||||
namespace Content.Shared.GameObjects.Components.Chemistry
|
||||
{
|
||||
/// <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;
|
||||
|
||||
void Refill(Solution solution)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
//
|
||||
// DRAINING
|
||||
//
|
||||
|
||||
bool CanDrain => false;
|
||||
ReagentUnit DrainAvailable => ReagentUnit.Zero;
|
||||
|
||||
Solution Drain(ReagentUnit amount)
|
||||
{
|
||||
return new();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,7 @@ namespace Content.Shared.GameObjects.Components.Chemistry
|
||||
/// <summary>
|
||||
/// Holds a <see cref="Solution"/> with a limited volume.
|
||||
/// </summary>
|
||||
public abstract class SharedSolutionContainerComponent : Component, IExamine
|
||||
public abstract class SharedSolutionContainerComponent : Component, IExamine, ISolutionInteractionsComponent
|
||||
{
|
||||
public override string Name => "SolutionContainer";
|
||||
|
||||
@@ -60,9 +60,11 @@ namespace Content.Shared.GameObjects.Components.Chemistry
|
||||
|
||||
public bool CanUseWithChemDispenser => Capabilities.HasCap(SolutionContainerCaps.FitsInDispenser);
|
||||
|
||||
public bool CanAddSolutions => Capabilities.HasCap(SolutionContainerCaps.AddTo);
|
||||
public bool CanInject => Capabilities.HasCap(SolutionContainerCaps.Injectable) || CanRefill;
|
||||
public bool CanDraw => Capabilities.HasCap(SolutionContainerCaps.Drawable) || CanDrain;
|
||||
|
||||
public bool CanRemoveSolutions => Capabilities.HasCap(SolutionContainerCaps.RemoveFrom);
|
||||
public bool CanRefill => Capabilities.HasCap(SolutionContainerCaps.Refillable);
|
||||
public bool CanDrain => Capabilities.HasCap(SolutionContainerCaps.Drainable);
|
||||
|
||||
public override void ExposeData(ObjectSerializer serializer)
|
||||
{
|
||||
@@ -71,7 +73,7 @@ namespace Content.Shared.GameObjects.Components.Chemistry
|
||||
serializer.DataField(this, x => x.CanReact, "canReact", true);
|
||||
serializer.DataField(this, x => x.MaxVolume, "maxVol", ReagentUnit.New(0));
|
||||
serializer.DataField(this, x => x.Solution, "contents", new Solution());
|
||||
serializer.DataField(this, x => x.Capabilities, "caps", SolutionContainerCaps.AddTo | SolutionContainerCaps.RemoveFrom | SolutionContainerCaps.CanExamine);
|
||||
serializer.DataField(this, x => x.Capabilities, "caps", SolutionContainerCaps.None);
|
||||
}
|
||||
|
||||
public void RemoveAllSolution()
|
||||
@@ -209,6 +211,43 @@ namespace Content.Shared.GameObjects.Components.Chemistry
|
||||
message.AddMarkup(Loc.GetString(messageString, colorHex, Loc.GetString(proto.PhysicalDescription)));
|
||||
}
|
||||
|
||||
ReagentUnit ISolutionInteractionsComponent.RefillSpaceAvailable => EmptyVolume;
|
||||
ReagentUnit ISolutionInteractionsComponent.InjectSpaceAvailable => EmptyVolume;
|
||||
ReagentUnit ISolutionInteractionsComponent.DrawAvailable => CurrentVolume;
|
||||
ReagentUnit ISolutionInteractionsComponent.DrainAvailable => CurrentVolume;
|
||||
|
||||
void ISolutionInteractionsComponent.Refill(Solution solution)
|
||||
{
|
||||
if (!CanRefill)
|
||||
return;
|
||||
|
||||
TryAddSolution(solution);
|
||||
}
|
||||
|
||||
void ISolutionInteractionsComponent.Inject(Solution solution)
|
||||
{
|
||||
if (!CanInject)
|
||||
return;
|
||||
|
||||
TryAddSolution(solution);
|
||||
}
|
||||
|
||||
Solution ISolutionInteractionsComponent.Draw(ReagentUnit amount)
|
||||
{
|
||||
if (!CanDraw)
|
||||
return new Solution();
|
||||
|
||||
return SplitSolution(amount);
|
||||
}
|
||||
|
||||
Solution ISolutionInteractionsComponent.Drain(ReagentUnit amount)
|
||||
{
|
||||
if (!CanDrain)
|
||||
return new Solution();
|
||||
|
||||
return SplitSolution(amount);
|
||||
}
|
||||
|
||||
private void UpdateAppearance()
|
||||
{
|
||||
if (Owner.Deleted || !Owner.TryGetComponent<SharedAppearanceComponent>(out var appearance))
|
||||
|
||||
@@ -16,10 +16,16 @@ namespace Content.Shared.Interfaces.GameObjects.Components
|
||||
/// </summary>
|
||||
public interface IAfterInteract
|
||||
{
|
||||
/// <summary>
|
||||
/// The interaction priority. Higher numbers get called first.
|
||||
/// </summary>
|
||||
/// <value>Priority defaults to 0</value>
|
||||
int Priority => 0;
|
||||
|
||||
/// <summary>
|
||||
/// Called when we interact with nothing, or when we interact with an entity out of range that has no behavior
|
||||
/// </summary>
|
||||
Task AfterInteract(AfterInteractEventArgs eventArgs);
|
||||
Task<bool> AfterInteract(AfterInteractEventArgs eventArgs);
|
||||
}
|
||||
|
||||
public class AfterInteractEventArgs : EventArgs
|
||||
|
||||
Reference in New Issue
Block a user