diff --git a/code/__DEFINES/speech_controller.dm b/code/__DEFINES/speech_controller.dm new file mode 100644 index 00000000000..e758cca60f6 --- /dev/null +++ b/code/__DEFINES/speech_controller.dm @@ -0,0 +1,12 @@ + +#define SPEECH_CONTROLLER_QUEUE_SAY_VERB "say_verb" + +#define SPEECH_CONTROLLER_QUEUE_WHISPER_VERB "whisper_verb" + +#define SPEECH_CONTROLLER_QUEUE_EMOTE_VERB "emote_verb" + +#define MOB_INDEX 1 + +#define MESSAGE_INDEX 2 + +#define CATEGORY_INDEX 3 diff --git a/code/__DEFINES/subsystems.dm b/code/__DEFINES/subsystems.dm index 8afb146fdca..8848ce0f450 100644 --- a/code/__DEFINES/subsystems.dm +++ b/code/__DEFINES/subsystems.dm @@ -113,6 +113,7 @@ #define INIT_ORDER_DBCORE 95 #define INIT_ORDER_BLACKBOX 94 #define INIT_ORDER_SERVER_MAINT 93 +#define INIT_ORDER_SPEECH_CONTROLLER 92 #define INIT_ORDER_INPUT 85 #define INIT_ORDER_SOUNDS 83 #define INIT_ORDER_INSTRUMENTS 82 @@ -200,6 +201,7 @@ #define FIRE_PRIORITY_EXPLOSIONS 666 #define FIRE_PRIORITY_TIMER 700 #define FIRE_PRIORITY_SOUND_LOOPS 800 +#define FIRE_PRIORITY_SPEECH_CONTROLLER 900 #define FIRE_PRIORITY_INPUT 1000 // This must always always be the max highest priority. Player input must never be lost. diff --git a/code/controllers/master.dm b/code/controllers/master.dm index 8e0d916247e..ed83dc8d3d2 100644 --- a/code/controllers/master.dm +++ b/code/controllers/master.dm @@ -463,7 +463,7 @@ GLOBAL_REAL(Master, /datum/controller/master) = new . = 1 -// Run thru the queue of subsystems to run, running them while balancing out their allocated tick precentage +/// Run thru the queue of subsystems to run, running them while balancing out their allocated tick precentage /datum/controller/master/proc/RunQueue() . = 0 var/datum/controller/subsystem/queue_node diff --git a/code/controllers/subsystem.dm b/code/controllers/subsystem.dm index 7b47daa2a9a..95c562d3c0c 100644 --- a/code/controllers/subsystem.dm +++ b/code/controllers/subsystem.dm @@ -99,7 +99,7 @@ /datum/controller/subsystem/proc/PreInit() return -//This is used so the mc knows when the subsystem sleeps. do not override. +///This is used so the mc knows when the subsystem sleeps. do not override. /datum/controller/subsystem/proc/ignite(resumed = FALSE) SHOULD_NOT_OVERRIDE(TRUE) set waitfor = FALSE @@ -114,9 +114,9 @@ state = SS_PAUSED queued_time = QT -//previously, this would have been named 'process()' but that name is used everywhere for different things! -//fire() seems more suitable. This is the procedure that gets called every 'wait' deciseconds. -//Sleeping in here prevents future fires until returned. +///previously, this would have been named 'process()' but that name is used everywhere for different things! +///fire() seems more suitable. This is the procedure that gets called every 'wait' deciseconds. +///Sleeping in here prevents future fires until returned. /datum/controller/subsystem/proc/fire(resumed = FALSE) flags |= SS_NO_FIRE CRASH("Subsystem [src]([type]) does not fire() but did not set the SS_NO_FIRE flag. Please add the SS_NO_FIRE flag to any subsystem that doesn't fire so it doesn't get added to the processing list and waste cpu.") @@ -154,9 +154,9 @@ next_fire = queued_time + wait + (world.tick_lag * (tick_overrun/100)) -//Queue it to run. -// (we loop thru a linked list until we get to the end or find the right point) -// (this lets us sort our run order correctly without having to re-sort the entire already sorted list) +///Queue it to run. +/// (we loop thru a linked list until we get to the end or find the right point) +/// (this lets us sort our run order correctly without having to re-sort the entire already sorted list) /datum/controller/subsystem/proc/enqueue() var/SS_priority = priority var/SS_flags = flags diff --git a/code/controllers/subsystem/speech_controller.dm b/code/controllers/subsystem/speech_controller.dm new file mode 100644 index 00000000000..96476127f41 --- /dev/null +++ b/code/controllers/subsystem/speech_controller.dm @@ -0,0 +1,54 @@ +SUBSYSTEM_DEF(speech_controller) + name = "Speech Controller" + wait = 1 + flags = SS_TICKER + priority = FIRE_PRIORITY_SPEECH_CONTROLLER//has to be high priority, second in priority ONLY to SSinput + init_order = INIT_ORDER_SPEECH_CONTROLLER + runlevels = RUNLEVELS_DEFAULT | RUNLEVEL_LOBBY + + ///used so that an admin can force all speech verbs to execute immediately instead of queueing + var/FOR_ADMINS_IF_BROKE_immediately_execute_all_speech = FALSE + + ///list of the form: list(client mob, message that mob is queued to say, other say arguments (if any)). + ///this is our process queue, processed every tick. + var/list/queued_says_to_execute = list() + +///queues mob_to_queue into our process list so they say(message) near the start of the next tick +/datum/controller/subsystem/speech_controller/proc/queue_say_for_mob(mob/mob_to_queue, message, message_type) + + if(!TICK_CHECK || FOR_ADMINS_IF_BROKE_immediately_execute_all_speech) + process_single_say(mob_to_queue, message, message_type) + return TRUE + + queued_says_to_execute += list(list(mob_to_queue, message, message_type)) + + return TRUE + +/datum/controller/subsystem/speech_controller/fire(resumed) + + /// cache for sanic speed (lists are references anyways) + var/list/says_to_process = queued_says_to_execute.Copy() + queued_says_to_execute.Cut()//we should be going through the entire list every single iteration + + for(var/list/say_to_process as anything in says_to_process) + + var/mob/mob_to_speak = say_to_process[MOB_INDEX]//index 1 is the mob, 2 is the message, 3 is the message category + var/message = say_to_process[MESSAGE_INDEX] + var/message_category = say_to_process[CATEGORY_INDEX] + + process_single_say(mob_to_speak, message, message_category) + +///used in fire() to process a single mobs message through the relevant proc. +///only exists so that sleeps in the message pipeline dont cause the whole queue to wait +/datum/controller/subsystem/speech_controller/proc/process_single_say(mob/mob_to_speak, message, message_category) + set waitfor = FALSE + + switch(message_category) + if(SPEECH_CONTROLLER_QUEUE_SAY_VERB) + mob_to_speak.say(message) + + if(SPEECH_CONTROLLER_QUEUE_WHISPER_VERB) + mob_to_speak.whisper(message) + + if(SPEECH_CONTROLLER_QUEUE_EMOTE_VERB) + mob_to_speak.emote("me",1,message,TRUE) diff --git a/code/modules/client/verbs/ooc.dm b/code/modules/client/verbs/ooc.dm index c49881756d2..4837dc037cd 100644 --- a/code/modules/client/verbs/ooc.dm +++ b/code/modules/client/verbs/ooc.dm @@ -1,6 +1,7 @@ GLOBAL_VAR_INIT(OOC_COLOR, null)//If this is null, use the CSS for OOC. Otherwise, use a custom colour. GLOBAL_VAR_INIT(normal_ooc_colour, "#002eb8") +///talking in OOC uses this /client/verb/ooc(msg as text) set name = "OOC" //Gave this shit a shorter name so you only have to time out "ooc" rather than "ooc message" to use it --NeoFite set category = "OOC" diff --git a/code/modules/mob/living/living_defines.dm b/code/modules/mob/living/living_defines.dm index 0f8d45789b0..5f82e92cb27 100644 --- a/code/modules/mob/living/living_defines.dm +++ b/code/modules/mob/living/living_defines.dm @@ -73,7 +73,8 @@ var/cameraFollow = null - var/tod = null /// Time of death + /// Time of death + var/tod = null var/on_fire = FALSE ///The "Are we on fire?" var var/fire_stacks = 0 ///Tracks how many stacks of fire we have on, max is usually 20 diff --git a/code/modules/mob/mob_say.dm b/code/modules/mob/mob_say.dm index 8ce7a821ade..1b6f51f1c02 100644 --- a/code/modules/mob/mob_say.dm +++ b/code/modules/mob/mob_say.dm @@ -1,27 +1,36 @@ //Speech verbs. -///Say verb +///what clients use to speak. when you type a message into the chat bar in say mode, this is the first thing that goes off serverside. /mob/verb/say_verb(message as text) set name = "Say" set category = "IC" + set instant = TRUE + if(GLOB.say_disabled) //This is here to try to identify lag problems to_chat(usr, span_danger("Speech is currently admin-disabled.")) return + + //queue this message because verbs are scheduled to process after SendMaps in the tick and speech is pretty expensive when it happens. + //by queuing this for next tick the mc can compensate for its cost instead of having speech delay the start of the next tick if(message) - say(message) + SSspeech_controller.queue_say_for_mob(src, message, SPEECH_CONTROLLER_QUEUE_SAY_VERB) ///Whisper verb /mob/verb/whisper_verb(message as text) set name = "Whisper" set category = "IC" + set instant = TRUE + if(GLOB.say_disabled) //This is here to try to identify lag problems to_chat(usr, span_danger("Speech is currently admin-disabled.")) return - whisper(message) + + if(message) + SSspeech_controller.queue_say_for_mob(src, message, SPEECH_CONTROLLER_QUEUE_WHISPER_VERB) ///whisper a message /mob/proc/whisper(message, datum/language/language=null) - say(message, language) //only living mobs actually whisper, everything else just talks + say(message, language = language) ///The me emote verb /mob/verb/me_verb(message as text) @@ -34,7 +43,7 @@ message = trim(copytext_char(sanitize(message), 1, MAX_MESSAGE_LEN)) - usr.emote("me",1,message,TRUE) + SSspeech_controller.queue_say_for_mob(src, message, SPEECH_CONTROLLER_QUEUE_EMOTE_VERB) ///Speak as a dead person (ghost etc) /mob/proc/say_dead(message) @@ -53,8 +62,6 @@ to_chat(src, span_danger("You have been banned from deadchat.")) return - - if (src.client) if(src.client.prefs.muted & MUTE_DEADCHAT) to_chat(src, span_danger("You cannot talk in deadchat (muted).")) diff --git a/tgstation.dme b/tgstation.dme index ddbba99af56..372eca4e65d 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -145,6 +145,7 @@ #include "code\__DEFINES\spaceman_dmm.dm" #include "code\__DEFINES\span.dm" #include "code\__DEFINES\spatial_gridmap.dm" +#include "code\__DEFINES\speech_controller.dm" #include "code\__DEFINES\stat.dm" #include "code\__DEFINES\stat_tracking.dm" #include "code\__DEFINES\station.dm" @@ -463,6 +464,7 @@ #include "code\controllers\subsystem\sounds.dm" #include "code\controllers\subsystem\spacedrift.dm" #include "code\controllers\subsystem\spatial_gridmap.dm" +#include "code\controllers\subsystem\speech_controller.dm" #include "code\controllers\subsystem\statpanel.dm" #include "code\controllers\subsystem\stickyban.dm" #include "code\controllers\subsystem\sun.dm"