@@ -84,7 +84,7 @@
|
||||
if(..())
|
||||
return
|
||||
var/mob/living/silicon/ai/AI = usr
|
||||
AI.announcement()
|
||||
AI.ai_announcement()
|
||||
|
||||
/atom/movable/screen/ai/call_shuttle
|
||||
name = "Call Emergency Shuttle"
|
||||
|
||||
@@ -974,10 +974,11 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
|
||||
addtimer(CALLBACK(SSassets.transport, TYPE_PROC_REF(/datum/asset_transport, send_assets_slow), src, SSassets.transport.preload), 5 SECONDS)
|
||||
|
||||
#if (PRELOAD_RSC == 0)
|
||||
for (var/name in GLOB.vox_sounds)
|
||||
var/file = GLOB.vox_sounds[name]
|
||||
Export("##action=load_rsc", file)
|
||||
stoplag()
|
||||
for (var/type in GLOB.vox_types)
|
||||
for(var/word in GLOB.vox_types[type])
|
||||
var/file = GLOB.vox_types[type][word]
|
||||
Export("##action=load_rsc", file)
|
||||
stoplag()
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
@@ -226,15 +226,22 @@
|
||||
spawn_flags = IC_SPAWN_RESEARCH
|
||||
|
||||
/obj/item/integrated_circuit/output/sound/vox
|
||||
name = "ai vox sound circuit"
|
||||
name = "Female ai vox sound circuit"
|
||||
desc = "Takes a sound name as an input, and will play said sound when pulsed. This circuit is often found in AI announcement systems."
|
||||
spawn_flags = IC_SPAWN_RESEARCH
|
||||
var/voice_type = "Female"
|
||||
|
||||
/obj/item/integrated_circuit/output/sound/vox/Initialize(mapload)
|
||||
.= ..()
|
||||
sounds = GLOB.vox_sounds
|
||||
. = ..()
|
||||
sounds = GLOB.vox_types[voice_type]
|
||||
extended_desc = "The first input pin determines which sound is used. It uses the AI Vox Broadcast word list. So either experiment to find words that work, or ask the AI to help in figuring them out. The second pin determines the volume of sound that is played, and the third determines if the frequency of the sound will vary with each activation."
|
||||
|
||||
/obj/item/integrated_circuit/output/sound/vox/male
|
||||
name = "Male ai vox sound circuit"
|
||||
desc = "Takes a sound name as an input, and will play said sound when pulsed. This circuit is often found in AI announcement systems."
|
||||
spawn_flags = IC_SPAWN_RESEARCH
|
||||
voice_type = "Male"
|
||||
|
||||
/obj/item/integrated_circuit/output/text_to_speech
|
||||
name = "text-to-speech circuit"
|
||||
desc = "Takes any string as an input and will make the device say the string when pulsed."
|
||||
|
||||
@@ -104,6 +104,10 @@
|
||||
// TODO: Currently unused, needs port from TG.
|
||||
/// Station alert datum for showing alerts UI
|
||||
var/datum/station_alert/alert_control
|
||||
/// Announcement UI
|
||||
var/datum/ai_announcement/ai_announcement
|
||||
/// Lists possible spoken words for announcements
|
||||
var/datum/announcement_help/announcement_help
|
||||
///remember AI's last location
|
||||
var/atom/lastloc
|
||||
interaction_range = INFINITY
|
||||
@@ -204,6 +208,8 @@
|
||||
QDEL_NULL(doomsday_device)
|
||||
QDEL_NULL(robot_control)
|
||||
// QDEL_NULL(alert_control)
|
||||
QDEL_NULL(ai_announcement)
|
||||
QDEL_NULL(announcement_help)
|
||||
QDEL_NULL(aiMulti)
|
||||
QDEL_NULL(aiPDA)
|
||||
malfhack = null
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
#ifdef AI_VOX
|
||||
/mob/living/silicon/ai/verb/ai_announcement()
|
||||
set name = "Vox Announcement"
|
||||
set desc = "Make an audible announcement!"
|
||||
set category = "AI Commands"
|
||||
|
||||
if(incapacitated())
|
||||
return
|
||||
|
||||
if(!ai_announcement)
|
||||
ai_announcement = new(src)
|
||||
|
||||
ai_announcement.ui_interact(src)
|
||||
|
||||
/datum/ai_announcement
|
||||
var/mob/living/silicon/ai/owner
|
||||
|
||||
/datum/ai_announcement/New(mob/living/silicon/ai/new_owner)
|
||||
if(!istype(new_owner))
|
||||
qdel(src)
|
||||
owner = new_owner
|
||||
|
||||
/datum/ai_announcement/ui_status(mob/living/silicon/ai/user)
|
||||
if(owner == user && !owner.incapacitated())
|
||||
return ..()
|
||||
return UI_CLOSE
|
||||
|
||||
/datum/ai_announcement/ui_state(mob/user)
|
||||
return GLOB.always_state
|
||||
|
||||
/datum/ai_announcement/ui_interact(mob/user, datum/tgui/ui)
|
||||
ui = SStgui.try_update_ui(user, src, ui)
|
||||
if(!ui)
|
||||
ui = new(user, src, "AIAnnouncement")
|
||||
ui.open()
|
||||
|
||||
/datum/ai_announcement/ui_data(mob/user)
|
||||
var/list/data = ..()
|
||||
data["last_announcement"] = owner.last_announcement
|
||||
return data
|
||||
|
||||
/datum/ai_announcement/ui_static_data(mob/user)
|
||||
var/list/data = ..()
|
||||
data["vox_types"] = GLOB.vox_types
|
||||
return data
|
||||
|
||||
/datum/ai_announcement/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
|
||||
. = ..()
|
||||
switch(action)
|
||||
if("announce")
|
||||
var/vox_type = params["vox_type"]
|
||||
if(!vox_type)
|
||||
return
|
||||
var/to_speak = params["to_speak"]
|
||||
if(!to_speak)
|
||||
return
|
||||
owner.announcement(vox_type, to_speak)
|
||||
ui.close()
|
||||
#endif
|
||||
@@ -0,0 +1,54 @@
|
||||
#ifdef AI_VOX
|
||||
/mob/living/silicon/ai/verb/announcement_help()
|
||||
|
||||
set name = "Announcement Help"
|
||||
set desc = "Display a list of vocal words to announce to the crew."
|
||||
set category = "AI Commands"
|
||||
|
||||
if(incapacitated())
|
||||
return
|
||||
|
||||
if(!announcement_help)
|
||||
announcement_help = new(src)
|
||||
|
||||
announcement_help.ui_interact(src)
|
||||
|
||||
/datum/announcement_help
|
||||
var/mob/living/silicon/ai/owner
|
||||
|
||||
/datum/announcement_help/New(mob/living/silicon/ai/new_owner)
|
||||
if(!istype(new_owner))
|
||||
qdel(src)
|
||||
owner = new_owner
|
||||
|
||||
/datum/announcement_help/ui_status(mob/living/silicon/ai/user)
|
||||
if(owner == user && !owner.incapacitated())
|
||||
return ..()
|
||||
return UI_CLOSE
|
||||
|
||||
/datum/announcement_help/ui_state(mob/user)
|
||||
return GLOB.always_state
|
||||
|
||||
/datum/announcement_help/ui_interact(mob/user, datum/tgui/ui)
|
||||
ui = SStgui.try_update_ui(user, src, ui)
|
||||
if(!ui)
|
||||
ui = new(user, src, "AnnouncementHelp")
|
||||
ui.open()
|
||||
|
||||
/datum/announcement_help/ui_static_data(mob/user)
|
||||
var/list/data = ..()
|
||||
data["vox_types"] = GLOB.vox_types
|
||||
return data
|
||||
|
||||
/datum/announcement_help/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
|
||||
. = ..()
|
||||
switch(action)
|
||||
if("say_word")
|
||||
var/vox_type = params["vox_type"]
|
||||
if(!vox_type)
|
||||
return
|
||||
var/to_speak = params["to_speak"]
|
||||
if(!to_speak)
|
||||
return
|
||||
play_vox_word(to_speak, null, owner, vox_type)
|
||||
#endif
|
||||
@@ -79,59 +79,23 @@
|
||||
|
||||
// Make sure that the code compiles with AI_VOX undefined
|
||||
#ifdef AI_VOX
|
||||
#define VOX_DELAY 600
|
||||
/mob/living/silicon/ai/verb/announcement_help()
|
||||
|
||||
set name = "Announcement Help"
|
||||
set desc = "Display a list of vocal words to announce to the crew."
|
||||
set category = "AI Commands"
|
||||
|
||||
if(incapacitated())
|
||||
return
|
||||
|
||||
var/dat = {"
|
||||
<font class='bad'>WARNING:</font> Misuse of the announcement system will get you job banned.<BR><BR>
|
||||
Here is a list of words you can type into the 'Announcement' button to create sentences to vocally announce to everyone on the same level at you.<BR>
|
||||
<UL><LI>You can also click on the word to PREVIEW it.</LI>
|
||||
<LI>You can only say 30 words for every announcement.</LI>
|
||||
<LI>Do not use punctuation as you would normally, if you want a pause you can use the full stop and comma characters by separating them with spaces, like so: 'Alpha . Test , Bravo'.</LI>
|
||||
<LI>Numbers are in word format, e.g. eight, sixty, etc </LI>
|
||||
<LI>Sound effects begin with an 's' before the actual word, e.g. scensor</LI>
|
||||
<LI>Use Ctrl+F to see if a word exists in the list.</LI></UL><HR>
|
||||
"}
|
||||
|
||||
var/index = 0
|
||||
for(var/word in GLOB.vox_sounds)
|
||||
index++
|
||||
dat += "<A href='?src=[REF(src)];say_word=[word]'>[capitalize(word)]</A>"
|
||||
if(index != GLOB.vox_sounds.len)
|
||||
dat += " / "
|
||||
|
||||
var/datum/browser/popup = new(src, "announce_help", "Announcement Help", 500, 400)
|
||||
popup.set_content(dat)
|
||||
popup.open()
|
||||
|
||||
|
||||
/mob/living/silicon/ai/proc/announcement()
|
||||
#define VOX_DELAY 1 MINUTES
|
||||
/mob/living/silicon/ai/proc/announcement(voxType = "Female", message)
|
||||
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>")
|
||||
to_chat(src, span_notice("Please wait [DisplayTimeText(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
|
||||
|
||||
last_announcement = message
|
||||
|
||||
var/voxType = input(src, "Male or female VOX?", "VOX-gender") in list("male", "female")
|
||||
|
||||
if(!message || announcing_vox > world.time)
|
||||
if(!message || !GLOB.vox_types[voxType])
|
||||
return
|
||||
|
||||
if(incapacitated())
|
||||
return
|
||||
|
||||
if(control_disabled)
|
||||
to_chat(src, "<span class='warning'>Wireless interface disabled, unable to interact with announcement PA.</span>")
|
||||
to_chat(src, span_warning("Wireless interface disabled, unable to interact with announcement PA."))
|
||||
return
|
||||
|
||||
var/list/words = splittext(trim(message), " ")
|
||||
@@ -145,13 +109,11 @@
|
||||
if(!word)
|
||||
words -= word
|
||||
continue
|
||||
if(!GLOB.vox_sounds[word] && voxType == "female")
|
||||
incorrect_words += word
|
||||
if(!GLOB.vox_sounds_male[word] && voxType == "male")
|
||||
if(!GLOB.vox_types[voxType][word])
|
||||
incorrect_words += word
|
||||
|
||||
if(incorrect_words.len)
|
||||
to_chat(src, "<span class='notice'>These words are not available on the announcement system: [english_list(incorrect_words)].</span>")
|
||||
to_chat(src, span_notice("These words are not available on the announcement system: [english_list(incorrect_words)]."))
|
||||
return
|
||||
|
||||
announcing_vox = world.time + VOX_DELAY
|
||||
@@ -162,18 +124,12 @@
|
||||
play_vox_word(word, src.z, null, voxType)
|
||||
|
||||
|
||||
/proc/play_vox_word(word, z_level, mob/only_listener, voxType = "female")
|
||||
/proc/play_vox_word(word, z_level, mob/only_listener, voxType = "Female")
|
||||
|
||||
word = lowertext(word)
|
||||
|
||||
if( (GLOB.vox_sounds[word] && voxType == "female") || (GLOB.vox_sounds_male[word] && voxType == "male") )
|
||||
|
||||
var/sound_file
|
||||
|
||||
if(voxType == "female")
|
||||
sound_file = GLOB.vox_sounds[word]
|
||||
else
|
||||
sound_file = GLOB.vox_sounds_male[word]
|
||||
var/sound_file = LAZYACCESSASSOC(GLOB.vox_types, voxType, word)
|
||||
if(sound_file)
|
||||
var/sound/voice = sound(sound_file, wait = 1, channel = CHANNEL_VOX)
|
||||
voice.status = SOUND_STREAM
|
||||
|
||||
|
||||
@@ -5,9 +5,11 @@
|
||||
// Regex for collecting a list of ogg files
|
||||
// (([a-zA-Z,.]+)\.ogg)
|
||||
|
||||
// For vim
|
||||
// :%s/\(\(.*\)\.ogg\)/"\2" = 'sound\/vox_fem\/\1',/g
|
||||
GLOBAL_LIST_INIT(vox_sounds, list("abduction" = 'sound/vox_fem/abduction.ogg',
|
||||
GLOBAL_LIST_INIT(vox_types, list(
|
||||
// For vim
|
||||
// :%s/\(\(.*\)\.ogg\)/"\2" = 'sound\/vox_fem\/\1',/g
|
||||
"Female" = list(
|
||||
"abduction" = 'sound/vox_fem/abduction.ogg',
|
||||
"abortions" = 'sound/vox_fem/abortions.ogg',
|
||||
"above" = 'sound/vox_fem/above.ogg',
|
||||
"abstain" = 'sound/vox_fem/abstain.ogg',
|
||||
@@ -974,11 +976,11 @@ GLOBAL_LIST_INIT(vox_sounds, list("abduction" = 'sound/vox_fem/abduction.ogg',
|
||||
"z" = 'sound/vox_fem/z.ogg',
|
||||
"zombie" = 'sound/vox_fem/zombie.ogg',
|
||||
"zone" = 'sound/vox_fem/zone.ogg',
|
||||
"zulu" = 'sound/vox_fem/zulu.ogg'))
|
||||
|
||||
//for vim
|
||||
// :%s/\(\(.*\)\.ogg\)/"\2" = 'sound\/vox\/\1',/g
|
||||
GLOBAL_LIST_INIT(vox_sounds_male, list("," = 'sound/vox/_comma.ogg',
|
||||
"zulu" = 'sound/vox_fem/zulu.ogg'),
|
||||
//for vim
|
||||
// :%s/\(\(.*\)\.ogg\)/"\2" = 'sound\/vox\/\1',/g
|
||||
"Male" = list(
|
||||
"," = 'sound/vox/_comma.ogg',
|
||||
"." = 'sound/vox/_period.ogg',
|
||||
"a" = 'sound/vox/a.ogg',
|
||||
"accelerating" = 'sound/vox/accelerating.ogg',
|
||||
@@ -1605,5 +1607,6 @@ GLOBAL_LIST_INIT(vox_sounds_male, list("," = 'sound/vox/_comma.ogg',
|
||||
"yourself" = 'sound/vox/yourself.ogg',
|
||||
"zero" = 'sound/vox/zero.ogg',
|
||||
"zone" = 'sound/vox/zone.ogg',
|
||||
"zulu" = 'sound/vox/zulu.ogg',))
|
||||
"zulu" = 'sound/vox/zulu.ogg',
|
||||
)))
|
||||
#endif
|
||||
|
||||
@@ -2830,8 +2830,10 @@
|
||||
#include "code\modules\mob\living\silicon\silicon_defense.dm"
|
||||
#include "code\modules\mob\living\silicon\silicon_movement.dm"
|
||||
#include "code\modules\mob\living\silicon\ai\ai.dm"
|
||||
#include "code\modules\mob\living\silicon\ai\ai_announcement.dm"
|
||||
#include "code\modules\mob\living\silicon\ai\ai_defense.dm"
|
||||
#include "code\modules\mob\living\silicon\ai\ai_portrait_picker.dm"
|
||||
#include "code\modules\mob\living\silicon\ai\announcement_help.dm"
|
||||
#include "code\modules\mob\living\silicon\ai\death.dm"
|
||||
#include "code\modules\mob\living\silicon\ai\emote.dm"
|
||||
#include "code\modules\mob\living\silicon\ai\examine.dm"
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
import { filter } from 'common/collections';
|
||||
import { useBackend } from '../backend';
|
||||
import { Button, Icon, Input, Section, Stack, Tabs } from '../components';
|
||||
import { useLocalState } from '../backend';
|
||||
import { Window } from '../layouts';
|
||||
|
||||
export const AIAnnouncement = (props, context) => {
|
||||
const { act, data } = useBackend(context);
|
||||
const {
|
||||
last_announcement,
|
||||
vox_types = {},
|
||||
} = data;
|
||||
|
||||
const [
|
||||
current_page,
|
||||
set_page,
|
||||
] = useLocalState(context, 'current_page', 0);
|
||||
|
||||
const [
|
||||
announcement_input,
|
||||
set_announcement_input,
|
||||
] = useLocalState(context, 'announcement_input', last_announcement);
|
||||
|
||||
// I love `Object`s!!
|
||||
const words_filtered = Object.keys(vox_types[Object.keys(vox_types)[current_page]]);
|
||||
|
||||
const search_split = announcement_input.split(" ").filter(element => element);
|
||||
|
||||
const missing_words = search_split.map(element => words_filtered.includes(element) ? null : element).filter(element => element);
|
||||
|
||||
return (
|
||||
<Window
|
||||
width={630}
|
||||
height={175}
|
||||
resizable
|
||||
title="Vox Announcement">
|
||||
<Window.Content>
|
||||
<Section fill overflow="auto">
|
||||
<Stack vertical>
|
||||
<Stack.Item>
|
||||
<font color="red">WARNING:</font> Misuse of this verb can result in you being job banned. More help is available in 'Announcement Help'
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Tabs fluid textAlign="center">
|
||||
{
|
||||
Object.keys(vox_types).map((vox_type, index) => (
|
||||
<Tabs.Tab
|
||||
key={index}
|
||||
onClick={() => set_page(index)}
|
||||
selected={current_page === index}>
|
||||
{vox_type}
|
||||
</Tabs.Tab>
|
||||
))
|
||||
}
|
||||
</Tabs>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Stack>
|
||||
<Stack.Item grow>
|
||||
<Input
|
||||
fluid
|
||||
value={last_announcement}
|
||||
placeholder="Announce something..."
|
||||
onInput={(e, value) => set_announcement_input(value)}
|
||||
onEnter={(e, value) => act("announce", {
|
||||
"vox_type": Object.keys(vox_types)[current_page],
|
||||
"to_speak": value,
|
||||
})}
|
||||
/>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Button icon="volume-high" mt={-0.2} onClick={() => act("announce", {
|
||||
"vox_type": Object.keys(vox_types)[current_page],
|
||||
"to_speak": announcement_input,
|
||||
})} />
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
{missing_words.length
|
||||
? <div>These words are not available on the announcement system: <font color="red">{missing_words.join(" ")}</font></div> : null}
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Section>
|
||||
</Window.Content>
|
||||
</Window>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,100 @@
|
||||
import { filter } from 'common/collections';
|
||||
import { flow } from 'common/fp';
|
||||
import { createSearch } from 'common/string';
|
||||
import { useBackend } from '../backend';
|
||||
import { Button, Collapsible, Icon, Input, Section, Stack, Tabs } from '../components';
|
||||
import { useLocalState } from '../backend';
|
||||
import { Window } from '../layouts';
|
||||
|
||||
export const AnnouncementHelp = (props, context) => {
|
||||
const { act, data } = useBackend(context);
|
||||
const { vox_types = {} } = data;
|
||||
|
||||
const [
|
||||
current_page,
|
||||
set_page,
|
||||
] = useLocalState(context, 'current_page', 0);
|
||||
|
||||
const [
|
||||
search_text,
|
||||
set_search_text,
|
||||
] = useLocalState(context, 'search_text', '');
|
||||
|
||||
// I love `Object`s!!
|
||||
const words_filtered = prepare_search(Object.keys(vox_types[Object.keys(vox_types)[current_page]]), search_text);
|
||||
|
||||
return (
|
||||
<Window
|
||||
width={500}
|
||||
height={500}
|
||||
resizable
|
||||
title="Announcement Help">
|
||||
<Window.Content>
|
||||
<Section fill overflow="auto">
|
||||
<Stack vertical>
|
||||
<Stack.Item>
|
||||
<Collapsible title="Help" textAlign="center">
|
||||
<font color="red">WARNING:</font> Misuse of the announcement system will get you job banned.<br /><br />
|
||||
Here is a list of words you can type into the 'Announcement' button to create sentences to vocally announce to everyone on the same level at you.<br />
|
||||
<ul><li>You can also click on the word to PREVIEW it.</li>
|
||||
<li>You can only say 30 words for every announcement.</li>
|
||||
<li>Do not use punctuation as you would normally, if you want a pause you can use the full stop and comma characters by separating them with spaces, like so: 'Alpha . Test , Bravo'.</li>
|
||||
<li>Numbers are in word format, e.g. eight, sixty, etc </li>
|
||||
<li>Sound effects begin with an 's' before the actual word, e.g. scensor</li>
|
||||
</ul><hr />
|
||||
</Collapsible>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Tabs fluid textAlign="center">
|
||||
{
|
||||
Object.keys(vox_types).map((vox_type, index) => (
|
||||
<Tabs.Tab
|
||||
key={index}
|
||||
onClick={() => set_page(index)}
|
||||
selected={current_page === index}>
|
||||
{vox_type}
|
||||
</Tabs.Tab>
|
||||
))
|
||||
}
|
||||
</Tabs>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Stack>
|
||||
<Stack.Item>
|
||||
<Icon name="search" mt={0.6} />
|
||||
</Stack.Item>
|
||||
<Stack.Item grow>
|
||||
<Input
|
||||
fluid
|
||||
placeholder="Find a word..."
|
||||
onInput={(e, value) => set_search_text(value)}
|
||||
/>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
{words_filtered.map(nestedKey => (
|
||||
<Button
|
||||
key={nestedKey}
|
||||
onClick={() => act("say_word", {
|
||||
"vox_type": Object.keys(vox_types)[current_page],
|
||||
"to_speak": nestedKey,
|
||||
})} >
|
||||
{nestedKey}
|
||||
</Button>
|
||||
))}
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Section>
|
||||
</Window.Content>
|
||||
</Window>
|
||||
);
|
||||
};
|
||||
|
||||
export const prepare_search = (words_filtered, search_text = '') => {
|
||||
const search = createSearch(search_text,
|
||||
word_soup => word_soup);
|
||||
return flow([
|
||||
search_text && filter(search),
|
||||
])(words_filtered);
|
||||
};
|
||||
Reference in New Issue
Block a user