Uploading all files.

This commit is contained in:
Quotefox
2019-09-23 18:26:34 +02:00
commit 7091cdbe8c
7779 changed files with 2601004 additions and 0 deletions
@@ -0,0 +1,68 @@
/datum/ntnet_conversation
var/id = null
var/title = "Untitled Conversation"
var/datum/computer_file/program/chatclient/operator // "Administrator" of this channel. Creator starts as channel's operator,
var/list/messages = list()
var/list/clients = list()
var/password
var/static/ntnrc_uid = 0
/datum/ntnet_conversation/New()
id = ntnrc_uid++
if(SSnetworks.station_network)
SSnetworks.station_network.chat_channels.Add(src)
..()
/datum/ntnet_conversation/Destroy()
if(SSnetworks.station_network)
SSnetworks.station_network.chat_channels.Remove(src)
return ..()
/datum/ntnet_conversation/proc/add_message(message, username)
message = "[STATION_TIME_TIMESTAMP("hh:mm:ss")] [username]: [message]"
messages.Add(message)
trim_message_list()
/datum/ntnet_conversation/proc/add_status_message(message)
messages.Add("[STATION_TIME_TIMESTAMP("hh:mm:ss")] -!- [message]")
trim_message_list()
/datum/ntnet_conversation/proc/trim_message_list()
if(messages.len <= 50)
return
messages = messages.Copy(messages.len-50 ,0)
/datum/ntnet_conversation/proc/add_client(datum/computer_file/program/chatclient/C)
if(!istype(C))
return
clients.Add(C)
add_status_message("[C.username] has joined the channel.")
// No operator, so we assume the channel was empty. Assign this user as operator.
if(!operator)
changeop(C)
/datum/ntnet_conversation/proc/remove_client(datum/computer_file/program/chatclient/C)
if(!istype(C) || !(C in clients))
return
clients.Remove(C)
add_status_message("[C.username] has left the channel.")
// Channel operator left, pick new operator
if(C == operator)
operator = null
if(clients.len)
var/datum/computer_file/program/chatclient/newop = pick(clients)
changeop(newop)
/datum/ntnet_conversation/proc/changeop(datum/computer_file/program/chatclient/newop)
if(istype(newop))
operator = newop
add_status_message("Channel operator status transferred to [newop.username].")
/datum/ntnet_conversation/proc/change_title(newtitle, datum/computer_file/program/chatclient/client)
if(operator != client)
return FALSE // Not Authorised
add_status_message("[client.username] has changed channel title from [title] to [newtitle]")
title = newtitle
@@ -0,0 +1,447 @@
// This is the base type that does all the hardware stuff.
// Other types expand it - tablets use a direct subtypes, and
// consoles and laptops use "procssor" item that is held inside machinery piece
/obj/item/modular_computer
name = "modular microcomputer"
desc = "A small portable microcomputer."
var/enabled = 0 // Whether the computer is turned on.
var/screen_on = 1 // Whether the computer is active/opened/it's screen is on.
var/datum/computer_file/program/active_program = null // A currently active program running on the computer.
var/hardware_flag = 0 // A flag that describes this device type
var/last_power_usage = 0
var/last_battery_percent = 0 // Used for deciding if battery percentage has chandged
var/last_world_time = "00:00"
var/list/last_header_icons
var/base_active_power_usage = 50 // Power usage when the computer is open (screen is active) and can be interacted with. Remember hardware can use power too.
var/base_idle_power_usage = 5 // Power usage when the computer is idle and screen is off (currently only applies to laptops)
// Modular computers can run on various devices. Each DEVICE (Laptop, Console, Tablet,..)
// must have it's own DMI file. Icon states must be called exactly the same in all files, but may look differently
// If you create a program which is limited to Laptops and Consoles you don't have to add it's icon_state overlay for Tablets too, for example.
icon = 'icons/obj/computer.dmi'
icon_state = "laptop-open"
var/icon_state_unpowered = null // Icon state when the computer is turned off.
var/icon_state_powered = null // Icon state when the computer is turned on.
var/icon_state_menu = "menu" // Icon state overlay when the computer is turned on, but no program is loaded that would override the screen.
var/max_hardware_size = 0 // Maximal hardware w_class. Tablets/PDAs have 1, laptops 2, consoles 4.
var/steel_sheet_cost = 5 // Amount of steel sheets refunded when disassembling an empty frame of this computer.
integrity_failure = 50
max_integrity = 100
armor = list("melee" = 0, "bullet" = 20, "laser" = 20, "energy" = 100, "bomb" = 0, "bio" = 100, "rad" = 100, "fire" = 0, "acid" = 0)
// Important hardware (must be installed for computer to work)
// Optional hardware (improves functionality, but is not critical for computer to work)
var/list/all_components = list() // List of "connection ports" in this computer and the components with which they are plugged
var/list/idle_threads // Idle programs on background. They still receive process calls but can't be interacted with.
var/obj/physical = null // Object that represents our computer. It's used for Adjacent() and UI visibility checks.
var/has_light = FALSE //If the computer has a flashlight/LED light/what-have-you installed
var/light_on = FALSE //If that light is enabled
var/comp_light_luminosity = 3 //The brightness of that light
var/comp_light_color //The color of that light
/obj/item/modular_computer/Initialize()
. = ..()
START_PROCESSING(SSobj, src)
if(!physical)
physical = src
comp_light_color = "#FFFFFF"
idle_threads = list()
update_icon()
/obj/item/modular_computer/Destroy()
kill_program(forced = TRUE)
STOP_PROCESSING(SSobj, src)
for(var/H in all_components)
var/obj/item/computer_hardware/CH = all_components[H]
if(CH.holder == src)
CH.on_remove(src)
CH.holder = null
all_components.Remove(CH.device_type)
qdel(CH)
physical = null
return ..()
/obj/item/modular_computer/proc/add_verb(var/path)
switch(path)
if(MC_CARD)
verbs += /obj/item/modular_computer/proc/eject_id
if(MC_SDD)
verbs += /obj/item/modular_computer/proc/eject_disk
if(MC_AI)
verbs += /obj/item/modular_computer/proc/eject_card
/obj/item/modular_computer/proc/remove_verb(path)
switch(path)
if(MC_CARD)
verbs -= /obj/item/modular_computer/proc/eject_id
if(MC_SDD)
verbs -= /obj/item/modular_computer/proc/eject_disk
if(MC_AI)
verbs -= /obj/item/modular_computer/proc/eject_card
// Eject ID card from computer, if it has ID slot with card inside.
/obj/item/modular_computer/proc/eject_id()
set name = "Eject ID"
set category = "Object"
set src in view(1)
if(issilicon(usr))
return
var/obj/item/computer_hardware/card_slot/card_slot = all_components[MC_CARD]
if(usr.canUseTopic(src))
card_slot.try_eject(null, usr)
// Eject ID card from computer, if it has ID slot with card inside.
/obj/item/modular_computer/proc/eject_card()
set name = "Eject Intellicard"
set category = "Object"
if(issilicon(usr))
return
var/obj/item/computer_hardware/ai_slot/ai_slot = all_components[MC_AI]
if(usr.canUseTopic(src))
ai_slot.try_eject(null, usr,1)
// Eject ID card from computer, if it has ID slot with card inside.
/obj/item/modular_computer/proc/eject_disk()
set name = "Eject Data Disk"
set category = "Object"
if(issilicon(usr))
return
if(usr.canUseTopic(src))
var/obj/item/computer_hardware/hard_drive/portable/portable_drive = all_components[MC_SDD]
if(uninstall_component(portable_drive, usr))
portable_drive.verb_pickup()
/obj/item/modular_computer/AltClick(mob/user)
..()
if(issilicon(user))
return
if(user.canUseTopic(src))
var/obj/item/computer_hardware/card_slot/card_slot = all_components[MC_CARD]
var/obj/item/computer_hardware/ai_slot/ai_slot = all_components[MC_AI]
var/obj/item/computer_hardware/hard_drive/portable/portable_drive = all_components[MC_SDD]
if(portable_drive)
if(uninstall_component(portable_drive, user))
portable_drive.verb_pickup()
else
if(card_slot && card_slot.try_eject(null, user))
return
if(ai_slot)
ai_slot.try_eject(null, user)
// Gets IDs/access levels from card slot. Would be useful when/if PDAs would become modular PCs.
/obj/item/modular_computer/GetAccess()
var/obj/item/computer_hardware/card_slot/card_slot = all_components[MC_CARD]
if(card_slot)
return card_slot.GetAccess()
return ..()
/obj/item/modular_computer/GetID()
var/obj/item/computer_hardware/card_slot/card_slot = all_components[MC_CARD]
if(card_slot)
return card_slot.GetID()
return ..()
/obj/item/modular_computer/MouseDrop(obj/over_object, src_location, over_location)
var/mob/M = usr
if((!istype(over_object, /obj/screen)) && usr.canUseTopic(src))
return attack_self(M)
return ..()
/obj/item/modular_computer/attack_ai(mob/user)
return attack_self(user)
/obj/item/modular_computer/attack_ghost(mob/dead/observer/user)
. = ..()
if(.)
return
if(enabled)
ui_interact(user)
else if(IsAdminGhost(user))
var/response = alert(user, "This computer is turned off. Would you like to turn it on?", "Admin Override", "Yes", "No")
if(response == "Yes")
turn_on(user)
/obj/item/modular_computer/emag_act(mob/user)
if(obj_flags & EMAGGED)
to_chat(user, "<span class='warning'>\The [src] was already emagged.</span>")
return 0
else
obj_flags |= EMAGGED
to_chat(user, "<span class='notice'>You emag \the [src]. It's screen briefly shows a \"OVERRIDE ACCEPTED: New software downloads available.\" message.</span>")
return 1
/obj/item/modular_computer/examine(mob/user)
..()
if(obj_integrity <= integrity_failure)
to_chat(user, "<span class='danger'>It is heavily damaged!</span>")
else if(obj_integrity < max_integrity)
to_chat(user, "<span class='warning'>It is damaged.</span>")
/obj/item/modular_computer/update_icon()
cut_overlays()
if(!enabled)
icon_state = icon_state_unpowered
else
icon_state = icon_state_powered
if(active_program)
add_overlay(active_program.program_icon_state ? active_program.program_icon_state : icon_state_menu)
else
add_overlay(icon_state_menu)
if(obj_integrity <= integrity_failure)
add_overlay("bsod")
add_overlay("broken")
// On-click handling. Turns on the computer if it's off and opens the GUI.
/obj/item/modular_computer/interact(mob/user)
if(enabled)
ui_interact(user)
else
turn_on(user)
/obj/item/modular_computer/proc/turn_on(mob/user)
var/issynth = issilicon(user) // Robots and AIs get different activation messages.
if(obj_integrity <= integrity_failure)
if(issynth)
to_chat(user, "<span class='warning'>You send an activation signal to \the [src], but it responds with an error code. It must be damaged.</span>")
else
to_chat(user, "<span class='warning'>You press the power button, but the computer fails to boot up, displaying variety of errors before shutting down again.</span>")
return
// If we have a recharger, enable it automatically. Lets computer without a battery work.
var/obj/item/computer_hardware/recharger/recharger = all_components[MC_CHARGE]
if(recharger)
recharger.enabled = 1
if(all_components[MC_CPU] && use_power()) // use_power() checks if the PC is powered
if(issynth)
to_chat(user, "<span class='notice'>You send an activation signal to \the [src], turning it on.</span>")
else
to_chat(user, "<span class='notice'>You press the power button and start up \the [src].</span>")
enabled = 1
update_icon()
ui_interact(user)
else // Unpowered
if(issynth)
to_chat(user, "<span class='warning'>You send an activation signal to \the [src] but it does not respond.</span>")
else
to_chat(user, "<span class='warning'>You press the power button but \the [src] does not respond.</span>")
// Process currently calls handle_power(), may be expanded in future if more things are added.
/obj/item/modular_computer/process()
if(!enabled) // The computer is turned off
last_power_usage = 0
return 0
if(obj_integrity <= integrity_failure)
shutdown_computer()
return 0
if(active_program && active_program.requires_ntnet && !get_ntnet_status(active_program.requires_ntnet_feature))
active_program.event_networkfailure(0) // Active program requires NTNet to run but we've just lost connection. Crash.
for(var/I in idle_threads)
var/datum/computer_file/program/P = I
if(P.requires_ntnet && !get_ntnet_status(P.requires_ntnet_feature))
P.event_networkfailure(1)
if(active_program)
if(active_program.program_state != PROGRAM_STATE_KILLED)
active_program.process_tick()
active_program.ntnet_status = get_ntnet_status()
else
active_program = null
for(var/I in idle_threads)
var/datum/computer_file/program/P = I
if(P.program_state != PROGRAM_STATE_KILLED)
P.process_tick()
P.ntnet_status = get_ntnet_status()
else
idle_threads.Remove(P)
handle_power() // Handles all computer power interaction
//check_update_ui_need()
// Function used by NanoUI's to obtain data for header. All relevant entries begin with "PC_"
/obj/item/modular_computer/proc/get_header_data()
var/list/data = list()
var/obj/item/computer_hardware/battery/battery_module = all_components[MC_CELL]
var/obj/item/computer_hardware/recharger/recharger = all_components[MC_CHARGE]
if(battery_module && battery_module.battery)
switch(battery_module.battery.percent())
if(80 to 200) // 100 should be maximal but just in case..
data["PC_batteryicon"] = "batt_100.gif"
if(60 to 80)
data["PC_batteryicon"] = "batt_80.gif"
if(40 to 60)
data["PC_batteryicon"] = "batt_60.gif"
if(20 to 40)
data["PC_batteryicon"] = "batt_40.gif"
if(5 to 20)
data["PC_batteryicon"] = "batt_20.gif"
else
data["PC_batteryicon"] = "batt_5.gif"
data["PC_batterypercent"] = "[round(battery_module.battery.percent())] %"
data["PC_showbatteryicon"] = 1
else
data["PC_batteryicon"] = "batt_5.gif"
data["PC_batterypercent"] = "N/C"
data["PC_showbatteryicon"] = battery_module ? 1 : 0
if(recharger && recharger.enabled && recharger.check_functionality() && recharger.use_power(0))
data["PC_apclinkicon"] = "charging.gif"
switch(get_ntnet_status())
if(0)
data["PC_ntneticon"] = "sig_none.gif"
if(1)
data["PC_ntneticon"] = "sig_low.gif"
if(2)
data["PC_ntneticon"] = "sig_high.gif"
if(3)
data["PC_ntneticon"] = "sig_lan.gif"
if(idle_threads.len)
var/list/program_headers = list()
for(var/I in idle_threads)
var/datum/computer_file/program/P = I
if(!P.ui_header)
continue
program_headers.Add(list(list(
"icon" = P.ui_header
)))
data["PC_programheaders"] = program_headers
data["PC_stationtime"] = STATION_TIME_TIMESTAMP("hh:mm:ss")
data["PC_hasheader"] = 1
data["PC_showexitprogram"] = active_program ? 1 : 0 // Hides "Exit Program" button on mainscreen
return data
// Relays kill program request to currently active program. Use this to quit current program.
/obj/item/modular_computer/proc/kill_program(forced = FALSE)
if(active_program)
active_program.kill_program(forced)
active_program = null
var/mob/user = usr
if(user && istype(user))
ui_interact(user) // Re-open the UI on this computer. It should show the main screen now.
update_icon()
// Returns 0 for No Signal, 1 for Low Signal and 2 for Good Signal. 3 is for wired connection (always-on)
/obj/item/modular_computer/proc/get_ntnet_status(specific_action = 0)
var/obj/item/computer_hardware/network_card/network_card = all_components[MC_NET]
if(network_card)
return network_card.get_signal(specific_action)
else
return 0
/obj/item/modular_computer/proc/add_log(text)
if(!get_ntnet_status())
return FALSE
var/obj/item/computer_hardware/network_card/network_card = all_components[MC_NET]
return SSnetworks.station_network.add_log(text, network_card)
/obj/item/modular_computer/proc/shutdown_computer(loud = 1)
kill_program(forced = TRUE)
for(var/datum/computer_file/program/P in idle_threads)
P.kill_program(forced = TRUE)
idle_threads.Remove(P)
if(loud)
physical.visible_message("<span class='notice'>\The [src] shuts down.</span>")
enabled = 0
update_icon()
/obj/item/modular_computer/attackby(obj/item/W as obj, mob/user as mob)
// Insert items into the components
for(var/h in all_components)
var/obj/item/computer_hardware/H = all_components[h]
if(H.try_insert(W, user))
return
// Insert new hardware
if(istype(W, /obj/item/computer_hardware))
if(install_component(W, user))
return
if(istype(W, /obj/item/wrench))
if(all_components.len)
to_chat(user, "<span class='warning'>Remove all components from \the [src] before disassembling it.</span>")
return
new /obj/item/stack/sheet/metal( get_turf(src.loc), steel_sheet_cost )
physical.visible_message("\The [src] has been disassembled by [user].")
relay_qdel()
qdel(src)
return
if(istype(W, /obj/item/weldingtool))
if(obj_integrity == max_integrity)
to_chat(user, "<span class='warning'>\The [src] does not require repairs.</span>")
return
if(!W.tool_start_check(user, amount=1))
return
to_chat(user, "<span class='notice'>You begin repairing damage to \the [src]...</span>")
if(W.use_tool(src, user, 20, volume=50, amount=1))
obj_integrity = max_integrity
to_chat(user, "<span class='notice'>You repair \the [src].</span>")
return
if(istype(W, /obj/item/screwdriver))
if(!all_components.len)
to_chat(user, "<span class='warning'>This device doesn't have any components installed.</span>")
return
var/list/component_names = list()
for(var/h in all_components)
var/obj/item/computer_hardware/H = all_components[h]
component_names.Add(H.name)
var/choice = input(user, "Which component do you want to uninstall?", "Computer maintenance", null) as null|anything in component_names
if(!choice)
return
if(!Adjacent(user))
return
var/obj/item/computer_hardware/H = find_hardware_by_name(choice)
if(!H)
return
uninstall_component(H, user)
return
..()
// Used by processor to relay qdel() to machinery type.
/obj/item/modular_computer/proc/relay_qdel()
return
// Perform adjacency checks on our physical counterpart, if any.
/obj/item/modular_computer/Adjacent(atom/neighbor)
if(physical && physical != src)
return physical.Adjacent(neighbor)
return ..()
@@ -0,0 +1,54 @@
/obj/item/modular_computer/proc/can_install_component(obj/item/computer_hardware/H, mob/living/user = null)
if(!H.can_install(src, user))
return FALSE
if(H.w_class > max_hardware_size)
to_chat(user, "<span class='warning'>This component is too large for \the [src]!</span>")
return FALSE
if(all_components[H.device_type])
to_chat(user, "<span class='warning'>This computer's hardware slot is already occupied by \the [all_components[H.device_type]].</span>")
return FALSE
return TRUE
// Installs component.
/obj/item/modular_computer/proc/install_component(obj/item/computer_hardware/H, mob/living/user = null)
if(!can_install_component(H, user))
return FALSE
if(user && !user.transferItemToLoc(H, src))
return FALSE
all_components[H.device_type] = H
to_chat(user, "<span class='notice'>You install \the [H] into \the [src].</span>")
H.holder = src
H.forceMove(src)
H.on_install(src, user)
// Uninstalls component.
/obj/item/modular_computer/proc/uninstall_component(obj/item/computer_hardware/H, mob/living/user = null)
if(H.holder != src) // Not our component at all.
return FALSE
all_components.Remove(H.device_type)
to_chat(user, "<span class='notice'>You remove \the [H] from \the [src].</span>")
H.forceMove(get_turf(src))
H.holder = null
H.on_remove(src, user)
if(enabled && !use_power())
shutdown_computer()
update_icon()
// Checks all hardware pieces to determine if name matches, if yes, returns the hardware piece, otherwise returns null
/obj/item/modular_computer/proc/find_hardware_by_name(name)
for(var/i in all_components)
var/obj/O = all_components[i]
if(O.name == name)
return O
return null
@@ -0,0 +1,31 @@
/obj/item/modular_computer/take_damage(damage_amount, damage_type = BRUTE, damage_flag = 0, sound_effect = 1)
. = ..()
var/component_probability = min(50, max(damage_amount*0.1, 1 - obj_integrity/max_integrity))
switch(damage_flag)
if("bullet")
component_probability = damage_amount * 0.5
if("laser")
component_probability = damage_amount * 0.66
if(component_probability)
for(var/I in all_components)
var/obj/item/computer_hardware/H = all_components[I]
if(prob(component_probability))
H.take_damage(round(damage_amount*0.5), damage_type, damage_flag, 0)
/obj/item/modular_computer/deconstruct(disassembled = TRUE)
break_apart()
/obj/item/modular_computer/proc/break_apart()
if(!(flags_1 & NODECONSTRUCT_1))
physical.visible_message("\The [src] breaks apart!")
var/turf/newloc = get_turf(src)
new /obj/item/stack/sheet/metal(newloc, round(steel_sheet_cost/2))
for(var/C in all_components)
var/obj/item/computer_hardware/H = all_components[C]
uninstall_component(H)
H.forceMove(newloc)
if(prob(25))
H.take_damage(rand(10,30), BRUTE, 0, 0)
relay_qdel()
qdel(src)
@@ -0,0 +1,65 @@
// Tries to draw power from charger or, if no operational charger is present, from power cell.
/obj/item/modular_computer/proc/use_power(amount = 0)
if(check_power_override())
return TRUE
var/obj/item/computer_hardware/recharger/recharger = all_components[MC_CHARGE]
if(recharger && recharger.check_functionality())
if(recharger.use_power(amount))
return TRUE
var/obj/item/computer_hardware/battery/battery_module = all_components[MC_CELL]
if(battery_module && battery_module.battery && battery_module.battery.charge)
var/obj/item/stock_parts/cell/cell = battery_module.battery
if(cell.use(amount * GLOB.CELLRATE))
return TRUE
else // Discharge the cell anyway.
cell.use(min(amount*GLOB.CELLRATE, cell.charge))
return FALSE
return FALSE
/obj/item/modular_computer/proc/give_power(amount)
var/obj/item/computer_hardware/battery/battery_module = all_components[MC_CELL]
if(battery_module && battery_module.battery)
return battery_module.battery.give(amount)
return 0
/obj/item/modular_computer/get_cell()
var/obj/item/computer_hardware/battery/battery_module = all_components[MC_CELL]
if(battery_module && battery_module.battery)
return battery_module.battery
// Used in following function to reduce copypaste
/obj/item/modular_computer/proc/power_failure()
if(enabled) // Shut down the computer
if(active_program)
active_program.event_powerfailure(0)
for(var/I in idle_threads)
var/datum/computer_file/program/PRG = I
PRG.event_powerfailure(1)
shutdown_computer(0)
// Handles power-related things, such as battery interaction, recharging, shutdown when it's discharged
/obj/item/modular_computer/proc/handle_power()
var/obj/item/computer_hardware/recharger/recharger = all_components[MC_CHARGE]
if(recharger)
recharger.process()
var/power_usage = screen_on ? base_active_power_usage : base_idle_power_usage
for(var/obj/item/computer_hardware/H in all_components)
if(H.enabled)
power_usage += H.power_usage
if(use_power(power_usage))
last_power_usage = power_usage
return TRUE
else
power_failure()
return FALSE
// Used by child types if they have other power source than battery or recharger
/obj/item/modular_computer/proc/check_power_override()
return FALSE
@@ -0,0 +1,165 @@
/obj/item/modular_computer/attack_self(mob/user)
. = ..()
ui_interact(user)
// Operates TGUI
/obj/item/modular_computer/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state)
if(!enabled)
if(ui)
ui.close()
return 0
if(!use_power())
if(ui)
ui.close()
return 0
// Robots don't really need to see the screen, their wireless connection works as long as computer is on.
if(!screen_on && !issilicon(user))
if(ui)
ui.close()
return 0
// If we have an active program switch to it now.
if(active_program)
if(ui) // This is the main laptop screen. Since we are switching to program's UI close it for now.
ui.close()
active_program.ui_interact(user)
return
// We are still here, that means there is no program loaded. Load the BIOS/ROM/OS/whatever you want to call it.
// This screen simply lists available programs and user may select them.
var/obj/item/computer_hardware/hard_drive/hard_drive = all_components[MC_HDD]
if(!hard_drive || !hard_drive.stored_files || !hard_drive.stored_files.len)
to_chat(user, "<span class='danger'>\The [src] beeps three times, it's screen displaying a \"DISK ERROR\" warning.</span>")
return // No HDD, No HDD files list or no stored files. Something is very broken.
ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
if (!ui)
var/datum/asset/assets = get_asset_datum(/datum/asset/simple/headers)
assets.send(user)
ui = new(user, src, ui_key, "ntos_main", "NTOS Main menu", 400, 500, master_ui, state)
ui.open()
ui.set_autoupdate(state = 1)
/obj/item/modular_computer/ui_data(mob/user)
var/list/data = get_header_data()
data["programs"] = list()
var/obj/item/computer_hardware/hard_drive/hard_drive = all_components[MC_HDD]
for(var/datum/computer_file/program/P in hard_drive.stored_files)
var/running = 0
if(P in idle_threads)
running = 1
data["programs"] += list(list("name" = P.filename, "desc" = P.filedesc, "running" = running))
data["has_light"] = has_light
data["light_on"] = light_on
data["comp_light_color"] = comp_light_color
return data
// Handles user's GUI input
/obj/item/modular_computer/ui_act(action, params)
if(..())
return
var/obj/item/computer_hardware/hard_drive/hard_drive = all_components[MC_HDD]
switch(action)
if("PC_exit")
kill_program()
return 1
if("PC_shutdown")
shutdown_computer()
return 1
if("PC_minimize")
var/mob/user = usr
if(!active_program || !all_components[MC_CPU])
return
idle_threads.Add(active_program)
active_program.program_state = PROGRAM_STATE_BACKGROUND // Should close any existing UIs
active_program = null
update_icon()
if(user && istype(user))
ui_interact(user) // Re-open the UI on this computer. It should show the main screen now.
if("PC_killprogram")
var/prog = params["name"]
var/datum/computer_file/program/P = null
var/mob/user = usr
if(hard_drive)
P = hard_drive.find_file_by_name(prog)
if(!istype(P) || P.program_state == PROGRAM_STATE_KILLED)
return
P.kill_program(forced = TRUE)
to_chat(user, "<span class='notice'>Program [P.filename].[P.filetype] with PID [rand(100,999)] has been killed.</span>")
if("PC_runprogram")
var/prog = params["name"]
var/datum/computer_file/program/P = null
var/mob/user = usr
if(hard_drive)
P = hard_drive.find_file_by_name(prog)
if(!P || !istype(P)) // Program not found or it's not executable program.
to_chat(user, "<span class='danger'>\The [src]'s screen shows \"I/O ERROR - Unable to run program\" warning.</span>")
return
P.computer = src
if(!P.is_supported_by_hardware(hardware_flag, 1, user))
return
// The program is already running. Resume it.
if(P in idle_threads)
P.program_state = PROGRAM_STATE_ACTIVE
active_program = P
idle_threads.Remove(P)
update_icon()
return
var/obj/item/computer_hardware/processor_unit/PU = all_components[MC_CPU]
if(idle_threads.len > PU.max_idle_programs)
to_chat(user, "<span class='danger'>\The [src] displays a \"Maximal CPU load reached. Unable to run another program.\" error.</span>")
return
if(P.requires_ntnet && !get_ntnet_status(P.requires_ntnet_feature)) // The program requires NTNet connection, but we are not connected to NTNet.
to_chat(user, "<span class='danger'>\The [src]'s screen shows \"Unable to connect to NTNet. Please retry. If problem persists contact your system administrator.\" warning.</span>")
return
if(P.run_program(user))
active_program = P
update_icon()
return 1
if("PC_toggle_light")
light_on = !light_on
if(light_on)
set_light(comp_light_luminosity, 1, comp_light_color)
else
set_light(0)
if("PC_light_color")
var/mob/user = usr
var/new_color
while(!new_color)
new_color = input(user, "Choose a new color for [src]'s flashlight.", "Light Color",light_color) as color|null
if(!new_color)
return
if(color_hex2num(new_color) < 200) //Colors too dark are rejected
to_chat(user, "<span class='warning'>That color is too dark! Choose a lighter one.</span>")
new_color = null
comp_light_color = new_color
light_color = new_color
update_light()
else
return
/obj/item/modular_computer/ui_host()
if(physical)
return physical
return src
@@ -0,0 +1,109 @@
/obj/item/modular_computer/laptop
name = "laptop"
desc = "A portable laptop computer."
icon = 'icons/obj/modular_laptop.dmi'
icon_state = "laptop-closed"
icon_state_powered = "laptop"
icon_state_unpowered = "laptop-off"
icon_state_menu = "menu"
hardware_flag = PROGRAM_LAPTOP
max_hardware_size = 2
w_class = WEIGHT_CLASS_NORMAL
// No running around with open laptops in hands.
item_flags = SLOWS_WHILE_IN_HAND
screen_on = 0 // Starts closed
var/start_open = TRUE // unless this var is set to 1
var/icon_state_closed = "laptop-closed"
var/w_class_open = WEIGHT_CLASS_BULKY
var/slowdown_open = TRUE
/obj/item/modular_computer/laptop/examine(mob/user)
..()
if(screen_on)
to_chat(user, "<span class='notice'>Alt-click to close it.</span>")
/obj/item/modular_computer/laptop/Initialize()
. = ..()
if(start_open && !screen_on)
toggle_open()
/obj/item/modular_computer/laptop/update_icon()
if(screen_on)
..()
else
cut_overlays()
icon_state = icon_state_closed
/obj/item/modular_computer/laptop/attack_self(mob/user)
if(!screen_on)
try_toggle_open(user)
else
return ..()
/obj/item/modular_computer/laptop/verb/open_computer()
set name = "Toggle Open"
set category = "Object"
set src in view(1)
try_toggle_open(usr)
/obj/item/modular_computer/laptop/MouseDrop(obj/over_object, src_location, over_location)
. = ..()
if(over_object == usr || over_object == src)
try_toggle_open(usr)
else if(istype(over_object, /obj/screen/inventory/hand))
var/obj/screen/inventory/hand/H = over_object
var/mob/M = usr
if(!M.restrained() && !M.stat)
if(!isturf(loc) || !Adjacent(M))
return
M.put_in_hand(src, H.held_index)
/obj/item/modular_computer/laptop/attack_hand(mob/user)
. = ..()
if(.)
return
if(screen_on && isturf(loc))
return attack_self(user)
/obj/item/modular_computer/laptop/proc/try_toggle_open(mob/living/user)
if(issilicon(user))
return
if(!isturf(loc) && !ismob(loc)) // No opening it in backpack.
return
if(!user.canUseTopic(src))
return
toggle_open(user)
/obj/item/modular_computer/laptop/AltClick(mob/user)
if(screen_on) // Close it.
try_toggle_open(user)
else
return ..()
/obj/item/modular_computer/laptop/proc/toggle_open(mob/living/user=null)
if(screen_on)
to_chat(user, "<span class='notice'>You close \the [src].</span>")
slowdown = initial(slowdown)
w_class = initial(w_class)
else
to_chat(user, "<span class='notice'>You open \the [src].</span>")
slowdown = slowdown_open
w_class = w_class_open
screen_on = !screen_on
update_icon()
// Laptop frame, starts empty and closed.
/obj/item/modular_computer/laptop/buildable
start_open = FALSE
@@ -0,0 +1,23 @@
/obj/item/modular_computer/laptop/preset/Initialize()
. = ..()
install_component(new /obj/item/computer_hardware/processor_unit/small)
install_component(new /obj/item/computer_hardware/battery(src, /obj/item/stock_parts/cell/computer))
install_component(new /obj/item/computer_hardware/hard_drive)
install_component(new /obj/item/computer_hardware/network_card)
install_programs()
/obj/item/modular_computer/laptop/preset/proc/install_programs()
return
/obj/item/modular_computer/laptop/preset/civillian
desc = "A low-end laptop often used for personal recreation."
/obj/item/modular_computer/laptop/preset/civillian/install_programs()
var/obj/item/computer_hardware/hard_drive/hard_drive = all_components[MC_HDD]
hard_drive.store_file(new/datum/computer_file/program/chatclient())
hard_drive.store_file(new/datum/computer_file/program/nttransfer())
@@ -0,0 +1,76 @@
// Held by /obj/machinery/modular_computer to reduce amount of copy-pasted code.
/obj/item/modular_computer/processor
name = "processing unit"
desc = "You shouldn't see this. If you do, report it."
icon = null
icon_state = null
icon_state_unpowered = null
icon_state_menu = null
hardware_flag = 0
var/obj/machinery/modular_computer/machinery_computer = null
/obj/item/modular_computer/processor/Destroy()
. = ..()
if(machinery_computer && (machinery_computer.cpu == src))
machinery_computer.cpu = null
machinery_computer = null
/obj/item/modular_computer/processor/New(comp)
STOP_PROCESSING(SSobj, src) // Processed by its machine
if(!comp || !istype(comp, /obj/machinery/modular_computer))
CRASH("Inapropriate type passed to obj/item/modular_computer/processor/New()! Aborting.")
return
// Obtain reference to machinery computer
all_components = list()
idle_threads = list()
machinery_computer = comp
machinery_computer.cpu = src
hardware_flag = machinery_computer.hardware_flag
max_hardware_size = machinery_computer.max_hardware_size
steel_sheet_cost = machinery_computer.steel_sheet_cost
obj_integrity = machinery_computer.obj_integrity
max_integrity = machinery_computer.max_integrity
integrity_failure = machinery_computer.integrity_failure
base_active_power_usage = machinery_computer.base_active_power_usage
base_idle_power_usage = machinery_computer.base_idle_power_usage
/obj/item/modular_computer/processor/relay_qdel()
qdel(machinery_computer)
/obj/item/modular_computer/processor/update_icon()
if(machinery_computer)
return machinery_computer.update_icon()
// This thing is not meant to be used on it's own, get topic data from our machinery owner.
//obj/item/modular_computer/processor/canUseTopic(atom/movable/M, be_close=FALSE, no_dextery=FALSE, no_tk=FALSE)
// if(!machinery_computer)
// return 0
// return machinery_computer.canUseTopic(user, state)
/obj/item/modular_computer/processor/shutdown_computer()
if(!machinery_computer)
return
..()
machinery_computer.update_icon()
return
/obj/item/modular_computer/processor/add_verb(path)
switch(path)
if(MC_CARD)
machinery_computer.verbs += /obj/machinery/modular_computer/proc/eject_id
if(MC_SDD)
machinery_computer.verbs += /obj/machinery/modular_computer/proc/eject_disk
if(MC_AI)
machinery_computer.verbs += /obj/machinery/modular_computer/proc/eject_card
/obj/item/modular_computer/processor/remove_verb(path)
switch(path)
if(MC_CARD)
machinery_computer.verbs -= /obj/machinery/modular_computer/proc/eject_id
if(MC_SDD)
machinery_computer.verbs -= /obj/machinery/modular_computer/proc/eject_disk
if(MC_AI)
machinery_computer.verbs -= /obj/machinery/modular_computer/proc/eject_card
@@ -0,0 +1,23 @@
/obj/item/modular_computer/tablet //Its called tablet for theme of 90ies but actually its a "big smartphone" sized
name = "tablet computer"
icon = 'icons/obj/modular_tablet.dmi'
icon_state = "tablet-red"
icon_state_unpowered = "tablet"
icon_state_powered = "tablet"
icon_state_menu = "menu"
hardware_flag = PROGRAM_TABLET
max_hardware_size = 1
w_class = WEIGHT_CLASS_SMALL
steel_sheet_cost = 1
slot_flags = ITEM_SLOT_ID | ITEM_SLOT_BELT
has_light = TRUE //LED flashlight!
comp_light_luminosity = 2.3 //Same as the PDA
var/finish_color = null
/obj/item/modular_computer/tablet/update_icon()
..()
if(!finish_color)
finish_color = pick("red","blue","brown","green","black")
icon_state = "tablet-[finish_color]"
icon_state_unpowered = "tablet-[finish_color]"
icon_state_powered = "tablet-[finish_color]"
@@ -0,0 +1,29 @@
// This is literally the worst possible cheap tablet
/obj/item/modular_computer/tablet/preset/cheap
desc = "A low-end tablet often seen among low ranked station personnel."
/obj/item/modular_computer/tablet/preset/cheap/Initialize()
. = ..()
install_component(new /obj/item/computer_hardware/processor_unit/small)
install_component(new /obj/item/computer_hardware/battery(src, /obj/item/stock_parts/cell/computer/micro))
install_component(new /obj/item/computer_hardware/hard_drive/small)
install_component(new /obj/item/computer_hardware/network_card)
// Alternative version, an average one, for higher ranked positions mostly
/obj/item/modular_computer/tablet/preset/advanced/Initialize()
. = ..()
install_component(new /obj/item/computer_hardware/processor_unit/small)
install_component(new /obj/item/computer_hardware/battery(src, /obj/item/stock_parts/cell/computer))
install_component(new /obj/item/computer_hardware/hard_drive/small)
install_component(new /obj/item/computer_hardware/network_card)
install_component(new /obj/item/computer_hardware/card_slot)
install_component(new /obj/item/computer_hardware/printer/mini)
/obj/item/modular_computer/tablet/preset/cargo/Initialize()
. = ..()
install_component(new /obj/item/computer_hardware/processor_unit/small)
install_component(new /obj/item/computer_hardware/battery(src, /obj/item/stock_parts/cell/computer))
install_component(new /obj/item/computer_hardware/hard_drive/small)
install_component(new /obj/item/computer_hardware/network_card)
install_component(new /obj/item/computer_hardware/printer/mini)
@@ -0,0 +1,87 @@
/obj/machinery/modular_computer/console/preset
// Can be changed to give devices specific hardware
var/_has_id_slot = FALSE
var/_has_printer = FALSE
var/_has_battery = FALSE
var/_has_ai = FALSE
/obj/machinery/modular_computer/console/preset/Initialize()
. = ..()
if(!cpu)
return
cpu.install_component(new /obj/item/computer_hardware/processor_unit)
if(_has_id_slot)
cpu.install_component(new /obj/item/computer_hardware/card_slot)
if(_has_printer)
cpu.install_component(new /obj/item/computer_hardware/printer)
if(_has_battery)
cpu.install_component(new /obj/item/computer_hardware/battery(cpu, /obj/item/stock_parts/cell/computer/super))
if(_has_ai)
cpu.install_component(new /obj/item/computer_hardware/ai_slot)
install_programs()
// Override in child types to install preset-specific programs.
/obj/machinery/modular_computer/console/preset/proc/install_programs()
return
// ===== ENGINEERING CONSOLE =====
/obj/machinery/modular_computer/console/preset/engineering
console_department = "Engineering"
name = "engineering console"
desc = "A stationary computer. This one comes preloaded with engineering programs."
/obj/machinery/modular_computer/console/preset/engineering/install_programs()
var/obj/item/computer_hardware/hard_drive/hard_drive = cpu.all_components[MC_HDD]
hard_drive.store_file(new/datum/computer_file/program/power_monitor())
hard_drive.store_file(new/datum/computer_file/program/alarm_monitor())
hard_drive.store_file(new/datum/computer_file/program/supermatter_monitor())
// ===== RESEARCH CONSOLE =====
/obj/machinery/modular_computer/console/preset/research
console_department = "Research"
name = "research director's console"
desc = "A stationary computer. This one comes preloaded with research programs."
_has_ai = TRUE
/obj/machinery/modular_computer/console/preset/research/examine(mob/user)
..()
to_chat(user, "<span class='notice'>Alt-click to eject the intelliCard.</span>")
/obj/machinery/modular_computer/console/preset/research/install_programs()
var/obj/item/computer_hardware/hard_drive/hard_drive = cpu.all_components[MC_HDD]
hard_drive.store_file(new/datum/computer_file/program/ntnetmonitor())
hard_drive.store_file(new/datum/computer_file/program/nttransfer())
hard_drive.store_file(new/datum/computer_file/program/chatclient())
hard_drive.store_file(new/datum/computer_file/program/aidiag())
// ===== COMMAND CONSOLE =====
/obj/machinery/modular_computer/console/preset/command
console_department = "Command"
name = "command console"
desc = "A stationary computer. This one comes preloaded with command programs."
_has_id_slot = TRUE
_has_printer = TRUE
/obj/machinery/modular_computer/console/preset/command/examine(mob/user)
..()
to_chat(user, "<span class='notice'>Alt-click [src] to eject the identification card.</span>")
/obj/machinery/modular_computer/console/preset/command/install_programs()
var/obj/item/computer_hardware/hard_drive/hard_drive = cpu.all_components[MC_HDD]
hard_drive.store_file(new/datum/computer_file/program/chatclient())
hard_drive.store_file(new/datum/computer_file/program/card_mod())
// ===== CIVILIAN CONSOLE =====
/obj/machinery/modular_computer/console/preset/civilian
console_department = "Civilian"
name = "civilian console"
desc = "A stationary computer. This one comes preloaded with generic programs."
/obj/machinery/modular_computer/console/preset/civilian/install_programs()
var/obj/item/computer_hardware/hard_drive/hard_drive = cpu.all_components[MC_HDD]
hard_drive.store_file(new/datum/computer_file/program/chatclient())
hard_drive.store_file(new/datum/computer_file/program/nttransfer())
@@ -0,0 +1,158 @@
// Modular Computer - device that runs various programs and operates with hardware
// DO NOT SPAWN THIS TYPE. Use /laptop/ or /console/ instead.
/obj/machinery/modular_computer
name = "modular computer"
desc = "An advanced computer."
use_power = IDLE_POWER_USE
idle_power_usage = 5
var/hardware_flag = 0 // A flag that describes this device type
var/last_power_usage = 0 // Power usage during last tick
// Modular computers can run on various devices. Each DEVICE (Laptop, Console, Tablet,..)
// must have it's own DMI file. Icon states must be called exactly the same in all files, but may look differently
// If you create a program which is limited to Laptops and Consoles you don't have to add it's icon_state overlay for Tablets too, for example.
icon = null
icon_state = null
var/icon_state_unpowered = null // Icon state when the computer is turned off.
var/icon_state_powered = null // Icon state when the computer is turned on.
var/screen_icon_state_menu = "menu" // Icon state overlay when the computer is turned on, but no program is loaded that would override the screen.
var/screen_icon_screensaver = "standby" // Icon state overlay when the computer is powered, but not 'switched on'.
var/max_hardware_size = 0 // Maximal hardware size. Currently, tablets have 1, laptops 2 and consoles 3. Limits what hardware types can be installed.
var/steel_sheet_cost = 10 // Amount of steel sheets refunded when disassembling an empty frame of this computer.
var/light_strength = 0 // Light luminosity when turned on
var/base_active_power_usage = 100 // Power usage when the computer is open (screen is active) and can be interacted with. Remember hardware can use power too.
var/base_idle_power_usage = 10 // Power usage when the computer is idle and screen is off (currently only applies to laptops)
var/obj/item/modular_computer/processor/cpu = null // CPU that handles most logic while this type only handles power and other specific things.
/obj/machinery/modular_computer/Initialize()
. = ..()
cpu = new(src)
cpu.physical = src
/obj/machinery/modular_computer/Destroy()
QDEL_NULL(cpu)
return ..()
/obj/machinery/modular_computer/attack_ghost(mob/dead/observer/user)
. = ..()
if(.)
return
if(cpu)
cpu.attack_ghost(user)
/obj/machinery/modular_computer/emag_act(mob/user)
return cpu ? cpu.emag_act(user) : 1
/obj/machinery/modular_computer/update_icon()
cut_overlays()
icon_state = icon_state_powered
if(!cpu || !cpu.enabled)
if (!(stat & NOPOWER) && (cpu && cpu.use_power()))
add_overlay(screen_icon_screensaver)
else
icon_state = icon_state_unpowered
set_light(0)
else
set_light(light_strength)
if(cpu.active_program)
add_overlay(cpu.active_program.program_icon_state ? cpu.active_program.program_icon_state : screen_icon_state_menu)
else
add_overlay(screen_icon_state_menu)
if(cpu && cpu.obj_integrity <= cpu.integrity_failure)
add_overlay("bsod")
add_overlay("broken")
// Eject ID card from computer, if it has ID slot with card inside.
/obj/machinery/modular_computer/proc/eject_id()
set name = "Eject ID"
set category = "Object"
if(cpu)
cpu.eject_id()
// Eject ID card from computer, if it has ID slot with card inside.
/obj/machinery/modular_computer/proc/eject_disk()
set name = "Eject Data Disk"
set category = "Object"
if(cpu)
cpu.eject_disk()
/obj/machinery/modular_computer/proc/eject_card()
set name = "Eject Intellicard"
set category = "Object"
set src in view(1)
if(cpu)
cpu.eject_card()
/obj/machinery/modular_computer/AltClick(mob/user)
if(cpu)
cpu.AltClick(user)
//ATTACK HAND IGNORING PARENT RETURN VALUE
// On-click handling. Turns on the computer if it's off and opens the GUI.
/obj/machinery/modular_computer/interact(mob/user)
if(cpu)
return cpu.interact(user) // CPU is an item, that's why we route attack_hand to attack_self
else
return ..()
// Process currently calls handle_power(), may be expanded in future if more things are added.
/obj/machinery/modular_computer/process()
if(cpu)
// Keep names in sync.
cpu.name = name
cpu.process()
// Used in following function to reduce copypaste
/obj/machinery/modular_computer/proc/power_failure(malfunction = 0)
var/obj/item/computer_hardware/battery/battery_module = cpu.all_components[MC_CELL]
if(cpu && cpu.enabled) // Shut down the computer
visible_message("<span class='danger'>\The [src]'s screen flickers [battery_module ? "\"BATTERY [malfunction ? "MALFUNCTION" : "CRITICAL"]\"" : "\"EXTERNAL POWER LOSS\""] warning as it shuts down unexpectedly.</span>")
if(cpu)
cpu.shutdown_computer(0)
stat |= NOPOWER
update_icon()
// Modular computers can have battery in them, we handle power in previous proc, so prevent this from messing it up for us.
/obj/machinery/modular_computer/power_change()
if(cpu && cpu.use_power()) // If MC_CPU still has a power source, PC wouldn't go offline.
stat &= ~NOPOWER
update_icon()
return
..()
update_icon()
/obj/machinery/modular_computer/attackby(var/obj/item/W as obj, mob/user)
if(cpu && !(flags_1 & NODECONSTRUCT_1))
return cpu.attackby(W, user)
return ..()
// Stronger explosions cause serious damage to internal components
// Minor explosions are mostly mitigitated by casing.
/obj/machinery/modular_computer/ex_act(severity)
if(cpu)
cpu.ex_act(severity)
..()
// EMPs are similar to explosions, but don't cause physical damage to the casing. Instead they screw up the components
/obj/machinery/modular_computer/emp_act(severity)
. = ..()
if(. & EMP_PROTECT_CONTENTS)
return
if(cpu)
cpu.emp_act(severity)
// "Stun" weapons can cause minor damage to components (short-circuits?)
// "Burn" damage is equally strong against internal components and exterior casing
// "Brute" damage mostly damages the casing.
/obj/machinery/modular_computer/bullet_act(obj/item/projectile/Proj)
if(cpu)
cpu.bullet_act(Proj)
@@ -0,0 +1,55 @@
/obj/machinery/modular_computer/console
name = "console"
desc = "A stationary computer."
icon = 'icons/obj/modular_console.dmi'
icon_state = "console"
icon_state_powered = "console"
icon_state_unpowered = "console-off"
screen_icon_state_menu = "menu"
hardware_flag = PROGRAM_CONSOLE
density = TRUE
base_idle_power_usage = 100
base_active_power_usage = 500
max_hardware_size = 4
steel_sheet_cost = 10
light_strength = 2
max_integrity = 300
integrity_failure = 150
var/console_department = "" // Used in New() to set network tag according to our area.
/obj/machinery/modular_computer/console/buildable/Initialize()
. = ..()
// User-built consoles start as empty frames.
var/obj/item/computer_hardware/hard_drive/hard_drive = cpu.all_components[MC_HDD]
var/obj/item/computer_hardware/hard_drive/network_card = cpu.all_components[MC_NET]
var/obj/item/computer_hardware/hard_drive/recharger = cpu.all_components[MC_CHARGE]
qdel(recharger)
qdel(network_card)
qdel(hard_drive)
/obj/machinery/modular_computer/console/Initialize()
. = ..()
var/obj/item/computer_hardware/battery/battery_module = cpu.all_components[MC_CELL]
if(battery_module)
qdel(battery_module)
var/obj/item/computer_hardware/network_card/wired/network_card = new()
cpu.install_component(network_card)
cpu.install_component(new /obj/item/computer_hardware/recharger/APC)
cpu.install_component(new /obj/item/computer_hardware/hard_drive/super) // Consoles generally have better HDDs due to lower space limitations
var/area/A = get_area(src)
// Attempts to set this console's tag according to our area. Since some areas have stuff like "XX - YY" in their names we try to remove that too.
if(A && console_department)
network_card.identification_string = replacetext(replacetext(replacetext("[A.name] [console_department] Console", " ", "_"), "-", ""), "__", "_") // Replace spaces with "_"
else if(A)
network_card.identification_string = replacetext(replacetext(replacetext("[A.name] Console", " ", "_"), "-", ""), "__", "_")
else if(console_department)
network_card.identification_string = replacetext(replacetext(replacetext("[console_department] Console", " ", "_"), "-", ""), "__", "_")
else
network_card.identification_string = "Unknown Console"
if(cpu)
cpu.screen_on = 1
update_icon()
@@ -0,0 +1,74 @@
# Modular computer programs
Ok. so a quick rundown on how to make a program. This is kind of a shitty documentation, but oh well I was asked to.
## Base setup
This is how the base program is setup. the rest is mostly tgui stuff. I'll use the ntnetmonitor as a base
```DM
/datum/computer_file/program/ntnetmonitor
/// This is obviously the name of the file itself. not much to be said
filename = "ntmonitor"
/// This is sort of the official name. it's what shows up on the main menu
filedesc = "NTNet Diagnostics and Monitoring"
/// This is what the screen will look like when the program is active
program_icon_state = "comm_monitor"
/// This is a sort of a description, visible when looking on the ntnet
extended_desc = "This program is a dummy."
/// size of the program. Big programs need more hard drive space. Don't
/// make it too big though.
size = 12
/// If this is set, the program will not run without an ntnet connection,
/// and will close if the connection is lost. Mainly for primarily online
/// programs.
requires_ntnet = 1
/// This is access required to run the program itself. ONLY SET THIS FOR
/// SUPER SECURE SHIT. This also acts as transfer_access as well.
required_access = access_network
/// This is the access needed to download from ntnet or host on the ptp
/// program. This is what you want to use most of the time.
transfer_access = access_change_ids
/// If it's available to download on ntnet. pretty self explanatory.
available_on_ntnet = 1
/// ditto but on emagged syndie net. Use this for antag programs
available_on_syndinet = 0
/// Bitflags (PROGRAM_CONSOLE, PROGRAM_LAPTOP, PROGRAM_TABLET combination)
/// or PROGRAM_ALL. Use this to limit what kind of machines can run the
/// program. For example, comms program should be limited to consoles and laptops.
usage_flags = PROGRAM_ALL
/// This one is kinda cool. If you have the program minimized, this will
/// show up in the header of the computer screen. You can even have the
/// program change what the header is based on the situation! See `alarm.dm`
/// for an example.
var/ui_header = "downloader_finished.gif"
```
## Preinstalls
Now. for pre-installing stuff.
Primarily done for consoles, there's an install_programs() proc in the console presets file in the machines folder.
for example, the command console one.
```DM
/obj/machinery/modular_computer/console/preset/command/install_programs()
cpu.hard_drive.store_file(new/datum/computer_file/program/chatclient())
cpu.hard_drive.store_file(new/datum/computer_file/program/card_mod())
```
Basically, you want to do cpu.hard_drive.store_file(new/*program path here*()) and put it in the subtype's install_programs().
Probably pretty self explanatory, but just in case.
Will probably be expanded when new features come around or I get asked to mention something.
@@ -0,0 +1,37 @@
/datum/computer_file
var/filename = "NewFile" // Placeholder. No spacebars
var/filetype = "XXX" // File full names are [filename].[filetype] so like NewFile.XXX in this case
var/size = 1 // File size in GQ. Integers only!
var/obj/item/computer_hardware/hard_drive/holder // Holder that contains this file.
var/unsendable = 0 // Whether the file may be sent to someone via NTNet transfer or other means.
var/undeletable = 0 // Whether the file may be deleted. Setting to 1 prevents deletion/renaming/etc.
var/uid // UID of this file
var/static/file_uid = 0
/datum/computer_file/New()
..()
uid = file_uid++
/datum/computer_file/Destroy()
if(!holder)
return ..()
holder.remove_file(src)
// holder.holder is the computer that has drive installed. If we are Destroy()ing program that's currently running kill it.
if(holder.holder && holder.holder.active_program == src)
holder.holder.kill_program(forced = TRUE)
holder = null
return ..()
// Returns independent copy of this file.
/datum/computer_file/proc/clone(rename = 0)
var/datum/computer_file/temp = new type
temp.unsendable = unsendable
temp.undeletable = undeletable
temp.size = size
if(rename)
temp.filename = filename + "(Copy)"
else
temp.filename = filename
temp.filetype = filetype
return temp
@@ -0,0 +1,20 @@
// /data/ files store data in string format.
// They don't contain other logic for now.
/datum/computer_file/data
var/stored_data = "" // Stored data in string format.
filetype = "DAT"
var/block_size = 250
var/do_not_edit = 0 // Whether the user will be reminded that the file probably shouldn't be edited.
/datum/computer_file/data/clone()
var/datum/computer_file/data/temp = ..()
temp.stored_data = stored_data
return temp
// Calculates file size from amount of characters in saved string
/datum/computer_file/data/proc/calculate_size()
size = max(1, round(length(stored_data) / block_size))
/datum/computer_file/data/logfile
filetype = "LOG"
@@ -0,0 +1,206 @@
// /program/ files are executable programs that do things.
/datum/computer_file/program
filetype = "PRG"
filename = "UnknownProgram" // File name. FILE NAME MUST BE UNIQUE IF YOU WANT THE PROGRAM TO BE DOWNLOADABLE FROM NTNET!
var/required_access = null // List of required accesses to *run* the program.
var/transfer_access = null // List of required access to download or file host the program
var/program_state = PROGRAM_STATE_KILLED// PROGRAM_STATE_KILLED or PROGRAM_STATE_BACKGROUND or PROGRAM_STATE_ACTIVE - specifies whether this program is running.
var/obj/item/modular_computer/computer // Device that runs this program.
var/filedesc = "Unknown Program" // User-friendly name of this program.
var/extended_desc = "N/A" // Short description of this program's function.
var/program_icon_state = null // Program-specific screen icon state
var/requires_ntnet = 0 // Set to 1 for program to require nonstop NTNet connection to run. If NTNet connection is lost program crashes.
var/requires_ntnet_feature = 0 // Optional, if above is set to 1 checks for specific function of NTNet (currently NTNET_SOFTWAREDOWNLOAD, NTNET_PEERTOPEER, NTNET_SYSTEMCONTROL and NTNET_COMMUNICATION)
var/ntnet_status = 1 // NTNet status, updated every tick by computer running this program. Don't use this for checks if NTNet works, computers do that. Use this for calculations, etc.
var/usage_flags = PROGRAM_ALL // Bitflags (PROGRAM_CONSOLE, PROGRAM_LAPTOP, PROGRAM_TABLET combination) or PROGRAM_ALL
var/network_destination = null // Optional string that describes what NTNet server/system this program connects to. Used in default logging.
var/available_on_ntnet = 1 // Whether the program can be downloaded from NTNet. Set to 0 to disable.
var/available_on_syndinet = 0 // Whether the program can be downloaded from SyndiNet (accessible via emagging the computer). Set to 1 to enable.
var/tgui_id // ID of TG UI interface
var/ui_style // ID of custom TG UI style (optional)
var/ui_x = 575 // Default size of TG UI window, in pixels
var/ui_y = 700
var/ui_header = null // Example: "something.gif" - a header image that will be rendered in computer's UI when this program is running at background. Images are taken from /icons/program_icons. Be careful not to use too large images!
/datum/computer_file/program/New(obj/item/modular_computer/comp = null)
..()
if(comp && istype(comp))
computer = comp
/datum/computer_file/program/Destroy()
computer = null
. = ..()
/datum/computer_file/program/clone()
var/datum/computer_file/program/temp = ..()
temp.required_access = required_access
temp.filedesc = filedesc
temp.program_icon_state = program_icon_state
temp.requires_ntnet = requires_ntnet
temp.requires_ntnet_feature = requires_ntnet_feature
temp.usage_flags = usage_flags
return temp
// Relays icon update to the computer.
/datum/computer_file/program/proc/update_computer_icon()
if(computer)
computer.update_icon()
// Attempts to create a log in global ntnet datum. Returns 1 on success, 0 on fail.
/datum/computer_file/program/proc/generate_network_log(text)
if(computer)
return computer.add_log(text)
return 0
/datum/computer_file/program/proc/is_supported_by_hardware(hardware_flag = 0, loud = 0, mob/user = null)
if(!(hardware_flag & usage_flags))
if(loud && computer && user)
to_chat(user, "<span class='danger'>\The [computer] flashes an \"Hardware Error - Incompatible software\" warning.</span>")
return 0
return 1
/datum/computer_file/program/proc/get_signal(specific_action = 0)
if(computer)
return computer.get_ntnet_status(specific_action)
return 0
// Called by Process() on device that runs us, once every tick.
/datum/computer_file/program/proc/process_tick()
return 1
// Check if the user can run program. Only humans can operate computer. Automatically called in run_program()
// User has to wear their ID for ID Scan to work.
// Can also be called manually, with optional parameter being access_to_check to scan the user's ID
/datum/computer_file/program/proc/can_run(mob/user, loud = 0, access_to_check, transfer = 0)
// Defaults to required_access
if(!access_to_check)
if(transfer && transfer_access)
access_to_check = transfer_access
else
access_to_check = required_access
if(!access_to_check) // No required_access, allow it.
return 1
if(!transfer && computer && (computer.obj_flags & EMAGGED)) //emags can bypass the execution locks but not the download ones.
return 1
if(IsAdminGhost(user))
return 1
if(issilicon(user))
return 1
if(ishuman(user))
var/obj/item/card/id/D
var/obj/item/computer_hardware/card_slot/card_slot
if(computer && card_slot)
card_slot = computer.all_components[MC_CARD]
D = card_slot.GetID()
var/mob/living/carbon/human/h = user
var/obj/item/card/id/I = h.get_idcard()
var/obj/item/card/id/C = h.get_active_held_item()
if(C)
C = C.GetID()
if(!(C && istype(C)))
C = null
if(!I && !C && !D)
if(loud)
to_chat(user, "<span class='danger'>\The [computer] flashes an \"RFID Error - Unable to scan ID\" warning.</span>")
return 0
if(I)
if(access_to_check in I.GetAccess())
return 1
else if(C)
if(access_to_check in C.GetAccess())
return 1
else if(D)
if(access_to_check in D.GetAccess())
return 1
if(loud)
to_chat(user, "<span class='danger'>\The [computer] flashes an \"Access Denied\" warning.</span>")
return 0
// This attempts to retrieve header data for UIs. If implementing completely new device of different type than existing ones
// always include the device here in this proc. This proc basically relays the request to whatever is running the program.
/datum/computer_file/program/proc/get_header_data()
if(computer)
return computer.get_header_data()
return list()
// This is performed on program startup. May be overridden to add extra logic. Remember to include ..() call. Return 1 on success, 0 on failure.
// When implementing new program based device, use this to run the program.
/datum/computer_file/program/proc/run_program(mob/living/user)
if(can_run(user, 1))
if(requires_ntnet && network_destination)
generate_network_log("Connection opened to [network_destination].")
program_state = PROGRAM_STATE_ACTIVE
return 1
return 0
// Use this proc to kill the program. Designed to be implemented by each program if it requires on-quit logic, such as the NTNRC client.
/datum/computer_file/program/proc/kill_program(forced = FALSE)
program_state = PROGRAM_STATE_KILLED
if(network_destination)
generate_network_log("Connection to [network_destination] closed.")
return 1
/datum/computer_file/program/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state)
ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
if(!ui && tgui_id)
var/datum/asset/assets = get_asset_datum(/datum/asset/simple/headers)
assets.send(user)
ui = new(user, src, ui_key, tgui_id, filedesc, ui_x, ui_y, state = state)
if(ui_style)
ui.set_style(ui_style)
ui.set_autoupdate(state = 1)
ui.open()
// CONVENTIONS, READ THIS WHEN CREATING NEW PROGRAM AND OVERRIDING THIS PROC:
// Topic calls are automagically forwarded from NanoModule this program contains.
// Calls beginning with "PRG_" are reserved for programs handling.
// Calls beginning with "PC_" are reserved for computer handling (by whatever runs the program)
// ALWAYS INCLUDE PARENT CALL ..() OR DIE IN FIRE.
/datum/computer_file/program/ui_act(action,params,datum/tgui/ui)
if(..())
return 1
if(computer)
switch(action)
if("PC_exit")
computer.kill_program()
ui.close()
return 1
if("PC_shutdown")
computer.shutdown_computer()
ui.close()
return 1
if("PC_minimize")
var/mob/user = usr
if(!computer.active_program || !computer.all_components[MC_CPU])
return
computer.idle_threads.Add(computer.active_program)
program_state = PROGRAM_STATE_BACKGROUND // Should close any existing UIs
computer.active_program = null
computer.update_icon()
ui.close()
if(user && istype(user))
computer.ui_interact(user) // Re-open the UI on this computer. It should show the main screen now.
/datum/computer_file/program/ui_host()
if(computer.physical)
return computer.physical
else
return computer
/datum/computer_file/program/ui_status(mob/user)
if(program_state != PROGRAM_STATE_ACTIVE) // Our program was closed. Close the ui if it exists.
return UI_CLOSE
return ..()
@@ -0,0 +1,18 @@
// Events are sent to the program by the computer.
// Always include a parent call when overriding an event.
// Called when the ID card is removed from computer. ID is removed AFTER this proc.
/datum/computer_file/program/proc/event_idremoved(background, slot)
return
// Called when the computer fails due to power loss. Override when program wants to specifically react to power loss.
/datum/computer_file/program/proc/event_powerfailure(background)
kill_program(forced = TRUE)
// Called when the network connectivity fails. Computer does necessary checks and only calls this when requires_ntnet_feature and similar variables are not met.
/datum/computer_file/program/proc/event_networkfailure(background)
kill_program(forced = TRUE)
if(background)
computer.visible_message("<span class='danger'>\The [computer]'s screen displays an \"Process [filename].[filetype] (PID [rand(100,999)]) terminated - Network Error\" error</span>")
else
computer.visible_message("<span class='danger'>\The [computer]'s screen briefly freezes and then shows \"NETWORK ERROR - NTNet connection lost. Please retry. If problem persists contact your system administrator.\" error.</span>")
@@ -0,0 +1,121 @@
/datum/computer_file/program/aidiag
filename = "aidiag"
filedesc = "AI Integrity Restorer"
program_icon_state = "generic"
extended_desc = "This program is capable of reconstructing damaged AI systems. Requires direct AI connection via intellicard slot."
size = 12
requires_ntnet = 0
usage_flags = PROGRAM_CONSOLE
transfer_access = ACCESS_HEADS
available_on_ntnet = 1
tgui_id = "ntos_ai_restorer"
ui_x = 600
ui_y = 400
var/restoring = FALSE
/datum/computer_file/program/aidiag/proc/get_ai(cardcheck)
var/obj/item/computer_hardware/ai_slot/ai_slot
if(computer)
ai_slot = computer.all_components[MC_AI]
if(computer && ai_slot && ai_slot.check_functionality())
if(cardcheck == 1)
return ai_slot
if(ai_slot.enabled && ai_slot.stored_card)
if(cardcheck == 2)
return ai_slot.stored_card
if(ai_slot.stored_card.AI)
return ai_slot.stored_card.AI
return null
/datum/computer_file/program/aidiag/ui_act(action, params)
if(..())
return TRUE
var/mob/living/silicon/ai/A = get_ai()
if(!A)
restoring = FALSE
switch(action)
if("PRG_beginReconstruction")
if(A && A.health < 100)
restoring = TRUE
return TRUE
if("PRG_eject")
if(computer.all_components[MC_AI])
var/obj/item/computer_hardware/ai_slot/ai_slot = computer.all_components[MC_AI]
if(ai_slot && ai_slot.stored_card)
ai_slot.try_eject(0,usr)
return TRUE
/datum/computer_file/program/aidiag/process_tick()
..()
if(!restoring) //Put the check here so we don't check for an ai all the time
return
var/obj/item/aicard/cardhold = get_ai(2)
var/obj/item/computer_hardware/ai_slot/ai_slot = get_ai(1)
var/mob/living/silicon/ai/A = get_ai()
if(!A || !cardhold)
restoring = FALSE // If the AI was removed, stop the restoration sequence.
if(ai_slot)
ai_slot.locked = FALSE
return
if(cardhold.flush)
ai_slot.locked = FALSE
restoring = FALSE
return
ai_slot.locked =TRUE
A.adjustOxyLoss(-1, 0)
A.adjustFireLoss(-1, 0)
A.adjustToxLoss(-1, 0)
A.adjustBruteLoss(-1, 0)
A.updatehealth()
if(A.health >= 0 && A.stat == DEAD)
A.revive()
// Finished restoring
if(A.health >= 100)
ai_slot.locked = FALSE
restoring = FALSE
return TRUE
/datum/computer_file/program/aidiag/ui_data(mob/user)
var/list/data = get_header_data()
var/mob/living/silicon/ai/AI
// A shortcut for getting the AI stored inside the computer. The program already does necessary checks.
AI = get_ai()
var/obj/item/aicard/aicard = get_ai(2)
if(!aicard)
data["nocard"] = TRUE
data["error"] = "Please insert an intelliCard."
else
if(!AI)
data["error"] = "No AI located"
else
var/obj/item/aicard/cardhold = AI.loc
if(cardhold.flush)
data["error"] = "Flush in progress"
else
data["name"] = AI.name
data["restoring"] = restoring
data["laws"] = AI.laws.get_law_list(include_zeroth = 1)
data["health"] = (AI.health + 100) / 2
data["isDead"] = AI.stat == DEAD
data["ai_laws"] = AI.laws.get_law_list(include_zeroth = 1)
return data
/datum/computer_file/program/aidiag/kill_program(forced)
restoring = FALSE
return ..(forced)
@@ -0,0 +1,101 @@
/datum/computer_file/program/alarm_monitor
filename = "alarmmonitor"
filedesc = "Alarm Monitor"
ui_header = "alarm_green.gif"
program_icon_state = "alert-green"
extended_desc = "This program provides visual interface for station's alarm system."
requires_ntnet = 1
network_destination = "alarm monitoring network"
size = 5
tgui_id = "ntos_station_alert"
ui_x = 315
ui_y = 500
var/has_alert = 0
var/alarms = list("Fire" = list(), "Atmosphere" = list(), "Power" = list())
/datum/computer_file/program/alarm_monitor/process_tick()
..()
if(has_alert)
program_icon_state = "alert-red"
ui_header = "alarm_red.gif"
update_computer_icon()
else
if(!has_alert)
program_icon_state = "alert-green"
ui_header = "alarm_green.gif"
update_computer_icon()
return 1
/datum/computer_file/program/alarm_monitor/ui_data(mob/user)
var/list/data = get_header_data()
data["alarms"] = list()
for(var/class in alarms)
data["alarms"][class] = list()
for(var/area in alarms[class])
data["alarms"][class] += area
return data
/datum/computer_file/program/alarm_monitor/proc/triggerAlarm(class, area/A, O, obj/source)
if(is_station_level(source.z))
if(!(A.type in GLOB.the_station_areas))
return
else if(!is_mining_level(source.z) || istype(A, /area/ruin))
return
var/list/L = alarms[class]
for(var/I in L)
if (I == A.name)
var/list/alarm = L[I]
var/list/sources = alarm[3]
if (!(source in sources))
sources += source
return 1
var/obj/machinery/camera/C = null
var/list/CL = null
if(O && istype(O, /list))
CL = O
if (CL.len == 1)
C = CL[1]
else if(O && istype(O, /obj/machinery/camera))
C = O
L[A.name] = list(A, (C ? C : O), list(source))
update_alarm_display()
return 1
/datum/computer_file/program/alarm_monitor/proc/cancelAlarm(class, area/A, obj/origin)
var/list/L = alarms[class]
var/cleared = 0
for (var/I in L)
if (I == A.name)
var/list/alarm = L[I]
var/list/srcs = alarm[3]
if (origin in srcs)
srcs -= origin
if (srcs.len == 0)
cleared = 1
L -= I
update_alarm_display()
return !cleared
/datum/computer_file/program/alarm_monitor/proc/update_alarm_display()
has_alert = FALSE
for(var/cat in alarms)
var/list/L = alarms[cat]
if(L.len)
has_alert = TRUE
/datum/computer_file/program/alarm_monitor/run_program(mob/user)
. = ..(user)
GLOB.alarmdisplay += src
/datum/computer_file/program/alarm_monitor/kill_program(forced = FALSE)
GLOB.alarmdisplay -= src
..()
@@ -0,0 +1,100 @@
/datum/computer_file/program/ntnet_dos
filename = "ntn_dos"
filedesc = "DoS Traffic Generator"
program_icon_state = "hostile"
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
tgui_id = "ntos_net_dos"
ui_style = "syndicate"
ui_x = 400
ui_y = 250
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 * 10
if(2)
dos_speed = NTNETSPEED_HIGHSIGNAL * 10
if(3)
dos_speed = NTNETSPEED_ETHERNET * 10
if(target && executed)
target.dos_overload += dos_speed
if(!target.is_operational())
target.dos_sources.Remove(src)
target = null
error = "Connection to destination relay lost."
/datum/computer_file/program/ntnet_dos/kill_program(forced = FALSE)
if(target)
target.dos_sources.Remove(src)
target = null
executed = 0
..()
/datum/computer_file/program/ntnet_dos/ui_act(action, params)
if(..())
return 1
switch(action)
if("PRG_target_relay")
for(var/obj/machinery/ntnet_relay/R in SSnetworks.station_network.relays)
if("[R.uid]" == params["targid"])
target = R
return 1
if("PRG_reset")
if(target)
target.dos_sources.Remove(src)
target = null
executed = 0
error = ""
return 1
if("PRG_execute")
if(target)
executed = 1
target.dos_sources.Add(src)
if(SSnetworks.station_network.intrusion_detection_enabled)
var/obj/item/computer_hardware/network_card/network_card = computer.all_components[MC_NET]
SSnetworks.station_network.add_log("IDS WARNING - Excess traffic flood targeting relay [target.uid] detected from device: [network_card.get_network_tag()]")
SSnetworks.station_network.intrusion_detection_alarm = 1
return 1
/datum/computer_file/program/ntnet_dos/ui_data(mob/user)
if(!SSnetworks.station_network)
return
var/list/data = list()
data = get_header_data()
if(error)
data["error"] = error
else if(target && executed)
data["target"] = 1
data["speed"] = 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 = target.dos_overload * 100 / target.dos_capacity
data["dos_strings"] = list()
for(var/j, j<10, j++)
var/string = ""
for(var/i, i<20, i++)
string = "[string][prob(percentage)]"
data["dos_strings"] += list(list("nums" = string))
else
data["relays"] = list()
for(var/obj/machinery/ntnet_relay/R in SSnetworks.station_network.relays)
data["relays"] += list(list("id" = R.uid))
data["focus"] = target ? target.uid : null
return data
@@ -0,0 +1,71 @@
/datum/computer_file/program/revelation
filename = "revelation"
filedesc = "Revelation"
program_icon_state = "hostile"
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
tgui_id = "ntos_revelation"
ui_style = "syndicate"
ui_x = 400
ui_y = 250
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)
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/obj/item/computer_hardware/hard_drive/hard_drive = computer.all_components[MC_HDD]
var/obj/item/computer_hardware/battery/battery_module = computer.all_components[MC_CELL]
var/obj/item/computer_hardware/recharger/recharger = computer.all_components[MC_CHARGE]
qdel(hard_drive)
computer.take_damage(25, BRUTE, 0, 0)
if(battery_module && prob(25))
qdel(battery_module)
computer.visible_message("<span class='notice'>\The [computer]'s battery explodes in rain of sparks.</span>")
var/datum/effect_system/spark_spread/spark_system = new /datum/effect_system/spark_spread
spark_system.start()
if(recharger && prob(50))
qdel(recharger)
computer.visible_message("<span class='notice'>\The [computer]'s recharger explodes in rain of sparks.</span>")
var/datum/effect_system/spark_spread/spark_system = new /datum/effect_system/spark_spread
spark_system.start()
/datum/computer_file/program/revelation/ui_act(action, params)
if(..())
return 1
switch(action)
if("PRG_arm")
armed = !armed
if("PRG_activate")
activate()
if("PRG_obfuscate")
var/mob/living/user = usr
var/newname = sanitize(input(user, "Enter new program name: "))
if(!newname)
return
filedesc = newname
/datum/computer_file/program/revelation/clone()
var/datum/computer_file/program/revelation/temp = ..()
temp.armed = armed
return temp
/datum/computer_file/program/revelation/ui_data(mob/user)
var/list/data = get_header_data()
data["armed"] = armed
return data
@@ -0,0 +1,481 @@
/datum/computer_file/program/card_mod
filename = "cardmod"
filedesc = "ID Card Modification"
program_icon_state = "id"
extended_desc = "Program for programming employee ID cards to access parts of the station."
transfer_access = ACCESS_HEADS
requires_ntnet = 0
size = 8
tgui_id = "ntos_card"
ui_x = 600
ui_y = 700
var/mod_mode = 1
var/is_centcom = 0
var/show_assignments = 0
var/minor = 0
var/authenticated = 0
var/list/reg_ids = list()
var/list/region_access = null
var/list/head_subordinates = null
var/target_dept = 0 //Which department this computer has access to. 0=all departments
var/change_position_cooldown = 30
//Jobs you cannot open new positions for
var/list/blacklisted = list(
"AI",
"Assistant",
"Cyborg",
"Captain",
"Head of Personnel",
"Head of Security",
"Chief Engineer",
"Research Director",
"Chief Medical Officer")
//The scaling factor of max total positions in relation to the total amount of people on board the station in %
var/max_relative_positions = 30 //30%: Seems reasonable, limit of 6 @ 20 players
//This is used to keep track of opened positions for jobs to allow instant closing
//Assoc array: "JobName" = (int)<Opened Positions>
var/list/opened_positions = list();
/datum/computer_file/program/card_mod/New()
..()
addtimer(CALLBACK(src, .proc/SetConfigCooldown), 0)
/datum/computer_file/program/card_mod/proc/SetConfigCooldown()
change_position_cooldown = CONFIG_GET(number/id_console_jobslot_delay)
/datum/computer_file/program/card_mod/event_idremoved(background, slot)
if(!slot || slot == 2)// slot being false means both are removed
minor = 0
authenticated = 0
head_subordinates = null
region_access = null
/datum/computer_file/program/card_mod/proc/job_blacklisted(jobtitle)
return (jobtitle in blacklisted)
//Logic check for if you can open the job
/datum/computer_file/program/card_mod/proc/can_open_job(datum/job/job)
if(job)
if(!job_blacklisted(job.title))
if((job.total_positions <= GLOB.player_list.len * (max_relative_positions / 100)))
var/delta = (world.time / 10) - GLOB.time_last_changed_position
if((change_position_cooldown < delta) || (opened_positions[job.title] < 0))
return 1
return -2
return 0
return 0
//Logic check for if you can close the job
/datum/computer_file/program/card_mod/proc/can_close_job(datum/job/job)
if(job)
if(!job_blacklisted(job.title))
if(job.total_positions > job.current_positions)
var/delta = (world.time / 10) - GLOB.time_last_changed_position
if((change_position_cooldown < delta) || (opened_positions[job.title] > 0))
return 1
return -2
return 0
return 0
/datum/computer_file/program/card_mod/proc/format_jobs(list/jobs)
var/obj/item/computer_hardware/card_slot/card_slot = computer.all_components[MC_CARD]
var/obj/item/card/id/id_card = card_slot.stored_card
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/computer_file/program/card_mod/ui_act(action, params)
if(..())
return 1
var/obj/item/computer_hardware/card_slot/card_slot
var/obj/item/computer_hardware/printer/printer
if(computer)
card_slot = computer.all_components[MC_CARD]
printer = computer.all_components[MC_PRINT]
if(!card_slot)
return
var/obj/item/card/id/user_id_card = null
var/mob/user = usr
var/obj/item/card/id/id_card = card_slot.stored_card
var/obj/item/card/id/auth_card = card_slot.stored_card2
if(auth_card)
user_id_card = auth_card
else
if(ishuman(user))
var/mob/living/carbon/human/h = user
user_id_card = h.get_idcard()
switch(action)
if("PRG_switchm")
if(params["target"] == "mod")
mod_mode = 1
else if (params["target"] == "manifest")
mod_mode = 0
else if (params["target"] == "manage")
mod_mode = 2
if("PRG_togglea")
if(show_assignments)
show_assignments = 0
else
show_assignments = 1
if("PRG_print")
if(computer && printer) //This option should never be called if there is no printer
if(mod_mode)
if(authorized())
var/contents = {"<h4>Access Report</h4>
<u>Prepared By:</u> [user_id_card && 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>Access:</u><br>
"}
var/known_access_rights = get_all_accesses()
for(var/A in id_card.access)
if(A in known_access_rights)
contents += " [get_access_desc(A)]"
if(!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>
[GLOB.data_core ? GLOB.data_core.get_manifest(0) : ""]
"}
if(!printer.print_text(contents,text("crew manifest ([])", STATION_TIME_TIMESTAMP("hh:mm:ss"))))
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("PRG_eject")
if(computer && card_slot)
var/select = params["target"]
switch(select)
if("id")
if(id_card)
GLOB.data_core.manifest_modify(id_card.registered_name, id_card.assignment)
card_slot.try_eject(1, user)
else
var/obj/item/I = usr.get_active_held_item()
if (istype(I, /obj/item/card/id))
if(!usr.transferItemToLoc(I, computer))
return
card_slot.stored_card = I
if("auth")
if(auth_card)
if(id_card)
GLOB.data_core.manifest_modify(id_card.registered_name, id_card.assignment)
head_subordinates = null
region_access = null
authenticated = 0
minor = 0
card_slot.try_eject(2, user)
else
var/obj/item/I = usr.get_active_held_item()
if (istype(I, /obj/item/card/id))
if(!usr.transferItemToLoc(I, computer))
return
card_slot.stored_card2 = I
if("PRG_terminate")
if(computer && ((id_card.assignment in head_subordinates) || id_card.assignment == "Assistant"))
id_card.assignment = "Unassigned"
remove_nt_access(id_card)
if("PRG_edit")
if(computer && authorized())
if(params["name"])
var/temp_name = reject_bad_name(input("Enter name.", "Name", id_card.registered_name))
if(temp_name)
id_card.registered_name = temp_name
else
computer.visible_message("<span class='notice'>[computer] buzzes rudely.</span>")
//else if(params["account"])
// var/account_num = text2num(input("Enter account number.", "Account", id_card.associated_account_number))
// id_card.associated_account_number = account_num
if("PRG_assign")
if(computer && authorized() && id_card)
var/t1 = params["assign_target"]
if(t1 == "Custom")
var/temp_t = reject_bad_text(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(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
if("PRG_access")
if(params["allowed"] && computer && authorized())
var/access_type = text2num(params["access_target"])
var/access_allowed = text2num(params["allowed"])
if(access_type in (is_centcom ? get_all_centcom_access() : get_all_accesses()))
id_card.access -= access_type
if(!access_allowed)
id_card.access += access_type
if("PRG_open_job")
var/edit_job_target = params["target"]
var/datum/job/j = SSjob.GetJob(edit_job_target)
if(!j)
return 0
if(can_open_job(j) != 1)
return 0
if(opened_positions[edit_job_target] >= 0)
GLOB.time_last_changed_position = world.time / 10
j.total_positions++
opened_positions[edit_job_target]++
if("PRG_close_job")
var/edit_job_target = params["target"]
var/datum/job/j = SSjob.GetJob(edit_job_target)
if(!j)
return 0
if(can_close_job(j) != 1)
return 0
//Allow instant closing without cooldown if a position has been opened before
if(opened_positions[edit_job_target] <= 0)
GLOB.time_last_changed_position = world.time / 10
j.total_positions--
opened_positions[edit_job_target]--
if("PRG_regsel")
if(!reg_ids)
reg_ids = list()
var/regsel = text2num(params["region"])
if(regsel in reg_ids)
reg_ids -= regsel
else
reg_ids += regsel
if(id_card)
id_card.name = text("[id_card.registered_name]'s ID Card ([id_card.assignment])")
return 1
/datum/computer_file/program/card_mod/proc/remove_nt_access(obj/item/card/id/id_card)
id_card.access -= get_all_accesses()
id_card.access -= get_all_centcom_access()
/datum/computer_file/program/card_mod/proc/apply_access(obj/item/card/id/id_card, list/accesses)
id_card.access |= accesses
/datum/computer_file/program/card_mod/ui_data(mob/user)
var/list/data = get_header_data()
var/obj/item/computer_hardware/card_slot/card_slot
var/obj/item/computer_hardware/printer/printer
if(computer)
card_slot = computer.all_components[MC_CARD]
printer = computer.all_components[MC_PRINT]
data["mmode"] = mod_mode
var/authed = 0
if(computer)
if(card_slot)
var/obj/item/card/id/auth_card = card_slot.stored_card2
data["auth_name"] = auth_card ? strip_html_simple(auth_card.name) : "-----"
authed = authorized()
if(mod_mode == 2)
data["slots"] = list()
var/list/pos = list()
for(var/datum/job/job in SSjob.occupations)
if(job.title in blacklisted)
continue
var/list/status_open = build_manage(job,1)
var/list/status_close = build_manage(job,0)
pos.Add(list(list(
"title" = job.title,
"current" = job.current_positions,
"total" = job.total_positions,
"status_open" = (authed && !minor) ? status_open["enable"]: 0,
"status_close" = (authed && !minor) ? status_close["enable"] : 0,
"desc_open" = status_open["desc"],
"desc_close" = status_close["desc"])))
data["slots"] = pos
data["src"] = "[REF(src)]"
data["station_name"] = station_name()
if(!mod_mode)
data["manifest"] = list()
var/list/crew = list()
for(var/datum/data/record/t in sortRecord(GLOB.data_core.general))
crew.Add(list(list(
"name" = t.fields["name"],
"rank" = t.fields["rank"])))
data["manifest"] = crew
data["assignments"] = show_assignments
if(computer)
data["have_id_slot"] = !!card_slot
data["have_printer"] = !!printer
if(!card_slot && mod_mode == 1)
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["centcom_access"] = is_centcom
data["authenticated"] = authed
if(mod_mode == 1 && computer)
if(card_slot)
var/obj/item/card/id/id_card = card_slot.stored_card
data["has_id"] = !!id_card
data["id_rank"] = id_card && id_card.assignment ? html_encode(id_card.assignment) : "Unassigned"
data["id_owner"] = id_card && id_card.registered_name ? html_encode(id_card.registered_name) : "-----"
data["id_name"] = id_card ? strip_html_simple(id_card.name) : "-----"
if(show_assignments)
data["engineering_jobs"] = format_jobs(GLOB.engineering_positions)
data["medical_jobs"] = format_jobs(GLOB.medical_positions)
data["science_jobs"] = format_jobs(GLOB.science_positions)
data["security_jobs"] = format_jobs(GLOB.security_positions)
data["cargo_jobs"] = format_jobs(GLOB.supply_positions)
data["civilian_jobs"] = format_jobs(GLOB.civilian_positions)
data["centcom_jobs"] = format_jobs(get_all_centcom_jobs())
if(card_slot.stored_card)
var/obj/item/card/id/id_card = 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++)
if((minor || target_dept) && !(i in region_access))
continue
var/list/accesses = list()
if(i in reg_ids)
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),
"regid" = i,
"selected" = (i in reg_ids) ? 1 : null,
"accesses" = accesses)))
data["regions"] = regions
data["minor"] = target_dept || minor ? 1 : 0
return data
/datum/computer_file/program/card_mod/proc/build_manage(datum/job,open = FALSE)
var/out = "Denied"
var/can_change= 0
if(open)
can_change = can_open_job(job)
else
can_change = can_close_job(job)
var/enable = 0
if(can_change == 1)
out = "[open ? "Open Position" : "Close Position"]"
enable = 1
else if(can_change == -2)
var/time_to_wait = round(change_position_cooldown - ((world.time / 10) - GLOB.time_last_changed_position), 1)
var/mins = round(time_to_wait / 60)
var/seconds = time_to_wait - (60*mins)
out = "Cooldown ongoing: [mins]:[(seconds < 10) ? "0[seconds]" : "[seconds]"]"
else
out = "Denied"
return list("enable" = enable, "desc" = out)
/datum/computer_file/program/card_mod/proc/authorized()
if(!authenticated && computer)
var/obj/item/computer_hardware/card_slot/card_slot = computer.all_components[MC_CARD]
if(card_slot)
var/obj/item/card/id/auth_card = card_slot.stored_card2
if(auth_card)
region_access = list()
if(ACCESS_CHANGE_IDS in auth_card.GetAccess())
minor = 0
authenticated = 1
return 1
else
if((ACCESS_HOP in auth_card.access) && ((target_dept==1) || !target_dept))
region_access |= 1
region_access |= 6
get_subordinates("Head of Personnel")
if((ACCESS_HOS in auth_card.access) && ((target_dept==2) || !target_dept))
region_access |= 2
get_subordinates("Head of Security")
if((ACCESS_CMO in auth_card.access) && ((target_dept==3) || !target_dept))
region_access |= 3
get_subordinates("Chief Medical Officer")
if((ACCESS_RD in auth_card.access) && ((target_dept==4) || !target_dept))
region_access |= 4
get_subordinates("Research Director")
if((ACCESS_CE in auth_card.access) && ((target_dept==5) || !target_dept))
region_access |= 5
get_subordinates("Chief Engineer")
if(region_access.len)
minor = 1
authenticated = 1
return 1
else
return authenticated
/datum/computer_file/program/card_mod/proc/get_subordinates(rank)
head_subordinates = list()
for(var/datum/job/job in SSjob.occupations)
if(rank in job.department_head)
head_subordinates += job.title
@@ -0,0 +1,67 @@
// 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 = "Hardware Configuration Tool"
extended_desc = "This program allows configuration of computer's hardware"
program_icon_state = "generic"
unsendable = 1
undeletable = 1
size = 4
available_on_ntnet = 0
requires_ntnet = 0
tgui_id = "ntos_configuration"
var/obj/item/modular_computer/movable = null
/datum/computer_file/program/computerconfig/ui_data(mob/user)
movable = computer
var/obj/item/computer_hardware/hard_drive/hard_drive = movable.all_components[MC_HDD]
var/obj/item/computer_hardware/battery/battery_module = movable.all_components[MC_CELL]
if(!istype(movable))
movable = null
// No computer connection, we can't get data from that.
if(!movable)
return 0
var/list/data = get_header_data()
data["disk_size"] = hard_drive.max_capacity
data["disk_used"] = hard_drive.used_capacity
data["power_usage"] = movable.last_power_usage
data["battery_exists"] = battery_module ? 1 : 0
if(battery_module && battery_module.battery)
data["battery_rating"] = battery_module.battery.maxcharge
data["battery_percent"] = round(battery_module.battery.percent())
if(battery_module && battery_module.battery)
data["battery"] = list("max" = battery_module.battery.maxcharge, "charge" = round(battery_module.battery.charge))
var/list/all_entries[0]
for(var/I in movable.all_components)
var/obj/item/computer_hardware/H = movable.all_components[I]
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
return data
/datum/computer_file/program/computerconfig/ui_act(action,params)
if(..())
return
switch(action)
if("PC_toggle_component")
var/obj/item/computer_hardware/H = movable.find_hardware_by_name(params["name"])
if(H && istype(H))
H.enabled = !H.enabled
. = TRUE
@@ -0,0 +1,232 @@
/datum/computer_file/program/filemanager
filename = "filemanager"
filedesc = "File Manager"
extended_desc = "This program allows management of files."
program_icon_state = "generic"
size = 8
requires_ntnet = 0
available_on_ntnet = 0
undeletable = 1
tgui_id = "ntos_file_manager"
var/open_file
var/error
/datum/computer_file/program/filemanager/ui_act(action, params)
if(..())
return 1
var/obj/item/computer_hardware/hard_drive/HDD = computer.all_components[MC_HDD]
var/obj/item/computer_hardware/hard_drive/RHDD = computer.all_components[MC_SDD]
var/obj/item/computer_hardware/printer/printer = computer.all_components[MC_PRINT]
switch(action)
if("PRG_openfile")
. = 1
open_file = params["name"]
if("PRG_newtextfile")
. = 1
var/newname = stripped_input(usr, "Enter file name or leave blank to cancel:", "File rename", max_length=50)
if(!newname)
return 1
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("PRG_deletefile")
. = 1
if(!HDD)
return 1
var/datum/computer_file/file = HDD.find_file_by_name(params["name"])
if(!file || file.undeletable)
return 1
HDD.remove_file(file)
if("PRG_usbdeletefile")
. = 1
if(!RHDD)
return 1
var/datum/computer_file/file = RHDD.find_file_by_name(params["name"])
if(!file || file.undeletable)
return 1
RHDD.remove_file(file)
if("PRG_closefile")
. = 1
open_file = null
error = null
if("PRG_clone")
. = 1
if(!HDD)
return 1
var/datum/computer_file/F = HDD.find_file_by_name(params["name"])
if(!F || !istype(F))
return 1
var/datum/computer_file/C = F.clone(1)
HDD.store_file(C)
if("PRG_rename")
. = 1
if(!HDD)
return 1
var/datum/computer_file/file = HDD.find_file_by_name(params["name"])
if(!file || !istype(file))
return 1
var/newname = stripped_input(usr, "Enter new file name:", "File rename", file.filename, max_length=50)
if(file && newname)
file.filename = newname
if("PRG_edit")
. = 1
if(!open_file)
return 1
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
// 16384 is the limit for file length in characters. Currently, papers have value of 2048 so this is 8 times as long, since we can't edit parts of the file independently.
var/newtext = stripped_multiline_input(usr, "Editing file [open_file]. You may use most tags used in paper formatting:", "Text Editor", html_decode(F.stored_data), 16384, TRUE)
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>[F.stored_data]<br><br>"
HDD.store_file(backup)
if("PRG_printfile")
. = 1
if(!open_file)
return 1
if(!HDD)
return 1
var/datum/computer_file/data/F = HDD.find_file_by_name(open_file)
if(!F || !istype(F))
return 1
if(!printer)
error = "Missing Hardware: Your computer does not have required hardware to complete this operation."
return 1
if(!printer.print_text("<font face=\"[(computer.obj_flags & EMAGGED) ? CRAYON_FONT : PRINTER_FONT]\">" + prepare_printjob(F.stored_data) + "</font>", open_file))
error = "Hardware error: Printer was unable to print the file. It may be out of paper."
return 1
if("PRG_copytousb")
. = 1
if(!HDD || !RHDD)
return 1
var/datum/computer_file/F = HDD.find_file_by_name(params["name"])
if(!F || !istype(F))
return 1
var/datum/computer_file/C = F.clone(0)
RHDD.store_file(C)
if("PRG_copyfromusb")
. = 1
if(!HDD || !RHDD)
return 1
var/datum/computer_file/F = RHDD.find_file_by_name(params["name"])
if(!F || !istype(F))
return 1
var/datum/computer_file/C = F.clone(0)
HDD.store_file(C)
/datum/computer_file/program/filemanager/proc/parse_tags(t)
t = replacetext(t, "\[center\]", "<center>")
t = replacetext(t, "\[/center\]", "</center>")
t = replacetext(t, "\[br\]", "<BR>")
t = replacetext(t, "\n", "<BR>")
t = replacetext(t, "\[b\]", "<B>")
t = replacetext(t, "\[/b\]", "</B>")
t = replacetext(t, "\[i\]", "<I>")
t = replacetext(t, "\[/i\]", "</I>")
t = replacetext(t, "\[u\]", "<U>")
t = replacetext(t, "\[/u\]", "</U>")
t = replacetext(t, "\[time\]", "[STATION_TIME_TIMESTAMP("hh:mm:ss")]")
t = replacetext(t, "\[date\]", "[time2text(world.realtime, "MMM DD")] [GLOB.year_integer+540]")
t = replacetext(t, "\[large\]", "<font size=\"4\">")
t = replacetext(t, "\[/large\]", "</font>")
t = replacetext(t, "\[h1\]", "<H1>")
t = replacetext(t, "\[/h1\]", "</H1>")
t = replacetext(t, "\[h2\]", "<H2>")
t = replacetext(t, "\[/h2\]", "</H2>")
t = replacetext(t, "\[h3\]", "<H3>")
t = replacetext(t, "\[/h3\]", "</H3>")
t = replacetext(t, "\[*\]", "<li>")
t = replacetext(t, "\[hr\]", "<HR>")
t = replacetext(t, "\[small\]", "<font size = \"1\">")
t = replacetext(t, "\[/small\]", "</font>")
t = replacetext(t, "\[list\]", "<ul>")
t = replacetext(t, "\[/list\]", "</ul>")
t = replacetext(t, "\[table\]", "<table border=1 cellspacing=0 cellpadding=3 style='border: 1px solid black;'>")
t = replacetext(t, "\[/table\]", "</td></tr></table>")
t = replacetext(t, "\[grid\]", "<table>")
t = replacetext(t, "\[/grid\]", "</td></tr></table>")
t = replacetext(t, "\[row\]", "</td><tr>")
t = replacetext(t, "\[tr\]", "</td><tr>")
t = replacetext(t, "\[td\]", "<td>")
t = replacetext(t, "\[cell\]", "<td>")
t = replacetext(t, "\[tab\]", "&nbsp;&nbsp;&nbsp;&nbsp;")
t = parsemarkdown_basic(t)
return t
/datum/computer_file/program/filemanager/proc/prepare_printjob(t) // Additional stuff to parse if we want to print it and make a happy Head of Personnel. Forms FTW.
t = replacetext(t, "\[field\]", "<span class=\"paper_field\"></span>")
t = replacetext(t, "\[sign\]", "<span class=\"paper_field\"></span>")
t = parse_tags(t)
t = replacetext(t, regex("(?:%s(?:ign)|%f(?:ield))(?=\\s|$)", "ig"), "<span class=\"paper_field\"></span>")
return t
/datum/computer_file/program/filemanager/ui_data(mob/user)
var/list/data = get_header_data()
var/obj/item/computer_hardware/hard_drive/HDD = computer.all_components[MC_HDD]
var/obj/item/computer_hardware/hard_drive/portable/RHDD = computer.all_components[MC_SDD]
if(error)
data["error"] = error
if(open_file)
var/datum/computer_file/data/file
if(!computer || !HDD)
data["error"] = "I/O ERROR: Unable to access hard drive."
else
file = HDD.find_file_by_name(open_file)
if(!istype(file))
data["error"] = "I/O ERROR: Unable to open file."
else
data["filedata"] = parse_tags(file.stored_data)
data["filename"] = "[file.filename].[file.filetype]"
else
if(!computer || !HDD)
data["error"] = "I/O ERROR: Unable to access hard drive."
else
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
return data
@@ -0,0 +1,171 @@
/datum/computer_file/program/ntnetdownload
filename = "ntndownloader"
filedesc = "Software Download Tool"
program_icon_state = "generic"
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
ui_header = "downloader_finished.gif"
tgui_id = "ntos_net_downloader"
var/datum/computer_file/program/downloaded_file = null
var/hacked_download = 0
var/download_completion = 0 //GQ of downloaded data.
var/download_netspeed = 0
var/downloaderror = ""
var/obj/item/modular_computer/my_computer = null
/datum/computer_file/program/ntnetdownload/proc/begin_file_download(filename)
if(downloaded_file)
return 0
var/datum/computer_file/program/PRG = SSnetworks.station_network.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.obj_flags & EMAGGED))
return 0
var/obj/item/computer_hardware/hard_drive/hard_drive = computer.all_components[MC_HDD]
if(!computer || !hard_drive || !hard_drive.can_store_file(PRG))
return 0
ui_header = "downloader_running.gif"
if(PRG in SSnetworks.station_network.available_station_software)
generate_network_log("Began downloading file [PRG.filename].[PRG.filetype] from NTNet Software Repository.")
hacked_download = 0
else if(PRG in SSnetworks.station_network.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/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]"].")
var/obj/item/computer_hardware/hard_drive/hard_drive = computer.all_components[MC_HDD]
if(!computer || !hard_drive || !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()
// 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/ui_act(action, params)
if(..())
return 1
switch(action)
if("PRG_downloadfile")
if(!downloaded_file)
begin_file_download(params["filename"])
return 1
if("PRG_reseterror")
if(downloaderror)
download_completion = 0
download_netspeed = 0
downloaded_file = null
downloaderror = ""
return 1
return 0
/datum/computer_file/program/ntnetdownload/ui_data(mob/user)
my_computer = computer
if(!istype(my_computer))
return
var/list/data = get_header_data()
// This IF cuts on data transferred to client, so i guess it's worth it.
if(downloaderror) // Download errored. Wait until user resets the program.
data["error"] = downloaderror
else if(downloaded_file) // Download running. Wait please..
data["downloadname"] = downloaded_file.filename
data["downloaddesc"] = downloaded_file.filedesc
data["downloadsize"] = downloaded_file.size
data["downloadspeed"] = download_netspeed
data["downloadcompletion"] = round(download_completion, 0.1)
else // No download running, pick file.
var/obj/item/computer_hardware/hard_drive/hard_drive = my_computer.all_components[MC_HDD]
data["disk_size"] = hard_drive.max_capacity
data["disk_used"] = hard_drive.used_capacity
var/list/all_entries[0]
for(var/A in SSnetworks.station_network.available_station_software)
var/datum/computer_file/program/P = A
// Only those programs our user can run will show in the list
if(!P.can_run(user,transfer = 1) || hard_drive.find_file_by_name(P.filename))
continue
all_entries.Add(list(list(
"filename" = P.filename,
"filedesc" = P.filedesc,
"fileinfo" = P.extended_desc,
"compatibility" = check_compatibility(P),
"size" = P.size
)))
data["hackedavailable"] = 0
if(computer.obj_flags & EMAGGED) // If we are running on emagged computer we have access to some "bonus" software
var/list/hacked_programs[0]
for(var/S in SSnetworks.station_network.available_antag_software)
var/datum/computer_file/program/P = S
if(hard_drive.find_file_by_name(P.filename))
continue
data["hackedavailable"] = 1
hacked_programs.Add(list(list(
"filename" = P.filename,
"filedesc" = P.filedesc,
"fileinfo" = P.extended_desc,
"size" = P.size
)))
data["hacked_programs"] = hacked_programs
data["downloadable_programs"] = all_entries
return data
/datum/computer_file/program/ntnetdownload/proc/check_compatibility(datum/computer_file/program/P)
var/hardflag = computer.hardware_flag
if(P && P.is_supported_by_hardware(hardflag,0))
return "Compatible"
return "Incompatible!"
/datum/computer_file/program/ntnetdownload/kill_program(forced)
abort_file_download()
return ..(forced)
@@ -0,0 +1,81 @@
/datum/computer_file/program/ntnetmonitor
filename = "ntmonitor"
filedesc = "NTNet Diagnostics and Monitoring"
program_icon_state = "comm_monitor"
extended_desc = "This program monitors stationwide NTNet network, provides access to logging systems, and allows for configuration changes"
size = 12
requires_ntnet = 1
required_access = ACCESS_NETWORK //NETWORK CONTROL IS A MORE SECURE PROGRAM.
available_on_ntnet = 1
tgui_id = "ntos_net_monitor"
/datum/computer_file/program/ntnetmonitor/ui_act(action, params)
if(..())
return 1
switch(action)
if("resetIDS")
. = 1
if(SSnetworks.station_network)
SSnetworks.station_network.resetIDS()
return 1
if("toggleIDS")
. = 1
if(SSnetworks.station_network)
SSnetworks.station_network.toggleIDS()
return 1
if("toggleWireless")
. = 1
if(!SSnetworks.station_network)
return 1
// NTNet is disabled. Enabling can be done without user prompt
if(SSnetworks.station_network.setting_disabled)
SSnetworks.station_network.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)
var/mob/user = usr
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")
SSnetworks.station_network.setting_disabled = 1
return 1
if("purgelogs")
. = 1
if(SSnetworks.station_network)
SSnetworks.station_network.purge_logs()
if("updatemaxlogs")
. = 1
var/mob/user = usr
var/logcount = text2num(input(user,"Enter amount of logs to keep in memory ([MIN_NTNET_LOGS]-[MAX_NTNET_LOGS]):"))
if(SSnetworks.station_network)
SSnetworks.station_network.update_max_log_count(logcount)
if("toggle_function")
. = 1
if(!SSnetworks.station_network)
return 1
SSnetworks.station_network.toggle_function(text2num(params["id"]))
/datum/computer_file/program/ntnetmonitor/ui_data(mob/user)
if(!SSnetworks.station_network)
return
var/list/data = get_header_data()
data["ntnetstatus"] = SSnetworks.station_network.check_function()
data["ntnetrelays"] = SSnetworks.station_network.relays.len
data["idsstatus"] = SSnetworks.station_network.intrusion_detection_enabled
data["idsalarm"] = SSnetworks.station_network.intrusion_detection_alarm
data["config_softwaredownload"] = SSnetworks.station_network.setting_softwaredownload
data["config_peertopeer"] = SSnetworks.station_network.setting_peertopeer
data["config_communication"] = SSnetworks.station_network.setting_communication
data["config_systemcontrol"] = SSnetworks.station_network.setting_systemcontrol
data["ntnetlogs"] = list()
for(var/i in SSnetworks.station_network.logs)
data["ntnetlogs"] += list(list("entry" = i))
data["ntnetmaxlogs"] = SSnetworks.station_network.setting_maxlogcount
return data
@@ -0,0 +1,224 @@
/datum/computer_file/program/chatclient
filename = "ntnrc_client"
filedesc = "Chat Client"
program_icon_state = "command"
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
tgui_id = "ntos_net_chat"
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/ui_act(action, params)
if(..())
return 1
switch(action)
if("PRG_speak")
. = 1
if(!channel)
return 1
var/mob/living/user = usr
var/message = reject_bad_text(input(user, "Enter message or leave blank to cancel: "))
if(!message || !channel)
return
channel.add_message(message, username)
user.log_talk(message, LOG_CHAT, tag="as [username] to channel [channel.title]")
if("PRG_joinchannel")
. = 1
var/datum/ntnet_conversation/C
for(var/datum/ntnet_conversation/chan in SSnetworks.station_network.chat_channels)
if(chan.id == text2num(params["id"]))
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 = reject_bad_text(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("PRG_leavechannel")
. = 1
if(channel)
channel.remove_client(src)
channel = null
if("PRG_newchannel")
. = 1
var/mob/living/user = usr
var/channel_title = reject_bad_text(input(user,"Enter channel name or leave blank to cancel:"))
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("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("PRG_changename")
. = 1
var/mob/living/user = usr
var/newname = sanitize(input(user,"Enter new nickname or leave blank to cancel:"))
if(!newname)
return 1
if(channel)
channel.add_status_message("[username] is now known as [newname].")
username = newname
if("PRG_savelog")
. = 1
if(!channel)
return
var/mob/living/user = usr
var/logname = stripped_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()
var/obj/item/computer_hardware/hard_drive/hard_drive = computer.all_components[MC_HDD]
if(!computer || !hard_drive || !hard_drive.store_file(logfile))
if(!computer)
// This program shouldn't even be runnable without computer.
CRASH("Var computer is null!")
return 1
if(!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("PRG_renamechannel")
. = 1
if(!operator_mode || !channel)
return 1
var/mob/living/user = usr
var/newname = reject_bad_text(input(user, "Enter new channel name or leave blank to cancel:"))
if(!newname || !channel)
return
channel.add_status_message("Channel renamed from [channel.title] to [newname] by operator.")
channel.title = newname
if("PRG_deletechannel")
. = 1
if(channel && ((channel.operator == src) || netadmin_mode))
qdel(channel)
channel = null
if("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(forced = FALSE)
if(channel)
channel.remove_client(src)
channel = null
..()
/datum/computer_file/program/chatclient/ui_data(mob/user)
if(!SSnetworks.station_network || !SSnetworks.station_network.chat_channels)
return
var/list/data = list()
data = get_header_data()
data["adminmode"] = netadmin_mode
if(channel)
data["title"] = channel.title
var/list/messages[0]
for(var/M in channel.messages)
messages.Add(list(list(
"msg" = M
)))
data["messages"] = messages
var/list/clients[0]
for(var/C in channel.clients)
var/datum/computer_file/program/chatclient/cl = C
clients.Add(list(list(
"name" = cl.username
)))
data["clients"] = clients
operator_mode = (channel.operator == src) ? 1 : 0
data["is_operator"] = operator_mode || netadmin_mode
else // Channel selection screen
var/list/all_channels[0]
for(var/C in SSnetworks.station_network.chat_channels)
var/datum/ntnet_conversation/conv = C
if(conv && conv.title)
all_channels.Add(list(list(
"chan" = conv.title,
"id" = conv.id
)))
data["all_channels"] = all_channels
return data
@@ -0,0 +1,183 @@
/datum/computer_file/program/nttransfer
filename = "nttransfer"
filedesc = "P2P Transfer Client"
extended_desc = "This program allows for simple file transfer via direct peer to peer connection."
program_icon_state = "comm_logs"
size = 7
requires_ntnet = 1
requires_ntnet_feature = NTNET_PEERTOPEER
network_destination = "other device via P2P tunnel"
available_on_ntnet = 1
tgui_id = "ntos_net_transfer"
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/download_netspeed = 0 // Our connectivity speed in GQ/s
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
var/static/nttransfer_uid = 0
/datum/computer_file/program/nttransfer/New()
unique_token = nttransfer_uid++
..()
/datum/computer_file/program/nttransfer/process_tick()
// Server mode
update_netspeed()
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.download_netspeed, download_netspeed)
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(forced = FALSE)
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)
/datum/computer_file/program/nttransfer/proc/update_netspeed()
download_netspeed = 0
switch(ntnet_status)
if(1)
download_netspeed = NTNETSPEED_LOWSIGNAL
if(2)
download_netspeed = NTNETSPEED_HIGHSIGNAL
if(3)
download_netspeed = NTNETSPEED_ETHERNET
// Finishes download and attempts to store the file on HDD
/datum/computer_file/program/nttransfer/proc/finish_download()
var/obj/item/computer_hardware/hard_drive/hard_drive = computer.all_components[MC_HDD]
if(!computer || !hard_drive || !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 occurred 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/computer_file/program/nttransfer/ui_act(action, params)
if(..())
return 1
switch(action)
if("PRG_downloadfile")
for(var/datum/computer_file/program/nttransfer/P in SSnetworks.station_network.fileservers)
if("[P.unique_token]" == params["id"])
remote = P
break
if(!remote || !remote.provided_file)
return
if(remote.server_password)
var/pass = reject_bad_text(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("PRG_reset")
error = ""
upload_menu = 0
finalize_download()
if(src in SSnetworks.station_network.fileservers)
SSnetworks.station_network.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("PRG_setpassword")
var/pass = reject_bad_text(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("PRG_uploadfile")
var/obj/item/computer_hardware/hard_drive/hard_drive = computer.all_components[MC_HDD]
for(var/datum/computer_file/F in hard_drive.stored_files)
if("[F.uid]" == params["id"])
if(F.unsendable)
error = "I/O Error: File locked."
return
if(istype(F, /datum/computer_file/program))
var/datum/computer_file/program/P = F
if(!P.can_run(usr,transfer = 1))
error = "Access Error: Insufficient rights to upload file."
provided_file = F
SSnetworks.station_network.fileservers.Add(src)
return
error = "I/O Error: Unable to locate file on hard drive."
return 1
if("PRG_uploadmenu")
upload_menu = 1
/datum/computer_file/program/nttransfer/ui_data(mob/user)
var/list/data = get_header_data()
if(error)
data["error"] = error
else if(downloaded_file)
data["downloading"] = 1
data["download_size"] = downloaded_file.size
data["download_progress"] = download_completion
data["download_netspeed"] = actual_netspeed
data["download_name"] = "[downloaded_file.filename].[downloaded_file.filetype]"
else if (provided_file)
data["uploading"] = 1
data["upload_uid"] = unique_token
data["upload_clients"] = connected_clients.len
data["upload_haspassword"] = server_password ? 1 : 0
data["upload_filename"] = "[provided_file.filename].[provided_file.filetype]"
else if (upload_menu)
var/list/all_files[0]
var/obj/item/computer_hardware/hard_drive/hard_drive = computer.all_components[MC_HDD]
for(var/datum/computer_file/F in 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 SSnetworks.station_network.fileservers)
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
return data
@@ -0,0 +1,104 @@
//normal computer version is located in code\modules\power\monitor.dm, /obj/machinery/computer/monitor
/datum/computer_file/program/power_monitor
filename = "powermonitor"
filedesc = "Power Monitor"
program_icon_state = "power_monitor"
extended_desc = "This program connects to sensors around the station to provide information about electrical systems"
ui_header = "power_norm.gif"
transfer_access = ACCESS_ENGINE
usage_flags = PROGRAM_CONSOLE
requires_ntnet = 0
network_destination = "power monitoring system"
size = 9
tgui_id = "ntos_power_monitor"
ui_x = 1200
ui_y = 1000
var/has_alert = 0
var/obj/structure/cable/attached_wire
var/obj/machinery/power/apc/local_apc
var/list/history = list()
var/record_size = 60
var/record_interval = 50
var/next_record = 0
/datum/computer_file/program/power_monitor/run_program(mob/living/user)
. = ..(user)
search()
history["supply"] = list()
history["demand"] = list()
/datum/computer_file/program/power_monitor/process_tick()
if(!get_powernet())
search()
else
record()
/datum/computer_file/program/power_monitor/proc/search() //keep in sync with /obj/machinery/computer/monitor's version
var/turf/T = get_turf(computer)
attached_wire = locate(/obj/structure/cable) in T
if(attached_wire)
return
var/area/A = get_area(computer) //if the computer isn't directly connected to a wire, attempt to find the APC powering it to pull it's powernet instead
if(!A)
return
local_apc = A.get_apc()
if(!local_apc)
return
if(!local_apc.terminal) //this really shouldn't happen without badminnery.
local_apc = null
/datum/computer_file/program/power_monitor/proc/get_powernet() //keep in sync with /obj/machinery/computer/monitor's version
if(attached_wire || (local_apc && local_apc.terminal))
return attached_wire ? attached_wire.powernet : local_apc.terminal.powernet
return FALSE
/datum/computer_file/program/power_monitor/proc/record() //keep in sync with /obj/machinery/computer/monitor's version
if(world.time >= next_record)
next_record = world.time + record_interval
var/datum/powernet/connected_powernet = get_powernet()
var/list/supply = history["supply"]
if(connected_powernet)
supply += connected_powernet.viewavail
if(supply.len > record_size)
supply.Cut(1, 2)
var/list/demand = history["demand"]
if(connected_powernet)
demand += connected_powernet.viewload
if(demand.len > record_size)
demand.Cut(1, 2)
/datum/computer_file/program/power_monitor/ui_data()
var/datum/powernet/connected_powernet = get_powernet()
var/list/data = get_header_data()
data["stored"] = record_size
data["interval"] = record_interval / 10
data["attached"] = connected_powernet ? TRUE : FALSE
if(connected_powernet)
data["supply"] = DisplayPower(connected_powernet.viewavail)
data["demand"] = DisplayPower(connected_powernet.viewload)
data["history"] = history
data["areas"] = list()
if(connected_powernet)
for(var/obj/machinery/power/terminal/term in connected_powernet.nodes)
var/obj/machinery/power/apc/A = term.master
if(istype(A))
data["areas"] += list(list(
"name" = A.area.name,
"charge" = A.cell ? A.cell.percent() : 0,
"load" = DisplayPower(A.lastused_total),
"charging" = A.charging,
"eqp" = A.equipment,
"lgt" = A.lighting,
"env" = A.environ
))
return data
@@ -0,0 +1,126 @@
/datum/computer_file/program/supermatter_monitor
filename = "smmonitor"
filedesc = "Supermatter Monitoring"
ui_header = "smmon_0.gif"
program_icon_state = "smmon_0"
extended_desc = "This program connects to specially calibrated supermatter sensors to provide information on the status of supermatter-based engines."
requires_ntnet = TRUE
transfer_access = ACCESS_CONSTRUCTION
network_destination = "supermatter monitoring system"
size = 5
tgui_id = "ntos_supermatter_monitor"
ui_x = 600
ui_y = 400
var/last_status = SUPERMATTER_INACTIVE
var/list/supermatters
var/obj/machinery/power/supermatter_crystal/active // Currently selected supermatter crystal.
/datum/computer_file/program/supermatter_monitor/process_tick()
..()
var/new_status = get_status()
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/computer_file/program/supermatter_monitor/run_program(mob/living/user)
. = ..(user)
refresh()
/datum/computer_file/program/supermatter_monitor/kill_program(forced = FALSE)
active = null
supermatters = null
..()
// Refreshes list of active supermatter crystals
/datum/computer_file/program/supermatter_monitor/proc/refresh()
supermatters = list()
var/turf/T = get_turf(ui_host())
if(!T)
return
for(var/obj/machinery/power/supermatter_crystal/S in GLOB.machines)
// Delaminating, not within coverage, not on a tile.
if (!isturf(S.loc) || !(is_station_level(S.z) || is_mining_level(S.z) || S.z == T.z))
continue
supermatters.Add(S)
if(!(active in supermatters))
active = null
/datum/computer_file/program/supermatter_monitor/proc/get_status()
. = SUPERMATTER_INACTIVE
for(var/obj/machinery/power/supermatter_crystal/S in supermatters)
. = max(., S.get_status())
/datum/computer_file/program/supermatter_monitor/ui_data()
var/list/data = get_header_data()
if(istype(active))
var/turf/T = get_turf(active)
if(!T)
active = null
refresh()
return
var/datum/gas_mixture/air = T.return_air()
if(!air)
active = null
return
data["active"] = TRUE
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"] = round((air.total_moles / air.group_multiplier) / 23.1, 0.01)
var/list/gasdata = list()
if(air.total_moles())
for(var/gasid in air.gases)
gasdata.Add(list(list(
"name"= GLOB.meta_gas_names[gasid],
"amount" = round(100*air.gases[gasid]/air.total_moles(),0.01))))
else
for(var/gasid in air.gases)
gasdata.Add(list(list(
"name"= GLOB.meta_gas_names[gasid],
"amount" = 0)))
data["gases"] = gasdata
else
var/list/SMS = list()
for(var/obj/machinery/power/supermatter_crystal/S in supermatters)
var/area/A = get_area(S)
if(A)
SMS.Add(list(list(
"area_name" = A.name,
"integrity" = S.get_integrity(),
"uid" = S.uid
)))
data["active"] = FALSE
data["supermatters"] = SMS
return data
/datum/computer_file/program/supermatter_monitor/ui_act(action, params)
if(..())
return TRUE
switch(action)
if("PRG_clear")
active = null
return TRUE
if("PRG_refresh")
refresh()
return TRUE
if("PRG_set")
var/newuid = text2num(params["target"])
for(var/obj/machinery/power/supermatter_crystal/S in supermatters)
if(S.uid == newuid)
active = S
return TRUE
@@ -0,0 +1,40 @@
// CPU that allows the computer to run programs.
// Better CPUs are obtainable via research and can run more programs on background.
/obj/item/computer_hardware/processor_unit
name = "processor board"
desc = "A standard CPU board used in most computers. It can run up to three programs simultaneously."
icon_state = "cpuboard"
w_class = WEIGHT_CLASS_SMALL
power_usage = 50
critical = 1
malfunction_probability = 1
var/max_idle_programs = 2 // 2 idle, + 1 active = 3 as said in description.
device_type = MC_CPU
/obj/item/computer_hardware/processor_unit/on_remove(obj/item/modular_computer/MC, mob/user)
MC.shutdown_computer()
/obj/item/computer_hardware/processor_unit/small
name = "microprocessor"
desc = "A miniaturised CPU used in portable devices. It can run up to two programs simultaneously."
icon_state = "cpu"
w_class = WEIGHT_CLASS_TINY
power_usage = 25
max_idle_programs = 1
/obj/item/computer_hardware/processor_unit/photonic
name = "photonic processor board"
desc = "An advanced experimental CPU board that uses photonic core instead of regular circuitry. It can run up to five programs simultaneously, but uses a lot of power."
icon_state = "cpuboard_super"
w_class = WEIGHT_CLASS_SMALL
power_usage = 250
max_idle_programs = 4
/obj/item/computer_hardware/processor_unit/photonic/small
name = "photonic microprocessor"
desc = "An advanced miniaturised CPU for use in portable devices. It uses photonic core instead of regular circuitry. It can run up to three programs simultaneously."
icon_state = "cpu_super"
w_class = WEIGHT_CLASS_TINY
power_usage = 75
max_idle_programs = 2
@@ -0,0 +1,103 @@
/obj/item/computer_hardware
name = "hardware"
desc = "Unknown Hardware."
icon = 'icons/obj/module.dmi'
icon_state = "std_mod"
w_class = WEIGHT_CLASS_TINY // w_class limits which devices can contain this component.
// 1: PDAs/Tablets, 2: Laptops, 3-4: Consoles only
var/obj/item/modular_computer/holder = null
// Computer that holds this hardware, if any.
var/power_usage = 0 // If the hardware uses extra power, change this.
var/enabled = 1 // If the hardware is turned off set this to 0.
var/critical = 0 // Prevent disabling for important component, like the CPU.
var/can_install = 1 // Prevents direct installation of removable media.
var/damage = 0 // Current damage level
var/max_damage = 100 // Maximal damage level.
var/damage_malfunction = 20 // "Malfunction" threshold. When damage exceeds this value the hardware piece will semi-randomly fail and do !!FUN!! things
var/damage_failure = 50 // "Failure" threshold. When damage exceeds this value the hardware piece will not work at all.
var/malfunction_probability = 10// Chance of malfunction when the component is damaged
var/device_type
/obj/item/computer_hardware/New(var/obj/L)
..()
pixel_x = rand(-8, 8)
pixel_y = rand(-8, 8)
/obj/item/computer_hardware/Destroy()
if(holder)
holder.uninstall_component(src)
return ..()
/obj/item/computer_hardware/attackby(obj/item/I, mob/living/user)
// Multitool. Runs diagnostics
if(istype(I, /obj/item/multitool))
to_chat(user, "***** DIAGNOSTICS REPORT *****")
diagnostics(user)
to_chat(user, "******************************")
return 1
// Cable coil. Works as repair method, but will probably require multiple applications and more cable.
if(istype(I, /obj/item/stack/cable_coil))
var/obj/item/stack/S = I
if(obj_integrity == max_integrity)
to_chat(user, "<span class='warning'>\The [src] doesn't seem to require repairs.</span>")
return 1
if(S.use(1))
to_chat(user, "<span class='notice'>You patch up \the [src] with a bit of \the [I].</span>")
obj_integrity = min(obj_integrity + 10, max_integrity)
return 1
if(try_insert(I, user))
return 1
return ..()
// Called on multitool click, prints diagnostic information to the user.
/obj/item/computer_hardware/proc/diagnostics(var/mob/user)
to_chat(user, "Hardware Integrity Test... (Corruption: [damage]/[max_damage]) [damage > damage_failure ? "FAIL" : damage > damage_malfunction ? "WARN" : "PASS"]")
// Handles damage checks
/obj/item/computer_hardware/proc/check_functionality()
if(!enabled) // Disabled.
return FALSE
if(damage > damage_failure) // Too damaged to work at all.
return FALSE
if(damage > damage_malfunction) // Still working. Well, sometimes...
if(prob(malfunction_probability))
return FALSE
return TRUE // Good to go.
/obj/item/computer_hardware/examine(var/mob/user)
. = ..()
if(damage > damage_failure)
to_chat(user, "<span class='danger'>It seems to be severely damaged!</span>")
else if(damage > damage_malfunction)
to_chat(user, "<span class='warning'>It seems to be damaged!</span>")
else if(damage)
to_chat(user, "<span class='notice'>It seems to be slightly damaged.</span>")
// Component-side compatibility check.
/obj/item/computer_hardware/proc/can_install(obj/item/modular_computer/M, mob/living/user = null)
return can_install
// Called when component is installed into PC.
/obj/item/computer_hardware/proc/on_install(obj/item/modular_computer/M, mob/living/user = null)
return
// Called when component is removed from PC.
/obj/item/computer_hardware/proc/on_remove(obj/item/modular_computer/M, mob/living/user = null)
try_eject(forced = 1)
// Called when someone tries to insert something in it - paper in printer, card in card reader, etc.
/obj/item/computer_hardware/proc/try_insert(obj/item/I, mob/living/user = null)
return FALSE
// Called when someone tries to eject something from it - card from card reader, etc.
/obj/item/computer_hardware/proc/try_eject(slot=0, mob/living/user = null, forced = 0)
return FALSE
@@ -0,0 +1,68 @@
/obj/item/computer_hardware/ai_slot
name = "intelliCard interface slot"
desc = "A module allowing this computer to interface with most common intelliCard modules. Necessary for some programs to run properly."
power_usage = 100 //W
icon_state = "card_mini"
w_class = WEIGHT_CLASS_SMALL
device_type = MC_AI
var/obj/item/aicard/stored_card = null
var/locked = FALSE
/obj/item/computer_hardware/ai_slot/examine(mob/user)
..()
if(stored_card)
to_chat(user, "There appears to be an intelliCard loaded. There appears to be a pinhole protecting a manual eject button. A screwdriver could probably press it.")
/obj/item/computer_hardware/ai_slot/on_install(obj/item/modular_computer/M, mob/living/user = null)
M.add_verb(device_type)
/obj/item/computer_hardware/ai_slot/on_remove(obj/item/modular_computer/M, mob/living/user = null)
M.remove_verb(device_type)
/obj/item/computer_hardware/ai_slot/try_insert(obj/item/I, mob/living/user = null)
if(!holder)
return FALSE
if(!istype(I, /obj/item/aicard))
return FALSE
if(stored_card)
to_chat(user, "<span class='warning'>You try to insert \the [I] into \the [src], but the slot is occupied.</span>")
return FALSE
if(user && !user.transferItemToLoc(I, src))
return FALSE
stored_card = I
to_chat(user, "<span class='notice'>You insert \the [I] into \the [src].</span>")
return TRUE
/obj/item/computer_hardware/ai_slot/try_eject(slot=0,mob/living/user = null,forced = 0)
if(!stored_card)
to_chat(user, "<span class='warning'>There is no card in \the [src].</span>")
return FALSE
if(locked && !forced)
to_chat(user, "<span class='warning'>Safeties prevent you from removing the card until reconstruction is complete...</span>")
return FALSE
if(stored_card)
stored_card.forceMove(get_turf(src))
locked = FALSE
stored_card.verb_pickup()
stored_card = null
to_chat(user, "<span class='notice'>You remove the card from \the [src].</span>")
return TRUE
return FALSE
/obj/item/computer_hardware/ai_slot/attackby(obj/item/I, mob/living/user)
if(..())
return
if(istype(I, /obj/item/screwdriver))
to_chat(user, "<span class='notice'>You press down on the manual eject button with \the [I].</span>")
try_eject(,user,1)
return
@@ -0,0 +1,94 @@
/obj/item/computer_hardware/battery
name = "power cell controller"
desc = "A charge controller for standard power cells, used in all kinds of modular computers."
icon_state = "cell_con"
critical = 1
malfunction_probability = 1
var/obj/item/stock_parts/cell/battery = null
device_type = MC_CELL
/obj/item/computer_hardware/battery/New(loc, battery_type = null)
if(battery_type)
battery = new battery_type(src)
..()
/obj/item/computer_hardware/battery/try_insert(obj/item/I, mob/living/user = null)
if(!holder)
return FALSE
if(!istype(I, /obj/item/stock_parts/cell))
return FALSE
if(battery)
to_chat(user, "<span class='warning'>You try to connect \the [I] to \the [src], but its connectors are occupied.</span>")
return FALSE
if(I.w_class > holder.max_hardware_size)
to_chat(user, "<span class='warning'>This power cell is too large for \the [holder]!</span>")
return FALSE
if(user && !user.transferItemToLoc(I, src))
return FALSE
battery = I
to_chat(user, "<span class='notice'>You connect \the [I] to \the [src].</span>")
return TRUE
/obj/item/computer_hardware/battery/try_eject(slot=0, mob/living/user = null, forced = 0)
if(!battery)
to_chat(user, "<span class='warning'>There is no power cell connected to \the [src].</span>")
return FALSE
else
battery.forceMove(get_turf(src))
to_chat(user, "<span class='notice'>You detach \the [battery] from \the [src].</span>")
battery = null
if(holder)
if(holder.enabled && !holder.use_power())
holder.shutdown_computer()
return TRUE
return FALSE
/obj/item/stock_parts/cell/computer
name = "standard battery"
desc = "A standard power cell, commonly seen in high-end portable microcomputers or low-end laptops."
icon = 'icons/obj/module.dmi'
icon_state = "cell_mini"
w_class = WEIGHT_CLASS_TINY
maxcharge = 750
/obj/item/stock_parts/cell/computer/advanced
name = "advanced battery"
desc = "An advanced power cell, often used in most laptops. It is too large to be fitted into smaller devices."
icon_state = "cell"
w_class = WEIGHT_CLASS_SMALL
maxcharge = 1500
/obj/item/stock_parts/cell/computer/super
name = "super battery"
desc = "An advanced power cell, often used in high-end laptops."
icon_state = "cell"
w_class = WEIGHT_CLASS_SMALL
maxcharge = 2000
/obj/item/stock_parts/cell/computer/micro
name = "micro battery"
desc = "A small power cell, commonly seen in most portable microcomputers."
icon_state = "cell_micro"
maxcharge = 500
/obj/item/stock_parts/cell/computer/nano
name = "nano battery"
desc = "A tiny power cell, commonly seen in low-end portable microcomputers."
icon_state = "cell_micro"
maxcharge = 300
@@ -0,0 +1,111 @@
/obj/item/computer_hardware/card_slot
name = "identification card authentication module" // \improper breaks the find_hardware_by_name proc
desc = "A module allowing this computer to read or write data on ID cards. Necessary for some programs to run properly."
power_usage = 10 //W
icon_state = "card_mini"
w_class = WEIGHT_CLASS_TINY
device_type = MC_CARD
var/obj/item/card/id/stored_card = null
var/obj/item/card/id/stored_card2 = null
/obj/item/computer_hardware/card_slot/Destroy()
try_eject()
return ..()
/obj/item/computer_hardware/card_slot/GetAccess()
if(stored_card && stored_card2) // Best of both worlds
return (stored_card.GetAccess() | stored_card2.GetAccess())
else if(stored_card)
return stored_card.GetAccess()
else if(stored_card2)
return stored_card2.GetAccess()
return ..()
/obj/item/computer_hardware/card_slot/GetID()
if(stored_card)
return stored_card
else if(stored_card2)
return stored_card2
return ..()
/obj/item/computer_hardware/card_slot/on_install(obj/item/modular_computer/M, mob/living/user = null)
M.add_verb(device_type)
/obj/item/computer_hardware/card_slot/on_remove(obj/item/modular_computer/M, mob/living/user = null)
M.remove_verb(device_type)
/obj/item/computer_hardware/card_slot/try_insert(obj/item/I, mob/living/user = null)
if(!holder)
return FALSE
if(!istype(I, /obj/item/card/id))
return FALSE
if(stored_card && stored_card2)
to_chat(user, "<span class='warning'>You try to insert \the [I] into \the [src], but its slots are occupied.</span>")
return FALSE
if(user)
if(!user.transferItemToLoc(I, src))
return FALSE
else
I.forceMove(src)
if(!stored_card)
stored_card = I
else
stored_card2 = I
to_chat(user, "<span class='notice'>You insert \the [I] into \the [src].</span>")
playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, 0)
return TRUE
/obj/item/computer_hardware/card_slot/try_eject(slot=0, mob/living/user = null, forced = 0)
if(!stored_card && !stored_card2)
to_chat(user, "<span class='warning'>There are no cards in \the [src].</span>")
return FALSE
var/ejected = 0
if(stored_card && (!slot || slot == 1))
if(user)
user.put_in_hands(stored_card)
else
stored_card.forceMove(get_turf(src))
stored_card = null
ejected++
if(stored_card2 && (!slot || slot == 2))
if(user)
user.put_in_hands(stored_card2)
else
stored_card2.forceMove(get_turf(src))
stored_card2 = null
ejected++
if(ejected)
if(holder)
if(holder.active_program)
holder.active_program.event_idremoved(0, slot)
for(var/I in holder.idle_threads)
var/datum/computer_file/program/P = I
P.event_idremoved(1, slot)
to_chat(user, "<span class='notice'>You remove the card[ejected>1 ? "s" : ""] from \the [src].</span>")
playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, 0)
return TRUE
return FALSE
/obj/item/computer_hardware/card_slot/attackby(obj/item/I, mob/living/user)
if(..())
return
if(istype(I, /obj/item/screwdriver))
to_chat(user, "<span class='notice'>You press down on the manual eject button with \the [I].</span>")
try_eject(0,user)
return
/obj/item/computer_hardware/card_slot/examine(mob/user)
..()
if(stored_card || stored_card2)
to_chat(user, "There appears to be something loaded in the card slots.")
@@ -0,0 +1,167 @@
/obj/item/computer_hardware/hard_drive
name = "hard disk drive"
desc = "A small HDD, for use in basic computers where power efficiency is desired."
power_usage = 25
icon_state = "harddisk_mini"
critical = 1
w_class = WEIGHT_CLASS_TINY
device_type = MC_HDD
var/max_capacity = 128
var/used_capacity = 0
var/list/stored_files = list() // List of stored files on this drive. DO NOT MODIFY DIRECTLY!
/obj/item/computer_hardware/hard_drive/on_remove(obj/item/modular_computer/MC, mob/user)
MC.shutdown_computer()
/obj/item/computer_hardware/hard_drive/proc/install_default_programs()
store_file(new/datum/computer_file/program/computerconfig(src)) // Computer configuration utility, allows hardware control and displays more info than status bar
store_file(new/datum/computer_file/program/ntnetdownload(src)) // NTNet Downloader Utility, allows users to download more software from NTNet repository
store_file(new/datum/computer_file/program/filemanager(src)) // File manager, allows text editor functions and basic file manipulation.
/obj/item/computer_hardware/hard_drive/examine(user)
..()
to_chat(user, "<span class='notice'>It has [max_capacity] GQ of storage capacity.</span>")
/obj/item/computer_hardware/hard_drive/diagnostics(var/mob/user)
..()
// 999 is a byond limit that is in place. It's unlikely someone will reach that many files anyway, since you would sooner run out of space.
to_chat(user, "NT-NFS File Table Status: [stored_files.len]/999")
to_chat(user, "Storage capacity: [used_capacity]/[max_capacity]GQ")
// Use this proc to add file to the drive. Returns 1 on success and 0 on failure. Contains necessary sanity checks.
/obj/item/computer_hardware/hard_drive/proc/store_file(var/datum/computer_file/F)
if(!F || !istype(F))
return 0
if(!can_store_file(F))
return 0
if(!check_functionality())
return 0
if(!stored_files)
return 0
// This file is already stored. Don't store it again.
if(F in stored_files)
return 0
F.holder = src
stored_files.Add(F)
recalculate_size()
return 1
// Use this proc to remove file from the drive. Returns 1 on success and 0 on failure. Contains necessary sanity checks.
/obj/item/computer_hardware/hard_drive/proc/remove_file(var/datum/computer_file/F)
if(!F || !istype(F))
return 0
if(!stored_files)
return 0
if(!check_functionality())
return 0
if(F in stored_files)
stored_files -= F
recalculate_size()
return 1
else
return 0
// Loops through all stored files and recalculates used_capacity of this drive
/obj/item/computer_hardware/hard_drive/proc/recalculate_size()
var/total_size = 0
for(var/datum/computer_file/F in stored_files)
total_size += F.size
used_capacity = total_size
// Checks whether file can be stored on the hard drive. We can only store unique files, so this checks whether we wouldn't get a duplicity by adding a file.
/obj/item/computer_hardware/hard_drive/proc/can_store_file(var/datum/computer_file/F)
if(!F || !istype(F))
return 0
if(F in stored_files)
return 0
var/name = F.filename + "." + F.filetype
for(var/datum/computer_file/file in stored_files)
if((file.filename + "." + file.filetype) == name)
return 0
// In the unlikely event someone manages to create that many files.
// BYOND is acting weird with numbers above 999 in loops (infinite loop prevention)
if(stored_files.len >= 999)
return 0
if((used_capacity + F.size) > max_capacity)
return 0
else
return 1
// Tries to find the file by filename. Returns null on failure
/obj/item/computer_hardware/hard_drive/proc/find_file_by_name(var/filename)
if(!check_functionality())
return null
if(!filename)
return null
if(!stored_files)
return null
for(var/datum/computer_file/F in stored_files)
if(F.filename == filename)
return F
return null
/obj/item/computer_hardware/hard_drive/Destroy()
stored_files = null
return ..()
/obj/item/computer_hardware/hard_drive/Initialize()
. = ..()
install_default_programs()
/obj/item/computer_hardware/hard_drive/advanced
name = "advanced hard disk drive"
desc = "A hybrid HDD, for use in higher grade computers where balance between power efficiency and capacity is desired."
max_capacity = 256
power_usage = 50 // Hybrid, medium capacity and medium power storage
icon_state = "harddisk_mini"
w_class = WEIGHT_CLASS_SMALL
/obj/item/computer_hardware/hard_drive/super
name = "super hard disk drive"
desc = "A high capacity HDD, for use in cluster storage solutions where capacity is more important than power efficiency."
max_capacity = 512
power_usage = 100 // High-capacity but uses lots of power, shortening battery life. Best used with APC link.
icon_state = "harddisk_mini"
w_class = WEIGHT_CLASS_SMALL
/obj/item/computer_hardware/hard_drive/cluster
name = "cluster hard disk drive"
desc = "A large storage cluster consisting of multiple HDDs for usage in dedicated storage systems."
power_usage = 500
max_capacity = 2048
icon_state = "harddisk"
w_class = WEIGHT_CLASS_NORMAL
// For tablets, etc. - highly power efficient.
/obj/item/computer_hardware/hard_drive/small
name = "solid state drive"
desc = "An efficient SSD for portable devices."
power_usage = 10
max_capacity = 64
icon_state = "ssd_mini"
w_class = WEIGHT_CLASS_TINY
/obj/item/computer_hardware/hard_drive/micro
name = "micro solid state drive"
desc = "A highly efficient SSD chip for portable devices."
power_usage = 2
max_capacity = 32
icon_state = "ssd_micro"
w_class = WEIGHT_CLASS_TINY
@@ -0,0 +1,79 @@
/obj/item/computer_hardware/network_card
name = "network card"
desc = "A basic wireless network card for usage with standard NTNet frequencies."
power_usage = 50
icon_state = "radio_mini"
var/identification_id = null // Identification ID. Technically MAC address of this device. Can't be changed by user.
var/identification_string = "" // Identification string, technically nickname seen in the network. Can be set by user.
var/long_range = 0
var/ethernet = 0 // Hard-wired, therefore always on, ignores NTNet wireless checks.
malfunction_probability = 1
device_type = MC_NET
var/global/ntnet_card_uid = 1
/obj/item/computer_hardware/network_card/diagnostics(var/mob/user)
..()
to_chat(user, "NIX Unique ID: [identification_id]")
to_chat(user, "NIX User Tag: [identification_string]")
to_chat(user, "Supported protocols:")
to_chat(user, "511.m SFS (Subspace) - Standard Frequency Spread")
if(long_range)
to_chat(user, "511.n WFS/HB (Subspace) - Wide Frequency Spread/High Bandiwdth")
if(ethernet)
to_chat(user, "OpenEth (Physical Connection) - Physical network connection port")
/obj/item/computer_hardware/network_card/New(var/l)
..()
identification_id = ntnet_card_uid++
// Returns a string identifier of this network card
/obj/item/computer_hardware/network_card/proc/get_network_tag()
return "[identification_string] (NID [identification_id])"
// 0 - No signal, 1 - Low signal, 2 - High signal. 3 - Wired Connection
/obj/item/computer_hardware/network_card/proc/get_signal(var/specific_action = 0)
if(!holder) // Hardware is not installed in anything. No signal. How did this even get called?
return 0
if(!check_functionality())
return 0
if(ethernet) // Computer is connected via wired connection.
return 3
if(!SSnetworks.station_network || !SSnetworks.station_network.check_function(specific_action)) // NTNet is down and we are not connected via wired connection. No signal.
return 0
if(holder)
var/turf/T = get_turf(holder)
if((T && istype(T)) && (is_station_level(T.z) || is_mining_level(T.z)))
// Computer is on station. Low/High signal depending on what type of network card you have
if(long_range)
return 2
else
return 1
if(long_range) // Computer is not on station, but it has upgraded network card. Low signal.
return 1
return 0 // Computer is not on station and does not have upgraded network card. No signal.
/obj/item/computer_hardware/network_card/advanced
name = "advanced network card"
desc = "An advanced network card for usage with standard NTNet frequencies. Its transmitter is strong enough to connect even off-station."
long_range = 1
power_usage = 100 // Better range but higher power usage.
icon_state = "radio"
lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi'
righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi'
w_class = WEIGHT_CLASS_TINY
/obj/item/computer_hardware/network_card/wired
name = "wired network card"
desc = "An advanced network card for usage with standard NTNet frequencies. This one also supports wired connection."
ethernet = 1
power_usage = 100 // Better range but higher power usage.
icon_state = "net_wired"
w_class = WEIGHT_CLASS_NORMAL
@@ -0,0 +1,32 @@
/obj/item/computer_hardware/hard_drive/portable
name = "data disk"
desc = "Removable disk used to store data."
power_usage = 10
icon_state = "datadisk6"
w_class = WEIGHT_CLASS_TINY
critical = 0
max_capacity = 16
device_type = MC_SDD
/obj/item/computer_hardware/hard_drive/portable/on_install(obj/item/modular_computer/M, mob/living/user = null)
M.add_verb(device_type)
/obj/item/computer_hardware/hard_drive/portable/on_remove(obj/item/modular_computer/M, mob/living/user = null)
..()
M.remove_verb(device_type)
/obj/item/computer_hardware/hard_drive/portable/install_default_programs()
return // Empty by default
/obj/item/computer_hardware/hard_drive/portable/advanced
name = "advanced data disk"
power_usage = 20
icon_state = "datadisk5"
max_capacity = 64
/obj/item/computer_hardware/hard_drive/portable/super
name = "super data disk"
desc = "Removable disk used to store large amounts of data."
power_usage = 40
icon_state = "datadisk3"
max_capacity = 256
@@ -0,0 +1,62 @@
/obj/item/computer_hardware/printer
name = "printer"
desc = "Computer-integrated printer with paper recycling module."
power_usage = 100
icon_state = "printer"
w_class = WEIGHT_CLASS_NORMAL
device_type = MC_PRINT
var/stored_paper = 20
var/max_paper = 30
/obj/item/computer_hardware/printer/diagnostics(mob/living/user)
..()
to_chat(user, "Paper level: [stored_paper]/[max_paper].")
/obj/item/computer_hardware/printer/examine(mob/user)
..()
to_chat(user, "<span class='notice'>Paper level: [stored_paper]/[max_paper].</span>")
/obj/item/computer_hardware/printer/proc/print_text(var/text_to_print, var/paper_title = "")
if(!stored_paper)
return FALSE
if(!check_functionality())
return FALSE
var/obj/item/paper/P = new/obj/item/paper(holder.drop_location())
// Damaged printer causes the resulting paper to be somewhat harder to read.
if(damage > damage_malfunction)
P.info = stars(text_to_print, 100-malfunction_probability)
else
P.info = text_to_print
if(paper_title)
P.name = paper_title
P.update_icon()
P.reload_fields()
stored_paper--
P = null
return TRUE
/obj/item/computer_hardware/printer/try_insert(obj/item/I, mob/living/user = null)
if(istype(I, /obj/item/paper))
if(stored_paper >= max_paper)
to_chat(user, "<span class='warning'>You try to add \the [I] into [src], but its paper bin is full!</span>")
return FALSE
if(user && !user.temporarilyRemoveItemFromInventory(I))
return FALSE
to_chat(user, "<span class='notice'>You insert \the [I] into [src]'s paper recycler.</span>")
qdel(I)
stored_paper++
return TRUE
return FALSE
/obj/item/computer_hardware/printer/mini
name = "miniprinter"
desc = "A small printer with paper recycling module."
power_usage = 50
icon_state = "printer_mini"
w_class = WEIGHT_CLASS_TINY
stored_paper = 5
max_paper = 15
@@ -0,0 +1,91 @@
/obj/item/computer_hardware/recharger
critical = 1
enabled = 1
var/charge_rate = 100
device_type = MC_CHARGE
/obj/item/computer_hardware/recharger/proc/use_power(amount, charging=0)
if(charging)
return 1
return 0
/obj/item/computer_hardware/recharger/process()
..()
var/obj/item/computer_hardware/battery/battery_module = holder.all_components[MC_CELL]
if(!holder || !battery_module || !battery_module.battery)
return
var/obj/item/stock_parts/cell/cell = battery_module.battery
if(cell.charge >= cell.maxcharge)
return
if(use_power(charge_rate, charging=1))
holder.give_power(charge_rate * GLOB.CELLRATE)
/obj/item/computer_hardware/recharger/APC
name = "area power connector"
desc = "A device that wirelessly recharges connected device from nearby APC."
icon_state = "charger_APC"
w_class = WEIGHT_CLASS_SMALL // Can't be installed into tablets/PDAs
/obj/item/computer_hardware/recharger/APC/use_power(amount, charging=0)
if(ismachinery(holder.physical))
var/obj/machinery/M = holder.physical
if(M.powered())
M.use_power(amount)
return 1
else
var/area/A = get_area(src)
if(!istype(A))
return 0
if(A.powered(EQUIP))
A.use_power(amount, EQUIP)
return 1
return 0
/obj/item/computer_hardware/recharger/wired
name = "wired power connector"
desc = "A power connector that recharges connected device from nearby power wire. Incompatible with portable computers."
icon_state = "charger_wire"
w_class = WEIGHT_CLASS_NORMAL
/obj/item/computer_hardware/recharger/wired/can_install(obj/item/modular_computer/M, mob/living/user = null)
if(ismachinery(M.physical) && M.physical.anchored)
return ..()
to_chat(user, "<span class='warning'>\The [src] is incompatible with portable computers!</span>")
return 0
/obj/item/computer_hardware/recharger/wired/use_power(amount, charging=0)
if(ismachinery(holder.physical) && holder.physical.anchored)
var/obj/machinery/M = holder.physical
var/turf/T = M.loc
if(!T || !istype(T))
return 0
var/obj/structure/cable/C = T.get_cable_node()
if(!C || !C.powernet)
return 0
var/power_in_net = C.powernet.avail-C.powernet.load
if(power_in_net && power_in_net > amount)
C.powernet.load += amount
return 1
return 0
// This is not intended to be obtainable in-game. Intended for adminbus and debugging purposes.
/obj/item/computer_hardware/recharger/lambda
name = "lambda coil"
desc = "A very complex device that draws power from its own bluespace dimension."
icon_state = "charger_lambda"
w_class = WEIGHT_CLASS_TINY
charge_rate = 100000
/obj/item/computer_hardware/recharger/lambda/use_power(amount, charging=0)
return 1
@@ -0,0 +1,291 @@
// A vendor machine for modular computer portable devices - Laptops and Tablets
/obj/machinery/lapvend
name = "computer vendor"
desc = "A vending machine with microfabricator capable of dispensing various NT-branded computers."
icon = 'icons/obj/vending.dmi'
icon_state = "robotics"
layer = 2.9
density = TRUE
// The actual laptop/tablet
var/obj/item/modular_computer/laptop/fabricated_laptop = null
var/obj/item/modular_computer/tablet/fabricated_tablet = null
// Utility vars
var/state = 0 // 0: Select device type, 1: Select loadout, 2: Payment, 3: Thankyou screen
var/devtype = 0 // 0: None(unselected), 1: Laptop, 2: Tablet
var/total_price = 0 // Price of currently vended device.
var/credits = 0
// Device loadout
var/dev_cpu = 1 // 1: Default, 2: Upgraded
var/dev_battery = 1 // 1: Default, 2: Upgraded, 3: Advanced
var/dev_disk = 1 // 1: Default, 2: Upgraded, 3: Advanced
var/dev_netcard = 0 // 0: None, 1: Basic, 2: Long-Range
var/dev_apc_recharger = 0 // 0: None, 1: Standard (LAPTOP ONLY)
var/dev_printer = 0 // 0: None, 1: Standard
var/dev_card = 0 // 0: None, 1: Standard
// Removes all traces of old order and allows you to begin configuration from scratch.
/obj/machinery/lapvend/proc/reset_order()
state = 0
devtype = 0
if(fabricated_laptop)
qdel(fabricated_laptop)
fabricated_laptop = null
if(fabricated_tablet)
qdel(fabricated_tablet)
fabricated_tablet = null
dev_cpu = 1
dev_battery = 1
dev_disk = 1
dev_netcard = 0
dev_apc_recharger = 0
dev_printer = 0
dev_card = 0
// Recalculates the price and optionally even fabricates the device.
/obj/machinery/lapvend/proc/fabricate_and_recalc_price(fabricate = 0)
total_price = 0
if(devtype == 1) // Laptop, generally cheaper to make it accessible for most station roles
var/obj/item/computer_hardware/battery/battery_module = null
if(fabricate)
fabricated_laptop = new /obj/item/modular_computer/laptop/buildable(src)
fabricated_laptop.install_component(new /obj/item/computer_hardware/battery)
battery_module = fabricated_laptop.all_components[MC_CELL]
total_price = 99
switch(dev_cpu)
if(1)
if(fabricate)
fabricated_laptop.install_component(new /obj/item/computer_hardware/processor_unit/small)
if(2)
if(fabricate)
fabricated_laptop.install_component(new /obj/item/computer_hardware/processor_unit)
total_price += 299
switch(dev_battery)
if(1) // Basic(750C)
if(fabricate)
battery_module.try_insert(new /obj/item/stock_parts/cell/computer)
if(2) // Upgraded(1100C)
if(fabricate)
battery_module.try_insert(new /obj/item/stock_parts/cell/computer/advanced)
total_price += 199
if(3) // Advanced(1500C)
if(fabricate)
battery_module.try_insert(new /obj/item/stock_parts/cell/computer/super)
total_price += 499
switch(dev_disk)
if(1) // Basic(128GQ)
if(fabricate)
fabricated_laptop.install_component(new /obj/item/computer_hardware/hard_drive)
if(2) // Upgraded(256GQ)
if(fabricate)
fabricated_laptop.install_component(new /obj/item/computer_hardware/hard_drive/advanced)
total_price += 99
if(3) // Advanced(512GQ)
if(fabricate)
fabricated_laptop.install_component(new /obj/item/computer_hardware/hard_drive/super)
total_price += 299
switch(dev_netcard)
if(1) // Basic(Short-Range)
if(fabricate)
fabricated_laptop.install_component(new /obj/item/computer_hardware/network_card)
total_price += 99
if(2) // Advanced (Long Range)
if(fabricate)
fabricated_laptop.install_component(new /obj/item/computer_hardware/network_card/advanced)
total_price += 299
if(dev_apc_recharger)
total_price += 399
if(fabricate)
fabricated_laptop.install_component(new /obj/item/computer_hardware/recharger/APC)
if(dev_printer)
total_price += 99
if(fabricate)
fabricated_laptop.install_component(new /obj/item/computer_hardware/printer/mini)
if(dev_card)
total_price += 199
if(fabricate)
fabricated_laptop.install_component(new /obj/item/computer_hardware/card_slot)
return total_price
else if(devtype == 2) // Tablet, more expensive, not everyone could probably afford this.
var/obj/item/computer_hardware/battery/battery_module = null
if(fabricate)
fabricated_tablet = new(src)
fabricated_tablet.install_component(new /obj/item/computer_hardware/battery)
fabricated_tablet.install_component(new /obj/item/computer_hardware/processor_unit/small)
battery_module = fabricated_tablet.all_components[MC_CELL]
total_price = 199
switch(dev_battery)
if(1) // Basic(300C)
if(fabricate)
battery_module.try_insert(new /obj/item/stock_parts/cell/computer/nano)
if(2) // Upgraded(500C)
if(fabricate)
battery_module.try_insert(new /obj/item/stock_parts/cell/computer/micro)
total_price += 199
if(3) // Advanced(750C)
if(fabricate)
battery_module.try_insert(new /obj/item/stock_parts/cell/computer)
total_price += 499
switch(dev_disk)
if(1) // Basic(32GQ)
if(fabricate)
fabricated_tablet.install_component(new /obj/item/computer_hardware/hard_drive/micro)
if(2) // Upgraded(64GQ)
if(fabricate)
fabricated_tablet.install_component(new /obj/item/computer_hardware/hard_drive/small)
total_price += 99
if(3) // Advanced(128GQ)
if(fabricate)
fabricated_tablet.install_component(new /obj/item/computer_hardware/hard_drive)
total_price += 299
switch(dev_netcard)
if(1) // Basic(Short-Range)
if(fabricate)
fabricated_tablet.install_component(new/obj/item/computer_hardware/network_card)
total_price += 99
if(2) // Advanced (Long Range)
if(fabricate)
fabricated_tablet.install_component(new/obj/item/computer_hardware/network_card/advanced)
total_price += 299
if(dev_printer)
total_price += 99
if(fabricate)
fabricated_tablet.install_component(new/obj/item/computer_hardware/printer)
if(dev_card)
total_price += 199
if(fabricate)
fabricated_tablet.install_component(new/obj/item/computer_hardware/card_slot)
return total_price
return 0
/obj/machinery/lapvend/ui_act(action, params)
if(..())
return 1
switch(action)
if("pick_device")
if(state) // We've already picked a device type
return 0
devtype = text2num(params["pick"])
state = 1
fabricate_and_recalc_price(0)
return 1
if("clean_order")
reset_order()
return 1
if("purchase")
try_purchase()
return 1
if((state != 1) && devtype) // Following IFs should only be usable when in the Select Loadout mode
return 0
switch(action)
if("confirm_order")
state = 2 // Wait for ID swipe for payment processing
fabricate_and_recalc_price(0)
return 1
if("hw_cpu")
dev_cpu = text2num(params["cpu"])
fabricate_and_recalc_price(0)
return 1
if("hw_battery")
dev_battery = text2num(params["battery"])
fabricate_and_recalc_price(0)
return 1
if("hw_disk")
dev_disk = text2num(params["disk"])
fabricate_and_recalc_price(0)
return 1
if("hw_netcard")
dev_netcard = text2num(params["netcard"])
fabricate_and_recalc_price(0)
return 1
if("hw_tesla")
dev_apc_recharger = text2num(params["tesla"])
fabricate_and_recalc_price(0)
return 1
if("hw_nanoprint")
dev_printer = text2num(params["print"])
fabricate_and_recalc_price(0)
return 1
if("hw_card")
dev_card = text2num(params["card"])
fabricate_and_recalc_price(0)
return 1
return 0
/obj/machinery/lapvend/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state)
if(stat & (BROKEN | NOPOWER | MAINT))
if(ui)
ui.close()
return 0
ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
if (!ui)
ui = new(user, src, ui_key, "computer_fabricator", "Personal Computer Vendor", 500, 400, state = state)
ui.open()
ui.set_autoupdate(state = 1)
/obj/machinery/lapvend/attackby(obj/item/I as obj, mob/user as mob)
if(istype(I, /obj/item/stack/spacecash))
var/obj/item/stack/spacecash/c = I
if(!user.temporarilyRemoveItemFromInventory(c))
return
credits += c.value
visible_message("<span class='info'><span class='name'>[usr]</span> inserts [c.value] credits into [src].</span>")
qdel(c)
return
return ..()
// Simplified payment processing, returns 1 on success.
/obj/machinery/lapvend/proc/process_payment()
if(total_price > credits)
say("Insufficient credits.")
return 0
else
return 1
/obj/machinery/lapvend/ui_data(mob/user)
var/list/data = list()
data["state"] = state
if(state == 1)
data["devtype"] = devtype
data["hw_battery"] = dev_battery
data["hw_disk"] = dev_disk
data["hw_netcard"] = dev_netcard
data["hw_tesla"] = dev_apc_recharger
data["hw_nanoprint"] = dev_printer
data["hw_card"] = dev_card
data["hw_cpu"] = dev_cpu
if(state == 1 || state == 2)
data["totalprice"] = total_price
data["credits"] = credits
return data
/obj/machinery/lapvend/proc/try_purchase()
// Awaiting payment state
if(state == 2)
if(process_payment())
fabricate_and_recalc_price(1)
if((devtype == 1) && fabricated_laptop)
fabricated_laptop.forceMove(src.loc)
fabricated_laptop = null
else if((devtype == 2) && fabricated_tablet)
fabricated_tablet.forceMove(src.loc)
fabricated_tablet = null
credits -= total_price
say("Enjoy your new product!")
state = 3
return 1
return 0