diff --git a/Content.Server/Nutrition/Components/SliceableFoodComponent.cs b/Content.Server/Nutrition/Components/SliceableFoodComponent.cs index 2b74d0c67e..7877e91d03 100644 --- a/Content.Server/Nutrition/Components/SliceableFoodComponent.cs +++ b/Content.Server/Nutrition/Components/SliceableFoodComponent.cs @@ -11,21 +11,27 @@ public sealed partial class SliceableFoodComponent : Component /// Prototype to spawn after slicing. /// If null then it can't be sliced. /// - [DataField, ViewVariables(VVAccess.ReadWrite)] + [DataField] public EntProtoId? Slice; - [DataField, ViewVariables(VVAccess.ReadWrite)] + [DataField] public SoundSpecifier Sound = new SoundPathSpecifier("/Audio/Items/Culinary/chop.ogg"); /// /// Number of slices the food starts with. /// - [DataField("count"), ViewVariables(VVAccess.ReadWrite)] + [DataField("count")] public ushort TotalCount = 5; /// - /// Number of slices left. + /// how long it takes for this food to be sliced /// - [ViewVariables(VVAccess.ReadWrite)] - public ushort Count; + [DataField] + public float SliceTime = 1f; + + /// + /// all the pieces will be shifted in random directions. + /// + [DataField] + public float SpawnOffset = 0.5f; } diff --git a/Content.Server/Nutrition/EntitySystems/SliceableFoodSystem.cs b/Content.Server/Nutrition/EntitySystems/SliceableFoodSystem.cs index 946a789b36..a865a22121 100644 --- a/Content.Server/Nutrition/EntitySystems/SliceableFoodSystem.cs +++ b/Content.Server/Nutrition/EntitySystems/SliceableFoodSystem.cs @@ -1,173 +1,162 @@ using Content.Server.Chemistry.Containers.EntitySystems; +using Content.Server.DoAfter; using Content.Server.Nutrition.Components; using Content.Shared.Nutrition; using Content.Shared.Nutrition.Components; using Content.Shared.Chemistry.Components; -using Content.Shared.Examine; +using Content.Shared.DoAfter; using Content.Shared.FixedPoint; using Content.Shared.Interaction; using Robust.Server.GameObjects; using Robust.Shared.Audio; using Robust.Shared.Audio.Systems; -using Robust.Shared.Containers; -namespace Content.Server.Nutrition.EntitySystems +namespace Content.Server.Nutrition.EntitySystems; + +public sealed class SliceableFoodSystem : EntitySystem { - public sealed class SliceableFoodSystem : EntitySystem + [Dependency] private readonly SolutionContainerSystem _solutionContainer = default!; + [Dependency] private readonly SharedAudioSystem _audio = default!; + [Dependency] private readonly TransformSystem _transform = default!; + [Dependency] private readonly DoAfterSystem _doAfter = default!; + + public override void Initialize() { - [Dependency] private readonly SolutionContainerSystem _solutionContainerSystem = default!; - [Dependency] private readonly SharedAudioSystem _audio = default!; - [Dependency] private readonly TransformSystem _xformSystem = default!; + base.Initialize(); - public override void Initialize() + SubscribeLocalEvent(OnInteractUsing); + SubscribeLocalEvent(OnSlicedoAfter); + SubscribeLocalEvent(OnComponentStartup); + } + + private void OnInteractUsing(Entity entity, ref InteractUsingEvent args) + { + if (args.Handled) + return; + + var doAfterArgs = new DoAfterArgs(EntityManager, + args.User, + entity.Comp.SliceTime, + new SliceFoodDoAfterEvent(), + entity, + entity, + args.Used) { - base.Initialize(); + BreakOnDamage = true, + BreakOnMove = true, + NeedHand = true, + }; + _doAfter.TryStartDoAfter(doAfterArgs); + } - SubscribeLocalEvent(OnExamined); - SubscribeLocalEvent(OnInteractUsing); - SubscribeLocalEvent(OnComponentStartup); - } + private void OnSlicedoAfter(Entity entity, ref SliceFoodDoAfterEvent args) + { + if (args.Cancelled || args.Handled || args.Args.Target == null) + return; - private void OnInteractUsing(Entity entity, ref InteractUsingEvent args) + if (TrySliceFood(entity, args.User, args.Used, entity.Comp)) + args.Handled = true; + } + + private bool TrySliceFood(EntityUid uid, + EntityUid user, + EntityUid? usedItem, + SliceableFoodComponent? component = null, + FoodComponent? food = null, + TransformComponent? transform = null) + { + if (!Resolve(uid, ref component, ref food, ref transform) || + string.IsNullOrEmpty(component.Slice)) + return false; + + if (!_solutionContainer.TryGetSolution(uid, food.Solution, out var soln, out var solution)) + return false; + + if (!TryComp(usedItem, out var utensil) || (utensil.Types & UtensilType.Knife) == 0) + return false; + + var sliceVolume = solution.Volume / FixedPoint2.New(component.TotalCount); + for (int i = 0; i < component.TotalCount; i++) { - if (args.Handled) - return; - - if (TrySliceFood(entity, args.User, args.Used, entity.Comp)) - args.Handled = true; - } - - private bool TrySliceFood(EntityUid uid, EntityUid user, EntityUid usedItem, - SliceableFoodComponent? component = null, FoodComponent? food = null, TransformComponent? transform = null) - { - if (!Resolve(uid, ref component, ref food, ref transform) || - string.IsNullOrEmpty(component.Slice)) - { - return false; - } - - if (!_solutionContainerSystem.TryGetSolution(uid, food.Solution, out var soln, out var solution)) - { - return false; - } - - if (!TryComp(usedItem, out var utensil) || (utensil.Types & UtensilType.Knife) == 0) - { - return false; - } - var sliceUid = Slice(uid, user, component, transform); - var lostSolution = _solutionContainerSystem.SplitSolution(soln.Value, solution.Volume / FixedPoint2.New(component.Count)); + var lostSolution = + _solutionContainer.SplitSolution(soln.Value, sliceVolume); // Fill new slice FillSlice(sliceUid, lostSolution); - - _audio.PlayPvs(component.Sound, transform.Coordinates, AudioParams.Default.WithVolume(-2)); - var ev = new SliceFoodEvent(); - RaiseLocalEvent(uid, ref ev); - - // Decrease size of item based on count - Could implement in the future - // Bug with this currently is the size in a container is not updated - // if (TryComp(uid, out ItemComponent? itemComp) && TryComp(sliceUid, out ItemComponent? sliceComp)) - // { - // itemComp.Size -= sliceComp.Size; - // } - - component.Count--; - - // If someone makes food proto with 1 slice... - if (component.Count < 1) - { - DeleteFood(uid, user, food); - return true; - } - - // Split last slice - if (component.Count > 1) - return true; - - sliceUid = Slice(uid, user, component, transform); - - // Fill last slice with the rest of the solution - FillSlice(sliceUid, solution); - - DeleteFood(uid, user, food); - return true; } - /// - /// Create a new slice in the world and returns its entity. - /// The solutions must be set afterwards. - /// - public EntityUid Slice(EntityUid uid, EntityUid user, SliceableFoodComponent? comp = null, TransformComponent? transform = null) + _audio.PlayPvs(component.Sound, transform.Coordinates, AudioParams.Default.WithVolume(-2)); + var ev = new SliceFoodEvent(); + RaiseLocalEvent(uid, ref ev); + + DeleteFood(uid, user, food); + return true; + } + + /// + /// Create a new slice in the world and returns its entity. + /// The solutions must be set afterwards. + /// + public EntityUid Slice(EntityUid uid, + EntityUid user, + SliceableFoodComponent? comp = null, + TransformComponent? transform = null) + { + if (!Resolve(uid, ref comp, ref transform)) + return EntityUid.Invalid; + + var sliceUid = Spawn(comp.Slice, _transform.GetMapCoordinates(uid)); + + // try putting the slice into the container if the food being sliced is in a container! + // this lets you do things like slice a pizza up inside of a hot food cart without making a food-everywhere mess + _transform.DropNextTo(sliceUid, (uid, transform)); + _transform.SetLocalRotation(sliceUid, 0); + + return sliceUid; + } + + private void DeleteFood(EntityUid uid, EntityUid user, FoodComponent foodComp) + { + var ev = new BeforeFullySlicedEvent { - if (!Resolve(uid, ref comp, ref transform)) - return EntityUid.Invalid; + User = user + }; + RaiseLocalEvent(uid, ev); + if (ev.Cancelled) + return; - var sliceUid = Spawn(comp.Slice, _xformSystem.GetMapCoordinates(uid)); + // Locate the sliced food and spawn its trash + foreach (var trash in foodComp.Trash) + { + var trashUid = Spawn(trash, _transform.GetMapCoordinates(uid)); - // try putting the slice into the container if the food being sliced is in a container! - // this lets you do things like slice a pizza up inside of a hot food cart without making a food-everywhere mess - _xformSystem.DropNextTo(sliceUid, (uid, transform)); - _xformSystem.SetLocalRotation(sliceUid, 0); - - return sliceUid; + // try putting the trash in the food's container too, to be consistent with slice spawning? + _transform.DropNextTo(trashUid, uid); + _transform.SetLocalRotation(trashUid, 0); } - private void DeleteFood(EntityUid uid, EntityUid user, FoodComponent foodComp) + QueueDel(uid); + } + + private void FillSlice(EntityUid sliceUid, Solution solution) + { + // Replace all reagents on prototype not just copying poisons (example: slices of eaten pizza should have less nutrition) + if (TryComp(sliceUid, out var sliceFoodComp) && + _solutionContainer.TryGetSolution(sliceUid, sliceFoodComp.Solution, out var itsSoln, out var itsSolution)) { - var ev = new BeforeFullySlicedEvent - { - User = user - }; - RaiseLocalEvent(uid, ev); - if (ev.Cancelled) - return; + _solutionContainer.RemoveAllSolution(itsSoln.Value); - if (foodComp.Trash.Count == 0) - { - QueueDel(uid); - return; - } - - // Locate the sliced food and spawn its trash - foreach (var trash in foodComp.Trash) - { - var trashUid = Spawn(trash, _xformSystem.GetMapCoordinates(uid)); - - // try putting the trash in the food's container too, to be consistent with slice spawning? - _xformSystem.DropNextTo(trashUid, uid); - _xformSystem.SetLocalRotation(trashUid, 0); - } - - QueueDel(uid); - } - - private void FillSlice(EntityUid sliceUid, Solution solution) - { - // Replace all reagents on prototype not just copying poisons (example: slices of eaten pizza should have less nutrition) - if (TryComp(sliceUid, out var sliceFoodComp) && - _solutionContainerSystem.TryGetSolution(sliceUid, sliceFoodComp.Solution, out var itsSoln, out var itsSolution)) - { - _solutionContainerSystem.RemoveAllSolution(itsSoln.Value); - - var lostSolutionPart = solution.SplitSolution(itsSolution.AvailableVolume); - _solutionContainerSystem.TryAddSolution(itsSoln.Value, lostSolutionPart); - } - } - - private void OnComponentStartup(Entity entity, ref ComponentStartup args) - { - entity.Comp.Count = entity.Comp.TotalCount; - - var foodComp = EnsureComp(entity); - _solutionContainerSystem.EnsureSolution(entity.Owner, foodComp.Solution); - } - - private void OnExamined(Entity entity, ref ExaminedEvent args) - { - args.PushMarkup(Loc.GetString("sliceable-food-component-on-examine-remaining-slices-text", ("remainingCount", entity.Comp.Count))); + var lostSolutionPart = solution.SplitSolution(itsSolution.AvailableVolume); + _solutionContainer.TryAddSolution(itsSoln.Value, lostSolutionPart); } } + + private void OnComponentStartup(Entity entity, ref ComponentStartup args) + { + var foodComp = EnsureComp(entity); + _solutionContainer.EnsureSolution(entity.Owner, foodComp.Solution); + } } + diff --git a/Content.Shared/Nutrition/Events.cs b/Content.Shared/Nutrition/Events.cs index f2936d603d..abba2583ba 100644 --- a/Content.Shared/Nutrition/Events.cs +++ b/Content.Shared/Nutrition/Events.cs @@ -59,3 +59,11 @@ public sealed partial class VapeDoAfterEvent : DoAfterEvent /// [ByRefEvent] public record struct SliceFoodEvent(); + +/// +/// is called after a successful attempt at slicing food. +/// +[Serializable, NetSerializable] +public sealed partial class SliceFoodDoAfterEvent : SimpleDoAfterEvent +{ +}