From f74b876b5e04fcffe4805ffe867eb4112a12c11a Mon Sep 17 00:00:00 2001 From: BongaTheProto <93835010+BongaTheProto@users.noreply.github.com> Date: Sat, 20 May 2023 15:38:43 -0500 Subject: [PATCH] we've got a new menu boys those crewmembers are looking quite exquisite today also felinids are half-price --- code/modules/client/preferences_savefile.dm | 4 + .../_globalvars/lists/character_directory.dm | 2 + modular_splurt/code/datums/mind.dm | 20 ++ .../code/modules/client/preferences.dm | 4 + .../modules/client/preferences_savefile.dm | 20 ++ .../client/verbs/character_directory.dm | 212 +++++++++++++++++ tgstation.dme | 2 + .../tgui/interfaces/CharacterDirectory.js | 217 ++++++++++++++++++ 8 files changed, 481 insertions(+) create mode 100644 modular_splurt/code/_globalvars/lists/character_directory.dm create mode 100644 modular_splurt/code/modules/client/verbs/character_directory.dm create mode 100644 tgui/packages/tgui/interfaces/CharacterDirectory.js diff --git a/code/modules/client/preferences_savefile.dm b/code/modules/client/preferences_savefile.dm index aad17eb424..134e91db52 100644 --- a/code/modules/client/preferences_savefile.dm +++ b/code/modules/client/preferences_savefile.dm @@ -1300,6 +1300,8 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car cit_character_pref_load(S) + splurt_character_pref_load(S) + return 1 /datum/preferences/proc/save_character(bypass_cooldown = FALSE) @@ -1545,6 +1547,8 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car cit_character_pref_save(S) + splurt_character_pref_save(S) + return 1 diff --git a/modular_splurt/code/_globalvars/lists/character_directory.dm b/modular_splurt/code/_globalvars/lists/character_directory.dm new file mode 100644 index 0000000000..a308e81e94 --- /dev/null +++ b/modular_splurt/code/_globalvars/lists/character_directory.dm @@ -0,0 +1,2 @@ +GLOBAL_LIST_INIT(char_directory_tags, list("Pred", "Pred-Pref", "Prey", "Prey-Pref", "Switch", "Non-Vore", "Unset")) +GLOBAL_LIST_INIT(char_directory_erptags, list("Top", "Bottom", "Switch", "No ERP", "Unset")) diff --git a/modular_splurt/code/datums/mind.dm b/modular_splurt/code/datums/mind.dm index 78a06ad7cb..e1eca4be65 100644 --- a/modular_splurt/code/datums/mind.dm +++ b/modular_splurt/code/datums/mind.dm @@ -1,3 +1,23 @@ +/datum/mind + var/show_in_directory + var/directory_tag + var/directory_erptag + var/directory_ad + var/ooc_notes + var/flavor_text + var/silicon_flavor_text + +/mob/living/mind_initialize() + . = ..() + if(client?.prefs) + mind.show_in_directory = client?.prefs.show_in_directory + mind.directory_tag = client?.prefs.directory_tag + mind.directory_erptag = client?.prefs.directory_erptag + mind.directory_ad = client?.prefs.directory_ad + mind.ooc_notes = client?.prefs.features["ooc_notes"] + mind.flavor_text = client?.prefs.features["flavor_text"] + mind.silicon_flavor_text = client?.prefs.features["silicon_flavor_text"] + /datum/mind/proc/remove_slaver() var/datum/antagonist/slaver/slaver = has_antag_datum(/datum/antagonist/slaver,TRUE) if(slaver) diff --git a/modular_splurt/code/modules/client/preferences.dm b/modular_splurt/code/modules/client/preferences.dm index 3ceec318df..444380e2fe 100644 --- a/modular_splurt/code/modules/client/preferences.dm +++ b/modular_splurt/code/modules/client/preferences.dm @@ -8,6 +8,10 @@ var/unholypref = "No" //Goin 2 hell fo dis one var/list/gfluid_blacklist = list() //Stuff you don't want people to cum into you var/new_character_creator = TRUE // old/new character creator + var/show_in_directory = 1 //Show in Character Directory + var/directory_tag = "Unset" //Sorting tag to use in character directory + var/directory_erptag = "Unset" //ditto, but for non-vore scenes + var/directory_ad = "" //Advertisement stuff to show in character directory. /datum/preferences/New(client/C) // Check if readable fluids list exists diff --git a/modular_splurt/code/modules/client/preferences_savefile.dm b/modular_splurt/code/modules/client/preferences_savefile.dm index 5471d37fd4..deda14d0e1 100644 --- a/modular_splurt/code/modules/client/preferences_savefile.dm +++ b/modular_splurt/code/modules/client/preferences_savefile.dm @@ -1,3 +1,23 @@ +/datum/preferences/proc/splurt_character_pref_load(savefile/S) //TODO: modularize our other savefile edits... maybe? + //Character directory + S["show_in_directory"] >> show_in_directory + S["directory_tag"] >> directory_tag + S["directory_erptag"] >> directory_erptag + S["directory_ad"] >> directory_ad + + //sanitize data + show_in_directory = sanitize_integer(show_in_directory, 0, 1, initial(show_in_directory)) + directory_tag = sanitize_inlist(directory_tag, GLOB.char_directory_tags, initial(directory_tag)) + directory_erptag = sanitize_inlist(directory_erptag, GLOB.char_directory_erptags, initial(directory_erptag)) + directory_ad = strip_html_simple(directory_ad, MAX_FLAVOR_LEN) + +/datum/preferences/proc/splurt_character_pref_save(savefile/S) //TODO: modularize our other savefile edits... maybe? + //Character directory + WRITE_FILE(S["show_in_directory"], show_in_directory) + WRITE_FILE(S["directory_tag"], directory_tag) + WRITE_FILE(S["directory_erptag"], directory_erptag) + WRITE_FILE(S["directory_ad"], directory_ad) + /datum/preferences/update_preferences(current_version, savefile/S) . = ..() if(current_version < 57.01) //a diff --git a/modular_splurt/code/modules/client/verbs/character_directory.dm b/modular_splurt/code/modules/client/verbs/character_directory.dm new file mode 100644 index 0000000000..2fa14e9223 --- /dev/null +++ b/modular_splurt/code/modules/client/verbs/character_directory.dm @@ -0,0 +1,212 @@ +GLOBAL_DATUM(character_directory, /datum/character_directory) + +/client + COOLDOWN_DECLARE(char_directory_cooldown) + +/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." + + // This is primarily to stop malicious users from trying to lag the server by spamming this verb + if(!COOLDOWN_FINISHED(src, char_directory_cooldown)) + to_chat(usr, "Don't spam character directory refresh.") + return + COOLDOWN_START(src, char_directory_cooldown, 10) + + if(!GLOB.character_directory) + GLOB.character_directory = new + GLOB.character_directory.ui_interact(mob) + + +// This is a global singleton. Keep in mind that all operations should occur on usr, not src. +/datum/character_directory + +/datum/character_directory/ui_state(mob/user) + return GLOB.always_state + +/datum/character_directory/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "CharacterDirectory", "Character Directory") + ui.open() + +/datum/character_directory/ui_data(mob/user) + . = ..() + var/list/data = . + + if (user?.mind && !isdead(user)) + data["personalVisibility"] = user.mind.show_in_directory + data["personalTag"] = user.mind.directory_tag || "Unset" + data["personalErpTag"] = user.mind.directory_erptag || "Unset" + data["prefsOnly"] = FALSE + else if (user?.client?.prefs) + data["personalVisibility"] = user.client.prefs.show_in_directory + data["personalTag"] = user.client.prefs.directory_tag || "Unset" + data["personalErpTag"] = user.client.prefs.directory_erptag || "Unset" + data["prefsOnly"] = TRUE + + return data + +/datum/character_directory/ui_static_data(mob/user) + . = ..() + var/list/data = . + + var/list/directory_mobs = list() + for(var/client/C in GLOB.clients) + // Allow opt-out and filter players not in the game + if(C?.mob?.mind ? !C.mob.mind.show_in_directory : !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/species = null + var/ooc_notes = null + var/flavor_text = null + var/tag + var/erptag + var/character_ad + if (C.mob?.mind) //could use ternary for all three but this is more efficient + tag = C.mob.mind.directory_tag || "Unset" + erptag = C.mob.mind.directory_erptag || "Unset" + character_ad = C.mob.mind.directory_ad + else + tag = C.prefs.directory_tag || "Unset" + erptag = C.prefs.directory_erptag || "Unset" + character_ad = C.prefs.directory_ad + + if(ishuman(C.mob)) + var/mob/living/carbon/human/H = C.mob + if(GLOB.data_core && GLOB.data_core.general) + if(!find_record("name", H.real_name, GLOB.data_core.general)) + continue + name = H.real_name + species = "[H.custom_species ? H.custom_species : H.dna.species]" + ooc_notes = H.mind.ooc_notes + flavor_text = H.mind.flavor_text + + if(isAI(C.mob)) + var/mob/living/silicon/ai/A = C.mob + name = A.name + species = "Artificial Intelligence" + ooc_notes = A.mind.ooc_notes + flavor_text = null // No flavor text for AIs :c + + if(iscyborg(C.mob)) + var/mob/living/silicon/robot/R = C.mob + if(R.scrambledcodes) + continue + name = R.name + species = "Cyborg" + ooc_notes = R.mind.ooc_notes + flavor_text = R.mind.silicon_flavor_text + + // 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, + "species" = species, + "ooc_notes" = ooc_notes, + "tag" = tag, + "erptag" = erptag, + "character_ad" = character_ad, + "flavor_text" = flavor_text, + ))) + + data["directory"] = directory_mobs + + return data + +/datum/character_directory/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state) + . = ..() + + if(.) + return + + if(action == "refresh") + // This is primarily to stop malicious users from trying to lag the server by spamming this verb + if(!COOLDOWN_FINISHED(usr.client, char_directory_cooldown)) + to_chat(usr, "Don't spam character directory refresh.") + return + COOLDOWN_START(usr.client, char_directory_cooldown, 10) + update_static_data(usr, ui) + return TRUE + else + return check_for_mind_or_prefs(usr, action, params["overwrite_prefs"]) + +/datum/character_directory/proc/check_for_mind_or_prefs(mob/user, action, overwrite_prefs) + if (!user.client) + return + var/can_set_prefs = overwrite_prefs && !!user.client.prefs + var/can_set_mind = !!user.mind && !isdead(user) + if (!can_set_prefs && !can_set_mind) + if (!overwrite_prefs && !!user.client.prefs) + to_chat(user, "You cannot change these settings if you don't have a mind to save them to. Enable overwriting prefs and switch to a slot you're fine with overwriting.") + return + switch(action) + if ("setTag") + var/list/new_tag = tgui_input_list(usr, "Pick a new Vore tag for the character directory", "Character Tag", GLOB.char_directory_tags) + if(!new_tag) + return + return set_for_mind_or_prefs(user, action, new_tag, can_set_prefs, can_set_mind) + if ("setErpTag") + var/list/new_erptag = tgui_input_list(usr, "Pick a new ERP tag for the character directory", "Character ERP Tag", GLOB.char_directory_erptags) + if(!new_erptag) + return + return set_for_mind_or_prefs(user, action, new_erptag, can_set_prefs, can_set_mind) + if ("setVisible") + var/visible = TRUE + if (can_set_mind) + visible = user.mind.show_in_directory + else if (can_set_prefs) + visible = user.client.prefs.show_in_directory + to_chat(usr, "You are now [!visible ? "shown" : "not shown"] in the directory.") + return set_for_mind_or_prefs(user, action, !visible, can_set_prefs, can_set_mind) + if ("editAd") + var/current_ad = (can_set_mind ? usr.mind.directory_ad : null) || (can_set_prefs ? usr.client.prefs.directory_ad : null) + var/new_ad = strip_html_simple(tgui_input_text(usr, "Change your character ad", "Character Ad", current_ad, MAX_FLAVOR_LEN, multiline = TRUE, prevent_enter = TRUE), MAX_FLAVOR_LEN) + if(isnull(new_ad)) + return + return set_for_mind_or_prefs(user, action, new_ad, can_set_prefs, can_set_mind) + else + to_chat(usr, span_warning("You can only make temporary changes while in game")) + +/datum/character_directory/proc/set_for_mind_or_prefs(mob/user, action, new_value, can_set_prefs, can_set_mind) + can_set_prefs &&= !!user.client.prefs + can_set_mind &&= !!user.mind + if (!can_set_prefs && !can_set_mind) + to_chat(user, "You seem to have lost either your mind, or your current preferences, while changing the values.[action == "editAd" ? " Here is your ad that you wrote. [new_value]" : null]") + return + switch(action) + if ("setTag") + if (can_set_prefs) + user.client.prefs.directory_tag = new_value + user.client.prefs.save_character() + if (can_set_mind) + user.mind.directory_tag = new_value + return TRUE + if ("setErpTag") + if (can_set_prefs) + user.client.prefs.directory_erptag = new_value + user.client.prefs.save_character() + if (can_set_mind) + user.mind.directory_erptag = new_value + return TRUE + if ("setVisible") + if (can_set_prefs) + user.client.prefs.show_in_directory = new_value + user.client.prefs.save_character() + if (can_set_mind) + user.mind.show_in_directory = new_value + return TRUE + if ("editAd") + if (can_set_prefs) + user.client.prefs.directory_ad = new_value + user.client.prefs.save_character() + if (can_set_mind) + user.mind.directory_ad = new_value + return TRUE diff --git a/tgstation.dme b/tgstation.dme index 0165558c72..d1142d8fa7 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -4323,6 +4323,7 @@ #include "modular_splurt\code\__HELPERS\text.dm" #include "modular_splurt\code\__HELPERS\unsorted.dm" #include "modular_splurt\code\_globalvars\tgui.dm" +#include "modular_splurt\code\_globalvars\lists\character_directory.dm" #include "modular_splurt\code\_globalvars\lists\global_lewd.dm" #include "modular_splurt\code\_globalvars\lists\mapping.dm" #include "modular_splurt\code\_globalvars\lists\mobs.dm" @@ -4614,6 +4615,7 @@ #include "modular_splurt\code\modules\client\loadout\donator\first_tier.dm" #include "modular_splurt\code\modules\client\loadout\donator\second_tier.dm" #include "modular_splurt\code\modules\client\loadout\donator\third_tier.dm" +#include "modular_splurt\code\modules\client\verbs\character_directory.dm" #include "modular_splurt\code\modules\client\verbs\looc.dm" #include "modular_splurt\code\modules\client\verbs\ooc.dm" #include "modular_splurt\code\modules\clothing\back.dm" diff --git a/tgui/packages/tgui/interfaces/CharacterDirectory.js b/tgui/packages/tgui/interfaces/CharacterDirectory.js new file mode 100644 index 0000000000..00c6c4b94b --- /dev/null +++ b/tgui/packages/tgui/interfaces/CharacterDirectory.js @@ -0,0 +1,217 @@ +import { Fragment } from 'inferno'; +import { useBackend, useLocalState } from '../backend'; +import { Box, Button, Icon, LabeledList, Section, Table } from '../components'; +import { Window } from '../layouts'; + +/* //General erp preferences are probably a better indicator for this station + const getErpTagColor = (tag) => { + switch (tag) { + case 'Unset': + return 'label'; + case 'Pred': + return 'red'; + case 'Pred-Pref': + return 'orange'; + case 'Prey': + return 'blue'; + case 'Prey-Pref': + return 'green'; + case 'Switch': + return 'yellow'; + case 'Non-Vore': + return 'black'; + } +};*/ + +const getErpTagColor = (tag) => { + switch (tag) { + case 'Unset': + return 'label'; + case 'Top': + return 'red'; + case 'Bottom': + return 'blue'; + case 'Switch': + return 'yellow'; + case 'No ERP': + return 'black'; + } +}; + +export const CharacterDirectory = (props, context) => { + const { act, data } = useBackend(context); + + const { personalVisibility, personalTag, personalErpTag, prefsOnly } = data; + + const [overlay, setOverlay] = useLocalState(context, 'overlay', null); + + const [overwritePrefs, setOverwritePrefs] = useLocalState(context, 'overwritePrefs', prefsOnly); + + return ( + + + {(overlay && ) || ( + +
+ + Save to current preferences slot:  + +
+ +
+ )} +
+
+ ); +}; + +const ViewCharacter = (props, context) => { + const [overlay, setOverlay] = useLocalState(context, 'overlay', null); + + return ( +
setOverlay(null)} />}> +
+ {overlay.species} +
+
+ + {overlay.tag} + +
+
+ {overlay.erptag} +
+
+ + {overlay.character_ad || 'Unset.'} + +
+
+ + {overlay.ooc_notes || 'Unset.'} + +
+
+ + {overlay.flavor_text || 'Unset.'} + +
+
+ ); +}; + +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 ( +
act('refresh')} />}> + + + Name + Species + Vore Tag + ERP Tag + + View + + + {directory + .sort((a, b) => { + const i = sortOrder ? 1 : -1; + return a[sortId].localeCompare(b[sortId]) * i; + }) + .map((character, i) => ( + + {character.name} + {character.species} + {character.tag} + {character.erptag} + +
+
+ ); +}; + +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 ( + + + + ); +};