From 2976177f2a5b82990e27d7ba49de761d726623b8 Mon Sep 17 00:00:00 2001 From: Ava <47678781+Ilysen@users.noreply.github.com> Date: Sun, 9 Apr 2023 14:49:59 -0400 Subject: [PATCH] Slimes can eat loose food, decals, dirt, and so on (#9064) * Slimes can eat food, decals, dirt, and so on * Bugfix I found in the course of math * I think I wrote that as a fallback but I'm not sure * review fix - tested this and it seems to work --- code/game/atoms.dm | 8 ++++ .../effects/decals/Cleanable/humans.dm | 5 +++ .../objects/effects/decals/Cleanable/misc.dm | 12 ++++++ .../effects/decals/Cleanable/robots.dm | 3 +- code/game/objects/effects/decals/cleanable.dm | 13 +++++++ code/game/turfs/simulated.dm | 12 ++++++ .../ai/ai_holder_subtypes/slime_xenobio_ai.dm | 37 ++++++++++++++++++- code/modules/food/food/snacks.dm | 25 +++++++++++++ .../subtypes/slime/xenobio/xenobio.dm | 19 ++++++++++ code/modules/organs/organ.dm | 13 +++++++ 10 files changed, 145 insertions(+), 2 deletions(-) diff --git a/code/game/atoms.dm b/code/game/atoms.dm index 254289474c..e63a02358a 100644 --- a/code/game/atoms.dm +++ b/code/game/atoms.dm @@ -676,6 +676,14 @@ var/global/list/pre_init_created_atoms // atom creation ordering means some stuf /atom/proc/get_visible_gender(mob/user, force) return gender +/// Checks if slimes will target this atom and move towards it. Defaults to null. +/atom/proc/is_slime_food() + return FALSE + +/// Called when a slime interacts with this atom as determined by `is_slime_food()`. +/// * `slime` - The slime causing the interaction. +/atom/proc/slime_chomp(mob/living/simple_mob/slime/xenobio/slime) + return /** * Constructs an atom/Topic link for the callee. diff --git a/code/game/objects/effects/decals/Cleanable/humans.dm b/code/game/objects/effects/decals/Cleanable/humans.dm index 8e1281a79c..cd2a574116 100644 --- a/code/game/objects/effects/decals/Cleanable/humans.dm +++ b/code/game/objects/effects/decals/Cleanable/humans.dm @@ -20,6 +20,7 @@ var/global/list/image/splatter_cache=list() var/synthblood = 0 var/list/datum/disease2/disease/virus2 = list() var/amount = 5 + slime_food = TRUE generic_filth = TRUE persistent = FALSE @@ -65,6 +66,10 @@ var/global/list/image/splatter_cache=list() name = initial(name) desc = initial(desc) +/obj/effect/decal/cleanable/blood/slime_chomp(mob/living/simple_mob/slime/xenobio/slime) + slime.adjust_nutrition(max(0, amount - 1)) // Gain a bit of extra nutrition based on the amount + return ..() + /mob/living/carbon/human/walk_through_blood(var/obj/effect/decal/cleanable/blood/blood) var/obj/item/organ/external/l_foot = get_organ("l_foot") var/obj/item/organ/external/r_foot = get_organ("r_foot") diff --git a/code/game/objects/effects/decals/Cleanable/misc.dm b/code/game/objects/effects/decals/Cleanable/misc.dm index fa6ceaf5ed..4d6fa44d88 100644 --- a/code/game/objects/effects/decals/Cleanable/misc.dm +++ b/code/game/objects/effects/decals/Cleanable/misc.dm @@ -11,6 +11,7 @@ name = "ashes" desc = "Ashes to ashes, dust to dust, and into space." gender = PLURAL + slime_food = TRUE icon = 'icons/obj/objects.dmi' icon_state = "ash" anchored = 1 @@ -34,6 +35,7 @@ gender = PLURAL density = 0 anchored = 1 + slime_food = TRUE icon = 'icons/effects/effects.dmi' icon_state = "dirt" mouse_opacity = 0 @@ -44,6 +46,7 @@ gender = PLURAL density = 0 anchored = 1 + slime_food = TRUE icon = 'icons/effects/effects.dmi' icon_state = "flour" @@ -54,6 +57,7 @@ density = 0 anchored = 1 light_range = 1 + slime_food = TRUE icon = 'icons/effects/effects.dmi' icon_state = "greenglow" @@ -63,6 +67,7 @@ density = 0 anchored = 1 plane = OBJ_PLANE + slime_food = TRUE icon = 'icons/effects/effects.dmi' icon_state = "cobweb1" @@ -72,6 +77,7 @@ density = 0 anchored = 1 plane = OBJ_PLANE + slime_food = TRUE icon = 'icons/obj/chemical.dmi' icon_state = "molten" @@ -81,6 +87,7 @@ density = 0 anchored = 1 plane = OBJ_PLANE + slime_food = TRUE icon = 'icons/effects/effects.dmi' icon_state = "cobweb2" @@ -91,6 +98,7 @@ gender = PLURAL density = 0 anchored = 1 + slime_food = TRUE icon = 'icons/effects/blood.dmi' icon_state = "vomit_1" random_icon_states = list("vomit_1", "vomit_2", "vomit_3", "vomit_4") @@ -101,6 +109,7 @@ desc = "It's red." density = 0 anchored = 1 + slime_food = TRUE icon = 'icons/effects/tomatodecal.dmi' random_icon_states = list("tomato_floor1", "tomato_floor2", "tomato_floor3") @@ -109,6 +118,7 @@ desc = "Seems like this one won't hatch." density = 0 anchored = 1 + slime_food = TRUE icon = 'icons/effects/tomatodecal.dmi' random_icon_states = list("smashed_egg1", "smashed_egg2", "smashed_egg3") @@ -117,6 +127,7 @@ desc = "It's pie cream from a cream pie." density = 0 anchored = 1 + slime_food = TRUE icon = 'icons/effects/tomatodecal.dmi' random_icon_states = list("smashed_pie") @@ -125,6 +136,7 @@ desc = "Some kind of fruit smear." density = 0 anchored = 1 + slime_food = TRUE icon = 'icons/effects/blood.dmi' icon_state = "mfloor1" random_icon_states = list("mfloor1", "mfloor2", "mfloor3", "mfloor4", "mfloor5", "mfloor6", "mfloor7") diff --git a/code/game/objects/effects/decals/Cleanable/robots.dm b/code/game/objects/effects/decals/Cleanable/robots.dm index ccafd9ecc1..16ea69c868 100644 --- a/code/game/objects/effects/decals/Cleanable/robots.dm +++ b/code/game/objects/effects/decals/Cleanable/robots.dm @@ -5,6 +5,7 @@ icon_state = "gib1" basecolor = SYNTH_BLOOD_COLOUR random_icon_states = list("gib1", "gib2", "gib3", "gib4", "gib5", "gib6", "gib7") + slime_food = FALSE generic_filth = FALSE persistent = FALSE @@ -49,4 +50,4 @@ /obj/effect/decal/cleanable/blood/oil/streak random_icon_states = list("mgibbl1", "mgibbl2", "mgibbl3", "mgibbl4", "mgibbl5") - amount = 2 \ No newline at end of file + amount = 2 diff --git a/code/game/objects/effects/decals/cleanable.dm b/code/game/objects/effects/decals/cleanable.dm index 6873dea89d..8953d6faf3 100644 --- a/code/game/objects/effects/decals/cleanable.dm +++ b/code/game/objects/effects/decals/cleanable.dm @@ -7,6 +7,8 @@ generic_filth = TRUE means when the decal is saved, it will be switched out for /obj/effect/decal/cleanable plane = DIRTY_PLANE + /// If `TRUE`, slimes can "eat" this decal for a small amount of nutrition. + var/slime_food = FALSE var/persistent = FALSE var/generic_filth = FALSE var/age = 0 @@ -32,3 +34,14 @@ generic_filth = TRUE means when the decal is saved, it will be switched out for qdel(src) return ..() + +/obj/effect/decal/cleanable/is_slime_food() + return slime_food + +/obj/effect/decal/cleanable/slime_chomp(mob/living/simple_mob/slime/xenobio/slime) // The base amount is always the same, but not all decals can be slime'd up + slime.adjust_nutrition(1) + slime.visible_message( + SPAN_NOTICE("\The [slime] rolls over \the [src] and absorbs it from \the [loc]."), + SPAN_NOTICE("You absorb \the [src]!") + ) + qdel(src) diff --git a/code/game/turfs/simulated.dm b/code/game/turfs/simulated.dm index e305f3b126..526e7184cf 100644 --- a/code/game/turfs/simulated.dm +++ b/code/game/turfs/simulated.dm @@ -171,3 +171,15 @@ new /obj/effect/decal/cleanable/blood/oil(src) else if(ishuman(M)) add_blood(M) + +/turf/simulated/is_slime_food() + return dirt >= 50 + +/turf/simulated/slime_chomp(mob/living/simple_mob/slime/xenobio/slime) + slime.adjust_nutrition(round(dirt / 5)) + dirt = 0 + slime.visible_message( + SPAN_NOTICE("\The [slime] cleans the dirt off of \the [src]!"), + SPAN_NOTICE("You eat all the dirt right off \the [src].") + ) + update_dirt() diff --git a/code/modules/ai/ai_holder_subtypes/slime_xenobio_ai.dm b/code/modules/ai/ai_holder_subtypes/slime_xenobio_ai.dm index 2f674b708a..a05d6d722f 100644 --- a/code/modules/ai/ai_holder_subtypes/slime_xenobio_ai.dm +++ b/code/modules/ai/ai_holder_subtypes/slime_xenobio_ai.dm @@ -112,8 +112,34 @@ adjust_discipline(-1) last_discipline_decay = world.time +/datum/ai_holder/simple_mob/xenobio_slime/find_target(list/possible_targets, has_targets_list) + if (hostile) + . = ..() + if (target || leader || rabid) // Always prioritize mobs, if we can. Otherwise, check for loose things to eat if we're not otherwise busy + return + var/list/yummies + for (var/atom/M in view(world.view, holder)) + if (M.is_slime_food()) + // Make sure that we can reach the object first, but don't try too hard at it and make sure that the thing is edible beforehand + var/list/path = AStar(holder.loc, M.loc, astar_adjacent_proc, /turf/proc/Distance, min_target_dist = 1, max_node_depth = world.view * 2, id = holder.IGetID(), exclude = obstacles) + if (path?.len) + LAZYDISTINCTADD(yummies, M) + if (!LAZYLEN(yummies)) + return + var/atom/yummy + var/closest_distance = INFINITY + for (var/atom/M in yummies) // Find the closest snack! + var/dist = get_dist(holder, M) + if (dist < closest_distance) + yummy = M + closest_distance = dist + give_target(yummy) + give_destination(yummy.loc) + return yummy + /datum/ai_holder/simple_mob/xenobio_slime/handle_special_tactic() evolve_and_reproduce() + nom() // Hit the correct verbs to keep the slime species going. /datum/ai_holder/simple_mob/xenobio_slime/proc/evolve_and_reproduce() @@ -125,6 +151,15 @@ else my_slime.evolve() // Turns our holder into an adult slime. +/// If we have a non-mob target, attempt to eat it and then stop tracking it. +/datum/ai_holder/simple_mob/xenobio_slime/proc/nom() + if (QDELETED(target) || ismob(target) || holder?.incapacitated() || !holder.Adjacent(target) || !target.is_slime_food()) + return + holder.face_atom(target) + holder.do_attack_animation(target) + holder.setClickCooldown(holder.get_attack_speed()) + target.slime_chomp(holder) + target = null // Called when pushed too far (or a red slime core was used). /datum/ai_holder/simple_mob/xenobio_slime/proc/enrage() @@ -289,4 +324,4 @@ /datum/ai_holder/simple_mob/xenobio_slime/can_violently_breakthrough() if(discipline && !rabid) // Good slimes don't shatter the windows because their buddy in an adjacent cell decided to piss off Slimesky. return FALSE - return ..() \ No newline at end of file + return ..() diff --git a/code/modules/food/food/snacks.dm b/code/modules/food/food/snacks.dm index 351ab11a00..8495399e24 100644 --- a/code/modules/food/food/snacks.dm +++ b/code/modules/food/food/snacks.dm @@ -224,6 +224,31 @@ if(package_open_state) icon_state = package_open_state +/obj/item/reagent_containers/food/snacks/is_slime_food() + return TRUE + +/obj/item/reagent_containers/food/snacks/slime_chomp(mob/living/simple_mob/slime/xenobio/slime) + if (package) + unpackage(slime) + // Reagents are only for carbons (for now), so we calculate and apply nutrition manually + var/nutrition_total = 1 + if (reagents?.total_volume) + for (var/V in reagents.reagent_list) + var/datum/reagent/R = V + if (istype(R, /datum/reagent/nutriment)) + var/datum/reagent/nutriment/N = R + nutrition_total += N.volume * round(N.nutriment_factor / (N.allergen_type & ALLERGEN_MEAT ? 15 : 30)) + slime.adjust_nutrition(nutrition_total) + slime.visible_message( + SPAN_NOTICE("\The [slime] [pick("absorbs", "consumes", "devours", "eats", "engulfs", "envelops", "schlorps up", "vacuums up")] \the [src]!"), + SPAN_NOTICE("You absorb \the [src]!") + ) + playsound(slime, 'sound/items/eatfood.ogg', rand(10, 50), TRUE) + playsound(slime, 'sound/effects/slime_squish.ogg', 30, TRUE) + if (trash) + new trash (get_turf(src)) + qdel(src) + //////////////////////////////////////////////////////////////////////////////// /// FOOD END //////////////////////////////////////////////////////////////////////////////// diff --git a/code/modules/mob/living/simple_mob/subtypes/slime/xenobio/xenobio.dm b/code/modules/mob/living/simple_mob/subtypes/slime/xenobio/xenobio.dm index dd22ad7fd0..a4af9f0d9e 100644 --- a/code/modules/mob/living/simple_mob/subtypes/slime/xenobio/xenobio.dm +++ b/code/modules/mob/living/simple_mob/subtypes/slime/xenobio/xenobio.dm @@ -94,6 +94,25 @@ else if(AI.discipline) . += "It has been subjugated by force, at least for now." +/mob/living/simple_mob/slime/xenobio/attackby(obj/item/I, mob/user) + // If you're feeling really confident, you can hand-feed your slimes + if (I.is_slime_food() && user.a_intent == I_HELP) + var/datum/ai_holder/simple_mob/xenobio_slime/AI = ai_holder + if (!istype(AI)) + to_chat(user, SPAN_WARNING("\The [src] doesn't seem to be interested in \the [I].")) + else if (ismob(AI.target) || AI.rabid) + to_chat(user, SPAN_WARNING("\The [src] is a little too preoccupied to be interested in \the [I]!")) + else if (AI.resentment > AI.discipline || !AI.obedience) + to_chat(user, SPAN_WARNING("\The [src] shows no interest in \the [I].")) + else + I.slime_chomp(src) + if (prob(25)) + AI.delayed_say("[pick("Thank you...", "Food...", "Thanks...")]") + AI.evolve_and_reproduce() + user.setClickCooldown(2 SECONDS) + return + return ..() + /mob/living/simple_mob/slime/xenobio/proc/make_adult() if(is_adult) return diff --git a/code/modules/organs/organ.dm b/code/modules/organs/organ.dm index b44627ea47..d602546530 100644 --- a/code/modules/organs/organ.dm +++ b/code/modules/organs/organ.dm @@ -558,3 +558,16 @@ var/global/list/organ_cache = list() critter.eat_food_item(src) return TRUE return ..() + +/obj/item/organ/is_slime_food() + return !robotic // no yucky metal + +/obj/item/organ/slime_chomp(mob/living/simple_mob/slime/xenobio/slime) + slime.adjust_nutrition(max(10, w_class * 10)) + slime.visible_message( + SPAN_NOTICE("\The [slime] [pick("absorbs", "consumes", "devours", "eats", "engulfs", "envelops", "schlorps up", "vacuums up")] \the [src]!"), + SPAN_NOTICE("You absorb \the [src]!") + ) + playsound(slime, 'sound/items/eatfood.ogg', rand(10, 50), TRUE) + playsound(slime, 'sound/effects/slime_squish.ogg', 30, TRUE) + qdel(src)