From 9168fc629c555b8c395d695d291faea1eeda1db6 Mon Sep 17 00:00:00 2001 From: Kara Date: Sat, 9 Jul 2022 02:48:16 -0700 Subject: [PATCH] Add egg laying + chicken/duck improvements (#9518) --- .../Animals/Components/EggLayerComponent.cs | 55 ++++++++++ .../Animals/Systems/EggLayerSystem.cs | 95 ++++++++++++++++++ Content.Shared/Storage/EntitySpawnEntry.cs | 5 + Resources/Audio/Effects/licenses.txt | 2 + Resources/Audio/Effects/pop.ogg | Bin 0 -> 4865 bytes Resources/Locale/en-US/accent/accents.ftl | 12 +++ .../Locale/en-US/actions/actions/egg-lay.ftl | 6 ++ Resources/Prototypes/Actions/types.yml | 8 ++ .../Prototypes/Entities/Mobs/NPCs/animals.yml | 14 +++ Resources/Prototypes/accents.yml | 16 +++ 10 files changed, 213 insertions(+) create mode 100644 Content.Server/Animals/Components/EggLayerComponent.cs create mode 100644 Content.Server/Animals/Systems/EggLayerSystem.cs create mode 100644 Resources/Audio/Effects/pop.ogg create mode 100644 Resources/Locale/en-US/actions/actions/egg-lay.ftl diff --git a/Content.Server/Animals/Components/EggLayerComponent.cs b/Content.Server/Animals/Components/EggLayerComponent.cs new file mode 100644 index 0000000000..84faa0ef27 --- /dev/null +++ b/Content.Server/Animals/Components/EggLayerComponent.cs @@ -0,0 +1,55 @@ +using Content.Shared.Actions; +using Content.Shared.Actions.ActionTypes; +using Content.Shared.Sound; +using Content.Shared.Storage; +using Robust.Shared.Serialization; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; + +namespace Content.Server.Animals.Components; + +/// +/// This component handles animals which lay eggs (or some other item) on a timer, using up hunger to do so. +/// It also grants an action to players who are controlling these entities, allowing them to do it manually. +/// +[RegisterComponent] +public sealed class EggLayerComponent : Component +{ + [DataField("eggLayAction", customTypeSerializer: typeof(PrototypeIdSerializer))] + public string EggLayAction = "AnimalLayEgg"; + + [ViewVariables(VVAccess.ReadWrite)] + [DataField("hungerUsage")] + public float HungerUsage = 60f; + + /// + /// Minimum cooldown used for the automatic egg laying. + /// + [ViewVariables(VVAccess.ReadWrite)] + [DataField("eggLayCooldownMin")] + public float EggLayCooldownMin = 60f; + + /// + /// Maximum cooldown used for the automatic egg laying. + /// + [ViewVariables(VVAccess.ReadWrite)] + [DataField("eggLayCooldownMax")] + public float EggLayCooldownMax = 120f; + + /// + /// Set during component init. + /// + [ViewVariables(VVAccess.ReadWrite)] + public float CurrentEggLayCooldown; + + [ViewVariables(VVAccess.ReadWrite)] + [DataField("eggSpawn", required: true)] + public List EggSpawn = default!; + + [DataField("eggLaySound")] + public SoundSpecifier EggLaySound = new SoundPathSpecifier("/Audio/Effects/pop.ogg"); + + [DataField("accumulatedFrametime")] + public float AccumulatedFrametime; +} + +public sealed class EggLayInstantActionEvent : InstantActionEvent {} diff --git a/Content.Server/Animals/Systems/EggLayerSystem.cs b/Content.Server/Animals/Systems/EggLayerSystem.cs new file mode 100644 index 0000000000..32fdb0bc24 --- /dev/null +++ b/Content.Server/Animals/Systems/EggLayerSystem.cs @@ -0,0 +1,95 @@ +using Content.Server.Actions; +using Content.Server.Animals.Components; +using Content.Server.Nutrition.Components; +using Content.Server.Popups; +using Content.Shared.Actions.ActionTypes; +using Content.Shared.Storage; +using Robust.Server.GameObjects; +using Robust.Shared.Audio; +using Robust.Shared.Player; +using Robust.Shared.Prototypes; +using Robust.Shared.Random; + +namespace Content.Server.Animals.Systems; + +public sealed class EggLayerSystem : EntitySystem +{ + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly IPrototypeManager _prototype = default!; + [Dependency] private readonly ActionsSystem _actions = default!; + [Dependency] private readonly PopupSystem _popup = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnComponentInit); + SubscribeLocalEvent(OnEggLayAction); + } + + public override void Update(float frameTime) + { + base.Update(frameTime); + + foreach (var eggLayer in EntityQuery()) + { + // Players should be using the action. + if (HasComp(eggLayer.Owner)) + return; + + eggLayer.AccumulatedFrametime += frameTime; + + if (eggLayer.AccumulatedFrametime < eggLayer.CurrentEggLayCooldown) + continue; + + eggLayer.AccumulatedFrametime -= eggLayer.CurrentEggLayCooldown; + eggLayer.CurrentEggLayCooldown = _random.NextFloat(eggLayer.EggLayCooldownMin, eggLayer.EggLayCooldownMax); + + TryLayEgg(eggLayer.Owner, eggLayer); + } + } + + private void OnComponentInit(EntityUid uid, EggLayerComponent component, ComponentInit args) + { + if (!_prototype.TryIndex(component.EggLayAction, out var action)) + return; + + _actions.AddAction(uid, new InstantAction(action), uid); + component.CurrentEggLayCooldown = _random.NextFloat(component.EggLayCooldownMin, component.EggLayCooldownMax); + } + + private void OnEggLayAction(EntityUid uid, EggLayerComponent component, EggLayInstantActionEvent args) + { + args.Handled = TryLayEgg(uid, component); + } + + public bool TryLayEgg(EntityUid uid, EggLayerComponent? component) + { + if (!Resolve(uid, ref component)) + return false; + + // Allow infinitely laying eggs if they can't get hungry + if (TryComp(uid, out var hunger)) + { + if (hunger.CurrentHunger < component.HungerUsage) + { + _popup.PopupEntity(Loc.GetString("action-popup-lay-egg-too-hungry"), uid, Filter.Entities(uid)); + return false; + } + + hunger.CurrentHunger -= component.HungerUsage; + } + + foreach (var ent in EntitySpawnCollection.GetSpawns(component.EggSpawn, _random)) + { + Spawn(ent, Transform(uid).Coordinates); + } + + // Sound + popups + SoundSystem.Play(component.EggLaySound.GetSound(), Filter.Pvs(uid), uid, component.EggLaySound.Params); + _popup.PopupEntity(Loc.GetString("action-popup-lay-egg-user"), uid, Filter.Entities(uid)); + _popup.PopupEntity(Loc.GetString("action-popup-lay-egg-others", ("entity", uid)), uid, Filter.PvsExcept(uid)); + + return true; + } +} diff --git a/Content.Shared/Storage/EntitySpawnEntry.cs b/Content.Shared/Storage/EntitySpawnEntry.cs index 9748f8dc30..2efbea010f 100644 --- a/Content.Shared/Storage/EntitySpawnEntry.cs +++ b/Content.Shared/Storage/EntitySpawnEntry.cs @@ -12,12 +12,14 @@ namespace Content.Shared.Storage; [DataDefinition] public struct EntitySpawnEntry : IPopulateDefaultValues { + [ViewVariables(VVAccess.ReadWrite)] [DataField("id", required: true, customTypeSerializer: typeof(PrototypeIdSerializer))] public string PrototypeId; /// /// The probability that an item will spawn. Takes decimal form so 0.05 is 5%, 0.50 is 50% etc. /// + [ViewVariables(VVAccess.ReadWrite)] [DataField("prob")] public float SpawnProbability; /// @@ -41,8 +43,10 @@ public struct EntitySpawnEntry : IPopulateDefaultValues /// /// /// + [ViewVariables(VVAccess.ReadWrite)] [DataField("orGroup")] public string? GroupId; + [ViewVariables(VVAccess.ReadWrite)] [DataField("amount")] public int Amount; /// @@ -50,6 +54,7 @@ public struct EntitySpawnEntry : IPopulateDefaultValues /// If this is lesser or equal to , it will spawn exactly. /// Otherwise, it chooses a random value between and on spawn. /// + [ViewVariables(VVAccess.ReadWrite)] [DataField("maxAmount")] public int MaxAmount; public void PopulateDefaultValues() diff --git a/Resources/Audio/Effects/licenses.txt b/Resources/Audio/Effects/licenses.txt index ee7381179e..18145786fc 100644 --- a/Resources/Audio/Effects/licenses.txt +++ b/Resources/Audio/Effects/licenses.txt @@ -37,3 +37,5 @@ The following sounds are taken from TGstation github (licensed under CC by 3.0): demon_consume.ogg: taken at https://github.com/tgstation/tgstation/commit/d4f678a1772007ff8d7eddd21cf7218c8e07bfc0 demon_dies.ogg: taken at https://github.com/tgstation/tgstation/commit/d4f678a1772007ff8d7eddd21cf7218c8e07bfc0 + +pop.ogg licensed under CC0 1.0 by mirrorcult \ No newline at end of file diff --git a/Resources/Audio/Effects/pop.ogg b/Resources/Audio/Effects/pop.ogg new file mode 100644 index 0000000000000000000000000000000000000000..97b7468e192d3ce1712eccc2722ab4498481bef5 GIT binary patch literal 4865 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 literal 0 HcmV?d00001 diff --git a/Resources/Locale/en-US/accent/accents.ftl b/Resources/Locale/en-US/accent/accents.ftl index 603505e780..54f4be86a5 100644 --- a/Resources/Locale/en-US/accent/accents.ftl +++ b/Resources/Locale/en-US/accent/accents.ftl @@ -46,3 +46,15 @@ accent-words-generic-aggressive-1 = Grr! accent-words-generic-aggressive-2 = Rrrr! accent-words-generic-aggressive-3 = Grr... accent-words-generic-aggressive-4 = Grrow!! + +# Duck +accent-words-duck-1 = Quack! +accent-words-duck-2 = Quack. +accent-words-duck-3 = Quack? +accent-words-duck-4 = Quack quack! + +# Chicken +accent-words-chicken-1 = Cluck! +accent-words-chicken-2 = Cluck. +accent-words-chicken-3 = Cluck? +accent-words-chicken-4 = Cluck cluck! diff --git a/Resources/Locale/en-US/actions/actions/egg-lay.ftl b/Resources/Locale/en-US/actions/actions/egg-lay.ftl new file mode 100644 index 0000000000..404eeee7d0 --- /dev/null +++ b/Resources/Locale/en-US/actions/actions/egg-lay.ftl @@ -0,0 +1,6 @@ +action-name-lay-egg = Lay egg +action-description-lay-egg = Uses hunger to lay an egg. + +action-popup-lay-egg-user = You lay an egg. +action-popup-lay-egg-others = {CAPITALIZE(THE($entity))} lays an egg. +action-popup-lay-egg-too-hungry = You need more food before you can lay another egg! diff --git a/Resources/Prototypes/Actions/types.yml b/Resources/Prototypes/Actions/types.yml index 29a85ce3e3..d30a32993c 100644 --- a/Resources/Prototypes/Actions/types.yml +++ b/Resources/Prototypes/Actions/types.yml @@ -69,3 +69,11 @@ icon: Objects/Weapons/Melee/shields.rsi/teleriot-icon.png iconOn: Objects/Weapons/Melee/shields.rsi/teleriot-on.png event: !type:ToggleActionEvent + +- type: instantAction + id: AnimalLayEgg + name: action-name-lay-egg + description: action-description-lay-egg + icon: Objects/Consumable/Food/egg.rsi/icon.png + useDelay: 60 + serverEvent: !type:EggLayInstantActionEvent diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml index 0b7cb19938..2aa24672df 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml @@ -193,6 +193,13 @@ path: /Audio/Animals/chicken_cluck_happy.ogg - type: Bloodstream bloodMaxVolume: 100 + - type: EggLayer + eggSpawn: + - id: FoodEgg + - type: ReplacementAccent + accent: chicken + - type: SentienceTarget + flavorKind: organic - type: entity name: mallard duck #Quack @@ -237,6 +244,13 @@ path: /Audio/Animals/duck_quack_happy.ogg - type: Bloodstream bloodMaxVolume: 100 + - type: EggLayer + eggSpawn: + - id: FoodEgg + - type: ReplacementAccent + accent: duck + - type: SentienceTarget + flavorKind: organic - type: entity name: white duck #Quack diff --git a/Resources/Prototypes/accents.yml b/Resources/Prototypes/accents.yml index e728a6fc82..ae32d9371f 100644 --- a/Resources/Prototypes/accents.yml +++ b/Resources/Prototypes/accents.yml @@ -62,3 +62,19 @@ - accent-words-generic-aggressive-2 - accent-words-generic-aggressive-3 - accent-words-generic-aggressive-4 + +- type: accent + id: duck + words: + - accent-words-duck-1 + - accent-words-duck-2 + - accent-words-duck-3 + - accent-words-duck-4 + +- type: accent + id: chicken + words: + - accent-words-chicken-1 + - accent-words-chicken-2 + - accent-words-chicken-3 + - accent-words-chicken-4