mirror of
https://github.com/CHOMPStation2/CHOMPStation2.git
synced 2026-08-29 08:11:11 +01:00
[MIRROR] Implementing tgchat to replace old VChat (#7371)
Co-authored-by: Heroman3003 <31296024+Heroman3003@users.noreply.github.com> Co-authored-by: Selis <selis@xynolabs.com>
This commit is contained in:
co-authored by
Heroman3003
Selis
parent
a5c370fb85
commit
c2bc0f78c8
@@ -0,0 +1,30 @@
|
||||
## /TG/ Chat
|
||||
|
||||
/TG/ Chat, which will be referred to as TgChat from this point onwards, is a system in which we can send messages to clients in a controlled and semi-reliable manner. The standard way of sending messages to BYOND clients simply dumps whatever you output to them directly into their chat window, however BYOND allows us to load our own code on the client to change this behaviour in a way that allows us to do some pretty neat things.
|
||||
|
||||
### Message Format
|
||||
|
||||
TgChat handles sending messages from the server to the client through the use of JSON payloads, of which the format will change depending on the type of message and the intended client endpoint. An example of the payload for chat messages is as follows:
|
||||
```json
|
||||
{
|
||||
"sequence": 0,
|
||||
"content": {
|
||||
"type": ". . .", // ?optional
|
||||
"text": ". . .", // ?optional !atleast-one
|
||||
"html": ". . .", // ?optional !atleast-one
|
||||
"avoidHighlighting": 0 // ?optional
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### Reliability
|
||||
|
||||
In the past there have been issues where BYOND will silently and without reason lose a message we sent to the client, to detect this and recover from it seamlessly TgChat also has a baked in reliability layer. This reliability layer is very primitive, and simply keeps track of received sequence numbers. Should the client receive an unexpected sequence number TgChat asks the server to resend any missing packets.
|
||||
|
||||
### Ping System
|
||||
|
||||
TgChat supports a round trip time ping measurement, which is displayed to the client so they can know how long it takes for their commands and inputs to reach the server. This is done by sending the client a ping request, `ping/soft`, which tells the client to send a ping to the server. When the server receives said ping it sends a reply, `ping/reply`, to the client with a payload containing the current DateTime which the client can reference against the initial ping request.
|
||||
|
||||
### Chat Tabs, Local Storage, and Highlighting
|
||||
|
||||
To make organizing and managing chat easier and more functional for both players and admins, TgChat has the ability to filter out messages based on their primary tag, such as individual departmental radios, to a dedicated chat tab for easier reading and comprehension. These tabs can also be configured to highlist messages based on a simple keyword search. You can set a multitude of different keywords to search for and they will be highlighting for instant alerting of the client. Said tabs, highlighting rules, and your chat history will persist thanks to use of local storage on the client. Using local storage TgChat can ensure that your preferences are saved and maintained between client restarts and switching between other /TG/ servers. Local Storage is also used to keep your chat history aswell, should you need to scroll through your chat logs.
|
||||
@@ -0,0 +1,48 @@
|
||||
/// Old VChat Code Stuff
|
||||
|
||||
/* Old bicon code
|
||||
/proc/expire_bicon_cache(key)
|
||||
if(GLOB.bicon_cache[key])
|
||||
GLOB.bicon_cache -= key
|
||||
return TRUE
|
||||
return FALSE
|
||||
|
||||
GLOBAL_LIST_EMPTY(bicon_cache) // Cache of the <img> tag results, not the icons
|
||||
*/
|
||||
|
||||
/proc/bicon(var/obj, var/use_class = 1, var/custom_classes = "")
|
||||
return icon2base64html(obj, custom_classes)
|
||||
|
||||
/* Old bicon code
|
||||
var/class = use_class ? "class='icon misc [custom_classes]'" : null
|
||||
if(!obj)
|
||||
return
|
||||
|
||||
// Try to avoid passing bicon an /icon directly. It is better to pass it an atom so it can cache.
|
||||
if(isicon(obj)) // Passed an icon directly, nothing to cache-key on, as icon refs get reused *often*
|
||||
return "<img [class] src='data:image/png;base64,[icon2base64(obj)]'>"
|
||||
|
||||
// Either an atom or somebody fucked up and is gonna get a runtime, which I'm fine with.
|
||||
var/atom/A = obj
|
||||
var/key
|
||||
var/changes_often = ishuman(A) || isobserver(A) // If this ends up with more, move it into a proc or var on atom.
|
||||
|
||||
if(changes_often)
|
||||
key = "\ref[A]"
|
||||
else
|
||||
key = "[istype(A.icon, /icon) ? "\ref[A.icon]" : A.icon]:[A.icon_state]"
|
||||
|
||||
var/base64 = GLOB.bicon_cache[key]
|
||||
// Non-human atom, no cache
|
||||
if(!base64) // Doesn't exist, make it.
|
||||
base64 = icon2base64(A.examine_icon(), key)
|
||||
GLOB.bicon_cache[key] = base64
|
||||
if(changes_often)
|
||||
addtimer(CALLBACK(GLOBAL_PROC, .proc/expire_bicon_cache, key), 50 SECONDS, TIMER_UNIQUE)
|
||||
|
||||
// May add a class to the img tag created by bicon
|
||||
if(use_class)
|
||||
class = "class='icon [A.icon_state] [custom_classes]'"
|
||||
|
||||
return "<IMG [class] src='data:image/png;base64,[base64]'>"
|
||||
*/
|
||||
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Message-related procs
|
||||
*
|
||||
* Message format (/list):
|
||||
* - type - Message type, must be one of defines in `code/__DEFINES/chat.dm`
|
||||
* - text - Plain message text
|
||||
* - html - HTML message text
|
||||
* - Optional metadata, can be any key/value pair.
|
||||
*
|
||||
* Copyright (c) 2020 Aleksej Komarov
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
/proc/message_to_html(message)
|
||||
// Here it is possible to add a switch statement
|
||||
// to custom-handle various message types.
|
||||
return message["html"] || message["text"]
|
||||
@@ -0,0 +1,85 @@
|
||||
/*!
|
||||
* Copyright (c) 2020 Aleksej Komarov
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
/**
|
||||
* Circumvents the message queue and sends the message
|
||||
* to the recipient (target) as soon as possible.
|
||||
*/
|
||||
/proc/to_chat_immediate(
|
||||
target,
|
||||
html,
|
||||
type = null,
|
||||
text = null,
|
||||
avoid_highlighting = FALSE,
|
||||
// FIXME: These flags are now pointless and have no effect
|
||||
handle_whitespace = TRUE,
|
||||
trailing_newline = TRUE,
|
||||
confidential = FALSE
|
||||
)
|
||||
// Useful where the integer 0 is the entire message. Use case is enabling to_chat(target, some_boolean) while preventing to_chat(target, "")
|
||||
html = "[html]"
|
||||
text = "[text]"
|
||||
|
||||
if(!target)
|
||||
return
|
||||
if(!html && !text)
|
||||
CRASH("Empty or null string in to_chat proc call.")
|
||||
if(target == world)
|
||||
target = GLOB.clients
|
||||
|
||||
// Build a message
|
||||
var/message = list()
|
||||
if(type) message["type"] = type
|
||||
if(text) message["text"] = text
|
||||
if(html) message["html"] = html
|
||||
if(avoid_highlighting) message["avoidHighlighting"] = avoid_highlighting
|
||||
|
||||
// send it immediately
|
||||
SSchat.send_immediate(target, message)
|
||||
|
||||
/**
|
||||
* Sends the message to the recipient (target).
|
||||
*
|
||||
* Recommended way to write to_chat calls:
|
||||
* ```
|
||||
* to_chat(client,
|
||||
* type = MESSAGE_TYPE_INFO,
|
||||
* html = "You have found <strong>[object]</strong>")
|
||||
* ```
|
||||
*/
|
||||
/proc/to_chat(
|
||||
target,
|
||||
html,
|
||||
type = null,
|
||||
text = null,
|
||||
avoid_highlighting = FALSE,
|
||||
// FIXME: These flags are now pointless and have no effect
|
||||
handle_whitespace = TRUE,
|
||||
trailing_newline = TRUE,
|
||||
confidential = FALSE
|
||||
)
|
||||
//if(isnull(Master) || !SSchat?.initialized || !MC_RUNNING(SSchat.init_stage))
|
||||
if(isnull(Master) || !SSchat?.subsystem_initialized)
|
||||
to_chat_immediate(target, html, type, text, avoid_highlighting)
|
||||
return
|
||||
|
||||
// Useful where the integer 0 is the entire message. Use case is enabling to_chat(target, some_boolean) while preventing to_chat(target, "")
|
||||
html = "[html]"
|
||||
text = "[text]"
|
||||
|
||||
if(!target)
|
||||
return
|
||||
if(!html && !text)
|
||||
CRASH("Empty or null string in to_chat proc call.")
|
||||
if(target == world)
|
||||
target = GLOB.clients
|
||||
|
||||
// Build a message
|
||||
var/message = list()
|
||||
if(type) message["type"] = type
|
||||
if(text) message["text"] = text
|
||||
if(html) message["html"] = html
|
||||
if(avoid_highlighting) message["avoidHighlighting"] = avoid_highlighting
|
||||
SSchat.queue(target, message)
|
||||
Reference in New Issue
Block a user