mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2025-12-19 06:03:14 +00:00
* Reworked PDA menu & NtOS themes (#73070) ## About The Pull Request This is a port/rework of https://github.com/yogstation13/Yogstation/pull/15735 - I changed a lot of how it acted (some themes are locked behind maintenance apps). The original author allowed this port to happen, and I really liked how it looked there so I'd like to add it here. ### Applications Removes the hardware configurator application, as all it did was show you your space and battery now that all hardware was removed. These are things your PC does by default, so it was just a waste of space. Adds a Theme manager application instead, which allows you to change your PDA's theme at will. Adds a new Maintenance application that will give a new theme, however it will also increase the size of the theme manager app itself as it's bloatware. ### Menu There's now a bar at the top of the menu showing 'special' tablet apps which, for one reason or another, should stand out from the rest of the apps. Currently this is PDA messenger and the Theme manager Flashlight and Flashlight color is now only an icon, and is shown on the same line as Updating you ID https://cdn.discordapp.com/attachments/961874788706574386/1069621173693972551/2023-01-30_09-10-52.mov  ### Themes Adds a lot of themes to choose from, although SOME are hidden behind Maintenance applications, which will give you a random theme. These are bloatware however, so they come with some extra cost to the app's required space storage. Themes are now supported on ALL APPLICATIONS! If you have a computer theme, you will have that theme in EVERY app you enter, rather than just a select few. ALSO also, emagging the tablet will automatically set & unlock the Syndicate theme, which makes your PDA obvious but you can disguise it if you wish through just re-painting it to something else. https://cdn.discordapp.com/attachments/828923843829432340/1069565383155122266/2023-01-30_05-29-53.mov ### Preferences This also adds a pref for theme, reworking the ringtone code to work with it as well. I also removed 2 entirely unused PDA prefs just 'cause. Screenshot not up-to-date, they now have proper names.  ### Other stuff Made defines for device_themes Added support for special app-side checks to download files Fixed programs downloading themselves TWICE because defines all had the same definition Removes the Chemistry computer disk as it was empty due to chemistry app's removal Removes the 'run_emag' proc, since apps can directly refer to the computer to check for emag status instead. Moved over and added better documentation on data computer files, and moved the ordnance ones to the same file as the others. ## Why It's Good For The Game It makes PDAs a lot more customizable while adding more features to maintenance applications. I think the themes look cool and it fits with PDAs being "personal" anyways. I also explained most of my other arguments in the about section, such as the hardware configuration application. ## Changelog 🆑 Chubbygummibear & JohnFulpWillard add: A ton of new NtOS themes, which are accessible by the new Themify application that comes with all PCs. add: Emagging a PC now defaults it to the Syndicate option (and adds it to go back to it if you wish) add: There's a new maintenance app that gives you rarer themes qol: The NtOS Main menu was moved around, added "header" applications that are shown where the Flashlight is, such as your Theme manager and PDA messenger. code: Made defines for device_themes code: Added support for special app-side checks to download files code: Removes the 'run_emag' proc, since apps can directly refer to the computer to check for emag status instead. fix: Programs no longer download twice. del: Removes the Chemistry computer disk as it was empty due to chemistry app's removal /🆑 --------- Co-authored-by: san7890 <the@ san7890.com> * Reworked PDA menu & NtOS themes --------- Co-authored-by: John Willard <53777086+JohnFulpWillard@users.noreply.github.com> Co-authored-by: san7890 <the@ san7890.com>
847 lines
29 KiB
Plaintext
847 lines
29 KiB
Plaintext
GLOBAL_LIST_EMPTY(TabletMessengers) // a list of all active messengers, similar to GLOB.PDAs (used primarily with ntmessenger.dm)
|
|
|
|
// This is the base type of computer
|
|
// Other types expand it - tablets and laptops are subtypes
|
|
// consoles use "procssor" item that is held inside it.
|
|
/obj/item/modular_computer
|
|
name = "modular microcomputer"
|
|
desc = "A small portable microcomputer."
|
|
icon = 'icons/obj/computer.dmi'
|
|
icon_state = "laptop"
|
|
light_on = FALSE
|
|
integrity_failure = 0.5
|
|
max_integrity = 100
|
|
armor_type = /datum/armor/item_modular_computer
|
|
light_system = MOVABLE_LIGHT_DIRECTIONAL
|
|
|
|
///The ID currently stored in the computer.
|
|
var/obj/item/card/id/computer_id_slot
|
|
///The disk in this PDA. If set, this will be inserted on Initialize.
|
|
var/obj/item/computer_disk/inserted_disk
|
|
///The power cell the computer uses to run on.
|
|
var/obj/item/stock_parts/cell/internal_cell = /obj/item/stock_parts/cell
|
|
///A pAI currently loaded into the modular computer.
|
|
var/obj/item/pai_card/inserted_pai
|
|
|
|
///The amount of storage space the computer starts with.
|
|
var/max_capacity = 128
|
|
///The amount of storage space we've got filled
|
|
var/used_capacity = 0
|
|
///List of stored files on this drive. Use `store_file` and `remove_file` instead of modifying directly!
|
|
var/list/datum/computer_file/stored_files = list()
|
|
|
|
///Non-static list of programs the computer should recieve on Initialize.
|
|
var/list/datum/computer_file/starting_programs = list()
|
|
///Static list of default programs that come with ALL computers, here so computers don't have to repeat this.
|
|
var/static/list/datum/computer_file/default_programs = list(
|
|
/datum/computer_file/program/themeify,
|
|
/datum/computer_file/program/ntnetdownload,
|
|
/datum/computer_file/program/filemanager,
|
|
)
|
|
|
|
///The program currently active on the tablet.
|
|
var/datum/computer_file/program/active_program
|
|
///Idle programs on background. They still receive process calls but can't be interacted with.
|
|
var/list/idle_threads = list()
|
|
/// Amount of programs that can be ran at once
|
|
var/max_idle_programs = 2
|
|
|
|
///Flag of the type of device the modular computer is, deciding what types of apps it can run.
|
|
var/hardware_flag = NONE
|
|
// Options: PROGRAM_ALL | PROGRAM_CONSOLE | PROGRAM_LAPTOP | PROGRAM_TABLET
|
|
|
|
///The theme, used for the main menu and file browser apps.
|
|
var/device_theme = PDA_THEME_NTOS
|
|
|
|
///Bool on whether the computer is currently active or not.
|
|
var/enabled = FALSE
|
|
///If the screen is open, only used by laptops.
|
|
var/screen_on = TRUE
|
|
|
|
///Looping sound for when the computer is on.
|
|
var/datum/looping_sound/computer/soundloop
|
|
///Whether or not this modular computer uses the looping sound
|
|
var/looping_sound = TRUE
|
|
|
|
///If the computer has a flashlight/LED light built-in.
|
|
var/has_light = FALSE
|
|
/// How far the computer's light can reach, is not editable by players.
|
|
var/comp_light_luminosity = 3
|
|
/// The built-in light's color, editable by players.
|
|
var/comp_light_color = "#FFFFFF"
|
|
|
|
///The last recorded amount of power used.
|
|
var/last_power_usage = 0
|
|
///Power usage when the computer is open (screen is active) and can be interacted with.
|
|
var/base_active_power_usage = 75
|
|
///Power usage when the computer is idle and screen is off (currently only applies to laptops)
|
|
var/base_idle_power_usage = 5
|
|
|
|
// Modular computers can run on various devices. Each DEVICE (Laptop, Console & Tablet)
|
|
// must have it's own DMI file. Icon states must be called exactly the same in all files, but may look differently
|
|
// If you create a program which is limited to Laptops and Consoles you don't have to add it's icon_state overlay for Tablets too, for example.
|
|
|
|
///If set, what the icon_state will be if the computer is unpowered.
|
|
var/icon_state_unpowered
|
|
///If set, what the icon_state will be if the computer is powered.
|
|
var/icon_state_powered
|
|
///Icon state overlay when the computer is turned on, but no program is loaded (programs override this).
|
|
var/icon_state_menu = "menu"
|
|
|
|
///The full name of the stored ID card's identity. These vars should probably be on the PDA.
|
|
var/saved_identification
|
|
///The job title of the stored ID card
|
|
var/saved_job
|
|
|
|
///The 'computer' itself, as an obj. Primarily used for Adjacent() and UI visibility checks, especially for computers.
|
|
var/obj/physical
|
|
///Amount of steel sheets refunded when disassembling an empty frame of this computer.
|
|
var/steel_sheet_cost = 5
|
|
|
|
///If hit by a Clown virus, remaining honks left until it stops.
|
|
var/honkvirus_amount = 0
|
|
///Whether the PDA can still use NTNet while out of NTNet's reach.
|
|
var/long_ranged = FALSE
|
|
/// Allow people with chunky fingers to use?
|
|
var/allow_chunky = FALSE
|
|
|
|
///The amount of paper currently stored in the PDA
|
|
var/stored_paper = 10
|
|
///The max amount of paper that can be held at once.
|
|
var/max_paper = 30
|
|
|
|
/datum/armor/item_modular_computer
|
|
bullet = 20
|
|
laser = 20
|
|
energy = 100
|
|
|
|
/obj/item/modular_computer/Initialize(mapload)
|
|
. = ..()
|
|
|
|
START_PROCESSING(SSobj, src)
|
|
if(!physical)
|
|
physical = src
|
|
set_light_color(comp_light_color)
|
|
set_light_range(comp_light_luminosity)
|
|
if(looping_sound)
|
|
soundloop = new(src, enabled)
|
|
UpdateDisplay()
|
|
if(has_light)
|
|
add_item_action(/datum/action/item_action/toggle_computer_light)
|
|
if(inserted_disk)
|
|
inserted_disk = new inserted_disk(src)
|
|
if(internal_cell)
|
|
internal_cell = new internal_cell(src)
|
|
|
|
update_appearance()
|
|
register_context()
|
|
init_network_id(NETWORK_TABLETS)
|
|
Add_Messenger()
|
|
install_default_programs()
|
|
|
|
/obj/item/modular_computer/proc/install_default_programs()
|
|
SHOULD_CALL_PARENT(FALSE)
|
|
for(var/programs in default_programs + starting_programs)
|
|
var/datum/computer_file/program/program_type = new programs
|
|
store_file(program_type)
|
|
|
|
/obj/item/modular_computer/Destroy()
|
|
STOP_PROCESSING(SSobj, src)
|
|
wipe_program(forced = TRUE)
|
|
for(var/datum/computer_file/program/idle as anything in idle_threads)
|
|
idle.kill_program(TRUE)
|
|
//Some components will actually try and interact with this, so let's do it later
|
|
QDEL_NULL(soundloop)
|
|
QDEL_LIST(stored_files)
|
|
Remove_Messenger()
|
|
|
|
if(istype(inserted_disk))
|
|
QDEL_NULL(inserted_disk)
|
|
if(istype(inserted_pai))
|
|
QDEL_NULL(inserted_pai)
|
|
if(computer_id_slot)
|
|
QDEL_NULL(computer_id_slot)
|
|
|
|
physical = null
|
|
return ..()
|
|
|
|
/obj/item/modular_computer/pre_attack_secondary(atom/A, mob/living/user, params)
|
|
if(active_program?.tap(A, user, params))
|
|
user.do_attack_animation(A) //Emulate this animation since we kill the attack in three lines
|
|
playsound(loc, 'sound/weapons/tap.ogg', get_clamped_volume(), TRUE, -1) //Likewise for the tap sound
|
|
addtimer(CALLBACK(src, PROC_REF(play_ping)), 0.5 SECONDS, TIMER_UNIQUE) //Slightly delayed ping to indicate success
|
|
return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
|
|
return ..()
|
|
|
|
// shameless copy of newscaster photo saving
|
|
|
|
/obj/item/modular_computer/proc/save_photo(icon/photo)
|
|
var/photo_file = copytext_char(md5("\icon[photo]"), 1, 6)
|
|
if(!fexists("[GLOB.log_directory]/photos/[photo_file].png"))
|
|
//Clean up repeated frames
|
|
var/icon/clean = new /icon()
|
|
clean.Insert(photo, "", SOUTH, 1, 0)
|
|
fcopy(clean, "[GLOB.log_directory]/photos/[photo_file].png")
|
|
return photo_file
|
|
|
|
/**
|
|
* 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/get_cell()
|
|
return internal_cell
|
|
|
|
/obj/item/modular_computer/AltClick(mob/user)
|
|
. = ..()
|
|
if(issilicon(user))
|
|
return FALSE
|
|
if(!user.canUseTopic(src, be_close = TRUE))
|
|
return FALSE
|
|
|
|
if(RemoveID(user))
|
|
return TRUE
|
|
|
|
if(istype(inserted_pai)) // Remove pAI
|
|
user.put_in_hands(inserted_pai)
|
|
balloon_alert(user, "removed pAI")
|
|
inserted_pai = null
|
|
update_appearance(UPDATE_ICON)
|
|
return TRUE
|
|
|
|
// Gets IDs/access levels from card slot. Would be useful when/if PDAs would become modular PCs. //guess what
|
|
/obj/item/modular_computer/GetAccess()
|
|
if(computer_id_slot)
|
|
return computer_id_slot.GetAccess()
|
|
return ..()
|
|
|
|
/obj/item/modular_computer/GetID()
|
|
if(computer_id_slot)
|
|
return computer_id_slot
|
|
return ..()
|
|
|
|
/obj/item/modular_computer/get_id_examine_strings(mob/user)
|
|
. = ..()
|
|
if(computer_id_slot)
|
|
. += "\The [src] is displaying [computer_id_slot]."
|
|
. += computer_id_slot.get_id_examine_strings(user)
|
|
|
|
/obj/item/modular_computer/proc/print_text(text_to_print, paper_title = "")
|
|
if(!stored_paper)
|
|
return FALSE
|
|
|
|
var/obj/item/paper/printed_paper = new /obj/item/paper(drop_location())
|
|
printed_paper.add_raw_text(text_to_print)
|
|
if(paper_title)
|
|
printed_paper.name = paper_title
|
|
printed_paper.update_appearance()
|
|
stored_paper--
|
|
return TRUE
|
|
|
|
/**
|
|
* InsertID
|
|
* Attempt to insert the ID in either card slot.
|
|
* Args:
|
|
* inserting_id - the ID being inserted
|
|
* user - The person inserting the ID
|
|
*/
|
|
/obj/item/modular_computer/InsertID(obj/item/card/inserting_id, mob/user)
|
|
//all slots taken
|
|
if(computer_id_slot)
|
|
return FALSE
|
|
|
|
computer_id_slot = inserting_id
|
|
if(user)
|
|
if(!user.transferItemToLoc(inserting_id, src))
|
|
return FALSE
|
|
to_chat(user, span_notice("You insert \the [inserting_id] into the card slot."))
|
|
else
|
|
inserting_id.forceMove(src)
|
|
|
|
playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, FALSE)
|
|
if(ishuman(loc))
|
|
var/mob/living/carbon/human/human_wearer = loc
|
|
if(human_wearer.wear_id == src)
|
|
human_wearer.sec_hud_set_ID()
|
|
update_appearance()
|
|
update_slot_icon()
|
|
return TRUE
|
|
|
|
/**
|
|
* Removes the ID card from the computer, and puts it in loc's hand if it's a mob
|
|
* Args:
|
|
* user - The mob trying to remove the ID, if there is one
|
|
*/
|
|
/obj/item/modular_computer/RemoveID(mob/user)
|
|
if(!computer_id_slot)
|
|
return ..()
|
|
|
|
if(user)
|
|
if(!issilicon(user) && in_range(src, user))
|
|
user.put_in_hands(computer_id_slot)
|
|
balloon_alert(user, "removed ID")
|
|
to_chat(user, span_notice("You remove the card from the card slot."))
|
|
else
|
|
computer_id_slot.forceMove(drop_location())
|
|
|
|
computer_id_slot = null
|
|
playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, FALSE)
|
|
|
|
if(ishuman(loc))
|
|
var/mob/living/carbon/human/human_wearer = loc
|
|
if(human_wearer.wear_id == src)
|
|
human_wearer.sec_hud_set_ID()
|
|
update_slot_icon()
|
|
update_appearance()
|
|
return TRUE
|
|
|
|
/obj/item/modular_computer/MouseDrop(obj/over_object, src_location, over_location)
|
|
var/mob/M = usr
|
|
if((!istype(over_object, /atom/movable/screen)) && usr.canUseTopic(src, be_close = TRUE))
|
|
return attack_self(M)
|
|
return ..()
|
|
|
|
/obj/item/modular_computer/attack_ai(mob/user)
|
|
return attack_self(user)
|
|
|
|
/obj/item/modular_computer/attack_ghost(mob/dead/observer/user)
|
|
. = ..()
|
|
if(.)
|
|
return
|
|
if(enabled)
|
|
ui_interact(user)
|
|
else if(isAdminGhostAI(user))
|
|
var/response = tgui_alert(user, "This computer is turned off. Would you like to turn it on?", "Admin Override", list("Yes", "No"))
|
|
if(response == "Yes")
|
|
turn_on(user)
|
|
|
|
/obj/item/modular_computer/emag_act(mob/user, forced)
|
|
if(!enabled && !forced)
|
|
to_chat(user, span_warning("You'd need to turn the [src] on first."))
|
|
return FALSE
|
|
if(obj_flags & EMAGGED)
|
|
to_chat(user, span_notice("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_flags |= EMAGGED
|
|
device_theme = PDA_THEME_SYNDICATE
|
|
to_chat(user, span_notice("You swipe \the [src]. A console window momentarily fills the screen, with white text rapidly scrolling past."))
|
|
return TRUE
|
|
|
|
/obj/item/modular_computer/examine(mob/user)
|
|
. = ..()
|
|
var/healthpercent = round((atom_integrity/max_integrity) * 100, 1)
|
|
switch(healthpercent)
|
|
if(50 to 99)
|
|
. += span_info("It looks slightly damaged.")
|
|
if(25 to 50)
|
|
. += span_info("It appears heavily damaged.")
|
|
if(0 to 25)
|
|
. += span_warning("It's falling apart!")
|
|
|
|
if(long_ranged)
|
|
. += "It is upgraded with an experimental long-ranged network capabilities, picking up NTNet frequencies while further away."
|
|
. += span_notice("It has [max_capacity] GQ of storage capacity.")
|
|
|
|
if(computer_id_slot)
|
|
if(Adjacent(user))
|
|
. += "It has \the [computer_id_slot] card installed in its card slot."
|
|
else
|
|
. += "Its identification card slot is currently occupied."
|
|
. += span_info("Alt-click [src] to eject the identification card.")
|
|
|
|
/obj/item/modular_computer/examine_more(mob/user)
|
|
. = ..()
|
|
. += "Storage capacity: [used_capacity]/[max_capacity]GQ"
|
|
|
|
for(var/datum/computer_file/app_examine as anything in stored_files)
|
|
if(app_examine.on_examine(src, user))
|
|
. += app_examine.on_examine(src, user)
|
|
|
|
if(Adjacent(user))
|
|
. += span_notice("Paper level: [stored_paper] / [max_paper].")
|
|
|
|
/obj/item/modular_computer/add_context(atom/source, list/context, obj/item/held_item, mob/living/user)
|
|
. = ..()
|
|
|
|
if(held_item?.tool_behaviour == TOOL_WRENCH)
|
|
context[SCREENTIP_CONTEXT_RMB] = "Deconstruct"
|
|
. = CONTEXTUAL_SCREENTIP_SET
|
|
|
|
if(computer_id_slot) // ID get removed first before pAIs
|
|
context[SCREENTIP_CONTEXT_ALT_LMB] = "Remove ID"
|
|
. = CONTEXTUAL_SCREENTIP_SET
|
|
else if(inserted_pai)
|
|
context[SCREENTIP_CONTEXT_ALT_LMB] = "Remove pAI"
|
|
. = CONTEXTUAL_SCREENTIP_SET
|
|
|
|
if(inserted_disk)
|
|
context[SCREENTIP_CONTEXT_CTRL_SHIFT_LMB] = "Remove Disk"
|
|
. = CONTEXTUAL_SCREENTIP_SET
|
|
return . || NONE
|
|
|
|
/obj/item/modular_computer/update_icon_state()
|
|
if(!icon_state_powered || !icon_state_unpowered) //no valid icon, don't update.
|
|
return ..()
|
|
icon_state = enabled ? icon_state_powered : icon_state_unpowered
|
|
return ..()
|
|
|
|
/obj/item/modular_computer/update_overlays()
|
|
. = ..()
|
|
var/init_icon = initial(icon)
|
|
if(!init_icon)
|
|
return
|
|
|
|
if(enabled)
|
|
. += active_program ? mutable_appearance(init_icon, active_program.program_icon_state) : mutable_appearance(init_icon, icon_state_menu)
|
|
if(atom_integrity <= integrity_failure * max_integrity)
|
|
. += mutable_appearance(init_icon, "bsod")
|
|
. += mutable_appearance(init_icon, "broken")
|
|
|
|
/obj/item/modular_computer/Exited(atom/movable/gone, direction)
|
|
if(internal_cell == gone)
|
|
internal_cell = null
|
|
if(enabled && !use_power())
|
|
shutdown_computer()
|
|
if(computer_id_slot == gone)
|
|
computer_id_slot = null
|
|
update_slot_icon()
|
|
if(ishuman(loc))
|
|
var/mob/living/carbon/human/human_wearer = loc
|
|
human_wearer.sec_hud_set_ID()
|
|
if(inserted_pai == gone)
|
|
inserted_pai = null
|
|
if(inserted_disk == gone)
|
|
inserted_disk = null
|
|
update_appearance(UPDATE_ICON)
|
|
return ..()
|
|
|
|
// On-click handling. Turns on the computer if it's off and opens the GUI.
|
|
/obj/item/modular_computer/interact(mob/user)
|
|
if(enabled)
|
|
ui_interact(user)
|
|
else
|
|
turn_on(user)
|
|
|
|
/obj/item/modular_computer/CtrlShiftClick(mob/user)
|
|
. = ..()
|
|
if(.)
|
|
return
|
|
if(!inserted_disk)
|
|
return
|
|
user.put_in_hands(inserted_disk)
|
|
inserted_disk = null
|
|
playsound(src, 'sound/machines/card_slide.ogg', 50)
|
|
|
|
/obj/item/modular_computer/proc/turn_on(mob/user, open_ui = TRUE)
|
|
var/issynth = issilicon(user) // Robots and AIs get different activation messages.
|
|
if(atom_integrity <= integrity_failure * max_integrity)
|
|
if(issynth)
|
|
to_chat(user, span_warning("You send an activation signal to \the [src], but it responds with an error code. It must be damaged."))
|
|
else
|
|
to_chat(user, span_warning("You press the power button, but the computer fails to boot up, displaying variety of errors before shutting down again."))
|
|
return FALSE
|
|
|
|
if(use_power()) // use_power() checks if the PC is powered
|
|
if(issynth)
|
|
to_chat(user, span_notice("You send an activation signal to \the [src], turning it on."))
|
|
else
|
|
to_chat(user, span_notice("You press the power button and start up \the [src]."))
|
|
if(looping_sound)
|
|
soundloop.start()
|
|
enabled = TRUE
|
|
update_appearance()
|
|
if(open_ui)
|
|
ui_interact(user)
|
|
return TRUE
|
|
else // Unpowered
|
|
if(issynth)
|
|
to_chat(user, span_warning("You send an activation signal to \the [src] but it does not respond."))
|
|
else
|
|
to_chat(user, span_warning("You press the power button but \the [src] does not respond."))
|
|
return FALSE
|
|
|
|
// Process currently calls handle_power(), may be expanded in future if more things are added.
|
|
/obj/item/modular_computer/process(delta_time)
|
|
if(!enabled) // The computer is turned off
|
|
last_power_usage = 0
|
|
return
|
|
|
|
if(atom_integrity <= integrity_failure * max_integrity)
|
|
shutdown_computer()
|
|
return
|
|
|
|
if(active_program && active_program.requires_ntnet && !get_ntnet_status(active_program.requires_ntnet_feature))
|
|
active_program.event_networkfailure(FALSE) // Active program requires NTNet to run but we've just lost connection. Crash.
|
|
|
|
for(var/datum/computer_file/program/idle_programs as anything in idle_threads)
|
|
if(idle_programs.program_state == PROGRAM_STATE_KILLED)
|
|
idle_threads.Remove(idle_programs)
|
|
continue
|
|
idle_programs.process_tick(delta_time)
|
|
idle_programs.ntnet_status = get_ntnet_status(idle_programs.requires_ntnet_feature)
|
|
if(idle_programs.requires_ntnet && !idle_programs.ntnet_status)
|
|
idle_programs.event_networkfailure(TRUE)
|
|
|
|
if(active_program)
|
|
if(active_program.program_state == PROGRAM_STATE_KILLED)
|
|
active_program = null
|
|
else
|
|
active_program.process_tick(delta_time)
|
|
active_program.ntnet_status = get_ntnet_status()
|
|
|
|
handle_power(delta_time) // Handles all computer power interaction
|
|
|
|
/**
|
|
* Displays notification text alongside a soundbeep when requested to by a program.
|
|
*
|
|
* After checking that 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 FALSE
|
|
playsound(src, sound, 50, TRUE)
|
|
loc.visible_message(span_notice("[icon2html(src)] [span_notice("The [src] displays a [caller.filedesc] notification: [alerttext]")]"))
|
|
|
|
/obj/item/modular_computer/proc/ring(ringtone) // bring bring
|
|
if(HAS_TRAIT(SSstation, STATION_TRAIT_PDA_GLITCHED))
|
|
playsound(src, pick('sound/machines/twobeep_voice1.ogg', 'sound/machines/twobeep_voice2.ogg'), 50, TRUE)
|
|
else
|
|
playsound(src, 'sound/machines/twobeep_high.ogg', 50, TRUE)
|
|
audible_message("*[ringtone]*")
|
|
|
|
/obj/item/modular_computer/proc/send_sound()
|
|
playsound(src, 'sound/machines/terminal_success.ogg', 15, TRUE)
|
|
|
|
// 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
|
|
data["PC_showbatteryicon"] = !!internal_cell
|
|
|
|
if(internal_cell)
|
|
switch(internal_cell.percent())
|
|
if(80 to 200) // 100 should be maximal but just in case..
|
|
data["PC_batteryicon"] = "batt_100.gif"
|
|
if(60 to 80)
|
|
data["PC_batteryicon"] = "batt_80.gif"
|
|
if(40 to 60)
|
|
data["PC_batteryicon"] = "batt_60.gif"
|
|
if(20 to 40)
|
|
data["PC_batteryicon"] = "batt_40.gif"
|
|
if(5 to 20)
|
|
data["PC_batteryicon"] = "batt_20.gif"
|
|
else
|
|
data["PC_batteryicon"] = "batt_5.gif"
|
|
data["PC_batterypercent"] = "[round(internal_cell.percent())]%"
|
|
else
|
|
data["PC_batteryicon"] = "batt_5.gif"
|
|
data["PC_batterypercent"] = "N/C"
|
|
|
|
switch(get_ntnet_status())
|
|
if(NTNET_NO_SIGNAL)
|
|
data["PC_ntneticon"] = "sig_none.gif"
|
|
if(NTNET_LOW_SIGNAL)
|
|
data["PC_ntneticon"] = "sig_low.gif"
|
|
if(NTNET_GOOD_SIGNAL)
|
|
data["PC_ntneticon"] = "sig_high.gif"
|
|
if(NTNET_ETHERNET_SIGNAL)
|
|
data["PC_ntneticon"] = "sig_lan.gif"
|
|
|
|
if(length(idle_threads))
|
|
var/list/program_headers = list()
|
|
for(var/datum/computer_file/program/idle_programs as anything in idle_threads)
|
|
if(!idle_programs.ui_header)
|
|
continue
|
|
program_headers.Add(list(list("icon" = idle_programs.ui_header)))
|
|
|
|
data["PC_programheaders"] = program_headers
|
|
|
|
data["PC_stationtime"] = station_time_timestamp()
|
|
data["PC_stationdate"] = "[time2text(world.realtime, "DDD, Month DD")], [CURRENT_STATION_YEAR]"
|
|
data["PC_showexitprogram"] = !!active_program // Hides "Exit Program" button on mainscreen
|
|
return data
|
|
|
|
///Wipes the computer's current program. Doesn't handle any of the niceties around doing this
|
|
/obj/item/modular_computer/proc/wipe_program(forced)
|
|
if(!active_program)
|
|
return
|
|
active_program.kill_program(forced)
|
|
active_program = null
|
|
|
|
// Relays kill program request to currently active program. Use this to quit current program.
|
|
/obj/item/modular_computer/proc/kill_program(forced = FALSE)
|
|
wipe_program(forced)
|
|
var/mob/user = usr
|
|
if(user && istype(user))
|
|
//Here to prevent programs sleeping in destroy
|
|
INVOKE_ASYNC(src, TYPE_PROC_REF(/datum, ui_interact), user) // Re-open the UI on this computer. It should show the main screen now.
|
|
update_appearance()
|
|
|
|
/obj/item/modular_computer/proc/open_program(mob/user, datum/computer_file/program/program)
|
|
if(program.computer != src)
|
|
CRASH("tried to open program that does not belong to this computer")
|
|
|
|
if(!program || !istype(program)) // Program not found or it's not executable program.
|
|
to_chat(user, span_danger("\The [src]'s screen shows \"I/O ERROR - Unable to run program\" warning."))
|
|
return FALSE
|
|
|
|
// The program is already running. Resume it.
|
|
if(program in idle_threads)
|
|
program.program_state = PROGRAM_STATE_ACTIVE
|
|
active_program = program
|
|
program.alert_pending = FALSE
|
|
idle_threads.Remove(program)
|
|
update_appearance()
|
|
updateUsrDialog()
|
|
return TRUE
|
|
|
|
if(!program.is_supported_by_hardware(hardware_flag, 1, user))
|
|
return FALSE
|
|
|
|
if(idle_threads.len > max_idle_programs)
|
|
to_chat(user, span_danger("\The [src] displays a \"Maximal CPU load reached. Unable to run another program.\" error."))
|
|
return FALSE
|
|
|
|
if(program.requires_ntnet && !get_ntnet_status(program.requires_ntnet_feature)) // The program requires NTNet connection, but we are not connected to NTNet.
|
|
to_chat(user, span_danger("\The [src]'s screen shows \"Unable to connect to NTNet. Please retry. If problem persists contact your system administrator.\" warning."))
|
|
return FALSE
|
|
|
|
if(!program.on_start(user))
|
|
return FALSE
|
|
|
|
active_program = program
|
|
program.alert_pending = FALSE
|
|
update_appearance()
|
|
updateUsrDialog()
|
|
return TRUE
|
|
|
|
// Returns 0 for No Signal, 1 for Low Signal and 2 for Good Signal. 3 is for wired connection (always-on)
|
|
/obj/item/modular_computer/proc/get_ntnet_status(specific_action = 0)
|
|
// NTNet is down and we are not connected via wired connection. No signal.
|
|
if(!SSmodular_computers.check_function(specific_action))
|
|
return NTNET_NO_SIGNAL
|
|
|
|
// computers are connected through ethernet
|
|
if(hardware_flag & PROGRAM_CONSOLE)
|
|
return NTNET_ETHERNET_SIGNAL
|
|
|
|
var/turf/current_turf = get_turf(src)
|
|
if(!current_turf || !istype(current_turf))
|
|
return NTNET_NO_SIGNAL
|
|
if(is_station_level(current_turf.z))
|
|
if(hardware_flag & PROGRAM_LAPTOP) //laptops can connect to ethernet but they have to be on station for that
|
|
return NTNET_ETHERNET_SIGNAL
|
|
return NTNET_GOOD_SIGNAL
|
|
else if(is_mining_level(current_turf.z))
|
|
return NTNET_LOW_SIGNAL
|
|
else if(long_ranged)
|
|
return NTNET_LOW_SIGNAL
|
|
return NTNET_NO_SIGNAL
|
|
|
|
/obj/item/modular_computer/proc/add_log(text)
|
|
if(!get_ntnet_status())
|
|
return FALSE
|
|
|
|
return SSnetworks.add_log(text, network_id)
|
|
|
|
/obj/item/modular_computer/proc/shutdown_computer(loud = 1)
|
|
kill_program(forced = TRUE)
|
|
for(var/datum/computer_file/program/idle_program in idle_threads)
|
|
idle_program.kill_program(forced = TRUE)
|
|
if(looping_sound)
|
|
soundloop.stop()
|
|
if(physical && loud)
|
|
physical.visible_message(span_notice("\The [src] shuts down."))
|
|
enabled = FALSE
|
|
update_appearance()
|
|
|
|
/obj/item/modular_computer/ui_action_click(mob/user, actiontype)
|
|
if(istype(actiontype, /datum/action/item_action/toggle_computer_light))
|
|
toggle_flashlight()
|
|
return
|
|
|
|
return ..()
|
|
|
|
/**
|
|
* Toggles the computer's flashlight, if it has one.
|
|
*
|
|
* Called from ui_act(), does as the name implies.
|
|
* It is separated from ui_act() to be overwritten as needed.
|
|
*/
|
|
/obj/item/modular_computer/proc/toggle_flashlight()
|
|
if(!has_light)
|
|
return FALSE
|
|
set_light_on(!light_on)
|
|
update_appearance()
|
|
update_item_action_buttons(force = TRUE) //force it because we added an overlay, not changed its icon
|
|
return TRUE
|
|
|
|
/**
|
|
* Sets the computer's light color, if it has a light.
|
|
*
|
|
* Called from ui_act(), this proc takes a color string and applies it.
|
|
* It is separated from ui_act() to be overwritten as needed.
|
|
* Arguments:
|
|
** color is the string that holds the color value that we should use. Proc auto-fails if this is null.
|
|
*/
|
|
/obj/item/modular_computer/proc/set_flashlight_color(color)
|
|
if(!has_light || !color)
|
|
return FALSE
|
|
comp_light_color = color
|
|
set_light_color(color)
|
|
return TRUE
|
|
|
|
/obj/item/modular_computer/proc/UpdateDisplay()
|
|
if(!saved_identification && !saved_job)
|
|
name = initial(name)
|
|
return
|
|
name = "[saved_identification] ([saved_job])"
|
|
|
|
/obj/item/modular_computer/attackby(obj/item/attacking_item, mob/user, params)
|
|
// Check for ID first
|
|
if(isidcard(attacking_item) && InsertID(attacking_item, user))
|
|
return
|
|
|
|
// Check for cash next
|
|
if(computer_id_slot && iscash(attacking_item))
|
|
var/obj/item/card/id/inserted_id = computer_id_slot.GetID()
|
|
if(inserted_id)
|
|
inserted_id.attackby(attacking_item, user) // If we do, try and put that attacking object in
|
|
return
|
|
|
|
// Inserting a pAI
|
|
if(istype(attacking_item, /obj/item/pai_card) && !inserted_pai)
|
|
if(!user.transferItemToLoc(attacking_item, src))
|
|
return
|
|
inserted_pai = attacking_item
|
|
balloon_alert(user, "inserted pai")
|
|
update_appearance(UPDATE_ICON)
|
|
return
|
|
|
|
if(istype(attacking_item, /obj/item/stock_parts/cell))
|
|
if(ismachinery(loc))
|
|
return
|
|
if(internal_cell)
|
|
to_chat(user, span_warning("You try to connect \the [attacking_item] to \the [src], but its connectors are occupied."))
|
|
return
|
|
if(user && !user.transferItemToLoc(attacking_item, src))
|
|
return
|
|
internal_cell = attacking_item
|
|
to_chat(user, span_notice("You plug \the [attacking_item] to \the [src]."))
|
|
return
|
|
|
|
// Check if any Applications need it
|
|
for(var/datum/computer_file/item_holding_app as anything in stored_files)
|
|
if(item_holding_app.application_attackby(attacking_item, user))
|
|
return
|
|
|
|
if(istype(attacking_item, /obj/item/paper))
|
|
if(stored_paper >= max_paper)
|
|
balloon_alert(user, "no more room!")
|
|
return
|
|
if(!user.temporarilyRemoveItemFromInventory(attacking_item))
|
|
return FALSE
|
|
balloon_alert(user, "inserted paper")
|
|
qdel(attacking_item)
|
|
stored_paper++
|
|
return
|
|
if(istype(attacking_item, /obj/item/paper_bin))
|
|
var/obj/item/paper_bin/bin = attacking_item
|
|
if(bin.total_paper <= 0)
|
|
balloon_alert(user, "empty bin!")
|
|
return
|
|
var/papers_added //just to keep track
|
|
while((bin.total_paper > 0) && (stored_paper < max_paper))
|
|
papers_added++
|
|
stored_paper++
|
|
bin.remove_paper()
|
|
if(!papers_added)
|
|
return
|
|
balloon_alert(user, "inserted paper")
|
|
to_chat(user, span_notice("Added in [papers_added] new sheets. You now have [stored_paper] / [max_paper] printing paper stored."))
|
|
bin.update_appearance()
|
|
return
|
|
|
|
// Insert a data disk
|
|
if(istype(attacking_item, /obj/item/computer_disk))
|
|
if(inserted_disk)
|
|
user.put_in_hands(inserted_disk)
|
|
balloon_alert(user, "disks swapped")
|
|
if(!user.transferItemToLoc(attacking_item, src))
|
|
return
|
|
inserted_disk = attacking_item
|
|
playsound(src, 'sound/machines/card_slide.ogg', 50)
|
|
return
|
|
|
|
return ..()
|
|
|
|
/obj/item/modular_computer/wrench_act_secondary(mob/living/user, obj/item/tool)
|
|
. = ..()
|
|
tool.play_tool_sound(src, user, 20, volume=20)
|
|
internal_cell?.forceMove(drop_location())
|
|
computer_id_slot?.forceMove(drop_location())
|
|
inserted_disk?.forceMove(drop_location())
|
|
inserted_pai?.forceMove(drop_location())
|
|
new /obj/item/stack/sheet/iron(get_turf(loc), steel_sheet_cost)
|
|
user.balloon_alert(user, "disassembled")
|
|
relay_qdel()
|
|
qdel(src)
|
|
return TOOL_ACT_TOOLTYPE_SUCCESS
|
|
|
|
/obj/item/modular_computer/welder_act(mob/living/user, obj/item/tool)
|
|
. = ..()
|
|
if(atom_integrity == max_integrity)
|
|
to_chat(user, span_warning("\The [src] does not require repairs."))
|
|
return TOOL_ACT_TOOLTYPE_SUCCESS
|
|
|
|
if(!tool.tool_start_check(user, amount=1))
|
|
return TOOL_ACT_TOOLTYPE_SUCCESS
|
|
|
|
to_chat(user, span_notice("You begin repairing damage to \the [src]..."))
|
|
if(!tool.use_tool(src, user, 20, volume=50, amount=1))
|
|
return TOOL_ACT_TOOLTYPE_SUCCESS
|
|
atom_integrity = max_integrity
|
|
to_chat(user, span_notice("You repair \the [src]."))
|
|
update_appearance()
|
|
return TOOL_ACT_TOOLTYPE_SUCCESS
|
|
|
|
/obj/item/modular_computer/deconstruct(disassembled = TRUE)
|
|
break_apart()
|
|
return ..()
|
|
|
|
/obj/item/modular_computer/proc/break_apart()
|
|
if(!(flags_1 & NODECONSTRUCT_1))
|
|
physical.visible_message(span_notice("\The [src] breaks apart!"))
|
|
var/turf/newloc = get_turf(src)
|
|
new /obj/item/stack/sheet/iron(newloc, round(steel_sheet_cost / 2))
|
|
relay_qdel()
|
|
|
|
// Used by processor to relay qdel() to machinery type.
|
|
/obj/item/modular_computer/proc/relay_qdel()
|
|
return
|
|
|
|
// Perform adjacency checks on our physical counterpart, if any.
|
|
/obj/item/modular_computer/Adjacent(atom/neighbor)
|
|
if(physical && physical != src)
|
|
return physical.Adjacent(neighbor)
|
|
return ..()
|
|
|
|
/obj/item/modular_computer/proc/Add_Messenger()
|
|
GLOB.TabletMessengers += src
|
|
|
|
/obj/item/modular_computer/proc/Remove_Messenger()
|
|
GLOB.TabletMessengers -= src
|