From 966b8e5fd858091ce079e893b210606d0a9b6776 Mon Sep 17 00:00:00 2001 From: Jacquerel Date: Sat, 1 Apr 2023 03:40:29 +0100 Subject: [PATCH] March into Mapness: Meateor (#74070) --- _maps/RandomRuins/SpaceRuins/meateor.dmm | 981 ++++++++++++++++++ code/__DEFINES/ai.dm | 9 + .../basic_subtrees/sleep_with_no_target.dm | 33 + .../basic_subtrees/targeted_mob_ability.dm | 5 +- code/datums/components/spawner.dm | 74 +- code/datums/elements/floorloving.dm | 21 + code/datums/elements/frozen.dm | 17 +- code/datums/looping_sounds/_looping_sound.dm | 11 +- code/datums/looping_sounds/item_sounds.dm | 4 + code/datums/ruins/space.dm | 6 + code/datums/storage/subtypes/organ_box.dm | 4 +- code/game/objects/items/storage/medkit.dm | 79 +- code/game/turfs/closed/wall/misc_walls.dm | 13 + code/game/turfs/open/floor/misc_floor.dm | 14 +- .../heretic/items/heretic_necks.dm | 8 + .../mapfluff/ruins/spaceruin_code/meateor.dm | 131 +++ .../meteor_heart/chasing_spikes.dm | 112 ++ .../meteor_heart/meteor_eyeball.dm | 36 + .../space_fauna/meteor_heart/meteor_heart.dm | 131 +++ .../meteor_heart/meteor_heart_ai.dm | 32 + .../space_fauna/meteor_heart/spine_traps.dm | 97 ++ .../living/simple_animal/hostile/headcrab.dm | 27 +- .../hostile/mining_mobs/hivelord.dm | 4 +- code/modules/mob_spawn/corpses/mob_corpses.dm | 13 + .../mob_spawn/corpses/nonhuman_corpses.dm | 10 + config/spaceruinblacklist.txt | 1 + icons/mob/simple/meteor_heart.dmi | Bin 0 -> 2757 bytes tgstation.dme | 8 + 28 files changed, 1785 insertions(+), 96 deletions(-) create mode 100644 _maps/RandomRuins/SpaceRuins/meateor.dmm create mode 100644 code/datums/ai/basic_mobs/basic_subtrees/sleep_with_no_target.dm create mode 100644 code/datums/elements/floorloving.dm create mode 100644 code/modules/mapfluff/ruins/spaceruin_code/meateor.dm create mode 100644 code/modules/mob/living/basic/space_fauna/meteor_heart/chasing_spikes.dm create mode 100644 code/modules/mob/living/basic/space_fauna/meteor_heart/meteor_eyeball.dm create mode 100644 code/modules/mob/living/basic/space_fauna/meteor_heart/meteor_heart.dm create mode 100644 code/modules/mob/living/basic/space_fauna/meteor_heart/meteor_heart_ai.dm create mode 100644 code/modules/mob/living/basic/space_fauna/meteor_heart/spine_traps.dm create mode 100644 icons/mob/simple/meteor_heart.dmi diff --git a/_maps/RandomRuins/SpaceRuins/meateor.dmm b/_maps/RandomRuins/SpaceRuins/meateor.dmm new file mode 100644 index 00000000000..b9a731a08cb --- /dev/null +++ b/_maps/RandomRuins/SpaceRuins/meateor.dmm @@ -0,0 +1,981 @@ +//MAP CONVERTED BY dmm2tgm.py THIS HEADER COMMENT PREVENTS RECONVERSION, DO NOT REMOVE +"a" = ( +/turf/template_noop, +/area/template_noop) +"e" = ( +/obj/effect/mob_spawn/corpse/headcrab, +/turf/open/floor/material/meat/airless, +/area/ruin/space/meateor) +"f" = ( +/obj/effect/mob_spawn/corpse/human/monkey, +/obj/effect/decal/cleanable/blood/tracks{ + dir = 8 + }, +/obj/effect/decal/cleanable/blood, +/turf/open/floor/material/meat/airless, +/area/ruin/space/meateor) +"g" = ( +/turf/closed/mineral/random, +/area/ruin/space/meateor) +"j" = ( +/obj/item/gun/energy/recharge/kinetic_accelerator, +/obj/effect/decal/cleanable/blood, +/turf/open/floor/material/meat/airless, +/area/ruin/space/meateor) +"k" = ( +/obj/item/flashlight/flare, +/turf/open/floor/material/meat/airless, +/area/ruin/space/meateor) +"m" = ( +/obj/item/toy/plush/snakeplushie, +/turf/open/floor/material/meat/airless, +/area/ruin/space/meateor) +"p" = ( +/obj/structure/statue/bone/rib, +/turf/open/floor/material/meat/airless, +/area/ruin/space/meateor) +"q" = ( +/obj/effect/mob_spawn/corpse/headcrab, +/obj/effect/decal/cleanable/xenoblood, +/turf/open/floor/material/meat/airless, +/area/ruin/space/meateor) +"r" = ( +/obj/effect/decal/cleanable/blood/tracks{ + dir = 8 + }, +/turf/open/floor/material/meat/airless, +/area/ruin/space/meateor) +"s" = ( +/obj/effect/mob_spawn/corpse/headcrab, +/obj/effect/decal/cleanable/xenoblood/xgibs, +/turf/open/floor/material/meat/airless, +/area/ruin/space/meateor) +"v" = ( +/obj/structure/meateor_fluff/eyeball, +/turf/open/floor/material/meat/airless, +/area/ruin/space/meateor) +"z" = ( +/obj/effect/mob_spawn/corpse/human/tigercultist/perforated, +/obj/item/storage/organbox/preloaded{ + desc = "Often used, but not often cleaned."; + name = "abandoned lunchbox" + }, +/turf/open/floor/material/meat/airless, +/area/ruin/space/meateor) +"A" = ( +/turf/closed/wall/material/meat/airless, +/area/ruin/space/meateor) +"B" = ( +/obj/item/paper/fluff/ruins/meateor/letter, +/obj/structure/bed/maint, +/obj/item/reagent_containers/cup/glass/bottle/ritual_wine{ + pixel_x = -10 + }, +/turf/open/floor/material/meat/airless, +/area/ruin/space/meateor) +"C" = ( +/obj/effect/mob_spawn/corpse/human/miner, +/obj/effect/decal/cleanable/blood, +/turf/open/floor/material/meat/airless, +/area/ruin/space/meateor) +"D" = ( +/obj/structure/statue/bone/rib{ + dir = 8 + }, +/turf/open/floor/material/meat/airless, +/area/ruin/space/meateor) +"G" = ( +/obj/structure/meateor_fluff/flesh_pod, +/turf/open/floor/material/meat/airless, +/area/ruin/space/meateor) +"L" = ( +/obj/effect/spawner/random/trash/food_packaging, +/turf/open/floor/material/meat/airless, +/area/ruin/space/meateor) +"O" = ( +/turf/open/floor/material/meat/airless, +/area/ruin/space/meateor) +"R" = ( +/obj/structure/meateor_fluff/abandoned_headcrab_egg, +/turf/open/floor/material/meat/airless, +/area/ruin/space/meateor) +"S" = ( +/obj/effect/decal/remains/human, +/turf/open/floor/material/meat/airless, +/area/ruin/space/meateor) +"T" = ( +/turf/open/misc/asteroid/airless, +/area/ruin/space/meateor) +"X" = ( +/mob/living/basic/meteor_heart, +/turf/open/floor/material/meat/airless, +/area/ruin/space/meateor) + +(1,1,1) = {" +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +"} +(2,1,1) = {" +a +a +a +a +a +a +a +g +g +g +g +g +g +a +a +a +a +a +a +a +g +g +g +a +a +a +a +a +a +"} +(3,1,1) = {" +a +a +a +a +a +g +g +g +A +A +A +A +g +g +g +a +a +a +a +g +g +g +g +g +a +a +a +a +a +"} +(4,1,1) = {" +a +a +a +a +g +g +A +A +A +A +A +A +A +g +g +g +g +a +a +a +A +A +g +g +a +a +a +a +a +"} +(5,1,1) = {" +a +a +a +a +g +A +A +A +L +G +S +G +A +A +A +A +g +g +a +a +a +A +A +g +g +a +a +a +a +"} +(6,1,1) = {" +a +a +a +g +g +A +B +z +O +v +O +O +O +O +G +A +g +g +g +a +a +a +A +A +g +a +a +a +a +"} +(7,1,1) = {" +a +a +a +g +g +A +e +D +O +O +O +O +v +D +R +A +A +g +g +a +a +a +A +A +g +a +a +a +a +"} +(8,1,1) = {" +a +a +a +g +A +A +L +G +O +O +O +O +O +O +O +R +A +A +g +g +a +a +a +A +g +a +a +a +a +"} +(9,1,1) = {" +a +a +a +g +A +A +O +O +O +O +O +O +O +O +O +G +A +A +g +g +a +a +a +g +a +a +a +a +a +"} +(10,1,1) = {" +a +a +g +g +A +G +O +v +O +O +X +O +O +O +O +O +O +A +A +g +g +a +a +a +a +a +a +a +a +"} +(11,1,1) = {" +a +a +g +A +A +O +O +O +O +O +O +O +S +O +v +O +O +A +A +g +g +a +a +a +a +a +g +a +a +"} +(12,1,1) = {" +a +g +g +A +A +G +O +S +O +O +O +O +O +R +O +O +O +A +A +g +g +a +a +a +a +g +g +g +a +"} +(13,1,1) = {" +a +g +g +A +A +R +O +p +O +O +v +O +O +p +O +G +A +A +g +g +g +a +a +a +a +A +A +g +a +"} +(14,1,1) = {" +a +g +g +A +A +A +O +O +O +O +O +O +O +O +R +A +A +A +g +g +g +a +a +a +a +a +A +g +a +"} +(15,1,1) = {" +a +g +g +A +A +A +A +A +q +A +A +A +A +A +A +A +m +A +g +g +a +a +a +a +a +a +a +a +a +"} +(16,1,1) = {" +a +g +g +g +A +A +A +D +k +C +D +O +R +D +A +A +A +A +g +g +g +a +a +a +a +a +a +a +a +"} +(17,1,1) = {" +a +g +g +g +A +A +R +O +j +O +O +q +v +O +R +A +A +g +g +g +g +a +a +a +a +a +a +a +a +"} +(18,1,1) = {" +a +a +g +g +A +A +O +O +O +s +O +O +O +O +O +A +A +g +g +g +g +a +a +a +a +a +a +a +a +"} +(19,1,1) = {" +a +a +g +g +g +A +R +O +v +O +O +O +O +O +G +A +A +A +A +g +a +a +a +a +a +a +a +a +a +"} +(20,1,1) = {" +a +a +g +g +g +A +A +p +R +O +p +O +r +p +A +A +A +A +T +T +a +a +a +a +a +a +a +a +a +"} +(21,1,1) = {" +a +a +a +g +g +g +A +A +A +O +A +A +r +A +A +O +v +T +T +a +a +a +a +a +a +a +a +a +a +"} +(22,1,1) = {" +a +a +a +a +g +g +g +A +A +O +O +O +f +A +A +O +A +A +T +a +a +a +a +a +a +a +a +a +a +"} +(23,1,1) = {" +a +a +a +a +g +g +g +g +A +A +A +O +O +A +A +O +A +A +A +g +a +a +A +g +a +a +a +a +a +"} +(24,1,1) = {" +a +a +a +a +a +g +g +g +g +g +A +A +v +O +O +O +A +A +g +g +a +a +A +A +g +a +a +a +a +"} +(25,1,1) = {" +a +a +a +a +a +a +g +g +g +g +g +A +A +A +A +A +A +g +g +a +a +A +A +A +g +a +a +a +a +"} +(26,1,1) = {" +a +a +a +a +a +a +a +a +a +g +g +g +g +A +A +A +g +g +a +a +g +g +A +g +a +a +a +a +a +"} +(27,1,1) = {" +a +a +a +a +a +a +a +a +a +a +a +g +g +g +g +g +g +a +a +a +a +g +g +g +a +a +a +a +a +"} +(28,1,1) = {" +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +a +"} diff --git a/code/__DEFINES/ai.dm b/code/__DEFINES/ai.dm index 78dde256424..32ed1445b55 100644 --- a/code/__DEFINES/ai.dm +++ b/code/__DEFINES/ai.dm @@ -226,6 +226,9 @@ #define BB_BASIC_MOB_FLEE_TARGET_HIDING_LOCATION "BB_basic_flee_target_hiding_location" #define BB_FLEE_TARGETTING_DATUM "flee_targetting_datum" +///How long have we spent with no target? +#define BB_TARGETLESS_TIME "BB_targetless_time" + ///List of mobs who have damaged us #define BB_BASIC_MOB_RETALIATE_LIST "BB_basic_mob_shitlist" @@ -249,6 +252,12 @@ #define BB_BILEWORM_RESURFACE "BB_bileworm_resurface" #define BB_BILEWORM_DEVOUR "BB_bileworm_devour" +// Meteor Heart AI keys +/// Key where we keep the spike trail ability +#define BB_METEOR_HEART_GROUND_SPIKES "BB_meteor_ground_spikes" +/// Key where we keep the spine traps ability +#define BB_METEOR_HEART_SPINE_TRAPS "BB_meteor_spine_traps" + // Spider AI keys /// Key where we store a turf to put webs on #define BB_SPIDER_WEB_TARGET "BB_spider_web_target" diff --git a/code/datums/ai/basic_mobs/basic_subtrees/sleep_with_no_target.dm b/code/datums/ai/basic_mobs/basic_subtrees/sleep_with_no_target.dm new file mode 100644 index 00000000000..1cef3a65147 --- /dev/null +++ b/code/datums/ai/basic_mobs/basic_subtrees/sleep_with_no_target.dm @@ -0,0 +1,33 @@ +/// Disables AI after a certain amount of time spent with no target, you will have to enable the AI again somewhere else +/datum/ai_planning_subtree/sleep_with_no_target + /// Behaviour to execute when sleeping + var/sleep_behaviour = /datum/ai_behavior/sleep_after_targetless_time + /// Target key to interrogate + var/target_key = BB_BASIC_MOB_CURRENT_TARGET + +/datum/ai_planning_subtree/sleep_with_no_target/SelectBehaviors(datum/ai_controller/controller, delta_time) + . = ..() + controller.queue_behavior(sleep_behaviour, BB_BASIC_MOB_CURRENT_TARGET) + +/// Disables AI after a certain amount of time spent with no target, you will have to enable the AI again somewhere else +/datum/ai_behavior/sleep_after_targetless_time + /// Turn off AI if we spend this many seconds without a target, don't use the macro because delta_time is already in seconds + var/time_to_wait = 10 + +/datum/ai_behavior/sleep_after_targetless_time/perform(delta_time, datum/ai_controller/controller, target_key) + var/datum/weakref/weak_target = controller.blackboard[target_key] + var/atom/target = weak_target?.resolve() + finish_action(controller, succeeded = !target, delta_time = delta_time) + +/datum/ai_behavior/sleep_after_targetless_time/finish_action(datum/ai_controller/controller, succeeded, delta_time) + . = ..() + if (!succeeded) + controller.blackboard[BB_TARGETLESS_TIME] = 0 + return + controller.blackboard[BB_TARGETLESS_TIME] += delta_time + if (controller.blackboard[BB_TARGETLESS_TIME] > time_to_wait) + enter_sleep(controller) + +/// Disables AI, override to do additional things or something else +/datum/ai_behavior/sleep_after_targetless_time/proc/enter_sleep(datum/ai_controller/controller) + controller.set_ai_status(AI_STATUS_OFF) diff --git a/code/datums/ai/basic_mobs/basic_subtrees/targeted_mob_ability.dm b/code/datums/ai/basic_mobs/basic_subtrees/targeted_mob_ability.dm index f748a877f05..1508a9a6f44 100644 --- a/code/datums/ai/basic_mobs/basic_subtrees/targeted_mob_ability.dm +++ b/code/datums/ai/basic_mobs/basic_subtrees/targeted_mob_ability.dm @@ -6,6 +6,8 @@ var/target_key = BB_BASIC_MOB_CURRENT_TARGET /// Behaviour to perform using ability var/use_ability_behaviour = /datum/ai_behavior/targeted_mob_ability + /// If true we terminate planning after trying to use the ability. + var/finish_planning = TRUE /datum/ai_planning_subtree/targeted_mob_ability/SelectBehaviors(datum/ai_controller/controller, delta_time) if (!ability_key) @@ -22,4 +24,5 @@ return controller.queue_behavior(use_ability_behaviour, ability_key, target_key) - return SUBTREE_RETURN_FINISH_PLANNING + if (finish_planning) + return SUBTREE_RETURN_FINISH_PLANNING diff --git a/code/datums/components/spawner.dm b/code/datums/components/spawner.dm index 4b0122571f2..81eb38acc6a 100644 --- a/code/datums/components/spawner.dm +++ b/code/datums/components/spawner.dm @@ -1,30 +1,30 @@ /datum/component/spawner /// Time to wait between spawns var/spawn_time - /// Maximum number of mobs we can have active at one time - var/max_mobs - /// Visible message to show when a mob spawns + /// Maximum number of atoms we can have active at one time + var/max_spawned + /// Visible message to show when something spawns var/spawn_text - /// List of mob types to spawn, picked randomly - var/list/mob_types - /// Faction to grant to mobs + /// List of atom types to spawn, picked randomly + var/list/spawn_types + /// Faction to grant to mobs (only applies to mobs) var/list/faction - /// List of weak references to mobs we have already created - var/list/spawned_mobs = list() + /// List of weak references to things we have already created + var/list/spawned_things = list() /// Time until we next spawn COOLDOWN_DECLARE(spawn_delay) -/datum/component/spawner/Initialize(mob_types = list(), spawn_time = 30 SECONDS, max_mobs = 5, faction = list(FACTION_MINING), spawn_text = "emerges from") - if (!islist(mob_types)) - CRASH("invalid mob_types to spawn specified for spawner component!") +/datum/component/spawner/Initialize(spawn_types = list(), spawn_time = 30 SECONDS, max_spawned = 5, faction = list(FACTION_MINING), spawn_text = null) + if (!islist(spawn_types)) + CRASH("invalid spawn_types to spawn specified for spawner component!") src.spawn_time = spawn_time - src.mob_types = mob_types + src.spawn_types = spawn_types src.faction = faction src.spawn_text = spawn_text - src.max_mobs = max_mobs + src.max_spawned = max_spawned RegisterSignal(parent, COMSIG_PARENT_QDELETING, PROC_REF(stop_spawning)) - START_PROCESSING(SSprocessing, src) + START_PROCESSING((spawn_time < 2 SECONDS ? SSfastprocess : SSprocessing), src) /datum/component/spawner/process() try_spawn_mob() @@ -34,44 +34,54 @@ SIGNAL_HANDLER STOP_PROCESSING(SSprocessing, src) - spawned_mobs = list() + spawned_things = list() /// Try to create a new mob /datum/component/spawner/proc/try_spawn_mob() if(!COOLDOWN_FINISHED(src, spawn_delay)) return validate_references() - if(length(spawned_mobs) >= max_mobs) + if(length(spawned_things) >= max_spawned) return var/atom/spawner = parent COOLDOWN_START(src, spawn_delay, spawn_time) - var/chosen_mob_type = pick(mob_types) - var/mob/living/created = new chosen_mob_type(spawner.loc) + var/chosen_mob_type = pick(spawn_types) + var/atom/created = new chosen_mob_type(spawner.loc) created.flags_1 |= (spawner.flags_1 & ADMIN_SPAWNED_1) - spawned_mobs += WEAKREF(created) - created.faction = src.faction - spawner.visible_message(span_danger("[created] [spawn_text] [spawner].")) + spawned_things += WEAKREF(created) + if (isliving(created)) + var/mob/living/created_mob = created + created_mob.faction = src.faction + RegisterSignal(created, COMSIG_MOB_STATCHANGE, PROC_REF(mob_stat_changed)) - RegisterSignal(created, COMSIG_PARENT_QDELETING, PROC_REF(mob_deleted)) - RegisterSignal(created, COMSIG_MOB_STATCHANGE, PROC_REF(mob_stat_changed)) + if (spawn_text) + spawner.visible_message(span_danger("[created] [spawn_text] [spawner].")) -/// Remove weakrefs to mobs which have been killed or deleted without us picking it up somehow + RegisterSignal(created, COMSIG_PARENT_QDELETING, PROC_REF(on_deleted)) + +/// Remove weakrefs to atoms which have been killed or deleted without us picking it up somehow /datum/component/spawner/proc/validate_references() - for (var/datum/weakref/weak_mob as anything in spawned_mobs) - var/mob/living/previously_spawned = weak_mob.resolve() - if (previously_spawned && previously_spawned.stat != DEAD) + for (var/datum/weakref/weak_thing as anything in spawned_things) + var/atom/previously_spawned = weak_thing.resolve() + if (!previously_spawned) + spawned_things -= weak_thing continue - spawned_mobs -= weak_mob + if (!isliving(previously_spawned)) + continue + var/mob/living/spawned_mob = previously_spawned + if (spawned_mob.stat != DEAD) + continue + spawned_things -= weak_thing -/// Called when a mob we spawned is deleted, remove it from the list -/datum/component/spawner/proc/mob_deleted(mob/living/source) +/// Called when an atom we spawned is deleted, remove it from the list +/datum/component/spawner/proc/on_deleted(atom/source) SIGNAL_HANDLER - spawned_mobs -= WEAKREF(source) + spawned_things -= WEAKREF(source) /// Called when a mob we spawned dies, remove it from the list and unregister signals /datum/component/spawner/proc/mob_stat_changed(mob/living/source) if (source.stat != DEAD) return - spawned_mobs -= WEAKREF(source) + spawned_things -= WEAKREF(source) UnregisterSignal(source, list(COMSIG_PARENT_QDELETING, COMSIG_MOB_STATCHANGE)) diff --git a/code/datums/elements/floorloving.dm b/code/datums/elements/floorloving.dm new file mode 100644 index 00000000000..229dea11da8 --- /dev/null +++ b/code/datums/elements/floorloving.dm @@ -0,0 +1,21 @@ +/// Prevents a movable atom from moving to somewhere which isn't an open turf with floor on it +/datum/element/floor_loving + +/datum/element/floor_loving/Attach(datum/target) + . = ..() + if(!ismovable(target)) + return ELEMENT_INCOMPATIBLE + RegisterSignal(target, COMSIG_MOVABLE_PRE_MOVE, PROC_REF(attempting_move)) + +/datum/element/floor_loving/Detach(datum/target) + UnregisterSignal(target, COMSIG_MOVABLE_PRE_MOVE) + return ..() + +/// Block movement to any non-floor location +/datum/element/floor_loving/proc/attempting_move(atom/movable/parent, newloc) + SIGNAL_HANDLER + if (!isopenturf(newloc) || isspaceturf(newloc) || isopenspaceturf(newloc)) + return COMPONENT_MOVABLE_BLOCK_PRE_MOVE + if (isliving(parent)) + var/mob/living/living_parent = parent + living_parent.balloon_alert(living_parent, "can't move there!") diff --git a/code/datums/elements/frozen.dm b/code/datums/elements/frozen.dm index ca40f9d174a..3fdf13965cf 100644 --- a/code/datums/elements/frozen.dm +++ b/code/datums/elements/frozen.dm @@ -20,6 +20,13 @@ GLOBAL_LIST_INIT(freon_color_matrix, list("#2E5E69", "#60A2A8", "#A1AFB1", rgb(0 target_obj.add_atom_colour(GLOB.freon_color_matrix, TEMPORARY_COLOUR_PRIORITY) target_obj.alpha -= 25 + if (isinternalorgan(target)) + var/obj/item/organ/internal/organ = target + organ.organ_flags |= ORGAN_FROZEN + else if (isbodypart(target)) + for(var/obj/item/organ/internal/organ in target_obj.contents) + organ.organ_flags |= ORGAN_FROZEN + RegisterSignal(target, COMSIG_MOVABLE_MOVED, PROC_REF(on_moved)) RegisterSignal(target, COMSIG_MOVABLE_THROW_LANDED, PROC_REF(shatter_on_throw)) RegisterSignal(target, COMSIG_MOVABLE_IMPACT, PROC_REF(shatter_on_throw)) @@ -32,7 +39,15 @@ GLOBAL_LIST_INIT(freon_color_matrix, list("#2E5E69", "#60A2A8", "#A1AFB1", rgb(0 obj_source.name = replacetext(obj_source.name, "frozen ", "") obj_source.remove_atom_colour(TEMPORARY_COLOUR_PRIORITY, GLOB.freon_color_matrix) obj_source.alpha += 25 - . = ..() + + if (isinternalorgan(source)) + var/obj/item/organ/internal/organ = source + organ.organ_flags &= ~ORGAN_FROZEN + else if (isbodypart(source)) + for(var/obj/item/organ/internal/organ in obj_source.contents) + organ.organ_flags &= ~ORGAN_FROZEN + + return ..() ///signal handler for COMSIG_OBJ_UNFREEZE that forces us to detach from the target /datum/element/frozen/proc/on_unfreeze(datum/source) diff --git a/code/datums/looping_sounds/_looping_sound.dm b/code/datums/looping_sounds/_looping_sound.dm index d19b6fcdae3..d9cf7ed668c 100644 --- a/code/datums/looping_sounds/_looping_sound.dm +++ b/code/datums/looping_sounds/_looping_sound.dm @@ -120,10 +120,19 @@ return // If we have a timer, we're varying mid length, and this is happening while we're runnin mid_sounds if(timer_id && mid_length_vary && start_time) - updatetimedelay(timer_id, mid_length + rand(-mid_length_vary, mid_length_vary)) + updatetimedelay(timer_id, mid_length + rand(-mid_length_vary, mid_length_vary), timer_subsystem = SSsound_loops) if(!chance || prob(chance)) play(get_sound()) +/** + * Applies a new mid length to the sound + */ +/datum/looping_sound/proc/set_mid_length(new_mid) + mid_length = new_mid + if(!timer_id) + return + updatetimedelay(timer_id, mid_length + rand(-mid_length_vary, mid_length_vary), timer_subsystem = SSsound_loops) + /** * The proc that handles actually playing the sound. * diff --git a/code/datums/looping_sounds/item_sounds.dm b/code/datums/looping_sounds/item_sounds.dm index d396d8e292e..d38dd6737a3 100644 --- a/code/datums/looping_sounds/item_sounds.dm +++ b/code/datums/looping_sounds/item_sounds.dm @@ -19,6 +19,10 @@ start_sound = list('sound/items/taperecorder/taperecorder_hiss_start.ogg' = 1) volume = 10 +/datum/looping_sound/heartbeat + mid_sounds = list('sound/effects/singlebeat.ogg' = 1) + mid_length = 1 SECONDS + /datum/looping_sound/trapped_machine_beep mid_sounds = list('sound/machines/beep.ogg' = 1) mid_length = 10 SECONDS diff --git a/code/datums/ruins/space.dm b/code/datums/ruins/space.dm index 94963ebfc6f..1541a33dc48 100644 --- a/code/datums/ruins/space.dm +++ b/code/datums/ruins/space.dm @@ -358,6 +358,12 @@ name = "Alternate Sciences Research Center" description = "When you're messing with the occult, who knows what you're going to get?" +/datum/map_template/ruin/space/meateor + id = "meateor" + suffix = "meateor.dmm" + name = "Meateor" + description = "A big chunk of meat floating in space. How did it get there?" + /datum/map_template/ruin/space/the_faceoff id = "the_faceoff" suffix = "the_faceoff.dmm" diff --git a/code/datums/storage/subtypes/organ_box.dm b/code/datums/storage/subtypes/organ_box.dm index 997e4bf50ba..de1cb5f5071 100644 --- a/code/datums/storage/subtypes/organ_box.dm +++ b/code/datums/storage/subtypes/organ_box.dm @@ -1,9 +1,9 @@ /datum/storage/organ_box -/datum/storage/organ_box/handle_enter(datum/source, obj/item/arrived) +/datum/storage/organ_box/handle_enter(obj/item/storage/organbox/source, obj/item/arrived) . = ..() - if(!istype(arrived)) + if(!istype(arrived) || !istype(source) || !source.coolant_to_spend()) return arrived.freeze() diff --git a/code/game/objects/items/storage/medkit.dm b/code/game/objects/items/storage/medkit.dm index 3a4a6a5e2f3..28f5a9820e8 100644 --- a/code/game/objects/items/storage/medkit.dm +++ b/code/game/objects/items/storage/medkit.dm @@ -550,6 +550,7 @@ for(var/i in 1 to 5) new /obj/item/reagent_containers/pill/paxpsych(src) +/// A box which takes in coolant and uses it to preserve organs and body parts /obj/item/storage/organbox name = "organ transport box" desc = "An advanced box with an cooling mechanism that uses cryostylane or other cold reagents to keep the organs or bodyparts inside preserved." @@ -579,56 +580,39 @@ /obj/item/storage/organbox/process(delta_time) ///if there is enough coolant var - var/cool = FALSE - var/amount = min(reagents.get_reagent_amount(/datum/reagent/cryostylane), 0.05 * delta_time) - if(amount > 0) - reagents.remove_reagent(/datum/reagent/cryostylane, amount) - cool = TRUE - else - amount = min(reagents.get_reagent_amount(/datum/reagent/consumable/ice), 0.1 * delta_time) - if(amount > 0) - reagents.remove_reagent(/datum/reagent/consumable/ice, amount) - cool = TRUE - if(!cooling && cool) - cooling = TRUE - update_appearance() - for(var/C in contents) - freeze_contents(C) + var/using_coolant = coolant_to_spend() + if (isnull(using_coolant)) + if (cooling) + cooling = FALSE + update_appearance() + for(var/obj/stored in contents) + stored.unfreeze() return - if(cooling && !cool) - cooling = FALSE - update_appearance() - for(var/C in contents) - unfreeze_contents(C) + + var/amount_used = 0.05 * delta_time + if (using_coolant != /datum/reagent/cryostylane) + amount_used *= 2 + reagents.remove_reagent(using_coolant, amount_used) + + if(cooling) + return + cooling = TRUE + update_appearance() + for(var/obj/stored in contents) + stored.freeze() + +/// Returns which coolant we are about to use, or null if there isn't any +/obj/item/storage/organbox/proc/coolant_to_spend() + if (reagents.get_reagent_amount(/datum/reagent/cryostylane)) + return /datum/reagent/cryostylane + if (reagents.get_reagent_amount(/datum/reagent/consumable/ice)) + return /datum/reagent/consumable/ice + return null /obj/item/storage/organbox/update_icon_state() icon_state = "[base_icon_state][cooling ? "-working" : null]" return ..() -///freezes the organ and loops bodyparts like heads -/obj/item/storage/organbox/proc/freeze_contents(datum/source, obj/item/I) - SIGNAL_HANDLER - if(isinternalorgan(I)) - var/obj/item/organ/internal/int_organ = I - int_organ.organ_flags |= ORGAN_FROZEN - return - if(isbodypart(I)) - var/obj/item/bodypart/B = I - for(var/obj/item/organ/internal/int_organ in B.contents) - int_organ.organ_flags |= ORGAN_FROZEN - -///unfreezes the organ and loops bodyparts like heads -/obj/item/storage/organbox/proc/unfreeze_contents(datum/source, obj/item/I) - SIGNAL_HANDLER - if(isinternalorgan(I)) - var/obj/item/organ/internal/int_organ = I - int_organ.organ_flags &= ~ORGAN_FROZEN - return - if(isbodypart(I)) - var/obj/item/bodypart/B = I - for(var/obj/item/organ/internal/int_organ in B.contents) - int_organ.organ_flags &= ~ORGAN_FROZEN - /obj/item/storage/organbox/attackby(obj/item/I, mob/user, params) if(is_reagent_container(I) && I.is_open_container()) var/obj/item/reagent_containers/RC = I @@ -659,3 +643,10 @@ user.adjust_bodytemperature(-300) user.apply_status_effect(/datum/status_effect/freon) return FIRELOSS + +/// A subtype of organ storage box which starts with a full coolant tank +/obj/item/storage/organbox/preloaded + +/obj/item/storage/organbox/preloaded/Initialize(mapload) + . = ..() + reagents.add_reagent(/datum/reagent/cryostylane, reagents.maximum_volume) diff --git a/code/game/turfs/closed/wall/misc_walls.dm b/code/game/turfs/closed/wall/misc_walls.dm index 2d8c271359d..e9a7bce2086 100644 --- a/code/game/turfs/closed/wall/misc_walls.dm +++ b/code/game/turfs/closed/wall/misc_walls.dm @@ -135,3 +135,16 @@ smoothing_flags = NONE canSmoothWith = null smoothing_groups = null + +/turf/closed/wall/material/meat + name = "living wall" + baseturfs = /turf/open/floor/material/meat + girder_type = null + material_flags = MATERIAL_EFFECTS | MATERIAL_COLOR | MATERIAL_AFFECT_STATISTICS + +/turf/closed/wall/material/meat/Initialize(mapload) + . = ..() + set_custom_materials(list(GET_MATERIAL_REF(/datum/material/meat) = MINERAL_MATERIAL_AMOUNT)) + +/turf/closed/wall/material/meat/airless + baseturfs = /turf/open/floor/material/meat/airless diff --git a/code/game/turfs/open/floor/misc_floor.dm b/code/game/turfs/open/floor/misc_floor.dm index 94f78e15453..8a86bbb6845 100644 --- a/code/game/turfs/open/floor/misc_floor.dm +++ b/code/game/turfs/open/floor/misc_floor.dm @@ -323,7 +323,19 @@ /turf/open/floor/cult/airless initial_gas_mix = AIRLESS_ATMOS -/// TGMC EMBLEM /// +/turf/open/floor/material/meat + name = "living floor" + icon_state = "grey" + baseturfs = /turf/open/misc/asteroid + material_flags = MATERIAL_EFFECTS | MATERIAL_COLOR | MATERIAL_AFFECT_STATISTICS + +/turf/open/floor/material/meat/Initialize(mapload) + . = ..() + set_custom_materials(list(GET_MATERIAL_REF(/datum/material/meat) = MINERAL_MATERIAL_AMOUNT)) + +/turf/open/floor/material/meat/airless + initial_gas_mix = AIRLESS_ATMOS + baseturfs = /turf/open/misc/asteroid/airless /turf/open/floor/iron/tgmcemblem name = "TGMC Emblem" diff --git a/code/modules/antagonists/heretic/items/heretic_necks.dm b/code/modules/antagonists/heretic/items/heretic_necks.dm index 341a167f51a..c46a4bf214c 100644 --- a/code/modules/antagonists/heretic/items/heretic_necks.dm +++ b/code/modules/antagonists/heretic/items/heretic_necks.dm @@ -42,3 +42,11 @@ name = "Piercing Eldritch Medallion" desc = "A strange medallion. Peering through the crystalline surface, the light refracts into new and terrifying spectrums of color. You see yourself, reflected off cascading mirrors, warped into impossible shapes." heretic_only_trait = TRAIT_XRAY_VISION + +// Cosmetic-only version +/obj/item/clothing/neck/fake_heretic_amulet + name = "religious icon" + desc = "A strange medallion, which makes its wearer look like they're part of some cult." + icon = 'icons/obj/eldritch.dmi' + icon_state = "eye_medalion" + w_class = WEIGHT_CLASS_SMALL diff --git a/code/modules/mapfluff/ruins/spaceruin_code/meateor.dm b/code/modules/mapfluff/ruins/spaceruin_code/meateor.dm new file mode 100644 index 00000000000..3a3ec7569d8 --- /dev/null +++ b/code/modules/mapfluff/ruins/spaceruin_code/meateor.dm @@ -0,0 +1,131 @@ +/area/ruin/space/meateor + name = "\improper Organic Asteroid" + sound_environment = SOUND_AREA_SMALL_ENCLOSED + +/obj/item/paper/fluff/ruins/meateor/letter + name = "letter" + default_raw_text = {"We offer our sincerest congratulations, to be chosen to take this journey is an honour and privilege afforded to few. +
While you are walking in the footsteps of the divine, don't forget about the rest of us back at the farm! +
"} + +/// Tiger cultist corpse but with an exit wound +/obj/effect/mob_spawn/corpse/human/tigercultist/perforated + +/obj/effect/mob_spawn/corpse/human/tigercultist/perforated/special(mob/living/carbon/human/spawned_human) + . = ..() + var/datum/wound/pierce/critical/exit_hole = new() + exit_hole.apply_wound(spawned_human.get_bodypart(BODY_ZONE_CHEST)) + +/// A fun drink enjoyed by the tiger cooperative, might corrode your brain if you drink the whole bottle +/obj/item/reagent_containers/cup/glass/bottle/ritual_wine + name = "ritual wine" + desc = "A bottle filled with liquids of a dubious nature, often enjoyed by members of the Tiger Cooperative." + icon_state = "winebottle" + list_reagents = list( + /datum/reagent/drug/mushroomhallucinogen = 25, + /datum/reagent/consumable/ethanol/ritual_wine = 25, + /datum/reagent/medicine/changelinghaste = 20, // This metabolises very fast + /datum/reagent/drug/bath_salts = 5, + /datum/reagent/blood = 5, + /datum/reagent/toxin/leadacetate = 5, + /datum/reagent/medicine/omnizine = 5, + /datum/reagent/medicine/c2/penthrite = 5, + /datum/reagent/consumable/vinegar = 5, + ) + drink_type = NONE + age_restricted = FALSE + +/// Abstract holder object for shared behaviour +/obj/structure/meateor_fluff + icon = 'icons/mob/simple/meteor_heart.dmi' + anchored = TRUE + +/obj/structure/meateor_fluff/play_attack_sound(damage_amount, damage_type, damage_flag) + switch(damage_type) + if(BRUTE) + if(damage_amount) + playsound(loc, 'sound/effects/attackblob.ogg', vol = 50, vary = TRUE, pressure_affected = FALSE) + else + playsound(loc, 'sound/effects/meatslap.ogg', vol = 50, vary = TRUE, pressure_affected = FALSE) + if(BURN) + playsound(loc, 'sound/effects/wounds/sizzle1.ogg', vol = 100, vary = TRUE, pressure_affected = FALSE) + +/// A sort of loot box for organs, cut it open and find a prize +/obj/structure/meateor_fluff/flesh_pod + name = "flesh pod" + desc = "A quivering pod of living meat. Something is pulsing inside." + icon_state = "flesh_pod" + max_integrity = 120 + density = TRUE + /// Typepath of the organ to spawn when this is destroyed + var/stored_organ + /// Types of organ we can spawn + var/static/list/allowed_organs = list( + /obj/item/organ/internal/heart/gland/egg = 7, + /obj/item/organ/internal/heart/gland/plasma = 7, + /obj/item/organ/internal/alien/plasmavessel = 5, + /obj/item/organ/internal/heart/gland/chem = 5, + /obj/item/organ/internal/heart/gland/mindshock = 5, + /obj/item/organ/internal/heart/gland/spiderman = 5, + /obj/item/organ/internal/heart/gland/transform = 5, + /obj/item/organ/internal/heart/gland/slime = 4, + /obj/item/organ/internal/heart/gland/trauma = 4, + /obj/item/organ/internal/heart/carp = 3, + /obj/item/organ/internal/heart/rat = 3, + /obj/item/organ/internal/heart/gland/electric = 3, + /obj/item/organ/internal/monster_core/brimdust_sac = 3, + /obj/item/organ/internal/monster_core/regenerative_core = 3, + /obj/item/organ/internal/monster_core/rush_gland = 3, + /obj/item/organ/internal/tongue/carp = 3, + /obj/item/organ/internal/alien/acid = 2, + /obj/item/organ/internal/alien/resinspinner = 2, + /obj/item/organ/internal/eyes/night_vision/goliath = 2, + /obj/item/organ/internal/eyes/night_vision/rat = 2, + /obj/item/organ/internal/heart/gland/ventcrawling = 1, + ) + +/obj/structure/meateor_fluff/flesh_pod/Initialize(mapload) + . = ..() + stored_organ = pick_weight(allowed_organs) + +/obj/structure/meateor_fluff/flesh_pod/attackby(obj/item/attacking_item, mob/user, params) + if (attacking_item.sharpness & SHARP_EDGED) + cut_open(user) + return + return ..() + +/// Cut the pod open and destroy it +/obj/structure/meateor_fluff/flesh_pod/proc/cut_open(mob/user) + balloon_alert(user, "slicing...") + if (!do_after(user, 3 SECONDS, target = src)) + return + take_damage(max_integrity) + +/obj/structure/meateor_fluff/flesh_pod/atom_destruction(damage_flag) + new stored_organ(loc) + new /obj/effect/decal/cleanable/blood(loc) + new /obj/structure/meateor_fluff/flesh_pod_open(loc) + playsound(loc, 'sound/effects/wounds/blood3.ogg', vol = 50, vary = TRUE, pressure_affected = FALSE) + return ..() + +/obj/structure/meateor_fluff/flesh_pod_open + name = "flesh pod" + desc = "A pod of living meat, this one has been hollowed out." + icon_state = "flesh_pod_open" + max_integrity = 60 + +/obj/structure/meateor_fluff/flesh_pod_open/atom_destruction(damage_flag) + new /obj/effect/gibspawner/human(loc) + return ..() + +/// Decorative fluff egg object +/obj/structure/meateor_fluff/abandoned_headcrab_egg + name = "meaty eggs" + desc = "A mass of fleshy, egg-shaped nodes." + icon_state = "eggs" + max_integrity = 15 + +/obj/structure/meateor_fluff/abandoned_headcrab_egg/atom_destruction(damage_flag) + new /obj/effect/decal/cleanable/xenoblood(loc) + playsound(loc, 'sound/effects/gib_step.ogg', vol = 50, vary = TRUE, pressure_affected = FALSE) + return ..() diff --git a/code/modules/mob/living/basic/space_fauna/meteor_heart/chasing_spikes.dm b/code/modules/mob/living/basic/space_fauna/meteor_heart/chasing_spikes.dm new file mode 100644 index 00000000000..70309716e43 --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/meteor_heart/chasing_spikes.dm @@ -0,0 +1,112 @@ +/// An ability which makes spikes come out of the ground towards your target +/datum/action/cooldown/chasing_spikes + name = "impaling tendril" + desc = "Send a spiked subterranean tendril chasing after your target." + button_icon = 'icons/mob/simple/meteor_heart.dmi' + button_icon_state = "spike" + cooldown_time = 10 SECONDS + click_to_activate = TRUE + /// Lazy list of references to spike trails + var/list/active_chasers + +/datum/action/cooldown/chasing_spikes/Activate(atom/target) + . = ..() + playsound(owner, 'sound/magic/demon_attack1.ogg', vol = 100, vary = TRUE, pressure_affected = FALSE) + var/obj/effect/temp_visual/spike_chaser/chaser = new(get_turf(owner), target) + LAZYADD(active_chasers, WEAKREF(chaser)) + RegisterSignal(chaser, COMSIG_PARENT_QDELETING, PROC_REF(on_chaser_destroyed)) + +/// Remove a spike trail from our list of active trails +/datum/action/cooldown/chasing_spikes/proc/on_chaser_destroyed(atom/chaser) + SIGNAL_HANDLER + LAZYREMOVE(active_chasers, WEAKREF(chaser)) + +// Clean up after ourselves +/datum/action/cooldown/chasing_spikes/Remove(mob/removed_from) + QDEL_LIST(active_chasers) + return ..() + +/// An invisible effect which chases a target, spawning spikes every so often. +/obj/effect/temp_visual/spike_chaser + name = "spike chaser" + desc = "An invisible effect, how did you examine this?" + icon = 'icons/mob/silicon/cameramob.dmi' + icon_state = "marker" + duration = 15 SECONDS + invisibility = INVISIBILITY_ABSTRACT + /// Speed at which we chase target + var/move_speed = 3 + /// What are we chasing? + var/datum/weakref/target + /// Handles chasing the target + var/datum/move_loop/movement + +/obj/effect/temp_visual/spike_chaser/Initialize(mapload, atom/target) + . = ..() + if (!target) + return INITIALIZE_HINT_QDEL + + AddElement(/datum/element/floor_loving) + AddComponent(/datum/component/spawner, spawn_types = list(/obj/effect/temp_visual/emerging_ground_spike), spawn_time = 0.5 SECONDS) + src.target = WEAKREF(target) + movement = SSmove_manager.move_towards(src, chasing = target, delay = move_speed, home = TRUE, timeout = duration, flags = MOVEMENT_LOOP_START_FAST) + + RegisterSignal(target, COMSIG_PARENT_QDELETING, PROC_REF(on_target_invalid)) + if (isliving(target)) + RegisterSignal(target, COMSIG_LIVING_DEATH, PROC_REF(on_target_invalid)) + +/// Destroy ourselves if the target is no longer valid +/obj/effect/temp_visual/spike_chaser/proc/on_target_invalid() + SIGNAL_HANDLER + qdel(src) + +/obj/effect/temp_visual/spike_chaser/Destroy() + QDEL_NULL(movement) + return ..() + +/// A spike comes out of the ground, dealing damage after a short delay +/obj/effect/temp_visual/emerging_ground_spike + name = "bone spike" + desc = "A sharp spur of bone erupting from the ground!" + icon = 'icons/mob/simple/meteor_heart.dmi' + icon_state = "spike" + duration = 1 SECONDS + /// Time until we hurt people stood on us + var/harm_delay = 0.3 SECONDS + /// Amount by which to vary our position on spawn + var/position_variance = 8 + /// Damage to deal on impale + var/impale_damage = 15 + /// Typecache of types of mobs not to damage + var/list/damage_blacklist_typecache = list( + /mob/living/basic/meteor_heart, + ) + /// Weighted list of body zones to target while standing + var/static/list/standing_damage_zones = list( + BODY_ZONE_CHEST = 1, + BODY_ZONE_R_LEG = 3, + BODY_ZONE_L_LEG = 3, + ) + +/obj/effect/temp_visual/emerging_ground_spike/Initialize(mapload) + . = ..() + damage_blacklist_typecache = typecacheof(damage_blacklist_typecache) + pixel_x += rand(-position_variance, position_variance) + pixel_y += rand(-position_variance, position_variance) + addtimer(CALLBACK(src, PROC_REF(impale)), harm_delay, TIMER_DELETE_ME) + +/// Stab people who are stood on us after a delay in the shins +/obj/effect/temp_visual/emerging_ground_spike/proc/impale() + if (!isturf(loc)) + return + var/hit_someone = FALSE + for(var/mob/living/victim in loc) + if (is_type_in_typecache(victim, damage_blacklist_typecache)) + continue + hit_someone = TRUE + var/target_zone = victim.resting ? BODY_ZONE_CHEST : pick_weight(standing_damage_zones) + victim.apply_damage(impale_damage, damagetype = BRUTE, def_zone = target_zone, sharpness = SHARP_POINTY) + if (hit_someone) + playsound(src, 'sound/weapons/slice.ogg', vol = 50, vary = TRUE, pressure_affected = FALSE) + else + playsound(src, 'sound/misc/splort.ogg', vol = 25, vary = TRUE, pressure_affected = FALSE) diff --git a/code/modules/mob/living/basic/space_fauna/meteor_heart/meteor_eyeball.dm b/code/modules/mob/living/basic/space_fauna/meteor_heart/meteor_eyeball.dm new file mode 100644 index 00000000000..44775ea7217 --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/meteor_heart/meteor_eyeball.dm @@ -0,0 +1,36 @@ +#define EYEBALL_BLINK_INTERVAL_MIN 10 SECONDS +#define EYEBALL_BLINK_INTERVAL_MAX 30 SECONDS + +/// List of all the meteor eyeballs so we can gib them upon meteor death +GLOBAL_LIST_EMPTY(meteor_eyeballs) + +/// Basically just an organic floor light +/obj/structure/meateor_fluff/eyeball + name = "beady eye" + desc = "An eyeball growing out of the ground, gross." + icon_state = "eyeball" + max_integrity = 15 + layer = LOW_OBJ_LAYER + plane = FLOOR_PLANE + +/obj/structure/meateor_fluff/eyeball/Initialize(mapload) + . = ..() + GLOB.meteor_eyeballs += src + set_light(l_range = 4, l_color = COLOR_VERY_SOFT_YELLOW) + blink() + +/// Play a blinking animation and queue it again +/obj/structure/meateor_fluff/eyeball/proc/blink() + flick("eyeball_blink", src) + addtimer(CALLBACK(src, PROC_REF(blink)), rand(EYEBALL_BLINK_INTERVAL_MIN, EYEBALL_BLINK_INTERVAL_MAX), TIMER_DELETE_ME) + +/obj/structure/meateor_fluff/eyeball/atom_destruction(damage_flag) + new /obj/effect/gibspawner/generic(loc) + return ..() + +/obj/structure/meateor_fluff/eyeball/Destroy() + GLOB.meteor_eyeballs -= src + return ..() + +#undef EYEBALL_BLINK_INTERVAL_MIN +#undef EYEBALL_BLINK_INTERVAL_MAX diff --git a/code/modules/mob/living/basic/space_fauna/meteor_heart/meteor_heart.dm b/code/modules/mob/living/basic/space_fauna/meteor_heart/meteor_heart.dm new file mode 100644 index 00000000000..e56bbc4aa84 --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/meteor_heart/meteor_heart.dm @@ -0,0 +1,131 @@ +#define HEARTBEAT_NORMAL (1.2 SECONDS) +#define HEARTBEAT_FAST (0.6 SECONDS) +#define HEARTBEAT_FRANTIC (0.4 SECONDS) + +/mob/living/basic/meteor_heart + name = "meteor heart" + desc = "A pulsing lump of flesh and bone growing directly out of the ground." + icon = 'icons/mob/simple/meteor_heart.dmi' + icon_state = "heart" + icon_living = "heart" + mob_biotypes = MOB_ORGANIC + basic_mob_flags = DEL_ON_DEATH + mob_size = MOB_SIZE_HUGE + health = 600 // 15 PKA shots + maxHealth = 600 + pressure_resistance = 200 + response_help_continuous = "pets" + response_help_simple = "pet" + response_disarm_continuous = "gently pushes" + response_disarm_simple = "gently push" + faction = list() + ai_controller = /datum/ai_controller/basic_controller/meteor_heart + habitable_atmos = list("min_oxy" = 0, "max_oxy" = 0, "min_plas" = 0, "max_plas" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0) + minimum_survivable_temperature = 0 + maximum_survivable_temperature = 1500 + combat_mode = TRUE + move_resist = INFINITY // This mob IS the floor + /// Action which sends a line of spikes chasing a player + var/datum/action/cooldown/chasing_spikes/spikes + /// Action which summons areas the player can't stand in + var/datum/action/cooldown/spine_traps/traps + /// Looping heartbeat sound + var/datum/looping_sound/heartbeat/soundloop + +/mob/living/basic/meteor_heart/Initialize(mapload) + . = ..() + ADD_TRAIT(src, TRAIT_IMMOBILIZED, INNATE_TRAIT) + AddElement(/datum/element/death_drops, list(/obj/effect/temp_visual/meteor_heart_death)) + AddElement(/datum/element/relay_attackers) + + spikes = new(src) + spikes.Grant(src) + ai_controller.blackboard[BB_METEOR_HEART_GROUND_SPIKES] = WEAKREF(spikes) + + traps = new(src) + traps.Grant(src) + ai_controller.blackboard[BB_METEOR_HEART_SPINE_TRAPS] = WEAKREF(traps) + + ai_controller.set_ai_status(AI_STATUS_OFF) + + RegisterSignal(src, COMSIG_MOB_ABILITY_FINISHED, PROC_REF(used_ability)) + RegisterSignal(src, COMSIG_ATOM_WAS_ATTACKED, PROC_REF(aggro)) + + for (var/obj/structure/meateor_fluff/body_part in view(5, src)) + RegisterSignal(body_part, COMSIG_ATOM_DESTRUCTION, PROC_REF(aggro)) + + soundloop = new(src, start_immediately = FALSE) + soundloop.mid_length = HEARTBEAT_NORMAL + soundloop.pressure_affected = FALSE + soundloop.start() + +/// Called when we get mad at something, either for attacking us or attacking the nearby area +/mob/living/basic/meteor_heart/proc/aggro() + if (ai_controller.ai_status == AI_STATUS_ON) + return + ai_controller.reset_ai_status() + if (!ai_controller.ai_status == AI_STATUS_ON) + return + icon_state = "heart_aggro" + soundloop.set_mid_length(HEARTBEAT_FAST) + +/// Called when we stop being mad +/mob/living/basic/meteor_heart/proc/deaggro() + ai_controller.set_ai_status(AI_STATUS_OFF) + icon_state = "heart" + soundloop.set_mid_length(HEARTBEAT_NORMAL) + +/// Animate when using certain abilities +/mob/living/basic/meteor_heart/proc/used_ability(mob/living/owner, datum/action/cooldown/ability) + SIGNAL_HANDLER + if (ability != spikes) + return + Shake(1, 0, 1.5 SECONDS) + +/mob/living/basic/meteor_heart/Destroy() + QDEL_NULL(spikes) + QDEL_NULL(traps) + QDEL_NULL(soundloop) + return ..() + +/// Dramatic death animation for the meteor heart mob +/obj/effect/temp_visual/meteor_heart_death + name = "meteor heart" + icon = 'icons/mob/simple/meteor_heart.dmi' + icon_state = "heart_dying" + desc = "You've killed this innocent asteroid, I hope you feel happy." + duration = 3 SECONDS + /// Looping heartbeat sound + var/datum/looping_sound/heartbeat/soundloop + +/obj/effect/temp_visual/meteor_heart_death/Initialize(mapload) + . = ..() + playsound(src, 'sound/magic/demon_dies.ogg', vol = 100, vary = TRUE, pressure_affected = FALSE) + Shake(2, 0, 3 SECONDS) + addtimer(CALLBACK(src, PROC_REF(gib)), duration - 1, TIMER_DELETE_ME) + soundloop = new(src, start_immediately = FALSE) + soundloop.mid_length = HEARTBEAT_FRANTIC + soundloop.pressure_affected = FALSE + soundloop.start() + +/obj/effect/temp_visual/meteor_heart_death/Destroy() + QDEL_NULL(soundloop) + return ..() + +/// Make this place a mess +/obj/effect/temp_visual/meteor_heart_death/proc/gib() + playsound(loc, 'sound/effects/attackblob.ogg', vol = 100, vary = TRUE, pressure_affected = FALSE) + var/turf/my_turf = get_turf(src) + new /obj/effect/gibspawner/human(my_turf) + for (var/obj/structure/eyeball as anything in GLOB.meteor_eyeballs) + if (eyeball.z != src.z) + continue + addtimer(CALLBACK(eyeball, TYPE_PROC_REF(/atom/, take_damage), eyeball.max_integrity), rand(0.5 SECONDS, 2 SECONDS)) // pop! + for (var/mob/murderer in range(10, src)) + if (!murderer.client || isspaceturf(get_turf(murderer))) + continue + shake_camera(murderer, duration = 2 SECONDS, strength = 2) + +#undef HEARTBEAT_NORMAL +#undef HEARTBEAT_FAST +#undef HEARTBEAT_FRANTIC diff --git a/code/modules/mob/living/basic/space_fauna/meteor_heart/meteor_heart_ai.dm b/code/modules/mob/living/basic/space_fauna/meteor_heart/meteor_heart_ai.dm new file mode 100644 index 00000000000..7684011bd66 --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/meteor_heart/meteor_heart_ai.dm @@ -0,0 +1,32 @@ +/// A spellcasting AI which does not move +/datum/ai_controller/basic_controller/meteor_heart + blackboard = list( + BB_TARGETTING_DATUM = new /datum/targetting_datum/basic(), + BB_TARGETLESS_TIME = 0, + ) + + planning_subtrees = list( + /datum/ai_planning_subtree/simple_find_target, + /datum/ai_planning_subtree/targeted_mob_ability/ground_spikes, + /datum/ai_planning_subtree/use_mob_ability/spine_traps, + /datum/ai_planning_subtree/sleep_with_no_target/meteor_heart, + ) + +/datum/ai_planning_subtree/targeted_mob_ability/ground_spikes + ability_key = BB_METEOR_HEART_GROUND_SPIKES + finish_planning = FALSE + +/datum/ai_planning_subtree/use_mob_ability/spine_traps + ability_key = BB_METEOR_HEART_SPINE_TRAPS + +/// After enough time with no target, deaggro and change animation state +/datum/ai_planning_subtree/sleep_with_no_target/meteor_heart + sleep_behaviour = /datum/ai_behavior/sleep_after_targetless_time/meteor_heart + +/datum/ai_behavior/sleep_after_targetless_time/meteor_heart + +/datum/ai_behavior/sleep_after_targetless_time/meteor_heart/enter_sleep(datum/ai_controller/controller) + var/mob/living/basic/meteor_heart/heart = controller.pawn + if (!istype(heart)) + return ..() + heart.deaggro() diff --git a/code/modules/mob/living/basic/space_fauna/meteor_heart/spine_traps.dm b/code/modules/mob/living/basic/space_fauna/meteor_heart/spine_traps.dm new file mode 100644 index 00000000000..a2a3b52b835 --- /dev/null +++ b/code/modules/mob/living/basic/space_fauna/meteor_heart/spine_traps.dm @@ -0,0 +1,97 @@ +/// Marks several areas with thrusting spines which damage and slow people +/datum/action/cooldown/spine_traps + name = "thrusting spines" + desc = "Mark several nearby areas with thrusting spines, which will spring up when disturbed." + button_icon = 'icons/mob/simple/meteor_heart.dmi' + button_icon_state = "spikes_stabbing" + cooldown_time = 15 SECONDS + /// Create zones at most this far away + var/range = 3 + /// Don't create zones within this radius + var/min_range = 2 + /// Number of zones to place + var/zones_to_create = 3 + +/datum/action/cooldown/spine_traps/Activate(atom/target) + . = ..() + + playsound(owner, 'sound/magic/demon_consume.ogg', vol = 100, falloff_exponent = 2, vary = TRUE, pressure_affected = FALSE) + var/list/valid_turfs = list() + var/turf/our_turf = get_turf(owner) + for (var/turf/zone_turf in orange(range, our_turf)) + if (!is_valid_turf(zone_turf) || get_dist(zone_turf, our_turf) < min_range) + continue + valid_turfs += zone_turf + + var/created = 0 + while(length(valid_turfs) && created < zones_to_create) + var/turf/place_turf = pick_n_take(valid_turfs) + var/list/covered_turfs = place_zone(place_turf) + valid_turfs -= covered_turfs + created++ + +/// Returns true if we can place a trap at the specified location +/datum/action/cooldown/spine_traps/proc/is_valid_turf(turf/target_turf) + return !target_turf.is_blocked_turf(exclude_mobs = TRUE) && !isspaceturf(target_turf) && !isopenspaceturf(target_turf) + +/// Places a 3x3 area of spike traps around a central provided point, returns the list of now occupied turfs +/datum/action/cooldown/spine_traps/proc/place_zone(turf/target_turf) + var/list/used_turfs = list() + for (var/turf/zone_turf in range(1, target_turf)) + if (!is_valid_turf(zone_turf)) + continue + new /obj/effect/temp_visual/thrusting_spines(zone_turf) + used_turfs += zone_turf + return used_turfs + +/obj/effect/temp_visual/thrusting_spines + icon = 'icons/mob/simple/meteor_heart.dmi' + icon_state = "spikes_idle" + desc = "Sharp spines lying in wait in the ground, you probably don't want to walk on those." + duration = 10 SECONDS + /// If this will trigger a trap when entered + var/active = FALSE + /// Damage to deal on activation + var/impale_damage = 10 + /// Time between activations + COOLDOWN_DECLARE(thrust_delay) + /// Weighted list of body zones to target while standing + var/static/list/standing_damage_zones = list( + BODY_ZONE_CHEST = 1, + BODY_ZONE_R_LEG = 3, + BODY_ZONE_L_LEG = 3, + ) + +/obj/effect/temp_visual/thrusting_spines/Initialize(mapload) + . = ..() + flick("spikes_emerge", src) + addtimer(CALLBACK(src, PROC_REF(ready)), 1 SECONDS, TIMER_DELETE_ME) + addtimer(CALLBACK(src, PROC_REF(retract)), duration - (0.5 SECONDS), TIMER_DELETE_ME) + + var/static/list/loc_connections = list( + COMSIG_ATOM_ENTERED = PROC_REF(on_entered), + ) + AddElement(/datum/element/connect_loc, loc_connections) + +/// Called when we're ready to start impaling people +/obj/effect/temp_visual/thrusting_spines/proc/ready() + active = TRUE + +/// Called when it is time to stop impaling people +/obj/effect/temp_visual/thrusting_spines/proc/retract() + active = FALSE + icon_state = "spikes_submerge" + +/// Called when something enters our turf, if it is a non-flying mob then give it a stab +/obj/effect/temp_visual/thrusting_spines/proc/on_entered(datum/source, atom/movable/arrived) + if (!active || !isliving(arrived) || (arrived.movement_type & (FLYING | FLOATING))) + return + if (!COOLDOWN_FINISHED(src, thrust_delay)) + return + + COOLDOWN_START(src, thrust_delay, 0.7 SECONDS) + playsound(src, 'sound/weapons/pierce.ogg', vol = 50, vary = TRUE, pressure_affected = FALSE) + var/mob/living/victim = arrived + flick("spikes_stabbing", src) + var/target_zone = victim.resting ? BODY_ZONE_CHEST : pick_weight(standing_damage_zones) + victim.apply_damage(impale_damage, damagetype = BRUTE, def_zone = target_zone, sharpness = SHARP_POINTY) diff --git a/code/modules/mob/living/simple_animal/hostile/headcrab.dm b/code/modules/mob/living/simple_animal/hostile/headcrab.dm index 7b38c352088..e0d761e7d25 100644 --- a/code/modules/mob/living/simple_animal/hostile/headcrab.dm +++ b/code/modules/mob/living/simple_animal/hostile/headcrab.dm @@ -21,8 +21,10 @@ obj_damage = 0 environment_smash = ENVIRONMENT_SMASH_NONE speak_emote = list("squeaks") + /// The mind to transfer to our egg when it hatches var/datum/mind/origin - var/egg_lain = 0 + /// Set to true once we've implanted our egg + var/egg_lain = FALSE /mob/living/simple_animal/hostile/headcrab/Initialize(mapload) . = ..() @@ -39,20 +41,21 @@ I.forceMove(egg) visible_message(span_warning("[src] plants something in [victim]'s flesh!"), \ span_danger("We inject our egg into [victim]'s body!")) - egg_lain = 1 + egg_lain = TRUE /mob/living/simple_animal/hostile/headcrab/AttackingTarget() . = ..() - if(. && !egg_lain && iscarbon(target) && !ismonkey(target)) - // Changeling egg can survive in aliens! - var/mob/living/carbon/C = target - if(C.stat == DEAD) - if(HAS_TRAIT(C, TRAIT_XENO_HOST)) - to_chat(src, span_userdanger("A foreign presence repels us from this body. Perhaps we should try to infest another?")) - return - Infect(target) - to_chat(src, span_userdanger("With our egg laid, our death approaches rapidly...")) - addtimer(CALLBACK(src, PROC_REF(death)), 100) + if (!. || egg_lain || !iscarbon(target) || ismonkey(target)) + return + var/mob/living/carbon/victim = target + if(victim.stat != DEAD) + return + if(HAS_TRAIT(victim, TRAIT_XENO_HOST)) + target.balloon_alert(src, "already pregnant!") // Maybe the worst balloon alert in the codebase + return + Infect(target) + to_chat(src, span_userdanger("With our egg laid, our death approaches rapidly...")) + addtimer(CALLBACK(src, PROC_REF(death)), 10 SECONDS) /obj/item/organ/internal/body_egg/changeling_egg name = "changeling egg" 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 beb3698ea8e..1bd981e2680 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 @@ -284,9 +284,9 @@ .=..() AddComponent(\ /datum/component/spawner,\ - mob_types = list(/mob/living/simple_animal/hostile/asteroid/hivelord/legion),\ + spawn_types = list(/mob/living/simple_animal/hostile/asteroid/hivelord/legion),\ spawn_time = 20 SECONDS,\ - max_mobs = 3,\ + max_spawned = 3,\ spawn_text = "peels itself off from",\ faction = faction,\ ) diff --git a/code/modules/mob_spawn/corpses/mob_corpses.dm b/code/modules/mob_spawn/corpses/mob_corpses.dm index 76d31e80d89..2e4901a2793 100644 --- a/code/modules/mob_spawn/corpses/mob_corpses.dm +++ b/code/modules/mob_spawn/corpses/mob_corpses.dm @@ -86,6 +86,19 @@ id = /obj/item/card/id/advanced/chameleon id_trim = /datum/id_trim/chameleon/operative +/obj/effect/mob_spawn/corpse/human/tigercultist + name = "Tiger Cooperative Cultist" + outfit = /datum/outfit/tigercultcorpse + +/datum/outfit/tigercultcorpse + name = "Tiger Cooperative Corpse" + uniform = /obj/item/clothing/under/rank/civilian/chaplain + suit = /obj/item/clothing/suit/hooded/chaplain_hoodie + shoes = /obj/item/clothing/shoes/laceup + neck = /obj/item/clothing/neck/fake_heretic_amulet + head = /obj/item/clothing/head/hooded/chaplain_hood + back = /obj/item/storage/backpack/cultpack + /obj/effect/mob_spawn/corpse/human/pirate name = "Pirate" skin_tone = "caucasian1" //all pirates are white because it's easier that way diff --git a/code/modules/mob_spawn/corpses/nonhuman_corpses.dm b/code/modules/mob_spawn/corpses/nonhuman_corpses.dm index bf70f7f33b7..6796bb011f8 100644 --- a/code/modules/mob_spawn/corpses/nonhuman_corpses.dm +++ b/code/modules/mob_spawn/corpses/nonhuman_corpses.dm @@ -43,3 +43,13 @@ mob_type = /mob/living/simple_animal/hostile/asteroid/goliath/beast icon = 'icons/mob/simple/lavaland/lavaland_monsters.dmi' icon_state = "goliath_dead_helper" + +/// Dead headcrab for changeling-themed ruins +/obj/effect/mob_spawn/corpse/headcrab + mob_type = /mob/living/simple_animal/hostile/headcrab + icon = 'icons/mob/simple/animal.dmi' + icon_state = "headcrab_dead" + +/obj/effect/mob_spawn/corpse/headcrab/special(mob/living/simple_animal/hostile/headcrab/crab) + . = ..() + crab.egg_lain = TRUE // Prevents using mad science to become a changeling diff --git a/config/spaceruinblacklist.txt b/config/spaceruinblacklist.txt index 35519bb5af1..17b380af519 100644 --- a/config/spaceruinblacklist.txt +++ b/config/spaceruinblacklist.txt @@ -38,6 +38,7 @@ #_maps/RandomRuins/SpaceRuins/hilbertshoteltestingsite.dmm #_maps/RandomRuins/SpaceRuins/intactemptyship.dmm #_maps/RandomRuins/SpaceRuins/listeningstation.dmm +#_maps/RandomRuins/SpaceRuins/meateor.dmm #_maps/RandomRuins/SpaceRuins/mechtransport.dmm #_maps/RandomRuins/SpaceRuins/mrow_thats_right #_maps/RandomRuins/SpaceRuins/old_infiltrator.dmm diff --git a/icons/mob/simple/meteor_heart.dmi b/icons/mob/simple/meteor_heart.dmi new file mode 100644 index 0000000000000000000000000000000000000000..2636a8293b8e01d3281f46788a6889a9154beadd GIT binary patch literal 2757 zcmV;$3Oeq8 zQ*QtO00DGTPE!Ct=GbNc009PiR9JLGWpiV4X>fFDZ*Bkpc$|%s&1wT75XaB@6p~z* z(v1n^(o0DwIV7(jiW8L>UDPJ*+t=New%N}x2s6XqpEIK_$@~7}a3Z_o0pB1*;~jY; z>z7xI?nWk^1G>UUgHbwt2Ql#k*HkkMfqc! z&i2T1EQE1t7_@l=t(w(Cz!icTsiXsF4I}G))e3vne2NL!x(Ay;8EHo_|Fjx-NW&`n zci6U>Aw~i2#>oKy<4OfTi=plGCNQ!dy$ITAeHe4L5H+j3oNfG9$$SNFRt`$Vc~K35 zTl=hz32WTt>38?o@{;UNyY}0B0VwwAXnVW0vH$=HD@jB_RCt{2T$?Vr+phg009Kv56CW@ud?H-(!EtOsD|dINq6w-*TF0|J|;FcToftiaj`u~Ya2f%W%6 ziU|P(a#Q$(MF8{2~MAyd0r1~CgGi~BKZV%`5wf#SY>lsf^byCD~1hWM-v|WzL!X}!dFM)ZlDAX6k<_|B1y{EW> zK=D2T>GDf5*P=rBye{Mw*u&4EHIuzX@U2v=V*3Jtn+k$^Uh8S#3>B? zvyq>eDL#co{*Yka*b6nnx%ON5Ap-Kx#k?ESd#rg{;j@jsATZe<=o%{W&&TYQwx99T z2*%Ri;taEBF@H#r4v_zsfuQwubaZrdbaZrdbTGlK!N2N#OhRjcmlr@A^L&BG0Q^;a zYrabF=m7MCSzrKt>9KiqUGX~g!>9Pmfy!GA_;bOm@HXcm*d^*MeJ0NzVPJr)&mn#| zkEc3d@d%DEbq|RQJX618^2FLXp!fU5UB}xW19`Cs=IKABMb9UUDVFOI9PMBATq z?tX=qQW?l1kKS)(pAB)p+s#aBrN{^ZUkFgC$hWb727DKenv6iH#FMnN3N0^PfK+@O z`#fR6gFQqJ!!+p-Le4t@p5FxMc^UX^d|dl{fhISshe6!+FpSe^I@v(xycH05)4<0- z@K)d^KCb<96dDhM{qIt=wjOK@vdE8h-i0USdG?>uLO&IECAA)`1K+&{YhEEsQvsz@htoM_0a;g@O^J)P@Ey_03z>ZNR6l7XWQQ| zko?#*X<>gPIZZJ;q+RRt296)6j9v!d-%r`)+MH>BH?|wGo#3WhwQ%d3jnP789?C(SEm}h48-8lByW$lm7(HOhX@_NBfK9F0^Pd>YB;J%qw zC)qzLFneVG_0{AebKN&h({dfpd7bh3`(xzwIIVX9u6?dRna}NHKMTOE4Fo?coOwNN zUgz7#`l|IindOy~F~D(N$fWkWa|~d!6H6V_Q_F4qeESg9`ki#0=PkPVJ&wx&Wa9Ob z&lM2y%d_n7QMi4SZs}xNysY)ue~SR7%zJ8(37lf@k3@WY`%K7S&7T1?`ze6%H|_ap z6`vadyC`ZpKEC}E6k7j!Twva-W#)e|Lh~FP6Th8dC)no-414p+{d``QTRw{~H7L=1 z`#VwG?Un1`%tJhkl=zi@rPm{rA7%LVpBA`Q8Xf4|eHLNj^9J@}A{Uq`4E*-}gZB68 zE0wS8mzs=ulrTFWc|*&L_YD@qlMZWK`%ep8*Y5}i(%&4ZjL%v~N6Vboa$&(WU%r3b z{!SF@cS`$5{CsaE_P0?CJAD8K)-eYQZaQf3LCn@zLiTAqznPpS1Eujctc zcCP0C2;RPahtRq{x1)Y1!2uHSHMFkJVf(kRQI~o3@p1SgsC9jAhkf@+tHrLfj)^3?CY)TbI87%f$4*u7$P@-QQ)y5Y+9cK_QMjI zlAjQ$yufk}*U`E@SGRx5GnCH3(F?5Pa0#vJbI5+srxyY@LliP}oQgSIM(g?5M8&a7Vl-#aCt?P4j`ysfbaJvGz4siRtJ+6_w?;|75Er4sf zb$zaGKMax9I_;6vKIn$fY(yWD&QLE*Dt{7Q3mlu)=j!%@z;v%Wk^|(2a4hn+7xie4 zxa483b$zaGKPpTT3x{px(zC$3JhMY+eQa}d6JUPSXkGuC6~=pOLSOhEEb<}GOd=Sa zF2zc2Lx9twPc`ke~d zqzc6_#8=VlcO4xa9UUDV9WRfE!PPM|#N+Y#`StaCB|eNgi_fCM$K%=|X8M18eSg;k zJ|2cMxJn||zZt;qub-cyLX*Ibw=Otw9tGRK&Cs8ppU1Z`QQ`kB@vXu3?>nd_@Vo9H zrq0o60^W!Ip?zb(rRZl_V3mJ-`bFSi3ZeIr{~aFUDq46S^ASh=*WX9}_c-|S>lBAC z2Jj&Nxc`0VAL9WAfXUButdaj6K(8}-xKVTccXV`gbaZrd{3iSdV(zIV9l@7W00000 LNkvXXu0mjfPbOcg literal 0 HcmV?d00001 diff --git a/tgstation.dme b/tgstation.dme index d460ff00cbd..9d5b480a75a 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -763,6 +763,7 @@ #include "code\datums\ai\basic_mobs\basic_subtrees\simple_attack_target.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\simple_find_nearest_target_to_flee.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\simple_find_target.dm" +#include "code\datums\ai\basic_mobs\basic_subtrees\sleep_with_no_target.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\speech_subtree.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\target_retaliate.dm" #include "code\datums\ai\basic_mobs\basic_subtrees\targeted_mob_ability.dm" @@ -1164,6 +1165,7 @@ #include "code\datums\elements\eyestab.dm" #include "code\datums\elements\falling_hazard.dm" #include "code\datums\elements\firestacker.dm" +#include "code\datums\elements\floorloving.dm" #include "code\datums\elements\footstep.dm" #include "code\datums\elements\forced_gravity.dm" #include "code\datums\elements\frozen.dm" @@ -3695,6 +3697,7 @@ #include "code\modules\mapfluff\ruins\spaceruin_code\hellfactory.dm" #include "code\modules\mapfluff\ruins\spaceruin_code\hilbertshotel.dm" #include "code\modules\mapfluff\ruins\spaceruin_code\listeningstation.dm" +#include "code\modules\mapfluff\ruins\spaceruin_code\meateor.dm" #include "code\modules\mapfluff\ruins\spaceruin_code\oldstation.dm" #include "code\modules\mapfluff\ruins\spaceruin_code\originalcontent.dm" #include "code\modules\mapfluff\ruins\spaceruin_code\spacehotel.dm" @@ -3849,6 +3852,11 @@ #include "code\modules\mob\living\basic\space_fauna\giant_spider\spider_abilities\lay_eggs.dm" #include "code\modules\mob\living\basic\space_fauna\giant_spider\spider_abilities\web.dm" #include "code\modules\mob\living\basic\space_fauna\giant_spider\spider_abilities\wrap.dm" +#include "code\modules\mob\living\basic\space_fauna\meteor_heart\chasing_spikes.dm" +#include "code\modules\mob\living\basic\space_fauna\meteor_heart\meteor_eyeball.dm" +#include "code\modules\mob\living\basic\space_fauna\meteor_heart\meteor_heart.dm" +#include "code\modules\mob\living\basic\space_fauna\meteor_heart\meteor_heart_ai.dm" +#include "code\modules\mob\living\basic\space_fauna\meteor_heart\spine_traps.dm" #include "code\modules\mob\living\basic\space_fauna\netherworld\blankbody.dm" #include "code\modules\mob\living\basic\space_fauna\netherworld\creature.dm" #include "code\modules\mob\living\basic\space_fauna\netherworld\migo.dm"