From 6d1441426c993ef1f6a7d22758342cb8f26340bf Mon Sep 17 00:00:00 2001 From: FalloutFalcon Date: Wed, 9 Sep 2026 08:18:48 -0500 Subject: [PATCH 1/2] stuff --- code/controllers/subsystem/ticker.dm | 5 + code/modules/client/preferences_savefile.dm | 2 +- .../modules/mob/dead/new_player/new_player.dm | 5 + .../powers/code/admin_preference_editor.dm | 3 +- .../storyteller_stats/code/_st_stats.dm | 4 + .../storyteller_stats/code/stat_validation.dm | 109 ++++++++++++++++++ tgstation.dme | 1 + 7 files changed, 126 insertions(+), 3 deletions(-) create mode 100644 modular_darkpack/modules/storyteller_stats/code/stat_validation.dm diff --git a/code/controllers/subsystem/ticker.dm b/code/controllers/subsystem/ticker.dm index 9066c336c5f1..c432efa575d0 100644 --- a/code/controllers/subsystem/ticker.dm +++ b/code/controllers/subsystem/ticker.dm @@ -575,6 +575,11 @@ SUBSYSTEM_DEF(ticker) else // clear any personalities the prefs added since our job clearly does not want them new_player_living.clear_personalities() + // DARKPACK EDIT ADD START + for(var/datum/preference_middleware/our_middleware in new_player_mob.client?.prefs?.middleware) + our_middleware.on_character_spawn(new_player_mob) + // DARKPACK EDIT ADD END + if(ishuman(new_player_living)) SEND_SIGNAL(new_player_living, COMSIG_HUMAN_CHARACTER_SETUP_FINISHED) CHECK_TICK diff --git a/code/modules/client/preferences_savefile.dm b/code/modules/client/preferences_savefile.dm index 8f4d6af5643b..60d5a752cc7d 100644 --- a/code/modules/client/preferences_savefile.dm +++ b/code/modules/client/preferences_savefile.dm @@ -529,7 +529,7 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car for (var/datum/preference_middleware/preference_middleware as anything in middleware) preference_middleware.on_new_character(usr) - character_preview_view.update_body() + character_preview_view?.update_body() // DARKPACK EDIT CHANGE - (We technicly can swap slots while they dont have it open) /datum/preferences/proc/remove_current_slot() PRIVATE_PROC(TRUE) diff --git a/code/modules/mob/dead/new_player/new_player.dm b/code/modules/mob/dead/new_player/new_player.dm index 09f26ee9835d..7c7cf3850637 100644 --- a/code/modules/mob/dead/new_player/new_player.dm +++ b/code/modules/mob/dead/new_player/new_player.dm @@ -275,6 +275,11 @@ else // clear any personalities the prefs added since our job clearly does not want them humanc.clear_personalities() + // DARKPACK EDIT ADD START + for(var/datum/preference_middleware/our_middleware in character.client?.prefs?.middleware) + our_middleware.on_character_spawn(character) + // DARKPACK EDIT ADD END + if(humanc) // Quirks may change manifest datapoints, so inject only after assigning quirks GLOB.manifest.inject(humanc) SEND_SIGNAL(humanc, COMSIG_HUMAN_CHARACTER_SETUP_FINISHED) diff --git a/modular_darkpack/modules/powers/code/admin_preference_editor.dm b/modular_darkpack/modules/powers/code/admin_preference_editor.dm index 978bb2f8822e..32864789823c 100644 --- a/modular_darkpack/modules/powers/code/admin_preference_editor.dm +++ b/modular_darkpack/modules/powers/code/admin_preference_editor.dm @@ -162,8 +162,7 @@ if(!slot) return FALSE slot = clamp(slot, 1, target_prefs.max_save_slots) - target_prefs.load_character(slot) - target_prefs.default_slot = slot + target_prefs.switch_to_slot(slot) selected_slot = slot return TRUE diff --git a/modular_darkpack/modules/storyteller_stats/code/_st_stats.dm b/modular_darkpack/modules/storyteller_stats/code/_st_stats.dm index c0dc8c49c5bb..5aa48605874f 100644 --- a/modular_darkpack/modules/storyteller_stats/code/_st_stats.dm +++ b/modular_darkpack/modules/storyteller_stats/code/_st_stats.dm @@ -169,6 +169,10 @@ SHOULD_NOT_OVERRIDE(TRUE) return points +/datum/st_stat/proc/get_initial_points() + SHOULD_NOT_OVERRIDE(TRUE) + return src::points + /datum/st_stat/proc/set_points(amount) SHOULD_NOT_OVERRIDE(TRUE) points = max(amount, 0) diff --git a/modular_darkpack/modules/storyteller_stats/code/stat_validation.dm b/modular_darkpack/modules/storyteller_stats/code/stat_validation.dm new file mode 100644 index 000000000000..89f75f2899ef --- /dev/null +++ b/modular_darkpack/modules/storyteller_stats/code/stat_validation.dm @@ -0,0 +1,109 @@ +/datum/preference_middleware/stats/proc/get_stat_validation_error() + var/list/stats = preferences.preference_storyteller_stats + if(!stats || !length(stats)) + return null + + var/list/score_violations = list() + for(var/stat_typepath in stats) + var/datum/st_stat/stat = stats[stat_typepath] + var/score = stat.get_score(include_bonus = FALSE) + if(score < stat.min_score || score > stat.max_score) + score_violations += "[stat.name]: [score] (allowed [stat.min_score] to [stat.max_score])" + + var/list/levels_by_abstract = list() + for(var/stat_typepath in stats) + var/datum/st_stat/stat = stats[stat_typepath] + if(!stat.editable || stat.type == stat.abstract_type) + continue + var/levels_above = max(0, stat.get_score(include_bonus = FALSE) - stat.starting_score) + if(!levels_above) + continue + if(!(stat.abstract_type in levels_by_abstract)) + levels_by_abstract[stat.abstract_type] = 0 + levels_by_abstract[stat.abstract_type] += levels_above + + var/list/budget_violations = list() + var/total_freebie_needed = 0 + for(var/abstract_typepath in levels_by_abstract) + var/datum/st_stat/abstract_stat = stats[abstract_typepath] + if(!abstract_stat) + continue + var/pool_size = abstract_stat.get_initial_points() + var/freebie_cost = initial(abstract_stat.freebie_point_cost) + var/levels_above = levels_by_abstract[abstract_typepath] + var/overflow = max(0, levels_above - pool_size) + if(!overflow || !freebie_cost || !abstract_stat.name) + continue + budget_violations += "[abstract_stat.name]: [levels_above] spent, budget is [pool_size] ([overflow] over)" + total_freebie_needed += overflow * freebie_cost + + var/datum/st_stat/freebie/freebie_stat = stats[STAT_FREEBIE_POINTS] + var/base_freebie = freebie_stat ? freebie_stat.get_initial_points() : /datum/st_stat/freebie::points + var/quirk_balance = 0 + for(var/quirk_name in preferences.all_quirks) + var/datum/quirk/quirk_type = SSquirks.quirks[quirk_name] + if(quirk_type) + quirk_balance -= quirk_type.value + var/available_freebie = max(0, base_freebie + quirk_balance) + var/freebie_short = total_freebie_needed - available_freebie + + if(!length(score_violations) && freebie_short <= 0) + return null + + var/msg = "STAT VALIDATION" + if(length(score_violations)) + msg += "\nStat scores out of range:\n" + for(var/v in score_violations) + msg += " [v]\n" + if(length(budget_violations)) + msg += "\nCategories over budget:\n" + for(var/v in budget_violations) + msg += " [v]\n" + if(freebie_short > 0) + msg += "\nFreebie points: need [total_freebie_needed], have [available_freebie] ([freebie_short] short of what is needed to be valid)\n" + return msg + +/datum/preference_middleware/proc/on_character_spawn(mob/living/spawning_mob) + +/datum/preference_middleware/stats/on_character_spawn(mob/living/spawning_mob) + validate_stats(spawning_mob) + +/datum/preference_middleware/stats/proc/validate_stats(mob/checked_mob) + var/stat_error = get_stat_validation_error() + if(!stat_error) + return + // to_chat(checked_mob, span_warning(stat_error)) + var/char_name = preferences?.read_preference(/datum/preference/name/real_name) + var/log_msg = "[key_name(checked_mob)]'s character '[char_name]' (slot [preferences?.default_slot]) has invalid stats:\n[stat_error]" + // SSoverwatch.record_action(null, log_msg) + message_admins(log_msg) + + +ADMIN_VERB(validate_sheets, R_ADMIN, "Validate Sheets", "Validate Sheets.", ADMIN_CATEGORY_MAIN) + var/choice = tgui_alert(user, "Validate unloaded characters as well?", "Validate Sheets", list("Yes", "No")) + // var/list/connected = list() + // var/list/invalid_ckeys = list() + for(var/ckey in GLOB.directory) + var/client/C = GLOB.directory[ckey] + if(!C || !C.mob || !C.prefs) + continue + // connected += ckey + /* + if(ishuman(C.mob)) + var/list/validation = validate_mob_sheet(C.mob) + if(validation && !validation["valid"]) + invalid_ckeys += ckey + */ + if(choice == "Yes") + var/original_slot = C.prefs.default_slot + var/list/profiles = C.prefs.create_character_profiles() // Helps filter for unfilled out slots + for(var/i in 1 to C.prefs.max_save_slots) + if(profiles[i]) + var/slot = clamp(i, 1, C.prefs.max_save_slots) + C.prefs.switch_to_slot(slot) + for(var/datum/preference_middleware/stats/stat in C.prefs.middleware) + stat.validate_stats(C.mob) + C.prefs.switch_to_slot(original_slot) + else + for(var/datum/preference_middleware/stats/stat in C.prefs.middleware) + stat.validate_stats(C.mob) diff --git a/tgstation.dme b/tgstation.dme index 9c90b7bb22da..b93c0170178c 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -8124,6 +8124,7 @@ #include "modular_darkpack\modules\storyteller_stats\code\global_procs.dm" #include "modular_darkpack\modules\storyteller_stats\code\logging.dm" #include "modular_darkpack\modules\storyteller_stats\code\stat_pref_middleware.dm" +#include "modular_darkpack\modules\storyteller_stats\code\stat_validation.dm" #include "modular_darkpack\modules\storyteller_stats\code\mob_affecting_adjustments\medicine_skill_surgery.dm" #include "modular_darkpack\modules\storyteller_stats\code\mob_affecting_adjustments\mob_procs.dm" #include "modular_darkpack\modules\storyteller_stats\code\mob_affecting_adjustments\movespeed_modifier.dm" From 121c430596030d4770ae7c553861f0820a44aacc Mon Sep 17 00:00:00 2001 From: FalloutFalcon Date: Wed, 9 Sep 2026 09:13:36 -0500 Subject: [PATCH 2/2] yea --- .../storyteller_stats/code/stat_validation.dm | 49 ++++++++++++++----- 1 file changed, 38 insertions(+), 11 deletions(-) diff --git a/modular_darkpack/modules/storyteller_stats/code/stat_validation.dm b/modular_darkpack/modules/storyteller_stats/code/stat_validation.dm index 89f75f2899ef..e214c455fa67 100644 --- a/modular_darkpack/modules/storyteller_stats/code/stat_validation.dm +++ b/modular_darkpack/modules/storyteller_stats/code/stat_validation.dm @@ -66,35 +66,36 @@ /datum/preference_middleware/proc/on_character_spawn(mob/living/spawning_mob) /datum/preference_middleware/stats/on_character_spawn(mob/living/spawning_mob) - validate_stats(spawning_mob) + validate_stats() -/datum/preference_middleware/stats/proc/validate_stats(mob/checked_mob) +/datum/preference_middleware/stats/proc/validate_stats() var/stat_error = get_stat_validation_error() if(!stat_error) + // message_admins("[key_name(preferences.parent?.ckey)]'s character '[char_name]' (slot [preferences.default_slot]): VALID") return + var/char_name = preferences.read_preference(/datum/preference/name/real_name) // to_chat(checked_mob, span_warning(stat_error)) - var/char_name = preferences?.read_preference(/datum/preference/name/real_name) - var/log_msg = "[key_name(checked_mob)]'s character '[char_name]' (slot [preferences?.default_slot]) has invalid stats:\n[stat_error]" + var/log_msg = "[key_name(preferences.parent?.ckey)]'s character '[char_name]' (slot [preferences.default_slot]) has invalid stats:\n[stat_error]" // SSoverwatch.record_action(null, log_msg) message_admins(log_msg) -ADMIN_VERB(validate_sheets, R_ADMIN, "Validate Sheets", "Validate Sheets.", ADMIN_CATEGORY_MAIN) - var/choice = tgui_alert(user, "Validate unloaded characters as well?", "Validate Sheets", list("Yes", "No")) - // var/list/connected = list() +ADMIN_VERB(validate_sheets, R_ADMIN, "Validate Sheets", "Validate Sheets.", ADMIN_CATEGORY_SECOND_CITY) + var/choice = tgui_alert(user, "Validate unloaded characters as well?", "Validate Sheets", list("Yes", "Including Offline", "No")) + var/list/connected = list() // var/list/invalid_ckeys = list() for(var/ckey in GLOB.directory) var/client/C = GLOB.directory[ckey] if(!C || !C.mob || !C.prefs) continue - // connected += ckey + connected += ckey /* if(ishuman(C.mob)) var/list/validation = validate_mob_sheet(C.mob) if(validation && !validation["valid"]) invalid_ckeys += ckey */ - if(choice == "Yes") + if(choice != "No") var/original_slot = C.prefs.default_slot var/list/profiles = C.prefs.create_character_profiles() // Helps filter for unfilled out slots for(var/i in 1 to C.prefs.max_save_slots) @@ -102,8 +103,34 @@ ADMIN_VERB(validate_sheets, R_ADMIN, "Validate Sheets", "Validate Sheets.", ADMI var/slot = clamp(i, 1, C.prefs.max_save_slots) C.prefs.switch_to_slot(slot) for(var/datum/preference_middleware/stats/stat in C.prefs.middleware) - stat.validate_stats(C.mob) + stat.validate_stats() C.prefs.switch_to_slot(original_slot) else for(var/datum/preference_middleware/stats/stat in C.prefs.middleware) - stat.validate_stats(C.mob) + stat.validate_stats() + if(choice != "Including Offline") + return + for(var/initial in flist("data/player_saves/")) + initial = replacetext(initial, "/", "") + for(var/search_ckey in flist("data/player_saves/[initial]/")) + search_ckey = replacetext(search_ckey, "/", "") + var/prefs_path = "data/player_saves/[initial]/[search_ckey]/preferences.json" + if(!fexists(prefs_path)) + continue + + if(search_ckey in connected) + continue + + var/datum/client_interface/mock = new + mock.ckey = search_ckey + mock.key = search_ckey + var/datum/preferences/offline_prefs = new(mock) + var/original_slot = offline_prefs.default_slot + var/list/profiles = offline_prefs.create_character_profiles() // Helps filter for unfilled out slots + for(var/i in 1 to offline_prefs.max_save_slots) + if(profiles[i]) + var/slot = clamp(i, 1, offline_prefs.max_save_slots) + offline_prefs.switch_to_slot(slot) + for(var/datum/preference_middleware/stats/stat in offline_prefs.middleware) + stat.validate_stats() + offline_prefs.switch_to_slot(original_slot)