diff --git a/code/__DEFINES/is_helpers.dm b/code/__DEFINES/is_helpers.dm index 3d08f8d70992..87462aa73691 100644 --- a/code/__DEFINES/is_helpers.dm +++ b/code/__DEFINES/is_helpers.dm @@ -11,6 +11,9 @@ #define isweakref(D) (istype(D, /datum/weakref)) +GLOBAL_VAR_INIT(magic_appearance_detecting_image, new /image) // appearances are awful to detect safely, but this seems to be the best way ~ninjanomnom +#define isappearance(thing) (!istype(thing, /image) && !ispath(thing) && istype(GLOB.magic_appearance_detecting_image, thing)) + #define isgenerator(A) (istype(A, /generator)) //Turfs diff --git a/code/modules/admin/view_variables/debug_variables.dm b/code/modules/admin/view_variables/debug_variables.dm index f92d5de53650..0cf2f84168e6 100644 --- a/code/modules/admin/view_variables/debug_variables.dm +++ b/code/modules/admin/view_variables/debug_variables.dm @@ -41,6 +41,10 @@ item = "[name_part] = /icon ([value])" #endif + else if(isappearance(value)) + var/image/actually_an_appearance = value + item = "[name_part] = /appearance ([actually_an_appearance.icon])" + else if (isfile(value)) item = "[name_part] = '[value]'" diff --git a/code/modules/client/preferences.dm b/code/modules/client/preferences.dm index 161931bb7b09..44d6e823132b 100644 --- a/code/modules/client/preferences.dm +++ b/code/modules/client/preferences.dm @@ -376,7 +376,7 @@ GLOBAL_LIST_EMPTY(preferences_datums) body = new // Without this, it doesn't show up in the menu - body.appearance_flags &= ~KEEP_TOGETHER + // body.appearance_flags &= ~KEEP_TOGETHER // NON-MODULE CHANGE /datum/preferences/proc/create_character_profiles() var/list/profiles = list() diff --git a/code/modules/client/preferences_savefile.dm b/code/modules/client/preferences_savefile.dm index eb0b923554e5..b534566a5380 100644 --- a/code/modules/client/preferences_savefile.dm +++ b/code/modules/client/preferences_savefile.dm @@ -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 43 +#define SAVEFILE_VERSION_MAX 43.1 /* SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Carn diff --git a/maplestation_modules/code/__DEFINES/_module_defines.dm b/maplestation_modules/code/__DEFINES/_module_defines.dm index c8dd1faf9724..7154e4b2982d 100644 --- a/maplestation_modules/code/__DEFINES/_module_defines.dm +++ b/maplestation_modules/code/__DEFINES/_module_defines.dm @@ -37,3 +37,6 @@ #define SOUND_NORMAL (1<<0) #define SOUND_QUESTION (1<<1) #define SOUND_EXCLAMATION (1<<2) + +/// Max loadout presets available +#define MAX_LOADOUTS 5 diff --git a/maplestation_modules/code/modules/client/preferences/height.dm b/maplestation_modules/code/modules/client/preferences/height.dm index 9331a75cca01..5a67a7cde9d3 100644 --- a/maplestation_modules/code/modules/client/preferences/height.dm +++ b/maplestation_modules/code/modules/client/preferences/height.dm @@ -37,7 +37,7 @@ return // Snowflake, but otherwise the dummy in the prefs menu will be resized and you can't see anything - if(istype(target, /mob/living/carbon/human/dummy)) + if(isdummy(target)) return // Just in case if(!ishuman(target)) @@ -143,6 +143,19 @@ #undef SIZE_PREF_PRIORITY #undef HEIGHT_PREF_PRIORITY +// To speed up the preference menu, we apply 1 filter to the entire mob +/mob/living/carbon/human/dummy/regenerate_icons() + . = ..() + apply_height_filters(src, TRUE) + +/mob/living/carbon/human/dummy/apply_height_filters(mutable_appearance/appearance, only_apply_in_prefs = FALSE) + if(only_apply_in_prefs) + return ..() + +// Not necessary with above +/mob/living/carbon/human/dummy/apply_height_offsets(mutable_appearance/appearance, upper_torso) + return + /mob/living/carbon/human/get_mob_height() // If you have roundstart dwarfism (IE: resized), it'll just return normal mob height, so no filters are applied if(HAS_TRAIT_FROM_ONLY(src, TRAIT_DWARF, ROUNDSTART_TRAIT)) diff --git a/maplestation_modules/code/modules/client/preferences/loadout_preference.dm b/maplestation_modules/code/modules/client/preferences/loadout_preference.dm index 65597c07bf55..7b6f5d2aa6d6 100644 --- a/maplestation_modules/code/modules/client/preferences/loadout_preference.dm +++ b/maplestation_modules/code/modules/client/preferences/loadout_preference.dm @@ -1,3 +1,16 @@ +/datum/preference/numeric/active_loadout + savefile_key = "active_loadout" + savefile_identifier = PREFERENCE_CHARACTER + can_randomize = FALSE + minimum = 1 + maximum = MAX_LOADOUTS + +/datum/preference/numeric/active_loadout/create_default_value() + return minimum + +/datum/preference/numeric/active_loadout/apply_to_human(mob/living/carbon/human/target, value) + return + /datum/preference/loadout savefile_key = "loadout_list" savefile_identifier = PREFERENCE_CHARACTER @@ -15,21 +28,56 @@ if(!istype(target)) return // Not a crash, 'cause this proc could be passed non-humans (AIs, etc) and that's fine - for(var/datum/loadout_item/item as anything in loadout_list_to_datums(value)) + var/slot = prefs.read_preference(/datum/preference/numeric/active_loadout) + for(var/datum/loadout_item/item as anything in loadout_list_to_datums(value[slot])) item.post_equip_item(prefs, target) -/datum/preference/loadout/serialize(input, datum/preferences/preferences) - // Sanitize on save even though it's highly unlikely this will need it - return sanitize_loadout_list(input) - /datum/preference/loadout/deserialize(input, datum/preferences/preferences) // Sanitize on load to ensure no invalid paths from older saves get in - // Pass in the prefernce owner so they can get feedback messages on stuff that failed to load (if they exist) - return sanitize_loadout_list(input, preferences.parent?.mob) + var/slot = preferences.read_preference(/datum/preference/numeric/active_loadout) -// Default value is NULL - the loadout list is a lazylist + for(var/i in 1 to length(input)) + if(islist(input[i])) + // Pass in the prefernce owner so they can get feedback messages on stuff that failed to load (if they exist) + input[i] = sanitize_loadout_list(input[i], preferences.parent?.mob, slot) + + return input + +// 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, loadout_slot) + 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)) + to_chat(optional_loadout_owner, span_boldnotice("The following invalid item path was found in loadout slot [loadout_slot]: [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)) + to_chat(optional_loadout_owner, span_boldnotice("The following invalid loadout item was found in loadout slot [loadout_slot]: [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 + +/datum/preferences/update_character(current_version, list/save_data) + . = ..() + if(current_version < 43.1) + save_loadout(src, save_data?["loadout_list"]) diff --git a/maplestation_modules/code/modules/loadouts/loadout_items/_loadout_datum.dm b/maplestation_modules/code/modules/loadouts/loadout_items/_loadout_datum.dm index 7a57b5d6b155..dfd97cf3a287 100644 --- a/maplestation_modules/code/modules/loadouts/loadout_items/_loadout_datum.dm +++ b/maplestation_modules/code/modules/loadouts/loadout_items/_loadout_datum.dm @@ -93,7 +93,7 @@ GLOBAL_LIST_EMPTY(all_loadout_datums) to_chat(user, span_warning("You already have a greyscaling window open!")) return - var/list/loadout = manager.preferences.read_preference(/datum/preference/loadout) + var/list/loadout = get_active_loadout(manager.preferences) var/list/allowed_configs = list() if(initial(item_path.greyscale_config)) allowed_configs += "[initial(item_path.greyscale_config)]" @@ -122,7 +122,7 @@ GLOBAL_LIST_EMPTY(all_loadout_datums) if(!istype(open_menu)) CRASH("set_slot_greyscale called without a greyscale menu!") - var/list/loadout = manager.preferences.read_preference(/datum/preference/loadout) + var/list/loadout = get_active_loadout(manager.preferences) if(!loadout?[item_path]) manager.select_item(src) @@ -131,11 +131,11 @@ GLOBAL_LIST_EMPTY(all_loadout_datums) return loadout[item_path][INFO_GREYSCALE] = colors.Join("") - manager.preferences.update_preference(GLOB.preference_entries[/datum/preference/loadout], loadout) + update_loadout(manager.preferences, loadout) manager.character_preview_view.update_body() /datum/loadout_item/proc/set_name(datum/preference_middleware/loadout/manager, mob/user) - var/list/loadout = manager.preferences.read_preference(/datum/preference/loadout) + var/list/loadout = get_active_loadout(manager.preferences) var/input_name = tgui_input_text( user = user, message = "What name do you want to give [name]? Leave blank to clear.", @@ -154,10 +154,10 @@ GLOBAL_LIST_EMPTY(all_loadout_datums) else loadout[item_path] -= INFO_NAMED - manager.preferences.update_preference(GLOB.preference_entries[/datum/preference/loadout], loadout) + update_loadout(manager.preferences, loadout) /datum/loadout_item/proc/set_skin(datum/preference_middleware/loadout/manager, mob/user) - var/list/loadout = manager.preferences.read_preference(/datum/preference/loadout) + var/list/loadout = get_active_loadout(manager.preferences) var/static/list/list/cached_reskins = list() if(!islist(cached_reskins[item_path])) var/obj/item/item_template = new item_path() @@ -185,7 +185,7 @@ GLOBAL_LIST_EMPTY(all_loadout_datums) else loadout[item_path][INFO_RESKIN] = input_skin - manager.preferences.update_preference(GLOB.preference_entries[/datum/preference/loadout], loadout) + update_loadout(manager.preferences, loadout) /** * Place our [var/item_path] into [outfit]. @@ -232,7 +232,7 @@ GLOBAL_LIST_EMPTY(all_loadout_datums) else // Not valid item_details -= INFO_RESKIN - preference_source.write_preference(GLOB.preference_entries[/datum/preference/loadout], preference_list) + save_loadout(preference_source, preference_list) return equipped_item diff --git a/maplestation_modules/code/modules/loadouts/loadout_items/loadout_datum_accessory.dm b/maplestation_modules/code/modules/loadouts/loadout_items/loadout_datum_accessory.dm index 7d233e4248ca..9e96a7fe89c7 100644 --- a/maplestation_modules/code/modules/loadouts/loadout_items/loadout_datum_accessory.dm +++ b/maplestation_modules/code/modules/loadouts/loadout_items/loadout_datum_accessory.dm @@ -35,7 +35,7 @@ return ..() /datum/loadout_item/accessory/proc/set_accessory_layer(datum/preference_middleware/loadout/manager, mob/user) - var/list/loadout = manager.preferences.read_preference(/datum/preference/loadout) + var/list/loadout = get_active_loadout(manager.preferences) if(!loadout?[item_path]) manager.select_item(src) @@ -44,7 +44,7 @@ loadout[item_path][INFO_LAYER] = !loadout[item_path][INFO_LAYER] to_chat(user, span_boldnotice("[name] will now appear [loadout[item_path][INFO_LAYER] ? "above" : "below"] suits.")) - manager.preferences.update_preference(GLOB.preference_entries[/datum/preference/loadout], loadout) + update_loadout(manager.preferences, loadout) /datum/loadout_item/accessory/insert_path_into_outfit(datum/outfit/outfit, mob/living/carbon/human/equipper, visuals_only = FALSE) if(outfit.accessory) diff --git a/maplestation_modules/code/modules/loadouts/loadout_ui/loadout_manager.dm b/maplestation_modules/code/modules/loadouts/loadout_ui/loadout_manager.dm index ecc9bf6635cd..af5b18a7cb8d 100644 --- a/maplestation_modules/code/modules/loadouts/loadout_ui/loadout_manager.dm +++ b/maplestation_modules/code/modules/loadouts/loadout_ui/loadout_manager.dm @@ -73,6 +73,7 @@ GLOBAL_LIST_INIT(loadout_categories, init_loadout_categories()) "toggle_job_clothes" = PROC_REF(action_toggle_job_outfit), "rotate_dummy" = PROC_REF(action_rotate_model_dir), "pass_to_loadout_item" = PROC_REF(action_pass_to_loadout_item), + "select_slot" = PROC_REF(select_slot), ) /// The preview dummy. @@ -112,7 +113,7 @@ GLOBAL_LIST_INIT(loadout_categories, init_loadout_categories()) return TRUE /datum/preference_middleware/loadout/proc/action_clear_all(list/params, mob/user) - preferences.update_preference(GLOB.preference_entries[/datum/preference/loadout], null) + update_loadout(preferences, null) character_preview_view.update_body() return TRUE @@ -143,7 +144,7 @@ GLOBAL_LIST_INIT(loadout_categories, init_loadout_categories()) /// 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/loadout = get_active_loadout(preferences) 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) @@ -153,13 +154,13 @@ GLOBAL_LIST_INIT(loadout_categories, init_loadout_categories()) return LAZYSET(loadout, selected_item.item_path, list()) - preferences.update_preference(GLOB.preference_entries[/datum/preference/loadout], loadout) + update_loadout(preferences, 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) + var/list/loadout = get_active_loadout(preferences) LAZYREMOVE(loadout, deselected_item.item_path) - preferences.update_preference(GLOB.preference_entries[/datum/preference/loadout], loadout) + update_loadout(preferences, loadout) /datum/preference_middleware/loadout/proc/register_greyscale_menu(datum/greyscale_modify_menu/open_menu) src.menu = open_menu @@ -169,6 +170,12 @@ GLOBAL_LIST_INIT(loadout_categories, init_loadout_categories()) SIGNAL_HANDLER menu = null +/datum/preference_middleware/loadout/proc/select_slot(list/params, mob/user) + preferences.write_preference(GLOB.preference_entries[/datum/preference/numeric/active_loadout], text2num(params["new_slot"])) + character_preview_view.update_body() + preferences.character_preview_view?.update_body() + preferences.update_static_data(user) + /datum/preference_middleware/loadout/get_ui_data(mob/user) var/list/data = list() @@ -176,12 +183,13 @@ GLOBAL_LIST_INIT(loadout_categories, init_loadout_categories()) character_preview_view = create_character_preview_view(user) var/list/all_selected_paths = list() - for(var/path in preferences.read_preference(/datum/preference/loadout)) + for(var/path in get_active_loadout(preferences)) all_selected_paths += path data["selected_loadout"] = all_selected_paths data["mob_name"] = preferences.read_preference(/datum/preference/name/real_name) data["job_clothes"] = character_preview_view.view_job_clothes + data["current_slot"] = preferences.read_preference(/datum/preference/numeric/active_loadout) return data @@ -202,6 +210,7 @@ GLOBAL_LIST_INIT(loadout_categories, init_loadout_categories()) data["loadout_tabs"] = loadout_tabs data["tutorial_text"] = get_tutorial_text() + data["max_loadout_slots"] = MAX_LOADOUTS return data /// Returns a formatted string for use in the UI. diff --git a/maplestation_modules/code/modules/loadouts/loadout_ui/loadout_outfit_helpers.dm b/maplestation_modules/code/modules/loadouts/loadout_ui/loadout_outfit_helpers.dm index eeee16902338..17c1b2e1d654 100644 --- a/maplestation_modules/code/modules/loadouts/loadout_ui/loadout_outfit_helpers.dm +++ b/maplestation_modules/code/modules/loadouts/loadout_ui/loadout_outfit_helpers.dm @@ -18,6 +18,9 @@ * preference_source - the preferences of the thing we're equipping */ /mob/living/carbon/human/proc/equip_outfit_and_loadout(datum/outfit/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)) @@ -27,13 +30,14 @@ else CRASH("Outfit passed to equip_outfit_and_loadout was neither a path nor an instantiated type!") - var/list/preference_list = preference_source?.read_preference(/datum/preference/loadout) + var/list/preference_list = get_active_loadout(preference_source) var/list/loadout_datums = loadout_list_to_datums(preference_list) // Place any loadout items into the outfit before going forward 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 - equipOutfit(equipped_outfit, visuals_only) + if(!equipped_outfit.equip(src, visuals_only)) + return FALSE // Handle any snowflake on_equips for(var/datum/loadout_item/item as anything in loadout_datums) item.on_equip_item(preference_source, src, visuals_only, preference_list) @@ -66,41 +70,42 @@ return datums /** - * Removes all invalid paths from loadout lists. - * This is a general sanitization for preference saving / loading. - * - * passed_list - the loadout list we're sanitizing. + * Gets the active loadout of the passed preference source. * - * returns a list, or null if empty + * Returns a loadout lazylist */ -/proc/sanitize_loadout_list(list/passed_list, mob/optional_loadout_owner) - var/list/sanitized_list - for(var/path in passed_list) - // Saving to json has each path in the list as a typepath that will be converted to string - // 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)) - #ifdef TESTING - // These stack traces are only useful in testing to find out why items aren't being saved when they should be - // In a production setting it should be OKAY for the sanitize proc to pick out invalid paths - stack_trace("invalid path found in loadout list! (Path: [path])") - #endif - to_chat(optional_loadout_owner, span_boldnotice("The following invalid item path was found in your loadout: [real_path || "null"]. \ - It has been removed, renamed, or is otherwise missing - You may want to check your loadout settings.")) - continue +/proc/get_active_loadout(datum/preferences/preferences) + RETURN_TYPE(/list) + var/slot = preferences.read_preference(/datum/preference/numeric/active_loadout) + var/list/all_loadouts = preferences.read_preference(/datum/preference/loadout) + if(slot > length(all_loadouts)) + return null + return all_loadouts[slot] - else if(!istype(GLOB.all_loadout_datums[real_path], /datum/loadout_item)) - #ifdef TESTING - // Same as above, stack trace only useful in testing to find out why items aren't being saved when they should be - stack_trace("invalid loadout item found in loadout list! Path: [path]") - #endif - to_chat(optional_loadout_owner, span_boldnotice("The following invalid loadout item was found in your loadout: [real_path || "null"]. \ - It has been removed, renamed, or is otherwise missing - You may want to check your loadout settings.")) - continue +/** + * Calls update_preference on the passed preference datum with the passed loadout list + */ +/proc/update_loadout(datum/preferences/preferences, list/loadout_list) + preferences.update_preference(GLOB.preference_entries[/datum/preference/loadout], get_updated_loadout_list(preferences, loadout_list)) - // Grab data using real path key - var/list/data = passed_list[path] - // Set into sanitize list using converted path key - LAZYSET(sanitized_list, real_path, LAZYCOPY(data)) +/** + * Calls write_preference on the passed preference datum with the passed loadout list + */ +/proc/save_loadout(datum/preferences/preferences, list/loadout_list) + preferences.write_preference(GLOB.preference_entries[/datum/preference/loadout], get_updated_loadout_list(preferences, loadout_list)) + +/** + * Returns a list of all loadouts belonging to the passed preference source, + * and appends the passed loadout list to the proper index of the list. + */ +/proc/get_updated_loadout_list(datum/preferences/preferences, list/loadout_list) + RETURN_TYPE(/list) + var/slot = preferences.read_preference(/datum/preference/numeric/active_loadout) + var/list/new_list = list() + for(var/list/loadout in preferences.read_preference(/datum/preference/loadout)) + UNTYPED_LIST_ADD(new_list, loadout) + while(length(new_list) < slot) + new_list += null - return sanitized_list + new_list[slot] = loadout_list + return new_list diff --git a/tgui/packages/tgui/interfaces/_LoadoutManager.tsx b/tgui/packages/tgui/interfaces/_LoadoutManager.tsx index d95775c825c5..93c3ce83e4d6 100644 --- a/tgui/packages/tgui/interfaces/_LoadoutManager.tsx +++ b/tgui/packages/tgui/interfaces/_LoadoutManager.tsx @@ -30,6 +30,8 @@ type Data = { loadout_preview_view: string; loadout_tabs: LoadoutCategory[]; tutorial_text: string; + current_slot: number; + max_loadout_slots: number; }; export const LoadoutPage = (props, context) => { @@ -297,8 +299,24 @@ const LoadoutTabs = (props, context) => { const LoadoutPreviewSection = (props, context) => { const { act, data } = useBackend(context); - const { mob_name, job_clothes, loadout_preview_view } = data; + const { + mob_name, + job_clothes, + loadout_preview_view, + current_slot, + max_loadout_slots, + } = data; + const [tutorialStatus] = useLocalState(context, 'tutorialStatus', false); + + const loadoutSlots = (maxSlots: number) => { + const slots: number[] = []; + for (let i = 1; i < maxSlots + 1; i++) { + slots.push(i); + } + return slots; + }; + return (
{ )} + + + {loadoutSlots(max_loadout_slots).map((slot) => ( + +