diff --git a/code/modules/mob/living/silicon/robot/robot_defense.dm b/code/modules/mob/living/silicon/robot/robot_defense.dm
index 8f2c07336d0..3e13c423cc4 100644
--- a/code/modules/mob/living/silicon/robot/robot_defense.dm
+++ b/code/modules/mob/living/silicon/robot/robot_defense.dm
@@ -369,6 +369,8 @@ GLOBAL_LIST_INIT(blacklisted_borg_hats, typecacheof(list( //Hats that don't real
sleep(5)
to_chat(src, "LAW SYNCHRONISATION ERROR")
sleep(5)
+ if(user)
+ logevent("LOG: New user \[[replacetext(user.real_name," ","")]\], groups \[root\]")
to_chat(src, "Would you like to send a report to NanoTraSoft? Y/N")
sleep(10)
to_chat(src, "> N")
diff --git a/code/modules/modular_computers/computers/item/computer.dm b/code/modules/modular_computers/computers/item/computer.dm
index d0d81e82914..edf69678825 100644
--- a/code/modules/modular_computers/computers/item/computer.dm
+++ b/code/modules/modular_computers/computers/item/computer.dm
@@ -78,6 +78,23 @@
physical = null
return ..()
+/obj/item/modular_computer/pre_attack_secondary(atom/A, mob/living/user, params)
+ if(active_program?.tap(A, user, params))
+ user.do_attack_animation(A) //Emulate this animation since we kill the attack in three lines
+ playsound(loc, 'sound/weapons/tap.ogg', get_clamped_volume(), TRUE, -1) //Likewise for the tap sound
+ addtimer(CALLBACK(src, .proc/play_ping), 0.5 SECONDS, TIMER_UNIQUE) //Slightly delayed ping to indicate success
+ return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
+ return ..()
+
+
+/**
+ * Plays a ping sound.
+ *
+ * Timers runtime if you try to make them call playsound. Yep.
+ */
+/obj/item/modular_computer/proc/play_ping()
+ playsound(loc, 'sound/machines/ping.ogg', get_clamped_volume(), FALSE, -1)
+
/obj/item/modular_computer/AltClick(mob/user)
..()
if(issilicon(user))
diff --git a/code/modules/modular_computers/file_system/program.dm b/code/modules/modular_computers/file_system/program.dm
index 7462d180a02..dc485262f15 100644
--- a/code/modules/modular_computers/file_system/program.dm
+++ b/code/modules/modular_computers/file_system/program.dm
@@ -74,6 +74,21 @@
return computer.add_log(text)
return 0
+/**
+ *Runs when the device is used to attack an atom in non-combat mode.
+ *
+ *Simulates using the device to read or scan something. Tap is called by the computer during pre_attack
+ *and sends us all of the related info. If we return TRUE, the computer will stop the attack process
+ *there. What we do with the info is up to us, but we should only return TRUE if we actually perform
+ *an action of some sort.
+ *Arguments:
+ *A is the atom being tapped
+ *user is the person making the attack action
+ *params is anything the pre_attack() proc had in the same-named variable.
+*/
+/datum/computer_file/program/proc/tap(atom/A, mob/living/user, params)
+ return FALSE
+
/datum/computer_file/program/proc/is_supported_by_hardware(hardware_flag = 0, loud = 0, mob/user = null)
if(!(hardware_flag & usage_flags))
if(loud && computer && user)
diff --git a/code/modules/modular_computers/file_system/programs/borg_monitor.dm b/code/modules/modular_computers/file_system/programs/borg_monitor.dm
index 4b350051030..71011035aa1 100644
--- a/code/modules/modular_computers/file_system/programs/borg_monitor.dm
+++ b/code/modules/modular_computers/file_system/programs/borg_monitor.dm
@@ -10,7 +10,21 @@
size = 5
tgui_id = "NtosCyborgRemoteMonitor"
program_icon = "project-diagram"
- var/emagged = FALSE
+ var/emagged = FALSE ///Bool of if this app has already been emagged
+ var/list/loglist = list() ///A list to copy a borg's IC log list into
+ var/mob/living/silicon/robot/DL_source ///reference of a borg if we're downloading a log, or null if not.
+ var/DL_progress = -1 ///Progress of current download, 0 to 100, -1 for no current download
+
+/datum/computer_file/program/borg_monitor/Destroy()
+ loglist = null
+ DL_source = null
+ return ..()
+
+/datum/computer_file/program/borg_monitor/kill_program(forced = FALSE)
+ loglist = null //Not everything is saved if you close an app
+ DL_source = null
+ DL_progress = 0
+ return ..()
/datum/computer_file/program/borg_monitor/run_emag()
if(emagged)
@@ -18,6 +32,49 @@
emagged = TRUE
return TRUE
+/datum/computer_file/program/borg_monitor/tap(atom/A, mob/living/user, params)
+ var/mob/living/silicon/robot/borgo = A
+ if(!istype(borgo) || !borgo.modularInterface)
+ return FALSE
+ DL_source = borgo
+ DL_progress = 0
+
+ var/username = "unknown user"
+ var/obj/item/card/id/stored_card = computer.GetID()
+ if(istype(stored_card) && stored_card.registered_name)
+ username = "user [stored_card.registered_name]"
+ to_chat(borgo, "Request received from [username] for the system log file. Upload in progress.")//Damning evidence may be contained, so warn the borg
+ borgo.logevent("File request by [username]: /var/logs/syslog")
+ return TRUE
+
+/datum/computer_file/program/borg_monitor/process_tick()
+ if(!DL_source)
+ DL_progress = -1
+ return
+
+ var/turf/here = get_turf(computer)
+ var/turf/there = get_turf(DL_source)
+ if(!here.Adjacent(there))//If someone walked away, cancel the download
+ to_chat(DL_source, "Log upload failed: general connection error.")//Let the borg know the upload stopped
+ DL_source = null
+ DL_progress = -1
+ return
+
+ if(DL_progress == 100)
+ if(!DL_source || !DL_source.modularInterface) //sanity check, in case the borg or their modular tablet poofs somehow
+ loglist = list("System log of unit [DL_source.name]")
+ loglist += "Error -- Download corrupted."
+ else
+ loglist = DL_source.modularInterface.borglog.Copy()
+ loglist.Insert(1,"System log of unit [DL_source.name]")
+ DL_progress = -1
+ DL_source = null
+ for(var/datum/tgui/window in SStgui.open_uis_by_src[REF(src)])
+ window.send_full_update()
+ return
+
+ DL_progress += 25
+
/datum/computer_file/program/borg_monitor/ui_data(mob/user)
var/list/data = get_header_data()
@@ -40,6 +97,7 @@
var/list/cyborg_data = list(
name = R.name,
+ integ = round((R.health + 100) / 2), //mob heath is -100 to 100, we want to scale that to 0 - 100
locked_down = R.lockcharge,
status = R.stat,
shell_discon = shell,
@@ -49,6 +107,12 @@
ref = REF(R)
)
data["cyborgs"] += list(cyborg_data)
+ data["DL_progress"] = DL_progress
+ return data
+
+/datum/computer_file/program/borg_monitor/ui_static_data(mob/user)
+ var/list/data = list()
+ data["borglog"] = loglist
return data
/datum/computer_file/program/borg_monitor/ui_act(action, params)
diff --git a/tgui/packages/tgui/interfaces/NtosCyborgRemoteMonitor.js b/tgui/packages/tgui/interfaces/NtosCyborgRemoteMonitor.js
index 4aa273c6c6b..d03133d83d4 100644
--- a/tgui/packages/tgui/interfaces/NtosCyborgRemoteMonitor.js
+++ b/tgui/packages/tgui/interfaces/NtosCyborgRemoteMonitor.js
@@ -1,5 +1,5 @@
-import { useBackend } from '../backend';
-import { Box, Button, LabeledList, NoticeBox, Section } from '../components';
+import { useBackend, useSharedState } from '../backend';
+import { Box, Button, LabeledList, NoticeBox, ProgressBar, Section, Stack, Tabs } from '../components';
import { NtosWindow } from '../layouts';
export const NtosCyborgRemoteMonitor = (props, context) => {
@@ -7,19 +7,39 @@ export const NtosCyborgRemoteMonitor = (props, context) => {
-
+
);
};
+export const ProgressSwitch = param => {
+ switch (param) {
+ case -1:
+ return '_';
+ case 0:
+ return 'Connecting';
+ case 25:
+ return 'Starting Transfer';
+ case 50:
+ return 'Downloading';
+ case 75:
+ return 'Downloading';
+ case 100:
+ return 'Formatting';
+ }
+};
+
export const NtosCyborgRemoteMonitorContent = (props, context) => {
const { act, data } = useBackend(context);
+ const [tab_main, setTab_main] = useSharedState(context, 'tab_main', 1);
const {
card,
cyborgs = [],
+ DL_progress,
} = data;
+ const storedlog = data.borglog || [];
if (!cyborgs.length) {
return (
@@ -30,64 +50,134 @@ export const NtosCyborgRemoteMonitorContent = (props, context) => {
}
return (
- <>
- {!card && (
-
- Certain features require an ID card login.
-
+
+
+
+ setTab_main(1)}>
+ Cyborgs
+
+ setTab_main(2)}>
+ Stored Log File
+
+
+
+ {tab_main === 1 && (
+ <>
+ {!card && (
+
+
+ Certain features require an ID card login.
+
+
+ )}
+
+
+ {cyborgs.map(cyborg => (
+ act('messagebot', {
+ ref: cyborg.ref,
+ })} />
+ )}>
+
+
+
+ {cyborg.status
+ ? "Not Responding"
+ : cyborg.locked_down
+ ? "Locked Down"
+ : cyborg.shell_discon
+ ? "Nominal/Disconnected"
+ : "Nominal"}
+
+
+
+
+ {cyborg.integ === 0
+ ? "Hard Fault"
+ : cyborg.integ <= 25
+ ? "Functionality Disrupted"
+ : cyborg.integ <= 75
+ ? "Functionality Impaired"
+ : "Operational"}
+
+
+
+
+ {typeof cyborg.charge === 'number'
+ ? cyborg.charge + "%"
+ : "Not Found"}
+
+
+
+ {cyborg.module}
+
+
+ {cyborg.upgrades}
+
+
+
+ ))}
+
+
+ >
)}
- {cyborgs.map(cyborg => {
- return (
- act('messagebot', {
- ref: cyborg.ref,
- })} />
- )}>
-
-
-
- {cyborg.status
- ? "Not Responding"
- : cyborg.locked_down
- ? "Locked Down"
- : cyborg.shell_discon
- ? "Nominal/Disconnected"
- : "Nominal"}
+ {tab_main === 2 && (
+ <>
+
+
+ Scan a cyborg to download stored logs.
+
+ {ProgressSwitch(DL_progress)}
+
+
+
+
+
+ {storedlog.map(log => (
+
+ {log}
-
-
-
- {typeof cyborg.charge === 'number'
- ? cyborg.charge + "%"
- : "Not Found"}
-
-
-
- {cyborg.module}
-
-
- {cyborg.upgrades}
-
-
-
- );
- })}
- >
+ ))}
+
+
+ >
+ )}
+
);
};