From dfd29100b4a2a60e1fd0dec04f8a58496b915824 Mon Sep 17 00:00:00 2001 From: Lucy Date: Fri, 22 Sep 2023 10:46:42 -0400 Subject: [PATCH] TGUI Crew Manifest: Shion Edition (#9836) * TGUI Crew Manifest: Shion Edition * Merge department color SCSS * fixy * Some improvements. * More tweaks to satisfy reviews * Remove unused typedefs * And an unused import * Address reviews * That looks less ugly... * Candystripe * No more autoupdate * Address reviews --- beestation.dme | 2 + code/__DEFINES/dcs/signals/signals_global.dm | 2 + code/__DEFINES/jobs.dm | 16 -- code/__HELPERS/_lists.dm | 12 ++ code/__HELPERS/jobs.dm | 166 ++++++++++-------- code/_globalvars/lists/jobs.dm | 30 ++++ code/datums/datacore.dm | 50 +++--- code/modules/client/client_defines.dm | 2 +- code/modules/mob/dead/crew_manifest.dm | 43 +++++ .../modules/mob/dead/new_player/new_player.dm | 13 +- code/modules/mob/dead/observer/observer.dm | 13 +- code/modules/mob/living/silicon/silicon.dm | 11 +- .../packages/tgui/interfaces/CrewManifest.tsx | 98 +++++++++++ tgui/packages/tgui/styles/colors.scss | 15 ++ .../tgui/styles/interfaces/CrewManifest.scss | 46 +++++ .../styles/interfaces/PreferencesMenu.scss | 16 +- tgui/packages/tgui/styles/main.scss | 1 + 17 files changed, 374 insertions(+), 162 deletions(-) create mode 100644 code/_globalvars/lists/jobs.dm create mode 100644 code/modules/mob/dead/crew_manifest.dm create mode 100644 tgui/packages/tgui/interfaces/CrewManifest.tsx create mode 100644 tgui/packages/tgui/styles/interfaces/CrewManifest.scss diff --git a/beestation.dme b/beestation.dme index 875317fa484ce..3ec1a6e9d73e4 100644 --- a/beestation.dme +++ b/beestation.dme @@ -304,6 +304,7 @@ #include "code\_globalvars\lists\ambience.dm" #include "code\_globalvars\lists\client.dm" #include "code\_globalvars\lists\flavor_misc.dm" +#include "code\_globalvars\lists\jobs.dm" #include "code\_globalvars\lists\maintenance_loot.dm" #include "code\_globalvars\lists\mapping.dm" #include "code\_globalvars\lists\mobs.dm" @@ -2846,6 +2847,7 @@ #include "code\modules\mob\transform_procs.dm" #include "code\modules\mob\update_icons.dm" #include "code\modules\mob\camera\camera.dm" +#include "code\modules\mob\dead\crew_manifest.dm" #include "code\modules\mob\dead\dead.dm" #include "code\modules\mob\dead\new_player\login.dm" #include "code\modules\mob\dead\new_player\logout.dm" diff --git a/code/__DEFINES/dcs/signals/signals_global.dm b/code/__DEFINES/dcs/signals/signals_global.dm index d5d5f1b418693..9ea10b18ad803 100644 --- a/code/__DEFINES/dcs/signals/signals_global.dm +++ b/code/__DEFINES/dcs/signals/signals_global.dm @@ -35,3 +35,5 @@ #define COMSIG_GLOB_POST_START "!post_start" /// Called when the parallax background changes colour. (new_colour, transition_time) #define COMSIG_GLOB_STARLIGHT_COLOUR_CHANGE "!starlight_colour_change" +/// Called whenever the crew manifest is updated +#define COMSIG_GLOB_CREW_MANIFEST_UPDATE "!crew_manifest_update" diff --git a/code/__DEFINES/jobs.dm b/code/__DEFINES/jobs.dm index 281ad8ab25e3a..e0383f51ddb5d 100644 --- a/code/__DEFINES/jobs.dm +++ b/code/__DEFINES/jobs.dm @@ -114,22 +114,6 @@ #define DEPT_BITFLAG_CAPTAIN (1<<10) #define DEPT_BITFLAG_ASSISTANT (1<<11) -/// For use in the preferences menu. -GLOBAL_LIST_INIT(dept_bitflag_to_name, list( - "[DEPT_BITFLAG_COM]" = "Command", - "[DEPT_BITFLAG_CIV]" = "Civilian", - "[DEPT_BITFLAG_SRV]" = "Service", - "[DEPT_BITFLAG_CAR]" = "Cargo", - "[DEPT_BITFLAG_SCI]" = "Science", - "[DEPT_BITFLAG_ENG]" = "Engineering", - "[DEPT_BITFLAG_MED]" = "Medical", - "[DEPT_BITFLAG_SEC]" = "Security", - "[DEPT_BITFLAG_VIP]" = "Very Important People", - "[DEPT_BITFLAG_SILICON]" = "Silicon", - "[DEPT_BITFLAG_CAPTAIN]" = "Captain", - "[DEPT_BITFLAG_ASSISTANT]" = "Assistant" -)) - // should check the ones in `\_DEFINES\economy.dm` // It's true that bitflags shouldn't be separated in two DEFINES if these are same, but just in case the system can be devided, it's remained separated. diff --git a/code/__HELPERS/_lists.dm b/code/__HELPERS/_lists.dm index 362e95550aa6f..caa0951c8a138 100644 --- a/code/__HELPERS/_lists.dm +++ b/code/__HELPERS/_lists.dm @@ -768,3 +768,15 @@ stack_trace("[name] is not sorted. value at [index] ([value]) is in the wrong place compared to the previous value of [last_value] (when compared to by [cmp])") last_value = value + +/** + * Converts a normal array list to an associated list, with the keys being the original values, and the value being the index of the value in the original list. + * All keys are converted to strings. + * Example: list("a", "b", 1, 2, 3) -> list("a" = 1, "b" = 2, "1" = 3, "2" = 4, "3" = 5) +*/ +/proc/list_to_assoc_index(list/input) + . = list() + for(var/i = 1 to length(input)) + var/key = "[input[i]]" + if(isnull(.[key])) + .[key] = i diff --git a/code/__HELPERS/jobs.dm b/code/__HELPERS/jobs.dm index b50576932ede8..c51f774726a49 100644 --- a/code/__HELPERS/jobs.dm +++ b/code/__HELPERS/jobs.dm @@ -74,88 +74,101 @@ ) return id_style[jobname] || "noname" // default: a card with no shape -// This returns a hud icon (from `hud.dmi`) by given job name. -// Some custom title is from `PDApainter.dm`. You neec to check it if you're going to remove custom job. -/proc/get_hud_by_jobname(jobname, returns_unknown=TRUE) - if(!jobname) - CRASH("The proc has taken a null value") +// You really shouldn't use this directly. +// Use get_hud_by_jobname unless you NEED direct access to this, i.e for the crew manifest tgui data +GLOBAL_LIST_INIT(id_to_hud, list( + // Command + JOB_NAME_CAPTAIN = JOB_HUD_CAPTAIN, + "Acting Captain" = JOB_HUD_ACTINGCAPTAIN , + "Command (Custom)" = JOB_HUD_RAWCOMMAND, - var/static/id_to_hud = list( - // Command - "Command (Custom)" = JOB_HUD_RAWCOMMAND, - JOB_NAME_CAPTAIN = JOB_HUD_CAPTAIN, - "Acting Captain" = JOB_HUD_ACTINGCAPTAIN , + // Service + JOB_NAME_HEADOFPERSONNEL = JOB_HUD_HEADOFPERSONNEL, + JOB_NAME_ASSISTANT = JOB_HUD_ASSISTANT, + JOB_NAME_BARTENDER = JOB_HUD_BARTENDER, + JOB_NAME_COOK = JOB_HUD_COOK, + JOB_NAME_BOTANIST = JOB_HUD_BOTANIST, + JOB_NAME_CURATOR = JOB_HUD_CURATOR, + JOB_NAME_CHAPLAIN = JOB_HUD_CHAPLAIN, + JOB_NAME_JANITOR = JOB_HUD_JANITOR, + JOB_NAME_LAWYER = JOB_HUD_LAWYER, + JOB_NAME_MIME = JOB_HUD_MIME, + JOB_NAME_CLOWN = JOB_HUD_CLOWN, + JOB_NAME_STAGEMAGICIAN = JOB_HUD_STAGEMAGICIAN, + JOB_NAME_BARBER = JOB_HUD_BARBER, + "Service (Custom)" = JOB_HUD_RAWSERVICE, - // Service - "Service (Custom)" = JOB_HUD_RAWSERVICE, - JOB_NAME_HEADOFPERSONNEL = JOB_HUD_HEADOFPERSONNEL, - JOB_NAME_ASSISTANT = JOB_HUD_ASSISTANT, - JOB_NAME_BARTENDER = JOB_HUD_BARTENDER, - JOB_NAME_COOK = JOB_HUD_COOK, - JOB_NAME_BOTANIST = JOB_HUD_BOTANIST, - JOB_NAME_CURATOR = JOB_HUD_CURATOR, - JOB_NAME_CHAPLAIN = JOB_HUD_CHAPLAIN, - JOB_NAME_JANITOR = JOB_HUD_JANITOR, - JOB_NAME_LAWYER = JOB_HUD_LAWYER, - JOB_NAME_MIME = JOB_HUD_MIME, - JOB_NAME_CLOWN = JOB_HUD_CLOWN, - JOB_NAME_STAGEMAGICIAN = JOB_HUD_STAGEMAGICIAN, - JOB_NAME_BARBER = JOB_HUD_BARBER, + // Cargo + JOB_NAME_QUARTERMASTER = JOB_HUD_QUARTERMASTER, + JOB_NAME_CARGOTECHNICIAN = JOB_HUD_CARGOTECHNICIAN, + JOB_NAME_SHAFTMINER = JOB_HUD_SHAFTMINER, + "Cargo (Custom)" = JOB_HUD_RAWCARGO, - // Cargo - "Cargo (Custom)" = JOB_HUD_RAWCARGO, - JOB_NAME_QUARTERMASTER = JOB_HUD_QUARTERMASTER, - JOB_NAME_CARGOTECHNICIAN = JOB_HUD_CARGOTECHNICIAN, - JOB_NAME_SHAFTMINER = JOB_HUD_SHAFTMINER, + // R&D + JOB_NAME_RESEARCHDIRECTOR = JOB_HUD_RESEARCHDIRECTOR, + JOB_NAME_SCIENTIST = JOB_HUD_SCIENTIST, + JOB_NAME_ROBOTICIST = JOB_HUD_ROBOTICIST, + JOB_NAME_EXPLORATIONCREW = JOB_HUD_EXPLORATIONCREW, + "Science (Custom)" = JOB_HUD_RAWSCIENCE, - // R&D - "Science (Custom)" = JOB_HUD_RAWSCIENCE, - JOB_NAME_RESEARCHDIRECTOR = JOB_HUD_RESEARCHDIRECTOR, - JOB_NAME_SCIENTIST = JOB_HUD_SCIENTIST, - JOB_NAME_ROBOTICIST = JOB_HUD_ROBOTICIST, - JOB_NAME_EXPLORATIONCREW = JOB_HUD_EXPLORATIONCREW, + // Engineering + JOB_NAME_CHIEFENGINEER = JOB_HUD_CHIEFENGINEER, + JOB_NAME_STATIONENGINEER = JOB_HUD_STATIONENGINEER, + JOB_NAME_ATMOSPHERICTECHNICIAN = JOB_HUD_ATMOSPHERICTECHNICIAN, + "Engineering (Custom)" = JOB_HUD_RAWENGINEERING, - // Engineering - "Engineering (Custom)" = JOB_HUD_RAWENGINEERING, - JOB_NAME_CHIEFENGINEER = JOB_HUD_CHIEFENGINEER, - JOB_NAME_STATIONENGINEER = JOB_HUD_STATIONENGINEER, - JOB_NAME_ATMOSPHERICTECHNICIAN = JOB_HUD_ATMOSPHERICTECHNICIAN, + // Medical + JOB_NAME_CHIEFMEDICALOFFICER = JOB_HUD_CHEIFMEDICALOFFICIER, + JOB_NAME_MEDICALDOCTOR = JOB_HUD_MEDICALDOCTOR, + JOB_NAME_PARAMEDIC = JOB_HUD_PARAMEDIC, + JOB_NAME_VIROLOGIST = JOB_HUD_VIROLOGIST, + JOB_NAME_CHEMIST = JOB_HUD_CHEMIST, + JOB_NAME_GENETICIST = JOB_HUD_GENETICIST, + JOB_NAME_PSYCHIATRIST = JOB_HUD_PSYCHIATRIST, + "Medical (Custom)" = JOB_HUD_RAWMEDICAL, - // Medical - "Medical (Custom)" = JOB_HUD_RAWMEDICAL, - JOB_NAME_CHIEFMEDICALOFFICER = JOB_HUD_CHEIFMEDICALOFFICIER, - JOB_NAME_MEDICALDOCTOR = JOB_HUD_MEDICALDOCTOR, - JOB_NAME_PARAMEDIC = JOB_HUD_PARAMEDIC, - JOB_NAME_VIROLOGIST = JOB_HUD_VIROLOGIST, - JOB_NAME_CHEMIST = JOB_HUD_CHEMIST, - JOB_NAME_GENETICIST = JOB_HUD_GENETICIST, - JOB_NAME_PSYCHIATRIST = JOB_HUD_PSYCHIATRIST, + // Security + JOB_NAME_HEADOFSECURITY = JOB_HUD_HEADOFSECURITY, + JOB_NAME_SECURITYOFFICER = JOB_HUD_SECURITYOFFICER, + JOB_NAME_WARDEN = JOB_HUD_WARDEN, + JOB_NAME_DETECTIVE = JOB_HUD_DETECTIVE, + JOB_NAME_BRIGPHYSICIAN = JOB_HUD_BRIGPHYSICIAN, + JOB_NAME_DEPUTY = JOB_HUD_DEPUTY, + "Security (Custom)" = JOB_HUD_RAWSECURITY, - // Security - "Security (Custom)" = JOB_HUD_RAWSECURITY, - JOB_NAME_HEADOFSECURITY = JOB_HUD_HEADOFSECURITY, - JOB_NAME_SECURITYOFFICER = JOB_HUD_SECURITYOFFICER, - JOB_NAME_WARDEN = JOB_HUD_WARDEN, - JOB_NAME_DETECTIVE = JOB_HUD_DETECTIVE, - JOB_NAME_BRIGPHYSICIAN = JOB_HUD_BRIGPHYSICIAN, - JOB_NAME_DEPUTY = JOB_HUD_DEPUTY, + // CentCom + "CentCom" = JOB_HUD_CENTCOM, + "ERT" = JOB_HUD_CENTCOM, + "CentCom (Custom)" = JOB_HUD_RAWCENTCOM, - // CentCom - "CentCom (Custom)" = JOB_HUD_RAWCENTCOM, - "CentCom" = JOB_HUD_CENTCOM, - "ERT" = JOB_HUD_CENTCOM, + // ETC + JOB_NAME_VIP = JOB_HUD_VIP, + JOB_NAME_KING = JOB_HUD_KING, + "Syndicate Agent" = JOB_HUD_SYNDICATE, + "Clown Operative" = JOB_HUD_SYNDICATE, + "Unassigned" = JOB_HUD_UNKNOWN, + JOB_NAME_PRISONER = JOB_HUD_PRISONER +)) - // ETC - JOB_NAME_VIP = JOB_HUD_VIP, - JOB_NAME_KING = JOB_HUD_KING, - "Syndicate Agent" = JOB_HUD_SYNDICATE, - "Clown Operative" = JOB_HUD_SYNDICATE, - "Unassigned" = JOB_HUD_UNKNOWN, - JOB_NAME_PRISONER = JOB_HUD_PRISONER - ) +GLOBAL_LIST_INIT(command_huds, list( + JOB_HUD_CAPTAIN, + JOB_HUD_ACTINGCAPTAIN, + JOB_HUD_RAWCOMMAND, + JOB_HUD_HEADOFPERSONNEL, + JOB_HUD_RESEARCHDIRECTOR, + JOB_HUD_CHIEFENGINEER, + JOB_HUD_CHEIFMEDICALOFFICIER, + JOB_HUD_HEADOFSECURITY +)) + +// This returns a hud icon (from `hud.dmi`) by given job name. +// Some custom title is from `PDApainter.dm`. You neec to check it if you're going to remove custom job. +/proc/get_hud_by_jobname(jobname, returns_unknown=TRUE) + if(!jobname) + CRASH("The proc has taken a null value") if(returns_unknown) - return id_to_hud[jobname] || JOB_HUD_UNKNOWN // default: a grey unknown hud - return id_to_hud[jobname] // this will return null + return GLOB.id_to_hud[jobname] || JOB_HUD_UNKNOWN // default: a grey unknown hud + return GLOB.id_to_hud[jobname] // this will return null // used to determine chat color by HUD in `chatmessage.dm` // Note: custom colors are what I really didn't put much attention into. feel free to change its color when you feel off. @@ -237,3 +250,12 @@ ) return hud_to_chatcolor[jobname] || JOB_CHATCOLOR_UNKNOWN +/proc/get_job_departments(field) + . = list() + for(var/flag in GLOB.bitflags) + var/key = "[flag]" + var/department = GLOB.dept_bitflag_to_name[key] + if(!department || !GLOB.departments[department]) + continue + if(CHECK_BITFIELD(field, flag)) + . += department diff --git a/code/_globalvars/lists/jobs.dm b/code/_globalvars/lists/jobs.dm new file mode 100644 index 0000000000000..1b9642e40217e --- /dev/null +++ b/code/_globalvars/lists/jobs.dm @@ -0,0 +1,30 @@ +/// A list of each bitflag and the name of its associated department. For use in the preferences menu. +GLOBAL_LIST_INIT(dept_bitflag_to_name, list( + "[DEPT_BITFLAG_COM]" = "Command", + "[DEPT_BITFLAG_CIV]" = "Civilian", + "[DEPT_BITFLAG_SRV]" = "Service", + "[DEPT_BITFLAG_CAR]" = "Cargo", + "[DEPT_BITFLAG_SCI]" = "Science", + "[DEPT_BITFLAG_ENG]" = "Engineering", + "[DEPT_BITFLAG_MED]" = "Medical", + "[DEPT_BITFLAG_SEC]" = "Security", + "[DEPT_BITFLAG_VIP]" = "Very Important People", + "[DEPT_BITFLAG_SILICON]" = "Silicon", + "[DEPT_BITFLAG_CAPTAIN]" = "Captain", + "[DEPT_BITFLAG_ASSISTANT]" = "Assistant" +)) + +/// A list of each department and its associated bitflag. +GLOBAL_LIST_INIT(departments, list( + "Command" = DEPT_BITFLAG_COM, + "Very Important People" = DEPT_BITFLAG_VIP, + "Security" = DEPT_BITFLAG_SEC, + "Engineering" = DEPT_BITFLAG_ENG, + "Medical" = DEPT_BITFLAG_MED, + "Science" = DEPT_BITFLAG_SCI, + "Supply" = DEPT_BITFLAG_CAR, + "Cargo" = DEPT_BITFLAG_CAR, // code seems to switch between calling it Supply and Cargo. not going to fix that today, let's just split the difference. + "Service" = DEPT_BITFLAG_SRV, + "Civilian" = DEPT_BITFLAG_CIV, + "Silicon" = DEPT_BITFLAG_SILICON +)) diff --git a/code/datums/datacore.dm b/code/datums/datacore.dm index e281ff55d85c1..4b4dee4d96047 100644 --- a/code/datums/datacore.dm +++ b/code/datums/datacore.dm @@ -175,53 +175,41 @@ if(N.new_character) log_manifest(N.ckey,N.new_character.mind,N.new_character) if(ishuman(N.new_character)) - manifest_inject(N.new_character) + manifest_inject(N.new_character, send_signal = FALSE) CHECK_TICK + SEND_GLOBAL_SIGNAL(COMSIG_GLOB_CREW_MANIFEST_UPDATE) /datum/datacore/proc/manifest_modify(name, assignment, hudstate) var/datum/data/record/foundrecord = find_record("name", name, GLOB.data_core.general) if(foundrecord) foundrecord.fields["rank"] = assignment foundrecord.fields["hud"] = hudstate + SEND_GLOBAL_SIGNAL(COMSIG_GLOB_CREW_MANIFEST_UPDATE) /datum/datacore/proc/get_manifest() var/list/manifest_out = list() - var/list/dept_list = list( - "Command" = DEPT_BITFLAG_COM, - "Very Important People" = DEPT_BITFLAG_VIP, - "Security" = DEPT_BITFLAG_SEC, - "Engineering" = DEPT_BITFLAG_ENG, - "Medical" = DEPT_BITFLAG_MED, - "Science" = DEPT_BITFLAG_SCI, - "Supply" = DEPT_BITFLAG_CAR, - "Service" = DEPT_BITFLAG_SRV, - "Civilian" = DEPT_BITFLAG_CIV, - "Silicon" = DEPT_BITFLAG_SILICON - ) + var/static/list/heads = make_associative(GLOB.command_positions) + for(var/datum/data/record/t in GLOB.data_core.general) var/name = t.fields["name"] var/rank = t.fields["rank"] + var/hud = t.fields["hud"] var/dept_bitflags = t.fields["active_dept"] var/has_department = FALSE - for(var/department in dept_list) - if(dept_bitflags & dept_list[department]) - if(!manifest_out[department]) - manifest_out[department] = list() - manifest_out[department] += list(list( - "name" = name, - "rank" = rank - )) - has_department = TRUE + var/entry = list("name" = name, "rank" = rank, "hud" = hud) + for(var/department in get_job_departments(dept_bitflags)) + var/list/department_manifest = manifest_out[department] + if(!department_manifest) + manifest_out[department] = department_manifest = list() + // Append to beginning of list if captain or department head + var/put_at_top = hud == JOB_HUD_CAPTAIN || hud == JOB_HUD_ACTINGCAPTAIN || (department != DEPT_COMMAND && heads[rank]) + department_manifest.Insert(put_at_top, list(entry)) + has_department = TRUE if(!has_department) - if(!manifest_out["Misc"]) - manifest_out["Misc"] = list() - manifest_out["Misc"] += list(list( - "name" = name, - "rank" = rank - )) + LAZYADDASSOCLIST(manifest_out, "Misc", entry) //Sort the list by 'departments' primarily so command is on top. var/list/sorted_out = list() - for(var/department in (dept_list += "Misc")) + for(var/department in (assoc_to_keys(GLOB.departments) + "Misc")) if(!isnull(manifest_out[department])) sorted_out[department] = manifest_out[department] return sorted_out @@ -255,7 +243,7 @@ return dat -/datum/datacore/proc/manifest_inject(mob/living/carbon/human/H) +/datum/datacore/proc/manifest_inject(mob/living/carbon/human/H, send_signal = TRUE) set waitfor = FALSE var/static/list/show_directions = list(SOUTH, WEST) if(H.mind && (H.mind.assigned_role != H.mind.special_role)) @@ -340,6 +328,8 @@ L.fields["character_appearance"] = character_appearance L.fields["mindref"] = H.mind locked += L + if(send_signal) + SEND_GLOBAL_SIGNAL(COMSIG_GLOB_CREW_MANIFEST_UPDATE) return /** diff --git a/code/modules/client/client_defines.dm b/code/modules/client/client_defines.dm index a71d849dc64e5..1656c0ddc7fa4 100644 --- a/code/modules/client/client_defines.dm +++ b/code/modules/client/client_defines.dm @@ -115,7 +115,7 @@ var/last_completed_asset_job = 0 /// rate limiting for the crew manifest - var/crew_manifest_delay + COOLDOWN_DECLARE(crew_manifest_delay) //Tick when ghost roles are useable again var/next_ghost_role_tick = 0 diff --git a/code/modules/mob/dead/crew_manifest.dm b/code/modules/mob/dead/crew_manifest.dm new file mode 100644 index 0000000000000..3f2662910802f --- /dev/null +++ b/code/modules/mob/dead/crew_manifest.dm @@ -0,0 +1,43 @@ +GLOBAL_DATUM_INIT(crew_manifest_tgui, /datum/crew_manifest, new) + +/datum/crew_manifest/New() + . = ..() + RegisterSignal(SSdcs, COMSIG_GLOB_CREW_MANIFEST_UPDATE, PROC_REF(on_manifest_update)) + +/datum/crew_manifest/proc/on_manifest_update() + SIGNAL_HANDLER + ui_update() + +/datum/crew_manifest/ui_state(mob/user) + return GLOB.always_state + +/datum/crew_manifest/ui_status(mob/user, datum/ui_state/state) + var/static/list/allowed_mobs_typecache = typecacheof(list(/mob/dead, /mob/living/silicon)) + return is_type_in_typecache(user, allowed_mobs_typecache) ? UI_INTERACTIVE : UI_CLOSE + +/datum/crew_manifest/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "CrewManifest") + ui.open() + +/datum/crew_manifest/ui_static_data(mob/user) + var/static/list/ordering = list_to_assoc_index(flatten_list(GLOB.id_to_hud)) + return list( + "command" = list( + "name" = "Command", + "huds" = GLOB.command_huds, + "jobs" = GLOB.command_positions, + "order" = SSjob.chain_of_command + ), + "order" = ordering, + ) + +/datum/crew_manifest/ui_data(mob/user) + var/user_theme = null + if(isdead(user)) + user_theme = "generic" + return list("manifest" = GLOB.data_core.get_manifest(), "user_theme" = user_theme) + +/datum/crew_manifest/ui_assets(mob/user) + return list(get_asset_datum(/datum/asset/spritesheet/job_icons)) diff --git a/code/modules/mob/dead/new_player/new_player.dm b/code/modules/mob/dead/new_player/new_player.dm index 25ae9136a0091..82c101da2f0f4 100644 --- a/code/modules/mob/dead/new_player/new_player.dm +++ b/code/modules/mob/dead/new_player/new_player.dm @@ -477,17 +477,10 @@ qdel(src) /mob/dead/new_player/proc/ViewManifest() - if(!client) - return - if(world.time < client.crew_manifest_delay) + if(!client || !COOLDOWN_FINISHED(client, crew_manifest_delay)) return - client.crew_manifest_delay = world.time + (1 SECONDS) - - var/dat = "" - dat += "

Crew Manifest

" - dat += GLOB.data_core.get_manifest_html() - - src << browse(dat, "window=manifest;size=387x420;can_close=1") + COOLDOWN_START(client, crew_manifest_delay, 1 SECONDS) + GLOB.crew_manifest_tgui.ui_interact(src) /mob/dead/new_player/Move() return 0 diff --git a/code/modules/mob/dead/observer/observer.dm b/code/modules/mob/dead/observer/observer.dm index 55b5c0d317317..c59a2060159d8 100644 --- a/code/modules/mob/dead/observer/observer.dm +++ b/code/modules/mob/dead/observer/observer.dm @@ -671,17 +671,10 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp set name = "View Crew Manifest" set category = "Ghost" - if(!client) - return - if(world.time < client.crew_manifest_delay) + if(!client || !COOLDOWN_FINISHED(client, crew_manifest_delay)) return - client.crew_manifest_delay = world.time + (1 SECONDS) - - var/dat - dat += "

Crew Manifest

" - dat += GLOB.data_core.get_manifest_html() - - src << browse(dat, "window=manifest;size=387x420;can_close=1") + COOLDOWN_START(client, crew_manifest_delay, 1 SECONDS) + GLOB.crew_manifest_tgui.ui_interact(src) //this is called when a ghost is drag clicked to something. /mob/dead/observer/MouseDrop(atom/over) diff --git a/code/modules/mob/living/silicon/silicon.dm b/code/modules/mob/living/silicon/silicon.dm index 4df434aabd866..4ed9a04c30f0d 100644 --- a/code/modules/mob/living/silicon/silicon.dm +++ b/code/modules/mob/living/silicon/silicon.dm @@ -374,15 +374,10 @@ usr << browse(list, "window=laws") /mob/living/silicon/proc/ai_roster() - if(!client) + if(!client || !COOLDOWN_FINISHED(client, crew_manifest_delay)) return - if(world.time < client.crew_manifest_delay) - return - client.crew_manifest_delay = world.time + (1 SECONDS) - - var/datum/browser/popup = new(src, "airoster", "Crew Manifest", 387, 420) - popup.set_content(GLOB.data_core.get_manifest_html()) - popup.open() + COOLDOWN_START(client, crew_manifest_delay, 1 SECONDS) + GLOB.crew_manifest_tgui.ui_interact(src) /mob/living/silicon/proc/set_autosay() //For allowing the AI and borgs to set the radio behavior of auto announcements (state laws, arrivals). if(!radio) diff --git a/tgui/packages/tgui/interfaces/CrewManifest.tsx b/tgui/packages/tgui/interfaces/CrewManifest.tsx new file mode 100644 index 0000000000000..cba75cc027584 --- /dev/null +++ b/tgui/packages/tgui/interfaces/CrewManifest.tsx @@ -0,0 +1,98 @@ +import { sortBy } from '../../common/collections'; +import { BooleanLike, classes } from 'common/react'; +import { useBackend } from '../backend'; +import { Box, Icon, Section, Table, Tooltip } from '../components'; +import { Window } from '../layouts'; + +type DepartmentCrew = { [department: string]: ManifestEntry[] }; +type JobOrdering = { [job: string]: number }; + +const sortSpecific = (entries: ManifestEntry[], chain: JobOrdering) => + sortBy((entry) => chain[entry.hud] ?? Object.keys(chain).length + 1)(entries); + +type ManifestEntry = { + /** The name of this crew member. */ + name: string; + /** The rank of this crew member. */ + rank: string; + /** The HUD icon of this crew member. */ + hud: string; +}; + +type CommandInfo = { + /** The name of the 'superior' department. Honestly, this is always going to be "Command", but well, apparently this shouldn't be hardcoded, so yolo! */ + dept: string; + /** A (static) list of HUD icons used by command roles. */ + huds: string[]; + /** A (static) list of job titles considered to be command roles. */ + jobs: string[]; + /** The ordering of which heads of staff should be listed in the command section, according to chain of command. */ + order: JobOrdering; +}; + +type CrewManifestData = { + /** Information pertaining to the command department */ + command: CommandInfo; + /** The crew staffing each department. */ + manifest: DepartmentCrew; + /** The ordering of which jobs should be listed, based on HUD icon. */ + order: JobOrdering; + /** The TGUI theme to use. */ + user_theme?: string; +}; + +export const CrewManifest = (_props, context) => { + const { + data: { command, order, manifest, user_theme }, + } = useBackend(context); + + return ( + + + {Object.entries(manifest).map(([dept, crew]) => { + const sorted_jobs = dept === command.dept ? sortSpecific(crew, command.order) : sortSpecific(crew, order); + return ( +
+ + {Object.entries(sorted_jobs).map(([crewIndex, crewMember]) => { + const is_command = command.huds.includes(crewMember.hud) || command.jobs.includes(crewMember.rank); + return ( + + + {crewMember.name} + + + {is_command && ( + + + + )} + + + + {crewMember.rank} + + + ); + })} +
+
+ ); + })} +
+
+ ); +}; diff --git a/tgui/packages/tgui/styles/colors.scss b/tgui/packages/tgui/styles/colors.scss index d1a9fc4fd9de9..24c6a6acd1239 100644 --- a/tgui/packages/tgui/styles/colors.scss +++ b/tgui/packages/tgui/styles/colors.scss @@ -90,3 +90,18 @@ $bg-map: (); ) ); } + +$departments: ( + 'Assistant': $grey, + 'Captain': fg($blue), + 'Cargo': $brown, + 'Supply': $brown, + 'Civilian': $grey, + 'Command': $yellow, + 'Security': $red, + 'Engineering': #f1a839, + 'Medical': $teal, + 'Science': fg($purple), + 'Service': $green, + 'Silicon': $pink, +); diff --git a/tgui/packages/tgui/styles/interfaces/CrewManifest.scss b/tgui/packages/tgui/styles/interfaces/CrewManifest.scss new file mode 100644 index 0000000000000..af1b8fb0a8f29 --- /dev/null +++ b/tgui/packages/tgui/styles/interfaces/CrewManifest.scss @@ -0,0 +1,46 @@ +@use '../colors.scss'; + +.CrewManifest { + @each $department-name, $color-value in colors.$departments { + &--#{$department-name} { + .Section { + &__title { + border-color: $color-value; + } + &__titleText { + color: $color-value; + } + } + } + } + + &__Cell { + padding: 3px 0; + vertical-align: middle; + + &--Rank { + color: colors.$label; + } + } + + &__Icons { + text-align: right; + } + + &__Icon { + color: colors.$label; + position: relative; + + &:not(:last-child) { + margin-right: 7px; + } + + &--Chevron { + padding-right: 2px; + } + + &--Command { + color: colors.$yellow; + } + } +} diff --git a/tgui/packages/tgui/styles/interfaces/PreferencesMenu.scss b/tgui/packages/tgui/styles/interfaces/PreferencesMenu.scss index d19ce6445b48a..91acb4464e6d4 100644 --- a/tgui/packages/tgui/styles/interfaces/PreferencesMenu.scss +++ b/tgui/packages/tgui/styles/interfaces/PreferencesMenu.scss @@ -3,20 +3,6 @@ @use '../components/Button.scss'; @use '../colors.scss'; -$department_map: ( - 'Assistant': colors.$grey, - 'Captain': colors.fg(colors.$blue), - 'Cargo': colors.$brown, - 'Civilian': colors.$grey, - 'Command': colors.$yellow, - 'Security': colors.$red, - 'Engineering': #f1a839, - 'Medical': colors.$teal, - 'Science': colors.fg(colors.$purple), - 'Service': colors.$green, - 'Silicon': colors.$pink, -); - .PreferencesMenu { &__Main { .Preferences__standard-palette { @@ -224,7 +210,7 @@ $department_map: ( } &__departments { - @each $department-name, $color-value in $department_map { + @each $department-name, $color-value in colors.$departments { &--#{$department-name} { &.head { background: $color-value; diff --git a/tgui/packages/tgui/styles/main.scss b/tgui/packages/tgui/styles/main.scss index 9806b94565c50..892f72a63a69a 100644 --- a/tgui/packages/tgui/styles/main.scss +++ b/tgui/packages/tgui/styles/main.scss @@ -49,6 +49,7 @@ @include meta.load-css('./interfaces/ColorPicker.scss'); @include meta.load-css('./interfaces/AntagInfo.scss'); @include meta.load-css('./interfaces/CameraConsole.scss'); +@include meta.load-css('./interfaces/CrewManifest.scss'); @include meta.load-css('./interfaces/ClockworkSlab.scss'); @include meta.load-css('./interfaces/NuclearBomb.scss'); @include meta.load-css('./interfaces/ModularFabricator.scss');