TGUI NTOS - Total Conversion

This commit is contained in:
ShadowLarkens
2020-09-05 16:09:15 -07:00
parent d8c7593395
commit dd4f9c9967
166 changed files with 6765 additions and 4894 deletions
@@ -14,51 +14,4 @@
size = 4
available_on_ntnet = 0
requires_ntnet = 0
nanomodule_path = /datum/nano_module/program/computer_configurator/
/datum/nano_module/program/computer_configurator
name = "NTOS Computer Configuration Tool"
var/obj/item/modular_computer/movable = null
/datum/nano_module/program/computer_configurator/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1, var/datum/topic_state/state = default_state)
if(program)
movable = program.computer
if(!istype(movable))
movable = null
// No computer connection, we can't get data from that.
if(!movable)
return 0
var/list/data = list()
if(program)
data = program.get_header_data()
var/list/hardware = movable.get_all_components()
data["disk_size"] = movable.hard_drive.max_capacity
data["disk_used"] = movable.hard_drive.used_capacity
data["power_usage"] = movable.last_power_usage
data["battery_exists"] = movable.battery_module ? 1 : 0
if(movable.battery_module)
data["battery_rating"] = movable.battery_module.battery.maxcharge
data["battery_percent"] = round(movable.battery_module.battery.percent())
var/list/all_entries[0]
for(var/obj/item/weapon/computer_hardware/H in hardware)
all_entries.Add(list(list(
"name" = H.name,
"desc" = H.desc,
"enabled" = H.enabled,
"critical" = H.critical,
"powerusage" = H.power_usage
)))
data["hardware"] = all_entries
ui = SSnanoui.try_update_ui(user, src, ui_key, ui, data, force_open)
if (!ui)
ui = new(user, src, ui_key, "laptop_configuration.tmpl", "NTOS Configuration Utility", 575, 700, state = state)
ui.auto_update_layout = 1
ui.set_initial_data(data)
ui.open()
tguimodule_path = /datum/tgui_module/computer_configurator
@@ -11,15 +11,15 @@
var/stored_login = ""
var/stored_password = ""
nanomodule_path = /datum/nano_module/email_client
tguimodule_path = /datum/tgui_module/email_client
// Persistency. Unless you log out, or unless your password changes, this will pre-fill the login data when restarting the program
/datum/computer_file/program/email_client/kill_program()
if(NM)
var/datum/nano_module/email_client/NME = NM
if(NME.current_account)
stored_login = NME.stored_login
stored_password = NME.stored_password
if(TM)
var/datum/tgui_module/email_client/TME = TM
if(TME.current_account)
stored_login = TME.stored_login
stored_password = TME.stored_password
else
stored_login = ""
stored_password = ""
@@ -27,473 +27,31 @@
/datum/computer_file/program/email_client/run_program()
. = ..()
if(NM)
var/datum/nano_module/email_client/NME = NM
NME.stored_login = stored_login
NME.stored_password = stored_password
NME.log_in()
NME.error = ""
NME.check_for_new_messages(1)
if(TM)
var/datum/tgui_module/email_client/TME = TM
TME.stored_login = stored_login
TME.stored_password = stored_password
TME.log_in()
TME.error = ""
TME.check_for_new_messages(1)
/datum/computer_file/program/email_client/proc/new_mail_notify()
computer.visible_message("\The [computer] beeps softly, indicating a new email has been received.", 1)
var/turf/T = get_turf(computer) // Because visible_message is being a butt
if(T)
T.visible_message("<span class='notice'>[computer] beeps softly, indicating a new email has been received.</span>")
playsound(computer, 'sound/misc/server-ready.ogg', 100, 0)
/datum/computer_file/program/email_client/process_tick()
..()
var/datum/nano_module/email_client/NME = NM
if(!istype(NME))
var/datum/tgui_module/email_client/TME = TM
if(!istype(TME))
return
NME.relayed_process(ntnet_speed)
TME.relayed_process(ntnet_speed)
var/check_count = NME.check_for_new_messages()
var/check_count = TME.check_for_new_messages()
if(check_count)
if(check_count == 2)
new_mail_notify()
ui_header = "ntnrc_new.gif"
else
ui_header = "ntnrc_idle.gif"
/datum/nano_module/email_client/
name = "Email Client"
var/stored_login = ""
var/stored_password = ""
var/error = ""
var/msg_title = ""
var/msg_body = ""
var/msg_recipient = ""
var/datum/computer_file/msg_attachment = null
var/folder = "Inbox"
var/addressbook = FALSE
var/new_message = FALSE
var/last_message_count = 0 // How many messages were there during last check.
var/read_message_count = 0 // How many messages were there when user has last accessed the UI.
var/datum/computer_file/downloading = null
var/download_progress = 0
var/download_speed = 0
var/datum/computer_file/data/email_account/current_account = null
var/datum/computer_file/data/email_message/current_message = null
/datum/nano_module/email_client/proc/log_in()
for(var/datum/computer_file/data/email_account/account in ntnet_global.email_accounts)
if(!account.can_login)
continue
if(account.login == stored_login)
if(account.password == stored_password)
if(account.suspended)
error = "This account has been suspended. Please contact the system administrator for assistance."
return 0
current_account = account
return 1
else
error = "Invalid Password"
return 0
error = "Invalid Login"
return 0
// Returns 0 if no new messages were received, 1 if there is an unread message but notification has already been sent.
// and 2 if there is a new message that appeared in this tick (and therefore notification should be sent by the program).
/datum/nano_module/email_client/proc/check_for_new_messages(var/messages_read = FALSE)
if(!current_account)
return 0
var/list/allmails = current_account.all_emails()
if(allmails.len > last_message_count)
. = 2
else if(allmails.len > read_message_count)
. = 1
else
. = 0
last_message_count = allmails.len
if(messages_read)
read_message_count = allmails.len
/datum/nano_module/email_client/proc/log_out()
current_account = null
downloading = null
download_progress = 0
last_message_count = 0
read_message_count = 0
/datum/nano_module/email_client/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1, var/datum/topic_state/state = default_state)
var/list/data = host.initial_data()
// Password has been changed by other client connected to this email account
if(current_account)
if(current_account.password != stored_password)
log_out()
error = "Invalid Password"
// Banned.
if(current_account.suspended)
log_out()
error = "This account has been suspended. Please contact the system administrator for assistance."
if(error)
data["error"] = error
else if(downloading)
data["downloading"] = 1
data["down_filename"] = "[downloading.filename].[downloading.filetype]"
data["down_progress"] = download_progress
data["down_size"] = downloading.size
data["down_speed"] = download_speed
else if(istype(current_account))
data["current_account"] = current_account.login
if(addressbook)
var/list/all_accounts = list()
for(var/datum/computer_file/data/email_account/account in ntnet_global.email_accounts)
if(!account.can_login)
continue
all_accounts.Add(list(list(
"login" = account.login
)))
data["addressbook"] = 1
data["accounts"] = all_accounts
else if(new_message)
data["new_message"] = 1
data["msg_title"] = msg_title
data["msg_body"] = pencode2html(msg_body)
data["msg_recipient"] = msg_recipient
if(msg_attachment)
data["msg_hasattachment"] = 1
data["msg_attachment_filename"] = "[msg_attachment.filename].[msg_attachment.filetype]"
data["msg_attachment_size"] = msg_attachment.size
else if (current_message)
data["cur_title"] = current_message.title
data["cur_body"] = pencode2html(current_message.stored_data)
data["cur_timestamp"] = current_message.timestamp
data["cur_source"] = current_message.source
data["cur_uid"] = current_message.uid
if(istype(current_message.attachment))
data["cur_hasattachment"] = 1
data["cur_attachment_filename"] = "[current_message.attachment.filename].[current_message.attachment.filetype]"
data["cur_attachment_size"] = current_message.attachment.size
else
data["label_inbox"] = "Inbox ([current_account.inbox.len])"
data["label_spam"] = "Spam ([current_account.spam.len])"
data["label_deleted"] = "Deleted ([current_account.deleted.len])"
var/list/message_source
if(folder == "Inbox")
message_source = current_account.inbox
else if(folder == "Spam")
message_source = current_account.spam
else if(folder == "Deleted")
message_source = current_account.deleted
if(message_source)
data["folder"] = folder
var/list/all_messages = list()
for(var/datum/computer_file/data/email_message/message in message_source)
all_messages.Add(list(list(
"title" = message.title,
"body" = pencode2html(message.stored_data),
"source" = message.source,
"timestamp" = message.timestamp,
"uid" = message.uid
)))
data["messages"] = all_messages
data["messagecount"] = all_messages.len
else
data["stored_login"] = stored_login
data["stored_password"] = stars(stored_password, 0)
ui = SSnanoui.try_update_ui(user, src, ui_key, ui, data, force_open)
if (!ui)
ui = new(user, src, ui_key, "email_client.tmpl", "Email Client", 600, 450, state = state)
if(host.update_layout())
ui.auto_update_layout = 1
ui.set_auto_update(1)
ui.set_initial_data(data)
ui.open()
/datum/nano_module/email_client/proc/find_message_by_fuid(var/fuid)
if(!istype(current_account))
return
// href_list works with strings, so this makes it a bit easier for us
if(istext(fuid))
fuid = text2num(fuid)
for(var/datum/computer_file/data/email_message/message in current_account.all_emails())
if(message.uid == fuid)
return message
/datum/nano_module/email_client/proc/clear_message()
new_message = FALSE
msg_title = ""
msg_body = ""
msg_recipient = ""
msg_attachment = null
current_message = null
/datum/nano_module/email_client/proc/relayed_process(var/netspeed)
download_speed = netspeed
if(!downloading)
return
download_progress = min(download_progress + netspeed, downloading.size)
if(download_progress >= downloading.size)
var/obj/item/modular_computer/MC = nano_host()
if(!istype(MC) || !MC.hard_drive || !MC.hard_drive.check_functionality())
error = "Error uploading file. Are you using a functional and NTOSv2-compliant device?"
downloading = null
download_progress = 0
return 1
if(MC.hard_drive.store_file(downloading))
error = "File successfully downloaded to local device."
else
error = "Error saving file: I/O Error: The hard drive may be full or nonfunctional."
downloading = null
download_progress = 0
return 1
/datum/nano_module/email_client/Topic(href, href_list)
if(..())
return 1
var/mob/living/user = usr
check_for_new_messages(1) // Any actual interaction (button pressing) is considered as acknowledging received message, for the purpose of notification icons.
if(href_list["login"])
log_in()
return 1
if(href_list["logout"])
log_out()
return 1
if(href_list["reset"])
error = ""
return 1
if(href_list["new_message"])
new_message = TRUE
return 1
if(href_list["cancel"])
if(addressbook)
addressbook = FALSE
else
clear_message()
return 1
if(href_list["addressbook"])
addressbook = TRUE
return 1
if(href_list["set_recipient"])
msg_recipient = sanitize(href_list["set_recipient"])
addressbook = FALSE
return 1
if(href_list["edit_title"])
var/newtitle = sanitize(input(user,"Enter title for your message:", "Message title", msg_title), 100)
if(newtitle)
msg_title = newtitle
return 1
// This uses similar editing mechanism as the FileManager program, therefore it supports various paper tags and remembers formatting.
if(href_list["edit_body"])
var/oldtext = html_decode(msg_body)
oldtext = replacetext(oldtext, "\[editorbr\]", "\n")
var/newtext = sanitize(replacetext(input(usr, "Enter your message. You may use most tags from paper formatting", "Message Editor", oldtext) as message|null, "\n", "\[editorbr\]"), 20000)
if(newtext)
msg_body = newtext
return 1
if(href_list["edit_recipient"])
var/newrecipient = sanitize(input(user,"Enter recipient's email address:", "Recipient", msg_recipient), 100)
if(newrecipient)
msg_recipient = newrecipient
return 1
if(href_list["edit_login"])
var/newlogin = sanitize(input(user,"Enter login", "Login", stored_login), 100)
if(newlogin)
stored_login = newlogin
return 1
if(href_list["edit_password"])
var/newpass = sanitize(input(user,"Enter password", "Password"), 100)
if(newpass)
stored_password = newpass
return 1
if(href_list["delete"])
if(!istype(current_account))
return 1
var/datum/computer_file/data/email_message/M = find_message_by_fuid(href_list["delete"])
if(!istype(M))
return 1
if(folder == "Deleted")
current_account.deleted.Remove(M)
qdel(M)
else
current_account.deleted.Add(M)
current_account.inbox.Remove(M)
current_account.spam.Remove(M)
if(current_message == M)
current_message = null
return 1
if(href_list["send"])
if(!current_account)
return 1
if((msg_title == "") || (msg_body == "") || (msg_recipient == ""))
error = "Error sending mail: Title or message body is empty!"
return 1
var/datum/computer_file/data/email_message/message = new()
message.title = msg_title
message.stored_data = msg_body
message.source = current_account.login
message.attachment = msg_attachment
if(!current_account.send_mail(msg_recipient, message))
error = "Error sending email: this address doesn't exist."
return 1
else
error = "Email successfully sent."
clear_message()
return 1
if(href_list["set_folder"])
folder = href_list["set_folder"]
return 1
if(href_list["reply"])
var/datum/computer_file/data/email_message/M = find_message_by_fuid(href_list["reply"])
if(!istype(M))
return 1
new_message = TRUE
msg_recipient = M.source
msg_title = "Re: [M.title]"
msg_body = "\[editorbr\]\[editorbr\]\[editorbr\]\[br\]==============================\[br\]\[editorbr\]"
msg_body += "Received by [current_account.login] at [M.timestamp]\[br\]\[editorbr\][M.stored_data]"
return 1
if(href_list["view"])
var/datum/computer_file/data/email_message/M = find_message_by_fuid(href_list["view"])
if(istype(M))
current_message = M
return 1
if(href_list["changepassword"])
var/oldpassword = sanitize(input(user,"Please enter your old password:", "Password Change"), 100)
if(!oldpassword)
return 1
var/newpassword1 = sanitize(input(user,"Please enter your new password:", "Password Change"), 100)
if(!newpassword1)
return 1
var/newpassword2 = sanitize(input(user,"Please re-enter your new password:", "Password Change"), 100)
if(!newpassword2)
return 1
if(!istype(current_account))
error = "Please log in before proceeding."
return 1
if(current_account.password != oldpassword)
error = "Incorrect original password"
return 1
if(newpassword1 != newpassword2)
error = "The entered passwords do not match."
return 1
current_account.password = newpassword1
stored_password = newpassword1
error = "Your password has been successfully changed!"
return 1
// The following entries are Modular Computer framework only, and therefore won't do anything in other cases (like AI View)
if(href_list["save"])
// Fully dependant on modular computers here.
var/obj/item/modular_computer/MC = nano_host()
if(!istype(MC) || !MC.hard_drive || !MC.hard_drive.check_functionality())
error = "Error exporting file. Are you using a functional and NTOS-compliant device?"
return 1
var/filename = sanitize(input(user,"Please specify file name:", "Message export"), 100)
if(!filename)
return 1
var/datum/computer_file/data/email_message/M = find_message_by_fuid(href_list["save"])
var/datum/computer_file/data/mail = istype(M) ? M.export() : null
if(!istype(mail))
return 1
mail.filename = filename
if(!MC.hard_drive || !MC.hard_drive.store_file(mail))
error = "Internal I/O error when writing file, the hard drive may be full."
else
error = "Email exported successfully"
return 1
if(href_list["addattachment"])
var/obj/item/modular_computer/MC = nano_host()
msg_attachment = null
if(!istype(MC) || !MC.hard_drive || !MC.hard_drive.check_functionality())
error = "Error uploading file. Are you using a functional and NTOSv2-compliant device?"
return 1
var/list/filenames = list()
for(var/datum/computer_file/CF in MC.hard_drive.stored_files)
if(CF.unsendable)
continue
filenames.Add(CF.filename)
var/picked_file = input(user, "Please pick a file to send as attachment (max 32GQ)") as null|anything in filenames
if(!picked_file)
return 1
if(!istype(MC) || !MC.hard_drive || !MC.hard_drive.check_functionality())
error = "Error uploading file. Are you using a functional and NTOSv2-compliant device?"
return 1
for(var/datum/computer_file/CF in MC.hard_drive.stored_files)
if(CF.unsendable)
continue
if(CF.filename == picked_file)
msg_attachment = CF.clone()
break
if(!istype(msg_attachment))
msg_attachment = null
error = "Unknown error when uploading attachment."
return 1
if(msg_attachment.size > 32)
error = "Error uploading attachment: File exceeds maximal permitted file size of 32GQ."
msg_attachment = null
else
error = "File [msg_attachment.filename].[msg_attachment.filetype] has been successfully uploaded."
return 1
if(href_list["downloadattachment"])
if(!current_account || !current_message || !current_message.attachment)
return 1
var/obj/item/modular_computer/MC = nano_host()
if(!istype(MC) || !MC.hard_drive || !MC.hard_drive.check_functionality())
error = "Error downloading file. Are you using a functional and NTOSv2-compliant device?"
return 1
downloading = current_message.attachment.clone()
download_progress = 0
return 1
if(href_list["canceldownload"])
downloading = null
download_progress = 0
return 1
if(href_list["remove_attachment"])
msg_attachment = null
return 1
@@ -6,202 +6,190 @@
program_key_state = "generic_key"
program_menu_icon = "folder-collapsed"
size = 8
requires_ntnet = 0
available_on_ntnet = 0
undeletable = 1
nanomodule_path = /datum/nano_module/program/computer_filemanager/
requires_ntnet = FALSE
available_on_ntnet = FALSE
undeletable = TRUE
tgui_id = "NtosFileManager"
var/open_file
var/error
/datum/computer_file/program/filemanager/Topic(href, href_list)
/datum/computer_file/program/filemanager/tgui_act(action, list/params, datum/tgui/ui)
if(..())
return 1
return TRUE
if(href_list["PRG_openfile"])
. = 1
open_file = href_list["PRG_openfile"]
if(href_list["PRG_newtextfile"])
. = 1
var/newname = sanitize(input(usr, "Enter file name or leave blank to cancel:", "File rename"))
if(!newname)
return 1
var/obj/item/weapon/computer_hardware/hard_drive/HDD = computer.hard_drive
if(!HDD)
return 1
var/datum/computer_file/data/F = new/datum/computer_file/data()
F.filename = newname
F.filetype = "TXT"
HDD.store_file(F)
if(href_list["PRG_deletefile"])
. = 1
var/obj/item/weapon/computer_hardware/hard_drive/HDD = computer.hard_drive
if(!HDD)
return 1
var/datum/computer_file/file = HDD.find_file_by_name(href_list["PRG_deletefile"])
if(!file || file.undeletable)
return 1
HDD.remove_file(file)
if(href_list["PRG_usbdeletefile"])
. = 1
var/obj/item/weapon/computer_hardware/hard_drive/RHDD = computer.portable_drive
if(!RHDD)
return 1
var/datum/computer_file/file = RHDD.find_file_by_name(href_list["PRG_usbdeletefile"])
if(!file || file.undeletable)
return 1
RHDD.remove_file(file)
if(href_list["PRG_closefile"])
. = 1
open_file = null
error = null
if(href_list["PRG_clone"])
. = 1
var/obj/item/weapon/computer_hardware/hard_drive/HDD = computer.hard_drive
if(!HDD)
return 1
var/datum/computer_file/F = HDD.find_file_by_name(href_list["PRG_clone"])
if(!F || !istype(F))
return 1
var/datum/computer_file/C = F.clone(1)
HDD.store_file(C)
if(href_list["PRG_rename"])
. = 1
var/obj/item/weapon/computer_hardware/hard_drive/HDD = computer.hard_drive
if(!HDD)
return 1
var/datum/computer_file/file = HDD.find_file_by_name(href_list["PRG_rename"])
if(!file || !istype(file))
return 1
var/newname = sanitize(input(usr, "Enter new file name:", "File rename", file.filename))
if(file && newname)
var/obj/item/weapon/computer_hardware/hard_drive/HDD = computer.hard_drive
var/obj/item/weapon/computer_hardware/hard_drive/RHDD = computer.portable_drive
switch(action)
if("PRG_openfile")
open_file = params["name"]
return TRUE
if("PRG_newtextfile")
if(!HDD)
return
var/newname = sanitize(input(usr, "Enter file name or leave blank to cancel:", "File rename"))
if(!newname)
return
var/datum/computer_file/data/F = new/datum/computer_file/data()
F.filename = newname
F.filetype = "TXT"
HDD.store_file(F)
return TRUE
if("PRG_closefile")
open_file = null
error = null
return TRUE
if("PRG_clone")
if(!HDD)
return
var/datum/computer_file/F = HDD.find_file_by_name(params["name"])
if(!F || !istype(F))
return
var/datum/computer_file/C = F.clone(1)
HDD.store_file(C)
return TRUE
if("PRG_edit")
if(!HDD)
return
if(!open_file)
return
var/datum/computer_file/data/F = HDD.find_file_by_name(open_file)
if(!F || !istype(F))
return
if(F.do_not_edit && (alert("WARNING: This file is not compatible with editor. Editing it may result in permanently corrupted formatting or damaged data consistency. Edit anyway?", "Incompatible File", "No", "Yes") == "No"))
return
var/oldtext = html_decode(F.stored_data)
oldtext = replacetext(oldtext, "\[br\]", "\n")
var/newtext = sanitize(replacetext(input(usr, "Editing file [open_file]. You may use most tags used in paper formatting:", "Text Editor", oldtext) as message|null, "\n", "\[br\]"), MAX_TEXTFILE_LENGTH)
if(!newtext)
return
if(F)
var/datum/computer_file/data/backup = F.clone()
HDD.remove_file(F)
F.stored_data = newtext
F.calculate_size()
// We can't store the updated file, it's probably too large. Print an error and restore backed up version.
// This is mostly intended to prevent people from losing texts they spent lot of time working on due to running out of space.
// They will be able to copy-paste the text from error screen and store it in notepad or something.
if(!HDD.store_file(F))
error = "I/O error: Unable to overwrite file. Hard drive is probably full. You may want to backup your changes before closing this window:<br><br>[html_decode(F.stored_data)]<br><br>"
HDD.store_file(backup)
return TRUE
if("PRG_printfile")
if(!HDD)
return
if(!open_file)
return
var/datum/computer_file/data/F = HDD.find_file_by_name(open_file)
if(!F || !istype(F))
return
if(!computer.nano_printer)
error = "Missing Hardware: Your computer does not have required hardware to complete this operation."
return
if(!computer.nano_printer.print_text(pencode2html(F.stored_data)))
error = "Hardware error: Printer was unable to print the file. It may be out of paper."
return
return TRUE
if("PRG_deletefile")
if(!HDD)
return
var/datum/computer_file/file = HDD.find_file_by_name(params["name"])
if(!file || file.undeletable)
return
HDD.remove_file(file)
return TRUE
if("PRG_usbdeletefile")
if(!RHDD)
return
var/datum/computer_file/file = RHDD.find_file_by_name(params["name"])
if(!file || file.undeletable)
return
RHDD.remove_file(file)
return TRUE
if("PRG_rename")
if(!HDD)
return
var/datum/computer_file/file = HDD.find_file_by_name(params["name"])
if(!file)
return
var/newname = params["new_name"]
if(!newname)
return
file.filename = newname
if(href_list["PRG_edit"])
. = 1
if(!open_file)
return 1
var/obj/item/weapon/computer_hardware/hard_drive/HDD = computer.hard_drive
if(!HDD)
return 1
var/datum/computer_file/data/F = HDD.find_file_by_name(open_file)
if(!F || !istype(F))
return 1
if(F.do_not_edit && (alert("WARNING: This file is not compatible with editor. Editing it may result in permanently corrupted formatting or damaged data consistency. Edit anyway?", "Incompatible File", "No", "Yes") == "No"))
return 1
return TRUE
if("PRG_copytousb")
if(!HDD || !RHDD)
return
var/datum/computer_file/F = HDD.find_file_by_name(params["name"])
if(!F)
return
var/datum/computer_file/C = F.clone(FALSE)
RHDD.store_file(C)
return TRUE
if("PRG_copyfromusb")
if(!HDD || !RHDD)
return
var/datum/computer_file/F = RHDD.find_file_by_name(params["name"])
if(!F || !istype(F))
return
var/datum/computer_file/C = F.clone(FALSE)
HDD.store_file(C)
return TRUE
var/oldtext = html_decode(F.stored_data)
oldtext = replacetext(oldtext, "\[br\]", "\n")
/datum/computer_file/program/filemanager/tgui_data(mob/user)
var/list/data = get_header_data()
var/newtext = sanitize(replacetext(input(usr, "Editing file [open_file]. You may use most tags used in paper formatting:", "Text Editor", oldtext) as message|null, "\n", "\[br\]"), MAX_TEXTFILE_LENGTH)
if(!newtext)
return
var/obj/item/weapon/computer_hardware/hard_drive/HDD = computer.hard_drive
var/obj/item/weapon/computer_hardware/hard_drive/portable/RHDD = computer.portable_drive
if(F)
var/datum/computer_file/data/backup = F.clone()
HDD.remove_file(F)
F.stored_data = newtext
F.calculate_size()
// We can't store the updated file, it's probably too large. Print an error and restore backed up version.
// This is mostly intended to prevent people from losing texts they spent lot of time working on due to running out of space.
// They will be able to copy-paste the text from error screen and store it in notepad or something.
if(!HDD.store_file(F))
error = "I/O error: Unable to overwrite file. Hard drive is probably full. You may want to backup your changes before closing this window:<br><br>[html_decode(F.stored_data)]<br><br>"
HDD.store_file(backup)
if(href_list["PRG_printfile"])
. = 1
if(!open_file)
return 1
var/obj/item/weapon/computer_hardware/hard_drive/HDD = computer.hard_drive
if(!HDD)
return 1
var/datum/computer_file/data/F = HDD.find_file_by_name(open_file)
if(!F || !istype(F))
return 1
if(!computer.nano_printer)
error = "Missing Hardware: Your computer does not have required hardware to complete this operation."
return 1
if(!computer.nano_printer.print_text(pencode2html(F.stored_data)))
error = "Hardware error: Printer was unable to print the file. It may be out of paper."
return 1
if(href_list["PRG_copytousb"])
. = 1
var/obj/item/weapon/computer_hardware/hard_drive/HDD = computer.hard_drive
var/obj/item/weapon/computer_hardware/hard_drive/portable/RHDD = computer.portable_drive
if(!HDD || !RHDD)
return 1
var/datum/computer_file/F = HDD.find_file_by_name(href_list["PRG_copytousb"])
if(!F || !istype(F))
return 1
var/datum/computer_file/C = F.clone(0)
RHDD.store_file(C)
if(href_list["PRG_copyfromusb"])
. = 1
var/obj/item/weapon/computer_hardware/hard_drive/HDD = computer.hard_drive
var/obj/item/weapon/computer_hardware/hard_drive/portable/RHDD = computer.portable_drive
if(!HDD || !RHDD)
return 1
var/datum/computer_file/F = RHDD.find_file_by_name(href_list["PRG_copyfromusb"])
if(!F || !istype(F))
return 1
var/datum/computer_file/C = F.clone(0)
HDD.store_file(C)
if(.)
SSnanoui.update_uis(NM)
data["error"] = null
if(error)
data["error"] = error
if(!computer || !HDD)
data["error"] = "I/O ERROR: Unable to access hard drive."
data["filedata"] = null
data["filename"] = null
data["files"] = list()
data["usbconnected"] = FALSE
data["usbfiles"] = list()
/datum/nano_module/program/computer_filemanager
name = "NTOS File Manager"
/datum/nano_module/program/computer_filemanager/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1, var/datum/topic_state/state = default_state)
var/list/data = host.initial_data()
var/datum/computer_file/program/filemanager/PRG
PRG = program
var/obj/item/weapon/computer_hardware/hard_drive/HDD
var/obj/item/weapon/computer_hardware/hard_drive/portable/RHDD
if(PRG.error)
data["error"] = PRG.error
if(PRG.open_file)
if(open_file)
var/datum/computer_file/data/file
if(!PRG.computer || !PRG.computer.hard_drive)
if(!computer || !computer.hard_drive)
data["error"] = "I/O ERROR: Unable to access hard drive."
else
HDD = PRG.computer.hard_drive
file = HDD.find_file_by_name(PRG.open_file)
file = HDD.find_file_by_name(open_file)
if(!istype(file))
data["error"] = "I/O ERROR: Unable to open file."
else
data["filedata"] = pencode2html(file.stored_data)
data["filename"] = "[file.filename].[file.filetype]"
else
if(!PRG.computer || !PRG.computer.hard_drive)
data["error"] = "I/O ERROR: Unable to access hard drive."
else
HDD = PRG.computer.hard_drive
RHDD = PRG.computer.portable_drive
var/list/files[0]
for(var/datum/computer_file/F in HDD.stored_files)
files.Add(list(list(
var/list/files = list()
for(var/datum/computer_file/F in HDD.stored_files)
files += list(list(
"name" = F.filename,
"type" = F.filetype,
"size" = F.size,
"undeletable" = F.undeletable
))
data["files"] = files
if(RHDD)
data["usbconnected"] = TRUE
var/list/usbfiles = list()
for(var/datum/computer_file/F in RHDD.stored_files)
usbfiles += list(list(
"name" = F.filename,
"type" = F.filetype,
"size" = F.size,
"undeletable" = F.undeletable
)))
data["files"] = files
if(RHDD)
data["usbconnected"] = 1
var/list/usbfiles[0]
for(var/datum/computer_file/F in RHDD.stored_files)
usbfiles.Add(list(list(
"name" = F.filename,
"type" = F.filetype,
"size" = F.size,
"undeletable" = F.undeletable
)))
data["usbfiles"] = usbfiles
))
data["usbfiles"] = usbfiles
ui = SSnanoui.try_update_ui(user, src, ui_key, ui, data, force_open)
if (!ui)
ui = new(user, src, ui_key, "file_manager.tmpl", "NTOS File Manager", 575, 700, state = state)
ui.auto_update_layout = 1
ui.set_initial_data(data)
ui.open()
return data
@@ -1,152 +1,188 @@
// This file is used as a reference for Modular Computers Development guide on the wiki. It contains a lot of excess comments, as it is intended as explanation
// for someone who may not be as experienced in coding. When making changes, please try to keep it this way.
// An actual program definition.
/datum/computer_file/program/game
filename = "arcadec" // File name, as shown in the file browser program.
filedesc = "Unknown Game" // User-Friendly name. In this case, we will generate a random name in constructor.
program_icon_state = "game" // Icon state of this program's screen.
program_menu_icon = "script"
extended_desc = "Fun for the whole family! Probably not an AAA title, but at least you can download it on the corporate network.." // A nice description.
size = 5 // Size in GQ. Integers only. Smaller sizes should be used for utility/low use programs (like this one), while large sizes are for important programs.
requires_ntnet = 0 // This particular program does not require NTNet network conectivity...
available_on_ntnet = 1 // ... but we want it to be available for download.
nanomodule_path = /datum/nano_module/arcade_classic/ // Path of relevant nano module. The nano module is defined further in the file.
var/picked_enemy_name
filename = "dsarcade" // File name, as shown in the file browser program.
filedesc = "Donksoft Micro Arcade" // User-Friendly name.
program_icon_state = "arcade" // Icon state of this program's screen.
extended_desc = "This is a port of the classic game 'Outbomb Cuban Pete', redesigned to run on tablets; Now with thrilling graphics and chilling storytelling." // A nice description.
size = 6 // Size in GQ. Integers only. Smaller sizes should be used for utility/low use programs (like this one), while large sizes are for important programs.
requires_ntnet = FALSE // This particular program does not require NTNet network conectivity...
available_on_ntnet = TRUE // ... but we want it to be available for download.
tgui_id = "NtosArcade" // Path of relevant tgui template.js file.
// Blatantly stolen and shortened version from arcade machines. Generates a random enemy name
/datum/computer_file/program/game/proc/random_enemy_name()
var/name_part1 = pick("the Automatic ", "Farmer ", "Lord ", "Professor ", "the Cuban ", "the Evil ", "the Dread King ", "the Space ", "Lord ", "the Great ", "Duke ", "General ")
var/name_part2 = pick("Melonoid", "Murdertron", "Sorcerer", "Ruin", "Jeff", "Ectoplasm", "Crushulon", "Uhangoid", "Vhakoid", "Peteoid", "Slime", "Lizard Man", "Unicorn")
return "[name_part1] [name_part2]"
///Returns TRUE if the game is being played.
var/game_active = TRUE
///This disables buttom actions from having any impact if TRUE. Resets to FALSE when the player is allowed to make an action again.
var/pause_state = FALSE
var/boss_hp = 45
var/boss_mp = 15
var/player_hp = 30
var/player_mp = 10
var/ticket_count = 0
///Shows what text is shown on the app, usually showing the log of combat actions taken by the player.
var/heads_up = "Nanotrasen says, winners make us money."
var/boss_name = "Cuban Pete's Minion"
///Determines which boss image to use on the UI.
var/boss_id = 1
// When the program is first created, we generate a new enemy name and name ourselves accordingly.
/datum/computer_file/program/game/New()
..()
picked_enemy_name = random_enemy_name()
filedesc = "Defeat [picked_enemy_name]"
// This is the primary game loop, which handles the logic of being defeated or winning.
/datum/computer_file/program/game/proc/game_check(mob/user)
sleep(5)
if(boss_hp <= 0)
heads_up = "You have crushed [boss_name]! Rejoice!"
playsound(computer.loc, 'sound/arcade/win.ogg', 50, TRUE, extrarange = -3, falloff = 10)
game_active = FALSE
program_icon_state = "arcade_off"
if(istype(computer))
computer.update_icon()
ticket_count += 1
// user?.mind?.adjust_experience(/datum/skill/gaming, 50)
sleep(10)
else if(player_hp <= 0 || player_mp <= 0)
heads_up = "You have been defeated... how will the station survive?"
playsound(computer.loc, 'sound/arcade/lose.ogg', 50, TRUE, extrarange = -3, falloff = 10)
game_active = FALSE
program_icon_state = "arcade_off"
if(istype(computer))
computer.update_icon()
// user?.mind?.adjust_experience(/datum/skill/gaming, 10)
sleep(10)
// Important in order to ensure that copied versions will have the same enemy name.
/datum/computer_file/program/game/clone()
var/datum/computer_file/program/game/G = ..()
G.picked_enemy_name = picked_enemy_name
return G
// When running the program, we also want to pass our enemy name to the nano module.
/datum/computer_file/program/game/run_program()
. = ..()
if(. && NM)
var/datum/nano_module/arcade_classic/NMC = NM
NMC.enemy_name = picked_enemy_name
// Nano module the program uses.
// This can be either /datum/nano_module/ or /datum/nano_module/program. The latter is intended for nano modules that are suposed to be exclusively used with modular computers,
// and should generally not be used, as such nano modules are hard to use on other places.
/datum/nano_module/arcade_classic/
name = "Classic Arcade"
var/player_mana // Various variables specific to the nano module. In this case, the nano module is a simple arcade game, so the variables store health and other stats.
var/player_health
var/enemy_mana
var/enemy_health
var/enemy_name = "Greytide Horde"
var/gameover
var/information
/datum/nano_module/arcade_classic/New()
..()
new_game()
// ui_interact handles transfer of data to NanoUI. Keep in mind that data you pass from here is actually sent to the client. In other words, don't send anything you don't want a client
// to see, and don't send unnecessarily large amounts of data (due to laginess).
/datum/nano_module/arcade_classic/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1, var/datum/topic_state/state = default_state)
var/list/data = host.initial_data()
data["player_health"] = player_health
data["player_mana"] = player_mana
data["enemy_health"] = enemy_health
data["enemy_mana"] = enemy_mana
data["enemy_name"] = enemy_name
data["gameover"] = gameover
data["information"] = information
ui = SSnanoui.try_update_ui(user, src, ui_key, ui, data, force_open)
if (!ui)
ui = new(user, src, ui_key, "arcade_classic.tmpl", "Defeat [enemy_name]", 500, 350, state = state)
if(host.update_layout())
ui.auto_update_layout = 1
ui.set_initial_data(data)
ui.open()
// Three helper procs i've created. These are unique to this particular nano module. If you are creating your own nano module, you'll most likely create similar procs too.
/datum/nano_module/arcade_classic/proc/enemy_play()
if((enemy_mana < 5) && prob(60))
var/steal = rand(2, 3)
player_mana -= steal
enemy_mana += steal
information += "[enemy_name] steals [steal] of your power!"
else if((enemy_health < 15) && (enemy_mana > 3) && prob(80))
var/healamt = min(rand(3, 5), enemy_mana)
enemy_mana -= healamt
enemy_health += healamt
information += "[enemy_name] heals for [healamt] health!"
// This handles the boss "AI".
/datum/computer_file/program/game/proc/enemy_check(mob/user)
var/boss_attackamt = 0 //Spam protection from boss attacks as well.
var/boss_mpamt = 0
var/bossheal = 0
if(pause_state == TRUE)
boss_attackamt = rand(3,6)
boss_mpamt = rand (2,4)
bossheal = rand (4,6)
if(game_active == FALSE)
return
if(boss_mp <= 5)
heads_up = "[boss_mpamt] magic power has been stolen from you!"
playsound(computer.loc, 'sound/arcade/steal.ogg', 50, TRUE, extrarange = -3, falloff = 10)
player_mp -= boss_mpamt
boss_mp += boss_mpamt
else if(boss_mp > 5 && boss_hp <12)
heads_up = "[boss_name] heals for [bossheal] health!"
playsound(computer.loc, 'sound/arcade/heal.ogg', 50, TRUE, extrarange = -3, falloff = 10)
boss_hp += bossheal
boss_mp -= boss_mpamt
else
var/dam = rand(3,6)
player_health -= dam
information += "[enemy_name] attacks for [dam] damage!"
heads_up = "[boss_name] attacks you for [boss_attackamt] damage!"
playsound(computer.loc, 'sound/arcade/hit.ogg', 50, TRUE, extrarange = -3, falloff = 10)
player_hp -= boss_attackamt
/datum/nano_module/arcade_classic/proc/check_gameover()
if((player_health <= 0) || player_mana <= 0)
if(enemy_health <= 0)
information += "You have defeated [enemy_name], but you have died in the fight!"
else
information += "You have been defeated by [enemy_name]!"
gameover = 1
pause_state = FALSE
game_check()
/**
* UI assets define a list of asset datums to be sent with the UI.
* In this case, it's a bunch of cute enemy sprites.
*/
/datum/computer_file/program/game/ui_assets(mob/user)
return list(
get_asset_datum(/datum/asset/simple/arcade),
)
/**
* This provides all of the relevant data to the UI in a list().
*/
/datum/computer_file/program/game/tgui_data(mob/user)
var/list/data = get_header_data()
data["Hitpoints"] = boss_hp
data["PlayerHitpoints"] = player_hp
data["PlayerMP"] = player_mp
data["TicketCount"] = ticket_count
data["GameActive"] = game_active
data["PauseState"] = pause_state
data["Status"] = heads_up
data["BossID"] = "boss[boss_id].gif"
return data
/**
* This is tgui's replacement for Topic(). It handles any user input from the UI.
*/
/datum/computer_file/program/game/tgui_act(action, list/params)
if(..()) // Always call parent in tgui_act, it handles making sure the user is allowed to interact with the UI.
return TRUE
else if(enemy_health <= 0)
gameover = 1
information += "Congratulations! You have defeated [enemy_name]!"
return TRUE
return FALSE
/datum/nano_module/arcade_classic/proc/new_game()
player_mana = 10
player_health = 30
enemy_mana = 20
enemy_health = 45
gameover = FALSE
information = "A new game has started!"
var/obj/item/weapon/computer_hardware/nano_printer/printer
if(computer)
printer = computer.nano_printer
/datum/nano_module/arcade_classic/Topic(href, href_list)
if(..()) // Always begin your Topic() calls with a parent call!
return 1
if(href_list["new_game"])
new_game()
return 1 // Returning 1 (TRUE) in Topic automatically handles UI updates.
if(gameover) // If the game has already ended, we don't want the following three topic calls to be processed at all.
return 1 // Instead of adding checks into each of those three, we can easily add this one check here to reduce on code copy-paste.
if(href_list["attack"])
var/damage = rand(2, 6)
information = "You attack for [damage] damage."
enemy_health -= damage
enemy_play()
check_gameover()
return 1
if(href_list["heal"])
var/healfor = rand(6, 8)
var/cost = rand(1, 3)
information = "You heal yourself for [healfor] damage, using [cost] energy in the process."
player_health += healfor
player_mana -= cost
enemy_play()
check_gameover()
return 1
if(href_list["regain_mana"])
var/regen = rand(4, 7)
information = "You rest of a while, regaining [regen] energy."
player_mana += regen
enemy_play()
check_gameover()
return 1
// var/gamerSkillLevel = usr.mind?.get_skill_level(/datum/skill/gaming)
// var/gamerSkill = usr.mind?.get_skill_modifier(/datum/skill/gaming, SKILL_RANDS_MODIFIER)
switch(action)
if("Attack")
var/attackamt = 0 //Spam prevention.
if(pause_state == FALSE)
attackamt = rand(2,6) // + rand(0, gamerSkill)
pause_state = TRUE
heads_up = "You attack for [attackamt] damage."
playsound(computer.loc, 'sound/arcade/hit.ogg', 50, TRUE, extrarange = -3, falloff = 10)
boss_hp -= attackamt
sleep(10)
game_check()
enemy_check()
return TRUE
if("Heal")
var/healamt = 0 //More Spam Prevention.
var/healcost = 0
if(pause_state == FALSE)
healamt = rand(6,8) // + rand(0, gamerSkill)
var/maxPointCost = 3
// if(gamerSkillLevel >= SKILL_LEVEL_JOURNEYMAN)
// maxPointCost = 2
healcost = rand(1, maxPointCost)
pause_state = TRUE
heads_up = "You heal for [healamt] damage."
playsound(computer.loc, 'sound/arcade/heal.ogg', 50, TRUE, extrarange = -3, falloff = 10)
player_hp += healamt
player_mp -= healcost
sleep(10)
game_check()
enemy_check()
return TRUE
if("Recharge_Power")
var/rechargeamt = 0 //As above.
if(pause_state == FALSE)
rechargeamt = rand(4,7) // + rand(0, gamerSkill)
pause_state = TRUE
heads_up = "You regain [rechargeamt] magic power."
playsound(computer.loc, 'sound/arcade/mana.ogg', 50, TRUE, extrarange = -3, falloff = 10)
player_mp += rechargeamt
sleep(10)
game_check()
enemy_check()
return TRUE
if("Dispense_Tickets")
if(!printer)
to_chat(usr, "<span class='notice'>Hardware error: A printer is required to redeem tickets.</span>")
return
if(printer.stored_paper <= 0)
to_chat(usr, "<span class='notice'>Hardware error: Printer is out of paper.</span>")
return
else
computer.visible_message("<span class='notice'>\The [computer] prints out paper.</span>")
if(ticket_count >= 1)
new /obj/item/stack/arcadeticket((get_turf(computer)), 1)
to_chat(usr, "<span class='notice'>[src] dispenses a ticket!</span>")
ticket_count -= 1
printer.stored_paper -= 1
else
to_chat(usr, "<span class='notice'>You don't have any stored tickets!</span>")
return TRUE
if("Start_Game")
game_active = TRUE
boss_hp = 45
player_hp = 30
player_mp = 10
heads_up = "You stand before [boss_name]! Prepare for battle!"
program_icon_state = "arcade"
boss_id = rand(1,6)
pause_state = FALSE
if(istype(computer))
computer.update_icon()
@@ -6,16 +6,17 @@
program_key_state = "generic_key"
program_menu_icon = "contact"
size = 4
requires_ntnet = 1
available_on_ntnet = 1
requires_ntnet = TRUE
available_on_ntnet = TRUE
tgui_id = "NtosNewsBrowser"
nanomodule_path = /datum/nano_module/program/computer_newsbrowser/
var/datum/computer_file/data/news_article/loaded_article
var/download_progress = 0
var/download_netspeed = 0
var/downloading = 0
var/downloading = FALSE
var/message = ""
var/show_archived = 0
var/show_archived = FALSE
/datum/computer_file/program/newsbrowser/process_tick()
if(!downloading)
@@ -33,86 +34,31 @@
if(download_progress >= loaded_article.size)
downloading = 0
requires_ntnet = 0 // Turn off NTNet requirement as we already loaded the file into local memory.
SSnanoui.update_uis(NM)
SStgui.update_uis(src)
/datum/computer_file/program/newsbrowser/kill_program()
..()
requires_ntnet = 1
loaded_article = null
download_progress = 0
downloading = 0
show_archived = 0
/datum/computer_file/program/newsbrowser/tgui_data(mob/user, datum/tgui/ui, datum/tgui_state/state)
var/list/data = get_header_data()
/datum/computer_file/program/newsbrowser/Topic(href, href_list)
if(..())
return 1
if(href_list["PRG_openarticle"])
. = 1
if(downloading || loaded_article)
return 1
for(var/datum/computer_file/data/news_article/N in ntnet_global.available_news)
if(N.uid == text2num(href_list["PRG_openarticle"]))
loaded_article = N.clone()
downloading = 1
break
if(href_list["PRG_reset"])
. = 1
downloading = 0
download_progress = 0
requires_ntnet = 1
loaded_article = null
if(href_list["PRG_clearmessage"])
. = 1
message = ""
if(href_list["PRG_savearticle"])
. = 1
if(downloading || !loaded_article)
return
var/savename = sanitize(input(usr, "Enter file name or leave blank to cancel:", "Save article", loaded_article.filename))
if(!savename)
return 1
var/obj/item/weapon/computer_hardware/hard_drive/HDD = computer.hard_drive
if(!HDD)
return 1
var/datum/computer_file/data/news_article/N = loaded_article.clone()
N.filename = savename
HDD.store_file(N)
if(href_list["PRG_toggle_archived"])
. = 1
show_archived = !show_archived
if(.)
SSnanoui.update_uis(NM)
/datum/nano_module/program/computer_newsbrowser
name = "NTNet/ExoNet News Browser"
/datum/nano_module/program/computer_newsbrowser/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1, var/datum/topic_state/state = default_state)
var/datum/computer_file/program/newsbrowser/PRG
var/list/data = list()
if(program)
data = program.get_header_data()
PRG = program
else
return
data["message"] = PRG.message
if(PRG.loaded_article && !PRG.downloading) // Viewing an article.
data["title"] = PRG.loaded_article.filename
data["cover"] = PRG.loaded_article.cover
data["article"] = PRG.loaded_article.stored_data
else if(PRG.downloading) // Downloading an article.
data["download_running"] = 1
data["download_progress"] = PRG.download_progress
data["download_maxprogress"] = PRG.loaded_article.size
data["download_rate"] = PRG.download_netspeed
var/list/all_articles = list()
data["message"] = message
data["showing_archived"] = show_archived
data["download"] = null
data["article"] = null
if(loaded_article && !downloading) // Viewing an article.
data["article"] = list(
"title" = loaded_article.filename,
"cover" = loaded_article.cover,
"content" = loaded_article.stored_data,
)
else if(downloading) // Downloading an article.
data["download"] = list(
"download_progress" = download_progress,
"download_maxprogress" = loaded_article.size,
"download_rate" = download_netspeed
)
else // Viewing list of articles
var/list/all_articles[0]
for(var/datum/computer_file/data/news_article/F in ntnet_global.available_news)
if(!PRG.show_archived && F.archived)
if(!show_archived && F.archived)
continue
all_articles.Add(list(list(
"name" = F.filename,
@@ -120,12 +66,56 @@
"uid" = F.uid,
"archived" = F.archived
)))
data["all_articles"] = all_articles
data["showing_archived"] = PRG.show_archived
data["all_articles"] = all_articles
return data
/datum/computer_file/program/newsbrowser/kill_program()
..()
requires_ntnet = TRUE
loaded_article = null
download_progress = 0
downloading = FALSE
show_archived = FALSE
/datum/computer_file/program/newsbrowser/tgui_act(action, list/params, datum/tgui/ui)
if(..())
return TRUE
switch(action)
if("PRG_openarticle")
. = TRUE
if(downloading || loaded_article)
return TRUE
for(var/datum/computer_file/data/news_article/N in ntnet_global.available_news)
if(N.uid == text2num(params["uid"]))
loaded_article = N.clone()
downloading = 1
break
if("PRG_reset")
. = TRUE
downloading = 0
download_progress = 0
requires_ntnet = 1
loaded_article = null
if("PRG_clearmessage")
. = TRUE
message = ""
if("PRG_savearticle")
. = TRUE
if(downloading || !loaded_article)
return
var/savename = sanitize(input(usr, "Enter file name or leave blank to cancel:", "Save article", loaded_article.filename))
if(!savename)
return TRUE
var/obj/item/weapon/computer_hardware/hard_drive/HDD = computer.hard_drive
if(!HDD)
return TRUE
var/datum/computer_file/data/news_article/N = loaded_article.clone()
N.filename = savename
HDD.store_file(N)
if("PRG_toggle_archived")
. = TRUE
show_archived = !show_archived
ui = SSnanoui.try_update_ui(user, src, ui_key, ui, data, force_open)
if (!ui)
ui = new(user, src, ui_key, "news_browser.tmpl", "NTNet/ExoNet News Browser", 575, 750, state = state)
ui.auto_update_layout = 1
ui.set_initial_data(data)
ui.open()
@@ -5,29 +5,26 @@
program_key_state = "generic_key"
program_menu_icon = "arrowthickstop-1-s"
extended_desc = "This program allows downloads of software from official NT repositories"
unsendable = 1
undeletable = 1
unsendable = TRUE
undeletable = TRUE
size = 4
requires_ntnet = 1
requires_ntnet = TRUE
requires_ntnet_feature = NTNET_SOFTWAREDOWNLOAD
available_on_ntnet = 0
nanomodule_path = /datum/nano_module/program/computer_ntnetdownload/
available_on_ntnet = FALSE
ui_header = "downloader_finished.gif"
tgui_id = "NtosNetDownloader"
var/datum/computer_file/program/downloaded_file = null
var/hacked_download = 0
var/download_completion = 0 //GQ of downloaded data.
var/download_netspeed = 0
var/downloaderror = ""
var/obj/item/modular_computer/my_computer = null
var/list/downloads_queue[0]
/datum/computer_file/program/ntnetdownload/kill_program()
..()
downloaded_file = null
download_completion = 0
download_netspeed = 0
downloaderror = ""
ui_header = "downloader_finished.gif"
abort_file_download()
/datum/computer_file/program/ntnetdownload/proc/begin_file_download(var/filename)
if(downloaded_file)
@@ -108,93 +105,85 @@
download_netspeed = NTNETSPEED_ETHERNET
download_completion += download_netspeed
/datum/computer_file/program/ntnetdownload/Topic(href, href_list)
/datum/computer_file/program/ntnetdownload/tgui_act(action, params)
if(..())
return 1
if(href_list["PRG_downloadfile"])
if(!downloaded_file)
begin_file_download(href_list["PRG_downloadfile"])
else if(check_file_download(href_list["PRG_downloadfile"]) && !downloads_queue.Find(href_list["PRG_downloadfile"]) && downloaded_file.filename != href_list["PRG_downloadfile"])
downloads_queue += href_list["PRG_downloadfile"]
return 1
if(href_list["PRG_removequeued"])
downloads_queue.Remove(href_list["PRG_removequeued"])
return 1
if(href_list["PRG_reseterror"])
if(downloaderror)
download_completion = 0
download_netspeed = 0
downloaded_file = null
downloaderror = ""
return 1
return 0
/datum/nano_module/program/computer_ntnetdownload
name = "Network Downloader"
var/obj/item/modular_computer/my_computer = null
/datum/nano_module/program/computer_ntnetdownload/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1, var/datum/topic_state/state = default_state)
if(program)
my_computer = program.computer
return TRUE
switch(action)
if("PRG_downloadfile")
if(!downloaded_file)
begin_file_download(params["filename"])
else if(check_file_download(params["filename"]) && !downloads_queue.Find(params["filename"]) && downloaded_file.filename != params["filename"])
downloads_queue += params["filename"]
return TRUE
if("PRG_removequeued")
downloads_queue.Remove(params["filename"])
return TRUE
if("PRG_reseterror")
if(downloaderror)
download_completion = 0
download_netspeed = 0
downloaded_file = null
downloaderror = ""
return TRUE
return FALSE
/datum/computer_file/program/ntnetdownload/tgui_data(mob/user)
my_computer = computer
if(!istype(my_computer))
return
var/list/data = list()
var/datum/computer_file/program/ntnetdownload/prog = program
// For now limited to execution by the downloader program
if(!prog || !istype(prog))
return
if(program)
data = program.get_header_data()
var/list/data = get_header_data()
// This IF cuts on data transferred to client, so i guess it's worth it.
if(prog.downloaderror) // Download errored. Wait until user resets the program.
data["error"] = prog.downloaderror
if(prog.downloaded_file) // Download running. Wait please..
data["downloadname"] = prog.downloaded_file.filename
data["downloaddesc"] = prog.downloaded_file.filedesc
data["downloadsize"] = prog.downloaded_file.size
data["downloadspeed"] = prog.download_netspeed
data["downloadcompletion"] = round(prog.download_completion, 0.1)
data["downloading"] = !!downloaded_file
data["error"] = downloaderror || FALSE
if(downloaded_file) // Download running. Wait please..
data["downloadname"] = downloaded_file.filename
data["downloaddesc"] = downloaded_file.filedesc
data["downloadsize"] = downloaded_file.size
data["downloadspeed"] = download_netspeed
data["downloadcompletion"] = round(download_completion, 0.1)
data["disk_size"] = my_computer.hard_drive.max_capacity
data["disk_used"] = my_computer.hard_drive.used_capacity
var/list/all_entries[0]
for(var/datum/computer_file/program/P in ntnet_global.available_station_software)
// Only those programs our user can run will show in the list
if(!P.can_run(user) && P.requires_access_to_download)
if(!P.can_run(user) && P.requires_access_to_download || my_computer.hard_drive.find_file_by_name(P.filename))
continue
all_entries.Add(list(list(
"filename" = P.filename,
"filedesc" = P.filedesc,
"fileinfo" = P.extended_desc,
"size" = P.size,
"icon" = P.program_menu_icon
)))
data["hackedavailable"] = 0
if(prog.computer_emagged) // If we are running on emagged computer we have access to some "bonus" software
var/list/hacked_programs[0]
for(var/datum/computer_file/program/P in ntnet_global.available_antag_software)
data["hackedavailable"] = 1
hacked_programs.Add(list(list(
"filename" = P.filename,
"filedesc" = P.filedesc,
"fileinfo" = P.extended_desc,
"compatibility" = check_compatibility(P),
"size" = P.size,
"icon" = P.program_menu_icon
)))
data["hackedavailable"] = FALSE
if(computer_emagged) // If we are running on emagged computer we have access to some "bonus" software
var/list/hacked_programs[0]
for(var/datum/computer_file/program/P in ntnet_global.available_antag_software)
if(my_computer.hard_drive.find_file_by_name(P.filename))
continue
data["hackedavailable"] = TRUE
hacked_programs.Add(list(list(
"filename" = P.filename,
"filedesc" = P.filedesc,
"fileinfo" = P.extended_desc,
"compatibility" = check_compatibility(P),
"size" = P.size,
"icon" = P.program_menu_icon
)))
data["hacked_programs"] = hacked_programs
data["downloadable_programs"] = all_entries
data["downloads_queue"] = downloads_queue
if(prog.downloads_queue.len > 0)
data["downloads_queue"] = prog.downloads_queue
return data
ui = SSnanoui.try_update_ui(user, src, ui_key, ui, data, force_open)
if (!ui)
ui = new(user, src, ui_key, "ntnet_downloader.tmpl", "NTNet Download Program", 575, 700, state = state)
ui.auto_update_layout = 1
ui.set_initial_data(data)
ui.open()
ui.set_auto_update(1)
/datum/computer_file/program/ntnetdownload/proc/check_compatibility(datum/computer_file/program/P)
var/hardflag = computer.hardware_flag
if(P && P.is_supported_by_hardware(hardflag,0))
return "Compatible"
return "Incompatible!"
@@ -11,222 +11,220 @@
network_destination = "NTNRC server"
ui_header = "ntnrc_idle.gif"
available_on_ntnet = 1
nanomodule_path = /datum/nano_module/program/computer_chatclient/
var/last_message = null // Used to generate the toolbar icon
tgui_id = "NtosNetChat"
var/last_message // Used to generate the toolbar icon
var/username
var/datum/ntnet_conversation/channel = null
var/operator_mode = 0 // Channel operator mode
var/netadmin_mode = 0 // Administrator mode (invisible to other users + bypasses passwords)
var/active_channel
var/list/channel_history = list()
var/operator_mode = FALSE // Channel operator mode
var/netadmin_mode = FALSE // Administrator mode (invisible to other users + bypasses passwords)
/datum/computer_file/program/chatclient/New()
username = "DefaultUser[rand(100, 999)]"
/datum/computer_file/program/chatclient/Topic(href, href_list)
/datum/computer_file/program/chatclient/tgui_act(action, params)
if(..())
return 1
return
if(href_list["PRG_speak"])
. = 1
if(!channel)
return 1
var/mob/living/user = usr
var/message = sanitize(input(user, "Enter message or leave blank to cancel: "), 512)
if(!message || !channel)
return
channel.add_message(message, username)
var/datum/ntnet_conversation/channel = ntnet_global.get_chat_channel_by_id(active_channel)
var/authed = FALSE
if(channel && ((channel.operator == src) || netadmin_mode))
authed = TRUE
switch(action)
if("PRG_speak")
if(!channel || isnull(active_channel))
return
var/message = reject_bad_text(params["message"])
if(!message)
return
if(channel.password && !(src in channel.clients))
if(channel.password == message)
channel.add_client(src)
return TRUE
if(href_list["PRG_joinchannel"])
. = 1
var/datum/ntnet_conversation/C
for(var/datum/ntnet_conversation/chan in ntnet_global.chat_channels)
if(chan.id == text2num(href_list["PRG_joinchannel"]))
C = chan
break
channel.add_message(message, username)
// var/mob/living/user = usr
// user.log_talk(message, LOG_CHAT, tag="as [username] to channel [channel.title]")
return TRUE
if("PRG_joinchannel")
var/new_target = text2num(params["id"])
if(isnull(new_target) || new_target == active_channel)
return
if(!C)
return 1
if(netadmin_mode)
active_channel = new_target // Bypasses normal leave/join and passwords. Technically makes the user invisible to others.
return TRUE
if(netadmin_mode)
channel = C // Bypasses normal leave/join and passwords. Technically makes the user invisible to others.
return 1
if(C.password)
active_channel = new_target
channel = ntnet_global.get_chat_channel_by_id(new_target)
if(!(src in channel.clients) && !channel.password)
channel.add_client(src)
return TRUE
if("PRG_leavechannel")
if(channel)
channel.remove_client(src)
active_channel = null
return TRUE
if("PRG_newchannel")
var/channel_title = reject_bad_text(params["new_channel_name"])
if(!channel_title)
return
var/datum/ntnet_conversation/C = new /datum/ntnet_conversation()
C.add_client(src)
C.operator = src
C.title = channel_title
active_channel = C.id
return TRUE
if("PRG_toggleadmin")
if(netadmin_mode)
netadmin_mode = FALSE
if(channel)
channel.remove_client(src) // We shouldn't be in channel's user list, but just in case...
return TRUE
var/mob/living/user = usr
var/password = sanitize(input(user,"Access Denied. Enter password:"))
if(C && (password == C.password))
C.add_client(src)
channel = C
return 1
C.add_client(src)
channel = C
if(href_list["PRG_leavechannel"])
. = 1
if(channel)
channel.remove_client(src)
channel = null
if(href_list["PRG_newchannel"])
. = 1
var/mob/living/user = usr
var/channel_title = sanitizeSafe(input(user,"Enter channel name or leave blank to cancel:"), 64)
if(!channel_title)
return
var/datum/ntnet_conversation/C = new/datum/ntnet_conversation()
C.add_client(src)
C.operator = src
channel = C
C.title = channel_title
if(href_list["PRG_toggleadmin"])
. = 1
if(netadmin_mode)
netadmin_mode = 0
if(channel)
channel.remove_client(src) // We shouldn't be in channel's user list, but just in case...
channel = null
return 1
var/mob/living/user = usr
if(can_run(usr, 1, access_network))
if(channel)
var/response = alert(user, "Really engage admin-mode? You will be disconnected from your current channel!", "NTNRC Admin mode", "Yes", "No")
if(response == "Yes")
if(channel)
channel.remove_client(src)
channel = null
else
return
netadmin_mode = 1
if(href_list["PRG_changename"])
. = 1
var/mob/living/user = usr
var/newname = sanitize(input(user,"Enter new nickname or leave blank to cancel:"), 20)
if(!newname)
return 1
if(channel)
channel.add_status_message("[username] is now known as [newname].")
username = newname
if(can_run(user, TRUE, access_network))
for(var/C in ntnet_global.chat_channels)
var/datum/ntnet_conversation/chan = C
chan.remove_client(src)
netadmin_mode = TRUE
return TRUE
if("PRG_changename")
var/newname = sanitize(params["new_name"])
if(!newname)
return
for(var/C in ntnet_global.chat_channels)
var/datum/ntnet_conversation/chan = C
if(src in chan.clients)
chan.add_status_message("[username] is now known as [newname].")
username = newname
return TRUE
if("PRG_savelog")
if(!channel)
return
var/logname = stripped_input(params["log_name"])
if(!logname)
return
var/datum/computer_file/data/logfile = new /datum/computer_file/data/logfile()
// Now we will generate HTML-compliant file that can actually be viewed/printed.
logfile.filename = logname
logfile.stored_data = "\[b\]Logfile dump from NTNRC channel [channel.title]\[/b\]\[BR\]"
for(var/logstring in channel.messages)
logfile.stored_data = "[logfile.stored_data][logstring]\[BR\]"
logfile.stored_data = "[logfile.stored_data]\[b\]Logfile dump completed.\[/b\]"
logfile.calculate_size()
if(!computer || !computer.hard_drive || !computer.hard_drive.store_file(logfile))
if(!computer)
// This program shouldn't even be runnable without computer.
CRASH("Var computer is null!")
if(!computer.hard_drive)
computer.visible_message("<span class='warning'>\The [computer] shows an \"I/O Error - Hard drive connection error\" warning.</span>")
else // In 99.9% cases this will mean our HDD is full
computer.visible_message("<span class='warning'>\The [computer] shows an \"I/O Error - Hard drive may be full. Please free some space and try again. Required space: [logfile.size]GQ\" warning.</span>")
return TRUE
if("PRG_renamechannel")
if(!authed)
return
var/newname = reject_bad_text(params["new_name"])
if(!newname || !channel)
return
channel.add_status_message("Channel renamed from [channel.title] to [newname] by operator.")
channel.title = newname
return TRUE
if("PRG_deletechannel")
if(authed)
qdel(channel)
active_channel = null
return TRUE
if("PRG_setpassword")
if(!authed)
return
if(href_list["PRG_savelog"])
. = 1
if(!channel)
return
var/mob/living/user = usr
var/logname = input(user,"Enter desired logfile name (.log) or leave blank to cancel:")
if(!logname || !channel)
return 1
var/datum/computer_file/data/logfile = new/datum/computer_file/data/logfile()
// Now we will generate HTML-compliant file that can actually be viewed/printed.
logfile.filename = logname
logfile.stored_data = "\[b\]Logfile dump from NTNRC channel [channel.title]\[/b\]\[BR\]"
for(var/logstring in channel.messages)
logfile.stored_data += "[logstring]\[BR\]"
logfile.stored_data += "\[b\]Logfile dump completed.\[/b\]"
logfile.calculate_size()
if(!computer || !computer.hard_drive || !computer.hard_drive.store_file(logfile))
if(!computer)
// This program shouldn't even be runnable without computer.
CRASH("Var computer is null!")
return 1
if(!computer.hard_drive)
computer.visible_message("\The [computer] shows an \"I/O Error - Hard drive connection error\" warning.")
else // In 99.9% cases this will mean our HDD is full
computer.visible_message("\The [computer] shows an \"I/O Error - Hard drive may be full. Please free some space and try again. Required space: [logfile.size]GQ\" warning.")
if(href_list["PRG_renamechannel"])
. = 1
if(!operator_mode || !channel)
return 1
var/mob/living/user = usr
var/newname = sanitize(input(user, "Enter new channel name or leave blank to cancel:"), 64)
if(!newname || !channel)
return
channel.add_status_message("Channel renamed from [channel.title] to [newname] by operator.")
channel.title = newname
if(href_list["PRG_deletechannel"])
. = 1
if(channel && ((channel.operator == src) || netadmin_mode))
qdel(channel)
channel = null
if(href_list["PRG_setpassword"])
. = 1
if(!channel || ((channel.operator != src) && !netadmin_mode))
return 1
var/new_password = sanitize(params["new_password"])
if(!authed)
return
var/mob/living/user = usr
var/newpassword = sanitize(input(user, "Enter new password for this channel. Leave blank to cancel, enter 'nopassword' to remove password completely:"))
if(!channel || !newpassword || ((channel.operator != src) && !netadmin_mode))
return 1
if(newpassword == "nopassword")
channel.password = ""
else
channel.password = newpassword
channel.password = new_password
return TRUE
/datum/computer_file/program/chatclient/process_tick()
..()
var/datum/ntnet_conversation/channel = ntnet_global.get_chat_channel_by_id(active_channel)
if(program_state != PROGRAM_STATE_KILLED)
ui_header = "ntnrc_idle.gif"
if(channel)
// Remember the last message. If there is no message in the channel remember null.
last_message = channel.messages.len ? channel.messages[channel.messages.len - 1] : null
last_message = length(channel.messages) ? channel.messages[channel.messages.len - 1] : null
else
last_message = null
return 1
if(channel && channel.messages && channel.messages.len)
if(channel?.messages?.len)
ui_header = last_message == channel.messages[channel.messages.len - 1] ? "ntnrc_idle.gif" : "ntnrc_new.gif"
else
ui_header = "ntnrc_idle.gif"
/datum/computer_file/program/chatclient/kill_program(var/forced = 0)
if(channel)
/datum/computer_file/program/chatclient/kill_program(forced = FALSE)
for(var/C in ntnet_global.chat_channels)
var/datum/ntnet_conversation/channel = C
channel.remove_client(src)
channel = null
..(forced)
/datum/nano_module/program/computer_chatclient
name = "NTNet Relay Chat Client"
/datum/nano_module/program/computer_chatclient/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1, var/datum/topic_state/state = default_state)
if(!ntnet_global || !ntnet_global.chat_channels)
return
..()
/datum/computer_file/program/chatclient/tgui_static_data(mob/user)
var/list/data = list()
if(program)
data = program.get_header_data()
data["can_admin"] = can_run(user, FALSE, access_network)
return data
/datum/computer_file/program/chatclient/tgui_data(mob/user)
if(!ntnet_global || !ntnet_global.chat_channels)
return list()
var/datum/computer_file/program/chatclient/C = program
if(!istype(C))
return
var/list/data = get_header_data()
data["adminmode"] = C.netadmin_mode
if(C.channel)
data["title"] = C.channel.title
var/list/messages[0]
for(var/M in C.channel.messages)
messages.Add(list(list(
"msg" = M
var/list/all_channels = list()
for(var/C in ntnet_global.chat_channels)
var/datum/ntnet_conversation/conv = C
if(conv && conv.title)
all_channels.Add(list(list(
"chan" = conv.title,
"id" = conv.id
)))
data["messages"] = messages
var/list/clients[0]
for(var/datum/computer_file/program/chatclient/cl in C.channel.clients)
data["all_channels"] = all_channels
data["active_channel"] = active_channel
data["username"] = username
data["adminmode"] = netadmin_mode
var/datum/ntnet_conversation/channel = ntnet_global.get_chat_channel_by_id(active_channel)
if(channel)
data["title"] = channel.title
var/authed = FALSE
if(!channel.password)
authed = TRUE
if(netadmin_mode)
authed = TRUE
var/list/clients = list()
for(var/C in channel.clients)
if(C == src)
authed = TRUE
var/datum/computer_file/program/chatclient/cl = C
clients.Add(list(list(
"name" = cl.username
)))
data["clients"] = clients
C.operator_mode = (C.channel.operator == C) ? 1 : 0
data["is_operator"] = C.operator_mode || C.netadmin_mode
else // Channel selection screen
var/list/all_channels[0]
for(var/datum/ntnet_conversation/conv in ntnet_global.chat_channels)
if(conv && conv.title)
all_channels.Add(list(list(
"chan" = conv.title,
"id" = conv.id
data["authed"] = authed
//no fishing for ui data allowed
if(authed)
data["clients"] = clients
var/list/messages = list()
for(var/M in channel.messages)
messages.Add(list(list(
"msg" = M
)))
data["all_channels"] = all_channels
data["messages"] = messages
data["is_operator"] = (channel.operator == src) || netadmin_mode
else
data["clients"] = list()
data["messages"] = list()
else
data["clients"] = list()
data["authed"] = FALSE
data["messages"] = list()
ui = SSnanoui.try_update_ui(user, src, ui_key, ui, data, force_open)
if (!ui)
ui = new(user, src, ui_key, "ntnet_chat.tmpl", "NTNet Relay Chat Client", 575, 700, state = state)
ui.auto_update_layout = 1
ui.set_initial_data(data)
ui.open()
ui.set_auto_update(1)
return data
@@ -12,7 +12,7 @@ var/global/nttransfer_uid = 0
requires_ntnet_feature = NTNET_PEERTOPEER
network_destination = "other device via P2P tunnel"
available_on_ntnet = 1
nanomodule_path = /datum/nano_module/program/computer_nttransfer/
tgui_id = "NtosNetTransfer"
var/error = "" // Error screen
var/server_password = "" // Optional password to download the file.
@@ -75,111 +75,101 @@ var/global/nttransfer_uid = 0
remote = null
download_completion = 0
/datum/computer_file/program/nttransfer/tgui_data(mob/user, datum/tgui/ui, datum/tgui_state/state)
var/list/data = get_header_data()
/datum/nano_module/program/computer_nttransfer
name = "NTNet P2P Transfer Client"
data["error"] = error
/datum/nano_module/program/computer_nttransfer/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1, var/datum/topic_state/state = default_state)
if(!program)
return
var/datum/computer_file/program/nttransfer/PRG = program
if(!istype(PRG))
return
var/list/data = program.get_header_data()
if(PRG.error)
data["error"] = PRG.error
else if(PRG.downloaded_file)
data["downloading"] = 1
data["download_size"] = PRG.downloaded_file.size
data["download_progress"] = PRG.download_completion
data["download_netspeed"] = PRG.actual_netspeed
data["download_name"] = "[PRG.downloaded_file.filename].[PRG.downloaded_file.filetype]"
else if (PRG.provided_file)
data["uploading"] = 1
data["upload_uid"] = PRG.unique_token
data["upload_clients"] = PRG.connected_clients.len
data["upload_haspassword"] = PRG.server_password ? 1 : 0
data["upload_filename"] = "[PRG.provided_file.filename].[PRG.provided_file.filetype]"
else if (PRG.upload_menu)
var/list/all_files[0]
for(var/datum/computer_file/F in PRG.computer.hard_drive.stored_files)
data["downloading"] = !!downloaded_file
if(downloaded_file)
data["download_size"] = downloaded_file.size
data["download_progress"] = download_completion
data["download_netspeed"] = actual_netspeed
data["download_name"] = "[downloaded_file.filename].[downloaded_file.filetype]"
data["uploading"] = !!provided_file
if(provided_file)
data["upload_uid"] = unique_token
data["upload_clients"] = connected_clients.len
data["upload_haspassword"] = server_password ? 1 : 0
data["upload_filename"] = "[provided_file.filename].[provided_file.filetype]"
data["upload_filelist"] = list()
if(upload_menu)
var/list/all_files = list()
for(var/datum/computer_file/F in computer.hard_drive.stored_files)
all_files.Add(list(list(
"uid" = F.uid,
"filename" = "[F.filename].[F.filetype]",
"size" = F.size
)))
data["upload_filelist"] = all_files
else
var/list/all_servers[0]
data["servers"] = list()
if(!(downloaded_file || provided_file || upload_menu))
var/list/all_servers = list()
for(var/datum/computer_file/program/nttransfer/P in ntnet_global.fileservers)
if(!P.provided_file)
continue
all_servers.Add(list(list(
"uid" = P.unique_token,
"filename" = "[P.provided_file.filename].[P.provided_file.filetype]",
"size" = P.provided_file.size,
"haspassword" = P.server_password ? 1 : 0
"uid" = P.unique_token,
"filename" = "[P.provided_file.filename].[P.provided_file.filetype]",
"size" = P.provided_file.size,
"haspassword" = P.server_password ? 1 : 0
)))
data["servers"] = all_servers
ui = SSnanoui.try_update_ui(user, src, ui_key, ui, data, force_open)
if (!ui)
ui = new(user, src, ui_key, "ntnet_transfer.tmpl", "NTNet P2P Transfer Client", 575, 700, state = state)
ui.auto_update_layout = 1
ui.set_initial_data(data)
ui.open()
ui.set_auto_update(1)
return data
/datum/computer_file/program/nttransfer/Topic(href, href_list)
/datum/computer_file/program/nttransfer/tgui_act(action, list/params, datum/tgui/ui)
if(..())
return 1
if(href_list["PRG_downloadfile"])
for(var/datum/computer_file/program/nttransfer/P in ntnet_global.fileservers)
if("[P.unique_token]" == href_list["PRG_downloadfile"])
remote = P
break
if(!remote || !remote.provided_file)
return
if(remote.server_password)
var/pass = sanitize(input(usr, "Code 401 Unauthorized. Please enter password:", "Password required"))
if(pass != remote.server_password)
error = "Incorrect Password"
return TRUE
switch(action)
if("PRG_downloadfile")
for(var/datum/computer_file/program/nttransfer/P in ntnet_global.fileservers)
if(P.unique_token == text2num(params["uid"]))
remote = P
break
if(!remote || !remote.provided_file)
return
downloaded_file = remote.provided_file.clone()
remote.connected_clients.Add(src)
return 1
if(href_list["PRG_reset"])
error = ""
upload_menu = 0
finalize_download()
if(src in ntnet_global.fileservers)
ntnet_global.fileservers.Remove(src)
for(var/datum/computer_file/program/nttransfer/T in connected_clients)
T.crash_download("Remote server has forcibly closed the connection")
provided_file = null
return 1
if(href_list["PRG_setpassword"])
var/pass = sanitize(input(usr, "Enter new server password. Leave blank to cancel, input 'none' to disable password.", "Server security", "none"))
if(!pass)
return
if(pass == "none")
server_password = ""
return
server_password = pass
return 1
if(href_list["PRG_uploadfile"])
for(var/datum/computer_file/F in computer.hard_drive.stored_files)
if("[F.uid]" == href_list["PRG_uploadfile"])
if(F.unsendable)
error = "I/O Error: File locked."
if(remote.server_password)
var/pass = sanitize(input(usr, "Code 401 Unauthorized. Please enter password:", "Password required"))
if(pass != remote.server_password)
error = "Incorrect Password"
return
provided_file = F
ntnet_global.fileservers.Add(src)
downloaded_file = remote.provided_file.clone()
remote.connected_clients.Add(src)
return TRUE
if("PRG_reset")
error = ""
upload_menu = 0
finalize_download()
if(src in ntnet_global.fileservers)
ntnet_global.fileservers.Remove(src)
for(var/datum/computer_file/program/nttransfer/T in connected_clients)
T.crash_download("Remote server has forcibly closed the connection")
provided_file = null
return TRUE
if("PRG_setpassword")
var/pass = sanitize(input(usr, "Enter new server password. Leave blank to cancel, input 'none' to disable password.", "Server security", "none"))
if(!pass)
return
error = "I/O Error: Unable to locate file on hard drive."
return 1
if(href_list["PRG_uploadmenu"])
upload_menu = 1
return 0
if(pass == "none")
server_password = ""
return
server_password = pass
return TRUE
if("PRG_uploadfile")
for(var/datum/computer_file/F in computer.hard_drive.stored_files)
if(F.uid == text2num(params["uid"]))
if(F.unsendable)
error = "I/O Error: File locked."
return
provided_file = F
ntnet_global.fileservers |= src
return
error = "I/O Error: Unable to locate file on hard drive."
return TRUE
if("PRG_uploadmenu")
upload_menu = 1
return TRUE
@@ -4,7 +4,7 @@
/datum/computer_file/program/uav
filename = "rigger"
filedesc = "UAV Control"
nanomodule_path = /datum/nano_module/uav
tguimodule_path = /datum/tgui_module/uav
program_icon_state = "comm_monitor"
program_key_state = "generic_key"
program_menu_icon = "link"
@@ -12,255 +12,3 @@
size = 12
available_on_ntnet = 1
//requires_ntnet = 1
/datum/nano_module/uav
name = "UAV Control program"
var/obj/item/device/uav/current_uav = null //The UAV we're watching
var/signal_strength = 0 //Our last signal strength report (cached for a few seconds)
var/signal_test_counter = 0 //How long until next signal strength check
var/list/viewers //Who's viewing a UAV through us
var/adhoc_range = 30 //How far we can operate on a UAV without NTnet
/datum/nano_module/uav/Destroy()
if(LAZYLEN(viewers))
for(var/weakref/W in viewers)
var/M = W.resolve()
if(M)
unlook(M)
. = ..()
/datum/nano_module/uav/ui_interact(mob/user, ui_key = "main", datum/nanoui/ui = null, force_open = 1, state = default_state)
var/list/data = host.initial_data()
if(current_uav)
if(QDELETED(current_uav))
set_current(null)
else if(signal_test_counter-- <= 0)
signal_strength = get_signal_to(current_uav)
if(!signal_strength)
set_current(null)
else // Don't reset counter until we find a UAV that's actually in range we can stay connected to
signal_test_counter = 20
data["current_uav"] = null
if(current_uav)
data["current_uav"] = list("status" = current_uav.get_status_string(), "power" = current_uav.state == 1 ? 1 : null)
data["signal_strength"] = signal_strength ? signal_strength >= 2 ? "High" : "Low" : "None"
data["in_use"] = LAZYLEN(viewers)
var/list/paired_map = list()
var/obj/item/modular_computer/mc_host = nano_host()
if(istype(mc_host))
for(var/puav in mc_host.paired_uavs)
var/weakref/wr = puav
var/obj/item/device/uav/U = wr.resolve()
paired_map[++paired_map.len] = list("name" = "[U ? U.nickname : "!!Missing!!"]", "uavref" = "\ref[U]")
data["paired_uavs"] = paired_map
ui = SSnanoui.try_update_ui(user, src, ui_key, ui, data, force_open)
if (!ui)
ui = new(user, src, ui_key, "mod_uav.tmpl", "UAV Control", 600, 500, state = state)
ui.set_initial_data(data)
ui.open()
ui.set_auto_update(1)
/datum/nano_module/uav/Topic(var/href, var/href_list = list(), var/datum/topic_state/state)
if((. = ..()))
return
state = state || DefaultTopicState() || global.default_state
if(CanUseTopic(usr, state, href_list) == STATUS_INTERACTIVE)
CouldUseTopic(usr)
return OnTopic(usr, href_list, state)
CouldNotUseTopic(usr)
return TRUE
/datum/nano_module/uav/proc/OnTopic(var/mob/user, var/list/href_list)
if(href_list["switch_uav"])
var/obj/item/device/uav/U = locate(href_list["switch_uav"]) //This is a \ref to the UAV itself
if(!istype(U))
to_chat(usr,"<span class='warning'>Something is blocking the connection to that UAV. In-person investigation is required.</span>")
return TOPIC_NOACTION
if(!get_signal_to(U))
to_chat(usr,"<span class='warning'>The screen freezes for a moment, before returning to the UAV selection menu. It's not able to connect to that UAV.</span>")
return TOPIC_NOACTION
set_current(U)
return TOPIC_REFRESH
if(href_list["del_uav"])
var/refstring = href_list["del_uav"] //This is a \ref to the UAV itself
var/obj/item/modular_computer/mc_host = nano_host()
//This is so we can really scrape up any weakrefs that can't resolve
for(var/weakref/wr in mc_host.paired_uavs)
if(wr.ref == refstring)
if(current_uav?.weakref == wr)
set_current(null)
LAZYREMOVE(mc_host.paired_uavs, wr)
else if(href_list["view_uav"])
if(!current_uav)
return TOPIC_NOACTION
if(current_uav.check_eye(user) < 0)
to_chat(usr,"<span class='warning'>The screen freezes for a moment, before returning to the UAV selection menu. It's not able to connect to that UAV.</span>")
else
viewing_uav(user) ? unlook(user) : look(user)
return TOPIC_NOACTION
else if(href_list["power_uav"])
if(!current_uav)
return TOPIC_NOACTION
else if(current_uav.toggle_power())
//Clean up viewers faster
if(LAZYLEN(viewers))
for(var/weakref/W in viewers)
var/M = W.resolve()
if(M)
unlook(M)
return TOPIC_REFRESH
/datum/nano_module/uav/proc/DefaultTopicState()
return global.default_state
/datum/nano_module/uav/proc/CouldNotUseTopic(mob/user)
. = ..()
unlook(user)
/datum/nano_module/uav/proc/CouldUseTopic(mob/user)
. = ..()
if(viewing_uav(user))
look(user)
/datum/nano_module/uav/proc/set_current(var/obj/item/device/uav/U)
if(current_uav == U)
return
signal_strength = 0
current_uav = U
if(LAZYLEN(viewers))
for(var/weakref/W in viewers)
var/M = W.resolve()
if(M)
if(current_uav)
to_chat(M, "<span class='warning'>You're disconnected from the UAV's camera!</span>")
unlook(M)
else
look(M)
////
//// Finding signal strength between us and the UAV
////
/datum/nano_module/uav/proc/get_signal_to(var/atom/movable/AM)
// Following roughly the ntnet signal levels
// 0 is none
// 1 is weak
// 2 is strong
var/obj/item/modular_computer/host = nano_host() //Better not add this to anything other than modular computers.
if(!istype(host))
return
var/our_signal = host.get_ntnet_status() //1 low, 2 good, 3 wired, 0 none
var/their_z = get_z(AM)
//If we have no NTnet connection don't bother getting theirs
if(!our_signal)
if(get_z(host) == their_z && (get_dist(host, AM) < adhoc_range))
return 1 //We can connect (with weak signal) in same z without ntnet, within 30 turfs
else
return 0
var/list/zlevels_in_range = using_map.get_map_levels(their_z, FALSE)
var/list/zlevels_in_long_range = using_map.get_map_levels(their_z, TRUE, om_range = DEFAULT_OVERMAP_RANGE) - zlevels_in_range
var/their_signal = 0
for(var/relay in ntnet_global.relays)
var/obj/machinery/ntnet_relay/R = relay
if(!R.operable())
continue
if(R.z == their_z)
their_signal = 2
break
if(R.z in zlevels_in_range)
their_signal = 2
break
if(R.z in zlevels_in_long_range)
their_signal = 1
break
if(!their_signal) //They have no NTnet at all
if(get_z(host) == their_z && (get_dist(host, AM) < adhoc_range))
return 1 //We can connect (with weak signal) in same z without ntnet, within 30 turfs
else
return 0
else
return max(our_signal, their_signal)
////
//// UAV viewer handling
////
/datum/nano_module/uav/proc/viewing_uav(mob/user)
return (weakref(user) in viewers)
/datum/nano_module/uav/proc/look(var/mob/user)
if(issilicon(user)) //Too complicated for me to want to mess with at the moment
to_chat(user, "<span class='warning'>Regulations prevent you from controlling several corporeal forms at the same time!</span>")
return
if(!current_uav)
return
user.set_machine(nano_host())
user.reset_view(current_uav)
current_uav.add_master(user)
LAZYDISTINCTADD(viewers, weakref(user))
/datum/nano_module/uav/proc/unlook(var/mob/user)
user.unset_machine()
user.reset_view()
if(current_uav)
current_uav.remove_master(user)
LAZYREMOVE(viewers, weakref(user))
/datum/nano_module/uav/check_eye(var/mob/user)
if(get_dist(user, nano_host()) > 1 || user.blinded || !current_uav)
unlook(user)
return -1
var/viewflag = current_uav.check_eye(user)
if (viewflag < 0) //camera doesn't work
unlook(user)
return -1
return viewflag
////
//// Relaying movements to the UAV
////
/datum/nano_module/uav/relaymove(var/mob/user, direction)
if(current_uav)
return current_uav.relaymove(user, direction, signal_strength)
////
//// The effects when looking through a UAV
////
/datum/nano_module/uav/apply_visual(var/mob/M)
if(!M.client)
return
if(weakref(M) in viewers)
M.overlay_fullscreen("fishbed",/obj/screen/fullscreen/fishbed)
M.overlay_fullscreen("scanlines",/obj/screen/fullscreen/scanline)
if(signal_strength <= 1)
M.overlay_fullscreen("whitenoise",/obj/screen/fullscreen/noise)
else
M.clear_fullscreen("whitenoise", 0)
else
remove_visual(M)
/datum/nano_module/uav/remove_visual(mob/M)
if(!M.client)
return
M.clear_fullscreen("fishbed",0)
M.clear_fullscreen("scanlines",0)
M.clear_fullscreen("whitenoise",0)
@@ -5,9 +5,10 @@
program_icon_state = "word"
program_key_state = "atmos_key"
size = 4
requires_ntnet = 0
available_on_ntnet = 1
nanomodule_path = /datum/nano_module/program/computer_wordprocessor/
requires_ntnet = FALSE
available_on_ntnet = TRUE
tgui_id = "NtosWordProcessor"
var/browsing
var/open_file
var/loaded_data
@@ -28,7 +29,7 @@
if(F)
open_file = F.filename
loaded_data = F.stored_data
return 1
return TRUE
/datum/computer_file/program/wordprocessor/proc/save_file(var/filename)
var/datum/computer_file/data/F = get_file(filename)
@@ -46,7 +47,7 @@
HDD.store_file(backup)
return 0
is_edited = 0
return 1
return TRUE
/datum/computer_file/program/wordprocessor/proc/create_file(var/newname, var/data = "")
if(!newname)
@@ -64,142 +65,144 @@
if(HDD.store_file(F))
return F
/datum/computer_file/program/wordprocessor/Topic(href, href_list)
/datum/computer_file/program/wordprocessor/tgui_act(action, list/params, datum/tgui/ui)
if(..())
return 1
return TRUE
if(href_list["PRG_txtrpeview"])
show_browser(usr,"<HTML><HEAD><TITLE>[open_file]</TITLE></HEAD>[pencode2html(loaded_data)]</BODY></HTML>", "window=[open_file]")
return 1
switch(action)
if("PRG_txtrpeview")
show_browser(usr,"<HTML><HEAD><TITLE>[open_file]</TITLE></HEAD>[pencode2html(loaded_data)]</BODY></HTML>", "window=[open_file]")
return TRUE
if(href_list["PRG_taghelp"])
to_chat(usr, "<span class='notice'>The hologram of a googly-eyed paper clip helpfully tells you:</span>")
var/help = {"
\[br\] : Creates a linebreak.
\[center\] - \[/center\] : Centers the text.
\[h1\] - \[/h1\] : First level heading.
\[h2\] - \[/h2\] : Second level heading.
\[h3\] - \[/h3\] : Third level heading.
\[b\] - \[/b\] : Bold.
\[i\] - \[/i\] : Italic.
\[u\] - \[/u\] : Underlined.
\[small\] - \[/small\] : Decreases the size of the text.
\[large\] - \[/large\] : Increases the size of the text.
\[field\] : Inserts a blank text field, which can be filled later. Useful for forms.
\[date\] : Current station date.
\[time\] : Current station time.
\[list\] - \[/list\] : Begins and ends a list.
\[*\] : A list item.
\[hr\] : Horizontal rule.
\[table\] - \[/table\] : Creates table using \[row\] and \[cell\] tags.
\[grid\] - \[/grid\] : Table without visible borders, for layouts.
\[row\] - New table row.
\[cell\] - New table cell.
\[logo\] - Inserts NT logo image.
\[redlogo\] - Inserts red NT logo image.
\[sglogo\] - Inserts Solgov insignia image."}
if("PRG_taghelp")
to_chat(usr, "<span class='notice'>The hologram of a googly-eyed paper clip helpfully tells you:</span>")
var/help = {"
\[br\] : Creates a linebreak.
\[center\] - \[/center\] : Centers the text.
\[h1\] - \[/h1\] : First level heading.
\[h2\] - \[/h2\] : Second level heading.
\[h3\] - \[/h3\] : Third level heading.
\[b\] - \[/b\] : Bold.
\[i\] - \[/i\] : Italic.
\[u\] - \[/u\] : Underlined.
\[small\] - \[/small\] : Decreases the size of the text.
\[large\] - \[/large\] : Increases the size of the text.
\[field\] : Inserts a blank text field, which can be filled later. Useful for forms.
\[date\] : Current station date.
\[time\] : Current station time.
\[list\] - \[/list\] : Begins and ends a list.
\[*\] : A list item.
\[hr\] : Horizontal rule.
\[table\] - \[/table\] : Creates table using \[row\] and \[cell\] tags.
\[grid\] - \[/grid\] : Table without visible borders, for layouts.
\[row\] - New table row.
\[cell\] - New table cell.
\[logo\] - Inserts NT logo image.
\[redlogo\] - Inserts red NT logo image.
\[sglogo\] - Inserts Solgov insignia image."}
to_chat(usr, help)
return 1
to_chat(usr, help)
return TRUE
if(href_list["PRG_closebrowser"])
browsing = 0
return 1
if("PRG_closebrowser")
browsing = 0
return TRUE
if(href_list["PRG_backtomenu"])
error = null
return 1
if("PRG_backtomenu")
error = null
return TRUE
if(href_list["PRG_loadmenu"])
browsing = 1
return 1
if("PRG_loadmenu")
browsing = 1
return TRUE
if(href_list["PRG_openfile"])
. = 1
if(is_edited)
if(alert("Would you like to save your changes first?",,"Yes","No") == "Yes")
save_file(open_file)
browsing = 0
if(!open_file(href_list["PRG_openfile"]))
error = "I/O error: Unable to open file '[href_list["PRG_openfile"]]'."
if("PRG_openfile")
if(is_edited)
if(alert("Would you like to save your changes first?",,"Yes","No") == "Yes")
save_file(open_file)
browsing = 0
if(!open_file(params["PRG_openfile"]))
error = "I/O error: Unable to open file '[params["PRG_openfile"]]'."
return TRUE
if(href_list["PRG_newfile"])
. = 1
if(is_edited)
if(alert("Would you like to save your changes first?",,"Yes","No") == "Yes")
save_file(open_file)
if("PRG_newfile")
if(is_edited)
if(alert("Would you like to save your changes first?",,"Yes","No") == "Yes")
save_file(open_file)
var/newname = sanitize(input(usr, "Enter file name:", "New File") as text|null)
if(!newname)
return 1
var/datum/computer_file/data/F = create_file(newname)
if(F)
open_file = F.filename
loaded_data = ""
return 1
else
error = "I/O error: Unable to create file '[href_list["PRG_saveasfile"]]'."
var/newname = sanitize(input(usr, "Enter file name:", "New File") as text|null)
if(!newname)
return TRUE
var/datum/computer_file/data/F = create_file(newname)
if(F)
open_file = F.filename
loaded_data = ""
return TRUE
else
error = "I/O error: Unable to create file '[params["PRG_saveasfile"]]'."
return TRUE
if(href_list["PRG_saveasfile"])
. = 1
var/newname = sanitize(input(usr, "Enter file name:", "Save As") as text|null)
if(!newname)
return 1
var/datum/computer_file/data/F = create_file(newname, loaded_data)
if(F)
open_file = F.filename
else
error = "I/O error: Unable to create file '[href_list["PRG_saveasfile"]]'."
return 1
if("PRG_saveasfile")
var/newname = sanitize(input(usr, "Enter file name:", "Save As") as text|null)
if(!newname)
return TRUE
var/datum/computer_file/data/F = create_file(newname, loaded_data)
if(F)
open_file = F.filename
else
error = "I/O error: Unable to create file '[params["PRG_saveasfile"]]'."
return TRUE
if(href_list["PRG_savefile"])
. = 1
if(!open_file)
open_file = sanitize(input(usr, "Enter file name:", "Save As") as text|null)
if("PRG_savefile")
if(!open_file)
return 0
if(!save_file(open_file))
error = "I/O error: Unable to save file '[open_file]'."
return 1
open_file = sanitize(input(usr, "Enter file name:", "Save As") as text|null)
if(!open_file)
return 0
if(!save_file(open_file))
error = "I/O error: Unable to save file '[open_file]'."
return TRUE
if(href_list["PRG_editfile"])
var/oldtext = html_decode(loaded_data)
oldtext = replacetext(oldtext, "\[br\]", "\n")
if("PRG_editfile")
var/oldtext = html_decode(loaded_data)
oldtext = replacetext(oldtext, "\[br\]", "\n")
var/newtext = sanitize(replacetext(input(usr, "Editing file '[open_file]'. You may use most tags used in paper formatting:", "Text Editor", oldtext) as message|null, "\n", "\[br\]"), MAX_TEXTFILE_LENGTH)
if(!newtext)
return
loaded_data = newtext
is_edited = 1
return 1
var/newtext = sanitize(replacetext(input(usr, "Editing file '[open_file]'. You may use most tags used in paper formatting:", "Text Editor", oldtext) as message|null, "\n", "\[br\]"), MAX_TEXTFILE_LENGTH)
if(!newtext)
return
loaded_data = newtext
is_edited = 1
return TRUE
if(href_list["PRG_printfile"])
. = 1
if(!computer.nano_printer)
error = "Missing Hardware: Your computer does not have the required hardware to complete this operation."
return 1
if(!computer.nano_printer.print_text(pencode2html(loaded_data)))
error = "Hardware error: Printer was unable to print the file. It may be out of paper."
return 1
if("PRG_printfile")
if(!computer.nano_printer)
error = "Missing Hardware: Your computer does not have the required hardware to complete this operation."
return TRUE
if(!computer.nano_printer.print_text(pencode2html(loaded_data)))
error = "Hardware error: Printer was unable to print the file. It may be out of paper."
return TRUE
return TRUE
/datum/nano_module/program/computer_wordprocessor
name = "Word Processor"
/datum/computer_file/program/wordprocessor/tgui_data(mob/user, datum/tgui/ui, datum/tgui_state/state)
var/list/data = get_header_data()
/datum/nano_module/program/computer_wordprocessor/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1, var/datum/topic_state/state = default_state)
var/list/data = host.initial_data()
var/datum/computer_file/program/wordprocessor/PRG
PRG = program
var/obj/item/weapon/computer_hardware/hard_drive/HDD = computer.hard_drive
var/obj/item/weapon/computer_hardware/hard_drive/portable/RHDD = computer.portable_drive
data["error"] = null
if(error)
data["error"] = error
var/obj/item/weapon/computer_hardware/hard_drive/HDD
var/obj/item/weapon/computer_hardware/hard_drive/portable/RHDD
if(PRG.error)
data["error"] = PRG.error
if(PRG.browsing)
data["browsing"] = PRG.browsing
if(!PRG.computer || !PRG.computer.hard_drive)
data["browsing"] = null
data["files"] = list()
data["usbconnected"] = FALSE
data["usbfiles"] = list()
data["filedata"] = null
data["filename"] = null
if(browsing)
data["browsing"] = browsing
if(!computer || !HDD)
data["error"] = "I/O ERROR: Unable to access hard drive."
else
HDD = PRG.computer.hard_drive
var/list/files[0]
for(var/datum/computer_file/F in HDD.stored_files)
if(F.filetype == "TXT")
@@ -209,7 +212,6 @@
)))
data["files"] = files
RHDD = PRG.computer.portable_drive
if(RHDD)
data["usbconnected"] = 1
var/list/usbfiles[0]
@@ -220,16 +222,11 @@
"size" = F.size,
)))
data["usbfiles"] = usbfiles
else if(PRG.open_file)
data["filedata"] = pencode2html(PRG.loaded_data)
data["filename"] = PRG.is_edited ? "[PRG.open_file]*" : PRG.open_file
else if(open_file)
data["filedata"] = pencode2html(loaded_data)
data["filename"] = is_edited ? "[open_file]*" : open_file
else
data["filedata"] = pencode2html(PRG.loaded_data)
data["filedata"] = pencode2html(loaded_data)
data["filename"] = "UNNAMED"
ui = SSnanoui.try_update_ui(user, src, ui_key, ui, data, force_open)
if (!ui)
ui = new(user, src, ui_key, "word_processor.tmpl", "Word Processor", 575, 700, state = state)
ui.auto_update_layout = 1
ui.set_initial_data(data)
ui.open()
return data