Adds NTNRC (NTNet Relay Chat)

- Adds NTNet relay chat, communication method that works as alternative to PDA messaging.
- NTNRC client is available to everyone for download. Once started, you can set your username (much as you can with IRC in real) and join channels created by other users (or create your own channel)
- Each channel has an operator. This is typically the channel's creator. If the operator leaves, operator status is transferred to randomly picked other user. If all users leave operator status is set as null until someone arrives again.
- Operator can delete or rename the channel, as well as save copy of chat logs to the hard drive (in future it will be editable/printable) and set channel password. Password-locked channels are private only for those who know the password.
- Added new access type (ID 42): access_network. RD is now, by default, given this access.
- People with network access can enter administrative mode, which allows them to make operator actions even without operator status on all channels. Furthermore, this hides them from other users (unless they decide to talk). They also bypass password locks, if they are set.
- Minor tweaks to NTNet monitoring tool. Fixed one minor graphical glitch in the UI and added access requirement to run the program (network access)
- Screenshot of NTNRC in action: http://i.imgur.com/c7hrWY5.png
This commit is contained in:
Atlantis
2015-11-28 07:31:23 +01:00
parent ed6fc50389
commit fb8341f6ae
13 changed files with 369 additions and 20 deletions

View File

@@ -7,4 +7,12 @@
/datum/computer_file/data/clone()
var/datum/computer_file/data/temp = ..()
temp.stored_data = stored_data
return temp
return temp
// Calculates file size from amount of characters in saved string
/datum/computer_file/data/proc/calculate_size(var/block_size = 250)
size = max(1, round(length(stored_data) / block_size))
/datum/computer_file/data/logfile
filetype = "LOG"

View File

@@ -54,8 +54,12 @@
// Check if the user can run program. Only humans can operate computer. Automatically called in run_program()
// User has to wear their ID or have it inhand for ID Scan to work.
/datum/computer_file/program/proc/can_run(var/mob/living/user, var/loud = 0)
if(!required_access) // No required_access, allow it.
// 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(var/mob/living/user, var/loud = 0, var/access_to_check)
// Defaults to required_access
if(!access_to_check)
access_to_check = required_access
if(!access_to_check) // No required_access, allow it.
return 1
if(istype(user, /mob/living/silicon)) // AI or robot. Allow it.
return 1
@@ -75,7 +79,7 @@
user << "<span class='danger'>\The [computer] flashes an \"RFID Error - Unable to scan ID\" warning.</span>"
return 0
if(required_access in I.access)
if(access_to_check in I.access)
return 1
if(loud && computer)
user << "<span class='danger'>\The [computer] flashes an \"Access Denied\" warning.</span>"

View File

@@ -44,14 +44,4 @@
network_destination = "RCON remote control system"
requires_ntnet_feature = NTNET_SYSTEMCONTROL
usage_flags = PROGRAM_LAPTOP | PROGRAM_CONSOLE
size = 25
/datum/computer_file/program/suit_sensors
filename = "sensormonitor"
filedesc = "Suit Sensors Monitoring"
nanomodule_path = /datum/nano_module/crew_monitor
program_icon_state = "crew"
keyboard_icon_state = "keyboard7"
requires_ntnet = 1
network_destination = "crew lifesigns monitoring system"
size = 20
size = 25

View File

@@ -0,0 +1,9 @@
/datum/computer_file/program/suit_sensors
filename = "sensormonitor"
filedesc = "Suit Sensors Monitoring"
nanomodule_path = /datum/nano_module/crew_monitor
program_icon_state = "crew"
keyboard_icon_state = "keyboard7"
requires_ntnet = 1
network_destination = "crew lifesigns monitoring system"
size = 20

View File

@@ -4,6 +4,7 @@
program_icon_state = "generic"
size = 7
requires_ntnet = 1
required_access = access_network
available_on_ntnet = 1
nanomodule_path = /datum/nano_module/computer_ntnetmonitor/

View File

