diff --git a/code/__DEFINES/misc.dm b/code/__DEFINES/misc.dm
index a39ebf36ba..a3fcb066d6 100644
--- a/code/__DEFINES/misc.dm
+++ b/code/__DEFINES/misc.dm
@@ -432,6 +432,7 @@ GLOBAL_LIST_INIT(pda_reskins, list(PDA_SKIN_CLASSIC = 'icons/obj/pda.dmi', PDA_S
//text files
#define BRAIN_DAMAGE_FILE "traumas.json"
#define ION_FILE "ion_laws.json"
+#define REDPILL_FILE "redpill.json"
#define PIRATE_NAMES_FILE "pirates.json"
diff --git a/code/__DEFINES/mobs.dm b/code/__DEFINES/mobs.dm
index 8d8b9f47ae..fa7758be1f 100644
--- a/code/__DEFINES/mobs.dm
+++ b/code/__DEFINES/mobs.dm
@@ -98,6 +98,7 @@
#define BRAIN_TRAUMA_MILD /datum/brain_trauma/mild
#define BRAIN_TRAUMA_SEVERE /datum/brain_trauma/severe
#define BRAIN_TRAUMA_SPECIAL /datum/brain_trauma/special
+#define BRAIN_TRAUMA_MAGIC /datum/brain_trauma/magic
#define TRAUMA_RESILIENCE_BASIC 1 //Curable with chems
#define TRAUMA_RESILIENCE_SURGERY 2 //Curable with brain surgery
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/__HELPERS/roundend.dm b/code/__HELPERS/roundend.dm
index fc79c546e7..045ca2c519 100644
--- a/code/__HELPERS/roundend.dm
+++ b/code/__HELPERS/roundend.dm
@@ -174,7 +174,7 @@
to_chat(world, "
The round has ended.")
if(LAZYLEN(GLOB.round_end_notifiees))
- send2irc("Notice", "[GLOB.round_end_notifiees.Join(", ")] the round has ended.")
+ world.TgsTargetedChatBroadcast("[GLOB.round_end_notifiees.Join(", ")] the round has ended.", FALSE)
for(var/I in round_end_events)
var/datum/callback/cb = I
diff --git a/code/controllers/configuration/configuration.dm b/code/controllers/configuration/configuration.dm
index 8f34901d46..31431ddab9 100644
--- a/code/controllers/configuration/configuration.dm
+++ b/code/controllers/configuration/configuration.dm
@@ -104,7 +104,9 @@
var/list/lines = world.file2list("[directory]/[filename]")
var/list/_entries = entries
var/list/postload_required = list()
+ var/linenumber = 0
for(var/L in lines)
+ linenumber++
L = trim(L)
if(!L)
continue
@@ -132,7 +134,7 @@
if(entry == "$include")
if(!value)
- log_config("Warning: Invalid $include directive: [value]")
+ log_config("LINE [linenumber]: Invalid $include directive: [value]")
else
LoadEntries(value, stack)
++.
@@ -140,7 +142,7 @@
var/datum/config_entry/E = _entries[entry]
if(!E)
- log_config("Unknown setting in configuration: '[entry]'")
+ log_config("LINE [linenumber]: Unknown setting: '[entry]'")
continue
if(lockthis)
@@ -150,7 +152,7 @@
var/datum/config_entry/new_ver = entries_by_type[E.deprecated_by]
var/new_value = E.DeprecationUpdate(value)
var/good_update = istext(new_value)
- log_config("Entry [entry] is deprecated and will be removed soon. Migrate to [new_ver.name]![good_update ? " Suggested new value is: [new_value]" : ""]")
+ log_config("LINE [linenumber]: [entry] is deprecated and will be removed soon. Migrate to [new_ver.name]![good_update ? " Suggested new value is: [new_value]" : ""]")
if(!warned_deprecated_configs)
addtimer(CALLBACK(GLOBAL_PROC, /proc/message_admins, "This server is using deprecated configuration settings. Please check the logs and update accordingly."), 0)
warned_deprecated_configs = TRUE
@@ -162,10 +164,10 @@
var/validated = E.ValidateAndSet(value, TRUE)
if(!validated)
- log_config("Failed to validate setting \"[value]\" for [entry]")
+ log_config("LINE [linenumber]: Failed to validate setting \"[value]\" for [entry]")
else
if(E.modified && !E.dupes_allowed)
- log_config("Duplicate setting for [entry] ([value], [E.resident_file]) detected! Using latest.")
+ log_config("LINE [linenumber]: Duplicate setting for [entry] ([value], [E.resident_file]) detected! Using latest.")
if(E.postload_required)
postload_required[E] = TRUE
diff --git a/code/controllers/configuration/entries/game_options.dm b/code/controllers/configuration/entries/game_options.dm
index a40f440983..437936d432 100644
--- a/code/controllers/configuration/entries/game_options.dm
+++ b/code/controllers/configuration/entries/game_options.dm
@@ -168,12 +168,14 @@
/datum/config_entry/flag/join_with_mutant_humans //players can pick mutant bodyparts for humans before joining the game
-/datum/config_entry/flag/no_summon_guns //No
+/datum/config_entry/flag/no_summon_guns //No
/datum/config_entry/flag/no_summon_magic //Fun
/datum/config_entry/flag/no_summon_events //Allowed
+/datum/config_entry/flag/no_summon_traumas //!
+
/datum/config_entry/flag/no_intercept_report //Whether or not to send a communications intercept report roundstart. This may be overridden by gamemodes.
/datum/config_entry/number/arrivals_shuttle_dock_window //Time from when a player late joins on the arrivals shuttle to when the shuttle docks on the station
diff --git a/code/controllers/subsystem/events.dm b/code/controllers/subsystem/events.dm
index abc46cba25..2e4e728191 100644
--- a/code/controllers/subsystem/events.dm
+++ b/code/controllers/subsystem/events.dm
@@ -12,7 +12,7 @@ SUBSYSTEM_DEF(events)
var/frequency_upper = 6000 //10 minutes upper bound. Basically an event will happen every 3 to 10 minutes.
var/list/holidays //List of all holidays occuring today or null if no holidays
- var/wizardmode = 0
+ var/wizardmode = FALSE
/datum/controller/subsystem/events/Initialize(time, zlevel)
for(var/type in typesof(/datum/round_event_control))
@@ -91,6 +91,7 @@ SUBSYSTEM_DEF(events)
if(. == EVENT_CANT_RUN)//we couldn't run this event for some reason, set its max_occurrences to 0
E.max_occurrences = 0
else if(. == EVENT_READY)
+ E.random = TRUE
E.runEvent(TRUE)
//allows a client to trigger an event
diff --git a/code/controllers/subsystem/input.dm b/code/controllers/subsystem/input.dm
index 7c51ba4506..32936af9e9 100644
--- a/code/controllers/subsystem/input.dm
+++ b/code/controllers/subsystem/input.dm
@@ -64,19 +64,33 @@ SUBSYSTEM_DEF(input)
// Misc
macroset_classic_input["Tab"] = "\".winset \\\"mainwindow.macro=[SKIN_MACROSET_CLASSIC_HOTKEYS] map.focus=true input.background-color=[COLOR_INPUT_DISABLED]\\\"\""
macroset_classic_input["Escape"] = "\".winset \\\"input.text=\\\"\\\"\\\"\""
-
+
// FINALLY, WE CAN DO SOMETHING MORE NORMAL FOR THE SNOWFLAKE-BUT-LESS KEYSET.
+
+ // HAHA - SIKE. Because of BYOND weirdness (tl;dr not specifically binding this way results in potentially duplicate chatboxes when
+ // conflicts occur with something like say indicator vs say), we're going to snowflake this anyways
+ var/list/hard_binds = list(
+ "O" = "ooc",
+ "T" = "say",
+ "L" = "looc",
+ "M" = "me"
+ )
+ var/list/hard_bind_anti_collision = list()
+ var/list/anti_collision_modifiers = list("Ctrl", "Alt", "Shift", "Ctrl+Alt", "Ctrl+Shift", "Alt+Shift", "Ctrl+Alt+Shift")
+ for(var/key in hard_binds)
+ for(var/modifier in anti_collision_modifiers)
+ hard_bind_anti_collision["[modifier]+[key]"] = ".NONSENSICAL_VERB_THAT_DOES_NOTHING"
+
macroset_classic_hotkey = list(
"Any" = "\"KeyDown \[\[*\]\]\"",
"Any+UP" = "\"KeyUp \[\[*\]\]\"",
"Tab" = "\".winset \\\"mainwindow.macro=[SKIN_MACROSET_CLASSIC_INPUT] input.focus=true input.background-color=[COLOR_INPUT_ENABLED]\\\"\"",
"Escape" = "\".winset \\\"input.text=\\\"\\\"\\\"\"",
"Back" = "\".winset \\\"input.text=\\\"\\\"\\\"\"",
- "O" = "ooc",
- "T" = "say",
- "L" = "looc",
- "M" = "me"
)
+
+ macroset_classic_hotkey |= hard_binds
+ macroset_classic_hotkey |= hard_bind_anti_collision
// And finally, the modern set.
macroset_hotkey = list(
@@ -85,11 +99,10 @@ SUBSYSTEM_DEF(input)
"Tab" = "\".winset \\\"input.focus=true?map.focus=true input.background-color=[COLOR_INPUT_DISABLED]:input.focus=true input.background-color=[COLOR_INPUT_ENABLED]\\\"\"",
"Escape" = "\".winset \\\"input.text=\\\"\\\"\\\"\"",
"Back" = "\".winset \\\"input.text=\\\"\\\"\\\"\"",
- "O" = "ooc",
- "T" = "say",
- "L" = "looc",
- "M" = "me"
)
+
+ macroset_hotkey |= hard_binds
+ macroset_hotkey |= hard_bind_anti_collision
// Badmins just wanna have fun ♪
/datum/controller/subsystem/input/proc/refresh_client_macro_sets()
@@ -104,3 +117,8 @@ SUBSYSTEM_DEF(input)
for(var/i in 1 to clients.len)
var/client/C = clients[i]
C.keyLoop()
+
+/// *sigh
+/client/verb/NONSENSICAL_VERB_THAT_DOES_NOTHING()
+ set name = ".NONSENSICAL_VERB_THAT_DOES_NOTHING"
+ set hidden = TRUE
diff --git a/code/datums/brain_damage/magic.dm b/code/datums/brain_damage/magic.dm
index 249907a1b8..ff04ceead9 100644
--- a/code/datums/brain_damage/magic.dm
+++ b/code/datums/brain_damage/magic.dm
@@ -5,6 +5,61 @@
/datum/brain_trauma/magic
resilience = TRAUMA_RESILIENCE_LOBOTOMY
+/datum/brain_trauma/magic/lumiphobia
+ name = "Lumiphobia"
+ desc = "Patient has an inexplicable adverse reaction to light."
+ scan_desc = "light hypersensitivity"
+ gain_text = "You feel a craving for darkness."
+ lose_text = "Light no longer bothers you."
+ var/next_damage_warning = 0
+
+/datum/brain_trauma/magic/lumiphobia/on_life()
+ ..()
+ var/turf/T = owner.loc
+ if(istype(T))
+ var/light_amount = T.get_lumcount()
+ if(light_amount > SHADOW_SPECIES_LIGHT_THRESHOLD) //if there's enough light, start dying
+ if(world.time > next_damage_warning)
+ to_chat(owner, "The light burns you!")
+ next_damage_warning = world.time + 100 //Avoid spamming
+ owner.take_overall_damage(0,3)
+
+/datum/brain_trauma/magic/poltergeist
+ name = "Poltergeist"
+ desc = "Patient appears to be targeted by a violent invisible entity."
+ scan_desc = "paranormal activity"
+ gain_text = "You feel a hateful presence close to you."
+ lose_text = "You feel the hateful presence fade away."
+
+/datum/brain_trauma/magic/poltergeist/on_life()
+ ..()
+ if(prob(4))
+ var/most_violent = -1 //So it can pick up items with 0 throwforce if there's nothing else
+ var/obj/item/throwing
+ for(var/obj/item/I in view(5, get_turf(owner)))
+ if(I.anchored)
+ continue
+ if(I.throwforce > most_violent)
+ most_violent = I.throwforce
+ throwing = I
+ if(throwing)
+ throwing.throw_at(owner, 8, 2)
+
+/datum/brain_trauma/magic/antimagic
+ name = "Athaumasia"
+ desc = "Patient is completely inert to magical forces."
+ scan_desc = "thaumic blank"
+ gain_text = "You realize that magic cannot be real."
+ lose_text = "You realize that magic might be real."
+
+/datum/brain_trauma/magic/antimagic/on_gain()
+ ADD_TRAIT(owner, TRAIT_ANTIMAGIC, TRAUMA_TRAIT)
+ ..()
+
+/datum/brain_trauma/magic/antimagic/on_lose()
+ REMOVE_TRAIT(owner, TRAIT_ANTIMAGIC, TRAUMA_TRAIT)
+ ..()
+
/datum/brain_trauma/magic/stalker
name = "Stalking Phantom"
desc = "Patient is stalked by a phantom only they can see."
diff --git a/code/datums/brain_damage/severe.dm b/code/datums/brain_damage/severe.dm
index cd8612fd19..3ba00d305b 100644
--- a/code/datums/brain_damage/severe.dm
+++ b/code/datums/brain_damage/severe.dm
@@ -119,8 +119,9 @@
/datum/brain_trauma/severe/paralysis/spinesnapped
random_gain = FALSE
+ clonable = FALSE
paralysis_type = "legs"
- resilience = TRAUMA_RESILIENCE_LOBOTOMY
+ resilience = TRAUMA_RESILIENCE_LOBOTOMY // It shouldn't fix severed spinal cords really, but there is no specific surgery for that yet.
/datum/brain_trauma/severe/narcolepsy
name = "Narcolepsy"
diff --git a/code/datums/components/crafting/glassware/lens_crafting.dm b/code/datums/components/crafting/glassware/lens_crafting.dm
index 117df25a30..98fa242058 100644
--- a/code/datums/components/crafting/glassware/lens_crafting.dm
+++ b/code/datums/components/crafting/glassware/lens_crafting.dm
@@ -92,7 +92,7 @@
qdel(src)
/obj/item/glasswork/glasses
- name = "Hand Made Glasses"
- desc = "Hande made glasses that have not been polished at all making them useless. Selling them could still be worth a bit of credits."
+ name = "Handmade Glasses"
+ desc = "Handmade glasses that have not been polished at all making them useless. Selling them could still be worth a few credits."
icon = 'icons/obj/glass_ware.dmi'
icon_state = "frames_2"
\ No newline at end of file
diff --git a/code/datums/components/crafting/recipes/recipes_misc.dm b/code/datums/components/crafting/recipes/recipes_misc.dm
index b076407efc..63ef711858 100644
--- a/code/datums/components/crafting/recipes/recipes_misc.dm
+++ b/code/datums/components/crafting/recipes/recipes_misc.dm
@@ -92,7 +92,7 @@
time = 150
subcategory = CAT_MISCELLANEOUS
category = CAT_MISC
- always_availible = FALSE // Disabled til learned
+ always_availible = FALSE // Disabled until learned
/datum/crafting_recipe/bloodsucker/candelabrum
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/mutations/actions.dm b/code/datums/mutations/actions.dm
index 1475cfa356..335ecf889f 100644
--- a/code/datums/mutations/actions.dm
+++ b/code/datums/mutations/actions.dm
@@ -42,6 +42,7 @@
school = "evocation"
charge_max = 600
clothes_req = NONE
+ antimagic_allowed = TRUE
range = 20
base_icon_state = "fireball"
action_icon_state = "fireball0"
@@ -122,6 +123,7 @@
desc = "A rare genome that attracts odd forces not usually observed. May sometimes pull you in randomly."
school = "evocation"
clothes_req = NONE
+ antimagic_allowed = TRUE
charge_max = 600
invocation = "DOOOOOOOOOOOOOOOOOOOOM!!!"
invocation_type = "shout"
@@ -155,6 +157,7 @@
dropmessage = "You let the electricity from your hand dissipate."
hand_path = /obj/item/melee/touch_attack/shock
charge_max = 400
+ antimagic_allowed = TRUE
clothes_req = NONE
action_icon_state = "zap"
@@ -212,6 +215,7 @@
desc = "Get a scent off of the item you're currently holding to track it. With an empty hand, you'll track the scent you've remembered."
charge_max = 100
clothes_req = NONE
+ antimagic_allowed = TRUE
range = -1
include_user = TRUE
action_icon_state = "nose"
@@ -290,6 +294,7 @@
name = "Drop a limb"
desc = "Concentrate to make a random limb pop right off your body."
clothes_req = NONE
+ antimagic_allowed = TRUE
charge_max = 100
action_icon_state = "autotomy"
@@ -327,6 +332,7 @@
name = "Lay Web"
desc = "Drops a web. Only you will be able to traverse your web easily, making it pretty good for keeping you safe."
clothes_req = NONE
+ antimagic_allowed = TRUE
charge_max = 4 SECONDS //the same time to lay a web
action_icon = 'icons/mob/actions/actions_genetic.dmi'
action_icon_state = "lay_web"
@@ -368,6 +374,7 @@
name = "Launch spike"
desc = "Shoot your tongue out in the direction you're facing, embedding it and dealing damage until they remove it."
clothes_req = NONE
+ antimagic_allowed = TRUE
charge_max = 100
action_icon = 'icons/mob/actions/actions_genetic.dmi'
action_icon_state = "spike"
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)] |