diff --git a/_maps/RandomRuins/LavaRuins/lavaland_surface_bileworm_nest.dmm b/_maps/RandomRuins/LavaRuins/lavaland_surface_bileworm_nest.dmm new file mode 100644 index 00000000000..0f8ded72290 --- /dev/null +++ b/_maps/RandomRuins/LavaRuins/lavaland_surface_bileworm_nest.dmm @@ -0,0 +1,278 @@ +//MAP CONVERTED BY dmm2tgm.py THIS HEADER COMMENT PREVENTS RECONVERSION, DO NOT REMOVE +"a" = ( +/turf/closed/mineral/volcanic/lava_land_surface, +/area/lavaland/surface/outdoors) +"b" = ( +/turf/open/misc/asteroid/lavaland_atmos, +/area/lavaland/surface/outdoors) +"c" = ( +/turf/template_noop, +/area/template_noop) +"d" = ( +/mob/living/basic/bileworm, +/turf/open/misc/asteroid/lavaland_atmos, +/area/lavaland/surface/outdoors) +"e" = ( +/obj/item/crusher_trophy/bileworm_spewlet, +/turf/open/misc/asteroid/lavaland_atmos, +/area/lavaland/surface/outdoors) +"f" = ( +/obj/structure/water_source/puddle, +/turf/open/misc/asteroid/lavaland_atmos, +/area/lavaland/surface/outdoors) + +(1,1,1) = {" +c +c +c +c +c +c +c +c +c +c +c +c +c +c +c +"} +(2,1,1) = {" +c +c +c +c +c +c +c +c +c +c +c +c +c +c +c +"} +(3,1,1) = {" +c +c +c +c +a +a +a +a +a +a +a +c +c +c +c +"} +(4,1,1) = {" +c +c +c +a +a +a +a +a +a +a +a +a +c +c +c +"} +(5,1,1) = {" +c +c +a +a +a +a +a +a +a +a +a +a +c +c +c +"} +(6,1,1) = {" +c +c +c +a +a +a +b +b +b +a +a +a +c +c +c +"} +(7,1,1) = {" +c +c +a +a +a +b +e +d +b +a +a +a +c +c +c +"} +(8,1,1) = {" +c +c +a +a +a +d +f +b +b +a +a +a +c +c +c +"} +(9,1,1) = {" +c +c +a +a +a +b +b +b +a +a +a +a +c +c +c +"} +(10,1,1) = {" +c +c +a +a +a +b +b +b +a +a +a +a +c +c +c +"} +(11,1,1) = {" +c +c +a +a +a +a +a +a +a +a +a +a +c +c +c +"} +(12,1,1) = {" +c +c +a +a +a +a +a +a +a +a +a +a +c +c +c +"} +(13,1,1) = {" +c +c +c +a +a +a +a +a +a +a +c +a +c +c +c +"} +(14,1,1) = {" +c +c +c +c +c +c +c +c +c +c +c +c +c +c +c +"} +(15,1,1) = {" +c +c +c +c +c +c +c +c +c +c +c +c +c +c +c +"} diff --git a/code/__DEFINES/ai.dm b/code/__DEFINES/ai.dm index 2b931df61e4..5badae11bb3 100644 --- a/code/__DEFINES/ai.dm +++ b/code/__DEFINES/ai.dm @@ -216,3 +216,11 @@ #define BB_BASIC_MOB_CURRENT_TARGET "BB_basic_current_target" #define BB_BASIC_MOB_CURRENT_TARGET_HIDING_LOCATION "BB_basic_current_target_hiding_location" #define BB_TARGETTING_DATUM "targetting_datum" +///some behaviors that check current_target also set this on deep crit mobs +#define BB_BASIC_MOB_EXECUTION_TARGET "BB_basic_execution_target" + +///Bileworm AI keys + +#define BB_BILEWORM_SPEW_BILE "BB_bileworm_spew_bile" +#define BB_BILEWORM_RESURFACE "BB_bileworm_resurface" +#define BB_BILEWORM_DEVOUR "BB_bileworm_devour" diff --git a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_living.dm b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_living.dm index 1b92c35488c..0eb855dfc50 100644 --- a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_living.dm +++ b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_living.dm @@ -94,3 +94,5 @@ ///From obj/item/toy/crayon/spraycan #define COMSIG_LIVING_MOB_PAINTED "living_mob_painted" +///From mob/living/proc/wabbajack_act +#define COMSIG_LIVING_WABBAJACKED "living_wabbajacked" diff --git a/code/datums/actions/cooldown_action.dm b/code/datums/actions/cooldown_action.dm index ef395616ce0..1005cc4def6 100644 --- a/code/datums/actions/cooldown_action.dm +++ b/code/datums/actions/cooldown_action.dm @@ -94,13 +94,7 @@ // "Shared cooldowns" covers actions which are not the same type, // but have the same cooldown group and are on the same mob if(shared_cooldown) - for(var/datum/action/cooldown/shared_ability in owner.actions - src) - if(!(shared_cooldown & shared_ability.shared_cooldown)) - continue - if(isnum(override_cooldown_time)) - shared_ability.StartCooldownSelf(override_cooldown_time) - else - shared_ability.StartCooldownSelf(cooldown_time) + StartCooldownOthers(override_cooldown_time) StartCooldownSelf(override_cooldown_time) @@ -119,6 +113,17 @@ UpdateButtons() START_PROCESSING(SSfastprocess, src) +/// Starts a cooldown time for other abilities that share a cooldown with this. Has some niche usage with more complicated attack ai! +/// Will use default cooldown time if an override is not specified +/datum/action/cooldown/proc/StartCooldownOthers(override_cooldown_time) + for(var/datum/action/cooldown/shared_ability in owner.actions - src) + if(!(shared_cooldown & shared_ability.shared_cooldown)) + continue + if(isnum(override_cooldown_time)) + shared_ability.StartCooldownSelf(override_cooldown_time) + else + shared_ability.StartCooldownSelf(cooldown_time) + /datum/action/cooldown/Trigger(trigger_flags, atom/target) . = ..() if(!.) diff --git a/code/datums/actions/mobs/projectileattack.dm b/code/datums/actions/mobs/projectileattack.dm index e05f563d20d..c0a3c62a7bb 100644 --- a/code/datums/actions/mobs/projectileattack.dm +++ b/code/datums/actions/mobs/projectileattack.dm @@ -240,9 +240,8 @@ if(!islist(dirs)) dirs = GLOB.alldirs.Copy() playsound(firer, projectile_sound, 200, TRUE, 2) - for(var/d in dirs) - var/turf/E = get_step(firer, d) - shoot_projectile(firer, E, null, firer, null, null) + for(var/dir in dirs) + shoot_projectile(firer, target, dir2angle(dir), firer) /datum/action/cooldown/mob_cooldown/projectile_attack/dir_shots/alternating name = "Alternating Shots" diff --git a/code/datums/ai/basic_mobs/basic_ai_behaviors/try_mob_ability.dm b/code/datums/ai/basic_mobs/basic_ai_behaviors/try_mob_ability.dm new file mode 100644 index 00000000000..32f721c8cfd --- /dev/null +++ b/code/datums/ai/basic_mobs/basic_ai_behaviors/try_mob_ability.dm @@ -0,0 +1,25 @@ +/datum/ai_behavior/try_mob_ability + +/datum/ai_behavior/try_mob_ability/perform(delta_time, datum/ai_controller/controller, ability_key, target_key) + + var/datum/action/cooldown/mob_cooldown/ability = controller.blackboard[ability_key] + var/mob/living/target = controller.blackboard[target_key] + if(!ability || !target) + finish_action(controller, FALSE, ability_key, target_key) + var/mob/pawn = controller.pawn + var/result = ability.InterceptClickOn(pawn, null, target) + finish_action(controller, result, ability_key, target_key) + +/datum/ai_behavior/try_mob_ability/finish_action(datum/ai_controller/controller, succeeded, ability_key, target_key) + . = ..() + var/mob/living/target = controller.blackboard[target_key] + if(!target || QDELETED(target) || target.stat >= UNCONSCIOUS) + controller.blackboard[target_key] = null + +///subtype of normal mob ability that moves the target into a special execution targetting. +///doesn't need another subtype to clear BB_BASIC_MOB_EXECUTION_TARGET because it will be the target key for above type +/datum/ai_behavior/try_mob_ability/and_plan_execute + +/datum/ai_behavior/try_mob_ability/and_plan_execute/finish_action(datum/ai_controller/controller, succeeded, ability_key, target_key) + controller.blackboard[BB_BASIC_MOB_EXECUTION_TARGET] = controller.blackboard[target_key] + return ..() diff --git a/code/datums/ai/basic_mobs/basic_subtrees/simple_attack_target.dm b/code/datums/ai/basic_mobs/basic_subtrees/simple_attack_target.dm index 633fe679992..56893ce767e 100644 --- a/code/datums/ai/basic_mobs/basic_subtrees/simple_attack_target.dm +++ b/code/datums/ai/basic_mobs/basic_subtrees/simple_attack_target.dm @@ -20,6 +20,3 @@ return controller.queue_behavior(ranged_attack_behavior, BB_BASIC_MOB_CURRENT_TARGET, BB_TARGETTING_DATUM, BB_BASIC_MOB_CURRENT_TARGET_HIDING_LOCATION) return SUBTREE_RETURN_FINISH_PLANNING //we are going into battle...no distractions. - - - diff --git a/code/datums/elements/content_barfer.dm b/code/datums/elements/content_barfer.dm new file mode 100644 index 00000000000..2652467e8a2 --- /dev/null +++ b/code/datums/elements/content_barfer.dm @@ -0,0 +1,28 @@ +/** + * Content Barfer; which expels the contents of a mob when it dies, or is transformed + * + * Used for morphs and bileworms! + */ +/datum/element/content_barfer + element_flags = ELEMENT_DETACH + id_arg_index = 2 + +/datum/element/content_barfer/Attach(datum/target, tally_string) + . = ..() + + if(!isliving(target)) + return ELEMENT_INCOMPATIBLE + + RegisterSignal(target, list(COMSIG_LIVING_DEATH, COMSIG_LIVING_WABBAJACKED), .proc/barf_contents) + +/datum/element/content_barfer/Detach(datum/target) + UnregisterSignal(target, list(COMSIG_LIVING_DEATH, COMSIG_LIVING_WABBAJACKED)) + return ..() + +/datum/element/content_barfer/proc/barf_contents(mob/living/target) + SIGNAL_HANDLER + + for(var/atom/movable/barfed_out in target) + barfed_out.forceMove(target.loc) + if(prob(90)) + step(barfed_out, pick(GLOB.alldirs)) diff --git a/code/datums/elements/crusher_loot.dm b/code/datums/elements/crusher_loot.dm new file mode 100644 index 00000000000..9c6dc86bdef --- /dev/null +++ b/code/datums/elements/crusher_loot.dm @@ -0,0 +1,42 @@ +/** + * Crusher Loot; which makes the attached mob drop a crusher trophy of some type if the majority damage was from a crusher! + * + * Used for all the mobs droppin' crusher trophies + */ +/datum/element/crusher_loot + element_flags = ELEMENT_BESPOKE|ELEMENT_DETACH + id_arg_index = 2 + /// Path of the trophy dropped + var/trophy_type + /// chance to drop the trophy, lowered by the mob only taking partial crusher damage instead of full + /// for example, 25% would mean ~4 mobs need to die before you find one. + /// but it would be more if you didn't deal full crusher damage to them. + var/drop_mod + /// If true, will immediately spawn the item instead of putting it in butcher loot. + var/drop_immediately + +/datum/element/crusher_loot/Attach(datum/target, trophy_type, drop_mod = 25, drop_immediately = FALSE) + . = ..() + + if(!isliving(target)) + return ELEMENT_INCOMPATIBLE + + RegisterSignal(target, COMSIG_LIVING_DEATH, .proc/on_death) + + src.trophy_type = trophy_type + src.drop_mod = drop_mod + src.drop_immediately = drop_immediately + +/datum/element/crusher_loot/Detach(datum/target) + UnregisterSignal(target, COMSIG_LIVING_DEATH) + return ..() + +/datum/element/crusher_loot/proc/on_death(mob/living/target, gibbed) + SIGNAL_HANDLER + + var/datum/status_effect/crusher_damage/damage = target.has_status_effect(/datum/status_effect/crusher_damage) + if(damage && prob((damage.total_damage/target.maxHealth) * drop_mod)) //on average, you'll need to kill 4 creatures before getting the item. by default. + if(drop_immediately) + new trophy_type(get_turf(target)) + else + target.butcher_results[trophy_type] = 1 diff --git a/code/datums/elements/mob_killed_tally.dm b/code/datums/elements/mob_killed_tally.dm new file mode 100644 index 00000000000..81043991d26 --- /dev/null +++ b/code/datums/elements/mob_killed_tally.dm @@ -0,0 +1,29 @@ +/** + * Mob Killed Tally; which ticks up a blackbox when the mob dies + * + * Used for all the mining mobs! + */ +/datum/element/mob_killed_tally + element_flags = ELEMENT_BESPOKE|ELEMENT_DETACH + id_arg_index = 2 + /// Which tally needs to be ticked up in the blackbox + var/tally_string + +/datum/element/mob_killed_tally/Attach(datum/target, tally_string) + . = ..() + + if(!isliving(target)) + return ELEMENT_INCOMPATIBLE + + RegisterSignal(target, COMSIG_LIVING_DEATH, .proc/on_death) + + src.tally_string = tally_string + +/datum/element/mob_killed_tally/Detach(datum/target) + UnregisterSignal(target, COMSIG_LIVING_DEATH) + return ..() + +/datum/element/mob_killed_tally/proc/on_death(mob/living/target, gibbed) + SIGNAL_HANDLER + + SSblackbox.record_feedback("tally", tally_string, 1, target.type) diff --git a/code/datums/mapgen/Cavegens/LavalandGenerator.dm b/code/datums/mapgen/Cavegens/LavalandGenerator.dm index 780b426ea11..34ae78696ae 100644 --- a/code/datums/mapgen/Cavegens/LavalandGenerator.dm +++ b/code/datums/mapgen/Cavegens/LavalandGenerator.dm @@ -3,11 +3,18 @@ closed_turf_types = list(/turf/closed/mineral/random/volcanic = 1) - mob_spawn_list = list(/mob/living/simple_animal/hostile/asteroid/goliath/beast/random = 50, /obj/structure/spawner/lavaland/goliath = 3, \ - /mob/living/simple_animal/hostile/asteroid/basilisk/watcher/random = 40, /obj/structure/spawner/lavaland = 2, \ - /mob/living/simple_animal/hostile/asteroid/hivelord/legion/random = 30, /obj/structure/spawner/lavaland/legion = 3, \ - SPAWN_MEGAFAUNA = 4, /mob/living/simple_animal/hostile/asteroid/goldgrub = 10, - /mob/living/simple_animal/hostile/asteroid/lobstrosity/lava = 20, /mob/living/simple_animal/hostile/asteroid/brimdemon = 20 + mob_spawn_list = list( + /mob/living/simple_animal/hostile/asteroid/goliath/beast/random = 50, + /obj/structure/spawner/lavaland/goliath = 3, + /mob/living/simple_animal/hostile/asteroid/basilisk/watcher/random = 40, + /obj/structure/spawner/lavaland = 2, + /mob/living/simple_animal/hostile/asteroid/hivelord/legion/random = 30, + /obj/structure/spawner/lavaland/legion = 3, + SPAWN_MEGAFAUNA = 4, + /mob/living/simple_animal/hostile/asteroid/goldgrub = 10, + /mob/living/simple_animal/hostile/asteroid/lobstrosity/lava = 20, + /mob/living/simple_animal/hostile/asteroid/brimdemon = 20, + /mob/living/basic/bileworm = 20, ) flora_spawn_list = list(/obj/structure/flora/ash/leaf_shroom = 2 , /obj/structure/flora/ash/cap_shroom = 2 , /obj/structure/flora/ash/stem_shroom = 2 , /obj/structure/flora/ash/cacti = 1, /obj/structure/flora/ash/tall_shroom = 2, /obj/structure/flora/ash/seraka = 2) ///Note that this spawn list is also in the icemoon generator diff --git a/code/datums/ruins/lavaland.dm b/code/datums/ruins/lavaland.dm index ff55ab9a103..7ae287a4b1d 100644 --- a/code/datums/ruins/lavaland.dm +++ b/code/datums/ruins/lavaland.dm @@ -253,3 +253,11 @@ suffix = "lavaland_surface_elephant_graveyard.dmm" allow_duplicates = FALSE cost = 10 + +/datum/map_template/ruin/lavaland/bileworm_nest + name = "Bileworm Nest" + id = "bileworm_nest" + description = "A small sanctuary from the harsh wilderness... if you're a bileworm, that is." + cost = 5 + suffix = "lavaland_surface_bileworm_nest.dmm" + allow_duplicates = FALSE diff --git a/code/game/objects/items/food/meat.dm b/code/game/objects/items/food/meat.dm index e5f2308e65b..e0a6db240da 100644 --- a/code/game/objects/items/food/meat.dm +++ b/code/game/objects/items/food/meat.dm @@ -847,6 +847,11 @@ name = "monkey meat" foodtypes = RAW | MEAT +/obj/item/food/meat/slab/bugmeat + name = "bug meat" + icon_state = "spidermeat" + foodtypes = RAW | MEAT | BUGS + /obj/item/food/meat/slab/mouse name = "mouse meat" desc = "A slab of mouse meat. Best not eat it raw." diff --git a/code/game/turfs/open/asteroid.dm b/code/game/turfs/open/asteroid.dm index 1ca7449645e..fdf21c7a293 100644 --- a/code/game/turfs/open/asteroid.dm +++ b/code/game/turfs/open/asteroid.dm @@ -92,6 +92,11 @@ base_icon_state = "asteroid_dug" icon_state = "asteroid_dug" +/turf/open/misc/asteroid/lavaland_atmos + initial_gas_mix = LAVALAND_DEFAULT_ATMOS + planetary_atmos = TRUE + baseturfs = /turf/open/misc/asteroid/lavaland_atmos + /// Used by ashstorms to replenish basalt tiles that have been dug up without going through all of them. GLOBAL_LIST_EMPTY(dug_up_basalt) diff --git a/code/modules/antagonists/morph/morph.dm b/code/modules/antagonists/morph/morph.dm index 02620776ec5..d95178a852f 100644 --- a/code/modules/antagonists/morph/morph.dm +++ b/code/modules/antagonists/morph/morph.dm @@ -47,6 +47,7 @@ . = ..() ADD_TRAIT(src, TRAIT_ALERT_GHOSTS_ON_DEATH, INNATE_TRAIT) ADD_TRAIT(src, TRAIT_VENTCRAWLER_ALWAYS, INNATE_TRAIT) + AddElement(/datum/element/content_barfer) /mob/living/simple_animal/hostile/morph/examine(mob/user) if(morphed) @@ -148,19 +149,8 @@ visible_message(span_warning("[src] twists and dissolves into a pile of green flesh!"), \ span_userdanger("Your skin ruptures! Your flesh breaks apart! No disguise can ward off de--")) restore() - barf_contents() ..() -/mob/living/simple_animal/hostile/morph/proc/barf_contents() - for(var/atom/movable/AM in src) - AM.forceMove(loc) - if(prob(90)) - step(AM, pick(GLOB.alldirs)) - -/mob/living/simple_animal/hostile/morph/wabbajack_act(mob/living/new_mob) - barf_contents() - . = ..() - /mob/living/simple_animal/hostile/morph/Aggro() // automated only ..() if(morphed) diff --git a/code/modules/instruments/instrument_data/fun.dm b/code/modules/instruments/instrument_data/fun.dm index 790abe46473..378a2dde96c 100644 --- a/code/modules/instruments/instrument_data/fun.dm +++ b/code/modules/instruments/instrument_data/fun.dm @@ -23,3 +23,9 @@ id = "mothscream" real_samples = list("60"='sound/voice/moth/scream_moth.ogg') admin_only = TRUE + +/datum/instrument/fun/bilehorn + name = "Bilehorn" + id = "bilehorn" + real_samples = list("60"='sound/creatures/bileworm/bileworm_spit.ogg') + admin_only = TRUE diff --git a/code/modules/mining/equipment/kinetic_crusher.dm b/code/modules/mining/equipment/kinetic_crusher.dm index 12fefce5e52..7142c38e271 100644 --- a/code/modules/mining/equipment/kinetic_crusher.dm +++ b/code/modules/mining/equipment/kinetic_crusher.dm @@ -223,21 +223,20 @@ else ..() -/obj/item/crusher_trophy/proc/add_to(obj/item/kinetic_crusher/H, mob/living/user) - for(var/t in H.trophies) - var/obj/item/crusher_trophy/T = t - if(istype(T, denied_type) || istype(src, T.denied_type)) - to_chat(user, span_warning("You can't seem to attach [src] to [H]. Maybe remove a few trophies?")) +/obj/item/crusher_trophy/proc/add_to(obj/item/kinetic_crusher/crusher, mob/living/user) + for(var/obj/item/crusher_trophy/trophy as anything in crusher.trophies) + if(istype(trophy, denied_type) || istype(src, trophy.denied_type)) + to_chat(user, span_warning("You can't seem to attach [src] to [crusher]. Maybe remove a few trophies?")) return FALSE - if(!user.transferItemToLoc(src, H)) + if(!user.transferItemToLoc(src, crusher)) return - H.trophies += src - to_chat(user, span_notice("You attach [src] to [H].")) + crusher.trophies += src + to_chat(user, span_notice("You attach [src] to [crusher].")) return TRUE -/obj/item/crusher_trophy/proc/remove_from(obj/item/kinetic_crusher/H, mob/living/user) - forceMove(get_turf(H)) - H.trophies -= src +/obj/item/crusher_trophy/proc/remove_from(obj/item/kinetic_crusher/crusher, mob/living/user) + forceMove(get_turf(crusher)) + crusher.trophies -= src return TRUE /obj/item/crusher_trophy/proc/on_melee_hit(mob/living/target, mob/living/user) //the target and the user diff --git a/code/modules/mob/living/basic/lavaland/bileworm/_bileworm.dm b/code/modules/mob/living/basic/lavaland/bileworm/_bileworm.dm new file mode 100644 index 00000000000..5e2b6daffdc --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/bileworm/_bileworm.dm @@ -0,0 +1,56 @@ +/mob/living/basic/bileworm + name = "bileworm" + desc = "Bileworms are dangerous detritivores that attack with the highly acidic bile they produce from consuming detritus." + icon = 'icons/mob/lavaland/bileworm.dmi' + icon_state = "bileworm" + icon_living = "bileworm" + icon_dead = "bileworm_dead" + mob_size = MOB_SIZE_LARGE + mob_biotypes = MOB_BUG + maxHealth = 150 + health = 150 + verb_say = "spittles" + verb_ask = "spittles questioningly" + verb_exclaim = "splutters and gurgles" + verb_yell = "splutters and gurgles" + butcher_results = list(/obj/item/food/meat/slab/bugmeat = 4) + guaranteed_butcher_results = list( + /obj/effect/gibspawner/generic = 1, + /obj/item/stack/sheet/animalhide/bileworm = 1, + ) + + //it can't be dragged, just butcher it + move_resist = INFINITY + //doesn't melee, at all. + //or move normally. + + combat_mode = TRUE + faction = list("mining") + + ai_controller = /datum/ai_controller/basic_controller/bileworm + +/mob/living/basic/bileworm/Initialize(mapload) + . = ..() + //traits and elements + + ADD_TRAIT(src, TRAIT_IMMOBILIZED, INNATE_TRAIT) + ADD_TRAIT(src, TRAIT_LAVA_IMMUNE, INNATE_TRAIT) + ADD_TRAIT(src, TRAIT_ASHSTORM_IMMUNE, INNATE_TRAIT) + AddElement(/datum/element/basic_body_temp_sensitive, max_body_temp = INFINITY) + AddElement(/datum/element/crusher_loot, /obj/item/crusher_trophy/bileworm_spewlet, 15) + AddElement(/datum/element/mob_killed_tally, "mobs_killed_mining") + AddElement(/datum/element/content_barfer) + + //setup mob abilities + + var/datum/action/cooldown/mob_cooldown/projectile_attack/dir_shots/bileworm/spew_bile = new(src) + spew_bile.Grant(src) + //well, one of them has to start on infinite cooldown + spew_bile.StartCooldownSelf(INFINITY) + var/datum/action/cooldown/mob_cooldown/resurface/resurface = new(src) + resurface.Grant(src) + var/datum/action/cooldown/mob_cooldown/devour/devour = new(src) + devour.Grant(src) + ai_controller.blackboard[BB_BILEWORM_SPEW_BILE] = spew_bile + ai_controller.blackboard[BB_BILEWORM_RESURFACE] = resurface + ai_controller.blackboard[BB_BILEWORM_DEVOUR] = devour diff --git a/code/modules/mob/living/basic/lavaland/bileworm/bileworm_actions.dm b/code/modules/mob/living/basic/lavaland/bileworm/bileworm_actions.dm new file mode 100644 index 00000000000..ace68ae90b1 --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/bileworm/bileworm_actions.dm @@ -0,0 +1,123 @@ +/datum/action/cooldown/mob_cooldown/resurface + name = "Resurface" + desc = "Burrow underground, and then move to a new location near your target. Must spew bile to refresh." + shared_cooldown = MOB_SHARED_COOLDOWN_1 | MOB_SHARED_COOLDOWN_2 + +/datum/action/cooldown/mob_cooldown/resurface/Activate(atom/target_atom) + StartCooldownSelf(INFINITY) + burrow(owner, target_atom) + //spew now off cooldown shortly + StartCooldownOthers(1.5 SECONDS) + +/datum/action/cooldown/mob_cooldown/resurface/proc/burrow(mob/living/burrower, atom/target) + var/turf/unburrow_turf = get_unburrow_turf(burrower, target) + if(!unburrow_turf) // means all the turfs nearby are station turfs or something, not lavaland + to_chat(burrower, span_warning("Couldn't burrow anywhere near the target!")) + return //just put it on cooldown and let the other ability reactivate, you couldn't burrow and that's okay. + playsound(burrower, 'sound/effects/break_stone.ogg', 50, TRUE) + new /obj/effect/temp_visual/mook_dust(get_turf(burrower)) + burrower.status_flags |= GODMODE + burrower.invisibility = INVISIBILITY_MAXIMUM + burrower.forceMove(unburrow_turf) + //not that it's gonna die with godmode but still + SLEEP_CHECK_DEATH(rand(0.75 SECONDS, 1.25 SECONDS), burrower) + playsound(burrower, 'sound/effects/break_stone.ogg', 50, TRUE) + new /obj/effect/temp_visual/mook_dust(unburrow_turf) + burrower.status_flags &= ~GODMODE + burrower.invisibility = 0 + +/datum/action/cooldown/mob_cooldown/resurface/proc/get_unburrow_turf(mob/living/burrower, atom/target) + //we want the worm to try guaranteeing a hit on a living target if it thinks it can + var/cardinal_only = FALSE + + if(isliving(target)) + var/mob/living/living_target = target + if(living_target.stat >= UNCONSCIOUS) + cardinal_only = TRUE + + var/list/potential_turfs = shuffle(oview(5, target))//get in view, shuffle + var/list/fallback_turfs = list() + for(var/turf/open/misc/chosen_one in potential_turfs)//first turf that counts as ground + if(cardinal_only && !(get_dir(chosen_one, target) in GLOB.cardinals)) + fallback_turfs.Add(chosen_one) + continue + return chosen_one + //even if a worm can't execute someone in crit, it should not fail if it has SOMETHING to move to. + if(fallback_turfs.len) + return pick(fallback_turfs) + +/datum/action/cooldown/mob_cooldown/projectile_attack/dir_shots/bileworm + name = "Spew Bile" + desc = "Spews bile everywhere. Must resurface after use to refresh." + projectile_type = /obj/projectile/bileworm_acid + projectile_sound = 'sound/creatures/bileworm/bileworm_spit.ogg' + shared_cooldown = MOB_SHARED_COOLDOWN_1 | MOB_SHARED_COOLDOWN_2 + +/datum/action/cooldown/mob_cooldown/projectile_attack/dir_shots/bileworm/Activate(atom/target_atom) + StartCooldownSelf(INFINITY) + attack_sequence(owner, target_atom) + //resurface now off cooldown shortly + StartCooldownOthers(1.5 SECONDS) + +/datum/action/cooldown/mob_cooldown/projectile_attack/dir_shots/bileworm/attack_sequence(mob/living/firer, atom/target) + fire_in_directions(firer, target, GLOB.cardinals) + SLEEP_CHECK_DEATH(0.25 SECONDS, firer) + fire_in_directions(firer, target, GLOB.diagonals) + SLEEP_CHECK_DEATH(0.25 SECONDS, firer) + fire_in_directions(firer, target, GLOB.cardinals) + +/obj/projectile/bileworm_acid + name = "acidic bile" + icon_state = "neurotoxin" + hitsound = 'sound/weapons/sear.ogg' + damage = 20 + armour_penetration = 100 + speed = 2 + jitter = 3 SECONDS + stutter = 3 SECONDS + damage_type = BURN + pass_flags = PASSTABLE + +/datum/action/cooldown/mob_cooldown/devour + name = "Devour" + desc = "Burrow underground, and then move to your target to consume them. Short cooldown, but your target must be unconscious." + shared_cooldown = MOB_SHARED_COOLDOWN_2 + +/datum/action/cooldown/mob_cooldown/devour/Activate(atom/target_atom) + if(target_atom == owner) + to_chat(owner, span_warning("You can't eat yourself!")) + return + if(!isliving(target_atom)) + to_chat(owner, span_warning("That's not food!")) + return + var/mob/living/living_target = target_atom + if(living_target.stat < UNCONSCIOUS) + to_chat(owner, span_warning("No way you're eating that while it's still kicking! It should at least be unconscious first.")) + return + burrow_and_devour(owner, living_target) + +/datum/action/cooldown/mob_cooldown/devour/proc/burrow_and_devour(mob/living/devourer, mob/living/target) + var/turf/devour_turf = get_turf(target) + if(!istype(devour_turf, /turf/open/misc)) // means all the turfs nearby are station turfs or something, not lavaland + to_chat(devourer, span_warning("Your target is on something you can't burrow through!")) + return //this will give up on devouring the target which is fine by me + playsound(devourer, 'sound/effects/break_stone.ogg', 50, TRUE) + new /obj/effect/temp_visual/mook_dust(get_turf(devourer)) + devourer.status_flags |= GODMODE + devourer.invisibility = INVISIBILITY_MAXIMUM + devourer.forceMove(devour_turf) + //not that it's gonna die with godmode but still + SLEEP_CHECK_DEATH(rand(0.75 SECONDS, 1.25 SECONDS), devourer) + playsound(devourer, 'sound/effects/break_stone.ogg', 50, TRUE) + new /obj/effect/temp_visual/mook_dust(devour_turf) + devourer.status_flags &= ~GODMODE + devourer.invisibility = 0 + if(!(target in devour_turf)) + to_chat(devourer, span_warning("Someone stole your dinner!")) + return + to_chat(target, span_userdanger("You are consumed by [devourer]!")) + devourer.visible_message("[devourer] consumes [target]!") + devourer.fully_heal() + playsound(devourer, 'sound/effects/splat.ogg', 50, TRUE) + //to be recieved on death + target.forceMove(devourer) diff --git a/code/modules/mob/living/basic/lavaland/bileworm/bileworm_ai.dm b/code/modules/mob/living/basic/lavaland/bileworm/bileworm_ai.dm new file mode 100644 index 00000000000..4ef7e3df99b --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/bileworm/bileworm_ai.dm @@ -0,0 +1,39 @@ +/datum/ai_controller/basic_controller/bileworm + blackboard = list( + BB_TARGETTING_DATUM = new /datum/targetting_datum/basic(), + ) + + planning_subtrees = list( + /datum/ai_planning_subtree/simple_find_target, + /datum/ai_planning_subtree/bileworm_attack, + /datum/ai_planning_subtree/bileworm_execute, + ) + +/datum/ai_planning_subtree/bileworm_attack + +/datum/ai_planning_subtree/bileworm_attack/SelectBehaviors(datum/ai_controller/controller, delta_time) + + var/atom/target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET] + if(!target || QDELETED(target)) + return + + var/datum/action/cooldown/mob_cooldown/resurface = controller.blackboard[BB_BILEWORM_RESURFACE] + + //because one ability is always INFINITY cooldown, this actually works to check which ability should be used + //sometimes it will try to spew bile on infinity cooldown, but that's okay because as soon as resurface is ready it will attempt that + if(resurface.next_use_time <= world.time) + controller.queue_behavior(/datum/ai_behavior/try_mob_ability/and_plan_execute, BB_BILEWORM_RESURFACE, BB_BASIC_MOB_CURRENT_TARGET) + else + controller.queue_behavior(/datum/ai_behavior/try_mob_ability/and_plan_execute, BB_BILEWORM_SPEW_BILE, BB_BASIC_MOB_CURRENT_TARGET) + return SUBTREE_RETURN_FINISH_PLANNING //focus on the fight + +/datum/ai_planning_subtree/bileworm_execute + +/datum/ai_planning_subtree/bileworm_execute/SelectBehaviors(datum/ai_controller/controller, delta_time) + + var/mob/living/target = controller.blackboard[BB_BASIC_MOB_EXECUTION_TARGET] + if(!target || QDELETED(target) || target.stat < UNCONSCIOUS) + return + + controller.queue_behavior(/datum/ai_behavior/try_mob_ability, BB_BILEWORM_DEVOUR, BB_BASIC_MOB_EXECUTION_TARGET) + return SUBTREE_RETURN_FINISH_PLANNING //focus on devouring this fool diff --git a/code/modules/mob/living/basic/lavaland/bileworm/bileworm_instrument.dm b/code/modules/mob/living/basic/lavaland/bileworm/bileworm_instrument.dm new file mode 100644 index 00000000000..f63e360c345 --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/bileworm/bileworm_instrument.dm @@ -0,0 +1,18 @@ +/obj/item/instrument/bilehorn + name = "bilehorn" + desc = "Bits of bileworm anatomy rearranged to produce wonderful music, not bile. Keeps the name though, because for an instrument, it is quite vile." + force = 5 + icon = 'icons/mob/lavaland/bileworm.dmi' + icon_state = "bilehorn" + allowed_instrument_ids = "bilehorn" + inhand_icon_state = "bilehorn" + +/datum/crafting_recipe/bilehorn + name = "Bilehorn" + reqs = list( + /obj/item/stack/sheet/animalhide/bileworm = 4, + /obj/item/stack/sheet/bone = 2, + /obj/item/crusher_trophy/bileworm_spewlet = 1, + ) + result = /obj/item/instrument/bilehorn + category = CAT_PRIMAL diff --git a/code/modules/mob/living/basic/lavaland/bileworm/bileworm_loot.dm b/code/modules/mob/living/basic/lavaland/bileworm/bileworm_loot.dm new file mode 100644 index 00000000000..6bf2c50d4cb --- /dev/null +++ b/code/modules/mob/living/basic/lavaland/bileworm/bileworm_loot.dm @@ -0,0 +1,58 @@ + +//skin + +/obj/item/stack/sheet/animalhide/bileworm + name = "bileworm skin" + desc = "The slushy, squishy and slightly shiny skin of a postmortem bileworm." + singular_name = "bileworm skin piece" + icon = 'icons/mob/lavaland/bileworm.dmi' + icon_state = "sheet-bileworm" + inhand_icon_state = "sheet-bileworm" + merge_type = /obj/item/stack/sheet/animalhide/bileworm + +//trophy + +/obj/item/crusher_trophy/bileworm_spewlet + name = "bileworm spewlet" + icon = 'icons/mob/lavaland/bileworm.dmi' + icon_state = "bileworm_spewlet" + desc = "A baby bileworm. Suitable as a trophy for a kinetic crusher." + denied_type = /obj/item/crusher_trophy/bileworm_spewlet + ///item ability that handles the effect + var/datum/action/cooldown/mob_cooldown/projectile_attack/dir_shots/spewlet/ability + +/obj/item/crusher_trophy/bileworm_spewlet/Initialize(mapload) + . = ..() + ability = new() + +/obj/item/crusher_trophy/bileworm_spewlet/Destroy(force) + . = ..() + QDEL_NULL(ability) + +/obj/item/crusher_trophy/bileworm_spewlet/add_to(obj/item/kinetic_crusher/crusher, mob/living/user) + . = ..() + if(.) + crusher.add_item_action(ability) + +/obj/item/crusher_trophy/bileworm_spewlet/remove_from(obj/item/kinetic_crusher/crusher, mob/living/user) + . = ..() + crusher.remove_item_action(ability) + +/obj/item/crusher_trophy/bileworm_spewlet/effect_desc() + return "mark detonation launches projectiles in cardinal directions on a 10 second cooldown" + +/obj/item/crusher_trophy/bileworm_spewlet/on_mark_detonation(mob/living/target, mob/living/user) + //ability itself handles cooldowns. + ability.InterceptClickOn(user, null, target) + +//yes this is a /mob_cooldown subtype being added to an item. I can't recommend you do what I'm doing +/datum/action/cooldown/mob_cooldown/projectile_attack/dir_shots/spewlet + check_flags = NONE + owner_has_control = FALSE + cooldown_time = 10 SECONDS + projectile_type = /obj/projectile/bileworm_acid + projectile_sound = 'sound/creatures/bileworm/bileworm_spit.ogg' + +/datum/action/cooldown/mob_cooldown/projectile_attack/dir_shots/spewlet/New(Target) + firing_directions = GLOB.cardinals.Copy() + return ..() diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index d07e6c7b4e0..716db5630eb 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -1419,6 +1419,7 @@ // Generally the mob we are currently in is about to be deleted /mob/living/proc/wabbajack_act(mob/living/new_mob) log_message("was wabbajack polymorphed into: [new_mob.name]([new_mob.type]).", LOG_GAME) + SEND_SIGNAL(src, COMSIG_LIVING_WABBAJACKED, new_mob) new_mob.name = real_name new_mob.real_name = real_name diff --git a/code/modules/mob/living/simple_animal/hostile/mining_mobs/hivelord.dm b/code/modules/mob/living/simple_animal/hostile/mining_mobs/hivelord.dm index faf629dc2d9..07c5a3143a9 100644 --- a/code/modules/mob/living/simple_animal/hostile/mining_mobs/hivelord.dm +++ b/code/modules/mob/living/simple_animal/hostile/mining_mobs/hivelord.dm @@ -53,9 +53,6 @@ OpenFire() return TRUE -/mob/living/simple_animal/hostile/asteroid/hivelord/spawn_crusher_loot() - loot += crusher_loot //we don't butcher - /mob/living/simple_animal/hostile/asteroid/hivelord/death(gibbed) mouse_opacity = MOUSE_OPACITY_ICON ..(gibbed) diff --git a/code/modules/mob/living/simple_animal/hostile/mining_mobs/ice_demon.dm b/code/modules/mob/living/simple_animal/hostile/mining_mobs/ice_demon.dm index 671f8d8f44c..3dd200f4b28 100644 --- a/code/modules/mob/living/simple_animal/hostile/mining_mobs/ice_demon.dm +++ b/code/modules/mob/living/simple_animal/hostile/mining_mobs/ice_demon.dm @@ -74,9 +74,6 @@ SLEEP_CHECK_DEATH(8, src) return ..() -/mob/living/simple_animal/hostile/asteroid/ice_demon/spawn_crusher_loot() - loot += crusher_loot - /mob/living/simple_animal/hostile/asteroid/ice_demon/death(gibbed) move_force = MOVE_FORCE_DEFAULT move_resist = MOVE_RESIST_DEFAULT diff --git a/code/modules/mob/living/simple_animal/hostile/mining_mobs/mining_mobs.dm b/code/modules/mob/living/simple_animal/hostile/mining_mobs/mining_mobs.dm index c274acd7150..1c2bdf52d70 100644 --- a/code/modules/mob/living/simple_animal/hostile/mining_mobs/mining_mobs.dm +++ b/code/modules/mob/living/simple_animal/hostile/mining_mobs/mining_mobs.dm @@ -13,15 +13,24 @@ response_harm_simple = "strike" status_flags = 0 combat_mode = TRUE - var/crusher_loot var/throw_message = "bounces off of" var/fromtendril = FALSE see_in_dark = 8 lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE mob_size = MOB_SIZE_LARGE var/icon_aggro = null + + ///what trophy this mob drops + var/crusher_loot + ///what is the chance the mob drops it if all their health was taken by crusher attacks var/crusher_drop_mod = 25 +/mob/living/simple_animal/hostile/asteroid/Initialize(mapload) + . = ..() + if(crusher_loot) + AddElement(/datum/element/crusher_loot, crusher_loot, crusher_drop_mod, del_on_death) + AddElement(/datum/element/mob_killed_tally, "mobs_killed_mining") + /mob/living/simple_animal/hostile/asteroid/Aggro() ..() if(vision_range == aggro_vision_range && icon_aggro) @@ -50,21 +59,3 @@ visible_message(span_notice("The [T.name] [throw_message] [src.name]!")) return ..() - -/mob/living/simple_animal/hostile/asteroid/death(gibbed) - SSblackbox.record_feedback("tally", "mobs_killed_mining", 1, type) - var/datum/status_effect/crusher_damage/C = has_status_effect(/datum/status_effect/crusher_damage) - var/crusher_loot_spawned = FALSE //SKYRAT ADDITION - if(C && crusher_loot && prob((C.total_damage/maxHealth) * crusher_drop_mod)) //on average, you'll need to kill 4 creatures before getting the item - spawn_crusher_loot() - crusher_loot_spawned = TRUE //SKYRAT ADDITION - //SKYRAT ADDITION START - ASHWALKER TROPHIES - var/datum/status_effect/ashwalker_damage/ashie_damage = has_status_effect(/datum/status_effect/ashwalker_damage) - if(!crusher_loot_spawned && ashie_damage && crusher_loot && prob((ashie_damage.total_damage / maxHealth) * crusher_drop_mod)) - spawn_crusher_loot() - crusher_loot_spawned = TRUE - //SKYRAT ADDITION END - ..(gibbed) - -/mob/living/simple_animal/hostile/asteroid/proc/spawn_crusher_loot() - butcher_results[crusher_loot] = 1 diff --git a/code/modules/mob/living/simple_animal/hostile/space_dragon.dm b/code/modules/mob/living/simple_animal/hostile/space_dragon.dm index dfdbdf635a6..3205a805dc9 100644 --- a/code/modules/mob/living/simple_animal/hostile/space_dragon.dm +++ b/code/modules/mob/living/simple_animal/hostile/space_dragon.dm @@ -105,6 +105,7 @@ ADD_TRAIT(src, TRAIT_NO_FLOATING_ANIM, INNATE_TRAIT) ADD_TRAIT(src, TRAIT_HEALS_FROM_CARP_RIFTS, INNATE_TRAIT) ADD_TRAIT(src, TRAIT_ALERT_GHOSTS_ON_DEATH, INNATE_TRAIT) + AddElement(/datum/element/content_barfer) small_sprite = new small_sprite.Grant(src) RegisterSignal(small_sprite, COMSIG_ACTION_TRIGGER, .proc/add_dragon_overlay) @@ -141,9 +142,9 @@ return if(riftTimer >= maxRiftTimer) to_chat(src, span_boldwarning("You've failed to summon the rift in a timely manner! You're being pulled back from whence you came!")) - destroy_rifts() + death(TRUE) playsound(src, 'sound/magic/demon_dies.ogg', 100, TRUE) - QDEL_NULL(src) + qdel(src) /mob/living/simple_animal/hostile/space_dragon/AttackingTarget() if(using_special) diff --git a/icons/mob/cows.dmi b/icons/mob/cows.dmi index 87c2247e0ed..28dc3123f0e 100644 Binary files a/icons/mob/cows.dmi and b/icons/mob/cows.dmi differ diff --git a/icons/mob/lavaland/bileworm.dmi b/icons/mob/lavaland/bileworm.dmi new file mode 100644 index 00000000000..f33cb0772eb Binary files /dev/null and b/icons/mob/lavaland/bileworm.dmi differ diff --git a/icons/obj/food/food.dmi b/icons/obj/food/food.dmi index c6b21dda81b..360866d5c4f 100644 Binary files a/icons/obj/food/food.dmi and b/icons/obj/food/food.dmi differ diff --git a/icons/obj/guns/projectiles.dmi b/icons/obj/guns/projectiles.dmi index de24bcb935b..8509e4228c2 100644 Binary files a/icons/obj/guns/projectiles.dmi and b/icons/obj/guns/projectiles.dmi differ diff --git a/icons/obj/items_and_weapons.dmi b/icons/obj/items_and_weapons.dmi index d463764b3b6..61eef185014 100644 Binary files a/icons/obj/items_and_weapons.dmi and b/icons/obj/items_and_weapons.dmi differ diff --git a/icons/obj/lavaland/artefacts.dmi b/icons/obj/lavaland/artefacts.dmi index 53fadffac27..5eb56db01c1 100644 Binary files a/icons/obj/lavaland/artefacts.dmi and b/icons/obj/lavaland/artefacts.dmi differ diff --git a/icons/obj/musician.dmi b/icons/obj/musician.dmi index c3e0f21f05a..22e5a24ba54 100644 Binary files a/icons/obj/musician.dmi and b/icons/obj/musician.dmi differ diff --git a/modular_skyrat/modules/modular_items/code/kinetic_crusher.dm b/modular_skyrat/modules/modular_items/code/kinetic_crusher.dm index b85d0218936..d023759b770 100644 --- a/modular_skyrat/modules/modular_items/code/kinetic_crusher.dm +++ b/modular_skyrat/modules/modular_items/code/kinetic_crusher.dm @@ -15,6 +15,3 @@ humi.coretemperature = humi.get_body_temp_normal() ..() -/mob/living/simple_animal/hostile/asteroid/ice_demon/spawn_crusher_loot() - for(var/item_path in crusher_loot) - new item_path(loc) diff --git a/sound/creatures/bileworm/bileworm_spit.ogg b/sound/creatures/bileworm/bileworm_spit.ogg new file mode 100644 index 00000000000..a1581dc2528 Binary files /dev/null and b/sound/creatures/bileworm/bileworm_spit.ogg differ diff --git a/tgstation.dme b/tgstation.dme index 9534b344c9d..52815ef3042 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -738,6 +738,7 @@ #include "code\datums\ai\basic_mobs\basic_ai_behaviors\basic_attacking.dm" #include "code\datums\ai\basic_mobs\basic_ai_behaviors\targetting.dm" #include "code\datums\ai\basic_mobs\basic_ai_behaviors\tipped_reaction.dm" +#include "code\datums\ai\basic_mobs\basic_ai_behaviors\try_mob_ability.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\simple_attack_target.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\simple_find_target.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\speech_subtree.dm" @@ -1045,7 +1046,9 @@ #include "code\datums\elements\cleaning.dm" #include "code\datums\elements\climbable.dm" #include "code\datums\elements\connect_loc.dm" +#include "code\datums\elements\content_barfer.dm" #include "code\datums\elements\crackable.dm" +#include "code\datums\elements\crusher_loot.dm" #include "code\datums\elements\cult_eyes.dm" #include "code\datums\elements\cult_halo.dm" #include "code\datums\elements\curse_announcement.dm" @@ -1078,6 +1081,7 @@ #include "code\datums\elements\light_blocking.dm" #include "code\datums\elements\light_eaten.dm" #include "code\datums\elements\light_eater.dm" +#include "code\datums\elements\mob_killed_tally.dm" #include "code\datums\elements\movement_turf_changer.dm" #include "code\datums\elements\movetype_handler.dm" #include "code\datums\elements\nerfed_pulling.dm" @@ -3461,6 +3465,11 @@ #include "code\modules\mob\living\basic\basic_defense.dm" #include "code\modules\mob\living\basic\health_adjustment.dm" #include "code\modules\mob\living\basic\farm_animals\cows.dm" +#include "code\modules\mob\living\basic\lavaland\bileworm\_bileworm.dm" +#include "code\modules\mob\living\basic\lavaland\bileworm\bileworm_actions.dm" +#include "code\modules\mob\living\basic\lavaland\bileworm\bileworm_ai.dm" +#include "code\modules\mob\living\basic\lavaland\bileworm\bileworm_instrument.dm" +#include "code\modules\mob\living\basic\lavaland\bileworm\bileworm_loot.dm" #include "code\modules\mob\living\basic\ruin_defender\stickman.dm" #include "code\modules\mob\living\basic\vermin\cockroach.dm" #include "code\modules\mob\living\basic\vermin\mothroach.dm"