diff --git a/_maps/map_files/rift/rift-01-underground3.dmm b/_maps/map_files/rift/rift-01-underground3.dmm
index c6410b6e242..5c1d19f00d7 100644
--- a/_maps/map_files/rift/rift-01-underground3.dmm
+++ b/_maps/map_files/rift/rift-01-underground3.dmm
@@ -585,6 +585,15 @@
},
/turf/simulated/floor/tiled/steel_grid,
/area/quartermaster/mining_airlock)
+"Hh" = (
+/obj/machinery/bioscan_antenna/permanent{
+ network_key_obfuscated = "rift_bioscanners"
+ },
+/obj/machinery/bioscan_antenna/permanent{
+ network_key_obfuscated = "rift_bioscanners"
+ },
+/turf/simulated/floor/plating,
+/area/quartermaster/mining_airlock)
"Ho" = (
/obj/effect/overlay/snow/floor/grav_surround{
dir = 1
@@ -20763,7 +20772,7 @@ KS
KS
aW
QE
-vr
+Hh
lP
Tr
pw
diff --git a/_maps/map_files/rift/rift-02-underground2.dmm b/_maps/map_files/rift/rift-02-underground2.dmm
index f6435408c55..cffb269e5b0 100644
--- a/_maps/map_files/rift/rift-02-underground2.dmm
+++ b/_maps/map_files/rift/rift-02-underground2.dmm
@@ -5536,6 +5536,9 @@
/obj/structure/railing{
dir = 4
},
+/obj/machinery/bioscan_antenna/permanent{
+ network_key_obfuscated = "rift_bioscanners"
+ },
/turf/simulated/floor/plating,
/area/maintenance/substation/atmos)
"pW" = (
diff --git a/_maps/map_files/rift/rift-03-underground1.dmm b/_maps/map_files/rift/rift-03-underground1.dmm
index c3155a968ee..a93642c4a25 100644
--- a/_maps/map_files/rift/rift-03-underground1.dmm
+++ b/_maps/map_files/rift/rift-03-underground1.dmm
@@ -3271,6 +3271,9 @@
/obj/effect/floor_decal/industrial/warning{
dir = 1
},
+/obj/machinery/bioscan_antenna/permanent{
+ network_key_obfuscated = "rift_bioscanners"
+ },
/turf/simulated/floor/plating,
/area/maintenance/public_bunker)
"kt" = (
diff --git a/_maps/map_files/rift/rift-04-surface1.dmm b/_maps/map_files/rift/rift-04-surface1.dmm
index e1122c66bb7..c08aaa61132 100644
--- a/_maps/map_files/rift/rift-04-surface1.dmm
+++ b/_maps/map_files/rift/rift-04-surface1.dmm
@@ -20923,6 +20923,9 @@
},
/obj/effect/floor_decal/industrial/warning,
/obj/structure/railing,
+/obj/machinery/bioscan_antenna/permanent{
+ network_key_obfuscated = "rift_bioscanners"
+ },
/turf/simulated/floor/tiled/techfloor,
/area/maintenance/security/lower)
"nzE" = (
diff --git a/_maps/map_files/rift/rift-05-surface2.dmm b/_maps/map_files/rift/rift-05-surface2.dmm
index b1f06b97807..b138e62d6c7 100644
--- a/_maps/map_files/rift/rift-05-surface2.dmm
+++ b/_maps/map_files/rift/rift-05-surface2.dmm
@@ -12417,6 +12417,9 @@
/obj/structure/cable/green{
icon_state = "2-4"
},
+/obj/machinery/bioscan_antenna/permanent{
+ network_key_obfuscated = "rift_bioscanners"
+ },
/turf/simulated/floor/plating,
/area/maintenance/substation/surface_two)
"jhU" = (
diff --git a/_maps/map_files/rift/rift-06-surface3.dmm b/_maps/map_files/rift/rift-06-surface3.dmm
index 23e89879557..3edd5d3987a 100644
--- a/_maps/map_files/rift/rift-06-surface3.dmm
+++ b/_maps/map_files/rift/rift-06-surface3.dmm
@@ -7534,6 +7534,9 @@
/obj/structure/sign/deck3{
pixel_y = 32
},
+/obj/machinery/bioscan_antenna/permanent{
+ network_key_obfuscated = "rift_bioscanners"
+ },
/turf/simulated/floor/tiled/techfloor,
/area/rift/turbolift/maint)
"aoO" = (
@@ -19897,6 +19900,9 @@
/obj/effect/floor_decal/corner/purple{
dir = 5
},
+/obj/machinery/computer/bioscan{
+ network_key_obfuscated = "rift_bioscanners"
+ },
/turf/simulated/floor/tiled/dark,
/area/bridge)
"aOK" = (
diff --git a/_maps/map_files/rift/rift-07-west_base.dmm b/_maps/map_files/rift/rift-07-west_base.dmm
index 1e0b9d09ac0..ebdc97cf71a 100644
--- a/_maps/map_files/rift/rift-07-west_base.dmm
+++ b/_maps/map_files/rift/rift-07-west_base.dmm
@@ -907,6 +907,9 @@
/obj/machinery/light/small{
dir = 8
},
+/obj/machinery/bioscan_antenna/permanent{
+ network_key_obfuscated = "rift_bioscanners"
+ },
/turf/simulated/floor/tiled/steel_dirty/lythios43c/indoors,
/area/rift/surfacebase/outside/west_base)
"SE" = (
diff --git a/_maps/map_files/rift/rift-08-west_deep.dmm b/_maps/map_files/rift/rift-08-west_deep.dmm
index 6f7e3810e07..19c0e5e9fc4 100644
--- a/_maps/map_files/rift/rift-08-west_deep.dmm
+++ b/_maps/map_files/rift/rift-08-west_deep.dmm
@@ -5664,6 +5664,9 @@
/obj/machinery/light/small{
dir = 8
},
+/obj/machinery/bioscan_antenna/permanent{
+ network_key_obfuscated = "rift_bioscanners"
+ },
/turf/simulated/floor/plating,
/area/outpost/mining_main/outpost/substation)
diff --git a/_maps/map_files/rift/rift-09-west_caves.dmm b/_maps/map_files/rift/rift-09-west_caves.dmm
index 87aa6909ed0..8778c828f93 100644
--- a/_maps/map_files/rift/rift-09-west_caves.dmm
+++ b/_maps/map_files/rift/rift-09-west_caves.dmm
@@ -916,6 +916,9 @@
/turf/simulated/floor/tiled/steel_dirty/lythios43c/indoors,
/area/rift/facility/interior/medical)
"kF" = (
+/obj/machinery/bioscan_antenna/permanent{
+ network_key_obfuscated = "rift_bioscanners"
+ },
/turf/simulated/floor,
/area/outpost/mining_main/outpost/substation)
"kJ" = (
diff --git a/_maps/map_files/rift/rift-10-west_plains.dmm b/_maps/map_files/rift/rift-10-west_plains.dmm
index 1f1b45c4d8e..4f9a873c1d2 100644
--- a/_maps/map_files/rift/rift-10-west_plains.dmm
+++ b/_maps/map_files/rift/rift-10-west_plains.dmm
@@ -6869,6 +6869,9 @@
/obj/structure/cable{
icon_state = "8-10"
},
+/obj/machinery/bioscan_antenna/permanent{
+ network_key_obfuscated = "rift_bioscanners"
+ },
/turf/simulated/floor,
/area/outpost/mining_main/outpost/substation)
"FP" = (
diff --git a/citadel.dme b/citadel.dme
index e008aa2e2ec..424cf47fdcd 100644
--- a/citadel.dme
+++ b/citadel.dme
@@ -809,6 +809,7 @@
#include "code\game\atoms\atoms_vv.dm"
#include "code\game\atoms\buckling.dm"
#include "code\game\atoms\defense.dm"
+#include "code\game\atoms\say.dm"
#include "code\game\dna\dna2.dm"
#include "code\game\dna\dna2_domutcheck.dm"
#include "code\game\dna\dna2_helpers.dm"
@@ -1112,6 +1113,7 @@
#include "code\game\machinery\computer\arcade\claw_machine.dm"
#include "code\game\machinery\computer\arcade\orion.dm"
#include "code\game\machinery\computer\arcade\orion_event.dm"
+#include "code\game\machinery\computer\misc\bioscan.dm"
#include "code\game\machinery\doors\airlock.dm"
#include "code\game\machinery\doors\airlock_control.dm"
#include "code\game\machinery\doors\airlock_electronics.dm"
@@ -1139,6 +1141,7 @@
#include "code\game\machinery\embedded_controller\embedded_program_base.dm"
#include "code\game\machinery\embedded_controller\mapping_helpers.dm"
#include "code\game\machinery\embedded_controller\simple_docking_controller.dm"
+#include "code\game\machinery\misc\bioscan_antenna.dm"
#include "code\game\machinery\pipe\construction.dm"
#include "code\game\machinery\pipe\pipe_dispenser.dm"
#include "code\game\machinery\pipe\pipe_recipes.dm"
@@ -1147,11 +1150,17 @@
#include "code\game\machinery\suit_storage\suit_storage_massive.dm"
#include "code\game\machinery\suit_storage\suit_storage_prepared.dm"
#include "code\game\machinery\suit_storage\suit_storage_unit.dm"
+#include "code\game\machinery\telecomms\_telecomms.dm"
+#include "code\game\machinery\telecomms\allinone.dm"
#include "code\game\machinery\telecomms\broadcaster.dm"
+#include "code\game\machinery\telecomms\bus.dm"
+#include "code\game\machinery\telecomms\hub.dm"
#include "code\game\machinery\telecomms\logbrowser.dm"
-#include "code\game\machinery\telecomms\machine_interactions.dm"
#include "code\game\machinery\telecomms\presets.dm"
-#include "code\game\machinery\telecomms\telecomunications.dm"
+#include "code\game\machinery\telecomms\processor.dm"
+#include "code\game\machinery\telecomms\receiver.dm"
+#include "code\game\machinery\telecomms\relay.dm"
+#include "code\game\machinery\telecomms\server.dm"
#include "code\game\machinery\telecomms\telemonitor.dm"
#include "code\game\machinery\teleporter\console.dm"
#include "code\game\machinery\teleporter\pad.dm"
@@ -1358,19 +1367,23 @@
#include "code\game\objects\items\circuitboards\frame.dm"
#include "code\game\objects\items\circuitboards\mecha.dm"
#include "code\game\objects\items\circuitboards\other.dm"
+#include "code\game\objects\items\circuitboards\computer\_computer.dm"
#include "code\game\objects\items\circuitboards\computer\air_management.dm"
#include "code\game\objects\items\circuitboards\computer\camera_monitor.dm"
#include "code\game\objects\items\circuitboards\computer\computer.dm"
+#include "code\game\objects\items\circuitboards\computer\misc.dm"
#include "code\game\objects\items\circuitboards\computer\research.dm"
#include "code\game\objects\items\circuitboards\computer\shuttle.dm"
#include "code\game\objects\items\circuitboards\computer\supply.dm"
#include "code\game\objects\items\circuitboards\computer\telecomms.dm"
+#include "code\game\objects\items\circuitboards\machinery\_machine.dm"
#include "code\game\objects\items\circuitboards\machinery\biogenerator.dm"
#include "code\game\objects\items\circuitboards\machinery\cloning.dm"
#include "code\game\objects\items\circuitboards\machinery\engineering.dm"
#include "code\game\objects\items\circuitboards\machinery\jukebox.dm"
#include "code\game\objects\items\circuitboards\machinery\mech_recharger.dm"
#include "code\game\objects\items\circuitboards\machinery\mining_drill.dm"
+#include "code\game\objects\items\circuitboards\machinery\misc.dm"
#include "code\game\objects\items\circuitboards\machinery\pacman.dm"
#include "code\game\objects\items\circuitboards\machinery\papershredder.dm"
#include "code\game\objects\items\circuitboards\machinery\power.dm"
@@ -3925,9 +3938,14 @@
#include "code\modules\research\designs\xenoarch_toys.dm"
#include "code\modules\research\designs\xenobio_toys.dm"
#include "code\modules\research\designs\circuits\ai_modules.dm"
+#include "code\modules\research\designs\circuits\circuit.dm"
#include "code\modules\research\designs\circuits\circuits.dm"
#include "code\modules\research\designs\circuits\circuits_vr.dm"
+#include "code\modules\research\designs\circuits\computer.dm"
#include "code\modules\research\designs\circuits\disks.dm"
+#include "code\modules\research\designs\circuits\machine.dm"
+#include "code\modules\research\designs\circuits\computers\misc.dm"
+#include "code\modules\research\designs\circuits\machines\misc.dm"
#include "code\modules\resleeving\circuitboards.dm"
#include "code\modules\resleeving\computers.dm"
#include "code\modules\resleeving\documents.dm"
diff --git a/code/__DEFINES/_cooldowns.dm b/code/__DEFINES/_cooldowns.dm
index 1f844d0fbcf..e732f82371b 100644
--- a/code/__DEFINES/_cooldowns.dm
+++ b/code/__DEFINES/_cooldowns.dm
@@ -24,6 +24,7 @@
// INDEXES FOR TIMER COOLDOWNS - Must be unique!
#define CD_INDEX_SONAR_PULSE "sonar_pulse"
+#define CD_INDEX_SONAR_NOISE "sonar_noise"
#define CD_INDEX_POWER_DRAIN_WARNING "power_drain_warning"
#define CD_INDEX_TAPE_TRANSLATION "tape_translation"
#define CD_INDEX_TAPE_PRINT "tape_print"
diff --git a/code/__DEFINES/misc/message_ranges.dm b/code/__DEFINES/misc/message_ranges.dm
index 321962729b3..434af54c848 100644
--- a/code/__DEFINES/misc/message_ranges.dm
+++ b/code/__DEFINES/misc/message_ranges.dm
@@ -4,3 +4,8 @@
#define MESSAGE_RANGE_COMBAT_SUBTLE 3
/// message range for loud combat actions
#define MESSAGE_RANGE_COMBAT_LOUD 7
+
+/// message range for most construction / deconstruction actions
+#define MESSAGE_RANGE_CONSTRUCTION 5
+/// message range for most machine / structure configuration actions
+#define MESSAGE_RANGE_CONFIGURATION 3
diff --git a/code/controllers/subsystem/mapping/obfuscation.dm b/code/controllers/subsystem/mapping/obfuscation.dm
index 3cdea674efa..ab499d073f3 100644
--- a/code/controllers/subsystem/mapping/obfuscation.dm
+++ b/code/controllers/subsystem/mapping/obfuscation.dm
@@ -6,11 +6,14 @@
/datum/controller/subsystem/mapping
/// "secret" key
var/obfuscation_secret
+ /// subtly obfuscated id lookup
+ var/list/obfuscation_cache
/datum/controller/subsystem/mapping/PreInit(recovering)
. = ..()
if(!obfuscation_secret)
obfuscation_secret = md5(GUID())
+ obfuscation_cache = recovering? ((istype(SSmapping) && SSmapping.obfuscation_cache) || list()) : list()
/**
* Generates an obfuscated but constant ID for an original ID for cases where you don't want players codediving for an ID.
@@ -19,7 +22,20 @@
*
* Both original and id_type are CASE INSENSITIVE.
*/
-/datum/controller/subsystem/mapping/proc/get_obfuscated_id(original, id_type = "GENERAL")
+/datum/controller/subsystem/mapping/proc/get_obfuscated_id(original, id_type = "$any")
if(!original)
return // no.
return md5("[obfuscation_secret]%[lowertext(original)]%[lowertext(id_type)]")
+
+/**
+ * more expensive obfuscation: just 4 random hexadecimals at the end.
+ * each given id should output the same resulting id.
+ * do not abuse this, we do cache this in memory.
+ *
+ * use in cases where you want a player-readable id that can be recovered
+ */
+/datum/controller/subsystem/mapping/proc/subtly_obfuscated_id(original, id_type = "$any")
+ if(isnull(obfuscation_cache[id_type]?[original]))
+ LAZYINITLIST(obfuscation_cache[id_type])
+ obfuscation_cache[id_type][original] = "[original]_[num2text(rand(0, (16 ** 4) - 1), 4, 16)]"
+ return obfuscation_cache[id_type][original]
diff --git a/code/game/atoms/atoms.dm b/code/game/atoms/atoms.dm
index aa1e15b548b..163e05fe319 100644
--- a/code/game/atoms/atoms.dm
+++ b/code/game/atoms/atoms.dm
@@ -805,46 +805,6 @@
/atom/proc/GenerateTag()
return
-// todo: refactor this shit to be unified saycode, christ
-/atom/proc/atom_say(message, datum/language/L)
- if(!message)
- return
- var/list/speech_bubble_hearers = list()
- var/no_runechat = FALSE
- for(var/mob/M in get_hearers_in_view(MESSAGE_RANGE_COMBAT_LOUD, src))
- var/processed = message
- if(L && !(L.name in M.languages))
- processed = L.scramble(message)
- no_runechat = TRUE
- M.show_message("[src] [L?.speech_verb || atom_say_verb], \"[processed]\"", 2, null, 1)
- if(M.client)
- speech_bubble_hearers += M.client
-
- if(length(speech_bubble_hearers) && !no_runechat)
- var/image/I = generate_speech_bubble(src, "[bubble_icon][say_test(message)]", FLY_LAYER)
- I.appearance_flags = APPEARANCE_UI_IGNORE_ALPHA
- INVOKE_ASYNC(GLOBAL_PROC, /.proc/flick_overlay, I, speech_bubble_hearers, 30)
- INVOKE_ASYNC(src, /atom/movable/proc/animate_chat, message, null, FALSE, speech_bubble_hearers, 30)
-
-/atom/proc/say_overhead(var/message, whispering, message_range = 7, var/datum/language/speaking = null, var/list/passed_hearing_list)
- var/list/speech_bubble_hearers = list()
- var/italics
- if(whispering)
- italics = TRUE
- for(var/mob/M in get_mobs_in_view(message_range, src))
- if(M.client)
- speech_bubble_hearers += M.client
- if(length(speech_bubble_hearers))
- INVOKE_ASYNC(src, /atom/movable/proc/animate_chat, message, speaking, italics, speech_bubble_hearers, 30)
-
-/proc/generate_speech_bubble(var/bubble_loc, var/speech_state, var/set_layer = FLOAT_LAYER)
- var/image/I = image('icons/mob/talk_vr.dmi', bubble_loc, speech_state, set_layer)
- I.appearance_flags |= (RESET_COLOR|PIXEL_SCALE)
- return I
-
-/atom/proc/speech_bubble(bubble_state = "", bubble_loc = src, list/bubble_recipients = list())
- return
-
//! Radiation
/**
* called when we're hit by a radiation wave
diff --git a/code/game/atoms/atoms_appearance.dm b/code/game/atoms/atoms_appearance.dm
index a31524ce92d..71e9db05076 100644
--- a/code/game/atoms/atoms_appearance.dm
+++ b/code/game/atoms/atoms_appearance.dm
@@ -59,12 +59,21 @@
. |= SEND_SIGNAL(src, COMSIG_ATOM_UPDATED_ICON, updates, .)
-/// Updates the icon state of the atom.
+/**
+ * Updates the icon state of the atom.
+ *
+ * Make sure to call return ..() last, so the comsig can force updates.
+ */
/atom/proc/update_icon_state()
SHOULD_CALL_PARENT(TRUE)
return SEND_SIGNAL(src, COMSIG_ATOM_UPDATE_ICON_STATE)
-/// Updates the overlays of the atom.
+
+/**
+ * Updates the icon state of the atom.
+ *
+ * Make sure to call return ..() last, so the comsig can force updates.
+ */
/atom/proc/update_overlays()
SHOULD_CALL_PARENT(TRUE)
. = list()
diff --git a/code/game/atoms/say.dm b/code/game/atoms/say.dm
new file mode 100644
index 00000000000..c132226d563
--- /dev/null
+++ b/code/game/atoms/say.dm
@@ -0,0 +1,42 @@
+/**
+ * shim until saycode rewrite
+ */
+// todo: refactor this shit to be unified saycode, christ
+/atom/proc/atom_say(message, datum/language/L)
+ if(!message)
+ return
+ var/list/speech_bubble_hearers = list()
+ var/no_runechat = FALSE
+ for(var/mob/M in get_hearers_in_view(MESSAGE_RANGE_COMBAT_LOUD, src))
+ var/processed = message
+ if(L && !(L.name in M.languages))
+ processed = L.scramble(message)
+ no_runechat = TRUE
+ M.show_message("[src] [L?.speech_verb || atom_say_verb], \"[processed]\"", 2, null, 1)
+ if(M.client)
+ speech_bubble_hearers += M.client
+
+ if(length(speech_bubble_hearers) && !no_runechat)
+ var/image/I = generate_speech_bubble(src, "[bubble_icon][say_test(message)]", FLY_LAYER)
+ I.appearance_flags = APPEARANCE_UI_IGNORE_ALPHA
+ INVOKE_ASYNC(GLOBAL_PROC, /.proc/flick_overlay, I, speech_bubble_hearers, 30)
+ INVOKE_ASYNC(src, /atom/movable/proc/animate_chat, message, null, FALSE, speech_bubble_hearers, 30)
+
+/atom/proc/say_overhead(var/message, whispering, message_range = 7, var/datum/language/speaking = null, var/list/passed_hearing_list)
+ var/list/speech_bubble_hearers = list()
+ var/italics
+ if(whispering)
+ italics = TRUE
+ for(var/mob/M in get_mobs_in_view(message_range, src))
+ if(M.client)
+ speech_bubble_hearers += M.client
+ if(length(speech_bubble_hearers))
+ INVOKE_ASYNC(src, /atom/movable/proc/animate_chat, message, speaking, italics, speech_bubble_hearers, 30)
+
+/proc/generate_speech_bubble(var/bubble_loc, var/speech_state, var/set_layer = FLOAT_LAYER)
+ var/image/I = image('icons/mob/talk_vr.dmi', bubble_loc, speech_state, set_layer)
+ I.appearance_flags |= (RESET_COLOR|PIXEL_SCALE)
+ return I
+
+/atom/proc/speech_bubble(bubble_state = "", bubble_loc = src, list/bubble_recipients = list())
+ return
diff --git a/code/game/machinery/_machinery.dm b/code/game/machinery/_machinery.dm
index ac713a6382d..c63827c2a11 100644
--- a/code/game/machinery/_machinery.dm
+++ b/code/game/machinery/_machinery.dm
@@ -106,6 +106,15 @@
// todo: wow rad contents is a weird system
rad_flags = RAD_BLOCK_CONTENTS
+ //* Construction / Deconstruction
+ /// Can be constructed / deconstructed by players by default
+ // todo: proc for allow / disallow, refactor
+ var/allow_deconstruct = FALSE
+ /// Can be anchored / unanchored by players without deconstructing by default
+ // todo: proc for allow / disallow, refactor, unify with can_be_unanchored
+ var/allow_unanchor = FALSE
+
+ //* unsorted
var/machine_stat = 0
var/emagged = FALSE
/**
diff --git a/code/game/machinery/computer/misc/bioscan.dm b/code/game/machinery/computer/misc/bioscan.dm
new file mode 100644
index 00000000000..5cc4ccc4866
--- /dev/null
+++ b/code/game/machinery/computer/misc/bioscan.dm
@@ -0,0 +1,127 @@
+/obj/machinery/computer/bioscan
+ name = "Bioscan Control Console"
+ desc = "Used to pinpoint signatures, biological or otherwise, using a series of antennaes."
+ icon_keyboard = "tech_key"
+
+ /// network key
+ var/network_key
+ /// automatically generate an obfuscated key - used by mappers
+ var/network_key_obfuscated
+ /// buffer - quite literally just holds a copy of generated ui data
+ var/list/buffer
+ /// last scan at
+ var/last_scan
+ /// scan cooldown
+ var/scan_delay = 10 SECONDS
+
+/obj/machinery/computer/bioscan/Initialize(mapload)
+ . = ..()
+ if(network_key_obfuscated)
+ network_key = SSmapping.subtly_obfuscated_id(network_key_obfuscated, "bioscan_network")
+
+/obj/machinery/computer/bioscan/ui_interact(mob/user, datum/tgui/ui, datum/tgui/parent_ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "BioscanConsole")
+ ui.open()
+
+/obj/machinery/computer/bioscan/ui_static_data(mob/user)
+ . = ..()
+ .["scan"] = buffer
+ .["antennas"] = ui_antenna_data()
+
+/obj/machinery/computer/bioscan/proc/ui_antenna_data()
+ var/list/antennas = network_key && GLOB.bioscan_antenna_list[network_key]
+ . = list()
+ for(var/obj/machinery/bioscan_antenna/A as anything in antennas)
+ var/turf/T = get_turf(A)
+ if(!T)
+ continue
+ . += list(list(
+ "level" = SSmapping.level_id(get_z(A)),
+ "id" = "[A.id]",
+ "anchor" = A.anchored,
+ "name" = A.name,
+ "x" = T.x,
+ "y" = T.y,
+ ))
+
+/obj/machinery/computer/bioscan/ui_data(mob/user, datum/tgui/ui, datum/ui_state/state)
+ . = ..()
+ .["scan_ready"] = !on_cooldown()
+ .["network"] = network_key || ""
+
+/obj/machinery/computer/bioscan/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
+ . = ..()
+ switch(action)
+ if("scan")
+ if(!network_key)
+ visible_message(SPAN_WARNING("[icon2html(src, world)] flashes a message, \"No network set.\""))
+ return
+ if(on_cooldown())
+ visible_message(SPAN_WARNING("[icon2html(src, world)] flashes a message, \"Bioscan still on cooldown.\""))
+ return
+ last_scan = world.time
+ atom_say("Commencing signature scan and updating buffers...")
+ scan()
+ return TRUE
+ if("set_network")
+ var/what = params["network"] || null
+ set_network(what)
+ return TRUE
+
+/obj/machinery/computer/bioscan/proc/on_cooldown()
+ return (world.time - last_scan) <= scan_delay
+
+/obj/machinery/computer/bioscan/proc/set_network(key)
+ network_key = key
+ void_scan()
+
+/obj/machinery/computer/bioscan/proc/void_scan()
+ buffer = null
+ send_tgui_data_immediate(data = list("scan" = null))
+
+/obj/machinery/computer/bioscan/proc/scan()
+ var/list/new_data = list()
+ /// get relevant antennas
+ var/list/antennas = network_key && GLOB.bioscan_antenna_list[network_key]
+ if(!length(antennas))
+ // abort
+ void_scan()
+ return
+ /// build list of levels
+ var/list/indices = list()
+ for(var/obj/machinery/bioscan_antenna/A as anything in antennas)
+ indices["[A.z]"] = list()
+ /// get mobs
+ for(var/mob/M as anything in GLOB.mob_list)
+ if(!indices[num2text(M.z)])
+ continue
+ indices[num2text(M.z)] += M
+ /// process mobs
+ var/list/assembled = list()
+ for(var/z_str in indices)
+ var/list/gottem = list()
+ gottem["id"] = SSmapping.level_id(text2num(z_str))
+ var/mobs_all = 0
+ var/mobs_complex = 0
+ var/mobs_complex_alive = 0
+ var/mobs_complex_dead = 0
+ for(var/mob/M as anything in indices[z_str])
+ ++mobs_all
+ if(!isliving(M))
+ continue // don't care didn't ask
+ if(!issimple(M))
+ ++mobs_complex
+ if(IS_DEAD(M))
+ ++mobs_complex_dead
+ else
+ ++mobs_complex_alive
+ gottem["all"] = mobs_all
+ gottem["complex"] = mobs_complex
+ gottem["complex_alive"] = mobs_complex_alive
+ gottem["complex_dead"] = mobs_complex_dead
+ assembled[++assembled.len] = gottem
+ new_data["levels"] = assembled
+ buffer = new_data
+ send_tgui_data_immediate(data = list("scan" = buffer))
diff --git a/code/game/machinery/misc/bioscan_antenna.dm b/code/game/machinery/misc/bioscan_antenna.dm
new file mode 100644
index 00000000000..4bde23f4809
--- /dev/null
+++ b/code/game/machinery/misc/bioscan_antenna.dm
@@ -0,0 +1,122 @@
+GLOBAL_LIST_EMPTY(bioscan_antenna_list)
+/obj/machinery/bioscan_antenna
+ name = "Bioscan Antenna"
+ desc = "A fragile antenna used to locate nearby biosignatures."
+ allow_deconstruct = TRUE
+ allow_unanchor = TRUE
+ icon = 'icons/machinery/bioscan.dmi'
+ base_icon_state = "antenna"
+ icon_state = "antenna"
+
+ /// network key
+ var/network_key
+ /// automatically generate an obfuscated key - used by mappers
+ var/network_key_obfuscated
+ /// can be reprogrammed
+ var/network_mutable = TRUE
+ /// id
+ var/id
+ /// next id
+ var/static/id_next = 0
+
+ // todo: scaling levels of how accurate they are
+ // todo: multiz / world-sector functionality; for now one must be there for each zlevel
+
+/obj/machinery/bioscan_antenna/Initialize(mapload)
+ . = ..()
+ id = "[++id_next]"
+ if(network_key_obfuscated)
+ network_key = SSmapping.subtly_obfuscated_id(network_key_obfuscated, "bioscan_network")
+ change_network(network_key)
+
+/obj/machinery/bioscan_antenna/Destroy()
+ change_network(null)
+ return ..()
+
+/obj/machinery/bioscan_antenna/crowbar_act(obj/item/I, mob/user, flags, hint)
+ if(!allow_deconstruct || !panel_open)
+ return ..()
+ if(default_deconstruction_crowbar(user, I))
+ user.visible_message(SPAN_NOTICE("[user] dismantles [src]."), range = MESSAGE_RANGE_CONSTRUCTION)
+ return TRUE
+ return ..()
+
+/obj/machinery/bioscan_antenna/screwdriver_act(obj/item/I, mob/user, flags, hint)
+ if(!allow_deconstruct)
+ return ..()
+ if(default_deconstruction_screwdriver(user, I))
+ user.visible_message(SPAN_NOTICE("[user] [panel_open? "opens" : "closes"] the panel on [src]."), range = MESSAGE_RANGE_CONSTRUCTION)
+ return TRUE
+ return ..()
+
+/obj/machinery/bioscan_antenna/wrench_act(obj/item/I, mob/user, flags, hint)
+ if(!allow_unanchor)
+ return ..()
+ if(default_unfasten_wrench(user, I, 4 SECONDS))
+ user.visible_message(SPAN_NOTICE("[user] [anchored? "fastens [src] to the ground" : "unfastens [src] from the ground"]."), range = MESSAGE_RANGE_CONSTRUCTION)
+ return TRUE
+ return ..()
+
+/obj/machinery/bioscan_antenna/multitool_act(obj/item/I, mob/user, flags, hint)
+ if(!network_mutable)
+ return ..()
+ . = TRUE
+ var/new_network = default_input_text(user, "What do you want to set the network key to?", "Modify Network", network_key)
+ if(!user.Reachability(src) || isnull(new_network))
+ return
+ user.visible_message(SPAN_NOTICE("[user] reprograms the network on [src]."), range = MESSAGE_RANGE_CONFIGURATION)
+ change_network(new_network)
+
+/obj/machinery/bioscan_antenna/dynamic_tool_functions(obj/item/I, mob/user)
+ . = list()
+ if(network_mutable)
+ .[TOOL_MULTITOOL] = "change network"
+ if(allow_unanchor)
+ .[TOOL_WRENCH] = anchored? "anchor" : "unanchor"
+ if(allow_deconstruct)
+ .[TOOL_SCREWDRIVER] = panel_open? "close panel" : "open panel"
+ if(panel_open)
+ .[TOOL_CROWBAR] = "deconstruct"
+
+/obj/machinery/bioscan_antenna/dynamic_tool_image(function, hint)
+ switch(function)
+ if(TOOL_WRENCH)
+ return anchored? dyntool_image_backward(TOOL_WRENCH) : dyntool_image_forward(TOOL_WRENCH)
+ if(TOOL_SCREWDRIVER)
+ return panel_open? dyntool_image_forward(TOOL_SCREWDRIVER) : dyntool_image_backward(TOOL_SCREWDRIVER)
+ return ..()
+
+/obj/machinery/bioscan_antenna/attack_hand(mob/user)
+ // todo: better xenomorphs
+ if(ishuman(user))
+ var/mob/living/carbon/human/H = user
+ if(istype(H.species, /datum/species/xenos))
+ if(!network_key)
+ to_chat(H, SPAN_WARNING("[src] is already de-programmed."))
+ else
+ change_network(null)
+ H.visible_message(SPAN_WARNING("[H] violently tears apart [src]'s wires."))
+ return
+ return ..()
+
+/obj/machinery/bioscan_antenna/proc/change_network(key)
+ if(src.network_key)
+ if(GLOB.bioscan_antenna_list[src.network_key])
+ GLOB.bioscan_antenna_list[src.network_key] -= src
+ if(!length(GLOB.bioscan_antenna_list[src.network_key]))
+ GLOB.bioscan_antenna_list -= src.network_key
+ src.network_key = key
+ if(src.network_key)
+ if(!GLOB.bioscan_antenna_list[src.network_key])
+ GLOB.bioscan_antenna_list[src.network_key] = list()
+ GLOB.bioscan_antenna_list[src.network_key] += src
+ update_icon()
+
+/obj/machinery/bioscan_antenna/update_icon_state()
+ icon_state = "[base_icon_state][network_key? "_active" : ""]"
+ return ..()
+
+/obj/machinery/bioscan_antenna/permanent
+ desc = "A less fragile antenna used to locate nearby biosignatures. This one cannot be anchored or moved, only reprogrammed."
+ allow_deconstruct = FALSE
+ allow_unanchor = FALSE
diff --git a/code/game/machinery/telecomms/machine_interactions.dm b/code/game/machinery/telecomms/_telecomms.dm
similarity index 57%
rename from code/game/machinery/telecomms/machine_interactions.dm
rename to code/game/machinery/telecomms/_telecomms.dm
index 1b887cf1f2f..626f5cf9a50 100644
--- a/code/game/machinery/telecomms/machine_interactions.dm
+++ b/code/game/machinery/telecomms/_telecomms.dm
@@ -1,5 +1,271 @@
//This file was auto-corrected by findeclaration.exe on 25.5.2012 20:42:32
+/*
+ Hello, friends, this is Doohl from sexylands. You may be wondering what this
+ monstrous code file is. Sit down, boys and girls, while I tell you the tale.
+
+
+ The machines defined in this file were designed to be compatible with any radio
+ signals, provided they use subspace transmission. Currently they are only used for
+ headsets, but they can eventually be outfitted for real COMPUTER networks. This
+ is just a skeleton, ladies and gentlemen.
+
+ Look at radio.dm for the prequel to this code.
+*/
+
+/obj/machinery/telecomms
+ icon = 'icons/obj/stationobjs.dmi'
+ ///List of machines this machine is linked to.
+ var/list/links = list()
+ ///Value increases as traffic increases.
+ var/traffic = 0
+ ///How much traffic to lose per tick (50 gigabytes/second * netspeed).
+ var/netspeed = 5
+ ///List of text/number values to link with.
+ var/list/autolinkers = list()
+ ///Identification string.
+ var/id = "NULL"
+ ///The network of the machinery.
+ var/network = "NULL"
+ //List of frequencies to tune into: if none, will listen to all.
+ var/list/freq_listening = list()
+ ///Just a hacky way of preventing alike machines from pairing.
+ var/machinetype = 0
+ ///Is it toggled on. //Not sure why we have this when we have var/on.
+ var/toggled = TRUE
+ var/on = TRUE
+ ///Basically HP, loses integrity by heat.
+ integrity = 100
+ ///Whether the machine will produce heat when on.
+ var/produces_heat = TRUE
+ ///How many process() ticks to delay per heat.
+ var/delay = 10
+ ///Can you link it across Z levels or on the otherside of the map? (Relay & Hub)
+ var/long_range_link = FALSE
+ ///Is it a hidden machine?
+ var/hide = 0
+ ///0 = auto set in New() - this is the z level that the machine is listening to.
+ var/listening_level = 0
+
+
+/obj/machinery/telecomms/proc/relay_information(datum/signal/signal, filter, copysig, amount = 20)
+ // relay signal to all linked machinery that are of type [filter]. If signal has been sent [amount] times, stop sending
+
+ if(!on)
+ return
+ //TO_WORLD("[src] ([src.id]) - [signal.debug_print()]")
+ var/send_count = 0
+
+ signal.data["slow"] += rand(0, round((100-integrity))) // apply some lag based on integrity
+
+ /*
+ // Edit by Atlantis: Commented out as emergency fix due to causing extreme delays in communications.
+ // Apply some lag based on traffic rates
+ var/netlag = round(traffic / 50)
+ if(netlag > signal.data["slow"])
+ signal.data["slow"] = netlag
+ */
+// Loop through all linked machines and send the signal or copy.
+ for(var/obj/machinery/telecomms/machine in links)
+ if(filter && !istype( machine, text2path(filter) ))
+ continue
+ if(!machine.on)
+ continue
+ if(amount && send_count >= amount)
+ break
+ if(machine.loc.z != listening_level)
+ if(long_range_link == 0 && machine.long_range_link == 0)
+ continue
+ // If we're sending a copy, be sure to create the copy for EACH machine and paste the data
+ var/datum/signal/copy
+ if(copysig)
+ copy = new
+ copy.transmission_method = TRANSMISSION_SUBSPACE
+ copy.frequency = signal.frequency
+ copy.data = signal.data.Copy()
+
+ // Keep the "original" signal constant
+ if(!signal.data["original"])
+ copy.data["original"] = signal
+ else
+ copy.data["original"] = signal.data["original"]
+
+ send_count++
+ if(machine.is_freq_listening(signal))
+ machine.traffic++
+
+ if(copysig && copy)
+ machine.receive_information(copy, src)
+ else
+ machine.receive_information(signal, src)
+
+
+ if(send_count > 0 && is_freq_listening(signal))
+ traffic++
+
+ return send_count
+
+/obj/machinery/telecomms/proc/relay_direct_information(datum/signal/signal, obj/machinery/telecomms/machine)
+ // send signal directly to a machine
+ machine.receive_information(signal, src)
+
+/obj/machinery/telecomms/proc/receive_information(datum/signal/signal, obj/machinery/telecomms/machine_from)
+ // receive information from linked machinery
+
+/obj/machinery/telecomms/proc/is_freq_listening(datum/signal/signal)
+ // return 1 if found, 0 if not found
+ if(!signal)
+ return 0
+ if((signal.frequency in freq_listening) || (!freq_listening.len))
+ return 1
+ else
+ return 0
+
+/obj/machinery/telecomms/Initialize(mapload)
+ GLOB.telecomms_list += src
+ . = ..()
+
+ //Set the listening_level if there's none.
+ if(!listening_level)
+ //Defaults to our Z level!
+ var/turf/position = get_turf(src)
+ listening_level = position.z
+ return INITIALIZE_HINT_LATELOAD
+
+/obj/machinery/telecomms/LateInitialize()
+ if(autolinkers.len)
+ // Links nearby machines
+ if(!long_range_link)
+ for(var/obj/machinery/telecomms/T in orange(20, src))
+ add_link(T)
+ else
+ for(var/obj/machinery/telecomms/T in GLOB.telecomms_list)
+ add_link(T)
+ return ..()
+
+/obj/machinery/telecomms/Destroy()
+ GLOB.telecomms_list -= src
+ for(var/obj/machinery/telecomms/comm in GLOB.telecomms_list)
+ comm.links -= src
+ links = list()
+ ..()
+
+// Used in auto linking
+/obj/machinery/telecomms/proc/add_link(var/obj/machinery/telecomms/T)
+ var/pos_z = get_z(src)
+ var/tpos_z = get_z(T)
+ if((pos_z == tpos_z) || (src.long_range_link && T.long_range_link))
+ for(var/x in autolinkers)
+ if(T.autolinkers.Find(x))
+ if(src != T)
+ links |= T
+
+/obj/machinery/telecomms/update_icon()
+ if(on)
+ icon_state = initial(icon_state)
+ else
+ icon_state = "[initial(icon_state)]_off"
+
+/obj/machinery/telecomms/proc/update_power()
+ if(toggled)
+ if(machine_stat & (BROKEN|NOPOWER|EMPED) || integrity <= 0) // if powered, on. if not powered, off. if too damaged, off
+ on = FALSE
+ else
+ on = TRUE
+ else
+ on = FALSE
+
+/obj/machinery/telecomms/process()
+ update_power()
+
+ // Check heat and generate some
+ checkheat()
+
+ // Update the icon
+ update_icon()
+
+ if(traffic > 0)
+ traffic -= netspeed
+
+/obj/machinery/telecomms/emp_act(severity)
+ if(prob(100/severity))
+ if(!(machine_stat & EMPED))
+ machine_stat |= EMPED
+ var/duration = (300 * 10)/severity
+ spawn(rand(duration - 20, duration + 20)) // Takes a long time for the machines to reboot.
+ machine_stat &= ~EMPED
+ ..()
+
+/obj/machinery/telecomms/proc/checkheat()
+ // Checks heat from the environment and applies any integrity damage
+ var/damage_chance = 0 // Percent based chance of applying 1 integrity damage this tick
+ switch(loc.return_temperature())
+ if((T0C + 40) to (T0C + 70)) // 40C-70C, minor overheat, 10% chance of taking damage
+ damage_chance = 10
+ if((T0C + 70) to (T0C + 130)) // 70C-130C, major overheat, 25% chance of taking damage
+ damage_chance = 25
+ if((T0C + 130) to (T0C + 200)) // 130C-200C, dangerous overheat, 50% chance of taking damage
+ damage_chance = 50
+ if((T0C + 200) to INFINITY) // More than 200C, INFERNO. Takes damage every tick.
+ damage_chance = 100
+ if (damage_chance && prob(damage_chance))
+ integrity = clamp( integrity - 1, 0, 100)
+
+ if(delay > 0)
+ delay--
+ else if(on)
+ produce_heat()
+ delay = initial(delay)
+
+/obj/machinery/telecomms/proc/produce_heat()
+ if (!produces_heat)
+ return
+
+ if (!use_power)
+ return
+
+ if(!(machine_stat & (NOPOWER|BROKEN)))
+ var/turf/simulated/L = loc
+ if(istype(L))
+ var/datum/gas_mixture/env = L.return_air()
+
+ var/transfer_moles = 0.25 * env.total_moles
+
+ var/datum/gas_mixture/removed = env.remove(transfer_moles)
+
+ if(removed)
+
+ var/heat_produced = idle_power_usage //obviously can't produce more heat than the machine draws from it's power source
+ if (traffic <= 0)
+ heat_produced *= 0.30 //if idle, produce less heat.
+
+ removed.adjust_thermal_energy(heat_produced)
+
+ env.merge(removed)
+
+//Generic telecomm connectivity test proc
+/proc/can_telecomm(var/atom/A, var/atom/B, var/ad_hoc = FALSE)
+ if(!A || !B)
+ log_debug(SPAN_DEBUG("can_telecomm(): Undefined endpoints!"))
+ return FALSE
+
+ //Can't in this case, obviously!
+ if(is_jammed(A) || is_jammed(B))
+ return FALSE
+
+ //Items don't have a Z when inside an object or mob
+ var/turf/src_z = get_z(A)
+ var/turf/dst_z = get_z(B)
+
+ //Nullspace, probably.
+ if(!src_z || !dst_z)
+ return FALSE
+
+ //We can do the simple check first, if you have ad_hoc radios.
+ if(ad_hoc && src_z == dst_z)
+ return TRUE
+
+ return src_z in GLOB.using_map.get_map_levels(dst_z, TRUE, om_range = DEFAULT_OVERMAP_RANGE)
/*
diff --git a/code/game/machinery/telecomms/allinone.dm b/code/game/machinery/telecomms/allinone.dm
new file mode 100644
index 00000000000..23f48b5d8c4
--- /dev/null
+++ b/code/game/machinery/telecomms/allinone.dm
@@ -0,0 +1,200 @@
+/*
+ Basically just an empty shell for receiving and broadcasting radio messages. Not
+ very flexible, but it gets the job done.
+ NOTE: This AIO device listens on *every* zlevel (it does not even check)
+*/
+
+/obj/machinery/telecomms/allinone
+ name = "Telecommunications Mainframe"
+ icon = 'icons/obj/stationobjs.dmi'
+ icon_state = "allinone"
+ desc = "A compact machine used for portable subspace telecommuniations processing."
+ density = 1
+ use_power = USE_POWER_IDLE
+ idle_power_usage = 20
+ anchored = 1
+ machinetype = 6
+ produces_heat = 0
+ var/intercept = 0 // if nonzero, broadcasts all messages to syndicate channel
+ var/overmap_range = 0 //Same turf
+
+ var/list/linked_radios_weakrefs = list()
+
+/obj/machinery/telecomms/allinone/proc/link_radio(var/obj/item/radio/R)
+ if(!istype(R))
+ return
+ linked_radios_weakrefs |= WEAKREF(R)
+
+/obj/machinery/telecomms/allinone/receive_signal(datum/signal/signal)
+
+ // Has to be on to receive messages
+ if(!on)
+ return
+
+ // Why did you use this subtype?
+ if(!GLOB.using_map.use_overmap)
+ return
+
+ // Someone else handling it?
+ if(signal.data["done"])
+ return
+
+ // Where are we able to hear from (and talk to, since we're AIO) anyway?
+ var/map_levels = GLOB.using_map.get_map_levels(z, TRUE, overmap_range)
+
+ //Bluespace can skip this check
+ if(signal.transmission_method != TRANSMISSION_BLUESPACE)
+ var/list/signal_levels = list()
+ signal_levels += signal.data["level"] //If it's text/number, it'll be the only entry, if it's a list, it'll get combined
+ var/list/overlap = map_levels & signal_levels //Returns a list of similar levels
+ if(!overlap.len)
+ return
+
+ if(is_freq_listening(signal)) // detect subspace signals
+
+ signal.data["done"] = 1 // mark the signal as being broadcasted since we're a broadcaster
+ signal.data["compression"] = 0 // decompress since we're a processor
+
+ // Search for the original signal and mark it as done as well
+ var/datum/signal/original = signal.data["original"]
+ if(original)
+ original.data["done"] = 1
+
+ // For some reason level is both used as a list and not a list, and now it needs to be a list.
+ signal.data["level"] = map_levels
+
+ if(signal.data["slow"] > 0)
+ sleep(signal.data["slow"]) // simulate the network lag if necessary
+
+ /* ###### Broadcast a message using signal.data ###### */
+
+ var/datum/radio_frequency/connection = signal.data["connection"]
+
+ var/list/forced_radios
+ for(var/datum/weakref/wr in linked_radios_weakrefs)
+ var/obj/item/radio/R = wr.resolve()
+ if(istype(R))
+ LAZYDISTINCTADD(forced_radios, R)
+
+ Broadcast_Message(
+ signal.data["connection"],
+ signal.data["mob"],
+ signal.data["vmask"],
+ signal.data["vmessage"],
+ signal.data["radio"],
+ signal.data["message"],
+ signal.data["name"],
+ signal.data["job"],
+ signal.data["realname"],
+ signal.data["vname"],
+ DATA_NORMAL,
+ signal.data["compression"],
+ signal.data["level"],
+ connection.frequency,
+ signal.data["verb"],
+ signal.data["language"],
+ forced_radios
+ )
+
+//Antag version with unlimited range (doesn't even check) and uses no power, to enable antag comms to work anywhere.
+/obj/machinery/telecomms/allinone/antag
+ use_power = USE_POWER_OFF
+ idle_power_usage = 0
+
+/obj/machinery/telecomms/allinone/antag/receive_signal(datum/signal/signal)
+ if(!on) // has to be on to receive messages
+ return
+
+ if(is_freq_listening(signal)) // detect subspace signals
+
+ //signal.data["done"] = 1 // mark the signal as being broadcasted since we're a broadcaster
+ signal.data["compression"] = 0
+
+ /*
+ // Search for the original signal and mark it as done as well
+ var/datum/signal/original = signal.data["original"]
+ if(original)
+ original.data["done"] = 1
+ */
+
+ // For some reason level is both used as a list and not a list, and now it needs to be a list.
+ // Because this is a 'all in one' machine, we're gonna just cheat.
+ //signal.data["level"] = GLOB.using_map.contact_levels.Copy()
+
+ if(signal.data["slow"] > 0)
+ sleep(signal.data["slow"]) // simulate the network lag if necessary
+
+ /* ###### Broadcast a message using signal.data ###### */
+
+ var/datum/radio_frequency/connection = signal.data["connection"]
+
+ var/list/forced_radios
+ for(var/datum/weakref/wr in linked_radios_weakrefs)
+ var/obj/item/radio/R = wr.resolve()
+ if(istype(R))
+ LAZYDISTINCTADD(forced_radios, R)
+
+ if(connection.frequency in ANTAG_FREQS) // if antag broadcast, just
+ Broadcast_Message(signal.data["connection"], signal.data["mob"],
+ signal.data["vmask"], signal.data["vmessage"],
+ signal.data["radio"], signal.data["message"],
+ signal.data["name"], signal.data["job"],
+ signal.data["realname"], signal.data["vname"], DATA_NORMAL,
+ signal.data["compression"], list(0), connection.frequency,
+ signal.data["verb"], signal.data["language"], forced_radios)
+ else
+ if(intercept)
+ Broadcast_Message(signal.data["connection"], signal.data["mob"],
+ signal.data["vmask"], signal.data["vmessage"],
+ signal.data["radio"], signal.data["message"],
+ signal.data["name"], signal.data["job"],
+ signal.data["realname"], signal.data["vname"], DATA_ANTAG,
+ signal.data["compression"], list(0), connection.frequency,
+ signal.data["verb"], signal.data["language"], forced_radios)
+
+//ERT version with unlimited range (doesn't even check) and uses no power, to enable ert comms to work anywhere.
+/obj/machinery/telecomms/allinone/ert
+ use_power = USE_POWER_OFF
+ idle_power_usage = 0
+
+/obj/machinery/telecomms/allinone/ert/receive_signal(datum/signal/signal)
+ if(!on) // has to be on to receive messages
+ return
+
+ if(is_freq_listening(signal)) // detect subspace signals
+
+ //signal.data["done"] = 1 // mark the signal as being broadcasted since we're a broadcaster
+ signal.data["compression"] = 0
+
+ /*
+ // Search for the original signal and mark it as done as well
+ var/datum/signal/original = signal.data["original"]
+ if(original)
+ original.data["done"] = 1
+ */
+
+ // For some reason level is both used as a list and not a list, and now it needs to be a list.
+ // Because this is a 'all in one' machine, we're gonna just cheat.
+ //signal.data["level"] = GLOB.using_map.contact_levels.Copy()
+
+ if(signal.data["slow"] > 0)
+ sleep(signal.data["slow"]) // simulate the network lag if necessary
+
+ /* ###### Broadcast a message using signal.data ###### */
+
+ var/datum/radio_frequency/connection = signal.data["connection"]
+
+ var/list/forced_radios
+ for(var/datum/weakref/wr in linked_radios_weakrefs)
+ var/obj/item/radio/R = wr.resolve()
+ if(istype(R))
+ LAZYDISTINCTADD(forced_radios, R)
+
+ if(connection.frequency in CENT_FREQS) // if ert broadcast, just
+ Broadcast_Message(signal.data["connection"], signal.data["mob"],
+ signal.data["vmask"], signal.data["vmessage"],
+ signal.data["radio"], signal.data["message"],
+ signal.data["name"], signal.data["job"],
+ signal.data["realname"], signal.data["vname"], DATA_NORMAL,
+ signal.data["compression"], list(0), connection.frequency,
+ signal.data["verb"], signal.data["language"], forced_radios)
diff --git a/code/game/machinery/telecomms/broadcaster.dm b/code/game/machinery/telecomms/broadcaster.dm
index 29bf70a5b5a..709e15d77f1 100644
--- a/code/game/machinery/telecomms/broadcaster.dm
+++ b/code/game/machinery/telecomms/broadcaster.dm
@@ -122,161 +122,6 @@ var/message_delay = 0 // To make sure restarting the recentmessages list is kept
message_delay = 0
..()
-
-/*
- Basically just an empty shell for receiving and broadcasting radio messages. Not
- very flexible, but it gets the job done.
- NOTE: This AIO device listens on *every* zlevel (it does not even check)
-*/
-
-/obj/machinery/telecomms/allinone
- name = "Telecommunications Mainframe"
- icon = 'icons/obj/stationobjs.dmi'
- icon_state = "allinone"
- desc = "A compact machine used for portable subspace telecommuniations processing."
- density = 1
- use_power = USE_POWER_IDLE
- idle_power_usage = 20
- anchored = 1
- machinetype = 6
- produces_heat = 0
- var/intercept = 0 // if nonzero, broadcasts all messages to syndicate channel
- var/overmap_range = 0 //Same turf
-
- var/list/linked_radios_weakrefs = list()
-
-/obj/machinery/telecomms/allinone/proc/link_radio(var/obj/item/radio/R)
- if(!istype(R))
- return
- linked_radios_weakrefs |= WEAKREF(R)
-
-/obj/machinery/telecomms/allinone/receive_signal(datum/signal/signal)
-
- // Has to be on to receive messages
- if(!on)
- return
-
- // Why did you use this subtype?
- if(!GLOB.using_map.use_overmap)
- return
-
- // Someone else handling it?
- if(signal.data["done"])
- return
-
- // Where are we able to hear from (and talk to, since we're AIO) anyway?
- var/map_levels = GLOB.using_map.get_map_levels(z, TRUE, overmap_range)
-
- //Bluespace can skip this check
- if(signal.transmission_method != TRANSMISSION_BLUESPACE)
- var/list/signal_levels = list()
- signal_levels += signal.data["level"] //If it's text/number, it'll be the only entry, if it's a list, it'll get combined
- var/list/overlap = map_levels & signal_levels //Returns a list of similar levels
- if(!overlap.len)
- return
-
- if(is_freq_listening(signal)) // detect subspace signals
-
- signal.data["done"] = 1 // mark the signal as being broadcasted since we're a broadcaster
- signal.data["compression"] = 0 // decompress since we're a processor
-
- // Search for the original signal and mark it as done as well
- var/datum/signal/original = signal.data["original"]
- if(original)
- original.data["done"] = 1
-
- // For some reason level is both used as a list and not a list, and now it needs to be a list.
- signal.data["level"] = map_levels
-
- if(signal.data["slow"] > 0)
- sleep(signal.data["slow"]) // simulate the network lag if necessary
-
- /* ###### Broadcast a message using signal.data ###### */
-
- var/datum/radio_frequency/connection = signal.data["connection"]
-
- var/list/forced_radios
- for(var/datum/weakref/wr in linked_radios_weakrefs)
- var/obj/item/radio/R = wr.resolve()
- if(istype(R))
- LAZYDISTINCTADD(forced_radios, R)
-
- Broadcast_Message(
- signal.data["connection"],
- signal.data["mob"],
- signal.data["vmask"],
- signal.data["vmessage"],
- signal.data["radio"],
- signal.data["message"],
- signal.data["name"],
- signal.data["job"],
- signal.data["realname"],
- signal.data["vname"],
- DATA_NORMAL,
- signal.data["compression"],
- signal.data["level"],
- connection.frequency,
- signal.data["verb"],
- signal.data["language"],
- forced_radios
- )
-
-//Antag version with unlimited range (doesn't even check) and uses no power, to enable antag comms to work anywhere.
-/obj/machinery/telecomms/allinone/antag
- use_power = USE_POWER_OFF
- idle_power_usage = 0
-
-/obj/machinery/telecomms/allinone/antag/receive_signal(datum/signal/signal)
- if(!on) // has to be on to receive messages
- return
-
- if(is_freq_listening(signal)) // detect subspace signals
-
- //signal.data["done"] = 1 // mark the signal as being broadcasted since we're a broadcaster
- signal.data["compression"] = 0
-
- /*
- // Search for the original signal and mark it as done as well
- var/datum/signal/original = signal.data["original"]
- if(original)
- original.data["done"] = 1
- */
-
- // For some reason level is both used as a list and not a list, and now it needs to be a list.
- // Because this is a 'all in one' machine, we're gonna just cheat.
- //signal.data["level"] = GLOB.using_map.contact_levels.Copy()
-
- if(signal.data["slow"] > 0)
- sleep(signal.data["slow"]) // simulate the network lag if necessary
-
- /* ###### Broadcast a message using signal.data ###### */
-
- var/datum/radio_frequency/connection = signal.data["connection"]
-
- var/list/forced_radios
- for(var/datum/weakref/wr in linked_radios_weakrefs)
- var/obj/item/radio/R = wr.resolve()
- if(istype(R))
- LAZYDISTINCTADD(forced_radios, R)
-
- if(connection.frequency in ANTAG_FREQS) // if antag broadcast, just
- Broadcast_Message(signal.data["connection"], signal.data["mob"],
- signal.data["vmask"], signal.data["vmessage"],
- signal.data["radio"], signal.data["message"],
- signal.data["name"], signal.data["job"],
- signal.data["realname"], signal.data["vname"], DATA_NORMAL,
- signal.data["compression"], list(0), connection.frequency,
- signal.data["verb"], signal.data["language"], forced_radios)
- else
- if(intercept)
- Broadcast_Message(signal.data["connection"], signal.data["mob"],
- signal.data["vmask"], signal.data["vmessage"],
- signal.data["radio"], signal.data["message"],
- signal.data["name"], signal.data["job"],
- signal.data["realname"], signal.data["vname"], DATA_ANTAG,
- signal.data["compression"], list(0), connection.frequency,
- signal.data["verb"], signal.data["language"], forced_radios)
-
/**
Here is the big, bad function that broadcasts a message given the appropriate
diff --git a/code/game/machinery/telecomms/broadcaster_vr.dm b/code/game/machinery/telecomms/broadcaster_vr.dm
deleted file mode 100644
index 775604bbf76..00000000000
--- a/code/game/machinery/telecomms/broadcaster_vr.dm
+++ /dev/null
@@ -1,46 +0,0 @@
-//ERT version with unlimited range (doesn't even check) and uses no power, to enable ert comms to work anywhere.
-/obj/machinery/telecomms/allinone/ert
- use_power = USE_POWER_OFF
- idle_power_usage = 0
-
-/obj/machinery/telecomms/allinone/ert/receive_signal(datum/signal/signal)
- if(!on) // has to be on to receive messages
- return
-
- if(is_freq_listening(signal)) // detect subspace signals
-
- //signal.data["done"] = 1 // mark the signal as being broadcasted since we're a broadcaster
- signal.data["compression"] = 0
-
- /*
- // Search for the original signal and mark it as done as well
- var/datum/signal/original = signal.data["original"]
- if(original)
- original.data["done"] = 1
- */
-
- // For some reason level is both used as a list and not a list, and now it needs to be a list.
- // Because this is a 'all in one' machine, we're gonna just cheat.
- //signal.data["level"] = GLOB.using_map.contact_levels.Copy()
-
- if(signal.data["slow"] > 0)
- sleep(signal.data["slow"]) // simulate the network lag if necessary
-
- /* ###### Broadcast a message using signal.data ###### */
-
- var/datum/radio_frequency/connection = signal.data["connection"]
-
- var/list/forced_radios
- for(var/weakref/wr in linked_radios_weakrefs)
- var/obj/item/radio/R = wr.resolve()
- if(istype(R))
- LAZYDISTINCTADD(forced_radios, R)
-
- if(connection.frequency in CENT_FREQS) // if ert broadcast, just
- Broadcast_Message(signal.data["connection"], signal.data["mob"],
- signal.data["vmask"], signal.data["vmessage"],
- signal.data["radio"], signal.data["message"],
- signal.data["name"], signal.data["job"],
- signal.data["realname"], signal.data["vname"], DATA_NORMAL,
- signal.data["compression"], list(0), connection.frequency,
- signal.data["verb"], signal.data["language"], forced_radios)
diff --git a/code/game/machinery/telecomms/bus.dm b/code/game/machinery/telecomms/bus.dm
new file mode 100644
index 00000000000..42de08a0c72
--- /dev/null
+++ b/code/game/machinery/telecomms/bus.dm
@@ -0,0 +1,54 @@
+/*
+ The bus mainframe idles and waits for hubs to relay them signals. They act
+ as junctions for the network.
+
+ They transfer uncompressed subspace packets to processor units, and then take
+ the processed packet to a server for logging.
+
+ Link to a subspace hub if it can't send to a server.
+*/
+
+/obj/machinery/telecomms/bus
+ name = "Bus Mainframe"
+ icon_state = "bus"
+ desc = "A mighty piece of hardware used to send massive amounts of data quickly."
+ density = 1
+ anchored = 1
+ use_power = USE_POWER_IDLE
+ idle_power_usage = 1000
+ machinetype = 2
+ circuit = /obj/item/circuitboard/telecomms/bus
+ netspeed = 40
+ var/change_frequency = 0
+
+/obj/machinery/telecomms/bus/Initialize(mapload)
+ . = ..()
+ default_apply_parts()
+
+/obj/machinery/telecomms/bus/receive_information(datum/signal/signal, obj/machinery/telecomms/machine_from)
+
+ if(is_freq_listening(signal))
+
+ if(change_frequency)
+ signal.frequency = change_frequency
+
+ if(!istype(machine_from, /obj/machinery/telecomms/processor) && machine_from != src) // Signal must be ready (stupid assuming machine), let's send it
+ // send to one linked processor unit
+ var/send_to_processor = relay_information(signal, "/obj/machinery/telecomms/processor")
+
+ if(send_to_processor)
+ return
+ // failed to send to a processor, relay information anyway
+ signal.data["slow"] += rand(1, 5) // slow the signal down only slightly
+ src.receive_information(signal, src)
+
+ // Try sending it!
+ var/list/try_send = list("/obj/machinery/telecomms/server", "/obj/machinery/telecomms/hub", "/obj/machinery/telecomms/broadcaster", "/obj/machinery/telecomms/bus")
+ var/i = 0
+ for(var/send in try_send)
+ if(i)
+ signal.data["slow"] += rand(0, 1) // slow the signal down only slightly
+ i++
+ var/can_send = relay_information(signal, send)
+ if(can_send)
+ break
diff --git a/code/game/machinery/telecomms/hub.dm b/code/game/machinery/telecomms/hub.dm
new file mode 100644
index 00000000000..6644fa720ee
--- /dev/null
+++ b/code/game/machinery/telecomms/hub.dm
@@ -0,0 +1,36 @@
+/*
+ The HUB idles until it receives information. It then passes on that information
+ depending on where it came from.
+
+ This is the heart of the Telecommunications Network, sending information where it
+ is needed. It mainly receives information from long-distance Relays and then sends
+ that information to be processed. Afterwards it gets the uncompressed information
+ from Servers/Buses and sends that back to the relay, to then be broadcasted.
+*/
+
+/obj/machinery/telecomms/hub
+ name = "Telecommunication Hub"
+ icon_state = "hub"
+ desc = "A mighty piece of hardware used to send/receive massive amounts of data."
+ density = 1
+ anchored = 1
+ use_power = USE_POWER_IDLE
+ idle_power_usage = 1600
+ machinetype = 7
+ circuit = /obj/item/circuitboard/telecomms/hub
+ long_range_link = 1
+ netspeed = 40
+
+/obj/machinery/telecomms/hub/Initialize(mapload)
+ . = ..()
+ default_apply_parts()
+
+/obj/machinery/telecomms/hub/receive_information(datum/signal/signal, obj/machinery/telecomms/machine_from)
+ if(is_freq_listening(signal))
+ if(istype(machine_from, /obj/machinery/telecomms/receiver))
+ //If the signal is compressed, send it to the bus.
+ relay_information(signal, "/obj/machinery/telecomms/bus", 1) // ideally relay the copied information to bus units
+ else
+ // Get a list of relays that we're linked to, then send the signal to their levels.
+ relay_information(signal, "/obj/machinery/telecomms/relay", 1)
+ relay_information(signal, "/obj/machinery/telecomms/broadcaster", 1) // Send it to a broadcaster.
diff --git a/code/game/machinery/telecomms/logbrowser.dm b/code/game/machinery/telecomms/logbrowser.dm
index 6ba60522e02..6ff4f804946 100644
--- a/code/game/machinery/telecomms/logbrowser.dm
+++ b/code/game/machinery/telecomms/logbrowser.dm
@@ -4,7 +4,7 @@
/obj/machinery/computer/telecomms/server
name = "Telecommunications Server Monitor"
- desc = "View communication logs here. Translation not guaranteed."
+ desc = "View communication logs and operate triangulation systems here. Translation not guaranteed."
icon_screen = "comm_logs"
var/list/servers = list() // the servers located by the computer
@@ -37,7 +37,9 @@
data["selectedServer"] = list(
"id" = SelectedServer.id,
"totalTraffic" = SelectedServer.totaltraffic,
- "logs" = list()
+ "logs" = list(),
+ "triangulating" = SelectedServer.triangulating,
+ "triangulation" = SelectedServer.triangulation_data(),
)
var/i = 0
@@ -138,6 +140,10 @@
temp = null
. = TRUE
+ if("toggle_triangulation")
+ SelectedServer.set_triangulating(!SelectedServer.triangulating)
+ return TRUE
+
/obj/machinery/computer/telecomms/server/emag_act(var/remaining_charges, var/mob/user)
if(!emagged)
playsound(src, 'sound/effects/sparks4.ogg', 75, 1)
diff --git a/code/game/machinery/telecomms/processor.dm b/code/game/machinery/telecomms/processor.dm
new file mode 100644
index 00000000000..d05509e1f47
--- /dev/null
+++ b/code/game/machinery/telecomms/processor.dm
@@ -0,0 +1,39 @@
+/*
+ The processor is a very simple machine that decompresses subspace signals and
+ transfers them back to the original bus. It is essential in producing audible
+ data.
+
+ Link to servers if bus is not present
+*/
+
+/obj/machinery/telecomms/processor
+ name = "Processor Unit"
+ icon_state = "processor"
+ desc = "This machine is used to process large quantities of information."
+ density = 1
+ anchored = 1
+ use_power = USE_POWER_IDLE
+ idle_power_usage = 600
+ machinetype = 3
+ delay = 5
+ circuit = /obj/item/circuitboard/telecomms/processor
+ var/process_mode = 1 // 1 = Uncompress Signals, 0 = Compress Signals
+
+/obj/machinery/telecomms/processor/Initialize(mapload)
+ . = ..()
+ default_apply_parts()
+
+/obj/machinery/telecomms/processor/receive_information(datum/signal/signal, obj/machinery/telecomms/machine_from)
+
+ if(is_freq_listening(signal))
+
+ if(process_mode)
+ signal.data["compression"] = 0 // uncompress subspace signal
+ else
+ signal.data["compression"] = 100 // even more compressed signal
+
+ if(istype(machine_from, /obj/machinery/telecomms/bus))
+ relay_direct_information(signal, machine_from) // send the signal back to the machine
+ else // no bus detected - send the signal to servers instead
+ signal.data["slow"] += rand(5, 10) // slow the signal down
+ relay_information(signal, "/obj/machinery/telecomms/server")
diff --git a/code/game/machinery/telecomms/receiver.dm b/code/game/machinery/telecomms/receiver.dm
new file mode 100644
index 00000000000..83c7e7e6721
--- /dev/null
+++ b/code/game/machinery/telecomms/receiver.dm
@@ -0,0 +1,83 @@
+/*
+ The receiver idles and receives messages from subspace-compatible radio equipment;
+ primarily headsets. They then just relay this information to all linked devices,
+ which can would probably be network hubs.
+
+ Link to Processor Units in case receiver can't send to bus units.
+*/
+
+/obj/machinery/telecomms/receiver
+ name = "Subspace Receiver"
+ icon_state = "broadcast receiver"
+ desc = "This machine has a dish-like shape and green lights. It is designed to detect and process subspace radio activity."
+ density = 1
+ anchored = 1
+ use_power = USE_POWER_IDLE
+ idle_power_usage = 600
+ machinetype = 1
+ produces_heat = 0
+ circuit = /obj/item/circuitboard/telecomms/receiver
+ //Vars only used if you're using the overmap
+ var/overmap_range = 0
+ var/overmap_range_min = 0
+ var/overmap_range_max = 5
+
+ var/list/linked_radios_weakrefs = list()
+
+/obj/machinery/telecomms/receiver/Initialize(mapload)
+ . = ..()
+ default_apply_parts()
+
+/obj/machinery/telecomms/receiver/proc/link_radio(var/obj/item/radio/R)
+ if(!istype(R))
+ return
+ linked_radios_weakrefs |= WEAKREF(R)
+
+/obj/machinery/telecomms/receiver/receive_signal(datum/signal/signal)
+ if(!on) // has to be on to receive messages
+ return
+ if(!signal)
+ return
+ if(!check_receive_level(signal))
+ return
+
+ if(signal.transmission_method == TRANSMISSION_SUBSPACE)
+
+ if(is_freq_listening(signal)) // detect subspace signals
+
+ //Remove the level and then start adding levels that it is being broadcasted in.
+ signal.data["level"] = list()
+
+ var/can_send = relay_information(signal, "/obj/machinery/telecomms/hub") // ideally relay the copied information to relays
+ if(!can_send)
+ relay_information(signal, "/obj/machinery/telecomms/bus") // Send it to a bus instead, if it's linked to one
+
+/obj/machinery/telecomms/receiver/proc/check_receive_level(datum/signal/signal)
+ // If it's a direct message from a bluespace radio, we eat it and convert it into a subspace signal locally
+ if(signal.transmission_method == TRANSMISSION_BLUESPACE)
+ var/obj/item/radio/R = signal.data["radio"]
+
+ //Who're you?
+ if(!(WEAKREF(R) in linked_radios_weakrefs))
+ signal.data["reject"] = 1
+ return 0
+
+ //We'll resend this for you
+ signal.data["level"] = z
+ signal.transmission_method = TRANSMISSION_SUBSPACE
+ return 1
+
+ //Where can we hear?
+ var/list/listening_levels = GLOB.using_map.get_map_levels(listening_level, TRUE, overmap_range)
+
+ // We couldn't 'hear' it, maybe a relay linked to our hub can 'hear' it
+ if(!(signal.data["level"] in listening_levels))
+ for(var/obj/machinery/telecomms/hub/H in links)
+ var/list/relayed_levels = list()
+ for(var/obj/machinery/telecomms/relay/R in H.links)
+ if(R.can_receive(signal))
+ relayed_levels |= R.listening_level
+ if(signal.data["level"] in relayed_levels)
+ return 1
+ return 0
+ return 1
diff --git a/code/game/machinery/telecomms/relay.dm b/code/game/machinery/telecomms/relay.dm
new file mode 100644
index 00000000000..811435255c0
--- /dev/null
+++ b/code/game/machinery/telecomms/relay.dm
@@ -0,0 +1,56 @@
+/*
+ The relay idles until it receives information. It then passes on that information
+ depending on where it came from.
+
+ The relay is needed in order to send information pass Z levels. It must be linked
+ with a HUB, the only other machine that can send/receive pass Z levels.
+*/
+
+/obj/machinery/telecomms/relay
+ name = "Telecommunication Relay"
+ icon_state = "relay"
+ desc = "A mighty piece of hardware used to send massive amounts of data far away."
+ density = 1
+ anchored = 1
+ use_power = USE_POWER_IDLE
+ idle_power_usage = 600
+ machinetype = 8
+ produces_heat = 0
+ circuit = /obj/item/circuitboard/telecomms/relay
+ netspeed = 5
+ long_range_link = 1
+ var/broadcasting = 1
+ var/receiving = 1
+
+/obj/machinery/telecomms/relay/Initialize(mapload)
+ . = ..()
+ default_apply_parts()
+
+/obj/machinery/telecomms/relay/onTransitZ(old_z, new_z)
+ . = ..()
+ listening_level = z
+
+/obj/machinery/telecomms/relay/receive_information(datum/signal/signal, obj/machinery/telecomms/machine_from)
+
+ // Add our level and send it back
+ if(can_send(signal))
+ signal.data["level"] |= GLOB.using_map.get_map_levels(listening_level)
+
+// Checks to see if it can send/receive.
+
+/obj/machinery/telecomms/relay/proc/can(datum/signal/signal)
+ if(!on)
+ return 0
+ if(!is_freq_listening(signal))
+ return 0
+ return 1
+
+/obj/machinery/telecomms/relay/proc/can_send(datum/signal/signal)
+ if(!can(signal))
+ return 0
+ return broadcasting
+
+/obj/machinery/telecomms/relay/proc/can_receive(datum/signal/signal)
+ if(!can(signal))
+ return 0
+ return receiving
diff --git a/code/game/machinery/telecomms/server.dm b/code/game/machinery/telecomms/server.dm
new file mode 100644
index 00000000000..6bd63a05c3e
--- /dev/null
+++ b/code/game/machinery/telecomms/server.dm
@@ -0,0 +1,231 @@
+/*
+ The server logs all traffic and signal data. Once it records the signal, it sends
+ it to the subspace broadcaster.
+
+ Store a maximum of 100 logs and then deletes them.
+*/
+
+
+/obj/machinery/telecomms/server
+ name = "Telecommunication Server"
+ icon_state = "comm_server"
+ desc = "A machine used to store data and network statistics."
+ density = 1
+ anchored = 1
+ use_power = USE_POWER_IDLE
+ idle_power_usage = 300
+ machinetype = 4
+ circuit = /obj/item/circuitboard/telecomms/server
+ var/list/log_entries = list()
+ var/logs = 0 // number of logs
+ var/totaltraffic = 0 // gigabytes (if > 1024, divide by 1024 -> terrabytes)
+
+ var/obj/item/radio/headset/server_radio = null
+
+ //? triangulation
+ /// enabled?
+ var/triangulating = FALSE
+ /// tracking source tags to /datum/network_triangulation
+ var/list/triangulation
+ /// accuracy loss per decisecond
+ var/triangulation_falloff = 1 / 60
+ /// accuracy loss per tile
+ var/triangulation_stickyness = 5 / 20
+ /// triangulation efficiency multiplier
+ var/triangulation_efficiency = 1
+
+/obj/machinery/telecomms/server/Initialize(mapload)
+ . = ..()
+ server_radio = new()
+
+/obj/machinery/telecomms/server/Initialize(mapload)
+ . = ..()
+ default_apply_parts()
+
+/obj/machinery/telecomms/server/receive_information(datum/signal/signal, obj/machinery/telecomms/machine_from)
+
+ if(signal.data["message"])
+
+ if(is_freq_listening(signal))
+
+ if(traffic > 0)
+ totaltraffic += traffic // add current traffic to total traffic
+
+ //Is this a test signal? Bypass logging
+ if(signal.data["type"] != SIGNAL_TEST)
+ if(signal.data["radio"] && triangulating)
+ triangulate(signal.data["radio"], update_name = signal.data["name"])
+
+ // If signal has a message and appropriate frequency
+
+ update_logs()
+
+ var/datum/comm_log_entry/log = new
+ var/mob/M = signal.data["mob"]
+
+ // Copy the signal.data entries we want
+ log.parameters["mobtype"] = signal.data["mobtype"]
+ log.parameters["job"] = signal.data["job"]
+ log.parameters["key"] = signal.data["key"]
+ log.parameters["vmessage"] = signal.data["message"]
+ log.parameters["vname"] = signal.data["vname"]
+ log.parameters["message"] = signal.data["message"]
+ log.parameters["name"] = signal.data["name"]
+ log.parameters["realname"] = signal.data["realname"]
+ log.parameters["timecode"] = worldtime2stationtime(world.time)
+
+ var/race = "unknown"
+ if(ishuman(M))
+ var/mob/living/carbon/human/H = M
+ race = "[H.species.name]"
+ log.parameters["intelligible"] = 1
+ else if(isbrain(M))
+ race = "Brain"
+ log.parameters["intelligible"] = 1
+ else if(M.isMonkey())
+ race = "Monkey"
+ else if(issilicon(M))
+ race = "Artificial Life"
+ log.parameters["intelligible"] = 1
+ else if(isslime(M))
+ race = "Slime"
+ else if(isanimal(M))
+ race = "Domestic Animal"
+
+ log.parameters["race"] = race
+
+ if(!istype(M, /mob/new_player) && M)
+ log.parameters["uspeech"] = M.universal_speak
+ else
+ log.parameters["uspeech"] = 0
+
+ // If the signal is still compressed, make the log entry gibberish
+ if(signal.data["compression"] > 0)
+ log.parameters["message"] = Gibberish(signal.data["message"], signal.data["compression"] + 50)
+ log.parameters["job"] = Gibberish(signal.data["job"], signal.data["compression"] + 50)
+ log.parameters["name"] = Gibberish(signal.data["name"], signal.data["compression"] + 50)
+ log.parameters["realname"] = Gibberish(signal.data["realname"], signal.data["compression"] + 50)
+ log.parameters["vname"] = Gibberish(signal.data["vname"], signal.data["compression"] + 50)
+ log.input_type = "Corrupt File"
+
+ // Log and store everything that needs to be logged
+ log_entries.Add(log)
+ logs++
+ signal.data["server"] = src
+
+ // Give the log a name
+ var/identifier = num2text( rand(-1000,1000) + world.time )
+ log.name = "data packet ([md5(identifier)])"
+
+ var/can_send = relay_information(signal, "/obj/machinery/telecomms/hub")
+ if(!can_send)
+ relay_information(signal, "/obj/machinery/telecomms/broadcaster")
+
+/obj/machinery/telecomms/server/proc/update_logs()
+ // start deleting the very first log entry
+ if(logs >= 400)
+ for(var/i = 1, i <= logs, i++) // locate the first garbage collectable log entry and remove it
+ var/datum/comm_log_entry/L = log_entries[i]
+ if(L.garbage_collector)
+ log_entries.Remove(L)
+ logs--
+ break
+
+/obj/machinery/telecomms/server/proc/add_entry(var/content, var/input)
+ var/datum/comm_log_entry/log = new
+ var/identifier = num2text( rand(-1000,1000) + world.time )
+ log.name = "[input] ([md5(identifier)])"
+ log.input_type = input
+ log.parameters["message"] = content
+ log.parameters["timecode"] = stationtime2text()
+ log_entries.Add(log)
+ update_logs()
+
+/obj/machinery/telecomms/server/proc/triangulation_data()
+ . = list()
+ if(!triangulation)
+ return
+ for(var/key in triangulation)
+ var/datum/network_triangulation/T = triangulation[key]
+ . += list(list(
+ "name" = T.scan_name,
+ "x" = T.random_x,
+ "y" = T.random_y,
+ "z" = SSmapping.level_id(T.random_z),
+ "accuracy" = T.accuracy,
+ "last" = world.time - T.last_updated,
+ "tag" = key,
+ ))
+
+/obj/machinery/telecomms/server/proc/triangulate(atom/movable/victim, reduction_factor = 1.8, update_name)
+ if(isnull(triangulation))
+ triangulation = list()
+
+ reduction_factor *= triangulation_efficiency
+
+ // if victim isn't even on a turf...
+ var/turf/location = get_turf(victim)
+ if(isnull(location))
+ return
+
+ // generate tag
+ var/triangulation_tag = ismob(victim)? victim.tag : ref(victim)
+
+ // grab or make entry, update location
+ var/datum/network_triangulation/entry = triangulation[triangulation_tag]
+ if(!entry)
+ triangulation[triangulation_tag] = (entry = new)
+ // starting at..
+ entry.accuracy = round(max(world.maxx, world.maxy) / reduction_factor, 1)
+ else
+ // narrow down
+ var/time_falloff = max(world.time - entry.last_updated, 0) * triangulation_falloff
+ var/dist_falloff = entry.last_turf ? get_dist(entry.last_turf, location) * triangulation_stickyness : 0
+ entry.accuracy = min(
+ round(max(world.maxx, world.maxy) / reduction_factor, 1), // don't get worse than a first attempt
+ (entry.accuracy + time_falloff + dist_falloff) / reduction_factor
+ )
+
+ // update name
+ if(update_name)
+ entry.scan_name = update_name
+
+ entry.last_turf = location
+ entry.last_updated = world.time
+ entry.randomize(location)
+
+/obj/machinery/telecomms/server/proc/set_triangulating(state)
+ triangulating = state
+ if(!state)
+ triangulation = null
+
+// Simple log entry datum
+
+/datum/comm_log_entry
+ var/parameters = list() // carbon-copy to signal.data[]
+ var/name = "data packet (#)"
+ var/garbage_collector = 1 // if set to 0, will not be garbage collected
+ var/input_type = "Speech File"
+
+/datum/network_triangulation
+ /// name of target
+ var/scan_name
+ /// last x
+ var/random_x
+ /// last y
+ var/random_y
+ /// last real z index
+ var/random_z
+ /// last accuracy
+ var/accuracy
+ /// last update time
+ var/last_updated
+ /// last turf
+ var/turf/last_turf
+
+/datum/network_triangulation/proc/randomize(turf/real_loc)
+ var/angle = rand(0, 360)
+ var/dist = accuracy * sqrt(rand(1, 10000) * 0.0001)
+ random_x = round(cos(angle) * dist, 1) + real_loc.x
+ random_y = round(sin(angle) * dist, 1) + real_loc.y
+ random_z = real_loc.z
diff --git a/code/game/machinery/telecomms/telecomunications.dm b/code/game/machinery/telecomms/telecomunications.dm
deleted file mode 100644
index 905a3a72997..00000000000
--- a/code/game/machinery/telecomms/telecomunications.dm
+++ /dev/null
@@ -1,699 +0,0 @@
-//This file was auto-corrected by findeclaration.exe on 25.5.2012 20:42:32
-
-/*
- Hello, friends, this is Doohl from sexylands. You may be wondering what this
- monstrous code file is. Sit down, boys and girls, while I tell you the tale.
-
-
- The machines defined in this file were designed to be compatible with any radio
- signals, provided they use subspace transmission. Currently they are only used for
- headsets, but they can eventually be outfitted for real COMPUTER networks. This
- is just a skeleton, ladies and gentlemen.
-
- Look at radio.dm for the prequel to this code.
-*/
-
-/obj/machinery/telecomms
- icon = 'icons/obj/stationobjs.dmi'
- ///List of machines this machine is linked to.
- var/list/links = list()
- ///Value increases as traffic increases.
- var/traffic = 0
- ///How much traffic to lose per tick (50 gigabytes/second * netspeed).
- var/netspeed = 5
- ///List of text/number values to link with.
- var/list/autolinkers = list()
- ///Identification string.
- var/id = "NULL"
- ///The network of the machinery.
- var/network = "NULL"
- //List of frequencies to tune into: if none, will listen to all.
- var/list/freq_listening = list()
- ///Just a hacky way of preventing alike machines from pairing.
- var/machinetype = 0
- ///Is it toggled on. //Not sure why we have this when we have var/on.
- var/toggled = TRUE
- var/on = TRUE
- ///Basically HP, loses integrity by heat.
- integrity = 100
- ///Whether the machine will produce heat when on.
- var/produces_heat = TRUE
- ///How many process() ticks to delay per heat.
- var/delay = 10
- ///Can you link it across Z levels or on the otherside of the map? (Relay & Hub)
- var/long_range_link = FALSE
- ///Is it a hidden machine?
- var/hide = 0
- ///0 = auto set in New() - this is the z level that the machine is listening to.
- var/listening_level = 0
-
-
-/obj/machinery/telecomms/proc/relay_information(datum/signal/signal, filter, copysig, amount = 20)
- // relay signal to all linked machinery that are of type [filter]. If signal has been sent [amount] times, stop sending
-
- if(!on)
- return
- //TO_WORLD("[src] ([src.id]) - [signal.debug_print()]")
- var/send_count = 0
-
- signal.data["slow"] += rand(0, round((100-integrity))) // apply some lag based on integrity
-
- /*
- // Edit by Atlantis: Commented out as emergency fix due to causing extreme delays in communications.
- // Apply some lag based on traffic rates
- var/netlag = round(traffic / 50)
- if(netlag > signal.data["slow"])
- signal.data["slow"] = netlag
- */
-// Loop through all linked machines and send the signal or copy.
- for(var/obj/machinery/telecomms/machine in links)
- if(filter && !istype( machine, text2path(filter) ))
- continue
- if(!machine.on)
- continue
- if(amount && send_count >= amount)
- break
- if(machine.loc.z != listening_level)
- if(long_range_link == 0 && machine.long_range_link == 0)
- continue
- // If we're sending a copy, be sure to create the copy for EACH machine and paste the data
- var/datum/signal/copy
- if(copysig)
- copy = new
- copy.transmission_method = TRANSMISSION_SUBSPACE
- copy.frequency = signal.frequency
- copy.data = signal.data.Copy()
-
- // Keep the "original" signal constant
- if(!signal.data["original"])
- copy.data["original"] = signal
- else
- copy.data["original"] = signal.data["original"]
-
- send_count++
- if(machine.is_freq_listening(signal))
- machine.traffic++
-
- if(copysig && copy)
- machine.receive_information(copy, src)
- else
- machine.receive_information(signal, src)
-
-
- if(send_count > 0 && is_freq_listening(signal))
- traffic++
-
- return send_count
-
-/obj/machinery/telecomms/proc/relay_direct_information(datum/signal/signal, obj/machinery/telecomms/machine)
- // send signal directly to a machine
- machine.receive_information(signal, src)
-
-/obj/machinery/telecomms/proc/receive_information(datum/signal/signal, obj/machinery/telecomms/machine_from)
- // receive information from linked machinery
-
-/obj/machinery/telecomms/proc/is_freq_listening(datum/signal/signal)
- // return 1 if found, 0 if not found
- if(!signal)
- return 0
- if((signal.frequency in freq_listening) || (!freq_listening.len))
- return 1
- else
- return 0
-
-/obj/machinery/telecomms/Initialize(mapload)
- GLOB.telecomms_list += src
- . = ..()
-
- //Set the listening_level if there's none.
- if(!listening_level)
- //Defaults to our Z level!
- var/turf/position = get_turf(src)
- listening_level = position.z
- return INITIALIZE_HINT_LATELOAD
-
-/obj/machinery/telecomms/LateInitialize()
- if(autolinkers.len)
- // Links nearby machines
- if(!long_range_link)
- for(var/obj/machinery/telecomms/T in orange(20, src))
- add_link(T)
- else
- for(var/obj/machinery/telecomms/T in GLOB.telecomms_list)
- add_link(T)
- return ..()
-
-/obj/machinery/telecomms/Destroy()
- GLOB.telecomms_list -= src
- for(var/obj/machinery/telecomms/comm in GLOB.telecomms_list)
- comm.links -= src
- links = list()
- ..()
-
-// Used in auto linking
-/obj/machinery/telecomms/proc/add_link(var/obj/machinery/telecomms/T)
- var/pos_z = get_z(src)
- var/tpos_z = get_z(T)
- if((pos_z == tpos_z) || (src.long_range_link && T.long_range_link))
- for(var/x in autolinkers)
- if(T.autolinkers.Find(x))
- if(src != T)
- links |= T
-
-/obj/machinery/telecomms/update_icon()
- if(on)
- icon_state = initial(icon_state)
- else
- icon_state = "[initial(icon_state)]_off"
-
-/obj/machinery/telecomms/proc/update_power()
- if(toggled)
- if(machine_stat & (BROKEN|NOPOWER|EMPED) || integrity <= 0) // if powered, on. if not powered, off. if too damaged, off
- on = FALSE
- else
- on = TRUE
- else
- on = FALSE
-
-/obj/machinery/telecomms/process()
- update_power()
-
- // Check heat and generate some
- checkheat()
-
- // Update the icon
- update_icon()
-
- if(traffic > 0)
- traffic -= netspeed
-
-/obj/machinery/telecomms/emp_act(severity)
- if(prob(100/severity))
- if(!(machine_stat & EMPED))
- machine_stat |= EMPED
- var/duration = (300 * 10)/severity
- spawn(rand(duration - 20, duration + 20)) // Takes a long time for the machines to reboot.
- machine_stat &= ~EMPED
- ..()
-
-/obj/machinery/telecomms/proc/checkheat()
- // Checks heat from the environment and applies any integrity damage
- var/damage_chance = 0 // Percent based chance of applying 1 integrity damage this tick
- switch(loc.return_temperature())
- if((T0C + 40) to (T0C + 70)) // 40C-70C, minor overheat, 10% chance of taking damage
- damage_chance = 10
- if((T0C + 70) to (T0C + 130)) // 70C-130C, major overheat, 25% chance of taking damage
- damage_chance = 25
- if((T0C + 130) to (T0C + 200)) // 130C-200C, dangerous overheat, 50% chance of taking damage
- damage_chance = 50
- if((T0C + 200) to INFINITY) // More than 200C, INFERNO. Takes damage every tick.
- damage_chance = 100
- if (damage_chance && prob(damage_chance))
- integrity = clamp( integrity - 1, 0, 100)
-
- if(delay > 0)
- delay--
- else if(on)
- produce_heat()
- delay = initial(delay)
-
-/obj/machinery/telecomms/proc/produce_heat()
- if (!produces_heat)
- return
-
- if (!use_power)
- return
-
- if(!(machine_stat & (NOPOWER|BROKEN)))
- var/turf/simulated/L = loc
- if(istype(L))
- var/datum/gas_mixture/env = L.return_air()
-
- var/transfer_moles = 0.25 * env.total_moles
-
- var/datum/gas_mixture/removed = env.remove(transfer_moles)
-
- if(removed)
-
- var/heat_produced = idle_power_usage //obviously can't produce more heat than the machine draws from it's power source
- if (traffic <= 0)
- heat_produced *= 0.30 //if idle, produce less heat.
-
- removed.adjust_thermal_energy(heat_produced)
-
- env.merge(removed)
-/*
- The receiver idles and receives messages from subspace-compatible radio equipment;
- primarily headsets. They then just relay this information to all linked devices,
- which can would probably be network hubs.
-
- Link to Processor Units in case receiver can't send to bus units.
-*/
-
-/obj/machinery/telecomms/receiver
- name = "Subspace Receiver"
- icon_state = "broadcast receiver"
- desc = "This machine has a dish-like shape and green lights. It is designed to detect and process subspace radio activity."
- density = 1
- anchored = 1
- use_power = USE_POWER_IDLE
- idle_power_usage = 600
- machinetype = 1
- produces_heat = 0
- circuit = /obj/item/circuitboard/telecomms/receiver
- //Vars only used if you're using the overmap
- var/overmap_range = 0
- var/overmap_range_min = 0
- var/overmap_range_max = 5
-
- var/list/linked_radios_weakrefs = list()
-
-/obj/machinery/telecomms/receiver/Initialize(mapload)
- . = ..()
- default_apply_parts()
-
-/obj/machinery/telecomms/receiver/proc/link_radio(var/obj/item/radio/R)
- if(!istype(R))
- return
- linked_radios_weakrefs |= WEAKREF(R)
-
-/obj/machinery/telecomms/receiver/receive_signal(datum/signal/signal)
- if(!on) // has to be on to receive messages
- return
- if(!signal)
- return
- if(!check_receive_level(signal))
- return
-
- if(signal.transmission_method == TRANSMISSION_SUBSPACE)
-
- if(is_freq_listening(signal)) // detect subspace signals
-
- //Remove the level and then start adding levels that it is being broadcasted in.
- signal.data["level"] = list()
-
- var/can_send = relay_information(signal, "/obj/machinery/telecomms/hub") // ideally relay the copied information to relays
- if(!can_send)
- relay_information(signal, "/obj/machinery/telecomms/bus") // Send it to a bus instead, if it's linked to one
-
-/obj/machinery/telecomms/receiver/proc/check_receive_level(datum/signal/signal)
- // If it's a direct message from a bluespace radio, we eat it and convert it into a subspace signal locally
- if(signal.transmission_method == TRANSMISSION_BLUESPACE)
- var/obj/item/radio/R = signal.data["radio"]
-
- //Who're you?
- if(!(WEAKREF(R) in linked_radios_weakrefs))
- signal.data["reject"] = 1
- return 0
-
- //We'll resend this for you
- signal.data["level"] = z
- signal.transmission_method = TRANSMISSION_SUBSPACE
- return 1
-
- //Where can we hear?
- var/list/listening_levels = GLOB.using_map.get_map_levels(listening_level, TRUE, overmap_range)
-
- // We couldn't 'hear' it, maybe a relay linked to our hub can 'hear' it
- if(!(signal.data["level"] in listening_levels))
- for(var/obj/machinery/telecomms/hub/H in links)
- var/list/relayed_levels = list()
- for(var/obj/machinery/telecomms/relay/R in H.links)
- if(R.can_receive(signal))
- relayed_levels |= R.listening_level
- if(signal.data["level"] in relayed_levels)
- return 1
- return 0
- return 1
-
-
-/*
- The HUB idles until it receives information. It then passes on that information
- depending on where it came from.
-
- This is the heart of the Telecommunications Network, sending information where it
- is needed. It mainly receives information from long-distance Relays and then sends
- that information to be processed. Afterwards it gets the uncompressed information
- from Servers/Buses and sends that back to the relay, to then be broadcasted.
-*/
-
-/obj/machinery/telecomms/hub
- name = "Telecommunication Hub"
- icon_state = "hub"
- desc = "A mighty piece of hardware used to send/receive massive amounts of data."
- density = 1
- anchored = 1
- use_power = USE_POWER_IDLE
- idle_power_usage = 1600
- machinetype = 7
- circuit = /obj/item/circuitboard/telecomms/hub
- long_range_link = 1
- netspeed = 40
-
-/obj/machinery/telecomms/hub/Initialize(mapload)
- . = ..()
- default_apply_parts()
-
-/obj/machinery/telecomms/hub/receive_information(datum/signal/signal, obj/machinery/telecomms/machine_from)
- if(is_freq_listening(signal))
- if(istype(machine_from, /obj/machinery/telecomms/receiver))
- //If the signal is compressed, send it to the bus.
- relay_information(signal, "/obj/machinery/telecomms/bus", 1) // ideally relay the copied information to bus units
- else
- // Get a list of relays that we're linked to, then send the signal to their levels.
- relay_information(signal, "/obj/machinery/telecomms/relay", 1)
- relay_information(signal, "/obj/machinery/telecomms/broadcaster", 1) // Send it to a broadcaster.
-
-
-/*
- The relay idles until it receives information. It then passes on that information
- depending on where it came from.
-
- The relay is needed in order to send information pass Z levels. It must be linked
- with a HUB, the only other machine that can send/receive pass Z levels.
-*/
-
-/obj/machinery/telecomms/relay
- name = "Telecommunication Relay"
- icon_state = "relay"
- desc = "A mighty piece of hardware used to send massive amounts of data far away."
- density = 1
- anchored = 1
- use_power = USE_POWER_IDLE
- idle_power_usage = 600
- machinetype = 8
- produces_heat = 0
- circuit = /obj/item/circuitboard/telecomms/relay
- netspeed = 5
- long_range_link = 1
- var/broadcasting = 1
- var/receiving = 1
-
-/obj/machinery/telecomms/relay/Initialize(mapload)
- . = ..()
- default_apply_parts()
-
-/obj/machinery/telecomms/relay/onTransitZ(old_z, new_z)
- . = ..()
- listening_level = z
-
-/obj/machinery/telecomms/relay/receive_information(datum/signal/signal, obj/machinery/telecomms/machine_from)
-
- // Add our level and send it back
- if(can_send(signal))
- signal.data["level"] |= GLOB.using_map.get_map_levels(listening_level)
-
-// Checks to see if it can send/receive.
-
-/obj/machinery/telecomms/relay/proc/can(datum/signal/signal)
- if(!on)
- return 0
- if(!is_freq_listening(signal))
- return 0
- return 1
-
-/obj/machinery/telecomms/relay/proc/can_send(datum/signal/signal)
- if(!can(signal))
- return 0
- return broadcasting
-
-/obj/machinery/telecomms/relay/proc/can_receive(datum/signal/signal)
- if(!can(signal))
- return 0
- return receiving
-
-/*
- The bus mainframe idles and waits for hubs to relay them signals. They act
- as junctions for the network.
-
- They transfer uncompressed subspace packets to processor units, and then take
- the processed packet to a server for logging.
-
- Link to a subspace hub if it can't send to a server.
-*/
-
-/obj/machinery/telecomms/bus
- name = "Bus Mainframe"
- icon_state = "bus"
- desc = "A mighty piece of hardware used to send massive amounts of data quickly."
- density = 1
- anchored = 1
- use_power = USE_POWER_IDLE
- idle_power_usage = 1000
- machinetype = 2
- circuit = /obj/item/circuitboard/telecomms/bus
- netspeed = 40
- var/change_frequency = 0
-
-/obj/machinery/telecomms/bus/Initialize(mapload)
- . = ..()
- default_apply_parts()
-
-/obj/machinery/telecomms/bus/receive_information(datum/signal/signal, obj/machinery/telecomms/machine_from)
-
- if(is_freq_listening(signal))
-
- if(change_frequency)
- signal.frequency = change_frequency
-
- if(!istype(machine_from, /obj/machinery/telecomms/processor) && machine_from != src) // Signal must be ready (stupid assuming machine), let's send it
- // send to one linked processor unit
- var/send_to_processor = relay_information(signal, "/obj/machinery/telecomms/processor")
-
- if(send_to_processor)
- return
- // failed to send to a processor, relay information anyway
- signal.data["slow"] += rand(1, 5) // slow the signal down only slightly
- src.receive_information(signal, src)
-
- // Try sending it!
- var/list/try_send = list("/obj/machinery/telecomms/server", "/obj/machinery/telecomms/hub", "/obj/machinery/telecomms/broadcaster", "/obj/machinery/telecomms/bus")
- var/i = 0
- for(var/send in try_send)
- if(i)
- signal.data["slow"] += rand(0, 1) // slow the signal down only slightly
- i++
- var/can_send = relay_information(signal, send)
- if(can_send)
- break
-
-
-
-/*
- The processor is a very simple machine that decompresses subspace signals and
- transfers them back to the original bus. It is essential in producing audible
- data.
-
- Link to servers if bus is not present
-*/
-
-/obj/machinery/telecomms/processor
- name = "Processor Unit"
- icon_state = "processor"
- desc = "This machine is used to process large quantities of information."
- density = 1
- anchored = 1
- use_power = USE_POWER_IDLE
- idle_power_usage = 600
- machinetype = 3
- delay = 5
- circuit = /obj/item/circuitboard/telecomms/processor
- var/process_mode = 1 // 1 = Uncompress Signals, 0 = Compress Signals
-
-/obj/machinery/telecomms/processor/Initialize(mapload)
- . = ..()
- default_apply_parts()
-
-/obj/machinery/telecomms/processor/receive_information(datum/signal/signal, obj/machinery/telecomms/machine_from)
-
- if(is_freq_listening(signal))
-
- if(process_mode)
- signal.data["compression"] = 0 // uncompress subspace signal
- else
- signal.data["compression"] = 100 // even more compressed signal
-
- if(istype(machine_from, /obj/machinery/telecomms/bus))
- relay_direct_information(signal, machine_from) // send the signal back to the machine
- else // no bus detected - send the signal to servers instead
- signal.data["slow"] += rand(5, 10) // slow the signal down
- relay_information(signal, "/obj/machinery/telecomms/server")
-
-
-/*
- The server logs all traffic and signal data. Once it records the signal, it sends
- it to the subspace broadcaster.
-
- Store a maximum of 100 logs and then deletes them.
-*/
-
-
-/obj/machinery/telecomms/server
- name = "Telecommunication Server"
- icon_state = "comm_server"
- desc = "A machine used to store data and network statistics."
- density = 1
- anchored = 1
- use_power = USE_POWER_IDLE
- idle_power_usage = 300
- machinetype = 4
- circuit = /obj/item/circuitboard/telecomms/server
- var/list/log_entries = list()
- var/list/stored_names = list()
- var/list/TrafficActions = list()
- var/logs = 0 // number of logs
- var/totaltraffic = 0 // gigabytes (if > 1024, divide by 1024 -> terrabytes)
-
- var/list/memory = list() // stored memory
- var/rawcode = "" // the code to compile (raw text)
- var/autoruncode = 0 // 1 if the code is set to run every time a signal is picked up
-
- var/encryption = "null" // encryption key: ie "password"
- var/salt = "null" // encryption salt: ie "123comsat"
- // would add up to md5("password123comsat")
- var/obj/item/radio/headset/server_radio = null
-
-/obj/machinery/telecomms/server/Initialize(mapload)
- . = ..()
- server_radio = new()
-
-/obj/machinery/telecomms/server/Initialize(mapload)
- . = ..()
- default_apply_parts()
-
-/obj/machinery/telecomms/server/receive_information(datum/signal/signal, obj/machinery/telecomms/machine_from)
-
- if(signal.data["message"])
-
- if(is_freq_listening(signal))
-
- if(traffic > 0)
- totaltraffic += traffic // add current traffic to total traffic
-
- //Is this a test signal? Bypass logging
- if(signal.data["type"] != SIGNAL_TEST)
-
- // If signal has a message and appropriate frequency
-
- update_logs()
-
- var/datum/comm_log_entry/log = new
- var/mob/M = signal.data["mob"]
-
- // Copy the signal.data entries we want
- log.parameters["mobtype"] = signal.data["mobtype"]
- log.parameters["job"] = signal.data["job"]
- log.parameters["key"] = signal.data["key"]
- log.parameters["vmessage"] = signal.data["message"]
- log.parameters["vname"] = signal.data["vname"]
- log.parameters["message"] = signal.data["message"]
- log.parameters["name"] = signal.data["name"]
- log.parameters["realname"] = signal.data["realname"]
- log.parameters["timecode"] = worldtime2stationtime(world.time)
-
- var/race = "unknown"
- if(ishuman(M))
- var/mob/living/carbon/human/H = M
- race = "[H.species.name]"
- log.parameters["intelligible"] = 1
- else if(isbrain(M))
- race = "Brain"
- log.parameters["intelligible"] = 1
- else if(M.isMonkey())
- race = "Monkey"
- else if(issilicon(M))
- race = "Artificial Life"
- log.parameters["intelligible"] = 1
- else if(isslime(M))
- race = "Slime"
- else if(isanimal(M))
- race = "Domestic Animal"
-
- log.parameters["race"] = race
-
- if(!istype(M, /mob/new_player) && M)
- log.parameters["uspeech"] = M.universal_speak
- else
- log.parameters["uspeech"] = 0
-
- // If the signal is still compressed, make the log entry gibberish
- if(signal.data["compression"] > 0)
- log.parameters["message"] = Gibberish(signal.data["message"], signal.data["compression"] + 50)
- log.parameters["job"] = Gibberish(signal.data["job"], signal.data["compression"] + 50)
- log.parameters["name"] = Gibberish(signal.data["name"], signal.data["compression"] + 50)
- log.parameters["realname"] = Gibberish(signal.data["realname"], signal.data["compression"] + 50)
- log.parameters["vname"] = Gibberish(signal.data["vname"], signal.data["compression"] + 50)
- log.input_type = "Corrupt File"
-
- // Log and store everything that needs to be logged
- log_entries.Add(log)
- if(!(signal.data["name"] in stored_names))
- stored_names.Add(signal.data["name"])
- logs++
- signal.data["server"] = src
-
- // Give the log a name
- var/identifier = num2text( rand(-1000,1000) + world.time )
- log.name = "data packet ([md5(identifier)])"
-
- var/can_send = relay_information(signal, "/obj/machinery/telecomms/hub")
- if(!can_send)
- relay_information(signal, "/obj/machinery/telecomms/broadcaster")
-
-
-/obj/machinery/telecomms/server/proc/setcode(var/t)
- if(t)
- if(istext(t))
- rawcode = t
-
-/obj/machinery/telecomms/server/proc/update_logs()
- // start deleting the very first log entry
- if(logs >= 400)
- for(var/i = 1, i <= logs, i++) // locate the first garbage collectable log entry and remove it
- var/datum/comm_log_entry/L = log_entries[i]
- if(L.garbage_collector)
- log_entries.Remove(L)
- logs--
- break
-
-/obj/machinery/telecomms/server/proc/add_entry(var/content, var/input)
- var/datum/comm_log_entry/log = new
- var/identifier = num2text( rand(-1000,1000) + world.time )
- log.name = "[input] ([md5(identifier)])"
- log.input_type = input
- log.parameters["message"] = content
- log.parameters["timecode"] = stationtime2text()
- log_entries.Add(log)
- update_logs()
-
-// Simple log entry datum
-
-/datum/comm_log_entry
- var/parameters = list() // carbon-copy to signal.data[]
- var/name = "data packet (#)"
- var/garbage_collector = 1 // if set to 0, will not be garbage collected
- var/input_type = "Speech File"
-
-//Generic telecomm connectivity test proc
-/proc/can_telecomm(var/atom/A, var/atom/B, var/ad_hoc = FALSE)
- if(!A || !B)
- log_debug(SPAN_DEBUG("can_telecomm(): Undefined endpoints!"))
- return FALSE
-
- //Can't in this case, obviously!
- if(is_jammed(A) || is_jammed(B))
- return FALSE
-
- //Items don't have a Z when inside an object or mob
- var/turf/src_z = get_z(A)
- var/turf/dst_z = get_z(B)
-
- //Nullspace, probably.
- if(!src_z || !dst_z)
- return FALSE
-
- //We can do the simple check first, if you have ad_hoc radios.
- if(ad_hoc && src_z == dst_z)
- return TRUE
-
- return src_z in GLOB.using_map.get_map_levels(dst_z, TRUE, om_range = DEFAULT_OVERMAP_RANGE)
diff --git a/code/game/objects/items/circuitboards/computer/_computer.dm b/code/game/objects/items/circuitboards/computer/_computer.dm
new file mode 100644
index 00000000000..03ed937eb4a
--- /dev/null
+++ b/code/game/objects/items/circuitboards/computer/_computer.dm
@@ -0,0 +1,2 @@
+/obj/item/circuitboard/computer
+ board_type = new /datum/frame/frame_types/computer
diff --git a/code/game/objects/items/circuitboards/computer/misc.dm b/code/game/objects/items/circuitboards/computer/misc.dm
new file mode 100644
index 00000000000..705287419e6
--- /dev/null
+++ b/code/game/objects/items/circuitboards/computer/misc.dm
@@ -0,0 +1,3 @@
+/obj/item/circuitboard/computer/bioscan
+ name = T_BOARD("Bioscan Control Console")
+ build_path = /obj/machinery/computer/bioscan
diff --git a/code/game/objects/items/circuitboards/machinery/_machine.dm b/code/game/objects/items/circuitboards/machinery/_machine.dm
new file mode 100644
index 00000000000..03eabb04d16
--- /dev/null
+++ b/code/game/objects/items/circuitboards/machinery/_machine.dm
@@ -0,0 +1,2 @@
+/obj/item/circuitboard/machine
+ board_type = new /datum/frame/frame_types/machine
diff --git a/code/game/objects/items/circuitboards/machinery/misc.dm b/code/game/objects/items/circuitboards/machinery/misc.dm
new file mode 100644
index 00000000000..6516b43c1c1
--- /dev/null
+++ b/code/game/objects/items/circuitboards/machinery/misc.dm
@@ -0,0 +1,6 @@
+/obj/item/circuitboard/machine/bioscan
+ name = T_BOARD("Bioscan Antenna")
+ build_path = /obj/machinery/bioscan_antenna
+ req_components = list(
+ /obj/item/stock_parts/scanning_module = 1,
+ )
diff --git a/code/modules/automata/sonar.dm b/code/modules/automata/sonar.dm
index 4cc44c7cd9c..0b16f942e6c 100644
--- a/code/modules/automata/sonar.dm
+++ b/code/modules/automata/sonar.dm
@@ -31,6 +31,12 @@
return ..()
/datum/automata/wave/sonar/proc/flick_sonar(atom/movable/AM)
+ if(ismob(AM))
+ var/mob/M = AM
+ if(M.client && !TIMER_COOLDOWN_CHECK(M, CD_INDEX_SONAR_NOISE))
+ to_chat(M, SPAN_WARNING("You hear a quiet click."))
+ TIMER_COOLDOWN_START(M, CD_INDEX_SONAR_NOISE, 7.5 SECONDS)
+ // todo: M.provoke() for AI...
var/atom/movable/holder = AM.make_sonar_image(resolution)
if(holder)
holder.alpha = 0
diff --git a/code/modules/research/designs/circuits/circuit.dm b/code/modules/research/designs/circuits/circuit.dm
new file mode 100644
index 00000000000..9c6d44a1bd9
--- /dev/null
+++ b/code/modules/research/designs/circuits/circuit.dm
@@ -0,0 +1,2 @@
+/datum/design/circuit
+ abstract_type = /datum/design/circuit
diff --git a/code/modules/research/designs/circuits/computer.dm b/code/modules/research/designs/circuits/computer.dm
new file mode 100644
index 00000000000..1374a55500b
--- /dev/null
+++ b/code/modules/research/designs/circuits/computer.dm
@@ -0,0 +1,6 @@
+/datum/design/circuit/computer
+ abstract_type = /datum/design/circuit/computer
+
+/datum/design/circuit/computer/AssembleDesignName()
+ name = "Computer circuit design ([name])"
+
diff --git a/code/modules/research/designs/circuits/computers/misc.dm b/code/modules/research/designs/circuits/computers/misc.dm
new file mode 100644
index 00000000000..ac50d9afabd
--- /dev/null
+++ b/code/modules/research/designs/circuits/computers/misc.dm
@@ -0,0 +1,5 @@
+/datum/design/circuit/computer/bioscan_console
+ name = "Bioscan Console"
+ build_path = /obj/item/circuitboard/computer/bioscan
+ id = "console_bioscan"
+ req_tech = list(TECH_DATA = 1)
diff --git a/code/modules/research/designs/circuits/machine.dm b/code/modules/research/designs/circuits/machine.dm
new file mode 100644
index 00000000000..8ac273cc4f8
--- /dev/null
+++ b/code/modules/research/designs/circuits/machine.dm
@@ -0,0 +1,5 @@
+/datum/design/circuit/machine
+ abstract_type = /datum/design/circuit/machine
+
+/datum/design/circuit/machine/AssembleDesignName()
+ name = "Machine circuit design ([name])"
diff --git a/code/modules/research/designs/circuits/machines/misc.dm b/code/modules/research/designs/circuits/machines/misc.dm
new file mode 100644
index 00000000000..944687ff1dc
--- /dev/null
+++ b/code/modules/research/designs/circuits/machines/misc.dm
@@ -0,0 +1,5 @@
+/datum/design/circuit/machine/bioscan_antenna
+ name = "Bioscan Antenna"
+ build_path = /obj/item/circuitboard/machine/bioscan
+ id = "machine_bioscan"
+ req_tech = list(TECH_DATA = 1)
diff --git a/code/modules/tgui/tgui_input_text.dm b/code/modules/tgui/tgui_input_text.dm
index f506c6d360a..d4a5290bb41 100644
--- a/code/modules/tgui/tgui_input_text.dm
+++ b/code/modules/tgui/tgui_input_text.dm
@@ -1,3 +1,21 @@
+/**
+ * Default operation for getting text input from a user.
+ *
+ * This can either route to TGUI or to native BYOND input depending on user prefs.
+ *
+ * @params
+ * * user - The user to show the text input to.
+ * * message - The content of the text input, shown in the body of the TGUI window.
+ * * title - The title of the text input modal, shown on the top of the TGUI window.
+ * * default - The default (or current) value, shown as a placeholder.
+ * * max_length - Specifies a max length for input. MAX_MESSAGE_LEN is default (1024)
+ * * multiline - Bool that determines if the input box is much larger. Good for large messages, laws, etc.
+ * * encode - Toggling this determines if input is filtered via html_encode. Setting this to FALSE gives raw input.
+ * * timeout - The timeout of the textbox, after which the modal will close and qdel itself. Set to zero for no timeout.
+ */
+/proc/default_input_text(mob/user, message = "", title = "Text Input", default, max_length = MAX_MESSAGE_LEN, multiline = FALSE, encode = TRUE, timeout = 0)
+ return input(user, message, title, default) as text|null
+
/**
* Creates a TGUI window with a text input. Returns the user's response.
*
diff --git a/icons/README.md b/icons/README.md
index a7d190a867f..b5ba6b542f3 100644
--- a/icons/README.md
+++ b/icons/README.md
@@ -19,6 +19,7 @@ Yes, this currently includes all turfs, mobs, objs, and misc things. Sorry. We'l
- /job - basic job uniforms
- /misc - unsorted things
- /rank - "formal" "rank"-wear like flight suits, military/naval, etc
+ - /machinery - used for sprites for general, non-module machines. use subdirectories if more than one .dmi is needed.
- /mapping - used for sprites for in-map-editor objects that are invisible in game world
- /helpers - mapping helpers like autopipe/autocable/baseturf replacers go here
- /landmarks - landmark icons
diff --git a/icons/machinery/bioscan.dmi b/icons/machinery/bioscan.dmi
new file mode 100644
index 00000000000..e0acd178a31
Binary files /dev/null and b/icons/machinery/bioscan.dmi differ
diff --git a/tgui/package.json b/tgui/package.json
index 3fb2d7f0cb0..9d4ca4a5d6b 100644
--- a/tgui/package.json
+++ b/tgui/package.json
@@ -42,6 +42,7 @@
"eslint-plugin-react": "^7.24.0",
"eslint-plugin-unused-imports": "^1.1.4",
"file-loader": "^6.2.0",
+ "ignore-loader": "^0.1.2",
"inferno": "^7.4.8",
"jest": "^27.0.6",
"jest-circus": "^27.0.6",
diff --git a/tgui/packages/tgui/backend.ts b/tgui/packages/tgui/backend.ts
index 4fd6ae5f7e5..0c34690abdd 100644
--- a/tgui/packages/tgui/backend.ts
+++ b/tgui/packages/tgui/backend.ts
@@ -246,7 +246,7 @@ type BackendState = {
title: string,
status: number,
interface: string,
- refreshing: boolean,
+ refreshing: number,
window: {
key: string,
size: [number, number],
diff --git a/tgui/packages/tgui/components/Collapsible.js b/tgui/packages/tgui/components/Collapsible.tsx
similarity index 68%
rename from tgui/packages/tgui/components/Collapsible.js
rename to tgui/packages/tgui/components/Collapsible.tsx
index 8b915814ba4..548bf21663a 100644
--- a/tgui/packages/tgui/components/Collapsible.js
+++ b/tgui/packages/tgui/components/Collapsible.tsx
@@ -4,16 +4,32 @@
* @license MIT
*/
-import { Component } from 'inferno';
-import { Box } from './Box';
+import { BooleanLike } from 'common/react';
+import { Component, InfernoNode } from 'inferno';
+import { Box, BoxProps } from './Box';
import { Button } from './Button';
-export class Collapsible extends Component {
+interface CollapsibleProps extends BoxProps {
+ buttons?: InfernoNode;
+ color?: string;
+ title?: string;
+ open?: BooleanLike;
+}
+
+interface CollapsibleState {
+ open: boolean;
+}
+
+export class Collapsible extends Component {
+ state: CollapsibleState = {
+ open: false,
+ }
+
constructor(props) {
super(props);
const { open } = props;
this.state = {
- open: open || false,
+ open: !!open || false,
};
}
diff --git a/tgui/packages/tgui/interfaces/README.md b/tgui/packages/tgui/interfaces/README.md
new file mode 100644
index 00000000000..7ef39b7a896
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/README.md
@@ -0,0 +1,36 @@
+# Interfaces Folder
+
+TGUI interfaces go in here.
+
+## Interface Routing
+
+The following paths are routed:
+- ./ (for everything else)
+. ./computers (for computers and consoles)
+- ./items (for items)
+- ./machines (for machinery for any kind)
+- ./modules (for TGUI modules)
+
+This means that we **only** search for interfaces in these paths. `routes.js` controls this routing.
+
+Interface routing uses the following paths:
+- {dir}/{name}.js
+- {dir}/{name}.tsx
+- {dir}/{name}/index.js
+- {dir}/{name}/index.tsx
+
+This means your interface must match one of these, *and* export the {name} in an `export const` for this to work!
+
+## Interface Prefixes
+
+Interfaces are resolved by name only. Two interfaces in different folders with the same name will collide.
+Hence, prefixes are enforced for the following folders:
+
+- ./modules interfaces should begin with `TGUI`, e.g. `TGUICardMod`
+- ./ui interfaces should begin with `UI`, e.g. `UIListPicker`
+
+## Common Interfaces
+
+Common interfaces that are meant to be *potentially* standalone can be a /datum/tgui_module, whose interfaces generally goes in ./modules.
+
+Common interfaces that are not meant to be standalone go in ./common. See ./common/Access.tsx for an example.
diff --git a/tgui/packages/tgui/interfaces/TelecommsLogBrowser.js b/tgui/packages/tgui/interfaces/TelecommsLogBrowser.tsx
similarity index 61%
rename from tgui/packages/tgui/interfaces/TelecommsLogBrowser.js
rename to tgui/packages/tgui/interfaces/TelecommsLogBrowser.tsx
index 148d8941de7..b64f5933aa6 100644
--- a/tgui/packages/tgui/interfaces/TelecommsLogBrowser.js
+++ b/tgui/packages/tgui/interfaces/TelecommsLogBrowser.tsx
@@ -1,19 +1,56 @@
import { round } from 'common/math';
+import { BooleanLike } from 'common/react';
import { Fragment } from 'inferno';
import { useBackend } from "../backend";
-import { Box, Button, Flex, NoticeBox, LabeledList, Section } from "../components";
+import { Box, Button, Flex, NoticeBox, LabeledList, Section, Collapsible } from "../components";
+import { formatTime } from '../format';
import { Window } from "../layouts";
-export const TelecommsLogBrowser = (props, context) => {
- const { act, data } = useBackend(context);
+interface TelecommsLogBrowserContext {
+ universal_translate: BooleanLike;
+ network: string;
+ temp: UITemporaryMessage;
+ servers: [TelecommsServer];
+ selectedServer: TelecommsServerSelected;
+}
- const {
- universal_translate,
- network,
- temp,
- servers,
- selectedServer,
- } = data;
+interface TelecommsServer {
+ id: string;
+ name: string;
+}
+
+interface TelecommsServerSelected {
+ id: string;
+ totalTraffic: number;
+ logs: [MessageLog];
+ triangulating: BooleanLike;
+ triangulation: [TriangulationEntry];
+}
+
+interface TriangulationEntry {
+ name: string;
+ x: number;
+ y: number;
+ z: string;
+ accuracy: number;
+ last: number;
+ tag: string;
+}
+
+interface MessageLog {
+ name: string;
+ input_type: string;
+ id: number;
+ parameters: [Record];
+}
+
+interface UITemporaryMessage {
+ color: string;
+ text: string;
+}
+
+export const TelecommsLogBrowser = (props, context) => {
+ const { act, data } = useBackend(context);
return (
{
height={450}
resizable>
- {(temp && temp.length) ? (
+ {!!data.temp && (
- {temp}
+ {data.temp.text}
- ) : null}
+ )}
{
color="bad"
icon="exclamation-triangle"
content="Flush Buffer"
- disabled={servers.length === 0}
+ disabled={!!data.servers.length}
onClick={() => act('release')} />
)}>
- {selectedServer
+ {data.selectedServer
? (
+ server={data.selectedServer}
+ universal_translate={data.universal_translate} />
) : (
+ network={data.network}
+ servers={data.servers} />
)}
@@ -114,38 +150,70 @@ const TelecommsServerSelection = (props, context) => {
);
};
-const TelecommsSelectedServer = (props, context) => {
- const { act, data } = useBackend(context);
- const {
- network,
- server,
- universal_translate,
- } = props;
+interface TelecommsSelectedServerProps {
+ server: TelecommsServerSelected;
+ universal_translate: BooleanLike;
+}
+
+const TelecommsSelectedServer = (props: TelecommsSelectedServerProps, context) => {
+ const { act, data } = useBackend(context);
return (
act("mainmenu")} />
}>
-
-
- {server.totalTraffic >= 1024
- ? round(server.totalTraffic / 1024) + " Terrabytes"
- : server.totalTraffic + " Gigabytes"}
-
-
+
+
+
+ {props.server.totalTraffic >= 1024
+ ? round(props.server.totalTraffic / 1024, 1) + " Terrabytes"
+ : props.server.totalTraffic + " Gigabytes"}
+
+
+
+
+ act('toggle_triangulation')} />
+ }>
+ {props.server.triangulation.sort((a, b) => (a.last - b.last)).map((data) => (
+
+
+
+ {data.z} - {data.x} / {data.y}
+
+
+ ~{data.accuracy}m
+
+
+ {data.name}
+
+
+ {formatTime(data.last)} ago.
+
+
+
+ ))}
+
+
- {(!server.logs || !server.logs.length)
+ {(!props.server.logs || !props.server.logs.length)
? "No Logs Detected."
- : server.logs.map(log => (
+ : props.server.logs.map(log => (
{
onClick={() => act('delete', { id: log.id })} />
- ) : (universal_translate
+ ) : (props.universal_translate
|| log.parameters["uspeech"]
|| log.parameters["intelligible"])
?
@@ -189,7 +257,7 @@ const TelecommsSelectedServer = (props, context) => {
const TelecommsLog = (props, context) => {
- const { act, data } = useBackend(context);
+ const { act, data } = useBackend(context);
const {
log,
error,
diff --git a/tgui/packages/tgui/interfaces/computers/BioscanConsole.tsx b/tgui/packages/tgui/interfaces/computers/BioscanConsole.tsx
new file mode 100644
index 00000000000..c3e4838169e
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/computers/BioscanConsole.tsx
@@ -0,0 +1,104 @@
+import { BooleanLike } from "../../../common/react";
+import { useBackend } from "../../backend";
+import { Button, Collapsible, Input, LabeledList, Section } from "../../components";
+import { Window } from "../../layouts";
+
+interface BioscanConsoleData {
+ network: string;
+ scan_ready: BooleanLike;
+ scan: BioscanResults;
+ antennas: [BioscanAntenna];
+}
+
+interface BioscanAntenna {
+ level: string;
+ id: string;
+ anchor: BooleanLike;
+ x: number;
+ y: number;
+ name: string;
+}
+
+interface BioscanResults {
+ levels: [BioscanLevel];
+}
+
+interface BioscanLevel {
+ id: string;
+ all: number;
+ complex: number;
+ complex_alive: number;
+ complex_dead: number;
+}
+
+export const BioscanConsole = (props, context) => {
+ let { act, data } = useBackend(context);
+ return (
+
+
+
+
+
+ {
+ data.antennas.sort((a, b) => (a.level.localeCompare(b.level))).map((antenna) => (
+
+
+
+ {antenna.x} / {antenna.y}
+
+
+ {antenna.level}
+
+
+ {antenna.anchor? "Anchored" : "Unanchored"}
+
+
+
+ ))
+ }
+
+
+
+ {data.scan? (
+ data.scan.levels.map((level) => (
+
+
+
+ {level.all}
+
+
+ {level.complex}
+
+
+ {level.complex_alive}
+
+
+ {level.complex_dead}
+
+
+
+ ))
+ ) : (
+ "No scan data."
+ )}
+
+
+
+ );
+};
diff --git a/tgui/packages/tgui/routes.js b/tgui/packages/tgui/routes.js
index 1f66bda6913..a4023339ecf 100644
--- a/tgui/packages/tgui/routes.js
+++ b/tgui/packages/tgui/routes.js
@@ -36,7 +36,6 @@ const SuspendedWindow = () => {
};
const RefreshingWindow = () => {
-
return (
@@ -55,6 +54,25 @@ const RefreshingWindow = () => {
);
};
+const interfaceSubdirectories = [
+ `.`,
+ `./computers`,
+ `./items`,
+ `./machines`,
+ `./modules`,
+ `./ui`,
+];
+
+const interfacePath = function* (name) {
+ for (let i = 0; i < interfaceSubdirectories.length; i++) {
+ let dir = interfaceSubdirectories[i];
+ yield `${dir}/${name}.js`;
+ yield `${dir}/${name}.tsx`;
+ yield `${dir}/${name}/index.js`;
+ yield `${dir}/${name}/index.tsx`;
+ }
+};
+
export const getRoutedComponent = store => {
const state = store.getState();
const { suspended, config } = selectBackend(state);
@@ -72,24 +90,20 @@ export const getRoutedComponent = store => {
}
}
const name = config?.interface;
- const interfacePathBuilders = [
- name => `./${name}.tsx`,
- name => `./${name}.js`,
- name => `./${name}/index.tsx`,
- name => `./${name}/index.js`,
- ];
+
let esModule;
- while (!esModule && interfacePathBuilders.length > 0) {
- const interfacePathBuilder = interfacePathBuilders.shift();
- const interfacePath = interfacePathBuilder(name);
+ for (let path of interfacePath(name)) {
try {
- esModule = requireInterface(interfacePath);
+ esModule = requireInterface(path);
}
catch (err) {
if (err.code !== 'MODULE_NOT_FOUND') {
throw err;
}
}
+ if (esModule) {
+ break;
+ }
}
if (!esModule) {
return routingError('notFound', name);
diff --git a/tgui/webpack.config.js b/tgui/webpack.config.js
index 1aa326eee87..fb23602a798 100644
--- a/tgui/webpack.config.js
+++ b/tgui/webpack.config.js
@@ -97,6 +97,14 @@ module.exports = (env = {}, argv) => {
},
],
},
+ {
+ test: /(readme|README)\.(md)$/,
+ use: [
+ {
+ loader: require.resolve('ignore-loader'),
+ },
+ ],
+ },
],
},
optimization: {
diff --git a/tgui/yarn.lock b/tgui/yarn.lock
index 16ce592fa67..b012b69ca23 100644
--- a/tgui/yarn.lock
+++ b/tgui/yarn.lock
@@ -4820,6 +4820,13 @@ __metadata:
languageName: node
linkType: hard
+"ignore-loader@npm:^0.1.2":
+ version: 0.1.2
+ resolution: "ignore-loader@npm:0.1.2"
+ checksum: 26b5f81b24e59c575d5314e1416c4ae21fb88e65b3c60c90581288ef925fcaa2b39e7f8f96cd449e488c217337d5240fba5a916f9aec073ec04495d7a5716ac4
+ languageName: node
+ linkType: hard
+
"ignore@npm:^4.0.6":
version: 4.0.6
resolution: "ignore@npm:4.0.6"
@@ -8359,6 +8366,7 @@ resolve@^2.0.0-next.3:
eslint-plugin-react: ^7.24.0
eslint-plugin-unused-imports: ^1.1.4
file-loader: ^6.2.0
+ ignore-loader: ^0.1.2
inferno: ^7.4.8
jest: ^27.0.6
jest-circus: ^27.0.6