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."