Skip to content

Commit

Permalink
implements a new loadout picker tgui menu (#7524)
Browse files Browse the repository at this point in the history
MORE tgui slop yum yum more `<DmIcon />` please!

vido



https://github.com/user-attachments/assets/385f3aab-0df5-478b-b2e1-b7d0d85be67c

🆑
ui: adds a prettier custom loadout menu with images
/🆑

---------

Co-authored-by: Drathek <[email protected]>
  • Loading branch information
harryob and Drulikar authored Nov 9, 2024
1 parent 065e509 commit ec29911
Show file tree
Hide file tree
Showing 8 changed files with 251 additions and 56 deletions.
89 changes: 89 additions & 0 deletions code/modules/client/loadout_picker.dm
Original file line number Diff line number Diff line change
@@ -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
72 changes: 19 additions & 53 deletions code/modules/client/preferences.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -396,7 +397,7 @@ GLOBAL_LIST_INIT(bgstate_options, list(
dat += "<b>Preferred Armor:</b> <a href ='?_src_=prefs;preference=prefarmor;task=input'><b>[preferred_armor]</b></a><br>"

dat += "<b>Show Job Gear:</b> <a href ='?_src_=prefs;preference=toggle_job_gear'><b>[show_job_gear ? "True" : "False"]</b></a><br>"
dat += "<b>Background:</b> <a href ='?_src_=prefs;preference=cycle_bg'><b>Cycle Background</b></a><br>"
dat += "<b>Background:</b> <a href ='?_src_=prefs;preference=cycle_bg'><b>Cycle Background</b></a><br><br>"

dat += "<b>Custom Loadout:</b> "
var/total_cost = 0
Expand All @@ -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) <a href='byond://?src=\ref[user];preference=loadout;task=remove;gear=[i]'><b>Remove</b></a><br>"
dat += "[gear[i]] ([G.cost] points)<br>"

dat += "<b>Used:</b> [total_cost] points"
else
dat += "None"

if(total_cost < MAX_GEAR_COST)
dat += " <a href='byond://?src=\ref[user];preference=loadout;task=input'><b>Add</b></a>"
if(LAZYLEN(gear))
dat += " <a href='byond://?src=\ref[user];preference=loadout;task=clear'><b>Clear</b></a>"
dat += "<br><a href='byond://?src=\ref[user];preference=loadout'><b>Open Loadout</b></a>"

dat += "</div>"

Expand Down Expand Up @@ -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"])
Expand Down Expand Up @@ -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))
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions code/modules/client/preferences_gear.dm
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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.
Expand Down
1 change: 1 addition & 0 deletions colonialmarines.dme
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
131 changes: 131 additions & 0 deletions tgui/packages/tgui/interfaces/LoadoutPicker.tsx
Original file line number Diff line number Diff line change
@@ -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<LoadoutPickerData>();

const { categories, points, max_points, loadout } = data;

const [selected, setSelected] = useState(categories[0]);

return (
<Window height={485} width={610} theme="crtblue">
<Window.Content className="LoadoutPicker">
<Stack fill>
<Stack.Item>
<Stack vertical fill>
<Stack.Item>
<Section scrollable height="220px">
<Stack vertical height="200px">
{categories.map((category) => (
<Stack.Item key={category.name}>
<Button
fluid
selected={selected === category}
onClick={() => setSelected(category)}
>
{category.name}
</Button>
</Stack.Item>
))}
</Stack>
</Section>
</Stack.Item>
<Stack.Item grow>
<Section
title={`Loadout (${points}/${max_points} points)`}
height="100%"
scrollable
>
<Stack wrap width="180px" height="165px">
{loadout.map((item) => (
<Stack.Item key={item.name} className="ItemPicker">
<ItemRender item={item} loadout />
</Stack.Item>
))}
</Stack>
</Section>
</Stack.Item>
</Stack>
</Stack.Item>
<Stack.Item grow>
<Section title={selected.name} fill width="100%" scrollable>
<Stack wrap>
{selected.items.map((item) => (
<Stack.Item key={item.name} className="ItemPicker">
<ItemRender item={item} />
</Stack.Item>
))}
</Stack>
</Section>
</Stack.Item>
</Stack>
</Window.Content>
</Window>
);
};

const ItemRender = (props: {
readonly item: LoadoutItem;
readonly loadout?: boolean;
}) => {
const { item, loadout } = props;

const { icon, icon_state, name, cost } = item;

const { data, act } = useBackend<LoadoutPickerData>();

const { points, max_points } = data;

const atLimit = points + cost > max_points;

return (
<Stack>
<Stack.Item>
<Button
tooltip={name}
onClick={() => act(loadout ? 'remove' : 'add', { name: name })}
disabled={!loadout && atLimit}
width="78px"
height="74px"
mb="3px"
mt="3px"
>
<DmIcon
icon={icon}
icon_state={icon_state}
height="64px"
width="64px"
mb={1}
mt={1}
fallback={<Loader />}
/>
<Box position="absolute" bottom="0px">
{cost}
</Box>
</Button>
</Stack.Item>
</Stack>
);
};
7 changes: 7 additions & 0 deletions tgui/packages/tgui/styles/interfaces/LoadoutPicker.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.theme-crtblue {
.LoadoutPicker {
.Stack--horizontal > .ItemPicker:first-of-type {
margin-left: 6px;
}
}
}
1 change: 1 addition & 0 deletions tgui/packages/tgui/styles/main.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down
2 changes: 1 addition & 1 deletion tgui/packages/tgui/styles/themes/crt.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down

0 comments on commit ec29911

Please sign in to comment.