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

🆑
refactor: Geese have been moved to the basic mob subsystem, please
report any unusual behaviour.
/🆑

---------

Co-authored-by: Ben10Omintrix <138636438+Ben10Omintrix@users.noreply.github.com>
This commit is contained in:
Jacquerel
2025-01-27 14:54:34 +02:00
committed by GitHub
co-authored by Ben10Omintrix
parent d0730ef651
commit 940e2d31cf
25 changed files with 496 additions and 360 deletions
@@ -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)
@@ -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" = (
@@ -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" = (
+1 -1
View File
@@ -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" = (
+1 -1
View File
@@ -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" = (
+8
View File
@@ -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
-1
View File
@@ -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,
+1 -1
View File
@@ -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,
@@ -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
+1 -1
View File
@@ -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)
/**
+14 -3
View File
@@ -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)
+1 -1
View File
@@ -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))
@@ -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,
)
+1 -1
View File
@@ -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,
)))
+1 -1
View File
@@ -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)
+1 -1
View File
@@ -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
@@ -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
@@ -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!")
@@ -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)
@@ -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"
@@ -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
@@ -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)
@@ -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
+3 -2
View File
@@ -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"
@@ -0,0 +1 @@
/mob/living/simple_animal/hostile/retaliate/goose/@SUBTYPES : /mob/living/basic/goose/@SUBTYPES{@OLD}