]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Special digestion & kudzu-eating (#16061)
authorKara <lunarautomaton6@gmail.com>
Thu, 4 May 2023 02:49:25 +0000 (19:49 -0700)
committerGitHub <noreply@github.com>
Thu, 4 May 2023 02:49:25 +0000 (12:49 +1000)
16 files changed:
Content.Server/Body/Components/StomachComponent.cs
Content.Server/Body/Systems/StomachSystem.cs
Content.Server/NPC/Systems/NPCUtilitySystem.cs
Content.Server/Nutrition/Components/FoodComponent.cs
Content.Server/Nutrition/EntitySystems/FoodSystem.cs
Resources/Locale/en-US/nutrition/components/food-component.ftl
Resources/Prototypes/Body/Organs/Animal/animal.yml [moved from Resources/Prototypes/Body/Organs/animal.yml with 97% similarity]
Resources/Prototypes/Body/Organs/Animal/ruminant.yml [new file with mode: 0644]
Resources/Prototypes/Body/Organs/arachnid.yml
Resources/Prototypes/Body/Organs/rat.yml
Resources/Prototypes/Body/Organs/reptilian.yml
Resources/Prototypes/Body/Prototypes/Animal/animal.yml [moved from Resources/Prototypes/Body/Prototypes/animal.yml with 100% similarity]
Resources/Prototypes/Body/Prototypes/Animal/ruminant.yml [new file with mode: 0644]
Resources/Prototypes/Entities/Mobs/NPCs/animals.yml
Resources/Prototypes/Entities/Objects/Misc/kudzu.yml
Resources/Prototypes/NPCs/mob.yml

index 32b0b4adb7245cadde3cb93c3fe502961a26233a..a22b11ffa997cfc370e718a980839356cebafc35 100644 (file)
@@ -1,9 +1,11 @@
 using Content.Server.Body.Systems;
+using Content.Server.Nutrition.EntitySystems;
 using Content.Shared.FixedPoint;
+using Content.Shared.Whitelist;
 
 namespace Content.Server.Body.Components
 {
-    [RegisterComponent, Access(typeof(StomachSystem))]
+    [RegisterComponent, Access(typeof(StomachSystem), typeof(FoodSystem))]
     public sealed class StomachComponent : Component
     {
         public float AccumulatedFrameTime;
@@ -20,12 +22,6 @@ namespace Content.Server.Body.Components
         [DataField("bodySolutionName")]
         public string BodySolutionName = BloodstreamComponent.DefaultChemicalsSolutionName;
 
-        /// <summary>
-        ///     Initial internal solution storage volume
-        /// </summary>
-        [DataField("initialMaxVolume", readOnly: true)]
-        public readonly FixedPoint2 InitialMaxVolume = FixedPoint2.New(50);
-
         /// <summary>
         ///     Time in seconds between reagents being ingested and them being
         ///     transferred to <see cref="BloodstreamComponent"/>
@@ -33,6 +29,12 @@ namespace Content.Server.Body.Components
         [DataField("digestionDelay")]
         public float DigestionDelay = 20;
 
+        /// <summary>
+        ///     A whitelist for what special-digestible-required foods this stomach is capable of eating.
+        /// </summary>
+        [DataField("specialDigestible")]
+        public EntityWhitelist? SpecialDigestible = null;
+
         /// <summary>
         ///     Used to track how long each reagent has been in the stomach
         /// </summary>
index 36f02c0b82dd6dbeff50956ac8fd33ac12caa0f6..3a4bd8e9c8d33253081e1feac8625aa2b2796eae 100644 (file)
@@ -16,7 +16,6 @@ namespace Content.Server.Body.Systems
 
         public override void Initialize()
         {
-            SubscribeLocalEvent<StomachComponent, ComponentInit>(OnComponentInit);
             SubscribeLocalEvent<StomachComponent, ApplyMetabolicMultiplierEvent>(OnApplyMetabolicMultiplier);
         }
 
@@ -87,11 +86,6 @@ namespace Content.Server.Body.Systems
                 component.AccumulatedFrameTime = component.UpdateInterval;
         }
 
-        private void OnComponentInit(EntityUid uid, StomachComponent component, ComponentInit args)
-        {
-            _solutionContainerSystem.EnsureSolution(uid, DefaultSolutionName, component.InitialMaxVolume, out _);
-        }
-
         public bool CanTransferSolution(EntityUid uid, Solution solution,
             SolutionContainerManagerComponent? solutions = null)
         {
index 65477982dedbf520821966ac08c69732faff4adc..5fc68188932c298c5557f0d78c4d2fbe52ec279a 100644 (file)
@@ -5,6 +5,7 @@ using Content.Server.NPC.Queries.Considerations;
 using Content.Server.NPC.Queries.Curves;
 using Content.Server.NPC.Queries.Queries;
 using Content.Server.Nutrition.Components;
+using Content.Server.Nutrition.EntitySystems;
 using Content.Server.Storage.Components;
 using Content.Shared.Examine;
 using Content.Shared.Mobs.Systems;
@@ -24,6 +25,7 @@ public sealed class NPCUtilitySystem : EntitySystem
     [Dependency] private readonly FactionSystem _faction = default!;
     [Dependency] private readonly MobStateSystem _mobState = default!;
     [Dependency] private readonly SharedTransformSystem _transform = default!;
+    [Dependency] private readonly FoodSystem _food = default!;
 
     /// <summary>
     /// Runs the UtilityQueryPrototype and returns the best-matching entities.
@@ -120,6 +122,11 @@ public sealed class NPCUtilitySystem : EntitySystem
                 if (!TryComp<FoodComponent>(targetUid, out var food))
                     return 0f;
 
+                var owner = blackboard.GetValue<EntityUid>(NPCBlackboard.Owner);
+
+                if (!_food.IsDigestibleBy(owner, targetUid, food))
+                    return 0f;
+
                 return 1f;
             }
             case TargetAccessibleCon:
index 5d7d325b0226fad0397fdf4dc76125813e1b0c2f..f6a74f109b8778be10184178670c1cc2d6e6a5bf 100644 (file)
@@ -1,3 +1,4 @@
+using Content.Server.Body.Components;
 using Content.Server.Chemistry.EntitySystems;
 using Content.Server.Nutrition.EntitySystems;
 using Content.Shared.FixedPoint;
@@ -34,6 +35,25 @@ namespace Content.Server.Nutrition.Components
         [DataField("utensilRequired")]
         public bool UtensilRequired = false;
 
+        /// <summary>
+        ///     If this is set to true, eating this food will require you to have a stomach with a
+        ///     <see cref="StomachComponent.SpecialDigestible"/> that includes this entity in its whitelist,
+        ///     rather than just being digestible by anything that can eat food.
+        /// </summary>
+        /// <remarks>
+        ///     TODO think about making this a little more complex, right now you cant disallow mobs from eating stuff
+        ///     that everyone else can eat
+        /// </remarks>
+        [DataField("requiresSpecialDigestion")]
+        public bool RequiresSpecialDigestion = false;
+
+        /// <summary>
+        ///     Stomachs required to digest this entity.
+        ///     Used to simulate 'ruminant' digestive systems (which can digest grass)
+        /// </summary>
+        [DataField("requiredStomachs")]
+        public int RequiredStomachs = 1;
+
         /// <summary>
         /// The localization identifier for the eat message. Needs a "food" entity argument passed to it.
         /// </summary>
index 65132cd63320bcbd9534b1c884e234c17d592faa..41e2e61f28b54cab388d381dff60346dadf1be16 100644 (file)
@@ -1,3 +1,4 @@
+using System.Linq;
 using Content.Server.Body.Components;
 using Content.Server.Body.Systems;
 using Content.Server.Chemistry.EntitySystems;
@@ -5,6 +6,7 @@ using Content.Server.Nutrition.Components;
 using Content.Server.Popups;
 using Content.Shared.Administration.Logs;
 using Content.Shared.Body.Components;
+using Content.Shared.Body.Organ;
 using Content.Shared.Chemistry;
 using Content.Shared.Chemistry.Reagent;
 using Content.Shared.Database;
@@ -85,16 +87,30 @@ namespace Content.Server.Nutrition.EntitySystems
         public bool TryFeed(EntityUid user, EntityUid target, EntityUid food, FoodComponent foodComp)
         {
             //Suppresses self-eating
-            if (food == user || EntityManager.TryGetComponent<MobStateComponent>(food, out var mobState) && _mobStateSystem.IsAlive(food, mobState)) // Suppresses eating alive mobs
+            if (food == user || TryComp<MobStateComponent>(food, out var mobState) && _mobStateSystem.IsAlive(food, mobState)) // Suppresses eating alive mobs
                 return false;
 
             // Target can't be fed or they're already eating
-            if (!EntityManager.HasComponent<BodyComponent>(target))
+            if (!TryComp<BodyComponent>(target, out var body))
                 return false;
 
             if (!_solutionContainerSystem.TryGetSolution(food, foodComp.SolutionName, out var foodSolution) || foodSolution.Name == null)
                 return false;
 
+            if (!_bodySystem.TryGetBodyOrganComponents<StomachComponent>(target, out var stomachs, body))
+                return false;
+
+            var forceFeed = user != target;
+
+            if (!IsDigestibleBy(food, foodComp, stomachs))
+            {
+                _popupSystem.PopupEntity(
+                    forceFeed
+                        ? Loc.GetString("food-system-cant-digest-other", ("entity", food))
+                        : Loc.GetString("food-system-cant-digest", ("entity", food)), user, user);
+                return false;
+            }
+
             var flavors = _flavorProfileSystem.GetLocalizedFlavorsMessage(food, user, foodSolution);
 
             if (foodComp.UsesRemaining <= 0)
@@ -113,8 +129,6 @@ namespace Content.Server.Nutrition.EntitySystems
             if (!TryGetRequiredUtensils(user, foodComp, out _))
                 return true;
 
-            var forceFeed = user != target;
-
             if (forceFeed)
             {
                 var userName = Identity.Entity(user, EntityManager);
@@ -183,11 +197,30 @@ namespace Content.Server.Nutrition.EntitySystems
             var transferAmount = component.TransferAmount != null ? FixedPoint2.Min((FixedPoint2) component.TransferAmount, solution.Volume) : solution.Volume;
 
             var split = _solutionContainerSystem.SplitSolution(uid, solution, transferAmount);
+
             //TODO: Get the stomach UID somehow without nabbing owner
-            var firstStomach = stomachs.FirstOrNull(stomach => _stomachSystem.CanTransferSolution(stomach.Comp.Owner, split));
+            // Get the stomach with the highest available solution volume
+            var highestAvailable = FixedPoint2.Zero;
+            StomachComponent? stomachToUse = null;
+            foreach (var (stomach, _) in stomachs)
+            {
+                var owner = stomach.Owner;
+                if (!_stomachSystem.CanTransferSolution(owner, split))
+                    continue;
+
+                if (!_solutionContainerSystem.TryGetSolution(owner, StomachSystem.DefaultSolutionName,
+                        out var stomachSol))
+                    continue;
+
+                if (stomachSol.AvailableVolume <= highestAvailable)
+                    continue;
+
+                stomachToUse = stomach;
+                highestAvailable = stomachSol.AvailableVolume;
+            }
 
             // No stomach so just popup a message that they can't eat.
-            if (firstStomach == null)
+            if (stomachToUse == null)
             {
                 _solutionContainerSystem.TryAddSolution(uid, solution, split);
                 _popupSystem.PopupEntity(forceFeed ? Loc.GetString("food-system-you-cannot-eat-any-more-other") : Loc.GetString("food-system-you-cannot-eat-any-more"), args.Target.Value, args.User);
@@ -195,7 +228,7 @@ namespace Content.Server.Nutrition.EntitySystems
             }
 
             _reaction.DoEntityReaction(args.Target.Value, solution, ReactionMethod.Ingestion);
-            _stomachSystem.TryTransferSolution(firstStomach.Value.Comp.Owner, split, firstStomach.Value.Comp);
+            _stomachSystem.TryTransferSolution(stomachToUse.Owner, split, stomachToUse);
 
             var flavors = args.FlavorMessage;
 
@@ -285,49 +318,43 @@ namespace Content.Server.Nutrition.EntitySystems
         }
 
         /// <summary>
-        ///     Force feeds someone remotely. Does not require utensils (well, not the normal type anyways).
+        ///     Returns true if the food item can be digested by the user.
         /// </summary>
-        public void ProjectileForceFeed(EntityUid uid, EntityUid target, EntityUid? user, FoodComponent? food = null, BodyComponent? body = null)
+        public bool IsDigestibleBy(EntityUid uid, EntityUid food, FoodComponent? foodComp = null)
         {
-            // TODO: Combine with regular feeding because holy code duplication batman.
-            if (!Resolve(uid, ref food, false) || !Resolve(target, ref body, false))
-                return;
-
-            if (IsMouthBlocked(target))
-                return;
-
-            if (!_solutionContainerSystem.TryGetSolution(uid, food.SolutionName, out var foodSolution))
-                return;
+            if (!Resolve(food, ref foodComp, false))
+                return false;
 
-            if (!_bodySystem.TryGetBodyOrganComponents<StomachComponent>(target, out var stomachs, body))
-                return;
+            if (!_bodySystem.TryGetBodyOrganComponents<StomachComponent>(uid, out var stomachs))
+                return false;
 
-            if (food.UsesRemaining <= 0)
-                DeleteAndSpawnTrash(food, uid);
+            return IsDigestibleBy(food, foodComp, stomachs);
+        }
 
-            var firstStomach = stomachs.FirstOrNull(
-                stomach => _stomachSystem.CanTransferSolution(((IComponent) stomach.Comp).Owner, foodSolution));
+        /// <summary>
+        ///     Returns true if <paramref name="stomachs"/> has a <see cref="StomachComponent"/> that is capable of
+        ///     digesting this <paramref name="food"/> (or if they even have enough stomachs in the first place).
+        /// </summary>
+        private bool IsDigestibleBy(EntityUid food, FoodComponent component, List<(StomachComponent, OrganComponent)> stomachs)
+        {
+            var digestible = true;
 
-            if (firstStomach == null)
-                return;
+            if (stomachs.Count < component.RequiredStomachs)
+                return false;
 
-            // logging
-            if (user == null)
-                _adminLogger.Add(LogType.ForceFeed, $"{ToPrettyString(uid):food} {SolutionContainerSystem.ToPrettyString(foodSolution):solution} was thrown into the mouth of {ToPrettyString(target):target}");
-            else
-                _adminLogger.Add(LogType.ForceFeed, $"{ToPrettyString(user.Value):user} threw {ToPrettyString(uid):food} {SolutionContainerSystem.ToPrettyString(foodSolution):solution} into the mouth of {ToPrettyString(target):target}");
+            if (!component.RequiresSpecialDigestion)
+                return true;
 
-            var filter = user == null ? Filter.Entities(target) : Filter.Entities(target, user.Value);
-            _popupSystem.PopupEntity(Loc.GetString(food.EatMessage, ("food", food.Owner)), target, filter, true);
+            foreach (var (comp, _) in stomachs)
+            {
+                if (comp.SpecialDigestible == null)
+                    continue;
 
-            foodSolution.DoEntityReaction(uid, ReactionMethod.Ingestion);
-            _stomachSystem.TryTransferSolution(((IComponent) firstStomach.Value.Comp).Owner, foodSolution, firstStomach.Value.Comp);
-            SoundSystem.Play(food.UseSound.GetSound(), Filter.Pvs(target), target, AudioParams.Default.WithVolume(-1f));
+                if (!comp.SpecialDigestible.IsValid(food, EntityManager))
+                    return false;
+            }
 
-            if (string.IsNullOrEmpty(food.TrashPrototype))
-                EntityManager.QueueDeleteEntity(food.Owner);
-            else
-                DeleteAndSpawnTrash(food, uid);
+            return digestible;
         }
 
         private bool TryGetRequiredUtensils(EntityUid user, FoodComponent component,
index e42fed40e427d84b4e816fc5095a5f483f94d6ac..bd5766cc0ad65b9e0f0f3a3ddc90c7e4adc48ff2 100644 (file)
@@ -13,8 +13,10 @@ food-system-remove-mask = You need to take off the {$entity} first.
 
 food-system-you-cannot-eat-any-more = You can't eat any more!
 food-system-you-cannot-eat-any-more-other = They can't eat any more!
-food-system-try-use-food-is-empty = {$entity} is empty!
-food-system-wrong-utensil = you can't eat {$food} with a {$utensil}.
+food-system-try-use-food-is-empty = {CAPITALIZE(THE($entity))} is empty!
+food-system-wrong-utensil = You can't eat {THE($food)} with {INDEFINITE($utensil)}.
+food-system-cant-digest = You can't digest {THE($entity)}!
+food-system-cant-digest-other = They can't digest {THE($entity)}!
 
 food-system-verb-eat = Eat
 
similarity index 97%
rename from Resources/Prototypes/Body/Organs/animal.yml
rename to Resources/Prototypes/Body/Organs/Animal/animal.yml
index d8f036e6044d31a8a0357bb61ff337f48e274711..a7a576949075010deae5d99e5af81cabf8bac34f 100644 (file)
@@ -43,9 +43,8 @@
   - type: SolutionContainerManager
     solutions:
       stomach:
-        maxVol: 100
+        maxVol: 40
   - type: Stomach
-    maxVolume: 10
   - type: Metabolizer
     maxReagents: 3
     metabolizerTypes: [ Animal ]
diff --git a/Resources/Prototypes/Body/Organs/Animal/ruminant.yml b/Resources/Prototypes/Body/Organs/Animal/ruminant.yml
new file mode 100644 (file)
index 0000000..3c3062d
--- /dev/null
@@ -0,0 +1,10 @@
+- type: entity
+  id: OrganAnimalRuminantStomach
+  parent: OrganAnimalStomach
+  name: ruminant stomach
+  noSpawn: true
+  components:
+  - type: SolutionContainerManager
+    solutions:
+      stomach:
+        maxVol: 80
index 4af60d876d9df2c2950a563a01ce22b06ebc35f8..154185966443605cac38f17f01e71f89bc4d8d84 100644 (file)
   noSpawn: true
   components:
   - type: Stomach
-    maxVolume: 50
     updateInterval: 1.5
+  - type: SolutionContainerManager
+    solutions:
+      stomach:
+        maxVol: 50
   - type: Metabolizer
     updateFrequency: 1.5
 
index ba2776b073c517a6b9276cb098410026329e346b..868505cb436f58a5637ad7d56e985c3597ad9f76 100644 (file)
@@ -11,7 +11,9 @@
   parent: OrganAnimalStomach
   suffix: "rat"
   components:
-  - type: Stomach
-    maxVolume: 50 # they're hungry
+  - type: SolutionContainerManager
+    solutions:
+      stomach:
+        maxVol: 50
   - type: Sprite
-    state: stomach
\ No newline at end of file
+    state: stomach
index 3233431a0253ca95a5152338f379dea052864ed1..176131380705eeab7cd4142fe1d44c39e9f329b3 100644 (file)
@@ -4,4 +4,7 @@
   noSpawn: true
   components:
   - type: Stomach
-    maxVol: 50
+  - type: SolutionContainerManager
+    solutions:
+      stomach:
+        maxVol: 50
diff --git a/Resources/Prototypes/Body/Prototypes/Animal/ruminant.yml b/Resources/Prototypes/Body/Prototypes/Animal/ruminant.yml
new file mode 100644 (file)
index 0000000..cd3ab1f
--- /dev/null
@@ -0,0 +1,22 @@
+- type: body
+  id: AnimalRuminant
+  name: "ruminant"
+  root: torso
+  slots:
+    torso:
+      part: TorsoAnimal
+      connections:
+      - legs
+      organs:
+        lungs: OrganAnimalLungs
+        stomach: OrganAnimalRuminantStomach
+        stomach2: OrganAnimalRuminantStomach
+        liver: OrganAnimalLiver
+        heart: OrganAnimalHeart
+        kidneys: OrganAnimalKidneys
+    legs:
+      part: LegsAnimal
+      connections:
+      - feet
+    feet:
+      part: FeetAnimal
index fa5b72ebbd953981a40eb2a2f2ea16dc0862d73b..d6ab19ef2f02e869c908df973a2440cf695c9c57 100644 (file)
   - type: Faction
     factions:
     - Passive
+  - type: Body
+    prototype: AnimalRuminant
+  - type: HTN
+    rootTask: RuminantCompound
 
 - type: entity
   name: crab
   - type: Faction
     factions:
     - Passive
+  - type: Body
+    prototype: AnimalRuminant
+  - type: HTN
+    rootTask: RuminantCompound
 
 # Note that we gotta make this bitch vomit someday when you feed it anthrax or sumthin. Needs to be a small item thief too and aggressive if attacked.
 - type: entity
index af62439c8741b5a1bef7924d92ec691db37b9cdc..79d4fc15f5723061137f7de8bd975acdefc6c359 100644 (file)
         kudzu:
           !type:SpreaderNode
           nodeGroupID: Spreader
-
+    - type: Food
+      requiredStomachs: 2 # ruminants have 4 stomachs but i dont care to give them literally 4 stomachs. 2 is good
+      # TODO make botany plants edible to ruminants as well ...
+      delay: 0.5
+    - type: FlavorProfile
+      flavors:
+      - fiber
+    - type: SolutionContainerManager
+      solutions:
+        food:
+          reagents:
+          - ReagentId: Nutriment
+            Quantity: 2
 
 - type: entity
   id: WeakKudzu
       ignoreWhitelist:
         tags:
         - Flesh
+    - type: Food # delightfully devilish !
+      delay: 0.5
+    - type: SolutionContainerManager
+      solutions:
+        food:
+          reagents:
+          - ReagentId: Protein
+            Quantity: 2
index e0a8f6a924957429ce86fe0918480673fd9084e2..dc1b970af8e8f9e9623dbacbc2a23ffb90dbdc32 100644 (file)
     - tasks:
         - id: IdleCompound
 
+- type: htnCompound
+  id: RuminantCompound
+  branches:
+  - tasks:
+    - id: FoodCompound
+  - tasks:
+    - id: IdleCompound
+
 - type: htnCompound
   id: DragonCarpCompound
   branches: