From a053cba8e8aa4304ab106b71c5abbdde7fba5119 Mon Sep 17 00:00:00 2001
From: Heroman3003 <31296024+Heroman3003@users.noreply.github.com>
Date: Sat, 3 Jun 2023 19:21:33 +1000
Subject: [PATCH] Merge pull request #14935 from Runa-Dacino/ventroliquism
Adds a set of admin verbs for speaking/emoting as objs/mobs/turf without possessing them
---
code/modules/admin/admin_verb_lists_vr.dm | 6 +-
code/modules/admin/verbs/entity_narrate.dm | 167 +++++++++++++++++++++
code/modules/client/client defines.dm | 1 +
vorestation.dme | 1 +
4 files changed, 174 insertions(+), 1 deletion(-)
create mode 100644 code/modules/admin/verbs/entity_narrate.dm
diff --git a/code/modules/admin/admin_verb_lists_vr.dm b/code/modules/admin/admin_verb_lists_vr.dm
index 1ce3f3ade0..24f27f3761 100644
--- a/code/modules/admin/admin_verb_lists_vr.dm
+++ b/code/modules/admin/admin_verb_lists_vr.dm
@@ -167,7 +167,11 @@ var/list/admin_verbs_fun = list(
/client/proc/admin_lightning_strike,
/client/proc/resize, //VOREStation Add,
/client/proc/cmd_admin_droppod_deploy,
- /client/proc/adminorbit //VOREStation Add,
+ /client/proc/adminorbit, //VOREStation Add
+ /client/proc/add_mob_for_narration, //VOREStation Add
+ /client/proc/remove_mob_for_narration, //VOREStation Add
+ /client/proc/narrate_mob, //VOREStation Add
+ /client/proc/narrate_mob_args //VOREStation Add
)
var/list/admin_verbs_spawn = list(
diff --git a/code/modules/admin/verbs/entity_narrate.dm b/code/modules/admin/verbs/entity_narrate.dm
new file mode 100644
index 0000000000..20926803f5
--- /dev/null
+++ b/code/modules/admin/verbs/entity_narrate.dm
@@ -0,0 +1,167 @@
+//Datum that's initialized on first calling the client procs. It's stored in /client
+//We keep a distinct names/refs list in an effort to make things speedy, and for easy checking if something is on our list.
+//We manually add to list element with procs to avoid dealing with clunky global lists that might not be relevant
+//and for ability to narrate from long range.
+/datum/entity_narrate
+ var/list/entity_names = list()
+ var/list/entity_refs = list()
+
+
+
+//Appears as a right click verb on any obj and mob within view range.
+//when not right clicking we get a list to pick from in aforementioned view range.
+/client/proc/add_mob_for_narration(E as obj|mob|turf in orange(world.view))
+ set name = "Narrate Entity (Add ref)"
+ set desc = "Saves a reference of target mob to be called when narrating."
+ set category = "Fun"
+
+ if(!check_rights(R_FUN)) return
+
+ //Making sure we got the list datum on our client.
+ if(!entity_narrate_holder)
+ entity_narrate_holder = new /datum/entity_narrate()
+ if(!istype(entity_narrate_holder, /datum/entity_narrate))
+ return
+ var/datum/entity_narrate/holder = entity_narrate_holder
+
+ //Since we extended to include all atoms, we're shutting things down with a guard clause for ghosts
+ if(istype(E, /mob/observer))
+ to_chat(usr, SPAN_NOTICE("Ghosts shouldn't be narrated! If you want a ghost, make it a subtype of mob/living!"))
+ return
+ //We require a static mob/living type to check for .client and also later on, to use the unique .say mechanics for stuttering and language
+ if(istype(E, /mob/living))
+ var/mob/living/L = E
+ if(L.client)
+ to_chat(usr, "You may not speak for players!")
+ log_and_message_admins("attempted to speak for [L.ckey]'s mob", usr)
+ return
+ var/unique_name = sanitize(tgui_input_text(usr, "Please give the entity a unique name to track internally. \
+ This doesn't override how it appears in game", "tracker", L.name))
+ holder.entity_names += unique_name
+ holder.entity_refs[unique_name] = L
+ log_and_message_admins("added [L.name] for their personal list to narrate", usr) //Logging here to avoid spam, while still safeguarding abuse
+
+ //Covering functionality for turfs and objs. We need static type to access the name var
+ else if(istype(E, /atom))
+ var/atom/A = E
+ var/unique_name = sanitize(tgui_input_text(usr, "Please give the entity a unique name to track internally. \
+ This doesn't override how it appears in game", "tracker", A.name))
+ holder.entity_names += unique_name
+ holder.entity_refs[unique_name] = A
+ log_and_message_admins("added [A.name] for their personal list to narrate", usr) //Logging here to avoid spam, while still safeguarding abuse
+
+//Proc for keeping our ref list relevant, deleting mobs that are no longer relevant for our event
+/client/proc/remove_mob_for_narration()
+ set name = "Narrate Entity (Remove ref)"
+ set desc = "Remove mobs you're no longer narrating from your list for easier work."
+ set category = "Fun"
+
+ if(!check_rights(R_FUN)) return
+
+ if(!entity_narrate_holder)
+ entity_narrate_holder = new /datum/entity_narrate()
+ to_chat(usr, "No references were added yet! First add references!")
+ return
+ if(!istype(entity_narrate_holder, /datum/entity_narrate))
+ return
+ var/datum/entity_narrate/holder = entity_narrate_holder
+
+ var/options = holder.entity_names + "Clear All"
+ var/removekey = tgui_input_list(usr, "Choose which entity to remove", "remove reference", options, null)
+ if(removekey == "Clear All")
+ var/confirm = tgui_alert(usr, "Do you really want to clear your entity list?", "confirm", list("Yes", "No"), "No")
+ if(confirm == "Yes")
+ holder.entity_names = list()
+ holder.entity_refs = list()
+ else if(removekey)
+ holder.entity_refs -= removekey
+ holder.entity_names -= removekey
+
+//Planned to have TGUI functionality
+//For now brings up a list of all entities on our reference list and gives us the option to choose what we wanna do
+//using TGUI/Byond list/alert inputs
+//Does not actually interact with the game world, it passes user input to narrate_mob_args(name, mode, message) after sanitizing
+/client/proc/narrate_mob()
+ set name = "Narrate Entity (Interface)"
+ set desc = "Send either a visible or audiable message through your chosen entities using an interface"
+ set category = "Fun"
+
+ if(!check_rights(R_FUN)) return
+
+ if(!entity_narrate_holder)
+ entity_narrate_holder = new /datum/entity_narrate()
+ to_chat(usr, "No references were added yet! First add references!")
+ return
+ if(!istype(entity_narrate_holder, /datum/entity_narrate))
+ return
+ var/datum/entity_narrate/holder = entity_narrate_holder
+
+
+ //Obtaining and sanitizing arguments for the actual proc
+ var/which_entity = tgui_input_list(usr, "Choose which mob to narrate", "Narrate mob", holder.entity_names, null)
+ if(!which_entity) return
+ var/mode = tgui_alert(usr, "Speak or emote?", "mode", list("Speak", "Emote", "Cancel"))
+ if(mode == "Cancel") return
+ var/message = sanitize(tgui_input_text(usr, "Input what you want [which_entity] to say or do", "narrate", null, multiline = TRUE, prevent_enter = TRUE))
+ if(message)
+ narrate_mob_args(which_entity, mode, message)
+
+//The actual logic of the verb. Called by narrate_mob() when used.
+/client/proc/narrate_mob_args(name as text, mode as text, message as text)
+ set name = "Narrate Entity"
+ set desc = "Narrate entities using positional arguments. Name should be as saved in ref list, mode should be Speak or Emote, follow with message"
+ set category = "Fun"
+
+
+
+ if(!check_rights(R_FUN)) return
+
+ if(!entity_narrate_holder)
+ entity_narrate_holder = new /datum/entity_narrate()
+ to_chat(usr, "No references were added yet! First add references!")
+ return
+ if(!istype(entity_narrate_holder, /datum/entity_narrate))
+ return
+ var/datum/entity_narrate/holder = entity_narrate_holder
+
+ //Sanitizing args
+ name = sanitize(name)
+ mode = sanitize(mode)
+ if(message)
+ message = sanitize(message)
+
+ if(!(mode in list("Speak", "Emote")))
+ to_chat(usr, SPAN_NOTICE("Valid modes are 'Speak' and 'Emote'."))
+ return
+ if(!holder.entity_refs[name])
+ to_chat(usr, SPAN_NOTICE("[name] not in saved references!"))
+
+ //Separate definition for mob/living and /obj due to .say() code allowing us to engage with languages, stuttering etc
+ //We also need this so we can check for .client
+ if(istype(holder.entity_refs[name], /mob/living))
+ var/mob/living/our_entity = holder.entity_refs[name]
+ if(our_entity.client) //Making sure we can't speak for players
+ to_chat(usr, SPAN_NOTICE("Cannot narrate mobs with active clients!"))
+ log_and_message_admins("attempted to speak for [our_entity.ckey]'s mob", usr)
+ return
+ if(!message)
+ message = sanitize(tgui_input_text(usr, "Input what you want [our_entity] to [mode]", "narrate", null))
+ if(message && mode == "Speak")
+ our_entity.say(message)
+ else if(message && mode == "Emote")
+ our_entity.custom_emote(VISIBLE_MESSAGE, message)
+ else
+ return
+
+ //This does cost us some code duplication, but I think it's worth it.
+ //furthermore, objs/turfs require the usr to specify the verb when speaking, otherwise it looks like an emote.
+ else if(istype(holder.entity_refs[name], /atom))
+ var/atom/our_entity = holder.entity_refs[name]
+ if(!message)
+ message = sanitize(tgui_input_text(usr, "Input what you want [our_entity] to [mode]", "narrate", null))
+ if(message && mode == "Speak")
+ our_entity.audible_message("[our_entity.name] [message]")
+ else if(message && mode == "Emote")
+ our_entity.visible_message("[our_entity.name] [message]")
+ else
+ return
diff --git a/code/modules/client/client defines.dm b/code/modules/client/client defines.dm
index b73a5fe290..7ffe616393 100644
--- a/code/modules/client/client defines.dm
+++ b/code/modules/client/client defines.dm
@@ -13,6 +13,7 @@
var/last_message = "" //Contains the last message sent by this client - used to protect against copy-paste spamming.
var/last_message_count = 0 //contins a number of how many times a message identical to last_message was sent.
var/ircreplyamount = 0
+ var/entity_narrate_holder //Holds /datum/entity_narrate when using the relevant admin verbs.
/////////
//OTHER//
diff --git a/vorestation.dme b/vorestation.dme
index b65fdc5e3f..132dbca018 100644
--- a/vorestation.dme
+++ b/vorestation.dme
@@ -1861,6 +1861,7 @@
#include "code\modules\admin\verbs\debug_vr.dm"
#include "code\modules\admin\verbs\diagnostics.dm"
#include "code\modules\admin\verbs\dice.dm"
+#include "code\modules\admin\verbs\entity_narrate.dm"
#include "code\modules\admin\verbs\fps.dm"
#include "code\modules\admin\verbs\getlogs.dm"
#include "code\modules\admin\verbs\grief_fixers.dm"