Modular Tablets: Converting PDAs to the NtOS System (#65755)

Converts PDA functions and applications over to modular tablets and devices, namely the messaging function. HREF data code is quite honestly clunky and difficult to work with, as I've definitely experienced whilst working on this. By moving from this system over the easier to read (and frankly, easier to add to) TGUI system, you get cleaner looking and more user friendly UIs and a greater degree of standardization amongst other UIs.

Co-authored-by: Seth Scherer <supernovaa41@gmx.com>
Co-authored-by: GoldenAlpharex <58045821+GoldenAlpharex@users.noreply.github.com>
Co-authored-by: Aleksej Komarov <stylemistake@gmail.com>
This commit is contained in:
magatsuchi
2022-04-20 03:08:41 +03:00
committed by GitHub
co-authored by Seth Scherer GoldenAlpharex Aleksej Komarov
parent ee7f0d0a60
commit cd1b891d79
146 changed files with 2733 additions and 3321 deletions
@@ -1,3 +1,5 @@
GLOBAL_LIST_EMPTY(TabletMessengers) // a list of all active messengers, similar to GLOB.PDAs (used primarily with ntmessenger.dm)
// This is the base type that does all the hardware stuff.
// Other types expand it - tablets use a direct subtypes, and
// consoles and laptops use "procssor" item that is held inside machinery piece
@@ -11,7 +13,11 @@
max_integrity = 100
armor = list(MELEE = 0, BULLET = 20, LASER = 20, ENERGY = 100, BOMB = 0, BIO = 100, FIRE = 0, ACID = 0)
var/bypass_state = FALSE // bypassing the set icon state
var/enabled = 0 // Whether the computer is turned on.
var/upgradable = TRUE // whether or not the computer can be upgraded
var/deconstructable = TRUE // whether or not the computer can be deconstructed
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.
@@ -46,14 +52,26 @@
/// Number of total expansion bays this computer has available.
var/max_bays = 0
var/saved_identification = null // next two values are the currently imprinted id and job values
var/saved_job = null
var/honkamnt = 0 /// honk honk honk honk honk honkh onk honkhnoohnk
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.
var/has_light = FALSE //If the computer has a flashlight/LED light/what-have-you installed
var/comp_light_luminosity = 3 //The brightness of that light
var/comp_light_color //The color of that light
var/invisible = FALSE // whether or not the tablet is invisible in messenger and other apps
var/datum/picture/saved_image // the saved image used for messaging purpose like come on dude
var/obj/item/paicard/pai = null
/obj/item/modular_computer/Initialize(mapload)
. = ..()
var/obj/item/computer_hardware/identifier/id = all_components[MC_IDENTIFY]
START_PROCESSING(SSobj, src)
if(!physical)
physical = src
@@ -61,20 +79,28 @@
idle_threads = list()
if(looping_sound)
soundloop = new(src, enabled)
if(id)
id.UpdateDisplay()
update_appearance()
Add_Messenger()
/obj/item/modular_computer/Destroy()
wipe_program(forced = TRUE)
for(var/datum/computer_file/program/idle as anything in idle_threads)
idle.kill_program(TRUE)
idle_threads.Cut()
idle_threads?.Cut()
STOP_PROCESSING(SSobj, src)
for(var/port in all_components)
var/obj/item/computer_hardware/component = all_components[port]
qdel(component)
all_components.Cut() //Die demon die
all_components?.Cut()
//Some components will actually try and interact with this, so let's do it later
QDEL_NULL(soundloop)
Remove_Messenger()
if(istype(pai))
QDEL_NULL(pai)
physical = null
return ..()
@@ -86,6 +112,16 @@
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.
@@ -103,7 +139,10 @@
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]
return (card_slot2?.try_eject(user) || card_slot?.try_eject(user)) //Try the secondary one first.
if(card_slot2?.try_eject(user) || card_slot?.try_eject(user))
return TRUE
if(!istype(src, /obj/item/modular_computer/tablet))
return FALSE
// Gets IDs/access levels from card slot. Would be useful when/if PDAs would become modular PCs.
/obj/item/modular_computer/GetAccess()
@@ -169,6 +208,8 @@
if(human_wearer.wear_id == src)
human_wearer.sec_hud_set_ID()
update_slot_icon()
update_appearance()
return removed_id
return ..()
@@ -176,10 +217,11 @@
/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))
return FALSE
var/obj/item/card/inserting_id = inserting_item.RemoveID()
var/obj/item/card/inserting_id = inserting_item.GetID()
if(!inserting_id)
return FALSE
@@ -188,8 +230,8 @@
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
return FALSE
@@ -241,19 +283,24 @@
. += get_modular_computer_parts_examine(user)
/obj/item/modular_computer/update_icon_state()
icon_state = enabled ? icon_state_powered : icon_state_unpowered
if(!bypass_state)
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(!display_overlays)
return
if(enabled)
. += active_program?.program_icon_state || icon_state_menu
. += 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)
. += "bsod"
. += "broken"
. += mutable_appearance(init_icon, "bsod")
. += mutable_appearance(init_icon, "broken")
// On-click handling. Turns on the computer if it's off and opens the GUI.
@@ -353,6 +400,16 @@
if(istype(holder))
to_chat(holder, "[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)
visible_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()
@@ -489,6 +546,8 @@
return TRUE
/obj/item/modular_computer/screwdriver_act(mob/user, obj/item/tool)
if(!deconstructable)
return
if(!length(all_components))
balloon_alert(user, "no components installed!")
return
@@ -520,6 +579,25 @@
if(istype(W, /obj/item/card/id) && InsertID(W))
return
// Insert a PAI.
if(istype(W, /obj/item/paicard) && !pai)
if(!user.transferItemToLoc(W, src))
return
pai = W
pai.slotted = TRUE
to_chat(user, span_notice("You slot \the [W] into [src]."))
return
// Scan a photo.
if(istype(W, /obj/item/photo))
var/obj/item/computer_hardware/hard_drive/hdd = all_components[MC_HDD]
var/obj/item/photo/pic = W
if(hdd)
for(var/datum/computer_file/program/messenger/messenger in hdd.stored_files)
saved_image = pic.picture
messenger.ProcessPhoto()
return
// Insert items into the components
for(var/h in all_components)
var/obj/item/computer_hardware/H = all_components[h]
@@ -527,7 +605,7 @@
return
// Insert new hardware
if(istype(W, /obj/item/computer_hardware))
if(istype(W, /obj/item/computer_hardware) && upgradable)
if(install_component(W, user))
return
@@ -574,3 +652,9 @@
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
@@ -4,6 +4,8 @@
// Operates TGUI
/obj/item/modular_computer/ui_interact(mob/user, datum/tgui/ui)
if(issilicon(user)) // silicons have some issues regarding real_name
saved_identification = user.real_name
if(!enabled)
if(ui)
ui.close()
@@ -37,6 +39,10 @@
to_chat(user, span_danger("\The [src] beeps three times, it's screen displaying a \"DISK ERROR\" warning."))
return // No HDD, No HDD files list or no stored files. Something is very broken.
if(honkamnt > 0) // EXTRA annoying, huh!
honkamnt--
playsound(src, 'sound/items/bikehorn.ogg', 30, TRUE)
ui = SStgui.try_update_ui(user, src, ui)
if (!ui)
ui = new(user, src, "NtosMain")
@@ -44,28 +50,55 @@
if(ui.open())
ui.send_asset(get_asset_datum(/datum/asset/simple/headers))
/obj/item/modular_computer/ui_static_data(mob/user)
. = ..()
var/list/data = list()
data["show_imprint"] = istype(src, /obj/item/modular_computer/tablet/)
return data
/obj/item/modular_computer/ui_data(mob/user)
var/list/data = get_header_data()
data["device_theme"] = device_theme
data["login"] = list()
data["disk"] = null
var/obj/item/computer_hardware/card_slot/cardholder = all_components[MC_CARD]
var/obj/item/computer_hardware/hard_drive/role/ssd = all_components[MC_HDD_JOB]
data["cardholder"] = FALSE
if(cardholder)
data["cardholder"] = TRUE
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,
)
var/stored_name = saved_identification
var/stored_title = saved_job
if(!stored_name)
stored_name = "Unknown"
if(!stored_title)
stored_title = "Unknown"
data["login"] = list(
IDName = saved_identification,
IDJob = saved_job,
)
data["proposed_login"] = list(
IDName = cardholder.current_identification,
IDJob = cardholder.current_job,
)
if(ssd)
data["disk"] = ssd
data["disk_name"] = ssd.name
for(var/datum/computer_file/program/prog in ssd.stored_files)
var/running = FALSE
if(prog in idle_threads)
running = TRUE
data["disk_programs"] += list(list("name" = prog.filename, "desc" = prog.filedesc, "running" = running, "icon" = prog.program_icon, "alert" = prog.alert_pending))
data["removable_media"] = list()
if(all_components[MC_SDD])
@@ -89,6 +122,7 @@
data["has_light"] = has_light
data["light_on"] = light_on
data["comp_light_color"] = comp_light_color
data["pai"] = pai
return data
@@ -134,10 +168,15 @@
if("PC_runprogram")
var/prog = params["name"]
var/is_disk = params["is_disk"]
var/datum/computer_file/program/P = null
var/obj/item/computer_hardware/hard_drive/role/ssd = all_components[MC_HDD_JOB]
var/mob/user = usr
if(hard_drive)
if(hard_drive && !is_disk)
P = hard_drive.find_file_by_name(prog)
if(ssd && is_disk)
P = ssd.find_file_by_name(prog)
if(!P || !istype(P)) // 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."))
@@ -198,6 +237,13 @@
if(uninstall_component(portable_drive, usr))
user.put_in_hands(portable_drive)
playsound(src, 'sound/machines/card_slide.ogg', 50)
if("job disk")
var/obj/item/computer_hardware/hard_drive/role/ssd = all_components[MC_HDD_JOB]
if(!ssd)
return
if(uninstall_component(ssd, usr))
user.put_in_hands(ssd)
playsound(src, 'sound/machines/card_slide.ogg', 50)
if("intelliCard")
var/obj/item/computer_hardware/ai_slot/intelliholder = all_components[MC_AI]
if(!intelliholder)
@@ -208,14 +254,37 @@
var/obj/item/computer_hardware/card_slot/cardholder = all_components[MC_CARD]
if(!cardholder)
return
cardholder.try_eject(user)
if(cardholder.try_eject(user))
playsound(src, 'sound/machines/card_slide.ogg', 50)
if("secondary RFID card")
var/obj/item/computer_hardware/card_slot/cardholder = all_components[MC_CARD2]
if(!cardholder)
return
cardholder.try_eject(user)
if(cardholder.try_eject(user))
playsound(src, 'sound/machines/card_slide.ogg', 50)
if("PC_Imprint_ID")
var/obj/item/computer_hardware/card_slot/cardholder = all_components[MC_CARD]
var/obj/item/computer_hardware/identifier/id_hardware = all_components[MC_IDENTIFY]
if(!cardholder)
return
saved_identification = cardholder.current_identification
saved_job = cardholder.current_job
if(id_hardware)
id_hardware.UpdateDisplay()
playsound(src, 'sound/machines/terminal_processing.ogg', 15, TRUE)
if("PC_Pai_Interact")
switch(params["option"])
if("eject")
usr.put_in_hands(pai)
pai.slotted = FALSE
pai = null
to_chat(usr, span_notice("You remove the pAI from the [name]."))
if("interact")
pai.attack_self(usr)
return UI_UPDATE
else
return
@@ -0,0 +1,227 @@
/obj/item/modular_computer/tablet/pda/medical
name = "medical PDA"
default_disk = /obj/item/computer_hardware/hard_drive/role/medical
greyscale_config = /datum/greyscale_config/tablet/stripe_thick
greyscale_colors = "#e2e2e2#000099#5d99be"
/obj/item/modular_computer/tablet/pda/viro
name = "virology PDA"
default_disk = /obj/item/computer_hardware/hard_drive/role/medical
greyscale_config = /datum/greyscale_config/tablet/stripe_split
greyscale_colors = "#e2e2e2#355FAC#789876"
/obj/item/modular_computer/tablet/pda/engineering
name = "engineering PDA"
default_disk = /obj/item/computer_hardware/hard_drive/role/engineering
greyscale_config = /datum/greyscale_config/tablet/stripe_thick
greyscale_colors = "#C5994C#69DBF3#D9D65B"
/obj/item/modular_computer/tablet/pda/security
name = "security PDA"
default_disk = /obj/item/computer_hardware/hard_drive/role/security
greyscale_colors = "#cc4242#0000cc"
/obj/item/modular_computer/tablet/pda/detective
name = "detective PDA"
default_disk = /obj/item/computer_hardware/hard_drive/role/detective
greyscale_colors = "#90714F#990202"
/obj/item/modular_computer/tablet/pda/warden
name = "warden PDA"
default_disk = /obj/item/computer_hardware/hard_drive/role/security
greyscale_config = /datum/greyscale_config/tablet/stripe_split
greyscale_colors = "#cc4242#0000cc#666666"
/obj/item/modular_computer/tablet/pda/janitor
name = "janitor PDA"
default_disk = /obj/item/computer_hardware/hard_drive/role/janitor
greyscale_colors = "#933ea8#235AB2"
/obj/item/modular_computer/tablet/pda/science
name = "scientist PDA"
default_disk = /obj/item/computer_hardware/hard_drive/role/signal/ordnance
greyscale_config = /datum/greyscale_config/tablet/stripe_thick
greyscale_colors = "#e2e2e2#000099#9F5CA5"
/obj/item/modular_computer/tablet/pda/heads
default_disk = /obj/item/computer_hardware/hard_drive/role/head
greyscale_config = /datum/greyscale_config/tablet/head
greyscale_colors = "#789876#a92323"
/obj/item/modular_computer/tablet/pda/heads/hop
name = "head of personnel PDA"
default_disk = /obj/item/computer_hardware/hard_drive/role/hop
/obj/item/modular_computer/tablet/pda/heads/hos
name = "head of security PDA"
default_disk = /obj/item/computer_hardware/hard_drive/role/hos
greyscale_config = /datum/greyscale_config/tablet/head
greyscale_colors = "#cc4242#0000cc"
/obj/item/modular_computer/tablet/pda/heads/ce
name = "chief engineer PDA"
default_disk = /obj/item/computer_hardware/hard_drive/role/ce
greyscale_config = /datum/greyscale_config/tablet/stripe_thick/head
greyscale_colors = "#C4A56D#69DBF3#e2e2e2"
/obj/item/modular_computer/tablet/pda/heads/cmo
name = "chief medical officer PDA"
default_disk = /obj/item/computer_hardware/hard_drive/role/cmo
greyscale_config = /datum/greyscale_config/tablet/stripe_thick/head
greyscale_colors = "#e2e2e2#000099#5d99be"
/obj/item/modular_computer/tablet/pda/heads/rd
name = "research director PDA"
default_disk = /obj/item/computer_hardware/hard_drive/role/rd
greyscale_config = /datum/greyscale_config/tablet/stripe_thick/head
greyscale_colors = "#e2e2e2#000099#9F5CA5"
insert_type = /obj/item/pen/fountain
/obj/item/modular_computer/tablet/pda/captain
name = "captain PDA"
default_disk = /obj/item/computer_hardware/hard_drive/role/captain
greyscale_config = /datum/greyscale_config/tablet/captain
greyscale_colors = "#2C7CB2#FF0000#FFFFFF#F5D67B"
insert_type = /obj/item/pen/fountain
/obj/item/modular_computer/tablet/pda/captain/Initialize(mapload)
. = ..()
RegisterSignal(src, COMSIG_TABLET_CHECK_DETONATE, .proc/tab_no_detonate)
/obj/item/modular_computer/tablet/pda/cargo
name = "cargo technician PDA"
default_disk = /obj/item/computer_hardware/hard_drive/role/quartermaster
greyscale_colors = "#D6B328#6506ca"
/obj/item/modular_computer/tablet/pda/quartermaster/Initialize(mapload)
. = ..()
install_component(new /obj/item/computer_hardware/printer/mini)
/obj/item/modular_computer/tablet/pda/quartermaster
name = "quartermaster PDA"
default_disk = /obj/item/computer_hardware/hard_drive/role/quartermaster
greyscale_config = /datum/greyscale_config/tablet/stripe_thick
greyscale_colors = "#D6B328#6506ca#927444"
/obj/item/modular_computer/tablet/pda/quartermaster/Initialize(mapload)
. = ..()
install_component(new /obj/item/computer_hardware/printer/mini)
/obj/item/modular_computer/tablet/pda/shaftminer
name = "shaft miner PDA"
greyscale_config = /datum/greyscale_config/tablet/stripe_thick
greyscale_colors = "#927444#D6B328#6C3BA1"
/obj/item/modular_computer/tablet/pda/chaplain
name = "chaplain PDA"
greyscale_config = /datum/greyscale_config/tablet/chaplain
greyscale_colors = "#333333#d11818"
/obj/item/modular_computer/tablet/pda/lawyer
name = "lawyer PDA"
default_disk = /obj/item/computer_hardware/hard_drive/role/lawyer
greyscale_colors = "#5B74A5#f7e062"
insert_type = /obj/item/pen/fountain
/obj/item/modular_computer/tablet/pda/botanist
name = "botanist PDA"
greyscale_config = /datum/greyscale_config/tablet/stripe_thick
greyscale_colors = "#50E193#E26F41#71A7CA"
/obj/item/modular_computer/tablet/pda/roboticist
name = "roboticist PDA"
greyscale_config = /datum/greyscale_config/tablet/stripe_split
greyscale_colors = "#484848#0099cc#d33725"
default_disk = /obj/item/computer_hardware/hard_drive/role/roboticist
/obj/item/modular_computer/tablet/pda/cook
name = "cook PDA"
greyscale_colors = "#e2e2e2#a92323"
/obj/item/modular_computer/tablet/pda/bar
name = "bartender PDA"
greyscale_colors = "#333333#c7c7c7"
/obj/item/modular_computer/tablet/pda/atmos
name = "atmospherics PDA"
default_disk = /obj/item/computer_hardware/hard_drive/role/atmos
greyscale_config = /datum/greyscale_config/tablet/stripe_thick
greyscale_colors = "#ceca2b#00E5DA#727272"
/obj/item/modular_computer/tablet/pda/chemist
name = "chemist PDA"
default_disk = /obj/item/computer_hardware/hard_drive/role/chemistry
greyscale_config = /datum/greyscale_config/tablet/stripe_thick
greyscale_colors = "#e2e2e2#355FAC#ea6400"
/obj/item/modular_computer/tablet/pda/geneticist
name = "geneticist PDA"
default_disk = /obj/item/computer_hardware/hard_drive/role/medical
greyscale_config = /datum/greyscale_config/tablet/stripe_split
greyscale_colors = "#e2e2e2#000099#0097ca"
/obj/item/modular_computer/tablet/pda/clown
name = "clown PDA"
default_disk = /obj/item/computer_hardware/hard_drive/role/virus/clown
icon_state = "pda-clown"
greyscale_config = null
greyscale_colors = null
insert_type = /obj/item/toy/crayon/rainbow
/obj/item/modular_computer/tablet/pda/clown/ComponentInitialize()
. = ..()
AddComponent(/datum/component/slippery/clowning, 120, NO_SLIP_WHEN_WALKING, CALLBACK(src, .proc/AfterSlip), slot_whitelist = list(ITEM_SLOT_ID, ITEM_SLOT_BELT))
AddComponent(/datum/component/wearertargeting/sitcomlaughter, CALLBACK(src, .proc/after_sitcom_laugh))
/obj/item/modular_computer/tablet/pda/clown/update_overlays()
. = ..()
. += mutable_appearance(icon, "pda_stripe_clown") // clowns have eyes that go over their screen, so it needs to be compiled last
/obj/item/modular_computer/tablet/pda/clown/proc/AfterSlip(mob/living/carbon/human/M)
if (istype(M) && (M.real_name != saved_identification))
var/obj/item/computer_hardware/hard_drive/role/virus/clown/cart = all_components[MC_HDD_JOB]
if(istype(cart) && cart.charges < 5)
cart.charges++
playsound(src,'sound/machines/ping.ogg',30,TRUE)
/obj/item/modular_computer/tablet/pda/clown/proc/after_sitcom_laugh(mob/victim)
victim.visible_message("[src] lets out a burst of laughter!")
/obj/item/modular_computer/tablet/pda/mime
name = "mime PDA"
default_disk = /obj/item/computer_hardware/hard_drive/role/virus/mime
greyscale_config = /datum/greyscale_config/tablet/mime
greyscale_colors = "#e2e2e2#cc4242"
insert_type = /obj/item/toy/crayon/mime
/obj/item/modular_computer/tablet/pda/mime/Initialize(mapload)
. = ..()
var/obj/item/computer_hardware/hard_drive/hdd = all_components[MC_HDD]
if(hdd)
for(var/datum/computer_file/program/messenger/msg in hdd.stored_files)
msg.mime_mode = TRUE
msg.allow_emojis = TRUE
/obj/item/modular_computer/tablet/pda/curator
name = "curator PDA"
default_disk = /obj/item/computer_hardware/hard_drive/role/curator
greyscale_config = null
greyscale_colors = null
icon_state = "pda-library"
insert_type = /obj/item/pen/fountain
display_overlays = FALSE
/obj/item/modular_computer/tablet/pda/syndicate
name = "military PDA"
greyscale_colors = "#891417#80FF80"
saved_identification = "John Doe"
saved_job = "Citizen"
invisible = TRUE
/obj/item/modular_computer/tablet/pda/clear
name = "clear PDA"
icon_state = "pda-clear"
greyscale_config = null
greyscale_colors = null
@@ -16,16 +16,95 @@
has_light = TRUE //LED flashlight!
comp_light_luminosity = 2.3 //Same as the PDA
looping_sound = FALSE
var/has_variants = TRUE
var/finish_color = null
var/list/contained_item = list(/obj/item/pen, /obj/item/toy/crayon, /obj/item/lipstick, /obj/item/flashlight/pen, /obj/item/clothing/mask/cigarette)
var/obj/item/insert_type = /obj/item/pen
var/obj/item/inserted_item
var/note = "Congratulations on your station upgrading to the new NtOS and Thinktronic based collaboration effort, bringing you the best in electronics and software since 2467!" // the note used by the notekeeping app, stored here for convenience
/obj/item/modular_computer/tablet/update_icon_state()
if(has_variants)
if(has_variants && !bypass_state)
if(!finish_color)
finish_color = pick("red", "blue", "brown", "green", "black")
icon_state = icon_state_powered = icon_state_unpowered = "[base_icon_state]-[finish_color]"
return ..()
/obj/item/modular_computer/tablet/interact(mob/user)
. = ..()
if(HAS_TRAIT(src, TRAIT_PDA_MESSAGE_MENU_RIGGED))
explode(usr, from_message_menu = TRUE)
return
/obj/item/modular_computer/tablet/attackby(obj/item/W, mob/user)
. = ..()
if(is_type_in_list(W, contained_item))
if(inserted_item)
to_chat(user, span_warning("There is already \a [inserted_item] in \the [src]!"))
else
if(!user.transferItemToLoc(W, src))
return
to_chat(user, span_notice("You insert \the [W] into \the [src]."))
inserted_item = W
playsound(src, 'sound/machines/pda_button1.ogg', 50, TRUE)
/obj/item/modular_computer/tablet/AltClick(mob/user)
. = ..()
if(.)
return
remove_pen(user)
/obj/item/modular_computer/tablet/proc/tab_no_detonate()
SIGNAL_HANDLER
return COMPONENT_TABLET_NO_DETONATE
/obj/item/modular_computer/tablet/proc/remove_pen(mob/user)
if(issilicon(user) || !user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) //TK doesn't work even with this removed but here for readability
return
if(inserted_item)
to_chat(user, span_notice("You remove [inserted_item] from [src]."))
user.put_in_hands(inserted_item)
inserted_item = null
update_appearance()
playsound(src, 'sound/machines/pda_button2.ogg', 50, TRUE)
else
to_chat(user, span_warning("This tablet does not have a pen in it!"))
// Tablet 'splosion..
/obj/item/modular_computer/tablet/proc/explode(mob/target, mob/bomber, from_message_menu = FALSE)
var/turf/T = get_turf(src)
if(from_message_menu)
log_bomber(null, null, target, "'s tablet exploded as [target.p_they()] tried to open their tablet message menu because of a recent tablet bomb.")
else
log_bomber(bomber, "successfully tablet-bombed", target, "as [target.p_they()] tried to reply to a rigged tablet message [bomber && !is_special_character(bomber) ? "(SENT BY NON-ANTAG)" : ""]")
if (ismob(loc))
var/mob/M = loc
M.show_message(span_userdanger("Your [src] explodes!"), MSG_VISUAL, span_warning("You hear a loud *pop*!"), MSG_AUDIBLE)
else
visible_message(span_danger("[src] explodes!"), span_warning("You hear a loud *pop*!"))
target.client?.give_award(/datum/award/achievement/misc/clickbait, target)
if(T)
T.hotspot_expose(700,125)
if(istype(all_components[MC_HDD_JOB], /obj/item/computer_hardware/hard_drive/role/virus/deto))
explosion(src, devastation_range = -1, heavy_impact_range = 1, light_impact_range = 3, flash_range = 4)
else
explosion(src, devastation_range = -1, heavy_impact_range = -1, light_impact_range = 2, flash_range = 3)
qdel(src)
// SUBTYPES
/obj/item/modular_computer/tablet/syndicate_contract_uplink
name = "contractor tablet"
icon = 'icons/obj/contractor_tablet.dmi'
@@ -65,8 +144,8 @@
has_light = FALSE //tablet light button actually enables/disables the borg lamp
comp_light_luminosity = 0
has_variants = FALSE
///Ref to the borg we're installed in. Set by the borg during our creation.
var/mob/living/silicon/robot/borgo
///Ref to the silicon we're installed in. Set by the borg during our creation.
var/mob/living/silicon/borgo
///Ref to the RoboTact app. Important enough to borgs to deserve a ref.
var/datum/computer_file/program/robotact/robotact
///IC log that borgs can view in their personal management app
@@ -119,22 +198,28 @@
/obj/item/modular_computer/tablet/integrated/ui_data(mob/user)
. = ..()
.["has_light"] = TRUE
.["light_on"] = borgo?.lamp_enabled
.["comp_light_color"] = borgo?.lamp_color
if(istype(borgo, /mob/living/silicon/robot))
var/mob/living/silicon/robot/robo = borgo
.["light_on"] = robo.lamp_enabled
.["comp_light_color"] = robo.lamp_color
//Makes the flashlight button affect the borg rather than the tablet
/obj/item/modular_computer/tablet/integrated/toggle_flashlight()
if(!borgo || QDELETED(borgo))
return FALSE
borgo.toggle_headlamp()
if(istype(borgo, /mob/living/silicon/robot))
var/mob/living/silicon/robot/robo = borgo
robo.toggle_headlamp()
return TRUE
//Makes the flashlight color setting affect the borg rather than the tablet
/obj/item/modular_computer/tablet/integrated/set_flashlight_color(color)
if(!borgo || QDELETED(borgo) || !color)
return FALSE
borgo.lamp_color = color
borgo.toggle_headlamp(FALSE, TRUE)
if(istype(borgo, /mob/living/silicon/robot))
var/mob/living/silicon/robot/robo = borgo
robo.lamp_color = color
robo.toggle_headlamp(FALSE, TRUE)
return TRUE
/obj/item/modular_computer/tablet/integrated/alert_call(datum/computer_file/program/caller, alerttext, sound = 'sound/machines/twobeep_high.ogg')
@@ -143,6 +228,8 @@
borgo.playsound_local(src, sound, 50, TRUE)
to_chat(borgo, span_notice("The [src] displays a [caller.filedesc] notification: [alerttext]"))
/obj/item/modular_computer/tablet/integrated/ui_state(mob/user)
return GLOB.reverse_contained_state
/obj/item/modular_computer/tablet/integrated/syndicate
icon_state = "tablet-silicon-syndicate"
@@ -153,4 +240,51 @@
/obj/item/modular_computer/tablet/integrated/syndicate/Initialize(mapload)
. = ..()
borgo.lamp_color = COLOR_RED //Syndicate likes it red
if(istype(borgo, /mob/living/silicon/robot))
var/mob/living/silicon/robot/robo = borgo
robo.lamp_color = COLOR_RED //Syndicate likes it red
// Round start tablets
/obj/item/modular_computer/tablet/pda
icon_state = "pda"
greyscale_config = /datum/greyscale_config/tablet
greyscale_colors = "#999875#a92323"
bypass_state = TRUE
var/default_disk = 0
/obj/item/modular_computer/tablet/pda/update_overlays()
. = ..()
var/init_icon = initial(icon)
var/obj/item/computer_hardware/card_slot/card = all_components[MC_CARD]
if(!init_icon)
return
if(card)
if(card.stored_card)
. += mutable_appearance(init_icon, "id_overlay")
if(light_on)
. += mutable_appearance(init_icon, "light_overlay")
/obj/item/modular_computer/tablet/pda/attack_ai(mob/user)
to_chat(user, span_notice("It doesn't feel right to snoop around like that..."))
return // we don't want ais or cyborgs using a private role tablet
/obj/item/modular_computer/tablet/pda/Initialize(mapload)
. = ..()
install_component(new /obj/item/computer_hardware/hard_drive/small)
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/network_card)
install_component(new /obj/item/computer_hardware/card_slot)
install_component(new /obj/item/computer_hardware/identifier)
install_component(new /obj/item/computer_hardware/sensorpackage)
if(default_disk)
var/obj/item/computer_hardware/hard_drive/portable/disk = new default_disk(src)
install_component(disk)
if(insert_type)
inserted_item = new insert_type(src)
@@ -107,6 +107,5 @@
/obj/item/modular_computer/tablet/integrated/Initialize(mapload)
. = ..()
install_component(new /obj/item/computer_hardware/processor_unit/small)
install_component(new /obj/item/computer_hardware/hard_drive/small/integrated)
install_component(new /obj/item/computer_hardware/recharger/cyborg)
install_component(new /obj/item/computer_hardware/network_card/integrated)
@@ -3,7 +3,7 @@
filedesc = "Newscaster"
required_access = list(ACCESS_LIBRARY)
category = PROGRAM_CATEGORY_CREW
program_icon_state = "computer"
program_icon_state = "bountyboard"
extended_desc = "This program allows any user to access the Newscaster network from anywhere."
size = 2
requires_ntnet = TRUE
@@ -0,0 +1,29 @@
/datum/computer_file/program/notepad
filename = "notepad"
filedesc = "Notepad"
category = PROGRAM_CATEGORY_MISC
program_icon_state = "generic"
extended_desc = "Jot down your work-safe thoughts and what not."
size = 2
tgui_id = "NtosNotepad"
program_icon = "book"
usage_flags = PROGRAM_TABLET
/datum/computer_file/program/notepad/ui_act(action, list/params, datum/tgui/ui)
. = ..()
if(.)
return
switch(action)
if("UpdateNote")
var/obj/item/modular_computer/tablet/comp = computer
comp.note = params["newnote"]
return UI_UPDATE
/datum/computer_file/program/notepad/ui_data(mob/user)
var/list/data = get_header_data()
var/obj/item/modular_computer/tablet/comp = computer
data["note"] = comp.note
return data
@@ -0,0 +1,366 @@
/datum/computer_file/program/messenger
filename = "nt_messenger"
filedesc = "Direct Messenger"
category = PROGRAM_CATEGORY_MISC
program_icon_state = "command"
extended_desc = "This program allows old-school communication with other modular devices."
size = 8
usage_flags = PROGRAM_TABLET
ui_header = "ntnrc_idle.gif"
available_on_ntnet = TRUE
tgui_id = "NtosMessenger"
program_icon = "comment-alt"
alert_able = TRUE
/// The current ringtone (displayed in the chat when a message is received).
var/ringtone = "beep"
/// Whether or not the ringtone is currently on.
var/ringer_status = TRUE
/// Whether or not we're sending and receiving messages.
var/sending_and_receiving = TRUE
/// The messages currently saved in the app.
var/messages = list()
/// great wisdom from PDA.dm - "no spamming" (prevents people from spamming the same message over and over)
var/last_text
/// even more wisdom from PDA.dm - "no everyone spamming" (prevents people from spamming the same message over and over)
var/last_text_everyone
/// Scanned photo for sending purposes.
var/datum/picture/picture
/// Whether or not we allow emojis to be sent by the user.
var/allow_emojis = FALSE
/// Whether or not we're currently looking at the message list.
var/viewing_messages = FALSE
// Whether or not this device is currently hidden from the message monitor.
var/monitor_hidden = FALSE
// Whether or not we're sorting by job.
var/sort_by_job = TRUE
// Whether or not we're sending (or trying to send) a virus.
var/sending_virus = FALSE
/// The path for the current loaded image in rsc
var/photo_path
/// Whether or not this app is loaded on a silicon's tablet.
var/is_silicon = FALSE
/// Whether or not we're in a mime PDA.
var/mime_mode = FALSE
/datum/computer_file/program/messenger/proc/ScrubMessengerList()
var/list/dictionary = list()
for(var/obj/item/modular_computer/messenger in GetViewableDevices(sort_by_job))
if(messenger.saved_identification && messenger.saved_job && !(messenger == computer))
var/list/data = list()
data["name"] = messenger.saved_identification
data["job"] = messenger.saved_job
data["ref"] = REF(messenger)
//if(data["ref"] != REF(computer)) // you cannot message yourself (despite all my rage)
dictionary += list(data)
return dictionary
/proc/GetViewableDevices(sort_by_job = FALSE)
var/list/dictionary = list()
var/sortmode
if(sort_by_job)
sortmode = /proc/cmp_pdajob_asc
else
sortmode = /proc/cmp_pdaname_asc
for(var/obj/item/modular_computer/P in sort_list(GLOB.TabletMessengers, sortmode))
var/obj/item/computer_hardware/hard_drive/drive = P.all_components[MC_HDD]
if(!drive)
continue
for(var/datum/computer_file/program/messenger/app in drive.stored_files)
if(!P.saved_identification || !P.saved_job || P.invisible || app.monitor_hidden)
continue
dictionary += P
return dictionary
/datum/computer_file/program/messenger/proc/StringifyMessengerTarget(obj/item/modular_computer/messenger)
return "[messenger.saved_identification] ([messenger.saved_job])"
/datum/computer_file/program/messenger/proc/ProcessPhoto()
if(computer.saved_image)
var/icon/img = computer.saved_image.picture_image
var/deter_path = "tmp_msg_photo[rand(0, 99999)].png"
usr << browse_rsc(img, deter_path) // funny random assignment for now, i'll make an actual key later
photo_path = deter_path
/datum/computer_file/program/messenger/ui_state(mob/user)
if(istype(user, /mob/living/silicon))
return GLOB.reverse_contained_state
return GLOB.default_state
/datum/computer_file/program/messenger/ui_act(action, list/params, datum/tgui/ui)
. = ..()
if(.)
return
switch(action)
if("PDA_ringSet")
var/t = tgui_input_text(usr, "Enter a new tablet ID", "Computer ID", "", 20)
var/mob/living/usr_mob = usr
if(in_range(computer, usr_mob) && computer.loc == usr_mob && t)
if(SEND_SIGNAL(computer, COMSIG_TABLET_CHANGE_ID, usr_mob, t) & COMPONENT_STOP_RINGTONE_CHANGE)
return
else
ringtone = t
return(UI_UPDATE)
if("PDA_ringer_status")
ringer_status = !ringer_status
return(UI_UPDATE)
if("PDA_sAndR")
sending_and_receiving = !sending_and_receiving
return(UI_UPDATE)
if("PDA_viewMessages")
viewing_messages = !viewing_messages
return(UI_UPDATE)
if("PDA_clearMessages")
messages = list()
return(UI_UPDATE)
if("PDA_changeSortStyle")
sort_by_job = !sort_by_job
return(UI_UPDATE)
if("PDA_sendEveryone")
if(!sending_and_receiving)
to_chat(usr, span_notice("ERROR: Device has sending disabled."))
return
var/list/targets = list()
for(var/obj/item/modular_computer/mc in GetViewableDevices())
targets += mc
if(targets.len > 0)
send_message(usr, targets, TRUE)
return(UI_UPDATE)
if("PDA_sendMessage")
if(!sending_and_receiving)
to_chat(usr, span_notice("ERROR: Device has sending disabled."))
return
var/obj/item/modular_computer/target = locate(params["ref"])
if(!target)
return // we don't want tommy sending his messages to nullspace
if(!(target.saved_identification == params["name"] && target.saved_job == params["job"]))
to_chat(usr, span_notice("ERROR: User no longer exists."))
return
var/obj/item/computer_hardware/hard_drive/drive = target.all_components[MC_HDD]
for(var/datum/computer_file/program/messenger/app in drive.stored_files)
if(!app.sending_and_receiving && !sending_virus)
to_chat(usr, span_notice("ERROR: Device has receiving disabled."))
return
if(sending_virus)
var/obj/item/computer_hardware/hard_drive/role/virus/disk = computer.all_components[MC_HDD_JOB]
if(istype(disk))
disk.send_virus(target, usr)
return(UI_UPDATE)
send_message(usr, list(target))
return(UI_UPDATE)
if("PDA_clearPhoto")
computer.saved_image = null
photo_path = null
return(UI_UPDATE)
if("PDA_toggleVirus")
sending_virus = !sending_virus
return(UI_UPDATE)
/datum/computer_file/program/messenger/ui_data(mob/user)
var/list/data = get_header_data()
var/obj/item/computer_hardware/hard_drive/role/disk = computer.all_components[MC_HDD_JOB]
data["owner"] = computer.saved_identification
data["messages"] = messages
data["ringer_status"] = ringer_status
data["sending_and_receiving"] = sending_and_receiving
data["messengers"] = ScrubMessengerList()
data["viewing_messages"] = viewing_messages
data["sortByJob"] = sort_by_job
data["isSilicon"] = is_silicon
data["photo"] = photo_path
if(disk)
data["canSpam"] = disk.CanSpam()
data["virus_attach"] = istype(disk, /obj/item/computer_hardware/hard_drive/role/virus)
data["sending_virus"] = sending_virus
return data
////////////////////////
// MESSAGE HANDLING
////////////////////////
// How I Learned To Stop Being A PDA Bloat Chump And Learn To Embrace The Lightweight
// Gets the input for a message being sent.
/datum/computer_file/program/messenger/proc/msg_input(mob/living/U = usr, rigged = FALSE)
var/t = null
if(mime_mode)
t = emoji_sanitize(tgui_input_text(U, "Enter emojis", "NT Messaging"))
else
t = tgui_input_text(U, "Enter a message", "NT Messaging")
if (!t || !sending_and_receiving)
return
if(!U.canUseTopic(computer, BE_CLOSE))
return
return sanitize(t)
/datum/computer_file/program/messenger/proc/send_message(mob/living/user, list/obj/item/modular_computer/targets, everyone = FALSE, rigged = FALSE, fake_name = null, fake_job = null)
var/message = msg_input(user, rigged)
if(!message || !targets.len)
return FALSE
if((last_text && world.time < last_text + 10) || (everyone && last_text_everyone && world.time < last_text_everyone + 2 MINUTES))
return FALSE
var/turf/position = get_turf(computer)
for(var/obj/item/jammer/jammer as anything in GLOB.active_jammers)
var/turf/jammer_turf = get_turf(jammer)
if(position?.z == jammer_turf.z && (get_dist(position, jammer_turf) <= jammer.range))
return FALSE
var/list/filter_result = CAN_BYPASS_FILTER(user) ? null : is_ic_filtered_for_pdas(message)
if (filter_result)
REPORT_CHAT_FILTER_TO_USER(user, filter_result)
return FALSE
var/list/soft_filter_result = CAN_BYPASS_FILTER(user) ? null : is_soft_ic_filtered_for_pdas(message)
if (soft_filter_result)
if(tgui_alert(usr,"Your message contains \"[soft_filter_result[CHAT_FILTER_INDEX_WORD]]\". \"[soft_filter_result[CHAT_FILTER_INDEX_REASON]]\", Are you sure you want to send it?", "Soft Blocked Word", list("Yes", "No")) != "Yes")
return FALSE
message_admins("[ADMIN_LOOKUPFLW(usr)] has passed the soft filter for \"[soft_filter_result[CHAT_FILTER_INDEX_WORD]]\" they may be using a disallowed term in PDA messages. Message: \"[html_encode(message)]\"")
log_admin_private("[key_name(usr)] has passed the soft filter for \"[soft_filter_result[CHAT_FILTER_INDEX_WORD]]\" they may be using a disallowed term in PDA messages. Message: \"[message]\"")
// Send the signal
var/list/string_targets = list()
for (var/obj/item/modular_computer/comp in targets)
if (comp.saved_identification && comp.saved_job) // != src is checked by the UI
string_targets += STRINGIFY_PDA_TARGET(comp.saved_identification, comp.saved_job)
if (!string_targets.len)
return FALSE
var/datum/signal/subspace/messaging/tablet_msg/signal = new(computer, list(
"name" = fake_name || computer.saved_identification,
"job" = fake_job || computer.saved_job,
"message" = html_decode(message),
"ref" = REF(computer),
"targets" = targets,
"emojis" = allow_emojis,
"rigged" = rigged,
"photo" = photo_path,
"automated" = FALSE,
))
if(rigged) //Will skip the message server and go straight to the hub so it can't be cheesed by disabling the message server machine
signal.data["rigged_user"] = REF(user) // Used for bomb logging
signal.server_type = /obj/machinery/telecomms/hub
signal.data["reject"] = FALSE // Do not refuse the message
signal.send_to_receivers()
// If it didn't reach, note that fact
if (!signal.data["done"])
to_chat(user, span_notice("ERROR: Server isn't responding."))
//if(!silent)
//playsound(src, 'sound/machines/terminal_error.ogg', 15, TRUE)
return FALSE
if(allow_emojis)
message = emoji_parse(message)//already sent- this just shows the sent emoji as one to the sender in the to_chat
signal.data["message"] = emoji_parse(signal.data["message"])
// Log it in our logs
var/list/message_data = list()
message_data["name"] = signal.data["name"]
message_data["job"] = signal.data["job"]
message_data["contents"] = html_decode(signal.data["message"])
message_data["outgoing"] = TRUE
message_data["ref"] = signal.data["ref"]
message_data["photo"] = signal.data["photo"]
// Show it to ghosts
var/ghost_message = span_name("[message_data["name"]] </span><span class='game say'>[rigged ? "Rigged" : ""] PDA Message</span> --> [span_name("[signal.format_target()]")]: <span class='message'>[signal.data["message"]]")
for(var/mob/M in GLOB.player_list)
if(isobserver(M) && (M.client?.prefs.chat_toggles & CHAT_GHOSTPDA))
to_chat(M, "[FOLLOW_LINK(M, user)] [ghost_message]")
// Log in the talk log
user.log_talk(message, LOG_PDA, tag="[rigged ? "Rigged" : ""] PDA: [initial(message_data["name"])] to [signal.format_target()]")
if(rigged)
log_bomber(user, "sent a rigged PDA message (Name: [message_data["name"]]. Job: [message_data["job"]]) to [english_list(string_targets)] [!is_special_character(user) ? "(SENT BY NON-ANTAG)" : ""]")
to_chat(user, span_info("PDA message sent to [signal.format_target()]: [signal.data["message"]]"))
if (ringer_status)
computer.send_sound()
last_text = world.time
if (everyone)
message_data["name"] = "Everyone"
message_data["job"] = ""
last_text_everyone = world.time
messages += list(message_data)
return TRUE
/datum/computer_file/program/messenger/proc/receive_message(datum/signal/subspace/messaging/tablet_msg/signal)
var/list/message_data = list()
message_data["name"] = signal.data["name"]
message_data["job"] = signal.data["job"]
message_data["contents"] = html_decode(signal.data["message"])
message_data["outgoing"] = FALSE
message_data["ref"] = signal.data["ref"]
message_data["automated"] = signal.data["automated"]
message_data["photo"] = signal.data["photo"]
messages += list(message_data)
var/mob/living/L = null
if(computer.loc && isliving(computer.loc))
L = computer.loc
//Maybe they are a pAI!
else
L = get(computer, /mob/living/silicon)
if(L && (L.stat == CONSCIOUS || L.stat == SOFT_CRIT))
var/reply = "(<a href='byond://?src=[REF(src)];choice=[signal.data["rigged"] ? "Mess_us_up" : "Message"];skiprefresh=1;target=[signal.data["ref"]]'>Reply</a>)"
var/hrefstart
var/hrefend
if (isAI(L))
hrefstart = "<a href='?src=[REF(L)];track=[html_encode(signal.data["name"])]'>"
hrefend = "</a>"
if(signal.data["automated"])
reply = "\[Automated Message\]"
var/inbound_message = signal.format_message()
if(signal.data["emojis"] == TRUE)//so will not parse emojis as such from pdas that don't send emojis
inbound_message = emoji_parse(inbound_message)
if(ringer_status)
to_chat(L, "<span class='infoplain'>[icon2html(src)] <b>PDA message from [hrefstart][signal.data["name"]] ([signal.data["job"]])[hrefend], </b>[inbound_message] [reply]</span>")
if (ringer_status)
computer.ring(ringtone)
/datum/computer_file/program/messenger/Topic(href, href_list)
..()
if(!href_list["close"] && usr.canUseTopic(computer, BE_CLOSE, FALSE, NO_TK))
switch(href_list["choice"])
if("Message")
send_message(usr, list(locate(href_list["target"])))
if("Mess_us_up")
if(!HAS_TRAIT(src, TRAIT_PDA_CAN_EXPLODE))
var/obj/item/modular_computer/tablet/comp = computer
comp.explode(usr, from_message_menu = TRUE)
return
@@ -0,0 +1,78 @@
/datum/computer_file/program/phys_scanner
filename = "phys_scanner"
filedesc = "Physical Scanner"
category = PROGRAM_CATEGORY_MISC
extended_desc = "This program allows the tablet to scan physical objects and display a data output."
size = 8
usage_flags = PROGRAM_TABLET
available_on_ntnet = FALSE
tgui_id = "NtosPhysScanner"
program_icon = "barcode"
var/current_mode = 0
var/available_modes = 0
var/last_record = ""
/datum/computer_file/program/phys_scanner/proc/ReadModes()
var/reads = list()
if(available_modes & DISK_CHEM)
reads += "Reagent"
if(available_modes & DISK_MED)
reads += "Health"
return reads
/datum/computer_file/program/phys_scanner/proc/ReadCurrent()
if(current_mode & DISK_CHEM)
return "Reagent"
if(current_mode & DISK_MED)
return "Health"
/datum/computer_file/program/phys_scanner/tap(atom/A, mob/living/user, params)
. = ..()
switch(current_mode)
if(DISK_CHEM)
if(!isnull(A.reagents))
if(A.reagents.reagent_list.len > 0)
var/reagents_length = A.reagents.reagent_list.len
last_record = "[reagents_length] chemical agent[reagents_length > 1 ? "s" : ""] found."
for (var/re in A.reagents.reagent_list)
last_record += "\t [re]"
else
last_record = "No active chemical agents found in [A]."
else
last_record = "No significant chemical agents found in [A]."
if(DISK_MED)
var/mob/living/carbon/carbon = A
if(istype(carbon))
carbon.visible_message(span_notice("[user] analyzes [A]'s vitals."))
last_record = healthscan(user, carbon, 1, tochat = FALSE)
/datum/computer_file/program/phys_scanner/ui_act(action, list/params, datum/tgui/ui)
. = ..()
if(.)
return
switch(action)
if("selectMode")
switch(params["newMode"])
if("Reagent")
current_mode = DISK_CHEM
if("Health")
current_mode = DISK_MED
return UI_UPDATE
/datum/computer_file/program/phys_scanner/ui_data(mob/user)
var/list/data = get_header_data()
data["set_mode"] = ReadCurrent()
data["last_record"] = last_record
data["available_modes"] = ReadModes()
return data
@@ -0,0 +1,74 @@
/datum/computer_file/program/records
filename = "ntrecords"
filedesc = "Records"
extended_desc = "Allows the user to view several basic records from the crew."
category = PROGRAM_CATEGORY_MISC
program_icon = "clipboard"
program_icon_state = "crew"
tgui_id = "NtosRecords"
size = 4
usage_flags = PROGRAM_TABLET | PROGRAM_LAPTOP
available_on_ntnet = FALSE
var/mode
/datum/computer_file/program/records/medical
filedesc = "Medical Records"
filename = "medrecords"
program_icon = "book-medical"
extended_desc = "Allows the user to view several basic medical records from the crew."
transfer_access = list(ACCESS_MEDICAL, ACCESS_FLAG_COMMAND)
available_on_ntnet = TRUE
mode = "medical"
/datum/computer_file/program/records/security
filedesc = "Security Records"
filename = "secrecords"
extended_desc = "Allows the user to view several basic security records from the crew."
transfer_access = list(ACCESS_SECURITY, ACCESS_FLAG_COMMAND)
available_on_ntnet = TRUE
mode = "security"
/datum/computer_file/program/records/proc/GetRecordsReadable()
var/list/all_records = list()
switch(mode)
if("security")
for(var/datum/data/record/person in GLOB.data_core.general)
var/datum/data/record/security_person = find_record("id", person.fields["id"], GLOB.data_core.security)
var/list/current_record = list()
if(security_person)
current_record["wanted"] = security_person.fields["criminal"]
current_record["id"] = person.fields["id"]
current_record["name"] = person.fields["name"]
current_record["rank"] = person.fields["rank"]
current_record["gender"] = person.fields["gender"]
current_record["age"] = person.fields["age"]
current_record["species"] = person.fields["species"]
current_record["fingerprint"] = person.fields["fingerprint"]
all_records += list(current_record)
if("medical")
for(var/datum/data/record/person in GLOB.data_core.medical)
var/list/current_record = list()
current_record["name"] = person.fields["name"]
current_record["bloodtype"] = person.fields["blood_type"]
current_record["mi_dis"] = person.fields["mi_dis"]
current_record["ma_dis"] = person.fields["ma_dis"]
current_record["notes"] = person.fields["notes"]
current_record["cnotes"] = person.fields["notes_d"]
all_records += list(current_record)
return all_records
/datum/computer_file/program/records/ui_data(mob/user)
var/list/data = get_header_data()
data["records"] = GetRecordsReadable()
data["mode"] = mode
return data
@@ -13,6 +13,8 @@
var/botcount = 0
///Access granted by the used to summon robots.
var/list/current_access = list()
///Whether or not this is the cartridge program version.
var/cart_mode = FALSE
var/list/drone_ping_types = list(
"Low",
"Medium",
@@ -27,20 +29,23 @@
var/list/botlist = list()
var/list/mulelist = list()
var/obj/item/computer_hardware/hard_drive/role/job_disk = computer ? computer.all_components[MC_HDD_JOB] : null
var/obj/item/computer_hardware/card_slot/card_slot = computer ? computer.all_components[MC_CARD] : null
var/obj/item/card/id/id_card = card_slot?.stored_card
data["have_id_slot"] = !!card_slot
if(computer)
data["has_id"] = !!id_card
data["id_owner"] = id_card ? id_card.registered_name : "No Card Inserted."
data["access_on_card"] = id_card ? id_card.access : null
var/obj/item/card/id/id_card = card_slot ? card_slot.stored_card : ""
data["id_owner"] = id_card
if(cart_mode && job_disk)
data["id_owner"] = "JOB DISK OVERRIDE"
botcount = 0
for(var/mob/living/simple_animal/bot/simple_bot as anything in GLOB.bots_list)
if(simple_bot.z != zlevel || !(simple_bot.bot_mode_flags & BOT_MODE_REMOTE_ENABLED)) //Only non-emagged bots on the same Z-level are detected!
continue
if(computer && !simple_bot.check_access(user, id_card)) // Only check Bots we can access
if(computer && !simple_bot.check_access(user) && !cart_mode) // Only check Bots we can access)
continue
if(!(simple_bot.bot_type in job_disk.bot_access) && cart_mode)
continue
var/list/newbot = list(
"name" = simple_bot.name,
@@ -19,14 +19,16 @@
. = ..()
if (!.)
return
if(!computer?.get_modular_computer_part(MC_SIGNALER)) //Giving a clue to users why the program is spitting out zeros.
var/obj/item/computer_hardware/hard_drive/role/signal/disk = computer?.get_modular_computer_part(MC_HDD_JOB)
if(!(computer?.get_modular_computer_part(MC_SIGNALER) || istype(disk))) //Giving a clue to users why the program is spitting out zeros.
to_chat(user, span_warning("\The [computer] flashes an error: \"hardware\\signal_hardware\\startup.bin -- file not found\"."))
/datum/computer_file/program/signaler/ui_data(mob/user)
var/list/data = get_header_data()
var/obj/item/computer_hardware/radio_card/sensor = computer?.get_modular_computer_part(MC_SIGNALER)
if(sensor?.check_functionality())
var/obj/item/computer_hardware/hard_drive/role/signal/disk = computer?.get_modular_computer_part(MC_HDD_JOB)
if(sensor?.check_functionality() || istype(disk))
data["frequency"] = signal_frequency
data["code"] = signal_code
data["minFrequency"] = MIN_FREE_FREQ
@@ -38,7 +40,8 @@
if(.)
return
var/obj/item/computer_hardware/radio_card/sensor = computer?.get_modular_computer_part(MC_SIGNALER)
if(!(sensor?.check_functionality()))
var/obj/item/computer_hardware/hard_drive/role/signal/disk = computer?.get_modular_computer_part(MC_HDD_JOB)
if(!(sensor?.check_functionality() || istype(disk)))
playsound(src, 'sound/machines/scanbuzz.ogg', 100, FALSE)
return
switch(action)
@@ -65,18 +68,19 @@
return
var/time = time2text(world.realtime,"hh:mm:ss")
var/turf/T = get_turf(src)
var/turf/T = get_turf(computer)
var/logging_data
if(usr)
logging_data = "[time] <B>:</B> [usr.key] used [src] @ location ([T.x],[T.y],[T.z]) <B>:</B> [format_frequency(signal_frequency)]/[signal_code]"
message_admins("usr is true")
logging_data = "[time] <B>:</B> [usr.key] used [computer] @ location ([T.x],[T.y],[T.z]) <B>:</B> [format_frequency(signal_frequency)]/[signal_code]"
GLOB.lastsignalers.Add(logging_data)
var/datum/signal/signal = new(list("code" = signal_code), logging_data = logging_data)
radio_connection.post_signal(src, signal)
radio_connection.post_signal(computer, signal)
/datum/computer_file/program/signaler/proc/set_frequency(new_frequency)
SSradio.remove_object(src, signal_frequency)
SSradio.remove_object(computer, signal_frequency)
signal_frequency = new_frequency
radio_connection = SSradio.add_object(src, signal_frequency, RADIO_SIGNALER)
radio_connection = SSradio.add_object(computer, signal_frequency, RADIO_SIGNALER)
return
@@ -0,0 +1,55 @@
/datum/computer_file/program/status
filename = "statusdisplay"
filedesc = "Status Display"
program_icon = "signal"
program_icon_state = "generic"
requires_ntnet = TRUE
size = 4
extended_desc = "An app used to change the message on the station status displays."
tgui_id = "NtosStatus"
usage_flags = PROGRAM_ALL
available_on_ntnet = FALSE
var/upper_text
var/lower_text
/datum/computer_file/program/status/proc/SendSignal()
var/datum/radio_frequency/frequency = SSradio.return_frequency(FREQ_STATUS_DISPLAYS)
if(!frequency)
return
var/datum/signal/status_signal = new(list("command" = "message"))
status_signal.data["msg1"] = reject_bad_text(upper_text || "", MAX_STATUS_LINE_LENGTH)
status_signal.data["msg2"] = reject_bad_text(lower_text || "", MAX_STATUS_LINE_LENGTH)
frequency.post_signal(computer, status_signal)
/datum/computer_file/program/status/proc/SetText(position, text)
switch(position)
if("upper")
upper_text = text
if("lower")
lower_text = text
/datum/computer_file/program/status/ui_act(action, list/params, datum/tgui/ui)
. = ..()
if(.)
return
switch(action)
if("stat_send")
SendSignal()
if("stat_update")
SetText(params["position"], params["text"]) // i hate the player i hate the player
/datum/computer_file/program/status/ui_data(mob/user)
var/list/data = get_header_data()
data["upper"] = upper_text
data["lower"] = lower_text
return data
@@ -7,6 +7,8 @@
device_type = MC_CARD
var/obj/item/card/id/stored_card
var/current_identification = null
var/current_job = null
///What happens when the ID card is removed (or deleted) from the module, through try_eject() or not.
/obj/item/computer_hardware/card_slot/Exited(atom/movable/gone, direction)
@@ -76,6 +78,10 @@
stored_card = I
to_chat(user, span_notice("You insert \the [I] into \the [expansion_hw ? "secondary":"primary"] [src]."))
playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, FALSE)
holder.update_appearance()
current_identification = stored_card.registered_name
current_job = stored_card.assignment
var/holder_loc = holder.loc
if(ishuman(holder_loc))
@@ -99,6 +105,11 @@
to_chat(user, span_notice("You remove the card from \the [src]."))
playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, FALSE)
holder.update_appearance()
stored_card = null
current_identification = null
current_job = null
return TRUE
@@ -9,6 +9,7 @@
var/max_capacity = 128
var/used_capacity = 0
var/list/stored_files = list() // List of stored files on this drive. DO NOT MODIFY DIRECTLY!
var/default_installs = TRUE // install the default progs
/obj/item/computer_hardware/hard_drive/on_remove(obj/item/modular_computer/remove_from, mob/user)
remove_from.shutdown_computer()
@@ -130,7 +131,9 @@
/obj/item/computer_hardware/hard_drive/Initialize(mapload)
. = ..()
install_default_programs()
if(default_installs)
install_default_programs()
/obj/item/computer_hardware/hard_drive/advanced
@@ -167,12 +170,25 @@
w_class = WEIGHT_CLASS_TINY
custom_price = PAYCHECK_MEDIUM * 2
/obj/item/computer_hardware/hard_drive/small/install_default_programs()
. = ..()
store_file(new /datum/computer_file/program/messenger(src))
store_file(new /datum/computer_file/program/notepad(src))
// For borg integrated tablets. No downloader.
/obj/item/computer_hardware/hard_drive/small/integrated/install_default_programs()
var/datum/computer_file/program/messenger/messenger = new(src)
messenger.is_silicon = TRUE
store_file(messenger)
/obj/item/computer_hardware/hard_drive/small/integrated/borg/install_default_programs()
store_file(new /datum/computer_file/program/computerconfig(src)) // Computer configuration utility, allows hardware control and displays more info than status bar
store_file(new /datum/computer_file/program/filemanager(src)) // File manager, allows text editor functions and basic file manipulation.
store_file(new /datum/computer_file/program/robotact(src))
var/datum/computer_file/program/messenger/messenger = new(src)
messenger.is_silicon = TRUE
store_file(messenger)
// Syndicate variant - very slight better
/obj/item/computer_hardware/hard_drive/small/syndicate
@@ -0,0 +1,13 @@
/obj/item/computer_hardware/identifier
name = "identifier"
desc = "Used to automatically update the names of modular devices."
power_usage = 0
w_class = WEIGHT_CLASS_TINY
device_type = MC_IDENTIFY
expansion_hw = FALSE
/obj/item/computer_hardware/identifier/proc/UpdateDisplay()
var/name = holder.saved_identification
var/job = holder.saved_job
holder.name = "[name] ([job])"
@@ -0,0 +1,253 @@
/obj/item/computer_hardware/hard_drive/role
name = "job data disk"
desc = "A disk meant to give a worker the needed programs to work."
power_usage = 0
icon_state = "datadisk6"
w_class = WEIGHT_CLASS_TINY
critical = FALSE
max_capacity = 500
device_type = MC_HDD_JOB
default_installs = FALSE
var/disk_flags = 0 // bit flag for the programs
var/can_spam = FALSE
var/list/bot_access = list()
/obj/item/computer_hardware/hard_drive/role/on_remove(obj/item/modular_computer/remove_from, mob/user)
return
/obj/item/computer_hardware/hard_drive/role/Initialize(mapload)
. = ..()
var/list/progs_to_store = list()
if(disk_flags & DISK_POWER)
progs_to_store += new /datum/computer_file/program/power_monitor(src)
progs_to_store += new /datum/computer_file/program/supermatter_monitor(src)
if(disk_flags & DISK_ATMOS)
progs_to_store += new /datum/computer_file/program/atmosscan(src)
if(disk_flags & DISK_MANIFEST)
progs_to_store += new /datum/computer_file/program/crew_manifest(src)
if(disk_flags & DISK_SEC)
progs_to_store += new /datum/computer_file/program/records/security(src)
if(disk_flags & DISK_JANI)
progs_to_store += new /datum/computer_file/program/radar/custodial_locator(src)
if((disk_flags & DISK_CHEM) || (disk_flags & DISK_MED))
var/datum/computer_file/program/phys_scanner/scanner = new(src)
if(disk_flags & DISK_CHEM)
scanner.available_modes += DISK_CHEM
if(disk_flags & DISK_MED)
progs_to_store += new /datum/computer_file/program/records/medical(src)
scanner.available_modes += DISK_MED
progs_to_store += scanner
if(disk_flags & DISK_ROBOS)
var/datum/computer_file/program/robocontrol/robo = new(src)
robo.cart_mode = TRUE
progs_to_store += robo
if(disk_flags & DISK_CARGO)
progs_to_store += new /datum/computer_file/program/shipping(src)
if(disk_flags & DISK_SIGNAL)
progs_to_store += new /datum/computer_file/program/signaler(src)
if(disk_flags & DISK_NEWS)
progs_to_store += new /datum/computer_file/program/newscaster(src)
if(disk_flags & DISK_BUDGET)
progs_to_store += new /datum/computer_file/program/budgetorders(src)
if(disk_flags & DISK_STATUS)
progs_to_store += new /datum/computer_file/program/status(src)
for (var/datum/computer_file/program/prog in progs_to_store)
prog.usage_flags = PROGRAM_ALL
prog.required_access = list()
prog.transfer_access = list()
store_file(prog)
/obj/item/computer_hardware/hard_drive/role/proc/CanSpam()
return can_spam
// Disk Definitions
/obj/item/computer_hardware/hard_drive/role/engineering
name = "Power-ON disk"
desc = "Engineers ignoring station power-draw since 2400."
icon_state = "datadisk2"
disk_flags = DISK_POWER
/obj/item/computer_hardware/hard_drive/role/atmos
name = "\improper BreatheDeep disk"
icon_state = "datadisk2"
disk_flags = DISK_ATMOS | DISK_ROBOS
bot_access = list(
FLOOR_BOT,
FIRE_BOT,
)
/obj/item/computer_hardware/hard_drive/role/medical
name = "\improper Med-U disk"
icon_state = "datadisk7"
disk_flags = DISK_MED | DISK_ROBOS
bot_access = list(
MED_BOT,
)
/obj/item/computer_hardware/hard_drive/role/chemistry
name = "\improper ChemWhiz disk"
icon_state = "datadisk7"
disk_flags = DISK_CHEM
/obj/item/computer_hardware/hard_drive/role/security
name = "\improper R.O.B.U.S.T. disk"
icon_state = "datadisk9"
disk_flags = DISK_SEC | DISK_MANIFEST | DISK_ROBOS
bot_access = list(
SEC_BOT,
ADVANCED_SEC_BOT,
)
/obj/item/computer_hardware/hard_drive/role/detective
name = "\improper D.E.T.E.C.T. disk"
icon_state = "datadisk9"
disk_flags = DISK_MED | DISK_SEC | DISK_MANIFEST | DISK_ROBOS
bot_access = list(
SEC_BOT,
ADVANCED_SEC_BOT,
)
/obj/item/computer_hardware/hard_drive/role/janitor
name = "\improper CustodiPRO disk"
icon_state = "datadisk5"
desc = "The ultimate in clean-room design."
disk_flags = DISK_JANI | DISK_ROBOS
bot_access = list(
CLEAN_BOT,
)
/obj/item/computer_hardware/hard_drive/role/lawyer
name = "\improper P.R.O.V.E. disk"
icon_state = "datadisk9"
disk_flags = DISK_SEC
can_spam = TRUE
/obj/item/computer_hardware/hard_drive/role/curator
name = "\improper Lib-Tweet disk"
icon_state = "datadisk2"
disk_flags = DISK_NEWS
/obj/item/computer_hardware/hard_drive/role/roboticist
name = "\improper B.O.O.P. Remote Control disk"
icon_state = "datadisk5"
desc = "Packed with heavy duty quad-bot interlink!"
disk_flags = DISK_ROBOS
bot_access = list(
FLOOR_BOT,
CLEAN_BOT,
MED_BOT,
FIRE_BOT,
VIBE_BOT,
)
/obj/item/computer_hardware/hard_drive/role/signal
name = "generic signaler disk"
icon_state = "datadisk5"
desc = "A data disk with an integrated radio signaler module."
disk_flags = DISK_SIGNAL
/obj/item/computer_hardware/hard_drive/role/signal/ordnance
name = "\improper Signal Ace 2 disk"
icon_state = "datadisk5"
desc = "Complete with integrated radio signaler!"
disk_flags = DISK_ATMOS | DISK_SIGNAL | DISK_CHEM
/obj/item/computer_hardware/hard_drive/role/quartermaster
name = "space parts & space vendors disk"
icon_state = "datadisk0"
desc = "Perfect for the Quartermaster on the go!"
disk_flags = DISK_CARGO | DISK_ROBOS | DISK_BUDGET
bot_access = list(
MULE_BOT,
)
/obj/item/computer_hardware/hard_drive/role/head
name = "\improper Easy-Record DELUXE disk"
icon_state = "datadisk7"
disk_flags = DISK_MANIFEST | DISK_STATUS | DISK_BUDGET
/obj/item/computer_hardware/hard_drive/role/hop
name = "\improper HumanResources9001 disk"
icon_state = "datadisk7"
disk_flags = DISK_MANIFEST | DISK_STATUS | DISK_JANI | DISK_SEC | DISK_NEWS | DISK_CARGO | DISK_ROBOS | DISK_BUDGET
bot_access = list(
MULE_BOT,
CLEAN_BOT,
VIBE_BOT,
)
/obj/item/computer_hardware/hard_drive/role/hos
name = "\improper R.O.B.U.S.T. DELUXE disk"
icon_state = "datadisk7"
disk_flags = DISK_MANIFEST | DISK_STATUS | DISK_SEC | DISK_ROBOS | DISK_BUDGET
bot_access = list(
SEC_BOT,
ADVANCED_SEC_BOT,
)
/obj/item/computer_hardware/hard_drive/role/ce
name = "\improper Power-On DELUXE disk"
icon_state = "datadisk7"
disk_flags = DISK_POWER | DISK_ATMOS | DISK_MANIFEST | DISK_STATUS | DISK_ROBOS | DISK_BUDGET
bot_access = list(
FLOOR_BOT,
FIRE_BOT,
)
/obj/item/computer_hardware/hard_drive/role/cmo
name = "\improper Med-U DELUXE disk"
icon_state = "datadisk7"
disk_flags = DISK_MANIFEST | DISK_STATUS | DISK_CHEM | DISK_ROBOS | DISK_BUDGET
bot_access = list(
MED_BOT,
)
/obj/item/computer_hardware/hard_drive/role/rd
name = "\improper Signal Ace DELUXE disk"
icon_state = "rndmajordisk"
disk_flags = DISK_ATMOS | DISK_MANIFEST | DISK_STATUS | DISK_CHEM | DISK_ROBOS | DISK_BUDGET | DISK_SIGNAL
bot_access = list(
FLOOR_BOT,
CLEAN_BOT,
MED_BOT,
FIRE_BOT,
VIBE_BOT,
)
/obj/item/computer_hardware/hard_drive/role/captain
name = "\improper Value-PAK disk"
icon_state = "datadisk8"
desc = "Now with 350% more value!" //Give the Captain...EVERYTHING! (Except Mime, Clown, and Syndie)
disk_flags = ~0
can_spam = TRUE
bot_access = list(
SEC_BOT,
ADVANCED_SEC_BOT,
MULE_BOT,
FLOOR_BOT,
CLEAN_BOT,
MED_BOT,
FIRE_BOT,
VIBE_BOT,
)
@@ -88,10 +88,12 @@
if(!modularInterface.borgo)
return FALSE //No borg found
if(modularInterface.borgo.lockcharge)
return FALSE //lockdown restricts borg networking
var/mob/living/silicon/robot/robo = modularInterface.borgo
if(istype(robo))
if(robo.lockcharge)
return FALSE //lockdown restricts borg networking
if(!modularInterface.borgo.cell || modularInterface.borgo.cell.charge == 0)
return FALSE //borg cell dying restricts borg networking
if(!robo.cell || robo.cell.charge == 0)
return FALSE //borg cell dying restricts borg networking
return ..()
@@ -0,0 +1,140 @@
/obj/item/computer_hardware/hard_drive/role/virus
name = "\improper generic virus disk"
icon_state = "virusdisk"
var/charges = 5
/obj/item/computer_hardware/hard_drive/role/virus/proc/send_virus(obj/item/modular_computer/tablet/target, mob/living/user)
return
/obj/item/computer_hardware/hard_drive/role/virus/clown
name = "\improper H.O.N.K. disk"
/obj/item/computer_hardware/hard_drive/role/virus/clown/send_virus(obj/item/modular_computer/tablet/target, mob/living/user)
if(charges <= 0)
to_chat(user, span_notice("ERROR: Out of charges."))
return
if(target)
user.show_message(span_notice("Success!"))
charges--
target.honkamnt = rand(15, 25)
else
to_chat(user, span_notice("ERROR: Could not find device."))
/obj/item/computer_hardware/hard_drive/role/virus/mime
name = "\improper sound of silence disk"
/obj/item/computer_hardware/hard_drive/role/virus/mime/send_virus(obj/item/modular_computer/tablet/target, mob/living/user)
if(charges <= 0)
to_chat(user, span_notice("ERROR: Out of charges."))
return
if(target)
user.show_message(span_notice("Success!"))
charges--
var/obj/item/computer_hardware/hard_drive/drive = target.all_components[MC_HDD]
for(var/datum/computer_file/program/messenger/app in drive.stored_files)
app.ringer_status = FALSE
app.ringtone = ""
else
to_chat(user, span_notice("ERROR: Could not find device."))
/obj/item/computer_hardware/hard_drive/role/virus/deto
name = "\improper D.E.T.O.M.A.T.I.X. disk"
charges = 6
/obj/item/computer_hardware/hard_drive/role/virus/deto/send_virus(obj/item/modular_computer/tablet/target, mob/living/user)
if(charges <= 0)
to_chat(user, span_notice("ERROR: Out of charges."))
return
var/difficulty = 0
var/obj/item/computer_hardware/hard_drive/role/disk = target.all_components[MC_HDD_JOB]
if(disk)
difficulty += bit_count(disk.disk_flags & (DISK_MED | DISK_SEC | DISK_POWER | DISK_MANIFEST))
if(disk.disk_flags & DISK_MANIFEST)
difficulty++ //if cartridge has manifest access it has extra snowflake difficulty
if(SEND_SIGNAL(target, COMSIG_TABLET_CHECK_DETONATE) & COMPONENT_TABLET_NO_DETONATE || prob(difficulty * 15))
user.show_message(span_danger("ERROR: Target could not be bombed."), MSG_VISUAL)
charges--
return
var/original_host = holder
var/fakename = sanitize_name(tgui_input_text(user, "Enter a name for the rigged message.", "Forge Message", max_length = MAX_NAME_LEN), allow_numbers = TRUE)
if(!fakename || holder != original_host || !user.canUseTopic(holder, BE_CLOSE))
return
var/fakejob = sanitize_name(tgui_input_text(user, "Enter a job for the rigged message.", "Forge Message", max_length = MAX_NAME_LEN), allow_numbers = TRUE)
if(!fakejob || holder != original_host || !user.canUseTopic(holder, BE_CLOSE))
return
var/obj/item/computer_hardware/hard_drive/drive = holder.all_components[MC_HDD]
for(var/datum/computer_file/program/messenger/app in drive.stored_files)
if(charges > 0 && app.send_message(user, list(target), rigged = REF(user), fake_name = fakename, fake_job = fakejob))
charges--
user.show_message(span_notice("Success!"))
var/reference = REF(src)
ADD_TRAIT(target, TRAIT_PDA_CAN_EXPLODE, reference)
ADD_TRAIT(target, TRAIT_PDA_MESSAGE_MENU_RIGGED, reference)
addtimer(TRAIT_CALLBACK_REMOVE(target, TRAIT_PDA_MESSAGE_MENU_RIGGED, reference), 10 SECONDS)
/obj/item/computer_hardware/hard_drive/role/virus/frame
name = "\improper F.R.A.M.E. disk"
var/telecrystals = 0
var/current_progression = 0
/obj/item/computer_hardware/hard_drive/role/virus/frame/send_virus(obj/item/modular_computer/tablet/target, mob/living/user)
if(charges <= 0)
to_chat(user, span_notice("ERROR: Out of charges."))
return
if(target)
charges--
var/lock_code = "[rand(100,999)] [pick(GLOB.phonetic_alphabet)]"
to_chat(user, span_notice("Success! The unlock code to the target is: [lock_code]"))
var/datum/component/uplink/hidden_uplink = target.GetComponent(/datum/component/uplink)
if(!hidden_uplink)
var/datum/mind/target_mind
var/list/backup_players = list()
for(var/datum/mind/player as anything in get_crewmember_minds())
if(player.assigned_role?.title == target.saved_job)
backup_players += player
if(player.name == target.saved_identification)
target_mind = player
break
if(!target_mind)
if(!length(backup_players))
target_mind = user.mind
else
target_mind = pick(backup_players)
hidden_uplink = target.AddComponent(/datum/component/uplink, target_mind, enabled = TRUE, starting_tc = telecrystals, has_progression = TRUE)
hidden_uplink.uplink_handler.has_objectives = TRUE
hidden_uplink.uplink_handler.owner = target_mind
hidden_uplink.uplink_handler.can_take_objectives = FALSE
hidden_uplink.uplink_handler.progression_points = min(SStraitor.current_global_progression, current_progression)
hidden_uplink.uplink_handler.generate_objectives()
SStraitor.register_uplink_handler(hidden_uplink.uplink_handler)
else
hidden_uplink.add_telecrystals(telecrystals)
telecrystals = 0
hidden_uplink.locked = FALSE
hidden_uplink.active = TRUE
else
to_chat(user, span_notice("ERROR: Could not find device."))
/obj/item/computer_hardware/hard_drive/role/virus/frame/attackby(obj/item/I, mob/user, params)
. = ..()
if(istype(I, /obj/item/stack/telecrystal))
if(!charges)
to_chat(user, span_notice("[src] is out of charges, it's refusing to accept [I]."))
return
var/obj/item/stack/telecrystal/telecrystalStack = I
telecrystals += telecrystalStack.amount
to_chat(user, span_notice("You slot [telecrystalStack] into [src]. The next time it's used, it will also give telecrystals."))
telecrystalStack.use(telecrystalStack.amount)