mirror of
https://github.com/fulpstation/fulpstation.git
synced 2025-12-09 16:09:15 +00:00
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."), \
|
|
)
|