Wires Panel and Signaler Expansion (#22846)

Fleshes out wire panel skill behavior and signaler management.

Electrical Engineering 4 was very OP for revealing all wire labels of
all machines, and it was super fun while it lasted.

Wire panels now have associated skills with their use, and increased
skill ranks will increase the proportion of wires (chosen at random)
that are easily recognizable on sight for certain types of wiring
panels.
**Professional:** 75% revealed
**Trained:** 50% revealed
**Familiar:** 30% revealed
_(note: having both Electrical and Mechanical engineering at least
Trained gives you that baseline 30%, reflecting how your knowledge can
probably translate to decent guesswork.)_

The following wire panels are affected:
**Electrical Engineering:**
- APCs
- Cameras
- IFF Beacons
- Radios
- Smartfridges
- SMES Units

**Mechanical Engineering**
- Airlocks
- Disposal Units
- Fabricators
- Vending Machines

**Atmospherics Systems**
- Air Alarms

**Reactor Systems**
- Particle Accelerator Control Boxes (lol)

**Robotics**
- Hardsuits
- Suit Storage Units (sharing the love is all)
- Robots
- Tag Scanners

**Xenobiology**
- Stasis Cages

Also, I made it possible to blow out all the lights in an APC's area by
snipping the power regulator cable and then pulsing the lights wire.
Great for signalers.

Speaking of signalers, new app deployed to all Engineering, Research,
and Robotics personal modular computers:
<img width="816" height="722" alt="Screenshot 2026-07-14 144049"
src="https://github.com/user-attachments/assets/3d588712-ebfd-47ac-b699-7cdac2c04907"
/>

Self-explanatory. Please only use corporate-provided software for
behavior within your contract regulations.
This commit is contained in:
Batrachophreno
2026-07-26 18:05:15 +00:00
committed by GitHub
parent 80ace1682c
commit c92d909534
42 changed files with 858 additions and 130 deletions
+2 -1
View File
@@ -3,6 +3,7 @@
/datum/wires/airlock
proper_name = "Airlock"
holder_type = /obj/structure/machinery/door/airlock
associated_skill = MECHANICAL_ENGINEERING_SKILL_COMPONENT
/datum/wires/airlock/New(atom/holder)
wires = list(
@@ -200,6 +201,6 @@
continue
dat += "<font color='[color]'>[capitalize(color)]</font>: [get_wire(color)]<br>"
var/datum/browser/wire_win = new(user, "airlockwires", "Airlock Wires", 450, 500)
var/datum/browser/wire_win = new(user, "airlockwires", "Airlock Wires", 450, 720)
wire_win.set_content(dat)
wire_win.open()
+4 -3
View File
@@ -1,8 +1,9 @@
/datum/wires/alarm
proper_name = "Air Alarm"
holder_type = /obj/structure/machinery/alarm
associated_skill = ATMOSPHERICS_SYSTEMS_SKILL_COMPONENT
/datum/wires/airalarm/New(atom/holder)
/datum/wires/alarm/New(atom/holder)
wires = list(
WIRE_POWER,
WIRE_IDSCAN, WIRE_AI,
@@ -12,7 +13,7 @@
add_duds(3)
..()
/datum/wires/airalarm/interactable(mob/user)
/datum/wires/alarm/interactable(mob/user)
if(!..())
return FALSE
var/obj/structure/machinery/alarm/A = holder
@@ -23,7 +24,7 @@
var/obj/structure/machinery/alarm/A = holder
. = ..()
. += A.locked ? "The air alarm is locked." : "The air alarm is unlocked."
. += (A.shorted || (A.stat & (NOPOWER|BROKEN))) ? "The Air Alarm is offline." : "The Air Alarm is working properly!"
. += (A.shorted || (A.stat & (NOPOWER|BROKEN))) ? "The air alarm is offline." : "The air alarm is working properly!"
. += A.aidisabled ? "The 'AI control allowed' light is off." : "The 'AI control allowed' light is on."
. += A.rcon_setting ? "The remote control link is enabled." : "The remote control link is disabled."
+12 -9
View File
@@ -5,7 +5,8 @@
/datum/wires/apc/New(atom/holder)
wires = list(
WIRE_POWER1, WIRE_POWER2,
WIRE_IDSCAN, WIRE_AI
WIRE_IDSCAN, WIRE_AI,
WIRE_POWER_LIMIT, WIRE_LIGHT
)
add_duds(6)
..()
@@ -14,8 +15,9 @@
var/obj/structure/machinery/power/apc/A = holder
. = ..()
. += A.locked ? "The APC is locked." : "The APC is unlocked."
. += A.shorted ? "The APCs power has been shorted." : "The APC is working properly!"
. += A.shorted ? "The APCs power has been shorted!" : "The APC is working properly."
. += A.aidisabled ? "The 'AI control allowed' light is off." : "The 'AI control allowed' light is on."
. += A.light_explosion_safety ? "The low-voltage regulator telltale is on." : "The low-voltage regulator telltale is off."
/datum/wires/apc/interactable(mob/user)
if(!..())
@@ -24,23 +26,21 @@
return A?.panel_open
/datum/wires/apc/on_pulse(wire)
var/obj/structure/machinery/power/apc/A = holder
switch(wire)
if(WIRE_IDSCAN)
set_locked(A, FALSE)
addtimer(CALLBACK(src, PROC_REF(set_locked), A, TRUE), 30 SECONDS)
if (WIRE_POWER1, WIRE_POWER2)
if(WIRE_POWER1, WIRE_POWER2)
set_short_out(A, TRUE)
addtimer(CALLBACK(src, PROC_REF(set_short_out), A, FALSE), 120 SECONDS)
if (WIRE_AI)
if(WIRE_AI)
set_ai_control(A, TRUE)
addtimer(CALLBACK(src, PROC_REF(set_ai_control), A, FALSE), 1 SECONDS)
if(WIRE_LIGHT)
if(!A.light_explosion_safety)
A.overload_lighting()
/datum/wires/apc/proc/set_locked(var/obj/structure/machinery/power/apc/A, var/setting)
if(A)
@@ -74,3 +74,6 @@
if(WIRE_AI)
A.aidisabled = !mend
if(WIRE_POWER_LIMIT)
A.light_explosion_safety = mend
+1 -1
View File
@@ -1,7 +1,7 @@
/datum/wires/disposal
proper_name = "Disposals"
random = TRUE
holder_type = /obj/structure/machinery/disposal
associated_skill = MECHANICAL_ENGINEERING_SKILL_COMPONENT
/datum/wires/disposal/New(atom/holder)
wires = list(
+4
View File
@@ -1,3 +1,7 @@
/datum/wires/explosive
// If we ever get an explosives or similar skill...
associated_skill = null
/datum/wires/explosive/New()
wires = list(
WIRE_EXPLODE
+1
View File
@@ -1,6 +1,7 @@
/datum/wires/fabricator
proper_name = "Fabricator"
holder_type = /obj/structure/machinery/fabricator
associated_skill = MECHANICAL_ENGINEERING_SKILL_COMPONENT
/datum/wires/fabricator/New(atom/holder)
wires = list(
+82
View File
@@ -0,0 +1,82 @@
/*
* Rig security can be snipped to disable ID access checks on rig.
* Rig AI override can be pulsed to toggle whether or not the AI can take control of the suit.
* System control can be pulsed to toggle some malfunctions.
* Interface lock can be pulsed to toggle whether or not the interface can be accessed.
*/
#define RIG_SECURITY BITFLAG(1)
#define RIG_AI_OVERRIDE BITFLAG(2)
#define RIG_SYSTEM_CONTROL BITFLAG(3)
#define RIG_INTERFACE_LOCK BITFLAG(4)
#define RIG_INTERFACE_SHOCK BITFLAG(5)
/datum/wires/rig
holder_type = /obj/item/rig
associated_skill = ROBOTICS_SKILL_COMPONENT
/datum/wires/rig/New(atom/holder)
wires = list(
RIG_SECURITY,
RIG_AI_OVERRIDE,
RIG_SYSTEM_CONTROL,
RIG_INTERFACE_LOCK,
RIG_INTERFACE_SHOCK
)
add_duds(3)
. = ..()
/datum/wires/rig/on_cut(wire, mend, source)
var/obj/item/rig/rig = holder
switch(wire)
if(RIG_SECURITY)
if(mend)
rig.req_access = initial(rig.req_access)
rig.req_one_access = initial(rig.req_one_access)
if(RIG_INTERFACE_SHOCK)
rig.electrified = mend ? 0 : -1
rig.shock(usr,100)
if(RIG_SYSTEM_CONTROL)
if(mend)
rig.malfunctioning = 0
rig.malfunction_delay = 0
else
rig.malfunctioning += 10
rig.malfunction_delay = 10000000000
/datum/wires/rig/on_pulse(wire)
var/obj/item/rig/rig = holder
switch(wire)
if(RIG_SECURITY)
rig.security_check_enabled = !rig.security_check_enabled
rig.visible_message("\The [rig] twitches as several suit locks [rig.security_check_enabled?"close":"open"].")
if(RIG_AI_OVERRIDE)
rig.ai_override_enabled = !rig.ai_override_enabled
rig.visible_message("A small red light on [rig] [rig.ai_override_enabled?"goes dead":"flickers on"].")
if(RIG_SYSTEM_CONTROL)
rig.malfunctioning += 10
if(rig.malfunction_delay <= 0)
rig.malfunction_delay = 20
rig.shock(usr,100)
if(RIG_INTERFACE_LOCK)
rig.interface_locked = !rig.interface_locked
rig.visible_message("\The [rig] clicks audibly as the software interface [rig.interface_locked?"darkens":"brightens"].")
if(RIG_INTERFACE_SHOCK)
if(rig.electrified != -1)
rig.electrified = 30
rig.shock(usr,100)
/datum/wires/rig/interactable(var/mob/living/L)
if(!..())
return FALSE
var/obj/item/rig/rig = holder
if(rig.open)
return TRUE
return FALSE
#undef RIG_SECURITY
#undef RIG_AI_OVERRIDE
#undef RIG_SYSTEM_CONTROL
#undef RIG_INTERFACE_LOCK
#undef RIG_INTERFACE_SHOCK
+1 -1
View File
@@ -1,7 +1,7 @@
/datum/wires/tag_scanner
proper_name = "IPC Tag Scanner"
random = TRUE
holder_type = /obj/item/ipc_tag_scanner
associated_skill = ROBOTICS_SKILL_COMPONENT
/datum/wires/tag_scanner/New()
wires = list(
+4
View File
@@ -1,3 +1,7 @@
/datum/wires/landmine
// If we ever get an explosives or similar skill...
associated_skill = null
/datum/wires/landmine/New()
wires = list(
WIRE_EXPLODE
+2
View File
@@ -2,6 +2,8 @@
proper_name = "Nuclear Fission Device"
holder_type = /obj/structure/machinery/nuclearbomb
random = 1
// If we ever get an explosives or similar skill... well, probably not even then.
associated_skill = null
/datum/wires/nuclearbomb/New()
wires = list(
@@ -1,6 +1,7 @@
/datum/wires/particle_acc/control_box
proper_name = "Particle Accelerator"
holder_type = /obj/structure/machinery/particle_accelerator/control_box
associated_skill = REACTOR_SYSTEMS_SKILL_COMPONENT
/datum/wires/particle_acc/control_box/New()
wires = list(
+1 -1
View File
@@ -1,7 +1,7 @@
/datum/wires/robot
proper_name = "Synthetic"
random = TRUE
holder_type = /mob/living/silicon/robot
associated_skill = ROBOTICS_SKILL_COMPONENT
/datum/wires/robot/New()
wires = list(
+56
View File
@@ -0,0 +1,56 @@
/datum/wires/stasis_cage
proper_name = "Stasis Cage"
holder_type = /obj/structure/machinery/stasis_cage
associated_skill = XENOBIOLOGY_SKILL_COMPONENT
/datum/wires/stasis_cage/New(atom/holder)
wires = list(
WIRE_SAFETY,
WIRE_RELEASE,
WIRE_LOCK
)
add_duds(3)
..()
/datum/wires/stasis_cage/interactable(mob/user)
SHOULD_CALL_PARENT(FALSE)
var/obj/structure/machinery/stasis_cage/stasis_cage = holder
if (stasis_cage.panel_open)
return TRUE
return FALSE
/datum/wires/stasis_cage/get_status()
var/obj/structure/machinery/stasis_cage/stasis_cage = holder
. = ..()
if(!stasis_cage.use_power)
. += "The panel is unpowered."
else
. += "The panel is powered."
. += "The biometric safety sensors are [(WIRE_SAFETY in cut_wires) ? "connected" : "disconnected"]."
. += "The cage's emergency auto-release mechanism is [(WIRE_RELEASE in cut_wires) ? "disabled" : "enabled"]."
. += "The cage lid motors are [(WIRE_LOCK in cut_wires) ? "overriden" : "nominal"]."
/datum/wires/stasis_cage/on_cut(wire, mend, source)
var/obj/structure/machinery/stasis_cage/stasis_cage = holder
switch (wire)
if (WIRE_SAFETY)
stasis_cage.safety = !stasis_cage.safety
if (WIRE_RELEASE)
if (stasis_cage.contained && !mend)
stasis_cage.release()
holder.update_icon()
if (WIRE_LOCK)
if (!mend)
playsound(stasis_cage.loc, 'sound/machines/BoltsDown.ogg', 60)
holder.visible_message(SPAN_WARNING("The cage's lid bolts down destructively, denting itself!"), SPAN_NOTICE("You notice the cage lid override flag blink hastily."))
stasis_cage.broken = TRUE
holder.update_icon()
/datum/wires/stasis_cage/on_pulse(wire)
var/obj/structure/machinery/stasis_cage/stasis_cage = holder
switch (wire)
if (WIRE_SAFETY)
if (stasis_cage.contained)
if (prob(20))
holder.visible_message(SPAN_WARNING("The cage hastily flicks open its lid!"), SPAN_NOTICE("You notice the biometric sensor flag blink fervently."))
stasis_cage.release()
+1
View File
@@ -1,6 +1,7 @@
/datum/wires/suit_storage_unit
proper_name = "Suit Storage Unit"
holder_type = /obj/structure/machinery/suit_cycler
associated_skill = ROBOTICS_SKILL_COMPONENT
/datum/wires/suit_storage_unit/New()
wires = list(
+2 -1
View File
@@ -1,6 +1,7 @@
/datum/wires/vending
proper_name = "Vending Machine"
holder_type = /obj/structure/machinery/vending
associated_skill = MECHANICAL_ENGINEERING_SKILL_COMPONENT
/datum/wires/vending/New()
wires = list(
@@ -78,6 +79,6 @@
continue
dat += "<font color='[color]'>[capitalize(color)]</font>: [get_wire(color)]<br>"
var/datum/browser/wire_win = new(user, "vendingwires", "Vending Wires", 450, 500)
var/datum/browser/wire_win = new(user, "vendingwires", "Vending Wires", 450, 720)
wire_win.set_content(dat)
wire_win.open()
+67 -8
View File
@@ -2,6 +2,7 @@
GLOBAL_LIST_INIT(wire_color_directory, list())
GLOBAL_LIST_INIT(wire_name_directory, list())
GLOBAL_LIST_INIT(wire_skill_reveal_directory, list())
/datum/wires
/// The holder (atom that contains these wires).
@@ -27,6 +28,10 @@ GLOBAL_LIST_INIT(wire_name_directory, list())
var/random = FALSE
/// Wire manufacturer for the UI skin.
var/manufacturer = "hephaestus"
/// Skill component path used for partial wire reveals. Null disables skill-based reveals.
var/associated_skill = ELECTRICAL_ENGINEERING_SKILL_COMPONENT
/// Key used for round-stable partial skill-based reveals
var/skill_reveal_key
/datum/wires/New(atom/holder)
..()
@@ -40,6 +45,7 @@ GLOBAL_LIST_INIT(wire_name_directory, list())
// If there is a dictionary key set, we'll want to use that. Otherwise, use the holder type.
var/key = dictionary_key ? dictionary_key : holder_type
skill_reveal_key = key
RegisterSignal(holder, COMSIG_QDELETING, PROC_REF(on_holder_qdel))
if(random)
@@ -54,6 +60,7 @@ GLOBAL_LIST_INIT(wire_name_directory, list())
/datum/wires/Destroy()
holder = null
skill_reveal_key = null
//properly clear refs to avoid harddels & other problems
for(var/color in assemblies)
var/obj/item/assembly/assembly = assemblies[color]
@@ -102,6 +109,8 @@ GLOBAL_LIST_INIT(wire_name_directory, list())
/datum/wires/proc/shuffle_wires()
colors.Cut()
if(skill_reveal_key)
GLOB.wire_skill_reveal_directory -= skill_reveal_key
randomize()
/datum/wires/proc/repair()
@@ -241,8 +250,8 @@ GLOBAL_LIST_INIT(wire_name_directory, list())
/**
* Checks whether wire assignments should be revealed.
*
* Returns TRUE if the wires should be revealed, FALSE otherwise.
* Currently checks for admin ghost AI, abductor multitool and blueprints.
* Returns TRUE if every wire assignment in this panel should be revealed, FALSE otherwise.
* Currently checks for admin ghosts and blueprints.
* Arguments:
* * user - The mob to check when deciding whether to reveal wires.
*/
@@ -259,10 +268,6 @@ GLOBAL_LIST_INIT(wire_name_directory, list())
if(istype(user.get_active_hand(), /obj/item/blueprints))
return TRUE
// Max electrical engineering skill grants this as a perk
if (GET_SKILL_LEVEL(user, ELECTRICAL_ENGINEERING_SKILL_COMPONENT) >= SKILL_LEVEL_PROFESSIONAL)
return TRUE
return FALSE
/**
@@ -276,6 +281,57 @@ GLOBAL_LIST_INIT(wire_name_directory, list())
/datum/wires/proc/always_reveal_wire(color)
return FALSE
/**
* Gets how many wires this user's associated skill reveals.
*
* Familiar: 15%; Trained: 35%; Professional: 65%.
* Arguments:
* * user - The mob to check when deciding how many wires to reveal.
*/
/datum/wires/proc/get_skill_revealed_wire_count(mob/user)
if(random || !associated_skill || !LAZYLEN(colors) || !user)
return 0
var/skill_level = GET_SKILL_LEVEL(user, associated_skill)
if(isnull(skill_level))
skill_level = SKILL_LEVEL_UNFAMILIAR
// If you're trained in various electromechanical biz, you can probably suss out at least some basics.
var/has_electromech_proficiency = (GET_SKILL_LEVEL(user, ELECTRICAL_ENGINEERING_SKILL_COMPONENT) >= SKILL_LEVEL_TRAINED && GET_SKILL_LEVEL(user, MECHANICAL_ENGINEERING_SKILL_COMPONENT) >= SKILL_LEVEL_TRAINED)
var/reveal_percent = 0
if(skill_level >= SKILL_LEVEL_PROFESSIONAL)
reveal_percent = 75
else if(skill_level >= SKILL_LEVEL_TRAINED)
reveal_percent = 50
else if(skill_level >= SKILL_LEVEL_FAMILIAR || has_electromech_proficiency)
reveal_percent = 30
else
return 0
return clamp(round(LAZYLEN(colors) * reveal_percent / 100), 0, LAZYLEN(colors))
/datum/wires/proc/get_skill_revealed_colors(var/reveal_count)
if(reveal_count <= 0 || random || !LAZYLEN(colors))
return null
var/reveal_key = skill_reveal_key ? skill_reveal_key : (dictionary_key ? dictionary_key : holder_type)
var/list/skill_reveal_order = GLOB.wire_skill_reveal_directory[reveal_key]
var/rebuild_order = !skill_reveal_order || length(skill_reveal_order) != LAZYLEN(colors)
if(!rebuild_order)
for(var/color in skill_reveal_order)
if(!(color in colors))
rebuild_order = TRUE
break
if(rebuild_order)
skill_reveal_order = list()
for(var/color in colors)
skill_reveal_order += color
skill_reveal_order = shuffle(skill_reveal_order)
GLOB.wire_skill_reveal_directory[reveal_key] = skill_reveal_order
return skill_reveal_order.Copy(1, reveal_count + 1)
/datum/wires/ui_host()
return holder
@@ -297,11 +353,14 @@ GLOBAL_LIST_INIT(wire_name_directory, list())
/datum/wires/ui_data(mob/user)
var/list/data = list()
var/list/payload = list()
var/reveal_wires = can_reveal_wires(user)
var/reveal_all = can_reveal_wires(user)
var/list/skill_revealed_colors
if(!reveal_all)
skill_revealed_colors = get_skill_revealed_colors(get_skill_revealed_wire_count(user))
for(var/color in colors)
var/wire_label = null
if (reveal_wires || always_reveal_wire(color))
if(reveal_all || always_reveal_wire(color) || (skill_revealed_colors && (color in skill_revealed_colors)))
wire_label = is_dud_color(color) ? "None" : get_wire(color)
payload.Add(list(list(