From 832ba7b6f3c492bf74e789e25b9649d0f192ca54 Mon Sep 17 00:00:00 2001 From: PilgrimViis Date: Tue, 16 May 2023 00:44:35 +0200 Subject: [PATCH] Added a component to eat bodies for health #8922 (#16345) --- Content.Client/Devour/DevourSystem.cs | 6 ++ Content.Server/Devour/DevourSystem.cs | 51 ++++++++++ .../Dragon/Components/DragonComponent.cs | 60 ------------ Content.Server/Dragon/DragonSystem.cs | 80 ---------------- .../Devour/Components/DevourerComponent.cs | 82 ++++++++++++++++ Content.Shared/Devour/SharedDevourSystem.cs | 94 +++++++++++++++++++ .../Dragon/DragonDevourDoAfterEvent.cs | 9 -- .../Entities/Mobs/Player/dragon.yml | 27 +++--- 8 files changed, 248 insertions(+), 161 deletions(-) create mode 100644 Content.Client/Devour/DevourSystem.cs create mode 100644 Content.Server/Devour/DevourSystem.cs create mode 100644 Content.Shared/Devour/Components/DevourerComponent.cs create mode 100644 Content.Shared/Devour/SharedDevourSystem.cs delete mode 100644 Content.Shared/Dragon/DragonDevourDoAfterEvent.cs diff --git a/Content.Client/Devour/DevourSystem.cs b/Content.Client/Devour/DevourSystem.cs new file mode 100644 index 0000000000..ad905ffab3 --- /dev/null +++ b/Content.Client/Devour/DevourSystem.cs @@ -0,0 +1,6 @@ +using Content.Shared.Devour; + +namespace Content.Client.Devour; +public sealed class DevourSystem : SharedDevourSystem +{ +} diff --git a/Content.Server/Devour/DevourSystem.cs b/Content.Server/Devour/DevourSystem.cs new file mode 100644 index 0000000000..5421751100 --- /dev/null +++ b/Content.Server/Devour/DevourSystem.cs @@ -0,0 +1,51 @@ +using Content.Shared.Devour; +using Content.Server.Body.Systems; +using Content.Shared.Humanoid; +using Content.Shared.Chemistry.Components; +using Content.Server.Devour.Components; +using Content.Shared.DoAfter; +using Robust.Shared.Serialization; + +namespace Content.Server.Devour; + +public sealed class DevourSystem : SharedDevourSystem +{ + [Dependency] private readonly BloodstreamSystem _bloodstreamSystem = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnDoAfter); + } + + private void OnDoAfter(EntityUid uid, DevourerComponent component, DevourDoAfterEvent args) + { + if (args.Handled || args.Cancelled) + return; + + var ichorInjection = new Solution(component.Chemical, component.HealRate); + + if (component.FoodPreference == FoodPreference.All || + (component.FoodPreference == FoodPreference.Humanoid && HasComp(args.Args.Target))) + { + ichorInjection.ScaleSolution(0.5f); + + if (component.ShouldStoreDevoured && args.Args.Target is not null) + { + component.Stomach.Insert(args.Args.Target.Value); + } + _bloodstreamSystem.TryAddToChemicals(uid, ichorInjection); + } + + //TODO: Figure out a better way of removing structures via devour that still entails standing still and waiting for a DoAfter. Somehow. + //If it's not human, it must be a structure + else if (args.Args.Target != null) + { + QueueDel(args.Args.Target.Value); + } + + _audioSystem.PlayPvs(component.SoundDevour, uid); + } +} + diff --git a/Content.Server/Dragon/Components/DragonComponent.cs b/Content.Server/Dragon/Components/DragonComponent.cs index 2222b64a15..430b5d5fc2 100644 --- a/Content.Server/Dragon/Components/DragonComponent.cs +++ b/Content.Server/Dragon/Components/DragonComponent.cs @@ -13,23 +13,6 @@ namespace Content.Server.Dragon [RegisterComponent] public sealed class DragonComponent : Component { - /// - /// The chemical ID injected upon devouring - /// - [DataField("devourChemical", customTypeSerializer: typeof(PrototypeIdSerializer))] - public string DevourChem = "Ichor"; - - /// - /// The amount of ichor injected per devour - /// - [ViewVariables(VVAccess.ReadWrite), DataField("devourHealRate")] - public float DevourHealRate = 15f; - - [DataField("devourActionId", customTypeSerializer: typeof(PrototypeIdSerializer))] - public string DevourActionId = "DragonDevour"; - - [DataField("devourAction")] - public EntityTargetAction? DevourAction; /// /// If we have active rifts. @@ -68,58 +51,15 @@ namespace Content.Server.Dragon [ViewVariables(VVAccess.ReadWrite), DataField("riftPrototype", customTypeSerializer: typeof(PrototypeIdSerializer))] public string RiftPrototype = "CarpRift"; - /// - /// The amount of time it takes to devour something - /// - /// NOTE: original intended design was to increase this proportionally with damage thresholds, but those proved quite difficult to get consistently. right now it devours the structure at a fixed timer. - /// - /// - [DataField("structureDevourTime")] - public float StructureDevourTime = 10f; - - [DataField("devourTime")] - public float DevourTime = 3f; - [ViewVariables(VVAccess.ReadWrite), DataField("soundDeath")] public SoundSpecifier? SoundDeath = new SoundPathSpecifier("/Audio/Animals/space_dragon_roar.ogg"); - [ViewVariables(VVAccess.ReadWrite), DataField("soundDevour")] - public SoundSpecifier? SoundDevour = new SoundPathSpecifier("/Audio/Effects/demon_consume.ogg") - { - Params = AudioParams.Default.WithVolume(-3f), - }; - - [ViewVariables(VVAccess.ReadWrite), DataField("soundStructureDevour")] - public SoundSpecifier? SoundStructureDevour = new SoundPathSpecifier("/Audio/Machines/airlock_creaking.ogg") - { - Params = AudioParams.Default.WithVolume(-3f), - }; - [ViewVariables(VVAccess.ReadWrite), DataField("soundRoar")] public SoundSpecifier? SoundRoar = new SoundPathSpecifier("/Audio/Animals/space_dragon_roar.ogg") { Params = AudioParams.Default.WithVolume(3f), }; - - [ViewVariables(VVAccess.ReadWrite), DataField("devourWhitelist")] - public EntityWhitelist? DevourWhitelist = new() - { - Components = new[] - { - "Door", - "MobState", - }, - Tags = new List - { - "Wall", - }, - }; - - /// - /// Where the entities go when dragon devours them, empties when the dragon is butchered. - /// - public Container DragonStomach = default!; } public sealed class DragonDevourActionEvent : EntityTargetActionEvent {} diff --git a/Content.Server/Dragon/DragonSystem.cs b/Content.Server/Dragon/DragonSystem.cs index 329a4379f6..befdc50ca4 100644 --- a/Content.Server/Dragon/DragonSystem.cs +++ b/Content.Server/Dragon/DragonSystem.cs @@ -59,12 +59,9 @@ namespace Content.Server.Dragon SubscribeLocalEvent(OnStartup); SubscribeLocalEvent(OnShutdown); - SubscribeLocalEvent(OnDevourAction); SubscribeLocalEvent(OnDragonRift); SubscribeLocalEvent(OnDragonMove); - SubscribeLocalEvent(OnDoAfter); - SubscribeLocalEvent(OnMobStateChanged); SubscribeLocalEvent(OnRiftShutdown); @@ -75,29 +72,6 @@ namespace Content.Server.Dragon SubscribeLocalEvent(OnRiftRoundEnd); } - private void OnDoAfter(EntityUid uid, DragonComponent component, DragonDevourDoAfterEvent args) - { - if (args.Handled || args.Cancelled) - return; - - var ichorInjection = new Solution(component.DevourChem, component.DevourHealRate); - - //Humanoid devours allow dragon to get eggs, corpses included - if (HasComp(args.Args.Target)) - { - ichorInjection.ScaleSolution(0.5f); - component.DragonStomach.Insert(args.Args.Target.Value); - _bloodstreamSystem.TryAddToChemicals(uid, ichorInjection); - } - - //TODO: Figure out a better way of removing structures via devour that still entails standing still and waiting for a DoAfter. Somehow. - //If it's not human, it must be a structure - else if (args.Args.Target != null) - EntityManager.QueueDeleteEntity(args.Args.Target.Value); - - _audioSystem.PlayPvs(component.SoundDevour, uid); - } - public override void Update(float frameTime) { base.Update(frameTime); @@ -303,8 +277,6 @@ namespace Content.Server.Dragon if (component.SoundDeath != null) _audioSystem.PlayPvs(component.SoundDeath, uid, component.SoundDeath.Params); - component.DragonStomach.EmptyContainer(); - foreach (var rift in component.Rifts) { QueueDel(rift); @@ -322,62 +294,10 @@ namespace Content.Server.Dragon private void OnStartup(EntityUid uid, DragonComponent component, ComponentStartup args) { - //Dragon doesn't actually chew, since he sends targets right into his stomach. - //I did it mom, I added ERP content into upstream. Legally! - component.DragonStomach = _containerSystem.EnsureContainer(uid, "dragon_stomach"); - - if (component.DevourAction != null) - _actionsSystem.AddAction(uid, component.DevourAction, null); - if (component.SpawnRiftAction != null) _actionsSystem.AddAction(uid, component.SpawnRiftAction, null); Roar(component); } - - /// - /// The devour action - /// - private void OnDevourAction(EntityUid uid, DragonComponent component, DragonDevourActionEvent args) - { - if (args.Handled || component.DevourWhitelist?.IsValid(args.Target, EntityManager) != true) - return; - - args.Handled = true; - var target = args.Target; - - // Structure and mob devours handled differently. - if (EntityManager.TryGetComponent(target, out MobStateComponent? targetState)) - { - switch (targetState.CurrentState) - { - case MobState.Critical: - case MobState.Dead: - - _doAfterSystem.TryStartDoAfter(new DoAfterArgs(uid, component.DevourTime, new DragonDevourDoAfterEvent(), uid, target: target, used: uid) - { - BreakOnTargetMove = true, - BreakOnUserMove = true, - }); - break; - default: - _popupSystem.PopupEntity(Loc.GetString("devour-action-popup-message-fail-target-alive"), uid, uid); - break; - } - - return; - } - - _popupSystem.PopupEntity(Loc.GetString("devour-action-popup-message-structure"), uid, uid); - - if (component.SoundStructureDevour != null) - _audioSystem.PlayPvs(component.SoundStructureDevour, uid, component.SoundStructureDevour.Params); - - _doAfterSystem.TryStartDoAfter(new DoAfterArgs(uid, component.StructureDevourTime, new DragonDevourDoAfterEvent(), uid, target: target, used: uid) - { - BreakOnTargetMove = true, - BreakOnUserMove = true, - }); - } } } diff --git a/Content.Shared/Devour/Components/DevourerComponent.cs b/Content.Shared/Devour/Components/DevourerComponent.cs new file mode 100644 index 0000000000..97085ca593 --- /dev/null +++ b/Content.Shared/Devour/Components/DevourerComponent.cs @@ -0,0 +1,82 @@ +using Content.Shared.Actions; +using Content.Shared.Actions.ActionTypes; +using Content.Shared.Chemistry.Reagent; +using Content.Shared.Devour; +using Content.Shared.Whitelist; +using Robust.Shared.Audio; +using Robust.Shared.Containers; +using Robust.Shared.GameStates; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; + +namespace Content.Server.Devour.Components; + +[RegisterComponent, NetworkedComponent] +[Access(typeof(SharedDevourSystem))] +public sealed class DevourerComponent : Component +{ + [DataField("devourActionId", customTypeSerializer: typeof(PrototypeIdSerializer))] + public string DevourActionId = "Devour"; + + [DataField("devourAction")] + public EntityTargetAction? DevourAction; + + [ViewVariables(VVAccess.ReadWrite), DataField("soundDevour")] + public SoundSpecifier? SoundDevour = new SoundPathSpecifier("/Audio/Effects/demon_consume.ogg") + { + Params = AudioParams.Default.WithVolume(-3f), + }; + + [DataField("devourTime")] + public float DevourTime = 3f; + + /// + /// The amount of time it takes to devour something + /// + /// NOTE: original intended design was to increase this proportionally with damage thresholds, but those proved quite difficult to get consistently. right now it devours the structure at a fixed timer. + /// + /// + [DataField("structureDevourTime")] + public float StructureDevourTime = 10f; + + [ViewVariables(VVAccess.ReadWrite), DataField("soundStructureDevour")] + public SoundSpecifier? SoundStructureDevour = new SoundPathSpecifier("/Audio/Machines/airlock_creaking.ogg") + { + Params = AudioParams.Default.WithVolume(-3f), + }; + + /// + /// Where the entities go when it devours them, empties when it is butchered. + /// + public Container Stomach = default!; + + [ViewVariables(VVAccess.ReadWrite), DataField("shouldStoreDevoured")] + public bool ShouldStoreDevoured = true; + + [ViewVariables(VVAccess.ReadWrite), DataField("whitelist")] + public EntityWhitelist? Whitelist = new() + { + Components = new[] + { + "MobState", + } + }; + + /// + /// The chemical ID injected upon devouring + /// + [DataField("chemical", customTypeSerializer: typeof(PrototypeIdSerializer))] + public string Chemical = "Ichor"; + + /// + /// The amount of ichor injected per devour + /// + [ViewVariables(VVAccess.ReadWrite), DataField("healRate")] + public float HealRate = 15f; + + /// + /// The favorite food not only feeds you, but also heals + /// + [DataField("foodPreference")] + public FoodPreference FoodPreference = FoodPreference.All; +} + diff --git a/Content.Shared/Devour/SharedDevourSystem.cs b/Content.Shared/Devour/SharedDevourSystem.cs new file mode 100644 index 0000000000..117d010606 --- /dev/null +++ b/Content.Shared/Devour/SharedDevourSystem.cs @@ -0,0 +1,94 @@ +using Content.Shared.DoAfter; +using Content.Shared.Mobs.Components; +using Content.Shared.Mobs; +using Robust.Shared.Containers; +using Content.Server.Devour.Components; +using Content.Shared.Actions; +using Content.Shared.Popups; +using Robust.Shared.Serialization; + +namespace Content.Shared.Devour; + +public abstract class SharedDevourSystem : EntitySystem +{ + [Dependency] protected readonly SharedAudioSystem _audioSystem = default!; + [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!; + [Dependency] private readonly SharedPopupSystem _popupSystem = default!; + [Dependency] private readonly SharedActionsSystem _actionsSystem = default!; + [Dependency] private readonly SharedContainerSystem _containerSystem = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnStartup); + SubscribeLocalEvent(OnDevourAction); + } + + protected void OnStartup(EntityUid uid, DevourerComponent component, ComponentStartup args) + { + //Devourer doesn't actually chew, since he sends targets right into his stomach. + //I did it mom, I added ERP content into upstream. Legally! + component.Stomach = _containerSystem.EnsureContainer(uid, "stomach"); + + if (component.DevourAction != null) + _actionsSystem.AddAction(uid, component.DevourAction, null); + } + + /// + /// The devour action + /// + protected void OnDevourAction(EntityUid uid, DevourerComponent component, DevourActionEvent args) + { + if (args.Handled || component.Whitelist?.IsValid(args.Target, EntityManager) != true) + return; + + args.Handled = true; + var target = args.Target; + + // Structure and mob devours handled differently. + if (TryComp(target, out MobStateComponent? targetState)) + { + switch (targetState.CurrentState) + { + case MobState.Critical: + case MobState.Dead: + + _doAfterSystem.TryStartDoAfter(new DoAfterArgs(uid, component.DevourTime, new DevourDoAfterEvent(), uid, target: target, used: uid) + { + BreakOnTargetMove = true, + BreakOnUserMove = true, + }); + break; + default: + _popupSystem.PopupEntity(Loc.GetString("devour-action-popup-message-fail-target-alive"), uid, uid); + break; + } + + return; + } + + _popupSystem.PopupEntity(Loc.GetString("devour-action-popup-message-structure"), uid, uid); + + if (component.SoundStructureDevour != null) + _audioSystem.PlayPvs(component.SoundStructureDevour, uid, component.SoundStructureDevour.Params); + + _doAfterSystem.TryStartDoAfter(new DoAfterArgs(uid, component.StructureDevourTime, new DevourDoAfterEvent(), uid, target: target, used: uid) + { + BreakOnTargetMove = true, + BreakOnUserMove = true, + }); + } +} + +public sealed class DevourActionEvent : EntityTargetActionEvent { } + +[Serializable, NetSerializable] +public sealed class DevourDoAfterEvent : SimpleDoAfterEvent { } + +[Serializable, NetSerializable] +public enum FoodPreference : byte +{ + Humanoid = 0, + All = 1 +} diff --git a/Content.Shared/Dragon/DragonDevourDoAfterEvent.cs b/Content.Shared/Dragon/DragonDevourDoAfterEvent.cs deleted file mode 100644 index 2f5f0d5fff..0000000000 --- a/Content.Shared/Dragon/DragonDevourDoAfterEvent.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Content.Shared.DoAfter; -using Robust.Shared.Serialization; - -namespace Content.Shared.Dragon; - -[Serializable, NetSerializable] -public sealed class DragonDevourDoAfterEvent : SimpleDoAfterEvent -{ -} \ No newline at end of file diff --git a/Resources/Prototypes/Entities/Mobs/Player/dragon.yml b/Resources/Prototypes/Entities/Mobs/Player/dragon.yml index d179fbe615..5abd832bc0 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/dragon.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/dragon.yml @@ -97,22 +97,25 @@ types: Piercing: 15 Slash: 15 - - type: Dragon - spawnsLeft: 2 - spawnsProto: MobCarpDragon + - type: Devourer + foodPreference: Humanoid + shouldStoreDevoured: true + chemical: Ichor + healRate: 15.0 + whitelist: + components: + - MobState + - Door + tags: + - Wall devourAction: - event: !type:DragonDevourActionEvent + event: !type:DevourActionEvent icon: Interface/Actions/devour.png name: action-name-devour description: action-description-devour - devourChemical: Ichor - devourHealRate: 15.0 - whitelist: - components: - - MobState - - Door - tags: - - Wall + - type: Dragon + spawnsLeft: 2 + spawnsProto: MobCarpDragon spawnRiftAction: event: !type:DragonSpawnRiftActionEvent icon: -- 2.51.2