diff --git a/code/__DEFINES/components.dm b/code/__DEFINES/components.dm
index 656500985d..2d3048554e 100644
--- a/code/__DEFINES/components.dm
+++ b/code/__DEFINES/components.dm
@@ -25,7 +25,7 @@
// start global signals with "!", this used to be necessary but now it's just a formatting choice
#define COMSIG_GLOB_NEW_Z "!new_z" //from base of datum/controller/subsystem/mapping/proc/add_new_zlevel(): (list/args)
#define COMSIG_GLOB_VAR_EDIT "!var_edit" //called after a successful var edit somewhere in the world: (list/args)
-
+#define COMSIG_GLOB_LIVING_SAY_SPECIAL "!say_special" //global living say plug - use sparingly: (mob/speaker , message)
//////////////////////////////////////////////////////////////////
// /datum signals
@@ -160,6 +160,7 @@
#define COMSIG_OBJ_BREAK "obj_break" //from base of /obj/obj_break(): (damage_flag)
#define COMSIG_OBJ_SETANCHORED "obj_setanchored" //called in /obj/structure/setAnchored(): (value)
+
// /obj/item signals
#define COMSIG_ITEM_ATTACK "item_attack" //from base of obj/item/attack(): (/mob/living/target, /mob/living/user)
#define COMSIG_ITEM_ATTACK_SELF "item_attack_self" //from base of obj/item/attack_self(): (/mob)
diff --git a/code/__DEFINES/logging.dm b/code/__DEFINES/logging.dm
index 8234059a36..2ba5012301 100644
--- a/code/__DEFINES/logging.dm
+++ b/code/__DEFINES/logging.dm
@@ -16,6 +16,7 @@
#define INVESTIGATE_EXONET "exonet"
#define INVESTIGATE_NANITES "nanites"
#define INVESTIGATE_CIRCUIT "circuit"
+#define INVESTIGATE_FERMICHEM "fermichem"
#define INVESTIGATE_RCD "rcd"
// Logging types for log_message()
diff --git a/code/__DEFINES/misc.dm b/code/__DEFINES/misc.dm
index 9179c98fb1..1a38e368c2 100644
--- a/code/__DEFINES/misc.dm
+++ b/code/__DEFINES/misc.dm
@@ -57,7 +57,7 @@ Will print: "/mob/living/carbon/human/death" (you can optionally embed it in a s
#define BODYPARTS_LAYER 28 //Initially "AUGMENTS", this was repurposed to be a catch-all bodyparts flag
#define MARKING_LAYER 27 //Matrixed body markings because clashing with snouts?
#define BODY_ADJ_LAYER 26 //certain mutantrace features (snout, body markings) that must appear above the body parts
-#define GENITALS_ADJ_LAYER 25
+#define GENITALS_FRONT_LAYER 25 //Draws some genitalia above clothes and the TAUR body if need be.
#define BODY_LAYER 24 //underwear, undershirts, socks, eyes, lips(makeup)
#define FRONT_MUTATIONS_LAYER 23 //mutations that should appear above body, body_adj and bodyparts layer (e.g. laser eyes)
#define DAMAGE_LAYER 22 //damage indicators (cuts and burns)
@@ -69,7 +69,6 @@ Will print: "/mob/living/carbon/human/death" (you can optionally embed it in a s
#define EARS_LAYER 17
#define BODY_TAUR_LAYER 16
#define SUIT_LAYER 15
-#define GENITALS_FRONT_LAYER 14 //Draws some genitalia above clothes and the TAUR body if need be.
#define GLASSES_LAYER 13
#define BELT_LAYER 12 //Possible make this an overlay of somethign required to wear a belt?
#define SUIT_STORE_LAYER 11
@@ -83,7 +82,7 @@ Will print: "/mob/living/carbon/human/death" (you can optionally embed it in a s
#define HANDS_LAYER 3
#define BODY_FRONT_LAYER 2
#define FIRE_LAYER 1 //If you're on fire
-#define TOTAL_LAYERS 31 //KEEP THIS UP-TO-DATE OR SHIT WILL BREAK ;_;
+#define TOTAL_LAYERS 30 //KEEP THIS UP-TO-DATE OR SHIT WILL BREAK ;_;
//Human Overlay Index Shortcuts for alternate_worn_layer, layers
//Because I *KNOW* somebody will think layer+1 means "above"
@@ -503,4 +502,4 @@ GLOBAL_LIST_INIT(pda_reskins, list(PDA_SKIN_CLASSIC = 'icons/obj/pda.dmi', PDA_S
#define PREF_SAVELOAD_COOLDOWN 5
#define VOMIT_TOXIC 1
-#define VOMIT_PURPLE 2
\ No newline at end of file
+#define VOMIT_PURPLE 2
diff --git a/code/__DEFINES/movespeed_modification.dm b/code/__DEFINES/movespeed_modification.dm
index 9f8d310193..c0aac36415 100644
--- a/code/__DEFINES/movespeed_modification.dm
+++ b/code/__DEFINES/movespeed_modification.dm
@@ -28,4 +28,6 @@
#define MOVESPEED_ID_SIMPLEMOB_VARSPEED "SIMPLEMOB_VARSPEED_MODIFIER"
#define MOVESPEED_ID_ADMIN_VAREDIT "ADMIN_VAREDIT_MODIFIER"
-#define MOVESPEED_ID_PAI_SPACEWALK_SPEEDMOD "PAI_SPACEWALK_MODIFIER"
\ No newline at end of file
+#define MOVESPEED_ID_PAI_SPACEWALK_SPEEDMOD "PAI_SPACEWALK_MODIFIER"
+
+#define MOVESPEED_ID_SANITY "MOOD_SANITY"
diff --git a/code/__DEFINES/reagents.dm b/code/__DEFINES/reagents.dm
index c3682d905d..174289d3bb 100644
--- a/code/__DEFINES/reagents.dm
+++ b/code/__DEFINES/reagents.dm
@@ -2,7 +2,13 @@
#define LIQUID 2
#define GAS 3
-// reagents_flags defines
+//reagents reaction var defines
+#define REAGENT_NORMAL_PH 7.000
+#define REAGENT_PH_ACCURACY 0.001
+#define REAGENT_PURITY_ACCURACY 0.001
+#define DEFAULT_SPECIFIC_HEAT 200
+
+// container_type defines
#define INJECTABLE (1<<0) // Makes it possible to add reagents through droppers and syringes.
#define DRAWABLE (1<<1) // Makes it possible to remove reagents through syringes.
diff --git a/code/__DEFINES/status_effects.dm b/code/__DEFINES/status_effects.dm
index ffaaa04d0f..3cd64af8c7 100644
--- a/code/__DEFINES/status_effects.dm
+++ b/code/__DEFINES/status_effects.dm
@@ -44,6 +44,8 @@
#define STATUS_EFFECT_SLEEPING /datum/status_effect/incapacitating/sleeping //the affected is asleep
+#define STATUS_EFFECT_PACIFY /datum/status_effect/pacify //the affected is pacified, preventing direct hostile actions
+
#define STATUS_EFFECT_BELLIGERENT /datum/status_effect/belligerent //forces the affected to walk, doing damage if they try to run
#define STATUS_EFFECT_GEISTRACKER /datum/status_effect/geis_tracker //if you're using geis, this tracks that and keeps you from using scripture
diff --git a/code/__DEFINES/time.dm b/code/__DEFINES/time.dm
index c5d6f3051e..f13f13510b 100644
--- a/code/__DEFINES/time.dm
+++ b/code/__DEFINES/time.dm
@@ -31,4 +31,4 @@ When using time2text(), please use "DDD" to find the weekday. Refrain from using
#define WORLDTIMEOFDAY2TEXT(format) GAMETIMESTAMP(format, world.timeofday)
#define TIME_STAMP(format, showds) showds ? "[WORLDTIMEOFDAY2TEXT(format)]:[world.timeofday % 10]" : WORLDTIMEOFDAY2TEXT(format)
#define STATION_TIME(display_only) ((((world.time - SSticker.round_start_time) * SSticker.station_time_rate_multiplier) + SSticker.gametime_offset) % 864000) - (display_only? GLOB.timezoneOffset : 0)
-#define STATION_TIME_TIMESTAMP(format) time2text(STATION_TIME(TRUE), format)
\ No newline at end of file
+#define STATION_TIME_TIMESTAMP(format) time2text(STATION_TIME(TRUE), format)
diff --git a/code/__DEFINES/traits.dm b/code/__DEFINES/traits.dm
index e23d8dba76..ed0007135d 100644
--- a/code/__DEFINES/traits.dm
+++ b/code/__DEFINES/traits.dm
@@ -108,6 +108,8 @@
#define TRAIT_NOHARDCRIT "nohardcrit"
#define TRAIT_NOSOFTCRIT "nosoftcrit"
#define TRAIT_MINDSHIELD "mindshield"
+#define TRAIT_FEARLESS "fearless"
+#define TRAIT_UNSTABLE "unstable"
#define TRAIT_PARALYSIS_L_ARM "para-l-arm" //These are used for brain-based paralysis, where replacing the limb won't fix it
#define TRAIT_PARALYSIS_R_ARM "para-r-arm"
#define TRAIT_PARALYSIS_L_LEG "para-l-leg"
@@ -144,12 +146,15 @@
#define TRAIT_CROCRIN_IMMUNE "crocin_immune"
#define TRAIT_NYMPHO "nymphomania"
#define TRAIT_MASO "masochism"
+#define TRAIT_HIGH_BLOOD "high_blood"
+#define TRAIT_PHARMA "hepatic_pharmacokinesis"
#define TRAIT_PARA "paraplegic"
#define TRAIT_EMPATH "empath"
#define TRAIT_FRIENDLY "friendly"
#define TRAIT_ASSBLASTUSA "assblastusa"
#define TRAIT_CULT_EYES "cult_eyes"
+
// common trait sources
#define TRAIT_GENERIC "generic"
#define EYE_DAMAGE "eye_damage"
diff --git a/code/__HELPERS/_cit_helpers.dm b/code/__HELPERS/_cit_helpers.dm
index 036bf0241b..77b79cf532 100644
--- a/code/__HELPERS/_cit_helpers.dm
+++ b/code/__HELPERS/_cit_helpers.dm
@@ -58,6 +58,7 @@ GLOBAL_LIST_EMPTY(ipc_antennas_list)
//Genitals and Arousal Lists
GLOBAL_LIST_EMPTY(cock_shapes_list)//global_lists.dm for the list initializations //Now also _DATASTRUCTURES globals.dm
GLOBAL_LIST_EMPTY(cock_shapes_icons) //Associated list for names->icon_states for cockshapes.
+GLOBAL_LIST_EMPTY(gentlemans_organ_names)
GLOBAL_LIST_EMPTY(balls_shapes_list)
GLOBAL_LIST_EMPTY(balls_shapes_icons)
GLOBAL_LIST_EMPTY(breasts_size_list)
diff --git a/code/__HELPERS/global_lists.dm b/code/__HELPERS/global_lists.dm
index c8a33959ed..ad2bedaa4e 100644
--- a/code/__HELPERS/global_lists.dm
+++ b/code/__HELPERS/global_lists.dm
@@ -52,7 +52,8 @@
init_sprite_accessory_subtypes(/datum/sprite_accessory/vagina, GLOB.vagina_shapes_list)
init_sprite_accessory_subtypes(/datum/sprite_accessory/breasts, GLOB.breasts_shapes_list)
- GLOB.breasts_size_list = list("a","b","c","d","e") //We need the list to choose from initialized, but it's no longer a sprite_accessory thing.
+ GLOB.breasts_size_list = list ("a", "b", "c", "d", "e") //We need the list to choose from initialized, but it's no longer a sprite_accessory thing.
+ GLOB.gentlemans_organ_names = list("phallus", "willy", "dick", "prick", "member", "tool", "gentleman's organ", "cock", "wang", "knob", "dong", "joystick", "pecker", "johnson", "weenie", "tadger", "schlong", "thirsty ferret", "baloney pony", "schlanger")
for(var/K in GLOB.breasts_shapes_list)
var/datum/sprite_accessory/breasts/value = GLOB.breasts_shapes_list[K]
GLOB.breasts_shapes_icons[K] = value.icon_state
diff --git a/code/_globalvars/lists/maintenance_loot.dm b/code/_globalvars/lists/maintenance_loot.dm
index d92ab17584..f65c15ec88 100644
--- a/code/_globalvars/lists/maintenance_loot.dm
+++ b/code/_globalvars/lists/maintenance_loot.dm
@@ -110,6 +110,7 @@ GLOBAL_LIST_INIT(maintenance_loot, list(
/obj/item/reagent_containers/food/snacks/cannedpeaches/maint = 1,
/obj/item/storage/daki = 3, //VERY IMPORTANT CIT CHANGE - adds bodypillows to maint
/obj/item/storage/pill_bottle/penis_enlargement = 2,
+ /obj/item/storage/pill_bottle/breast_enlargement = 2,
/obj/item/clothing/shoes/wheelys = 1,
/obj/item/clothing/shoes/kindleKicks = 1,
/obj/item/autosurgeon/penis = 1,
diff --git a/code/controllers/subsystem/processing/chemistry.dm b/code/controllers/subsystem/processing/chemistry.dm
new file mode 100644
index 0000000000..da31d65fb3
--- /dev/null
+++ b/code/controllers/subsystem/processing/chemistry.dm
@@ -0,0 +1,5 @@
+PROCESSING_SUBSYSTEM_DEF(chemistry)
+ wait = 5
+ flags = SS_KEEP_TIMING
+
+
diff --git a/code/datums/brain_damage/hypnosis.dm b/code/datums/brain_damage/hypnosis.dm
new file mode 100644
index 0000000000..8909d1b85f
--- /dev/null
+++ b/code/datums/brain_damage/hypnosis.dm
@@ -0,0 +1,72 @@
+/datum/brain_trauma/hypnosis
+ name = "Hypnosis"
+ desc = "Patient's unconscious is completely enthralled by a word or sentence, focusing their thoughts and actions on it."
+ scan_desc = "looping thought pattern"
+ gain_text = ""
+ lose_text = ""
+ resilience = TRAUMA_RESILIENCE_SURGERY
+ var/hypnotic_phrase = ""
+ var/regex/target_phrase
+
+/datum/brain_trauma/hypnosis/New(phrase, quirk = FALSE)
+ if(!phrase)
+ qdel(src)
+ if(quirk == TRUE)
+ hypnotic_phrase = phrase
+ else
+ friendliify(phrase)
+ if(IsAdminAdvancedProcCall())
+ to_chat(usr, "Hypnosis New() skipped due to try/catch incompatibility with admin proccalling.")
+ qdel(src)
+ try
+ target_phrase = new("(\\b[hypnotic_phrase]\\b)","ig")
+ catch(var/exception/e)
+ stack_trace("[e] on [e.file]:[e.line]")
+ qdel(src)
+ ..()
+
+/datum/brain_trauma/hypnosis/proc/friendliify(phrase)
+ phrase = replacetext(lowertext(phrase), "kill", "hug")
+ phrase = replacetext(lowertext(phrase), "murder", "cuddle")
+ phrase = replacetext(lowertext(phrase), "harm", "snuggle")
+ phrase = replacetext(lowertext(phrase), "decapitate", "headpat")
+ phrase = replacetext(lowertext(phrase), "strangle", "meow at")
+ phrase = replacetext(lowertext(phrase), "suicide", "self-love")
+ phrase = replacetext(lowertext(phrase), "lynch", "kiss")
+ hypnotic_phrase = phrase
+
+/datum/brain_trauma/hypnosis/on_gain()
+ message_admins("[ADMIN_LOOKUPFLW(owner)] was hypnotized with the phrase '[hypnotic_phrase]'.")
+ log_game("[key_name(owner)] was hypnotized with the phrase '[hypnotic_phrase]'.")
+ to_chat(owner, "[hypnotic_phrase]")
+ to_chat(owner, "[pick("You feel your thoughts focusing on this phrase... you can't seem to get it out of your head.",\
+ "Your head hurts, but this is all you can think of. It must be vitally important.",\
+ "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,\
+ 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.")
+ ..()
+
+/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.")
+ ..()
+
+/datum/brain_trauma/hypnosis/on_life()
+ ..()
+ if(prob(2))
+ switch(rand(1,2))
+ if(1)
+ to_chat(owner, "...[lowertext(hypnotic_phrase)]...")
+ 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
diff --git a/code/datums/brain_damage/phobia.dm b/code/datums/brain_damage/phobia.dm
index 6723c89ba5..f802555c7e 100644
--- a/code/datums/brain_damage/phobia.dm
+++ b/code/datums/brain_damage/phobia.dm
@@ -31,6 +31,8 @@
/datum/brain_trauma/mild/phobia/on_life()
..()
+ if(HAS_TRAIT(owner, TRAIT_FEARLESS))
+ return
if(is_blind(owner))
return
if(world.time > next_check && world.time > next_scare)
@@ -70,6 +72,8 @@
/datum/brain_trauma/mild/phobia/on_hear(message, speaker, message_language, raw_message, radio_freq)
if(!owner.can_hear() || world.time < next_scare) //words can't trigger you if you can't hear them *taps head*
return message
+ if(HAS_TRAIT(owner, TRAIT_FEARLESS))
+ return message
for(var/word in trigger_words)
var/reg = regex("(\\b|\\A)[REGEX_QUOTE(word)]'?s*(\\b|\\Z)", "i")
@@ -79,6 +83,8 @@
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")
diff --git a/code/datums/brain_damage/severe.dm b/code/datums/brain_damage/severe.dm
index d094c162d7..890e9cf903 100644
--- a/code/datums/brain_damage/severe.dm
+++ b/code/datums/brain_damage/severe.dm
@@ -254,3 +254,20 @@
/datum/brain_trauma/severe/pacifism/on_lose()
REMOVE_TRAIT(owner, TRAIT_PACIFISM, TRAUMA_TRAIT)
..()
+
+//ported from TG
+/datum/brain_trauma/severe/hypnotic_stupor
+ name = "Hypnotic Stupor"
+ desc = "Patient is prone to episodes of extreme stupor that leaves them extremely suggestible."
+ scan_desc = "oneiric feedback loop"
+ gain_text = "You feel somewhat dazed."
+ lose_text = "You feel like a fog was lifted from your mind."
+
+/datum/brain_trauma/severe/hypnotic_stupor/on_lose() //hypnosis must be cleared separately, but brain surgery should get rid of both anyway
+ ..()
+ owner.remove_status_effect(/datum/status_effect/trance)
+
+/datum/brain_trauma/severe/hypnotic_stupor/on_life()
+ ..()
+ if(prob(1) && !owner.has_status_effect(/datum/status_effect/trance))
+ owner.apply_status_effect(/datum/status_effect/trance, rand(100,300), FALSE)
diff --git a/code/datums/components/mood.dm b/code/datums/components/mood.dm
index b32921a4ce..730ffa9ff0 100644
--- a/code/datums/components/mood.dm
+++ b/code/datums/components/mood.dm
@@ -6,6 +6,7 @@
var/sanity = 100 //Current sanity
var/shown_mood //Shown happiness, this is what others can see when they try to examine you, prevents antag checking by noticing traitors are always very happy.
var/mood_level = 5 //To track what stage of moodies they're on
+ var/sanity_level = 5 //To track what stage of sanity they're on
var/mood_modifier = 1 //Modifier to allow certain mobs to be less affected by moodlets
var/datum/mood_event/list/mood_events = list()
var/insanity_effect = 0 //is the owner being punished for low mood? If so, how much?
@@ -118,6 +119,8 @@
if(owner.client && owner.hud_used)
if(sanity < 25)
screen_obj.icon_state = "mood_insane"
+ else if (owner.has_status_effect(/datum/status_effect/chem/enthrall))//Fermichem enthral chem, maybe change?
+ screen_obj.icon_state = "mood_entrance"
else
screen_obj.icon_state = "mood[mood_level]"
@@ -163,6 +166,58 @@
HandleNutrition(owner)
+/datum/component/mood/proc/setSanity(amount, minimum=SANITY_INSANE, maximum=SANITY_NEUTRAL)//I'm sure bunging this in here will have no negative repercussions.
+ var/mob/living/master = parent
+
+ if(amount == sanity)
+ return
+ // If we're out of the acceptable minimum-maximum range move back towards it in steps of 0.5
+ // If the new amount would move towards the acceptable range faster then use it instead
+ if(sanity < minimum && amount < sanity + 0.5)
+ amount = sanity + 0.5
+ else if(sanity > maximum && amount > sanity - 0.5)
+ amount = sanity - 0.5
+
+ // Disturbed stops you from getting any more sane
+ if(HAS_TRAIT(master, TRAIT_UNSTABLE))
+ sanity = min(amount,sanity)
+ else
+ sanity = amount
+
+ switch(sanity)
+ if(SANITY_INSANE to SANITY_CRAZY)
+ setInsanityEffect(MAJOR_INSANITY_PEN)
+ master.add_movespeed_modifier(MOVESPEED_ID_SANITY, TRUE, 100, override=TRUE, multiplicative_slowdown=1.5) //Did we change something ? movetypes is runtiming, movetypes=(~FLYING))
+ sanity_level = 6
+ if(SANITY_CRAZY to SANITY_UNSTABLE)
+ setInsanityEffect(MINOR_INSANITY_PEN)
+ master.add_movespeed_modifier(MOVESPEED_ID_SANITY, TRUE, 100, override=TRUE, multiplicative_slowdown=1)//, movetypes=(~FLYING))
+ sanity_level = 5
+ if(SANITY_UNSTABLE to SANITY_DISTURBED)
+ setInsanityEffect(0)
+ master.add_movespeed_modifier(MOVESPEED_ID_SANITY, TRUE, 100, override=TRUE, multiplicative_slowdown=0.5)//, movetypes=(~FLYING))
+ sanity_level = 4
+ if(SANITY_DISTURBED to SANITY_NEUTRAL)
+ setInsanityEffect(0)
+ master.remove_movespeed_modifier(MOVESPEED_ID_SANITY, TRUE)
+ sanity_level = 3
+ if(SANITY_NEUTRAL+1 to SANITY_GREAT+1) //shitty hack but +1 to prevent it from responding to super small differences
+ setInsanityEffect(0)
+ master.remove_movespeed_modifier(MOVESPEED_ID_SANITY, TRUE)
+ sanity_level = 2
+ if(SANITY_GREAT+1 to INFINITY)
+ setInsanityEffect(0)
+ master.remove_movespeed_modifier(MOVESPEED_ID_SANITY, TRUE)
+ sanity_level = 1
+ //update_mood_icon()
+
+/datum/component/mood/proc/setInsanityEffect(newval)//More code so that the previous proc works
+ if(newval == insanity_effect)
+ return
+ var/mob/living/master = parent
+ master.crit_threshold = (master.crit_threshold - insanity_effect) + newval
+ insanity_effect = newval
+
/datum/component/mood/proc/DecreaseSanity(amount, minimum = SANITY_INSANE)
if(sanity < minimum) //This might make KevinZ stop fucking pinging me.
IncreaseSanity(0.5)
@@ -175,6 +230,10 @@
insanity_effect = (MINOR_INSANITY_PEN)
/datum/component/mood/proc/IncreaseSanity(amount, maximum = SANITY_NEUTRAL)
+ // Disturbed stops you from getting any more sane - I'm just gonna bung this in here
+ var/mob/living/owner = parent
+ if(HAS_TRAIT(owner, TRAIT_UNSTABLE))
+ return
if(sanity > maximum)
DecreaseSanity(0.5) //Removes some sanity to go back to our current limit.
else
@@ -195,7 +254,7 @@
if(the_event.timeout)
addtimer(CALLBACK(src, .proc/clear_event, null, category), the_event.timeout, TIMER_UNIQUE|TIMER_OVERRIDE)
return 0 //Don't have to update the event.
- the_event = new type(src, param)
+ the_event = new type(src, param)//This causes a runtime for some reason, was this me? No - there's an event floating around missing a definition.
mood_events[category] = the_event
update_mood()
diff --git a/code/datums/diseases/advance/symptoms/oxygen.dm b/code/datums/diseases/advance/symptoms/oxygen.dm
index 7bb6934707..cb7d1a6d6d 100644
--- a/code/datums/diseases/advance/symptoms/oxygen.dm
+++ b/code/datums/diseases/advance/symptoms/oxygen.dm
@@ -44,7 +44,7 @@ Bonus
if(4, 5)
M.adjustOxyLoss(-7, 0)
M.losebreath = max(0, M.losebreath - 4)
- if(regenerate_blood && M.blood_volume < BLOOD_VOLUME_NORMAL)
+ if(regenerate_blood && M.blood_volume < (BLOOD_VOLUME_NORMAL * M.blood_ratio))
M.blood_volume += 1
else
if(prob(base_message_chance))
diff --git a/code/datums/looping_sounds/_looping_sound.dm b/code/datums/looping_sounds/_looping_sound.dm
index 1b7a304494..49942976ce 100644
--- a/code/datums/looping_sounds/_looping_sound.dm
+++ b/code/datums/looping_sounds/_looping_sound.dm
@@ -96,4 +96,4 @@
/datum/looping_sound/proc/on_stop()
if(end_sound)
- play(end_sound)
\ No newline at end of file
+ play(end_sound)
diff --git a/code/datums/mood_events/drink_events.dm b/code/datums/mood_events/drink_events.dm
index 5d0eb0e0cb..70acffdb8f 100644
--- a/code/datums/mood_events/drink_events.dm
+++ b/code/datums/mood_events/drink_events.dm
@@ -6,24 +6,24 @@
/datum/mood_event/quality_nice
description = "That drink wasn't bad at all.\n"
mood_change = 1
- timeout = 1200
+ timeout = 2 MINUTES
/datum/mood_event/quality_good
description = "That drink was pretty good.\n"
mood_change = 2
- timeout = 1200
+ timeout = 2 MINUTES
/datum/mood_event/quality_verygood
description = "That drink was great!\n"
mood_change = 3
- timeout = 1200
+ timeout = 2 MINUTES
/datum/mood_event/quality_fantastic
description = "That drink was amazing!\n"
mood_change = 4
- timeout = 1200
+ timeout = 2 MINUTES
/datum/mood_event/amazingtaste
description = "Amazing taste!\n"
- mood_change = 50
+ mood_change = 50 //Is this not really high..?
timeout = 10 MINUTES
diff --git a/code/datums/mood_events/drug_events.dm b/code/datums/mood_events/drug_events.dm
index 40c239180e..469bf80979 100644
--- a/code/datums/mood_events/drug_events.dm
+++ b/code/datums/mood_events/drug_events.dm
@@ -37,3 +37,19 @@
/datum/mood_event/withdrawal_critical/add_effects(drug_name)
description = "[drug_name]! [drug_name]! [drug_name]!\n"
+
+/datum/mood_event/happiness_drug
+ description = "I can't feel anything and I never want this to end.\n"
+ mood_change = 10
+
+/datum/mood_event/happiness_drug_good_od
+ description = "YES! YES!! YES!!!\n"
+ mood_change = 20
+ timeout = 300
+ //special_screen_obj = "mood_happiness_good" Originally in tg, but I personally think they look dumb
+
+/datum/mood_event/happiness_drug_bad_od
+ description = "NO! NO!! NO!!!\n"
+ mood_change = -20
+ timeout = 300
+ //special_screen_obj = "mood_happiness_bad" Originally in tg
diff --git a/code/datums/mood_events/generic_negative_events.dm b/code/datums/mood_events/generic_negative_events.dm
index b2b03fb56b..69f1a66814 100644
--- a/code/datums/mood_events/generic_negative_events.dm
+++ b/code/datums/mood_events/generic_negative_events.dm
@@ -1,3 +1,5 @@
+
+
/datum/mood_event/handcuffed
description = "I guess my antics have finally caught up with me.\n"
mood_change = -1
@@ -17,7 +19,7 @@
/datum/mood_event/burnt_thumb
description = "I shouldn't play with lighters...\n"
mood_change = -1
- timeout = 1200
+ timeout = 2 MINUTES
/datum/mood_event/cold
description = "It's way too cold in here.\n"
@@ -30,17 +32,17 @@
/datum/mood_event/creampie
description = "I've been creamed. Tastes like pie flavor.\n"
mood_change = -2
- timeout = 1800
+ timeout = 3 MINUTES
/datum/mood_event/slipped
description = "I slipped. I should be more careful next time...\n"
mood_change = -2
- timeout = 1800
+ timeout = 3 MINUTES
/datum/mood_event/eye_stab
description = "I used to be an adventurer like you, until I took a screwdriver to the eye.\n"
mood_change = -4
- timeout = 1800
+ timeout = 3 MINUTES
/datum/mood_event/delam //SM delamination
description = "Those God damn engineers can't do anything right...\n"
@@ -50,12 +52,12 @@
/datum/mood_event/depression
description = "I feel sad for no particular reason.\n"
mood_change = -9
- timeout = 1200
+ timeout = 2 MINUTES
/datum/mood_event/shameful_suicide //suicide_acts that return SHAME, like sord
description = "I can't even end it all!\n"
mood_change = -10
- timeout = 600
+ timeout = 1 MINUTES
/datum/mood_event/dismembered
description = "AHH! I WAS USING THAT LIMB!\n"
@@ -69,7 +71,7 @@
/datum/mood_event/tased
description = "There's no \"z\" in \"taser\". It's in the zap.\n"
mood_change = -3
- timeout = 1200
+ timeout = 2 MINUTES
/datum/mood_event/embedded
description = "Pull it out!\n"
@@ -78,7 +80,7 @@
/datum/mood_event/table
description = "Someone threw me on a table!\n"
mood_change = -2
- timeout = 1200
+ timeout = 2 MINUTES
/datum/mood_event/table/add_effects()
if(ishuman(owner))
@@ -117,6 +119,30 @@
description = "I'm missing my family heirloom...\n"
mood_change = -4
+/datum/mood_event/healsbadman
+ description = "I feel a lot better, but wow that was disgusting.\n"
+ mood_change = -4
+ timeout = 2 MINUTES
+
+/datum/mood_event/jittery
+ description = "I'm nervous and on edge and I can't stand still!!\n"
+ mood_change = -2
+
+/datum/mood_event/vomit
+ description = "I just threw up. Gross.\n"
+ mood_change = -2
+ timeout = 2 MINUTES
+
+/datum/mood_event/vomitself
+ description = "I just threw up all over myself. This is disgusting.\n"
+ mood_change = -4
+ timeout = 3 MINUTES
+
+/datum/mood_event/painful_medicine
+ description = "Medicine may be good for me but right now it stings like hell.\n"
+ mood_change = -5
+ timeout = 1 MINUTES
+
/datum/mood_event/loud_gong
description = "That loud gong noise really hurt my ears!\n"
mood_change = -3
diff --git a/code/datums/mood_events/generic_positive_events.dm b/code/datums/mood_events/generic_positive_events.dm
index 051a548d1d..422ec4476c 100644
--- a/code/datums/mood_events/generic_positive_events.dm
+++ b/code/datums/mood_events/generic_positive_events.dm
@@ -1,7 +1,7 @@
/datum/mood_event/hug
description = "Hugs are nice.\n"
mood_change = 1
- timeout = 1200
+ timeout = 2 MINUTES
/datum/mood_event/arcade
description = "I beat the arcade game!\n"
@@ -50,7 +50,7 @@
/datum/mood_event/jolly
description = "I feel happy for no particular reason.\n"
mood_change = 6
- timeout = 1200
+ timeout = 2 MINUTES
/datum/mood_event/focused
description = "I have a goal, and I will reach it, whatever it takes!\n" //Used for syndies, nukeops etc so they can focus on their goals
@@ -76,6 +76,20 @@
mood_change = 3
timeout = 600
+/datum/mood_event/chemical_euphoria
+ description = "Heh...hehehe...hehe...\n"
+ mood_change = 4
+
+ /datum/mood_event/chemical_laughter
+ description = "Laughter really is the best medicine! Or is it?\n"
+ mood_change = 4
+ timeout = 3 MINUTES
+
+ /datum/mood_event/chemical_superlaughter
+ description = "*WHEEZE*\n"
+ mood_change = 12
+ timeout = 3 MINUTES
+
/datum/mood_event/betterhug
description = "Someone was very nice to me.\n"
mood_change = 3
@@ -94,8 +108,8 @@
/datum/mood_event/happy_empath
description = "Someone seems happy!\n"
- mood_change = 2
+ mood_change = 3
timeout = 600
/datum/mood_event/happy_empath/add_effects(var/mob/happytarget)
- description = "[happytarget.name]'s happiness is infectious!\n"
+ description = "[happytarget.name]'s happiness is infectious!\n"
diff --git a/code/datums/mood_events/needs_events.dm b/code/datums/mood_events/needs_events.dm
index 75b7e6931f..eb58f2aa92 100644
--- a/code/datums/mood_events/needs_events.dm
+++ b/code/datums/mood_events/needs_events.dm
@@ -59,4 +59,4 @@
/datum/mood_event/nice_shower
description = "I have recently had a nice shower.\n"
mood_change = 2
- timeout = 1800
+ timeout = 3 MINUTES
diff --git a/code/datums/status_effects/debuffs.dm b/code/datums/status_effects/debuffs.dm
index 1c77520108..e930c77c36 100644
--- a/code/datums/status_effects/debuffs.dm
+++ b/code/datums/status_effects/debuffs.dm
@@ -507,3 +507,101 @@
desc = "Your body is covered in blue ichor! You can't be revived by vitality matrices."
icon_state = "ichorial_stain"
alerttooltipstyle = "clockcult"
+
+datum/status_effect/pacify
+ id = "pacify"
+ status_type = STATUS_EFFECT_REPLACE
+ tick_interval = 1
+ duration = 100
+ alert_type = null
+
+/datum/status_effect/pacify/on_creation(mob/living/new_owner, set_duration)
+ if(isnum(set_duration))
+ duration = set_duration
+ . = ..()
+
+/datum/status_effect/pacify/on_apply()
+ ADD_TRAIT(owner, TRAIT_PACIFISM, "status_effect")
+ return ..()
+
+/datum/status_effect/pacify/on_remove()
+ REMOVE_TRAIT(owner, TRAIT_PACIFISM, "status_effect")
+
+/datum/status_effect/trance
+ id = "trance"
+ status_type = STATUS_EFFECT_UNIQUE
+ duration = 300
+ tick_interval = 10
+ examine_text = "SUBJECTPRONOUN seems slow and unfocused."
+ var/stun = TRUE
+ var/triggered = FALSE
+ alert_type = null
+
+/obj/screen/alert/status_effect/trance
+ name = "Trance"
+ desc = "Everything feels so distant, and you can feel your thoughts forming loops inside your head..."
+ 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
+
+/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.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.
+ 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)
+ 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)
+ if(!owner.can_hear())
+ return
+ if(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.proc/Stun, 60, TRUE, TRUE), 15) //Take some time to think about it
+ qdel(src)
diff --git a/code/datums/traits/good.dm b/code/datums/traits/good.dm
index 300a1264eb..dffdc92630 100644
--- a/code/datums/traits/good.dm
+++ b/code/datums/traits/good.dm
@@ -186,3 +186,20 @@
var/obj/item/autosurgeon/gloweyes/gloweyes = new(get_turf(H))
H.equip_to_slot(gloweyes, SLOT_IN_BACKPACK)
H.regenerate_icons()
+
+/datum/quirk/bloodpressure
+ name = "Polycythemia vera"
+ desc = "You've a treated form of Polycythemia vera that increases the total blood volume inside of you as well as the rate of replenishment!"
+ value = 2 //I honeslty dunno if this is a good trait? I just means you use more of medbays blood and make janitors madder, but you also regen blood a lil faster.
+ mob_trait = TRAIT_HIGH_BLOOD
+ gain_text = "You feel full of blood!"
+ lose_text = "You feel like your blood pressure went down."
+
+/datum/quirk/bloodpressure/add()
+ var/mob/living/M = quirk_holder
+ M.blood_ratio = 1.2
+ M.blood_volume += 150
+
+/datum/quirk/bloodpressure/remove()
+ var/mob/living/M = quirk_holder
+ M.blood_ratio = 1
diff --git a/code/datums/traits/negative.dm b/code/datums/traits/negative.dm
index 36c6733cf7..f77fe37333 100644
--- a/code/datums/traits/negative.dm
+++ b/code/datums/traits/negative.dm
@@ -354,3 +354,12 @@
if(quirk_holder.mind && LAZYLEN(quirk_holder.mind.antag_datums))
to_chat(quirk_holder, "Your antagonistic nature has caused your voice to be heard.")
qdel(src)
+
+/datum/quirk/unstable
+ name = "Unstable"
+ desc = "Due to past troubles, you are unable to recover your sanity if you lose it. Be very careful managing your mood!"
+ value = -2
+ mob_trait = TRAIT_UNSTABLE
+ gain_text = "There's a lot on your mind right now."
+ lose_text = "Your mind finally feels calm."
+ medical_record_text = "Patient's mind is in a vulnerable state, and cannot recover from traumatic events."
diff --git a/code/game/machinery/computer/arcade.dm b/code/game/machinery/computer/arcade.dm
index 8d0cfb95e8..e38445f606 100644
--- a/code/game/machinery/computer/arcade.dm
+++ b/code/game/machinery/computer/arcade.dm
@@ -57,6 +57,7 @@
/obj/item/stack/tile/fakespace/loaded = ARCADE_WEIGHT_TRICK,
/obj/item/stack/tile/fakepit/loaded = ARCADE_WEIGHT_TRICK,
/obj/item/restraints/handcuffs/fake = ARCADE_WEIGHT_TRICK,
+ /obj/item/clothing/gloves/rapid/hug = ARCADE_WEIGHT_TRICK,
/obj/item/grenade/chem_grenade/glitter/pink = ARCADE_WEIGHT_TRICK,
/obj/item/grenade/chem_grenade/glitter/blue = ARCADE_WEIGHT_TRICK,
diff --git a/code/game/machinery/iv_drip.dm b/code/game/machinery/iv_drip.dm
index 84a688c724..b217c14e53 100644
--- a/code/game/machinery/iv_drip.dm
+++ b/code/game/machinery/iv_drip.dm
@@ -145,7 +145,7 @@
return
// If the human is losing too much blood, beep.
- if(attached.blood_volume < BLOOD_VOLUME_SAFE && prob(5))
+ if(attached.blood_volume < ( (BLOOD_VOLUME_SAFE*attached.blood_ratio) && prob(5) ) )
visible_message("[src] beeps loudly.")
playsound(loc, 'sound/machines/twobeep.ogg', 50, 1)
attached.transfer_blood_to(beaker, amount)
@@ -218,4 +218,4 @@
to_chat(user, "[attached ? attached : "No one"] is attached.")
#undef IV_TAKING
-#undef IV_INJECTING
\ No newline at end of file
+#undef IV_INJECTING
diff --git a/code/game/machinery/teleporter.dm b/code/game/machinery/teleporter.dm
index 64081a77e4..1f1f13ee22 100644
--- a/code/game/machinery/teleporter.dm
+++ b/code/game/machinery/teleporter.dm
@@ -68,16 +68,16 @@
if (ismovableatom(M))
if(do_teleport(M, com.target, channel = TELEPORT_CHANNEL_BLUESPACE))
use_power(5000)
- if(!calibrated && prob(30 - ((accurate) * 10))) //oh dear a problem
- log_game("[M] ([key_name(M)]) was turned into a fly person")
- if(ishuman(M))//don't remove people from the round randomly you jerks
- var/mob/living/carbon/human/human = M
- if(human.dna && human.dna.species.id == "human")
- to_chat(M, "You hear a buzzing in your ears.")
- human.set_species(/datum/species/fly)
- human.apply_effect((rand(120 - accurate * 40, 180 - accurate * 60)), EFFECT_IRRADIATE, 0)
- calibrated = 0
+ if(!calibrated && iscarbon(M) && prob(30 - ((accurate) * 10))) //oh dear a problem
+ var/mob/living/carbon/C = M
+ if(C.dna?.species && C.dna.species.id != "fly" && !HAS_TRAIT(C, TRAIT_RADIMMUNE))
+ to_chat(C, "You hear a buzzing in your ears.")
+ C.set_species(/datum/species/fly)
+ log_game("[C] ([key_name(C)]) was turned into a fly person")
+ C.apply_effect((rand(120 - accurate * 40, 180 - accurate * 60)), EFFECT_IRRADIATE, 0)
+
+ calibrated = FALSE
return
/obj/machinery/teleport/hub/update_icon()
diff --git a/code/game/objects/effects/alien_acid.dm b/code/game/objects/effects/alien_acid.dm
index 1a605d64b0..3b5a029df4 100644
--- a/code/game/objects/effects/alien_acid.dm
+++ b/code/game/objects/effects/alien_acid.dm
@@ -17,7 +17,7 @@
target = get_turf(src)
if(acid_amt)
- acid_level = min(acid_amt*acid_pwr, 12000) //capped so the acid effect doesn't last a half hour on the floor.
+ acid_level = min( (CLAMP(round(acid_amt, 1), 0, INFINITY)) *acid_pwr, 12000) //capped so the acid effect doesn't last a half hour on the floor.
//handle APCs and newscasters and stuff nicely
pixel_x = target.pixel_x + rand(-4,4)
diff --git a/code/game/objects/items/devices/scanners.dm b/code/game/objects/items/devices/scanners.dm
index 57b9e89af7..0059938720 100644
--- a/code/game/objects/items/devices/scanners.dm
+++ b/code/game/objects/items/devices/scanners.dm
@@ -163,7 +163,37 @@ SLIME SCANNER
msg += "\tSevere brain damage detected. Subject likely to have mental traumas.\n"
else if (M.getBrainLoss() >= 45)
msg += "\tBrain damage detected.\n"
- if(iscarbon(M))
+ if(ishuman(M) && advanced) // Should I make this not advanced?
+ var/mob/living/carbon/human/H = M
+ var/obj/item/organ/liver/L = H.getorganslot("liver")
+ if(L)
+ if(L.swelling > 20)
+ msg += "\tSubject is suffering from an enlarged liver.\n" //i.e. shrink their liver or give them a transplant.
+ else
+ msg += "\tSubject's liver is missing.\n"
+ var/obj/item/organ/tongue/T = H.getorganslot("tongue")
+ if(T)
+ if(T.damage > 40)
+ msg += "\tSubject is suffering from severe burn tissue on their tongue.\n" //i.e. their tongue is shot
+ if(T.name == "fluffy tongue")
+ msg += "\tSubject is suffering from a fluffified tongue. Suggested cure: Yamerol or a tongue transplant.\n"
+ else
+ msg += "\tSubject's tongue is missing.\n"
+ var/obj/item/organ/lungs/Lung = H.getorganslot("lungs")
+ if(Lung)
+ if(Lung.damage > 150)
+ msg += "\tSubject is suffering from acute emphysema leading to trouble breathing.\n" //i.e. Their lungs are shot
+ else
+ msg += "\tSubject's lungs have collapsed from trauma!\n"
+ var/obj/item/organ/genital/penis/P = H.getorganslot("penis")
+ if(P)
+ if(P.length>20)
+ msg += "\tSubject has a sizeable gentleman's organ at [P.length] inches.\n"
+ var/obj/item/organ/genital/breasts/Br = H.getorganslot("breasts")
+ if(Br)
+ if(Br.cached_size>5)
+ msg += "\tSubject has a sizeable bosom with a [Br.size] cup.\n"
+
var/mob/living/carbon/C = M
if(LAZYLEN(C.get_traumas()))
var/list/trauma_text = list()
@@ -183,7 +213,7 @@ SLIME SCANNER
msg += "\tSubject has the following physiological traits: [C.get_trait_string()].\n"
if(advanced)
msg += "\tBrain Activity Level: [(200 - M.getBrainLoss())/2]%.\n"
- if (M.radiation)
+ if(M.radiation)
msg += "\tSubject is irradiated.\n"
if(advanced)
msg += "\tRadiation Level: [M.radiation]%.\n"
@@ -191,6 +221,17 @@ SLIME SCANNER
if(advanced && M.hallucinating())
msg += "\tSubject is hallucinating.\n"
+ //MKUltra
+ if(advanced && M.has_status_effect(/datum/status_effect/chem/enthrall))
+ msg += "\tSubject has abnormal brain fuctions.\n"
+
+ //Astrogen shenanigans
+ if(advanced && M.reagents.has_reagent("astral"))
+ if(M.mind)
+ msg += "\tWarning: subject may be possesed.\n"
+ else
+ msg += "\tSubject appears to be astrally projecting.\n"
+
//Eyes and ears
if(advanced)
if(iscarbon(M))
@@ -256,6 +297,7 @@ SLIME SCANNER
for(var/obj/item/bodypart/org in damaged)
msg += "\t\t[capitalize(org.name)]: [(org.brute_dam > 0) ? "[org.brute_dam]" : "0"]-[(org.burn_dam > 0) ? "[org.burn_dam]" : "0"]\n"
+
// Species and body temperature
if(ishuman(M))
var/mob/living/carbon/human/H = M
@@ -310,7 +352,7 @@ SLIME SCANNER
var/mob/living/carbon/human/H = C
if(H.bleed_rate)
msg += "Subject is bleeding!\n"
- var/blood_percent = round((C.blood_volume / BLOOD_VOLUME_NORMAL)*100)
+ var/blood_percent = round((C.blood_volume / (BLOOD_VOLUME_NORMAL * C.blood_ratio))*100)
var/blood_type = C.dna.blood_type
if(blood_id != "blood")//special blood substance
var/datum/reagent/R = GLOB.chemical_reagents_list[blood_id]
@@ -318,9 +360,9 @@ SLIME SCANNER
blood_type = R.name
else
blood_type = blood_id
- if(C.blood_volume <= BLOOD_VOLUME_SAFE && C.blood_volume > BLOOD_VOLUME_OKAY)
+ if(C.blood_volume <= (BLOOD_VOLUME_SAFE*C.blood_ratio) && C.blood_volume > (BLOOD_VOLUME_OKAY*C.blood_ratio))
msg += "LOW blood level [blood_percent] %, [C.blood_volume] cl, type: [blood_type]\n"
- else if(C.blood_volume <= BLOOD_VOLUME_OKAY)
+ else if(C.blood_volume <= (BLOOD_VOLUME_OKAY*C.blood_ratio))
msg += "CRITICAL blood level [blood_percent] %, [C.blood_volume] cl, type: [blood_type]\n"
else
msg += "Blood level [blood_percent] %, [C.blood_volume] cl, type: [blood_type]\n"
diff --git a/code/game/objects/items/handcuffs.dm b/code/game/objects/items/handcuffs.dm
index 36a92b3db9..a306b48385 100644
--- a/code/game/objects/items/handcuffs.dm
+++ b/code/game/objects/items/handcuffs.dm
@@ -164,21 +164,6 @@
/obj/item/restraints/handcuffs/cable/white
item_color = "white"
-/obj/item/restraints/handcuffs/alien
- icon_state = "handcuffAlien"
-
-/obj/item/restraints/handcuffs/fake
- name = "fake handcuffs"
- desc = "Fake handcuffs meant for gag purposes."
- breakouttime = 10 //Deciseconds = 1s
-
-/obj/item/restraints/handcuffs/fake/kinky
- name = "kinky handcuffs"
- desc = "Fake handcuffs meant for erotic roleplay."
- icon = 'modular_citadel/icons/obj/items_and_weapons.dmi'
- icon_state = "handcuffgag"
- item_state = "kinkycuff"
-
/obj/item/restraints/handcuffs/cable/attackby(obj/item/I, mob/user, params)
..()
if(istype(I, /obj/item/stack/rods))
@@ -213,7 +198,6 @@
/obj/item/restraints/handcuffs/cable/zipties
name = "zipties"
desc = "Plastic, disposable zipties that can be used to restrain temporarily but are destroyed after use."
- icon_state = "zipties"
item_state = "zipties"
lefthand_file = 'icons/mob/inhands/equipment/security_lefthand.dmi'
righthand_file = 'icons/mob/inhands/equipment/security_righthand.dmi'
@@ -229,6 +213,21 @@
/obj/item/restraints/handcuffs/cable/zipties/used/attack()
return
+/obj/item/restraints/handcuffs/alien
+ icon_state = "handcuffAlien"
+
+/obj/item/restraints/handcuffs/fake
+ name = "fake handcuffs"
+ desc = "Fake handcuffs meant for gag purposes."
+ breakouttime = 10 //Deciseconds = 1s
+
+/obj/item/restraints/handcuffs/fake/kinky
+ name = "kinky handcuffs"
+ desc = "Fake handcuffs meant for erotic roleplay."
+ icon = 'modular_citadel/icons/obj/items_and_weapons.dmi'
+ icon_state = "handcuffgag"
+ item_state = "kinkycuff"
+
//Legcuffs
/obj/item/restraints/legcuffs
diff --git a/code/game/objects/items/storage/firstaid.dm b/code/game/objects/items/storage/firstaid.dm
index e9b074d40c..312ef35430 100644
--- a/code/game/objects/items/storage/firstaid.dm
+++ b/code/game/objects/items/storage/firstaid.dm
@@ -1,365 +1,391 @@
-/* First aid storage
- * Contains:
- * First Aid Kits
- * Pill Bottles
- * Dice Pack (in a pill bottle)
- */
-
-/*
- * First Aid Kits
- */
-/obj/item/storage/firstaid
- name = "first-aid kit"
- desc = "It's an emergency medical kit for those serious boo-boos."
- icon_state = "firstaid"
- lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi'
- righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi'
- throw_speed = 3
- throw_range = 7
- var/empty = FALSE
-
-/obj/item/storage/firstaid/regular
- icon_state = "firstaid"
- desc = "A first aid kit with the ability to heal common types of injuries."
-
-/obj/item/storage/firstaid/regular/suicide_act(mob/living/carbon/user)
- user.visible_message("[user] begins giving [user.p_them()]self aids with \the [src]! It looks like [user.p_theyre()] trying to commit suicide!")
- return BRUTELOSS
-
-/obj/item/storage/firstaid/regular/PopulateContents()
- if(empty)
- return
- new /obj/item/stack/medical/gauze(src)
- new /obj/item/stack/medical/bruise_pack(src)
- new /obj/item/stack/medical/bruise_pack(src)
- new /obj/item/stack/medical/ointment(src)
- new /obj/item/stack/medical/ointment(src)
- new /obj/item/reagent_containers/hypospray/medipen(src)
- new /obj/item/healthanalyzer(src)
-
-/obj/item/storage/firstaid/ancient
- icon_state = "firstaid"
- desc = "A first aid kit with the ability to heal common types of injuries."
-
-/obj/item/storage/firstaid/ancient/PopulateContents()
- if(empty)
- return
- new /obj/item/stack/medical/gauze(src)
- new /obj/item/stack/medical/bruise_pack(src)
- new /obj/item/stack/medical/bruise_pack(src)
- new /obj/item/stack/medical/bruise_pack(src)
- new /obj/item/stack/medical/ointment(src)
- new /obj/item/stack/medical/ointment(src)
- new /obj/item/stack/medical/ointment(src)
-
-/obj/item/storage/firstaid/fire
- name = "burn treatment kit"
- desc = "A specialized medical kit for when the toxins lab -spontaneously- burns down."
- icon_state = "ointment"
- item_state = "firstaid-ointment"
-
-/obj/item/storage/firstaid/fire/suicide_act(mob/living/carbon/user)
- user.visible_message("[user] begins rubbing \the [src] against [user.p_them()]self! It looks like [user.p_theyre()] trying to start a fire!")
- return FIRELOSS
-
-/obj/item/storage/firstaid/fire/Initialize(mapload)
- . = ..()
- icon_state = pick("ointment","firefirstaid")
-
-/obj/item/storage/firstaid/fire/PopulateContents()
- if(empty)
- return
- for(var/i in 1 to 3)
- new /obj/item/reagent_containers/pill/patch/silver_sulf(src)
- new /obj/item/reagent_containers/pill/oxandrolone(src)
- new /obj/item/reagent_containers/pill/oxandrolone(src)
- new /obj/item/reagent_containers/hypospray/medipen(src)
- new /obj/item/healthanalyzer(src)
-
-/obj/item/storage/firstaid/toxin
- name = "toxin treatment kit"
- desc = "Used to treat toxic blood content and radiation poisoning."
- icon_state = "antitoxin"
- item_state = "firstaid-toxin"
-
-/obj/item/storage/firstaid/toxin/suicide_act(mob/living/carbon/user)
- user.visible_message("[user] begins licking the lead paint off \the [src]! It looks like [user.p_theyre()] trying to commit suicide!")
- return TOXLOSS
-
-/obj/item/storage/firstaid/toxin/Initialize(mapload)
- . = ..()
- icon_state = pick("antitoxin","antitoxfirstaid","antitoxfirstaid2","antitoxfirstaid3")
-
-/obj/item/storage/firstaid/toxin/PopulateContents()
- if(empty)
- return
- for(var/i in 1 to 4)
- new /obj/item/reagent_containers/syringe/charcoal(src)
- for(var/i in 1 to 2)
- new /obj/item/storage/pill_bottle/charcoal(src)
- new /obj/item/healthanalyzer(src)
-
-/obj/item/storage/firstaid/radbgone
- name = "radiation treatment kit"
- desc = "Used to treat minor toxic blood content and major radiation poisoning."
- icon_state = "antitoxin"
- item_state = "firstaid-toxin"
-
-/obj/item/storage/firstaid/radbgone/suicide_act(mob/living/carbon/user)
- user.visible_message("[user] begins licking the lead paint off \the [src]! It looks like [user.p_theyre()] trying to commit suicide!")
- return TOXLOSS
-
-/obj/item/storage/firstaid/radbgone/PopulateContents()
- if(empty)
- return
- if(prob(50))
- new /obj/item/reagent_containers/pill/mutarad(src)
- if(prob(80))
- new /obj/item/reagent_containers/pill/antirad_plus(src)
- new /obj/item/reagent_containers/syringe/charcoal(src)
- new /obj/item/storage/pill_bottle/charcoal(src)
- new /obj/item/reagent_containers/pill/mutadone(src)
- new /obj/item/reagent_containers/pill/antirad(src)
- new /obj/item/reagent_containers/food/drinks/bottle/vodka(src)
- new /obj/item/healthanalyzer(src)
-
-
-/obj/item/storage/firstaid/o2
- name = "oxygen deprivation treatment kit"
- desc = "A box full of oxygen goodies."
- icon_state = "o2"
- item_state = "firstaid-o2"
-
-/obj/item/storage/firstaid/o2/suicide_act(mob/living/carbon/user)
- user.visible_message("[user] begins hitting [user.p_their()] neck with \the [src]! It looks like [user.p_theyre()] trying to commit suicide!")
- return OXYLOSS
-
-/obj/item/storage/firstaid/o2/PopulateContents()
- if(empty)
- return
- for(var/i in 1 to 4)
- new /obj/item/reagent_containers/pill/salbutamol(src)
- new /obj/item/reagent_containers/hypospray/medipen(src)
- new /obj/item/reagent_containers/hypospray/medipen(src)
- new /obj/item/healthanalyzer(src)
-
-/obj/item/storage/firstaid/brute
- name = "brute trauma treatment kit"
- desc = "A first aid kit for when you get toolboxed."
- icon_state = "brute"
- item_state = "firstaid-brute"
-
-/obj/item/storage/firstaid/brute/suicide_act(mob/living/carbon/user)
- user.visible_message("[user] begins beating [user.p_them()]self over the head with \the [src]! It looks like [user.p_theyre()] trying to commit suicide!")
- return BRUTELOSS
-
-/obj/item/storage/firstaid/brute/PopulateContents()
- if(empty)
- return
- for(var/i in 1 to 4)
- new /obj/item/reagent_containers/pill/patch/styptic(src)
- new /obj/item/stack/medical/gauze(src)
- new /obj/item/stack/medical/gauze(src)
- new /obj/item/healthanalyzer(src)
-
-/obj/item/storage/firstaid/tactical
- name = "combat medical kit"
- desc = "I hope you've got insurance."
- icon_state = "bezerk"
-
-/obj/item/storage/firstaid/tactical/ComponentInitialize()
- . = ..()
- GET_COMPONENT(STR, /datum/component/storage)
- STR.max_w_class = WEIGHT_CLASS_NORMAL
-
-/obj/item/storage/firstaid/tactical/PopulateContents()
- if(empty)
- return
- new /obj/item/stack/medical/gauze(src)
- new /obj/item/defibrillator/compact/combat/loaded(src)
- new /obj/item/reagent_containers/hypospray/combat(src)
- new /obj/item/reagent_containers/pill/patch/styptic(src)
- new /obj/item/reagent_containers/pill/patch/silver_sulf(src)
- new /obj/item/reagent_containers/syringe/lethal/choral(src)
- new /obj/item/clothing/glasses/hud/health/night(src)
-
-/*
- * Pill Bottles
- */
-
-/obj/item/storage/pill_bottle
- name = "pill bottle"
- desc = "It's an airtight container for storing medication."
- icon_state = "pill_canister"
- icon = 'icons/obj/chemical.dmi'
- item_state = "contsolid"
- lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi'
- righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi'
- w_class = WEIGHT_CLASS_SMALL
-
-/obj/item/storage/pill_bottle/ComponentInitialize()
- . = ..()
- GET_COMPONENT(STR, /datum/component/storage)
- STR.allow_quick_gather = TRUE
- STR.click_gather = TRUE
- STR.can_hold = typecacheof(list(/obj/item/reagent_containers/pill, /obj/item/dice))
-
-/obj/item/storage/pill_bottle/suicide_act(mob/user)
- user.visible_message("[user] is trying to get the cap off [src]! It looks like [user.p_theyre()] trying to commit suicide!")
- return (TOXLOSS)
-
-/obj/item/storage/pill_bottle/charcoal
- name = "bottle of charcoal pills"
- desc = "Contains pills used to counter toxins."
-
-/obj/item/storage/pill_bottle/charcoal/PopulateContents()
- for(var/i in 1 to 7)
- new /obj/item/reagent_containers/pill/charcoal(src)
-
-/obj/item/storage/pill_bottle/antirad
- name = "bottle of charcoal pills"
- desc = "Contains pills used to counter radiation poisoning."
-
-/obj/item/storage/pill_bottle/anitrad/PopulateContents()
- for(var/i in 1 to 5)
- new /obj/item/reagent_containers/pill/antirad(src)
-
-/obj/item/storage/pill_bottle/epinephrine
- name = "bottle of epinephrine pills"
- desc = "Contains pills used to stabilize patients."
-
-/obj/item/storage/pill_bottle/epinephrine/PopulateContents()
- for(var/i in 1 to 7)
- new /obj/item/reagent_containers/pill/epinephrine(src)
-
-/obj/item/storage/pill_bottle/mutadone
- name = "bottle of mutadone pills"
- desc = "Contains pills used to treat genetic abnormalities."
-
-/obj/item/storage/pill_bottle/mutadone/PopulateContents()
- for(var/i in 1 to 7)
- new /obj/item/reagent_containers/pill/mutadone(src)
-
-/obj/item/storage/pill_bottle/mannitol
- name = "bottle of mannitol pills"
- desc = "Contains pills used to treat brain damage."
-
-/obj/item/storage/pill_bottle/mannitol/PopulateContents()
- for(var/i in 1 to 7)
- new /obj/item/reagent_containers/pill/mannitol(src)
-
-/obj/item/storage/pill_bottle/stimulant
- name = "bottle of stimulant pills"
- desc = "Guaranteed to give you that extra burst of energy during a long shift!"
-
-/obj/item/storage/pill_bottle/stimulant/PopulateContents()
- for(var/i in 1 to 5)
- new /obj/item/reagent_containers/pill/stimulant(src)
-
-/obj/item/storage/pill_bottle/mining
- name = "bottle of patches"
- desc = "Contains patches used to treat brute and burn damage."
-
-/obj/item/storage/pill_bottle/mining/PopulateContents()
- new /obj/item/reagent_containers/pill/patch/silver_sulf(src)
- for(var/i in 1 to 3)
- new /obj/item/reagent_containers/pill/patch/styptic(src)
-
-/obj/item/storage/pill_bottle/zoom
- name = "suspicious pill bottle"
- desc = "The label is pretty old and almost unreadable, you recognize some chemical compounds."
-
-/obj/item/storage/pill_bottle/zoom/PopulateContents()
- for(var/i in 1 to 5)
- new /obj/item/reagent_containers/pill/zoom(src)
-
-/obj/item/storage/pill_bottle/happy
- name = "suspicious pill bottle"
- desc = "There is a smiley on the top."
-
-/obj/item/storage/pill_bottle/happy/PopulateContents()
- for(var/i in 1 to 5)
- new /obj/item/reagent_containers/pill/happy(src)
-
-/obj/item/storage/pill_bottle/lsd
- name = "suspicious pill bottle"
- desc = "There is a badly drawn thing with the shape of a mushroom."
-
-/obj/item/storage/pill_bottle/lsd/PopulateContents()
- for(var/i in 1 to 5)
- new /obj/item/reagent_containers/pill/lsd(src)
-
-/obj/item/storage/pill_bottle/aranesp
- name = "suspicious pill bottle"
- desc = "The label says 'gotta go fast'."
-
-/obj/item/storage/pill_bottle/aranesp/PopulateContents()
- for(var/i in 1 to 5)
- new /obj/item/reagent_containers/pill/aranesp(src)
-
-/obj/item/storage/pill_bottle/antirad_plus
- name = "anti radiation deluxe pill bottle"
- desc = "The label says 'Med-Co branded pills'."
-
-/obj/item/storage/pill_bottle/antirad_plus/PopulateContents()
- for(var/i in 1 to 7)
- new /obj/item/reagent_containers/pill/antirad_plus(src)
-
-/obj/item/storage/pill_bottle/mutarad
- name = "radiation treatment deluxe pill bottle"
- desc = "The label says 'Med-Co branded pills' and below that 'Contains Mutadone in each pill!`."
-
-/obj/item/storage/pill_bottle/mutarad/PopulateContents()
- for(var/i in 1 to 7)
- new /obj/item/reagent_containers/pill/mutarad(src)
-
-/obj/item/storage/pill_bottle/penis_enlargement
- name = "penis enlargement pills"
- desc = "You want penis enlargement pills?"
-
-/obj/item/storage/pill_bottle/penis_enlargement/PopulateContents()
- for(var/i in 1 to 7)
- new /obj/item/reagent_containers/pill/penis_enlargement(src)
-
-/////////////
-//Organ Box//
-/////////////
-
-/obj/item/storage/belt/organbox
- name = "Organ Storge"
- desc = "A compact box that helps hold massive amounts of implants, organs, and some tools. Has a belt clip for easy carrying"
- w_class = WEIGHT_CLASS_BULKY
- icon = 'icons/obj/mysterybox.dmi'
- icon_state = "organbox_open"
- lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi'
- righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi'
- throw_speed = 1
- throw_range = 1
-
-/obj/item/storage/belt/organbox/ComponentInitialize()
- . = ..()
- GET_COMPONENT(STR, /datum/component/storage)
- STR.max_items = 16
- STR.max_w_class = WEIGHT_CLASS_BULKY
- STR.max_combined_w_class = 20
- STR.can_hold = typecacheof(list(
- /obj/item/storage/pill_bottle,
- /obj/item/reagent_containers/hypospray,
- /obj/item/healthanalyzer,
- /obj/item/reagent_containers/syringe,
- /obj/item/clothing/glasses/hud/health,
- /obj/item/hemostat,
- /obj/item/scalpel,
- /obj/item/retractor,
- /obj/item/cautery,
- /obj/item/surgical_drapes,
- /obj/item/autosurgeon,
- /obj/item/organ,
- /obj/item/implant,
- /obj/item/implantpad,
- /obj/item/implantcase,
- /obj/item/implanter,
- /obj/item/circuitboard/computer/operating,
- /obj/item/stack/sheet/mineral/silver,
- /obj/item/organ_storage
- ))
+
+/* First aid storage
+ * Contains:
+ * First Aid Kits
+ * Pill Bottles
+ * Dice Pack (in a pill bottle)
+ */
+
+/*
+ * First Aid Kits
+ */
+/obj/item/storage/firstaid
+ name = "first-aid kit"
+ desc = "It's an emergency medical kit for those serious boo-boos."
+ icon_state = "firstaid"
+ lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi'
+ throw_speed = 3
+ throw_range = 7
+ var/empty = FALSE
+
+/obj/item/storage/firstaid/regular
+ icon_state = "firstaid"
+ desc = "A first aid kit with the ability to heal common types of injuries."
+
+/obj/item/storage/firstaid/regular/suicide_act(mob/living/carbon/user)
+ user.visible_message("[user] begins giving [user.p_them()]self aids with \the [src]! It looks like [user.p_theyre()] trying to commit suicide!")
+ return BRUTELOSS
+
+/obj/item/storage/firstaid/regular/PopulateContents()
+ if(empty)
+ return
+ new /obj/item/stack/medical/gauze(src)
+ new /obj/item/stack/medical/bruise_pack(src)
+ new /obj/item/stack/medical/bruise_pack(src)
+ new /obj/item/stack/medical/ointment(src)
+ new /obj/item/stack/medical/ointment(src)
+ new /obj/item/reagent_containers/hypospray/medipen(src)
+ new /obj/item/healthanalyzer(src)
+
+/obj/item/storage/firstaid/ancient
+ icon_state = "firstaid"
+ desc = "A first aid kit with the ability to heal common types of injuries."
+
+/obj/item/storage/firstaid/ancient/PopulateContents()
+ if(empty)
+ return
+ new /obj/item/stack/medical/gauze(src)
+ new /obj/item/stack/medical/bruise_pack(src)
+ new /obj/item/stack/medical/bruise_pack(src)
+ new /obj/item/stack/medical/bruise_pack(src)
+ new /obj/item/stack/medical/ointment(src)
+ new /obj/item/stack/medical/ointment(src)
+ new /obj/item/stack/medical/ointment(src)
+
+/obj/item/storage/firstaid/fire
+ name = "burn treatment kit"
+ desc = "A specialized medical kit for when the toxins lab -spontaneously- burns down."
+ icon_state = "ointment"
+ item_state = "firstaid-ointment"
+
+/obj/item/storage/firstaid/fire/suicide_act(mob/living/carbon/user)
+ user.visible_message("[user] begins rubbing \the [src] against [user.p_them()]self! It looks like [user.p_theyre()] trying to start a fire!")
+ return FIRELOSS
+
+/obj/item/storage/firstaid/fire/Initialize(mapload)
+ . = ..()
+ icon_state = pick("ointment","firefirstaid")
+
+/obj/item/storage/firstaid/fire/PopulateContents()
+ if(empty)
+ return
+ for(var/i in 1 to 3)
+ new /obj/item/reagent_containers/pill/patch/silver_sulf(src)
+ new /obj/item/reagent_containers/pill/oxandrolone(src)
+ new /obj/item/reagent_containers/pill/oxandrolone(src)
+ new /obj/item/reagent_containers/hypospray/medipen(src)
+ new /obj/item/healthanalyzer(src)
+
+/obj/item/storage/firstaid/toxin
+ name = "toxin treatment kit"
+ desc = "Used to treat toxic blood content and radiation poisoning."
+ icon_state = "antitoxin"
+ item_state = "firstaid-toxin"
+
+/obj/item/storage/firstaid/toxin/suicide_act(mob/living/carbon/user)
+ user.visible_message("[user] begins licking the lead paint off \the [src]! It looks like [user.p_theyre()] trying to commit suicide!")
+ return TOXLOSS
+
+/obj/item/storage/firstaid/toxin/Initialize(mapload)
+ . = ..()
+ icon_state = pick("antitoxin","antitoxfirstaid","antitoxfirstaid2","antitoxfirstaid3")
+
+/obj/item/storage/firstaid/toxin/PopulateContents()
+ if(empty)
+ return
+ for(var/i in 1 to 4)
+ new /obj/item/reagent_containers/syringe/charcoal(src)
+ for(var/i in 1 to 2)
+ new /obj/item/storage/pill_bottle/charcoal(src)
+ new /obj/item/healthanalyzer(src)
+
+/obj/item/storage/firstaid/radbgone
+ name = "radiation treatment kit"
+ desc = "Used to treat minor toxic blood content and major radiation poisoning."
+ icon_state = "antitoxin"
+ item_state = "firstaid-toxin"
+
+/obj/item/storage/firstaid/radbgone/suicide_act(mob/living/carbon/user)
+ user.visible_message("[user] begins licking the lead paint off \the [src]! It looks like [user.p_theyre()] trying to commit suicide!")
+ return TOXLOSS
+
+/obj/item/storage/firstaid/radbgone/PopulateContents()
+ if(empty)
+ return
+ if(prob(50))
+ new /obj/item/reagent_containers/pill/mutarad(src)
+ if(prob(80))
+ new /obj/item/reagent_containers/pill/antirad_plus(src)
+ new /obj/item/reagent_containers/syringe/charcoal(src)
+ new /obj/item/storage/pill_bottle/charcoal(src)
+ new /obj/item/reagent_containers/pill/mutadone(src)
+ new /obj/item/reagent_containers/pill/antirad(src)
+ new /obj/item/reagent_containers/food/drinks/bottle/vodka(src)
+ new /obj/item/healthanalyzer(src)
+
+
+/obj/item/storage/firstaid/o2
+ name = "oxygen deprivation treatment kit"
+ desc = "A box full of oxygen goodies."
+ icon_state = "o2"
+ item_state = "firstaid-o2"
+
+/obj/item/storage/firstaid/o2/suicide_act(mob/living/carbon/user)
+ user.visible_message("[user] begins hitting [user.p_their()] neck with \the [src]! It looks like [user.p_theyre()] trying to commit suicide!")
+ return OXYLOSS
+
+/obj/item/storage/firstaid/o2/PopulateContents()
+ if(empty)
+ return
+ for(var/i in 1 to 4)
+ new /obj/item/reagent_containers/pill/salbutamol(src)
+ new /obj/item/reagent_containers/hypospray/medipen(src)
+ new /obj/item/reagent_containers/hypospray/medipen(src)
+ new /obj/item/healthanalyzer(src)
+
+/obj/item/storage/firstaid/brute
+ name = "brute trauma treatment kit"
+ desc = "A first aid kit for when you get toolboxed."
+ icon_state = "brute"
+ item_state = "firstaid-brute"
+
+/obj/item/storage/firstaid/brute/suicide_act(mob/living/carbon/user)
+ user.visible_message("[user] begins beating [user.p_them()]self over the head with \the [src]! It looks like [user.p_theyre()] trying to commit suicide!")
+ return BRUTELOSS
+
+/obj/item/storage/firstaid/brute/PopulateContents()
+ if(empty)
+ return
+ for(var/i in 1 to 4)
+ new /obj/item/reagent_containers/pill/patch/styptic(src)
+ new /obj/item/stack/medical/gauze(src)
+ new /obj/item/stack/medical/gauze(src)
+ new /obj/item/healthanalyzer(src)
+
+/obj/item/storage/firstaid/tactical
+ name = "combat medical kit"
+ desc = "I hope you've got insurance."
+ icon_state = "bezerk"
+
+/obj/item/storage/firstaid/tactical/ComponentInitialize()
+ . = ..()
+ GET_COMPONENT(STR, /datum/component/storage)
+ STR.max_w_class = WEIGHT_CLASS_NORMAL
+
+/obj/item/storage/firstaid/tactical/PopulateContents()
+ if(empty)
+ return
+ new /obj/item/stack/medical/gauze(src)
+ new /obj/item/defibrillator/compact/combat/loaded(src)
+ new /obj/item/reagent_containers/hypospray/combat(src)
+ new /obj/item/reagent_containers/pill/patch/styptic(src)
+ new /obj/item/reagent_containers/pill/patch/silver_sulf(src)
+ new /obj/item/reagent_containers/syringe/lethal/choral(src)
+ new /obj/item/clothing/glasses/hud/health/night(src)
+
+/*
+ * Pill Bottles
+ */
+
+/obj/item/storage/pill_bottle
+ name = "pill bottle"
+ desc = "It's an airtight container for storing medication."
+ icon_state = "pill_canister"
+ icon = 'icons/obj/chemical.dmi'
+ item_state = "contsolid"
+ lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi'
+ w_class = WEIGHT_CLASS_SMALL
+
+/obj/item/storage/pill_bottle/ComponentInitialize()
+ . = ..()
+ GET_COMPONENT(STR, /datum/component/storage)
+ STR.allow_quick_gather = TRUE
+ STR.click_gather = TRUE
+ STR.can_hold = typecacheof(list(/obj/item/reagent_containers/pill, /obj/item/dice))
+
+/obj/item/storage/pill_bottle/suicide_act(mob/user)
+ user.visible_message("[user] is trying to get the cap off [src]! It looks like [user.p_theyre()] trying to commit suicide!")
+ return (TOXLOSS)
+
+/obj/item/storage/pill_bottle/charcoal
+ name = "bottle of charcoal pills"
+ desc = "Contains pills used to counter toxins."
+
+/obj/item/storage/pill_bottle/charcoal/PopulateContents()
+ for(var/i in 1 to 7)
+ new /obj/item/reagent_containers/pill/charcoal(src)
+
+/obj/item/storage/pill_bottle/antirad
+ name = "bottle of charcoal pills"
+ desc = "Contains pills used to counter radiation poisoning."
+
+/obj/item/storage/pill_bottle/anitrad/PopulateContents()
+ for(var/i in 1 to 5)
+ new /obj/item/reagent_containers/pill/antirad(src)
+
+/obj/item/storage/pill_bottle/epinephrine
+ name = "bottle of epinephrine pills"
+ desc = "Contains pills used to stabilize patients."
+
+/obj/item/storage/pill_bottle/epinephrine/PopulateContents()
+ for(var/i in 1 to 7)
+ new /obj/item/reagent_containers/pill/epinephrine(src)
+
+/obj/item/storage/pill_bottle/mutadone
+ name = "bottle of mutadone pills"
+ desc = "Contains pills used to treat genetic abnormalities."
+
+/obj/item/storage/pill_bottle/mutadone/PopulateContents()
+ for(var/i in 1 to 7)
+ new /obj/item/reagent_containers/pill/mutadone(src)
+
+/obj/item/storage/pill_bottle/mannitol
+ name = "bottle of mannitol pills"
+ desc = "Contains pills used to treat brain damage."
+
+/obj/item/storage/pill_bottle/mannitol/PopulateContents()
+ for(var/i in 1 to 7)
+ new /obj/item/reagent_containers/pill/mannitol(src)
+
+/obj/item/storage/pill_bottle/stimulant
+ name = "bottle of stimulant pills"
+ desc = "Guaranteed to give you that extra burst of energy during a long shift!"
+
+/obj/item/storage/pill_bottle/stimulant/PopulateContents()
+ for(var/i in 1 to 5)
+ new /obj/item/reagent_containers/pill/stimulant(src)
+
+/obj/item/storage/pill_bottle/mining
+ name = "bottle of patches"
+ desc = "Contains patches used to treat brute and burn damage."
+
+/obj/item/storage/pill_bottle/mining/PopulateContents()
+ new /obj/item/reagent_containers/pill/patch/silver_sulf(src)
+ for(var/i in 1 to 3)
+ new /obj/item/reagent_containers/pill/patch/styptic(src)
+
+/obj/item/storage/pill_bottle/zoom
+ name = "suspicious pill bottle"
+ desc = "The label is pretty old and almost unreadable, you recognize some chemical compounds."
+
+/obj/item/storage/pill_bottle/zoom/PopulateContents()
+ for(var/i in 1 to 5)
+ new /obj/item/reagent_containers/pill/zoom(src)
+
+/obj/item/storage/pill_bottle/happy
+ name = "suspicious pill bottle"
+ desc = "There is a smiley on the top."
+
+/obj/item/storage/pill_bottle/happy/PopulateContents()
+ for(var/i in 1 to 5)
+ new /obj/item/reagent_containers/pill/happy(src)
+
+/obj/item/storage/pill_bottle/lsd
+ name = "suspicious pill bottle"
+ desc = "There is a badly drawn thing with the shape of a mushroom."
+
+/obj/item/storage/pill_bottle/lsd/PopulateContents()
+ for(var/i in 1 to 5)
+ new /obj/item/reagent_containers/pill/lsd(src)
+
+/obj/item/storage/pill_bottle/aranesp
+ name = "suspicious pill bottle"
+ desc = "The label says 'gotta go fast'."
+
+/obj/item/storage/pill_bottle/aranesp/PopulateContents()
+ for(var/i in 1 to 5)
+ new /obj/item/reagent_containers/pill/aranesp(src)
+
+/obj/item/storage/pill_bottle/psicodine
+ name = "bottle of psicodine pills"
+ desc = "Contains pills used to treat mental distress and traumas."
+
+/obj/item/storage/pill_bottle/psicodine/PopulateContents()
+ for(var/i in 1 to 7)
+ new /obj/item/reagent_containers/pill/psicodine(src)
+
+/obj/item/storage/pill_bottle/happiness
+ name = "happiness pill bottle"
+ desc = "The label is long gone, in its place an 'H' written with a marker."
+
+/obj/item/storage/pill_bottle/happiness/PopulateContents()
+ for(var/i in 1 to 5)
+ new /obj/item/reagent_containers/pill/happiness(src)
+
+/obj/item/storage/pill_bottle/antirad_plus
+ name = "anti radiation deluxe pill bottle"
+ desc = "The label says 'Med-Co branded pills'."
+
+/obj/item/storage/pill_bottle/antirad_plus/PopulateContents()
+ for(var/i in 1 to 7)
+ new /obj/item/reagent_containers/pill/antirad_plus(src)
+
+/obj/item/storage/pill_bottle/mutarad
+ name = "radiation treatment deluxe pill bottle"
+ desc = "The label says 'Med-Co branded pills' and below that 'Contains Mutadone in each pill!`."
+
+/obj/item/storage/pill_bottle/mutarad/PopulateContents()
+ for(var/i in 1 to 7)
+ new /obj/item/reagent_containers/pill/mutarad(src)
+
+/obj/item/storage/pill_bottle/penis_enlargement
+ name = "penis enlargement pills"
+ desc = "You want penis enlargement pills?"
+
+/obj/item/storage/pill_bottle/penis_enlargement/PopulateContents()
+ for(var/i in 1 to 7)
+ new /obj/item/reagent_containers/pill/penis_enlargement(src)
+
+/obj/item/storage/pill_bottle/breast_enlargement
+ name = "breast enlargement pills"
+ desc = "Made by Fermichem - They have a woman with breasts larger than she is on them. The warming states not to take more than 10u at a time."
+
+/obj/item/storage/pill_bottle/breast_enlargement/PopulateContents()
+ for(var/i in 1 to 7)
+ new /obj/item/reagent_containers/pill/breast_enlargement(src)
+
+/////////////
+//Organ Box//
+/////////////
+
+/obj/item/storage/belt/organbox
+ name = "Organ Storge"
+ desc = "A compact box that helps hold massive amounts of implants, organs, and some tools. Has a belt clip for easy carrying"
+ w_class = WEIGHT_CLASS_BULKY
+ icon = 'icons/obj/mysterybox.dmi'
+ icon_state = "organbox_open"
+ lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi'
+ throw_speed = 1
+ throw_range = 1
+
+/obj/item/storage/belt/organbox/ComponentInitialize()
+ . = ..()
+ GET_COMPONENT(STR, /datum/component/storage)
+ STR.max_items = 16
+ STR.max_w_class = WEIGHT_CLASS_BULKY
+ STR.max_combined_w_class = 20
+ STR.can_hold = typecacheof(list(
+ /obj/item/storage/pill_bottle,
+ /obj/item/reagent_containers/hypospray,
+ /obj/item/healthanalyzer,
+ /obj/item/reagent_containers/syringe,
+ /obj/item/clothing/glasses/hud/health,
+ /obj/item/hemostat,
+ /obj/item/scalpel,
+ /obj/item/retractor,
+ /obj/item/cautery,
+ /obj/item/surgical_drapes,
+ /obj/item/autosurgeon,
+ /obj/item/organ,
+ /obj/item/implant,
+ /obj/item/implantpad,
+ /obj/item/implantcase,
+ /obj/item/implanter,
+ /obj/item/circuitboard/computer/operating,
+ /obj/item/stack/sheet/mineral/silver,
+ /obj/item/organ_storage
+ ))
+
diff --git a/code/game/objects/structures/crates_lockers/closets.dm b/code/game/objects/structures/crates_lockers/closets.dm
index eedb49f2c3..f097d2903a 100644
--- a/code/game/objects/structures/crates_lockers/closets.dm
+++ b/code/game/objects/structures/crates_lockers/closets.dm
@@ -1,600 +1,616 @@
-/obj/structure/closet
- name = "closet"
- desc = "It's a basic storage unit."
- icon = 'icons/obj/closet.dmi'
- icon_state = "generic"
- density = TRUE
- layer = BELOW_OBJ_LAYER
- var/icon_door = null
- var/icon_door_override = FALSE //override to have open overlay use icon different to its base's
- var/secure = FALSE //secure locker or not, also used if overriding a non-secure locker with a secure door overlay to add fancy lights
- var/opened = FALSE
- var/welded = FALSE
- var/locked = FALSE
- var/large = TRUE
- var/wall_mounted = 0 //never solid (You can always pass over it)
- max_integrity = 200
- integrity_failure = 50
- armor = list("melee" = 20, "bullet" = 10, "laser" = 10, "energy" = 0, "bomb" = 10, "bio" = 0, "rad" = 0, "fire" = 70, "acid" = 60)
- var/breakout_time = 1200
- var/message_cooldown
- var/can_weld_shut = TRUE
- var/horizontal = FALSE
- var/allow_objects = FALSE
- var/allow_dense = FALSE
- var/dense_when_open = FALSE //if it's dense when open or not
- var/max_mob_size = MOB_SIZE_HUMAN //Biggest mob_size accepted by the container
- var/mob_storage_capacity = 3 // how many human sized mob/living can fit together inside a closet.
- var/storage_capacity = 30 //This is so that someone can't pack hundreds of items in a locker/crate then open it in a populated area to crash clients.
- var/cutting_tool = /obj/item/weldingtool
- var/open_sound = 'sound/machines/click.ogg'
- var/close_sound = 'sound/machines/click.ogg'
- var/material_drop = /obj/item/stack/sheet/metal
- var/material_drop_amount = 2
- var/delivery_icon = "deliverycloset" //which icon to use when packagewrapped. null to be unwrappable.
- var/anchorable = TRUE
- var/icon_welded = "welded"
- var/obj/item/electronics/airlock/lockerelectronics //Installed electronics
- var/lock_in_use = FALSE //Someone is doing some stuff with the lock here, better not proceed further
-
-/obj/structure/closet/Initialize(mapload)
- . = ..()
- update_icon()
- PopulateContents()
- if(mapload && !opened) // if closed, any item at the crate's loc is put in the contents
- take_contents()
- if(secure)
- lockerelectronics = new(src)
- lockerelectronics.accesses = req_access
-
-//USE THIS TO FILL IT, NOT INITIALIZE OR NEW
-/obj/structure/closet/proc/PopulateContents()
- return
-
-/obj/structure/closet/Destroy()
- dump_contents(override = FALSE)
- return ..()
-
-/obj/structure/closet/update_icon()
- cut_overlays()
- if(opened & icon_door_override)
- add_overlay("[icon_door]_open")
- layer = OBJ_LAYER
- return
- else if(opened)
- add_overlay("[icon_state]_open")
- return
- if(icon_door)
- add_overlay("[icon_door]_door")
- else
- layer = BELOW_OBJ_LAYER
- add_overlay("[icon_state]_door")
- if(welded)
- add_overlay("welded")
- if(!secure)
- return
- if(broken)
- add_overlay("off")
- add_overlay("sparking")
- else if(locked)
- add_overlay("locked")
- else
- add_overlay("unlocked")
-
-/obj/structure/closet/examine(mob/user)
- ..()
- if(welded)
- to_chat(user, "It's welded shut.")
- if(anchored)
- to_chat(user, "It is bolted to the ground.")
- if(opened)
- to_chat(user, "The parts are welded together.")
- else if(secure && !opened)
- else if(broken)
- to_chat(user, "The lock is screwed in.")
- else if(secure)
- to_chat(user, "Alt-click to [locked ? "unlock" : "lock"].")
- if(isliving(user))
- var/mob/living/L = user
- if(HAS_TRAIT(L, TRAIT_SKITTISH))
- to_chat(user, "Ctrl-Shift-click [src] to jump inside.")
-
-/obj/structure/closet/CanPass(atom/movable/mover, turf/target)
- if(wall_mounted)
- return TRUE
- return !density
-
-/obj/structure/closet/proc/can_open(mob/living/user)
- if(welded || locked)
- return FALSE
- var/turf/T = get_turf(src)
- for(var/mob/living/L in T)
- if(L.anchored || horizontal && L.mob_size > MOB_SIZE_TINY && L.density)
- if(user)
- to_chat(user, "There's something large on top of [src], preventing it from opening." )
- return FALSE
- return TRUE
-
-/obj/structure/closet/proc/can_close(mob/living/user)
- var/turf/T = get_turf(src)
- for(var/obj/structure/closet/closet in T)
- if(closet != src && !closet.wall_mounted)
- return FALSE
- for(var/mob/living/L in T)
- if(L.anchored || horizontal && L.mob_size > MOB_SIZE_TINY && L.density)
- if(user)
- to_chat(user, "There's something too large in [src], preventing it from closing.")
- return FALSE
- return TRUE
-
-/obj/structure/closet/proc/can_lock(mob/living/user, var/check_access = TRUE) //set check_access to FALSE if you only need to check if a locker has a functional lock rather than access
- if(!secure)
- return FALSE
- if(broken)
- to_chat(user, "[src] is broken!")
- return FALSE
- if(QDELETED(lockerelectronics) && !locked) //We want to be able to unlock it regardless of electronics, but only lockable with electronics
- to_chat(user, "[src] is missing locker electronics!")
- return FALSE
- if(!check_access)
- return TRUE
- if(allowed(user))
- return TRUE
- to_chat(user, "Access denied.")
-
-/obj/structure/closet/proc/togglelock(mob/living/user)
- add_fingerprint(user)
- if(opened)
- return
- if(!can_lock(user))
- return
- locked = !locked
- user.visible_message("[user] [locked ? null : "un"]locks [src].",
- "You [locked ? null : "un"]lock [src].")
- update_icon()
-
-/obj/structure/closet/proc/dump_contents(var/override = TRUE) //Override is for not revealing the locker electronics when you open the locker, for example
- var/atom/L = drop_location()
- for(var/atom/movable/AM in src)
- if(AM == lockerelectronics && override)
- continue
- AM.forceMove(L)
- if(throwing) // you keep some momentum when getting out of a thrown closet
- step(AM, dir)
- if(throwing)
- throwing.finalize(FALSE)
-
-/obj/structure/closet/proc/take_contents()
- var/atom/L = drop_location()
- for(var/atom/movable/AM in L)
- if(AM != src && insert(AM) == -1) // limit reached
- break
-
-/obj/structure/closet/proc/open(mob/living/user)
- if(opened || !can_open(user))
- return
- playsound(loc, open_sound, 15, 1, -3)
- opened = TRUE
- if(!dense_when_open)
- density = FALSE
- climb_time *= 0.5 //it's faster to climb onto an open thing
- dump_contents()
- update_icon()
- return 1
-
-/obj/structure/closet/proc/insert(atom/movable/AM)
- if(contents.len >= storage_capacity)
- return -1
- if(insertion_allowed(AM))
- AM.forceMove(src)
- return TRUE
- else
- return FALSE
-
-
-/obj/structure/closet/proc/insertion_allowed(atom/movable/AM)
- if(ismob(AM))
- if(!isliving(AM)) //let's not put ghosts or camera mobs inside closets...
- return FALSE
- var/mob/living/L = AM
- if(L.anchored || L.buckled || L.incorporeal_move || L.has_buckled_mobs())
- return FALSE
- if(L.mob_size > MOB_SIZE_TINY) // Tiny mobs are treated as items.
- if(horizontal && L.density)
- return FALSE
- if(L.mob_size > max_mob_size)
- return FALSE
- var/mobs_stored = 0
- for(var/mob/living/M in contents)
- if(++mobs_stored >= mob_storage_capacity)
- return FALSE
- L.stop_pulling()
-
- else if(istype(AM, /obj/structure/closet))
- return FALSE
-
- else if(istype(AM, /obj/effect))
- return FALSE
-
- else if(isobj(AM))
- if((!allow_dense && AM.density) || AM.anchored || AM.has_buckled_mobs())
- return FALSE
- if(isitem(AM) && !HAS_TRAIT(AM, TRAIT_NODROP))
- return TRUE
- else if(!allow_objects && !istype(AM, /obj/effect/dummy/chameleon))
- return FALSE
- else
- return FALSE
-
- return TRUE
-
-/obj/structure/closet/proc/close(mob/living/user)
- if(!opened || !can_close(user))
- return FALSE
- take_contents()
- playsound(loc, close_sound, 15, 1, -3)
- climb_time = initial(climb_time)
- opened = FALSE
- density = TRUE
- update_icon()
- return TRUE
-
-/obj/structure/closet/proc/toggle(mob/living/user)
- if(opened)
- return close(user)
- else
- return open(user)
-
-/obj/structure/closet/proc/bust_open()
- welded = FALSE //applies to all lockers
- locked = FALSE //applies to critter crates and secure lockers only
- broken = TRUE //applies to secure lockers only
- open()
-
-/obj/structure/closet/proc/handle_lock_addition(mob/user, obj/item/electronics/airlock/E)
- add_fingerprint(user)
- if(lock_in_use)
- to_chat(user, "Wait for work on [src] to be done first!")
- return
- if(secure)
- to_chat(user, "This locker already has a lock!")
- return
- if(broken)
- to_chat(user, "Unscrew the broken lock first!")
- return
- if(!istype(E))
- return
- user.visible_message("[user] begins installing a lock on [src]...","You begin installing a lock on [src]...")
- lock_in_use = TRUE
- playsound(loc, 'sound/items/screwdriver.ogg', 50, 1)
- if(!do_after(user, 60, target = src))
- lock_in_use = FALSE
- return
- lock_in_use = FALSE
- to_chat(user, "You finish the lock on [src]!")
- E.forceMove(src)
- lockerelectronics = E
- req_access = E.accesses
- secure = TRUE
- update_icon()
- return TRUE
-
-/obj/structure/closet/proc/handle_lock_removal(mob/user, obj/item/screwdriver/S)
- if(lock_in_use)
- to_chat(user, "Wait for work on [src] to be done first!")
- return
- if(locked)
- to_chat(user, "Unlock it first!")
- return
- if(!secure)
- to_chat(user, "[src] doesn't have a lock that you can remove!")
- return
- if(!istype(S))
- return
- var/brokenword = broken ? "broken " : null
- user.visible_message("[user] begins removing the [brokenword]lock on [src]...","You begin removing the [brokenword]lock on [src]...")
- playsound(loc, S.usesound, 50, 1)
- lock_in_use = TRUE
- if(!do_after(user, 100 * S.toolspeed, target = src))
- lock_in_use = FALSE
- return
- to_chat(user, "You remove the [brokenword]lock from [src]!")
- if(!QDELETED(lockerelectronics))
- lockerelectronics.add_fingerprint(user)
- lockerelectronics.forceMove(user.loc)
- lockerelectronics = null
- req_access = null
- secure = FALSE
- broken = FALSE
- locked = FALSE
- lock_in_use = FALSE
- update_icon()
- return TRUE
-
-
-/obj/structure/closet/deconstruct(disassembled = TRUE)
- if(ispath(material_drop) && material_drop_amount && !(flags_1 & NODECONSTRUCT_1))
- new material_drop(loc, material_drop_amount)
- qdel(src)
-
-/obj/structure/closet/obj_break(damage_flag)
- if(!broken && !(flags_1 & NODECONSTRUCT_1))
- bust_open()
-
-/obj/structure/closet/attackby(obj/item/W, mob/user, params)
- if(user in src)
- return
- if(src.tool_interact(W,user))
- return 1 // No afterattack
- else
- return ..()
-
-/obj/structure/closet/proc/tool_interact(obj/item/W, mob/user)//returns TRUE if attackBy call shouldnt be continued (because tool was used/closet was of wrong type), FALSE if otherwise
- . = TRUE
- if(opened)
- if(istype(W, cutting_tool))
- if(istype(W, /obj/item/weldingtool))
- if(!W.tool_start_check(user, amount=0))
- return
-
- to_chat(user, "You begin cutting \the [src] apart...")
- if(W.use_tool(src, user, 40, volume=50))
- if(!opened)
- return
- user.visible_message("[user] slices apart \the [src].",
- "You cut \the [src] apart with \the [W].",
- "You hear welding.")
- deconstruct(TRUE)
- return
- else // for example cardboard box is cut with wirecutters
- user.visible_message("[user] cut apart \the [src].", \
- "You cut \the [src] apart with \the [W].")
- deconstruct(TRUE)
- return
- if(user.transferItemToLoc(W, drop_location())) // so we put in unlit welder too
- return TRUE
- else if(istype(W, /obj/item/electronics/airlock))
- handle_lock_addition(user, W)
- else if(istype(W, /obj/item/screwdriver))
- handle_lock_removal(user, W)
- else if(istype(W, /obj/item/weldingtool) && can_weld_shut)
- if(!W.tool_start_check(user, amount=0))
- return
-
- to_chat(user, "You begin [welded ? "unwelding":"welding"] \the [src]...")
- if(W.use_tool(src, user, 40, volume=50))
- if(opened)
- return
- welded = !welded
- after_weld(welded)
- user.visible_message("[user] [welded ? "welds shut" : "unwelds"] \the [src].",
- "You [welded ? "weld" : "unwelded"] \the [src] with \the [W].",
- "You hear welding.")
- update_icon()
- else if(istype(W, /obj/item/wrench) && anchorable)
- if(isinspace() && !anchored)
- return
- setAnchored(!anchored)
- W.play_tool_sound(src, 75)
- user.visible_message("[user] [anchored ? "anchored" : "unanchored"] \the [src] [anchored ? "to" : "from"] the ground.", \
- "You [anchored ? "anchored" : "unanchored"] \the [src] [anchored ? "to" : "from"] the ground.", \
- "You hear a ratchet.")
- else if(user.a_intent != INTENT_HARM && !(W.item_flags & NOBLUDGEON))
- if(W.GetID() || !toggle(user))
- togglelock(user)
- else
- return FALSE
-
-/obj/structure/closet/proc/after_weld(weld_state)
- return
-
-/obj/structure/closet/MouseDrop_T(atom/movable/O, mob/living/user)
- if(!istype(O) || O.anchored || istype(O, /obj/screen))
- return
- if(!istype(user) || user.incapacitated() || user.lying)
- return
- if(!Adjacent(user) || !user.Adjacent(O))
- return
- if(user == O) //try to climb onto it
- return ..()
- if(!opened)
- return
- if(!isturf(O.loc))
- return
-
- var/actuallyismob = 0
- if(isliving(O))
- actuallyismob = 1
- else if(!isitem(O))
- return
- var/turf/T = get_turf(src)
- var/list/targets = list(O, src)
- add_fingerprint(user)
- user.visible_message("[user] [actuallyismob ? "tries to ":""]stuff [O] into [src].", \
- "You [actuallyismob ? "try to ":""]stuff [O] into [src].", \
- "You hear clanging.")
- if(actuallyismob)
- if(do_after_mob(user, targets, 40))
- user.visible_message("[user] stuffs [O] into [src].", \
- "You stuff [O] into [src].", \
- "You hear a loud metal bang.")
- var/mob/living/L = O
- if(!issilicon(L))
- L.Knockdown(40)
- O.forceMove(T)
- close()
- else
- O.forceMove(T)
- return 1
-
-/obj/structure/closet/relaymove(mob/user)
- if(user.stat || !isturf(loc) || !isliving(user))
- return
- if(locked)
- if(message_cooldown <= world.time)
- message_cooldown = world.time + 50
- to_chat(user, "[src]'s door won't budge!")
- return
- container_resist(user)
-
-/obj/structure/closet/attack_hand(mob/user)
- . = ..()
- if(.)
- return
- if(user.lying && get_dist(src, user) > 0)
- return
-
- if(!toggle(user))
- togglelock(user)
-
-/obj/structure/closet/attack_paw(mob/user)
- return attack_hand(user)
-
-/obj/structure/closet/attack_robot(mob/user)
- if(user.Adjacent(src))
- return attack_hand(user)
-
-// tk grab then use on self
-/obj/structure/closet/attack_self_tk(mob/user)
- return attack_hand(user)
-
-/obj/structure/closet/verb/verb_toggleopen()
- set src in oview(1)
- set category = "Object"
- set name = "Toggle Open"
-
- if(!usr.canmove || usr.stat || usr.restrained())
- return
-
- if(iscarbon(usr) || issilicon(usr) || isdrone(usr))
- return attack_hand(usr)
- else
- to_chat(usr, "This mob type can't use this verb.")
-
-// Objects that try to exit a locker by stepping were doing so successfully,
-// and due to an oversight in turf/Enter() were going through walls. That
-// should be independently resolved, but this is also an interesting twist.
-/obj/structure/closet/Exit(atom/movable/AM)
- open()
- if(AM.loc == src)
- return 0
- return 1
-
-/obj/structure/closet/container_resist(mob/living/user)
- if(opened)
- return
- if(ismovableatom(loc))
- user.changeNext_move(CLICK_CD_BREAKOUT)
- user.last_special = world.time + CLICK_CD_BREAKOUT
- var/atom/movable/AM = loc
- AM.relay_container_resist(user, src)
- return
- if(!welded && !locked)
- open()
- return
-
- //okay, so the closet is either welded or locked... resist!!!
- user.changeNext_move(CLICK_CD_BREAKOUT)
- user.last_special = world.time + CLICK_CD_BREAKOUT
- user.visible_message("[src] begins to shake violently!", \
- "You lean on the back of [src] and start pushing the door open... (this will take about [DisplayTimeText(breakout_time)].)", \
- "You hear banging from [src].")
- if(do_after(user,(breakout_time), target = src))
- if(!user || user.stat != CONSCIOUS || user.loc != src || opened || (!locked && !welded) )
- return
- //we check after a while whether there is a point of resisting anymore and whether the user is capable of resisting
- user.visible_message("[user] successfully broke out of [src]!",
- "You successfully break out of [src]!")
- bust_open()
- else
- if(user.loc == src) //so we don't get the message if we resisted multiple times and succeeded.
- to_chat(user, "You fail to break out of [src]!")
-
-/obj/structure/closet/AltClick(mob/user)
- ..()
- if(!user.canUseTopic(src, be_close=TRUE) || !isturf(loc))
- to_chat(user, "You can't do that right now!")
- return
- togglelock(user)
-
-/obj/structure/closet/CtrlShiftClick(mob/living/user)
- if(!HAS_TRAIT(user, TRAIT_SKITTISH))
- return ..()
- if(!user.canUseTopic(src) || !isturf(user.loc))
- return
- dive_into(user)
-
-/obj/structure/closet/emag_act(mob/user)
- if(secure && !broken)
- user.visible_message("Sparks fly from [src]!",
- "You scramble [src]'s lock, breaking it open!",
- "You hear a faint electrical spark.")
- playsound(src, "sparks", 50, 1)
- broken = TRUE
- locked = FALSE
- if(!QDELETED(lockerelectronics))
- qdel(lockerelectronics)
- lockerelectronics = null
- update_icon()
-
-/obj/structure/closet/get_remote_view_fullscreens(mob/user)
- if(user.stat == DEAD || !(user.sight & (SEEOBJS|SEEMOBS)))
- user.overlay_fullscreen("remote_view", /obj/screen/fullscreen/impaired, 1)
-
-/obj/structure/closet/emp_act(severity)
- . = ..()
- if(. & EMP_PROTECT_SELF)
- return
- if (!(. & EMP_PROTECT_CONTENTS))
- for(var/obj/O in src)
- O.emp_act(severity)
- if(!secure || broken)
- return ..()
- if(prob(50 / severity))
- locked = !locked
- update_icon()
- if(prob(20 / severity) && !opened)
- if(!locked)
- open()
- else
- req_access = list()
- req_access += pick(get_all_accesses())
- if(!QDELETED(lockerelectronics))
- lockerelectronics.accesses = req_access
-
-/obj/structure/closet/contents_explosion(severity, target)
- for(var/atom/A in contents)
- A.ex_act(severity, target)
- CHECK_TICK
-
-/obj/structure/closet/singularity_act()
- dump_contents()
- ..()
-
-/obj/structure/closet/AllowDrop()
- return TRUE
-
-
-/obj/structure/closet/return_temperature()
- return
-
-/obj/structure/closet/proc/dive_into(mob/living/user)
- var/turf/T1 = get_turf(user)
- var/turf/T2 = get_turf(src)
- if(!opened)
- if(locked)
- togglelock(user, TRUE)
- if(!open(user))
- to_chat(user, "It won't budge!")
- return
- step_towards(user, T2)
- T1 = get_turf(user)
- if(T1 == T2)
- user.resting = TRUE //so people can jump into crates without slamming the lid on their head
- if(!close(user))
- to_chat(user, "You can't get [src] to close!")
- user.resting = FALSE
- return
- user.resting = FALSE
- togglelock(user)
- T1.visible_message("[user] dives into [src]!")
+/obj/structure/closet
+ name = "closet"
+ desc = "It's a basic storage unit."
+ icon = 'icons/obj/closet.dmi'
+ icon_state = "generic"
+ density = TRUE
+ layer = BELOW_OBJ_LAYER
+ var/icon_door = null
+ var/icon_door_override = FALSE //override to have open overlay use icon different to its base's
+ var/secure = FALSE //secure locker or not, also used if overriding a non-secure locker with a secure door overlay to add fancy lights
+ var/opened = FALSE
+ var/welded = FALSE
+ var/locked = FALSE
+ var/large = TRUE
+ var/wall_mounted = 0 //never solid (You can always pass over it)
+ max_integrity = 200
+ integrity_failure = 50
+ armor = list("melee" = 20, "bullet" = 10, "laser" = 10, "energy" = 0, "bomb" = 10, "bio" = 0, "rad" = 0, "fire" = 70, "acid" = 60)
+ var/breakout_time = 1200
+ var/message_cooldown
+ var/can_weld_shut = TRUE
+ var/horizontal = FALSE
+ var/allow_objects = FALSE
+ var/allow_dense = FALSE
+ var/dense_when_open = FALSE //if it's dense when open or not
+ var/max_mob_size = MOB_SIZE_HUMAN //Biggest mob_size accepted by the container
+ var/mob_storage_capacity = 3 // how many human sized mob/living can fit together inside a closet.
+ var/storage_capacity = 30 //This is so that someone can't pack hundreds of items in a locker/crate then open it in a populated area to crash clients.
+ var/cutting_tool = /obj/item/weldingtool
+ var/open_sound = 'sound/machines/click.ogg'
+ var/close_sound = 'sound/machines/click.ogg'
+ var/material_drop = /obj/item/stack/sheet/metal
+ var/material_drop_amount = 2
+ var/delivery_icon = "deliverycloset" //which icon to use when packagewrapped. null to be unwrappable.
+ var/anchorable = TRUE
+ var/icon_welded = "welded"
+ var/obj/item/electronics/airlock/lockerelectronics //Installed electronics
+ var/lock_in_use = FALSE //Someone is doing some stuff with the lock here, better not proceed further
+ var/eigen_teleport = FALSE //If the closet leads to Mr Tumnus.
+ var/obj/structure/closet/eigen_target //Where you go to.
+
+
+/obj/structure/closet/Initialize(mapload)
+ . = ..()
+ update_icon()
+ PopulateContents()
+ if(mapload && !opened) // if closed, any item at the crate's loc is put in the contents
+ take_contents()
+ if(secure)
+ lockerelectronics = new(src)
+ lockerelectronics.accesses = req_access
+
+//USE THIS TO FILL IT, NOT INITIALIZE OR NEW
+/obj/structure/closet/proc/PopulateContents()
+ return
+
+/obj/structure/closet/Destroy()
+ dump_contents(override = FALSE)
+ return ..()
+
+/obj/structure/closet/update_icon()
+ cut_overlays()
+ if(opened & icon_door_override)
+ add_overlay("[icon_door]_open")
+ layer = OBJ_LAYER
+ return
+ else if(opened)
+ add_overlay("[icon_state]_open")
+ return
+ if(icon_door)
+ add_overlay("[icon_door]_door")
+ else
+ layer = BELOW_OBJ_LAYER
+ add_overlay("[icon_state]_door")
+ if(welded)
+ add_overlay("welded")
+ if(!secure)
+ return
+ if(broken)
+ add_overlay("off")
+ add_overlay("sparking")
+ else if(locked)
+ add_overlay("locked")
+ else
+ add_overlay("unlocked")
+
+/obj/structure/closet/examine(mob/user)
+ ..()
+ if(welded)
+ to_chat(user, "It's welded shut.")
+ if(anchored)
+ to_chat(user, "It is bolted to the ground.")
+ if(opened)
+ to_chat(user, "The parts are welded together.")
+ else if(secure && !opened)
+ else if(broken)
+ to_chat(user, "The lock is screwed in.")
+ else if(secure)
+ to_chat(user, "Alt-click to [locked ? "unlock" : "lock"].")
+ if(isliving(user))
+ var/mob/living/L = user
+ if(HAS_TRAIT(L, TRAIT_SKITTISH))
+ to_chat(user, "Ctrl-Shift-click [src] to jump inside.")
+
+/obj/structure/closet/CanPass(atom/movable/mover, turf/target)
+ if(wall_mounted)
+ return TRUE
+ return !density
+
+/obj/structure/closet/proc/can_open(mob/living/user)
+ if(welded || locked)
+ return FALSE
+ var/turf/T = get_turf(src)
+ for(var/mob/living/L in T)
+ if(L.anchored || horizontal && L.mob_size > MOB_SIZE_TINY && L.density)
+ if(user)
+ to_chat(user, "There's something large on top of [src], preventing it from opening." )
+ return FALSE
+ return TRUE
+
+/obj/structure/closet/proc/can_close(mob/living/user)
+ var/turf/T = get_turf(src)
+ for(var/obj/structure/closet/closet in T)
+ if(closet != src && !closet.wall_mounted)
+ return FALSE
+ for(var/mob/living/L in T)
+ if(L.anchored || horizontal && L.mob_size > MOB_SIZE_TINY && L.density)
+ if(user)
+ to_chat(user, "There's something too large in [src], preventing it from closing.")
+ return FALSE
+ return TRUE
+
+/obj/structure/closet/proc/can_lock(mob/living/user, var/check_access = TRUE) //set check_access to FALSE if you only need to check if a locker has a functional lock rather than access
+ if(!secure)
+ return FALSE
+ if(broken)
+ to_chat(user, "[src] is broken!")
+ return FALSE
+ if(QDELETED(lockerelectronics) && !locked) //We want to be able to unlock it regardless of electronics, but only lockable with electronics
+ to_chat(user, "[src] is missing locker electronics!")
+ return FALSE
+ if(!check_access)
+ return TRUE
+ if(allowed(user))
+ return TRUE
+ to_chat(user, "Access denied.")
+
+/obj/structure/closet/proc/togglelock(mob/living/user)
+ add_fingerprint(user)
+ if(eigen_target)
+ return
+ if(opened)
+ return
+ if(!can_lock(user))
+ return
+ locked = !locked
+ user.visible_message("[user] [locked ? null : "un"]locks [src].",
+ "You [locked ? null : "un"]lock [src].")
+ update_icon()
+
+/obj/structure/closet/proc/dump_contents(var/override = TRUE) //Override is for not revealing the locker electronics when you open the locker, for example
+ var/atom/L = drop_location()
+ for(var/atom/movable/AM in src)
+ if(AM == lockerelectronics && override)
+ continue
+ AM.forceMove(L)
+ if(throwing) // you keep some momentum when getting out of a thrown closet
+ step(AM, dir)
+ if(throwing)
+ throwing.finalize(FALSE)
+
+/obj/structure/closet/proc/take_contents()
+ var/atom/L = drop_location()
+ for(var/atom/movable/AM in L)
+ if(AM != src && insert(AM) == -1) // limit reached
+ break
+
+/obj/structure/closet/proc/open(mob/living/user)
+ if(opened || !can_open(user))
+ return
+ playsound(loc, open_sound, 15, 1, -3)
+ opened = TRUE
+ if(!dense_when_open)
+ density = FALSE
+ climb_time *= 0.5 //it's faster to climb onto an open thing
+ dump_contents()
+ update_icon()
+ return 1
+
+/obj/structure/closet/proc/insert(atom/movable/AM)
+ if(contents.len >= storage_capacity)
+ return -1
+ if(insertion_allowed(AM))
+ if(eigen_teleport) // For teleporting people with linked lockers.
+ do_teleport(AM, get_turf(eigen_target), 0)
+ if(eigen_target.opened == FALSE)
+ eigen_target.bust_open()
+ else
+ AM.forceMove(src)
+ return TRUE
+ else
+ return FALSE
+
+
+/obj/structure/closet/proc/insertion_allowed(atom/movable/AM)
+ if(ismob(AM))
+ if(!isliving(AM)) //let's not put ghosts or camera mobs inside closets...
+ return FALSE
+ var/mob/living/L = AM
+ if(L.anchored || L.buckled || L.incorporeal_move || L.has_buckled_mobs())
+ return FALSE
+ if(L.mob_size > MOB_SIZE_TINY) // Tiny mobs are treated as items.
+ if(horizontal && L.density)
+ return FALSE
+ if(L.mob_size > max_mob_size)
+ return FALSE
+ var/mobs_stored = 0
+ for(var/mob/living/M in contents)
+ if(++mobs_stored >= mob_storage_capacity)
+ return FALSE
+ L.stop_pulling()
+
+ else if(istype(AM, /obj/structure/closet))
+ return FALSE
+
+ else if(istype(AM, /obj/effect))
+ return FALSE
+
+ else if(isobj(AM))
+ if((!allow_dense && AM.density) || AM.anchored || AM.has_buckled_mobs())
+ return FALSE
+ if(isitem(AM) && !HAS_TRAIT(AM, TRAIT_NODROP))
+ return TRUE
+ else if(!allow_objects && !istype(AM, /obj/effect/dummy/chameleon))
+ return FALSE
+ else
+ return FALSE
+
+ return TRUE
+
+/obj/structure/closet/proc/close(mob/living/user)
+ if(!opened || !can_close(user))
+ return FALSE
+ take_contents()
+ playsound(loc, close_sound, 15, 1, -3)
+ climb_time = initial(climb_time)
+ opened = FALSE
+ density = TRUE
+ update_icon()
+ return TRUE
+
+/obj/structure/closet/proc/toggle(mob/living/user)
+ if(opened)
+ return close(user)
+ else
+ return open(user)
+
+/obj/structure/closet/proc/bust_open()
+ welded = FALSE //applies to all lockers
+ locked = FALSE //applies to critter crates and secure lockers only
+ broken = TRUE //applies to secure lockers only
+ open()
+
+/obj/structure/closet/proc/handle_lock_addition(mob/user, obj/item/electronics/airlock/E)
+ add_fingerprint(user)
+ if(lock_in_use)
+ to_chat(user, "Wait for work on [src] to be done first!")
+ return
+ if(secure)
+ to_chat(user, "This locker already has a lock!")
+ return
+ if(broken)
+ to_chat(user, "Unscrew the broken lock first!")
+ return
+ if(!istype(E))
+ return
+ user.visible_message("[user] begins installing a lock on [src]...","You begin installing a lock on [src]...")
+ lock_in_use = TRUE
+ playsound(loc, 'sound/items/screwdriver.ogg', 50, 1)
+ if(!do_after(user, 60, target = src))
+ lock_in_use = FALSE
+ return
+ lock_in_use = FALSE
+ to_chat(user, "You finish the lock on [src]!")
+ E.forceMove(src)
+ lockerelectronics = E
+ req_access = E.accesses
+ secure = TRUE
+ update_icon()
+ return TRUE
+
+/obj/structure/closet/proc/handle_lock_removal(mob/user, obj/item/screwdriver/S)
+ if(lock_in_use)
+ to_chat(user, "Wait for work on [src] to be done first!")
+ return
+ if(locked)
+ to_chat(user, "Unlock it first!")
+ return
+ if(!secure)
+ to_chat(user, "[src] doesn't have a lock that you can remove!")
+ return
+ if(!istype(S))
+ return
+ var/brokenword = broken ? "broken " : null
+ user.visible_message("[user] begins removing the [brokenword]lock on [src]...","You begin removing the [brokenword]lock on [src]...")
+ playsound(loc, S.usesound, 50, 1)
+ lock_in_use = TRUE
+ if(!do_after(user, 100 * S.toolspeed, target = src))
+ lock_in_use = FALSE
+ return
+ to_chat(user, "You remove the [brokenword]lock from [src]!")
+ if(!QDELETED(lockerelectronics))
+ lockerelectronics.add_fingerprint(user)
+ lockerelectronics.forceMove(user.loc)
+ lockerelectronics = null
+ req_access = null
+ secure = FALSE
+ broken = FALSE
+ locked = FALSE
+ lock_in_use = FALSE
+ update_icon()
+ return TRUE
+
+
+/obj/structure/closet/deconstruct(disassembled = TRUE)
+ if(ispath(material_drop) && material_drop_amount && !(flags_1 & NODECONSTRUCT_1))
+ new material_drop(loc, material_drop_amount)
+ qdel(src)
+
+/obj/structure/closet/obj_break(damage_flag)
+ if(!broken && !(flags_1 & NODECONSTRUCT_1))
+ bust_open()
+
+/obj/structure/closet/attackby(obj/item/W, mob/user, params)
+ if(user in src)
+ return
+ if(src.tool_interact(W,user))
+ return 1 // No afterattack
+ else
+ return ..()
+
+/obj/structure/closet/proc/tool_interact(obj/item/W, mob/user)//returns TRUE if attackBy call shouldnt be continued (because tool was used/closet was of wrong type), FALSE if otherwise
+ . = TRUE
+ if(opened)
+ if(istype(W, cutting_tool))
+ if(istype(W, /obj/item/weldingtool))
+ if(!W.tool_start_check(user, amount=0))
+ return
+
+ to_chat(user, "You begin cutting \the [src] apart...")
+ if(W.use_tool(src, user, 40, volume=50))
+ if(eigen_teleport)
+ to_chat(user, "The unstable nature of \the [src] makes it impossible to cut!")
+ return
+ if(!opened)
+ return
+ user.visible_message("[user] slices apart \the [src].",
+ "You cut \the [src] apart with \the [W].",
+ "You hear welding.")
+ deconstruct(TRUE)
+ return
+ else // for example cardboard box is cut with wirecutters
+ user.visible_message("[user] cut apart \the [src].", \
+ "You cut \the [src] apart with \the [W].")
+ deconstruct(TRUE)
+ return
+ if(user.transferItemToLoc(W, drop_location())) // so we put in unlit welder too
+ return TRUE
+ else if(istype(W, /obj/item/electronics/airlock))
+ handle_lock_addition(user, W)
+ else if(istype(W, /obj/item/screwdriver))
+ handle_lock_removal(user, W)
+ else if(istype(W, /obj/item/weldingtool) && can_weld_shut)
+ if(!W.tool_start_check(user, amount=0))
+ return
+
+ to_chat(user, "You begin [welded ? "unwelding":"welding"] \the [src]...")
+ if(W.use_tool(src, user, 40, volume=50))
+ if(eigen_teleport)
+ to_chat(user, "The unstable nature of \the [src] makes it impossible to weld!")
+ return
+ if(opened)
+ return
+ welded = !welded
+ after_weld(welded)
+ user.visible_message("[user] [welded ? "welds shut" : "unwelds"] \the [src].",
+ "You [welded ? "weld" : "unwelded"] \the [src] with \the [W].",
+ "You hear welding.")
+ update_icon()
+ else if(istype(W, /obj/item/wrench) && anchorable)
+ if(isinspace() && !anchored)
+ return
+ setAnchored(!anchored)
+ W.play_tool_sound(src, 75)
+ user.visible_message("[user] [anchored ? "anchored" : "unanchored"] \the [src] [anchored ? "to" : "from"] the ground.", \
+ "You [anchored ? "anchored" : "unanchored"] \the [src] [anchored ? "to" : "from"] the ground.", \
+ "You hear a ratchet.")
+ else if(user.a_intent != INTENT_HARM && !(W.item_flags & NOBLUDGEON))
+ if(W.GetID() || !toggle(user))
+ togglelock(user)
+ else
+ return FALSE
+
+/obj/structure/closet/proc/after_weld(weld_state)
+ return
+
+/obj/structure/closet/MouseDrop_T(atom/movable/O, mob/living/user)
+ if(!istype(O) || O.anchored || istype(O, /obj/screen))
+ return
+ if(!istype(user) || user.incapacitated() || user.lying)
+ return
+ if(!Adjacent(user) || !user.Adjacent(O))
+ return
+ if(user == O) //try to climb onto it
+ return ..()
+ if(!opened)
+ return
+ if(!isturf(O.loc))
+ return
+
+ var/actuallyismob = 0
+ if(isliving(O))
+ actuallyismob = 1
+ else if(!isitem(O))
+ return
+ var/turf/T = get_turf(src)
+ var/list/targets = list(O, src)
+ add_fingerprint(user)
+ user.visible_message("[user] [actuallyismob ? "tries to ":""]stuff [O] into [src].", \
+ "You [actuallyismob ? "try to ":""]stuff [O] into [src].", \
+ "You hear clanging.")
+ if(actuallyismob)
+ if(do_after_mob(user, targets, 40))
+ user.visible_message("[user] stuffs [O] into [src].", \
+ "You stuff [O] into [src].", \
+ "You hear a loud metal bang.")
+ var/mob/living/L = O
+ if(!issilicon(L))
+ L.Knockdown(40)
+ O.forceMove(T)
+ close()
+ else
+ O.forceMove(T)
+ return 1
+
+/obj/structure/closet/relaymove(mob/user)
+ if(user.stat || !isturf(loc) || !isliving(user))
+ return
+ if(locked)
+ if(message_cooldown <= world.time)
+ message_cooldown = world.time + 50
+ to_chat(user, "[src]'s door won't budge!")
+ return
+ container_resist(user)
+
+/obj/structure/closet/attack_hand(mob/user)
+ . = ..()
+ if(.)
+ return
+ if(user.lying && get_dist(src, user) > 0)
+ return
+
+ if(!toggle(user))
+ togglelock(user)
+
+/obj/structure/closet/attack_paw(mob/user)
+ return attack_hand(user)
+
+/obj/structure/closet/attack_robot(mob/user)
+ if(user.Adjacent(src))
+ return attack_hand(user)
+
+// tk grab then use on self
+/obj/structure/closet/attack_self_tk(mob/user)
+ return attack_hand(user)
+
+/obj/structure/closet/verb/verb_toggleopen()
+ set src in oview(1)
+ set category = "Object"
+ set name = "Toggle Open"
+
+ if(!usr.canmove || usr.stat || usr.restrained())
+ return
+
+ if(iscarbon(usr) || issilicon(usr) || isdrone(usr))
+ return attack_hand(usr)
+ else
+ to_chat(usr, "This mob type can't use this verb.")
+
+// Objects that try to exit a locker by stepping were doing so successfully,
+// and due to an oversight in turf/Enter() were going through walls. That
+// should be independently resolved, but this is also an interesting twist.
+/obj/structure/closet/Exit(atom/movable/AM)
+ open()
+ if(AM.loc == src)
+ return 0
+ return 1
+
+/obj/structure/closet/container_resist(mob/living/user)
+ if(opened)
+ return
+ if(ismovableatom(loc))
+ user.changeNext_move(CLICK_CD_BREAKOUT)
+ user.last_special = world.time + CLICK_CD_BREAKOUT
+ var/atom/movable/AM = loc
+ AM.relay_container_resist(user, src)
+ return
+ if(!welded && !locked)
+ open()
+ return
+
+ //okay, so the closet is either welded or locked... resist!!!
+ user.changeNext_move(CLICK_CD_BREAKOUT)
+ user.last_special = world.time + CLICK_CD_BREAKOUT
+ user.visible_message("[src] begins to shake violently!", \
+ "You lean on the back of [src] and start pushing the door open... (this will take about [DisplayTimeText(breakout_time)].)", \
+ "You hear banging from [src].")
+ if(do_after(user,(breakout_time), target = src))
+ if(!user || user.stat != CONSCIOUS || user.loc != src || opened || (!locked && !welded) )
+ return
+ //we check after a while whether there is a point of resisting anymore and whether the user is capable of resisting
+ user.visible_message("[user] successfully broke out of [src]!",
+ "You successfully break out of [src]!")
+ bust_open()
+ else
+ if(user.loc == src) //so we don't get the message if we resisted multiple times and succeeded.
+ to_chat(user, "You fail to break out of [src]!")
+
+/obj/structure/closet/AltClick(mob/user)
+ ..()
+ if(!user.canUseTopic(src, be_close=TRUE) || !isturf(loc))
+ to_chat(user, "You can't do that right now!")
+ return
+ togglelock(user)
+
+/obj/structure/closet/CtrlShiftClick(mob/living/user)
+ if(!HAS_TRAIT(user, TRAIT_SKITTISH))
+ return ..()
+ if(!user.canUseTopic(src) || !isturf(user.loc))
+ return
+ dive_into(user)
+
+/obj/structure/closet/emag_act(mob/user)
+ if(secure && !broken)
+ user.visible_message("Sparks fly from [src]!",
+ "You scramble [src]'s lock, breaking it open!",
+ "You hear a faint electrical spark.")
+ playsound(src, "sparks", 50, 1)
+ broken = TRUE
+ locked = FALSE
+ if(!QDELETED(lockerelectronics))
+ qdel(lockerelectronics)
+ lockerelectronics = null
+ update_icon()
+
+/obj/structure/closet/get_remote_view_fullscreens(mob/user)
+ if(user.stat == DEAD || !(user.sight & (SEEOBJS|SEEMOBS)))
+ user.overlay_fullscreen("remote_view", /obj/screen/fullscreen/impaired, 1)
+
+/obj/structure/closet/emp_act(severity)
+ . = ..()
+ if(. & EMP_PROTECT_SELF)
+ return
+ if (!(. & EMP_PROTECT_CONTENTS))
+ for(var/obj/O in src)
+ O.emp_act(severity)
+ if(!secure || broken)
+ return ..()
+ if(prob(50 / severity))
+ locked = !locked
+ update_icon()
+ if(prob(20 / severity) && !opened)
+ if(!locked)
+ open()
+ else
+ req_access = list()
+ req_access += pick(get_all_accesses())
+ if(!QDELETED(lockerelectronics))
+ lockerelectronics.accesses = req_access
+
+/obj/structure/closet/contents_explosion(severity, target)
+ for(var/atom/A in contents)
+ A.ex_act(severity, target)
+ CHECK_TICK
+
+/obj/structure/closet/singularity_act()
+ dump_contents()
+ ..()
+
+/obj/structure/closet/AllowDrop()
+ return TRUE
+
+
+/obj/structure/closet/return_temperature()
+ return
+
+/obj/structure/closet/proc/dive_into(mob/living/user)
+ var/turf/T1 = get_turf(user)
+ var/turf/T2 = get_turf(src)
+ if(!opened)
+ if(locked)
+ togglelock(user, TRUE)
+ if(!open(user))
+ to_chat(user, "It won't budge!")
+ return
+ step_towards(user, T2)
+ T1 = get_turf(user)
+ if(T1 == T2)
+ user.resting = TRUE //so people can jump into crates without slamming the lid on their head
+ if(!close(user))
+ to_chat(user, "You can't get [src] to close!")
+ user.resting = FALSE
+ return
+ user.resting = FALSE
+ togglelock(user)
+ T1.visible_message("[user] dives into [src]!")
diff --git a/code/game/objects/structures/dresser.dm b/code/game/objects/structures/dresser.dm
index 8dc560b936..cdca354563 100644
--- a/code/game/objects/structures/dresser.dm
+++ b/code/game/objects/structures/dresser.dm
@@ -79,4 +79,4 @@
var/n_color = input(H, "Choose your [garment_type]'\s color.", "Character Preference", default_color) as color|null
if(!n_color || !H.canUseTopic(src, BE_CLOSE, FALSE, NO_TK))
return default_color
- return sanitize_hexcolor(n_color, include_crunch= TRUE)
+ return sanitize_hexcolor(n_color)
diff --git a/code/modules/antagonists/clockcult/clock_structures/ark_of_the_clockwork_justicar.dm b/code/modules/antagonists/clockcult/clock_structures/ark_of_the_clockwork_justicar.dm
index 4cf7c1ba60..56b36d13e6 100644
--- a/code/modules/antagonists/clockcult/clock_structures/ark_of_the_clockwork_justicar.dm
+++ b/code/modules/antagonists/clockcult/clock_structures/ark_of_the_clockwork_justicar.dm
@@ -131,9 +131,7 @@
if(!M || !M.current)
continue
if(isliving(M.current) && M.current.stat != DEAD)
- var/turf/t_turf
- if(isAI(M.current))
- t_turf = isAI(M.current) ? get_step(get_step(src, NORTH),NORTH) : get_turf(src) // AI too fat, must make sure it always ends up a 2 tiles north instead of on the ark.
+ var/turf/t_turf = isAI(M.current) ? get_step(get_step(src, NORTH),NORTH) : get_turf(src) // AI too fat, must make sure it always ends up a 2 tiles north instead of on the ark.
do_teleport(M, t_turf, channel = TELEPORT_CHANNEL_CULT, forced = TRUE)
M.current.overlay_fullscreen("flash", /obj/screen/fullscreen/flash)
M.current.clear_fullscreen("flash", 5)
diff --git a/code/modules/antagonists/cult/blood_magic.dm b/code/modules/antagonists/cult/blood_magic.dm
index f58ffc56d2..1dbae4ca98 100644
--- a/code/modules/antagonists/cult/blood_magic.dm
+++ b/code/modules/antagonists/cult/blood_magic.dm
@@ -655,15 +655,15 @@
if(H.stat == DEAD)
to_chat(user,"Only a revive rune can bring back the dead!")
return
- if(H.blood_volume < BLOOD_VOLUME_SAFE)
- var/restore_blood = BLOOD_VOLUME_SAFE - H.blood_volume
+ if(H.blood_volume < (BLOOD_VOLUME_SAFE*H.blood_ratio))
+ var/restore_blood = (BLOOD_VOLUME_SAFE*H.blood_ratio) - H.blood_volume
if(uses*2 < restore_blood)
H.blood_volume += uses*2
to_chat(user,"You use the last of your blood rites to restore what blood you could!")
uses = 0
return ..()
else
- H.blood_volume = BLOOD_VOLUME_SAFE
+ H.blood_volume = (BLOOD_VOLUME_SAFE*H.blood_ratio)
uses -= round(restore_blood/2)
to_chat(user,"Your blood rites have restored [H == user ? "your" : "[H.p_their()]"] blood to safe levels!")
var/overall_damage = H.getBruteLoss() + H.getFireLoss() + H.getToxLoss() + H.getOxyLoss()
@@ -698,7 +698,7 @@
if(H.cultslurring)
to_chat(user,"[H.p_their(TRUE)] blood has been tainted by an even stronger form of blood magic, it's no use to us like this!")
return
- if(H.blood_volume > BLOOD_VOLUME_SAFE)
+ if(H.blood_volume > (BLOOD_VOLUME_SAFE*H.blood_ratio))
H.blood_volume -= 100
uses += 50
user.Beam(H,icon_state="drainbeam",time=10)
@@ -799,4 +799,4 @@
to_chat(user, "Your hands glow with POWER OVERWHELMING!!!")
else
to_chat(user, "You need a free hand for this rite!")
- qdel(rite)
\ No newline at end of file
+ qdel(rite)
diff --git a/code/modules/antagonists/cult/cult_structures.dm b/code/modules/antagonists/cult/cult_structures.dm
index 64d57c2f94..499d7a861e 100644
--- a/code/modules/antagonists/cult/cult_structures.dm
+++ b/code/modules/antagonists/cult/cult_structures.dm
@@ -188,7 +188,7 @@
var/mob/living/simple_animal/M = L
if(M.health < M.maxHealth)
M.adjustHealth(-3)
- if(ishuman(L) && L.blood_volume < BLOOD_VOLUME_NORMAL)
+ if(ishuman(L) && L.blood_volume < (BLOOD_VOLUME_NORMAL * L.blood_ratio))
L.blood_volume += 1.0
CHECK_TICK
if(last_corrupt <= world.time)
diff --git a/code/modules/antagonists/valentines/valentine.dm b/code/modules/antagonists/valentines/valentine.dm
index 31e69b32c2..21e54374a4 100644
--- a/code/modules/antagonists/valentines/valentine.dm
+++ b/code/modules/antagonists/valentines/valentine.dm
@@ -27,6 +27,7 @@
var/mob/living/L = owner
L.remove_status_effect(STATUS_EFFECT_INLOVE)
+
/datum/antagonist/valentine/greet()
to_chat(owner, "You're on a date with [date.name]! Protect [date.p_them()] at all costs. This takes priority over all other loyalties.")
@@ -42,4 +43,21 @@
if(objectives_complete)
return "[owner.name] protected [owner.p_their()] date"
else
- return "[owner.name] date failed!"
\ No newline at end of file
+ return "[owner.name] date failed!"
+
+//Just so it's distinct, basically.
+/datum/antagonist/valentine/chem/greet()
+ to_chat(owner, "You're in love with [date.name]! Protect [date.p_them()] at all costs. This takes priority over all other loyalties.")
+
+/datum/antagonist/valentine/chem/roundend_report()
+ var/objectives_complete = TRUE
+ if(owner.objectives.len)
+ for(var/datum/objective/objective in owner.objectives)
+ if(!objective.check_completion())
+ objectives_complete = FALSE
+ break
+
+ if(objectives_complete)
+ return "[owner.name] protected [owner.p_their()] love: [date.name]! What a cutie!"
+ else
+ return "[owner.name] date failed!"
diff --git a/code/modules/assembly/flash.dm b/code/modules/assembly/flash.dm
index b1aa63c242..186eb1b024 100644
--- a/code/modules/assembly/flash.dm
+++ b/code/modules/assembly/flash.dm
@@ -12,6 +12,7 @@
crit_fail = FALSE //Is the flash burnt out?
light_color = LIGHT_COLOR_WHITE
light_power = FLASH_LIGHT_POWER
+ var/flashing_overlay = "flash-f"
var/times_used = 0 //Number of times it's been used.
var/burnout_resistance = 0
var/last_used = 0 //last world.time it was used.
@@ -36,8 +37,8 @@
add_overlay("flashburnt")
attached_overlays += "flashburnt"
if(flash)
- add_overlay("flash-f")
- attached_overlays += "flash-f"
+ add_overlay(flashing_overlay)
+ attached_overlays += flashing_overlay
addtimer(CALLBACK(src, .proc/update_icon), 5)
if(holder)
holder.update_icon()
@@ -313,3 +314,50 @@
/obj/item/assembly/flash/shield/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK)
activate()
return ..()
+
+//ported from tg - check to make sure it can't appear where it's not supposed to.
+/obj/item/assembly/flash/hypnotic
+ desc = "A modified flash device, programmed to emit a sequence of subliminal flashes that can send a vulnerable target into a hypnotic trance."
+ flashing_overlay = "flash-hypno" //I cannot find this icon no matter how hard I look in tg, so I might just make my own.
+ light_color = LIGHT_COLOR_PINK
+ cooldown = 20
+
+/obj/item/assembly/flash/hypnotic/burn_out()
+ return
+
+/obj/item/assembly/flash/hypnotic/flash_carbon(mob/living/carbon/M, mob/user, power = 15, targeted = TRUE, generic_message = FALSE)
+ if(!istype(M))
+ return
+ if(user)
+ log_combat(user, M, "[targeted? "hypno-flashed(targeted)" : "hypno-flashed(AOE)"]", src)
+ else //caused by emp/remote signal
+ M.log_message("was [targeted? "hypno-flashed(targeted)" : "hypno-flashed(AOE)"]",LOG_ATTACK)
+ if(generic_message && M != user)
+ to_chat(M, "[src] emits a soothing light...")
+ if(targeted)
+ if(M.flash_act(1, 1))
+ var/hypnosis = FALSE
+ if(M.hypnosis_vulnerable())
+ hypnosis = TRUE
+ if(user)
+ user.visible_message("[user] blinds [M] with the flash!", "You hypno-flash [M]!")
+
+ if(!hypnosis)
+ to_chat(M, "The light makes you feel oddly relaxed...")
+ M.confused += min(M.confused + 10, 20)
+ M.dizziness += min(M.dizziness + 10, 20)
+ M.drowsyness += min(M.drowsyness + 10, 20)
+ M.apply_status_effect(STATUS_EFFECT_PACIFY, 100)
+ else
+ M.apply_status_effect(/datum/status_effect/trance, 200, TRUE)
+
+ else if(user)
+ user.visible_message("[user] fails to blind [M] with the flash!", "You fail to hypno-flash [M]!")
+ else
+ to_chat(M, "[src] fails to blind you!")
+
+ else if(M.flash_act())
+ to_chat(M, "Such a pretty light...")
+ M.confused += min(M.confused + 4, 20)
+ M.dizziness += min(M.dizziness + 4, 20)
+ M.drowsyness += min(M.drowsyness + 4, 20)
diff --git a/code/modules/client/preferences.dm b/code/modules/client/preferences.dm
index cd22685952..4cf7c0178b 100644
--- a/code/modules/client/preferences.dm
+++ b/code/modules/client/preferences.dm
@@ -72,11 +72,11 @@ GLOBAL_LIST_EMPTY(preferences_datums)
var/gender = MALE //gender of character (well duh)
var/age = 30 //age of character
var/underwear = "Nude" //underwear type
- var/undie_color = "#FFFFFF"
+ var/undie_color = "FFF"
var/undershirt = "Nude" //undershirt type
- var/shirt_color = "#FFFFFF"
+ var/shirt_color = "FFF"
var/socks = "Nude" //socks type
- var/socks_color = "#FFFFFF"
+ var/socks_color = "FFF"
var/backbag = DBACKPACK //backpack type
var/jumpsuit_style = PREF_SUIT //suit/skirt
var/hair_style = "Bald" //Hair type
@@ -796,6 +796,7 @@ GLOBAL_LIST_EMPTY(preferences_datums)
dat += "Voracious MediHound sleepers: [(cit_toggles & MEDIHOUND_SLEEPER) ? "Yes" : "No"]
"
dat += "Hear Vore Sounds: [(cit_toggles & EATING_NOISES) ? "Yes" : "No"]
"
dat += "Hear Vore Digestion Sounds: [(cit_toggles & DIGESTION_NOISES) ? "Yes" : "No"]
"
+ dat += "Lewdchem:[lewdchem == TRUE ? "Enabled" : "Disabled"]
"
dat += "Widescreen: [widescreenpref ? "Enabled ([CONFIG_GET(string/default_view)])" : "Disabled (15x15)"]
"
dat += "Auto stand: [autostand ? "Enabled" : "Disabled"]
"
dat += "Screen Shake: [(screenshake==100) ? "Full" : ((screenshake==0) ? "None" : "[screenshake]")]
"
@@ -1533,7 +1534,7 @@ GLOBAL_LIST_EMPTY(preferences_datums)
if("undie_color")
var/n_undie_color = input(user, "Choose your underwear's color.", "Character Preference", undie_color) as color|null
if(n_undie_color)
- undie_color = sanitize_hexcolor(n_undie_color, include_crunch= TRUE)
+ undie_color = sanitize_hexcolor(n_undie_color)
if("undershirt")
var/new_undershirt = input(user, "Choose your character's undershirt:", "Character Preference") as null|anything in GLOB.undershirt_list
@@ -1543,7 +1544,7 @@ GLOBAL_LIST_EMPTY(preferences_datums)
if("shirt_color")
var/n_shirt_color = input(user, "Choose your undershirt's color.", "Character Preference", shirt_color) as color|null
if(n_shirt_color)
- shirt_color = sanitize_hexcolor(n_shirt_color, include_crunch= TRUE)
+ shirt_color = sanitize_hexcolor(n_shirt_color)
if("socks")
var/new_socks = input(user, "Choose your character's socks:", "Character Preference") as null|anything in GLOB.socks_list
@@ -1553,7 +1554,7 @@ GLOBAL_LIST_EMPTY(preferences_datums)
if("socks_color")
var/n_socks_color = input(user, "Choose your socks' color.", "Character Preference", socks_color) as color|null
if(n_socks_color)
- socks_color = sanitize_hexcolor(n_socks_color, include_crunch= TRUE)
+ socks_color = sanitize_hexcolor(n_socks_color)
if("eyes")
var/new_eyes = input(user, "Choose your character's eye colour:", "Character Preference","#"+eye_color) as color|null
@@ -2037,6 +2038,8 @@ GLOBAL_LIST_EMPTY(preferences_datums)
features["genitals_use_skintone"] = !features["genitals_use_skintone"]
if("arousable")
arousable = !arousable
+ if("lewdchem")
+ lewdchem = !lewdchem
if("has_cock")
features["has_cock"] = !features["has_cock"]
if(features["has_cock"] == FALSE)
diff --git a/code/modules/client/preferences_savefile.dm b/code/modules/client/preferences_savefile.dm
index 4ce91ffd94..0f0d887c83 100644
--- a/code/modules/client/preferences_savefile.dm
+++ b/code/modules/client/preferences_savefile.dm
@@ -117,6 +117,7 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
S["widescreenpref"] >> widescreenpref
S["autostand"] >> autostand
S["cit_toggles"] >> cit_toggles
+ S["lewdchem"] >> lewdchem
//try to fix any outdated data if necessary
if(needs_update >= 0)
@@ -211,6 +212,7 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
WRITE_FILE(S["widescreenpref"], widescreenpref)
WRITE_FILE(S["autostand"], autostand)
WRITE_FILE(S["cit_toggles"], cit_toggles)
+ WRITE_FILE(S["lewdchem"], lewdchem)
return 1
diff --git a/code/modules/clothing/gloves/miscellaneous.dm b/code/modules/clothing/gloves/miscellaneous.dm
index bc36353ac5..1af24f8e61 100644
--- a/code/modules/clothing/gloves/miscellaneous.dm
+++ b/code/modules/clothing/gloves/miscellaneous.dm
@@ -72,9 +72,30 @@
M.adjustStaminaLoss(-2) //Restore 2/3 of the stamina used assuming empty stam buffer. With proper stamina buffer management, this results in a net gain of +.5 stamina per click.
if(warcry)
M.say("[warcry]", ignore_spam = TRUE, forced = "north star warcry")
+
.= FALSE
+
/obj/item/clothing/gloves/rapid/attack_self(mob/user)
var/input = stripped_input(user,"What do you want your battlecry to be? Max length of 6 characters.", ,"", 7)
if(input)
warcry = input
+
+/obj/item/clothing/gloves/rapid/hug
+ name = "Hugs of the North Star"
+ desc = "Just looking at these fills you with an urge to hug the shit out of people"
+ warcry = "owo" //Shouldn't ever come into play
+
+/obj/item/clothing/gloves/rapid/hug/Touch(mob/living/target,proximity = TRUE)
+ var/mob/living/M = loc
+
+ if(M.a_intent == INTENT_HELP)
+ if(target.health >= 0 && !HAS_TRAIT(target, TRAIT_FAKEDEATH)) //Can't hug people who are dying/dead
+ if(target.on_fire || target.lying ) //No spamming extinguishing, helping them up, or other non-hugging/patting help interactions
+ return
+ else
+ M.changeNext_move(CLICK_CD_RAPID)
+ . = FALSE
+
+/obj/item/clothing/gloves/rapid/hug/attack_self(mob/user)
+ return FALSE
\ No newline at end of file
diff --git a/code/modules/events/holiday/vday.dm b/code/modules/events/holiday/vday.dm
index a4e8b276b5..8f77af58eb 100644
--- a/code/modules/events/holiday/vday.dm
+++ b/code/modules/events/holiday/vday.dm
@@ -41,11 +41,16 @@
else
L.mind.add_antag_datum(/datum/antagonist/heartbreaker)
-/proc/forge_valentines_objective(mob/living/lover,mob/living/date)
+/proc/forge_valentines_objective(mob/living/lover,mob/living/date,var/chemLove = FALSE)
lover.mind.special_role = "valentine"
- var/datum/antagonist/valentine/V = new
- V.date = date.mind
- lover.mind.add_antag_datum(V) //These really should be teams but i can't be assed to incorporate third wheels right now
+ if (chemLove == TRUE)
+ var/datum/antagonist/valentine/chem/V = new //Changes text and EOG check basically.
+ V.date = date.mind
+ lover.mind.add_antag_datum(V)
+ else
+ var/datum/antagonist/valentine/V = new
+ V.date = date.mind
+ lover.mind.add_antag_datum(V) //These really should be teams but i can't be assed to incorporate third wheels right now
/datum/round_event/valentines/announce(fake)
priority_announce("It's Valentine's Day! Give a valentine to that special someone!")
diff --git a/code/modules/events/meteor_wave.dm b/code/modules/events/meteor_wave.dm
index 798bcf82dd..af22ae3b96 100644
--- a/code/modules/events/meteor_wave.dm
+++ b/code/modules/events/meteor_wave.dm
@@ -15,6 +15,12 @@
var/list/wave_type
var/wave_name = "normal"
+/datum/round_event/meteor_wave/setup()
+ announceWhen = 1
+ startWhen = rand(300, 600) //Yeah for SOME REASON this is measured in seconds and not deciseconds???
+ endWhen = startWhen + 60
+
+
/datum/round_event/meteor_wave/New()
..()
if(!wave_type)
@@ -46,7 +52,7 @@
kill()
/datum/round_event/meteor_wave/announce(fake)
- priority_announce("Meteors have been detected on collision course with the station.", "Meteor Alert", 'sound/ai/meteors.ogg')
+ priority_announce("Meteors have been detected on collision course with the station. Estimated time until impact: [round(startWhen/60)] minutes.", "Meteor Alert", 'sound/ai/meteors.ogg')
/datum/round_event/meteor_wave/tick()
if(ISMULTIPLE(activeFor, 3))
diff --git a/code/modules/food_and_drinks/food/snacks_other.dm b/code/modules/food_and_drinks/food/snacks_other.dm
index e36efe750b..c4b9451c7f 100644
--- a/code/modules/food_and_drinks/food/snacks_other.dm
+++ b/code/modules/food_and_drinks/food/snacks_other.dm
@@ -573,4 +573,14 @@
name = "Maintenance Peaches"
desc = "I have a mouth and I must eat."
icon_state = "peachcanmaint"
- tastes = list("peaches" = 1, "tin" = 7)
\ No newline at end of file
+ tastes = list("peaches" = 1, "tin" = 7)
+
+/obj/item/reagent_containers/food/snacks/chocolatestrawberry
+ name = "Chocolate dipped strawberries"
+ desc = "A strawberry dipped in a bit of chocolate."
+ icon_state = "chocolatestrawberry"
+ list_reagents = list("sugar" = 5, "nutriment" = 2)
+ filling_color = "#ffdf26"
+ w_class = WEIGHT_CLASS_NORMAL
+ tastes = list("strawberries" = 5, "chocolate" = 3)
+ foodtype = FRUIT | SUGAR
\ No newline at end of file
diff --git a/code/modules/food_and_drinks/food/snacks_pastry.dm b/code/modules/food_and_drinks/food/snacks_pastry.dm
index 238bb4f86a..9b87002738 100644
--- a/code/modules/food_and_drinks/food/snacks_pastry.dm
+++ b/code/modules/food_and_drinks/food/snacks_pastry.dm
@@ -308,7 +308,6 @@
tastes = list("bread" = 1, "egg" = 1, "cheese" = 1)
foodtype = GRAIN | MEAT | DAIRY
-
/obj/item/reagent_containers/food/snacks/sugarcookie
name = "sugar cookie"
desc = "Just like your little sister used to make."
@@ -369,6 +368,16 @@
tastes = list("cake" = 3, "blue cherry" = 1)
foodtype = GRAIN | FRUIT | SUGAR
+/obj/item/reagent_containers/food/snacks/strawberrycupcake
+ name = "Strawberry cupcake"
+ desc = "Strawberry inside a delicious cupcake."
+ icon_state = "strawberrycupcake"
+ bonus_reagents = list("nutriment" = 1, "vitamin" = 3)
+ list_reagents = list("nutriment" = 5, "vitamin" = 1)
+ filling_color = "#F0E68C"
+ tastes = list("cake" = 2, "strawberry" = 1)
+ foodtype = GRAIN | FRUIT | SUGAR
+
/obj/item/reagent_containers/food/snacks/honeybun
name = "honey bun"
desc = "A sticky pastry bun glazed with honey."
diff --git a/code/modules/food_and_drinks/food/snacks_pie.dm b/code/modules/food_and_drinks/food/snacks_pie.dm
index 20313fd1b7..be6c11fd68 100644
--- a/code/modules/food_and_drinks/food/snacks_pie.dm
+++ b/code/modules/food_and_drinks/food/snacks_pie.dm
@@ -315,4 +315,12 @@
filling_color = "#1E90FF"
list_reagents = list("nutriment" = 2, "vitamin" = 4)
tastes = list("nuts" = 1, "pie" = 1)
- foodtype = GRAIN
\ No newline at end of file
+ foodtype = GRAIN
+
+/obj/item/reagent_containers/food/snacks/pie/strawberrypie
+ name = "strawberry pie"
+ desc = "A strawberry.pie."
+ icon_state = "strawberrypie"
+ bonus_reagents = list("nutriment" = 6, "vitamin" = 6)
+ tastes = list("strawberry" = 1, "pie" = 1)
+ foodtype = GRAIN | FRUIT | SUGAR
diff --git a/code/modules/food_and_drinks/food/snacks_vend.dm b/code/modules/food_and_drinks/food/snacks_vend.dm
index 317a434040..8f661b868e 100644
--- a/code/modules/food_and_drinks/food/snacks_vend.dm
+++ b/code/modules/food_and_drinks/food/snacks_vend.dm
@@ -7,7 +7,7 @@
desc = "Nougat love it or hate it."
icon_state = "candy"
trash = /obj/item/trash/candy
- list_reagents = list("nutriment" = 1, "sugar" = 3)
+ list_reagents = list("nutriment" = 1, "sugar" = 3, "cocoa" = 3)
junkiness = 25
filling_color = "#D2691E"
tastes = list("candy" = 1)
diff --git a/code/modules/food_and_drinks/recipes/tablecraft/recipes_misc.dm b/code/modules/food_and_drinks/recipes/tablecraft/recipes_misc.dm
index ce33cbef77..71ba5dfe41 100644
--- a/code/modules/food_and_drinks/recipes/tablecraft/recipes_misc.dm
+++ b/code/modules/food_and_drinks/recipes/tablecraft/recipes_misc.dm
@@ -332,3 +332,12 @@
)
result = /obj/item/reagent_containers/food/snacks/riceball
subcategory = CAT_MISCFOOD
+
+/datum/crafting_recipe/food/chocolatestrawberry
+ name = "Chocolate Strawberry"
+ reqs = list(
+ /obj/item/reagent_containers/food/snacks/chocolatebar = 1,
+ /obj/item/reagent_containers/food/snacks/grown/strawberries = 1
+ )
+ result = /obj/item/reagent_containers/food/snacks/chocolatestrawberry
+ subcategory = CAT_MISCFOOD
\ No newline at end of file
diff --git a/code/modules/food_and_drinks/recipes/tablecraft/recipes_pastry.dm b/code/modules/food_and_drinks/recipes/tablecraft/recipes_pastry.dm
index 2246d12df4..f3675ccb0d 100644
--- a/code/modules/food_and_drinks/recipes/tablecraft/recipes_pastry.dm
+++ b/code/modules/food_and_drinks/recipes/tablecraft/recipes_pastry.dm
@@ -325,6 +325,15 @@ datum/crafting_recipe/food/donut/meat
result = /obj/item/reagent_containers/food/snacks/bluecherrycupcake
subcategory = CAT_PASTRY
+/datum/crafting_recipe/food/strawberrycupcake
+ name = "Strawberry cherry cupcake"
+ reqs = list(
+ /obj/item/reagent_containers/food/snacks/pastrybase = 1,
+ /obj/item/reagent_containers/food/snacks/grown/strawberries = 1
+ )
+ result = /obj/item/reagent_containers/food/snacks/strawberrycupcake
+ subcategory = CAT_PASTRY
+
/datum/crafting_recipe/food/honeybun
name = "Honey bun"
reqs = list(
diff --git a/code/modules/food_and_drinks/recipes/tablecraft/recipes_pie.dm b/code/modules/food_and_drinks/recipes/tablecraft/recipes_pie.dm
index 39eb4ce05e..a0a225c44f 100644
--- a/code/modules/food_and_drinks/recipes/tablecraft/recipes_pie.dm
+++ b/code/modules/food_and_drinks/recipes/tablecraft/recipes_pie.dm
@@ -160,6 +160,15 @@
result = /obj/item/reagent_containers/food/snacks/pie/frostypie
subcategory = CAT_PIE
+/datum/crafting_recipe/food/strawberrypie
+ name = "Strawberry pie"
+ reqs = list(
+ /obj/item/reagent_containers/food/snacks/pie/plain = 1,
+ /obj/item/reagent_containers/food/snacks/grown/strawberries = 1
+ )
+ result = /obj/item/reagent_containers/food/snacks/pie/strawberrypie
+ subcategory = CAT_PIE
+
/datum/crafting_recipe/food/baklava
name = "Baklava pie"
reqs = list(
diff --git a/code/modules/goonchat/browserassets/css/browserOutput.css b/code/modules/goonchat/browserassets/css/browserOutput.css
index b6db020df0..7b942494f4 100644
--- a/code/modules/goonchat/browserassets/css/browserOutput.css
+++ b/code/modules/goonchat/browserassets/css/browserOutput.css
@@ -404,6 +404,31 @@ h1.alert, h2.alert {color: #99aab5;}
.redtext {color: #c51e1e; font-size: 24px;}
.clown {color: #ff70c1; font-size: 24px; font-family: "Comic Sans MS", cursive, sans-serif; font-weight: bold;}
.his_grace {color: #15D512; font-family: "Courier New", cursive, sans-serif; font-style: italic;}
+.velvet {color: #660015; font-weight: bold; animation: velvet 5000ms infinite;}
+@keyframes velvet {
+ 0% { color: #400020; }
+ 40% { color: #FF0000; }
+ 50% { color: #FF8888; }
+ 60% { color: #FF0000; }
+ 100% { color: #400020; }
+}
+
+.hypnophrase {color: #202020; font-weight: bold; animation: hypnocolor 1500ms infinite;}
+@keyframes hypnocolor {
+ 0% { color: #202020; }
+ 25% { color: #4b02ac; }
+ 50% { color: #9f41f1; }
+ 75% { color: #541c9c; }
+ 100% { color: #7adbf3; }
+}
+
+.phobia {color: #dd0000; font-weight: bold; animation: phobia 750ms infinite;}
+@keyframes phobia {
+ 0% { color: #f75a5a; }
+ 50% { color: #dd0000; }
+ 100% { color: #f75a5a; }
+}
+
.icon {height: 1em; width: auto;}
diff --git a/code/modules/hydroponics/grown/berries.dm b/code/modules/hydroponics/grown/berries.dm
index 19abdacf3a..f106a0dfe0 100644
--- a/code/modules/hydroponics/grown/berries.dm
+++ b/code/modules/hydroponics/grown/berries.dm
@@ -215,3 +215,22 @@
filling_color = "#7FFF00"
tastes = list("green grape" = 1)
distill_reagent = "cognac"
+
+// Strawberry
+/obj/item/seeds/strawberries
+ name = "pack of green grape seeds"
+ desc = "These seeds grow into strawberries vines."
+ icon_state = "seed-strawberry"
+ species = "strawberry"
+ plantname = "Strawberry Vine"
+ product = /obj/item/reagent_containers/food/snacks/grown/strawberries
+ reagents_add = list("vitamin" = 0.07, "nutriment" = 0.1, "sugar" = 0.1)
+ mutatelist = list()
+
+/obj/item/reagent_containers/food/snacks/grown/strawberries
+ seed = /obj/item/seeds/strawberries
+ name = "strawberry"
+ icon_state = "strawberries"
+ filling_color = "#7FFF00"
+ tastes = list("strawberries" = 1)
+ wine_power = 20
\ No newline at end of file
diff --git a/code/modules/jobs/job_types/medical.dm b/code/modules/jobs/job_types/medical.dm
index 1a6d2aa2f6..48cc854c5a 100644
--- a/code/modules/jobs/job_types/medical.dm
+++ b/code/modules/jobs/job_types/medical.dm
@@ -127,6 +127,7 @@ Chemist
backpack = /obj/item/storage/backpack/chemistry
satchel = /obj/item/storage/backpack/satchel/chem
duffelbag = /obj/item/storage/backpack/duffelbag/med
+ l_hand = /obj/item/fermichem/pHbooklet
chameleon_extras = /obj/item/gun/syringe
diff --git a/code/modules/mob/living/blood.dm b/code/modules/mob/living/blood.dm
index 25c91cc9ca..9191f627e5 100644
--- a/code/modules/mob/living/blood.dm
+++ b/code/modules/mob/living/blood.dm
@@ -18,10 +18,10 @@
/mob/living/carbon/monkey/handle_blood()
if(bodytemperature >= TCRYO && !(HAS_TRAIT(src, TRAIT_NOCLONE))) //cryosleep or husked people do not pump the blood.
//Blood regeneration if there is some space
- if(blood_volume < BLOOD_VOLUME_NORMAL)
+ if(blood_volume < (BLOOD_VOLUME_NORMAL * blood_ratio))
blood_volume += 0.1 // regenerate blood VERY slowly
- if(blood_volume < BLOOD_VOLUME_OKAY)
- adjustOxyLoss(round((BLOOD_VOLUME_NORMAL - blood_volume) * 0.02, 1))
+ if(blood_volume < (BLOOD_VOLUME_OKAY * blood_ratio))
+ adjustOxyLoss(round(((BLOOD_VOLUME_NORMAL * blood_ratio) - blood_volume) * 0.02, 1))
// Takes care blood loss and regeneration
/mob/living/carbon/human/handle_blood()
@@ -33,7 +33,7 @@
if(bodytemperature >= TCRYO && !(HAS_TRAIT(src, TRAIT_NOCLONE))) //cryosleep or husked people do not pump the blood.
//Blood regeneration if there is some space
- if(blood_volume < BLOOD_VOLUME_NORMAL && !HAS_TRAIT(src, TRAIT_NOHUNGER))
+ if(blood_volume < (BLOOD_VOLUME_NORMAL * blood_ratio) && !HAS_TRAIT(src, TRAIT_NOHUNGER))
var/nutrition_ratio = 0
switch(nutrition)
if(0 to NUTRITION_LEVEL_STARVING)
@@ -46,20 +46,22 @@
nutrition_ratio = 0.8
else
nutrition_ratio = 1
+ if(HAS_TRAIT(src, TRAIT_HIGH_BLOOD))
+ nutrition_ratio *= 1.2
if(satiety > 80)
nutrition_ratio *= 1.25
nutrition = max(0, nutrition - nutrition_ratio * HUNGER_FACTOR)
- blood_volume = min(BLOOD_VOLUME_NORMAL, blood_volume + 0.5 * nutrition_ratio)
+ blood_volume = min((BLOOD_VOLUME_NORMAL * blood_ratio), blood_volume + 0.5 * nutrition_ratio)
//Effects of bloodloss
var/word = pick("dizzy","woozy","faint")
- switch(blood_volume)
+ switch(blood_volume * INVERSE(blood_ratio))
if(BLOOD_VOLUME_OKAY to BLOOD_VOLUME_SAFE)
if(prob(5))
to_chat(src, "You feel [word].")
- adjustOxyLoss(round((BLOOD_VOLUME_NORMAL - blood_volume) * 0.01, 1))
+ adjustOxyLoss(round(((BLOOD_VOLUME_NORMAL * blood_ratio) - blood_volume) * 0.01, 1))
if(BLOOD_VOLUME_BAD to BLOOD_VOLUME_OKAY)
- adjustOxyLoss(round((BLOOD_VOLUME_NORMAL - blood_volume) * 0.02, 1))
+ adjustOxyLoss(round(((BLOOD_VOLUME_NORMAL * blood_ratio) - blood_volume) * 0.02, 1))
if(prob(5))
blur_eyes(6)
to_chat(src, "You feel very [word].")
@@ -111,7 +113,7 @@
blood_volume = initial(blood_volume)
/mob/living/carbon/human/restore_blood()
- blood_volume = BLOOD_VOLUME_NORMAL
+ blood_volume = (BLOOD_VOLUME_NORMAL * blood_ratio)
bleed_rate = 0
/****************************************************
@@ -122,7 +124,7 @@
/mob/living/proc/transfer_blood_to(atom/movable/AM, amount, forced)
if(!blood_volume || !AM.reagents)
return 0
- if(blood_volume < BLOOD_VOLUME_BAD && !forced)
+ if(blood_volume < (BLOOD_VOLUME_BAD * blood_ratio) && !forced)
return 0
if(blood_volume < amount)
@@ -322,3 +324,24 @@
var/obj/effect/decal/cleanable/oil/B = locate() in T.contents
if(!B)
B = new(T)
+
+//This is a terrible way of handling it.
+/mob/living/proc/ResetBloodVol()
+ if(ishuman(src))
+ var/mob/living/carbon/human/H = src
+ if (HAS_TRAIT(src, TRAIT_HIGH_BLOOD))
+ blood_ratio = 1.2
+ H.handle_blood()
+ return
+ blood_ratio = 1
+ H.handle_blood()
+ return
+ blood_ratio = 1
+
+/mob/living/proc/AdjustBloodVol(var/value)
+ if(blood_ratio == value)
+ return
+ blood_ratio = value
+ if(ishuman(src))
+ var/mob/living/carbon/human/H = src
+ H.handle_blood()
diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm
index e94a6eac86..72d1d7392b 100644
--- a/code/modules/mob/living/carbon/carbon.dm
+++ b/code/modules/mob/living/carbon/carbon.dm
@@ -6,6 +6,7 @@
create_reagents(1000)
update_body_parts() //to update the carbon's new bodyparts appearance
GLOB.carbon_list += src
+ blood_volume = (BLOOD_VOLUME_NORMAL * blood_ratio)
/mob/living/carbon/Destroy()
//This must be done first, so the mob ghosts correctly before DNA etc is nulled
@@ -477,11 +478,13 @@
if(message)
visible_message("[src] throws up all over [p_them()]self!", \
"You throw up all over yourself!")
+ SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "vomit", /datum/mood_event/vomitself)
distance = 0
else
if(message)
visible_message("[src] throws up!", "You throw up!")
-
+ if(!isflyperson(src))
+ SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "vomit", /datum/mood_event/vomit)
if(stun)
Stun(80)
@@ -929,3 +932,17 @@
/mob/living/carbon/can_resist()
return bodyparts.len > 2 && ..()
+
+/mob/living/carbon/proc/hypnosis_vulnerable()//unused atm, but added in case
+ if(HAS_TRAIT(src, TRAIT_MINDSHIELD))
+ return FALSE
+ if(hallucinating())
+ return TRUE
+ if(IsSleeping())
+ return TRUE
+ if(HAS_TRAIT(src, TRAIT_DUMB))
+ return TRUE
+ GET_COMPONENT_FROM(mood, /datum/component/mood, src)
+ if(mood)
+ if(mood.sanity < SANITY_UNSTABLE)
+ return TRUE
diff --git a/code/modules/mob/living/carbon/human/examine.dm b/code/modules/mob/living/carbon/human/examine.dm
index 2f8f7977d7..0a34b02e4b 100644
--- a/code/modules/mob/living/carbon/human/examine.dm
+++ b/code/modules/mob/living/carbon/human/examine.dm
@@ -278,7 +278,7 @@
if(DISGUST_LEVEL_DISGUSTED to INFINITY)
msg += "[t_He] look[p_s()] extremely disgusted.\n"
- if(blood_volume < BLOOD_VOLUME_SAFE)
+ if(blood_volume < (BLOOD_VOLUME_SAFE*blood_ratio))
msg += "[t_He] [t_has] pale skin.\n"
if(bleedsuppress)
@@ -312,6 +312,13 @@
if(91.01 to INFINITY)
msg += "[t_He] [t_is] a shitfaced, slobbering wreck.\n"
+ if(reagents.has_reagent("astral"))
+ msg += "[t_He] have wild, spacey eyes"
+ if(mind)
+ msg += " and have a strange, abnormal look to them.\n"
+ else
+ msg += " and don't look like they're all there.\n"
+
if(isliving(user))
var/mob/living/L = user
if(src != user && HAS_TRAIT(L, TRAIT_EMPATH) && !appears_dead)
@@ -335,6 +342,13 @@
msg += ""
+ var/obj/item/organ/vocal_cords/Vc = user.getorganslot(ORGAN_SLOT_VOICE)
+ if(Vc)
+ if(istype(Vc, /obj/item/organ/vocal_cords/velvet))
+ if(client?.prefs.lewdchem)
+ msg += "You feel your chords resonate looking at them.\n"
+
+
if(!appears_dead)
if(stat == UNCONSCIOUS)
msg += "[t_He] [t_is]n't responding to anything around [t_him] and seem[p_s()] to be asleep.\n"
diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm
index 1f37b8a4ab..daf5917f87 100644
--- a/code/modules/mob/living/carbon/human/human.dm
+++ b/code/modules/mob/living/carbon/human/human.dm
@@ -815,6 +815,8 @@
for(var/datum/mutation/human/HM in dna.mutations)
if(HM.quality != POSITIVE)
dna.remove_mutation(HM.name)
+ if(blood_volume < (BLOOD_VOLUME_NORMAL*blood_ratio))
+ blood_volume = (BLOOD_VOLUME_NORMAL*blood_ratio)
..()
/mob/living/carbon/human/check_weakness(obj/item/weapon, mob/living/attacker)
diff --git a/code/modules/mob/living/carbon/human/human_defines.dm b/code/modules/mob/living/carbon/human/human_defines.dm
index 1a7933e74a..7bc7aa62d1 100644
--- a/code/modules/mob/living/carbon/human/human_defines.dm
+++ b/code/modules/mob/living/carbon/human/human_defines.dm
@@ -51,6 +51,7 @@
var/list/blood_smear = list(BLOOD_STATE_BLOOD = 0, BLOOD_STATE_OIL = 0, BLOOD_STATE_NOT_BLOODY = 0)
var/name_override //For temporary visible name changes
+ var/genital_override = FALSE //Force genitals on things incase of chems
var/nameless = FALSE //For drones of both the insectoid and robotic kind. And other types of nameless critters.
diff --git a/code/modules/mob/living/carbon/human/species.dm b/code/modules/mob/living/carbon/human/species.dm
index 5dfd6fdce6..73149c422d 100644
--- a/code/modules/mob/living/carbon/human/species.dm
+++ b/code/modules/mob/living/carbon/human/species.dm
@@ -517,7 +517,7 @@ GLOBAL_LIST_EMPTY(roundstart_races)
if(B)
var/mutable_appearance/MA = mutable_appearance(B.icon, B.icon_state, -BODY_LAYER)
if(UNDIE_COLORABLE(B))
- MA.color = H.undie_color
+ MA.color = "#[H.undie_color]"
standing += MA
if(H.undershirt)
@@ -533,7 +533,7 @@ GLOBAL_LIST_EMPTY(roundstart_races)
else
MA = mutable_appearance(T.icon, T.icon_state, -BODY_LAYER)
if(UNDIE_COLORABLE(T))
- MA.color = H.shirt_color
+ MA.color = "#[H.shirt_color]"
standing += MA
if(H.socks && H.get_num_legs(FALSE) >= 2)
@@ -546,7 +546,7 @@ GLOBAL_LIST_EMPTY(roundstart_races)
var/digilegs = (DIGITIGRADE in species_traits) ? "_d" : ""
var/mutable_appearance/MA = mutable_appearance(S.icon, "[S.icon_state][digilegs]", -BODY_LAYER)
if(UNDIE_COLORABLE(S))
- MA.color = H.socks_color
+ MA.color = "[H.socks_color]"
standing += MA
if(standing.len)
diff --git a/code/modules/mob/living/carbon/human/species_types/jellypeople.dm b/code/modules/mob/living/carbon/human/species_types/jellypeople.dm
index 750491f1d6..b564ca33e4 100644
--- a/code/modules/mob/living/carbon/human/species_types/jellypeople.dm
+++ b/code/modules/mob/living/carbon/human/species_types/jellypeople.dm
@@ -50,14 +50,14 @@
H.adjustBruteLoss(5)
to_chat(H, "You feel empty!")
- if(H.blood_volume < BLOOD_VOLUME_NORMAL)
+ if(H.blood_volume < (BLOOD_VOLUME_NORMAL * H.blood_ratio))
if(H.nutrition >= NUTRITION_LEVEL_STARVING)
H.blood_volume += 3
H.nutrition -= 2.5
- if(H.blood_volume < BLOOD_VOLUME_OKAY)
+ if(H.blood_volume < (BLOOD_VOLUME_OKAY*H.blood_ratio))
if(prob(5))
to_chat(H, "You feel drained!")
- if(H.blood_volume < BLOOD_VOLUME_BAD)
+ if(H.blood_volume < (BLOOD_VOLUME_BAD*H.blood_ratio))
Cannibalize_Body(H)
if(regenerate_limbs)
regenerate_limbs.UpdateButtonIcon()
@@ -89,7 +89,7 @@
var/list/limbs_to_heal = H.get_missing_limbs()
if(limbs_to_heal.len < 1)
return 0
- if(H.blood_volume >= BLOOD_VOLUME_OKAY+40)
+ if(H.blood_volume >= (BLOOD_VOLUME_OKAY*H.blood_ratio)+40)
return 1
return 0
@@ -100,13 +100,13 @@
to_chat(H, "You feel intact enough as it is.")
return
to_chat(H, "You focus intently on your missing [limbs_to_heal.len >= 2 ? "limbs" : "limb"]...")
- if(H.blood_volume >= 40*limbs_to_heal.len+BLOOD_VOLUME_OKAY)
+ if(H.blood_volume >= 40*limbs_to_heal.len+(BLOOD_VOLUME_OKAY*H.blood_ratio))
H.regenerate_limbs()
H.blood_volume -= 40*limbs_to_heal.len
to_chat(H, "...and after a moment you finish reforming!")
return
else if(H.blood_volume >= 40)//We can partially heal some limbs
- while(H.blood_volume >= BLOOD_VOLUME_OKAY+40)
+ while(H.blood_volume >= (BLOOD_VOLUME_OKAY*H.blood_ratio)+40)
var/healed_limb = pick(limbs_to_heal)
H.regenerate_limb(healed_limb)
limbs_to_heal -= healed_limb
@@ -140,7 +140,7 @@
bodies -= C // This means that the other bodies maintain a link
// so if someone mindswapped into them, they'd still be shared.
bodies = null
- C.blood_volume = min(C.blood_volume, BLOOD_VOLUME_NORMAL)
+ C.blood_volume = min(C.blood_volume, (BLOOD_VOLUME_NORMAL*C.blood_ratio))
..()
/datum/species/jelly/slime/on_species_gain(mob/living/carbon/C, datum/species/old_species)
@@ -731,4 +731,4 @@
to_chat(H, "You connect [target]'s mind to your slime link!")
else
to_chat(H, "You can't seem to link [target]'s mind...")
- to_chat(target, "The foreign presence leaves your mind.")
\ No newline at end of file
+ to_chat(target, "The foreign presence leaves your mind.")
diff --git a/code/modules/mob/living/carbon/human/species_types/vampire.dm b/code/modules/mob/living/carbon/human/species_types/vampire.dm
index 4bc3d622ac..53c6f1bd0f 100644
--- a/code/modules/mob/living/carbon/human/species_types/vampire.dm
+++ b/code/modules/mob/living/carbon/human/species_types/vampire.dm
@@ -46,7 +46,7 @@
C.adjustCloneLoss(-4)
return
C.blood_volume -= 0.75
- if(C.blood_volume <= BLOOD_VOLUME_SURVIVE)
+ if(C.blood_volume <= (BLOOD_VOLUME_SURVIVE*C.blood_ratio))
to_chat(C, "You ran out of blood!")
C.dust()
var/area/A = get_area(C)
diff --git a/code/modules/mob/living/carbon/human/whisper.dm b/code/modules/mob/living/carbon/human/whisper.dm
index 65a4c5d33f..51c7ad9d25 100644
--- a/code/modules/mob/living/carbon/human/whisper.dm
+++ b/code/modules/mob/living/carbon/human/whisper.dm
@@ -88,4 +88,4 @@
AM.Hear(rendered, src, language, message, , spans)
if(critical) //Dying words.
- succumb(1)
+ succumb()
diff --git a/code/modules/mob/living/carbon/life.dm b/code/modules/mob/living/carbon/life.dm
index ec80da6b53..6c17e17ca8 100644
--- a/code/modules/mob/living/carbon/life.dm
+++ b/code/modules/mob/living/carbon/life.dm
@@ -229,6 +229,9 @@
else if(SA_partialpressure > 0.01)
if(prob(20))
emote(pick("giggle","laugh"))
+ SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "chemical_euphoria", /datum/mood_event/chemical_euphoria)
+ else
+ SEND_SIGNAL(src, COMSIG_CLEAR_MOOD_EVENT, "chemical_euphoria")
//BZ (Facepunch port of their Agent B)
if(breath_gases[/datum/gas/bz])
@@ -527,6 +530,9 @@ GLOBAL_LIST_INIT(ballmer_windows_me_msg, list("Yo man, what if, we like, uh, put
if(jitteriness)
do_jitter_animation(jitteriness)
jitteriness = max(jitteriness - restingpwr, 0)
+ SEND_SIGNAL(src, COMSIG_ADD_MOOD_EVENT, "jittery", /datum/mood_event/jittery)
+ else
+ SEND_SIGNAL(src, COMSIG_CLEAR_MOOD_EVENT, "jittery")
if(stuttering)
stuttering = max(stuttering-1, 0)
@@ -612,6 +618,8 @@ GLOBAL_LIST_INIT(ballmer_windows_me_msg, list("Yo man, what if, we like, uh, put
if(drunkenness >= 101)
adjustToxLoss(4) //Let's be honest you shouldn't be alive by now
+ else
+ SEND_SIGNAL(src, COMSIG_CLEAR_MOOD_EVENT, "drunk")
//used in human and monkey handle_environment()
/mob/living/carbon/proc/natural_bodytemperature_stabilization()
diff --git a/code/modules/mob/living/carbon/update_icons.dm b/code/modules/mob/living/carbon/update_icons.dm
index ea79603793..0ddcd90f08 100644
--- a/code/modules/mob/living/carbon/update_icons.dm
+++ b/code/modules/mob/living/carbon/update_icons.dm
@@ -191,7 +191,7 @@
clear_alert("legcuffed")
if(legcuffed)
var/mutable_appearance/legcuffs = mutable_appearance('icons/mob/restraints.dmi', legcuffed.item_state, -LEGCUFF_LAYER)
- legcuffs.color = handcuffed.color
+ legcuffs.color = legcuffed.color
overlays_standing[HANDCUFF_LAYER] = legcuffs
apply_overlay(LEGCUFF_LAYER)
diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm
index 0559e4f3d8..d992a46245 100644
--- a/code/modules/mob/living/living.dm
+++ b/code/modules/mob/living/living.dm
@@ -318,17 +318,27 @@
visible_message("[src] points at [A].", "You point at [A].")
return TRUE
-/mob/living/verb/succumb(whispered as null)
+/mob/living/verb/succumb()
set name = "Succumb"
set category = "IC"
+ if(src.has_status_effect(/datum/status_effect/chem/enthrall))
+ var/datum/status_effect/chem/enthrall/E = src.has_status_effect(/datum/status_effect/chem/enthrall)
+ if(E.phase < 3)
+ if(HAS_TRAIT(src, TRAIT_MINDSHIELD))
+ to_chat(src, "Your mindshield prevents your mind from giving in!")
+ else if(src.mind.assigned_role in GLOB.command_positions)
+ to_chat(src, "Your dedication to your department prevents you from giving in!")
+ else
+ E.enthrallTally += 20
+ to_chat(src, "You give into [E.master]'s influence.")
if (InCritical())
- log_message("Has [whispered ? "whispered his final words" : "succumbed to death"] while in [InFullCritical() ? "hard":"soft"] critical with [round(health, 0.1)] points of health!", LOG_ATTACK)
+ log_message("Has succumbed to death while in [InFullCritical() ? "hard":"soft"] critical with [round(health, 0.1)] points of health!", LOG_ATTACK)
adjustOxyLoss(health - HEALTH_THRESHOLD_DEAD)
updatehealth()
- if(!whispered)
- to_chat(src, "You have given up life and succumbed to death.")
+ to_chat(src, "You have given up life and succumbed to death.")
death()
+
/mob/living/incapacitated(ignore_restraints, ignore_grab)
if(stat || IsUnconscious() || IsStun() || IsKnockdown() || recoveringstam || (!ignore_restraints && restrained(ignore_grab))) // CIT CHANGE - adds recoveringstam check here
return TRUE
@@ -532,7 +542,7 @@
var/trail_type = getTrail()
if(trail_type)
var/brute_ratio = round(getBruteLoss() / maxHealth, 0.1)
- if(blood_volume && blood_volume > max(BLOOD_VOLUME_NORMAL*(1 - brute_ratio * 0.25), 0))//don't leave trail if blood volume below a threshold
+ if(blood_volume && blood_volume > max((BLOOD_VOLUME_NORMAL*blood_ratio)*(1 - brute_ratio * 0.25), 0))//don't leave trail if blood volume below a threshold
blood_volume = max(blood_volume - max(1, brute_ratio * 2), 0) //that depends on our brute damage.
var/newdir = get_dir(target_turf, start)
if(newdir != direction)
diff --git a/code/modules/mob/living/living_defines.dm b/code/modules/mob/living/living_defines.dm
index 3b0af53866..c24e6ab108 100644
--- a/code/modules/mob/living/living_defines.dm
+++ b/code/modules/mob/living/living_defines.dm
@@ -77,6 +77,7 @@
var/stun_absorption = null //converted to a list of stun absorption sources this mob has when one is added
var/blood_volume = 0 //how much blood the mob has
+ var/blood_ratio = 1 //How much blood the mob needs, in terms of ratio (i.e 1.2 will require BLOOD_VOLUME_NORMAL of 672) DO NOT GO ABOVE 3.55 Well, actually you can but, then they can't get enough blood.
var/obj/effect/proc_holder/ranged_ability //Any ranged ability the mob has, as a click override
var/see_override = 0 //0 for no override, sets see_invisible = see_override in silicon & carbon life process via update_sight()
diff --git a/code/modules/mob/living/say.dm b/code/modules/mob/living/say.dm
index bfd27c11cc..3b045b9eee 100644
--- a/code/modules/mob/living/say.dm
+++ b/code/modules/mob/living/say.dm
@@ -211,7 +211,7 @@ GLOBAL_LIST_INIT(department_radio_keys, list(
send_speech(message, message_range, src, bubble_type, spans, language, message_mode)
if(succumbed)
- succumb(1)
+ succumb()
to_chat(src, compose_message(src, language, message, , spans, message_mode))
return 1
@@ -281,6 +281,7 @@ GLOBAL_LIST_INIT(department_radio_keys, list(
AM.Hear(eavesrendered, src, message_language, eavesdropping, , spans, message_mode)
else
AM.Hear(rendered, src, message_language, message, , spans, message_mode)
+ SEND_GLOBAL_SIGNAL(COMSIG_GLOB_LIVING_SAY_SPECIAL, src, message)
//speech bubble
var/list/speech_bubble_recipients = list()
diff --git a/code/modules/mob/living/simple_animal/astral.dm b/code/modules/mob/living/simple_animal/astral.dm
new file mode 100644
index 0000000000..9bac53ef22
--- /dev/null
+++ b/code/modules/mob/living/simple_animal/astral.dm
@@ -0,0 +1,59 @@
+/mob/living/simple_animal/astral
+ name = "Astral projection"
+ desc = "A soul of someone projecting their mind."
+ icon = 'icons/mob/mob.dmi'
+ icon_state = "ghost"
+ icon_living = "ghost"
+ mob_biotypes = list(MOB_SPIRIT)
+ attacktext = "raises the hairs on the neck of"
+ response_harm = "disrupts the concentration of"
+ response_disarm = "wafts"
+ friendly = "communes with"
+ loot = null
+ maxHealth = 10
+ health = 10
+ melee_damage_lower = 0
+ melee_damage_upper = 0
+ obj_damage = 0
+ deathmessage = "disappears as if it was never really there to begin with"
+ incorporeal_move = 1
+ alpha = 50
+ attacktext = "touches the mind of"
+ speak_emote = list("echos")
+ movement_type = FLYING
+ var/pseudo_death = FALSE
+ var/posses_safe = FALSE
+ atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0)
+ unsuitable_atmos_damage = 0
+ minbodytemp = 0
+ maxbodytemp = 100000
+
+/mob/living/simple_animal/astral/death()
+ icon_state = "shade_dead"
+ Stun(1000)
+ canmove = 0
+ friendly = "deads at"
+ pseudo_death = TRUE
+ incorporeal_move = 0
+ to_chat(src, "Your astral projection is interrupted and your mind is sent back to your body with a shock!")
+
+/mob/living/simple_animal/astral/ClickOn(var/atom/A, var/params)
+ ..()
+ if(pseudo_death == FALSE)
+ if(isliving(A))
+ if(ishuman(A))
+ var/mob/living/carbon/human/H = A
+ if(H.reagents.has_reagent("astral") && !H.mind)
+ var/datum/reagent/fermi/astral/As = locate(/datum/reagent/fermi/astral) in H.reagents.reagent_list
+ if(As.originalmind == src.mind && As.current_cycle < 10 && H.stat != DEAD) //So you can return to your body.
+ to_chat(src, "The intensity of the astrogen in your body is too much allow you to return to yourself yet!")
+ return
+ to_chat(src, "You astrally possess [H]!")
+ log_game("FERMICHEM: [src] has astrally possessed [A]!")
+ src.mind.transfer_to(H)
+ qdel(src)
+ var/message = html_decode(stripped_input(src, "Enter a message to send to [A]", MAX_MESSAGE_LEN))
+ if(!message)
+ return
+ to_chat(A, "[src] projects into your mind, \"[message]\"")
+ log_game("FERMICHEM: [src] has astrally transmitted [message] into [A]")
diff --git a/code/modules/mob/living/simple_animal/friendly/cat.dm b/code/modules/mob/living/simple_animal/friendly/cat.dm
index 5e5b486435..3a21a04bf9 100644
--- a/code/modules/mob/living/simple_animal/friendly/cat.dm
+++ b/code/modules/mob/living/simple_animal/friendly/cat.dm
@@ -292,3 +292,31 @@
if(L.a_intent == INTENT_HARM && L.reagents && !stat)
L.reagents.add_reagent("nutriment", 0.4)
L.reagents.add_reagent("vitamin", 0.4)
+
+//Cat made
+/mob/living/simple_animal/pet/cat/custom_cat
+ name = "White cat" //Incase it somehow gets spawned without an ID
+ desc = "A cute white catto!"
+ icon_state = "custom_cat"
+ icon_living = "custom_cat"
+ icon_dead = "custom_cat_dead"
+ gender = FEMALE
+ gold_core_spawnable = NO_SPAWN
+ health = 50 //So people can't instakill it s
+ maxHealth = 50
+ speak = list("Meowrowr!", "Mew!", "Miauen!")
+ speak_emote = list("wigglepurrs", "mewls")
+ emote_hear = list("meows.", "mews.")
+ emote_see = list("looks at you eagerly for pets!", "wiggles enthusiastically.")
+ gold_core_spawnable = NO_SPAWN
+ var/pseudo_death = FALSE
+
+/mob/living/simple_animal/pet/cat/custom_cat/death()
+ if (pseudo_death == TRUE) //secret cat chem
+ icon_state = "custom_cat_dead"
+ Stun(1000)
+ canmove = 0
+ friendly = "deads at"
+ return
+ else
+ ..()
diff --git a/code/modules/mob/living/simple_animal/hostile/zombie.dm b/code/modules/mob/living/simple_animal/hostile/zombie.dm
index 21c2d4804a..7d89941687 100644
--- a/code/modules/mob/living/simple_animal/hostile/zombie.dm
+++ b/code/modules/mob/living/simple_animal/hostile/zombie.dm
@@ -55,4 +55,28 @@
/mob/living/simple_animal/hostile/zombie/drop_loot()
. = ..()
corpse.forceMove(drop_location())
- corpse.create()
\ No newline at end of file
+ corpse.create()
+
+/mob/living/simple_animal/hostile/unemployedclone
+ name = "Failed clone"
+ desc = "Somebody failed chemistry."
+ icon = 'icons/mob/human.dmi'
+ icon_state = "husk"
+ icon_living = "husk"
+ icon_dead = "husk"
+ mob_biotypes = list(MOB_ORGANIC, MOB_HUMANOID)
+ speak_chance = 0
+ stat_attack = UNCONSCIOUS //braains
+ maxHealth = 100
+ health = 100
+ harm_intent_damage = 5
+ melee_damage_lower = 21
+ melee_damage_upper = 21
+ attacktext = "bites"
+ attack_sound = 'sound/hallucinations/growl1.ogg'
+ a_intent = INTENT_HARM
+ atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0)
+ minbodytemp = 0
+ spacewalk = FALSE
+ status_flags = CANPUSH
+ del_on_death = 0
diff --git a/code/modules/mob/living/taste.dm b/code/modules/mob/living/taste.dm
index fec024cebf..282fe0a716 100644
--- a/code/modules/mob/living/taste.dm
+++ b/code/modules/mob/living/taste.dm
@@ -32,4 +32,33 @@
last_taste_time = world.time
last_taste_text = text_output
+//FermiChem - How to check pH of a beaker without a meter/pH paper.
+//Basically checks the pH of the holder and burns your poor tongue if it's too acidic!
+//TRAIT_AGEUSIA players can't taste, unless it's burning them.
+//taking sips of a strongly acidic/alkaline substance will burn your tongue.
+/mob/living/carbon/taste(datum/reagents/from)
+ var/obj/item/organ/tongue/T = getorganslot("tongue")
+ if (!T)
+ return
+ .=..()
+ if ((from.pH > 12.5) || (from.pH < 1.5))
+ to_chat(src, "You taste chemical burns!")
+ T.adjustTongueLoss(src, 4)
+ if(istype(T, /obj/item/organ/tongue/cybernetic))
+ to_chat(src, "Your tongue moves on it's own in response to the liquid.")
+ say("The pH is appropriately [round(from.pH, 1)].")
+ return
+ if (!HAS_TRAIT(src, TRAIT_AGEUSIA)) //I'll let you get away with not having 1 damage.
+ switch(from.pH)
+ if(11.5 to INFINITY)
+ to_chat(src, "You taste a strong alkaline flavour!")
+ T.adjustTongueLoss(src, 1)
+ if(8.5 to 11.5)
+ to_chat(src, "You taste a sort of soapy tone in the mixture.")
+ if(2.5 to 5.5)
+ to_chat(src, "You taste a sort of acid tone in the mixture.")
+ if(-INFINITY to 2.5)
+ to_chat(src, "You taste a strong acidic flavour!")
+ T.adjustTongueLoss(src, 1)
+
#undef DEFAULT_TASTE_SENSITIVITY
diff --git a/code/modules/reagents/chemistry/holder.dm b/code/modules/reagents/chemistry/holder.dm
index dc86ab4e6c..300e73a6fd 100644
--- a/code/modules/reagents/chemistry/holder.dm
+++ b/code/modules/reagents/chemistry/holder.dm
@@ -1,4 +1,3 @@
-
/proc/build_chemical_reagent_list()
//Chemical Reagents - Initialises all /datum/reagent into a list indexed by reagent id
@@ -53,10 +52,16 @@
var/maximum_volume = 100
var/atom/my_atom = null
var/chem_temp = 150
+ var/pH = REAGENT_NORMAL_PH//Potential of hydrogen. Edited on adding new reagents, deleting reagents, and during fermi reactions.
+ var/overallPurity = 1
var/last_tick = 1
var/addiction_tick = 1
var/list/datum/reagent/addiction_list = new/list()
var/reagents_holder_flags
+ var/targetVol = 0 //the target volume, i.e. the total amount that can be created during a fermichem reaction.
+ var/reactedVol = 0 //how much of the reagent is reacted during a fermireaction
+ var/fermiIsReacting = FALSE //that prevents multiple reactions from occurring (i.e. add_reagent calls to process_reactions(), this stops any extra reactions.)
+ var/fermiReactID = null //ID of the chem being made during a fermireaction, kept here so it's cache isn't lost between loops/procs.
/datum/reagents/New(maximum=100, new_flags)
maximum_volume = maximum
@@ -121,11 +126,13 @@
/datum/reagents/proc/remove_all(amount = 1)
var/list/cached_reagents = reagent_list
+ if((total_volume - amount) <= 0)//Because this can result in 0, I don't want it to crash.
+ pH = 7
if(total_volume > 0)
var/part = amount / total_volume
for(var/reagent in cached_reagents)
var/datum/reagent/R = reagent
- remove_reagent(R.id, R.volume * part)
+ remove_reagent(R.id, R.volume * part, ignore_pH = TRUE)
update_total()
handle_reactions()
@@ -189,8 +196,11 @@
var/transfer_amount = T.volume * part
if(preserve_data)
trans_data = copy_data(T)
- R.add_reagent(T.id, transfer_amount * multiplier, trans_data, chem_temp, no_react = 1) //we only handle reaction after every reagent has been transfered.
- remove_reagent(T.id, transfer_amount)
+
+
+ R.add_reagent(T.id, transfer_amount * multiplier, trans_data, chem_temp, T.purity, pH, no_react = TRUE, ignore_pH = TRUE) //we only handle reaction after every reagent has been transfered.
+
+ remove_reagent(T.id, transfer_amount, ignore_pH = TRUE)
update_total()
R.update_total()
@@ -249,7 +259,8 @@
if(current_reagent.id == reagent)
if(preserve_data)
trans_data = current_reagent.data
- R.add_reagent(current_reagent.id, amount, trans_data, src.chem_temp)
+ R.add_reagent(current_reagent.id, amount, trans_data, chem_temp, current_reagent.purity, pH, no_react = TRUE)
+
remove_reagent(current_reagent.id, amount, 1)
break
@@ -350,14 +361,21 @@
R.on_update (A)
update_total()
-/datum/reagents/proc/handle_reactions()
+
+/datum/reagents/proc/handle_reactions()//HERE EDIT HERE THE MAIN REACTION
+ if(fermiIsReacting == TRUE)
+ return
+
if(reagents_holder_flags & NO_REACT)
return //Yup, no reactions here. No siree.
+
var/list/cached_reagents = reagent_list
var/list/cached_reactions = GLOB.chemical_reactions_list
var/datum/cached_my_atom = my_atom
- var/reaction_occurred = 0
+ var/reaction_occurred = 0 // checks if reaction, binary variable
+ var/continue_reacting = FALSE //Helps keep track what kind of reaction is occuring; standard or fermi.
+
do
var/list/possible_reactions = list()
reaction_occurred = 0
@@ -382,6 +400,7 @@
var/has_special_react = C.special_react
var/can_special_react = 0
+
for(var/B in cached_required_reagents)
if(!has_reagent(B, cached_required_reagents[B]))
break
@@ -402,7 +421,7 @@
if(!C.required_other)
matching_other = 1
- else if(istype(cached_my_atom, /obj/item/slime_extract))
+ else if(istype(cached_my_atom, /obj/item/slime_extract))//if the object is a slime_extract.
var/obj/item/slime_extract/M = cached_my_atom
if(M.Uses > 0) // added a limit to slime cores -- Muskets requested this
@@ -413,7 +432,7 @@
if(!C.required_other)
matching_other = 1
- if(required_temp == 0 || (is_cold_recipe && chem_temp <= required_temp) || (!is_cold_recipe && chem_temp >= required_temp))
+ if(required_temp == 0 || (is_cold_recipe && chem_temp <= required_temp) || (!is_cold_recipe && chem_temp >= required_temp))//Temperature check!!
meets_temp_requirement = 1
if(!has_special_react || C.check_special_react(src))
@@ -427,53 +446,268 @@
//select the reaction with the most extreme temperature requirements
for(var/V in possible_reactions)
var/datum/chemical_reaction/competitor = V
- if(selected_reaction.is_cold_recipe) //if there are no recipe conflicts, everything in possible_reactions will have this same value for is_cold_reaction. warranty void if assumption not met.
+ if(selected_reaction.is_cold_recipe)
if(competitor.required_temp <= selected_reaction.required_temp)
selected_reaction = competitor
else
- if(competitor.required_temp >= selected_reaction.required_temp)
+ if(competitor.required_temp >= selected_reaction.required_temp) //will return with the hotter reacting first.
selected_reaction = competitor
- var/list/cached_required_reagents = selected_reaction.required_reagents
- var/list/cached_results = selected_reaction.results
+ var/list/cached_required_reagents = selected_reaction.required_reagents//update reagents list
+ var/list/cached_results = selected_reaction.results//resultant chemical list
var/special_react_result = selected_reaction.check_special_react(src)
var/list/multiplier = INFINITY
- for(var/B in cached_required_reagents)
- multiplier = min(multiplier, round(get_reagent_amount(B) / cached_required_reagents[B]))
- for(var/B in cached_required_reagents)
- remove_reagent(B, (multiplier * cached_required_reagents[B]), safety = 1)
+ //Splits reactions into two types; FermiChem is advanced reaction mechanics, Other is default reaction.
+ //FermiChem relies on two additional properties; pH and impurity
+ //Temperature plays into a larger role too.
+ var/datum/chemical_reaction/C = selected_reaction
- for(var/P in selected_reaction.results)
- multiplier = max(multiplier, 1) //this shouldnt happen ...
- SSblackbox.record_feedback("tally", "chemical_reaction", cached_results[P]*multiplier, P)
- add_reagent(P, cached_results[P]*multiplier, null, chem_temp)
+ if (C.FermiChem == TRUE && !continue_reacting)
+ if (chem_temp > C.ExplodeTemp) //This is first to ensure explosions.
+ var/datum/chemical_reaction/fermi/Ferm = selected_reaction
+ fermiIsReacting = FALSE
+ SSblackbox.record_feedback("tally", "fermi_chem", 1, ("[Ferm] explosion"))
+ Ferm.FermiExplode(src, my_atom, volume = total_volume, temp = chem_temp, pH = pH)
+ return 0
- var/list/seen = viewers(4, get_turf(my_atom))
- var/iconhtml = icon2html(cached_my_atom, seen)
- if(cached_my_atom)
- if(!ismob(cached_my_atom)) // No bubbling mobs
- if(selected_reaction.mix_sound)
- playsound(get_turf(cached_my_atom), selected_reaction.mix_sound, 80, 1)
+ for(var/B in cached_required_reagents)
+ multiplier = min(multiplier, round((get_reagent_amount(B) / cached_required_reagents[B]), 0.01))
+ for(var/P in selected_reaction.results)
+ targetVol = cached_results[P]*multiplier
- for(var/mob/M in seen)
- to_chat(M, "[iconhtml] [selected_reaction.mix_message]")
+ if( (chem_temp <= C.ExplodeTemp) && (chem_temp >= C.OptimalTempMin))
+ if( (pH >= (C.OptimalpHMin - C.ReactpHLim)) && (pH <= (C.OptimalpHMax + C.ReactpHLim)) )//To prevent pointless reactions
+
+ if (fermiIsReacting == TRUE)
+ return 0
+ else
+ START_PROCESSING(SSprocessing, src)
+ selected_reaction.on_reaction(src, my_atom, multiplier)
+ fermiIsReacting = TRUE
+ fermiReactID = selected_reaction
+ reaction_occurred = 1
+
+ else //It's a little bit of a confusing nest, but esstentially we check if it's a fermireaction, then temperature, then pH. If this is true, the remainer of this handler is run.
+ return 0 //If pH is out of range
+ else
+ return 0 //If not hot enough
+
+ //Standard reaction mechanics:
+ else
+ if (C.FermiChem == TRUE)//Just to make sure
+ return 0
+
+ for(var/B in cached_required_reagents) //
+ multiplier = min(multiplier, round((get_reagent_amount(B) / cached_required_reagents[B]), 0.01))
+
+ for(var/B in cached_required_reagents)
+ remove_reagent(B, (multiplier * cached_required_reagents[B]), safety = 1, ignore_pH = TRUE)
+
+ for(var/P in selected_reaction.results)
+ multiplier = max(multiplier, 1) //this shouldnt happen ...
+ SSblackbox.record_feedback("tally", "chemical_reaction", cached_results[P]*multiplier, P)//log
+ add_reagent(P, cached_results[P]*multiplier, null, chem_temp)
+
+
+ var/list/seen = viewers(4, get_turf(my_atom))//Sound and sight checkers
+ var/iconhtml = icon2html(cached_my_atom, seen)
+ if(cached_my_atom)
+ if(!ismob(cached_my_atom)) // No bubbling mobs
+ if(selected_reaction.mix_sound)
+ playsound(get_turf(cached_my_atom), selected_reaction.mix_sound, 80, 1)
- if(istype(cached_my_atom, /obj/item/slime_extract))
- var/obj/item/slime_extract/ME2 = my_atom
- ME2.Uses--
- if(ME2.Uses <= 0) // give the notification that the slime core is dead
for(var/mob/M in seen)
- to_chat(M, "[iconhtml] \The [my_atom]'s power is consumed in the reaction.")
- ME2.name = "used slime extract"
- ME2.desc = "This extract has been used up."
+ to_chat(M, "[iconhtml] [selected_reaction.mix_message]")
- selected_reaction.on_reaction(src, multiplier, special_react_result)
- reaction_occurred = 1
+ if(istype(cached_my_atom, /obj/item/slime_extract))//if there's an extract and it's used up.
+ var/obj/item/slime_extract/ME2 = my_atom
+ ME2.Uses--
+ if(ME2.Uses <= 0) // give the notification that the slime core is dead
+ for(var/mob/M in seen)
+ to_chat(M, "[iconhtml] \The [my_atom]'s power is consumed in the reaction.")
+ ME2.name = "used slime extract"
+ ME2.desc = "This extract has been used up."
+
+ selected_reaction.on_reaction(src, multiplier, special_react_result)
+ reaction_occurred = 1
+ continue_reacting = TRUE
while(reaction_occurred)
update_total()
return 0
+/datum/reagents/process()
+ var/datum/chemical_reaction/fermi/C = fermiReactID
+
+ var/list/cached_required_reagents = C.required_reagents//update reagents list
+ var/list/cached_results = C.results//resultant chemical list
+ var/multiplier = INFINITY
+
+ for(var/B in cached_required_reagents) //
+ multiplier = min(multiplier, round((get_reagent_amount(B) / cached_required_reagents[B]), 0.001))
+ if (multiplier == 0)
+ fermiEnd()
+ return
+ for(var/P in cached_results)
+ targetVol = cached_results[P]*multiplier
+
+ if (fermiIsReacting == FALSE)
+ CRASH("Fermi has refused to stop reacting even though we asked her nicely.")
+
+ if (chem_temp > C.OptimalTempMin && fermiIsReacting == TRUE)//To prevent pointless reactions
+ if( (pH >= (C.OptimalpHMin - C.ReactpHLim)) && (pH <= (C.OptimalpHMax + C.ReactpHLim)) )
+ if (reactedVol < targetVol)
+ reactedVol = fermiReact(fermiReactID, chem_temp, pH, reactedVol, targetVol, cached_required_reagents, cached_results, multiplier)
+ else//Volume is used up
+ fermiEnd()
+ return
+ else//pH is out of range
+ fermiEnd()
+ return
+ else//Temperature is too low, or reaction has stopped.
+ fermiEnd()
+ return
+
+/datum/reagents/proc/fermiEnd()
+ var/datum/chemical_reaction/fermi/C = fermiReactID
+ STOP_PROCESSING(SSprocessing, src)
+ fermiIsReacting = FALSE
+ reactedVol = 0
+ targetVol = 0
+ //pH check, handled at the end to reduce calls.
+ if(istype(my_atom, /obj/item/reagent_containers))
+ var/obj/item/reagent_containers/RC = my_atom
+ RC.pH_check()
+ C.FermiFinish(src, my_atom)
+ handle_reactions()
+ update_total()
+ //Reaction sounds and words
+ playsound(get_turf(my_atom), C.mix_sound, 80, 1)
+ var/list/seen = viewers(5, get_turf(my_atom))
+ var/iconhtml = icon2html(my_atom, seen)
+ for(var/mob/M in seen)
+ to_chat(M, "[iconhtml] [C.mix_message]")
+
+/datum/reagents/proc/fermiReact(selected_reaction, cached_temp, cached_pH, reactedVol, targetVol, cached_required_reagents, cached_results, multiplier)
+ var/datum/chemical_reaction/fermi/C = selected_reaction
+ var/deltaT = 0
+ var/deltapH = 0
+ var/stepChemAmmount = 0
+
+ //get purity from combined beaker reactant purities HERE.
+ var/purity = 1
+
+ //Begin checks
+ //For now, purity is handled elsewhere (on add)
+ //Calculate DeltapH (Deviation of pH from optimal)
+ //Lower range
+ if (cached_pH < C.OptimalpHMin)
+ if (cached_pH < (C.OptimalpHMin - C.ReactpHLim))
+ deltapH = 0
+ return//If outside pH range, no reaction
+ else
+ deltapH = (((cached_pH - (C.OptimalpHMin - C.ReactpHLim))**C.CurveSharppH)/((C.ReactpHLim**C.CurveSharppH)))
+ //Upper range
+ else if (cached_pH > C.OptimalpHMax)
+ if (cached_pH > (C.OptimalpHMax + C.ReactpHLim))
+ deltapH = 0
+ return //If outside pH range, no reaction
+ else
+ deltapH = (((- cached_pH + (C.OptimalpHMax + C.ReactpHLim))**C.CurveSharppH)/(C.ReactpHLim**C.CurveSharppH))//Reverse - to + to prevent math operation failures.
+ //Within mid range
+ else if (cached_pH >= C.OptimalpHMin && cached_pH <= C.OptimalpHMax)
+ deltapH = 1
+ //This should never proc:
+ else
+ WARNING("[my_atom] attempted to determine FermiChem pH for '[C.id]' which broke for some reason! ([usr])")
+
+ //Calculate DeltaT (Deviation of T from optimal)
+ if (cached_temp < C.OptimalTempMax && cached_temp >= C.OptimalTempMin)
+ deltaT = (((cached_temp - C.OptimalTempMin)**C.CurveSharpT)/((C.OptimalTempMax - C.OptimalTempMin)**C.CurveSharpT))
+ else if (cached_temp >= C.OptimalTempMax)
+ deltaT = 1
+ else
+ deltaT = 0
+
+ purity = (deltapH)//set purity equal to pH offset
+
+ //Then adjust purity of result with reagent purity.
+ purity *= reactant_purity(C)
+
+ var/removeChemAmmount //remove factor
+ var/addChemAmmount //add factor
+ //ONLY WORKS FOR ONE PRODUCT AT THE MOMENT
+ //Calculate how much product to make and how much reactant to remove factors..
+ for(var/P in cached_results)
+ //stepChemAmmount = CLAMP(((deltaT * multiplier), 0, ((targetVol - reactedVol)/cached_results[P])) //used to have multipler, now it does
+ stepChemAmmount = (multiplier*cached_results[P])
+ if (stepChemAmmount >= C.RateUpLim)
+ stepChemAmmount = (C.RateUpLim)
+ addChemAmmount = deltaT * stepChemAmmount
+ if (addChemAmmount >= (targetVol - reactedVol))
+ addChemAmmount = (targetVol - reactedVol)
+ if (addChemAmmount < 0.01)
+ addChemAmmount = 0.01
+ removeChemAmmount = (addChemAmmount/cached_results[P])
+ //This is kept for future bugtesters.
+ //message_admins("Reaction vars: PreReacted: [reactedVol] of [targetVol]. deltaT [deltaT], multiplier [multiplier], Step [stepChemAmmount], uncapped Step [deltaT*(multiplier*cached_results[P])], addChemAmmount [addChemAmmount], removeFactor [removeChemAmmount] Pfactor [cached_results[P]], adding [addChemAmmount]")
+
+ //remove reactants
+ for(var/B in cached_required_reagents)
+ remove_reagent(B, (removeChemAmmount * cached_required_reagents[B]), safety = 1, ignore_pH = TRUE)
+
+ //add product
+ var/TotalStep = 0
+ for(var/P in cached_results)
+ SSblackbox.record_feedback("tally", "chemical_reaction", addChemAmmount, P)//log
+ SSblackbox.record_feedback("tally", "fermi_chem", addChemAmmount, P)
+ add_reagent(P, (addChemAmmount), null, cached_temp, purity)//add reagent function!! I THINK I can do this:
+ TotalStep += addChemAmmount//for multiple products
+ //Above should reduce yeild based on holder purity.
+ //Purity Check
+ for(var/datum/reagent/R in my_atom.reagents.reagent_list)
+ if(P == R.id)
+ if (R.purity < C.PurityMin)//If purity is below the min, blow it up.
+ fermiIsReacting = FALSE
+ SSblackbox.record_feedback("tally", "fermi_chem", 1, ("[P] explosion"))
+ C.FermiExplode(src, my_atom, (reactedVol+targetVol), cached_temp, pH)
+ STOP_PROCESSING(SSprocessing, src)
+ return 0
+
+ C.FermiCreate(src)//proc that calls when step is done
+
+ //Apply pH changes and thermal output of reaction to beaker
+ chem_temp = round(cached_temp + (C.ThermicConstant * addChemAmmount))
+ pH += (C.HIonRelease * addChemAmmount)
+ //keep track of the current reacted amount
+ reactedVol = reactedVol + addChemAmmount
+
+ //Check extremes
+ if (chem_temp > C.ExplodeTemp)
+ //go to explode proc
+ fermiIsReacting = FALSE
+ SSblackbox.record_feedback("tally", "fermi_chem", 1, ("[C] explosions"))
+ C.FermiExplode(src, my_atom, (reactedVol+targetVol), chem_temp, pH)
+ STOP_PROCESSING(SSprocessing, src)
+ return
+
+ //Make sure things are limited.
+ pH = CLAMP(pH, 0, 14)
+
+ //return said amount to compare for next step.
+ return (reactedVol)
+
+//Currently calculates it irrespective of required reagents at the start
+/datum/reagents/proc/reactant_purity(var/datum/chemical_reaction/fermi/C, holder)
+ var/list/cached_reagents = reagent_list
+ var/i = 0
+ var/cachedPurity
+ for(var/datum/reagent/R in my_atom.reagents.reagent_list)
+ if (R in cached_reagents)
+ cachedPurity += R.purity
+ i++
+ return cachedPurity/i
+
/datum/reagents/proc/isolate_reagent(reagent)
var/list/cached_reagents = reagent_list
for(var/_reagent in cached_reagents)
@@ -517,6 +751,7 @@
for(var/reagent in cached_reagents)
var/datum/reagent/R = reagent
del_reagent(R.id)
+ pH = REAGENT_NORMAL_PH
return 0
/datum/reagents/proc/reaction(atom/A, method = TOUCH, volume_modifier = 1, show_message = 1)
@@ -563,13 +798,17 @@
/datum/reagents/proc/adjust_thermal_energy(J, min_temp = 2.7, max_temp = 1000)
var/S = specific_heat()
- chem_temp = CLAMP(chem_temp + (J / (S * total_volume)), 2.7, 1000)
+ chem_temp = CLAMP(chem_temp + (J / (S * total_volume)), min_temp, max_temp)
+ if(istype(my_atom, /obj/item/reagent_containers))
+ var/obj/item/reagent_containers/RC = my_atom
+ RC.temp_check()
+
+/datum/reagents/proc/add_reagent(reagent, amount, list/data=null, reagtemp = 300, other_purity = 1, other_pH, no_react = 0, ignore_pH = FALSE)
-/datum/reagents/proc/add_reagent(reagent, amount, list/data=null, reagtemp = 300, no_react = 0)
if(!isnum(amount) || !amount)
return FALSE
- if(amount <= 0)
+ if(amount <= 0.00)
return FALSE
var/datum/reagent/D = GLOB.chemical_reagents_list[reagent]
@@ -577,6 +816,23 @@
WARNING("[my_atom] attempted to add a reagent called '[reagent]' which doesn't exist. ([usr])")
return FALSE
+ if (D.id == "water" && no_react == FALSE && !istype(my_atom, /obj/item/reagent_containers/food)) //Do like an otter, add acid to water, but also don't blow up botany.
+ if (pH <= 2)
+ SSblackbox.record_feedback("tally", "fermi_chem", 1, "water-acid explosions")
+ var/datum/effect_system/smoke_spread/chem/s = new
+ var/turf/T = get_turf(my_atom)
+ var/datum/reagents/R = new/datum/reagents(3000)
+ R.add_reagent("fermiAcid", amount)
+ for (var/datum/reagent/reagentgas in reagent_list)
+ R.add_reagent(reagentgas, amount/5)
+ remove_reagent(reagentgas, amount/5)
+ s.set_up(R, CLAMP(amount/10, 0, 2), T)
+ s.start()
+ return FALSE
+
+ if(!pH)
+ other_pH = D.pH
+
update_total()
var/cached_total = total_volume
if(cached_total + amount > maximum_volume)
@@ -587,6 +843,10 @@
var/cached_temp = chem_temp
var/list/cached_reagents = reagent_list
+ var/cached_pH = pH
+
+
+
//Equalize temperature - Not using specific_heat() because the new chemical isn't in yet.
var/specific_heat = 0
var/thermal_energy = 0
@@ -597,32 +857,55 @@
specific_heat += D.specific_heat * (amount / new_total)
thermal_energy += D.specific_heat * amount * reagtemp
chem_temp = thermal_energy / (specific_heat * new_total)
- ////
+
+ //cacluate reagent based pH shift.
+ if(ignore_pH == TRUE)
+ pH = ((cached_pH * cached_total)+(other_pH * amount))/(cached_total + amount)//should be right
+ else
+ pH = ((cached_pH * cached_total)+(D.pH * amount))/(cached_total + amount)//should be right
+ if(istype(my_atom, /obj/item/reagent_containers/))
+ var/obj/item/reagent_containers/RC = my_atom
+ RC.pH_check()//checks beaker resilience
//add the reagent to the existing if it exists
for(var/A in cached_reagents)
var/datum/reagent/R = A
- if (R.id == reagent)
+ if (R.id == reagent) //IF MERGING
+ //Add amount and equalize purity
R.volume += amount
+ R.purity = ((R.purity * R.volume) + (other_purity * amount)) /((R.volume + amount)) //This should add the purity to the product
+
update_total()
if(my_atom)
my_atom.on_reagent_change(ADD_REAGENT)
- R.on_merge(data, amount)
+ if(isliving(my_atom))
+ if(R.OnMobMergeCheck == TRUE)//Forces on_mob_add proc when a chem is merged
+ R.on_mob_add(my_atom, amount)
+ //else
+ // R.on_merge(data, amount, my_atom, other_purity)
+ R.on_merge(data, amount, my_atom, other_purity)
if(!no_react)
handle_reactions()
+
return TRUE
+
//otherwise make a new one
var/datum/reagent/R = new D.type(data)
cached_reagents += R
R.holder = src
R.volume = amount
+ R.purity = other_purity
+ R.loc = get_turf(my_atom)
if(data)
R.data = data
R.on_new(data)
+ if(R.addProc == TRUE)//Allows on new without data overhead.
+ R.on_new(pH) //Add more as desired.
+
if(isliving(my_atom))
- R.on_mob_add(my_atom) //Must occur befor it could posibly run on_mob_delete
+ R.on_mob_add(my_atom, amount)
update_total()
if(my_atom)
my_atom.on_reagent_change(ADD_REAGENT)
@@ -630,12 +913,13 @@
handle_reactions()
return TRUE
+
/datum/reagents/proc/add_reagent_list(list/list_reagents, list/data=null) // Like add_reagent but you can enter a list. Format it like this: list("toxin" = 10, "beer" = 15)
for(var/r_id in list_reagents)
var/amt = list_reagents[r_id]
add_reagent(r_id, amt, data)
-/datum/reagents/proc/remove_reagent(reagent, amount, safety)//Added a safety check for the trans_id_to
+/datum/reagents/proc/remove_reagent(reagent, amount, safety, ignore_pH = FALSE)//Added a safety check for the trans_id_to
if(isnull(amount))
amount = 0
@@ -653,6 +937,15 @@
for(var/A in cached_reagents)
var/datum/reagent/R = A
if (R.id == reagent)
+ if((total_volume - amount) <= 0)//Because this can result in 0, I don't want it to crash.
+ pH = 7
+ //In practice this is really confusing and players feel like it randomly melts their beakers, but I'm not sure how else to handle it. We'll see how it goes and I can remove this if it confuses people.
+ else if (ignore_pH == FALSE)
+ //if (((pH > R.pH) && (pH <= 7)) || ((pH < R.pH) && (pH >= 7)))
+ pH = (((pH - R.pH) / total_volume) * amount) + pH
+ if(istype(my_atom, /obj/item/reagent_containers/))
+ var/obj/item/reagent_containers/RC = my_atom
+ RC.pH_check()//checks beaker resilience)
//clamp the removal amount to be between current reagent amount
//and zero, to prevent removing more than the holder has stored
amount = CLAMP(amount, 0, R.volume)
diff --git a/code/modules/reagents/chemistry/machinery/chem_dispenser.dm b/code/modules/reagents/chemistry/machinery/chem_dispenser.dm
index fa9d60a219..bc998c6657 100644
--- a/code/modules/reagents/chemistry/machinery/chem_dispenser.dm
+++ b/code/modules/reagents/chemistry/machinery/chem_dispenser.dm
@@ -189,10 +189,16 @@
data["beakerCurrentVolume"] = beakerCurrentVolume
data["beakerMaxVolume"] = beaker.volume
data["beakerTransferAmounts"] = beaker.possible_transfer_amounts
+ data["beakerCurrentpH"] = beaker.reagents.pH
+ //pH accuracy
+ for(var/obj/item/stock_parts/capacitor/C in component_parts)
+ data["partRating"]= 10**(C.rating-1)
+
else
data["beakerCurrentVolume"] = null
data["beakerMaxVolume"] = null
data["beakerTransferAmounts"] = null
+ data["beakerCurrentpH"] = null
var/chemicals[0]
var/recipes[0]
diff --git a/code/modules/reagents/chemistry/machinery/chem_heater.dm b/code/modules/reagents/chemistry/machinery/chem_heater.dm
index a17b1e8190..436ee80e7b 100644
--- a/code/modules/reagents/chemistry/machinery/chem_heater.dm
+++ b/code/modules/reagents/chemistry/machinery/chem_heater.dm
@@ -97,13 +97,21 @@
data["isBeakerLoaded"] = beaker ? 1 : 0
data["currentTemp"] = beaker ? beaker.reagents.chem_temp : null
+ data["currentpH"] = beaker ? beaker.reagents.pH : null
data["beakerCurrentVolume"] = beaker ? beaker.reagents.total_volume : null
data["beakerMaxVolume"] = beaker ? beaker.volume : null
+ //purity and pH accuracy
+ for(var/obj/item/stock_parts/micro_laser/M in component_parts)
+ data["partRating"]= 10**(M.rating-1)
+ if(M.rating == 4)
+ data["showPurity"] = 1
+ else
+ data["showPurity"] = 0
var beakerContents[0]
if(beaker)
for(var/datum/reagent/R in beaker.reagents.reagent_list)
- beakerContents.Add(list(list("name" = R.name, "volume" = R.volume))) // list in a list because Byond merges the first list...
+ beakerContents.Add(list(list("name" = R.name, "volume" = R.volume, "purity" = R.purity))) // list in a list because Byond merges the first list...
data["beakerContents"] = beakerContents
return data
diff --git a/code/modules/reagents/chemistry/machinery/chem_master.dm b/code/modules/reagents/chemistry/machinery/chem_master.dm
index 1d13b44c7a..002c54be94 100644
--- a/code/modules/reagents/chemistry/machinery/chem_master.dm
+++ b/code/modules/reagents/chemistry/machinery/chem_master.dm
@@ -21,6 +21,7 @@
var/analyzeVars[0]
var/useramount = 30 // Last used amount
var/list/pillStyles
+ var/fermianalyze //Give more detail on fermireactions on analysis
/obj/machinery/chem_master/Initialize()
create_reagents(100)
@@ -170,6 +171,7 @@
data["condi"] = condi
data["screen"] = screen
data["analyzeVars"] = analyzeVars
+ data["fermianalyze"] = fermianalyze
data["chosenPillStyle"] = chosenPillStyle
data["isPillBottleLoaded"] = bottle ? 1 : 0
if(bottle)
@@ -211,11 +213,13 @@
var/id = params["id"]
var/amount = text2num(params["amount"])
if (amount > 0)
+ end_fermi_reaction()
beaker.reagents.trans_id_to(src, id, amount)
. = TRUE
else if (amount == -1) // -1 means custom amount
useramount = input("Enter the Amount you want to transfer:", name, useramount) as num|null
if (useramount > 0)
+ end_fermi_reaction()
beaker.reagents.trans_id_to(src, id, useramount)
. = TRUE
@@ -387,7 +391,14 @@
state = "Gas"
var/const/P = 3 //The number of seconds between life ticks
var/T = initial(R.metabolization_rate) * (60 / P)
- analyzeVars = list("name" = initial(R.name), "state" = state, "color" = initial(R.color), "description" = initial(R.description), "metaRate" = T, "overD" = initial(R.overdose_threshold), "addicD" = initial(R.addiction_threshold))
+ if(istype(R, /datum/reagent/fermi))
+ fermianalyze = TRUE
+ var/datum/chemical_reaction/Rcr = get_chemical_reaction(R.id)
+ var/pHpeakCache = (Rcr.OptimalpHMin + Rcr.OptimalpHMax)/2
+ analyzeVars = list("name" = initial(R.name), "state" = state, "color" = initial(R.color), "description" = initial(R.description), "metaRate" = T, "overD" = initial(R.overdose_threshold), "addicD" = initial(R.addiction_threshold), "purityF" = initial(R.purity), "inverseRatioF" = initial(R.InverseChemVal), "purityE" = initial(Rcr.PurityMin), "minTemp" = initial(Rcr.OptimalTempMin), "maxTemp" = initial(Rcr.OptimalTempMax), "eTemp" = initial(Rcr.ExplodeTemp), "pHpeak" = pHpeakCache)
+ else
+ fermianalyze = FALSE
+ analyzeVars = list("name" = initial(R.name), "state" = state, "color" = initial(R.color), "description" = initial(R.description), "metaRate" = T, "overD" = initial(R.overdose_threshold), "addicD" = initial(R.addiction_threshold))
screen = "analyze"
return
@@ -397,6 +408,9 @@
+/obj/machinery/chem_master/proc/end_fermi_reaction()//Ends any reactions upon moving.
+ if(beaker.reagents.fermiIsReacting)
+ beaker.reagents.fermiEnd()
/obj/machinery/chem_master/proc/isgoodnumber(num)
if(isnum(num))
diff --git a/code/modules/reagents/chemistry/reagents.dm b/code/modules/reagents/chemistry/reagents.dm
index 0e5c2b39a5..c21629ce8f 100644
--- a/code/modules/reagents/chemistry/reagents.dm
+++ b/code/modules/reagents/chemistry/reagents.dm
@@ -33,6 +33,16 @@
var/addiction_stage4_end = 40
var/overdosed = 0 // You fucked up and this is now triggering its overdose effects, purge that shit quick.
var/self_consuming = FALSE
+ //Fermichem vars:
+ var/purity = 1 //How pure a chemical is from 0 - 1.
+ var/addProc = FALSE //If the chemical should force an on_new() call
+ var/turf/loc = null //Should be the creation location!
+ var/pH = 7 //pH of the specific reagent, used for calculating the sum pH of a holder.
+ var/ImpureChem = "fermiTox"// What chemical is metabolised with an inpure reaction
+ var/InverseChemVal = 0.25 // If the impurity is below 0.5, replace ALL of the chem with InverseChem upon metabolising
+ var/InverseChem = "fermiTox"// What chem is metabolised when purity is below InverseChemVal, this shouldn't be made, but if it does, well, I guess I'll know about it.
+ var/DoNotSplit = FALSE // If impurity is handled within the main chem itself
+ var/OnMobMergeCheck = FALSE //Call on_mob_life proc when reagents are merging.
var/metabolizing = FALSE
var/invisible = FALSE //Set to true if it doesn't appear on handheld health analyzers.
@@ -59,7 +69,8 @@
/datum/reagent/proc/on_mob_life(mob/living/carbon/M)
current_cycle++
- holder.remove_reagent(src.id, metabolization_rate * M.metabolism_efficiency) //By default it slowly disappears.
+ if(holder)
+ holder.remove_reagent(src.id, metabolization_rate * M.metabolism_efficiency) //By default it slowly disappears.
return
// Called when this reagent is first added to a mob
diff --git a/code/modules/reagents/chemistry/reagents/alcohol_reagents.dm b/code/modules/reagents/chemistry/reagents/alcohol_reagents.dm
index a46a4d61e4..79b6f96781 100644
--- a/code/modules/reagents/chemistry/reagents/alcohol_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/alcohol_reagents.dm
@@ -14,6 +14,7 @@
nutriment_factor = 0
taste_description = "alcohol"
var/boozepwr = 65 //Higher numbers equal higher hardness, higher hardness equals more intense alcohol poisoning
+ pH = 7.33
/*
Boozepwr Chart
@@ -86,6 +87,8 @@ All effects don't start immediately, but rather get worse over time; the rate is
taste_description = "piss water"
glass_name = "glass of beer"
glass_desc = "A freezing pint of beer."
+ pH = 4
+
/datum/reagent/consumable/ethanol/beer/light
name = "Light Beer"
@@ -95,6 +98,7 @@ All effects don't start immediately, but rather get worse over time; the rate is
taste_description = "dish water"
glass_name = "glass of light beer"
glass_desc = "A freezing pint of watery light beer."
+ pH = 5
/datum/reagent/consumable/ethanol/beer/green
name = "Green Beer"
@@ -105,6 +109,7 @@ All effects don't start immediately, but rather get worse over time; the rate is
glass_icon_state = "greenbeerglass"
glass_name = "glass of green beer"
glass_desc = "A freezing pint of green beer. Festive."
+ pH = 6
/datum/reagent/consumable/ethanol/beer/green/on_mob_life(mob/living/carbon/M)
if(M.color != color)
@@ -124,6 +129,8 @@ All effects don't start immediately, but rather get worse over time; the rate is
glass_name = "glass of RR coffee liquor"
glass_desc = "DAMN, THIS THING LOOKS ROBUST!"
shot_glass_icon_state = "shotglasscream"
+ pH = 6
+
/datum/reagent/consumable/ethanol/kahlua/on_mob_life(mob/living/carbon/M)
M.dizziness = max(0,M.dizziness-5)
@@ -145,6 +152,7 @@ All effects don't start immediately, but rather get worse over time; the rate is
glass_name = "glass of whiskey"
glass_desc = "The silky, smokey whiskey goodness inside the glass makes the drink look very classy."
shot_glass_icon_state = "shotglassbrown"
+ pH = 4.5
/datum/reagent/consumable/ethanol/thirteenloko
name = "Thirteen Loko"
@@ -161,6 +169,7 @@ All effects don't start immediately, but rather get worse over time; the rate is
glass_name = "glass of Thirteen Loko"
glass_desc = "This is a glass of Thirteen Loko, it appears to be of the highest quality. The drink, not the glass."
+
/datum/reagent/consumable/ethanol/thirteenloko/on_mob_life(mob/living/carbon/M)
M.drowsyness = max(0,M.drowsyness-7)
M.AdjustSleeping(-40)
@@ -221,6 +230,7 @@ All effects don't start immediately, but rather get worse over time; the rate is
glass_name = "glass of vodka"
glass_desc = "The glass contain wodka. Xynta."
shot_glass_icon_state = "shotglassclear"
+ pH = 8.1
/datum/reagent/consumable/ethanol/vodka/on_mob_life(mob/living/carbon/M)
M.radiation = max(M.radiation-2,0)
@@ -255,6 +265,7 @@ All effects don't start immediately, but rather get worse over time; the rate is
glass_icon_state = "threemileislandglass"
glass_name = "Three Mile Island Ice Tea"
glass_desc = "A glass of this is sure to prevent a meltdown."
+ pH = 3.5
/datum/reagent/consumable/ethanol/threemileisland/on_mob_life(mob/living/carbon/M)
M.set_drugginess(50)
@@ -270,6 +281,7 @@ All effects don't start immediately, but rather get worse over time; the rate is
glass_icon_state = "ginvodkaglass"
glass_name = "glass of gin"
glass_desc = "A crystal clear glass of Griffeater gin."
+ pH = 6.9
/datum/reagent/consumable/ethanol/rum
name = "Rum"
@@ -282,6 +294,7 @@ All effects don't start immediately, but rather get worse over time; the rate is
glass_name = "glass of rum"
glass_desc = "Now you want to Pray for a pirate suit, don't you?"
shot_glass_icon_state = "shotglassbrown"
+ pH = 6.5
/datum/reagent/consumable/ethanol/tequila
name = "Tequila"
@@ -294,6 +307,7 @@ All effects don't start immediately, but rather get worse over time; the rate is
glass_name = "glass of tequila"
glass_desc = "Now all that's missing is the weird colored shades!"
shot_glass_icon_state = "shotglassgold"
+ pH = 4
/datum/reagent/consumable/ethanol/vermouth
name = "Vermouth"
@@ -306,6 +320,7 @@ All effects don't start immediately, but rather get worse over time; the rate is
glass_name = "glass of vermouth"
glass_desc = "You wonder why you're even drinking this straight."
shot_glass_icon_state = "shotglassclear"
+ pH = 3.25
/datum/reagent/consumable/ethanol/wine
name = "Wine"
@@ -318,6 +333,7 @@ All effects don't start immediately, but rather get worse over time; the rate is
glass_name = "glass of wine"
glass_desc = "A very classy looking drink."
shot_glass_icon_state = "shotglassred"
+ pH = 3.45
/datum/reagent/consumable/ethanol/lizardwine
name = "Lizard wine"
@@ -327,6 +343,7 @@ All effects don't start immediately, but rather get worse over time; the rate is
boozepwr = 45
quality = DRINK_FANTASTIC
taste_description = "scaley sweetness"
+ pH = 3
/datum/reagent/consumable/ethanol/grappa
name = "Grappa"
@@ -338,6 +355,7 @@ All effects don't start immediately, but rather get worse over time; the rate is
glass_icon_state = "grappa"
glass_name = "glass of grappa"
glass_desc = "A fine drink originally made to prevent waste by using the leftovers from winemaking."
+ pH = 3.5
/datum/reagent/consumable/ethanol/cognac
name = "Cognac"
@@ -350,6 +368,7 @@ All effects don't start immediately, but rather get worse over time; the rate is
glass_name = "glass of cognac"
glass_desc = "Damn, you feel like some kind of French aristocrat just by holding this."
shot_glass_icon_state = "shotglassbrown"
+ pH = 3.5
/datum/reagent/consumable/ethanol/absinthe
name = "Absinthe"
@@ -395,6 +414,7 @@ All effects don't start immediately, but rather get worse over time; the rate is
glass_icon_state = "aleglass"
glass_name = "glass of ale"
glass_desc = "A freezing pint of delicious Ale."
+ pH = 4.5
/datum/reagent/consumable/ethanol/goldschlager
name = "Goldschlager"
@@ -421,6 +441,7 @@ All effects don't start immediately, but rather get worse over time; the rate is
glass_name = "glass of patron"
glass_desc = "Drinking patron in the bar, with all the subpar ladies."
shot_glass_icon_state = "shotglassclear"
+ pH = 4.5
/datum/reagent/consumable/ethanol/gintonic
name = "Gin and Tonic"
@@ -433,6 +454,7 @@ All effects don't start immediately, but rather get worse over time; the rate is
glass_icon_state = "gintonicglass"
glass_name = "Gin and Tonic"
glass_desc = "A mild but still great cocktail. Drink up, like a true Englishman."
+ pH = 3
/datum/reagent/consumable/ethanol/rum_coke
name = "Rum and Coke"
@@ -445,6 +467,7 @@ All effects don't start immediately, but rather get worse over time; the rate is
glass_icon_state = "whiskeycolaglass"
glass_name = "Rum and Coke"
glass_desc = "The classic go-to of space-fratboys."
+ pH = 4
/datum/reagent/consumable/ethanol/cuba_libre
name = "Cuba Libre"
@@ -458,6 +481,7 @@ All effects don't start immediately, but rather get worse over time; the rate is
glass_name = "Cuba Libre"
glass_desc = "A classic mix of rum, cola, and lime. A favorite of revolutionaries everywhere!"
+
/datum/reagent/consumable/ethanol/cuba_libre/on_mob_life(mob/living/carbon/M)
if(M.mind && M.mind.has_antag_datum(/datum/antagonist/rev)) //Cuba Libre, the traditional drink of revolutions! Heals revolutionaries.
M.adjustBruteLoss(-1, 0)
@@ -504,6 +528,7 @@ All effects don't start immediately, but rather get worse over time; the rate is
glass_name = "Vodka martini"
glass_desc ="A bastardisation of the classic martini. Still great."
+
/datum/reagent/consumable/ethanol/white_russian
name = "White Russian"
id = "whiterussian"
@@ -557,8 +582,8 @@ All effects don't start immediately, but rather get worse over time; the rate is
glass_desc = "Tomato juice, mixed with Vodka and a lil' bit of lime. Tastes like liquid murder."
/datum/reagent/consumable/ethanol/bloody_mary/on_mob_life(mob/living/carbon/C)
- if(C.blood_volume < BLOOD_VOLUME_NORMAL)
- C.blood_volume = min(BLOOD_VOLUME_NORMAL, C.blood_volume + 3) //Bloody Mary quickly restores blood loss.
+ if(C.blood_volume < (BLOOD_VOLUME_NORMAL*C.blood_ratio))
+ C.blood_volume = min((BLOOD_VOLUME_NORMAL*C.blood_ratio), C.blood_volume + 3) //Bloody Mary quickly restores blood loss.
..()
/datum/reagent/consumable/ethanol/brave_bull
@@ -643,6 +668,7 @@ All effects don't start immediately, but rather get worse over time; the rate is
glass_icon_state = "beepskysmashglass"
glass_name = "Beepsky Smash"
glass_desc = "Heavy, hot and strong. Just like the Iron fist of the LAW."
+ pH = 2
overdose_threshold = 40
var/datum/brain_trauma/special/beepsky/B
@@ -1578,6 +1604,7 @@ All effects don't start immediately, but rather get worse over time; the rate is
glass_name = "Bastion Bourbon"
glass_desc = "If you're feeling low, count on the buttery flavor of our own bastion bourbon."
shot_glass_icon_state = "shotglassgreen"
+ pH = 4
/datum/reagent/consumable/ethanol/bastion_bourbon/on_mob_metabolize(mob/living/L)
var/heal_points = 10
@@ -2106,6 +2133,7 @@ All effects don't start immediately, but rather get worse over time; the rate is
can_synth = FALSE
var/list/names = list("null fruit" = 1) //Names of the fruits used. Associative list where name is key, value is the percentage of that fruit.
var/list/tastes = list("bad coding" = 1) //List of tastes. See above.
+ pH = 4
/datum/reagent/consumable/ethanol/fruit_wine/on_new(list/data)
names = data["names"]
diff --git a/code/modules/reagents/chemistry/reagents/drink_reagents.dm b/code/modules/reagents/chemistry/reagents/drink_reagents.dm
index 8aa555944a..bfefc073f8 100644
--- a/code/modules/reagents/chemistry/reagents/drink_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/drink_reagents.dm
@@ -168,6 +168,7 @@
/datum/reagent/consumable/laughter/on_mob_life(mob/living/carbon/M)
M.emote("laugh")
+ SEND_SIGNAL(M, COMSIG_ADD_MOOD_EVENT, "chemical_laughter", /datum/mood_event/chemical_laughter)
..()
/datum/reagent/consumable/superlaughter
@@ -182,6 +183,7 @@
if(prob(30))
M.visible_message("[M] bursts out into a fit of uncontrollable laughter!", "You burst out in a fit of uncontrollable laughter!")
M.Stun(5)
+ SEND_SIGNAL(M, COMSIG_ADD_MOOD_EVENT, "chemical_laughter", /datum/mood_event/chemical_superlaughter)
..()
/datum/reagent/consumable/potato_juice
diff --git a/code/modules/reagents/chemistry/reagents/drug_reagents.dm b/code/modules/reagents/chemistry/reagents/drug_reagents.dm
index a4586dd997..5b06710472 100644
--- a/code/modules/reagents/chemistry/reagents/drug_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/drug_reagents.dm
@@ -15,6 +15,7 @@
description = "An illegal chemical compound used as drug."
color = "#60A584" // rgb: 96, 165, 132
overdose_threshold = 30
+ pH = 9
/datum/reagent/drug/space_drugs/on_mob_life(mob/living/carbon/M)
M.set_drugginess(15)
@@ -44,6 +45,7 @@
addiction_threshold = 30
taste_description = "smoke"
trippy = FALSE
+ pH = 8
/datum/reagent/drug/nicotine/on_mob_life(mob/living/carbon/M)
if(prob(1))
@@ -65,6 +67,7 @@
color = "#FA00C8"
overdose_threshold = 20
addiction_threshold = 10
+ pH = 10
/datum/reagent/drug/crank/on_mob_life(mob/living/carbon/M)
if(prob(5))
@@ -112,6 +115,7 @@
color = "#0064B4"
overdose_threshold = 20
addiction_threshold = 15
+ pH = 9
/datum/reagent/drug/krokodil/on_mob_life(mob/living/carbon/M)
@@ -167,6 +171,7 @@
var/brain_damage = TRUE
var/jitter = TRUE
var/confusion = TRUE
+ pH = 5
/datum/reagent/drug/methamphetamine/on_mob_metabolize(mob/living/L)
..()
@@ -261,6 +266,7 @@
addiction_threshold = 10
taste_description = "salt" // because they're bathsalts?
var/datum/brain_trauma/special/psychotic_brawling/bath_salts/rage
+ pH = 8.2
/datum/reagent/drug/bath_salts/on_mob_metabolize(mob/living/L)
..()
@@ -357,6 +363,7 @@
description = "Amps you up and gets you going, fixes all stamina damage you might have but can cause toxin and oxygen damage."
reagent_state = LIQUID
color = "#78FFF0"
+ pH = 9.2
/datum/reagent/drug/aranesp/on_mob_life(mob/living/carbon/M)
var/high_message = pick("You feel amped up.", "You feel ready.", "You feel like you can push it to the limit.")
@@ -370,6 +377,84 @@
..()
. = 1
+/datum/reagent/drug/happiness
+ name = "Happiness"
+ id = "happiness"
+ description = "Fills you with ecstasic numbness and causes minor brain damage. Highly addictive. If overdosed causes sudden mood swings."
+ reagent_state = LIQUID
+ color = "#FFF378"
+ addiction_threshold = 10
+ overdose_threshold = 20
+ pH = 10.5
+
+/datum/reagent/drug/happiness/on_mob_add(mob/living/L)
+ ..()
+ ADD_TRAIT(L, TRAIT_FEARLESS, id)
+ SEND_SIGNAL(L, COMSIG_ADD_MOOD_EVENT, "happiness_drug", /datum/mood_event/happiness_drug)
+
+/datum/reagent/drug/happiness/on_mob_delete(mob/living/L)
+ REMOVE_TRAIT(L, TRAIT_FEARLESS, id)
+ SEND_SIGNAL(L, COMSIG_CLEAR_MOOD_EVENT, "happiness_drug")
+ ..()
+
+/datum/reagent/drug/happiness/on_mob_life(mob/living/carbon/M)
+ M.jitteriness = 0
+ M.confused = 0
+ M.disgust = 0
+ M.adjustBrainLoss(0.2)
+ ..()
+ . = 1
+
+/datum/reagent/drug/happiness/overdose_process(mob/living/M)
+ if(prob(30))
+ var/reaction = rand(1,3)
+ switch(reaction)
+ if(1)
+ M.emote("laugh")
+ SEND_SIGNAL(M, COMSIG_ADD_MOOD_EVENT, "happiness_drug", /datum/mood_event/happiness_drug_good_od)
+ if(2)
+ M.emote("sway")
+ M.Dizzy(25)
+ if(3)
+ M.emote("frown")
+ SEND_SIGNAL(M, COMSIG_ADD_MOOD_EVENT, "happiness_drug", /datum/mood_event/happiness_drug_bad_od)
+ M.adjustBrainLoss(0.5)
+ ..()
+ . = 1
+
+/datum/reagent/drug/happiness/addiction_act_stage1(mob/living/M)// all work and no play makes jack a dull boy
+ GET_COMPONENT_FROM(mood, /datum/component/mood, M)
+ mood.setSanity(min(mood.sanity, SANITY_DISTURBED))
+ M.Jitter(5)
+ if(prob(20))
+ M.emote(pick("twitch","laugh","frown"))
+ ..()
+
+/datum/reagent/drug/happiness/addiction_act_stage2(mob/living/M)
+ GET_COMPONENT_FROM(mood, /datum/component/mood, M)
+ mood.setSanity(min(mood.sanity, SANITY_UNSTABLE))
+ M.Jitter(10)
+ if(prob(30))
+ M.emote(pick("twitch","laugh","frown"))
+ ..()
+
+/datum/reagent/drug/happiness/addiction_act_stage3(mob/living/M)
+ GET_COMPONENT_FROM(mood, /datum/component/mood, M)
+ mood.setSanity(min(mood.sanity, SANITY_CRAZY))
+ M.Jitter(15)
+ if(prob(40))
+ M.emote(pick("twitch","laugh","frown"))
+ ..()
+
+/datum/reagent/drug/happiness/addiction_act_stage4(mob/living/carbon/human/M)
+ GET_COMPONENT_FROM(mood, /datum/component/mood, M)
+ mood.setSanity(SANITY_INSANE)
+ M.Jitter(20)
+ if(prob(50))
+ M.emote(pick("twitch","laugh","frown"))
+ ..()
+ . = 1
+
/datum/reagent/drug/skooma
name = "Skooma"
id = "skooma"
@@ -380,6 +465,7 @@
addiction_threshold = 1
addiction_stage3_end = 40
addiction_stage4_end = 240
+ pH = 12.5
/datum/reagent/drug/skooma/on_mob_metabolize(mob/living/L)
. = ..()
@@ -440,4 +526,3 @@
if(prob(40))
M.emote(pick("twitch","drool","moan"))
..()
-
diff --git a/code/modules/reagents/chemistry/reagents/food_reagents.dm b/code/modules/reagents/chemistry/reagents/food_reagents.dm
index 4a835d0839..19aeaeb25e 100644
--- a/code/modules/reagents/chemistry/reagents/food_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/food_reagents.dm
@@ -230,6 +230,7 @@
description = "A special oil that noticably chills the body. Extracted from Icepeppers and slimes."
color = "#8BA6E9" // rgb: 139, 166, 233
taste_description = "mint"
+ pH = 13 //HMM! I wonder
/datum/reagent/consumable/frostoil/on_mob_life(mob/living/carbon/M)
var/cooling = 0
@@ -275,6 +276,7 @@
description = "A chemical agent used for self-defense and in police work."
color = "#B31008" // rgb: 179, 16, 8
taste_description = "scorching agony"
+ pH = 7.4
/datum/reagent/consumable/condensedcapsaicin/reaction_mob(mob/living/M, method=TOUCH, reac_volume)
if(!ishuman(M) && !ismonkey(M))
@@ -402,6 +404,7 @@
color = "#E700E7" // rgb: 231, 0, 231
metabolization_rate = 0.2 * REAGENTS_METABOLISM
taste_description = "mushroom"
+ pH = 11
/datum/reagent/drug/mushroomhallucinogen/on_mob_life(mob/living/carbon/M)
M.slurring = max(M.slurring,50)
@@ -618,6 +621,7 @@
description = "A blinding substance extracted from certain onions."
color = "#c0c9a0"
taste_description = "bitterness"
+ pH = 5
/datum/reagent/consumable/tearjuice/reaction_mob(mob/living/M, method=TOUCH, reac_volume)
if(!istype(M))
@@ -672,6 +676,7 @@
description = "An ichor, derived from a certain mushroom, makes for a bad time."
color = "#1d043d"
taste_description = "bitter mushroom"
+ pH = 12
/datum/reagent/consumable/entpoly/on_mob_life(mob/living/carbon/M)
if(current_cycle >= 10)
@@ -692,6 +697,7 @@
description = "A stimulating ichor which causes luminescent fungi to grow on the skin. "
color = "#b5a213"
taste_description = "tingling mushroom"
+ pH = 11.2
/datum/reagent/consumable/tinlux/reaction_mob(mob/living/M)
M.set_light(2)
@@ -706,6 +712,7 @@
color = "#d3a308"
nutriment_factor = 3 * REAGENTS_METABOLISM
taste_description = "fruity mushroom"
+ pH = 10.4
/datum/reagent/consumable/vitfro/on_mob_life(mob/living/carbon/M)
if(prob(80))
@@ -721,6 +728,7 @@
nutriment_factor = 5 * REAGENTS_METABOLISM
color = "#eef442" // rgb: 238, 244, 66
taste_description = "mournful honking"
+ pH = 9.2
/datum/reagent/consumable/astrotame
name = "Astrotame"
@@ -750,3 +758,4 @@
quality = FOOD_AMAZING
taste_mult = 100
can_synth = FALSE
+ pH = 6.1
diff --git a/code/modules/reagents/chemistry/reagents/medicine_reagents.dm b/code/modules/reagents/chemistry/reagents/medicine_reagents.dm
index fe70db250d..eb7438aa20 100644
--- a/code/modules/reagents/chemistry/reagents/medicine_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/medicine_reagents.dm
@@ -18,6 +18,7 @@
name = "Leporazine"
id = "leporazine"
description = "Leporazine will effectively regulate a patient's body temperature, ensuring it never leaves safe levels."
+ pH = 8.4
color = "#82b8aa"
/datum/reagent/medicine/leporazine/on_mob_life(mob/living/carbon/M)
@@ -60,6 +61,9 @@
M.SetSleeping(0, 0)
M.jitteriness = 0
M.cure_all_traumas(TRAUMA_RESILIENCE_MAGIC)
+ if(M.blood_volume < (BLOOD_VOLUME_NORMAL*M.blood_ratio))
+ M.blood_volume = (BLOOD_VOLUME_NORMAL*M.blood_ratio)
+
for(var/thing in M.diseases)
var/datum/disease/D = thing
if(D.severity == DISEASE_SEVERITY_POSITIVE)
@@ -79,6 +83,7 @@
id = "synaptizine"
description = "Increases resistance to stuns as well as reducing drowsiness and hallucinations."
color = "#FF00FF"
+ pH = 4
/datum/reagent/medicine/synaptizine/on_mob_life(mob/living/carbon/M)
M.drowsyness = max(M.drowsyness-5, 0)
@@ -98,6 +103,7 @@
id = "synaphydramine"
description = "Reduces drowsiness, hallucinations, and Histamine from body."
color = "#EC536D" // rgb: 236, 83, 109
+ pH = 5.2
/datum/reagent/medicine/synaphydramine/on_mob_life(mob/living/carbon/M)
M.drowsyness = max(M.drowsyness-5, 0)
@@ -116,6 +122,7 @@
id = "inacusiate"
description = "Instantly restores all hearing to the patient, but does not cure deafness."
color = "#6600FF" // rgb: 100, 165, 255
+ pH = 2
/datum/reagent/medicine/inacusiate/on_mob_life(mob/living/carbon/M)
M.restoreEars()
@@ -127,6 +134,7 @@
description = "A chemical mixture with almost magical healing powers. Its main limitation is that the patient's body temperature must be under 270K for it to metabolise correctly."
color = "#0000C8"
taste_description = "sludge"
+ pH = 11
/datum/reagent/medicine/cryoxadone/on_mob_life(mob/living/carbon/M)
var/power = -0.00003 * (M.bodytemperature ** 2) + 3
@@ -148,6 +156,7 @@
color = "#0000C8"
taste_description = "muscle"
metabolization_rate = 1.5 * REAGENTS_METABOLISM
+ pH = 13
/datum/reagent/medicine/clonexadone/on_mob_life(mob/living/carbon/M)
if(M.bodytemperature < T0C)
@@ -163,6 +172,7 @@
description = "A mixture of cryoxadone and slime jelly, that apparently inverses the requirement for its activation."
color = "#f7832a"
taste_description = "spicy jelly"
+ pH = 12
/datum/reagent/medicine/pyroxadone/on_mob_life(mob/living/carbon/M)
if(M.bodytemperature > BODYTEMP_HEAT_DAMAGE_LIMIT)
@@ -194,6 +204,7 @@
color = "#669900" // rgb: 102, 153, 0
overdose_threshold = 30
taste_description = "fish"
+ pH = 12.2
/datum/reagent/medicine/rezadone/on_mob_life(mob/living/carbon/M)
M.setCloneLoss(0) //Rezadone is almost never used in favor of cryoxadone. Hopefully this will change that.
@@ -215,6 +226,7 @@
description = "Spaceacillin will prevent a patient from conventionally spreading any diseases they are currently infected with."
color = "#f2f2f2"
metabolization_rate = 0.1 * REAGENTS_METABOLISM
+ pH = 8.1
//Goon Chems. Ported mainly from Goonstation. Easily mixable (or not so easily) and provide a variety of effects.
/datum/reagent/medicine/silver_sulfadiazine
@@ -222,6 +234,7 @@
id = "silver_sulfadiazine"
description = "If used in touch-based applications, immediately restores burn wounds as well as restoring more over time. If ingested through other means, deals minor toxin damage."
reagent_state = LIQUID
+ pH = 7.2
color = "#ffeac9"
/datum/reagent/medicine/silver_sulfadiazine/reaction_mob(mob/living/M, method=TOUCH, reac_volume, show_message = 1)
@@ -235,6 +248,7 @@
if(show_message)
to_chat(M, "You feel your burns healing! It stings like hell!")
M.emote("scream")
+ SEND_SIGNAL(M, COMSIG_ADD_MOOD_EVENT, "painful_medicine", /datum/mood_event/painful_medicine)
..()
/datum/reagent/medicine/silver_sulfadiazine/on_mob_life(mob/living/carbon/M)
@@ -250,6 +264,7 @@
color = "#f7ffa5"
metabolization_rate = 0.5 * REAGENTS_METABOLISM
overdose_threshold = 25
+ pH = 10.7
/datum/reagent/medicine/oxandrolone/on_mob_life(mob/living/carbon/M)
if(M.getFireLoss() > 50)
@@ -271,6 +286,7 @@
description = "If used in touch-based applications, immediately restores bruising as well as restoring more over time. If ingested through other means, deals minor toxin damage."
reagent_state = LIQUID
color = "#FF9696"
+ pH = 6.7
/datum/reagent/medicine/styptic_powder/reaction_mob(mob/living/M, method=TOUCH, reac_volume, show_message = 1)
if(iscarbon(M) && M.stat != DEAD)
@@ -283,6 +299,7 @@
if(show_message)
to_chat(M, "You feel your bruises healing! It stings like hell!")
M.emote("scream")
+ SEND_SIGNAL(M, COMSIG_ADD_MOOD_EVENT, "painful_medicine", /datum/mood_event/painful_medicine)
..()
@@ -302,6 +319,7 @@
taste_description = "sweetness and salt"
var/last_added = 0
var/maximum_reachable = BLOOD_VOLUME_NORMAL - 10 //So that normal blood regeneration can continue with salglu active
+ pH = 5.5
/datum/reagent/medicine/salglu_solution/on_mob_life(mob/living/carbon/M)
if(last_added)
@@ -340,6 +358,7 @@
reagent_state = LIQUID
color = "#6D6374"
metabolization_rate = 0.4 * REAGENTS_METABOLISM
+ pH = 2.6
/datum/reagent/medicine/mine_salve/on_mob_life(mob/living/carbon/C)
C.hal_screwyhud = SCREWYHUD_HEALTHY
@@ -378,6 +397,7 @@
description = "Has a 100% chance of instantly healing brute and burn damage. One unit of the chemical will heal one point of damage. Touch application only."
reagent_state = LIQUID
color = "#FFEBEB"
+ pH = 11.5
/datum/reagent/medicine/synthflesh/reaction_mob(mob/living/M, method=TOUCH, reac_volume,show_message = 1)
if(iscarbon(M))
@@ -388,6 +408,7 @@
M.adjustFireLoss(-1.25 * reac_volume)
if(show_message)
to_chat(M, "You feel your burns and bruises healing! It stings like hell!")
+ SEND_SIGNAL(M, COMSIG_ADD_MOOD_EVENT, "painful_medicine", /datum/mood_event/painful_medicine)
..()
/datum/reagent/medicine/charcoal
@@ -398,6 +419,7 @@
color = "#000000"
metabolization_rate = 0.5 * REAGENTS_METABOLISM
taste_description = "ash"
+ pH = 5
/datum/reagent/medicine/charcoal/on_mob_life(mob/living/carbon/M)
M.adjustToxLoss(-2*REM, 0)
@@ -415,6 +437,7 @@
color = "#DCDCDC"
metabolization_rate = 0.25 * REAGENTS_METABOLISM
overdose_threshold = 30
+ pH = 2
/datum/reagent/medicine/omnizine/on_mob_life(mob/living/carbon/M)
M.adjustToxLoss(-0.5*REM, 0)
@@ -440,6 +463,7 @@
color = "#19C832"
metabolization_rate = 0.5 * REAGENTS_METABOLISM
taste_description = "acid"
+ pH = 1.5
/datum/reagent/medicine/calomel/on_mob_life(mob/living/carbon/M)
for(var/datum/reagent/R in M.reagents.reagent_list)
@@ -457,6 +481,7 @@
reagent_state = LIQUID
color = "#14FF3C"
metabolization_rate = 2 * REAGENTS_METABOLISM
+ pH = 12 //It's a reducing agent
/datum/reagent/medicine/potass_iodide/on_mob_life(mob/living/carbon/M)
if(M.radiation > 0)
@@ -470,6 +495,7 @@
reagent_state = LIQUID
color = "#003153" // RGB 0, 49, 83
metabolization_rate = 0.5 * REAGENTS_METABOLISM
+ pH = 8.9
/datum/reagent/medicine/prussian_blue/on_mob_life(mob/living/carbon/M)
if(M.radiation > 0)
@@ -483,6 +509,7 @@
reagent_state = LIQUID
color = "#E6FFF0"
metabolization_rate = 0.5 * REAGENTS_METABOLISM
+ pH = 1 //One of the best buffers, NEVERMIND!
var/healtoxinlover = FALSE
/datum/reagent/medicine/pen_acid/on_mob_life(mob/living/carbon/M)
@@ -500,6 +527,7 @@
description = "Reduces massive amounts of radiation and toxin damage while purging other chemicals from the body. Slimepeople friendly!"
color = "#91D865"
healtoxinlover = TRUE
+ pH = 12//invert
/datum/reagent/medicine/sal_acid
name = "Salicyclic Acid"
@@ -509,6 +537,7 @@
color = "#D2D2D2"
metabolization_rate = 0.5 * REAGENTS_METABOLISM
overdose_threshold = 25
+ pH = 2.1
/datum/reagent/medicine/sal_acid/on_mob_life(mob/living/carbon/M)
@@ -532,6 +561,7 @@
reagent_state = LIQUID
color = "#00FFFF"
metabolization_rate = 0.25 * REAGENTS_METABOLISM
+ pH = 2
/datum/reagent/medicine/salbutamol/on_mob_life(mob/living/carbon/M)
M.adjustOxyLoss(-3*REM, 0)
@@ -547,6 +577,7 @@
reagent_state = LIQUID
color = "#FF6464"
metabolization_rate = 0.25 * REAGENTS_METABOLISM
+ pH = 11
/datum/reagent/medicine/perfluorodecalin/on_mob_life(mob/living/carbon/human/M)
M.adjustOxyLoss(-12*REM, 0)
@@ -566,6 +597,7 @@
metabolization_rate = 0.5 * REAGENTS_METABOLISM
overdose_threshold = 45
addiction_threshold = 30
+ pH = 12
/datum/reagent/medicine/ephedrine/on_mob_life(mob/living/carbon/M)
M.AdjustStun(-20, 0)
@@ -620,6 +652,7 @@
reagent_state = LIQUID
color = "#64FFE6"
metabolization_rate = 0.5 * REAGENTS_METABOLISM
+ pH = 11.5
/datum/reagent/medicine/diphenhydramine/on_mob_life(mob/living/carbon/M)
if(prob(10))
@@ -637,6 +670,7 @@
metabolization_rate = 0.5 * REAGENTS_METABOLISM
overdose_threshold = 30
addiction_threshold = 25
+ pH = 8.96
/datum/reagent/medicine/morphine/on_mob_metabolize(mob/living/L)
..()
@@ -705,6 +739,7 @@
color = "#FFFFFF"
metabolization_rate = 0.25 * REAGENTS_METABOLISM
taste_description = "dull toxin"
+ pH = 10
/datum/reagent/medicine/oculine/on_mob_life(mob/living/carbon/M)
var/obj/item/organ/eyes/eyes = M.getorganslot(ORGAN_SLOT_EYES)
@@ -736,6 +771,7 @@
color = "#000000"
metabolization_rate = 0.25 * REAGENTS_METABOLISM
overdose_threshold = 35
+ pH = 12
/datum/reagent/medicine/atropine/on_mob_life(mob/living/carbon/M)
if(M.health < 0)
@@ -765,6 +801,7 @@
color = "#D2FFFA"
metabolization_rate = 0.25 * REAGENTS_METABOLISM
overdose_threshold = 30
+ pH = 10.2
/datum/reagent/medicine/epinephrine/on_mob_life(mob/living/carbon/M)
if(M.health < 0)
@@ -801,6 +838,7 @@
color = "#A0E85E"
metabolization_rate = 0.5 * REAGENTS_METABOLISM
taste_description = "magnets"
+ pH = 0
/datum/reagent/medicine/strange_reagent/reaction_mob(mob/living/carbon/human/M, method=TOUCH, reac_volume)
if(M.stat == DEAD)
@@ -834,6 +872,7 @@
id = "mannitol"
description = "Efficiently restores brain damage."
color = "#DCDCFF"
+ pH = 10.4
/datum/reagent/medicine/mannitol/on_mob_life(mob/living/carbon/C)
C.adjustBrainLoss(-2*REM)
@@ -841,12 +880,26 @@
C.cure_trauma_type(resilience = TRAUMA_RESILIENCE_BASIC)
..()
+/datum/reagent/medicine/neurine
+ name = "Neurine"
+ id = "neurine"
+ description = "Reacts with neural tissue, helping reform damaged connections. Can cure minor traumas."
+ color = "#EEFF8F"
+
+/datum/reagent/medicine/neurine/on_mob_life(mob/living/carbon/C)
+ if(holder.has_reagent("neurotoxin"))
+ holder.remove_reagent("neurotoxin", 5)
+ if(prob(15))
+ C.cure_trauma_type(resilience = TRAUMA_RESILIENCE_BASIC)
+ ..()
+
/datum/reagent/medicine/mutadone
name = "Mutadone"
id = "mutadone"
description = "Removes jitteriness and restores genetic defects."
color = "#5096C8"
taste_description = "acid"
+ pH = 2
/datum/reagent/medicine/mutadone/on_mob_life(mob/living/carbon/M)
M.jitteriness = 0
@@ -861,6 +914,7 @@
description = "Purges alcoholic substance from the patient's body and eliminates its side effects."
color = "#00B4C8"
taste_description = "raw egg"
+ pH = 4
/datum/reagent/medicine/antihol/on_mob_life(mob/living/carbon/M)
M.dizziness = 0
@@ -882,6 +936,7 @@
color = "#78008C"
metabolization_rate = 0.5 * REAGENTS_METABOLISM
overdose_threshold = 60
+ pH = 8.7
/datum/reagent/medicine/stimulants/on_mob_metabolize(mob/living/L)
..()
@@ -919,6 +974,7 @@
reagent_state = LIQUID
color = "#FFFFF0"
metabolization_rate = 0.5 * REAGENTS_METABOLISM
+ pH = 6.7
/datum/reagent/medicine/insulin/on_mob_life(mob/living/carbon/M)
if(M.AdjustSleeping(-20, FALSE))
@@ -934,6 +990,7 @@
reagent_state = LIQUID
color = "#fc2626"
overdose_threshold = 30
+ pH = 5
/datum/reagent/medicine/bicaridine/on_mob_life(mob/living/carbon/M)
M.adjustBruteLoss(-2*REM, 0)
@@ -952,6 +1009,7 @@
reagent_state = LIQUID
color = "#13d2f0"
overdose_threshold = 30
+ pH = 9.7
/datum/reagent/medicine/dexalin/on_mob_life(mob/living/carbon/M)
M.adjustOxyLoss(-2*REM, 0)
@@ -970,6 +1028,7 @@
reagent_state = LIQUID
color = "#ffc400"
overdose_threshold = 30
+ pH = 9
/datum/reagent/medicine/kelotane/on_mob_life(mob/living/carbon/M)
M.adjustFireLoss(-2*REM, 0)
@@ -989,6 +1048,7 @@
color = "#6aff00"
overdose_threshold = 30
taste_description = "a roll of gauze"
+ pH = 10
/datum/reagent/medicine/antitoxin/on_mob_life(mob/living/carbon/M)
M.adjustToxLoss(-2*REM, 0)
@@ -1007,6 +1067,7 @@
id = "inaprovaline"
description = "Stabilizes the breathing of patients. Good for those in critical condition."
reagent_state = LIQUID
+ pH = 8.5
color = "#5dc1f0"
/datum/reagent/medicine/inaprovaline/on_mob_life(mob/living/carbon/M)
@@ -1062,6 +1123,7 @@
description = "Miniature medical robots that swiftly restore bodily damage."
reagent_state = SOLID
color = "#555555"
+ pH = 11
/datum/reagent/medicine/syndicate_nanites/on_mob_life(mob/living/carbon/M)
M.adjustBruteLoss(-5*REM, 0) //A ton of healing - this is a 50 telecrystal investment.
@@ -1075,14 +1137,15 @@
. = 1
/datum/reagent/medicine/neo_jelly
- name = "Neo Jelly"
- id = "neo_jelly"
- description = "Gradually regenerates all types of damage, without harming slime anatomy.Can OD"
- reagent_state = LIQUID
- metabolization_rate = 1 * REAGENTS_METABOLISM
- color = "#91D865"
- overdose_threshold = 30
- taste_description = "jelly"
+ name = "Neo Jelly"
+ id = "neo_jelly"
+ description = "Gradually regenerates all types of damage, without harming slime anatomy.Can OD"
+ reagent_state = LIQUID
+ metabolization_rate = 1 * REAGENTS_METABOLISM
+ color = "#91D865"
+ overdose_threshold = 30
+ taste_description = "jelly"
+ pH = 11.8
/datum/reagent/medicine/neo_jelly/on_mob_life(mob/living/carbon/M)
M.adjustBruteLoss(-1.5*REM, 0)
@@ -1105,6 +1168,7 @@
description = "Ichor from an extremely powerful plant. Great for restoring wounds, but it's a little heavy on the brain."
color = rgb(255, 175, 0)
overdose_threshold = 25
+ pH = 11
/datum/reagent/medicine/earthsblood/on_mob_life(mob/living/carbon/M)
M.adjustBruteLoss(-3 * REM, 0)
@@ -1132,6 +1196,7 @@
reagent_state = LIQUID
color = "#27870a"
metabolization_rate = 0.4 * REAGENTS_METABOLISM
+ pH = 4.3
/datum/reagent/medicine/haloperidol/on_mob_life(mob/living/carbon/M)
for(var/datum/reagent/drug/R in M.reagents.reagent_list)
@@ -1154,6 +1219,7 @@
color = "#a1a1a1"
overdose_threshold = 3 //To prevent people stacking massive amounts of a very strong healing reagent
can_synth = FALSE
+ pH = 14
/datum/reagent/medicine/lavaland_extract/on_mob_life(mob/living/carbon/M)
M.heal_bodypart_damage(5,5)
@@ -1217,6 +1283,7 @@
description = "A medication used to treat pain, fever, and inflammation, along with heart attacks."
color = "#F5F5F5"
self_consuming = TRUE
+ pH = 12.5
/datum/reagent/medicine/corazone/on_mob_metabolize(mob/living/M)
..()
@@ -1249,6 +1316,7 @@
overdose_threshold = 20 // with the random effects this might be awesome or might kill you at less than 10u (extensively tested)
taste_description = "salt" // it actually does taste salty
var/overdose_progress = 0 // to track overdose progress
+ pH = 7.89
/datum/reagent/medicine/modafinil/on_mob_metabolize(mob/living/M)
ADD_TRAIT(M, TRAIT_SLEEPIMMUNE, id)
@@ -1305,3 +1373,38 @@
M.adjustStaminaLoss(1.5*REM, 0)
..()
return TRUE
+
+/datum/reagent/medicine/psicodine
+ name = "Psicodine"
+ id = "psicodine"
+ description = "Suppresses anxiety and other various forms of mental distress. Overdose causes hallucinations and minor toxin damage."
+ reagent_state = LIQUID
+ color = "#07E79E"
+ metabolization_rate = 0.25 * REAGENTS_METABOLISM
+ overdose_threshold = 30
+ pH = 9.12
+
+/datum/reagent/medicine/psicodine/on_mob_add(mob/living/L)
+ ..()
+ ADD_TRAIT(L, TRAIT_FEARLESS, id)
+
+/datum/reagent/medicine/psicodine/on_mob_delete(mob/living/L)
+ REMOVE_TRAIT(L, TRAIT_FEARLESS, id)
+ ..()
+
+/datum/reagent/medicine/psicodine/on_mob_life(mob/living/carbon/M)
+ M.jitteriness = max(0, M.jitteriness-6)
+ M.dizziness = max(0, M.dizziness-6)
+ M.confused = max(0, M.confused-6)
+ M.disgust = max(0, M.disgust-6)
+ GET_COMPONENT_FROM(mood, /datum/component/mood, M)
+ if(mood.sanity <= SANITY_NEUTRAL) // only take effect if in negative sanity and then...
+ mood.setSanity(min(mood.sanity+5, SANITY_NEUTRAL)) // set minimum to prevent unwanted spiking over neutral
+ ..()
+ . = 1
+
+/datum/reagent/medicine/psicodine/overdose_process(mob/living/M)
+ M.hallucination = min(max(0, M.hallucination + 5), 60)
+ M.adjustToxLoss(1, 0)
+ ..()
+ . = 1
diff --git a/code/modules/reagents/chemistry/reagents/other_reagents.dm b/code/modules/reagents/chemistry/reagents/other_reagents.dm
index 247c8c2271..7bb5acff72 100644
--- a/code/modules/reagents/chemistry/reagents/other_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/other_reagents.dm
@@ -10,6 +10,7 @@
glass_name = "glass of tomato juice"
glass_desc = "Are you sure this is tomato juice?"
shot_glass_icon_state = "shotglassred"
+ pH = 7.4
/datum/reagent/blood/reaction_mob(mob/living/L, method=TOUCH, reac_volume)
if(data && data["viruses"])
@@ -183,6 +184,7 @@
taste_description = "gross iron"
shot_glass_icon_state = "shotglassred"
data = list("donor"=null,"viruses"=null,"blood_DNA"=null, "bloodcolor" = BLOOD_COLOR_HUMAN, "blood_type"= "O+","resistances"=null,"trace_chem"=null,"mind"=null,"ckey"=null,"gender"=null,"real_name"=null,"cloneable"=null,"factions"=null)
+ pH = 7.45
/datum/reagent/liquidgibs/xeno
name = "Liquid xeno gibs"
@@ -191,6 +193,7 @@
taste_description = "blended heresy"
shot_glass_icon_state = "shotglassgreen"
data = list("donor"=null,"viruses"=null,"blood_DNA"=null, "bloodcolor" = BLOOD_COLOR_XENO, "blood_type"="X*","resistances"=null,"trace_chem"=null,"mind"=null,"ckey"=null,"gender"=null,"real_name"=null,"cloneable"=null,"factions"=null)
+ pH = 2.5
/datum/reagent/liquidgibs/slime
name = "Slime sludge"
@@ -213,7 +216,7 @@
color = BLOOD_COLOR_OIL
taste_description = "chunky burnt oil"
data = list("donor"=null,"viruses"=null,"blood_DNA"=null, "bloodcolor" = BLOOD_COLOR_OIL, "blood_type"="HF","resistances"=null,"trace_chem"=null,"mind"=null,"ckey"=null,"gender"=null,"real_name"=null,"cloneable"=null,"factions"=null)
-
+ pH = 9.75
/datum/reagent/vaccine
//data must contain virus type
@@ -314,6 +317,7 @@
glass_icon_state = "glass_clear"
glass_name = "glass of holy water"
glass_desc = "A glass of holy water."
+ pH = 7.5 //God is alkaline
/datum/reagent/water/holywater/on_mob_metabolize(mob/living/L)
..()
@@ -390,6 +394,7 @@
id = "unholywater"
description = "Something that shouldn't exist on this plane of existence."
taste_description = "suffering"
+ pH = 6.5
/datum/reagent/fuel/unholywater/reaction_mob(mob/living/M, method=TOUCH, reac_volume)
if(method == TOUCH || method == VAPOR)
@@ -408,7 +413,7 @@
M.adjustOxyLoss(-2, 0)
M.adjustBruteLoss(-2, 0)
M.adjustFireLoss(-2, 0)
- if(ishuman(M) && M.blood_volume < BLOOD_VOLUME_NORMAL)
+ if(ishuman(M) && M.blood_volume < (BLOOD_VOLUME_NORMAL*M.blood_ratio))
M.blood_volume += 3
else // Will deal about 90 damage when 50 units are thrown
M.adjustBrainLoss(3, 150)
@@ -432,6 +437,7 @@
M.adjustFireLoss(1, 0) //Hence the other damages... ain't I a bastard?
M.adjustBrainLoss(5, 150)
holder.remove_reagent(id, 1)
+ pH = 0.1
/datum/reagent/fuel/holyoil //Its oil
name = "Zelus Oil"
@@ -499,6 +505,7 @@
metabolization_rate = 10 * REAGENTS_METABOLISM // very fast, so it can be applied rapidly. But this changes on an overdose
overdose_threshold = 11 //Slightly more than one un-nozzled spraybottle.
taste_description = "sour oranges"
+ pH = 5
/datum/reagent/spraytan/reaction_mob(mob/living/M, method=TOUCH, reac_volume, show_message = 1)
if(ishuman(M))
@@ -697,6 +704,46 @@
race = /datum/species/android
mutationtext = "The pain subsides. You feel... artificial."
+//Citadel Races
+/datum/reagent/mutationtoxin/mammal
+ name = "Mammal Mutation Toxin"
+ id = "mammalmutationtoxin"
+ description = "A glowing toxin."
+ color = "#5EFF3B" //RGB: 94, 255, 59
+ race = /datum/species/mammal
+ mutationtext = "The pain subsides. You feel... fluffier."
+
+/datum/reagent/mutationtoxin/aquatic
+ name = "Aquatic Mutation Toxin"
+ id = "aquaticmutationtoxin"
+ description = "A glowing toxin."
+ color = "#5EFF3B" //RGB: 94, 255, 59
+ race = /datum/species/aquatic
+ mutationtext = "The pain subsides. You feel... attracted to sea."
+
+/datum/reagent/mutationtoxin/avian
+ name = "Avian Mutation Toxin"
+ id = "avianmutationtoxin"
+ description = "A glowing toxin."
+ color = "#5EFF3B" //RGB: 94, 255, 59
+ race = /datum/species/avian
+ mutationtext = "The pain subsides. You feel... feathery."
+
+/datum/reagent/mutationtoxin/insect
+ name = "Insect Mutation Toxin"
+ id = "insectmutationtoxin"
+ description = "A glowing toxin."
+ color = "#5EFF3B" //RGB: 94, 255, 59
+ race = /datum/species/insect
+ mutationtext = "The pain subsides. You feel... attracted to dark, moist areas."
+
+/datum/reagent/mutationtoxin/xenoperson
+ name = "Xeno Mutation Toxin"
+ id = "xenopersonmutationtoxin"
+ description = "A glowing toxin."
+ color = "#5EFF3B" //RGB: 94, 255, 59
+ race = /datum/species/xenoperson
+ mutationtext = "The pain subsides. You feel... oddly longing for the Queen." //sadly, not the British one.
//BLACKLISTED RACES
/datum/reagent/mutationtoxin/skeleton
@@ -823,6 +870,7 @@
color = "#202040" // rgb: 20, 20, 40
metabolization_rate = 0.25 * REAGENTS_METABOLISM
taste_description = "bitterness"
+ pH = 10
/datum/reagent/serotrotium/on_mob_life(mob/living/carbon/M)
if(ishuman(M))
@@ -837,6 +885,7 @@
reagent_state = GAS
color = "#808080" // rgb: 128, 128, 128
taste_mult = 0 // oderless and tasteless
+ pH = 9.2//It's acutally a huge range and very dependant on the chemistry but pH is basically a made up var in it's implementation anyways
/datum/reagent/oxygen/reaction_obj(obj/O, reac_volume)
if((!O) || (!reac_volume))
@@ -857,6 +906,7 @@
reagent_state = SOLID
color = "#6E3B08" // rgb: 110, 59, 8
taste_description = "metal"
+ pH = 5.5
/datum/reagent/copper/reaction_obj(obj/O, reac_volume)
if(istype(O, /obj/item/stack/sheet/metal))
@@ -873,6 +923,7 @@
color = "#808080" // rgb: 128, 128, 128
taste_mult = 0
+
/datum/reagent/nitrogen/reaction_obj(obj/O, reac_volume)
if((!O) || (!reac_volume))
return 0
@@ -892,6 +943,7 @@
reagent_state = GAS
color = "#808080" // rgb: 128, 128, 128
taste_mult = 0
+ pH = 0.1//Now I'm stuck in a trap of my own design. Maybe I should make -ve pHes? (not 0 so I don't get div/0 errors)
/datum/reagent/potassium
name = "Potassium"
@@ -923,6 +975,7 @@
reagent_state = SOLID
color = "#BF8C00" // rgb: 191, 140, 0
taste_description = "rotten eggs"
+ pH = 4.5
/datum/reagent/carbon
name = "Carbon"
@@ -931,6 +984,7 @@
reagent_state = SOLID
color = "#1C1300" // rgb: 30, 20, 0
taste_description = "sour chalk"
+ pH = 5
/datum/reagent/carbon/reaction_turf(turf/T, reac_volume)
if(!isspaceturf(T))
@@ -945,6 +999,7 @@
reagent_state = GAS
color = "#808080" // rgb: 128, 128, 128
taste_description = "chlorine"
+ pH = 7.4
/datum/reagent/chlorine/on_mob_life(mob/living/carbon/M)
M.take_bodypart_damage(1*REM, 0, 0, 0)
@@ -958,6 +1013,7 @@
reagent_state = GAS
color = "#808080" // rgb: 128, 128, 128
taste_description = "acid"
+ pH = 2
/datum/reagent/fluorine/on_mob_life(mob/living/carbon/M)
M.adjustToxLoss(1*REM, 0)
@@ -971,6 +1027,7 @@
reagent_state = SOLID
color = "#808080" // rgb: 128, 128, 128
taste_description = "salty metal"
+ pH = 11.6
/datum/reagent/phosphorus
name = "Phosphorus"
@@ -979,6 +1036,7 @@
reagent_state = SOLID
color = "#832828" // rgb: 131, 40, 40
taste_description = "vinegar"
+ pH = 6.5
/datum/reagent/lithium
name = "Lithium"
@@ -987,6 +1045,7 @@
reagent_state = SOLID
color = "#808080" // rgb: 128, 128, 128
taste_description = "metal"
+ pH = 11.3
/datum/reagent/lithium/on_mob_life(mob/living/carbon/M)
if(M.canmove && !isspaceturf(M.loc))
@@ -1001,6 +1060,7 @@
description = "Glycerol is a simple polyol compound. Glycerol is sweet-tasting and of low toxicity."
color = "#808080" // rgb: 128, 128, 128
taste_description = "sweetness"
+ pH = 9
/datum/reagent/radium
name = "Radium"
@@ -1009,6 +1069,7 @@
reagent_state = SOLID
color = "#C7C7C7" // rgb: 199,199,199
taste_description = "the colour blue and regret"
+ pH = 10
/datum/reagent/radium/on_mob_life(mob/living/carbon/M)
M.apply_effect(2*REM/M.metabolism_efficiency,EFFECT_IRRADIATE,0)
@@ -1028,6 +1089,7 @@
description = "Sterilizes wounds in preparation for surgery."
color = "#e6f1f5" // rgb: 200, 165, 220
taste_description = "bitterness"
+ pH = 10.5
/datum/reagent/space_cleaner/sterilizine/reaction_mob(mob/living/carbon/C, method=TOUCH, reac_volume)
if(method in list(TOUCH, VAPOR, PATCH))
@@ -1043,11 +1105,12 @@
description = "Pure iron is a metal."
reagent_state = SOLID
taste_description = "iron"
+ pH = 6
color = "#c2391d"
/datum/reagent/iron/on_mob_life(mob/living/carbon/C)
- if(C.blood_volume < BLOOD_VOLUME_NORMAL)
+ if(C.blood_volume < (BLOOD_VOLUME_NORMAL*C.blood_ratio))
C.blood_volume += 0.01 //we'll have synthetics from medbay.
..()
@@ -1085,6 +1148,7 @@
reagent_state = SOLID
color = "#B8B8C0" // rgb: 184, 184, 192
taste_description = "the inside of a reactor"
+ pH = 4
/datum/reagent/uranium/on_mob_life(mob/living/carbon/M)
M.apply_effect(1/M.metabolism_efficiency,EFFECT_IRRADIATE,0)
@@ -1105,6 +1169,7 @@
reagent_state = SOLID
color = "#0000CC"
taste_description = "fizzling blue"
+ pH = 12
/datum/reagent/bluespace/reaction_mob(mob/living/M, method=TOUCH, reac_volume)
if(method == TOUCH || method == VAPOR)
@@ -1137,6 +1202,7 @@
reagent_state = SOLID
color = "#A8A8A8" // rgb: 168, 168, 168
taste_mult = 0
+ pH = 10
/datum/reagent/fuel
name = "Welding fuel"
@@ -1147,6 +1213,8 @@
glass_icon_state = "dr_gibb_glass"
glass_name = "glass of welder fuel"
glass_desc = "Unless you're an industrial tool, this is probably not safe for consumption."
+ pH = 4
+
/datum/reagent/fuel/reaction_mob(mob/living/M, method=TOUCH, reac_volume)//Splashing people with welding fuel to make them easy to ignite!
if(method == TOUCH || method == VAPOR)
@@ -1165,6 +1233,7 @@
description = "A compound used to clean things. Now with 50% more sodium hypochlorite!"
color = "#A5F0EE" // rgb: 165, 240, 238
taste_description = "sourness"
+ pH = 5.5
/datum/reagent/space_cleaner/reaction_obj(obj/O, reac_volume)
if(istype(O, /obj/effect/decal/cleanable))
@@ -1222,6 +1291,7 @@
description = "A powerful, acidic cleaner sold by Waffle Co. Affects organic matter while leaving other objects unaffected."
metabolization_rate = 1.5 * REAGENTS_METABOLISM
taste_description = "acid"
+ pH = 2
/datum/reagent/space_cleaner/ez_clean/on_mob_life(mob/living/carbon/M)
M.adjustBruteLoss(3.33)
@@ -1242,6 +1312,7 @@
color = "#7529b3" // rgb: 200, 165, 220
metabolization_rate = 1.5 * REAGENTS_METABOLISM
taste_description = "sourness"
+ pH = 11.9
/datum/reagent/cryptobiolin/on_mob_life(mob/living/carbon/M)
M.Dizzy(1)
@@ -1256,6 +1327,7 @@
description = "Impedrezene is a narcotic that impedes one's ability by slowing down the higher brain cell functions."
color = "#587a31" // rgb: 200, 165, 220A
taste_description = "numbness"
+ pH = 9.1
/datum/reagent/impedrezene/on_mob_life(mob/living/carbon/M)
M.jitteriness = max(M.jitteriness-5,0)
@@ -1298,6 +1370,7 @@
color = "#92D17D" // rgb: 146, 209, 125
can_synth = FALSE
taste_description = "slime"
+ pH = 11
/datum/reagent/fungalspores/reaction_mob(mob/living/L, method=TOUCH, reac_volume, show_message = 1, touch_protection = 0)
if(method==PATCH || method==INGEST || method==INJECT || (method == VAPOR && prob(min(reac_volume,100)*(1 - touch_protection))))
@@ -1309,6 +1382,7 @@
description = "A perfluoronated sulfonic acid that forms a foam when mixed with water."
color = "#9E6B38" // rgb: 158, 107, 56
taste_description = "metal"
+ pH = 13
/datum/reagent/foaming_agent// Metal foaming agent. This is lithium hydride. Add other recipes (e.g. LiH + H2O -> LiOH + H2) eventually.
name = "Foaming agent"
@@ -1317,6 +1391,7 @@
reagent_state = SOLID
color = "#664B63" // rgb: 102, 75, 99
taste_description = "metal"
+ pH = 12.5
/datum/reagent/smart_foaming_agent //Smart foaming agent. Functions similarly to metal foam, but conforms to walls.
name = "Smart foaming agent"
@@ -1325,6 +1400,7 @@
reagent_state = SOLID
color = "#664B63" // rgb: 102, 75, 99
taste_description = "metal"
+ pH = 11.8
/datum/reagent/ammonia
name = "Ammonia"
@@ -1333,6 +1409,7 @@
reagent_state = GAS
color = "#404030" // rgb: 64, 64, 48
taste_description = "mordant"
+ pH = 11.6
/datum/reagent/diethylamine
name = "Diethylamine"
@@ -1340,6 +1417,7 @@
description = "A secondary amine, mildly corrosive."
color = "#604030" // rgb: 96, 64, 48
taste_description = "iron"
+ pH = 12
/datum/reagent/carbondioxide
name = "Carbon Dioxide"
@@ -1348,6 +1426,7 @@
description = "A gas commonly produced by burning carbon fuels. You're constantly producing this in your lungs."
color = "#B0B0B0" // rgb : 192, 192, 192
taste_description = "something unknowable"
+ pH = 6
/datum/reagent/carbondioxide/reaction_obj(obj/O, reac_volume)
if((!O) || (!reac_volume))
@@ -1369,6 +1448,7 @@
metabolization_rate = 1.5 * REAGENTS_METABOLISM
color = "#808080"
taste_description = "sweetness"
+ pH = 5.8
/datum/reagent/nitrous_oxide/reaction_obj(obj/O, reac_volume)
if((!O) || (!reac_volume))
@@ -1428,6 +1508,7 @@
metabolization_rate = REAGENTS_METABOLISM
color = "90560B"
taste_description = "burning"
+ pH = 2
/datum/reagent/nitryl/on_mob_metabolize(mob/living/L)
..()
@@ -1461,6 +1542,7 @@
colorname = "red"
color = "#DA0000" // red
random_color_list = list("#DA0000")
+ pH = 0.5
/datum/reagent/colorful_reagent/crayonpowder/orange
name = "Orange Crayon Powder"
@@ -1468,6 +1550,7 @@
colorname = "orange"
color = "#FF9300" // orange
random_color_list = list("#FF9300")
+ pH = 2
/datum/reagent/colorful_reagent/crayonpowder/yellow
name = "Yellow Crayon Powder"
@@ -1475,6 +1558,7 @@
colorname = "yellow"
color = "#FFF200" // yellow
random_color_list = list("#FFF200")
+ pH = 5
/datum/reagent/colorful_reagent/crayonpowder/green
name = "Green Crayon Powder"
@@ -1483,12 +1567,14 @@
color = "#A8E61D" // green
random_color_list = list("#A8E61D")
+
/datum/reagent/colorful_reagent/crayonpowder/blue
name = "Blue Crayon Powder"
id = "bluecrayonpowder"
colorname = "blue"
color = "#00B7EF" // blue
random_color_list = list("#00B7EF")
+ pH = 10
/datum/reagent/colorful_reagent/crayonpowder/purple
name = "Purple Crayon Powder"
@@ -1496,6 +1582,7 @@
colorname = "purple"
color = "#DA00FF" // purple
random_color_list = list("#DA00FF")
+ pH = 13
/datum/reagent/colorful_reagent/crayonpowder/invisible
name = "Invisible Crayon Powder"
@@ -1530,6 +1617,7 @@
color = "#000000" // RBG: 0, 0, 0
var/tox_prob = 0
taste_description = "plant food"
+ pH = 3
/datum/reagent/plantnutriment/on_mob_life(mob/living/carbon/M)
if(prob(tox_prob))
@@ -1543,6 +1631,7 @@
description = "Cheap and extremely common type of plant nutriment."
color = "#376400" // RBG: 50, 100, 0
tox_prob = 10
+ pH = 2
/datum/reagent/plantnutriment/left4zednutriment
name = "Left 4 Zed"
@@ -1550,6 +1639,7 @@
description = "Unstable nutriment that makes plants mutate more often than usual."
color = "#1A1E4D" // RBG: 26, 30, 77
tox_prob = 25
+ pH = 1.5
/datum/reagent/plantnutriment/robustharvestnutriment
name = "Robust Harvest"
@@ -1557,6 +1647,7 @@
description = "Very potent nutriment that prevents plants from mutating."
color = "#9D9D00" // RBG: 157, 157, 0
tox_prob = 15
+ pH = 1
@@ -1584,6 +1675,7 @@
color = "#6b008f"
taste_description = "bitterness"
taste_mult = 1.5
+ pH = 1.5
/datum/reagent/stable_plasma/on_mob_life(mob/living/carbon/C)
C.adjustPlasma(10)
@@ -1596,6 +1688,7 @@
reagent_state = LIQUID
color = "#694600"
taste_description = "metal"
+ pH = 4.5
/datum/reagent/carpet
name = "Carpet"
@@ -1618,14 +1711,16 @@
reagent_state = LIQUID
color = "#b37740"
taste_description = "chemicals"
+ pH = 7.8
/datum/reagent/phenol
name = "Phenol"
id = "phenol"
description = "An aromatic ring of carbon with a hydroxyl group. A useful precursor to some medicines, but has no healing properties on its own."
reagent_state = LIQUID
+ taste_description = "sweet and tarry" //Again, not a strong acid.
+ pH = 5.5
color = "#e6e8ff"
- taste_description = "acid"
/datum/reagent/ash
name = "Ash"
@@ -1634,14 +1729,15 @@
reagent_state = LIQUID
color = "#665c56"
taste_description = "ash"
+ pH = 6.5
/datum/reagent/acetone
name = "Acetone"
id = "acetone"
description = "A slick, slightly carcinogenic liquid. Has a multitude of mundane uses in everyday life."
reagent_state = LIQUID
+ taste_description = "solvent"//It's neutral though..?
color = "#e6e6e6"
- taste_description = "acid"
/datum/reagent/colorful_reagent
name = "Colorful Reagent"
@@ -1731,6 +1827,7 @@
reagent_state = LIQUID
color = "#60A584" // rgb: 96, 165, 132
taste_description = "cool salt"
+ pH = 11.2
/datum/reagent/lye
name = "Lye"
@@ -1738,7 +1835,8 @@
description = "Also known as sodium hydroxide. As a profession making this is somewhat underwhelming."
reagent_state = LIQUID
color = "#FFFFD6" // very very light yellow
- taste_description = "acid"
+ taste_description = "alkali" //who put ACID for NaOH ????
+ pH = 13
/datum/reagent/drying_agent
name = "Drying agent"
@@ -1747,6 +1845,7 @@
reagent_state = LIQUID
color = "#A70FFF"
taste_description = "dryness"
+ pH = 10.7
/datum/reagent/drying_agent/reaction_turf(turf/open/T, reac_volume)
if(istype(T))
@@ -1818,6 +1917,7 @@
description = "Royal Bee Jelly, if injected into a Queen Space Bee said bee will split into two bees."
color = "#00ff80"
taste_description = "strange honey"
+ pH = 3
/datum/reagent/royal_bee_jelly/on_mob_life(mob/living/carbon/M)
if(prob(2))
@@ -1839,6 +1939,7 @@
metabolization_rate = INFINITY
can_synth = FALSE
taste_description = "brains"
+ pH = 0.5
/datum/reagent/romerol/reaction_mob(mob/living/carbon/human/H, method=TOUCH, reac_volume)
// Silently add the zombie infection organ to be activated upon death
@@ -1897,6 +1998,7 @@
description = "the petroleum based components of plastic."
color = "#f7eded"
taste_description = "plastic"
+ pH = 6
/datum/reagent/glitter
name = "generic glitter"
@@ -1939,6 +2041,7 @@
color = "#AAAAAA55"
taste_description = "water"
metabolization_rate = 0.25 * REAGENTS_METABOLISM
+ pH = 15
/datum/reagent/pax/on_mob_metabolize(mob/living/L)
..()
@@ -2027,6 +2130,13 @@
color = "#FAEAFF"
taste_description = "synthetic catnip"
+/datum/reagent/moonsugar/on_mob_life(mob/living/carbon/M)
+ if(prob(20))
+ to_chat(M, "You find yourself unable to supress the desire to meow!")
+ M.emote("nya")
+ ..()
+
+//Kept for legacy, I think it will break everything if you enable it.
/datum/reagent/penis_enlargement
name = "Penis Enlargement"
id = "penis_enlargement"
diff --git a/code/modules/reagents/chemistry/reagents/toxin_reagents.dm b/code/modules/reagents/chemistry/reagents/toxin_reagents.dm
index 6bd165f23e..2848c336e2 100644
--- a/code/modules/reagents/chemistry/reagents/toxin_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/toxin_reagents.dm
@@ -23,6 +23,7 @@
color = "#792300" // rgb: 121, 35, 0
toxpwr = 2.5
taste_description = "mushroom"
+ pH = 13
/datum/reagent/toxin/mutagen
name = "Unstable mutagen"
@@ -32,6 +33,7 @@
toxpwr = 0
taste_description = "slime"
taste_mult = 0.9
+ pH = 2
/datum/reagent/toxin/mutagen/reaction_mob(mob/living/carbon/M, method=TOUCH, reac_volume)
if(!..())
@@ -61,6 +63,7 @@
taste_mult = 1.5
color = "#8228A0"
toxpwr = 3
+ pH = 4
/datum/reagent/toxin/plasma/on_mob_life(mob/living/carbon/C)
if(holder.has_reagent("epinephrine"))
@@ -93,6 +96,7 @@
color = "#7DC3A0"
toxpwr = 0
taste_description = "acid"
+ pH = 1.2
/datum/reagent/toxin/lexorin/on_mob_life(mob/living/carbon/C)
. = TRUE
@@ -115,6 +119,7 @@
toxpwr = 0
taste_description = "slime"
taste_mult = 1.3
+ pH = 10
/datum/reagent/toxin/slimejelly/on_mob_life(mob/living/carbon/M)
if(prob(10))
@@ -133,6 +138,7 @@
color = "#CF3600" // rgb: 207, 54, 0
toxpwr = 0
taste_description = "mint"
+ pH = 8
/datum/reagent/toxin/minttoxin/on_mob_life(mob/living/carbon/M)
if(HAS_TRAIT(M, TRAIT_FAT))
@@ -146,6 +152,7 @@
color = "#003333" // rgb: 0, 51, 51
toxpwr = 2
taste_description = "fish"
+ pH = 12
/datum/reagent/toxin/zombiepowder
name = "Zombie Powder"
@@ -155,6 +162,7 @@
color = "#669900" // rgb: 102, 153, 0
toxpwr = 0.5
taste_description = "death"
+ pH = 13
/datum/reagent/toxin/zombiepowder/on_mob_metabolize(mob/living/L)
..()
@@ -177,6 +185,7 @@
color = "#664700" // rgb: 102, 71, 0
toxpwr = 0.8
taste_description = "death"
+ pH = 14.5
/datum/reagent/toxin/ghoulpowder/on_mob_metabolize(mob/living/L)
..()
@@ -198,6 +207,7 @@
color = "#B31008" // rgb: 139, 166, 233
toxpwr = 0
taste_description = "sourness"
+ pH = 11
/datum/reagent/toxin/mindbreaker/on_mob_life(mob/living/carbon/M)
M.hallucination += 5
@@ -210,6 +220,7 @@
color = "#49002E" // rgb: 73, 0, 46
toxpwr = 1
taste_mult = 1
+ pH = 2
/datum/reagent/toxin/plantbgone/reaction_obj(obj/O, reac_volume)
if(istype(O, /obj/structure/alien/weeds))
@@ -234,6 +245,7 @@
id = "weedkiller"
description = "A harmful toxic mixture to kill weeds. Do not ingest!"
color = "#4B004B" // rgb: 75, 0, 75
+ pH = 3
/datum/reagent/toxin/pestkiller
name = "Pest Killer"
@@ -241,6 +253,7 @@
description = "A harmful toxic mixture to kill pests. Do not ingest!"
color = "#4B004B" // rgb: 75, 0, 75
toxpwr = 1
+ pH = 3.2
/datum/reagent/toxin/pestkiller/reaction_mob(mob/living/M, method=TOUCH, reac_volume)
..()
@@ -254,6 +267,7 @@
description = "A natural toxin produced by blob spores that inhibits vision when ingested."
color = "#9ACD32"
toxpwr = 1
+ pH = 11
/datum/reagent/toxin/spore/on_mob_life(mob/living/carbon/C)
C.damageoverlaytemp = 60
@@ -268,6 +282,7 @@
color = "#9ACD32"
toxpwr = 0.5
taste_description = "burning"
+ pH = 13
/datum/reagent/toxin/spore_burning/on_mob_life(mob/living/carbon/M)
M.adjust_fire_stacks(2)
@@ -282,6 +297,7 @@
color = "#000067" // rgb: 0, 0, 103
toxpwr = 0
metabolization_rate = 1.5 * REAGENTS_METABOLISM
+ pH = 11
/datum/reagent/toxin/chloralhydrate/on_mob_life(mob/living/carbon/M)
switch(current_cycle)
@@ -326,6 +342,7 @@
glass_icon_state = "beerglass"
glass_name = "glass of beer"
glass_desc = "A freezing pint of beer."
+ pH = 2
/datum/reagent/toxin/fakebeer/on_mob_life(mob/living/carbon/M)
switch(current_cycle)
@@ -343,6 +360,7 @@
reagent_state = SOLID
color = "#5B2E0D" // rgb: 91, 46, 13
toxpwr = 0.5
+ pH = 4.2
/datum/reagent/toxin/teapowder
name = "Ground Tea Leaves"
@@ -351,6 +369,7 @@
reagent_state = SOLID
color = "#7F8400" // rgb: 127, 132, 0
toxpwr = 0.5
+ pH = 4.9
/datum/reagent/toxin/mutetoxin //the new zombie powder.
name = "Mute Toxin"
@@ -359,6 +378,7 @@
color = "#F0F8FF" // rgb: 240, 248, 255
toxpwr = 0
taste_description = "silence"
+ pH = 12.2
/datum/reagent/toxin/mutetoxin/on_mob_life(mob/living/carbon/M)
M.silent = max(M.silent, 3)
@@ -816,6 +836,7 @@
var/acidpwr = 10 //the amount of protection removed from the armour
taste_description = "acid"
self_consuming = TRUE
+ pH = 2.75
/datum/reagent/toxin/acid/reaction_mob(mob/living/carbon/C, method=TOUCH, reac_volume)
if(!istype(C))
diff --git a/code/modules/reagents/chemistry/recipes.dm b/code/modules/reagents/chemistry/recipes.dm
index 50006eef62..32155c9b89 100644
--- a/code/modules/reagents/chemistry/recipes.dm
+++ b/code/modules/reagents/chemistry/recipes.dm
@@ -17,6 +17,24 @@
var/mix_message = "The solution begins to bubble." //The message shown to nearby people upon mixing, if applicable
var/mix_sound = 'sound/effects/bubbles.ogg' //The sound played upon mixing, if applicable
+ //FermiChem!
+ var/OptimalTempMin = 200 // Lower area of bell curve for determining heat based rate reactions
+ var/OptimalTempMax = 800 // Upper end for above
+ var/ExplodeTemp = 900 // Temperature at which reaction explodes - If any reaction is this hot, it explodes!
+ var/OptimalpHMin = 5 // Lowest value of pH determining pH a 1 value for pH based rate reactions (Plateu phase)
+ var/OptimalpHMax = 10 // Higest value for above
+ var/ReactpHLim = 3 // How far out pH wil react, giving impurity place (Exponential phase)
+ var/CatalystFact = 0 // How much the catalyst affects the reaction (0 = no catalyst)//Not implemented yet
+ var/CurveSharpT = 2 // How sharp the temperature exponential curve is (to the power of value)
+ var/CurveSharppH = 2 // How sharp the pH exponential curve is (to the power of value)
+ var/ThermicConstant = 1 // Temperature change per 1u produced
+ var/HIonRelease = 0.1 // pH change per 1u reaction
+ var/RateUpLim = 10 // Optimal/max rate possible if all conditions are perfect
+ var/FermiChem = FALSE // If the chemical uses the Fermichem reaction mechanics//If the chemical uses the Fermichem reaction mechanics
+ var/FermiExplode = FALSE // If the chemical explodes in a special way
+ var/PurityMin = 0.15 //If purity is below 0.15, it explodes too. Set to 0 to disable this.
+
+
/datum/chemical_reaction/proc/on_reaction(datum/reagents/holder, created_volume, specialreact)
return
//I recommend you set the result amount to the total volume of all components.
diff --git a/code/modules/reagents/chemistry/recipes/drugs.dm b/code/modules/reagents/chemistry/recipes/drugs.dm
index d91e2af7e9..27b1fe12ee 100644
--- a/code/modules/reagents/chemistry/recipes/drugs.dm
+++ b/code/modules/reagents/chemistry/recipes/drugs.dm
@@ -41,6 +41,13 @@
results = list("aranesp" = 3)
required_reagents = list("epinephrine" = 1, "atropine" = 1, "morphine" = 1)
+/datum/chemical_reaction/happiness
+ name = "Happiness"
+ id = "happiness"
+ results = list("happiness" = 4)
+ required_reagents = list("nitrous_oxide" = 2, "epinephrine" = 1, "ethanol" = 1)
+ required_catalysts = list("plasma" = 5)
+
/datum/chemical_reaction/skooma
name = "skooma"
id = "skooma"
diff --git a/code/modules/reagents/chemistry/recipes/medicine.dm b/code/modules/reagents/chemistry/recipes/medicine.dm
index 9c292c0fe8..a56da26071 100644
--- a/code/modules/reagents/chemistry/recipes/medicine.dm
+++ b/code/modules/reagents/chemistry/recipes/medicine.dm
@@ -178,6 +178,12 @@
results = list("mutadone" = 3)
required_reagents = list("mutagen" = 1, "acetone" = 1, "bromine" = 1)
+/datum/chemical_reaction/neurine
+ name = "Neurine"
+ id = "neurine"
+ results = list("neurine" = 3)
+ required_reagents = list("mannitol" = 1, "acetone" = 1, "oxygen" = 1)
+
/datum/chemical_reaction/antihol
name = "antihol"
id = "antihol"
@@ -264,3 +270,9 @@
results = list("modafinil" = 5)
required_reagents = list("diethylamine" = 1, "ammonia" = 1, "phenol" = 1, "acetone" = 1, "sacid" = 1)
required_catalysts = list("bromine" = 1) // as close to the real world synthesis as possible
+
+/datum/chemical_reaction/psicodine
+ name = "Psicodine"
+ id = "psicodine"
+ results = list("psicodine" = 5)
+ required_reagents = list( "mannitol" = 2, "water" = 2, "impedrezene" = 1)
diff --git a/code/modules/reagents/chemistry/recipes/others.dm b/code/modules/reagents/chemistry/recipes/others.dm
index bcd08b1853..e34f34675c 100644
--- a/code/modules/reagents/chemistry/recipes/others.dm
+++ b/code/modules/reagents/chemistry/recipes/others.dm
@@ -528,6 +528,16 @@
/datum/chemical_reaction/life/on_reaction(datum/reagents/holder, created_volume)
chemical_mob_spawn(holder, rand(1, round(created_volume, 1)), "Life") // Lol.
+//This is missing, I'm adding it back (see tgwiki). Not sure why we don't have it.
+/datum/chemical_reaction/life_friendly
+ name = "Life (Friendly)"
+ id = "life_friendly"
+ required_reagents = list("strange_reagent" = 1, "synthflesh" = 1, "sugar" = 1)
+ required_temp = 374
+
+/datum/chemical_reaction/life_friendly/on_reaction(datum/reagents/holder, created_volume)
+ chemical_mob_spawn(holder, rand(1, round(created_volume, 1)), "Life (friendly)", FRIENDLY_SPAWN) //Pray for cute cats
+
/datum/chemical_reaction/corgium
name = "corgium"
id = "corgium"
diff --git a/code/modules/reagents/reagent_containers.dm b/code/modules/reagents/reagent_containers.dm
index 13e809f7cb..48abfcb649 100644
--- a/code/modules/reagents/reagent_containers.dm
+++ b/code/modules/reagents/reagent_containers.dm
@@ -1,3 +1,6 @@
+#define PH_WEAK (1 << 0)
+#define TEMP_WEAK (1 << 1)
+
/obj/item/reagent_containers
name = "Container"
desc = "..."
@@ -12,6 +15,8 @@
var/spawned_disease = null
var/disease_amount = 20
var/spillable = FALSE
+ var/beaker_weakness_bitflag = NONE//Bitflag!
+ var/container_HP = 2
/obj/item/reagent_containers/Initialize(mapload, vol)
. = ..()
@@ -123,9 +128,50 @@
reagents.clear_reagents()
+//melts plastic beakers
/obj/item/reagent_containers/microwave_act(obj/machinery/microwave/M)
reagents.expose_temperature(1000)
+ if(beaker_weakness_bitflag & TEMP_WEAK)
+ var/list/seen = viewers(5, get_turf(src))
+ var/iconhtml = icon2html(src, seen)
+ for(var/mob/H in seen)
+ to_chat(H, "[iconhtml] \The [src]'s melts from the temperature!")
+ playsound(get_turf(src), 'sound/FermiChem/heatmelt.ogg', 80, 1)
+ qdel(src)
..()
+//melts plastic beakers
/obj/item/reagent_containers/temperature_expose(datum/gas_mixture/air, exposed_temperature, exposed_volume)
- reagents.expose_temperature(exposed_temperature)
\ No newline at end of file
+ reagents.expose_temperature(exposed_temperature)
+ temp_check()
+
+/obj/item/reagent_containers/proc/temp_check()
+ if(beaker_weakness_bitflag & TEMP_WEAK)
+ if(reagents.chem_temp >= 444)//assuming polypropylene
+ var/list/seen = viewers(5, get_turf(src))
+ var/iconhtml = icon2html(src, seen)
+ for(var/mob/M in seen)
+ to_chat(M, "[iconhtml] \The [src]'s melts from the temperature!")
+ playsound(get_turf(src), 'sound/FermiChem/heatmelt.ogg', 80, 1)
+ to_chat(M, "[iconhtml] Have you tried using glass or meta beakers for high temperature reactions? These are immune to temperature effects.")
+ SSblackbox.record_feedback("tally", "fermi_chem", 1, "Times beakers have melted from temperature")
+ qdel(src)
+
+//melts glass beakers
+/obj/item/reagent_containers/proc/pH_check()
+ if(beaker_weakness_bitflag & PH_WEAK)
+ if((reagents.pH < 0.5) || (reagents.pH > 13.5))
+ var/list/seen = viewers(5, get_turf(src))
+ var/iconhtml = icon2html(src, seen)
+ container_HP--
+ if(container_HP <= 0)
+ for(var/mob/M in seen)
+ to_chat(M, "[iconhtml] \The [src]'s melts from the extreme pH!")
+ playsound(get_turf(src), 'sound/FermiChem/acidmelt.ogg', 80, 1)
+ SSblackbox.record_feedback("tally", "fermi_chem", 1, "Times beakers have melted from pH")
+ qdel(src)
+ else
+ for(var/mob/M in seen)
+ to_chat(M, "[iconhtml] \The [src]'s is damaged by the extreme pH and begins to deform!")
+ playsound(get_turf(src), 'sound/FermiChem/bufferadd.ogg', 50, 1)
+ to_chat(M, "[iconhtml] Have you tried using plastic beakers (XL) or metabeakers for high pH reactions? These beakers are immune to pH effects.")
diff --git a/code/modules/reagents/reagent_containers/bottle.dm b/code/modules/reagents/reagent_containers/bottle.dm
index 66befb1bb6..249ed56d20 100644
--- a/code/modules/reagents/reagent_containers/bottle.dm
+++ b/code/modules/reagents/reagent_containers/bottle.dm
@@ -240,7 +240,7 @@
name = "Rhinovirus culture bottle"
desc = "A small bottle. Contains XY-rhinovirus culture in synthblood medium."
spawned_disease = /datum/disease/advance/cold
-
+
/obj/item/reagent_containers/glass/bottle/flu_virion
name = "Flu virion culture bottle"
desc = "A small bottle. Contains H13N1 flu virion culture in synthblood medium."
diff --git a/code/modules/reagents/reagent_containers/glass.dm b/code/modules/reagents/reagent_containers/glass.dm
index 9bc6bef8a5..014401b72c 100644
--- a/code/modules/reagents/reagent_containers/glass.dm
+++ b/code/modules/reagents/reagent_containers/glass.dm
@@ -6,6 +6,7 @@
reagent_flags = OPENCONTAINER
spillable = TRUE
resistance_flags = ACID_PROOF
+ container_HP = 3
/obj/item/reagent_containers/glass/attack(mob/M, mob/user, obj/target)
@@ -108,11 +109,13 @@
/obj/item/reagent_containers/glass/beaker
name = "beaker"
- desc = "A beaker. It can hold up to 50 units."
+ desc = "A beaker. It can hold up to 50 units. Unable to withstand extreme pHes"
icon = 'icons/obj/chemical.dmi'
icon_state = "beaker"
item_state = "beaker"
materials = list(MAT_GLASS=500)
+ beaker_weakness_bitflag = PH_WEAK
+ container_HP = 5
/obj/item/reagent_containers/glass/beaker/Initialize()
. = ..()
@@ -152,27 +155,33 @@
/obj/item/reagent_containers/glass/beaker/jar
name = "honey jar"
- desc = "A jar for honey. It can hold up to 50 units of sweet delight."
+ desc = "A jar for honey. It can hold up to 50 units of sweet delight. Unable to withstand reagents of an extreme pH."
icon = 'icons/obj/chemical.dmi'
icon_state = "vapour"
/obj/item/reagent_containers/glass/beaker/large
name = "large beaker"
- desc = "A large beaker. Can hold up to 100 units."
+ desc = "A large beaker. Can hold up to 100 units. Unable to withstand reagents of an extreme pH."
icon_state = "beakerlarge"
materials = list(MAT_GLASS=2500)
volume = 100
amount_per_transfer_from_this = 10
possible_transfer_amounts = list(5,10,15,20,25,30,50,100)
+ container_HP = 6
/obj/item/reagent_containers/glass/beaker/plastic
name = "x-large beaker"
- desc = "An extra-large beaker. Can hold up to 120 units."
+ desc = "An extra-large beaker. Can hold up to 150 units. Is able to resist acid and alkaline solutions, but melts at 444K"
icon_state = "beakerwhite"
materials = list(MAT_GLASS=2500, MAT_PLASTIC=3000)
- volume = 120
+ volume = 150
amount_per_transfer_from_this = 10
- possible_transfer_amounts = list(5,10,15,20,25,30,60,120)
+ possible_transfer_amounts = list(5,10,15,20,25,30,50,100,150)
+
+/obj/item/reagent_containers/glass/beaker/plastic/Initialize()
+ beaker_weakness_bitflag &= ~PH_WEAK
+ beaker_weakness_bitflag |= TEMP_WEAK
+ . = ..()
/obj/item/reagent_containers/glass/beaker/plastic/update_icon()
icon_state = "beakerlarge" // hack to lets us reuse the large beaker reagent fill states
@@ -181,12 +190,16 @@
/obj/item/reagent_containers/glass/beaker/meta
name = "metamaterial beaker"
- desc = "A large beaker. Can hold up to 180 units."
+ desc = "A large beaker. Can hold up to 200 units. Is able to withstand all chemical situations."
icon_state = "beakergold"
materials = list(MAT_GLASS=2500, MAT_PLASTIC=3000, MAT_GOLD=1000, MAT_TITANIUM=1000)
- volume = 180
+ volume = 200
amount_per_transfer_from_this = 10
- possible_transfer_amounts = list(5,10,15,20,25,30,60,120,180)
+ possible_transfer_amounts = list(5,10,15,20,25,30,50,100,200)
+
+/obj/item/reagent_containers/glass/beaker/meta/Initialize()
+ beaker_weakness_bitflag &= ~PH_WEAK
+ . = ..()
/obj/item/reagent_containers/glass/beaker/noreact
name = "cryostasis beaker"
@@ -197,17 +210,24 @@
reagent_flags = OPENCONTAINER | NO_REACT
volume = 50
amount_per_transfer_from_this = 10
+ container_HP = 10//shouldn't be needed
+
+/obj/item/reagent_containers/glass/beaker/noreact/Initialize()
+ beaker_weakness_bitflag &= ~PH_WEAK
+ . = ..()
+ //reagents.set_reacting(FALSE) was this removed in a recent pr?
/obj/item/reagent_containers/glass/beaker/bluespace
name = "bluespace beaker"
desc = "A bluespace beaker, powered by experimental bluespace technology \
and Element Cuban combined with the Compound Pete. Can hold up to \
- 300 units."
+ 300 units. Unable to withstand reagents of an extreme pH."
icon_state = "beakerbluespace"
materials = list(MAT_GLASS=3000)
volume = 300
amount_per_transfer_from_this = 10
possible_transfer_amounts = list(5,10,15,20,25,30,50,100,300)
+ container_HP = 8
/obj/item/reagent_containers/glass/beaker/cryoxadone
list_reagents = list("cryoxadone" = 30)
@@ -264,6 +284,11 @@
SLOT_L_STORE, SLOT_R_STORE,\
SLOT_GENERC_DEXTROUS_STORAGE
)
+ container_HP = 2
+
+/obj/item/reagent_containers/glass/bucket/Initialize()
+ beaker_weakness_bitflag |= TEMP_WEAK
+ . = ..()
/obj/item/reagent_containers/glass/bucket/attackby(obj/O, mob/user, params)
if(istype(O, /obj/item/mop))
@@ -313,6 +338,11 @@
materials = list(MAT_GLASS=0)
volume = 50
amount_per_transfer_from_this = 10
+ container_HP = 2
+
+/obj/item/reagent_containers/glass/beaker/waterbottle/Initialize()
+ beaker_weakness_bitflag |= TEMP_WEAK
+ . = ..()
/obj/item/reagent_containers/glass/beaker/waterbottle/empty
list_reagents = list()
@@ -324,6 +354,7 @@
list_reagents = list("water" = 100)
volume = 100
amount_per_transfer_from_this = 20
+ container_HP = 2
/obj/item/reagent_containers/glass/beaker/waterbottle/large/empty
list_reagents = list()
diff --git a/code/modules/reagents/reagent_containers/pill.dm b/code/modules/reagents/reagent_containers/pill.dm
index 280337e686..38880f669f 100644
--- a/code/modules/reagents/reagent_containers/pill.dm
+++ b/code/modules/reagents/reagent_containers/pill.dm
@@ -1,242 +1,259 @@
-/obj/item/reagent_containers/pill
- name = "pill"
- desc = "A tablet or capsule."
- icon = 'icons/obj/chemical.dmi'
- icon_state = "pill"
- item_state = "pill"
- lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi'
- righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi'
- possible_transfer_amounts = list()
- volume = 50
- grind_results = list()
- var/apply_type = INGEST
- var/apply_method = "swallow"
- var/roundstart = 0
- var/self_delay = 0 //pills are instant, this is because patches inheret their aplication from pills
- var/dissolvable = TRUE
-
-/obj/item/reagent_containers/pill/Initialize()
- . = ..()
- if(!icon_state)
- icon_state = "pill[rand(1,20)]"
- if(reagents.total_volume && roundstart)
- name += " ([reagents.total_volume]u)"
-
-
-/obj/item/reagent_containers/pill/attack_self(mob/user)
- return
-
-
-/obj/item/reagent_containers/pill/attack(mob/M, mob/user, def_zone)
- if(!canconsume(M, user))
- return 0
-
- if(M == user)
- M.visible_message("[user] attempts to [apply_method] [src].")
- if(self_delay)
- if(!do_mob(user, M, self_delay))
- return 0
- to_chat(M, "You [apply_method] [src].")
-
- else
- M.visible_message("[user] attempts to force [M] to [apply_method] [src].", \
- "[user] attempts to force [M] to [apply_method] [src].")
- if(!do_mob(user, M))
- return 0
- M.visible_message("[user] forces [M] to [apply_method] [src].", \
- "[user] forces [M] to [apply_method] [src].")
-
- var/makes_me_think = pick(strings("redpill.json", "redpill_questions"))
- if(icon_state == "pill4" && prob(5)) //you take the red pill - you stay in Wonderland, and I show you how deep the rabbit hole goes
- addtimer(CALLBACK(GLOBAL_PROC, /proc/to_chat, M, "[makes_me_think]"), 50)
-
- log_combat(user, M, "fed", reagents.log_list())
- if(reagents.total_volume)
- reagents.reaction(M, apply_type)
- reagents.trans_to(M, reagents.total_volume)
- qdel(src)
- return 1
-
-
-/obj/item/reagent_containers/pill/afterattack(obj/target, mob/user , proximity)
- . = ..()
- if(!proximity)
- return
- if(!dissolvable || !target.is_refillable())
- return
- if(target.is_drainable() && !target.reagents.total_volume)
- to_chat(user, "[target] is empty! There's nothing to dissolve [src] in.")
- return
-
- if(target.reagents.holder_full())
- to_chat(user, "[target] is full.")
- return
-
- to_chat(user, "You dissolve [src] in [target].")
- for(var/mob/O in viewers(2, user)) //viewers is necessary here because of the small radius
- to_chat(O, "[user] slips something into [target]!")
- reagents.trans_to(target, reagents.total_volume)
- qdel(src)
-
-/obj/item/reagent_containers/pill/tox
- name = "toxins pill"
- desc = "Highly toxic."
- icon_state = "pill5"
- list_reagents = list("toxin" = 50)
- roundstart = 1
-
-/obj/item/reagent_containers/pill/cyanide
- name = "cyanide pill"
- desc = "Don't swallow this."
- icon_state = "pill5"
- list_reagents = list("cyanide" = 50)
- roundstart = 1
-
-/obj/item/reagent_containers/pill/adminordrazine
- name = "adminordrazine pill"
- desc = "It's magic. We don't have to explain it."
- icon_state = "pill16"
- list_reagents = list("adminordrazine" = 50)
- roundstart = 1
-
-/obj/item/reagent_containers/pill/morphine
- name = "morphine pill"
- desc = "Commonly used to treat insomnia."
- icon_state = "pill8"
- list_reagents = list("morphine" = 30)
- roundstart = 1
-
-/obj/item/reagent_containers/pill/stimulant
- name = "stimulant pill"
- desc = "Often taken by overworked employees, athletes, and the inebriated. You'll snap to attention immediately!"
- icon_state = "pill19"
- list_reagents = list("ephedrine" = 10, "antihol" = 10, "coffee" = 30)
- roundstart = 1
-
-/obj/item/reagent_containers/pill/salbutamol
- name = "salbutamol pill"
- desc = "Used to treat oxygen deprivation."
- icon_state = "pill16"
- list_reagents = list("salbutamol" = 30)
- roundstart = 1
-
-/obj/item/reagent_containers/pill/charcoal
- name = "charcoal pill"
- desc = "Neutralizes many common toxins."
- icon_state = "pill17"
- list_reagents = list("charcoal" = 10)
- roundstart = 1
-
-/obj/item/reagent_containers/pill/epinephrine
- name = "epinephrine pill"
- desc = "Used to stabilize patients."
- icon_state = "pill5"
- list_reagents = list("epinephrine" = 15)
- roundstart = 1
-
-/obj/item/reagent_containers/pill/mannitol
- name = "mannitol pill"
- desc = "Used to treat brain damage."
- icon_state = "pill17"
- list_reagents = list("mannitol" = 50)
- roundstart = 1
-
-/obj/item/reagent_containers/pill/mutadone
- name = "mutadone pill"
- desc = "Used to treat genetic damage."
- icon_state = "pill20"
- list_reagents = list("mutadone" = 50)
- roundstart = 1
-
-/obj/item/reagent_containers/pill/salicyclic
- name = "salicylic acid pill"
- desc = "Used to dull pain."
- icon_state = "pill9"
- list_reagents = list("sal_acid" = 24)
- roundstart = 1
-
-/obj/item/reagent_containers/pill/oxandrolone
- name = "oxandrolone pill"
- desc = "Used to stimulate burn healing."
- icon_state = "pill11"
- list_reagents = list("oxandrolone" = 24)
- roundstart = 1
-
-/obj/item/reagent_containers/pill/insulin
- name = "insulin pill"
- desc = "Handles hyperglycaemic coma."
- icon_state = "pill18"
- list_reagents = list("insulin" = 50)
- roundstart = 1
-
-/obj/item/reagent_containers/pill/antirad
- name = "potassium iodide pill"
- desc = "Used to treat radition used to counter radiation poisoning."
- icon_state = "pill18"
- list_reagents = list("potass_iodide" = 50)
- roundstart = 1
-
-/obj/item/reagent_containers/pill/antirad_plus
- name = "prussian blue pill"
- desc = "Used to treat heavy radition poisoning."
- icon = 'modular_citadel/icons/obj/modularpills.dmi'
- icon_state = "prussian_blue"
- list_reagents = list("prussian_blue" = 25, "water" = 10)
- roundstart = 1
-
-/obj/item/reagent_containers/pill/mutarad
- name = "radiation treatment deluxe pill"
- desc = "Used to treat heavy radition poisoning and genetic defects."
- icon = 'modular_citadel/icons/obj/modularpills.dmi'
- icon_state = "anit_rad_fixgene"
- list_reagents = list("prussian_blue" = 15, "potass_iodide" = 15, "mutadone" = 15, "water" = 5)
- roundstart = 1
-
-///////////////////////////////////////// this pill is used only in a legion mob drop
-/obj/item/reagent_containers/pill/shadowtoxin
- name = "black pill"
- desc = "I wouldn't eat this if I were you."
- icon_state = "pill9"
- color = "#454545"
- list_reagents = list("shadowmutationtoxin" = 1)
-//////////////////////////////////////// drugs
-/obj/item/reagent_containers/pill/zoom
- name = "zoom pill"
- list_reagents = list("synaptizine" = 10, "nicotine" = 10, "methamphetamine" = 1)
-
-
-/obj/item/reagent_containers/pill/happy
- name = "happy pill"
- list_reagents = list("sugar" = 10, "space_drugs" = 10)
-
-
-/obj/item/reagent_containers/pill/lsd
- name = "hallucinogen pill"
- list_reagents = list("mushroomhallucinogen" = 15, "mindbreaker" = 15)
-
-
-/obj/item/reagent_containers/pill/aranesp
- name = "speedy pill"
- list_reagents = list("aranesp" = 10)
-
-/obj/item/reagent_containers/pill/floorpill
- name = "floorpill"
- desc = "A strange pill found in the depths of maintenance"
- icon_state = "pill21"
- var/static/list/names = list("maintenance pill","floorpill","mystery pill","suspicious pill","strange pill")
- var/static/list/descs = list("Your feeling is telling you no, but...","Drugs are expensive, you can't afford not to eat any pills that you find."\
- , "Surely, there's no way this could go bad.")
-
-/obj/item/reagent_containers/pill/floorpill/Initialize()
- list_reagents = list(get_random_reagent_id() = rand(10,50))
- . = ..()
- name = pick(names)
- if(prob(20))
- desc = pick(descs)
-
-/obj/item/reagent_containers/pill/get_belt_overlay()
- return mutable_appearance('icons/obj/clothing/belt_overlays.dmi', "pouch")
-
-/obj/item/reagent_containers/pill/penis_enlargement
- name = "penis enlargement pill"
- list_reagents = list("penis_enlargement" = 30)
+/obj/item/reagent_containers/pill
+ name = "pill"
+ desc = "A tablet or capsule."
+ icon = 'icons/obj/chemical.dmi'
+ icon_state = "pill"
+ item_state = "pill"
+ lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi'
+ possible_transfer_amounts = list()
+ volume = 50
+ grind_results = list()
+ var/apply_type = INGEST
+ var/apply_method = "swallow"
+ var/roundstart = 0
+ var/self_delay = 0 //pills are instant, this is because patches inheret their aplication from pills
+ var/dissolvable = TRUE
+
+/obj/item/reagent_containers/pill/Initialize()
+ . = ..()
+ if(!icon_state)
+ icon_state = "pill[rand(1,20)]"
+ if(reagents.total_volume && roundstart)
+ name += " ([reagents.total_volume]u)"
+
+
+/obj/item/reagent_containers/pill/attack_self(mob/user)
+ return
+
+
+/obj/item/reagent_containers/pill/attack(mob/M, mob/user, def_zone)
+ if(!canconsume(M, user))
+ return 0
+
+ if(M == user)
+ M.visible_message("[user] attempts to [apply_method] [src].")
+ if(self_delay)
+ if(!do_mob(user, M, self_delay))
+ return 0
+ to_chat(M, "You [apply_method] [src].")
+
+ else
+ M.visible_message("[user] attempts to force [M] to [apply_method] [src].", \
+ "[user] attempts to force [M] to [apply_method] [src].")
+ if(!do_mob(user, M))
+ return 0
+ M.visible_message("[user] forces [M] to [apply_method] [src].", \
+ "[user] forces [M] to [apply_method] [src].")
+
+ var/makes_me_think = pick(strings("redpill.json", "redpill_questions"))
+ if(icon_state == "pill4" && prob(5)) //you take the red pill - you stay in Wonderland, and I show you how deep the rabbit hole goes
+ addtimer(CALLBACK(GLOBAL_PROC, /proc/to_chat, M, "[makes_me_think]"), 50)
+
+ log_combat(user, M, "fed", reagents.log_list())
+ if(reagents.total_volume)
+ reagents.reaction(M, apply_type)
+ reagents.trans_to(M, reagents.total_volume)
+ qdel(src)
+ return 1
+
+
+/obj/item/reagent_containers/pill/afterattack(obj/target, mob/user , proximity)
+ . = ..()
+ if(!proximity)
+ return
+ if(!dissolvable || !target.is_refillable())
+ return
+ if(target.is_drainable() && !target.reagents.total_volume)
+ to_chat(user, "[target] is empty! There's nothing to dissolve [src] in.")
+ return
+
+ if(target.reagents.holder_full())
+ to_chat(user, "[target] is full.")
+ return
+
+ to_chat(user, "You dissolve [src] in [target].")
+ for(var/mob/O in viewers(2, user)) //viewers is necessary here because of the small radius
+ to_chat(O, "[user] slips something into [target]!")
+ reagents.trans_to(target, reagents.total_volume)
+ qdel(src)
+
+/obj/item/reagent_containers/pill/tox
+ name = "toxins pill"
+ desc = "Highly toxic."
+ icon_state = "pill5"
+ list_reagents = list("toxin" = 50)
+ roundstart = 1
+
+/obj/item/reagent_containers/pill/cyanide
+ name = "cyanide pill"
+ desc = "Don't swallow this."
+ icon_state = "pill5"
+ list_reagents = list("cyanide" = 50)
+ roundstart = 1
+
+/obj/item/reagent_containers/pill/adminordrazine
+ name = "adminordrazine pill"
+ desc = "It's magic. We don't have to explain it."
+ icon_state = "pill16"
+ list_reagents = list("adminordrazine" = 50)
+ roundstart = 1
+
+/obj/item/reagent_containers/pill/morphine
+ name = "morphine pill"
+ desc = "Commonly used to treat insomnia."
+ icon_state = "pill8"
+ list_reagents = list("morphine" = 30)
+ roundstart = 1
+
+/obj/item/reagent_containers/pill/stimulant
+ name = "stimulant pill"
+ desc = "Often taken by overworked employees, athletes, and the inebriated. You'll snap to attention immediately!"
+ icon_state = "pill19"
+ list_reagents = list("ephedrine" = 10, "antihol" = 10, "coffee" = 30)
+ roundstart = 1
+
+/obj/item/reagent_containers/pill/salbutamol
+ name = "salbutamol pill"
+ desc = "Used to treat oxygen deprivation."
+ icon_state = "pill16"
+ list_reagents = list("salbutamol" = 30)
+ roundstart = 1
+
+/obj/item/reagent_containers/pill/charcoal
+ name = "charcoal pill"
+ desc = "Neutralizes many common toxins."
+ icon_state = "pill17"
+ list_reagents = list("charcoal" = 10)
+ roundstart = 1
+
+/obj/item/reagent_containers/pill/epinephrine
+ name = "epinephrine pill"
+ desc = "Used to stabilize patients."
+ icon_state = "pill5"
+ list_reagents = list("epinephrine" = 15)
+ roundstart = 1
+
+/obj/item/reagent_containers/pill/mannitol
+ name = "mannitol pill"
+ desc = "Used to treat brain damage."
+ icon_state = "pill17"
+ list_reagents = list("mannitol" = 50)
+ roundstart = 1
+
+/obj/item/reagent_containers/pill/mutadone
+ name = "mutadone pill"
+ desc = "Used to treat genetic damage."
+ icon_state = "pill20"
+ list_reagents = list("mutadone" = 50)
+ roundstart = 1
+
+/obj/item/reagent_containers/pill/salicyclic
+ name = "salicylic acid pill"
+ desc = "Used to dull pain."
+ icon_state = "pill9"
+ list_reagents = list("sal_acid" = 24)
+ roundstart = 1
+
+/obj/item/reagent_containers/pill/oxandrolone
+ name = "oxandrolone pill"
+ desc = "Used to stimulate burn healing."
+ icon_state = "pill11"
+ list_reagents = list("oxandrolone" = 24)
+ roundstart = 1
+
+/obj/item/reagent_containers/pill/insulin
+ name = "insulin pill"
+ desc = "Handles hyperglycaemic coma."
+ icon_state = "pill18"
+ list_reagents = list("insulin" = 50)
+ roundstart = 1
+
+/obj/item/reagent_containers/pill/psicodine
+ name = "psicodine pill"
+ desc = "Used to treat mental instability and traumas."
+ list_reagents = list("psicodine" = 10)
+ icon_state = "pill22"
+ roundstart = 1
+
+/obj/item/reagent_containers/pill/antirad
+ name = "potassium iodide pill"
+ desc = "Used to treat radition used to counter radiation poisoning."
+ icon_state = "pill18"
+ list_reagents = list("potass_iodide" = 50)
+ roundstart = 1
+
+/obj/item/reagent_containers/pill/antirad_plus
+ name = "prussian blue pill"
+ desc = "Used to treat heavy radition poisoning."
+ icon = 'modular_citadel/icons/obj/modularpills.dmi'
+ icon_state = "prussian_blue"
+ list_reagents = list("prussian_blue" = 25, "water" = 10)
+ roundstart = 1
+
+/obj/item/reagent_containers/pill/mutarad
+ name = "radiation treatment deluxe pill"
+ desc = "Used to treat heavy radition poisoning and genetic defects."
+ icon = 'modular_citadel/icons/obj/modularpills.dmi'
+ icon_state = "anit_rad_fixgene"
+ list_reagents = list("prussian_blue" = 15, "potass_iodide" = 15, "mutadone" = 15, "water" = 5)
+ roundstart = 1
+
+///////////////////////////////////////// this pill is used only in a legion mob drop
+/obj/item/reagent_containers/pill/shadowtoxin
+ name = "black pill"
+ desc = "I wouldn't eat this if I were you."
+ icon_state = "pill9"
+ color = "#454545"
+ list_reagents = list("shadowmutationtoxin" = 1)
+//////////////////////////////////////// drugs
+/obj/item/reagent_containers/pill/zoom
+ name = "zoom pill"
+ list_reagents = list("synaptizine" = 10, "nicotine" = 10, "methamphetamine" = 1)
+
+
+/obj/item/reagent_containers/pill/happy
+ name = "happy pill"
+ list_reagents = list("sugar" = 10, "space_drugs" = 10)
+
+
+/obj/item/reagent_containers/pill/lsd
+ name = "hallucinogen pill"
+ list_reagents = list("mushroomhallucinogen" = 15, "mindbreaker" = 15)
+
+
+/obj/item/reagent_containers/pill/aranesp
+ name = "speedy pill"
+ list_reagents = list("aranesp" = 10)
+
+/obj/item/reagent_containers/pill/happiness
+ name = "happiness pill"
+ desc = "It has a creepy smiling face on it."
+ icon_state = "pill_happy"
+ list_reagents = list("happiness" = 10)
+
+/obj/item/reagent_containers/pill/floorpill
+ name = "floorpill"
+ desc = "A strange pill found in the depths of maintenance"
+ icon_state = "pill21"
+ var/static/list/names = list("maintenance pill","floorpill","mystery pill","suspicious pill","strange pill")
+ var/static/list/descs = list("Your feeling is telling you no, but...","Drugs are expensive, you can't afford not to eat any pills that you find."\
+ , "Surely, there's no way this could go bad.")
+
+/obj/item/reagent_containers/pill/floorpill/Initialize()
+ list_reagents = list(get_random_reagent_id() = rand(10,50))
+ . = ..()
+ name = pick(names)
+ if(prob(20))
+ desc = pick(descs)
+
+/obj/item/reagent_containers/pill/get_belt_overlay()
+ return mutable_appearance('icons/obj/clothing/belt_overlays.dmi', "pouch")
+
+/obj/item/reagent_containers/pill/penis_enlargement
+ name = "penis enlargement pill"
+ list_reagents = list("penis_enlarger" = 10)
+
+/obj/item/reagent_containers/pill/breast_enlargement
+ name = "breast enlargement pill"
+ list_reagents = list("breast_enlarger" = 10)
diff --git a/code/modules/research/designs/medical_designs.dm b/code/modules/research/designs/medical_designs.dm
index 3a7ef68903..33568524cd 100644
--- a/code/modules/research/designs/medical_designs.dm
+++ b/code/modules/research/designs/medical_designs.dm
@@ -554,6 +554,16 @@
category = list("Medical Designs")
departmental_flags = DEPARTMENTAL_FLAG_MEDICAL
+/datum/design/cybernetic_tongue
+ name = "Cybernetic tongue"
+ desc = "A fancy cybernetic tongue."
+ id = "cybernetic_tongue"
+ build_type = PROTOLATHE
+ materials = list(MAT_METAL = 500, MAT_GLASS = 500)
+ build_path = /obj/item/organ/tongue/cybernetic
+ category = list("Medical Designs")
+ departmental_flags = DEPARTMENTAL_FLAG_MEDICAL
+
/////////////////////
//Adv Surgery Tools//
/////////////////////
diff --git a/code/modules/research/designs/misc_designs.dm b/code/modules/research/designs/misc_designs.dm
index d27c876f88..0968ce049e 100644
--- a/code/modules/research/designs/misc_designs.dm
+++ b/code/modules/research/designs/misc_designs.dm
@@ -1,4 +1,3 @@
-
/////////////////////////////////////////
/////////////////HUDs////////////////////
/////////////////////////////////////////
@@ -480,6 +479,16 @@
category = list("Equipment")
departmental_flags = DEPARTMENTAL_FLAG_SCIENCE | DEPARTMENTAL_FLAG_ENGINEERING
+/datum/design/pHmeter
+ name = "Chemical Analyser"
+ desc = "A a electrode attached to a small circuit box that will tell you the pH of a solution."
+ id = "pHmeter"
+ build_type = PROTOLATHE
+ materials = list(MAT_METAL = 1000, MAT_SILVER = 100, MAT_DIAMOND = 100)
+ build_path = /obj/item/fermichem/pHmeter
+ category = list("Equipment")
+ departmental_flags = DEPARTMENTAL_FLAG_MEDICAL | DEPARTMENTAL_FLAG_SCIENCE
+
/////////////////////////////////////////
////////////Armour///////////////////////
/////////////////////////////////////////
diff --git a/code/modules/research/nanites/nanite_programs/healing.dm b/code/modules/research/nanites/nanite_programs/healing.dm
index d3a268047e..686e0fafea 100644
--- a/code/modules/research/nanites/nanite_programs/healing.dm
+++ b/code/modules/research/nanites/nanite_programs/healing.dm
@@ -93,7 +93,7 @@
/datum/nanite_program/blood_restoring/check_conditions()
if(iscarbon(host_mob))
var/mob/living/carbon/C = host_mob
- if(C.blood_volume >= BLOOD_VOLUME_SAFE)
+ if(C.blood_volume >= (BLOOD_VOLUME_SAFE*C.blood_ratio))
return FALSE
else
return FALSE
@@ -256,4 +256,3 @@
log_game("[C] has been successfully defibrillated by nanites.")
else
playsound(C, 'sound/machines/defib_failed.ogg', 50, 0)
-
diff --git a/code/modules/research/techweb/all_nodes.dm b/code/modules/research/techweb/all_nodes.dm
index 44a3949158..9e7d9be783 100644
--- a/code/modules/research/techweb/all_nodes.dm
+++ b/code/modules/research/techweb/all_nodes.dm
@@ -69,7 +69,7 @@
display_name = "Advanced Biotechnology"
description = "Advanced Biotechnology"
prereq_ids = list("biotech")
- design_ids = list("piercesyringe", "crewpinpointer", "smoke_machine", "plasmarefiller", "limbgrower", "defibrillator", "meta_beaker", "healthanalyzer_advanced","harvester","holobarrier_med","smartdartgun","medicinalsmartdart")
+ design_ids = list("piercesyringe", "crewpinpointer", "smoke_machine", "plasmarefiller", "limbgrower", "defibrillator", "meta_beaker", "healthanalyzer_advanced","harvester","holobarrier_med","smartdartgun","medicinalsmartdart", "pHmeter")
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500)
export_price = 5000
@@ -526,7 +526,7 @@
display_name = "Cybernetic Organs"
description = "We have the technology to rebuild him."
prereq_ids = list("adv_biotech")
- design_ids = list("cybernetic_heart", "cybernetic_liver", "cybernetic_liver_u", "cybernetic_lungs", "cybernetic_lungs_u")
+ design_ids = list("cybernetic_heart", "cybernetic_liver", "cybernetic_liver_u", "cybernetic_lungs", "cybernetic_lungs_u", "cybernetic_tongue")
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500)
export_price = 5000
diff --git a/code/modules/shuttle/supply.dm b/code/modules/shuttle/supply.dm
index b54fceb3e7..2249f5f10e 100644
--- a/code/modules/shuttle/supply.dm
+++ b/code/modules/shuttle/supply.dm
@@ -60,6 +60,10 @@ GLOBAL_LIST_INIT(blacklisted_cargo_types, typecacheof(list(
for(var/a in T.GetAllContents())
if(is_type_in_typecache(a, GLOB.blacklisted_cargo_types))
return FALSE
+ if(istype(a, /obj/structure/closet))//Prevents eigenlockers from ending up at CC
+ var/obj/structure/closet/c = a
+ if(c.eigen_teleport == TRUE)
+ return FALSE
return TRUE
/obj/docking_port/mobile/supply/request(obj/docking_port/stationary/S)
diff --git a/code/modules/spells/spell_types/shapeshift.dm b/code/modules/spells/spell_types/shapeshift.dm
index 88c6ce175f..1576c55326 100644
--- a/code/modules/spells/spell_types/shapeshift.dm
+++ b/code/modules/spells/spell_types/shapeshift.dm
@@ -15,7 +15,6 @@
var/die_with_shapeshifted_form = TRUE
var/convert_damage = FALSE //If you want to convert the caster's health to the shift, and vice versa.
var/convert_damage_type = BRUTE //Since simplemobs don't have advanced damagetypes, what to convert damage back into.
-
var/shapeshift_type
var/list/possible_shapes = list(/mob/living/simple_animal/mouse,\
/mob/living/simple_animal/pet/dog/corgi,\
@@ -167,4 +166,4 @@
/datum/soullink/shapeshift/sharerDies(gibbed, mob/living/sharer)
if(source)
- source.shapeDeath(gibbed)
\ No newline at end of file
+ source.shapeDeath(gibbed)
diff --git a/code/modules/surgery/organs/liver.dm b/code/modules/surgery/organs/liver.dm
index f666fc209b..94973a8e10 100755
--- a/code/modules/surgery/organs/liver.dm
+++ b/code/modules/surgery/organs/liver.dm
@@ -1,6 +1,7 @@
#define LIVER_DEFAULT_HEALTH 100 //amount of damage required for liver failure
#define LIVER_DEFAULT_TOX_TOLERANCE 3 //amount of toxins the liver can filter out
#define LIVER_DEFAULT_TOX_LETHALITY 0.01 //lower values lower how harmful toxins are to the liver
+#define LIVER_SWELLING_MOVE_MODIFY "pharma"
/obj/item/organ/liver
name = "liver"
@@ -16,6 +17,8 @@
var/toxTolerance = LIVER_DEFAULT_TOX_TOLERANCE//maximum amount of toxins the liver can just shrug off
var/toxLethality = LIVER_DEFAULT_TOX_LETHALITY//affects how much damage toxins do to the liver
var/filterToxins = TRUE //whether to filter toxins
+ var/swelling = 0
+ var/cachedmoveCalc = 1
/obj/item/organ/liver/on_life()
var/mob/living/carbon/C = owner
@@ -45,11 +48,40 @@
if(damage > maxHealth)//cap liver damage
damage = maxHealth
+ if(swelling >= 10)
+ pharmacokinesis()
+
/obj/item/organ/liver/prepare_eat()
var/obj/S = ..()
S.reagents.add_reagent("iron", 5)
return S
+//Just in case
+/obj/item/organ/liver/Remove(mob/living/carbon/M, special = 0)
+ ..()
+ M.remove_movespeed_modifier(LIVER_SWELLING_MOVE_MODIFY)
+ M.ResetBloodVol() //At the moment, this shouldn't allow application twice. You either have this OR a thirsty ferret.
+ sizeMoveMod(1, M)
+
+//Applies some of the effects to the patient.
+/obj/item/organ/liver/proc/pharmacokinesis()
+ var/moveCalc = 1+((round(swelling) - 9)/3)
+ if(moveCalc == cachedmoveCalc)//reduce calculations
+ return
+ if(prob(5))
+ to_chat(owner, "You feel a stange ache in your side, almost like a sitch. This pain is affecting your movements and making you feel lightheaded.")
+ var/mob/living/carbon/human/H = owner
+ H.add_movespeed_modifier(LIVER_SWELLING_MOVE_MODIFY, TRUE, 100, NONE, override = TRUE, multiplicative_slowdown = moveCalc)
+ H.AdjustBloodVol(moveCalc/3)
+ sizeMoveMod(moveCalc, H)
+
+/obj/item/organ/liver/proc/sizeMoveMod(var/value, mob/living/carbon/human/H)
+ if(cachedmoveCalc == value)
+ return
+ H.next_move_modifier /= cachedmoveCalc
+ H.next_move_modifier *= value
+ cachedmoveCalc = value
+
/obj/item/organ/liver/fly
name = "insectoid liver"
icon_state = "liver-x" //xenomorph liver? It's just a black liver so it fits.
diff --git a/code/modules/surgery/organs/lungs.dm b/code/modules/surgery/organs/lungs.dm
index 64b2c2ee90..cfbb530e69 100644
--- a/code/modules/surgery/organs/lungs.dm
+++ b/code/modules/surgery/organs/lungs.dm
@@ -1,3 +1,5 @@
+#define LUNGS_MAX_HEALTH 300
+
/obj/item/organ/lungs
name = "lungs"
icon_state = "lungs"
@@ -54,8 +56,40 @@
var/crit_stabilizing_reagent = "epinephrine"
+ //health
+ var/maxHealth = LUNGS_MAX_HEALTH
+ var/damage = 0
+
+//TODO: lung health affects lung function
+/obj/item/organ/lungs/proc/adjustLungLoss(damage_mod, mob/living/carbon/M) //damage might be too low atm.
+ if (maxHealth == INFINITY)
+ return
+ if(damage+damage_mod < 0)
+ damage = 0
+ return
+
+ damage += damage_mod
+ if ((damage / maxHealth) > 1)
+ to_chat(M, "You feel your lungs collapse within your chest as you gasp for air, unable to inflate them anymore!")
+ M.emote("gasp")
+ SSblackbox.record_feedback("tally", "fermi_chem", 1, "Lungs lost")
+ qdel(src)
+ else if ((damage / maxHealth) > 0.75)
+ to_chat(M, "It's getting really hard to breathe!!")
+ M.emote("gasp")
+ M.Dizzy(3)
+ else if ((damage / maxHealth) > 0.5)
+ M.Dizzy(2)
+ to_chat(M, "Your chest is really starting to hurt.")
+ M.emote("cough")
+ else if ((damage / maxHealth) > 0.2)
+ to_chat(M, "You feel an ache within your chest.")
+ M.emote("cough")
+ M.Dizzy(1)
/obj/item/organ/lungs/proc/check_breath(datum/gas_mixture/breath, mob/living/carbon/human/H)
+//TODO: add lung damage = less oxygen gains
+ var/breathModifier = (5-(5*(damage/maxHealth)/2)) //range 2.5 - 5
if((H.status_flags & GODMODE))
return
if(HAS_TRAIT(H, TRAIT_NOBREATH))
@@ -124,7 +158,7 @@
else
H.failed_last_breath = FALSE
if(H.health >= H.crit_threshold)
- H.adjustOxyLoss(-5)
+ H.adjustOxyLoss(-breathModifier) //More damaged lungs = slower oxy rate up to a factor of half
gas_breathed = breath_gases[/datum/gas/oxygen]
H.clear_alert("not_enough_oxy")
@@ -153,7 +187,7 @@
else
H.failed_last_breath = FALSE
if(H.health >= H.crit_threshold)
- H.adjustOxyLoss(-5)
+ H.adjustOxyLoss(-breathModifier)
gas_breathed = breath_gases[/datum/gas/nitrogen]
H.clear_alert("nitro")
@@ -190,7 +224,7 @@
else
H.failed_last_breath = FALSE
if(H.health >= H.crit_threshold)
- H.adjustOxyLoss(-5)
+ H.adjustOxyLoss(-breathModifier)
gas_breathed = breath_gases[/datum/gas/carbon_dioxide]
H.clear_alert("not_enough_co2")
@@ -220,7 +254,7 @@
else
H.failed_last_breath = FALSE
if(H.health >= H.crit_threshold)
- H.adjustOxyLoss(-5)
+ H.adjustOxyLoss(-breathModifier)
gas_breathed = breath_gases[/datum/gas/plasma]
H.clear_alert("not_enough_tox")
@@ -244,6 +278,9 @@
else if(SA_pp > 0.01) // There is sleeping gas in their lungs, but only a little, so give them a bit of a warning
if(prob(20))
H.emote(pick("giggle", "laugh"))
+ SEND_SIGNAL(owner, COMSIG_ADD_MOOD_EVENT, "chemical_euphoria", /datum/mood_event/chemical_euphoria)
+ else
+ SEND_SIGNAL(owner, COMSIG_CLEAR_MOOD_EVENT, "chemical_euphoria")
// BZ
@@ -368,10 +405,13 @@
var/cold_modifier = H.dna.species.coldmod
if(breath_temperature < cold_level_3_threshold)
H.apply_damage_type(cold_level_3_damage*cold_modifier, cold_damage_type)
+ adjustLungLoss((cold_level_3_damage*cold_modifier*2), H)
if(breath_temperature > cold_level_3_threshold && breath_temperature < cold_level_2_threshold)
H.apply_damage_type(cold_level_2_damage*cold_modifier, cold_damage_type)
+ adjustLungLoss((cold_level_2_damage*cold_modifier*2), H)
if(breath_temperature > cold_level_2_threshold && breath_temperature < cold_level_1_threshold)
H.apply_damage_type(cold_level_1_damage*cold_modifier, cold_damage_type)
+ adjustLungLoss((cold_level_1_damage*cold_modifier*2), H)
if(breath_temperature < cold_level_1_threshold)
if(prob(20))
to_chat(H, "You feel [cold_message] in your [name]!")
@@ -380,10 +420,13 @@
var/heat_modifier = H.dna.species.heatmod
if(breath_temperature > heat_level_1_threshold && breath_temperature < heat_level_2_threshold)
H.apply_damage_type(heat_level_1_damage*heat_modifier, heat_damage_type)
+ adjustLungLoss((heat_level_1_damage*heat_modifier*2), H)
if(breath_temperature > heat_level_2_threshold && breath_temperature < heat_level_3_threshold)
H.apply_damage_type(heat_level_2_damage*heat_modifier, heat_damage_type)
+ adjustLungLoss((heat_level_2_damage*heat_modifier*2), H)
if(breath_temperature > heat_level_3_threshold)
H.apply_damage_type(heat_level_3_damage*heat_modifier, heat_damage_type)
+ adjustLungLoss((heat_level_3_damage*heat_modifier*2), H)
if(breath_temperature > heat_level_1_threshold)
if(prob(20))
to_chat(H, "You feel [hot_message] in your [name]!")
@@ -402,12 +445,14 @@
safe_oxygen_max = 0 // Like, at all.
safe_toxins_min = 16 //We breath THIS!
safe_toxins_max = 0
+ maxHealth = INFINITY//I don't understand how plamamen work, so I'm not going to try t give them special lungs atm
/obj/item/organ/lungs/cybernetic
name = "cybernetic lungs"
desc = "A cybernetic version of the lungs found in traditional humanoid entities. It functions the same as an organic lung and is merely meant as a replacement."
icon_state = "lungs-c"
synthetic = TRUE
+ maxHealth = 400
/obj/item/organ/lungs/cybernetic/emp_act()
. = ..()
@@ -427,6 +472,7 @@
cold_level_1_threshold = 200
cold_level_2_threshold = 140
cold_level_3_threshold = 100
+ maxHealth = 550
/obj/item/organ/lungs/ashwalker
name = "ash lungs"
diff --git a/code/modules/surgery/organs/organ_internal.dm b/code/modules/surgery/organs/organ_internal.dm
index b16967b6b0..9f910de9a9 100644
--- a/code/modules/surgery/organs/organ_internal.dm
+++ b/code/modules/surgery/organs/organ_internal.dm
@@ -153,6 +153,17 @@
// if they have no mutant tongues, give them a regular one
T.Insert(src)
+ else
+ var/obj/item/organ/tongue/oT = getorganslot(ORGAN_SLOT_TONGUE)
+ if(oT.name == "fluffy tongue")
+ var/obj/item/organ/tongue/T
+ if(dna && dna.species && dna.species.mutanttongue)
+ T = new dna.species.mutanttongue()
+ else
+ T = new()
+ oT.Remove(src)
+ qdel(oT)
+ T.Insert(src)
if(!getorganslot(ORGAN_SLOT_EYES))
var/obj/item/organ/eyes/E
diff --git a/code/modules/surgery/organs/tongue.dm b/code/modules/surgery/organs/tongue.dm
index ccfc21f878..3eaaa8de92 100644
--- a/code/modules/surgery/organs/tongue.dm
+++ b/code/modules/surgery/organs/tongue.dm
@@ -1,3 +1,5 @@
+#define TONGUE_MAX_HEALTH 60
+
/obj/item/organ/tongue
name = "tongue"
desc = "A fleshy muscle mostly used for lying."
@@ -8,6 +10,8 @@
var/list/languages_possible
var/say_mod = null
var/taste_sensitivity = 15 // lower is more sensitive.
+ var/maxHealth = TONGUE_MAX_HEALTH
+ var/damage = 0
var/modifies_speech = FALSE
var/static/list/languages_possible_base = typecacheof(list(
/datum/language/common,
@@ -27,6 +31,29 @@
/obj/item/organ/tongue/proc/handle_speech(datum/source, list/speech_args)
+/obj/item/organ/tongue/proc/adjustTongueLoss(mob/living/carbon/M, damage_mod)
+ if (maxHealth == "alien")
+ return
+ if (maxHealth == "bone")
+ var/target = M.get_bodypart(BODY_ZONE_HEAD)
+ M.apply_damage(damage_mod, BURN, target)
+ to_chat(M, "The drink burns your skull! Oof, your bones!")
+ return
+ if(damage+damage_mod < 0)
+ damage = 0
+ return
+
+ damage += damage_mod
+ if ((damage / maxHealth) > 1)
+ to_chat(M, "Your tongue is singed beyond recognition, and disintegrates!")
+ SSblackbox.record_feedback("tally", "fermi_chem", 1, "Tongues lost to Fermi")
+ qdel(src)
+ else if ((damage / maxHealth) > 0.85)
+ to_chat(M, "Your tongue feels like it's about to fall out!.")
+ else if ((damage / maxHealth) > 0.5)
+ to_chat(M, "Your tongue is really starting to hurt.")
+
+
/obj/item/organ/tongue/Insert(mob/living/carbon/M, special = 0)
..()
if(say_mod && M.dna && M.dna.species)
@@ -51,6 +78,7 @@
icon_state = "tonguelizard"
say_mod = "hisses"
taste_sensitivity = 10 // combined nose + tongue, extra sensitive
+ maxHealth = 40 //extra sensitivity means tongue is more susceptible to damage
modifies_speech = TRUE
/obj/item/organ/tongue/lizard/handle_speech(datum/source, list/speech_args)
@@ -68,6 +96,7 @@
icon_state = "tonguefly"
say_mod = "buzzes"
taste_sensitivity = 25 // you eat vomit, this is a mercy
+ maxHealth = 80 //years of eatting trash has made your tongue strong
modifies_speech = TRUE
/obj/item/organ/tongue/fly/handle_speech(datum/source, list/speech_args)
@@ -85,6 +114,7 @@
icon_state = "tongueayylmao"
say_mod = "gibbers"
taste_sensitivity = 101 // ayys cannot taste anything.
+ maxHealth = 120 //Ayys probe a lot
modifies_speech = TRUE
/obj/item/organ/tongue/abductor/handle_speech(datum/source, list/speech_args)
@@ -113,6 +143,7 @@
icon_state = "tonguezombie"
say_mod = "moans"
taste_sensitivity = 32
+ maxHealth = 65 //Stop! It's already dead...!
modifies_speech = TRUE
/obj/item/organ/tongue/zombie/handle_speech(datum/source, list/speech_args)
@@ -137,6 +168,7 @@
icon_state = "tonguexeno"
say_mod = "hisses"
taste_sensitivity = 10 // LIZARDS ARE ALIENS CONFIRMED
+ maxHealth = "alien" //Their blood is acid, so, no, though a tongueless xeno might be funny
modifies_speech = TRUE // not really, they just hiss
var/static/list/languages_possible_alien = typecacheof(list(
/datum/language/xenocommon,
@@ -159,6 +191,7 @@
say_mod = "rattles"
attack_verb = list("bitten", "chattered", "chomped", "enamelled", "boned")
taste_sensitivity = 101 // skeletons cannot taste anything
+ maxHealth = "bone" //Take brute damage instead
modifies_speech = TRUE
var/chattering = FALSE
var/phomeme_type = "sans"
@@ -181,6 +214,7 @@
name = "plasma bone \"tongue\""
desc = "Like animated skeletons, Plasmamen vibrate their teeth in order to produce speech."
icon_state = "tongueplasma"
+ maxHealth = "alien"
modifies_speech = FALSE
/obj/item/organ/tongue/robot
@@ -192,6 +226,7 @@
attack_verb = list("beeped", "booped")
modifies_speech = TRUE
taste_sensitivity = 25 // not as good as an organic tongue
+ maxHealth = 100 //RoboTongue!
var/electronics_magic = TRUE
/obj/item/organ/tongue/robot/can_speak_in_language(datum/language/dt)
@@ -200,8 +235,39 @@
/obj/item/organ/tongue/robot/handle_speech(datum/source, list/speech_args)
speech_args[SPEECH_SPANS] |= SPAN_ROBOT
+/obj/item/organ/tongue/fluffy
+ name = "fluffy tongue"
+ desc = "OwO what's this?"
+ icon_state = "tonguefluffy"
+ taste_sensitivity = 10 // extra sensitive and inquisitive uwu
+ maxHealth = 35 //Sensitive tongue!
+ modifies_speech = TRUE
+
+/obj/item/organ/tongue/fluffy/handle_speech(datum/source, list/speech_args)
+ var/message = speech_args[SPEECH_MESSAGE]
+ if(copytext(message, 1, 2) != "*")
+ message = replacetext(message, "ne", "nye")
+ message = replacetext(message, "nu", "nyu")
+ message = replacetext(message, "na", "nya")
+ message = replacetext(message, "no", "nyo")
+ message = replacetext(message, "ove", "uv")
+ message = replacetext(message, "l", "w")
+ message = replacetext(message, "r", "w")
+ message = lowertext(message)
+ speech_args[SPEECH_MESSAGE] = message
+
+/obj/item/organ/tongue/cybernetic
+ name = "cybernetic tongue"
+ desc = "A state of the art robotic tongue that can detect the pH of anything drank."
+ icon_state = "tonguecybernetic"
+ taste_sensitivity = 10
+ maxHealth = 60 //It's robotic!
+
+/obj/item/organ/tongue/cybernetic/handle_speech(datum/source, list/speech_args)
+ speech_args[SPEECH_SPANS] |= SPAN_ROBOT
+
/obj/item/organ/tongue/robot/ipc
name = "positronic voicebox"
say_mod = "beeps"
desc = "A voice synthesizer used by IPCs to smoothly interface with organic lifeforms."
- electronics_magic = FALSE
\ No newline at end of file
+ electronics_magic = FALSE
diff --git a/code/modules/surgery/organs/vocal_cords.dm b/code/modules/surgery/organs/vocal_cords.dm
index ad13de0a01..8103224306 100644
--- a/code/modules/surgery/organs/vocal_cords.dm
+++ b/code/modules/surgery/organs/vocal_cords.dm
@@ -575,7 +575,7 @@
cooldown = COOLDOWN_MEME
for(var/V in listeners)
var/mob/living/carbon/human/H = V
- if(H.canbearoused && H.has_dna()) // probably a redundant check but for good measure
+ if(H.canbearoused && H.has_dna() && HAS_TRAIT(H, TRAIT_NYMPHO)) // probably a redundant check but for good measure
H.mob_climax(forced_climax=TRUE)
//DAB
@@ -608,6 +608,867 @@
return cooldown
+//////////////////////////////////////
+///////ENTHRAL VELVET CHORDS//////////
+//////////////////////////////////////
+
+//Heavily modified voice of god code
+/obj/item/organ/vocal_cords/velvet
+ name = "Velvet chords"
+ desc = "The voice spoken from these just make you want to drift off, sleep and obey."
+ icon_state = "velvet_chords"
+ actions_types = list(/datum/action/item_action/organ_action/velvet)
+ spans = list("velvet")
+
+/datum/action/item_action/organ_action/velvet
+ name = "Velvet chords"
+ var/obj/item/organ/vocal_cords/velvet/cords = null
+ //icon_icon = 'icons/mob/screen_alert.dmi'
+ //button_icon_state = "velvet_chords"
+ //icon = 'icons/mob/screen_alert.dmi'
+ //icon_state = "in_love"
+
+/datum/action/item_action/organ_action/velvet/New()
+ ..()
+ cords = target
+
+/datum/action/item_action/organ_action/velvet/IsAvailable()
+ return TRUE
+
+/datum/action/item_action/organ_action/velvet/Trigger()
+ . = ..()
+ var/command = input(owner, "Speak in a sultry tone", "Command")
+ if(QDELETED(src) || QDELETED(owner))
+ return
+ if(!command)
+ return
+ owner.say(".x[command]")
+
+/obj/item/organ/vocal_cords/velvet/can_speak_with()
+ return TRUE
+
+/obj/item/organ/vocal_cords/velvet/handle_speech(message) //actually say the message
+ owner.say(message, spans = spans, sanitize = FALSE)
+ velvetspeech(message, owner, 1)
+
+//////////////////////////////////////
+///////////FermiChem//////////////////
+//////////////////////////////////////
+//Removed span_list from input arguments.
+/proc/velvetspeech(message, mob/living/user, base_multiplier = 1, message_admins = FALSE, debug = FALSE)
+
+ if(!user || !user.can_speak() || user.stat)
+ return 0 //no cooldown
+
+ var/log_message = message
+
+ //FIND THRALLS
+ message = lowertext(message)
+ var/mob/living/list/listeners = list()
+ for(var/mob/living/L in get_hearers_in_view(8, user))
+ if(L.can_hear() && !L.anti_magic_check(FALSE, TRUE) && L.stat != DEAD)
+ if(L.has_status_effect(/datum/status_effect/chem/enthrall))//Check to see if they have the status
+ var/datum/status_effect/chem/enthrall/E = L.has_status_effect(/datum/status_effect/chem/enthrall)//Check to see if pet is on cooldown from last command and if the master is right
+ if(E.master != user)
+ continue
+ if(ishuman(L))
+ var/mob/living/carbon/human/H = L
+ if(istype(H.ears, /obj/item/clothing/ears/earmuffs))
+ continue
+
+ if (E.cooldown > 0)//If they're on cooldown you can't give them more commands.
+ continue
+ listeners += L
+
+ if(!listeners.len)
+ return 0
+
+ //POWER CALCULATIONS
+
+ var/power_multiplier = base_multiplier
+
+ // Not sure I want to give extra power to anyone at the moment...? We'll see how it turns out
+ if(user.mind)
+ //Chaplains are very good at indoctrinating
+ if(user.mind.assigned_role == "Chaplain")
+ power_multiplier *= 1.2
+ //Command staff has authority
+ if(user.mind.assigned_role in GLOB.command_positions)
+ power_multiplier *= 1.1
+ //Why are you speaking
+ if(user.mind.assigned_role == "Mime")
+ power_multiplier *= 0.5
+
+ //Cultists are closer to their gods and are better at indoctrinating
+ if(iscultist(user))
+ power_multiplier *= 1.2
+ else if (is_servant_of_ratvar(user))
+ power_multiplier *= 1.2
+ else if (is_devil(user))//The devil is supposed to be seductive, right?
+ power_multiplier *= 1.2
+
+ //range = 0.5 - 1.4~
+ //most cases = 1
+
+ //Try to check if the speaker specified a name or a job to focus on
+ var/list/specific_listeners = list()
+ var/found_string = null
+
+ //Get the proper job titles
+ message = get_full_job_name(message)
+
+ for(var/V in listeners)
+ var/mob/living/L = V
+ if(dd_hasprefix(message, L.real_name))
+ specific_listeners += L //focus on those with the specified name
+ //Cut out the name so it doesn't trigger commands
+ found_string = L.real_name
+ power_multiplier += 0.5
+
+ else if(dd_hasprefix(message, L.first_name()))
+ specific_listeners += L //focus on those with the specified name
+ //Cut out the name so it doesn't trigger commands
+ found_string = L.first_name()
+ power_multiplier += 0.5
+
+ else if(L.mind && L.mind.assigned_role && dd_hasprefix(message, L.mind.assigned_role))
+ specific_listeners += L //focus on those with the specified job
+ //Cut out the job so it doesn't trigger commands
+ found_string = L.mind.assigned_role
+ power_multiplier += 0.25
+
+ if(specific_listeners.len)
+ listeners = specific_listeners
+ //power_multiplier *= (1 + (1/specific_listeners.len)) //Put this is if it becomes OP, power is judged internally on a thrall, so shouldn't be nessicary.
+ message = copytext(message, 0, 1)+copytext(message, 1 + length(found_string), length(message) + 1)//I have no idea what this does
+
+ var/obj/item/organ/tongue/T = user.getorganslot(ORGAN_SLOT_TONGUE)
+ if (T.name == "fluffy tongue") //If you sound hillarious, it's hard to take you seriously. This is a way for other players to combat/reduce their effectiveness.
+ power_multiplier *= 0.75
+
+ if(debug == TRUE)
+ to_chat(world, "[user]'s power is [power_multiplier].")
+
+ //Mixables
+ var/static/regex/enthral_words = regex("relax|obey|love|serve|docile|so easy|ara ara")
+ var/static/regex/reward_words = regex("good boy|good girl|good pet|good job")
+ var/static/regex/punish_words = regex("bad boy|bad girl|bad pet|bad job")
+ //phase 0
+ var/static/regex/saymyname_words = regex("say my name|who am i|whoami")
+ var/static/regex/wakeup_words = regex("revert|awaken|snap") //works
+ //phase1
+ var/static/regex/petstatus_words = regex("how are you|what is your status|are you okay")
+ var/static/regex/silence_words = regex("shut up|silence|be silent|ssh|quiet|hush")
+ var/static/regex/speak_words = regex("talk to me|speak")
+ var/static/regex/antiresist_words = regex("unable to resist|give in")//useful if you think your target is resisting a lot
+ var/static/regex/resist_words = regex("resist|snap out of it|fight")//useful if two enthrallers are fighting
+ var/static/regex/forget_words = regex("forget|muddled|awake and forget")
+ var/static/regex/attract_words = regex("come here|come to me|get over here|attract")
+ //phase 2
+ var/static/regex/orgasm_words = regex("cum|orgasm|climax|squirt|heyo") //wah, lewd
+ var/static/regex/awoo_words = regex("howl|awoo|bark")
+ var/static/regex/nya_words = regex("nya|meow|mewl")
+ var/static/regex/sleep_words = regex("sleep|slumber|rest")
+ var/static/regex/strip_words = regex("strip|derobe|nude")
+ var/static/regex/walk_words = regex("slow down|walk")
+ var/static/regex/run_words = regex("run|speed up")
+ var/static/regex/liedown_words = regex("lie down") //TO ADD
+ var/static/regex/knockdown_words = regex("drop|fall|trip|knockdown|kneel")
+ //phase 3
+ var/static/regex/statecustom_words = regex("state triggers|state your triggers")
+ var/static/regex/custom_words = regex("new trigger|listen to me")
+ var/static/regex/custom_words_words = regex("speak|echo|shock|cum|kneel|strip|trance")//What a descriptive name!
+ var/static/regex/custom_echo = regex("obsess|fills your mind|loop")
+ var/static/regex/instill_words = regex("feel|entice|overwhel")
+ var/static/regex/recognise_words = regex("recognise me|did you miss me?")
+ var/static/regex/objective_words = regex("new objective|obey this command|unable to resist|compulsed")
+ var/static/regex/heal_words = regex("live|heal|survive|mend|life|pets never die")
+ var/static/regex/stun_words = regex("stop|wait|stand still|hold on|halt")
+ var/static/regex/hallucinate_words = regex("get high|hallucinate")
+ var/static/regex/hot_words = regex("heat|hot|hell")
+ var/static/regex/cold_words = regex("cold|cool down|chill|freeze")
+ var/static/regex/getup_words = regex("get up")
+ var/static/regex/pacify_words = regex("more and more docile|complaisant|friendly|pacifist")
+ var/static/regex/charge_words = regex("charge|oorah|attack")
+
+ var/distancelist = list(2,2,1.5,1.3,1.15,1,0.8,0.6,0.5,0.25)
+
+ //CALLBACKS ARE USED FOR MESSAGES BECAUSE SAY IS HANDLED AFTER THE PROCESSING.
+
+ //Tier 1
+ //ENTHRAL mixable (works I think)
+ if(findtext(message, enthral_words))
+ for(var/V in listeners)
+ var/mob/living/L = V
+ var/datum/status_effect/chem/enthrall/E = L.has_status_effect(/datum/status_effect/chem/enthrall)
+ power_multiplier *= distancelist[get_dist(user, V)+1]
+ if(L == user)
+ continue
+ if(length(message))
+ E.enthrallTally += (power_multiplier*(((length(message))/200) + 1)) //encourage players to say more than one word.
+ else
+ E.enthrallTally += power_multiplier*1.25 //thinking about it, I don't know how this can proc
+ if(L.canbearoused)
+ if(L.client?.prefs.lewdchem)
+ addtimer(CALLBACK(GLOBAL_PROC, .proc/to_chat, L, "[E.enthrallGender] is so nice to listen to."), 5)
+ E.cooldown += 1
+
+ //REWARD mixable works
+ if(findtext(message, reward_words))
+ for(var/V in listeners)
+ var/mob/living/L = V
+ var/datum/status_effect/chem/enthrall/E = L.has_status_effect(/datum/status_effect/chem/enthrall)
+ power_multiplier *= distancelist[get_dist(user, V)+1]
+ if(L == user)
+ continue
+ if (L.client?.prefs.lewdchem)
+ addtimer(CALLBACK(GLOBAL_PROC, .proc/to_chat, L, "[E.enthrallGender] has praised me!!"), 5)
+ if(HAS_TRAIT(L, TRAIT_NYMPHO))
+ L.adjustArousalLoss(2*power_multiplier)
+ if(HAS_TRAIT(L, TRAIT_MASO))
+ E.enthrallTally -= power_multiplier
+ E.resistanceTally += power_multiplier
+ E.cooldown += 1
+ else
+ addtimer(CALLBACK(GLOBAL_PROC, .proc/to_chat, L, "I've been praised for doing a good job!"), 5)
+ E.resistanceTally -= power_multiplier
+ E.enthrallTally += power_multiplier
+ var/descmessage = "[(L.client?.prefs.lewdchem?"I feel so happy! I'm a good pet who [E.enthrallGender] loves!":"I did a good job!")]"
+ SEND_SIGNAL(L, COMSIG_ADD_MOOD_EVENT, "enthrallpraise", /datum/mood_event/enthrallpraise, descmessage)
+ E.cooldown += 1
+
+ //PUNISH mixable works
+ else if(findtext(message, punish_words))
+ for(var/V in listeners)
+ var/mob/living/L = V
+ var/datum/status_effect/chem/enthrall/E = L.has_status_effect(/datum/status_effect/chem/enthrall)
+ var/descmessage = "[(L.client?.prefs.lewdchem?"I've failed [E.enthrallGender]... What a bad, bad pet!":"I did a bad job...")]"
+ if(L == user)
+ continue
+ if (L.client?.prefs.lewdchem)
+ if(HAS_TRAIT(L, TRAIT_MASO))
+ L.adjustArousalLoss(3*power_multiplier)
+ descmessage += "And yet, it feels so good..!" //I don't really understand masco, is this the right sort of thing they like?
+ E.enthrallTally += power_multiplier
+ E.resistanceTally -= power_multiplier
+ addtimer(CALLBACK(GLOBAL_PROC, .proc/to_chat, L, "I've let [E.enthrallGender] down...!"), 5)
+ else
+ addtimer(CALLBACK(GLOBAL_PROC, .proc/to_chat, L, "I've let [E.enthrallGender] down..."), 5)
+ else
+ addtimer(CALLBACK(GLOBAL_PROC, .proc/to_chat, L, "I've failed [E.master]..."), 5)
+ E.resistanceTally += power_multiplier
+ E.enthrallTally += power_multiplier
+ E.cooldown += 1
+ SEND_SIGNAL(L, COMSIG_ADD_MOOD_EVENT, "enthrallscold", /datum/mood_event/enthrallscold, descmessage)
+ E.cooldown += 1
+
+
+
+ //teir 0
+ //SAY MY NAME works
+ if((findtext(message, saymyname_words)))
+ for(var/V in listeners)
+ var/mob/living/carbon/C = V
+ var/datum/status_effect/chem/enthrall/E = C.has_status_effect(/datum/status_effect/chem/enthrall)
+ REMOVE_TRAIT(C, TRAIT_MUTE, "enthrall")
+ C.silent = 0
+ if(C.client?.prefs.lewdchem)
+ addtimer(CALLBACK(C, /atom/movable/proc/say, "[E.enthrallGender]"), 5)
+ else
+ addtimer(CALLBACK(C, /atom/movable/proc/say, "[E.master]"), 5)
+
+ //WAKE UP
+ else if((findtext(message, wakeup_words)))
+ for(var/V in listeners)
+ var/mob/living/L = V
+ var/datum/status_effect/chem/enthrall/E = L.has_status_effect(/datum/status_effect/chem/enthrall)
+ L.SetSleeping(0)//Can you hear while asleep?
+ switch(E.phase)
+ if(0)
+ E.phase = 3
+ E.status = null
+ user.emote("snap")
+ if(L.client?.prefs.lewdchem)
+ addtimer(CALLBACK(GLOBAL_PROC, .proc/to_chat, L, "The snapping of your [E.enthrallGender]'s fingers brings you back to your enthralled state, obedient and ready to serve."), 5)
+ else
+ addtimer(CALLBACK(GLOBAL_PROC, .proc/to_chat, L, "The snapping of [E.master]'s fingers brings you back to being under their influence."), 5)
+ to_chat(user, "You wake up [L]!")
+
+ //tier 1
+
+ //PETSTATUS i.e. how they are
+ else if((findtext(message, petstatus_words)))
+ for(var/V in listeners)
+ var/mob/living/carbon/human/H = V
+ var/datum/status_effect/chem/enthrall/E = H.has_status_effect(/datum/status_effect/chem/enthrall)
+ REMOVE_TRAIT(H, TRAIT_MUTE, "enthrall")
+ var/speaktrigger = ""
+ //phase
+ switch(E.phase)
+ if(0)
+ continue
+ if(1)
+ addtimer(CALLBACK(H, /atom/movable/proc/say, "I feel happy being with you."), 5)
+ continue
+ if(2)
+ speaktrigger += "[(H.client?.prefs.lewdchem?"I think I'm in love with you... ":"I find you really inspirational, ")]" //'
+ if(3)
+ speaktrigger += "[(H.client?.prefs.lewdchem?"I'm devoted to being your pet":"I'm commited to following your cause!")]! "
+ if(4)
+ speaktrigger += "[(H.client?.prefs.lewdchem?"You are my whole world and all of my being belongs to you, ":"I cannot think of anything else but aiding your cause, ")] "//Redflags!!
+
+ //mood
+ GET_COMPONENT_FROM(mood, /datum/component/mood, H)
+ switch(mood.sanity)
+ if(SANITY_GREAT to INFINITY)
+ speaktrigger += "I'm beyond elated!! " //did you mean byond elated? hohoho
+ if(SANITY_NEUTRAL to SANITY_GREAT)
+ speaktrigger += "I'm really happy! "
+ if(SANITY_DISTURBED to SANITY_NEUTRAL)
+ speaktrigger += "I'm a little sad, "
+ if(SANITY_UNSTABLE to SANITY_DISTURBED)
+ speaktrigger += "I'm really upset, "
+ if(SANITY_CRAZY to SANITY_UNSTABLE)
+ speaktrigger += "I'm about to fall apart without you! "
+ if(SANITY_INSANE to SANITY_CRAZY)
+ speaktrigger += "Hold me, please.. "
+
+ //Withdrawal
+ switch(E.withdrawalTick)
+ if(10 to 36) //denial
+ speaktrigger += "I missed you, "
+ if(36 to 66) //barganing
+ speaktrigger += "I missed you, but I knew you'd come back for me! "
+ if(66 to 90) //anger
+ speaktrigger += "I couldn't take being away from you like that, "
+ if(90 to 140) //depression
+ speaktrigger += "I was so scared you'd never come back, "
+ if(140 to INFINITY) //acceptance
+ speaktrigger += "I'm hurt that you left me like that... I felt so alone... "
+
+ //hunger
+ switch(H.nutrition)
+ if(0 to NUTRITION_LEVEL_STARVING)
+ speaktrigger += "I'm famished, please feed me..! "
+ if(NUTRITION_LEVEL_STARVING to NUTRITION_LEVEL_HUNGRY)
+ speaktrigger += "I'm so hungry... "
+ if(NUTRITION_LEVEL_HUNGRY to NUTRITION_LEVEL_FED)
+ speaktrigger += "I'm hungry, "
+ if(NUTRITION_LEVEL_FED to NUTRITION_LEVEL_WELL_FED)
+ speaktrigger += "I'm sated, "
+ if(NUTRITION_LEVEL_WELL_FED to NUTRITION_LEVEL_FULL)
+ speaktrigger += "I've a full belly! "
+ if(NUTRITION_LEVEL_FULL to INFINITY)
+ speaktrigger += "I'm fat... "
+
+ //health
+ switch(H.health)
+ if(100 to INFINITY)
+ speaktrigger += "I feel fit, "
+ if(80 to 99)
+ speaktrigger += "I ache a little bit, "
+ if(40 to 80)
+ speaktrigger += "I'm really hurt, "
+ if(0 to 40)
+ speaktrigger += "I'm in a lot of pain, help! "
+ if(-INFINITY to 0)
+ speaktrigger += "I'm barely concious and in so much pain, please help me! "
+ //toxin
+ switch(H.getToxLoss())
+ if(10 to 30)
+ speaktrigger += "I feel a bit queasy... "
+ if(30 to 60)
+ speaktrigger += "I feel nauseous... "
+ if(60 to INFINITY)
+ speaktrigger += "My head is pounding and I feel like I'm going to be sick... "
+ //oxygen
+ if (H.getOxyLoss() >= 25)
+ speaktrigger += "I can't breathe! "
+ //blind
+ if (HAS_TRAIT(H, TRAIT_BLIND))
+ speaktrigger += "I can't see! "
+ //deaf..?
+ if (HAS_TRAIT(H, TRAIT_DEAF))//How the heck you managed to get here I have no idea, but just in case!
+ speaktrigger += "I can barely hear you! "
+ //And the brain damage. And the brain damage. And the brain damage. And the brain damage. And the brain damage.
+ switch(H.getBrainLoss())
+ if(20 to 40)
+ speaktrigger += "I have a mild head ache, "
+ if(40 to 80)
+ speaktrigger += "I feel disorentated and confused, "
+ if(80 to 120)
+ speaktrigger += "My head feels like it's about to explode, "
+ if(120 to 160)
+ speaktrigger += "You are the only thing keeping my mind sane, "
+ if(160 to INFINITY)
+ speaktrigger += "I feel like I'm on the brink of losing my mind, "
+
+ //horny
+ if(HAS_TRAIT(H, TRAIT_NYMPHO) && H.canbearoused && H.client?.prefs.lewdchem)
+ switch(H.getArousalLoss())
+ if(40 to 60)
+ speaktrigger += "I'm feeling a little horny, "
+ if(60 to 80)
+ speaktrigger += "I'm feeling horny, "
+ if(80 to INFINITY)
+ speaktrigger += "I'm really, really horny, "
+
+ //collar
+ if(istype(H.wear_neck, /obj/item/clothing/neck/petcollar))
+ speaktrigger += "I love the collar you gave me, "
+ //End
+ if(H.client?.prefs.lewdchem)
+ speaktrigger += "[E.enthrallGender]!"
+ else
+ speaktrigger += "[user.first_name()]!"
+ //say it!
+ addtimer(CALLBACK(H, /atom/movable/proc/say, "[speaktrigger]"), 5)
+ E.cooldown += 1
+
+ //SILENCE
+ else if((findtext(message, silence_words)))
+ for(var/mob/living/carbon/C in listeners)
+ var/datum/status_effect/chem/enthrall/E = C.has_status_effect(/datum/status_effect/chem/enthrall)
+ power_multiplier *= distancelist[get_dist(user, C)+1]
+ if (E.phase >= 3) //If target is fully enthralled,
+ ADD_TRAIT(C, TRAIT_MUTE, "enthrall")
+ else
+ C.silent += ((10 * power_multiplier) * E.phase)
+ addtimer(CALLBACK(GLOBAL_PROC, .proc/to_chat, C, "You are unable to speak!"), 5)
+ to_chat(user, "You silence [C].")
+ E.cooldown += 3
+
+ //SPEAK
+ else if((findtext(message, speak_words)))//fix
+ for(var/mob/living/carbon/C in listeners)
+ var/datum/status_effect/chem/enthrall/E = C.has_status_effect(/datum/status_effect/chem/enthrall)
+ REMOVE_TRAIT(C, TRAIT_MUTE, "enthrall")
+ C.silent = 0
+ E.cooldown += 3
+ to_chat(user, "You [(C.client?.prefs.lewdchem?"allow [C] to speak again":"encourage [C] to speak again")].")
+
+
+ //Antiresist
+ else if((findtext(message, antiresist_words)))
+ for(var/V in listeners)
+ var/mob/living/L = V
+ var/datum/status_effect/chem/enthrall/E = L.has_status_effect(/datum/status_effect/chem/enthrall)
+ E.status = "Antiresist"
+ addtimer(CALLBACK(GLOBAL_PROC, .proc/to_chat, L, "Your mind clouds over, as you find yourself unable to resist!"), 5)
+ E.statusStrength = (1 * power_multiplier * E.phase)
+ E.cooldown += 15//Too short? yes, made 15
+ to_chat(user, "You frustrate [L]'s attempts at resisting.")
+
+ //RESIST
+ else if((findtext(message, resist_words)))
+ for(var/mob/living/carbon/C in listeners)
+ var/datum/status_effect/chem/enthrall/E = C.has_status_effect(/datum/status_effect/chem/enthrall)
+ power_multiplier *= distancelist[get_dist(user, C)+1]
+ E.deltaResist += (power_multiplier)
+ E.owner_resist()
+ E.cooldown += 2
+ addtimer(CALLBACK(GLOBAL_PROC, .proc/to_chat, C, "You are spurred into resisting from [user]'s words!'"), 5)
+ to_chat(user, "You spark resistance in [C].")
+
+ //FORGET (A way to cancel the process)
+ else if((findtext(message, forget_words)))
+ for(var/mob/living/carbon/C in listeners)
+ var/datum/status_effect/chem/enthrall/E = C.has_status_effect(/datum/status_effect/chem/enthrall)
+ if(E.phase == 4)
+ addtimer(CALLBACK(GLOBAL_PROC, .proc/to_chat, C, "You're unable to forget about [(C.client?.prefs.lewdchem?"the dominating presence of [E.enthrallGender]":"[E.master]")]!"), 5)
+ continue
+ addtimer(CALLBACK(GLOBAL_PROC, .proc/to_chat, C, "You wake up, forgetting everything that just happened. You must've dozed off..? How embarassing!"), 5)
+ C.Sleeping(50)
+ switch(E.phase)
+ if(1 to 2)
+ E.phase = -1
+ to_chat(C, "You have no recollection of being enthralled by [E.master]!")
+ to_chat(user, "You revert [C] back to their state before enthrallment.")
+ if(3)
+ E.phase = 0
+ E.cooldown = 0
+ if(C.client?.prefs.lewdchem)
+ addtimer(CALLBACK(GLOBAL_PROC, .proc/to_chat, C, "You revert to yourself before being enthralled by your [E.enthrallGender], with no memory of what happened."), 5)
+ else
+ addtimer(CALLBACK(GLOBAL_PROC, .proc/to_chat, C, "You revert to who you were before, with no memory of what happened with [E.master]."), 5)
+ to_chat(user, "You put [C] into a sleeper state, ready to turn them back at the snap of your fingers.")
+
+ //ATTRACT
+ else if((findtext(message, attract_words)))
+ for(var/V in listeners)
+ var/mob/living/L = V
+ var/datum/status_effect/chem/enthrall/E = L.has_status_effect(/datum/status_effect/chem/enthrall)
+ L.throw_at(get_step_towards(user,L), 3 * power_multiplier, 1 * power_multiplier)
+ E.cooldown += 3
+ addtimer(CALLBACK(GLOBAL_PROC, .proc/to_chat, L, "You are drawn towards [user]!"), 5)
+ to_chat(user, "You draw [L] towards you!")
+
+
+ //teir 2
+
+ /* removed for now
+ //ORGASM
+ else if((findtext(message, orgasm_words)))
+ for(var/V in listeners)
+ var/mob/living/carbon/human/H = V
+ var/datum/status_effect/chem/enthrall/E = H.has_status_effect(/datum/status_effect/chem/enthrall)
+ if(E.phase > 1)
+ if(HAS_TRAIT(H, TRAIT_NYMPHO) && H.canbearoused && H.client?.prefs.lewdchem) // probably a redundant check but for good measure
+ addtimer(CALLBACK(GLOBAL_PROC, .proc/to_chat, H, "Your [E.enthrallGender] pushes you over the limit, overwhelming your body with pleasure."), 5)
+ H.mob_climax(forced_climax=TRUE)
+ H.SetStun(20)
+ H.setArousalLoss(H.min_arousal)
+ E.resistanceTally = 0 //makes resistance 0, but resets arousal, resistance buildup is faster unaroused (massively so).
+ E.enthrallTally += power_multiplier
+ E.cooldown += 6
+ else
+ H.throw_at(get_step_towards(user,H), 3 * power_multiplier, 1 * power_multiplier)
+ */
+
+
+ //awoo
+ else if((findtext(message, awoo_words)))
+ for(var/V in listeners)
+ var/mob/living/carbon/human/H = V
+ var/datum/status_effect/chem/enthrall/E = H.has_status_effect(/datum/status_effect/chem/enthrall)
+ switch(E.phase)
+ if(2 to INFINITY)
+ H.say("*awoo")
+ E.cooldown += 1
+
+ //Nya
+ else if((findtext(message, nya_words)))
+ for(var/V in listeners)
+ var/mob/living/carbon/human/H = V
+ var/datum/status_effect/chem/enthrall/E = H.has_status_effect(/datum/status_effect/chem/enthrall)
+ switch(E.phase)
+ if(2 to INFINITY)
+ playsound(get_turf(H), pick('sound/effects/meow1.ogg', 'modular_citadel/sound/voice/nya.ogg'), 50, 1, -1) //I'm very tempted to write a Fermis clause that makes them merowr.ogg if it's me. But, I also don't think snowflakism is okay. I would've gotten away for it too, if it wern't for my morals.
+ H.emote("me", 1, "lets out a nya!")
+ E.cooldown += 1
+
+ //SLEEP
+ else if((findtext(message, sleep_words)))
+ for(var/mob/living/carbon/C in listeners)
+ var/datum/status_effect/chem/enthrall/E = C.has_status_effect(/datum/status_effect/chem/enthrall)
+ switch(E.phase)
+ if(2 to INFINITY)
+ C.Sleeping(45 * power_multiplier)
+ E.cooldown += 10
+ addtimer(CALLBACK(GLOBAL_PROC, .proc/to_chat, C, "Drowsiness suddenly overwhelms you as you fall asleep!"), 5)
+ to_chat(user, "You send [C] to sleep.")
+
+ //STRIP
+ else if((findtext(message, strip_words)))
+ for(var/V in listeners)
+ var/mob/living/carbon/human/H = V
+ var/datum/status_effect/chem/enthrall/E = H.has_status_effect(/datum/status_effect/chem/enthrall)
+ switch(E.phase)
+ if(2 to INFINITY)
+ var/items = H.get_contents()
+ for(var/obj/item/W in items)
+ if(W == H.wear_suit)
+ H.dropItemToGround(W, TRUE)
+ addtimer(CALLBACK(GLOBAL_PROC, .proc/to_chat, H, "Before you can even think about it, you quickly remove your clothes in response to [(H.client?.prefs.lewdchem?"your [E.enthrallGender]'s command'":"[E.master]'s directive'")]."), 5)
+ E.cooldown += 10
+
+ //WALK
+ else if((findtext(message, walk_words)))
+ for(var/V in listeners)
+ var/mob/living/L = V
+ var/datum/status_effect/chem/enthrall/E = L.has_status_effect(/datum/status_effect/chem/enthrall)
+ switch(E.phase)
+ if(2 to INFINITY)
+ if(L.m_intent != MOVE_INTENT_WALK)
+ L.toggle_move_intent()
+ E.cooldown += 1
+ addtimer(CALLBACK(GLOBAL_PROC, .proc/to_chat, L, "You slow down to a walk."), 5)
+ to_chat(user, "You encourage [L] to slow down.")
+
+ //RUN
+ else if((findtext(message, run_words)))
+ for(var/V in listeners)
+ var/mob/living/L = V
+ var/datum/status_effect/chem/enthrall/E = L.has_status_effect(/datum/status_effect/chem/enthrall)
+ switch(E.phase)
+ if(2 to INFINITY)
+ if(L.m_intent != MOVE_INTENT_RUN)
+ L.toggle_move_intent()
+ E.cooldown += 1
+ addtimer(CALLBACK(GLOBAL_PROC, .proc/to_chat, L, "You speed up into a jog!"), 5)
+ to_chat(user, "You encourage [L] to pick up the pace!")
+
+ //LIE DOWN
+ else if(findtext(message, liedown_words))
+ for(var/V in listeners)
+ var/mob/living/L = V
+ var/datum/status_effect/chem/enthrall/E = L.has_status_effect(/datum/status_effect/chem/enthrall)
+ switch(E.phase)
+ if(2 to INFINITY)
+ L.lay_down()
+ E.cooldown += 10
+ addtimer(CALLBACK(GLOBAL_PROC, .proc/to_chat, L, "[(L.client?.prefs.lewdchem?"You eagerly lie down!":"You suddenly lie down!")]"), 5)
+ to_chat(user, "You encourage [L] to lie down.")
+
+ //KNOCKDOWN
+ else if(findtext(message, knockdown_words))
+ for(var/V in listeners)
+ var/mob/living/L = V
+ var/datum/status_effect/chem/enthrall/E = L.has_status_effect(/datum/status_effect/chem/enthrall)
+ switch(E.phase)
+ if(2 to INFINITY)
+ L.Knockdown(30 * power_multiplier * E.phase)
+ E.cooldown += 8
+ addtimer(CALLBACK(GLOBAL_PROC, .proc/to_chat, L, "You suddenly drop to the ground!"), 5)
+ to_chat(user, "You encourage [L] to drop down to the ground.")
+
+ //tier3
+
+ //STATE TRIGGERS
+ else if((findtext(message, statecustom_words)))//doesn't work
+ for(var/V in listeners)
+ var/mob/living/carbon/C = V
+ var/datum/status_effect/chem/enthrall/E = C.has_status_effect(/datum/status_effect/chem/enthrall)
+ if (E.phase == 3)
+ var/speaktrigger = ""
+ C.emote("me", 1, "whispers something quietly.")
+ if (get_dist(user, C) > 1)//Requires user to be next to their pet.
+ to_chat(user, "You need to be next to your pet to hear them!")
+ continue
+ for (var/trigger in E.customTriggers)
+ speaktrigger += "[trigger], "
+ to_chat(user, "[C] whispers, \"[speaktrigger] are my triggers.\"")//So they don't trigger themselves!
+ addtimer(CALLBACK(GLOBAL_PROC, .proc/to_chat, C, "You whisper your triggers to [(C.client?.prefs.lewdchem?"Your [E.enthrallGender]":"[E.master]")]."), 5)
+
+
+ //CUSTOM TRIGGERS
+ else if((findtext(message, custom_words)))
+ for(var/V in listeners)
+ var/mob/living/carbon/human/H = V
+ var/datum/status_effect/chem/enthrall/E = H.has_status_effect(/datum/status_effect/chem/enthrall)
+ if(E.phase == 3)
+ if (get_dist(user, H) > 1)//Requires user to be next to their pet.
+ to_chat(user, "You need to be next to your pet to give them a new trigger!")
+ continue
+ else
+ user.emote("me", 1, "puts their hands upon [H.name]'s head and looks deep into their eyes, whispering something to them.")
+ user.SetStun(1000)//Hands are handy, so you have to stay still
+ H.SetStun(1000)
+ if (E.mental_capacity >= 5)
+ var/trigger = html_decode(stripped_input(user, "Enter the trigger phrase", MAX_MESSAGE_LEN))
+ var/custom_words_words_list = list("Speak", "Echo", "Shock", "Cum", "Kneel", "Strip", "Trance", "Cancel")
+ var/trigger2 = input(user, "Pick an effect", "Effects") in custom_words_words_list
+ trigger2 = lowertext(trigger2)
+ if ((findtext(trigger2, custom_words_words)))
+ if (trigger2 == "speak" || trigger2 == "echo")
+ var/trigger3 = html_decode(stripped_input(user, "Enter the phrase spoken. Abusing this to self antag is bannable.", MAX_MESSAGE_LEN))
+ E.customTriggers[trigger] = list(trigger2, trigger3)
+ log_game("FERMICHEM: [H] has been implanted by [user] with [trigger], triggering [trigger2], to send [trigger3].")
+ if(findtext(trigger3, "admin"))
+ message_admins("FERMICHEM: [user] maybe be trying to abuse MKUltra by implanting by [H] with [trigger], triggering [trigger2], to send [trigger3].")
+ else
+ E.customTriggers[trigger] = trigger2
+ log_game("FERMICHEM: [H] has been implanted by [user] with [trigger], triggering [trigger2].")
+ E.mental_capacity -= 5
+ addtimer(CALLBACK(GLOBAL_PROC, .proc/to_chat, H, "[(H.client?.prefs.lewdchem?"your [E.enthrallGender]":"[E.master]")] whispers you a new trigger."), 5)
+ to_chat(user, "You sucessfully set the trigger word [trigger] in [H]")
+ else
+ to_chat(user, "Your pet looks at you confused, it seems they don't understand that effect!")
+ else
+ to_chat(user, "Your pet looks at you with a vacant blase expression, you don't think you can program anything else into them")
+ user.SetStun(0)
+ H.SetStun(0)
+
+ //CUSTOM ECHO
+ else if((findtext(message, custom_echo)))
+ for(var/V in listeners)
+ var/mob/living/carbon/human/H = V
+ var/datum/status_effect/chem/enthrall/E = H.has_status_effect(/datum/status_effect/chem/enthrall)
+ if(E.phase == 3)
+ if (get_dist(user, H) > 1)//Requires user to be next to their pet.
+ to_chat(user, "You need to be next to your pet to give them a new echophrase!")
+ continue
+ else
+ user.emote("me", 1, "puts their hands upon [H.name]'s head and looks deep into their eyes, whispering something to them.")
+ user.SetStun(1000)//Hands are handy, so you have to stay still
+ H.SetStun(1000)
+ var/trigger = stripped_input(user, "Enter the loop phrase", MAX_MESSAGE_LEN)
+ var/customSpan = list("Notice", "Warning", "Hypnophrase", "Love", "Velvet")
+ var/trigger2 = input(user, "Pick the style", "Style") in customSpan
+ trigger2 = lowertext(trigger2)
+ E.customEcho = trigger
+ E.customSpan = trigger2
+ user.SetStun(0)
+ H.SetStun(0)
+ to_chat(user, "You sucessfully set an echoing phrase in [H]")
+
+ //CUSTOM OBJECTIVE
+ else if((findtext(message, objective_words)))
+ for(var/V in listeners)
+ var/mob/living/carbon/human/H = V
+ var/datum/status_effect/chem/enthrall/E = H.has_status_effect(/datum/status_effect/chem/enthrall)
+ if(E.phase == 3)
+ if (get_dist(user, H) > 1)//Requires user to be next to their pet.
+ to_chat(user, "You need to be next to your pet to give them a new objective!")
+ continue
+ else
+ user.emote("me", 1, "puts their hands upon [H.name]'s head and looks deep into their eyes, whispering something to them.'")
+ user.SetStun(1000)//So you can't run away!
+ H.SetStun(1000)
+ if (E.mental_capacity >= 200)
+ var/datum/objective/brainwashing/objective = stripped_input(user, "Add an objective to give your pet.", MAX_MESSAGE_LEN)
+ if(!LAZYLEN(objective))
+ to_chat(user, "You can't give your pet an objective to do nothing!")
+ continue
+ //Pets don't understand harm
+ objective = replacetext(lowertext(objective), "kill", "hug")
+ objective = replacetext(lowertext(objective), "murder", "cuddle")
+ objective = replacetext(lowertext(objective), "harm", "snuggle")
+ objective = replacetext(lowertext(objective), "decapitate", "headpat")
+ objective = replacetext(lowertext(objective), "strangle", "meow at")
+ objective = replacetext(lowertext(objective), "suicide", "self-love")
+ message_admins("[H] has been implanted by [user] with the objective [objective].")
+ log_game("FERMICHEM: [H] has been implanted by [user] with the objective [objective] via MKUltra.")
+ addtimer(CALLBACK(GLOBAL_PROC, .proc/to_chat, H, "[(H.client?.prefs.lewdchem?"Your [E.enthrallGender]":"[E.master]")] whispers you a new objective."), 5)
+ brainwash(H, objective)
+ E.mental_capacity -= 200
+ to_chat(user, "You sucessfully give an objective to [H]")
+ else
+ to_chat(user, "Your pet looks at you with a vacant blasé expression, you don't think you can program anything else into them")
+ user.SetStun(0)
+ H.SetStun(0)
+
+ //INSTILL
+ else if((findtext(message, instill_words)))
+ for(var/V in listeners)
+ var/mob/living/carbon/human/H = V
+ var/datum/status_effect/chem/enthrall/E = H.has_status_effect(/datum/status_effect/chem/enthrall)
+ if(E.phase == 3 && H.client?.prefs.lewdchem)
+ var/instill = stripped_input(user, "Instill an emotion in your [(user.client?.prefs.lewdchem?"Your pet":"listener")].", MAX_MESSAGE_LEN)
+ to_chat(H, "[instill]")
+ to_chat(user, "You sucessfully instill a feeling in [H]")
+ log_game("FERMICHEM: [H] has been instilled by [user] with [instill] via MKUltra.")
+ E.cooldown += 1
+
+ //RECOGNISE
+ else if((findtext(message, recognise_words)))
+ for(var/V in listeners)
+ var/mob/living/carbon/human/H = V
+ var/datum/status_effect/chem/enthrall/E = H.has_status_effect(/datum/status_effect/chem/enthrall)
+ if(E.phase > 1)
+ if(user.ckey == E.enthrallID && user.real_name == E.master.real_name)
+ E.master = user
+ addtimer(CALLBACK(GLOBAL_PROC, .proc/to_chat, H, "[(H.client?.prefs.lewdchem?"You hear the words of your [E.enthrallGender] again!! They're back!!":"You recognise the voice of [E.master].")]"), 5)
+ to_chat(user, "[H] looks at you with sparkling eyes, recognising you!")
+
+ //I dunno how to do state objectives without them revealing they're an antag
+
+ //HEAL (maybe make this nap instead?)
+ else if(findtext(message, heal_words))
+ for(var/V in listeners)
+ var/mob/living/L = V
+ var/datum/status_effect/chem/enthrall/E = L.has_status_effect(/datum/status_effect/chem/enthrall)
+ switch(E.phase)
+ if(3)//Tier 3 only
+ E.status = "heal"
+ E.statusStrength = (5 * power_multiplier)
+ E.cooldown += 5
+ addtimer(CALLBACK(GLOBAL_PROC, .proc/to_chat, L, "You begin to lick your wounds."), 5)
+ L.Stun(15 * power_multiplier)
+ to_chat(user, "[L] begins to lick their wounds.")
+
+ //STUN
+ else if(findtext(message, stun_words))
+ for(var/V in listeners)
+ var/mob/living/L = V
+ var/datum/status_effect/chem/enthrall/E = L.has_status_effect(/datum/status_effect/chem/enthrall)
+ switch(E.phase)
+ if(3 to INFINITY)
+ L.Stun(40 * power_multiplier)
+ E.cooldown += 8
+ addtimer(CALLBACK(GLOBAL_PROC, .proc/to_chat, L, "Your muscles freeze up!"), 5)
+ to_chat(user, "You cause [L] to freeze up!")
+
+ //HALLUCINATE
+ else if(findtext(message, hallucinate_words))
+ for(var/V in listeners)
+ var/mob/living/carbon/C = V
+ var/datum/status_effect/chem/enthrall/E = C.has_status_effect(/datum/status_effect/chem/enthrall)
+ switch(E.phase)
+ if(3 to INFINITY)
+ new /datum/hallucination/delusion(C, TRUE, null,150 * power_multiplier,0)
+ to_chat(user, "You send [C] on a trip.")
+
+ //HOT
+ else if(findtext(message, hot_words))
+ for(var/V in listeners)
+ var/mob/living/L = V
+ var/datum/status_effect/chem/enthrall/E = L.has_status_effect(/datum/status_effect/chem/enthrall)
+ switch(E.phase)
+ if(3 to INFINITY)
+ L.adjust_bodytemperature(50 * power_multiplier)//This seems nuts, reduced it, but then it didn't do anything, so I reverted it.
+ addtimer(CALLBACK(GLOBAL_PROC, .proc/to_chat, L, "You feel your metabolism speed up!"), 5)
+ to_chat(user, "You speed [L]'s metabolism up!")
+
+ //COLD
+ else if(findtext(message, cold_words))
+ for(var/V in listeners)
+ var/mob/living/L = V
+ var/datum/status_effect/chem/enthrall/E = L.has_status_effect(/datum/status_effect/chem/enthrall)
+ switch(E.phase)
+ if(3 to INFINITY)
+ L.adjust_bodytemperature(-50 * power_multiplier)
+ addtimer(CALLBACK(GLOBAL_PROC, .proc/to_chat, L, "You feel your metabolism slow down!"), 5)
+ to_chat(user, "You slow [L]'s metabolism down!")
+
+ //GET UP
+ else if(findtext(message, getup_words))
+ for(var/V in listeners)
+ var/mob/living/L = V
+ var/datum/status_effect/chem/enthrall/E = L.has_status_effect(/datum/status_effect/chem/enthrall)
+ switch(E.phase)
+ if(3 to INFINITY)//Tier 3 only
+ if(L.resting)
+ L.lay_down() //aka get up
+ L.SetStun(0)
+ L.SetKnockdown(0)
+ L.SetUnconscious(0) //i said get up i don't care if you're being tased
+ E.cooldown += 10 //This could be really strong
+ addtimer(CALLBACK(GLOBAL_PROC, .proc/to_chat, L, "You jump to your feet from sheer willpower!"), 5)
+ to_chat(user, "You spur [L] to their feet!")
+
+ //PACIFY
+ else if(findtext(message, pacify_words))
+ for(var/V in listeners)
+ var/mob/living/L = V
+ var/datum/status_effect/chem/enthrall/E = L.has_status_effect(/datum/status_effect/chem/enthrall)
+ switch(E.phase)
+ if(3)//Tier 3 only
+ E.status = "pacify"
+ E.cooldown += 10
+ addtimer(CALLBACK(GLOBAL_PROC, .proc/to_chat, L, "You feel like never hurting anyone ever again."), 5)
+ to_chat(user, "You remove any intent to harm from [L]'s mind.")
+
+ //CHARGE
+ else if(findtext(message, charge_words))
+ for(var/V in listeners)
+ var/mob/living/L = V
+ var/datum/status_effect/chem/enthrall/E = L.has_status_effect(/datum/status_effect/chem/enthrall)
+ switch(E.phase)
+ if(3)//Tier 3 only
+ E.statusStrength = 2* power_multiplier
+ E.status = "charge"
+ E.cooldown += 10
+ to_chat(user, "You rally [L], leading them into a charge!")
+
+ if(message_admins || debug)//Do you want this in?
+ message_admins("[ADMIN_LOOKUPFLW(user)] has said '[log_message]' with a Velvet Voice, affecting [english_list(listeners)], with a power multiplier of [power_multiplier].")
+ log_game("FERMICHEM: [key_name(user)] ckey: [user.key] has said '[log_message]' with a Velvet Voice, affecting [english_list(listeners)], with a power multiplier of [power_multiplier].")
+ SSblackbox.record_feedback("tally", "fermi_chem", 1, "Times people have spoken with a velvet voice")
+ //SSblackbox.record_feedback("tally", "Velvet_voice", 1, log_message) If this is on, it fills the thing up and OOFs the server
+
+ return
+
#undef COOLDOWN_STUN
#undef COOLDOWN_DAMAGE
diff --git a/code/modules/uplink/uplink_items.dm b/code/modules/uplink/uplink_items.dm
index 38c31a1294..8c02138900 100644
--- a/code/modules/uplink/uplink_items.dm
+++ b/code/modules/uplink/uplink_items.dm
@@ -1325,7 +1325,7 @@ datum/uplink_item/stealthy_weapons/taeclowndo_shoes
desc = "A small device intended for recharging Cryptographic Sequencers. Using it will add five extra charges to the Cryptographic Sequencer."
item = /obj/item/emagrecharge
cost = 2
-
+
/datum/uplink_item/device_tools/cutouts
name = "Adaptive Cardboard Cutouts"
desc = "These cardboard cutouts are coated with a thin material that prevents discoloration and makes the images on them appear more lifelike. \
diff --git a/code/modules/vending/medical.dm b/code/modules/vending/medical.dm
index 523606aa6a..44dca6b257 100644
--- a/code/modules/vending/medical.dm
+++ b/code/modules/vending/medical.dm
@@ -28,7 +28,9 @@
contraband = list(/obj/item/reagent_containers/pill/tox = 3,
/obj/item/reagent_containers/pill/morphine = 4,
/obj/item/reagent_containers/pill/charcoal = 6)
- premium = list(/obj/item/storage/box/hug/medical = 1,
+ premium = list(/obj/item/reagent_containers/medspray/synthflesh = 2,
+ /obj/item/storage/box/hug/medical = 1,
+ /obj/item/storage/pill_bottle/psicodine = 2,
/obj/item/reagent_containers/hypospray/medipen = 3,
/obj/item/storage/belt/medical = 3,
/obj/item/wrench/medical = 1,
diff --git a/code/modules/vending/wardrobes.dm b/code/modules/vending/wardrobes.dm
index e86c8521a6..08ed655297 100644
--- a/code/modules/vending/wardrobes.dm
+++ b/code/modules/vending/wardrobes.dm
@@ -338,7 +338,8 @@
/obj/item/clothing/suit/toggle/labcoat/chemist = 3,
/obj/item/storage/backpack/chemistry = 3,
/obj/item/storage/backpack/satchel/chem = 3,
- /obj/item/storage/bag/chemistry = 3)
+ /obj/item/storage/bag/chemistry = 3,
+ /obj/item/fermichem/pHbooklet = 3)//pH indicator)
refill_canister = /obj/item/vending_refill/wardrobe/chem_wardrobe
/obj/item/vending_refill/wardrobe/chem_wardrobe
diff --git a/config/admins.txt b/config/admins.txt
index 27a2178e2c..1fdf480dd3 100644
--- a/config/admins.txt
+++ b/config/admins.txt
@@ -1,11 +1,11 @@
-###############################################################################################
-# Basically, ckey goes first. Rank goes after the "=" #
-# Case is not important for ckey. #
-# Case IS important for the rank. #
-# All punctuation (spaces etc) EXCEPT '-', '_' and '@' will be stripped from rank names. #
-# Ranks can be anything defined in admin_ranks.txt #
-# NOTE: if the rank-name cannot be found in admin_ranks.txt, they will not be adminned! ~Carn #
-# NOTE: syntax was changed to allow hyphenation of ranknames, since spaces are stripped. #
-###############################################################################################
-
-yourckeygoeshere = Host
+###############################################################################################
+# Basically, ckey goes first. Rank goes after the "=" #
+# Case is not important for ckey. #
+# Case IS important for the rank. #
+# All punctuation (spaces etc) EXCEPT '-', '_' and '@' will be stripped from rank names. #
+# Ranks can be anything defined in admin_ranks.txt #
+# NOTE: if the rank-name cannot be found in admin_ranks.txt, they will not be adminned! ~Carn #
+# NOTE: syntax was changed to allow hyphenation of ranknames, since spaces are stripped. #
+###############################################################################################
+
+yourckeygoeshere = Host
\ No newline at end of file
diff --git a/goon/browserassets/css/browserOutput.css b/goon/browserassets/css/browserOutput.css
new file mode 100644
index 0000000000..58129ac1cb
--- /dev/null
+++ b/goon/browserassets/css/browserOutput.css
@@ -0,0 +1,876 @@
+/*****************************************
+*
+* GLOBAL STYLES
+*
+******************************************/
+html, body {
+ padding: 0;
+ margin: 0;
+ height: 100%;
+ color: #000000;
+}
+body {
+ background: #E0E0E0; /*CIT CHANGE - darkens chatbox a lil*/
+ font-family: Verdana, sans-serif;
+ font-size: 9pt;
+ line-height: 1.2;
+ overflow-x: hidden;
+ overflow-y: scroll;
+ word-wrap: break-word;
+}
+
+em {
+ font-style: normal;
+ font-weight: bold;
+}
+
+img {
+ margin: 0;
+ padding: 0;
+ line-height: 1;
+ -ms-interpolation-mode: nearest-neighbor;
+ image-rendering: pixelated;
+}
+img.icon {
+ height: 1em;
+ min-height: 16px;
+ width: auto;
+ vertical-align: bottom;
+}
+
+
+.r:before { /* "repeated" badge class for combined messages */
+ content: 'x';
+}
+.r {
+ display: inline-block;
+ min-width: 0.5em;
+ font-size: 0.7em;
+ padding: 0.2em 0.3em;
+ line-height: 1;
+ color: white;
+ text-align: center;
+ white-space: nowrap;
+ vertical-align: middle;
+ background-color: crimson;
+ border-radius: 10px;
+}
+
+a {color: #0000ff;}
+a.visited {color: #ff00ff;}
+a:visited {color: #ff00ff;}
+a.popt {text-decoration: none;}
+
+/*****************************************
+*
+* OUTPUT NOT RELATED TO ACTUAL MESSAGES
+*
+******************************************/
+#loading {
+ position: fixed;
+ width: 300px;
+ height: 150px;
+ text-align: center;
+ left: 50%;
+ top: 50%;
+ margin: -75px 0 0 -150px;
+}
+#loading i {display: block; padding-bottom: 3px;}
+
+#messages {
+ font-size: 13px;
+ padding: 3px;
+ margin: 0;
+ word-wrap: break-word;
+}
+#newMessages {
+ position: fixed;
+ display: block;
+ bottom: 0;
+ right: 0;
+ padding: 8px;
+ background: #ddd;
+ text-decoration: none;
+ font-variant: small-caps;
+ font-size: 1.1em;
+ font-weight: bold;
+ color: #333;
+}
+#newMessages:hover {background: #ccc;}
+#newMessages i {vertical-align: middle; padding-left: 3px;}
+#ping {
+ position: fixed;
+ top: 0;
+ right: 80px;
+ width: 45px;
+ background: #ddd;
+ height: 30px;
+ padding: 8px 0 2px 0;
+}
+#ping i {display: block; text-align: center;}
+#ping .ms {
+ display: block;
+ text-align: center;
+ font-size: 8pt;
+ padding-top: 2px;
+}
+#userBar {
+ position: fixed;
+ top: 0;
+ right: 0;
+}
+#userBar .subCell {
+ background: #ddd;
+ height: 30px;
+ padding: 5px 0;
+ display: block;
+ color: #333;
+ text-decoration: none;
+ line-height: 28px;
+ border-top: 1px solid #b4b4b4;
+}
+#userBar .subCell:hover {background: #ccc;}
+#userBar .toggle {
+ width: 40px;
+ background: #ccc;
+ border-top: 0;
+ float: right;
+ text-align: center;
+}
+#userBar .sub {clear: both; display: none; width: 160px;}
+#userBar .sub.scroll {overflow-y: scroll;}
+#userBar .sub.subCell {padding: 3px 0 3px 8px; line-height: 30px; font-size: 0.9em; clear: both;}
+#userBar .sub span {
+ display: block;
+ line-height: 30px;
+ float: left;
+}
+#userBar .sub i {
+ display: block;
+ padding: 0 5px;
+ font-size: 1.1em;
+ width: 22px;
+ text-align: center;
+ line-height: 30px;
+ float: right;
+}
+#userBar .sub input {
+ position: absolute;
+ padding: 7px 5px;
+ width: 121px;
+ line-height: 30px;
+ float: left;
+}
+#userBar .topCell {border-top: 0;}
+
+/* POPUPS */
+.popup {
+ position: fixed;
+ top: 50%;
+ left: 50%;
+ background: #ddd;
+}
+.popup .close {
+ position: absolute;
+ background: #aaa;
+ top: 0;
+ right: 0;
+ color: #333;
+ text-decoration: none;
+ z-index: 2;
+ padding: 0 10px;
+ height: 30px;
+ line-height: 30px;
+}
+.popup .close:hover {background: #999;}
+.popup .head {
+ background: #999;
+ color: #ddd;
+ padding: 0 10px;
+ height: 30px;
+ line-height: 30px;
+ text-transform: uppercase;
+ font-size: 0.9em;
+ font-weight: bold;
+ border-bottom: 2px solid green;
+}
+.popup input {border: 1px solid #999; background: #fff; margin: 0; padding: 5px; outline: none; color: #333;}
+.popup input[type=text]:hover, .popup input[type=text]:active, .popup input[type=text]:focus {border-color: green;}
+.popup input[type=submit] {padding: 5px 10px; background: #999; color: #ddd; text-transform: uppercase; font-size: 0.9em; font-weight: bold;}
+.popup input[type=submit]:hover, .popup input[type=submit]:focus, .popup input[type=submit]:active {background: #aaa; cursor: pointer;}
+
+.changeFont {padding: 10px;}
+.changeFont a {display: block; text-decoration: none; padding: 3px; color: #333;}
+.changeFont a:hover {background: #ccc;}
+
+.highlightPopup {padding: 10px; text-align: center;}
+.highlightPopup input[type=text] {display: block; width: 215px; text-align: left; margin-top: 5px;}
+.highlightPopup input.highlightColor {background-color: #FFFF00;}
+.highlightPopup input.highlightTermSubmit {margin-top: 5px;}
+
+/* ADMIN CONTEXT MENU */
+.contextMenu {
+ background-color: #ddd;
+ position: fixed;
+ margin: 2px;
+ width: 150px;
+}
+.contextMenu a {
+ display: block;
+ padding: 2px 5px;
+ text-decoration: none;
+ color: #333;
+}
+
+.contextMenu a:hover {
+ background-color: #ccc;
+}
+
+/* ADMIN FILTER MESSAGES MENU */
+.filterMessages {padding: 5px;}
+.filterMessages div {padding: 2px 0;}
+.filterMessages input {}
+.filterMessages label {}
+
+.icon-stack {height: 1em; line-height: 1em; width: 1em; vertical-align: middle; margin-top: -2px;}
+
+
+/*****************************************
+*
+* OUTPUT ACTUALLY RELATED TO MESSAGES
+*
+******************************************/
+
+/* MOTD */
+.motd {color: #638500; font-family: Verdana, sans-serif;}
+.motd h1, .motd h2, .motd h3, .motd h4, .motd h5, .motd h6 {color: #638500; text-decoration: underline;}
+.motd a, .motd a:link, .motd a:visited, .motd a:active, .motd a:hover {color: #638500;}
+
+/* ADD HERE FOR BOLD */
+.bold, .name, .prefix, .ooc, .looc, .adminooc, .admin, .medal, .yell {font-weight: bold;}
+
+/* ADD HERE FOR ITALIC */
+.italic, .italics, .emote {font-style: italic;}
+
+/* OUTPUT COLORS */
+.highlight {background: yellow;}
+
+h1, h2, h3, h4, h5, h6 {color: #0000ff;font-family: Georgia, Verdana, sans-serif;}
+h1.alert, h2.alert {color: #000000;}
+
+em {font-style: normal; font-weight: bold;}
+
+.ooc { font-weight: bold;}
+.adminobserverooc {color: #0099cc; font-weight: bold;}
+.adminooc {color: #700038; font-weight: bold;}
+
+.adminobserver {color: #996600; font-weight: bold;}
+.admin {color: #386aff; font-weight: bold;}
+
+.name { font-weight: bold;}
+
+.say {}
+.deadsay {color: #5c00e6;}
+.binarysay {color: #20c20e; background-color: #000000; display: block;}
+.binarysay a {color: #00ff00;}
+.binarysay a:active, .binarysay a:visited {color: #88ff88;}
+.radio {color: #008000;}
+.sciradio {color: #993399;}
+.comradio {color: #948f02;}
+.secradio {color: #a30000;}
+.medradio {color: #337296;}
+.engradio {color: #fb5613;}
+.suppradio {color: #a8732b;}
+.servradio {color: #6eaa2c;}
+.syndradio {color: #6d3f40;}
+.centcomradio {color: #686868;}
+.aiprivradio {color: #ff00ff;}
+.redteamradio {color: #ff0000;}
+.blueteamradio {color: #0000ff;}
+
+.yell { font-weight: bold;}
+
+.alert {color: #ff0000;}
+h1.alert, h2.alert {color: #000000;}
+
+.emote { font-style: italic;}
+.selecteddna {color: #ffffff; background-color: #001B1B}
+
+.attack {color: #ff0000;}
+.disarm {color: #990000;}
+.passive {color: #660000;}
+
+.userdanger {color: #ff0000; font-weight: bold; font-size: 24px;}
+.danger {color: #ff0000;}
+.warning {color: #ff0000; font-style: italic;}
+.boldwarning {color: #ff0000; font-style: italic; font-weight: bold}
+.announce {color: #228b22; font-weight: bold;}
+.boldannounce {color: #ff0000; font-weight: bold;}
+.greenannounce {color: #00ff00; font-weight: bold;}
+.rose {color: #ff5050;}
+.info {color: #0000CC;}
+.notice {color: #000099;}
+.boldnotice {color: #000099; font-weight: bold;}
+.adminnotice {color: #0000ff;}
+.adminhelp {color: #ff0000; font-weight: bold;}
+.unconscious {color: #0000ff; font-weight: bold;}
+.suicide {color: #ff5050; font-style: italic;}
+.green {color: #03ff39;}
+.nicegreen {color: #14a833;}
+.shadowling {color: #3b2769;}
+.cult {color: #960000;}
+
+.cultitalic {color: #960000; font-style: italic;}
+.cultbold {color: #960000; font-style: italic; font-weight: bold;}
+.cultboldtalic {color: #960000; font-weight: bold; font-size: 24px;}
+
+.cultlarge {color: #960000; font-weight: bold; font-size: 24px;}
+.narsie {color: #960000; font-weight: bold; font-size: 120px;}
+.narsiesmall {color: #960000; font-weight: bold; font-size: 48px;}
+.colossus {color: #7F282A; font-size: 40px;}
+.hierophant {color: #660099; font-weight: bold; font-style: italic;}
+.hierophant_warning {color: #660099; font-style: italic;}
+.purple {color: #5e2d79;}
+.holoparasite {color: #35333a;}
+
+.revennotice {color: #1d2953;}
+.revenboldnotice {color: #1d2953; font-weight: bold;}
+.revenbignotice {color: #1d2953; font-weight: bold; font-size: 24px;}
+.revenminor {color: #823abb}
+.revenwarning {color: #760fbb; font-style: italic;}
+.revendanger {color: #760fbb; font-weight: bold; font-size: 24px;}
+.umbra {color: #5000A0;}
+.umbra_emphasis {color: #5000A0; font-weight: bold; font-style: italic;}
+.umbra_large {color: #5000A0; font-size: 24px; font-weight: bold; font-style: italic;}
+
+.deconversion_message {color: #5000A0; font-size: 24px; font-style: italic;}
+
+.brass {color: #BE8700;}
+.heavy_brass {color: #BE8700; font-weight: bold; font-style: italic;}
+.large_brass {color: #BE8700; font-size: 24px;}
+.big_brass {color: #BE8700; font-size: 24px; font-weight: bold; font-style: italic;}
+.ratvar {color: #BE8700; font-size: 48px; font-weight: bold; font-style: italic;}
+.alloy {color: #42474D;}
+.heavy_alloy {color: #42474D; font-weight: bold; font-style: italic;}
+.nezbere_large {color: #42474D; font-size: 24px; font-weight: bold; font-style: italic;}
+.nezbere {color: #42474D; font-weight: bold; font-style: italic;}
+.nezbere_small {color: #42474D;}
+.sevtug_large {color: #AF0AAF; font-size: 24px; font-weight: bold; font-style: italic;}
+.sevtug {color: #AF0AAF; font-weight: bold; font-style: italic;}
+.sevtug_small {color: #AF0AAF;}
+.inathneq_large {color: #1E8CE1; font-size: 24px; font-weight: bold; font-style: italic;}
+.inathneq {color: #1E8CE1; font-weight: bold; font-style: italic;}
+.inathneq_small {color: #1E8CE1;}
+.nzcrentr_large {color: #DAAA18; font-size: 24px; font-weight: bold; font-style: italic;}
+.nzcrentr {color: #DAAA18; font-weight: bold; font-style: italic;}
+.nzcrentr_small {color: #DAAA18;}
+.neovgre_large {color: #6E001A; font-size: 24px; font-weight: bold; font-style: italic;}
+.neovgre {color: #6E001A; font-weight: bold; font-style: italic;}
+.neovgre_small {color: #6E001A;}
+
+.newscaster {color: #800000;}
+.ghostalert {color: #5c00e6; font-style: italic; font-weight: bold;}
+
+.alien {color: #543354;}
+.noticealien {color: #00c000;}
+.alertalien {color: #00c000; font-weight: bold;}
+.changeling {color: #800080; font-style: italic;}
+
+.spider {color: #4d004d;}
+
+.interface {color: #330033;}
+
+.sans {font-family: "Comic Sans MS", cursive, sans-serif;}
+.papyrus {font-family: "Papyrus", cursive, sans-serif;}
+.robot {font-family: "Courier New", cursive, sans-serif;}
+
+.command_headset {font-weight: bold; font-size: 24px;}
+.small {font-size: 8px;}
+.big {font-size: 24px;}
+.reallybig {font-size: 32px;}
+.extremelybig {font-size: 40px;}
+.greentext {color: #00FF00; font-size: 24px;}
+.redtext {color: #FF0000; font-size: 24px;}
+.clown {color: #FF69Bf; font-size: 24px; font-family: "Comic Sans MS", cursive, sans-serif; font-weight: bold;}
+.his_grace {color: #15D512; font-family: "Courier New", cursive, sans-serif; font-style: italic;}
+.velvet {color: #660015; font-weight: bold; animation: velvet 5000ms infinite;}
+@keyframes velvet {
+ 0% { color: #400020; }
+ 40% { color: #FF0000; }
+ 50% { color: #FF8888; }
+ 60% { color: #FF0000; }
+ 100% { color: #400020; }
+}
+
+.hypnophrase {color: #202020; font-weight: bold; animation: hypnocolor 1500ms infinite;}
+@keyframes hypnocolor {
+ 0% { color: #202020; }
+ 25% { color: #4b02ac; }
+ 50% { color: #9f41f1; }
+ 75% { color: #541c9c; }
+ 100% { color: #7adbf3; }
+}
+
+.phobia {color: #dd0000; font-weight: bold; animation: phobia 750ms infinite;}
+@keyframes phobia {
+ 0% { color: #f75a5a; }
+ 50% { color: #dd0000; }
+ 100% { color: #f75a5a; }
+}
+
+.icon {height: 1em; width: auto;}
+
+.memo {color: #638500; text-align: center;}
+.memoedit {text-align: center; font-size: 16px;}
+.abductor {color: #800080; font-style: italic;}
+.mind_control {color: #A00D6F; font-size: 3; font-weight: bold; font-style: italic;}
+.slime {color: #00CED1;}
+.drone {color: #848482;}
+.monkey {color: #975032;}
+.swarmer {color: #2C75FF;}
+.resonate {color: #298F85;}
+
+.monkeyhive {color: #774704;}
+.monkeylead {color: #774704; font-size: 2;}
+
+.connectionClosed, .fatalError {background: red; color: white; padding: 5px;}
+.connectionClosed.restored {background: green;}
+.internal.boldnshit {color: blue; font-weight: bold;}
+
+/* HELPER CLASSES */
+.text-normal {font-weight: normal; font-style: normal;}
+.hidden {display: none; visibility: hidden;}/*****************************************
+*
+* GLOBAL STYLES
+*
+******************************************/
+html, body {
+ padding: 0;
+ margin: 0;
+ height: 100%;
+ color: #000000;
+}
+body {
+ background: #E0E0E0; /*CIT CHANGE - darkens chatbox a lil*/
+ font-family: Verdana, sans-serif;
+ font-size: 9pt;
+ line-height: 1.2;
+ overflow-x: hidden;
+ overflow-y: scroll;
+ word-wrap: break-word;
+}
+
+em {
+ font-style: normal;
+ font-weight: bold;
+}
+
+img {
+ margin: 0;
+ padding: 0;
+ line-height: 1;
+ -ms-interpolation-mode: nearest-neighbor;
+ image-rendering: pixelated;
+}
+img.icon {
+ height: 1em;
+ min-height: 16px;
+ width: auto;
+ vertical-align: bottom;
+}
+
+
+.r:before { /* "repeated" badge class for combined messages */
+ content: 'x';
+}
+.r {
+ display: inline-block;
+ min-width: 0.5em;
+ font-size: 0.7em;
+ padding: 0.2em 0.3em;
+ line-height: 1;
+ color: white;
+ text-align: center;
+ white-space: nowrap;
+ vertical-align: middle;
+ background-color: crimson;
+ border-radius: 10px;
+}
+
+a {color: #0000ff;}
+a.visited {color: #ff00ff;}
+a:visited {color: #ff00ff;}
+a.popt {text-decoration: none;}
+
+/*****************************************
+*
+* OUTPUT NOT RELATED TO ACTUAL MESSAGES
+*
+******************************************/
+#loading {
+ position: fixed;
+ width: 300px;
+ height: 150px;
+ text-align: center;
+ left: 50%;
+ top: 50%;
+ margin: -75px 0 0 -150px;
+}
+#loading i {display: block; padding-bottom: 3px;}
+
+#messages {
+ font-size: 13px;
+ padding: 3px;
+ margin: 0;
+ word-wrap: break-word;
+}
+#newMessages {
+ position: fixed;
+ display: block;
+ bottom: 0;
+ right: 0;
+ padding: 8px;
+ background: #ddd;
+ text-decoration: none;
+ font-variant: small-caps;
+ font-size: 1.1em;
+ font-weight: bold;
+ color: #333;
+}
+#newMessages:hover {background: #ccc;}
+#newMessages i {vertical-align: middle; padding-left: 3px;}
+#ping {
+ position: fixed;
+ top: 0;
+ right: 80px;
+ width: 45px;
+ background: #ddd;
+ height: 30px;
+ padding: 8px 0 2px 0;
+}
+#ping i {display: block; text-align: center;}
+#ping .ms {
+ display: block;
+ text-align: center;
+ font-size: 8pt;
+ padding-top: 2px;
+}
+#userBar {
+ position: fixed;
+ top: 0;
+ right: 0;
+}
+#userBar .subCell {
+ background: #ddd;
+ height: 30px;
+ padding: 5px 0;
+ display: block;
+ color: #333;
+ text-decoration: none;
+ line-height: 28px;
+ border-top: 1px solid #b4b4b4;
+}
+#userBar .subCell:hover {background: #ccc;}
+#userBar .toggle {
+ width: 40px;
+ background: #ccc;
+ border-top: 0;
+ float: right;
+ text-align: center;
+}
+#userBar .sub {clear: both; display: none; width: 160px;}
+#userBar .sub.scroll {overflow-y: scroll;}
+#userBar .sub.subCell {padding: 3px 0 3px 8px; line-height: 30px; font-size: 0.9em; clear: both;}
+#userBar .sub span {
+ display: block;
+ line-height: 30px;
+ float: left;
+}
+#userBar .sub i {
+ display: block;
+ padding: 0 5px;
+ font-size: 1.1em;
+ width: 22px;
+ text-align: center;
+ line-height: 30px;
+ float: right;
+}
+#userBar .sub input {
+ position: absolute;
+ padding: 7px 5px;
+ width: 121px;
+ line-height: 30px;
+ float: left;
+}
+#userBar .topCell {border-top: 0;}
+
+/* POPUPS */
+.popup {
+ position: fixed;
+ top: 50%;
+ left: 50%;
+ background: #ddd;
+}
+.popup .close {
+ position: absolute;
+ background: #aaa;
+ top: 0;
+ right: 0;
+ color: #333;
+ text-decoration: none;
+ z-index: 2;
+ padding: 0 10px;
+ height: 30px;
+ line-height: 30px;
+}
+.popup .close:hover {background: #999;}
+.popup .head {
+ background: #999;
+ color: #ddd;
+ padding: 0 10px;
+ height: 30px;
+ line-height: 30px;
+ text-transform: uppercase;
+ font-size: 0.9em;
+ font-weight: bold;
+ border-bottom: 2px solid green;
+}
+.popup input {border: 1px solid #999; background: #fff; margin: 0; padding: 5px; outline: none; color: #333;}
+.popup input[type=text]:hover, .popup input[type=text]:active, .popup input[type=text]:focus {border-color: green;}
+.popup input[type=submit] {padding: 5px 10px; background: #999; color: #ddd; text-transform: uppercase; font-size: 0.9em; font-weight: bold;}
+.popup input[type=submit]:hover, .popup input[type=submit]:focus, .popup input[type=submit]:active {background: #aaa; cursor: pointer;}
+
+.changeFont {padding: 10px;}
+.changeFont a {display: block; text-decoration: none; padding: 3px; color: #333;}
+.changeFont a:hover {background: #ccc;}
+
+.highlightPopup {padding: 10px; text-align: center;}
+.highlightPopup input[type=text] {display: block; width: 215px; text-align: left; margin-top: 5px;}
+.highlightPopup input.highlightColor {background-color: #FFFF00;}
+.highlightPopup input.highlightTermSubmit {margin-top: 5px;}
+
+/* ADMIN CONTEXT MENU */
+.contextMenu {
+ background-color: #ddd;
+ position: fixed;
+ margin: 2px;
+ width: 150px;
+}
+.contextMenu a {
+ display: block;
+ padding: 2px 5px;
+ text-decoration: none;
+ color: #333;
+}
+
+.contextMenu a:hover {
+ background-color: #ccc;
+}
+
+/* ADMIN FILTER MESSAGES MENU */
+.filterMessages {padding: 5px;}
+.filterMessages div {padding: 2px 0;}
+.filterMessages input {}
+.filterMessages label {}
+
+.icon-stack {height: 1em; line-height: 1em; width: 1em; vertical-align: middle; margin-top: -2px;}
+
+
+/*****************************************
+*
+* OUTPUT ACTUALLY RELATED TO MESSAGES
+*
+******************************************/
+
+/* MOTD */
+.motd {color: #638500; font-family: Verdana, sans-serif;}
+.motd h1, .motd h2, .motd h3, .motd h4, .motd h5, .motd h6 {color: #638500; text-decoration: underline;}
+.motd a, .motd a:link, .motd a:visited, .motd a:active, .motd a:hover {color: #638500;}
+
+/* ADD HERE FOR BOLD */
+.bold, .name, .prefix, .ooc, .looc, .adminooc, .admin, .medal, .yell {font-weight: bold;}
+
+/* ADD HERE FOR ITALIC */
+.italic, .italics, .emote {font-style: italic;}
+
+/* OUTPUT COLORS */
+.highlight {background: yellow;}
+
+h1, h2, h3, h4, h5, h6 {color: #0000ff;font-family: Georgia, Verdana, sans-serif;}
+h1.alert, h2.alert {color: #000000;}
+
+em {font-style: normal; font-weight: bold;}
+
+.ooc { font-weight: bold;}
+.adminobserverooc {color: #0099cc; font-weight: bold;}
+.adminooc {color: #700038; font-weight: bold;}
+
+.adminsay {color: #FF4500; font-weight: bold;}
+.admin {color: #386aff; font-weight: bold;}
+
+.name { font-weight: bold;}
+
+.say {}
+.deadsay {color: #5c00e6;}
+.binarysay {color: #20c20e; background-color: #000000; display: block;}
+.binarysay a {color: #00ff00;}
+.binarysay a:active, .binarysay a:visited {color: #88ff88;}
+.radio {color: #008000;}
+.sciradio {color: #993399;}
+.comradio {color: #948f02;}
+.secradio {color: #a30000;}
+.medradio {color: #337296;}
+.engradio {color: #fb5613;}
+.suppradio {color: #a8732b;}
+.servradio {color: #6eaa2c;}
+.syndradio {color: #6d3f40;}
+.centcomradio {color: #686868;}
+.aiprivradio {color: #ff00ff;}
+.redteamradio {color: #ff0000;}
+.blueteamradio {color: #0000ff;}
+
+.yell { font-weight: bold;}
+
+.alert {color: #ff0000;}
+h1.alert, h2.alert {color: #000000;}
+
+.emote { font-style: italic;}
+.selecteddna {color: #ffffff; background-color: #001B1B}
+
+.attack {color: #ff0000;}
+.disarm {color: #990000;}
+.passive {color: #660000;}
+
+.userdanger {color: #ff0000; font-weight: bold; font-size: 24px;}
+.danger {color: #ff0000;}
+.warning {color: #ff0000; font-style: italic;}
+.boldwarning {color: #ff0000; font-style: italic; font-weight: bold}
+.announce {color: #228b22; font-weight: bold;}
+.boldannounce {color: #ff0000; font-weight: bold;}
+.greenannounce {color: #00ff00; font-weight: bold;}
+.rose {color: #ff5050;}
+.info {color: #0000CC;}
+.notice {color: #000099;}
+.boldnotice {color: #000099; font-weight: bold;}
+.adminnotice {color: #0000ff;}
+.adminhelp {color: #ff0000; font-weight: bold;}
+.unconscious {color: #0000ff; font-weight: bold;}
+.suicide {color: #ff5050; font-style: italic;}
+.green {color: #03ff39;}
+.nicegreen {color: #14a833;}
+.userlove {color: #FF1493; font-style: italic; font-weight: bold; text-shadow: 0 0 6px #ff6dbc;}
+.love {color: #ff006a; font-style: italic; text-shadow: 0 0 6px #ff6d6d;}
+.shadowling {color: #3b2769;}
+.cult {color: #960000;}
+
+.cultitalic {color: #960000; font-style: italic;}
+.cultbold {color: #960000; font-style: italic; font-weight: bold;}
+.cultboldtalic {color: #960000; font-weight: bold; font-size: 24px;}
+
+.cultlarge {color: #960000; font-weight: bold; font-size: 24px;}
+.narsie {color: #960000; font-weight: bold; font-size: 120px;}
+.narsiesmall {color: #960000; font-weight: bold; font-size: 48px;}
+.colossus {color: #7F282A; font-size: 40px;}
+.hierophant {color: #660099; font-weight: bold; font-style: italic;}
+.hierophant_warning {color: #660099; font-style: italic;}
+.purple {color: #5e2d79;}
+.holoparasite {color: #35333a;}
+
+.revennotice {color: #1d2953;}
+.revenboldnotice {color: #1d2953; font-weight: bold;}
+.revenbignotice {color: #1d2953; font-weight: bold; font-size: 24px;}
+.revenminor {color: #823abb}
+.revenwarning {color: #760fbb; font-style: italic;}
+.revendanger {color: #760fbb; font-weight: bold; font-size: 24px;}
+.umbra {color: #5000A0;}
+.umbra_emphasis {color: #5000A0; font-weight: bold; font-style: italic;}
+.umbra_large {color: #5000A0; font-size: 24px; font-weight: bold; font-style: italic;}
+
+.deconversion_message {color: #5000A0; font-size: 24px; font-style: italic;}
+
+.brass {color: #BE8700;}
+.heavy_brass {color: #BE8700; font-weight: bold; font-style: italic;}
+.large_brass {color: #BE8700; font-size: 24px;}
+.big_brass {color: #BE8700; font-size: 24px; font-weight: bold; font-style: italic;}
+.ratvar {color: #BE8700; font-size: 48px; font-weight: bold; font-style: italic;}
+.alloy {color: #42474D;}
+.heavy_alloy {color: #42474D; font-weight: bold; font-style: italic;}
+.nezbere_large {color: #42474D; font-size: 24px; font-weight: bold; font-style: italic;}
+.nezbere {color: #42474D; font-weight: bold; font-style: italic;}
+.nezbere_small {color: #42474D;}
+.sevtug_large {color: #AF0AAF; font-size: 24px; font-weight: bold; font-style: italic;}
+.sevtug {color: #AF0AAF; font-weight: bold; font-style: italic;}
+.sevtug_small {color: #AF0AAF;}
+.inathneq_large {color: #1E8CE1; font-size: 24px; font-weight: bold; font-style: italic;}
+.inathneq {color: #1E8CE1; font-weight: bold; font-style: italic;}
+.inathneq_small {color: #1E8CE1;}
+.nzcrentr_large {color: #DAAA18; font-size: 24px; font-weight: bold; font-style: italic;}
+.nzcrentr {color: #DAAA18; font-weight: bold; font-style: italic;}
+.nzcrentr_small {color: #DAAA18;}
+.neovgre_large {color: #6E001A; font-size: 24px; font-weight: bold; font-style: italic;}
+.neovgre {color: #6E001A; font-weight: bold; font-style: italic;}
+.neovgre_small {color: #6E001A;}
+
+.newscaster {color: #800000;}
+.ghostalert {color: #5c00e6; font-style: italic; font-weight: bold;}
+
+.alien {color: #543354;}
+.noticealien {color: #00c000;}
+.alertalien {color: #00c000; font-weight: bold;}
+.changeling {color: #800080; font-style: italic;}
+
+.spider {color: #4d004d;}
+
+.interface {color: #330033;}
+
+.sans {font-family: "Comic Sans MS", cursive, sans-serif;}
+.papyrus {font-family: "Papyrus", cursive, sans-serif;}
+.robot {font-family: "Courier New", cursive, sans-serif;}
+
+.command_headset {font-weight: bold; font-size: 24px;}
+.small {font-size: 8px;}
+.big {font-size: 24px;}
+.reallybig {font-size: 32px;}
+.extremelybig {font-size: 40px;}
+.greentext {color: #00FF00; font-size: 24px;}
+.redtext {color: #FF0000; font-size: 24px;}
+.clown {color: #FF69Bf; font-size: 24px; font-family: "Comic Sans MS", cursive, sans-serif; font-weight: bold;}
+.hypnophrase {color: #202020; font-weight: bold; animation: hypnocolor 1500ms infinite;}
+@keyframes hypnocolor {
+ 0% { color: #202020; }
+ 25% { color: #4b02ac; }
+ 50% { color: #9f41f1; }
+ 75% { color: #541c9c; }
+ 100% { color: #7adbf3; }
+}
+
+.phobia {color: #dd0000; font-weight: bold; animation: phobia 750ms infinite;}
+@keyframes phobia {
+ 0% { color: #f75a5a; }
+ 50% { color: #dd0000; }
+ 100% { color: #f75a5a; }
+}
+.his_grace {color: #15D512; font-family: "Courier New", cursive, sans-serif; font-style: italic;}
+
+.icon {height: 1em; width: auto;}
+
+.memo {color: #638500; text-align: center;}
+.memoedit {text-align: center; font-size: 16px;}
+.abductor {color: #800080; font-style: italic;}
+.mind_control {color: #A00D6F; font-size: 3; font-weight: bold; font-style: italic;}
+.slime {color: #00CED1;}
+.drone {color: #848482;}
+.monkey {color: #975032;}
+.swarmer {color: #2C75FF;}
+.resonate {color: #298F85;}
+
+.monkeyhive {color: #774704;}
+.monkeylead {color: #774704; font-size: 2;}
+
+.connectionClosed, .fatalError {background: red; color: white; padding: 5px;}
+.connectionClosed.restored {background: green;}
+.internal.boldnshit {color: blue; font-weight: bold;}
+
+/* HELPER CLASSES */
+.text-normal {font-weight: normal; font-style: normal;}
+.hidden {display: none; visibility: hidden;}
diff --git a/html/changelogs/AutoChangeLog-pr-9064.yml b/html/changelogs/AutoChangeLog-pr-9064.yml
new file mode 100644
index 0000000000..6e775c1eb4
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-9064.yml
@@ -0,0 +1,4 @@
+author: "Cebutris"
+delete-after: True
+changes:
+ - rscadd: "Hugs of the North Star! Get them from the arcades (if you're lucky) and hug your friends at INCREDIBLE hihg speeds!"
diff --git a/html/changelogs/AutoChangeLog-pr-9070.yml b/html/changelogs/AutoChangeLog-pr-9070.yml
new file mode 100644
index 0000000000..2013550dd2
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-9070.yml
@@ -0,0 +1,5 @@
+author: "Linzolle"
+delete-after: True
+changes:
+ - rscadd: "inhands sprite for rainbow knife"
+ - tweak: "colour of highlight on the regular knife when held in the right hand"
diff --git a/html/changelogs/AutoChangeLog-pr-9078.yml b/html/changelogs/AutoChangeLog-pr-9078.yml
new file mode 100644
index 0000000000..2e202ec43a
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-9078.yml
@@ -0,0 +1,7 @@
+author: "kappa-sama"
+delete-after: True
+changes:
+ - rscdel: "Removed racism"
+ - tweak: "Teleporter calibration actually matters to all roundstart players"
+ - balance: "Slows down teleportation with the console/hub/teleporter setup if you care for your species."
+ - balance: "Dedicated non-humans can now get hulk without having to become human."
diff --git a/html/changelogs/AutoChangeLog-pr-9081.yml b/html/changelogs/AutoChangeLog-pr-9081.yml
new file mode 100644
index 0000000000..c66148e446
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-9081.yml
@@ -0,0 +1,4 @@
+author: "BurgerBB"
+delete-after: True
+changes:
+ - balance: "Central Command informs you when a Meteor Storm is about to hit 5 to 10 minutes before it happens."
diff --git a/html/changelogs/AutoChangeLog-pr-9083.yml b/html/changelogs/AutoChangeLog-pr-9083.yml
new file mode 100644
index 0000000000..74b4d95ca3
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-9083.yml
@@ -0,0 +1,4 @@
+author: "Trilbyspaceclone"
+delete-after: True
+changes:
+ - rscadd: "strawbarries and such"
diff --git a/html/changelogs/AutoChangeLog-pr-9084.yml b/html/changelogs/AutoChangeLog-pr-9084.yml
new file mode 100644
index 0000000000..c2569d10b4
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-9084.yml
@@ -0,0 +1,4 @@
+author: "Ghommie"
+delete-after: True
+changes:
+ - bugfix: "fixes clock cult mass recall."
diff --git a/html/changelogs/AutoChangeLog-pr-9085.yml b/html/changelogs/AutoChangeLog-pr-9085.yml
new file mode 100644
index 0000000000..92f5dc4cf8
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-9085.yml
@@ -0,0 +1,4 @@
+author: "Ghommie"
+delete-after: True
+changes:
+ - bugfix: "Fixes underwear colors a bit."
diff --git a/icons/mob/actions.dmi b/icons/mob/actions.dmi
index 65b5733ffe..4e23c102ce 100644
Binary files a/icons/mob/actions.dmi and b/icons/mob/actions.dmi differ
diff --git a/icons/mob/inhands/equipment/kitchen_lefthand.dmi b/icons/mob/inhands/equipment/kitchen_lefthand.dmi
index 277a7d8f05..93cd988cff 100644
Binary files a/icons/mob/inhands/equipment/kitchen_lefthand.dmi and b/icons/mob/inhands/equipment/kitchen_lefthand.dmi differ
diff --git a/icons/mob/inhands/equipment/kitchen_righthand.dmi b/icons/mob/inhands/equipment/kitchen_righthand.dmi
index 0103bd19b5..075b4c2033 100644
Binary files a/icons/mob/inhands/equipment/kitchen_righthand.dmi and b/icons/mob/inhands/equipment/kitchen_righthand.dmi differ
diff --git a/icons/mob/pets.dmi b/icons/mob/pets.dmi
index fe0146ccc4..10f29f51e4 100644
Binary files a/icons/mob/pets.dmi and b/icons/mob/pets.dmi differ
diff --git a/icons/mob/screen_gen.dmi b/icons/mob/screen_gen.dmi
index 9ce97dfd6d..3cd7388378 100644
Binary files a/icons/mob/screen_gen.dmi and b/icons/mob/screen_gen.dmi differ
diff --git a/icons/obj/assemblies/new_assemblies.dmi b/icons/obj/assemblies/new_assemblies.dmi
index df9517aeaa..fa0c138d21 100644
Binary files a/icons/obj/assemblies/new_assemblies.dmi and b/icons/obj/assemblies/new_assemblies.dmi differ
diff --git a/icons/obj/chemical.dmi b/icons/obj/chemical.dmi
index 93daa6149e..b63e9344f1 100644
Binary files a/icons/obj/chemical.dmi and b/icons/obj/chemical.dmi differ
diff --git a/icons/obj/clothing/hats.dmi b/icons/obj/clothing/hats.dmi
index 8256c1fc07..a8ec22c950 100644
Binary files a/icons/obj/clothing/hats.dmi and b/icons/obj/clothing/hats.dmi differ
diff --git a/icons/obj/food/food.dmi b/icons/obj/food/food.dmi
index 92b5203f5d..6fda702cb3 100644
Binary files a/icons/obj/food/food.dmi and b/icons/obj/food/food.dmi differ
diff --git a/icons/obj/food/piecake.dmi b/icons/obj/food/piecake.dmi
index 1bf5b7ee96..df1e61c849 100644
Binary files a/icons/obj/food/piecake.dmi and b/icons/obj/food/piecake.dmi differ
diff --git a/icons/obj/hydroponics/growing_fruits.dmi b/icons/obj/hydroponics/growing_fruits.dmi
index d309884be0..9b0bc9816b 100644
Binary files a/icons/obj/hydroponics/growing_fruits.dmi and b/icons/obj/hydroponics/growing_fruits.dmi differ
diff --git a/icons/obj/hydroponics/harvest.dmi b/icons/obj/hydroponics/harvest.dmi
index a1ab5b08e5..61070621c2 100644
Binary files a/icons/obj/hydroponics/harvest.dmi and b/icons/obj/hydroponics/harvest.dmi differ
diff --git a/icons/obj/hydroponics/seeds.dmi b/icons/obj/hydroponics/seeds.dmi
index 5a2088c332..30be27486b 100644
Binary files a/icons/obj/hydroponics/seeds.dmi and b/icons/obj/hydroponics/seeds.dmi differ
diff --git a/icons/obj/surgery.dmi b/icons/obj/surgery.dmi
index 1bddaaf3b1..a07e175fc6 100755
Binary files a/icons/obj/surgery.dmi and b/icons/obj/surgery.dmi differ
diff --git a/interface/stylesheet.dm b/interface/stylesheet.dm
index cdf6df2dab..2ecd9f9004 100644
--- a/interface/stylesheet.dm
+++ b/interface/stylesheet.dm
@@ -153,6 +153,30 @@ h1.alert, h2.alert {color: #000000;}
.redtext {color: #FF0000; font-size: 3;}
.clown {color: #FF69Bf; font-size: 3; font-family: "Comic Sans MS", cursive, sans-serif; font-weight: bold;}
.his_grace {color: #15D512; font-family: "Courier New", cursive, sans-serif; font-style: italic;}
+.velvet {color: #660015; font-weight: bold; animation: velvet 5000ms infinite;}
+@keyframes velvet {
+ 0% { color: #400020; }
+ 40% { color: #FF0000; }
+ 50% { color: #FF8888; }
+ 60% { color: #FF0000; }
+ 100% { color: #400020; }
+}
+
+.hypnophrase {color: #3bb5d3; font-weight: bold; animation: hypnocolor 1500ms infinite;}
+@keyframes hypnocolor {
+ 0% { color: #0d0d0d; }
+ 25% { color: #410194; }
+ 50% { color: #7f17d8; }
+ 75% { color: #410194; }
+ 100% { color: #3bb5d3; }
+}
+
+.phobia {color: #dd0000; font-weight: bold; animation: phobia 750ms infinite;}
+ @keyframes phobia {
+ 0% { color: #0d0d0d; }
+ 50% { color: #dd0000; }
+ 100% { color: #0d0d0d; }
+}
.icon {height: 1em; width: auto;}
diff --git a/modular_citadel/code/datums/mood_events/chem_events.dm b/modular_citadel/code/datums/mood_events/chem_events.dm
new file mode 100644
index 0000000000..c496a0fa27
--- /dev/null
+++ b/modular_citadel/code/datums/mood_events/chem_events.dm
@@ -0,0 +1,59 @@
+/datum/mood_event/eigenstate
+ mood_change = -3
+ description = "Where the hell am I? Is this an alternative dimension ?\n"
+
+/datum/mood_event/enthrall
+ mood_change = 5
+
+/datum/mood_event/enthrall/add_effects(message)
+ description = "[message]\n"
+
+/datum/mood_event/enthrallpraise
+ mood_change = 10
+ timeout = 1 MINUTES
+
+/datum/mood_event/enthrallpraise/add_effects(message)
+ description = "[message]\n"
+
+/datum/mood_event/enthrallscold
+ mood_change = -10
+ timeout = 1 MINUTES
+
+/datum/mood_event/enthrallscold/add_effects(message)
+ description = "[message]\n"//aaa I'm not kinky enough for this
+
+/datum/mood_event/enthrallmissing1
+ mood_change = -5
+
+/datum/mood_event/enthrallmissing1/add_effects(message)
+ description = "[message]\n"
+
+/datum/mood_event/enthrallmissing2
+ mood_change = -10
+
+/datum/mood_event/enthrallmissing2/add_effects(message)
+ description = "[message]\n"
+
+/datum/mood_event/enthrallmissing3
+ mood_change = -15
+
+/datum/mood_event/enthrallmissing3/add_effects(message)
+ description = "[message]\n"
+
+/datum/mood_event/enthrallmissing4
+ mood_change = -25
+
+/datum/mood_event/enthrallmissing4/add_effects(message)
+ description = "[message]\n"
+
+/datum/mood_event/InLove
+ mood_change = 10
+
+/datum/mood_event/InLove/add_effects(message)
+ description = "[message]\n"
+
+/datum/mood_event/MissingLove
+ mood_change = -10
+
+/datum/mood_event/MissingLove/add_effects(message)
+ description = "[message]\n"
diff --git a/modular_citadel/code/datums/mood_events/generic_negative_events.dm b/modular_citadel/code/datums/mood_events/generic_negative_events.dm
index c0f2591656..bb04b0b283 100644
--- a/modular_citadel/code/datums/mood_events/generic_negative_events.dm
+++ b/modular_citadel/code/datums/mood_events/generic_negative_events.dm
@@ -3,19 +3,19 @@
/datum/mood_event/plushjack
description = "I have butchered a plush recently.\n"
mood_change = -1
- timeout = 1200
+ timeout = 2 MINUTES
/datum/mood_event/plush_nostuffing
description = "A plush I tried to pet had no stuffing...\n"
mood_change = -1
- timeout = 1200
+ timeout = 2 MINUTES
/datum/mood_event/emptypred
description = "I had to let someone out.\n"
mood_change = -2
- timeout = 600
+ timeout = 1 MINUTES
/datum/mood_event/emptyprey
description = "It feels quite cold out here.\n"
mood_change = -2
- timeout = 600
\ No newline at end of file
+ timeout = 1 MINUTES
diff --git a/modular_citadel/code/datums/mood_events/generic_positive_events.dm b/modular_citadel/code/datums/mood_events/generic_positive_events.dm
index 7b989d7700..ffa661e6e9 100644
--- a/modular_citadel/code/datums/mood_events/generic_positive_events.dm
+++ b/modular_citadel/code/datums/mood_events/generic_positive_events.dm
@@ -3,12 +3,12 @@
/datum/mood_event/headpat
description = "Headpats are nice.\n"
mood_change = 2
- timeout = 1200
+ timeout = 2 MINUTES
/datum/mood_event/hugbox
description = "I hugged a box of hugs recently.\n"
mood_change = 1
- timeout = 1200
+ timeout = 2 MINUTES
/datum/mood_event/plushpet
description = "I pet a plush recently.\n"
diff --git a/modular_citadel/code/datums/status_effects/chems.dm b/modular_citadel/code/datums/status_effects/chems.dm
new file mode 100644
index 0000000000..4bc83d2af6
--- /dev/null
+++ b/modular_citadel/code/datums/status_effects/chems.dm
@@ -0,0 +1,760 @@
+#define DICK_MOVEMENT_SPEED "hugedick"
+#define BREAST_MOVEMENT_SPEED "megamilk"
+
+/datum/status_effect/chem/SGDF
+ id = "SGDF"
+ var/mob/living/fermi_Clone
+ var/mob/living/original
+ var/datum/mind/originalmind
+ var/status_set = FALSE
+ alert_type = null
+
+/datum/status_effect/chem/SGDF/on_apply()
+ log_game("FERMICHEM: SGDF status appied on [owner], ID: [owner.key]")
+ fermi_Clone = owner
+ return ..()
+
+/datum/status_effect/chem/SGDF/tick()
+ if(!status_set)
+ return ..()
+ if(original.stat == DEAD || original == null || !original)
+ if((fermi_Clone && fermi_Clone.stat != DEAD) || (fermi_Clone == null))
+ if(originalmind)
+ owner.remove_status_effect(src)
+ ..()
+
+/datum/status_effect/chem/SGDF/on_remove(mob/living/carbon/M)
+ log_game("FERMICHEM: SGDF mind shift applied. [owner] is now playing as their clone and should not have memories after their clone split (look up SGDF status applied). ID: [owner.key]")
+ originalmind.transfer_to(fermi_Clone)
+ to_chat(owner, "Lucidity shoots to your previously blank mind as your mind suddenly finishes the cloning process. You marvel for a moment at yourself, as your mind subconciously recollects all your memories up until the point when you cloned yourself. curiously, you find that you memories are blank after you ingested the sythetic serum, leaving you to wonder where the other you is.")
+ to_chat(M, "Lucidity shoots to your previously blank mind as your mind suddenly finishes the cloning process. You marvel for a moment at yourself, as your mind subconciously recollects all your memories up until the point when you cloned yourself. curiously, you find that you memories are blank after you ingested the sythetic serum, leaving you to wonder where the other you is.")
+ fermi_Clone = null
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/datum/status_effect/chem/breast_enlarger
+ id = "breast_enlarger"
+ alert_type = null
+ var/moveCalc = 1
+ var/cachedmoveCalc = 1
+
+/datum/status_effect/chem/breast_enlarger/on_apply(mob/living/carbon/human/H)//Removes clothes, they're too small to contain you. You belong to space now.
+ log_game("FERMICHEM: [owner]'s breasts has reached comical sizes. ID: [owner.key]")
+ var/mob/living/carbon/human/o = owner
+ var/items = o.get_contents()
+ for(var/obj/item/W in items)
+ if(W == o.w_uniform || W == o.wear_suit)
+ o.dropItemToGround(W, TRUE)
+ playsound(o.loc, 'sound/items/poster_ripped.ogg', 50, 1)
+ to_chat(o, "Your clothes give, ripping into peices under the strain of your swelling breasts! Unless you manage to reduce the size of your breasts, there's no way you're going to be able to put anything on over these melons..!")
+ o.visible_message("[o]'s chest suddenly bursts forth, ripping their clothes off!'")
+ else
+ to_chat(o, "Your bountiful bosom is so rich with mass, you seriously doubt you'll be able to fit any clothes over it.")
+ return ..()
+
+/datum/status_effect/chem/breast_enlarger/tick(mob/living/carbon/human/H)//If you try to wear clothes, you fail. Slows you down if you're comically huge
+ var/mob/living/carbon/human/o = owner
+ var/obj/item/organ/genital/breasts/B = o.getorganslot("breasts")
+ moveCalc = 1+((round(B.cached_size) - 9)/3) //Afffects how fast you move, and how often you can click.
+ if(!B)
+ o.remove_movespeed_modifier(BREAST_MOVEMENT_SPEED)
+ sizeMoveMod(1)
+ owner.remove_status_effect(src)
+ var/items = o.get_contents()
+ for(var/obj/item/W in items)
+ if(W == o.w_uniform || W == o.wear_suit)
+ o.dropItemToGround(W, TRUE)
+ playsound(o.loc, 'sound/items/poster_ripped.ogg', 50, 1)
+ to_chat(owner, "Your enormous breasts are way too large to fit anything over them!")
+ if (B.size == "huge")
+ if(prob(1))
+ to_chat(owner, "Your back is feeling sore.")
+ var/target = o.get_bodypart(BODY_ZONE_CHEST)
+ o.apply_damage(0.1, BRUTE, target)
+ if(!B.cached_size == B.breast_values[B.prev_size])
+ o.add_movespeed_modifier(BREAST_MOVEMENT_SPEED, TRUE, 100, NONE, override = TRUE, multiplicative_slowdown = moveCalc)
+ sizeMoveMod(moveCalc)
+ return ..()
+ else if (B.breast_values[B.size] > B.breast_values[B.prev_size])
+ o.add_movespeed_modifier(BREAST_MOVEMENT_SPEED, TRUE, 100, NONE, override = TRUE, multiplicative_slowdown = moveCalc)
+ sizeMoveMod(moveCalc)
+ else if (B.breast_values[B.size] < B.breast_values[B.prev_size])
+ o.add_movespeed_modifier(BREAST_MOVEMENT_SPEED, TRUE, 100, NONE, override = TRUE, multiplicative_slowdown = moveCalc)
+ sizeMoveMod(moveCalc)
+ if((B.cached_size) < 16)
+ switch(round(B.cached_size))
+ if(9)
+ if (B.breast_values[B.prev_size] != B.breast_values[B.size])
+ to_chat(o, "Your expansive chest has become a more managable size, liberating your movements.")
+ if(10 to INFINITY)
+ if (B.breast_values[B.prev_size] != B.breast_values[B.size])
+ to_chat(H, "Your indulgent busom is so substantial, it's affecting your movements!")
+ if(prob(1))
+ to_chat(owner, "Your back is feeling a little sore.")
+ ..()
+
+/datum/status_effect/chem/breast_enlarger/on_remove(mob/living/carbon/M)
+ log_game("FERMICHEM: [owner]'s breasts has reduced to an acceptable size. ID: [owner.key]")
+ owner.remove_movespeed_modifier(BREAST_MOVEMENT_SPEED)
+ sizeMoveMod(1)
+
+/datum/status_effect/chem/breast_enlarger/proc/sizeMoveMod(var/value)
+ if(cachedmoveCalc == value)
+ return
+ owner.next_move_modifier /= cachedmoveCalc
+ owner.next_move_modifier *= value
+ cachedmoveCalc = value
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/datum/status_effect/chem/penis_enlarger
+ id = "penis_enlarger"
+ alert_type = null
+ var/bloodCalc
+ var/moveCalc
+
+/datum/status_effect/chem/penis_enlarger/on_apply(mob/living/carbon/human/H)//Removes clothes, they're too small to contain you. You belong to space now.
+ log_game("FERMICHEM: [owner]'s dick has reached comical sizes. ID: [owner.key]")
+ var/mob/living/carbon/human/o = owner
+ var/items = o.get_contents()
+ if(o.w_uniform || o.wear_suit)
+ to_chat(o, "Your clothes give, ripping into peices under the strain of your swelling pecker! Unless you manage to reduce the size of your emancipated trouser snake, there's no way you're going to be able to put anything on over this girth..!")
+ owner.visible_message("[o]'s schlong suddenly bursts forth, ripping their clothes off!'")
+ else
+ to_chat(o, "Your emancipated trouser snake is so ripe with girth, you seriously doubt you'll be able to fit any clothes over it.")
+ for(var/obj/item/W in items)
+ if(W == o.w_uniform || W == o.wear_suit)
+ o.dropItemToGround(W, TRUE)
+ playsound(o.loc, 'sound/items/poster_ripped.ogg', 50, 1)
+ return ..()
+
+
+/datum/status_effect/chem/penis_enlarger/tick(mob/living/carbon/M)
+ var/mob/living/carbon/human/o = owner
+ var/obj/item/organ/genital/penis/P = o.getorganslot("penis")
+ moveCalc = 1+((round(P.length) - 21)/3) //effects how fast you can move
+ bloodCalc = 1+((round(P.length) - 21)/15) //effects how much blood you need (I didn' bother adding an arousal check because I'm spending too much time on this organ already.)
+ if(!P)
+ o.remove_movespeed_modifier(DICK_MOVEMENT_SPEED)
+ o.ResetBloodVol()
+ owner.remove_status_effect(src)
+ var/items = o.get_contents()
+ for(var/obj/item/W in items)
+ if(W == o.w_uniform || W == o.wear_suit)
+ o.dropItemToGround(W, TRUE)
+ playsound(o.loc, 'sound/items/poster_ripped.ogg', 50, 1)
+ to_chat(owner, "Your enormous package is way to large to fit anything over!")
+ switch(round(P.cached_length))
+ if(21)
+ to_chat(o, "Your rascally willy has become a more managable size, liberating your movements.")
+ o.remove_movespeed_modifier(DICK_MOVEMENT_SPEED)
+ o.AdjustBloodVol(bloodCalc)
+ if(22 to INFINITY)
+ if(prob(2))
+ to_chat(o, "Your indulgent johnson is so substantial, it's taking all your blood and affecting your movements!")
+ o.add_movespeed_modifier(DICK_MOVEMENT_SPEED, TRUE, 100, NONE, override = TRUE, multiplicative_slowdown = moveCalc)
+ o.AdjustBloodVol(bloodCalc)
+ ..()
+
+/datum/status_effect/chem/penis_enlarger/on_remove(mob/living/carbon/human/o)
+ log_game("FERMICHEM: [owner]'s dick has reduced to an acceptable size. ID: [owner.key]")
+ owner.remove_movespeed_modifier(DICK_MOVEMENT_SPEED)
+ owner.ResetBloodVol()
+
+
+/*//////////////////////////////////////////
+ Mind control functions
+///////////////////////////////////////////
+*/
+
+//Preamble
+/*
+/mob/living
+ var/lewd = TRUE
+*/
+
+/mob/living/verb/toggle_lewd()
+ set category = "IC"
+ set name = "Toggle Lewdchem"
+ set desc = "Allows you to toggle if you'd like lewd flavour messages."
+ client.prefs.lewdchem = !(client.prefs.lewdchem)
+ to_chat(usr, "You [(client.prefs.lewdchem?"will":"no longer")] receive lewdchem messages.")
+
+/datum/status_effect/chem/enthrall
+ id = "enthrall"
+ alert_type = null
+ //examine_text TODO
+ var/enthrallTally = 1 //Keeps track of the enthralling process
+ var/resistanceTally = 0 //Keeps track of the resistance
+ var/deltaResist //The total resistance added per resist click
+
+ var/phase = 1 //-1: resisted state, due to be removed.0: sleeper agent, no effects unless triggered 1: initial, 2: 2nd stage - more commands, 3rd: fully enthralled, 4th Mindbroken
+
+ var/status = null //status effects
+ var/statusStrength = 0 //strength of status effect
+
+ var/mob/living/master //Enchanter's person
+ var/enthrallID //Enchanter's ckey
+ var/enthrallGender //Use master or mistress
+
+ var/mental_capacity //Higher it is, lower the cooldown on commands, capacity reduces with resistance.
+ var/datum/weakref/redirect_component //resistance
+
+ var/distancelist = list(2,1.5,1,0.8,0.6,0.5,0.4,0.3,0.2) //Distance multipliers
+
+ var/withdrawal = FALSE //withdrawl
+ var/withdrawalTick = 0 //counts how long withdrawl is going on for
+
+ var/list/customTriggers = list() //the list of custom triggers
+
+ var/cooldown = 0 //cooldown on commands
+ var/cooldownMsg = TRUE //If cooldown message has been sent
+ var/cTriggered = FALSE //If someone is triggered (so they can't trigger themselves with what they say for infinite loops)
+ var/resistGrowth = 0 //Resistance accrues over time
+ var/DistApart = 1 //Distance between master and owner
+ var/tranceTime = 0 //how long trance effects apply on trance status
+
+ var/customEcho //Custom looping text in owner
+ var/customSpan //Custom spans for looping text
+
+/datum/status_effect/chem/enthrall/on_apply()
+ var/mob/living/carbon/M = owner
+ var/datum/reagent/fermi/enthrall/E = locate(/datum/reagent/fermi/enthrall) in M.reagents.reagent_list
+ if(!E)
+ message_admins("WARNING: FermiChem: No master found in thrall, did you bus in the status? You need to set up the vars manually in the chem if it's not reacted/bussed. Someone set up the reaction/status proc incorrectly if not (Don't use donor blood). Console them with a chemcat plush maybe?")
+ owner.remove_status_effect(src)
+ enthrallID = E.creatorID
+ enthrallGender = E.creatorGender
+ master = get_mob_by_key(enthrallID)
+ //if(M.ckey == enthrallID)
+ // owner.remove_status_effect(src)//At the moment, a user can enthrall themselves, toggle this back in if that should be removed.
+ redirect_component = WEAKREF(owner.AddComponent(/datum/component/redirect, list(COMSIG_LIVING_RESIST = CALLBACK(src, .proc/owner_resist)))) //Do resistance calc if resist is pressed#
+ RegisterSignal(owner, COMSIG_MOVABLE_HEAR, .proc/owner_hear)
+ var/obj/item/organ/brain/B = M.getorganslot(ORGAN_SLOT_BRAIN) //It's their brain!
+ mental_capacity = 500 - B.get_brain_damage()
+ var/mob/living/carbon/human/H = owner
+ if(H)//Prefs
+ if(!H.canbearoused)
+ H.client?.prefs.lewdchem = FALSE
+ var/message = "[(owner.client?.prefs.lewdchem?"I am a good pet for [enthrallGender].":"[master] is a really inspirational person!")]"
+ SEND_SIGNAL(M, COMSIG_ADD_MOOD_EVENT, "enthrall", /datum/mood_event/enthrall, message)
+ to_chat(owner, "You feel inexplicably drawn towards [master], their words having a demonstrable effect on you. It seems the closer you are to them, the stronger the effect is. However you aren't fully swayed yet and can resist their effects by repeatedly resisting as much as you can!")
+ log_game("FERMICHEM: MKULTRA: Status applied on [owner] ckey: [owner.key] with a master of [master] ckey: [enthrallID].")
+ SSblackbox.record_feedback("tally", "fermi_chem", 1, "Enthrall attempts")
+ return ..()
+
+/datum/status_effect/chem/enthrall/tick()
+ var/mob/living/carbon/M = owner
+
+ //chem calculations
+ if(!owner.reagents.has_reagent("enthrall") && !owner.reagents.has_reagent("enthrallTest"))
+ if (phase < 3 && phase != 0)
+ deltaResist += 3//If you've no chem, then you break out quickly
+ if(prob(5))
+ to_chat(owner, "Your mind starts to restore some of it's clarity as you feel the effects of the drug wain.")
+ if (mental_capacity <= 500 || phase == 4)
+ if (owner.reagents.has_reagent("mannitol"))
+ mental_capacity += 5
+ if (owner.reagents.has_reagent("neurine"))
+ mental_capacity += 10
+
+ //mindshield check
+ if(HAS_TRAIT(M, TRAIT_MINDSHIELD))//If you manage to enrapture a head, wow, GJ. (resisting gives a bigger bonus with a mindshield) From what I can tell, this isn't possible.
+ resistanceTally += 2
+ if(prob(10))
+ to_chat(owner, "You feel lucidity returning to your mind as the mindshield buzzes, attempting to return your brain to normal function.")
+ if(phase == 4)
+ mental_capacity += 5
+
+ //phase specific events
+ switch(phase)
+ if(-1)//fully removed
+ SEND_SIGNAL(M, COMSIG_CLEAR_MOOD_EVENT, "enthrall")
+ log_game("FERMICHEM: MKULTRA: Status REMOVED from [owner] ckey: [owner.key] with a master of [master] ckey: [enthrallID].")
+ owner.remove_status_effect(src)
+ return
+ if(0)// sleeper agent
+ if (cooldown > 0)
+ cooldown -= 1
+ return
+ if(1)//Initial enthrallment
+ if (enthrallTally > 125)
+ phase += 1
+ mental_capacity -= resistanceTally//leftover resistance per step is taken away from mental_capacity.
+ resistanceTally /= 2
+ enthrallTally = 0
+ SSblackbox.record_feedback("tally", "fermi_chem", 1, "Enthralled to state 2")
+ if(owner.client?.prefs.lewdchem)
+ to_chat(owner, "Your conciousness slips, as you sink deeper into trance and servitude.")
+ else
+ to_chat(owner, "Your conciousness slips, as you feel more drawn to following [master].")
+
+ else if (resistanceTally > 125)
+ phase = -1
+ to_chat(owner, "You break free of the influence in your mind, your thoughts suddenly turning lucid!")
+ if(DistApart < 10)
+ to_chat(master, "[(master.client?.prefs.lewdchem?"Your pet":"Your thrall")] seems to have broken free of your enthrallment!")
+ SSblackbox.record_feedback("tally", "fermi_chem", 1, "Thralls broken free")
+ owner.remove_status_effect(src) //If resisted in phase 1, effect is removed.
+ if(prob(10))
+ if(owner.client?.prefs.lewdchem)
+ to_chat(owner, "[pick("It feels so good to listen to [master].", "You can't keep your eyes off [master].", "[master]'s voice is making you feel so sleepy.", "You feel so comfortable with [master]", "[master] is so dominant, it feels right to obey them.")].")
+ if (2) //partially enthralled
+ if(enthrallTally > 200)
+ phase += 1
+ mental_capacity -= resistanceTally//leftover resistance per step is taken away from mental_capacity.
+ enthrallTally = 0
+ resistanceTally /= 2
+ if(owner.client?.prefs.lewdchem)
+ to_chat(owner, "Your mind gives, eagerly obeying and serving [master].")
+ to_chat(owner, "You are now fully enthralled to [master], and eager to follow their commands. However you find that in your intoxicated state you are unable to resort to violence. Equally you are unable to commit suicide, even if ordered to, as you cannot serve your [enthrallGender] in death. ")//If people start using this as an excuse to be violent I'll just make them all pacifists so it's not OP.
+ else
+ to_chat(owner, "You are unable to put up a resistance any longer, and now are under the influence of [master]. However you find that in your intoxicated state you are unable to resort to violence. Equally you are unable to commit suicide, even if ordered to, as you cannot follow [master] in death. ")
+ to_chat(master, "Your [(master.client?.prefs.lewdchem?"pet":"follower")] [owner] appears to have fully fallen under your sway.")
+ log_game("FERMICHEM: MKULTRA: Status on [owner] ckey: [owner.key] has been fully entrhalled (state 3) with a master of [master] ckey: [enthrallID].")
+ SSblackbox.record_feedback("tally", "fermi_chem", 1, "thralls fully enthralled.")
+ else if (resistanceTally > 200)
+ enthrallTally *= 0.5
+ phase -= 1
+ resistanceTally = 0
+ resistGrowth = 0
+ to_chat(owner, "You manage to shake some of the effects from your addled mind, however you can still feel yourself drawn towards [master].")
+ if(prob(10))
+ if(owner.client?.prefs.lewdchem)
+ to_chat(owner, "[pick("It feels so good to listen to [enthrallGender].", "You can't keep your eyes off [enthrallGender].", "[enthrallGender]'s voice is making you feel so sleepy.", "You feel so comfortable with [enthrallGender]", "[enthrallGender] is so dominant, it feels right to obey them.")].")
+ if (3)//fully entranced
+ if ((resistanceTally >= 200 && withdrawalTick >= 150) || (HAS_TRAIT(M, TRAIT_MINDSHIELD) && (resistanceTally >= 100)))
+ enthrallTally = 0
+ phase -= 1
+ resistanceTally = 0
+ resistGrowth = 0
+ to_chat(owner, "The separation from [(owner.client?.prefs.lewdchem?"your [enthrallGender]":"[master]")] sparks a small flame of resistance in yourself, as your mind slowly starts to return to normal.")
+ REMOVE_TRAIT(owner, TRAIT_PACIFISM, "MKUltra")
+ if(prob(1))
+ if(owner.client?.prefs.lewdchem && !customEcho)
+ to_chat(owner, "[pick("I belong to [enthrallGender].", "[enthrallGender] knows whats best for me.", "Obedence is pleasure.", "I exist to serve [enthrallGender].", "[enthrallGender] is so dominant, it feels right to obey them.")].")
+ if (4) //mindbroken
+ if (mental_capacity >= 499 && (owner.getBrainLoss() <=0 || HAS_TRAIT(M, TRAIT_MINDSHIELD)) && !owner.reagents.has_reagent("MKUltra"))
+ phase = 2
+ mental_capacity = 500
+ customTriggers = list()
+ to_chat(owner, "Your mind starts to heal, fixing the damage caused by the massive amounts of chem injected into your system earlier, returning clarity to your mind. Though, you still feel drawn towards [master]'s words...'")
+ M.slurring = 0
+ M.confused = 0
+ resistGrowth = 0
+ else
+ if (cooldown > 0)
+ cooldown -= (0.8 + (mental_capacity/500))
+ cooldownMsg = FALSE
+ else if (cooldownMsg == FALSE)
+ if(DistApart < 10)
+ if(master.client?.prefs.lewdchem)
+ to_chat(master, "Your pet [owner] appears to have finished internalising your last command.")
+ cooldownMsg = TRUE
+ else
+ to_chat(master, "Your thrall [owner] appears to have finished internalising your last command.")
+ cooldownMsg = TRUE
+ if(get_dist(master, owner) > 10)
+ if(prob(10))
+ to_chat(owner, "You feel [(owner.client?.prefs.lewdchem?"a deep NEED to return to your [enthrallGender]":"like you have to return to [master]")].")
+ M.throw_at(get_step_towards(master,owner), 5, 1)
+ return//If you break the mind of someone, you can't use status effects on them.
+
+
+ //distance calculations
+ DistApart = get_dist(master, owner)
+ switch(DistApart)
+ if(0 to 8)//If the enchanter is within range, increase enthrallTally, remove withdrawal subproc and undo withdrawal effects.
+ if(phase <= 2)
+ enthrallTally += distancelist[get_dist(master, owner)+1]
+ if(withdrawalTick > 0)
+ withdrawalTick -= 1
+ //calming effects
+ M.hallucination = max(0, M.hallucination - 5)
+ M.stuttering = max(0, M.stuttering - 5)
+ M.jitteriness = max(0, M.jitteriness - 5)
+ if(owner.getBrainLoss() >=20)
+ owner.adjustBrainLoss(-0.2)
+ if(withdrawal == TRUE)
+ REMOVE_TRAIT(owner, TRAIT_PACIFISM, "MKUltra")
+ SEND_SIGNAL(M, COMSIG_CLEAR_MOOD_EVENT, "EnthMissing1")
+ SEND_SIGNAL(M, COMSIG_CLEAR_MOOD_EVENT, "EnthMissing2")
+ SEND_SIGNAL(M, COMSIG_CLEAR_MOOD_EVENT, "EnthMissing3")
+ SEND_SIGNAL(M, COMSIG_CLEAR_MOOD_EVENT, "EnthMissing4")
+ withdrawal = FALSE
+ if(9 to INFINITY)//If they're not nearby, enable withdrawl effects.
+ withdrawal = TRUE
+
+ //Withdrawal subproc:
+ if (withdrawal == TRUE)//Your minions are really REALLY needy.
+ switch(withdrawalTick)//denial
+ if(5)//To reduce spam
+ to_chat(owner, "You are unable to complete [(owner.client?.prefs.lewdchem?"your [enthrallGender]":"[master]")]'s orders without their presence, and any commands and objectives given to you prior are not in effect until you are back with them.")
+ ADD_TRAIT(owner, TRAIT_PACIFISM, "MKUltra") //IMPORTANT
+ if(10 to 35)//Gives wiggle room, so you're not SUPER needy
+ if(prob(5))
+ to_chat(owner, "You're starting to miss [(owner.client?.prefs.lewdchem?"your [enthrallGender]":"[master]")].")
+ if(prob(5))
+ owner.adjustBrainLoss(0.1)
+ to_chat(owner, "[(owner.client?.prefs.lewdchem?"[enthrallGender]":"[master]")] will surely be back soon") //denial
+ if(36)
+ var/message = "[(owner.client?.prefs.lewdchem?"I feel empty when [enthrallGender]'s not around..":"I miss [master]'s presence")]"
+ SEND_SIGNAL(M, COMSIG_ADD_MOOD_EVENT, "EnthMissing1", /datum/mood_event/enthrallmissing1, message)
+ if(37 to 65)//barganing
+ if(prob(10))
+ to_chat(owner, "They are coming back, right...?")
+ owner.adjustBrainLoss(0.5)
+ if(prob(10))
+ if(owner.client?.prefs.lewdchem)
+ to_chat(owner, "I just need to be a good pet for [enthrallGender], they'll surely return if I'm a good pet.")
+ owner.adjustBrainLoss(-1.5)
+ if(66)
+ SEND_SIGNAL(M, COMSIG_CLEAR_MOOD_EVENT, "EnthMissing1")
+ var/message = "[(owner.client?.prefs.lewdchem?"I feel so lost in this complicated world without [enthrallGender]..":"I have to return to [master]!")]"
+ to_chat(owner, "You start to feel really angry about how you're not with [(owner.client?.prefs.lewdchem?"your [enthrallGender]":"[master]")]!")
+ SEND_SIGNAL(M, COMSIG_ADD_MOOD_EVENT, "EnthMissing2", /datum/mood_event/enthrallmissing2, message)
+ owner.stuttering += 50
+ owner.jitteriness += 250
+ if(67 to 89) //anger
+ if(prob(10))
+ addtimer(CALLBACK(M, /mob/verb/a_intent_change, INTENT_HARM), 2)
+ addtimer(CALLBACK(M, /mob/proc/click_random_mob), 2)
+ if(owner.client?.prefs.lewdchem)
+ to_chat(owner, "You are overwhelmed with anger at the lack of [enthrallGender]'s presence and suddenly lash out!")
+ else
+ to_chat(owner, "You are overwhelmed with anger and suddenly lash out!")
+ if(90)
+ SEND_SIGNAL(M, COMSIG_CLEAR_MOOD_EVENT, "EnthMissing2")
+ var/message = "[(owner.client?.prefs.lewdchem?"Where are you [enthrallGender]??!":"I need to find [master]!")]"
+ SEND_SIGNAL(M, COMSIG_ADD_MOOD_EVENT, "EnthMissing3", /datum/mood_event/enthrallmissing3, message)
+ if(owner.client?.prefs.lewdchem)
+ to_chat(owner, "You need to find your [enthrallGender] at all costs, you can't hold yourself back anymore!")
+ else
+ to_chat(owner, "You need to find [master] at all costs, you can't hold yourself back anymore!")
+ if(91 to 100)//depression
+ if(prob(10))
+ M.gain_trauma_type(BRAIN_TRAUMA_MILD)
+ owner.stuttering += 35
+ owner.jitteriness += 35
+ else if(prob(25))
+ M.hallucination += 10
+ if(101)
+ SEND_SIGNAL(M, COMSIG_CLEAR_MOOD_EVENT, "EnthMissing3")
+ var/message = "[(owner.client?.prefs.lewdchem?"I'm all alone, It's so hard to continute without [enthrallGender]...":"I really need to find [master]!!!")]"
+ SEND_SIGNAL(M, COMSIG_ADD_MOOD_EVENT, "EnthMissing4", /datum/mood_event/enthrallmissing4, message)
+ to_chat(owner, "You can hardly find the strength to continue without [(owner.client?.prefs.lewdchem?"your [enthrallGender]":"[master]")].")
+ M.gain_trauma_type(BRAIN_TRAUMA_SEVERE)
+ if(102 to 140) //depression 2, revengeance
+ if(prob(20))
+ owner.Stun(50)
+ owner.emote("cry")//does this exist?
+ if(owner.client?.prefs.lewdchem)
+ to_chat(owner, "You're unable to hold back your tears, suddenly sobbing as the desire to see your [enthrallGender] oncemore overwhelms you.")
+ else
+ to_chat(owner, "You are overwheled with withdrawl from [master].")
+ owner.adjustBrainLoss(1)
+ owner.stuttering += 35
+ owner.jitteriness += 35
+ if(prob(10))//2% chance
+ switch(rand(1,5))//Now let's see what hopefully-not-important part of the brain we cut off
+ if(1 to 3)
+ M.gain_trauma_type(BRAIN_TRAUMA_MILD)
+ if(4)
+ M.gain_trauma_type(BRAIN_TRAUMA_SEVERE)
+ if(5)//0.4% chance
+ M.gain_trauma_type(BRAIN_TRAUMA_SPECIAL)
+ if(prob(5))
+ deltaResist += 5
+ if(140 to INFINITY) //acceptance
+ if(prob(15))
+ deltaResist += 5
+ owner.adjustBrainLoss(-1)
+ if(prob(20))
+ if(owner.client?.prefs.lewdchem)
+ to_chat(owner, "Maybe you'll be okay without your [enthrallGender].")
+ else
+ to_chat(owner, "You feel your mental functions slowly begin to return.")
+ if(prob(5))
+ owner.adjustBrainLoss(1)
+ M.hallucination += 30
+
+ withdrawalTick += 0.5//Enough to leave you with a major brain trauma, but not kill you.
+
+ //Status subproc - statuses given to you from your Master
+ //currently 3 statuses; antiresist -if you press resist, increases your enthrallment instead, HEAL - which slowly heals the pet, CHARGE - which breifly increases speed, PACIFY - makes pet a pacifist, ANTIRESIST - frustrates resist presses.
+ if (status)
+
+ if(status == "Antiresist")
+ if (statusStrength < 0)
+ status = null
+ to_chat(owner, "Your mind feels able to resist oncemore.")
+ else
+ statusStrength -= 1
+
+ else if(status == "heal")
+ if (statusStrength < 0)
+ status = null
+ to_chat(owner, "You finish licking your wounds.")
+ else
+ statusStrength -= 1
+ owner.heal_overall_damage(1, 1, 0, FALSE, FALSE)
+ cooldown += 1 //Cooldown doesn't process till status is done
+
+ else if(status == "charge")
+ ADD_TRAIT(owner, TRAIT_GOTTAGOFAST, "MKUltra")
+ status = "charged"
+ if(master.client?.prefs.lewdchem)
+ to_chat(owner, "Your [enthrallGender]'s order fills you with a burst of speed!")
+ else
+ to_chat(owner, "[master]'s command fills you with a burst of speed!")
+
+ else if (status == "charged")
+ if (statusStrength < 0)
+ status = null
+ REMOVE_TRAIT(owner, TRAIT_GOTTAGOFAST, "MKUltra")
+ owner.Knockdown(50)
+ to_chat(owner, "Your body gives out as the adrenaline in your system runs out.")
+ else
+ statusStrength -= 1
+ cooldown += 1 //Cooldown doesn't process till status is done
+
+ else if (status == "pacify")
+ ADD_TRAIT(owner, TRAIT_PACIFISM, "MKUltraStatus")
+ status = null
+
+ //Truth serum?
+ //adrenals?
+
+ //customEcho
+ if(customEcho && withdrawal == FALSE && owner.client?.prefs.lewdchem)
+ if(prob(2))
+ if(!customSpan) //just in case!
+ customSpan = "notice"
+ to_chat(owner, "[customEcho].")
+
+ //final tidying
+ resistanceTally += deltaResist
+ deltaResist = 0
+ if(cTriggered >= 0)
+ cTriggered -= 1
+ if (cooldown > 0)
+ cooldown -= (0.8 + (mental_capacity/500))
+ cooldownMsg = FALSE
+ else if (cooldownMsg == FALSE)
+ if(DistApart < 10)
+ if(master.client?.prefs.lewdchem)
+ to_chat(master, "Your pet [owner] appears to have finished internalising your last command.")
+ else
+ to_chat(master, "Your thrall [owner] appears to have finished internalising your last command.")
+ cooldownMsg = TRUE
+ cooldown = 0
+ if (tranceTime > 0 && tranceTime != 51) //custom trances only last 50 ticks.
+ tranceTime -= 1
+ else if (tranceTime == 0) //remove trance after.
+ M.cure_trauma_type(/datum/brain_trauma/hypnosis, TRAUMA_RESILIENCE_SURGERY)
+ M.remove_status_effect(/datum/status_effect/trance)
+ tranceTime = 51
+ //..()
+
+//Remove all stuff
+/datum/status_effect/chem/enthrall/on_remove()
+ var/mob/living/carbon/M = owner
+ M.mind.remove_antag_datum(/datum/antagonist/brainwashed)
+ SEND_SIGNAL(M, COMSIG_CLEAR_MOOD_EVENT, "enthrall")
+ SEND_SIGNAL(M, COMSIG_CLEAR_MOOD_EVENT, "enthrallpraise")
+ SEND_SIGNAL(M, COMSIG_CLEAR_MOOD_EVENT, "enthrallscold")
+ SEND_SIGNAL(M, COMSIG_CLEAR_MOOD_EVENT, "EnthMissing1")
+ SEND_SIGNAL(M, COMSIG_CLEAR_MOOD_EVENT, "EnthMissing2")
+ SEND_SIGNAL(M, COMSIG_CLEAR_MOOD_EVENT, "EnthMissing3")
+ SEND_SIGNAL(M, COMSIG_CLEAR_MOOD_EVENT, "EnthMissing4")
+ qdel(redirect_component.resolve())
+ redirect_component = null
+ UnregisterSignal(owner, COMSIG_MOVABLE_HEAR)
+ REMOVE_TRAIT(owner, TRAIT_PACIFISM, "MKUltra")
+ to_chat(owner, "You're now free of [master]'s influence, and fully independent!'")
+ UnregisterSignal(owner, COMSIG_GLOB_LIVING_SAY_SPECIAL)
+
+
+/datum/status_effect/chem/enthrall/proc/owner_hear(var/hearer, message, atom/movable/speaker, message_language, raw_message, radio_freq, list/spans, message_mode)
+ if(owner.client?.prefs.lewdchem == FALSE)
+ return
+ if (cTriggered > 0)
+ return
+ var/mob/living/carbon/C = owner
+ raw_message = lowertext(raw_message)
+ for (var/trigger in customTriggers)
+ var/cached_trigger = lowertext(trigger)
+ if (findtext(raw_message, cached_trigger))//if trigger1 is the message
+ cTriggered = 5 //Stops triggerparties and as a result, stops servercrashes.
+ log_game("FERMICHEM: MKULTRA: [owner] ckey: [owner.key] has been triggered with [cached_trigger] from [speaker] saying: \"[message]\". (their master being [master] ckey: [enthrallID].)")
+
+ //Speak (Forces player to talk)
+ if (lowertext(customTriggers[trigger][1]) == "speak")//trigger2
+ var/saytext = "Your mouth moves on it's own before you can even catch it."
+ if(HAS_TRAIT(C, TRAIT_NYMPHO))
+ saytext += " You find yourself fully believing in the validity of what you just said and don't think to question it."
+ addtimer(CALLBACK(GLOBAL_PROC, .proc/to_chat, C, "[saytext]"), 5)
+ addtimer(CALLBACK(C, /atom/movable/proc/say, "[customTriggers[trigger][2]]"), 5)
+ //(C.say(customTriggers[trigger][2]))//trigger3
+ log_game("FERMICHEM: MKULTRA: [owner] ckey: [owner.key] has been forced to say: \"[customTriggers[trigger][2]]\" from previous trigger.")
+
+
+ //Echo (repeats message!) allows customisation, but won't display var calls! Defaults to hypnophrase.
+ else if (lowertext(customTriggers[trigger][1]) == "echo")//trigger2
+ addtimer(CALLBACK(GLOBAL_PROC, .proc/to_chat, C, "[customTriggers[trigger][2]]"), 5)
+ //(to_chat(owner, "[customTriggers[trigger][2]]"))//trigger3
+
+ //Shocking truth!
+ else if (lowertext(customTriggers[trigger]) == "shock")
+ if (C.canbearoused && C.client?.prefs.lewdchem)
+ C.adjustArousalLoss(5)
+ C.jitteriness += 100
+ C.stuttering += 25
+ C.Knockdown(60)
+ C.Stun(60)
+ to_chat(owner, "Your muscles seize up, then start spasming wildy!")
+
+ //wah intensifies wah-rks
+ else if (lowertext(customTriggers[trigger]) == "cum")//aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ if (HAS_TRAIT(C, TRAIT_NYMPHO) && C.client?.prefs.lewdchem)
+ if (C.getArousalLoss() > 80)
+ C.mob_climax(forced_climax=TRUE)
+ C.SetStun(10)//We got your stun effects in somewhere, Kev.
+ else
+ C.adjustArousalLoss(10)
+ to_chat(C, "You feel a surge of arousal!")
+ else
+ C.throw_at(get_step_towards(speaker,C), 3, 1) //cut this if it's too hard to get working
+
+ //kneel (knockdown)
+ else if (lowertext(customTriggers[trigger]) == "kneel")//as close to kneeling as you can get, I suppose.
+ to_chat(owner, "You drop to the ground unsurreptitiously.")
+ C.lay_down()
+
+ //strip (some) clothes
+ else if (lowertext(customTriggers[trigger]) == "strip")//This wasn't meant to just be a lewd thing oops.
+ var/mob/living/carbon/human/o = owner
+ var/items = o.get_contents()
+ for(var/obj/item/W in items)
+ if(W == o.w_uniform || W == o.wear_suit)
+ o.dropItemToGround(W, TRUE)
+ to_chat(owner,"You feel compelled to strip your clothes.")
+
+ //trance
+ else if (lowertext(customTriggers[trigger]) == "trance")//Maaaybe too strong. Weakened it, only lasts 50 ticks.
+ var/mob/living/carbon/human/o = owner
+ o.apply_status_effect(/datum/status_effect/trance, 200, TRUE)
+ tranceTime = 50
+ log_game("FERMICHEM: MKULTRA: [owner] ckey: [owner.key] has been tranced from previous trigger.")
+
+ return
+
+/datum/status_effect/chem/enthrall/proc/owner_resist()
+ var/mob/living/carbon/M = owner
+ to_chat(owner, "You attempt to fight against [master]'s influence!")
+
+ //Able to resist checks
+ if (status == "Sleeper" || phase == 0)
+ return
+ else if (phase == 4)
+ if(owner.client?.prefs.lewdchem)
+ to_chat(owner, "Your mind is too far gone to even entertain the thought of resisting. Unless you can fix the brain damage, you won't be able to break free of your [enthrallGender]'s control.")
+ else
+ to_chat(owner, "Your brain is too overwhelmed with from the high volume of chemicals in your system, rendering you unable to resist, unless you can fix the brain damage.")
+ return
+ else if (phase == 3 && withdrawal == FALSE)
+ if(owner.client?.prefs.lewdchem)
+ to_chat(owner, "The presence of your [enthrallGender] fully captures the horizon of your mind, removing any thoughts of resistance. If you get split up from them, then you might be able to entertain the idea of resisting.")
+ else
+ to_chat(owner, "You are unable to resist [master] in your current state. If you get split up from them, then you might be able to resist.")
+ return
+ else if (status == "Antiresist")//If ordered to not resist; resisting while ordered to not makes it last longer, and increases the rate in which you are enthralled.
+ if (statusStrength > 0)
+ if(owner.client?.prefs.lewdchem)
+ to_chat(owner, "The order from your [enthrallGender] to give in is conflicting with your attempt to resist, drawing you deeper into trance! You'll have to wait a bit before attemping again, lest your attempts become frustrated again.")
+ else
+ to_chat(owner, "The order from your [master] to give in is conflicting with your attempt to resist. You'll have to wait a bit before attemping again, lest your attempts become frustrated again.")
+ statusStrength += 1
+ enthrallTally += 1
+ return
+ else
+ status = null
+
+ //base resistance
+ if (deltaResist != 0)//So you can't spam it, you get one deltaResistance per tick.
+ deltaResist += 0.1 //Though I commend your spamming efforts.
+ return
+ else
+ deltaResist = 1.8 + resistGrowth
+ resistGrowth += 0.05
+
+ //distance modifer
+ switch(DistApart)
+ if(0)
+ deltaResist *= 0.8
+ if(1 to 8)//If they're far away, increase resistance.
+ deltaResist *= (1+(DistApart/10))
+ if(9 to INFINITY)//If
+ deltaResist *= 2
+
+
+ if(prob(5))
+ M.emote("me",1,"squints, shaking their head for a moment.")//shows that you're trying to resist sometimes
+ deltaResist *= 1.5
+ //nymphomania
+ if (M.canbearoused && HAS_TRAIT(M, TRAIT_NYMPHO))//I'm okay with this being removed.
+ deltaResist*= 0.5-(((2/200)*M.arousalloss)/1)//more aroused you are, the weaker resistance you can give, the less you are, the more you gain. (+/- 0.5)
+
+ //chemical resistance, brain and annaphros are the key to undoing, but the subject has to to be willing to resist.
+ if (owner.reagents.has_reagent("mannitol"))
+ deltaResist *= 1.25
+ if (owner.reagents.has_reagent("neurine"))
+ deltaResist *= 1.5
+ if (!HAS_TRAIT(owner, TRAIT_CROCRIN_IMMUNE) && M.canbearoused)
+ if (owner.reagents.has_reagent("anaphro"))
+ deltaResist *= 1.5
+ if (owner.reagents.has_reagent("anaphro+"))
+ deltaResist *= 2
+ if (owner.reagents.has_reagent("aphro"))
+ deltaResist *= 0.75
+ if (owner.reagents.has_reagent("aphro+"))
+ deltaResist *= 0.5
+
+ //Antag resistance
+ //cultists are already brainwashed by their god
+ if(iscultist(owner))
+ deltaResist *= 1.3
+ else if (is_servant_of_ratvar(owner))
+ deltaResist *= 1.3
+ //antags should be able to resist, so they can do their other objectives. This chem does frustrate them, but they've all the tools to break free when an oportunity presents itself.
+ else if (owner.mind.assigned_role in GLOB.antagonists)
+ deltaResist *= 1.2
+
+ //role resistance
+ //Chaplains are already brainwashed by their god
+ if(owner.mind.assigned_role == "Chaplain")
+ deltaResist *= 1.2
+ //Command staff has authority,
+ if(owner.mind.assigned_role in GLOB.command_positions)
+ deltaResist *= 1.1
+ //Chemists should be familiar with drug effects
+ if(owner.mind.assigned_role == "Chemist")
+ deltaResist *= 1.2
+
+ //Happiness resistance
+ //Your Thralls are like pets, you need to keep them happy.
+ if(owner.nutrition < 300)
+ deltaResist += (300-owner.nutrition)/6
+ if(owner.health < 100)//Harming your thrall will make them rebel harder.
+ deltaResist *= ((120-owner.health)/100)+1
+ //if(owner.mood.mood) //datum/component/mood TO ADD in FERMICHEM 2
+ //Add cold/hot, oxygen, sanity, happiness? (happiness might be moot, since the mood effects are so strong)
+ //Mental health could play a role too in the other direction
+
+ //If you've a collar, you get a sense of pride
+ if(istype(M.wear_neck, /obj/item/clothing/neck/petcollar))
+ deltaResist *= 0.5
+ if(HAS_TRAIT(M, TRAIT_MINDSHIELD))
+ deltaResist += 5//even faster!
+
+ return
diff --git a/modular_citadel/code/datums/traits/negative.dm b/modular_citadel/code/datums/traits/negative.dm
new file mode 100644
index 0000000000..c0cbe57b5a
--- /dev/null
+++ b/modular_citadel/code/datums/traits/negative.dm
@@ -0,0 +1 @@
+// Citadel-specific Negative Traits
diff --git a/modular_citadel/code/datums/traits/neutral.dm b/modular_citadel/code/datums/traits/neutral.dm
index 05aeb27361..197c9b94e1 100644
--- a/modular_citadel/code/datums/traits/neutral.dm
+++ b/modular_citadel/code/datums/traits/neutral.dm
@@ -31,3 +31,13 @@
mob_trait = TRAIT_MASO
gain_text = "You desire to be hurt."
lose_text = "Pain has become less exciting for you."
+
+/datum/quirk/pharmacokinesis //Prevents unwanted organ additions.
+ name = "Acute hepatic pharmacokinesis"
+ desc = "You've a rare genetic disorder that causes Incubus draft and Sucubus milk to be absorbed by your liver instead."
+ value = 0
+ mob_trait = TRAIT_PHARMA
+ lose_text = "Your liver feels different."
+ var/active = FALSE
+ var/power = 0
+ var/cachedmoveCalc = 1
diff --git a/modular_citadel/code/game/machinery/vending.dm b/modular_citadel/code/game/machinery/vending.dm
index 12c29f7d98..2de98a3171 100755
--- a/modular_citadel/code/game/machinery/vending.dm
+++ b/modular_citadel/code/game/machinery/vending.dm
@@ -22,6 +22,7 @@
/obj/item/storage/hypospraykit/toxin = 2,
/obj/item/storage/hypospraykit/o2 = 2,
/obj/item/storage/hypospraykit/brute = 2,
+ /obj/item/storage/hypospraykit/enlarge = 2,
/obj/item/reagent_containers/glass/bottle/vial/small = 5)
/obj/machinery/vending/wardrobe/chap_wardrobe
diff --git a/modular_citadel/code/game/objects/items/storage/firstaid.dm b/modular_citadel/code/game/objects/items/storage/firstaid.dm
index 6ff1bfbe2c..125330a9b9 100755
--- a/modular_citadel/code/game/objects/items/storage/firstaid.dm
+++ b/modular_citadel/code/game/objects/items/storage/firstaid.dm
@@ -103,6 +103,22 @@
new /obj/item/reagent_containers/glass/bottle/vial/small/preloaded/dexalin(src)
new /obj/item/reagent_containers/glass/bottle/vial/small/preloaded/dexalin(src)
+/obj/item/storage/hypospraykit/enlarge
+ name = "organomegaly trauma hypospray kit"
+ icon_state = "enlarge-mini"
+ item_state = "firstaid-brute"
+
+/obj/item/storage/hypospraykit/enlarge/PopulateContents()
+ if(empty)
+ return
+ new /obj/item/hypospray/mkii/enlarge(src)
+ new /obj/item/reagent_containers/glass/bottle/vial/small/preloaded/breastreduction(src)
+ new /obj/item/reagent_containers/glass/bottle/vial/small/preloaded/breastreduction(src)
+ new /obj/item/reagent_containers/glass/bottle/vial/small/preloaded/breastreduction(src)
+ new /obj/item/reagent_containers/glass/bottle/vial/small/preloaded/penisreduction(src)
+ new /obj/item/reagent_containers/glass/bottle/vial/small/preloaded/penisreduction(src)
+ new /obj/item/reagent_containers/glass/bottle/vial/small/preloaded/penisreduction(src)
+
/obj/item/storage/hypospraykit/brute
name = "brute trauma hypospray kit"
icon_state = "brute-mini"
diff --git a/modular_citadel/code/init.dm b/modular_citadel/code/init.dm
index 2c2b5b811f..ce80580af2 100644
--- a/modular_citadel/code/init.dm
+++ b/modular_citadel/code/init.dm
@@ -22,4 +22,4 @@
GLOB.cock_shapes_icons[K] = value.icon_state
init_sprite_accessory_subtypes(/datum/sprite_accessory/vagina, GLOB.vagina_shapes_list)
init_sprite_accessory_subtypes(/datum/sprite_accessory/breasts, GLOB.breasts_shapes_list)
- GLOB.breasts_size_list = list("a","b","c","d","e") //We need the list to choose from initialized, but it's no longer a sprite_accessory thing.
+ //GLOB.breasts_size_list = list("a","b","c","d","e") //We need the list to choose from initialized, but it's no longer a sprite_accessory thing. This is defined twice?
diff --git a/modular_citadel/code/modules/arousal/organs/breasts.dm b/modular_citadel/code/modules/arousal/organs/breasts.dm
index 9771dd0897..3df2218766 100644
--- a/modular_citadel/code/modules/arousal/organs/breasts.dm
+++ b/modular_citadel/code/modules/arousal/organs/breasts.dm
@@ -5,11 +5,17 @@
icon = 'modular_citadel/icons/obj/genitals/breasts.dmi'
zone = "chest"
slot = "breasts"
- size = BREASTS_SIZE_DEF
+ w_class = 3
+ size = BREASTS_SIZE_DEF //SHOULD BE A LETTER, starts as a number...???
+ var/cached_size = null //for enlargement SHOULD BE A NUMBER
+ var/prev_size //For flavour texts SHOULD BE A LETTER
+ //var/breast_sizes = list ("a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "huge", "flat")
+ var/breast_values = list ("a" = 1, "b" = 2, "c" = 3, "d" = 4, "e" = 5, "f" = 6, "g" = 7, "h" = 8, "i" = 9, "j" = 10, "k" = 11, "l" = 12, "m" = 13, "n" = 14, "o" = 15, "huge" = 16, "flat" = 0)
+ var/statuscheck = FALSE
fluid_id = "milk"
var/amount = 2
producing = TRUE
- shape = "pair"
+ shape = "Pair"
can_masturbate_with = TRUE
masturbation_verb = "massage"
can_climax = TRUE
@@ -49,10 +55,15 @@
desc = "You see three sets of breasts, running from their chest to their belly."
else
desc = "You see some breasts, they seem to be quite exotic."
- if (size)
- desc += " You estimate that they're [uppertext(size)]-cups."
- else
- desc += " You wouldn't measure them in cup sizes."
+ if(cached_size > 16)
+ desc = "You see [pick("some serious honkers", "a real set of badonkers", "some dobonhonkeros", "massive dohoonkabhankoloos", "two big old tonhongerekoogers", "a couple of giant bonkhonagahoogs", "a pair of humongous hungolomghnonoloughongous")]. Their volume is way beyond cupsize now, measuring in about [round(cached_size)]cm in diameter."
+ else if (!isnum(size))
+ if (size == "flat")
+ desc += " They're very small and flatchested, however."
+ else
+ desc += " You estimate that they're [uppertext(size)]-cups."
+ //string = "breasts_[lowertext(shape)]_[size]-s"
+
if(producing && aroused_state)
desc += " They're leaking [fluid_id]."
var/string
@@ -69,3 +80,57 @@
var/mob/living/carbon/human/H = owner
icon_state = sanitize_text(string)
H.update_genitals()
+
+ icon_state = sanitize_text(string)
+
+
+//Allows breasts to grow and change size, with sprite changes too.
+//maximum wah
+//Comical sizes slow you down in movement and actions.
+//Rediculous sizes makes you more cumbersome.
+//this is far too lewd wah
+
+/obj/item/organ/genital/breasts/update_size()//wah
+
+ if(!ishuman(owner) || !owner)
+ return
+ if(cached_size < 0)//I don't actually know what round() does to negative numbers, so to be safe!!fixed
+ to_chat(owner, "You feel your breasts shrinking away from your body as your chest flattens out.")
+ src.Remove(owner)
+ switch(round(cached_size))
+ if(0) //If flatchested
+ size = "flat"
+ if(owner.has_status_effect(/datum/status_effect/chem/breast_enlarger))
+ owner.remove_status_effect(/datum/status_effect/chem/breast_enlarger)
+ statuscheck = FALSE
+ if(1 to 8) //If modest size
+ size = breast_values[round(cached_size)]
+ if(owner.has_status_effect(/datum/status_effect/chem/breast_enlarger))
+ owner.remove_status_effect(/datum/status_effect/chem/breast_enlarger)
+ statuscheck = FALSE
+ if(9 to 15) //If massive
+ size = breast_values[round(cached_size)]
+ if(!owner.has_status_effect(/datum/status_effect/chem/breast_enlarger))
+ owner.apply_status_effect(/datum/status_effect/chem/breast_enlarger)
+ statuscheck = TRUE
+ if(16 to INFINITY) //if Rediculous
+ size = cached_size
+
+ if(round(cached_size) < 16)//Because byond doesn't count from 0, I have to do this.
+ if (prev_size == 0)
+ prev_size = "flat"
+ if(size == 0)//Bloody byond with it's counting from 1
+ size = "flat"
+ if(isnum(prev_size))
+ prev_size = breast_values[prev_size]
+ if (breast_values[size] > breast_values[prev_size])
+ to_chat(owner, "Your breasts [pick("swell up to", "flourish into", "expand into", "burst forth into", "grow eagerly into", "amplify into")] a [uppertext(size)]-cup.")
+ var/mob/living/carbon/human/H = owner
+ H.Force_update_genitals()
+ else if ((breast_values[size] < breast_values[prev_size]) && (breast_values[size] > 0.5))
+ to_chat(owner, "Your breasts [pick("shrink down to", "decrease into", "diminish into", "deflate into", "shrivel regretfully into", "contracts into")] a [uppertext(size)]-cup.")
+ var/mob/living/carbon/human/H = owner
+ H.Force_update_genitals()
+ prev_size = size
+ else if (cached_size >= 16)
+ size = "huge"
diff --git a/modular_citadel/code/modules/arousal/organs/genitals.dm b/modular_citadel/code/modules/arousal/organs/genitals.dm
index c4fffea355..1346263257 100644
--- a/modular_citadel/code/modules/arousal/organs/genitals.dm
+++ b/modular_citadel/code/modules/arousal/organs/genitals.dm
@@ -1,7 +1,7 @@
/obj/item/organ/genital
color = "#fcccb3"
w_class = WEIGHT_CLASS_NORMAL
- var/shape = "human"
+ var/shape = "Human" //Changed to be uppercase, let me know if this breaks everything..!!
var/sensitivity = AROUSAL_START_VALUE
var/list/genital_flags = list()
var/can_masturbate_with = FALSE
@@ -172,6 +172,8 @@
P.length = dna.features["cock_length"]
P.girth_ratio = dna.features["cock_girth_ratio"]
P.shape = dna.features["cock_shape"]
+ P.prev_length = P.length
+ P.cached_length = P.length
P.update()
/mob/living/carbon/human/proc/give_balls()
@@ -214,6 +216,18 @@
else
B.color = "#[dna.features["breasts_color"]]"
B.size = dna.features["breasts_size"]
+ if(!isnum(B.size))
+ if(B.size == "flat")
+ B.cached_size = 0
+ B.prev_size = 0
+ else if (B.cached_size == "huge")
+ B.prev_size = "huge"
+ else
+ B.cached_size = B.breast_values[B.size]
+ B.prev_size = B.size
+ else
+ B.cached_size = B.size
+ B.prev_size = B.size
B.shape = dna.features["breasts_shape"]
B.fluid_id = dna.features["breasts_fluid"]
B.update()
@@ -256,8 +270,8 @@
switch(layer)
if(GENITALS_BEHIND_LAYER)
return "BEHIND"
- if(GENITALS_ADJ_LAYER)
- return "ADJ"
+ /*if(GENITALS_ADJ_LAYER)
+ return "ADJ"*/
if(GENITALS_FRONT_LAYER)
return "FRONT"
@@ -279,27 +293,65 @@
if(src && !QDELETED(src))
dna.species.handle_genitals(src)
-/datum/species/proc/handle_genitals(mob/living/carbon/human/H)
+//fermichem procs
+/mob/living/carbon/human/proc/Force_update_genitals(mob/living/carbon/human/H) //called in fermiChem
+ dna.species.handle_genitals(src)//should work.
+ //dna.species.handle_breasts(src)
+
+//Checks to see if organs are new on the mob, and changes their colours so that they don't get crazy colours.
+/mob/living/carbon/human/proc/emergent_genital_call()
+ var/organCheck = FALSE
+ var/breastCheck = FALSE
+ var/willyCheck = FALSE
+ if(!canbearoused)
+ ADD_TRAIT(src, TRAIT_PHARMA, "pharma")//Prefs prevent unwanted organs.
+ return
+ for(var/obj/item/organ/O in internal_organs)
+ if(istype(O, /obj/item/organ/genital))
+ organCheck = TRUE
+ if(/obj/item/organ/genital/penis)
+ //dna.features["has_cock"] = TRUE
+ willyCheck = TRUE
+ if(/obj/item/organ/genital/breasts)
+ //dna.features["has_breasts"] = TRUE//Goddamnit get in there.
+ breastCheck = TRUE
+ if(organCheck == FALSE)
+ if(ishuman(src) && dna.species.id == "human")
+ dna.features["genitals_use_skintone"] = TRUE
+ dna.species.use_skintones = TRUE
+ if(MUTCOLORS)
+ if(src.dna.species.fixed_mut_color)
+ dna.features["cock_color"] = "[src.dna.species.fixed_mut_color]"
+ dna.features["breasts_color"] = "[src.dna.species.fixed_mut_color]"
+ return
+ //So people who haven't set stuff up don't get rainbow surprises.
+ dna.features["cock_color"] = "[dna.features["mcolor"]]"
+ dna.features["breasts_color"] = "[dna.features["mcolor"]]"
+ else //If there's a new organ, make it the same colour.
+ if(breastCheck == FALSE)
+ dna.features["breasts_color"] = dna.features["cock_color"]
+ else if (willyCheck == FALSE)
+ dna.features["cock_color"] = dna.features["breasts_color"]
+ return
+
+/datum/species/proc/handle_genitals(mob/living/carbon/human/H)//more like handle sadness
if(!H)//no args
CRASH("H = null")
if(!LAZYLEN(H.internal_organs))//if they have no organs, we're done
return
- if(NOGENITALS in species_traits)//golems and such
+ if((NOGENITALS in species_traits) && (H.genital_override = FALSE))//golems and such - things that shouldn't
return
if(HAS_TRAIT(H, TRAIT_HUSK))
return
-
var/list/genitals_to_add = list()
- var/list/relevant_layers = list(GENITALS_BEHIND_LAYER, GENITALS_ADJ_LAYER, GENITALS_FRONT_LAYER)
+ var/list/relevant_layers = list(GENITALS_BEHIND_LAYER, GENITALS_FRONT_LAYER) //GENITALS_ADJ_LAYER removed
var/list/standing = list()
var/size
var/aroused_state
for(var/L in relevant_layers) //Less hardcode
H.remove_overlay(L)
-
//start scanning for genitals
- //var/list/worn_stuff = H.get_equipped_items()//cache this list so it's not built again
for(var/obj/item/organ/O in H.internal_organs)
if(isgenital(O))
var/obj/item/organ/genital/G = O
@@ -308,7 +360,6 @@
if(G.is_exposed()) //Checks appropriate clothing slot and if it's through_clothes
genitals_to_add += H.getorganslot(G.slot)
//Now we added all genitals that aren't internal and should be rendered
-
//start applying overlays
for(var/layer in relevant_layers)
var/layertext = genitals_layertext(layer)
@@ -326,6 +377,9 @@
if(/obj/item/organ/genital/breasts)
S = GLOB.breasts_shapes_list[G.shape]
+
+
+
if(!S || S.icon_state == "none")
continue
diff --git a/modular_citadel/code/modules/arousal/organs/genitals_sprite_accessories.dm b/modular_citadel/code/modules/arousal/organs/genitals_sprite_accessories.dm
index b913a90fb6..f4af8a40ba 100644
--- a/modular_citadel/code/modules/arousal/organs/genitals_sprite_accessories.dm
+++ b/modular_citadel/code/modules/arousal/organs/genitals_sprite_accessories.dm
@@ -31,6 +31,19 @@
icon_state = "tapered"
name = "Tapered"
+/datum/sprite_accessory/penis/tentacle
+ icon_state = "tentacle"
+ name = "Tentacled"
+
+/datum/sprite_accessory/penis/hemi
+ icon_state = "hemi"
+ name = "Hemi"
+
+/datum/sprite_accessory/penis/hemiknot
+ icon_state = "hemiknot"
+ name = "Knotted Hemi"
+
+
////////////////////////
// Taur cocks go here //
////////////////////////
@@ -111,6 +124,10 @@
icon_state = "furred"
name = "Furred"
+/datum/sprite_accessory/vagina/gaping
+ icon_state = "gaping"
+ name = "Gaping"
+
//BREASTS BE HERE
/datum/sprite_accessory/breasts
icon = 'modular_citadel/icons/obj/genitals/breasts_onmob.dmi'
diff --git a/modular_citadel/code/modules/arousal/organs/penis.dm b/modular_citadel/code/modules/arousal/organs/penis.dm
index b6cb8fa4b2..43a512acf6 100644
--- a/modular_citadel/code/modules/arousal/organs/penis.dm
+++ b/modular_citadel/code/modules/arousal/organs/penis.dm
@@ -12,28 +12,61 @@
size = 2 //arbitrary value derived from length and girth for sprites.
var/length = 6 //inches
var/cached_length //used to detect a change in length
- var/girth = 0
+ var/girth = 4.38
var/girth_ratio = COCK_GIRTH_RATIO_DEF //0.73; check citadel_defines.dm
var/knot_girth_ratio = KNOT_GIRTH_RATIO_DEF
var/list/dickflags = list()
var/list/knotted_types = list("knotted", "barbed, knotted")
+ var/prev_length = 6 //really should be renamed to prev_length
+
+/obj/item/organ/genital/penis/Initialize()
+ . = ..()
+ /* I hate genitals.*/
/obj/item/organ/genital/penis/update_size()
- if(length == cached_length)
+ var/mob/living/carbon/human/o = owner
+ if(!ishuman(o) || !o)
return
- switch(length)
- if(-INFINITY to 5)
+ if(cached_length < 0)//I don't actually know what round() does to negative numbers, so to be safe!!
+ var/obj/item/organ/genital/penis/P = o.getorganslot("penis")
+ to_chat(o, "You feel your tallywacker shrinking away from your body as your groin flattens out!")
+ P.Remove(o)
+ switch(round(cached_length))
+ if(0 to 4) //If modest size
+ length = cached_length
size = 1
- if(5 to 9)
+ if(owner.has_status_effect(/datum/status_effect/chem/penis_enlarger))
+ o.remove_status_effect(/datum/status_effect/chem/penis_enlarger)
+ if(5 to 10) //If modest size
+ length = cached_length
size = 2
- if(15 to INFINITY)
- size = 3//no new sprites for anything larger yet
-/* if(9 to 15)
+ if(owner.has_status_effect(/datum/status_effect/chem/penis_enlarger))
+ o.remove_status_effect(/datum/status_effect/chem/penis_enlarger)
+ if(11 to 20) //If massive
+ length = cached_length
size = 3
- if(15 to INFINITY)
- size = 3*/
- girth = (length * girth_ratio)
- cached_length = length
+ if(owner.has_status_effect(/datum/status_effect/chem/penis_enlarger))
+ o.remove_status_effect(/datum/status_effect/chem/penis_enlarger)
+ if(21 to 35) //If massive and due for large effects
+ length = cached_length
+ size = 3
+ if(!owner.has_status_effect(/datum/status_effect/chem/penis_enlarger))
+ o.apply_status_effect(/datum/status_effect/chem/penis_enlarger)
+ if(36 to INFINITY) //If comical
+ length = cached_length
+ size = 4 //no new sprites for anything larger yet
+ if(!owner.has_status_effect(/datum/status_effect/chem/penis_enlarger))
+ o.apply_status_effect(/datum/status_effect/chem/penis_enlarger)
+
+ if (round(length) > round(prev_length))
+ to_chat(o, "Your [pick(GLOB.gentlemans_organ_names)] [pick("swells up to", "flourishes into", "expands into", "bursts forth into", "grows eagerly into", "amplifys into")] a [uppertext(round(length))] inch penis.")
+ else if ((round(length) < round(prev_length)) && (length > 0.5))
+ to_chat(o, "Your [pick(GLOB.gentlemans_organ_names)] [pick("shrinks down to", "decreases into", "diminishes into", "deflates into", "shrivels regretfully into", "contracts into")] a [uppertext(round(length))] inch penis.")
+ prev_length = length
+ icon_state = sanitize_text("penis_[shape]_[size]")
+ girth = (length * girth_ratio)//Is it just me or is this ludicous, why not make it exponentially decay?
+
+ //I have no idea on how to update sprites and I hate it
/obj/item/organ/genital/penis/update_appearance()
var/string
diff --git a/modular_citadel/code/modules/client/preferences.dm b/modular_citadel/code/modules/client/preferences.dm
index dfe419964e..1caa4cfed7 100644
--- a/modular_citadel/code/modules/client/preferences.dm
+++ b/modular_citadel/code/modules/client/preferences.dm
@@ -15,6 +15,7 @@
var/arousable = TRUE
var/widescreenpref = TRUE
var/autostand = TRUE
+ var/lewdchem = TRUE
//vore prefs
var/toggleeatingnoise = TRUE
@@ -53,5 +54,6 @@ datum/preferences/copy_to(mob/living/carbon/human/character, icon_updates = 1)
character.give_genitals(TRUE)
character.flavor_text = features["flavor_text"] //Let's update their flavor_text at least initially
character.canbearoused = arousable
+ character.client?.prefs.lewdchem = lewdchem
if(icon_updates)
character.update_genitals()
diff --git a/modular_citadel/code/modules/reagents/chemistry/reagents/MKUltra.dm b/modular_citadel/code/modules/reagents/chemistry/reagents/MKUltra.dm
new file mode 100644
index 0000000000..1508a5d519
--- /dev/null
+++ b/modular_citadel/code/modules/reagents/chemistry/reagents/MKUltra.dm
@@ -0,0 +1,373 @@
+/*
+////////////////////////////////////////
+// MKULTA //
+////////////////////////////////////////
+The magnum opus of FermiChem -
+Long and complicated, I highly recomend you look at the two other files heavily involved in this
+modular_citadel/code/datums/status_effects/chems.dm - handles the subject's reactions
+code/modules/surgery/organs/vocal_cords.dm - handles the enchanter speaking
+
+HOW IT WORKS
+Fermis_Reagent.dm
+There's 3 main ways this chemical works; I'll start off with discussing how it's set up.
+Upon reacting with blood as a catalyst, the blood is used to define who the enthraller is - thus only the creator is/can choose who the master will be. As a side note, you can't adminbus this chem, even admins have to earn it.
+This uses the fermichem only proc; FermiCreate, which is basically the same as On_new, except it doesn't require "data" which is something to do with blood and breaks everything so I said bugger it and made my own proc. It basically sets up vars.
+When it's first made, the creator has to drink some of it, in order to give them the vocal chords needed.
+When it's given to someone, it gives them the status effect and kicks off that side of things. For every metabolism tick, it increases the enthrall tally.
+Finally, if you manage to pump 100u into some poor soul, you overload them, and mindbreak them. Making them your willing, but broken slave. Which can only be reversed by; fixing their brain with mannitol and neurine (100 / 50u respectively) (or less with both),
+
+vocal_cords.dm
+This handles when the enchanter speaks - basically uses code from voice of god, but only for people with the staus effect. Most of the words are self explainitory, and has a smaller range of commands. If you're not sure what one does, it likely affects the enthrall tally, or the resist tally.
+list of commands:
+
+-mixables-
+enthral_words
+reward_words
+punish_words
+0
+saymyname_words
+wakeup_words
+1
+silence_words
+antiresist_words
+resist_words
+forget_words
+attract_words
+orgasm_words
+2
+awoo_words
+nya_words
+sleep_words
+strip_words
+walk_words
+run_words
+knockdown_words
+3
+statecustom_words
+custom_words
+objective_words
+heal_words
+stun_words
+hallucinate_words
+hot_words
+cold_words
+getup_words
+pacify_words
+charge_words
+
+Mixables can be used intersperced with other commands, 0 is commands that work on sleeper against (i.e. players enthralled to state 3, then ordered to wake up and forget, they can be triggered back instantly)
+1 is for players who immediately are injected with the chem - no stuns, only a silence and something that draws them towrds them. This is the best time to try to fight it and you're likely to win by spamming resist, unless the enchantress has plans.
+2 is the seconds stage, which allows removal of clothes, slowdown and light stunning. You can also make them nya and awoo, because cute.
+3 is the finaly state, which allows application of a few status effects (see chem.dm) and allows custom triggers to be installed (kind of like nanites), again, see chem.dm
+In a nutshell, this is the way you enthrall people, by typing messages into chat and managing cooldowns on the stronger words. You have to type words and your message strength is increases with the number of characters - if you type short messages the cooldown will be too much and the other player will overcome the chem.
+I suppose people could spam gdjshogndjoadphgiuaodp but, the truth of this chem is that it mostly allows a casus beli for subs to give in, and everyones a sub on cit (mostly), so if you aujigbnadjgipagdsjk then they might resist harder cause you're a baddie and baddies don't deserve pets.
+Also, the use of this chem as a murder aid is antithetic to it's design, the subject gains bonus resistance if they're hurt or hungry (I'd like to expland this more, I like the idea that you have to look after all of them otherwise they aren't as effective, kind of like tamagachis!). If this becomes a problem, I'll deal with it, I'm not happy with people abusing this chem for an easy murder. (I might make it so you an't strike your pet when health is too low.)
+Additionaly, in lieu of previous statement - the pet is ordered to not kill themselves, even if ordered to.
+
+chem.dm
+oof
+There's a few basic things that have to be understood with this status effect
+1. There is a min loop which calculates the enthrall state of the subject, when the entrall tally is over a certain amount, it will push you up 1 phase.
+0 - Sleeper
+1 - initial
+2 - enthralled
+3 - Fully entranced
+4 - mindbroken
+4 can only be reached via OD, whereas you can increment up from 1 > 2 > 3. 0 is only obtainable on a state 3 pet, and it toggles between the two.
+
+1.5 Chem warfare
+Since this is a chem, it's expected that you will use all of the chemicals at your disposal. Using aphro and aphro+ will weaken the resistance of the subject, while ananphro, anaphro+, mannitol and neurine will strengthen it.
+Additionally, the more aroused you are, the weaker your resistance will be, as a result players immune to aphro and anaphro give a flat bonus to the enthraller.
+using furranium and hatmium on the enchanter weakens their power considerably, because they sound rediculous. "Youwe fweewing wery sweepy uwu" This completely justifies their existance.
+The impure toxin for this chem increases resistance too, so if they're a bad chemist it'll be unlikely they have a good ratio (and as a secret bonus, really good chemists cann purposely make the impure chem, to use either to combat the use of it against them, or as smoke grenades to deal with a large party)
+
+2. There is a resistance proc which occurs whenever the player presses resist. You have to press it a lot, this is intentional. If you're trying to fight the enchanter, then you can't click both. You usually will win if you just mash resist and the enchanter does nothing, so you've got to react.
+Each step futher it becomes harder to resist, in state 2 it's longer, but resisting is still worthwhile. If you're not in state 3, and you've not got MKultra inside of you, you generate resistance very fast. So in some cases the better option will be to stall out any attempts to entrance you.
+At the moment, resistance doesn't affect the commands - mostly because it's a way to tell if a state 3 is trying to resist. But this might change if it gets too hard to fight them off.
+Durign state 3, it's impossible to resist if the enthraller is in your presence (8 tiles), you generate no resistance if so. If they're out of your range, then you start to go into the addiction processed
+As your resistance is tied to your arousal, sometimes your best option is to wah
+
+3. The addition process starts when the enthraller is out of range, it roughtly follows the five stages of grief; denial, anger, bargaining, depression and acceptance.
+What it mostly does makes you sad, hurts your brain, and sometimes you lash out in anger.
+Denial - minor brain damaged
+bargaining - 50:50 chance of brain damage and brain healing
+anger - randomly lashing out and hitting people
+depression - massive mood loss, stuttering, jittering, hallucinations and brain damage
+depression, again - random stunning and crying, brain damage, and resistance
+acceptance - minor brain damage and resistance.
+You can also resist while out of range, but you can only break free of a stange 3 enthrallment by hitting the acceptance phase with a high enough resistance.
+Finally, being near your enthraller reverts the damages caused.
+It is expected that if you intend to break free you'll need to use psicodine and mannitol or you'll end up in a bad, but not dead, state. This gives more work for medical!! Finally the true rational of this complicated chem comes out.
+
+4. Status effects in status effects.
+There's a few commands that give status effects, such as antiresist, which will cause resistance presses to increase the enthrallment instead, theses are called from the vocal chords.
+They're mostly self explainitory; antiresist, charge, pacify and heal. Heals quite weak for obvious reasons. I'd like to add more, maybe some weak adneals with brute/exhaustion costs after the status is over. A truth serum might be neat too.
+State 4 pets don't get status effects.
+
+5. Custom triggers
+Because it wasnt complicated enough already.
+Custom triggers are set by stating a trigger word, which will call a sub proc, which is also defined when the trigger is Called
+The effects avalible at the moment are:
+Speak - forces pet to say a preallocated phrase in response to the trigger
+Echo - sends a message to that player only (i.e. makes them think something)
+Shock - gives them a seizure/zaps them
+You can look this one up yourself - it's what you expect, it's cit
+kneel - gives a short knockdown
+strip - strips jumpsuit only
+objective - gives the pet a new objective. This requires a high ammount of mental capasity - which is determined by how much you resist. If you resist enough during phase 1 and 2, then they can't give you an objective.
+Feel free to add more.
+triggers work when said by ANYONE, not just the enchanter.
+This is only state 3 pets, state 4 pets cannot get custom triggers, you broke them you bully.
+
+7. If you're an antage you get a bonus to resistance AND to enthralling. Thus it can be worth using this on both sides. It shouldn't be hard to resist as an antag. There are futher bonuses to command, Chaplains and chemist.
+If you give your pet a collar then their resistance reduced too.
+(I think thats everything?)
+
+Failstates:
+Blowing up the reaction produces a gas that causes everyone to fall in love with one another.
+
+Creating a chem with a low purity will make you permanently fall in love with someone, and tasked with keeping them safe. If someone else drinks it, you fall for them.
+*/
+
+/datum/reagent/fermi/enthrall
+ name = "MKUltra"
+ id = "enthrall"
+ description = "A forbidden deep red mixture that overwhelms a foreign body with waves of pleasure, intoxicating them into servitude. When taken by the creator, it will enhance the draw of their voice to those affected by it."
+ color = "#660015" // rgb: , 0, 255
+ taste_description = "synthetic chocolate, a base tone of alcohol, and high notes of roses"
+ overdose_threshold = 100 //If this is too easy to get 100u of this, then double it please.
+ DoNotSplit = TRUE
+ metabolization_rate = 0.1//It has to be slow, so there's time for the effect.
+ data = list("creatorID" = null, "creatorGender" = null, "creatorName" = null)
+ var/creatorID //ckey
+ var/creatorGender
+ var/creatorName
+ var/mob/living/creator
+ pH = 10
+ OnMobMergeCheck = TRUE //Procs on_mob_add when merging into a human
+ can_synth = FALSE
+
+
+/datum/reagent/fermi/enthrall/test
+ name = "MKUltraTest"
+ id = "enthrallTest"
+ description = "A forbidden deep red mixture that overwhelms a foreign body with waves of joy, intoxicating them into servitude. When taken by the creator, it will enhance the draw of their voice to those affected by it."
+ data = list("creatorID" = "honkatonkbramblesnatch", "creatorGender" = "Mistress", "creatorName" = "Fermis Yakumo")
+ creatorID = "honkatonkbramblesnatch"//ckey
+ creatorGender = "Mistress"
+ creatorName = "Fermis Yakumo"
+ purity = 1
+ DoNotSplit = TRUE
+
+/datum/reagent/fermi/enthrall/test/on_new()
+ id = "enthrall"
+ ..()
+ creator = get_mob_by_key(creatorID)
+
+/datum/reagent/fermi/enthrall/on_new(list/data)
+ creatorID = data.["creatorID"]
+ creatorGender = data.["creatorGender"]
+ creatorName = data.["creatorName"]
+ creator = get_mob_by_key(creatorID)
+
+/datum/reagent/fermi/enthrall/on_mob_add(mob/living/carbon/M)
+ . = ..()
+ if(!ishuman(M))//Just to make sure screwy stuff doesn't happen.
+ return
+ if(!creatorID)
+ //This happens when the reaction explodes.
+ return
+ if(purity < 0.5)//Impure chems don't function as you expect
+ return
+ var/datum/status_effect/chem/enthrall/E = M.has_status_effect(/datum/status_effect/chem/enthrall)
+ if(E)
+ if(E.enthrallID == M.ckey && creatorID != M.ckey)//If you're enthralled to yourself (from OD) and someone else tries to enthrall you, you become thralled to them instantly.
+ E.enthrallID = creatorID
+ E.enthrallGender = creatorGender
+ E.master = get_mob_by_key(creatorID)
+ to_chat(M, to_chat(M, "Your aldled, plastic, mind bends under the chemical influence of a new [(M.client?.prefs.lewdchem?"master":"leader")]. Your highest priority is now to stay by [creatorName]'s side, following and aiding them at all costs.")) //THIS SHOULD ONLY EVER APPEAR IF YOU MINDBREAK YOURSELF AND THEN GET INJECTED FROM SOMEONE ELSE.
+ log_game("FERMICHEM: Narcissist [M] ckey: [M.key] been rebound to [creatorName], ID: [creatorID]")
+ return
+ if((M.ckey == creatorID) && (creatorName == M.real_name)) //same name AND same player - same instance of the player. (should work for clones?)
+ log_game("FERMICHEM: [M] ckey: [M.key] has been given velvetspeech")
+ var/obj/item/organ/vocal_cords/Vc = M.getorganslot(ORGAN_SLOT_VOICE)
+ var/obj/item/organ/vocal_cords/nVc = new /obj/item/organ/vocal_cords/velvet
+ if(Vc)
+ Vc.Remove(M)
+ nVc.Insert(M)
+ qdel(Vc)
+ to_chat(M, "You feel your vocal chords tingle as your voice comes out in a more sultry tone.")
+ else
+ log_game("FERMICHEM: MKUltra: [creatorName], [creatorID], is enthralling [M.name], [M.ckey]")
+ M.apply_status_effect(/datum/status_effect/chem/enthrall)
+ log_game("FERMICHEM: [M] ckey: [M.key] has taken MKUltra")
+
+/datum/reagent/fermi/enthrall/on_mob_life(mob/living/carbon/M)
+ . = ..()
+ if(purity < 0.5)//DO NOT SPLIT INTO DIFFERENT CHEM: This relies on DoNotSplit - has to be done this way.
+ if(volume < 0.5)//You don't get to escape that easily
+ FallInLove(pick(GLOB.player_list), M)
+ M.reagents.remove_reagent(id, volume)
+
+ if (M.ckey == creatorID && creatorName == M.real_name)//If the creator drinks it, they fall in love randomly. If someone else drinks it, the creator falls in love with them.
+ if(M.has_status_effect(STATUS_EFFECT_INLOVE))//Can't be enthralled when enthralled, so to speak.
+ return
+ var/list/seen = viewers(7, get_turf(M))
+ for(var/victim in seen)
+ if(ishuman(victim))
+ var/mob/living/carbon/V = victim
+ if((V == M) || (!V.client) || (V.stat == DEAD))
+ seen = seen - victim
+ else
+ seen = seen - victim
+
+ if(LAZYLEN(seen))
+ return
+ M.reagents.remove_reagent(id, volume)
+ FallInLove(M, pick(seen))
+ return
+
+ else // If someone else drinks it, the creator falls in love with them!
+ var/mob/living/carbon/C = get_mob_by_key(creatorID)
+ if(M.has_status_effect(STATUS_EFFECT_INLOVE))
+ return
+ if((C in viewers(7, get_turf(M))) && (C.client))
+ M.reagents.remove_reagent(id, volume)
+ FallInLove(C, M)
+ return
+
+ if (M.ckey == creatorID && creatorName == M.real_name)//If you yourself drink it, it supresses the vocal effects, for stealth. NEVERMIND ADD THIS LATER I CAN'T GET IT TO WORK
+ return
+ if(!M.client)
+ metabolization_rate = 0 //Stops powergamers from quitting to avoid affects. but prevents affects on players that don't exist for performance.
+ return
+ if(metabolization_rate == 0)
+ metabolization_rate = 0.1
+ var/datum/status_effect/chem/enthrall/E = M.has_status_effect(/datum/status_effect/chem/enthrall)//If purity is over 5, works as intended
+ if(!E)
+ return
+ else
+ E.enthrallTally += 1
+ ..()
+
+/datum/reagent/fermi/enthrall/overdose_start(mob/living/carbon/M)//I made it so the creator is set to gain the status for someone random.
+ . = ..()
+ metabolization_rate = 1//Mostly to manage brain damage and reduce server stress
+ if (M.ckey == creatorID && creatorName == M.real_name)//If the creator drinks 100u, then you get the status for someone random (They don't have the vocal chords though, so it's limited.)
+ if (!M.has_status_effect(/datum/status_effect/chem/enthrall))
+ to_chat(M, "You are unable to resist your own charms anymore, and become a full blown narcissist.")
+ /*Old way of handling, left in as an option B
+ var/list/seen = viewers(7, get_turf(M))//Sound and sight checkers
+ for(var/mob/living/carbon/victim in seen)
+ if(victim == M)//as much as I want you to fall for beepsky, he doesn't have a ckey
+ seen = seen - victim
+ if(!victim.ckey)
+ seen = seen - victim
+ var/mob/living/carbon/chosen = pick(seen)
+ creatorID = chosen.ckey
+ if (chosen.gender == "female")
+ creatorGender = "Mistress"
+ else
+ creatorGender = "Master"
+ creatorName = chosen.real_name
+ creator = get_mob_by_key(creatorID)
+ */
+ ADD_TRAIT(M, TRAIT_PACIFISM, "MKUltra")
+ var/datum/status_effect/chem/enthrall/E
+ if (!M.has_status_effect(/datum/status_effect/chem/enthrall))
+ M.apply_status_effect(/datum/status_effect/chem/enthrall)
+ E = M.has_status_effect(/datum/status_effect/chem/enthrall)
+ E.enthrallID = creatorID
+ E.enthrallGender = creatorGender
+ E.master = creator
+ else
+ E = M.has_status_effect(/datum/status_effect/chem/enthrall)
+ if(M.client?.prefs.lewdchem)
+ to_chat(M, "Your mind shatters under the volume of the mild altering chem inside of you, breaking all will and thought completely. Instead the only force driving you now is the instinctual desire to obey and follow [creatorName]. Your highest priority is now to stay by their side and protect them at all costs.")
+ else
+ to_chat(M, "The might volume of chemicals in your system overwhelms your mind, and you suddenly agree with what [creatorName] has been saying. Your highest priority is now to stay by their side and protect them at all costs.")
+ log_game("FERMICHEM: [M] ckey: [M.key] has been mindbroken for [creatorName] ckey: [creatorID]")
+ M.slurring = 100
+ M.confused = 100
+ E.phase = 4
+ E.mental_capacity = 0
+ E.customTriggers = list()
+ SSblackbox.record_feedback("tally", "fermi_chem", 1, "Thralls mindbroken")
+
+/datum/reagent/fermi/enthrall/overdose_process(mob/living/carbon/M)
+ M.adjustBrainLoss(0.2)//should be ~30 in total
+ ..()
+
+//Creates a gas cloud when the reaction blows up, causing everyone in it to fall in love with someone/something while it's in their system.
+/datum/reagent/fermi/enthrallExplo//Created in a gas cloud when it explodes
+ name = "MKUltra"
+ id = "enthrallExplo"
+ description = "A forbidden deep red mixture that overwhelms a foreign body with waves of desire, inducing a chemial love for another. Also, how the HECC did you get this?"
+ color = "#2C051A" // rgb: , 0, 255
+ metabolization_rate = 0.1
+ taste_description = "synthetic chocolate, a base tone of alcohol, and high notes of roses."
+ DoNotSplit = TRUE
+ can_synth = FALSE
+ var/mob/living/carbon/love
+
+/datum/reagent/fermi/enthrallExplo/on_mob_life(mob/living/carbon/M)//Love gas, only affects while it's in your system,Gives a positive moodlet if close, gives brain damagea and a negative moodlet if not close enough.
+ if(HAS_TRAIT(M, TRAIT_MINDSHIELD))
+ return ..()
+ if(!M.has_status_effect(STATUS_EFFECT_INLOVE))
+ var/list/seen = viewers(7, get_turf(M))//Sound and sight checkers
+ for(var/victim in seen)
+ if((istype(victim, /mob/living/simple_animal/pet/)) || (victim == M) || (M.stat == DEAD) || (!isliving(victim)))
+ seen = seen - victim
+ if(seen.len == 0)
+ return
+ love = pick(seen)
+ if(!love)
+ return
+ M.apply_status_effect(STATUS_EFFECT_INLOVE, love)
+ to_chat(M, "[(M.client?.prefs.lewdchem?"":"")][(M.client?.prefs.lewdchem?"You develop a sudden crush on [love], your heart beginning to race as you look upon them with new eyes.":"You suddenly feel like making friends with [love].")] You feel strangely drawn towards them.")
+ log_game("FERMICHEM: [M] ckey: [M.key] has temporarily bonded with [love] ckey: [love.key]")
+ SSblackbox.record_feedback("tally", "fermi_chem", 1, "Times people have bonded")
+ else
+ if(get_dist(M, love) < 8)
+ if(HAS_TRAIT(M, TRAIT_NYMPHO)) //Add this back when merged/updated.
+ M.adjustArousalLoss(5)
+ var/message = "[(M.client?.prefs.lewdchem?"I'm next to my crush..! Eee!":"I'm making friends with [love]!")]"
+ SEND_SIGNAL(M, COMSIG_ADD_MOOD_EVENT, "InLove", /datum/mood_event/InLove, message)
+ SEND_SIGNAL(M, COMSIG_CLEAR_MOOD_EVENT, "MissingLove")
+ else
+ var/message = "[(M.client?.prefs.lewdchem?"I can't keep my crush off my mind, I need to see them again!":"I really want to make friends with [love]!")]"
+ SEND_SIGNAL(M, COMSIG_ADD_MOOD_EVENT, "MissingLove", /datum/mood_event/MissingLove, message)
+ SEND_SIGNAL(M, COMSIG_CLEAR_MOOD_EVENT, "InLove")
+ if(prob(5))
+ M.Stun(10)
+ M.emote("whimper")//does this exist?
+ to_chat(M, "[(M.client?.prefs.lewdchem?"":"")] You're overcome with a desire to see [love].")
+ M.adjustBrainLoss(0.5)//I found out why everyone was so damaged!
+ ..()
+
+/datum/reagent/fermi/enthrallExplo/on_mob_delete(mob/living/carbon/M)
+ if(HAS_TRAIT(M, TRAIT_MINDSHIELD))
+ return ..()
+ M.remove_status_effect(STATUS_EFFECT_INLOVE)
+ SEND_SIGNAL(M, COMSIG_CLEAR_MOOD_EVENT, "InLove")
+ SEND_SIGNAL(M, COMSIG_CLEAR_MOOD_EVENT, "MissingLove")
+ to_chat(M, "[(M.client?.prefs.lewdchem?"":"")]Your feelings for [love] suddenly vanish!")
+ log_game("FERMICHEM: [M] ckey: [M.key] is no longer in temp bond")
+ ..()
+
+/datum/reagent/fermi/proc/FallInLove(mob/living/carbon/Lover, mob/living/carbon/Love)
+ if(Lover.has_status_effect(STATUS_EFFECT_INLOVE))
+ to_chat(Lover, "You are already fully devoted to someone else!")
+ return
+ to_chat(Lover, "[(Lover.client?.prefs.lewdchem?"":"")]You develop a deep and sudden bond with [Love][(Lover.client?.prefs.lewdchem?", your heart beginning to race as your mind filles with thoughts about them.":".")] You are determined to keep them safe and happy, and feel drawn towards them.")
+ if(Lover.mind)
+ Lover.mind.store_memory("You are in love with [Love].")
+ Lover.faction |= "[REF(Love)]"
+ Lover.apply_status_effect(STATUS_EFFECT_INLOVE, Love)
+ forge_valentines_objective(Lover, Love, TRUE)
+ SSblackbox.record_feedback("tally", "fermi_chem", 1, "Times people have become infatuated.")
+ log_game("FERMICHEM: [Lover] ckey: [Lover.key] has been chemically made to fall for [Love] ckey: [Love.key]")
+ return
+
+//For addiction see chem.dm
+//For vocal commands see vocal_cords.dm
diff --git a/modular_citadel/code/modules/reagents/chemistry/reagents/SDGF.dm b/modular_citadel/code/modules/reagents/chemistry/reagents/SDGF.dm
new file mode 100644
index 0000000000..cb222fd967
--- /dev/null
+++ b/modular_citadel/code/modules/reagents/chemistry/reagents/SDGF.dm
@@ -0,0 +1,364 @@
+/*SDGF
+////////////////////////////////////////////////////
+// synthetic-derived growth factor //
+//////////////////////////////////////////////////
+other files that are relivant:
+modular_citadel/code/datums/status_effects/chems.dm - SDGF
+WHAT IT DOES
+
+Several outcomes are possible (in priority order):
+
+Before the chem is even created, there is a risk of the reaction "exploding", which produces an angry teratoma that attacks the player.
+0. Before the chem is activated, the purity is checked, if the purity of the reagent is less than 0.5, then sythetic-derived zombie factor is metabolised instead
+ 0.1 If SDZF is injected, the chem appears to act the same as normal, with nutrition gain, until the end, where it becomes toxic instead, giving a short window of warning to the player
+ 0.1.2 If the player can take pent in time, the player will spawn a hostile teratoma on them (less damaging), if they don't, then a zombie is spawned instead, with a small defence increase propotional to the volume
+ 0.2 If the purity is above 0.5, then the remaining impure volume created SDGFtox instead, which reduces blood volume and causes clone damage
+1.Normal function creates a (another)player controlled clone of the player, which is spawned nude, with damage to the clone
+ 1.1 The remaining volume is transferred to the clone, which heals it over time, thus the player has to make a substantial ammount of the chem in order to produce a healthy clone
+ 1.2 If the player is infected with a zombie tumor, the tumor is transferred to the ghost controlled clone ONLY.
+2. If no player can be found, a brainless clone is created over a long period of time, this body has no controller.
+ 2.1 If the player dies with a clone, then they play as the clone instead. However no memories are retained after splitting.
+3. If there is already a clone, then SDGF heals clone, fire and brute damage slowly. This shouldn't normalise this chem as the de facto clone healing chem, as it will always try to make a ghost clone, and then a brainless clone first.
+4. If there is insuffient volume to complete the cloning process, there are two outcomes
+ 4.1 At lower volumes, the players nutrition and blood is refunded, with light healing
+ 4.2 At higher volumes a stronger heal is applied to the user
+
+IMPORTANT FACTORS TO CONSIDER WHILE BALANCING
+1. The most important factor is the required volume, this is easily edited with the metabolism rate, this chem is HARD TO MAKE, You need to make a lot of it and it's a substantial effort on the players part. There is also a substantial risk; you could spawn a hotile teratoma during the reation, you could damage yourself with clone damage, you could accidentally spawn a zombie... Basically, you've a good chance of killing yourself.
+ 1.1 Additionally, if you're trying to make SDZF purposely, you've no idea if you have or not, and that reaction is even harder to do. Plus, the player has a huge time window to get to medical to deal with it. If you take pent while it's in you, it'll be removed before it can spawn, and only spawns a teratoma if it's late stage.
+2. The rate in which the clone is made, This thing takes time to produce fruits, it slows you down and makes you useless in combat/working. Basically you can't do anything during it. It will only get you killed if you use it in combat, If you do use it and you spawn a player clone, they're gimped for a long time, as they have to heal off the clone damage.
+3. The healing - it's pretty low and a cyropod is more Useful
+4. If you're an antag, you've a 50% chance of making a clone that will help you with your efforts, and you've no idea if they will or not. While clones can't directly harm you and care for you, they can hinder your efforts.
+5. If people are being arses when they're a clone, slap them for it, they are told to NOT bugger around with someone else character, if it gets bad I'll add a blacklist, or do a check to see if you've played X amount of hours.
+ 5.1 Another solution I'm okay with is to rename the clone to [M]'s clone, so it's obvious, this obviously ruins anyone trying to clone themselves to get an alibi however. I'd prefer this to not be the case.
+ 5.2 Additionally, this chem is a soft buff to changelings, which apparently need a buff!
+ 5.3 Other similar things exist already though in the codebase; impostors, split personalites, abductors, ect.
+6. Giving this to someone without concent is against space law and gets you sent to gulag.
+*/
+
+//Clone serum #chemClone
+/datum/reagent/fermi/SDGF //vars, mostly only care about keeping track if there's a player in the clone or not.
+ name = "synthetic-derived growth factor"
+ id = "SDGF"
+ description = "A rapidly diving mass of Embryonic stem cells. These cells are missing a nucleus and quickly replicate a host’s DNA before growing to form an almost perfect clone of the host. In some cases neural replication takes longer, though the underlying reason underneath has yet to be determined."
+ color = "#a502e0" // rgb: 96, 0, 255
+ var/playerClone = FALSE
+ var/unitCheck = FALSE
+ metabolization_rate = 0.5 * REAGENTS_METABOLISM
+ taste_description = "a weird chemical fleshy flavour"
+ //var/datum/status_effect/chem/SDGF/candidates/candies
+ var/list/candies = list()
+ //var/polling = FALSE
+ var/list/result = list()
+ var/list/group = null
+ var/pollStarted = FALSE
+ var/location_created
+ var/startHunger
+ ImpureChem = "SDGFtox"
+ InverseChemVal = 0.5
+ InverseChem = "SDZF"
+ can_synth = TRUE
+
+
+//Main SDGF chemical
+/datum/reagent/fermi/SDGF/on_mob_life(mob/living/carbon/M) //Clones user, then puts a ghost in them! If that fails, makes a braindead clone.
+ //Setup clone
+ switch(current_cycle)
+ if(1)
+ startHunger = M.nutrition
+ if(pollStarted == FALSE)
+ pollStarted = TRUE
+ candies = pollGhostCandidates("Do you want to play as a clone of [M], and do you agree to respect their character and act in a similar manner to them? Do not engage in ERP as them unless you have LOOC permission from them, and ensure it is permission from the original, not a clone.")
+ log_game("FERMICHEM: [M] ckey: [M.key] has taken SDGF, and ghosts have been polled.")
+ if(20 to INFINITY)
+ if(LAZYLEN(candies) && playerClone == FALSE) //If there's candidates, clone the person and put them in there!
+ log_game("FERMICHEM: [M] ckey: [M.key] is creating a clone, controlled by [candies]")
+ to_chat(M, "The cells reach a critical micelle concentration, nucleating rapidly within your body!")
+ var/typepath = M.type
+ var/mob/living/carbon/human/fermi_Gclone = new typepath(M.loc)
+ var/mob/living/carbon/human/SM = fermi_Gclone
+ if(istype(SM) && istype(M))
+ SM.real_name = M.real_name
+ M.dna.transfer_identity(SM)
+ SM.updateappearance(mutcolor_update=1)
+ var/mob/dead/observer/C = pick(candies)
+ message_admins("Ghost candidate found! [C] key [C.key] is becoming a clone of [M] key: [M.key] (They agreed to respect the character they're becoming, and agreed to not ERP without express permission from the original.)")
+ SM.key = C.key
+ SM.mind.enslave_mind_to_creator(M)
+
+ //If they're a zombie, they can try to negate it with this.
+ //I seriously wonder if anyone will ever use this function.
+ if(M.getorganslot(ORGAN_SLOT_ZOMBIE))//sure, it "treats" it, but "you've" still got it. Doesn't always work as well; needs a ghost.
+ var/obj/item/organ/zombie_infection/ZI = M.getorganslot(ORGAN_SLOT_ZOMBIE)
+ ZI.Remove(M)
+ ZI.Insert(SM)
+ log_game("FERMICHEM: [M] ckey: [M.key]'s zombie_infection has been transferred to their clone")
+
+ to_chat(SM, "You feel a strange sensation building in your mind as you realise there's two of you, before you get a chance to think about it, you suddenly split from your old body, and find yourself face to face with your original, a perfect clone of your origin.")
+
+ if(prob(50))
+ to_chat(SM, "While you find your newfound existence strange, you share the same memories as [M.real_name]. However, You find yourself indifferent to the goals you previously had, and take more interest in your newfound independence, but still have an indescribable care for the safety of your original.")
+ log_game("FERMICHEM: [SM] ckey: [SM.key]'s is not bound by [M] ckey [M.key]'s will, and is free to determine their own goals, while respecting and acting as their origin.")
+ else
+ to_chat(SM, "While you find your newfound existence strange, you share the same memories as [M.real_name]. Your mind has not deviated from the tasks you set out to do, and now that there's two of you the tasks should be much easier.")
+ log_game("FERMICHEM: [SM] ckey: [SM.key]'s is bound by [M] ckey [M.key]'s objectives, and is encouraged to help them complete them.")
+
+ to_chat(M, "You feel a strange sensation building in your mind as you realise there's two of you, before you get a chance to think about it, you suddenly split from your old body, and find yourself face to face with yourself.")
+ M.visible_message("[M] suddenly shudders, and splits into two identical twins!")
+ SM.copy_known_languages_from(M, FALSE)
+ playerClone = TRUE
+ M.next_move_modifier = 1
+ M.nutrition -= 500
+
+ //Damage the clone
+ SM.blood_volume = (BLOOD_VOLUME_NORMAL*SM.blood_ratio)/2
+ SM.adjustCloneLoss(60, 0)
+ SM.setBrainLoss(40)
+ SM.nutrition = startHunger/2
+
+ //Transfer remaining reagent to clone. I think around 30u will make a healthy clone, otherwise they'll have clone damage, blood loss, brain damage and hunger.
+ SM.reagents.add_reagent("SDGFheal", volume)
+ M.reagents.remove_reagent(id, volume)
+ log_game("FERMICHEM: [volume]u of SDGFheal has been transferred to the clone")
+ SSblackbox.record_feedback("tally", "fermi_chem", 1, "Sentient clones made")
+ return
+
+ else if(playerClone == FALSE) //No candidates leads to two outcomes; if there's already a braincless clone, it heals the user, as well as being a rare souce of clone healing (thematic!).
+ unitCheck = TRUE
+ if(M.has_status_effect(/datum/status_effect/chem/SGDF)) // Heal the user if they went to all this trouble to make it and can't get a clone, the poor fellow.
+ switch(current_cycle)
+ if(21)
+ to_chat(M, "The cells fail to catalyse around a nucleation event, instead merging with your cells.") //This stuff is hard enough to make to rob a user of some benefit. Shouldn't replace Rezadone as it requires the user to not only risk making a player controlled clone, but also requires them to have split in two (which also requires 30u of SGDF).
+ REMOVE_TRAIT(M, TRAIT_DISFIGURED, TRAIT_GENERIC)
+ log_game("FERMICHEM: [M] ckey: [M.key] is being healed by SDGF")
+ if(22 to INFINITY)
+ M.adjustCloneLoss(-1, 0)
+ M.adjustBruteLoss(-1, 0)
+ M.adjustFireLoss(-1, 0)
+ M.heal_bodypart_damage(1,1)
+ M.reagents.remove_reagent(id, 1)//faster rate of loss.
+ else //If there's no ghosts, but they've made a large amount, then proceed to make flavourful clone, where you become fat and useless until you split.
+ switch(current_cycle)
+ if(21)
+ to_chat(M, "You feel the synethic cells rest uncomfortably within your body as they start to pulse and grow rapidly.")
+ if(22 to 29)
+ M.nutrition = M.nutrition + (M.nutrition/10)
+ if(30)
+ to_chat(M, "You feel the synethic cells grow and expand within yourself, bloating your body outwards.")
+ if(31 to 49)
+ M.nutrition = M.nutrition + (M.nutrition/5)
+ if(50)
+ to_chat(M, "The synthetic cells begin to merge with your body, it feels like your body is made of a viscous water, making your movements difficult.")
+ M.next_move_modifier += 4//If this makes you fast then please fix it, it should make you slow!!
+ //candidates = pollGhostCandidates("Do you want to play as a clone of [M.name] and do you agree to respect their character and act in a similar manner to them? I swear to god if you diddle them I will be very disapointed in you. ", "FermiClone", null, ROLE_SENTIENCE, 300) // see poll_ignore.dm, should allow admins to ban greifers or bullies
+ if(51 to 79)
+ M.nutrition = M.nutrition + (M.nutrition/2)
+ if(80)
+ to_chat(M, "The cells begin to precipitate outwards of your body, you feel like you'll split soon...")
+ if (M.nutrition < 20000)
+ M.nutrition = 20000 //https://www.youtube.com/watch?v=Bj_YLenOlZI
+ if(86)//Upon splitting, you get really hungry and are capable again. Deletes the chem after you're done.
+ M.nutrition = 15//YOU BEST BE EATTING AFTER THIS YOU CUTIE
+ M.next_move_modifier -= 4
+ to_chat(M, "Your body splits away from the cell clone of yourself, leaving you with a drained and hollow feeling inside.")
+
+ //clone
+ var/typepath = M.type
+ var/mob/living/fermi_Clone = new typepath(M.loc)
+ var/mob/living/carbon/C = fermi_Clone
+
+ if(istype(C) && istype(M))
+ C.real_name = M.real_name
+ M.dna.transfer_identity(C, transfer_SE=1)
+ C.updateappearance(mutcolor_update=1)
+ C.apply_status_effect(/datum/status_effect/chem/SGDF)
+ var/datum/status_effect/chem/SGDF/S = C.has_status_effect(/datum/status_effect/chem/SGDF)
+ S.original = M
+ S.originalmind = M.mind
+ S.status_set = TRUE
+
+ log_game("FERMICHEM: [M] ckey: [M.key] has created a mindless clone of themselves")
+ SSblackbox.record_feedback("tally", "fermi_chem", 1, "Braindead clones made")
+ if(87 to INFINITY)
+ M.reagents.remove_reagent(id, volume)//removes SGDF on completion. Has to do it this way because of how i've coded it. If some madlab gets over 1k of SDGF, they can have the clone healing.
+
+
+ ..()
+
+/datum/reagent/fermi/SDGF/on_mob_delete(mob/living/M) //When the chem is removed, a few things can happen, mostly consolation prizes.
+ pollStarted = FALSE
+ if (playerClone == TRUE)//If the player made a clone with it, then thats all they get.
+ playerClone = FALSE
+ return
+ if (M.next_move_modifier == 4 && !M.has_status_effect(/datum/status_effect/chem/SGDF))//checks if they're ingested over 20u of the stuff, but fell short of the required 30u to make a clone.
+ to_chat(M, "You feel the cells begin to merge with your body, unable to reach nucleation, they instead merge with your body, healing any wounds.")
+ M.adjustCloneLoss(-10, 0) //I don't want to make Rezadone obsolete.
+ M.adjustBruteLoss(-25, 0)// Note that this takes a long time to apply and makes you fat and useless when it's in you, I don't think this small burst of healing will be useful considering how long it takes to get there.
+ M.adjustFireLoss(-25, 0)
+ M.blood_volume += 250
+ M.heal_bodypart_damage(1,1)
+ M.next_move_modifier = 1
+ if (M.nutrition < 1500)
+ M.nutrition += 250
+ else if (unitCheck == TRUE && !M.has_status_effect(/datum/status_effect/chem/SGDF))// If they're ingested a little bit (10u minimum), then give them a little healing.
+ unitCheck = FALSE
+ to_chat(M, "the cells fail to hold enough mass to generate a clone, instead diffusing into your system.")
+ M.adjustBruteLoss(-10, 0)
+ M.adjustFireLoss(-10, 0)
+ M.blood_volume += 100
+ M.next_move_modifier = 1
+ if (M.nutrition < 1500)
+ M.nutrition += 500
+
+/datum/reagent/fermi/SDGF/reaction_mob(mob/living/carbon/human/M, method=TOUCH, reac_volume)
+ if(volume<5)
+ M.visible_message("The growth factor froths upon [M]'s body, failing to do anything of note.")
+ return
+ if(M.stat == DEAD)
+ if(M.suiciding || (HAS_TRAIT(M, TRAIT_NOCLONE)) || M.hellbound)
+ M.visible_message("The growth factor inertly sticks to [M]'s body, failing to do anything of note.")
+ return
+ if(!M.mind)
+ M.visible_message("The growth factor shudders, merging with [M]'s body, but is unable to replicate properly.")
+
+ var/bodydamage = (M.getBruteLoss() + M.getFireLoss())
+ var/typepath = M.type
+ volume =- 5
+
+ var/mob/living/carbon/human/fermi_Gclone = new typepath(M.loc)
+ var/mob/living/carbon/human/SM = fermi_Gclone
+ if(istype(SM) && istype(M))
+ SM.real_name = M.real_name
+ M.dna.transfer_identity(SM)
+ SM.updateappearance(mutcolor_update=1)
+ M.mind.transfer_to(SM)
+ M.visible_message("[M]'s body shudders, the growth factor rapidly splitting into a new clone of [M].")
+
+ if(bodydamage>50)
+ SM.adjustOxyLoss(-(bodydamage/10), 0)
+ SM.adjustToxLoss(-(bodydamage/10), 0)
+ SM.blood_volume = (BLOOD_VOLUME_NORMAL*SM.blood_ratio)/1.5
+ SM.adjustCloneLoss((bodydamage/10), 0)
+ SM.setBrainLoss((bodydamage/10))
+ SM.nutrition = 400
+ if(bodydamage>200)
+ SM.gain_trauma_type(BRAIN_TRAUMA_MILD)
+ if(bodydamage>300)
+ var/obj/item/bodypart/l_arm = SM.get_bodypart(BODY_ZONE_L_ARM) //We get the body parts we want this way.
+ var/obj/item/bodypart/r_arm = SM.get_bodypart(BODY_ZONE_R_ARM)
+ l_arm.drop_limb()
+ r_arm.drop_limb()
+ if(bodydamage>400)
+ var/obj/item/bodypart/l_leg = SM.get_bodypart(BODY_ZONE_L_LEG) //We get the body parts we want this way.
+ var/obj/item/bodypart/r_leg = SM.get_bodypart(BODY_ZONE_R_LEG)
+ l_leg.drop_limb()
+ r_leg.drop_limb()
+ if(bodydamage>500)
+ SM.gain_trauma_type(BRAIN_TRAUMA_SEVERE)
+ if(bodydamage>600)
+ var/datum/species/mutation = pick(subtypesof(/datum/species))
+ SM.set_species(mutation)
+
+ //Transfer remaining reagent to clone. I think around 30u will make a healthy clone, otherwise they'll have clone damage, blood loss, brain damage and hunger.
+ SM.reagents.add_reagent("SDGFheal", volume)
+ M.reagents.remove_reagent(id, volume)
+
+ SM.updatehealth()
+ SM.emote("gasp")
+ log_combat(M, M, "SDGF clone-vived", src)
+ ..()
+
+//Unobtainable, used in clone spawn.
+/datum/reagent/fermi/SDGFheal
+ name = "synthetic-derived growth factor"
+ id = "SDGFheal"
+ metabolization_rate = 1
+ can_synth = FALSE
+
+/datum/reagent/fermi/SDGFheal/on_mob_life(mob/living/carbon/M)//Used to heal the clone after splitting, the clone spawns damaged. (i.e. insentivies players to make more than required, so their clone doesn't have to be treated)
+ if(M.blood_volume < (BLOOD_VOLUME_NORMAL*M.blood_ratio))
+ M.blood_volume += 10
+ M.adjustCloneLoss(-2, 0)
+ M.setBrainLoss(-1)
+ M.nutrition += 10
+ ..()
+
+//Unobtainable, used if SDGF is impure but not too impure
+/datum/reagent/fermi/SDGFtox
+ name = "synthetic-derived growth factor"
+ id = "SDGFtox"
+ description = "A chem that makes a certain chemcat angry at you if you're reading this, how did you get this???"//i.e. tell me please, figure it's a good way to get pinged for bugfixes.
+ metabolization_rate = 1
+ can_synth = FALSE
+
+/datum/reagent/fermi/SDGFtox/on_mob_life(mob/living/carbon/M)//Damages the taker if their purity is low. Extended use of impure chemicals will make the original die. (thus can't be spammed unless you've very good)
+ M.blood_volume -= 10
+ M.adjustCloneLoss(2, 0)
+ ..()
+
+//Fail state of SDGF
+/datum/reagent/fermi/SDZF
+ name = "synthetic-derived growth factor"
+ id = "SDZF"
+ description = "A horribly peverse mass of Embryonic stem cells made real by the hands of a failed chemist. This message should never appear, how did you manage to get a hold of this?"
+ color = "#a502e0" // rgb: 96, 0, 255
+ metabolization_rate = 0.5 * REAGENTS_METABOLISM
+ var/startHunger
+ can_synth = TRUE
+
+/datum/reagent/fermi/SDZF/on_mob_life(mob/living/carbon/M) //If you're bad at fermichem, turns your clone into a zombie instead.
+ switch(current_cycle)//Pretends to be normal
+ if(20)
+ to_chat(M, "You feel the synethic cells rest uncomfortably within your body as they start to pulse and grow rapidly.")
+ startHunger = M.nutrition
+ if(21 to 29)
+ M.nutrition = M.nutrition + (M.nutrition/10)
+ if(30)
+ to_chat(M, "You feel the synethic cells grow and expand within yourself, bloating your body outwards.")
+ if(31 to 49)
+ M.nutrition = M.nutrition + (M.nutrition/5)
+ if(50)
+ to_chat(M, "The synethic cells begin to merge with your body, it feels like your body is made of a viscous water, making your movements difficult.")
+ M.next_move_modifier = 4//If this makes you fast then please fix it, it should make you slow!!
+ if(51 to 73)
+ M.nutrition = M.nutrition + (M.nutrition/2)
+ if(74)
+ to_chat(M, "The cells begin to precipitate outwards of your body, but... something is wrong, the sythetic cells are beginnning to rot...")
+ if (M.nutrition < 20000) //whoever knows the maxcap, please let me know, this seems a bit low.
+ M.nutrition = 20000 //https://www.youtube.com/watch?v=Bj_YLenOlZI
+ if(75 to 85)
+ M.adjustToxLoss(1, 0)// the warning!
+
+ if(86)//mean clone time!
+ if (!M.reagents.has_reagent("pen_acid"))//Counterplay is pent.)
+ message_admins("(non-infectious) Zombie spawned at [M.loc], produced by impure chem, wah!")
+ M.nutrition = startHunger - 500//YOU BEST BE RUNNING AWAY AFTER THIS YOU BADDIE
+ M.next_move_modifier = 1
+ to_chat(M, "Your body splits away from the cell clone of yourself, your attempted clone birthing itself violently from you as it begins to shamble around, a terrifying abomination of science.")
+ M.visible_message("[M] suddenly shudders, and splits into a funky smelling copy of themselves!")
+ M.emote("scream")
+ M.adjustToxLoss(30, 0)
+ var/mob/living/simple_animal/hostile/unemployedclone/ZI = new(get_turf(M.loc))
+ ZI.damage_coeff = list(BRUTE = ((1 / volume)**0.25) , BURN = ((1 / volume)**0.1), TOX = 1, CLONE = 1, STAMINA = 0, OXY = 1)
+ ZI.real_name = M.real_name//Give your offspring a big old kiss.
+ ZI.name = M.real_name
+ ZI.desc = "[M]'s clone, gone horribly wrong."
+ log_game("FERMICHEM: [M] ckey: [M.key]'s clone has become a horrifying zombie instead")
+ M.reagents.remove_reagent(id, 20)
+
+ else//easier to deal with
+ to_chat(M, "The pentetic acid seems to have stopped the decay for now, clumping up the cells into a horrifying tumour!")
+ M.nutrition = startHunger - 500
+ var/mob/living/simple_animal/slime/S = new(get_turf(M.loc),"grey") //TODO: replace slime as own simplemob/add tumour slime cores for science/chemistry interplay
+ S.damage_coeff = list(BRUTE = ((1 / volume)**0.1) , BURN = 2, TOX = 1, CLONE = 1, STAMINA = 0, OXY = 1)
+ S.name = "Living teratoma"
+ S.real_name = "Living teratoma"//horrifying!!
+ S.rabid = 1//Make them an angery boi
+ M.reagents.remove_reagent(id, volume)
+ to_chat(M, "A large glob of the tumour suddenly splits itself from your body. You feel grossed out and slimey...")
+ log_game("FERMICHEM: [M] ckey: [M.key]'s clone has become a horrifying teratoma instead")
+ SSblackbox.record_feedback("tally", "fermi_chem", 1, "Zombie clones made!")
+
+ if(87 to INFINITY)
+ M.adjustToxLoss(1, 0)
+ ..()
diff --git a/modular_citadel/code/modules/reagents/chemistry/reagents/astrogen.dm b/modular_citadel/code/modules/reagents/chemistry/reagents/astrogen.dm
new file mode 100644
index 0000000000..d05cfb552e
--- /dev/null
+++ b/modular_citadel/code/modules/reagents/chemistry/reagents/astrogen.dm
@@ -0,0 +1,152 @@
+/*
+////////////////////////////////////////////////////////////////////////////////////////////////////
+// ASTROGEN
+///////////////////////////////////////////////////////////////////////////////////////////////////
+More fun chems!
+When you take it, it spawns a ghost that the player controls. (No access to deadchat)
+This ghost moves pretty quickly and is mostly invisible, but is still visible for people with eyes.
+When it's out of your system, you return back to yourself. It doesn't last long and metabolism of the chem is exponential.
+Addiction is particularlly brutal, it slowly turns you invisible with flavour text, then kills you at a low enough alpha. (i've also added something to prevent geneticists speeding this up)
+There's afairly major catch regarding the death though. I'm not gonna say here, go read the code, it explains it and puts my comments on it in context. I know that anyone reading it without understanding it is going to freak out so, this is my attempt to get you to read it and understand it.
+I'd like to point out from my calculations it'll take about 60-80 minutes to die this way too. Plenty of time to visit me and ask for some pills to quench your addiction.
+*/
+
+
+
+/datum/reagent/fermi/astral // Gives you the ability to astral project for a moment!
+ name = "Astrogen"
+ id = "astral"
+ description = "An opalescent murky liquid that is said to distort your soul from your being."
+ color = "#A080H4" // rgb: , 0, 255
+ taste_description = "your mind"
+ metabolization_rate = 0//Removal is exponential, see code
+ overdose_threshold = 20
+ addiction_threshold = 24.5
+ addiction_stage1_end = 9999//Should never end. There is no escape make your time
+ var/mob/living/carbon/origin
+ var/mob/living/simple_animal/astral/G = null
+ var/datum/mind/originalmind
+ var/antiGenetics = 255
+ var/sleepytime = 0
+ InverseChemVal = 0.25
+ can_synth = FALSE
+
+/datum/action/chem/astral
+ name = "Return to body"
+ var/mob/living/carbon/origin = null
+ var/mob/living/simple_animal/hostile/retaliate/ghost = null
+
+/datum/action/chem/astral/Trigger()
+ ghost.mind.transfer_to(origin)
+ qdel(src)
+
+/datum/reagent/fermi/astral/reaction_turf(turf/T, reac_volume)
+ if(isplatingturf(T) || istype(T, /turf/open/floor/plasteel))
+ var/turf/open/floor/F = T
+ F.PlaceOnTop(/turf/open/floor/fakespace)
+ ..()
+
+/datum/reagent/fermi/astral/reaction_obj(obj/O, reac_volume)
+ if(istype(O, /obj/item/bedsheet))
+ new /obj/item/bedsheet/cosmos(get_turf(O))
+ qdel(O)
+
+
+/datum/reagent/fermi/astral/on_mob_life(mob/living/carbon/M) // Gives you the ability to astral project for a moment!
+ M.alpha = 255
+ if(current_cycle == 0)
+ originalmind = M.mind
+ log_game("FERMICHEM: [M] ckey: [M.key] became an astral ghost")
+ origin = M
+ if (G == null)
+ G = new(get_turf(M.loc))
+ G.name = "[M]'s astral projection"
+ var/datum/action/chem/astral/AS = new(G)
+ AS.origin = M
+ AS.ghost = G
+ if(M.mind)
+ M.mind.transfer_to(G)
+ SSblackbox.record_feedback("tally", "fermi_chem", 1, "Astral projections")
+ if(overdosed)
+ if(prob(50))
+ to_chat(G, "The high conentration of Astrogen in your blood causes you to lapse your concentration for a moment, bringing your projection back to yourself!")
+ do_teleport(G, M.loc)
+ M.reagents.remove_reagent(id, current_cycle/10, FALSE)//exponent
+ sleepytime+=5
+ if(G)//This is a mess because of how slow qdel is, so this is all to stop runtimes.
+ if(G.mind)
+ if(G.stat == DEAD || G.pseudo_death == TRUE)
+ G.mind.transfer_to(M)
+ qdel(G)
+ ..()
+
+/datum/reagent/fermi/astral/on_mob_delete(mob/living/carbon/M)
+ if(!G)
+ if(M.mind)
+ var/mob/living/simple_animal/astral/G = new(get_turf(M.loc))
+ M.mind.transfer_to(G)//Just in case someone else is inside of you, it makes them a ghost and should hopefully bring them home at the end.
+ to_chat(G, "[M]'s conciousness snaps back to them as their astrogen runs out, kicking your projected mind out!'")
+ log_game("FERMICHEM: [M]'s possesser has been booted out into a astral ghost!")
+ originalmind.transfer_to(M)
+ else if(G.mind)
+ G.mind.transfer_to(origin)
+ qdel(G)
+ if(overdosed)
+ to_chat(M, "The high volume of astrogen you just took causes you to black out momentarily as your mind snaps back to your body.")
+ M.Sleeping(sleepytime, 0)
+ antiGenetics = 255
+ if(G)//just in case
+ qdel(G)
+ log_game("FERMICHEM: [M] has astrally returned to their body!")
+ ..()
+
+//Okay so, this might seem a bit too good, but my counterargument is that it'll likely take all round to eventually kill you this way, then you have to be revived without a body. It takes approximately 50-80 minutes to die from this.
+/datum/reagent/fermi/astral/addiction_act_stage1(mob/living/carbon/M)
+ if(addiction_stage < 2)
+ antiGenetics = 255
+ M.alpha = 255 //Antigenetics is to do with stopping geneticists from turning people invisible to kill them.
+ if(prob(70))
+ M.alpha--
+ antiGenetics--
+ switch(antiGenetics)
+ if(245)
+ to_chat(M, "You notice your body starting to disappear, maybe you took too much Astrogen...?")
+ M.alpha--
+ antiGenetics--
+ log_game("FERMICHEM: [M] ckey: [M.key] has become addicted to Astrogen")
+ if(220)
+ to_chat(M, "Your addiction is only getting worse as your body disappears. Maybe you should get some more, and fast?")
+ M.alpha--
+ antiGenetics--
+ if(200)
+ to_chat(M, "You feel a substantial part of your soul flake off into the ethereal world, rendering yourself unclonable.")
+ M.alpha--
+ antiGenetics--
+ ADD_TRAIT(M, TRAIT_NOCLONE, "astral") //So you can't scan yourself, then die, to metacomm. You can only use your memories if you come back as something else.
+ M.hellbound = TRUE
+ if(180)
+ to_chat(M, "You feel fear build up in yourself as more and more of your body and consciousness begins to fade.")
+ M.alpha--
+ antiGenetics--
+ if(120)
+ to_chat(M, "As you lose more and more of yourself, you start to think that maybe shedding your mortality isn't too bad.")
+ M.alpha--
+ antiGenetics--
+ if(80)
+ to_chat(M, "You feel a thrill shoot through your body as what's left of your mind contemplates your forthcoming oblivion.")
+ M.alpha--
+ antiGenetics--
+ if(45)
+ to_chat(M, "The last vestiges of your mind eagerly await your imminent annihilation.")
+ M.alpha--
+ antiGenetics--
+ if(-INFINITY to 30)
+ to_chat(M, "Your body disperses from existence, as you become one with the universe.")
+ to_chat(M, "As your body disappears, your consciousness doesn't. Should you find a way back into the mortal coil, your memories of your previous life remain with you. (At the cost of staying in character while dead. Failure to do this may get you banned from this chem. You are still obligated to follow your directives if you play a midround antag, you do not remember the afterlife IC)")//Legalised IC OOK? I have a suspicion this won't make it past the review. At least it'll be presented as a neat idea! If this is unacceptable how about the player can retain living memories across lives if they die in this way only.
+ deadchat_broadcast("[M] has become one with the universe, meaning that their IC conciousness is continuous in a new life. If they find a way back to life, they are allowed to remember their previous life. Be careful what you say. If they abuse this, bwoink the FUCK outta them.")
+ M.visible_message("[M] suddenly disappears, their body evaporating from existence, freeing [M] from their mortal coil.")
+ message_admins("[M] (ckey: [M.ckey]) has become one with the universe, and have continuous memories thoughout their lives should they find a way to come back to life (such as an inteligence potion, midround antag, ghost role).")
+ SSblackbox.record_feedback("tally", "fermi_chem", 1, "Astral obliterations")
+ qdel(M) //Approx 60minutes till death from initial addiction
+ log_game("FERMICHEM: [M] ckey: [M.key] has been obliterated from Astrogen addiction")
+ ..()
diff --git a/modular_citadel/code/modules/reagents/chemistry/reagents/eigentstasium.dm b/modular_citadel/code/modules/reagents/chemistry/reagents/eigentstasium.dm
new file mode 100644
index 0000000000..bf915d5b6f
--- /dev/null
+++ b/modular_citadel/code/modules/reagents/chemistry/reagents/eigentstasium.dm
@@ -0,0 +1,203 @@
+////////////////////////////////////////////////////////////////////////////////////////////////////
+// EIGENSTASIUM
+///////////////////////////////////////////////////////////////////////////////////////////////////
+//eigenstate Chem
+//Teleports you to chemistry and back
+//OD teleports you randomly around the Station
+//Addiction send you on a wild ride and replaces you with an alternative reality version of yourself.
+//During the process you get really hungry, then your items start teleporting randomly,
+//then alternative versions of yourself are brought in from a different universe and they yell at you.
+//and finally you yourself get teleported to an alternative universe, and character your playing is replaced with said alternative
+
+/datum/reagent/fermi/eigenstate
+ name = "Eigenstasium"
+ id = "eigenstate"
+ description = "A strange mixture formed from a controlled reaction of bluespace with plasma, that causes localised eigenstate fluxuations within the patient"
+ taste_description = "wiggly cosmic dust."
+ color = "#5020F4" // rgb: 50, 20, 255
+ overdose_threshold = 15
+ addiction_threshold = 15
+ metabolization_rate = 1.2 * REAGENTS_METABOLISM
+ addiction_stage2_end = 30
+ addiction_stage3_end = 41
+ addiction_stage4_end = 44 //Incase it's too long
+ data = list("location_created" = null)
+ var/turf/location_created
+ var/obj/effect/overlay/holo_pad_hologram/Eigenstate
+ var/turf/open/location_return = null
+ var/addictCyc3 = 0
+ var/mob/living/carbon/fermi_Tclone = null
+ var/teleBool = FALSE
+ pH = 3.7
+ can_synth = TRUE
+
+/datum/reagent/fermi/eigenstate/on_new(list/data)
+ location_created = data.["location_created"]
+
+//Main functions
+/datum/reagent/fermi/eigenstate/on_mob_life(mob/living/M) //Teleports to chemistry!
+ if(current_cycle == 0)
+ log_game("FERMICHEM: [M] ckey: [M.key] took eigenstasium")
+
+ //make hologram at return point
+ Eigenstate = new(loc)
+ Eigenstate.appearance = M.appearance
+ Eigenstate.alpha = 170
+ Eigenstate.add_atom_colour("#77abff", FIXED_COLOUR_PRIORITY)
+ Eigenstate.mouse_opacity = MOUSE_OPACITY_TRANSPARENT//So you can't click on it.
+ Eigenstate.layer = FLY_LAYER//Above all the other objects/mobs. Or the vast majority of them.
+ Eigenstate.setAnchored(TRUE)//So space wind cannot drag it.
+ Eigenstate.name = "[M]'s' eigenstate"//If someone decides to right click.
+ Eigenstate.set_light(2) //hologram lighting
+
+ location_return = get_turf(M) //sets up return point
+ to_chat(M, "You feel your wavefunction split!")
+ if(purity > 0.9) //Teleports you home if it's pure enough
+ if(!location_created && data) //Just in case
+ location_created = data.["location_created"]
+ log_game("FERMICHEM: [M] ckey: [M.key] returned to [location_created] using eigenstasium")
+ do_sparks(5,FALSE,M)
+ do_teleport(M, location_created, 0, asoundin = 'sound/effects/phasein.ogg')
+ do_sparks(5,FALSE,M)
+ SSblackbox.record_feedback("tally", "fermi_chem", 1, "Pure eigentstate jumps")
+
+
+ if(prob(20))
+ do_sparks(5,FALSE,M)
+ ..()
+
+/datum/reagent/fermi/eigenstate/on_mob_delete(mob/living/M) //returns back to original location
+ do_sparks(5,FALSE,M)
+ to_chat(M, "You feel your wavefunction collapse!")
+ do_teleport(M, location_return, 0, asoundin = 'sound/effects/phasein.ogg') //Teleports home
+ do_sparks(5,FALSE,M)
+ qdel(Eigenstate)
+ ..()
+
+/datum/reagent/fermi/eigenstate/overdose_start(mob/living/M) //Overdose, makes you teleport randomly
+ . = ..()
+ to_chat(M, "Oh god, you feel like your wavefunction is about to tear.")
+ log_game("FERMICHEM: [M] ckey: [M.key] has overdosed on eigenstasium")
+ M.Jitter(20)
+ metabolization_rate += 0.5 //So you're not stuck forever teleporting.
+
+/datum/reagent/fermi/eigenstate/overdose_process(mob/living/M) //Overdose, makes you teleport randomly, probably one of my favourite effects. Sometimes kills you.
+ do_sparks(5,FALSE,M)
+ do_teleport(M, get_turf(M), 10, asoundin = 'sound/effects/phasein.ogg')
+ do_sparks(5,FALSE,M)
+ ..()
+
+//Addiction
+/datum/reagent/fermi/eigenstate/addiction_act_stage1(mob/living/M) //Welcome to Fermis' wild ride.
+ if(addiction_stage == 1)
+ to_chat(M, "Your wavefunction feels like it's been ripped in half. You feel empty inside.")
+ log_game("FERMICHEM: [M] ckey: [M.key] has become addicted to eigenstasium")
+ M.Jitter(10)
+ M.nutrition = M.nutrition - (M.nutrition/15)
+ ..()
+
+/datum/reagent/fermi/eigenstate/addiction_act_stage2(mob/living/M)
+ if(addiction_stage == 11)
+ to_chat(M, "You start to convlse violently as you feel your consciousness split and merge across realities as your possessions fly wildy off your body.")
+ M.Jitter(200)
+ M.Knockdown(200)
+ M.Stun(80)
+ var/items = M.get_contents()
+ if(!LAZYLEN(items))
+ return ..()
+ var/obj/item/I = pick(items)
+ if(istype(I, /obj/item/implant))
+ qdel(I)
+ to_chat(M, "You feel your implant rip itself out of you, sent flying off to another dimention!")
+ else
+ M.dropItemToGround(I, TRUE)
+ do_sparks(5,FALSE,I)
+ do_teleport(I, get_turf(I), 5, no_effects=TRUE);
+ do_sparks(5,FALSE,I)
+ ..()
+
+/datum/reagent/fermi/eigenstate/addiction_act_stage3(mob/living/M)//Pulls multiple copies of the character from alternative realities while teleporting them around!
+ //Clone function - spawns a clone then deletes it - simulates multiple copies of the player teleporting in
+ switch(addictCyc3) //Loops 0 -> 1 -> 2 -> 1 -> 2 -> 1 ...ect.
+ if(0)
+ M.Jitter(100)
+ to_chat(M, "Your eigenstate starts to rip apart, causing a localised collapsed field as you're ripped from alternative universes, trapped around the densisty of the event horizon.")
+ if(1)
+ var/typepath = M.type
+ fermi_Tclone = new typepath(M.loc)
+ var/mob/living/carbon/C = fermi_Tclone
+ fermi_Tclone.appearance = M.appearance
+ C.real_name = M.real_name
+ M.visible_message("[M] collapses in from an alternative reality!")
+ do_teleport(C, get_turf(C), 2, no_effects=TRUE) //teleports clone so it's hard to find the real one!
+ do_sparks(5,FALSE,C)
+ C.emote("spin")
+ M.emote("spin")
+ M.emote("me",1,"flashes into reality suddenly, gasping as they gaze around in a bewildered and highly confused fashion!",TRUE)
+ C.emote("me",1,"[pick("says", "cries", "mewls", "giggles", "shouts", "screams", "gasps", "moans", "whispers", "announces")], \"[pick("Bugger me, whats all this then?", "Hot damn, where is this?", "sacre bleu! Ou suis-je?!", "Yee haw! This is one hell of a hootenanny!", "WHAT IS HAPPENING?!", "Picnic!", "Das ist nicht deutschland. Das ist nicht akzeptabel!!!", "I've come from the future to warn you to not take eigenstasium! Oh no! I'm too late!", "You fool! You took too much eigenstasium! You've doomed us all!", "What...what's with these teleports? It's like one of my Japanese animes...!", "Ik stond op het punt om mehki op tafel te zetten, en nu, waar ben ik?", "This must be the will of Stein's gate.", "Fermichem was a mistake", "This is one hell of a beepsky smash.", "Now neither of us will be virgins!")]\"")
+ if(2)
+ var/mob/living/carbon/C = fermi_Tclone
+ do_sparks(5,FALSE,C)
+ qdel(C) //Deletes CLONE, or at least I hope it is.
+ M.visible_message("[M] is snapped across to a different alternative reality!")
+ addictCyc3 = 0 //counter
+ fermi_Tclone = null
+ addictCyc3++
+ do_teleport(M, get_turf(M), 2, no_effects=TRUE) //Teleports player randomly
+ do_sparks(5,FALSE,M)
+ ..()
+
+/datum/reagent/fermi/eigenstate/addiction_act_stage4(mob/living/M) //Thanks for riding Fermis' wild ride. Mild jitter and player buggery.
+ if(addiction_stage == 42)
+ do_sparks(5,FALSE,M)
+ do_teleport(M, get_turf(M), 2, no_effects=TRUE) //teleports clone so it's hard to find the real one!
+ do_sparks(5,FALSE,M)
+ M.Sleeping(100, 0)
+ M.Jitter(50)
+ M.Knockdown(100)
+ to_chat(M, "You feel your eigenstate settle, snapping an alternative version of yourself into reality. All your previous memories are lost and replaced with the alternative version of yourself. This version of you feels more [pick("affectionate", "happy", "lusty", "radical", "shy", "ambitious", "frank", "voracious", "sensible", "witty")] than your previous self, sent to god knows what universe.")
+ M.emote("me",1,"flashes into reality suddenly, gasping as they gaze around in a bewildered and highly confused fashion!",TRUE)
+ log_game("FERMICHEM: [M] ckey: [M.key] has become an alternative universe version of themselves.")
+ M.reagents.remove_all_type(/datum/reagent, 100, 0, 1)
+ /*
+ for(var/datum/mood_event/Me in M)
+ SEND_SIGNAL(M, COMSIG_CLEAR_MOOD_EVENT, Me) //Why does this not work?
+ */
+ SEND_SIGNAL(M, COMSIG_ADD_MOOD_EVENT, "Alternative dimension", /datum/mood_event/eigenstate)
+ SSblackbox.record_feedback("tally", "fermi_chem", 1, "Wild rides ridden")
+
+ if(prob(20))
+ do_sparks(5,FALSE,M)
+ SEND_SIGNAL(M, COMSIG_CLEAR_MOOD_EVENT, "[id]_overdose")//holdover until above fix works
+ ..()
+
+/datum/reagent/fermi/eigenstate/reaction_turf(turf/T, reac_volume)
+ //if(cached_purity < 0.99) To add with next batch of fixes and tweaks.
+ var/obj/structure/closet/First
+ var/obj/structure/closet/Previous
+ for(var/obj/structure/closet/C in T.contents)
+ if(C.eigen_teleport == TRUE)
+ C.visible_message("[C] fizzes, it's already linked to something else!")
+ continue
+ if(!Previous)
+ First = C
+ Previous = C
+ continue
+ C.eigen_teleport = TRUE
+ C.eigen_target = Previous
+ C.color = "#9999FF" //Tint the locker slightly.
+ C.alpha = 200
+ do_sparks(5,FALSE,C)
+ Previous = C
+ if(!First)
+ return
+ if(Previous == First)
+ return
+ First.eigen_teleport = TRUE
+ First.eigen_target = Previous
+ First.color = "#9999FF"
+ First.alpha = 200
+ do_sparks(5,FALSE,First)
+ First.visible_message("The lockers' eigenstates spilt and merge, linking each of their contents together.")
+
+//eigenstate END
diff --git a/modular_citadel/code/modules/reagents/chemistry/reagents/enlargement.dm b/modular_citadel/code/modules/reagents/chemistry/reagents/enlargement.dm
new file mode 100644
index 0000000000..d603f3ba10
--- /dev/null
+++ b/modular_citadel/code/modules/reagents/chemistry/reagents/enlargement.dm
@@ -0,0 +1,365 @@
+////////////////////////////////////////////////////////////////////////////////////////////////////
+// BREAST ENLARGE
+///////////////////////////////////////////////////////////////////////////////////////////////////
+//Other files that are relivant:
+//modular_citadel/code/datums/status_effects/chems.dm
+//modular_citadel/code/modules/arousal/organs/breasts.dm
+
+//breast englargement
+//Honestly the most requested chems
+//I'm not a very kinky person, sorry if it's not great
+//I tried to make it interesting..!!
+
+//Normal function increases your breast size by 0.05, 10units = 1 cup.
+//If you get stupid big, it presses against your clothes, causing brute and oxydamage. Then rips them off.
+//If you keep going, it makes you slower, in speed and action.
+//decreasing your size will return you to normal.
+//(see the status effect in chem.dm)
+//Overdosing on (what is essentially space estrogen) makes you female, removes balls and shrinks your dick.
+//OD is low for a reason. I'd like fermichems to have low ODs, and dangerous ODs, and since this is a meme chem that everyone will rush to make, it'll be a lesson learnt early.
+
+/datum/reagent/fermi/breast_enlarger
+ name = "Succubus milk"
+ id = "breast_enlarger"
+ description = "A volatile collodial mixture derived from milk that encourages mammary production via a potent estrogen mix."
+ color = "#E60584" // rgb: 96, 0, 255
+ taste_description = "a milky ice cream like flavour."
+ overdose_threshold = 17
+ metabolization_rate = 0.25
+ ImpureChem = "BEsmaller" //If you make an inpure chem, it stalls growth
+ InverseChemVal = 0.35
+ InverseChem = "BEsmaller" //At really impure vols, it just becomes 100% inverse
+ can_synth = FALSE
+
+/datum/reagent/fermi/breast_enlarger/on_mob_add(mob/living/carbon/M)
+ . = ..()
+ if(!ishuman(M)) //The monkey clause
+ if(volume >= 15) //To prevent monkey breast farms
+ var/turf/T = get_turf(M)
+ var/obj/item/organ/genital/breasts/B = new /obj/item/organ/genital/breasts(T)
+ var/list/seen = viewers(8, T)
+ for(var/mob/S in seen)
+ to_chat(S, "A pair of breasts suddenly fly out of the [M]!")
+ //var/turf/T2 = pick(turf in view(5, M))
+ var/T2 = get_random_station_turf()
+ M.adjustBruteLoss(25)
+ M.Knockdown(50)
+ M.Stun(50)
+ B.throw_at(T2, 8, 1)
+ M.reagents.remove_reagent(id, volume)
+ return
+ log_game("FERMICHEM: [M] ckey: [M.key] has ingested Sucubus milk")
+ var/mob/living/carbon/human/H = M
+ H.genital_override = TRUE
+ var/obj/item/organ/genital/breasts/B = H.getorganslot("breasts")
+ if(!B)
+ H.emergent_genital_call()
+ return
+ if(!B.size == "huge")
+ var/sizeConv = list("a" = 1, "b" = 2, "c" = 3, "d" = 4, "e" = 5)
+ B.prev_size = B.size
+ B.cached_size = sizeConv[B.size]
+
+/datum/reagent/fermi/breast_enlarger/on_mob_life(mob/living/carbon/M) //Increases breast size
+ if(!ishuman(M))//Just in case
+ return..()
+
+ var/mob/living/carbon/human/H = M
+ var/obj/item/organ/genital/breasts/B = M.getorganslot("breasts")
+ if(!B) //If they don't have breasts, give them breasts.
+
+ //If they have Acute hepatic pharmacokinesis, then route processing though liver.
+ if(HAS_TRAIT(M, TRAIT_PHARMA))
+ var/obj/item/organ/liver/L = M.getorganslot("liver")
+ if(L)
+ L.swelling+= 0.05
+ return..()
+ else
+ M.adjustToxLoss(1)
+ return..()
+
+ //otherwise proceed as normal
+ var/obj/item/organ/genital/breasts/nB = new
+ nB.Insert(M)
+ if(nB)
+ if(M.dna.species.use_skintones && M.dna.features["genitals_use_skintone"])
+ nB.color = skintone2hex(H.skin_tone)
+ else if(M.dna.features["breasts_color"])
+ nB.color = "#[M.dna.features["breasts_color"]]"
+ else
+ nB.color = skintone2hex(H.skin_tone)
+ nB.size = "flat"
+ nB.cached_size = 0
+ nB.prev_size = 0
+ to_chat(M, "Your chest feels warm, tingling with newfound sensitivity.")
+ M.reagents.remove_reagent(id, 5)
+ B = nB
+ //If they have them, increase size. If size is comically big, limit movement and rip clothes.
+ B.cached_size = B.cached_size + 0.05
+ if (B.cached_size >= 8.5 && B.cached_size < 9)
+ if(H.w_uniform || H.wear_suit)
+ var/target = M.get_bodypart(BODY_ZONE_CHEST)
+ to_chat(M, "Your breasts begin to strain against your clothes tightly!")
+ M.adjustOxyLoss(5, 0)
+ M.apply_damage(1, BRUTE, target)
+ B.update()
+ ..()
+
+/datum/reagent/fermi/breast_enlarger/overdose_process(mob/living/carbon/M) //Turns you into a female if male and ODing, doesn't touch nonbinary and object genders.
+
+ //Acute hepatic pharmacokinesis.
+ if(HAS_TRAIT(M, TRAIT_PHARMA))
+ var/obj/item/organ/liver/L = M.getorganslot("liver")
+ L.swelling+= 0.05
+ return ..()
+
+ var/obj/item/organ/genital/penis/P = M.getorganslot("penis")
+ var/obj/item/organ/genital/testicles/T = M.getorganslot("testicles")
+ var/obj/item/organ/genital/vagina/V = M.getorganslot("vagina")
+ var/obj/item/organ/genital/womb/W = M.getorganslot("womb")
+
+ if(M.gender == MALE)
+ M.gender = FEMALE
+ M.visible_message("[M] suddenly looks more feminine!", "You suddenly feel more feminine!")
+
+ if(P)
+ P.cached_length = P.cached_length - 0.05
+ P.update()
+ if(T)
+ T.Remove(M)
+ if(!V)
+ var/obj/item/organ/genital/vagina/nV = new
+ nV.Insert(M)
+ V = nV
+ if(!W)
+ var/obj/item/organ/genital/womb/nW = new
+ nW.Insert(M)
+ W = nW
+ ..()
+
+/datum/reagent/fermi/BEsmaller
+ name = "Modesty milk"
+ id = "BEsmaller"
+ description = "A volatile collodial mixture derived from milk that encourages mammary reduction via a potent estrogen mix."
+ color = "#E60584" // rgb: 96, 0, 255
+ taste_description = "a milky ice cream like flavour."
+ metabolization_rate = 0.25
+ can_synth = FALSE
+
+/datum/reagent/fermi/BEsmaller/on_mob_life(mob/living/carbon/M)
+ var/obj/item/organ/genital/breasts/B = M.getorganslot("breasts")
+ if(!B)
+ //Acute hepatic pharmacokinesis.
+ if(HAS_TRAIT(M, TRAIT_PHARMA))
+ var/obj/item/organ/liver/L = M.getorganslot("liver")
+ L.swelling-= 0.05
+ return ..()
+
+ //otherwise proceed as normal
+ return..()
+ B.cached_size = B.cached_size - 0.05
+ B.update()
+ ..()
+
+/datum/reagent/fermi/BEsmaller_hypo
+ name = "Rectify milk" //Rectify
+ id = "BEsmaller_hypo"
+ color = "#E60584"
+ taste_description = "a milky ice cream like flavour."
+ metabolization_rate = 0.25
+ description = "A medicine used to treat organomegaly in a patient's breasts."
+ var/sizeConv = list("a" = 1, "b" = 2, "c" = 3, "d" = 4, "e" = 5)
+ can_synth = TRUE
+
+/datum/reagent/fermi/BEsmaller_hypo/on_mob_add(mob/living/carbon/M)
+ . = ..()
+ if(!M.getorganslot("vagina"))
+ if(M.dna.features["has_vag"])
+ var/obj/item/organ/genital/vagina/nV = new
+ nV.Insert(M)
+ if(!M.getorganslot("womb"))
+ if(M.dna.features["has_womb"])
+ var/obj/item/organ/genital/womb/nW = new
+ nW.Insert(M)
+
+/datum/reagent/fermi/BEsmaller_hypo/on_mob_life(mob/living/carbon/M)
+ var/obj/item/organ/genital/breasts/B = M.getorganslot("breasts")
+ if(!B)
+ return..()
+ if(!M.dna.features["has_breasts"])//Fast fix for those who don't want it.
+ B.cached_size = B.cached_size - 0.1
+ B.update()
+ else if(B.cached_size > (sizeConv[M.dna.features["breasts_size"]]+0.1))
+ B.cached_size = B.cached_size - 0.05
+ B.update()
+ else if(B.cached_size < (sizeConv[M.dna.features["breasts_size"]])+0.1)
+ B.cached_size = B.cached_size + 0.05
+ B.update()
+ ..()
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+// PENIS ENLARGE
+///////////////////////////////////////////////////////////////////////////////////////////////////
+//See breast explanation, it's the same but with taliwhackers
+//instead of slower movement and attacks, it slows you and increases the total blood you need in your system.
+//Since someone else made this in the time it took me to PR it, I merged them.
+/datum/reagent/fermi/penis_enlarger // Due to popular demand...!
+ name = "Incubus draft"
+ id = "penis_enlarger"
+ description = "A volatile collodial mixture derived from various masculine solutions that encourages a larger gentleman's package via a potent testosterone mix, formula derived from a collaboration from Fermichem and Doctor Ronald Hyatt, who is well known for his phallus palace." //The toxic masculinity thing is a joke because I thought it would be funny to include it in the reagents, but I don't think many would find it funny? dumb
+ color = "#888888" // This is greyish..?
+ taste_description = "chinese dragon powder"
+ overdose_threshold = 17 //ODing makes you male and removes female genitals
+ metabolization_rate = 0.5
+ ImpureChem = "PEsmaller" //If you make an inpure chem, it stalls growth
+ InverseChemVal = 0.35
+ InverseChem = "PEsmaller" //At really impure vols, it just becomes 100% inverse and shrinks instead.
+ can_synth = FALSE
+
+/datum/reagent/fermi/penis_enlarger/on_mob_add(mob/living/carbon/M)
+ . = ..()
+ if(!ishuman(M)) //Just monkeying around.
+ if(volume >= 15) //to prevent monkey penis farms
+ var/turf/T = get_turf(M)
+ var/obj/item/organ/genital/penis/P = new /obj/item/organ/genital/penis(T)
+ var/list/seen = viewers(8, T)
+ for(var/mob/S in seen)
+ to_chat(S, "A penis suddenly flies out of the [M]!")
+ var/T2 = get_random_station_turf()
+ M.adjustBruteLoss(25)
+ M.Knockdown(50)
+ M.Stun(50)
+ P.throw_at(T2, 8, 1)
+ M.reagents.remove_reagent(id, volume)
+ return
+ var/mob/living/carbon/human/H = M
+ H.genital_override = TRUE
+ var/obj/item/organ/genital/penis/P = M.getorganslot("penis")
+ if(!P)
+ H.emergent_genital_call()
+ return
+ P.prev_length = P.length
+ P.cached_length = P.length
+
+/datum/reagent/fermi/penis_enlarger/on_mob_life(mob/living/carbon/M) //Increases penis size, 5u = +1 inch.
+ if(!ishuman(M))
+ return
+ var/mob/living/carbon/human/H = M
+ var/obj/item/organ/genital/penis/P = M.getorganslot("penis")
+ if(!P)//They do have a preponderance for escapism, or so I've heard.
+
+ //If they have Acute hepatic pharmacokinesis, then route processing though liver.
+ if(HAS_TRAIT(M, TRAIT_PHARMA))
+ var/obj/item/organ/liver/L = M.getorganslot("liver")
+ if(L)
+ L.swelling+= 0.05
+ return..()
+ else
+ M.adjustToxLoss(1)
+ return..()
+
+ //otherwise proceed as normal
+ var/obj/item/organ/genital/penis/nP = new
+ nP.Insert(M)
+ if(nP)
+ nP.length = 1
+ to_chat(M, "Your groin feels warm, as you feel a newly forming bulge down below.")
+ nP.cached_length = 1
+ nP.prev_length = 1
+ M.reagents.remove_reagent(id, 5)
+ P = nP
+
+ P.cached_length = P.cached_length + 0.1
+ if (P.cached_length >= 20.5 && P.cached_length < 21)
+ if(H.w_uniform || H.wear_suit)
+ var/target = M.get_bodypart(BODY_ZONE_CHEST)
+ to_chat(M, "Your cock begin to strain against your clothes tightly!")
+ M.apply_damage(2.5, BRUTE, target)
+
+ P.update()
+ ..()
+
+/datum/reagent/fermi/penis_enlarger/overdose_process(mob/living/carbon/M) //Turns you into a male if female and ODing, doesn't touch nonbinary and object genders.
+ //Acute hepatic pharmacokinesis.
+ if(HAS_TRAIT(M, TRAIT_PHARMA))
+ var/obj/item/organ/liver/L = M.getorganslot("liver")
+ L.swelling+= 0.05
+ return..()
+
+ var/obj/item/organ/genital/breasts/B = M.getorganslot("breasts")
+ var/obj/item/organ/genital/testicles/T = M.getorganslot("testicles")
+ var/obj/item/organ/genital/vagina/V = M.getorganslot("vagina")
+ var/obj/item/organ/genital/womb/W = M.getorganslot("womb")
+
+ if(M.gender == FEMALE)
+ M.gender = MALE
+ M.visible_message("[M] suddenly looks more masculine!", "You suddenly feel more masculine!")
+
+ if(B)
+ B.cached_size = B.cached_size - 0.05
+ B.update()
+ if(V)
+ V.Remove(M)
+ if(W)
+ W.Remove(M)
+ if(!T)
+ var/obj/item/organ/genital/testicles/nT = new
+ nT.Insert(M)
+ T = nT
+ ..()
+
+/datum/reagent/fermi/PEsmaller // Due to cozmo's request...!
+ name = "Chastity draft"
+ id = "PEsmaller"
+ description = "A volatile collodial mixture derived from various masculine solutions that encourages a smaller gentleman's package via a potent testosterone mix, formula derived from a collaboration from Fermichem and Doctor Ronald Hyatt, who is well known for his phallus palace."
+ color = "#888888" // This is greyish..?
+ taste_description = "chinese dragon powder"
+ metabolization_rate = 0.5
+ can_synth = FALSE
+
+/datum/reagent/fermi/PEsmaller/on_mob_life(mob/living/carbon/M)
+ var/mob/living/carbon/human/H = M
+ var/obj/item/organ/genital/penis/P = H.getorganslot("penis")
+ if(!P)
+ //Acute hepatic pharmacokinesis.
+ if(HAS_TRAIT(M, TRAIT_PHARMA))
+ var/obj/item/organ/liver/L = M.getorganslot("liver")
+ L.swelling-= 0.05
+ return..()
+
+ //otherwise proceed as normal
+ return..()
+ P.cached_length = P.cached_length - 0.1
+ P.update()
+ ..()
+
+/datum/reagent/fermi/PEsmaller_hypo
+ name = "Rectify draft"
+ id = "PEsmaller_hypo"
+ color = "#888888" // This is greyish..?
+ taste_description = "chinese dragon powder"
+ description = "A medicine used to treat organomegaly in a patient's penis."
+ metabolization_rate = 0.5
+ can_synth = TRUE
+
+/datum/reagent/fermi/PEsmaller_hypo/on_mob_add(mob/living/carbon/M)
+ . = ..()
+ if(!M.getorganslot("testicles"))
+ if(M.dna.features["has_balls"])
+ var/obj/item/organ/genital/testicles/nT = new
+ nT.Insert(M)
+
+/datum/reagent/fermi/PEsmaller_hypo/on_mob_life(mob/living/carbon/M)
+ var/obj/item/organ/genital/penis/P = M.getorganslot("penis")
+ if(!P)
+ return ..()
+ if(!M.dna.features["has_cock"])//Fast fix for those who don't want it.
+ P.cached_length = P.cached_length - 0.2
+ P.update()
+ else if(P.cached_length > (M.dna.features["cock_length"]+0.1))
+ P.cached_length = P.cached_length - 0.1
+ P.update()
+ else if(P.cached_length < (M.dna.features["cock_length"]+0.1))
+ P.cached_length = P.cached_length + 0.1
+ P.update()
+ ..()
diff --git a/modular_citadel/code/modules/reagents/chemistry/reagents/fermi_reagents.dm b/modular_citadel/code/modules/reagents/chemistry/reagents/fermi_reagents.dm
new file mode 100644
index 0000000000..275c244a83
--- /dev/null
+++ b/modular_citadel/code/modules/reagents/chemistry/reagents/fermi_reagents.dm
@@ -0,0 +1,462 @@
+ //Fermichem!!
+//Fun chems for all the family
+
+/datum/reagent/fermi
+ name = "Fermi" //This should never exist, but it does so that it can exist in the case of errors..
+ id = "fermi"
+ taste_description = "affection and love!"
+ can_synth = FALSE
+
+//This should process fermichems to find out how pure they are and what effect to do.
+/datum/reagent/fermi/on_mob_add(mob/living/carbon/M, amount)
+ . = ..()
+ if(!M)
+ return
+ if(purity < 0)
+ CRASH("Purity below 0 for chem: [id], Please let Fermis Know!")
+ if (purity == 1 || DoNotSplit == TRUE)
+ log_game("FERMICHEM: [M] ckey: [M.key] has ingested [volume]u of [id]")
+ return
+ else if (InverseChemVal > purity)//Turns all of a added reagent into the inverse chem
+ M.reagents.remove_reagent(id, amount, FALSE)
+ M.reagents.add_reagent(InverseChem, amount, FALSE, other_purity = 1)
+ log_game("FERMICHEM: [M] ckey: [M.key] has ingested [volume]u of [InverseChem]")
+ return
+ else
+ var/impureVol = amount * (1 - purity) //turns impure ratio into impure chem
+ M.reagents.remove_reagent(id, (impureVol), FALSE)
+ M.reagents.add_reagent(ImpureChem, impureVol, FALSE, other_purity = 1)
+ log_game("FERMICHEM: [M] ckey: [M.key] has ingested [volume - impureVol]u of [id]")
+ log_game("FERMICHEM: [M] ckey: [M.key] has ingested [volume]u of [ImpureChem]")
+ return
+
+//When merging two fermichems, see above
+/datum/reagent/fermi/on_merge(data, amount, mob/living/carbon/M, purity)//basically on_mob_add but for merging
+ . = ..()
+ if(!ishuman(M))
+ return
+ if (purity < 0)
+ CRASH("Purity below 0 for chem: [id], Please let Fermis Know!")
+ if (purity == 1 || DoNotSplit == TRUE)
+ log_game("FERMICHEM: [M] ckey: [M.key] has merged [volume]u of [id] in themselves")
+ return
+ else if (InverseChemVal > purity)
+ M.reagents.remove_reagent(id, amount, FALSE)
+ M.reagents.add_reagent(InverseChem, amount, FALSE, other_purity = 1)
+ for(var/datum/reagent/fermi/R in M.reagents.reagent_list)
+ if(R.name == "")
+ R.name = name//Negative effects are hidden
+ log_game("FERMICHEM: [M] ckey: [M.key] has merged [volume]u of [InverseChem]")
+ return
+ else
+ var/impureVol = amount * (1 - purity)
+ M.reagents.remove_reagent(id, impureVol, FALSE)
+ M.reagents.add_reagent(ImpureChem, impureVol, FALSE, other_purity = 1)
+ for(var/datum/reagent/fermi/R in M.reagents.reagent_list)
+ if(R.name == "")
+ R.name = name//Negative effects are hidden
+ log_game("FERMICHEM: [M] ckey: [M.key] has merged [volume - impureVol]u of [id]")
+ log_game("FERMICHEM: [M] ckey: [M.key] has merged [volume]u of [ImpureChem]")
+ return
+
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+// HATIMUIM
+///////////////////////////////////////////////////////////////////////////////////////////////////
+//Adds a heat upon your head, and tips their hat
+//Also has a speech alteration effect when the hat is there
+//Increase armour; 1 armour per 10u
+//but if you OD it becomes negative.
+
+
+/datum/reagent/fermi/hatmium //for hatterhat
+ name = "Hat growth serium"
+ id = "hatmium"
+ description = "A strange substance that draws in a hat from the hat dimention."
+ color = "#7c311a" // rgb: , 0, 255
+ taste_description = "like jerky, whiskey and an off aftertaste of a crypt."
+ metabolization_rate = 0.2
+ overdose_threshold = 25
+ DoNotSplit = TRUE
+ pH = 4
+ can_synth = TRUE
+
+
+/datum/reagent/fermi/hatmium/on_mob_add(mob/living/carbon/human/M)
+ . = ..()
+ if(M.head)
+ var/obj/item/W = M.head
+ if(istype(W, /obj/item/clothing/head/hattip))
+ qdel(W)
+ else
+ M.dropItemToGround(W, TRUE)
+ var/hat = new /obj/item/clothing/head/hattip()
+ M.equip_to_slot(hat, SLOT_HEAD, 1, 1)
+
+
+/datum/reagent/fermi/hatmium/on_mob_life(mob/living/carbon/human/M)
+ if(!istype(M.head, /obj/item/clothing/head/hattip))
+ return ..()
+ var/hatArmor = 0
+ if(!overdosed)
+ hatArmor = (purity/10)
+ else
+ hatArmor = - (purity/10)
+ if(hatArmor > 90)
+ return ..()
+ var/obj/item/W = M.head
+ W.armor = W.armor.modifyAllRatings(hatArmor)
+ ..()
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+// FURRANIUM
+///////////////////////////////////////////////////////////////////////////////////////////////////
+//OwO whats this?
+//Makes you nya and awoo
+//At a certain amount of time in your system it gives you a fluffy tongue, if pure enough, it's permanent.
+
+/datum/reagent/fermi/furranium
+ name = "Furranium"
+ id = "furranium"
+ description = "OwO whats this?"
+ color = "#f9b9bc" // rgb: , 0, 255
+ taste_description = "dewicious degenyewacy"
+ metabolization_rate = 0.5 * REAGENTS_METABOLISM
+ InverseChemVal = 0
+ var/obj/item/organ/tongue/nT
+ DoNotSplit = TRUE
+ pH = 5
+ var/obj/item/organ/tongue/T
+ can_synth = TRUE
+
+/datum/reagent/fermi/furranium/reaction_mob(mob/living/carbon/human/M, method=INJECT, reac_volume)
+ if(method == INJECT)
+ var/turf/T = get_turf(M)
+ M.adjustOxyLoss(15)
+ M.Knockdown(50)
+ M.Stun(50)
+ M.emote("cough")
+ var/obj/item/toy/plush/P = pick(subtypesof(/obj/item/toy/plush))
+ new P(T)
+ to_chat(M, "You feel a lump form in your throat, as you suddenly cough up what seems to be a hairball?")
+ var/list/seen = viewers(8, T)
+ for(var/mob/S in seen)
+ to_chat(S, "[M] suddenly coughs up a [P.name]!")
+ var/T2 = get_random_station_turf()
+ P.throw_at(T2, 8, 1)
+ ..()
+
+/datum/reagent/fermi/furranium/on_mob_life(mob/living/carbon/M)
+
+ switch(current_cycle)
+ if(1 to 9)
+ if(prob(20))
+ to_chat(M, "Your tongue feels... fluffy")
+ if(10 to 15)
+ if(prob(10))
+ to_chat(M, "You find yourself unable to supress the desire to meow!")
+ M.emote("nya")
+ if(prob(10))
+ to_chat(M, "You find yourself unable to supress the desire to howl!")
+ M.emote("awoo")
+ if(prob(20))
+ var/list/seen = viewers(5, get_turf(M))//Sound and sight checkers
+ for(var/victim in seen)
+ if((istype(victim, /mob/living/simple_animal/pet/)) || (victim == M) || (!isliving(victim)))
+ seen = seen - victim
+ if(LAZYLEN(seen))
+ to_chat(M, "You notice [pick(seen)]'s bulge [pick("OwO!", "uwu!")]")
+ if(16)
+ T = M.getorganslot(ORGAN_SLOT_TONGUE)
+ var/obj/item/organ/tongue/nT = new /obj/item/organ/tongue/fluffy
+ T.Remove(M)
+ nT.Insert(M)
+ T.moveToNullspace()//To valhalla
+ to_chat(M, "Your tongue feels... weally fwuffy!!")
+ if(17 to INFINITY)
+ if(prob(5))
+ to_chat(M, "You find yourself unable to supress the desire to meow!")
+ M.emote("nya")
+ if(prob(5))
+ to_chat(M, "You find yourself unable to supress the desire to howl!")
+ M.emote("awoo")
+ if(prob(5))
+ var/list/seen = viewers(5, get_turf(M))//Sound and sight checkers
+ for(var/victim in seen)
+ if((istype(victim, /mob/living/simple_animal/pet/)) || (victim == M) || (!isliving(victim)))
+ seen = seen - victim
+ if(LAZYLEN(seen))
+ to_chat(M, "You notice [pick(seen)]'s bulge [pick("OwO!", "uwu!")]")
+ ..()
+
+/datum/reagent/fermi/furranium/on_mob_delete(mob/living/carbon/M)
+ if(purity < 1)//Only permanent if you're a good chemist.
+ nT = M.getorganslot(ORGAN_SLOT_TONGUE)
+ nT.Remove(M)
+ qdel(nT)
+ T.Insert(M)
+ to_chat(M, "You feel your tongue.... unfluffify...?")
+ M.say("Pleh!")
+ else
+ log_game("FERMICHEM: [M] ckey: [M.key]'s tongue has been made permanent")
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////
+//Nanite removal
+//Writen by Trilby!! Embellsished a little by me.
+
+/datum/reagent/fermi/nanite_b_gone
+ name = "Naninte bane"
+ id = "nanite_b_gone"
+ description = "A stablised EMP that is highly volatile, shocking small nano machines that will kill them off at a rapid rate in a patient's system."
+ color = "#708f8f"
+ overdose_threshold = 15
+ ImpureChem = "nanite_b_goneTox" //If you make an inpure chem, it stalls growth
+ InverseChemVal = 0.25
+ InverseChem = "nanite_b_goneTox" //At really impure vols, it just becomes 100% inverse
+ taste_description = "what can only be described as licking a battery."
+ pH = 9
+ can_synth = FALSE
+
+/datum/reagent/fermi/nanite_b_gone/on_mob_life(mob/living/carbon/C)
+ //var/component/nanites/N = M.GetComponent(/datum/component/nanites)
+ GET_COMPONENT_FROM(N, /datum/component/nanites, C)
+ if(isnull(N))
+ return ..()
+ N.nanite_volume = -purity//0.5 seems to be the default to me, so it'll neuter them.
+ ..()
+
+/datum/reagent/fermi/nanite_b_gone/overdose_process(mob/living/carbon/C)
+ //var/component/nanites/N = M.GetComponent(/datum/component/nanites)
+ GET_COMPONENT_FROM(N, /datum/component/nanites, C)
+ if(prob(5))
+ to_chat(C, "The residual voltage from the nanites causes you to seize up!")
+ C.electrocute_act(10, (get_turf(C)), 1, FALSE, FALSE, FALSE, TRUE)
+ if(prob(10))
+ //empulse((get_turf(C)), 3, 2)//So the nanites randomize
+ var/atom/T = C
+ T.emp_act(EMP_HEAVY)
+ to_chat(C, "The nanites short circuit within your system!")
+ if(isnull(N))
+ return ..()
+ N.nanite_volume = -2
+ ..()
+
+/datum/reagent/fermi/nanite_b_gone/reaction_obj(obj/O, reac_volume)
+ O.emp_act(EMP_HEAVY)
+
+/datum/reagent/fermi/nanite_b_goneTox
+ name = "Naninte bain"
+ id = "nanite_b_goneTox"
+ description = "Poorly made, and shocks you!"
+ metabolization_rate = 1
+
+//Increases shock events.
+/datum/reagent/fermi/nanite_b_goneTox/on_mob_life(mob/living/carbon/C)//Damages the taker if their purity is low. Extended use of impure chemicals will make the original die. (thus can't be spammed unless you've very good)
+ if(prob(15))
+ to_chat(C, "The residual voltage in your system causes you to seize up!")
+ C.electrocute_act(10, (get_turf(C)), 1, FALSE, FALSE, FALSE, TRUE)
+ if(prob(50))
+ var/atom/T = C
+ T.emp_act(EMP_HEAVY)
+ to_chat(C, "You feel your hair stand on end as you glow brightly for a moment!")
+ ..()
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////
+// MISC FERMICHEM CHEMS FOR SPECIFIC INTERACTIONS ONLY
+///////////////////////////////////////////////////////////////////////////////////////////////
+
+/datum/reagent/fermi/fermiAcid
+ name = "Acid vapour"
+ id = "fermiAcid"
+ description = "Someone didn't do like an otter, and add acid to water."
+ taste_description = "acid burns, ow"
+ color = "#FFFFFF"
+ pH = 0
+ can_synth = FALSE
+
+/datum/reagent/fermi/fermiAcid/reaction_mob(mob/living/carbon/C, method)
+ var/target = C.get_bodypart(BODY_ZONE_CHEST)
+ var/acidstr
+ if(!C.reagents.pH || C.reagents.pH >5)
+ acidstr = 3
+ else
+ acidstr = ((5-C.reagents.pH)*2) //runtime - null.pH ?
+ C.adjustFireLoss(acidstr/2, 0)
+ if((method==VAPOR) && (!C.wear_mask))
+ if(prob(20))
+ to_chat(C, "You can feel your lungs burning!")
+ var/obj/item/organ/lungs/L = C.getorganslot(ORGAN_SLOT_LUNGS)
+ L.adjustLungLoss(acidstr*2, C)
+ C.apply_damage(acidstr/5, BURN, target)
+ C.acid_act(acidstr, volume)
+ ..()
+
+/datum/reagent/fermi/fermiAcid/reaction_obj(obj/O, reac_volume)
+ if(ismob(O.loc)) //handled in human acid_act()
+ return
+ if((holder.pH > 5) || (volume < 0.1)) //Shouldn't happen, but just in case
+ return
+ reac_volume = round(volume,0.1)
+ var/acidstr = (5-holder.pH)*2 //(max is 10)
+ O.acid_act(acidstr, volume)
+ ..()
+
+/datum/reagent/fermi/fermiAcid/reaction_turf(turf/T, reac_volume)
+ if (!istype(T))
+ return
+ reac_volume = round(volume,0.1)
+ var/acidstr = (5-holder.pH)
+ T.acid_act(acidstr, volume)
+ ..()
+
+/datum/reagent/fermi/fermiTest
+ name = "Fermis Test Reagent"
+ id = "fermiTest"
+ description = "You should be really careful with this...! Also, how did you get this?"
+ addProc = TRUE
+ can_synth = FALSE
+
+/datum/reagent/fermi/fermiTest/on_new(datum/reagents/holder)
+ ..()
+ if(LAZYLEN(holder.reagent_list) == 1)
+ return
+ else
+ holder.remove_reagent("fermiTest", volume)//Avoiding recurrsion
+ var/location = get_turf(holder.my_atom)
+ if(purity < 0.34 || purity == 1)
+ var/datum/effect_system/foam_spread/s = new()
+ s.set_up(volume*2, location, holder)
+ s.start()
+ if((purity < 0.67 && purity >= 0.34)|| purity == 1)
+ var/datum/effect_system/smoke_spread/chem/s = new()
+ s.set_up(holder, volume*2, location)
+ s.start()
+ if(purity >= 0.67)
+ for (var/datum/reagent/reagent in holder.reagent_list)
+ if (istype(reagent, /datum/reagent/fermi))
+ var/datum/chemical_reaction/fermi/Ferm = GLOB.chemical_reagents_list[reagent.id]
+ Ferm.FermiExplode(src, holder.my_atom, holder, holder.total_volume, holder.chem_temp, holder.pH)
+ else
+ var/datum/chemical_reaction/Ferm = GLOB.chemical_reagents_list[reagent.id]
+ Ferm.on_reaction(holder, reagent.volume)
+ for(var/mob/M in viewers(8, location))
+ to_chat(M, "The solution reacts dramatically, with a meow!")
+ playsound(get_turf(M), 'modular_citadel/sound/voice/merowr.ogg', 50, 1)
+ holder.clear_reagents()
+
+/datum/reagent/fermi/fermiTox
+ name = "FermiTox"
+ id = "fermiTox"
+ description = "You should be really careful with this...! Also, how did you get this? You shouldn't have this!"
+ data = "merge"
+ color = "FFFFFF"
+ can_synth = FALSE
+
+//I'm concerned this is too weak, but I also don't want deathmixes.
+/datum/reagent/fermi/fermiTox/on_mob_life(mob/living/carbon/C, method)
+ if(C.dna && istype(C.dna.species, /datum/species/jelly))
+ C.adjustToxLoss(-2)
+ else
+ C.adjustToxLoss(2)
+ ..()
+
+/datum/reagent/fermi/acidic_buffer
+ name = "Acidic buffer"
+ id = "acidic_buffer"
+ description = "This reagent will consume itself and move the pH of a beaker towards acidity when added to another."
+ color = "#fbc314"
+ pH = 0
+ can_synth = TRUE
+
+//Consumes self on addition and shifts pH
+/datum/reagent/fermi/acidic_buffer/on_new(datapH)
+ data = datapH
+ if(LAZYLEN(holder.reagent_list) == 1)
+ return
+ holder.pH = ((holder.pH * holder.total_volume)+(pH * (volume)))/(holder.total_volume + (volume))
+ var/list/seen = viewers(5, get_turf(holder))
+ for(var/mob/M in seen)
+ to_chat(M, "The beaker fizzes as the pH changes!")
+ playsound(get_turf(holder.my_atom), 'sound/FermiChem/bufferadd.ogg', 50, 1)
+ holder.remove_reagent(id, volume, ignore_pH = TRUE)
+ ..()
+
+/datum/reagent/fermi/basic_buffer
+ name = "Basic buffer"
+ id = "basic_buffer"
+ description = "This reagent will consume itself and move the pH of a beaker towards alkalinity when added to another."
+ color = "#3853a4"
+ pH = 14
+ can_synth = TRUE
+
+/datum/reagent/fermi/basic_buffer/on_new(datapH)
+ data = datapH
+ if(LAZYLEN(holder.reagent_list) == 1)
+ return
+ holder.pH = ((holder.pH * holder.total_volume)+(pH * (volume)))/(holder.total_volume + (volume))
+ var/list/seen = viewers(5, get_turf(holder))
+ for(var/mob/M in seen)
+ to_chat(M, "The beaker froths as the pH changes!")
+ playsound(get_turf(holder.my_atom), 'sound/FermiChem/bufferadd.ogg', 50, 1)
+ holder.remove_reagent(id, volume, ignore_pH = TRUE)
+ ..()
+
+//Turns you into a cute catto while it's in your system.
+//If you manage to gamble perfectly, makes you have cat ears after you transform back. But really, you shouldn't end up with that with how random it is.
+/datum/reagent/fermi/secretcatchem //Should I hide this from code divers? A secret cit chem?
+ name = "secretcatchem" //an attempt at hiding it
+ id = "secretcatchem"
+ description = "An illegal and hidden chem that turns people into cats. It's said that it's so rare and unstable that having it means you've been blessed."
+ taste_description = "hairballs and cream"
+ color = "#ffc224"
+ var/catshift = FALSE
+ var/mob/living/simple_animal/pet/cat/custom_cat/catto = null
+ can_synth = FALSE
+
+/datum/reagent/fermi/secretcatchem/New()
+ name = "Catbalti[pick("a","u","e","y")]m [pick("apex", "prime", "meow")]"//rename
+
+/datum/reagent/fermi/secretcatchem/on_mob_add(mob/living/carbon/human/H)
+ . = ..()
+ if(purity >= 0.8)//ONLY if purity is high, and given the stuff is random. It's very unlikely to get this to 1. It already requires felind too, so no new functionality there.
+ //exception(al) handler:
+ H.dna.features["ears"] = "Cat"
+ H.dna.features["mam_ears"] = "Cat"
+ H.verb_say = "mewls"
+ catshift = TRUE
+ playsound(get_turf(H), 'modular_citadel/sound/voice/merowr.ogg', 50, 1, -1)
+ to_chat(H, "You suddenly turn into a cat!")
+ catto = new(get_turf(H.loc))
+ H.mind.transfer_to(catto)
+ catto.name = H.name
+ catto.desc = "A cute catto! They remind you of [H] somehow."
+ catto.color = "#[H.dna.features["mcolor"]]"
+ catto.pseudo_death = TRUE
+ H.forceMove(catto)
+ log_game("FERMICHEM: [H] ckey: [H.key] has been made into a cute catto.")
+ SSblackbox.record_feedback("tally", "fermi_chem", 1, "cats")
+ //Just to deal with rascally ghosts
+ //ADD_TRAIT(catto, TRAIT_NODEATH, "catto")//doesn't work
+ //catto.health = 1000 //To simulate fake death, while preventing ghosts escaping.
+
+/datum/reagent/fermi/secretcatchem/on_mob_life(mob/living/carbon/H)
+ if(catto.health <= 0) //So the dead can't ghost
+ if(prob(10))
+ to_chat(H, "You feel your body start to slowly shift back from it's dead form.")
+ else if(prob(5))
+ playsound(get_turf(catto), 'modular_citadel/sound/voice/merowr.ogg', 50, 1, -1)
+ catto.say("lets out a meowrowr!*")
+ ..()
+
+/datum/reagent/fermi/secretcatchem/on_mob_delete(mob/living/carbon/H)
+ var/words = "Your body shifts back to normal."
+ H.forceMove(catto.loc)
+ catto.mind.transfer_to(H)
+ if(catshift == TRUE)
+ words += " ...But wait, are those cat ears?"
+ H.say("*wag")//force update sprites.
+ to_chat(H, "[words]")
+ qdel(catto)
+ log_game("FERMICHEM: [H] ckey: [H.key] has returned to normal")
diff --git a/modular_citadel/code/modules/reagents/chemistry/reagents/healing.dm b/modular_citadel/code/modules/reagents/chemistry/reagents/healing.dm
new file mode 100644
index 0000000000..b717948a20
--- /dev/null
+++ b/modular_citadel/code/modules/reagents/chemistry/reagents/healing.dm
@@ -0,0 +1,95 @@
+/datum/reagent/fermi/yamerol
+ name = "Yamerol"
+ id = "yamerol"
+ description = "For when you've trouble speaking or breathing, just yell YAMEROL! A chem that helps soothe any congestion problems and at high concentrations restores damaged lungs and tongues!"
+ taste_description = "a weird, syrupy flavour, yamero"
+ color = "#68e83a"
+ pH = 8.6
+ overdose_threshold = 35
+ ImpureChem = "yamerol_tox"
+ InverseChemVal = 0.4
+ InverseChem = "yamerol_tox"
+ can_synth = TRUE
+
+/datum/reagent/fermi/yamerol/on_mob_life(mob/living/carbon/C)
+ var/obj/item/organ/tongue/T = C.getorganslot(ORGAN_SLOT_TONGUE)
+ var/obj/item/organ/lungs/L = C.getorganslot(ORGAN_SLOT_LUNGS)
+
+ if(T)
+ T.adjustTongueLoss(C, -2)//Fix the inputs me!
+ if(L)
+ L.adjustLungLoss(-5, C)
+ C.adjustOxyLoss(-2)
+ else
+ C.adjustOxyLoss(-10)
+
+ if(T)
+ if(T.name == "fluffy tongue")
+ var/obj/item/organ/tongue/nT
+ if(C.dna && C.dna.species && C.dna.species.mutanttongue)
+ nT = new C.dna.species.mutanttongue()
+ else
+ nT = new()
+ T.Remove(C)
+ qdel(T)
+ nT.Insert(C)
+ to_chat(C, "You feel your tongue.... unfluffify...?")
+ holder.remove_reagent(src.id, "10")
+ ..()
+
+/datum/reagent/fermi/yamerol/overdose_process(mob/living/carbon/C)
+ var/obj/item/organ/tongue/oT = C.getorganslot(ORGAN_SLOT_TONGUE)
+ if(current_cycle == 1)
+ to_chat(C, "You feel the Yamerol sooth your tongue and lungs.")
+ if(current_cycle > 10)
+ if(!C.getorganslot(ORGAN_SLOT_TONGUE))
+ var/obj/item/organ/tongue/T
+ if(C.dna && C.dna.species && C.dna.species.mutanttongue)
+ T = new C.dna.species.mutanttongue()
+ else
+ T = new()
+ T.Insert(C)
+ to_chat(C, "You feel your tongue reform in your mouth.")
+ holder.remove_reagent(src.id, "10")
+ else
+ if((oT.name == "fluffy tongue") && (purity == 1))
+ var/obj/item/organ/tongue/T
+ if(C.dna && C.dna.species && C.dna.species.mutanttongue)
+ T = new C.dna.species.mutanttongue()
+ else
+ T = new()
+ oT.Remove(C)
+ qdel(oT)
+ T.Insert(C)
+ to_chat(C, "You feel your tongue.... unfluffify...?")
+ holder.remove_reagent(src.id, "10")
+
+ if(!C.getorganslot(ORGAN_SLOT_LUNGS))
+ var/obj/item/organ/lungs/L = new()
+ L.Insert(C)
+ to_chat(C, "You feel your lungs reform in your chest.")
+ holder.remove_reagent(src.id, "10")
+
+ C.adjustOxyLoss(-3)
+ ..()
+
+/datum/reagent/fermi/yamerol_tox
+ name = "Yamerol"
+ id = "yamerol_tox"
+ description = "For when you've trouble speaking or breathing, just yell YAMEROL! A chem that helps soothe any congestion problems and at high concentrations restores damaged lungs and tongues!"
+ taste_description = "a weird, syrupy flavour, yamero"
+ color = "#68e83a"
+ pH = 8.6
+
+/datum/reagent/fermi/yamerol_tox/on_mob_life(mob/living/carbon/C)
+ var/obj/item/organ/tongue/T = C.getorganslot(ORGAN_SLOT_TONGUE)
+ var/obj/item/organ/lungs/L = C.getorganslot(ORGAN_SLOT_LUNGS)
+
+ if(T)
+ T.adjustTongueLoss(C, 1)
+ if(L)
+ L.adjustLungLoss(4, C)
+ C.adjustOxyLoss(3)
+ else
+ C.adjustOxyLoss(10)
+ ..()
diff --git a/modular_citadel/code/modules/reagents/chemistry/recipes/FermiCalc.Py b/modular_citadel/code/modules/reagents/chemistry/recipes/FermiCalc.Py
new file mode 100644
index 0000000000..9ffc057743
--- /dev/null
+++ b/modular_citadel/code/modules/reagents/chemistry/recipes/FermiCalc.Py
@@ -0,0 +1,11 @@
+# -*- coding: utf-8 -*-
+"""
+Created on Fri May 17 05:12:17 2019
+
+@author: Fermi
+"""
+
+import numpy
+import matplotlib as mpl
+import matplotlib.pyplot as plt
+
diff --git a/modular_citadel/code/modules/reagents/chemistry/recipes/fermi.dm b/modular_citadel/code/modules/reagents/chemistry/recipes/fermi.dm
new file mode 100644
index 0000000000..078e1c8a71
--- /dev/null
+++ b/modular_citadel/code/modules/reagents/chemistry/recipes/fermi.dm
@@ -0,0 +1,541 @@
+/datum/chemical_reaction/fermi
+ mix_sound = 'sound/effects/bubbles.ogg'
+
+//Called for every reaction step
+/datum/chemical_reaction/fermi/proc/FermiCreate(holder)
+ return
+
+//Called when reaction STOP_PROCESSING
+/datum/chemical_reaction/fermi/proc/FermiFinish(datum/reagents/holder)
+ return
+
+//Called when temperature is above a certain threshold, or if purity is too low.
+/datum/chemical_reaction/fermi/proc/FermiExplode(datum/reagents, var/atom/my_atom, volume, temp, pH, Exploding = FALSE)
+ if (Exploding == TRUE)
+ return
+
+ if(!pH)//Dunno how things got here without a pH, but just in case
+ pH = 7
+ var/ImpureTot = 0
+ var/turf/T = get_turf(my_atom)
+
+ if(temp>500)//if hot, start a fire
+ switch(temp)
+ if (500 to 750)
+ for(var/turf/turf in range(1,T))
+ new /obj/effect/hotspot(turf)
+
+ if (751 to 1100)
+ for(var/turf/turf in range(2,T))
+ new /obj/effect/hotspot(turf)
+
+ if (1101 to 1500) //If you're crafty
+ for(var/turf/turf in range(3,T))
+ new /obj/effect/hotspot(turf)
+
+ if (1501 to 2500) //requested
+ for(var/turf/turf in range(4,T))
+ new /obj/effect/hotspot(turf)
+
+ if (2501 to 5000)
+ for(var/turf/turf in range(5,T))
+ new /obj/effect/hotspot(turf)
+
+ if (5001 to INFINITY)
+ for(var/turf/turf in range(6,T))
+ new /obj/effect/hotspot(turf)
+
+
+ message_admins("Fermi explosion at [T], with a temperature of [temp], pH of [pH], Impurity tot of [ImpureTot].")
+ log_game("Fermi explosion at [T], with a temperature of [temp], pH of [pH], Impurity tot of [ImpureTot].")
+ var/datum/reagents/R = new/datum/reagents(3000)//Hey, just in case.
+ var/datum/effect_system/smoke_spread/chem/s = new()
+ R.my_atom = my_atom //Give the gas a fingerprint
+
+ for (var/datum/reagent/reagent in my_atom.reagents.reagent_list) //make gas for reagents, has to be done this way, otherwise it never stops Exploding
+ R.add_reagent(reagent.id, reagent.volume/3) //Seems fine? I think I fixed the infinite explosion bug.
+
+ if (reagent.purity < 0.6)
+ ImpureTot = (ImpureTot + (1-reagent.purity)) / 2
+
+ if(pH < 4) //if acidic, make acid spray
+ R.add_reagent("fermiAcid", (volume/3))
+ if(R.reagent_list)
+ s.set_up(R, (volume/5), my_atom)
+ s.start()
+
+ if (pH > 10) //if alkaline, small explosion.
+ var/datum/effect_system/reagents_explosion/e = new()
+ e.set_up(round((volume/30)*(pH-9)), T, 0, 0)
+ e.start()
+
+ if(!ImpureTot == 0) //If impure, v.small emp (0.6 or less)
+ ImpureTot *= volume
+ var/empVol = CLAMP (volume/10, 0, 15)
+ empulse(T, empVol, ImpureTot/10, 1)
+
+ my_atom.reagents.clear_reagents() //just in case
+ return
+
+/datum/chemical_reaction/fermi/eigenstate
+ name = "Eigenstasium"
+ id = "eigenstate"
+ results = list("eigenstate" = 0.1)
+ required_reagents = list("bluespace" = 0.1, "stable_plasma" = 0.1, "sugar" = 0.1)
+ mix_message = "the reaction zaps suddenly!"
+ //FermiChem vars:
+ OptimalTempMin = 350 // Lower area of bell curve for determining heat based rate reactions
+ OptimalTempMax = 600 // Upper end for above
+ ExplodeTemp = 650 //Temperature at which reaction explodes
+ OptimalpHMin = 7 // Lowest value of pH determining pH a 1 value for pH based rate reactions (Plateu phase)
+ OptimalpHMax = 9 // Higest value for above
+ ReactpHLim = 5 // How far out pH wil react, giving impurity place (Exponential phase)
+ CatalystFact = 0 // How much the catalyst affects the reaction (0 = no catalyst)
+ CurveSharpT = 1.5 // How sharp the temperature exponential curve is (to the power of value)
+ CurveSharppH = 3 // How sharp the pH exponential curve is (to the power of value)
+ ThermicConstant = 10 //Temperature change per 1u produced
+ HIonRelease = -0.02 //pH change per 1u reaction
+ RateUpLim = 3 //Optimal/max rate possible if all conditions are perfect
+ FermiChem = TRUE//If the chemical uses the Fermichem reaction mechanics
+ FermiExplode = FALSE //If the chemical explodes in a special way
+ PurityMin = 0.4 //The minimum purity something has to be above, otherwise it explodes.
+
+/datum/chemical_reaction/fermi/eigenstate/FermiFinish(datum/reagents/holder, var/atom/my_atom)//Strange how this doesn't work but the other does.
+ if(!locate(/datum/reagent/fermi/eigenstate) in my_atom.reagents.reagent_list)
+ return
+ var/turf/open/location = get_turf(my_atom)
+ var/datum/reagent/fermi/eigenstate/E = locate(/datum/reagent/fermi/eigenstate) in my_atom.reagents.reagent_list
+ if(location)
+ E.location_created = location
+ E.data.["location_created"] = location
+
+
+//serum
+/datum/chemical_reaction/fermi/SDGF
+ name = "Synthetic-derived growth factor"
+ id = "SDGF"
+ results = list("SDGF" = 0.3)
+ required_reagents = list("stable_plasma" = 0.15, "clonexadone" = 0.15, "uranium" = 0.15, "synthflesh" = 0.15)
+ mix_message = "the reaction gives off a blorble!"
+ required_temp = 1
+ //FermiChem vars:
+ OptimalTempMin = 600 // Lower area of bell curve for determining heat based rate reactions
+ OptimalTempMax = 630 // Upper end for above
+ ExplodeTemp = 635 // Temperature at which reaction explodes
+ OptimalpHMin = 3 // Lowest value of pH determining pH a 1 value for pH based rate reactions (Plateu phase)
+ OptimalpHMax = 3.5 // Higest value for above
+ ReactpHLim = 2 // How far out pH wil react, giving impurity place (Exponential phase)
+ CatalystFact = 0 // How much the catalyst affects the reaction (0 = no catalyst)
+ CurveSharpT = 4 // How sharp the temperature exponential curve is (to the power of value)
+ CurveSharppH = 4 // How sharp the pH exponential curve is (to the power of value)
+ ThermicConstant = -10 // Temperature change per 1u produced
+ HIonRelease = 0.02 // pH change per 1u reaction (inverse for some reason)
+ RateUpLim = 1 // Optimal/max rate possible if all conditions are perfect
+ FermiChem = TRUE // If the chemical uses the Fermichem reaction mechanics
+ FermiExplode = TRUE // If the chemical explodes in a special way
+ PurityMin = 0.2
+
+/datum/chemical_reaction/fermi/SDGF/FermiExplode(datum/reagents, var/atom/my_atom, volume, temp, pH)//Spawns an angery teratoma!
+ var/turf/T = get_turf(my_atom)
+ var/mob/living/simple_animal/slime/S = new(T,"green")
+ S.damage_coeff = list(BRUTE = 0.9 , BURN = 2, TOX = 1, CLONE = 1, STAMINA = 0, OXY = 1)
+ S.name = "Living teratoma"
+ S.real_name = "Living teratoma"
+ S.rabid = 1//Make them an angery boi
+ S.color = "#810010"
+ my_atom.reagents.clear_reagents()
+ var/list/seen = viewers(8, get_turf(my_atom))
+ for(var/mob/M in seen)
+ to_chat(M, "The cells clump up into a horrifying tumour!")
+
+/datum/chemical_reaction/fermi/breast_enlarger
+ name = "Sucubus milk"
+ id = "breast_enlarger"
+ results = list("breast_enlarger" = 0.8)
+ required_reagents = list("salglu_solution" = 0.1, "milk" = 0.1, "synthflesh" = 0.2, "silicon" = 0.3, "aphro" = 0.3)
+ mix_message = "the reaction gives off a mist of milk."
+ //FermiChem vars:
+ OptimalTempMin = 200
+ OptimalTempMax = 800
+ ExplodeTemp = 900
+ OptimalpHMin = 6
+ OptimalpHMax = 10
+ ReactpHLim = 3
+ CatalystFact = 0
+ CurveSharpT = 2
+ CurveSharppH = 1
+ ThermicConstant = 1
+ HIonRelease = -0.1
+ RateUpLim = 5
+ FermiChem = TRUE
+ FermiExplode = TRUE
+ PurityMin = 0.1
+
+/datum/chemical_reaction/fermi/breast_enlarger/FermiFinish(datum/reagents/holder, var/atom/my_atom)
+ var/datum/reagent/fermi/breast_enlarger/BE = locate(/datum/reagent/fermi/breast_enlarger) in my_atom.reagents.reagent_list
+ var/cached_volume = BE.volume
+ if(BE.purity < 0.35)
+ holder.remove_reagent(src.id, cached_volume)
+ holder.add_reagent("BEsmaller", cached_volume)
+
+
+/datum/chemical_reaction/fermi/breast_enlarger/FermiExplode(datum/reagents, var/atom/my_atom, volume, temp, pH)
+ var/obj/item/organ/genital/breasts/B = new /obj/item/organ/genital/breasts(get_turf(my_atom))
+ var/list/seen = viewers(8, get_turf(my_atom))
+ for(var/mob/M in seen)
+ to_chat(M, "The reaction suddenly condenses, creating a pair of breasts!")
+ var/datum/reagent/fermi/breast_enlarger/BE = locate(/datum/reagent/fermi/breast_enlarger) in my_atom.reagents.reagent_list
+ B.size = ((BE.volume * BE.purity) / 10) //half as effective.
+ my_atom.reagents.clear_reagents()
+
+/datum/chemical_reaction/fermi/penis_enlarger
+ name = "Incubus draft"
+ id = "penis_enlarger"
+ results = list("penis_enlarger" = 0.8)
+ required_reagents = list("blood" = 0.5, "synthflesh" = 0.2, "carbon" = 0.2, "aphro" = 0.2, "salglu_solution" = 0.1,)
+ mix_message = "the reaction gives off a spicy mist."
+ //FermiChem vars:
+ OptimalTempMin = 200
+ OptimalTempMax = 800
+ ExplodeTemp = 900
+ OptimalpHMin = 2
+ OptimalpHMax = 6
+ ReactpHLim = 3
+ CatalystFact = 0
+ CurveSharpT = 2
+ CurveSharppH = 1
+ ThermicConstant = 1
+ HIonRelease = 0.1
+ RateUpLim = 5
+ FermiChem = TRUE
+ FermiExplode = TRUE
+ PurityMin = 0.1
+
+/datum/chemical_reaction/fermi/penis_enlarger/FermiExplode(datum/reagents, var/atom/my_atom, volume, temp, pH)
+ var/obj/item/organ/genital/penis/P = new /obj/item/organ/genital/penis(get_turf(my_atom))
+ var/list/seen = viewers(8, get_turf(my_atom))
+ for(var/mob/M in seen)
+ to_chat(M, "The reaction suddenly condenses, creating a penis!")
+ var/datum/reagent/fermi/penis_enlarger/PE = locate(/datum/reagent/fermi/penis_enlarger) in my_atom.reagents.reagent_list
+ P.length = ((PE.volume * PE.purity) / 10)//half as effective.
+ my_atom.reagents.clear_reagents()
+
+/datum/chemical_reaction/fermi/penis_enlarger/FermiFinish(datum/reagents/holder, var/atom/my_atom)
+ var/datum/reagent/fermi/penis_enlarger/PE = locate(/datum/reagent/fermi/penis_enlarger) in my_atom.reagents.reagent_list
+ var/cached_volume = PE.volume
+ if(PE.purity < 0.35)
+ holder.remove_reagent(src.id, cached_volume)
+ holder.add_reagent("PEsmaller", cached_volume)
+
+/datum/chemical_reaction/fermi/astral
+ name = "Astrogen"
+ id = "astral"
+ results = list("astral" = 0.5)
+ required_reagents = list("eigenstate" = 0.1, "plasma" = 0.3, "synaptizine" = 0.1, "aluminium" = 0.5)
+ //FermiChem vars:
+ OptimalTempMin = 700
+ OptimalTempMax = 800
+ ExplodeTemp = 1150
+ OptimalpHMin = 10
+ OptimalpHMax = 13
+ ReactpHLim = 2
+ CatalystFact = 0
+ CurveSharpT = 1
+ CurveSharppH = 1
+ ThermicConstant = 25
+ HIonRelease = 0.02
+ RateUpLim = 15
+ FermiChem = TRUE
+ FermiExplode = TRUE
+ PurityMin = 0.25
+
+
+/datum/chemical_reaction/fermi/enthrall/ //check this
+ name = "MKUltra"
+ id = "enthrall"
+ results = list("enthrall" = 0.5)
+ //required_reagents = list("iron" = 1, "iodine" = 1) Test vars
+ //required_reagents = list("cocoa" = 0.1, "astral" = 0.1, "mindbreaker" = 0.1, "psicodine" = 0.1, "happiness" = 0.1)
+ required_reagents = list("cocoa" = 0.1, "bluespace" = 0.1, "mindbreaker" = 0.1, "psicodine" = 0.1, "happiness" = 0.1) //TEMPORARY UNTIL HEADMINS GIVE THE OKAY FOR MK USE.
+ required_catalysts = list("blood" = 1)
+ mix_message = "the reaction gives off a burgundy plume of smoke!"
+ //FermiChem vars:
+ OptimalTempMin = 780
+ OptimalTempMax = 820
+ ExplodeTemp = 840
+ OptimalpHMin = 12
+ OptimalpHMax = 13
+ ReactpHLim = 2
+ //CatalystFact = 0
+ CurveSharpT = 0.5
+ CurveSharppH = 4
+ ThermicConstant = 15
+ HIonRelease = 0.1
+ RateUpLim = 1
+ FermiChem = TRUE
+ FermiExplode = TRUE
+ PurityMin = 0.2
+
+/datum/chemical_reaction/fermi/enthrall/FermiFinish(datum/reagents/holder, var/atom/my_atom)
+ var/datum/reagent/blood/B = locate(/datum/reagent/blood) in my_atom.reagents.reagent_list
+ var/datum/reagent/fermi/enthrall/E = locate(/datum/reagent/fermi/enthrall) in my_atom.reagents.reagent_list
+ if(!B)
+ return
+ if(!B.data)
+ var/list/seen = viewers(5, get_turf(my_atom))
+ for(var/mob/M in seen)
+ to_chat(M, "The reaction splutters and fails to react properly.") //Just in case
+ E.purity = 0
+ if (B.data.["gender"] == "female")
+ E.data.["creatorGender"] = "Mistress"
+ E.creatorGender = "Mistress"
+ else
+ E.data.["creatorGender"] = "Master"
+ E.creatorGender = "Master"
+ E.data["creatorName"] = B.data.["real_name"]
+ E.creatorName = B.data.["real_name"]
+ E.data.["creatorID"] = B.data.["ckey"]
+ E.creatorID = B.data.["ckey"]
+
+//So slimes can play too.
+/datum/chemical_reaction/fermi/enthrall/slime
+ required_catalysts = list("slimejelly" = 1)
+
+/datum/chemical_reaction/fermi/enthrall/slime/FermiFinish(datum/reagents/holder, var/atom/my_atom)
+ var/datum/reagent/toxin/slimejelly/B = locate(/datum/reagent/toxin/slimejelly) in my_atom.reagents.reagent_list//The one line change.
+ var/datum/reagent/fermi/enthrall/E = locate(/datum/reagent/fermi/enthrall) in my_atom.reagents.reagent_list
+ if(!B.data)
+ var/list/seen = viewers(5, get_turf(my_atom))
+ for(var/mob/M in seen)
+ to_chat(M, "The reaction splutters and fails to react.") //Just in case
+ E.purity = 0
+ if (B.data.["gender"] == "female")
+ E.data.["creatorGender"] = "Mistress"
+ E.creatorGender = "Mistress"
+ else
+ E.data.["creatorGender"] = "Master"
+ E.creatorGender = "Master"
+ E.data["creatorName"] = B.data.["real_name"]
+ E.creatorName = B.data.["real_name"]
+ E.data.["creatorID"] = B.data.["ckey"]
+ E.creatorID = B.data.["ckey"]
+
+/datum/chemical_reaction/fermi/enthrall/FermiExplode(datum/reagents, var/atom/my_atom, volume, temp, pH)
+ var/turf/T = get_turf(my_atom)
+ var/datum/reagents/R = new/datum/reagents(1000)
+ var/datum/effect_system/smoke_spread/chem/s = new()
+ R.add_reagent("enthrallExplo", volume)
+ s.set_up(R, volume/2, T)
+ s.start()
+ my_atom.reagents.clear_reagents()
+
+/datum/chemical_reaction/fermi/hatmium // done
+ name = "Hat growth serum"
+ id = "hatmium"
+ results = list("hatmium" = 0.5)
+ required_reagents = list("ethanol" = 0.1, "nutriment" = 0.3, "cooking_oil" = 0.2, "iron" = 0.1, "gold" = 0.3)
+ //mix_message = ""
+ //FermiChem vars:
+ OptimalTempMin = 500
+ OptimalTempMax = 700
+ ExplodeTemp = 750
+ OptimalpHMin = 2
+ OptimalpHMax = 5
+ ReactpHLim = 3
+ //CatalystFact = 0 //To do 1
+ CurveSharpT = 8
+ CurveSharppH = 0.5
+ ThermicConstant = -2
+ HIonRelease = -0.1
+ RateUpLim = 2
+ FermiChem = TRUE
+ FermiExplode = TRUE
+ PurityMin = 0.5
+
+/datum/chemical_reaction/fermi/hatmium/FermiExplode(src, var/atom/my_atom, volume, temp, pH)
+ var/obj/item/clothing/head/hattip/hat = new /obj/item/clothing/head/hattip(get_turf(my_atom))
+ hat.animate_atom_living()
+ var/list/seen = viewers(8, get_turf(my_atom))
+ for(var/mob/M in seen)
+ to_chat(M, "The makes an off sounding pop, as a hat suddenly climbs out of the beaker!")
+ my_atom.reagents.clear_reagents()
+
+/datum/chemical_reaction/fermi/furranium
+ name = "Furranium"
+ id = "furranium"
+ results = list("furranium" = 0.5)
+ required_reagents = list("aphro" = 0.1, "moonsugar" = 0.1, "silver" = 0.2, "salglu_solution" = 0.1)
+ mix_message = "You think you can hear a howl come from the beaker."
+ //FermiChem vars:
+ OptimalTempMin = 350
+ OptimalTempMax = 600
+ ExplodeTemp = 700
+ OptimalpHMin = 8
+ OptimalpHMax = 10
+ ReactpHLim = 2
+ //CatalystFact = 0 //To do 1
+ CurveSharpT = 2
+ CurveSharppH = 0.5
+ ThermicConstant = -10
+ HIonRelease = -0.1
+ RateUpLim = 2
+ FermiChem = TRUE
+ PurityMin = 0.3
+
+//FOR INSTANT REACTIONS - DO NOT MULTIPLY LIMIT BY 10.
+//There's a weird rounding error or something ugh.
+
+//Nano-b-gone
+/datum/chemical_reaction/fermi/nanite_b_gone//done test
+ name = "Naninte bain"
+ id = "nanite_b_gone"
+ results = list("nanite_b_gone" = 4)
+ required_reagents = list("synthflesh" = 1, "uranium" = 1, "iron" = 1, "salglu_solution" = 1)
+ mix_message = "the reaction gurgles, encapsulating the reagents in flesh before the emp can be set off."
+ required_temp = 450//To force fermireactions before EMP.
+ //FermiChem vars:
+ OptimalTempMin = 500
+ OptimalTempMax = 600
+ ExplodeTemp = 700
+ OptimalpHMin = 6
+ OptimalpHMax = 6.25
+ ReactpHLim = 3
+ //CatalystFact = 0 //To do 1
+ CurveSharpT = 0
+ CurveSharppH = 1
+ ThermicConstant = 5
+ HIonRelease = 0.01
+ RateUpLim = 1
+ FermiChem = TRUE
+
+/datum/chemical_reaction/fermi/acidic_buffer//done test
+ name = "Acetic acid buffer"
+ id = "acidic_buffer"
+ results = list("acidic_buffer" = 2) //acetic acid
+ required_reagents = list("salglu_solution" = 0.2, "ethanol" = 0.6, "oxygen" = 0.6, "water" = 0.6)
+ //FermiChem vars:
+ OptimalTempMin = 250
+ OptimalTempMax = 500
+ ExplodeTemp = 9999 //check to see overflow doesn't happen!
+ OptimalpHMin = 2
+ OptimalpHMax = 6
+ ReactpHLim = 0
+ //CatalystFact = 0 //To do 1
+ CurveSharpT = 4
+ CurveSharppH = 0
+ ThermicConstant = 0
+ HIonRelease = -0.01
+ RateUpLim = 20
+ FermiChem = TRUE
+
+
+/datum/chemical_reaction/fermi/acidic_buffer/FermiFinish(datum/reagents/holder, var/atom/my_atom) //might need this
+ if(!locate(/datum/reagent/fermi/acidic_buffer) in my_atom.reagents.reagent_list)
+ return
+ var/datum/reagent/fermi/acidic_buffer/Fa = locate(/datum/reagent/fermi/acidic_buffer) in my_atom.reagents.reagent_list
+ Fa.data = 0.1//setting it to 0 means byond thinks it's not there.
+
+/datum/chemical_reaction/fermi/basic_buffer//done test
+ name = "Ethyl Ethanoate buffer"
+ id = "basic_buffer"
+ results = list("basic_buffer" = 1.5)
+ required_reagents = list("acidic_buffer" = 0.5, "ethanol" = 0.5, "water" = 0.5)
+ required_catalysts = list("sacid" = 1) //vagely acetic
+ //FermiChem vars:x
+ OptimalTempMin = 250
+ OptimalTempMax = 500
+ ExplodeTemp = 9999 //check to see overflow doesn't happen!
+ OptimalpHMin = 5
+ OptimalpHMax = 12
+ ReactpHLim = 0
+ //CatalystFact = 0 //To do 1
+ CurveSharpT = 4
+ CurveSharppH = 0
+ ThermicConstant = 0
+ HIonRelease = 0.01
+ RateUpLim = 15
+ FermiChem = TRUE
+
+
+/datum/chemical_reaction/fermi/basic_buffer/FermiFinish(datum/reagents/holder, var/atom/my_atom) //might need this
+ if(!locate(/datum/reagent/fermi/basic_buffer) in my_atom.reagents.reagent_list)
+ return
+ var/datum/reagent/fermi/basic_buffer/Fb = locate(/datum/reagent/fermi/basic_buffer) in my_atom.reagents.reagent_list
+ Fb.data = 14
+
+//secretcatchemcode, shh!! Of couse I hide it amongst cats. Though, I moved it with your requests.
+//I'm not trying to be sneaky, I'm trying to keep it a secret!
+//I don't know how to do hidden chems like Aurora
+//ChemReactionVars:
+/datum/chemical_reaction/fermi/secretcatchem //DONE
+ name = "secretcatchem"
+ id = "secretcatchem"
+ results = list("secretcatchem" = 0.5)
+ required_reagents = list("stable_plasma" = 0.1, "sugar" = 0.1, "cream" = 0.1, "clonexadone" = 0.1)//Yes this will make a plushie if you don't lucky guess. It'll eat all your reagents too.
+ required_catalysts = list("SDGF" = 1)
+ required_temp = 600
+ mix_message = "the reaction gives off a meow!"
+ mix_sound = "modular_citadel/sound/voice/merowr.ogg"
+ //FermiChem vars:
+ OptimalTempMin = 650
+ OptimalpHMin = 0
+ ReactpHLim = 2
+ CurveSharpT = 0
+ CurveSharppH = 0
+ ThermicConstant = 0
+ HIonRelease = 0
+ RateUpLim = 0.1
+ FermiChem = TRUE
+ FermiExplode = FALSE
+ PurityMin = 0.2
+
+/datum/chemical_reaction/fermi/secretcatchem/New()
+ //rand doesn't seem to work with n^-e
+ OptimalTempMin += rand(-100, 100)
+ OptimalTempMax = (OptimalTempMin+rand(20, 200))
+ ExplodeTemp = (OptimalTempMax+rand(20, 200))
+ OptimalpHMin += rand(1, 10)
+ OptimalpHMax = (OptimalpHMin + rand(1, 5))
+ ReactpHLim += rand(-1.5, 2.5)
+ CurveSharpT += (rand(1, 500)/100)
+ CurveSharppH += (rand(1, 500)/100)
+ ThermicConstant += rand(-20, 20)
+ HIonRelease += (rand(-25, 25)/100)
+ RateUpLim += (rand(1, 1000)/100)
+ PurityMin += (rand(-1, 1)/10)
+ var/additions = list("aluminium", "silver", "gold", "plasma", "silicon", "uranium", "milk")
+ required_reagents[pick(additions)] = rand(0.1, 0.5)//weird
+
+/datum/chemical_reaction/fermi/secretcatchem/FermiFinish(datum/reagents/holder, var/atom/my_atom)
+ SSblackbox.record_feedback("tally", "catgirlium")//log
+
+/datum/chemical_reaction/fermi/secretcatchem/FermiExplode(datum/reagents, var/atom/my_atom, volume, temp, pH)
+ var/mob/living/simple_animal/pet/cat/custom_cat/catto = new(get_turf(my_atom))
+ var/list/seen = viewers(8, get_turf(my_atom))
+ for(var/mob/M in seen)
+ to_chat(M, "The reaction suddenly gives out a meow, condensing into a chemcat!")//meow!
+ playsound(get_turf(my_atom), 'modular_citadel/sound/voice/merowr.ogg', 50, 1, -1)
+ catto.name = "Chemcat"
+ catto.desc = "A cute chem cat, created by a lot of compicated and confusing chemistry!"
+ catto.color = "#770000"
+ my_atom.reagents.remove_all(5)
+
+/datum/chemical_reaction/fermi/yamerol//done test
+ name = "Yamerol"
+ id = "yamerol"
+ results = list("yamerol" = 1.5)
+ required_reagents = list("perfluorodecalin" = 0.5, "salbutamol" = 0.5, "water" = 0.5)
+ //FermiChem vars:
+ OptimalTempMin = 300
+ OptimalTempMax = 500
+ ExplodeTemp = 800 //check to see overflow doesn't happen!
+ OptimalpHMin = 6.8
+ OptimalpHMax = 7.2
+ ReactpHLim = 4
+ //CatalystFact = 0 //To do 1
+ CurveSharpT = 5
+ CurveSharppH = 0.5
+ ThermicConstant = -15
+ HIonRelease = 0.1
+ RateUpLim = 2
+ FermiChem = TRUE
diff --git a/modular_citadel/code/modules/reagents/objects/clothes.dm b/modular_citadel/code/modules/reagents/objects/clothes.dm
new file mode 100644
index 0000000000..708f1f59f8
--- /dev/null
+++ b/modular_citadel/code/modules/reagents/objects/clothes.dm
@@ -0,0 +1,69 @@
+//Fermiclothes!
+//Clothes made from FermiChem
+
+/obj/item/clothing/head/hattip //I wonder if anyone else has played cryptworlds
+ name = "Sythetic hat"
+ icon = 'icons/obj/clothing/hats.dmi'
+ icon_state = "cowboy"
+ desc = "A sythesized hat, you can't seem to take it off. And tips their hat."
+ armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0)
+ //item_flags = NODROP //Tips their hat!
+
+/obj/item/clothing/head/hattip/attack_hand(mob/user)
+ if(iscarbon(user))
+ var/mob/living/carbon/C = user
+ if(is_ninja(C))
+ to_chat(C, "Using your superior ninja reflexes, you take the hat off before tipping.")
+ return ..()
+
+ if(src == C.head)
+ C.emote("me",1,"tips their hat.",TRUE)
+ return
+ else
+ user.emote("me",1,"admires such a spiffy hat.",TRUE)
+ return ..()
+
+/obj/item/clothing/head/hattip/MouseDrop(atom/over_object)
+ //You sure do love tipping your hat.
+ if(usr)
+ var/mob/living/carbon/C = usr
+ if(is_ninja(C))
+ to_chat(C, "Using your superior ninja reflexes, you take the hat off before tipping.")
+ return ..()
+
+ if(src == C.head)
+ C.emote("me",1,"tips their hat.",TRUE)
+ return
+ ..()
+
+/obj/item/clothing/head/hattip/equipped(mob/M, slot)
+ . = ..()
+ if (slot == SLOT_HEAD)
+ RegisterSignal(M, COMSIG_MOB_SAY, .proc/handle_speech)
+ else
+ UnregisterSignal(M, COMSIG_MOB_SAY)
+
+/obj/item/clothing/head/hattip/dropped(mob/M)
+ . = ..()
+ UnregisterSignal(M, COMSIG_MOB_SAY)
+
+/obj/item/clothing/head/hattip/proc/handle_speech(datum/source, mob/speech_args)
+ var/message = speech_args[SPEECH_MESSAGE]
+ var/mob/living/carbon/C = get_wearer()//user
+ var/obj/item/organ/tongue/T = C.getorganslot(ORGAN_SLOT_TONGUE)
+ if (T.name == "fluffy tongue")
+ if(prob(0.01))
+ message += "\" and tips their hat. \"swpy's sappin' my chem dispwencer uwu!!"
+ else
+ message += "\" and tips their hat. \"[pick("weehaw!", "bwoy howdy.", "dawn tuutin'.", "weww don't that beat aww.", "whoooowee, wouwd ya wook at that!", "whoooowee! makin' bwacon!", "cweam gwavy!", "yippekeeyah-heeyapeeah-kwayoh!", "mwove 'em uut!", "gwiddy up!")]"
+ speech_args[SPEECH_MESSAGE] = trim(message)
+ return
+ if(prob(0.01))
+ message += "\" and tips their hat. \"Spy's sappin' my chem dispenser!"//How did I not think of this earlier
+ message_admins("I really appreciate all the hard work you put into adminning citadel, I hope you're all having a good day and I hope this hidden and rare message_admins brightens up your day.")
+ else
+ message += "\" and tips their hat. \"[pick("Yeehaw!", "Boy howdy.", "Darn tootin'.", "Well don't that beat all.", "Whoooowee, would ya look at that!", "Whoooowee! Makin' bacon!", "Cream Gravy!", "Yippekeeyah-heeyapeeah-kayoh!", "Move 'em out!", "Giddy up!")]"
+ speech_args[SPEECH_MESSAGE] = trim(message)
+
+/obj/item/clothing/head/hattip/proc/get_wearer()
+ return loc
diff --git a/modular_citadel/code/modules/reagents/objects/items.dm b/modular_citadel/code/modules/reagents/objects/items.dm
new file mode 100644
index 0000000000..02f2db7c1e
--- /dev/null
+++ b/modular_citadel/code/modules/reagents/objects/items.dm
@@ -0,0 +1,147 @@
+/obj/item/fermichem/pHbooklet
+ name = "pH indicator booklet"
+ desc = "A booklet containing paper soaked in universal indicator."
+ icon_state = "pHbooklet"
+ icon = 'modular_citadel/icons/obj/FermiChem.dmi'
+ item_flags = NOBLUDGEON
+ var/numberOfPages = 50
+ resistance_flags = FLAMMABLE
+ w_class = WEIGHT_CLASS_TINY
+
+//A little janky with pockets
+/obj/item/fermichem/pHbooklet/attack_hand(mob/user)
+ if(user.get_held_index_of_item(src))//Does this check pockets too..?
+ if(numberOfPages == 50)
+ icon_state = "pHbookletOpen"
+ if(numberOfPages >= 1)
+ var/obj/item/fermichem/pHpaper/P = new /obj/item/fermichem/pHpaper
+ P.add_fingerprint(user)
+ P.forceMove(user.loc)
+ user.put_in_active_hand(P)
+ to_chat(user, "You take [P] out of \the [src].")
+ numberOfPages--
+ playsound(user.loc, 'sound/items/poster_ripped.ogg', 50, 1)
+ add_fingerprint(user)
+ if(numberOfPages == 0)
+ icon_state = "pHbookletEmpty"
+ return
+ else
+ to_chat(user, "[src] is empty!")
+ add_fingerprint(user)
+ return
+ . = ..()
+ if(. & COMPONENT_NO_INTERACT)
+ return
+ var/I = user.get_active_held_item()
+ if(!I)
+ user.put_in_active_hand(src)
+
+/obj/item/fermichem/pHbooklet/MouseDrop()
+ var/mob/living/user = usr
+ if(numberOfPages >= 1)
+ var/obj/item/fermichem/pHpaper/P = new /obj/item/fermichem/pHpaper
+ P.add_fingerprint(user)
+ P.forceMove(user)
+ user.put_in_active_hand(P)
+ to_chat(user, "You take [P] out of \the [src].")
+ numberOfPages--
+ playsound(user.loc, 'sound/items/poster_ripped.ogg', 50, 1)
+ add_fingerprint(user)
+ if(numberOfPages == 0)
+ icon_state = "pHbookletEmpty"
+ return
+ else
+ to_chat(user, "[src] is empty!")
+ add_fingerprint(user)
+ return
+ ..()
+
+/obj/item/fermichem/pHpaper
+ name = "pH indicator strip"
+ desc = "A piece of paper that will change colour depending on the pH of a solution."
+ icon_state = "pHpaper"
+ icon = 'modular_citadel/icons/obj/FermiChem.dmi'
+ item_flags = NOBLUDGEON
+ color = "#f5c352"
+ var/used = FALSE
+ resistance_flags = FLAMMABLE
+ w_class = WEIGHT_CLASS_TINY
+
+/obj/item/fermichem/pHpaper/afterattack(obj/item/reagent_containers/cont, mob/user, proximity)
+ if(!istype(cont))
+ return
+ if(used == TRUE)
+ to_chat(user, "[user] has already been used!")
+ return
+ if(!LAZYLEN(cont.reagents.reagent_list))
+ return
+ switch(round(cont.reagents.pH, 1))
+ if(14 to INFINITY)
+ color = "#462c83"
+ if(13 to 14)
+ color = "#63459b"
+ if(12 to 13)
+ color = "#5a51a2"
+ if(11 to 12)
+ color = "#3853a4"
+ if(10 to 11)
+ color = "#3f93cf"
+ if(9 to 10)
+ color = "#0bb9b7"
+ if(8 to 9)
+ color = "#23b36e"
+ if(7 to 8)
+ color = "#3aa651"
+ if(6 to 7)
+ color = "#4cb849"
+ if(5 to 6)
+ color = "#b5d335"
+ if(4 to 5)
+ color = "#f7ec1e"
+ if(3 to 4)
+ color = "#fbc314"
+ if(2 to 3)
+ color = "#f26724"
+ if(1 to 2)
+ color = "#ef1d26"
+ if(-INFINITY to 1)
+ color = "#c6040c"
+ desc += " The paper looks to be around a pH of [round(cont.reagents.pH, 1)]"
+ used = TRUE
+
+/obj/item/fermichem/pHmeter
+ name = "Chemistry Analyser"
+ desc = "A a electrode attached to a small circuit box that will tell you the pH of a solution. The screen currently displays nothing."
+ icon_state = "pHmeter"
+ icon = 'modular_citadel/icons/obj/FermiChem.dmi'
+ resistance_flags = FLAMMABLE
+ w_class = WEIGHT_CLASS_TINY
+ var/scanmode = 1
+
+/obj/item/fermichem/pHmeter/attack_self(mob/user)
+ if(!scanmode)
+ to_chat(user, "You switch the chemical analyzer to give a detailed report.")
+ scanmode = 1
+ else
+ to_chat(user, "You switch the chemical analyzer to give a reduced report.")
+ scanmode = 0
+
+/obj/item/fermichem/pHmeter/afterattack(atom/A, mob/user, proximity)
+ . = ..()
+ if(!istype(A, /obj/item/reagent_containers))
+ return
+ var/obj/item/reagent_containers/cont = A
+ if(LAZYLEN(cont.reagents.reagent_list) == null)
+ return
+ var/out_message
+ to_chat(user, "The chemistry meter beeps and displays:")
+ out_message += "Total volume: [round(cont.volume, 0.01)] Total pH: [round(cont.reagents.pH, 0.1)]\n"
+ if(cont.reagents.fermiIsReacting)
+ out_message += "A reaction appears to be occuring currently.\n"
+ out_message += "Chemicals found in the beaker:\n"
+ for(var/datum/reagent/R in cont.reagents.reagent_list)
+ out_message += "[R.volume]u of [R.name], Purity: [R.purity], [(scanmode?"[(R.overdose_threshold?"Overdose: [R.overdose_threshold]u, ":"")][(R.addiction_threshold?"Addiction: [R.addiction_threshold]u, ":"")]Base pH: [R.pH].":".")]\n"
+ if(scanmode)
+ out_message += "Analysis: [R.description]\n"
+ to_chat(user, "[out_message]")
+ desc = "An electrode attached to a small circuit box that will analyse a beaker. It can be toggled to give a reduced or extended report. The screen currently displays [round(cont.reagents.pH, 0.1)]."
diff --git a/modular_citadel/code/modules/reagents/reagent container/hypospraymkii.dm b/modular_citadel/code/modules/reagents/reagent container/hypospraymkii.dm
index bee56cde15..6cd9e1badc 100755
--- a/modular_citadel/code/modules/reagents/reagent container/hypospraymkii.dm
+++ b/modular_citadel/code/modules/reagents/reagent container/hypospraymkii.dm
@@ -50,6 +50,9 @@
/obj/item/hypospray/mkii/tricord
start_vial = /obj/item/reagent_containers/glass/bottle/vial/small/preloaded/tricord
+/obj/item/hypospray/mkii/enlarge
+ spawnwithvial = FALSE
+
/obj/item/hypospray/mkii/CMO
name = "hypospray mk.II deluxe"
allowed_containers = list(/obj/item/reagent_containers/glass/bottle/vial/tiny, /obj/item/reagent_containers/glass/bottle/vial/small, /obj/item/reagent_containers/glass/bottle/vial/large)
diff --git a/modular_citadel/code/modules/reagents/reagent container/hypovial.dm b/modular_citadel/code/modules/reagents/reagent container/hypovial.dm
index 4b7972d0a7..c1e0d6ff01 100755
--- a/modular_citadel/code/modules/reagents/reagent container/hypovial.dm
+++ b/modular_citadel/code/modules/reagents/reagent container/hypovial.dm
@@ -14,7 +14,8 @@
"green hypovial" = "hypovial-a",
"orange hypovial" = "hypovial-k",
"purple hypovial" = "hypovial-p",
- "black hypovial" = "hypovial-t"
+ "black hypovial" = "hypovial-t",
+ "pink hypovial" = "hypovial-pink"
)
always_reskinnable = TRUE
@@ -136,6 +137,16 @@
icon_state = "hypovial"
comes_with = list("tricordrazine" = 30)
+/obj/item/reagent_containers/glass/bottle/vial/small/preloaded/breastreduction
+ name = "pink hypovial (breast treatment)"
+ icon_state = "hypovial-pink"
+ comes_with = list("BEsmaller_hypo" = 30)
+
+/obj/item/reagent_containers/glass/bottle/vial/small/preloaded/penisreduction
+ name = "pink hypovial (penis treatment)"
+ icon_state = "hypovial-pink"
+ comes_with = list("PEsmaller_hypo" = 30)
+
/obj/item/reagent_containers/glass/bottle/vial/large/preloaded/CMO
name = "deluxe hypovial"
icon_state = "hypoviallarge-cmos"
diff --git a/modular_citadel/icons/firstaid.dmi b/modular_citadel/icons/firstaid.dmi
index 693f5a2fa4..e7ac378222 100644
Binary files a/modular_citadel/icons/firstaid.dmi and b/modular_citadel/icons/firstaid.dmi differ
diff --git a/modular_citadel/icons/obj/FermiChem.dmi b/modular_citadel/icons/obj/FermiChem.dmi
new file mode 100644
index 0000000000..de7a86d574
Binary files /dev/null and b/modular_citadel/icons/obj/FermiChem.dmi differ
diff --git a/modular_citadel/icons/obj/genitals/breasts.dmi b/modular_citadel/icons/obj/genitals/breasts.dmi
index 8c76891396..d70207ee69 100644
Binary files a/modular_citadel/icons/obj/genitals/breasts.dmi and b/modular_citadel/icons/obj/genitals/breasts.dmi differ
diff --git a/modular_citadel/icons/obj/genitals/breasts_onmob.dmi b/modular_citadel/icons/obj/genitals/breasts_onmob.dmi
index c4b75b1172..371041b6f4 100644
Binary files a/modular_citadel/icons/obj/genitals/breasts_onmob.dmi and b/modular_citadel/icons/obj/genitals/breasts_onmob.dmi differ
diff --git a/modular_citadel/icons/obj/genitals/penis.dmi b/modular_citadel/icons/obj/genitals/penis.dmi
index 397fa335e5..517758b248 100644
Binary files a/modular_citadel/icons/obj/genitals/penis.dmi and b/modular_citadel/icons/obj/genitals/penis.dmi differ
diff --git a/modular_citadel/icons/obj/genitals/penis_onmob.dmi b/modular_citadel/icons/obj/genitals/penis_onmob.dmi
index 434f171e22..416965415d 100644
Binary files a/modular_citadel/icons/obj/genitals/penis_onmob.dmi and b/modular_citadel/icons/obj/genitals/penis_onmob.dmi differ
diff --git a/modular_citadel/icons/obj/genitals/taur_penis_onmob.dmi b/modular_citadel/icons/obj/genitals/taur_penis_onmob.dmi
index 3debbd0047..ae2339e2e0 100644
Binary files a/modular_citadel/icons/obj/genitals/taur_penis_onmob.dmi and b/modular_citadel/icons/obj/genitals/taur_penis_onmob.dmi differ
diff --git a/modular_citadel/icons/obj/genitals/vagina_onmob.dmi b/modular_citadel/icons/obj/genitals/vagina_onmob.dmi
index c4cf891eac..ab71b22e3b 100644
Binary files a/modular_citadel/icons/obj/genitals/vagina_onmob.dmi and b/modular_citadel/icons/obj/genitals/vagina_onmob.dmi differ
diff --git a/modular_citadel/icons/obj/vial.dmi b/modular_citadel/icons/obj/vial.dmi
index bc282ab1d3..8d1fefe470 100755
Binary files a/modular_citadel/icons/obj/vial.dmi and b/modular_citadel/icons/obj/vial.dmi differ
diff --git a/sound/FermiChem/SoundSources.txt b/sound/FermiChem/SoundSources.txt
new file mode 100644
index 0000000000..bd45f866f5
--- /dev/null
+++ b/sound/FermiChem/SoundSources.txt
@@ -0,0 +1,10 @@
+heatmelt.ogg - from https://freesound.org/people/toiletrolltube/sounds/181483/
+ from https://freesound.org/people/MrVasLuk/sounds/304619/
+ from https://freesound.org/people/Benboncan/sounds/74899/
+ from bubbles2.ogg
+heatacid.ogg - from https://freesound.org/people/klankbeeld/sounds/233697/
+ from bubbles2.ogg
+ from fuse.ogg
+bufferadd.ogg- https://freesound.org/people/toiletrolltube/sounds/181483/
+
+Work is licensed under the Creative Commons and Attribution License.
\ No newline at end of file
diff --git a/sound/FermiChem/acidmelt.ogg b/sound/FermiChem/acidmelt.ogg
new file mode 100644
index 0000000000..bef257be55
Binary files /dev/null and b/sound/FermiChem/acidmelt.ogg differ
diff --git a/sound/FermiChem/bufferadd.ogg b/sound/FermiChem/bufferadd.ogg
new file mode 100644
index 0000000000..31bc440425
Binary files /dev/null and b/sound/FermiChem/bufferadd.ogg differ
diff --git a/sound/FermiChem/heatmelt.ogg b/sound/FermiChem/heatmelt.ogg
new file mode 100644
index 0000000000..87dab3a18f
Binary files /dev/null and b/sound/FermiChem/heatmelt.ogg differ
diff --git a/tgstation.dme b/tgstation.dme
index d48708f4b9..b208563fed 100755
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -274,6 +274,7 @@
#include "code\controllers\subsystem\vore.dm"
#include "code\controllers\subsystem\vote.dm"
#include "code\controllers\subsystem\weather.dm"
+#include "code\controllers\subsystem\processing\chemistry.dm"
#include "code\controllers\subsystem\processing\circuit.dm"
#include "code\controllers\subsystem\processing\fastprocess.dm"
#include "code\controllers\subsystem\processing\fields.dm"
@@ -327,6 +328,7 @@
#include "code\datums\actions\beam_rifle.dm"
#include "code\datums\actions\ninja.dm"
#include "code\datums\brain_damage\brain_trauma.dm"
+#include "code\datums\brain_damage\hypnosis.dm"
#include "code\datums\brain_damage\imaginary_friend.dm"
#include "code\datums\brain_damage\mild.dm"
#include "code\datums\brain_damage\phobia.dm"
@@ -2083,6 +2085,7 @@
#include "code\modules\mob\living\silicon\robot\robot_movement.dm"
#include "code\modules\mob\living\silicon\robot\say.dm"
#include "code\modules\mob\living\simple_animal\animal_defense.dm"
+#include "code\modules\mob\living\simple_animal\astral.dm"
#include "code\modules\mob\living\simple_animal\constructs.dm"
#include "code\modules\mob\living\simple_animal\corpse.dm"
#include "code\modules\mob\living\simple_animal\damage_procs.dm"
@@ -2841,11 +2844,14 @@
#include "modular_citadel\code\datums\components\material_container.dm"
#include "modular_citadel\code\datums\components\phantomthief.dm"
#include "modular_citadel\code\datums\components\souldeath.dm"
+#include "modular_citadel\code\datums\mood_events\chem_events.dm"
#include "modular_citadel\code\datums\mood_events\generic_negative_events.dm"
#include "modular_citadel\code\datums\mood_events\generic_positive_events.dm"
#include "modular_citadel\code\datums\mood_events\moodular.dm"
#include "modular_citadel\code\datums\mutations\hulk.dm"
+#include "modular_citadel\code\datums\status_effects\chems.dm"
#include "modular_citadel\code\datums\status_effects\debuffs.dm"
+#include "modular_citadel\code\datums\traits\negative.dm"
#include "modular_citadel\code\datums\traits\neutral.dm"
#include "modular_citadel\code\datums\wires\airlock.dm"
#include "modular_citadel\code\datums\wires\autoylathe.dm"
@@ -3027,7 +3033,17 @@
#include "modular_citadel\code\modules\projectiles\guns\energy\laser.dm"
#include "modular_citadel\code\modules\projectiles\projectile\energy.dm"
#include "modular_citadel\code\modules\projectiles\projectiles\reusable.dm"
+#include "modular_citadel\code\modules\reagents\chemistry\reagents\astrogen.dm"
+#include "modular_citadel\code\modules\reagents\chemistry\reagents\eigentstasium.dm"
+#include "modular_citadel\code\modules\reagents\chemistry\reagents\enlargement.dm"
+#include "modular_citadel\code\modules\reagents\chemistry\reagents\fermi_reagents.dm"
+#include "modular_citadel\code\modules\reagents\chemistry\reagents\healing.dm"
+#include "modular_citadel\code\modules\reagents\chemistry\reagents\MKUltra.dm"
#include "modular_citadel\code\modules\reagents\chemistry\reagents\other_reagents.dm"
+#include "modular_citadel\code\modules\reagents\chemistry\reagents\SDGF.dm"
+#include "modular_citadel\code\modules\reagents\chemistry\recipes\fermi.dm"
+#include "modular_citadel\code\modules\reagents\objects\clothes.dm"
+#include "modular_citadel\code\modules\reagents\objects\items.dm"
#include "modular_citadel\code\modules\reagents\reagent container\cit_kegs.dm"
#include "modular_citadel\code\modules\reagents\reagent container\hypospraymkii.dm"
#include "modular_citadel\code\modules\reagents\reagent container\hypovial.dm"
diff --git a/tgui/src/interfaces/chem_dispenser.ract b/tgui/src/interfaces/chem_dispenser.ract
index 67671c5cd4..50443ebfea 100644
--- a/tgui/src/interfaces/chem_dispenser.ract
+++ b/tgui/src/interfaces/chem_dispenser.ract
@@ -34,6 +34,7 @@
{{#if data.isBeakerLoaded}}
{{Math.round(adata.beakerCurrentVolume)}}/{{data.beakerMaxVolume}} Units
+ pH: {{Math.round(adata.beakerCurrentpH*adata.partRating)/adata.partRating}}
{{#each adata.beakerContents}}
{{Math.fixed(volume, 2)}} units of {{name}}
diff --git a/tgui/src/interfaces/chem_heater.ract b/tgui/src/interfaces/chem_heater.ract
index 9093804cb7..ce4924356a 100644
--- a/tgui/src/interfaces/chem_heater.ract
+++ b/tgui/src/interfaces/chem_heater.ract
@@ -16,9 +16,14 @@
{{#if data.isBeakerLoaded}}
Temperature: {{Math.round(adata.currentTemp)}} K
+
+ pH: {{Math.round(adata.currentpH*adata.partRating)/adata.partRating}}
{{#each adata.beakerContents}}
- {{Math.fixed(volume, 2)}} units of {{name}}
+ {{Math.fixed(volume, 2)}} units of {{name}}
+ {{#if data.showPurity}}
+ Purity: {{Math.fixed(purity, 2)}}
+ {{/if}}
{{else}}
Beaker Empty
{{/each}}
diff --git a/tgui/src/interfaces/chem_master.ract b/tgui/src/interfaces/chem_master.ract
index 83122de6f7..b1b5b5cce6 100644
--- a/tgui/src/interfaces/chem_master.ract
+++ b/tgui/src/interfaces/chem_master.ract
@@ -112,6 +112,29 @@
Addiction Threshold:
{{data.analyzeVars.addicD}}
+ {{#if data.fermianalyze}}
+ Minumum Reaction Temperature:
+ {{data.analyzeVars.minTemp}}K
+
+ Optimal Reaction Temperature:
+ {{data.analyzeVars.maxTemp}}K
+
+ Explosion Reaction Temperature:
+ {{data.analyzeVars.eTemp}}K
+
+ Optimal reaction pH:
+ {{data.analyzeVars.pHpeak}}
+
+ Current Purity:
+ {{data.analyzeVars.purityF}}
+
+ Inverse Purity Threshold:
+ {{data.analyzeVars.inverseRatioF}}
+
+ Explosion Purity Threshold:
+ {{data.analyzeVars.purityE}}
+
+ {{/if}}
Back