mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2025-12-10 01:34:01 +00:00
Adds Character Loadout Tab to preferences (with just a small handful of items to start) (#83521)
## About The Pull Request Adds a Character Loadout Tab to the preference menu This tab lets you pick items to start the round with  This also has some additional mechanics, such as being able to recolor colorable items, rename certain items (such as plushies), set item skins (such as the pride pin)  ## Why It's Good For The Game This has been headcoder sanctioned for some time, just no one did it. So here we are. Allows players to add some additional customization to their characters. Keeps us from cluttering the quirks list with quirks that do nothing but grants items. ## Changelog 🆑 Melbert add: Character Loadouts del: Pride Pin quirk (it's in the Loadout menu now) /🆑
This commit is contained in:
@@ -142,3 +142,17 @@
|
||||
|
||||
/// The key used for sprite accessories that should never actually be applied to the player.
|
||||
#define SPRITE_ACCESSORY_NONE "None"
|
||||
|
||||
// Loadout
|
||||
/// Used to make something not recolorable even if it's capable
|
||||
#define DONT_GREYSCALE -1
|
||||
// Loadout item info keys
|
||||
// Changing these will break existing loadouts
|
||||
/// Tracks GAGS color information
|
||||
#define INFO_GREYSCALE "greyscale"
|
||||
/// Used to set custom names
|
||||
#define INFO_NAMED "name"
|
||||
/// Used for specific alt-reskins, like the pride pin
|
||||
#define INFO_RESKIN "reskin"
|
||||
/// Handles which layer the item will be on, for accessories
|
||||
#define INFO_LAYER "layer"
|
||||
|
||||
@@ -1151,4 +1151,7 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
|
||||
/// Does this item bypass ranged armor checks?
|
||||
#define TRAIT_BYPASS_RANGED_ARMOR "bypass_ranged_armor"
|
||||
|
||||
/// This item cannot be selected for or used by a theft objective (Spies, Traitors, etc.)
|
||||
#define TRAIT_ITEM_OBJECTIVE_BLOCKED "item_objective_blocked"
|
||||
|
||||
// END TRAIT DEFINES
|
||||
|
||||
@@ -522,6 +522,7 @@ GLOBAL_LIST_INIT(traits_by_type, list(
|
||||
"TRAIT_HAUNTED" = TRAIT_HAUNTED,
|
||||
"TRAIT_HONKSPAMMING" = TRAIT_HONKSPAMMING,
|
||||
"TRAIT_INNATELY_FANTASTICAL_ITEM" = TRAIT_INNATELY_FANTASTICAL_ITEM,
|
||||
"TRAIT_ITEM_OBJECTIVE_BLOCKED" = TRAIT_ITEM_OBJECTIVE_BLOCKED,
|
||||
"TRAIT_NEEDS_TWO_HANDS" = TRAIT_NEEDS_TWO_HANDS,
|
||||
"TRAIT_NO_BARCODES" = TRAIT_NO_BARCODES,
|
||||
"TRAIT_NO_STORAGE_INSERT" = TRAIT_NO_STORAGE_INSERT,
|
||||
|
||||
@@ -557,7 +557,7 @@ SUBSYSTEM_DEF(job)
|
||||
SEND_SIGNAL(equipping, COMSIG_JOB_RECEIVED, job)
|
||||
|
||||
equipping.mind?.set_assigned_role_with_greeting(job, player_client)
|
||||
equipping.on_job_equipping(job)
|
||||
equipping.on_job_equipping(job, player_client)
|
||||
job.announce_job(equipping)
|
||||
|
||||
if(player_client?.holder)
|
||||
|
||||
@@ -10,7 +10,7 @@ GLOBAL_LIST_INIT_TYPED(quirk_blacklist, /list/datum/quirk, list(
|
||||
list(/datum/quirk/no_taste, /datum/quirk/vegetarian, /datum/quirk/deviant_tastes, /datum/quirk/gamer),
|
||||
list(/datum/quirk/pineapple_liker, /datum/quirk/pineapple_hater, /datum/quirk/gamer),
|
||||
list(/datum/quirk/alcohol_tolerance, /datum/quirk/light_drinker),
|
||||
list(/datum/quirk/item_quirk/clown_enjoyer, /datum/quirk/item_quirk/mime_fan, /datum/quirk/item_quirk/pride_pin),
|
||||
list(/datum/quirk/item_quirk/clown_enjoyer, /datum/quirk/item_quirk/mime_fan),
|
||||
list(/datum/quirk/bad_touch, /datum/quirk/friendly),
|
||||
list(/datum/quirk/extrovert, /datum/quirk/introvert),
|
||||
list(/datum/quirk/prosthetic_limb, /datum/quirk/quadruple_amputee, /datum/quirk/transhumanist, /datum/quirk/body_purist),
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
/datum/quirk/item_quirk/pride_pin
|
||||
name = "Pride Pin"
|
||||
desc = "Show off your pride with this changing pride pin!"
|
||||
icon = FA_ICON_RAINBOW
|
||||
value = 0
|
||||
gain_text = span_notice("You feel fruity.")
|
||||
lose_text = span_danger("You feel only slightly less fruity than before.")
|
||||
medical_record_text = "Patient appears to be fruity."
|
||||
|
||||
/datum/quirk/item_quirk/pride_pin/add_unique(client/client_source)
|
||||
var/obj/item/clothing/accessory/pride/pin = new(get_turf(quirk_holder))
|
||||
|
||||
var/pride_choice = client_source?.prefs?.read_preference(/datum/preference/choiced/pride_pin) || assoc_to_keys(GLOB.pride_pin_reskins)[1]
|
||||
var/pride_reskin = GLOB.pride_pin_reskins[pride_choice]
|
||||
|
||||
pin.current_skin = pride_choice
|
||||
pin.icon_state = pride_reskin
|
||||
|
||||
give_item_to_holder(pin, list(LOCATION_BACKPACK = ITEM_SLOT_BACKPACK, LOCATION_HANDS = ITEM_SLOT_HANDS))
|
||||
@@ -688,6 +688,9 @@ GLOBAL_LIST_EMPTY(possible_items)
|
||||
var/list/all_items = M.current.get_all_contents() //this should get things in cheesewheels, books, etc.
|
||||
|
||||
for(var/obj/I in all_items) //Check for items
|
||||
if(HAS_TRAIT(I, TRAIT_ITEM_OBJECTIVE_BLOCKED))
|
||||
continue
|
||||
|
||||
if(istype(I, steal_target))
|
||||
if(!targetinfo) //If there's no targetinfo, then that means it was a custom objective. At this point, we know you have the item, so return 1.
|
||||
return TRUE
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
//Contains the target item datums for Steal objectives.
|
||||
/datum/objective_item
|
||||
/// How the item is described in the objective
|
||||
var/name = "A silly bike horn! Honk!"
|
||||
var/name = "a silly bike horn! Honk!"
|
||||
/// Typepath of item
|
||||
var/targetitem = /obj/item/bikehorn
|
||||
/// Valid containers that the target item can be in.
|
||||
@@ -566,7 +566,7 @@
|
||||
// A number of special early-game steal objectives intended to be used with the steal-and-destroy objective.
|
||||
// They're basically items of utility or emotional value that may be found on many players or lying around the station.
|
||||
/datum/objective_item/steal/traitor/insuls
|
||||
name = "insulated gloves"
|
||||
name = "some insulated gloves"
|
||||
targetitem = /obj/item/clothing/gloves/color/yellow
|
||||
excludefromjob = list(JOB_CARGO_TECHNICIAN, JOB_QUARTERMASTER, JOB_ATMOSPHERIC_TECHNICIAN, JOB_STATION_ENGINEER, JOB_CHIEF_ENGINEER)
|
||||
item_owner = list(JOB_STATION_ENGINEER, JOB_CHIEF_ENGINEER)
|
||||
@@ -578,7 +578,7 @@
|
||||
return add_item_to_steal(src, /obj/item/clothing/gloves/color/yellow)
|
||||
|
||||
/datum/objective_item/steal/traitor/moth_plush
|
||||
name = "cute moth plush toy"
|
||||
name = "a cute moth plush toy"
|
||||
targetitem = /obj/item/toy/plush/moth
|
||||
excludefromjob = list(JOB_PSYCHOLOGIST, JOB_PARAMEDIC, JOB_CHEMIST, JOB_MEDICAL_DOCTOR, JOB_CHIEF_MEDICAL_OFFICER, JOB_CORONER)
|
||||
exists_on_map = TRUE
|
||||
@@ -589,7 +589,7 @@
|
||||
return add_item_to_steal(src, /obj/item/toy/plush/moth)
|
||||
|
||||
/datum/objective_item/steal/traitor/lizard_plush
|
||||
name = "cute lizard plush toy"
|
||||
name = "a cute lizard plush toy"
|
||||
targetitem = /obj/item/toy/plush/lizard_plushie
|
||||
exists_on_map = TRUE
|
||||
difficulty = 1
|
||||
@@ -633,7 +633,7 @@
|
||||
return add_item_to_steal(src, /obj/item/book/manual/wiki/security_space_law)
|
||||
|
||||
/datum/objective_item/steal/traitor/rpd
|
||||
name = "rapid pipe dispenser"
|
||||
name = "a rapid pipe dispenser"
|
||||
targetitem = /obj/item/pipe_dispenser
|
||||
excludefromjob = list(
|
||||
JOB_ATMOSPHERIC_TECHNICIAN,
|
||||
@@ -679,7 +679,7 @@
|
||||
objective_type = OBJECTIVE_ITEM_TYPE_SPY
|
||||
|
||||
/datum/objective_item/steal/spy/lamarr
|
||||
name = "The Research Director's pet headcrab"
|
||||
name = "the Research Director's pet headcrab"
|
||||
targetitem = /obj/item/clothing/mask/facehugger/lamarr
|
||||
excludefromjob = list(JOB_RESEARCH_DIRECTOR)
|
||||
exists_on_map = TRUE
|
||||
@@ -809,7 +809,7 @@
|
||||
return add_item_to_steal(src, /obj/item/stamp/head)
|
||||
|
||||
/datum/objective_item/steal/spy/sunglasses
|
||||
name = "sunglasses"
|
||||
name = "some sunglasses"
|
||||
targetitem = /obj/item/clothing/glasses/sunglasses
|
||||
excludefromjob = list(
|
||||
JOB_CAPTAIN,
|
||||
@@ -828,7 +828,7 @@
|
||||
You can also obtain a pair from dissassembling hudglasses."
|
||||
|
||||
/datum/objective_item/steal/spy/ce_modsuit
|
||||
name = "the cheif engineer's advanced MOD control unit"
|
||||
name = "the chief engineer's advanced MOD control unit"
|
||||
targetitem = /obj/item/mod/control/pre_equipped/advanced
|
||||
excludefromjob = list(JOB_CHIEF_ENGINEER)
|
||||
exists_on_map = TRUE
|
||||
|
||||
@@ -531,6 +531,11 @@
|
||||
desc = "An adorable stuffed toy that resembles a green lizardperson. This one fills you with nostalgia and soul."
|
||||
greyscale_colors = "#66ff33#000000"
|
||||
|
||||
/obj/item/toy/plush/lizard_plushie/greyscale
|
||||
desc = "An adorable stuffed toy that resembles a lizardperson. This one has been custom made."
|
||||
greyscale_colors = "#d3d3d3#000000"
|
||||
flags_1 = IS_PLAYER_COLORABLE_1
|
||||
|
||||
/obj/item/toy/plush/lizard_plushie/space
|
||||
name = "space lizard plushie"
|
||||
desc = "An adorable stuffed toy that resembles a very determined spacefaring lizardperson. To infinity and beyond, little guy."
|
||||
|
||||
@@ -209,7 +209,11 @@ ADMIN_VERB(create_mapping_job_icons, R_DEBUG, "Generate job landmarks icons", "G
|
||||
for(var/obj/item/I in D)
|
||||
qdel(I)
|
||||
randomize_human(D)
|
||||
D.dress_up_as_job(JB, TRUE)
|
||||
D.dress_up_as_job(
|
||||
equipping = JB,
|
||||
visual_only = TRUE,
|
||||
consistent = TRUE,
|
||||
)
|
||||
var/icon/I = icon(getFlatIcon(D), frame = 1)
|
||||
final.Insert(I, JB.title)
|
||||
qdel(D)
|
||||
|
||||
@@ -219,6 +219,8 @@
|
||||
continue
|
||||
if(!is_station_level(thing_turf.z) && !is_mining_level(thing_turf.z))
|
||||
continue
|
||||
if(HAS_TRAIT(existing_thing, TRAIT_ITEM_OBJECTIVE_BLOCKED))
|
||||
continue
|
||||
all_valid_existing_things += existing_thing
|
||||
|
||||
if(!length(all_valid_existing_things))
|
||||
@@ -253,16 +255,14 @@
|
||||
return FALSE
|
||||
|
||||
desired_item = pick(valid_possible_items)
|
||||
// We need to do some snowflake for items that do exist vs generic items
|
||||
var/list/obj/item/existing_items = GLOB.steal_item_handler.objectives_by_path[desired_item.targetitem]
|
||||
var/obj/item/the_item = length(existing_items) ? pick(existing_items) : desired_item.targetitem
|
||||
var/the_item_name = istype(the_item) ? the_item.name : initial(the_item.name)
|
||||
name = "[the_item_name] [difficulty == SPY_DIFFICULTY_HARD ? "Grand ":""]Theft"
|
||||
help = "Steal any [the_item_name][desired_item.steal_hint ? ": [desired_item.steal_hint]" : "."]"
|
||||
name = "[desired_item.name] [difficulty == SPY_DIFFICULTY_HARD ? "Grand ":""]Theft"
|
||||
help = "Steal [desired_item.name][desired_item.steal_hint ? ": [desired_item.steal_hint]" : "."]"
|
||||
return TRUE
|
||||
|
||||
/datum/spy_bounty/objective_item/is_stealable(atom/movable/stealing)
|
||||
return istype(stealing, desired_item.targetitem) && desired_item.check_special_completion(stealing)
|
||||
return istype(stealing, desired_item.targetitem) \
|
||||
&& !HAS_TRAIT(stealing, TRAIT_ITEM_OBJECTIVE_BLOCKED) \
|
||||
&& desired_item.check_special_completion(stealing)
|
||||
|
||||
/datum/spy_bounty/objective_item/random_easy
|
||||
difficulty = SPY_DIFFICULTY_EASY
|
||||
|
||||
@@ -29,6 +29,8 @@ GLOBAL_DATUM_INIT(steal_item_handler, /datum/objective_item_handler, new())
|
||||
|
||||
/datum/objective_item_handler/proc/new_item_created(datum/source, obj/item/item)
|
||||
SIGNAL_HANDLER
|
||||
if(HAS_TRAIT(item, TRAIT_ITEM_OBJECTIVE_BLOCKED))
|
||||
return
|
||||
if(!generated_items)
|
||||
item.add_stealing_item_objective()
|
||||
return
|
||||
@@ -224,6 +226,8 @@ GLOBAL_DATUM_INIT(steal_item_handler, /datum/objective_item_handler, new())
|
||||
|
||||
/datum/traitor_objective/steal_item/proc/handle_special_case(obj/item/source, obj/item/target)
|
||||
SIGNAL_HANDLER
|
||||
if(HAS_TRAIT(target, TRAIT_ITEM_OBJECTIVE_BLOCKED))
|
||||
return COMPONENT_FORCE_FAIL_PLACEMENT
|
||||
if(istype(target, target_item.targetitem))
|
||||
if(!target_item.check_special_completion(target))
|
||||
return COMPONENT_FORCE_FAIL_PLACEMENT
|
||||
|
||||
@@ -225,8 +225,7 @@ GLOBAL_LIST_EMPTY(preferences_datums)
|
||||
|
||||
return TRUE
|
||||
if ("rotate")
|
||||
character_preview_view.dir = turn(character_preview_view.dir, -90)
|
||||
|
||||
character_preview_view.setDir(turn(character_preview_view.dir, -90))
|
||||
return TRUE
|
||||
if ("set_preference")
|
||||
var/requested_preference_key = params["preference"]
|
||||
@@ -351,6 +350,8 @@ GLOBAL_LIST_EMPTY(preferences_datums)
|
||||
var/mob/living/carbon/human/dummy/body
|
||||
/// The preferences this refers to
|
||||
var/datum/preferences/preferences
|
||||
/// Whether we show current job clothes or nude/loadout only
|
||||
var/show_job_clothes = TRUE
|
||||
|
||||
/atom/movable/screen/map_view/char_preview/Initialize(mapload, datum/preferences/preferences)
|
||||
. = ..()
|
||||
@@ -368,16 +369,14 @@ GLOBAL_LIST_EMPTY(preferences_datums)
|
||||
create_body()
|
||||
else
|
||||
body.wipe_state()
|
||||
appearance = preferences.render_new_preview_appearance(body)
|
||||
|
||||
appearance = preferences.render_new_preview_appearance(body, show_job_clothes)
|
||||
|
||||
/atom/movable/screen/map_view/char_preview/proc/create_body()
|
||||
QDEL_NULL(body)
|
||||
|
||||
body = new
|
||||
|
||||
// Without this, it doesn't show up in the menu
|
||||
body.appearance_flags &= ~KEEP_TOGETHER
|
||||
|
||||
/datum/preferences/proc/create_character_profiles()
|
||||
var/list/profiles = list()
|
||||
|
||||
|
||||
@@ -18,11 +18,16 @@
|
||||
/// support the "use gender" option.
|
||||
#define PREFERENCE_PRIORITY_BODY_TYPE 5
|
||||
|
||||
/// Equpping items based on preferences.
|
||||
/// Should happen after species and body type to make sure it looks right.
|
||||
/// Mostly redundant, but a safety net for saving/loading.
|
||||
#define PREFERENCE_PRIORITY_LOADOUT 6
|
||||
|
||||
/// The priority at which names are decided, needed for proper randomization.
|
||||
#define PREFERENCE_PRIORITY_NAMES 6
|
||||
#define PREFERENCE_PRIORITY_NAMES 7
|
||||
|
||||
/// Preferences that aren't names, but change the name changes set by PREFERENCE_PRIORITY_NAMES.
|
||||
#define PREFERENCE_PRIORITY_NAME_MODIFICATIONS 7
|
||||
#define PREFERENCE_PRIORITY_NAME_MODIFICATIONS 8
|
||||
|
||||
/// The maximum preference priority, keep this updated, but don't use it for `priority`.
|
||||
#define MAX_PREFERENCE_PRIORITY PREFERENCE_PRIORITY_NAME_MODIFICATIONS
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Move quirk items into loadout items
|
||||
*
|
||||
* If this is accompanied with removal of a quirk,
|
||||
* you don't need to worry about handling that here -
|
||||
* quirk sanitization happens AFTER migration
|
||||
*/
|
||||
/datum/preferences/proc/migrate_quirk_to_loadout(quirk_to_migrate, new_typepath, list/data_to_migrate)
|
||||
ASSERT(istext(quirk_to_migrate) && ispath(new_typepath, /obj/item))
|
||||
if(quirk_to_migrate in all_quirks)
|
||||
add_loadout_item(new_typepath, data_to_migrate)
|
||||
|
||||
/// Helper for slotting in a new loadout item
|
||||
/datum/preferences/proc/add_loadout_item(typepath, list/data = list())
|
||||
PRIVATE_PROC(TRUE)
|
||||
|
||||
var/list/loadout_list = read_preference(/datum/preference/loadout) || list()
|
||||
loadout_list[typepath] = data
|
||||
write_preference(GLOB.preference_entries[/datum/preference/loadout], loadout_list)
|
||||
|
||||
/// Helper for removing a loadout item
|
||||
/datum/preferences/proc/remove_loadout_item(typepath)
|
||||
PRIVATE_PROC(TRUE)
|
||||
|
||||
var/list/loadout_list = read_preference(/datum/preference/loadout)
|
||||
if(loadout_list?.Remove(typepath))
|
||||
write_preference(GLOB.preference_entries[/datum/preference/loadout], loadout_list)
|
||||
@@ -1,16 +0,0 @@
|
||||
/datum/preference/choiced/pride_pin
|
||||
category = PREFERENCE_CATEGORY_SECONDARY_FEATURES
|
||||
savefile_key = "pride_pin"
|
||||
savefile_identifier = PREFERENCE_CHARACTER
|
||||
|
||||
/datum/preference/choiced/pride_pin/init_possible_values()
|
||||
return assoc_to_keys(GLOB.pride_pin_reskins)
|
||||
|
||||
/datum/preference/choiced/pride_pin/is_accessible(datum/preferences/preferences)
|
||||
if (!..(preferences))
|
||||
return FALSE
|
||||
|
||||
return "Pride Pin" in preferences.all_quirks
|
||||
|
||||
/datum/preference/choiced/pride_pin/apply_to_human(mob/living/carbon/human/target, value)
|
||||
return
|
||||
@@ -5,7 +5,7 @@
|
||||
// You do not need to raise this if you are adding new values that have sane defaults.
|
||||
// Only raise this value when changing the meaning/format/name/layout of an existing value
|
||||
// where you would want the updater procs below to run
|
||||
#define SAVEFILE_VERSION_MAX 44
|
||||
#define SAVEFILE_VERSION_MAX 45
|
||||
|
||||
/*
|
||||
SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Carn
|
||||
@@ -104,6 +104,13 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
|
||||
if (current_version < 43)
|
||||
migrate_legacy_sound_toggles(savefile)
|
||||
|
||||
if (current_version < 45)
|
||||
migrate_quirk_to_loadout(
|
||||
quirk_to_migrate = "Pride Pin",
|
||||
new_typepath = /obj/item/clothing/accessory/pride,
|
||||
data_to_migrate = list(INFO_RESKIN = save_data?["pride_pin"]),
|
||||
)
|
||||
|
||||
/// checks through keybindings for outdated unbound keys and updates them
|
||||
/datum/preferences/proc/check_keybindings()
|
||||
if(!parent)
|
||||
|
||||
@@ -84,6 +84,8 @@
|
||||
name = "flat cap"
|
||||
desc = "A working man's cap."
|
||||
icon_state = "beret_flat"
|
||||
icon_preview = 'icons/obj/clothing/head/beret.dmi'
|
||||
icon_state_preview = "beret_flat"
|
||||
greyscale_config = /datum/greyscale_config/beret
|
||||
greyscale_config_worn = /datum/greyscale_config/beret/worn
|
||||
greyscale_colors = "#8F7654"
|
||||
|
||||
@@ -385,6 +385,8 @@
|
||||
name = "beret"
|
||||
desc = "A beret, a mime's favorite headwear."
|
||||
icon_state = "beret"
|
||||
icon_preview = 'icons/obj/clothing/head/beret.dmi'
|
||||
icon_state_preview = "beret"
|
||||
dog_fashion = /datum/dog_fashion/head/beret
|
||||
greyscale_config = /datum/greyscale_config/beret
|
||||
greyscale_config_worn = /datum/greyscale_config/beret/worn
|
||||
|
||||
@@ -398,6 +398,8 @@
|
||||
/obj/item/clothing/neck/large_scarf
|
||||
name = "large scarf"
|
||||
icon_state = "large_scarf"
|
||||
icon_preview = 'icons/obj/fluff/previews.dmi'
|
||||
icon_state_preview = "scarf_large"
|
||||
w_class = WEIGHT_CLASS_TINY
|
||||
custom_price = PAYCHECK_CREW
|
||||
greyscale_colors = "#C6C6C6#EEEEEE"
|
||||
|
||||
@@ -175,8 +175,12 @@
|
||||
name = "Pre-Approved Cyborg Candidate dogtag"
|
||||
display = "This employee has been screened for negative mental traits to an acceptable level of accuracy, and is approved for the NT Cyborg program as an alternative to medical resuscitation."
|
||||
|
||||
/// Reskins for the pride pin accessory, mapped by display name to icon state
|
||||
GLOBAL_LIST_INIT(pride_pin_reskins, list(
|
||||
/obj/item/clothing/accessory/pride
|
||||
name = "pride pin"
|
||||
desc = "A Nanotrasen Diversity & Inclusion Center-sponsored holographic pin to show off your pride, reminding the crew of their unwavering commitment to equity, diversity, and inclusion!"
|
||||
icon_state = "pride"
|
||||
obj_flags = UNIQUE_RENAME | INFINITE_RESKIN
|
||||
unique_reskin = list(
|
||||
"Rainbow Pride" = "pride",
|
||||
"Bisexual Pride" = "pride_bi",
|
||||
"Pansexual Pride" = "pride_pan",
|
||||
@@ -185,16 +189,9 @@ GLOBAL_LIST_INIT(pride_pin_reskins, list(
|
||||
"Transgender Pride" = "pride_trans",
|
||||
"Intersex Pride" = "pride_intersex",
|
||||
"Lesbian Pride" = "pride_lesbian",
|
||||
))
|
||||
|
||||
/obj/item/clothing/accessory/pride
|
||||
name = "pride pin"
|
||||
desc = "A Nanotrasen Diversity & Inclusion Center-sponsored holographic pin to show off your pride, reminding the crew of their unwavering commitment to equity, diversity, and inclusion!"
|
||||
icon_state = "pride"
|
||||
obj_flags = UNIQUE_RENAME | INFINITE_RESKIN
|
||||
)
|
||||
|
||||
/obj/item/clothing/accessory/pride/setup_reskinning()
|
||||
unique_reskin = GLOB.pride_pin_reskins
|
||||
if(!check_setup_reskinning())
|
||||
return
|
||||
|
||||
|
||||
@@ -173,7 +173,7 @@
|
||||
spawned_human.mind.adjust_experience(i, roundstart_experience[i], TRUE)
|
||||
|
||||
/// Return the outfit to use
|
||||
/datum/job/proc/get_outfit()
|
||||
/datum/job/proc/get_outfit(consistent)
|
||||
return outfit
|
||||
|
||||
/// Announce that this job as joined the round to all crew members.
|
||||
@@ -188,12 +188,12 @@
|
||||
return TRUE
|
||||
|
||||
|
||||
/mob/living/proc/on_job_equipping(datum/job/equipping)
|
||||
/mob/living/proc/on_job_equipping(datum/job/equipping, client/player_client)
|
||||
return
|
||||
|
||||
#define VERY_LATE_ARRIVAL_TOAST_PROB 20
|
||||
|
||||
/mob/living/carbon/human/on_job_equipping(datum/job/equipping)
|
||||
/mob/living/carbon/human/on_job_equipping(datum/job/equipping, client/player_client)
|
||||
if(equipping.paycheck_department)
|
||||
var/datum/bank_account/bank_account = new(real_name, equipping, dna.species.payday_modifier)
|
||||
bank_account.payday(STARTING_PAYCHECKS, TRUE)
|
||||
@@ -201,19 +201,24 @@
|
||||
bank_account.replaceable = FALSE
|
||||
add_mob_memory(/datum/memory/key/account, remembered_id = account_id)
|
||||
|
||||
dress_up_as_job(equipping)
|
||||
dress_up_as_job(
|
||||
equipping = equipping,
|
||||
visual_only = FALSE,
|
||||
player_client = player_client,
|
||||
consistent = FALSE,
|
||||
)
|
||||
|
||||
if(EMERGENCY_PAST_POINT_OF_NO_RETURN && prob(VERY_LATE_ARRIVAL_TOAST_PROB))
|
||||
equip_to_slot_or_del(new /obj/item/food/griddle_toast(src), ITEM_SLOT_MASK)
|
||||
|
||||
#undef VERY_LATE_ARRIVAL_TOAST_PROB
|
||||
|
||||
/mob/living/proc/dress_up_as_job(datum/job/equipping, visual_only = FALSE)
|
||||
/mob/living/proc/dress_up_as_job(datum/job/equipping, visual_only = FALSE, client/player_client, consistent = FALSE)
|
||||
return
|
||||
|
||||
/mob/living/carbon/human/dress_up_as_job(datum/job/equipping, visual_only = FALSE)
|
||||
/mob/living/carbon/human/dress_up_as_job(datum/job/equipping, visual_only = FALSE, client/player_client, consistent = FALSE)
|
||||
dna.species.pre_equip_species_outfit(equipping, src, visual_only)
|
||||
equipOutfit(equipping.get_outfit(), visual_only)
|
||||
equip_outfit_and_loadout(equipping.get_outfit(consistent), player_client?.prefs, visual_only)
|
||||
|
||||
/datum/job/proc/announce_head(mob/living/carbon/human/H, channels) //tells the given channel that the given mob is the new department head. See communications.dm for valid channels.
|
||||
if(H && GLOB.announcement_systems.len)
|
||||
|
||||
@@ -37,7 +37,9 @@ Assistant
|
||||
rpg_title = "Lout"
|
||||
config_tag = "ASSISTANT"
|
||||
|
||||
/datum/job/assistant/get_outfit()
|
||||
/datum/job/assistant/get_outfit(consistent)
|
||||
if(consistent)
|
||||
return /datum/outfit/job/assistant/consistent
|
||||
if(!HAS_TRAIT(SSstation, STATION_TRAIT_ASSISTANT_GIMMICKS))
|
||||
return ..()
|
||||
|
||||
|
||||
92
code/modules/loadout/categories/accessories.dm
Normal file
92
code/modules/loadout/categories/accessories.dm
Normal file
@@ -0,0 +1,92 @@
|
||||
/// Accessory Items (Moves overrided items to backpack)
|
||||
/datum/loadout_category/accessories
|
||||
category_name = "Accessory"
|
||||
category_ui_icon = FA_ICON_VEST
|
||||
type_to_generate = /datum/loadout_item/accessory
|
||||
tab_order = /datum/loadout_category/head::tab_order + 3
|
||||
|
||||
/datum/loadout_item/accessory
|
||||
abstract_type = /datum/loadout_item/accessory
|
||||
/// Can we adjust this accessory to be above or below suits?
|
||||
VAR_FINAL/can_be_layer_adjusted = FALSE
|
||||
|
||||
/datum/loadout_item/accessory/New()
|
||||
. = ..()
|
||||
if(ispath(item_path, /obj/item/clothing/accessory))
|
||||
can_be_layer_adjusted = TRUE
|
||||
|
||||
/datum/loadout_item/accessory/get_ui_buttons()
|
||||
if(!can_be_layer_adjusted)
|
||||
return ..()
|
||||
|
||||
var/list/buttons = ..()
|
||||
|
||||
UNTYPED_LIST_ADD(buttons, list(
|
||||
"label" = "Layer",
|
||||
"act_key" = "set_layer",
|
||||
"active_key" = INFO_LAYER,
|
||||
"active_text" = "Above Suit",
|
||||
"inactive_text" = "Below Suit",
|
||||
))
|
||||
|
||||
return buttons
|
||||
|
||||
/datum/loadout_item/accessory/handle_loadout_action(datum/preference_middleware/loadout/manager, mob/user, action, params)
|
||||
if(action == "set_layer")
|
||||
return set_accessory_layer(manager, user)
|
||||
|
||||
return ..()
|
||||
|
||||
/datum/loadout_item/accessory/proc/set_accessory_layer(datum/preference_middleware/loadout/manager, mob/user)
|
||||
if(!can_be_layer_adjusted)
|
||||
return FALSE
|
||||
|
||||
var/list/loadout = manager.preferences.read_preference(/datum/preference/loadout)
|
||||
if(!loadout?[item_path])
|
||||
return FALSE
|
||||
|
||||
if(isnull(loadout[item_path][INFO_LAYER]))
|
||||
loadout[item_path][INFO_LAYER] = FALSE
|
||||
|
||||
loadout[item_path][INFO_LAYER] = !loadout[item_path][INFO_LAYER]
|
||||
manager.preferences.update_preference(GLOB.preference_entries[/datum/preference/loadout], loadout)
|
||||
return TRUE // Update UI
|
||||
|
||||
/datum/loadout_item/accessory/insert_path_into_outfit(datum/outfit/outfit, mob/living/carbon/human/equipper, visuals_only = FALSE)
|
||||
if(outfit.accessory)
|
||||
LAZYADD(outfit.backpack_contents, outfit.accessory)
|
||||
outfit.accessory = item_path
|
||||
|
||||
/datum/loadout_item/accessory/on_equip_item(
|
||||
obj/item/clothing/accessory/equipped_item,
|
||||
datum/preferences/preference_source,
|
||||
list/preference_list,
|
||||
mob/living/carbon/human/equipper,
|
||||
visuals_only = FALSE,
|
||||
)
|
||||
. = ..()
|
||||
if(istype(equipped_item))
|
||||
equipped_item.above_suit = !!preference_list[item_path]?[INFO_LAYER]
|
||||
. |= (ITEM_SLOT_OCLOTHING|ITEM_SLOT_ICLOTHING)
|
||||
|
||||
/datum/loadout_item/accessory/maid_apron
|
||||
name = "Maid Apron"
|
||||
item_path = /obj/item/clothing/accessory/maidapron
|
||||
|
||||
/datum/loadout_item/accessory/waistcoat
|
||||
name = "Waistcoat"
|
||||
item_path = /obj/item/clothing/accessory/waistcoat
|
||||
|
||||
/datum/loadout_item/accessory/pocket_protector
|
||||
name = "Pocket Protector"
|
||||
item_path = /obj/item/clothing/accessory/pocketprotector
|
||||
|
||||
/datum/loadout_item/accessory/full_pocket_protector
|
||||
name = "Pocket Protector (Filled)"
|
||||
item_path = /obj/item/clothing/accessory/pocketprotector/full
|
||||
additional_displayed_text = list("Contains pens")
|
||||
|
||||
/datum/loadout_item/accessory/pride
|
||||
name = "Pride Pin"
|
||||
item_path = /obj/item/clothing/accessory/pride
|
||||
can_be_reskinned = TRUE
|
||||
55
code/modules/loadout/categories/glasses.dm
Normal file
55
code/modules/loadout/categories/glasses.dm
Normal file
@@ -0,0 +1,55 @@
|
||||
/// Glasses Slot Items (Moves overrided items to backpack)
|
||||
/datum/loadout_category/glasses
|
||||
category_name = "Glasses"
|
||||
category_ui_icon = FA_ICON_GLASSES
|
||||
type_to_generate = /datum/loadout_item/glasses
|
||||
tab_order = /datum/loadout_category/head::tab_order + 1
|
||||
|
||||
/datum/loadout_item/glasses
|
||||
abstract_type = /datum/loadout_item/glasses
|
||||
|
||||
/datum/loadout_item/glasses/insert_path_into_outfit(datum/outfit/outfit, mob/living/carbon/human/equipper, visuals_only = FALSE)
|
||||
if(outfit.glasses)
|
||||
LAZYADD(outfit.backpack_contents, outfit.glasses)
|
||||
outfit.glasses = item_path
|
||||
|
||||
/datum/loadout_item/glasses/prescription_glasses
|
||||
name = "Glasses"
|
||||
item_path = /obj/item/clothing/glasses/regular
|
||||
additional_displayed_text = list("Prescription")
|
||||
|
||||
/datum/loadout_item/glasses/prescription_glasses/circle_glasses
|
||||
name = "Circle Glasses"
|
||||
item_path = /obj/item/clothing/glasses/regular/circle
|
||||
|
||||
/datum/loadout_item/glasses/prescription_glasses/hipster_glasses
|
||||
name = "Hipster Glasses"
|
||||
item_path = /obj/item/clothing/glasses/regular/hipster
|
||||
|
||||
/datum/loadout_item/glasses/prescription_glasses/jamjar_glasses
|
||||
name = "Jamjar Glasses"
|
||||
item_path = /obj/item/clothing/glasses/regular/jamjar
|
||||
|
||||
/datum/loadout_item/glasses/black_blindfold
|
||||
name = "Black Blindfold"
|
||||
item_path = /obj/item/clothing/glasses/blindfold
|
||||
|
||||
/datum/loadout_item/glasses/cold_glasses
|
||||
name = "Cold Glasses"
|
||||
item_path = /obj/item/clothing/glasses/cold
|
||||
|
||||
/datum/loadout_item/glasses/heat_glasses
|
||||
name = "Heat Glasses"
|
||||
item_path = /obj/item/clothing/glasses/heat
|
||||
|
||||
/datum/loadout_item/glasses/orange_glasses
|
||||
name = "Orange Glasses"
|
||||
item_path = /obj/item/clothing/glasses/orange
|
||||
|
||||
/datum/loadout_item/glasses/red_glasses
|
||||
name = "Red Glasses"
|
||||
item_path = /obj/item/clothing/glasses/red
|
||||
|
||||
/datum/loadout_item/glasses/eyepatch
|
||||
name = "Eyepatch"
|
||||
item_path = /obj/item/clothing/glasses/eyepatch
|
||||
138
code/modules/loadout/categories/heads.dm
Normal file
138
code/modules/loadout/categories/heads.dm
Normal file
@@ -0,0 +1,138 @@
|
||||
/// Head Slot Items (Deletes overrided items)
|
||||
/datum/loadout_category/head
|
||||
category_name = "Head"
|
||||
category_ui_icon = FA_ICON_HAT_COWBOY
|
||||
type_to_generate = /datum/loadout_item/head
|
||||
tab_order = 1
|
||||
|
||||
/datum/loadout_item/head
|
||||
abstract_type = /datum/loadout_item/head
|
||||
|
||||
/datum/loadout_item/head/insert_path_into_outfit(datum/outfit/outfit, mob/living/carbon/human/equipper, visuals_only = FALSE)
|
||||
if(equipper.dna?.species?.outfit_important_for_life)
|
||||
if(!visuals_only)
|
||||
to_chat(equipper, "Your loadout helmet was not equipped directly due to your species outfit.")
|
||||
LAZYADD(outfit.backpack_contents, item_path)
|
||||
else
|
||||
outfit.head = item_path
|
||||
|
||||
/datum/loadout_item/head/beanie
|
||||
name = "Beanie (Colorable)"
|
||||
item_path = /obj/item/clothing/head/beanie
|
||||
|
||||
/datum/loadout_item/head/fancy_cap
|
||||
name = "Fancy Hat (Colorable)"
|
||||
item_path = /obj/item/clothing/head/costume/fancy
|
||||
|
||||
/datum/loadout_item/head/red_beret
|
||||
name = "Red Beret (Colorable)"
|
||||
item_path = /obj/item/clothing/head/beret
|
||||
|
||||
/datum/loadout_item/head/black_cap
|
||||
name = "Cap (Black)"
|
||||
item_path = /obj/item/clothing/head/soft/black
|
||||
|
||||
/datum/loadout_item/head/blue_cap
|
||||
name = "Cap (Blue)"
|
||||
item_path = /obj/item/clothing/head/soft/blue
|
||||
|
||||
/datum/loadout_item/head/delinquent_cap
|
||||
name = "Cap (Delinquent)"
|
||||
item_path = /obj/item/clothing/head/costume/delinquent
|
||||
|
||||
/datum/loadout_item/head/green_cap
|
||||
name = "Cap (Green)"
|
||||
item_path = /obj/item/clothing/head/soft/green
|
||||
|
||||
/datum/loadout_item/head/grey_cap
|
||||
name = "Cap (Grey)"
|
||||
item_path = /obj/item/clothing/head/soft/grey
|
||||
|
||||
/datum/loadout_item/head/orange_cap
|
||||
name = "Cap (Orange)"
|
||||
item_path = /obj/item/clothing/head/soft/orange
|
||||
|
||||
/datum/loadout_item/head/purple_cap
|
||||
name = "Cap (Purple)"
|
||||
item_path = /obj/item/clothing/head/soft/purple
|
||||
|
||||
/datum/loadout_item/head/rainbow_cap
|
||||
name = "Cap (Rainbow)"
|
||||
item_path = /obj/item/clothing/head/soft/rainbow
|
||||
|
||||
/datum/loadout_item/head/red_cap
|
||||
name = "Cap (Red)"
|
||||
item_path = /obj/item/clothing/head/soft/red
|
||||
|
||||
/datum/loadout_item/head/white_cap
|
||||
name = "Cap (White)"
|
||||
item_path = /obj/item/clothing/head/soft
|
||||
|
||||
/datum/loadout_item/head/yellow_cap
|
||||
name = "Cap (Yellow)"
|
||||
item_path = /obj/item/clothing/head/soft/yellow
|
||||
|
||||
/datum/loadout_item/head/flatcap
|
||||
name = "Cap (Flat)"
|
||||
item_path = /obj/item/clothing/head/flatcap
|
||||
|
||||
/datum/loadout_item/head/beige_fedora
|
||||
name = "Fedora (Beige)"
|
||||
item_path = /obj/item/clothing/head/fedora/beige
|
||||
|
||||
/datum/loadout_item/head/black_fedora
|
||||
name = "Fedora (Black)"
|
||||
item_path = /obj/item/clothing/head/fedora
|
||||
|
||||
/datum/loadout_item/head/white_fedora
|
||||
name = "Fedora (White)"
|
||||
item_path = /obj/item/clothing/head/fedora/white
|
||||
|
||||
/datum/loadout_item/head/mail_cap
|
||||
name = "Cap (Mail)"
|
||||
item_path = /obj/item/clothing/head/costume/mailman
|
||||
|
||||
/datum/loadout_item/head/kitty_ears
|
||||
name = "Kitty Ears"
|
||||
item_path = /obj/item/clothing/head/costume/kitty
|
||||
|
||||
/datum/loadout_item/head/rabbit_ears
|
||||
name = "Rabbit Ears"
|
||||
item_path = /obj/item/clothing/head/costume/rabbitears
|
||||
|
||||
/datum/loadout_item/head/bandana
|
||||
name = "Bandana Thin"
|
||||
item_path = /obj/item/clothing/head/costume/tmc
|
||||
|
||||
/datum/loadout_item/head/rastafarian
|
||||
name = "Cap (Rastafarian)"
|
||||
item_path = /obj/item/clothing/head/rasta
|
||||
|
||||
/datum/loadout_item/head/top_hat
|
||||
name = "Top Hat"
|
||||
item_path = /obj/item/clothing/head/hats/tophat
|
||||
|
||||
/datum/loadout_item/head/bowler_hat
|
||||
name = "Bowler Hat"
|
||||
item_path = /obj/item/clothing/head/hats/bowler
|
||||
|
||||
/datum/loadout_item/head/bear_pelt
|
||||
name = "Bear Pelt"
|
||||
item_path = /obj/item/clothing/head/costume/bearpelt
|
||||
|
||||
/datum/loadout_item/head/ushanka
|
||||
name ="Ushanka"
|
||||
item_path = /obj/item/clothing/head/costume/ushanka
|
||||
|
||||
/datum/loadout_item/head/plague_doctor
|
||||
name = "Cap (Plague Doctor)"
|
||||
item_path = /obj/item/clothing/head/bio_hood/plague
|
||||
|
||||
/datum/loadout_item/head/rose
|
||||
name = "Rose"
|
||||
item_path = /obj/item/food/grown/rose
|
||||
|
||||
/datum/loadout_item/head/wig
|
||||
name = "Wig"
|
||||
item_path = /obj/item/clothing/head/wig/natural
|
||||
additional_displayed_text = list("Hair Color")
|
||||
33
code/modules/loadout/categories/inhands.dm
Normal file
33
code/modules/loadout/categories/inhands.dm
Normal file
@@ -0,0 +1,33 @@
|
||||
/// Inhand items (Moves overrided items to backpack)
|
||||
/datum/loadout_category/inhands
|
||||
category_name = "Inhand"
|
||||
category_ui_icon = FA_ICON_BRIEFCASE
|
||||
type_to_generate = /datum/loadout_item/inhand
|
||||
tab_order = /datum/loadout_category/head::tab_order + 4
|
||||
|
||||
/datum/loadout_item/inhand
|
||||
abstract_type = /datum/loadout_item/inhand
|
||||
|
||||
/datum/loadout_item/inhand/insert_path_into_outfit(datum/outfit/outfit, mob/living/carbon/human/equipper, visuals_only = FALSE)
|
||||
if(outfit.l_hand && !outfit.r_hand)
|
||||
outfit.r_hand = item_path
|
||||
else
|
||||
if(outfit.l_hand)
|
||||
LAZYADD(outfit.backpack_contents, outfit.l_hand)
|
||||
outfit.l_hand = item_path
|
||||
|
||||
/datum/loadout_item/inhand/cane
|
||||
name = "Cane"
|
||||
item_path = /obj/item/cane
|
||||
|
||||
/datum/loadout_item/inhand/cane_white
|
||||
name = "White Cane"
|
||||
item_path = /obj/item/cane/white
|
||||
|
||||
/datum/loadout_item/inhand/briefcase
|
||||
name = "Briefcase (Leather)"
|
||||
item_path = /obj/item/storage/briefcase
|
||||
|
||||
/datum/loadout_item/inhand/briefcase_secure
|
||||
name = "Briefcase (Secure)"
|
||||
item_path = /obj/item/storage/briefcase/secure
|
||||
36
code/modules/loadout/categories/neck.dm
Normal file
36
code/modules/loadout/categories/neck.dm
Normal file
@@ -0,0 +1,36 @@
|
||||
/// Neck Slot Items (Deletes overrided items)
|
||||
/datum/loadout_category/neck
|
||||
category_name = "Neck"
|
||||
category_ui_icon = FA_ICON_USER_TIE
|
||||
type_to_generate = /datum/loadout_item/neck
|
||||
tab_order = /datum/loadout_category/head::tab_order + 2
|
||||
|
||||
/datum/loadout_item/neck
|
||||
abstract_type = /datum/loadout_item/neck
|
||||
|
||||
/datum/loadout_item/neck/insert_path_into_outfit(datum/outfit/outfit, mob/living/carbon/human/equipper, visuals_only = FALSE)
|
||||
outfit.neck = item_path
|
||||
|
||||
/datum/loadout_item/neck/scarf_greyscale
|
||||
name = "Scarf (Colorable)"
|
||||
item_path = /obj/item/clothing/neck/scarf
|
||||
|
||||
/datum/loadout_item/neck/greyscale_large
|
||||
name = "Scarf (Large, Colorable)"
|
||||
item_path = /obj/item/clothing/neck/large_scarf
|
||||
|
||||
/datum/loadout_item/neck/greyscale_larger
|
||||
name = "Scarf (Larger, Colorable)"
|
||||
item_path = /obj/item/clothing/neck/infinity_scarf
|
||||
|
||||
/datum/loadout_item/neck/necktie
|
||||
name = "Necktie (Colorable)"
|
||||
item_path = /obj/item/clothing/neck/tie
|
||||
|
||||
/datum/loadout_item/neck/necktie_disco
|
||||
name = "Necktie (Ugly)"
|
||||
item_path = /obj/item/clothing/neck/tie/horrible
|
||||
|
||||
/datum/loadout_item/neck/necktie_loose
|
||||
name = "Necktie (Loose)"
|
||||
item_path = /obj/item/clothing/neck/tie/detective
|
||||
203
code/modules/loadout/categories/pocket.dm
Normal file
203
code/modules/loadout/categories/pocket.dm
Normal file
@@ -0,0 +1,203 @@
|
||||
/// Pocket items (Moved to backpack)
|
||||
/datum/loadout_category/pocket
|
||||
category_name = "Other"
|
||||
category_ui_icon = FA_ICON_QUESTION
|
||||
type_to_generate = /datum/loadout_item/pocket_items
|
||||
tab_order = /datum/loadout_category/head::tab_order + 5
|
||||
/// How many pocket items are allowed
|
||||
VAR_PRIVATE/max_allowed = 2
|
||||
|
||||
/datum/loadout_category/pocket/New()
|
||||
. = ..()
|
||||
category_info = "([max_allowed] allowed)"
|
||||
|
||||
/datum/loadout_category/pocket/handle_duplicate_entires(
|
||||
datum/preference_middleware/loadout/manager,
|
||||
datum/loadout_item/conflicting_item,
|
||||
datum/loadout_item/added_item,
|
||||
list/datum/loadout_item/all_loadout_items,
|
||||
)
|
||||
var/list/datum/loadout_item/pocket_items/other_pocket_items = list()
|
||||
for(var/datum/loadout_item/pocket_items/other_pocket_item in all_loadout_items)
|
||||
other_pocket_items += other_pocket_item
|
||||
|
||||
if(length(other_pocket_items) >= max_allowed)
|
||||
// We only need to deselect something if we're above the limit
|
||||
// (And if we are we prioritize the first item found, FIFO)
|
||||
manager.deselect_item(other_pocket_items[1])
|
||||
return TRUE
|
||||
|
||||
/datum/loadout_item/pocket_items
|
||||
abstract_type = /datum/loadout_item/pocket_items
|
||||
|
||||
/datum/loadout_item/pocket_items/on_equip_item(
|
||||
obj/item/equipped_item,
|
||||
datum/preferences/preference_source,
|
||||
list/preference_list,
|
||||
mob/living/carbon/human/equipper,
|
||||
visuals_only = FALSE,
|
||||
)
|
||||
// Backpack items aren't created if it's a visual equipping, so don't do any on equip stuff. It doesn't exist.
|
||||
if(visuals_only)
|
||||
return NONE
|
||||
|
||||
return ..()
|
||||
|
||||
/datum/loadout_item/pocket_items/lipstick_black
|
||||
name = "Lipstick (Black)"
|
||||
item_path = /obj/item/lipstick/black
|
||||
additional_displayed_text = list("Black")
|
||||
|
||||
/datum/loadout_item/pocket_items/lipstick_blue
|
||||
name = "Lipstick (Blue)"
|
||||
item_path = /obj/item/lipstick/blue
|
||||
additional_displayed_text = list("Blue")
|
||||
|
||||
|
||||
/datum/loadout_item/pocket_items/lipstick_green
|
||||
name = "Lipstick (Green)"
|
||||
item_path = /obj/item/lipstick/green
|
||||
additional_displayed_text = list("Green")
|
||||
|
||||
|
||||
/datum/loadout_item/pocket_items/lipstick_jade
|
||||
name = "Lipstick (Jade)"
|
||||
item_path = /obj/item/lipstick/jade
|
||||
additional_displayed_text = list("Jade")
|
||||
|
||||
/datum/loadout_item/pocket_items/lipstick_purple
|
||||
name = "Lipstick (Purple)"
|
||||
item_path = /obj/item/lipstick/purple
|
||||
additional_displayed_text = list("Purple")
|
||||
|
||||
/datum/loadout_item/pocket_items/lipstick_red
|
||||
name = "Lipstick (Red)"
|
||||
item_path = /obj/item/lipstick
|
||||
additional_displayed_text = list("Red")
|
||||
|
||||
/datum/loadout_item/pocket_items/lipstick_white
|
||||
name = "Lipstick (White)"
|
||||
item_path = /obj/item/lipstick/white
|
||||
additional_displayed_text = list("White")
|
||||
|
||||
/datum/loadout_item/pocket_items/plush
|
||||
abstract_type = /datum/loadout_item/pocket_items/plush
|
||||
can_be_named = TRUE
|
||||
|
||||
/datum/loadout_item/pocket_items/plush/bee
|
||||
name = "Plush (Bee)"
|
||||
item_path = /obj/item/toy/plush/beeplushie
|
||||
|
||||
/datum/loadout_item/pocket_items/plush/carp
|
||||
name = "Plush (Carp)"
|
||||
item_path = /obj/item/toy/plush/carpplushie
|
||||
|
||||
/datum/loadout_item/pocket_items/plush/lizard_greyscale
|
||||
name = "Plush (Lizard, Colorable)"
|
||||
item_path = /obj/item/toy/plush/lizard_plushie/greyscale
|
||||
|
||||
/datum/loadout_item/pocket_items/plush/lizard_random
|
||||
name = "Plush (Lizard, Random)"
|
||||
can_be_greyscale = DONT_GREYSCALE
|
||||
item_path = /obj/item/toy/plush/lizard_plushie
|
||||
additional_displayed_text = list("Random color")
|
||||
|
||||
/datum/loadout_item/pocket_items/plush/moth
|
||||
name = "Plush (Moth)"
|
||||
item_path = /obj/item/toy/plush/moth
|
||||
|
||||
/datum/loadout_item/pocket_items/plush/narsie
|
||||
name = "Plush (Nar'sie)"
|
||||
item_path = /obj/item/toy/plush/narplush
|
||||
|
||||
/datum/loadout_item/pocket_items/plush/nukie
|
||||
name = "Plush (Nukie)"
|
||||
item_path = /obj/item/toy/plush/nukeplushie
|
||||
|
||||
/datum/loadout_item/pocket_items/plush/peacekeeper
|
||||
name = "Plush (Peacekeeper)"
|
||||
item_path = /obj/item/toy/plush/pkplush
|
||||
|
||||
/datum/loadout_item/pocket_items/plush/plasmaman
|
||||
name = "Plush (Plasmaman)"
|
||||
item_path = /obj/item/toy/plush/plasmamanplushie
|
||||
|
||||
/datum/loadout_item/pocket_items/plush/ratvar
|
||||
name = "Plush (Ratvar)"
|
||||
item_path = /obj/item/toy/plush/ratplush
|
||||
|
||||
/datum/loadout_item/pocket_items/plush/rouny
|
||||
name = "Plush (Rouny)"
|
||||
item_path = /obj/item/toy/plush/rouny
|
||||
|
||||
/datum/loadout_item/pocket_items/plush/snake
|
||||
name = "Plush (Snake)"
|
||||
item_path = /obj/item/toy/plush/snakeplushie
|
||||
|
||||
/datum/loadout_item/pocket_items/card_binder
|
||||
name = "Card Binder"
|
||||
item_path = /obj/item/storage/card_binder
|
||||
|
||||
/datum/loadout_item/pocket_items/card_deck
|
||||
name = "Playing Card Deck"
|
||||
item_path = /obj/item/toy/cards/deck
|
||||
|
||||
/datum/loadout_item/pocket_items/kotahi_deck
|
||||
name = "Kotahi Deck"
|
||||
item_path = /obj/item/toy/cards/deck/kotahi
|
||||
|
||||
/datum/loadout_item/pocket_items/wizoff_deck
|
||||
name = "Wizoff Deck"
|
||||
item_path = /obj/item/toy/cards/deck/wizoff
|
||||
|
||||
/datum/loadout_item/pocket_items/dice_bag
|
||||
name = "Dice Bag"
|
||||
item_path = /obj/item/storage/dice
|
||||
|
||||
/datum/loadout_item/pocket_items/d1
|
||||
name = "D1"
|
||||
item_path = /obj/item/dice/d1
|
||||
|
||||
/datum/loadout_item/pocket_items/d2
|
||||
name = "D2"
|
||||
item_path = /obj/item/dice/d2
|
||||
|
||||
/datum/loadout_item/pocket_items/d4
|
||||
name = "D4"
|
||||
item_path = /obj/item/dice/d4
|
||||
|
||||
/datum/loadout_item/pocket_items/d6
|
||||
name = "D6"
|
||||
item_path = /obj/item/dice/d6
|
||||
|
||||
/datum/loadout_item/pocket_items/d6_ebony
|
||||
name = "D6 (Ebony)"
|
||||
item_path = /obj/item/dice/d6/ebony
|
||||
|
||||
/datum/loadout_item/pocket_items/d6_space
|
||||
name = "D6 (Space)"
|
||||
item_path = /obj/item/dice/d6/space
|
||||
|
||||
/datum/loadout_item/pocket_items/d8
|
||||
name = "D8"
|
||||
item_path = /obj/item/dice/d8
|
||||
|
||||
/datum/loadout_item/pocket_items/d10
|
||||
name = "D10"
|
||||
item_path = /obj/item/dice/d10
|
||||
|
||||
/datum/loadout_item/pocket_items/d12
|
||||
name = "D12"
|
||||
item_path = /obj/item/dice/d12
|
||||
|
||||
/datum/loadout_item/pocket_items/d20
|
||||
name = "D20"
|
||||
item_path = /obj/item/dice/d20
|
||||
|
||||
/datum/loadout_item/pocket_items/d100
|
||||
name = "D100"
|
||||
item_path = /obj/item/dice/d100
|
||||
|
||||
/datum/loadout_item/pocket_items/d00
|
||||
name = "D00"
|
||||
item_path = /obj/item/dice/d00
|
||||
79
code/modules/loadout/loadout_categories.dm
Normal file
79
code/modules/loadout/loadout_categories.dm
Normal file
@@ -0,0 +1,79 @@
|
||||
/**
|
||||
* # Loadout categories
|
||||
*
|
||||
* Loadout categories are singletons used to group loadout items together in the loadout screen.
|
||||
*/
|
||||
/datum/loadout_category
|
||||
/// The name of the category, shown in the tabs
|
||||
var/category_name
|
||||
/// FontAwesome icon for the category
|
||||
var/category_ui_icon
|
||||
/// String to display on the top-right of a category tab
|
||||
var/category_info
|
||||
/// Order which they appear in the tabs, ties go alphabetically
|
||||
var/tab_order = -1
|
||||
/// What type of loadout items should be generated for this category?
|
||||
var/type_to_generate
|
||||
/// List of all loadout items in this category
|
||||
VAR_FINAL/list/datum/loadout_item/associated_items
|
||||
|
||||
/datum/loadout_category/New()
|
||||
. = ..()
|
||||
associated_items = get_items()
|
||||
for(var/datum/loadout_item/item as anything in associated_items)
|
||||
if(GLOB.all_loadout_datums[item.item_path])
|
||||
stack_trace("Loadout datum collision - [item.item_path] is shared between multiple loadout datums.")
|
||||
GLOB.all_loadout_datums[item.item_path] = item
|
||||
|
||||
/datum/loadout_category/Destroy(force, ...)
|
||||
if(!force)
|
||||
stack_trace("QDEL called on loadout category [type]. This shouldn't ever happen. (Use FORCE if necessary.)")
|
||||
return QDEL_HINT_LETMELIVE
|
||||
|
||||
associated_items.Cut()
|
||||
return ..()
|
||||
|
||||
/// Return a list of all /datum/loadout_items in this category.
|
||||
/datum/loadout_category/proc/get_items() as /list
|
||||
var/list/all_items = list()
|
||||
for(var/datum/loadout_item/found_type as anything in typesof(type_to_generate))
|
||||
if(found_type == initial(found_type.abstract_type))
|
||||
continue
|
||||
|
||||
if(!ispath(initial(found_type.item_path), /obj/item))
|
||||
stack_trace("Loadout get_items(): Attempted to instantiate a loadout item ([found_type]) with an invalid or null typepath! (got path: [initial(found_type.item_path)])")
|
||||
continue
|
||||
|
||||
var/datum/loadout_item/spawned_type = new found_type(src)
|
||||
all_items += spawned_type
|
||||
|
||||
return all_items
|
||||
|
||||
/// Returns a list of all /datum/loadout_items in this category, formatted for UI use. Only ran once.
|
||||
/datum/loadout_category/proc/items_to_ui_data() as /list
|
||||
if(!length(associated_items))
|
||||
return list()
|
||||
|
||||
var/list/formatted_list = list()
|
||||
|
||||
for(var/datum/loadout_item/item as anything in associated_items)
|
||||
var/list/item_data = item.to_ui_data()
|
||||
UNTYPED_LIST_ADD(formatted_list, item_data)
|
||||
|
||||
sortTim(formatted_list, /proc/cmp_assoc_list_name) // Alphabetizing
|
||||
return formatted_list
|
||||
|
||||
/**
|
||||
* Handles what happens when two items of this category are selected at once
|
||||
*
|
||||
* Return TRUE if it's okay to continue with adding the incoming item,
|
||||
* or return FALSE to stop the new item from being added
|
||||
*/
|
||||
/datum/loadout_category/proc/handle_duplicate_entires(
|
||||
datum/preference_middleware/loadout/manager,
|
||||
datum/loadout_item/conflicting_item,
|
||||
datum/loadout_item/added_item,
|
||||
list/datum/loadout_item/all_loadout_items,
|
||||
)
|
||||
manager.deselect_item(conflicting_item)
|
||||
return TRUE
|
||||
80
code/modules/loadout/loadout_helpers.dm
Normal file
80
code/modules/loadout/loadout_helpers.dm
Normal file
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* Equips this mob with a given outfit and loadout items as per the passed preferences.
|
||||
*
|
||||
* Loadout items override the pre-existing item in the corresponding slot of the job outfit.
|
||||
* Some job items are preserved after being overridden - belt items, ear items, and glasses.
|
||||
* The rest of the slots, the items are overridden completely and deleted.
|
||||
*
|
||||
* Species with special outfits are snowflaked to have loadout items placed in their bags instead of overriding the outfit.
|
||||
*
|
||||
* * outfit - the job outfit we're equipping
|
||||
* * preference_source - the preferences to draw loadout items from.
|
||||
* * visuals_only - whether we call special equipped procs, or if we just look like we equipped it
|
||||
*/
|
||||
/mob/living/carbon/human/proc/equip_outfit_and_loadout(
|
||||
datum/outfit/outfit = /datum/outfit,
|
||||
datum/preferences/preference_source,
|
||||
visuals_only = FALSE,
|
||||
)
|
||||
if(isnull(preference_source))
|
||||
return equipOutfit(outfit, visuals_only)
|
||||
|
||||
var/datum/outfit/equipped_outfit
|
||||
if(ispath(outfit, /datum/outfit))
|
||||
equipped_outfit = new outfit()
|
||||
else if(istype(outfit, /datum/outfit))
|
||||
equipped_outfit = outfit
|
||||
else
|
||||
CRASH("Invalid outfit passed to equip_outfit_and_loadout ([outfit])")
|
||||
|
||||
var/list/preference_list = preference_source.read_preference(/datum/preference/loadout)
|
||||
var/list/loadout_datums = loadout_list_to_datums(preference_list)
|
||||
// Slap our things into the outfit given
|
||||
for(var/datum/loadout_item/item as anything in loadout_datums)
|
||||
item.insert_path_into_outfit(equipped_outfit, src, visuals_only)
|
||||
// Equip the outfit loadout items included
|
||||
if(!equipped_outfit.equip(src, visuals_only))
|
||||
return FALSE
|
||||
// Handle any snowflake on_equips
|
||||
var/list/new_contents = get_all_gear()
|
||||
var/update = NONE
|
||||
for(var/datum/loadout_item/item as anything in loadout_datums)
|
||||
var/obj/item/equipped = locate(item.item_path) in new_contents
|
||||
if(isnull(equipped))
|
||||
continue
|
||||
update |= item.on_equip_item(
|
||||
equipped_item = equipped,
|
||||
preference_source = preference_source,
|
||||
preference_list = preference_list,
|
||||
equipper = src,
|
||||
visuals_only = visuals_only,
|
||||
)
|
||||
|
||||
if(update)
|
||||
update_clothing(update)
|
||||
|
||||
return TRUE
|
||||
|
||||
/**
|
||||
* Takes a list of paths (such as a loadout list)
|
||||
* and returns a list of their singleton loadout item datums
|
||||
*
|
||||
* loadout_list - the list being checked
|
||||
*
|
||||
* Returns a list of singleton datums
|
||||
*/
|
||||
/proc/loadout_list_to_datums(list/loadout_list) as /list
|
||||
var/list/datums = list()
|
||||
|
||||
if(!length(GLOB.all_loadout_datums))
|
||||
CRASH("No loadout datums in the global loadout list!")
|
||||
|
||||
for(var/path in loadout_list)
|
||||
var/actual_datum = GLOB.all_loadout_datums[path]
|
||||
if(!istype(actual_datum, /datum/loadout_item))
|
||||
stack_trace("Could not find ([path]) loadout item in the global list of loadout datums!")
|
||||
continue
|
||||
|
||||
datums += actual_datum
|
||||
|
||||
return datums
|
||||
371
code/modules/loadout/loadout_items.dm
Normal file
371
code/modules/loadout/loadout_items.dm
Normal file
@@ -0,0 +1,371 @@
|
||||
/// Global list of ALL loadout datums instantiated.
|
||||
/// Loadout datums are created by loadout categories.
|
||||
GLOBAL_LIST_EMPTY(all_loadout_datums)
|
||||
|
||||
/// Global list of all loadout categories
|
||||
/// Doesn't really NEED to be a global but we need to init this early for preferences,
|
||||
/// as the categories instantiate all the loadout datums
|
||||
GLOBAL_LIST_INIT(all_loadout_categories, init_loadout_categories())
|
||||
|
||||
/// Inits the global list of loadout category singletons
|
||||
/// Also inits loadout item singletons
|
||||
/proc/init_loadout_categories()
|
||||
var/list/loadout_categories = list()
|
||||
for(var/category_type in subtypesof(/datum/loadout_category))
|
||||
loadout_categories += new category_type()
|
||||
|
||||
sortTim(loadout_categories, /proc/cmp_loadout_categories)
|
||||
return loadout_categories
|
||||
|
||||
/proc/cmp_loadout_categories(datum/loadout_category/A, datum/loadout_category/B)
|
||||
var/a_order = A::tab_order
|
||||
var/b_order = B::tab_order
|
||||
if(a_order == b_order)
|
||||
return cmp_text_asc(A::category_name, B::category_name)
|
||||
return cmp_numeric_asc(a_order, b_order)
|
||||
|
||||
/**
|
||||
* # Loadout item datum
|
||||
*
|
||||
* Singleton that holds all the information about each loadout items, and how to equip them.
|
||||
*/
|
||||
/datum/loadout_item
|
||||
/// The category of the loadout item. Set automatically in New
|
||||
VAR_FINAL/datum/loadout_category/category
|
||||
/// Displayed name of the loadout item.
|
||||
/// Defaults to the item's name if unset.
|
||||
var/name
|
||||
/// Whether this item has greyscale support.
|
||||
/// Only works if the item is compatible with the GAGS system of coloring.
|
||||
/// Set automatically to TRUE for all items that have the flag [IS_PLAYER_COLORABLE_1].
|
||||
/// If you really want it to not be colorable set this to [DONT_GREYSCALE]
|
||||
var/can_be_greyscale = FALSE
|
||||
/// Whether this item can be renamed.
|
||||
/// I recommend you apply this sparingly becuase it certainly can go wrong (or get reset / overridden easily)
|
||||
var/can_be_named = FALSE
|
||||
/// Whether this item can be reskinned.
|
||||
/// Only works if the item has a "unique reskin" list set.
|
||||
var/can_be_reskinned = FALSE
|
||||
/// The abstract parent of this loadout item, to determine which items to not instantiate
|
||||
var/abstract_type = /datum/loadout_item
|
||||
/// The actual item path of the loadout item.
|
||||
var/obj/item/item_path
|
||||
/// Lazylist of additional "information" text to display about this item.
|
||||
var/list/additional_displayed_text
|
||||
/// Icon file (DMI) for the UI to use for preview icons.
|
||||
/// Set automatically if null
|
||||
var/ui_icon
|
||||
/// Icon state for the UI to use for preview icons.
|
||||
/// Set automatically if null
|
||||
var/ui_icon_state
|
||||
/// Reskin options of this item if it can be reskinned.
|
||||
VAR_FINAL/list/cached_reskin_options
|
||||
|
||||
/datum/loadout_item/New(category)
|
||||
src.category = category
|
||||
|
||||
if(can_be_greyscale == DONT_GREYSCALE)
|
||||
can_be_greyscale = FALSE
|
||||
else if(item_path::flags_1 & IS_PLAYER_COLORABLE_1)
|
||||
can_be_greyscale = TRUE
|
||||
|
||||
if(isnull(name))
|
||||
name = item_path::name
|
||||
|
||||
if(isnull(ui_icon) && isnull(ui_icon_state))
|
||||
ui_icon = item_path::icon_preview || item_path::icon
|
||||
ui_icon_state = item_path::icon_state_preview || item_path::icon_state
|
||||
|
||||
if(can_be_reskinned)
|
||||
var/obj/item/dummy_item = new item_path()
|
||||
if(!length(dummy_item.unique_reskin))
|
||||
can_be_reskinned = FALSE
|
||||
stack_trace("Loadout item [item_path] has can_be_reskinned set to TRUE but has no unique reskins.")
|
||||
else
|
||||
cached_reskin_options = dummy_item.unique_reskin.Copy()
|
||||
qdel(dummy_item)
|
||||
|
||||
/datum/loadout_item/Destroy(force, ...)
|
||||
if(force)
|
||||
stack_trace("QDEL called on loadout item [type]. This shouldn't ever happen. (Use FORCE if necessary.)")
|
||||
return QDEL_HINT_LETMELIVE
|
||||
|
||||
GLOB.all_loadout_datums -= item_path
|
||||
return ..()
|
||||
|
||||
/**
|
||||
* Takes in an action from a loadout manager and applies it
|
||||
*
|
||||
* Useful for subtypes of loadout items with unique actions
|
||||
*
|
||||
* Return TRUE to force an update to the UI / character preview
|
||||
*/
|
||||
/datum/loadout_item/proc/handle_loadout_action(datum/preference_middleware/loadout/manager, mob/user, action, params)
|
||||
SHOULD_CALL_PARENT(TRUE)
|
||||
|
||||
switch(action)
|
||||
if("select_color")
|
||||
if(can_be_greyscale)
|
||||
return set_item_color(manager, user)
|
||||
|
||||
if("set_name")
|
||||
if(can_be_named)
|
||||
return set_name(manager, user)
|
||||
|
||||
if("set_skin")
|
||||
return set_skin(manager, user, params)
|
||||
|
||||
return TRUE
|
||||
|
||||
/// Opens up the GAGS editing menu.
|
||||
/datum/loadout_item/proc/set_item_color(datum/preference_middleware/loadout/manager, mob/user)
|
||||
if(manager.menu)
|
||||
return FALSE
|
||||
|
||||
var/list/loadout = manager.preferences.read_preference(/datum/preference/loadout)
|
||||
var/list/allowed_configs = list()
|
||||
if(initial(item_path.greyscale_config))
|
||||
allowed_configs += "[initial(item_path.greyscale_config)]"
|
||||
if(initial(item_path.greyscale_config_worn))
|
||||
allowed_configs += "[initial(item_path.greyscale_config_worn)]"
|
||||
if(initial(item_path.greyscale_config_inhand_left))
|
||||
allowed_configs += "[initial(item_path.greyscale_config_inhand_left)]"
|
||||
if(initial(item_path.greyscale_config_inhand_right))
|
||||
allowed_configs += "[initial(item_path.greyscale_config_inhand_right)]"
|
||||
|
||||
var/datum/greyscale_modify_menu/menu = new(
|
||||
manager,
|
||||
user,
|
||||
allowed_configs,
|
||||
CALLBACK(src, PROC_REF(set_slot_greyscale), manager),
|
||||
starting_icon_state = initial(item_path.icon_state),
|
||||
starting_config = initial(item_path.greyscale_config),
|
||||
starting_colors = loadout?[item_path]?[INFO_GREYSCALE] || initial(item_path.greyscale_colors),
|
||||
)
|
||||
|
||||
manager.register_greyscale_menu(menu)
|
||||
menu.ui_interact(user)
|
||||
return TRUE
|
||||
|
||||
/// Callback for GAGS menu to set this item's color.
|
||||
/datum/loadout_item/proc/set_slot_greyscale(datum/preference_middleware/loadout/manager, datum/greyscale_modify_menu/open_menu)
|
||||
if(!istype(open_menu))
|
||||
CRASH("set_slot_greyscale called without a greyscale menu!")
|
||||
|
||||
var/list/loadout = manager.preferences.read_preference(/datum/preference/loadout)
|
||||
if(!loadout?[item_path])
|
||||
return FALSE
|
||||
|
||||
var/list/colors = open_menu.split_colors
|
||||
if(!colors)
|
||||
return FALSE
|
||||
|
||||
loadout[item_path][INFO_GREYSCALE] = colors.Join("")
|
||||
manager.preferences.update_preference(GLOB.preference_entries[/datum/preference/loadout], loadout)
|
||||
return TRUE // update UI
|
||||
|
||||
/// Sets the name of the item.
|
||||
/datum/loadout_item/proc/set_name(datum/preference_middleware/loadout/manager, mob/user)
|
||||
var/list/loadout = manager.preferences.read_preference(/datum/preference/loadout)
|
||||
var/input_name = tgui_input_text(
|
||||
user = user,
|
||||
message = "What name do you want to give the [name]? Leave blank to clear.",
|
||||
title = "[name] name",
|
||||
default = loadout?[item_path]?[INFO_NAMED], // plop in existing name (if any)
|
||||
max_length = MAX_NAME_LEN,
|
||||
)
|
||||
if(QDELETED(src) || QDELETED(user) || QDELETED(manager) || QDELETED(manager.preferences))
|
||||
return FALSE
|
||||
|
||||
loadout = manager.preferences.read_preference(/datum/preference/loadout) // Make sure no shenanigans happened
|
||||
if(!loadout?[item_path])
|
||||
return FALSE
|
||||
|
||||
if(input_name)
|
||||
loadout[item_path][INFO_NAMED] = input_name
|
||||
else if(input_name == "")
|
||||
loadout[item_path] -= INFO_NAMED
|
||||
|
||||
manager.preferences.update_preference(GLOB.preference_entries[/datum/preference/loadout], loadout)
|
||||
return FALSE // no update needed
|
||||
|
||||
/// Used for reskinning an item to an alt skin.
|
||||
/datum/loadout_item/proc/set_skin(datum/preference_middleware/loadout/manager, mob/user, params)
|
||||
if(!can_be_reskinned)
|
||||
return FALSE
|
||||
|
||||
var/reskin_to = params["skin"]
|
||||
if(!cached_reskin_options[reskin_to])
|
||||
return FALSE
|
||||
|
||||
var/list/loadout = manager.preferences.read_preference(/datum/preference/loadout)
|
||||
if(!loadout?[item_path])
|
||||
return FALSE
|
||||
|
||||
loadout[item_path][INFO_RESKIN] = reskin_to
|
||||
manager.preferences.update_preference(GLOB.preference_entries[/datum/preference/loadout], loadout)
|
||||
return TRUE // always update UI
|
||||
|
||||
/**
|
||||
* Place our [item_path] into the passed [outfit].
|
||||
*
|
||||
* By default, just adds the item into the outfit's backpack contents, if non-visual.
|
||||
*
|
||||
* Arguments:
|
||||
* * outfit - The outfit we're equipping our items into.
|
||||
* * equipper - If we're equipping out outfit onto a mob at the time, this is the mob it is equipped on. Can be null.
|
||||
* * visual - If TRUE, then our outfit is only for visual use (for example, a preview).
|
||||
*/
|
||||
/datum/loadout_item/proc/insert_path_into_outfit(datum/outfit/outfit, mob/living/carbon/human/equipper, visuals_only = FALSE)
|
||||
if(!visuals_only)
|
||||
LAZYADD(outfit.backpack_contents, item_path)
|
||||
|
||||
/**
|
||||
* Called When the item is equipped on [equipper].
|
||||
*
|
||||
* At this point the item is in the mob's contents
|
||||
*
|
||||
* Arguments:
|
||||
* * preference_source - the datum/preferences our loadout item originated from - cannot be null
|
||||
* * equipper - the mob we're equipping this item onto - cannot be null
|
||||
* * visuals_only - whether or not this is only concerned with visual things (not backpack, not renaming, etc)
|
||||
* * preference_list - what the raw loadout list looks like in the preferences
|
||||
*
|
||||
* Return a bitflag of slot flags to update
|
||||
*/
|
||||
/datum/loadout_item/proc/on_equip_item(
|
||||
obj/item/equipped_item,
|
||||
datum/preferences/preference_source,
|
||||
list/preference_list,
|
||||
mob/living/carbon/human/equipper,
|
||||
visuals_only = FALSE,
|
||||
)
|
||||
ASSERT(!isnull(equipped_item))
|
||||
|
||||
if(!visuals_only)
|
||||
ADD_TRAIT(equipped_item, TRAIT_ITEM_OBJECTIVE_BLOCKED, "Loadout")
|
||||
|
||||
var/list/item_details = preference_list[item_path]
|
||||
var/update_flag = NONE
|
||||
|
||||
if(can_be_greyscale && item_details?[INFO_GREYSCALE])
|
||||
equipped_item.set_greyscale(item_details[INFO_GREYSCALE])
|
||||
update_flag |= equipped_item.slot_flags
|
||||
|
||||
if(can_be_named && item_details?[INFO_NAMED] && !visuals_only)
|
||||
equipped_item.name = trim(item_details[INFO_NAMED], PREVENT_CHARACTER_TRIM_LOSS(MAX_NAME_LEN))
|
||||
ADD_TRAIT(equipped_item, TRAIT_WAS_RENAMED, "Loadout")
|
||||
|
||||
if(can_be_reskinned && item_details?[INFO_RESKIN])
|
||||
var/skin_chosen = item_details[INFO_RESKIN]
|
||||
if(skin_chosen in equipped_item.unique_reskin)
|
||||
equipped_item.current_skin = skin_chosen
|
||||
equipped_item.icon_state = equipped_item.unique_reskin[skin_chosen]
|
||||
if(istype(equipped_item, /obj/item/clothing/accessory))
|
||||
// Snowflake handing for accessories, because we need to update the thing it's attached to instead
|
||||
if(isclothing(equipped_item.loc))
|
||||
var/obj/item/clothing/under/attached_to = equipped_item.loc
|
||||
attached_to.update_accessory_overlay()
|
||||
update_flag |= (ITEM_SLOT_OCLOTHING|ITEM_SLOT_ICLOTHING)
|
||||
else
|
||||
update_flag |= equipped_item.slot_flags
|
||||
|
||||
else
|
||||
// Not valid, update the preference
|
||||
item_details -= INFO_RESKIN
|
||||
preference_source.write_preference(GLOB.preference_entries[/datum/preference/loadout], preference_list)
|
||||
|
||||
return update_flag
|
||||
|
||||
/**
|
||||
* Returns a formatted list of data for this loadout item.
|
||||
*/
|
||||
/datum/loadout_item/proc/to_ui_data() as /list
|
||||
SHOULD_CALL_PARENT(TRUE)
|
||||
|
||||
var/list/formatted_item = list()
|
||||
formatted_item["name"] = name
|
||||
formatted_item["path"] = item_path
|
||||
formatted_item["information"] = get_item_information()
|
||||
formatted_item["buttons"] = get_ui_buttons()
|
||||
formatted_item["reskins"] = get_reskin_options()
|
||||
formatted_item["icon"] = ui_icon
|
||||
formatted_item["icon_state"] = ui_icon_state
|
||||
return formatted_item
|
||||
|
||||
/**
|
||||
* Returns a list of information to display about this item in the loadout UI.
|
||||
*
|
||||
* These should be short strings, sub 14 characters generally.
|
||||
*/
|
||||
/datum/loadout_item/proc/get_item_information() as /list
|
||||
SHOULD_CALL_PARENT(TRUE)
|
||||
|
||||
var/list/displayed_text = list()
|
||||
|
||||
displayed_text += (additional_displayed_text || list())
|
||||
|
||||
if(can_be_greyscale)
|
||||
displayed_text += "Recolorable"
|
||||
|
||||
if(can_be_named)
|
||||
displayed_text += "Renamable"
|
||||
|
||||
if(can_be_reskinned)
|
||||
displayed_text += "Reskinnable"
|
||||
|
||||
return displayed_text
|
||||
|
||||
/**
|
||||
* Returns a list of buttons that are shown in the loadout UI for customizing this item.
|
||||
*
|
||||
* Buttons contain
|
||||
* - 'L'abel: The text displayed beside the button
|
||||
* - act_key: The key that is sent to the loadout manager when the button is clicked,
|
||||
* for use in handle_loadout_action
|
||||
* - button_icon: The FontAwesome icon to display on the button
|
||||
* - active_key: In the loadout UI, this key is checked in the user's loadout list for this item
|
||||
* to determine if the button is 'active' (green) or not (blue).
|
||||
* - active_text: Optional, if provided, the button appears to be a checkbox and this text is shown when 'active'
|
||||
* - inactive_text: Optional, if provided, the button appears to be a checkbox and this text is shown when not 'active'
|
||||
*/
|
||||
/datum/loadout_item/proc/get_ui_buttons() as /list
|
||||
SHOULD_CALL_PARENT(TRUE)
|
||||
|
||||
var/list/button_list = list()
|
||||
|
||||
if(can_be_greyscale)
|
||||
UNTYPED_LIST_ADD(button_list, list(
|
||||
"label" = "Recolor",
|
||||
"act_key" = "select_color",
|
||||
"button_icon" = FA_ICON_PALETTE,
|
||||
"active_key" = INFO_GREYSCALE,
|
||||
))
|
||||
|
||||
if(can_be_named)
|
||||
UNTYPED_LIST_ADD(button_list, list(
|
||||
"label" = "Rename",
|
||||
"act_key" = "set_name",
|
||||
"button_icon" = FA_ICON_PEN,
|
||||
"active_key" = INFO_NAMED,
|
||||
))
|
||||
|
||||
return button_list
|
||||
|
||||
/**
|
||||
* Returns a list of options this item can be reskinned into.
|
||||
*/
|
||||
/datum/loadout_item/proc/get_reskin_options() as /list
|
||||
if(!can_be_reskinned)
|
||||
return null
|
||||
|
||||
var/list/reskins = list()
|
||||
|
||||
for(var/skin in cached_reskin_options)
|
||||
UNTYPED_LIST_ADD(reskins, list(
|
||||
"name" = skin,
|
||||
"tooltip" = skin,
|
||||
"skin_icon_state" = cached_reskin_options[skin],
|
||||
))
|
||||
|
||||
return reskins
|
||||
120
code/modules/loadout/loadout_menu.dm
Normal file
120
code/modules/loadout/loadout_menu.dm
Normal file
@@ -0,0 +1,120 @@
|
||||
/datum/preference_middleware/loadout
|
||||
action_delegations = list(
|
||||
"clear_all_items" = PROC_REF(action_clear_all),
|
||||
"pass_to_loadout_item" = PROC_REF(action_pass_to_loadout_item),
|
||||
"rotate_dummy" = PROC_REF(action_rotate_model_dir),
|
||||
"select_item" = PROC_REF(action_select_item),
|
||||
"toggle_job_clothes" = PROC_REF(action_toggle_job_outfit),
|
||||
"close_greyscale_menu" = PROC_REF(force_close_greyscale_menu),
|
||||
)
|
||||
/// Our currently open greyscaling menu.
|
||||
VAR_FINAL/datum/greyscale_modify_menu/menu
|
||||
|
||||
/datum/preference_middleware/loadout/Destroy(force, ...)
|
||||
QDEL_NULL(menu)
|
||||
return ..()
|
||||
|
||||
/datum/preference_middleware/loadout/on_new_character(mob/user)
|
||||
preferences.character_preview_view?.update_body()
|
||||
|
||||
/datum/preference_middleware/loadout/proc/action_select_item(list/params, mob/user)
|
||||
PRIVATE_PROC(TRUE)
|
||||
var/path_to_use = text2path(params["path"])
|
||||
var/datum/loadout_item/interacted_item = GLOB.all_loadout_datums[path_to_use]
|
||||
if(!istype(interacted_item))
|
||||
stack_trace("Failed to locate desired loadout item (path: [params["path"]]) in the global list of loadout datums!")
|
||||
return TRUE // update
|
||||
|
||||
if(params["deselect"])
|
||||
deselect_item(interacted_item)
|
||||
else
|
||||
select_item(interacted_item)
|
||||
return TRUE
|
||||
|
||||
/datum/preference_middleware/loadout/proc/action_clear_all(list/params, mob/user)
|
||||
PRIVATE_PROC(TRUE)
|
||||
preferences.update_preference(GLOB.preference_entries[/datum/preference/loadout], null)
|
||||
return TRUE
|
||||
|
||||
/datum/preference_middleware/loadout/proc/action_toggle_job_outfit(list/params, mob/user)
|
||||
PRIVATE_PROC(TRUE)
|
||||
preferences.character_preview_view.show_job_clothes = !preferences.character_preview_view.show_job_clothes
|
||||
preferences.character_preview_view.update_body()
|
||||
return TRUE
|
||||
|
||||
/datum/preference_middleware/loadout/proc/action_rotate_model_dir(list/params, mob/user)
|
||||
PRIVATE_PROC(TRUE)
|
||||
switch(params["dir"])
|
||||
if("left")
|
||||
preferences.character_preview_view.setDir(turn(preferences.character_preview_view.dir, -90))
|
||||
if("right")
|
||||
preferences.character_preview_view.setDir(turn(preferences.character_preview_view.dir, 90))
|
||||
|
||||
/datum/preference_middleware/loadout/proc/action_pass_to_loadout_item(list/params, mob/user)
|
||||
PRIVATE_PROC(TRUE)
|
||||
var/path_to_use = text2path(params["path"])
|
||||
var/datum/loadout_item/interacted_item = GLOB.all_loadout_datums[path_to_use]
|
||||
if(!istype(interacted_item)) // no you cannot href exploit to spawn with a pulse rifle
|
||||
stack_trace("Failed to locate desired loadout item (path: [params["path"]]) in the global list of loadout datums!")
|
||||
return TRUE // update
|
||||
|
||||
if(interacted_item.handle_loadout_action(src, user, params["subaction"], params))
|
||||
preferences.character_preview_view.update_body()
|
||||
return TRUE
|
||||
|
||||
return FALSE
|
||||
|
||||
/// Select [path] item to [category_slot] slot.
|
||||
/datum/preference_middleware/loadout/proc/select_item(datum/loadout_item/selected_item)
|
||||
var/list/loadout = preferences.read_preference(/datum/preference/loadout)
|
||||
var/list/datum/loadout_item/loadout_datums = loadout_list_to_datums(loadout)
|
||||
for(var/datum/loadout_item/item as anything in loadout_datums)
|
||||
if(item.category != selected_item.category)
|
||||
continue
|
||||
if(!item.category.handle_duplicate_entires(src, item, selected_item, loadout_datums))
|
||||
return
|
||||
|
||||
LAZYSET(loadout, selected_item.item_path, list())
|
||||
preferences.update_preference(GLOB.preference_entries[/datum/preference/loadout], loadout)
|
||||
|
||||
/// Deselect [deselected_item].
|
||||
/datum/preference_middleware/loadout/proc/deselect_item(datum/loadout_item/deselected_item)
|
||||
var/list/loadout = preferences.read_preference(/datum/preference/loadout)
|
||||
LAZYREMOVE(loadout, deselected_item.item_path)
|
||||
preferences.update_preference(GLOB.preference_entries[/datum/preference/loadout], loadout)
|
||||
|
||||
/datum/preference_middleware/loadout/proc/register_greyscale_menu(datum/greyscale_modify_menu/open_menu)
|
||||
src.menu = open_menu
|
||||
RegisterSignal(menu, COMSIG_QDELETING, PROC_REF(cleanup_greyscale_menu))
|
||||
|
||||
/datum/preference_middleware/loadout/proc/cleanup_greyscale_menu()
|
||||
SIGNAL_HANDLER
|
||||
menu = null
|
||||
|
||||
/datum/preference_middleware/loadout/proc/force_close_greyscale_menu()
|
||||
menu?.ui_close()
|
||||
|
||||
/datum/preference_middleware/loadout/get_ui_data(mob/user)
|
||||
var/list/data = list()
|
||||
data["job_clothes"] = preferences.character_preview_view.show_job_clothes
|
||||
return data
|
||||
|
||||
/datum/preference_middleware/loadout/get_ui_static_data(mob/user)
|
||||
var/list/data = list()
|
||||
data["loadout_preview_view"] = preferences.character_preview_view.assigned_map
|
||||
return data
|
||||
|
||||
/datum/preference_middleware/loadout/get_constant_data()
|
||||
var/list/data = list()
|
||||
var/list/loadout_tabs = list()
|
||||
for(var/datum/loadout_category/category as anything in GLOB.all_loadout_categories)
|
||||
var/list/cat_data = list(
|
||||
"name" = category.category_name,
|
||||
"category_icon" = category.category_ui_icon,
|
||||
"category_info" = category.category_info,
|
||||
"contents" = category.items_to_ui_data(),
|
||||
)
|
||||
UNTYPED_LIST_ADD(loadout_tabs, cat_data)
|
||||
|
||||
data["loadout_tabs"] = loadout_tabs
|
||||
return data
|
||||
59
code/modules/loadout/loadout_preference.dm
Normal file
59
code/modules/loadout/loadout_preference.dm
Normal file
@@ -0,0 +1,59 @@
|
||||
/datum/preference/loadout
|
||||
savefile_key = "loadout_list"
|
||||
savefile_identifier = PREFERENCE_CHARACTER
|
||||
priority = PREFERENCE_PRIORITY_LOADOUT
|
||||
can_randomize = FALSE
|
||||
// Loadout preference is an assoc list [item_path] = [loadout item information list]
|
||||
//
|
||||
// it may look something like
|
||||
// - list(/obj/item/glasses = list())
|
||||
// or
|
||||
// - list(/obj/item/plush/lizard = list("name" = "Tests-The-Loadout", "color" = "#FF0000"))
|
||||
|
||||
// Loadouts are applied with job equip code.
|
||||
/datum/preference/loadout/apply_to_human(mob/living/carbon/human/target, value)
|
||||
return
|
||||
|
||||
// Sanitize on load to ensure no invalid paths from older saves get in
|
||||
/datum/preference/loadout/deserialize(input, datum/preferences/preferences)
|
||||
return sanitize_loadout_list(input, preferences.parent?.mob)
|
||||
|
||||
// Default value is null - the loadout list is a lazylist
|
||||
/datum/preference/loadout/create_default_value(datum/preferences/preferences)
|
||||
return null
|
||||
|
||||
/datum/preference/loadout/is_valid(value)
|
||||
return isnull(value) || islist(value)
|
||||
|
||||
/**
|
||||
* Removes all invalid paths from loadout lists.
|
||||
* This is a general sanitization for preference loading.
|
||||
*
|
||||
* Returns a list, or null if empty
|
||||
*/
|
||||
/datum/preference/loadout/proc/sanitize_loadout_list(list/passed_list, mob/optional_loadout_owner) as /list
|
||||
var/list/sanitized_list
|
||||
for(var/path in passed_list)
|
||||
// Loading from json has each path in the list as a string that we need to convert back to typepath
|
||||
var/obj/item/real_path = istext(path) ? text2path(path) : path
|
||||
if(!ispath(real_path, /obj/item))
|
||||
if(optional_loadout_owner)
|
||||
to_chat(optional_loadout_owner, span_boldnotice("The following invalid item path was found \
|
||||
in your character loadout: [real_path || "null"]. \
|
||||
It has been removed, renamed, or is otherwise missing - \
|
||||
You may want to check your loadout settings."))
|
||||
continue
|
||||
|
||||
else if(!istype(GLOB.all_loadout_datums[real_path], /datum/loadout_item))
|
||||
if(optional_loadout_owner)
|
||||
to_chat(optional_loadout_owner, span_boldnotice("The following invalid loadout item was found \
|
||||
in your character loadout: [real_path || "null"]. \
|
||||
It has been removed, renamed, or is otherwise missing - \
|
||||
You may want to check your loadout settings."))
|
||||
continue
|
||||
|
||||
// Set into sanitize list using converted path key
|
||||
var/list/data = passed_list[path]
|
||||
LAZYSET(sanitized_list, real_path, LAZYLISTDUPLICATE(data))
|
||||
|
||||
return sanitized_list
|
||||
@@ -93,8 +93,9 @@
|
||||
|
||||
return preview_job
|
||||
|
||||
/datum/preferences/proc/render_new_preview_appearance(mob/living/carbon/human/dummy/mannequin)
|
||||
var/datum/job/preview_job = get_highest_priority_job()
|
||||
/datum/preferences/proc/render_new_preview_appearance(mob/living/carbon/human/dummy/mannequin, show_job_clothes = TRUE)
|
||||
var/datum/job/no_job = SSjob.GetJobType(/datum/job/unassigned)
|
||||
var/datum/job/preview_job = get_highest_priority_job() || no_job
|
||||
|
||||
if(preview_job)
|
||||
// Silicons only need a very basic preview since there is no customization for them.
|
||||
@@ -106,9 +107,13 @@
|
||||
// Set up the dummy for its photoshoot
|
||||
apply_prefs_to(mannequin, TRUE)
|
||||
|
||||
if(preview_job)
|
||||
mannequin.job = preview_job.title
|
||||
mannequin.dress_up_as_job(preview_job, TRUE)
|
||||
mannequin.dress_up_as_job(
|
||||
equipping = show_job_clothes ? preview_job : no_job,
|
||||
visual_only = TRUE,
|
||||
player_client = parent,
|
||||
consistent = TRUE,
|
||||
)
|
||||
|
||||
// Apply visual quirks
|
||||
// Yes we do it every time because it needs to be done after job gear
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 7.6 KiB After Width: | Height: | Size: 8.3 KiB |
@@ -1727,7 +1727,6 @@
|
||||
#include "code\datums\quirks\neutral_quirks\photographer.dm"
|
||||
#include "code\datums\quirks\neutral_quirks\pineapple_hater.dm"
|
||||
#include "code\datums\quirks\neutral_quirks\pineapple_liker.dm"
|
||||
#include "code\datums\quirks\neutral_quirks\pride_pin.dm"
|
||||
#include "code\datums\quirks\neutral_quirks\shifty_eyes.dm"
|
||||
#include "code\datums\quirks\neutral_quirks\snob.dm"
|
||||
#include "code\datums\quirks\neutral_quirks\transhumanist.dm"
|
||||
@@ -3695,7 +3694,6 @@
|
||||
#include "code\modules\client\preferences\pixel_size.dm"
|
||||
#include "code\modules\client\preferences\playtime_reward_cloak.dm"
|
||||
#include "code\modules\client\preferences\preferred_map.dm"
|
||||
#include "code\modules\client\preferences\pride_pin.dm"
|
||||
#include "code\modules\client\preferences\prisoner_crime.dm"
|
||||
#include "code\modules\client\preferences\prosthetic_limb.dm"
|
||||
#include "code\modules\client\preferences\prosthetic_organ.dm"
|
||||
@@ -3730,6 +3728,7 @@
|
||||
#include "code\modules\client\preferences\migrations\body_type_migration.dm"
|
||||
#include "code\modules\client\preferences\migrations\convert_to_json_savefile.dm"
|
||||
#include "code\modules\client\preferences\migrations\legacy_sound_toggles_migration.dm"
|
||||
#include "code\modules\client\preferences\migrations\quirk_loadout_migration.dm"
|
||||
#include "code\modules\client\preferences\migrations\tgui_prefs_migration.dm"
|
||||
#include "code\modules\client\preferences\migrations\tts_blip_migration.dm"
|
||||
#include "code\modules\client\preferences\species_features\basic.dm"
|
||||
@@ -4403,6 +4402,17 @@
|
||||
#include "code\modules\lighting\lighting_source.dm"
|
||||
#include "code\modules\lighting\lighting_turf.dm"
|
||||
#include "code\modules\lighting\static_lighting_area.dm"
|
||||
#include "code\modules\loadout\loadout_categories.dm"
|
||||
#include "code\modules\loadout\loadout_helpers.dm"
|
||||
#include "code\modules\loadout\loadout_items.dm"
|
||||
#include "code\modules\loadout\loadout_menu.dm"
|
||||
#include "code\modules\loadout\loadout_preference.dm"
|
||||
#include "code\modules\loadout\categories\accessories.dm"
|
||||
#include "code\modules\loadout\categories\glasses.dm"
|
||||
#include "code\modules\loadout\categories\heads.dm"
|
||||
#include "code\modules\loadout\categories\inhands.dm"
|
||||
#include "code\modules\loadout\categories\neck.dm"
|
||||
#include "code\modules\loadout\categories\pocket.dm"
|
||||
#include "code\modules\logging\log_category.dm"
|
||||
#include "code\modules\logging\log_entry.dm"
|
||||
#include "code\modules\logging\log_holder.dm"
|
||||
|
||||
@@ -7,6 +7,7 @@ import { Window } from '../../layouts';
|
||||
import { AntagsPage } from './AntagsPage';
|
||||
import { PreferencesMenuData } from './data';
|
||||
import { JobsPage } from './JobsPage';
|
||||
import { LoadoutPage } from './loadout/index';
|
||||
import { MainPage } from './MainPage';
|
||||
import { PageButton } from './PageButton';
|
||||
import { QuirksPage } from './QuirksPage';
|
||||
@@ -18,6 +19,7 @@ enum Page {
|
||||
Jobs,
|
||||
Species,
|
||||
Quirks,
|
||||
Loadout,
|
||||
}
|
||||
|
||||
const CharacterProfiles = (props: {
|
||||
@@ -75,6 +77,11 @@ export const CharacterPreferenceWindow = (props) => {
|
||||
case Page.Quirks:
|
||||
pageContents = <QuirksPage />;
|
||||
break;
|
||||
|
||||
case Page.Loadout:
|
||||
pageContents = <LoadoutPage />;
|
||||
break;
|
||||
|
||||
default:
|
||||
exhaustiveCheck(currentPage);
|
||||
}
|
||||
@@ -116,6 +123,16 @@ export const CharacterPreferenceWindow = (props) => {
|
||||
</PageButton>
|
||||
</Stack.Item>
|
||||
|
||||
<Stack.Item grow>
|
||||
<PageButton
|
||||
currentPage={currentPage}
|
||||
page={Page.Loadout}
|
||||
setPage={setCurrentPage}
|
||||
>
|
||||
Loadout
|
||||
</PageButton>
|
||||
</Stack.Item>
|
||||
|
||||
<Stack.Item grow>
|
||||
<PageButton
|
||||
currentPage={currentPage}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { BooleanLike } from 'common/react';
|
||||
|
||||
import { sendAct } from '../../backend';
|
||||
import { LoadoutCategory, LoadoutList } from './loadout/base';
|
||||
import { Gender } from './preferences/gender';
|
||||
|
||||
export enum Food {
|
||||
@@ -147,6 +148,8 @@ export type PreferencesMenuData = {
|
||||
gender: Gender;
|
||||
joblessrole: JoblessRole;
|
||||
species: string;
|
||||
loadout_list: LoadoutList;
|
||||
job_clothes: BooleanLike;
|
||||
};
|
||||
|
||||
randomization: Record<string, RandomSetting>;
|
||||
@@ -191,6 +194,9 @@ export type ServerData = {
|
||||
random: {
|
||||
randomizable: string[];
|
||||
};
|
||||
loadout: {
|
||||
loadout_tabs: LoadoutCategory[];
|
||||
};
|
||||
species: Record<string, Species>;
|
||||
[otheyKey: string]: unknown;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,147 @@
|
||||
import { createSearch } from '../../../../common/string';
|
||||
import { useBackend } from '../../../backend';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
DmIcon,
|
||||
Flex,
|
||||
Icon,
|
||||
NoticeBox,
|
||||
} from '../../../components';
|
||||
import { LoadoutCategory, LoadoutItem, LoadoutManagerData } from './base';
|
||||
|
||||
export const ItemIcon = (props: { item: LoadoutItem; scale?: number }) => {
|
||||
const { item, scale = 3 } = props;
|
||||
const icon_to_use = item.icon;
|
||||
const icon_state_to_use = item.icon_state;
|
||||
|
||||
if (!icon_to_use || !icon_state_to_use) {
|
||||
return (
|
||||
<Icon
|
||||
name="question"
|
||||
size={Math.round(scale * 2.5)}
|
||||
color="red"
|
||||
style={{
|
||||
transform: `translateX(${scale * 2}px) translateY(${scale * 2}px)`,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<DmIcon
|
||||
fallback={<Icon name="spinner" spin color="gray" />}
|
||||
icon={icon_to_use}
|
||||
icon_state={icon_state_to_use}
|
||||
style={{
|
||||
transform: `scale(${scale}) translateX(${scale * 3}px) translateY(${
|
||||
scale * 3
|
||||
}px)`,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const ItemDisplay = (props: {
|
||||
active: boolean;
|
||||
item: LoadoutItem;
|
||||
scale?: number;
|
||||
}) => {
|
||||
const { act } = useBackend();
|
||||
const { active, item, scale = 3 } = props;
|
||||
|
||||
const boxSize = `${scale * 32}px`;
|
||||
|
||||
return (
|
||||
<Button
|
||||
height={boxSize}
|
||||
width={boxSize}
|
||||
color={active ? 'green' : 'default'}
|
||||
style={{ textTransform: 'capitalize', zIndex: '1' }}
|
||||
tooltip={item.name}
|
||||
tooltipPosition={'bottom'}
|
||||
onClick={() =>
|
||||
act('select_item', {
|
||||
path: item.path,
|
||||
deselect: active,
|
||||
})
|
||||
}
|
||||
>
|
||||
<Flex vertical>
|
||||
<Flex.Item>
|
||||
<ItemIcon item={item} scale={scale} />
|
||||
</Flex.Item>
|
||||
{item.information.length > 0 && (
|
||||
<Flex.Item ml={-5.5} style={{ zIndex: '3' }}>
|
||||
{item.information.map((info) => (
|
||||
<Box
|
||||
height="9px"
|
||||
key={info}
|
||||
fontSize="9px"
|
||||
textColor={'darkgray'}
|
||||
bold
|
||||
>
|
||||
{info}
|
||||
</Box>
|
||||
))}
|
||||
</Flex.Item>
|
||||
)}
|
||||
</Flex>
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
const ItemListDisplay = (props: { items: LoadoutItem[] }) => {
|
||||
const { data } = useBackend<LoadoutManagerData>();
|
||||
const { loadout_list } = data.character_preferences.misc;
|
||||
return (
|
||||
<Flex wrap>
|
||||
{props.items.map((item) => (
|
||||
<Flex.Item key={item.name} mr={2} mb={2}>
|
||||
<ItemDisplay
|
||||
item={item}
|
||||
active={loadout_list && loadout_list[item.path] !== undefined}
|
||||
/>
|
||||
</Flex.Item>
|
||||
))}
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export const LoadoutTabDisplay = (props: {
|
||||
category: LoadoutCategory | undefined;
|
||||
}) => {
|
||||
const { category } = props;
|
||||
if (!category) {
|
||||
return (
|
||||
<NoticeBox>
|
||||
Erroneous category detected! This is a bug, please report it.
|
||||
</NoticeBox>
|
||||
);
|
||||
}
|
||||
|
||||
return <ItemListDisplay items={category.contents} />;
|
||||
};
|
||||
|
||||
export const SearchDisplay = (props: {
|
||||
loadout_tabs: LoadoutCategory[];
|
||||
currentSearch: string;
|
||||
}) => {
|
||||
const { loadout_tabs, currentSearch } = props;
|
||||
|
||||
const search = createSearch(
|
||||
currentSearch,
|
||||
(loadout_item: LoadoutItem) => loadout_item.name,
|
||||
);
|
||||
|
||||
const validLoadoutItems = loadout_tabs
|
||||
.flatMap((tab) => tab.contents)
|
||||
.filter(search)
|
||||
.sort((a, b) => (a.name > b.name ? 1 : -1));
|
||||
|
||||
if (validLoadoutItems.length === 0) {
|
||||
return <NoticeBox>No items found!</NoticeBox>;
|
||||
}
|
||||
|
||||
return <ItemListDisplay items={validLoadoutItems} />;
|
||||
};
|
||||
@@ -0,0 +1,219 @@
|
||||
import { useBackend } from '../../../backend';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Dimmer,
|
||||
DmIcon,
|
||||
Flex,
|
||||
Icon,
|
||||
LabeledList,
|
||||
Section,
|
||||
Stack,
|
||||
} from '../../../components';
|
||||
import { FAIcon, LoadoutItem, LoadoutManagerData, ReskinOption } from './base';
|
||||
import { ItemIcon } from './ItemDisplay';
|
||||
|
||||
// Used in LoadoutItem to make buttons relating to how an item can be edited
|
||||
export type LoadoutButton = {
|
||||
label: string;
|
||||
act_key?: string;
|
||||
button_icon?: FAIcon;
|
||||
button_text?: string;
|
||||
active_key?: string;
|
||||
active_text?: string;
|
||||
inactive_text?: string;
|
||||
tooltip_text?: string;
|
||||
};
|
||||
|
||||
const LoadoutModifyButton = (props: {
|
||||
button: LoadoutButton;
|
||||
modifyItemDimmer: LoadoutItem;
|
||||
}) => {
|
||||
const { act, data } = useBackend<LoadoutManagerData>();
|
||||
const { loadout_list } = data.character_preferences.misc;
|
||||
const { button, modifyItemDimmer } = props;
|
||||
|
||||
const buttonIsActive =
|
||||
button.active_key && loadout_list[modifyItemDimmer.path][button.active_key];
|
||||
|
||||
if (button.active_text && button.inactive_text) {
|
||||
return (
|
||||
<Button.Checkbox
|
||||
tooltip={button.tooltip_text}
|
||||
checked={buttonIsActive}
|
||||
color={buttonIsActive ? 'green' : 'default'}
|
||||
onClick={() => {
|
||||
act('pass_to_loadout_item', {
|
||||
subaction: button.act_key,
|
||||
path: modifyItemDimmer.path,
|
||||
});
|
||||
}}
|
||||
>
|
||||
{buttonIsActive ? button.active_text : button.inactive_text}
|
||||
</Button.Checkbox>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Button
|
||||
icon={button.button_icon}
|
||||
tooltip={button.tooltip_text}
|
||||
disabled={!button.act_key}
|
||||
color={buttonIsActive ? 'green' : 'default'}
|
||||
onClick={() => {
|
||||
act('pass_to_loadout_item', {
|
||||
subaction: button.act_key,
|
||||
path: modifyItemDimmer.path,
|
||||
});
|
||||
}}
|
||||
>
|
||||
{button.button_text}
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
const LoadoutModifyButtons = (props: { modifyItemDimmer: LoadoutItem }) => {
|
||||
const { act, data } = useBackend<LoadoutManagerData>();
|
||||
const { loadout_list } = data.character_preferences.misc;
|
||||
const { modifyItemDimmer } = props;
|
||||
|
||||
const isActive = (item: LoadoutItem, reskin: ReskinOption) => {
|
||||
return loadout_list && loadout_list[item.path]['reskin']
|
||||
? loadout_list[item.path]['reskin'] === reskin.name
|
||||
: item.icon_state === reskin.skin_icon_state;
|
||||
};
|
||||
|
||||
return (
|
||||
<Stack>
|
||||
<Stack.Item>
|
||||
<LabeledList>
|
||||
{!!modifyItemDimmer.reskins && (
|
||||
<LabeledList.Item label="Styles" verticalAlign="middle">
|
||||
<Flex wrap width="50%">
|
||||
{modifyItemDimmer.reskins.map((reskin) => (
|
||||
<Flex.Item key={reskin.tooltip} mr={1} mb={1}>
|
||||
<Button
|
||||
tooltip={reskin.tooltip}
|
||||
color={
|
||||
isActive(modifyItemDimmer, reskin) ? 'green' : 'default'
|
||||
}
|
||||
onClick={() => {
|
||||
act('pass_to_loadout_item', {
|
||||
subaction: 'set_skin',
|
||||
path: modifyItemDimmer.path,
|
||||
skin: reskin.name,
|
||||
});
|
||||
}}
|
||||
>
|
||||
{modifyItemDimmer.icon ? (
|
||||
<DmIcon
|
||||
fallback={<Icon name="spinner" spin color="gray" />}
|
||||
icon={modifyItemDimmer.icon}
|
||||
icon_state={reskin.skin_icon_state}
|
||||
style={{
|
||||
transform: `scale(2) translateY(2px)`,
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
// Should never happen, hopefully
|
||||
<Box>{reskin.name}</Box>
|
||||
)}
|
||||
</Button>
|
||||
</Flex.Item>
|
||||
))}
|
||||
</Flex>
|
||||
</LabeledList.Item>
|
||||
)}
|
||||
{modifyItemDimmer.buttons.map((button) => (
|
||||
<LabeledList.Item key={button.label} label={button.label}>
|
||||
<LoadoutModifyButton
|
||||
button={button}
|
||||
modifyItemDimmer={modifyItemDimmer}
|
||||
/>
|
||||
</LabeledList.Item>
|
||||
))}
|
||||
</LabeledList>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
const LoadoutModifyItemDisplay = (props: { modifyItemDimmer: LoadoutItem }) => {
|
||||
const { modifyItemDimmer } = props;
|
||||
return (
|
||||
<Stack vertical justify="center">
|
||||
<Stack.Item>
|
||||
<Box bold width="80px" textAlign="center">
|
||||
{modifyItemDimmer.name}
|
||||
</Box>
|
||||
</Stack.Item>
|
||||
<Stack.Item ml={-0.5}>
|
||||
<ItemIcon item={modifyItemDimmer} scale={3} />
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export const LoadoutModifyDimmer = (props: {
|
||||
modifyItemDimmer: LoadoutItem;
|
||||
setModifyItemDimmer: (dimmer: LoadoutItem | null) => void;
|
||||
}) => {
|
||||
const { act } = useBackend();
|
||||
const { modifyItemDimmer, setModifyItemDimmer } = props;
|
||||
return (
|
||||
<Dimmer style={{ zIndex: '100' }}>
|
||||
<Stack
|
||||
vertical
|
||||
width="400px"
|
||||
backgroundColor="#101010"
|
||||
style={{
|
||||
borderRadius: '2px',
|
||||
position: 'relative',
|
||||
display: 'inline-block',
|
||||
padding: '5px',
|
||||
}}
|
||||
>
|
||||
<Stack.Item height="20px">
|
||||
<Flex justify="flex-end">
|
||||
<Flex.Item>
|
||||
<Button
|
||||
icon="times"
|
||||
color="red"
|
||||
onClick={() => {
|
||||
setModifyItemDimmer(null);
|
||||
act('close_greyscale_menu');
|
||||
}}
|
||||
/>
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
</Stack.Item>
|
||||
<Stack.Item width="100%" height="100%">
|
||||
<Flex>
|
||||
<Flex.Item mr={1}>
|
||||
<Section width="90px" height="160px">
|
||||
<LoadoutModifyItemDisplay modifyItemDimmer={modifyItemDimmer} />
|
||||
</Section>
|
||||
</Flex.Item>
|
||||
<Flex.Item width="310px">
|
||||
<Section fill>
|
||||
<LoadoutModifyButtons modifyItemDimmer={modifyItemDimmer} />
|
||||
</Section>
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Stack justify="center">
|
||||
<Button
|
||||
onClick={() => {
|
||||
setModifyItemDimmer(null);
|
||||
act('close_greyscale_menu');
|
||||
}}
|
||||
>
|
||||
Done
|
||||
</Button>
|
||||
</Stack>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Dimmer>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,46 @@
|
||||
import { BooleanLike } from '../../../../common/react';
|
||||
import { PreferencesMenuData } from '../data';
|
||||
import { LoadoutButton } from './ModifyPanel';
|
||||
|
||||
// Generic types
|
||||
export type DmIconFile = string;
|
||||
export type DmIconState = string;
|
||||
export type FAIcon = string;
|
||||
export type typePath = string;
|
||||
|
||||
type LoadoutInfoKey = string;
|
||||
type LoadoutInfoValue = string;
|
||||
// Info about a loadout item (key to info, such as color, reskin, layer, etc)
|
||||
type LoadoutListInfo = Record<LoadoutInfoKey, LoadoutInfoValue> | [];
|
||||
// Typepath to info about the item
|
||||
export type LoadoutList = Record<typePath, LoadoutListInfo>;
|
||||
|
||||
// Used for holding reskin information
|
||||
export type ReskinOption = {
|
||||
name: string;
|
||||
tooltip: string;
|
||||
skin_icon_state: DmIconState; // The icon is the same as the item icon
|
||||
};
|
||||
|
||||
// Actual item passed in from the loadout
|
||||
export type LoadoutItem = {
|
||||
name: string;
|
||||
path: typePath;
|
||||
icon: DmIconFile | null;
|
||||
icon_state: DmIconState | null;
|
||||
buttons: LoadoutButton[];
|
||||
reskins: ReskinOption[] | null;
|
||||
information: string[];
|
||||
};
|
||||
|
||||
// Category of items in the loadout
|
||||
export type LoadoutCategory = {
|
||||
name: string;
|
||||
category_icon: FAIcon | null;
|
||||
category_info: string | null;
|
||||
contents: LoadoutItem[];
|
||||
};
|
||||
|
||||
export type LoadoutManagerData = PreferencesMenuData & {
|
||||
job_clothes: BooleanLike;
|
||||
};
|
||||
337
tgui/packages/tgui/interfaces/PreferencesMenu/loadout/index.tsx
Normal file
337
tgui/packages/tgui/interfaces/PreferencesMenu/loadout/index.tsx
Normal file
@@ -0,0 +1,337 @@
|
||||
import { Fragment, useState } from 'react';
|
||||
|
||||
import { useBackend } from '../../../backend';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Divider,
|
||||
Icon,
|
||||
Input,
|
||||
NoticeBox,
|
||||
Section,
|
||||
Stack,
|
||||
Tabs,
|
||||
} from '../../../components';
|
||||
import { CharacterPreview } from '../../common/CharacterPreview';
|
||||
import { ServerData } from '../data';
|
||||
import { ServerPreferencesFetcher } from '../ServerPreferencesFetcher';
|
||||
import {
|
||||
LoadoutCategory,
|
||||
LoadoutItem,
|
||||
LoadoutManagerData,
|
||||
typePath,
|
||||
} from './base';
|
||||
import { ItemIcon, LoadoutTabDisplay, SearchDisplay } from './ItemDisplay';
|
||||
import { LoadoutModifyDimmer } from './ModifyPanel';
|
||||
|
||||
export const LoadoutPage = () => {
|
||||
return (
|
||||
<ServerPreferencesFetcher
|
||||
render={(serverData) => {
|
||||
if (!serverData) {
|
||||
return <NoticeBox>Loading...</NoticeBox>;
|
||||
}
|
||||
const loadoutServerData: ServerData = serverData;
|
||||
return (
|
||||
<LoadoutPageInner
|
||||
loadout_tabs={loadoutServerData.loadout.loadout_tabs}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const LoadoutPageInner = (props: { loadout_tabs: LoadoutCategory[] }) => {
|
||||
const { loadout_tabs } = props;
|
||||
const [searchLoadout, setSearchLoadout] = useState('');
|
||||
const [selectedTabName, setSelectedTab] = useState(loadout_tabs[0].name);
|
||||
const [modifyItemDimmer, setModifyItemDimmer] = useState<LoadoutItem | null>(
|
||||
null,
|
||||
);
|
||||
|
||||
return (
|
||||
<Stack vertical fill>
|
||||
<Stack.Item>
|
||||
{!!modifyItemDimmer && (
|
||||
<LoadoutModifyDimmer
|
||||
modifyItemDimmer={modifyItemDimmer}
|
||||
setModifyItemDimmer={setModifyItemDimmer}
|
||||
/>
|
||||
)}
|
||||
<Section
|
||||
title=" "
|
||||
align="center"
|
||||
buttons={
|
||||
<Input
|
||||
width="200px"
|
||||
onInput={(_, value) => setSearchLoadout(value)}
|
||||
placeholder="Search for an item..."
|
||||
value={searchLoadout}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Tabs fluid align="center">
|
||||
{loadout_tabs.map((curTab) => (
|
||||
<Tabs.Tab
|
||||
key={curTab.name}
|
||||
selected={
|
||||
searchLoadout.length <= 1 && curTab.name === selectedTabName
|
||||
}
|
||||
onClick={() => {
|
||||
setSelectedTab(curTab.name);
|
||||
setSearchLoadout('');
|
||||
}}
|
||||
>
|
||||
<Box>
|
||||
{curTab.category_icon && (
|
||||
<Icon name={curTab.category_icon} mr={1} />
|
||||
)}
|
||||
{curTab.name}
|
||||
</Box>
|
||||
</Tabs.Tab>
|
||||
))}
|
||||
</Tabs>
|
||||
</Section>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<LoadoutTabs
|
||||
loadout_tabs={loadout_tabs}
|
||||
currentTab={selectedTabName}
|
||||
currentSearch={searchLoadout}
|
||||
modifyItemDimmer={modifyItemDimmer}
|
||||
setModifyItemDimmer={setModifyItemDimmer}
|
||||
/>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
const LoadoutTabs = (props: {
|
||||
loadout_tabs: LoadoutCategory[];
|
||||
currentTab: string;
|
||||
currentSearch: string;
|
||||
modifyItemDimmer: LoadoutItem | null;
|
||||
setModifyItemDimmer: (dimmer: LoadoutItem | null) => void;
|
||||
}) => {
|
||||
const {
|
||||
loadout_tabs,
|
||||
currentTab,
|
||||
currentSearch,
|
||||
modifyItemDimmer,
|
||||
setModifyItemDimmer,
|
||||
} = props;
|
||||
const activeCategory = loadout_tabs.find((curTab) => {
|
||||
return curTab.name === currentTab;
|
||||
});
|
||||
const searching = currentSearch.length > 1;
|
||||
|
||||
return (
|
||||
<Stack fill height="550px">
|
||||
<Stack.Item align="center" width="250px" height="100%">
|
||||
<Stack vertical fill>
|
||||
<Stack.Item height="60%">
|
||||
<LoadoutPreviewSection />
|
||||
</Stack.Item>
|
||||
<Stack.Item grow>
|
||||
<LoadoutSelectedSection
|
||||
all_tabs={loadout_tabs}
|
||||
modifyItemDimmer={modifyItemDimmer}
|
||||
setModifyItemDimmer={setModifyItemDimmer}
|
||||
/>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Stack.Item>
|
||||
<Stack.Item grow>
|
||||
{searching || activeCategory?.contents ? (
|
||||
<Section
|
||||
title={searching ? 'Searching...' : 'Catalog'}
|
||||
fill
|
||||
scrollable
|
||||
buttons={
|
||||
activeCategory?.category_info ? (
|
||||
<Box italic mt={0.5}>
|
||||
{activeCategory.category_info}
|
||||
</Box>
|
||||
) : null
|
||||
}
|
||||
>
|
||||
<Stack vertical>
|
||||
<Stack.Item>
|
||||
{searching ? (
|
||||
<SearchDisplay
|
||||
loadout_tabs={loadout_tabs}
|
||||
currentSearch={currentSearch}
|
||||
/>
|
||||
) : (
|
||||
<LoadoutTabDisplay category={activeCategory} />
|
||||
)}
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Section>
|
||||
) : (
|
||||
<Section fill>
|
||||
<Box>No contents for selected tab.</Box>
|
||||
</Section>
|
||||
)}
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
const typepathToLoadoutItem = (
|
||||
typepath: typePath,
|
||||
all_tabs: LoadoutCategory[],
|
||||
) => {
|
||||
// Maybe a bit inefficient, could be replaced with a hashmap?
|
||||
for (const tab of all_tabs) {
|
||||
for (const item of tab.contents) {
|
||||
if (item.path === typepath) {
|
||||
return item;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const LoadoutSelectedItem = (props: {
|
||||
path: typePath;
|
||||
all_tabs: LoadoutCategory[];
|
||||
modifyItemDimmer: LoadoutItem | null;
|
||||
setModifyItemDimmer: (dimmer: LoadoutItem | null) => void;
|
||||
}) => {
|
||||
const { all_tabs, path, modifyItemDimmer, setModifyItemDimmer } = props;
|
||||
const { act } = useBackend();
|
||||
|
||||
const item = typepathToLoadoutItem(path, all_tabs);
|
||||
if (!item) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack align={'center'}>
|
||||
<Stack.Item>
|
||||
<ItemIcon item={item} scale={1} />
|
||||
</Stack.Item>
|
||||
<Stack.Item width="55%">{item.name}</Stack.Item>
|
||||
{item.buttons.length ? (
|
||||
<Stack.Item>
|
||||
<Button
|
||||
color="none"
|
||||
width="32px"
|
||||
onClick={() => {
|
||||
setModifyItemDimmer(item);
|
||||
}}
|
||||
>
|
||||
<Icon size={1.8} name="cogs" color="grey" />
|
||||
</Button>
|
||||
</Stack.Item>
|
||||
) : (
|
||||
<Stack.Item width="32px" /> // empty space
|
||||
)}
|
||||
<Stack.Item>
|
||||
<Button
|
||||
color="none"
|
||||
width="32px"
|
||||
onClick={() => act('select_item', { path: path, deselect: true })}
|
||||
>
|
||||
<Icon size={2.4} name="times" color="red" />
|
||||
</Button>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
const LoadoutSelectedSection = (props: {
|
||||
all_tabs: LoadoutCategory[];
|
||||
modifyItemDimmer: LoadoutItem | null;
|
||||
setModifyItemDimmer: (dimmer: LoadoutItem | null) => void;
|
||||
}) => {
|
||||
const { act, data } = useBackend<LoadoutManagerData>();
|
||||
const { loadout_list } = data.character_preferences.misc;
|
||||
const { all_tabs, modifyItemDimmer, setModifyItemDimmer } = props;
|
||||
|
||||
return (
|
||||
<Section
|
||||
title=" "
|
||||
scrollable
|
||||
fill
|
||||
buttons={
|
||||
<Button.Confirm
|
||||
icon="times"
|
||||
color="red"
|
||||
align="center"
|
||||
disabled={!loadout_list || Object.keys(loadout_list).length === 0}
|
||||
tooltip="Clears ALL selected items from all categories."
|
||||
onClick={() => act('clear_all_items')}
|
||||
>
|
||||
Clear All
|
||||
</Button.Confirm>
|
||||
}
|
||||
>
|
||||
{loadout_list &&
|
||||
Object.entries(loadout_list).map(([path, item]) => (
|
||||
<Fragment key={path}>
|
||||
<LoadoutSelectedItem
|
||||
path={path}
|
||||
all_tabs={all_tabs}
|
||||
modifyItemDimmer={modifyItemDimmer}
|
||||
setModifyItemDimmer={setModifyItemDimmer}
|
||||
/>
|
||||
<Divider />
|
||||
</Fragment>
|
||||
))}
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
const LoadoutPreviewSection = () => {
|
||||
const { act, data } = useBackend<LoadoutManagerData>();
|
||||
|
||||
return (
|
||||
<Section
|
||||
fill
|
||||
title=" "
|
||||
buttons={
|
||||
<Button.Checkbox
|
||||
align="center"
|
||||
checked={data.job_clothes}
|
||||
onClick={() => act('toggle_job_clothes')}
|
||||
>
|
||||
Job Clothes
|
||||
</Button.Checkbox>
|
||||
}
|
||||
>
|
||||
<Stack vertical fill>
|
||||
<Stack.Item grow align="center">
|
||||
<CharacterPreview height="100%" id={data.character_preview_view} />
|
||||
</Stack.Item>
|
||||
<Stack.Divider />
|
||||
<Stack.Item align="center">
|
||||
<Stack>
|
||||
<Stack.Item>
|
||||
<Button
|
||||
icon="chevron-left"
|
||||
onClick={() =>
|
||||
act('rotate_dummy', {
|
||||
dir: 'left',
|
||||
})
|
||||
}
|
||||
/>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Button
|
||||
icon="chevron-right"
|
||||
onClick={() =>
|
||||
act('rotate_dummy', {
|
||||
dir: 'right',
|
||||
})
|
||||
}
|
||||
/>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
@@ -1,7 +0,0 @@
|
||||
import { FeatureChoiced } from '../base';
|
||||
import { FeatureDropdownInput } from '../dropdowns';
|
||||
|
||||
export const pride_pin: FeatureChoiced = {
|
||||
name: 'Pride Pin',
|
||||
component: FeatureDropdownInput,
|
||||
};
|
||||
Reference in New Issue
Block a user