From 6622c4ff8f2ecaa9768dd784995b081fbaf3459c Mon Sep 17 00:00:00 2001 From: nmajask Date: Tue, 19 Oct 2021 22:43:32 -0400 Subject: [PATCH 1/6] Ports stuff Ports a lot of stuff from TG, some of it might even work! --- code/__DEFINES/components.dm | 6 + code/__DEFINES/is_helpers.dm | 2 + code/__DEFINES/machines.dm | 8 + code/game/machinery/computer/card.dm | 2 +- code/game/objects/items/cards_ids.dm | 8 +- code/game/objects/items/devices/PDA/PDA.dm | 67 +++++-- code/game/objects/items/storage/wallets.dm | 15 ++ code/modules/jobs/access.dm | 6 + .../jobs/job_types/atmospheric_technician.dm | 2 +- code/modules/jobs/job_types/chief_engineer.dm | 2 +- .../jobs/job_types/chief_medical_officer.dm | 2 +- .../jobs/job_types/head_of_personnel.dm | 2 +- .../jobs/job_types/head_of_security.dm | 2 +- .../jobs/job_types/research_director.dm | 2 +- .../mob/living/carbon/human/human_helpers.dm | 16 +- code/modules/mob/mob.dm | 3 + .../computers/_modular_computer_shared.dm | 12 +- .../computers/item/computer.dm | 174 +++++++++------- .../computers/item/computer_components.dm | 13 ++ .../computers/item/computer_ui.dm | 92 +++++++-- .../computers/item/laptop.dm | 1 + .../modular_computers/computers/item/phone.dm | 1 + .../computers/item/processor.dm | 24 +-- .../computers/item/tablet.dm | 15 ++ .../computers/item/tablet_presets.dm | 19 ++ .../computers/machinery/console_presets.dm | 9 +- .../computers/machinery/modular_computer.dm | 30 +-- .../modular_computers/file_system/program.dm | 108 ++++++---- .../file_system/program_events.dm | 2 +- .../file_system/programs/airestorer.dm | 5 +- .../file_system/programs/alarm.dm | 2 + .../programs/antagonist/contract_uplink.dm | 3 + .../file_system/programs/antagonist/dos.dm | 4 +- .../programs/antagonist/revelation.dm | 4 +- .../file_system/programs/arcade.dm | 1 + .../file_system/programs/atmosscan.dm | 17 +- .../file_system/programs/borg_monitor.dm | 3 + .../file_system/programs/bounty_board.dm | 1 + .../file_system/programs/card.dm | 81 ++++---- .../file_system/programs/configurator.dm | 1 + .../file_system/programs/crewmanifest.dm | 2 + .../file_system/programs/file_browser.dm | 18 +- .../file_system/programs/jobmanagement.dm | 2 + .../file_system/programs/ntdownloader.dm | 129 +++++++----- .../file_system/programs/ntmonitor.dm | 4 +- .../file_system/programs/ntnrc_client.dm | 4 +- .../file_system/programs/powermonitor.dm | 2 + .../file_system/programs/radar.dm | 187 +++++++++++------- .../file_system/programs/robocontrol.dm | 5 +- .../file_system/programs/sm_monitor.dm | 65 +++++- .../modular_computers/hardware/_hardware.dm | 8 +- .../modular_computers/hardware/ai_slot.dm | 9 +- .../hardware/battery_module.dm | 8 +- .../modular_computers/hardware/card_slot.dm | 126 ++++++------ .../modular_computers/hardware/hard_drive.dm | 11 ++ .../hardware/portable_disk.dm | 8 +- .../modular_computers/hardware/printer.dm | 1 + .../hardware/sensor_package.dm | 8 + .../modular_computers/laptop_vendor.dm | 20 +- code/modules/power/supermatter/supermatter.dm | 4 + .../research/designs/computer_part_designs.dm | 18 ++ code/modules/research/techweb/all_nodes.dm | 4 +- code/modules/tgui/tgui.dm | 14 +- code/modules/vending/modularpc.dm | 6 +- icons/obj/modular_laptop.dmi | Bin 24422 -> 28928 bytes icons/obj/modular_phone.dmi | Bin 3773 -> 4180 bytes icons/obj/modular_tablet.dmi | Bin 9684 -> 5665 bytes .../tgui/interfaces/ComputerFabricator.js | 11 +- .../interfaces/NtosCyborgRemoteMonitor.js | 1 + .../tgui/interfaces/NtosFileManager.js | 11 +- tgui/packages/tgui/interfaces/NtosMain.js | 63 ++++-- .../tgui/interfaces/NtosNetDownloader.js | 181 +++++++++++------ yogstation.dme | 1 + 73 files changed, 1137 insertions(+), 561 deletions(-) create mode 100644 code/modules/modular_computers/hardware/sensor_package.dm diff --git a/code/__DEFINES/components.dm b/code/__DEFINES/components.dm index 1760fe6b09ea..270cd7da3c70 100644 --- a/code/__DEFINES/components.dm +++ b/code/__DEFINES/components.dm @@ -254,6 +254,12 @@ #define COMSIG_MACHINERY_POWER_LOST "machinery_power_lost" //from base power_change() when power is lost #define COMSIG_MACHINERY_POWER_RESTORED "machinery_power_restored" //from base power_change() when power is restored +// /obj/machinery/power/supermatter_crystal signals +/// from /obj/machinery/power/supermatter_crystal/process_atmos(); when the SM delam reaches the point of sounding alarms +#define COMSIG_SUPERMATTER_DELAM_START_ALARM "sm_delam_start_alarm" +/// from /obj/machinery/power/supermatter_crystal/process_atmos(); when the SM sounds an audible alarm +#define COMSIG_SUPERMATTER_DELAM_ALARM "sm_delam_alarm" + // /obj/item signals #define COMSIG_ITEM_ATTACK "item_attack" //from base of obj/item/attack(): (/mob/living/target, /mob/living/user) #define COMSIG_ITEM_ATTACK_SELF "item_attack_self" //from base of obj/item/attack_self(): (/mob) diff --git a/code/__DEFINES/is_helpers.dm b/code/__DEFINES/is_helpers.dm index dd9d7b9d9935..40b7a0d550d1 100644 --- a/code/__DEFINES/is_helpers.dm +++ b/code/__DEFINES/is_helpers.dm @@ -213,6 +213,8 @@ GLOBAL_LIST_INIT(heavyfootmob, typecacheof(list( #define isitem(A) (istype(A, /obj/item)) +#define isidcard(I) (istype(I, /obj/item/card/id)) + #define isstructure(A) (istype(A, /obj/structure)) #define ismachinery(A) (istype(A, /obj/machinery)) diff --git a/code/__DEFINES/machines.dm b/code/__DEFINES/machines.dm index 9bc081b282af..89077b48d904 100644 --- a/code/__DEFINES/machines.dm +++ b/code/__DEFINES/machines.dm @@ -39,11 +39,13 @@ #define MC_HDD "HDD" #define MC_SDD "SDD" #define MC_CARD "CARD" +#define MC_CARD2 "CARD2" #define MC_NET "NET" #define MC_PRINT "PRINT" #define MC_CELL "CELL" #define MC_CHARGE "CHARGE" #define MC_AI "AI" +#define MC_SENSORS "SENSORS" //NTNet stuff, for modular computers // NTNet module-configuration values. Do not change these. If you need to add another use larger number (5..6..7 etc) @@ -71,6 +73,12 @@ #define PROGRAM_STATE_KILLED 0 #define PROGRAM_STATE_BACKGROUND 1 #define PROGRAM_STATE_ACTIVE 2 +//Program categories +#define PROGRAM_CATEGORY_CREW "Crew" +#define PROGRAM_CATEGORY_ENGI "Engineering" +#define PROGRAM_CATEGORY_ROBO "Robotics" +#define PROGRAM_CATEGORY_SUPL "Supply" +#define PROGRAM_CATEGORY_MISC "Other" #define FIREDOOR_OPEN 1 #define FIREDOOR_CLOSED 2 diff --git a/code/game/machinery/computer/card.dm b/code/game/machinery/computer/card.dm index 4688a4607cf0..f84206b01e4e 100644 --- a/code/game/machinery/computer/card.dm +++ b/code/game/machinery/computer/card.dm @@ -64,7 +64,7 @@ GLOBAL_VAR_INIT(time_last_changed_position, 0) change_position_cooldown = CONFIG_GET(number/id_console_jobslot_delay) /obj/machinery/computer/card/attackby(obj/O, mob/user, params)//TODO:SANITY - if(istype(O, /obj/item/card/id)) + if(isidcard(O)) var/obj/item/card/id/idcard = O if(!modify) if (!user.transferItemToLoc(idcard,src)) diff --git a/code/game/objects/items/cards_ids.dm b/code/game/objects/items/cards_ids.dm index c7782039530e..c8dab14af0df 100644 --- a/code/game/objects/items/cards_ids.dm +++ b/code/game/objects/items/cards_ids.dm @@ -48,11 +48,6 @@ detail_overlay.color = detail_color add_overlay(detail_overlay) -/obj/item/proc/GetCard() - -/obj/item/card/data/GetCard() - return src - /obj/item/card/data/full_color desc = "A plastic magstripe card for simple and speedy data storage and transfer. This one has the entire card colored." icon_state = "data_2" @@ -256,6 +251,9 @@ /obj/item/card/id/GetID() return src +/obj/item/card/id/RemoveID() + return src + /* Usage: update_label() diff --git a/code/game/objects/items/devices/PDA/PDA.dm b/code/game/objects/items/devices/PDA/PDA.dm index 4068cbc0473e..cba668cf85e4 100644 --- a/code/game/objects/items/devices/PDA/PDA.dm +++ b/code/game/objects/items/devices/PDA/PDA.dm @@ -160,6 +160,18 @@ GLOBAL_LIST_EMPTY(PDAs) /obj/item/pda/GetID() return id +/obj/item/pda/RemoveID() + return do_remove_id() + +/obj/item/pda/InsertID(obj/item/inserting_item) + var/obj/item/card/inserting_id = inserting_item.RemoveID() + if(!inserting_id) + return + insert_id(inserting_id) + if(id == inserting_id) + return TRUE + return FALSE + /obj/item/pda/update_icon() cut_overlays() var/mutable_appearance/overlay = new() @@ -696,16 +708,27 @@ GLOBAL_LIST_EMPTY(PDAs) if(issilicon(usr) || !usr.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) return + do_remove_id(usr) + + +/obj/item/pda/proc/do_remove_id(mob/user) + if(!id) + return + if(user) + user.put_in_hands(id) + to_chat(user, "You remove the ID from the [name].") + else + id.forceMove(get_turf(src)) + + . = id + id = null + update_icon() + + if(ishuman(loc)) + var/mob/living/carbon/human/H = loc + if(H.wear_id == src) + H.sec_hud_set_ID() - if (id) - usr.put_in_hands(id) - to_chat(usr, span_notice("You remove the ID from the [name].")) - id = null - update_icon() - if(ishuman(loc)) - var/mob/living/carbon/human/H = loc - if(H.wear_id == src) - H.sec_hud_set_ID() /obj/item/pda/proc/msg_input(mob/living/U = usr) var/t = stripped_input(U, "Please enter message", name) @@ -923,20 +946,28 @@ GLOBAL_LIST_EMPTY(PDAs) if(istype(C)) I = C - if(I && I.registered_name) + if(I && I?.registered_name) if(!user.transferItemToLoc(I, src)) return FALSE - var/obj/old_id = id - id = I - if(ishuman(loc)) - var/mob/living/carbon/human/H = loc - if(H.wear_id == src) - H.sec_hud_set_ID() - if(old_id) - user.put_in_hands(old_id) + insert_id(I, user) update_icon() return TRUE + +/obj/item/pda/proc/insert_id(obj/item/card/id/inserting_id, mob/user) + var/obj/old_id = id + id = inserting_id + if(ishuman(loc)) + var/mob/living/carbon/human/human_wearer = loc + if(human_wearer.wear_id == src) + human_wearer.sec_hud_set_ID() + if(old_id) + if(user) + user.put_in_hands(old_id) + else + old_id.forceMove(get_turf(src)) + + // access to status display signals /obj/item/pda/attackby(obj/item/C, mob/user, params) if(istype(C, /obj/item/cartridge) && !cartridge) diff --git a/code/game/objects/items/storage/wallets.dm b/code/game/objects/items/storage/wallets.dm index 2ab66c509a48..9b2a65ec972a 100644 --- a/code/game/objects/items/storage/wallets.dm +++ b/code/game/objects/items/storage/wallets.dm @@ -71,6 +71,21 @@ /obj/item/storage/wallet/GetID() return front_id +/obj/item/storage/wallet/RemoveID() + if(!front_id) + return + . = front_id + front_id.forceMove(get_turf(src)) + +/obj/item/storage/wallet/InsertID(obj/item/inserting_item) + var/obj/item/card/inserting_id = inserting_item.RemoveID() + if(!inserting_id) + return FALSE + attackby(inserting_id) + if(inserting_id in contents) + return TRUE + return FALSE + /obj/item/storage/wallet/GetAccess() if(LAZYLEN(combined_access)) return combined_access diff --git a/code/modules/jobs/access.dm b/code/modules/jobs/access.dm index b1623cc3d18a..66585f220bfa 100644 --- a/code/modules/jobs/access.dm +++ b/code/modules/jobs/access.dm @@ -37,6 +37,12 @@ /obj/item/proc/GetID() return null +/obj/item/proc/RemoveID() + return null + +/obj/item/proc/InsertID() + return FALSE + /// Convert a text string to a list of accesses /obj/proc/text2access(access_text) . = list() diff --git a/code/modules/jobs/job_types/atmospheric_technician.dm b/code/modules/jobs/job_types/atmospheric_technician.dm index f6eb729f72c8..1d37643dedf6 100644 --- a/code/modules/jobs/job_types/atmospheric_technician.dm +++ b/code/modules/jobs/job_types/atmospheric_technician.dm @@ -46,7 +46,7 @@ duffelbag = /obj/item/storage/backpack/duffelbag/engineering box = /obj/item/storage/box/engineer pda_slot = SLOT_L_STORE - backpack_contents = list(/obj/item/modular_computer/tablet/preset/advanced=1) + backpack_contents = list(/obj/item/modular_computer/tablet/preset/advanced/atmos=1) /datum/outfit/job/atmos/rig name = "Atmospheric Technician (Hardsuit)" diff --git a/code/modules/jobs/job_types/chief_engineer.dm b/code/modules/jobs/job_types/chief_engineer.dm index 4e5c78b64cdd..1a05efd9a431 100644 --- a/code/modules/jobs/job_types/chief_engineer.dm +++ b/code/modules/jobs/job_types/chief_engineer.dm @@ -45,7 +45,7 @@ alt_shoes = /obj/item/clothing/shoes/xeno_wraps/command // Provides Command shoes to digitigrade species head = /obj/item/clothing/head/hardhat/white gloves = /obj/item/clothing/gloves/color/black/ce - backpack_contents = list(/obj/item/melee/classic_baton/telescopic=1, /obj/item/modular_computer/tablet/preset/advanced=1) //yogs - removes eng budget + backpack_contents = list(/obj/item/melee/classic_baton/telescopic=1, /obj/item/modular_computer/tablet/preset/advanced/command=1) //yogs - removes eng budget backpack = /obj/item/storage/backpack/industrial satchel = /obj/item/storage/backpack/satchel/eng diff --git a/code/modules/jobs/job_types/chief_medical_officer.dm b/code/modules/jobs/job_types/chief_medical_officer.dm index e1de06758588..190cae27a20d 100644 --- a/code/modules/jobs/job_types/chief_medical_officer.dm +++ b/code/modules/jobs/job_types/chief_medical_officer.dm @@ -50,7 +50,7 @@ l_hand = /obj/item/storage/firstaid/medical suit_store = /obj/item/flashlight/pen/paramedic glasses = /obj/item/clothing/glasses/hud/health/sunglasses - backpack_contents = list(/obj/item/melee/classic_baton/telescopic=1, /obj/item/modular_computer/tablet/preset/advanced=1) //yogs - removes med budget + backpack_contents = list(/obj/item/melee/classic_baton/telescopic=1, /obj/item/modular_computer/tablet/preset/advanced/command=1) //yogs - removes med budget backpack = /obj/item/storage/backpack/medic satchel = /obj/item/storage/backpack/satchel/med diff --git a/code/modules/jobs/job_types/head_of_personnel.dm b/code/modules/jobs/job_types/head_of_personnel.dm index 29df1103c986..9590c3481795 100644 --- a/code/modules/jobs/job_types/head_of_personnel.dm +++ b/code/modules/jobs/job_types/head_of_personnel.dm @@ -55,6 +55,6 @@ shoes = /obj/item/clothing/shoes/sneakers/brown head = /obj/item/clothing/head/hopcap backpack_contents = list(/obj/item/storage/box/ids=1,\ - /obj/item/melee/classic_baton/telescopic=1, /obj/item/modular_computer/tablet/preset/advanced=1) //yogs - removes serv budget + /obj/item/melee/classic_baton/telescopic=1, /obj/item/modular_computer/tablet/preset/advanced/command=1) //yogs - removes serv budget chameleon_extras = list(/obj/item/gun/energy/e_gun, /obj/item/stamp/hop) diff --git a/code/modules/jobs/job_types/head_of_security.dm b/code/modules/jobs/job_types/head_of_security.dm index ae1265c89b35..9d07ede4cda5 100644 --- a/code/modules/jobs/job_types/head_of_security.dm +++ b/code/modules/jobs/job_types/head_of_security.dm @@ -56,7 +56,7 @@ suit_store = /obj/item/gun/energy/e_gun r_pocket = /obj/item/assembly/flash/handheld l_pocket = /obj/item/restraints/handcuffs - backpack_contents = list(/obj/item/melee/baton/loaded=1, /obj/item/modular_computer/tablet/preset/advanced=1) //yogs - removed departmental budget ID //come here often? + backpack_contents = list(/obj/item/melee/baton/loaded=1, /obj/item/modular_computer/tablet/preset/advanced/command=1) //yogs - removed departmental budget ID //come here often? backpack = /obj/item/storage/backpack/security satchel = /obj/item/storage/backpack/satchel/sec diff --git a/code/modules/jobs/job_types/research_director.dm b/code/modules/jobs/job_types/research_director.dm index 7c5401df6192..4c9e1437a2ea 100644 --- a/code/modules/jobs/job_types/research_director.dm +++ b/code/modules/jobs/job_types/research_director.dm @@ -54,7 +54,7 @@ suit = /obj/item/clothing/suit/toggle/labcoat l_hand = /obj/item/clipboard l_pocket = /obj/item/laser_pointer - backpack_contents = list(/obj/item/melee/classic_baton/telescopic=1, /obj/item/modular_computer/tablet/preset/advanced=1) //yogs - removes sci budget + backpack_contents = list(/obj/item/melee/classic_baton/telescopic=1, /obj/item/modular_computer/tablet/preset/advanced/command=1) //yogs - removes sci budget backpack = /obj/item/storage/backpack/science satchel = /obj/item/storage/backpack/satchel/tox diff --git a/code/modules/mob/living/carbon/human/human_helpers.dm b/code/modules/mob/living/carbon/human/human_helpers.dm index 2f899e1e2676..b329ff9fcfcd 100644 --- a/code/modules/mob/living/carbon/human/human_helpers.dm +++ b/code/modules/mob/living/carbon/human/human_helpers.dm @@ -75,12 +75,8 @@ . = pda.owner else if(istype(tablet)) var/obj/item/computer_hardware/card_slot/card_slot = tablet.all_components[MC_CARD] - if(card_slot && (card_slot.stored_card2 || card_slot.stored_card)) - if(card_slot.stored_card2) //The second card is the one used for authorization in the ID changing program, so we prioritize it here for consistency - . = card_slot.stored_card2.registered_name - else - if(card_slot.stored_card) - . = card_slot.stored_card.registered_name + if(card_slot?.stored_card) + . = card_slot.stored_card.registered_name if(!.) . = if_no_id //to prevent null-names making the mob unclickable return @@ -114,6 +110,12 @@ if(id_card) return id_card +/mob/living/carbon/human/get_id_in_hand() + var/obj/item/held_item = get_active_held_item() + if(!held_item) + return + return held_item.GetID() + /mob/living/carbon/human/IsAdvancedToolUser() if(HAS_TRAIT(src, TRAIT_MONKEYLIKE)) return FALSE @@ -272,4 +274,4 @@ WRITE_FILE(F["current_scar_index"], sanitize_integer(scar_index)) /mob/living/carbon/human/get_biological_state() - return dna.species.get_biological_state() \ No newline at end of file + return dna.species.get_biological_state() diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm index d01e789aa4c2..9eac34b701b9 100644 --- a/code/modules/mob/mob.dm +++ b/code/modules/mob/mob.dm @@ -1174,6 +1174,9 @@ /mob/proc/get_idcard(hand_first) return +/mob/proc/get_id_in_hand() + return + /** * Get the mob VV dropdown extras */ diff --git a/code/modules/modular_computers/computers/_modular_computer_shared.dm b/code/modules/modular_computers/computers/_modular_computer_shared.dm index 61e214893029..953393439b08 100644 --- a/code/modules/modular_computers/computers/_modular_computer_shared.dm +++ b/code/modules/modular_computers/computers/_modular_computer_shared.dm @@ -44,18 +44,20 @@ . += "It has a slot installed for an intelliCard." var/obj/item/computer_hardware/card_slot/card_slot = get_modular_computer_part(MC_CARD) + var/obj/item/computer_hardware/card_slot/card_slot2 = get_modular_computer_part(MC_CARD2) + var/multiple_slots = istype(card_slot) && istype(card_slot2) if(card_slot) - if(card_slot.stored_card || card_slot.stored_card2) + if(card_slot.stored_card || card_slot2.stored_card) var/obj/item/card/id/first_ID = card_slot.stored_card - var/obj/item/card/id/second_ID = card_slot.stored_card2 + var/obj/item/card/id/second_ID = card_slot2.stored_card var/multiple_cards = istype(first_ID) && istype(second_ID) if(user_is_adjacent) - . += "It has two slots for identification cards installed[multiple_cards ? " which contain [first_ID] and [second_ID]" : ", one of which contains [first_ID ? first_ID : second_ID]"]." + . += "It has [multiple_slots ? "two slots" : "a slot"] for identification cards installed[multiple_cards ? " which contain [first_ID] and [second_ID]" : ", one of which contains [first_ID ? first_ID : second_ID]"]." else - . += "It has two slots for identification cards installed, [multiple_cards ? "both of which appear" : "and one of them appears"] to be occupied." + . += "It has [multiple_slots ? "two slots" : "a slot"] for identification cards installed, [multiple_cards ? "both of which appear" : "and one of them appears"] to be occupied." . += span_info("Alt-click [src] to eject the identification card[multiple_cards ? "s":""].") else - . += "It has two slots installed for identification cards." + . += "It has [multiple_slots ? "two slots" : "a slot"] installed for identification cards." var/obj/item/computer_hardware/printer/printer_slot = get_modular_computer_part(MC_PRINT) if(printer_slot) diff --git a/code/modules/modular_computers/computers/item/computer.dm b/code/modules/modular_computers/computers/item/computer.dm index 443fff0a6bac..be3c25330f87 100644 --- a/code/modules/modular_computers/computers/item/computer.dm +++ b/code/modules/modular_computers/computers/item/computer.dm @@ -7,6 +7,7 @@ var/enabled = 0 // Whether the computer is turned on. var/screen_on = 1 // Whether the computer is active/opened/it's screen is on. + var/device_theme = "ntos" // Sets the theme for the main menu, hardware config, and file browser apps. Overridden by certain non-NT devices. var/datum/computer_file/program/active_program = null // A currently active program running on the computer. var/hardware_flag = 0 // A flag that describes this device type var/last_power_usage = 0 @@ -33,11 +34,12 @@ max_integrity = 100 armor = list("melee" = 0, "bullet" = 20, "laser" = 20, "energy" = 100, "bomb" = 0, "bio" = 100, "rad" = 100, "fire" = 0, "acid" = 0) - // Important hardware (must be installed for computer to work) - - // Optional hardware (improves functionality, but is not critical for computer to work) - - var/list/all_components = list() // List of "connection ports" in this computer and the components with which they are plugged + /// List of "connection ports" in this computer and the components with which they are plugged + var/list/all_components = list() + /// Lazy List of extra hardware slots that can be used modularly. + var/list/expansion_bays + /// Number of total expansion bays this computer has available. + var/max_bays = 0 var/list/idle_threads // Idle programs on background. They still receive process calls but can't be interacted with. var/obj/physical = null // Object that represents our computer. It's used for Adjacent() and UI visibility checks. @@ -69,61 +71,22 @@ physical = null return ..() +/obj/item/modular_computer/attack(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 + return ..() -/obj/item/modular_computer/proc/add_verb(var/path) - switch(path) - if(MC_CARD) - verbs += /obj/item/modular_computer/proc/eject_id - if(MC_SDD) - verbs += /obj/item/modular_computer/proc/eject_disk - if(MC_AI) - verbs += /obj/item/modular_computer/proc/eject_card - -/obj/item/modular_computer/proc/remove_verb(path) - switch(path) - if(MC_CARD) - verbs -= /obj/item/modular_computer/proc/eject_id - if(MC_SDD) - verbs -= /obj/item/modular_computer/proc/eject_disk - if(MC_AI) - verbs -= /obj/item/modular_computer/proc/eject_card - -// Eject ID card from computer, if it has ID slot with card inside. -/obj/item/modular_computer/proc/eject_id() - set name = "Eject ID" - set category = "Object" - set src in view(1) - - if(issilicon(usr)) - return - var/obj/item/computer_hardware/card_slot/card_slot = all_components[MC_CARD] - if(usr.canUseTopic(src, BE_CLOSE)) - card_slot.try_eject(null, usr) - -// Eject ID card from computer, if it has ID slot with card inside. -/obj/item/modular_computer/proc/eject_card() - set name = "Eject Intellicard" - set category = "Object" - - if(issilicon(usr)) - return - var/obj/item/computer_hardware/ai_slot/ai_slot = all_components[MC_AI] - if(usr.canUseTopic(src, BE_CLOSE)) - ai_slot.try_eject(null, usr,1) - - -// Eject ID card from computer, if it has ID slot with card inside. -/obj/item/modular_computer/proc/eject_disk() - set name = "Eject Data Disk" - set category = "Object" - - if(issilicon(usr)) - return - if(usr.canUseTopic(src, BE_CLOSE)) - var/obj/item/computer_hardware/hard_drive/portable/portable_drive = all_components[MC_SDD] - if(uninstall_component(portable_drive, usr)) - portable_drive.verb_pickup() +/** + * 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) ..() @@ -131,17 +94,9 @@ return if(user.canUseTopic(src, BE_CLOSE)) + var/obj/item/computer_hardware/card_slot/card_slot2 = all_components[MC_CARD2] var/obj/item/computer_hardware/card_slot/card_slot = all_components[MC_CARD] - var/obj/item/computer_hardware/ai_slot/ai_slot = all_components[MC_AI] - var/obj/item/computer_hardware/hard_drive/portable/portable_drive = all_components[MC_SDD] - if(portable_drive) - if(uninstall_component(portable_drive, user)) - portable_drive.verb_pickup() - else - if(card_slot && card_slot.try_eject(null, user)) - return - if(ai_slot) - ai_slot.try_eject(null, user) + return (card_slot2?.try_eject(user) || card_slot?.try_eject(user)) //Try the secondary one first. // Gets IDs/access levels from card slot. Would be useful when/if PDAs would become modular PCs. @@ -151,12 +106,48 @@ return card_slot.GetAccess() return ..() +/obj/item/modular_computer/RemoveID() + var/obj/item/computer_hardware/card_slot/card_slot2 = all_components[MC_CARD2] + var/obj/item/computer_hardware/card_slot/card_slot = all_components[MC_CARD] + return (card_slot2?.try_eject() || card_slot?.try_eject()) //Try the secondary one first. + +/obj/item/modular_computer/InsertID(obj/item/inserting_item) + var/obj/item/computer_hardware/card_slot/card_slot = all_components[MC_CARD] + var/obj/item/computer_hardware/card_slot/card_slot2 = all_components[MC_CARD2] + if(!(card_slot || card_slot2)) + //to_chat(user, "There isn't anywhere you can fit a card into on this computer.") + return FALSE + + var/obj/item/card/inserting_id = inserting_item.RemoveID() + if(!inserting_id) + return FALSE + + if((card_slot?.try_insert(inserting_id)) || (card_slot2?.try_insert(inserting_id))) + return TRUE + //to_chat(user, "This computer doesn't have an open card slot.") + return FALSE + /obj/item/modular_computer/GetID() var/obj/item/computer_hardware/card_slot/card_slot = all_components[MC_CARD] if(card_slot) return card_slot.GetID() return ..() +/obj/item/modular_computer/RemoveID() + var/obj/item/computer_hardware/card_slot/card_slot = all_components[MC_CARD] + if(!card_slot) + return + return card_slot.RemoveID() + +/obj/item/modular_computer/InsertID(obj/item/inserting_item) + var/obj/item/computer_hardware/card_slot/card_slot = all_components[MC_CARD] + if(!card_slot) + return FALSE + var/obj/item/card/inserting_id = inserting_item.RemoveID() + if(!inserting_id) + return FALSE + return card_slot.try_insert(inserting_id) + /obj/item/modular_computer/MouseDrop(obj/over_object, src_location, over_location) var/mob/M = usr if((!istype(over_object, /obj/screen)) && usr.canUseTopic(src, BE_CLOSE)) @@ -178,13 +169,22 @@ turn_on(user) /obj/item/modular_computer/emag_act(mob/user) - if(obj_flags & EMAGGED) - to_chat(user, span_warning("\The [src] was already emagged.")) - return 0 - else - obj_flags |= EMAGGED - to_chat(user, span_notice("You emag \the [src]. It's screen briefly shows a \"OVERRIDE ACCEPTED: New software downloads available.\" message.")) - return 1 + if(!enabled) + to_chat(user, "You'd need to turn the [src] on first.") + return FALSE + obj_flags |= EMAGGED //Mostly for consistancy purposes; the programs will do their own emag handling + var/newemag = FALSE + var/obj/item/computer_hardware/hard_drive/drive = all_components[MC_HDD] + for(var/datum/computer_file/program/app in drive.stored_files) + if(!istype(app)) + continue + if(app.run_emag()) + newemag = TRUE + if(newemag) + to_chat(user, "You swipe \the [src]. A console window momentarily fills the screen, with white text rapidly scrolling past.") + return TRUE + to_chat(user, "You swipe \the [src]. A console window fills the screen, but it quickly closes itself after only a few lines are written to it.") + return FALSE /obj/item/modular_computer/examine(mob/user) . = ..() @@ -282,10 +282,34 @@ handle_power() // Handles all computer power interaction //check_update_ui_need() +/** + * Displays notification text alongside a soundbeep when requested to by a program. + * + * After checking tha the requesting program is allowed to send an alert, creates + * a visible message of the requested text alongside a soundbeep. This proc adds + * text to indicate that the message is coming from this device and the program + * on it, so the supplied text should be the exact message and ending punctuation. + * + * Arguments: + * The program calling this proc. + * The message that the program wishes to display. + */ + +/obj/item/modular_computer/proc/alert_call(datum/computer_file/program/caller, alerttext, sound = 'sound/machines/twobeep_high.ogg') + if(!caller || !caller.alert_able || caller.alert_silenced || !alerttext) //Yeah, we're checking alert_able. No, you don't get to make alerts that the user can't silence. + return + playsound(src, sound, 50, TRUE) + visible_message("The [src] displays a [caller.filedesc] notification: [alerttext]") + var/mob/living/holder = loc + if(istype(holder)) + to_chat(holder, "[icon2html(src)] The [src] displays a [caller.filedesc] notification: [alerttext]") + // Function used by NanoUI's to obtain data for header. All relevant entries begin with "PC_" /obj/item/modular_computer/proc/get_header_data() var/list/data = list() + data["PC_device_theme"] = device_theme + var/obj/item/computer_hardware/battery/battery_module = all_components[MC_CELL] var/obj/item/computer_hardware/recharger/recharger = all_components[MC_CHARGE] diff --git a/code/modules/modular_computers/computers/item/computer_components.dm b/code/modules/modular_computers/computers/item/computer_components.dm index 106ac9d19b58..b328e6701e27 100644 --- a/code/modules/modular_computers/computers/item/computer_components.dm +++ b/code/modules/modular_computers/computers/item/computer_components.dm @@ -6,6 +6,14 @@ to_chat(user, span_warning("This component is too large for \the [src]!")) return FALSE + if(H.expansion_hw) + if(LAZYLEN(expansion_bays) >= max_bays) + to_chat(user, "All of the computer's expansion bays are filled.") + return FALSE + if(LAZYACCESS(expansion_bays, H.device_type)) + to_chat(user, "The computer immediately ejects /the [H] and flashes an error: \"Hardware Address Conflict\".") + return FALSE + if(all_components[H.device_type]) to_chat(user, span_warning("This computer's hardware slot is already occupied by \the [all_components[H.device_type]].")) return FALSE @@ -20,6 +28,8 @@ if(user && !user.transferItemToLoc(H, src)) return FALSE + if(H.expansion_hw) + LAZYSET(expansion_bays, H.device_type, H) all_components[H.device_type] = H to_chat(user, span_notice("You install \the [H] into \the [src].")) @@ -32,7 +42,9 @@ /obj/item/modular_computer/proc/uninstall_component(obj/item/computer_hardware/H, mob/living/user = null) if(H.holder != src) // Not our component at all. return FALSE + if(H.expansion_hw) + LAZYREMOVE(expansion_bays, H.device_type) all_components.Remove(H.device_type) to_chat(user, span_notice("You remove \the [H] from \the [src].")) @@ -43,6 +55,7 @@ if(enabled && !use_power()) shutdown_computer() update_icon() + return TRUE // Checks all hardware pieces to determine if name matches, if yes, returns the hardware piece, otherwise returns null diff --git a/code/modules/modular_computers/computers/item/computer_ui.dm b/code/modules/modular_computers/computers/item/computer_ui.dm index e8bcde73ca6a..3b75662ee05a 100644 --- a/code/modules/modular_computers/computers/item/computer_ui.dm +++ b/code/modules/modular_computers/computers/item/computer_ui.dm @@ -6,17 +6,17 @@ if(!enabled) if(ui) ui.close() - return 0 + return FALSE if(!use_power()) if(ui) ui.close() - return 0 + return FALSE // Robots don't really need to see the screen, their wireless connection works as long as computer is on. if(!screen_on && !issilicon(user)) if(ui) ui.close() - return 0 - return 1 + return FALSE + return TRUE // Operates TGUI /obj/item/modular_computer/ui_interact(mob/user, datum/tgui/ui) @@ -38,22 +38,54 @@ ui = SStgui.try_update_ui(user, src, ui) if (!ui) - ui = new(user, src, "NtosMain") - ui.set_autoupdate(TRUE) - ui.open() - ui.send_asset(get_asset_datum(/datum/asset/simple/headers)) + var/headername + switch(device_theme) + if("ntos") + headername = "NtOS Main Menu" + if("syndicate") + headername = "Syndix Main Menu" + ui = new(user, src, "NtosMain", headername, 400, 500) + if(ui.open()) + ui.send_asset(get_asset_datum(/datum/asset/simple/headers)) /obj/item/modular_computer/ui_data(mob/user) var/list/data = get_header_data() + data["device_theme"] = device_theme + data["login"] = list() + var/obj/item/computer_hardware/card_slot/cardholder = all_components[MC_CARD] + if(cardholder) + var/obj/item/card/id/stored_card = cardholder.GetID() + if(stored_card) + var/stored_name = stored_card.registered_name + var/stored_title = stored_card.assignment + if(!stored_name) + stored_name = "Unknown" + if(!stored_title) + stored_title = "Unknown" + data["login"] = list( + IDName = stored_name, + IDJob = stored_title, + ) + + data["removable_media"] = list() + if(all_components[MC_SDD]) + data["removable_media"] += "removable storage disk" + var/obj/item/computer_hardware/ai_slot/intelliholder = all_components[MC_AI] + if(intelliholder?.stored_card) + data["removable_media"] += "intelliCard" + var/obj/item/computer_hardware/card_slot/secondarycardholder = all_components[MC_CARD2] + if(secondarycardholder?.stored_card) + data["removable_media"] += "secondary RFID card" + data["programs"] = list() var/obj/item/computer_hardware/hard_drive/hard_drive = all_components[MC_HDD] for(var/datum/computer_file/program/P in hard_drive.stored_files) - var/running = 0 + var/running = FALSE if(P in idle_threads) - running = 1 + running = TRUE - data["programs"] += list(list("name" = P.filename, "desc" = P.filedesc, "running" = running)) + data["programs"] += list(list("name" = P.filename, "desc" = P.filedesc, "running" = running, "icon" = P.program_icon, "alert" = P.alert_pending)) data["has_light"] = has_light data["light_on"] = light_on @@ -69,10 +101,10 @@ switch(action) if("PC_exit") kill_program() - return 1 + return TRUE if("PC_shutdown") shutdown_computer() - return 1 + return TRUE if("PC_minimize") var/mob/user = usr if(!active_program || !all_components[MC_CPU]) @@ -119,6 +151,7 @@ if(P in idle_threads) P.program_state = PROGRAM_STATE_ACTIVE active_program = P + P.alert_pending = FALSE idle_threads.Remove(P) update_icon() return @@ -134,8 +167,9 @@ return if(P.run_program(user)) active_program = P + P.alert_pending = FALSE update_icon() - return 1 + return TRUE if("PC_toggle_light") light_on = !light_on @@ -159,6 +193,36 @@ light_color = new_color update_light() return TRUE + + if("PC_Eject_Disk") + var/param = params["name"] + var/mob/user = usr + switch(param) + if("removable storage disk") + var/obj/item/computer_hardware/hard_drive/portable/portable_drive = all_components[MC_SDD] + if(!portable_drive) + return + if(uninstall_component(portable_drive, usr)) + user.put_in_hands(portable_drive) + playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50) + if("intelliCard") + var/obj/item/computer_hardware/ai_slot/intelliholder = all_components[MC_AI] + if(!intelliholder) + return + if(intelliholder.try_eject(user)) + playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50) + if("ID") + var/obj/item/computer_hardware/card_slot/cardholder = all_components[MC_CARD] + if(!cardholder) + return + cardholder.try_eject(user) + if("secondary RFID card") + var/obj/item/computer_hardware/card_slot/cardholder = all_components[MC_CARD2] + if(!cardholder) + return + cardholder.try_eject(user) + + else return diff --git a/code/modules/modular_computers/computers/item/laptop.dm b/code/modules/modular_computers/computers/item/laptop.dm index cd40143ac2b0..82ede8ccad77 100644 --- a/code/modules/modular_computers/computers/item/laptop.dm +++ b/code/modules/modular_computers/computers/item/laptop.dm @@ -11,6 +11,7 @@ hardware_flag = PROGRAM_LAPTOP max_hardware_size = WEIGHT_CLASS_NORMAL w_class = WEIGHT_CLASS_NORMAL + max_bays = 4 // No running around with open laptops in hands. item_flags = SLOWS_WHILE_IN_HAND diff --git a/code/modules/modular_computers/computers/item/phone.dm b/code/modules/modular_computers/computers/item/phone.dm index 3906bb8ff16b..b0374a406d73 100644 --- a/code/modules/modular_computers/computers/item/phone.dm +++ b/code/modules/modular_computers/computers/item/phone.dm @@ -8,5 +8,6 @@ hardware_flag = PROGRAM_PHONE max_hardware_size = WEIGHT_CLASS_TINY w_class = WEIGHT_CLASS_SMALL + max_bays = 2 steel_sheet_cost = 1 slot_flags = ITEM_SLOT_ID | ITEM_SLOT_BELT diff --git a/code/modules/modular_computers/computers/item/processor.dm b/code/modules/modular_computers/computers/item/processor.dm index 5a93c656f593..4ab6264a6ee0 100644 --- a/code/modules/modular_computers/computers/item/processor.dm +++ b/code/modules/modular_computers/computers/item/processor.dm @@ -58,23 +58,11 @@ machinery_computer.update_icon() return -/obj/item/modular_computer/processor/add_verb(path) - switch(path) - if(MC_CARD) - machinery_computer.verbs += /obj/machinery/modular_computer/proc/eject_id - if(MC_SDD) - machinery_computer.verbs += /obj/machinery/modular_computer/proc/eject_disk - if(MC_AI) - machinery_computer.verbs += /obj/machinery/modular_computer/proc/eject_card - -/obj/item/modular_computer/processor/remove_verb(path) - switch(path) - if(MC_CARD) - machinery_computer.verbs -= /obj/machinery/modular_computer/proc/eject_id - if(MC_SDD) - machinery_computer.verbs -= /obj/machinery/modular_computer/proc/eject_disk - if(MC_AI) - machinery_computer.verbs -= /obj/machinery/modular_computer/proc/eject_card - /obj/item/modular_computer/processor/attack_ghost(mob/user) ui_interact(user) + +/obj/item/modular_computer/processor/alert_call(datum/computer_file/program/caller, alerttext) + if(!caller || !caller.alert_able || caller.alert_silenced || !alerttext) + return + playsound(src, 'sound/machines/twobeep_high.ogg', 50, TRUE) + machinery_computer.visible_message("The [src] displays a [caller.filedesc] notification: [alerttext]") diff --git a/code/modules/modular_computers/computers/item/tablet.dm b/code/modules/modular_computers/computers/item/tablet.dm index 687b942462dd..3c6216a51975 100644 --- a/code/modules/modular_computers/computers/item/tablet.dm +++ b/code/modules/modular_computers/computers/item/tablet.dm @@ -9,6 +9,7 @@ hardware_flag = PROGRAM_TABLET max_hardware_size = WEIGHT_CLASS_SMALL w_class = WEIGHT_CLASS_NORMAL + max_bays = 3 steel_sheet_cost = 1 slot_flags = ITEM_SLOT_BELT has_light = TRUE //LED flashlight! @@ -25,6 +26,7 @@ icon_state_unpowered = "[icon_state_base]-[finish_color]" icon_state_powered = "[icon_state_base]-[finish_color]" + /obj/item/modular_computer/tablet/syndicate_contract_uplink name = "contractor tablet" icon = 'icons/obj/contractor_tablet.dmi' @@ -36,3 +38,16 @@ slot_flags = ITEM_SLOT_ID | ITEM_SLOT_BELT comp_light_luminosity = 6.3 has_variants = FALSE + +/// Given to Nuke Ops members. +/obj/item/modular_computer/tablet/nukeops + comp_light_luminosity = 6.3 + finish_color = "red" + device_theme = "syndicate" + +/obj/item/modular_computer/tablet/nukeops/emag_act(mob/user) + if(!enabled) + to_chat(user, "You'd need to turn the [src] on first.") + return FALSE + to_chat(user, "You swipe \the [src]. It's screen briefly shows a message reading \"MEMORY CODE INJECTION DETECTED AND SUCCESSFULLY QUARANTINED\".") + return FALSE diff --git a/code/modules/modular_computers/computers/item/tablet_presets.dm b/code/modules/modular_computers/computers/item/tablet_presets.dm index ef8b09a82cbf..2c44aa4a7396 100644 --- a/code/modules/modular_computers/computers/item/tablet_presets.dm +++ b/code/modules/modular_computers/computers/item/tablet_presets.dm @@ -25,9 +25,19 @@ install_component(new /obj/item/computer_hardware/processor_unit/small) install_component(new /obj/item/computer_hardware/battery(src, /obj/item/stock_parts/cell/computer)) install_component(new /obj/item/computer_hardware/hard_drive/small) + install_component(new /obj/item/computer_hardware/card_slot) install_component(new /obj/item/computer_hardware/network_card) install_component(new /obj/item/computer_hardware/printer/mini) +/obj/item/modular_computer/tablet/preset/advanced/atmos/Initialize() //This will be defunct and will be replaced when NtOS PDAs are done + . = ..() + install_component(new /obj/item/computer_hardware/sensorpackage) + +/obj/item/modular_computer/tablet/preset/advanced/command/Initialize() + . = ..() + install_component(new /obj/item/computer_hardware/sensorpackage) + install_component(new /obj/item/computer_hardware/card_slot/secondary) + /// Given by the syndicate as part of the contract uplink bundle - loads in the Contractor Uplink. /obj/item/modular_computer/tablet/syndicate_contract_uplink/preset/uplink/Initialize() . = ..() @@ -47,6 +57,14 @@ install_component(new /obj/item/computer_hardware/card_slot) install_component(new /obj/item/computer_hardware/printer/mini) +/// Given to Nuke Ops members. +/obj/item/modular_computer/tablet/nukeops/Initialize() + . = ..() + install_component(new /obj/item/computer_hardware/processor_unit/small) + install_component(new /obj/item/computer_hardware/battery(src, /obj/item/stock_parts/cell/computer)) + install_component(new /obj/item/computer_hardware/hard_drive/small/nukeops) + install_component(new /obj/item/computer_hardware/network_card) + //Phone Presets// // This is literally the worst possible cheap phone @@ -75,5 +93,6 @@ install_component(new /obj/item/computer_hardware/processor_unit/small) install_component(new /obj/item/computer_hardware/battery(src, /obj/item/stock_parts/cell/computer)) install_component(new /obj/item/computer_hardware/hard_drive/small) + install_component(new /obj/item/computer_hardware/card_slot) install_component(new /obj/item/computer_hardware/network_card) install_component(new /obj/item/computer_hardware/printer/mini) diff --git a/code/modules/modular_computers/computers/machinery/console_presets.dm b/code/modules/modular_computers/computers/machinery/console_presets.dm index 8b4fcc27ed1f..1a2d724e2c8f 100644 --- a/code/modules/modular_computers/computers/machinery/console_presets.dm +++ b/code/modules/modular_computers/computers/machinery/console_presets.dm @@ -1,6 +1,6 @@ /obj/machinery/modular_computer/console/preset // Can be changed to give devices specific hardware - var/_has_id_slot = FALSE + var/_has_second_id_slot = FALSE var/_has_printer = FALSE var/_has_battery = FALSE var/_has_ai = FALSE @@ -11,8 +11,9 @@ return cpu.install_component(new /obj/item/computer_hardware/processor_unit) - if(_has_id_slot) - cpu.install_component(new /obj/item/computer_hardware/card_slot) + cpu.install_component(new /obj/item/computer_hardware/card_slot) + if(_has_second_id_slot) + cpu.install_component(new /obj/item/computer_hardware/card_slot/secondary) if(_has_printer) cpu.install_component(new /obj/item/computer_hardware/printer) if(_has_battery) @@ -59,7 +60,7 @@ console_department = "Command" name = "command console" desc = "A stationary computer. This one comes preloaded with command programs." - _has_id_slot = TRUE + _has_second_id_slot = TRUE _has_printer = TRUE /obj/machinery/modular_computer/console/preset/command/install_programs() diff --git a/code/modules/modular_computers/computers/machinery/modular_computer.dm b/code/modules/modular_computers/computers/machinery/modular_computer.dm index 0a1a8e11fd3c..0af703169ba0 100644 --- a/code/modules/modular_computers/computers/machinery/modular_computer.dm +++ b/code/modules/modular_computers/computers/machinery/modular_computer.dm @@ -48,7 +48,10 @@ cpu.attack_ghost(user) /obj/machinery/modular_computer/emag_act(mob/user) - return cpu ? cpu.emag_act(user) : 1 + if(!cpu) + to_chat(user, "You'd need to turn the [src] on first.") + return FALSE + return (cpu.emag_act(user)) /obj/machinery/modular_computer/update_icon() cut_overlays() @@ -71,29 +74,6 @@ add_overlay("bsod") add_overlay("broken") -// Eject ID card from computer, if it has ID slot with card inside. -/obj/machinery/modular_computer/proc/eject_id() - set name = "Eject ID" - set category = "Object" - - if(cpu) - cpu.eject_id() - -// Eject ID card from computer, if it has ID slot with card inside. -/obj/machinery/modular_computer/proc/eject_disk() - set name = "Eject Data Disk" - set category = "Object" - - if(cpu) - cpu.eject_disk() - -/obj/machinery/modular_computer/proc/eject_card() - set name = "Eject Intellicard" - set category = "Object" - set src in view(1) - - if(cpu) - cpu.eject_card() /obj/machinery/modular_computer/AltClick(mob/user) if(cpu) @@ -133,7 +113,7 @@ . = ..() /obj/machinery/modular_computer/attackby(var/obj/item/W as obj, mob/user) - if(cpu && !(flags_1 & NODECONSTRUCT_1)) + if(user.a_intent == INTENT_HELP && cpu && !(flags_1 & NODECONSTRUCT_1)) return cpu.attackby(W, user) return ..() diff --git a/code/modules/modular_computers/file_system/program.dm b/code/modules/modular_computers/file_system/program.dm index f5d63600b494..15e7f519ca2f 100644 --- a/code/modules/modular_computers/file_system/program.dm +++ b/code/modules/modular_computers/file_system/program.dm @@ -15,6 +15,8 @@ var/filedesc = "Unknown Program" /// Short description of this program's function. var/extended_desc = "N/A" + /// Category in the NTDownloader. + var/category = PROGRAM_CATEGORY_MISC /// Program-specific screen icon state var/program_icon_state = null /// Set to 1 for program to require nonstop NTNet connection to run. If NTNet connection is lost program crashes. @@ -35,6 +37,14 @@ var/tgui_id /// Example: "something.gif" - a header image that will be rendered in computer's UI when this program is running at background. Images are taken from /icons/program_icons. Be careful not to use too large images! var/ui_header = null + /// Font Awesome icon to use as this program's icon in the modular computer main menu. Defaults to a basic program maximize window icon if not overridden. + var/program_icon = "window-maximize-o" + /// Whether this program can send alerts while minimized or closed. Used to show a mute button per program in the file manager + var/alert_able = FALSE + /// Whether the user has muted this program's ability to send alerts. + var/alert_silenced = FALSE + /// Whether to highlight our program in the main screen. Intended for alerts, but loosely available for any need to notify of changed conditions. Think Windows task bar highlighting. Available even if alerts are muted. + var/alert_pending = FALSE /datum/computer_file/program/New(obj/item/modular_computer/comp = null) ..() @@ -64,28 +74,51 @@ /datum/computer_file/program/proc/generate_network_log(text) if(computer) return computer.add_log(text) - return 0 + return FALSE + +/** + *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) to_chat(user, span_danger("\The [computer] flashes an \"Hardware Error - Incompatible software\" warning.")) - return 0 - return 1 + return FALSE + return TRUE /datum/computer_file/program/proc/get_signal(specific_action = 0) if(computer) return computer.get_ntnet_status(specific_action) - return 0 + return FALSE // Called by Process() on device that runs us, once every tick. /datum/computer_file/program/proc/process_tick() - return 1 - -// Check if the user can run program. Only humans can operate computer. Automatically called in run_program() -// User has to wear their ID for ID Scan to work. -// Can also be called manually, with optional parameter being access_to_check to scan the user's ID -/datum/computer_file/program/proc/can_run(mob/user, loud = FALSE, access_to_check, transfer = FALSE) + return TRUE + +/** + *Check if the user can run program. Only humans can operate computer. Automatically called in run_program() + *ID must be inserted into a card slot to be read. If the program is not currently installed (as is the case when + *NT Software Hub is checking available software), a list can be given to be used instead. + *Arguments: + *user is a ref of the mob using the device. + *loud is a bool deciding if this proc should use to_chats + *access_to_check is an access level that will be checked against the ID + *transfer, if TRUE and access_to_check is null, will tell this proc to use the program's transfer_access in place of access_to_check + *access can contain a list of access numbers to check against. If access is not empty, it will be used istead of checking any inserted ID. +*/ +/datum/computer_file/program/proc/can_run(mob/user, loud = FALSE, access_to_check, transfer = FALSE, var/list/access) // Defaults to required_access if(!access_to_check) if(transfer && transfer_access) @@ -104,29 +137,24 @@ if(issilicon(user)) return TRUE - if(ishuman(user)) + if(!length(access)) var/obj/item/card/id/D var/obj/item/computer_hardware/card_slot/card_slot - if(computer && card_slot) + if(computer) card_slot = computer.all_components[MC_CARD] - D = card_slot.GetID() - var/mob/living/carbon/human/h = user - var/obj/item/card/id/I = h.get_idcard(TRUE) + D = card_slot?.GetID() - if(!I && !D) + if(!D) if(loud) to_chat(user, span_danger("\The [computer] flashes an \"RFID Error - Unable to scan ID\" warning.")) return FALSE + access = D.GetAccess() - if(I) - if(access_to_check in I.GetAccess()) - return TRUE - else if(D) - if(access_to_check in D.GetAccess()) - return TRUE - if(loud) - to_chat(user, span_danger("\The [computer] flashes an \"Access Denied\" warning.")) - return 0 + if(access_to_check in access) + return TRUE + if(loud) + to_chat(user, "\The [computer] flashes an \"Access Denied\" warning.") + return FALSE // This attempts to retrieve header data for UIs. If implementing completely new device of different type than existing ones // always include the device here in this proc. This proc basically relays the request to whatever is running the program. @@ -142,15 +170,29 @@ if(requires_ntnet && network_destination) generate_network_log("Connection opened to [network_destination].") program_state = PROGRAM_STATE_ACTIVE - return 1 - return 0 + return TRUE + return FALSE + +/** + * + *Called by the device when it is emagged. + * + *Emagging the device allows certain programs to unlock new functions. However, the program will + *need to be downloaded first, and then handle the unlock on their own in their run_emag() proc. + *The device will allow an emag to be run multiple times, so the user can re-emag to run the + *override again, should they download something new. The run_emag() proc should return TRUE if + *the emagging affected anything, and FALSE if no change was made (already emagged, or has no + *emag functions). +**/ +/datum/computer_file/program/proc/run_emag() + return FALSE // Use this proc to kill the program. Designed to be implemented by each program if it requires on-quit logic, such as the NTNRC client. /datum/computer_file/program/proc/kill_program(forced = FALSE) program_state = PROGRAM_STATE_KILLED if(network_destination) generate_network_log("Connection to [network_destination] closed.") - return 1 + return TRUE /datum/computer_file/program/ui_interact(mob/user, datum/tgui/ui) @@ -159,8 +201,8 @@ return if(!ui && tgui_id) ui = new(user, src, tgui_id, filedesc) - ui.open() - ui.send_asset(get_asset_datum(/datum/asset/simple/headers)) + if(ui.open()) + ui.send_asset(get_asset_datum(/datum/asset/simple/headers)) // CONVENTIONS, READ THIS WHEN CREATING NEW PROGRAM AND OVERRIDING THIS PROC: // Topic calls are automagically forwarded from NanoModule this program contains. // Calls beginning with "PRG_" are reserved for programs handling. @@ -168,17 +210,17 @@ // ALWAYS INCLUDE PARENT CALL ..() OR DIE IN FIRE. /datum/computer_file/program/ui_act(action,list/params,datum/tgui/ui) if(..()) - return 1 + return TRUE if(computer) switch(action) if("PC_exit") computer.kill_program() ui.close() - return 1 + return TRUE if("PC_shutdown") computer.shutdown_computer() ui.close() - return 1 + return TRUE if("PC_minimize") var/mob/user = usr if(!computer.active_program || !computer.all_components[MC_CPU]) diff --git a/code/modules/modular_computers/file_system/program_events.dm b/code/modules/modular_computers/file_system/program_events.dm index f1d1ad0bd9a5..25e5fa1cccf1 100644 --- a/code/modules/modular_computers/file_system/program_events.dm +++ b/code/modules/modular_computers/file_system/program_events.dm @@ -2,7 +2,7 @@ // Always include a parent call when overriding an event. // Called when the ID card is removed from computer. ID is removed AFTER this proc. -/datum/computer_file/program/proc/event_idremoved(background, slot) +/datum/computer_file/program/proc/event_idremoved(background) return // Called when the computer fails due to power loss. Override when program wants to specifically react to power loss. diff --git a/code/modules/modular_computers/file_system/programs/airestorer.dm b/code/modules/modular_computers/file_system/programs/airestorer.dm index 62d699fab801..8e9572aa8a35 100644 --- a/code/modules/modular_computers/file_system/programs/airestorer.dm +++ b/code/modules/modular_computers/file_system/programs/airestorer.dm @@ -1,6 +1,7 @@ /datum/computer_file/program/aidiag filename = "aidiag" filedesc = "AI Integrity Restorer" + category = PROGRAM_CATEGORY_ROBO program_icon_state = "generic" extended_desc = "This program is capable of reconstructing damaged AI systems. Requires direct AI connection via intellicard slot." size = 12 @@ -9,6 +10,8 @@ transfer_access = ACCESS_HEADS available_on_ntnet = TRUE tgui_id = "NtosAiRestorer" + program_icon = "laptop-code" + /// Variable dictating if we are in the process of restoring the AI in the inserted intellicard var/restoring = FALSE @@ -48,7 +51,7 @@ if(computer.all_components[MC_AI]) var/obj/item/computer_hardware/ai_slot/ai_slot = computer.all_components[MC_AI] if(ai_slot && ai_slot.stored_card) - ai_slot.try_eject(0,usr) + ai_slot.try_eject(usr) return TRUE /datum/computer_file/program/aidiag/process_tick() diff --git a/code/modules/modular_computers/file_system/programs/alarm.dm b/code/modules/modular_computers/file_system/programs/alarm.dm index d5a3ffd4a886..9761f3ccc733 100644 --- a/code/modules/modular_computers/file_system/programs/alarm.dm +++ b/code/modules/modular_computers/file_system/programs/alarm.dm @@ -1,6 +1,7 @@ /datum/computer_file/program/alarm_monitor filename = "alarmmonitor" filedesc = "Alarm Monitor" + category = PROGRAM_CATEGORY_ENGI ui_header = "alarm_green.gif" program_icon_state = "alert-green" extended_desc = "This program provides visual interface for station's alarm system." @@ -8,6 +9,7 @@ network_destination = "alarm monitoring network" size = 5 tgui_id = "NtosStationAlertConsole" + program_icon = "bell" var/has_alert = 0 var/alarms = list("Fire" = list(), "Atmosphere" = list(), "Power" = list()) diff --git a/code/modules/modular_computers/file_system/programs/antagonist/contract_uplink.dm b/code/modules/modular_computers/file_system/programs/antagonist/contract_uplink.dm index 935c79b195e3..a98ae04d5036 100644 --- a/code/modules/modular_computers/file_system/programs/antagonist/contract_uplink.dm +++ b/code/modules/modular_computers/file_system/programs/antagonist/contract_uplink.dm @@ -1,6 +1,7 @@ /datum/computer_file/program/contract_uplink filename = "contractor uplink" filedesc = "Syndicate Contractor Uplink" + category = PROGRAM_CATEGORY_MISC program_icon_state = "assign" extended_desc = "A standard, Syndicate issued system for handling important contracts while on the field." size = 10 @@ -9,6 +10,8 @@ unsendable = 1 undeletable = 1 tgui_id = "SyndContractor" + program_icon = "tasks" + var/error = "" var/info_screen = TRUE var/assigned = FALSE diff --git a/code/modules/modular_computers/file_system/programs/antagonist/dos.dm b/code/modules/modular_computers/file_system/programs/antagonist/dos.dm index 9ad6926f2c77..aa77ed2c50b2 100644 --- a/code/modules/modular_computers/file_system/programs/antagonist/dos.dm +++ b/code/modules/modular_computers/file_system/programs/antagonist/dos.dm @@ -1,6 +1,7 @@ /datum/computer_file/program/ntnet_dos filename = "ntn_dos" filedesc = "DoS Traffic Generator" + category = PROGRAM_CATEGORY_MISC program_icon_state = "hostile" extended_desc = "This advanced script can perform denial of service attacks against NTNet quantum relays. The system administrator will probably notice this. Multiple devices can run this program together against same relay for increased effect" size = 20 @@ -8,6 +9,7 @@ available_on_ntnet = FALSE available_on_syndinet = TRUE tgui_id = "NtosNetDos" + program_icon = "satellite-dish" var/obj/machinery/ntnet_relay/target = null var/dos_speed = 0 @@ -85,4 +87,4 @@ data["relays"] += list(list("id" = R.uid)) data["focus"] = target ? target.uid : null - return data \ No newline at end of file + return data diff --git a/code/modules/modular_computers/file_system/programs/antagonist/revelation.dm b/code/modules/modular_computers/file_system/programs/antagonist/revelation.dm index b9af7c594a87..d53b6b27f158 100644 --- a/code/modules/modular_computers/file_system/programs/antagonist/revelation.dm +++ b/code/modules/modular_computers/file_system/programs/antagonist/revelation.dm @@ -1,6 +1,7 @@ /datum/computer_file/program/revelation filename = "revelation" filedesc = "Revelation" + category = PROGRAM_CATEGORY_MISC program_icon_state = "hostile" extended_desc = "This virus can destroy hard drive of system it is executed on. It may be obfuscated to look like another non-malicious program. Once armed, it will destroy the system upon next execution." size = 13 @@ -8,6 +9,7 @@ available_on_ntnet = FALSE available_on_syndinet = TRUE tgui_id = "NtosRevelation" + program_icon = "magnet" var/armed = 0 @@ -67,4 +69,4 @@ data["armed"] = armed - return data \ No newline at end of file + return data diff --git a/code/modules/modular_computers/file_system/programs/arcade.dm b/code/modules/modular_computers/file_system/programs/arcade.dm index b72ce2f99b9e..6c68d629fb8a 100644 --- a/code/modules/modular_computers/file_system/programs/arcade.dm +++ b/code/modules/modular_computers/file_system/programs/arcade.dm @@ -7,6 +7,7 @@ network_destination = "arcade network" size = 6 tgui_id = "NtosArcade" + program_icon = "gamepad" var/game_active = TRUE //Checks to see if a game is in progress. var/pause_state = FALSE //This disables buttons in order to prevent multiple actions before the opponent's actions. diff --git a/code/modules/modular_computers/file_system/programs/atmosscan.dm b/code/modules/modular_computers/file_system/programs/atmosscan.dm index f8c9424f6586..d3a1e5eb04aa 100644 --- a/code/modules/modular_computers/file_system/programs/atmosscan.dm +++ b/code/modules/modular_computers/file_system/programs/atmosscan.dm @@ -1,17 +1,28 @@ /datum/computer_file/program/atmosscan filename = "atmosscan" filedesc = "Atmospheric Scanner" + category = PROGRAM_CATEGORY_ENGI program_icon_state = "air" extended_desc = "A small built-in sensor reads out the atmospheric conditions around the device." network_destination = "atmos scan" size = 4 tgui_id = "NtosAtmos" + program_icon = "thermometer-half" + +/datum/computer_file/program/atmosscan/run_program(mob/living/user) + . = ..() + if (!.) + return + if(!computer?.get_modular_computer_part(MC_SENSORS)) //Giving a clue to users why the program is spitting out zeros. + to_chat(user, "\The [computer] flashes an error: \"hardware\\sensorpackage\\startup.bin -- file not found\".") + /datum/computer_file/program/atmosscan/ui_data(mob/user) var/list/data = get_header_data() var/list/airlist = list() var/turf/T = get_turf(ui_host()) - if(T) + var/obj/item/computer_hardware/sensorpackage/sensors = computer?.get_modular_computer_part(MC_SENSORS) + if(T && sensors?.check_functionality()) var/datum/gas_mixture/environment = T.return_air() var/list/env_gases = environment.get_gases() var/pressure = environment.return_pressure() @@ -24,6 +35,10 @@ if(gas_level > 0) airlist += list(list("name" = "[env_gases[id][GAS_META][META_GAS_NAME]]", "percentage" = round(gas_level*100, 0.01))) data["AirData"] = airlist + else + data["AirPressure"] = 0 + data["AirTemp"] = 0 + data["AirData"] = list(list()) return data /datum/computer_file/program/atmosscan/ui_act(action, list/params) 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 790dd2f5b528..34ff0b8f1bd7 100644 --- a/code/modules/modular_computers/file_system/programs/borg_monitor.dm +++ b/code/modules/modular_computers/file_system/programs/borg_monitor.dm @@ -1,6 +1,7 @@ /datum/computer_file/program/borg_monitor filename = "cyborgmonitor" filedesc = "Cyborg Remote Monitoring" + category = PROGRAM_CATEGORY_ROBO ui_header = "borg_mon.gif" program_icon_state = "generic" extended_desc = "This program allows for remote monitoring of station cyborgs." @@ -9,6 +10,7 @@ network_destination = "cyborg remote monitoring" size = 5 tgui_id = "NtosCyborgRemoteMonitor" + program_icon = "project-diagram" /datum/computer_file/program/borg_monitor/ui_data(mob/user) var/list/data = get_header_data() @@ -83,6 +85,7 @@ /datum/computer_file/program/borg_monitor/syndicate filename = "scyborgmonitor" filedesc = "Mission-Specific Cyborg Remote Monitoring" + category = PROGRAM_CATEGORY_ROBO ui_header = "borg_mon.gif" program_icon_state = "generic" extended_desc = "This program allows for remote monitoring of mission-assigned cyborgs." diff --git a/code/modules/modular_computers/file_system/programs/bounty_board.dm b/code/modules/modular_computers/file_system/programs/bounty_board.dm index 8ff073a44f2d..2cdc79174bdb 100644 --- a/code/modules/modular_computers/file_system/programs/bounty_board.dm +++ b/code/modules/modular_computers/file_system/programs/bounty_board.dm @@ -1,6 +1,7 @@ /datum/computer_file/program/bounty_board filename = "bountyboard" filedesc = "Bounty Board Request Network" + category = PROGRAM_CATEGORY_SUPL program_icon_state = "bountyboard" extended_desc = "A multi-platform network for placing requests across the station, with payment across the network being possible.." requires_ntnet = TRUE diff --git a/code/modules/modular_computers/file_system/programs/card.dm b/code/modules/modular_computers/file_system/programs/card.dm index e729b503057c..2e81906c3cba 100644 --- a/code/modules/modular_computers/file_system/programs/card.dm +++ b/code/modules/modular_computers/file_system/programs/card.dm @@ -9,6 +9,7 @@ /datum/computer_file/program/card_mod filename = "cardmod" filedesc = "ID Card Modification" + category = PROGRAM_CATEGORY_CREW program_icon_state = "id" extended_desc = "Program for programming employee ID cards to access parts of the station." transfer_access = ACCESS_HEADS @@ -16,6 +17,7 @@ size = 8 usage_flags = PROGRAM_CONSOLE + PROGRAM_LAPTOP + PROGRAM_TABLET //Probably a more efficent way to do this tgui_id = "NtosCard" + program_icon = "id-card" var/is_centcom = FALSE var/minor = FALSE @@ -99,17 +101,19 @@ return TRUE var/obj/item/computer_hardware/card_slot/card_slot + var/obj/item/computer_hardware/card_slot/card_slot2 var/obj/item/computer_hardware/printer/printer if(computer) card_slot = computer.all_components[MC_CARD] + card_slot2 = computer.all_components[MC_CARD2] printer = computer.all_components[MC_PRINT] - if(!card_slot) + if(!card_slot || !card_slot2) return var/mob/user = usr - var/obj/item/card/id/user_id_card = user.get_idcard(FALSE) + var/obj/item/card/id/user_id_card = card_slot.stored_card - var/obj/item/card/id/id_card = card_slot.stored_card + var/obj/item/card/id/target_id_card = card_slot2.stored_card switch(action) if("PRG_authenticate") @@ -130,14 +134,14 @@ return var/contents = {"

