diff --git a/code/__DEFINES/components.dm b/code/__DEFINES/components.dm
index bf30b547fe..b79f1c2e59 100644
--- a/code/__DEFINES/components.dm
+++ b/code/__DEFINES/components.dm
@@ -121,7 +121,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)
#define COMSIG_MOVABLE_TELEPORTED "movable_teleported" //from base of do_teleport(): (channel, turf/origin, turf/destination)
diff --git a/code/__DEFINES/status_effects.dm b/code/__DEFINES/status_effects.dm
index ff19976cff..0f1a36d663 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 //
///////////
@@ -74,6 +76,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
+
#define STATUS_EFFECT_BREASTS_ENLARGEMENT /datum/status_effect/chem/breast_enlarger //Applied slowdown due to the ominous bulk.
#define STATUS_EFFECT_PENIS_ENLARGEMENT /datum/status_effect/chem/penis_enlarger //More applied slowdown, just like the above.
diff --git a/code/__DEFINES/traits.dm b/code/__DEFINES/traits.dm
index 2382e0e444..60212385f8 100644
--- a/code/__DEFINES/traits.dm
+++ b/code/__DEFINES/traits.dm
@@ -109,6 +109,7 @@
#define TRAIT_NOHARDCRIT "nohardcrit"
#define TRAIT_NOSOFTCRIT "nosoftcrit"
#define TRAIT_MINDSHIELD "mindshield"
+#define TRAIT_SIXTHSENSE "sixthsense"
#define TRAIT_DISSECTED "dissected"
#define TRAIT_FEARLESS "fearless"
#define TRAIT_UNSTABLE "unstable"
@@ -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"
@@ -179,6 +181,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
@@ -213,4 +216,4 @@
#define NINJA_SUIT_TRAIT "ninja-suit"
#define ANTI_DROP_IMPLANT_TRAIT "anti-drop-implant"
#define ABDUCTOR_ANTAGONIST "abductor-antagonist"
-#define MADE_UNCLONEABLE "made-uncloneable"
+#define MADE_UNCLONEABLE "made-uncloneable"
\ No newline at end of file
diff --git a/code/__HELPERS/mobs.dm b/code/__HELPERS/mobs.dm
index 8e7938a312..0d6345dae2 100644
--- a/code/__HELPERS/mobs.dm
+++ b/code/__HELPERS/mobs.dm
@@ -522,12 +522,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 3a026d2aa5..e30ce1d1ed 100644
--- a/code/_globalvars/lists/poll_ignore.dm
+++ b/code/_globalvars/lists/poll_ignore.dm
@@ -13,6 +13,8 @@
#define POLL_IGNORE_GOLEM "golem"
#define POLL_IGNORE_SWARMER "swarmer"
#define POLL_IGNORE_DRONE "drone"
+#define POLL_IGNORE_IMAGINARYFRIEND "imaginary_friend"
+#define POLL_IGNORE_SPLITPERSONALITY "split_personality"
#define POLL_IGNORE_DEMON "demon"
#define POLL_IGNORE_WIZARD "wizard"
#define POLL_IGNORE_CLONE "clone"
@@ -31,6 +33,8 @@ GLOBAL_LIST_INIT(poll_ignore_desc, list(
POLL_IGNORE_GOLEM = "Golems",
POLL_IGNORE_SWARMER = "Swarmer shells",
POLL_IGNORE_DRONE = "Drone shells",
+ POLL_IGNORE_IMAGINARYFRIEND = "Imaginary Friend",
+ POLL_IGNORE_SPLITPERSONALITY = "Split Personality",
POLL_IGNORE_DEMON = "Demons",
POLL_IGNORE_WIZARD = "Wizards",
POLL_IGNORE_CLONE = "Defective/SDGF clones"
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/hypnosis.dm b/code/datums/brain_damage/hypnosis.dm
index 8909d1b85f..f937c19658 100644
--- a/code/datums/brain_damage/hypnosis.dm
+++ b/code/datums/brain_damage/hypnosis.dm
@@ -5,6 +5,7 @@
gain_text = ""
lose_text = ""
resilience = TRAUMA_RESILIENCE_SURGERY
+
var/hypnotic_phrase = ""
var/regex/target_phrase
@@ -44,18 +45,17 @@
"You feel a part of your mind repeating this over and over. You need to follow these words.",\
"Something about this sounds... right, for some reason. You feel like you should follow these words.",\
"These words keep echoing in your mind. You find yourself completely fascinated by them.")]")
- if(!HAS_TRAIT(owner, "hypnotherapy"))
- to_chat(owner, "You've been hypnotized by this sentence. You must follow these words. If it isn't a clear order, you can freely interpret how to do so,\
+ to_chat(owner, "You've been hypnotized by this sentence. You must follow these words. If it isn't a clear order, you can freely interpret how to do so,\
as long as you act like the words are your highest priority.")
- else
- to_chat(owner, "You've been hypnotized by this sentence. You feel an incredible desire to follow these words, but are able to resist it somewhat. If it isn't a clear order, you can freely interpret how to do so,\
- however this does not take precedence over your other objectives.")
+ var/obj/screen/alert/hypnosis/hypno_alert = owner.throw_alert("hypnosis", /obj/screen/alert/hypnosis)
+ hypno_alert.desc = "\"[hypnotic_phrase]\"... your mind seems to be fixated on this concept."
..()
/datum/brain_trauma/hypnosis/on_lose()
message_admins("[ADMIN_LOOKUPFLW(owner)] is no longer hypnotized with the phrase '[hypnotic_phrase]'.")
log_game("[key_name(owner)] is no longer hypnotized with the phrase '[hypnotic_phrase]'.")
- to_chat(owner, "You suddenly snap out of your fixation. The phrase '[hypnotic_phrase]' no longer feels important to you.")
+ to_chat(owner, "You suddenly snap out of your hypnosis. The phrase '[hypnotic_phrase]' no longer feels important to you.")
+ owner.clear_alert("hypnosis")
..()
/datum/brain_trauma/hypnosis/on_life()
@@ -67,6 +67,5 @@
if(2)
new /datum/hallucination/chat(owner, TRUE, FALSE, "[hypnotic_phrase]")
-/datum/brain_trauma/hypnosis/on_hear(message, speaker, message_language, raw_message, radio_freq)
- message = target_phrase.Replace(message, "$1")
- return message
+/datum/brain_trauma/hypnosis/handle_hearing(datum/source, list/hearing_args)
+ hearing_args[HEARING_MESSAGE] = target_phrase.Replace(hearing_args[HEARING_MESSAGE], "$1")
diff --git a/code/datums/brain_damage/imaginary_friend.dm b/code/datums/brain_damage/imaginary_friend.dm
index 5bbc5de4a5..8337a8e4d5 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)
C.transfer_ckey(friend, FALSE)
@@ -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..012f771a29 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 1a26ea7a14..dfe63ce141 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)
C.transfer_ckey(stranger_backseat, FALSE)
@@ -191,13 +195,13 @@
/datum/brain_trauma/severe/split_personality/brainwashing/on_life()
return //no random switching
-/datum/brain_trauma/severe/split_personality/brainwashing/on_hear(message, speaker, message_language, raw_message, radio_freq)
- if(HAS_TRAIT(owner, TRAIT_DEAF) || owner == speaker)
- return message
+/datum/brain_trauma/severe/split_personality/brainwashing/handle_hearing(datum/source, list/hearing_args)
+ if(HAS_TRAIT(owner, TRAIT_DEAF) || owner == hearing_args[HEARING_SPEAKER])
+ return
+ var/message = hearing_args[HEARING_MESSAGE]
if(findtext(message, codeword))
- message = replacetext(message, codeword, "[codeword]")
+ hearing_args[HEARING_MESSAGE] = replacetext(message, codeword, "[codeword]")
addtimer(CALLBACK(src, /datum/brain_trauma/severe/split_personality.proc/switch_personalities), 10)
- return message
/datum/brain_trauma/severe/split_personality/brainwashing/handle_speech(datum/source, list/speech_args)
if(findtext(speech_args[SPEECH_MESSAGE], codeword))
diff --git a/code/datums/components/nanites.dm b/code/datums/components/nanites.dm
index 3f7f794435..362961a24f 100644
--- a/code/datums/components/nanites.dm
+++ b/code/datums/components/nanites.dm
@@ -53,7 +53,6 @@
RegisterSignal(parent, COMSIG_MOB_ALLOWED, .proc/check_access)
RegisterSignal(parent, COMSIG_LIVING_ELECTROCUTE_ACT, .proc/on_shock)
RegisterSignal(parent, COMSIG_LIVING_MINOR_SHOCK, .proc/on_minor_shock)
- RegisterSignal(parent, COMSIG_MOVABLE_HEAR, .proc/on_hear)
RegisterSignal(parent, COMSIG_SPECIES_GAIN, .proc/check_viable_biotype)
RegisterSignal(parent, COMSIG_NANITE_SIGNAL, .proc/receive_signal)
@@ -191,11 +190,6 @@
var/datum/nanite_program/NP = X
NP.on_death(gibbed)
-/datum/component/nanites/proc/on_hear(datum/source, message, atom/movable/speaker, message_language, raw_message, radio_freq, list/spans, message_mode)
- for(var/X in programs)
- var/datum/nanite_program/NP = X
- NP.on_hear(message, speaker, message_language, raw_message, radio_freq, spans, message_mode)
-
/datum/component/nanites/proc/receive_signal(datum/source, code, source = "an unidentified source")
for(var/X in programs)
var/datum/nanite_program/NP = X
diff --git a/code/datums/components/stationloving.dm b/code/datums/components/stationloving.dm
index 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 fb636f6911..84ed012b82 100644
--- a/code/datums/status_effects/debuffs.dm
+++ b/code/datums/status_effects/debuffs.dm
@@ -567,8 +567,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"
@@ -576,17 +575,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
@@ -594,47 +582,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 bc1cfba112..0d84aab763 100644
--- a/code/datums/status_effects/status_effect.dm
+++ b/code/datums/status_effects/status_effect.dm
@@ -64,6 +64,12 @@
owner = null
qdel(src)
+/datum/status_effect/proc/refresh()
+ var/original_duration = initial(duration)
+ if(original_duration == -1)
+ return
+ duration = world.time + original_duration
+
//clickdelay/nextmove modifiers!
/datum/status_effect/proc/nextmove_modifier()
return 1
@@ -92,6 +98,9 @@
if(S.id == initial(S1.id) && S.status_type)
if(S.status_type == STATUS_EFFECT_REPLACE)
S.be_replaced()
+ else if(S.status_type == STATUS_EFFECT_REFRESH)
+ S.refresh()
+ return
else
return
var/list/arguments = args.Copy()
diff --git a/code/game/atoms.dm b/code/game/atoms.dm
index 20fc88f34e..6f633f7c6d 100644
--- a/code/game/atoms.dm
+++ b/code/game/atoms.dm
@@ -660,7 +660,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 fbad000098..0990486ae3 100644
--- a/code/game/objects/effects/temporary_visuals/miscellaneous.dm
+++ b/code/game/objects/effects/temporary_visuals/miscellaneous.dm
@@ -315,6 +315,11 @@
randomdir = 0
duration = 6
+/obj/effect/temp_visual/desynchronizer
+ name = "desynchronizer field"
+ icon_state = "chronofield"
+ duration = 3
+
/obj/effect/temp_visual/impact_effect
icon_state = "impact_bullet"
duration = 5
diff --git a/code/game/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/game/say.dm b/code/game/say.dm
index 4ce1d3c710..60189618be 100644
--- a/code/game/say.dm
+++ b/code/game/say.dm
@@ -29,7 +29,7 @@ GLOBAL_LIST_INIT(freqtospan, list(
send_speech(message, 7, src, , spans, message_language=language)
/atom/movable/proc/Hear(message, atom/movable/speaker, message_language, raw_message, radio_freq, list/spans, message_mode)
- SEND_SIGNAL(src, COMSIG_MOVABLE_HEAR, message, speaker, message_language, raw_message, radio_freq, spans, message_mode)
+ SEND_SIGNAL(src, COMSIG_MOVABLE_HEAR, args)
/atom/movable/proc/can_speak()
return 1
diff --git a/code/modules/antagonists/traitor/datum_traitor.dm b/code/modules/antagonists/traitor/datum_traitor.dm
index 26eea3f59e..8c0f61bb1d 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 df711a6dca..6c1a2cfec9 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 fdb8dde232..bd60880324 100644
--- a/code/modules/reagents/chemistry/reagents/alcohol_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/alcohol_reagents.dm
@@ -1830,6 +1830,7 @@ All effects don't start immediately, but rather get worse over time; the rate is
value = 2
/datum/reagent/consumable/ethanol/peppermint_patty/on_mob_life(mob/living/carbon/M)
+ M.apply_status_effect(/datum/status_effect/throat_soothed)
M.adjust_bodytemperature(5 * TEMPERATURE_DAMAGE_COEFFICIENT, 0, BODYTEMP_NORMAL)
..()
diff --git a/code/modules/reagents/chemistry/reagents/drink_reagents.dm b/code/modules/reagents/chemistry/reagents/drink_reagents.dm
index 7cecfc016d..63e3b46fed 100644
--- a/code/modules/reagents/chemistry/reagents/drink_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/drink_reagents.dm
@@ -786,6 +786,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..535b92c6e1 100644
--- a/code/modules/research/nanites/nanite_programs/sensor.dm
+++ b/code/modules/research/nanites/nanite_programs/sensor.dm
@@ -345,6 +345,14 @@
var/sentence = ""
var/inclusive = TRUE
+
+/datum/nanite_program/sensor/voice/on_mob_add()
+ . = ..()
+ RegisterSignal(host_mob, COMSIG_MOVABLE_HEAR, .proc/on_hear)
+
+/datum/nanite_program/sensor/voice/on_mob_remove()
+ UnregisterSignal(host_mob, COMSIG_MOVABLE_HEAR, .proc/on_hear)
+
/datum/nanite_program/sensor/voice/set_extra_setting(user, setting)
if(setting == "Sent Code")
var/new_code = input(user, "Set the sent code (1-9999):", name, null) as null|num
@@ -378,15 +386,12 @@
target.sentence = sentence
target.inclusive = inclusive
-/datum/nanite_program/sensor/voice/on_hear(message, atom/movable/speaker, message_language, raw_message, radio_freq, list/spans, message_mode)
+/datum/nanite_program/sensor/voice/proc/on_hear(datum/source, list/hearing_args)
if(!sentence)
return
- //To make it not case sensitive
- var/low_message = lowertext(raw_message)
- var/low_sentence = lowertext(sentence)
if(inclusive)
- if(findtext(low_message, low_sentence))
+ if(findtextEx(hearing_args[HEARING_RAW_MESSAGE], sentence))
send_code()
else
- if(low_message == low_sentence)
+ if(hearing_args[HEARING_RAW_MESSAGE] == sentence)
send_code()
\ No newline at end of file
diff --git a/code/modules/research/techweb/all_nodes.dm b/code/modules/research/techweb/all_nodes.dm
index a2482c49a8..c0aa4a3127 100644
--- a/code/modules/research/techweb/all_nodes.dm
+++ b/code/modules/research/techweb/all_nodes.dm
@@ -322,6 +322,15 @@
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500)
export_price = 5000
+/datum/techweb_node/unregulated_bluespace
+ id = "unregulated_bluespace"
+ display_name = "Unregulated Bluespace Research"
+ description = "Bluespace technology using unstable or unbalanced procedures, prone to damaging the fabric of bluespace. Outlawed by galactic conventions."
+ prereq_ids = list("bluespace_warping", "syndicate_basic")
+ design_ids = list("desynchronizer")
+ research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500)
+ export_price = 2500
+
/////////////////////////plasma tech/////////////////////////
/datum/techweb_node/basic_plasma
id = "basic_plasma"
diff --git a/icons/mob/screen_alert.dmi b/icons/mob/screen_alert.dmi
index d724747a50..30b8eb8f9a 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 db0e5a301a..f467da6fbf 100644
Binary files a/icons/obj/device.dmi and b/icons/obj/device.dmi differ
diff --git a/tgstation.dme b/tgstation.dme
index f5dc622315..8fe7413751 100755
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -879,6 +879,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"