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