From e931d41e5536055f103671ca84acfcea020d97c4 Mon Sep 17 00:00:00 2001 From: Crystalic <39885003+blackcrystall@users.noreply.github.com> Date: Wed, 7 Aug 2024 11:51:03 +0500 Subject: [PATCH] Hold my Beer (Fix of hi intellligent data lookup in TGUI Who/Staff Who (#6786) :cl: BlackCrystalic fix: No more admin data sended to normal players in who/staff who /:cl: --------- Co-authored-by: Deleted user Co-authored-by: Drathek <76988376+Drulikar@users.noreply.github.com> --- code/controllers/subsystem/who.dm | 295 ++++++++++----------- tgui/packages/tgui/interfaces/StaffWho.jsx | 164 +++++++++--- tgui/packages/tgui/interfaces/Who.jsx | 198 ++++++++------ 3 files changed, 375 insertions(+), 282 deletions(-) diff --git a/code/controllers/subsystem/who.dm b/code/controllers/subsystem/who.dm index 43ecbb435587..6c817fb1245e 100644 --- a/code/controllers/subsystem/who.dm +++ b/code/controllers/subsystem/who.dm @@ -17,17 +17,29 @@ SUBSYSTEM_DEF(who) who.update_data() staff_who.update_data() -//datum + + +// WHO DATA /datum/player_list var/tgui_name = "Who" var/tgui_interface_name = "Who" - var/list/mobs_ckey = list() - var/list/list_data = list() + var/list/base_data = list() + var/list/admin_sorted_additional = list() /datum/player_list/proc/update_data() - var/list/new_list_data = list() - var/list/new_mobs_ckey = list() - var/list/additional_data = list( + var/list/base_data = list() + var/list/admin_sorted_additional = list() + + var/list/factions_additional = list() + admin_sorted_additional["factions_additional"] = list("flags" = R_MOD|R_ADMIN, "data" = factions_additional) + + var/list/player_additional = list() + admin_sorted_additional["player_additional"] = list("flags" = R_MOD|R_ADMIN, "data" = player_additional) + + var/list/player_stealthed_additional = list() + admin_sorted_additional["player_stealthed_additional"] = list("flags" = R_STEALTH, "data" = player_stealthed_additional) + + var/list/counted_additional = list( "lobby" = 0, "admin_observers" = 0, "observers" = 0, @@ -38,35 +50,40 @@ SUBSYSTEM_DEF(who) "uscm" = 0, "uscm_marines" = 0, ) - new_list_data["additional_info"] = list() var/list/counted_factions = list() + + // Running thru all clients and doing some counts for(var/client/client as anything in sortTim(GLOB.clients, GLOBAL_PROC_REF(cmp_ckey_asc))) - CHECK_TICK - new_list_data["all_clients"]++ var/list/client_payload = list() - client_payload["ckey"] = "[client.key]" - client_payload["text"] = "[client.key]" + client_payload["text"] = client.key client_payload["ckey_color"] = "white" + if(CLIENT_IS_STEALTHED(client)) + player_stealthed_additional["total_players"] += list(list(client.key = list(client_payload))) + else if(client.admin_holder?.fakekey) + player_additional["total_players"] += list(list(client.key = list(client_payload))) + else + base_data["total_players"] += list(list(client.key = list(client_payload.Copy()))) + player_additional["total_players"] += list(list(client.key = list(client_payload))) + var/mob/client_mob = client.mob - new_mobs_ckey[client.key] = client_mob if(client_mob) if(istype(client_mob, /mob/new_player)) client_payload["text"] += " - in Lobby" - additional_data["lobby"]++ + counted_additional["lobby"]++ else if(isobserver(client_mob)) client_payload["text"] += " - Playing as [client_mob.real_name]" if(CLIENT_IS_STAFF(client)) - additional_data["admin_observers"]++ + counted_additional["admin_observers"]++ else - additional_data["observers"]++ + counted_additional["observers"]++ var/mob/dead/observer/observer = client_mob if(observer.started_as_observer) - client_payload["color"] += "#ce89cd" + client_payload["color"] = "#ce89cd" client_payload["text"] += " - Spectating" else - client_payload["color"] += "#A000D0" + client_payload["color"] = "#A000D0" client_payload["text"] += " - DEAD" else @@ -74,120 +91,66 @@ SUBSYSTEM_DEF(who) switch(client_mob.stat) if(UNCONSCIOUS) - client_payload["color"] += "#B0B0B0" + client_payload["color"] = "#B0B0B0" client_payload["text"] += " - Unconscious" if(DEAD) - client_payload["color"] += "#A000D0" + client_payload["color"] = "#A000D0" client_payload["text"] += " - DEAD" if(client_mob.stat != DEAD) if(isxeno(client_mob)) - client_payload["color"] += "#ec3535" + client_payload["color"] = "#ec3535" client_payload["text"] += " - Xenomorph" else if(ishuman(client_mob)) if(client_mob.faction == FACTION_ZOMBIE) counted_factions[FACTION_ZOMBIE]++ - client_payload["color"] += "#2DACB1" + client_payload["color"] = "#2DACB1" client_payload["text"] += " - Zombie" else if(client_mob.faction == FACTION_YAUTJA) - client_payload["color"] += "#7ABA19" + client_payload["color"] = "#7ABA19" client_payload["text"] += " - Yautja" - additional_data["yautja"]++ + counted_additional["yautja"]++ if(client_mob.status_flags & XENO_HOST) - additional_data["infected_preds"]++ + counted_additional["infected_preds"]++ else - additional_data["humans"]++ + counted_additional["humans"]++ if(client_mob.status_flags & XENO_HOST) - additional_data["infected_humans"]++ + counted_additional["infected_humans"]++ if(client_mob.faction == FACTION_MARINE) - additional_data["uscm"]++ + counted_additional["uscm"]++ if(client_mob.job in (GLOB.ROLES_MARINES)) - additional_data["uscm_marines"]++ + counted_additional["uscm_marines"]++ else counted_factions[client_mob.faction]++ - new_list_data["total_players"] += list(client_payload) - - new_list_data["additional_info"] += list(list( - "content" = "In Lobby: [additional_data["lobby"]]", - "color" = "#777", - "text" = "Player in lobby", - )) - - new_list_data["additional_info"] += list(list( - "content" = "Spectating Players: [additional_data["observers"]]", - "color" = "#777", - "text" = "Spectating players", - )) - - new_list_data["additional_info"] += list(list( - "content" = "Spectating Admins: [additional_data["admin_observers"]]", - "color" = "#777", - "text" = "Spectating administrators", - )) - - new_list_data["additional_info"] += list(list( - "content" = "Humans: [additional_data["humans"]]", - "color" = "#2C7EFF", - "text" = "Players playing as Human", - )) - - new_list_data["additional_info"] += list(list( - "content" = "Infected Humans: [additional_data["infected_humans"]]", - "color" = "#ec3535", - "text" = "Players playing as Infected Human", - )) - - new_list_data["additional_info"] += list(list( - "content" = "[MAIN_SHIP_NAME] Personnel: [additional_data["uscm"]]", - "color" = "#5442bd", - "text" = "Players playing as [MAIN_SHIP_NAME] Personnel", - )) - - new_list_data["additional_info"] += list(list( - "content" = "Marines: [additional_data["uscm_marines"]]", - "color" = "#5442bd", - "text" = "Players playing as Marines", - )) - - new_list_data["additional_info"] += list(list( - "content" = "Yautjas: [additional_data["yautja"]]", - "color" = "#7ABA19", - "text" = "Players playing as Yautja", - )) - - new_list_data["additional_info"] += list(list( - "content" = "Infected Predators: [additional_data["infected_preds"]]", - "color" = "#7ABA19", - "text" = "Players playing as Infected Yautja", - )) + //Bulky section with pre writen names and desc for counts + factions_additional += list(list("content" = "In Lobby: [counted_additional["lobby"]]", "color" = "#777", "text" = "Player in lobby")) + factions_additional += list(list("content" = "Spectating Players: [counted_additional["observers"]]", "color" = "#777", "text" = "Spectating players")) + factions_additional += list(list("content" = "Spectating Admins: [counted_additional["admin_observers"]]", "color" = "#777", "text" = "Spectating administrators")) + factions_additional += list(list("content" = "Humans: [counted_additional["humans"]]", "color" = "#2C7EFF", "text" = "Players playing as Human")) + factions_additional += list(list("content" = "Infected Humans: [counted_additional["infected_humans"]]", "color" = "#ec3535", "text" = "Players playing as Infected Human")) + factions_additional += list(list("content" = "[MAIN_SHIP_NAME] Personnel: [counted_additional["uscm"]]", "color" = "#5442bd", "text" = "Players playing as [MAIN_SHIP_NAME] Personnel")) + factions_additional += list(list("content" = "Marines: [counted_additional["uscm_marines"]]", "color" = "#5442bd", "text" = "Players playing as Marines")) + factions_additional += list(list("content" = "Yautjas: [counted_additional["yautja"]]", "color" = "#7ABA19", "text" = "Players playing as Yautja")) + factions_additional += list(list("content" = "Infected Predators: [counted_additional["infected_preds"]]", "color" = "#7ABA19", "text" = "Players playing as Infected Yautja")) for(var/i in 1 to length(counted_factions)) - if(counted_factions[counted_factions[i]]) - new_list_data["factions"] += list(list( - "content" = "[counted_factions[i]]: [counted_factions[counted_factions[i]]]", - "color" = "#2C7EFF", - "text" = "Other", - )) + if(!counted_factions[counted_factions[i]]) + continue + factions_additional += list(list("content" = "[counted_factions[i]]: [counted_factions[counted_factions[i]]]", "color" = "#2C7EFF", "text" = "Other")) + if(counted_factions[FACTION_NEUTRAL]) - new_list_data["factions"] += list(list( - "content" = "[FACTION_NEUTRAL] Humans: [counted_factions[FACTION_NEUTRAL]]", - "color" = "#688944", - "text" = "Neutrals", - )) + factions_additional += list(list("content" = "[FACTION_NEUTRAL] Humans: [counted_factions[FACTION_NEUTRAL]]", "color" = "#688944", "text" = "Neutrals")) for(var/faction_to_get in ALL_XENO_HIVES) var/datum/hive_status/hive = GLOB.hive_datum[faction_to_get] - if(hive && length(hive.totalXenos)) - new_list_data["xenomorphs"] += list(list( - "content" = "[hive.name]: [length(hive.totalXenos)]", - "color" = hive.color ? hive.color : "#8200FF", - "text" = "Queen: [hive.living_xeno_queen ? "Alive" : "Dead"]", - )) + if(!hive || !length(hive.totalXenos)) + continue + factions_additional += list(list("content" = "[hive.name]: [length(hive.totalXenos)]", "color" = hive.color ? hive.color : "#8200FF", "text" = "Queen: [hive.living_xeno_queen ? "Alive" : "Dead"]")) - list_data = new_list_data - mobs_ckey = new_mobs_ckey + src.base_data = base_data + src.admin_sorted_additional = admin_sorted_additional /datum/player_list/tgui_interact(mob/user, datum/tgui/ui) ui = SStgui.try_update_ui(user, src, ui) @@ -197,12 +160,17 @@ SUBSYSTEM_DEF(who) ui.set_autoupdate(TRUE) /datum/player_list/ui_data(mob/user) - . = list_data - -/datum/player_list/ui_static_data(mob/user) . = list() + // Sending base client data, this data sended to EVERYONE + .["base_data"] = base_data - .["admin"] = CLIENT_IS_STAFF(user.client) + // Admin rights based data + if(!CLIENT_IS_STAFF(user.client)) + return + for(var/data_packet_name in admin_sorted_additional) // One by one for Drulikar complains + if(!check_client_rights(user.client, admin_sorted_additional[data_packet_name]["flags"], FALSE)) + continue + . += list("[data_packet_name]" = admin_sorted_additional[data_packet_name]["data"]) /datum/player_list/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state) . = ..() @@ -211,13 +179,21 @@ SUBSYSTEM_DEF(who) switch(action) if("get_player_panel") - if(mobs_ckey[params["ckey"]]) - GLOB.admin_datums[usr.client.ckey].show_player_panel(mobs_ckey[params["ckey"]]) + if(!CLIENT_IS_STAFF(ui.user.client)) + return + var/chosen_ckey = params["ckey"] + for(var/client/target in GLOB.clients) + if(target.key != chosen_ckey) + continue + if(target.mob) + GLOB.admin_datums[ui.user.client.ckey].show_player_panel(target.mob) + break /datum/player_list/ui_status(mob/user, datum/ui_state/state) return UI_INTERACTIVE +// STAFF DATA /datum/player_list/staff tgui_name = "StaffWho" tgui_interface_name = "Staff Who" @@ -231,74 +207,79 @@ SUBSYSTEM_DEF(who) ) /datum/player_list/staff/update_data() - var/list/new_list_data = list() - mobs_ckey = list() + var/list/base_data = list() + var/list/admin_sorted_additional = list() - var/list/listings - var/list/mappings + var/list/admin_additional = list() + admin_sorted_additional["admin_additional"] = list("flags" = R_MOD|R_ADMIN, "data" = admin_additional) + + var/list/admin_stealthed_additional = list() + admin_sorted_additional["admin_stealthed_additional"] = list("flags" = R_STEALTH, "data" = admin_stealthed_additional) + + var/list/listings = list() if(CONFIG_GET(flag/show_manager)) - LAZYSET(mappings, "Management", R_PERMISSIONS) + listings["Management"] = list(R_PERMISSIONS, list()) if(CONFIG_GET(flag/show_devs)) - LAZYSET(mappings, "Maintainers", R_PROFILER) - LAZYSET(mappings, "Administrators", R_ADMIN) + listings["Maintainers"] = list(R_PROFILER, list()) + listings["Administrators"] = list(R_ADMIN, list()) if(CONFIG_GET(flag/show_mods)) - LAZYSET(mappings, "Moderators", R_MOD && R_BAN) + listings["Moderators"] = list(R_MOD|R_BAN, list()) if(CONFIG_GET(flag/show_mentors)) - LAZYSET(mappings, "Mentors", R_MENTOR) - - for(var/category in mappings) - LAZYSET(listings, category, list()) + listings["Mentors"] = list(R_MENTOR, list()) for(var/client/client as anything in GLOB.admins) - if(client.admin_holder?.fakekey && !CLIENT_IS_STAFF(client)) - continue - - for(var/category in mappings) - if(CLIENT_HAS_RIGHTS(client, mappings[category])) - LAZYADD(listings[category], client) + for(var/category in listings) + if(CLIENT_HAS_RIGHTS(client, listings[category][1])) + listings[category][2] += client break for(var/category in listings) - var/list/admins = list() - for(var/client/entry as anything in listings[category]) - var/list/admin = list() - var/rank = entry.admin_holder.rank - if(entry.admin_holder.extra_titles?.len) - for(var/srank in entry.admin_holder.extra_titles) - rank += " & [srank]" + base_data["categories"] += list(list( + "category" = category, + "category_color" = category_colors[category], + )) - admin["content"] = "[entry.key] ([rank])" - admin["text"] = "" + for(var/client/client as anything in listings[category][2]) + var/list/admin_payload = list() + admin_payload["category"] = category + var/rank = client.admin_holder.rank + if(client.admin_holder.extra_titles?.len) + for(var/srank in client.admin_holder.extra_titles) + rank += " & [srank]" - if(entry.admin_holder?.fakekey) - admin["text"] += " (HIDDEN)" + if(CLIENT_IS_STEALTHED(client)) + admin_payload["special_color"] = "#b60d0d" + admin_payload["special_text"] = " (STEALTHED)" + admin_stealthed_additional["total_admins"] += list(list("[client.key] ([rank])" = list(admin_payload))) + else if(client.admin_holder?.fakekey) + admin_payload["special_color"] = "#7b582f" + admin_payload["special_text"] += " (HIDDEN)" + admin_additional["total_admins"] += list(list("[client.key] ([rank])" = list(admin_payload))) + else + admin_additional["total_admins"] += list(list("[client.key] ([rank])" = list(admin_payload))) + base_data["total_admins"] += list(list("[client.key] ([rank])" = list(admin_payload.Copy()))) - if(istype(entry.mob, /mob/dead/observer)) - admin["color"] = "#808080" - admin["text"] += " Spectating" + admin_payload["text"] = "" + if(istype(client.mob, /mob/dead/observer)) + admin_payload["color"] = "#808080" + admin_payload["text"] += "Spectating" - else if(istype(entry.mob, /mob/new_player)) - admin["color"] = "#688944" - admin["text"] += " in Lobby" + else if(istype(client.mob, /mob/new_player)) + admin_payload["color"] = "#FFFFFF" + admin_payload["text"] += "in Lobby" else - admin["color"] = "#688944" - admin["text"] += " Playing" - - if(entry.is_afk()) - admin["color"] = "#A040D0" - admin["text"] += " (AFK)" + admin_payload["color"] = "#688944" + admin_payload["text"] += "Playing" - admins += list(admin) + if(client.is_afk()) + admin_payload["color"] = "#A040D0" + admin_payload["special_text"] += " (AFK)" - new_list_data["administrators"] += list(list( - "category" = category, - "category_color" = category_colors[category], - "category_administrators" = length(listings[category]), - "admins" = admins, - )) + src.base_data = base_data + src.admin_sorted_additional = admin_sorted_additional - list_data = new_list_data +// VERBS /mob/verb/who() set category = "OOC" set name = "Who" diff --git a/tgui/packages/tgui/interfaces/StaffWho.jsx b/tgui/packages/tgui/interfaces/StaffWho.jsx index 1ce71c9b5001..e2d1ecc2d774 100644 --- a/tgui/packages/tgui/interfaces/StaffWho.jsx +++ b/tgui/packages/tgui/interfaces/StaffWho.jsx @@ -3,32 +3,25 @@ import { Button, Collapsible, Stack } from '../components'; import { Window } from '../layouts'; export const StaffWho = (props, context) => { - const { data } = useBackend(context); - const { admin, administrators } = data; + const { data } = useBackend(); + const { base_data, admin_additional, admin_stealthed_additional } = data; + + const total_admins = mergeArrays( + base_data.total_admins, + admin_additional?.total_admins, + admin_stealthed_additional?.total_admins, + ); return ( - {administrators !== undefined ? ( + {base_data ? ( - {administrators.map((x, index) => ( - - {x.admins.map((x, index) => ( - - ))} - - ))} + ) : null} @@ -37,6 +30,23 @@ export const StaffWho = (props, context) => { ); }; +const FilterCategories = (props, context) => { + const { categories, total_admins } = props; + + return categories.map((category) => { + const category_admins = total_admins.filter((adminObj) => + isMatch(adminObj, category.category), + ); + return ( + + ); + }); +}; + const StaffWhoCollapsible = (props, context) => { const { title, color, children } = props; return ( @@ -46,33 +56,113 @@ const StaffWhoCollapsible = (props, context) => { ); }; +const CategoryDropDown = (props, context) => { + const { category, category_admins } = props; + return ( + + + + ); +}; + +const FilterAdmins = (props, context) => { + const { category_admins } = props; + + return category_admins.map((adminObj) => { + const ckey = Object.keys(adminObj)[0]; + return ; + }); +}; + const GetAdminInfo = (props, context) => { - const { admin, content, color, text } = props; - return admin ? ( + const { ckey, special_color, special_text, text, color } = props; + return ( - ) : ( - ); }; + +const isMatch = (adminObj, search) => { + if (!search) { + return true; + } + + let found = false; + const adminKey = Object.keys(adminObj)[0]; + const params = adminObj[adminKey]; + params.forEach((param) => { + if (found) { + return; + } + Object.keys(param).forEach((key) => { + if (param[key] === search) { + found = true; + return; + } + }); + }); + return found; +}; + +// Krill me please +const mergeArrays = (...arrays) => { + const mergedObject = {}; + + arrays.forEach((array) => { + if (!array) return; + + array.forEach((item) => { + if (!item) return; + + const key = Object.keys(item)[0]; + const value = item[key]; + + if (!mergedObject[key]) { + mergedObject[key] = []; + } + + value.forEach((subItem) => { + if (typeof subItem !== 'object' || subItem === null) return; + + const existingItemIndex = mergedObject[key].findIndex( + (existingSubItem) => + Object.keys(existingSubItem).some((subKey) => + Object.prototype.hasOwnProperty.call(subItem, subKey), + ), + ); + + if (existingItemIndex !== -1) { + mergedObject[key][existingItemIndex] = { + ...mergedObject[key][existingItemIndex], + ...subItem, + }; + } else { + mergedObject[key].push(subItem); + } + }); + }); + }); + + return Object.keys(mergedObject).map((key) => ({ [key]: mergedObject[key] })); +}; diff --git a/tgui/packages/tgui/interfaces/Who.jsx b/tgui/packages/tgui/interfaces/Who.jsx index dccf3d1e39f0..5be9dc134b07 100644 --- a/tgui/packages/tgui/interfaces/Who.jsx +++ b/tgui/packages/tgui/interfaces/Who.jsx @@ -1,4 +1,6 @@ -import { useBackend, useLocalState } from '../backend'; +import { useState } from 'react'; + +import { useBackend } from '../backend'; import { Box, Button, @@ -11,20 +13,24 @@ import { import { Window } from '../layouts'; export const Who = (props, context) => { - const { act, data } = useBackend(context); + const { act, data } = useBackend(); const { - admin, - all_clients, - total_players = [], - additional_info = [], - factions = [], - xenomorphs = [], + base_data, + player_additional, + player_stealthed_additional, + factions_additional, } = data; - const [searchQuery, setSearchQuery] = useLocalState('searchQuery', ''); + const total_players = mergeArrays( + base_data?.total_players, + player_additional?.total_players, + player_stealthed_additional?.total_players, + ); + + const [searchQuery, setSearchQuery] = useState(''); const searchPlayers = () => - total_players.filter((player) => isMatch(player, searchQuery)); + total_players.filter((playerObj) => isMatch(playerObj, searchQuery)); const filteredTotalPlayers = searchPlayers(); @@ -42,11 +48,13 @@ export const Who = (props, context) => { + onEnter={(e, value) => { + const clientObj = searchPlayers()?.[0]; + if (!clientObj) return; act('get_player_panel', { - ckey: searchPlayers()?.[0].ckey, - }) - } + ckey: Object.keys(clientObj)[0], + }); + }} onInput={(e) => setSearchQuery(e.target.value)} placeholder="Search..." value={searchQuery} @@ -56,55 +64,34 @@ export const Who = (props, context) => { -
- - {filteredTotalPlayers.length ? ( + {filteredTotalPlayers && ( +
+ - {filteredTotalPlayers.map((x) => ( - - ))} + - ) : null} - -
- {admin !== 0 ? ( +
+
+ )} + {factions_additional && (
- {additional_info.length - ? additional_info.map((x, index) => ( - - )) - : null} - {factions.length - ? factions.map((x, index) => ( - - )) - : null} - {xenomorphs.length - ? xenomorphs.map((x, index) => ( - - )) - : null} + {factions_additional.map((x, index) => ( + + ))}
- ) : null} + )}
@@ -122,16 +109,15 @@ const WhoCollapsible = (props, context) => { }; const GetAddInfo = (props, context) => { - const { act } = useBackend(context); const { content, color, text } = props; return ( - ) : ( - ); }; -const isMatch = (player, searchQuery) => { +const isMatch = (playerObj, searchQuery) => { if (!searchQuery) { return true; } - return ( - player.ckey.toLowerCase().includes(searchQuery?.toLowerCase()) || false - ); + const key = Object.keys(playerObj)[0]; + return key.toLowerCase().includes(searchQuery?.toLowerCase()) || false; +}; + +// Krill me please +const mergeArrays = (...arrays) => { + const mergedObject = {}; + + arrays.forEach((array) => { + if (!array) return; + + array.forEach((item) => { + if (!item) return; + + const key = Object.keys(item)[0]; + const value = item[key]; + + if (!mergedObject[key]) { + mergedObject[key] = []; + } + + value.forEach((subItem) => { + if (typeof subItem !== 'object' || subItem === null) return; + + const existingItemIndex = mergedObject[key].findIndex( + (existingSubItem) => + Object.keys(existingSubItem).some((subKey) => + Object.prototype.hasOwnProperty.call(subItem, subKey), + ), + ); + + if (existingItemIndex !== -1) { + mergedObject[key][existingItemIndex] = { + ...mergedObject[key][existingItemIndex], + ...subItem, + }; + } else { + mergedObject[key].push(subItem); + } + }); + }); + }); + + return Object.keys(mergedObject).map((key) => ({ [key]: mergedObject[key] })); };