mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-07-14 01:24:21 +01:00
Improved PDA Direct Messenger (#75820)
## About The Pull Request Fixes #76708, Closes #76729 (sorry Zephyr) This PR expands the Direct Messenger UI, adding a chat screen for each available messenger that you can find, and moving message sending over to TGUI. This chat screen includes a message log that displays messages sent by you as well as messages received from the recipient. This gets rid of the previous chat log, which just had all messages thrown together that you received or have sent, in one big list. Furthermore, all messaging is now done inside the UI. This kills all TGUI popups you would ever need to send messages forever (except for quick replies). Use the input bar on the bottom, press Enter or the Send button, and it sends your message. Spam mode is now done in the UI too, via a text field you can find in the contacts list. Additionally, because I have a habit of blowing things massively out of scope, I've also completely refactored how messages and chat logs are stored in the PDA messenger. I plan on using this in a PR that merges the chat client with the messenger, sometime in the future. Sorry this took so long. Stuff left to do before I open this PR for review: - [x] Add "recent messages" - [x] Add "unread messages" - [x] Add message drafts - [x] Make photo sending not shit - [x] Implement the edge cases for automated and rigged messages - [x] Make sure shit isn't fucked - [x] Profit <details> <summary>Screenshots</summary>          </details> ## Why It's Good For The Game The UI has largely stayed the same since modular tablets were added a year ago. Even better, direct messaging has been the same since PDAs were first added *more than a decade ago*. Imagine that. Now we finally actually (!) make use of those brand new features that we got from the TGUI switch in this regard. ## Changelog 🆑 distributivgesetz add: Updated Direct Messenger to v6.5.3. Now including brand new individual chat rooms, proper image attachments and a revolutionary message input field! add: Added a "Reset Imprint" option to the PDA painter. refactor: Refactored PDA imprinting code just a bit. fix: PDAs should now properly respond to rigged messages. /🆑 --------- Co-authored-by: Jeremiah <42397676+jlsnow301@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
f820543bda
commit
ebbc45b161
@@ -38,7 +38,7 @@
|
||||
/datum/computer_file/program/scipaper_program/proc/recheck_file_presence()
|
||||
if(selected_file in computer.stored_files)
|
||||
return FALSE
|
||||
UnregisterSignal(selected_file, COMSIG_MODULAR_COMPUTER_FILE_DELETED)
|
||||
UnregisterSignal(selected_file, COMSIG_COMPUTER_FILE_DELETE)
|
||||
selected_file = null
|
||||
paper_to_be.set_experiment()
|
||||
|
||||
@@ -189,12 +189,12 @@
|
||||
return TRUE
|
||||
if("select_file") // Selecting new file will necessitate a change in paper type. This will be done on select_experiment and not here.
|
||||
if(selected_file)
|
||||
UnregisterSignal(selected_file, COMSIG_MODULAR_COMPUTER_FILE_DELETED)
|
||||
UnregisterSignal(selected_file, COMSIG_COMPUTER_FILE_DELETE)
|
||||
paper_to_be.set_experiment() // Clears the paper info.
|
||||
for(var/datum/computer_file/data/ordnance/ordnance_data in computer.stored_files)
|
||||
if(ordnance_data.uid == params["selected_uid"])
|
||||
selected_file = ordnance_data
|
||||
RegisterSignal(selected_file, COMSIG_MODULAR_COMPUTER_FILE_DELETED, PROC_REF(recheck_file_presence))
|
||||
RegisterSignal(selected_file, COMSIG_COMPUTER_FILE_DELETE, PROC_REF(recheck_file_presence))
|
||||
return TRUE
|
||||
if("select_experiment")
|
||||
var/ex_path = text2path(params["selected_expath"])
|
||||
@@ -229,7 +229,7 @@
|
||||
if(linked_techweb.add_scientific_paper(paper_to_be))
|
||||
computer.say("\"[paper_to_be.title]\" has been published!")
|
||||
paper_to_be = new
|
||||
UnregisterSignal(selected_file, COMSIG_MODULAR_COMPUTER_FILE_DELETED)
|
||||
UnregisterSignal(selected_file, COMSIG_COMPUTER_FILE_DELETE)
|
||||
selected_file = null
|
||||
SStgui.update_uis(src)
|
||||
playsound(computer, 'sound/machines/ping.ogg', 25)
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
/// A list of all active and visible messengers
|
||||
GLOBAL_LIST_EMPTY_TYPED(pda_messengers, /datum/computer_file/program/messenger)
|
||||
|
||||
/// Registers an NTMessenger instance to the list of pda_messengers.
|
||||
/proc/add_messenger(datum/computer_file/program/messenger/messenger)
|
||||
var/obj/item/modular_computer/messenger_device = messenger.computer
|
||||
// a bunch of empty PDAs are normally allocated, we don't want that clutter
|
||||
if(!messenger_device.saved_identification || !messenger_device.saved_job)
|
||||
return
|
||||
|
||||
if(!istype(messenger))
|
||||
return
|
||||
|
||||
var/messenger_ref = REF(messenger)
|
||||
if(messenger_ref in GLOB.pda_messengers)
|
||||
return
|
||||
|
||||
GLOB.pda_messengers[messenger_ref] = messenger
|
||||
|
||||
/// Unregisters an NTMessenger instance from the pda_messengers table.
|
||||
/proc/remove_messenger(datum/computer_file/program/messenger/messenger)
|
||||
if(!istype(messenger))
|
||||
return
|
||||
|
||||
var/messenger_ref = REF(messenger)
|
||||
if(!(messenger_ref in GLOB.pda_messengers))
|
||||
return
|
||||
|
||||
GLOB.pda_messengers.Remove(messenger_ref)
|
||||
|
||||
/// Gets all messengers, sorted by their name
|
||||
/proc/get_messengers_sorted_by_name()
|
||||
return sortTim(GLOB.pda_messengers.Copy(), GLOBAL_PROC_REF(cmp_pdaname_asc), associative = TRUE)
|
||||
|
||||
/// Gets all messengers, sorted by their job
|
||||
/proc/get_messengers_sorted_by_job()
|
||||
return sortTim(GLOB.pda_messengers.Copy(), GLOBAL_PROC_REF(cmp_pdajob_asc), associative = TRUE)
|
||||
|
||||
/// Get the display name of a messenger instance
|
||||
/proc/get_messenger_name(datum/computer_file/program/messenger/messenger)
|
||||
if(!istype(messenger))
|
||||
return null
|
||||
var/obj/item/modular_computer/computer = messenger.computer
|
||||
if(!istype(computer))
|
||||
return null
|
||||
return STRINGIFY_PDA_TARGET(computer.saved_identification, computer.saved_job)
|
||||
|
||||
/**
|
||||
* Chat log data type, stores information about the recipient,
|
||||
* the messages themselves and other metadata.
|
||||
*/
|
||||
/datum/pda_chat
|
||||
/// The cached name of the recipient, so we can
|
||||
/// identify this chat even after the recipient is deleted
|
||||
var/cached_name = "Unknown"
|
||||
/// The cached job of the recipient
|
||||
var/cached_job = "Unknown"
|
||||
/// Weakref to the recipient messenger
|
||||
var/datum/weakref/recipient = null
|
||||
/// A list of messages in this chat
|
||||
var/list/datum/pda_message/messages = list()
|
||||
/// Used to determine if we should show this in recents
|
||||
var/visible_in_recents = FALSE
|
||||
/// Used to determine if you can talk in a chat
|
||||
var/can_reply = TRUE
|
||||
/// Saved draft of a message so the sender can leave and come back later
|
||||
var/message_draft = ""
|
||||
/// Number of unread messages in this chat
|
||||
var/unread_messages = 0
|
||||
|
||||
/datum/pda_chat/New(datum/computer_file/program/messenger/recipient)
|
||||
src.recipient = WEAKREF(recipient)
|
||||
src.can_reply = !isnull(recipient)
|
||||
|
||||
/// Adds a message to the chat log and optionally shows the chat in recents.
|
||||
/// Call this instead of adding to messages directly.
|
||||
/datum/pda_chat/proc/add_message(datum/pda_message/message, show_in_recents = TRUE)
|
||||
messages += message
|
||||
if(!visible_in_recents && show_in_recents)
|
||||
visible_in_recents = TRUE
|
||||
return message
|
||||
|
||||
/// Returns this datum as an associative list, used for ui_data calls.
|
||||
/datum/pda_chat/proc/get_ui_data(mob/user)
|
||||
var/list/data = list()
|
||||
|
||||
var/list/recipient_data = list()
|
||||
|
||||
recipient_data["name"] = get_recipient_name()
|
||||
recipient_data["job"] = get_recipient_job()
|
||||
recipient_data["ref"] = recipient?.reference
|
||||
|
||||
data["ref"] = REF(src)
|
||||
data["recipient"] = recipient_data
|
||||
|
||||
var/list/messages_data = list()
|
||||
for(var/datum/pda_message/message as anything in messages)
|
||||
messages_data += list(message.get_ui_data(user))
|
||||
data["messages"] = messages_data
|
||||
data["message_draft"] = message_draft
|
||||
|
||||
data["visible"] = visible_in_recents
|
||||
data["can_reply"] = can_reply
|
||||
data["unread_messages"] = unread_messages
|
||||
|
||||
return data
|
||||
|
||||
/// Returns the messenger's name, caches the name in case the recipient becomes invalid later.
|
||||
/datum/pda_chat/proc/get_recipient_name()
|
||||
var/datum/computer_file/program/messenger/messenger = recipient?.resolve()
|
||||
if(istype(messenger) && (recipient.reference in GLOB.pda_messengers))
|
||||
cached_name = messenger.computer.saved_identification
|
||||
return cached_name
|
||||
|
||||
/// Returns the messenger's job, caches the job in case the recipient becomes invalid later.
|
||||
/datum/pda_chat/proc/get_recipient_job()
|
||||
var/datum/computer_file/program/messenger/messenger = recipient?.resolve()
|
||||
if(istype(messenger) && (recipient.reference in GLOB.pda_messengers))
|
||||
cached_job = messenger.computer.saved_job
|
||||
return cached_job
|
||||
|
||||
/**
|
||||
* Chat message data type, stores data about messages themselves.
|
||||
*/
|
||||
/datum/pda_message
|
||||
/// The message itself.
|
||||
var/message
|
||||
/// Whether the message is sent by the user or not.
|
||||
var/outgoing
|
||||
/// The name of the photo asset in the SSassets cache, the URL of which is sent to the client.
|
||||
var/photo_name
|
||||
/// Whether this message was sent to everyone.
|
||||
var/everyone
|
||||
/// The station time at which this message was made.
|
||||
var/timestamp
|
||||
|
||||
/datum/pda_message/New(message, outgoing, timestamp, photo_name = null, everyone = FALSE)
|
||||
src.message = message
|
||||
src.outgoing = outgoing
|
||||
src.timestamp = timestamp
|
||||
src.photo_name = photo_name
|
||||
src.everyone = everyone
|
||||
|
||||
/// Returns an associative list of the message's data, used for ui_data calls.
|
||||
/datum/pda_message/proc/get_ui_data(mob/user)
|
||||
var/list/data = list()
|
||||
data["message"] = message
|
||||
data["outgoing"] = outgoing
|
||||
data["photo_path"] = photo_name ? SSassets.transport.get_asset_url(photo_name) : null
|
||||
data["everyone"] = everyone
|
||||
data["timestamp"] = timestamp
|
||||
return data
|
||||
@@ -0,0 +1,731 @@
|
||||
/// Used to generate an asset key for a temporary image unique to this user.
|
||||
#define TEMP_IMAGE_PATH(ref) ("ntos_messenger[ref]_temp_image.png")
|
||||
/// Purpose is evident by the name, hopefully.
|
||||
#define MAX_PDA_MESSAGE_LEN 1024
|
||||
/// Format of message timestamps.
|
||||
#define PDA_MESSAGE_TIMESTAMP_FORMAT "hh:mm"
|
||||
|
||||
/datum/computer_file/program/messenger
|
||||
filename = "nt_messenger"
|
||||
filedesc = "Direct Messenger"
|
||||
category = PROGRAM_CATEGORY_MISC
|
||||
program_icon_state = "command"
|
||||
extended_desc = "This program allows old-school communication with other modular devices."
|
||||
size = 0
|
||||
undeletable = TRUE // It comes by default in tablets, can't be downloaded, takes no space and should obviously not be able to be deleted.
|
||||
header_program = TRUE
|
||||
available_on_ntnet = FALSE
|
||||
usage_flags = PROGRAM_TABLET
|
||||
ui_header = "ntnrc_idle.gif"
|
||||
tgui_id = "NtosMessenger"
|
||||
program_icon = "comment-alt"
|
||||
alert_able = TRUE
|
||||
|
||||
/// Whether the user is invisible to the message list.
|
||||
var/invisible = FALSE
|
||||
/// great wisdom from PDA.dm - "no spamming" (prevents people from spamming the same message over and over)
|
||||
COOLDOWN_DECLARE(last_text)
|
||||
/// even more wisdom from PDA.dm - "no everyone spamming" (prevents people from spamming the same message over and over)
|
||||
COOLDOWN_DECLARE(last_text_everyone)
|
||||
/// Whether or not we're in a mime PDA.
|
||||
var/mime_mode = FALSE
|
||||
/// Whether this app can send messages to all.
|
||||
var/spam_mode = FALSE
|
||||
|
||||
/// An asssociative list of chats we have started, format: chatref -> pda_chat.
|
||||
var/list/saved_chats = list()
|
||||
/// Whose chatlogs we currently have open. If we are in the contacts list, this is null.
|
||||
var/viewing_messages_of = null
|
||||
|
||||
/// The current ringtone (displayed in the chat when a message is received).
|
||||
var/ringtone = MESSENGER_RINGTONE_DEFAULT
|
||||
/// Whether or not we're sorting by job.
|
||||
var/sort_by_job = TRUE
|
||||
/// Whether or not we're sending and receiving messages.
|
||||
var/sending_and_receiving = TRUE
|
||||
/// Selected photo for sending purposes.
|
||||
var/selected_image = null
|
||||
/// Whether or not we're sending (or trying to send) a virus.
|
||||
var/sending_virus = FALSE
|
||||
|
||||
/datum/computer_file/program/messenger/on_install()
|
||||
. = ..()
|
||||
RegisterSignal(computer, COMSIG_MODULAR_COMPUTER_FILE_STORE, PROC_REF(check_new_photo))
|
||||
RegisterSignal(computer, COMSIG_MODULAR_COMPUTER_FILE_DELETE, PROC_REF(check_photo_removed))
|
||||
RegisterSignal(computer, COMSIG_MODULAR_PDA_IMPRINT_UPDATED, PROC_REF(on_imprint_added))
|
||||
RegisterSignal(computer, COMSIG_MODULAR_PDA_IMPRINT_RESET, PROC_REF(on_imprint_reset))
|
||||
|
||||
/datum/computer_file/program/messenger/proc/check_new_photo(sender, datum/computer_file/picture/storing_picture)
|
||||
SIGNAL_HANDLER
|
||||
if(!istype(storing_picture))
|
||||
return
|
||||
update_pictures_for_all()
|
||||
|
||||
/datum/computer_file/program/messenger/proc/check_photo_removed(sender, datum/computer_file/picture/photo_removed)
|
||||
SIGNAL_HANDLER
|
||||
if(istype(photo_removed) && selected_image == photo_removed.picture_name)
|
||||
selected_image = null
|
||||
|
||||
/datum/computer_file/program/messenger/proc/on_imprint_added(sender)
|
||||
SIGNAL_HANDLER
|
||||
add_messenger(src)
|
||||
|
||||
/datum/computer_file/program/messenger/proc/on_imprint_reset(sender)
|
||||
SIGNAL_HANDLER
|
||||
remove_messenger(src)
|
||||
saved_chats = list()
|
||||
selected_image = null
|
||||
viewing_messages_of = null
|
||||
|
||||
/datum/computer_file/program/messenger/Destroy(force)
|
||||
if(!QDELETED(computer))
|
||||
stack_trace("Attempted to qdel messenger of [computer] without qdeling computer, this will cause problems later")
|
||||
remove_messenger(src)
|
||||
return ..()
|
||||
|
||||
/// Gets the list of available messengers
|
||||
/datum/computer_file/program/messenger/proc/get_messengers()
|
||||
var/list/dictionary = list()
|
||||
|
||||
var/list/messengers_sorted = sort_by_job ? get_messengers_sorted_by_job() : get_messengers_sorted_by_name()
|
||||
|
||||
for(var/messenger_ref in messengers_sorted)
|
||||
var/datum/computer_file/program/messenger/messenger = messengers_sorted[messenger_ref]
|
||||
if(messenger == src || messenger.invisible)
|
||||
continue
|
||||
|
||||
var/list/data = list()
|
||||
data["name"] = messenger.computer.saved_identification
|
||||
data["job"] = messenger.computer.saved_job
|
||||
data["ref"] = REF(messenger)
|
||||
|
||||
dictionary[data["ref"]] = data
|
||||
|
||||
return dictionary
|
||||
|
||||
/// Checks if the person can send an everyone message
|
||||
/datum/computer_file/program/messenger/proc/can_send_everyone_message()
|
||||
return COOLDOWN_FINISHED(src, last_text) && COOLDOWN_FINISHED(src, last_text_everyone)
|
||||
|
||||
/// Gets all currently relevant photo asset keys
|
||||
/datum/computer_file/program/messenger/proc/get_picture_assets()
|
||||
var/list/data = list()
|
||||
|
||||
for(var/datum/computer_file/picture/photo in computer.stored_files)
|
||||
data |= photo.picture_name
|
||||
|
||||
if(viewing_messages_of in saved_chats)
|
||||
var/datum/pda_chat/chat = saved_chats[viewing_messages_of]
|
||||
for(var/datum/pda_message/message as anything in chat.messages)
|
||||
if(isnull(message.photo_name))
|
||||
continue
|
||||
data |= message.photo_name
|
||||
|
||||
if(!isnull(selected_image))
|
||||
data |= selected_image
|
||||
|
||||
return data
|
||||
|
||||
/// Sends new datum/picture assets to everyone
|
||||
/datum/computer_file/program/messenger/proc/update_pictures_for_all()
|
||||
var/list/data = get_picture_assets()
|
||||
|
||||
if(isnull(computer.open_uis))
|
||||
return
|
||||
|
||||
for(var/datum/tgui/window as anything in computer.open_uis)
|
||||
SSassets.transport.send_assets(window.user, data)
|
||||
|
||||
/datum/computer_file/program/messenger/ui_interact(mob/user, datum/tgui/ui)
|
||||
var/list/data = get_picture_assets()
|
||||
SSassets.transport.send_assets(user, data)
|
||||
|
||||
/datum/computer_file/program/messenger/ui_state(mob/user)
|
||||
if(issilicon(user))
|
||||
return GLOB.reverse_contained_state
|
||||
return GLOB.default_state
|
||||
|
||||
/datum/computer_file/program/messenger/ui_act(action, list/params, datum/tgui/ui)
|
||||
switch(action)
|
||||
if("PDA_ringSet")
|
||||
var/new_ringtone = tgui_input_text(usr, "Enter a new ringtone", "Ringtone", ringtone, MESSENGER_RINGTONE_MAX_LENGTH)
|
||||
var/mob/living/usr_mob = usr
|
||||
if(!new_ringtone || !in_range(computer, usr_mob) || computer.loc != usr_mob)
|
||||
return FALSE
|
||||
|
||||
if(SEND_SIGNAL(computer, COMSIG_TABLET_CHANGE_ID, usr_mob, new_ringtone) & COMPONENT_STOP_RINGTONE_CHANGE)
|
||||
return FALSE
|
||||
|
||||
ringtone = new_ringtone
|
||||
return TRUE
|
||||
|
||||
if("PDA_toggleAlerts")
|
||||
alert_silenced = !alert_silenced
|
||||
return TRUE
|
||||
|
||||
if("PDA_toggleSendingAndReceiving")
|
||||
sending_and_receiving = !sending_and_receiving
|
||||
return TRUE
|
||||
|
||||
if("PDA_viewMessages")
|
||||
if(viewing_messages_of in saved_chats)
|
||||
var/datum/pda_chat/chat = saved_chats[viewing_messages_of]
|
||||
chat.unread_messages = 0
|
||||
|
||||
viewing_messages_of = params["ref"]
|
||||
|
||||
if (viewing_messages_of in saved_chats)
|
||||
var/datum/pda_chat/chat = saved_chats[viewing_messages_of]
|
||||
chat.visible_in_recents = TRUE
|
||||
|
||||
selected_image = null
|
||||
return TRUE
|
||||
|
||||
if("PDA_closeMessages")
|
||||
var/target = params["ref"]
|
||||
|
||||
if(!(target in saved_chats))
|
||||
return FALSE
|
||||
|
||||
var/datum/pda_chat/chat = saved_chats[target]
|
||||
chat.visible_in_recents = FALSE
|
||||
if(viewing_messages_of == target)
|
||||
viewing_messages_of = null
|
||||
return TRUE
|
||||
|
||||
if("PDA_clearMessages")
|
||||
var/chat_ref = params["ref"]
|
||||
|
||||
if(chat_ref in saved_chats)
|
||||
saved_chats.Remove(chat_ref)
|
||||
else if(isnull(chat_ref))
|
||||
saved_chats = list()
|
||||
|
||||
viewing_messages_of = null
|
||||
return TRUE
|
||||
|
||||
if("PDA_changeSortStyle")
|
||||
sort_by_job = !sort_by_job
|
||||
return TRUE
|
||||
|
||||
if("PDA_sendEveryone")
|
||||
if(!sending_and_receiving)
|
||||
to_chat(usr, span_notice("ERROR: This device has sending disabled."))
|
||||
return FALSE
|
||||
|
||||
if(!spam_mode)
|
||||
to_chat(usr, span_notice("ERROR: This device does not have mass-messaging perms."))
|
||||
return FALSE
|
||||
|
||||
if(!can_send_everyone_message())
|
||||
return FALSE
|
||||
|
||||
return send_message_to_all(usr, params["message"])
|
||||
|
||||
if("PDA_saveMessageDraft")
|
||||
var/target_chat_ref = params["ref"]
|
||||
var/message_draft = params["message"]
|
||||
|
||||
if(!(target_chat_ref in saved_chats))
|
||||
return FALSE
|
||||
|
||||
var/datum/pda_chat/chat = saved_chats[target_chat_ref]
|
||||
|
||||
chat.message_draft = message_draft
|
||||
|
||||
return TRUE
|
||||
|
||||
if("PDA_clearUnreads")
|
||||
var/target_chat_ref = params["ref"]
|
||||
|
||||
if(!(target_chat_ref in saved_chats))
|
||||
return FALSE
|
||||
|
||||
var/datum/pda_chat/chat = saved_chats[target_chat_ref]
|
||||
chat.unread_messages = 0
|
||||
|
||||
return TRUE
|
||||
|
||||
if("PDA_sendMessage")
|
||||
if(!sending_and_receiving)
|
||||
to_chat(usr, span_notice("ERROR: This device has sending disabled."))
|
||||
return FALSE
|
||||
|
||||
// target ref, can either be a chat in saved_chats
|
||||
// or a messenger ref in GLOB.pda_messengers
|
||||
var/target_ref = params["ref"]
|
||||
|
||||
var/target = null
|
||||
|
||||
if(target_ref in saved_chats)
|
||||
target = saved_chats[target_ref]
|
||||
else if(target_ref in GLOB.pda_messengers)
|
||||
target = GLOB.pda_messengers[target_ref]
|
||||
else
|
||||
return FALSE
|
||||
|
||||
if(sending_virus)
|
||||
var/obj/item/computer_disk/virus/disk = computer.inserted_disk
|
||||
if(!istype(disk))
|
||||
return FALSE
|
||||
|
||||
var/datum/computer_file/program/messenger/target_messenger = null
|
||||
|
||||
if(istype(target, /datum/pda_chat))
|
||||
var/datum/pda_chat/target_chat = target
|
||||
target_messenger = target_chat.recipient?.resolve()
|
||||
if(!istype(target_messenger))
|
||||
to_chat(usr, span_notice("ERROR: Recipient no longer exists."))
|
||||
return FALSE
|
||||
else if(istype(target, /datum/computer_file/program/messenger))
|
||||
target_messenger = target
|
||||
|
||||
return disk.send_virus(computer, target_messenger.computer, usr, params["message"])
|
||||
|
||||
return send_message(usr, params["message"], list(target))
|
||||
|
||||
if("PDA_clearPhoto")
|
||||
selected_image = null
|
||||
return TRUE
|
||||
|
||||
if("PDA_toggleVirus")
|
||||
sending_virus = !sending_virus
|
||||
return TRUE
|
||||
|
||||
if("PDA_selectPhoto")
|
||||
if(issilicon(usr))
|
||||
return FALSE
|
||||
|
||||
var/photo_uid = text2num(params["uid"])
|
||||
|
||||
var/datum/computer_file/picture/selected_photo = computer.find_file_by_uid(photo_uid)
|
||||
|
||||
if(!istype(selected_photo))
|
||||
return FALSE
|
||||
|
||||
selected_image = selected_photo.picture_name
|
||||
return TRUE
|
||||
|
||||
if("PDA_siliconSelectPhoto")
|
||||
if(!issilicon(usr))
|
||||
return FALSE
|
||||
var/mob/living/silicon/user = usr
|
||||
if(!user.aicamera)
|
||||
return FALSE
|
||||
var/datum/picture/selected_photo = user.aicamera.selectpicture(user)
|
||||
if(!selected_photo)
|
||||
return FALSE
|
||||
SSassets.transport.register_asset(TEMP_IMAGE_PATH(REF(src)), selected_photo.picture_image)
|
||||
selected_image = TEMP_IMAGE_PATH(REF(src))
|
||||
update_pictures_for_all()
|
||||
return TRUE
|
||||
|
||||
/datum/computer_file/program/messenger/ui_static_data(mob/user)
|
||||
var/list/static_data = list()
|
||||
|
||||
static_data["can_spam"] = spam_mode
|
||||
static_data["is_silicon"] = issilicon(user)
|
||||
static_data["alert_able"] = alert_able
|
||||
|
||||
return static_data
|
||||
|
||||
/datum/computer_file/program/messenger/ui_data(mob/user)
|
||||
var/list/data = list()
|
||||
|
||||
var/list/chats_data = list()
|
||||
for(var/chat_ref in saved_chats)
|
||||
var/datum/pda_chat/chat = saved_chats[chat_ref]
|
||||
var/list/chat_data = chat.get_ui_data(user)
|
||||
chats_data[chat_ref] = chat_data
|
||||
|
||||
var/list/messengers = get_messengers()
|
||||
|
||||
data["owner"] = ((REF(src) in GLOB.pda_messengers) ? list(
|
||||
"name" = computer.saved_identification,
|
||||
"job" = computer.saved_job,
|
||||
"ref" = REF(src)
|
||||
) : null)
|
||||
data["saved_chats"] = chats_data
|
||||
data["messengers"] = messengers
|
||||
data["sort_by_job"] = sort_by_job
|
||||
data["alert_silenced"] = alert_silenced
|
||||
data["sending_and_receiving"] = sending_and_receiving
|
||||
data["open_chat"] = viewing_messages_of
|
||||
|
||||
// silicons handle selecting photos a bit differently for now
|
||||
if(!issilicon(user))
|
||||
var/list/stored_photos = list()
|
||||
for(var/datum/computer_file/picture/photo_file in computer.stored_files)
|
||||
stored_photos += list(list(
|
||||
"uid" = photo_file.uid,
|
||||
"path" = SSassets.transport.get_asset_url(photo_file.picture_name)
|
||||
))
|
||||
data["stored_photos"] = stored_photos
|
||||
data["selected_photo_path"] = !isnull(selected_image) ? SSassets.transport.get_asset_url(selected_image) : null
|
||||
data["on_spam_cooldown"] = !can_send_everyone_message()
|
||||
|
||||
var/obj/item/computer_disk/virus/disk = computer.inserted_disk
|
||||
if(istype(disk))
|
||||
data["virus_attach"] = TRUE
|
||||
data["sending_virus"] = sending_virus
|
||||
return data
|
||||
|
||||
//////////////////////
|
||||
// MESSAGE HANDLING //
|
||||
//////////////////////
|
||||
|
||||
/// Brings up the quick reply prompt to send a message.
|
||||
/datum/computer_file/program/messenger/proc/quick_reply_prompt(mob/living/user, datum/pda_chat/chat)
|
||||
if(!istype(chat))
|
||||
return
|
||||
var/datum/computer_file/program/messenger/target = chat.recipient?.resolve()
|
||||
if(!istype(target) || !istype(target.computer))
|
||||
to_chat(user, span_notice("ERROR: Recipient no longer exists."))
|
||||
chat.recipient = null
|
||||
chat.can_reply = FALSE
|
||||
return
|
||||
var/target_name = target.computer.saved_identification
|
||||
var/input_message = tgui_input_text(user, "Enter [mime_mode ? "emojis":"a message"]", "NT Messaging[target_name ? " ([target_name])" : ""]", encode = FALSE)
|
||||
send_message(user, input_message, list(chat))
|
||||
|
||||
/// Helper proc that sends a message to everyone
|
||||
/datum/computer_file/program/messenger/proc/send_message_to_all(mob/living/user, message)
|
||||
var/list/datum/pda_chat/chats = list()
|
||||
var/list/messenger_targets = list()
|
||||
|
||||
for(var/mc in get_messengers())
|
||||
messenger_targets += mc
|
||||
|
||||
for(var/chatref in saved_chats)
|
||||
var/datum/pda_chat/chat = saved_chats[chatref]
|
||||
if(!(chat.recipient?.reference in messenger_targets)) // if its in messenger_targets, it's valid
|
||||
continue
|
||||
messenger_targets -= chat.recipient.reference
|
||||
chats += chat
|
||||
|
||||
for(var/missing_messenger in messenger_targets)
|
||||
var/datum/pda_chat/new_chat = create_chat(missing_messenger)
|
||||
chats += new_chat
|
||||
|
||||
if(send_message(user, message, chats, everyone = TRUE))
|
||||
COOLDOWN_START(src, last_text_everyone, 2 MINUTES)
|
||||
|
||||
/// Creates a chat and adds it to saved_chats. Supports fake users. Returns the newly created chat.
|
||||
/datum/computer_file/program/messenger/proc/create_chat(recipient_ref, name, job)
|
||||
var/datum/computer_file/program/messenger/recipient = null
|
||||
|
||||
if(isnull(name) && isnull(job))
|
||||
if(!(recipient_ref in GLOB.pda_messengers))
|
||||
CRASH("tried to create a chat with a messenger that isn't registered")
|
||||
recipient = GLOB.pda_messengers[recipient_ref]
|
||||
|
||||
var/datum/pda_chat/new_chat = new(recipient)
|
||||
|
||||
// this is a chat with a "fake user" (automated or forged message)
|
||||
if(!istype(recipient))
|
||||
new_chat.cached_name = name
|
||||
new_chat.cached_job = job
|
||||
new_chat.can_reply = FALSE
|
||||
|
||||
saved_chats[REF(new_chat)] = new_chat
|
||||
|
||||
return new_chat
|
||||
|
||||
/// Gets the chat by the recipient, either by their name or messenger ref
|
||||
/datum/computer_file/program/messenger/proc/find_chat_by_recipient(recipient, fake_user = FALSE)
|
||||
for(var/chat_ref in saved_chats)
|
||||
var/datum/pda_chat/chat = saved_chats[chat_ref]
|
||||
if(fake_user && chat.cached_name == recipient)
|
||||
return chat
|
||||
else if(chat.recipient?.reference == recipient)
|
||||
return chat
|
||||
return null
|
||||
|
||||
/// Returns a message input, sanitized and checked against the filter
|
||||
/datum/computer_file/program/messenger/proc/sanitize_pda_message(message, mob/sender)
|
||||
message = sanitize(trim(message, MAX_PDA_MESSAGE_LEN))
|
||||
|
||||
if(mime_mode)
|
||||
message = emoji_sanitize(message)
|
||||
|
||||
// check message against filter
|
||||
if(!check_pda_message_against_filter(message, sender))
|
||||
return null
|
||||
|
||||
return message
|
||||
|
||||
/// Sends a message to targets via PDA. When sending to everyone, set `everyone` to true so the message is formatted accordingly
|
||||
/datum/computer_file/program/messenger/proc/send_message(mob/living/sender, message, list/targets, everyone = FALSE)
|
||||
message = sanitize_pda_message(message, sender)
|
||||
|
||||
if(!message)
|
||||
return FALSE
|
||||
|
||||
// upgrade the image asset to a permanent key
|
||||
var/photo_asset_key = selected_image
|
||||
if(photo_asset_key == TEMP_IMAGE_PATH(REF(src)))
|
||||
var/datum/asset_cache_item/img_asset = SSassets.cache[photo_asset_key]
|
||||
photo_asset_key = SSmodular_computers.get_next_picture_name()
|
||||
SSassets.transport.register_asset(photo_asset_key, img_asset.resource, img_asset.hash)
|
||||
|
||||
// our sender targets
|
||||
var/list/datum/computer_file/program/messenger/target_messengers = list()
|
||||
var/list/datum/pda_chat/target_chats = list()
|
||||
|
||||
var/should_alert = length(targets) == 1
|
||||
|
||||
// filter out invalid targets
|
||||
for(var/target in targets)
|
||||
var/datum/pda_chat/target_chat = null
|
||||
var/datum/computer_file/program/messenger/target_messenger = null
|
||||
|
||||
if(istype(target, /datum/pda_chat))
|
||||
target_chat = target
|
||||
|
||||
if(!target_chat.can_reply)
|
||||
if(should_alert)
|
||||
to_chat(sender, span_notice("ERROR: Recipient has receiving disabled."))
|
||||
continue
|
||||
|
||||
target_messenger = target_chat.recipient?.resolve()
|
||||
|
||||
if(!istype(target_messenger))
|
||||
if(should_alert)
|
||||
to_chat(sender, span_notice("ERROR: Recipient no longer exists."))
|
||||
target_chat.can_reply = FALSE
|
||||
target_chat.recipient = null
|
||||
continue
|
||||
|
||||
if(!target_messenger.sending_and_receiving)
|
||||
if(should_alert)
|
||||
to_chat(sender, span_notice("ERROR: Recipient has receiving disabled."))
|
||||
continue
|
||||
|
||||
else if(istype(target, /datum/computer_file/program/messenger))
|
||||
target_messenger = target
|
||||
|
||||
if(!target_messenger.sending_and_receiving)
|
||||
if(should_alert)
|
||||
to_chat(sender, span_notice("ERROR: Recipient has receiving disabled."))
|
||||
continue
|
||||
|
||||
target_chat = find_chat_by_recipient(REF(target))
|
||||
|
||||
if(!istype(target_chat))
|
||||
target_chat = create_chat(REF(target))
|
||||
|
||||
else
|
||||
stack_trace("invalid target [target]")
|
||||
continue
|
||||
|
||||
target_chats += target_chat
|
||||
target_messengers += target_messenger
|
||||
|
||||
if(!send_message_signal(sender, message, target_messengers, photo_asset_key, everyone))
|
||||
return FALSE
|
||||
|
||||
// Log it in our logs
|
||||
var/datum/pda_message/message_datum = new(message, TRUE, station_time_timestamp(PDA_MESSAGE_TIMESTAMP_FORMAT), photo_asset_key, everyone)
|
||||
for(var/datum/pda_chat/target_chat as anything in target_chats)
|
||||
target_chat.add_message(message_datum, show_in_recents = !everyone)
|
||||
target_chat.unread_messages = 0
|
||||
|
||||
// send new pictures to everyone
|
||||
if(!isnull(photo_asset_key))
|
||||
update_pictures_for_all()
|
||||
|
||||
// switch our chat screen after sending a message, but do it only if it's not to everyone
|
||||
if(!everyone)
|
||||
viewing_messages_of = REF(target_chats[1])
|
||||
|
||||
return TRUE
|
||||
|
||||
/// Sends a rigged message that explodes when the recipient tries to reply or look at it.
|
||||
/datum/computer_file/program/messenger/proc/send_rigged_message(mob/sender, message, list/datum/computer_file/program/messenger/targets, fake_name, fake_job, attach_fake_photo)
|
||||
message = sanitize_pda_message(message, sender)
|
||||
|
||||
if(!message)
|
||||
return FALSE
|
||||
|
||||
var/fake_photo = attach_fake_photo ? ">:3c" : null
|
||||
|
||||
return send_message_signal(sender, message, targets, fake_photo, FALSE, TRUE, fake_name, fake_job)
|
||||
|
||||
/datum/computer_file/program/messenger/proc/send_message_signal(mob/sender, message, list/datum/computer_file/program/messenger/targets, photo_path = null, everyone = FALSE, rigged = FALSE, fake_name = null, fake_job = null)
|
||||
if(!sender.can_perform_action(computer))
|
||||
return FALSE
|
||||
|
||||
if(!COOLDOWN_FINISHED(src, last_text))
|
||||
return FALSE
|
||||
|
||||
if(!length(targets))
|
||||
return FALSE
|
||||
|
||||
// check for jammers
|
||||
if(is_within_radio_jammer_range(computer) && !rigged)
|
||||
// different message so people know it's a radio jammer
|
||||
to_chat(sender, span_notice("ERROR: Network unavailable, please try again later."))
|
||||
if(alert_able && !alert_silenced)
|
||||
playsound(computer, 'sound/machines/terminal_error.ogg', 15, TRUE)
|
||||
return FALSE
|
||||
|
||||
// used for logging
|
||||
var/list/stringified_targets = list()
|
||||
|
||||
for(var/datum/computer_file/program/messenger/messenger as anything in targets)
|
||||
stringified_targets += get_messenger_name(messenger)
|
||||
|
||||
var/datum/signal/subspace/messaging/tablet_message/signal = new(computer, list(
|
||||
"ref" = REF(src),
|
||||
"message" = message,
|
||||
"targets" = targets,
|
||||
"rigged" = rigged,
|
||||
"everyone" = everyone,
|
||||
"photo" = photo_path,
|
||||
"automated" = FALSE,
|
||||
))
|
||||
if(rigged) //Will skip the message server and go straight to the hub so it can't be cheesed by disabling the message server machine
|
||||
signal.data["fakename"] = fake_name
|
||||
signal.data["fakejob"] = fake_job
|
||||
signal.server_type = /obj/machinery/telecomms/hub
|
||||
signal.data["reject"] = FALSE // Do not refuse the message
|
||||
|
||||
signal.send_to_receivers()
|
||||
|
||||
// If it didn't reach, note that fact
|
||||
if (!signal.data["done"])
|
||||
to_chat(sender, span_notice("ERROR: Server is not responding."))
|
||||
if(alert_able && !alert_silenced)
|
||||
playsound(computer, 'sound/machines/terminal_error.ogg', 15, TRUE)
|
||||
return FALSE
|
||||
|
||||
// Log in the talk log
|
||||
sender.log_talk(message, LOG_PDA, tag="[rigged ? "Rigged" : ""] PDA: [computer.saved_identification] to [signal.format_target()]")
|
||||
if(rigged)
|
||||
log_bomber(sender, "sent a rigged PDA message (Name: [fake_name]. Job: [fake_job]) to [english_list(stringified_targets)] [!is_special_character(sender) ? "(SENT BY NON-ANTAG)" : ""]")
|
||||
|
||||
message = emoji_parse(message) //already sent- this just shows the sent emoji as one to the sender in the to_chat
|
||||
|
||||
// Show it to ghosts
|
||||
var/ghost_message = span_name("[sender] [rigged ? "(as [fake_name]) Rigged " : ""]PDA Message --> [span_name("[signal.format_target()]")]: \"[signal.format_message()]\"")
|
||||
for(var/mob/player_mob as anything in GLOB.current_observers_list)
|
||||
if(player_mob.client && !player_mob.client?.prefs)
|
||||
stack_trace("[player_mob] ([player_mob.ckey]) had null prefs, which shouldn't be possible!")
|
||||
continue
|
||||
|
||||
if(isobserver(player_mob) && (player_mob.client?.prefs.chat_toggles & CHAT_GHOSTPDA))
|
||||
to_chat(player_mob, "[FOLLOW_LINK(player_mob, sender)] [ghost_message]")
|
||||
|
||||
to_chat(sender, span_info("PDA message sent to [signal.format_target()]: \"[message]\""))
|
||||
|
||||
if (alert_able && !alert_silenced)
|
||||
computer.send_sound()
|
||||
|
||||
COOLDOWN_START(src, last_text, 1 SECONDS)
|
||||
|
||||
selected_image = null
|
||||
return TRUE
|
||||
|
||||
/datum/computer_file/program/messenger/proc/receive_message(datum/signal/subspace/messaging/tablet_message/signal)
|
||||
var/datum/pda_chat/chat = null
|
||||
|
||||
var/is_rigged = signal.data["rigged"]
|
||||
var/is_automated = signal.data["automated"]
|
||||
var/is_fake_user = is_rigged || is_automated || isnull(signal.data["ref"])
|
||||
var/fake_name = is_fake_user ? signal.data["fakename"] : null
|
||||
var/fake_job = is_fake_user ? signal.data["fakejob"] : null
|
||||
|
||||
var/sender_ref = signal.data["ref"]
|
||||
|
||||
// don't create a new chat for rigged messages, make it a one off notif
|
||||
if(!is_rigged)
|
||||
var/datum/pda_message/message = new(signal.data["message"], FALSE, station_time_timestamp(PDA_MESSAGE_TIMESTAMP_FORMAT), signal.data["photo"], signal.data["everyone"])
|
||||
|
||||
chat = find_chat_by_recipient(is_fake_user ? fake_name : sender_ref, is_fake_user)
|
||||
if(!istype(chat))
|
||||
chat = create_chat(!is_fake_user ? sender_ref : null, fake_name, fake_job)
|
||||
chat.add_message(message)
|
||||
chat.unread_messages++
|
||||
|
||||
// the recipient (us) currently has a chat with the sender open, so update their ui
|
||||
if(!isnull(viewing_messages_of) && viewing_messages_of == sender_ref)
|
||||
viewing_messages_of = REF(chat)
|
||||
|
||||
var/mob/living/receiver_mob = null
|
||||
//Check our immediate loc
|
||||
if(isliving(computer.loc))
|
||||
receiver_mob = computer.loc
|
||||
//Maybe they are a silicon!
|
||||
else
|
||||
receiver_mob = get(computer, /mob/living/silicon)
|
||||
|
||||
var/should_ring = !alert_silenced || is_rigged
|
||||
|
||||
if(should_ring && istype(receiver_mob) && receiver_mob.is_literate() && (receiver_mob.stat == CONSCIOUS || receiver_mob.stat == SOFT_CRIT))
|
||||
var/reply_href = signal.data["rigged"] ? "explode" : "message"
|
||||
var/photo_href = signal.data["rigged"] ? "explode" : "open"
|
||||
var/reply
|
||||
if(is_automated)
|
||||
reply = "\[Automated Message\]"
|
||||
else
|
||||
reply = "(<a href='byond://?src=[REF(src)];choice=[reply_href];skiprefresh=1;target=[REF(chat)]'>Reply</a>)"
|
||||
|
||||
// resolving w/o nullcheck here, assume the messenger exists if a real person sent a message
|
||||
var/datum/computer_file/program/messenger/sender_messenger = chat.recipient?.resolve()
|
||||
|
||||
var/sender_title = is_fake_user ? STRINGIFY_PDA_TARGET(fake_name, fake_job) : get_messenger_name(sender_messenger)
|
||||
var/sender_name = is_fake_user ? fake_name : sender_messenger.computer.saved_identification
|
||||
|
||||
if (isAI(receiver_mob))
|
||||
sender_title = "<a href='?src=[REF(receiver_mob)];track=[html_encode(sender_name)]'>[sender_title]</a>"
|
||||
|
||||
var/inbound_message = "[signal.format_message()]"
|
||||
inbound_message = emoji_parse(inbound_message)
|
||||
|
||||
var/photo_message = signal.data["photo"] ? " (<a href='byond://?src=[REF(src)];choice=[photo_href];skiprefresh=1;target=[REF(chat)]'>Photo Attached</a>)" : ""
|
||||
to_chat(receiver_mob, span_infoplain("[icon2html(computer, receiver_mob)] <b>PDA message from [sender_title], </b>\"[inbound_message]\"[photo_message] [reply]"))
|
||||
|
||||
if (alert_able && should_ring)
|
||||
computer.ring(ringtone)
|
||||
|
||||
SStgui.update_uis(computer)
|
||||
update_pictures_for_all()
|
||||
|
||||
/// topic call that answers to people pressing "(Reply)" in chat
|
||||
/datum/computer_file/program/messenger/Topic(href, href_list)
|
||||
..()
|
||||
|
||||
if(QDELETED(src))
|
||||
return
|
||||
if(!usr.can_perform_action(computer, FORBID_TELEKINESIS_REACH))
|
||||
return
|
||||
|
||||
// send an activation message and open the messenger
|
||||
if(!(computer.enabled || computer.turn_on(usr, open_ui = FALSE)))
|
||||
return
|
||||
if(!(computer.active_program == src || computer.open_program(usr, src, open_ui = FALSE)))
|
||||
return
|
||||
|
||||
var/target_href = href_list["target"]
|
||||
|
||||
switch(href_list["choice"])
|
||||
if("message")
|
||||
if(!(target_href in saved_chats))
|
||||
return
|
||||
quick_reply_prompt(usr, saved_chats[target_href])
|
||||
|
||||
if("open")
|
||||
if(target_href in saved_chats)
|
||||
viewing_messages_of = target_href
|
||||
computer.update_tablet_open_uis(usr)
|
||||
|
||||
if("explode")
|
||||
if(!HAS_TRAIT(computer, TRAIT_PDA_CAN_EXPLODE))
|
||||
return
|
||||
|
||||
var/obj/item/modular_computer/pda/comp = computer
|
||||
comp.explode(usr, from_message_menu = TRUE)
|
||||
|
||||
#undef PDA_MESSAGE_TIMESTAMP_FORMAT
|
||||
#undef MAX_PDA_MESSAGE_LEN
|
||||
#undef TEMP_IMAGE_PATH
|
||||
@@ -1,426 +0,0 @@
|
||||
/datum/computer_file/program/messenger
|
||||
filename = "nt_messenger"
|
||||
filedesc = "Direct Messenger"
|
||||
category = PROGRAM_CATEGORY_MISC
|
||||
program_icon_state = "command"
|
||||
extended_desc = "This program allows old-school communication with other modular devices."
|
||||
size = 0
|
||||
undeletable = TRUE // It comes by default in tablets, can't be downloaded, takes no space and should obviously not be able to be deleted.
|
||||
header_program = TRUE
|
||||
available_on_ntnet = FALSE
|
||||
usage_flags = PROGRAM_TABLET
|
||||
ui_header = "ntnrc_idle.gif"
|
||||
tgui_id = "NtosMessenger"
|
||||
program_icon = "comment-alt"
|
||||
alert_able = TRUE
|
||||
|
||||
/// The current ringtone (displayed in the chat when a message is received).
|
||||
var/ringtone = MESSENGER_RINGTONE_DEFAULT
|
||||
/// Whether or not the ringtone is currently on.
|
||||
var/ringer_status = TRUE
|
||||
/// Whether or not we're sending and receiving messages.
|
||||
var/sending_and_receiving = TRUE
|
||||
/// The messages currently saved in the app.
|
||||
var/messages = list()
|
||||
/// great wisdom from PDA.dm - "no spamming" (prevents people from spamming the same message over and over)
|
||||
var/last_text
|
||||
/// even more wisdom from PDA.dm - "no everyone spamming" (prevents people from spamming the same message over and over)
|
||||
var/last_text_everyone
|
||||
/// Scanned photo for sending purposes.
|
||||
var/datum/picture/saved_image
|
||||
/// Whether the user is invisible to the message list.
|
||||
var/invisible = FALSE
|
||||
/// Whether or not we're currently looking at the message list.
|
||||
var/viewing_messages = FALSE
|
||||
// Whether or not this device is currently hidden from the message monitor.
|
||||
var/monitor_hidden = FALSE
|
||||
// Whether or not we're sorting by job.
|
||||
var/sort_by_job = TRUE
|
||||
// Whether or not we're sending (or trying to send) a virus.
|
||||
var/sending_virus = FALSE
|
||||
|
||||
/// The path for the current loaded image in rsc
|
||||
var/photo_path
|
||||
|
||||
/// Whether or not we're in a mime PDA.
|
||||
var/mime_mode = FALSE
|
||||
/// Whether this app can send messages to all.
|
||||
var/spam_mode = FALSE
|
||||
|
||||
/datum/computer_file/program/messenger/application_attackby(obj/item/attacking_item, mob/living/user)
|
||||
if(!istype(attacking_item, /obj/item/photo))
|
||||
return FALSE
|
||||
var/obj/item/photo/pic = attacking_item
|
||||
saved_image = pic.picture
|
||||
ProcessPhoto()
|
||||
user.balloon_alert(user, "photo uploaded")
|
||||
return TRUE
|
||||
|
||||
/datum/computer_file/program/messenger/proc/ScrubMessengerList()
|
||||
var/list/dictionary = list()
|
||||
|
||||
for(var/obj/item/modular_computer/messenger in GetViewableDevices(sort_by_job))
|
||||
if(messenger.saved_identification && messenger.saved_job && !(messenger == computer))
|
||||
var/list/data = list()
|
||||
data["name"] = messenger.saved_identification
|
||||
data["job"] = messenger.saved_job
|
||||
data["ref"] = REF(messenger)
|
||||
|
||||
//if(data["ref"] != REF(computer)) // you cannot message yourself (despite all my rage)
|
||||
dictionary += list(data)
|
||||
|
||||
return dictionary
|
||||
|
||||
/proc/GetViewableDevices(sort_by_job = FALSE)
|
||||
var/list/dictionary = list()
|
||||
|
||||
var/sortmode
|
||||
if(sort_by_job)
|
||||
sortmode = GLOBAL_PROC_REF(cmp_pdajob_asc)
|
||||
else
|
||||
sortmode = GLOBAL_PROC_REF(cmp_pdaname_asc)
|
||||
|
||||
for(var/obj/item/modular_computer/P in sort_list(GLOB.TabletMessengers, sortmode))
|
||||
for(var/datum/computer_file/program/messenger/app in P.stored_files)
|
||||
if(!P.saved_identification || !P.saved_job || app.invisible || app.monitor_hidden)
|
||||
continue
|
||||
dictionary += P
|
||||
|
||||
return dictionary
|
||||
|
||||
/datum/computer_file/program/messenger/proc/StringifyMessengerTarget(obj/item/modular_computer/messenger)
|
||||
return "[messenger.saved_identification] ([messenger.saved_job])"
|
||||
|
||||
/datum/computer_file/program/messenger/proc/ProcessPhoto()
|
||||
if(saved_image)
|
||||
var/icon/img = saved_image.picture_image
|
||||
var/deter_path = "tmp_msg_photo[rand(0, 99999)].png"
|
||||
usr << browse_rsc(img, deter_path) // funny random assignment for now, i'll make an actual key later
|
||||
photo_path = deter_path
|
||||
|
||||
/datum/computer_file/program/messenger/ui_state(mob/user)
|
||||
if(issilicon(user))
|
||||
return GLOB.reverse_contained_state
|
||||
return GLOB.default_state
|
||||
|
||||
/datum/computer_file/program/messenger/ui_act(action, list/params, datum/tgui/ui)
|
||||
switch(action)
|
||||
if("PDA_ringSet")
|
||||
var/new_ringtone = tgui_input_text(usr, "Enter a new ringtone", "Ringtone", ringtone, MESSENGER_RINGTONE_MAX_LENGTH)
|
||||
var/mob/living/usr_mob = usr
|
||||
if(!new_ringtone || !in_range(computer, usr_mob) || computer.loc != usr_mob)
|
||||
return
|
||||
|
||||
if(SEND_SIGNAL(computer, COMSIG_TABLET_CHANGE_ID, usr_mob, new_ringtone) & COMPONENT_STOP_RINGTONE_CHANGE)
|
||||
return
|
||||
|
||||
ringtone = new_ringtone
|
||||
return TRUE
|
||||
|
||||
if("PDA_ringer_status")
|
||||
ringer_status = !ringer_status
|
||||
return TRUE
|
||||
|
||||
if("PDA_sAndR")
|
||||
sending_and_receiving = !sending_and_receiving
|
||||
return TRUE
|
||||
|
||||
if("PDA_viewMessages")
|
||||
viewing_messages = !viewing_messages
|
||||
return TRUE
|
||||
|
||||
if("PDA_clearMessages")
|
||||
messages = list()
|
||||
return TRUE
|
||||
|
||||
if("PDA_changeSortStyle")
|
||||
sort_by_job = !sort_by_job
|
||||
return TRUE
|
||||
|
||||
if("PDA_sendEveryone")
|
||||
if(!sending_and_receiving)
|
||||
to_chat(usr, span_notice("ERROR: Device has sending disabled."))
|
||||
return
|
||||
|
||||
if(!spam_mode)
|
||||
to_chat(usr, span_notice("ERROR: Device does not have mass-messaging perms."))
|
||||
return
|
||||
|
||||
var/list/targets = list()
|
||||
|
||||
for(var/obj/item/modular_computer/mc in GetViewableDevices())
|
||||
targets += mc
|
||||
|
||||
if(targets.len > 0)
|
||||
send_message(usr, targets, TRUE)
|
||||
|
||||
return TRUE
|
||||
|
||||
if("PDA_sendMessage")
|
||||
if(!sending_and_receiving)
|
||||
to_chat(usr, span_notice("ERROR: Device has sending disabled."))
|
||||
return
|
||||
|
||||
var/obj/item/modular_computer/target = locate(params["ref"])
|
||||
if(!target)
|
||||
return // we don't want tommy sending his messages to nullspace
|
||||
|
||||
if(!(target.saved_identification == params["name"] && target.saved_job == params["job"]))
|
||||
to_chat(usr, span_notice("ERROR: User no longer exists."))
|
||||
return
|
||||
|
||||
for(var/datum/computer_file/program/messenger/app in computer.stored_files)
|
||||
if(!app.sending_and_receiving && !sending_virus)
|
||||
to_chat(usr, span_notice("ERROR: Device has receiving disabled."))
|
||||
return
|
||||
|
||||
if(sending_virus)
|
||||
var/obj/item/computer_disk/virus/disk = computer.inserted_disk
|
||||
if(istype(disk))
|
||||
disk.send_virus(computer, target, usr)
|
||||
update_static_data(usr, ui)
|
||||
return TRUE
|
||||
|
||||
send_message(usr, list(target))
|
||||
return TRUE
|
||||
|
||||
if("PDA_clearPhoto")
|
||||
saved_image = null
|
||||
photo_path = null
|
||||
return TRUE
|
||||
|
||||
if("PDA_toggleVirus")
|
||||
sending_virus = !sending_virus
|
||||
return TRUE
|
||||
|
||||
if("PDA_selectPhoto")
|
||||
if(!issilicon(usr))
|
||||
return
|
||||
var/mob/living/silicon/user = usr
|
||||
if(!user.aicamera)
|
||||
return
|
||||
var/datum/picture/selected_photo = user.aicamera.selectpicture(user)
|
||||
if(!selected_photo)
|
||||
return
|
||||
saved_image = selected_photo
|
||||
ProcessPhoto()
|
||||
return TRUE
|
||||
|
||||
/datum/computer_file/program/messenger/ui_static_data(mob/user)
|
||||
var/list/data = list()
|
||||
data["owner"] = computer.saved_identification
|
||||
return data
|
||||
|
||||
/datum/computer_file/program/messenger/ui_data(mob/user)
|
||||
var/list/data = list()
|
||||
data["messages"] = messages
|
||||
data["sortByJob"] = sort_by_job
|
||||
data["isSilicon"] = issilicon(user)
|
||||
data["messengers"] = ScrubMessengerList()
|
||||
data["ringer_status"] = ringer_status
|
||||
data["sending_and_receiving"] = sending_and_receiving
|
||||
data["viewing_messages"] = viewing_messages
|
||||
data["photo"] = photo_path
|
||||
data["canSpam"] = spam_mode
|
||||
|
||||
var/obj/item/computer_disk/virus/disk = computer.inserted_disk
|
||||
if(disk && istype(disk))
|
||||
data["virus_attach"] = TRUE
|
||||
data["sending_virus"] = sending_virus
|
||||
return data
|
||||
|
||||
//////////////////////
|
||||
// MESSAGE HANDLING //
|
||||
//////////////////////
|
||||
|
||||
///Gets an input message from user and returns the sanitized message.
|
||||
/datum/computer_file/program/messenger/proc/msg_input(mob/living/user, target_name, rigged = FALSE)
|
||||
var/input_message
|
||||
if(mime_mode)
|
||||
input_message = emoji_sanitize(tgui_input_text(user, "Enter emojis", "NT Messaging[target_name ? " ([target_name])" : ""]"))
|
||||
else
|
||||
input_message = tgui_input_text(user, "Enter a message", "NT Messaging[target_name ? " ([target_name])" : ""]")
|
||||
|
||||
if (!input_message || !sending_and_receiving)
|
||||
return
|
||||
if(!user.can_perform_action(computer))
|
||||
return
|
||||
return sanitize(input_message)
|
||||
|
||||
/datum/computer_file/program/messenger/proc/send_message(mob/living/user, list/obj/item/modular_computer/targets, everyone = FALSE, rigged = FALSE, fake_name = null, fake_job = null)
|
||||
if(!targets.len)
|
||||
return FALSE
|
||||
|
||||
var/target_name = length(targets) == 1 ? targets[1].saved_identification : "Everyone"
|
||||
var/message = msg_input(user, target_name, rigged)
|
||||
|
||||
if(!message)
|
||||
return FALSE
|
||||
|
||||
if((last_text && world.time < last_text + 10) || (everyone && last_text_everyone && world.time < last_text_everyone + 2 MINUTES))
|
||||
to_chat(user, span_warning("The subspace transmitter of your tablet is still cooling down!"))
|
||||
return FALSE
|
||||
|
||||
var/turf/position = get_turf(computer)
|
||||
for(var/obj/item/jammer/jammer as anything in GLOB.active_jammers)
|
||||
var/turf/jammer_turf = get_turf(jammer)
|
||||
if(position?.z == jammer_turf.z && (get_dist(position, jammer_turf) <= jammer.range))
|
||||
return FALSE
|
||||
|
||||
var/list/filter_result = CAN_BYPASS_FILTER(user) ? null : is_ic_filtered_for_pdas(message)
|
||||
if (filter_result)
|
||||
REPORT_CHAT_FILTER_TO_USER(user, filter_result)
|
||||
return FALSE
|
||||
|
||||
var/list/soft_filter_result = CAN_BYPASS_FILTER(user) ? null : is_soft_ic_filtered_for_pdas(message)
|
||||
if (soft_filter_result)
|
||||
if(tgui_alert(usr,"Your message contains \"[soft_filter_result[CHAT_FILTER_INDEX_WORD]]\". \"[soft_filter_result[CHAT_FILTER_INDEX_REASON]]\", Are you sure you want to send it?", "Soft Blocked Word", list("Yes", "No")) != "Yes")
|
||||
return FALSE
|
||||
message_admins("[ADMIN_LOOKUPFLW(usr)] has passed the soft filter for \"[soft_filter_result[CHAT_FILTER_INDEX_WORD]]\" they may be using a disallowed term in PDA messages. Message: \"[html_encode(message)]\"")
|
||||
log_admin_private("[key_name(usr)] has passed the soft filter for \"[soft_filter_result[CHAT_FILTER_INDEX_WORD]]\" they may be using a disallowed term in PDA messages. Message: \"[message]\"")
|
||||
|
||||
// Send the signal
|
||||
var/list/string_targets = list()
|
||||
for (var/obj/item/modular_computer/comp in targets)
|
||||
if (comp.saved_identification && comp.saved_job) // != src is checked by the UI
|
||||
string_targets += STRINGIFY_PDA_TARGET(comp.saved_identification, comp.saved_job)
|
||||
|
||||
if (!string_targets.len)
|
||||
return FALSE
|
||||
var/sent_prob = 1
|
||||
if(ishuman(user))
|
||||
var/mob/living/carbon/human/old_person = user
|
||||
sent_prob = old_person.age >= 30 ? 25 : sent_prob
|
||||
if (computer && prob(sent_prob))
|
||||
message += " [computer.get_messenger_ending()]"
|
||||
|
||||
var/datum/signal/subspace/messaging/tablet_msg/signal = new(computer, list(
|
||||
"name" = fake_name || computer.saved_identification,
|
||||
"job" = fake_job || computer.saved_job,
|
||||
"message" = html_decode(message),
|
||||
"ref" = REF(computer),
|
||||
"targets" = targets,
|
||||
"rigged" = rigged,
|
||||
"photo" = saved_image,
|
||||
"photo_path" = photo_path,
|
||||
"automated" = FALSE,
|
||||
))
|
||||
if(rigged) //Will skip the message server and go straight to the hub so it can't be cheesed by disabling the message server machine
|
||||
signal.data["rigged_user"] = REF(user) // Used for bomb logging
|
||||
signal.server_type = /obj/machinery/telecomms/hub
|
||||
signal.data["reject"] = FALSE // Do not refuse the message
|
||||
|
||||
signal.send_to_receivers()
|
||||
|
||||
// If it didn't reach, note that fact
|
||||
if (!signal.data["done"])
|
||||
to_chat(user, span_notice("ERROR: Server isn't responding."))
|
||||
if(ringer_status)
|
||||
playsound(src, 'sound/machines/terminal_error.ogg', 15, TRUE)
|
||||
return FALSE
|
||||
|
||||
message = emoji_parse(message)//already sent- this just shows the sent emoji as one to the sender in the to_chat
|
||||
signal.data["message"] = emoji_parse(signal.data["message"])
|
||||
|
||||
// Log it in our logs
|
||||
var/list/message_data = list()
|
||||
message_data["name"] = signal.data["name"]
|
||||
message_data["job"] = signal.data["job"]
|
||||
message_data["contents"] = html_decode(signal.format_message())
|
||||
message_data["outgoing"] = TRUE
|
||||
message_data["ref"] = signal.data["ref"]
|
||||
message_data["photo_path"] = signal.data["photo_path"]
|
||||
message_data["photo"] = signal.data["photo"]
|
||||
message_data["target_details"] = signal.format_target()
|
||||
|
||||
// Show it to ghosts
|
||||
var/ghost_message = span_name("[message_data["name"]] </span><span class='game say'>[rigged ? "Rigged" : ""] PDA Message</span> --> [span_name("[signal.format_target()]")]: <span class='message'>[signal.format_message()]")
|
||||
for(var/mob/player_mob in GLOB.player_list)
|
||||
if(player_mob.client && !player_mob.client?.prefs)
|
||||
stack_trace("[player_mob] ([player_mob.ckey]) had null prefs, which shouldn't be possible!")
|
||||
continue
|
||||
|
||||
if(isobserver(player_mob) && (player_mob.client?.prefs.chat_toggles & CHAT_GHOSTPDA))
|
||||
to_chat(player_mob, "[FOLLOW_LINK(player_mob, user)] [ghost_message]")
|
||||
|
||||
// Log in the talk log
|
||||
user.log_talk(message, LOG_PDA, tag="[rigged ? "Rigged" : ""] PDA: [message_data["name"]] to [signal.format_target()]")
|
||||
if(rigged)
|
||||
log_bomber(user, "sent a rigged PDA message (Name: [message_data["name"]]. Job: [message_data["job"]]) to [english_list(string_targets)] [!is_special_character(user) ? "(SENT BY NON-ANTAG)" : ""]")
|
||||
to_chat(user, span_info("PDA message sent to [signal.format_target()]: [signal.format_message()]"))
|
||||
|
||||
if (ringer_status)
|
||||
computer.send_sound()
|
||||
|
||||
last_text = world.time
|
||||
if (everyone)
|
||||
message_data["name"] = "Everyone"
|
||||
message_data["job"] = ""
|
||||
last_text_everyone = world.time
|
||||
|
||||
messages += list(message_data)
|
||||
saved_image = null
|
||||
photo_path = null
|
||||
return TRUE
|
||||
|
||||
/datum/computer_file/program/messenger/proc/receive_message(datum/signal/subspace/messaging/tablet_msg/signal)
|
||||
var/list/message_data = list()
|
||||
message_data["name"] = signal.data["name"]
|
||||
message_data["job"] = signal.data["job"]
|
||||
message_data["contents"] = html_decode(signal.format_message())
|
||||
message_data["outgoing"] = FALSE
|
||||
message_data["ref"] = signal.data["ref"]
|
||||
message_data["automated"] = signal.data["automated"]
|
||||
message_data["photo_path"] = signal.data["photo_path"]
|
||||
message_data["photo"] = signal.data["photo"]
|
||||
messages += list(message_data)
|
||||
|
||||
var/mob/living/L = null
|
||||
if(computer.loc && isliving(computer.loc))
|
||||
L = computer.loc
|
||||
//Maybe they are a pAI!
|
||||
else
|
||||
L = get(computer, /mob/living/silicon)
|
||||
|
||||
if(L && (L.stat == CONSCIOUS || L.stat == SOFT_CRIT))
|
||||
var/reply = "(<a href='byond://?src=[REF(src)];choice=[signal.data["rigged"] ? "mess_us_up" : "Message"];skiprefresh=1;target=[signal.data["ref"]]'>Reply</a>)"
|
||||
var/hrefstart
|
||||
var/hrefend
|
||||
if (isAI(L))
|
||||
hrefstart = "<a href='?src=[REF(L)];track=[html_encode(signal.data["name"])]'>"
|
||||
hrefend = "</a>"
|
||||
|
||||
if(signal.data["automated"])
|
||||
reply = "\[Automated Message\]"
|
||||
|
||||
var/inbound_message = signal.format_message()
|
||||
inbound_message = emoji_parse(inbound_message)
|
||||
|
||||
if(L.is_literate())
|
||||
var/photo_message = message_data["photo"] ? " (<a href='byond://?src=[REF(signal.logged)];photo=1'>Photo</a>)" : ""
|
||||
to_chat(L, span_infoplain("[icon2html(computer)] <b>PDA message from [hrefstart][signal.data["name"]] ([signal.data["job"]])[hrefend], </b>[inbound_message][photo_message] [reply]"))
|
||||
|
||||
if (ringer_status)
|
||||
computer.ring(ringtone)
|
||||
|
||||
/// topic call that answers to people pressing "(Reply)" in chat
|
||||
/datum/computer_file/program/messenger/Topic(href, href_list)
|
||||
..()
|
||||
if(QDELETED(src))
|
||||
return
|
||||
// send an activation message, open the messenger, kill whoever reads this nesting mess
|
||||
if(!computer.enabled)
|
||||
if(!computer.turn_on(usr, open_ui = FALSE))
|
||||
return
|
||||
if(computer.active_program != src)
|
||||
if(!computer.open_program(usr, src, open_ui = FALSE))
|
||||
return
|
||||
if(!href_list["close"] && usr.can_perform_action(computer, FORBID_TELEKINESIS_REACH))
|
||||
switch(href_list["choice"])
|
||||
if("Message")
|
||||
send_message(usr, list(locate(href_list["target"])))
|
||||
if("mess_us_up")
|
||||
if(!HAS_TRAIT(src, TRAIT_PDA_CAN_EXPLODE))
|
||||
var/obj/item/modular_computer/pda/comp = computer
|
||||
comp.explode(usr, from_message_menu = TRUE)
|
||||
return
|
||||
@@ -29,11 +29,10 @@
|
||||
SSmodular_computers.purge_logs()
|
||||
return TRUE
|
||||
if("toggle_mass_pda")
|
||||
var/obj/item/modular_computer/target_tablet = locate(params["ref"]) in GLOB.TabletMessengers
|
||||
if(!istype(target_tablet))
|
||||
if(!(params["ref"] in GLOB.pda_messengers))
|
||||
return
|
||||
for(var/datum/computer_file/program/messenger/messenger_app in target_tablet.stored_files)
|
||||
messenger_app.spam_mode = !messenger_app.spam_mode
|
||||
var/datum/computer_file/program/messenger/target_messenger = GLOB.pda_messengers[params["ref"]]
|
||||
target_messenger.spam_mode = !target_messenger.spam_mode
|
||||
return TRUE
|
||||
|
||||
/datum/computer_file/program/ntnetmonitor/ui_data(mob/user)
|
||||
@@ -56,14 +55,14 @@
|
||||
data["ntnetlogs"] += list(list("entry" = i))
|
||||
|
||||
data["tablets"] = list()
|
||||
for(var/obj/item/modular_computer/messenger as anything in GetViewableDevices())
|
||||
var/list/tablet_data = list()
|
||||
if(messenger.saved_identification)
|
||||
for(var/datum/computer_file/program/messenger/messenger_app in computer.stored_files)
|
||||
tablet_data["enabled_spam"] += messenger_app.spam_mode
|
||||
for(var/messenger_ref in get_messengers_sorted_by_name())
|
||||
var/datum/computer_file/program/messenger/app = GLOB.pda_messengers[messenger_ref]
|
||||
var/obj/item/modular_computer/pda = app.computer
|
||||
|
||||
tablet_data["name"] += messenger.saved_identification
|
||||
tablet_data["ref"] += REF(messenger)
|
||||
var/list/tablet_data = list()
|
||||
tablet_data["enabled_spam"] = app.spam_mode
|
||||
tablet_data["name"] = pda.saved_identification
|
||||
tablet_data["ref"] = REF(app)
|
||||
|
||||
data["tablets"] += list(tablet_data)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user