From f7e80ff35bb7a258c5b41a6ede57a4c2f2895355 Mon Sep 17 00:00:00 2001 From: RandomGamer123 <31096837+RandomGamer123@users.noreply.github.com> Date: Sat, 13 Aug 2022 20:48:55 -0400 Subject: [PATCH] Player-craftable Trapdoors (#68920) Makes trapdoors player-craftable. Player-crafted trapdoors have a tiny outline as well as being visible on examine. (Mapped in trapdoors have conspicuous = FALSE by default) Trapdoors can be made by using a trapdoor kit (crafted via player crafting menu) on openspace. Trapdoor electronics / assembly can be made via autolathe or engineering lathe. A preloaded trapdoor remote (optional) can be made by crafting the electronics with a compact remote (from science lathe / circuit lab) and some cable coil. Also allows unlinking trapdoors via a multitool, decreased trapdoor link range, and made some changes to trapdoor code. Added change: Trapdoors now don't break with just a crowbar Why It's Good For The Game Trapdoors currently can only be added in by mappers and this would allow for a lot of interesting contraptions, while having player-crafted trapdoors be detectable provides some counterplay Picture trapdoorlookdemo Changelog cl add: You can now make trapdoors. Craft a trapdoor kit and use it on an openspace tile to make one, then link and activate it with some trapdoor electronics (printable at an autolathe or the engineering lathe) and optionally a trapdoor remote (crafted in personal crafting menu). qol: You can now unlink trapdoors by using a multitool on them. balance: Trapdoors now won't break if you just crowbar them. You need to block them from closing, such as with a lattice, cover it up with a wall, or fully destroy the floor tile its on to get rid of them. /cl --- code/__DEFINES/turfs.dm | 1 + code/datums/components/crafting/recipes.dm | 23 ++++ code/datums/components/trapdoor.dm | 110 ++++++++++++++++-- code/modules/mapping/mapping_helpers.dm | 3 +- .../research/designs/autolathe_designs.dm | 9 ++ code/modules/research/techweb/all_nodes.dm | 1 + icons/turf/overlays.dmi | Bin 6131 -> 6165 bytes 7 files changed, 137 insertions(+), 10 deletions(-) diff --git a/code/__DEFINES/turfs.dm b/code/__DEFINES/turfs.dm index 1af08fc90a9..220a1067982 100644 --- a/code/__DEFINES/turfs.dm +++ b/code/__DEFINES/turfs.dm @@ -4,6 +4,7 @@ #define CHANGETURF_SKIP (1<<3) // A flag for PlaceOnTop to just instance the new turf instead of calling ChangeTurf. Used for uninitialized turfs NOTHING ELSE #define CHANGETURF_INHERIT_AIR (1<<4) // Inherit air from previous turf. Implies CHANGETURF_IGNORE_AIR #define CHANGETURF_RECALC_ADJACENT (1<<5) //Immediately recalc adjacent atmos turfs instead of queuing. +#define CHANGETURF_TRAPDOOR_INDUCED (1<<6) // Caused by a trapdoor, for trapdoor to know that this changeturf was caused by itself #define IS_OPAQUE_TURF(turf) (turf.directional_opacity == ALL_CARDINALS) diff --git a/code/datums/components/crafting/recipes.dm b/code/datums/components/crafting/recipes.dm index b3ad127b364..6aad6f5c4ee 100644 --- a/code/datums/components/crafting/recipes.dm +++ b/code/datums/components/crafting/recipes.dm @@ -759,6 +759,29 @@ time = 20 SECONDS category = CAT_MISC +/datum/crafting_recipe/trapdoor_kit + name = "Trapdoor Construction Kit" + result = /obj/item/trapdoor_kit + reqs = list(/obj/item/stack/sheet/iron = 4, + /obj/item/stack/rods = 4, + /obj/item/stack/cable_coil = 10, + /obj/item/stock_parts/manipulator = 2, + /obj/item/assembly/signaler = 1) + tool_behaviors = list(TOOL_WELDER, TOOL_SCREWDRIVER) + time = 10 SECONDS + category = CAT_MISC + +/datum/crafting_recipe/trapdoor_remote + name = "Trapdoor Remote" + result = /obj/item/trapdoor_remote/preloaded // since its useless without its assembly just require an assembly to craft it + reqs = list( + /obj/item/compact_remote = 1, + /obj/item/stack/cable_coil = 5, + /obj/item/assembly/trapdoor = 1) + tool_behaviors = list(TOOL_SCREWDRIVER, TOOL_WIRECUTTER) + time = 5 SECONDS + category = CAT_MISC + /datum/crafting_recipe/mousetrap name = "Mouse Trap" result = /obj/item/assembly/mousetrap diff --git a/code/datums/components/trapdoor.dm b/code/datums/components/trapdoor.dm index 32a562f3ba6..9b6072d2621 100644 --- a/code/datums/components/trapdoor.dm +++ b/code/datums/components/trapdoor.dm @@ -2,7 +2,7 @@ ///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 +#define TRAPDOOR_LINKING_SEARCH_RANGE 4 /** * ## trapdoor component! @@ -15,12 +15,21 @@ 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 + /// is this trapdoor "conspicuous" (ie. it gets examine text and overlay added) + var/conspicuous + /// overlay that makes trapdoors more obvious + var/static/trapdoor_overlay -/datum/component/trapdoor/Initialize(starts_open, trapdoor_turf_path, assembly) +/datum/component/trapdoor/Initialize(starts_open, trapdoor_turf_path, assembly, conspicuous = TRUE) if(!isopenturf(parent)) return COMPONENT_INCOMPATIBLE + src.conspicuous = conspicuous src.assembly = assembly + + if(!trapdoor_overlay) + trapdoor_overlay = mutable_appearance('icons/turf/overlays.dmi', "border_black", ABOVE_NORMAL_TURF_LAYER) + if(IS_OPEN(parent)) openspace_trapdoor_setup(trapdoor_turf_path, assembly) else @@ -38,14 +47,19 @@ src.trapdoor_turf_path = parent.type if(assembly && assembly.stored_decals.len) reapply_all_decals() + if(conspicuous) + var/turf/parent_turf = parent + parent_turf.add_overlay(trapdoor_overlay) /datum/component/trapdoor/RegisterWithParent() . = ..() RegisterSignal(parent, COMSIG_TURF_CHANGE, .proc/turf_changed_pre) + RegisterSignal(parent, COMSIG_PARENT_EXAMINE, .proc/on_examine) if(!src.assembly) RegisterSignal(SSdcs, COMSIG_GLOB_TRAPDOOR_LINK, .proc/on_link_requested) else RegisterSignal(assembly, COMSIG_ASSEMBLY_PULSED, .proc/toggle_trapdoor) + RegisterSignal(parent, COMSIG_ATOM_TOOL_ACT(TOOL_MULTITOOL), .proc/try_unlink) /datum/component/trapdoor/UnregisterFromParent() . = ..() @@ -53,6 +67,33 @@ if(assembly) UnregisterSignal(assembly, COMSIG_ASSEMBLY_PULSED) UnregisterSignal(parent, COMSIG_TURF_CHANGE) + UnregisterSignal(parent, COMSIG_PARENT_EXAMINE) + UnregisterSignal(parent, COMSIG_ATOM_TOOL_ACT(TOOL_MULTITOOL)) + +/datum/component/trapdoor/proc/try_unlink(turf/source, mob/user, obj/item/tool) + SIGNAL_HANDLER + if(!assembly) + return + if(IS_OPEN(parent)) + source.balloon_alert(user, "can't unlink trapdoor when its open") + return + source.balloon_alert(user, "unlinking trapdoor") + INVOKE_ASYNC(src, .proc/async_try_unlink, source, user, tool) + return + +/datum/component/trapdoor/proc/async_try_unlink(turf/source, mob/user, obj/item/tool) + if(!do_after(user, 5 SECONDS, target=source)) + return + if(IS_OPEN(parent)) + source.balloon_alert(user, "can't unlink trapdoor when its open") + return + assembly.linked = FALSE + assembly.stored_decals = list() + UnregisterSignal(assembly, COMSIG_ASSEMBLY_PULSED) + UnregisterSignal(parent, COMSIG_ATOM_TOOL_ACT(TOOL_MULTITOOL)) + RegisterSignal(SSdcs, COMSIG_GLOB_TRAPDOOR_LINK, .proc/on_link_requested) + assembly = null + source.balloon_alert(user, "trapdoor unlinked") /datum/component/trapdoor/proc/decal_detached(datum/source, description, cleanable, directional, pic) SIGNAL_HANDLER @@ -76,13 +117,14 @@ ///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) SIGNAL_HANDLER - if(get_dist(parent, assembly) > TRAPDOOR_LINKING_SEARCH_RANGE) + if(get_dist(parent, assembly) > TRAPDOOR_LINKING_SEARCH_RANGE || assembly.linked) return . = LINKED_UP src.assembly = assembly assembly.linked = TRUE UnregisterSignal(SSdcs, COMSIG_GLOB_TRAPDOOR_LINK) RegisterSignal(assembly, COMSIG_ASSEMBLY_PULSED, .proc/toggle_trapdoor) + RegisterSignal(parent, COMSIG_ATOM_TOOL_ACT(TOOL_MULTITOOL), .proc/try_unlink) ///signal called by our assembly being pulsed /datum/component/trapdoor/proc/toggle_trapdoor(datum/source) @@ -96,14 +138,18 @@ /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 + if((flags & CHANGETURF_TRAPDOOR_INDUCED) == 0) //not a process of the trapdoor + if(!IS_OPEN(parent) && !ispath(path, /turf/closed) && !ispath(path, /turf/open/openspace)) // allow people to place tiles on plating / change tiles without breaking the trapdoor + post_change_callbacks += CALLBACK(src, /datum/component/trapdoor.proc/carry_over_trapdoor, path, conspicuous, assembly) + return + // otherwise, break trapdoor dying_trapdoor.visible_message(span_warning("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) + post_change_callbacks += CALLBACK(src, /datum/component/trapdoor.proc/carry_over_trapdoor, trapdoor_turf_path, conspicuous, assembly) /** * ## carry_over_trapdoor @@ -111,8 +157,18 @@ * 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) +/datum/component/trapdoor/proc/carry_over_trapdoor(trapdoor_turf_path, conspicuous, assembly, turf/new_turf) + new_turf.AddComponent(/datum/component/trapdoor, FALSE, trapdoor_turf_path, assembly, conspicuous) + +/** + * ## on_examine + * + * examine message for conspicuous trapdoors that makes it obvious + */ +/datum/component/trapdoor/proc/on_examine(datum/source, mob/user, list/examine_text) + SIGNAL_HANDLER + if(conspicuous) + examine_text += "There seems to be a tiny gap around this tile with some wires that you might be able to pulse with a multitool." /** * ## try_opening @@ -127,7 +183,7 @@ RegisterSignal(parent, COMSIG_TURF_DECAL_DETACHED, .proc/decal_detached) playsound(trapdoor_turf, 'sound/machines/trapdoor/trapdoor_open.ogg', 50) trapdoor_turf.visible_message(span_warning("[trapdoor_turf] swings open!")) - trapdoor_turf.ChangeTurf(/turf/open/openspace, flags = CHANGETURF_INHERIT_AIR) + trapdoor_turf.ChangeTurf(/turf/open/openspace, flags = CHANGETURF_INHERIT_AIR | CHANGETURF_TRAPDOOR_INDUCED) /** * ## try_closing @@ -143,7 +199,7 @@ return playsound(trapdoor_turf, 'sound/machines/trapdoor/trapdoor_shut.ogg', 50) trapdoor_turf.visible_message(span_warning("The trapdoor mechanism in [trapdoor_turf] swings shut!")) - trapdoor_turf.ChangeTurf(trapdoor_turf_path, flags = CHANGETURF_INHERIT_AIR) + trapdoor_turf.ChangeTurf(trapdoor_turf_path, flags = CHANGETURF_INHERIT_AIR | CHANGETURF_TRAPDOOR_INDUCED) #undef IS_OPEN @@ -280,3 +336,39 @@ /obj/item/trapdoor_remote/preloaded/Initialize(mapload) . = ..() internals = new(src) + +/// trapdoor parts kit, allows trapdoors to be made by players +/obj/item/trapdoor_kit + name = "trapdoor parts kit" + desc = "A kit containing all the parts needed to build a trapdoor. Can only be used on open space." + icon = 'icons/obj/improvised.dmi' + icon_state = "kitsuitcase" + var/in_use = FALSE + +/obj/item/trapdoor_kit/Initialize(mapload) + . = ..() + AddElement(/datum/element/openspace_item_click_handler) + +/obj/item/trapdoor_kit/handle_openspace_click(turf/target, mob/user, proximity_flag, click_parameters) + afterattack(target, user, proximity_flag, click_parameters) + +/obj/item/trapdoor_kit/afterattack(atom/target, mob/user, proximity_flag) + . = ..() + if(!proximity_flag) + return + var/turf/target_turf = get_turf(target) + if(!isopenspaceturf(target_turf)) + return + in_use = TRUE + balloon_alert(user, "constructing trapdoor") + if(!do_after(user, 5 SECONDS, target = target)) + in_use = FALSE + return + in_use = FALSE + if(!isopenspaceturf(target_turf)) // second check to make sure nothing changed during constructions + return + var/turf/new_turf = target_turf.PlaceOnTop(/turf/open/floor/plating, flags = CHANGETURF_INHERIT_AIR) + new_turf.AddComponent(/datum/component/trapdoor, starts_open = FALSE, conspicuous = TRUE) + balloon_alert(user, "trapdoor constructed") + qdel(src) + return diff --git a/code/modules/mapping/mapping_helpers.dm b/code/modules/mapping/mapping_helpers.dm index 63a0ef170c6..c1deedcdae2 100644 --- a/code/modules/mapping/mapping_helpers.dm +++ b/code/modules/mapping/mapping_helpers.dm @@ -674,6 +674,7 @@ INITIALIZE_IMMEDIATE(/obj/effect/mapping_helpers/no_lava) * ## trapdoor placer! * * This places an unlinked trapdoor in the tile its on (so someone with a remote needs to link it up first) + * Pre-mapped trapdoors (unlike player-made ones) are not conspicuous by default so nothing stands out with them * 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 @@ -685,7 +686,7 @@ INITIALIZE_IMMEDIATE(/obj/effect/mapping_helpers/no_lava) /obj/effect/mapping_helpers/trapdoor_placer/LateInitialize() var/turf/component_target = get_turf(src) - component_target.AddComponent(/datum/component/trapdoor, starts_open = FALSE) + component_target.AddComponent(/datum/component/trapdoor, starts_open = FALSE, conspicuous = FALSE) qdel(src) /obj/effect/mapping_helpers/ztrait_injector diff --git a/code/modules/research/designs/autolathe_designs.dm b/code/modules/research/designs/autolathe_designs.dm index 9305657ac53..11fb02db590 100644 --- a/code/modules/research/designs/autolathe_designs.dm +++ b/code/modules/research/designs/autolathe_designs.dm @@ -223,6 +223,15 @@ category = list(RND_CATEGORY_INITIAL, RND_CATEGORY_ELECTRONICS) departmental_flags = DEPARTMENT_BITFLAG_ENGINEERING +/datum/design/trapdoor_electronics + name = "Trapdoor Controller Electronics" + id = "trapdoor_electronics" + build_type = AUTOLATHE | PROTOLATHE | AWAY_LATHE + materials = list(/datum/material/iron = 50, /datum/material/glass = 50) + build_path = /obj/item/assembly/trapdoor + category = list("initial", "Electronics") + departmental_flags = DEPARTMENT_BITFLAG_ENGINEERING + /datum/design/camera name = "Camera" id = "camera" diff --git a/code/modules/research/techweb/all_nodes.dm b/code/modules/research/techweb/all_nodes.dm index 5ce3b9392ef..0b2691dbb1a 100644 --- a/code/modules/research/techweb/all_nodes.dm +++ b/code/modules/research/techweb/all_nodes.dm @@ -62,6 +62,7 @@ "titaniumglass", "toner", "toner_large", + "trapdoor_electronics", "turbine_part_compressor", "turbine_part_rotor", "turbine_part_stator", diff --git a/icons/turf/overlays.dmi b/icons/turf/overlays.dmi index c1867fb12f0e51342290655213d81eb5635f6b4a..625537fc173e5ef86b900ea489a608625e2d3045 100644 GIT binary patch delta 5992 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 delta 5959 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