diff --git a/code/modules/modular_computers/computers/item/computer_ui.dm b/code/modules/modular_computers/computers/item/computer_ui.dm
index 4313bf2efbd..49859f46b4d 100644
--- a/code/modules/modular_computers/computers/item/computer_ui.dm
+++ b/code/modules/modular_computers/computers/item/computer_ui.dm
@@ -108,6 +108,7 @@
var/datum/computer_file/program/ai_restorer/airestore_app = locate() in stored_files
if(airestore_app?.stored_card)
data["removable_media"] += "intelliCard"
+ data["removable_media"] += handle_ui_removable_media_insert(user) // FLUFFY FRONTIER ADD
data["programs"] = list()
for(var/datum/computer_file/program/program in stored_files)
@@ -207,6 +208,7 @@
if(RemoveID(user))
playsound(src, 'sound/machines/card_slide.ogg', 50)
return TRUE
+ return handle_ui_removable_media_eject(param, user) // FLUFFFY FRONTIER ADD
if("PC_Imprint_ID")
imprint_id()
diff --git a/code/modules/modular_computers/file_system/programs/techweb.dm b/code/modules/modular_computers/file_system/programs/techweb.dm
index a72eef1bc9a..e6ceafb28f5 100644
--- a/code/modules/modular_computers/file_system/programs/techweb.dm
+++ b/code/modules/modular_computers/file_system/programs/techweb.dm
@@ -25,13 +25,17 @@
. = ..()
if(!CONFIG_GET(flag/no_default_techweb_link) && !stored_research)
CONNECT_TO_RND_SERVER_ROUNDSTART(stored_research, computer)
+ handle_rnd_control_install() // FLUFFY FRONTIER ADD
/datum/computer_file/program/science/application_attackby(obj/item/attacking_item, mob/living/user)
+ if (istype(attacking_item, /obj/item/disk/tech_disk) || istype(attacking_item, /obj/item/disk/design_disk)) return handle_disks_insertion(attacking_item, user) // FLUFFY FRONTTIER ADD
if(!istype(attacking_item, /obj/item/multitool))
return FALSE
var/obj/item/multitool/attacking_tool = attacking_item
if(!QDELETED(attacking_tool.buffer) && istype(attacking_tool.buffer, /datum/techweb))
+ handle_rnd_control_remove() // FLUFFY FRONTIER ADD
stored_research = attacking_tool.buffer
+ handle_rnd_control_install() // FLUFFY FRONTIER ADD
return TRUE
/datum/computer_file/program/science/ui_assets(mob/user)
@@ -57,6 +61,7 @@
"d_disk" = null, //See above.
"locked" = locked,
)
+ data = handle_disks_ui_data(data) // FLUFFY FRONTIER ADD
// Serialize all nodes to display
for(var/tier in stored_research.tiers)
@@ -94,6 +99,7 @@
if (locked && action != "toggleLock")
computer.say("Console is locked, cannot perform further actions.")
return TRUE
+ if (action in list("ejectDisk", "uploadDisk", "loadTech")) return handle_disks_ui_act(action, params) // FLUFFY FRONTIER ADD
switch (action)
if ("toggleLock")
diff --git a/tff_modular/modules/all_computers_to_modular_consoles/_helper.dm b/tff_modular/modules/all_computers_to_modular_consoles/_helper.dm
new file mode 100644
index 00000000000..9da8b5c412f
--- /dev/null
+++ b/tff_modular/modules/all_computers_to_modular_consoles/_helper.dm
@@ -0,0 +1,53 @@
+/*
+// May I be cursed, but I am to lazy to copypast atom/allowed() code now
+/datum/computer_file/program/proc/allowed(mob/accessor)
+ var/list/tmp_req_access = computer.req_access
+ var/list/tmp_req_one_access = computer.req_one_access
+ computer.req_access = null
+ computer.req_one_access = run_access
+ . = computer.allowed(accessor)
+ computer.req_access = tmp_req_access
+ computer.req_one_access = tmp_req_one_access
+*/
+/datum/techweb
+ // We wanna track not only consoles but NT apps that connected to us too
+ var/list/datum/computer_file/program/science/apps_accessing = list()
+
+/datum/computer_file/program/proc/can_run_Adjacent(mob/accessor, loud, access_to_check, downloading, list/access)
+ // TODO: atom/allowed() handles syndie borgs. We - not.
+ if (can_run(accessor, loud, access_to_check, downloading, access))
+ return TRUE
+
+ // atom/allowed() copycode
+ var/obj/item/active_item = accessor.get_active_held_item()
+ var/obj/item/inactive_item = accessor.get_inactive_held_item()
+ if((active_item && can_run(accessor, loud, access_to_check, downloading, active_item?.GetAccess())) || (inactive_item && can_run(accessor, loud, access_to_check, downloading, inactive_item?.GetAccess())))
+ return TRUE
+ else if(ishuman(accessor))
+ var/mob/living/carbon/human/human_accessor = accessor
+ if(can_run(accessor, loud, access_to_check, downloading, human_accessor.wear_id?.GetAccess()))
+ return TRUE
+ else if(isanimal(accessor))
+ var/mob/living/simple_animal/animal = accessor
+ if(can_run(accessor, loud, access_to_check, downloading, animal.access_card?.GetAccess()))
+ return TRUE
+ else if(isbrain(accessor))
+ var/obj/item/mmi/brain_mmi = get(accessor.loc, /obj/item/mmi)
+ if(brain_mmi && ismecha(brain_mmi.loc))
+ var/obj/vehicle/sealed/mecha/big_stompy_robot = brain_mmi.loc
+ return can_run(accessor, loud, access_to_check, downloading, big_stompy_robot.accesses)
+ return FALSE
+
+// Required access override for disk_binded
+/datum/computer_file/program/disk_binded/can_run_Adjacent(mob/accessor, loud, access_to_check, downloading, list/access)
+ if (!access_to_check && length(download_access))
+ access_to_check = download_access
+
+ return ..()
+
+/datum/computer_file/program/proc/can_run_on_flags_to_text(flags = can_run_on_flags, as_list = FALSE)
+ if (flags == PROGRAM_ALL)
+ return as_list ? list("Anything") : "Anything"
+ else
+ var/list/supportable = bitfield_to_list(flags, list("Console", "Laptop", "PDA"))
+ return as_list ? supportable : supportable.Join(" | ")
diff --git a/tff_modular/modules/all_computers_to_modular_consoles/circuit_disk.dm b/tff_modular/modules/all_computers_to_modular_consoles/circuit_disk.dm
new file mode 100644
index 00000000000..ec1459da4ba
--- /dev/null
+++ b/tff_modular/modules/all_computers_to_modular_consoles/circuit_disk.dm
@@ -0,0 +1,30 @@
+/obj/item/computer_console_disk
+ name = "Encrypted NTnet Modem"
+ desc = "Contains software which allows computer to establish secure connection to NTNet for certain function"
+ icon = 'icons/obj/devices/circuitry_n_data.dmi'
+ icon_state = "datadisk6"
+ // Actual program for instalation
+ var/datum/computer_file/program/disk_binded/program
+ // Pointer to program, cloned into PC, to remove when disk ejecting
+ var/datum/computer_file/program/disk_binded/installed_clone
+
+/obj/item/computer_console_disk/Initialize(mapload)
+ . = ..()
+ if (program)
+ if (ispath(program))
+ program = new program()
+ name = "encrypted connection driver ([program.filename])"
+ desc = "Contains software which allows computer to establish secure connection to NTNet for certain function.\n\n[program.extended_desc]"
+
+/obj/item/computer_console_disk/Destroy(force)
+ if (program && isdatum(program))
+ qdel(program)
+ program = null
+ if (installed_clone)
+ qdel(installed_clone)
+ installed_clone = null
+
+ . = ..()
+
+/obj/item/computer_console_disk/command
+ icon_state = "datadisk7"
diff --git a/tff_modular/modules/all_computers_to_modular_consoles/computer_ui.dm b/tff_modular/modules/all_computers_to_modular_consoles/computer_ui.dm
new file mode 100644
index 00000000000..ae01eaafa5a
--- /dev/null
+++ b/tff_modular/modules/all_computers_to_modular_consoles/computer_ui.dm
@@ -0,0 +1,36 @@
+/obj/item/modular_computer/proc/handle_ui_removable_media_insert(mob/user)
+ var/list/removable_media = list()
+
+ // Removable console data disk
+ var/datum/computer_file/program/filemanager/fm = locate() in stored_files
+ if (fm?.console_disk)
+ removable_media += "[HAS_TRAIT(user, TRAIT_KNOW_ENGI_WIRES) ? "Safe removal:" : "Unsafe eject:"] [fm.console_disk.program?.filename] driver"
+
+ // Science Hub disks
+ var/datum/computer_file/program/science/rnd = locate() in stored_files
+ if (rnd?.t_disk)
+ removable_media += "Technology Disk"
+ if (rnd?.d_disk)
+ removable_media += "Design Disk"
+
+ return removable_media
+
+/obj/item/modular_computer/proc/handle_ui_removable_media_eject(param, mob/user)
+ // Removable console data disk (switch wants constant expression)
+ var/datum/computer_file/program/filemanager/fm = locate() in stored_files
+ if (param == "[HAS_TRAIT(user, TRAIT_KNOW_ENGI_WIRES) ? "Safe removal:" : "Unsafe eject:"] [fm?.console_disk?.program?.filename] driver")
+ if (fm?.try_eject(user))
+ playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, FALSE)
+ return TRUE
+ return
+ switch(param)
+ // Science Hub disks
+ if ("Technology Disk", "Design Disk")
+ var/datum/computer_file/program/science/rnd = locate() in stored_files
+ if (!rnd)
+ return
+ if(rnd.try_eject(user))
+ playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, FALSE)
+ return TRUE
+
+ return
diff --git a/tff_modular/modules/all_computers_to_modular_consoles/consoles_preset.dm b/tff_modular/modules/all_computers_to_modular_consoles/consoles_preset.dm
new file mode 100644
index 00000000000..03068bc1659
--- /dev/null
+++ b/tff_modular/modules/all_computers_to_modular_consoles/consoles_preset.dm
@@ -0,0 +1,107 @@
+/obj/machinery/modular_computer/preset/battery_less
+ var/start_on_power_restore = FALSE
+
+/obj/machinery/modular_computer/preset/battery_less/Initialize(mapload)
+ . = ..()
+ if (!cpu)
+ return
+
+ // ugh.. no idea how to change cpu type without breaking everything.. So just let it be and remove it's cell
+ var/cell = cpu.internal_cell
+ cpu.internal_cell = null
+ if (cell)
+ qdel(cell)
+
+/obj/machinery/modular_computer/preset/battery_less/power_change()
+ if (!start_on_power_restore)
+ return ..()
+
+ var/was_unpowered = machine_stat & NOPOWER
+ . = ..()
+ if (was_unpowered && !(machine_stat & (BROKEN|NOPOWER)) && cpu && !cpu.enabled)
+ // Why not cpu.turn_on()? because its crashes without user =/
+ if(cpu.looping_sound)
+ cpu.soundloop.start()
+ cpu.enabled = TRUE
+ cpu.update_appearance()
+ SEND_SIGNAL(cpu, COMSIG_MODULAR_COMPUTER_TURNED_ON, null)
+
+/obj/machinery/modular_computer/preset/battery_less/console
+ start_on_power_restore = TRUE
+ // Disk that will be installed on Initialize()
+ var/obj/item/computer_console_disk/console_disk
+ // If no disk. But us we making sure, that we autorun always as PC exist
+ // No need to fill if console_disk filled
+ var/datum/computer_file/program/autorunnable
+ // Sprites from consoles file. Written by program on console_disk. Can be overriden by you or mapper
+ var/icon_keyboard
+
+/obj/machinery/modular_computer/preset/battery_less/console/Initialize(mapload)
+ if (!console_disk && autorunnable && !(autorunnable in starting_programs))
+ starting_programs += autorunnable
+
+ . = ..()
+
+ if (cpu && console_disk)
+ var/datum/computer_file/program/filemanager/filemanager = cpu.find_file_by_name("filemanager")
+ console_disk = new console_disk(cpu)
+
+ // Oh, preset? Get fancy keyboard for free! (if provided by your program and not overriden)
+ if (!icon_keyboard && console_disk.program)
+ icon_keyboard = console_disk.program.icon_keyboard
+
+ filemanager.application_attackby(console_disk)
+
+ else if (cpu && autorunnable)
+ var/datum/computer_file/program/prog = locate(autorunnable) in cpu.stored_files
+ // First start for free
+ cpu.active_program = prog
+ RegisterSignal(cpu, COMSIG_MODULAR_COMPUTER_TURNED_ON, PROC_REF(autorun))
+
+ // Autoenable on init
+ // cpu.turn_on() copycode
+ if(cpu.use_energy(cpu.base_active_power_usage)) // checks if the PC is powered
+ if(cpu.looping_sound)
+ cpu.soundloop.skip_starting_sounds = TRUE
+ cpu.soundloop.start()
+ cpu.soundloop.skip_starting_sounds = initial(cpu.soundloop.skip_starting_sounds)
+ cpu.enabled = TRUE
+ cpu.update_appearance()
+ SEND_SIGNAL(cpu, COMSIG_MODULAR_COMPUTER_TURNED_ON, null)
+
+/obj/machinery/modular_computer/preset/battery_less/console/Destroy()
+ UnregisterSignal(cpu, COMSIG_MODULAR_COMPUTER_TURNED_ON)
+ . = ..()
+
+// Custom keyboard icon for maploaded consoles
+/obj/machinery/modular_computer/preset/battery_less/console/update_overlays()
+ . = ..()
+ if (icon_keyboard)
+ // There was keyboard_change_icon var but its always TRUE...
+ if(machine_stat & NOPOWER || !cpu?.enabled)
+ . += mutable_appearance('icons/obj/machines/computer.dmi', "[icon_keyboard]_off")
+ else
+ . += mutable_appearance('icons/obj/machines/computer.dmi', icon_keyboard)
+
+
+// Only for not disked programs like Science Hub or Cargo. Those who accessed ingame via NTnet
+/obj/machinery/modular_computer/preset/battery_less/console/proc/autorun(datum/source, mob/user)
+ SIGNAL_HANDLER
+
+ if (cpu && autorunnable)
+ var/datum/computer_file/program/prog = locate(autorunnable) in cpu.stored_files
+ if (prog)
+ // Not writing in active_programs so user need to check his access
+ cpu.open_program(user, prog, cpu.enabled)
+
+// Actual presets of non console_disk computers
+/obj/machinery/modular_computer/preset/battery_less/console/rdconsole_unQoL
+ name = "R&D Console"
+ desc = "A console used to interface with R&D tools."
+ icon_keyboard = "rd_key"
+ autorunnable = /datum/computer_file/program/science
+
+/obj/machinery/modular_computer/preset/battery_less/console/cargo_unQoL
+ name = "supply console"
+ desc = "Used to order supplies, approve requests, and control the shuttle."
+ autorunnable = /datum/computer_file/program/budgetorders
diff --git a/tff_modular/modules/all_computers_to_modular_consoles/disk_binded.dm b/tff_modular/modules/all_computers_to_modular_consoles/disk_binded.dm
new file mode 100644
index 00000000000..da69453381c
--- /dev/null
+++ b/tff_modular/modules/all_computers_to_modular_consoles/disk_binded.dm
@@ -0,0 +1,28 @@
+/datum/computer_file/program/disk_binded
+ size = 0
+ program_flags = PROGRAM_REQUIRES_NTNET
+ can_run_on_flags = PROGRAM_CONSOLE
+ undeletable = TRUE
+ // Okay. Now about accesses: we are never on NTstore, so download_access doesn't care
+ // Meanwhile program run is always (or almost) free, but interactions...
+ // So run_access should be empty, but all yours req_access type into download_access
+ // So I didn't have to create another access variable
+ download_access = list()
+ run_access = list()
+ // Icon_state of the keyboard overlay for mapload. If any...
+ var/icon_keyboard
+
+/datum/computer_file/program/disk_binded/on_install(datum/computer_file/source, obj/item/modular_computer/computer_installing)
+ ..()
+ RegisterSignal(computer, COMSIG_MODULAR_COMPUTER_TURNED_ON, PROC_REF(autorun))
+
+/datum/computer_file/program/disk_binded/Destroy()
+ . = ..()
+
+ if (!QDELETED(computer))
+ UnregisterSignal(computer, COMSIG_MODULAR_COMPUTER_TURNED_ON)
+
+/datum/computer_file/program/disk_binded/proc/autorun(datum/source, mob/user)
+ SIGNAL_HANDLER
+
+ computer.open_program(user, src, computer.enabled)
diff --git a/tff_modular/modules/all_computers_to_modular_consoles/file_browser.dm b/tff_modular/modules/all_computers_to_modular_consoles/file_browser.dm
new file mode 100644
index 00000000000..11e0cd40dbb
--- /dev/null
+++ b/tff_modular/modules/all_computers_to_modular_consoles/file_browser.dm
@@ -0,0 +1,70 @@
+/datum/computer_file/program/filemanager
+ var/obj/item/computer_console_disk/console_disk
+
+/datum/computer_file/program/filemanager/application_attackby(obj/item/computer_console_disk/attacking_item, mob/living/user)
+ if (!istype(attacking_item))
+ return FALSE
+
+ if (console_disk)
+ if (user)
+ to_chat(user, span_warning("It's secure disk drive already occupied!"))
+ return FALSE
+ if (!attacking_item.program)
+ computer.say("I/O ERROR: Unable to access encrypted data disk. Ejecting...")
+ return FALSE
+
+ if (!attacking_item.program.is_supported_by_hardware(computer.hardware_flag))
+ var/supported_hardware = attacking_item.program.can_run_on_flags_to_text()
+ if (supported_hardware == "Anything")
+ // how you aren't supported, if you support anything?!
+ computer.say("HARDWARE ERROR: Software compatibility mismatch! Please report that info to NTTechSupport. PC hardware code: [computer.hardware_flag]. Filename: [attacking_item.program.filename].[lowertext(attacking_item.program.filetype)]")
+ return FALSE
+ else
+ computer.say("HARDWARE ERROR: Incompatible software. Ejecting... Supported devices: [supported_hardware]")
+ return FALSE
+
+ if(user && !user.transferItemToLoc(attacking_item, computer))
+ return FALSE
+ console_disk = attacking_item
+ playsound(computer, 'sound/machines/terminal_insert_disc.ogg', 50, FALSE)
+
+ if (console_disk.program)
+ // Remove BSOD if present
+ var/datum/computer_file/program/bsod/bsod = computer.find_file_by_name("nt_recovery")
+ if (bsod)
+ computer.remove_file(bsod)
+
+ var/datum/computer_file/program/disk_binded/clone = console_disk.program.clone()
+ console_disk.installed_clone = clone
+ computer.store_file(clone)
+ // Initial start
+ computer.open_program(user, clone, computer.enabled)
+
+ return TRUE
+
+/datum/computer_file/program/filemanager/try_eject(mob/living/user, forced = FALSE)
+ if (forced || !user || HAS_TRAIT(user, TRAIT_KNOW_ENGI_WIRES))
+ if (user)
+ user.put_in_hands(console_disk)
+ user.visible_message(span_warning("[user] removes [console_disk] from [computer]!"), span_notice("You use 'Safely Remove Hardware' option to eject [console_disk] from [computer]..."))
+ else
+ console_disk.forceMove(computer.drop_location())
+ computer.remove_file(console_disk.installed_clone)
+ console_disk.installed_clone = null
+ console_disk = null
+ return TRUE
+ else
+ // 2 to unscrew, 3 to eject glass, cut wires and eject circuit
+ user.visible_message(span_warning("[user] tries to rip off [console_disk] from [computer]!"), span_notice("You try to forcibly remove stuck [console_disk] from [computer]..."))
+ if (do_after(user, 5 SECONDS, computer.physical ? computer.physical : get_turf(computer)))
+ var/datum/computer_file/program/bsod/bsod = new(lowertext("[console_disk.program.filename].[console_disk.program.filetype]"))
+
+ computer.remove_file(console_disk.installed_clone)
+ user.put_in_hands(console_disk)
+ console_disk.installed_clone = null
+ console_disk = null
+
+ computer.store_file(bsod)
+ return TRUE
+ to_chat(user, span_warning("You should be near \the [computer.physical ? computer.physical : computer]!"))
+ return FALSE
diff --git a/tff_modular/modules/all_computers_to_modular_consoles/integration.dm b/tff_modular/modules/all_computers_to_modular_consoles/integration.dm
new file mode 100644
index 00000000000..f6231584326
--- /dev/null
+++ b/tff_modular/modules/all_computers_to_modular_consoles/integration.dm
@@ -0,0 +1,21 @@
+GLOBAL_LIST_INIT(consoles_replacement_map, list(
+ /obj/machinery/computer/rdservercontrol = /obj/machinery/modular_computer/preset/battery_less/console/rdservercontrol,
+))
+
+/obj/machinery/computer/Initialize(mapload, obj/item/circuitboard/C)
+ . = ..()
+ if (mapload && (src.type in GLOB.consoles_replacement_map))
+ var/obj/machinery/modular_computer/preset/battery_less/console/console = GLOB.consoles_replacement_map[src.type]
+ console = new console(src.loc)
+ transfer_data_to_modular_console(console)
+ console.update_appearance()
+ return INITIALIZE_HINT_QDEL
+
+/obj/machinery/computer/proc/transfer_data_to_modular_console(obj/machinery/modular_computer/preset/battery_less/console/console)
+ SHOULD_CALL_PARENT(TRUE)
+
+ console.setDir(dir)
+ console.name = name
+
+ if (console.cpu)
+ console.cpu.desc = desc
diff --git a/tff_modular/modules/all_computers_to_modular_consoles/programms/_bsod_program.dm b/tff_modular/modules/all_computers_to_modular_consoles/programms/_bsod_program.dm
new file mode 100644
index 00000000000..e82d9b81c3d
--- /dev/null
+++ b/tff_modular/modules/all_computers_to_modular_consoles/programms/_bsod_program.dm
@@ -0,0 +1,129 @@
+/datum/computer_file/program/bsod
+ filename = "nt_recovery"
+ filedesc = "System Recovery"
+ extended_desc = "When something goes wrong this program should tell you how to fix it."
+ undeletable = TRUE
+ size = 0
+ power_cell_use = NONE
+ program_flags = PROGRAM_HEADER | PROGRAM_RUNS_WITHOUT_POWER
+ program_open_overlay = "bsod"
+ program_icon = "bug-slash"
+ tgui_id = "NtosConsolesRevamp"
+
+ var/bsod_reason = "unknown.dbg"
+ var/initial_icon_state_menu = "menu"
+ var/modular_icon_state_menu = "menu"
+ var/modular_icon_state_screensaver = "standby"
+
+/datum/computer_file/program/bsod/New(bsod_source)
+ . = ..()
+ if (bsod_source)
+ bsod_reason = bsod_source
+
+/datum/computer_file/program/bsod/on_install(datum/computer_file/source, obj/item/modular_computer/computer_installing)
+ ..()
+ RegisterSignal(computer, COMSIG_MODULAR_COMPUTER_TURNED_ON, PROC_REF(computer_on))
+
+ playsound(computer, 'sound/machines/terminal_alert.ogg', 100)
+ initial_icon_state_menu = computer_installing.icon_state_menu
+
+ // Show BSOD in any condition
+ computer_installing.icon_state_menu = "bsod"
+ if (computer_installing.physical && istype(computer_installing.physical, /obj/machinery/modular_computer))
+ var/obj/machinery/modular_computer/console = computer_installing.physical
+ modular_icon_state_menu = console.screen_icon_state_menu
+ modular_icon_state_screensaver = console.screen_icon_screensaver
+
+ console.screen_icon_state_menu = "bsod"
+ console.screen_icon_screensaver = "bsod"
+
+ if(!computer.open_program(null, src, computer_installing.enabled))
+ // Opening program will update icon, so we need to do this only if program wasn't opened
+ update_computer_icon()
+
+/datum/computer_file/program/bsod/Destroy()
+ computer.icon_state_menu = initial_icon_state_menu
+
+ // Curse you staionary console!
+ if (computer.physical && istype(computer.physical, /obj/machinery/modular_computer))
+ var/obj/machinery/modular_computer/console = computer.physical
+ console.screen_icon_state_menu = modular_icon_state_menu
+ console.screen_icon_screensaver = modular_icon_state_screensaver
+
+ computer.physical?.visible_message(span_notice("\The [computer] flashes its screen few times as it reboots from safe mode."))
+ playsound(computer, 'sound/machines/computer/computer_start.ogg', 10)
+ update_computer_icon()
+ UnregisterSignal(computer, COMSIG_MODULAR_COMPUTER_TURNED_ON)
+ . = ..()
+
+/datum/computer_file/program/bsod/on_examine(obj/item/modular_computer/source, mob/user)
+ var/list/examine_text = list()
+ examine_text += span_warning("Its screen tells you that previous session of [bsod_reason] finished incorrectly.")
+ examine_text += span_notice("However you can reboot [computer]\s driver with multitool to remove that noisy message.\nOr just install another similar program.")
+ return examine_text
+
+/datum/computer_file/program/bsod/application_attackby(obj/item/attacking_item, mob/living/user)
+ if(!istype(attacking_item, /obj/item/multitool))
+ return FALSE
+
+ user.visible_message(span_notice("[user] tries to diagnose [computer]\s BSOD reason."), span_notice("You plug [attacking_item] pins into [computer] to force restart its drivers..."))
+ playsound(computer, 'sound/machines/terminal_processing.ogg', 50)
+ if (do_after(user, 10 SECONDS, computer.physical ? computer.physical : get_turf(computer)))
+ playsound(computer, 'sound/machines/high_tech_confirm.ogg', 50)
+ computer.remove_file(src)
+ return TRUE
+ return FALSE
+
+/datum/computer_file/program/bsod/ui_static_data(mob/user)
+ var/list/data = list()
+ data["show_imprint"] = istype(computer, /obj/item/modular_computer/pda)
+ return data
+
+// If PC has active program it won't sent us any data.
+// But for fancy "Overlay" we still need it. So we will collect it manualy
+/datum/computer_file/program/bsod/ui_data(mob/user)
+ var/list/data = list()
+ data["pai"] = computer.inserted_pai
+ data["has_light"] = computer.has_light
+ data["light_on"] = computer.light_on
+ data["comp_light_color"] = computer.comp_light_color
+
+ data["login"] = list(
+ IDName = computer.saved_identification || "Unknown",
+ IDJob = computer.saved_job || "Unknown",
+ )
+
+ data["proposed_login"] = list(
+ IDInserted = computer.computer_id_slot ? TRUE : FALSE,
+ IDName = computer.computer_id_slot?.registered_name,
+ IDJob = computer.computer_id_slot?.assignment,
+ )
+
+ data["removable_media"] = list()
+ if(computer.inserted_disk)
+ data["removable_media"] += "Eject Disk"
+ var/datum/computer_file/program/ai_restorer/airestore_app = locate() in computer.stored_files
+ if(airestore_app?.stored_card)
+ data["removable_media"] += "intelliCard"
+ data["removable_media"] += computer.handle_ui_removable_media_insert(user)
+
+ data["programs"] = list()
+ for(var/datum/computer_file/program/program in computer.stored_files)
+ data["programs"] += list(list(
+ "name" = program.filename,
+ "desc" = program.filedesc,
+ "header_program" = !!(program.program_flags & PROGRAM_HEADER),
+ "running" = !!(program in computer.idle_threads),
+ "icon" = program.program_icon,
+ "alert" = program.alert_pending,
+ ))
+
+ // Nope no way to avoid us
+ data["PC_showexitprogram"] = FALSE
+ data["reason"] = bsod_reason
+ return data
+
+// I am inevitable
+/datum/computer_file/program/bsod/proc/computer_on(datum/source, mob/user)
+ SIGNAL_HANDLER
+ computer.open_program(user, src, TRUE)
diff --git a/tff_modular/modules/all_computers_to_modular_consoles/programms/rdconsole.dm b/tff_modular/modules/all_computers_to_modular_consoles/programms/rdconsole.dm
new file mode 100644
index 00000000000..cc484ed0697
--- /dev/null
+++ b/tff_modular/modules/all_computers_to_modular_consoles/programms/rdconsole.dm
@@ -0,0 +1,154 @@
+#define RND_TECH_DISK "tech"
+#define RND_DESIGN_DISK "design"
+
+/datum/computer_file/program/science
+ // The stored technology disk, if present
+ var/obj/item/disk/tech_disk/t_disk
+ // The stored design disk, if present
+ var/obj/item/disk/design_disk/d_disk
+ // Options same as can_run_on_flags
+ var/disk_support_hardware_flags = PROGRAM_CONSOLE
+ var/techweb_tracked = FALSE
+
+/datum/computer_file/program/science/proc/handle_rnd_control_install()
+ if (stored_research)
+ if (!techweb_tracked)
+ // Do not count PDAs in nullspace, please
+ if (computer.loc && !(src in stored_research.apps_accessing))
+ stored_research.apps_accessing += src
+ techweb_tracked = TRUE
+ // Oh wait, you are off-station or emgged? Be unlocked, please!
+ if (!istype(stored_research, /datum/techweb/science) || (computer.obj_flags & EMAGGED))
+ locked = FALSE
+ else
+ techweb_tracked = FALSE
+
+/datum/computer_file/program/science/proc/handle_rnd_control_remove()
+ if (stored_research)
+ stored_research.apps_accessing -= src
+ techweb_tracked = FALSE
+
+/datum/computer_file/program/science/Destroy()
+ handle_rnd_control_remove()
+ . = ..()
+
+// Why? Because on computer_file init moment, we are in nullspace.
+// And bringing here LateInititialize() proc only for this is a bad idea
+// Still do not need nullspaced apps in my RD Server Control >=(
+/datum/computer_file/program/science/on_start(mob/living/user)
+ . = ..()
+ if (!techweb_tracked)
+ handle_rnd_control_install()
+
+/datum/computer_file/program/science/clone()
+ var/datum/computer_file/program/science/temp = ..()
+ // No, you can't reassemble console to reset access lock
+ temp.locked = TRUE
+ return temp
+
+/datum/computer_file/program/science/kill_program(mob/user)
+ try_eject(forced = TRUE)
+ return ..()
+
+/datum/computer_file/program/science/on_examine(obj/item/modular_computer/source, mob/user)
+ if (!(disk_support_hardware_flags & source.hardware_flag))
+ return
+
+ var/list/examine_text = list()
+ if(!t_disk && !d_disk)
+ examine_text += "It has a slot installed for science data disk."
+ return examine_text
+
+ if(computer.Adjacent(user))
+ examine_text += "It has a slot installed for science data which contains: [t_disk ? t_disk.name : d_disk.name]"
+ else
+ examine_text += "It has a slot installed for science data, which appears to be occupied."
+ // examine_text += span_info("Alt-click to eject the science data disk.")
+ return examine_text
+
+/datum/computer_file/program/science/proc/handle_disks_insertion(obj/item/D, mob/living/user)
+ // No disks in PDA please
+ if (!(disk_support_hardware_flags & computer.hardware_flag))
+ to_chat(user, span_warning("There is no slot for [D]. Maybe you should try: [can_run_on_flags_to_text(disk_support_hardware_flags)]?"))
+ return FALSE
+ // Unfortunatly eject code doesn't support diffrent ejectables
+ if (t_disk || d_disk)
+ to_chat(user, span_warning("Science data disk slot already occupied!"))
+ return FALSE
+ if(istype(D, /obj/item/disk/tech_disk))
+ if(!user.transferItemToLoc(D, computer))
+ to_chat(user, span_warning("[D] is stuck to your hand!"))
+ return FALSE
+ t_disk = D
+ else if (istype(D, /obj/item/disk/design_disk))
+ if(!user.transferItemToLoc(D, computer))
+ to_chat(user, span_warning("[D] is stuck to your hand!"))
+ return FALSE
+ d_disk = D
+ to_chat(user, span_notice("You insert [D] into \the [computer.name]!"))
+ playsound(computer, 'sound/machines/terminal_insert_disc.ogg', 50, FALSE)
+ return TRUE
+
+/datum/computer_file/program/science/proc/handle_disks_ui_data(list/data)
+ if (t_disk)
+ data["t_disk"] = list (
+ "stored_research" = t_disk.stored_research.researched_nodes,
+ )
+ if (d_disk)
+ data["d_disk"] = list("blueprints" = list())
+ for (var/datum/design/D in d_disk.blueprints)
+ data["d_disk"]["blueprints"] += D.id
+
+ return data
+
+/datum/computer_file/program/science/proc/handle_disks_ui_act(action, list/params)
+ switch(action)
+ if ("ejectDisk")
+ return try_eject()
+ if ("uploadDisk")
+ if (params["type"] == RND_DESIGN_DISK)
+ if(QDELETED(d_disk))
+ computer.say("No design disk inserted!")
+ return TRUE
+ for(var/D in d_disk.blueprints)
+ if(D)
+ stored_research.add_design(D, TRUE)
+ computer.say("Uploading blueprints from disk.")
+ d_disk.on_upload(stored_research)
+ return TRUE
+ if (params["type"] == RND_TECH_DISK)
+ if (QDELETED(t_disk))
+ computer.say("No tech disk inserted!")
+ return TRUE
+ computer.say("Uploading technology disk.")
+ t_disk.stored_research.copy_research_to(stored_research)
+ return TRUE
+ //Tech disk-only action.
+ if ("loadTech")
+ if(QDELETED(t_disk))
+ computer.say("No tech disk inserted!")
+ return
+ stored_research.copy_research_to(t_disk.stored_research)
+ computer.say("Downloading to technology disk.")
+ return TRUE
+
+/datum/computer_file/program/science/try_eject(mob/living/user, forced = FALSE)
+ if (!t_disk && !d_disk)
+ if (user)
+ to_chat(user, span_warning("There is no card in \the [computer.name]."))
+ return FALSE
+
+ var/obj/item/disk = t_disk ? t_disk : d_disk
+ if(user && computer.Adjacent(user))
+ to_chat(user, span_notice("You remove [disk] from [computer.name]."))
+ user.put_in_hands(disk)
+ else
+ disk.forceMove(computer.drop_location())
+
+ t_disk = null
+ d_disk = null
+
+ return TRUE
+
+#undef RND_TECH_DISK
+#undef RND_DESIGN_DISK
diff --git a/tff_modular/modules/all_computers_to_modular_consoles/programms/server_control.dm b/tff_modular/modules/all_computers_to_modular_consoles/programms/server_control.dm
new file mode 100644
index 00000000000..d706b16da64
--- /dev/null
+++ b/tff_modular/modules/all_computers_to_modular_consoles/programms/server_control.dm
@@ -0,0 +1,118 @@
+/datum/computer_file/program/disk_binded/rdservercontrol
+ filename = "sci_net_admin"
+ filedesc = "Researh Network Admin Panel"
+ program_open_overlay = "research"
+ extended_desc = "Connect to the internal science server in order to control their behavior."
+ program_flags = PROGRAM_REQUIRES_NTNET
+ size = 0
+ tgui_id = "NtosServerControl"
+ program_icon = FA_ICON_SERVER
+ download_access = list(ACCESS_RD)
+ icon_keyboard = "rd_key"
+ // Reference to global science techweb
+ var/datum/techweb/stored_research
+
+/datum/computer_file/program/disk_binded/rdservercontrol/on_install(datum/computer_file/source, obj/item/modular_computer/computer_installing)
+ . = ..()
+ if(!CONFIG_GET(flag/no_default_techweb_link) && !stored_research)
+ CONNECT_TO_RND_SERVER_ROUNDSTART(stored_research, computer)
+
+/datum/computer_file/program/disk_binded/rdservercontrol/application_attackby(obj/item/attacking_item, mob/living/user)
+ if(!istype(attacking_item, /obj/item/multitool))
+ return FALSE
+ var/obj/item/multitool/attacking_tool = attacking_item
+ if(!QDELETED(attacking_tool.buffer) && istype(attacking_tool.buffer, /datum/techweb))
+ stored_research = attacking_tool.buffer
+ computer.say("[filedesc]: Established connection to [stored_research.organization] research network.") // Network id: [stored_research.id] not sure, id may be OOC info
+ return TRUE
+ return FALSE
+
+/datum/computer_file/program/disk_binded/rdservercontrol/ui_data(mob/user)
+ var/list/data = list()
+
+ data["server_connected"] = !!stored_research
+
+ if(stored_research)
+ data["logs"] += stored_research.research_logs
+
+ for(var/obj/machinery/rnd/server/server as anything in stored_research.techweb_servers)
+ data["servers"] += list(list(
+ "server_name" = server,
+ "server_details" = server.get_status_text(),
+ "server_disabled" = server.research_disabled,
+ "server_ref" = REF(server),
+ ))
+
+ for(var/obj/machinery/computer/rdconsole/console as anything in stored_research.consoles_accessing)
+ data["consoles"] += list(list(
+ "console_name" = console,
+ "console_location" = console.loc == null ? "UNKNOWN" : get_area(console),
+ "console_locked" = console.locked,
+ "console_ref" = REF(console),
+ ))
+ for (var/datum/computer_file/program/science/app in stored_research.apps_accessing)
+ data["consoles"] += list(list(
+ "console_name" = app.computer,
+ "console_location" = app.computer.loc == null ? "UNKNOWN" : get_area(app.computer),
+ "console_locked" = app.locked,
+ "console_ref" = REF(app),
+ ))
+
+ return data
+
+/datum/computer_file/program/disk_binded/rdservercontrol/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
+ . = ..()
+ if(.)
+ return TRUE
+ if(!can_run_Adjacent(usr) && !(computer.obj_flags & EMAGGED))
+ computer.say("Access denied!")
+ playsound(computer, 'sound/machines/terminal_error.ogg', 20, TRUE)
+ return TRUE
+
+ switch(action)
+ if("lockdown_server")
+ var/obj/machinery/rnd/server/server_selected = locate(params["selected_server"]) in stored_research.techweb_servers
+ if(!server_selected)
+ return FALSE
+ server_selected.toggle_disable(usr)
+ return TRUE
+ if("lock_console")
+ var/obj/machinery/computer/rdconsole/console_selected = locate(params["selected_console"]) in stored_research.consoles_accessing
+ if(!console_selected)
+ var/datum/computer_file/program/science/app = locate(params["selected_console"]) in stored_research.apps_accessing
+ if (!app)
+ return FALSE
+ app.locked = !app.locked
+ return TRUE
+ console_selected.locked = !console_selected.locked
+ return TRUE
+
+// Legacy computer code
+// Inject into console code to add science app tracking
+/*
+/obj/machinery/computer/rdservercontrol/proc/handle_ui_data_apps_insertion()
+ var/list/data = list()
+
+ for (var/datum/computer_file/program/science/app in stored_research.apps_accessing)
+ data += list(list(
+ "console_name" = app.computer,
+ "console_location" = app.computer.loc == null ? "UNKNOWN" : get_area(app.computer),
+ "console_locked" = app.locked,
+ "console_ref" = REF(app),
+ ))
+ return data
+
+/obj/machinery/computer/rdservercontrol/proc/handle_ui_act_apps_lock(choosen_app)
+ var/datum/computer_file/program/science/app = locate(choosen_app) in stored_research.apps_accessing
+ if (!app)
+ return FALSE
+ app.locked = !app.locked
+ return TRUE
+*/
+/obj/item/computer_console_disk/command/rdservercontrol
+ program = /datum/computer_file/program/disk_binded/rdservercontrol
+
+/obj/machinery/modular_computer/preset/battery_less/console/rdservercontrol
+ name = "R&D Server Controller"
+ desc = "Manages access to research databases and consoles."
+ console_disk = /obj/item/computer_console_disk/command/rdservercontrol
diff --git a/tgstation.dme b/tgstation.dme
index 33f9e58a08f..953d08802b9 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -8282,6 +8282,16 @@
#include "tff_modular\master_files\code\modules\mod\_module.dm"
#include "tff_modular\master_files\code\modules\mod\mod_clothes.dm"
#include "tff_modular\master_files\code\modules\reagents\recipe\coagulant_recipe.dm"
+#include "tff_modular\modules\all_computers_to_modular_consoles\_helper.dm"
+#include "tff_modular\modules\all_computers_to_modular_consoles\circuit_disk.dm"
+#include "tff_modular\modules\all_computers_to_modular_consoles\computer_ui.dm"
+#include "tff_modular\modules\all_computers_to_modular_consoles\consoles_preset.dm"
+#include "tff_modular\modules\all_computers_to_modular_consoles\disk_binded.dm"
+#include "tff_modular\modules\all_computers_to_modular_consoles\file_browser.dm"
+#include "tff_modular\modules\all_computers_to_modular_consoles\integration.dm"
+#include "tff_modular\modules\all_computers_to_modular_consoles\programms\_bsod_program.dm"
+#include "tff_modular\modules\all_computers_to_modular_consoles\programms\rdconsole.dm"
+#include "tff_modular\modules\all_computers_to_modular_consoles\programms\server_control.dm"
#include "tff_modular\modules\autoaccent\code\autoaccent.dm"
#include "tff_modular\modules\barsigns\code\barsigns.dm"
#include "tff_modular\modules\blooper\atoms_movable.dm"
diff --git a/tgui/packages/tgui/interfaces/NtosConsolesRevamp.tsx b/tgui/packages/tgui/interfaces/NtosConsolesRevamp.tsx
new file mode 100644
index 00000000000..135d2e3c63f
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/NtosConsolesRevamp.tsx
@@ -0,0 +1,80 @@
+// THIS IS A FLUFFY FONTIER UI FILE
+import { ReactElement } from 'react';
+
+import { useBackend } from '../backend';
+import { Blink, Box, Dimmer, Flex, Icon } from '../components';
+import { NtosWindow, Window } from '../layouts';
+import { NtosMain } from './NtosMain';
+// Components for replacement
+import { ServerControl } from './ServerControl';
+
+type Data = {
+ reason: string;
+};
+
+const replaceWindowWithNtosWindow = (node: ReactElement) => {
+ return (
+
+
+ {node.props.children.props.children}
+
+
+ );
+};
+
+export const NtosConsolesRevamp = (props) => {
+ const mainScreen = NtosMain(NtosWindow);
+ const { data } = useBackend();
+ const { reason } = data;
+ return (
+
+
+
+
+
+ The application is not responding
+
+
+
+
+
+
+
+
+
+ Error with process:
+
+ '
+
+ {reason}
+
+ '
+
+
+
+ Try to plug back installation device or restart disk drive systems
+ with multitool
+
+
+
+ {mainScreen.props.children.props.children}
+
+
+ );
+};
+
+export const NtServerControl = (props) => {
+ return replaceWindowWithNtosWindow(ServerControl(Window));
+};
diff --git a/tgui/packages/tgui/interfaces/NtosServerControl.tsx b/tgui/packages/tgui/interfaces/NtosServerControl.tsx
new file mode 100644
index 00000000000..0813a240217
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/NtosServerControl.tsx
@@ -0,0 +1,5 @@
+import { NtServerControl } from './NtosConsolesRevamp';
+
+export const NtosServerControl = (props) => {
+ return ;
+};