From 8b6898a6715b7e4bc3ecefad268f56778cbcd972 Mon Sep 17 00:00:00 2001 From: SkyratBot <59378654+SkyratBot@users.noreply.github.com> Date: Sat, 20 Mar 2021 01:09:29 +0100 Subject: [PATCH] [MIRROR] Adds the ability to download a borg's logs using SiliConnect. (#4258) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Adds the ability to download a borg's logs using SiliConnect. (#57300) About The Pull Request Adds a function to Siliconnect in which you can tap a borg using a mobile device that is currently running the software, and it will download the borg's logs. These logs list events such as being locked or unlocked, taken offline and being restored, and the process of being emagged. Borg logs will now list the emag user's name as a new user. Downloading the logs is done by right-clicking the borg while the software is open and active. This will "tap" the borg with the device, initiating the log transfer automatically. The transfer will take eight seconds to complete, requiring you and the borg to stay adjacent the entire time, and the borg will get a large red text alert about the upload when it starts. The logs are also not stored permanently on the device, and will be lost if the app is closed. This PR also introduces the tap() proc for modular computer programs, and adds functionality the the computers to call it when right-clicking with the tablet as a tool. Should the app use the tap in a meaningful way (such as starting a borg log download), it will return TRUE, and the computer will end the secondary attack chain. Currently, this requires using a tablet or laptop with Siliconnect, as you cannot tap with a console. Someday I hope to add an additional hardware option for consoles in the form of a wireless hand scanner to replicate tapping. Why It's Good For The Game Adds a neat way to "diagnose" a borg acting oddly, assuming you have a way to keep them still. Allows one to view if the borg has been emagged, by whom, what SiliConnect messages the borg has received, the number of law changes that have been made, as well as some other (somewhat fluff) information relating to taking damage and getting upgrades. Changelog 🆑 add: You can now download a cyborg's internal logs by right-clicking them with a mobile device running SiliConnect. Take a look if one is acting a bit strange, you might find something interesting. add: Borg integrity (health) is now roughly shown in SiliConnect, under "Condition". /🆑 To Do: Fix the to_chat not being sent to the borg when a log transfer breaks due to distance Add some sort of text feedback when tapping an atom with a device, or viewing one being tapped. Possibly restrict syndicate borgs from having their logs viewed by SiliConnect (and likewise with station borgs and Roboverlord) Changed my mind on this, will implement later if we find it's needed. Change the log area of the window to expand naturally rather than use a fixed height image I'm not super deadset on the emag user's name being listed. It seems like a neat function unique to having a tablet running Siliconnect (as most of the other info is already available through other means), and one can always pop the brain out for a re-borg and demand the name anyway. * Adds the ability to download a borg's logs using SiliConnect. Co-authored-by: zxaber <37497534+zxaber@users.noreply.github.com> --- .../mob/living/silicon/robot/robot_defense.dm | 2 + .../computers/item/computer.dm | 17 ++ .../modular_computers/file_system/program.dm | 15 ++ .../file_system/programs/borg_monitor.dm | 66 +++++- .../interfaces/NtosCyborgRemoteMonitor.js | 210 +++++++++++++----- 5 files changed, 249 insertions(+), 61 deletions(-) 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} - - -
- ); - })} - + ))} +
+ + + )} +
); };