/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 (prob(sent_prob))
message += " Sent from my PDA"
// SKYRAT EDIT BEGIN - PDA messages show a visible message; again!
user.visible_message(span_notice("[user]'s PDA rings out with the soft sound of keypresses."), vision_distance = COMBAT_MESSAGE_RANGE)
//SKYRAT EDIT END
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"]] [rigged ? "Rigged" : ""] PDA Message --> [span_name("[signal.format_target()]")]: [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 = "(Reply)"
var/hrefstart
var/hrefend
if (isAI(L))
hrefstart = ""
hrefend = ""
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"] ? " (Photo)" : ""
to_chat(L, span_infoplain("[icon2html(computer)] PDA message from [hrefstart][signal.data["name"]] ([signal.data["job"]])[hrefend], [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