mirror of
https://github.com/VOREStation/VOREStation.git
synced 2026-07-13 16:13:49 +01:00
[REVIEW] Ports Modular Computers from Baystation
This commit is contained in:
@@ -0,0 +1,186 @@
|
||||
// Returns which access is relevant to passed network. Used by the program.
|
||||
/proc/get_camera_access(var/network)
|
||||
if(!network)
|
||||
return 0
|
||||
. = using_map.get_network_access(network)
|
||||
if(.)
|
||||
return
|
||||
|
||||
switch(network)
|
||||
if(NETWORK_THUNDER)
|
||||
return 0
|
||||
if(NETWORK_ENGINE,NETWORK_ENGINEERING,NETWORK_ENGINEERING_OUTPOST,NETWORK_ALARM_ATMOS,NETWORK_ALARM_FIRE,NETWORK_ALARM_POWER)
|
||||
return access_engine
|
||||
if(NETWORK_MEDICAL)
|
||||
return access_medical
|
||||
if(NETWORK_RESEARCH,NETWORK_RESEARCH_OUTPOST)
|
||||
return access_research
|
||||
if(NETWORK_MINE,NETWORK_CARGO )
|
||||
return access_mailsorting // Cargo office - all cargo staff should have access here.
|
||||
if(NETWORK_COMMAND,NETWORK_TELECOM)
|
||||
return access_heads
|
||||
if(NETWORK_ERT)
|
||||
return access_cent_specops
|
||||
|
||||
return access_security // Default for all other networks
|
||||
|
||||
/datum/computer_file/program/camera_monitor
|
||||
filename = "cammon"
|
||||
filedesc = "Camera Monitoring"
|
||||
nanomodule_path = /datum/nano_module/camera_monitor
|
||||
program_icon_state = "cameras"
|
||||
program_key_state = "generic_key"
|
||||
program_menu_icon = "search"
|
||||
extended_desc = "This program allows remote access to the camera system. Some camera networks may have additional access requirements."
|
||||
size = 12
|
||||
available_on_ntnet = 1
|
||||
requires_ntnet = 1
|
||||
|
||||
/datum/nano_module/camera_monitor
|
||||
name = "Camera Monitoring program"
|
||||
var/obj/machinery/camera/current_camera = null
|
||||
var/current_network = null
|
||||
|
||||
/datum/nano_module/camera_monitor/ui_interact(mob/user, ui_key = "main", datum/nanoui/ui = null, force_open = 1, state = default_state)
|
||||
var/list/data = host.initial_data()
|
||||
|
||||
data["current_camera"] = current_camera ? current_camera.nano_structure() : null
|
||||
data["current_network"] = current_network
|
||||
|
||||
var/list/all_networks[0]
|
||||
for(var/network in using_map.station_networks)
|
||||
all_networks.Add(list(list(
|
||||
"tag" = network,
|
||||
"has_access" = can_access_network(user, get_camera_access(network))
|
||||
)))
|
||||
|
||||
all_networks = modify_networks_list(all_networks)
|
||||
|
||||
data["networks"] = all_networks
|
||||
|
||||
if(current_network)
|
||||
data["cameras"] = camera_repository.cameras_in_network(current_network)
|
||||
|
||||
ui = SSnanoui.try_update_ui(user, src, ui_key, ui, data, force_open)
|
||||
if (!ui)
|
||||
ui = new(user, src, ui_key, "mod_sec_camera.tmpl", "Camera Monitoring", 900, 800)
|
||||
// ui.auto_update_layout = 1 // Disabled as with suit sensors monitor - breaks the UI map. Re-enable once it's fixed somehow.
|
||||
|
||||
ui.add_template("mapContent", "sec_camera_map_content.tmpl")
|
||||
ui.add_template("mapHeader", "mod_sec_camera_map_header.tmpl")
|
||||
ui.set_initial_data(data)
|
||||
ui.open()
|
||||
|
||||
// Intended to be overriden by subtypes to manually add non-station networks to the list.
|
||||
/datum/nano_module/camera_monitor/proc/modify_networks_list(var/list/networks)
|
||||
return networks
|
||||
|
||||
/datum/nano_module/camera_monitor/proc/can_access_network(var/mob/user, var/network_access)
|
||||
// No access passed, or 0 which is considered no access requirement. Allow it.
|
||||
if(!network_access)
|
||||
return 1
|
||||
|
||||
return check_access(user, access_security) || check_access(user, network_access)
|
||||
|
||||
/datum/nano_module/camera_monitor/Topic(href, href_list)
|
||||
if(..())
|
||||
return 1
|
||||
|
||||
if(href_list["switch_camera"])
|
||||
var/obj/machinery/camera/C = locate(href_list["switch_camera"]) in cameranet.cameras
|
||||
if(!C)
|
||||
return
|
||||
if(!(current_network in C.network))
|
||||
return
|
||||
|
||||
switch_to_camera(usr, C)
|
||||
return 1
|
||||
|
||||
else if(href_list["switch_network"])
|
||||
// Either security access, or access to the specific camera network's department is required in order to access the network.
|
||||
if(can_access_network(usr, get_camera_access(href_list["switch_network"])))
|
||||
current_network = href_list["switch_network"]
|
||||
else
|
||||
to_chat(usr, "\The [nano_host()] shows an \"Network Access Denied\" error message.")
|
||||
return 1
|
||||
|
||||
else if(href_list["reset"])
|
||||
reset_current()
|
||||
usr.reset_view(current_camera)
|
||||
return 1
|
||||
|
||||
/datum/nano_module/camera_monitor/proc/switch_to_camera(var/mob/user, var/obj/machinery/camera/C)
|
||||
//don't need to check if the camera works for AI because the AI jumps to the camera location and doesn't actually look through cameras.
|
||||
if(isAI(user))
|
||||
var/mob/living/silicon/ai/A = user
|
||||
// Only allow non-carded AIs to view because the interaction with the eye gets all wonky otherwise.
|
||||
if(!A.is_in_chassis())
|
||||
return 0
|
||||
|
||||
A.eyeobj.setLoc(get_turf(C))
|
||||
A.client.eye = A.eyeobj
|
||||
return 1
|
||||
|
||||
set_current(C)
|
||||
user.machine = nano_host()
|
||||
user.reset_view(C)
|
||||
return 1
|
||||
|
||||
/datum/nano_module/camera_monitor/proc/set_current(var/obj/machinery/camera/C)
|
||||
if(current_camera == C)
|
||||
return
|
||||
|
||||
if(current_camera)
|
||||
reset_current()
|
||||
|
||||
current_camera = C
|
||||
if(current_camera)
|
||||
var/mob/living/L = current_camera.loc
|
||||
if(istype(L))
|
||||
L.tracking_initiated()
|
||||
|
||||
/datum/nano_module/camera_monitor/proc/reset_current()
|
||||
if(current_camera)
|
||||
var/mob/living/L = current_camera.loc
|
||||
if(istype(L))
|
||||
L.tracking_cancelled()
|
||||
current_camera = null
|
||||
|
||||
/datum/nano_module/camera_monitor/check_eye(var/mob/user as mob)
|
||||
if(!current_camera)
|
||||
return 0
|
||||
var/viewflag = current_camera.check_eye(user)
|
||||
if ( viewflag < 0 ) //camera doesn't work
|
||||
reset_current()
|
||||
return viewflag
|
||||
|
||||
|
||||
// ERT Variant of the program
|
||||
/datum/computer_file/program/camera_monitor/ert
|
||||
filename = "ntcammon"
|
||||
filedesc = "Advanced Camera Monitoring"
|
||||
extended_desc = "This program allows remote access to the camera system. Some camera networks may have additional access requirements. This version has an integrated database with additional encrypted keys."
|
||||
size = 14
|
||||
nanomodule_path = /datum/nano_module/camera_monitor/ert
|
||||
available_on_ntnet = 0
|
||||
|
||||
/datum/nano_module/camera_monitor/ert
|
||||
name = "Advanced Camera Monitoring Program"
|
||||
//available_to_ai = FALSE
|
||||
|
||||
// The ERT variant has access to ERT and crescent cams, but still checks for accesses. ERT members should be able to use it.
|
||||
/datum/nano_module/camera_monitor/ert/modify_networks_list(var/list/networks)
|
||||
..()
|
||||
networks.Add(list(list("tag" = NETWORK_ERT, "has_access" = 1)))
|
||||
networks.Add(list(list("tag" = NETWORK_CRESCENT, "has_access" = 1)))
|
||||
return networks
|
||||
|
||||
/datum/nano_module/camera_monitor/apply_visual(mob/M)
|
||||
if(current_camera)
|
||||
current_camera.apply_visual(M)
|
||||
else
|
||||
remove_visual(M)
|
||||
|
||||
/datum/nano_module/camera_monitor/remove_visual(mob/M)
|
||||
if(current_camera)
|
||||
current_camera.remove_visual(M)
|
||||
@@ -0,0 +1,64 @@
|
||||
// This is special hardware configuration program.
|
||||
// It is to be used only with modular computers.
|
||||
// It allows you to toggle components of your device.
|
||||
|
||||
/datum/computer_file/program/computerconfig
|
||||
filename = "compconfig"
|
||||
filedesc = "Computer Configuration Tool"
|
||||
extended_desc = "This program allows configuration of computer's hardware"
|
||||
program_icon_state = "generic"
|
||||
program_key_state = "generic_key"
|
||||
program_menu_icon = "gear"
|
||||
unsendable = 1
|
||||
undeletable = 1
|
||||
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()
|
||||
@@ -0,0 +1,499 @@
|
||||
/datum/computer_file/program/email_client
|
||||
filename = "emailc"
|
||||
filedesc = "Email Client"
|
||||
extended_desc = "This program may be used to log in into your email account."
|
||||
program_icon_state = "generic"
|
||||
program_key_state = "generic_key"
|
||||
program_menu_icon = "mail-closed"
|
||||
size = 7
|
||||
requires_ntnet = 1
|
||||
available_on_ntnet = 1
|
||||
var/stored_login = ""
|
||||
var/stored_password = ""
|
||||
|
||||
nanomodule_path = /datum/nano_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
|
||||
else
|
||||
stored_login = ""
|
||||
stored_password = ""
|
||||
. = ..()
|
||||
|
||||
/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)
|
||||
|
||||
/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)
|
||||
|
||||
/datum/computer_file/program/email_client/process_tick()
|
||||
..()
|
||||
var/datum/nano_module/email_client/NME = NM
|
||||
if(!istype(NME))
|
||||
return
|
||||
NME.relayed_process(ntnet_speed)
|
||||
|
||||
var/check_count = NME.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
|
||||
@@ -0,0 +1,207 @@
|
||||
/datum/computer_file/program/filemanager
|
||||
filename = "filemanager"
|
||||
filedesc = "NTOS File Manager"
|
||||
extended_desc = "This program allows management of files."
|
||||
program_icon_state = "generic"
|
||||
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/
|
||||
var/open_file
|
||||
var/error
|
||||
|
||||
/datum/computer_file/program/filemanager/Topic(href, href_list)
|
||||
if(..())
|
||||
return 1
|
||||
|
||||
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)
|
||||
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
|
||||
|
||||
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)
|
||||
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)
|
||||
|
||||
/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)
|
||||
var/datum/computer_file/data/file
|
||||
|
||||
if(!PRG.computer || !PRG.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)
|
||||
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(
|
||||
"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
|
||||
|
||||
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()
|
||||
@@ -0,0 +1,152 @@
|
||||
// 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
|
||||
|
||||
// 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]"
|
||||
|
||||
// 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]"
|
||||
|
||||
// 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!"
|
||||
else
|
||||
var/dam = rand(3,6)
|
||||
player_health -= dam
|
||||
information += "[enemy_name] attacks for [dam] damage!"
|
||||
|
||||
/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
|
||||
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!"
|
||||
|
||||
|
||||
|
||||
/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
|
||||
@@ -0,0 +1,131 @@
|
||||
/datum/computer_file/program/newsbrowser
|
||||
filename = "newsbrowser"
|
||||
filedesc = "NTNet/ExoNet News Browser"
|
||||
extended_desc = "This program may be used to view and download news articles from the network."
|
||||
program_icon_state = "generic"
|
||||
program_key_state = "generic_key"
|
||||
program_menu_icon = "contact"
|
||||
size = 4
|
||||
requires_ntnet = 1
|
||||
available_on_ntnet = 1
|
||||
|
||||
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/message = ""
|
||||
var/show_archived = 0
|
||||
|
||||
/datum/computer_file/program/newsbrowser/process_tick()
|
||||
if(!downloading)
|
||||
return
|
||||
download_netspeed = 0
|
||||
// Speed defines are found in misc.dm
|
||||
switch(ntnet_status)
|
||||
if(1)
|
||||
download_netspeed = NTNETSPEED_LOWSIGNAL
|
||||
if(2)
|
||||
download_netspeed = NTNETSPEED_HIGHSIGNAL
|
||||
if(3)
|
||||
download_netspeed = NTNETSPEED_ETHERNET
|
||||
download_progress += download_netspeed
|
||||
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)
|
||||
|
||||
/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/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
|
||||
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)
|
||||
continue
|
||||
all_articles.Add(list(list(
|
||||
"name" = F.filename,
|
||||
"size" = F.size,
|
||||
"uid" = F.uid,
|
||||
"archived" = F.archived
|
||||
)))
|
||||
data["all_articles"] = all_articles
|
||||
data["showing_archived"] = PRG.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()
|
||||
@@ -0,0 +1,200 @@
|
||||
/datum/computer_file/program/ntnetdownload
|
||||
filename = "ntndownloader"
|
||||
filedesc = "NTNet Software Download Tool"
|
||||
program_icon_state = "generic"
|
||||
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
|
||||
size = 4
|
||||
requires_ntnet = 1
|
||||
requires_ntnet_feature = NTNET_SOFTWAREDOWNLOAD
|
||||
available_on_ntnet = 0
|
||||
nanomodule_path = /datum/nano_module/program/computer_ntnetdownload/
|
||||
ui_header = "downloader_finished.gif"
|
||||
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/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"
|
||||
|
||||
|
||||
/datum/computer_file/program/ntnetdownload/proc/begin_file_download(var/filename)
|
||||
if(downloaded_file)
|
||||
return 0
|
||||
|
||||
var/datum/computer_file/program/PRG = ntnet_global.find_ntnet_file_by_name(filename)
|
||||
|
||||
if(!check_file_download(filename))
|
||||
return 0
|
||||
|
||||
ui_header = "downloader_running.gif"
|
||||
|
||||
if(PRG in ntnet_global.available_station_software)
|
||||
generate_network_log("Began downloading file [PRG.filename].[PRG.filetype] from NTNet Software Repository.")
|
||||
hacked_download = 0
|
||||
else if(PRG in ntnet_global.available_antag_software)
|
||||
generate_network_log("Began downloading file **ENCRYPTED**.[PRG.filetype] from unspecified server.")
|
||||
hacked_download = 1
|
||||
else
|
||||
generate_network_log("Began downloading file [PRG.filename].[PRG.filetype] from unspecified server.")
|
||||
hacked_download = 0
|
||||
|
||||
downloaded_file = PRG.clone()
|
||||
|
||||
/datum/computer_file/program/ntnetdownload/proc/check_file_download(var/filename)
|
||||
//returns 1 if file can be downloaded, returns 0 if download prohibited
|
||||
var/datum/computer_file/program/PRG = ntnet_global.find_ntnet_file_by_name(filename)
|
||||
|
||||
if(!PRG || !istype(PRG))
|
||||
return 0
|
||||
|
||||
// Attempting to download antag only program, but without having emagged computer. No.
|
||||
if(PRG.available_on_syndinet && !computer_emagged)
|
||||
return 0
|
||||
|
||||
if(!computer || !computer.hard_drive || !computer.hard_drive.try_store_file(PRG))
|
||||
return 0
|
||||
|
||||
return 1
|
||||
|
||||
/datum/computer_file/program/ntnetdownload/proc/abort_file_download()
|
||||
if(!downloaded_file)
|
||||
return
|
||||
generate_network_log("Aborted download of file [hacked_download ? "**ENCRYPTED**" : downloaded_file.filename].[downloaded_file.filetype].")
|
||||
downloaded_file = null
|
||||
download_completion = 0
|
||||
ui_header = "downloader_finished.gif"
|
||||
|
||||
/datum/computer_file/program/ntnetdownload/proc/complete_file_download()
|
||||
if(!downloaded_file)
|
||||
return
|
||||
generate_network_log("Completed download of file [hacked_download ? "**ENCRYPTED**" : downloaded_file.filename].[downloaded_file.filetype].")
|
||||
if(!computer || !computer.hard_drive || !computer.hard_drive.store_file(downloaded_file))
|
||||
// The download failed
|
||||
downloaderror = "I/O ERROR - Unable to save file. Check whether you have enough free space on your hard drive and whether your hard drive is properly connected. If the issue persists contact your system administrator for assistance."
|
||||
downloaded_file = null
|
||||
download_completion = 0
|
||||
ui_header = "downloader_finished.gif"
|
||||
|
||||
/datum/computer_file/program/ntnetdownload/process_tick()
|
||||
if(!downloaded_file)
|
||||
return
|
||||
if(download_completion >= downloaded_file.size)
|
||||
complete_file_download()
|
||||
if(downloads_queue.len > 0)
|
||||
begin_file_download(downloads_queue[1])
|
||||
downloads_queue.Remove(downloads_queue[1])
|
||||
|
||||
// Download speed according to connectivity state. NTNet server is assumed to be on unlimited speed so we're limited by our local connectivity
|
||||
download_netspeed = 0
|
||||
// Speed defines are found in misc.dm
|
||||
switch(ntnet_status)
|
||||
if(1)
|
||||
download_netspeed = NTNETSPEED_LOWSIGNAL
|
||||
if(2)
|
||||
download_netspeed = NTNETSPEED_HIGHSIGNAL
|
||||
if(3)
|
||||
download_netspeed = NTNETSPEED_ETHERNET
|
||||
download_completion += download_netspeed
|
||||
|
||||
/datum/computer_file/program/ntnetdownload/Topic(href, href_list)
|
||||
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
|
||||
|
||||
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()
|
||||
|
||||
// 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["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)
|
||||
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,
|
||||
"size" = P.size,
|
||||
"icon" = P.program_menu_icon
|
||||
)))
|
||||
data["hacked_programs"] = hacked_programs
|
||||
|
||||
data["downloadable_programs"] = all_entries
|
||||
|
||||
if(prog.downloads_queue.len > 0)
|
||||
data["downloads_queue"] = prog.downloads_queue
|
||||
|
||||
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)
|
||||
@@ -0,0 +1,232 @@
|
||||
/datum/computer_file/program/chatclient
|
||||
filename = "ntnrc_client"
|
||||
filedesc = "NTNet Relay Chat Client"
|
||||
program_icon_state = "command"
|
||||
program_key_state = "med_key"
|
||||
program_menu_icon = "comment"
|
||||
extended_desc = "This program allows communication over NTNRC network"
|
||||
size = 8
|
||||
requires_ntnet = 1
|
||||
requires_ntnet_feature = NTNET_COMMUNICATION
|
||||
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
|
||||
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)
|
||||
|
||||
/datum/computer_file/program/chatclient/New()
|
||||
username = "DefaultUser[rand(100, 999)]"
|
||||
|
||||
/datum/computer_file/program/chatclient/Topic(href, href_list)
|
||||
if(..())
|
||||
return 1
|
||||
|
||||
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)
|
||||
|
||||
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
|
||||
|
||||
if(!C)
|
||||
return 1
|
||||
|
||||
if(netadmin_mode)
|
||||
channel = C // Bypasses normal leave/join and passwords. Technically makes the user invisible to others.
|
||||
return 1
|
||||
|
||||
if(C.password)
|
||||
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(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/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
|
||||
|
||||
/datum/computer_file/program/chatclient/process_tick()
|
||||
..()
|
||||
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
|
||||
else
|
||||
last_message = null
|
||||
return 1
|
||||
if(channel && channel.messages && 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)
|
||||
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
|
||||
|
||||
var/list/data = list()
|
||||
if(program)
|
||||
data = program.get_header_data()
|
||||
|
||||
var/datum/computer_file/program/chatclient/C = program
|
||||
if(!istype(C))
|
||||
return
|
||||
|
||||
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
|
||||
)))
|
||||
data["messages"] = messages
|
||||
var/list/clients[0]
|
||||
for(var/datum/computer_file/program/chatclient/cl in C.channel.clients)
|
||||
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["all_channels"] = all_channels
|
||||
|
||||
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)
|
||||
@@ -0,0 +1,185 @@
|
||||
var/global/nttransfer_uid = 0
|
||||
|
||||
/datum/computer_file/program/nttransfer
|
||||
filename = "nttransfer"
|
||||
filedesc = "NTNet P2P Transfer Client"
|
||||
extended_desc = "This program allows for simple file transfer via direct peer to peer connection."
|
||||
program_icon_state = "comm_logs"
|
||||
program_key_state = "generic_key"
|
||||
program_menu_icon = "transferthick-e-w"
|
||||
size = 7
|
||||
requires_ntnet = 1
|
||||
requires_ntnet_feature = NTNET_PEERTOPEER
|
||||
network_destination = "other device via P2P tunnel"
|
||||
available_on_ntnet = 1
|
||||
nanomodule_path = /datum/nano_module/program/computer_nttransfer/
|
||||
|
||||
var/error = "" // Error screen
|
||||
var/server_password = "" // Optional password to download the file.
|
||||
var/datum/computer_file/provided_file = null // File which is provided to clients.
|
||||
var/datum/computer_file/downloaded_file = null // File which is being downloaded
|
||||
var/list/connected_clients = list() // List of connected clients.
|
||||
var/datum/computer_file/program/nttransfer/remote // Client var, specifies who are we downloading from.
|
||||
var/download_completion = 0 // Download progress in GQ
|
||||
var/actual_netspeed = 0 // Displayed in the UI, this is the actual transfer speed.
|
||||
var/unique_token // UID of this program
|
||||
var/upload_menu = 0 // Whether we show the program list and upload menu
|
||||
|
||||
/datum/computer_file/program/nttransfer/New()
|
||||
unique_token = nttransfer_uid
|
||||
nttransfer_uid++
|
||||
..()
|
||||
|
||||
/datum/computer_file/program/nttransfer/process_tick()
|
||||
..()
|
||||
// Server mode
|
||||
if(provided_file)
|
||||
for(var/datum/computer_file/program/nttransfer/C in connected_clients)
|
||||
// Transfer speed is limited by device which uses slower connectivity.
|
||||
// We can have multiple clients downloading at same time, but let's assume we use some sort of multicast transfer
|
||||
// so they can all run on same speed.
|
||||
C.actual_netspeed = min(C.ntnet_speed, ntnet_speed)
|
||||
C.download_completion += C.actual_netspeed
|
||||
if(C.download_completion >= provided_file.size)
|
||||
C.finish_download()
|
||||
else if(downloaded_file) // Client mode
|
||||
if(!remote)
|
||||
crash_download("Connection to remote server lost")
|
||||
|
||||
/datum/computer_file/program/nttransfer/kill_program(var/forced = 0)
|
||||
if(downloaded_file) // Client mode, clean up variables for next use
|
||||
finalize_download()
|
||||
|
||||
if(provided_file) // Server mode, disconnect all clients
|
||||
for(var/datum/computer_file/program/nttransfer/P in connected_clients)
|
||||
P.crash_download("Connection terminated by remote server")
|
||||
downloaded_file = null
|
||||
..(forced)
|
||||
|
||||
// Finishes download and attempts to store the file on HDD
|
||||
/datum/computer_file/program/nttransfer/proc/finish_download()
|
||||
if(!computer || !computer.hard_drive || !computer.hard_drive.store_file(downloaded_file))
|
||||
error = "I/O Error: Unable to save file. Check your hard drive and try again."
|
||||
finalize_download()
|
||||
|
||||
// Crashes the download and displays specific error message
|
||||
/datum/computer_file/program/nttransfer/proc/crash_download(var/message)
|
||||
error = message ? message : "An unknown error has occured during download"
|
||||
finalize_download()
|
||||
|
||||
// Cleans up variables for next use
|
||||
/datum/computer_file/program/nttransfer/proc/finalize_download()
|
||||
if(remote)
|
||||
remote.connected_clients.Remove(src)
|
||||
downloaded_file = null
|
||||
remote = null
|
||||
download_completion = 0
|
||||
|
||||
|
||||
/datum/nano_module/program/computer_nttransfer
|
||||
name = "NTNet P2P Transfer Client"
|
||||
|
||||
/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)
|
||||
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]
|
||||
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
|
||||
)))
|
||||
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)
|
||||
|
||||
/datum/computer_file/program/nttransfer/Topic(href, href_list)
|
||||
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
|
||||
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."
|
||||
return
|
||||
provided_file = F
|
||||
ntnet_global.fileservers.Add(src)
|
||||
return
|
||||
error = "I/O Error: Unable to locate file on hard drive."
|
||||
return 1
|
||||
if(href_list["PRG_uploadmenu"])
|
||||
upload_menu = 1
|
||||
return 0
|
||||
@@ -0,0 +1,235 @@
|
||||
/datum/computer_file/program/wordprocessor
|
||||
filename = "wordprocessor"
|
||||
filedesc = "NanoWord"
|
||||
extended_desc = "This program allows the editing and preview of text documents."
|
||||
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/
|
||||
var/browsing
|
||||
var/open_file
|
||||
var/loaded_data
|
||||
var/error
|
||||
var/is_edited
|
||||
|
||||
/datum/computer_file/program/wordprocessor/proc/get_file(var/filename)
|
||||
var/obj/item/weapon/computer_hardware/hard_drive/HDD = computer.hard_drive
|
||||
if(!HDD)
|
||||
return
|
||||
var/datum/computer_file/data/F = HDD.find_file_by_name(filename)
|
||||
if(!istype(F))
|
||||
return
|
||||
return F
|
||||
|
||||
/datum/computer_file/program/wordprocessor/proc/open_file(var/filename)
|
||||
var/datum/computer_file/data/F = get_file(filename)
|
||||
if(F)
|
||||
open_file = F.filename
|
||||
loaded_data = F.stored_data
|
||||
return 1
|
||||
|
||||
/datum/computer_file/program/wordprocessor/proc/save_file(var/filename)
|
||||
var/datum/computer_file/data/F = get_file(filename)
|
||||
if(!F) //try to make one if it doesn't exist
|
||||
F = create_file(filename, loaded_data)
|
||||
return !isnull(F)
|
||||
var/datum/computer_file/data/backup = F.clone()
|
||||
var/obj/item/weapon/computer_hardware/hard_drive/HDD = computer.hard_drive
|
||||
if(!HDD)
|
||||
return
|
||||
HDD.remove_file(F)
|
||||
F.stored_data = loaded_data
|
||||
F.calculate_size()
|
||||
if(!HDD.store_file(F))
|
||||
HDD.store_file(backup)
|
||||
return 0
|
||||
is_edited = 0
|
||||
return 1
|
||||
|
||||
/datum/computer_file/program/wordprocessor/proc/create_file(var/newname, var/data = "")
|
||||
if(!newname)
|
||||
return
|
||||
var/obj/item/weapon/computer_hardware/hard_drive/HDD = computer.hard_drive
|
||||
if(!HDD)
|
||||
return
|
||||
if(get_file(newname))
|
||||
return
|
||||
var/datum/computer_file/data/F = new/datum/computer_file/data()
|
||||
F.filename = newname
|
||||
F.filetype = "TXT"
|
||||
F.stored_data = data
|
||||
F.calculate_size()
|
||||
if(HDD.store_file(F))
|
||||
return F
|
||||
|
||||
/datum/computer_file/program/wordprocessor/Topic(href, href_list)
|
||||
if(..())
|
||||
return 1
|
||||
|
||||
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
|
||||
|
||||
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."}
|
||||
|
||||
to_chat(usr, help)
|
||||
return 1
|
||||
|
||||
if(href_list["PRG_closebrowser"])
|
||||
browsing = 0
|
||||
return 1
|
||||
|
||||
if(href_list["PRG_backtomenu"])
|
||||
error = null
|
||||
return 1
|
||||
|
||||
if(href_list["PRG_loadmenu"])
|
||||
browsing = 1
|
||||
return 1
|
||||
|
||||
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(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)
|
||||
|
||||
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"]]'."
|
||||
|
||||
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(href_list["PRG_savefile"])
|
||||
. = 1
|
||||
if(!open_file)
|
||||
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 1
|
||||
|
||||
if(href_list["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
|
||||
|
||||
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
|
||||
|
||||
/datum/nano_module/program/computer_wordprocessor
|
||||
name = "Word Processor"
|
||||
|
||||
/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
|
||||
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["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")
|
||||
files.Add(list(list(
|
||||
"name" = F.filename,
|
||||
"size" = F.size
|
||||
)))
|
||||
data["files"] = files
|
||||
|
||||
RHDD = PRG.computer.portable_drive
|
||||
if(RHDD)
|
||||
data["usbconnected"] = 1
|
||||
var/list/usbfiles[0]
|
||||
for(var/datum/computer_file/F in RHDD.stored_files)
|
||||
if(F.filetype == "TXT")
|
||||
usbfiles.Add(list(list(
|
||||
"name" = F.filename,
|
||||
"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
|
||||
data["filedata"] = pencode2html(PRG.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()
|
||||
Reference in New Issue
Block a user