Access Report

Prepared By: [user_id_card && user_id_card.registered_name ? user_id_card.registered_name : "Unknown"]
- For: [id_card.registered_name ? id_card.registered_name : "Unregistered"]
+ For: [target_id_card.registered_name ? target_id_card.registered_name : "Unregistered"]

- Assignment: [id_card.assignment]
+ Assignment: [target_id_card.assignment]
Access:
"} var/known_access_rights = get_all_accesses() - for(var/A in id_card.access) + for(var/A in target_id_card.access) if(A in known_access_rights) contents += " [get_access_desc(A)]" @@ -149,43 +153,40 @@ computer.visible_message(span_notice("\The [computer] prints out a paper.")) return TRUE if("PRG_eject") - if(!computer || !card_slot) + if(!computer || !card_slot2) return - if(id_card) - GLOB.data_core.manifest_modify(id_card.registered_name, id_card.assignment) - card_slot.try_eject(TRUE, user) + if(target_id_card) + GLOB.data_core.manifest_modify(target_id_card.registered_name, target_id_card.assignment) + return card_slot2.try_eject(user) else var/obj/item/I = user.get_active_held_item() if(istype(I, /obj/item/card/id)) - if(!user.transferItemToLoc(I, computer)) - return - card_slot.stored_card = I - playsound(computer, 'sound/machines/terminal_insert_disc.ogg', 50, FALSE) - return TRUE + return card_slot2.try_insert(I) + return FALSE if("PRG_terminate") if(!computer || !authenticated) return if(minor) - if(!(id_card.assignment in head_subordinates) && id_card.assignment != "Assistant") + if(!(target_id_card.assignment in head_subordinates) && target_id_card.assignment != "Assistant") return - id_card.access -= get_all_centcom_access() + get_all_accesses() - id_card.assignment = "Unassigned" - id_card.update_label() + target_id_card.access -= get_all_centcom_access() + get_all_accesses() + target_id_card.assignment = "Unassigned" + target_id_card.update_label() playsound(computer, 'sound/machines/terminal_prompt_deny.ogg', 50, FALSE) return TRUE if("PRG_edit") - if(!computer || !authenticated || !id_card) + if(!computer || !authenticated || !target_id_card) return var/new_name = params["name"] if(!new_name) return - id_card.registered_name = new_name - id_card.update_label() + target_id_card.registered_name = new_name + target_id_card.update_label() playsound(computer, "terminal_type", 50, FALSE) return TRUE if("PRG_assign") - if(!computer || !authenticated || !id_card) + if(!computer || !authenticated || !target_id_card) return var/target = params["assign_target"] if(!target) @@ -194,8 +195,8 @@ if(target == "Custom") var/custom_name = params["custom_name"] if(custom_name) - id_card.assignment = custom_name - id_card.update_label() + target_id_card.assignment = custom_name + target_id_card.update_label() else if(minor && !(target in head_subordinates)) return @@ -213,10 +214,10 @@ to_chat(user, span_warning("No class exists for this job: [target]")) return new_access = job.get_access() - id_card.access -= get_all_centcom_access() + get_all_accesses() - id_card.access |= new_access - id_card.assignment = target - id_card.update_label() + target_id_card.access -= get_all_centcom_access() + get_all_accesses() + target_id_card.access |= new_access + target_id_card.assignment = target + target_id_card.update_label() playsound(computer, 'sound/machines/terminal_prompt_confirm.ogg', 50, FALSE) return TRUE if("PRG_access") @@ -224,22 +225,22 @@ return var/access_type = text2num(params["access_target"]) if(access_type in (is_centcom ? get_all_centcom_access() : get_all_accesses())) - if(access_type in id_card.access) - id_card.access -= access_type + if(access_type in target_id_card.access) + target_id_card.access -= access_type else - id_card.access |= access_type + target_id_card.access |= access_type playsound(computer, "terminal_type", 50, FALSE) return TRUE if("PRG_grantall") if(!computer || !authenticated || minor) return - id_card.access |= (is_centcom ? get_all_centcom_access() : get_all_accesses()) + target_id_card.access |= (is_centcom ? get_all_centcom_access() : get_all_accesses()) playsound(computer, 'sound/machines/terminal_prompt_confirm.ogg', 50, FALSE) return TRUE if("PRG_denyall") if(!computer || !authenticated || minor) return - id_card.access.Cut() + target_id_card.access.Cut() playsound(computer, 'sound/machines/terminal_prompt_deny.ogg', 50, FALSE) return TRUE if("PRG_grantregion") @@ -248,7 +249,7 @@ var/region = text2num(params["region"]) if(isnull(region) || !(region in region_access)) return - id_card.access |= get_region_accesses(region) + target_id_card.access |= get_region_accesses(region) playsound(computer, 'sound/machines/terminal_prompt_confirm.ogg', 50, FALSE) return TRUE if("PRG_denyregion") @@ -257,7 +258,7 @@ var/region = text2num(params["region"]) if(isnull(region) || !(region in region_access)) return - id_card.access -= get_region_accesses(region) + target_id_card.access -= get_region_accesses(region) playsound(computer, 'sound/machines/terminal_prompt_deny.ogg', 50, FALSE) return TRUE @@ -322,17 +323,17 @@ /datum/computer_file/program/card_mod/ui_data(mob/user) var/list/data = get_header_data() - var/obj/item/computer_hardware/card_slot/card_slot + var/obj/item/computer_hardware/card_slot/card_slot2 var/obj/item/computer_hardware/printer/printer if(computer) - card_slot = computer.all_components[MC_CARD] + card_slot2 = computer.all_components[MC_CARD2] printer = computer.all_components[MC_PRINT] data["station_name"] = station_name() if(computer) - data["have_id_slot"] = !!card_slot + data["have_id_slot"] = !!(card_slot2) data["have_printer"] = !!printer else data["have_id_slot"] = FALSE @@ -341,7 +342,7 @@ data["authenticated"] = authenticated if(computer) - var/obj/item/card/id/id_card = card_slot.stored_card + var/obj/item/card/id/id_card = card_slot2.stored_card data["has_id"] = !!id_card data["id_name"] = id_card ? id_card.name : "-----" if(id_card) diff --git a/code/modules/modular_computers/file_system/programs/configurator.dm b/code/modules/modular_computers/file_system/programs/configurator.dm index fae06544d5f9..f4a264797193 100644 --- a/code/modules/modular_computers/file_system/programs/configurator.dm +++ b/code/modules/modular_computers/file_system/programs/configurator.dm @@ -13,6 +13,7 @@ available_on_ntnet = 0 requires_ntnet = 0 tgui_id = "NtosConfiguration" + program_icon = "cog" var/obj/item/modular_computer/movable = null diff --git a/code/modules/modular_computers/file_system/programs/crewmanifest.dm b/code/modules/modular_computers/file_system/programs/crewmanifest.dm index 0e9762e69ed2..026bca5d8466 100644 --- a/code/modules/modular_computers/file_system/programs/crewmanifest.dm +++ b/code/modules/modular_computers/file_system/programs/crewmanifest.dm @@ -1,12 +1,14 @@ /datum/computer_file/program/crew_manifest filename = "crewmani" filedesc = "Crew Manifest" + category = PROGRAM_CATEGORY_CREW program_icon_state = "id" extended_desc = "Program for viewing and printing the current crew manifest" transfer_access = ACCESS_HEADS requires_ntnet = FALSE size = 4 tgui_id = "NtosCrewManifest" + program_icon = "clipboard-list" /datum/computer_file/program/crew_manifest/ui_static_data(mob/user) var/list/data = list() diff --git a/code/modules/modular_computers/file_system/programs/file_browser.dm b/code/modules/modular_computers/file_system/programs/file_browser.dm index aba826fce89d..673f18aa33e2 100644 --- a/code/modules/modular_computers/file_system/programs/file_browser.dm +++ b/code/modules/modular_computers/file_system/programs/file_browser.dm @@ -8,6 +8,7 @@ available_on_ntnet = FALSE undeletable = TRUE tgui_id = "NtosFileManager" + program_icon = "folder" var/open_file var/error @@ -65,6 +66,13 @@ var/datum/computer_file/C = F.clone(FALSE) HDD.store_file(C) return TRUE + if("PRG_togglesilence") + if(!HDD) + return + var/datum/computer_file/program/binary = HDD.find_file_by_name(params["name"]) + if(!binary || !istype(binary)) + return + binary.alert_silenced = !binary.alert_silenced /datum/computer_file/program/filemanager/ui_data(mob/user) var/list/data = get_header_data() @@ -78,11 +86,19 @@ else var/list/files = list() for(var/datum/computer_file/F in HDD.stored_files) + var/noisy = FALSE + var/silenced = FALSE + var/datum/computer_file/program/binary = F + if(istype(binary)) + noisy = binary.alert_able + silenced = binary.alert_silenced files += list(list( "name" = F.filename, "type" = F.filetype, "size" = F.size, - "undeletable" = F.undeletable + "undeletable" = F.undeletable, + "alert_able" = noisy, + "alert_silenced" = silenced )) data["files"] = files if(RHDD) diff --git a/code/modules/modular_computers/file_system/programs/jobmanagement.dm b/code/modules/modular_computers/file_system/programs/jobmanagement.dm index bccc6e4dbe21..d9e3e0667581 100644 --- a/code/modules/modular_computers/file_system/programs/jobmanagement.dm +++ b/code/modules/modular_computers/file_system/programs/jobmanagement.dm @@ -1,12 +1,14 @@ /datum/computer_file/program/job_management filename = "job_manage" filedesc = "Job Manager" + category = PROGRAM_CATEGORY_CREW program_icon_state = "id" extended_desc = "Program for viewing and changing job slot avalibility." transfer_access = ACCESS_HEADS requires_ntnet = 0 size = 4 tgui_id = "NtosJobManager" + program_icon = "address-book" var/change_position_cooldown = 30 //Jobs you cannot open new positions for diff --git a/code/modules/modular_computers/file_system/programs/ntdownloader.dm b/code/modules/modular_computers/file_system/programs/ntdownloader.dm index b743714ca179..a209d446e657 100644 --- a/code/modules/modular_computers/file_system/programs/ntdownloader.dm +++ b/code/modules/modular_computers/file_system/programs/ntdownloader.dm @@ -11,43 +11,66 @@ available_on_ntnet = 0 ui_header = "downloader_finished.gif" tgui_id = "NtosNetDownloader" + program_icon = "download" var/datum/computer_file/program/downloaded_file = null - var/hacked_download = 0 + var/hacked_download = FALSE var/download_completion = 0 //GQ of downloaded data. var/download_netspeed = 0 var/downloaderror = "" var/obj/item/modular_computer/my_computer = null - + var/emagged = FALSE + var/list/main_repo + var/list/antag_repo + + var/list/show_categories = list( + PROGRAM_CATEGORY_CREW, + PROGRAM_CATEGORY_ENGI, + PROGRAM_CATEGORY_ROBO, + PROGRAM_CATEGORY_SUPL, + PROGRAM_CATEGORY_MISC, + ) + + +/datum/computer_file/program/ntnetdownload/run_program() + . = ..() + main_repo = SSnetworks.station_network.available_station_software + antag_repo = SSnetworks.station_network.available_antag_software + +/datum/computer_file/program/ntnetdownload/run_emag() + if(emagged) + return FALSE + emagged = TRUE + return TRUE /datum/computer_file/program/ntnetdownload/proc/begin_file_download(filename) if(downloaded_file) - return 0 + return FALSE var/datum/computer_file/program/PRG = SSnetworks.station_network.find_ntnet_file_by_name(filename) if(!PRG || !istype(PRG)) - return 0 + return FALSE - // Attempting to download antag only program, but without having emagged computer. No. - if(PRG.available_on_syndinet && !(computer.obj_flags & EMAGGED)) - return 0 + // Attempting to download antag only program, but without having emagged/syndicate computer. No. + if(PRG.available_on_syndinet && !emagged) + return FALSE var/obj/item/computer_hardware/hard_drive/hard_drive = computer.all_components[MC_HDD] if(!computer || !hard_drive || !hard_drive.can_store_file(PRG)) - return 0 + return FALSE ui_header = "downloader_running.gif" - if(PRG in SSnetworks.station_network.available_station_software) + if(PRG in main_repo) generate_network_log("Began downloading file [PRG.filename].[PRG.filetype] from NTNet Software Repository.") - hacked_download = 0 - else if(PRG in SSnetworks.station_network.available_antag_software) + hacked_download = FALSE + else if(PRG in antag_repo) generate_network_log("Began downloading file **ENCRYPTED**.[PRG.filetype] from unspecified server.") - hacked_download = 1 + hacked_download = TRUE else generate_network_log("Began downloading file [PRG.filename].[PRG.filetype] from unspecified server.") - hacked_download = 0 + hacked_download = FALSE downloaded_file = PRG.clone() @@ -90,26 +113,28 @@ /datum/computer_file/program/ntnetdownload/ui_act(action, params) if(..()) - return 1 + return TRUE switch(action) if("PRG_downloadfile") if(!downloaded_file) begin_file_download(params["filename"]) - return 1 + return TRUE if("PRG_reseterror") if(downloaderror) download_completion = 0 download_netspeed = 0 downloaded_file = null downloaderror = "" - return 1 - return 0 + return TRUE + return FALSE /datum/computer_file/program/ntnetdownload/ui_data(mob/user) my_computer = computer if(!istype(my_computer)) return + var/obj/item/computer_hardware/card_slot/card_slot = computer.all_components[MC_CARD] + var/list/access = card_slot?.GetAccess() var/list/data = get_header_data() @@ -127,36 +152,29 @@ var/obj/item/computer_hardware/hard_drive/hard_drive = my_computer.all_components[MC_HDD] data["disk_size"] = hard_drive.max_capacity data["disk_used"] = hard_drive.used_capacity - var/list/all_entries[0] - for(var/A in SSnetworks.station_network.available_station_software) - var/datum/computer_file/program/P = A - // Only those programs our user can run will show in the list - if(!P.can_run(user,transfer = 1) || hard_drive.find_file_by_name(P.filename)) - continue - all_entries.Add(list(list( + data["emagged"] = emagged + + var/list/repo = antag_repo | main_repo + var/list/program_categories = list() + + for(var/I in repo) + var/datum/computer_file/program/P = I + if(!(P.category in program_categories)) + program_categories.Add(P.category) + data["programs"] += list(list( + "icon" = P.program_icon, "filename" = P.filename, "filedesc" = P.filedesc, "fileinfo" = P.extended_desc, - "compatibility" = check_compatibility(P), + "category" = P.category, + "installed" = !!hard_drive.find_file_by_name(P.filename), + "compatible" = check_compatibility(P), "size" = P.size, - ))) - data["hackedavailable"] = 0 - if(computer.obj_flags & EMAGGED) // If we are running on emagged computer we have access to some "bonus" software - var/list/hacked_programs[0] - for(var/S in SSnetworks.station_network.available_antag_software) - var/datum/computer_file/program/P = S - if(hard_drive.find_file_by_name(P.filename)) - continue - data["hackedavailable"] = 1 - hacked_programs.Add(list(list( - "filename" = P.filename, - "filedesc" = P.filedesc, - "fileinfo" = P.extended_desc, - "size" = P.size, - ))) - data["hacked_programs"] = hacked_programs - - data["downloadable_programs"] = all_entries + "access" = emagged && P.available_on_syndinet ? TRUE : P.can_run(user,transfer = 1, access = access), + "verifiedsource" = P.available_on_ntnet, + )) + + data["categories"] = show_categories & program_categories return data @@ -164,9 +182,30 @@ var/hardflag = computer.hardware_flag if(P && P.is_supported_by_hardware(hardflag,0)) - return "Compatible" - return "Incompatible!" + return TRUE + return FALSE /datum/computer_file/program/ntnetdownload/kill_program(forced) abort_file_download() return ..(forced) + +//////////////////////// +//Syndicate Downloader// +//////////////////////// + +/// This app only lists programs normally found in the emagged section of the normal downloader app + +/datum/computer_file/program/ntnetdownload/syndicate + filename = "syndownloader" + filedesc = "Software Download Tool" + program_icon_state = "generic" + extended_desc = "This program allows downloads of software from shared Syndicate repositories" + requires_ntnet = 0 + ui_header = "downloader_finished.gif" + tgui_id = "NtosNetDownloader" + emagged = TRUE + +/datum/computer_file/program/ntnetdownload/syndicate/run_program() + . = ..() + main_repo = SSnetworks.station_network.available_antag_software + antag_repo = null diff --git a/code/modules/modular_computers/file_system/programs/ntmonitor.dm b/code/modules/modular_computers/file_system/programs/ntmonitor.dm index 9b5cf2e6c451..c0b2f5a46039 100644 --- a/code/modules/modular_computers/file_system/programs/ntmonitor.dm +++ b/code/modules/modular_computers/file_system/programs/ntmonitor.dm @@ -1,6 +1,7 @@ /datum/computer_file/program/ntnetmonitor filename = "ntmonitor" filedesc = "NTNet Diagnostics and Monitoring" + category = PROGRAM_CATEGORY_MISC program_icon_state = "comm_monitor" extended_desc = "This program monitors stationwide NTNet network, provides access to logging systems, and allows for configuration changes" size = 12 @@ -8,6 +9,7 @@ required_access = ACCESS_NETWORK //NETWORK CONTROL IS A MORE SECURE PROGRAM. available_on_ntnet = TRUE tgui_id = "NtosNetMonitor" + program_icon = "network-wired" /datum/computer_file/program/ntnetmonitor/ui_act(action, params) if(..()) @@ -70,4 +72,4 @@ data["ntnetlogs"] += list(list("entry" = i)) data["ntnetmaxlogs"] = SSnetworks.station_network.setting_maxlogcount - return data \ No newline at end of file + return data diff --git a/code/modules/modular_computers/file_system/programs/ntnrc_client.dm b/code/modules/modular_computers/file_system/programs/ntnrc_client.dm index 649982540ec8..5f045f7a2466 100644 --- a/code/modules/modular_computers/file_system/programs/ntnrc_client.dm +++ b/code/modules/modular_computers/file_system/programs/ntnrc_client.dm @@ -1,6 +1,7 @@ /datum/computer_file/program/chatclient filename = "ntnrc_client" filedesc = "Chat Client" + category = PROGRAM_CATEGORY_MISC program_icon_state = "command" extended_desc = "This program allows communication over NTNRC network" size = 8 @@ -10,6 +11,7 @@ ui_header = "ntnrc_idle.gif" available_on_ntnet = 1 tgui_id = "NtosNetChat" + program_icon = "comment-alt" var/last_message // Used to generate the toolbar icon var/username @@ -259,4 +261,4 @@ data["authed"] = FALSE data["messages"] = list() - return data \ No newline at end of file + return data diff --git a/code/modules/modular_computers/file_system/programs/powermonitor.dm b/code/modules/modular_computers/file_system/programs/powermonitor.dm index bd11474858b6..361d0f724d7f 100644 --- a/code/modules/modular_computers/file_system/programs/powermonitor.dm +++ b/code/modules/modular_computers/file_system/programs/powermonitor.dm @@ -3,6 +3,7 @@ /datum/computer_file/program/power_monitor filename = "powermonitor" filedesc = "Power Monitor" + category = PROGRAM_CATEGORY_ENGI program_icon_state = "power_monitor" extended_desc = "This program connects to sensors around the station to provide information about electrical systems" ui_header = "power_norm.gif" @@ -12,6 +13,7 @@ network_destination = "power monitoring system" size = 9 tgui_id = "NtosPowerMonitor" + program_icon = "plug" var/has_alert = 0 var/obj/structure/cable/attached_wire diff --git a/code/modules/modular_computers/file_system/programs/radar.dm b/code/modules/modular_computers/file_system/programs/radar.dm index afafa9f90706..2404f821de40 100644 --- a/code/modules/modular_computers/file_system/programs/radar.dm +++ b/code/modules/modular_computers/file_system/programs/radar.dm @@ -1,12 +1,13 @@ /datum/computer_file/program/radar //generic parent that handles most of the process filename = "genericfinder" filedesc = "debug_finder" + category = PROGRAM_CATEGORY_CREW ui_header = "borg_mon.gif" //DEBUG -- new icon before PR - program_icon_state = "generic" - extended_desc = "generic" + program_icon_state = "radarntos" requires_ntnet = TRUE transfer_access = null available_on_ntnet = FALSE + usage_flags = PROGRAM_LAPTOP | PROGRAM_TABLET | PROGRAM_PHONE network_destination = "tracking program" size = 5 tgui_id = "NtosRadar" @@ -17,10 +18,28 @@ var/atom/selected ///Used to store when the next scan is available. Updated by the scan() proc. var/next_scan = 0 + ///Used to keep track of the last value program_icon_state was set to, to prevent constant unnecessary update_icon() calls + var/last_icon_state = "" + ///Used by the tgui interface, themed NT or Syndicate. + var/arrowstyle = "ntosradarpointer.png" + ///Used by the tgui interface, themed for NT or Syndicate colors. + var/pointercolor = "green" + +/datum/computer_file/program/radar/run_program(mob/living/user) + . = ..() + if(.) + START_PROCESSING(SSfastprocess, src) + return + return FALSE /datum/computer_file/program/radar/kill_program(forced = FALSE) objects = list() selected = null + STOP_PROCESSING(SSfastprocess, src) + return ..() + +/datum/computer_file/program/radar/Destroy() + STOP_PROCESSING(SSfastprocess, src) return ..() /datum/computer_file/program/radar/Destroy() @@ -70,7 +89,37 @@ * */ /datum/computer_file/program/radar/proc/track() - return + var/atom/movable/signal = find_atom() + if(!trackable(signal)) + return + + var/turf/here_turf = (get_turf(computer)) + var/turf/target_turf = (get_turf(signal)) + var/userot = FALSE + var/rot = 0 + var/pointer="crosshairs" + var/locx = (target_turf.x - here_turf.x) + 24 + var/locy = (here_turf.y - target_turf.y) + 24 + + if(get_dist_euclidian(here_turf, target_turf) > 24) + userot = TRUE + rot = round(Get_Angle(here_turf, target_turf)) + else + if(target_turf.z > here_turf.z) + pointer="caret-up" + else if(target_turf.z < here_turf.z) + pointer="caret-down" + + var/list/trackinfo = list( + "locx" = locx, + "locy" = locy, + "userot" = userot, + "rot" = rot, + "arrowstyle" = arrowstyle, + "color" = pointercolor, + "pointer" = pointer, + ) + return trackinfo /** * @@ -82,10 +131,12 @@ **arg1 is the atom being evaluated. */ /datum/computer_file/program/radar/proc/trackable(atom/movable/signal) - if(!signal) + if(!signal || !computer) return FALSE var/turf/here = get_turf(computer) var/turf/there = get_turf(signal) + if(!here || !there) + return FALSE //I was still getting a runtime even after the above check while scanning, so fuck it return (there.z == here.z) || (is_station_level(here.z) && is_station_level(there.z)) /** @@ -103,6 +154,59 @@ /datum/computer_file/program/radar/proc/scan() return +/** + * + *Finds the atom in the appropriate list that the `selected` var indicates + * + *The `selected` var holds a REF, which is a string. A mob REF may be + *something like "mob_209". In order to find the actual atom, we need + *to search the appropriate list for the REF string. This is dependant + *on the program (Lifeline uses GLOB.human_list, while Fission360 uses + *GLOB.poi_list), but the result will be the same; evaluate the string and + *return an atom reference. +*/ +/datum/computer_file/program/radar/proc/find_atom() + return + +//We use SSfastprocess for the program icon state because it runs faster than process_tick() does. +/datum/computer_file/program/radar/process() + if(computer.active_program != src) + STOP_PROCESSING(SSfastprocess, src) //We're not the active program, it's time to stop. + return + if(!selected) + return + + var/atom/movable/signal = find_atom() + if(!trackable(signal)) + program_icon_state = "[initial(program_icon_state)]lost" + if(last_icon_state != program_icon_state) + computer.update_icon() + last_icon_state = program_icon_state + return + + var/here_turf = get_turf(computer) + var/target_turf = get_turf(signal) + var/trackdistance = get_dist_euclidian(here_turf, target_turf) + switch(trackdistance) + if(0) + program_icon_state = "[initial(program_icon_state)]direct" + if(1 to 12) + program_icon_state = "[initial(program_icon_state)]close" + if(13 to 24) + program_icon_state = "[initial(program_icon_state)]medium" + if(25 to INFINITY) + program_icon_state = "[initial(program_icon_state)]far" + + if(last_icon_state != program_icon_state) + computer.update_icon() + last_icon_state = program_icon_state + computer.setDir(get_dir(here_turf, target_turf)) + +//We can use process_tick to restart fast processing, since the computer will be running this constantly either way. +/datum/computer_file/program/radar/process_tick() + if(computer.active_program == src) + START_PROCESSING(SSfastprocess, src) + /////////////////// //Suit Sensor App// /////////////////// @@ -111,44 +215,14 @@ /datum/computer_file/program/radar/lifeline filename = "Lifeline" filedesc = "Lifeline" - program_icon_state = "generic" extended_desc = "This program allows for tracking of crew members via their suit sensors." requires_ntnet = TRUE transfer_access = ACCESS_MEDICAL available_on_ntnet = TRUE + program_icon = "heartbeat" -/datum/computer_file/program/radar/lifeline/track() - var/mob/living/carbon/human/humanoid = locate(selected) in GLOB.mob_living_list - if(!istype(humanoid) || !trackable(humanoid)) - return - - var/turf/here_turf = (get_turf(computer)) - var/turf/target_turf = (get_turf(humanoid)) - var/userot = FALSE - var/rot = 0 - var/pointer="crosshairs" - var/locx = (target_turf.x - here_turf.x) - var/locy = (here_turf.y - target_turf.y) - if(get_dist_euclidian(here_turf, target_turf) > 24) //If they're too far away, we need the angle for the arrow along the edge of the radar display - userot = TRUE - rot = round(Get_Angle(here_turf, target_turf)) - else - locx = locx + 24 - locy = locy + 24 - if(target_turf.z > here_turf.z) - pointer="caret-up" - else if(target_turf.z < here_turf.z) - pointer="caret-down" - var/list/trackinfo = list( - locx = locx, - locy = locy, - userot = userot, - rot = rot, - arrowstyle = "ntosradarpointer.png", //For the rotation arrow, it's stupid I know - color = "green", - pointer = pointer, - ) - return trackinfo +/datum/computer_file/program/radar/lifeline/find_atom() + return locate(selected) in GLOB.carbon_list /datum/computer_file/program/radar/lifeline/scan() if(world.time < next_scan) @@ -191,46 +265,19 @@ /datum/computer_file/program/radar/fission360 filename = "Fission360" filedesc = "Fission360" - program_icon_state = "generic" + category = PROGRAM_CATEGORY_MISC + program_icon_state = "radarsyndicate" extended_desc = "This program allows for tracking of nuclear authorization disks and warheads." requires_ntnet = FALSE transfer_access = null available_on_ntnet = FALSE available_on_syndinet = TRUE tgui_id = "NtosRadarSyndicate" + arrowstyle = "ntosradarpointerS.png" + pointercolor = "red" -/datum/computer_file/program/radar/fission360/track() - var/obj/nuke = locate(selected) in GLOB.poi_list - if(!trackable(nuke)) - return - - var/turf/here_turf = (get_turf(computer)) - var/turf/target_turf = (get_turf(nuke)) - var/userot = FALSE - var/rot = 0 - var/pointer="crosshairs" - var/locx = (target_turf.x - here_turf.x) - var/locy = (here_turf.y - target_turf.y) - if(get_dist_euclidian(here_turf, target_turf) > 24) //If they're too far away, we need the angle for the arrow along the edge of the radar display - userot = TRUE - rot = round(Get_Angle(here_turf, target_turf)) - else - locx = locx + 24 - locy = locy + 24 - if(target_turf.z > here_turf.z) - pointer="caret-up" - else if(target_turf.z < here_turf.z) - pointer="caret-down" - var/list/trackinfo = list( - locx = locx, - locy = locy, - userot = userot, - rot = rot, - arrowstyle = "ntosradarpointerS.png", - color = "red", - pointer = pointer, - ) - return trackinfo +/datum/computer_file/program/radar/fission360/find_atom() + return locate(selected) in GLOB.poi_list /datum/computer_file/program/radar/fission360/scan() if(world.time < next_scan) diff --git a/code/modules/modular_computers/file_system/programs/robocontrol.dm b/code/modules/modular_computers/file_system/programs/robocontrol.dm index 8644ce09b433..41fe1b574109 100644 --- a/code/modules/modular_computers/file_system/programs/robocontrol.dm +++ b/code/modules/modular_computers/file_system/programs/robocontrol.dm @@ -2,6 +2,7 @@ /datum/computer_file/program/robocontrol filename = "robocontrol" filedesc = "Bot Remote Controller" + category = PROGRAM_CATEGORY_ROBO program_icon_state = "robot" extended_desc = "A remote controller used for giving basic commands to non-sentient robots." transfer_access = ACCESS_ROBOTICS @@ -9,6 +10,8 @@ network_destination = "robotics control network" size = 12 tgui_id = "NtosRoboControl" + program_icon = "robot" + ///Number of simple robots on-station. var/botcount = 0 ///Used to find the location of the user for the purposes of summoning robots. @@ -78,7 +81,7 @@ return if(id_card) GLOB.data_core.manifest_modify(id_card.registered_name, id_card.assignment) - card_slot.try_eject(TRUE, current_user) + card_slot.try_eject(current_user) else playsound(get_turf(ui_host()) , 'sound/machines/buzz-sigh.ogg', 25, FALSE) return diff --git a/code/modules/modular_computers/file_system/programs/sm_monitor.dm b/code/modules/modular_computers/file_system/programs/sm_monitor.dm index 1acfb54c7b2f..c49a0cd002f2 100644 --- a/code/modules/modular_computers/file_system/programs/sm_monitor.dm +++ b/code/modules/modular_computers/file_system/programs/sm_monitor.dm @@ -1,6 +1,7 @@ /datum/computer_file/program/supermatter_monitor filename = "smmonitor" filedesc = "Supermatter Monitoring" + category = PROGRAM_CATEGORY_ENGI ui_header = "smmon_0.gif" program_icon_state = "smmon_0" extended_desc = "This program connects to specially calibrated supermatter sensors to provide information on the status of supermatter-based engines." @@ -9,11 +10,18 @@ network_destination = "supermatter monitoring system" size = 5 tgui_id = "NtosSupermatterMonitor" + program_icon = "radiation" + alert_able = TRUE + var/last_status = SUPERMATTER_INACTIVE var/list/supermatters var/obj/machinery/power/supermatter_crystal/active // Currently selected supermatter crystal. var/data_corrupted = FALSE //used for when supermatter corruptor is attached +/datum/computer_file/program/supermatter_monitor/Destroy() + clear_signals() + active = null + return ..() /datum/computer_file/program/supermatter_monitor/process_tick() ..() @@ -27,10 +35,11 @@ /datum/computer_file/program/supermatter_monitor/run_program(mob/living/user) . = ..(user) + if(!(active in GLOB.machines)) + active = null refresh() /datum/computer_file/program/supermatter_monitor/kill_program(forced = FALSE) - active = null supermatters = null ..() @@ -54,6 +63,58 @@ for(var/obj/machinery/power/supermatter_crystal/S in supermatters) . = max(., S.get_status()) +/** + * Sets up the signal listener for Supermatter delaminations. + * + * Unregisters any old listners for SM delams, and then registers one for the SM refered + * to in the `active` variable. This proc is also used with no active SM to simply clear + * the signal and exit. + */ +/datum/computer_file/program/supermatter_monitor/proc/set_signals() + if(active) + RegisterSignal(active, COMSIG_SUPERMATTER_DELAM_ALARM, .proc/send_alert, override = TRUE) + RegisterSignal(active, COMSIG_SUPERMATTER_DELAM_START_ALARM, .proc/send_start_alert, override = TRUE) + +/** + * Removes the signal listener for Supermatter delaminations from the selected supermatter. + * + * Pretty much does what it says. + */ +/datum/computer_file/program/supermatter_monitor/proc/clear_signals() + if(active) + UnregisterSignal(active, COMSIG_SUPERMATTER_DELAM_ALARM) + UnregisterSignal(active, COMSIG_SUPERMATTER_DELAM_START_ALARM) + +/** + * Sends an SM delam alert to the computer. + * + * Triggered by a signal from the selected supermatter, this proc sends a notification + * to the computer if the program is either closed or minimized. We do not send these + * notifications to the comptuer if we're the active program, because engineers fixing + * the supermatter probably don't need constant beeping to distract them. + */ +/datum/computer_file/program/supermatter_monitor/proc/send_alert() + if(!computer.get_ntnet_status()) + return + if(computer.active_program != src) + computer.alert_call(src, "Crystal delamination in progress!") + alert_pending = TRUE + +/** + * Sends an SM delam start alert to the computer. + * + * Triggered by a signal from the selected supermatter at the start of a delamination, + * this proc sends a notification to the computer if this program is the active one. + * We do this so that people carrying a tablet with NT CIMS open but with the NTOS window + * closed will still get one audio alert. This is not sent to computers with the program + * minimized or closed to avoid double-notifications. + */ +/datum/computer_file/program/supermatter_monitor/proc/send_start_alert() + if(!computer.get_ntnet_status()) + return + if(computer.active_program == src) + computer.alert_call(src, "Crystal delamination in progress!") + /datum/computer_file/program/supermatter_monitor/ui_data() var/list/data = get_header_data() @@ -132,6 +193,7 @@ switch(action) if("PRG_clear") + clear_signals() active = null return TRUE if("PRG_refresh") @@ -142,4 +204,5 @@ for(var/obj/machinery/power/supermatter_crystal/S in supermatters) if(S.uid == newuid) active = S + set_signals() return TRUE diff --git a/code/modules/modular_computers/hardware/_hardware.dm b/code/modules/modular_computers/hardware/_hardware.dm index bc40f6e80c76..29da37e09948 100644 --- a/code/modules/modular_computers/hardware/_hardware.dm +++ b/code/modules/modular_computers/hardware/_hardware.dm @@ -10,9 +10,11 @@ // Computer that holds this hardware, if any. var/power_usage = 0 // If the hardware uses extra power, change this. - var/enabled = 1 // If the hardware is turned off set this to 0. - var/critical = 0 // Prevent disabling for important component, like the CPU. - var/can_install = 1 // Prevents direct installation of removable media. + var/enabled = TRUE // If the hardware is turned off set this to 0. + var/critical = FALSE // Prevent disabling for important component, like the CPU. + var/can_install = TRUE // Prevents direct installation of removable media. + var/expansion_hw = FALSE // Hardware that fits into expansion bays. + var/removable = TRUE // Whether the hardware is removable or not. var/damage = 0 // Current damage level var/max_damage = 100 // Maximal damage level. var/damage_malfunction = 20 // "Malfunction" threshold. When damage exceeds this value the hardware piece will semi-randomly fail and do !!FUN!! things diff --git a/code/modules/modular_computers/hardware/ai_slot.dm b/code/modules/modular_computers/hardware/ai_slot.dm index 6ff87c7890ca..280bb7a69b70 100644 --- a/code/modules/modular_computers/hardware/ai_slot.dm +++ b/code/modules/modular_computers/hardware/ai_slot.dm @@ -5,6 +5,7 @@ icon_state = "card_mini" w_class = WEIGHT_CLASS_NORMAL device_type = MC_AI + expansion_hw = TRUE var/obj/item/aicard/stored_card = null var/locked = FALSE @@ -19,12 +20,6 @@ if(stored_card) . += "There appears to be an intelliCard loaded. There appears to be a pinhole protecting a manual eject button. A screwdriver could probably press it." -/obj/item/computer_hardware/ai_slot/on_install(obj/item/modular_computer/M, mob/living/user = null) - M.add_verb(device_type) - -/obj/item/computer_hardware/ai_slot/on_remove(obj/item/modular_computer/M, mob/living/user = null) - M.remove_verb(device_type) - /obj/item/computer_hardware/ai_slot/try_insert(obj/item/I, mob/living/user = null) if(!holder) return FALSE @@ -44,7 +39,7 @@ return TRUE -/obj/item/computer_hardware/ai_slot/try_eject(slot=0,mob/living/user = null,forced = 0) +/obj/item/computer_hardware/ai_slot/try_eject(mob/living/user = null,forced = FALSE) if(!stored_card) to_chat(user, span_warning("There is no card in \the [src].")) return FALSE diff --git a/code/modules/modular_computers/hardware/battery_module.dm b/code/modules/modular_computers/hardware/battery_module.dm index 4a431391680d..f30e598a134b 100644 --- a/code/modules/modular_computers/hardware/battery_module.dm +++ b/code/modules/modular_computers/hardware/battery_module.dm @@ -12,9 +12,13 @@ battery = new battery_type(src) ..() +/obj/item/computer_hardware/battery/Destroy() + . = ..() + QDEL_NULL(battery) + /obj/item/computer_hardware/battery/handle_atom_del(atom/A) if(A == battery) - try_eject(0, null, TRUE) + try_eject(forced = TRUE) . = ..() /obj/item/computer_hardware/battery/try_insert(obj/item/I, mob/living/user = null) @@ -41,7 +45,7 @@ return TRUE -/obj/item/computer_hardware/battery/try_eject(slot=0, mob/living/user = null, forced = 0) +/obj/item/computer_hardware/battery/try_eject(mob/living/user = null, forced = FALSE) if(!battery) to_chat(user, span_warning("There is no power cell connected to \the [src].")) return FALSE diff --git a/code/modules/modular_computers/hardware/card_slot.dm b/code/modules/modular_computers/hardware/card_slot.dm index 94c6960801d1..79bee51b2444 100644 --- a/code/modules/modular_computers/hardware/card_slot.dm +++ b/code/modules/modular_computers/hardware/card_slot.dm @@ -7,13 +7,10 @@ device_type = MC_CARD var/obj/item/card/id/stored_card = null - var/obj/item/card/id/stored_card2 = null /obj/item/computer_hardware/card_slot/handle_atom_del(atom/A) if(A == stored_card) - try_eject(1, null, TRUE) - if(A == stored_card2) - try_eject(2, null, TRUE) + try_eject(null, TRUE) . = ..() /obj/item/computer_hardware/card_slot/Destroy() @@ -21,26 +18,25 @@ return ..() /obj/item/computer_hardware/card_slot/GetAccess() - if(stored_card && stored_card2) // Best of both worlds - return (stored_card.GetAccess() | stored_card2.GetAccess()) - else if(stored_card) - return stored_card.GetAccess() - else if(stored_card2) - return stored_card2.GetAccess() - return ..() + var/list/total_access + if(stored_card) + total_access = stored_card.GetAccess() + var/obj/item/computer_hardware/card_slot/card_slot2 = holder?.all_components[MC_CARD2] //Best of both worlds + if(card_slot2?.stored_card) + total_access |= card_slot2.stored_card.GetAccess() + return total_access /obj/item/computer_hardware/card_slot/GetID() if(stored_card) return stored_card - else if(stored_card2) - return stored_card2 return ..() -/obj/item/computer_hardware/card_slot/on_install(obj/item/modular_computer/M, mob/living/user = null) - M.add_verb(device_type) - -/obj/item/computer_hardware/card_slot/on_remove(obj/item/modular_computer/M, mob/living/user = null) - M.remove_verb(device_type) +/obj/item/computer_hardware/card_slot/RemoveID() + if(stored_card) + . = stored_card + if(!try_eject()) + return null + return /obj/item/computer_hardware/card_slot/try_insert(obj/item/I, mob/living/user = null) if(!holder) @@ -49,8 +45,7 @@ if(!istype(I, /obj/item/card/id)) return FALSE - if(stored_card && stored_card2) - to_chat(user, span_warning("You try to insert \the [I] into \the [src], but its slots are occupied.")) + if(stored_card) return FALSE if(user) if(!user.transferItemToLoc(I, src)) @@ -58,11 +53,8 @@ else I.forceMove(src) - if(!stored_card) - stored_card = I - else - stored_card2 = I - to_chat(user, span_notice("You insert \the [I] into \the [src].")) + stored_card = I + to_chat(user, "You insert \the [I] into \the [expansion_hw ? "secondary":"primary"] [src].") playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, 0) if(ishuman(user)) var/mob/living/carbon/human/H = user @@ -71,53 +63,61 @@ return TRUE -/obj/item/computer_hardware/card_slot/try_eject(slot=0, mob/living/user = null, forced = 0) - if(!stored_card && !stored_card2) +/obj/item/computer_hardware/card_slot/try_eject(mob/living/user = null, forced = FALSE) + if(!stored_card) to_chat(user, span_warning("There are no cards in \the [src].")) return FALSE - var/ejected = 0 - if(stored_card && (!slot || slot == 1)) - if(user && Adjacent(user)) - user.put_in_hands(stored_card) - else - stored_card.forceMove(drop_location()) - stored_card = null - ejected++ - - if(stored_card2 && (!slot || slot == 2)) - if(user && Adjacent(user)) - user.put_in_hands(stored_card2) - else - stored_card2.forceMove(drop_location()) - stored_card2 = null - ejected++ - - if(ejected) - if(holder) - if(holder.active_program) - holder.active_program.event_idremoved(0, slot) - - for(var/I in holder.idle_threads) - var/datum/computer_file/program/P = I - P.event_idremoved(1, slot) - if(ishuman(user)) - var/mob/living/carbon/human/H = user - H.sec_hud_set_ID() - to_chat(user, span_notice("You remove the card[ejected>1 ? "s" : ""] from \the [src].")) - playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, 0) - return TRUE - return FALSE + if(user) + user.put_in_hands(stored_card) + else + stored_card.forceMove(drop_location()) + stored_card = null + + if(holder) + if(holder.active_program) + holder.active_program.event_idremoved(0) + + for(var/p in holder.idle_threads) + var/datum/computer_file/program/computer_program = p + computer_program.event_idremoved(1) + if(ishuman(user)) + var/mob/living/carbon/human/human_user = user + human_user.sec_hud_set_ID() + to_chat(user, "You remove the card from \the [src].") + playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, FALSE) + return TRUE /obj/item/computer_hardware/card_slot/attackby(obj/item/I, mob/living/user) if(..()) return if(I.tool_behaviour == TOOL_SCREWDRIVER) - to_chat(user, span_notice("You press down on the manual eject button with \the [I].")) - try_eject(0,user) - return + if(stored_card) + to_chat(user, "You press down on the manual eject button with \the [I].") + try_eject(user) + return + swap_slot() + to_chat(user, "You adjust the connecter to fit into [expansion_hw ? "an expansion bay" : "the primary ID bay"].") + +/** + *Swaps the card_slot hardware between using the dedicated card slot bay on a computer, and using an expansion bay. +*/ +/obj/item/computer_hardware/card_slot/proc/swap_slot() + expansion_hw = !expansion_hw + if(expansion_hw) + device_type = MC_CARD2 + else + device_type = MC_CARD /obj/item/computer_hardware/card_slot/examine(mob/user) . = ..() - if(stored_card || stored_card2) + . += "The connector is set to fit into [expansion_hw ? "an expansion bay" : "a computer's primary ID bay"], but can be adjusted with a screwdriver." + if(stored_card) . += "There appears to be something loaded in the card slots." + +/obj/item/computer_hardware/card_slot/secondary + name = "auxillary identification card authentication module" // \improper breaks the find_hardware_by_name proc + desc = "A secondary identification card authentication module, allowing this computer to write data on ID cards. Necessary for some programs to run properly." + w_class = WEIGHT_CLASS_SMALL + device_type = MC_CARD2 + expansion_hw = TRUE diff --git a/code/modules/modular_computers/hardware/hard_drive.dm b/code/modules/modular_computers/hardware/hard_drive.dm index 3fc7ae625489..e08d7108493f 100644 --- a/code/modules/modular_computers/hardware/hard_drive.dm +++ b/code/modules/modular_computers/hardware/hard_drive.dm @@ -166,6 +166,17 @@ max_capacity = 70 var/datum/antagonist/traitor/traitor_data // Syndicate hard drive has the user's data baked directly into it on creation +/// For tablets given to nuke ops +/obj/item/computer_hardware/hard_drive/small/nukeops + power_usage = 8 + max_capacity = 70 + +/obj/item/computer_hardware/hard_drive/small/nukeops/install_default_programs() + store_file(new/datum/computer_file/program/computerconfig(src)) + store_file(new/datum/computer_file/program/ntnetdownload/syndicate(src)) // Syndicate version; automatic access to syndicate apps and no NT apps + store_file(new/datum/computer_file/program/filemanager(src)) + store_file(new/datum/computer_file/program/radar/fission360(src)) //I am legitimately afraid if I don't do this, Ops players will think they just don't get a pinpointer anymore. + /obj/item/computer_hardware/hard_drive/micro name = "micro solid state drive" desc = "A highly efficient SSD chip for portable devices." diff --git a/code/modules/modular_computers/hardware/portable_disk.dm b/code/modules/modular_computers/hardware/portable_disk.dm index b5a957be04a8..89b0382e8662 100644 --- a/code/modules/modular_computers/hardware/portable_disk.dm +++ b/code/modules/modular_computers/hardware/portable_disk.dm @@ -8,12 +8,8 @@ max_capacity = 16 device_type = MC_SDD -/obj/item/computer_hardware/hard_drive/portable/on_install(obj/item/modular_computer/M, mob/living/user = null) - M.add_verb(device_type) - -/obj/item/computer_hardware/hard_drive/portable/on_remove(obj/item/modular_computer/M, mob/living/user = null) - ..() - M.remove_verb(device_type) +/obj/item/computer_hardware/hard_drive/portable/on_remove(obj/item/modular_computer/MC, mob/user) + return //this is a floppy disk, let's not shut the computer down when it gets pulled out. /obj/item/computer_hardware/hard_drive/portable/install_default_programs() return // Empty by default diff --git a/code/modules/modular_computers/hardware/printer.dm b/code/modules/modular_computers/hardware/printer.dm index 3ca2c1ad6246..c974a29be025 100644 --- a/code/modules/modular_computers/hardware/printer.dm +++ b/code/modules/modular_computers/hardware/printer.dm @@ -5,6 +5,7 @@ icon_state = "printer" w_class = WEIGHT_CLASS_BULKY device_type = MC_PRINT + expansion_hw = TRUE var/stored_paper = 20 var/max_paper = 30 diff --git a/code/modules/modular_computers/hardware/sensor_package.dm b/code/modules/modular_computers/hardware/sensor_package.dm new file mode 100644 index 000000000000..3c123ff18f56 --- /dev/null +++ b/code/modules/modular_computers/hardware/sensor_package.dm @@ -0,0 +1,8 @@ +//This item doesn't do much on its own, but is required by apps such as Atmospheric Scanner. +/obj/item/computer_hardware/sensorpackage + name = "sensor package" + desc = "An integrated sensor package allowing a computer to take readings from the environment. Required by certain programs." + icon_state = "servo" + w_class = WEIGHT_CLASS_TINY + device_type = MC_SENSORS + expansion_hw = TRUE diff --git a/code/modules/modular_computers/laptop_vendor.dm b/code/modules/modular_computers/laptop_vendor.dm index 9134579a26d4..89c517f2d370 100644 --- a/code/modules/modular_computers/laptop_vendor.dm +++ b/code/modules/modular_computers/laptop_vendor.dm @@ -53,6 +53,7 @@ var/obj/item/computer_hardware/battery/battery_module = null if(fabricate) fabricated_laptop = new /obj/item/modular_computer/laptop/buildable(src) + fabricated_laptop.install_component(new /obj/item/computer_hardware/card_slot) fabricated_laptop.install_component(new /obj/item/computer_hardware/battery) battery_module = fabricated_laptop.all_components[MC_CELL] total_price = 49 @@ -108,7 +109,7 @@ if(dev_card) total_price += 99 if(fabricate) - fabricated_laptop.install_component(new /obj/item/computer_hardware/card_slot) + fabricated_laptop.install_component(new /obj/item/computer_hardware/card_slot/secondary) return total_price @@ -118,6 +119,7 @@ fabricated_tablet = new(src) fabricated_tablet.install_component(new /obj/item/computer_hardware/battery) fabricated_tablet.install_component(new /obj/item/computer_hardware/processor_unit/small) + fabricated_tablet.install_component(new/obj/item/computer_hardware/card_slot) battery_module = fabricated_tablet.all_components[MC_CELL] total_price = 99 switch(dev_battery) @@ -156,11 +158,11 @@ if(dev_printer) total_price += 49 if(fabricate) - fabricated_tablet.install_component(new/obj/item/computer_hardware/printer) + fabricated_tablet.install_component(new/obj/item/computer_hardware/printer/mini) if(dev_card) - total_price += 99 + total_price += 199 if(fabricate) - fabricated_tablet.install_component(new/obj/item/computer_hardware/card_slot) + fabricated_tablet.install_component(new/obj/item/computer_hardware/card_slot/secondary) return total_price else if(devtype == 3) // Phone, very portable and can function similar to the PDA, though it is even more expensive than a tablet. @@ -205,13 +207,11 @@ fabricated_phone.install_component(new/obj/item/computer_hardware/network_card/advanced) total_price += 149 if(dev_printer) - total_price += 49 - if(fabricate) - fabricated_phone.install_component(new/obj/item/computer_hardware/printer) + fabricated_tablet.install_component(new/obj/item/computer_hardware/printer/mini) if(dev_card) - total_price += 99 + total_price += 199 if(fabricate) - fabricated_phone.install_component(new/obj/item/computer_hardware/card_slot) + fabricated_tablet.install_component(new/obj/item/computer_hardware/card_slot/secondary) return total_price return 0 @@ -311,7 +311,7 @@ say("Insufficient money on card to purchase!") return credits += target_credits - say("$[target_credits] has been desposited from your account.") + say("[target_credits] cr have been withdrawn from your account.") return return ..() diff --git a/code/modules/power/supermatter/supermatter.dm b/code/modules/power/supermatter/supermatter.dm index 7f5f85424758..50211f228c99 100644 --- a/code/modules/power/supermatter/supermatter.dm +++ b/code/modules/power/supermatter/supermatter.dm @@ -567,6 +567,8 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal) supermatter_anomaly_gen(src, PYRO_ANOMALY, rand(5, 10)) if(damage > warning_point) // while the core is still damaged and it's still worth noting its status + if(damage_archived < warning_point) //If damage_archive is under the warning point, this is the very first cycle that we've reached said point. + SEND_SIGNAL(src, COMSIG_SUPERMATTER_DELAM_START_ALARM) if((REALTIMEOFDAY - lastwarning) / 10 >= WARNING_DELAY) alarm() @@ -575,6 +577,7 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal) radio.talk_into(src, "[warning_alert] Integrity: [get_fake_integrity()]%!", common_channel) else radio.talk_into(src, "[emergency_alert] Integrity: [get_integrity()]%", common_channel) + SEND_SIGNAL(src, COMSIG_SUPERMATTER_DELAM_ALARM) log_game("The supermatter crystal: [emergency_alert] Integrity: [get_integrity()]%") // yogs start - Logs SM chatter investigate_log("The supermatter crystal: [emergency_alert] Integrity: [get_integrity()]%", INVESTIGATE_SUPERMATTER) // yogs end lastwarning = REALTIMEOFDAY @@ -587,6 +590,7 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal) radio.talk_into(src, "[warning_alert] Integrity: [get_fake_integrity()]%!", engineering_channel) else radio.talk_into(src, "[warning_alert] Integrity: [get_integrity()]%", engineering_channel) + SEND_SIGNAL(src, COMSIG_SUPERMATTER_DELAM_ALARM) log_game("The supermatter crystal: [warning_alert] Integrity: [get_integrity()]%") // yogs start - Logs SM chatter investigate_log("The supermatter crystal: [warning_alert] Integrity: [get_integrity()]%", INVESTIGATE_SUPERMATTER) // yogs end lastwarning = REALTIMEOFDAY - (WARNING_DELAY * 5) diff --git a/code/modules/research/designs/computer_part_designs.dm b/code/modules/research/designs/computer_part_designs.dm index ab487b0aaa49..86121f56892f 100644 --- a/code/modules/research/designs/computer_part_designs.dm +++ b/code/modules/research/designs/computer_part_designs.dm @@ -122,6 +122,15 @@ category = list("Computer Parts") departmental_flags = DEPARTMENTAL_FLAG_SCIENCE | DEPARTMENTAL_FLAG_ENGINEERING +/datum/design/cardslot/secondary + name = "Auxilary ID Card Slot" + id = "secondcardslot" + build_type = PROTOLATHE + materials = list(/datum/material/iron = 600) + build_path = /obj/item/computer_hardware/card_slot/secondary + category = list("Computer Parts") + departmental_flags = DEPARTMENTAL_FLAG_SCIENCE | DEPARTMENTAL_FLAG_ENGINEERING + // Intellicard slot /datum/design/aislot name = "Intellicard Slot" @@ -243,3 +252,12 @@ build_path = /obj/item/computer_hardware/processor_unit/photonic/small category = list("Computer Parts") departmental_flags = DEPARTMENTAL_FLAG_SCIENCE | DEPARTMENTAL_FLAG_ENGINEERING + +/datum/design/sensorpackage + name = "Sensor Package" + id = "sensorpackage" + build_type = PROTOLATHE + materials = list(/datum/material/iron = 200, /datum/material/glass = 100, /datum/material/gold = 50, /datum/material/silver = 50) + build_path = /obj/item/computer_hardware/sensorpackage + category = list("Computer Parts") + departmental_flags = DEPARTMENTAL_FLAG_SCIENCE | DEPARTMENTAL_FLAG_ENGINEERING diff --git a/code/modules/research/techweb/all_nodes.dm b/code/modules/research/techweb/all_nodes.dm index 5a2caf65a5b5..41f1a1d2b40e 100644 --- a/code/modules/research/techweb/all_nodes.dm +++ b/code/modules/research/techweb/all_nodes.dm @@ -439,8 +439,8 @@ research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 1000) //they are really shitty export_price = 2000 design_ids = list("hdd_basic", "hdd_advanced", "hdd_super", "hdd_cluster", "ssd_small", "ssd_micro", "netcard_basic", "netcard_advanced", "netcard_wired", - "portadrive_basic", "portadrive_advanced", "portadrive_super", "cardslot", "aislot", "miniprinter", "APClink", "bat_control", "bat_normal", "bat_advanced", - "bat_super", "bat_micro", "bat_nano", "cpu_normal", "pcpu_normal", "cpu_small", "pcpu_small") + "portadrive_basic", "portadrive_advanced", "portadrive_super", "cardslot", "secondcardslot", "aislot", "miniprinter", "APClink", "bat_control", "bat_normal", "bat_advanced", + "bat_super", "bat_micro", "bat_nano", "cpu_normal", "pcpu_normal", "cpu_small", "pcpu_small", "sensorpackage") /datum/techweb_node/computer_board_gaming id = "computer_board_gaming" diff --git a/code/modules/tgui/tgui.dm b/code/modules/tgui/tgui.dm index 9393b9641fed..4353a93b3b90 100644 --- a/code/modules/tgui/tgui.dm +++ b/code/modules/tgui/tgui.dm @@ -67,18 +67,20 @@ * public * * Open this UI (and initialize it with data). + * + * return bool - TRUE if a new pooled window is opened, FALSE in all other situations including if a new pooled window didn't open because one already exists. */ /datum/tgui/proc/open() if(!user.client) - return null + return FALSE if(window) - return null + return FALSE process_status() if(status < UI_UPDATE) - return null + return FALSE window = SStgui.request_pooled_window(user) if(!window) - return null + return FALSE opened_at = world.time window.acquire_lock(src) if(!window.is_ready()) @@ -101,6 +103,8 @@ with_static_data = TRUE)) SStgui.on_open(src) + return TRUE + /** * public * @@ -156,7 +160,7 @@ */ /datum/tgui/proc/send_asset(datum/asset/asset) if(!window) - CRASH("send_asset() can only be called after open().") + CRASH("send_asset() was called either without calling open() first or when open() did not return TRUE.") return window.send_asset(asset) /** diff --git a/code/modules/vending/modularpc.dm b/code/modules/vending/modularpc.dm index ee8bf6a24ca6..2dbdb6fcf9c2 100644 --- a/code/modules/vending/modularpc.dm +++ b/code/modules/vending/modularpc.dm @@ -15,8 +15,10 @@ /obj/item/computer_hardware/battery = 8, /obj/item/stock_parts/cell/computer = 8, /obj/item/computer_hardware/processor_unit = 4, - /obj/item/computer_hardware/processor_unit/small = 4) - premium = list(/obj/item/computer_hardware/card_slot = 2, + /obj/item/computer_hardware/processor_unit/small = 4, + /obj/item/computer_hardware/card_slot = 4, + /obj/item/computer_hardware/sensorpackage = 4) + premium = list(/obj/item/computer_hardware/card_slot/secondary = 2, /obj/item/computer_hardware/ai_slot = 2, /obj/item/computer_hardware/printer/mini = 2, /obj/item/computer_hardware/recharger/APC = 2) diff --git a/icons/obj/modular_laptop.dmi b/icons/obj/modular_laptop.dmi index 2f40b4ad6d5b9ebb735baedffe6eeb84a1620fd3..3030b1e5dde598a8fab9f10174a1c291d981e613 100644 GIT binary patch literal 28928 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 EHxnq=XaE2J diff --git a/icons/obj/modular_phone.dmi b/icons/obj/modular_phone.dmi index 2e62b3cff54c8ce538b259d3165cd994927d6f64..8cd68430cf1373b02f8eb111718db2e51a415f9f 100644 GIT binary patch literal 4180 zcmbtWc{tSj{vReIqoK_**6Fla(kS~bDTFZKIh(le$Vs!KF|C6em$S%y}mx*&-XVZ!U_VG0|Nj6 zh>f-71pq*Z@>6yK1tY>+)~5t&0%_-XhC-nf!nC4qz>30DFI-4-a&qd`(yb_hzsWk0 zncR-U;q>;&9MTkX){<6&oH~5?u&eT+ojZ4)b%KUp5Q87LH#0N4di5$F000Yl0qg*K zgp1Wi)Breu`H6mb75si0-<9twaHXN40fWI3_U{AO*&&1gNx}dZ8^FjfFz1mG01h|O zq?Q#6;3E<6s;b%(a7sCxKjU(f&(}&wNC<-Zx{W{+pvJlhXf&EYARIq_yu7?zS6A2E z+}zsQIyE)b58(I8=2dS_FThVrJy~7LPfL(gz5k&ufny=;1;j;w`PT_Q0e&6h=z_KM z4fhEP2*n131Oot3dHD|p5lNc+Q)=E`+)b|bOR^`gSQMY=oKKe=z=rC|1!Nd39g2-* zq}|vT7}O>0&trzZFKmaDEi7?6W^7|Nw;#m;puZWC5fA5Bm&dw>oR<&u-**)WbNjII z(#7i~t)LZE{^>Ts*v7B)(9D>0ci?G@Q?a;`k9%)Oj1a|g(Lv-S^ivgj>!rP~6S_Z+)=9~!GsHye zl-!;*?w3_pG%@z)w zBlR3ZC_MCemo96El?v^OC`r7qoF((#)l^tUNACM`v!HE1Mc|v8MIIC6@eH2Q#J31l zanjzefk!jyNFBu{%WALXXi{m{TPk-4vIAoHVUa%;IXfJy>JxEKp%4MFFWFe0a*WDb z%7>u#*+{3XVDdzyQ2RxPga_~0iCUX0_<H8?#@F2rc- zNAW#%r3`rHh1n%@Fqz{0D=~sf^9=S&1Plsda|+? z5anrsc4$QzL$L^ z*@7)b#zSYyp-o$A&vS=vk*x>q$>N~1qQmo`WoG-NgZzY1+FC1?MoBtnI#0w?soe39 zlg{W6_{P9C2uhU^js0{_7dc{to}S#u}MH zInsi}f6gD6)kMk(T_z{Mwa??nH!b!_MaBOOHh`_LOlAeLdV`d8CVB3zK}D{^3%| zCnalBPofsCng(gAbfAV{_T#J9LQ*@5i$7@w-90}g#&7w~ljsRUJ}AA6R+Wp2i;D{j z%hQF`5j%6=oiE8ljbHawOh0J#0=vY8AP(9j)o(_INgLQKyk-9K_QhHN=WzZVRjU;U z81ya%zTwGXr_Rl=e)l@=-f}}bNju4bp6`?q$�ST$m5t<+vK~3Gxo~j#CdD1|if` z1OYJZKf4A1D`m{~dhlVb?Sm>o1e}P{7e$L~uyR46x|H8=IEpX|V2BXD4%h1u7Osl; z>1u8&xIpluJN5ayT_7(!EC42wDn0%*}Q(rnJR(z+jUJy z{Bt72Yaj5ZHAd`NZlJZeXA`Q@ze>i)>)`9M0@^ zYkw(*&;RtrBZA{G|Al)P!sF_%wmg*hcW?g$uHWC2Tv^eVcn(HpwT$N2wk6F#eP|lH z_0*5wvf75#!K%0)J#UuJtYXfbxFHz~YS5z=fmej}!?kI=I@B>!GK>v{PZnb^y z;ZFs@HxA1DUhs_ojaXvWEiB7ltd=S-Ej)e%722Gnc8#BnzWaoks$4638PlG2U}S&I z^Wdr_`}b3}!FF_ayJRsCGj*ontKPr_J@AH;vfcHBGKg`$s?agnALoq8y~eMThsDxC zmnK^zi`eR#yYLHVC}-BjwRcRa33e2$=yn!MYl~h=MP>?h8Vrn>^0Jc`do)Ts5ue>Z zt2w??-pJDOZ#Z*10?KRyH#id-;y0YihlD1@3Lh)M@5Xy&SN7w*(l&E}J|gYm+Q@23 z6T*`YuKzmRDuraxzV=$8~&x8N~dy7)^s71b>gLOw)P z33tBzT*wNXNzCsCcAUIrwfLJY>61I&uAZ8 zTWz34IGtUe8MU47h+iEvr40-prTQYJxO2X zoaS4vw79AvuZFxY>pLlW?uu65$+y@S+@HpGg%i99*Q6>gD}xWW)P8FoGjpcp5eJ8? zPM93BBsAO%8mB6!et$TNt^0vOzs}gx2p;i3hpeVX?NXdr+7M*cnuY@%#)vFGMKx7L zHj|xCX*r@@I-fuU8P{(YKA3X5p3egQ^Pc)og$#e&WaGzJB2*$@=kUVo&7zHV3fmHrynh@}ak5N>E#pv4fSNAh0JuQ3~Ix1E&z=gvhj_9bOYhi zl@g*KPB^LPTeu%70Kk1^G_(q@k_OXD8APSAZl8>Gz5 zhU{0j8`S{{ivMwG6kO|!Tn2B~JH@G)mO?o7>o#Kt8x1tCv;_&?GYXB53^d7UPj{(Z z28urub^>b6jaPBdVT~7Ja+p=hh&8N80-bYAZvT3JqLPjLeEg2e1mpa;WX#*~9BO5_ zzwr+JfKXrL3cJpmP@W6Pnz4UDWq^Fgq<}s5syq|69imDgCl{Bc#*)D!leWs?$9?!X zJNe5aw0_AEEYkpy56osk_|3OVm=SXS!^>AxRS(WXRrk@z-_xku`R0 ziBaur^Y!hI2z~e3qXlOEL*prPbAoR&tB8PlUXEGnkUX>w_dK{D5%_|bufE6O`ksE} z`UQ;DOxL}Kyjcc^)BH3kp1SER#LLtrEON=fmoRsxZ8J{9nY4hI=aky+ZF>;r!-@KPW16AhD0DWCxvvFtCe>&Z^_;X z&i_f10j)V;IFn}8pO@bCr-`+;!~vP}cqj`qpTok_QM;R;35Wl=@V`zKOq9;n#jfJj zt6P{$R-}aw4&%RgMv1e{F$o!QUM-N4#7k8z8K36I3~FcDdV+fo@S^{c=Ib z>7&X`LxPI|k{<{Ads61&EAL4~nUcQ-(BebPn(f=uvFWj#fSlp;B(L4jOExZ^7_jKe{Dk(MXXb#!}!E{+kM1+ zs$>P-Kv7JwoDuI6n@JZ2_O57O+!B#3S&To=%q?h#fEvld?&`yM) z{{@J;6xsh5OH8Ix5T?8T9xx#}YK}UxE#LJo#au#d@{;U$SbI;KqpAz?cb_TY9<-RK zAn}TCWAFG#Xn)QRxyTBE&5%i+v@OY#f&%IAK_}@_XGP=$e0<8*ySqZvR2xW(Y)jvk d|I67i9X{}68R8r7AX5A1kj)u{CD8(%_zxq-+6w>x literal 3773 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 Iei#?`e|7&F1ONa4 diff --git a/icons/obj/modular_tablet.dmi b/icons/obj/modular_tablet.dmi index 489a500030550ce3691af8a7e4206aa71d85697b..88c07473fdc37ad79ac2b1675710acb1ce4f7194 100644 GIT binary patch literal 5665 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 F{|j=!wzB{L literal 9684 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 { - Card Reader: + Secondary Card Reader: diff --git a/tgui/packages/tgui/interfaces/NtosCyborgRemoteMonitor.js b/tgui/packages/tgui/interfaces/NtosCyborgRemoteMonitor.js index ce562f52366a..03285c5c6c44 100644 --- a/tgui/packages/tgui/interfaces/NtosCyborgRemoteMonitor.js +++ b/tgui/packages/tgui/interfaces/NtosCyborgRemoteMonitor.js @@ -6,6 +6,7 @@ import { NtosWindow } from '../layouts'; export const NtosCyborgRemoteMonitor = (props, context) => { return ( diff --git a/tgui/packages/tgui/interfaces/NtosFileManager.js b/tgui/packages/tgui/interfaces/NtosFileManager.js index da762e459f42..f093b3147a8b 100644 --- a/tgui/packages/tgui/interfaces/NtosFileManager.js +++ b/tgui/packages/tgui/interfaces/NtosFileManager.js @@ -24,7 +24,8 @@ export const NtosFileManager = (props, context) => { name: file, new_name: newName, })} - onDuplicate={file => act('PRG_clone', { file: file })} /> + onDuplicate={file => act('PRG_clone', { file: file })} + onToggleSilence={file => act('PRG_togglesilence', { name: file })} /> {usbconnected && (
@@ -54,6 +55,7 @@ const FileTable = props => { onUpload, onDelete, onRename, + onToggleSilence, } = props; return ( @@ -89,6 +91,13 @@ const FileTable = props => { {file.size} + {!!file.alert_able && ( + )} +
act('PC_Eject_Disk', { name: "ID" })} + /> + )}> +
+ + ID Name: {login.IDName} + + + Assignment: {login.IDJob} + +
+
+ {!!removable_media.length && ( +
+ + {removable_media.map(device => ( + + +
+
+ )}
{programs.map(program => ( @@ -63,9 +85,8 @@ export const NtosMain = (props, context) => {