diff --git a/Content.Server/Nutrition/EntitySystems/FoodSystem.cs b/Content.Server/Nutrition/EntitySystems/FoodSystem.cs index 1e0d253dcb..c875bfaf8b 100644 --- a/Content.Server/Nutrition/EntitySystems/FoodSystem.cs +++ b/Content.Server/Nutrition/EntitySystems/FoodSystem.cs @@ -293,9 +293,16 @@ namespace Content.Server.Nutrition.EntitySystems return; } - if (string.IsNullOrEmpty(component.TrashPrototype)) - EntityManager.QueueDeleteEntity(uid); + var ev = new BeforeFullyEatenEvent + { + User = args.User + }; + RaiseLocalEvent(uid, ev); + if (ev.Cancelled) + return; + if (string.IsNullOrEmpty(component.TrashPrototype)) + QueueDel(uid); else DeleteAndSpawnTrash(component, uid, args.User); } diff --git a/Content.Server/Nutrition/EntitySystems/SliceableFoodSystem.cs b/Content.Server/Nutrition/EntitySystems/SliceableFoodSystem.cs index 3825d78045..c4b248ff93 100644 --- a/Content.Server/Nutrition/EntitySystems/SliceableFoodSystem.cs +++ b/Content.Server/Nutrition/EntitySystems/SliceableFoodSystem.cs @@ -1,5 +1,6 @@ using Content.Server.Chemistry.Components.SolutionManager; using Content.Server.Chemistry.EntitySystems; +using Content.Server.Nutrition; using Content.Server.Nutrition.Components; using Content.Shared.Chemistry.Components; using Content.Shared.Examine; @@ -51,12 +52,12 @@ namespace Content.Server.Nutrition.EntitySystems return false; } - if (!EntityManager.TryGetComponent(usedItem, out UtensilComponent ? utensil) || (utensil.Types & UtensilType.Knife) == 0) + if (!TryComp(usedItem, out var utensil) || (utensil.Types & UtensilType.Knife) == 0) { return false; } - var sliceUid = EntityManager.SpawnEntity(component.Slice, transform.Coordinates); + var sliceUid = Spawn(component.Slice, transform.Coordinates); var lostSolution = _solutionContainerSystem.SplitSolution(uid, solution, solution.Volume / FixedPoint2.New(component.Count)); @@ -91,7 +92,7 @@ namespace Content.Server.Nutrition.EntitySystems // If someone makes food proto with 1 slice... if (component.Count < 1) { - EntityManager.DeleteEntity(uid); + DeleteFood(uid, user); return true; } @@ -99,7 +100,7 @@ namespace Content.Server.Nutrition.EntitySystems if (component.Count > 1) return true; - sliceUid = EntityManager.SpawnEntity(component.Slice, transform.Coordinates); + sliceUid = Spawn(component.Slice, transform.Coordinates); // Fill last slice with the rest of the solution FillSlice(sliceUid, solution); @@ -115,14 +116,26 @@ namespace Content.Server.Nutrition.EntitySystems xform.LocalRotation = 0; } - EntityManager.DeleteEntity(uid); + DeleteFood(uid, user); return true; } + private void DeleteFood(EntityUid uid, EntityUid user) + { + var ev = new BeforeFullySlicedEvent + { + User = user + }; + RaiseLocalEvent(uid, ev); + + if (!ev.Cancelled) + Del(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 (EntityManager.TryGetComponent(sliceUid, out var sliceFoodComp) && + if (TryComp(sliceUid, out var sliceFoodComp) && _solutionContainerSystem.TryGetSolution(sliceUid, sliceFoodComp.SolutionName, out var itsSolution)) { _solutionContainerSystem.RemoveAllSolution(sliceUid, itsSolution); @@ -135,9 +148,9 @@ namespace Content.Server.Nutrition.EntitySystems private void OnComponentStartup(EntityUid uid, SliceableFoodComponent component, ComponentStartup args) { component.Count = component.TotalCount; - var foodComp = EntityManager.EnsureComponent(uid); + var foodComp = EnsureComp(uid); - EntityManager.EnsureComponent(uid); + EnsureComp(uid); _solutionContainerSystem.EnsureSolution(uid, foodComp.SolutionName); } diff --git a/Content.Server/Nutrition/IngestionEvents.cs b/Content.Server/Nutrition/IngestionEvents.cs index c9668ad9db..ae1d22fb71 100644 --- a/Content.Server/Nutrition/IngestionEvents.cs +++ b/Content.Server/Nutrition/IngestionEvents.cs @@ -10,3 +10,27 @@ public sealed class IngestionAttemptEvent : CancellableEntityEventArgs /// public EntityUid? Blocker = null; } + +/// +/// Raised directed at the food after finishing eating a food before it's deleted. +/// Cancel this if you want to do something special before a food is deleted. +/// +public sealed class BeforeFullyEatenEvent : CancellableEntityEventArgs +{ + /// + /// The person that ate the food. + /// + public EntityUid User; +} + +/// +/// Raised directed at the food being sliced before it's deleted. +/// Cancel this if you want to do something special before a food is deleted. +/// +public sealed class BeforeFullySlicedEvent : CancellableEntityEventArgs +{ + /// + /// The person slicing the food. + /// + public EntityUid User; +} diff --git a/Content.Server/Polymorph/Systems/PolymorphSystem.cs b/Content.Server/Polymorph/Systems/PolymorphSystem.cs index 621eafb3b6..f0368aedce 100644 --- a/Content.Server/Polymorph/Systems/PolymorphSystem.cs +++ b/Content.Server/Polymorph/Systems/PolymorphSystem.cs @@ -3,6 +3,7 @@ using Content.Server.Humanoid; using Content.Server.Inventory; using Content.Server.Mind.Commands; using Content.Server.Mind.Components; +using Content.Server.Nutrition; using Content.Server.Polymorph.Components; using Content.Shared.Actions; using Content.Shared.Actions.ActionTypes; @@ -41,7 +42,7 @@ namespace Content.Server.Polymorph.Systems [Dependency] private readonly SharedPopupSystem _popup = default!; [Dependency] private readonly TransformSystem _transform = default!; - private readonly ISawmill _saw = default!; + private ISawmill _sawmill = default!; public override void Initialize() { @@ -50,10 +51,14 @@ namespace Content.Server.Polymorph.Systems SubscribeLocalEvent(OnStartup); SubscribeLocalEvent(OnPolymorphActionEvent); SubscribeLocalEvent(OnStartup); + SubscribeLocalEvent(OnBeforeFullyEaten); + SubscribeLocalEvent(OnBeforeFullySliced); SubscribeLocalEvent(OnRevertPolymorphActionEvent); InitializeCollide(); InitializeMap(); + + _sawmill = Logger.GetSawmill("polymorph"); } private void OnStartup(EntityUid uid, PolymorphableComponent component, ComponentStartup args) @@ -82,7 +87,7 @@ namespace Content.Server.Polymorph.Systems if (!_proto.TryIndex(component.Prototype, out PolymorphPrototype? proto)) { // warning instead of error because of the all-comps one entity test. - Logger.Warning($"{nameof(PolymorphSystem)} encountered an improperly set up polymorph component while initializing. Entity {ToPrettyString(uid)}. Prototype: {component.Prototype}"); + _sawmill.Warning($"{nameof(PolymorphSystem)} encountered an improperly set up polymorph component while initializing. Entity {ToPrettyString(uid)}. Prototype: {component.Prototype}"); RemCompDeferred(uid, component); return; } @@ -102,6 +107,36 @@ namespace Content.Server.Polymorph.Systems _actions.AddAction(uid, act, null); } + private void OnBeforeFullyEaten(EntityUid uid, PolymorphedEntityComponent comp, BeforeFullyEatenEvent args) + { + if (!_proto.TryIndex(comp.Prototype, out var proto)) + { + _sawmill.Error("Invalid polymorph prototype {comp.Prototype}"); + return; + } + + if (proto.RevertOnEat) + { + args.Cancel(); + Revert(uid, comp); + } + } + + private void OnBeforeFullySliced(EntityUid uid, PolymorphedEntityComponent comp, BeforeFullySlicedEvent args) + { + if (!_proto.TryIndex(comp.Prototype, out var proto)) + { + _sawmill.Error("Invalid polymorph prototype {comp.Prototype}"); + return; + } + + if (proto.RevertOnEat) + { + args.Cancel(); + Revert(uid, comp); + } + } + /// /// Polymorphs the target entity into the specific polymorph prototype /// @@ -111,7 +146,7 @@ namespace Content.Server.Polymorph.Systems { if (!_proto.TryIndex(id, out var proto)) { - _saw.Error("Invalid polymorph prototype"); + _sawmill.Error("Invalid polymorph prototype {id}"); return null; } @@ -225,7 +260,7 @@ namespace Content.Server.Polymorph.Systems if (!_proto.TryIndex(component.Prototype, out PolymorphPrototype? proto)) { - Logger.Error($"{nameof(PolymorphSystem)} encountered an improperly initialized polymorph component while reverting. Entity {ToPrettyString(uid)}. Prototype: {component.Prototype}"); + _sawmill.Error($"{nameof(PolymorphSystem)} encountered an improperly initialized polymorph component while reverting. Entity {ToPrettyString(uid)}. Prototype: {component.Prototype}"); return; } @@ -236,9 +271,6 @@ namespace Content.Server.Polymorph.Systems parentXform.Coordinates = uidXform.Coordinates; parentXform.LocalRotation = uidXform.LocalRotation; - if (_container.TryGetContainingContainer(uid, out var cont)) - cont.Insert(component.Parent); - if (proto.TransferDamage && TryComp(parent, out var damageParent) && _mobThreshold.GetScaledDamage(uid, parent, out var damage) && @@ -277,6 +309,9 @@ namespace Content.Server.Polymorph.Systems mind.Mind.TransferTo(parent); } + // if an item polymorph was picked up, put it back down after reverting + Transform(parent).AttachToGridOrMap(); + _popup.PopupEntity(Loc.GetString("polymorph-revert-popup-generic", ("parent", Identity.Entity(uid, EntityManager)), ("child", Identity.Entity(parent, EntityManager))), @@ -293,7 +328,7 @@ namespace Content.Server.Polymorph.Systems { if (!_proto.TryIndex(id, out var polyproto)) { - _saw.Error("Invalid polymorph prototype"); + _sawmill.Error("Invalid polymorph prototype"); return; } @@ -335,27 +370,32 @@ namespace Content.Server.Polymorph.Systems { base.Update(frameTime); - foreach (var comp in EntityQuery()) + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var uid, out var comp)) { comp.Time += frameTime; - var ent = comp.Owner; if (!_proto.TryIndex(comp.Prototype, out PolymorphPrototype? proto)) { - Logger.Error($"{nameof(PolymorphSystem)} encountered an improperly initialized polymorph component while updating. Entity {ToPrettyString(ent)}. Prototype: {comp.Prototype}"); - RemCompDeferred(ent, comp); + _sawmill.Error($"{nameof(PolymorphSystem)} encountered an improperly initialized polymorph component while updating. Entity {ToPrettyString(uid)}. Prototype: {comp.Prototype}"); + RemCompDeferred(uid, comp); continue; } - if(proto.Duration != null && comp.Time >= proto.Duration) - Revert(ent, comp); + if (proto.Duration != null && comp.Time >= proto.Duration) + { + Revert(uid, comp); + continue; + } - if (!TryComp(ent, out var mob)) + if (!TryComp(uid, out var mob)) continue; - if (proto.RevertOnDeath && _mobState.IsDead(ent, mob) || - proto.RevertOnCrit && _mobState.IsIncapacitated(ent, mob)) - Revert(ent, comp); + if (proto.RevertOnDeath && _mobState.IsDead(uid, mob) || + proto.RevertOnCrit && _mobState.IsIncapacitated(uid, mob)) + { + Revert(uid, comp); + } } UpdateCollide(); diff --git a/Content.Shared/Polymorph/PolymorphPrototype.cs b/Content.Shared/Polymorph/PolymorphPrototype.cs index 1685422213..7663e960b8 100644 --- a/Content.Shared/Polymorph/PolymorphPrototype.cs +++ b/Content.Shared/Polymorph/PolymorphPrototype.cs @@ -89,6 +89,12 @@ namespace Content.Shared.Polymorph [DataField("revertOnDeath", serverOnly: true)] public bool RevertOnDeath = true; + /// + /// Whether or not the polymorph reverts when the entity is eaten or fully sliced. + /// + [DataField("revertOnEat", serverOnly: true)] + public bool RevertOnEat = false; + [DataField("allowRepeatedMorphs", serverOnly: true)] public bool AllowRepeatedMorphs = false; } diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Basic/wands.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Basic/wands.yml index af2b3b86d6..e723a1db5f 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Basic/wands.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Basic/wands.yml @@ -105,3 +105,14 @@ proto: ProjectilePolyboltCluwne capacity: 3 count: 3 + +- type: entity + parent: WeaponWandPolymorphBase + id: WeaponWandPolymorphBread + name: magic bread wand + description: Turn all your friends into bread! Your boss! Your enemies! Your dog! Make everything bread! + components: + - type: BasicEntityAmmoProvider + proto: ProjectilePolyboltBread + capacity: 10 + count: 10 diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/magic.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/magic.yml index 472233d3b3..e1628c3a36 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/magic.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/magic.yml @@ -141,3 +141,15 @@ Cold: 20 Structural: 40 +- type: entity + parent: ProjectilePolyboltBase + id: ProjectilePolyboltBread + name: bread polybolt + description: Nooo, I don't wanna be bread! + noSpawn: true + components: + - type: PolymorphOnCollide + polymorph: BreadMorph + whitelist: + components: + - Body diff --git a/Resources/Prototypes/Polymorphs/polymorph.yml b/Resources/Prototypes/Polymorphs/polymorph.yml index 9c6076fc6f..ccc35ae47a 100644 --- a/Resources/Prototypes/Polymorphs/polymorph.yml +++ b/Resources/Prototypes/Polymorphs/polymorph.yml @@ -83,3 +83,14 @@ transferName: true revertOnCrit: false revertOnDeath: false + +- type: polymorph + id: BreadMorph + entity: FoodBreadPlain + forced: true + inventory: None + transferName: false + transferDamage: true + revertOnCrit: false + revertOnDeath: true + revertOnEat: true