diff --git a/code/modules/client/loadout_picker.dm b/code/modules/client/loadout_picker.dm new file mode 100644 index 000000000000..b535a5deb1d0 --- /dev/null +++ b/code/modules/client/loadout_picker.dm @@ -0,0 +1,89 @@ +/datum/loadout_picker/ui_static_data(mob/user) + . = ..() + + .["categories"] = list() + for(var/category in GLOB.gear_datums_by_category) + var/list/datum/gear/gears = GLOB.gear_datums_by_category[category] + + var/items = list() + + for(var/gear_key as anything in gears) + var/datum/gear/gear = gears[gear_key] + items += list( + list("name" = gear.display_name, "cost" = gear.cost, "icon" = gear.path::icon, "icon_state" = gear.path::icon_state) + ) + + .["categories"] += list( + list("name" = category, "items" = items) + ) + + .["max_points"] = MAX_GEAR_COST + +/datum/loadout_picker/ui_data(mob/user) + . = ..() + + var/datum/preferences/prefs = user.client?.prefs + if(!prefs) + return + + var/points = 0 + + .["loadout"] = list() + + for(var/item in prefs.gear) + var/datum/gear/gear = GLOB.gear_datums_by_name[item] + points += gear.cost + + .["loadout"] += list( + list("name" = gear.display_name, "cost" = gear.cost, "icon" = gear.path::icon, "icon_state" = gear.path::icon_state) + ) + + .["points"] = points + +/datum/loadout_picker/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state) + . = ..() + + var/datum/preferences/prefs = ui.user.client?.prefs + if(!prefs) + return + + switch(action) + if("add") + var/datum/gear/gear = GLOB.gear_datums_by_name[params["name"]] + if(!istype(gear)) + return + + var/total_cost = 0 + for(var/gear_name in prefs.gear) + total_cost += GLOB.gear_datums_by_name[gear_name].cost + + total_cost += gear.cost + if(total_cost > MAX_GEAR_COST) + return + + prefs.gear += gear.display_name + + if("remove") + var/datum/gear/gear = GLOB.gear_datums_by_name[params["name"]] + if(!istype(gear)) + return + + prefs.gear -= gear.display_name + + prefs.ShowChoices(ui.user) + return TRUE + +/datum/loadout_picker/tgui_interact(mob/user, datum/tgui/ui) + . = ..() + + ui = SStgui.try_update_ui(user, src, ui) + + if(!ui) + ui = new(user, src, "LoadoutPicker", "Loadout Picker") + ui.open() + ui.set_autoupdate(FALSE) + + winset(user, ui.window.id, "focus=true") + +/datum/loadout_picker/ui_state(mob/user) + return GLOB.always_state diff --git a/code/modules/client/preferences.dm b/code/modules/client/preferences.dm index 5096ebc7dd53..9f7f2b5338ea 100644 --- a/code/modules/client/preferences.dm +++ b/code/modules/client/preferences.dm @@ -32,6 +32,7 @@ GLOBAL_LIST_INIT(bgstate_options, list( var/static/datum/hair_picker/hair_picker = new var/static/datum/body_picker/body_picker = new + var/static/datum/loadout_picker/loadout_picker = new //doohickeys for savefiles var/path @@ -396,7 +397,7 @@ GLOBAL_LIST_INIT(bgstate_options, list( dat += "Preferred Armor: [preferred_armor]
" dat += "Show Job Gear: [show_job_gear ? "True" : "False"]
" - dat += "Background: Cycle Background
" + dat += "Background: Cycle Background

" dat += "Custom Loadout: " var/total_cost = 0 @@ -410,16 +411,13 @@ GLOBAL_LIST_INIT(bgstate_options, list( var/datum/gear/G = GLOB.gear_datums_by_name[gear[i]] if(G) total_cost += G.cost - dat += "[gear[i]] ([G.cost] points) Remove
" + dat += "[gear[i]] ([G.cost] points)
" dat += "Used: [total_cost] points" else dat += "None" - if(total_cost < MAX_GEAR_COST) - dat += " Add" - if(LAZYLEN(gear)) - dat += " Clear" + dat += "
Open Loadout" dat += "" @@ -1033,39 +1031,8 @@ GLOBAL_LIST_INIT(bgstate_options, list( set_job_slots(user) return TRUE if("loadout") - switch(href_list["task"]) - if("input") - var/gear_category = tgui_input_list(user, "Select gear category: ", "Gear to add", GLOB.gear_datums_by_category) - if(!gear_category) - return - var/choice = tgui_input_list(user, "Select gear to add: ", gear_category, GLOB.gear_datums_by_category[gear_category]) - if(!choice) - return - - var/total_cost = 0 - var/datum/gear/G - if(isnull(gear) || !islist(gear)) - gear = list() - if(length(gear)) - for(var/gear_name in gear) - G = GLOB.gear_datums_by_name[gear_name] - total_cost += G?.cost - - G = GLOB.gear_datums_by_category[gear_category][choice] - total_cost += G.cost - if(total_cost <= MAX_GEAR_COST) - gear += G.display_name - to_chat(user, SPAN_NOTICE("Added \the '[G.display_name]' for [G.cost] points ([MAX_GEAR_COST - total_cost] points remaining).")) - else - to_chat(user, SPAN_WARNING("Adding \the '[choice]' will exceed the maximum loadout cost of [MAX_GEAR_COST] points.")) - - if("remove") - var/i_remove = text2num(href_list["gear"]) - if(i_remove < 1 || i_remove > length(gear)) return - gear.Cut(i_remove, i_remove + 1) - - if("clear") - gear.Cut() + loadout_picker.tgui_interact(user) + return if("flavor_text") switch(href_list["task"]) @@ -1938,13 +1905,7 @@ GLOBAL_LIST_INIT(bgstate_options, list( load_character() reload_cooldown = world.time + 50 - // Refresh pickers - var/datum/tgui/picker_ui = SStgui.get_open_ui(user, hair_picker) - if(picker_ui) - picker_ui.send_update() - picker_ui = SStgui.get_open_ui(user, body_picker) - if(picker_ui) - picker_ui.send_update() + update_all_pickers(user) if("open_load_dialog") if(!IsGuestKey(user.key)) @@ -1962,13 +1923,7 @@ GLOBAL_LIST_INIT(bgstate_options, list( if(istype(np)) np.new_player_panel_proc() - // Refresh pickers - var/datum/tgui/picker_ui = SStgui.get_open_ui(user, hair_picker) - if(picker_ui) - picker_ui.send_update() - picker_ui = SStgui.get_open_ui(user, body_picker) - if(picker_ui) - picker_ui.send_update() + update_all_pickers(user) if("tgui_fancy") tgui_fancy = !tgui_fancy @@ -2325,6 +2280,17 @@ GLOBAL_LIST_INIT(bgstate_options, list( completed_tutorials = splittext(savestring, ";") return completed_tutorials +/// Refreshes all open TGUI interfaces inside the character prefs menu +/datum/preferences/proc/update_all_pickers(mob/user) + var/datum/tgui/picker_ui = SStgui.get_open_ui(user, hair_picker) + picker_ui?.send_update() + + picker_ui = SStgui.get_open_ui(user, body_picker) + picker_ui?.send_update() + + picker_ui = SStgui.get_open_ui(user, loadout_picker) + picker_ui?.send_update() + #undef MENU_MARINE #undef MENU_XENOMORPH #undef MENU_CO diff --git a/code/modules/client/preferences_gear.dm b/code/modules/client/preferences_gear.dm index 6f649eb61ddb..dc35ba261b2c 100644 --- a/code/modules/client/preferences_gear.dm +++ b/code/modules/client/preferences_gear.dm @@ -1,5 +1,5 @@ GLOBAL_LIST_EMPTY(gear_datums_by_category) -GLOBAL_LIST_EMPTY(gear_datums_by_name) +GLOBAL_LIST_EMPTY_TYPED(gear_datums_by_name, /datum/gear) /proc/populate_gear_list() var/datum/gear/G @@ -19,7 +19,7 @@ GLOBAL_LIST_EMPTY(gear_datums_by_name) /datum/gear var/display_name // Name/index. var/category //Used for sorting in the loadout selection. - var/path // Path to item. + var/obj/item/path // Path to item. var/cost = 2 // Number of points used. var/slot // Slot to equip to, if any. var/list/allowed_roles // Roles that can spawn with this item. diff --git a/colonialmarines.dme b/colonialmarines.dme index bc20c8cef91b..ef502051b1b5 100644 --- a/colonialmarines.dme +++ b/colonialmarines.dme @@ -1564,6 +1564,7 @@ #include "code\modules\client\color_picker.dm" #include "code\modules\client\country_flags.dm" #include "code\modules\client\hair_picker.dm" +#include "code\modules\client\loadout_picker.dm" #include "code\modules\client\player_details.dm" #include "code\modules\client\preferences.dm" #include "code\modules\client\preferences_factions.dm" diff --git a/tgui/packages/tgui/interfaces/LoadoutPicker.tsx b/tgui/packages/tgui/interfaces/LoadoutPicker.tsx new file mode 100644 index 000000000000..08f7285bf2c5 --- /dev/null +++ b/tgui/packages/tgui/interfaces/LoadoutPicker.tsx @@ -0,0 +1,131 @@ +import { useState } from 'react'; + +import { useBackend } from '../backend'; +import { Box, Button, DmIcon, Section, Stack } from '../components'; +import { Window } from '../layouts'; +import { Loader } from './common/Loader'; + +type LoadoutPickerData = { + categories: { + name: string; + items: LoadoutItem[]; + }[]; + points: number; + max_points: number; + loadout: LoadoutItem[]; +}; + +type LoadoutItem = { + name: string; + cost: number; + icon: string; + icon_state: string; +}; + +export const LoadoutPicker = () => { + const { data } = useBackend(); + + const { categories, points, max_points, loadout } = data; + + const [selected, setSelected] = useState(categories[0]); + + return ( + + + + + + +
+ + {categories.map((category) => ( + + + + ))} + +
+
+ +
+ + {loadout.map((item) => ( + + + + ))} + +
+
+
+
+ +
+ + {selected.items.map((item) => ( + + + + ))} + +
+
+
+
+
+ ); +}; + +const ItemRender = (props: { + readonly item: LoadoutItem; + readonly loadout?: boolean; +}) => { + const { item, loadout } = props; + + const { icon, icon_state, name, cost } = item; + + const { data, act } = useBackend(); + + const { points, max_points } = data; + + const atLimit = points + cost > max_points; + + return ( + + + + + + ); +}; diff --git a/tgui/packages/tgui/styles/interfaces/LoadoutPicker.scss b/tgui/packages/tgui/styles/interfaces/LoadoutPicker.scss new file mode 100644 index 000000000000..4e77b3e9f1e7 --- /dev/null +++ b/tgui/packages/tgui/styles/interfaces/LoadoutPicker.scss @@ -0,0 +1,7 @@ +.theme-crtblue { + .LoadoutPicker { + .Stack--horizontal > .ItemPicker:first-of-type { + margin-left: 6px; + } + } +} diff --git a/tgui/packages/tgui/styles/main.scss b/tgui/packages/tgui/styles/main.scss index dbc514428cae..d8995a546f47 100644 --- a/tgui/packages/tgui/styles/main.scss +++ b/tgui/packages/tgui/styles/main.scss @@ -64,6 +64,7 @@ @include meta.load-css('./interfaces/ElevatorControl.scss'); @include meta.load-css('./interfaces/ExperimentConfigure.scss'); @include meta.load-css('./interfaces/HairPicker.scss'); +@include meta.load-css('./interfaces/LoadoutPicker.scss'); @include meta.load-css('./interfaces/MarkMenu.scss'); @include meta.load-css('./interfaces/MedalsViewer.scss'); @include meta.load-css('./interfaces/NavigationShuttle.scss'); diff --git a/tgui/packages/tgui/styles/themes/crt.scss b/tgui/packages/tgui/styles/themes/crt.scss index 71043439b4cf..ba4264ddb1fb 100644 --- a/tgui/packages/tgui/styles/themes/crt.scss +++ b/tgui/packages/tgui/styles/themes/crt.scss @@ -165,7 +165,7 @@ $scrollbar-color-multiplier: 0.5 !default; font-weight: bold; } - .Button { + .Button:not(.Button--selected) { font-family: $font-family; font-weight: bold; border: 1px solid base.$color-fg;