From 8fd728651dd039258f5c68383e6a1c5e72726375 Mon Sep 17 00:00:00 2001 From: Kevin Granade Date: Mon, 22 Sep 2025 10:31:41 -0700 Subject: [PATCH] Add an external option to enforce stat caps and set it for core game Also for MoM --- data/core/external_options.json | 28 +++++++++++++++++++++++++++ data/mods/Magiclysm/modinfo.json | 28 +++++++++++++++++++++++++++ data/mods/MindOverMatter/modinfo.json | 21 ++++++++++++++++++++ data/mods/TEST_DATA/enchantments.json | 2 +- data/mods/TEST_DATA/items.json | 2 +- data/mods/Xedra_Evolved/modinfo.json | 28 +++++++++++++++++++++++++++ src/character.cpp | 21 ++++++++++++-------- src/character.h | 5 +++++ src/options.cpp | 15 ++++++++++++++ tests/enchantments_test.cpp | 6 +++--- 10 files changed, 143 insertions(+), 13 deletions(-) diff --git a/data/core/external_options.json b/data/core/external_options.json index df0ac3bd6b7c3..d2d976b31efd3 100644 --- a/data/core/external_options.json +++ b/data/core/external_options.json @@ -20,6 +20,34 @@ "stype": "int", "value": 3500 }, + { + "type": "EXTERNAL_OPTION", + "name": "PLAYER_MAX_STR_VALUE", + "//": "Sets a cap on maximum effective strength for characters.", + "stype": "int", + "value": 20 + }, + { + "type": "EXTERNAL_OPTION", + "name": "PLAYER_MAX_DEX_VALUE", + "//": "Sets a cap on maximum effective dexterity for characters.", + "stype": "int", + "value": 20 + }, + { + "type": "EXTERNAL_OPTION", + "name": "PLAYER_MAX_PER_VALUE", + "//": "Sets a cap on maximum effective perception for characters.", + "stype": "int", + "value": 20 + }, + { + "type": "EXTERNAL_OPTION", + "name": "PLAYER_MAX_INT_VALUE", + "//": "Sets a cap on maximum effective intelligence for characters.", + "stype": "int", + "value": 20 + }, { "type": "EXTERNAL_OPTION", "name": "PLAYER_CARDIOFIT_STAMINA_SCALING", diff --git a/data/mods/Magiclysm/modinfo.json b/data/mods/Magiclysm/modinfo.json index 121b3a43b6b51..f3f57de43d094 100644 --- a/data/mods/Magiclysm/modinfo.json +++ b/data/mods/Magiclysm/modinfo.json @@ -16,5 +16,33 @@ "display_category": "display_ranged", "sort_rank": 14500, "description": "Your skill in the arcane. Represents magic theory and all that entails. A higher skill increases how quickly you can learn spells, and decreases their spell failure chance. You learn this skill by studying books or spells." + }, + { + "type": "EXTERNAL_OPTION", + "name": "PLAYER_MAX_STR_VALUE", + "//": "Sets a cap on maximum effective strength for characters.", + "stype": "int", + "value": 35 + }, + { + "type": "EXTERNAL_OPTION", + "name": "PLAYER_MAX_DEX_VALUE", + "//": "Sets a cap on maximum effective dexterity for characters.", + "stype": "int", + "value": 35 + }, + { + "type": "EXTERNAL_OPTION", + "name": "PLAYER_MAX_PER_VALUE", + "//": "Sets a cap on maximum effective perception for characters.", + "stype": "int", + "value": 35 + }, + { + "type": "EXTERNAL_OPTION", + "name": "PLAYER_MAX_INT_VALUE", + "//": "Sets a cap on maximum effective inteligence for characters.", + "stype": "int", + "value": 35 } ] diff --git a/data/mods/MindOverMatter/modinfo.json b/data/mods/MindOverMatter/modinfo.json index 19441db876d95..617765fe50f02 100644 --- a/data/mods/MindOverMatter/modinfo.json +++ b/data/mods/MindOverMatter/modinfo.json @@ -9,5 +9,26 @@ "category": "content", "dependencies": [ "dda" ], "conflicts": [ "aftershock_exoplanet" ] + }, + { + "type": "EXTERNAL_OPTION", + "name": "PLAYER_MAX_STR_VALUE", + "//": "Sets a cap on maximum effective strength for characters.", + "stype": "int", + "value": 35 + }, + { + "type": "EXTERNAL_OPTION", + "name": "PLAYER_MAX_DEX_VALUE", + "//": "Sets a cap on maximum effective dexterity for characters.", + "stype": "int", + "value": 35 + }, + { + "type": "EXTERNAL_OPTION", + "name": "PLAYER_MAX_PER_VALUE", + "//": "Sets a cap on maximum effective perception for characters.", + "stype": "int", + "value": 35 } ] diff --git a/data/mods/TEST_DATA/enchantments.json b/data/mods/TEST_DATA/enchantments.json index 09444d0a8f406..8432f955a3ae6 100644 --- a/data/mods/TEST_DATA/enchantments.json +++ b/data/mods/TEST_DATA/enchantments.json @@ -5,7 +5,7 @@ "condition": "ALWAYS", "emitter": "emit_shadow_field", "values": [ - { "value": "DEXTERITY", "multiply": 2, "add": 25 }, + { "value": "DEXTERITY", "multiply": 1, "add": 1 }, { "value": "SOCIAL_LIE", "multiply": 0.5, "add": 15 }, { "value": "SOCIAL_PERSUADE", "multiply": 0.5, "add": 15 }, { "value": "SOCIAL_INTIMIDATE", "multiply": 0.5, "add": 1 } diff --git a/data/mods/TEST_DATA/items.json b/data/mods/TEST_DATA/items.json index b0c2266dce3e6..8774ba88cce42 100644 --- a/data/mods/TEST_DATA/items.json +++ b/data/mods/TEST_DATA/items.json @@ -5245,7 +5245,7 @@ "passive_effects": [ { "values": [ - { "value": "STRENGTH", "add": 3, "multiply": 1 }, + { "value": "STRENGTH", "add": 2, "multiply": 0.25 }, { "value": "DEXTERITY", "add": -2 }, { "value": "INTELLIGENCE", "add": 1, "multiply": -0.5 }, { "value": "PERCEPTION", "add": -7 } diff --git a/data/mods/Xedra_Evolved/modinfo.json b/data/mods/Xedra_Evolved/modinfo.json index fcb694120ea99..ead1a2f3c3ad4 100644 --- a/data/mods/Xedra_Evolved/modinfo.json +++ b/data/mods/Xedra_Evolved/modinfo.json @@ -9,5 +9,33 @@ "category": "content", "dependencies": [ "dda" ], "loading_images": [ "xedra1.png", "xedra2.png", "xedra3.png", "xedra4.png", "xedra5.png" ] + }, + { + "type": "EXTERNAL_OPTION", + "name": "PLAYER_MAX_STR_VALUE", + "//": "Sets a cap on maximum effective strength for characters.", + "stype": "int", + "value": 35 + }, + { + "type": "EXTERNAL_OPTION", + "name": "PLAYER_MAX_DEX_VALUE", + "//": "Sets a cap on maximum effective dexterity for characters.", + "stype": "int", + "value": 35 + }, + { + "type": "EXTERNAL_OPTION", + "name": "PLAYER_MAX_PER_VALUE", + "//": "Sets a cap on maximum effective perception for characters.", + "stype": "int", + "value": 35 + }, + { + "type": "EXTERNAL_OPTION", + "name": "PLAYER_MAX_INT_VALUE", + "//": "Sets a cap on maximum effective inteligence for characters.", + "stype": "int", + "value": 35 } ] diff --git a/src/character.cpp b/src/character.cpp index 91c0e3f688f66..ef737477828e2 100644 --- a/src/character.cpp +++ b/src/character.cpp @@ -513,6 +513,11 @@ static const std::string type_skin_tone( "skin_tone" ); static const std::string type_facial_hair( "facial_hair" ); static const std::string type_eye_color( "eye_color" ); +int character_max_str = 20; +int character_max_dex = 20; +int character_max_per = 20; +int character_max_int = 20; + namespace io { @@ -4330,36 +4335,36 @@ body_part_set Character::exclusive_flag_coverage( const flag_id &flag ) const // get_stat_bonus() is always just the bonus amount int Character::get_str() const { - return std::max( 0, get_str_base() + str_bonus ); + return std::min( character_max_str, std::max( 0, get_str_base() + str_bonus ) ); } int Character::get_dex() const { - return std::max( 0, get_dex_base() + dex_bonus ); + return std::min( character_max_dex, std::max( 0, get_dex_base() + dex_bonus ) ); } int Character::get_per() const { - return std::max( 0, get_per_base() + per_bonus ); + return std::min( character_max_per, std::max( 0, get_per_base() + per_bonus ) ); } int Character::get_int() const { - return std::max( 0, get_int_base() + int_bonus ); + return std::min( character_max_int, std::max( 0, get_int_base() + int_bonus ) ); } int Character::get_str_base() const { - return str_max; + return std::min( character_max_str, str_max ); } int Character::get_dex_base() const { - return dex_max; + return std::min( character_max_dex, dex_max ); } int Character::get_per_base() const { - return per_max; + return std::min( character_max_per, per_max ); } int Character::get_int_base() const { - return int_max; + return std::min( character_max_int, int_max ); } int Character::get_str_bonus() const diff --git a/src/character.h b/src/character.h index fc5459c83d7c8..fac34c39909aa 100644 --- a/src/character.h +++ b/src/character.h @@ -123,6 +123,11 @@ template struct enum_traits; using bionic_uid = unsigned int; +extern int character_max_str; +extern int character_max_dex; +extern int character_max_per; +extern int character_max_int; + constexpr int MAX_CLAIRVOYANCE = 40; // kcal in a kilogram of fat, used to convert stored kcal into body weight. 3500kcal/lb * 2.20462lb/kg = 7716.17 constexpr float KCAL_PER_KG = 3500 * 2.20462; diff --git a/src/options.cpp b/src/options.cpp index c5e6e8beec024..9eefcd8d1c761 100644 --- a/src/options.cpp +++ b/src/options.cpp @@ -4033,6 +4033,21 @@ void options_manager::update_options_cache() trigdist = ::get_option( "CIRCLEDIST" ); use_tiles = ::get_option( "USE_TILES" ); + // Since these are external options they aren't loaded before the first time + // update_options_cache is called, so they're conditionally loaded. + if( ::has_option( "PLAYER_MAX_STR_VALUE" ) ) { + character_max_str = ::get_option( "PLAYER_MAX_STR_VALUE" ); + } + if( ::has_option( "PLAYER_MAX_DEX_VALUE" ) ) { + character_max_dex = ::get_option( "PLAYER_MAX_DEX_VALUE" ); + } + if( ::has_option( "PLAYER_MAX_PER_VALUE" ) ) { + character_max_per = ::get_option( "PLAYER_MAX_PER_VALUE" ); + } + if( ::has_option( "PLAYER_MAX_INT_VALUE" ) ) { + character_max_int = ::get_option( "PLAYER_MAX_INT_VALUE" ); + } + prevent_occlusion = ::get_option( "PREVENT_OCCLUSION" ); prevent_occlusion_retract = ::get_option( "PREVENT_OCCLUSION_RETRACT" ); prevent_occlusion_transp = ::get_option( "PREVENT_OCCLUSION_TRANSP" ); diff --git a/tests/enchantments_test.cpp b/tests/enchantments_test.cpp index a0e7813b74005..b70f25ac2ac83 100644 --- a/tests/enchantments_test.cpp +++ b/tests/enchantments_test.cpp @@ -73,7 +73,7 @@ static void test_generic_ench( avatar &p, enchant_test enc_test ) // wait a turn for the effect to kick in p.process_turn(); - CHECK( p.get_dex() == ( enc_test.dex_before + 25 ) * 3 ); + CHECK( p.get_dex() == ( enc_test.dex_before + 1 ) * 2 ); CHECK( get_talker_for( p )->trial_chance_mod( "lie" ) == static_cast( round( ( enc_test.lie_before + 15 ) * 1.5 ) ) ); CHECK( get_talker_for( p )->trial_chance_mod( "persuade" ) == static_cast( round( ( @@ -167,7 +167,7 @@ TEST_CASE( "Enchantments_change_stats", "[magic][enchantments]" ) guy.recalculate_enchantment_cache(); advance_turn( guy ); INFO( "Stats change accordingly" ); - REQUIRE( guy.get_str() == 22 ); + REQUIRE( guy.get_str() == 12 ); REQUIRE( guy.get_dex() == 6 ); REQUIRE( guy.get_int() == 5 ); REQUIRE( guy.get_per() == 1 ); @@ -191,7 +191,7 @@ TEST_CASE( "Enchantments_change_stats", "[magic][enchantments]" ) guy.recalculate_enchantment_cache(); advance_turn( guy ); INFO( "Stats change accordingly" ); - REQUIRE( guy.get_str() == 42 ); + REQUIRE( guy.get_str() == 18 ); REQUIRE( guy.get_dex() == 4 ); REQUIRE( guy.get_int() == 0 ); REQUIRE( guy.get_per() == 0 );