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>
  

![dreamseeker_HIrEfrap5X](https://github.com/tgstation/tgstation/assets/47710522/97c713b7-dda3-44d3-a8f5-d0ec11c92668)

![qIOWhVld4l](https://github.com/tgstation/tgstation/assets/47710522/3ab4e2c1-a38f-4b20-8e9f-509ea14c0434)

![dreamseeker_LIqwi05i4O](https://github.com/tgstation/tgstation/assets/47710522/c051c791-b595-4166-a4d3-82cb7568411f)

![BIYxNVjGL7](https://github.com/tgstation/tgstation/assets/47710522/b9c97eab-52b5-449f-b00f-a0d8aa5f865c)

![dreamseeker_IWdoSsUinC](https://github.com/tgstation/tgstation/assets/47710522/2a4cd76a-2bdc-4283-b642-09e92476fef5)

![L9DxzFHDEF](https://github.com/tgstation/tgstation/assets/47710522/6a5b0e29-d535-4c7e-a88e-e9b71198719b)

![rAuDgqBLNE](https://github.com/tgstation/tgstation/assets/47710522/128a0291-91da-4f9e-9bc5-a65cf411ea6d)

![dreamseeker_voui6S8MUf](https://github.com/tgstation/tgstation/assets/47710522/6e3ba044-b8df-492d-b58d-6c73ab07233d)

![image](https://github.com/tgstation/tgstation/assets/47710522/522c1d85-b9cf-4e0e-9588-9d3993eea03f)

</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:
distributivgesetz
2023-08-03 23:43:31 +02:00
committed by GitHub
parent f820543bda
commit ebbc45b161
45 changed files with 2219 additions and 895 deletions
@@ -1,5 +1,3 @@
GLOBAL_LIST_EMPTY(TabletMessengers) // a list of all active messengers, similar to GLOB.PDAs (used primarily with ntmessenger.dm)
// This is the base type of computer
// Other types expand it - tablets and laptops are subtypes
// consoles use "procssor" item that is held inside it.
@@ -134,10 +132,9 @@ GLOBAL_LIST_EMPTY(TabletMessengers) // a list of all active messengers, similar
if(internal_cell)
internal_cell = new internal_cell(src)
update_appearance()
register_context()
Add_Messenger()
install_default_programs()
register_context()
update_appearance()
/obj/item/modular_computer/proc/install_default_programs()
SHOULD_CALL_PARENT(FALSE)
@@ -151,7 +148,6 @@ GLOBAL_LIST_EMPTY(TabletMessengers) // a list of all active messengers, similar
//Some components will actually try and interact with this, so let's do it later
QDEL_NULL(soundloop)
QDEL_LIST(stored_files)
Remove_Messenger()
if(istype(inserted_disk))
QDEL_NULL(inserted_disk)
@@ -647,6 +643,21 @@ GLOBAL_LIST_EMPTY(TabletMessengers) // a list of all active messengers, similar
enabled = FALSE
update_appearance()
///Imprints name and job into the modular computer, and calls back to necessary functions.
///Acts as a replacement to directly setting the imprints fields. All fields are optional, the proc will try to fill in missing gaps.
/obj/item/modular_computer/proc/imprint_id(name = null, job_name = null)
saved_identification = name || computer_id_slot?.registered_name || saved_identification
saved_job = job_name || computer_id_slot?.assignment || saved_job
SEND_SIGNAL(src, COMSIG_MODULAR_PDA_IMPRINT_UPDATED, saved_identification, saved_job)
UpdateDisplay()
///Resets the imprinted name and job back to null.
/obj/item/modular_computer/proc/reset_imprint()
saved_identification = null
saved_job = null
SEND_SIGNAL(src, COMSIG_MODULAR_PDA_IMPRINT_RESET)
UpdateDisplay()
/obj/item/modular_computer/ui_action_click(mob/user, actiontype)
if(istype(actiontype, /datum/action/item_action/toggle_computer_light))
toggle_flashlight()
@@ -722,6 +733,14 @@ GLOBAL_LIST_EMPTY(TabletMessengers) // a list of all active messengers, similar
to_chat(user, span_notice("You plug \the [attacking_item] to \the [src]."))
return
if(istype(attacking_item, /obj/item/photo))
var/obj/item/photo/attacking_photo = attacking_item
if(store_file(new /datum/computer_file/picture(attacking_photo.picture)))
balloon_alert(user, "photo scanned")
else
balloon_alert(user, "no space!")
return
// Check if any Applications need it
for(var/datum/computer_file/item_holding_app as anything in stored_files)
if(item_holding_app.application_attackby(attacking_item, user))
@@ -818,12 +837,6 @@ GLOBAL_LIST_EMPTY(TabletMessengers) // a list of all active messengers, similar
return physical.Adjacent(neighbor)
return ..()
/obj/item/modular_computer/proc/Add_Messenger()
GLOB.TabletMessengers += src
/obj/item/modular_computer/proc/Remove_Messenger()
GLOB.TabletMessengers -= src
///Returns a string of what to send at the end of messenger's messages.
/obj/item/modular_computer/proc/get_messenger_ending()
return "Sent from my PDA"
@@ -16,7 +16,8 @@
file_storing.computer = src
used_capacity += file_storing.size
SEND_SIGNAL(file_storing, COMSIG_MODULAR_COMPUTER_FILE_STORE, src)
SEND_SIGNAL(file_storing, COMSIG_COMPUTER_FILE_STORE, src)
SEND_SIGNAL(src, COMSIG_MODULAR_COMPUTER_FILE_STORE, file_storing)
return TRUE
/**
@@ -37,7 +38,8 @@
stored_files.Remove(file_removing)
used_capacity -= file_removing.size
SEND_SIGNAL(file_removing, COMSIG_MODULAR_COMPUTER_FILE_DELETED)
SEND_SIGNAL(src, COMSIG_MODULAR_COMPUTER_FILE_DELETE, file_removing)
SEND_SIGNAL(file_removing, COMSIG_COMPUTER_FILE_DELETE)
qdel(file_removing)
return TRUE
@@ -73,14 +75,34 @@
* If a computer disk is passed instead, it will check the disk over the computer.
*/
/obj/item/modular_computer/proc/find_file_by_name(filename, obj/item/computer_disk/target_disk)
if(!filename)
if(!istext(filename))
return null
if(target_disk)
for(var/datum/computer_file/file as anything in target_disk.stored_files)
if(file.filename == filename)
return file
else
if(isnull(target_disk))
for(var/datum/computer_file/file as anything in stored_files)
if(file.filename == filename)
return file
else
for(var/datum/computer_file/file as anything in target_disk.stored_files)
if(file.filename == filename)
return file
return null
/**
* find_file_by_uid
*
* Will check all files in this computer and returns the file with the matching uid.
* A file's uid is always unique to them, so this proc is sometimes preferable over find_file_by_name.
* If a computer disk is passed instead, it will check the disk over the computer.
*/
/obj/item/modular_computer/proc/find_file_by_uid(uid, obj/item/computer_disk/target_disk)
if(!isnum(uid))
return null
if(isnull(target_disk))
for(var/datum/computer_file/file as anything in stored_files)
if(file.uid == uid)
return file
else
for(var/datum/computer_file/file as anything in target_disk.stored_files)
if(file.uid == uid)
return file
return null
@@ -15,6 +15,7 @@
if(!active_ui)
if(active_program)
active_ui = new(user, src, active_program.tgui_id, active_program.filedesc)
active_program.ui_interact(user, active_ui)
else
active_ui = new(user, src, "NtosMain")
return active_ui.open()
@@ -22,6 +23,7 @@
if(active_program)
active_ui.interface = active_program.tgui_id
active_ui.title = active_program.filedesc
active_program.ui_interact(user, active_ui)
else
active_ui.interface = "NtosMain"
@@ -197,8 +199,7 @@
return TRUE
if("PC_Imprint_ID")
saved_identification = computer_id_slot.registered_name
saved_job = computer_id_slot.assignment
imprint_id()
UpdateDisplay()
playsound(src, 'sound/machines/terminal_processing.ogg', 15, TRUE)
@@ -9,7 +9,7 @@
///How many charges the virus has left
var/charges = 5
/obj/item/computer_disk/virus/proc/send_virus(obj/item/modular_computer/pda/source, obj/item/modular_computer/pda/target, mob/living/user)
/obj/item/computer_disk/virus/proc/send_virus(obj/item/modular_computer/pda/source, obj/item/modular_computer/pda/target, mob/living/user, message)
if(charges <= 0)
to_chat(user, span_notice("ERROR: Out of charges."))
return FALSE
@@ -26,7 +26,7 @@
/obj/item/computer_disk/virus/clown
name = "\improper H.O.N.K. disk"
/obj/item/computer_disk/virus/clown/send_virus(obj/item/modular_computer/pda/source, obj/item/modular_computer/pda/target, mob/living/user)
/obj/item/computer_disk/virus/clown/send_virus(obj/item/modular_computer/pda/source, obj/item/modular_computer/pda/target, mob/living/user, message)
. = ..()
if(!.)
return FALSE
@@ -43,7 +43,7 @@
/obj/item/computer_disk/virus/mime
name = "\improper sound of silence disk"
/obj/item/computer_disk/virus/mime/send_virus(obj/item/modular_computer/pda/source, obj/item/modular_computer/pda/target, mob/living/user)
/obj/item/computer_disk/virus/mime/send_virus(obj/item/modular_computer/pda/source, obj/item/modular_computer/pda/target, mob/living/user, message)
. = ..()
if(!.)
return FALSE
@@ -53,7 +53,7 @@
return FALSE
user.show_message(span_notice("Success!"))
charges--
app.ringer_status = FALSE
app.alert_silenced = TRUE
app.ringtone = ""
/**
@@ -64,7 +64,7 @@
name = "\improper D.E.T.O.M.A.T.I.X. disk"
charges = 6
/obj/item/computer_disk/virus/detomatix/send_virus(obj/item/modular_computer/pda/source, obj/item/modular_computer/pda/target, mob/living/user)
/obj/item/computer_disk/virus/detomatix/send_virus(obj/item/modular_computer/pda/source, obj/item/modular_computer/pda/target, mob/living/user, message)
. = ..()
if(!.)
return FALSE
@@ -82,15 +82,18 @@
var/fakejob = sanitize_name(tgui_input_text(user, "Enter a job for the rigged message.", "Forge Message", max_length = MAX_NAME_LEN), allow_numbers = TRUE)
if(!fakejob || source != original_host || !user.can_perform_action(source))
return
var/attach_fake_photo = tgui_alert(user, "Attach a fake photo?", "Forge Message", list("Yes", "No")) == "Yes"
var/datum/computer_file/program/messenger/app = locate() in source.stored_files
if(!app || charges <= 0 || !app.send_message(user, list(target), rigged = REF(user), fake_name = fakename, fake_job = fakejob))
var/datum/computer_file/program/messenger/target_app = locate() in target.stored_files
if(!app || charges <= 0 || !app.send_rigged_message(user, message, list(target_app), fakename, fakejob, attach_fake_photo))
return FALSE
charges--
user.show_message(span_notice("Success!"))
var/reference = REF(src)
target.add_traits(list(TRAIT_PDA_CAN_EXPLODE, TRAIT_PDA_MESSAGE_MENU_RIGGED), reference)
addtimer(TRAIT_CALLBACK_REMOVE(target, TRAIT_PDA_MESSAGE_MENU_RIGGED, reference), 10 SECONDS)
addtimer(TRAIT_CALLBACK_REMOVE(target, TRAIT_PDA_CAN_EXPLODE, reference), 1 MINUTES)
return TRUE
/**
@@ -119,7 +122,7 @@
telecrystal_stack.use(telecrystal_stack.amount)
/obj/item/computer_disk/virus/frame/send_virus(obj/item/modular_computer/pda/source, obj/item/modular_computer/pda/target, mob/living/user)
/obj/item/computer_disk/virus/frame/send_virus(obj/item/modular_computer/pda/source, obj/item/modular_computer/pda/target, mob/living/user, message)
. = ..()
if(!.)
return FALSE
@@ -362,7 +362,7 @@
. = ..()
for(var/datum/computer_file/program/messenger/msg in stored_files)
msg.mime_mode = TRUE
msg.ringer_status = FALSE
msg.alert_silenced = TRUE
/obj/item/modular_computer/pda/curator
name = "curator PDA"
@@ -380,7 +380,7 @@
/obj/item/modular_computer/pda/curator/Initialize(mapload)
. = ..()
for(var/datum/computer_file/program/messenger/msg in stored_files)
msg.ringer_status = FALSE
msg.alert_silenced = TRUE
/obj/item/modular_computer/pda/psychologist
name = "psychologist PDA"