diff --git a/_maps/map_files/IceBoxStation/IceBoxStation.dmm b/_maps/map_files/IceBoxStation/IceBoxStation.dmm index dafb5f834a1..c3d0f0dc4c8 100644 --- a/_maps/map_files/IceBoxStation/IceBoxStation.dmm +++ b/_maps/map_files/IceBoxStation/IceBoxStation.dmm @@ -53711,6 +53711,7 @@ }, /obj/effect/turf_decal/delivery, /obj/machinery/bluespace_vendor/north, +/obj/effect/mapping_helpers/trapdoor_placer, /turf/open/floor/iron, /area/hallway/primary/central) "vHp" = ( @@ -54657,7 +54658,14 @@ /area/security/brig) "wit" = ( /obj/structure/table, -/obj/machinery/recharger, +/obj/machinery/recharger{ + pixel_x = 5; + pixel_y = 2 + }, +/obj/item/trapdoor_remote/preloaded{ + pixel_x = -5; + pixel_y = 2 + }, /turf/open/floor/iron, /area/command/heads_quarters/hop) "wiA" = ( diff --git a/_maps/map_files/IceBoxStation/IcemoonUnderground_Above.dmm b/_maps/map_files/IceBoxStation/IcemoonUnderground_Above.dmm index 0235d0465bb..b5f4a6d5d37 100644 --- a/_maps/map_files/IceBoxStation/IcemoonUnderground_Above.dmm +++ b/_maps/map_files/IceBoxStation/IcemoonUnderground_Above.dmm @@ -2916,6 +2916,9 @@ /obj/effect/spawner/structure/window/reinforced, /turf/open/floor/plating, /area/mine/laborcamp/security) +"sT" = ( +/turf/open/floor/plating/snowed/smoothed/icemoon, +/area/mine/storage) "sX" = ( /obj/structure/rack, /obj/effect/spawner/lootdrop/maintenance/two, @@ -32018,9 +32021,9 @@ ak ak ak ak -ak -ak -ak +AF +sT +AF ak ak ak @@ -32275,9 +32278,9 @@ ak ak ak ak -ak -ak -ak +sT +NH +sT ak ak ak @@ -32532,9 +32535,9 @@ ak ak ak ak -ak -ak -ak +AF +sT +AF ak ak ak @@ -32789,9 +32792,9 @@ ak ak ak ak -ak -ak -ak +NH +NH +NH ak ak ak @@ -33045,10 +33048,10 @@ ak ak ak ak -ak -ak -ak -ak +NH +NH +NH +NH ak ak ak @@ -33302,10 +33305,10 @@ ak ak ak ak -ak -ak -ak -ak +NH +NH +NH +NH ak ak ak @@ -33558,12 +33561,12 @@ ak ak ak ak -ak -ak -ak -ak -ak -ak +NH +NH +NH +NH +NH +NH ak ak ak @@ -33818,9 +33821,9 @@ NH NH NH NH -ak -ak -ak +NH +NH +NH ak ak ak @@ -34076,8 +34079,8 @@ NH NH NH NH -ak -ak +NH +NH ak ak ak diff --git a/_maps/map_files/tramstation/tramstation.dmm b/_maps/map_files/tramstation/tramstation.dmm index 0c82f2a37eb..ca8c20383ce 100644 --- a/_maps/map_files/tramstation/tramstation.dmm +++ b/_maps/map_files/tramstation/tramstation.dmm @@ -9034,6 +9034,9 @@ pixel_y = -34; req_access_txt = "28" }, +/obj/item/trapdoor_remote/preloaded{ + pixel_x = -8 + }, /turf/open/floor/wood, /area/command/heads_quarters/hop) "aCS" = ( @@ -42336,6 +42339,7 @@ "mjK" = ( /obj/effect/turf_decal/delivery, /obj/effect/landmark/start/hangover, +/obj/effect/mapping_helpers/trapdoor_placer, /turf/open/floor/iron, /area/hallway/secondary/command) "mjQ" = ( diff --git a/code/__DEFINES/combat.dm b/code/__DEFINES/combat.dm index 4348653babb..a8c59583256 100644 --- a/code/__DEFINES/combat.dm +++ b/code/__DEFINES/combat.dm @@ -129,6 +129,7 @@ #define ATTACK_EFFECT_BOOP "boop" //Honk //the define for visible message range in combat +#define SAMETILE_MESSAGE_RANGE 1 #define COMBAT_MESSAGE_RANGE 3 #define DEFAULT_MESSAGE_RANGE 7 diff --git a/code/__DEFINES/dcs/signals.dm b/code/__DEFINES/dcs/signals.dm index 6288faf0c7d..248772f6cba 100644 --- a/code/__DEFINES/dcs/signals.dm +++ b/code/__DEFINES/dcs/signals.dm @@ -34,6 +34,10 @@ #define CANCEL_PRE_RANDOM_EVENT (1<<0) /// a person somewhere has thrown something : (mob/living/carbon/carbon_thrower, target) #define COMSIG_GLOB_CARBON_THROW_THING "!throw_thing" +/// a trapdoor remote has sent out a signal to link with a trapdoor +#define COMSIG_GLOB_TRAPDOOR_LINK "!trapdoor_link" + ///successfully linked to a trapdoor! + #define LINKED_UP (1<<0) /// an obj/item is created! (obj/item/created_item) #define COMSIG_GLOB_NEW_ITEM "!new_item" @@ -382,6 +386,9 @@ ///from /turf/open/temperature_expose(datum/gas_mixture/air, exposed_temperature) #define COMSIG_TURF_EXPOSE "turf_expose" +///from /datum/element/decal/Detach(): (description, cleanable, directional, mutable_appearance/pic) +#define COMSIG_TURF_DECAL_DETACHED "turf_decal_detached" + // /atom/movable signals ///from base of atom/movable/Moved(): (/atom) diff --git a/code/datums/components/trapdoor.dm b/code/datums/components/trapdoor.dm new file mode 100644 index 00000000000..8ab65607b07 --- /dev/null +++ b/code/datums/components/trapdoor.dm @@ -0,0 +1,269 @@ + +///makes this file more legible +#define IS_OPEN(parent) isgroundlessturf(parent) +///distance a trapdoor will accept a link request. +#define TRAPDOOR_LINKING_SEARCH_RANGE 7 + +/** + * ## trapdoor component! + * + * component attached to floors to turn them into trapdoors, a constructable trap that when signalled drops people to the level below. + * assembly code at the bottom of this file + */ +/datum/component/trapdoor + ///assembly tied to this trapdoor + var/obj/item/assembly/trapdoor/assembly + ///path of the turf this should change into when the assembly is pulsed. needed for openspace trapdoors knowing what to turn back into + var/trapdoor_turf_path + +/datum/component/trapdoor/Initialize(starts_open, trapdoor_turf_path, assembly) + if(!isopenturf(parent)) + return COMPONENT_INCOMPATIBLE + + src.assembly = assembly + if(IS_OPEN(parent)) + openspace_trapdoor_setup(trapdoor_turf_path, assembly) + else + tile_trapdoor_setup(trapdoor_turf_path, assembly) + + if(starts_open) + try_opening() + +///initializing as an opened trapdoor, we need to trust that we were given the data by a closed trapdoor +/datum/component/trapdoor/proc/openspace_trapdoor_setup(trapdoor_turf_path) + src.trapdoor_turf_path = trapdoor_turf_path + +///initializing as a closed trapdoor, we need to take data from the tile we're on to give it to the open state to store +/datum/component/trapdoor/proc/tile_trapdoor_setup(trapdoor_turf_path) + src.trapdoor_turf_path = parent.type + if(assembly && assembly.stored_decals.len) + reapply_all_decals() + +/datum/component/trapdoor/RegisterWithParent() + . = ..() + RegisterSignal(parent, COMSIG_TURF_CHANGE, .proc/turf_changed_pre) + if(!src.assembly) + RegisterSignal(SSdcs, COMSIG_GLOB_TRAPDOOR_LINK, .proc/on_link_requested) + else + RegisterSignal(assembly, COMSIG_ASSEMBLY_PULSED, .proc/toggle_trapdoor) + +/datum/component/trapdoor/UnregisterFromParent() + . = ..() + UnregisterSignal(SSdcs, COMSIG_GLOB_TRAPDOOR_LINK) + UnregisterSignal(assembly, COMSIG_ASSEMBLY_PULSED) + UnregisterSignal(parent, COMSIG_TURF_CHANGE) + +/datum/component/trapdoor/proc/decal_detached(datum/source, description, cleanable, directional, pic) + SIGNAL_HANDLER + ///so it adds the list to the list, not appending it to the end. thank you byond, very cool. + assembly.stored_decals += list(list(description, cleanable, directional, pic)) + +/** + * ## reapply_all_decals + * + * changing turfs does not bring over decals, so we must perform a little bit of element reapplication. + */ +/datum/component/trapdoor/proc/reapply_all_decals() + for(var/list/element_data as anything in assembly.stored_decals) + apply_decal(element_data[1], element_data[2], element_data[3], element_data[4]) + assembly.stored_decals = list() + +/// small proc that takes passed arguments and drops it into a new element +/datum/component/trapdoor/proc/apply_decal(description, cleanable, directional, pic) + parent.AddElement(/datum/element/decal, _description = description, _cleanable = cleanable, _dir = directional, _pic = pic) + +///called by linking remotes to tie an assembly to the trapdoor +/datum/component/trapdoor/proc/on_link_requested(datum/source, obj/item/assembly/trapdoor/assembly) + if(get_dist(parent, assembly) > TRAPDOOR_LINKING_SEARCH_RANGE) + return + . = LINKED_UP + src.assembly = assembly + assembly.linked = TRUE + UnregisterSignal(SSdcs, COMSIG_GLOB_TRAPDOOR_LINK) + RegisterSignal(assembly, COMSIG_ASSEMBLY_PULSED, .proc/toggle_trapdoor) + +///signal called by our assembly being pulsed +/datum/component/trapdoor/proc/toggle_trapdoor(datum/source) + SIGNAL_HANDLER + if(!IS_OPEN(parent)) + try_opening() + else + try_closing() + +///signal called by turf changing +/datum/component/trapdoor/proc/turf_changed_pre(datum/source, path, new_baseturfs, flags, post_change_callbacks) + SIGNAL_HANDLER + var/turf/open/dying_trapdoor = parent + if((!IS_OPEN(dying_trapdoor) && !IS_OPEN(path)) || path == /turf/open/floor/plating) //not a process of the trapdoor, so this trapdoor has been destroyed + dying_trapdoor.visible_message("The trapdoor mechanism in [dying_trapdoor] is broken!") + if(assembly) + assembly.linked = FALSE + assembly.stored_decals.Cut() + assembly = null + return + post_change_callbacks += CALLBACK(assembly, /obj/item/assembly/trapdoor.proc/carry_over_trapdoor, trapdoor_turf_path) + +/** + * ## carry_over_trapdoor + * + * applies the trapdoor to the new turf (created by the last trapdoor) + * apparently callbacks with arguments on invoke and the callback itself have the callback args go first. interesting! + */ +/obj/item/assembly/trapdoor/proc/carry_over_trapdoor(trapdoor_turf_path, turf/new_turf) + new_turf.AddComponent(/datum/component/trapdoor, FALSE, trapdoor_turf_path, src) + +/** + * ## try_opening + * + * small proc for opening the turf into openspace + * there are no checks for opening a trapdoor, but closed has some + */ +/datum/component/trapdoor/proc/try_opening() + var/turf/open/trapdoor_turf = parent + ///we want to save this turf's decals as they were right before deletion, so this is the point where we begin listening + if(assembly) + RegisterSignal(parent, COMSIG_TURF_DECAL_DETACHED, .proc/decal_detached) + playsound(trapdoor_turf, 'sound/machines/trapdoor/trapdoor_open.ogg', 50) + trapdoor_turf.visible_message("[trapdoor_turf] swings open!") + trapdoor_turf.ChangeTurf(/turf/open/openspace, flags = CHANGETURF_INHERIT_AIR) + +/** + * ## try_closing + * + * small proc for closing the turf back into what it should be + * trapdoor can be blocked by building things on the openspace turf + */ +/datum/component/trapdoor/proc/try_closing() + var/turf/open/trapdoor_turf = parent + var/obj/structure/lattice/blocking = locate() in trapdoor_turf.contents + if(blocking) + trapdoor_turf.visible_message("The trapdoor mechanism in [trapdoor_turf] tries to shut, but is jammed by [blocking]!") + return + playsound(trapdoor_turf, 'sound/machines/trapdoor/trapdoor_shut.ogg', 50) + trapdoor_turf.visible_message("The trapdoor mechanism in [trapdoor_turf] swings shut!") + trapdoor_turf.ChangeTurf(trapdoor_turf_path, flags = CHANGETURF_INHERIT_AIR) + +#undef IS_OPEN + +/obj/item/assembly/trapdoor + name = "trapdoor controller" + desc = "A sinister-looking controller for a trapdoor." + icon_state = "trapdoor" + ///if the trapdoor isn't linked it will try to link on pulse, this shouldn't be spammable + COOLDOWN_DECLARE(search_cooldown) + ///trapdoor link cooldown time here! + var/search_cooldown_time = 10 SECONDS + ///if true, a trapdoor in the world has a reference to this assembly and is listening for when it is pulsed. + var/linked = FALSE + /** + * list of lists that are arguments for readding decals when the linked trapdoor comes back. pain. + * + * we are storing this data FOR the trapdoor component we are linked to. kinda like a multitool. + * format: list(list(element's description, element's cleanable, element's directional, element's pic)) + * the list will be filled with all the data of the deleting elements (when ChangeTurf is called) only when the trapdoor begins to open. + * so any other case the elements will be changed but not recorded. + */ + var/list/stored_decals = list() + + +/obj/item/assembly/trapdoor/pulsed(radio) + . = ..() + if(linked) + return + if(!COOLDOWN_FINISHED(src, search_cooldown)) + visible_message("[src] cannot attempt another trapdoor linkup so soon!") + return + attempt_link_up() + COOLDOWN_START(src, search_cooldown, search_cooldown_time) + +/obj/item/assembly/trapdoor/proc/attempt_link_up() + var/turf/assembly_turf = get_turf(src) + if(!COOLDOWN_FINISHED(src, search_cooldown)) + var/timeleft = DisplayTimeText(COOLDOWN_TIMELEFT(src, search_cooldown)) + assembly_turf.visible_message("[src] is on cooldown! Please wait [timeleft].", vision_distance = SAMETILE_MESSAGE_RANGE) + return + if(SEND_GLOBAL_SIGNAL(COMSIG_GLOB_TRAPDOOR_LINK, src) & LINKED_UP) + playsound(assembly_turf, 'sound/machines/chime.ogg', 50, TRUE) + assembly_turf.visible_message("[src] has linked up to a nearby trapdoor! \ + You may now use it to check where the trapdoor is... be careful!", vision_distance = SAMETILE_MESSAGE_RANGE) + else + playsound(assembly_turf, 'sound/machines/buzz-sigh.ogg', 50, FALSE) + assembly_turf.visible_message("[src] has failed to find a trapdoor nearby to link to.", vision_distance = SAMETILE_MESSAGE_RANGE) + +/** + * ## trapdoor remotes! + * + * Item that accepts the assembly for the internals and helps link/activate it. + * This base type is an empty shell that needs the assembly added to it first to work. + */ +/obj/item/trapdoor_remote + name = "trapdoor remote" + desc = "A small machine that interfaces with a trapdoor controller for easy use." + icon = 'icons/obj/device.dmi' + icon_state = "trapdoor_remote" + COOLDOWN_DECLARE(trapdoor_cooldown) + var/trapdoor_cooldown_time = 2 SECONDS + var/obj/item/assembly/trapdoor/internals + +/obj/item/trapdoor_remote/examine(mob/user) + . = ..() + if(!internals) + . += "[src] has no internals! It needs a trapdoor controller to function." + return + . += "The internals can be removed with a screwdriver." + if(!internals.linked) + . += "[src] is not linked to a trapdoor." + return + . += "[src] is linked to a trapdoor." + if(!COOLDOWN_FINISHED(src, trapdoor_cooldown)) + . += "It is on a short cooldown." + +/obj/item/trapdoor_remote/screwdriver_act(mob/living/user, obj/item/tool) + . = ..() + if(!internals) + to_chat(user, "[src] has no internals!") + return + to_chat(user, "You pop [internals] out of [src].") + internals.forceMove(get_turf(src)) + internals = null + +/obj/item/trapdoor_remote/attackby(obj/item/assembly/trapdoor/assembly, mob/living/user, params) + . = ..() + if(. || !istype(assembly)) + return + if(internals) + to_chat(user, "[src] already has internals!") + return + to_chat(user, "You add [assembly] to [src].") + internals = assembly + assembly.forceMove(src) + +/obj/item/trapdoor_remote/attack_self(mob/user, modifiers) + . = ..() + if(.) + return + if(!internals) + to_chat(user, "[src] has no internals!") + return + if(!internals.linked) + to_chat(user, "You activate [src].") + internals.pulsed() + return + if(!COOLDOWN_FINISHED(src, trapdoor_cooldown)) + to_chat(user, "[src] is on a short cooldown.") + return + to_chat(user, "You activate [src].") + playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, FALSE) + icon_state = "trapdoor_pressed" + addtimer(VARSET_CALLBACK(src, icon_state, initial(icon_state)), trapdoor_cooldown_time) + COOLDOWN_START(src, trapdoor_cooldown, trapdoor_cooldown_time) + internals.pulsed() + +#undef TRAPDOOR_LINKING_SEARCH_RANGE + +///subtype with internals already included. If you're giving a department a roundstart trapdoor, this is what you want +/obj/item/trapdoor_remote/preloaded + +/obj/item/trapdoor_remote/preloaded/Initialize() + . = ..() + internals = new(src) diff --git a/code/datums/elements/decal.dm b/code/datums/elements/decal.dm index 500a0264267..cce7ddfd188 100644 --- a/code/datums/elements/decal.dm +++ b/code/datums/elements/decal.dm @@ -59,10 +59,12 @@ /datum/element/decal/Attach(atom/target, _icon, _icon_state, _dir, _cleanable=FALSE, _color, _layer=TURF_LAYER, _description, _alpha=255, mutable_appearance/_pic) . = ..() - if(!isatom(target) || !generate_appearance(_icon, _icon_state, _dir, _layer, _color, _alpha, target)) + if(!isatom(target)) return ELEMENT_INCOMPATIBLE if(_pic) pic = _pic + else if(!generate_appearance(_icon, _icon_state, _dir, _layer, _color, _alpha, target)) + return ELEMENT_INCOMPATIBLE description = _description cleanable = _cleanable directional = _dir @@ -83,6 +85,14 @@ RegisterSignal(target, COMSIG_TURF_ON_SHUTTLE_MOVE, .proc/shuttle_move_react,TRUE) +/** + * ## generate_appearance + * + * If the decal was not given an appearance, it will generate one based on the other given arguments. + * element won't be compatible if it cannot do either + * all args are fed into creating an image, they are byond vars for images you'll recognize in the byond docs + * (except source, source is the object whose appearance we're copying.) + */ /datum/element/decal/proc/generate_appearance(_icon, _icon_state, _dir, _layer, _color, _alpha, source) if(!_icon || !_icon_state) return FALSE @@ -97,6 +107,7 @@ source.update_appearance() if(isitem(source)) INVOKE_ASYNC(source, /obj/item/.proc/update_slot_icon) + SEND_SIGNAL(source, COMSIG_TURF_DECAL_DETACHED, description, cleanable, directional, pic) return ..() /datum/element/decal/proc/late_update_icon(atom/source) diff --git a/code/game/objects/effects/temporary_visuals/temporary_visual.dm b/code/game/objects/effects/temporary_visuals/temporary_visual.dm index ee6ef355dd0..a4a6fcf9d91 100644 --- a/code/game/objects/effects/temporary_visuals/temporary_visual.dm +++ b/code/game/objects/effects/temporary_visuals/temporary_visual.dm @@ -1,11 +1,14 @@ -//temporary visual effects +///temporary visual effects /obj/effect/temp_visual icon_state = "nothing" anchored = TRUE layer = ABOVE_MOB_LAYER mouse_opacity = MOUSE_OPACITY_TRANSPARENT - var/duration = 10 //in deciseconds + ///time, in deciseconds, that this object will exist + var/duration = 10 + ///if true, will pick a random direction when created. var/randomdir = TRUE + ///id of the deletion timer var/timerid /obj/effect/temp_visual/Initialize() diff --git a/code/modules/mapping/mapping_helpers.dm b/code/modules/mapping/mapping_helpers.dm index 6a6b45879bc..5786007f64a 100644 --- a/code/modules/mapping/mapping_helpers.dm +++ b/code/modules/mapping/mapping_helpers.dm @@ -490,3 +490,21 @@ INITIALIZE_IMMEDIATE(/obj/effect/mapping_helpers/no_lava) icon_cache[url] = I query_in_progress = FALSE return I + +/** + * ## trapdoor placer! + * + * This places an unlinked trapdoor in the tile its on (so someone with a remote needs to link it up first) + * Admins may spawn this in the round for additional trapdoors if they so desire + * if YOU want to learn more about trapdoors, read about the component at trapdoor.dm + * note: this is not a turf subtype because the trapdoor needs the type of the turf to turn back into + */ +/obj/effect/mapping_helpers/trapdoor_placer + name = "trapdoor placer" + late = TRUE + icon_state = "trapdoor" + +/obj/effect/mapping_helpers/trapdoor_placer/LateInitialize() + var/turf/component_target = get_turf(src) + component_target.AddComponent(/datum/component/trapdoor, starts_open = FALSE) + qdel(src) diff --git a/icons/effects/mapping_helpers.dmi b/icons/effects/mapping_helpers.dmi index e141a97dbd2..b4f0d5d11f0 100644 Binary files a/icons/effects/mapping_helpers.dmi and b/icons/effects/mapping_helpers.dmi differ diff --git a/icons/obj/assemblies/new_assemblies.dmi b/icons/obj/assemblies/new_assemblies.dmi index ebbf78692a5..469d4d16d57 100644 Binary files a/icons/obj/assemblies/new_assemblies.dmi and b/icons/obj/assemblies/new_assemblies.dmi differ diff --git a/icons/obj/device.dmi b/icons/obj/device.dmi index 85936c1e34a..7b70285723b 100644 Binary files a/icons/obj/device.dmi and b/icons/obj/device.dmi differ diff --git a/sound/machines/trapdoor/trapdoor_open.ogg b/sound/machines/trapdoor/trapdoor_open.ogg new file mode 100644 index 00000000000..f64212b9117 Binary files /dev/null and b/sound/machines/trapdoor/trapdoor_open.ogg differ diff --git a/sound/machines/trapdoor/trapdoor_shut.ogg b/sound/machines/trapdoor/trapdoor_shut.ogg new file mode 100644 index 00000000000..21021c5f8c1 Binary files /dev/null and b/sound/machines/trapdoor/trapdoor_shut.ogg differ diff --git a/tgstation.dme b/tgstation.dme index a84c421c03b..c27e000c821 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -567,6 +567,7 @@ #include "code\datums\components\technoshy.dm" #include "code\datums\components\tether.dm" #include "code\datums\components\thermite.dm" +#include "code\datums\components\trapdoor.dm" #include "code\datums\components\twohanded.dm" #include "code\datums\components\udder.dm" #include "code\datums\components\uplink.dm"