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
+3 -1
View File
@@ -719,6 +719,7 @@
#include "code\datums\wires\disposals.dm"
#include "code\datums\wires\explosive.dm"
#include "code\datums\wires\fabricator.dm"
#include "code\datums\wires\hardsuit.dm"
#include "code\datums\wires\iff.dm"
#include "code\datums\wires\ipc_tag_scanner.dm"
#include "code\datums\wires\landmine.dm"
@@ -728,6 +729,7 @@
#include "code\datums\wires\robot.dm"
#include "code\datums\wires\smartfridge.dm"
#include "code\datums\wires\smes.dm"
#include "code\datums\wires\stasis_cage.dm"
#include "code\datums\wires\suit_storage_unit.dm"
#include "code\datums\wires\vending.dm"
#include "code\datums\wires\wires.dm"
@@ -2155,7 +2157,6 @@
#include "code\modules\clothing\spacesuits\rig\rig_construction.dm"
#include "code\modules\clothing\spacesuits\rig\rig_pieces.dm"
#include "code\modules\clothing\spacesuits\rig\rig_verbs.dm"
#include "code\modules\clothing\spacesuits\rig\rig_wiring.dm"
#include "code\modules\clothing\spacesuits\rig\modules\combat.dm"
#include "code\modules\clothing\spacesuits\rig\modules\computer.dm"
#include "code\modules\clothing\spacesuits\rig\modules\modules.dm"
@@ -3139,6 +3140,7 @@
#include "code\modules\modular_computers\file_system\programs\generic\ntnrc_client.dm"
#include "code\modules\modular_computers\file_system\programs\generic\records.dm"
#include "code\modules\modular_computers\file_system\programs\generic\scanner.dm"
#include "code\modules\modular_computers\file_system\programs\generic\signaler.dm"
#include "code\modules\modular_computers\file_system\programs\medical\suit_sensors.dm"
#include "code\modules\modular_computers\file_system\programs\pai\access_lock.dm"
#include "code\modules\modular_computers\file_system\programs\pai\directives.dm"
-3
View File
@@ -61,9 +61,6 @@
#define WIRE_TIMING "Timing"
#define WIRE_TX "Transmit"
#define WIRE_UNBOLT "Unbolt"
#define WIRE_ZAP "High Voltage Circuit"
#define WIRE_ZAP1 "High Voltage Circuit 1"
#define WIRE_ZAP2 "High Voltage Circuit 2"
#define WIRE_OVERCLOCK "Overclock"
#define WIRE_FOCUS "Focus"
#define WIRE_FLUSH "Flush"
+25 -9
View File
@@ -10,10 +10,13 @@
skill_level_descriptions = alist(
SKILL_LEVEL_UNFAMILIAR = "You have little to no experience working with electrical systems.",
SKILL_LEVEL_FAMILIAR = "You have some experience working with electrical systems, though are not formally trained.<br>" \
+ " - You can use a power cell to attempt emergency power bypasses on piloting consoles when they're unpowered.",
SKILL_LEVEL_TRAINED = "You have formal training in electrical engineering concepts, equivalent to a Bachelor's Degree.",
+ " - You can use a power cell to attempt emergency power bypasses on piloting consoles when they're unpowered.<br>" \
+ " - You can recognize what a few wires do in electrical devices by sight.",
SKILL_LEVEL_TRAINED = "You have formal training in electrical engineering concepts, equivalent to a Bachelor's Degree.<br>" \
+ " - You can use a power cell to attempt emergency power bypasses on piloting consoles when they're unpowered.<br>" \
+ " - You can recognize what several wires do in electrical devices by sight.",
SKILL_LEVEL_PROFESSIONAL = "You have many years of training in electrical related Engineering concepts, equivalent to a Master's Degree or better.<br>" \
+ " - You can tell what most machinery and airlock wires do just by looking at them." \
+ " - You can recognize what many wires do in electrical devices by sight." \
)
/singleton/skill/mechanical_engineering
@@ -35,14 +38,17 @@
SKILL_LEVEL_FAMILIAR = "You have some experience working with physical machinery, though are not formally trained.<br>" \
+ " - Items you craft will be of slightly lower than average quality.<br>" \
+ " - You take 50% longer to craft anything.<br>" \
+ " - You can interact with some Engineering-related machinery.",
+ " - You can interact with some Engineering-related machinery.<br>" \
+ " - You can recognize what a few wires do in mechanical devices by sight.",
SKILL_LEVEL_TRAINED = "You have formal training in general Engineering concepts, equivalent to a Bachelor's Degree. <br>" \
+ " - You can craft items at the standard speed.<br>" \
+ " - Items you craft are generally at average quality.<br>" \
+ " - You can interact with most if not all Engineering equipment.",
+ " - You can craft a wider variety of items.<br>" \
+ " - You can interact with most if not all Engineering equipment.<br>" \
+ " - You can recognize what several wires do in mechanical devices by sight.",
SKILL_LEVEL_PROFESSIONAL = "You have many years of training in general Engineering concepts, equivalent to a Master's Degree or better.<br>" \
+ " - You can craft items 50% faster.<br>" \
+ " - Items you craft are of much higher quality on average.<br>" \
+ " - You can craft a much wider variety of items.<br>" \
+ " - You can recognize what many wires do in mechanical devices by sight.",
)
/singleton/skill/atmospherics_systems
@@ -54,6 +60,16 @@
subcategory = SKILL_SUBCATEGORY_ENGINEERING
required = TRUE
component_type = ATMOSPHERICS_SYSTEMS_SKILL_COMPONENT
skill_level_descriptions = alist(
SKILL_LEVEL_UNFAMILIAR = "You have little to no experience working with atmospherics systems.<br>" \
+ " - No mechanical changes implemented at this skill level.",
SKILL_LEVEL_FAMILIAR = "You have some experience working with atmospherics systems, though are not formally trained.<br>" \
+ " - You can recognize what a few wires do in atmospherics devices by sight.",
SKILL_LEVEL_TRAINED = "You have formal training in general Engineering concepts, equivalent to a Bachelor's Degree. <br>" \
+ " - You can recognize what some wires do in atmospherics devices by sight.",
SKILL_LEVEL_PROFESSIONAL = "You have many years of training in general Engineering concepts, equivalent to a Master's Degree or better.<br>" \
+ " - You can recognize what many wires do in atmospherics devices by sight." \
)
/singleton/skill/reactor_systems
name = "Reactor Systems"
@@ -66,9 +82,9 @@
required = TRUE
component_type = REACTOR_SYSTEMS_SKILL_COMPONENT
skill_level_descriptions = alist(
SKILL_LEVEL_UNFAMILIAR = "You have little to no experience working with Reactor systems.<br>" \
SKILL_LEVEL_UNFAMILIAR = "You have little to no experience working with reactor systems.<br>" \
+ " - You cannot interact with Reactor computers such as the INDRA controllers.",
SKILL_LEVEL_FAMILIAR = "You have some experience working with physical machinery, though are not formally trained.<br>" \
SKILL_LEVEL_FAMILIAR = "You have some experience working with reactor systems, though are not formally trained.<br>" \
+ " - You cannot interact with Reactor computers such as the INDRA controller.",
SKILL_LEVEL_TRAINED = "You have formal training in general Engineering concepts, equivalent to a Bachelor's Degree. <br>" \
+ " - This is the minimum skill level required to operate the INDRA.",
@@ -15,12 +15,14 @@
SKILL_LEVEL_UNFAMILIAR = "You have zero training or experience with synthetics.<br>" \
+ " - You suffer a large penalty to the chances of successfully performing any surgery on synthetics.",
SKILL_LEVEL_FAMILIAR = "You have minimal training on the basics of synthetic repair and maintenance. This could be the level of a hobbyist, or someone currently pursuing a degree in robotics.<br>" \
+ " - You can recognize what a few wires do in mechatronic devices by sight.<br>" \
+ "You can perform the following procedures without penalties: <br>" \
+ " - Opening or closing external maintenance panels to make superficial repairs.<br>" \
+ " - Repairing basic damage with a welder or cables.<br>" \
+ " - Repairing external damage to mechanical limbs.<br>" \
+ " - Cutting someone out of a hardsuit.",
SKILL_LEVEL_TRAINED = "You have years of formal training or experience on repairing and maintaining synthetics equivalent to a Bachelor's degree in Robotics.<br>" \
+ " - You can recognize what several wires do in mechatronic devices by sight.<br>" \
+ "You can perform the following procedures without penalties:<br>" \
+ " - Opening or closing external maintenance panels to make superficial repairs.<br>" \
+ " - Repairing basic damage with a welder or cables.<br>" \
@@ -33,7 +35,8 @@
+ " - Re-attach (organic) limbs. Robotic limbs require the Robotics skill instead.<br>" \
+ " - Perform all forms of internal repairs to IPCs.<br>" \
+ " - Prepare an MMI for cyborgification.",
SKILL_LEVEL_PROFESSIONAL = "Not currently implemented, functions exactly as per Trained.<br>" \
SKILL_LEVEL_PROFESSIONAL = "You have years of formal training or experience on repairing and maintaining synthetics equivalent to a Master's degree in Robotics or better.<br>" \
+ " - You can recognize what many wires do in mechatronic devices by sight.<br>" \
+ "You can perform the following procedures without penalties:<br>" \
+ " - Opening or closing external maintenance panels to make superficial repairs.<br>" \
+ " - Repairing basic damage with a welder or cables.<br>" \
@@ -39,16 +39,19 @@
skill_level_descriptions = alist(
SKILL_LEVEL_UNFAMILIAR = "You have little to no knowledge of Xenobiology",
SKILL_LEVEL_FAMILIAR = "You have a passing experience with Xenobiology.<br>" \
+ " - You can recognize what a few wires do in xenobiological sciences hardware by sight.<br>" \
+ " - Your melee attacks made against Xenofauna such as Space Carp deal 10% more damage.<br>" \
+ " - You harvest one additional part when butchering Xenofauna.<br>" \
+ " - You are +2.5% more likely to succeed when performing surgery on a patient that is a different species than your own.<br>" \
+ " - You can perform surgeries 5% faster on a patient that is a different species than your own.",
SKILL_LEVEL_TRAINED = "You have a moderate experience with Xenobiology.<br>" \
+ " - You can recognize what several wires do in xenobiological sciences hardware by sight.<br>" \
+ " - Your melee attacks made against Xenofauna such as Space Carp deal 15% more damage.<br>" \
+ " - You harvest two additional parts when butchering Xenofauna.<br>" \
+ " - You are +5% more likely to succeed when performing surgery on a patient that is a different species than your own. <br>" \
+ " - You can perform surgeries 10% faster on a patient that is a different species than your own.",
SKILL_LEVEL_PROFESSIONAL = "You have extensive experience with Xenobiology.<br>" \
+ " - You can recognize what many wires do in xenobiological sciences hardware by sight.<br>" \
+ " - Your melee attacks made against Xenofauna such as Space Carp deal 20% more damage.<br>" \
+ " - You harvest three additional parts when butchering Xenofauna.<br>" \
+ " - You are +7.5% more likely to succeed when performing surgery on a patient that is a different species than your own.<br>" \
+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(
@@ -11,8 +11,8 @@
#define RIG_INTERFACE_SHOCK BITFLAG(5)
/datum/wires/rig
random = 1
holder_type = /obj/item/rig
associated_skill = ROBOTICS_SKILL_COMPONENT
/datum/wires/rig/New(atom/holder)
wires = list(
+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(
+69 -8
View File
@@ -44,8 +44,12 @@
var/frequency = 1457
var/code = 30.0
var/last_transmission
var/listening = TRUE
var/datum/radio_frequency/radio_connection
/obj/item/integrated_signaler/signal/network_card
listening = FALSE
/obj/item/integrated_signaler/signal/Initialize()
. = ..()
if(!SSradio)
@@ -57,26 +61,83 @@
set_frequency(frequency)
/obj/item/integrated_signaler/signal/proc/set_frequency(new_frequency)
SSradio.remove_object(src, frequency)
frequency = new_frequency
radio_connection = SSradio.add_object(src, frequency)
if(!SSradio || !new_frequency)
return FALSE
var/sanitized_frequency = sanitize_frequency(new_frequency, RADIO_LOW_FREQ, RADIO_HIGH_FREQ)
if(sanitized_frequency > RADIO_HIGH_FREQ)
sanitized_frequency = RADIO_HIGH_FREQ - 1
if(radio_connection && frequency == sanitized_frequency)
return TRUE
if(radio_connection)
SSradio.remove_object(src, frequency)
frequency = sanitized_frequency
if(listening)
radio_connection = SSradio.add_object(src, frequency)
else
radio_connection = null
return TRUE
/obj/item/integrated_signaler/signal/proc/send_signal(message="ACTIVATE")
/obj/item/integrated_signaler/signal/proc/set_listening(var/new_listening)
new_listening = !!new_listening
if(listening == new_listening)
return TRUE
listening = new_listening
if(listening)
return set_frequency(frequency)
if(radio_connection)
SSradio.remove_object(src, frequency)
radio_connection = null
return TRUE
/obj/item/integrated_signaler/signal/proc/send_signal(message="ACTIVATE", var/mob/user)
if(!SSradio)
return FALSE
var/datum/radio_frequency/transmit_connection = radio_connection
if(!transmit_connection)
transmit_connection = SSradio.return_frequency(frequency)
if(!transmit_connection)
return FALSE
if(within_jamming_range(src))
return FALSE
if(last_transmission && world.time < (last_transmission + 5))
return
return FALSE
last_transmission = world.time
var/time = time2text(world.realtime,"hh:mm:ss")
var/turf/T = get_turf(src)
GLOB.lastsignalers.Add("[time] <B>:</B> [usr.key] used [src] @ location ([T.x],[T.y],[T.z]) <B>:</B> [format_frequency(frequency)]/[code]")
var/user_key = user ? user.key : usr?.key
GLOB.lastsignalers.Add("[time] <B>:</B> [user_key ? user_key : "userless"] used [src] @ location ([T ? T.x : 0],[T ? T.y : 0],[T ? T.z : 0]) <B>:</B> [format_frequency(frequency)]/[code]")
var/datum/signal/signal = new
signal.source = src
signal.encryption = code
signal.data["message"] = message
if(user)
signal.data["user"] = WEAKREF(user)
radio_connection.post_signal(src, signal)
transmit_connection.post_signal(src, signal)
return TRUE
/obj/item/integrated_signaler/signal/receive_signal(var/datum/signal/signal)
if(!signal)
return FALSE
if(signal.source == src)
return FALSE
if(signal.data["message"] != "ACTIVATE")
return FALSE
if(isnull(signal.encryption))
return FALSE
if(within_jamming_range(src))
return FALSE
var/obj/item/computer_hardware/network_card/network_card = loc
if(istype(network_card))
return network_card.receive_signaler_signal(src, signal)
return FALSE
/obj/item/integrated_signaler/signal/Destroy()
SSradio.remove_object(src, frequency)
if(SSradio)
SSradio.remove_object(src, frequency)
return ..()
@@ -567,7 +567,23 @@ update_flag
/obj/structure/machinery/portable_atmospherics/canister/do_signaler()
valve_open = !valve_open
if(valve_open)
if(holding)
release_log += "Valve was <b>opened</b> by a signaler, starting the transfer into [holding]<br>"
else
release_log += "Valve was <b>opened</b> by a signaler, starting the transfer into the <span class='warning'><b>air</b></span><br>"
log_open_userless("a signaler")
if(!(processing_flags & MACHINERY_PROCESS_SELF))
START_PROCESSING_MACHINE(src, MACHINERY_PROCESS_SELF)
process()
else
if(holding)
release_log += "Valve was <b>closed</b> by a signaler, stopping the transfer into [holding]<br>"
else
release_log += "Valve was <b>closed</b> by a signaler, stopping the transfer into the <span class='warning'><b>air</b></span><br>"
update_connected_network()
update_icon()
SStgui.update_uis(src)
/**
* Dirty way to fill room with gas. However it is a bit easier to do than creating some floor/engine/n2o -rastaf0
@@ -252,65 +252,3 @@
if(target.buckled_to)
target.buckled_to.unbuckle()
contain(user, target)
/datum/wires/stasis_cage
proper_name = "Stasis Cage"
holder_type = /obj/structure/machinery/stasis_cage
/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()
@@ -22,7 +22,7 @@
/obj/item/flash/synthetic = 4,
/obj/item/cell/high = 12,
/obj/item/assembly/prox_sensor = 8,
/obj/item/assembly/signaler = 8,
/obj/item/assembly/signaler = 20,
/obj/item/healthanalyzer = 3,
/obj/item/surgery/scalpel = 2,
/obj/item/surgery/circular_saw = 2,
@@ -62,6 +62,7 @@
/obj/item/multitool = 4,
/obj/item/taperoll/engineering = 4,
/obj/item/clothing/glasses/safety/goggles = 4,
/obj/item/assembly/signaler = 20,
/obj/item/airlock_electronics = 20,
/obj/item/module/power_control = 10,
/obj/item/airalarm_electronics = 10,
+1 -1
View File
@@ -4,7 +4,7 @@
icon = 'icons/obj/assemblies/new_assemblies.dmi'
icon_state = ""
obj_flags = OBJ_FLAG_CONDUCTABLE
w_class = WEIGHT_CLASS_SMALL
w_class = WEIGHT_CLASS_TINY
matter = list(MATERIAL_STEEL = 100)
recyclable = TRUE
throwforce = 2
+13 -4
View File
@@ -189,13 +189,22 @@
return FALSE
if(!secured)
audible_message("[icon2html(src, viewers(get_turf(src)))] *beep* *beep*", "*beep* *beep*")
return FALSE
if(normal && a_right && a_left)
if(a_right != D)
a_right.pulsed(FALSE)
if(a_left != D)
a_left.pulsed(FALSE)
var/obj/item/assembly/right = a_right
if(right && right != D && right.holder == src)
right.pulsed(FALSE)
if(QDELETED(src))
return TRUE
var/obj/item/assembly/left = a_left
if(left && left != D && left.holder == src)
left.pulsed(FALSE)
if(QDELETED(src))
return TRUE
if(master)
master.receive_signal()
else
audible_message("[icon2html(src, viewers(get_turf(src)))] *beep* *beep*", "*beep* *beep*")
return TRUE
/obj/item/assembly_holder/hear_talk(mob/living/M, msg, verb, datum/language/speaking)
+3 -2
View File
@@ -72,8 +72,9 @@
switch(action)
if("freq")
var/new_frequency = text2num(params["freq"]) * 10
if(new_frequency < RADIO_LOW_FREQ || new_frequency > RADIO_HIGH_FREQ)
new_frequency = sanitize_frequency(new_frequency, RADIO_LOW_FREQ, RADIO_HIGH_FREQ)
new_frequency = sanitize_frequency(new_frequency, RADIO_LOW_FREQ, RADIO_HIGH_FREQ)
if(new_frequency > RADIO_HIGH_FREQ)
new_frequency = RADIO_HIGH_FREQ - 1
set_frequency(new_frequency)
if("code")
@@ -60,6 +60,10 @@
. = ..()
card_slot.stored_item = new /obj/item/pen/silver
/obj/item/modular_computer/handheld/preset/engineering/install_default_hardware()
..()
network_card = new /obj/item/computer_hardware/network_card/signaler(src)
/obj/item/modular_computer/handheld/preset/engineering/atmos
_app_preset_type = /datum/modular_computer_app_presets/engineering/atmos
@@ -86,6 +90,10 @@
/obj/item/modular_computer/handheld/preset/supply/machinist
_app_preset_type = /datum/modular_computer_app_presets/supply/machinist
/obj/item/modular_computer/handheld/preset/supply/machinist/install_default_hardware()
..()
network_card = new /obj/item/computer_hardware/network_card/signaler(src)
// Cargo Delivery
/obj/item/modular_computer/handheld/preset/supply/cargo_delivery
_app_preset_type = /datum/modular_computer_app_presets/cargo_delivery
@@ -125,6 +133,10 @@
. = ..()
card_slot.stored_item = new /obj/item/pen/white
/obj/item/modular_computer/handheld/preset/research/install_default_hardware()
..()
network_card = new /obj/item/computer_hardware/network_card/signaler(src)
/obj/item/modular_computer/handheld/preset/research/rd
_app_preset_type = /datum/modular_computer_app_presets/research/rd
@@ -31,6 +31,11 @@
_app_preset_type = /datum/modular_computer_app_presets/engineering
enrolled = DEVICE_COMPANY
/obj/item/modular_computer/laptop/preset/engineering/install_default_hardware()
..()
QDEL_NULL(network_card)
network_card = new /obj/item/computer_hardware/network_card/signaler(src)
/obj/item/modular_computer/laptop/preset/engineering/ce
name = "chief engineer's laptop"
desc = "A portable computer belonging to the chief engineer."
@@ -58,6 +63,8 @@
/obj/item/modular_computer/laptop/preset/research/install_default_hardware()
..()
ai_slot = new /obj/item/computer_hardware/ai_slot(src)
QDEL_NULL(network_card)
network_card = new /obj/item/computer_hardware/network_card/signaler(src)
/obj/item/modular_computer/laptop/preset/research/rd
name = "research director's laptop"
@@ -130,6 +137,8 @@
/obj/item/modular_computer/laptop/preset/supply/robotics/install_default_hardware()
..()
access_cable_dongle = new /obj/item/computer_hardware/access_cable_dongle(src)
QDEL_NULL(network_card)
network_card = new /obj/item/computer_hardware/network_card/signaler(src)
// Representative
/obj/item/modular_computer/laptop/preset/representative
@@ -60,6 +60,10 @@
. = ..()
card_slot.stored_item = new /obj/item/pen/silver
/obj/item/modular_computer/handheld/pda/engineering/install_default_hardware()
..()
network_card = new /obj/item/computer_hardware/network_card/signaler(src)
/obj/item/modular_computer/handheld/pda/engineering/atmos
_app_preset_type = /datum/modular_computer_app_presets/engineering/atmos
@@ -82,6 +86,10 @@
/obj/item/modular_computer/handheld/pda/supply/machinist
_app_preset_type = /datum/modular_computer_app_presets/supply/machinist
/obj/item/modular_computer/handheld/pda/supply/machinist/install_default_hardware()
..()
network_card = new /obj/item/computer_hardware/network_card/signaler(src)
/obj/item/modular_computer/handheld/pda/supply/om
icon_add = "q"
@@ -117,6 +125,10 @@
. = ..()
card_slot.stored_item = new /obj/item/pen/white
/obj/item/modular_computer/handheld/pda/research/install_default_hardware()
..()
network_card = new /obj/item/computer_hardware/network_card/signaler(src)
/obj/item/modular_computer/handheld/pda/research/rd
_app_preset_type = /datum/modular_computer_app_presets/research/rd
icon_add = "rd"
@@ -154,6 +154,10 @@
. = ..()
card_slot.stored_item = new /obj/item/pen/silver
/obj/item/modular_computer/handheld/wristbound/preset/pda/engineering/install_default_hardware()
..()
network_card = new /obj/item/computer_hardware/network_card/signaler(src)
/obj/item/modular_computer/handheld/wristbound/preset/pda/engineering/atmos
_app_preset_type = /datum/modular_computer_app_presets/engineering/atmos
@@ -183,6 +187,10 @@
/obj/item/modular_computer/handheld/wristbound/preset/pda/supply/machinist
_app_preset_type = /datum/modular_computer_app_presets/supply/machinist
/obj/item/modular_computer/handheld/wristbound/preset/pda/supply/machinist/install_default_hardware()
..()
network_card = new /obj/item/computer_hardware/network_card/signaler(src)
// Medical
/obj/item/modular_computer/handheld/wristbound/preset/pda/medical
@@ -211,6 +219,10 @@
. = ..()
card_slot.stored_item = new /obj/item/pen/white
/obj/item/modular_computer/handheld/wristbound/preset/pda/research/install_default_hardware()
..()
network_card = new /obj/item/computer_hardware/network_card/signaler(src)
/obj/item/modular_computer/handheld/wristbound/preset/pda/research/rd
_app_preset_type = /datum/modular_computer_app_presets/research/rd
icon_add = "rd"
@@ -55,6 +55,7 @@ ABSTRACT_TYPE(/datum/modular_computer_app_presets)
/datum/computer_file/program/camera_monitor,\
/datum/computer_file/program/lighting_control,\
/datum/computer_file/program/fusion_codex,\
/datum/computer_file/program/signaler,\
)
#define COMPUTER_APP_PRESET_HORIZON_MEDICAL list(\
@@ -71,6 +72,7 @@ ABSTRACT_TYPE(/datum/modular_computer_app_presets)
/datum/computer_file/program/scanner/science,\
/datum/computer_file/program/scanner/gas,\
/datum/computer_file/program/away_manifest,\
/datum/computer_file/program/signaler,\
)
#define COMPUTER_APP_PRESET_HORIZON_SECURITY list(\
@@ -294,7 +294,7 @@
/datum/modular_computer_app_presets/supply/machinist/New()
. = ..()
program_list += list(/datum/computer_file/program/records/medical, /datum/computer_file/program/scanner/science, /datum/computer_file/program/robotics)
program_list += list(/datum/computer_file/program/records/medical, /datum/computer_file/program/scanner/science, /datum/computer_file/program/robotics, /datum/computer_file/program/signaler)
//Machinist is the bastard child of supply/operation, it doesn't have access to shit essentially
program_list -= list(/datum/computer_file/program/civilian/cargocontrol, /datum/computer_file/program/civilian/cargodelivery, /datum/computer_file/program/away_manifest)
@@ -4,7 +4,7 @@
program_icon_state = "medcomp"
program_key_icon_state = "teal_key"
extended_desc = "Useful program to reference reaction chains for nuclear fusion reactors."
size = 14
size = 4
requires_access_to_run = PROGRAM_ACCESS_LIST_ONE
required_access_run = list(ACCESS_ENGINE)
required_access_download = list(ACCESS_ENGINE)
@@ -0,0 +1,249 @@
#define NTOS_SIGNALER_CHANNEL_COUNT 8
#define NTOS_SIGNALER_RECEIVE_CHANNELS 2
#define NTOS_SIGNALER_DEFAULT_FREQUENCY 1457
#define NTOS_SIGNALER_DEFAULT_CODE 30
#define NTOS_SIGNALER_MAX_LABEL_LENGTH 24
/datum/computer_file/program/signaler
filename = "signaler"
filedesc = "Signal Manager"
extended_desc = "This program configures signaler-capable network cards for receiving and sending signals on custom frequencies."
program_icon_state = "generic"
program_key_icon_state = "green_key"
color = LIGHT_COLOR_GREEN
size = 1
requires_ntnet = FALSE
available_on_ntnet = TRUE
usage_flags = PROGRAM_ALL_REGULAR
tgui_id = "NTOSSignaler"
var/list/signal_configurations = list()
/datum/computer_file/program/signaler/New(obj/item/modular_computer/comp)
. = ..()
ensure_signal_configurations()
/datum/computer_file/program/signaler/clone(var/rename = FALSE, var/computer)
ensure_signal_configurations()
var/datum/computer_file/program/signaler/signaler_clone = ..(rename, computer)
signaler_clone.signal_configurations = list()
for(var/list/configuration in signal_configurations)
signaler_clone.signal_configurations += list(configuration.Copy())
return signaler_clone
/datum/computer_file/program/signaler/run_program(var/mob/user)
if(!..())
return FALSE
sync_receive_radios()
return TRUE
/datum/computer_file/program/signaler/ui_data(mob/user)
ensure_signal_configurations()
sync_receive_radios()
var/list/data = initial_data()
data["hardware_ready"] = !!get_signaler_card()
data["hardware_status"] = get_hardware_status()
data["min_frequency"] = RADIO_LOW_FREQ
data["max_frequency"] = RADIO_HIGH_FREQ
data["receive_channels"] = NTOS_SIGNALER_RECEIVE_CHANNELS
data["silent"] = silent
data["channels"] = list()
for(var/i = 1 to NTOS_SIGNALER_CHANNEL_COUNT)
var/list/configuration = signal_configurations[i]
data["channels"] += list(list(
"index" = i,
"label" = configuration["label"],
"frequency" = configuration["frequency"],
"code" = configuration["code"],
"receiving" = i <= NTOS_SIGNALER_RECEIVE_CHANNELS,
"receiving_enabled" = configuration["receiving_enabled"],
"last_received_code" = configuration["last_received_code"],
"last_received_time" = configuration["last_received_time"]
))
return data
/datum/computer_file/program/signaler/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
if(..())
return TRUE
if(action == "toggle_silent")
silent = !silent
. = TRUE
return
ensure_signal_configurations()
var/channel_index = clamp(round(text2num(params["index"])), 1, NTOS_SIGNALER_CHANNEL_COUNT)
var/list/configuration = signal_configurations[channel_index]
if(!configuration)
return TRUE
switch(action)
if("rename")
var/new_label = tgui_input_text(usr, "Enter a new label for this [channel_index <= NTOS_SIGNALER_RECEIVE_CHANNELS ? "receiver" : "signaler"].", "Rename Signal", configuration["label"], NTOS_SIGNALER_MAX_LABEL_LENGTH)
if(isnull(new_label))
return TRUE
new_label = sanitize(new_label, NTOS_SIGNALER_MAX_LABEL_LENGTH)
configuration["label"] = length(new_label) ? new_label : default_label(channel_index)
. = TRUE
if("frequency")
configuration["frequency"] = clean_frequency(text2num(params["frequency"]))
if(channel_index <= NTOS_SIGNALER_RECEIVE_CHANNELS)
sync_receive_radios()
. = TRUE
if("toggle_receive")
if(channel_index <= NTOS_SIGNALER_RECEIVE_CHANNELS)
configuration["receiving_enabled"] = !configuration["receiving_enabled"]
if(!configuration["receiving_enabled"])
configuration["last_received_code"] = null
configuration["last_received_time"] = null
sync_receive_radios()
. = TRUE
if("code")
if(channel_index > NTOS_SIGNALER_RECEIVE_CHANNELS)
configuration["code"] = clamp(round(text2num(params["code"])), 1, 100)
. = TRUE
if("reset")
configuration["frequency"] = NTOS_SIGNALER_DEFAULT_FREQUENCY
configuration["code"] = NTOS_SIGNALER_DEFAULT_CODE
if(channel_index <= NTOS_SIGNALER_RECEIVE_CHANNELS)
configuration["receiving_enabled"] = FALSE
configuration["last_received_code"] = null
configuration["last_received_time"] = null
sync_receive_radios()
. = TRUE
if("send")
if(channel_index > NTOS_SIGNALER_RECEIVE_CHANNELS)
send_signal(channel_index, usr)
. = TRUE
/datum/computer_file/program/signaler/proc/ensure_signal_configurations()
for(var/i = 1 to min(length(signal_configurations), NTOS_SIGNALER_CHANNEL_COUNT))
var/list/configuration = signal_configurations[i]
if(!configuration)
continue
if(isnull(configuration["receiving_enabled"]))
configuration["receiving_enabled"] = FALSE
for(var/i = length(signal_configurations) + 1 to NTOS_SIGNALER_CHANNEL_COUNT)
signal_configurations += list(list(
"label" = default_label(i),
"frequency" = NTOS_SIGNALER_DEFAULT_FREQUENCY,
"code" = NTOS_SIGNALER_DEFAULT_CODE,
"receiving_enabled" = FALSE,
"last_received_code" = null,
"last_received_time" = null
))
/datum/computer_file/program/signaler/proc/default_label(var/channel_index)
if(channel_index <= NTOS_SIGNALER_RECEIVE_CHANNELS)
return "Receiver [channel_index]"
return "Signal [channel_index - NTOS_SIGNALER_RECEIVE_CHANNELS]"
/datum/computer_file/program/signaler/proc/clean_frequency(var/frequency)
if(!isnum(frequency))
frequency = NTOS_SIGNALER_DEFAULT_FREQUENCY
frequency = sanitize_frequency(frequency, RADIO_LOW_FREQ, RADIO_HIGH_FREQ)
if(frequency > RADIO_HIGH_FREQ)
frequency = RADIO_HIGH_FREQ - 1
return frequency
/datum/computer_file/program/signaler/proc/get_signaler_card()
if(!computer?.network_card)
return null
var/obj/item/computer_hardware/network_card/network_card = computer.network_card
if(!network_card.enabled || !network_card.check_functionality() || !network_card.sradio)
return null
return network_card
/datum/computer_file/program/signaler/proc/get_hardware_status()
if(!computer?.network_card)
return "No network card detected."
var/obj/item/computer_hardware/network_card/network_card = computer.network_card
if(!network_card.sradio)
return "Installed network card does not support signaling."
if(!network_card.enabled)
return "Signaler network card is disabled."
if(!network_card.check_functionality())
return "Signaler network card is damaged."
return "Ready."
/datum/computer_file/program/signaler/proc/sync_receive_radios()
var/obj/item/computer_hardware/network_card/network_card = get_signaler_card()
if(!network_card)
return FALSE
for(var/i = 1 to NTOS_SIGNALER_RECEIVE_CHANNELS)
var/obj/item/integrated_signaler/signal/radio = network_card.get_signaler_radio(i)
var/list/configuration = signal_configurations[i]
if(radio && configuration)
if(configuration["receiving_enabled"])
radio.set_frequency(configuration["frequency"])
radio.set_listening(TRUE)
else
radio.set_listening(FALSE)
return TRUE
/datum/computer_file/program/signaler/proc/send_signal(var/channel_index, var/mob/user)
var/obj/item/computer_hardware/network_card/network_card = get_signaler_card()
if(!network_card)
computer?.output_error(FONT_SMALL(SPAN_WARNING("Hardware Error - Signaler network card unavailable.")))
return FALSE
var/list/configuration = signal_configurations[channel_index]
if(!configuration)
return FALSE
var/obj/item/integrated_signaler/signal/radio = network_card.get_signaler_radio(0)
if(!radio)
return FALSE
var/old_frequency = radio.frequency
var/old_code = radio.code
radio.set_frequency(configuration["frequency"])
radio.code = configuration["code"]
var/sent = radio.send_signal("ACTIVATE", user)
radio.code = old_code
radio.set_frequency(old_frequency)
if(sent)
computer?.output_notice("Signal sent on [format_frequency(configuration["frequency"])]/[configuration["code"]].", 0)
else
computer?.output_error(FONT_SMALL(SPAN_WARNING("Unable to send signal. The transmitter may be cooling down or jammed.")))
return sent
/datum/computer_file/program/signaler/proc/receive_signal(var/channel_index, var/datum/signal/signal)
if(channel_index < 1 || channel_index > NTOS_SIGNALER_RECEIVE_CHANNELS)
return FALSE
ensure_signal_configurations()
var/list/configuration = signal_configurations[channel_index]
if(!configuration)
return FALSE
if(!configuration["receiving_enabled"])
return FALSE
configuration["last_received_code"] = signal.encryption
configuration["last_received_time"] = worldtime2text()
if(computer)
var/received_message = "Signal received on [configuration["label"]]: [isnull(signal.encryption) ? "no code" : signal.encryption]."
if(silent)
computer.output_notice(received_message, 0)
else
playsound(get_turf(computer), 'sound/machines/twobeep.ogg', 20, 1)
computer.audible_message("[icon2html(computer, viewers(1, get_turf(computer)))] [computer] beeps, \"[received_message]\"")
SStgui.update_uis(src)
return TRUE
#undef NTOS_SIGNALER_CHANNEL_COUNT
#undef NTOS_SIGNALER_RECEIVE_CHANNELS
#undef NTOS_SIGNALER_DEFAULT_FREQUENCY
#undef NTOS_SIGNALER_DEFAULT_CODE
#undef NTOS_SIGNALER_MAX_LABEL_LENGTH
@@ -13,6 +13,8 @@ GLOBAL_VAR_INIT(ntnet_card_uid, 1)
var/long_range = FALSE
var/ethernet = FALSE // Hard-wired, therefore always on, ignores NTNet wireless checks.
var/obj/item/integrated_signaler/signal/sradio = FALSE // integrated signaler - not present on basic model.
var/obj/item/integrated_signaler/signal/secondary_sradio = FALSE // second receiver for NTOS signaler software.
var/obj/item/integrated_signaler/signal/transmit_sradio = FALSE // transmitter for NTOS signaler software.
malfunction_probability = 1
/obj/item/computer_hardware/network_card/diagnostics(mob/user)
@@ -41,7 +43,7 @@ GLOBAL_VAR_INIT(ntnet_card_uid, 1)
/obj/item/computer_hardware/network_card/signaler/Initialize()
. = ..()
sradio = new /obj/item/integrated_signaler/signal(src)
initialize_signaler_radios()
/obj/item/computer_hardware/network_card/advanced
name = "advanced NTNet network card"
@@ -54,7 +56,7 @@ GLOBAL_VAR_INIT(ntnet_card_uid, 1)
/obj/item/computer_hardware/network_card/advanced/Initialize()
. = ..()
sradio = new /obj/item/integrated_signaler/signal(src)
initialize_signaler_radios()
/obj/item/computer_hardware/network_card/wired
name = "wired NTNet network card"
@@ -67,7 +69,46 @@ GLOBAL_VAR_INIT(ntnet_card_uid, 1)
/obj/item/computer_hardware/network_card/wired/Initialize()
. = ..()
sradio = new /obj/item/integrated_signaler/signal(src)
initialize_signaler_radios()
/obj/item/computer_hardware/network_card/proc/initialize_signaler_radios()
if(!sradio)
sradio = new /obj/item/integrated_signaler/signal/network_card(src)
sradio.set_listening(FALSE)
if(!secondary_sradio)
secondary_sradio = new /obj/item/integrated_signaler/signal/network_card(src)
secondary_sradio.set_listening(FALSE)
if(!transmit_sradio)
transmit_sradio = new /obj/item/integrated_signaler/signal/network_card(src)
transmit_sradio.set_listening(FALSE)
/obj/item/computer_hardware/network_card/proc/get_signaler_radio(var/channel = 1)
if(channel == 0)
return transmit_sradio
if(channel == 2)
return secondary_sradio
return sradio
/obj/item/computer_hardware/network_card/proc/receive_signaler_signal(var/obj/item/integrated_signaler/signal/receiver, var/datum/signal/signal)
if(!parent_computer || !parent_computer.enabled || !enabled || !check_functionality())
return FALSE
var/channel
if(receiver == sradio)
channel = 1
else if(receiver == secondary_sradio)
channel = 2
else
return FALSE
if(istype(parent_computer.active_program, /datum/computer_file/program/signaler))
var/datum/computer_file/program/signaler/active_signaler = parent_computer.active_program
active_signaler.receive_signal(channel, signal)
for(var/datum/computer_file/program/signaler/signaler_program in parent_computer.idle_threads)
signaler_program.receive_signal(channel, signal)
return TRUE
// Returns a string identifier of this network card
/obj/item/computer_hardware/network_card/proc/get_network_tag()
@@ -106,6 +147,10 @@ GLOBAL_VAR_INIT(ntnet_card_uid, 1)
/obj/item/computer_hardware/network_card/Destroy()
if(sradio)
QDEL_NULL(sradio)
if(secondary_sradio)
QDEL_NULL(secondary_sradio)
if(transmit_sradio)
QDEL_NULL(transmit_sradio)
if(parent_computer?.network_card == src)
parent_computer.network_card = null
parent_computer = null
+2
View File
@@ -175,6 +175,8 @@ ABSTRACT_TYPE(/obj/structure/machinery/power/apc)
/// If we're actually able to charge
var/charge_mode = CHARGE_MODE_CHARGE
var/last_time = 1
/// For doing silly games with blowing out lights
var/light_explosion_safety = TRUE
/obj/structure/machinery/power/apc/mechanics_hints(mob/user, distance, is_adjacent)
. += ..()
+5 -1
View File
@@ -159,6 +159,7 @@
SPAN_NOTICE("You detach [rig] from \the [src]"))
rig.forceMove(get_turf(user))
rig.detached()
rig = null
overlays = new/list()
@@ -176,12 +177,15 @@
if (rig)
to_chat(user, SPAN_WARNING("There is another device in the way."))
return ..()
var/obj/item/assembly_holder/H = attacking_item
if(!H.secured)
to_chat(user, SPAN_WARNING("The assembly must be secured with a screwdriver."))
return TRUE
user.visible_message("[user] begins rigging [attacking_item] to \the [src].", "You begin rigging [attacking_item] to \the [src]")
if(do_after(user, 20))
user.visible_message(SPAN_NOTICE("[user] rigs [attacking_item] to \the [src]."),
SPAN_NOTICE("You rig [attacking_item] to \the [src]"))
var/obj/item/assembly_holder/H = attacking_item
if (istype(H.a_left,/obj/item/assembly/igniter) || istype(H.a_right,/obj/item/assembly/igniter))
message_admins("[key_name_admin(user)] rigged fueltank at [loc.loc.name] ([loc.x],[loc.y],[loc.z]) for explosion. (<A href='byond://?_src_=holder;adminplayerobservecoodjump=1;X=[loc.x];Y=[loc.y];Z=[loc.z]'>JMP</a>)")
log_game("[key_name(user)] rigged fueltank at [loc.loc.name] ([loc.x],[loc.y],[loc.z]) for explosion.")
+13
View File
@@ -0,0 +1,13 @@
author: Batrachophrenoboocosmomachia
delete-after: True
changes:
- qol: "Eligible non-random wire panels now use relevant domain skills where applicable: engineering skills, robotics, and xenobiology implemented, instead of only Electrical Engineering 4."
- rscadd: "Adds new wire to APCs that blows out all the lights if you pulse it with the safeties off."
- rscadd: "Adds the Signal Manager NTOS app for signaler-capable network cards. Pre-installed in Engineering, Research, and Machinist app presets."
- rscadd: "Engineering, Research, and Machinist character modular computers now come with signaler-capable network cards."
- qol: "Increases remote signaler availability in EngiTech vendors and made assembly items tiny."
- bugfix: "Fixes air alarm wiring to use the correct wire datum path."
- bugfix: "Fixes canister-attached signaler regression."
- bugfix: "Fixes assembly holders failing silently when unsecured to fuel tanks; fuel tanks now reject unsecured assembly holders."
- bugfix: "Fixes runtime when an activation deletes or detaches one of the paired assemblies."
- qol: "Reduces the Fusion Codex program size."
@@ -0,0 +1,187 @@
import {
Box,
Button,
LabeledList,
NumberInput,
Section,
Table,
} from 'tgui-core/components';
import { toFixed } from 'tgui-core/math';
import { useBackend } from '../backend';
import { NtosWindow } from '../layouts';
type SignalerChannel = {
index: number;
label: string;
frequency: number;
code: number;
receiving: boolean;
receiving_enabled: boolean;
last_received_code: number | null;
last_received_time: string | null;
};
type Data = {
hardware_ready: boolean;
hardware_status: string;
min_frequency: number;
max_frequency: number;
receive_channels: number;
silent: boolean;
channels: SignalerChannel[];
};
export const NTOSSignaler = () => {
const { act, data } = useBackend<Data>();
const { channels = [] } = data;
const rows: SignalerChannel[][] = [];
for (let i = 0; i < channels.length; i += 2) {
rows.push(channels.slice(i, i + 2));
}
return (
<NtosWindow width={820} height={720} theme="ntos">
<NtosWindow.Content scrollable>
<Section
title="Signal Manager"
buttons={
<Button
icon={data.silent ? 'volume-mute' : 'volume-up'}
content={data.silent ? 'Sound Off' : 'Sound On'}
selected={!data.silent}
onClick={() => act('toggle_silent')}
/>
}
>
<Box color={data.hardware_ready ? 'good' : 'bad'}>
{data.hardware_status}
</Box>
</Section>
<Table>
{rows.map((row) => (
<Table.Row key={row[0]?.index}>
{row.map((channel) => (
<Table.Cell key={channel.index} width="50%" verticalAlign="top">
<SignalSlot channel={channel} data={data} />
</Table.Cell>
))}
</Table.Row>
))}
</Table>
</NtosWindow.Content>
</NtosWindow>
);
};
const SignalSlot = (props: { channel: SignalerChannel; data: Data }) => {
const { act } = useBackend<Data>();
const { channel, data } = props;
const channelNumber = channel.receiving
? channel.index
: channel.index - data.receive_channels;
return (
<Section
title={channel.label}
buttons={
<>
<Button
icon="pen"
tooltip={`Rename ${channel.receiving ? 'receiver' : 'signaler'} ${channelNumber}`}
onClick={() => act('rename', { index: channel.index })}
/>
<Button
icon="sync"
tooltip="Reset"
color="yellow"
onClick={() => act('reset', { index: channel.index })}
/>
</>
}
>
<LabeledList>
<LabeledList.Item label="Frequency">
<NumberInput
animated
tickWhileDragging
unit="kHz"
minValue={data.min_frequency / 10}
maxValue={data.max_frequency / 10}
value={channel.frequency / 10}
step={0.1}
stepPixelSize={6}
format={(value) => toFixed(value, 1)}
width="80px"
onChange={(value) =>
act('frequency', {
index: channel.index,
frequency: value * 10,
})
}
/>
</LabeledList.Item>
{channel.receiving ? (
<>
<LabeledList.Item label="Receiver">
<Button
fluid
icon={channel.receiving_enabled ? 'toggle-on' : 'toggle-off'}
selected={channel.receiving_enabled}
color={channel.receiving_enabled ? 'good' : 'default'}
content={channel.receiving_enabled ? 'Enabled' : 'Disabled'}
onClick={() =>
act('toggle_receive', {
index: channel.index,
})
}
/>
</LabeledList.Item>
<LabeledList.Item label="Last Code">
{channel.receiving_enabled
? (channel.last_received_code ?? 'None')
: 'Disabled'}
</LabeledList.Item>
<LabeledList.Item label="Last Signal">
{channel.receiving_enabled
? channel.last_received_time || 'None'
: 'Disabled'}
</LabeledList.Item>
</>
) : (
<>
<LabeledList.Item label="Code">
<NumberInput
animated
tickWhileDragging
minValue={1}
maxValue={100}
value={channel.code}
step={1}
stepPixelSize={6}
width="80px"
onChange={(value) =>
act('code', {
index: channel.index,
code: value,
})
}
/>
</LabeledList.Item>
<LabeledList.Item label="Send">
<Button
fluid
icon="wifi"
disabled={!data.hardware_ready}
color="good"
content="Send Signal"
onClick={() => act('send', { index: channel.index })}
/>
</LabeledList.Item>
</>
)}
</LabeledList>
</Section>
);
};
+6 -6
View File
@@ -32,15 +32,15 @@ export const Signaler = (props) => {
<LabeledList>
<LabeledList.Item label="Frequency">
<NumberInput
minValue={12}
maxValue={16}
minValue={120}
maxValue={160}
unit="kHz"
value={data.frequency / 10}
step={0.2}
value={data.frequency}
step={0.1}
stepPixelSize={6}
format={(value) => toFixed(value, 2)}
format={(value) => toFixed(value, 1)}
width="80px"
onChange={(value) => act('freq', { freq: value * 10 })}
onChange={(value) => act('freq', { freq: value })}
/>
</LabeledList.Item>
<LabeledList.Item label="Code">