mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2025-12-11 10:11:09 +00:00
## About The Pull Request 1. Hallucination effects are now tiered Hallucinations now all have tiers, ranging from common to special. If you are just hallucinating a teeny bit, you will not experience the more extreme hallucinations, like bubblegum or your mom. But if you're hallucinating off your butt, you will be a bit more likely to experience them. 2. Hallucination rate has been tweaked Default hallucination cooldown is now 20-80 seconds, up from 10-60 seconds. However the cooldown will *also* vary depending on just how much you're hallucinating, going down to 10-40 seconds. 3. RDS is now capped a bit lower (meaning you don't see the higher tiers like bubblegum). But I added a preference to uncap it. For the people who actually like bubblegum visits. 4. If a hallucination fails to trigger, the cooldown will partially reset. (by 75%) 5. "Fake chat" hallucination will pick more viable subjects. Fake chat will try to find someone who can actually speak your language, rather than make a monkey speak mothic or something. (I may revisit this so if you're super-hallucinating it reverts to old behavior though.) 6. Adds a hallucination: Fake blood You hallucinate that you start bleeding, very simple. 7. Adds a hallucination: Fake telepathy You hallucinate a random telepathic message, similar to fake chat. 8. Adds a hallucination: Eyes in the Dark A nearby dark turf will have a set of glowing red eyes shine through the dark. A classic. 9. Adds some new sub-hallucination: PDA ringtone (fake sound), summon guns/magic (fake item) Funny prank. 10. Makes mindbreaker a bit more effective at combating RDS. Pretty much does nothing right now unless you gulp like 50u. ## Why It's Good For The Game Hallucinations are pretty one note if you experience them for longer than 10 minutes. This is due to two fold: - Many hallucinations are goofy, and not subtle - Hallucinations trigger very rapidly You will never fall for a hallucination because in between "You see John Greytide put the blueprints away", you get your mom yelling at you, everyone looking like syndies (again), and bubblegum This pr addresses it by - Limiting the wacky hallucinations for when you're really off your gourd - Reducing the period between triggers - Adding a few hallucinations If the wackier hallucinations are reserved for when you're really off your rocker, this lets the more subtle ones sink in over time, leaves more room for second guessing ## Changelog 🆑 Melbert add: Adds 4-5 new hallucinations. Collect them all. balance: If you are only hallucinating a little bit, the game will prefer to pick more subtle hallucinations. If you are hallucinating a ton, it will prefer the more wacky hallucinations. balance: If you are only hallucinating a little bit, the cooldown between hallucinations is longer. If you are hallucinating a ton, it will be shorter. balance: If a hallucination fails to trigger (such as a deaf person getting a sound hallucination) the next one will be a lot sooner. balance: RDS hallucination amount is capped at mid tier hallucinations. This means bubblegum and co. will be a lot rarer, or will even never show. HOWEVER, there is now a preference allowing you to uncap your RDS hallucinations. balance: Mindbreaker toxin is more effective at suppressing RDS. balance: Some hallucinations effects have been tweaked up or down according to the new thresholds. Madness mask as an example. fix: "Fake Fire" Hallucination works again, and now has a unique message for if you stop-drop-roll that other people see. /🆑
302 lines
14 KiB
Plaintext
302 lines
14 KiB
Plaintext
/// A global list of all ongoing hallucinations, primarily for easy access to be able to stop (delete) hallucinations.
|
|
GLOBAL_LIST_EMPTY(all_ongoing_hallucinations)
|
|
|
|
// Hallucination tiers
|
|
/// Very common hallucinations, minor stuff that'll make you double-take but is otherwise very subtle.
|
|
#define HALLUCINATION_TIER_COMMON 1
|
|
/// Uncommon hallucinations, more noticeable and potentially more impactful (causing temporary stuns or stamina damage).
|
|
#define HALLUCINATION_TIER_UNCOMMON 2
|
|
/// Rarer hallucinations which are usually pretty obvious, but also pretty impactful.
|
|
#define HALLUCINATION_TIER_RARE 3
|
|
/// Hallucinations which are generally just for laughs and are obviously fake
|
|
#define HALLUCINATION_TIER_VERYSPECIAL 4
|
|
/// Hallucinations which are never picked, only forced
|
|
#define HALLUCINATION_TIER_NEVER 5
|
|
|
|
/// What typepath of the hallucination
|
|
#define HALLUCINATION_ARG_TYPE 1
|
|
/// Where the hallucination came from, for logging
|
|
#define HALLUCINATION_ARG_SOURCE 2
|
|
|
|
/// Onwards from this index, it's the arglist that gets passed into the hallucination created.
|
|
#define HALLUCINATION_ARGLIST 3
|
|
|
|
/// Biotypes which cannot hallucinate for balance and logic reasons (not code)
|
|
#define NO_HALLUCINATION_BIOTYPES (MOB_ROBOTIC|MOB_SPIRIT|MOB_SPECIAL)
|
|
|
|
// Macro wrapper for _cause_hallucination so we can cheat in named arguments, like AddComponent.
|
|
/**
|
|
* Causes a hallucination of a certain type to the mob.
|
|
*
|
|
* First argument is always the type of halllucination, a /datum/hallucination, required.
|
|
* second argument is always the key source of the hallucination, used for admin logging, required.
|
|
*
|
|
* Additionally, named arguments are supported for passing them forward to the created hallucination's new().
|
|
*/
|
|
#define cause_hallucination(arguments...) _cause_hallucination(list(##arguments))
|
|
|
|
/// Unless you need this for an explicit reason, use the cause_hallucination wrapper.
|
|
/mob/living/proc/_cause_hallucination(list/raw_args)
|
|
if(!length(raw_args))
|
|
CRASH("cause_hallucination called with no arguments.")
|
|
|
|
var/datum/hallucination/hallucination_type = raw_args[HALLUCINATION_ARG_TYPE] // first arg is the type always
|
|
if(!ispath(hallucination_type))
|
|
CRASH("cause_hallucination was given a non-hallucination type.")
|
|
|
|
var/hallucination_source = raw_args[HALLUCINATION_ARG_SOURCE] // and second arg, the source
|
|
var/datum/hallucination/new_hallucination
|
|
|
|
if(length(raw_args) >= HALLUCINATION_ARGLIST)
|
|
var/list/passed_args = raw_args.Copy(HALLUCINATION_ARGLIST)
|
|
passed_args.Insert(HALLUCINATION_ARG_TYPE, src)
|
|
|
|
new_hallucination = new hallucination_type(arglist(passed_args))
|
|
else
|
|
new_hallucination = new hallucination_type(src)
|
|
|
|
// For some reason, we qdel'd in New, maybe something went wrong.
|
|
if(QDELETED(new_hallucination))
|
|
return
|
|
// It's not guaranteed that the hallucination passed can successfully be initiated.
|
|
// This means there may be cases where someone should have a hallucination but nothing happens,
|
|
// notably if you pass a randomly picked hallucination type into this.
|
|
// Maybe there should be a separate proc to reroll on failure?
|
|
if(!new_hallucination.start())
|
|
qdel(new_hallucination)
|
|
return
|
|
|
|
investigate_log("was afflicted with a hallucination of type [hallucination_type] by: [hallucination_source]. \
|
|
([new_hallucination.feedback_details])", INVESTIGATE_HALLUCINATIONS)
|
|
return new_hallucination
|
|
|
|
/**
|
|
* Emits a hallucinating pulse around the passed atom.
|
|
* Affects everyone in the passed radius who can view the center,
|
|
* except for those with TRAIT_MADNESS_IMMUNE, or those who are blind.
|
|
*
|
|
* center - required, the center of the pulse
|
|
* radius - the radius around that the pulse reaches
|
|
* hallucination_duration - how much hallucination is added by the pulse. reduced based on distance to the center.
|
|
* hallucination_max_duration - a cap on how much hallucination can be added
|
|
* optional_messages - optional list of messages passed. Those affected by pulses will be given one of the messages in said list.
|
|
*/
|
|
/proc/visible_hallucination_pulse(atom/center, radius = 7, hallucination_duration = 50 SECONDS, hallucination_max_duration, list/optional_messages)
|
|
for(var/mob/living/nearby_living in view(center, radius))
|
|
if(HAS_MIND_TRAIT(nearby_living, TRAIT_MADNESS_IMMUNE))
|
|
continue
|
|
|
|
if(nearby_living.mob_biotypes & NO_HALLUCINATION_BIOTYPES)
|
|
continue
|
|
|
|
if(nearby_living.is_blind())
|
|
continue
|
|
|
|
// Everyone else gets hallucinations.
|
|
var/dist = sqrt(1 / max(1, get_dist(nearby_living, center)))
|
|
nearby_living.adjust_hallucinations_up_to(hallucination_duration * dist, hallucination_max_duration)
|
|
if(length(optional_messages))
|
|
to_chat(nearby_living, pick(optional_messages))
|
|
|
|
/**
|
|
* Emits a hallucinating pulse around the passed atom.
|
|
* Affects everyone in the passed radius except for those with TRAIT_MADNESS_IMMUNE. This affects blind players.
|
|
*
|
|
* center - required, the center of the pulse
|
|
* radius - the radius around that the pulse reaches
|
|
* hallucination_duration - how much hallucination is added by the pulse. reduced based on distance to the center.
|
|
* hallucination_max_duration - a cap on how much hallucination can be added
|
|
* optional_messages - optional list of messages passed. Those affected by pulses will be given one of the messages in said list.
|
|
*/
|
|
/proc/hallucination_pulse(atom/center, radius = 7, hallucination_duration = 50 SECONDS, hallucination_max_duration, list/optional_messages)
|
|
for(var/mob/living/nearby_living in range(center, radius))
|
|
if(HAS_MIND_TRAIT(nearby_living, TRAIT_MADNESS_IMMUNE))
|
|
continue
|
|
|
|
if(nearby_living.mob_biotypes & NO_HALLUCINATION_BIOTYPES)
|
|
continue
|
|
|
|
// Everyone else gets hallucinations.
|
|
var/dist = sqrt(1 / max(1, get_dist(nearby_living, center)))
|
|
nearby_living.adjust_hallucinations_up_to(hallucination_duration * dist, hallucination_max_duration)
|
|
if(length(optional_messages))
|
|
to_chat(nearby_living, pick(optional_messages))
|
|
|
|
/// Global weighted list of all hallucinations that can show up randomly.
|
|
GLOBAL_LIST_INIT_TYPED(random_hallucination_weighted_list, /list, generate_hallucination_weighted_list())
|
|
|
|
/// Generates the global weighted list of random hallucinations.
|
|
/proc/generate_hallucination_weighted_list()
|
|
var/list/weighted_list = list()
|
|
|
|
for(var/datum/hallucination/hallucination_type as anything in typesof(/datum/hallucination))
|
|
if(hallucination_type == initial(hallucination_type.abstract_hallucination_parent))
|
|
continue
|
|
var/weight = initial(hallucination_type.random_hallucination_weight)
|
|
if(weight <= 0)
|
|
continue
|
|
|
|
LAZYSET(weighted_list["[initial(hallucination_type.hallucination_tier)]"], hallucination_type, weight)
|
|
|
|
return weighted_list
|
|
|
|
/// Select a random hallucination from the hallucination pool
|
|
///
|
|
/// * tier - the tier of hallucination to select from
|
|
/// * strict - if true, only select from the passed tier. If false, select from the passed tier and all tiers below it.
|
|
/proc/get_random_hallucination(tier = HALLUCINATION_TIER_COMMON, strict = FALSE)
|
|
if(!GLOB.random_hallucination_weighted_list[tier])
|
|
CRASH("get_random_hallucination - No hallucinations in tier \[[tier]\].")
|
|
|
|
var/list/pool = GLOB.random_hallucination_weighted_list["[tier]"].Copy()
|
|
if(!strict)
|
|
tier -= 1
|
|
while(tier >= HALLUCINATION_TIER_COMMON)
|
|
pool += GLOB.random_hallucination_weighted_list["[tier]"]
|
|
tier -= 1
|
|
|
|
return pick_weight(pool)
|
|
|
|
/// Debug proc for getting the total weight of the random_hallucination_weighted_list
|
|
/proc/debug_hallucination_weighted_list()
|
|
var/total_weight = 0
|
|
for(var/tier in GLOB.random_hallucination_weighted_list)
|
|
for(var/datum/hallucination/hallucination_type as anything in GLOB.random_hallucination_weighted_list[tier])
|
|
total_weight += GLOB.random_hallucination_weighted_list[tier][hallucination_type]
|
|
|
|
to_chat(usr, span_boldnotice("The total weight of the hallucination weighted list is [total_weight]."))
|
|
return total_weight
|
|
|
|
ADMIN_VERB(debug_hallucination_weighted_list_per_type, R_DEBUG, "Show Hallucination Weights", "View the weight of each hallucination subtype in the random weighted list.", ADMIN_CATEGORY_DEBUG)
|
|
var/header = "<tr><th>Type</th> <th>Weight</th> <th>Tier</th> <th>Percent</th>"
|
|
|
|
var/total_weight = debug_hallucination_weighted_list()
|
|
var/list/all_weights = list()
|
|
var/datum/hallucination/last_type
|
|
var/last_type_weight = 0
|
|
for(var/tier in GLOB.random_hallucination_weighted_list)
|
|
for(var/datum/hallucination/hallucination_type as anything in GLOB.random_hallucination_weighted_list[tier])
|
|
var/this_weight = GLOB.random_hallucination_weighted_list[tier][hallucination_type]
|
|
// Last_type is the abstract parent of the last hallucination type we iterated over
|
|
if(last_type)
|
|
// If this hallucination is the same path as the last type (subtype), add it to the total of the last type weight
|
|
if(ispath(hallucination_type, last_type))
|
|
last_type_weight += this_weight
|
|
continue
|
|
|
|
// Otherwise we moved onto the next hallucination subtype so we can stop
|
|
else
|
|
all_weights["<tr><td>[last_type]</td> <td>[last_type_weight] / [total_weight]</td> <td>[initial(hallucination_type.hallucination_tier)]</td> <td>[round(100 * (last_type_weight / total_weight), 0.01)]% chance</td></tr>"] = last_type_weight
|
|
|
|
// Set last_type to the abstract parent of this hallucination
|
|
last_type = initial(hallucination_type.abstract_hallucination_parent)
|
|
// If last_type is the base hallucination it has no distinct subtypes so we can total it up immediately
|
|
if(last_type == /datum/hallucination)
|
|
all_weights["<tr><td>[hallucination_type]</td> <td>[this_weight] / [total_weight]</td> <td>[initial(hallucination_type.hallucination_tier)]</td> <td>[round(100 * (this_weight / total_weight), 0.01)]% chance</td></tr>"] = this_weight
|
|
last_type = null
|
|
|
|
// Otherwise we start the weight sum for the next entry here
|
|
else
|
|
last_type_weight = this_weight
|
|
|
|
// Sort by weight descending, where weight is the values (not the keys). We assoc_to_keys later to get JUST the text
|
|
sortTim(all_weights, GLOBAL_PROC_REF(cmp_numeric_dsc), associative = TRUE)
|
|
|
|
var/page_style = "<style>table, th, td {border: 1px solid black;border-collapse: collapse;}</style>"
|
|
var/page_contents = "[page_style]<table style=\"width:100%\">[header][jointext(assoc_to_keys(all_weights), "")]</table>"
|
|
var/datum/browser/popup = new(user.mob, "hallucinationdebug", "Hallucination Weights", 600, 400)
|
|
popup.set_content(page_contents)
|
|
popup.open()
|
|
|
|
/// Gets a random subtype of the passed hallucination type that has a random_hallucination_weight > 0.
|
|
/// If no subtype is passed, it will get any random hallucination subtype that is not abstract and has weight > 0.
|
|
/// This can be used instead of picking from the global weighted list to just get a random valid hallucination.
|
|
/proc/get_random_valid_hallucination_subtype(passed_type = /datum/hallucination)
|
|
if(!ispath(passed_type, /datum/hallucination))
|
|
CRASH("get_random_valid_hallucination_subtype - get_random_valid_hallucination_subtype passed not a hallucination subtype.")
|
|
|
|
for(var/datum/hallucination/hallucination_type as anything in shuffle(subtypesof(passed_type)))
|
|
if(initial(hallucination_type.abstract_hallucination_parent) == hallucination_type)
|
|
continue
|
|
if(initial(hallucination_type.random_hallucination_weight) <= 0)
|
|
continue
|
|
|
|
return hallucination_type
|
|
|
|
return null
|
|
|
|
/// Helper to give the passed mob the ability to select a hallucination from the list of all hallucination subtypes.
|
|
/proc/select_hallucination_type(mob/user, message = "Select a hallucination subtype", title = "Choose Hallucination")
|
|
var/static/list/hallucinations
|
|
if(!hallucinations)
|
|
hallucinations = typesof(/datum/hallucination)
|
|
for(var/datum/hallucination/hallucination_type as anything in hallucinations)
|
|
if(initial(hallucination_type.abstract_hallucination_parent) == hallucination_type)
|
|
hallucinations -= hallucination_type
|
|
|
|
var/chosen = tgui_input_list(user, message, title, hallucinations)
|
|
if(!chosen || !ispath(chosen, /datum/hallucination))
|
|
return null
|
|
|
|
return chosen
|
|
|
|
/// Helper to give the passed mob the ability to create a delusion hallucination (even a custom one).
|
|
/// Returns a list of arguments - pass these to _cause_hallucination to cause the desired hallucination
|
|
/proc/create_delusion(mob/user)
|
|
var/static/list/delusions
|
|
if(!delusions)
|
|
delusions = typesof(/datum/hallucination/delusion)
|
|
for(var/datum/hallucination/delusion_type as anything in delusions)
|
|
if(initial(delusion_type.abstract_hallucination_parent) == delusion_type)
|
|
delusions -= delusion_type
|
|
|
|
var/chosen = tgui_input_list(user, "Select a delusion type. Custom will allow for custom icon entry.", "Select Delusion", delusions)
|
|
if(!chosen || !ispath(chosen, /datum/hallucination/delusion))
|
|
return
|
|
|
|
var/list/delusion_args = list()
|
|
var/static/list/options = list("Yes", "No")
|
|
var/duration = tgui_input_number(user, "How long should it last in seconds?", "Delusion: Duration", max_value = INFINITY, min_value = 1, default = 30)
|
|
var/affects_us = (tgui_alert(user, "Should they see themselves as the delusion?", "Delusion: Affects us", options) == "Yes")
|
|
var/affects_others = (tgui_alert(user, "Should they see everyone else delusion?", "Delusion: Affects others", options) == "Yes")
|
|
var/skip_nearby = (tgui_alert(user, "Should the delusion only affect people outside of their view?", "Delusion: Skip in view", options) == "Yes")
|
|
var/play_wabbajack = (tgui_alert(user, "Play the wabbajack sound when it happens?", "Delusion: Wabbajack sound", options) == "Yes")
|
|
|
|
delusion_args = list(
|
|
chosen,
|
|
"forced delusion",
|
|
duration = duration * 1 SECONDS,
|
|
affects_us = affects_us,
|
|
affects_others = affects_others,
|
|
skip_nearby = skip_nearby,
|
|
play_wabbajack = play_wabbajack,
|
|
)
|
|
|
|
if(ispath(chosen, /datum/hallucination/delusion/custom))
|
|
var/custom_icon_file = input(user, "Pick file for custom delusion:", "Custom Delusion: File") as null|file
|
|
if(!custom_icon_file)
|
|
return
|
|
|
|
var/custom_icon_state = tgui_input_text(user, "What icon state do you wanna use from the file?", "Custom Delusion: Icon State")
|
|
if(!custom_icon_state)
|
|
return
|
|
|
|
var/custom_name = tgui_input_text(user, "What name should it show up as? (Can be empty)", "Custom Delusion: Name", max_length = MAX_NAME_LEN)
|
|
|
|
delusion_args += list(
|
|
custom_icon_file = custom_icon_file,
|
|
custom_icon_state = custom_icon_state,
|
|
custom_name = custom_name,
|
|
)
|
|
|
|
return delusion_args
|
|
|
|
/// Lines the bubblegum hallucinatoin uses when it pops up
|
|
#define BUBBLEGUM_HALLUCINATION_LINES list( \
|
|
span_colossus("I AM IMMORTAL."), \
|
|
span_colossus("I SHALL TAKE YOUR WORLD."), \
|
|
span_colossus("I SEE YOU."), \
|
|
span_colossus("YOU CANNOT ESCAPE ME FOREVER."), \
|
|
span_colossus("NOTHING CAN HOLD ME."), \
|
|
)
|