From 7bde5d5dfbf3a4737b64e55738b426a8ea87ce9c Mon Sep 17 00:00:00 2001 From: macha Date: Sun, 3 Nov 2024 04:39:31 +0800 Subject: [PATCH 01/16] the tguiening --- .../mob/living/carbon/carbon_update_icons.dm | 6 + .../augmentsplus/_defines.dm | 1 + .../augmentsplus/_limb_datums.dm | 137 +++++++ .../augmentsplus/_markings.dm | 11 + .../augmentsplus/augments_arms.dm | 72 ++++ .../augmentsplus/body_markings/other.dm | 10 + .../icons/markings/other_markings.dmi | Bin 0 -> 14464 bytes .../augmentsplus/limb_preferences.dm | 53 +++ .../augmentsplus/limbs.dm | 138 +++++++ .../augmentsplus/markings_preferences | 39 ++ .../bodypart/bodypart_overrides.dm | 4 + tgstation.dme | 8 + tgui/packages/tgui/assets/bg-doppie.svg | 9 + .../CharacterPreferenceWindow.tsx | 31 +- .../interfaces/PreferencesMenu/MainPage.tsx | 81 +++- .../tgui/interfaces/PreferencesMenu/data.ts | 1 + .../dopplershift_preferences/mutant_limbs.tsx | 8 + .../features/game_preferences/_limbs.tsx | 17 + .../packages/tgui/interfaces/_LimbManager.tsx | 358 ++++++++++++++++++ tgui/packages/tgui/styles/base.scss | 4 +- tgui/packages/tgui/styles/colors.scss | 37 +- tgui/packages/tgui/styles/main.scss | 2 +- 22 files changed, 988 insertions(+), 39 deletions(-) create mode 100644 modular_doppler/modular_customization/augmentsplus/_defines.dm create mode 100644 modular_doppler/modular_customization/augmentsplus/_limb_datums.dm create mode 100644 modular_doppler/modular_customization/augmentsplus/_markings.dm create mode 100644 modular_doppler/modular_customization/augmentsplus/augments_arms.dm create mode 100644 modular_doppler/modular_customization/augmentsplus/body_markings/other.dm create mode 100644 modular_doppler/modular_customization/augmentsplus/icons/markings/other_markings.dmi create mode 100644 modular_doppler/modular_customization/augmentsplus/limb_preferences.dm create mode 100644 modular_doppler/modular_customization/augmentsplus/limbs.dm create mode 100644 modular_doppler/modular_customization/augmentsplus/markings_preferences create mode 100644 tgui/packages/tgui/assets/bg-doppie.svg create mode 100644 tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/game_preferences/_limbs.tsx create mode 100644 tgui/packages/tgui/interfaces/_LimbManager.tsx diff --git a/code/modules/mob/living/carbon/carbon_update_icons.dm b/code/modules/mob/living/carbon/carbon_update_icons.dm index 1049a0bbf9aa4..49e7a747bae52 100644 --- a/code/modules/mob/living/carbon/carbon_update_icons.dm +++ b/code/modules/mob/living/carbon/carbon_update_icons.dm @@ -533,7 +533,13 @@ for(var/datum/bodypart_overlay/overlay as anything in bodypart_overlays) if(!overlay.can_draw_on_bodypart(owner)) continue + var/list/prior = . + var/priorjoined = prior.Join() + testing("return is [priorjoined]") . += "-[jointext(overlay.generate_icon_cache(), "-")]" + var/list/after = . + var/afterjoined = after.Join() + testing("modified return is [afterjoined]") if(ishuman(owner)) var/mob/living/carbon/human/human_owner = owner . += "-[human_owner.get_mob_height()]" diff --git a/modular_doppler/modular_customization/augmentsplus/_defines.dm b/modular_doppler/modular_customization/augmentsplus/_defines.dm new file mode 100644 index 0000000000000..8802f054d5d25 --- /dev/null +++ b/modular_doppler/modular_customization/augmentsplus/_defines.dm @@ -0,0 +1 @@ +#define PREFERENCE_CATEGORY_MARKINGS "markings" diff --git a/modular_doppler/modular_customization/augmentsplus/_limb_datums.dm b/modular_doppler/modular_customization/augmentsplus/_limb_datums.dm new file mode 100644 index 0000000000000..ce55986c148a9 --- /dev/null +++ b/modular_doppler/modular_customization/augmentsplus/_limb_datums.dm @@ -0,0 +1,137 @@ +/// An assoc list of [limb typepath] to [singleton limb datum]s used in the limb manager +GLOBAL_LIST_INIT(limb_loadout_options, init_loadout_limb_options()) + +/// Inits the limb manager global list +/proc/init_loadout_limb_options() + var/list/created = list() + for(var/datum/limb_option_datum/to_create as anything in typesof(/datum/limb_option_datum)) + var/obj/item/limb_path = initial(to_create.limb_path) + if(isnull(limb_path)) + continue + + created[limb_path] = new to_create() + + return created + +/** + * Used as holders for paths to be used in the limb editor menu + * + * Similar to loadout datums but, for limbs and organs that one can start roundstart with + * + * I could've just tied this into loadout datums (they're pretty much the same thing) + * but I would rather keep the typepaths separate for ease of use + */ +/datum/limb_option_datum + /// Name shown up in UI + var/name + /// Used in UI tooltips + var/desc + /// The actual item that is created and equipped to the player + var/obj/item/limb_path + /// Determines what body zone this is slotted into in the UI + /// Uses the following limb body zones: + /// [BODY_ZONE_HEAD], [BODY_ZONE_CHEST], [BODY_ZONE_R_ARM], [BODY_ZONE_L_ARM], [BODY_ZONE_R_LEG], [BODY_ZONE_L_LEG] + var/ui_zone + /// Determines what key the path of this is slotted into in the assoc list of preferences + /// A bodypart might use their body zone while an organ may use their organ slot + /// This essently determines what other datums this datum is incompatible with + var/pref_list_slot + +/datum/limb_option_datum/New() + . = ..() + if(isnull(name)) + name = capitalize(initial(limb_path.name)) + if(isnull(desc)) + desc = initial(limb_path.desc) + +/// Applies the datum to the mob. +/datum/limb_option_datum/proc/apply_limb(mob/living/carbon/human/apply_to) + return + +/datum/limb_option_datum/bodypart + +/datum/limb_option_datum/bodypart/New() + . = ..() + var/obj/item/bodypart/part_path = limb_path + if(isnull(ui_zone)) + ui_zone = initial(part_path.body_zone) + if(isnull(pref_list_slot)) + pref_list_slot = initial(part_path.body_zone) + +/datum/limb_option_datum/bodypart/apply_limb(mob/living/carbon/human/apply_to) + apply_to.del_and_replace_bodypart(new limb_path(), special = TRUE) + +/datum/limb_option_datum/bodypart/prosthetic_r_leg + name = "Prosthetic Right Leg" + limb_path = /obj/item/bodypart/leg/right/robot/surplus + +/datum/limb_option_datum/bodypart/prosthetic_l_leg + name = "Prosthetic Left Leg" + limb_path = /obj/item/bodypart/leg/left/robot/surplus + +/datum/limb_option_datum/bodypart/prosthetic_r_arm + name = "Prosthetic Right Arm" + limb_path = /obj/item/bodypart/arm/right/robot/surplus + +/datum/limb_option_datum/bodypart/prosthetic_l_arm + name = "Prosthetic Left Arm" + limb_path = /obj/item/bodypart/arm/left/robot/surplus + +/datum/limb_option_datum/organ + +/datum/limb_option_datum/organ/New() + . = ..() + var/obj/item/organ/organ_path = limb_path + if(isnull(ui_zone)) + ui_zone = deprecise_zone(initial(organ_path.zone)) + if(isnull(pref_list_slot)) + pref_list_slot = initial(organ_path.slot) + +/datum/limb_option_datum/organ/apply_limb(mob/living/carbon/human/apply_to) + if(istype(apply_to, /mob/living/carbon/human/dummy)) // thog don't caare + return + + var/obj/item/organ/internal/new_organ = new limb_path() + new_organ.Insert(apply_to, special = TRUE, drop_if_replaced = FALSE) + +/datum/limb_option_datum/organ/cyberheart + name = "Cybernetic Heart" + limb_path = /obj/item/organ/internal/heart/cybernetic + +/datum/limb_option_datum/organ/cyberliver + name = "Cybernetic Liver" + limb_path = /obj/item/organ/internal/liver/cybernetic + +/datum/limb_option_datum/organ/cyberlungs + name = "Cybernetic Lungs" + limb_path = /obj/item/organ/internal/lungs/cybernetic + +/datum/limb_option_datum/organ/cyberstomach + name = "Cybernetic Stomach" + limb_path = /obj/item/organ/internal/stomach/cybernetic + +/datum/limb_option_datum/organ/eyes + name = "Cybernetic Eyes" + limb_path = /obj/item/organ/internal/eyes/robotic/basic + +/datum/limb_option_datum/organ/ears + name = "Cybernetic Ears" + limb_path = /obj/item/organ/internal/ears/cybernetic + +/datum/limb_option_datum/organ/robotongue + name = "Voicebox" + desc = "A voice synthesizer that is designed to replace a tongue. Makes you sound like a robot." + limb_path = /obj/item/organ/internal/tongue/robot + +/datum/limb_option_datum/organ/lighter_implant + name = "Lighter Implant" + desc = "A lighter that is implanted into the tip of your finger. Light it with a snap... like a badass." + limb_path = /obj/item/organ/internal/cyberimp/arm/lighter + ui_zone = BODY_ZONE_R_ARM + pref_list_slot = ORGAN_SLOT_RIGHT_ARM_AUG + +/datum/limb_option_datum/organ/lighter_implant/left + limb_path = /obj/item/organ/internal/cyberimp/arm/lighter/left + ui_zone = BODY_ZONE_L_ARM + pref_list_slot = ORGAN_SLOT_LEFT_ARM_AUG + // Yeah you can have one in both arms if you want, don't really care diff --git a/modular_doppler/modular_customization/augmentsplus/_markings.dm b/modular_doppler/modular_customization/augmentsplus/_markings.dm new file mode 100644 index 0000000000000..44fed075302b1 --- /dev/null +++ b/modular_doppler/modular_customization/augmentsplus/_markings.dm @@ -0,0 +1,11 @@ +#define DEFAULT_SPRITE_LIST "default_sprites" + +/datum/controller/subsystem/accessories + var/list/body_markings + +/datum/controller/subsystem/accessories/setup_lists() + . = ..() + + body_markings = init_sprite_accessory_subtypes(/datum/sprite_accessory/body_marking)[DEFAULT_SPRITE_LIST] + +#undef DEFAULT_SPRITE_LIST diff --git a/modular_doppler/modular_customization/augmentsplus/augments_arms.dm b/modular_doppler/modular_customization/augmentsplus/augments_arms.dm new file mode 100644 index 0000000000000..b13de527706bd --- /dev/null +++ b/modular_doppler/modular_customization/augmentsplus/augments_arms.dm @@ -0,0 +1,72 @@ +/obj/item/organ/internal/cyberimp/arm/lighter + name = "finger-bound lighter" + desc = "Allows you to light cigarettes with the snap of a finger." + items_to_create = list(/obj/item/lighter/implanted) + +/obj/item/organ/internal/cyberimp/arm/lighter/emp_act(severity) + . = ..() + if((. & EMP_PROTECT_SELF)) + return + var/obj/item/bodypart/real_hand = hand + if(!istype(real_hand)) + return + real_hand.receive_damage(burn = 10 / severity, wound_bonus = 10, damage_source = src) + to_chat(owner, span_warning("You feel your [parse_zone(zone)] begin to burn up!")) + + +/obj/item/organ/internal/cyberimp/arm/lighter/Extend(obj/item/augment) + . = ..() + var/obj/item/lighter/implanted/lighter = augment + if(!istype(augment) || augment.loc == src) + return + lighter.set_lit(TRUE) + +/obj/item/organ/internal/cyberimp/arm/lighter/Retract() + var/obj/item/lighter/implanted/lighter = active_item + if(istype(lighter)) + lighter.set_lit(FALSE) + return ..() + +/obj/item/organ/internal/cyberimp/arm/lighter/proc/on_snap(mob/living/source) + SIGNAL_HANDLER + if(source.get_active_hand() != hand) + return + if(organ_flags & ORGAN_FAILING) + return + if(isnull(active_item)) + Extend(contents[1]) + source.visible_message( + span_infoplain(span_rose("With a snap, [source]'s finger emits a low flame.")), + span_infoplain(span_rose("With a snap, your finger begins to emit a low flame.")), + ) + + else + Retract() + source.visible_message( + span_infoplain(span_rose("With a snap, [source]'s finger extinguishes.")), + span_infoplain(span_rose("With a snap, your finger is extinguished.")), + ) + +/obj/item/organ/internal/cyberimp/arm/lighter/left + zone = BODY_ZONE_L_ARM + +/obj/item/lighter/implanted + name = "implanted lighter" + desc = "A lighter implanted in your finger." + item_flags = EXAMINE_SKIP + +/obj/item/lighter/implanted/ignition_effect(atom/A, mob/user) + if(get_temperature()) + return span_infoplain(span_rose( + "With a snap, [user]'s finger emits a low flame, which they use to light [A] ablaze. \ + Hot damn, [user.p_theyre()] badass.")) + +/obj/item/lighter/implanted/attack_self(mob/living/user) + return + +/obj/item/lighter/implanted/set_lit(new_lit) + . = ..() + if(lit) + name = "\proper [loc]'s finger-light" + else + name = initial(name) diff --git a/modular_doppler/modular_customization/augmentsplus/body_markings/other.dm b/modular_doppler/modular_customization/augmentsplus/body_markings/other.dm new file mode 100644 index 0000000000000..4ee710ba293dc --- /dev/null +++ b/modular_doppler/modular_customization/augmentsplus/body_markings/other.dm @@ -0,0 +1,10 @@ +/datum/sprite_accessory/body_marking/none + name = SPRITE_ACCESSORY_NONE + icon_state = SPRITE_ACCESSORY_NONE + +/datum/sprite_accessory/body_marking/pilot_jaw + icon = 'modular_doppler/modular_customization/augmentsplus/icons/markings/other_markings.dmi' + name = "Pilot Jaw" + icon_state = "pilot_jaw" + gender_specific = TRUE + color_src = "#CCCCCC" diff --git a/modular_doppler/modular_customization/augmentsplus/icons/markings/other_markings.dmi b/modular_doppler/modular_customization/augmentsplus/icons/markings/other_markings.dmi new file mode 100644 index 0000000000000000000000000000000000000000..43f96101f05cf0a5ab18c45a4935494b045fc585 GIT binary patch literal 14464 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 literal 0 HcmV?d00001 diff --git a/modular_doppler/modular_customization/augmentsplus/limb_preferences.dm b/modular_doppler/modular_customization/augmentsplus/limb_preferences.dm new file mode 100644 index 0000000000000..1f03b0af6c4ef --- /dev/null +++ b/modular_doppler/modular_customization/augmentsplus/limb_preferences.dm @@ -0,0 +1,53 @@ +/datum/preference/limbs + savefile_key = "limb_list" + savefile_identifier = PREFERENCE_CHARACTER + can_randomize = FALSE + +/datum/preference/limbs/apply_to_human(mob/living/carbon/human/target, value) + var/list/in_order_datums = list( + // Apply bodyparts first, as organs / implants are housed in bodyparts - to prevent accidental overriding + "Bodyparts" = list(), + // Then apply organs into new bodyparts + "Organs" = list(), + // Then whatever is left + "Other" = list(), + ) + + for(var/limb_zone in value) + var/obj/item/limb_path = value[limb_zone] + var/datum/limb_option_datum/equipping = GLOB.limb_loadout_options[limb_path] + if(isnull(equipping)) + stack_trace("Invalid limb path in limb loadout preference: [limb_path]") + continue + + if(ispath(limb_path, /obj/item/bodypart)) + in_order_datums["Bodyparts"] += equipping + else if(ispath(limb_path, /obj/item/organ)) + in_order_datums["Organs"] += equipping + else + in_order_datums["Other"] += equipping + + for(var/to_apply_key in in_order_datums) + for(var/datum/limb_option_datum/equipping_datum as anything in in_order_datums[to_apply_key]) + equipping_datum.apply_limb(target) + +/datum/preference/limbs/deserialize(input, datum/preferences/preferences) + var/list/corrected_list = list() + for(var/limb_zone in input) + var/obj/item/limb_path_as_text = input[limb_zone] + if(istext(limb_path_as_text)) + // Loading from json loads as text rather than paths we love + limb_path_as_text = text2path(limb_path_as_text) + + if(isnull(GLOB.limb_loadout_options[limb_path_as_text])) + continue + + corrected_list[limb_zone] = limb_path_as_text + + return corrected_list + +/datum/preference/limbs/create_default_value(datum/preferences/preferences) + return null + +/datum/preference/limbs/is_valid(value) + return isnull(value) || islist(value) diff --git a/modular_doppler/modular_customization/augmentsplus/limbs.dm b/modular_doppler/modular_customization/augmentsplus/limbs.dm new file mode 100644 index 0000000000000..7d7d0f731285c --- /dev/null +++ b/modular_doppler/modular_customization/augmentsplus/limbs.dm @@ -0,0 +1,138 @@ +/mob/living/carbon/human/dummy/wipe_state() + . = ..() + // Gets rid of prosthetics and stuff that may have been added + for(var/obj/item/bodypart/whatever as anything in bodyparts) + whatever.change_exempt_flags &= ~BP_BLOCK_CHANGE_SPECIES + dna?.species?.replace_body(src) + +/atom/movable/screen/map_view/char_preview/limb_viewer + +/atom/movable/screen/map_view/char_preview/limb_viewer/update_body() + if (isnull(body)) + create_body() + else + body.wipe_state() + + preferences.apply_prefs_to(body, TRUE) // no clothes, no quirks, no nothing. + appearance = body.appearance + +/atom/movable/screen/map_view/char_preview/limb_viewer/Destroy() + /// See [/atom/movable/screen/map_view/char_preview/loadout] for why this is needed. + preferences = null + return ..() + +/datum/preference_middleware/limbs + action_delegations = list( + "select_path" = PROC_REF(action_select), + "deselect_path" = PROC_REF(action_deselect), + ) + + /// The preview dummy + VAR_FINAL/atom/movable/screen/map_view/char_preview/limb_viewer/character_preview_view + /// Records all paths that were selected the last time we updated the preview icon + /// This is done because we have to use getflat icon (very laggy) rather than byondui + /// as byondUI doesn't really allow layering other UI elements such as SVGs above it + VAR_FINAL/list/paths_on_last_ui_update + /// Caches the last icon we generated, see above + VAR_FINAL/cached_icon + +/datum/preference_middleware/limbs/Destroy(force, ...) + QDEL_NULL(character_preview_view) + return ..() + +/datum/preference_middleware/limbs/on_new_character(mob/user) + paths_on_last_ui_update = null + cached_icon = null + character_preview_view?.update_body() + +/// Initialize our character dummy. +/datum/preference_middleware/limbs/proc/create_character_preview_view(mob/user) + character_preview_view = new(null, preferences) + character_preview_view.update_body() + return character_preview_view + +/datum/preference_middleware/limbs/proc/action_select(list/params, mob/user) + var/obj/item/path_selecting = text2path(params["path_to_use"]) + var/datum/limb_option_datum/selecting_datum = GLOB.limb_loadout_options[path_selecting] + if(isnull(selecting_datum)) + return TRUE + + var/list/selected_paths = preferences.read_preference(/datum/preference/limbs) + LAZYSET(selected_paths, selecting_datum.pref_list_slot, path_selecting) + preferences.update_preference(GLOB.preference_entries[/datum/preference/limbs], selected_paths) + character_preview_view.update_body() + return TRUE + +/datum/preference_middleware/limbs/proc/action_deselect(list/params, mob/user) + var/obj/item/path_deselecting = text2path(params["path_to_use"]) + var/datum/limb_option_datum/deselecting_datum = GLOB.limb_loadout_options[path_deselecting] + if(isnull(deselecting_datum)) + return TRUE + + var/list/selected_paths = preferences.read_preference(/datum/preference/limbs) + LAZYREMOVE(selected_paths, deselecting_datum.pref_list_slot) + preferences.update_preference(GLOB.preference_entries[/datum/preference/limbs], selected_paths) + character_preview_view.update_body() + return TRUE + +/datum/preference_middleware/limbs/get_ui_assets(mob/user) + return list( + get_asset_datum(/datum/asset/simple/body_zones), + ) + +/datum/preference_middleware/limbs/get_ui_data(mob/user) + var/list/data = list() + var/list/selected_paths = preferences.read_preference(/datum/preference/limbs) + data["selected_limbs"] = flatten_list(selected_paths) + + if(isnull(character_preview_view)) + character_preview_view = create_character_preview_view(user) + if(isnull(cached_icon) || length(selected_paths ^ paths_on_last_ui_update) >= 1) + paths_on_last_ui_update = LAZYLISTDUPLICATE(selected_paths) + cached_icon = icon2base64(getFlatIcon(character_preview_view.body, no_anim = TRUE)) + + data["preview_flat_icon"] = cached_icon + return data + +/datum/preference_middleware/limbs/get_ui_static_data(mob/user) + var/list/data = list() + + // This should all be moved to constant data when I figure out how tee hee + var/static/list/limbs_data + if(isnull(limbs_data)) + var/list/raw_data = list( + BODY_ZONE_HEAD = list(), + BODY_ZONE_CHEST = list(), + BODY_ZONE_L_ARM = list(), + BODY_ZONE_R_ARM = list(), + BODY_ZONE_L_LEG = list(), + BODY_ZONE_R_LEG = list(), + ) + + for(var/limb_type in GLOB.limb_loadout_options) + var/datum/limb_option_datum/limb_datum = GLOB.limb_loadout_options[limb_type] + var/limb_zone = limb_datum.ui_zone + + if(isnull(limb_zone) || !islist(raw_data[limb_zone])) + stack_trace("Invalid limb zone found in limb datums: [limb_zone || "null"]. (From: [limb_type])") + continue + + var/list/limb_data = list( + "name" = limb_datum.name, + "tooltip" = limb_datum.desc, + "path" = limb_type, + ) + + UNTYPED_LIST_ADD(raw_data[limb_zone], limb_data) + + limbs_data = list() + for(var/raw_list_key in raw_data) + var/list/ui_formatted_raw_list = list( + "category_name" = raw_list_key, + "category_data" = raw_data[raw_list_key], + ) + UNTYPED_LIST_ADD(limbs_data, ui_formatted_raw_list) + + + data["limbs"] = limbs_data + return data diff --git a/modular_doppler/modular_customization/augmentsplus/markings_preferences b/modular_doppler/modular_customization/augmentsplus/markings_preferences new file mode 100644 index 0000000000000..40f39008d1220 --- /dev/null +++ b/modular_doppler/modular_customization/augmentsplus/markings_preferences @@ -0,0 +1,39 @@ +/datum/preference/choiced/markings_head + savefile_key = "markings_head" + savefile_identifier = PREFERENCE_CHARACTER + category = PREFERENCE_CATEGORY_MARKINGS + relevant_external_organ = null + main_feature_name = "Bodymarkings Head" + +/datum/preference/choiced/markings_head/init_possible_values() + return assoc_to_keys_features(SSaccessories.body_markings) + +/datum/preference/choiced/markings_head/create_default_value() + return SPRITE_ACCESSORY_NONE + +/datum/preference/choiced/markings_head/apply_to_human(mob/living/carbon/human/target, value) + target.dna.features["markings_head"] = value + testing("value is [value]") + +/datum/species/add_body_markings(mob/living/carbon/human/target) + if(target.dna.features["markings_head"] && target.dna.features["markings_head"] != SPRITE_ACCESSORY_NONE) + var/obj/item/bodypart/people_part = target.get_bodypart(BODY_ZONE_HEAD) + if(people_part) + var/datum/bodypart_overlay/simple/body_marking/body_markings/markings = new /datum/bodypart_overlay/simple/body_marking/body_markings() + var/accessory_name = target.dna.features["markings_head"] + var/datum/sprite_accessory/accessory = markings.get_accessory(accessory_name) + var/datum/bodypart_overlay/simple/body_marking/overlay = new /datum/bodypart_overlay/simple/body_marking() + + if(isnull(accessory)) + CRASH("Value: [accessory_name] did not have a corresponding sprite accessory!") + + overlay.icon = accessory.icon + overlay.icon_state = accessory.icon_state + overlay.use_gender = accessory.gender_specific + overlay.draw_color = accessory.color_src + + people_part.add_bodypart_overlay(overlay) + . = ..() + +/datum/bodypart_overlay/simple/body_marking/body_markings/get_accessory(name) + return SSaccessories.body_markings[name] diff --git a/modular_doppler/modular_customization/bodypart/bodypart_overrides.dm b/modular_doppler/modular_customization/bodypart/bodypart_overrides.dm index 6b7fa91ebddda..f061f4efa727b 100644 --- a/modular_doppler/modular_customization/bodypart/bodypart_overrides.dm +++ b/modular_doppler/modular_customization/bodypart/bodypart_overrides.dm @@ -1,4 +1,8 @@ /// If you need to make edits to existing bodyparts, do so in here. +/obj/item/bodypart + /// Override of the base bodypart datum to add a seperate list for customization markings. This is to avoid the dimorphic marking system while trying to keep the code as modular as possible using overrides. + var/list/markings = list() + /obj/item/bodypart/head /// Override of the eyes icon file - used for ramatae as test dummies, followed by teshies, vox, possibly moths & insects, and more! var/eyes_icon diff --git a/tgstation.dme b/tgstation.dme index 608523d1c467d..956a660953349 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -6774,6 +6774,14 @@ #include "modular_doppler\modular_customization\accessories\code\underwear_accessories\undershirts.dm" #include "modular_doppler\modular_customization\accessories\code\underwear_accessories\underwear.dm" #include "modular_doppler\modular_customization\accessories\code\~overrides\code\overrides.dm" +#include "modular_doppler\modular_customization\augmentsplus\_defines.dm" +#include "modular_doppler\modular_customization\augmentsplus\_limb_datums.dm" +#include "modular_doppler\modular_customization\augmentsplus\_markings.dm" +#include "modular_doppler\modular_customization\augmentsplus\augments_arms.dm" +#include "modular_doppler\modular_customization\augmentsplus\limb_preferences.dm" +#include "modular_doppler\modular_customization\augmentsplus\limbs.dm" +#include "modular_doppler\modular_customization\augmentsplus\body_markings\other.dm" +#include "modular_doppler\modular_customization\augmentsplus\markings_preferences" #include "modular_doppler\modular_customization\bodypart\bodypart_overrides.dm" #include "modular_doppler\modular_customization\organs\_organs.dm" #include "modular_doppler\modular_customization\organs\external\fluff.dm" diff --git a/tgui/packages/tgui/assets/bg-doppie.svg b/tgui/packages/tgui/assets/bg-doppie.svg new file mode 100644 index 0000000000000..b3d271da46ce7 --- /dev/null +++ b/tgui/packages/tgui/assets/bg-doppie.svg @@ -0,0 +1,9 @@ + + raster + + + + + + \ No newline at end of file diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/CharacterPreferenceWindow.tsx b/tgui/packages/tgui/interfaces/PreferencesMenu/CharacterPreferenceWindow.tsx index 108f7fdb7e893..eab5965735fda 100644 --- a/tgui/packages/tgui/interfaces/PreferencesMenu/CharacterPreferenceWindow.tsx +++ b/tgui/packages/tgui/interfaces/PreferencesMenu/CharacterPreferenceWindow.tsx @@ -8,14 +8,13 @@ import { Stack, } from '../../components'; /* DOPPLER EDIT: Adds in Dropdown and Flex */ import { Window } from '../../layouts'; +import { LimbManagerPage } from '../_LimbManager'; /* DOPPLER ADDITION */ import { AntagsPage } from './AntagsPage'; import { PreferencesMenuData } from './data'; import { JobsPage } from './JobsPage'; -// DOPPLER EDIT -import { LanguagesPage } from './LanguagesMenu'; -// DOPPLER EDIT +import { LanguagesPage } from './LanguagesMenu'; /* DOPPLER ADDITION */ import { LoadoutPage } from './loadout/index'; -import { LorePage } from './LorePage'; /* DOPPLER EDIT ADDITION */ +import { LorePage } from './LorePage'; /* DOPPLER ADDITION */ import { MainPage } from './MainPage'; import { PageButton } from './PageButton'; import { QuirksPage } from './QuirksPage'; @@ -26,12 +25,13 @@ enum Page { Main, Jobs, // DOPPLER EDIT + Lore, Languages, + Limbs, // DOPPLER EDIT END Species, Quirks, Loadout, - Lore /* DOPPLER EDIT ADDITION */, } const CharacterProfiles = (props: { @@ -109,6 +109,10 @@ export const CharacterPreferenceWindow = (props) => { break; /* DOPPLER ADDITION END */ + case Page.Limbs: + pageContents = ; + break; + default: exhaustiveCheck(currentPage); } @@ -161,10 +165,21 @@ export const CharacterPreferenceWindow = (props) => { > Lore - { - // DOPPLER EDIT END - } + + + + Limbs + + + + { + // DOPPLER EDIT END + } void }) => { const [multiNameInputOpen, setMultiNameInputOpen] = useState(false); const [randomToggleEnabled] = useRandomToggleState(); + enum PrefPage { + Character, // The generic character options + Markings, // Markings + } + + const [currentPrefPage, setCurrentPrefPage] = useState(PrefPage.Character); + return ( { @@ -527,6 +536,10 @@ export const MainPage = (props: { openSpecies: () => void }) => { ...data.character_preferences.non_contextual, }; + const MarkingPreferences = { + ...data.character_preferences.markings, + }; + if (randomBodyEnabled) { nonContextualPreferences['random_species'] = data.character_preferences.randomization['species']; @@ -536,6 +549,40 @@ export const MainPage = (props: { openSpecies: () => void }) => { delete nonContextualPreferences['random_name']; } + let prefPageContents; + switch (currentPrefPage) { + case PrefPage.Character: + prefPageContents = ( + + ); + break; + case PrefPage.Markings: + prefPageContents = ( + + ); + break; + default: + exhaustiveCheck(currentPrefPage); + } + return ( <> {multiNameInputOpen && ( @@ -653,17 +700,31 @@ export const MainPage = (props: { openSpecies: () => void }) => { + {/* SKYRAT EDIT BEGIN: Swappable pref menus */} + + + + Character + + + + + Markings + + + + - + + {prefPageContents} /* DOPPLER EDIT ADDITION */; secondary_features: Record; supplemental_features: Record; + markings: Record /* DOPPLER EDIT ADDITION */; manually_rendered_features: Record; names: Record; diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/dopplershift_preferences/mutant_limbs.tsx b/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/dopplershift_preferences/mutant_limbs.tsx index a99d032fe2de4..fa114562b3f82 100644 --- a/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/dopplershift_preferences/mutant_limbs.tsx +++ b/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/dopplershift_preferences/mutant_limbs.tsx @@ -22,6 +22,14 @@ export const chest_type: FeatureChoiced = { description: ` Add a cybernetic chassis to your character. `, + component: FeatureDropdownInput, +}; + +export const markings_head: FeatureChoiced = { + name: 'Head Markings', + description: ` + Add a suitable marking to your character's head. + `, component: ( props: FeatureValueProps, ) => { diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/game_preferences/_limbs.tsx b/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/game_preferences/_limbs.tsx new file mode 100644 index 0000000000000..b14fcadbe80bf --- /dev/null +++ b/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/game_preferences/_limbs.tsx @@ -0,0 +1,17 @@ +import { Button, Stack } from '../../../../../components'; +import { Feature, FeatureValueProps } from '../base'; + +export const limb_list: Feature = { + name: 'Access Limbs', + component: (props: FeatureValueProps) => { + const { act } = props; + + return ( + + +