diff --git a/code/datums/components/traits/crowd_detection.dm b/code/datums/components/traits/crowd_detection.dm new file mode 100644 index 00000000000..fa04781cbb6 --- /dev/null +++ b/code/datums/components/traits/crowd_detection.dm @@ -0,0 +1,292 @@ +#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 diff --git a/code/modules/mob/living/carbon/human/human_defines.dm b/code/modules/mob/living/carbon/human/human_defines.dm index 61f1518757f..0bdaefd20d8 100644 --- a/code/modules/mob/living/carbon/human/human_defines.dm +++ b/code/modules/mob/living/carbon/human/human_defines.dm @@ -169,9 +169,6 @@ var/phobias //For holding a list of phobias - var/loneliness_stage = 0 - var/next_loneliness_time = 0 - var/redgate_restricted = FALSE var/hide_headset = FALSE var/hide_glasses = FALSE diff --git a/code/modules/mob/living/carbon/human/species/station/traits/negative.dm b/code/modules/mob/living/carbon/human/species/station/traits/negative.dm index d748c15b7fa..74d54818930 100644 --- a/code/modules/mob/living/carbon/human/species/station/traits/negative.dm +++ b/code/modules/mob/living/carbon/human/species/station/traits/negative.dm @@ -326,11 +326,8 @@ name = "Agoraphobia" desc = "You very much dislike being in crowded places. When in the company of more than two other people, you start to panic and experience adverse effects." cost = -3 - var/warning_cap = 400 - var/hallucination_cap = 25 - var/escalation_speed = 0.8 - special_env = TRUE excludes = list(/datum/trait/negative/lonely,/datum/trait/negative/lonely/major) + added_component_path = /datum/component/crowd_detection/agoraphobia //Traitgenes is_genetrait = TRUE @@ -339,136 +336,19 @@ activation_message="You feel like you need some space" primitive_expression_messages=list("keeps their distance from others") -/datum/trait/negative/agoraphobia/handle_environment_special(var/mob/living/carbon/human/H) - spawn(0) - if(isbelly(H.loc)) //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 handle all the stress of it when (IF) we are let out of the gut. - return - var/list/in_range = list() - // If they're dead or unconcious they're a bit beyond this kind of thing. - if(H.stat) - return - // No point processing if we're already stressing the hell out. - if(H.hallucination >= hallucination_cap && H.loneliness_stage >= warning_cap) - return - in_range |= check_mob_company(H,H) //Checks our item slots and bellies for any people. - in_range |= belly_check(H,H.loc) //Recursive check if we're in anyones bellies, are they in anyone's belly, etc. - in_range |= holder_check(H,H.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(H))) - in_range |= check_mob_company(H,M) - - for(var/obj/effect/overlay/aiholo/A in range(5, H)) - in_range |= A - - if(in_range.len > 2) - if(H.loneliness_stage < warning_cap) - H.loneliness_stage = min(warning_cap,H.loneliness_stage+escalation_speed) - handle_loneliness(H) - if(H.loneliness_stage >= warning_cap && H.hallucination < hallucination_cap) - H.hallucination = min(hallucination_cap,H.hallucination+2.5*escalation_speed) - else - H.loneliness_stage = max(H.loneliness_stage-4,0) - - -/datum/trait/negative/agoraphobia/proc/handle_loneliness(var/mob/living/carbon/human/H) - if(world.time < H.next_loneliness_time) - return //Moved this at the top so we dont waste time assigning vars we will never use - var/ms = handle_loneliness_message(H) - if(ms) - to_chat(H, ms) - H.next_loneliness_time = world.time+500 - H.fear = min((H.fear + 3), 102) - -/datum/trait/negative/agoraphobia/proc/handle_loneliness_message(var/mob/living/carbon/human/H) - var/Lonely = H.loneliness_stage - if(Lonely == escalation_speed) - return "You notice there's more people than you feel comfortable with around you..." - else if(Lonely >= 50 && Lonely < 250) - return "You start to feel anxious from the number of people around you." - else if(Lonely >= 250 && Lonely < warning_cap) - if(H.stuttering < hallucination_cap) - H.stuttering += 5 - return "[pick("You don't think you can last much longer with this much company!", "You should go find some space!")]" //if we add more here make it a list for readability - else if(Lonely >= warning_cap) - var/list/panicmessages = list( "Why am I still here? I have to leave and get some space!", - "Please, just let me be alone!", - "I need to be alone!") - return span_bolddanger("[pick(panicmessages)]") - return FALSE - -/datum/trait/negative/agoraphobia/proc/find_held_by(var/atom/item) - if(!item || !istype(item)) - return null - else if(istype(item,/mob/living)) - return item - else - return find_held_by(item.loc) - -/datum/trait/negative/agoraphobia/proc/holder_check(var/mob/living/carbon/human/H,var/obj/item/holder/H_holder) - 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(H,held_by,FALSE) - in_range |= holder_check(H,held_by) - return in_range - -/datum/trait/negative/agoraphobia/proc/belly_check(var/mob/living/carbon/human/H,var/obj/belly/B) - var/list/in_range = list() - if(istype(B)) - in_range |= check_mob_company(H,B.owner,FALSE) - if(isbelly(B.owner.loc)) - in_range |= belly_check(H,B.owner.loc) - return in_range - -/datum/trait/negative/agoraphobia/proc/check_mob_company(var/mob/living/carbon/human/H,var/mob/living/M,var/invis_matters = TRUE) - var/list/in_range = list() - if(!istype(M)) - return in_range - var/social_check = !istype(M, /mob/living/carbon) && !istype(M, /mob/living/silicon/robot) - var/ckey_check = !M.ckey - var/overall_checks = M == H || M.stat == DEAD || social_check || ckey_check - if(invis_matters && M.invisibility > H.see_invisible) - return in_range - if(!overall_checks) - in_range |= M - in_range |= check_contents(M,H) - return in_range - -/datum/trait/negative/agoraphobia/proc/check_contents(var/atom/item,var/mob/living/carbon/human/H,var/max_layer = 3,var/current_layer = 1) - 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(H,contentholder.held_mob) - else - in_range |= check_contents(content,H,max_layer,current_layer+1) - return in_range - /datum/trait/negative/lonely name = "Minor loneliness vulnerability" desc = "You're very prone to loneliness! Being alone for extended periods of time causes adverse effects. Most mobs will cure this loneliness as long as they aren't hostile." cost = -1 - var/warning_cap = 400 - var/only_people = FALSE - var/hallucination_cap = 25 - var/escalation_speed = 0.8 - special_env = TRUE excludes = list(/datum/trait/negative/lonely/major,/datum/trait/negative/agoraphobia) + added_component_path = /datum/component/crowd_detection/lonely /datum/trait/negative/lonely/major name = "Major loneliness vulnerability" desc = "You're extremely prone to loneliness! Being alone for extended periods of time causes adverse effects. Most mobs won't be enough to cure this loneliness, you need other social beings." cost = -3 - warning_cap = 300 - hallucination_cap = 50 - escalation_speed = 1.3 - only_people = TRUE - special_env = TRUE excludes = list(/datum/trait/negative/lonely,/datum/trait/negative/agoraphobia) + added_component_path = /datum/component/crowd_detection/lonely/major //Traitgenes is_genetrait = TRUE @@ -477,118 +357,6 @@ activation_message="You feel like you are in desperate need of company..." primitive_expression_messages=list("Looks up at you pleadingly") -/datum/trait/negative/lonely/proc/sub_loneliness(var/mob/living/carbon/human/H,var/amount = 4) - H.loneliness_stage = max(H.loneliness_stage - 4, 0) - if(world.time >= H.next_loneliness_time && H.loneliness_stage > 0) - to_chat(H, span_infoplain("The nearby company calms you down...")) - H.next_loneliness_time = world.time+500 - -/datum/trait/negative/lonely/proc/check_mob_company(var/mob/living/carbon/human/H,var/mob/living/M) - if(!istype(M)) - return 0 - var/social_check = only_people && !istype(M, /mob/living/carbon) && !istype(M, /mob/living/silicon/robot) - var/self_invisible_check = M == H || M.invisibility > H.see_invisible - var/ckey_check = only_people && !M.ckey - var/overall_checks = M.stat == DEAD || social_check || ckey_check - if(self_invisible_check) - return 0 - if((M.faction == "neutral" || M.faction == H.faction) && !overall_checks) - sub_loneliness(H) - return 1 - else - if(M.vore_organs) - for(var/obj/belly/B in M.vore_organs) - for(var/mob/living/content in B.contents) - if(istype(content)) - check_mob_company(H,content) - return 0 - -/datum/trait/negative/lonely/proc/check_contents(var/atom/item,var/mob/living/carbon/human/H,var/max_layer = 3,var/current_layer = 1) - if(!item || !istype(item) || current_layer > max_layer) - return 0 - for(var/datum/content in item.contents) - if(istype(content,/obj/item/holder)) - var/obj/item/holder/contentholder = content - if(check_mob_company(H,contentholder.held_mob)) - return 1 - else - if(check_contents(content,H,max_layer,current_layer+1)) - return 1 - return 0 - -/datum/trait/negative/lonely/handle_environment_special(var/mob/living/carbon/human/H) - spawn(0) - // If they're dead or unconcious they're a bit beyond this kind of thing. - if(H.stat) - return - // No point processing if we're already stressing the hell out. - if(H.hallucination >= hallucination_cap && H.loneliness_stage >= warning_cap) - return - - var/datum/component/antag/changeling/comp = H.GetComponent(/datum/component/antag/changeling) - if(comp) // We are never alone~ - H.loneliness_stage = 0 - return - if(H.has_brain_worms()) // Brain friends! - sub_loneliness(H) - return - - // Vored? Not gonna get frightened. - if(isbelly(H.loc)) - sub_loneliness(H) - return - if(istype(H.loc, /obj/item/holder)) - sub_loneliness(H) - return - // Check for company. - if(check_contents(H,H)) //Check our item slots and storage for any micros. - sub_loneliness(H) - return - for(var/mob/living/M in viewers(get_turf(H))) - if(check_mob_company(H,M)) - return - //Check to see if there's anyone in our belly - if(H.vore_organs) - for(var/obj/belly/B in H.vore_organs) - for(var/mob/living/content in B.contents) - if(istype(content)) - if(check_mob_company(H,content)) - return - for(var/obj/item/holder/micro/M in range(1, H)) - sub_loneliness(H) - for(var/obj/effect/overlay/aiholo/A in range(5, H)) - sub_loneliness(H) - - for(var/obj/item/toy/plushie/teshari/P in range(5, H)) - sub_loneliness(H) - - // No company? Suffer :( - if(H.loneliness_stage < warning_cap) - H.loneliness_stage = min(warning_cap,H.loneliness_stage+escalation_speed) - handle_loneliness(H) - if(H.loneliness_stage >= warning_cap && H.hallucination < hallucination_cap) - H.hallucination = min(hallucination_cap,H.hallucination+2.5*escalation_speed) - -/datum/trait/negative/lonely/proc/handle_loneliness(var/mob/living/carbon/human/H) - var/ms = "" - if(H.loneliness_stage == escalation_speed) - ms = "[pick("Well.. No one is around you anymore...","Well.. You're alone now...","You suddenly feel alone...")]" - if(H.loneliness_stage >= 50) - ms = "[pick("You begin to feel alone...","You feel isolated...","You need company...","Where is everyone?...","You need to find someone...")]" - if(H.loneliness_stage >= 250) - ms = "[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(H.stuttering < hallucination_cap) - H.stuttering += 5 - if(H.loneliness_stage >= warning_cap) - ms = 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(world.time < H.next_loneliness_time) - return - if(ms != "") - to_chat(H, ms) - H.next_loneliness_time = world.time+500 - H.fear = min((H.fear + 3), 102) - - /datum/trait/negative/endurance_glass // Glass Cannon name = "Glass Endurance" desc = "Your body is very fragile. Reduces your maximum hitpoints to 25. Beware sneezes. You require only 50 damage in total to die, compared to 200 normally. You will go into crit after losing 25 HP, compared to crit at 100 HP." diff --git a/vorestation.dme b/vorestation.dme index 75d1e16edda..815cc782b87 100644 --- a/vorestation.dme +++ b/vorestation.dme @@ -660,6 +660,7 @@ #include "code\datums\components\species\shadekin\powers\dark_tunnel\dark_tunnel_structures.dm" #include "code\datums\components\species\shadekin\powers\dark_tunnel\dark_tunneling.dm" #include "code\datums\components\traits\burninlight.dm" +#include "code\datums\components\traits\crowd_detection.dm" #include "code\datums\components\traits\drippy.dm" #include "code\datums\components\traits\gargoyle.dm" #include "code\datums\components\traits\nutrition_size_change.dm"