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
@@ -148,41 +148,41 @@ namespace Content.Server.GameObjects.Components.ActionBlocking
|
||||
return new HandcuffedComponentState(Broken ? BrokenState : string.Empty);
|
||||
}
|
||||
|
||||
async Task IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs)
|
||||
async Task<bool> IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs)
|
||||
{
|
||||
if (eventArgs.Target == null || !ActionBlockerSystem.CanUse(eventArgs.User) || !eventArgs.Target.TryGetComponent<CuffableComponent>(out var cuffed))
|
||||
{
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (eventArgs.Target == eventArgs.User)
|
||||
{
|
||||
eventArgs.User.PopupMessage(Loc.GetString("You can't cuff yourself!"));
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (Broken)
|
||||
{
|
||||
eventArgs.User.PopupMessage(Loc.GetString("The cuffs are broken!"));
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!eventArgs.Target.TryGetComponent<HandsComponent>(out var hands))
|
||||
{
|
||||
eventArgs.User.PopupMessage(Loc.GetString("{0:theName} has no hands!", eventArgs.Target));
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (cuffed.CuffedHandCount == hands.Count)
|
||||
{
|
||||
eventArgs.User.PopupMessage(Loc.GetString("{0:theName} has no free hands to handcuff!", eventArgs.Target));
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!eventArgs.InRangeUnobstructed(_interactRange, ignoreInsideBlocker: true))
|
||||
{
|
||||
eventArgs.User.PopupMessage(Loc.GetString("You are too far away to use the cuffs!"));
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
eventArgs.User.PopupMessage(Loc.GetString("You start cuffing {0:theName}.", eventArgs.Target));
|
||||
@@ -190,6 +190,7 @@ namespace Content.Server.GameObjects.Components.ActionBlocking
|
||||
_audioSystem.PlayFromEntity(StartCuffSound, Owner);
|
||||
|
||||
TryUpdateCuff(eventArgs.User, eventArgs.Target, cuffed);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -253,18 +253,20 @@ namespace Content.Server.GameObjects.Components.Atmos
|
||||
}
|
||||
}
|
||||
|
||||
async Task IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs)
|
||||
async Task<bool> IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs)
|
||||
{
|
||||
if (!eventArgs.CanReach)
|
||||
{
|
||||
eventArgs.User.PopupMessage(Loc.GetString("You can't reach there!"));
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (eventArgs.User.TryGetComponent(out IActorComponent? actor))
|
||||
{
|
||||
OpenInterface(actor.playerSession, eventArgs.ClickLocation);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -36,11 +36,11 @@ namespace Content.Server.GameObjects.Components.Body
|
||||
}
|
||||
}
|
||||
|
||||
async Task IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs)
|
||||
async Task<bool> IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs)
|
||||
{
|
||||
if (eventArgs.Target == null)
|
||||
{
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
CloseAllSurgeryUIs();
|
||||
@@ -61,6 +61,8 @@ namespace Content.Server.GameObjects.Components.Body
|
||||
eventArgs.Target.PopupMessage(eventArgs.User, Loc.GetString("You can't fit it in!"));
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void SendBodyPartListToUser(AfterInteractEventArgs eventArgs, IBody body)
|
||||
|
||||
@@ -99,12 +99,12 @@ namespace Content.Server.GameObjects.Components.Body.Part
|
||||
}
|
||||
}
|
||||
|
||||
public async Task AfterInteract(AfterInteractEventArgs eventArgs)
|
||||
public async Task<bool> AfterInteract(AfterInteractEventArgs eventArgs)
|
||||
{
|
||||
// TODO BODY
|
||||
if (eventArgs.Target == null)
|
||||
{
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
CloseAllSurgeryUIs();
|
||||
@@ -116,6 +116,8 @@ namespace Content.Server.GameObjects.Components.Body.Part
|
||||
{
|
||||
SendSlots(eventArgs, body);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void SendSlots(AfterInteractEventArgs eventArgs, IBody body)
|
||||
|
||||
@@ -50,16 +50,16 @@ namespace Content.Server.GameObjects.Components.Body.Surgery
|
||||
|
||||
public IEntity? PerformerCache { get; private set; }
|
||||
|
||||
async Task IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs)
|
||||
async Task<bool> IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs)
|
||||
{
|
||||
if (eventArgs.Target == null)
|
||||
{
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!eventArgs.User.TryGetComponent(out IActorComponent? actor))
|
||||
{
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
CloseAllSurgeryUIs();
|
||||
@@ -101,20 +101,22 @@ namespace Content.Server.GameObjects.Components.Body.Surgery
|
||||
if (!part.SurgeryCheck(_surgeryType))
|
||||
{
|
||||
NotUsefulPopup();
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
// ...do the surgery.
|
||||
if (part.AttemptSurgery(_surgeryType, part, this,
|
||||
eventArgs.User))
|
||||
{
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Log error if the surgery fails somehow.
|
||||
Logger.Debug($"Error when trying to perform surgery on ${nameof(IBodyPart)} {eventArgs.User.Name}");
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public float BaseOperationTime { get => _baseOperateTime; set => _baseOperateTime = value; }
|
||||
|
||||
@@ -12,6 +12,7 @@ using Content.Server.Utility;
|
||||
using Content.Shared.Audio;
|
||||
using Content.Shared.Chemistry;
|
||||
using Content.Shared.GameObjects.Components.Botany;
|
||||
using Content.Shared.GameObjects.Components.Chemistry;
|
||||
using Content.Shared.GameObjects.EntitySystems;
|
||||
using Content.Shared.GameObjects.EntitySystems.ActionBlocker;
|
||||
using Content.Shared.Interfaces;
|
||||
@@ -718,23 +719,28 @@ namespace Content.Server.GameObjects.Components.Botany
|
||||
return true;
|
||||
}
|
||||
|
||||
if (usingItem.TryGetComponent(out SolutionContainerComponent? solution) && solution.CanRemoveSolutions)
|
||||
if (usingItem.TryGetComponent(out ISolutionInteractionsComponent? solution) && solution.CanDrain)
|
||||
{
|
||||
var amount = 5f;
|
||||
var amount = ReagentUnit.New(5);
|
||||
var sprayed = false;
|
||||
|
||||
if (usingItem.TryGetComponent(out SprayComponent? spray))
|
||||
{
|
||||
sprayed = true;
|
||||
amount = 1f;
|
||||
amount = ReagentUnit.New(1);
|
||||
EntitySystem.Get<AudioSystem>().PlayFromEntity(spray.SpraySound, usingItem, AudioHelpers.WithVariation(0.125f));
|
||||
}
|
||||
|
||||
var chemAmount = ReagentUnit.New(amount);
|
||||
var split = solution.Drain(amount);
|
||||
if (split.TotalVolume == 0)
|
||||
{
|
||||
user.PopupMessageCursor(Loc.GetString("{0:TheName} is empty!", usingItem));
|
||||
return true;
|
||||
}
|
||||
|
||||
var split = solution.Solution.SplitSolution(chemAmount <= solution.Solution.TotalVolume ? chemAmount : solution.Solution.TotalVolume);
|
||||
|
||||
user.PopupMessageCursor(Loc.GetString(sprayed ? $"You spray {Owner.Name} with {usingItem.Name}." : $"You transfer {split.TotalVolume.ToString()}u to {Owner.Name}"));
|
||||
user.PopupMessageCursor(Loc.GetString(
|
||||
sprayed ? "You spray {0:TheName}" : "You transfer {1}u to {0:TheName}",
|
||||
Owner, split.TotalVolume));
|
||||
|
||||
_solutionContainer?.TryAddSolution(split);
|
||||
|
||||
|
||||
@@ -52,10 +52,12 @@ namespace Content.Server.GameObjects.Components.Chemistry
|
||||
return TryDoInject(target, user);
|
||||
}
|
||||
|
||||
Task IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs)
|
||||
async Task<bool> IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs)
|
||||
{
|
||||
TryDoInject(eventArgs.Target, eventArgs.User);
|
||||
return Task.CompletedTask;
|
||||
if (!eventArgs.CanReach)
|
||||
return false;
|
||||
|
||||
return TryDoInject(eventArgs.Target, eventArgs.User);
|
||||
}
|
||||
|
||||
private bool TryDoInject(IEntity? target, IEntity user)
|
||||
|
||||
@@ -28,21 +28,18 @@ namespace Content.Server.GameObjects.Components.Chemistry
|
||||
/// Whether or not the injector is able to draw from containers or if it's a single use
|
||||
/// device that can only inject.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
private bool _injectOnly;
|
||||
[ViewVariables] private bool _injectOnly;
|
||||
|
||||
/// <summary>
|
||||
/// Amount to inject or draw on each usage. If the injector is inject only, it will
|
||||
/// attempt to inject it's entire contents upon use.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
private ReagentUnit _transferAmount;
|
||||
[ViewVariables] private ReagentUnit _transferAmount;
|
||||
|
||||
/// <summary>
|
||||
/// Initial storage volume of the injector
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
private ReagentUnit _initialMaxVolume;
|
||||
[ViewVariables] private ReagentUnit _initialMaxVolume;
|
||||
|
||||
private InjectorToggleMode _toggleState;
|
||||
|
||||
@@ -68,19 +65,14 @@ namespace Content.Server.GameObjects.Components.Chemistry
|
||||
serializer.DataField(ref _injectOnly, "injectOnly", false);
|
||||
serializer.DataField(ref _initialMaxVolume, "initialMaxVolume", ReagentUnit.New(15));
|
||||
serializer.DataField(ref _transferAmount, "transferAmount", ReagentUnit.New(5));
|
||||
serializer.DataField(ref _toggleState, "toggleState", _injectOnly ? InjectorToggleMode.Inject : InjectorToggleMode.Draw);
|
||||
serializer.DataField(ref _toggleState, "toggleState",
|
||||
_injectOnly ? InjectorToggleMode.Inject : InjectorToggleMode.Draw);
|
||||
}
|
||||
|
||||
protected override void Startup()
|
||||
{
|
||||
base.Startup();
|
||||
|
||||
var solution = Owner.EnsureComponent<SolutionContainerComponent>();
|
||||
solution.Capabilities = SolutionContainerCaps.AddTo | SolutionContainerCaps.RemoveFrom;
|
||||
|
||||
// Set _toggleState based on prototype
|
||||
_toggleState = _injectOnly ? InjectorToggleMode.Inject : InjectorToggleMode.Draw;
|
||||
|
||||
Dirty();
|
||||
}
|
||||
|
||||
@@ -116,58 +108,55 @@ namespace Content.Server.GameObjects.Components.Chemistry
|
||||
/// Called when clicking on entities while holding in active hand
|
||||
/// </summary>
|
||||
/// <param name="eventArgs"></param>
|
||||
async Task IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs)
|
||||
async Task<bool> IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs)
|
||||
{
|
||||
if (!eventArgs.InRangeUnobstructed(ignoreInsideBlocker: true, popup: true)) return;
|
||||
if (!eventArgs.InRangeUnobstructed(ignoreInsideBlocker: true, popup: true))
|
||||
return false;
|
||||
|
||||
//Make sure we have the attacking entity
|
||||
if (eventArgs.Target == null || !Owner.TryGetComponent(out SolutionContainerComponent? solution))
|
||||
if (eventArgs.Target == null || !Owner.HasComponent<SolutionContainerComponent>())
|
||||
{
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
var targetEntity = eventArgs.Target;
|
||||
|
||||
// Handle injecting/drawing for solutions
|
||||
if (targetEntity.TryGetComponent<SolutionContainerComponent>(out var targetSolution))
|
||||
if (targetEntity.TryGetComponent<ISolutionInteractionsComponent>(out var targetSolution))
|
||||
{
|
||||
if (ToggleState == InjectorToggleMode.Inject)
|
||||
{
|
||||
if (solution.CanRemoveSolutions && targetSolution.CanAddSolutions)
|
||||
if (targetSolution.CanInject)
|
||||
{
|
||||
TryInject(targetSolution, eventArgs.User);
|
||||
}
|
||||
else
|
||||
{
|
||||
eventArgs.User.PopupMessage(eventArgs.User, Loc.GetString("You aren't able to transfer to {0:theName}!", targetSolution.Owner));
|
||||
eventArgs.User.PopupMessage(eventArgs.User,
|
||||
Loc.GetString("You aren't able to transfer to {0:theName}!", targetSolution.Owner));
|
||||
}
|
||||
}
|
||||
else if (ToggleState == InjectorToggleMode.Draw)
|
||||
{
|
||||
if (targetSolution.CanRemoveSolutions && solution.CanAddSolutions)
|
||||
if (targetSolution.CanDraw)
|
||||
{
|
||||
TryDraw(targetSolution, eventArgs.User);
|
||||
}
|
||||
else
|
||||
{
|
||||
eventArgs.User.PopupMessage(eventArgs.User, Loc.GetString("You aren't able to draw from {0:theName}!", targetSolution.Owner));
|
||||
eventArgs.User.PopupMessage(eventArgs.User,
|
||||
Loc.GetString("You aren't able to draw from {0:theName}!", targetSolution.Owner));
|
||||
}
|
||||
}
|
||||
}
|
||||
else // Handle injecting into bloodstream
|
||||
// Handle injecting into bloodstream
|
||||
else if (targetEntity.TryGetComponent(out BloodstreamComponent? bloodstream) &&
|
||||
ToggleState == InjectorToggleMode.Inject)
|
||||
{
|
||||
if (targetEntity.TryGetComponent(out BloodstreamComponent? bloodstream) && ToggleState == InjectorToggleMode.Inject)
|
||||
{
|
||||
if (solution.CanRemoveSolutions)
|
||||
{
|
||||
TryInjectIntoBloodstream(bloodstream, eventArgs.User);
|
||||
}
|
||||
else
|
||||
{
|
||||
eventArgs.User.PopupMessage(eventArgs.User, Loc.GetString("You aren't able to inject {0:theName}!", targetEntity));
|
||||
}
|
||||
}
|
||||
TryInjectIntoBloodstream(bloodstream, eventArgs.User);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -193,7 +182,8 @@ namespace Content.Server.GameObjects.Components.Chemistry
|
||||
|
||||
if (realTransferAmount <= 0)
|
||||
{
|
||||
Owner.PopupMessage(user, Loc.GetString("You aren't able to inject {0:theName}!", targetBloodstream.Owner));
|
||||
Owner.PopupMessage(user,
|
||||
Loc.GetString("You aren't able to inject {0:theName}!", targetBloodstream.Owner));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -213,12 +203,14 @@ namespace Content.Server.GameObjects.Components.Chemistry
|
||||
|
||||
removedSolution.DoEntityReaction(targetBloodstream.Owner, ReactionMethod.Injection);
|
||||
|
||||
Owner.PopupMessage(user, Loc.GetString("You inject {0}u into {1:theName}!", removedSolution.TotalVolume, targetBloodstream.Owner));
|
||||
Owner.PopupMessage(user,
|
||||
Loc.GetString("You inject {0}u into {1:theName}!", removedSolution.TotalVolume,
|
||||
targetBloodstream.Owner));
|
||||
Dirty();
|
||||
AfterInject();
|
||||
}
|
||||
|
||||
private void TryInject(SolutionContainerComponent targetSolution, IEntity user)
|
||||
private void TryInject(ISolutionInteractionsComponent targetSolution, IEntity user)
|
||||
{
|
||||
if (!Owner.TryGetComponent(out SolutionContainerComponent? solution) || solution.CurrentVolume == 0)
|
||||
{
|
||||
@@ -226,7 +218,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
|
||||
}
|
||||
|
||||
// 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.InjectSpaceAvailable);
|
||||
|
||||
if (realTransferAmount <= 0)
|
||||
{
|
||||
@@ -237,16 +229,12 @@ namespace Content.Server.GameObjects.Components.Chemistry
|
||||
// Move units from attackSolution to targetSolution
|
||||
var removedSolution = solution.SplitSolution(realTransferAmount);
|
||||
|
||||
if (!targetSolution.CanAddSolution(removedSolution))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
removedSolution.DoEntityReaction(targetSolution.Owner, ReactionMethod.Injection);
|
||||
|
||||
targetSolution.TryAddSolution(removedSolution);
|
||||
targetSolution.Inject(removedSolution);
|
||||
|
||||
Owner.PopupMessage(user, Loc.GetString("You transfer {0}u to {1:theName}", removedSolution.TotalVolume, targetSolution.Owner));
|
||||
Owner.PopupMessage(user,
|
||||
Loc.GetString("You transfer {0}u to {1:theName}", removedSolution.TotalVolume, targetSolution.Owner));
|
||||
Dirty();
|
||||
AfterInject();
|
||||
}
|
||||
@@ -260,7 +248,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
|
||||
}
|
||||
}
|
||||
|
||||
private void TryDraw(SolutionContainerComponent targetSolution, IEntity user)
|
||||
private void TryDraw(ISolutionInteractionsComponent targetSolution, IEntity user)
|
||||
{
|
||||
if (!Owner.TryGetComponent(out SolutionContainerComponent? solution) || solution.EmptyVolume == 0)
|
||||
{
|
||||
@@ -268,7 +256,7 @@ namespace Content.Server.GameObjects.Components.Chemistry
|
||||
}
|
||||
|
||||
// Get transfer amount. May be smaller than _transferAmount if not enough room
|
||||
var realTransferAmount = ReagentUnit.Min(_transferAmount, targetSolution.CurrentVolume);
|
||||
var realTransferAmount = ReagentUnit.Min(_transferAmount, targetSolution.DrawAvailable);
|
||||
|
||||
if (realTransferAmount <= 0)
|
||||
{
|
||||
@@ -277,14 +265,15 @@ namespace Content.Server.GameObjects.Components.Chemistry
|
||||
}
|
||||
|
||||
// Move units from attackSolution to targetSolution
|
||||
var removedSolution = targetSolution.SplitSolution(realTransferAmount);
|
||||
var removedSolution = targetSolution.Draw(realTransferAmount);
|
||||
|
||||
if (!solution.TryAddSolution(removedSolution))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Owner.PopupMessage(user, Loc.GetString("Drew {0}u from {1:theName}", removedSolution.TotalVolume, targetSolution.Owner));
|
||||
Owner.PopupMessage(user,
|
||||
Loc.GetString("Drew {0}u from {1:theName}", removedSolution.TotalVolume, targetSolution.Owner));
|
||||
Dirty();
|
||||
AfterDraw();
|
||||
}
|
||||
|
||||
@@ -59,14 +59,15 @@ namespace Content.Server.GameObjects.Components.Chemistry
|
||||
}
|
||||
|
||||
// Feeding someone else
|
||||
public async Task AfterInteract(AfterInteractEventArgs eventArgs)
|
||||
public async Task<bool> AfterInteract(AfterInteractEventArgs eventArgs)
|
||||
{
|
||||
if (eventArgs.Target == null)
|
||||
{
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
TryUseFood(eventArgs.User, eventArgs.Target);
|
||||
return true;
|
||||
}
|
||||
|
||||
public override bool TryUseFood(IEntity user, IEntity target, UtensilComponent utensilUsed = null)
|
||||
|
||||
@@ -1,113 +0,0 @@
|
||||
using System.Threading.Tasks;
|
||||
using Content.Shared.Chemistry;
|
||||
using Content.Shared.Interfaces;
|
||||
using Content.Shared.Interfaces.GameObjects.Components;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Chemistry
|
||||
{
|
||||
/// <summary>
|
||||
/// Gives an entity click behavior for pouring reagents into
|
||||
/// other entities and being poured into. The entity must have
|
||||
/// a SolutionComponent or DrinkComponent for this to work.
|
||||
/// (DrinkComponent adds a SolutionComponent if one isn't present).
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
class PourableComponent : Component, IInteractUsing
|
||||
{
|
||||
public override string Name => "Pourable";
|
||||
|
||||
private ReagentUnit _transferAmount;
|
||||
|
||||
/// <summary>
|
||||
/// The amount of solution to be transferred from this solution when clicking on other solutions with it.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public ReagentUnit TransferAmount
|
||||
{
|
||||
get => _transferAmount;
|
||||
set => _transferAmount = value;
|
||||
}
|
||||
|
||||
public override void ExposeData(ObjectSerializer serializer)
|
||||
{
|
||||
base.ExposeData(serializer);
|
||||
serializer.DataField(ref _transferAmount, "transferAmount", ReagentUnit.New(5.0));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the owner of this component is clicked on with another entity.
|
||||
/// The owner of this component is the target.
|
||||
/// The entity used to click on this one is the attacker.
|
||||
/// </summary>
|
||||
/// <param name="eventArgs">Attack event args</param>
|
||||
/// <returns></returns>
|
||||
async Task<bool> IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs)
|
||||
{
|
||||
//Get target solution component
|
||||
if (!Owner.TryGetComponent<SolutionContainerComponent>(out var targetSolution))
|
||||
return false;
|
||||
|
||||
//Get attack solution component
|
||||
var attackEntity = eventArgs.Using;
|
||||
if (!attackEntity.TryGetComponent<SolutionContainerComponent>(out var attackSolution))
|
||||
return false;
|
||||
|
||||
// Calculate possibe solution transfer
|
||||
if (targetSolution.CanAddSolutions && attackSolution.CanRemoveSolutions)
|
||||
{
|
||||
// default logic (beakers and glasses)
|
||||
// transfer solution from object in hand to attacked
|
||||
return TryTransfer(eventArgs, attackSolution, targetSolution);
|
||||
}
|
||||
else if (targetSolution.CanRemoveSolutions && attackSolution.CanAddSolutions)
|
||||
{
|
||||
// storage tanks and sinks logic
|
||||
// drain solution from attacked object to object in hand
|
||||
return TryTransfer(eventArgs, targetSolution, attackSolution);
|
||||
}
|
||||
|
||||
// No transfer possible
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TryTransfer(InteractUsingEventArgs eventArgs, SolutionContainerComponent fromSolution, SolutionContainerComponent toSolution)
|
||||
{
|
||||
var fromEntity = fromSolution.Owner;
|
||||
|
||||
if (!fromEntity.TryGetComponent<PourableComponent>(out var fromPourable))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
//Get transfer amount. May be smaller than _transferAmount if not enough room
|
||||
var realTransferAmount = ReagentUnit.Min(fromPourable.TransferAmount, toSolution.EmptyVolume);
|
||||
|
||||
if (realTransferAmount <= 0) // Special message if container is full
|
||||
{
|
||||
Owner.PopupMessage(eventArgs.User, Loc.GetString("{0:theName} is full!", toSolution.Owner));
|
||||
return false;
|
||||
}
|
||||
|
||||
//Move units from attackSolution to targetSolution
|
||||
var removedSolution = fromSolution.SplitSolution(realTransferAmount);
|
||||
|
||||
if (removedSolution.TotalVolume <= ReagentUnit.Zero)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!toSolution.TryAddSolution(removedSolution))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Owner.PopupMessage(eventArgs.User, Loc.GetString("You transfer {0}u to {1:theName}.", removedSolution.TotalVolume, toSolution.Owner));
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
using Content.Shared.Chemistry;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Chemistry
|
||||
{
|
||||
[RegisterComponent]
|
||||
public class ReagentTankComponent : Component
|
||||
{
|
||||
public override string Name => "ReagentTank";
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public ReagentUnit TransferAmount { get; set; }
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public ReagentTankType TankType { get; set; }
|
||||
|
||||
public override void ExposeData(ObjectSerializer serializer)
|
||||
{
|
||||
base.ExposeData(serializer);
|
||||
|
||||
serializer.DataField(this, c => c.TransferAmount, "transferAmount", ReagentUnit.New(10));
|
||||
serializer.DataField(this, c => c.TankType, "tankType", ReagentTankType.Unspecified);
|
||||
}
|
||||
}
|
||||
|
||||
public enum ReagentTankType : byte
|
||||
{
|
||||
Unspecified,
|
||||
Fuel
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
#nullable enable
|
||||
#nullable enable
|
||||
using Content.Server.Administration;
|
||||
using Content.Server.Eui;
|
||||
using Content.Server.GameObjects.Components.GUI;
|
||||
@@ -6,6 +6,7 @@ using Content.Shared.Administration;
|
||||
using Content.Shared.Chemistry;
|
||||
using Content.Shared.Eui;
|
||||
using Content.Shared.GameObjects.Components.Chemistry;
|
||||
using Content.Shared.GameObjects.EntitySystems;
|
||||
using Content.Shared.GameObjects.EntitySystems.ActionBlocker;
|
||||
using Content.Shared.GameObjects.Verbs;
|
||||
using Robust.Server.Interfaces.GameObjects;
|
||||
@@ -19,213 +20,8 @@ namespace Content.Server.GameObjects.Components.Chemistry
|
||||
{
|
||||
[RegisterComponent]
|
||||
[ComponentReference(typeof(SharedSolutionContainerComponent))]
|
||||
[ComponentReference(typeof(ISolutionInteractionsComponent))]
|
||||
public class SolutionContainerComponent : SharedSolutionContainerComponent
|
||||
{
|
||||
/// <summary>
|
||||
/// Transfers solution from the held container to the target container.
|
||||
/// </summary>
|
||||
[Verb]
|
||||
private sealed class FillTargetVerb : Verb<SolutionContainerComponent>
|
||||
{
|
||||
protected override void GetData(IEntity user, SolutionContainerComponent component, VerbData data)
|
||||
{
|
||||
if (!ActionBlockerSystem.CanInteract(user) ||
|
||||
!user.TryGetComponent<HandsComponent>(out var hands) ||
|
||||
hands.GetActiveHand == null ||
|
||||
hands.GetActiveHand.Owner == component.Owner ||
|
||||
!hands.GetActiveHand.Owner.TryGetComponent<SolutionContainerComponent>(out var solution) ||
|
||||
!solution.CanRemoveSolutions ||
|
||||
!component.CanAddSolutions)
|
||||
{
|
||||
data.Visibility = VerbVisibility.Invisible;
|
||||
return;
|
||||
}
|
||||
|
||||
var heldEntityName = hands.GetActiveHand.Owner?.Prototype?.Name ?? "<Item>";
|
||||
var myName = component.Owner.Prototype?.Name ?? "<Item>";
|
||||
|
||||
var locHeldEntityName = Loc.GetString(heldEntityName);
|
||||
var locMyName = Loc.GetString(myName);
|
||||
|
||||
data.Visibility = VerbVisibility.Visible;
|
||||
data.Text = Loc.GetString("Transfer liquid from [{0}] to [{1}].", locHeldEntityName, locMyName);
|
||||
}
|
||||
|
||||
protected override void Activate(IEntity user, SolutionContainerComponent component)
|
||||
{
|
||||
if (!user.TryGetComponent<HandsComponent>(out var hands) || hands.GetActiveHand == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!hands.GetActiveHand.Owner.TryGetComponent<SolutionContainerComponent>(out var handSolutionComp) ||
|
||||
!handSolutionComp.CanRemoveSolutions ||
|
||||
!component.CanAddSolutions)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var transferQuantity = ReagentUnit.Min(component.MaxVolume - component.CurrentVolume,
|
||||
handSolutionComp.CurrentVolume, ReagentUnit.New(10));
|
||||
|
||||
if (transferQuantity <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var transferSolution = handSolutionComp.SplitSolution(transferQuantity);
|
||||
component.TryAddSolution(transferSolution);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Transfers solution from a target container to the held container.
|
||||
/// </summary>
|
||||
[Verb]
|
||||
private sealed class EmptyTargetVerb : Verb<SolutionContainerComponent>
|
||||
{
|
||||
protected override void GetData(IEntity user, SolutionContainerComponent component, VerbData data)
|
||||
{
|
||||
if (!ActionBlockerSystem.CanInteract(user) ||
|
||||
!user.TryGetComponent<HandsComponent>(out var hands) ||
|
||||
hands.GetActiveHand == null ||
|
||||
hands.GetActiveHand.Owner == component.Owner ||
|
||||
!hands.GetActiveHand.Owner.TryGetComponent<SolutionContainerComponent>(out var solution) ||
|
||||
!solution.CanAddSolutions ||
|
||||
!component.CanRemoveSolutions)
|
||||
{
|
||||
data.Visibility = VerbVisibility.Invisible;
|
||||
return;
|
||||
}
|
||||
|
||||
var heldEntityName = hands.GetActiveHand.Owner?.Prototype?.Name ?? "<Item>";
|
||||
var myName = component.Owner.Prototype?.Name ?? "<Item>";
|
||||
|
||||
var locHeldEntityName = Loc.GetString(heldEntityName);
|
||||
var locMyName = Loc.GetString(myName);
|
||||
|
||||
data.Visibility = VerbVisibility.Visible;
|
||||
data.Text = Loc.GetString("Transfer liquid from [{0}] to [{1}].", locMyName, locHeldEntityName);
|
||||
return;
|
||||
}
|
||||
|
||||
protected override void Activate(IEntity user, SolutionContainerComponent component)
|
||||
{
|
||||
if (!user.TryGetComponent<HandsComponent>(out var hands) || hands.GetActiveHand == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!hands.GetActiveHand.Owner.TryGetComponent<SolutionContainerComponent>(out var handSolutionComp) ||
|
||||
!handSolutionComp.CanAddSolutions ||
|
||||
!component.CanRemoveSolutions)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var transferQuantity = ReagentUnit.Min(handSolutionComp.MaxVolume - handSolutionComp.CurrentVolume,
|
||||
component.CurrentVolume, ReagentUnit.New(10));
|
||||
|
||||
if (transferQuantity <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var transferSolution = component.SplitSolution(transferQuantity);
|
||||
handSolutionComp.TryAddSolution(transferSolution);
|
||||
}
|
||||
}
|
||||
|
||||
[Verb]
|
||||
private sealed class AdminAddReagentVerb : Verb<SolutionContainerComponent>
|
||||
{
|
||||
private const AdminFlags ReqFlags = AdminFlags.Fun;
|
||||
|
||||
protected override void GetData(IEntity user, SolutionContainerComponent component, VerbData data)
|
||||
{
|
||||
data.Text = Loc.GetString("Add Reagent...");
|
||||
data.CategoryData = VerbCategories.Debug;
|
||||
data.Visibility = VerbVisibility.Invisible;
|
||||
|
||||
var adminManager = IoCManager.Resolve<IAdminManager>();
|
||||
|
||||
if (user.TryGetComponent<IActorComponent>(out var player))
|
||||
{
|
||||
if (adminManager.HasAdminFlag(player.playerSession, ReqFlags))
|
||||
{
|
||||
data.Visibility = VerbVisibility.Visible;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Activate(IEntity user, SolutionContainerComponent component)
|
||||
{
|
||||
var groupController = IoCManager.Resolve<IAdminManager>();
|
||||
if (user.TryGetComponent<IActorComponent>(out var player))
|
||||
{
|
||||
if (groupController.HasAdminFlag(player.playerSession, ReqFlags))
|
||||
OpenAddReagentMenu(player.playerSession, component);
|
||||
}
|
||||
}
|
||||
|
||||
private static void OpenAddReagentMenu(IPlayerSession player, SolutionContainerComponent comp)
|
||||
{
|
||||
var euiMgr = IoCManager.Resolve<EuiManager>();
|
||||
euiMgr.OpenEui(new AdminAddReagentEui(comp), player);
|
||||
}
|
||||
|
||||
private sealed class AdminAddReagentEui : BaseEui
|
||||
{
|
||||
private readonly SolutionContainerComponent _target;
|
||||
[Dependency] private readonly IAdminManager _adminManager = default!;
|
||||
|
||||
public AdminAddReagentEui(SolutionContainerComponent target)
|
||||
{
|
||||
_target = target;
|
||||
|
||||
IoCManager.InjectDependencies(this);
|
||||
}
|
||||
|
||||
public override void Opened()
|
||||
{
|
||||
StateDirty();
|
||||
}
|
||||
|
||||
public override EuiStateBase GetNewState()
|
||||
{
|
||||
return new AdminAddReagentEuiState
|
||||
{
|
||||
CurVolume = _target.CurrentVolume,
|
||||
MaxVolume = _target.MaxVolume
|
||||
};
|
||||
}
|
||||
|
||||
public override void HandleMessage(EuiMessageBase msg)
|
||||
{
|
||||
switch (msg)
|
||||
{
|
||||
case AdminAddReagentEuiMsg.Close:
|
||||
Close();
|
||||
break;
|
||||
case AdminAddReagentEuiMsg.DoAdd doAdd:
|
||||
// Double check that user wasn't de-adminned in the mean time...
|
||||
// Or the target was deleted.
|
||||
if (!_adminManager.HasAdminFlag(Player, ReqFlags) || _target.Deleted)
|
||||
{
|
||||
Close();
|
||||
return;
|
||||
}
|
||||
|
||||
_target.TryAddReagent(doAdd.ReagentId, doAdd.Amount, out _);
|
||||
StateDirty();
|
||||
|
||||
if (doAdd.CloseAfter)
|
||||
Close();
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
#nullable enable
|
||||
using System.Threading.Tasks;
|
||||
using Content.Shared.Chemistry;
|
||||
using Content.Shared.GameObjects.Components.Chemistry;
|
||||
using Content.Shared.Interfaces;
|
||||
using Content.Shared.Interfaces.GameObjects.Components;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Chemistry
|
||||
{
|
||||
/// <summary>
|
||||
/// Gives click behavior for transferring to/from other reagent containers.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed class SolutionTransferComponent : Component, IAfterInteract
|
||||
{
|
||||
// Behavior is as such:
|
||||
// If it's a reagent tank, TAKE reagent.
|
||||
// If it's anything else, GIVE reagent.
|
||||
// Of course, only if possible.
|
||||
|
||||
public override string Name => "SolutionTransfer";
|
||||
|
||||
private ReagentUnit _transferAmount;
|
||||
private bool _canReceive;
|
||||
private bool _canSend;
|
||||
|
||||
/// <summary>
|
||||
/// The amount of solution to be transferred from this solution when clicking on other solutions with it.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public ReagentUnit TransferAmount
|
||||
{
|
||||
get => _transferAmount;
|
||||
set => _transferAmount = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Can this entity take reagent from reagent tanks?
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool CanReceive
|
||||
{
|
||||
get => _canReceive;
|
||||
set => _canReceive = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Can this entity give reagent to other reagent containers?
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool CanSend
|
||||
{
|
||||
get => _canSend;
|
||||
set => _canSend = value;
|
||||
}
|
||||
|
||||
public override void ExposeData(ObjectSerializer serializer)
|
||||
{
|
||||
base.ExposeData(serializer);
|
||||
serializer.DataField(ref _transferAmount, "transferAmount", ReagentUnit.New(5));
|
||||
serializer.DataField(ref _canReceive, "canReceive", true);
|
||||
serializer.DataField(ref _canSend, "canSend", true);
|
||||
}
|
||||
|
||||
async Task<bool> IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs)
|
||||
{
|
||||
if (!eventArgs.CanReach || eventArgs.Target == null)
|
||||
return false;
|
||||
|
||||
if (!Owner.TryGetComponent(out ISolutionInteractionsComponent? ownerSolution))
|
||||
return false;
|
||||
|
||||
var target = eventArgs.Target;
|
||||
if (!target.TryGetComponent(out ISolutionInteractionsComponent? targetSolution))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (CanReceive && target.TryGetComponent(out ReagentTankComponent? tank)
|
||||
&& ownerSolution.CanRefill && targetSolution.CanDrain)
|
||||
{
|
||||
var transferred = DoTransfer(targetSolution, ownerSolution, tank.TransferAmount, eventArgs.User);
|
||||
if (transferred > 0)
|
||||
{
|
||||
var toTheBrim = ownerSolution.RefillSpaceAvailable == 0;
|
||||
var msg = toTheBrim
|
||||
? "You fill {0:TheName} to the brim with {1}u from {2:theName}"
|
||||
: "You fill {0:TheName} with {1}u from {2:theName}";
|
||||
|
||||
target.PopupMessage(eventArgs.User, Loc.GetString(msg, Owner, transferred, target));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (CanSend && targetSolution.CanRefill && ownerSolution.CanDrain)
|
||||
{
|
||||
var transferred = DoTransfer(ownerSolution, targetSolution, TransferAmount, eventArgs.User);
|
||||
|
||||
if (transferred > 0)
|
||||
{
|
||||
Owner.PopupMessage(eventArgs.User, Loc.GetString("You transfer {0}u to {1:theName}.",
|
||||
transferred, target));
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <returns>The actual amount transferred.</returns>
|
||||
private static ReagentUnit DoTransfer(
|
||||
ISolutionInteractionsComponent source,
|
||||
ISolutionInteractionsComponent target,
|
||||
ReagentUnit amount,
|
||||
IEntity user)
|
||||
{
|
||||
if (source.DrainAvailable == 0)
|
||||
{
|
||||
source.Owner.PopupMessage(user, Loc.GetString("{0:TheName} is empty!", source.Owner));
|
||||
return ReagentUnit.Zero;
|
||||
}
|
||||
|
||||
if (target.RefillSpaceAvailable == 0)
|
||||
{
|
||||
target.Owner.PopupMessage(user, Loc.GetString("{0:TheName} is full!", target.Owner));
|
||||
return ReagentUnit.Zero;
|
||||
}
|
||||
|
||||
var actualAmount =
|
||||
ReagentUnit.Min(amount, ReagentUnit.Min(source.DrainAvailable, target.RefillSpaceAvailable));
|
||||
|
||||
var solution = source.Drain(actualAmount);
|
||||
target.Refill(solution);
|
||||
|
||||
return actualAmount;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,282 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Content.Server.Administration;
|
||||
using Content.Server.Eui;
|
||||
using Content.Server.GameObjects.Components.GUI;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.Chemistry;
|
||||
using Content.Shared.Eui;
|
||||
using Content.Shared.GameObjects.Components.Chemistry;
|
||||
using Content.Shared.GameObjects.EntitySystems.ActionBlocker;
|
||||
using Content.Shared.GameObjects.Verbs;
|
||||
using Robust.Server.Interfaces.GameObjects;
|
||||
using Robust.Server.Interfaces.Player;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Chemistry
|
||||
{
|
||||
internal abstract class SolutionTransferVerbBase : GlobalVerb
|
||||
{
|
||||
protected static bool GetHeldSolution(
|
||||
IEntity holder,
|
||||
[NotNullWhen(true)]
|
||||
out IEntity? held,
|
||||
[NotNullWhen(true)]
|
||||
out ISolutionInteractionsComponent? heldSolution)
|
||||
{
|
||||
if (!holder.TryGetComponent(out HandsComponent? hands)
|
||||
|| hands.GetActiveHand == null
|
||||
|| !hands.GetActiveHand.Owner.TryGetComponent(out heldSolution))
|
||||
{
|
||||
held = null;
|
||||
heldSolution = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
held = heldSolution.Owner;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Transfers solution from the held container to the target container.
|
||||
/// </summary>
|
||||
[GlobalVerb]
|
||||
internal sealed class SolutionFillTargetVerb : SolutionTransferVerbBase
|
||||
{
|
||||
public override void GetData(IEntity user, IEntity target, VerbData data)
|
||||
{
|
||||
if (!target.TryGetComponent(out ISolutionInteractionsComponent? targetSolution) ||
|
||||
!ActionBlockerSystem.CanInteract(user) ||
|
||||
!GetHeldSolution(user, out var source, out var sourceSolution) ||
|
||||
source != target ||
|
||||
!sourceSolution.CanDrain ||
|
||||
!targetSolution.CanRefill)
|
||||
{
|
||||
data.Visibility = VerbVisibility.Invisible;
|
||||
return;
|
||||
}
|
||||
|
||||
data.Visibility = VerbVisibility.Visible;
|
||||
data.Text = Loc.GetString("Transfer liquid from [{0}] to [{1}].", source.Name, target.Name);
|
||||
}
|
||||
|
||||
public override void Activate(IEntity user, IEntity target)
|
||||
{
|
||||
if (!GetHeldSolution(user, out _, out var handSolutionComp))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!handSolutionComp.CanDrain ||
|
||||
!target.TryGetComponent(out ISolutionInteractionsComponent? targetComp) ||
|
||||
!targetComp.CanRefill)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var transferQuantity = ReagentUnit.Min(
|
||||
targetComp.RefillSpaceAvailable,
|
||||
handSolutionComp.DrainAvailable,
|
||||
ReagentUnit.New(10));
|
||||
|
||||
if (transferQuantity <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var transferSolution = handSolutionComp.Drain(transferQuantity);
|
||||
targetComp.Refill(transferSolution);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Transfers solution from a target container to the held container.
|
||||
/// </summary>
|
||||
[GlobalVerb]
|
||||
internal sealed class SolutionDrainTargetVerb : SolutionTransferVerbBase
|
||||
{
|
||||
public override void GetData(IEntity user, IEntity target, VerbData data)
|
||||
{
|
||||
if (!target.TryGetComponent(out ISolutionInteractionsComponent? sourceSolution) ||
|
||||
!ActionBlockerSystem.CanInteract(user) ||
|
||||
!GetHeldSolution(user, out var held, out var targetSolution) ||
|
||||
!sourceSolution.CanDrain ||
|
||||
!targetSolution.CanRefill)
|
||||
{
|
||||
data.Visibility = VerbVisibility.Invisible;
|
||||
return;
|
||||
}
|
||||
|
||||
data.Visibility = VerbVisibility.Visible;
|
||||
data.Text = Loc.GetString("Transfer liquid from [{0}] to [{1}].", held.Name, target.Name);
|
||||
}
|
||||
|
||||
public override void Activate(IEntity user, IEntity target)
|
||||
{
|
||||
if (!GetHeldSolution(user, out _, out var targetComp))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!targetComp.CanRefill ||
|
||||
!target.TryGetComponent(out ISolutionInteractionsComponent? sourceComp) ||
|
||||
!sourceComp.CanDrain)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var transferQuantity = ReagentUnit.Min(
|
||||
targetComp.RefillSpaceAvailable,
|
||||
sourceComp.DrainAvailable,
|
||||
ReagentUnit.New(10));
|
||||
|
||||
if (transferQuantity <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var transferSolution = sourceComp.Drain(transferQuantity);
|
||||
targetComp.Refill(transferSolution);
|
||||
}
|
||||
}
|
||||
|
||||
[GlobalVerb]
|
||||
internal sealed class AdminAddReagentVerb : GlobalVerb
|
||||
{
|
||||
public override bool RequireInteractionRange => false;
|
||||
public override bool BlockedByContainers => false;
|
||||
|
||||
private const AdminFlags ReqFlags = AdminFlags.Fun;
|
||||
|
||||
private static void OpenAddReagentMenu(IPlayerSession player, IEntity target)
|
||||
{
|
||||
var euiMgr = IoCManager.Resolve<EuiManager>();
|
||||
euiMgr.OpenEui(new AdminAddReagentEui(target), player);
|
||||
}
|
||||
|
||||
public override void GetData(IEntity user, IEntity target, VerbData data)
|
||||
{
|
||||
// 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.
|
||||
if (!target.HasComponent<SolutionContainerComponent>()
|
||||
&& !(target.TryGetComponent(out ISolutionInteractionsComponent? interactions)
|
||||
&& interactions.CanInject))
|
||||
{
|
||||
data.Visibility = VerbVisibility.Invisible;
|
||||
return;
|
||||
}
|
||||
|
||||
data.Text = Loc.GetString("Add Reagent...");
|
||||
data.CategoryData = VerbCategories.Debug;
|
||||
data.Visibility = VerbVisibility.Invisible;
|
||||
|
||||
var adminManager = IoCManager.Resolve<IAdminManager>();
|
||||
|
||||
if (user.TryGetComponent<IActorComponent>(out var player))
|
||||
{
|
||||
if (adminManager.HasAdminFlag(player.playerSession, ReqFlags))
|
||||
{
|
||||
data.Visibility = VerbVisibility.Visible;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void Activate(IEntity user, IEntity target)
|
||||
{
|
||||
var groupController = IoCManager.Resolve<IAdminManager>();
|
||||
if (user.TryGetComponent<IActorComponent>(out var player))
|
||||
{
|
||||
if (groupController.HasAdminFlag(player.playerSession, ReqFlags))
|
||||
OpenAddReagentMenu(player.playerSession, target);
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class AdminAddReagentEui : BaseEui
|
||||
{
|
||||
private readonly IEntity _target;
|
||||
[Dependency] private readonly IAdminManager _adminManager = default!;
|
||||
|
||||
public AdminAddReagentEui(IEntity target)
|
||||
{
|
||||
_target = target;
|
||||
|
||||
IoCManager.InjectDependencies(this);
|
||||
}
|
||||
|
||||
public override void Opened()
|
||||
{
|
||||
StateDirty();
|
||||
}
|
||||
|
||||
public override EuiStateBase GetNewState()
|
||||
{
|
||||
if (_target.TryGetComponent(out SolutionContainerComponent? container))
|
||||
{
|
||||
return new AdminAddReagentEuiState
|
||||
{
|
||||
CurVolume = container.CurrentVolume,
|
||||
MaxVolume = container.MaxVolume
|
||||
};
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
CurVolume = ReagentUnit.Zero,
|
||||
MaxVolume = ReagentUnit.Zero
|
||||
};
|
||||
}
|
||||
|
||||
public override void HandleMessage(EuiMessageBase msg)
|
||||
{
|
||||
switch (msg)
|
||||
{
|
||||
case AdminAddReagentEuiMsg.Close:
|
||||
Close();
|
||||
break;
|
||||
case AdminAddReagentEuiMsg.DoAdd doAdd:
|
||||
// Double check that user wasn't de-adminned in the mean time...
|
||||
// Or the target was deleted.
|
||||
if (!_adminManager.HasAdminFlag(Player, ReqFlags) || _target.Deleted)
|
||||
{
|
||||
Close();
|
||||
return;
|
||||
}
|
||||
|
||||
var id = doAdd.ReagentId;
|
||||
var amount = doAdd.Amount;
|
||||
|
||||
if (_target.TryGetComponent(out SolutionContainerComponent? container))
|
||||
{
|
||||
container.TryAddReagent(id, amount, out _);
|
||||
}
|
||||
else if (_target.TryGetComponent(out ISolutionInteractionsComponent? interactions))
|
||||
{
|
||||
var solution = new Solution(id, amount);
|
||||
interactions.Inject(solution);
|
||||
}
|
||||
|
||||
StateDirty();
|
||||
|
||||
if (doAdd.CloseAfter)
|
||||
Close();
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -109,19 +109,22 @@ namespace Content.Server.GameObjects.Components
|
||||
return false;
|
||||
}
|
||||
|
||||
async Task IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs)
|
||||
async Task<bool> IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs)
|
||||
{
|
||||
if (!eventArgs.InRangeUnobstructed(ignoreInsideBlocker: false, popup: true,
|
||||
collisionMask: Shared.Physics.CollisionGroup.MobImpassable)) return;
|
||||
collisionMask: Shared.Physics.CollisionGroup.MobImpassable))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (Charges <= 0)
|
||||
{
|
||||
eventArgs.User.PopupMessage(Loc.GetString("Not enough left."));
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
var entityManager = IoCManager.Resolve<IServerEntityManager>();
|
||||
|
||||
|
||||
var entity = entityManager.SpawnEntity("CrayonDecal", eventArgs.ClickLocation);
|
||||
if (entity.TryGetComponent(out AppearanceComponent? appearance))
|
||||
{
|
||||
@@ -138,6 +141,7 @@ namespace Content.Server.GameObjects.Components
|
||||
// Decrease "Ammo"
|
||||
Charges--;
|
||||
Dirty();
|
||||
return true;
|
||||
}
|
||||
|
||||
void IDropped.Dropped(DroppedEventArgs eventArgs)
|
||||
|
||||
@@ -107,9 +107,10 @@ namespace Content.Server.GameObjects.Components.Culinary
|
||||
serializer.DataField(ref _breakSound, "breakSound", "/Audio/Items/snap.ogg");
|
||||
}
|
||||
|
||||
async Task IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs)
|
||||
async Task<bool> IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs)
|
||||
{
|
||||
TryUseUtensil(eventArgs.User, eventArgs.Target);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void TryUseUtensil(IEntity user, IEntity? target)
|
||||
|
||||
@@ -62,14 +62,16 @@ namespace Content.Server.GameObjects.Components.Fluids
|
||||
Owner.EnsureComponentWarn(out SolutionContainerComponent _);
|
||||
}
|
||||
|
||||
async Task IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs)
|
||||
async Task<bool> IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs)
|
||||
{
|
||||
if (!Owner.TryGetComponent(out SolutionContainerComponent? contents)) return;
|
||||
if (!eventArgs.InRangeUnobstructed(ignoreInsideBlocker: true, popup: true)) return;
|
||||
if (!Owner.TryGetComponent(out SolutionContainerComponent? contents))
|
||||
return false;
|
||||
if (!eventArgs.InRangeUnobstructed(ignoreInsideBlocker: true, popup: true))
|
||||
return false;
|
||||
|
||||
if (CurrentVolume <= 0)
|
||||
{
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (eventArgs.Target == null)
|
||||
@@ -77,12 +79,12 @@ namespace Content.Server.GameObjects.Components.Fluids
|
||||
// Drop the liquid on the mop on to the ground
|
||||
contents.SplitSolution(CurrentVolume).SpillAt(eventArgs.ClickLocation, "PuddleSmear");
|
||||
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!eventArgs.Target.TryGetComponent(out PuddleComponent? puddleComponent))
|
||||
{
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
// Essentially pickup either:
|
||||
// - _pickupAmount,
|
||||
@@ -101,7 +103,7 @@ namespace Content.Server.GameObjects.Components.Fluids
|
||||
}
|
||||
else
|
||||
{
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -121,12 +123,12 @@ namespace Content.Server.GameObjects.Components.Fluids
|
||||
// Give some visual feedback shit's happening (for anyone who can't hear sound)
|
||||
Owner.PopupMessage(eventArgs.User, Loc.GetString("Swish"));
|
||||
|
||||
if (string.IsNullOrWhiteSpace(_pickupSound))
|
||||
if (!string.IsNullOrWhiteSpace(_pickupSound))
|
||||
{
|
||||
return;
|
||||
EntitySystem.Get<AudioSystem>().PlayFromEntity(_pickupSound, Owner);
|
||||
}
|
||||
|
||||
EntitySystem.Get<AudioSystem>().PlayFromEntity(_pickupSound, Owner);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using Content.Server.GameObjects.Components.Chemistry;
|
||||
using Content.Shared.Chemistry;
|
||||
using Content.Shared.GameObjects.EntitySystems;
|
||||
using Content.Shared.Chemistry;
|
||||
using Content.Shared.GameObjects.Components.Chemistry;
|
||||
using Content.Shared.GameObjects.EntitySystems.ActionBlocker;
|
||||
using Content.Shared.GameObjects.Verbs;
|
||||
using Content.Shared.Interfaces;
|
||||
@@ -24,34 +23,37 @@ namespace Content.Server.GameObjects.Components.Fluids
|
||||
protected override void GetData(IEntity user, SpillableComponent component, VerbData data)
|
||||
{
|
||||
if (!ActionBlockerSystem.CanInteract(user) ||
|
||||
!component.Owner.TryGetComponent(out SolutionContainerComponent solutionComponent) ||
|
||||
!solutionComponent.CanRemoveSolutions)
|
||||
!component.Owner.TryGetComponent(out ISolutionInteractionsComponent solutionComponent) ||
|
||||
!solutionComponent.CanDrain)
|
||||
{
|
||||
data.Visibility = VerbVisibility.Invisible;
|
||||
return;
|
||||
}
|
||||
|
||||
data.Text = Loc.GetString("Spill liquid");
|
||||
data.Visibility = solutionComponent.CurrentVolume > ReagentUnit.Zero ? VerbVisibility.Visible : VerbVisibility.Disabled;
|
||||
data.Visibility = solutionComponent.DrainAvailable > ReagentUnit.Zero
|
||||
? VerbVisibility.Visible
|
||||
: VerbVisibility.Disabled;
|
||||
}
|
||||
|
||||
protected override void Activate(IEntity user, SpillableComponent component)
|
||||
{
|
||||
if (component.Owner.TryGetComponent<SolutionContainerComponent>(out var solutionComponent))
|
||||
if (component.Owner.TryGetComponent<ISolutionInteractionsComponent>(out var solutionComponent))
|
||||
{
|
||||
if (!solutionComponent.CanRemoveSolutions)
|
||||
if (!solutionComponent.CanDrain)
|
||||
{
|
||||
user.PopupMessage(user, Loc.GetString("You can't pour anything from {0:theName}!", component.Owner));
|
||||
user.PopupMessage(user,
|
||||
Loc.GetString("You can't pour anything from {0:theName}!", component.Owner));
|
||||
}
|
||||
|
||||
if (solutionComponent.CurrentVolume.Float() <= 0)
|
||||
if (solutionComponent.DrainAvailable <= 0)
|
||||
{
|
||||
user.PopupMessage(user, Loc.GetString("{0:theName} is empty!", component.Owner));
|
||||
}
|
||||
|
||||
// Need this as when we split the component's owner may be deleted
|
||||
var entityLocation = component.Owner.Transform.Coordinates;
|
||||
var solution = solutionComponent.SplitSolution(solutionComponent.CurrentVolume);
|
||||
var solution = solutionComponent.Drain(solutionComponent.DrainAvailable);
|
||||
solution.SpillAt(entityLocation, "PuddleSmear");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,34 +100,34 @@ namespace Content.Server.GameObjects.Components.Fluids
|
||||
serializer.DataField(ref _safety, "safety", true);
|
||||
}
|
||||
|
||||
async Task IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs)
|
||||
async Task<bool> IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs)
|
||||
{
|
||||
if (!ActionBlockerSystem.CanInteract(eventArgs.User))
|
||||
return;
|
||||
return false;
|
||||
|
||||
if (_hasSafety && _safety)
|
||||
{
|
||||
Owner.PopupMessage(eventArgs.User, Loc.GetString("Its safety is on!"));
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (CurrentVolume <= 0)
|
||||
{
|
||||
Owner.PopupMessage(eventArgs.User, Loc.GetString("It's empty!"));
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
var curTime = _gameTiming.CurTime;
|
||||
|
||||
if(curTime < _cooldownEnd)
|
||||
return;
|
||||
return true;
|
||||
|
||||
var playerPos = eventArgs.User.Transform.Coordinates;
|
||||
if (eventArgs.ClickLocation.GetGridId(_serverEntityManager) != playerPos.GetGridId(_serverEntityManager))
|
||||
return;
|
||||
return true;
|
||||
|
||||
if (!Owner.TryGetComponent(out SolutionContainerComponent contents))
|
||||
return;
|
||||
return true;
|
||||
|
||||
var direction = (eventArgs.ClickLocation.Position - playerPos.Position).Normalized;
|
||||
var threeQuarters = direction * 0.75f;
|
||||
@@ -183,6 +183,8 @@ namespace Content.Server.GameObjects.Components.Fluids
|
||||
cooldown.CooldownStart = _lastUseTime;
|
||||
cooldown.CooldownEnd = _cooldownEnd;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool UseEntity(UseEntityEventArgs eventArgs)
|
||||
|
||||
@@ -21,9 +21,10 @@ namespace Content.Server.GameObjects.Components.Interactable
|
||||
public override string Name => "TilePrying";
|
||||
private bool _toolComponentNeeded = true;
|
||||
|
||||
public async Task AfterInteract(AfterInteractEventArgs eventArgs)
|
||||
public async Task<bool> AfterInteract(AfterInteractEventArgs eventArgs)
|
||||
{
|
||||
TryPryTile(eventArgs.User, eventArgs.ClickLocation);
|
||||
return true;
|
||||
}
|
||||
|
||||
public override void ExposeData(ObjectSerializer serializer)
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.Atmos;
|
||||
using Content.Server.Explosions;
|
||||
using Content.Server.GameObjects.Components.Chemistry;
|
||||
using Content.Server.GameObjects.Components.Items.Storage;
|
||||
using Content.Server.GameObjects.EntitySystems;
|
||||
@@ -10,12 +11,15 @@ using Content.Server.Interfaces.GameObjects;
|
||||
using Content.Server.Utility;
|
||||
using Content.Shared.Chemistry;
|
||||
using Content.Shared.GameObjects;
|
||||
using Content.Shared.GameObjects.Components.Chemistry;
|
||||
using Content.Shared.GameObjects.Components.Interactable;
|
||||
using Content.Shared.GameObjects.EntitySystems;
|
||||
using Content.Shared.Interfaces;
|
||||
using Content.Shared.Interfaces.GameObjects.Components;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Server.GameObjects.EntitySystems;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameObjects.Systems;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
@@ -29,7 +33,7 @@ namespace Content.Server.GameObjects.Components.Interactable
|
||||
[ComponentReference(typeof(ToolComponent))]
|
||||
[ComponentReference(typeof(IToolComponent))]
|
||||
[ComponentReference(typeof(IHotItem))]
|
||||
public class WelderComponent : ToolComponent, IExamine, IUse, ISuicideAct, ISolutionChange, IHotItem
|
||||
public class WelderComponent : ToolComponent, IExamine, IUse, ISuicideAct, ISolutionChange, IHotItem, IAfterInteract
|
||||
{
|
||||
[Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;
|
||||
|
||||
@@ -293,5 +297,38 @@ namespace Content.Server.GameObjects.Components.Interactable
|
||||
}
|
||||
|
||||
|
||||
async Task<bool> IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs)
|
||||
{
|
||||
if (eventArgs.Target == null || !eventArgs.CanReach)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (eventArgs.Target.TryGetComponent(out ReagentTankComponent? tank)
|
||||
&& tank.TankType == ReagentTankType.Fuel
|
||||
&& eventArgs.Target.TryGetComponent(out ISolutionInteractionsComponent? targetSolution)
|
||||
&& targetSolution.CanDrain
|
||||
&& _solutionComponent != null)
|
||||
{
|
||||
if (WelderLit)
|
||||
{
|
||||
// Oh no no
|
||||
eventArgs.Target.SpawnExplosion();
|
||||
return true;
|
||||
}
|
||||
|
||||
var trans = ReagentUnit.Min(_solutionComponent.EmptyVolume, targetSolution.DrainAvailable);
|
||||
if (trans > 0)
|
||||
{
|
||||
var drained = targetSolution.Drain(trans);
|
||||
_solutionComponent.TryAddSolution(drained);
|
||||
|
||||
EntitySystem.Get<AudioSystem>().PlayFromEntity("/Audio/Effects/refill.ogg", Owner);
|
||||
eventArgs.Target.PopupMessage(eventArgs.User, Loc.GetString("Welder refueled"));
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,52 @@
|
||||
using Robust.Shared.GameObjects;
|
||||
using System.Threading.Tasks;
|
||||
using Content.Server.GameObjects.Components.Chemistry;
|
||||
using Content.Shared.Chemistry;
|
||||
using Content.Shared.GameObjects.Components.Chemistry;
|
||||
using Content.Shared.Interfaces;
|
||||
using Content.Shared.Interfaces.GameObjects.Components;
|
||||
using Robust.Server.GameObjects.EntitySystems;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameObjects.Systems;
|
||||
using Robust.Shared.Localization;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace Content.Server.GameObjects.Components.Items
|
||||
{
|
||||
[RegisterComponent]
|
||||
public class FireExtinguisherComponent : Component
|
||||
public class FireExtinguisherComponent : Component, IAfterInteract
|
||||
{
|
||||
public override string Name => "FireExtinguisher";
|
||||
|
||||
// Higher priority than sprays.
|
||||
int IAfterInteract.Priority => 1;
|
||||
|
||||
async Task<bool> IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs)
|
||||
{
|
||||
if (eventArgs.Target == null || !eventArgs.CanReach)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (eventArgs.Target.TryGetComponent(out ReagentTankComponent? tank)
|
||||
&& eventArgs.Target.TryGetComponent(out ISolutionInteractionsComponent? targetSolution)
|
||||
&& targetSolution.CanDrain
|
||||
&& Owner.TryGetComponent(out SolutionContainerComponent? container))
|
||||
{
|
||||
var trans = ReagentUnit.Min(container.EmptyVolume, targetSolution.DrainAvailable);
|
||||
if (trans > 0)
|
||||
{
|
||||
var drained = targetSolution.Drain(trans);
|
||||
container.TryAddSolution(drained);
|
||||
|
||||
EntitySystem.Get<AudioSystem>().PlayFromEntity("/Audio/Effects/refill.ogg", Owner);
|
||||
eventArgs.Target.PopupMessage(eventArgs.User, Loc.GetString("{0:TheName} is now refilled", Owner));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,10 +56,14 @@ namespace Content.Server.GameObjects.Components.Items
|
||||
EntitySystem.Get<AudioSystem>().PlayAtCoords("/Audio/Items/genhit.ogg", location, AudioHelpers.WithVariation(0.125f));
|
||||
}
|
||||
|
||||
public async Task AfterInteract(AfterInteractEventArgs eventArgs)
|
||||
public async Task<bool> AfterInteract(AfterInteractEventArgs eventArgs)
|
||||
{
|
||||
if (!eventArgs.InRangeUnobstructed(ignoreInsideBlocker: true, popup: true)) return;
|
||||
if (!Owner.TryGetComponent(out StackComponent stack)) return;
|
||||
if (!eventArgs.InRangeUnobstructed(ignoreInsideBlocker: true, popup: true))
|
||||
return true;
|
||||
|
||||
if (!Owner.TryGetComponent(out StackComponent stack))
|
||||
return true;
|
||||
|
||||
var mapManager = IoCManager.Resolve<IMapManager>();
|
||||
|
||||
var location = eventArgs.ClickLocation.AlignWithClosestGridTile();
|
||||
@@ -88,10 +92,9 @@ namespace Content.Server.GameObjects.Components.Items
|
||||
PlaceAt(mapGrid, location, _tileDefinitionManager[_outputTiles[0]].TileId, mapGrid.TileSize / 2f);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,17 +32,17 @@ namespace Content.Server.GameObjects.Components.Items.RCD
|
||||
message.AddMarkup(Loc.GetString("It holds {0} charges.", refillAmmo));
|
||||
}
|
||||
|
||||
public async Task AfterInteract(AfterInteractEventArgs eventArgs)
|
||||
public async Task<bool> AfterInteract(AfterInteractEventArgs eventArgs)
|
||||
{
|
||||
if (eventArgs.Target == null || !eventArgs.Target.TryGetComponent(out RCDComponent rcdComponent) || !eventArgs.User.TryGetComponent(out IHandsComponent hands))
|
||||
{
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (rcdComponent.maxAmmo - rcdComponent._ammo < refillAmmo)
|
||||
{
|
||||
rcdComponent.Owner.PopupMessage(eventArgs.User, Loc.GetString("The RCD is full!"));
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
rcdComponent._ammo = Math.Min(rcdComponent.maxAmmo, rcdComponent._ammo + refillAmmo);
|
||||
@@ -51,6 +51,7 @@ namespace Content.Server.GameObjects.Components.Items.RCD
|
||||
//Deleting a held item causes a lot of errors
|
||||
hands.Drop(Owner, false);
|
||||
Owner.Delete();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,7 +94,7 @@ namespace Content.Server.GameObjects.Components.Items.RCD
|
||||
message.AddMarkup(Loc.GetString("It's currently on {0} mode, and holds {1} charges.",_mode.ToString(), _ammo));
|
||||
}
|
||||
|
||||
public async Task AfterInteract(AfterInteractEventArgs eventArgs)
|
||||
public async Task<bool> AfterInteract(AfterInteractEventArgs eventArgs)
|
||||
{
|
||||
//No changing mode mid-RCD
|
||||
var startingMode = _mode;
|
||||
@@ -116,7 +116,7 @@ namespace Content.Server.GameObjects.Components.Items.RCD
|
||||
var result = await doAfterSystem.DoAfter(doAfterEventArgs);
|
||||
if (result == DoAfterStatus.Cancelled)
|
||||
{
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (_mode)
|
||||
@@ -146,12 +146,12 @@ namespace Content.Server.GameObjects.Components.Items.RCD
|
||||
airlock.Transform.LocalRotation = Owner.Transform.LocalRotation; //Now apply icon smoothing.
|
||||
break;
|
||||
default:
|
||||
return; //I don't know why this would happen, but sure I guess. Get out of here invalid state!
|
||||
return true; //I don't know why this would happen, but sure I guess. Get out of here invalid state!
|
||||
}
|
||||
|
||||
_entitySystemManager.GetEntitySystem<AudioSystem>().PlayFromEntity("/Audio/Items/deconstruct.ogg", Owner);
|
||||
_ammo--;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool IsRCDStillValid(AfterInteractEventArgs eventArgs, IMapGrid mapGrid, TileRef tile, Vector2i snapPos, RcdMode startingMode)
|
||||
|
||||
@@ -13,6 +13,7 @@ using Content.Server.Utility;
|
||||
using Content.Shared.Chemistry;
|
||||
using Content.Shared.GameObjects.Components.Body;
|
||||
using Content.Shared.GameObjects.Components.Body.Part;
|
||||
using Content.Shared.GameObjects.Components.Chemistry;
|
||||
using Content.Shared.GameObjects.Components.Power;
|
||||
using Content.Shared.GameObjects.EntitySystems;
|
||||
using Content.Shared.Interfaces;
|
||||
@@ -213,10 +214,10 @@ namespace Content.Server.GameObjects.Components.Kitchen
|
||||
return false;
|
||||
}
|
||||
|
||||
if (itemEntity.TryGetComponent<PourableComponent>(out var attackPourable))
|
||||
if (itemEntity.TryGetComponent<SolutionTransferComponent>(out var attackPourable))
|
||||
{
|
||||
if (!itemEntity.TryGetComponent<SolutionContainerComponent>(out var attackSolution)
|
||||
|| !attackSolution.CanRemoveSolutions)
|
||||
if (!itemEntity.TryGetComponent<ISolutionInteractionsComponent>(out var attackSolution)
|
||||
|| !attackSolution.CanDrain)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -235,7 +236,7 @@ namespace Content.Server.GameObjects.Components.Kitchen
|
||||
}
|
||||
|
||||
//Move units from attackSolution to targetSolution
|
||||
var removedSolution = attackSolution.SplitSolution(realTransferAmount);
|
||||
var removedSolution = attackSolution.Drain(realTransferAmount);
|
||||
if (!solution.TryAddSolution(removedSolution))
|
||||
{
|
||||
return false;
|
||||
|
||||
@@ -26,39 +26,41 @@ namespace Content.Server.GameObjects.Components.Medical
|
||||
serializer.DataField(this, h => h.Heal, "heal", new Dictionary<DamageType, int>());
|
||||
}
|
||||
|
||||
public async Task AfterInteract(AfterInteractEventArgs eventArgs)
|
||||
public async Task<bool> AfterInteract(AfterInteractEventArgs eventArgs)
|
||||
{
|
||||
if (eventArgs.Target == null)
|
||||
{
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!eventArgs.Target.TryGetComponent(out IDamageableComponent damageable))
|
||||
{
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!ActionBlockerSystem.CanInteract(eventArgs.User))
|
||||
{
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (eventArgs.User != eventArgs.Target &&
|
||||
!eventArgs.InRangeUnobstructed(ignoreInsideBlocker: true, popup: true))
|
||||
{
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (Owner.TryGetComponent(out StackComponent stack) &&
|
||||
!stack.Use(1))
|
||||
{
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
foreach (var (type, amount) in Heal)
|
||||
{
|
||||
damageable.ChangeDamage(type, -amount, true);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,11 +3,9 @@ using System.Threading.Tasks;
|
||||
using Content.Server.GameObjects.Components.Body.Behavior;
|
||||
using Content.Server.GameObjects.Components.Chemistry;
|
||||
using Content.Server.GameObjects.Components.Fluids;
|
||||
using Content.Server.GameObjects.EntitySystems;
|
||||
using Content.Shared.Audio;
|
||||
using Content.Shared.Chemistry;
|
||||
using Content.Shared.GameObjects.Components.Body;
|
||||
using Content.Shared.GameObjects.Components.Body.Mechanism;
|
||||
using Content.Shared.GameObjects.Components.Nutrition;
|
||||
using Content.Shared.GameObjects.EntitySystems;
|
||||
using Content.Shared.Interfaces;
|
||||
@@ -75,7 +73,7 @@ namespace Content.Server.GameObjects.Components.Nutrition
|
||||
_contents = Owner.AddComponent<SolutionContainerComponent>();
|
||||
}
|
||||
|
||||
_contents.Capabilities = SolutionContainerCaps.AddTo | SolutionContainerCaps.RemoveFrom;
|
||||
_contents.Capabilities = SolutionContainerCaps.Refillable | SolutionContainerCaps.Drainable;
|
||||
Opened = _defaultToOpened;
|
||||
UpdateAppearance();
|
||||
}
|
||||
@@ -113,9 +111,10 @@ namespace Content.Server.GameObjects.Components.Nutrition
|
||||
}
|
||||
|
||||
//Force feeding a drink to someone.
|
||||
async Task IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs)
|
||||
async Task<bool> IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs)
|
||||
{
|
||||
TryUseDrink(eventArgs.Target, forced: true);
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Examine(FormattedMessage message, bool inDetailsRange)
|
||||
@@ -131,7 +130,7 @@ namespace Content.Server.GameObjects.Components.Nutrition
|
||||
|
||||
private bool TryUseDrink(IEntity target, bool forced = false)
|
||||
{
|
||||
if (target == null || !_contents.CanRemoveSolutions)
|
||||
if (target == null || !_contents.CanDrain)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -100,14 +100,15 @@ namespace Content.Server.GameObjects.Components.Nutrition
|
||||
}
|
||||
|
||||
// Feeding someone else
|
||||
async Task IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs)
|
||||
async Task<bool> IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs)
|
||||
{
|
||||
if (eventArgs.Target == null)
|
||||
{
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
TryUseFood(eventArgs.User, eventArgs.Target);
|
||||
return true;
|
||||
}
|
||||
|
||||
public virtual bool TryUseFood(IEntity? user, IEntity? target, UtensilComponent? utensilUsed = null)
|
||||
|
||||
@@ -23,7 +23,7 @@ using Robust.Shared.ViewVariables;
|
||||
namespace Content.Server.GameObjects.Components.Portal
|
||||
{
|
||||
[RegisterComponent]
|
||||
public class TeleporterComponent : Component, IAfterInteract
|
||||
public class TeleporterComponent : Component, IAfterInteract
|
||||
{
|
||||
[Dependency] private readonly IServerEntityManager _serverEntityManager = default!;
|
||||
[Dependency] private readonly IRobustRandom _spreadRandom = default!;
|
||||
@@ -78,7 +78,7 @@ namespace Content.Server.GameObjects.Components.Portal
|
||||
_state = newState;
|
||||
}
|
||||
|
||||
async Task IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs)
|
||||
async Task<bool> IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs)
|
||||
{
|
||||
if (_teleporterType == TeleporterType.Directed)
|
||||
{
|
||||
@@ -89,6 +89,8 @@ namespace Content.Server.GameObjects.Components.Portal
|
||||
{
|
||||
TryRandomTeleport(eventArgs.User);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void TryDirectedTeleport(IEntity user, MapCoordinates mapCoords)
|
||||
|
||||
@@ -34,28 +34,29 @@ namespace Content.Server.GameObjects.Components.Power
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task AfterInteract(AfterInteractEventArgs eventArgs)
|
||||
public async Task<bool> AfterInteract(AfterInteractEventArgs eventArgs)
|
||||
{
|
||||
if (_wirePrototypeID == null)
|
||||
return;
|
||||
|
||||
if (!eventArgs.InRangeUnobstructed(ignoreInsideBlocker: true, popup: true)) return;
|
||||
return true;
|
||||
if (!eventArgs.InRangeUnobstructed(ignoreInsideBlocker: true, popup: true))
|
||||
return true;
|
||||
if(!_mapManager.TryGetGrid(eventArgs.ClickLocation.GetGridId(Owner.EntityManager), out var grid))
|
||||
return;
|
||||
return true;
|
||||
var snapPos = grid.SnapGridCellFor(eventArgs.ClickLocation, SnapGridOffset.Center);
|
||||
var snapCell = grid.GetSnapGridCell(snapPos, SnapGridOffset.Center);
|
||||
if(grid.GetTileRef(snapPos).Tile.IsEmpty)
|
||||
return;
|
||||
return true;
|
||||
foreach (var snapComp in snapCell)
|
||||
{
|
||||
if (snapComp.Owner.TryGetComponent<WireComponent>(out var wire) && wire.WireType == _blockingWireType)
|
||||
{
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (Owner.TryGetComponent<StackComponent>(out var stack) && !stack.Use(1))
|
||||
return;
|
||||
return true;
|
||||
Owner.EntityManager.SpawnEntity(_wirePrototypeID, grid.GridTileToLocal(snapPos));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,11 +146,11 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Ammunition
|
||||
return entity;
|
||||
}
|
||||
|
||||
async Task IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs)
|
||||
async Task<bool> IAfterInteract.AfterInteract(AfterInteractEventArgs eventArgs)
|
||||
{
|
||||
if (eventArgs.Target == null)
|
||||
{
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
// This area is dirty but not sure of an easier way to do it besides add an interface or somethin
|
||||
@@ -203,6 +203,8 @@ namespace Content.Server.GameObjects.Components.Weapon.Ranged.Ammunition
|
||||
{
|
||||
UpdateAppearance();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
async Task<bool> IInteractUsing.InteractUsing(InteractUsingEventArgs eventArgs)
|
||||
|
||||
@@ -416,13 +416,8 @@ namespace Content.Server.GameObjects.EntitySystems.Click
|
||||
return;
|
||||
}
|
||||
|
||||
var afterInteracts = weapon.GetAllComponents<IAfterInteract>().ToList();
|
||||
var afterInteractEventArgs = new AfterInteractEventArgs(user, clickLocation, null, canReach);
|
||||
|
||||
foreach (var afterInteract in afterInteracts)
|
||||
{
|
||||
await afterInteract.AfterInteract(afterInteractEventArgs);
|
||||
}
|
||||
await DoAfterInteract(weapon, afterInteractEventArgs);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -465,13 +460,9 @@ namespace Content.Server.GameObjects.EntitySystems.Click
|
||||
}
|
||||
|
||||
// If we aren't directly attacking the nearby object, lets see if our item has an after attack we can do
|
||||
var afterAttacks = weapon.GetAllComponents<IAfterInteract>().ToList();
|
||||
var afterAttackEventArgs = new AfterInteractEventArgs(user, clickLocation, attacked, canReach: true);
|
||||
|
||||
foreach (var afterAttack in afterAttacks)
|
||||
{
|
||||
await afterAttack.AfterInteract(afterAttackEventArgs);
|
||||
}
|
||||
await DoAfterInteract(weapon, afterAttackEventArgs);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -835,13 +826,21 @@ namespace Content.Server.GameObjects.EntitySystems.Click
|
||||
if (afterAtkMsg.Handled)
|
||||
return;
|
||||
|
||||
var afterAttacks = weapon.GetAllComponents<IAfterInteract>().ToList();
|
||||
// See if we have a ranged attack interaction
|
||||
var afterAttackEventArgs = new AfterInteractEventArgs(user, clickLocation, attacked, canReach: false);
|
||||
await DoAfterInteract(weapon, afterAttackEventArgs);
|
||||
}
|
||||
|
||||
private static async Task DoAfterInteract(IEntity weapon, AfterInteractEventArgs afterAttackEventArgs)
|
||||
{
|
||||
var afterAttacks = weapon.GetAllComponents<IAfterInteract>().OrderByDescending(x => x.Priority).ToList();
|
||||
|
||||
//See if we have a ranged attack interaction
|
||||
foreach (var afterAttack in afterAttacks)
|
||||
{
|
||||
await afterAttack.AfterInteract(afterAttackEventArgs);
|
||||
if (await afterAttack.AfterInteract(afterAttackEventArgs))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
BIN
Resources/Audio/Effects/refill.ogg
Normal file
BIN
Resources/Audio/Effects/refill.ogg
Normal file
Binary file not shown.
@@ -534,8 +534,6 @@ entities:
|
||||
pos: -15.694785,24.608267
|
||||
rot: -1.5707963267948966 rad
|
||||
type: Transform
|
||||
- caps: AddTo, RemoveFrom
|
||||
type: SolutionContainer
|
||||
- uid: 60
|
||||
type: DisgustingSweptSoup
|
||||
components:
|
||||
@@ -652,8 +650,6 @@ entities:
|
||||
pos: -3.470539,16.956116
|
||||
rot: -1.5707963267948966 rad
|
||||
type: Transform
|
||||
- caps: AddTo, RemoveFrom
|
||||
type: SolutionContainer
|
||||
- uid: 74
|
||||
type: Carpet
|
||||
components:
|
||||
@@ -703,8 +699,6 @@ entities:
|
||||
pos: 8.439846,26.712742
|
||||
rot: 1.5707963267948966 rad
|
||||
type: Transform
|
||||
- caps: AddTo, RemoveFrom
|
||||
type: SolutionContainer
|
||||
- uid: 81
|
||||
type: Table
|
||||
components:
|
||||
@@ -929,8 +923,6 @@ entities:
|
||||
- parent: 853
|
||||
pos: 8.661116,25.513401
|
||||
type: Transform
|
||||
- caps: AddTo, RemoveFrom
|
||||
type: SolutionContainer
|
||||
- uid: 114
|
||||
type: BoxDonkpocket
|
||||
components:
|
||||
@@ -32236,8 +32228,6 @@ entities:
|
||||
pos: -8.476567,-17.420076
|
||||
rot: -1.5707963267948966 rad
|
||||
type: Transform
|
||||
- caps: AddTo, RemoveFrom
|
||||
type: SolutionContainer
|
||||
- uid: 2443
|
||||
type: TableWood
|
||||
components:
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
- type: InteractionOutline
|
||||
- type: SolutionContainer
|
||||
maxVol: 100
|
||||
caps: AddTo
|
||||
caps: Refillable
|
||||
- type: Appearance
|
||||
visuals:
|
||||
- type: MicrowaveVisualizer
|
||||
|
||||
@@ -38,8 +38,7 @@
|
||||
drawWarnings: false
|
||||
- type: SolutionContainer
|
||||
maxVol: 200
|
||||
caps: AddTo
|
||||
- type: Pourable
|
||||
caps: Refillable
|
||||
- type: SnapGrid
|
||||
offset: Center
|
||||
- type: Appearance
|
||||
|
||||
@@ -33,9 +33,9 @@
|
||||
acts: ["Destruction"]
|
||||
- type: SolutionContainer
|
||||
maxVol: 1500
|
||||
caps: RemoveFrom
|
||||
- type: Pourable
|
||||
transferAmount: 100.0
|
||||
caps: Drainable
|
||||
- type: ReagentTank
|
||||
|
||||
placement:
|
||||
snap:
|
||||
- Wall
|
||||
|
||||
@@ -17,9 +17,7 @@
|
||||
reagents:
|
||||
- ReagentId: chem.WeldingFuel
|
||||
Quantity: 1500
|
||||
- type: DamageOnToolInteract
|
||||
damage: 200
|
||||
tools:
|
||||
- Welding
|
||||
- type: Anchorable
|
||||
- type: Pullable
|
||||
- type: ReagentTank
|
||||
tankType: Fuel
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
# Organs
|
||||
- type: SolutionContainer
|
||||
maxVol: 250
|
||||
caps: AddTo, RemoveFrom
|
||||
caps: Injectable, Drawable
|
||||
- type: Bloodstream
|
||||
max_volume: 100
|
||||
# StatusEffects
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# TODO: Find remaining cans and move to drinks_cans
|
||||
# TODO: Find remaining cans and move to drinks_cans
|
||||
# TODO: Find empty containers (e.g. mug, pitcher) and move to their own yml
|
||||
# TODO: Move bottles to their own yml
|
||||
- type: entity
|
||||
@@ -8,7 +8,7 @@
|
||||
components:
|
||||
- type: SolutionContainer
|
||||
maxVol: 50
|
||||
- type: Pourable
|
||||
- type: SolutionTransfer
|
||||
transferAmount: 5
|
||||
- type: Drink
|
||||
- type: Sprite
|
||||
@@ -55,7 +55,7 @@
|
||||
- type: SolutionContainer
|
||||
fillingState: glass
|
||||
maxVol: 50
|
||||
- type: Pourable
|
||||
- type: SolutionTransfer
|
||||
transferAmount: 5
|
||||
- type: TransformableContainer
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
openSounds: bottleOpenSounds
|
||||
- type: SolutionContainer
|
||||
maxVol: 100
|
||||
- type: Pourable
|
||||
- type: SolutionTransfer
|
||||
transferAmount: 5
|
||||
- type: Sprite
|
||||
state: icon
|
||||
|
||||
@@ -8,12 +8,12 @@
|
||||
pressurized: true
|
||||
- type: SolutionContainer
|
||||
maxVol: 20
|
||||
caps: AddTo, RemoveFrom
|
||||
caps: None
|
||||
contents:
|
||||
reagents:
|
||||
- ReagentId: chem.Cola
|
||||
Quantity: 20
|
||||
- type: Pourable
|
||||
- type: SolutionTransfer
|
||||
transferAmount: 5
|
||||
- type: Sprite
|
||||
state: icon
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
components:
|
||||
- type: SolutionContainer
|
||||
maxVol: 20
|
||||
- type: Pourable
|
||||
- type: SolutionTransfer
|
||||
transferAmount: 5
|
||||
- type: Drink
|
||||
isOpen: true
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
reagents:
|
||||
- ReagentId: chem.Flour
|
||||
Quantity: 50
|
||||
- type: Pourable
|
||||
- type: SolutionTransfer
|
||||
transferAmount: 5
|
||||
- type: Drink
|
||||
- type: Sprite
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
- type: SolutionContainer
|
||||
maxVol: 10
|
||||
- type: Pourable
|
||||
- type: SolutionTransfer
|
||||
transferAmount: 5
|
||||
- type: Drink
|
||||
isOpen: true
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
size: 10
|
||||
- type: SolutionContainer
|
||||
maxVol: 100
|
||||
caps: AddTo, RemoveFrom
|
||||
caps: Refillable, Drainable
|
||||
contents:
|
||||
reagents:
|
||||
- ReagentId: chem.Water
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
netsync: false
|
||||
- type: SolutionContainer
|
||||
maxVol: 5
|
||||
caps: AddTo, RemoveFrom
|
||||
caps: Injectable, Drawable
|
||||
|
||||
|
||||
- type: entity
|
||||
|
||||
@@ -13,8 +13,8 @@
|
||||
- type: SolutionContainer
|
||||
fillingState: beaker
|
||||
maxVol: 50
|
||||
caps: CanExamine, AddTo, RemoveFrom, FitsInDispenser # can add and remove solutions and fits in the chemmaster.
|
||||
- type: Pourable
|
||||
caps: OpenContainer, FitsInDispenser # can add and remove solutions and fits in the chemmaster.
|
||||
- type: SolutionTransfer
|
||||
transferAmount: 5.0
|
||||
- type: Spillable
|
||||
- type: GlassBeaker
|
||||
@@ -34,8 +34,8 @@
|
||||
- type: SolutionContainer
|
||||
fillingState: beakerlarge
|
||||
maxVol: 100
|
||||
caps: CanExamine, AddTo, RemoveFrom, FitsInDispenser
|
||||
- type: Pourable
|
||||
caps: OpenContainer, FitsInDispenser
|
||||
- type: SolutionTransfer
|
||||
transferAmount: 5.0
|
||||
- type: Spillable
|
||||
- type: GlassBeaker
|
||||
@@ -53,7 +53,7 @@
|
||||
fillingState: dropper
|
||||
fillingSteps: 2
|
||||
maxVol: 5
|
||||
- type: Pourable
|
||||
- type: SolutionTransfer
|
||||
transferAmount: 5.0
|
||||
- type: Spillable
|
||||
|
||||
@@ -85,7 +85,7 @@
|
||||
- type: Drink
|
||||
- type: SolutionContainer
|
||||
maxVol: 30
|
||||
- type: Pourable
|
||||
- type: SolutionTransfer
|
||||
transferAmount: 5
|
||||
- type: Spillable
|
||||
|
||||
|
||||
@@ -198,8 +198,8 @@
|
||||
state: cleaner
|
||||
- type: SolutionContainer
|
||||
maxVol: 100
|
||||
caps: AddTo, RemoveFrom
|
||||
- type: Pourable
|
||||
caps: Refillable, Drainable
|
||||
- type: SolutionTransfer
|
||||
transferAmount: 5.0
|
||||
- type: Spillable
|
||||
- type: ItemCooldown
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
- ReagentId: chem.Nutriment
|
||||
Quantity: 10
|
||||
maxVol: 11 # needs room for water
|
||||
caps: AddTo
|
||||
caps: Refillable
|
||||
- type: Sprite
|
||||
sprite: Objects/Consumable/Food/monkeycube.rsi
|
||||
- type: Rehydratable
|
||||
@@ -28,6 +28,6 @@
|
||||
- ReagentId: chem.Nutriment
|
||||
Quantity: 10
|
||||
maxVol: 11 # needs room for water
|
||||
caps: AddTo
|
||||
caps: Refillable
|
||||
- type: Rehydratable
|
||||
target: CarpMob_Content
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
- type: entity
|
||||
- type: entity
|
||||
name: mini hoe
|
||||
parent: BaseItem
|
||||
id: MiniHoe
|
||||
@@ -24,7 +24,7 @@
|
||||
state: plantbgone
|
||||
- type: SolutionContainer
|
||||
maxVol: 100
|
||||
caps: RemoveFrom
|
||||
caps: Drainable
|
||||
contents:
|
||||
reagents:
|
||||
- ReagentId: chem.PlantBGone
|
||||
@@ -42,12 +42,12 @@
|
||||
state: weedspray
|
||||
- type: SolutionContainer
|
||||
maxVol: 50
|
||||
caps: RemoveFrom
|
||||
caps: Drainable
|
||||
contents:
|
||||
reagents:
|
||||
- ReagentId: chem.WeedKiller
|
||||
Quantity: 50
|
||||
- type: Pourable
|
||||
- type: SolutionTransfer
|
||||
transferAmount: 1.0
|
||||
- type: Spillable
|
||||
- type: ItemCooldown
|
||||
@@ -65,7 +65,7 @@
|
||||
state: pestspray
|
||||
- type: SolutionContainer
|
||||
maxVol: 50
|
||||
caps: RemoveFrom
|
||||
caps: Drainable
|
||||
contents:
|
||||
reagents:
|
||||
- ReagentId: chem.PestKiller
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
- type: entity
|
||||
- type: entity
|
||||
name: haycutters
|
||||
parent: BaseItem
|
||||
id: Haycutters
|
||||
@@ -114,7 +114,7 @@
|
||||
- type: ItemStatus
|
||||
- type: SolutionContainer
|
||||
maxVol: 50
|
||||
caps: AddTo
|
||||
caps: Refillable
|
||||
contents:
|
||||
reagents:
|
||||
- ReagentId: chem.WeldingFuel
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
- type: ItemStatus
|
||||
- type: SolutionContainer
|
||||
maxVol: 100
|
||||
caps: AddTo
|
||||
caps: Refillable
|
||||
contents:
|
||||
reagents:
|
||||
- ReagentId: chem.WeldingFuel
|
||||
@@ -44,7 +44,7 @@
|
||||
sprite: Objects/Tools/welder_experimental.rsi
|
||||
- type: SolutionContainer
|
||||
maxVol: 1000
|
||||
caps: AddTo
|
||||
caps: Refillable
|
||||
contents:
|
||||
reagents:
|
||||
- ReagentId: chem.WeldingFuel
|
||||
@@ -66,7 +66,7 @@
|
||||
sprite: Objects/Tools/welder_mini.rsi
|
||||
- type: SolutionContainer
|
||||
maxVol: 25
|
||||
caps: AddTo
|
||||
caps: Refillable
|
||||
contents:
|
||||
reagents:
|
||||
- ReagentId: chem.WeldingFuel
|
||||
|
||||
@@ -102,5 +102,5 @@
|
||||
- type: ChemicalAmmo
|
||||
- type: SolutionContainer
|
||||
maxVol: 15
|
||||
caps: AddTo, RemoveFrom
|
||||
- type: Pourable
|
||||
caps: Refillable, Drainable
|
||||
- type: SolutionTransfer
|
||||
|
||||
@@ -91,6 +91,6 @@
|
||||
Blunt: 1
|
||||
- type: SolutionContainer
|
||||
maxVol: 15
|
||||
caps: AddTo, RemoveFrom
|
||||
caps: Refillable, Drainable
|
||||
- type: ChemicalInjectionProjectile
|
||||
transferAmount: 15
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
- type: entity
|
||||
- type: entity
|
||||
name: spear
|
||||
parent: BaseItem
|
||||
id: Spear
|
||||
@@ -22,7 +22,7 @@
|
||||
- type: MeleeChemicalInjector
|
||||
- type: SolutionContainer
|
||||
maxVol: 5
|
||||
- type: Pourable
|
||||
- type: SolutionTransfer
|
||||
|
||||
- type: MeleeWeaponAnimation
|
||||
id: spear
|
||||
|
||||
@@ -11,5 +11,5 @@
|
||||
sprite: Objects/Specific/Medical/hypospray.rsi
|
||||
- type: SolutionContainer
|
||||
maxVol: 30
|
||||
caps: AddTo, CanExamine
|
||||
caps: Refillable, CanExamine
|
||||
- type: Hypospray
|
||||
|
||||
@@ -89,6 +89,7 @@
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Constructible/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Cooldowns/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Diethylamine/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Drainable/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Firelock/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Cuffable/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=cvar/@EntryIndexedValue">True</s:Boolean>
|
||||
|
||||
Reference in New Issue
Block a user