Files
CHOMPStation2/code/datums/components/traits/crowd_detection.dm
CHOMPStation2StaffMirrorBot 782f8288c5 [MIRROR] Crowd detection component (#11854)
Co-authored-by: Will <7099514+Willburd@users.noreply.github.com>
Co-authored-by: Cameron Lennox <killer65311@gmail.com>
2025-10-22 01:55:34 -04:00

293 lines
10 KiB
Plaintext

#define MIN_DISCOMFORT_MESSAGE 50
/**
* Component that serves as a base type for detecting groups of mobs or players around us.
* It functions as a framework for lonely and agoraphobia to share large sections of code.
* Can be extended for other situations in the future.
* */
/datum/component/crowd_detection
VAR_PROTECTED/mob/living/carbon/human/human_parent
VAR_PROTECTED/discomfort = 0
VAR_PROTECTED/hallucination_cap = 25
VAR_PROTECTED/warning_cap = 400
VAR_PROTECTED/escalation_speed = 0.8
VAR_PROTECTED/only_people = FALSE
VAR_PROTECTED/invis_matters = TRUE
VAR_PROTECTED/belly_is_calming = TRUE
VAR_PRIVATE/is_calm = TRUE
VAR_PRIVATE/next_message_time = 0
/datum/component/crowd_detection/Initialize()
if(!ishuman(parent))
return COMPONENT_INCOMPATIBLE
human_parent = parent
RegisterSignal(human_parent, COMSIG_LIVING_LIFE, PROC_REF(handle_life))
/datum/component/crowd_detection/Destroy(force = FALSE)
UnregisterSignal(human_parent, COMSIG_LIVING_LIFE)
human_parent = null
. = ..()
/datum/component/crowd_detection/proc/handle_life()
SHOULD_CALL_PARENT(TRUE)
PROTECTED_PROC(TRUE)
SIGNAL_HANDLER
// If they're dead or unconcious they're a bit beyond this kind of thing.
if(human_parent.stat)
discomfort = 0
return TRUE
// No point processing if we're already stressing the hell out.
if(human_parent.hallucination >= hallucination_cap && discomfort >= warning_cap)
discomfort = warning_cap
return TRUE
// I don't need to be told every 2 seconds I'm freaking out when I'm in a belly trying to WRITE!!! We can't be reasonably crowded in a belly, and won't be alone in one, so lets just handle it here.
if(isbelly(human_parent.loc))
if(belly_is_calming)
calm_discomfort()
return TRUE
// Changelings are immune to these, so we may as well stop handling it... It's unlikely you will turn back from a changeling as well.
if(human_parent.GetComponent(/datum/component/antag/changeling)) // We are never alone~
qdel(src)
return TRUE
return FALSE
/datum/component/crowd_detection/proc/increase_discomfort()
SHOULD_NOT_OVERRIDE(TRUE)
PROTECTED_PROC(TRUE)
// No company? Suffer :(
if(discomfort < warning_cap)
discomfort = min(warning_cap,discomfort+escalation_speed)
// Handle message delay
if(world.time >= next_message_time)
var/ms = get_discomfort_message(discomfort)
if(ms)
to_chat(human_parent, ms)
next_message_time = world.time + 50 SECONDS
human_parent.fear = min((human_parent.fear + 3), 102)
is_calm = FALSE // We'll only get one message early
// Hallucinations
if(discomfort >= warning_cap && human_parent.hallucination < hallucination_cap)
human_parent.hallucination = min(hallucination_cap,human_parent.hallucination+2.5*escalation_speed)
/datum/component/crowd_detection/proc/check_contents(atom/item, max_layer = 3, current_layer = 1)
SHOULD_NOT_OVERRIDE(TRUE)
PROTECTED_PROC(TRUE)
RETURN_TYPE(/list)
var/list/in_range = list()
if(!item || !istype(item) || current_layer > max_layer)
return in_range
for(var/datum/content in item.contents)
if(istype(content,/obj/item/holder))
var/obj/item/holder/contentholder = content
in_range |= check_mob_company(contentholder.held_mob)
else
in_range |= check_contents(content,max_layer,current_layer+1)
return in_range
/datum/component/crowd_detection/proc/check_mob_company(mob/living/M)
SHOULD_NOT_OVERRIDE(TRUE)
PROTECTED_PROC(TRUE)
RETURN_TYPE(/list)
var/list/in_range = list()
if(!istype(M))
return in_range
var/social_check = only_people && !istype(M, /mob/living/carbon) && !istype(M, /mob/living/silicon/robot)
var/self_invisible_check = M == human_parent || M.invisibility > human_parent.see_invisible
var/ckey_check = only_people && !M.ckey
var/overall_checks = M == human_parent || M.stat == DEAD || social_check || ckey_check
if(invis_matters && self_invisible_check)
return in_range
if((M.faction == FACTION_NEUTRAL || M.faction == human_parent.faction) && !overall_checks)
in_range |= M
else
in_range |= check_contents(M)
if(M.vore_organs)
for(var/obj/belly/B in M.vore_organs)
for(var/mob/living/content in B.contents)
if(istype(content))
in_range |= check_mob_company(content)
return in_range
/datum/component/crowd_detection/proc/get_discomfort_message(current_discomfort)
SHOULD_CALL_PARENT(TRUE)
PROTECTED_PROC(TRUE)
return null
/datum/component/crowd_detection/proc/calm_discomfort(amount = 4, message = null)
SHOULD_CALL_PARENT(TRUE)
PROTECTED_PROC(TRUE)
discomfort = max(discomfort - amount, 0)
if(world.time >= next_message_time && (discomfort > 0 || !is_calm))
if(message)
to_chat(human_parent, message)
if(discomfort < MIN_DISCOMFORT_MESSAGE)
is_calm = TRUE
next_message_time = world.time + 50 SECONDS
/datum/component/crowd_detection/proc/find_held_by(atom/item)
SHOULD_NOT_OVERRIDE(TRUE)
PROTECTED_PROC(TRUE)
RETURN_TYPE(/atom)
if(!item || !istype(item))
return null
else if(istype(item,/mob/living))
return item
else
return find_held_by(item.loc)
/datum/component/crowd_detection/proc/get_calm()
SHOULD_NOT_OVERRIDE(TRUE)
return is_calm
/datum/component/crowd_detection/proc/get_discomfort()
SHOULD_NOT_OVERRIDE(TRUE)
return discomfort
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Lonelyness
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/datum/component/crowd_detection/lonely
/datum/component/crowd_detection/lonely/major
only_people = TRUE
warning_cap = 300
hallucination_cap = 50
escalation_speed = 1.3
/datum/component/crowd_detection/lonely/handle_life()
if(..())
return
// Brain friends!
if(human_parent.has_brain_worms())
calm_discomfort()
return
// Grabbed by a mob
if(istype(human_parent.loc, /obj/item/holder))
calm_discomfort()
return
// Check for company.
if(length(check_contents(human_parent))) //Check our item slots and storage for any micros.
calm_discomfort()
return
for(var/mob/living/M in viewers(get_turf(human_parent)))
if(length(check_mob_company(M)))
calm_discomfort()
return
//Check to see if there's anyone in our belly
if(human_parent.vore_organs)
for(var/obj/belly/B in human_parent.vore_organs)
for(var/mob/living/content in B.contents)
if(istype(content))
if(length(check_mob_company(content)))
calm_discomfort()
return
//Things that are not mobs, but will calm lonelyness
for(var/obj/effect/overlay/aiholo/A in range(5, human_parent))
calm_discomfort()
return
for(var/obj/item/toy/plushie/teshari/P in range(5, human_parent))
calm_discomfort()
return
increase_discomfort()
/datum/component/crowd_detection/lonely/get_discomfort_message(var/current_discomfort)
if(current_discomfort >= warning_cap)
human_parent.stuttering += 25
return span_danger(span_bold(pick("Where are the others?", "Please, there has to be someone nearby!", "I don't want to be alone!","Please, anyone! I don't want to be alone!")))
if(current_discomfort >= 250)
if(human_parent.stuttering < hallucination_cap)
human_parent.stuttering += 5
return pick("You don't think you can last much longer without some visible company!", "You should go find someone to be with!","You need to find company!","Find someone to be with!")
if(current_discomfort >= MIN_DISCOMFORT_MESSAGE)
return pick("You begin to feel alone...","You feel isolated...","You need company...","Where is everyone?...","You need to find someone...")
if(get_calm())
return pick("Well.. No one is around you anymore...","Well.. You're alone now...","You suddenly feel alone...")
. = ..()
/datum/component/crowd_detection/lonely/calm_discomfort(var/amount = 4, var/message)
if(!message && !get_calm())
message = span_infoplain("The nearby company calms you down...")
. = ..(amount, message)
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Agoraphobia
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/datum/component/crowd_detection/agoraphobia
belly_is_calming = FALSE // Stops message spam, but doesn't decrease discomfort
/datum/component/crowd_detection/agoraphobia/handle_life()
if(..())
return
var/list/in_range = list()
in_range |= check_mob_company(human_parent) //Checks our item slots and bellies for any people.
in_range |= belly_check(human_parent.loc) //Recursive check if we're in anyones bellies, are they in anyone's belly, etc.
in_range |= holder_check(human_parent.loc) //Recursive check if someone's holding us, is anyone holding them, etc.
// Check for company.
for(var/mob/living/M in viewers(get_turf(human_parent)))
in_range |= check_mob_company(M)
for(var/obj/effect/overlay/aiholo/A in range(5, human_parent))
in_range |= A
if(length(in_range) <= 2)
calm_discomfort()
return
increase_discomfort()
/datum/component/crowd_detection/agoraphobia/get_discomfort_message( current_discomfort)
if(current_discomfort >= warning_cap)
human_parent.stuttering += 25
return span_bolddanger(pick("Why am I still here? I have to leave and get some space!",
"Please, just let me be alone!",
"I need to be alone!"))
if(current_discomfort >= 250)
if(human_parent.stuttering < hallucination_cap)
human_parent.stuttering += 5
return pick("You don't think you can last much longer with this much company!", "You should go find some space!")
if(current_discomfort >= MIN_DISCOMFORT_MESSAGE)
return "You start to feel anxious from the number of people around you."
if(get_calm())
return "You notice there's more people than you feel comfortable with around you..."
. = ..()
/datum/component/crowd_detection/agoraphobia/calm_discomfort(amount = 4, message)
if(!message && !get_calm())
message = span_infoplain("You feel calmer with noone around...")
. = ..(amount, message)
/datum/component/crowd_detection/agoraphobia/proc/holder_check(obj/item/holder/H_holder)
SHOULD_NOT_OVERRIDE(TRUE)
PRIVATE_PROC(TRUE)
RETURN_TYPE(/list)
var/list/in_range = list()
if(istype(H_holder))
var/mob/living/held_by = find_held_by(H_holder)
if(held_by)
in_range |= check_mob_company(held_by)
in_range |= holder_check(human_parent,held_by)
return in_range
/datum/component/crowd_detection/agoraphobia/proc/belly_check(obj/belly/B)
SHOULD_NOT_OVERRIDE(TRUE)
PRIVATE_PROC(TRUE)
RETURN_TYPE(/list)
var/list/in_range = list()
if(istype(B))
in_range |= check_mob_company(B.owner)
if(isbelly(B.owner.loc))
in_range |= belly_check(human_parent,B.owner.loc)
return in_range
#undef MIN_DISCOMFORT_MESSAGE