mirror of
https://github.com/VOREStation/VOREStation.git
synced 2026-07-22 04:28:33 +01:00
Extends entity narrate functionality with TGUI
*Adds all the necessary procs for TGUI functionality as backend *Creates EntityNarrate.tsx *Extends entity_narrate datum with helper vars for TGUI *TGUI window now allows: * * Multi Selection * * Single Selection * * It shows details if single * * It shows how many if multi * * Allows picking between subtle/loud * * Allows picking between talk/emote * * Allows sending messages every 0.5 seconds * * Uses new textbox feature to make editing and tracking narration easy!
This commit is contained in:
@@ -7,6 +7,20 @@
|
||||
var/list/entity_refs = list()
|
||||
|
||||
|
||||
//TGUI Helper Vars
|
||||
var/tgui_id = "EntityNarrate"
|
||||
var/tgui_selection_mode = 0 //0 for single entity, 1 for multi entity
|
||||
var/tgui_selected_name = "" //String for single selection in-game name
|
||||
var/tgui_selected_type = "" //String for single selection type
|
||||
var/tgui_selected_id = "" //String to retrieve ref from entity_refs
|
||||
var/tgui_selected_refs //object references
|
||||
var/list/tgui_selected_id_multi = list() //List of strings containing mob ids for multi selection
|
||||
var/tgui_narrate_mode = 0 //0 for speak, 1 for emote
|
||||
var/tgui_narrate_privacy = 0 //0 for loud, 1 for subtle
|
||||
var/tgui_last_message = 0 // int to avoid spam
|
||||
|
||||
|
||||
|
||||
|
||||
//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.
|
||||
@@ -98,13 +112,17 @@
|
||||
|
||||
|
||||
//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)
|
||||
var/choices = holder.entity_names + "Open TGUI"
|
||||
var/which_entity = tgui_input_list(usr, "Choose which mob to narrate", "Narrate mob", choices, 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)
|
||||
if(which_entity == "Open TGUI")
|
||||
holder.tgui_interact(usr)
|
||||
else
|
||||
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)
|
||||
@@ -165,3 +183,123 @@
|
||||
our_entity.visible_message("<b>[our_entity.name]</b> [message]")
|
||||
else
|
||||
return
|
||||
|
||||
|
||||
/datum/entity_narrate/tgui_state(mob/user)
|
||||
return GLOB.tgui_admin_state
|
||||
|
||||
/datum/entity_narrate/tgui_interact(mob/user, datum/tgui/ui)
|
||||
ui = SStgui.try_update_ui(user, src, ui)
|
||||
if(!ui)
|
||||
ui = new(user, src, tgui_id, "Entity Narration")
|
||||
ui.open()
|
||||
|
||||
/datum/entity_narrate/tgui_data(mob/user)
|
||||
var/list/data = list()
|
||||
data["mode_select"] = tgui_narrate_mode
|
||||
data["privacy_select"] = tgui_narrate_privacy
|
||||
data["selected_id"] = tgui_selected_id
|
||||
data["selected_name"] = tgui_selected_name
|
||||
data["selected_type"] = tgui_selected_type
|
||||
data["selection_mode"] = tgui_selection_mode
|
||||
data["multi_id_selection"] = tgui_selected_id_multi
|
||||
data["number_mob_selected"] = LAZYLEN(tgui_selected_id_multi)
|
||||
data["entity_names"] = entity_names
|
||||
|
||||
return data
|
||||
|
||||
/datum/entity_narrate/tgui_act(action, list/params)
|
||||
. = ..()
|
||||
|
||||
if(.) return
|
||||
if(!check_rights_for(usr.client, R_FUN)) return
|
||||
|
||||
switch(action)
|
||||
if("change_mode_multi")
|
||||
tgui_selection_mode = !tgui_selection_mode
|
||||
//Clearing selections after switching mode
|
||||
tgui_selected_id_multi = list()
|
||||
tgui_selected_id = ""
|
||||
tgui_selected_type = ""
|
||||
tgui_selected_name = ""
|
||||
tgui_selected_refs = null
|
||||
if("change_mode_privacy")
|
||||
tgui_narrate_privacy = !tgui_narrate_privacy
|
||||
if("change_mode_narration")
|
||||
tgui_narrate_mode = !tgui_narrate_mode
|
||||
if("select_entity")
|
||||
if(tgui_selection_mode)
|
||||
if(params["id_selected"] in tgui_selected_id_multi)
|
||||
tgui_selected_id_multi -= params["id_selected"]
|
||||
else
|
||||
tgui_selected_id_multi += params["id_selected"]
|
||||
else
|
||||
if(params["id_selected"] in tgui_selected_id_multi)
|
||||
tgui_selected_id_multi -= params["id_selected"]
|
||||
tgui_selected_id = ""
|
||||
tgui_selected_type = ""
|
||||
tgui_selected_name = ""
|
||||
tgui_selected_refs = null
|
||||
else
|
||||
tgui_selected_id_multi = list() //Using the same var for ease of implementation. Thus, we must reset to empty each time.
|
||||
tgui_selected_id_multi += params["id_selected"]
|
||||
tgui_selected_id = params["id_selected"]
|
||||
tgui_selected_refs = entity_refs[tgui_selected_id]
|
||||
if(istype(tgui_selected_refs, /mob/living))
|
||||
var/mob/living/L = tgui_selected_refs
|
||||
if(L.client)
|
||||
tgui_selected_type = "!!!!PLAYER!!!!"
|
||||
tgui_selected_name = L.name
|
||||
else
|
||||
tgui_selected_type = L.type
|
||||
tgui_selected_name = L.name
|
||||
else if(istype(tgui_selected_refs, /atom))
|
||||
var/atom/A = tgui_selected_refs
|
||||
tgui_selected_type = A.type
|
||||
tgui_selected_name = A.name
|
||||
if("narrate")
|
||||
if(world.time < (tgui_last_message + 0.5 SECONDS))
|
||||
to_chat(usr, SPAN_NOTICE("You can't messages that quickly! Wait at least half a second"))
|
||||
else
|
||||
to_chat(usr, SPAN_NOTICE("Message successfully sent!"))
|
||||
tgui_last_message = world.time
|
||||
var/message = sanitize(params["message"])
|
||||
if(tgui_selection_mode)
|
||||
for(var/entity in tgui_selected_id_multi)
|
||||
var/ref = entity_refs[entity]
|
||||
if(istype(ref, /mob/living))
|
||||
var/mob/living/L = ref
|
||||
if(L.client) continue //Skipping over mobs with players
|
||||
narrate_tgui_mob(L, message)
|
||||
else if(istype(ref, /atom))
|
||||
var/atom/A = ref
|
||||
narrate_tgui_atom(A, message)
|
||||
else
|
||||
var/ref = entity_refs[tgui_selected_id]
|
||||
if(istype(ref, /mob/living))
|
||||
var/mob/living/L = ref
|
||||
if(!L.client)
|
||||
narrate_tgui_mob(L, message)
|
||||
else if(istype(ref, /atom))
|
||||
var/atom/A = ref
|
||||
narrate_tgui_atom(A, message)
|
||||
|
||||
/datum/entity_narrate/proc/narrate_tgui_mob(mob/living/L, message as text)
|
||||
if(tgui_narrate_mode && tgui_narrate_privacy)
|
||||
L.custom_emote_vr(m_type = VISIBLE_MESSAGE, message = message)
|
||||
else if(tgui_narrate_mode && !tgui_narrate_privacy)
|
||||
L.custom_emote(VISIBLE_MESSAGE, message)
|
||||
else if(!tgui_narrate_mode && tgui_narrate_privacy)
|
||||
L.say(message, whispering = 1)
|
||||
else if(!tgui_narrate_mode && !tgui_narrate_privacy)
|
||||
L.say(message)
|
||||
|
||||
/datum/entity_narrate/proc/narrate_tgui_atom(atom/A, message as text)
|
||||
if(tgui_narrate_mode && tgui_narrate_privacy)
|
||||
A.visible_message("<i><b>[A.name]</b> [message]</i>", range = 1)
|
||||
else if(tgui_narrate_mode && !tgui_narrate_privacy)
|
||||
A.visible_message("<b>[A.name]</b> [message]",)
|
||||
else if(!tgui_narrate_mode && tgui_narrate_privacy)
|
||||
A.audible_message("<i><b>[A.name]</b> [message]</i>", hearing_distance = 1)
|
||||
else if(!tgui_narrate_mode && !tgui_narrate_privacy)
|
||||
A.audible_message("<b>[A.name]</b> [message]")
|
||||
|
||||
@@ -0,0 +1,182 @@
|
||||
import { BooleanLike } from 'common/react';
|
||||
import { useBackend, useLocalState } from '../backend';
|
||||
import { Flex, Tabs, Section, Button, Box, TextArea, Divider } from '../components';
|
||||
import { Window } from '../layouts';
|
||||
|
||||
type data = {
|
||||
mode_select: BooleanLike; // Data for emote/talk. 0 for talk, 1 for emote
|
||||
privacy_select: BooleanLike; // Data for subtle/loud, 0 for loud, 1 for subtle
|
||||
selection_mode: BooleanLike; // Data for whether we multiselect entities, 0 for single, 1 for multi
|
||||
number_mob_selected: number; // Data to display on how many we selected
|
||||
entity_names: string[]; // List of IDs to populate selection
|
||||
selected_id: string; // To be used for single-mode details view
|
||||
selected_name: string; // To be used for single-mode details view
|
||||
selected_type: string; // To be used for single-mode details view
|
||||
multi_id_selection: string[]; // To be used to highlight selection, and multi-use narrate
|
||||
};
|
||||
|
||||
export const EntityNarrate = (props, context) => {
|
||||
const { act, data } = useBackend<data>(context);
|
||||
return (
|
||||
<Window width={800} height={470} theme="abstract" resizable>
|
||||
<Window.Content scrollable>
|
||||
<Section>
|
||||
<Flex>
|
||||
<Flex.Item scrollable grow={2} fill>
|
||||
<Section scrollable>
|
||||
<EntitySelection />
|
||||
</Section>
|
||||
</Flex.Item>
|
||||
<Flex.Item grow={0.25} fill>
|
||||
<Divider vertical />
|
||||
</Flex.Item>
|
||||
<Flex.Item grow={6.75} fill>
|
||||
<Section>
|
||||
<Flex direction="column" justify="space-between">
|
||||
<Flex.Item Flex>
|
||||
<Section title="Details">
|
||||
<DisplayDetails />
|
||||
</Section>
|
||||
</Flex.Item>
|
||||
<Flex.Item Flex>
|
||||
<Section title="Select Behaviour">
|
||||
<ModeSelector />
|
||||
</Section>
|
||||
</Flex.Item>
|
||||
<Flex.Item Flex>
|
||||
<NarrationInput />
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
</Section>
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
</Section>
|
||||
</Window.Content>
|
||||
</Window>
|
||||
);
|
||||
};
|
||||
|
||||
// Selects entity from a vertical list, with mode to allow multiple selections.
|
||||
// Clicking the tab again removes it
|
||||
export const EntitySelection = (props, context) => {
|
||||
const { act, data } = useBackend<data>(context);
|
||||
const { selection_mode, multi_id_selection, entity_names } = data;
|
||||
return (
|
||||
<Flex direction="column" grow>
|
||||
<Flex.Item>
|
||||
<Section
|
||||
title="Choose!"
|
||||
buttons={
|
||||
<Button
|
||||
selected={selection_mode}
|
||||
fill
|
||||
content="Multi-Selection"
|
||||
onClick={() => act('change_mode_multi')}
|
||||
/>
|
||||
}>
|
||||
<Tabs vertical>
|
||||
{entity_names.map((name) => (
|
||||
<Tabs.Tab
|
||||
key={name}
|
||||
selected={multi_id_selection.includes(name)}
|
||||
onClick={() => act('select_entity', { id_selected: name })}>
|
||||
<Box inline>{name}</Box>
|
||||
</Tabs.Tab>
|
||||
))}
|
||||
</Tabs>
|
||||
</Section>
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export const DisplayDetails = (props, context) => {
|
||||
const { act, data } = useBackend<data>(context);
|
||||
const {
|
||||
selection_mode,
|
||||
number_mob_selected,
|
||||
selected_id,
|
||||
selected_name,
|
||||
selected_type,
|
||||
} = data;
|
||||
if (selection_mode) {
|
||||
return (
|
||||
<Box>
|
||||
<b>Number of entities selected:</b> {number_mob_selected}
|
||||
</Box>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<Box>
|
||||
<b>Selected ID:</b> {selected_id} <br />
|
||||
<b>Selected Name:</b> {selected_name} <br />
|
||||
<b>Selected Type:</b> {selected_type} <br />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export const ModeSelector = (props, context) => {
|
||||
const { act, data } = useBackend<data>(context);
|
||||
const { privacy_select, mode_select } = data;
|
||||
|
||||
return (
|
||||
<Flex direction="row">
|
||||
<Flex.Item grow>
|
||||
<Button
|
||||
onClick={() => act('change_mode_privacy')}
|
||||
selected={privacy_select}
|
||||
fluid
|
||||
tooltip={
|
||||
'This button changes whether your narration is loud (any who see/hear) or subtle (range of 1 tile)' +
|
||||
' ' +
|
||||
(privacy_select
|
||||
? 'Click here to disable subtle mode'
|
||||
: 'Click here to enable subtle mode')
|
||||
}
|
||||
content={privacy_select ? 'Currently: Subtle' : 'Currently: Loud'}
|
||||
/>
|
||||
</Flex.Item>
|
||||
<Flex.Item grow>
|
||||
<Button
|
||||
onClick={() => act('change_mode_narration')}
|
||||
selected={mode_select}
|
||||
fluid
|
||||
tooltip={
|
||||
'This button sets your narration to talk audiably or emote visibly' +
|
||||
' ' +
|
||||
(mode_select
|
||||
? 'Click here to emote visibly.'
|
||||
: 'Click here to talk audiably.')
|
||||
}
|
||||
content={mode_select ? 'Currently: Emoting' : 'Currently: Talking'}
|
||||
/>
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export const NarrationInput = (props, context) => {
|
||||
const { act, data } = useBackend<data>(context);
|
||||
const [narration, setNarration] = useLocalState(context, 'narration', '');
|
||||
return (
|
||||
<Section
|
||||
title="Narration Text"
|
||||
buttons={
|
||||
<Button
|
||||
onClick={() => act('narrate', { message: narration })}
|
||||
content="Send Narration"
|
||||
/>
|
||||
}>
|
||||
<Flex>
|
||||
<Flex.Item width="85%">
|
||||
<TextArea
|
||||
height={'18rem'}
|
||||
onChange={(e, val) => setNarration(val)}
|
||||
value={narration || ''}
|
||||
/>
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user