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(OnCreamPieHit); SubscribeLocalEvent(OnCreamPieLand); SubscribeLocalEvent(OnCreamPiedHitBy); SubscribeLocalEvent(OnToolRefine); SubscribeLocalEvent(OnRejuvenate); } /// /// SPLAT! /// public void SplatCreamPie(Entity 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(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); } /// /// Drop any item hidden in the cream pie and trigger it. /// 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(item.Value, out var timerTrigger)) _trigger.ActivateTimerTrigger((item.Value, timerTrigger)); } /// /// Sets the creampied status of an entity. /// This toggles the visuals for the pie in their face. /// public void SetCreamPied(Entity 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 ent, ref LandEvent args) { SplatCreamPie(ent); } private void OnCreamPieHit(Entity ent, ref ThrowDoHitEvent args) { SplatCreamPie(ent); } private void OnCreamPiedHitBy(Entity creamPied, ref ThrowHitByEvent args) { if (!Exists(args.Thrown) || !TryComp(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. if (_net.IsClient) return; _popup.PopupEntity( Loc.GetString( "cream-pied-component-on-hit-by-message", ("thrown", args.Thrown)), Loc.GetString( "cream-pied-component-on-hit-by-message-others", ("owner", Identity.Entity(creamPied.Owner, EntityManager)), ("thrown", args.Thrown)), creamPied.Owner, creamPied.Owner); } private void OnRejuvenate(Entity 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 ent, ref BeforeToolRefinedEvent args) { ActivatePayload(ent); } public void CopyComponent(Entity entity, EntityUid clone) { if (!Resolve(entity, ref entity.Comp, false)) return; // We don't clone whether you are actively creampied on purpose. var cloneComp = EnsureComp(clone); cloneComp.Displacement = entity.Comp.Displacement; cloneComp.Sprite = entity.Comp.Sprite; Dirty(clone, cloneComp); } }