From 940e2d31cfdf548d0f0b215fc4f58f415b832546 Mon Sep 17 00:00:00 2001 From: Jacquerel Date: Mon, 27 Jan 2025 12:54:34 +0000 Subject: [PATCH] tgstation is back online and you are a horrible goose (#89204) ## About The Pull Request Converts geese to basic mobs. Nobody else did this one because two separate other developers have said they started and then examining what the goose does made them feel mildly ill, but I am stronger. I will admit though I wasn't 100% committed to making it work exactly the same way, I rewrote the entire system to use interfaces I like more (read: I put all this shit in a status effect which means any mob can be given the ability to vomit out everything in its contents) and if that means the behaviour is only "inspired by" that didn't bother me that much. **Geese:** - Wander randomly around. - Peck people who attack them. - Occasionally start pecking other nearby animals for absolutely no reason. - Eat any food they randomly wander within one tile of, but don't seek it out further than that. - Eat anything made of plastic that they randomly wander within one tile of. - Choke to death over 30 seconds if they eat anything made of plastic. - Vomit out whatever it was that they choked on when they die. - Honk (this is new). The more famous subtype of goose is Birdboat. Birdboat is a unique goose present on several maps with some additional behaviour. **Birdboat:** - Is chill and doesn't start pecking people for no reason. - Is occasionally possessed by ghosts. - Builds up an internal vomit-meter as he eats things. Moving around and just sort of generally hanging will start rolling dice to find out when Birdboat's tummy gets upset. - May start vomiting instead of choking to death on plastic, thereby saving his own life. - Vomits out everything that he just ate while running around, making a mess of the floor. - Starts eating everything he just vomited out again. Unlike regular geese who just eat your food and it's gone, Birdboat's miraculous digestion preserves all of the food he eats so if he consumes the entire kitchen counter it will eventually come back out again the way it went in. Although you might not want to eat it any more. The precise way in which this manifests may be slightly different, but largely this is also what these animals did before. Other stuff: I noticed a bunch of find/set behaviours were not setting a search range? I think that means they were never finding anything? I did not actually test any of them to see if they were broken, but it's possible that a bunch of broken AI behaviours like "climbing trees" may now actually start triggering because they have a search radius greater than an orange of 0. I added "keep this in contents instead of deleting it" as a parameter for generic eating and slapped it on the goldgrub, as it is used in two places and may end up being used in more. ## Why It's Good For The Game This kills off the last user of the `retaliate` subtype and makes our list so so much closer to finish. It's like... a couple of bots, a handful of oddballs (I'll probably handle these soon), and then just the mining bosses and minibosses to go. If you give a human the vomit goose ability (now that I made it work on any mob) they will eject all their organs and body parts via the mouth until they die, if you don't do the brain or heart first you can vomit your own head off. ## Changelog :cl: refactor: Geese have been moved to the basic mob subsystem, please report any unusual behaviour. /:cl: --------- Co-authored-by: Ben10Omintrix <138636438+Ben10Omintrix@users.noreply.github.com> --- .../Basketball/greytide_worldwide.dmm | 2 +- .../map_files/Deltastation/DeltaStation2.dmm | 2 +- .../map_files/IceBoxStation/IceBoxStation.dmm | 2 +- _maps/map_files/MetaStation/MetaStation.dmm | 2 +- _maps/map_files/wawastation/wawastation.dmm | 2 +- code/__DEFINES/ai/ai_blackboard.dm | 8 + code/_globalvars/lists/mobs.dm | 1 - code/_globalvars/phobias.dm | 2 +- .../ai/basic_mobs/basic_subtrees/find_food.dm | 6 +- code/datums/ai/generic/find_and_set.dm | 2 +- code/datums/elements/basic_eating.dm | 17 +- code/game/turfs/turf.dm | 2 +- .../modules/deathmatch/deathmatch_modifier.dm | 2 +- code/modules/events/ghost_role/sentience.dm | 2 +- code/modules/events/vent_clog.dm | 2 +- code/modules/events/wizard/petsplosion.dm | 2 +- .../living/basic/farm_animals/goose/goose.dm | 177 ++++++++++++ .../basic/farm_animals/goose/goose_ai.dm | 82 ++++++ .../basic/farm_animals/goose/goose_vomit.dm | 187 ++++++++++++ .../basic/lavaland/goldgrub/goldgrub.dm | 17 +- .../simple_animal/hostile/retaliate/goose.dm | 269 ------------------ .../hostile/retaliate/retaliate.dm | 59 ---- .../unit_tests/simple_animal_freeze.dm | 3 - tgstation.dme | 5 +- .../Scripts/89204_simple_to_basic_goose.txt | 1 + 25 files changed, 496 insertions(+), 360 deletions(-) create mode 100644 code/modules/mob/living/basic/farm_animals/goose/goose.dm create mode 100644 code/modules/mob/living/basic/farm_animals/goose/goose_ai.dm create mode 100644 code/modules/mob/living/basic/farm_animals/goose/goose_vomit.dm delete mode 100644 code/modules/mob/living/simple_animal/hostile/retaliate/goose.dm delete mode 100644 code/modules/mob/living/simple_animal/hostile/retaliate/retaliate.dm create mode 100644 tools/UpdatePaths/Scripts/89204_simple_to_basic_goose.txt diff --git a/_maps/map_files/Basketball/greytide_worldwide.dmm b/_maps/map_files/Basketball/greytide_worldwide.dmm index 77c5f30af4c..6711c66743d 100644 --- a/_maps/map_files/Basketball/greytide_worldwide.dmm +++ b/_maps/map_files/Basketball/greytide_worldwide.dmm @@ -704,7 +704,7 @@ /obj/effect/turf_decal/box/red/corners{ dir = 1 }, -/mob/living/simple_animal/hostile/retaliate/goose/vomit, +/mob/living/basic/goose/vomit, /obj/effect/turf_decal/tile/neutral/fourcorners, /turf/open/floor/iron/dark, /area/centcom/basketball) diff --git a/_maps/map_files/Deltastation/DeltaStation2.dmm b/_maps/map_files/Deltastation/DeltaStation2.dmm index c1e1b7aaf48..e11938e405d 100644 --- a/_maps/map_files/Deltastation/DeltaStation2.dmm +++ b/_maps/map_files/Deltastation/DeltaStation2.dmm @@ -36288,7 +36288,7 @@ dir = 8 }, /obj/effect/turf_decal/tile/neutral/fourcorners, -/mob/living/simple_animal/hostile/retaliate/goose/vomit, +/mob/living/basic/goose/vomit, /turf/open/floor/iron/dark, /area/station/service/abandoned_gambling_den) "jap" = ( diff --git a/_maps/map_files/IceBoxStation/IceBoxStation.dmm b/_maps/map_files/IceBoxStation/IceBoxStation.dmm index ecda43c7556..07594b2b8d4 100644 --- a/_maps/map_files/IceBoxStation/IceBoxStation.dmm +++ b/_maps/map_files/IceBoxStation/IceBoxStation.dmm @@ -38588,7 +38588,7 @@ /area/mine/laborcamp) "kYF" = ( /obj/structure/light_construct/directional/west, -/mob/living/simple_animal/hostile/retaliate/goose/vomit, +/mob/living/basic/goose/vomit, /turf/open/floor/wood, /area/station/maintenance/port/aft) "kYI" = ( diff --git a/_maps/map_files/MetaStation/MetaStation.dmm b/_maps/map_files/MetaStation/MetaStation.dmm index c69694b8f48..4a78966445f 100644 --- a/_maps/map_files/MetaStation/MetaStation.dmm +++ b/_maps/map_files/MetaStation/MetaStation.dmm @@ -14361,7 +14361,7 @@ /turf/open/floor/iron, /area/station/hallway/secondary/exit/departure_lounge) "fgn" = ( -/mob/living/simple_animal/hostile/retaliate/goose/vomit, +/mob/living/basic/goose/vomit, /turf/open/floor/wood, /area/station/maintenance/port/aft) "fgu" = ( diff --git a/_maps/map_files/wawastation/wawastation.dmm b/_maps/map_files/wawastation/wawastation.dmm index 64cad41bbeb..feae642f8b1 100644 --- a/_maps/map_files/wawastation/wawastation.dmm +++ b/_maps/map_files/wawastation/wawastation.dmm @@ -39018,7 +39018,7 @@ "nMj" = ( /obj/effect/turf_decal/sand/plating, /obj/effect/mapping_helpers/broken_floor, -/mob/living/simple_animal/hostile/retaliate/goose/vomit, +/mob/living/basic/goose/vomit, /turf/open/floor/plating, /area/station/maintenance/central/greater) "nMk" = ( diff --git a/code/__DEFINES/ai/ai_blackboard.dm b/code/__DEFINES/ai/ai_blackboard.dm index f7f77a7169e..161f9973b36 100644 --- a/code/__DEFINES/ai/ai_blackboard.dm +++ b/code/__DEFINES/ai/ai_blackboard.dm @@ -49,6 +49,11 @@ #define BB_BANE_BATMAN "BB_bane_batman" //yep that's it +/// Are we a panicking goose? +#define BB_GOOSE_PANICKED "BB_goose_panicked" +/// Are we a panicking goose? +#define BB_GOOSE_VOMIT_CHANCE "BB_goose_vomit_chance" + //Hunting BB keys ///key that holds our current hunting target #define BB_CURRENT_HUNTING_TARGET "BB_current_hunting_target" @@ -140,6 +145,9 @@ /// Blackboard key for a held item #define BB_SIMPLE_CARRY_ITEM "BB_SIMPLE_CARRY_ITEM" +///key holding a range to look for stuff in +#define BB_SEARCH_RANGE "BB_search_range" + ///Mob the MOD is trying to attach to #define BB_MOD_TARGET "BB_mod_target" ///The implant the AI was created from diff --git a/code/_globalvars/lists/mobs.dm b/code/_globalvars/lists/mobs.dm index 6024f799138..73c75fec3ba 100644 --- a/code/_globalvars/lists/mobs.dm +++ b/code/_globalvars/lists/mobs.dm @@ -30,7 +30,6 @@ GLOBAL_LIST_INIT(abstract_mob_types, list( /mob/living/simple_animal/hostile/asteroid, /mob/living/simple_animal/hostile/megafauna, /mob/living/basic/mimic, // Cannot exist if spawned without being passed an item reference - /mob/living/simple_animal/hostile/retaliate, /mob/living/simple_animal/hostile, /mob/living/simple_animal/soulscythe, // As mimic, can't exist if spawned outside an item /mob/living/simple_animal, diff --git a/code/_globalvars/phobias.dm b/code/_globalvars/phobias.dm index c482722fbf9..699e9b4de01 100644 --- a/code/_globalvars/phobias.dm +++ b/code/_globalvars/phobias.dm @@ -69,10 +69,10 @@ GLOBAL_LIST_INIT(phobia_mobs, list( "birds" = typecacheof(list( /mob/living/basic/chick, /mob/living/basic/chicken, + /mob/living/basic/goose, /mob/living/basic/parrot, /mob/living/basic/pet/penguin, /mob/living/basic/raptor, - /mob/living/simple_animal/hostile/retaliate/goose, )), "conspiracies" = typecacheof(list( /mob/living/basic/drone, diff --git a/code/datums/ai/basic_mobs/basic_subtrees/find_food.dm b/code/datums/ai/basic_mobs/basic_subtrees/find_food.dm index feca9869093..9b3e2883290 100644 --- a/code/datums/ai/basic_mobs/basic_subtrees/find_food.dm +++ b/code/datums/ai/basic_mobs/basic_subtrees/find_food.dm @@ -8,6 +8,8 @@ var/found_food_key = BB_TARGET_FOOD ///key holding any emotes we play after eating food var/emotes_blackboard_list = BB_EAT_EMOTES + ///key where we store our search range + var/search_range = BB_SEARCH_RANGE /datum/ai_planning_subtree/find_food/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) var/list/foods_list = controller.blackboard[food_list_key] @@ -15,9 +17,11 @@ CRASH("the types of food has not been supplied in the [food_list_key] key!") if(controller.blackboard[BB_NEXT_FOOD_EAT] > world.time) return + if(!controller.blackboard_key_exists(found_food_key)) - controller.queue_behavior(finding_behavior, found_food_key, foods_list) + controller.queue_behavior(finding_behavior, found_food_key, foods_list, controller.blackboard[BB_SEARCH_RANGE]) return + controller.queue_behavior(/datum/ai_behavior/interact_with_target/eat_food, found_food_key, emotes_blackboard_list) return SUBTREE_RETURN_FINISH_PLANNING diff --git a/code/datums/ai/generic/find_and_set.dm b/code/datums/ai/generic/find_and_set.dm index c7534510b6a..dd25a730629 100644 --- a/code/datums/ai/generic/find_and_set.dm +++ b/code/datums/ai/generic/find_and_set.dm @@ -17,7 +17,7 @@ controller.set_blackboard_key(set_key, find_this_thing) return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED -/datum/ai_behavior/find_and_set/proc/search_tactic(datum/ai_controller/controller, locate_path, search_range) +/datum/ai_behavior/find_and_set/proc/search_tactic(datum/ai_controller/controller, locate_path, search_range = 3) return locate(locate_path) in oview(search_range, controller.pawn) /** diff --git a/code/datums/elements/basic_eating.dm b/code/datums/elements/basic_eating.dm index 1e148b30b15..0e292054677 100644 --- a/code/datums/elements/basic_eating.dm +++ b/code/datums/elements/basic_eating.dm @@ -14,10 +14,12 @@ var/damage_type /// Whether to flavor it as drinking rather than eating. var/drinking + /// If true, we put food in our tummy instead of deleting it + var/add_to_contents /// Types the animal can eat. var/list/food_types -/datum/element/basic_eating/Attach(datum/target, heal_amt = 0, damage_amount = 0, damage_type = null, drinking = FALSE, food_types = list()) +/datum/element/basic_eating/Attach(datum/target, heal_amt = 0, damage_amount = 0, damage_type = null, drinking = FALSE, add_to_contents = FALSE, food_types = list()) . = ..() if(!isliving(target)) @@ -28,6 +30,7 @@ src.damage_amount = damage_amount src.damage_type = damage_type src.drinking = drinking + src.add_to_contents = add_to_contents src.food_types = food_types RegisterSignal(target, COMSIG_ATOM_ITEM_INTERACTION, PROC_REF(try_feed)) @@ -65,6 +68,8 @@ return FALSE if(SEND_SIGNAL(eater, COMSIG_MOB_PRE_EAT, target, feeder) & COMSIG_MOB_CANCEL_EAT) return FALSE + if(add_to_contents && !ismovable(target)) + return FALSE var/eat_verb if(drinking) eat_verb = pick("slurp","sip","guzzle","drink","quaff","suck") @@ -98,5 +103,11 @@ if(isstack(target)) //if stack, only consume 1 var/obj/item/stack/food_stack = target final_target = food_stack.split_stack(eater, 1) - eater.log_message("has eaten [target]!", LOG_ATTACK) - qdel(final_target) + + eater.log_message("has eaten [target], [add_to_contents ? "swallowing it" : "destroying it"]!", LOG_ATTACK) + + if (add_to_contents) + var/atom/movable/movable_target = final_target + movable_target.forceMove(eater) + else + qdel(final_target) diff --git a/code/game/turfs/turf.dm b/code/game/turfs/turf.dm index ab5d93f7064..cb0e0b24cca 100644 --- a/code/game/turfs/turf.dm +++ b/code/game/turfs/turf.dm @@ -657,7 +657,7 @@ GLOBAL_LIST_EMPTY(station_turfs) return TRUE /turf/proc/add_vomit_floor(mob/living/vomiter, vomit_type = /obj/effect/decal/cleanable/vomit, vomit_flags, purge_ratio = 0.1) - var/obj/effect/decal/cleanable/vomit/throw_up = new vomit_type (src, vomiter.get_static_viruses()) + var/obj/effect/decal/cleanable/vomit/throw_up = new vomit_type (src, vomiter?.get_static_viruses()) // if the vomit combined, apply toxicity and reagents to the old vomit if (QDELETED(throw_up)) diff --git a/code/modules/deathmatch/deathmatch_modifier.dm b/code/modules/deathmatch/deathmatch_modifier.dm index 77ef9785aba..ad2ab6adb62 100644 --- a/code/modules/deathmatch/deathmatch_modifier.dm +++ b/code/modules/deathmatch/deathmatch_modifier.dm @@ -391,6 +391,7 @@ /mob/living/basic/dark_wizard = 2, /mob/living/basic/flesh_spider = 2, /mob/living/basic/garden_gnome = 2, + /mob/living/basic/goose = 2, /mob/living/basic/killer_tomato = 2, /mob/living/basic/leaper = 1, /mob/living/basic/mega_arachnid = 1, @@ -404,7 +405,6 @@ /mob/living/basic/spider/giant/nurse/scrawny = 2, /mob/living/basic/spider/giant/tarantula/scrawny = 2, /mob/living/basic/spider/giant/hunter/scrawny = 2, - /mob/living/simple_animal/hostile/retaliate/goose = 2, /mob/living/simple_animal/hostile/ooze = 1, ) diff --git a/code/modules/events/ghost_role/sentience.dm b/code/modules/events/ghost_role/sentience.dm index b12dd5c5174..60b43216ab2 100644 --- a/code/modules/events/ghost_role/sentience.dm +++ b/code/modules/events/ghost_role/sentience.dm @@ -6,6 +6,7 @@ GLOBAL_LIST_INIT(high_priority_sentience, typecacheof(list( /mob/living/basic/crab, /mob/living/basic/cow, /mob/living/basic/goat, + /mob/living/basic/goose/vomit, /mob/living/basic/lizard, /mob/living/basic/mouse/brown/tom, /mob/living/basic/parrot, @@ -17,7 +18,6 @@ GLOBAL_LIST_INIT(high_priority_sentience, typecacheof(list( /mob/living/basic/snake, /mob/living/basic/spider/giant/sgt_araneus, /mob/living/simple_animal/bot/secbot/beepsky, - /mob/living/simple_animal/hostile/retaliate/goose/vomit, /mob/living/basic/bear/snow/misha, /mob/living/basic/mining/lobstrosity/juvenile, ))) diff --git a/code/modules/events/vent_clog.dm b/code/modules/events/vent_clog.dm index c3abb3d2bd1..bd990e98c34 100644 --- a/code/modules/events/vent_clog.dm +++ b/code/modules/events/vent_clog.dm @@ -295,11 +295,11 @@ var/static/list/mob_list = list( /mob/living/basic/bear, /mob/living/basic/cockroach/glockroach/mobroach, + /mob/living/basic/goose, /mob/living/basic/lightgeist, /mob/living/basic/mothroach, /mob/living/basic/mushroom, /mob/living/basic/viscerator, - /mob/living/simple_animal/hostile/retaliate/goose, //Janitors HATE geese. /mob/living/basic/pet/gondola, ) return pick(mob_list) diff --git a/code/modules/events/wizard/petsplosion.dm b/code/modules/events/wizard/petsplosion.dm index 58d463ab9df..09ee4348f70 100644 --- a/code/modules/events/wizard/petsplosion.dm +++ b/code/modules/events/wizard/petsplosion.dm @@ -6,6 +6,7 @@ GLOBAL_LIST_INIT(petsplosion_candidates, typecacheof(list( /mob/living/basic/chicken, /mob/living/basic/cow, /mob/living/basic/goat, + /mob/living/basic/goose/vomit, /mob/living/basic/lizard, /mob/living/basic/mothroach, /mob/living/basic/mouse/brown/tom, @@ -17,7 +18,6 @@ GLOBAL_LIST_INIT(petsplosion_candidates, typecacheof(list( /mob/living/basic/sloth, /mob/living/basic/snake, /mob/living/basic/spider/giant/sgt_araneus, - /mob/living/simple_animal/hostile/retaliate/goose/vomit, ))) /datum/round_event_control/wizard/petsplosion //the horror diff --git a/code/modules/mob/living/basic/farm_animals/goose/goose.dm b/code/modules/mob/living/basic/farm_animals/goose/goose.dm new file mode 100644 index 00000000000..0a34400f161 --- /dev/null +++ b/code/modules/mob/living/basic/farm_animals/goose/goose.dm @@ -0,0 +1,177 @@ +/// The most random things a goose can have inside of it +#define GOOSE_SATIATED 50 + +/// A mob that gets mad at people at random and tries to eat nearby objects +/mob/living/basic/goose + name = "goose" + desc = "It's loose." + icon_state = "goose" + icon_living = "goose" + icon_dead = "goose_dead" + mob_biotypes = MOB_ORGANIC|MOB_BEAST + faction = list(FACTION_NEUTRAL) + maxHealth = 25 + health = 25 + melee_damage_lower = 5 + melee_damage_upper = 5 + attack_verb_continuous = "pecks" + attack_verb_simple = "peck" + attack_sound = "goose" + attack_vis_effect = ATTACK_EFFECT_BITE + speak_emote = list("honks") + response_help_continuous = "pets" + response_help_simple = "pet" + response_disarm_continuous = "gently pushes aside" + response_disarm_simple = "gently push aside" + response_harm_continuous = "kicks" + response_harm_simple = "kick" + ai_controller = /datum/ai_controller/basic_controller/goose + butcher_results = list(/obj/item/food/meat/slab/grassfed = 2) + gold_core_spawnable = HOSTILE_SPAWN + /// Do we actually destroy food we eat? + var/conserve_food = FALSE + /// Unfortunately, geese want to eat every item + var/static/list/item_typecache = typecacheof(/obj/item) + +/mob/living/basic/goose/Initialize(mapload) + . = ..() + AddElement(/datum/element/ai_retaliate) + AddElement(/datum/element/content_barfer) + AddElement(/datum/element/basic_eating, add_to_contents = conserve_food, food_types = string_list(list(/obj/item))) + + RegisterSignal(src, COMSIG_MOB_PRE_EAT, PROC_REF(on_tried_gobbling)) + RegisterSignal(src, COMSIG_MOB_ATE, PROC_REF(on_gobbled)) + + ai_controller.set_blackboard_key(BB_BASIC_FOODS, item_typecache) + +/mob/living/basic/goose/death(gibbed) + if (!gibbed && length(contents)) + var/turf/drop_turf = drop_location() + if (istype(drop_turf)) + playsound(drop_turf, 'sound/effects/splat.ogg', 50, TRUE) + drop_turf.add_vomit_floor(src) + return ..() + +/// Called when we try to eat something +/mob/living/basic/goose/proc/on_tried_gobbling(datum/source, obj/item/potential_food) + SIGNAL_HANDLER + if (ai_controller?.blackboard[BB_GOOSE_PANICKED]) + return COMSIG_MOB_CANCEL_EAT + if (potential_food.has_material_type(/datum/material/plastic) || IsEdible(potential_food)) + return NONE// Geese only eat FOOD or PLASTIC + return COMSIG_MOB_CANCEL_EAT + +/// Called when we've eaten something +/mob/living/basic/goose/proc/on_gobbled(atom/source, obj/item/food, mob/feeder) + SIGNAL_HANDLER + if (!food.has_material_type(/datum/material/plastic)) + return NONE + + visible_message(span_boldwarning("[src] is choking on \the [food]!")) + food.forceMove(src) + choke(food) + + return COMSIG_MOB_TERMINATE_EAT + +/// Start choking on something we just ate +/mob/living/basic/goose/proc/choke(obj/item/not_food_after_all) + apply_status_effect(/datum/status_effect/goose_choking) + +/// A less grumpy but much grosser variant of the goose, who will decorate the halls in their own special way +/mob/living/basic/goose/vomit + name = "Birdboat" + real_name = "Birdboat" + desc = "It's a sick-looking goose, probably ate too much maintenance trash. Best not to move it around too much." + gender = MALE + faction = list(FACTION_NEUTRAL, FACTION_MAINT_CREATURES) + gold_core_spawnable = NO_SPAWN + ai_controller = /datum/ai_controller/basic_controller/goose/calm + conserve_food = TRUE + /// Cooldown to make sure we can't spam chat with notifications that we are full + COOLDOWN_DECLARE(eat_fail_feedback_cooldown) + /// An action we use to throw up + var/datum/action/cooldown/mob_cooldown/goose_vomit/vomit_action + +/mob/living/basic/goose/vomit/Initialize(mapload) + . = ..() + + vomit_action = new(src) + vomit_action.Grant(src) + RegisterSignal(src, COMSIG_MOB_ABILITY_STARTED, PROC_REF(on_started_vomiting)) + + // 5% chance every round to have anarchy mode deadchat control on birdboat. + if (!prob(5)) + return + desc = "[initial(desc)] It's waddling more than usual. It seems to be possessed." + deadchat_plays() + +/mob/living/basic/goose/vomit/Destroy() + . = ..() + QDEL_NULL(vomit_action) + +/mob/living/basic/goose/vomit/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change) + . = ..() + var/vomit_chance = ai_controller?.blackboard[BB_GOOSE_VOMIT_CHANCE] || 0 + if (prob(vomit_chance)) + vomit() + +/mob/living/basic/goose/vomit/examine(mob/user) + . = ..() + . += span_notice("Somehow, it still looks hungry.") + +/mob/living/basic/goose/vomit/on_gobbled(atom/source, obj/item/food, mob/feeder) + if (length(contents) > GOOSE_SATIATED) + if (COOLDOWN_FINISHED(src, eat_fail_feedback_cooldown)) + if (feeder) + visible_message(span_notice("[src] looks too full to eat \the [food]!")) + COOLDOWN_START(src, eat_fail_feedback_cooldown, 5 SECONDS) + return COMSIG_MOB_TERMINATE_EAT + + . = ..() + if (. == COMSIG_MOB_TERMINATE_EAT) + return NONE// It's plastic, if it's not plastic we already filtered it for edible + + // This also increases my vomit chance, but we atomised this field to the inside of a component and I need to read it + var/datum/component/edible/edible = food.GetComponent(/datum/component/edible) + if ((edible?.foodtypes & GROSS)) + ai_controller?.add_blackboard_key(BB_GOOSE_VOMIT_CHANCE, 3) + vomit_action?.extra_duration += 0.2 SECONDS + else + ai_controller?.add_blackboard_key(BB_GOOSE_VOMIT_CHANCE, 1) + +/mob/living/basic/goose/vomit/choke(obj/item/not_food_after_all) + if (prob(75)) + return ..() + visible_message(span_warning("[src] is gagging on \the [not_food_after_all]!")) + manual_emote("gags!") + addtimer(CALLBACK(src, PROC_REF(vomit)), 5 SECONDS) + +/// Start making a mess +/mob/living/basic/goose/vomit/proc/vomit() + vomit_action?.Trigger(target = src) + +/mob/living/basic/goose/vomit/proc/on_started_vomiting(mob/living/owner, datum/action/cooldown/activated) + SIGNAL_HANDLER + if (activated != vomit_action) + return + remove_status_effect(/datum/status_effect/goose_choking) // We're going to cough it out + +/mob/living/basic/goose/vomit/proc/stop_deadchat_plays() + var/initial_behaviour = initial(ai_controller?.idle_behavior) + ai_controller?.idle_behavior = SSidle_ai_behaviors.idle_behaviors[initial_behaviour] + +/mob/living/basic/goose/vomit/deadchat_plays(mode = ANARCHY_MODE, cooldown = 12 SECONDS) + var/list/goose_inputs = list( + "vomit" = CALLBACK(src, PROC_REF(vomit)), + "honk" = CALLBACK(src, TYPE_PROC_REF(/atom/movable, say), "HONK!!!"), + "spin" = CALLBACK(src, TYPE_PROC_REF(/mob, emote), "spin")) + + . = AddComponent(/datum/component/deadchat_control/cardinal_movement, mode, goose_inputs, cooldown, CALLBACK(src, PROC_REF(stop_deadchat_plays))) + + if (. == COMPONENT_INCOMPATIBLE) + return + + // Stop automated movement, retain the other behaviour so you can lead the horse to plastic and have it drink. + ai_controller?.idle_behavior = null + +#undef GOOSE_SATIATED diff --git a/code/modules/mob/living/basic/farm_animals/goose/goose_ai.dm b/code/modules/mob/living/basic/farm_animals/goose/goose_ai.dm new file mode 100644 index 00000000000..cd10e45adc5 --- /dev/null +++ b/code/modules/mob/living/basic/farm_animals/goose/goose_ai.dm @@ -0,0 +1,82 @@ +/// Geese like to eat random objects and kill themselves, and occasionally get pissed off for no reason +/datum/ai_controller/basic_controller/goose + blackboard = list( + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + BB_SEARCH_RANGE = 1, + BB_EAT_FOOD_COOLDOWN = 10 SECONDS, + BB_EAT_EMOTES = list() + ) + + ai_traits = STOP_MOVING_WHEN_PULLED + ai_movement = /datum/ai_movement/basic_avoidance + idle_behavior = /datum/idle_behavior/idle_random_walk/goose + + planning_subtrees = list( + /datum/ai_planning_subtree/random_speech/goose, + /datum/ai_planning_subtree/capricious_retaliate, + /datum/ai_planning_subtree/target_retaliate, + /datum/ai_planning_subtree/find_food/goose, + /datum/ai_planning_subtree/basic_melee_attack_subtree, + ) + +/// Goose who doesn't randomly retaliate but does still try to die by eating random items +/datum/ai_controller/basic_controller/goose/calm + blackboard = list( + BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, + BB_SEARCH_RANGE = 1, + BB_EAT_FOOD_COOLDOWN = 0.5 SECONDS, // Uh oh + BB_EAT_EMOTES = list() + ) + planning_subtrees = list( + /datum/ai_planning_subtree/random_speech/goose, + /datum/ai_planning_subtree/use_mob_ability/goose_vomit, + /datum/ai_planning_subtree/target_retaliate, + /datum/ai_planning_subtree/find_food/goose, + /datum/ai_planning_subtree/basic_melee_attack_subtree, + ) + +/// Walk more if we're choking or vomiting +/datum/idle_behavior/idle_random_walk/goose + +/datum/idle_behavior/idle_random_walk/goose/perform_idle_behavior(seconds_per_tick, datum/ai_controller/controller) + walk_chance = controller.blackboard[BB_GOOSE_PANICKED] ? 100 : 25 // I think this sets it for every goose but that's fine because it'll reset it before using it + return ..() + +/// Only look for things geese will try to eat +/datum/ai_planning_subtree/find_food/goose + finding_behavior = /datum/ai_behavior/find_and_set/in_list/goose_food + +/datum/ai_planning_subtree/find_food/goose/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + if (controller.blackboard[BB_GOOSE_PANICKED]) + return // Don't look for food while choking or vomiting + return ..() + +/// Only set things geese will try to eat +/datum/ai_behavior/find_and_set/in_list/goose_food + +/datum/ai_behavior/find_and_set/in_list/goose_food/search_tactic(datum/ai_controller/controller, locate_paths, search_range) + var/list/found = typecache_filter_list(oview(search_range, controller.pawn), locate_paths) + if(!length(found)) + return + + var/list/filtered = list() + for (var/obj/item/thing as anything in found) + if (IsEdible(thing) || thing.has_material_type(/datum/material/plastic)) + filtered += thing + + if(length(filtered)) + return pick(filtered) + +/// Use this ability only if we roll a dice correctly +/datum/ai_planning_subtree/use_mob_ability/goose_vomit + +/datum/ai_planning_subtree/use_mob_ability/goose_vomit/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) + var/trigger_prob = controller.blackboard[BB_GOOSE_VOMIT_CHANCE] || 0 + if (prob(trigger_prob)) + return ..() + +/datum/ai_planning_subtree/random_speech/goose + speech_chance = 3 + emote_hear = list("honks.", "honks loudly.", "honks aggressively.") + emote_see = list("flaps.", "preens.", "glares around.") + speak = list("Honk!") diff --git a/code/modules/mob/living/basic/farm_animals/goose/goose_vomit.dm b/code/modules/mob/living/basic/farm_animals/goose/goose_vomit.dm new file mode 100644 index 00000000000..2b347903e22 --- /dev/null +++ b/code/modules/mob/living/basic/farm_animals/goose/goose_vomit.dm @@ -0,0 +1,187 @@ +/// Repeatedly throw up until there's nothing left inside, regrettably sufficiently complex that it requires its own file +/datum/action/cooldown/mob_cooldown/goose_vomit + name = "Vomit" + check_flags = AB_CHECK_CONSCIOUS | AB_CHECK_INCAPACITATED + button_icon_state = "vomit" + button_icon = 'icons/mob/simple/animal.dmi' + cooldown_time = INFINITY // We reset the cooldown when we are done throwing up + text_cooldown = FALSE + melee_cooldown_time = 0 + shared_cooldown = NONE + click_to_activate = FALSE + /// Extra time to spend chundering + var/extra_duration = 0 SECONDS + +/datum/action/cooldown/mob_cooldown/goose_vomit/Grant(mob/granted_to) + . = ..() + if(!owner) + return + RegisterSignals(owner, list(COMSIG_ATOM_ENTERED, COMSIG_ATOM_EXITED), PROC_REF(update_status_on_signal)) + +/datum/action/cooldown/mob_cooldown/goose_vomit/Remove(mob/removed_from) + UnregisterSignal(owner, list(COMSIG_ATOM_ENTERED, COMSIG_ATOM_EXITED)) + return ..() + +/datum/action/cooldown/mob_cooldown/goose_vomit/IsAvailable(feedback) + . = ..() + if (!.) + return FALSE + if (!length(owner.contents)) + if (feedback) + owner.balloon_alert(owner, "stomach empty!") + return FALSE + if (!isliving(owner)) + if (feedback) + owner.balloon_alert(owner, "you're not alive!") + return FALSE + return TRUE + +/datum/action/cooldown/mob_cooldown/goose_vomit/Activate(atom/target) + StartCooldown(INFINITY) + if (istype(owner, /mob/living/basic/goose)) + owner.icon_state = "vomit" + flick("vomit_start", owner) + owner.ai_controller?.set_blackboard_key(BB_GOOSE_VOMIT_CHANCE, 0) + addtimer(CALLBACK(src, PROC_REF(start_vomiting)), 13) // 13 frame long animation + else + start_vomiting() + +/// Start the performance +/datum/action/cooldown/mob_cooldown/goose_vomit/proc/start_vomiting() + var/mob/living/living_owner = owner + living_owner.apply_status_effect(/datum/status_effect/goose_vomit, extra_duration) + extra_duration = 0 + +/// Handles iteratively emptying our stomach +/datum/status_effect/goose_vomit + id = "goose_vomit" + alert_type = null + tick_interval = 1 SECONDS + /// How long do we vomit for? + var/vomit_duration = 2.5 SECONDS + /// How long have we spent vomiting? + var/elapsed_time = 0 SECONDS + /// Chance to step in a random direction + var/move_chance = 80 + /// Chance to produce an item per tick + var/vomit_item_chance = 50 + +/datum/status_effect/goose_vomit/on_creation(mob/living/new_owner, extra_duration = 0) + vomit_duration += extra_duration + return ..() + +/datum/status_effect/goose_vomit/on_apply() + owner.set_jitter_if_lower(vomit_duration) + owner.ai_controller?.set_blackboard_key(BB_GOOSE_PANICKED, TRUE) + RegisterSignal(owner, COMSIG_LIVING_DEATH, PROC_REF(on_owner_died)) + RegisterSignal(owner, COMSIG_MOVABLE_MOVED, PROC_REF(on_owner_moved)) + return TRUE + +/datum/status_effect/goose_vomit/on_remove() + UnregisterSignal(owner, list(COMSIG_LIVING_DEATH, COMSIG_MOVABLE_MOVED)) + owner.ai_controller?.set_blackboard_key(BB_GOOSE_PANICKED, FALSE) + if (istype(owner, /mob/living/basic/goose) && owner.stat != DEAD) + var/mob/living/basic/goose_mob = owner + flick("vomit_end", owner) + goose_mob.icon_state = goose_mob.icon_living + + var/datum/action/cooldown/mob_cooldown/goose_vomit/vomit_action = locate() in owner.actions + vomit_action?.StartCooldown(0 SECONDS) + +/// Don't keep vomiting from beyond the grave +/datum/status_effect/goose_vomit/proc/on_owner_died() + SIGNAL_HANDLER + qdel(src) + +/// For good measure we'll spit up every time we take a step too +/datum/status_effect/goose_vomit/proc/on_owner_moved() + SIGNAL_HANDLER + vomit_iteratively(can_move = FALSE) + +/datum/status_effect/goose_vomit/tick(seconds_between_ticks) + elapsed_time += seconds_between_ticks + + if (!length(owner.contents)) + qdel(src) + return + + if (elapsed_time <= vomit_duration) + vomit_iteratively() + else + vomit_finale() + +/// One to come up +/datum/status_effect/goose_vomit/proc/vomit_iteratively(can_move = TRUE) + if (prob(vomit_item_chance)) + hurl_item() + else + make_mess(owner.drop_location()) + + if (can_move && prob(move_chance)) + var/move_dir = pick(GLOB.alldirs) + var/turf/destination_turf = get_step(owner, move_dir) + owner.Move(destination_turf, move_dir) + +/// Stop fucking around and get the rest of it out +/datum/status_effect/goose_vomit/proc/vomit_finale() + tick_interval = 0.1 SECONDS + owner.set_jitter_if_lower(1 SECONDS) + hurl_item(vomit_strongly = TRUE) + +/// Produce an item from our inventory +/datum/status_effect/goose_vomit/proc/hurl_item(vomit_strongly = FALSE) + if (!length(owner.contents)) + return + var/obj/item/thing = pick_n_take(owner.contents) + if (!ismovable(thing) || HAS_TRAIT(thing, TRAIT_NOT_BARFABLE)) + qdel(src) // Someone is fucking around with this goose, let's not get stuck in this forever + return + var/drop_location = owner.drop_location() + + thing.forceMove(drop_location) + if (isopenturf(drop_location)) + make_mess(drop_location) + var/destination = get_edge_target_turf(drop_location, pick(GLOB.alldirs)) + var/throwRange = vomit_strongly ? rand(2, 8) : 1 + thing.safe_throw_at(destination, throwRange, 2) + +/// Make a mess +/datum/status_effect/goose_vomit/proc/make_mess(turf/open/drop_turf) + if (!istype(drop_turf)) + return + playsound(drop_turf, 'sound/effects/splat.ogg', 50, TRUE) + drop_turf.add_vomit_floor(owner) + + +/// Wheeze until we die +/datum/status_effect/goose_choking + id = "goose_choking" + alert_type = null + duration = 30 SECONDS + tick_interval = 2 SECONDS + /// Chance per second to emote + var/emote_prob = 18 + /// What things do we do while dying + var/static/list/emotes = list("chokes.", "coughs.", "gasps.", "tries urgently to breathe.", "shudders violently.", "wheezes.") + +/datum/status_effect/goose_choking/tick(seconds_between_ticks) + if (SPT_PROB(emote_prob, seconds_between_ticks)) + owner.manual_emote(pick(emotes)) + +/datum/status_effect/goose_choking/on_apply() + owner.set_jitter_if_lower(duration) + owner.ai_controller?.set_blackboard_key(BB_GOOSE_PANICKED, TRUE) + RegisterSignal(owner, COMSIG_LIVING_DEATH, PROC_REF(on_owner_died)) + return TRUE + +/datum/status_effect/goose_choking/on_remove() + UnregisterSignal(owner, COMSIG_LIVING_DEATH) + if (duration >= world.time) + return // Saved by something, although probably by dying early + owner.death_message = "lets out one final oxygen-deprived honk before [owner.p_they()] go[owner.p_es()] limp and lifeless.." + owner.death() + +/// Don't keep dying if we died +/datum/status_effect/goose_choking/proc/on_owner_died() + SIGNAL_HANDLER + qdel(src) diff --git a/code/modules/mob/living/basic/lavaland/goldgrub/goldgrub.dm b/code/modules/mob/living/basic/lavaland/goldgrub/goldgrub.dm index ce1c4e7cf19..9336af84f0e 100644 --- a/code/modules/mob/living/basic/lavaland/goldgrub/goldgrub.dm +++ b/code/modules/mob/living/basic/lavaland/goldgrub/goldgrub.dm @@ -47,12 +47,15 @@ else can_lay_eggs = FALSE + var/list/food_types = string_list(list(/obj/item/stack/ore)) var/static/list/innate_actions = list( /datum/action/cooldown/mob_cooldown/spit_ore = BB_SPIT_ABILITY, /datum/action/cooldown/mob_cooldown/burrow = BB_BURROW_ABILITY, ) grant_actions_by_list(innate_actions) + AddElement(/datum/element/ore_collecting) + AddElement(/datum/element/basic_eating, food_types = food_types, add_to_contents = TRUE) AddElement(/datum/element/wall_tearer, allow_reinforced = FALSE) AddComponent(/datum/component/ai_listen_to_weather) AddComponent(\ @@ -60,14 +63,15 @@ overlay_icon = 'icons/mob/simple/lavaland/lavaland_monsters_wide.dmi',\ overlay_state = "goldgrub_alert",\ ) + if(can_tame) - make_tameable() + make_tameable(food_types) if(can_lay_eggs) make_egg_layer() + ADD_TRAIT(src, TRAIT_BOULDER_BREAKER, INNATE_TRAIT) ADD_TRAIT(src, TRAIT_INSTANTLY_PROCESSES_BOULDERS, INNATE_TRAIT) RegisterSignal(src, COMSIG_ATOM_PRE_BULLET_ACT, PROC_REF(block_bullets)) - RegisterSignal(src, COMSIG_MOB_ATE, PROC_REF(on_eat)) /mob/living/basic/mining/goldgrub/proc/block_bullets(datum/source, obj/projectile/hitting_projectile) SIGNAL_HANDLER @@ -105,8 +109,7 @@ barf_contents(gibbed) return ..() -/mob/living/basic/mining/goldgrub/proc/make_tameable() - var/list/food_types = string_list(list(/obj/item/stack/ore)) +/mob/living/basic/mining/goldgrub/proc/make_tameable(list/food_types) AddComponent(/datum/component/tameable, food_types = food_types, tame_chance = 25, bonus_tame_chance = 5) /mob/living/basic/mining/goldgrub/tamed(mob/living/tamer, atom/food) @@ -135,12 +138,6 @@ return new /obj/item/food/egg/green/grub_egg(get_turf(src)) -/mob/living/basic/mining/goldgrub/proc/on_eat(atom/source, atom/movable/food, mob/feeder) - SIGNAL_HANDLER - - food.forceMove(src) - return COMSIG_MOB_TERMINATE_EAT - /mob/living/basic/mining/goldgrub/baby icon = 'icons/mob/simple/lavaland/lavaland_monsters.dmi' name = "goldgrub baby" diff --git a/code/modules/mob/living/simple_animal/hostile/retaliate/goose.dm b/code/modules/mob/living/simple_animal/hostile/retaliate/goose.dm deleted file mode 100644 index b177cc651ec..00000000000 --- a/code/modules/mob/living/simple_animal/hostile/retaliate/goose.dm +++ /dev/null @@ -1,269 +0,0 @@ -#define GOOSE_SATIATED 50 -/mob/living/simple_animal/hostile/retaliate/goose - name = "goose" - desc = "It's loose" - icon_state = "goose" // sprites by cogwerks from goonstation, used with permission - icon_living = "goose" - icon_dead = "goose_dead" - mob_biotypes = MOB_ORGANIC|MOB_BEAST - speak_chance = 0 - turns_per_move = 5 - butcher_results = list(/obj/item/food/meat/slab/grassfed = 2) - response_help_continuous = "pets" - response_help_simple = "pet" - response_disarm_continuous = "gently pushes aside" - response_disarm_simple = "gently push aside" - response_harm_continuous = "kicks" - response_harm_simple = "kick" - emote_taunt = list("hisses") - taunt_chance = 30 - speed = 0 - maxHealth = 25 - health = 25 - harm_intent_damage = 5 - melee_damage_lower = 5 - melee_damage_upper = 5 - attack_verb_continuous = "pecks" - attack_verb_simple = "peck" - attack_sound = "goose" - attack_vis_effect = ATTACK_EFFECT_BITE - speak_emote = list("honks") - faction = list(FACTION_NEUTRAL) - attack_same = TRUE - gold_core_spawnable = HOSTILE_SPAWN - var/random_retaliate = TRUE - var/icon_vomit_start = "vomit_start" - var/icon_vomit = "vomit" - var/icon_vomit_end = "vomit_end" - var/message_cooldown = 0 - var/choking = FALSE - -/mob/living/simple_animal/hostile/retaliate/goose/Initialize(mapload) - . = ..() - RegisterSignal(src, COMSIG_MOVABLE_MOVED, PROC_REF(goosement)) - -/mob/living/simple_animal/hostile/retaliate/goose/proc/goosement(atom/movable/AM, OldLoc, Dir, Forced) - SIGNAL_HANDLER - if(stat == DEAD) - return - if(prob(5) && random_retaliate) - Retaliate() - -/mob/living/simple_animal/hostile/retaliate/goose/handle_automated_action() - . = ..() - feed_random() - -/mob/living/simple_animal/hostile/retaliate/goose/proc/feed_random() - var/obj/item/eat_it_motherfucker = pick(locate(/obj/item) in loc) - if(!eat_it_motherfucker) - return - feed(eat_it_motherfucker) - -/mob/living/simple_animal/hostile/retaliate/goose/vomit/feed_random() - for(var/obj/item/eat_it_motherfucker in loc) - if(!eat_it_motherfucker.has_material_type(/datum/material/plastic)) - continue - feed(eat_it_motherfucker) - break - -/mob/living/simple_animal/hostile/retaliate/goose/proc/feed(obj/item/suffocator) - if(stat == DEAD || choking) // plapatin I swear to god - return FALSE - if(suffocator.has_material_type(/datum/material/plastic)) // dumb goose'll swallow food or drink with plastic in it - visible_message(span_danger("[src] hungrily gobbles up \the [suffocator]! ")) - visible_message(span_boldwarning("[src] is choking on \the [suffocator]! ")) - suffocator.forceMove(src) - choke(suffocator) - choking = TRUE - return TRUE - -/mob/living/simple_animal/hostile/retaliate/goose/vomit - name = "Birdboat" - real_name = "Birdboat" - desc = "It's a sick-looking goose, probably ate too much maintenance trash. Best not to move it around too much." - gender = MALE - response_help_continuous = "pets" - response_help_simple = "pet" - response_disarm_continuous = "gently pushes aside" - response_disarm_simple = "gently push aside" - response_harm_continuous = "kicks" - response_harm_simple = "kick" - faction = list(FACTION_NEUTRAL, FACTION_MAINT_CREATURES) - gold_core_spawnable = NO_SPAWN - random_retaliate = FALSE - var/vomiting = FALSE - var/vomitCoefficient = 1 - var/vomitTimeBonus = 0 - var/datum/action/cooldown/vomit/goosevomit - -/mob/living/simple_animal/hostile/retaliate/goose/vomit/Initialize(mapload) - . = ..() - goosevomit = new - goosevomit.Grant(src) - // 5% chance every round to have anarchy mode deadchat control on birdboat. - if(prob(5)) - desc = "[initial(desc)] It's waddling more than usual. It seems to be possessed." - deadchat_plays() - -/mob/living/simple_animal/hostile/retaliate/goose/vomit/Destroy() - UnregisterSignal(src, COMSIG_MOVABLE_MOVED) - QDEL_NULL(goosevomit) - return ..() - -/mob/living/simple_animal/hostile/retaliate/goose/vomit/examine(user) - . = ..() - . += span_notice("Somehow, it still looks hungry.") - -/mob/living/simple_animal/hostile/retaliate/goose/attackby(obj/item/O, mob/user) - . = ..() - if(feed(O)) - return TRUE - -/mob/living/simple_animal/hostile/retaliate/goose/vomit/feed(obj/item/food/tasty) - . = ..() - if(. || !istype(tasty)) - return FALSE - if (contents.len > GOOSE_SATIATED) - if(message_cooldown < world.time) - visible_message(span_notice("[src] looks too full to eat \the [tasty]!")) - message_cooldown = world.time + 5 SECONDS - return FALSE - if (tasty.foodtypes & GROSS) - visible_message(span_notice("[src] hungrily gobbles up \the [tasty]!")) - tasty.forceMove(src) - playsound(src,'sound/items/eatfood.ogg', 70, TRUE) - vomitCoefficient += 3 - vomitTimeBonus += 2 - return TRUE - else - if(message_cooldown < world.time) - visible_message(span_notice("[src] refuses to eat \the [tasty].")) - message_cooldown = world.time + 5 SECONDS - return FALSE - -/mob/living/simple_animal/hostile/retaliate/goose/proc/choke(obj/item/food/plastic) - if(stat == DEAD || choking) - return - addtimer(CALLBACK(src, PROC_REF(suffocate)), 30 SECONDS) - -/mob/living/simple_animal/hostile/retaliate/goose/vomit/choke(obj/item/food/plastic) - if(stat == DEAD || choking) - return - if(prob(25)) - visible_message(span_warning("[src] is gagging on \the [plastic]!")) - manual_emote("gags!") - addtimer(CALLBACK(src, PROC_REF(vomit)), 30 SECONDS) - else - addtimer(CALLBACK(src, PROC_REF(suffocate)), 30 SECONDS) - -/mob/living/simple_animal/hostile/retaliate/goose/Life(seconds_per_tick = SSMOBS_DT, times_fired) - . = ..() - if(!choking || stat) - return - do_jitter_animation(50) - if(SPT_PROB(10, seconds_per_tick)) - INVOKE_ASYNC(src, PROC_REF(emote), "gasp") - -/mob/living/simple_animal/hostile/retaliate/goose/proc/suffocate() - if(!choking) - return - death_message = "lets out one final oxygen-deprived honk before [p_they()] go[p_es()] limp and lifeless.." - death() - -/mob/living/simple_animal/hostile/retaliate/goose/vomit/proc/vomit() - if (stat == DEAD) - return - var/turf/T = get_turf(src) - var/obj/item/consumed = locate() in contents //Barf out a single food item from our guts - choking = FALSE // assume birdboat is vomiting out whatever he was choking on - if (prob(50) && consumed) - barf_food(consumed) - else - playsound(T, 'sound/effects/splat.ogg', 50, TRUE) - T.add_vomit_floor(src) - -/mob/living/simple_animal/hostile/retaliate/goose/vomit/proc/barf_food(atom/A, hard = FALSE) - if (stat == DEAD) - return - if(!istype(A, /obj/item/food)) - return - var/turf/currentTurf = get_turf(src) - var/obj/item/food/consumed = A - consumed.forceMove(currentTurf) - var/destination = get_edge_target_turf(currentTurf, pick(GLOB.alldirs)) //Pick a random direction to toss them in - var/throwRange = hard ? rand(2,8) : 1 - consumed.safe_throw_at(destination, throwRange, 2) //Thow the food at a random tile 1 spot away - sleep(0.2 SECONDS) - if (QDELETED(src) || QDELETED(consumed)) - return - currentTurf = get_turf(consumed) - currentTurf.add_vomit_floor(src) - playsound(currentTurf, 'sound/effects/splat.ogg', 50, TRUE) - -/mob/living/simple_animal/hostile/retaliate/goose/vomit/proc/vomit_prestart(duration) - flick("vomit_start",src) - addtimer(CALLBACK(src, PROC_REF(vomit_start), duration), 13) //13 is the length of the vomit_start animation in gooseloose.dmi - -/mob/living/simple_animal/hostile/retaliate/goose/vomit/proc/vomit_start(duration) - vomiting = TRUE - icon_state = "vomit" - vomit() - addtimer(CALLBACK(src, PROC_REF(vomit_preend)), duration) - -/mob/living/simple_animal/hostile/retaliate/goose/vomit/proc/vomit_preend() - for (var/obj/item/consumed in contents) //Get rid of any food left in the poor thing - barf_food(consumed, TRUE) - sleep(0.1 SECONDS) - if (QDELETED(src)) - return - vomit_end() - -/mob/living/simple_animal/hostile/retaliate/goose/vomit/proc/vomit_end() - flick("vomit_end",src) - vomiting = FALSE - icon_state = initial(icon_state) - -/mob/living/simple_animal/hostile/retaliate/goose/vomit/goosement(atom/movable/AM, OldLoc, Dir, Forced) - . = ..() - if(vomiting) - INVOKE_ASYNC(src, PROC_REF(vomit)) // its supposed to keep vomiting if you move - return - if(prob(vomitCoefficient * 0.2)) - vomit_prestart(vomitTimeBonus + 25) - vomitCoefficient = 1 - vomitTimeBonus = 0 - -/// A proc to make it easier for admins to make the goose playable by deadchat. -/mob/living/simple_animal/hostile/retaliate/goose/vomit/deadchat_plays(mode = ANARCHY_MODE, cooldown = 12 SECONDS) - . = AddComponent(/datum/component/deadchat_control/cardinal_movement, mode, list( - "vomit" = CALLBACK(src, PROC_REF(vomit_prestart), 25), - "honk" = CALLBACK(src, TYPE_PROC_REF(/atom/movable, say), "HONK!!!"), - "spin" = CALLBACK(src, TYPE_PROC_REF(/mob, emote), "spin")), cooldown, CALLBACK(src, PROC_REF(stop_deadchat_plays))) - - if(. == COMPONENT_INCOMPATIBLE) - return - - stop_automated_movement = TRUE - -/datum/action/cooldown/vomit - name = "Vomit" - check_flags = AB_CHECK_CONSCIOUS | AB_CHECK_INCAPACITATED - button_icon_state = "vomit" - button_icon = 'icons/mob/simple/animal.dmi' - cooldown_time = 250 - -/datum/action/cooldown/vomit/Activate(atom/target) - if(!istype(owner, /mob/living/simple_animal/hostile/retaliate/goose/vomit)) - return FALSE - - StartCooldown(10 SECONDS) - var/mob/living/simple_animal/hostile/retaliate/goose/vomit/probably_birdboat = owner - if(!probably_birdboat.vomiting) - probably_birdboat.vomit_prestart(probably_birdboat.vomitTimeBonus + 25) - probably_birdboat.vomitCoefficient = 1 - probably_birdboat.vomitTimeBonus = 0 - - StartCooldown() - return TRUE - -#undef GOOSE_SATIATED diff --git a/code/modules/mob/living/simple_animal/hostile/retaliate/retaliate.dm b/code/modules/mob/living/simple_animal/hostile/retaliate/retaliate.dm deleted file mode 100644 index bf1c12d5da1..00000000000 --- a/code/modules/mob/living/simple_animal/hostile/retaliate/retaliate.dm +++ /dev/null @@ -1,59 +0,0 @@ -/mob/living/simple_animal/hostile/retaliate - ///A list of weakrefs pointing at things that we consider targets - var/list/enemies = list() - -/mob/living/simple_animal/hostile/retaliate/Found(atom/A) - if(isliving(A)) - var/mob/living/L = A - if(!L.stat) - return L - else - enemies -= WEAKREF(L) - else if(ismecha(A)) - var/obj/vehicle/sealed/mecha/M = A - if(LAZYLEN(M.occupants)) - return A - -/mob/living/simple_animal/hostile/retaliate/ListTargets() - if(!enemies.len) - return list() - var/list/see = ..() - var/list/actual_enemies = list() - for(var/datum/weakref/enemy as anything in enemies) - var/mob/flesh_and_blood = enemy.resolve() - if(!flesh_and_blood) - enemies -= enemy - continue - actual_enemies += flesh_and_blood - - see &= actual_enemies // Remove all entries that aren't in enemies - return see - -/mob/living/simple_animal/hostile/retaliate/proc/Retaliate() - var/list/around = view(src, vision_range) - - for(var/atom/movable/A in around) - if(A == src) - continue - if(isliving(A)) - var/mob/living/M = A - if(faction_check_atom(M) && attack_same || !faction_check_atom(M)) - enemies |= WEAKREF(M) - else if(ismecha(A)) - var/obj/vehicle/sealed/mecha/M = A - if(LAZYLEN(M.occupants)) - enemies |= WEAKREF(M) - add_enemies(M.occupants) - - for(var/mob/living/simple_animal/hostile/retaliate/H in around) - if(faction_check_atom(H) && !attack_same && !H.attack_same) - H.enemies |= enemies - -/mob/living/simple_animal/hostile/retaliate/adjustHealth(amount, updating_health = TRUE, forced = FALSE) - . = ..() - if(. > 0 && stat == CONSCIOUS) - Retaliate() - -/mob/living/simple_animal/hostile/retaliate/proc/add_enemies(new_enemies) - for(var/new_enemy in new_enemies) - enemies |= WEAKREF(new_enemy) diff --git a/code/modules/unit_tests/simple_animal_freeze.dm b/code/modules/unit_tests/simple_animal_freeze.dm index 39e239a37ed..0bea535c3e3 100644 --- a/code/modules/unit_tests/simple_animal_freeze.dm +++ b/code/modules/unit_tests/simple_animal_freeze.dm @@ -58,9 +58,6 @@ /mob/living/simple_animal/hostile/ooze, /mob/living/simple_animal/hostile/ooze/gelatinous, /mob/living/simple_animal/hostile/ooze/grapes, - /mob/living/simple_animal/hostile/retaliate, - /mob/living/simple_animal/hostile/retaliate/goose, - /mob/living/simple_animal/hostile/retaliate/goose/vomit, /mob/living/simple_animal/soulscythe, // DO NOT ADD NEW ENTRIES TO THIS LIST // READ THE COMMENT ABOVE diff --git a/tgstation.dme b/tgstation.dme index 9be45492d4f..25ecd8cb435 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -4934,6 +4934,9 @@ #include "code\modules\mob\living\basic\farm_animals\goat\_goat.dm" #include "code\modules\mob\living\basic\farm_animals\goat\goat_ai.dm" #include "code\modules\mob\living\basic\farm_animals\goat\goat_subtypes.dm" +#include "code\modules\mob\living\basic\farm_animals\goose\goose.dm" +#include "code\modules\mob\living\basic\farm_animals\goose\goose_ai.dm" +#include "code\modules\mob\living\basic\farm_animals\goose\goose_vomit.dm" #include "code\modules\mob\living\basic\farm_animals\gorilla\gorilla.dm" #include "code\modules\mob\living\basic\farm_animals\gorilla\gorilla_accessories.dm" #include "code\modules\mob\living\basic\farm_animals\gorilla\gorilla_ai.dm" @@ -5378,8 +5381,6 @@ #include "code\modules\mob\living\simple_animal\hostile\mining_mobs\elites\herald.dm" #include "code\modules\mob\living\simple_animal\hostile\mining_mobs\elites\legionnaire.dm" #include "code\modules\mob\living\simple_animal\hostile\mining_mobs\elites\pandora.dm" -#include "code\modules\mob\living\simple_animal\hostile\retaliate\goose.dm" -#include "code\modules\mob\living\simple_animal\hostile\retaliate\retaliate.dm" #include "code\modules\mob_spawn\mob_spawn.dm" #include "code\modules\mob_spawn\corpses\job_corpses.dm" #include "code\modules\mob_spawn\corpses\mining_corpses.dm" diff --git a/tools/UpdatePaths/Scripts/89204_simple_to_basic_goose.txt b/tools/UpdatePaths/Scripts/89204_simple_to_basic_goose.txt new file mode 100644 index 00000000000..29ba0e74f54 --- /dev/null +++ b/tools/UpdatePaths/Scripts/89204_simple_to_basic_goose.txt @@ -0,0 +1 @@ +/mob/living/simple_animal/hostile/retaliate/goose/@SUBTYPES : /mob/living/basic/goose/@SUBTYPES{@OLD}