CatStation/Content.Shared/Nutrition/EntitySystems/SharedCreamPieSystem.cs
2026-06-01 20:56:21 +00:00

170 lines
6.3 KiB
C#

using Content.Shared.Chemistry.EntitySystems;
using Content.Shared.Containers.ItemSlots;
using Content.Shared.Fluids;
using Content.Shared.IdentityManagement;
using Content.Shared.Nutrition.Components;
using Content.Shared.Popups;
using Content.Shared.Rejuvenate;
using Content.Shared.Stunnable;
using Content.Shared.Throwing;
using Content.Shared.Tools.Systems;
using Content.Shared.Trigger.Components;
using Content.Shared.Trigger.Systems;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Network;
using Robust.Shared.Player;
namespace Content.Shared.Nutrition.EntitySystems;
public abstract partial class SharedCreamPieSystem : EntitySystem
{
[Dependency] private SharedStunSystem _stunSystem = default!;
[Dependency] private SharedAppearanceSystem _appearance = default!;
[Dependency] private IngestionSystem _ingestion = default!;
[Dependency] private ItemSlotsSystem _itemSlots = default!;
[Dependency] private SharedPopupSystem _popup = default!;
[Dependency] private SharedPuddleSystem _puddle = default!;
[Dependency] private SharedAudioSystem _audio = default!;
[Dependency] private SharedSolutionContainerSystem _solutions = default!;
[Dependency] private TriggerSystem _trigger = default!;
[Dependency] private INetManager _net = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<CreamPieComponent, ThrowDoHitEvent>(OnCreamPieHit);
SubscribeLocalEvent<CreamPieComponent, LandEvent>(OnCreamPieLand);
SubscribeLocalEvent<CreamPiedComponent, ThrowHitByEvent>(OnCreamPiedHitBy);
SubscribeLocalEvent<CreamPieComponent, BeforeToolRefinedEvent>(OnToolRefine);
SubscribeLocalEvent<CreamPiedComponent, RejuvenateEvent>(OnRejuvenate);
}
/// <summary>
/// SPLAT!
/// </summary>
public void SplatCreamPie(Entity<CreamPieComponent> creamPie)
{
// Already splatted! Do nothing.
if (creamPie.Comp.Splatted)
return;
// The pie will be queued for deletion but there may be multiple collisions in the same tick, so we prevent it from splatting more than once.
creamPie.Comp.Splatted = true;
Dirty(creamPie);
// The entity is being deleted, so play the sound at its position rather than parenting.
if (_net.IsServer) // we don't have a user to pass in TODO: make the popup API sane and remove this guard
{
var coordinates = Transform(creamPie).Coordinates;
_audio.PlayPvs(creamPie.Comp.Sound, coordinates);
}
if (TryComp<EdibleComponent>(creamPie, out var edibleComp))
{
if (_solutions.TryGetSolution(creamPie.Owner, edibleComp.Solution, out _, out var solution))
_puddle.TrySpillAt(creamPie.Owner, solution, out _, false);
_ingestion.SpawnTrash((creamPie.Owner, edibleComp));
}
ActivatePayload(creamPie);
PredictedQueueDel(creamPie);
}
/// <summary>
/// Drop any item hidden in the cream pie and trigger it.
/// </summary>
public void ActivatePayload(EntityUid uid)
{
// Keep this server side for now since we don't have a user we can pass in for prediction purposes.
// Ideally the popup and audio API will be reworked so that is not needed anymore.
if (_net.IsClient)
return;
if (_itemSlots.TryGetSlot(uid, CreamPieComponent.PayloadSlotName, out var itemSlot)
&& _itemSlots.TryEject(uid, itemSlot, user: null, out var item)
&& TryComp<TimerTriggerComponent>(item.Value, out var timerTrigger))
_trigger.ActivateTimerTrigger((item.Value, timerTrigger));
}
/// <summary>
/// Sets the creampied status of an entity.
/// This toggles the visuals for the pie in their face.
/// </summary>
public void SetCreamPied(Entity<CreamPiedComponent?> ent, bool value)
{
if (!Resolve(ent, ref ent.Comp))
return;
if (value == ent.Comp.CreamPied)
return;
ent.Comp.CreamPied = value;
Dirty(ent);
_appearance.SetData(ent.Owner, CreamPiedVisuals.Creamed, value);
}
private void OnCreamPieLand(Entity<CreamPieComponent> ent, ref LandEvent args)
{
SplatCreamPie(ent);
}
private void OnCreamPieHit(Entity<CreamPieComponent> ent, ref ThrowDoHitEvent args)
{
SplatCreamPie(ent);
}
private void OnCreamPiedHitBy(Entity<CreamPiedComponent> creamPied, ref ThrowHitByEvent args)
{
if (!Exists(args.Thrown) || !TryComp<CreamPieComponent>(args.Thrown, out var creamPie))
return;
_stunSystem.TryUpdateParalyzeDuration(creamPied.Owner, creamPie.ParalyzeTime);
// Already creamed, no need to spam popups.
if (creamPied.Comp.CreamPied)
return;
// TODO: Check if they even have a head that can be hit.
SetCreamPied(creamPied.AsNullable(), true);
// Throwing is not predicted, so the thrower is not equal to the client predicting the collision, so we cannot pass in a user.
// TODO: Make the popup API sane.
if (_net.IsClient)
return;
// Shown only to the player that was hit.
_popup.PopupEntity(
Loc.GetString(
"cream-pied-component-on-hit-by-message",
("thrown", args.Thrown)),
creamPied.Owner, creamPied.Owner);
var otherPlayers = Filter.PvsExcept(creamPied.Owner);
// Show to everyone else.
_popup.PopupEntity(
Loc.GetString(
"cream-pied-component-on-hit-by-message-others",
("owner", Identity.Entity(creamPied.Owner, EntityManager)),
("thrown", args.Thrown)),
creamPied.Owner, otherPlayers, false);
}
private void OnRejuvenate(Entity<CreamPiedComponent> ent, ref RejuvenateEvent args)
{
SetCreamPied(ent.AsNullable(), false);
}
// TODO
// A regression occured here. Previously creampies would activate their hidden payload if you tried to eat them.
// However, the refactor to IngestionSystem caused the event to not be reached,
// because eating is blocked if an item is inside the food.
private void OnToolRefine(Entity<CreamPieComponent> ent, ref BeforeToolRefinedEvent args)
{
ActivatePayload(ent);
}
}