From e31ec376feb424b78cfbb1b90378101773df63fe Mon Sep 17 00:00:00 2001 From: monster860 Date: Sat, 5 Jun 2021 06:23:53 -0400 Subject: [PATCH 1/8] Voice Announcement System --- code/__DEFINES/sound.dm | 3 +- code/_onclick/hud/ai.dm | 6 +- .../configuration/entries/general.dm | 4 + code/controllers/subsystem/communications.dm | 8 + code/datums/voice_announcements.dm | 182 ++++++++++++++++++ code/datums/world_topic.dm | 8 + .../game/machinery/computer/communications.dm | 17 ++ code/modules/admin/sql_ban_system.dm | 2 +- code/modules/mob/living/silicon/ai/say.dm | 24 ++- config/private_default.txt | 5 +- config/private_server.txt | 3 + .../tgui/interfaces/CommunicationsConsole.js | 7 + yogstation.dme | 1 + 13 files changed, 261 insertions(+), 9 deletions(-) create mode 100644 code/datums/voice_announcements.dm diff --git a/code/__DEFINES/sound.dm b/code/__DEFINES/sound.dm index 0a10f8c3a6d1..44dfd9f5eb91 100644 --- a/code/__DEFINES/sound.dm +++ b/code/__DEFINES/sound.dm @@ -8,11 +8,12 @@ #define CHANNEL_AMBIENCE 1018 #define CHANNEL_BUZZ 1017 #define CHANNEL_BICYCLE 1016 +#define CHANNEL_VOICE_ANNOUNCE 1015 //THIS SHOULD ALWAYS BE THE LOWEST ONE! //KEEP IT UPDATED -#define CHANNEL_HIGHEST_AVAILABLE 1015 +#define CHANNEL_HIGHEST_AVAILABLE 1014 #define MAX_INSTRUMENT_CHANNELS (128 * 6) diff --git a/code/_onclick/hud/ai.dm b/code/_onclick/hud/ai.dm index 49bdd3f3c047..97706a48b176 100644 --- a/code/_onclick/hud/ai.dm +++ b/code/_onclick/hud/ai.dm @@ -84,7 +84,11 @@ if(..()) return var/mob/living/silicon/ai/AI = usr - AI.announcement() + var/announce_type = input(usr, "What kind?", "Announce") in list("Voice", "Vox") + if(announce_type == "Voice") + AI.voice_announce() + else + AI.announcement() /obj/screen/ai/call_shuttle name = "Call Emergency Shuttle" diff --git a/code/controllers/configuration/entries/general.dm b/code/controllers/configuration/entries/general.dm index 49e0c6b6e98b..1230d5899dec 100644 --- a/code/controllers/configuration/entries/general.dm +++ b/code/controllers/configuration/entries/general.dm @@ -274,6 +274,10 @@ /datum/config_entry/string/invoke_youtubedl protection = CONFIG_ENTRY_LOCKED | CONFIG_ENTRY_HIDDEN +/datum/config_entry/string/voice_announce_url_base + +/datum/config_entry/string/voice_announce_dir + /datum/config_entry/flag/show_irc_name /datum/config_entry/flag/see_own_notes //Can players see their own admin notes diff --git a/code/controllers/subsystem/communications.dm b/code/controllers/subsystem/communications.dm index 894e8844db5c..7cf3c581412d 100644 --- a/code/controllers/subsystem/communications.dm +++ b/code/controllers/subsystem/communications.dm @@ -7,6 +7,7 @@ SUBSYSTEM_DEF(communications) var/silicon_message_cooldown var/nonsilicon_message_cooldown + var/last_voice_announce_open = 0 /datum/controller/subsystem/communications/proc/can_announce(mob/living/user, is_silicon) if(is_silicon && silicon_message_cooldown > world.time) @@ -42,5 +43,12 @@ SUBSYSTEM_DEF(communications) P.info = sending.content P.update_icon() +/datum/controller/subsystem/communications/Shutdown() + for(var/I = GLOB.voice_announce_list.len, I >= 1, I--) + var/id = GLOB.voice_announce_list[I] + var/datum/voice_announce/announce = GLOB.voice_announce_list[id] + if(announce) + qdel(announce) + #undef COMMUNICATION_COOLDOWN #undef COMMUNICATION_COOLDOWN_AI diff --git a/code/datums/voice_announcements.dm b/code/datums/voice_announcements.dm new file mode 100644 index 000000000000..82d4600e63bb --- /dev/null +++ b/code/datums/voice_announcements.dm @@ -0,0 +1,182 @@ +GLOBAL_LIST_EMPTY(voice_announce_list) + +/datum/voice_announce + var/id + var/client/client + var/is_ai = FALSE + var/topic_token + var/json_file + var/open = FALSE + var/started_playing = FALSE + var/duration = 300 + var/canceled = FALSE + +/datum/voice_announce/New(client/client) + . = ..() + src.client = client + id = "[client.ckey]_[sha1("[world.time]-[world.realtime]-[world.timeofday]-[rand()]")]" + topic_token = sha1("[client.ckey]-[rand()]") + +/datum/voice_announce/Destroy() + if(json_file && fexists(json_file)) + fdel(json_file) + GLOB.voice_announce_list -= id + . = ..() + +/datum/voice_announce/proc/open() + if(SScommunications.last_voice_announce_open + 30 > world.time) + // Keep cheeky fucks from trying to waste resources by spamming the button + return + var/url_base = CONFIG_GET(string/voice_announce_url_base) + var/dir = CONFIG_GET(string/voice_announce_dir) + if(!url_base || !dir) + return + + if(is_banned_from(client.ckey, "Voice Announcements")) + to_chat(client, "You are banned from making voice announcements.") + return FALSE + return TRUE + +/datum/voice_announce/proc/announce(snd) + +/datum/voice_announce/proc/handle_announce(ogg_filename, base_filename, ip, duration) + src.duration = duration + GLOB.voice_announce_list -= id + var/ogg_file = file("[CONFIG_GET(string/voice_announce_dir)]/[ogg_filename]") + var/base_file = file("[CONFIG_GET(string/voice_announce_dir)]/[base_filename]") + if(check_valid()) + var/snd = fcopy_rsc(ogg_file) + fdel(ogg_file) + fcopy(base_file, "[GLOB.log_directory]/[base_filename]") + fdel(base_file) + + log_admin("[key_name(client)] has made a voice announcement via [ip], saved to [base_filename]") + message_admins("[key_name_admin(client)] has made a voice announcement. ((CANCEL))") + + announce(snd) + else + fdel(ogg_file) + fdel(base_file) + +/datum/voice_announce/ai + is_ai = TRUE + +/datum/voice_announce/ai/check_valid() + if(!..()) + return FALSE + var/mob/living/silicon/ai/M = client.mob + if(!istype(M)) + return FALSE + if(GLOB.announcing_vox > world.time || M.control_disabled || M.incapacitated()) + return FALSE + return TRUE + +/datum/voice_announce/ai/announce(snd) + set waitfor = 0 + + GLOB.announcing_vox = world.time + 600 + + var/var/turf/mob_turf = get_turf(client.mob) + var/z_level = mob_turf.z + + var/sound/sound1 = sound('sound/vox/doop.ogg') + var/sound/sound2 = sound(snd, channel = CHANNEL_VOICE_ANNOUNCE, volume = 70) + do_announce_sound(sound1, sound2, 5, z_level) + +/datum/voice_announce/command + var/obj/machinery/computer/communications/comms_console + +/datum/voice_announce/command/New(client/client, obj/machinery/computer/communications/console) + . = ..() + comms_console = console + +/datum/voice_announce/command/check_valid() + if(!..()) + return FALSE + if(SScommunications.nonsilicon_message_cooldown > world.time) + return FALSE + var/mob/living/user = client.mob + if(!istype(user) || !user.canUseTopic(comms_console, !issilicon(user))) + return FALSE + return TRUE + +/datum/voice_announce/command/announce(snd) + set waitfor = 0 + var/var/turf/console_turf = get_turf(comms_console) + var/z_level = console_turf.z + SScommunications.nonsilicon_message_cooldown = world.time + 300 + var/mob/living/user = client.mob + deadchat_broadcast(" made a priority announcement from [get_area_name(user, TRUE)].", "[user.real_name]", user) + var/sound/sound1 = sound('sound/misc/announce.ogg') + var/sound/sound2 = sound(snd, channel = CHANNEL_VOICE_ANNOUNCE, volume = 70) + do_announce_sound(sound1, sound2, 15, z_level) diff --git a/code/datums/world_topic.dm b/code/datums/world_topic.dm index ff837386395f..7aca5c726f04 100644 --- a/code/datums/world_topic.dm +++ b/code/datums/world_topic.dm @@ -174,6 +174,14 @@ return jointext(message, "") +/datum/world_topic/voice_announce + keyword = "voice_announce" + +/datum/world_topic/voice_announce/Run(list/input) + var/datum/voice_announce/A = GLOB.voice_announce_list[input["voice_announce"]] + if(istype(A) && A.topic_token == input["voice_announce_token"]) + A.handle_announce(input["ogg_file"], input["uploaded_file"], input["ip"], text2num(input["duration"])) + /datum/world_topic/status keyword = "status" diff --git a/code/game/machinery/computer/communications.dm b/code/game/machinery/computer/communications.dm index fbcf30e78554..326945943754 100755 --- a/code/game/machinery/computer/communications.dm +++ b/code/game/machinery/computer/communications.dm @@ -161,6 +161,10 @@ if (!authenticated_as_silicon_or_captain(usr)) return make_announcement(usr) + if ("makeVoiceAnnouncement") + if (!authenticated_as_non_silicon_captain(usr)) + return + make_voice_announcement(usr) if ("messageAssociates") if (!authenticated_as_non_silicon_captain(usr)) return @@ -343,6 +347,7 @@ if (STATE_MAIN) data["canBuyShuttles"] = can_buy_shuttles(user) data["canMakeAnnouncement"] = FALSE + data["canMakeVoiceAnnouncement"] = FALSE data["canMessageAssociates"] = FALSE data["canRecallShuttles"] = !issilicon(user) data["canRequestNuke"] = FALSE @@ -381,6 +386,7 @@ data["alertLevelTick"] = alert_level_tick data["canMakeAnnouncement"] = TRUE + data["canMakeVoiceAnnouncement"] = ishuman(user) data["canSetAlertLevel"] = issilicon(user) ? "NO_SWIPE_NEEDED" : "SWIPE_NEEDED" if (SSshuttle.emergency.mode != SHUTTLE_IDLE && SSshuttle.emergency.mode != SHUTTLE_RECALL) @@ -481,6 +487,17 @@ SScommunications.make_announcement(user, is_ai, input) deadchat_broadcast(" made a priority announcement from [get_area_name(usr, TRUE)].", "[user.real_name]", user) +/obj/machinery/computer/communications/proc/make_voice_announcement(mob/living/user) + var/is_ai = issilicon(user) + if(is_ai) + return FALSE + if(!SScommunications.can_announce(user, is_ai)) + to_chat(user, "Intercomms recharging. Please stand by.") + return + var/datum/voice_announce/command/announce_datum = new(user.client, src) + announce_datum.open() + + /obj/machinery/computer/communications/proc/post_status(command, data1, data2) var/datum/radio_frequency/frequency = SSradio.return_frequency(FREQ_STATUS_DISPLAYS) diff --git a/code/modules/admin/sql_ban_system.dm b/code/modules/admin/sql_ban_system.dm index 3480853aee8b..814cce00c729 100644 --- a/code/modules/admin/sql_ban_system.dm +++ b/code/modules/admin/sql_ban_system.dm @@ -252,7 +252,7 @@ output += "" //departments/groups that don't have command staff would throw a javascript error since there's no corresponding reference for toggle_head() var/list/headless_job_lists = list("Silicon" = GLOB.nonhuman_positions, - "Abstract" = list("Appearance", "Emote", "OOC")) + "Abstract" = list("Appearance", "Emote", "OOC", "Voice Announcements")) for(var/department in headless_job_lists) output += "
" break_counter = 0 diff --git a/code/modules/mob/living/silicon/ai/say.dm b/code/modules/mob/living/silicon/ai/say.dm index 9bbe30634b0b..688f04464533 100644 --- a/code/modules/mob/living/silicon/ai/say.dm +++ b/code/modules/mob/living/silicon/ai/say.dm @@ -85,11 +85,25 @@ popup.set_content(dat) popup.open() +/mob/living/silicon/ai/proc/voice_announce() + var/static/GLOB.announcing_vox = 0 // Stores the time of the last announcement + if(GLOB.announcing_vox > world.time) + to_chat(src, "Please wait [DisplayTimeText(GLOB.announcing_vox - world.time)].") + return + if(incapacitated()) + return + if(control_disabled) + to_chat(src, "Wireless interface disabled, unable to interact with announcement PA.") + return + + var/datum/voice_announce/ai/announce_datum = new(client) + announce_datum.open() + +GLOBAL_VAR_INIT(announcing_vox, 0) /mob/living/silicon/ai/proc/announcement() - var/static/announcing_vox = 0 // Stores the time of the last announcement - if(announcing_vox > world.time) - to_chat(src, "Please wait [DisplayTimeText(announcing_vox - world.time)].") + if(GLOB.announcing_vox > world.time) + to_chat(src, "Please wait [DisplayTimeText(GLOB.announcing_vox - world.time)].") return var/message = input(src, "WARNING: Misuse of this verb can result in you being job banned. More help is available in 'Announcement Help'", "Announcement", src.last_announcement) as text @@ -98,7 +112,7 @@ var/voxType = input(src, "Which voice?", "VOX") in list("Victor (male)", "Verity (female)", "Oscar (military)") //Victor is vox_sounds_male, Verity is vox_sounds, Oscar is vox_sounds_military - if(!message || announcing_vox > world.time) + if(!message || GLOB.announcing_vox > world.time) return if(incapacitated()) @@ -130,7 +144,7 @@ to_chat(src, "These words are not available on the announcement system: [english_list(incorrect_words)].") return - announcing_vox = world.time + VOX_DELAY + GLOB.announcing_vox = world.time + VOX_DELAY log_game("[key_name(src)] made a vocal announcement with the following message: [message].") diff --git a/config/private_default.txt b/config/private_default.txt index d067d9f29d43..6e0ad67df1d5 100644 --- a/config/private_default.txt +++ b/config/private_default.txt @@ -14,7 +14,7 @@ $include resources.txt SERVERNAME Space station 13 ## Server SQL name: This is the name used to identify the server to the SQL DB, distinct from SERVERNAME as it must be at most 32 characters. -#SERVERSQLNAME +SERVERSQLNAME yogstation ## Put on byond hub: Uncomment this to put your server on the byond hub. #HUB @@ -79,3 +79,6 @@ TICK_LIMIT_MC_INIT 500 ## FEEDBACK_TABLEPREFIX SS13_ ## Remove "SS13_" if you are using the standard schema file. FEEDBACK_TABLEPREFIX SS13_ + +VOICE_ANNOUNCE_URL_BASE http://localhost/voice_announce/ +VOICE_ANNOUNCE_DIR ../Yogstation.net/voice_announce_tmp diff --git a/config/private_server.txt b/config/private_server.txt index ff053aa02290..45b66efccd88 100644 --- a/config/private_server.txt +++ b/config/private_server.txt @@ -86,3 +86,6 @@ SQL_ENABLED ## FEEDBACK_TABLEPREFIX SS13_ ## Remove "SS13_" if you are using the standard schema file. FEEDBACK_TABLEPREFIX erro_ + +VOICE_ANNOUNCE_URL_BASE https://www.yogstation.net/voice_announce/ +VOICE_ANNOUNCE_DIR tmp/voice_announce_tmp diff --git a/tgui/packages/tgui/interfaces/CommunicationsConsole.js b/tgui/packages/tgui/interfaces/CommunicationsConsole.js index cb46bc66ef64..fea57f37dcbd 100644 --- a/tgui/packages/tgui/interfaces/CommunicationsConsole.js +++ b/tgui/packages/tgui/interfaces/CommunicationsConsole.js @@ -268,6 +268,7 @@ const PageMain = (props, context) => { callShuttleReasonMinLength, canBuyShuttles, canMakeAnnouncement, + canMakeVoiceAnnouncement, canMessageAssociates, canRecallShuttles, canRequestNuke, @@ -380,6 +381,12 @@ const PageMain = (props, context) => { onClick={() => act("makePriorityAnnouncement")} />} + {!!canMakeVoiceAnnouncement &&