mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-08-23 05:00:55 +01:00
## About The Pull Request Notable changes: - Obsessed get a slight strength buff related to their obsession - When grabbing any of your targets (your obsession included), your grab is harder to escape from, and cause (slightly) more stamina damage from failed escapes - Obsessed also get a slight strength buff while standing near their obsession (for at least 20 seconds). This effect is applied instantly if the obsession is incapacitated. - The grab strength bonus applies to all mobs, rather than just your targets - You get a 25% reduction to incoming stamina damage - Psicodine or Alcohol will prevent Obsession stammering / emote tells, but also prevents these beneficial effects from activating - Witnessing the death of your obsession grants you a permanent positive moodlet (until they are revived) - Obsessed tells have been reworked a bit, being less obvious in general and having their chance to trigger reduced the longer you stand around your obsession (resetting after leaving their view) - Obsessed stammer was also reworked a bit, now being closet to Social Anxiety's stammer, also reducing in severity the longer you stand around your obsession (resetting after leaving their view) - Obsessed antag status is now lost when transferring out of the affected brain and gained when transferring into the affected brain, making it play nicer with slime clones or mindswap Other changes: - Modernized a bunch of obsession code - Replaced hardcoded checks for grab resistance with a signal ## Why It's Good For The Game I wanted to give Obsession just a *little* something to help enable them. I figure grab strength and pain resistance is a pretty fitting / flavorful place to start. The grab strength is intended to give them more ability to detain their targets (by means of wrestling). Even a passive grab isn't a guaranteed escape. The stamina damage resistance is intended to give them a slight chance to resist being detained by security. Not a significant one, just a slight one. ## Changelog 🆑 Melbert balance: Obsessions get moderately stronger grabs against their objective targets (this includes their obsession and any other kill/theft targets they may have). balance: While standing near their obsession (for 20 seconds or if they are incapacitated), Obsessions get slightly stronger grabs against anyone, rather than just their objective targets. balance: While standing near their obsession (for 20 seconds or if they are incapacitated), Obsessions get slight stamina damage resistance. balance: Obsession stammering has been reduced in severity a bit, and also becomes less likely the longer you stand around your obsession. balance: Obsession emotes have been made a bit less obvious, and also become less likely the longer you stand around your obsession. balance: Taking Psicodine or drinking alcohol will prevent Obsessions from stammering or emoting around their obsession, but also disables their stamina resistance and stronger grabs. balance: Obsessions get a "permanent" mood bonus for killing their obsession (until they are revived). refactor: Obsessed has been refactored significantly. It will now play a lot nicer with mindswaps. Report any oddities. refactor: Grab strength has been refactored a bit, primarily affects martial arts and drunken brawling. Report any oddities. /🆑
348 lines
12 KiB
Plaintext
348 lines
12 KiB
Plaintext
/datum/status_effect/speech
|
|
id = STATUS_EFFECT_ID_ABSTRACT
|
|
alert_type = null
|
|
remove_on_fullheal = TRUE
|
|
tick_interval = STATUS_EFFECT_NO_TICK
|
|
/// If TRUE, TTS will say the original message rather than what we changed it to
|
|
var/make_tts_message_original = FALSE
|
|
/// If set, this will be appended to the TTS filter of the message
|
|
var/tts_filter = ""
|
|
|
|
/datum/status_effect/speech/on_creation(mob/living/new_owner, duration = 10 SECONDS)
|
|
src.duration = duration
|
|
return ..()
|
|
|
|
/datum/status_effect/speech/on_apply()
|
|
RegisterSignal(owner, COMSIG_LIVING_TREAT_MESSAGE, PROC_REF(handle_message))
|
|
return TRUE
|
|
|
|
/datum/status_effect/speech/on_remove()
|
|
UnregisterSignal(owner, COMSIG_LIVING_TREAT_MESSAGE)
|
|
|
|
/**
|
|
* Signal proc for [COMSIG_LIVING_TREAT_MESSAGE]
|
|
*
|
|
* Iterates over all of the characters in the passed message
|
|
* and calls apply_speech() on each.
|
|
*/
|
|
/datum/status_effect/speech/proc/handle_message(datum/source, list/message_args)
|
|
SIGNAL_HANDLER
|
|
|
|
var/phrase = html_decode(message_args[TREAT_MESSAGE_ARG])
|
|
if(!length(phrase))
|
|
return
|
|
|
|
var/final_phrase = ""
|
|
var/list/words = splittext(phrase, " ")
|
|
var/list/new_words = list()
|
|
for(var/i in 1 to length(words))
|
|
new_words += apply_speech(words[i], i)
|
|
final_phrase = jointext(new_words, " ")
|
|
|
|
if(final_phrase == phrase)
|
|
return // No change was done, whatever
|
|
|
|
if(length(tts_filter) > 0)
|
|
message_args[TREAT_TTS_FILTER_ARG] += tts_filter
|
|
if(make_tts_message_original)
|
|
message_args[TREAT_TTS_MESSAGE_ARG] = message_args[TREAT_MESSAGE_ARG]
|
|
|
|
message_args[TREAT_MESSAGE_ARG] = sanitize(final_phrase)
|
|
|
|
/**
|
|
* Applies the speech effects on the past character, changing
|
|
* the original_word into some modified_word
|
|
*
|
|
* Return the newly modified word
|
|
*/
|
|
/datum/status_effect/speech/proc/apply_speech(original_word, index)
|
|
stack_trace("[type] didn't implement apply_speech.")
|
|
return original_word
|
|
|
|
/datum/status_effect/speech/stutter
|
|
id = "stutter"
|
|
make_tts_message_original = TRUE
|
|
tts_filter = "tremolo=f=10:d=0.8,rubberband=tempo=0.5"
|
|
|
|
/// The probability of adding a stutter to any word
|
|
var/stutter_prob = 75
|
|
/// The chance of a four character stutter
|
|
var/four_char_chance = 10
|
|
/// The chance of a three character stutter
|
|
var/three_char_chance = 20
|
|
/// The chance of a two character stutter
|
|
var/two_char_chance = 90
|
|
/// Regex to apply generically to stuttered words
|
|
/// * 1st capture group is any leading invalid characters (can be empty)
|
|
/// * 2nd capture group is either a digraph (th, qu, ch) or a consonant
|
|
/// * 3rd capture group is the rest of the word (can be empty)
|
|
VAR_FINAL/static/regex/stutter_regex
|
|
|
|
/datum/status_effect/speech/stutter/on_creation(mob/living/new_owner, ...)
|
|
stutter_regex ||= regex(@@^([\s"'()[\]{}.!?,:;_`~-]*\b)([^aeoiuh\d]h|qu|[^\d])(.*)@, "i")
|
|
return ..()
|
|
|
|
/datum/status_effect/speech/stutter/apply_speech(original_word, index)
|
|
if(!prob(stutter_prob))
|
|
return original_word
|
|
|
|
if(stutter_regex.Find(original_word))
|
|
return "[stutter_regex.group[1]][stutter_char(stutter_regex.group[2])][stutter_regex.group[3]]"
|
|
|
|
return original_word // i give up
|
|
|
|
/datum/status_effect/speech/stutter/proc/stutter_char(some_char)
|
|
if(prob(four_char_chance))
|
|
return "[some_char]-[some_char]-[some_char]-[some_char]"
|
|
if(prob(three_char_chance))
|
|
return "[some_char]-[some_char]-[some_char]"
|
|
if(prob(two_char_chance))
|
|
return "[some_char]-[some_char]"
|
|
|
|
return some_char
|
|
|
|
/datum/status_effect/speech/stutter/anxiety
|
|
id = "anxiety_stutter"
|
|
stutter_prob = 5
|
|
four_char_chance = 4
|
|
three_char_chance = 10
|
|
two_char_chance = 100
|
|
remove_on_fullheal = FALSE
|
|
|
|
/datum/status_effect/speech/stutter/anxiety/handle_message(datum/source, list/message_args)
|
|
if(HAS_TRAIT(owner, TRAIT_FEARLESS) || HAS_TRAIT(owner, TRAIT_SIGN_LANG))
|
|
stutter_prob = 0
|
|
else
|
|
var/datum/quirk/social_anxiety/host_quirk = owner.get_quirk(/datum/quirk/social_anxiety)
|
|
stutter_prob = clamp(host_quirk?.calculate_mood_mod() * 0.5, 5, 50)
|
|
return ..()
|
|
|
|
/datum/status_effect/speech/stutter/obsession
|
|
id = "obsession_stutter"
|
|
stutter_prob = 25
|
|
four_char_chance = 5
|
|
three_char_chance = 25
|
|
two_char_chance = 100
|
|
remove_on_fullheal = FALSE
|
|
|
|
COOLDOWN_DECLARE(stutter_cooldown)
|
|
|
|
/datum/status_effect/speech/stutter/obsession/handle_message(datum/source, list/message_args)
|
|
if(should_stutter())
|
|
var/datum/brain_trauma/special/obsessed/obsession_trauma = get_obsession()
|
|
stutter_prob = initial(stutter_prob)
|
|
four_char_chance = initial(four_char_chance) * max(1 - (obsession_trauma.time_spend_creeping / (10 SECONDS)), 0.01)
|
|
three_char_chance = initial(three_char_chance) * max(1 - (obsession_trauma.time_spend_creeping / (20 SECONDS)), 0.01)
|
|
two_char_chance = initial(two_char_chance) * max(1 - (obsession_trauma.time_spend_creeping / (40 SECONDS)), 0.01)
|
|
else
|
|
stutter_prob = 0
|
|
return ..()
|
|
|
|
/datum/status_effect/speech/stutter/obsession/apply_speech(original_word, index)
|
|
. = ..()
|
|
if(. == original_word || !should_stutter())
|
|
return
|
|
var/datum/brain_trauma/special/obsessed/obsession_trauma = get_obsession()
|
|
to_chat(owner, span_warning("Being near [obsession_trauma.obsession.real_name] makes you nervous and stutter..."))
|
|
COOLDOWN_START(src, stutter_cooldown, rand(4, 8) SECONDS)
|
|
|
|
/datum/status_effect/speech/stutter/obsession/proc/get_obsession()
|
|
if(!ishuman(owner))
|
|
return null
|
|
|
|
var/mob/living/carbon/human/human_owner = owner
|
|
return human_owner.has_trauma_type(/datum/brain_trauma/special/obsessed)
|
|
|
|
/datum/status_effect/speech/stutter/obsession/proc/should_stutter()
|
|
if(HAS_TRAIT(owner, TRAIT_FEARLESS))
|
|
return FALSE
|
|
if(!COOLDOWN_FINISHED(src, stutter_cooldown))
|
|
return FALSE
|
|
var/datum/brain_trauma/special/obsessed/obsession_trauma = get_obsession()
|
|
if(isnull(obsession_trauma) || !obsession_trauma.viewing || obsession_trauma.witnessed_death)
|
|
return FALSE
|
|
return TRUE
|
|
|
|
/datum/status_effect/speech/stutter/derpspeech
|
|
id = "derp_stutter"
|
|
/// The probability of making our message entirely uppercase + adding exclamations
|
|
var/capitalize_prob = 50
|
|
/// The probability of adding a stutter to the entire message, if we're not already stuttering
|
|
var/message_stutter_prob = 15
|
|
|
|
/datum/status_effect/speech/stutter/derpspeech/handle_message(datum/source, list/message_args)
|
|
|
|
var/message = html_decode(message_args[TREAT_MESSAGE_ARG])
|
|
|
|
message = replacetext(message, " am ", " ")
|
|
message = replacetext(message, " is ", " ")
|
|
message = replacetext(message, " are ", " ")
|
|
message = replacetext(message, "you", "u")
|
|
message = replacetext(message, "help", "halp")
|
|
message = replacetext(message, "grief", "grife")
|
|
message = replacetext(message, "space", "spess")
|
|
message = replacetext(message, "carp", "crap")
|
|
message = replacetext(message, "reason", "raisin")
|
|
|
|
if(prob(capitalize_prob))
|
|
var/exclamation = pick("!", "!!", "!!!")
|
|
message = uppertext(message)
|
|
message += "[stutter_char(exclamation)]"
|
|
|
|
message_args[TREAT_MESSAGE_ARG] = message
|
|
|
|
var/mob/living/living_source = source
|
|
if(!isliving(source) || living_source.has_status_effect(/datum/status_effect/speech/stutter))
|
|
return
|
|
|
|
// If we're not stuttering, we have a chance of calling parent here, adding stutter effects
|
|
if(prob(message_stutter_prob))
|
|
return ..()
|
|
|
|
// Otherwise just return and don't call parent, we already modified our speech
|
|
return
|
|
|
|
/datum/status_effect/speech/slurring
|
|
/// The chance that any given character in a message will be replaced with a common character
|
|
var/common_prob = 25
|
|
/// The chance that any given character in a message will be replaced with an uncommon character
|
|
var/uncommon_prob = 10
|
|
/// The chance that any given character will be entirely replaced with a new string / will have a string appended onto it
|
|
var/replacement_prob = 5
|
|
/// The chance that any given character will be doubled, or even tripled
|
|
var/doubletext_prob = 0
|
|
|
|
/// The file we pull text modifications from
|
|
var/text_modification_file = ""
|
|
|
|
/// Common replacements for characters - populated in on_creation
|
|
var/list/common_replacements
|
|
/// Uncommon replacements for characters - populated in on_creation
|
|
var/list/uncommon_replacements
|
|
/// Strings that fully replace a character - populated in on_creation
|
|
var/list/string_replacements
|
|
/// Strings that are appended to a character - populated in on_creation
|
|
var/list/string_additions
|
|
|
|
/// Regex for characters we won't apply any slurring to
|
|
var/static/regex/no_slur
|
|
|
|
/// If the last character we processed was a replacement, don't do another replacement right after it
|
|
VAR_PRIVATE/replacement_dupe_check = FALSE
|
|
|
|
/datum/status_effect/speech/slurring/on_creation(mob/living/new_owner, duration = 10 SECONDS)
|
|
. = ..()
|
|
if(!.)
|
|
return
|
|
|
|
if(!text_modification_file)
|
|
CRASH("[type] was created without a text modification file.")
|
|
|
|
var/list/speech_changes = strings(text_modification_file, "replacements")
|
|
common_replacements = speech_changes["characters"]["common"]
|
|
uncommon_replacements = speech_changes["characters"]["uncommon"]
|
|
string_replacements = speech_changes["string_replacements"]
|
|
string_additions = speech_changes["string_additions"]
|
|
|
|
no_slur ||= regex(@@[ "'()[\]{}.!?,:;_`~-]@)
|
|
|
|
/datum/status_effect/speech/slurring/apply_speech(original_word, index)
|
|
var/original_char = ""
|
|
var/modified_word = ""
|
|
for(var/i = 1, i <= length(original_word), i += length(original_char))
|
|
original_char = original_word[i]
|
|
modified_word += slur_character(original_char, i)
|
|
return modified_word
|
|
|
|
/datum/status_effect/speech/slurring/proc/slur_character(original_char, index)
|
|
var/modified_char = original_char
|
|
var/allow_slurring = index != 1 && !no_slur.Find(modified_char)
|
|
var/lower_char = LOWER_TEXT(modified_char)
|
|
if(prob(common_prob) && (lower_char in common_replacements))
|
|
var/to_replace = common_replacements[lower_char]
|
|
modified_char = islist(to_replace) ? pick(to_replace) : to_replace
|
|
replacement_dupe_check = FALSE
|
|
|
|
else if(prob(uncommon_prob) && (modified_char in uncommon_replacements))
|
|
var/to_replace = uncommon_replacements[modified_char]
|
|
modified_char = islist(to_replace) ? pick(to_replace) : to_replace
|
|
replacement_dupe_check = FALSE
|
|
|
|
else if(allow_slurring) // Don't do replacements on the first character, or punctuation
|
|
if(!replacement_dupe_check && prob(replacement_prob))
|
|
var/replacements_len = length(string_replacements)
|
|
var/additions_len = length(string_additions)
|
|
if(replacements_len && additions_len)
|
|
// Calculate the probability of grabbing a replacement vs an addition
|
|
var/weight = (replacements_len + additions_len) / replacements_len * 100
|
|
if(prob(weight))
|
|
modified_char = pick(string_replacements)
|
|
else
|
|
modified_char += pick(string_additions)
|
|
|
|
else if(replacements_len)
|
|
modified_char = pick(string_replacements)
|
|
|
|
else if(additions_len)
|
|
modified_char += pick(string_additions)
|
|
replacement_dupe_check = TRUE
|
|
|
|
else if(prob(doubletext_prob))
|
|
modified_char += "[modified_char][prob(50) ? "" : modified_char]"
|
|
replacement_dupe_check = FALSE
|
|
|
|
else
|
|
// If we don't allow replacements we don't want the next character to be a replacement either
|
|
// This makes some more coherent speech by disallowing structures like "Y'''e"
|
|
replacement_dupe_check = TRUE
|
|
|
|
return modified_char
|
|
|
|
/datum/status_effect/speech/slurring/generic
|
|
id = "generic_slurring"
|
|
common_prob = 33
|
|
uncommon_prob = 0
|
|
replacement_prob = 24
|
|
doubletext_prob = 40
|
|
text_modification_file = "slurring_drunk_text.json"
|
|
|
|
/datum/status_effect/speech/slurring/drunk
|
|
id = "drunk_slurring"
|
|
// These defaults are updated when speech event occur.
|
|
common_prob = -1
|
|
uncommon_prob = -1
|
|
replacement_prob = -1
|
|
doubletext_prob = -1
|
|
text_modification_file = "slurring_drunk_text.json"
|
|
|
|
/datum/status_effect/speech/slurring/drunk/handle_message(datum/source, list/message_args)
|
|
var/current_drunkness = owner.get_drunk_amount()
|
|
// These numbers are arbitarily picked
|
|
// Common replacements start at about 20, and maxes out at about 85
|
|
common_prob = clamp((current_drunkness * 0.8) - 16, 4, 50)
|
|
// Uncommon replacements (burping) start at 50 and max out at 110 (when you are dying)
|
|
uncommon_prob = clamp((current_drunkness * 0.3) - 10, 0, 12)
|
|
// Replacements start at 20 and max out at about 60
|
|
replacement_prob = clamp((current_drunkness * 0.6) - 8, 0, 12)
|
|
// Double texting start out at about 25 and max out at about 60
|
|
doubletext_prob = clamp((current_drunkness * 0.8) - 8, 2, 50)
|
|
return ..()
|
|
|
|
/datum/status_effect/speech/slurring/cult
|
|
id = "cult_slurring"
|
|
common_prob = 50
|
|
uncommon_prob = 25
|
|
replacement_prob = 33
|
|
doubletext_prob = 0
|
|
text_modification_file = "slurring_cult_text.json"
|
|
|
|
tts_filter = "rubberband=pitch=0.5,vibrato=5"
|
|
|
|
/datum/status_effect/speech/slurring/heretic
|
|
id = "heretic_slurring"
|
|
common_prob = 50
|
|
uncommon_prob = 20
|
|
replacement_prob = 30
|
|
doubletext_prob = 5
|
|
text_modification_file = "slurring_heretic_text.json"
|