Skip to content
This repository was archived by the owner on May 22, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 3 additions & 6 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,9 @@
"tgui/.yarn": true,
"tgui/.pnp.*": true
},
"workbench.editorAssociations": [
{
"filenamePattern": "*.dmi",
"viewType": "imagePreview.previewEditor"
}
],
"workbench.editorAssociations": {
"*.dmi": "imagePreview.previewEditor"
},
"files.eol": "\n",
"gitlens.advanced.blame.customArguments": ["-w"]
}
3 changes: 2 additions & 1 deletion code/__DEFINES/sound.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
2 changes: 1 addition & 1 deletion code/__HELPERS/files.dm
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/client/proc/browse_files(root="data/logs/", max_iterations=10, list/valid_extensions=list("txt","log","htm", "html", "json"))
/client/proc/browse_files(root="data/logs/", max_iterations=10, list/valid_extensions=list("txt","log","htm", "html", "json", "aac", "mp3", "ogg", "opus", "wav", "weba"))
if(IsAdminAdvancedProcCall())
log_admin_private("BROWSEFILES: Admin proc call blocked")
message_admins("BROWSEFILES: Admin proc call blocked")
Expand Down
4 changes: 4 additions & 0 deletions code/controllers/configuration/entries/general.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions code/controllers/subsystem/communications.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
173 changes: 173 additions & 0 deletions code/datums/voice_announcements.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
GLOBAL_LIST_EMPTY(voice_announce_list)

/datum/voice_announce
var/id
var/client/client
var/is_ai = FALSE
var/started_playing = FALSE
var/duration = 300
var/canceled = FALSE
var/was_queried = FALSE

/datum/voice_announce/New(client/client)
. = ..()
src.client = client
id = "[client.ckey]_[GenerateToken()]"

/datum/voice_announce/Destroy()
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, "<span class='warning>You are banned from making voice announcements.</span>")
return

SScommunications.last_voice_announce_open = world.time

GLOB.voice_announce_list[id] = src
usr << link(url_base + "[CONFIG_GET(string/serversqlname)]/[id]")
addtimer(CALLBACK(src, .proc/timeout1), 15 SECONDS)
addtimer(CALLBACK(src, .proc/timeout2), 5 MINUTES)

/datum/voice_announce/proc/timeout1()
if(!was_queried && !started_playing)
qdel(src)

/datum/voice_announce/proc/timeout2()
if(!started_playing)
qdel(src)

/datum/voice_announce/Topic(href, href_list)
. = ..()
if(href_list["stop_announce"] && started_playing && !canceled && check_rights(NONE))
SEND_SOUND(world, sound(null, channel = CHANNEL_VOICE_ANNOUNCE))
canceled = TRUE
log_admin("[key_name(usr)] has canceled the voice announcement")
message_admins("[key_name_admin(usr)] has canceled the voice announcement")


/datum/voice_announce/proc/do_announce_sound(sound/sound1, sound/sound2, sounds_delay, z_level)
started_playing = TRUE
// Play for admins
for(var/mob/M in GLOB.player_list)
if(M.client && M.client.holder && M.can_hear() && (M.client.prefs.toggles & SOUND_ANNOUNCEMENTS))
var/turf/T = get_turf(M)
if(T.z == z_level)
SEND_SOUND(M, sound1)
sleep(sounds_delay)
for(var/mob/M in GLOB.player_list)
if(M.client && M.client.holder && M.can_hear() && (M.client.prefs.toggles & SOUND_ANNOUNCEMENTS))
var/turf/T = get_turf(M)
if(T.z == z_level)
SEND_SOUND(M, sound2)
sleep(30)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't it make more sense to do this?

Suggested change
sleep(30)
sleep(duration + 5 SECONDS)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's an unreasonably long wait time IMO

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not too sure about having it played for everyone before admins get to hear the full message tho

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

admins get to hear bad words before everyone else, hit cancel button, sure they'll get a message cut off halfway but whatever right?

if(canceled)
return
// Play for everyone else
for(var/mob/M in GLOB.player_list)
if(M.client && !M.client.holder && M.can_hear() && (M.client.prefs.toggles & SOUND_ANNOUNCEMENTS))
var/turf/T = get_turf(M)
if(T.z == z_level)
SEND_SOUND(M, sound1)
sleep(sounds_delay)
if(canceled)
return
for(var/mob/M in GLOB.player_list)
if(M.client && !M.client.holder && M.can_hear() && (M.client.prefs.toggles & SOUND_ANNOUNCEMENTS))
var/turf/T = get_turf(M)
if(T.z == z_level)
SEND_SOUND(M, sound2)
sleep(duration)
qdel(src)

/datum/voice_announce/proc/check_valid()
if(client == null)
return FALSE
if(is_banned_from(client.ckey, "Voice Announcements"))
to_chat(client, "<span class='warning>You are banned from making voice announcements.</span>")
return FALSE
return TRUE

