Files
space-station-14/Content.Shared/Fluids/SharedPuddleSystem.Spillable.cs
Princess Cheeseballs 24005e3e93 Jet Injector Tweaks and Cleanup. (#42158)
* delete metabolismmovespeedmodifiersystem

* Revert "delete metabolismmovespeedmodifiersystem"

This reverts commit 19572fa085.

* misc cleanup

* math shows I should do this

* prevent popups

* fix handling as well

* this too

* actually these can create popups so just always handle em...

* remove comment

* final fixes

---------

Co-authored-by: Princess Cheeseballs <66055347+Pronana@users.noreply.github.com>
2026-01-01 05:29:36 +00:00

208 lines
7.9 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using Content.Shared.Chemistry;
using Content.Shared.Chemistry.Components;
using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Chemistry.Prototypes;
using Content.Shared.Chemistry.Reaction;
using Content.Shared.CombatMode.Pacification;
using Content.Shared.Database;
using Content.Shared.DoAfter;
using Content.Shared.Examine;
using Content.Shared.FixedPoint;
using Content.Shared.Fluids.Components;
using Content.Shared.IdentityManagement;
using Content.Shared.Nutrition.EntitySystems;
using Content.Shared.Popups;
using Content.Shared.Spillable;
using Content.Shared.Verbs;
using Content.Shared.Weapons.Melee;
using Content.Shared.Weapons.Melee.Events;
using Robust.Shared.Player;
namespace Content.Shared.Fluids;
public abstract partial class SharedPuddleSystem
{
private static readonly FixedPoint2 MeleeHitTransferProportion = 0.25;
[Dependency] private readonly InjectorSystem _injectorSystem = default!;
protected virtual void InitializeSpillable()
{
SubscribeLocalEvent<SpillableComponent, ExaminedEvent>(OnExamined);
SubscribeLocalEvent<SpillableComponent, GetVerbsEvent<Verb>>(AddSpillVerb);
SubscribeLocalEvent<SpillableComponent, MeleeHitEvent>(SplashOnMeleeHit, after: [typeof(OpenableSystem)]);
SubscribeLocalEvent<SpillableComponent, AttemptPacifiedThrowEvent>(OnAttemptPacifiedThrow);
}
private void OnExamined(Entity<SpillableComponent> entity, ref ExaminedEvent args)
{
using (args.PushGroup(nameof(SpillableComponent)))
{
args.PushMarkup(Loc.GetString("spill-examine-is-spillable"));
if (HasComp<MeleeWeaponComponent>(entity))
args.PushMarkup(Loc.GetString("spill-examine-spillable-weapon"));
}
}
private void AddSpillVerb(Entity<SpillableComponent> entity, ref GetVerbsEvent<Verb> args)
{
if (!args.CanAccess || !args.CanInteract || args.Hands == null)
return;
if (!_solutionContainerSystem.TryGetSolution(args.Target,
entity.Comp.SolutionName,
out var soln,
out var solution))
return;
if (Openable.IsClosed(args.Target))
return;
if (solution.Volume == FixedPoint2.Zero)
return;
Verb verb = new()
{
Text = Loc.GetString("spill-target-verb-get-data-text")
};
// TODO VERB ICONS spill icon? pouring out a glass/beaker?
if (entity.Comp.SpillDelay == null)
{
var target = args.Target;
var user = args.User;
verb.Act = () =>
{
var puddleSolution = _solutionContainerSystem.SplitSolution(soln.Value, solution.Volume);
TrySpillAt(Transform(target).Coordinates, puddleSolution, out _);
// TODO: Make this an event subscription once spilling puddles is predicted.
// Injectors should not be hardcoded here.
if (TryComp<InjectorComponent>(entity, out var injectorComp)
&& _prototypeManager.Resolve(injectorComp.ActiveModeProtoId, out var activeMode)
&& !activeMode.Behavior.HasAnyFlag(InjectorBehavior.Draw | InjectorBehavior.Dynamic))
{
foreach (var mode in injectorComp.AllowedModes)
{
if (!_prototypeManager.Resolve(mode, out var protoMode))
continue;
if (protoMode.Behavior.HasAnyFlag(InjectorBehavior.Draw | InjectorBehavior.Dynamic))
{
_injectorSystem.ToggleMode((entity, injectorComp), user, protoMode);
break;
}
}
Dirty(entity, injectorComp);
}
};
}
else
{
var user = args.User;
verb.Act = () =>
{
_doAfterSystem.TryStartDoAfter(new DoAfterArgs(EntityManager,
user,
entity.Comp.SpillDelay ?? 0,
new SpillDoAfterEvent(),
entity.Owner,
target: entity.Owner)
{
BreakOnDamage = true,
BreakOnMove = true,
NeedHand = true,
});
};
}
verb.Impact = LogImpact.Medium; // dangerous reagent reaction are logged separately.
verb.DoContactInteraction = true;
args.Verbs.Add(verb);
}
private void SplashOnMeleeHit(Entity<SpillableComponent> entity, ref MeleeHitEvent args)
{
if (args.Handled)
return;
// When attacking someone reactive with a spillable entity,
// splash a little on them (touch react)
// If this also has solution transfer, then assume the transfer amount is how much we want to spill.
// Otherwise let's say they want to spill a quarter of its max volume.
if (!_solutionContainerSystem.TryGetDrainableSolution(entity.Owner, out var soln, out var solution))
return;
var hitCount = args.HitEntities.Count;
var totalSplit = FixedPoint2.Min(solution.MaxVolume * MeleeHitTransferProportion, solution.Volume);
if (TryComp<SolutionTransferComponent>(entity, out var transfer))
totalSplit = FixedPoint2.Min(transfer.TransferAmount, solution.Volume);
// a little lame, but reagent quantity is not very balanced and we don't want people
// spilling like 100u of reagent on someone at once!
totalSplit = FixedPoint2.Min(totalSplit, entity.Comp.MaxMeleeSpillAmount);
if (totalSplit == 0)
return;
// Optionally allow further melee handling occur
args.Handled = entity.Comp.PreventMelee;
// First update the hit count so anything that is not reactive wont count towards the total!
foreach (var hit in args.HitEntities)
{
if (!_reactiveQuery.HasComp(hit))
hitCount -= 1;
}
foreach (var hit in args.HitEntities)
{
if (!_reactiveQuery.HasComp(hit))
continue;
var splitSolution = _solutionContainerSystem.SplitSolution(soln.Value, totalSplit / hitCount);
AdminLogger.Add(LogType.MeleeHit,
$"{ToPrettyString(args.User):actor} "
+ $"splashed {SharedSolutionContainerSystem.ToPrettyString(splitSolution):solution} "
+ $"from {ToPrettyString(entity.Owner):entity} onto {ToPrettyString(hit):target}");
Reactive.DoEntityReaction(hit, splitSolution, ReactionMethod.Touch);
Popups.PopupClient(Loc.GetString("spill-melee-hit-attacker",
("amount", totalSplit / hitCount),
("spillable", entity.Owner),
("target", Identity.Entity(hit, EntityManager, args.User))),
hit,
args.User);
Popups.PopupEntity(
Loc.GetString("spill-melee-hit-others",
("attacker", Identity.Entity(args.User, EntityManager)),
("spillable", entity.Owner),
("target", Identity.Entity(hit, EntityManager))),
hit,
Filter.PvsExcept(args.User),
true,
PopupType.SmallCaution);
}
}
/// <summary>
/// Prevent Pacified entities from throwing items that can spill liquids.
/// </summary>
private void OnAttemptPacifiedThrow(Entity<SpillableComponent> ent, ref AttemptPacifiedThrowEvent args)
{
// Dont care about closed containers.
if (Openable.IsClosed(ent))
return;
// Dont care about empty containers.
if (!_solutionContainerSystem.TryGetSolution(ent.Owner, ent.Comp.SolutionName, out _, out var solution)
|| solution.Volume <= 0)
return;
args.Cancel("pacified-cannot-throw-spill");
}
}