@@ -0,0 +1,199 @@
/datum/computer_file/program/chatclient
filename = "ntnrc_client"
filedesc = "NTNet Relay Chat Client"
program_icon_state = "generic"
size = 10
requires_ntnet = 1
requires_ntnet_feature = NTNET_COMMUNICATION
network_destination = "NTNRC server"
available_on_ntnet = 1
nanomodule_path = /datum/nano_module/computer_chatclient/
var/username
var/datum/ntnet_conversation/channel = null
var/operator_mode = 0 // Channel operator mode
var/netadmin_mode = 0 // Administrator mode (invisible to other users + bypasses passwords)
/datum/computer_file/program/chatclient/New()
username = "DefaultUser[rand(100, 999)]"
/datum/computer_file/program/chatclient/Topic(href, href_list)
if(href_list["PRG_speak"])
if(!channel)
return
var/mob/living/user = usr
var/message = sanitize(input(user, "Enter message or leave blank to cancel: "))
if(!message || !channel)
return
channel.add_message(message, username)
if(href_list["PRG_joinchannel"])
var/datum/ntnet_conversation/C
for(var/datum/ntnet_conversation/chan in ntnet_global.chat_channels)
if(chan.title == href_list["PRG_joinchannel"])
C = chan
break
if(!C)
return
if(netadmin_mode)
channel = C // Bypasses normal leave/join and passwords. Technically makes the user invisible to others.
return
if(C.password)
var/mob/living/user = usr
var/password = sanitize(input(user,"Access Denied. Enter password:"))
if(C && (password == C.password))
C.add_client(src)
channel = C
return
C.add_client(src)
channel = C
if(href_list["PRG_leavechannel"])
if(channel)
channel.remove_client(src)
channel = null
if(href_list["PRG_newchannel"])
var/mob/living/user = usr
var/channel_title = sanitize(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(href_list["PRG_toggleadmin"])
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
var/mob/living/user = usr
if(can_run(usr, 1, access_network))
if(channel)
var/response = alert(user, "Really engage admin-mode? You will be disconnected from your current channel!", "NTNRC Admin mode", "Yes", "No")
if(response == "Yes")
if(channel)
channel.remove_client(src)
channel = null
else
return
netadmin_mode = 1
if(href_list["PRG_changename"])
var/mob/living/user = usr
var/newname = sanitize(input(user,"Enter new nickname or leave blank to cancel:"))
if(!newname)
return
if(channel)
channel.add_status_message("[username] is now known as [newname].")
username = newname
if(href_list["PRG_savelog"])
if(!channel)
return
var/mob/living/user = usr
var/logname = input(user,"Enter desired logfile name (.log) or leave blank to cancel:")
if(!logname || !channel)
return
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.stored_data = "<b>Logfile dump from NTNRC channel [channel.title]</b><BR>"
for(var/logstring in channel.messages)
logfile.stored_data += "[logstring]<BR>"
logfile.stored_data = "<b>Logfile dump completed.</b>"
logfile.calculate_size()
if(!computer || !computer.hard_drive || !computer.hard_drive.store_file(logfile))
if(!computer)
// This program shouldn't even be runnable without computer.
CRASH("Var computer is null!")
return
if(!computer.hard_drive)
computer.visible_message("\The [computer] shows an \"I/O Error - Hard drive connection error\" warning.")
else // In 99.9% cases this will mean our HDD is full
computer.visible_message("\The [computer] shows an \"I/O Error - Hard drive may be full. Please free some space and try again. Required space: [logfile.size]GQ\" warning.")
if(href_list["PRG_renamechannel"])
if(!operator_mode || !channel)
return
var/mob/living/user = usr
var/newname = sanitize(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(href_list["PRG_deletechannel"])
if(channel && ((channel.operator == src) || netadmin_mode))
del(channel)
if(href_list["PRG_setpassword"])
if(!channel || ((channel.operator != src) && !netadmin_mode))
return
var/mob/living/user = usr
var/newpassword = sanitize(input(user, "Enter new password for this channel. Leave blank to cancel, enter 'nopassword' to remove password completely:"))
if(!channel || !newpassword || ((channel.operator != src) && !netadmin_mode))
return
if(newpassword == "nopassword")
channel.password = ""
else
channel.password = newpassword
..(href, href_list)
/datum/computer_file/program/chatclient/kill_program(var/forced = 0)
if(channel)
channel.remove_client(src)
channel = null
..(forced)
/datum/nano_module/computer_chatclient
name = "NTNet Relay Chat Client"
/datum/nano_module/computer_chatclient/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1, var/datum/topic_state/state = default_state)
if(!ntnet_global || !ntnet_global.chat_channels)
return
var/list/data = list()
if(program)
data = program.get_header_data()
var/datum/computer_file/program/chatclient/C = program
if(!istype(C))
return
data["adminmode"] = C.netadmin_mode
if(C.channel)
data["title"] = C.channel.title
var/list/messages[0]
for(var/M in C.channel.messages)
messages.Add(list(list(
"msg" = M
)))
data["messages"] = messages
var/list/clients[0]
for(var/datum/computer_file/program/chatclient/cl in C.channel.clients)
clients.Add(list(list(
"name" = cl.username
)))
data["clients"] = clients
C.operator_mode = (C.channel.operator == C) ? 1 : 0
data["is_operator"] = C.operator_mode || C.netadmin_mode
else // Channel selection screen
var/list/all_channels[0]
for(var/datum/ntnet_conversation/conv in ntnet_global.chat_channels)
if(conv && conv.title)
all_channels.Add(list(list(
"chan" = conv.title
)))
data["all_channels"] = all_channels
ui = nanomanager.try_update_ui(user, src, ui_key, ui, data, force_open)
if (!ui)
ui = new(user, src, ui_key, "ntnet_chat.tmpl", "NTNet Relay Chat Client", 575, 700, state = state)
ui.auto_update_layout = 1
ui.set_initial_data(data)
ui.open()
ui.set_auto_update(1)