/datum/voice_announce/proc/announce(snd)
stack_trace("announce() is unimplemented")

/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. ((<a href='?src=[REF(src)]&stop_announce=1'>CANCEL</a>))")

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/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
var/mob/living/user = client.mob
if(!SScommunications.can_announce(user, issilicon(user)))
return FALSE
if(!istype(user) || !user.canUseTopic(comms_console, !issilicon(user)))
return FALSE
return TRUE

/datum/voice_announce/command/announce(snd)
set waitfor = 0
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 <span class='name'>[get_area_name(user, TRUE)]</span>.", "<span class='name'>[user.real_name]</span>", 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)
35 changes: 35 additions & 0 deletions code/datums/world_topic.dm
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,41 @@

return jointext(message, "")

// Plays a voice announcement, given the ID of a voice annoucnement datum and a filename of a file in the shared folder, among other things
/datum/world_topic/voice_announce
keyword = "voice_announce"
require_comms_key = TRUE

/datum/world_topic/voice_announce/Run(list/input)
var/datum/voice_announce/A = GLOB.voice_announce_list[input["voice_announce"]]
if(istype(A))
A.handle_announce(input["ogg_file"], input["uploaded_file"], input["ip"], text2num(input["duration"]))

// Cancels a voice announcement, given the ID of voice announcement datum, used if the user closes their browser window instead of uploading
/datum/world_topic/voice_announce_cancel
keyword = "voice_announce_cancel"
require_comms_key = TRUE

/datum/world_topic/voice_announce_cancel/Run(list/input)
var/datum/voice_announce/A = GLOB.voice_announce_list[input["voice_announce_cancel"]]
if(istype(A))
qdel(A)

// Queries information about a voice announcement.
/datum/world_topic/voice_announce_query
keyword = "voice_announce_query"
require_comms_key = TRUE

/datum/world_topic/voice_announce_query/Run(list/input)
. = list()
var/datum/voice_announce/A = GLOB.voice_announce_list[input["voice_announce_query"]]
if(istype(A))
A.was_queried = TRUE
.["exists"] = TRUE
.["is_ai"] = A.is_ai
else
.["exists"] = FALSE

/datum/world_topic/status
keyword = "status"

Expand Down
14 changes: 14 additions & 0 deletions code/game/machinery/computer/communications.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -481,6 +487,14 @@
SScommunications.make_announcement(user, is_ai, input)
deadchat_broadcast(" made a priority announcement from <span class='name'>[get_area_name(usr, TRUE)]</span>.", "<span class='name'>[user.real_name]</span>", user)

/obj/machinery/computer/communications/proc/make_voice_announcement(mob/living/user)
if(!SScommunications.can_announce(user, FALSE))
to_chat(user, "<span class='alert'>Intercomms recharging. Please stand by.</span>")
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)
Expand Down
2 changes: 1 addition & 1 deletion code/modules/admin/sql_ban_system.dm
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@
output += "</div></div>"
//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 += "<div class='column'><label class='rolegroup [ckey(department)]'><input type='checkbox' name='[department]' class='hidden' [usr.client.prefs.tgui_fancy ? " onClick='toggle_checkboxes(this, \"_com\")'" : ""]>[department]</label><div class='content'>"
break_counter = 0
Expand Down
34 changes: 27 additions & 7 deletions code/modules/mob/living/silicon/ai/say.dm
Original file line number Diff line number Diff line change
Expand Up @@ -85,20 +85,40 @@
popup.set_content(dat)
popup.open()

/mob/living/silicon/ai/proc/voice_announce()
if(GLOB.announcing_vox > world.time)
to_chat(src, "<span class='notice'>Please wait [DisplayTimeText(GLOB.announcing_vox - world.time)].</span>")
return
if(incapacitated())
return
if(control_disabled)
to_chat(src, "<span class='warning'>Wireless interface disabled, unable to interact with announcement PA.</span>")
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, "<span class='notice'>Please wait [DisplayTimeText(announcing_vox - world.time)].</span>")
if(GLOB.announcing_vox > world.time)
to_chat(src, "<span class='notice'>Please wait [DisplayTimeText(GLOB.announcing_vox - world.time)].</span>")
return

var/list/types_list = list("Victor (male)", "Verity (female)", "Oscar (military)") //Victor is vox_sounds_male, Verity is vox_sounds, Oscar is vox_sounds_military
if(!is_banned_from(ckey, "Voice Announcements"))
types_list += "Use Microphone"
var/voxType = input(src, "Which voice?", "VOX") in types_list

if(voxType == "Use Microphone")
voice_announce()
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

last_announcement = message

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())
Expand Down Expand Up @@ -130,7 +150,7 @@
to_chat(src, "<span class='notice'>These words are not available on the announcement system: [english_list(incorrect_words)].</span>")
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].")

Expand Down
5 changes: 4 additions & 1 deletion config/private_default.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Loading