diff --git a/code/__DEFINES/dcs/flags.dm b/code/__DEFINES/dcs/flags.dm index 3f3db0212b6..fb93e3a337a 100644 --- a/code/__DEFINES/dcs/flags.dm +++ b/code/__DEFINES/dcs/flags.dm @@ -1,4 +1,4 @@ -/// Return this from `/datum/component/Initialize` or `datum/component/OnTransfer` to have the component be deleted if it's applied to an incorrect type. +/// Return this from `/datum/component/Initialize` or `/datum/component/OnTransfer` or `/datum/component/on_source_add` to have the component be deleted if it's applied to an incorrect type. /// `parent` must not be modified if this is to be returned. /// This will be noted in the runtime logs #define COMPONENT_INCOMPATIBLE 1 @@ -33,8 +33,8 @@ /** * Component uses source tracking to manage adding and removal logic. * Add a source/spawn to/the component by using AddComponentFrom(source, component_type, args...) - * Only the first args will be respected, and you should instead handle most of your logic in the on_source_added proc. * Removing the last source will automatically remove the component from the parent. + * Arguments will be passed to on_source_add(source, args...); ensure that Initialize and on_source_add have the same signature. */ #define COMPONENT_DUPE_SOURCES 3 /// old component is given the initialization args of the new diff --git a/code/datums/components/_component.dm b/code/datums/components/_component.dm index dd9f44d5f23..c8bc176b5a9 100644 --- a/code/datums/components/_component.dm +++ b/code/datums/components/_component.dm @@ -169,12 +169,13 @@ return /** - * Called when the component has a new source registered + * Called when the component has a new source registered. + * Return COMPONENT_INCOMPATIBLE to signal that the source is incompatible and should not be added */ -/datum/component/proc/on_source_add(source) +/datum/component/proc/on_source_add(source, ...) SHOULD_CALL_PARENT(TRUE) if(dupe_mode != COMPONENT_DUPE_SOURCES) - CRASH("Component '[type]' does not use sources but has been given a source") + return COMPONENT_INCOMPATIBLE LAZYOR(sources, source) /** @@ -339,7 +340,7 @@ var/datum/component/old_component raw_args[1] = src - if(dupe_mode != COMPONENT_DUPE_ALLOWED && dupe_mode != COMPONENT_DUPE_SELECTIVE) + if(dupe_mode != COMPONENT_DUPE_ALLOWED && dupe_mode != COMPONENT_DUPE_SELECTIVE && dupe_mode != COMPONENT_DUPE_SOURCES) if(!dupe_type) old_component = GetExactComponent(component_type) else @@ -372,10 +373,14 @@ if(COMPONENT_DUPE_SOURCES) if(source in old_component.sources) return old_component // source already registered, no work to do - old_component.on_source_add(source) + + if(old_component.on_source_add(arglist(list(source) + raw_args.Copy(2))) == COMPONENT_INCOMPATIBLE) + stack_trace("incompatible source added to a [old_component.type]. Args: [json_encode(raw_args)]") + return null else if(!new_component) new_component = new component_type(raw_args) // There's a valid dupe mode but there's no old component, act like normal + else if(dupe_mode == COMPONENT_DUPE_SELECTIVE) var/list/arguments = raw_args.Copy() arguments[1] = new_component @@ -387,12 +392,17 @@ break if(!new_component && make_new_component) new_component = new component_type(raw_args) + + else if(dupe_mode == COMPONENT_DUPE_SOURCES) + new_component = new component_type(raw_args) + if(new_component.on_source_add(arglist(list(source) + raw_args.Copy(2))) == COMPONENT_INCOMPATIBLE) + stack_trace("incompatible source added to a [new_component.type]. Args: [json_encode(raw_args)]") + return null + else if(!new_component) new_component = new component_type(raw_args) // Dupes are allowed, act like normal if(!old_component && !QDELETED(new_component)) // Nothing related to duplicate components happened and the new component is healthy - if(uses_sources) // make sure they have the source added if they use sources - new_component.on_source_add(source) SEND_SIGNAL(src, COMSIG_COMPONENT_ADDED, new_component) return new_component diff --git a/code/datums/components/area_based_godmode.dm b/code/datums/components/area_based_godmode.dm new file mode 100644 index 00000000000..4f03ae57794 --- /dev/null +++ b/code/datums/components/area_based_godmode.dm @@ -0,0 +1,123 @@ +#define MAP_AREA_TYPE "area_type" +#define MAP_ALLOW_AREA_SUBTYPES "allow_area_subtypes" +#define DEFAULT_GAIN_MESSAGE span_big(span_green("You are now invulnerable.")) +#define DEFAULT_LOSE_MESSAGE span_big(span_red("You are no longer invulnerable.")) + +/** + * Area-based godmode. + * Gain and Lose message can only be set once, at initial component creation; adding a source will not update them. + */ +/datum/component/area_based_godmode + dupe_mode = COMPONENT_DUPE_SOURCES + + /// The type of area that will trigger godmode. + var/list/sources_to_area_type + + /// Whether or not to allow subtypes of the area type to trigger godmode. + var/allow_area_subtypes + + /// The message to send to the mob when they gain godmode. + var/gain_message + + /// The message to send to the mob when they lose godmode. + var/lose_message + + /// Cached state of check_area, prevents recalculating on source add + var/check_area_cached_state = FALSE + +/datum/component/area_based_godmode/Initialize( + area_type, + allow_area_subtypes, + gain_message = DEFAULT_GAIN_MESSAGE, + lose_message = DEFAULT_LOSE_MESSAGE, +) + var/mob/mob_target = parent + if(!istype(mob_target)) + return COMPONENT_INCOMPATIBLE + if(initial(mob_target.status_flags) & GODMODE) + return COMPONENT_INCOMPATIBLE + + sources_to_area_type = list() + src.gain_message = gain_message + src.lose_message = lose_message + RegisterSignal(mob_target, COMSIG_ENTER_AREA, PROC_REF(check_area)) + +/datum/component/area_based_godmode/UnregisterFromParent() + UnregisterSignal(parent, COMSIG_ENTER_AREA) + +/datum/component/area_based_godmode/on_source_add( + source, + area_type, + allow_area_subtypes = FALSE, + gain_message = DEFAULT_GAIN_MESSAGE, + lose_message = DEFAULT_LOSE_MESSAGE, +) + . = ..() + if(. == COMPONENT_INCOMPATIBLE) + return + + var/list/information_map = list( + MAP_AREA_TYPE = area_type, + MAP_ALLOW_AREA_SUBTYPES = allow_area_subtypes, + ) + sources_to_area_type[source] = information_map + + var/mob/mob_target = parent // no need to istype here, done at creation + mob_target.become_area_sensitive("[REF(src)]:[source]") + if(!check_area_cached_state) + check_area(mob_target) + +/datum/component/area_based_godmode/on_source_remove(source) + sources_to_area_type -= source + var/mob/mob_target = parent + mob_target.lose_area_sensitivity("[REF(src)]:[source]") + if(check_area_cached_state) + check_area(mob_target) + return ..() + +/datum/component/area_based_godmode/proc/check_in_valid_area(mob/checking) + var/list/area/allowed_areas = list() + for(var/source in sources_to_area_type) + var/list/source_map = sources_to_area_type[source] + var/area/top_level = source_map[MAP_AREA_TYPE] + if(!allowed_areas[top_level]) + allowed_areas[top_level] = source_map[MAP_ALLOW_AREA_SUBTYPES] + + if(!length(allowed_areas)) + stack_trace("called check_in_valid_area with zero sources") + return FALSE + + var/area/area = get_area(checking) + if(area.type in allowed_areas) + return TRUE + + for(var/area/allowed_area as anything in allowed_areas) + if(!allowed_areas[allowed_area]) + continue + if(istype(area, allowed_area)) + return TRUE + + return FALSE + +/datum/component/area_based_godmode/proc/check_area(mob/source) + SIGNAL_HANDLER + + var/has_godmode = source.status_flags & GODMODE + if(!check_in_valid_area(source)) + if(has_godmode) + to_chat(source, lose_message) + source.status_flags ^= GODMODE + check_area_cached_state = FALSE + return + + check_area_cached_state = TRUE + if(has_godmode) + return + + to_chat(source, gain_message) + source.status_flags ^= GODMODE + +#undef MAP_AREA_TYPE +#undef MAP_ALLOW_AREA_SUBTYPES +#undef DEFAULT_GAIN_MESSAGE +#undef DEFAULT_LOSE_MESSAGE diff --git a/code/modules/admin/view_variables/topic_basic.dm b/code/modules/admin/view_variables/topic_basic.dm index 167d44f7087..d98483e6442 100644 --- a/code/modules/admin/view_variables/topic_basic.dm +++ b/code/modules/admin/view_variables/topic_basic.dm @@ -61,22 +61,34 @@ names += componentsubtypes names += "---Elements---" names += sort_list(subtypesof(/datum/element), GLOBAL_PROC_REF(cmp_typepaths_asc)) + var/result = tgui_input_list(usr, "Choose a component/element to add", "Add Component", names) if(isnull(result)) return if(!usr || result == "---Components---" || result == "---Elements---") return + if(QDELETED(src)) to_chat(usr, "That thing doesn't exist anymore!", confidential = TRUE) return + + var/add_source + if(ispath(result, /datum/component)) + var/datum/component/comp_path = result + if(initial(comp_path.dupe_mode) == COMPONENT_DUPE_SOURCES) + add_source = tgui_input_text(usr, "Enter a source for the component", "Add Component", "ADMIN-ABUSE") + if(isnull(add_source)) + return + var/list/lst = get_callproc_args() if(!lst) return + var/datumname = "error" lst.Insert(1, result) if(result in componentsubtypes) datumname = "component" - target._AddComponent(lst) + target._AddComponent(lst, add_source) else datumname = "element" target._AddElement(lst) diff --git a/code/modules/shuttle/special.dm b/code/modules/shuttle/special.dm index c28aaf2731c..901b9a0987f 100644 --- a/code/modules/shuttle/special.dm +++ b/code/modules/shuttle/special.dm @@ -169,9 +169,7 @@ /mob/living/simple_animal/drone/snowflake/bardrone/Initialize(mapload) . = ..() access_card.add_access(list(ACCESS_CENT_BAR)) - become_area_sensitive(ROUNDSTART_TRAIT) - RegisterSignal(src, COMSIG_ENTER_AREA, PROC_REF(check_barstaff_godmode)) - check_barstaff_godmode() + AddComponentFrom(ROUNDSTART_TRAIT, /datum/component/area_based_godmode, area_type = /area/shuttle/escape, allow_area_subtypes = TRUE) /mob/living/simple_animal/hostile/alien/maid/barmaid gold_core_spawnable = NO_SPAWN @@ -192,22 +190,12 @@ access_card.add_access(cap_trim.access + cap_trim.wildcard_access + list(ACCESS_CENT_BAR)) ADD_TRAIT(access_card, TRAIT_NODROP, ABSTRACT_ITEM_TRAIT) - become_area_sensitive(ROUNDSTART_TRAIT) - RegisterSignal(src, COMSIG_ENTER_AREA, PROC_REF(check_barstaff_godmode)) - check_barstaff_godmode() + AddComponentFrom(ROUNDSTART_TRAIT, /datum/component/area_based_godmode, area_type = /area/shuttle/escape, allow_area_subtypes = TRUE) /mob/living/simple_animal/hostile/alien/maid/barmaid/Destroy() qdel(access_card) . = ..() -/mob/living/simple_animal/proc/check_barstaff_godmode() - SIGNAL_HANDLER - - if(istype(get_area(loc), /area/shuttle/escape)) - status_flags |= GODMODE - else - status_flags &= ~GODMODE - // Bar table, a wooden table that kicks you in a direction if you're not // barstaff (defined as someone who was a roundstart bartender or someone // with CENTCOM_BARSTAFF) diff --git a/tgstation.dme b/tgstation.dme index 98aa181de2c..8b8977f682a 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -870,6 +870,7 @@ #include "code\datums\components\ai_retaliate_advanced.dm" #include "code\datums\components\anti_magic.dm" #include "code\datums\components\aquarium.dm" +#include "code\datums\components\area_based_godmode.dm" #include "code\datums\components\area_sound_manager.dm" #include "code\datums\components\areabound.dm" #include "code\datums\components\armor_plate.dm"