March into Mapness: Meateor (#74070)

This commit is contained in:
Jacquerel
2023-03-31 19:40:29 -07:00
committed by GitHub
parent 17e23fed64
commit 966b8e5fd8
28 changed files with 1785 additions and 96 deletions
+981
View File
@@ -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
"}
+9
View File
@@ -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"
@@ -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)
@@ -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
+42 -32
View File
@@ -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))
+21
View File
@@ -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!")
+16 -1
View File
@@ -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)
+10 -1
View File
@@ -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.
*
@@ -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
+6
View File
@@ -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"
+2 -2
View File
@@ -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()
+35 -44
View File
@@ -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)
+13
View File
@@ -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
+13 -1
View File
@@ -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"
@@ -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
@@ -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.
<br> While you are walking in the footsteps of the divine, don't forget about the rest of us back at the farm!
<br> <This letter has been signed by 15 people.>"}
/// 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 ..()
@@ -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)
@@ -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
@@ -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
@@ -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()
@@ -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)
@@ -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"
@@ -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,\
)
@@ -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
@@ -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
+1
View File
@@ -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
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

+8
View File
@@ -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"