diff --git a/code/__HELPERS/maths.dm b/code/__HELPERS/maths.dm
index e3bf9e2acb3..9789b63b5ca 100644
--- a/code/__HELPERS/maths.dm
+++ b/code/__HELPERS/maths.dm
@@ -148,6 +148,65 @@
var/a = arccos(x / sqrt(x*x + y*y))
return y >= 0 ? a : -a
+// Floating Point Hyperbolic Functions
+/**
+ * Returns the Hyperbolic Sine of a given value.
+ * See: https://en.wikipedia.org/wiki/Hyperbolic_functions
+ */
+/proc/fsinh(x)
+ if (x == 0)
+ return 0
+
+ return ((NUM_E ** x) - (NUM_E ** -x)) / 2
+
+/**
+ * Returns the Hyperbolic Cosecant of a given value.
+ * Will return +INFINITY if x = 0
+ * See: https://en.wikipedia.org/wiki/Hyperbolic_functions
+ */
+/proc/fcsch(x)
+ if (x == 0)
+ return INFINITY
+
+ return 1 / fsinh(x)
+
+/**
+ * Returns the Hyperbolic Cosine of a given value.
+ * See: https://en.wikipedia.org/wiki/Hyperbolic_functions
+ */
+/proc/fcosh(x)
+ return ((NUM_E ** x) + (NUM_E ** -x)) / 2
+
+/**
+ * Returns the Hyperbolic Secant of a given value.
+ * See: https://en.wikipedia.org/wiki/Hyperbolic_functions
+ */
+/proc/fsech(x)
+ return 1 / fcosh(x)
+
+/**
+ * Returns the Hyperbolic Tangent of a given value.
+ * See: https://en.wikipedia.org/wiki/Hyperbolic_functions
+ */
+/proc/ftanh(x)
+ if (x == 0)
+ return 0
+
+ var/exTwoP = NUM_E ** (2 * x)
+ return (exTwoP - 1) / (exTwoP + 1)
+
+/**
+ * Returns the Hyperbolic Cotangent of a given value.
+ * Will return +INFINITY if x = 0
+ * See: https://en.wikipedia.org/wiki/Hyperbolic_functions
+ */
+/proc/fcoth(x)
+ if (x == 0)
+ return INFINITY
+
+ var/exTwoP = NUM_E ** (2 * x)
+ return (exTwoP + 1) / (exTwoP - 1)
+
/// Value or the next integer in a positive direction: Ceil(-1.5) = -1 , Ceil(1.5) = 2
#define Ceil(value) ( -round(-(value)) )
diff --git a/code/modules/psionics/abilities/read_mind.dm b/code/modules/psionics/abilities/read_mind.dm
index 203764951e4..0db11f9d014 100644
--- a/code/modules/psionics/abilities/read_mind.dm
+++ b/code/modules/psionics/abilities/read_mind.dm
@@ -23,10 +23,7 @@
read_mind(hit_atom, user)
/obj/item/spell/read_mind/proc/read_mind(atom/hit_atom, mob/user)
- if(!isliving(hit_atom))
- return
-
- if(!isliving(user))
+ if(!isliving(hit_atom) || !isliving(user))
return
var/mob/living/target = hit_atom
@@ -48,36 +45,79 @@
user.visible_message(SPAN_WARNING("[user] lays a palm on [hit_atom]'s forehead..."))
var/question
if(!safe_mode)
- question = sanitize(input(user, "Ask your question.", "Read Mind") as null|text)
+ question = tgui_input_text(user, "Ask your question.", "Read Mind", timeout = 1 MINUTE)
if((!safe_mode && !question) || user.incapacitated())
return TRUE
- var/started_mindread = world.time
- if(target.has_psi_aug())
- to_chat(user, SPAN_NOTICE("Your psyche links with [target]'s psi-receiver, seeking [safe_mode ? "their surface thoughts." : "an answer from their mind's surface: [question]"]"))
- to_chat(target, SPAN_NOTICE("[user]'s psyche links with your psi-receiver. [safe_mode ? "What are you thinking about, currently?" : "You cannot avoid the following question, and must answer truthfully: [question]"]"))
- else
- to_chat(user, SPAN_NOTICE("You dip your mentality into the surface layer of \the [target]'s mind, seeking an answer: [question]"))
- to_chat(target, SPAN_NOTICE("Your mind is compelled to answer. [safe_mode ? "What are you thinking about, currently?" : "You cannot avoid the following question, and must answer truthfully: [question]"]"))
- var/answer = sanitize(input(target, "[question]\n[safe_mode ? "You must answer with what you are currently thinking about." : "You must answer truthfully."]\nYou have 25 seconds to type a response.", "Read Mind") as null|text)
- if(!answer || world.time > started_mindread + 25 SECONDS || user.stat != CONSCIOUS)
+ to_chat(user, SPAN_NOTICE(\
+ target_sensitivity >= PSI_RANK_SENSITIVE \
+ ? "Your consciousness mingles with [target]'s thoughts, imploring them to share an answer: [question]"\
+ : "You dip your mentality into the surface layer of \the [target]'s mind, seeking an answer: [question]"))
+ to_chat(target, SPAN_NOTICE("Your mind is compelled to answer." \
+ + safe_mode \
+ /* If the target wins the psi-sensitivity check, they're prompted to say whatever they want.*/\
+ ? "What are you thinking about, currently?" \
+ /* And if the caster wins, the target is forced to answer a specific question.*/\
+ : "You cannot avoid the following question, and must answer truthfully: " \
+ + "[question]"))
+
+ // Attempt to get an answer from the target.
+ var/answer = tgui_input_text(\
+ target,\
+ "[question]\n"\
+ + safe_mode \
+ ? "You must answer with what you are currently thinking about.\n" \
+ : "You must answer truthfully.\n"\
+ + "You have one minute to type a response.", \
+ "Read Mind", \
+ timeout = 1 MINUTE)
+
+ // Exit early if the target failed to answer.
+ if(!answer || user.stat != CONSCIOUS)
to_chat(user, SPAN_NOTICE("You receive nothing useful from \the [target]."))
to_chat(target, SPAN_NOTICE("Your mind blanks out momentarily."))
+ return
+
+ if (target_sensitivity < 0)
+ // Negative sensitivity (of target) case. Underdeveloped Zona Bovinae return a scrambled message.
+ var/scrambled_message = stars(answer, (abs(target_sensitivity) * 25))
+ to_chat(user, SPAN_NOTICE("You tease a half-formed thought from [target]'s mind: [scrambled_message]"))
+ to_chat(target, SPAN_NOTICE("Your answer to [user] arrives vaguely as: [scrambled_message]"))
+ else if(safe_mode)
+ to_chat(user, SPAN_NOTICE("You skim the first thoughts in [target]'s mind: [answer]"))
+ to_chat(target, SPAN_NOTICE("you answer [user] with: [answer]"))
else
- if (target_sensitivity < 0)
- // Negative sensitivity (of target) case. Underdeveloped Zona Bovinae return a scrambled message.
- var/scrambled_message = stars(answer, (abs(target_sensitivity) * 25))
- to_chat(user, SPAN_NOTICE("You tease a half-formed thought from [target]'s mind: [scrambled_message]"))
- else if(safe_mode)
- to_chat(user, SPAN_NOTICE("You skim the first thoughts in [target]'s mind: [answer]"))
- else
- to_chat(user, SPAN_NOTICE("You pry the answer to your question from [target]'s mind: [answer]"))
- msg_admin_attack("[key_name(user)] read mind of [key_name(target)] [safe_mode ? "skimming their surface thoughts" : "forcing them to answer truthfully with question \"[question]\""] and [answer?"got answer \"[answer]\".":"got no answer."]")
- if(safe_mode)
- target.confused += 15
- target.adjustBrainLoss(10)
- to_chat(target, SPAN_WARNING("You feel somewhat nauseated, and a headache's come up too..."))
- else
- target.adjustBrainLoss(20)
- target.confused += 20
- to_chat(target, SPAN_DANGER("Your head feels like it's going to explode, and you feel nauseated..."))
+ to_chat(user, SPAN_NOTICE("You pry the answer to your question from [target]'s mind: [answer]"))
+ to_chat(target, SPAN_NOTICE("You are compelled to answer [user] with: [answer]"))
+
+ msg_admin_attack("[key_name(user)] read mind of [key_name(target)] " \
+ + safe_mode \
+ ? "skimming their surface thoughts " \
+ : "forcing them to answer truthfully with question: \"[question]\" " \
+ + "and " \
+ + answer \
+ ? "got answer: \"[answer]\"." \
+ : "got no answer.")
+
+ // We take the logistic curve of the psi_sensitivity in order to create a double asymptote that confines the effects without requiring truncating to 0, or setting a maximum.
+ // Maximums occur at +/- infinity. While normal values roughly around 0.5x occur very close to +/- 1.
+ // "More sensitivity" reduces negative effects of the power.
+ // "Less sensitivity" increases said negative effects.
+ // See this graph for a visual explanation of what this is doing: https://www.desmos.com/calculator/dl8zapsnsn
+ var/psi_sensitivity_ratio = (20 - (ftanh(0.5 * target_sensitivity) * 20)) * (safe_mode ? 0.5 : 1)
+
+ // Minimum of confusion time 0 seconds, maximum of 40 seconds. 20 seconds for an unaugmented human.
+ target.confused += psi_sensitivity_ratio SECONDS
+
+ // Minimum brain damage of 0, maximum of 20%. 10% for a unaugmented human.
+ target.adjustBrainLoss(psi_sensitivity_ratio)
+
+ to_chat(target, \
+ target_sensitivity >= PSI_RANK_SENSITIVE \
+ /* Case for high-sensitivity.*/\
+ ? SPAN_NOTICE("Your mind reels as it resists a blast of psychic pressure.")\
+ : target_sensitivity >= 0 \
+ /* Case for standard-human psi-sensitivity.*/\
+ ? SPAN_DANGER("Your head feels like it's going to explode, and you feel nauseated...")\
+ /* Case for low-sensitivity.*/\
+ : SPAN_DANGER("You black out briefly, awakening to a world that inexplicably smells like burnt toast."))
diff --git a/html/changelogs/hellfirejag-read-mind-refactoring.yml b/html/changelogs/hellfirejag-read-mind-refactoring.yml
new file mode 100644
index 00000000000..583eb2cadef
--- /dev/null
+++ b/html/changelogs/hellfirejag-read-mind-refactoring.yml
@@ -0,0 +1,6 @@
+author: Hellfirejag
+delete-after: True
+changes:
+ - code_imp: "Added a library of Floating Point Hyperbolic functions. I just wanted Hyperbolic Tangent but figured I might as well add the whole set."
+ - rscadd: "Reworked the Read Mind power to be more interactive with psi-sensitivity. It also now gives feedback to the victim on what message was sent."
+ - bugfix: "Fixed improper sanitization for Read Mind messages."