diff --git a/code/__DEFINES/components.dm b/code/__DEFINES/components.dm
index 40aea24105..d387575235 100644
--- a/code/__DEFINES/components.dm
+++ b/code/__DEFINES/components.dm
@@ -118,7 +118,15 @@
#define COMPONENT_CANCEL_THROW 1
#define COMSIG_MOVABLE_POST_THROW "movable_post_throw" //from base of atom/movable/throw_at(): (datum/thrownthing, spin)
#define COMSIG_MOVABLE_Z_CHANGED "movable_ztransit" //from base of atom/movable/onTransitZ(): (old_z, new_z)
+#define COMSIG_MOVABLE_SECLUDED_LOCATION "movable_secluded" //called when the movable is placed in an unaccessible area, used for stationloving: ()
#define COMSIG_MOVABLE_HEAR "movable_hear" //from base of atom/movable/Hear(): (message, atom/movable/speaker, message_language, raw_message, radio_freq, list/spans, message_mode)
+ #define HEARING_MESSAGE 1
+ #define HEARING_SPEAKER 2
+// #define HEARING_LANGUAGE 3
+ #define HEARING_RAW_MESSAGE 4
+ /* #define HEARING_RADIO_FREQ 5
+ #define HEARING_SPANS 6
+ #define HEARING_MESSAGE_MODE 7 */
#define COMSIG_MOVABLE_DISPOSING "movable_disposing" //called when the movable is added to a disposal holder object for disposal movement: (obj/structure/disposalholder/holder, obj/machinery/disposal/source)
// /mob signals
diff --git a/code/__DEFINES/status_effects.dm b/code/__DEFINES/status_effects.dm
index 3cd64af8c7..96207e2468 100644
--- a/code/__DEFINES/status_effects.dm
+++ b/code/__DEFINES/status_effects.dm
@@ -7,6 +7,8 @@
#define STATUS_EFFECT_REPLACE 2 //if it allows only one, but new instances replace
+#define STATUS_EFFECT_REFRESH 3 // if it only allows one, and new instances just instead refresh the timer
+
///////////
// BUFFS //
///////////
@@ -72,6 +74,8 @@
#define STATUS_EFFECT_ICHORIAL_STAIN /datum/status_effect/ichorial_stain //Prevents a servant from being revived by vitality matrices for one minute.
+#define STATUS_EFFECT_SPASMS /datum/status_effect/spasms //causes random muscle spasms
+
/////////////
// NEUTRAL //
/////////////
diff --git a/code/__DEFINES/traits.dm b/code/__DEFINES/traits.dm
index e198041c6e..546d587f56 100644
--- a/code/__DEFINES/traits.dm
+++ b/code/__DEFINES/traits.dm
@@ -110,6 +110,7 @@
#define TRAIT_NOHARDCRIT "nohardcrit"
#define TRAIT_NOSOFTCRIT "nosoftcrit"
#define TRAIT_MINDSHIELD "mindshield"
+#define TRAIT_SIXTHSENSE "sixthsense"
#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
@@ -117,6 +118,7 @@
#define TRAIT_PARALYSIS_L_LEG "para-l-leg"
#define TRAIT_PARALYSIS_R_LEG "para-r-leg"
#define TRAIT_UNINTELLIGIBLE_SPEECH "unintelligible-speech"
+#define TRAIT_SOOTHED_THROAT "soothed-throat"
#define TRAIT_LAW_ENFORCEMENT_METABOLISM "law-enforcement-metabolism"
#define TRAIT_STRONG_GRABBER "strong_grabber"
#define TRAIT_CALCIUM_HEALER "calcium_healer"
@@ -174,6 +176,7 @@
#define CULT_TRAIT "cult"
#define CURSED_ITEM_TRAIT "cursed-item" // The item is magically cursed
#define ABSTRACT_ITEM_TRAIT "abstract-item"
+#define STATUS_EFFECT_TRAIT "status-effect"
#define ROUNDSTART_TRAIT "roundstart" //cannot be removed without admin intervention
// unique trait sources, still defines
diff --git a/code/__HELPERS/mobs.dm b/code/__HELPERS/mobs.dm
index 8f3cd4f60d..be9ce87e86 100644
--- a/code/__HELPERS/mobs.dm
+++ b/code/__HELPERS/mobs.dm
@@ -514,12 +514,14 @@ GLOBAL_LIST_EMPTY(species_list)
else
prefs = new
- var/adminoverride = 0
+ var/override = FALSE
if(M.client && M.client.holder && (prefs.chat_toggles & CHAT_DEAD))
- adminoverride = 1
- if(isnewplayer(M) && !adminoverride)
+ override = TRUE
+ if(HAS_TRAIT(M, TRAIT_SIXTHSENSE))
+ override = TRUE
+ if(isnewplayer(M) && !override)
continue
- if(M.stat != DEAD && !adminoverride)
+ if(M.stat != DEAD && !override)
continue
if(speaker_key && speaker_key in prefs.ignoring)
continue
diff --git a/code/_globalvars/lists/poll_ignore.dm b/code/_globalvars/lists/poll_ignore.dm
index 94723cae38..2aa12ad7a7 100644
--- a/code/_globalvars/lists/poll_ignore.dm
+++ b/code/_globalvars/lists/poll_ignore.dm
@@ -13,6 +13,8 @@
#define POLL_IGNORE_GOLEM "golem"
#define POLL_IGNORE_SWARMER "swarmer"
#define POLL_IGNORE_DRONE "drone"
+#define POLL_IGNORE_IMAGINARYFRIEND "imaginary_friend"
+#define POLL_IGNORE_SPLITPERSONALITY "split_personality"
GLOBAL_LIST_INIT(poll_ignore_desc, list(
POLL_IGNORE_SENTIENCE_POTION = "Sentience potion",
@@ -28,6 +30,8 @@ GLOBAL_LIST_INIT(poll_ignore_desc, list(
POLL_IGNORE_GOLEM = "Golems",
POLL_IGNORE_SWARMER = "Swarmer shells",
POLL_IGNORE_DRONE = "Drone shells",
+ POLL_IGNORE_IMAGINARYFRIEND = "Imaginary Friend",
+ POLL_IGNORE_SPLITPERSONALITY = "Split Personality",
))
GLOBAL_LIST_INIT(poll_ignore, init_poll_ignore())
diff --git a/code/_onclick/hud/alert.dm b/code/_onclick/hud/alert.dm
index cc183dd6d3..391e86f390 100644
--- a/code/_onclick/hud/alert.dm
+++ b/code/_onclick/hud/alert.dm
@@ -213,6 +213,12 @@ or something covering your eyes."
desc = "Whoa man, you're tripping balls! Careful you don't get addicted... if you aren't already."
icon_state = "high"
+/obj/screen/alert/hypnosis
+ name = "Hypnosis"
+ desc = "Something's hypnotizing you, but you're not really sure about what."
+ icon_state = "hypnosis"
+ var/phrase
+
/obj/screen/alert/drunk //Not implemented
name = "Drunk"
desc = "All that alcohol you've been drinking is impairing your speech, motor skills, and mental cognition. Make sure to act like it."
diff --git a/code/datums/brain_damage/brain_trauma.dm b/code/datums/brain_damage/brain_trauma.dm
index 56a3f3969b..1aa1341c9c 100644
--- a/code/datums/brain_damage/brain_trauma.dm
+++ b/code/datums/brain_damage/brain_trauma.dm
@@ -1,10 +1,12 @@
//Brain Traumas are the new actual brain damage. Brain damage itself acts as a way to acquire traumas: every time brain damage is dealt, there's a chance of receiving a trauma.
//This chance gets higher the higher the mob's brainloss is. Removing traumas is a separate thing from removing brain damage: you can get restored to full brain operativity,
-//but keep the quirks, until repaired by mannitol (for mild/special ones) or brain surgery (for severe ones).
+// but keep the quirks, until repaired by neurine, surgery, lobotomy or magic; depending on the resilience
+// of the trauma.
+
/datum/brain_trauma
var/name = "Brain Trauma"
var/desc = "A trauma caused by brain damage, which causes issues to the patient."
- var/scan_desc = "a generic brain trauma" //description when detected by a health scanner
+ var/scan_desc = "generic brain trauma" //description when detected by a health scanner
var/mob/living/carbon/owner //the poor bastard
var/obj/item/organ/brain/brain //the poor bastard's brain
var/gain_text = "You feel traumatized."
@@ -12,15 +14,21 @@
var/can_gain = TRUE
var/random_gain = TRUE //can this be gained through random traumas?
var/resilience = TRAUMA_RESILIENCE_BASIC //how hard is this to cure?
+ var/clonable = TRUE // will this transfer if the brain is cloned?
/datum/brain_trauma/Destroy()
- brain.traumas -= src
+ if(brain && brain.traumas)
+ brain.traumas -= src
if(owner)
on_lose()
brain = null
owner = null
return ..()
+/datum/brain_trauma/proc/on_clone()
+ if(clonable)
+ return new type
+
//Called on life ticks
/datum/brain_trauma/proc/on_life()
return
@@ -33,17 +41,24 @@
/datum/brain_trauma/proc/on_gain()
to_chat(owner, gain_text)
RegisterSignal(owner, COMSIG_MOB_SAY, .proc/handle_speech)
+ RegisterSignal(owner, COMSIG_MOVABLE_HEAR, .proc/handle_hearing)
//Called when removed from a mob
/datum/brain_trauma/proc/on_lose(silent)
if(!silent)
to_chat(owner, lose_text)
UnregisterSignal(owner, COMSIG_MOB_SAY)
+ UnregisterSignal(owner, COMSIG_MOVABLE_HEAR)
//Called when hearing a spoken message
-/datum/brain_trauma/proc/on_hear(message, speaker, message_language, raw_message, radio_freq)
- return message
+/datum/brain_trauma/proc/handle_hearing(datum/source, list/hearing_args)
+ UnregisterSignal(owner, COMSIG_MOVABLE_HEAR)
//Called when speaking
/datum/brain_trauma/proc/handle_speech(datum/source, list/speech_args)
UnregisterSignal(owner, COMSIG_MOB_SAY)
+
+
+//Called when hugging. expand into generally interacting, where future coders could switch the intent?
+/datum/brain_trauma/proc/on_hug(mob/living/hugger, mob/living/hugged)
+ return
diff --git a/code/datums/brain_damage/creepy_trauma.dm b/code/datums/brain_damage/creepy_trauma.dm
new file mode 100644
index 0000000000..8c0e49d5e6
--- /dev/null
+++ b/code/datums/brain_damage/creepy_trauma.dm
@@ -0,0 +1,116 @@
+/datum/brain_trauma/special/obsessed
+ name = "Psychotic Schizophrenia"
+ desc = "Patient has a subtype of delusional disorder, becoming irrationally attached to someone."
+ scan_desc = "psychotic schizophrenic delusions"
+ gain_text = "If you see this message, make a github issue report. The trauma initialized wrong."
+ lose_text = "The voices in your head fall silent."
+ can_gain = TRUE
+ random_gain = FALSE
+ resilience = TRAUMA_RESILIENCE_SURGERY
+ var/mob/living/obsession
+ var/datum/objective/spendtime/attachedobsessedobj
+ var/datum/antagonist/obsessed/antagonist
+ var/viewing = FALSE //it's a lot better to store if the owner is watching the obsession than checking it twice between two procs
+
+ var/total_time_creeping = 0 //just for roundend fun
+ var/time_spent_away = 0
+ var/obsession_hug_count = 0
+
+/datum/brain_trauma/special/obsessed/on_gain()
+
+ //setup, linking, etc//
+ if(!obsession)//admins didn't set one
+ obsession = find_obsession()
+ if(!obsession)//we didn't find one
+ lose_text = ""
+ qdel(src)
+ return
+ gain_text = "You hear a sickening, raspy voice in your head. It wants one small task of you..."
+ owner.mind.add_antag_datum(/datum/antagonist/obsessed)
+ antagonist = owner.mind.has_antag_datum(/datum/antagonist/obsessed)
+ antagonist.trauma = src
+ ..()
+ //antag stuff//
+ antagonist.forge_objectives(obsession.mind)
+ antagonist.greet()
+
+/datum/brain_trauma/special/obsessed/on_life()
+ if(!obsession || obsession.stat == DEAD)
+ viewing = FALSE//important, makes sure you no longer stutter when happy if you murdered them while viewing
+ return
+ if(get_dist(get_turf(owner), get_turf(obsession)) > 7)
+ viewing = FALSE //they are further than our viewrange they are not viewing us
+ out_of_view()
+ return//so we're not searching everything in view every tick
+ if(obsession in view(7, owner))
+ viewing = TRUE
+ else
+ viewing = FALSE
+ if(viewing)
+ SEND_SIGNAL(owner, COMSIG_ADD_MOOD_EVENT, "creeping", /datum/mood_event/creeping, obsession.name)
+ total_time_creeping += 20
+ time_spent_away = 0
+ if(attachedobsessedobj)//if an objective needs to tick down, we can do that since traumas coexist with the antagonist datum
+ attachedobsessedobj.timer -= 20 //mob subsystem ticks every 2 seconds(?), remove 20 deciseconds from the timer. sure, that makes sense.
+ else
+ out_of_view()
+
+/datum/brain_trauma/special/obsessed/proc/out_of_view()
+ time_spent_away += 20
+ if(time_spent_away > 1800) //3 minutes
+ SEND_SIGNAL(owner, COMSIG_ADD_MOOD_EVENT, "creeping", /datum/mood_event/notcreepingsevere, obsession.name)
+ else
+ SEND_SIGNAL(owner, COMSIG_ADD_MOOD_EVENT, "creeping", /datum/mood_event/notcreeping, obsession.name)
+
+/datum/brain_trauma/special/obsessed/on_lose()
+ ..()
+ owner.mind.remove_antag_datum(/datum/antagonist/obsessed)
+
+/datum/brain_trauma/special/obsessed/handle_speech(datum/source, list/speech_args)
+ if(!viewing)
+ return
+ var/datum/component/mood/mood = owner.GetComponent(/datum/component/mood)
+ if(mood && mood.sanity >= SANITY_GREAT && social_interaction())
+ speech_args[SPEECH_MESSAGE] = ""
+
+/datum/brain_trauma/special/obsessed/on_hug(mob/living/hugger, mob/living/hugged)
+ if(hugged == obsession)
+ obsession_hug_count++
+
+/datum/brain_trauma/special/obsessed/proc/social_interaction()
+ var/fail = FALSE //whether you can finish a sentence while doing it
+ owner.stuttering = max(3, owner.stuttering)
+ owner.blur_eyes(10)
+ switch(rand(1,4))
+ if(1)
+ shake_camera(owner, 15, 1)
+ owner.vomit()
+ fail = TRUE
+ if(2)
+ owner.emote("cough")
+ owner.dizziness += 10
+ fail = TRUE
+ if(3)
+ to_chat(owner, "You feel your heart lurching in your chest...")
+ owner.Stun(20)
+ shake_camera(owner, 15, 1)
+ if(4)
+ to_chat(owner, "You faint.")
+ owner.Unconscious(80)
+ fail = TRUE
+ return fail
+
+
+/datum/brain_trauma/special/obsessed/proc/find_obsession()
+ var/chosen_victim
+ var/list/possible_targets = list()
+ var/list/viable_minds = list()
+ for(var/mob/Player in GLOB.player_list)//prevents crewmembers falling in love with nuke ops they never met, and other annoying hijinks
+ if(Player.mind && Player.stat != DEAD && !isnewplayer(Player) && !isbrain(Player) && Player.client && Player != owner && SSjob.GetJob(Player.mind.assigned_role))
+ viable_minds += Player.mind
+ for(var/datum/mind/possible_target in viable_minds)
+ if(possible_target != owner && ishuman(possible_target.current))
+ possible_targets += possible_target.current
+ if(possible_targets.len > 0)
+ chosen_victim = pick(possible_targets)
+ return chosen_victim
diff --git a/code/datums/brain_damage/hypnosis.dm b/code/datums/brain_damage/hypnosis.dm
index 8909d1b85f..e717d43859 100644
--- a/code/datums/brain_damage/hypnosis.dm
+++ b/code/datums/brain_damage/hypnosis.dm
@@ -5,19 +5,14 @@
gain_text = ""
lose_text = ""
resilience = TRAUMA_RESILIENCE_SURGERY
+
var/hypnotic_phrase = ""
var/regex/target_phrase
-/datum/brain_trauma/hypnosis/New(phrase, quirk = FALSE)
+/datum/brain_trauma/hypnosis/New(phrase)
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)
+ hypnotic_phrase = phrase
try
target_phrase = new("(\\b[hypnotic_phrase]\\b)","ig")
catch(var/exception/e)
@@ -25,16 +20,6 @@
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]'.")
@@ -44,18 +29,17 @@
"You feel a part of your mind repeating this over and over. You need to follow these words.",\
"Something about this sounds... right, for some reason. You feel like you should follow these words.",\
"These words keep echoing in your mind. You find yourself completely fascinated by them.")]")
- if(!HAS_TRAIT(owner, "hypnotherapy"))
- to_chat(owner, "You've been hypnotized by this sentence. You must follow these words. If it isn't a clear order, you can freely interpret how to do so,\
+ to_chat(owner, "You've been hypnotized by this sentence. You must follow these words. If it isn't a clear order, you can freely interpret how to do so,\
as long as you act like the words are your highest priority.")
- else
- to_chat(owner, "You've been hypnotized by this sentence. You feel an incredible desire to follow these words, but are able to resist it somewhat. If it isn't a clear order, you can freely interpret how to do so,\
- however this does not take precedence over your other objectives.")
+ var/obj/screen/alert/hypnosis/hypno_alert = owner.throw_alert("hypnosis", /obj/screen/alert/hypnosis)
+ hypno_alert.desc = "\"[hypnotic_phrase]\"... your mind seems to be fixated on this concept."
..()
/datum/brain_trauma/hypnosis/on_lose()
message_admins("[ADMIN_LOOKUPFLW(owner)] is no longer hypnotized with the phrase '[hypnotic_phrase]'.")
log_game("[key_name(owner)] is no longer hypnotized with the phrase '[hypnotic_phrase]'.")
- to_chat(owner, "You suddenly snap out of your fixation. The phrase '[hypnotic_phrase]' no longer feels important to you.")
+ to_chat(owner, "You suddenly snap out of your hypnosis. The phrase '[hypnotic_phrase]' no longer feels important to you.")
+ owner.clear_alert("hypnosis")
..()
/datum/brain_trauma/hypnosis/on_life()
@@ -67,6 +51,5 @@
if(2)
new /datum/hallucination/chat(owner, TRUE, FALSE, "[hypnotic_phrase]")
-/datum/brain_trauma/hypnosis/on_hear(message, speaker, message_language, raw_message, radio_freq)
- message = target_phrase.Replace(message, "$1")
- return message
+/datum/brain_trauma/hypnosis/handle_hearing(datum/source, list/hearing_args)
+ hearing_args[HEARING_MESSAGE] = target_phrase.Replace(hearing_args[HEARING_MESSAGE], "$1")
diff --git a/code/datums/brain_damage/imaginary_friend.dm b/code/datums/brain_damage/imaginary_friend.dm
index 1bc7d8e3be..3115ea914f 100644
--- a/code/datums/brain_damage/imaginary_friend.dm
+++ b/code/datums/brain_damage/imaginary_friend.dm
@@ -8,6 +8,10 @@
var/friend_initialized = FALSE
/datum/brain_trauma/special/imaginary_friend/on_gain()
+ var/mob/living/M = owner
+ if(M.stat == DEAD || !M.client)
+ qdel(src)
+ return
..()
make_friend()
get_ghost()
@@ -43,7 +47,7 @@
/datum/brain_trauma/special/imaginary_friend/proc/get_ghost()
set waitfor = FALSE
- var/list/mob/dead/observer/candidates = pollCandidatesForMob("Do you want to play as [owner]'s imaginary friend?", ROLE_PAI, null, null, 75, friend)
+ var/list/mob/dead/observer/candidates = pollCandidatesForMob("Do you want to play as [owner]'s imaginary friend?", ROLE_PAI, null, null, 75, friend, POLL_IGNORE_IMAGINARYFRIEND)
if(LAZYLEN(candidates))
var/mob/dead/observer/C = pick(candidates)
friend.key = C.key
@@ -74,26 +78,34 @@
/mob/camera/imaginary_friend/Login()
..()
- to_chat(src, "You are the imaginary friend of [owner]!")
- to_chat(src, "You are absolutely loyal to your friend, no matter what.")
- to_chat(src, "You cannot directly influence the world around you, but you can see what [owner] cannot.")
+ greet()
Show()
+/mob/camera/imaginary_friend/proc/greet()
+ to_chat(src, "You are the imaginary friend of [owner]!")
+ to_chat(src, "You are absolutely loyal to your friend, no matter what.")
+ to_chat(src, "You cannot directly influence the world around you, but you can see what [owner] cannot.")
+
/mob/camera/imaginary_friend/Initialize(mapload, _trauma)
. = ..()
- var/gender = pick(MALE, FEMALE)
- real_name = random_unique_name(gender)
- name = real_name
+
trauma = _trauma
owner = trauma.owner
copy_known_languages_from(owner, TRUE)
- human_image = get_flat_human_icon(null, pick(SSjob.occupations))
+
+ setup_friend()
join = new
join.Grant(src)
hide = new
hide.Grant(src)
+/mob/camera/imaginary_friend/proc/setup_friend()
+ var/gender = pick(MALE, FEMALE)
+ real_name = random_unique_name(gender)
+ name = real_name
+ human_image = get_flat_human_icon(null, pick(SSjob.occupations))
+
/mob/camera/imaginary_friend/proc/Show()
if(!client) //nobody home
return
@@ -132,7 +144,7 @@
if(client.prefs.muted & MUTE_IC)
to_chat(src, "You cannot send IC messages (muted).")
return
- if (src.client.handle_spam_prevention(message,MUTE_IC))
+ if (!(ignore_spam || forced) && src.client.handle_spam_prevention(message,MUTE_IC))
return
friend_talk(message)
@@ -218,4 +230,42 @@
var/mob/camera/imaginary_friend/I = owner
I.hidden = !I.hidden
I.Show()
- update_status()
\ No newline at end of file
+ update_status()
+
+//down here is the trapped mind
+//like imaginary friend but a lot less imagination and more like mind prison//
+
+/datum/brain_trauma/special/imaginary_friend/trapped_owner
+ name = "Trapped Victim"
+ desc = "Patient appears to be targeted by an invisible entity."
+ gain_text = ""
+ lose_text = ""
+ random_gain = FALSE
+
+/datum/brain_trauma/special/imaginary_friend/trapped_owner/make_friend()
+ friend = new /mob/camera/imaginary_friend/trapped(get_turf(owner), src)
+
+/datum/brain_trauma/special/imaginary_friend/trapped_owner/reroll_friend() //no rerolling- it's just the last owner's hell
+ if(friend.client) //reconnected
+ return
+ friend_initialized = FALSE
+ QDEL_NULL(friend)
+ qdel(src)
+
+/datum/brain_trauma/special/imaginary_friend/trapped_owner/get_ghost() //no randoms
+ return
+
+/mob/camera/imaginary_friend/trapped
+ name = "figment of imagination?"
+ real_name = "figment of imagination?"
+ desc = "The previous host of this body."
+
+/mob/camera/imaginary_friend/trapped/greet()
+ to_chat(src, "You have managed to hold on as a figment of the new host's imagination!")
+ to_chat(src, "All hope is lost for you, but at least you may interact with your host. You do not have to be loyal to them.")
+ to_chat(src, "You cannot directly influence the world around you, but you can see what the host cannot.")
+
+/mob/camera/imaginary_friend/trapped/setup_friend()
+ real_name = "[owner.real_name]?"
+ name = real_name
+ human_image = icon('icons/mob/lavaland/lavaland_monsters.dmi', icon_state = "curseblob")
diff --git a/code/datums/brain_damage/mild.dm b/code/datums/brain_damage/mild.dm
index c049a7db33..bacc171fbe 100644
--- a/code/datums/brain_damage/mild.dm
+++ b/code/datums/brain_damage/mild.dm
@@ -64,21 +64,21 @@
name = "Speech Impediment"
desc = "Patient is unable to form coherent sentences."
scan_desc = "communication disorder"
- gain_text = "" //mutation will handle the text
- lose_text = ""
+ gain_text = "You can't seem to form any coherent thoughts!"
+ lose_text = "Your mind feels more clear."
/datum/brain_trauma/mild/speech_impediment/on_gain()
ADD_TRAIT(owner, TRAIT_UNINTELLIGIBLE_SPEECH, TRAUMA_TRAIT)
- . = ..()
+ ..()
/datum/brain_trauma/mild/speech_impediment/on_lose()
REMOVE_TRAIT(owner, TRAIT_UNINTELLIGIBLE_SPEECH, TRAUMA_TRAIT)
- . = ..()
+ ..()
/datum/brain_trauma/mild/concussion
name = "Concussion"
desc = "Patient's brain is concussed."
- scan_desc = "a concussion"
+ scan_desc = "concussion"
gain_text = "Your head hurts!"
lose_text = "The pressure inside your head starts fading."
@@ -157,54 +157,108 @@
gain_text = "Your muscles feel oddly faint."
lose_text = "You feel in control of your muscles again."
-/datum/brain_trauma/mild/muscle_spasms/on_life()
- if(prob(7))
- switch(rand(1,5))
- if(1)
- if(owner.canmove && !isspaceturf(owner.loc))
- to_chat(owner, "Your leg spasms!")
- step(owner, pick(GLOB.cardinals))
- if(2)
- if(owner.incapacitated())
- return
- var/obj/item/I = owner.get_active_held_item()
- if(I)
- to_chat(owner, "Your fingers spasm!")
- owner.log_message("used [I] due to a Muscle Spasm", LOG_ATTACK)
- I.attack_self(owner)
- if(3)
- var/prev_intent = owner.a_intent
- owner.a_intent = INTENT_HARM
-
- var/range = 1
- if(istype(owner.get_active_held_item(), /obj/item/gun)) //get targets to shoot at
- range = 7
-
- var/list/mob/living/targets = list()
- for(var/mob/M in oview(owner, range))
- if(isliving(M))
- targets += M
- if(LAZYLEN(targets))
- to_chat(owner, "Your arm spasms!")
- owner.log_message(" attacked someone due to a Muscle Spasm") //the following attack will log itself
- owner.ClickOn(pick(targets))
- owner.a_intent = prev_intent
- if(4)
- var/prev_intent = owner.a_intent
- owner.a_intent = INTENT_HARM
- to_chat(owner, "Your arm spasms!")
- owner.log_message("attacked [owner.p_them()]self to a Muscle Spasm", LOG_ATTACK)
- owner.ClickOn(owner)
- owner.a_intent = prev_intent
- if(5)
- if(owner.incapacitated())
- return
- var/obj/item/I = owner.get_active_held_item()
- var/list/turf/targets = list()
- for(var/turf/T in oview(owner, 3))
- targets += T
- if(LAZYLEN(targets) && I)
- to_chat(owner, "Your arm spasms!")
- owner.log_message("threw [I] due to a Muscle Spasm", LOG_ATTACK)
- owner.throw_item(pick(targets))
+/datum/brain_trauma/mild/muscle_spasms/on_gain()
+ owner.apply_status_effect(STATUS_EFFECT_SPASMS)
..()
+
+/datum/brain_trauma/mild/muscle_spasms/on_lose()
+ owner.remove_status_effect(STATUS_EFFECT_SPASMS)
+ ..()
+
+/datum/brain_trauma/mild/nervous_cough
+ name = "Nervous Cough"
+ desc = "Patient feels a constant need to cough."
+ scan_desc = "nervous cough"
+ gain_text = "Your throat itches incessantly..."
+ lose_text = "Your throat stops itching."
+
+/datum/brain_trauma/mild/nervous_cough/on_life()
+ if(prob(12) && !HAS_TRAIT(owner, TRAIT_SOOTHED_THROAT))
+ if(prob(5))
+ to_chat(owner, "[pick("You have a coughing fit!", "You can't stop coughing!")]")
+ owner.stun(20)
+ owner.emote("cough")
+ addtimer(CALLBACK(owner, /mob/.proc/emote, "cough"), 6)
+ addtimer(CALLBACK(owner, /mob/.proc/emote, "cough"), 12)
+ owner.emote("cough")
+ ..()
+
+/datum/brain_trauma/mild/expressive_aphasia
+ name = "Expressive Aphasia"
+ desc = "Patient is affected by partial loss of speech leading to a reduced vocabulary."
+ scan_desc = "inability to form complex sentences"
+ gain_text = "You lose your grasp on complex words."
+ lose_text = "You feel your vocabulary returning to normal again."
+
+ var/static/list/common_words = world.file2list("strings/1000_most_common.txt")
+
+/datum/brain_trauma/mild/expressive_aphasia/handle_speech(datum/source, list/speech_args)
+ var/message = speech_args[SPEECH_MESSAGE]
+ if(message)
+ var/list/message_split = splittext(message, " ")
+ var/list/new_message = list()
+
+ for(var/word in message_split)
+ var/suffix = copytext(word,-1)
+
+ // Check if we have a suffix and break it out of the word
+ if(suffix in list("." , "," , ";" , "!" , ":" , "?"))
+ word = copytext(word,1,-1)
+ else
+ suffix = ""
+
+ word = html_decode(word)
+
+ if(lowertext(word) in common_words)
+ new_message += word + suffix
+ else
+ if(prob(30) && message_split.len > 2)
+ new_message += pick("uh","erm")
+ break
+ else
+ var/list/charlist = string2charlist(word) // Stupid shit code
+ shuffle_inplace(charlist)
+ charlist.len = round(charlist.len * 0.5,1)
+ new_message += html_encode(jointext(charlist,"")) + suffix
+
+ message = jointext(new_message, " ")
+
+ speech_args[SPEECH_MESSAGE] = trim(message)
+
+/datum/brain_trauma/mild/mind_echo
+ name = "Mind Echo"
+ desc = "Patient's language neurons do not terminate properly, causing previous speech patterns to occasionally resurface spontaneously."
+ scan_desc = "looping neural pattern"
+ gain_text = "You feel a faint echo of your thoughts..."
+ lose_text = "The faint echo fades away."
+ var/list/hear_dejavu = list()
+ var/list/speak_dejavu = list()
+
+/datum/brain_trauma/mild/mind_echo/handle_hearing(datum/source, list/hearing_args)
+ if(owner == hearing_args[HEARING_SPEAKER])
+ return
+ if(hear_dejavu.len >= 5)
+ if(prob(25))
+ var/deja_vu = pick_n_take(hear_dejavu)
+ var/static/regex/quoted_spoken_message = regex("\".+\"", "gi")
+ hearing_args[HEARING_MESSAGE] = quoted_spoken_message.Replace(hearing_args[HEARING_MESSAGE], "\"[deja_vu]\"") //Quotes included to avoid cases where someone says part of their name
+ return
+ if(hear_dejavu.len >= 15)
+ if(prob(50))
+ popleft(hear_dejavu) //Remove the oldest
+ hear_dejavu += hearing_args[HEARING_RAW_MESSAGE]
+ else
+ hear_dejavu += hearing_args[HEARING_RAW_MESSAGE]
+
+/datum/brain_trauma/mild/mind_echo/handle_speech(datum/source, list/speech_args)
+ if(speak_dejavu.len >= 5)
+ if(prob(25))
+ var/deja_vu = pick_n_take(speak_dejavu)
+ speech_args[SPEECH_MESSAGE] = deja_vu
+ return
+ if(speak_dejavu.len >= 15)
+ if(prob(50))
+ popleft(speak_dejavu) //Remove the oldest
+ speak_dejavu += speech_args[SPEECH_MESSAGE]
+ else
+ speak_dejavu += speech_args[SPEECH_MESSAGE]
\ No newline at end of file
diff --git a/code/datums/brain_damage/phobia.dm b/code/datums/brain_damage/phobia.dm
index f802555c7e..034f2baaaf 100644
--- a/code/datums/brain_damage/phobia.dm
+++ b/code/datums/brain_damage/phobia.dm
@@ -2,8 +2,8 @@
name = "Phobia"
desc = "Patient is unreasonably afraid of something."
scan_desc = "phobia"
- gain_text = ""
- lose_text = ""
+ gain_text = "You start finding default values very unnerving..."
+ lose_text = "You no longer feel afraid of default values."
var/phobia_type
var/next_check = 0
var/next_scare = 0
@@ -14,8 +14,10 @@
var/list/trigger_turfs
var/list/trigger_species
-/datum/brain_trauma/mild/phobia/New(specific_type)
- phobia_type = specific_type
+/datum/brain_trauma/mild/phobia/New(new_phobia_type)
+ if(new_phobia_type)
+ phobia_type = new_phobia_type
+
if(!phobia_type)
phobia_type = pick(SStraumas.phobia_types)
@@ -29,6 +31,11 @@
trigger_species = SStraumas.phobia_species[phobia_type]
..()
+
+/datum/brain_trauma/mild/phobia/on_clone()
+ if(clonable)
+ return new type(phobia_type)
+
/datum/brain_trauma/mild/phobia/on_life()
..()
if(HAS_TRAIT(owner, TRAIT_FEARLESS))
@@ -44,6 +51,12 @@
if(is_type_in_typecache(O, trigger_objs))
freak_out(O)
return
+ for(var/mob/living/carbon/human/HU in seen_atoms) //check equipment for trigger items
+ for(var/X in HU.get_all_slots() | HU.held_items)
+ var/obj/I = X
+ if(!QDELETED(I) && is_type_in_typecache(I, trigger_objs))
+ freak_out(I)
+ return
if(LAZYLEN(trigger_turfs))
for(var/turf/T in seen_atoms)
@@ -51,45 +64,41 @@
freak_out(T)
return
- if(LAZYLEN(trigger_mobs) || LAZYLEN(trigger_objs))
+ seen_atoms -= owner //make sure they aren't afraid of themselves.
+ if(LAZYLEN(trigger_mobs) || LAZYLEN(trigger_species))
for(var/mob/M in seen_atoms)
if(is_type_in_typecache(M, trigger_mobs))
freak_out(M)
return
- else if(ishuman(M)) //check their equipment for trigger items
+ else if(ishuman(M)) //check their species
var/mob/living/carbon/human/H = M
if(LAZYLEN(trigger_species) && H.dna && H.dna.species && is_type_in_typecache(H.dna.species, trigger_species))
freak_out(H)
+ return
- for(var/X in H.get_all_slots() | H.held_items)
- var/obj/I = X
- if(!QDELETED(I) && is_type_in_typecache(I, trigger_objs))
- freak_out(I)
- return
-
-/datum/brain_trauma/mild/phobia/on_hear(message, speaker, message_language, raw_message, radio_freq)
+/datum/brain_trauma/mild/phobia/handle_hearing(datum/source, list/hearing_args)
if(!owner.can_hear() || world.time < next_scare) //words can't trigger you if you can't hear them *taps head*
- return message
+ return
if(HAS_TRAIT(owner, TRAIT_FEARLESS))
- return message
+ return
for(var/word in trigger_words)
- var/reg = regex("(\\b|\\A)[REGEX_QUOTE(word)]'?s*(\\b|\\Z)", "i")
+ var/regex/reg = regex("(\\b|\\A)[REGEX_QUOTE(word)]'?s*(\\b|\\Z)", "i")
- if(findtext(raw_message, reg))
+ if(findtext(hearing_args[HEARING_RAW_MESSAGE], reg))
addtimer(CALLBACK(src, .proc/freak_out, null, word), 10) //to react AFTER the chat message
+ hearing_args[HEARING_MESSAGE] = reg.Replace(hearing_args[HEARING_MESSAGE], "$1")
break
- return message
/datum/brain_trauma/mild/phobia/handle_speech(datum/source, list/speech_args)
if(HAS_TRAIT(owner, TRAIT_FEARLESS))
return
for(var/word in trigger_words)
- var/reg = regex("(\\b|\\A)[REGEX_QUOTE(word)]'?s*(\\b|\\Z)", "i")
+ var/regex/reg = regex("(\\b|\\A)[REGEX_QUOTE(word)]'?s*(\\b|\\Z)", "i")
if(findtext(speech_args[SPEECH_MESSAGE], reg))
- to_chat(owner, "You can't bring yourself to say the word \"[word]\"!")
+ to_chat(owner, "You can't bring yourself to say the word \"[word]\"!")
speech_args[SPEECH_MESSAGE] = ""
/datum/brain_trauma/mild/phobia/proc/freak_out(atom/reason, trigger_word)
@@ -125,6 +134,76 @@
owner.Jitter(10)
owner.stuttering += 10
+// Defined phobia types for badminry, not included in the RNG trauma pool to avoid diluting.
+
+/datum/brain_trauma/mild/phobia/spiders
+ phobia_type = "spiders"
+ random_gain = FALSE
+
+/datum/brain_trauma/mild/phobia/space
+ phobia_type = "space"
+ random_gain = FALSE
+
/datum/brain_trauma/mild/phobia/security
phobia_type = "security"
random_gain = FALSE
+
+/datum/brain_trauma/mild/phobia/clowns
+ phobia_type = "clowns"
+ random_gain = FALSE
+
+/datum/brain_trauma/mild/phobia/greytide
+ phobia_type = "greytide"
+ random_gain = FALSE
+
+/datum/brain_trauma/mild/phobia/lizards
+ phobia_type = "lizards"
+ random_gain = FALSE
+
+/datum/brain_trauma/mild/phobia/skeletons
+ phobia_type = "skeletons"
+ random_gain = FALSE
+
+/datum/brain_trauma/mild/phobia/snakes
+ phobia_type = "snakes"
+ random_gain = FALSE
+
+/datum/brain_trauma/mild/phobia/robots
+ phobia_type = "robots"
+ random_gain = FALSE
+
+/datum/brain_trauma/mild/phobia/doctors
+ phobia_type = "doctors"
+ random_gain = FALSE
+
+/datum/brain_trauma/mild/phobia/authority
+ phobia_type = "authority"
+ random_gain = FALSE
+
+/datum/brain_trauma/mild/phobia/supernatural
+ phobia_type = "the supernatural"
+ random_gain = FALSE
+
+/datum/brain_trauma/mild/phobia/aliens
+ phobia_type = "aliens"
+ random_gain = FALSE
+
+/datum/brain_trauma/mild/phobia/strangers
+ phobia_type = "strangers"
+ random_gain = FALSE
+
+/datum/brain_trauma/mild/phobia/birds
+ phobia_type = "birds"
+ random_gain = FALSE
+
+/datum/brain_trauma/mild/phobia/falling
+ phobia_type = "falling"
+ random_gain = FALSE
+
+/datum/brain_trauma/mild/phobia/anime
+ phobia_type = "anime"
+ random_gain = FALSE
+
+/datum/brain_trauma/mild/phobia/conspiracies
+ phobia_type = "conspiracies"
+ random_gain = FALSE
diff --git a/code/datums/brain_damage/severe.dm b/code/datums/brain_damage/severe.dm
index 890e9cf903..0e08c4cd15 100644
--- a/code/datums/brain_damage/severe.dm
+++ b/code/datums/brain_damage/severe.dm
@@ -119,7 +119,7 @@
owner.update_disabled_bodyparts()
/datum/brain_trauma/severe/paralysis/paraplegic
- //can_gain = FALSE maybe breaks.
+ random_gain = FALSE
paralysis_type = "legs"
resilience = TRAUMA_RESILIENCE_ABSOLUTE
@@ -149,7 +149,7 @@
/datum/brain_trauma/severe/monophobia
name = "Monophobia"
desc = "Patient feels sick and distressed when not around other people, leading to potentially lethal levels of stress."
- scan_desc = "severe monophobia"
+ scan_desc = "monophobia"
gain_text = ""
lose_text = "You feel like you could be safe on your own."
var/stress = 0
@@ -168,7 +168,7 @@
if(stress > 10 && (prob(5)))
stress_reaction()
else
- stress -= 4
+ stress = max(stress - 4, 0)
/datum/brain_trauma/severe/monophobia/proc/check_alone()
if(HAS_TRAIT(owner, TRAIT_BLIND))
diff --git a/code/datums/brain_damage/special.dm b/code/datums/brain_damage/special.dm
index d9e6f00643..b52c7d391c 100644
--- a/code/datums/brain_damage/special.dm
+++ b/code/datums/brain_damage/special.dm
@@ -22,6 +22,14 @@
else
speak("neutral", prob(25))
+/datum/brain_trauma/special/godwoken/on_gain()
+ ADD_TRAIT(owner, TRAIT_HOLY, TRAUMA_TRAIT)
+ ..()
+
+/datum/brain_trauma/special/godwoken/on_lose()
+ REMOVE_TRAIT(owner, TRAIT_HOLY, TRAUMA_TRAIT)
+ ..()
+
/datum/brain_trauma/special/godwoken/proc/speak(type, include_owner = FALSE)
var/message
switch(type)
@@ -36,7 +44,7 @@
else
message = pick_list_replacements(BRAIN_DAMAGE_FILE, "god_neutral")
- playsound(get_turf(owner), 'sound/magic/clockwork/invoke_general.ogg', 200, 1, 5)
+ playsound(get_turf(owner), 'sound/magic/clockwork/invoke_general.ogg', 200, TRUE, 5)
voice_of_god(message, owner, list("colossus","yell"), 2.5, include_owner, FALSE)
/datum/brain_trauma/special/bluespace_prophet
@@ -134,7 +142,101 @@
/datum/brain_trauma/special/psychotic_brawling/bath_salts
name = "Chemical Violent Psychosis"
- random_gain = FALSE
+ clonable = FALSE
+
+/datum/brain_trauma/special/tenacity
+ name = "Tenacity"
+ desc = "Patient is psychologically unaffected by pain and injuries, and can remain standing far longer than a normal person."
+ scan_desc = "traumatic neuropathy"
+ gain_text = "You suddenly stop feeling pain."
+ lose_text = "You realize you can feel pain again."
+
+/datum/brain_trauma/special/tenacity/on_gain()
+ ADD_TRAIT(owner, TRAIT_NOSOFTCRIT, TRAUMA_TRAIT)
+ ADD_TRAIT(owner, TRAIT_NOHARDCRIT, TRAUMA_TRAIT)
+ ..()
+
+/datum/brain_trauma/special/tenacity/on_lose()
+ REMOVE_TRAIT(owner, TRAIT_NOSOFTCRIT, TRAUMA_TRAIT)
+ REMOVE_TRAIT(owner, TRAIT_NOHARDCRIT, TRAUMA_TRAIT)
+ ..()
+
+/datum/brain_trauma/special/death_whispers
+ name = "Functional Cerebral Necrosis"
+ desc = "Patient's brain is stuck in a functional near-death state, causing occasional moments of lucid hallucinations, which are often interpreted as the voices of the dead."
+ scan_desc = "chronic functional necrosis"
+ gain_text = "You feel dead inside."
+ lose_text = "You feel alive again."
+ var/active = FALSE
+
+/datum/brain_trauma/special/death_whispers/on_life()
+ ..()
+ if(!active && prob(2))
+ whispering()
+
+/datum/brain_trauma/special/death_whispers/on_lose()
+ if(active)
+ cease_whispering()
+ ..()
+
+/datum/brain_trauma/special/death_whispers/proc/whispering()
+ ADD_TRAIT(owner, TRAIT_SIXTHSENSE, TRAUMA_TRAIT)
+ active = TRUE
+ addtimer(CALLBACK(src, .proc/cease_whispering), rand(50, 300))
+
+/datum/brain_trauma/special/death_whispers/proc/cease_whispering()
+ REMOVE_TRAIT(owner, TRAIT_SIXTHSENSE, TRAUMA_TRAIT)
+ active = FALSE
+
+/datum/brain_trauma/special/existential_crisis
+ name = "Existential Crisis"
+ desc = "Patient's hold on reality becomes faint, causing occasional bouts of non-existence."
+ scan_desc = "existential crisis"
+ gain_text = "You feel less real."
+ lose_text = "You feel more substantial again."
+ var/obj/effect/abstract/sync_holder/veil/veil
+ var/next_crisis = 0
+
+/datum/brain_trauma/special/existential_crisis/on_life()
+ ..()
+ if(!veil && world.time > next_crisis && prob(3))
+ if(isturf(owner.loc))
+ fade_out()
+
+/datum/brain_trauma/special/existential_crisis/on_lose()
+ if(veil)
+ fade_in()
+ ..()
+
+/datum/brain_trauma/special/existential_crisis/proc/fade_out()
+ if(veil)
+ return
+ var/duration = rand(50, 450)
+ veil = new(owner.drop_location())
+ to_chat(owner, "[pick("You stop thinking for a moment. Therefore you are not.",\
+ "To be or not to be...",\
+ "Why exist?",\
+ "You stop keeping it real.",\
+ "Your grip on existence slips.",\
+ "Do you even exist?",\
+ "You simply fade away.")]")
+ owner.forceMove(veil)
+ SEND_SIGNAL(owner, COMSIG_MOVABLE_SECLUDED_LOCATION)
+ for(var/thing in owner)
+ var/atom/movable/AM = thing
+ SEND_SIGNAL(AM, COMSIG_MOVABLE_SECLUDED_LOCATION)
+ next_crisis = world.time + 600
+ addtimer(CALLBACK(src, .proc/fade_in), duration)
+
+/datum/brain_trauma/special/existential_crisis/proc/fade_in()
+ QDEL_NULL(veil)
+ to_chat(owner, "You fade back into reality.")
+ next_crisis = world.time + 600
+
+//base sync holder is in desynchronizer.dm
+/obj/effect/abstract/sync_holder/veil
+ name = "non-existence"
+ desc = "Existence is just a state of mind."
/datum/brain_trauma/special/beepsky
name = "Criminal"
@@ -142,6 +244,7 @@
scan_desc = "criminal mind"
gain_text = "Justice is coming for you."
lose_text = "You were absolved for your crimes."
+ clonable = FALSE
random_gain = FALSE
var/obj/effect/hallucination/simple/securitron/beepsky
@@ -201,4 +304,4 @@
/obj/effect/hallucination/simple/securitron/Destroy()
STOP_PROCESSING(SSfastprocess,src)
- return ..()
\ No newline at end of file
+ return ..()
diff --git a/code/datums/brain_damage/split_personality.dm b/code/datums/brain_damage/split_personality.dm
index 653b8a98c0..8766596191 100644
--- a/code/datums/brain_damage/split_personality.dm
+++ b/code/datums/brain_damage/split_personality.dm
@@ -13,6 +13,10 @@
var/mob/living/split_personality/owner_backseat
/datum/brain_trauma/severe/split_personality/on_gain()
+ var/mob/living/M = owner
+ if(M.stat == DEAD) //No use assigning people to a corpse
+ qdel(src)
+ return
..()
make_backseats()
get_ghost()
@@ -23,7 +27,7 @@
/datum/brain_trauma/severe/split_personality/proc/get_ghost()
set waitfor = FALSE
- var/list/mob/dead/observer/candidates = pollCandidatesForMob("Do you want to play as [owner]'s split personality?", ROLE_PAI, null, null, 75, stranger_backseat)
+ var/list/mob/dead/observer/candidates = pollCandidatesForMob("Do you want to play as [owner]'s split personality?", ROLE_PAI, null, null, 75, stranger_backseat, POLL_IGNORE_SPLITPERSONALITY)
if(LAZYLEN(candidates))
var/mob/dead/observer/C = pick(candidates)
stranger_backseat.key = C.key
@@ -191,13 +195,13 @@
/datum/brain_trauma/severe/split_personality/brainwashing/on_life()
return //no random switching
-/datum/brain_trauma/severe/split_personality/brainwashing/on_hear(message, speaker, message_language, raw_message, radio_freq)
- if(HAS_TRAIT(owner, TRAIT_DEAF) || owner == speaker)
- return message
+/datum/brain_trauma/severe/split_personality/brainwashing/handle_hearing(datum/source, list/hearing_args)
+ if(HAS_TRAIT(owner, TRAIT_DEAF) || owner == hearing_args[HEARING_SPEAKER])
+ return
+ var/message = hearing_args[HEARING_MESSAGE]
if(findtext(message, codeword))
- message = replacetext(message, codeword, "[codeword]")
+ hearing_args[HEARING_MESSAGE] = replacetext(message, codeword, "[codeword]")
addtimer(CALLBACK(src, /datum/brain_trauma/severe/split_personality.proc/switch_personalities), 10)
- return message
/datum/brain_trauma/severe/split_personality/brainwashing/handle_speech(datum/source, list/speech_args)
if(findtext(speech_args[SPEECH_MESSAGE], codeword))
diff --git a/code/datums/components/stationloving.dm b/code/datums/components/stationloving.dm
index bbcb0d88a6..91928656e1 100644
--- a/code/datums/components/stationloving.dm
+++ b/code/datums/components/stationloving.dm
@@ -8,6 +8,7 @@
if(!ismovableatom(parent))
return COMPONENT_INCOMPATIBLE
RegisterSignal(parent, list(COMSIG_MOVABLE_Z_CHANGED), .proc/check_in_bounds)
+ RegisterSignal(parent, list(COMSIG_MOVABLE_SECLUDED_LOCATION), .proc/relocate)
RegisterSignal(parent, list(COMSIG_PARENT_PREQDELETED), .proc/check_deletion)
RegisterSignal(parent, list(COMSIG_ITEM_IMBUE_SOUL), .proc/check_soul_imbue)
src.inform_admins = inform_admins
@@ -32,6 +33,7 @@
var/atom/movable/AM = parent
AM.forceMove(targetturf)
+ to_chat(get(parent, /mob), "You can't help but feel that you just lost something back there...")
// move the disc, so ghosts remain orbiting it even if it's "destroyed"
return targetturf
@@ -40,7 +42,6 @@
return
else
var/turf/currentturf = get_turf(src)
- to_chat(get(parent, /mob), "You can't help but feel that you just lost something back there...")
var/turf/targetturf = relocate()
log_game("[parent] has been moved out of bounds in [loc_name(currentturf)]. Moving it to [loc_name(targetturf)].")
if(inform_admins)
diff --git a/code/datums/status_effects/debuffs.dm b/code/datums/status_effects/debuffs.dm
index e930c77c36..7eb7d6b220 100644
--- a/code/datums/status_effects/debuffs.dm
+++ b/code/datums/status_effects/debuffs.dm
@@ -534,8 +534,7 @@ datum/status_effect/pacify
tick_interval = 10
examine_text = "SUBJECTPRONOUN seems slow and unfocused."
var/stun = TRUE
- var/triggered = FALSE
- alert_type = null
+ alert_type = /obj/screen/alert/status_effect/trance
/obj/screen/alert/status_effect/trance
name = "Trance"
@@ -543,17 +542,6 @@ datum/status_effect/pacify
icon_state = "high"
/datum/status_effect/trance/tick()
- if(HAS_TRAIT(owner, "hypnotherapy"))
- if(triggered == TRUE)
- UnregisterSignal(owner, COMSIG_MOVABLE_HEAR)
- RegisterSignal(owner, COMSIG_MOVABLE_HEAR, .proc/hypnotize)
- ADD_TRAIT(owner, TRAIT_MUTE, "trance")
- if(!owner.has_quirk(/datum/quirk/monochromatic))
- owner.add_client_colour(/datum/client_colour/monochrome)
- to_chat(owner, "[pick("You feel your thoughts slow down...", "You suddenly feel extremely dizzy...", "You feel like you're in the middle of a dream...","You feel incredibly relaxed...")]")
- triggered = FALSE
- else
- return
if(stun)
owner.Stun(60, TRUE, TRUE)
owner.dizziness = 20
@@ -561,47 +549,88 @@ datum/status_effect/pacify
/datum/status_effect/trance/on_apply()
if(!iscarbon(owner))
return FALSE
- if(HAS_TRAIT(owner, "hypnotherapy"))
- RegisterSignal(owner, COMSIG_MOVABLE_HEAR, .proc/listen)
- return TRUE
- alert_type = /obj/screen/alert/status_effect/trance
RegisterSignal(owner, COMSIG_MOVABLE_HEAR, .proc/hypnotize)
ADD_TRAIT(owner, TRAIT_MUTE, "trance")
- if(!owner.has_quirk(/datum/quirk/monochromatic))
- owner.add_client_colour(/datum/client_colour/monochrome)
+ owner.add_client_colour(/datum/client_colour/monochrome/trance)
owner.visible_message("[stun ? "[owner] stands still as [owner.p_their()] eyes seem to focus on a distant point." : ""]", \
"[pick("You feel your thoughts slow down...", "You suddenly feel extremely dizzy...", "You feel like you're in the middle of a dream...","You feel incredibly relaxed...")]")
return TRUE
-/datum/status_effect/trance/on_creation(mob/living/new_owner, _duration, _stun = TRUE, source_quirk = FALSE)//hypnoquirk makes no visible message, prevents self antag messages, and places phrase below objectives.
+/datum/status_effect/trance/on_creation(mob/living/new_owner, _duration, _stun = TRUE)
duration = _duration
stun = _stun
- if(source_quirk == FALSE && HAS_TRAIT(owner, "hypnotherapy"))
- REMOVE_TRAIT(owner, "hypnotherapy", ROUNDSTART_TRAIT)
return ..()
/datum/status_effect/trance/on_remove()
UnregisterSignal(owner, COMSIG_MOVABLE_HEAR)
REMOVE_TRAIT(owner, TRAIT_MUTE, "trance")
owner.dizziness = 0
- if(!owner.has_quirk(/datum/quirk/monochromatic))
- owner.remove_client_colour(/datum/client_colour/monochrome)
+ owner.remove_client_colour(/datum/client_colour/monochrome/trance)
to_chat(owner, "You snap out of your trance!")
-/datum/status_effect/trance/proc/listen(datum/source, message, atom/movable/speaker, message_language, raw_message, radio_freq, list/spans, message_mode)
- to_chat(owner, "[speaker] accidentally sets off your implanted trigger, sending you into a hypnotic daze!")
- triggered = TRUE
-
-/datum/status_effect/trance/proc/hypnotize(datum/source, message, atom/movable/speaker, message_language, raw_message, radio_freq, list/spans, message_mode)
+/datum/status_effect/trance/proc/hypnotize(datum/source, list/hearing_args)
if(!owner.can_hear())
return
- if(speaker == owner)
+ if(hearing_args[HEARING_SPEAKER] == owner)
return
var/mob/living/carbon/C = owner
C.cure_trauma_type(/datum/brain_trauma/hypnosis, TRAUMA_RESILIENCE_SURGERY) //clear previous hypnosis
- if(HAS_TRAIT(C, "hypnotherapy"))
- addtimer(CALLBACK(C, /mob/living/carbon.proc/gain_trauma, /datum/brain_trauma/hypnosis, TRAUMA_RESILIENCE_SURGERY, raw_message, TRUE), 10)
- else
- addtimer(CALLBACK(C, /mob/living/carbon.proc/gain_trauma, /datum/brain_trauma/hypnosis, TRAUMA_RESILIENCE_SURGERY, raw_message), 10)
+ addtimer(CALLBACK(C, /mob/living/carbon.proc/gain_trauma, /datum/brain_trauma/hypnosis, TRAUMA_RESILIENCE_SURGERY, hearing_args[HEARING_RAW_MESSAGE]), 10)
addtimer(CALLBACK(C, /mob/living.proc/Stun, 60, TRUE, TRUE), 15) //Take some time to think about it
qdel(src)
+
+/datum/status_effect/spasms
+ id = "spasms"
+ status_type = STATUS_EFFECT_MULTIPLE
+ alert_type = null
+
+/datum/status_effect/spasms/tick()
+ if(prob(15))
+ switch(rand(1,5))
+ if(1)
+ if((!owner.lying && !owner.buckled) && isturf(owner.loc))
+ to_chat(owner, "Your leg spasms!")
+ step(owner, pick(GLOB.cardinals))
+ if(2)
+ if(owner.incapacitated())
+ return
+ var/obj/item/I = owner.get_active_held_item()
+ if(I)
+ to_chat(owner, "Your fingers spasm!")
+ owner.log_message("used [I] due to a Muscle Spasm", LOG_ATTACK)
+ I.attack_self(owner)
+ if(3)
+ var/prev_intent = owner.a_intent
+ owner.a_intent = INTENT_HARM
+
+ var/range = 1
+ if(istype(owner.get_active_held_item(), /obj/item/gun)) //get targets to shoot at
+ range = 7
+
+ var/list/mob/living/targets = list()
+ for(var/mob/M in oview(owner, range))
+ if(isliving(M))
+ targets += M
+ if(LAZYLEN(targets))
+ to_chat(owner, "Your arm spasms!")
+ owner.log_message(" attacked someone due to a Muscle Spasm", LOG_ATTACK) //the following attack will log itself
+ owner.ClickOn(pick(targets))
+ owner.a_intent = prev_intent
+ if(4)
+ var/prev_intent = owner.a_intent
+ owner.a_intent = INTENT_HARM
+ to_chat(owner, "Your arm spasms!")
+ owner.log_message("attacked [owner.p_them()]self to a Muscle Spasm", LOG_ATTACK)
+ owner.ClickOn(owner)
+ owner.a_intent = prev_intent
+ if(5)
+ if(owner.incapacitated())
+ return
+ var/obj/item/I = owner.get_active_held_item()
+ var/list/turf/targets = list()
+ for(var/turf/T in oview(owner, 3))
+ targets += T
+ if(LAZYLEN(targets) && I)
+ to_chat(owner, "Your arm spasms!")
+ owner.log_message("threw [I] due to a Muscle Spasm", LOG_ATTACK)
+ owner.throw_item(pick(targets))
\ No newline at end of file
diff --git a/code/datums/status_effects/neutral.dm b/code/datums/status_effects/neutral.dm
index 655863e0e0..cb2b4174b5 100644
--- a/code/datums/status_effects/neutral.dm
+++ b/code/datums/status_effects/neutral.dm
@@ -69,3 +69,17 @@
/datum/status_effect/in_love/tick()
if(date)
new /obj/effect/temp_visual/love_heart/invisible(get_turf(date.loc), owner)
+
+/datum/status_effect/throat_soothed
+ id = "throat_soothed"
+ duration = 60 SECONDS
+ status_type = STATUS_EFFECT_REFRESH
+ alert_type = null
+
+/datum/status_effect/throat_soothed/on_apply()
+ . = ..()
+ ADD_TRAIT(owner, TRAIT_SOOTHED_THROAT, "[STATUS_EFFECT_TRAIT]_[id]")
+
+/datum/status_effect/throat_soothed/on_remove()
+ . = ..()
+ REMOVE_TRAIT(owner, TRAIT_SOOTHED_THROAT, "[STATUS_EFFECT_TRAIT]_[id]")
\ No newline at end of file
diff --git a/code/datums/status_effects/status_effect.dm b/code/datums/status_effects/status_effect.dm
index c8bcd831e2..6684065cc8 100644
--- a/code/datums/status_effects/status_effect.dm
+++ b/code/datums/status_effects/status_effect.dm
@@ -63,6 +63,12 @@
owner = null
qdel(src)
+/datum/status_effect/proc/refresh()
+ var/original_duration = initial(duration)
+ if(original_duration == -1)
+ return
+ duration = world.time + original_duration
+
//clickdelay/nextmove modifiers!
/datum/status_effect/proc/nextmove_modifier()
return 1
@@ -91,6 +97,9 @@
if(S.id == initial(S1.id) && S.status_type)
if(S.status_type == STATUS_EFFECT_REPLACE)
S.be_replaced()
+ else if(S.status_type == STATUS_EFFECT_REFRESH)
+ S.refresh()
+ return
else
return
var/list/arguments = args.Copy()
diff --git a/code/game/atoms.dm b/code/game/atoms.dm
index 7c9dc3f4fe..78a1869663 100644
--- a/code/game/atoms.dm
+++ b/code/game/atoms.dm
@@ -538,7 +538,7 @@
var/atom/L = loc
if(!L)
return null
- return L.AllowDrop() ? L : get_turf(L)
+ return L.AllowDrop() ? L : L.drop_location()
/atom/Entered(atom/movable/AM, atom/oldLoc)
SEND_SIGNAL(src, COMSIG_ATOM_ENTERED, AM, oldLoc)
diff --git a/code/game/objects/effects/temporary_visuals/miscellaneous.dm b/code/game/objects/effects/temporary_visuals/miscellaneous.dm
index 7d02d9d383..fb044b1ce7 100644
--- a/code/game/objects/effects/temporary_visuals/miscellaneous.dm
+++ b/code/game/objects/effects/temporary_visuals/miscellaneous.dm
@@ -313,6 +313,11 @@
randomdir = 0
duration = 6
+/obj/effect/temp_visual/desynchronizer
+ name = "desynchronizer field"
+ icon_state = "chronofield"
+ duration = 3
+
/obj/effect/temp_visual/impact_effect
icon_state = "impact_bullet"
duration = 5
diff --git a/code/game/objects/items/devices/desynchronizer.dm b/code/game/objects/items/devices/desynchronizer.dm
new file mode 100644
index 0000000000..4a6e2d5a46
--- /dev/null
+++ b/code/game/objects/items/devices/desynchronizer.dm
@@ -0,0 +1,87 @@
+/obj/item/desynchronizer
+ name = "desynchronizer"
+ desc = "An experimental device that can temporarily desynchronize the user from spacetime, effectively making them disappear while it's active."
+ icon = 'icons/obj/device.dmi'
+ icon_state = "desynchronizer"
+ item_state = "electronic"
+ w_class = WEIGHT_CLASS_SMALL
+ item_flags = NOBLUDGEON
+ lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi'
+ materials = list(MAT_METAL = 250, MAT_GLASS = 500)
+ var/max_duration = 3000
+ var/duration = 300
+ var/last_use = 0
+ var/next_use = 0
+ var/obj/effect/abstract/sync_holder/sync_holder
+
+/obj/item/desynchronizer/attack_self(mob/living/user)
+ if(world.time < next_use)
+ to_chat(user, "[src] is still recharging.")
+ return
+ if(!sync_holder)
+ desync(user)
+ else
+ resync()
+
+/obj/item/desynchronizer/examine(mob/user)
+ . = ..()
+ if(world.time < next_use)
+ . += "Time left to recharge: [DisplayTimeText(next_use - world.time)]"
+ . += "Alt-click to customize the duration. Current duration: [DisplayTimeText(duration)]."
+ . += "Can be used again to interrupt the effect early. The recharge time is the same as the time spent in desync."
+
+/obj/item/desynchronizer/AltClick(mob/living/user)
+ if(!istype(user) || !user.canUseTopic(src, BE_CLOSE, ismonkey(user)))
+ return
+ var/new_duration = input(user, "Set the duration (5-300):", "Desynchronizer", duration / 10) as null|num
+ if(new_duration)
+ new_duration = new_duration SECONDS
+ new_duration = CLAMP(new_duration, 50, max_duration)
+ duration = new_duration
+ to_chat(user, "You set the duration to [DisplayTimeText(duration)].")
+
+/obj/item/desynchronizer/proc/desync(mob/living/user)
+ if(sync_holder)
+ return
+ sync_holder = new(drop_location())
+ new /obj/effect/temp_visual/desynchronizer(drop_location())
+ to_chat(user, "You activate [src], desynchronizing yourself from the present. You can still see your surroundings, but you feel eerily dissociated from reality.")
+ user.forceMove(sync_holder)
+ SEND_SIGNAL(user, COMSIG_MOVABLE_SECLUDED_LOCATION)
+ for(var/thing in user)
+ var/atom/movable/AM = thing
+ SEND_SIGNAL(AM, COMSIG_MOVABLE_SECLUDED_LOCATION)
+ last_use = world.time
+ icon_state = "desynchronizer-on"
+ addtimer(CALLBACK(src, .proc/resync), duration)
+
+/obj/item/desynchronizer/proc/resync()
+ new /obj/effect/temp_visual/desynchronizer(sync_holder.drop_location())
+ QDEL_NULL(sync_holder)
+ icon_state = initial(icon_state)
+ next_use = world.time + (world.time - last_use) // Could be 2*world.time-last_use but that would just be confusing
+
+/obj/item/desynchronizer/Destroy()
+ resync()
+ return ..()
+
+/obj/effect/abstract/sync_holder
+ name = "desyncronized pocket"
+ desc = "A pocket in spacetime, keeping the user a fraction of a second in the future."
+ icon = null
+ icon_state = null
+ alpha = 0
+ invisibility = INVISIBILITY_ABSTRACT
+ mouse_opacity = MOUSE_OPACITY_TRANSPARENT
+ anchored = TRUE
+ resistance_flags = INDESTRUCTIBLE
+
+/obj/effect/abstract/sync_holder/Destroy()
+ for(var/I in contents)
+ var/atom/movable/AM = I
+ AM.forceMove(drop_location())
+ return ..()
+
+/obj/effect/abstract/sync_holder/AllowDrop()
+ return TRUE //no dropping spaghetti out of your spacetime pocket
\ No newline at end of file
diff --git a/code/modules/antagonists/traitor/datum_traitor.dm b/code/modules/antagonists/traitor/datum_traitor.dm
index e43c2f6254..933c32917c 100644
--- a/code/modules/antagonists/traitor/datum_traitor.dm
+++ b/code/modules/antagonists/traitor/datum_traitor.dm
@@ -49,6 +49,12 @@
A.malf_picker.remove_malf_verbs(A)
qdel(A.malf_picker)
+/datum/antagonist/traitor/proc/handle_hearing(datum/source, list/hearing_args)
+ var/message = hearing_args[HEARING_MESSAGE]
+ message = GLOB.syndicate_code_phrase_regex.Replace(message, "$1")
+ message = GLOB.syndicate_code_response_regex.Replace(message, "$1")
+ hearing_args[HEARING_MESSAGE] = message
+
SSticker.mode.traitors -= owner
if(!silent && owner.current)
to_chat(owner.current," You are no longer the [special_role]! ")
diff --git a/code/modules/client/client_colour.dm b/code/modules/client/client_colour.dm
index f1477fb4d2..22b15ea868 100644
--- a/code/modules/client/client_colour.dm
+++ b/code/modules/client/client_colour.dm
@@ -111,3 +111,6 @@
/datum/client_colour/monochrome
colour = list(rgb(77,77,77), rgb(150,150,150), rgb(28,28,28), rgb(0,0,0))
priority = INFINITY //we can't see colors anyway!
+
+/datum/client_colour/monochrome/trance
+ priority = 1
\ No newline at end of file
diff --git a/code/modules/mob/living/carbon/say.dm b/code/modules/mob/living/carbon/say.dm
index 452c8f8b78..30c962f9a5 100644
--- a/code/modules/mob/living/carbon/say.dm
+++ b/code/modules/mob/living/carbon/say.dm
@@ -17,18 +17,4 @@
if(T)
. = T.could_speak_in_language(dt)
else
- . = initial(dt.flags) & TONGUELESS_SPEECH
-
-/mob/living/carbon/hear_intercept(message, atom/movable/speaker, datum/language/message_language, raw_message, radio_freq, list/spans, message_mode)
- . = ..()
- if(!client)
- return
- for(var/T in get_traumas())
- var/datum/brain_trauma/trauma = T
- message = trauma.on_hear(message, speaker, message_language, raw_message, radio_freq)
-
- if (src.mind.has_antag_datum(/datum/antagonist/traitor))
- message = GLOB.syndicate_code_phrase_regex.Replace(message, "$1")
- message = GLOB.syndicate_code_response_regex.Replace(message, "$1")
-
- return message
+ . = initial(dt.flags) & TONGUELESS_SPEECH
\ No newline at end of file
diff --git a/code/modules/mob/living/emote.dm b/code/modules/mob/living/emote.dm
index 25d8c4d44c..c5c81e74ce 100644
--- a/code/modules/mob/living/emote.dm
+++ b/code/modules/mob/living/emote.dm
@@ -58,9 +58,9 @@
message = "coughs!"
emote_type = EMOTE_AUDIBLE
-/datum/emote/living/cough/can_run_emote(mob/user, status_check = TRUE)
+/datum/emote/living/cough/can_run_emote(mob/user, status_check = TRUE , intentional)
. = ..()
- if(user.reagents && (user.reagents.get_reagent("menthol") || user.reagents.get_reagent("peppermint_patty")))
+ if(HAS_TRAIT(user, TRAIT_SOOTHED_THROAT))
return FALSE
/datum/emote/living/dance
diff --git a/code/modules/reagents/chemistry/reagents/alcohol_reagents.dm b/code/modules/reagents/chemistry/reagents/alcohol_reagents.dm
index 88073ca5f3..6d7521c7a5 100644
--- a/code/modules/reagents/chemistry/reagents/alcohol_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/alcohol_reagents.dm
@@ -1741,6 +1741,7 @@ All effects don't start immediately, but rather get worse over time; the rate is
glass_desc = "A boozy minty hot cocoa that warms your belly on a cold night."
/datum/reagent/consumable/ethanol/peppermint_patty/on_mob_life(mob/living/carbon/M)
+ M.apply_status_effect(/datum/status_effect/throat_soothed)
M.adjust_bodytemperature(5 * TEMPERATURE_DAMAGE_COEFFICIENT, 0, BODYTEMP_NORMAL)
..()
diff --git a/code/modules/reagents/chemistry/reagents/drink_reagents.dm b/code/modules/reagents/chemistry/reagents/drink_reagents.dm
index dd51ce2bc7..ed90b975ae 100644
--- a/code/modules/reagents/chemistry/reagents/drink_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/drink_reagents.dm
@@ -785,6 +785,10 @@
glass_name = "glass of menthol"
glass_desc = "Tastes naturally minty, and imparts a very mild numbing sensation."
+/datum/reagent/consumable/menthol/on_mob_life(mob/living/L)
+ L.apply_status_effect(/datum/status_effect/throat_soothed)
+ ..()
+
/datum/reagent/consumable/grenadine
name = "Grenadine"
id = "grenadine"
diff --git a/code/modules/research/designs/bluespace_designs.dm b/code/modules/research/designs/bluespace_designs.dm
index c9d11a5a3e..ec4309b69e 100644
--- a/code/modules/research/designs/bluespace_designs.dm
+++ b/code/modules/research/designs/bluespace_designs.dm
@@ -65,6 +65,16 @@
category = list("Bluespace Designs")
departmental_flags = DEPARTMENTAL_FLAG_SCIENCE | DEPARTMENTAL_FLAG_ENGINEERING | DEPARTMENTAL_FLAG_CARGO
+/datum/design/desynchronizer
+ name = "Desynchronizer"
+ desc = "A device that can desynchronize the user from spacetime."
+ id = "desynchronizer"
+ build_type = PROTOLATHE
+ materials = list(MAT_METAL = 1000, MAT_GLASS = 500, MAT_SILVER = 1500, MAT_BLUESPACE = 1000)
+ build_path = /obj/item/desynchronizer
+ category = list("Bluespace Designs")
+ departmental_flags = DEPARTMENTAL_FLAG_SCIENCE
+
/datum/design/miningsatchel_holding
name = "Mining Satchel of Holding"
desc = "A mining satchel that can hold an infinite amount of ores."
diff --git a/code/modules/research/nanites/nanite_programs.dm b/code/modules/research/nanites/nanite_programs.dm
index a06311f26f..2a6563fe52 100644
--- a/code/modules/research/nanites/nanite_programs.dm
+++ b/code/modules/research/nanites/nanite_programs.dm
@@ -208,9 +208,6 @@ datum/nanite_program/proc/on_mob_remove()
/datum/nanite_program/proc/on_death()
return
-/datum/nanite_program/proc/on_hear(message, atom/movable/speaker, message_language, raw_message, radio_freq, list/spans, message_mode)
- return
-
/datum/nanite_program/proc/software_error(type)
if(!type)
type = rand(1,5)
diff --git a/code/modules/research/nanites/nanite_programs/sensor.dm b/code/modules/research/nanites/nanite_programs/sensor.dm
index d494ec8cdf..3ca634c708 100644
--- a/code/modules/research/nanites/nanite_programs/sensor.dm
+++ b/code/modules/research/nanites/nanite_programs/sensor.dm
@@ -378,15 +378,12 @@
target.sentence = sentence
target.inclusive = inclusive
-/datum/nanite_program/sensor/voice/on_hear(message, atom/movable/speaker, message_language, raw_message, radio_freq, list/spans, message_mode)
+/datum/nanite_program/sensor/voice/proc/on_hear(datum/source, list/hearing_args)
if(!sentence)
return
- //To make it not case sensitive
- var/low_message = lowertext(raw_message)
- var/low_sentence = lowertext(sentence)
if(inclusive)
- if(findtext(low_message, low_sentence))
+ if(findtextEx(hearing_args[HEARING_RAW_MESSAGE], sentence))
send_code()
else
- if(low_message == low_sentence)
+ if(hearing_args[HEARING_RAW_MESSAGE] == sentence)
send_code()
\ No newline at end of file
diff --git a/code/modules/research/techweb/all_nodes.dm b/code/modules/research/techweb/all_nodes.dm
index 038a41b3cd..d7b652fb41 100644
--- a/code/modules/research/techweb/all_nodes.dm
+++ b/code/modules/research/techweb/all_nodes.dm
@@ -294,6 +294,15 @@
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500)
export_price = 5000
+/datum/techweb_node/unregulated_bluespace
+ id = "unregulated_bluespace"
+ display_name = "Unregulated Bluespace Research"
+ description = "Bluespace technology using unstable or unbalanced procedures, prone to damaging the fabric of bluespace. Outlawed by galactic conventions."
+ prereq_ids = list("bluespace_travel", "syndicate_basic")
+ design_ids = list("desynchronizer")
+ research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500)
+ export_price = 2500
+
/////////////////////////plasma tech/////////////////////////
/datum/techweb_node/basic_plasma
id = "basic_plasma"
diff --git a/icons/mob/screen_alert.dmi b/icons/mob/screen_alert.dmi
index 8142e03498..d833966eff 100644
Binary files a/icons/mob/screen_alert.dmi and b/icons/mob/screen_alert.dmi differ
diff --git a/icons/obj/device.dmi b/icons/obj/device.dmi
index 032b0c27ad..469f365f88 100644
Binary files a/icons/obj/device.dmi and b/icons/obj/device.dmi differ
diff --git a/tgstation.dme b/tgstation.dme
index cceeb1f915..38834b6b60 100755
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -859,6 +859,7 @@
#include "code\game\objects\items\devices\camera_bug.dm"
#include "code\game\objects\items\devices\chameleonproj.dm"
#include "code\game\objects\items\devices\compressionkit.dm"
+#include "code\game\objects\items\devices\desynchronizer.dm"
#include "code\game\objects\items\devices\dogborg_sleeper.dm"
#include "code\game\objects\items\devices\doorCharge.dm"
#include "code\game\objects\items\devices\electroadaptive_pseudocircuit.dm"