From dcda1373975ba6b89a6fb115afbb363deb09e366 Mon Sep 17 00:00:00 2001 From: GuardianDll Date: Thu, 19 Sep 2024 00:48:53 +0200 Subject: [PATCH] implement .22 perk --- .../abomination_weakpoints.json | 1 + .../arthropod_weakpoints.json | 2 ++ .../monster_weakpoints/cyborg_weakpoints.json | 1 + .../generic_weakpoints.json | 3 +++ .../humanoid_weakpoints.json | 4 ++++ .../monster_weakpoints/slime_weakpoints.json | 1 + .../triffid_weakpoints.json | 1 + .../monster_weakpoints/yrax_weakpoints.json | 2 ++ data/mods/BombasticPerks/perkmenu.json | 16 ++++++++++++++ data/mods/BombasticPerks/perks.json | 16 ++++++++++++++ doc/EFFECT_ON_CONDITION.md | 17 +++++++++++++++ doc/MAGIC.md | 2 ++ doc/MONSTERS.md | 1 + src/condition.cpp | 13 ++++++++++++ src/magic_enchantment.cpp | 2 ++ src/magic_enchantment.h | 2 ++ src/ranged.cpp | 2 ++ src/talker.h | 3 +++ src/talker_character.cpp | 21 +++++++++++++++++++ src/talker_character.h | 1 + src/weakpoint.cpp | 15 +++++++++++-- src/weakpoint.h | 2 ++ 22 files changed, 126 insertions(+), 2 deletions(-) diff --git a/data/json/monster_weakpoints/abomination_weakpoints.json b/data/json/monster_weakpoints/abomination_weakpoints.json index 4183596bd9f1b..d00e9588274ff 100644 --- a/data/json/monster_weakpoints/abomination_weakpoints.json +++ b/data/json/monster_weakpoints/abomination_weakpoints.json @@ -172,6 +172,7 @@ { "id": "armor_thick", "name": "a thick part of its armor", + "is_good": false, "armor_mult": { "all": 2.0 }, "crit_mult": { "all": 0.75 }, "coverage_mult": { "cut": 1.5 }, diff --git a/data/json/monster_weakpoints/arthropod_weakpoints.json b/data/json/monster_weakpoints/arthropod_weakpoints.json index b102205dee857..6078c58fb3cf0 100644 --- a/data/json/monster_weakpoints/arthropod_weakpoints.json +++ b/data/json/monster_weakpoints/arthropod_weakpoints.json @@ -73,6 +73,7 @@ { "id": "armor_thick", "name": "a thick part of the exoskeleton", + "is_good": false, "armor_mult": { "all": 1.5 }, "crit_mult": { "all": 0.75 }, "coverage_mult": { "cut": 1.5 }, @@ -329,6 +330,7 @@ { "id": "armor_thick", "name": "a thick part of its armor", + "is_good": false, "armor_mult": { "all": 1.5 }, "crit_mult": { "all": 0.75 }, "coverage_mult": { "cut": 1.5 }, diff --git a/data/json/monster_weakpoints/cyborg_weakpoints.json b/data/json/monster_weakpoints/cyborg_weakpoints.json index 8598f59cd1e7a..8f9ef9a814c66 100644 --- a/data/json/monster_weakpoints/cyborg_weakpoints.json +++ b/data/json/monster_weakpoints/cyborg_weakpoints.json @@ -22,6 +22,7 @@ { "id": "hard_point", "name": "a particularly thick patch of armor", + "is_good": false, "armor_mult": { "all": 1.25 }, "crit_mult": { "all": 0.75 }, "coverage_mult": { "melee": 0.75 }, diff --git a/data/json/monster_weakpoints/generic_weakpoints.json b/data/json/monster_weakpoints/generic_weakpoints.json index 09a393dc58529..787611b411f40 100644 --- a/data/json/monster_weakpoints/generic_weakpoints.json +++ b/data/json/monster_weakpoints/generic_weakpoints.json @@ -22,6 +22,7 @@ { "id": "hard_hide", "name": "a particularly thick patch of hide", + "is_good": false, "armor_mult": { "all": 1.25 }, "crit_mult": { "all": 0.75 }, "coverage_mult": { "melee": 0.75 }, @@ -52,6 +53,7 @@ { "id": "hard_point", "name": "a particularly thick patch of bone", + "is_good": false, "armor_mult": { "all": 1.25 }, "crit_mult": { "all": 0.75 }, "coverage_mult": { "melee": 0.75 }, @@ -83,6 +85,7 @@ { "id": "hard_point", "name": "a strong, rigid chunk of metal", + "is_good": false, "armor_mult": { "all": 1.4 }, "crit_mult": { "all": 0.75 }, "coverage_mult": { "melee": 0.75 }, diff --git a/data/json/monster_weakpoints/humanoid_weakpoints.json b/data/json/monster_weakpoints/humanoid_weakpoints.json index e9317feba4942..cf7c3102dc2f4 100644 --- a/data/json/monster_weakpoints/humanoid_weakpoints.json +++ b/data/json/monster_weakpoints/humanoid_weakpoints.json @@ -283,6 +283,7 @@ { "id": "armour_plate_bullet", "name": "the body armor", + "is_good": false, "armor_mult": { "cut": 1.5, "stab": 1.5, "bash": 1.5 }, "coverage_mult": { "ranged": 0.8, "melee": 0 }, "//": "Note that most bullet hits on an armour plate probably shatter it, but not all of them shatter it enough to matter for future attacks.", @@ -315,6 +316,7 @@ { "id": "armour_plate_melee", "name": "the body armor", + "is_good": false, "armor_mult": { "cut": 1.3, "stab": 1.15, "bash": 1.15 }, "coverage_mult": { "ranged": 0, "melee": 0.8 }, "//": "Note that most bullet hits on an armour plate probably shatter it, but not all of them shatter it enough to matter for future attacks.", @@ -446,6 +448,7 @@ "id": "helmet", "//": "the hard part of the helmet is actually the opposite of a weakpoint.", "name": "the helmet", + "is_good": false, "armor_mult": { "physical": 1.5 }, "crit_mult": { "all": 0.5 }, "coverage_mult": { "melee": 0.75 }, @@ -520,6 +523,7 @@ "id": "helmet", "name": "the helmet", "//": "the hard part of the helmet is actually the opposite of a weakpoint.", + "is_good": false, "armor_mult": { "physical": 1.5 }, "crit_mult": { "all": 0.5 }, "coverage_mult": { "melee": 0.75 }, diff --git a/data/json/monster_weakpoints/slime_weakpoints.json b/data/json/monster_weakpoints/slime_weakpoints.json index 8226ae9e6fe0f..67a42bed8da22 100644 --- a/data/json/monster_weakpoints/slime_weakpoints.json +++ b/data/json/monster_weakpoints/slime_weakpoints.json @@ -23,6 +23,7 @@ { "id": "insensitive_spot", "name": "a spot with virtually nothing to damage", + "is_good": false, "armor_mult": { "all": 1.25 }, "crit_mult": { "all": 0.75 }, "coverage_mult": { "melee": 0.75 }, diff --git a/data/json/monster_weakpoints/triffid_weakpoints.json b/data/json/monster_weakpoints/triffid_weakpoints.json index baa865e108e72..fa3680678dfd2 100644 --- a/data/json/monster_weakpoints/triffid_weakpoints.json +++ b/data/json/monster_weakpoints/triffid_weakpoints.json @@ -83,6 +83,7 @@ { "id": "thick bark", "name": "a particularly thick patch of the bark", + "is_good": false, "armor_mult": { "all": 1.25 }, "crit_mult": { "all": 0.75 }, "coverage_mult": { "melee": 0.75 }, diff --git a/data/json/monster_weakpoints/yrax_weakpoints.json b/data/json/monster_weakpoints/yrax_weakpoints.json index a68bea1f19032..b6cab46fb65b1 100644 --- a/data/json/monster_weakpoints/yrax_weakpoints.json +++ b/data/json/monster_weakpoints/yrax_weakpoints.json @@ -21,6 +21,7 @@ { "id": "heavy_armor", "name": "a segment of sloped armor", + "is_good": false, "armor_mult": { "all": 1 }, "crit_mult": { "all": 0.1 }, "coverage_mult": { "melee": 1.5 }, @@ -51,6 +52,7 @@ { "id": "heavy_armor", "name": "a segment of sloped armor", + "is_good": false, "armor_mult": { "all": 5 }, "crit_mult": { "all": 0.1 }, "coverage_mult": { "melee": 0.1 }, diff --git a/data/mods/BombasticPerks/perkmenu.json b/data/mods/BombasticPerks/perkmenu.json index 7eece1ede3517..8306c15e3db65 100644 --- a/data/mods/BombasticPerks/perkmenu.json +++ b/data/mods/BombasticPerks/perkmenu.json @@ -1380,6 +1380,22 @@ ], "topic": "TALK_PERK_MENU_SELECT_PLAYSTYLE" }, + { + "condition": { "not": { "u_has_trait": "22_is_cool" } }, + "text": "Gain []", + "effect": [ + { "set_string_var": "", "target_var": { "context_val": "trait_name" } }, + { "set_string_var": "", "target_var": { "context_val": "trait_description" } }, + { "set_string_var": "22_is_cool", "target_var": { "context_val": "trait_id" } }, + { + "set_string_var": "No Requirements", + "target_var": { "context_val": "trait_requirement_description" }, + "i18n": true + }, + { "set_condition": "perk_condition", "condition": { "math": [ "0", "==", "0" ] } } + ], + "topic": "TALK_PERK_MENU_SELECT_PLAYSTYLE" + }, { "condition": { "not": { "u_has_trait": "perk_empath" } }, "text": "Gain []", diff --git a/data/mods/BombasticPerks/perks.json b/data/mods/BombasticPerks/perks.json index 235529038d830..1f45eddf381a9 100644 --- a/data/mods/BombasticPerks/perks.json +++ b/data/mods/BombasticPerks/perks.json @@ -1198,5 +1198,21 @@ ] } ] + }, + { + "type": "mutation", + "id": "22_is_cool", + "name": { "str": "Twenty two weakpoints" }, + "description": "You always appreciated the size of this little fella named \"Dot Two Two Long Rifle\", but they laughed at you. Not anymore! You found that small size of a bullet is perfect to hit into little spots between armor or narrow places. Gives additional armor penetration and quadruple your chanses to hit a weakpoint if you use .22 weapon.", + "points": 0, + "purifiable": false, + "valid": false, + "category": [ "perk" ], + "enchantments": [ + { + "condition": { "u_has_wielded_with_ammotype": "22" }, + "values": [ { "value": "RANGED_ARMOR_PENETRATION", "add": 5 }, { "value": "WEAKPOINT_ACCURACY", "multiply": 3 } ] + } + ] } ] diff --git a/doc/EFFECT_ON_CONDITION.md b/doc/EFFECT_ON_CONDITION.md index 66d27a3c46a5d..b83163b8ce6e6 100644 --- a/doc/EFFECT_ON_CONDITION.md +++ b/doc/EFFECT_ON_CONDITION.md @@ -979,6 +979,23 @@ check do you wield a gun with `pistol` skill { "u_has_wielded_with_skill": "pistol" } ``` +### `u_has_wielded_with_ammotype`, `npc_has_wielded_with_ammotype` +- type: string or [variable object](#variable-object) +- return true if alpha or beta talker wield an item that can have this ammo type +- works with items that allow multiple ammo types + +#### Valid talkers: + +| Avatar | Character | NPC | Monster | Furniture | Item | +| ------ | --------- | --------- | ---- | ------- | --- | +| ✔️ | ✔️ | ✔️ | ❌ | ❌ | ❌ | + +#### Examples +check do you wield a gun with `22` ammo type (.22 LR) +```json +{ "u_has_wielded_with_ammotype": "22" } +``` + ### `u_can_see`, `npc_can_see` - type: simple string - return true if alpha or beta talker can see (not blind) diff --git a/doc/MAGIC.md b/doc/MAGIC.md index b7ef93022ad1d..3102813359376 100644 --- a/doc/MAGIC.md +++ b/doc/MAGIC.md @@ -878,6 +878,7 @@ Character status value | Description `PHASE_DISTANCE` | Distance the character is able to teleport through impassible terrain. Values less than 1 do nothing and the max distance is 48 tiles. `POWER_TRICKLE` | Generates this amount of millijoules each second. Default value is zero, so better to use `add` `RANGE` | Modifies your characters range with firearms +`RANGED_ARMOR_PENETRATION` | Adds armor penetration to ranged attacks. `RANGED_DAMAGE` | Adds damage to ranged attacks. `RANGE_DODGE` | Chance to dodge projectile attack, no matter of it's speed; Consumes dodges similarly to melee dodges, and fails, if character has no dodges left. `add` and `multiply` behave equally. `add: 0.5` would result in 50% chance to avoid projectile `READING_EXP` | Changes the minimum you learn from each reading increment. @@ -912,6 +913,7 @@ Character status value | Description `VITAMIN_ABSORB_MOD` | Increases amount of vitamins obtained from the food `VOMIT_MUL` | Affects your chances to vomit. `WEAKNESS_TO_WATER` | Amount of damage character gets when wet, once per second; scales with wetness, being 50% wet deal only half of damage; negative values restore hp; flat number with default value of 0, so `multiply` is useful only in combination with `add`; Works with float numbers, so `"add": -0.3` would result in restoring 1 hp with 30% change, and 70% chance to do nothing +`WEAKPOINT_ACCURACY` | Increases the coverage of every weakpoint you hit, therefore, increasing chances to hit said weakpoint. Works only if weakpoint has `"is_good": true` (all weakpoints have it true by default) `WEAPON_DISPERSION` | Positive value increase the dispersion, negative decrease one. diff --git a/doc/MONSTERS.md b/doc/MONSTERS.md index b686f5243f5f7..2ebd973ae3312 100644 --- a/doc/MONSTERS.md +++ b/doc/MONSTERS.md @@ -346,6 +346,7 @@ Field | Description `id` | id of the weakpoint. Defaults to `name`, if not specified. `name` | name of the weakpoint. Used in hit messages. `coverage` | base percentage chance of hitting the weakpoint. (e.g. A coverage of 5 means a 5% base chance of hitting the weakpoint) +`is_good` | marks mutation, that is beneficial for you to hit (like headshot); false means it is a bad weakpoint for you to hit (like thick piece of armor); default true; `coverage_mult` | object mapping weapon types to constant coverage multipliers. `difficulty` | object mapping weapon types to difficulty values. Difficulty acts as soft "gate" on the attacker's skill. If the the attacker has skill equal to the difficulty, coverage is reduced to 50%. `armor_mult` | object mapping damage types to multipliers on the monster's base protection, when hitting the weakpoint. diff --git a/src/condition.cpp b/src/condition.cpp index 576da19339e17..6da35cac5d16f 100644 --- a/src/condition.cpp +++ b/src/condition.cpp @@ -1946,6 +1946,8 @@ conditional_t::func f_has_wielded_with_weapon_category( const JsonObject &jo, conditional_t::func f_has_wielded_with_skill( const JsonObject &jo, std::string_view member, bool is_npc ) { + // ideally all this "u wield with X" should be moved to some mutator + // and a single effect should check mutator applied to the item in your hands str_or_var w_skill = get_str_or_var( jo.get_member( member ), member, true ); return [w_skill, is_npc]( dialogue const & d ) { @@ -1953,6 +1955,16 @@ conditional_t::func f_has_wielded_with_skill( const JsonObject &jo, std::string_ }; } +conditional_t::func f_has_wielded_with_ammotype( const JsonObject &jo, std::string_view member, + bool is_npc ) +{ + str_or_var w_ammotype = get_str_or_var( jo.get_member( member ), member, true ); + return [w_ammotype, is_npc]( dialogue const & d ) { + + return d.actor( is_npc )->wielded_with_item_ammotype( ammotype( w_ammotype.evaluate( d ) ) ); + }; +} + conditional_t::func f_can_see( bool is_npc ) { return [is_npc]( dialogue const & d ) { @@ -2542,6 +2554,7 @@ parsers = { {"u_has_wielded_with_flag", "npc_has_wielded_with_flag", jarg::member, &conditional_fun::f_has_wielded_with_flag }, {"u_has_wielded_with_weapon_category", "npc_has_wielded_with_weapon_category", jarg::member, &conditional_fun::f_has_wielded_with_weapon_category }, {"u_has_wielded_with_skill", "npc_has_wielded_with_skill", jarg::member, &conditional_fun::f_has_wielded_with_skill }, + {"u_has_wielded_with_ammotype", "npc_has_wielded_with_ammotype", jarg::member, &conditional_fun::f_has_wielded_with_ammotype }, {"u_is_on_terrain", "npc_is_on_terrain", jarg::member, &conditional_fun::f_is_on_terrain }, {"u_is_on_terrain_with_flag", "npc_is_on_terrain_with_flag", jarg::member, &conditional_fun::f_is_on_terrain_with_flag }, {"u_is_in_field", "npc_is_in_field", jarg::member, &conditional_fun::f_is_in_field }, diff --git a/src/magic_enchantment.cpp b/src/magic_enchantment.cpp index cc158590c3762..3fda33478a7a1 100644 --- a/src/magic_enchantment.cpp +++ b/src/magic_enchantment.cpp @@ -90,6 +90,7 @@ namespace io case enchant_vals::mod::PAIN_PENALTY_MOD_SPEED: return "PAIN_PENALTY_MOD_SPEED"; case enchant_vals::mod::MELEE_DAMAGE: return "MELEE_DAMAGE"; case enchant_vals::mod::RANGED_DAMAGE: return "RANGED_DAMAGE"; + case enchant_vals::mod::RANGED_ARMOR_PENETRATION: return "RANGED_ARMOR_PENETRATION"; case enchant_vals::mod::DODGE_CHANCE: return "DODGE_CHANCE"; case enchant_vals::mod::BONUS_BLOCK: return "BONUS_BLOCK"; case enchant_vals::mod::BONUS_DODGE: return "BONUS_DODGE"; @@ -202,6 +203,7 @@ namespace io case enchant_vals::mod::SWEAT_MULTIPLIER: return "SWEAT_MULTIPLIER"; case enchant_vals::mod::STAMINA_REGEN_MOD: return "STAMINA_REGEN_MOD"; case enchant_vals::mod::MOVEMENT_EXERTION_MODIFIER: return "MOVEMENT_EXERTION_MODIFIER"; + case enchant_vals::mod::WEAKPOINT_ACCURACY: return "WEAKPOINT_ACCURACY"; case enchant_vals::mod::NUM_MOD: break; } cata_fatal( "Invalid enchant_vals::mod" ); diff --git a/src/magic_enchantment.h b/src/magic_enchantment.h index 484a2ccf5f362..9a7e56b9f523a 100644 --- a/src/magic_enchantment.h +++ b/src/magic_enchantment.h @@ -69,6 +69,7 @@ enum class mod : int { BONUS_BLOCK, MELEE_DAMAGE, RANGED_DAMAGE, + RANGED_ARMOR_PENETRATION, ATTACK_NOISE, SHOUT_NOISE, FOOTSTEP_NOISE, @@ -179,6 +180,7 @@ enum class mod : int { SWEAT_MULTIPLIER, STAMINA_REGEN_MOD, MOVEMENT_EXERTION_MODIFIER, + WEAKPOINT_ACCURACY, NUM_MOD }; } // namespace enchant_vals diff --git a/src/ranged.cpp b/src/ranged.cpp index 28a5152f6c24a..a840f05af651b 100644 --- a/src/ranged.cpp +++ b/src/ranged.cpp @@ -1049,6 +1049,8 @@ int Character::fire_gun( const tripoint &target, int shots, item &gun, item_loca for( damage_unit &elem : proj.impact.damage_units ) { elem.amount = enchantment_cache->modify_value( enchant_vals::mod::RANGED_DAMAGE, elem.amount ); + elem.res_pen = enchantment_cache->modify_value( enchant_vals::mod::RANGED_ARMOR_PENETRATION, + elem.amount ); } dispersion_sources dispersion = total_gun_dispersion( gun, recoil_total(), proj.shot_spread ); diff --git a/src/talker.h b/src/talker.h index d9fcca4cb74a9..b721d6057ac96 100644 --- a/src/talker.h +++ b/src/talker.h @@ -610,6 +610,9 @@ class talker virtual bool wielded_with_weapon_skill( const skill_id & ) const { return false; } + virtual bool wielded_with_item_ammotype( const ammotype & ) const { + return false; + } virtual bool has_item_with_flag( const flag_id & ) const { return false; } diff --git a/src/talker_character.cpp b/src/talker_character.cpp index 75c063d066ba8..2121cfb5d49d9 100644 --- a/src/talker_character.cpp +++ b/src/talker_character.cpp @@ -791,6 +791,27 @@ bool talker_character_const::wielded_with_weapon_skill( const skill_id &w_skill } } +bool talker_character_const::wielded_with_item_ammotype( const ammotype &w_ammotype ) const +{ + if( !me_chr_const->get_wielded_item() ) { + return false; + } + item *it = me_chr_const->get_wielded_item().get_item(); + if( it->ammo_types().empty() ) { + return false; + } + std::set it_ammotype = it->ammo_types(); + bool match = false; + + for( ammotype ammo : it_ammotype ) { + if( ammo == w_ammotype ) { + match = true; + } + return match; + } + +} + bool talker_character_const::has_item_with_flag( const flag_id &flag ) const { return me_chr_const->cache_has_item_with( flag ); diff --git a/src/talker_character.h b/src/talker_character.h index 0a10f4a1d22e7..0eca4d44ee6b0 100644 --- a/src/talker_character.h +++ b/src/talker_character.h @@ -174,6 +174,7 @@ class talker_character_const: public talker_cloner bool wielded_with_flag( const flag_id &flag ) const override; bool wielded_with_weapon_category( const weapon_category_id &w_cat ) const override; bool wielded_with_weapon_skill( const skill_id &w_skill ) const override; + bool wielded_with_item_ammotype( const ammotype &w_ammotype ) const override; bool has_item_with_flag( const flag_id &flag ) const override; int item_rads( const flag_id &flag, aggregate_type agg_func ) const override; diff --git a/src/weakpoint.cpp b/src/weakpoint.cpp index b294bf7a7a0ca..ae5acd8889e9a 100644 --- a/src/weakpoint.cpp +++ b/src/weakpoint.cpp @@ -414,6 +414,9 @@ void weakpoint::load( const JsonObject &jo ) assign( jo, "id", id ); assign( jo, "name", name ); assign( jo, "coverage", coverage, false, 0.0f, 100.0f ); + if( jo.has_bool( "is_good" ) ) { + assign( jo, "is_good", is_good ); + } if( jo.has_object( "armor_mult" ) ) { armor_mult = load_damage_map( jo.get_object( "armor_mult" ) ); } @@ -536,8 +539,16 @@ float weakpoint::hit_chance( const weakpoint_attack &attack ) const // exceeding the difficulty. float diff = attack.wp_skill - difficulty.of( attack ); float difficulty_mult = 0.5f * ( 1.0f + erf( diff / ( 2.0f * sqrt( 2.0f ) ) ) ); - // Compute the total value - return constant_mult * difficulty_mult * coverage; + float final_coverage; + + if( attack.source && attack.source->as_character() && is_good ) { + final_coverage = attack.source->as_character()->enchantment_cache->modify_value( + enchant_vals::mod::WEAKPOINT_ACCURACY, coverage ); + } else { + final_coverage = coverage; + } + + return constant_mult * difficulty_mult * final_coverage; } // Reweighs the probability distribution of hitting a weakpoint. diff --git a/src/weakpoint.h b/src/weakpoint.h index 091b2a75ee5f5..2fad8f2cd60a5 100644 --- a/src/weakpoint.h +++ b/src/weakpoint.h @@ -127,6 +127,8 @@ struct weakpoint { translation name; // Percent chance of hitting the weakpoint. Can be increased by skill. float coverage = 100.0f; + // Separate wp that benefit attacker and hurt monster from wp that do not + bool is_good = true; // Multiplier for existing armor values. Defaults to 1. std::unordered_map armor_mult; // Flat penalty to armor values. Applied after the multiplier.