diff --git a/Content.Client/GameObjects/Components/Kitchen/KitchenSpikeComponent.cs b/Content.Client/GameObjects/Components/Kitchen/KitchenSpikeComponent.cs new file mode 100644 index 0000000000..1868788f17 --- /dev/null +++ b/Content.Client/GameObjects/Components/Kitchen/KitchenSpikeComponent.cs @@ -0,0 +1,16 @@ +#nullable enable +using Content.Shared.GameObjects.Components.Kitchen; +using Content.Shared.Interfaces.GameObjects.Components; +using Robust.Shared.GameObjects; + +namespace Content.Client.GameObjects.Components.Kitchen +{ + [RegisterComponent] + internal sealed class KitchenSpikeComponent : SharedKitchenSpikeComponent + { + public override bool DragDropOn(DragDropEventArgs eventArgs) + { + return true; + } + } +} diff --git a/Content.Client/IgnoredComponents.cs b/Content.Client/IgnoredComponents.cs index 2f97a9199a..af62b68c5e 100644 --- a/Content.Client/IgnoredComponents.cs +++ b/Content.Client/IgnoredComponents.cs @@ -173,7 +173,6 @@ namespace Content.Client "Matchstick", "Matchbox", "BlockGameArcade", - "KitchenSpike", "Butcherable", "Rehydratable", "Headset", diff --git a/Content.Server/GameObjects/Components/Kitchen/KitchenSpikeComponent.cs b/Content.Server/GameObjects/Components/Kitchen/KitchenSpikeComponent.cs index 66f2e5ac39..f97a5cc723 100644 --- a/Content.Server/GameObjects/Components/Kitchen/KitchenSpikeComponent.cs +++ b/Content.Server/GameObjects/Components/Kitchen/KitchenSpikeComponent.cs @@ -1,79 +1,134 @@ #nullable enable +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using Content.Server.GameObjects.EntitySystems.DoAfter; using Content.Server.Interfaces.Chat; using Content.Server.Interfaces.GameObjects; using Content.Server.Utility; -using Content.Shared.GameObjects.EntitySystems; using Content.Shared.Interfaces; using Content.Shared.Interfaces.GameObjects.Components; using Robust.Server.GameObjects; using Robust.Shared.GameObjects; using Robust.Shared.Localization; +using Content.Shared.GameObjects.Components.Kitchen; namespace Content.Server.GameObjects.Components.Kitchen { [RegisterComponent] [ComponentReference(typeof(IActivate))] - public class KitchenSpikeComponent : Component, IActivate, ISuicideAct + public class KitchenSpikeComponent : SharedKitchenSpikeComponent, IActivate, ISuicideAct { - public override string Name => "KitchenSpike"; - private int _meatParts; private string? _meatPrototype; private string _meatSource1p = "?"; private string _meatSource0 = "?"; private string _meatName = "?"; + private List _beingButchered = new(); + void IActivate.Activate(ActivateEventArgs eventArgs) { SpriteComponent? sprite; - if (!EntitySystem.Get().TryGetPulled(eventArgs.User, out var victim)) + if (_meatParts == 0) { - if (_meatParts == 0) - { - return; - } - _meatParts--; - - if (!string.IsNullOrEmpty(_meatPrototype)) - { - var meat = Owner.EntityManager.SpawnEntity(_meatPrototype, Owner.Transform.Coordinates); - if (meat != null) - { - meat.Name = _meatName; - } - } - - if (_meatParts != 0) - { - eventArgs.User.PopupMessage(_meatSource1p); - } - else - { - if (Owner.TryGetComponent(out sprite)) - { - sprite.LayerSetState(0, "spike"); - } - - eventArgs.User.PopupMessage(_meatSource0); - } - return; } + _meatParts--; + + if (!string.IsNullOrEmpty(_meatPrototype)) + { + var meat = Owner.EntityManager.SpawnEntity(_meatPrototype, Owner.Transform.Coordinates); + if (meat != null) + { + meat.Name = _meatName; + } + } + + if (_meatParts != 0) + { + eventArgs.User.PopupMessage(_meatSource1p); + } + else + { + if (Owner.TryGetComponent(out sprite)) + { + sprite.LayerSetState(0, "spike"); + } + + eventArgs.User.PopupMessage(_meatSource0); + } + + return; + + } + + public override bool DragDropOn(DragDropEventArgs eventArgs) + { + TrySpike(eventArgs.Dragged, eventArgs.User); + return true; + } + + private bool Spikeable(IEntity user, IEntity victim, [NotNullWhen(true)] out ButcherableComponent? butcherable) + { + butcherable = null; if (_meatParts > 0) { - Owner.PopupMessage(eventArgs.User, Loc.GetString("The spike already has something on it, finish collecting its meat first!")); - return; + Owner.PopupMessage(user, Loc.GetString("The spike already has something on it, finish collecting its meat first!")); + return false; } - if (!victim.TryGetComponent(out var food)) + if (!victim.TryGetComponent(out butcherable)) { - Owner.PopupMessage(eventArgs.User, Loc.GetString("{0:theName} can't be butchered on the spike.", victim)); - return; + Owner.PopupMessage(user, Loc.GetString("{0:theName} can't be butchered on the spike.", victim)); + return false; } - _meatPrototype = food.MeatPrototype; + return true; + } + + public async void TrySpike(IEntity victim, IEntity user) + { + var victimUid = victim.Uid; + if (_beingButchered.Contains(victimUid)) return; + + ButcherableComponent? butcherable; + + if (!Spikeable(user, victim, out butcherable)) return; + + if (user != victim) + { + Owner.PopupMessage(victim, Loc.GetString("{0:theName} begins dragging you onto {1:theName}!", user, Owner)); + } + else + { + Owner.PopupMessage(user, Loc.GetString("You begin dragging yourself onto {0:theName}!", Owner)); + } + + var doAfterSystem = EntitySystem.Get(); + + var doAfterArgs = new DoAfterEventArgs(user, SpikeDelay, default, victim) + { + BreakOnTargetMove = true, + BreakOnUserMove = true, + BreakOnDamage = true, + BreakOnStun = true, + NeedHand = true, + }; + + _beingButchered.Add(victimUid); + + var result = await doAfterSystem.DoAfter(doAfterArgs); + + _beingButchered.Remove(victimUid); + + if (result == DoAfterStatus.Cancelled) + return; + + if (!Spikeable(user, victim, out butcherable)) return; + + _meatPrototype = butcherable.MeatPrototype; _meatParts = 5; _meatSource1p = Loc.GetString("You remove some meat from {0:theName}.", victim); _meatSource0 = Loc.GetString("You remove the last piece of meat from {0:theName}!", victim); @@ -81,13 +136,15 @@ namespace Content.Server.GameObjects.Components.Kitchen // But Name is RobustToolbox-level, so presumably it'd have to be done in some other way (interface???) _meatName = Loc.GetString("{0:name} meat", victim); - if (Owner.TryGetComponent(out sprite)) + // TODO: Visualizer + if (Owner.TryGetComponent(out var sprite)) { sprite.LayerSetState(0, "spikebloody"); } - Owner.PopupMessageEveryone(Loc.GetString("{0:theName} has forced {1:theName} onto the spike, killing them instantly!", eventArgs.User, victim)); + Owner.PopupMessageEveryone(Loc.GetString("{0:theName} has forced {1:theName} onto the spike, killing them instantly!", user, victim)); victim.Delete(); + return; } SuicideKind ISuicideAct.Suicide(IEntity victim, IChatManager chat) diff --git a/Content.Shared/Kitchen/SharedKitchenSpikeComponent.cs b/Content.Shared/Kitchen/SharedKitchenSpikeComponent.cs new file mode 100644 index 0000000000..7e4df97533 --- /dev/null +++ b/Content.Shared/Kitchen/SharedKitchenSpikeComponent.cs @@ -0,0 +1,42 @@ +#nullable enable +using Content.Shared.GameObjects.Components.Mobs.State; +using Content.Shared.Interfaces.GameObjects.Components; +using Content.Shared.GameObjects.EntitySystems.ActionBlocker; +using Robust.Shared.GameObjects; +using Robust.Shared.Serialization; +using Robust.Shared.ViewVariables; + +namespace Content.Shared.GameObjects.Components.Kitchen +{ + public abstract class SharedKitchenSpikeComponent : Component, IDragDropOn + { + public override string Name => "KitchenSpike"; + + [ViewVariables] + protected float SpikeDelay; + + public override void ExposeData(ObjectSerializer serializer) + { + base.ExposeData(serializer); + serializer.DataField(ref SpikeDelay, "delay", 10.0f); + } + + bool IDragDropOn.CanDragDropOn(DragDropEventArgs eventArgs) + { + if (!eventArgs.Dragged.TryGetComponent(out var state)) + { + return false; + } + + // TODO: Wouldn't we just need the CanMove check? + if (state.IsDead() || state.IsCritical() || state.IsIncapacitated() || !ActionBlockerSystem.CanMove(eventArgs.Dragged)) + { + return true; + } + + return false; + } + + public abstract bool DragDropOn(DragDropEventArgs eventArgs); + } +}