[REVIEW] Ports Modular Computers from Baystation

This commit is contained in:
Neerti
2019-04-28 22:06:27 -04:00
committed by VirgoBot
parent 96669c91f0
commit 16b3b58259
242 changed files with 13200 additions and 6636 deletions
@@ -0,0 +1,129 @@
/datum/computer_file/program/access_decrypter
filename = "nt_accrypt"
filedesc = "NTNet Access Decrypter"
program_icon_state = "hostile"
program_key_state = "security_key"
program_menu_icon = "unlocked"
extended_desc = "This highly advanced script can very slowly decrypt operational codes used in almost any network. These codes can be downloaded to an ID card to expand the available access. The system administrator will probably notice this."
size = 34
requires_ntnet = 1
available_on_ntnet = 0
available_on_syndinet = 1
nanomodule_path = /datum/nano_module/program/access_decrypter/
var/message = ""
var/running = FALSE
var/progress = 0
var/target_progress = 300
var/datum/access/target_access = null
/datum/computer_file/program/access_decrypter/kill_program(var/forced)
reset()
..(forced)
/datum/computer_file/program/access_decrypter/proc/reset()
running = FALSE
message = ""
progress = 0
/datum/computer_file/program/access_decrypter/process_tick()
. = ..()
if(!running)
return
var/obj/item/weapon/computer_hardware/processor_unit/CPU = computer.processor_unit
var/obj/item/weapon/computer_hardware/card_slot/RFID = computer.card_slot
if(!istype(CPU) || !CPU.check_functionality() || !istype(RFID) || !RFID.check_functionality())
message = "A fatal hardware error has been detected."
return
if(!istype(RFID.stored_card))
message = "RFID card has been removed from the device. Operation aborted."
return
progress += CPU.max_idle_programs
if(progress >= target_progress)
reset()
RFID.stored_card.access |= target_access.id
if(ntnet_global.intrusion_detection_enabled)
ntnet_global.add_log("IDS WARNING - Unauthorised access to primary keycode database from device: [computer.network_card.get_network_tag()] - downloaded access codes for: [target_access.desc].")
ntnet_global.intrusion_detection_alarm = 1
message = "Successfully decrypted and saved operational key codes. Downloaded access codes for: [target_access.desc]"
target_access = null
/datum/computer_file/program/access_decrypter/Topic(href, href_list)
if(..())
return 1
if(href_list["PRG_reset"])
reset()
return 1
if(href_list["PRG_execute"])
if(running)
return 1
if(text2num(href_list["allowed"]))
return 1
var/obj/item/weapon/computer_hardware/processor_unit/CPU = computer.processor_unit
var/obj/item/weapon/computer_hardware/card_slot/RFID = computer.card_slot
if(!istype(CPU) || !CPU.check_functionality() || !istype(RFID) || !RFID.check_functionality())
message = "A fatal hardware error has been detected."
return
if(!istype(RFID.stored_card))
message = "RFID card is not present in the device. Operation aborted."
return
running = TRUE
target_access = get_access_by_id(href_list["PRG_execute"])
if(ntnet_global.intrusion_detection_enabled)
ntnet_global.add_log("IDS WARNING - Unauthorised access attempt to primary keycode database from device: [computer.network_card.get_network_tag()]")
ntnet_global.intrusion_detection_alarm = 1
return 1
/datum/nano_module/program/access_decrypter
name = "NTNet Access Decrypter"
var/list/restricted_access_codes = list(access_change_ids, access_network) // access codes that are not hackable due to balance reasons
/datum/nano_module/program/access_decrypter/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)
return
var/datum/computer_file/program/access_decrypter/PRG = program
var/list/data = list()
if(!istype(PRG))
return
data = PRG.get_header_data()
if(PRG.message)
data["message"] = PRG.message
else if(PRG.running)
data["running"] = 1
data["rate"] = PRG.computer.processor_unit.max_idle_programs
// Stolen from DOS traffic generator, generates strings of 1s and 0s
var/percentage = (PRG.progress / PRG.target_progress) * 100
var/list/strings[0]
for(var/j, j<10, j++)
var/string = ""
for(var/i, i<20, i++)
string = "[string][prob(percentage)]"
strings.Add(string)
data["dos_strings"] = strings
else if(program.computer.card_slot && program.computer.card_slot.stored_card)
var/obj/item/weapon/card/id/id_card = program.computer.card_slot.stored_card
var/list/regions = list()
for(var/i = 1; i <= 7; i++)
var/list/accesses = list()
for(var/access in get_region_accesses(i))
if (get_access_desc(access))
accesses.Add(list(list(
"desc" = replacetext(get_access_desc(access), " ", "&nbsp"),
"ref" = access,
"allowed" = (access in id_card.access) ? 1 : 0,
"blocked" = (access in restricted_access_codes) ? 1 : 0)))
regions.Add(list(list(
"name" = get_region_accesses_name(i),
"accesses" = accesses)))
data["regions"] = regions
ui = SSnanoui.try_update_ui(user, src, ui_key, ui, data, force_open)
if (!ui)
ui = new(user, src, ui_key, "access_decrypter.tmpl", "NTNet Access Decrypter", 550, 400, state = state)
ui.auto_update_layout = 1
ui.set_initial_data(data)
ui.open()
ui.set_auto_update(1)
@@ -0,0 +1,108 @@
/datum/computer_file/program/ntnet_dos
filename = "ntn_dos"
filedesc = "DoS Traffic Generator"
program_icon_state = "hostile"
program_key_state = "security_key"
program_menu_icon = "arrow-4-diag"
extended_desc = "This advanced script can perform denial of service attacks against NTNet quantum relays. The system administrator will probably notice this. Multiple devices can run this program together against same relay for increased effect"
size = 20
requires_ntnet = 1
available_on_ntnet = 0
available_on_syndinet = 1
nanomodule_path = /datum/nano_module/program/computer_dos/
var/obj/machinery/ntnet_relay/target = null
var/dos_speed = 0
var/error = ""
var/executed = 0
/datum/computer_file/program/ntnet_dos/process_tick()
dos_speed = 0
switch(ntnet_status)
if(1)
dos_speed = NTNETSPEED_LOWSIGNAL * NTNETSPEED_DOS_AMPLIFICATION
if(2)
dos_speed = NTNETSPEED_HIGHSIGNAL * NTNETSPEED_DOS_AMPLIFICATION
if(3)
dos_speed = NTNETSPEED_ETHERNET * NTNETSPEED_DOS_AMPLIFICATION
if(target && executed)
target.dos_overload += dos_speed
if(!target.operable())
target.dos_sources.Remove(src)
target = null
error = "Connection to destination relay lost."
/datum/computer_file/program/ntnet_dos/kill_program(var/forced)
if(target)
target.dos_sources.Remove(src)
target = null
executed = 0
..(forced)
/datum/nano_module/program/computer_dos
name = "DoS Traffic Generator"
/datum/nano_module/program/computer_dos/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)
return
var/datum/computer_file/program/ntnet_dos/PRG = program
var/list/data = list()
if(!istype(PRG))
return
data = PRG.get_header_data()
if(PRG.error)
data["error"] = PRG.error
else if(PRG.target && PRG.executed)
data["target"] = 1
data["speed"] = PRG.dos_speed
// This is mostly visual, generate some strings of 1s and 0s
// Probability of 1 is equal of completion percentage of DoS attack on this relay.
// Combined with UI updates this adds quite nice effect to the UI
var/percentage = PRG.target.dos_overload * 100 / PRG.target.dos_capacity
var/list/strings[0]
for(var/j, j<10, j++)
var/string = ""
for(var/i, i<20, i++)
string = "[string][prob(percentage)]"
strings.Add(string)
data["dos_strings"] = strings
else
var/list/relays[0]
for(var/obj/machinery/ntnet_relay/R in ntnet_global.relays)
relays.Add(R.uid)
data["relays"] = relays
data["focus"] = PRG.target ? PRG.target.uid : null
ui = SSnanoui.try_update_ui(user, src, ui_key, ui, data, force_open)
if (!ui)
ui = new(user, src, ui_key, "ntnet_dos.tmpl", "DoS Traffic Generator", 400, 250, state = state)
ui.auto_update_layout = 1
ui.set_initial_data(data)
ui.open()
ui.set_auto_update(1)
/datum/computer_file/program/ntnet_dos/Topic(href, href_list)
if(..())
return 1
if(href_list["PRG_target_relay"])
for(var/obj/machinery/ntnet_relay/R in ntnet_global.relays)
if("[R.uid]" == href_list["PRG_target_relay"])
target = R
return 1
if(href_list["PRG_reset"])
if(target)
target.dos_sources.Remove(src)
target = null
executed = 0
error = ""
return 1
if(href_list["PRG_execute"])
if(target)
executed = 1
target.dos_sources.Add(src)
if(ntnet_global.intrusion_detection_enabled)
ntnet_global.add_log("IDS WARNING - Excess traffic flood targeting relay [target.uid] detected from device: [computer.network_card.get_network_tag()]")
ntnet_global.intrusion_detection_alarm = 1
return 1
@@ -0,0 +1,39 @@
/datum/computer_file/program/camera_monitor/hacked
filename = "camcrypt"
filedesc = "Camera Decryption Tool"
nanomodule_path = /datum/nano_module/camera_monitor/hacked
program_icon_state = "hostile"
program_key_state = "security_key"
program_menu_icon = "zoomin"
extended_desc = "This very advanced piece of software uses adaptive programming and large database of cipherkeys to bypass most encryptions used on camera networks. Be warned that system administrator may notice this."
size = 73 // Very large, a price for bypassing ID checks completely.
available_on_ntnet = 0
available_on_syndinet = 1
/datum/computer_file/program/camera_monitor/hacked/process_tick()
..()
if(program_state != PROGRAM_STATE_ACTIVE) // Background programs won't trigger alarms.
return
var/datum/nano_module/camera_monitor/hacked/HNM = NM
// The program is active and connected to one of the station's networks. Has a very small chance to trigger IDS alarm every tick.
if(HNM && HNM.current_network && (HNM.current_network in using_map.station_networks) && prob(0.1))
if(ntnet_global.intrusion_detection_enabled)
ntnet_global.add_log("IDS WARNING - Unauthorised access detected to camera network [HNM.current_network] by device with NID [computer.network_card.get_network_tag()]")
ntnet_global.intrusion_detection_alarm = 1
/datum/nano_module/camera_monitor/hacked
name = "Hacked Camera Monitoring Program"
//available_to_ai = FALSE
/datum/nano_module/camera_monitor/hacked/can_access_network(var/mob/user, var/network_access)
return 1
// The hacked variant has access to all commonly used networks.
/datum/nano_module/camera_monitor/hacked/modify_networks_list(var/list/networks)
networks.Add(list(list("tag" = NETWORK_MERCENARY, "has_access" = 1)))
networks.Add(list(list("tag" = NETWORK_ERT, "has_access" = 1)))
networks.Add(list(list("tag" = NETWORK_CRESCENT, "has_access" = 1)))
return networks
@@ -0,0 +1,84 @@
/datum/computer_file/program/revelation
filename = "revelation"
filedesc = "Revelation"
program_icon_state = "hostile"
program_key_state = "security_key"
program_menu_icon = "home"
extended_desc = "This virus can destroy hard drive of system it is executed on. It may be obfuscated to look like another non-malicious program. Once armed, it will destroy the system upon next execution."
size = 13
requires_ntnet = 0
available_on_ntnet = 0
available_on_syndinet = 1
nanomodule_path = /datum/nano_module/program/revelation/
var/armed = 0
/datum/computer_file/program/revelation/run_program(var/mob/living/user)
. = ..(user)
if(armed)
activate()
/datum/computer_file/program/revelation/proc/activate()
if(!computer)
return
computer.visible_message("<span class='notice'>\The [computer]'s screen brightly flashes and loud electrical buzzing is heard.</span>")
computer.enabled = 0
computer.update_icon()
var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread
s.set_up(10, 1, computer.loc)
s.start()
if(computer.hard_drive)
qdel(computer.hard_drive)
if(computer.battery_module && prob(25))
qdel(computer.battery_module)
if(computer.tesla_link && prob(50))
qdel(computer.tesla_link)
/datum/computer_file/program/revelation/Topic(href, href_list)
if(..())
return 1
else if(href_list["PRG_arm"])
armed = !armed
else if(href_list["PRG_activate"])
activate()
else if(href_list["PRG_obfuscate"])
var/mob/living/user = usr
var/newname = sanitize(input(user, "Enter new program name: "))
if(!newname)
return
filedesc = newname
for(var/datum/computer_file/program/P in ntnet_global.available_station_software)
if(filedesc == P.filedesc)
program_menu_icon = P.program_menu_icon
break
return 1
/datum/computer_file/program/revelation/clone()
var/datum/computer_file/program/revelation/temp = ..()
temp.armed = armed
return temp
/datum/nano_module/program/revelation
name = "Revelation Virus"
/datum/nano_module/program/revelation/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 = list()
var/datum/computer_file/program/revelation/PRG = program
if(!istype(PRG))
return
data = PRG.get_header_data()
data["armed"] = PRG.armed
ui = SSnanoui.try_update_ui(user, src, ui_key, ui, data, force_open)
if (!ui)
ui = new(user, src, ui_key, "revelation.tmpl", "Revelation Virus", 400, 250, state = state)
ui.auto_update_layout = 1
ui.set_initial_data(data)
ui.open()
ui.set_auto_update(1)
@@ -0,0 +1,233 @@
/datum/computer_file/program/card_mod
filename = "cardmod"
filedesc = "ID card modification program"
nanomodule_path = /datum/nano_module/program/card_mod
program_icon_state = "id"
program_key_state = "id_key"
program_menu_icon = "key"
extended_desc = "Program for programming crew ID cards."
required_access = access_change_ids
requires_ntnet = 0
size = 8
/datum/nano_module/program/card_mod
name = "ID card modification program"
var/mod_mode = 1
var/is_centcom = 0
var/show_assignments = 0
/datum/nano_module/program/card_mod/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["src"] = "\ref[src]"
data["station_name"] = station_name()
data["manifest"] = data_core ? data_core.get_manifest(0) : null
data["assignments"] = show_assignments
if(program && program.computer)
data["have_id_slot"] = !!program.computer.card_slot
data["have_printer"] = !!program.computer.nano_printer
data["authenticated"] = program.can_run(user)
if(!program.computer.card_slot)
mod_mode = 0 //We can't modify IDs when there is no card reader
else
data["have_id_slot"] = 0
data["have_printer"] = 0
data["authenticated"] = 0
data["mmode"] = mod_mode
data["centcom_access"] = is_centcom
if(program && program.computer && program.computer.card_slot)
var/obj/item/weapon/card/id/id_card = program.computer.card_slot.stored_card
data["has_id"] = !!id_card
data["id_account_number"] = id_card ? id_card.associated_account_number : null
data["id_rank"] = id_card && id_card.assignment ? id_card.assignment : "Unassigned"
data["id_owner"] = id_card && id_card.registered_name ? id_card.registered_name : "-----"
data["id_name"] = id_card ? id_card.name : "-----"
data["command_jobs"] = format_jobs(command_positions)
data["engineering_jobs"] = format_jobs(engineering_positions)
data["medical_jobs"] = format_jobs(medical_positions)
data["science_jobs"] = format_jobs(science_positions)
data["security_jobs"] = format_jobs(security_positions)
data["cargo_jobs"] = format_jobs(cargo_positions)
data["civilian_jobs"] = format_jobs(civilian_positions)
data["centcom_jobs"] = format_jobs(get_all_centcom_jobs())
data["all_centcom_access"] = is_centcom ? get_accesses(1) : null
data["regions"] = get_accesses()
if(program.computer.card_slot && program.computer.card_slot.stored_card)
var/obj/item/weapon/card/id/id_card = program.computer.card_slot.stored_card
if(is_centcom)
var/list/all_centcom_access = list()
for(var/access in get_all_centcom_access())
all_centcom_access.Add(list(list(
"desc" = replacetext(get_centcom_access_desc(access), " ", "&nbsp"),
"ref" = access,
"allowed" = (access in id_card.access) ? 1 : 0)))
data["all_centcom_access"] = all_centcom_access
else
var/list/regions = list()
for(var/i = 1; i <= 7; i++)
var/list/accesses = list()
for(var/access in get_region_accesses(i))
if (get_access_desc(access))
accesses.Add(list(list(
"desc" = replacetext(get_access_desc(access), " ", "&nbsp"),
"ref" = access,
"allowed" = (access in id_card.access) ? 1 : 0)))
regions.Add(list(list(
"name" = get_region_accesses_name(i),
"accesses" = accesses)))
data["regions"] = regions
ui = SSnanoui.try_update_ui(user, src, ui_key, ui, data, force_open)
if (!ui)
ui = new(user, src, ui_key, "mod_identification_computer.tmpl", name, 600, 700, state = state)
ui.auto_update_layout = 1
ui.set_initial_data(data)
ui.open()
/datum/nano_module/program/card_mod/proc/format_jobs(list/jobs)
var/obj/item/weapon/card/id/id_card = program.computer.card_slot ? program.computer.card_slot.stored_card : null
var/list/formatted = list()
for(var/job in jobs)
formatted.Add(list(list(
"display_name" = replacetext(job, " ", "&nbsp"),
"target_rank" = id_card && id_card.assignment ? id_card.assignment : "Unassigned",
"job" = job)))
return formatted
/datum/nano_module/program/card_mod/proc/get_accesses(var/is_centcom = 0)
return null
/datum/computer_file/program/card_mod/Topic(href, href_list)
if(..())
return 1
var/mob/user = usr
var/obj/item/weapon/card/id/user_id_card = user.GetIdCard()
var/obj/item/weapon/card/id/id_card
if (computer.card_slot)
id_card = computer.card_slot.stored_card
var/datum/nano_module/program/card_mod/module = NM
switch(href_list["action"])
if("switchm")
if(href_list["target"] == "mod")
module.mod_mode = 1
else if (href_list["target"] == "manifest")
module.mod_mode = 0
if("togglea")
if(module.show_assignments)
module.show_assignments = 0
else
module.show_assignments = 1
if("print")
if(computer && computer.nano_printer) //This option should never be called if there is no printer
if(module.mod_mode)
if(can_run(user, 1))
var/contents = {"<h4>Access Report</h4>
<u>Prepared By:</u> [user_id_card.registered_name ? user_id_card.registered_name : "Unknown"]<br>
<u>For:</u> [id_card.registered_name ? id_card.registered_name : "Unregistered"]<br>
<hr>
<u>Assignment:</u> [id_card.assignment]<br>
<u>Account Number:</u> #[id_card.associated_account_number]<br>
<u>Blood Type:</u> [id_card.blood_type]<br><br>
<u>Access:</u><br>
"}
var/known_access_rights = get_access_ids(ACCESS_TYPE_STATION|ACCESS_TYPE_CENTCOM)
for(var/A in id_card.access)
if(A in known_access_rights)
contents += " [get_access_desc(A)]"
if(!computer.nano_printer.print_text(contents,"access report"))
to_chat(usr, "<span class='notice'>Hardware error: Printer was unable to print the file. It may be out of paper.</span>")
return
else
computer.visible_message("<span class='notice'>\The [computer] prints out paper.</span>")
else
var/contents = {"<h4>Crew Manifest</h4>
<br>
[data_core ? data_core.get_manifest(0) : ""]
"}
if(!computer.nano_printer.print_text(contents,text("crew manifest ([])", stationtime2text())))
to_chat(usr, "<span class='notice'>Hardware error: Printer was unable to print the file. It may be out of paper.</span>")
return
else
computer.visible_message("<span class='notice'>\The [computer] prints out paper.</span>")
if("eject")
if(computer && computer.card_slot)
if(id_card)
data_core.manifest_modify(id_card.registered_name, id_card.assignment)
computer.proc_eject_id(user)
if("terminate")
if(computer && can_run(user, 1))
id_card.assignment = "Terminated"
remove_nt_access(id_card)
callHook("terminate_employee", list(id_card))
if("edit")
if(computer && can_run(user, 1))
if(href_list["name"])
var/temp_name = sanitizeName(input("Enter name.", "Name", id_card.registered_name),allow_numbers=TRUE)
if(temp_name)
id_card.registered_name = temp_name
else
computer.visible_message("<span class='notice'>[computer] buzzes rudely.</span>")
else if(href_list["account"])
var/account_num = text2num(input("Enter account number.", "Account", id_card.associated_account_number))
id_card.associated_account_number = account_num
if("assign")
if(computer && can_run(user, 1) && id_card)
var/t1 = href_list["assign_target"]
if(t1 == "Custom")
var/temp_t = sanitize(input("Enter a custom job assignment.","Assignment", id_card.assignment), 45)
//let custom jobs function as an impromptu alt title, mainly for sechuds
if(temp_t)
id_card.assignment = temp_t
else
var/list/access = list()
if(module.is_centcom)
access = get_centcom_access(t1)
else
var/datum/job/jobdatum
for(var/jobtype in typesof(/datum/job))
var/datum/job/J = new jobtype
if(ckey(J.title) == ckey(t1))
jobdatum = J
break
if(!jobdatum)
to_chat(usr, "<span class='warning'>No log exists for this job: [t1]</span>")
return
access = jobdatum.get_access()
remove_nt_access(id_card)
apply_access(id_card, access)
id_card.assignment = t1
id_card.rank = t1
callHook("reassign_employee", list(id_card))
if("access")
if(href_list["allowed"] && computer && can_run(user, 1))
var/access_type = text2num(href_list["access_target"])
var/access_allowed = text2num(href_list["allowed"])
if(access_type in get_access_ids(ACCESS_TYPE_STATION|ACCESS_TYPE_CENTCOM))
id_card.access -= access_type
if(!access_allowed)
id_card.access += access_type
if(id_card)
id_card.name = text("[id_card.registered_name]'s ID Card ([id_card.assignment])")
SSnanoui.update_uis(NM)
return 1
/datum/computer_file/program/card_mod/proc/remove_nt_access(var/obj/item/weapon/card/id/id_card)
id_card.access -= get_access_ids(ACCESS_TYPE_STATION|ACCESS_TYPE_CENTCOM)
/datum/computer_file/program/card_mod/proc/apply_access(var/obj/item/weapon/card/id/id_card, var/list/accesses)
id_card.access |= accesses
@@ -0,0 +1,326 @@
#define STATE_DEFAULT 1
#define STATE_MESSAGELIST 2
#define STATE_VIEWMESSAGE 3
#define STATE_STATUSDISPLAY 4
#define STATE_ALERT_LEVEL 5
/datum/computer_file/program/comm
filename = "comm"
filedesc = "Command and Communications Program"
program_icon_state = "comm"
program_key_state = "med_key"
program_menu_icon = "flag"
nanomodule_path = /datum/nano_module/program/comm
extended_desc = "Used to command and control. Can relay long-range communications. This program can not be run on tablet computers."
required_access = access_heads
requires_ntnet = 1
size = 12
usage_flags = PROGRAM_CONSOLE | PROGRAM_LAPTOP
network_destination = "long-range communication array"
var/datum/comm_message_listener/message_core = new
/datum/computer_file/program/comm/clone()
var/datum/computer_file/program/comm/temp = ..()
temp.message_core.messages = null
temp.message_core.messages = message_core.messages.Copy()
return temp
/datum/nano_module/program/comm
name = "Command and Communications Program"
//available_to_ai = TRUE
var/current_status = STATE_DEFAULT
var/msg_line1 = ""
var/msg_line2 = ""
var/centcomm_message_cooldown = 0
var/announcment_cooldown = 0
var/datum/announcement/priority/crew_announcement = new
var/current_viewing_message_id = 0
var/current_viewing_message = null
/datum/nano_module/program/comm/New()
..()
crew_announcement.newscast = 1
/datum/nano_module/program/comm/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()
if(program)
data["emagged"] = program.computer_emagged
data["net_comms"] = !!program.get_signal(NTNET_COMMUNICATION) //Double !! is needed to get 1 or 0 answer
data["net_syscont"] = !!program.get_signal(NTNET_SYSTEMCONTROL)
if(program.computer)
data["have_printer"] = !!program.computer.nano_printer
else
data["have_printer"] = 0
else
data["emagged"] = 0
data["net_comms"] = 1
data["net_syscont"] = 1
data["have_printer"] = 0
data["message_line1"] = msg_line1
data["message_line2"] = msg_line2
data["state"] = current_status
data["isAI"] = issilicon(usr)
data["authenticated"] = is_autenthicated(user)
data["current_security_level"] = security_level
data["current_security_level_title"] = num2seclevel(security_level)
data["def_SEC_LEVEL_DELTA"] = SEC_LEVEL_DELTA
data["def_SEC_LEVEL_YELLOW"] = SEC_LEVEL_YELLOW
data["def_SEC_LEVEL_ORANGE"] = SEC_LEVEL_ORANGE
data["def_SEC_LEVEL_VIOLET"] = SEC_LEVEL_VIOLET
data["def_SEC_LEVEL_BLUE"] = SEC_LEVEL_BLUE
data["def_SEC_LEVEL_GREEN"] = SEC_LEVEL_GREEN
var/datum/comm_message_listener/l = obtain_message_listener()
data["messages"] = l.messages
data["message_deletion_allowed"] = l != global_message_listener
data["message_current_id"] = current_viewing_message_id
if(current_viewing_message)
data["message_current"] = current_viewing_message
if(emergency_shuttle.location())
data["have_shuttle"] = 1
if(emergency_shuttle.online())
data["have_shuttle_called"] = 1
else
data["have_shuttle_called"] = 0
else
data["have_shuttle"] = 0
ui = SSnanoui.try_update_ui(user, src, ui_key, ui, data, force_open)
if(!ui)
ui = new(user, src, ui_key, "mod_communication.tmpl", name, 550, 420, state = state)
ui.auto_update_layout = 1
ui.set_initial_data(data)
ui.open()
/datum/nano_module/program/comm/proc/is_autenthicated(var/mob/user)
if(program)
return program.can_run(user)
return 1
/datum/nano_module/program/comm/proc/obtain_message_listener()
if(program)
var/datum/computer_file/program/comm/P = program
return P.message_core
return global_message_listener
/datum/nano_module/program/comm/Topic(href, href_list)
if(..())
return 1
var/mob/user = usr
var/ntn_comm = program ? !!program.get_signal(NTNET_COMMUNICATION) : 1
var/ntn_cont = program ? !!program.get_signal(NTNET_SYSTEMCONTROL) : 1
var/datum/comm_message_listener/l = obtain_message_listener()
switch(href_list["action"])
if("sw_menu")
. = 1
current_status = text2num(href_list["target"])
if("announce")
. = 1
if(is_autenthicated(user) && !issilicon(usr) && ntn_comm)
if(user)
var/obj/item/weapon/card/id/id_card = user.GetIdCard()
crew_announcement.announcer = GetNameAndAssignmentFromId(id_card)
else
crew_announcement.announcer = "Unknown"
if(announcment_cooldown)
to_chat(usr, "Please allow at least one minute to pass between announcements")
return TRUE
var/input = input(usr, "Please write a message to announce to the station crew.", "Priority Announcement") as null|text
if(!input || !can_still_topic())
return 1
crew_announcement.Announce(input)
announcment_cooldown = 1
spawn(600)//One minute cooldown
announcment_cooldown = 0
if("message")
. = 1
if(href_list["target"] == "emagged")
if(program)
if(is_autenthicated(user) && program.computer_emagged && !issilicon(usr) && ntn_comm)
if(centcomm_message_cooldown)
to_chat(usr, "<span class='warning'>Arrays recycling. Please stand by.</span>")
SSnanoui.update_uis(src)
return
var/input = sanitize(input(usr, "Please choose a message to transmit to \[ABNORMAL ROUTING CORDINATES\] via quantum entanglement. Please be aware that this process is very expensive, and abuse will lead to... termination. Transmission does not guarantee a response. There is a 30 second delay before you may send another message, be clear, full and concise.", "To abort, send an empty message.", "") as null|text)
if(!input || !can_still_topic())
return 1
Syndicate_announce(input, usr)
to_chat(usr, "<span class='notice'>Message transmitted.</span>")
log_say("[key_name(usr)] has made an illegal announcement: [input]")
centcomm_message_cooldown = 1
spawn(300)//30 second cooldown
centcomm_message_cooldown = 0
else if(href_list["target"] == "regular")
if(is_autenthicated(user) && !issilicon(usr) && ntn_comm)
if(centcomm_message_cooldown)
to_chat(usr, "<span class='warning'>Arrays recycling. Please stand by.</span>")
SSnanoui.update_uis(src)
return
if(!is_relay_online())//Contact Centcom has a check, Syndie doesn't to allow for Traitor funs.
to_chat(usr, "<span class='warning'>No Emergency Bluespace Relay detected. Unable to transmit message.</span>")
return 1
var/input = sanitize(input("Please choose a message to transmit to Centcomm via quantum entanglement. Please be aware that this process is very expensive, and abuse will lead to... termination. Transmission does not guarantee a response. There is a 30 second delay before you may send another message, be clear, full and concise.", "To abort, send an empty message.", "") as null|text)
if(!input || !can_still_topic())
return 1
CentCom_announce(input, usr)
to_chat(usr, "<span class='notice'>Message transmitted.</span>")
log_say("[key_name(usr)] has made an IA Centcomm announcement: [input]")
centcomm_message_cooldown = 1
spawn(300) //30 second cooldown
centcomm_message_cooldown = 0
if("shuttle")
. = 1
if(is_autenthicated(user) && ntn_cont)
if(href_list["target"] == "call")
var/confirm = alert("Are you sure you want to call the shuttle?", name, "No", "Yes")
if(confirm == "Yes" && can_still_topic())
call_shuttle_proc(usr)
if(href_list["target"] == "cancel" && !issilicon(usr))
var/confirm = alert("Are you sure you want to cancel the shuttle?", name, "No", "Yes")
if(confirm == "Yes" && can_still_topic())
cancel_call_proc(usr)
if("setstatus")
. = 1
if(is_autenthicated(user) && ntn_cont)
switch(href_list["target"])
if("line1")
var/linput = reject_bad_text(sanitize(input("Line 1", "Enter Message Text", msg_line1) as text|null, 40), 40)
if(can_still_topic())
msg_line1 = linput
if("line2")
var/linput = reject_bad_text(sanitize(input("Line 2", "Enter Message Text", msg_line2) as text|null, 40), 40)
if(can_still_topic())
msg_line2 = linput
if("message")
post_status("message", msg_line1, msg_line2)
if("alert")
post_status("alert", href_list["alert"])
else
post_status(href_list["target"])
if("setalert")
. = 1
if(is_autenthicated(user) && !issilicon(usr) && ntn_cont && ntn_comm)
var/current_level = text2num(href_list["target"])
var/confirm = alert("Are you sure you want to change alert level to [num2seclevel(current_level)]?", name, "No", "Yes")
if(confirm == "Yes" && can_still_topic())
var/old_level = security_level
if(!current_level) current_level = SEC_LEVEL_GREEN
if(current_level < SEC_LEVEL_GREEN) current_level = SEC_LEVEL_GREEN
if(current_level > SEC_LEVEL_BLUE) current_level = SEC_LEVEL_BLUE //Cannot engage delta with this
set_security_level(current_level)
if(security_level != old_level)
log_game("[key_name(usr)] has changed the security level to [get_security_level()].")
message_admins("[key_name_admin(usr)] has changed the security level to [get_security_level()].")
switch(security_level)
if(SEC_LEVEL_GREEN)
feedback_inc("alert_comms_green",1)
if(SEC_LEVEL_YELLOW)
feedback_inc("alert_comms_yellow",1)
if(SEC_LEVEL_ORANGE)
feedback_inc("alert_comms_orange",1)
if(SEC_LEVEL_VIOLET)
feedback_inc("alert_comms_violet",1)
if(SEC_LEVEL_BLUE)
feedback_inc("alert_comms_blue",1)
else
to_chat(usr, "You press button, but red light flashes and nothing happens.")//This should never happen
current_status = STATE_DEFAULT
if("viewmessage")
. = 1
if(is_autenthicated(user) && ntn_comm)
current_viewing_message_id = text2num(href_list["target"])
for(var/list/m in l.messages)
if(m["id"] == current_viewing_message_id)
current_viewing_message = m
current_status = STATE_VIEWMESSAGE
if("delmessage")
. = 1
if(is_autenthicated(user) && ntn_comm && l != global_message_listener)
l.Remove(current_viewing_message)
current_status = STATE_MESSAGELIST
if("printmessage")
. = 1
if(is_autenthicated(user) && ntn_comm)
if(program && program.computer && program.computer.nano_printer)
if(!program.computer.nano_printer.print_text(current_viewing_message["contents"],current_viewing_message["title"]))
to_chat(usr, "<span class='notice'>Hardware error: Printer was unable to print the file. It may be out of paper.</span>")
else
program.computer.visible_message("<span class='notice'>\The [program.computer] prints out paper.</span>")
/datum/nano_module/program/comm/proc/post_status(var/command, var/data1, var/data2)
var/datum/radio_frequency/frequency = radio_controller.return_frequency(1435)
if(!frequency) return
var/datum/signal/status_signal = new
status_signal.source = src
status_signal.transmission_method = 1
status_signal.data["command"] = command
switch(command)
if("message")
status_signal.data["msg1"] = data1
status_signal.data["msg2"] = data2
log_admin("STATUS: [key_name(usr)] set status screen message with [src]: [data1] [data2]")
if("alert")
status_signal.data["picture_state"] = data1
frequency.post_signal(src, status_signal)
#undef STATE_DEFAULT
#undef STATE_MESSAGELIST
#undef STATE_VIEWMESSAGE
#undef STATE_STATUSDISPLAY
#undef STATE_ALERT_LEVEL
/*
General message handling stuff
*/
var/list/comm_message_listeners = list() //We first have to initialize list then we can use it.
var/datum/comm_message_listener/global_message_listener = new //May be used by admins
var/last_message_id = 0
proc/get_comm_message_id()
last_message_id = last_message_id + 1
return last_message_id
proc/post_comm_message(var/message_title, var/message_text)
var/list/message = list()
message["id"] = get_comm_message_id()
message["title"] = message_title
message["contents"] = message_text
for (var/datum/comm_message_listener/l in comm_message_listeners)
l.Add(message)
//Old console support
for (var/obj/machinery/computer/communications/comm in machines)
if (!(comm.stat & (BROKEN | NOPOWER)) && comm.prints_intercept)
var/obj/item/weapon/paper/intercept = new /obj/item/weapon/paper( comm.loc )
intercept.name = message_title
intercept.info = message_text
comm.messagetitle.Add(message_title)
comm.messagetext.Add(message_text)
/datum/comm_message_listener
var/list/messages
/datum/comm_message_listener/New()
..()
messages = list()
comm_message_listeners.Add(src)
/datum/comm_message_listener/proc/Add(var/list/message)
messages[++messages.len] = message
/datum/comm_message_listener/proc/Remove(var/list/message)
messages -= list(message)
@@ -0,0 +1,134 @@
/datum/computer_file/program/alarm_monitor
filename = "alarmmonitor"
filedesc = "Alarm Monitoring"
nanomodule_path = /datum/nano_module/alarm_monitor/engineering
ui_header = "alarm_green.gif"
program_icon_state = "alert-green"
program_key_state = "atmos_key"
program_menu_icon = "alert"
extended_desc = "This program provides visual interface for the alarm system."
requires_ntnet = 1
network_destination = "alarm monitoring network"
size = 5
var/has_alert = 0
/datum/computer_file/program/alarm_monitor/process_tick()
..()
var/datum/nano_module/alarm_monitor/NMA = NM
if(istype(NMA) && NMA.has_major_alarms())
if(!has_alert)
program_icon_state = "alert-red"
ui_header = "alarm_red.gif"
update_computer_icon()
has_alert = 1
else
if(has_alert)
program_icon_state = "alert-green"
ui_header = "alarm_green.gif"
update_computer_icon()
has_alert = 0
return 1
/datum/nano_module/alarm_monitor
name = "Alarm monitor"
var/list_cameras = 0 // Whether or not to list camera references. A future goal would be to merge this with the enginering/security camera console. Currently really only for AI-use.
var/list/datum/alarm_handler/alarm_handlers // The particular list of alarm handlers this alarm monitor should present to the user.
//available_to_ai = FALSE
/datum/nano_module/alarm_monitor/New()
..()
alarm_handlers = list()
/datum/nano_module/alarm_monitor/all/New()
..()
alarm_handlers = alarm_manager.all_handlers
/datum/nano_module/alarm_monitor/engineering/New()
..()
alarm_handlers = list(atmosphere_alarm, camera_alarm, fire_alarm, power_alarm)
/datum/nano_module/alarm_monitor/security/New()
..()
alarm_handlers = list(camera_alarm, motion_alarm)
/datum/nano_module/alarm_monitor/proc/register_alarm(var/object, var/procName)
for(var/datum/alarm_handler/AH in alarm_handlers)
AH.register_alarm(object, procName)
/datum/nano_module/alarm_monitor/proc/unregister_alarm(var/object)
for(var/datum/alarm_handler/AH in alarm_handlers)
AH.unregister_alarm(object)
/datum/nano_module/alarm_monitor/proc/all_alarms()
var/list/all_alarms = new()
for(var/datum/alarm_handler/AH in alarm_handlers)
all_alarms += AH.visible_alarms()
return all_alarms
/datum/nano_module/alarm_monitor/proc/major_alarms()
var/list/all_alarms = new()
for(var/datum/alarm_handler/AH in alarm_handlers)
all_alarms += AH.major_alarms()
return all_alarms
// Modified version of above proc that uses slightly less resources, returns 1 if there is a major alarm, 0 otherwise.
/datum/nano_module/alarm_monitor/proc/has_major_alarms()
for(var/datum/alarm_handler/AH in alarm_handlers)
if(AH.has_major_alarms())
return 1
return 0
/datum/nano_module/alarm_monitor/proc/minor_alarms()
var/list/all_alarms = new()
for(var/datum/alarm_handler/AH in alarm_handlers)
all_alarms += AH.minor_alarms()
return all_alarms
/datum/nano_module/alarm_monitor/Topic(ref, href_list)
if(..())
return 1
if(href_list["switchTo"])
var/obj/machinery/camera/C = locate(href_list["switchTo"]) in cameranet.cameras
if(!C)
return
usr.switch_to_camera(C)
return 1
/datum/nano_module/alarm_monitor/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/categories[0]
for(var/datum/alarm_handler/AH in alarm_handlers)
categories[++categories.len] = list("category" = AH.category, "alarms" = list())
for(var/datum/alarm/A in AH.major_alarms())
var/cameras[0]
var/lost_sources[0]
if(isAI(user))
for(var/obj/machinery/camera/C in A.cameras())
cameras[++cameras.len] = C.nano_structure()
for(var/datum/alarm_source/AS in A.sources)
if(!AS.source)
lost_sources[++lost_sources.len] = AS.source_name
categories[categories.len]["alarms"] += list(list(
"name" = sanitize(A.alarm_name()),
"origin_lost" = A.origin == null,
"has_cameras" = cameras.len,
"cameras" = cameras,
"lost_sources" = lost_sources.len ? sanitize(english_list(lost_sources, nothing_text = "", and_text = ", ")) : ""))
data["categories"] = categories
ui = SSnanoui.try_update_ui(user, src, ui_key, ui, data, force_open)
if (!ui)
ui = new(user, src, ui_key, "alarm_monitor.tmpl", "Alarm Monitoring Console", 800, 800, state = state)
if(host.update_layout()) // This is necessary to ensure the status bar remains updated along with rest of the UI.
ui.auto_update_layout = 1
ui.set_initial_data(data)
ui.open()
ui.set_auto_update(1)
@@ -0,0 +1,103 @@
/datum/computer_file/program/atmos_control
filename = "atmoscontrol"
filedesc = "Atmosphere Control"
nanomodule_path = /datum/nano_module/atmos_control
program_icon_state = "atmos_control"
program_key_state = "atmos_key"
program_menu_icon = "shuffle"
extended_desc = "This program allows remote control of air alarms. This program can not be run on tablet computers."
required_access = access_atmospherics
requires_ntnet = 1
network_destination = "atmospheric control system"
requires_ntnet_feature = NTNET_SYSTEMCONTROL
usage_flags = PROGRAM_LAPTOP | PROGRAM_CONSOLE
size = 17
/datum/nano_module/atmos_control
name = "Atmospherics Control"
var/obj/access = new()
var/emagged = 0
var/ui_ref
var/list/monitored_alarms = list()
/datum/nano_module/atmos_control/New(atmos_computer, req_access, req_one_access, monitored_alarm_ids)
..()
access.req_access = req_access
access.req_one_access = req_one_access
if(monitored_alarm_ids)
for(var/obj/machinery/alarm/alarm in machines)
if(alarm.alarm_id && alarm.alarm_id in monitored_alarm_ids)
monitored_alarms += alarm
// machines may not yet be ordered at this point
monitored_alarms = dd_sortedObjectList(monitored_alarms)
/datum/nano_module/atmos_control/Topic(href, href_list)
if(..())
return 1
if(href_list["alarm"])
if(ui_ref)
var/obj/machinery/alarm/alarm = locate(href_list["alarm"]) in (monitored_alarms.len ? monitored_alarms : machines)
if(alarm)
var/datum/topic_state/TS = generate_state(alarm)
alarm.ui_interact(usr, master_ui = ui_ref, state = TS)
return 1
/datum/nano_module/atmos_control/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1, var/master_ui = null, var/datum/topic_state/state = default_state)
var/list/data = host.initial_data()
var/alarms[0]
var/turf/T = get_turf(nano_host())
// TODO: Move these to a cache, similar to cameras
for(var/obj/machinery/alarm/alarm in (monitored_alarms.len ? monitored_alarms : machines))
if(!monitored_alarms.len && alarm.alarms_hidden)
continue
alarms[++alarms.len] = list(
"name" = sanitize(alarm.name),
"ref"= "\ref[alarm]",
"danger" = max(alarm.danger_level, alarm.alarm_area.atmosalm),
"x" = alarm.x,
"y" = alarm.y,
"z" = alarm.z)
data["alarms"] = alarms
data["map_levels"] = using_map.get_map_levels(T.z)
ui = SSnanoui.try_update_ui(user, src, ui_key, ui, data, force_open)
if(!ui)
ui = new(user, src, ui_key, "atmos_control.tmpl", src.name, 625, 625, state = state)
if(host.update_layout()) // This is necessary to ensure the status bar remains updated along with rest of the UI.
ui.auto_update_layout = 1
// adding a template with the key "mapContent" enables the map ui functionality
ui.add_template("mapContent", "atmos_control_map_content.tmpl")
// adding a template with the key "mapHeader" replaces the map header content
ui.add_template("mapHeader", "atmos_control_map_header.tmpl")
ui.set_initial_data(data)
ui.open()
ui.set_auto_update(0)
ui_ref = ui
/datum/nano_module/atmos_control/proc/generate_state(air_alarm)
var/datum/topic_state/air_alarm/state = new()
state.atmos_control = src
state.air_alarm = air_alarm
return state
/datum/topic_state/air_alarm
var/datum/nano_module/atmos_control/atmos_control = null
var/obj/machinery/alarm/air_alarm = null
/datum/topic_state/air_alarm/can_use_topic(var/src_object, var/mob/user)
if(has_access(user))
return STATUS_INTERACTIVE
return STATUS_UPDATE
/datum/topic_state/air_alarm/href_list(var/mob/user)
var/list/extra_href = list()
extra_href["remote_connection"] = 1
extra_href["remote_access"] = has_access(user)
return extra_href
/datum/topic_state/air_alarm/proc/has_access(var/mob/user)
return user && (isAI(user) || atmos_control.access.allowed(user) || atmos_control.emagged || air_alarm.rcon_setting == RCON_YES || (air_alarm.alarm_area.atmosalm && air_alarm.rcon_setting == RCON_AUTO))
@@ -0,0 +1,113 @@
/datum/computer_file/program/power_monitor
filename = "powermonitor"
filedesc = "Power Monitoring"
nanomodule_path = /datum/nano_module/power_monitor/
program_icon_state = "power_monitor"
program_key_state = "power_key"
program_menu_icon = "battery-3"
extended_desc = "This program connects to sensors to provide information about electrical systems"
ui_header = "power_norm.gif"
required_access = access_engine
requires_ntnet = 1
network_destination = "power monitoring system"
size = 9
var/has_alert = 0
/datum/computer_file/program/power_monitor/process_tick()
..()
var/datum/nano_module/power_monitor/NMA = NM
if(istype(NMA) && NMA.has_alarm())
if(!has_alert)
program_icon_state = "power_monitor_warn"
ui_header = "power_warn.gif"
update_computer_icon()
has_alert = 1
else
if(has_alert)
program_icon_state = "power_monitor"
ui_header = "power_norm.gif"
update_computer_icon()
has_alert = 0
/datum/nano_module/power_monitor
name = "Power monitor"
var/list/grid_sensors
var/active_sensor = null //name_tag of the currently selected sensor
/datum/nano_module/power_monitor/New()
..()
refresh_sensors()
// Checks whether there is an active alarm, if yes, returns 1, otherwise returns 0.
/datum/nano_module/power_monitor/proc/has_alarm()
for(var/obj/machinery/power/sensor/S in grid_sensors)
if(S.check_grid_warning())
return 1
return 0
// If PC is not null header template is loaded. Use PC.get_header_data() to get relevant nanoui data from it. All data entries begin with "PC_...."
// In future it may be expanded to other modular computer devices.
/datum/nano_module/power_monitor/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/list/sensors = list()
// Focus: If it remains null if no sensor is selected and UI will display sensor list, otherwise it will display sensor reading.
var/obj/machinery/power/sensor/focus = null
var/turf/T = get_turf(nano_host())
// Build list of data from sensor readings.
for(var/obj/machinery/power/sensor/S in grid_sensors)
sensors.Add(list(list(
"name" = S.name_tag,
"alarm" = S.check_grid_warning()
)))
if(S.name_tag == active_sensor)
focus = S
data["all_sensors"] = sensors
if(focus)
data["focus"] = focus.return_reading_data()
data["map_levels"] = using_map.get_map_levels(T.z)
ui = SSnanoui.try_update_ui(user, src, ui_key, ui, data, force_open)
if (!ui)
ui = new(user, src, ui_key, "power_monitor.tmpl", "Power Monitoring Console", 800, 500, state = state)
if(host.update_layout()) // This is necessary to ensure the status bar remains updated along with rest of the UI.
ui.auto_update_layout = 1
// adding a template with the key "mapContent" enables the map ui functionality
ui.add_template("mapContent", "power_monitor_map_content.tmpl")
// adding a template with the key "mapHeader" replaces the map header content
ui.add_template("mapHeader", "power_monitor_map_header.tmpl")
ui.set_initial_data(data)
ui.open()
ui.set_auto_update(1)
// Refreshes list of active sensors kept on this computer.
/datum/nano_module/power_monitor/proc/refresh_sensors()
grid_sensors = list()
var/turf/T = get_turf(nano_host())
var/list/levels = list()
if(!T) // Safety check
return
if(T)
levels += using_map.get_map_levels(T.z, FALSE)
for(var/obj/machinery/power/sensor/S in machines)
if(T && (S.loc.z == T.z) || (S.loc.z in levels) || (S.long_range)) // Consoles have range on their Z-Level. Sensors with long_range var will work between Z levels.
if(S.name_tag == "#UNKN#") // Default name. Shouldn't happen!
warning("Powernet sensor with unset ID Tag! [S.x]X [S.y]Y [S.z]Z")
else
grid_sensors += S
// Allows us to process UI clicks, which are relayed in form of hrefs.
/datum/nano_module/power_monitor/Topic(href, href_list)
if(..())
return 1
if( href_list["clear"] )
active_sensor = null
. = 1
if( href_list["refresh"] )
refresh_sensors()
. = 1
else if( href_list["setsensor"] )
active_sensor = href_list["setsensor"]
. = 1
@@ -0,0 +1,132 @@
/datum/computer_file/program/rcon_console
filename = "rconconsole"
filedesc = "RCON Remote Control"
nanomodule_path = /datum/nano_module/rcon
program_icon_state = "generic"
program_key_state = "rd_key"
program_menu_icon = "power"
extended_desc = "This program allows remote control of power distribution systems. This program can not be run on tablet computers."
required_access = access_engine
requires_ntnet = 1
network_destination = "RCON remote control system"
requires_ntnet_feature = NTNET_SYSTEMCONTROL
usage_flags = PROGRAM_LAPTOP | PROGRAM_CONSOLE
size = 19
/datum/nano_module/rcon
name = "Power RCON"
var/list/known_SMESs = null
var/list/known_breakers = null
// Allows you to hide specific parts of the UI
var/hide_SMES = 0
var/hide_SMES_details = 0
var/hide_breakers = 0
/datum/nano_module/rcon/ui_interact(mob/user, ui_key = "rcon", datum/nanoui/ui=null, force_open=1, var/datum/topic_state/state = default_state)
FindDevices() // Update our devices list
var/list/data = host.initial_data()
// SMES DATA (simplified view)
var/list/smeslist[0]
for(var/obj/machinery/power/smes/buildable/SMES in known_SMESs)
smeslist.Add(list(list(
"charge" = round(SMES.Percentage()),
"input_set" = SMES.input_attempt,
"input_val" = round(SMES.input_level/1000, 0.1),
"output_set" = SMES.output_attempt,
"output_val" = round(SMES.output_level/1000, 0.1),
"output_load" = round(SMES.output_used/1000, 0.1),
"RCON_tag" = SMES.RCon_tag
)))
data["smes_info"] = sortByKey(smeslist, "RCON_tag")
// BREAKER DATA (simplified view)
var/list/breakerlist[0]
for(var/obj/machinery/power/breakerbox/BR in known_breakers)
breakerlist.Add(list(list(
"RCON_tag" = BR.RCon_tag,
"enabled" = BR.on
)))
data["breaker_info"] = breakerlist
data["hide_smes"] = hide_SMES
data["hide_smes_details"] = hide_SMES_details
data["hide_breakers"] = hide_breakers
ui = SSnanoui.try_update_ui(user, src, ui_key, ui, data, force_open)
if (!ui)
ui = new(user, src, ui_key, "rcon.tmpl", "RCON Console", 600, 400, state = state)
if(host.update_layout()) // This is necessary to ensure the status bar remains updated along with rest of the UI.
ui.auto_update_layout = 1
ui.set_initial_data(data)
ui.open()
ui.set_auto_update(1)
// Proc: Topic()
// Parameters: 2 (href, href_list - allows us to process UI clicks)
// Description: Allows us to process UI clicks, which are relayed in form of hrefs.
/datum/nano_module/rcon/Topic(href, href_list)
if(..())
return
if(href_list["smes_in_toggle"])
var/obj/machinery/power/smes/buildable/SMES = GetSMESByTag(href_list["smes_in_toggle"])
if(SMES)
SMES.toggle_input()
if(href_list["smes_out_toggle"])
var/obj/machinery/power/smes/buildable/SMES = GetSMESByTag(href_list["smes_out_toggle"])
if(SMES)
SMES.toggle_output()
if(href_list["smes_in_set"])
var/obj/machinery/power/smes/buildable/SMES = GetSMESByTag(href_list["smes_in_set"])
if(SMES)
var/inputset = (input(usr, "Enter new input level (0-[SMES.input_level_max/1000] kW)", "SMES Input Power Control", SMES.input_level/1000) as num) * 1000
SMES.set_input(inputset)
if(href_list["smes_out_set"])
var/obj/machinery/power/smes/buildable/SMES = GetSMESByTag(href_list["smes_out_set"])
if(SMES)
var/outputset = (input(usr, "Enter new output level (0-[SMES.output_level_max/1000] kW)", "SMES Output Power Control", SMES.output_level/1000) as num) * 1000
SMES.set_output(outputset)
if(href_list["toggle_breaker"])
var/obj/machinery/power/breakerbox/toggle = null
for(var/obj/machinery/power/breakerbox/breaker in known_breakers)
if(breaker.RCon_tag == href_list["toggle_breaker"])
toggle = breaker
if(toggle)
if(toggle.update_locked)
to_chat(usr, "The breaker box was recently toggled. Please wait before toggling it again.")
else
toggle.auto_toggle()
if(href_list["hide_smes"])
hide_SMES = !hide_SMES
if(href_list["hide_smes_details"])
hide_SMES_details = !hide_SMES_details
if(href_list["hide_breakers"])
hide_breakers = !hide_breakers
// Proc: GetSMESByTag()
// Parameters: 1 (tag - RCON tag of SMES we want to look up)
// Description: Looks up and returns SMES which has matching RCON tag
/datum/nano_module/rcon/proc/GetSMESByTag(var/tag)
if(!tag)
return
for(var/obj/machinery/power/smes/buildable/S in known_SMESs)
if(S.RCon_tag == tag)
return S
// Proc: FindDevices()
// Parameters: None
// Description: Refreshes local list of known devices.
/datum/nano_module/rcon/proc/FindDevices()
known_SMESs = new /list()
for(var/obj/machinery/power/smes/buildable/SMES in machines)
if(SMES.RCon_tag && (SMES.RCon_tag != "NO_TAG") && SMES.RCon)
known_SMESs.Add(SMES)
known_breakers = new /list()
for(var/obj/machinery/power/breakerbox/breaker in machines)
if(breaker.RCon_tag != "NO_TAG")
known_breakers.Add(breaker)
@@ -0,0 +1,132 @@
/datum/computer_file/program/supermatter_monitor
filename = "supmon"
filedesc = "Supermatter Monitoring"
nanomodule_path = /datum/nano_module/supermatter_monitor/
program_icon_state = "smmon_0"
program_key_state = "tech_key"
program_menu_icon = "notice"
extended_desc = "This program connects to specially calibrated supermatter sensors to provide information on the status of supermatter-based engines."
ui_header = "smmon_0.gif"
required_access = access_engine
requires_ntnet = 1
network_destination = "supermatter monitoring system"
size = 5
var/last_status = 0
/datum/computer_file/program/supermatter_monitor/process_tick()
..()
var/datum/nano_module/supermatter_monitor/NMS = NM
var/new_status = istype(NMS) ? NMS.get_status() : 0
if(last_status != new_status)
last_status = new_status
ui_header = "smmon_[last_status].gif"
program_icon_state = "smmon_[last_status]"
if(istype(computer))
computer.update_icon()
/datum/nano_module/supermatter_monitor
name = "Supermatter monitor"
var/list/supermatters
var/obj/machinery/power/supermatter/active = null // Currently selected supermatter crystal.
/datum/nano_module/supermatter_monitor/Destroy()
. = ..()
active = null
supermatters = null
/datum/nano_module/supermatter_monitor/New()
..()
refresh()
// Refreshes list of active supermatter crystals
/datum/nano_module/supermatter_monitor/proc/refresh()
supermatters = list()
var/turf/T = get_turf(nano_host())
if(!T)
return
var/valid_z_levels = (GetConnectedZlevels(T.z) & using_map.station_levels)
for(var/obj/machinery/power/supermatter/S in machines)
// Delaminating, not within coverage, not on a tile.
if(S.grav_pulling || S.exploded || !(S.z in valid_z_levels) || !istype(S.loc, /turf/))
continue
supermatters.Add(S)
if(!(active in supermatters))
active = null
/datum/nano_module/supermatter_monitor/proc/get_status()
. = SUPERMATTER_INACTIVE
for(var/obj/machinery/power/supermatter/S in supermatters)
. = max(., S.get_status())
/datum/nano_module/supermatter_monitor/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()
if(istype(active))
var/turf/T = get_turf(active)
if(!T)
active = null
return
var/datum/gas_mixture/air = T.return_air()
if(!istype(air))
active = null
return
data["active"] = 1
data["SM_integrity"] = active.get_integrity()
data["SM_power"] = active.power
data["SM_ambienttemp"] = air.temperature
data["SM_ambientpressure"] = air.return_pressure()
//data["SM_EPR"] = active.get_epr()
if(air.total_moles)
data["SM_gas_O2"] = round(100*air.gas["oxygen"]/air.total_moles,0.01)
data["SM_gas_CO2"] = round(100*air.gas["carbon_dioxide"]/air.total_moles,0.01)
data["SM_gas_N2"] = round(100*air.gas["nitrogen"]/air.total_moles,0.01)
data["SM_gas_PH"] = round(100*air.gas["phoron"]/air.total_moles,0.01)
data["SM_gas_N2O"] = round(100*air.gas["sleeping_agent"]/air.total_moles,0.01)
else
data["SM_gas_O2"] = 0
data["SM_gas_CO2"] = 0
data["SM_gas_N2"] = 0
data["SM_gas_PH"] = 0
data["SM_gas_N2O"] = 0
else
var/list/SMS = list()
for(var/obj/machinery/power/supermatter/S in supermatters)
var/area/A = get_area(S)
if(!A)
continue
SMS.Add(list(list(
"area_name" = A.name,
"integrity" = S.get_integrity(),
"uid" = S.uid
)))
data["active"] = 0
data["supermatters"] = SMS
ui = SSnanoui.try_update_ui(user, src, ui_key, ui, data, force_open)
if (!ui)
ui = new(user, src, ui_key, "supermatter_monitor.tmpl", "Supermatter Monitoring", 600, 400, state = state)
if(host.update_layout())
ui.auto_update_layout = 1
ui.set_initial_data(data)
ui.open()
ui.set_auto_update(1)
/datum/nano_module/supermatter_monitor/Topic(href, href_list)
if(..())
return 1
if( href_list["clear"] )
active = null
return 1
if( href_list["refresh"] )
refresh()
return 1
if( href_list["set"] )
var/newuid = text2num(href_list["set"])
for(var/obj/machinery/power/supermatter/S in supermatters)
if(S.uid == newuid)
active = S
return 1
@@ -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()
@@ -0,0 +1,69 @@
/datum/computer_file/program/suit_sensors
filename = "sensormonitor"
filedesc = "Suit Sensors Monitoring"
nanomodule_path = /datum/nano_module/crew_monitor
program_icon_state = "crew"
program_key_state = "med_key"
program_menu_icon = "heart"
extended_desc = "This program connects to life signs monitoring system to provide basic information on crew health."
required_access = access_medical
requires_ntnet = 1
network_destination = "crew lifesigns monitoring system"
size = 11
/datum/nano_module/crew_monitor
name = "Crew monitor"
/datum/nano_module/crew_monitor/Topic(href, href_list)
if(..()) return 1
var/turf/T = get_turf(nano_host()) // TODO: Allow setting any using_map.contact_levels from the interface.
if (!T || !(T.z in using_map.player_levels))
usr << "<span class='warning'>Unable to establish a connection</span>: You're too far away from the station!"
return 0
if(href_list["track"])
if(isAI(usr))
var/mob/living/silicon/ai/AI = usr
var/mob/living/carbon/human/H = locate(href_list["track"]) in mob_list
if(hassensorlevel(H, SUIT_SENSOR_TRACKING))
AI.ai_actual_track(H)
return 1
/datum/nano_module/crew_monitor/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/turf/T = get_turf(nano_host())
data["isAI"] = isAI(user)
data["map_levels"] = using_map.get_map_levels(T.z, FALSE)
data["crewmembers"] = list()
for(var/z in (data["map_levels"] | T.z)) // Always show crew from the current Z even if we can't show a map
data["crewmembers"] += crew_repository.health_data(z)
ui = SSnanoui.try_update_ui(user, src, ui_key, ui, data, force_open)
if(!ui)
ui = new(user, src, ui_key, "crew_monitor.tmpl", "Crew Monitoring Computer", 900, 800, state = state)
// adding a template with the key "mapContent" enables the map ui functionality
ui.add_template("mapContent", "crew_monitor_map_content.tmpl")
// adding a template with the key "mapHeader" replaces the map header content
ui.add_template("mapHeader", "crew_monitor_map_header.tmpl")
if(!(ui.map_z_level in data["map_levels"]))
ui.set_map_z_level(data["map_levels"][1])
ui.set_initial_data(data)
ui.open()
// should make the UI auto-update; doesn't seem to?
ui.set_auto_update(1)
/*/datum/nano_module/crew_monitor/proc/scan()
for(var/mob/living/carbon/human/H in mob_list)
if(istype(H.w_uniform, /obj/item/clothing/under))
var/obj/item/clothing/under/C = H.w_uniform
if (C.has_sensor)
tracked |= C
return 1
*/
@@ -0,0 +1,144 @@
/datum/computer_file/program/email_administration
filename = "emailadmin"
filedesc = "Email Administration Utility"
extended_desc = "This program may be used to administrate NTNet's emailing service."
program_icon_state = "comm_monitor"
program_key_state = "generic_key"
program_menu_icon = "mail-open"
size = 12
requires_ntnet = 1
available_on_ntnet = 1
nanomodule_path = /datum/nano_module/email_administration
required_access = access_network
/datum/nano_module/email_administration/
name = "Email Client"
var/datum/computer_file/data/email_account/current_account = null
var/datum/computer_file/data/email_message/current_message = null
var/error = ""
/datum/nano_module/email_administration/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()
if(error)
data["error"] = error
else if(istype(current_message))
data["msg_title"] = current_message.title
data["msg_body"] = pencode2html(current_message.stored_data)
data["msg_timestamp"] = current_message.timestamp
data["msg_source"] = current_message.source
else if(istype(current_account))
data["current_account"] = current_account.login
data["cur_suspended"] = current_account.suspended
var/list/all_messages = list()
for(var/datum/computer_file/data/email_message/message in (current_account.inbox | current_account.spam | current_account.deleted))
all_messages.Add(list(list(
"title" = message.title,
"source" = message.source,
"timestamp" = message.timestamp,
"uid" = message.uid
)))
data["messages"] = all_messages
data["messagecount"] = all_messages.len
else
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,
"uid" = account.uid
)))
data["accounts"] = all_accounts
data["accountcount"] = all_accounts.len
ui = SSnanoui.try_update_ui(user, src, ui_key, ui, data, force_open)
if (!ui)
ui = new(user, src, ui_key, "email_administration.tmpl", "Email Administration Utility", 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_administration/Topic(href, href_list)
if(..())
return 1
var/mob/user = usr
if(!istype(user))
return 1
// High security - can only be operated when the user has an ID with access on them.
var/obj/item/weapon/card/id/I = user.GetIdCard()
if(!istype(I) || !(access_network in I.access))
return 1
if(href_list["back"])
if(error)
error = ""
else if(current_message)
current_message = null
else
current_account = null
return 1
if(href_list["ban"])
if(!current_account)
return 1
current_account.suspended = !current_account.suspended
ntnet_global.add_log_with_ids_check("EMAIL LOG: SA-EDIT Account [current_account.login] has been [current_account.suspended ? "" : "un" ]suspended by SA [I.registered_name] ([I.assignment]).")
error = "Account [current_account.login] has been [current_account.suspended ? "" : "un" ]suspended."
return 1
if(href_list["changepass"])
if(!current_account)
return 1
var/newpass = sanitize(input(user,"Enter new password for account [current_account.login]", "Password"), 100)
if(!newpass)
return 1
current_account.password = newpass
ntnet_global.add_log_with_ids_check("EMAIL LOG: SA-EDIT Password for account [current_account.login] has been changed by SA [I.registered_name] ([I.assignment]).")
return 1
if(href_list["viewmail"])
if(!current_account)
return 1
for(var/datum/computer_file/data/email_message/received_message in (current_account.inbox | current_account.spam | current_account.deleted))
if(received_message.uid == text2num(href_list["viewmail"]))
current_message = received_message
break
return 1
if(href_list["viewaccount"])
for(var/datum/computer_file/data/email_account/email_account in ntnet_global.email_accounts)
if(email_account.uid == text2num(href_list["viewaccount"]))
current_account = email_account
break
return 1
if(href_list["newaccount"])
var/newdomain = sanitize(input(user,"Pick domain:", "Domain name") as null|anything in using_map.usable_email_tlds)
if(!newdomain)
return 1
var/newlogin = sanitize(input(user,"Pick account name (@[newdomain]):", "Account name"), 100)
if(!newlogin)
return 1
var/complete_login = "[newlogin]@[newdomain]"
if(ntnet_global.does_email_exist(complete_login))
error = "Error creating account: An account with same address already exists."
return 1
var/datum/computer_file/data/email_account/new_account = new/datum/computer_file/data/email_account()
new_account.login = complete_login
new_account.password = GenerateKey()
error = "Email [new_account.login] has been created, with generated password [new_account.password]"
return 1
@@ -0,0 +1,102 @@
/datum/computer_file/program/ntnetmonitor
filename = "ntmonitor"
filedesc = "NTNet Diagnostics and Monitoring"
program_icon_state = "comm_monitor"
program_key_state = "generic_key"
program_menu_icon = "wrench"
extended_desc = "This program monitors the local NTNet network, provides access to logging systems, and allows for configuration changes"
size = 12
requires_ntnet = 1
required_access = access_network
available_on_ntnet = 1
nanomodule_path = /datum/nano_module/computer_ntnetmonitor/
/datum/nano_module/computer_ntnetmonitor
name = "NTNet Diagnostics and Monitoring"
//available_to_ai = TRUE
/datum/nano_module/computer_ntnetmonitor/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)
return
var/list/data = host.initial_data()
data["ntnetstatus"] = ntnet_global.check_function()
data["ntnetrelays"] = ntnet_global.relays.len
data["idsstatus"] = ntnet_global.intrusion_detection_enabled
data["idsalarm"] = ntnet_global.intrusion_detection_alarm
data["config_softwaredownload"] = ntnet_global.setting_softwaredownload
data["config_peertopeer"] = ntnet_global.setting_peertopeer
data["config_communication"] = ntnet_global.setting_communication
data["config_systemcontrol"] = ntnet_global.setting_systemcontrol
data["ntnetlogs"] = ntnet_global.logs
data["ntnetmaxlogs"] = ntnet_global.setting_maxlogcount
data["banned_nids"] = list(ntnet_global.banned_nids)
ui = SSnanoui.try_update_ui(user, src, ui_key, ui, data, force_open)
if (!ui)
ui = new(user, src, ui_key, "ntnet_monitor.tmpl", "NTNet Diagnostics and Monitoring Tool", 575, 700, state = state)
if(host.update_layout())
ui.auto_update_layout = 1
ui.set_initial_data(data)
ui.open()
ui.set_auto_update(1)
/datum/nano_module/computer_ntnetmonitor/Topic(href, href_list, state)
var/mob/user = usr
if(..())
return 1
if(href_list["resetIDS"])
if(ntnet_global)
ntnet_global.resetIDS()
return 1
if(href_list["toggleIDS"])
if(ntnet_global)
ntnet_global.toggleIDS()
return 1
if(href_list["toggleWireless"])
if(!ntnet_global)
return 1
// NTNet is disabled. Enabling can be done without user prompt
if(ntnet_global.setting_disabled)
ntnet_global.setting_disabled = 0
return 1
// NTNet is enabled and user is about to shut it down. Let's ask them if they really want to do it, as wirelessly connected computers won't connect without NTNet being enabled (which may prevent people from turning it back on)
if(!user)
return 1
var/response = alert(user, "Really disable NTNet wireless? If your computer is connected wirelessly you won't be able to turn it back on! This will affect all connected wireless devices.", "NTNet shutdown", "Yes", "No")
if(response == "Yes")
ntnet_global.setting_disabled = 1
return 1
if(href_list["purgelogs"])
if(ntnet_global)
ntnet_global.purge_logs()
return 1
if(href_list["updatemaxlogs"])
var/logcount = text2num(input(user,"Enter amount of logs to keep in memory ([MIN_NTNET_LOGS]-[MAX_NTNET_LOGS]):"))
if(ntnet_global)
ntnet_global.update_max_log_count(logcount)
return 1
if(href_list["toggle_function"])
if(!ntnet_global)
return 1
ntnet_global.toggle_function(href_list["toggle_function"])
return 1
if(href_list["ban_nid"])
if(!ntnet_global)
return 1
var/nid = input(user,"Enter NID of device which you want to block from the network:", "Enter NID") as null|num
if(nid && CanUseTopic(user, state))
ntnet_global.banned_nids |= nid
return 1
if(href_list["unban_nid"])
if(!ntnet_global)
return 1
var/nid = input(user,"Enter NID of device which you want to unblock from the network:", "Enter NID") as null|num
if(nid && CanUseTopic(user, state))
ntnet_global.banned_nids -= nid
return 1
@@ -0,0 +1,143 @@
var/warrant_uid = 0
/datum/datacore/var/list/warrants[] = list()
/datum/data/record/warrant
var/warrant_id
/datum/data/record/warrant/New()
..()
warrant_id = warrant_uid++
/datum/computer_file/program/digitalwarrant
filename = "digitalwarrant"
filedesc = "Warrant Assistant"
extended_desc = "Official NTsec program for creation and handling of warrants."
size = 8
program_icon_state = "warrant"
program_key_state = "security_key"
program_menu_icon = "star"
requires_ntnet = 1
available_on_ntnet = 1
required_access = access_security
usage_flags = PROGRAM_ALL
nanomodule_path = /datum/nano_module/program/digitalwarrant/
/datum/nano_module/program/digitalwarrant/
name = "Warrant Assistant"
var/datum/data/record/warrant/activewarrant
/datum/nano_module/program/digitalwarrant/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()
if(activewarrant)
data["warrantname"] = activewarrant.fields["namewarrant"]
data["warrantcharges"] = activewarrant.fields["charges"]
data["warrantauth"] = activewarrant.fields["auth"]
data["type"] = activewarrant.fields["arrestsearch"]
else
var/list/allwarrants = list()
for(var/datum/data/record/warrant/W in data_core.warrants)
allwarrants.Add(list(list(
"warrantname" = W.fields["namewarrant"],
"charges" = "[copytext(W.fields["charges"],1,min(length(W.fields["charges"]) + 1, 50))]...",
"auth" = W.fields["auth"],
"id" = W.warrant_id,
"arrestsearch" = W.fields["arrestsearch"]
)))
data["allwarrants"] = allwarrants
ui = SSnanoui.try_update_ui(user, src, ui_key, ui, data, force_open)
if (!ui)
ui = new(user, src, ui_key, "digitalwarrant.tmpl", name, 500, 350, state = state)
ui.auto_update_layout = 1
ui.set_initial_data(data)
ui.open()
/datum/nano_module/program/digitalwarrant/Topic(href, href_list)
if(..())
return 1
if(href_list["sw_menu"])
activewarrant = null
if(href_list["editwarrant"])
. = 1
for(var/datum/data/record/warrant/W in data_core.warrants)
if(W.warrant_id == text2num(href_list["editwarrant"]))
activewarrant = W
break
// The following actions will only be possible if the user has an ID with security access equipped. This is in line with modular computer framework's authentication methods,
// which also use RFID scanning to allow or disallow access to some functions. Anyone can view warrants, editing requires ID. This also prevents situations where you show a tablet
// to someone who is to be arrested, which allows them to change the stuff there.
var/mob/user = usr
if(!istype(user))
return
var/obj/item/weapon/card/id/I = user.GetIdCard()
if(!istype(I) || !I.registered_name || !(access_security in I.access))
to_chat(user, "Authentication error: Unable to locate ID with apropriate access to allow this operation.")
return
if(href_list["addwarrant"])
. = 1
var/datum/data/record/warrant/W = new()
var/temp = sanitize(input(usr, "Do you want to create a search-, or an arrest warrant?") as null|anything in list("search","arrest"))
if(CanInteract(user, default_state))
if(temp == "arrest")
W.fields["namewarrant"] = "Unknown"
W.fields["charges"] = "No charges present"
W.fields["auth"] = "Unauthorized"
W.fields["arrestsearch"] = "arrest"
if(temp == "search")
W.fields["namewarrant"] = "No location given"
W.fields["charges"] = "No reason given"
W.fields["auth"] = "Unauthorized"
W.fields["arrestsearch"] = "search"
activewarrant = W
if(href_list["savewarrant"])
. = 1
data_core.warrants |= activewarrant
activewarrant = null
if(href_list["deletewarrant"])
. = 1
data_core.warrants -= activewarrant
activewarrant = null
if(href_list["editwarrantname"])
. = 1
var/namelist = list()
for(var/datum/data/record/t in data_core.general)
namelist += t.fields["name"]
var/new_name = sanitize(input(usr, "Please input name") as null|anything in namelist)
if(CanInteract(user, default_state))
if (!new_name)
return
activewarrant.fields["namewarrant"] = new_name
if(href_list["editwarrantnamecustom"])
. = 1
var/new_name = sanitize(input("Please input name") as null|text)
if(CanInteract(user, default_state))
if (!new_name)
return
activewarrant.fields["namewarrant"] = new_name
if(href_list["editwarrantcharges"])
. = 1
var/new_charges = sanitize(input("Please input charges", "Charges", activewarrant.fields["charges"]) as null|text)
if(CanInteract(user, default_state))
if (!new_charges)
return
activewarrant.fields["charges"] = new_charges
if(href_list["editwarrantauth"])
. = 1
activewarrant.fields["auth"] = "[I.registered_name] - [I.assignment ? I.assignment : "(Unknown)"]"
if(href_list["back"])
. = 1
activewarrant = null