diff --git a/code/__DEFINES/traits/declarations.dm b/code/__DEFINES/traits/declarations.dm index 6613ae862ed..1a28fe66c50 100644 --- a/code/__DEFINES/traits/declarations.dm +++ b/code/__DEFINES/traits/declarations.dm @@ -1657,6 +1657,9 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai /// Makes the owner desensetized to death, but happy whenever someone gets blown to pieces (as a sacrifice to the necropolis) unless its another worshipper #define TRAIT_NECROPOLIS_WORSHIP "necropolis_worship" +/// Applied to an area, stops random power failures from affecting it +#define TRAIT_AREA_BLOCK_POWER_FAIL "area_block_power_fail" + /// Sunlight on this turf is blocked and thus you can't get solar power or whatever #define TRAIT_TURF_SUN_BLOCKED "turf_sun_blocked" diff --git a/code/__HELPERS/areas.dm b/code/__HELPERS/areas.dm index ce482645ef7..5967e44a413 100644 --- a/code/__HELPERS/areas.dm +++ b/code/__HELPERS/areas.dm @@ -2,15 +2,6 @@ #define EXTRA_ROOM_CHECK_SKIP 1 #define EXTRA_ROOM_CHECK_FAIL 2 -GLOBAL_LIST_INIT(typecache_powerfailure_safe_areas, typecacheof(list( - /area/station/engineering/main, - /area/station/engineering/supermatter, - /area/station/engineering/atmospherics_engine, - /area/station/ai/satellite/chamber, - /area/ruin/comms_agent //fixes icemoon comms station being affected - -))) - // Gets an atmos isolated contained space // Returns an associative list of turf|dirs pairs // The dirs are connected turfs in the same space diff --git a/code/__HELPERS/game.dm b/code/__HELPERS/game.dm index 8dbec0cc56f..5ec019f7492 100644 --- a/code/__HELPERS/game.dm +++ b/code/__HELPERS/game.dm @@ -307,17 +307,20 @@ return FALSE -///Disable power in the station APCs +/** + * Disables power in most station APCs temporarily + * + * * duration_min - the minimum duration of the power failure in seconds (not deciseconds) + * * duration_max - the maximum duration of the power failure in seconds (not deciseconds) + */ /proc/power_fail(duration_min, duration_max) for(var/obj/machinery/power/apc/current_apc as anything in SSmachines.get_machines_by_type_and_subtypes(/obj/machinery/power/apc)) - if(!current_apc.cell || !SSmapping.level_trait(current_apc.z, ZTRAIT_STATION)) - continue - var/area/apc_area = current_apc.area - if(is_type_in_typecache(apc_area, GLOB.typecache_powerfailure_safe_areas)) + if(!current_apc.cell || !SSmapping.level_trait(current_apc.z, ZTRAIT_STATION) || HAS_TRAIT(current_apc.area, TRAIT_AREA_BLOCK_POWER_FAIL)) continue - var/duration = rand(duration_min,duration_max) + var/duration = rand(duration_min, duration_max) current_apc.energy_fail(duration) + CHECK_TICK /** * Sends a round tip to a target. If selected_tip is null, a random tip will be sent instead (5% chance of it being silly). diff --git a/code/_globalvars/traits/_traits.dm b/code/_globalvars/traits/_traits.dm index 18c9e3b0d26..5a68ba55356 100644 --- a/code/_globalvars/traits/_traits.dm +++ b/code/_globalvars/traits/_traits.dm @@ -99,6 +99,7 @@ GLOBAL_LIST_INIT(traits_by_type, list( "TRAIT_STRONGPULL" = TRAIT_STRONGPULL, ), /area = list( + "TRAIT_AREA_BLOCK_POWER_FAIL" = TRAIT_AREA_BLOCK_POWER_FAIL, "TRAIT_HAS_SHUTTLE_CONSTRUCTION_TURF" = TRAIT_HAS_SHUTTLE_CONSTRUCTION_TURF, ), /datum/controller/global_vars = list( diff --git a/code/datums/elements/block_area_power_fail.dm b/code/datums/elements/block_area_power_fail.dm new file mode 100644 index 00000000000..8de9ed24118 --- /dev/null +++ b/code/datums/elements/block_area_power_fail.dm @@ -0,0 +1,67 @@ +// Used to differentiate what is protecting an area +#define AREA_TRAIT_SOURCE(the_source) "[type]_[the_source.type]" +#define MOVABLE_TRAIT_SOURCE(the_source) "[type]_[REF(the_source)]" +#define TURF_TRAIT_SOURCE(the_source) "[type]_[the_source.x]-[the_source.y]-[the_source.z]" + +/** + * ## block_area_power_fail + * + * Element that interacts with the grid check event (and similar effects) to protect certain rooms + * + * * Attach to an area to prevent arbitrary power outages from affecting it + * * Attach to a movable to prevent arbitrary power outages from affecting the area it's in + * * Attach to a turf to prevent arbitrary power outages from affecting its area + */ +/datum/element/block_area_power_fail + element_flags = ELEMENT_DETACH_ON_HOST_DESTROY + +/datum/element/block_area_power_fail/Attach(datum/target) + . = ..() + if(isarea(target)) // practically just a complicated way to do add trait + ADD_TRAIT(target, TRAIT_AREA_BLOCK_POWER_FAIL, AREA_TRAIT_SOURCE(target)) + + else if(ismovable(target)) // manages adding and removing the trait as the movable moves + var/atom/movable/movable_target = target + movable_target.become_area_sensitive(type) + RegisterSignal(target, COMSIG_ENTER_AREA, PROC_REF(on_movable_entered_area)) + RegisterSignal(target, COMSIG_EXIT_AREA, PROC_REF(on_movable_exited_area)) + on_movable_entered_area(target, get_area(movable_target)) + + else if(isturf(target)) // turfs don't move so it just adds the trait to the turf's area + var/turf/turf_target = target + ADD_TRAIT(turf_target.loc, TRAIT_AREA_BLOCK_POWER_FAIL, TURF_TRAIT_SOURCE(turf_target)) + + else + return ELEMENT_INCOMPATIBLE + +/datum/element/block_area_power_fail/Detach(datum/source) + . = ..() + if(isarea(source)) + REMOVE_TRAIT(source, TRAIT_AREA_BLOCK_POWER_FAIL, AREA_TRAIT_SOURCE(source)) + + else if(ismovable(source)) + var/atom/movable/movable_source = source + movable_source.lose_area_sensitivity(type) + UnregisterSignal(source, COMSIG_ENTER_AREA) + UnregisterSignal(source, COMSIG_EXIT_AREA) + on_movable_exited_area(source, get_area(movable_source)) + + else if(isturf(source)) + var/turf/turf_source = source + REMOVE_TRAIT(turf_source.loc, TRAIT_AREA_BLOCK_POWER_FAIL, TURF_TRAIT_SOURCE(turf_source)) + +/datum/element/block_area_power_fail/proc/on_movable_entered_area(atom/movable/source, area/new_area) + SIGNAL_HANDLER + + if(new_area) + ADD_TRAIT(new_area, TRAIT_AREA_BLOCK_POWER_FAIL, MOVABLE_TRAIT_SOURCE(source)) + +/datum/element/block_area_power_fail/proc/on_movable_exited_area(atom/movable/source, area/old_area) + SIGNAL_HANDLER + + if(old_area) + REMOVE_TRAIT(old_area, TRAIT_AREA_BLOCK_POWER_FAIL, MOVABLE_TRAIT_SOURCE(source)) + +#undef AREA_TRAIT_SOURCE +#undef MOVABLE_TRAIT_SOURCE +#undef TURF_TRAIT_SOURCE diff --git a/code/game/area/areas/ruins/icemoon.dm b/code/game/area/areas/ruins/icemoon.dm index 80f8f6bb33a..d44ab31c59b 100644 --- a/code/game/area/areas/ruins/icemoon.dm +++ b/code/game/area/areas/ruins/icemoon.dm @@ -27,6 +27,10 @@ name = "\improper Listening Post" sound_environment = SOUND_ENVIRONMENT_CITY +/area/ruin/comms_agent/Initialize(mapload) + . = ..() + AddElement(/datum/element/block_area_power_fail) + /area/ruin/comms_agent/maint name = "\improper Listening Post Maintenance" sound_environment = SOUND_AREA_TUNNEL_ENCLOSED diff --git a/code/game/area/areas/station/ai.dm b/code/game/area/areas/station/ai.dm index ff12a728f28..faa717845b3 100644 --- a/code/game/area/areas/station/ai.dm +++ b/code/game/area/areas/station/ai.dm @@ -45,6 +45,10 @@ icon_state = "ai_chamber" annoying_ambience = null +/area/station/ai/satellite/chamber/Initialize(mapload) + . = ..() + AddElement(/datum/element/block_area_power_fail) + /area/station/ai/satellite/exterior name = "\improper AI Satellite Exterior" secure = FALSE diff --git a/code/game/area/areas/station/engineering.dm b/code/game/area/areas/station/engineering.dm index 1193404c975..9ad25f233cb 100644 --- a/code/game/area/areas/station/engineering.dm +++ b/code/game/area/areas/station/engineering.dm @@ -16,6 +16,10 @@ name = "Engineering" icon_state = "engine" +/area/station/engineering/main/Initialize(mapload) + . = ..() + AddElement(/datum/element/block_area_power_fail) + /area/station/engineering/hallway name = "Engineering Hallway" icon_state = "engine_hallway" @@ -69,6 +73,10 @@ icon_state = "atmos_engine" area_flags = BLOBS_ALLOWED | CULT_PERMITTED +/area/station/engineering/atmospherics_engine/Initialize(mapload) + . = ..() + AddElement(/datum/element/block_area_power_fail) + /area/station/engineering/lobby name = "\improper Engineering Lobby" icon_state = "engi_lobby" @@ -79,6 +87,10 @@ area_flags = BLOBS_ALLOWED | CULT_PERMITTED sound_environment = SOUND_AREA_SMALL_ENCLOSED +/area/station/engineering/supermatter/Initialize(mapload) + . = ..() + AddElement(/datum/element/block_area_power_fail) + /area/station/engineering/supermatter/waste name = "\improper Supermatter Waste Chamber" icon_state = "engine_sm_waste" diff --git a/code/game/gamemodes/events.dm b/code/game/gamemodes/events.dm index 171086b06b6..002ec4787a6 100644 --- a/code/game/gamemodes/events.dm +++ b/code/game/gamemodes/events.dm @@ -23,7 +23,7 @@ continue if(!station_area.requires_power || station_area.always_unpowered ) continue - if(GLOB.typecache_powerfailure_safe_areas[station_area.type]) + if(HAS_TRAIT(station_area, TRAIT_AREA_BLOCK_POWER_FAIL)) continue station_area.power_light = FALSE @@ -34,7 +34,7 @@ for(var/obj/machinery/power/apc/C as anything in SSmachines.get_machines_by_type_and_subtypes(/obj/machinery/power/apc)) if(C.cell && is_station_level(C.z)) var/area/A = C.area - if(GLOB.typecache_powerfailure_safe_areas[A.type]) + if(HAS_TRAIT(A, TRAIT_AREA_BLOCK_POWER_FAIL)) continue C.cell.charge = 0 diff --git a/code/game/machinery/_machinery.dm b/code/game/machinery/_machinery.dm index fc61e5f062e..26a8fabd0cf 100644 --- a/code/game/machinery/_machinery.dm +++ b/code/game/machinery/_machinery.dm @@ -185,12 +185,15 @@ if(occupant_typecache) occupant_typecache = typecacheof(occupant_typecache) - if((resistance_flags & INDESTRUCTIBLE) && component_parts){ // This is needed to prevent indestructible machinery still blowing up. If an explosion occurs on the same tile as the indestructible machinery without the PREVENT_CONTENTS_EXPLOSION_1 flag, /datum/controller/subsystem/explosions/proc/propagate_blastwave will call ex_act on all movable atoms inside the machine, including the circuit board and component parts. However, if those parts get deleted, the entire machine gets deleted, allowing for INDESTRUCTIBLE machines to be destroyed. (See #62164 for more info) + if((resistance_flags & INDESTRUCTIBLE) && component_parts) // This is needed to prevent indestructible machinery still blowing up. If an explosion occurs on the same tile as the indestructible machinery without the PREVENT_CONTENTS_EXPLOSION_1 flag, /datum/controller/subsystem/explosions/proc/propagate_blastwave will call ex_act on all movable atoms inside the machine, including the circuit board and component parts. However, if those parts get deleted, the entire machine gets deleted, allowing for INDESTRUCTIBLE machines to be destroyed. (See #62164 for more info) flags_1 |= PREVENT_CONTENTS_EXPLOSION_1 - } + + if(critical_machine) + AddElement(/datum/element/block_area_power_fail) if(HAS_TRAIT(SSstation, STATION_TRAIT_MACHINES_GLITCHED) && mapload) randomize_language_if_on_station() + SEND_GLOBAL_SIGNAL(COMSIG_GLOB_NEW_MACHINE, src) return INITIALIZE_HINT_LATELOAD @@ -1289,8 +1292,18 @@ set_machine_stat(vval) datum_flags |= DF_VAR_EDITED return TRUE + if(vname == NAMEOF(src, critical_machine)) + if(critical_machine == !!vval) // boolean cast in case a badmin tries to set it to 2 for some reason + return FALSE + critical_machine = !!vval + if(critical_machine) + AddElement(/datum/element/block_area_power_fail) + else + RemoveElement(/datum/element/block_area_power_fail) + datum_flags |= DF_VAR_EDITED + return TRUE - return ..() + . = ..() /** * Alerts the AI that a hack is in progress. diff --git a/code/modules/events/grid_check.dm b/code/modules/events/grid_check.dm index 8fd3b86f14b..d68cdb72f14 100644 --- a/code/modules/events/grid_check.dm +++ b/code/modules/events/grid_check.dm @@ -27,4 +27,4 @@ COOLDOWN_START(controller, announcement_spam_protection, 30 SECONDS) /datum/round_event/grid_check/start() - power_fail(30, 120) + power_fail(60, 240) // 1 to 4 minutes diff --git a/code/modules/mob/living/silicon/ai/ai.dm b/code/modules/mob/living/silicon/ai/ai.dm index 63d59db1a53..aa960b3444a 100644 --- a/code/modules/mob/living/silicon/ai/ai.dm +++ b/code/modules/mob/living/silicon/ai/ai.dm @@ -97,6 +97,7 @@ RegisterSignal(ai_tracking_tool, COMSIG_TRACKABLE_GLIDE_CHANGED, PROC_REF(tracked_glidesize_changed)) add_traits(list(TRAIT_PULL_BLOCKED, TRAIT_AI_ACCESS, TRAIT_HANDS_BLOCKED, TRAIT_CAN_GET_AI_TRACKING_MESSAGE, TRAIT_LOUD_BINARY), INNATE_TRAIT) + AddElement(/datum/element/block_area_power_fail) //Heads up to other binary chat listeners that a new AI is online and listening to Binary. if(announce_init_to_others && !is_centcom_level(z)) //Skip new syndicate AIs and also new AIs on centcom Z diff --git a/code/modules/power/apc/apc_main.dm b/code/modules/power/apc/apc_main.dm index 4af0bb0496b..7335f9a9c44 100644 --- a/code/modules/power/apc/apc_main.dm +++ b/code/modules/power/apc/apc_main.dm @@ -100,7 +100,7 @@ var/long_term_power = 10 ///Automatically name the APC after the area is in var/auto_name = FALSE - ///Time to allow the APC to regain some power and to turn the channels back online + ///Time to allow the APC to regain some power and to turn the channels back online in seconds var/failure_timer = 0 ///Forces an update on the power use to ensure that the apc has enough power var/force_update = FALSE @@ -282,8 +282,8 @@ /obj/machinery/power/apc/on_saboteur(datum/source, disrupt_duration) . = ..() - disrupt_duration *= 0.1 // so, turns out, failure timer is in seconds, not deciseconds; without this, disruptions last 10 times as long as they probably should - energy_fail(disrupt_duration) + // failure timer is in seconds, not deciseconds, so we need to convert + energy_fail(disrupt_duration * 0.1) return TRUE /obj/machinery/power/apc/on_set_is_operational(old_value) @@ -589,7 +589,7 @@ if(!area?.requires_power) return if(failure_timer) - failure_timer-- + failure_timer = max(0, failure_timer - seconds_per_tick) force_update = TRUE return diff --git a/code/modules/power/apc/apc_power_proc.dm b/code/modules/power/apc/apc_power_proc.dm index a561ba88a84..cd1b412181a 100644 --- a/code/modules/power/apc/apc_power_proc.dm +++ b/code/modules/power/apc/apc_power_proc.dm @@ -127,16 +127,15 @@ terminal.master = null terminal = null +/** + * Temporarily disables all power to the room for a set duration + * + * Some rooms are immune to this effect due to having important machines + * + * * duration - the duration of the power failure in seconds (not deciseconds) + */ /obj/machinery/power/apc/proc/energy_fail(duration) - for(var/obj/machinery/failing_machine in area.contents) - if(failing_machine.critical_machine) - return - - for(var/mob/living/silicon/ai as anything in GLOB.ai_list) - if(get_area(ai) == area) - return - - failure_timer = max(failure_timer, round(duration)) + failure_timer = max(failure_timer, round(duration, SSMACHINES_DT)) update() queue_icon_update() @@ -147,10 +146,9 @@ if(nightshift_lights == on) return //no change nightshift_lights = on - for (var/list/zlevel_turfs as anything in area.get_zlevel_turf_lists()) - for(var/turf/area_turf as anything in zlevel_turfs) - for(var/obj/machinery/light/night_light in area_turf) - if(night_light.nightshift_allowed) - night_light.nightshift_enabled = nightshift_lights - night_light.update(FALSE) - CHECK_TICK + for(var/turf/area_turf as anything in area.get_turfs_from_all_zlevels()) + for(var/obj/machinery/light/night_light in area_turf) + if(night_light.nightshift_allowed) + night_light.nightshift_enabled = nightshift_lights + night_light.update(FALSE) + CHECK_TICK diff --git a/tgstation.dme b/tgstation.dme index 4b491aeb074..1c0af793190 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -1550,6 +1550,7 @@ #include "code\datums\elements\beauty.dm" #include "code\datums\elements\bed_tucking.dm" #include "code\datums\elements\befriend_petting.dm" +#include "code\datums\elements\block_area_power_fail.dm" #include "code\datums\elements\block_turf_fingerprints.dm" #include "code\datums\elements\blocks_explosives.dm" #include "code\datums\elements\blood_limb_overlay.dm"