diff --git a/code/__DEFINES/components.dm b/code/__DEFINES/components.dm
index 1e2ba19d..e2ef92ed 100644
--- a/code/__DEFINES/components.dm
+++ b/code/__DEFINES/components.dm
@@ -1,104 +1,104 @@
-#define SEND_SIGNAL(target, sigtype, arguments...) ( !target.comp_lookup || !target.comp_lookup[sigtype] ? NONE : target._SendSignal(sigtype, list(target, ##arguments)) )
-
-#define SEND_GLOBAL_SIGNAL(sigtype, arguments...) ( SEND_SIGNAL(SSdcs, sigtype, ##arguments) )
-
-#define COMPONENT_INCOMPATIBLE 1
-#define COMPONENT_NOTRANSFER 2
-
-// How multiple components of the exact same type are handled in the same datum
-
-#define COMPONENT_DUPE_HIGHLANDER 0 //old component is deleted (default)
-#define COMPONENT_DUPE_ALLOWED 1 //duplicates allowed
-#define COMPONENT_DUPE_UNIQUE 2 //new component is deleted
-#define COMPONENT_DUPE_UNIQUE_PASSARGS 4 //old component is given the initialization args of the new
-
-// All signals. Format:
-// When the signal is called: (signal arguments)
-// All signals send the source datum of the signal as the first argument
-
-// global signals
-// These are signals which can be listened to by any component on any parent
-// start global signals with "!", this used to be necessary but now it's just a formatting choice
-//////////////////////////////////////////////////////////////////
-
-// /datum signals
-// /atom signals
- //Positions for overrides list
- //End positions
-#define COMSIG_ATOM_RATVAR_ACT "atom_ratvar_act" //from base of atom/ratvar_act(): ()
-/////////////////
-
-// /area signals
-
-// /turf signals
-
-// /atom/movable signals
-
-// /mob/living signals
-
-// /mob/living/carbon signals
-
-// /obj signals
-#define COMSIG_OBJ_BREAK "obj_break" //from base of /obj/obj_break(): (damage_flag)
-
-// /obj/item signals
-
-// /obj/item/clothing signals
-
-// /obj/item/implant signals
- //This uses all return values of COMSIG_IMPLANT_OTHER
-
-// /obj/item/pda signals
-
-// /obj/item/radio signals
-
-// /obj/item/pen signals
-
-// /mob/living/carbon/human signals
-
-// /datum/species signals
-
-/*******Component Specific Signals*******/
-//Janitor
-
-//Food
-
-//Mood
-#define COMSIG_INCREASE_SANITY "decrease_sanity" //Called when you want to increase sanity from anywhere in the code.
-#define COMSIG_DECREASE_SANITY "increase_sanity" //Same as above but to decrease sanity instead.
-
-//NTnet
-
-//Nanites
-
-// /datum/component/storage signals
-
-// /datum/action signals
-
-/*******Non-Signal Component Related Defines*******/
-
-//Redirection component init flags
-#define REDIRECT_TRANSFER_WITH_TURF 1
-
-//Arch
-#define ARCH_PROB "probability" //Probability for each item
-#define ARCH_MAXDROP "max_drop_amount" //each item's max drop amount
-
-//Ouch my toes!
-#define CALTROP_BYPASS_SHOES 1
-#define CALTROP_IGNORE_WALKERS 2
-
-#define ELEMENT_INCOMPATIBLE 1 // Return value to cancel attaching
-
-// /datum/element flags
-/// Causes the detach proc to be called when the host object is being deleted
-#define ELEMENT_DETACH (1 << 0)
-/**
- * Only elements created with the same arguments given after `id_arg_index` share an element instance
- * The arguments are the same when the text and number values are the same and all other values have the same ref
- */
-#define ELEMENT_BESPOKE (1 << 1)
-#define COMSIG_MOVABLE_UPDATE_GLIDE_SIZE "movable_glide_size" //Called when the movable's glide size is updated: (new_glide_size)
-
-#define COMSIG_LIVING_FULLY_HEAL "living_fully_healed" //from base of /mob/living/fully_heal(): (admin_revive)
-
+#define SEND_SIGNAL(target, sigtype, arguments...) ( !target.comp_lookup || !target.comp_lookup[sigtype] ? NONE : target._SendSignal(sigtype, list(target, ##arguments)) )
+
+#define SEND_GLOBAL_SIGNAL(sigtype, arguments...) ( SEND_SIGNAL(SSdcs, sigtype, ##arguments) )
+
+#define COMPONENT_INCOMPATIBLE 1
+#define COMPONENT_NOTRANSFER 2
+
+// How multiple components of the exact same type are handled in the same datum
+
+#define COMPONENT_DUPE_HIGHLANDER 0 //old component is deleted (default)
+#define COMPONENT_DUPE_ALLOWED 1 //duplicates allowed
+#define COMPONENT_DUPE_UNIQUE 2 //new component is deleted
+#define COMPONENT_DUPE_UNIQUE_PASSARGS 4 //old component is given the initialization args of the new
+
+// All signals. Format:
+// When the signal is called: (signal arguments)
+// All signals send the source datum of the signal as the first argument
+
+// global signals
+// These are signals which can be listened to by any component on any parent
+// start global signals with "!", this used to be necessary but now it's just a formatting choice
+//////////////////////////////////////////////////////////////////
+
+// /datum signals
+// /atom signals
+ //Positions for overrides list
+ //End positions
+#define COMSIG_ATOM_RATVAR_ACT "atom_ratvar_act" //from base of atom/ratvar_act(): ()
+/////////////////
+
+// /area signals
+
+// /turf signals
+
+// /atom/movable signals
+
+// /mob/living signals
+
+// /mob/living/carbon signals
+
+// /obj signals
+#define COMSIG_OBJ_BREAK "obj_break" //from base of /obj/obj_break(): (damage_flag)
+
+// /obj/item signals
+
+// /obj/item/clothing signals
+
+// /obj/item/implant signals
+ //This uses all return values of COMSIG_IMPLANT_OTHER
+
+// /obj/item/pda signals
+
+// /obj/item/radio signals
+
+// /obj/item/pen signals
+
+// /mob/living/carbon/human signals
+
+// /datum/species signals
+
+/*******Component Specific Signals*******/
+//Janitor
+
+//Food
+
+//Mood
+#define COMSIG_INCREASE_SANITY "decrease_sanity" //Called when you want to increase sanity from anywhere in the code.
+#define COMSIG_DECREASE_SANITY "increase_sanity" //Same as above but to decrease sanity instead.
+
+//NTnet
+
+//Nanites
+
+// /datum/component/storage signals
+
+// /datum/action signals
+
+/*******Non-Signal Component Related Defines*******/
+
+//Redirection component init flags
+#define REDIRECT_TRANSFER_WITH_TURF 1
+
+//Arch
+#define ARCH_PROB "probability" //Probability for each item
+#define ARCH_MAXDROP "max_drop_amount" //each item's max drop amount
+
+//Ouch my toes!
+#define CALTROP_BYPASS_SHOES 1
+#define CALTROP_IGNORE_WALKERS 2
+
+#define ELEMENT_INCOMPATIBLE 1 // Return value to cancel attaching
+
+// /datum/element flags
+/// Causes the detach proc to be called when the host object is being deleted
+#define ELEMENT_DETACH (1 << 0)
+/**
+ * Only elements created with the same arguments given after `id_arg_index` share an element instance
+ * The arguments are the same when the text and number values are the same and all other values have the same ref
+ */
+#define ELEMENT_BESPOKE (1 << 1)
+#define COMSIG_MOVABLE_UPDATE_GLIDE_SIZE "movable_glide_size" //Called when the movable's glide size is updated: (new_glide_size)
+
+#define COMSIG_LIVING_FULLY_HEAL "living_fully_healed" //from base of /mob/living/fully_heal(): (admin_revive)
+
diff --git a/code/__DEFINES/status_effects.dm b/code/__DEFINES/status_effects.dm
index 664ebf8b..1ab4fd3f 100644
--- a/code/__DEFINES/status_effects.dm
+++ b/code/__DEFINES/status_effects.dm
@@ -7,6 +7,8 @@
#define STATUS_EFFECT_REPLACE 2 //if it allows only one, but new instances replace
+#define STATUS_EFFECT_REFRESH 3 // if it only allows one, and new instances just instead refresh the timer
+
///////////
// BUFFS //
///////////
@@ -75,6 +77,7 @@
#define STATUS_EFFECT_ICHORIAL_STAIN /datum/status_effect/ichorial_stain //Prevents a servant from being revived by vitality matrices for one minute.
#define STATUS_EFFECT_STASIS /datum/status_effect/incapacitating/stasis //Halts biological functions like bleeding, chemical processing, blood regeneration, walking, etc
+#define STATUS_EFFECT_SPASMS /datum/status_effect/spasms //causes random muscle spasms
#define STATUS_EFFECT_NO_COMBAT_MODE /datum/status_effect/no_combat_mode //Wont allow combat mode and will disable it
#define STATUS_EFFECT_MESMERIZE /datum/status_effect/no_combat_mode/mesmerize //Just reskinned no_combat_mode
diff --git a/code/__DEFINES/traits.dm b/code/__DEFINES/traits.dm
index 91d7f5a8..3bfb2da0 100644
--- a/code/__DEFINES/traits.dm
+++ b/code/__DEFINES/traits.dm
@@ -112,6 +112,7 @@
#define TRAIT_NOHARDCRIT "nohardcrit"
#define TRAIT_NOSOFTCRIT "nosoftcrit"
#define TRAIT_MINDSHIELD "mindshield"
+#define TRAIT_SIXTHSENSE "sixthsense"
#define TRAIT_DISSECTED "dissected"
#define TRAIT_FEARLESS "fearless"
#define TRAIT_UNSTABLE "unstable"
@@ -121,6 +122,7 @@
#define TRAIT_PARALYSIS_R_LEG "para-r-leg"
#define TRAIT_DISK_VERIFIER "disk-verifier"
#define TRAIT_UNINTELLIGIBLE_SPEECH "unintelligible-speech"
+#define TRAIT_SOOTHED_THROAT "soothed-throat"
#define TRAIT_LAW_ENFORCEMENT_METABOLISM "law-enforcement-metabolism"
#define TRAIT_STRONG_GRABBER "strong_grabber"
#define TRAIT_CALCIUM_HEALER "calcium_healer"
@@ -206,6 +208,7 @@
#define CULT_TRAIT "cult"
#define CURSED_ITEM_TRAIT "cursed-item" // The item is magically cursed
#define ABSTRACT_ITEM_TRAIT "abstract-item"
+#define STATUS_EFFECT_TRAIT "status-effect"
#define ROUNDSTART_TRAIT "roundstart" //cannot be removed without admin intervention
// unique trait sources, still defines
diff --git a/code/__HELPERS/mobs.dm b/code/__HELPERS/mobs.dm
index 49ffe578..854fa01e 100644
--- a/code/__HELPERS/mobs.dm
+++ b/code/__HELPERS/mobs.dm
@@ -1,598 +1,600 @@
-/proc/random_blood_type()
- return pick(4;"O-", 36;"O+", 3;"A-", 28;"A+", 1;"B-", 20;"B+", 1;"AB-", 5;"AB+")
-
-/proc/random_eye_color()
- switch(pick(20;"brown",20;"hazel",20;"grey",15;"blue",15;"green",1;"amber",1;"albino"))
- if("brown")
- return "630"
- if("hazel")
- return "542"
- if("grey")
- return pick("666","777","888","999","aaa","bbb","ccc")
- if("blue")
- return "36c"
- if("green")
- return "060"
- if("amber")
- return "fc0"
- if("albino")
- return pick("c","d","e","f") + pick("0","1","2","3","4","5","6","7","8","9") + pick("0","1","2","3","4","5","6","7","8","9")
- else
- return "000"
-
-#define UNDIE_COLORABLE(U) (U?.has_color)
-
-/proc/random_underwear(gender)
- if(!GLOB.underwear_list.len)
- init_sprite_accessory_subtypes(/datum/sprite_accessory/underwear/bottom, GLOB.underwear_list, GLOB.underwear_m, GLOB.underwear_f)
- switch(gender)
- if(MALE)
- return pick(GLOB.underwear_m)
- if(FEMALE)
- return pick(GLOB.underwear_f)
- else
- return pick(GLOB.underwear_list)
-
-/proc/random_undershirt(gender)
- if(!GLOB.undershirt_list.len)
- init_sprite_accessory_subtypes(/datum/sprite_accessory/underwear/top, GLOB.undershirt_list, GLOB.undershirt_m, GLOB.undershirt_f)
- switch(gender)
- if(MALE)
- return pick(GLOB.undershirt_m)
- if(FEMALE)
- return pick(GLOB.undershirt_f)
- else
- return pick(GLOB.undershirt_list)
-
-/proc/random_socks()
- if(!GLOB.socks_list.len)
- init_sprite_accessory_subtypes(/datum/sprite_accessory/underwear/socks, GLOB.socks_list)
- return pick(GLOB.socks_list)
-
-/proc/random_features(intendedspecies)
- if(!GLOB.tails_list_human.len)
- init_sprite_accessory_subtypes(/datum/sprite_accessory/tails/human, GLOB.tails_list_human)
- if(!GLOB.tails_list_lizard.len)
- init_sprite_accessory_subtypes(/datum/sprite_accessory/tails/lizard, GLOB.tails_list_lizard)
- if(!GLOB.snouts_list.len)
- init_sprite_accessory_subtypes(/datum/sprite_accessory/snouts, GLOB.snouts_list)
- if(!GLOB.horns_list.len)
- init_sprite_accessory_subtypes(/datum/sprite_accessory/horns, GLOB.horns_list)
- if(!GLOB.ears_list.len)
- init_sprite_accessory_subtypes(/datum/sprite_accessory/ears, GLOB.horns_list)
- if(!GLOB.frills_list.len)
- init_sprite_accessory_subtypes(/datum/sprite_accessory/frills, GLOB.frills_list)
- if(!GLOB.spines_list.len)
- init_sprite_accessory_subtypes(/datum/sprite_accessory/spines, GLOB.spines_list)
- if(!GLOB.legs_list.len)
- init_sprite_accessory_subtypes(/datum/sprite_accessory/legs, GLOB.legs_list)
- if(!GLOB.body_markings_list.len)
- init_sprite_accessory_subtypes(/datum/sprite_accessory/body_markings, GLOB.body_markings_list)
- if(!GLOB.wings_list.len)
- init_sprite_accessory_subtypes(/datum/sprite_accessory/wings, GLOB.wings_list)
- if(!GLOB.deco_wings_list.len)
- init_sprite_accessory_subtypes(/datum/sprite_accessory/deco_wings, GLOB.deco_wings_list)
- if(!GLOB.moth_wings_list.len)
- init_sprite_accessory_subtypes(/datum/sprite_accessory/moth_wings, GLOB.moth_wings_list)
- if(!GLOB.moth_fluffs_list.len)
- init_sprite_accessory_subtypes(/datum/sprite_accessory/moth_fluff, GLOB.moth_fluffs_list)
- if(!GLOB.moth_markings_list.len)
- init_sprite_accessory_subtypes(/datum/sprite_accessory/moth_markings, GLOB.moth_markings_list)
-
- //CIT CHANGES - genitals and such
- if(!GLOB.cock_shapes_list.len)
- init_sprite_accessory_subtypes(/datum/sprite_accessory/penis, GLOB.cock_shapes_list)
- if(!GLOB.balls_shapes_list.len)
- init_sprite_accessory_subtypes(/datum/sprite_accessory/testicles, GLOB.balls_shapes_list)
- if(!GLOB.vagina_shapes_list.len)
- init_sprite_accessory_subtypes(/datum/sprite_accessory/vagina, GLOB.vagina_shapes_list)
- if(!GLOB.breasts_shapes_list.len)
- init_sprite_accessory_subtypes(/datum/sprite_accessory/breasts, GLOB.breasts_shapes_list)
- if(!GLOB.ipc_screens_list.len)
- init_sprite_accessory_subtypes(/datum/sprite_accessory/screen, GLOB.ipc_screens_list)
- if(!GLOB.ipc_antennas_list.len)
- init_sprite_accessory_subtypes(/datum/sprite_accessory/antenna, GLOB.ipc_antennas_list)
- if(!GLOB.mam_body_markings_list.len)
- init_sprite_accessory_subtypes(/datum/sprite_accessory/mam_body_markings, GLOB.mam_body_markings_list)
- if(!GLOB.mam_tails_list.len)
- init_sprite_accessory_subtypes(/datum/sprite_accessory/mam_tails, GLOB.mam_tails_list)
- if(!GLOB.mam_ears_list.len)
- init_sprite_accessory_subtypes(/datum/sprite_accessory/mam_ears, GLOB.mam_ears_list)
- if(!GLOB.mam_snouts_list.len)
- init_sprite_accessory_subtypes(/datum/sprite_accessory/mam_snouts, GLOB.mam_snouts_list)
-
- //snowflake check so people's ckey features don't get randomly put on unmonkeys/spawns
- var/list/snowflake_mam_tails_list = list()
- for(var/mtpath in GLOB.mam_tails_list)
- var/datum/sprite_accessory/mam_tails/instance = GLOB.mam_tails_list[mtpath]
- if(istype(instance, /datum/sprite_accessory))
- var/datum/sprite_accessory/S = instance
- if(intendedspecies && S.recommended_species && !S.recommended_species.Find(intendedspecies))
- continue
- if(!S.ckeys_allowed)
- snowflake_mam_tails_list[S.name] = mtpath
- var/list/snowflake_markings_list = list()
- for(var/mmpath in GLOB.mam_body_markings_list)
- var/datum/sprite_accessory/mam_body_markings/instance = GLOB.mam_body_markings_list[mmpath]
- if(istype(instance, /datum/sprite_accessory))
- var/datum/sprite_accessory/S = instance
- if(intendedspecies && S.recommended_species && !S.recommended_species.Find(intendedspecies))
- continue
- if(!S.ckeys_allowed)
- snowflake_markings_list[S.name] = mmpath
- var/list/snowflake_ears_list = list()
- for(var/mepath in GLOB.mam_ears_list)
- var/datum/sprite_accessory/mam_ears/instance = GLOB.mam_ears_list[mepath]
- if(istype(instance, /datum/sprite_accessory))
- var/datum/sprite_accessory/S = instance
- if(intendedspecies && S.recommended_species && !S.recommended_species.Find(intendedspecies))
- continue
- if(!S.ckeys_allowed)
- snowflake_ears_list[S.name] = mepath
- var/list/snowflake_mam_snouts_list = list()
- for(var/mspath in GLOB.mam_snouts_list)
- var/datum/sprite_accessory/mam_snouts/instance = GLOB.mam_snouts_list[mspath]
- if(istype(instance, /datum/sprite_accessory))
- var/datum/sprite_accessory/S = instance
- if(!S.ckeys_allowed)
- snowflake_mam_snouts_list[S.name] = mspath
- var/list/snowflake_ipc_antenna_list = list()
- for(var/mspath in GLOB.ipc_antennas_list)
- var/datum/sprite_accessory/mam_snouts/instance = GLOB.ipc_antennas_list[mspath]
- if(istype(instance, /datum/sprite_accessory))
- var/datum/sprite_accessory/S = instance
- if(intendedspecies && S.recommended_species && !S.recommended_species.Find(intendedspecies))
- continue
- if(!S.ckeys_allowed)
- snowflake_ipc_antenna_list[S.name] = mspath
- var/color1 = random_short_color()
- var/color2 = random_short_color()
- var/color3 = random_short_color()
-
- //CIT CHANGE - changes this entire return to support cit's snowflake parts
- return(list(
- "mcolor" = color1,
- "mcolor2" = color2,
- "mcolor3" = color3,
- "tail_lizard" = pick(GLOB.tails_list_lizard),
- "tail_human" = "None",
- "wings" = "None",
- "snout" = pick(GLOB.snouts_list),
- "horns" = pick(GLOB.horns_list),
- "ears" = "None",
- "frills" = pick(GLOB.frills_list),
- "spines" = pick(GLOB.spines_list),
- "body_markings" = pick(GLOB.body_markings_list),
- "legs" = pick("Normal Legs","Digitigrade Legs"),
- "caps" = pick(GLOB.caps_list),
- "moth_wings" = pick(GLOB.moth_wings_list),
- "moth_markings" = pick(GLOB.moth_markings_list),
- "insect_fluff" = "None",
- "taur" = "None",
- "mam_body_markings" = snowflake_markings_list.len ? pick(snowflake_markings_list) : "None",
- "mam_ears" = snowflake_ears_list ? pick(snowflake_ears_list) : "None",
- "mam_snouts" = snowflake_mam_snouts_list ? pick(snowflake_mam_snouts_list) : "None",
- "mam_tail" = snowflake_mam_tails_list ? pick(snowflake_mam_tails_list) : "None",
- "mam_tail_animated" = "None",
- "xenodorsal" = "Standard",
- "xenohead" = "Standard",
- "xenotail" = "Xenomorph Tail",
- "exhibitionist" = FALSE,
- "genitals_use_skintone" = FALSE,
- "has_cock" = FALSE,
- "cock_shape" = pick(GLOB.cock_shapes_list),
- "cock_length" = 6,
- "cock_girth_ratio" = COCK_GIRTH_RATIO_DEF,
- "cock_color" = pick("FFFFFF","7F7F7F", "7FFF7F", "7F7FFF", "FF7F7F", "7FFFFF", "FF7FFF", "FFFF7F"),
- "has_sheath" = FALSE,
- "sheath_color" = pick("FFFFFF","7F7F7F", "7FFF7F", "7F7FFF", "FF7F7F", "7FFFFF", "FF7FFF", "FFFF7F"),
- "has_balls" = FALSE,
- "balls_internal" = FALSE,
- "balls_color" = pick("FFFFFF","7F7F7F", "7FFF7F", "7F7FFF", "FF7F7F", "7FFFFF", "FF7FFF", "FFFF7F"),
- "balls_amount" = 2,
- "balls_sack_size" = BALLS_SACK_SIZE_DEF,
- "balls_size" = BALLS_SIZE_DEF,
- "balls_shape" = "Single",
- "balls_cum_rate" = CUM_RATE,
- "balls_cum_mult" = CUM_RATE_MULT,
- "balls_efficiency" = CUM_EFFICIENCY,
- "balls_fluid" = /datum/reagent/consumable/semen,
- "has_ovi" = FALSE,
- "ovi_shape" = "knotted",
- "ovi_length" = 6,
- "ovi_color" = pick("FFFFFF","7F7F7F", "7FFF7F", "7F7FFF", "FF7F7F", "7FFFFF", "FF7FFF", "FFFF7F"),
- "has_eggsack" = FALSE,
- "eggsack_internal" = TRUE,
- "eggsack_color" = pick("FFFFFF","7F7F7F", "7FFF7F", "7F7FFF", "FF7F7F", "7FFFFF", "FF7FFF", "FFFF7F"),
- "eggsack_size" = BALLS_SACK_SIZE_DEF,
- "eggsack_egg_color" = pick("FFFFFF","7F7F7F", "7FFF7F", "7F7FFF", "FF7F7F", "7FFFFF", "FF7FFF", "FFFF7F"),
- "eggsack_egg_size" = EGG_GIRTH_DEF,
- "has_breasts" = FALSE,
- "breasts_color" = pick("FFFFFF","7F7F7F", "7FFF7F", "7F7FFF", "FF7F7F", "7FFFFF", "FF7FFF", "FFFF7F"),
- "breasts_size" = pick(GLOB.breasts_size_list),
- "breasts_shape" = "Pair",
- "breasts_fluid" = /datum/reagent/consumable/milk,
- "breasts_producing" = FALSE,
- "has_vag" = FALSE,
- "vag_shape" = pick(GLOB.vagina_shapes_list),
- "vag_color" = pick("FFFFFF","7F7F7F", "7FFF7F", "7F7FFF", "FF7F7F", "7FFFFF", "FF7FFF", "FFFF7F"),
- "vag_clits" = 1,
- "vag_clit_diam" = 0.25,
- "vag_clit_len" = 0.25,
- "has_womb" = FALSE,
- "can_get_preg" = FALSE,
- "womb_cum_rate" = CUM_RATE,
- "womb_cum_mult" = CUM_RATE_MULT,
- "womb_efficiency" = CUM_EFFICIENCY,
- "womb_fluid" = "femcum",
- "ipc_screen" = snowflake_ipc_antenna_list ? pick(snowflake_ipc_antenna_list) : "None",
- "ipc_antenna" = "None",
- "flavor_text" = ""))
-
-/proc/random_hair_style(gender)
- switch(gender)
- if(MALE)
- return pick(GLOB.hair_styles_male_list)
- if(FEMALE)
- return pick(GLOB.hair_styles_female_list)
- else
- return pick(GLOB.hair_styles_list)
-
-/proc/random_facial_hair_style(gender)
- switch(gender)
- if(MALE)
- return pick(GLOB.facial_hair_styles_male_list)
- if(FEMALE)
- return pick(GLOB.facial_hair_styles_female_list)
- else
- return pick(GLOB.facial_hair_styles_list)
-
-/proc/random_unique_name(gender, attempts_to_find_unique_name=10)
- for(var/i in 1 to attempts_to_find_unique_name)
- if(gender==FEMALE)
- . = capitalize(pick(GLOB.first_names_female)) + " " + capitalize(pick(GLOB.last_names))
- else
- . = capitalize(pick(GLOB.first_names_male)) + " " + capitalize(pick(GLOB.last_names))
-
- if(!findname(.))
- break
-
-/proc/random_unique_lizard_name(gender, attempts_to_find_unique_name=10)
- for(var/i in 1 to attempts_to_find_unique_name)
- . = capitalize(lizard_name(gender))
-
- if(!findname(.))
- break
-
-/proc/random_unique_plasmaman_name(attempts_to_find_unique_name=10)
- for(var/i in 1 to attempts_to_find_unique_name)
- . = capitalize(plasmaman_name())
-
- if(!findname(.))
- break
-
-/proc/random_unique_moth_name(attempts_to_find_unique_name=10)
- for(var/i in 1 to attempts_to_find_unique_name)
- . = capitalize(pick(GLOB.moth_first)) + " " + capitalize(pick(GLOB.moth_last))
-
- if(!findname(.))
- break
-
-/proc/random_skin_tone()
- return pick(GLOB.skin_tones)
-
-GLOBAL_LIST_INIT(skin_tones, list(
- "albino",
- "caucasian1",
- "caucasian2",
- "caucasian3",
- "latino",
- "mediterranean",
- "asian1",
- "asian2",
- "arab",
- "indian",
- "african1",
- "african2"
- ))
-
-GLOBAL_LIST_EMPTY(species_list)
-
-/proc/age2agedescription(age)
- switch(age)
- if(0 to 1)
- return "infant"
- if(1 to 3)
- return "toddler"
- if(3 to 13)
- return "child"
- if(13 to 19)
- return "teenager"
- if(19 to 30)
- return "young adult"
- if(30 to 45)
- return "adult"
- if(45 to 60)
- return "middle-aged"
- if(60 to 70)
- return "aging"
- if(70 to INFINITY)
- return "elderly"
- else
- return "unknown"
-
-/proc/do_mob(mob/user , mob/target, time = 30, uninterruptible = 0, progress = 1, datum/callback/extra_checks = null, ignorehelditem = 0)
- if(!user || !target)
- return 0
- var/user_loc = user.loc
-
- var/drifting = 0
- if(!user.Process_Spacemove(0) && user.inertia_dir)
- drifting = 1
-
- var/target_loc = target.loc
-
- var/holding = user.get_active_held_item()
- var/datum/progressbar/progbar
- if (progress)
- progbar = new(user, time, target)
-
- var/endtime = world.time+time
- var/starttime = world.time
- . = 1
- while (world.time < endtime)
- stoplag(1)
- if (progress)
- progbar.update(world.time - starttime)
- if(QDELETED(user) || QDELETED(target))
- . = 0
- break
- if(uninterruptible)
- continue
-
- if(drifting && !user.inertia_dir)
- drifting = 0
- user_loc = user.loc
-
- if((!drifting && user.loc != user_loc) || target.loc != target_loc || (!ignorehelditem && user.get_active_held_item() != holding) || user.incapacitated() || user.lying || (extra_checks && !extra_checks.Invoke()))
- . = 0
- break
- if (progress)
- qdel(progbar)
-
-
-//some additional checks as a callback for for do_afters that want to break on losing health or on the mob taking action
-/mob/proc/break_do_after_checks(list/checked_health, check_clicks)
- if(check_clicks && next_move > world.time)
- return FALSE
- return TRUE
-
-//pass a list in the format list("health" = mob's health var) to check health during this
-/mob/living/break_do_after_checks(list/checked_health, check_clicks)
- if(islist(checked_health))
- if(health < checked_health["health"])
- return FALSE
- checked_health["health"] = health
- return ..()
-
-/proc/do_after(mob/user, var/delay, needhand = 1, atom/target = null, progress = 1, datum/callback/extra_checks = null)
- if(!user)
- return 0
- var/atom/Tloc = null
- if(target && !isturf(target))
- Tloc = target.loc
-
- var/atom/Uloc = user.loc
-
- var/drifting = 0
- if(!user.Process_Spacemove(0) && user.inertia_dir)
- drifting = 1
-
- var/holding = user.get_active_held_item()
-
- var/holdingnull = 1 //User's hand started out empty, check for an empty hand
- if(holding)
- holdingnull = 0 //Users hand started holding something, check to see if it's still holding that
-
- delay *= user.do_after_coefficent()
-
- var/datum/progressbar/progbar
- if (progress)
- progbar = new(user, delay, target)
-
- var/endtime = world.time + delay
- var/starttime = world.time
- . = 1
- while (world.time < endtime)
- stoplag(1)
- if (progress)
- progbar.update(world.time - starttime)
-
- if(drifting && !user.inertia_dir)
- drifting = 0
- Uloc = user.loc
-
- if(QDELETED(user) || user.stat || user.IsKnockdown() || user.IsStun() || (!drifting && user.loc != Uloc) || (extra_checks && !extra_checks.Invoke()))
- . = 0
- break
-
- if(isliving(user))
- var/mob/living/L = user
- if(L.recoveringstam)
- . = 0
- break
-
- if(!QDELETED(Tloc) && (QDELETED(target) || Tloc != target.loc))
- if((Uloc != Tloc || Tloc != user) && !drifting)
- . = 0
- break
-
- if(needhand)
- //This might seem like an odd check, but you can still need a hand even when it's empty
- //i.e the hand is used to pull some item/tool out of the construction
- if(!holdingnull)
- if(!holding)
- . = 0
- break
- if(user.get_active_held_item() != holding)
- . = 0
- break
- if (progress)
- qdel(progbar)
-
-/mob/proc/do_after_coefficent() // This gets added to the delay on a do_after, default 1
- . = 1
- return
-
-/proc/do_after_mob(mob/user, var/list/targets, time = 30, uninterruptible = 0, progress = 1, datum/callback/extra_checks)
- if(!user || !targets)
- return 0
- if(!islist(targets))
- targets = list(targets)
- var/user_loc = user.loc
-
- var/drifting = 0
- if(!user.Process_Spacemove(0) && user.inertia_dir)
- drifting = 1
-
- var/list/originalloc = list()
- for(var/atom/target in targets)
- originalloc[target] = target.loc
-
- var/holding = user.get_active_held_item()
- var/datum/progressbar/progbar
- if(progress)
- progbar = new(user, time, targets[1])
-
- var/endtime = world.time + time
- var/starttime = world.time
- . = 1
- mainloop:
- while(world.time < endtime)
- stoplag(1)
- if(progress)
- progbar.update(world.time - starttime)
- if(QDELETED(user) || !targets)
- . = 0
- break
- if(uninterruptible)
- continue
-
- if(drifting && !user.inertia_dir)
- drifting = 0
- user_loc = user.loc
-
- for(var/atom/target in targets)
- if((!drifting && user_loc != user.loc) || QDELETED(target) || originalloc[target] != target.loc || user.get_active_held_item() != holding || user.incapacitated() || user.lying || (extra_checks && !extra_checks.Invoke()))
- . = 0
- break mainloop
- if(progbar)
- qdel(progbar)
-
-/proc/is_species(A, species_datum)
- . = FALSE
- if(ishuman(A))
- var/mob/living/carbon/human/H = A
- if(H.dna && istype(H.dna.species, species_datum))
- . = TRUE
-
-/proc/spawn_atom_to_turf(spawn_type, target, amount, admin_spawn=FALSE, list/extra_args)
- var/turf/T = get_turf(target)
- if(!T)
- CRASH("attempt to spawn atom type: [spawn_type] in nullspace")
-
- var/list/new_args = list(T)
- if(extra_args)
- new_args += extra_args
-
- for(var/j in 1 to amount)
- var/atom/X = new spawn_type(arglist(new_args))
- if (admin_spawn)
- X.flags_1 |= ADMIN_SPAWNED_1
-
-/proc/spawn_and_random_walk(spawn_type, target, amount, walk_chance=100, max_walk=3, always_max_walk=FALSE, admin_spawn=FALSE)
- var/turf/T = get_turf(target)
- var/step_count = 0
- if(!T)
- CRASH("attempt to spawn atom type: [spawn_type] in nullspace")
-
- for(var/j in 1 to amount)
- var/atom/movable/X = new spawn_type(T)
- if (admin_spawn)
- X.flags_1 |= ADMIN_SPAWNED_1
-
- if(always_max_walk || prob(walk_chance))
- if(always_max_walk)
- step_count = max_walk
- else
- step_count = rand(1, max_walk)
-
- for(var/i in 1 to step_count)
- step(X, pick(NORTH, SOUTH, EAST, WEST))
-
-/proc/deadchat_broadcast(message, mob/follow_target=null, turf/turf_target=null, speaker_key=null, message_type=DEADCHAT_REGULAR)
- message = "[message]"
- for(var/mob/M in GLOB.player_list)
- var/datum/preferences/prefs
- if(M.client && M.client.prefs)
- prefs = M.client.prefs
- else
- prefs = new
-
- var/adminoverride = 0
- if(M.client && M.client.holder && (prefs.chat_toggles & CHAT_DEAD))
- adminoverride = 1
- if(isnewplayer(M) && !adminoverride)
- continue
- if(M.stat != DEAD && !adminoverride)
- continue
- if(speaker_key && speaker_key in prefs.ignoring)
- continue
-
- switch(message_type)
- if(DEADCHAT_DEATHRATTLE)
- if(prefs.toggles & DISABLE_DEATHRATTLE)
- continue
- if(DEADCHAT_ARRIVALRATTLE)
- if(prefs.toggles & DISABLE_ARRIVALRATTLE)
- continue
-
- if(isobserver(M))
- var/rendered_message = message
-
- if(follow_target)
- var/F
- if(turf_target)
- F = FOLLOW_OR_TURF_LINK(M, follow_target, turf_target)
- else
- F = FOLLOW_LINK(M, follow_target)
- rendered_message = "[F] [message]"
- else if(turf_target)
- var/turf_link = TURF_LINK(M, turf_target)
- rendered_message = "[turf_link] [message]"
-
- to_chat(M, rendered_message)
- else
- to_chat(M, message)
-
-//Used in chemical_mob_spawn. Generates a random mob based on a given gold_core_spawnable value.
-/proc/create_random_mob(spawn_location, mob_class = HOSTILE_SPAWN)
- var/static/list/mob_spawn_meancritters = list() // list of possible hostile mobs
- var/static/list/mob_spawn_nicecritters = list() // and possible friendly mobs
-
- if(mob_spawn_meancritters.len <= 0 || mob_spawn_nicecritters.len <= 0)
- for(var/T in typesof(/mob/living/simple_animal))
- var/mob/living/simple_animal/SA = T
- switch(initial(SA.gold_core_spawnable))
- if(HOSTILE_SPAWN)
- mob_spawn_meancritters += T
- if(FRIENDLY_SPAWN)
- mob_spawn_nicecritters += T
-
- var/chosen
- if(mob_class == FRIENDLY_SPAWN)
- chosen = pick(mob_spawn_nicecritters)
- else
- chosen = pick(mob_spawn_meancritters)
- var/mob/living/simple_animal/C = new chosen(spawn_location)
- return C
+/proc/random_blood_type()
+ return pick(4;"O-", 36;"O+", 3;"A-", 28;"A+", 1;"B-", 20;"B+", 1;"AB-", 5;"AB+")
+
+/proc/random_eye_color()
+ switch(pick(20;"brown",20;"hazel",20;"grey",15;"blue",15;"green",1;"amber",1;"albino"))
+ if("brown")
+ return "630"
+ if("hazel")
+ return "542"
+ if("grey")
+ return pick("666","777","888","999","aaa","bbb","ccc")
+ if("blue")
+ return "36c"
+ if("green")
+ return "060"
+ if("amber")
+ return "fc0"
+ if("albino")
+ return pick("c","d","e","f") + pick("0","1","2","3","4","5","6","7","8","9") + pick("0","1","2","3","4","5","6","7","8","9")
+ else
+ return "000"
+
+#define UNDIE_COLORABLE(U) (U?.has_color)
+
+/proc/random_underwear(gender)
+ if(!GLOB.underwear_list.len)
+ init_sprite_accessory_subtypes(/datum/sprite_accessory/underwear/bottom, GLOB.underwear_list, GLOB.underwear_m, GLOB.underwear_f)
+ switch(gender)
+ if(MALE)
+ return pick(GLOB.underwear_m)
+ if(FEMALE)
+ return pick(GLOB.underwear_f)
+ else
+ return pick(GLOB.underwear_list)
+
+/proc/random_undershirt(gender)
+ if(!GLOB.undershirt_list.len)
+ init_sprite_accessory_subtypes(/datum/sprite_accessory/underwear/top, GLOB.undershirt_list, GLOB.undershirt_m, GLOB.undershirt_f)
+ switch(gender)
+ if(MALE)
+ return pick(GLOB.undershirt_m)
+ if(FEMALE)
+ return pick(GLOB.undershirt_f)
+ else
+ return pick(GLOB.undershirt_list)
+
+/proc/random_socks()
+ if(!GLOB.socks_list.len)
+ init_sprite_accessory_subtypes(/datum/sprite_accessory/underwear/socks, GLOB.socks_list)
+ return pick(GLOB.socks_list)
+
+/proc/random_features(intendedspecies)
+ if(!GLOB.tails_list_human.len)
+ init_sprite_accessory_subtypes(/datum/sprite_accessory/tails/human, GLOB.tails_list_human)
+ if(!GLOB.tails_list_lizard.len)
+ init_sprite_accessory_subtypes(/datum/sprite_accessory/tails/lizard, GLOB.tails_list_lizard)
+ if(!GLOB.snouts_list.len)
+ init_sprite_accessory_subtypes(/datum/sprite_accessory/snouts, GLOB.snouts_list)
+ if(!GLOB.horns_list.len)
+ init_sprite_accessory_subtypes(/datum/sprite_accessory/horns, GLOB.horns_list)
+ if(!GLOB.ears_list.len)
+ init_sprite_accessory_subtypes(/datum/sprite_accessory/ears, GLOB.horns_list)
+ if(!GLOB.frills_list.len)
+ init_sprite_accessory_subtypes(/datum/sprite_accessory/frills, GLOB.frills_list)
+ if(!GLOB.spines_list.len)
+ init_sprite_accessory_subtypes(/datum/sprite_accessory/spines, GLOB.spines_list)
+ if(!GLOB.legs_list.len)
+ init_sprite_accessory_subtypes(/datum/sprite_accessory/legs, GLOB.legs_list)
+ if(!GLOB.body_markings_list.len)
+ init_sprite_accessory_subtypes(/datum/sprite_accessory/body_markings, GLOB.body_markings_list)
+ if(!GLOB.wings_list.len)
+ init_sprite_accessory_subtypes(/datum/sprite_accessory/wings, GLOB.wings_list)
+ if(!GLOB.deco_wings_list.len)
+ init_sprite_accessory_subtypes(/datum/sprite_accessory/deco_wings, GLOB.deco_wings_list)
+ if(!GLOB.moth_wings_list.len)
+ init_sprite_accessory_subtypes(/datum/sprite_accessory/moth_wings, GLOB.moth_wings_list)
+ if(!GLOB.moth_fluffs_list.len)
+ init_sprite_accessory_subtypes(/datum/sprite_accessory/moth_fluff, GLOB.moth_fluffs_list)
+ if(!GLOB.moth_markings_list.len)
+ init_sprite_accessory_subtypes(/datum/sprite_accessory/moth_markings, GLOB.moth_markings_list)
+
+ //CIT CHANGES - genitals and such
+ if(!GLOB.cock_shapes_list.len)
+ init_sprite_accessory_subtypes(/datum/sprite_accessory/penis, GLOB.cock_shapes_list)
+ if(!GLOB.balls_shapes_list.len)
+ init_sprite_accessory_subtypes(/datum/sprite_accessory/testicles, GLOB.balls_shapes_list)
+ if(!GLOB.vagina_shapes_list.len)
+ init_sprite_accessory_subtypes(/datum/sprite_accessory/vagina, GLOB.vagina_shapes_list)
+ if(!GLOB.breasts_shapes_list.len)
+ init_sprite_accessory_subtypes(/datum/sprite_accessory/breasts, GLOB.breasts_shapes_list)
+ if(!GLOB.ipc_screens_list.len)
+ init_sprite_accessory_subtypes(/datum/sprite_accessory/screen, GLOB.ipc_screens_list)
+ if(!GLOB.ipc_antennas_list.len)
+ init_sprite_accessory_subtypes(/datum/sprite_accessory/antenna, GLOB.ipc_antennas_list)
+ if(!GLOB.mam_body_markings_list.len)
+ init_sprite_accessory_subtypes(/datum/sprite_accessory/mam_body_markings, GLOB.mam_body_markings_list)
+ if(!GLOB.mam_tails_list.len)
+ init_sprite_accessory_subtypes(/datum/sprite_accessory/mam_tails, GLOB.mam_tails_list)
+ if(!GLOB.mam_ears_list.len)
+ init_sprite_accessory_subtypes(/datum/sprite_accessory/mam_ears, GLOB.mam_ears_list)
+ if(!GLOB.mam_snouts_list.len)
+ init_sprite_accessory_subtypes(/datum/sprite_accessory/mam_snouts, GLOB.mam_snouts_list)
+
+ //snowflake check so people's ckey features don't get randomly put on unmonkeys/spawns
+ var/list/snowflake_mam_tails_list = list()
+ for(var/mtpath in GLOB.mam_tails_list)
+ var/datum/sprite_accessory/mam_tails/instance = GLOB.mam_tails_list[mtpath]
+ if(istype(instance, /datum/sprite_accessory))
+ var/datum/sprite_accessory/S = instance
+ if(intendedspecies && S.recommended_species && !S.recommended_species.Find(intendedspecies))
+ continue
+ if(!S.ckeys_allowed)
+ snowflake_mam_tails_list[S.name] = mtpath
+ var/list/snowflake_markings_list = list()
+ for(var/mmpath in GLOB.mam_body_markings_list)
+ var/datum/sprite_accessory/mam_body_markings/instance = GLOB.mam_body_markings_list[mmpath]
+ if(istype(instance, /datum/sprite_accessory))
+ var/datum/sprite_accessory/S = instance
+ if(intendedspecies && S.recommended_species && !S.recommended_species.Find(intendedspecies))
+ continue
+ if(!S.ckeys_allowed)
+ snowflake_markings_list[S.name] = mmpath
+ var/list/snowflake_ears_list = list()
+ for(var/mepath in GLOB.mam_ears_list)
+ var/datum/sprite_accessory/mam_ears/instance = GLOB.mam_ears_list[mepath]
+ if(istype(instance, /datum/sprite_accessory))
+ var/datum/sprite_accessory/S = instance
+ if(intendedspecies && S.recommended_species && !S.recommended_species.Find(intendedspecies))
+ continue
+ if(!S.ckeys_allowed)
+ snowflake_ears_list[S.name] = mepath
+ var/list/snowflake_mam_snouts_list = list()
+ for(var/mspath in GLOB.mam_snouts_list)
+ var/datum/sprite_accessory/mam_snouts/instance = GLOB.mam_snouts_list[mspath]
+ if(istype(instance, /datum/sprite_accessory))
+ var/datum/sprite_accessory/S = instance
+ if(!S.ckeys_allowed)
+ snowflake_mam_snouts_list[S.name] = mspath
+ var/list/snowflake_ipc_antenna_list = list()
+ for(var/mspath in GLOB.ipc_antennas_list)
+ var/datum/sprite_accessory/mam_snouts/instance = GLOB.ipc_antennas_list[mspath]
+ if(istype(instance, /datum/sprite_accessory))
+ var/datum/sprite_accessory/S = instance
+ if(intendedspecies && S.recommended_species && !S.recommended_species.Find(intendedspecies))
+ continue
+ if(!S.ckeys_allowed)
+ snowflake_ipc_antenna_list[S.name] = mspath
+ var/color1 = random_short_color()
+ var/color2 = random_short_color()
+ var/color3 = random_short_color()
+
+ //CIT CHANGE - changes this entire return to support cit's snowflake parts
+ return(list(
+ "mcolor" = color1,
+ "mcolor2" = color2,
+ "mcolor3" = color3,
+ "tail_lizard" = pick(GLOB.tails_list_lizard),
+ "tail_human" = "None",
+ "wings" = "None",
+ "snout" = pick(GLOB.snouts_list),
+ "horns" = pick(GLOB.horns_list),
+ "ears" = "None",
+ "frills" = pick(GLOB.frills_list),
+ "spines" = pick(GLOB.spines_list),
+ "body_markings" = pick(GLOB.body_markings_list),
+ "legs" = pick("Normal Legs","Digitigrade Legs"),
+ "caps" = pick(GLOB.caps_list),
+ "moth_wings" = pick(GLOB.moth_wings_list),
+ "moth_markings" = pick(GLOB.moth_markings_list),
+ "insect_fluff" = "None",
+ "taur" = "None",
+ "mam_body_markings" = snowflake_markings_list.len ? pick(snowflake_markings_list) : "None",
+ "mam_ears" = snowflake_ears_list ? pick(snowflake_ears_list) : "None",
+ "mam_snouts" = snowflake_mam_snouts_list ? pick(snowflake_mam_snouts_list) : "None",
+ "mam_tail" = snowflake_mam_tails_list ? pick(snowflake_mam_tails_list) : "None",
+ "mam_tail_animated" = "None",
+ "xenodorsal" = "Standard",
+ "xenohead" = "Standard",
+ "xenotail" = "Xenomorph Tail",
+ "exhibitionist" = FALSE,
+ "genitals_use_skintone" = FALSE,
+ "has_cock" = FALSE,
+ "cock_shape" = pick(GLOB.cock_shapes_list),
+ "cock_length" = 6,
+ "cock_girth_ratio" = COCK_GIRTH_RATIO_DEF,
+ "cock_color" = pick("FFFFFF","7F7F7F", "7FFF7F", "7F7FFF", "FF7F7F", "7FFFFF", "FF7FFF", "FFFF7F"),
+ "has_sheath" = FALSE,
+ "sheath_color" = pick("FFFFFF","7F7F7F", "7FFF7F", "7F7FFF", "FF7F7F", "7FFFFF", "FF7FFF", "FFFF7F"),
+ "has_balls" = FALSE,
+ "balls_internal" = FALSE,
+ "balls_color" = pick("FFFFFF","7F7F7F", "7FFF7F", "7F7FFF", "FF7F7F", "7FFFFF", "FF7FFF", "FFFF7F"),
+ "balls_amount" = 2,
+ "balls_sack_size" = BALLS_SACK_SIZE_DEF,
+ "balls_size" = BALLS_SIZE_DEF,
+ "balls_shape" = "Single",
+ "balls_cum_rate" = CUM_RATE,
+ "balls_cum_mult" = CUM_RATE_MULT,
+ "balls_efficiency" = CUM_EFFICIENCY,
+ "balls_fluid" = /datum/reagent/consumable/semen,
+ "has_ovi" = FALSE,
+ "ovi_shape" = "knotted",
+ "ovi_length" = 6,
+ "ovi_color" = pick("FFFFFF","7F7F7F", "7FFF7F", "7F7FFF", "FF7F7F", "7FFFFF", "FF7FFF", "FFFF7F"),
+ "has_eggsack" = FALSE,
+ "eggsack_internal" = TRUE,
+ "eggsack_color" = pick("FFFFFF","7F7F7F", "7FFF7F", "7F7FFF", "FF7F7F", "7FFFFF", "FF7FFF", "FFFF7F"),
+ "eggsack_size" = BALLS_SACK_SIZE_DEF,
+ "eggsack_egg_color" = pick("FFFFFF","7F7F7F", "7FFF7F", "7F7FFF", "FF7F7F", "7FFFFF", "FF7FFF", "FFFF7F"),
+ "eggsack_egg_size" = EGG_GIRTH_DEF,
+ "has_breasts" = FALSE,
+ "breasts_color" = pick("FFFFFF","7F7F7F", "7FFF7F", "7F7FFF", "FF7F7F", "7FFFFF", "FF7FFF", "FFFF7F"),
+ "breasts_size" = pick(GLOB.breasts_size_list),
+ "breasts_shape" = "Pair",
+ "breasts_fluid" = /datum/reagent/consumable/milk,
+ "breasts_producing" = FALSE,
+ "has_vag" = FALSE,
+ "vag_shape" = pick(GLOB.vagina_shapes_list),
+ "vag_color" = pick("FFFFFF","7F7F7F", "7FFF7F", "7F7FFF", "FF7F7F", "7FFFFF", "FF7FFF", "FFFF7F"),
+ "vag_clits" = 1,
+ "vag_clit_diam" = 0.25,
+ "vag_clit_len" = 0.25,
+ "has_womb" = FALSE,
+ "can_get_preg" = FALSE,
+ "womb_cum_rate" = CUM_RATE,
+ "womb_cum_mult" = CUM_RATE_MULT,
+ "womb_efficiency" = CUM_EFFICIENCY,
+ "womb_fluid" = "femcum",
+ "ipc_screen" = snowflake_ipc_antenna_list ? pick(snowflake_ipc_antenna_list) : "None",
+ "ipc_antenna" = "None",
+ "flavor_text" = ""))
+
+/proc/random_hair_style(gender)
+ switch(gender)
+ if(MALE)
+ return pick(GLOB.hair_styles_male_list)
+ if(FEMALE)
+ return pick(GLOB.hair_styles_female_list)
+ else
+ return pick(GLOB.hair_styles_list)
+
+/proc/random_facial_hair_style(gender)
+ switch(gender)
+ if(MALE)
+ return pick(GLOB.facial_hair_styles_male_list)
+ if(FEMALE)
+ return pick(GLOB.facial_hair_styles_female_list)
+ else
+ return pick(GLOB.facial_hair_styles_list)
+
+/proc/random_unique_name(gender, attempts_to_find_unique_name=10)
+ for(var/i in 1 to attempts_to_find_unique_name)
+ if(gender==FEMALE)
+ . = capitalize(pick(GLOB.first_names_female)) + " " + capitalize(pick(GLOB.last_names))
+ else
+ . = capitalize(pick(GLOB.first_names_male)) + " " + capitalize(pick(GLOB.last_names))
+
+ if(!findname(.))
+ break
+
+/proc/random_unique_lizard_name(gender, attempts_to_find_unique_name=10)
+ for(var/i in 1 to attempts_to_find_unique_name)
+ . = capitalize(lizard_name(gender))
+
+ if(!findname(.))
+ break
+
+/proc/random_unique_plasmaman_name(attempts_to_find_unique_name=10)
+ for(var/i in 1 to attempts_to_find_unique_name)
+ . = capitalize(plasmaman_name())
+
+ if(!findname(.))
+ break
+
+/proc/random_unique_moth_name(attempts_to_find_unique_name=10)
+ for(var/i in 1 to attempts_to_find_unique_name)
+ . = capitalize(pick(GLOB.moth_first)) + " " + capitalize(pick(GLOB.moth_last))
+
+ if(!findname(.))
+ break
+
+/proc/random_skin_tone()
+ return pick(GLOB.skin_tones)
+
+GLOBAL_LIST_INIT(skin_tones, list(
+ "albino",
+ "caucasian1",
+ "caucasian2",
+ "caucasian3",
+ "latino",
+ "mediterranean",
+ "asian1",
+ "asian2",
+ "arab",
+ "indian",
+ "african1",
+ "african2"
+ ))
+
+GLOBAL_LIST_EMPTY(species_list)
+
+/proc/age2agedescription(age)
+ switch(age)
+ if(0 to 1)
+ return "infant"
+ if(1 to 3)
+ return "toddler"
+ if(3 to 13)
+ return "child"
+ if(13 to 19)
+ return "teenager"
+ if(19 to 30)
+ return "young adult"
+ if(30 to 45)
+ return "adult"
+ if(45 to 60)
+ return "middle-aged"
+ if(60 to 70)
+ return "aging"
+ if(70 to INFINITY)
+ return "elderly"
+ else
+ return "unknown"
+
+/proc/do_mob(mob/user , mob/target, time = 30, uninterruptible = 0, progress = 1, datum/callback/extra_checks = null, ignorehelditem = 0)
+ if(!user || !target)
+ return 0
+ var/user_loc = user.loc
+
+ var/drifting = 0
+ if(!user.Process_Spacemove(0) && user.inertia_dir)
+ drifting = 1
+
+ var/target_loc = target.loc
+
+ var/holding = user.get_active_held_item()
+ var/datum/progressbar/progbar
+ if (progress)
+ progbar = new(user, time, target)
+
+ var/endtime = world.time+time
+ var/starttime = world.time
+ . = 1
+ while (world.time < endtime)
+ stoplag(1)
+ if (progress)
+ progbar.update(world.time - starttime)
+ if(QDELETED(user) || QDELETED(target))
+ . = 0
+ break
+ if(uninterruptible)
+ continue
+
+ if(drifting && !user.inertia_dir)
+ drifting = 0
+ user_loc = user.loc
+
+ if((!drifting && user.loc != user_loc) || target.loc != target_loc || (!ignorehelditem && user.get_active_held_item() != holding) || user.incapacitated() || user.lying || (extra_checks && !extra_checks.Invoke()))
+ . = 0
+ break
+ if (progress)
+ qdel(progbar)
+
+
+//some additional checks as a callback for for do_afters that want to break on losing health or on the mob taking action
+/mob/proc/break_do_after_checks(list/checked_health, check_clicks)
+ if(check_clicks && next_move > world.time)
+ return FALSE
+ return TRUE
+
+//pass a list in the format list("health" = mob's health var) to check health during this
+/mob/living/break_do_after_checks(list/checked_health, check_clicks)
+ if(islist(checked_health))
+ if(health < checked_health["health"])
+ return FALSE
+ checked_health["health"] = health
+ return ..()
+
+/proc/do_after(mob/user, var/delay, needhand = 1, atom/target = null, progress = 1, datum/callback/extra_checks = null)
+ if(!user)
+ return 0
+ var/atom/Tloc = null
+ if(target && !isturf(target))
+ Tloc = target.loc
+
+ var/atom/Uloc = user.loc
+
+ var/drifting = 0
+ if(!user.Process_Spacemove(0) && user.inertia_dir)
+ drifting = 1
+
+ var/holding = user.get_active_held_item()
+
+ var/holdingnull = 1 //User's hand started out empty, check for an empty hand
+ if(holding)
+ holdingnull = 0 //Users hand started holding something, check to see if it's still holding that
+
+ delay *= user.do_after_coefficent()
+
+ var/datum/progressbar/progbar
+ if (progress)
+ progbar = new(user, delay, target)
+
+ var/endtime = world.time + delay
+ var/starttime = world.time
+ . = 1
+ while (world.time < endtime)
+ stoplag(1)
+ if (progress)
+ progbar.update(world.time - starttime)
+
+ if(drifting && !user.inertia_dir)
+ drifting = 0
+ Uloc = user.loc
+
+ if(QDELETED(user) || user.stat || user.IsKnockdown() || user.IsStun() || (!drifting && user.loc != Uloc) || (extra_checks && !extra_checks.Invoke()))
+ . = 0
+ break
+
+ if(isliving(user))
+ var/mob/living/L = user
+ if(L.recoveringstam)
+ . = 0
+ break
+
+ if(!QDELETED(Tloc) && (QDELETED(target) || Tloc != target.loc))
+ if((Uloc != Tloc || Tloc != user) && !drifting)
+ . = 0
+ break
+
+ if(needhand)
+ //This might seem like an odd check, but you can still need a hand even when it's empty
+ //i.e the hand is used to pull some item/tool out of the construction
+ if(!holdingnull)
+ if(!holding)
+ . = 0
+ break
+ if(user.get_active_held_item() != holding)
+ . = 0
+ break
+ if (progress)
+ qdel(progbar)
+
+/mob/proc/do_after_coefficent() // This gets added to the delay on a do_after, default 1
+ . = 1
+ return
+
+/proc/do_after_mob(mob/user, var/list/targets, time = 30, uninterruptible = 0, progress = 1, datum/callback/extra_checks)
+ if(!user || !targets)
+ return 0
+ if(!islist(targets))
+ targets = list(targets)
+ var/user_loc = user.loc
+
+ var/drifting = 0
+ if(!user.Process_Spacemove(0) && user.inertia_dir)
+ drifting = 1
+
+ var/list/originalloc = list()
+ for(var/atom/target in targets)
+ originalloc[target] = target.loc
+
+ var/holding = user.get_active_held_item()
+ var/datum/progressbar/progbar
+ if(progress)
+ progbar = new(user, time, targets[1])
+
+ var/endtime = world.time + time
+ var/starttime = world.time
+ . = 1
+ mainloop:
+ while(world.time < endtime)
+ stoplag(1)
+ if(progress)
+ progbar.update(world.time - starttime)
+ if(QDELETED(user) || !targets)
+ . = 0
+ break
+ if(uninterruptible)
+ continue
+
+ if(drifting && !user.inertia_dir)
+ drifting = 0
+ user_loc = user.loc
+
+ for(var/atom/target in targets)
+ if((!drifting && user_loc != user.loc) || QDELETED(target) || originalloc[target] != target.loc || user.get_active_held_item() != holding || user.incapacitated() || user.lying || (extra_checks && !extra_checks.Invoke()))
+ . = 0
+ break mainloop
+ if(progbar)
+ qdel(progbar)
+
+/proc/is_species(A, species_datum)
+ . = FALSE
+ if(ishuman(A))
+ var/mob/living/carbon/human/H = A
+ if(H.dna && istype(H.dna.species, species_datum))
+ . = TRUE
+
+/proc/spawn_atom_to_turf(spawn_type, target, amount, admin_spawn=FALSE, list/extra_args)
+ var/turf/T = get_turf(target)
+ if(!T)
+ CRASH("attempt to spawn atom type: [spawn_type] in nullspace")
+
+ var/list/new_args = list(T)
+ if(extra_args)
+ new_args += extra_args
+
+ for(var/j in 1 to amount)
+ var/atom/X = new spawn_type(arglist(new_args))
+ if (admin_spawn)
+ X.flags_1 |= ADMIN_SPAWNED_1
+
+/proc/spawn_and_random_walk(spawn_type, target, amount, walk_chance=100, max_walk=3, always_max_walk=FALSE, admin_spawn=FALSE)
+ var/turf/T = get_turf(target)
+ var/step_count = 0
+ if(!T)
+ CRASH("attempt to spawn atom type: [spawn_type] in nullspace")
+
+ for(var/j in 1 to amount)
+ var/atom/movable/X = new spawn_type(T)
+ if (admin_spawn)
+ X.flags_1 |= ADMIN_SPAWNED_1
+
+ if(always_max_walk || prob(walk_chance))
+ if(always_max_walk)
+ step_count = max_walk
+ else
+ step_count = rand(1, max_walk)
+
+ for(var/i in 1 to step_count)
+ step(X, pick(NORTH, SOUTH, EAST, WEST))
+
+/proc/deadchat_broadcast(message, mob/follow_target=null, turf/turf_target=null, speaker_key=null, message_type=DEADCHAT_REGULAR)
+ message = "[message]"
+ for(var/mob/M in GLOB.player_list)
+ var/datum/preferences/prefs
+ if(M.client && M.client.prefs)
+ prefs = M.client.prefs
+ else
+ prefs = new
+
+ var/override = FALSE
+ if(M.client && M.client.holder && (prefs.chat_toggles & CHAT_DEAD))
+ override = TRUE
+ if(HAS_TRAIT(M, TRAIT_SIXTHSENSE))
+ override = TRUE
+ if(isnewplayer(M) && !override)
+ continue
+ if(M.stat != DEAD && !override)
+ continue
+ if(speaker_key && speaker_key in prefs.ignoring)
+ continue
+
+ switch(message_type)
+ if(DEADCHAT_DEATHRATTLE)
+ if(prefs.toggles & DISABLE_DEATHRATTLE)
+ continue
+ if(DEADCHAT_ARRIVALRATTLE)
+ if(prefs.toggles & DISABLE_ARRIVALRATTLE)
+ continue
+
+ if(isobserver(M))
+ var/rendered_message = message
+
+ if(follow_target)
+ var/F
+ if(turf_target)
+ F = FOLLOW_OR_TURF_LINK(M, follow_target, turf_target)
+ else
+ F = FOLLOW_LINK(M, follow_target)
+ rendered_message = "[F] [message]"
+ else if(turf_target)
+ var/turf_link = TURF_LINK(M, turf_target)
+ rendered_message = "[turf_link] [message]"
+
+ to_chat(M, rendered_message)
+ else
+ to_chat(M, message)
+
+//Used in chemical_mob_spawn. Generates a random mob based on a given gold_core_spawnable value.
+/proc/create_random_mob(spawn_location, mob_class = HOSTILE_SPAWN)
+ var/static/list/mob_spawn_meancritters = list() // list of possible hostile mobs
+ var/static/list/mob_spawn_nicecritters = list() // and possible friendly mobs
+
+ if(mob_spawn_meancritters.len <= 0 || mob_spawn_nicecritters.len <= 0)
+ for(var/T in typesof(/mob/living/simple_animal))
+ var/mob/living/simple_animal/SA = T
+ switch(initial(SA.gold_core_spawnable))
+ if(HOSTILE_SPAWN)
+ mob_spawn_meancritters += T
+ if(FRIENDLY_SPAWN)
+ mob_spawn_nicecritters += T
+
+ var/chosen
+ if(mob_class == FRIENDLY_SPAWN)
+ chosen = pick(mob_spawn_nicecritters)
+ else
+ chosen = pick(mob_spawn_meancritters)
+ var/mob/living/simple_animal/C = new chosen(spawn_location)
+ return C
diff --git a/code/_globalvars/lists/poll_ignore.dm b/code/_globalvars/lists/poll_ignore.dm
index b28ee975..fea595ab 100644
--- a/code/_globalvars/lists/poll_ignore.dm
+++ b/code/_globalvars/lists/poll_ignore.dm
@@ -13,6 +13,8 @@
#define POLL_IGNORE_GOLEM "golem"
#define POLL_IGNORE_SWARMER "swarmer"
#define POLL_IGNORE_DRONE "drone"
+#define POLL_IGNORE_IMAGINARYFRIEND "imaginary_friend"
+#define POLL_IGNORE_SPLITPERSONALITY "split_personality"
#define POLL_IGNORE_DEMON "demon"
#define POLL_IGNORE_WIZARD "wizard"
@@ -30,6 +32,8 @@ GLOBAL_LIST_INIT(poll_ignore_desc, list(
POLL_IGNORE_GOLEM = "Golems",
POLL_IGNORE_SWARMER = "Swarmer shells",
POLL_IGNORE_DRONE = "Drone shells",
+ POLL_IGNORE_IMAGINARYFRIEND = "Imaginary Friend",
+ POLL_IGNORE_SPLITPERSONALITY = "Split Personality",
POLL_IGNORE_DEMON = "Demons",
POLL_IGNORE_WIZARD = "Wizards",
))
diff --git a/code/_onclick/hud/alert.dm b/code/_onclick/hud/alert.dm
index afbe3218..7c135d7b 100644
--- a/code/_onclick/hud/alert.dm
+++ b/code/_onclick/hud/alert.dm
@@ -213,6 +213,12 @@ or something covering your eyes."
desc = "Whoa man, you're tripping balls! Careful you don't get addicted... if you aren't already."
icon_state = "high"
+/obj/screen/alert/hypnosis
+ name = "Hypnosis"
+ desc = "Something's hypnotizing you, but you're not really sure about what."
+ icon_state = "hypnosis"
+ var/phrase
+
/obj/screen/alert/drunk //Not implemented
name = "Drunk"
desc = "All that alcohol you've been drinking is impairing your speech, motor skills, and mental cognition. Make sure to act like it."
diff --git a/code/datums/brain_damage/brain_trauma.dm b/code/datums/brain_damage/brain_trauma.dm
index 56a3f396..1aa1341c 100644
--- a/code/datums/brain_damage/brain_trauma.dm
+++ b/code/datums/brain_damage/brain_trauma.dm
@@ -1,10 +1,12 @@
//Brain Traumas are the new actual brain damage. Brain damage itself acts as a way to acquire traumas: every time brain damage is dealt, there's a chance of receiving a trauma.
//This chance gets higher the higher the mob's brainloss is. Removing traumas is a separate thing from removing brain damage: you can get restored to full brain operativity,
-//but keep the quirks, until repaired by mannitol (for mild/special ones) or brain surgery (for severe ones).
+// but keep the quirks, until repaired by neurine, surgery, lobotomy or magic; depending on the resilience
+// of the trauma.
+
/datum/brain_trauma
var/name = "Brain Trauma"
var/desc = "A trauma caused by brain damage, which causes issues to the patient."
- var/scan_desc = "a generic brain trauma" //description when detected by a health scanner
+ var/scan_desc = "generic brain trauma" //description when detected by a health scanner
var/mob/living/carbon/owner //the poor bastard
var/obj/item/organ/brain/brain //the poor bastard's brain
var/gain_text = "You feel traumatized."
@@ -12,15 +14,21 @@
var/can_gain = TRUE
var/random_gain = TRUE //can this be gained through random traumas?
var/resilience = TRAUMA_RESILIENCE_BASIC //how hard is this to cure?
+ var/clonable = TRUE // will this transfer if the brain is cloned?
/datum/brain_trauma/Destroy()
- brain.traumas -= src
+ if(brain && brain.traumas)
+ brain.traumas -= src
if(owner)
on_lose()
brain = null
owner = null
return ..()
+/datum/brain_trauma/proc/on_clone()
+ if(clonable)
+ return new type
+
//Called on life ticks
/datum/brain_trauma/proc/on_life()
return
@@ -33,17 +41,24 @@
/datum/brain_trauma/proc/on_gain()
to_chat(owner, gain_text)
RegisterSignal(owner, COMSIG_MOB_SAY, .proc/handle_speech)
+ RegisterSignal(owner, COMSIG_MOVABLE_HEAR, .proc/handle_hearing)
//Called when removed from a mob
/datum/brain_trauma/proc/on_lose(silent)
if(!silent)
to_chat(owner, lose_text)
UnregisterSignal(owner, COMSIG_MOB_SAY)
+ UnregisterSignal(owner, COMSIG_MOVABLE_HEAR)
//Called when hearing a spoken message
-/datum/brain_trauma/proc/on_hear(message, speaker, message_language, raw_message, radio_freq)
- return message
+/datum/brain_trauma/proc/handle_hearing(datum/source, list/hearing_args)
+ UnregisterSignal(owner, COMSIG_MOVABLE_HEAR)
//Called when speaking
/datum/brain_trauma/proc/handle_speech(datum/source, list/speech_args)
UnregisterSignal(owner, COMSIG_MOB_SAY)
+
+
+//Called when hugging. expand into generally interacting, where future coders could switch the intent?
+/datum/brain_trauma/proc/on_hug(mob/living/hugger, mob/living/hugged)
+ return
diff --git a/code/datums/brain_damage/hypnosis.dm b/code/datums/brain_damage/hypnosis.dm
index 8909d1b8..f937c196 100644
--- a/code/datums/brain_damage/hypnosis.dm
+++ b/code/datums/brain_damage/hypnosis.dm
@@ -5,6 +5,7 @@
gain_text = ""
lose_text = ""
resilience = TRAUMA_RESILIENCE_SURGERY
+
var/hypnotic_phrase = ""
var/regex/target_phrase
@@ -44,18 +45,17 @@
"You feel a part of your mind repeating this over and over. You need to follow these words.",\
"Something about this sounds... right, for some reason. You feel like you should follow these words.",\
"These words keep echoing in your mind. You find yourself completely fascinated by them.")]")
- if(!HAS_TRAIT(owner, "hypnotherapy"))
- to_chat(owner, "You've been hypnotized by this sentence. You must follow these words. If it isn't a clear order, you can freely interpret how to do so,\
+ to_chat(owner, "You've been hypnotized by this sentence. You must follow these words. If it isn't a clear order, you can freely interpret how to do so,\
as long as you act like the words are your highest priority.")
- else
- to_chat(owner, "You've been hypnotized by this sentence. You feel an incredible desire to follow these words, but are able to resist it somewhat. If it isn't a clear order, you can freely interpret how to do so,\
- however this does not take precedence over your other objectives.")
+ var/obj/screen/alert/hypnosis/hypno_alert = owner.throw_alert("hypnosis", /obj/screen/alert/hypnosis)
+ hypno_alert.desc = "\"[hypnotic_phrase]\"... your mind seems to be fixated on this concept."
..()
/datum/brain_trauma/hypnosis/on_lose()
message_admins("[ADMIN_LOOKUPFLW(owner)] is no longer hypnotized with the phrase '[hypnotic_phrase]'.")
log_game("[key_name(owner)] is no longer hypnotized with the phrase '[hypnotic_phrase]'.")
- to_chat(owner, "You suddenly snap out of your fixation. The phrase '[hypnotic_phrase]' no longer feels important to you.")
+ to_chat(owner, "You suddenly snap out of your hypnosis. The phrase '[hypnotic_phrase]' no longer feels important to you.")
+ owner.clear_alert("hypnosis")
..()
/datum/brain_trauma/hypnosis/on_life()
@@ -67,6 +67,5 @@
if(2)
new /datum/hallucination/chat(owner, TRUE, FALSE, "[hypnotic_phrase]")
-/datum/brain_trauma/hypnosis/on_hear(message, speaker, message_language, raw_message, radio_freq)
- message = target_phrase.Replace(message, "$1")
- return message
+/datum/brain_trauma/hypnosis/handle_hearing(datum/source, list/hearing_args)
+ hearing_args[HEARING_MESSAGE] = target_phrase.Replace(hearing_args[HEARING_MESSAGE], "$1")
diff --git a/code/datums/brain_damage/imaginary_friend.dm b/code/datums/brain_damage/imaginary_friend.dm
index 912dd0dd..47a96e6e 100644
--- a/code/datums/brain_damage/imaginary_friend.dm
+++ b/code/datums/brain_damage/imaginary_friend.dm
@@ -8,6 +8,10 @@
var/friend_initialized = FALSE
/datum/brain_trauma/special/imaginary_friend/on_gain()
+ var/mob/living/M = owner
+ if(M.stat == DEAD || !M.client)
+ qdel(src)
+ return
..()
make_friend()
get_ghost()
@@ -43,7 +47,7 @@
/datum/brain_trauma/special/imaginary_friend/proc/get_ghost()
set waitfor = FALSE
- var/list/mob/dead/observer/candidates = pollCandidatesForMob("Do you want to play as [owner]'s imaginary friend?", ROLE_PAI, null, null, 75, friend)
+ var/list/mob/dead/observer/candidates = pollCandidatesForMob("Do you want to play as [owner]'s imaginary friend?", ROLE_PAI, null, null, 75, friend, POLL_IGNORE_IMAGINARYFRIEND)
if(LAZYLEN(candidates))
var/mob/dead/observer/C = pick(candidates)
friend.key = C.key
@@ -74,26 +78,34 @@
/mob/camera/imaginary_friend/Login()
..()
- to_chat(src, "You are the imaginary friend of [owner]!")
- to_chat(src, "You are absolutely loyal to your friend, no matter what.")
- to_chat(src, "You cannot directly influence the world around you, but you can see what [owner] cannot.")
+ greet()
Show()
+/mob/camera/imaginary_friend/proc/greet()
+ to_chat(src, "You are the imaginary friend of [owner]!")
+ to_chat(src, "You are absolutely loyal to your friend, no matter what.")
+ to_chat(src, "You cannot directly influence the world around you, but you can see what [owner] cannot.")
+
/mob/camera/imaginary_friend/Initialize(mapload, _trauma)
. = ..()
- var/gender = pick(MALE, FEMALE)
- real_name = random_unique_name(gender)
- name = real_name
+
trauma = _trauma
owner = trauma.owner
copy_known_languages_from(owner, TRUE)
- human_image = get_flat_human_icon(null, pick(SSjob.occupations))
+
+ setup_friend()
join = new
join.Grant(src)
hide = new
hide.Grant(src)
+/mob/camera/imaginary_friend/proc/setup_friend()
+ var/gender = pick(MALE, FEMALE)
+ real_name = random_unique_name(gender)
+ name = real_name
+ human_image = get_flat_human_icon(null, pick(SSjob.occupations))
+
/mob/camera/imaginary_friend/proc/Show()
if(!client) //nobody home
return
@@ -132,7 +144,7 @@
if(client.prefs.muted & MUTE_IC)
to_chat(src, "You cannot send IC messages (muted).")
return
- if (src.client.handle_spam_prevention(message,MUTE_IC))
+ if (!(ignore_spam || forced) && src.client.handle_spam_prevention(message,MUTE_IC))
return
friend_talk(message)
@@ -220,4 +232,42 @@
var/mob/camera/imaginary_friend/I = owner
I.hidden = !I.hidden
I.Show()
- update_status()
\ No newline at end of file
+ update_status()
+
+//down here is the trapped mind
+//like imaginary friend but a lot less imagination and more like mind prison//
+
+/datum/brain_trauma/special/imaginary_friend/trapped_owner
+ name = "Trapped Victim"
+ desc = "Patient appears to be targeted by an invisible entity."
+ gain_text = ""
+ lose_text = ""
+ random_gain = FALSE
+
+/datum/brain_trauma/special/imaginary_friend/trapped_owner/make_friend()
+ friend = new /mob/camera/imaginary_friend/trapped(get_turf(owner), src)
+
+/datum/brain_trauma/special/imaginary_friend/trapped_owner/reroll_friend() //no rerolling- it's just the last owner's hell
+ if(friend.client) //reconnected
+ return
+ friend_initialized = FALSE
+ QDEL_NULL(friend)
+ qdel(src)
+
+/datum/brain_trauma/special/imaginary_friend/trapped_owner/get_ghost() //no randoms
+ return
+
+/mob/camera/imaginary_friend/trapped
+ name = "figment of imagination?"
+ real_name = "figment of imagination?"
+ desc = "The previous host of this body."
+
+/mob/camera/imaginary_friend/trapped/greet()
+ to_chat(src, "You have managed to hold on as a figment of the new host's imagination!")
+ to_chat(src, "All hope is lost for you, but at least you may interact with your host. You do not have to be loyal to them.")
+ to_chat(src, "You cannot directly influence the world around you, but you can see what the host cannot.")
+
+/mob/camera/imaginary_friend/trapped/setup_friend()
+ real_name = "[owner.real_name]?"
+ name = real_name
+ human_image = icon('icons/mob/lavaland/lavaland_monsters.dmi', icon_state = "curseblob")
diff --git a/code/datums/brain_damage/mild.dm b/code/datums/brain_damage/mild.dm
index c049a7db..012f771a 100644
--- a/code/datums/brain_damage/mild.dm
+++ b/code/datums/brain_damage/mild.dm
@@ -64,21 +64,21 @@
name = "Speech Impediment"
desc = "Patient is unable to form coherent sentences."
scan_desc = "communication disorder"
- gain_text = "" //mutation will handle the text
- lose_text = ""
+ gain_text = "You can't seem to form any coherent thoughts!"
+ lose_text = "Your mind feels more clear."
/datum/brain_trauma/mild/speech_impediment/on_gain()
ADD_TRAIT(owner, TRAIT_UNINTELLIGIBLE_SPEECH, TRAUMA_TRAIT)
- . = ..()
+ ..()
/datum/brain_trauma/mild/speech_impediment/on_lose()
REMOVE_TRAIT(owner, TRAIT_UNINTELLIGIBLE_SPEECH, TRAUMA_TRAIT)
- . = ..()
+ ..()
/datum/brain_trauma/mild/concussion
name = "Concussion"
desc = "Patient's brain is concussed."
- scan_desc = "a concussion"
+ scan_desc = "concussion"
gain_text = "Your head hurts!"
lose_text = "The pressure inside your head starts fading."
@@ -157,54 +157,108 @@
gain_text = "Your muscles feel oddly faint."
lose_text = "You feel in control of your muscles again."
-/datum/brain_trauma/mild/muscle_spasms/on_life()
- if(prob(7))
- switch(rand(1,5))
- if(1)
- if(owner.canmove && !isspaceturf(owner.loc))
- to_chat(owner, "Your leg spasms!")
- step(owner, pick(GLOB.cardinals))
- if(2)
- if(owner.incapacitated())
- return
- var/obj/item/I = owner.get_active_held_item()
- if(I)
- to_chat(owner, "Your fingers spasm!")
- owner.log_message("used [I] due to a Muscle Spasm", LOG_ATTACK)
- I.attack_self(owner)
- if(3)
- var/prev_intent = owner.a_intent
- owner.a_intent = INTENT_HARM
-
- var/range = 1
- if(istype(owner.get_active_held_item(), /obj/item/gun)) //get targets to shoot at
- range = 7
-
- var/list/mob/living/targets = list()
- for(var/mob/M in oview(owner, range))
- if(isliving(M))
- targets += M
- if(LAZYLEN(targets))
- to_chat(owner, "Your arm spasms!")
- owner.log_message(" attacked someone due to a Muscle Spasm") //the following attack will log itself
- owner.ClickOn(pick(targets))
- owner.a_intent = prev_intent
- if(4)
- var/prev_intent = owner.a_intent
- owner.a_intent = INTENT_HARM
- to_chat(owner, "Your arm spasms!")
- owner.log_message("attacked [owner.p_them()]self to a Muscle Spasm", LOG_ATTACK)
- owner.ClickOn(owner)
- owner.a_intent = prev_intent
- if(5)
- if(owner.incapacitated())
- return
- var/obj/item/I = owner.get_active_held_item()
- var/list/turf/targets = list()
- for(var/turf/T in oview(owner, 3))
- targets += T
- if(LAZYLEN(targets) && I)
- to_chat(owner, "Your arm spasms!")
- owner.log_message("threw [I] due to a Muscle Spasm", LOG_ATTACK)
- owner.throw_item(pick(targets))
+/datum/brain_trauma/mild/muscle_spasms/on_gain()
+ owner.apply_status_effect(STATUS_EFFECT_SPASMS)
..()
+
+/datum/brain_trauma/mild/muscle_spasms/on_lose()
+ owner.remove_status_effect(STATUS_EFFECT_SPASMS)
+ ..()
+
+/datum/brain_trauma/mild/nervous_cough
+ name = "Nervous Cough"
+ desc = "Patient feels a constant need to cough."
+ scan_desc = "nervous cough"
+ gain_text = "Your throat itches incessantly..."
+ lose_text = "Your throat stops itching."
+
+/datum/brain_trauma/mild/nervous_cough/on_life()
+ if(prob(12) && !HAS_TRAIT(owner, TRAIT_SOOTHED_THROAT))
+ if(prob(5))
+ to_chat(owner, "[pick("You have a coughing fit!", "You can't stop coughing!")]")
+ owner.Stun(20)
+ owner.emote("cough")
+ addtimer(CALLBACK(owner, /mob/.proc/emote, "cough"), 6)
+ addtimer(CALLBACK(owner, /mob/.proc/emote, "cough"), 12)
+ owner.emote("cough")
+ ..()
+
+/datum/brain_trauma/mild/expressive_aphasia
+ name = "Expressive Aphasia"
+ desc = "Patient is affected by partial loss of speech leading to a reduced vocabulary."
+ scan_desc = "inability to form complex sentences"
+ gain_text = "You lose your grasp on complex words."
+ lose_text = "You feel your vocabulary returning to normal again."
+
+ var/static/list/common_words = world.file2list("strings/1000_most_common.txt")
+
+/datum/brain_trauma/mild/expressive_aphasia/handle_speech(datum/source, list/speech_args)
+ var/message = speech_args[SPEECH_MESSAGE]
+ if(message)
+ var/list/message_split = splittext(message, " ")
+ var/list/new_message = list()
+
+ for(var/word in message_split)
+ var/suffix = copytext(word,-1)
+
+ // Check if we have a suffix and break it out of the word
+ if(suffix in list("." , "," , ";" , "!" , ":" , "?"))
+ word = copytext(word,1,-1)
+ else
+ suffix = ""
+
+ word = html_decode(word)
+
+ if(lowertext(word) in common_words)
+ new_message += word + suffix
+ else
+ if(prob(30) && message_split.len > 2)
+ new_message += pick("uh","erm")
+ break
+ else
+ var/list/charlist = string2charlist(word) // Stupid shit code
+ shuffle_inplace(charlist)
+ charlist.len = round(charlist.len * 0.5,1)
+ new_message += html_encode(jointext(charlist,"")) + suffix
+
+ message = jointext(new_message, " ")
+
+ speech_args[SPEECH_MESSAGE] = trim(message)
+
+/datum/brain_trauma/mild/mind_echo
+ name = "Mind Echo"
+ desc = "Patient's language neurons do not terminate properly, causing previous speech patterns to occasionally resurface spontaneously."
+ scan_desc = "looping neural pattern"
+ gain_text = "You feel a faint echo of your thoughts..."
+ lose_text = "The faint echo fades away."
+ var/list/hear_dejavu = list()
+ var/list/speak_dejavu = list()
+
+/datum/brain_trauma/mild/mind_echo/handle_hearing(datum/source, list/hearing_args)
+ if(owner == hearing_args[HEARING_SPEAKER])
+ return
+ if(hear_dejavu.len >= 5)
+ if(prob(25))
+ var/deja_vu = pick_n_take(hear_dejavu)
+ var/static/regex/quoted_spoken_message = regex("\".+\"", "gi")
+ hearing_args[HEARING_MESSAGE] = quoted_spoken_message.Replace(hearing_args[HEARING_MESSAGE], "\"[deja_vu]\"") //Quotes included to avoid cases where someone says part of their name
+ return
+ if(hear_dejavu.len >= 15)
+ if(prob(50))
+ popleft(hear_dejavu) //Remove the oldest
+ hear_dejavu += hearing_args[HEARING_RAW_MESSAGE]
+ else
+ hear_dejavu += hearing_args[HEARING_RAW_MESSAGE]
+
+/datum/brain_trauma/mild/mind_echo/handle_speech(datum/source, list/speech_args)
+ if(speak_dejavu.len >= 5)
+ if(prob(25))
+ var/deja_vu = pick_n_take(speak_dejavu)
+ speech_args[SPEECH_MESSAGE] = deja_vu
+ return
+ if(speak_dejavu.len >= 15)
+ if(prob(50))
+ popleft(speak_dejavu) //Remove the oldest
+ speak_dejavu += speech_args[SPEECH_MESSAGE]
+ else
+ speak_dejavu += speech_args[SPEECH_MESSAGE]
\ No newline at end of file
diff --git a/code/datums/brain_damage/phobia.dm b/code/datums/brain_damage/phobia.dm
index f802555c..034f2baa 100644
--- a/code/datums/brain_damage/phobia.dm
+++ b/code/datums/brain_damage/phobia.dm
@@ -2,8 +2,8 @@
name = "Phobia"
desc = "Patient is unreasonably afraid of something."
scan_desc = "phobia"
- gain_text = ""
- lose_text = ""
+ gain_text = "You start finding default values very unnerving..."
+ lose_text = "You no longer feel afraid of default values."
var/phobia_type
var/next_check = 0
var/next_scare = 0
@@ -14,8 +14,10 @@
var/list/trigger_turfs
var/list/trigger_species
-/datum/brain_trauma/mild/phobia/New(specific_type)
- phobia_type = specific_type
+/datum/brain_trauma/mild/phobia/New(new_phobia_type)
+ if(new_phobia_type)
+ phobia_type = new_phobia_type
+
if(!phobia_type)
phobia_type = pick(SStraumas.phobia_types)
@@ -29,6 +31,11 @@
trigger_species = SStraumas.phobia_species[phobia_type]
..()
+
+/datum/brain_trauma/mild/phobia/on_clone()
+ if(clonable)
+ return new type(phobia_type)
+
/datum/brain_trauma/mild/phobia/on_life()
..()
if(HAS_TRAIT(owner, TRAIT_FEARLESS))
@@ -44,6 +51,12 @@
if(is_type_in_typecache(O, trigger_objs))
freak_out(O)
return
+ for(var/mob/living/carbon/human/HU in seen_atoms) //check equipment for trigger items
+ for(var/X in HU.get_all_slots() | HU.held_items)
+ var/obj/I = X
+ if(!QDELETED(I) && is_type_in_typecache(I, trigger_objs))
+ freak_out(I)
+ return
if(LAZYLEN(trigger_turfs))
for(var/turf/T in seen_atoms)
@@ -51,45 +64,41 @@
freak_out(T)
return
- if(LAZYLEN(trigger_mobs) || LAZYLEN(trigger_objs))
+ seen_atoms -= owner //make sure they aren't afraid of themselves.
+ if(LAZYLEN(trigger_mobs) || LAZYLEN(trigger_species))
for(var/mob/M in seen_atoms)
if(is_type_in_typecache(M, trigger_mobs))
freak_out(M)
return
- else if(ishuman(M)) //check their equipment for trigger items
+ else if(ishuman(M)) //check their species
var/mob/living/carbon/human/H = M
if(LAZYLEN(trigger_species) && H.dna && H.dna.species && is_type_in_typecache(H.dna.species, trigger_species))
freak_out(H)
+ return
- for(var/X in H.get_all_slots() | H.held_items)
- var/obj/I = X
- if(!QDELETED(I) && is_type_in_typecache(I, trigger_objs))
- freak_out(I)
- return
-
-/datum/brain_trauma/mild/phobia/on_hear(message, speaker, message_language, raw_message, radio_freq)
+/datum/brain_trauma/mild/phobia/handle_hearing(datum/source, list/hearing_args)
if(!owner.can_hear() || world.time < next_scare) //words can't trigger you if you can't hear them *taps head*
- return message
+ return
if(HAS_TRAIT(owner, TRAIT_FEARLESS))
- return message
+ return
for(var/word in trigger_words)
- var/reg = regex("(\\b|\\A)[REGEX_QUOTE(word)]'?s*(\\b|\\Z)", "i")
+ var/regex/reg = regex("(\\b|\\A)[REGEX_QUOTE(word)]'?s*(\\b|\\Z)", "i")
- if(findtext(raw_message, reg))
+ if(findtext(hearing_args[HEARING_RAW_MESSAGE], reg))
addtimer(CALLBACK(src, .proc/freak_out, null, word), 10) //to react AFTER the chat message
+ hearing_args[HEARING_MESSAGE] = reg.Replace(hearing_args[HEARING_MESSAGE], "$1")
break
- return message
/datum/brain_trauma/mild/phobia/handle_speech(datum/source, list/speech_args)
if(HAS_TRAIT(owner, TRAIT_FEARLESS))
return
for(var/word in trigger_words)
- var/reg = regex("(\\b|\\A)[REGEX_QUOTE(word)]'?s*(\\b|\\Z)", "i")
+ var/regex/reg = regex("(\\b|\\A)[REGEX_QUOTE(word)]'?s*(\\b|\\Z)", "i")
if(findtext(speech_args[SPEECH_MESSAGE], reg))
- to_chat(owner, "You can't bring yourself to say the word \"[word]\"!")
+ to_chat(owner, "You can't bring yourself to say the word \"[word]\"!")
speech_args[SPEECH_MESSAGE] = ""
/datum/brain_trauma/mild/phobia/proc/freak_out(atom/reason, trigger_word)
@@ -125,6 +134,76 @@
owner.Jitter(10)
owner.stuttering += 10
+// Defined phobia types for badminry, not included in the RNG trauma pool to avoid diluting.
+
+/datum/brain_trauma/mild/phobia/spiders
+ phobia_type = "spiders"
+ random_gain = FALSE
+
+/datum/brain_trauma/mild/phobia/space
+ phobia_type = "space"
+ random_gain = FALSE
+
/datum/brain_trauma/mild/phobia/security
phobia_type = "security"
random_gain = FALSE
+
+/datum/brain_trauma/mild/phobia/clowns
+ phobia_type = "clowns"
+ random_gain = FALSE
+
+/datum/brain_trauma/mild/phobia/greytide
+ phobia_type = "greytide"
+ random_gain = FALSE
+
+/datum/brain_trauma/mild/phobia/lizards
+ phobia_type = "lizards"
+ random_gain = FALSE
+
+/datum/brain_trauma/mild/phobia/skeletons
+ phobia_type = "skeletons"
+ random_gain = FALSE
+
+/datum/brain_trauma/mild/phobia/snakes
+ phobia_type = "snakes"
+ random_gain = FALSE
+
+/datum/brain_trauma/mild/phobia/robots
+ phobia_type = "robots"
+ random_gain = FALSE
+
+/datum/brain_trauma/mild/phobia/doctors
+ phobia_type = "doctors"
+ random_gain = FALSE
+
+/datum/brain_trauma/mild/phobia/authority
+ phobia_type = "authority"
+ random_gain = FALSE
+
+/datum/brain_trauma/mild/phobia/supernatural
+ phobia_type = "the supernatural"
+ random_gain = FALSE
+
+/datum/brain_trauma/mild/phobia/aliens
+ phobia_type = "aliens"
+ random_gain = FALSE
+
+/datum/brain_trauma/mild/phobia/strangers
+ phobia_type = "strangers"
+ random_gain = FALSE
+
+/datum/brain_trauma/mild/phobia/birds
+ phobia_type = "birds"
+ random_gain = FALSE
+
+/datum/brain_trauma/mild/phobia/falling
+ phobia_type = "falling"
+ random_gain = FALSE
+
+/datum/brain_trauma/mild/phobia/anime
+ phobia_type = "anime"
+ random_gain = FALSE
+
+/datum/brain_trauma/mild/phobia/conspiracies
+ phobia_type = "conspiracies"
+ random_gain = FALSE
diff --git a/code/datums/brain_damage/severe.dm b/code/datums/brain_damage/severe.dm
index 890e9cf9..0e08c4cd 100644
--- a/code/datums/brain_damage/severe.dm
+++ b/code/datums/brain_damage/severe.dm
@@ -119,7 +119,7 @@
owner.update_disabled_bodyparts()
/datum/brain_trauma/severe/paralysis/paraplegic
- //can_gain = FALSE maybe breaks.
+ random_gain = FALSE
paralysis_type = "legs"
resilience = TRAUMA_RESILIENCE_ABSOLUTE
@@ -149,7 +149,7 @@
/datum/brain_trauma/severe/monophobia
name = "Monophobia"
desc = "Patient feels sick and distressed when not around other people, leading to potentially lethal levels of stress."
- scan_desc = "severe monophobia"
+ scan_desc = "monophobia"
gain_text = ""
lose_text = "You feel like you could be safe on your own."
var/stress = 0
@@ -168,7 +168,7 @@
if(stress > 10 && (prob(5)))
stress_reaction()
else
- stress -= 4
+ stress = max(stress - 4, 0)
/datum/brain_trauma/severe/monophobia/proc/check_alone()
if(HAS_TRAIT(owner, TRAIT_BLIND))
diff --git a/code/datums/brain_damage/special.dm b/code/datums/brain_damage/special.dm
index d9e6f006..b52c7d39 100644
--- a/code/datums/brain_damage/special.dm
+++ b/code/datums/brain_damage/special.dm
@@ -22,6 +22,14 @@
else
speak("neutral", prob(25))
+/datum/brain_trauma/special/godwoken/on_gain()
+ ADD_TRAIT(owner, TRAIT_HOLY, TRAUMA_TRAIT)
+ ..()
+
+/datum/brain_trauma/special/godwoken/on_lose()
+ REMOVE_TRAIT(owner, TRAIT_HOLY, TRAUMA_TRAIT)
+ ..()
+
/datum/brain_trauma/special/godwoken/proc/speak(type, include_owner = FALSE)
var/message
switch(type)
@@ -36,7 +44,7 @@
else
message = pick_list_replacements(BRAIN_DAMAGE_FILE, "god_neutral")
- playsound(get_turf(owner), 'sound/magic/clockwork/invoke_general.ogg', 200, 1, 5)
+ playsound(get_turf(owner), 'sound/magic/clockwork/invoke_general.ogg', 200, TRUE, 5)
voice_of_god(message, owner, list("colossus","yell"), 2.5, include_owner, FALSE)
/datum/brain_trauma/special/bluespace_prophet
@@ -134,7 +142,101 @@
/datum/brain_trauma/special/psychotic_brawling/bath_salts
name = "Chemical Violent Psychosis"
- random_gain = FALSE
+ clonable = FALSE
+
+/datum/brain_trauma/special/tenacity
+ name = "Tenacity"
+ desc = "Patient is psychologically unaffected by pain and injuries, and can remain standing far longer than a normal person."
+ scan_desc = "traumatic neuropathy"
+ gain_text = "You suddenly stop feeling pain."
+ lose_text = "You realize you can feel pain again."
+
+/datum/brain_trauma/special/tenacity/on_gain()
+ ADD_TRAIT(owner, TRAIT_NOSOFTCRIT, TRAUMA_TRAIT)
+ ADD_TRAIT(owner, TRAIT_NOHARDCRIT, TRAUMA_TRAIT)
+ ..()
+
+/datum/brain_trauma/special/tenacity/on_lose()
+ REMOVE_TRAIT(owner, TRAIT_NOSOFTCRIT, TRAUMA_TRAIT)
+ REMOVE_TRAIT(owner, TRAIT_NOHARDCRIT, TRAUMA_TRAIT)
+ ..()
+
+/datum/brain_trauma/special/death_whispers
+ name = "Functional Cerebral Necrosis"
+ desc = "Patient's brain is stuck in a functional near-death state, causing occasional moments of lucid hallucinations, which are often interpreted as the voices of the dead."
+ scan_desc = "chronic functional necrosis"
+ gain_text = "You feel dead inside."
+ lose_text = "You feel alive again."
+ var/active = FALSE
+
+/datum/brain_trauma/special/death_whispers/on_life()
+ ..()
+ if(!active && prob(2))
+ whispering()
+
+/datum/brain_trauma/special/death_whispers/on_lose()
+ if(active)
+ cease_whispering()
+ ..()
+
+/datum/brain_trauma/special/death_whispers/proc/whispering()
+ ADD_TRAIT(owner, TRAIT_SIXTHSENSE, TRAUMA_TRAIT)
+ active = TRUE
+ addtimer(CALLBACK(src, .proc/cease_whispering), rand(50, 300))
+
+/datum/brain_trauma/special/death_whispers/proc/cease_whispering()
+ REMOVE_TRAIT(owner, TRAIT_SIXTHSENSE, TRAUMA_TRAIT)
+ active = FALSE
+
+/datum/brain_trauma/special/existential_crisis
+ name = "Existential Crisis"
+ desc = "Patient's hold on reality becomes faint, causing occasional bouts of non-existence."
+ scan_desc = "existential crisis"
+ gain_text = "You feel less real."
+ lose_text = "You feel more substantial again."
+ var/obj/effect/abstract/sync_holder/veil/veil
+ var/next_crisis = 0
+
+/datum/brain_trauma/special/existential_crisis/on_life()
+ ..()
+ if(!veil && world.time > next_crisis && prob(3))
+ if(isturf(owner.loc))
+ fade_out()
+
+/datum/brain_trauma/special/existential_crisis/on_lose()
+ if(veil)
+ fade_in()
+ ..()
+
+/datum/brain_trauma/special/existential_crisis/proc/fade_out()
+ if(veil)
+ return
+ var/duration = rand(50, 450)
+ veil = new(owner.drop_location())
+ to_chat(owner, "[pick("You stop thinking for a moment. Therefore you are not.",\
+ "To be or not to be...",\
+ "Why exist?",\
+ "You stop keeping it real.",\
+ "Your grip on existence slips.",\
+ "Do you even exist?",\
+ "You simply fade away.")]")
+ owner.forceMove(veil)
+ SEND_SIGNAL(owner, COMSIG_MOVABLE_SECLUDED_LOCATION)
+ for(var/thing in owner)
+ var/atom/movable/AM = thing
+ SEND_SIGNAL(AM, COMSIG_MOVABLE_SECLUDED_LOCATION)
+ next_crisis = world.time + 600
+ addtimer(CALLBACK(src, .proc/fade_in), duration)
+
+/datum/brain_trauma/special/existential_crisis/proc/fade_in()
+ QDEL_NULL(veil)
+ to_chat(owner, "You fade back into reality.")
+ next_crisis = world.time + 600
+
+//base sync holder is in desynchronizer.dm
+/obj/effect/abstract/sync_holder/veil
+ name = "non-existence"
+ desc = "Existence is just a state of mind."
/datum/brain_trauma/special/beepsky
name = "Criminal"
@@ -142,6 +244,7 @@
scan_desc = "criminal mind"
gain_text = "Justice is coming for you."
lose_text = "You were absolved for your crimes."
+ clonable = FALSE
random_gain = FALSE
var/obj/effect/hallucination/simple/securitron/beepsky
@@ -201,4 +304,4 @@
/obj/effect/hallucination/simple/securitron/Destroy()
STOP_PROCESSING(SSfastprocess,src)
- return ..()
\ No newline at end of file
+ return ..()
diff --git a/code/datums/brain_damage/split_personality.dm b/code/datums/brain_damage/split_personality.dm
index 653b8a98..87665961 100644
--- a/code/datums/brain_damage/split_personality.dm
+++ b/code/datums/brain_damage/split_personality.dm
@@ -13,6 +13,10 @@
var/mob/living/split_personality/owner_backseat
/datum/brain_trauma/severe/split_personality/on_gain()
+ var/mob/living/M = owner
+ if(M.stat == DEAD) //No use assigning people to a corpse
+ qdel(src)
+ return
..()
make_backseats()
get_ghost()
@@ -23,7 +27,7 @@
/datum/brain_trauma/severe/split_personality/proc/get_ghost()
set waitfor = FALSE
- var/list/mob/dead/observer/candidates = pollCandidatesForMob("Do you want to play as [owner]'s split personality?", ROLE_PAI, null, null, 75, stranger_backseat)
+ var/list/mob/dead/observer/candidates = pollCandidatesForMob("Do you want to play as [owner]'s split personality?", ROLE_PAI, null, null, 75, stranger_backseat, POLL_IGNORE_SPLITPERSONALITY)
if(LAZYLEN(candidates))
var/mob/dead/observer/C = pick(candidates)
stranger_backseat.key = C.key
@@ -191,13 +195,13 @@
/datum/brain_trauma/severe/split_personality/brainwashing/on_life()
return //no random switching
-/datum/brain_trauma/severe/split_personality/brainwashing/on_hear(message, speaker, message_language, raw_message, radio_freq)
- if(HAS_TRAIT(owner, TRAIT_DEAF) || owner == speaker)
- return message
+/datum/brain_trauma/severe/split_personality/brainwashing/handle_hearing(datum/source, list/hearing_args)
+ if(HAS_TRAIT(owner, TRAIT_DEAF) || owner == hearing_args[HEARING_SPEAKER])
+ return
+ var/message = hearing_args[HEARING_MESSAGE]
if(findtext(message, codeword))
- message = replacetext(message, codeword, "[codeword]")
+ hearing_args[HEARING_MESSAGE] = replacetext(message, codeword, "[codeword]")
addtimer(CALLBACK(src, /datum/brain_trauma/severe/split_personality.proc/switch_personalities), 10)
- return message
/datum/brain_trauma/severe/split_personality/brainwashing/handle_speech(datum/source, list/speech_args)
if(findtext(speech_args[SPEECH_MESSAGE], codeword))
diff --git a/code/datums/components/nanites.dm b/code/datums/components/nanites.dm
index ef52a8fc..11a260d2 100644
--- a/code/datums/components/nanites.dm
+++ b/code/datums/components/nanites.dm
@@ -53,7 +53,6 @@
RegisterSignal(parent, COMSIG_MOB_ALLOWED, .proc/check_access)
RegisterSignal(parent, COMSIG_LIVING_ELECTROCUTE_ACT, .proc/on_shock)
RegisterSignal(parent, COMSIG_LIVING_MINOR_SHOCK, .proc/on_minor_shock)
- RegisterSignal(parent, COMSIG_MOVABLE_HEAR, .proc/on_hear)
RegisterSignal(parent, COMSIG_SPECIES_GAIN, .proc/check_viable_biotype)
RegisterSignal(parent, COMSIG_NANITE_SIGNAL, .proc/receive_signal)
@@ -191,11 +190,6 @@
var/datum/nanite_program/NP = X
NP.on_death(gibbed)
-/datum/component/nanites/proc/on_hear(datum/source, message, atom/movable/speaker, message_language, raw_message, radio_freq, list/spans, message_mode)
- for(var/X in programs)
- var/datum/nanite_program/NP = X
- NP.on_hear(message, speaker, message_language, raw_message, radio_freq, spans, message_mode)
-
/datum/component/nanites/proc/receive_signal(datum/source, code, source = "an unidentified source")
for(var/X in programs)
var/datum/nanite_program/NP = X
diff --git a/code/datums/components/stationloving.dm b/code/datums/components/stationloving.dm
index bbcb0d88..91928656 100644
--- a/code/datums/components/stationloving.dm
+++ b/code/datums/components/stationloving.dm
@@ -8,6 +8,7 @@
if(!ismovableatom(parent))
return COMPONENT_INCOMPATIBLE
RegisterSignal(parent, list(COMSIG_MOVABLE_Z_CHANGED), .proc/check_in_bounds)
+ RegisterSignal(parent, list(COMSIG_MOVABLE_SECLUDED_LOCATION), .proc/relocate)
RegisterSignal(parent, list(COMSIG_PARENT_PREQDELETED), .proc/check_deletion)
RegisterSignal(parent, list(COMSIG_ITEM_IMBUE_SOUL), .proc/check_soul_imbue)
src.inform_admins = inform_admins
@@ -32,6 +33,7 @@
var/atom/movable/AM = parent
AM.forceMove(targetturf)
+ to_chat(get(parent, /mob), "You can't help but feel that you just lost something back there...")
// move the disc, so ghosts remain orbiting it even if it's "destroyed"
return targetturf
@@ -40,7 +42,6 @@
return
else
var/turf/currentturf = get_turf(src)
- to_chat(get(parent, /mob), "You can't help but feel that you just lost something back there...")
var/turf/targetturf = relocate()
log_game("[parent] has been moved out of bounds in [loc_name(currentturf)]. Moving it to [loc_name(targetturf)].")
if(inform_admins)
diff --git a/code/datums/status_effects/debuffs.dm b/code/datums/status_effects/debuffs.dm
index 20bba021..a9ba87b1 100644
--- a/code/datums/status_effects/debuffs.dm
+++ b/code/datums/status_effects/debuffs.dm
@@ -635,8 +635,7 @@ datum/status_effect/pacify
tick_interval = 10
examine_text = "SUBJECTPRONOUN seems slow and unfocused."
var/stun = TRUE
- var/triggered = FALSE
- alert_type = null
+ alert_type = /obj/screen/alert/status_effect/trance
/obj/screen/alert/status_effect/trance
name = "Trance"
@@ -644,17 +643,6 @@ datum/status_effect/pacify
icon_state = "high"
/datum/status_effect/trance/tick()
- if(HAS_TRAIT(owner, "hypnotherapy"))
- if(triggered == TRUE)
- UnregisterSignal(owner, COMSIG_MOVABLE_HEAR)
- RegisterSignal(owner, COMSIG_MOVABLE_HEAR, .proc/hypnotize)
- ADD_TRAIT(owner, TRAIT_MUTE, "trance")
- if(!owner.has_quirk(/datum/quirk/monochromatic))
- owner.add_client_colour(/datum/client_colour/monochrome)
- to_chat(owner, "[pick("You feel your thoughts slow down...", "You suddenly feel extremely dizzy...", "You feel like you're in the middle of a dream...","You feel incredibly relaxed...")]")
- triggered = FALSE
- else
- return
if(stun)
owner.Stun(60, TRUE, TRUE)
owner.dizziness = 20
@@ -662,48 +650,33 @@ datum/status_effect/pacify
/datum/status_effect/trance/on_apply()
if(!iscarbon(owner))
return FALSE
- if(HAS_TRAIT(owner, "hypnotherapy"))
- RegisterSignal(owner, COMSIG_MOVABLE_HEAR, .proc/listen)
- return TRUE
- alert_type = /obj/screen/alert/status_effect/trance
RegisterSignal(owner, COMSIG_MOVABLE_HEAR, .proc/hypnotize)
ADD_TRAIT(owner, TRAIT_MUTE, "trance")
- if(!owner.has_quirk(/datum/quirk/monochromatic))
- owner.add_client_colour(/datum/client_colour/monochrome)
+ owner.add_client_colour(/datum/client_colour/monochrome/trance)
owner.visible_message("[stun ? "[owner] stands still as [owner.p_their()] eyes seem to focus on a distant point." : ""]", \
"[pick("You feel your thoughts slow down...", "You suddenly feel extremely dizzy...", "You feel like you're in the middle of a dream...","You feel incredibly relaxed...")]")
return TRUE
-/datum/status_effect/trance/on_creation(mob/living/new_owner, _duration, _stun = TRUE, source_quirk = FALSE)//hypnoquirk makes no visible message, prevents self antag messages, and places phrase below objectives.
+/datum/status_effect/trance/on_creation(mob/living/new_owner, _duration, _stun = TRUE)
duration = _duration
stun = _stun
- if(source_quirk == FALSE && HAS_TRAIT(owner, "hypnotherapy"))
- REMOVE_TRAIT(owner, "hypnotherapy", ROUNDSTART_TRAIT)
return ..()
/datum/status_effect/trance/on_remove()
UnregisterSignal(owner, COMSIG_MOVABLE_HEAR)
REMOVE_TRAIT(owner, TRAIT_MUTE, "trance")
owner.dizziness = 0
- if(!owner.has_quirk(/datum/quirk/monochromatic))
- owner.remove_client_colour(/datum/client_colour/monochrome)
+ owner.remove_client_colour(/datum/client_colour/monochrome/trance)
to_chat(owner, "You snap out of your trance!")
-/datum/status_effect/trance/proc/listen(datum/source, message, atom/movable/speaker, message_language, raw_message, radio_freq, list/spans, message_mode)
- to_chat(owner, "[speaker] accidentally sets off your implanted trigger, sending you into a hypnotic daze!")
- triggered = TRUE
-
-/datum/status_effect/trance/proc/hypnotize(datum/source, message, atom/movable/speaker, message_language, raw_message, radio_freq, list/spans, message_mode)
+/datum/status_effect/trance/proc/hypnotize(datum/source, list/hearing_args)
if(!owner.can_hear())
return
- if(speaker == owner)
+ if(hearing_args[HEARING_SPEAKER] == owner)
return
var/mob/living/carbon/C = owner
C.cure_trauma_type(/datum/brain_trauma/hypnosis, TRAUMA_RESILIENCE_SURGERY) //clear previous hypnosis
- if(HAS_TRAIT(C, "hypnotherapy"))
- addtimer(CALLBACK(C, /mob/living/carbon.proc/gain_trauma, /datum/brain_trauma/hypnosis, TRAUMA_RESILIENCE_SURGERY, raw_message, TRUE), 10)
- else
- addtimer(CALLBACK(C, /mob/living/carbon.proc/gain_trauma, /datum/brain_trauma/hypnosis, TRAUMA_RESILIENCE_SURGERY, raw_message), 10)
+ addtimer(CALLBACK(C, /mob/living/carbon.proc/gain_trauma, /datum/brain_trauma/hypnosis, TRAUMA_RESILIENCE_SURGERY, hearing_args[HEARING_RAW_MESSAGE]), 10)
addtimer(CALLBACK(C, /mob/living.proc/Stun, 60, TRUE, TRUE), 15) //Take some time to think about it
qdel(src)
@@ -729,3 +702,59 @@ datum/status_effect/pacify
name = "Genetic Breakdown"
desc = "I don't feel so good. Your body can't handle the mutations! You have one minute to remove your mutations, or you will be met with a horrible fate."
icon_state = "dna_melt"
+
+/datum/status_effect/spasms
+ id = "spasms"
+ status_type = STATUS_EFFECT_MULTIPLE
+ alert_type = null
+
+/datum/status_effect/spasms/tick()
+ if(prob(15))
+ switch(rand(1,5))
+ if(1)
+ if((!owner.lying && !owner.buckled) && isturf(owner.loc))
+ to_chat(owner, "Your leg spasms!")
+ step(owner, pick(GLOB.cardinals))
+ if(2)
+ if(owner.incapacitated())
+ return
+ var/obj/item/I = owner.get_active_held_item()
+ if(I)
+ to_chat(owner, "Your fingers spasm!")
+ owner.log_message("used [I] due to a Muscle Spasm", LOG_ATTACK)
+ I.attack_self(owner)
+ if(3)
+ var/prev_intent = owner.a_intent
+ owner.a_intent = INTENT_HARM
+
+ var/range = 1
+ if(istype(owner.get_active_held_item(), /obj/item/gun)) //get targets to shoot at
+ range = 7
+
+ var/list/mob/living/targets = list()
+ for(var/mob/M in oview(owner, range))
+ if(isliving(M))
+ targets += M
+ if(LAZYLEN(targets))
+ to_chat(owner, "Your arm spasms!")
+ owner.log_message(" attacked someone due to a Muscle Spasm", LOG_ATTACK) //the following attack will log itself
+ owner.ClickOn(pick(targets))
+ owner.a_intent = prev_intent
+ if(4)
+ var/prev_intent = owner.a_intent
+ owner.a_intent = INTENT_HARM
+ to_chat(owner, "Your arm spasms!")
+ owner.log_message("attacked [owner.p_them()]self to a Muscle Spasm", LOG_ATTACK)
+ owner.ClickOn(owner)
+ owner.a_intent = prev_intent
+ if(5)
+ if(owner.incapacitated())
+ return
+ var/obj/item/I = owner.get_active_held_item()
+ var/list/turf/targets = list()
+ for(var/turf/T in oview(owner, 3))
+ targets += T
+ if(LAZYLEN(targets) && I)
+ to_chat(owner, "Your arm spasms!")
+ owner.log_message("threw [I] due to a Muscle Spasm", LOG_ATTACK)
+ owner.throw_item(pick(targets))
\ No newline at end of file
diff --git a/code/datums/status_effects/neutral.dm b/code/datums/status_effects/neutral.dm
index 655863e0..cb2b4174 100644
--- a/code/datums/status_effects/neutral.dm
+++ b/code/datums/status_effects/neutral.dm
@@ -69,3 +69,17 @@
/datum/status_effect/in_love/tick()
if(date)
new /obj/effect/temp_visual/love_heart/invisible(get_turf(date.loc), owner)
+
+/datum/status_effect/throat_soothed
+ id = "throat_soothed"
+ duration = 60 SECONDS
+ status_type = STATUS_EFFECT_REFRESH
+ alert_type = null
+
+/datum/status_effect/throat_soothed/on_apply()
+ . = ..()
+ ADD_TRAIT(owner, TRAIT_SOOTHED_THROAT, "[STATUS_EFFECT_TRAIT]_[id]")
+
+/datum/status_effect/throat_soothed/on_remove()
+ . = ..()
+ REMOVE_TRAIT(owner, TRAIT_SOOTHED_THROAT, "[STATUS_EFFECT_TRAIT]_[id]")
\ No newline at end of file
diff --git a/code/datums/status_effects/status_effect.dm b/code/datums/status_effects/status_effect.dm
index bc1cfba1..0d84aab7 100644
--- a/code/datums/status_effects/status_effect.dm
+++ b/code/datums/status_effects/status_effect.dm
@@ -64,6 +64,12 @@
owner = null
qdel(src)
+/datum/status_effect/proc/refresh()
+ var/original_duration = initial(duration)
+ if(original_duration == -1)
+ return
+ duration = world.time + original_duration
+
//clickdelay/nextmove modifiers!
/datum/status_effect/proc/nextmove_modifier()
return 1
@@ -92,6 +98,9 @@
if(S.id == initial(S1.id) && S.status_type)
if(S.status_type == STATUS_EFFECT_REPLACE)
S.be_replaced()
+ else if(S.status_type == STATUS_EFFECT_REFRESH)
+ S.refresh()
+ return
else
return
var/list/arguments = args.Copy()
diff --git a/code/game/atoms.dm b/code/game/atoms.dm
index 6dbf96e3..00614f91 100644
--- a/code/game/atoms.dm
+++ b/code/game/atoms.dm
@@ -1,870 +1,870 @@
-/atom
- layer = TURF_LAYER
- plane = GAME_PLANE
- var/level = 2
- var/article // If non-null, overrides a/an/some in all cases
-
- var/flags_1 = NONE
- var/interaction_flags_atom = NONE
- var/datum/reagents/reagents = null
-
- //This atom's HUD (med/sec, etc) images. Associative list.
- var/list/image/hud_list = null
- //HUD images that this atom can provide.
- var/list/hud_possible
-
- /// Last name used to calculate a color for the chatmessage overlays
- var/chat_color_name
- /// Last color calculated for the the chatmessage overlays
- var/chat_color
- /// A luminescence-shifted value of the last color calculated for chatmessage overlays
- var/chat_color_darkened
-
- //Value used to increment ex_act() if reactionary_explosions is on
- var/explosion_block = 0
-
- var/list/atom_colours //used to store the different colors on an atom
- //its inherent color, the colored paint applied on it, special color effect etc...
-
- var/list/priority_overlays //overlays that should remain on top and not normally removed when using cut_overlay functions, like c4.
- var/list/remove_overlays // a very temporary list of overlays to remove
- var/list/add_overlays // a very temporary list of overlays to add
-
- var/list/managed_vis_overlays //vis overlays managed by SSvis_overlays to automaticaly turn them like other overlays
- var/list/managed_overlays
- var/datum/proximity_monitor/proximity_monitor
- var/buckle_message_cooldown = 0
- var/fingerprintslast
-
- var/list/filter_data //For handling persistent filters
-
- var/datum/component/orbiter/orbiters
-
- var/rad_flags = NONE // Will move to flags_1 when i can be arsed to
- var/rad_insulation = RAD_NO_INSULATION
-
- var/icon/blood_splatter_icon
- var/icon/cum_splatter_icon
- var/list/fingerprints
- var/list/fingerprintshidden
- var/list/blood_DNA
- var/list/suit_fibers
-
-/atom/New(loc, ...)
- //atom creation method that preloads variables at creation
- if(GLOB.use_preloader && (src.type == GLOB._preloader.target_path))//in case the instanciated atom is creating other atoms in New()
- GLOB._preloader.load(src)
-
- if(datum_flags & DF_USE_TAG)
- GenerateTag()
-
- var/do_initialize = SSatoms.initialized
- if(do_initialize != INITIALIZATION_INSSATOMS)
- args[1] = do_initialize == INITIALIZATION_INNEW_MAPLOAD
- if(SSatoms.InitAtom(src, args))
- //we were deleted
- return
-
-//Called after New if the map is being loaded. mapload = TRUE
-//Called from base of New if the map is not being loaded. mapload = FALSE
-//This base must be called or derivatives must set initialized to TRUE
-//must not sleep
-//Other parameters are passed from New (excluding loc), this does not happen if mapload is TRUE
-//Must return an Initialize hint. Defined in __DEFINES/subsystems.dm
-
-//Note: the following functions don't call the base for optimization and must copypasta:
-// /turf/Initialize
-// /turf/open/space/Initialize
-
-/atom/proc/Initialize(mapload, ...)
- if(flags_1 & INITIALIZED_1)
- stack_trace("Warning: [src]([type]) initialized multiple times!")
- flags_1 |= INITIALIZED_1
-
- //atom color stuff
- if(color)
- add_atom_colour(color, FIXED_COLOUR_PRIORITY)
-
- if (light_power && light_range)
- update_light()
-
- if (opacity && isturf(loc))
- var/turf/T = loc
- T.has_opaque_atom = TRUE // No need to recalculate it in this case, it's guaranteed to be on afterwards anyways.
-
- if (canSmoothWith)
- canSmoothWith = typelist("canSmoothWith", canSmoothWith)
-
- ComponentInitialize()
-
- return INITIALIZE_HINT_NORMAL
-
-//called if Initialize returns INITIALIZE_HINT_LATELOAD
-/atom/proc/LateInitialize()
- return
-
-// Put your AddComponent() calls here
-/atom/proc/ComponentInitialize()
- return
-
-/atom/Destroy()
- if(alternate_appearances)
- for(var/K in alternate_appearances)
- var/datum/atom_hud/alternate_appearance/AA = alternate_appearances[K]
- AA.remove_from_hud(src)
-
- if(reagents)
- qdel(reagents)
-
- LAZYCLEARLIST(overlays)
- LAZYCLEARLIST(priority_overlays)
-
- QDEL_NULL(light)
-
- return ..()
-
-/atom/proc/handle_ricochet(obj/item/projectile/P)
- return
-
-/atom/proc/CanPass(atom/movable/mover, turf/target)
- return !density
-
-/atom/proc/onCentCom()
- var/turf/T = get_turf(src)
- if(!T)
- return FALSE
-
- if(is_reserved_level(T.z))
- for(var/A in SSshuttle.mobile)
- var/obj/docking_port/mobile/M = A
- if(M.launch_status == ENDGAME_TRANSIT)
- for(var/place in M.shuttle_areas)
- var/area/shuttle/shuttle_area = place
- if(T in shuttle_area)
- return TRUE
-
- if(!is_centcom_level(T.z))//if not, don't bother
- return FALSE
-
- //Check for centcom itself
- if(istype(T.loc, /area/centcom))
- return TRUE
-
- //Check for centcom shuttles
- for(var/A in SSshuttle.mobile)
- var/obj/docking_port/mobile/M = A
- if(M.launch_status == ENDGAME_LAUNCHED)
- for(var/place in M.shuttle_areas)
- var/area/shuttle/shuttle_area = place
- if(T in shuttle_area)
- return TRUE
-
-/atom/proc/onSyndieBase()
- var/turf/T = get_turf(src)
- if(!T)
- return FALSE
-
- if(!is_centcom_level(T.z))//if not, don't bother
- return FALSE
-
- if(istype(T.loc, /area/shuttle/syndicate) || istype(T.loc, /area/syndicate_mothership) || istype(T.loc, /area/shuttle/assault_pod))
- return TRUE
-
- return FALSE
-
-/atom/proc/attack_hulk(mob/living/carbon/human/user, does_attack_animation = FALSE)
- SEND_SIGNAL(src, COMSIG_ATOM_HULK_ATTACK, user)
- if(does_attack_animation)
- user.changeNext_move(CLICK_CD_MELEE)
- log_combat(user, src, "punched", "hulk powers")
- user.do_attack_animation(src, ATTACK_EFFECT_SMASH)
-
-/atom/proc/CheckParts(list/parts_list)
- for(var/A in parts_list)
- if(istype(A, /datum/reagent))
- if(!reagents)
- reagents = new()
- reagents.reagent_list.Add(A)
- reagents.conditional_update()
- else if(ismovableatom(A))
- var/atom/movable/M = A
- if(isliving(M.loc))
- var/mob/living/L = M.loc
- L.transferItemToLoc(M, src)
- else
- M.forceMove(src)
-
-//common name
-/atom/proc/update_multiz(prune_on_fail = FALSE)
- return FALSE
-
-/atom/proc/assume_air(datum/gas_mixture/giver)
- qdel(giver)
- return null
-
-/atom/proc/remove_air(amount)
- return null
-
-/atom/proc/return_air()
- if(loc)
- return loc.return_air()
- else
- return null
-
-/atom/proc/check_eye(mob/user)
- return
-
-/atom/proc/Bumped(atom/movable/AM)
- set waitfor = FALSE
-
-// Convenience procs to see if a container is open for chemistry handling
-/atom/proc/is_open_container()
- return is_refillable() && is_drainable()
-
-/atom/proc/is_injectable(allowmobs = TRUE)
- return reagents && (reagents.reagents_holder_flags & (INJECTABLE | REFILLABLE))
-
-/atom/proc/is_drawable(allowmobs = TRUE)
- return reagents && (reagents.reagents_holder_flags & (DRAWABLE | DRAINABLE))
-
-/atom/proc/is_refillable()
- return reagents && (reagents.reagents_holder_flags & REFILLABLE)
-
-/atom/proc/is_drainable()
- return reagents && (reagents.reagents_holder_flags & DRAINABLE)
-
-
-/atom/proc/AllowDrop()
- return FALSE
-
-/atom/proc/CheckExit()
- return TRUE
-
-/atom/proc/HasProximity(atom/movable/AM as mob|obj)
- return
-
-/atom/proc/emp_act(severity)
- var/protection = SEND_SIGNAL(src, COMSIG_ATOM_EMP_ACT, severity)
- if(!(protection & EMP_PROTECT_WIRES) && istype(wires))
- wires.emp_pulse()
- return protection // Pass the protection value collected here upwards
-
-/atom/proc/bullet_act(obj/item/projectile/P, def_zone)
- SEND_SIGNAL(src, COMSIG_ATOM_BULLET_ACT, P, def_zone)
- . = P.on_hit(src, 0, def_zone)
-
-//used on altdisarm() for special interactions between the shoved victim (target) and the src, with user being the one shoving the target on it.
-// IMPORTANT: if you wish to add a new own shove_act() to a certain object, remember to add SHOVABLE_ONTO to its obj_flags bitfied var first.
-/atom/proc/shove_act(mob/living/target, mob/living/user)
- return FALSE
-
-/atom/proc/in_contents_of(container)//can take class or object instance as argument
- if(ispath(container))
- if(istype(src.loc, container))
- return TRUE
- else if(src in container)
- return TRUE
- return FALSE
-
-/atom/proc/update_icon_state()
-
-/atom/proc/update_overlaysb()
- SHOULD_CALL_PARENT(1)
- . = list()
- SEND_SIGNAL(src, COMSIG_ATOM_UPDATE_OVERLAYS, .)
-
-/atom/proc/update_iconb()
- var/signalOut = SEND_SIGNAL(src, COMSIG_ATOM_UPDATE_ICON)
-
- if(!(signalOut & COMSIG_ATOM_NO_UPDATE_ICON_STATE))
- update_icon_state()
-
- if(!(signalOut & COMSIG_ATOM_NO_UPDATE_OVERLAYS))
- var/list/new_overlays = update_overlaysb()
- if(managed_overlays)
- cut_overlay(managed_overlays)
- managed_overlays = null
- if(length(new_overlays))
- managed_overlays = new_overlays
- add_overlay(new_overlays)
-
-/atom/proc/get_examine_name(mob/user)
- . = "\a [src]"
- var/list/override = list(gender == PLURAL ? "some" : "a", " ", "[name]")
- if(article)
- . = "[article] [src]"
- override[EXAMINE_POSITION_ARTICLE] = article
-
- var/should_override = FALSE
-
- if(SEND_SIGNAL(src, COMSIG_ATOM_GET_EXAMINE_NAME, user, override) & COMPONENT_EXNAME_CHANGED)
- should_override = TRUE
-
-
- if(blood_DNA && !istype(src, /obj/effect/decal))
- override[EXAMINE_POSITION_BEFORE] = " blood-stained "
- should_override = TRUE
-
- if(should_override)
- . = override.Join("")
-
-///Generate the full examine string of this atom (including icon for goonchat)
-/atom/proc/get_examine_string(mob/user, thats = FALSE)
- return "[icon2html(src, user)] [thats? "That's ":""][get_examine_name(user)]"
-
-/atom/proc/examine(mob/user)
- . = list("[get_examine_string(user, TRUE)].")
-
- if(desc)
- . += desc
-
- if(reagents)
- if(reagents.reagents_holder_flags & TRANSPARENT)
- . += "It contains:"
- if(length(reagents.reagent_list))
- if(user.can_see_reagents()) //Show each individual reagent
- for(var/datum/reagent/R in reagents.reagent_list)
- . += "[R.volume] units of [R.name]"
- else //Otherwise, just show the total volume
- var/total_volume = 0
- for(var/datum/reagent/R in reagents.reagent_list)
- total_volume += R.volume
- . += "[total_volume] units of various reagents"
- else
- . += "Nothing."
- else if(reagents.reagents_holder_flags & AMOUNT_VISIBLE)
- if(reagents.total_volume)
- . += "It has [reagents.total_volume] unit\s left."
- else
- . += "It's empty."
- SEND_SIGNAL(src, COMSIG_PARENT_EXAMINE, user, .)
-
-/atom/proc/relaymove(mob/user)
- if(buckle_message_cooldown <= world.time)
- buckle_message_cooldown = world.time + 50
- to_chat(user, "You can't move while buckled to [src]!")
- return
-
-/atom/proc/prevent_content_explosion()
- return FALSE
-
-/atom/proc/contents_explosion(severity, target)
- return //For handling the effects of explosions on contents that would not normally be effected
-
-/atom/proc/ex_act(severity, target)
- set waitfor = FALSE
- contents_explosion(severity, target)
- SEND_SIGNAL(src, COMSIG_ATOM_EX_ACT, severity, target)
-
-/atom/proc/blob_act(obj/structure/blob/B)
- SEND_SIGNAL(src, COMSIG_ATOM_BLOB_ACT, B)
- return
-
-/atom/proc/fire_act(exposed_temperature, exposed_volume)
- SEND_SIGNAL(src, COMSIG_ATOM_FIRE_ACT, exposed_temperature, exposed_volume)
- return
-
-/atom/proc/hitby(atom/movable/AM, skipcatch, hitpush, blocked, datum/thrownthing/throwingdatum)
- if(density && !has_gravity(AM)) //thrown stuff bounces off dense stuff in no grav, unless the thrown stuff ends up inside what it hit(embedding, bola, etc...).
- addtimer(CALLBACK(src, .proc/hitby_react, AM), 2)
-
-/atom/proc/hitby_react(atom/movable/AM)
- if(AM && isturf(AM.loc))
- step(AM, turn(AM.dir, 180))
-
-/atom/proc/handle_slip(mob/living/carbon/C, knockdown_amount, obj/O, lube)
- return
-
-//returns the mob's dna info as a list, to be inserted in an object's blood_DNA list
-/mob/living/proc/get_blood_dna_list()
- var/blood_id = get_blood_id()
- if(!(blood_id in GLOB.blood_reagent_types))
- return
- return list("ANIMAL DNA" = "Y-")
-
-/mob/living/carbon/get_blood_dna_list()
- var/blood_id = get_blood_id()
- if(!(blood_id in GLOB.blood_reagent_types))
- return
- var/list/blood_dna = list()
- if(dna)
- blood_dna[dna.unique_enzymes] = dna.blood_type
- else
- blood_dna["UNKNOWN DNA"] = "X*"
- return blood_dna
-
-/mob/living/carbon/alien/get_blood_dna_list()
- return list("UNKNOWN DNA" = "X*")
-
-//to add a mob's dna info into an object's blood_DNA list.
-/atom/proc/transfer_mob_blood_dna(mob/living/L)
- // Returns 0 if we have that blood already
- var/new_blood_dna = L.get_blood_dna_list()
- if(!new_blood_dna)
- return FALSE
- LAZYINITLIST(blood_DNA) //if our list of DNA doesn't exist yet, initialise it.
- var/old_length = blood_DNA.len
- blood_DNA |= new_blood_dna
- if(blood_DNA.len == old_length)
- return FALSE
- return TRUE
-
-//to add blood dna info to the object's blood_DNA list
-/atom/proc/transfer_blood_dna(list/blood_dna, list/datum/disease/diseases)
- LAZYINITLIST(blood_DNA)
- var/old_length = blood_DNA.len
- blood_DNA |= blood_dna
- if(blood_DNA.len > old_length)
- return TRUE
- //some new blood DNA was added
-
-//to add blood from a mob onto something, and transfer their dna info
-/atom/proc/add_mob_blood(mob/living/M)
- var/list/blood_dna = M.get_blood_dna_list()
- if(!blood_dna)
- return FALSE
- return add_blood_DNA(blood_dna, M.diseases)
-
-//to add blood onto something, with blood dna info to include.
-/atom/proc/add_blood_DNA(list/blood_dna, list/datum/disease/diseases)
- return FALSE
-
-/obj/add_blood_DNA(list/blood_dna, list/datum/disease/diseases)
- return transfer_blood_dna(blood_dna, diseases)
-
-/obj/item/add_blood_DNA(list/blood_dna, list/datum/disease/diseases)
- . = ..()
- if(!.)
- return
- add_blood_overlay()
-
-/obj/item/proc/add_blood_overlay()
- if(!blood_DNA.len)
- return
- if(initial(icon) && initial(icon_state))
- blood_splatter_icon = icon(initial(icon), initial(icon_state), , 1) //we only want to apply blood-splatters to the initial icon_state for each object
- blood_splatter_icon.Blend("#fff", ICON_ADD) //fills the icon_state with white (except where it's transparent)
- blood_splatter_icon.Blend(icon('icons/effects/blood.dmi', "itemblood"), ICON_MULTIPLY) //adds blood and the remaining white areas become transparant
- blood_splatter_icon.Blend(blood_DNA_to_color(), ICON_MULTIPLY)
- add_overlay(blood_splatter_icon)
-
-/obj/item/clothing/gloves/add_blood_DNA(list/blood_dna, list/datum/disease/diseases)
- . = ..()
- transfer_blood = rand(2, 4)
-
-/turf/add_blood_DNA(list/blood_dna, list/datum/disease/diseases)
- var/obj/effect/decal/cleanable/blood/splatter/B = locate() in src
- if(!B)
- B = new /obj/effect/decal/cleanable/blood/splatter(src, diseases)
- B.transfer_blood_dna(blood_dna, diseases) //give blood info to the blood decal.
- return TRUE //we bloodied the floor
-
-/mob/living/carbon/human/add_blood_DNA(list/blood_dna, list/datum/disease/diseases)
- if(head)
- head.add_blood_DNA(blood_dna, diseases)
- update_inv_head()
- else if(wear_mask)
- wear_mask.add_blood_DNA(blood_dna, diseases)
- update_inv_wear_mask()
- if(wear_neck)
- wear_neck.add_blood_DNA(blood_dna, diseases)
- update_inv_neck()
- if(wear_suit)
- wear_suit.add_blood_DNA(blood_dna, diseases)
- update_inv_wear_suit()
- else if(w_uniform)
- w_uniform.add_blood_DNA(blood_dna, diseases)
- update_inv_w_uniform()
- if(gloves)
- var/obj/item/clothing/gloves/G = gloves
- G.add_blood_DNA(blood_dna, diseases)
- else
- transfer_blood_dna(blood_dna, diseases)
- bloody_hands = rand(2, 4)
- update_inv_gloves() //handles bloody hands overlays and updating
- return TRUE
-
-/atom/proc/blood_DNA_to_color()
- var/list/colors = list()//first we make a list of all bloodtypes present
- for(var/bloop in blood_DNA)
- if(colors[blood_DNA[bloop]])
- colors[blood_DNA[bloop]]++
- else
- colors[blood_DNA[bloop]] = 1
-
- var/final_rgb = BLOOD_COLOR_HUMAN //a default so we don't have white blood graphics if something messed up
-
- if(colors.len)
- var/sum = 0 //this is all shitcode, but it works; trust me
- final_rgb = bloodtype_to_color(colors[1])
- sum = colors[colors[1]]
- if(colors.len > 1)
- var/i = 2
- while(i <= colors.len)
- var/tmp = colors[colors[i]]
- final_rgb = BlendRGB(final_rgb, bloodtype_to_color(colors[i]), tmp/(tmp+sum))
- sum += tmp
- i++
-
- return final_rgb
-
-/atom/proc/clean_blood()
- . = blood_DNA? TRUE : FALSE
- blood_DNA = null
-
-/atom/proc/wash_cream()
- return TRUE
-
-/atom/proc/isinspace()
- if(isspaceturf(get_turf(src)))
- return TRUE
- else
- return FALSE
-
-/atom/proc/handle_fall()
- return
-
-/atom/proc/singularity_act()
- return
-
-/atom/proc/singularity_pull(obj/singularity/S, current_size)
- SEND_SIGNAL(src, COMSIG_ATOM_SING_PULL, S, current_size)
-
-/atom/proc/acid_act(acidpwr, acid_volume)
- SEND_SIGNAL(src, COMSIG_ATOM_ACID_ACT, acidpwr, acid_volume)
-
-/atom/proc/emag_act()
- SEND_SIGNAL(src, COMSIG_ATOM_EMAG_ACT)
-
-/atom/proc/rad_act(strength)
- SEND_SIGNAL(src, COMSIG_ATOM_RAD_ACT, strength)
-
-/atom/proc/narsie_act()
- SEND_SIGNAL(src, COMSIG_ATOM_NARSIE_ACT)
-
-/atom/proc/ratvar_act()
- SEND_SIGNAL(src, COMSIG_ATOM_RATVAR_ACT)
-
-/atom/proc/rcd_vals(mob/user, obj/item/construction/rcd/the_rcd)
- return FALSE
-
-/atom/proc/rcd_act(mob/user, obj/item/construction/rcd/the_rcd, passed_mode)
- SEND_SIGNAL(src, COMSIG_ATOM_RCD_ACT, user, the_rcd, passed_mode)
- return FALSE
-
-/atom/proc/storage_contents_dump_act(obj/item/storage/src_object, mob/user)
- if(GetComponent(/datum/component/storage))
- return component_storage_contents_dump_act(src_object, user)
- return FALSE
-
-/atom/proc/component_storage_contents_dump_act(datum/component/storage/src_object, mob/user)
- var/list/things = src_object.contents()
- var/datum/progressbar/progress = new(user, things.len, src)
- var/datum/component/storage/STR = GetComponent(/datum/component/storage)
- while (do_after(user, 10, TRUE, src, FALSE, CALLBACK(STR, /datum/component/storage.proc/handle_mass_item_insertion, things, src_object, user, progress)))
- stoplag(1)
- qdel(progress)
- to_chat(user, "You dump as much of [src_object.parent]'s contents into [STR.insert_preposition]to [src] as you can.")
- STR.orient2hud(user)
- src_object.orient2hud(user)
- if(user.active_storage) //refresh the HUD to show the transfered contents
- user.active_storage.close(user)
- user.active_storage.show_to(user)
- return TRUE
-
-/atom/proc/get_dumping_location(obj/item/storage/source,mob/user)
- return null
-
-//This proc is called on the location of an atom when the atom is Destroy()'d
-/atom/proc/handle_atom_del(atom/A)
- SEND_SIGNAL(src, COMSIG_ATOM_CONTENTS_DEL, A)
-
-//called when the turf the atom resides on is ChangeTurfed
-/atom/proc/HandleTurfChange(turf/T)
- for(var/a in src)
- var/atom/A = a
- A.HandleTurfChange(T)
-
-//the vision impairment to give to the mob whose perspective is set to that atom (e.g. an unfocused camera giving you an impaired vision when looking through it)
-/atom/proc/get_remote_view_fullscreens(mob/user)
- return
-
-//the sight changes to give to the mob whose perspective is set to that atom (e.g. A mob with nightvision loses its nightvision while looking through a normal camera)
-/atom/proc/update_remote_sight(mob/living/user)
- return
-
-
-//Hook for running code when a dir change occurs
-/atom/proc/setDir(newdir)
- SEND_SIGNAL(src, COMSIG_ATOM_DIR_CHANGE, dir, newdir)
- dir = newdir
-
-/atom/proc/mech_melee_attack(obj/mecha/M)
- return
-
-//If a mob logouts/logins in side of an object you can use this proc
-/atom/proc/on_log(login)
- if(loc)
- loc.on_log(login)
-
-
-/*
- Atom Colour Priority System
- A System that gives finer control over which atom colour to colour the atom with.
- The "highest priority" one is always displayed as opposed to the default of
- "whichever was set last is displayed"
-*/
-
-
-/*
- Adds an instance of colour_type to the atom's atom_colours list
-*/
-/atom/proc/add_atom_colour(coloration, colour_priority)
- if(!atom_colours || !atom_colours.len)
- atom_colours = list()
- atom_colours.len = COLOUR_PRIORITY_AMOUNT //four priority levels currently.
- if(!coloration)
- return
- if(colour_priority > atom_colours.len)
- return
- atom_colours[colour_priority] = coloration
- update_atom_colour()
-
-
-/*
- Removes an instance of colour_type from the atom's atom_colours list
-*/
-/atom/proc/remove_atom_colour(colour_priority, coloration)
- if(!atom_colours)
- atom_colours = list()
- atom_colours.len = COLOUR_PRIORITY_AMOUNT //four priority levels currently.
- if(colour_priority > atom_colours.len)
- return
- if(coloration && atom_colours[colour_priority] != coloration)
- return //if we don't have the expected color (for a specific priority) to remove, do nothing
- atom_colours[colour_priority] = null
- update_atom_colour()
-
-
-/*
- Resets the atom's color to null, and then sets it to the highest priority
- colour available
-*/
-/atom/proc/update_atom_colour()
- if(!atom_colours)
- atom_colours = list()
- atom_colours.len = COLOUR_PRIORITY_AMOUNT //four priority levels currently.
- color = null
- for(var/C in atom_colours)
- if(islist(C))
- var/list/L = C
- if(L.len)
- color = L
- return
- else if(C)
- color = C
- return
-
-/atom/vv_edit_var(var_name, var_value)
- if(!GLOB.Debug2)
- flags_1 |= ADMIN_SPAWNED_1
- . = ..()
- switch(var_name)
- if("color")
- add_atom_colour(color, ADMIN_COLOUR_PRIORITY)
-
-/atom/vv_get_dropdown()
- . = ..()
- . += "---"
- var/turf/curturf = get_turf(src)
- if (curturf)
- .["Jump to"] = "?_src_=holder;[HrefToken()];adminplayerobservecoodjump=1;X=[curturf.x];Y=[curturf.y];Z=[curturf.z]"
- .["Modify Transform"] = "?_src_=vars;[HrefToken()];modtransform=[REF(src)]"
- .["Add reagent"] = "?_src_=vars;[HrefToken()];addreagent=[REF(src)]"
- .["Trigger EM pulse"] = "?_src_=vars;[HrefToken()];emp=[REF(src)]"
- .["Trigger explosion"] = "?_src_=vars;[HrefToken()];explode=[REF(src)]"
-
-/atom/proc/drop_location()
- var/atom/L = loc
- if(!L)
- return null
- return L.AllowDrop() ? L : get_turf(L)
-
-/atom/Entered(atom/movable/AM, atom/oldLoc)
- SEND_SIGNAL(src, COMSIG_ATOM_ENTERED, AM, oldLoc)
-
-/atom/Exit(atom/movable/AM, atom/newLoc)
- . = ..()
- if(SEND_SIGNAL(src, COMSIG_ATOM_EXIT, AM, newLoc) & COMPONENT_ATOM_BLOCK_EXIT)
- return FALSE
-
-/atom/Exited(atom/movable/AM, atom/newLoc)
- SEND_SIGNAL(src, COMSIG_ATOM_EXITED, AM, newLoc)
-
-/atom/proc/return_temperature()
- return
-
-// Tool behavior procedure. Redirects to tool-specific procs by default.
-// You can override it to catch all tool interactions, for use in complex deconstruction procs.
-// Just don't forget to return ..() in the end.
-/atom/proc/tool_act(mob/living/user, obj/item/I, tool_type)
- switch(tool_type)
- if(TOOL_CROWBAR)
- return crowbar_act(user, I)
- if(TOOL_MULTITOOL)
- return multitool_act(user, I)
- if(TOOL_SCREWDRIVER)
- return screwdriver_act(user, I)
- if(TOOL_WRENCH)
- return wrench_act(user, I)
- if(TOOL_WIRECUTTER)
- return wirecutter_act(user, I)
- if(TOOL_WELDER)
- return welder_act(user, I)
- if(TOOL_ANALYZER)
- return analyzer_act(user, I)
-
-// Tool-specific behavior procs. To be overridden in subtypes.
-/atom/proc/crowbar_act(mob/living/user, obj/item/I)
- return
-
-/atom/proc/multitool_act(mob/living/user, obj/item/I)
- return
-
-/atom/proc/screwdriver_act(mob/living/user, obj/item/I)
- SEND_SIGNAL(src, COMSIG_ATOM_SCREWDRIVER_ACT, user, I)
-
-/atom/proc/wrench_act(mob/living/user, obj/item/I)
- return
-
-/atom/proc/wirecutter_act(mob/living/user, obj/item/I)
- return
-
-/atom/proc/welder_act(mob/living/user, obj/item/I)
- return
-
-/atom/proc/analyzer_act(mob/living/user, obj/item/I)
- return
-
-/atom/proc/GenerateTag()
- return
-
-// Generic logging helper
-/atom/proc/log_message(message, message_type, color=null, log_globally=TRUE)
- if(!log_globally)
- return
-
- var/log_text = "[key_name(src)] [message] [loc_name(src)]"
- switch(message_type)
- if(LOG_ATTACK)
- log_attack(log_text)
- if(LOG_SAY)
- log_say(log_text)
- if(LOG_WHISPER)
- log_whisper(log_text)
- if(LOG_EMOTE)
- log_emote(log_text)
- if(LOG_DSAY)
- log_dsay(log_text)
- if(LOG_PDA)
- log_pda(log_text)
- if(LOG_CHAT)
- log_chat(log_text)
- if(LOG_COMMENT)
- log_comment(log_text)
- if(LOG_TELECOMMS)
- log_telecomms(log_text)
- if(LOG_OOC)
- log_ooc(log_text)
- if(LOG_ADMIN)
- log_admin(log_text)
- if(LOG_ADMIN_PRIVATE)
- log_admin_private(log_text)
- if(LOG_ASAY)
- log_adminsay(log_text)
- if(LOG_OWNERSHIP)
- log_game(log_text)
- if(LOG_GAME)
- log_game(log_text)
- else
- stack_trace("Invalid individual logging type: [message_type]. Defaulting to [LOG_GAME] (LOG_GAME).")
- log_game(log_text)
-
-// Helper for logging chat messages or other logs with arbitrary inputs (e.g. announcements)
-/atom/proc/log_talk(message, message_type, tag=null, log_globally=TRUE, forced_by=null)
- var/prefix = tag ? "([tag]) " : ""
- var/suffix = forced_by ? " FORCED by [forced_by]" : ""
- log_message("[prefix]\"[message]\"[suffix]", message_type, log_globally=log_globally)
-
-// Helper for logging of messages with only one sender and receiver
-/proc/log_directed_talk(atom/source, atom/target, message, message_type, tag)
- if(!tag)
- stack_trace("Unspecified tag for private message")
- tag = "UNKNOWN"
-
- source.log_talk(message, message_type, tag="[tag] to [key_name(target)]")
- if(source != target)
- target.log_talk(message, message_type, tag="[tag] from [key_name(source)]", log_globally=FALSE)
-
-/*
-Proc for attack log creation, because really why not
-1 argument is the actor performing the action
-2 argument is the target of the action
-3 is a verb describing the action (e.g. punched, throwed, kicked, etc.)
-4 is a tool with which the action was made (usually an item)
-5 is any additional text, which will be appended to the rest of the log line
-*/
-
-/proc/log_combat(atom/user, atom/target, what_done, atom/object=null, addition=null)
- var/ssource = key_name(user)
- var/starget = key_name(target)
-
- var/mob/living/living_target = target
- var/hp = istype(living_target) ? " (NEWHP: [living_target.health]) " : ""
-
- var/sobject = ""
- if(object)
- sobject = " with [key_name(object)]"
- var/saddition = ""
- if(addition)
- saddition = " [addition]"
-
- var/postfix = "[sobject][saddition][hp]"
-
- var/message = "has [what_done] [starget][postfix]"
- user.log_message(message, LOG_ATTACK, color="red")
-
- if(user != target)
- var/reverse_message = "has been [what_done] by [ssource][postfix]"
- target.log_message(reverse_message, LOG_ATTACK, color="orange", log_globally=FALSE)
-
-// Filter stuff
-/atom/movable/proc/add_filter(name,priority,list/params)
- if(!filter_data)
- filter_data = list()
- var/list/p = params.Copy()
- p["priority"] = priority
- filter_data[name] = p
- update_filters()
-
-/atom/movable/proc/update_filters()
- filters = null
- sortTim(filter_data,associative = TRUE)
- for(var/f in filter_data)
- var/list/data = filter_data[f]
- var/list/arguments = data.Copy()
- arguments -= "priority"
- filters += filter(arglist(arguments))
-
-/atom/movable/proc/get_filter(name)
- if(filter_data && filter_data[name])
- return filters[filter_data.Find(name)]
-
-/atom/movable/proc/remove_filter(name)
- if(filter_data[name])
- filter_data -= name
- update_filters()
- return TRUE
-
-/atom/proc/intercept_zImpact(atom/movable/AM, levels = 1)
- . |= SEND_SIGNAL(src, COMSIG_ATOM_INTERCEPT_Z_FALL, AM, levels)
+/atom
+ layer = TURF_LAYER
+ plane = GAME_PLANE
+ var/level = 2
+ var/article // If non-null, overrides a/an/some in all cases
+
+ var/flags_1 = NONE
+ var/interaction_flags_atom = NONE
+ var/datum/reagents/reagents = null
+
+ //This atom's HUD (med/sec, etc) images. Associative list.
+ var/list/image/hud_list = null
+ //HUD images that this atom can provide.
+ var/list/hud_possible
+
+ /// Last name used to calculate a color for the chatmessage overlays
+ var/chat_color_name
+ /// Last color calculated for the the chatmessage overlays
+ var/chat_color
+ /// A luminescence-shifted value of the last color calculated for chatmessage overlays
+ var/chat_color_darkened
+
+ //Value used to increment ex_act() if reactionary_explosions is on
+ var/explosion_block = 0
+
+ var/list/atom_colours //used to store the different colors on an atom
+ //its inherent color, the colored paint applied on it, special color effect etc...
+
+ var/list/priority_overlays //overlays that should remain on top and not normally removed when using cut_overlay functions, like c4.
+ var/list/remove_overlays // a very temporary list of overlays to remove
+ var/list/add_overlays // a very temporary list of overlays to add
+
+ var/list/managed_vis_overlays //vis overlays managed by SSvis_overlays to automaticaly turn them like other overlays
+ var/list/managed_overlays
+ var/datum/proximity_monitor/proximity_monitor
+ var/buckle_message_cooldown = 0
+ var/fingerprintslast
+
+ var/list/filter_data //For handling persistent filters
+
+ var/datum/component/orbiter/orbiters
+
+ var/rad_flags = NONE // Will move to flags_1 when i can be arsed to
+ var/rad_insulation = RAD_NO_INSULATION
+
+ var/icon/blood_splatter_icon
+ var/icon/cum_splatter_icon
+ var/list/fingerprints
+ var/list/fingerprintshidden
+ var/list/blood_DNA
+ var/list/suit_fibers
+
+/atom/New(loc, ...)
+ //atom creation method that preloads variables at creation
+ if(GLOB.use_preloader && (src.type == GLOB._preloader.target_path))//in case the instanciated atom is creating other atoms in New()
+ GLOB._preloader.load(src)
+
+ if(datum_flags & DF_USE_TAG)
+ GenerateTag()
+
+ var/do_initialize = SSatoms.initialized
+ if(do_initialize != INITIALIZATION_INSSATOMS)
+ args[1] = do_initialize == INITIALIZATION_INNEW_MAPLOAD
+ if(SSatoms.InitAtom(src, args))
+ //we were deleted
+ return
+
+//Called after New if the map is being loaded. mapload = TRUE
+//Called from base of New if the map is not being loaded. mapload = FALSE
+//This base must be called or derivatives must set initialized to TRUE
+//must not sleep
+//Other parameters are passed from New (excluding loc), this does not happen if mapload is TRUE
+//Must return an Initialize hint. Defined in __DEFINES/subsystems.dm
+
+//Note: the following functions don't call the base for optimization and must copypasta:
+// /turf/Initialize
+// /turf/open/space/Initialize
+
+/atom/proc/Initialize(mapload, ...)
+ if(flags_1 & INITIALIZED_1)
+ stack_trace("Warning: [src]([type]) initialized multiple times!")
+ flags_1 |= INITIALIZED_1
+
+ //atom color stuff
+ if(color)
+ add_atom_colour(color, FIXED_COLOUR_PRIORITY)
+
+ if (light_power && light_range)
+ update_light()
+
+ if (opacity && isturf(loc))
+ var/turf/T = loc
+ T.has_opaque_atom = TRUE // No need to recalculate it in this case, it's guaranteed to be on afterwards anyways.
+
+ if (canSmoothWith)
+ canSmoothWith = typelist("canSmoothWith", canSmoothWith)
+
+ ComponentInitialize()
+
+ return INITIALIZE_HINT_NORMAL
+
+//called if Initialize returns INITIALIZE_HINT_LATELOAD
+/atom/proc/LateInitialize()
+ return
+
+// Put your AddComponent() calls here
+/atom/proc/ComponentInitialize()
+ return
+
+/atom/Destroy()
+ if(alternate_appearances)
+ for(var/K in alternate_appearances)
+ var/datum/atom_hud/alternate_appearance/AA = alternate_appearances[K]
+ AA.remove_from_hud(src)
+
+ if(reagents)
+ qdel(reagents)
+
+ LAZYCLEARLIST(overlays)
+ LAZYCLEARLIST(priority_overlays)
+
+ QDEL_NULL(light)
+
+ return ..()
+
+/atom/proc/handle_ricochet(obj/item/projectile/P)
+ return
+
+/atom/proc/CanPass(atom/movable/mover, turf/target)
+ return !density
+
+/atom/proc/onCentCom()
+ var/turf/T = get_turf(src)
+ if(!T)
+ return FALSE
+
+ if(is_reserved_level(T.z))
+ for(var/A in SSshuttle.mobile)
+ var/obj/docking_port/mobile/M = A
+ if(M.launch_status == ENDGAME_TRANSIT)
+ for(var/place in M.shuttle_areas)
+ var/area/shuttle/shuttle_area = place
+ if(T in shuttle_area)
+ return TRUE
+
+ if(!is_centcom_level(T.z))//if not, don't bother
+ return FALSE
+
+ //Check for centcom itself
+ if(istype(T.loc, /area/centcom))
+ return TRUE
+
+ //Check for centcom shuttles
+ for(var/A in SSshuttle.mobile)
+ var/obj/docking_port/mobile/M = A
+ if(M.launch_status == ENDGAME_LAUNCHED)
+ for(var/place in M.shuttle_areas)
+ var/area/shuttle/shuttle_area = place
+ if(T in shuttle_area)
+ return TRUE
+
+/atom/proc/onSyndieBase()
+ var/turf/T = get_turf(src)
+ if(!T)
+ return FALSE
+
+ if(!is_centcom_level(T.z))//if not, don't bother
+ return FALSE
+
+ if(istype(T.loc, /area/shuttle/syndicate) || istype(T.loc, /area/syndicate_mothership) || istype(T.loc, /area/shuttle/assault_pod))
+ return TRUE
+
+ return FALSE
+
+/atom/proc/attack_hulk(mob/living/carbon/human/user, does_attack_animation = FALSE)
+ SEND_SIGNAL(src, COMSIG_ATOM_HULK_ATTACK, user)
+ if(does_attack_animation)
+ user.changeNext_move(CLICK_CD_MELEE)
+ log_combat(user, src, "punched", "hulk powers")
+ user.do_attack_animation(src, ATTACK_EFFECT_SMASH)
+
+/atom/proc/CheckParts(list/parts_list)
+ for(var/A in parts_list)
+ if(istype(A, /datum/reagent))
+ if(!reagents)
+ reagents = new()
+ reagents.reagent_list.Add(A)
+ reagents.conditional_update()
+ else if(ismovableatom(A))
+ var/atom/movable/M = A
+ if(isliving(M.loc))
+ var/mob/living/L = M.loc
+ L.transferItemToLoc(M, src)
+ else
+ M.forceMove(src)
+
+//common name
+/atom/proc/update_multiz(prune_on_fail = FALSE)
+ return FALSE
+
+/atom/proc/assume_air(datum/gas_mixture/giver)
+ qdel(giver)
+ return null
+
+/atom/proc/remove_air(amount)
+ return null
+
+/atom/proc/return_air()
+ if(loc)
+ return loc.return_air()
+ else
+ return null
+
+/atom/proc/check_eye(mob/user)
+ return
+
+/atom/proc/Bumped(atom/movable/AM)
+ set waitfor = FALSE
+
+// Convenience procs to see if a container is open for chemistry handling
+/atom/proc/is_open_container()
+ return is_refillable() && is_drainable()
+
+/atom/proc/is_injectable(allowmobs = TRUE)
+ return reagents && (reagents.reagents_holder_flags & (INJECTABLE | REFILLABLE))
+
+/atom/proc/is_drawable(allowmobs = TRUE)
+ return reagents && (reagents.reagents_holder_flags & (DRAWABLE | DRAINABLE))
+
+/atom/proc/is_refillable()
+ return reagents && (reagents.reagents_holder_flags & REFILLABLE)
+
+/atom/proc/is_drainable()
+ return reagents && (reagents.reagents_holder_flags & DRAINABLE)
+
+
+/atom/proc/AllowDrop()
+ return FALSE
+
+/atom/proc/CheckExit()
+ return TRUE
+
+/atom/proc/HasProximity(atom/movable/AM as mob|obj)
+ return
+
+/atom/proc/emp_act(severity)
+ var/protection = SEND_SIGNAL(src, COMSIG_ATOM_EMP_ACT, severity)
+ if(!(protection & EMP_PROTECT_WIRES) && istype(wires))
+ wires.emp_pulse()
+ return protection // Pass the protection value collected here upwards
+
+/atom/proc/bullet_act(obj/item/projectile/P, def_zone)
+ SEND_SIGNAL(src, COMSIG_ATOM_BULLET_ACT, P, def_zone)
+ . = P.on_hit(src, 0, def_zone)
+
+//used on altdisarm() for special interactions between the shoved victim (target) and the src, with user being the one shoving the target on it.
+// IMPORTANT: if you wish to add a new own shove_act() to a certain object, remember to add SHOVABLE_ONTO to its obj_flags bitfied var first.
+/atom/proc/shove_act(mob/living/target, mob/living/user)
+ return FALSE
+
+/atom/proc/in_contents_of(container)//can take class or object instance as argument
+ if(ispath(container))
+ if(istype(src.loc, container))
+ return TRUE
+ else if(src in container)
+ return TRUE
+ return FALSE
+
+/atom/proc/update_icon_state()
+
+/atom/proc/update_overlaysb()
+ SHOULD_CALL_PARENT(1)
+ . = list()
+ SEND_SIGNAL(src, COMSIG_ATOM_UPDATE_OVERLAYS, .)
+
+/atom/proc/update_iconb()
+ var/signalOut = SEND_SIGNAL(src, COMSIG_ATOM_UPDATE_ICON)
+
+ if(!(signalOut & COMSIG_ATOM_NO_UPDATE_ICON_STATE))
+ update_icon_state()
+
+ if(!(signalOut & COMSIG_ATOM_NO_UPDATE_OVERLAYS))
+ var/list/new_overlays = update_overlaysb()
+ if(managed_overlays)
+ cut_overlay(managed_overlays)
+ managed_overlays = null
+ if(length(new_overlays))
+ managed_overlays = new_overlays
+ add_overlay(new_overlays)
+
+/atom/proc/get_examine_name(mob/user)
+ . = "\a [src]"
+ var/list/override = list(gender == PLURAL ? "some" : "a", " ", "[name]")
+ if(article)
+ . = "[article] [src]"
+ override[EXAMINE_POSITION_ARTICLE] = article
+
+ var/should_override = FALSE
+
+ if(SEND_SIGNAL(src, COMSIG_ATOM_GET_EXAMINE_NAME, user, override) & COMPONENT_EXNAME_CHANGED)
+ should_override = TRUE
+
+
+ if(blood_DNA && !istype(src, /obj/effect/decal))
+ override[EXAMINE_POSITION_BEFORE] = " blood-stained "
+ should_override = TRUE
+
+ if(should_override)
+ . = override.Join("")
+
+///Generate the full examine string of this atom (including icon for goonchat)
+/atom/proc/get_examine_string(mob/user, thats = FALSE)
+ return "[icon2html(src, user)] [thats? "That's ":""][get_examine_name(user)]"
+
+/atom/proc/examine(mob/user)
+ . = list("[get_examine_string(user, TRUE)].")
+
+ if(desc)
+ . += desc
+
+ if(reagents)
+ if(reagents.reagents_holder_flags & TRANSPARENT)
+ . += "It contains:"
+ if(length(reagents.reagent_list))
+ if(user.can_see_reagents()) //Show each individual reagent
+ for(var/datum/reagent/R in reagents.reagent_list)
+ . += "[R.volume] units of [R.name]"
+ else //Otherwise, just show the total volume
+ var/total_volume = 0
+ for(var/datum/reagent/R in reagents.reagent_list)
+ total_volume += R.volume
+ . += "[total_volume] units of various reagents"
+ else
+ . += "Nothing."
+ else if(reagents.reagents_holder_flags & AMOUNT_VISIBLE)
+ if(reagents.total_volume)
+ . += "It has [reagents.total_volume] unit\s left."
+ else
+ . += "It's empty."
+ SEND_SIGNAL(src, COMSIG_PARENT_EXAMINE, user, .)
+
+/atom/proc/relaymove(mob/user)
+ if(buckle_message_cooldown <= world.time)
+ buckle_message_cooldown = world.time + 50
+ to_chat(user, "You can't move while buckled to [src]!")
+ return
+
+/atom/proc/prevent_content_explosion()
+ return FALSE
+
+/atom/proc/contents_explosion(severity, target)
+ return //For handling the effects of explosions on contents that would not normally be effected
+
+/atom/proc/ex_act(severity, target)
+ set waitfor = FALSE
+ contents_explosion(severity, target)
+ SEND_SIGNAL(src, COMSIG_ATOM_EX_ACT, severity, target)
+
+/atom/proc/blob_act(obj/structure/blob/B)
+ SEND_SIGNAL(src, COMSIG_ATOM_BLOB_ACT, B)
+ return
+
+/atom/proc/fire_act(exposed_temperature, exposed_volume)
+ SEND_SIGNAL(src, COMSIG_ATOM_FIRE_ACT, exposed_temperature, exposed_volume)
+ return
+
+/atom/proc/hitby(atom/movable/AM, skipcatch, hitpush, blocked, datum/thrownthing/throwingdatum)
+ if(density && !has_gravity(AM)) //thrown stuff bounces off dense stuff in no grav, unless the thrown stuff ends up inside what it hit(embedding, bola, etc...).
+ addtimer(CALLBACK(src, .proc/hitby_react, AM), 2)
+
+/atom/proc/hitby_react(atom/movable/AM)
+ if(AM && isturf(AM.loc))
+ step(AM, turn(AM.dir, 180))
+
+/atom/proc/handle_slip(mob/living/carbon/C, knockdown_amount, obj/O, lube)
+ return
+
+//returns the mob's dna info as a list, to be inserted in an object's blood_DNA list
+/mob/living/proc/get_blood_dna_list()
+ var/blood_id = get_blood_id()
+ if(!(blood_id in GLOB.blood_reagent_types))
+ return
+ return list("ANIMAL DNA" = "Y-")
+
+/mob/living/carbon/get_blood_dna_list()
+ var/blood_id = get_blood_id()
+ if(!(blood_id in GLOB.blood_reagent_types))
+ return
+ var/list/blood_dna = list()
+ if(dna)
+ blood_dna[dna.unique_enzymes] = dna.blood_type
+ else
+ blood_dna["UNKNOWN DNA"] = "X*"
+ return blood_dna
+
+/mob/living/carbon/alien/get_blood_dna_list()
+ return list("UNKNOWN DNA" = "X*")
+
+//to add a mob's dna info into an object's blood_DNA list.
+/atom/proc/transfer_mob_blood_dna(mob/living/L)
+ // Returns 0 if we have that blood already
+ var/new_blood_dna = L.get_blood_dna_list()
+ if(!new_blood_dna)
+ return FALSE
+ LAZYINITLIST(blood_DNA) //if our list of DNA doesn't exist yet, initialise it.
+ var/old_length = blood_DNA.len
+ blood_DNA |= new_blood_dna
+ if(blood_DNA.len == old_length)
+ return FALSE
+ return TRUE
+
+//to add blood dna info to the object's blood_DNA list
+/atom/proc/transfer_blood_dna(list/blood_dna, list/datum/disease/diseases)
+ LAZYINITLIST(blood_DNA)
+ var/old_length = blood_DNA.len
+ blood_DNA |= blood_dna
+ if(blood_DNA.len > old_length)
+ return TRUE
+ //some new blood DNA was added
+
+//to add blood from a mob onto something, and transfer their dna info
+/atom/proc/add_mob_blood(mob/living/M)
+ var/list/blood_dna = M.get_blood_dna_list()
+ if(!blood_dna)
+ return FALSE
+ return add_blood_DNA(blood_dna, M.diseases)
+
+//to add blood onto something, with blood dna info to include.
+/atom/proc/add_blood_DNA(list/blood_dna, list/datum/disease/diseases)
+ return FALSE
+
+/obj/add_blood_DNA(list/blood_dna, list/datum/disease/diseases)
+ return transfer_blood_dna(blood_dna, diseases)
+
+/obj/item/add_blood_DNA(list/blood_dna, list/datum/disease/diseases)
+ . = ..()
+ if(!.)
+ return
+ add_blood_overlay()
+
+/obj/item/proc/add_blood_overlay()
+ if(!blood_DNA.len)
+ return
+ if(initial(icon) && initial(icon_state))
+ blood_splatter_icon = icon(initial(icon), initial(icon_state), , 1) //we only want to apply blood-splatters to the initial icon_state for each object
+ blood_splatter_icon.Blend("#fff", ICON_ADD) //fills the icon_state with white (except where it's transparent)
+ blood_splatter_icon.Blend(icon('icons/effects/blood.dmi', "itemblood"), ICON_MULTIPLY) //adds blood and the remaining white areas become transparant
+ blood_splatter_icon.Blend(blood_DNA_to_color(), ICON_MULTIPLY)
+ add_overlay(blood_splatter_icon)
+
+/obj/item/clothing/gloves/add_blood_DNA(list/blood_dna, list/datum/disease/diseases)
+ . = ..()
+ transfer_blood = rand(2, 4)
+
+/turf/add_blood_DNA(list/blood_dna, list/datum/disease/diseases)
+ var/obj/effect/decal/cleanable/blood/splatter/B = locate() in src
+ if(!B)
+ B = new /obj/effect/decal/cleanable/blood/splatter(src, diseases)
+ B.transfer_blood_dna(blood_dna, diseases) //give blood info to the blood decal.
+ return TRUE //we bloodied the floor
+
+/mob/living/carbon/human/add_blood_DNA(list/blood_dna, list/datum/disease/diseases)
+ if(head)
+ head.add_blood_DNA(blood_dna, diseases)
+ update_inv_head()
+ else if(wear_mask)
+ wear_mask.add_blood_DNA(blood_dna, diseases)
+ update_inv_wear_mask()
+ if(wear_neck)
+ wear_neck.add_blood_DNA(blood_dna, diseases)
+ update_inv_neck()
+ if(wear_suit)
+ wear_suit.add_blood_DNA(blood_dna, diseases)
+ update_inv_wear_suit()
+ else if(w_uniform)
+ w_uniform.add_blood_DNA(blood_dna, diseases)
+ update_inv_w_uniform()
+ if(gloves)
+ var/obj/item/clothing/gloves/G = gloves
+ G.add_blood_DNA(blood_dna, diseases)
+ else
+ transfer_blood_dna(blood_dna, diseases)
+ bloody_hands = rand(2, 4)
+ update_inv_gloves() //handles bloody hands overlays and updating
+ return TRUE
+
+/atom/proc/blood_DNA_to_color()
+ var/list/colors = list()//first we make a list of all bloodtypes present
+ for(var/bloop in blood_DNA)
+ if(colors[blood_DNA[bloop]])
+ colors[blood_DNA[bloop]]++
+ else
+ colors[blood_DNA[bloop]] = 1
+
+ var/final_rgb = BLOOD_COLOR_HUMAN //a default so we don't have white blood graphics if something messed up
+
+ if(colors.len)
+ var/sum = 0 //this is all shitcode, but it works; trust me
+ final_rgb = bloodtype_to_color(colors[1])
+ sum = colors[colors[1]]
+ if(colors.len > 1)
+ var/i = 2
+ while(i <= colors.len)
+ var/tmp = colors[colors[i]]
+ final_rgb = BlendRGB(final_rgb, bloodtype_to_color(colors[i]), tmp/(tmp+sum))
+ sum += tmp
+ i++
+
+ return final_rgb
+
+/atom/proc/clean_blood()
+ . = blood_DNA? TRUE : FALSE
+ blood_DNA = null
+
+/atom/proc/wash_cream()
+ return TRUE
+
+/atom/proc/isinspace()
+ if(isspaceturf(get_turf(src)))
+ return TRUE
+ else
+ return FALSE
+
+/atom/proc/handle_fall()
+ return
+
+/atom/proc/singularity_act()
+ return
+
+/atom/proc/singularity_pull(obj/singularity/S, current_size)
+ SEND_SIGNAL(src, COMSIG_ATOM_SING_PULL, S, current_size)
+
+/atom/proc/acid_act(acidpwr, acid_volume)
+ SEND_SIGNAL(src, COMSIG_ATOM_ACID_ACT, acidpwr, acid_volume)
+
+/atom/proc/emag_act()
+ SEND_SIGNAL(src, COMSIG_ATOM_EMAG_ACT)
+
+/atom/proc/rad_act(strength)
+ SEND_SIGNAL(src, COMSIG_ATOM_RAD_ACT, strength)
+
+/atom/proc/narsie_act()
+ SEND_SIGNAL(src, COMSIG_ATOM_NARSIE_ACT)
+
+/atom/proc/ratvar_act()
+ SEND_SIGNAL(src, COMSIG_ATOM_RATVAR_ACT)
+
+/atom/proc/rcd_vals(mob/user, obj/item/construction/rcd/the_rcd)
+ return FALSE
+
+/atom/proc/rcd_act(mob/user, obj/item/construction/rcd/the_rcd, passed_mode)
+ SEND_SIGNAL(src, COMSIG_ATOM_RCD_ACT, user, the_rcd, passed_mode)
+ return FALSE
+
+/atom/proc/storage_contents_dump_act(obj/item/storage/src_object, mob/user)
+ if(GetComponent(/datum/component/storage))
+ return component_storage_contents_dump_act(src_object, user)
+ return FALSE
+
+/atom/proc/component_storage_contents_dump_act(datum/component/storage/src_object, mob/user)
+ var/list/things = src_object.contents()
+ var/datum/progressbar/progress = new(user, things.len, src)
+ var/datum/component/storage/STR = GetComponent(/datum/component/storage)
+ while (do_after(user, 10, TRUE, src, FALSE, CALLBACK(STR, /datum/component/storage.proc/handle_mass_item_insertion, things, src_object, user, progress)))
+ stoplag(1)
+ qdel(progress)
+ to_chat(user, "You dump as much of [src_object.parent]'s contents into [STR.insert_preposition]to [src] as you can.")
+ STR.orient2hud(user)
+ src_object.orient2hud(user)
+ if(user.active_storage) //refresh the HUD to show the transfered contents
+ user.active_storage.close(user)
+ user.active_storage.show_to(user)
+ return TRUE
+
+/atom/proc/get_dumping_location(obj/item/storage/source,mob/user)
+ return null
+
+//This proc is called on the location of an atom when the atom is Destroy()'d
+/atom/proc/handle_atom_del(atom/A)
+ SEND_SIGNAL(src, COMSIG_ATOM_CONTENTS_DEL, A)
+
+//called when the turf the atom resides on is ChangeTurfed
+/atom/proc/HandleTurfChange(turf/T)
+ for(var/a in src)
+ var/atom/A = a
+ A.HandleTurfChange(T)
+
+//the vision impairment to give to the mob whose perspective is set to that atom (e.g. an unfocused camera giving you an impaired vision when looking through it)
+/atom/proc/get_remote_view_fullscreens(mob/user)
+ return
+
+//the sight changes to give to the mob whose perspective is set to that atom (e.g. A mob with nightvision loses its nightvision while looking through a normal camera)
+/atom/proc/update_remote_sight(mob/living/user)
+ return
+
+
+//Hook for running code when a dir change occurs
+/atom/proc/setDir(newdir)
+ SEND_SIGNAL(src, COMSIG_ATOM_DIR_CHANGE, dir, newdir)
+ dir = newdir
+
+/atom/proc/mech_melee_attack(obj/mecha/M)
+ return
+
+//If a mob logouts/logins in side of an object you can use this proc
+/atom/proc/on_log(login)
+ if(loc)
+ loc.on_log(login)
+
+
+/*
+ Atom Colour Priority System
+ A System that gives finer control over which atom colour to colour the atom with.
+ The "highest priority" one is always displayed as opposed to the default of
+ "whichever was set last is displayed"
+*/
+
+
+/*
+ Adds an instance of colour_type to the atom's atom_colours list
+*/
+/atom/proc/add_atom_colour(coloration, colour_priority)
+ if(!atom_colours || !atom_colours.len)
+ atom_colours = list()
+ atom_colours.len = COLOUR_PRIORITY_AMOUNT //four priority levels currently.
+ if(!coloration)
+ return
+ if(colour_priority > atom_colours.len)
+ return
+ atom_colours[colour_priority] = coloration
+ update_atom_colour()
+
+
+/*
+ Removes an instance of colour_type from the atom's atom_colours list
+*/
+/atom/proc/remove_atom_colour(colour_priority, coloration)
+ if(!atom_colours)
+ atom_colours = list()
+ atom_colours.len = COLOUR_PRIORITY_AMOUNT //four priority levels currently.
+ if(colour_priority > atom_colours.len)
+ return
+ if(coloration && atom_colours[colour_priority] != coloration)
+ return //if we don't have the expected color (for a specific priority) to remove, do nothing
+ atom_colours[colour_priority] = null
+ update_atom_colour()
+
+
+/*
+ Resets the atom's color to null, and then sets it to the highest priority
+ colour available
+*/
+/atom/proc/update_atom_colour()
+ if(!atom_colours)
+ atom_colours = list()
+ atom_colours.len = COLOUR_PRIORITY_AMOUNT //four priority levels currently.
+ color = null
+ for(var/C in atom_colours)
+ if(islist(C))
+ var/list/L = C
+ if(L.len)
+ color = L
+ return
+ else if(C)
+ color = C
+ return
+
+/atom/vv_edit_var(var_name, var_value)
+ if(!GLOB.Debug2)
+ flags_1 |= ADMIN_SPAWNED_1
+ . = ..()
+ switch(var_name)
+ if("color")
+ add_atom_colour(color, ADMIN_COLOUR_PRIORITY)
+
+/atom/vv_get_dropdown()
+ . = ..()
+ . += "---"
+ var/turf/curturf = get_turf(src)
+ if (curturf)
+ .["Jump to"] = "?_src_=holder;[HrefToken()];adminplayerobservecoodjump=1;X=[curturf.x];Y=[curturf.y];Z=[curturf.z]"
+ .["Modify Transform"] = "?_src_=vars;[HrefToken()];modtransform=[REF(src)]"
+ .["Add reagent"] = "?_src_=vars;[HrefToken()];addreagent=[REF(src)]"
+ .["Trigger EM pulse"] = "?_src_=vars;[HrefToken()];emp=[REF(src)]"
+ .["Trigger explosion"] = "?_src_=vars;[HrefToken()];explode=[REF(src)]"
+
+/atom/proc/drop_location()
+ var/atom/L = loc
+ if(!L)
+ return null
+ return L.AllowDrop() ? L : L.drop_location()
+
+/atom/Entered(atom/movable/AM, atom/oldLoc)
+ SEND_SIGNAL(src, COMSIG_ATOM_ENTERED, AM, oldLoc)
+
+/atom/Exit(atom/movable/AM, atom/newLoc)
+ . = ..()
+ if(SEND_SIGNAL(src, COMSIG_ATOM_EXIT, AM, newLoc) & COMPONENT_ATOM_BLOCK_EXIT)
+ return FALSE
+
+/atom/Exited(atom/movable/AM, atom/newLoc)
+ SEND_SIGNAL(src, COMSIG_ATOM_EXITED, AM, newLoc)
+
+/atom/proc/return_temperature()
+ return
+
+// Tool behavior procedure. Redirects to tool-specific procs by default.
+// You can override it to catch all tool interactions, for use in complex deconstruction procs.
+// Just don't forget to return ..() in the end.
+/atom/proc/tool_act(mob/living/user, obj/item/I, tool_type)
+ switch(tool_type)
+ if(TOOL_CROWBAR)
+ return crowbar_act(user, I)
+ if(TOOL_MULTITOOL)
+ return multitool_act(user, I)
+ if(TOOL_SCREWDRIVER)
+ return screwdriver_act(user, I)
+ if(TOOL_WRENCH)
+ return wrench_act(user, I)
+ if(TOOL_WIRECUTTER)
+ return wirecutter_act(user, I)
+ if(TOOL_WELDER)
+ return welder_act(user, I)
+ if(TOOL_ANALYZER)
+ return analyzer_act(user, I)
+
+// Tool-specific behavior procs. To be overridden in subtypes.
+/atom/proc/crowbar_act(mob/living/user, obj/item/I)
+ return
+
+/atom/proc/multitool_act(mob/living/user, obj/item/I)
+ return
+
+/atom/proc/screwdriver_act(mob/living/user, obj/item/I)
+ SEND_SIGNAL(src, COMSIG_ATOM_SCREWDRIVER_ACT, user, I)
+
+/atom/proc/wrench_act(mob/living/user, obj/item/I)
+ return
+
+/atom/proc/wirecutter_act(mob/living/user, obj/item/I)
+ return
+
+/atom/proc/welder_act(mob/living/user, obj/item/I)
+ return
+
+/atom/proc/analyzer_act(mob/living/user, obj/item/I)
+ return
+
+/atom/proc/GenerateTag()
+ return
+
+// Generic logging helper
+/atom/proc/log_message(message, message_type, color=null, log_globally=TRUE)
+ if(!log_globally)
+ return
+
+ var/log_text = "[key_name(src)] [message] [loc_name(src)]"
+ switch(message_type)
+ if(LOG_ATTACK)
+ log_attack(log_text)
+ if(LOG_SAY)
+ log_say(log_text)
+ if(LOG_WHISPER)
+ log_whisper(log_text)
+ if(LOG_EMOTE)
+ log_emote(log_text)
+ if(LOG_DSAY)
+ log_dsay(log_text)
+ if(LOG_PDA)
+ log_pda(log_text)
+ if(LOG_CHAT)
+ log_chat(log_text)
+ if(LOG_COMMENT)
+ log_comment(log_text)
+ if(LOG_TELECOMMS)
+ log_telecomms(log_text)
+ if(LOG_OOC)
+ log_ooc(log_text)
+ if(LOG_ADMIN)
+ log_admin(log_text)
+ if(LOG_ADMIN_PRIVATE)
+ log_admin_private(log_text)
+ if(LOG_ASAY)
+ log_adminsay(log_text)
+ if(LOG_OWNERSHIP)
+ log_game(log_text)
+ if(LOG_GAME)
+ log_game(log_text)
+ else
+ stack_trace("Invalid individual logging type: [message_type]. Defaulting to [LOG_GAME] (LOG_GAME).")
+ log_game(log_text)
+
+// Helper for logging chat messages or other logs with arbitrary inputs (e.g. announcements)
+/atom/proc/log_talk(message, message_type, tag=null, log_globally=TRUE, forced_by=null)
+ var/prefix = tag ? "([tag]) " : ""
+ var/suffix = forced_by ? " FORCED by [forced_by]" : ""
+ log_message("[prefix]\"[message]\"[suffix]", message_type, log_globally=log_globally)
+
+// Helper for logging of messages with only one sender and receiver
+/proc/log_directed_talk(atom/source, atom/target, message, message_type, tag)
+ if(!tag)
+ stack_trace("Unspecified tag for private message")
+ tag = "UNKNOWN"
+
+ source.log_talk(message, message_type, tag="[tag] to [key_name(target)]")
+ if(source != target)
+ target.log_talk(message, message_type, tag="[tag] from [key_name(source)]", log_globally=FALSE)
+
+/*
+Proc for attack log creation, because really why not
+1 argument is the actor performing the action
+2 argument is the target of the action
+3 is a verb describing the action (e.g. punched, throwed, kicked, etc.)
+4 is a tool with which the action was made (usually an item)
+5 is any additional text, which will be appended to the rest of the log line
+*/
+
+/proc/log_combat(atom/user, atom/target, what_done, atom/object=null, addition=null)
+ var/ssource = key_name(user)
+ var/starget = key_name(target)
+
+ var/mob/living/living_target = target
+ var/hp = istype(living_target) ? " (NEWHP: [living_target.health]) " : ""
+
+ var/sobject = ""
+ if(object)
+ sobject = " with [key_name(object)]"
+ var/saddition = ""
+ if(addition)
+ saddition = " [addition]"
+
+ var/postfix = "[sobject][saddition][hp]"
+
+ var/message = "has [what_done] [starget][postfix]"
+ user.log_message(message, LOG_ATTACK, color="red")
+
+ if(user != target)
+ var/reverse_message = "has been [what_done] by [ssource][postfix]"
+ target.log_message(reverse_message, LOG_ATTACK, color="orange", log_globally=FALSE)
+
+// Filter stuff
+/atom/movable/proc/add_filter(name,priority,list/params)
+ if(!filter_data)
+ filter_data = list()
+ var/list/p = params.Copy()
+ p["priority"] = priority
+ filter_data[name] = p
+ update_filters()
+
+/atom/movable/proc/update_filters()
+ filters = null
+ sortTim(filter_data,associative = TRUE)
+ for(var/f in filter_data)
+ var/list/data = filter_data[f]
+ var/list/arguments = data.Copy()
+ arguments -= "priority"
+ filters += filter(arglist(arguments))
+
+/atom/movable/proc/get_filter(name)
+ if(filter_data && filter_data[name])
+ return filters[filter_data.Find(name)]
+
+/atom/movable/proc/remove_filter(name)
+ if(filter_data[name])
+ filter_data -= name
+ update_filters()
+ return TRUE
+
+/atom/proc/intercept_zImpact(atom/movable/AM, levels = 1)
+ . |= SEND_SIGNAL(src, COMSIG_ATOM_INTERCEPT_Z_FALL, AM, levels)
diff --git a/code/game/objects/effects/temporary_visuals/miscellaneous.dm b/code/game/objects/effects/temporary_visuals/miscellaneous.dm
index 3be5e846..b1638844 100644
--- a/code/game/objects/effects/temporary_visuals/miscellaneous.dm
+++ b/code/game/objects/effects/temporary_visuals/miscellaneous.dm
@@ -315,6 +315,11 @@
randomdir = 0
duration = 6
+/obj/effect/temp_visual/desynchronizer
+ name = "desynchronizer field"
+ icon_state = "chronofield"
+ duration = 3
+
/obj/effect/temp_visual/impact_effect
icon_state = "impact_bullet"
duration = 5
diff --git a/code/game/say.dm b/code/game/say.dm
index 9c2ef0a9..8f7c9704 100644
--- a/code/game/say.dm
+++ b/code/game/say.dm
@@ -29,7 +29,7 @@ GLOBAL_LIST_INIT(freqtospan, list(
send_speech(message, 7, src, , spans, message_language=language)
/atom/movable/proc/Hear(message, atom/movable/speaker, message_language, raw_message, radio_freq, list/spans, message_mode)
- SEND_SIGNAL(src, COMSIG_MOVABLE_HEAR, message, speaker, message_language, raw_message, radio_freq, spans, message_mode)
+ SEND_SIGNAL(src, COMSIG_MOVABLE_HEAR, args)
/atom/movable/proc/can_speak()
return 1
diff --git a/code/modules/antagonists/traitor/datum_traitor.dm b/code/modules/antagonists/traitor/datum_traitor.dm
index c5c59156..ba762bea 100644
--- a/code/modules/antagonists/traitor/datum_traitor.dm
+++ b/code/modules/antagonists/traitor/datum_traitor.dm
@@ -49,6 +49,12 @@
A.malf_picker.remove_malf_verbs(A)
qdel(A.malf_picker)
+/datum/antagonist/traitor/proc/handle_hearing(datum/source, list/hearing_args)
+ var/message = hearing_args[HEARING_MESSAGE]
+ message = GLOB.syndicate_code_phrase_regex.Replace(message, "$1")
+ message = GLOB.syndicate_code_response_regex.Replace(message, "$1")
+ hearing_args[HEARING_MESSAGE] = message
+
SSticker.mode.traitors -= owner
if(!silent && owner.current)
to_chat(owner.current," You are no longer the [special_role]! ")
diff --git a/code/modules/client/client_colour.dm b/code/modules/client/client_colour.dm
index f1477fb4..22b15ea8 100644
--- a/code/modules/client/client_colour.dm
+++ b/code/modules/client/client_colour.dm
@@ -111,3 +111,6 @@
/datum/client_colour/monochrome
colour = list(rgb(77,77,77), rgb(150,150,150), rgb(28,28,28), rgb(0,0,0))
priority = INFINITY //we can't see colors anyway!
+
+/datum/client_colour/monochrome/trance
+ priority = 1
\ No newline at end of file
diff --git a/code/modules/mob/living/carbon/say.dm b/code/modules/mob/living/carbon/say.dm
index 452c8f8b..30c962f9 100644
--- a/code/modules/mob/living/carbon/say.dm
+++ b/code/modules/mob/living/carbon/say.dm
@@ -17,18 +17,4 @@
if(T)
. = T.could_speak_in_language(dt)
else
- . = initial(dt.flags) & TONGUELESS_SPEECH
-
-/mob/living/carbon/hear_intercept(message, atom/movable/speaker, datum/language/message_language, raw_message, radio_freq, list/spans, message_mode)
- . = ..()
- if(!client)
- return
- for(var/T in get_traumas())
- var/datum/brain_trauma/trauma = T
- message = trauma.on_hear(message, speaker, message_language, raw_message, radio_freq)
-
- if (src.mind.has_antag_datum(/datum/antagonist/traitor))
- message = GLOB.syndicate_code_phrase_regex.Replace(message, "$1")
- message = GLOB.syndicate_code_response_regex.Replace(message, "$1")
-
- return message
+ . = initial(dt.flags) & TONGUELESS_SPEECH
\ No newline at end of file
diff --git a/code/modules/mob/living/emote.dm b/code/modules/mob/living/emote.dm
index 43293556..2fc3bc0c 100644
--- a/code/modules/mob/living/emote.dm
+++ b/code/modules/mob/living/emote.dm
@@ -1,519 +1,519 @@
-
-/* EMOTE DATUMS */
-/datum/emote/living
- mob_type_allowed_typecache = /mob/living
- mob_type_blacklist_typecache = list(/mob/living/simple_animal/slime, /mob/living/brain)
-
-/datum/emote/living/blush
- key = "blush"
- key_third_person = "blushes"
- message = "blushes."
-
-/datum/emote/living/bow
- key = "bow"
- key_third_person = "bows"
- message = "bows."
- message_param = "bows to %t."
- restraint_check = TRUE
-
-/datum/emote/living/burp
- key = "burp"
- key_third_person = "burps"
- message = "burps."
- emote_type = EMOTE_AUDIBLE
-
-/datum/emote/living/choke
- key = "choke"
- key_third_person = "chokes"
- message = "chokes!"
- emote_type = EMOTE_AUDIBLE
-
-/datum/emote/living/cross
- key = "cross"
- key_third_person = "crosses"
- message = "crosses their arms."
- restraint_check = TRUE
-
-/datum/emote/living/chuckle
- key = "chuckle"
- key_third_person = "chuckles"
- message = "chuckles."
- emote_type = EMOTE_AUDIBLE
-
-/datum/emote/living/collapse
- key = "collapse"
- key_third_person = "collapses"
- message = "collapses!"
- emote_type = EMOTE_AUDIBLE
-
-/datum/emote/living/collapse/run_emote(mob/user, params)
- . = ..()
- if(. && isliving(user))
- var/mob/living/L = user
- L.Unconscious(40)
-
-/datum/emote/living/cough
- key = "cough"
- key_third_person = "coughs"
- message = "coughs!"
- emote_type = EMOTE_AUDIBLE
-
-/datum/emote/living/cough/can_run_emote(mob/user, status_check = TRUE)
- . = ..()
- if(user.reagents && (user.reagents.get_reagent("menthol") || user.reagents.get_reagent("peppermint_patty")))
- return FALSE
-
-/datum/emote/living/dance
- key = "dance"
- key_third_person = "dances"
- message = "dances around happily."
- restraint_check = TRUE
-
-/datum/emote/living/deathgasp
- key = "deathgasp"
- key_third_person = "deathgasps"
- message = "seizes up and falls limp, their eyes dead and lifeless..."
- message_robot = "shudders violently for a moment before falling still, its eyes slowly darkening."
- message_AI = "lets out a flurry of sparks, its screen flickering as its systems slowly halt."
- message_alien = "lets out a waning guttural screech, green blood bubbling from its maw..."
- message_larva = "lets out a sickly hiss of air and falls limply to the floor..."
- message_monkey = "lets out a faint chimper as it collapses and stops moving..."
- message_simple = "stops moving..."
- stat_allowed = UNCONSCIOUS
-
-/datum/emote/living/deathgasp/run_emote(mob/user, params)
- var/mob/living/simple_animal/S = user
- if(istype(S) && S.deathmessage)
- message_simple = S.deathmessage
- . = ..()
- message_simple = initial(message_simple)
- if(. && isalienadult(user))
- playsound(user.loc, 'sound/voice/hiss6.ogg', 80, 1, 1)
-
-/datum/emote/living/drool
- key = "drool"
- key_third_person = "drools"
- message = "drools."
-
-/datum/emote/living/faint
- key = "faint"
- key_third_person = "faints"
- message = "faints."
-
-/datum/emote/living/faint/run_emote(mob/user, params)
- . = ..()
- if(. && isliving(user))
- var/mob/living/L = user
- L.SetSleeping(200)
-
-/datum/emote/living/flap
- key = "flap"
- key_third_person = "flaps"
- message = "flaps their wings."
- restraint_check = TRUE
- var/wing_time = 20
-
-/datum/emote/living/flap/run_emote(mob/user, params)
- . = ..()
- if(. && ishuman(user))
- var/mob/living/carbon/human/H = user
- var/open = FALSE
- if(H.dna.features["wings"] != "None")
- if("wingsopen" in H.dna.species.mutant_bodyparts)
- open = TRUE
- H.CloseWings()
- else
- H.OpenWings()
- addtimer(CALLBACK(H, open ? /mob/living/carbon/human.proc/OpenWings : /mob/living/carbon/human.proc/CloseWings), wing_time)
-
-/datum/emote/living/flap/aflap
- key = "aflap"
- key_third_person = "aflaps"
- message = "flaps their wings ANGRILY!"
- restraint_check = TRUE
- wing_time = 10
-
-/datum/emote/living/frown
- key = "frown"
- key_third_person = "frowns"
- message = "frowns."
-
-/datum/emote/living/gag
- key = "gag"
- key_third_person = "gags"
- message = "gags."
- emote_type = EMOTE_AUDIBLE
-
-/datum/emote/living/gasp
- key = "gasp"
- key_third_person = "gasps"
- message = "gasps!"
- emote_type = EMOTE_AUDIBLE
- stat_allowed = UNCONSCIOUS
-
-/datum/emote/living/giggle
- key = "giggle"
- key_third_person = "giggles"
- message = "giggles."
- message_mime = "giggles silently!"
- emote_type = EMOTE_AUDIBLE
-
-/datum/emote/living/glare
- key = "glare"
- key_third_person = "glares"
- message = "glares."
- message_param = "glares at %t."
- emote_type = EMOTE_AUDIBLE
-
-/datum/emote/living/grin
- key = "grin"
- key_third_person = "grins"
- message = "grins."
-
-/datum/emote/living/groan
- key = "groan"
- key_third_person = "groans"
- message = "groans!"
- message_mime = "appears to groan!"
-
-/datum/emote/living/grimace
- key = "grimace"
- key_third_person = "grimaces"
- message = "grimaces."
-
-/datum/emote/living/jump
- key = "jump"
- key_third_person = "jumps"
- message = "jumps!"
- restraint_check = TRUE
-
-/datum/emote/living/kiss
- key = "kiss"
- key_third_person = "kisses"
- message = "blows a kiss."
- message_param = "blows a kiss to %t."
- emote_type = EMOTE_AUDIBLE
-
-/datum/emote/living/laugh
- key = "laugh"
- key_third_person = "laughs"
- message = "laughs."
- message_mime = "laughs silently!"
- emote_type = EMOTE_AUDIBLE
-
-/datum/emote/living/laugh/can_run_emote(mob/living/user, status_check = TRUE)
- . = ..()
- if(. && iscarbon(user))
- var/mob/living/carbon/C = user
- return !C.silent
-
-/datum/emote/living/laugh/run_emote(mob/user, params)
- . = ..()
- if(. && iscarbon(user)) //Citadel Edit because this is hilarious
- var/mob/living/carbon/C = user
- if(!C.mind || C.mind.miming)
- return
- if(iscatperson(C)) //we ask for is cat first because they're a subtype that tests true for ishumanbasic because HERESY
- playsound(C, pick('sound/voice/catpeople/nyahaha1.ogg',
- 'sound/voice/catpeople/nyahaha2.ogg',
- 'sound/voice/catpeople/nyaha.ogg',
- 'sound/voice/catpeople/nyahehe.ogg'),
- 50, 1)
- return
- if(ishumanbasic(C))
- if(user.gender == FEMALE)
- playsound(C, 'sound/voice/human/womanlaugh.ogg', 50, 1)
- else
- playsound(C, pick('sound/voice/human/manlaugh1.ogg', 'sound/voice/human/manlaugh2.ogg'), 50, 1)
-
-/datum/emote/living/look
- key = "look"
- key_third_person = "looks"
- message = "looks."
- message_param = "looks at %t."
-
-/datum/emote/living/nod
- key = "nod"
- key_third_person = "nods"
- message = "nods."
- message_param = "nods at %t."
-
-/datum/emote/living/point
- key = "point"
- key_third_person = "points"
- message = "points."
- message_param = "points at %t."
- restraint_check = TRUE
-
-/datum/emote/living/point/run_emote(mob/user, params)
- message_param = initial(message_param) // reset
- if(ishuman(user))
- var/mob/living/carbon/human/H = user
- if(H.get_num_arms() == 0)
- if(H.get_num_legs() != 0)
- message_param = "tries to point at %t with a leg, falling down in the process!"
- H.Knockdown(20)
- else
- message_param = "bumps [user.p_their()] head on the ground trying to motion towards %t."
- H.adjustOrganLoss(ORGAN_SLOT_BRAIN, 5)
- ..()
-
-/datum/emote/living/pout
- key = "pout"
- key_third_person = "pouts"
- message = "pouts."
- emote_type = EMOTE_AUDIBLE
-
-/datum/emote/living/scream
- key = "scream"
- key_third_person = "screams"
- message = "screams."
- message_mime = "acts out a scream!"
- emote_type = EMOTE_AUDIBLE
-
-/datum/emote/living/scowl
- key = "scowl"
- key_third_person = "scowls"
- message = "scowls."
- emote_type = EMOTE_AUDIBLE
-
-/datum/emote/living/shake
- key = "shake"
- key_third_person = "shakes"
- message = "shakes their head."
- emote_type = EMOTE_AUDIBLE
-
-/datum/emote/living/shiver
- key = "shiver"
- key_third_person = "shiver"
- message = "shivers."
- emote_type = EMOTE_AUDIBLE
-
-/datum/emote/living/sigh
- key = "sigh"
- key_third_person = "sighs"
- message = "sighs."
- emote_type = EMOTE_AUDIBLE
-
-/datum/emote/living/sit
- key = "sit"
- key_third_person = "sits"
- message = "sits down."
-
-/datum/emote/living/smile
- key = "smile"
- key_third_person = "smiles"
- message = "smiles."
-
-/datum/emote/living/sneeze
- key = "sneeze"
- key_third_person = "sneezes"
- message = "sneezes."
- emote_type = EMOTE_AUDIBLE
-
-/datum/emote/living/smug
- key = "smug"
- key_third_person = "smugs"
- message = "grins smugly."
-
-/datum/emote/living/sniff
- key = "sniff"
- key_third_person = "sniffs"
- message = "sniffs."
- emote_type = EMOTE_AUDIBLE
-
-/datum/emote/living/snore
- key = "snore"
- key_third_person = "snores"
- message = "snores."
- message_mime = "sleeps soundly."
- emote_type = EMOTE_AUDIBLE
- stat_allowed = UNCONSCIOUS
-
-/datum/emote/living/stare
- key = "stare"
- key_third_person = "stares"
- message = "stares."
- message_param = "stares at %t."
-
-/datum/emote/living/strech
- key = "stretch"
- key_third_person = "stretches"
- message = "stretches their arms."
-
-/datum/emote/living/sulk
- key = "sulk"
- key_third_person = "sulks"
- message = "sulks down sadly."
-
-/datum/emote/living/surrender
- key = "surrender"
- key_third_person = "surrenders"
- message = "puts their hands on their head and falls to the ground, they surrender!"
- emote_type = EMOTE_AUDIBLE
-
-/datum/emote/living/surrender/run_emote(mob/user, params)
- . = ..()
- if(. && isliving(user))
- var/mob/living/L = user
- L.Knockdown(200)
-
-/datum/emote/living/sway
- key = "sway"
- key_third_person = "sways"
- message = "sways around dizzily."
-
-/datum/emote/living/tremble
- key = "tremble"
- key_third_person = "trembles"
- message = "trembles in fear!"
-
-/datum/emote/living/twitch
- key = "twitch"
- key_third_person = "twitches"
- message = "twitches violently."
-
-/datum/emote/living/twitch_s
- key = "twitch_s"
- message = "twitches."
-
-/datum/emote/living/wave
- key = "wave"
- key_third_person = "waves"
- message = "waves."
-
-/datum/emote/living/whimper
- key = "whimper"
- key_third_person = "whimpers"
- message = "whimpers."
- message_mime = "appears hurt."
-
-/datum/emote/living/wsmile
- key = "wsmile"
- key_third_person = "wsmiles"
- message = "smiles weakly."
-
-/datum/emote/living/yawn
- key = "yawn"
- key_third_person = "yawns"
- message = "yawns."
- emote_type = EMOTE_AUDIBLE
-
-/datum/emote/living/custom
- key = "me"
- key_third_person = "custom"
- message = null
-
-/datum/emote/living/custom/proc/check_invalid(mob/user, input)
- . = TRUE
- if(copytext(input,1,5) == "says")
- to_chat(user, "Invalid emote.")
- else if(copytext(input,1,9) == "exclaims")
- to_chat(user, "Invalid emote.")
- else if(copytext(input,1,6) == "yells")
- to_chat(user, "Invalid emote.")
- else if(copytext(input,1,5) == "asks")
- to_chat(user, "Invalid emote.")
- else
- . = FALSE
-
-/datum/emote/living/custom/run_emote(mob/user, params, type_override = null)
- if(jobban_isbanned(user, "emote"))
- to_chat(user, "You cannot send custom emotes (banned).")
- return FALSE
- else if(QDELETED(user))
- return FALSE
- else if(user.client && user.client.prefs.muted & MUTE_IC)
- to_chat(user, "You cannot send IC messages (muted).")
- return FALSE
- else if(!params)
- var/custom_emote = copytext(sanitize(input("Choose an emote to display.") as message|null), 1, MAX_MESSAGE_LEN) //CIT CHANGE - expands emote textbox
- if(custom_emote && !check_invalid(user, custom_emote))
- var/type = input("Is this a visible or hearable emote?") as null|anything in list("Visible", "Hearable")
- switch(type)
- if("Visible")
- emote_type = EMOTE_VISIBLE
- if("Hearable")
- emote_type = EMOTE_AUDIBLE
- else
- alert("Unable to use this emote, must be either hearable or visible.")
- return
- message = custom_emote
- else
- message = params
- if(type_override)
- emote_type = type_override
- message = user.say_emphasis(message)
- . = ..()
- message = null
- emote_type = EMOTE_VISIBLE
-
-/datum/emote/living/custom/replace_pronoun(mob/user, message)
- return message
-
-/datum/emote/living/help
- key = "help"
-
-/datum/emote/living/help/run_emote(mob/user, params)
- var/list/keys = list()
- var/list/message = list("Available emotes, you can use them with say \"*emote\": ")
-
- var/datum/emote/E
- var/list/emote_list = E.emote_list
- for(var/e in emote_list)
- if(e in keys)
- continue
- E = emote_list[e]
- if(E.can_run_emote(user, status_check = FALSE))
- keys += E.key
-
- keys = sortList(keys)
-
- for(var/emote in keys)
- if(LAZYLEN(message) > 1)
- message += ", [emote]"
- else
- message += "[emote]"
-
- message += "."
-
- message = jointext(message, "")
-
- to_chat(user, message)
-
-/datum/emote/sound/beep
- key = "beep"
- key_third_person = "beeps"
- message = "beeps."
- message_param = "beeps at %t."
- sound = 'sound/machines/twobeep.ogg'
- mob_type_allowed_typecache = list(/mob/living/brain, /mob/living/silicon, /mob/living/carbon/human)
-
-/datum/emote/living/circle
- key = "circle"
- key_third_person = "circles"
- restraint_check = TRUE
-
-/datum/emote/living/circle/run_emote(mob/user, params)
- . = ..()
- var/obj/item/circlegame/N = new(user)
- if(user.put_in_hands(N))
- to_chat(user, "You make a circle with your hand.")
- else
- qdel(N)
- to_chat(user, "You don't have any free hands to make a circle with.")
-
-/datum/emote/living/slap
- key = "slap"
- key_third_person = "slaps"
- restraint_check = TRUE
-
-/datum/emote/living/slap/run_emote(mob/user, params)
- . = ..()
- if(!.)
- return
- var/obj/item/slapper/N = new(user)
- if(user.put_in_hands(N))
- to_chat(user, "You ready your slapping hand.")
- else
- to_chat(user, "You're incapable of slapping in your current state.")
+
+/* EMOTE DATUMS */
+/datum/emote/living
+ mob_type_allowed_typecache = /mob/living
+ mob_type_blacklist_typecache = list(/mob/living/simple_animal/slime, /mob/living/brain)
+
+/datum/emote/living/blush
+ key = "blush"
+ key_third_person = "blushes"
+ message = "blushes."
+
+/datum/emote/living/bow
+ key = "bow"
+ key_third_person = "bows"
+ message = "bows."
+ message_param = "bows to %t."
+ restraint_check = TRUE
+
+/datum/emote/living/burp
+ key = "burp"
+ key_third_person = "burps"
+ message = "burps."
+ emote_type = EMOTE_AUDIBLE
+
+/datum/emote/living/choke
+ key = "choke"
+ key_third_person = "chokes"
+ message = "chokes!"
+ emote_type = EMOTE_AUDIBLE
+
+/datum/emote/living/cross
+ key = "cross"
+ key_third_person = "crosses"
+ message = "crosses their arms."
+ restraint_check = TRUE
+
+/datum/emote/living/chuckle
+ key = "chuckle"
+ key_third_person = "chuckles"
+ message = "chuckles."
+ emote_type = EMOTE_AUDIBLE
+
+/datum/emote/living/collapse
+ key = "collapse"
+ key_third_person = "collapses"
+ message = "collapses!"
+ emote_type = EMOTE_AUDIBLE
+
+/datum/emote/living/collapse/run_emote(mob/user, params)
+ . = ..()
+ if(. && isliving(user))
+ var/mob/living/L = user
+ L.Unconscious(40)
+
+/datum/emote/living/cough
+ key = "cough"
+ key_third_person = "coughs"
+ message = "coughs!"
+ emote_type = EMOTE_AUDIBLE
+
+/datum/emote/living/cough/can_run_emote(mob/user, status_check = TRUE , intentional)
+ . = ..()
+ if(HAS_TRAIT(user, TRAIT_SOOTHED_THROAT))
+ return FALSE
+
+/datum/emote/living/dance
+ key = "dance"
+ key_third_person = "dances"
+ message = "dances around happily."
+ restraint_check = TRUE
+
+/datum/emote/living/deathgasp
+ key = "deathgasp"
+ key_third_person = "deathgasps"
+ message = "seizes up and falls limp, their eyes dead and lifeless..."
+ message_robot = "shudders violently for a moment before falling still, its eyes slowly darkening."
+ message_AI = "lets out a flurry of sparks, its screen flickering as its systems slowly halt."
+ message_alien = "lets out a waning guttural screech, green blood bubbling from its maw..."
+ message_larva = "lets out a sickly hiss of air and falls limply to the floor..."
+ message_monkey = "lets out a faint chimper as it collapses and stops moving..."
+ message_simple = "stops moving..."
+ stat_allowed = UNCONSCIOUS
+
+/datum/emote/living/deathgasp/run_emote(mob/user, params)
+ var/mob/living/simple_animal/S = user
+ if(istype(S) && S.deathmessage)
+ message_simple = S.deathmessage
+ . = ..()
+ message_simple = initial(message_simple)
+ if(. && isalienadult(user))
+ playsound(user.loc, 'sound/voice/hiss6.ogg', 80, 1, 1)
+
+/datum/emote/living/drool
+ key = "drool"
+ key_third_person = "drools"
+ message = "drools."
+
+/datum/emote/living/faint
+ key = "faint"
+ key_third_person = "faints"
+ message = "faints."
+
+/datum/emote/living/faint/run_emote(mob/user, params)
+ . = ..()
+ if(. && isliving(user))
+ var/mob/living/L = user
+ L.SetSleeping(200)
+
+/datum/emote/living/flap
+ key = "flap"
+ key_third_person = "flaps"
+ message = "flaps their wings."
+ restraint_check = TRUE
+ var/wing_time = 20
+
+/datum/emote/living/flap/run_emote(mob/user, params)
+ . = ..()
+ if(. && ishuman(user))
+ var/mob/living/carbon/human/H = user
+ var/open = FALSE
+ if(H.dna.features["wings"] != "None")
+ if("wingsopen" in H.dna.species.mutant_bodyparts)
+ open = TRUE
+ H.CloseWings()
+ else
+ H.OpenWings()
+ addtimer(CALLBACK(H, open ? /mob/living/carbon/human.proc/OpenWings : /mob/living/carbon/human.proc/CloseWings), wing_time)
+
+/datum/emote/living/flap/aflap
+ key = "aflap"
+ key_third_person = "aflaps"
+ message = "flaps their wings ANGRILY!"
+ restraint_check = TRUE
+ wing_time = 10
+
+/datum/emote/living/frown
+ key = "frown"
+ key_third_person = "frowns"
+ message = "frowns."
+
+/datum/emote/living/gag
+ key = "gag"
+ key_third_person = "gags"
+ message = "gags."
+ emote_type = EMOTE_AUDIBLE
+
+/datum/emote/living/gasp
+ key = "gasp"
+ key_third_person = "gasps"
+ message = "gasps!"
+ emote_type = EMOTE_AUDIBLE
+ stat_allowed = UNCONSCIOUS
+
+/datum/emote/living/giggle
+ key = "giggle"
+ key_third_person = "giggles"
+ message = "giggles."
+ message_mime = "giggles silently!"
+ emote_type = EMOTE_AUDIBLE
+
+/datum/emote/living/glare
+ key = "glare"
+ key_third_person = "glares"
+ message = "glares."
+ message_param = "glares at %t."
+ emote_type = EMOTE_AUDIBLE
+
+/datum/emote/living/grin
+ key = "grin"
+ key_third_person = "grins"
+ message = "grins."
+
+/datum/emote/living/groan
+ key = "groan"
+ key_third_person = "groans"
+ message = "groans!"
+ message_mime = "appears to groan!"
+
+/datum/emote/living/grimace
+ key = "grimace"
+ key_third_person = "grimaces"
+ message = "grimaces."
+
+/datum/emote/living/jump
+ key = "jump"
+ key_third_person = "jumps"
+ message = "jumps!"
+ restraint_check = TRUE
+
+/datum/emote/living/kiss
+ key = "kiss"
+ key_third_person = "kisses"
+ message = "blows a kiss."
+ message_param = "blows a kiss to %t."
+ emote_type = EMOTE_AUDIBLE
+
+/datum/emote/living/laugh
+ key = "laugh"
+ key_third_person = "laughs"
+ message = "laughs."
+ message_mime = "laughs silently!"
+ emote_type = EMOTE_AUDIBLE
+
+/datum/emote/living/laugh/can_run_emote(mob/living/user, status_check = TRUE)
+ . = ..()
+ if(. && iscarbon(user))
+ var/mob/living/carbon/C = user
+ return !C.silent
+
+/datum/emote/living/laugh/run_emote(mob/user, params)
+ . = ..()
+ if(. && iscarbon(user)) //Citadel Edit because this is hilarious
+ var/mob/living/carbon/C = user
+ if(!C.mind || C.mind.miming)
+ return
+ if(iscatperson(C)) //we ask for is cat first because they're a subtype that tests true for ishumanbasic because HERESY
+ playsound(C, pick('sound/voice/catpeople/nyahaha1.ogg',
+ 'sound/voice/catpeople/nyahaha2.ogg',
+ 'sound/voice/catpeople/nyaha.ogg',
+ 'sound/voice/catpeople/nyahehe.ogg'),
+ 50, 1)
+ return
+ if(ishumanbasic(C))
+ if(user.gender == FEMALE)
+ playsound(C, 'sound/voice/human/womanlaugh.ogg', 50, 1)
+ else
+ playsound(C, pick('sound/voice/human/manlaugh1.ogg', 'sound/voice/human/manlaugh2.ogg'), 50, 1)
+
+/datum/emote/living/look
+ key = "look"
+ key_third_person = "looks"
+ message = "looks."
+ message_param = "looks at %t."
+
+/datum/emote/living/nod
+ key = "nod"
+ key_third_person = "nods"
+ message = "nods."
+ message_param = "nods at %t."
+
+/datum/emote/living/point
+ key = "point"
+ key_third_person = "points"
+ message = "points."
+ message_param = "points at %t."
+ restraint_check = TRUE
+
+/datum/emote/living/point/run_emote(mob/user, params)
+ message_param = initial(message_param) // reset
+ if(ishuman(user))
+ var/mob/living/carbon/human/H = user
+ if(H.get_num_arms() == 0)
+ if(H.get_num_legs() != 0)
+ message_param = "tries to point at %t with a leg, falling down in the process!"
+ H.Knockdown(20)
+ else
+ message_param = "bumps [user.p_their()] head on the ground trying to motion towards %t."
+ H.adjustOrganLoss(ORGAN_SLOT_BRAIN, 5)
+ ..()
+
+/datum/emote/living/pout
+ key = "pout"
+ key_third_person = "pouts"
+ message = "pouts."
+ emote_type = EMOTE_AUDIBLE
+
+/datum/emote/living/scream
+ key = "scream"
+ key_third_person = "screams"
+ message = "screams."
+ message_mime = "acts out a scream!"
+ emote_type = EMOTE_AUDIBLE
+
+/datum/emote/living/scowl
+ key = "scowl"
+ key_third_person = "scowls"
+ message = "scowls."
+ emote_type = EMOTE_AUDIBLE
+
+/datum/emote/living/shake
+ key = "shake"
+ key_third_person = "shakes"
+ message = "shakes their head."
+ emote_type = EMOTE_AUDIBLE
+
+/datum/emote/living/shiver
+ key = "shiver"
+ key_third_person = "shiver"
+ message = "shivers."
+ emote_type = EMOTE_AUDIBLE
+
+/datum/emote/living/sigh
+ key = "sigh"
+ key_third_person = "sighs"
+ message = "sighs."
+ emote_type = EMOTE_AUDIBLE
+
+/datum/emote/living/sit
+ key = "sit"
+ key_third_person = "sits"
+ message = "sits down."
+
+/datum/emote/living/smile
+ key = "smile"
+ key_third_person = "smiles"
+ message = "smiles."
+
+/datum/emote/living/sneeze
+ key = "sneeze"
+ key_third_person = "sneezes"
+ message = "sneezes."
+ emote_type = EMOTE_AUDIBLE
+
+/datum/emote/living/smug
+ key = "smug"
+ key_third_person = "smugs"
+ message = "grins smugly."
+
+/datum/emote/living/sniff
+ key = "sniff"
+ key_third_person = "sniffs"
+ message = "sniffs."
+ emote_type = EMOTE_AUDIBLE
+
+/datum/emote/living/snore
+ key = "snore"
+ key_third_person = "snores"
+ message = "snores."
+ message_mime = "sleeps soundly."
+ emote_type = EMOTE_AUDIBLE
+ stat_allowed = UNCONSCIOUS
+
+/datum/emote/living/stare
+ key = "stare"
+ key_third_person = "stares"
+ message = "stares."
+ message_param = "stares at %t."
+
+/datum/emote/living/strech
+ key = "stretch"
+ key_third_person = "stretches"
+ message = "stretches their arms."
+
+/datum/emote/living/sulk
+ key = "sulk"
+ key_third_person = "sulks"
+ message = "sulks down sadly."
+
+/datum/emote/living/surrender
+ key = "surrender"
+ key_third_person = "surrenders"
+ message = "puts their hands on their head and falls to the ground, they surrender!"
+ emote_type = EMOTE_AUDIBLE
+
+/datum/emote/living/surrender/run_emote(mob/user, params)
+ . = ..()
+ if(. && isliving(user))
+ var/mob/living/L = user
+ L.Knockdown(200)
+
+/datum/emote/living/sway
+ key = "sway"
+ key_third_person = "sways"
+ message = "sways around dizzily."
+
+/datum/emote/living/tremble
+ key = "tremble"
+ key_third_person = "trembles"
+ message = "trembles in fear!"
+
+/datum/emote/living/twitch
+ key = "twitch"
+ key_third_person = "twitches"
+ message = "twitches violently."
+
+/datum/emote/living/twitch_s
+ key = "twitch_s"
+ message = "twitches."
+
+/datum/emote/living/wave
+ key = "wave"
+ key_third_person = "waves"
+ message = "waves."
+
+/datum/emote/living/whimper
+ key = "whimper"
+ key_third_person = "whimpers"
+ message = "whimpers."
+ message_mime = "appears hurt."
+
+/datum/emote/living/wsmile
+ key = "wsmile"
+ key_third_person = "wsmiles"
+ message = "smiles weakly."
+
+/datum/emote/living/yawn
+ key = "yawn"
+ key_third_person = "yawns"
+ message = "yawns."
+ emote_type = EMOTE_AUDIBLE
+
+/datum/emote/living/custom
+ key = "me"
+ key_third_person = "custom"
+ message = null
+
+/datum/emote/living/custom/proc/check_invalid(mob/user, input)
+ . = TRUE
+ if(copytext(input,1,5) == "says")
+ to_chat(user, "Invalid emote.")
+ else if(copytext(input,1,9) == "exclaims")
+ to_chat(user, "Invalid emote.")
+ else if(copytext(input,1,6) == "yells")
+ to_chat(user, "Invalid emote.")
+ else if(copytext(input,1,5) == "asks")
+ to_chat(user, "Invalid emote.")
+ else
+ . = FALSE
+
+/datum/emote/living/custom/run_emote(mob/user, params, type_override = null)
+ if(jobban_isbanned(user, "emote"))
+ to_chat(user, "You cannot send custom emotes (banned).")
+ return FALSE
+ else if(QDELETED(user))
+ return FALSE
+ else if(user.client && user.client.prefs.muted & MUTE_IC)
+ to_chat(user, "You cannot send IC messages (muted).")
+ return FALSE
+ else if(!params)
+ var/custom_emote = copytext(sanitize(input("Choose an emote to display.") as message|null), 1, MAX_MESSAGE_LEN) //CIT CHANGE - expands emote textbox
+ if(custom_emote && !check_invalid(user, custom_emote))
+ var/type = input("Is this a visible or hearable emote?") as null|anything in list("Visible", "Hearable")
+ switch(type)
+ if("Visible")
+ emote_type = EMOTE_VISIBLE
+ if("Hearable")
+ emote_type = EMOTE_AUDIBLE
+ else
+ alert("Unable to use this emote, must be either hearable or visible.")
+ return
+ message = custom_emote
+ else
+ message = params
+ if(type_override)
+ emote_type = type_override
+ message = user.say_emphasis(message)
+ . = ..()
+ message = null
+ emote_type = EMOTE_VISIBLE
+
+/datum/emote/living/custom/replace_pronoun(mob/user, message)
+ return message
+
+/datum/emote/living/help
+ key = "help"
+
+/datum/emote/living/help/run_emote(mob/user, params)
+ var/list/keys = list()
+ var/list/message = list("Available emotes, you can use them with say \"*emote\": ")
+
+ var/datum/emote/E
+ var/list/emote_list = E.emote_list
+ for(var/e in emote_list)
+ if(e in keys)
+ continue
+ E = emote_list[e]
+ if(E.can_run_emote(user, status_check = FALSE))
+ keys += E.key
+
+ keys = sortList(keys)
+
+ for(var/emote in keys)
+ if(LAZYLEN(message) > 1)
+ message += ", [emote]"
+ else
+ message += "[emote]"
+
+ message += "."
+
+ message = jointext(message, "")
+
+ to_chat(user, message)
+
+/datum/emote/sound/beep
+ key = "beep"
+ key_third_person = "beeps"
+ message = "beeps."
+ message_param = "beeps at %t."
+ sound = 'sound/machines/twobeep.ogg'
+ mob_type_allowed_typecache = list(/mob/living/brain, /mob/living/silicon, /mob/living/carbon/human)
+
+/datum/emote/living/circle
+ key = "circle"
+ key_third_person = "circles"
+ restraint_check = TRUE
+
+/datum/emote/living/circle/run_emote(mob/user, params)
+ . = ..()
+ var/obj/item/circlegame/N = new(user)
+ if(user.put_in_hands(N))
+ to_chat(user, "You make a circle with your hand.")
+ else
+ qdel(N)
+ to_chat(user, "You don't have any free hands to make a circle with.")
+
+/datum/emote/living/slap
+ key = "slap"
+ key_third_person = "slaps"
+ restraint_check = TRUE
+
+/datum/emote/living/slap/run_emote(mob/user, params)
+ . = ..()
+ if(!.)
+ return
+ var/obj/item/slapper/N = new(user)
+ if(user.put_in_hands(N))
+ to_chat(user, "You ready your slapping hand.")
+ else
+ to_chat(user, "You're incapable of slapping in your current state.")
diff --git a/code/modules/reagents/chemistry/reagents/alcohol_reagents.dm b/code/modules/reagents/chemistry/reagents/alcohol_reagents.dm
index 0cc469dd..a9f9bb48 100644
--- a/code/modules/reagents/chemistry/reagents/alcohol_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/alcohol_reagents.dm
@@ -1743,6 +1743,7 @@ datum/reagent/consumable/ethanol/creme_de_coconut
value = 2
/datum/reagent/consumable/ethanol/peppermint_patty/on_mob_life(mob/living/carbon/M)
+ M.apply_status_effect(/datum/status_effect/throat_soothed)
M.adjust_bodytemperature(5 * TEMPERATURE_DAMAGE_COEFFICIENT, 0, BODYTEMP_NORMAL)
..()
diff --git a/code/modules/reagents/chemistry/reagents/drink_reagents.dm b/code/modules/reagents/chemistry/reagents/drink_reagents.dm
index b27d66e2..55356066 100644
--- a/code/modules/reagents/chemistry/reagents/drink_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/drink_reagents.dm
@@ -760,6 +760,10 @@
glass_name = "glass of menthol"
glass_desc = "Tastes naturally minty, and imparts a very mild numbing sensation."
+/datum/reagent/consumable/menthol/on_mob_life(mob/living/L)
+ L.apply_status_effect(/datum/status_effect/throat_soothed)
+ ..()
+
/datum/reagent/consumable/grenadine
name = "Grenadine"
description = "Not cherry flavored!"
diff --git a/code/modules/research/designs/bluespace_designs.dm b/code/modules/research/designs/bluespace_designs.dm
index b7a201f5..ec4309b6 100644
--- a/code/modules/research/designs/bluespace_designs.dm
+++ b/code/modules/research/designs/bluespace_designs.dm
@@ -1,76 +1,86 @@
-
-/////////////////////////////////////////
-//////////////Blue Space/////////////////
-/////////////////////////////////////////
-
-/datum/design/beacon
- name = "Tracking Beacon"
- desc = "A blue space tracking beacon."
- id = "beacon"
- build_type = PROTOLATHE
- materials = list(MAT_METAL = 150, MAT_GLASS = 100)
- build_path = /obj/item/beacon
- category = list("Bluespace Designs")
- departmental_flags = DEPARTMENTAL_FLAG_SCIENCE | DEPARTMENTAL_FLAG_ENGINEERING | DEPARTMENTAL_FLAG_CARGO | DEPARTMENTAL_FLAG_SECURITY
-
-/datum/design/bag_holding
- name = "Bag of Holding"
- desc = "A backpack that opens into a localized pocket of bluespace."
- id = "bag_holding"
- build_type = PROTOLATHE
- materials = list(MAT_GOLD = 3000, MAT_DIAMOND = 1500, MAT_URANIUM = 250, MAT_BLUESPACE = 2000)
- build_path = /obj/item/storage/backpack/holding
- category = list("Bluespace Designs")
- dangerous_construction = TRUE
- departmental_flags = DEPARTMENTAL_FLAG_SCIENCE
-
-/datum/design/satchel_holding
- name = "Satchel of Holding"
- desc = "A satchel that opens into a localized pocket of bluespace."
- id = "satchel_holding"
- build_type = PROTOLATHE
- materials = list(MAT_GOLD = 3000, MAT_DIAMOND = 1500, MAT_URANIUM = 250, MAT_BLUESPACE = 2000)
- build_path = /obj/item/storage/backpack/holding/satchel
- category = list("Bluespace Designs")
- dangerous_construction = TRUE
- departmental_flags = DEPARTMENTAL_FLAG_SCIENCE
-
-/datum/design/biobag_holding
- name = "Bio Bag of Holding"
- desc = "A chemical holding thingy. Mostly used for xenobiology."
- id = "biobag_holding"
- build_type = PROTOLATHE
- materials = list(MAT_GOLD = 1500, MAT_DIAMOND = 750, MAT_URANIUM = 250, MAT_BLUESPACE = 1000)
- build_path = /obj/item/storage/bag/bio/holding
- category = list("Bluespace Designs")
- departmental_flags = DEPARTMENTAL_FLAG_SCIENCE
-
-/datum/design/bluespace_crystal
- name = "Artificial Bluespace Crystal"
- desc = "A small blue crystal with mystical properties."
- id = "bluespace_crystal"
- build_type = PROTOLATHE
- materials = list(MAT_DIAMOND = 1500, MAT_PLASMA = 1500)
- build_path = /obj/item/stack/ore/bluespace_crystal/artificial
- category = list("Bluespace Designs")
- departmental_flags = DEPARTMENTAL_FLAG_SCIENCE
-
-/datum/design/telesci_gps
- name = "GPS Device"
- desc = "Little thingie that can track its position at all times."
- id = "telesci_gps"
- build_type = PROTOLATHE
- materials = list(MAT_METAL = 500, MAT_GLASS = 1000)
- build_path = /obj/item/gps
- category = list("Bluespace Designs")
- departmental_flags = DEPARTMENTAL_FLAG_SCIENCE | DEPARTMENTAL_FLAG_ENGINEERING | DEPARTMENTAL_FLAG_CARGO
-
-/datum/design/miningsatchel_holding
- name = "Mining Satchel of Holding"
- desc = "A mining satchel that can hold an infinite amount of ores."
- id = "minerbag_holding"
- build_type = PROTOLATHE
- materials = list(MAT_GOLD = 250, MAT_URANIUM = 500) //quite cheap, for more convenience
- build_path = /obj/item/storage/bag/ore/holding
- category = list("Bluespace Designs")
- departmental_flags = DEPARTMENTAL_FLAG_CARGO
+
+/////////////////////////////////////////
+//////////////Blue Space/////////////////
+/////////////////////////////////////////
+
+/datum/design/beacon
+ name = "Tracking Beacon"
+ desc = "A blue space tracking beacon."
+ id = "beacon"
+ build_type = PROTOLATHE
+ materials = list(MAT_METAL = 150, MAT_GLASS = 100)
+ build_path = /obj/item/beacon
+ category = list("Bluespace Designs")
+ departmental_flags = DEPARTMENTAL_FLAG_SCIENCE | DEPARTMENTAL_FLAG_ENGINEERING | DEPARTMENTAL_FLAG_CARGO | DEPARTMENTAL_FLAG_SECURITY
+
+/datum/design/bag_holding
+ name = "Bag of Holding"
+ desc = "A backpack that opens into a localized pocket of bluespace."
+ id = "bag_holding"
+ build_type = PROTOLATHE
+ materials = list(MAT_GOLD = 3000, MAT_DIAMOND = 1500, MAT_URANIUM = 250, MAT_BLUESPACE = 2000)
+ build_path = /obj/item/storage/backpack/holding
+ category = list("Bluespace Designs")
+ dangerous_construction = TRUE
+ departmental_flags = DEPARTMENTAL_FLAG_SCIENCE
+
+/datum/design/satchel_holding
+ name = "Satchel of Holding"
+ desc = "A satchel that opens into a localized pocket of bluespace."
+ id = "satchel_holding"
+ build_type = PROTOLATHE
+ materials = list(MAT_GOLD = 3000, MAT_DIAMOND = 1500, MAT_URANIUM = 250, MAT_BLUESPACE = 2000)
+ build_path = /obj/item/storage/backpack/holding/satchel
+ category = list("Bluespace Designs")
+ dangerous_construction = TRUE
+ departmental_flags = DEPARTMENTAL_FLAG_SCIENCE
+
+/datum/design/biobag_holding
+ name = "Bio Bag of Holding"
+ desc = "A chemical holding thingy. Mostly used for xenobiology."
+ id = "biobag_holding"
+ build_type = PROTOLATHE
+ materials = list(MAT_GOLD = 1500, MAT_DIAMOND = 750, MAT_URANIUM = 250, MAT_BLUESPACE = 1000)
+ build_path = /obj/item/storage/bag/bio/holding
+ category = list("Bluespace Designs")
+ departmental_flags = DEPARTMENTAL_FLAG_SCIENCE
+
+/datum/design/bluespace_crystal
+ name = "Artificial Bluespace Crystal"
+ desc = "A small blue crystal with mystical properties."
+ id = "bluespace_crystal"
+ build_type = PROTOLATHE
+ materials = list(MAT_DIAMOND = 1500, MAT_PLASMA = 1500)
+ build_path = /obj/item/stack/ore/bluespace_crystal/artificial
+ category = list("Bluespace Designs")
+ departmental_flags = DEPARTMENTAL_FLAG_SCIENCE
+
+/datum/design/telesci_gps
+ name = "GPS Device"
+ desc = "Little thingie that can track its position at all times."
+ id = "telesci_gps"
+ build_type = PROTOLATHE
+ materials = list(MAT_METAL = 500, MAT_GLASS = 1000)
+ build_path = /obj/item/gps
+ category = list("Bluespace Designs")
+ departmental_flags = DEPARTMENTAL_FLAG_SCIENCE | DEPARTMENTAL_FLAG_ENGINEERING | DEPARTMENTAL_FLAG_CARGO
+
+/datum/design/desynchronizer
+ name = "Desynchronizer"
+ desc = "A device that can desynchronize the user from spacetime."
+ id = "desynchronizer"
+ build_type = PROTOLATHE
+ materials = list(MAT_METAL = 1000, MAT_GLASS = 500, MAT_SILVER = 1500, MAT_BLUESPACE = 1000)
+ build_path = /obj/item/desynchronizer
+ category = list("Bluespace Designs")
+ departmental_flags = DEPARTMENTAL_FLAG_SCIENCE
+
+/datum/design/miningsatchel_holding
+ name = "Mining Satchel of Holding"
+ desc = "A mining satchel that can hold an infinite amount of ores."
+ id = "minerbag_holding"
+ build_type = PROTOLATHE
+ materials = list(MAT_GOLD = 250, MAT_URANIUM = 500) //quite cheap, for more convenience
+ build_path = /obj/item/storage/bag/ore/holding
+ category = list("Bluespace Designs")
+ departmental_flags = DEPARTMENTAL_FLAG_CARGO
diff --git a/code/modules/research/nanites/nanite_programs.dm b/code/modules/research/nanites/nanite_programs.dm
index a06311f2..2a6563fe 100644
--- a/code/modules/research/nanites/nanite_programs.dm
+++ b/code/modules/research/nanites/nanite_programs.dm
@@ -208,9 +208,6 @@ datum/nanite_program/proc/on_mob_remove()
/datum/nanite_program/proc/on_death()
return
-/datum/nanite_program/proc/on_hear(message, atom/movable/speaker, message_language, raw_message, radio_freq, list/spans, message_mode)
- return
-
/datum/nanite_program/proc/software_error(type)
if(!type)
type = rand(1,5)
diff --git a/code/modules/research/nanites/nanite_programs/sensor.dm b/code/modules/research/nanites/nanite_programs/sensor.dm
index d494ec8c..535b92c6 100644
--- a/code/modules/research/nanites/nanite_programs/sensor.dm
+++ b/code/modules/research/nanites/nanite_programs/sensor.dm
@@ -345,6 +345,14 @@
var/sentence = ""
var/inclusive = TRUE
+
+/datum/nanite_program/sensor/voice/on_mob_add()
+ . = ..()
+ RegisterSignal(host_mob, COMSIG_MOVABLE_HEAR, .proc/on_hear)
+
+/datum/nanite_program/sensor/voice/on_mob_remove()
+ UnregisterSignal(host_mob, COMSIG_MOVABLE_HEAR, .proc/on_hear)
+
/datum/nanite_program/sensor/voice/set_extra_setting(user, setting)
if(setting == "Sent Code")
var/new_code = input(user, "Set the sent code (1-9999):", name, null) as null|num
@@ -378,15 +386,12 @@
target.sentence = sentence
target.inclusive = inclusive
-/datum/nanite_program/sensor/voice/on_hear(message, atom/movable/speaker, message_language, raw_message, radio_freq, list/spans, message_mode)
+/datum/nanite_program/sensor/voice/proc/on_hear(datum/source, list/hearing_args)
if(!sentence)
return
- //To make it not case sensitive
- var/low_message = lowertext(raw_message)
- var/low_sentence = lowertext(sentence)
if(inclusive)
- if(findtext(low_message, low_sentence))
+ if(findtextEx(hearing_args[HEARING_RAW_MESSAGE], sentence))
send_code()
else
- if(low_message == low_sentence)
+ if(hearing_args[HEARING_RAW_MESSAGE] == sentence)
send_code()
\ No newline at end of file
diff --git a/code/modules/research/techweb/all_nodes.dm b/code/modules/research/techweb/all_nodes.dm
index a13739e3..0a518774 100644
--- a/code/modules/research/techweb/all_nodes.dm
+++ b/code/modules/research/techweb/all_nodes.dm
@@ -323,6 +323,15 @@
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500)
export_price = 5000
+/datum/techweb_node/unregulated_bluespace
+ id = "unregulated_bluespace"
+ display_name = "Unregulated Bluespace Research"
+ description = "Bluespace technology using unstable or unbalanced procedures, prone to damaging the fabric of bluespace. Outlawed by galactic conventions."
+ prereq_ids = list("bluespace_warping", "syndicate_basic")
+ design_ids = list("desynchronizer")
+ research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500)
+ export_price = 2500
+
/////////////////////////plasma tech/////////////////////////
/datum/techweb_node/basic_plasma
id = "basic_plasma"
diff --git a/icons/mob/screen_alert.dmi b/icons/mob/screen_alert.dmi
index 397d2ce5..fef57630 100644
Binary files a/icons/mob/screen_alert.dmi and b/icons/mob/screen_alert.dmi differ
diff --git a/icons/obj/device.dmi b/icons/obj/device.dmi
index 9a9a4017..37a1a087 100644
Binary files a/icons/obj/device.dmi and b/icons/obj/device.dmi differ
diff --git a/tgstation.dme b/tgstation.dme
index bd08e84b..5cebfa8f 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -890,6 +890,7 @@
#include "code\game\objects\items\devices\camera_bug.dm"
#include "code\game\objects\items\devices\chameleonproj.dm"
#include "code\game\objects\items\devices\compressionkit.dm"
+#include "code\game\objects\items\devices\desynchronizer.dm"
#include "code\game\objects\items\devices\dogborg_sleeper.dm"
#include "code\game\objects\items\devices\doorCharge.dm"
#include "code\game\objects\items\devices\electroadaptive_pseudocircuit.dm"