From 476c526c69b79a398944655241a76e621bc44158 Mon Sep 17 00:00:00 2001 From: CRUNCH <143041327+Fordoxia@users.noreply.github.com> Date: Tue, 8 Apr 2025 15:20:01 +0100 Subject: [PATCH] Atomized APC Construction Update (#28817) * initial commit * Update apc_construction.dm * Apply suggestions from code review Co-authored-by: 1080pCat <96908085+1080pCat@users.noreply.github.com> Signed-off-by: CRUNCH <143041327+Fordoxia@users.noreply.github.com> * Split * Update apc.dm * Update apc.dm * Update apc.dm * Update apc.dm * Update apc.dm * Apply suggestions from code review Co-authored-by: warriorstar-orion Signed-off-by: CRUNCH <143041327+Fordoxia@users.noreply.github.com> * Defines * Now with 100 percent more tests * this was not supposed to be here Whoops! * Update test_apc_construction.dm * Update test_apc_construction.dm * Update test_apc_construction.dm * Update test_apc_construction.dm * Update test_apc_construction.dm * Update test_apc_construction.dm * Update test_apc_construction.dm * Update test_apc_construction.dm * Update test_apc_construction.dm * Update test_apc_construction.dm * Update test_apc_construction.dm * Update test_apc_construction.dm * Update apc.dm * Update test_apc_construction.dm --------- Signed-off-by: CRUNCH <143041327+Fordoxia@users.noreply.github.com> Co-authored-by: 1080pCat <96908085+1080pCat@users.noreply.github.com> Co-authored-by: warriorstar-orion --- .../items/mountable_frames/apc_frame.dm | 3 + code/modules/power/apc/apc.dm | 218 +++++++++++------- code/modules/power/apc/apc_construction.dm | 151 ++++++------ code/tests/game_tests.dm | 1 + code/tests/test_apc_construction.dm | 57 +++++ 5 files changed, 275 insertions(+), 155 deletions(-) create mode 100644 code/tests/test_apc_construction.dm diff --git a/code/game/objects/items/mountable_frames/apc_frame.dm b/code/game/objects/items/mountable_frames/apc_frame.dm index d4dff1e66e4..2cdc4fb65e2 100644 --- a/code/game/objects/items/mountable_frames/apc_frame.dm +++ b/code/game/objects/items/mountable_frames/apc_frame.dm @@ -15,13 +15,16 @@ if(A.get_apc()) to_chat(user, "This area already has an APC!") return //only one APC per area + if(!A.requires_power) to_chat(user, "You cannot place [src] in this area!") return //can't place apcs in areas with no power requirement + for(var/obj/machinery/power/terminal/E in T) if(E.master) to_chat(user, "There is another network terminal here!") return + else var/obj/item/stack/cable_coil/C = new /obj/item/stack/cable_coil(T) C.amount = 10 diff --git a/code/modules/power/apc/apc.dm b/code/modules/power/apc/apc.dm index de1cd920af3..a72b7d2b8a0 100644 --- a/code/modules/power/apc/apc.dm +++ b/code/modules/power/apc/apc.dm @@ -19,6 +19,15 @@ damage_deflection = 10 move_resist = INFINITY + /*** APC construction vars***/ + // These exist here and not as defines in `apc_defines.dm` so they can be modified for `test_apc_construction.dm`. I hate that these timers exist at all. + var/apc_cover_replacement_time = 2 SECONDS + var/apc_frame_replacement_time = 5 SECONDS + var/apc_frame_welding_time = 5 SECONDS + var/apc_electronics_installation_time = 1 SECONDS + var/apc_electronics_crowbar_time = 5 SECONDS + var/apc_terminal_wiring_time = 2 SECONDS + // set so that APCs aren't found as powernet nodes //Hackish, Horrible, was like this before I changed it :( powernet = 0 @@ -74,7 +83,7 @@ /// The current setting for the environment channel var/environment_channel = APC_CHANNEL_SETTING_AUTO_ON /// Is the APC cover locked? i.e cannot be opened? - var/coverlocked = TRUE + var/cover_locked = TRUE /// Is the APC User Interface locked (prevents interaction)? Will not prevent silicons or admin observers from interacting var/locked = TRUE /// If TRUE, the APC will automatically draw power from connect terminal, if FALSE it will not charge @@ -242,127 +251,156 @@ attack_hand(user) return ITEM_INTERACT_COMPLETE - else if(istype(used, /obj/item/stock_parts/cell) && opened) // trying to put a cell inside + // Adding power cell. + if(istype(used, /obj/item/stock_parts/cell) && opened) if(cell) - to_chat(user, "There is a power cell already installed!") + to_chat(user, "[src] already has a power cell!") return ITEM_INTERACT_COMPLETE - else - if(stat & MAINT) - to_chat(user, "There is no connector for your power cell!") - return ITEM_INTERACT_COMPLETE - if(!user.drop_item()) - return ITEM_INTERACT_COMPLETE - used.forceMove(src) - cell = used - for(var/mob/living/simple_animal/demon/pulse_demon/demon in cell) - demon.forceMove(src) - demon.current_power = src - if(!being_hijacked) // first come first serve - demon.try_hijack_apc(src) - if(being_hijacked) - cell.rigged = FALSE // don't blow the demon up + if(stat & MAINT) + to_chat(user, "[src] has no electronics inside!") + return ITEM_INTERACT_COMPLETE - user.visible_message(\ - "[user.name] has inserted the power cell to [name]!",\ - "You insert the power cell.") - chargecount = 0 - update_icon() + if(!user.drop_item()) + return ITEM_INTERACT_COMPLETE + used.forceMove(src) + cell = used + user.visible_message( + "[user] inserts [used] into [src].", + "You insert [used] into [src]." + ) + for(var/mob/living/simple_animal/demon/pulse_demon/demon in cell) + demon.forceMove(src) + demon.current_power = src + if(!being_hijacked) // First come, first serve! + demon.try_hijack_apc(src) + if(being_hijacked) + cell.rigged = FALSE // Do not explode the demon. + chargecount = 0 + update_icon() return ITEM_INTERACT_COMPLETE - else if(used.GetID()) // trying to unlock the interface with an ID card + + // Swiping ID card. + if(used.GetID()) togglelock(user) return ITEM_INTERACT_COMPLETE - else if(istype(used, /obj/item/stack/cable_coil) && opened) + // Adding cables. + if(istype(used, /obj/item/stack/cable_coil) && opened) var/turf/host_turf = get_turf(src) if(!host_turf) throw EXCEPTION("attackby on APC when it's not on a turf") return ITEM_INTERACT_COMPLETE + if(host_turf.intact) - to_chat(user, "You must remove the floor plating in front of the APC first!") + to_chat(user, "You must expose the floor plating in front of [src] first!") return ITEM_INTERACT_COMPLETE - else if(terminal) // it already have terminal - to_chat(user, "This APC is already wired!") + + if(terminal) + to_chat(user, "[src] is already wired!") return ITEM_INTERACT_COMPLETE - else if(!has_electronics()) - to_chat(user, "There is nothing to wire!") + + if(!has_electronics()) + to_chat(user, "[src] has no electronics inside to wire!") return ITEM_INTERACT_COMPLETE var/obj/item/stack/cable_coil/C = used if(C.get_amount() < 10) - to_chat(user, "You need ten lengths of cable for APC!") + to_chat(user, "You need ten lengths of cable to wire [src]!") return ITEM_INTERACT_COMPLETE - user.visible_message("[user.name] adds cables to the APC frame.", \ - "You start adding cables to the APC frame...") + + user.visible_message( + "[user] starts adding cables to [src]...", + "You start adding cables to [src]..." + ) playsound(loc, 'sound/items/deconstruct.ogg', 50, TRUE) - if(do_after(user, 20, target = src)) + if(do_after(user, apc_terminal_wiring_time, target = src)) if(C.get_amount() < 10 || !C) return ITEM_INTERACT_COMPLETE + if(C.get_amount() >= 10 && !terminal && opened && has_electronics()) var/turf/T = get_turf(src) var/obj/structure/cable/N = T.get_cable_node() if(prob(50) && electrocute_mob(usr, N, N, 1, TRUE)) do_sparks(5, TRUE, src) return ITEM_INTERACT_COMPLETE + C.use(10) - to_chat(user, "You add cables to the APC frame.") + to_chat(user, "You add cables to [src].") make_terminal() terminal.connect_to_network() - return ITEM_INTERACT_COMPLETE - else if(istype(used, /obj/item/apc_electronics) && opened) - if(has_electronics()) // there are already electronicks inside - to_chat(user, "You cannot put the board inside, there already is one!") - return ITEM_INTERACT_COMPLETE - else if(stat & BROKEN) - to_chat(user, "You cannot put the board inside, the frame is damaged!") - return ITEM_INTERACT_COMPLETE - - user.visible_message("[user.name] inserts [used] into [src].", \ - "You start to insert [used] into the frame...") - playsound(loc, 'sound/items/deconstruct.ogg', 50, TRUE) - if(do_after(user, 10, target = src)) - if(!has_electronics()) - electronics_state = APC_ELECTRONICS_INSTALLED - locked = FALSE - to_chat(user, "You place [used] inside the frame.") - stat &= ~MAINT - update_icon() - qdel(used) - - return ITEM_INTERACT_COMPLETE - - else if(istype(used, /obj/item/mounted/frame/apc_frame) && opened) - if(!(stat & BROKEN || opened == APC_COVER_OFF || obj_integrity < max_integrity)) // There is nothing to repair - to_chat(user, "You found no reason for repairing this APC.") - return ITEM_INTERACT_COMPLETE - if(!(stat & BROKEN) && opened == APC_COVER_OFF) // Cover is the only thing broken, we do not need to remove elctronicks to replace cover - user.visible_message("[user.name] replaces missing APC's cover.",\ - "You begin to replace APC's cover...") - if(do_after(user, 20, target = src)) // replacing cover is quicker than replacing whole frame - to_chat(user, "You replace missing APC's cover.") - qdel(used) - opened = APC_OPENED - update_icon() - return ITEM_INTERACT_COMPLETE + // Adding APC electronics. + if(istype(used, /obj/item/apc_electronics) && opened) if(has_electronics()) - to_chat(user, "You cannot repair this APC until you remove the electronics still inside!") + to_chat(user, "[src] already contains APC electronics!") return ITEM_INTERACT_COMPLETE - user.visible_message("[user.name] replaces the damaged APC frame with a new one.",\ - "You begin to replace the damaged APC frame...") - if(do_after(user, 50, target = src)) - to_chat(user, "You replace the damaged APC frame with a new one.") - qdel(used) - stat &= ~BROKEN - obj_integrity = max_integrity - if(opened == APC_COVER_OFF) - opened = APC_OPENED + + if(stat & BROKEN) + to_chat(user, "[src] is damaged! You must repair the frame before you can install [used]!") + return ITEM_INTERACT_COMPLETE + + if(!has_electronics()) + to_chat(user, "You start to add [used] to [src].") + if(!do_after(user, apc_electronics_installation_time, target = src)) + return ITEM_INTERACT_COMPLETE + + user.visible_message( + "[user] installs [used] into [src].", + "You install [used] into [src]." + ) + playsound(loc, 'sound/items/deconstruct.ogg', 50, TRUE) + electronics_state = APC_ELECTRONICS_INSTALLED + locked = FALSE + stat &= ~MAINT update_icon() + qdel(used) return ITEM_INTERACT_COMPLETE - else - return ..() + + // APC frame repair. + if(istype(used, /obj/item/mounted/frame/apc_frame) && opened) + if(!(stat & BROKEN || opened == APC_COVER_OFF || obj_integrity < max_integrity)) + to_chat(user, "[src] has no damage to fix!") + return ITEM_INTERACT_COMPLETE + + // Only cover is broken, no need to remove any components. + if(!(stat & BROKEN) && opened == APC_COVER_OFF) + to_chat(user, "You begin to replace the missing cover of [src].") + if(!do_after(user, apc_cover_replacement_time, target = src)) + return ITEM_INTERACT_COMPLETE + + user.visible_message( + "[user] replaces the missing cover of [src].", + "You replace the missing cover of [src]." + ) + qdel(used) + opened = APC_OPENED + update_icon() + return ITEM_INTERACT_COMPLETE + + if(has_electronics()) + to_chat(user, "You cannot repair [src] until you remove the electronics!") + return ITEM_INTERACT_COMPLETE + + to_chat(user, "You begin to replace the damaged APC frame...") + if(!do_after(user, apc_frame_replacement_time, target = src)) + return ITEM_INTERACT_COMPLETE + + user.visible_message( + "[user] replaces the damaged frame of [src].", + "You replace the damaged frame of [src]." + ) + qdel(used) + stat &= ~BROKEN + obj_integrity = max_integrity + if(opened == APC_COVER_OFF) + opened = APC_OPENED + update_icon() + return ITEM_INTERACT_COMPLETE + + return ..() /obj/machinery/power/apc/AltClick(mob/user) if(Adjacent(user)) @@ -381,11 +419,14 @@ /obj/machinery/power/apc/attack_hand(mob/user) if(!user) return - add_fingerprint(user) - if(usr == user && opened && !issilicon(user)) + add_fingerprint(user) + if(opened && !issilicon(user)) if(cell) - user.visible_message("[user.name] removes [cell] from [src]!", "You remove [cell].") + user.visible_message( + "[user] removes [cell] from [src].", + "You remove [cell]." + ) user.put_in_hands(cell) cell.add_fingerprint(user) cell.update_icon(UPDATE_OVERLAYS) @@ -393,6 +434,7 @@ charging = APC_NOT_CHARGING update_icon() return + if(stat & (BROKEN|MAINT)) return @@ -431,7 +473,7 @@ data["chargeMode"] = chargemode data["chargingStatus"] = charging data["totalLoad"] = round(last_used_equipment + last_used_lighting + last_used_environment) - data["coverLocked"] = coverlocked + data["coverLocked"] = cover_locked data["siliconUser"] = issilicon(user) data["siliconLock"] = locked data["malfStatus"] = get_malf_status(user) @@ -558,7 +600,7 @@ to_chat(user, "Access Denied!") return FALSE if("cover") - coverlocked = !coverlocked + cover_locked = !cover_locked if("breaker") toggle_breaker(user) if("toggle_nightshift") diff --git a/code/modules/power/apc/apc_construction.dm b/code/modules/power/apc/apc_construction.dm index 6d44abca0fd..f091e9d7661 100644 --- a/code/modules/power/apc/apc_construction.dm +++ b/code/modules/power/apc/apc_construction.dm @@ -9,9 +9,9 @@ set_broken() if(opened != APC_COVER_OFF) opened = APC_COVER_OFF - coverlocked = FALSE + cover_locked = FALSE visible_message( - "The APC cover falls off!", + "The cover falls off [src]!", "You hear a small flat object falling to the floor!" ) update_icon() @@ -21,110 +21,127 @@ if(!I.tool_start_check(src, user, 0)) return - if(opened) // a) on open apc - if(electronics_state == APC_ELECTRONICS_INSTALLED) - if(terminal) - to_chat(user, "Disconnect the wires first!") + // 1. Opened APC + if(opened) + if(cell) + if(opened == APC_OPENED) // Do not magically create a new cover if it broke off. + opened = APC_CLOSED + cover_locked = TRUE //closing cover relocks it + update_icon() + user.visible_message( + "[user] closes the cover of [src].", + "You close the cover of [src].") return - to_chat(user, "You start trying to remove the APC electronics..." ) - if(I.use_tool(src, user, 50, volume = I.tool_volume)) - if(has_electronics()) - electronics_state = APC_ELECTRONICS_NONE - if(stat & BROKEN) - user.visible_message( - "[user.name] rips out the broken the APC electronics inside [name]!", - "You break the charred APC electronics and remove the remains.", - "You hear metallic levering and a crack.") - stat |= MAINT - update_icon() - return + else + to_chat(user, "Remove the cell first!") + return - //SSticker.mode:apcs-- //XSI said no and I agreed. -rastaf0 - if(emagged) // We emag board, not APC's frame - emagged = FALSE - user.visible_message( - "[user.name] has discarded the shorted APC electronics from [name]!", - "You discarded the shorted board.", - "You hear metallic levering." - ) - stat |= MAINT - update_icon() - return + if(electronics_state == APC_ELECTRONICS_NONE) + to_chat(user, "There's nothing inside!") + return - if(malfhack) // AI hacks board, not APC's frame - user.visible_message(\ - "[user.name] has discarded the strangely programmed APC electronics from [name]!", - "You discarded the strangely programmed board.", - "You hear metallic levering." - ) - malfai = null - malfhack = FALSE - stat |= MAINT - update_icon() - return + if(terminal) + to_chat(user, "Disconnect the wires first!") + return - user.visible_message(\ - "[user.name] has removed the APC electronics from [name]!", - "You remove the APC electronics.", - "You hear metallic levering." - ) - new /obj/item/apc_electronics(loc) + if(I.use_tool(src, user, apc_electronics_crowbar_time, volume = I.tool_volume)) + if(has_electronics()) + electronics_state = APC_ELECTRONICS_NONE + if(stat & BROKEN) + user.visible_message( + "[user] rips out the broken the APC electronics inside [src]!", + "You break the charred APC electronics and remove the remains.", + "You hear metallic levering and a crack.") stat |= MAINT update_icon() return - if(opened != APC_COVER_OFF) //cover isn't removed - opened = APC_CLOSED - coverlocked = TRUE //closing cover relocks it - update_icon() - return + if(emagged) // We emag board, not APC's frame + emagged = FALSE + user.visible_message( + "[user] has discarded the shorted APC electronics from [src]!", + "You discarded the shorted board.", + "You hear metallic levering." + ) + stat |= MAINT + update_icon() + return - if(!(stat & BROKEN)) // b) on closed and not broken APC - if(coverlocked && !(stat & MAINT)) // locked... - to_chat(user, "The cover is locked and cannot be opened!") - return + if(malfhack) // AI hacks board, not APC's frame + user.visible_message(\ + "[name] has discarded the strangely programmed APC electronics from [src]!", + "You discarded the strangely programmed board.", + "You hear metallic levering." + ) + malfai = null + malfhack = FALSE + stat |= MAINT + update_icon() + return + user.visible_message( + "[user] has removed the APC electronics from [src]!", + "You remove the APC electronics.", + "You hear metallic levering." + ) + new /obj/item/apc_electronics(loc) + stat |= MAINT + update_icon() + return + + // 2. Closed APC + if(!(stat & BROKEN)) if(panel_open) // wires are exposed - to_chat(user, "Exposed wires prevents you from opening it!") + to_chat(user, "Exposed wiring prevents you from opening [src]!") return + if(cover_locked && !(stat & MAINT)) // locked... + to_chat(user, "The cover of [src] is locked!") + return + + to_chat(user, "You open the cover of [src].") opened = APC_OPENED update_icon() /obj/machinery/power/apc/screwdriver_act(mob/living/user, obj/item/I) . = TRUE - if(!I.use_tool(src, user, 0, volume = I.tool_volume)) - return if(opened) - to_chat(user, "Close the APC first!") //Less hints more mystery! + to_chat(user, "Close the APC first!") return if(emagged) to_chat(user, "The interface is broken!") return + if(!I.use_tool(src, user, FALSE, volume = I.tool_volume)) + return + panel_open = !panel_open to_chat(user, "The wires have been [panel_open ? "exposed" : "unexposed"]") update_icon() /obj/machinery/power/apc/wirecutter_act(mob/living/user, obj/item/I) . = TRUE - if(!I.use_tool(src, user, 0, volume = I.tool_volume)) + + if(terminal && opened) + if(!I.use_tool(src, user, FALSE, volume = I.tool_volume)) + return + terminal.dismantle(user, I) return if(panel_open && !opened) + if(!I.use_tool(src, user, FALSE, volume = I.tool_volume)) + return wires.Interact(user) - else if(terminal && opened) - terminal.dismantle(user, I) /obj/machinery/power/apc/multitool_act(mob/living/user, obj/item/I) . = TRUE - if(!I.use_tool(src, user, 0, volume = I.tool_volume)) - return if(panel_open && !opened) + if(!I.use_tool(src, user, FALSE, volume = I.tool_volume)) + return wires.Interact(user) /obj/machinery/power/apc/welder_act(mob/user, obj/item/I) @@ -136,18 +153,18 @@ return WELDER_ATTEMPT_SLICING_MESSAGE - if(I.use_tool(src, user, 50, amount = 3, volume = I.tool_volume)) + if(I.use_tool(src, user, apc_frame_welding_time, amount = 3, volume = I.tool_volume)) if((stat & BROKEN) || opened == APC_COVER_OFF) new /obj/item/stack/sheet/metal(loc) user.visible_message(\ - "[user.name] has cut [src] apart with [I].", + "[user] has cut [src] apart with [I].", "You disassembled the broken APC frame.", "You hear welding." ) else new /obj/item/mounted/frame/apc_frame(loc) user.visible_message(\ - "[user.name] has cut [src] from the wall with [I].", + "[user] has cut [src] from the wall with [I].", "You cut the APC frame from the wall.", "You hear welding." ) diff --git a/code/tests/game_tests.dm b/code/tests/game_tests.dm index 6058b4f2057..e7bd5315b61 100644 --- a/code/tests/game_tests.dm +++ b/code/tests/game_tests.dm @@ -22,6 +22,7 @@ #include "jobs\test_job_globals.dm" #include "test_aicard_icons.dm" #include "test_announcements.dm" +#include "test_apc_construction.dm" #include "test_components.dm" #include "test_config_sanity.dm" #include "test_crafting_lists.dm" diff --git a/code/tests/test_apc_construction.dm b/code/tests/test_apc_construction.dm new file mode 100644 index 00000000000..1fbb366a379 --- /dev/null +++ b/code/tests/test_apc_construction.dm @@ -0,0 +1,57 @@ +/datum/game_test/test_apc_construction/Run() + var/datum/test_puppeteer/player = new(src) + var/turf/wall = player.change_turf_nearby(/turf/simulated/wall, NORTH) + // Allow APC construction. + var/area/test_area = get_area(player.puppet) + test_area.requires_power = TRUE + // First we build the APC + var/obj/crowbar = player.spawn_obj_in_hand(/obj/item/crowbar) + var/player_floor = player.puppet.loc + player.click_on(player_floor) + player.put_away(crowbar) + player.spawn_obj_in_hand(/obj/item/mounted/frame/apc_frame) + player.click_on(wall) + var/obj/machinery/power/apc/apc_frame = player.find_nearby(/obj/machinery/power/apc) + // Make this not take 5 billion years. + apc_frame.apc_cover_replacement_time = 0 + apc_frame.apc_frame_replacement_time = 0 + apc_frame.apc_frame_welding_time = 0 + apc_frame.apc_electronics_installation_time = 0 + apc_frame.apc_electronics_crowbar_time = 0 + apc_frame.apc_terminal_wiring_time = 0 + + var/obj/the_electronics = player.spawn_obj_in_hand(/obj/item/apc_electronics/) + player.click_on(apc_frame) + TEST_ASSERT_LAST_CHATLOG(player, "You install [the_electronics] into [apc_frame].") + player.spawn_obj_in_hand(/obj/item/stack/cable_coil/ten) + player.click_on(apc_frame) + TEST_ASSERT_LAST_CHATLOG(player, "You add cables to [apc_frame].") + var/obj/the_cell = player.spawn_obj_in_hand(/obj/item/stock_parts/cell) + player.click_on(apc_frame) + TEST_ASSERT_LAST_CHATLOG(player, "You insert [the_cell] into [apc_frame]") + player.retrieve(crowbar) + player.click_on(apc_frame) + TEST_ASSERT_LAST_CHATLOG(player, "You close the cover of [apc_frame].") + + // Now we dismantle it again. + apc_frame.cover_locked = FALSE + player.click_on(apc_frame) + TEST_ASSERT_LAST_CHATLOG(player, "You open the cover of [apc_frame].") + player.put_away(crowbar) + player.click_on(apc_frame) + TEST_ASSERT_LAST_CHATLOG(player, "You remove [the_cell].") + player.put_away(the_cell) + var/obj/wirecutters = player.spawn_obj_in_hand(/obj/item/wirecutters) + player.click_on(apc_frame) + sleep(5 SECONDS) // This is not an APC define. + TEST_ASSERT_LAST_CHATLOG(player, "You cut the cables and dismantle the power terminal.") + player.put_away(wirecutters) + player.retrieve(crowbar) + player.click_on(apc_frame) + TEST_ASSERT_LAST_CHATLOG(player, "You remove the APC electronics.") + player.put_away(crowbar) + var/obj/welder = player.spawn_obj_in_hand(/obj/item/weldingtool) + player.click_on(welder) + player.click_on(apc_frame) + TEST_ASSERT_LAST_CHATLOG(player, "You cut the APC frame from the wall.") + test_area.requires_power = FALSE