diff --git a/code/__DEFINES/skills/defines.dm b/code/__DEFINES/skills/defines.dm index 659c0fe11b..60d2321927 100644 --- a/code/__DEFINES/skills/defines.dm +++ b/code/__DEFINES/skills/defines.dm @@ -100,4 +100,10 @@ ///Ascending priority defines. #define MODIFIER_SKILL_PRIORITY_LOW 100 #define MODIFIER_SKILL_PRIORITY_DEF 50 -#define MODIFIER_SKILL_PRIORITY_MAX 1 //max priority, meant for job/antag modifiers so they don't null out other (de)buffs \ No newline at end of file +#define MODIFIER_SKILL_PRIORITY_MAX 1 //max priority, meant for job/antag modifiers so they don't null out other (de)buffs + +// UI Defines +///Categories of skills, these will be displayed alphabetically. +#define SKILL_UI_CAT_ENG "Engineering" +#define SKILL_UI_CAT_MED "Medical" +#define SKILL_UI_CAT_MISC "Misc" diff --git a/code/__HELPERS/_lists.dm b/code/__HELPERS/_lists.dm index ba29205452..719735392d 100644 --- a/code/__HELPERS/_lists.dm +++ b/code/__HELPERS/_lists.dm @@ -69,7 +69,7 @@ /proc/english_list(list/input, nothing_text = "nothing", and_text = " and ", comma_text = ", ", final_comma_text = "" ) var/total = input.len if (!total) - return "[nothing_text]" + return nothing_text else if (total == 1) return "[input[1]]" else if (total == 2) diff --git a/code/__HELPERS/cmp.dm b/code/__HELPERS/cmp.dm index 5fb4d05d42..dce4d554b7 100644 --- a/code/__HELPERS/cmp.dm +++ b/code/__HELPERS/cmp.dm @@ -120,3 +120,8 @@ GLOBAL_VAR_INIT(cmp_field, "name") /proc/cmp_item_block_priority_asc(obj/item/A, obj/item/B) return A.block_priority - B.block_priority + +/proc/cmp_skill_categories(datum/skill/A, datum/skill/B) + if(A.ui_category == B.ui_category) + return sorttext(A.name, B.name) + return sorttext(A.ui_category, B.ui_category) diff --git a/code/datums/mind.dm b/code/datums/mind.dm index 2a6835cc23..8ac9bc4276 100644 --- a/code/datums/mind.dm +++ b/code/datums/mind.dm @@ -67,7 +67,7 @@ var/datum/skill_holder/skill_holder /datum/mind/New(var/key) - skill_holder = new() + skill_holder = new(src) src.key = key soulOwner = src martial_art = default_martial_art diff --git a/code/datums/skills/_check_skills.dm b/code/datums/skills/_check_skills.dm index a94c7c95ba..2421bd36ff 100644 --- a/code/datums/skills/_check_skills.dm +++ b/code/datums/skills/_check_skills.dm @@ -11,6 +11,66 @@ if(!mind.skill_holder) to_chat(usr, "How do you check the skills of [(usr == src)? "yourself when you are" : "something"] without the capability for skills? (PROBABLY A BUG, PRESS F1.)") return - var/datum/browser/B = new(usr, "skilldisplay_[REF(src)]", "Skills of [src]") - B.set_content(mind.skill_html_readout()) - B.open() + + mind.skill_holder.ui_interact(src) + +/datum/skill_holder/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.always_state) + ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + ui = new(user, src, ui_key, "skillpanel", "[owner.name]'s Skills", 620, 580, master_ui, state) + ui.set_autoupdate(FALSE) // This UI is only ever opened by one person, and never is updated outside of user input. + ui.open() + else if(need_static_data_update) + update_static_data(user) + need_static_data_update = FALSE + +/datum/skill_holder/ui_static_data(mob/user) + . = list() + .["skills"] = list() + for(var/path in GLOB.skill_datums) + var/datum/skill/S = GLOB.skill_datums[path] + var/list/dat = S.get_skill_data(src) + if(islist(dat["modifiers"])) + dat["modifiers"] = jointext(dat["modifiers"], ", ") + dat["percent_base"] = (dat["value_base"] / dat["max_value"]) + dat["percent_mod"] = (dat["value_mod"] / dat["max_value"]) + .["skills"] += list(dat) + +/datum/skill_holder/ui_data(mob/user) + . = list() + .["playername"] = owner.name + .["see_skill_mods"] = see_skill_mods + .["admin"] = check_rights(R_DEBUG) + +/datum/skill_holder/ui_act(action, params) + . = ..() + if(.) + return + switch(action) + if("toggle_mods") + see_skill_mods = !see_skill_mods + return TRUE + if ("adj_exp") + if(!check_rights(R_DEBUG)) + return + var/skill = text2path(params["skill"]) + var/number = input("Please insert the amount of experience/progress you'd like to add/subtract:") as num|null + if (number) + owner.set_skill_value(skill, owner.get_skill_value(skill, FALSE) + number) + return TRUE + if ("set_exp") + if(!check_rights(R_DEBUG)) + return + var/skill = text2path(params["skill"]) + var/number = input("Please insert the number you want to set the player's exp/progress to:") as num|null + if (!isnull(number)) + owner.set_skill_value(skill, number) + return TRUE + if ("set_lvl") + if(!check_rights(R_DEBUG)) + return + var/datum/skill/level/S = GLOB.skill_datums[text2path(params["skill"])] + var/number = input("Please insert a whole number between 0[S.associative ? " ([S.unskilled_tier])" : ""] and [S.max_levels][S.associative ? " ([S.levels[S.max_levels]])" : ""] corresponding to the level you'd like to set the player to.") as num|null + if (number >= 0 && number <= S.max_levels) + owner.set_skill_value(S.type, S.get_skill_level_value(number)) + return TRUE diff --git a/code/datums/skills/_skill.dm b/code/datums/skills/_skill.dm index 571a8274d8..e3f1d00ae6 100644 --- a/code/datums/skills/_skill.dm +++ b/code/datums/skills/_skill.dm @@ -8,6 +8,7 @@ GLOBAL_LIST_INIT_TYPED(skill_datums, /datum/skill, init_skill_datums()) continue S = new path .[S.type] = S + . = sortTim(., /proc/cmp_skill_categories, TRUE) /** * Skill datums @@ -34,6 +35,8 @@ GLOBAL_LIST_INIT_TYPED(skill_datums, /datum/skill, init_skill_datums()) var/competency_multiplier = 1 /// A list of ways this skill can affect or be affected through actions and skill modifiers. var/list/skill_traits = list(SKILL_SANITY, SKILL_INTELLIGENCE) + /// Index of this skill in the UI + var/ui_category = SKILL_UI_CAT_MISC /** * Ensures what someone's setting as a value for this skill is valid. @@ -57,10 +60,28 @@ GLOBAL_LIST_INIT_TYPED(skill_datums, /datum/skill, init_skill_datums()) return new_value > existing /** - * Standard value "render" + * Get a list of data used in the skill panel menu. */ -/datum/skill/proc/standard_render_value(value, level) - return value +/datum/skill/proc/get_skill_data(datum/skill_holder/H) + var/skill_value = H.owner.get_skill_value(type, FALSE) + . = list( + "name" = name, + "desc" = desc, + "path" = type, + "value_base" = skill_value, + "value_mod" = skill_value, + "modifiers" = "None", + "max_value" = 1 //To avoid division by zero later on. + ) + var/list/mods = LAZYACCESS(H.skill_value_mods, type) + if(mods) + var/list/mod_names = list() + for(var/k in mods) + var/datum/skill_modifier/M = GLOB.skill_modifiers[k] + mod_names |= M.name + skill_value = M.apply_modifier(skill_value, type, H, MODIFIER_TARGET_VALUE) + .["value_mod"] = skill_value + .["modifiers"] = mod_names //Will be jointext()'d later. // Just saying, the choice to use different sub-parent-types is to force coders to resolve issues as I won't be implementing custom procs to grab skill levels in a certain context. // Aka: So people don't forget to change checks if they change a skill's progression type. @@ -71,10 +92,12 @@ GLOBAL_LIST_INIT_TYPED(skill_datums, /datum/skill, init_skill_datums()) competency_thresholds = list(THRESHOLD_COMPETENT = FALSE, THRESHOLD_EXPERT = TRUE, THRESHOLD_MASTER = TRUE) /datum/skill/binary/sanitize_value(new_value) - return new_value? TRUE : FALSE + return new_value >= 1 ? TRUE : FALSE -/datum/skill/binary/standard_render_value(value, level) - return value? "Yes" : "No" +/datum/skill/binary/get_skill_data(datum/skill_holder/H) + . = ..() + .["base_readout"] = .["value_base"] ? "Learned: Yes" : "Learned: No" + .["mod_readout"] = .["value_mod"] ? "Learned: Yes" : "Learned: No" /datum/skill/numerical abstract_type = /datum/skill/numerical @@ -84,14 +107,15 @@ GLOBAL_LIST_INIT_TYPED(skill_datums, /datum/skill, init_skill_datums()) var/max_value = 100 /// Min value of this skill var/min_value = 0 - /// Display as a percent in standard_render_value? - var/display_as_percent = FALSE /datum/skill/numerical/sanitize_value(new_value) return clamp(new_value, min_value, max_value) -/datum/skill/numerical/standard_render_value(value, level) - return display_as_percent? "[round(value/max_value/100, 0.01)]%" : "[value] / [max_value]" +/datum/skill/numerical/get_skill_data(datum/skill_holder/H) + . = ..() + .["base_readout"] = "Skill Progress: \[[.["value_base"]] / [max_value]\]" + .["mod_readout"] = "Skill Progress: \[[.["value_mod"]] / [max_value]\]" + .["max_value"] = max_value /datum/skill/enum abstract_type = /datum/skill/enum @@ -123,13 +147,7 @@ GLOBAL_LIST_INIT_TYPED(skill_datums, /datum/skill, init_skill_datums()) var/max_assoc = "" var/max_assoc_start = 1 for(var/lvl in 1 to max_levels) - var/value - switch(level_up_method) - if(STANDARD_LEVEL_UP) - value = XP_LEVEL(standard_xp_lvl_up, xp_lvl_multiplier, lvl) - if(DWARFY_LEVEL_UP) - value = DORF_XP_LEVEL(standard_xp_lvl_up, xp_lvl_multiplier, lvl) - value = round(value, 1) + var/value = round(get_skill_level_value(lvl), 1) if(!associative) levels += value continue @@ -167,8 +185,13 @@ GLOBAL_LIST_INIT_TYPED(skill_datums, /datum/skill, init_skill_datums()) else if(. < 0) to_chat(M.current, "I feel like I've become worse at [name]!") -/datum/skill/level/standard_render_value(value, level) - var/current_lvl = associative ? (!level ? unskilled_tier : levels[level]) : level +/datum/skill/level/get_skill_data(datum/skill_holder/H) + . = ..() + var/skill_value_base = .["value_base"] + var/skill_value_mod = .["value_mod"] + .["level_based"] = TRUE + + var/level = LAZYACCESS(H.skill_levels, type) || 0 var/current_lvl_xp_sum = 0 if(level) current_lvl_xp_sum = associative ? levels[levels[level]] : levels[level] @@ -176,8 +199,50 @@ GLOBAL_LIST_INIT_TYPED(skill_datums, /datum/skill, init_skill_datums()) var/next_lvl_xp = associative ? levels[levels[next_index]] : levels[next_index] if(next_lvl_xp > current_lvl_xp_sum) next_lvl_xp -= current_lvl_xp_sum + .["lvl_base_num"] = .["lvl_mod_num"] = level + .["lvl_base"] = .["lvl_mod"] = associative ? (!level ? unskilled_tier : levels[level]) : level + .["base_style"] = .["mod_style"] = "font-weight:bold; color:hsl([(level+1)*(350/max_levels+1)], 50%, 50%)" + .["xp_next_lvl_base"] = .["xp_next_lvl_mod"] = "\[[skill_value_base - current_lvl_xp_sum]/[next_lvl_xp]\]" - return "[associative ? current_lvl : "Lvl. [current_lvl]"] ([value - current_lvl_xp_sum]/[next_lvl_xp])[level == max_levels ? " \[MAX!\]" : ""]" + .["max_lvl"] = max_levels + var/max_value = associative ? levels[levels[max_levels]] : levels[max_levels] + .["max_value"] = max_value + + .["base_readout"] = "Overall Skill Progress: \[[skill_value_base]/[max_value]\]" + .["mod_readout"] = "Overall Skill Progress: \[[skill_value_mod]/[max_value]\]" + + var/list/mods = LAZYACCESS(H.skill_level_mods, type) + if(mods) //I'm not proud of doing the same-ish process twice a row but here we go. + var/list/mod_names = .["modifiers"] + if(!mod_names) + .["modifiers"] = mod_names = list() + for(var/k in mods) + var/datum/skill_modifier/M = GLOB.skill_modifiers[k] + mod_names |= M.name + level = M.apply_modifier(level, type, H, MODIFIER_TARGET_LEVEL) + + if(level) + current_lvl_xp_sum = associative ? levels[levels[level]] : levels[level] + else + current_lvl_xp_sum = 0 + next_index = min(max_levels, level+1) + next_lvl_xp = associative ? levels[levels[next_index]] : levels[next_index] + if(next_lvl_xp > current_lvl_xp_sum) + next_lvl_xp -= current_lvl_xp_sum + .["lvl_mod_num"] = level + .["lvl_mod"] = associative ? (!level ? unskilled_tier : levels[level]) : level + .["mod_style"] = "font-weight:bold; color:hsl([(level+1)*(300/(max_levels+1))], 50%, 50%)" + .["xp_next_lvl_mod"] = "\[[skill_value_mod - current_lvl_xp_sum]/[next_lvl_xp]\]" + +/** + * Gets the base value required to reach a level specified by the 'num' arg. + */ +/datum/skill/level/proc/get_skill_level_value(num) + switch(level_up_method) + if(STANDARD_LEVEL_UP) + . = XP_LEVEL(standard_xp_lvl_up, xp_lvl_multiplier, num) + if(DWARFY_LEVEL_UP) + . = DORF_XP_LEVEL(standard_xp_lvl_up, xp_lvl_multiplier, num) /datum/skill/level/job abstract_type = /datum/skill/level/job diff --git a/code/datums/skills/_skill_holder.dm b/code/datums/skills/_skill_holder.dm index 0a5c4f8f58..8357211463 100644 --- a/code/datums/skills/_skill_holder.dm +++ b/code/datums/skills/_skill_holder.dm @@ -20,6 +20,18 @@ var/list/original_values var/list/original_affinities var/list/original_levels + /// The mind datum this skill is associated with, only used for the check_skills UI + var/datum/mind/owner + /// For UI updates. + var/need_static_data_update = TRUE + /// Whether modifiers and final skill values or only base values are displayed. + var/see_skill_mods = TRUE + /// The current selected skill category. + var/selected_category + +/datum/skill_holder/New(owner) + ..() + src.owner = owner /** * Grabs the value of a skill. @@ -79,6 +91,7 @@ CRASH("Invalid set_skill_value call. Use skill typepaths.") //until a time when we somehow need text ids for dynamic skills, I'm enforcing this. var/datum/skill/S = GLOB.skill_datums[skill] value = S.sanitize_value(value) + skill_holder.need_static_data_update = TRUE if(!isnull(value)) LAZYINITLIST(skill_holder.skills) S.set_skill_value(skill_holder, value, src, silent) @@ -183,18 +196,3 @@ divisor++ if(divisor) . = modifier_is_multiplier ? value*(sum/divisor) : value/(sum/divisor) - -/** - * Generates a HTML readout of our skills. - * Port to tgui-next when? - */ -/datum/mind/proc/skill_html_readout() - var/list/out = list("
| Skill | Value |
|---|---|
| [S.name] | [S.standard_render_value(skill_value, skill_level)] |