mirror of
https://github.com/CHOMPStation2/CHOMPStation2.git
synced 2025-12-10 18:22:39 +00:00
@@ -6,3 +6,5 @@ GLOBAL_LIST_EMPTY(wire_color_directory) // This is an associative list with the
|
||||
|
||||
// Reference list for disposal sort junctions. Filled up by sorting junction's New()
|
||||
GLOBAL_LIST_EMPTY(tagger_locations)
|
||||
|
||||
GLOBAL_LIST_INIT(char_directory_tags, list("Pred", "Prey", "Switch", "Non-Vore", "Unset"))
|
||||
@@ -1,61 +1,130 @@
|
||||
GLOBAL_DATUM(character_directory, /datum/character_directory)
|
||||
|
||||
/client/verb/show_character_directory()
|
||||
set name = "Character Directory"
|
||||
set category = "OOC"
|
||||
set desc = "Shows a listing of all active characters, along with their associated OOC notes, flavor text, and more."
|
||||
|
||||
if(mob.next_move >= world.time) //This is primarily to stop malicious users from trying to lag the server by spamming this verb
|
||||
// This is primarily to stop malicious users from trying to lag the server by spamming this verb
|
||||
if(!usr.checkMoveCooldown())
|
||||
to_chat(usr, "<span class='warning'>Don't spam character directory refresh.</span>")
|
||||
return
|
||||
usr.setMoveCooldown(10)
|
||||
|
||||
mob.next_move = world.time + 10
|
||||
if(!GLOB.character_directory)
|
||||
GLOB.character_directory = new
|
||||
GLOB.character_directory.tgui_interact(mob)
|
||||
|
||||
var/html = "<script> function togglesection(targetsection) { var targettext = document.getElementById(targetsection); if (targettext.style.display === 'none') { targettext.style.display = ''; } else { targettext.style.display = 'none'; } } </script>"
|
||||
|
||||
var/curID = 0
|
||||
// This is a global singleton. Keep in mind that all operations should occur on usr, not src.
|
||||
/datum/character_directory
|
||||
/datum/character_directory/tgui_state(mob/user)
|
||||
return GLOB.tgui_always_state
|
||||
|
||||
/datum/character_directory/tgui_interact(mob/user, datum/tgui/ui, datum/tgui/parent_ui)
|
||||
ui = SStgui.try_update_ui(user, src, ui)
|
||||
if(!ui)
|
||||
ui = new(user, src, "CharacterDirectory", "Character Directory")
|
||||
ui.open()
|
||||
|
||||
/datum/character_directory/tgui_data(mob/user, datum/tgui/ui, datum/tgui_state/state)
|
||||
var/list/data = ..()
|
||||
|
||||
data["personalVisibility"] = user?.client?.prefs?.show_in_directory
|
||||
data["personalTag"] = user?.client?.prefs?.directory_tag
|
||||
|
||||
return data
|
||||
|
||||
/datum/character_directory/tgui_static_data(mob/user, datum/tgui/ui, datum/tgui_state/state)
|
||||
var/list/data = ..()
|
||||
|
||||
var/list/directory_mobs = list()
|
||||
for(var/client/C in GLOB.clients)
|
||||
if(C.prefs && !C.prefs.show_in_directory)
|
||||
// Allow opt-out.
|
||||
if(!C?.prefs?.show_in_directory)
|
||||
continue
|
||||
|
||||
// These are the three vars we're trying to find
|
||||
// The approach differs based on the mob the client is controlling
|
||||
var/name = null
|
||||
var/ooc_notes = null
|
||||
var/flavor_text = null
|
||||
var/tag = C.prefs.directory_tag
|
||||
var/character_ad = C.prefs.directory_ad
|
||||
|
||||
if(ishuman(C.mob))
|
||||
var/mob/living/carbon/human/H = C.mob
|
||||
if(data_core && data_core.general)
|
||||
if(!find_general_record("name", H.real_name))
|
||||
if(!find_record("name", H.real_name, data_core.hidden_general))
|
||||
continue
|
||||
curID++
|
||||
html += "<div class='block'>"
|
||||
html += "<h3 class='uiContent highlight' style='font-size:16px'>[H.real_name]</h3><br>"
|
||||
if(H.flavor_texts["general"])
|
||||
html += "<a onclick='togglesection(\"[ckey(H.real_name)] [curID] flavor\")'>Flavor text</a>"
|
||||
html += "<p class='uiContent' style='display:none' id='[ckey(H.real_name)] [curID] flavor'>[H.flavor_texts["general"]]</p>"
|
||||
if(H.ooc_notes)
|
||||
html += "<a onclick='togglesection(\"[ckey(H.real_name)] [curID] ooc\")'>OOC notes</a>"
|
||||
html += "<p class='uiContent' style='display:none' id='[ckey(H.real_name)] [curID] ooc'>[H.ooc_notes]</p>"
|
||||
html += "</div>"
|
||||
name = H.real_name
|
||||
ooc_notes = H.ooc_notes
|
||||
flavor_text = H.flavor_texts["general"]
|
||||
|
||||
if(isAI(C.mob))
|
||||
var/mob/living/silicon/ai/A = C.mob
|
||||
curID++
|
||||
html += "<div class='block'>"
|
||||
html += "<h3 class='uiContent highlight' style='font-size:16px'>[A.name] (Artificial Intelligence)</h3><br>"
|
||||
if(A.ooc_notes)
|
||||
html += "<a onclick='togglesection(\"[ckey(A.name)] [curID] ooc\")'>OOC notes</a>"
|
||||
html += "<p class='uiContent' style='display:none' id='[ckey(A.name)] [curID] ooc'>[A.ooc_notes]</p>"
|
||||
html += "</div>"
|
||||
name = "[A.name] (Artificial Intelligence)"
|
||||
ooc_notes = A.ooc_notes
|
||||
flavor_text = null // No flavor text for AIs :c
|
||||
|
||||
if(isrobot(C.mob))
|
||||
var/mob/living/silicon/robot/R = C.mob
|
||||
if(R.scrambledcodes || (R.module && R.module.hide_on_manifest))
|
||||
continue
|
||||
curID++
|
||||
html += "<div class='block'>"
|
||||
html += "<h3 class='uiContent highlight' style='font-size:16px'>[R.name] ([R.modtype] [R.braintype])</h3><br>"
|
||||
if(R.flavor_text)
|
||||
html += "<a onclick='togglesection(\"[ckey(R.name)] [curID] flavor\")'>Flavor text</a>"
|
||||
html += "<p class='uiContent' style='display:none' id='[ckey(R.name)] [curID] flavor'>[R.flavor_text]</p>"
|
||||
if(R.ooc_notes)
|
||||
html += "<a onclick='togglesection(\"[ckey(R.name)] [curID] ooc\")'>OOC notes</a>"
|
||||
html += "<p class='uiContent' style='display:none' id='[ckey(R.name)] [curID] ooc'>[R.ooc_notes]</p>"
|
||||
html += "</div>"
|
||||
if(!curID)
|
||||
html += "<p class='uiContent'>404: Station not found</p>"
|
||||
name = "[R.name] ([R.modtype] [R.braintype])"
|
||||
ooc_notes = R.ooc_notes
|
||||
flavor_text = R.flavor_text
|
||||
|
||||
var/datum/browser/popup = new(mob, "chardir", "Character Directory", 640, 480)
|
||||
popup.set_content(html)
|
||||
popup.open()
|
||||
// It's okay if we fail to find OOC notes and flavor text
|
||||
// But if we can't find the name, they must be using a non-compatible mob type currently.
|
||||
if(!name)
|
||||
continue
|
||||
|
||||
directory_mobs.Add(list(list(
|
||||
"name" = name,
|
||||
"ooc_notes" = ooc_notes,
|
||||
"tag" = tag,
|
||||
"character_ad" = character_ad,
|
||||
"flavor_text" = flavor_text,
|
||||
)))
|
||||
|
||||
data["directory"] = directory_mobs
|
||||
|
||||
return data
|
||||
|
||||
|
||||
/datum/character_directory/tgui_act(action, list/params, datum/tgui/ui, datum/tgui_state/state)
|
||||
. = ..()
|
||||
if(.)
|
||||
return
|
||||
|
||||
switch(action)
|
||||
if("refresh")
|
||||
// This is primarily to stop malicious users from trying to lag the server by spamming this verb
|
||||
if(!usr.checkMoveCooldown())
|
||||
to_chat(usr, "<span class='warning'>Don't spam character directory refresh.</span>")
|
||||
return
|
||||
usr.setMoveCooldown(10)
|
||||
update_tgui_static_data(usr, ui)
|
||||
return TRUE
|
||||
if("setTag")
|
||||
var/list/new_tag = input(usr, "Pick a new tag for the character directory", "Character Tag", usr?.client?.prefs?.directory_tag) as null|anything in GLOB.char_directory_tags
|
||||
if(!new_tag)
|
||||
return
|
||||
usr?.client?.prefs?.directory_tag = new_tag
|
||||
return TRUE
|
||||
if("setVisible")
|
||||
usr?.client?.prefs?.show_in_directory = !usr?.client?.prefs?.show_in_directory
|
||||
to_chat(usr, "<span class='notice'>You are now [usr.client.prefs.show_in_directory ? "shown" : "not shown"] in the directory.</span>")
|
||||
return TRUE
|
||||
if("editAd")
|
||||
if(!usr?.client?.prefs)
|
||||
return
|
||||
|
||||
var/current_ad = usr.client.prefs.directory_ad
|
||||
var/new_ad = sanitize(input(usr, "Change your character ad", "Character Ad", current_ad) as message|null, extra = 0)
|
||||
if(isnull(new_ad))
|
||||
return
|
||||
usr.client.prefs.directory_ad = new_ad
|
||||
return TRUE
|
||||
@@ -6,10 +6,14 @@
|
||||
|
||||
/datum/category_item/player_setup_item/vore/misc/load_character(var/savefile/S)
|
||||
S["show_in_directory"] >> pref.show_in_directory
|
||||
S["directory_tag"] >> pref.directory_tag
|
||||
S["directory_ad"] >> pref.directory_ad
|
||||
S["sensorpref"] >> pref.sensorpref //TFF 5/8/19 - add sensor pref setting to load after saved
|
||||
|
||||
/datum/category_item/player_setup_item/vore/misc/save_character(var/savefile/S)
|
||||
S["show_in_directory"] << pref.show_in_directory
|
||||
S["directory_tag"] << pref.directory_tag
|
||||
S["directory_ad"] << pref.directory_ad
|
||||
S["sensorpref"] << pref.sensorpref //TFF 5/8/19 - add sensor pref setting to be saveable
|
||||
|
||||
//TFF 5/8/19 - add new datum category to allow for setting multiple settings when this is selected in the loadout.
|
||||
@@ -20,17 +24,30 @@
|
||||
|
||||
/datum/category_item/player_setup_item/vore/misc/sanitize_character()
|
||||
pref.show_in_directory = sanitize_integer(pref.show_in_directory, 0, 1, initial(pref.show_in_directory))
|
||||
pref.directory_tag = sanitize_inlist(pref.directory_tag, GLOB.char_directory_tags, initial(pref.directory_tag))
|
||||
pref.sensorpref = sanitize_integer(pref.sensorpref, 1, sensorpreflist.len, initial(pref.sensorpref)) //TFF - 5/8/19 - add santisation for sensor prefs
|
||||
|
||||
/datum/category_item/player_setup_item/vore/misc/content(var/mob/user)
|
||||
. += "<br>"
|
||||
. += "<b>Appear in Character Directory:</b> <a [pref.show_in_directory ? "class='linkOn'" : ""] href='?src=\ref[src];toggle_show_in_directory=1'><b>[pref.show_in_directory ? "Yes" : "No"]</b></a><br>"
|
||||
. += "<b>Character Directory Tag:</b> <a href='?src=\ref[src];directory_tag=1'><b>[pref.directory_tag]</b></a><br>"
|
||||
. += "<b>Character Directory Advertisement:</b> <a href='?src=\ref[src];directory_ad=1'><b>Set Directory Ad</b></a><br>"
|
||||
. += "<b>Suit Sensors Preference:</b> <a [pref.sensorpref ? "" : ""] href='?src=\ref[src];toggle_sensor_setting=1'><b>[sensorpreflist[pref.sensorpref]]</b></a><br>" //TFF 5/8/19 - Allow selection of sensor settings from off, binary, vitals, tracking, or random
|
||||
|
||||
/datum/category_item/player_setup_item/vore/misc/OnTopic(var/href, var/list/href_list, var/mob/user)
|
||||
if(href_list["toggle_show_in_directory"])
|
||||
pref.show_in_directory = pref.show_in_directory ? 0 : 1;
|
||||
return TOPIC_REFRESH
|
||||
else if(href_list["directory_tag"])
|
||||
var/new_tag = input(user, "Pick a new tag for the character directory", "Character Tag", pref.directory_tag) as null|anything in GLOB.char_directory_tags
|
||||
if(!new_tag)
|
||||
return
|
||||
pref.directory_tag = new_tag
|
||||
return TOPIC_REFRESH
|
||||
else if(href_list["directory_ad"])
|
||||
var/msg = sanitize(input(user,"Write your advertisement here!", "Flavor Text", html_decode(pref.directory_ad)) as message, extra = 0) //VOREStation Edit: separating out OOC notes
|
||||
pref.directory_ad = msg
|
||||
return TOPIC_REFRESH
|
||||
//TFF 5/8/19 - add new thing so you can choose the sensor setting your character can get.
|
||||
else if(href_list["toggle_sensor_setting"])
|
||||
var/new_sensorpref = input(user, "Choose your character's sensor preferences:", "Character Preferences", sensorpreflist[pref.sensorpref]) as null|anything in sensorpreflist
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
/datum/preferences
|
||||
var/show_in_directory = 1 //Show in Character Directory
|
||||
var/directory_tag = "Unset" //Sorting tag to use in character directory
|
||||
var/directory_ad = "" //Advertisement stuff to show in character directory.
|
||||
var/sensorpref = 5 //Set character's suit sensor level
|
||||
var/job_talon_high = 0
|
||||
var/job_talon_med = 0
|
||||
|
||||
186
tgui/packages/tgui/interfaces/CharacterDirectory.js
Normal file
186
tgui/packages/tgui/interfaces/CharacterDirectory.js
Normal file
@@ -0,0 +1,186 @@
|
||||
import { round } from 'common/math';
|
||||
import { Fragment } from 'inferno';
|
||||
import { useBackend, useLocalState } from "../backend";
|
||||
import { Box, Button, Flex, Icon, LabeledList, Modal, ProgressBar, Section, Table } from "../components";
|
||||
import { Window } from "../layouts";
|
||||
|
||||
const getTagColor = tag => {
|
||||
switch (tag) {
|
||||
case "Unset":
|
||||
return "label";
|
||||
case "Pred":
|
||||
return "red";
|
||||
case "Prey":
|
||||
return "blue";
|
||||
case "Switch":
|
||||
return "purple";
|
||||
case "Non-Vore":
|
||||
return "green";
|
||||
}
|
||||
};
|
||||
|
||||
export const CharacterDirectory = (props, context) => {
|
||||
const { act, data } = useBackend(context);
|
||||
|
||||
const {
|
||||
personalVisibility,
|
||||
personalTag,
|
||||
} = data;
|
||||
|
||||
const [overlay, setOverlay] = useLocalState(context, "overlay", null);
|
||||
|
||||
return (
|
||||
<Window width={640} height={480} resizeable>
|
||||
<Window.Content scrollable>
|
||||
{overlay && (
|
||||
<ViewCharacter />
|
||||
) || (
|
||||
<Fragment>
|
||||
<Section title="Controls">
|
||||
<LabeledList>
|
||||
<LabeledList.Item label="Visibility">
|
||||
<Button
|
||||
fluid
|
||||
content={personalVisibility ? "Shown" : "Not Shown"}
|
||||
onClick={() => act("setVisible")} />
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Tag">
|
||||
<Button
|
||||
fluid
|
||||
content={personalTag}
|
||||
onClick={() => act("setTag")} />
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Advertisement">
|
||||
<Button
|
||||
fluid
|
||||
content="Edit Ad"
|
||||
onClick={() => act("editAd")} />
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
</Section>
|
||||
<CharacterDirectoryList />
|
||||
</Fragment>
|
||||
)}
|
||||
</Window.Content>
|
||||
</Window>
|
||||
);
|
||||
};
|
||||
|
||||
const ViewCharacter = (props, context) => {
|
||||
const [overlay, setOverlay] = useLocalState(context, "overlay", null);
|
||||
|
||||
return (
|
||||
<Section title={overlay.name} buttons={
|
||||
<Button
|
||||
icon="arrow-left"
|
||||
content="Back"
|
||||
onClick={() => setOverlay(null)} />
|
||||
}>
|
||||
<Section level={2} title="Tag">
|
||||
<Box p={1} backgroundColor={getTagColor(overlay.tag)}>
|
||||
{overlay.tag}
|
||||
</Box>
|
||||
</Section>
|
||||
<Section level={2} title="Character Ad">
|
||||
<Box style={{ "word-break": "break-all" }}>
|
||||
{overlay.character_ad ? overlay.character_ad.split("\n").map((c, i) => <Box key={i}>{c}</Box>) : "Unset."}
|
||||
</Box>
|
||||
</Section>
|
||||
<Section level={2} title="OOC Notes">
|
||||
<Box style={{ "word-break": "break-all" }}>
|
||||
{overlay.ooc_notes ? overlay.ooc_notes.split("\n").map((c, i) => <Box key={i}>{c}</Box>) : "Unset."}
|
||||
</Box>
|
||||
</Section>
|
||||
<Section level={2} title="Flavor Text">
|
||||
<Box style={{ "word-break": "break-all" }}>
|
||||
{overlay.flavor_text ? overlay.flavor_text.split("\n").map((c, i) => <Box key={i}>{c}</Box>) : "Unset."}
|
||||
</Box>
|
||||
</Section>
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
const CharacterDirectoryList = (props, context) => {
|
||||
const { act, data } = useBackend(context);
|
||||
|
||||
const {
|
||||
directory,
|
||||
} = data;
|
||||
|
||||
const [sortId, _setSortId] = useLocalState(context, "sortId", "name");
|
||||
const [sortOrder, _setSortOrder] = useLocalState(context, "sortOrder", "name");
|
||||
const [overlay, setOverlay] = useLocalState(context, "overlay", null);
|
||||
|
||||
return (
|
||||
<Section title="Directory" buttons={
|
||||
<Button
|
||||
icon="sync"
|
||||
content="Refresh"
|
||||
onClick={() => act("refresh")} />
|
||||
}>
|
||||
<Table>
|
||||
<Table.Row bold>
|
||||
<SortButton id="name">Name</SortButton>
|
||||
<SortButton id="tag">Tag</SortButton>
|
||||
<Table.Cell collapsing textAlign="right">View</Table.Cell>
|
||||
</Table.Row>
|
||||
{directory
|
||||
.sort((a, b) => {
|
||||
const i = sortOrder ? 1 : -1;
|
||||
return a[sortId].localeCompare(b[sortId]) * i;
|
||||
})
|
||||
.map((character, i) => (
|
||||
<Table.Row key={i} backgroundColor={getTagColor(character.tag)}>
|
||||
<Table.Cell p={1}>{character.name}</Table.Cell>
|
||||
<Table.Cell>{character.tag}</Table.Cell>
|
||||
<Table.Cell collapsing textAlign="right">
|
||||
<Button
|
||||
onClick={() => setOverlay(character)}
|
||||
color="transparent"
|
||||
icon="sticky-note"
|
||||
mr={1}
|
||||
content="View" />
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
))}
|
||||
</Table>
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
const SortButton = (props, context) => {
|
||||
const { act, data } = useBackend(context);
|
||||
|
||||
const {
|
||||
id,
|
||||
children,
|
||||
} = props;
|
||||
|
||||
// Hey, same keys mean same data~
|
||||
const [sortId, setSortId] = useLocalState(context, "sortId", "name");
|
||||
const [sortOrder, setSortOrder] = useLocalState(context, "sortOrder", "name");
|
||||
|
||||
return (
|
||||
<Table.Cell collapsing>
|
||||
<Button
|
||||
width="100%"
|
||||
color={sortId !== id && "transparent"}
|
||||
onClick={() => {
|
||||
if (sortId === id) {
|
||||
setSortOrder(!sortOrder);
|
||||
} else {
|
||||
setSortId(id);
|
||||
setSortOrder(true);
|
||||
}
|
||||
}}>
|
||||
{children}
|
||||
{sortId === id && (
|
||||
<Icon
|
||||
name={sortOrder ? "sort-up" : "sort-down"}
|
||||
ml="0.25rem;"
|
||||
/>
|
||||
)}
|
||||
</Button>
|
||||
</Table.Cell>
|
||||
);
|
||||
};
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user