From 58b7e55ca463964f57e035b9b4d5681ee203b879 Mon Sep 17 00:00:00 2001 From: Chaosvolt Date: Sat, 20 May 2023 10:29:32 -0500 Subject: [PATCH 01/23] Commit what I've got for the moment --- data/json/items/armor/shields.json | 62 +++++++++++++++++++ src/character.cpp | 98 ++++++++++++++++++++++++++++++ src/character.h | 3 + src/creature.cpp | 3 +- src/creature.h | 4 ++ src/monster.cpp | 5 ++ src/monster.h | 1 + 7 files changed, 175 insertions(+), 1 deletion(-) create mode 100644 data/json/items/armor/shields.json diff --git a/data/json/items/armor/shields.json b/data/json/items/armor/shields.json new file mode 100644 index 000000000000..21b0e89dca24 --- /dev/null +++ b/data/json/items/armor/shields.json @@ -0,0 +1,62 @@ +[ + { + "id": "shield_wooden", + "type": "ARMOR", + "name": { "str": "wooden shield" }, + "description": "A crude wooden shield, lacking any metal or leather reinforcement. Lightweight but not very tough.", + "weight": "2267 g", + "volume": "3750 ml", + "price": 80000, + "to_hit": -1, + "bashing": 8, + "material": [ "wood" ], + "symbol": "[", + "color": "brown", + "covers": [ "arm_either", "hand_either" ], + "coverage": 90, + "encumbrance": 15, + "material_thickness": 2, + "techniques": [ "WBLOCK_2" ], + "flags": [ "OVERSIZE", "BELTED", "RESTRICT_HANDS", "BLOCK_WHILE_WORN" ] + }, + { + "id": "shield_wooden_large", + "type": "ARMOR", + "name": { "str": "large wooden shield" }, + "description": "An crude wooden tower shield, lacking any metal or leather reinforcement. Bulky, but offers a decent amount of protection.", + "weight": "3828 g", + "volume": "5 L", + "price": 90000, + "to_hit": -1, + "bashing": 10, + "material": [ "wood" ], + "symbol": "[", + "color": "light_gray", + "covers": [ "arm_either", "hand_either" ], + "coverage": 90, + "encumbrance": 25, + "material_thickness": 2, + "techniques": [ "WBLOCK_3" ], + "flags": [ "OVERSIZE", "BELTED", "RESTRICT_HANDS", "BLOCK_WHILE_WORN" ] + }, + { + "id": "shield_riot", + "type": "ARMOR", + "name": { "str": "riot shield" }, + "description": "A large but fairly light plastic shield, designed for riot police officers. Not too encumbering, but designed for fending off thrown rocks rather than bullets.", + "weight": "2700 g", + "volume": "5 L", + "price": 120000, + "to_hit": -1, + "bashing": 8, + "material": [ "plastic" ], + "symbol": "[", + "color": "light_gray", + "covers": [ "arm_either", "hand_either" ], + "coverage": 100, + "encumbrance": 10, + "material_thickness": 6, + "techniques": [ "WBLOCK_3" ], + "flags": [ "OVERSIZE", "BELTED", "RESTRICT_HANDS", "BLOCK_WHILE_WORN" ] + } +] diff --git a/src/character.cpp b/src/character.cpp index 176449ee5f69..25c5fe52cd8e 100644 --- a/src/character.cpp +++ b/src/character.cpp @@ -57,6 +57,7 @@ #include "mapdata.h" #include "material.h" #include "math_defines.h" +#include "martialarts.h" #include "memorial_logger.h" #include "messages.h" #include "mission.h" @@ -114,6 +115,10 @@ static const activity_id ACT_WAIT_STAMINA( "ACT_WAIT_STAMINA" ); static const bionic_id bio_eye_optic( "bio_eye_optic" ); static const bionic_id bio_watch( "bio_watch" ); +static const matec_id WBLOCK_1( "WBLOCK_1" ); +static const matec_id WBLOCK_2( "WBLOCK_2" ); +static const matec_id WBLOCK_3( "WBLOCK_3" ); + static const efftype_id effect_adrenaline( "adrenaline" ); static const efftype_id effect_ai_waiting( "ai_waiting" ); static const efftype_id effect_alarm_clock( "alarm_clock" ); @@ -10631,6 +10636,99 @@ bool Character::uncanny_dodge() return character_funcs::try_uncanny_dodge( *this ); } +bool Character::block_ranged_hit( Creature *source, bodypart_id &bp_hit, damage_instance &dam ) +{ + // Having access to more than one shield is not normal in vanilla, for now keep it simple and only give one chance to catch a bullet. + item &shield = best_shield(); + + // Bail out early just in case, if blocking with bare hands. + if( shield.is_null() ) { + return false; + } + + // Also bail out on the following conditions: + // 1. Best item available doesn't count as a shield. + // 2. Shield already protects the part we're interested in. + // 3. Targeted bodypart is a foot, unlikely to ever successfully block that low. + if( !shield.has_flag( "BLOCK_WHILE_WORN" ) || shield.covers( bp_hit->token ) || bp_hit == bodypart_str_id( "foot_l" ) || bp_hit == bodypart_str_id( "foot_r" ) ) { + return false; + } + + // Modify chance based on coverage and blocking ability. Exclude armguards here. + float shield_coverage_modifier = shield.get_coverage(); + if( shield.has_technique( WBLOCK_3 ) ) { + shield_coverage_modifier *= 0.8; + } else if( shield.has_technique( WBLOCK_2 ) ) { + shield_coverage_modifier *= 0.6; + } else if( shield.has_technique( WBLOCK_1 ) ) { + shield_coverage_modifier *= 0.4; + } else { + return false; + } + // Targeting the legs halves the chance. + if( bp_hit == bodypart_str_id( "leg_l" ) || bp_hit == bodypart_str_id( "leg_r" ) ) { + shield_coverage_modifier *= 0.5; + } + + // Now roll coverage to determine if we intercept the shot. + if( rng( 1, 100 ) > shield_coverage_modifier ) { + return false; + } + + std::string thing_blocked_with = shield.tname(); + add_msg_player_or_npc( + _( "The shot hits your %s!" ), + _( "The shot hits 's %s!" ), + thing_blocked_with ); + + float wear_modifier = 1.0f; + if( source != nullptr && source->is_hallucination() ) { + wear_modifier = 0.0f; + } + handle_melee_wear( shield, wear_modifier ); + + float total_damage = 0.0; + float damage_blocked = 0.0; + + for( auto &elem : dam.damage_units ) { + total_damage += elem.amount; + // Go through all relevant damage types and reduce by armor value if one exists. + if( elem.type == DT_BASH ) { + float previous_amount = elem.amount; + float block_amount = shield.bash_resist(); + elem.amount -= block_amount; + damage_blocked += previous_amount - elem.amount; + } else if( elem.type == DT_CUT ) { + float previous_amount = elem.amount; + float block_amount = shield.cut_resist(); + elem.amount -= block_amount; + damage_blocked += previous_amount - elem.amount; + } else if( elem.type == DT_STAB ) { + float previous_amount = elem.amount; + float block_amount = shield.stab_resist(); + elem.amount -= block_amount; + damage_blocked += previous_amount - elem.amount; + } else if( elem.type == DT_BULLET ) { + float previous_amount = elem.amount; + float block_amount = shield.bullet_resist(); + elem.amount -= block_amount; + damage_blocked += previous_amount - elem.amount; + } else if( elem.type == DT_HEAT ) { + float previous_amount = elem.amount; + float block_amount = shield.fire_resist(); + elem.amount -= block_amount; + damage_blocked += previous_amount - elem.amount; + } else if( elem.type == DT_ACID ) { + float previous_amount = elem.amount; + float block_amount = shield.acid_resist(); + elem.amount -= block_amount; + damage_blocked += previous_amount - elem.amount; + } + } + + return true; +} + float Character::fall_damage_mod() const { if( has_effect_with_flag( "EFFECT_FEATHER_FALL" ) ) { diff --git a/src/character.h b/src/character.h index 9e4814d8d374..ab7b3c81212f 100644 --- a/src/character.h +++ b/src/character.h @@ -584,6 +584,9 @@ class Character : public Creature, public visitable bool uncanny_dodge() override; + /** Checks for chance that a ranged attack will hit other armor along the way */ + bool block_ranged_hit( Creature *source, bodypart_id &bp_hit, damage_instance &dam ) override; + // melee.cpp /** Checks for valid block abilities and reduces damage accordingly. Returns true if the player blocks */ bool block_hit( Creature *source, bodypart_id &bp_hit, damage_instance &dam ) override; diff --git a/src/creature.cpp b/src/creature.cpp index a05c1542ff4e..cd9bd9547983 100644 --- a/src/creature.cpp +++ b/src/creature.cpp @@ -782,7 +782,8 @@ void Creature::deal_projectile_attack( Creature *source, dealt_projectile_attack impact.mult_damage( 1.0f / dmg_ratio ); } } - + // If we have a shield, it might passively block ranged impacts + block_ranged_hit( source, bp_hit, impact ); dealt_dam = deal_damage( source, bp_hit, impact ); dealt_dam.bp_hit = bp_hit->token; diff --git a/src/creature.h b/src/creature.h index cb9c09db863e..d85c1789bb91 100644 --- a/src/creature.h +++ b/src/creature.h @@ -239,6 +239,10 @@ class Creature virtual bool block_hit( Creature *source, bodypart_id &bp_hit, damage_instance &dam ) = 0; + // handles interaction of shields and ranged attacks. mutates &dam + virtual bool block_ranged_hit( Creature *source, bodypart_id &bp_hit, + damage_instance &dam ) = 0; + // handles armor absorption (including clothing damage etc) // of damage instance. mutates &dam virtual void absorb_hit( const bodypart_id &bp, damage_instance &dam ) = 0; diff --git a/src/monster.cpp b/src/monster.cpp index 0a779fe1c782..e00f4b4b750c 100644 --- a/src/monster.cpp +++ b/src/monster.cpp @@ -1378,6 +1378,11 @@ bool monster::block_hit( Creature *, bodypart_id &, damage_instance & ) return false; } +bool monster::block_ranged_hit( Creature *, bodypart_id &, damage_instance & ) +{ + return false; +} + void monster::absorb_hit( const bodypart_id &, damage_instance &dam ) { for( auto &elem : dam.damage_units ) { diff --git a/src/monster.h b/src/monster.h index a2fe2a9ea77b..511aa12bf33c 100644 --- a/src/monster.h +++ b/src/monster.h @@ -314,6 +314,7 @@ class monster : public Creature, public visitable void absorb_hit( const bodypart_id &bp, damage_instance &dam ) override; bool block_hit( Creature *source, bodypart_id &bp_hit, damage_instance &d ) override; + bool block_ranged_hit( Creature *source, bodypart_id &bp_hit, damage_instance &d ) override; void melee_attack( Creature &target ); void melee_attack( Creature &target, float accuracy ); void melee_attack( Creature &p, bool ) = delete; From ed857bba25bf7571bf1fd568771100380af2e02c Mon Sep 17 00:00:00 2001 From: Chaosvolt Date: Sat, 20 May 2023 10:47:18 -0500 Subject: [PATCH 02/23] Apply suggestions from code review Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- src/character.cpp | 3 ++- src/creature.h | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/character.cpp b/src/character.cpp index 25c5fe52cd8e..61b71576f37e 100644 --- a/src/character.cpp +++ b/src/character.cpp @@ -10650,7 +10650,8 @@ bool Character::block_ranged_hit( Creature *source, bodypart_id &bp_hit, damage_ // 1. Best item available doesn't count as a shield. // 2. Shield already protects the part we're interested in. // 3. Targeted bodypart is a foot, unlikely to ever successfully block that low. - if( !shield.has_flag( "BLOCK_WHILE_WORN" ) || shield.covers( bp_hit->token ) || bp_hit == bodypart_str_id( "foot_l" ) || bp_hit == bodypart_str_id( "foot_r" ) ) { + if( !shield.has_flag( "BLOCK_WHILE_WORN" ) || shield.covers( bp_hit->token ) || + bp_hit == bodypart_str_id( "foot_l" ) || bp_hit == bodypart_str_id( "foot_r" ) ) { return false; } diff --git a/src/creature.h b/src/creature.h index d85c1789bb91..4d9ecac480b5 100644 --- a/src/creature.h +++ b/src/creature.h @@ -241,7 +241,7 @@ class Creature // handles interaction of shields and ranged attacks. mutates &dam virtual bool block_ranged_hit( Creature *source, bodypart_id &bp_hit, - damage_instance &dam ) = 0; + damage_instance &dam ) = 0; // handles armor absorption (including clothing damage etc) // of damage instance. mutates &dam From 2922ad784b2ca4c496e70d4cddebcbed532d5386 Mon Sep 17 00:00:00 2001 From: Chaosvolt Date: Sat, 20 May 2023 11:10:10 -0500 Subject: [PATCH 03/23] Update character.cpp --- src/character.cpp | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/character.cpp b/src/character.cpp index 61b71576f37e..064beceb83a1 100644 --- a/src/character.cpp +++ b/src/character.cpp @@ -10688,42 +10688,32 @@ bool Character::block_ranged_hit( Creature *source, bodypart_id &bp_hit, damage_ } handle_melee_wear( shield, wear_modifier ); - float total_damage = 0.0; - float damage_blocked = 0.0; - for( auto &elem : dam.damage_units ) { - total_damage += elem.amount; // Go through all relevant damage types and reduce by armor value if one exists. if( elem.type == DT_BASH ) { float previous_amount = elem.amount; float block_amount = shield.bash_resist(); elem.amount -= block_amount; - damage_blocked += previous_amount - elem.amount; } else if( elem.type == DT_CUT ) { float previous_amount = elem.amount; float block_amount = shield.cut_resist(); elem.amount -= block_amount; - damage_blocked += previous_amount - elem.amount; } else if( elem.type == DT_STAB ) { float previous_amount = elem.amount; float block_amount = shield.stab_resist(); elem.amount -= block_amount; - damage_blocked += previous_amount - elem.amount; } else if( elem.type == DT_BULLET ) { float previous_amount = elem.amount; float block_amount = shield.bullet_resist(); elem.amount -= block_amount; - damage_blocked += previous_amount - elem.amount; } else if( elem.type == DT_HEAT ) { float previous_amount = elem.amount; float block_amount = shield.fire_resist(); elem.amount -= block_amount; - damage_blocked += previous_amount - elem.amount; } else if( elem.type == DT_ACID ) { float previous_amount = elem.amount; float block_amount = shield.acid_resist(); elem.amount -= block_amount; - damage_blocked += previous_amount - elem.amount; } } From 8a9ec71f0324f0100e78be77a60b2e5a73c6b2e3 Mon Sep 17 00:00:00 2001 From: Chaosvolt Date: Sat, 20 May 2023 11:36:35 -0500 Subject: [PATCH 04/23] Update character.cpp --- src/character.cpp | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/character.cpp b/src/character.cpp index 064beceb83a1..6db1f46c0112 100644 --- a/src/character.cpp +++ b/src/character.cpp @@ -10691,27 +10691,21 @@ bool Character::block_ranged_hit( Creature *source, bodypart_id &bp_hit, damage_ for( auto &elem : dam.damage_units ) { // Go through all relevant damage types and reduce by armor value if one exists. if( elem.type == DT_BASH ) { - float previous_amount = elem.amount; float block_amount = shield.bash_resist(); elem.amount -= block_amount; } else if( elem.type == DT_CUT ) { - float previous_amount = elem.amount; float block_amount = shield.cut_resist(); elem.amount -= block_amount; } else if( elem.type == DT_STAB ) { - float previous_amount = elem.amount; float block_amount = shield.stab_resist(); elem.amount -= block_amount; } else if( elem.type == DT_BULLET ) { - float previous_amount = elem.amount; float block_amount = shield.bullet_resist(); elem.amount -= block_amount; } else if( elem.type == DT_HEAT ) { - float previous_amount = elem.amount; float block_amount = shield.fire_resist(); elem.amount -= block_amount; } else if( elem.type == DT_ACID ) { - float previous_amount = elem.amount; float block_amount = shield.acid_resist(); elem.amount -= block_amount; } From 825aa2dbba6e1d8b121dd74d1042d349552cc8db Mon Sep 17 00:00:00 2001 From: Chaosvolt Date: Sat, 20 May 2023 12:51:32 -0500 Subject: [PATCH 05/23] [Eternal Screaming] --- src/character.cpp | 25 +++++++++++++++++++------ src/character.h | 2 +- src/creature.cpp | 8 ++++---- src/creature.h | 2 +- src/monster.cpp | 4 ++-- src/monster.h | 2 +- 6 files changed, 28 insertions(+), 15 deletions(-) diff --git a/src/character.cpp b/src/character.cpp index 6db1f46c0112..a7b8bcf34393 100644 --- a/src/character.cpp +++ b/src/character.cpp @@ -10636,14 +10636,14 @@ bool Character::uncanny_dodge() return character_funcs::try_uncanny_dodge( *this ); } -bool Character::block_ranged_hit( Creature *source, bodypart_id &bp_hit, damage_instance &dam ) +float Character::block_ranged_hit( Creature *source, bodypart_id &bp_hit, damage_instance &dam ) { // Having access to more than one shield is not normal in vanilla, for now keep it simple and only give one chance to catch a bullet. item &shield = best_shield(); // Bail out early just in case, if blocking with bare hands. if( shield.is_null() ) { - return false; + return 1.0f; } // Also bail out on the following conditions: @@ -10652,7 +10652,7 @@ bool Character::block_ranged_hit( Creature *source, bodypart_id &bp_hit, damage_ // 3. Targeted bodypart is a foot, unlikely to ever successfully block that low. if( !shield.has_flag( "BLOCK_WHILE_WORN" ) || shield.covers( bp_hit->token ) || bp_hit == bodypart_str_id( "foot_l" ) || bp_hit == bodypart_str_id( "foot_r" ) ) { - return false; + return 1.0f; } // Modify chance based on coverage and blocking ability. Exclude armguards here. @@ -10664,7 +10664,7 @@ bool Character::block_ranged_hit( Creature *source, bodypart_id &bp_hit, damage_ } else if( shield.has_technique( WBLOCK_1 ) ) { shield_coverage_modifier *= 0.4; } else { - return false; + return 1.0f; } // Targeting the legs halves the chance. if( bp_hit == bodypart_str_id( "leg_l" ) || bp_hit == bodypart_str_id( "leg_r" ) ) { @@ -10673,7 +10673,7 @@ bool Character::block_ranged_hit( Creature *source, bodypart_id &bp_hit, damage_ // Now roll coverage to determine if we intercept the shot. if( rng( 1, 100 ) > shield_coverage_modifier ) { - return false; + return 1.0f; } std::string thing_blocked_with = shield.tname(); @@ -10688,30 +10688,43 @@ bool Character::block_ranged_hit( Creature *source, bodypart_id &bp_hit, damage_ } handle_melee_wear( shield, wear_modifier ); + float total_damage = 0.0; + float damage_blocked = 0.0; + for( auto &elem : dam.damage_units ) { + total_damage += elem.amount; + // Go through all relevant damage types and reduce by armor value if one exists. if( elem.type == DT_BASH ) { float block_amount = shield.bash_resist(); elem.amount -= block_amount; + damage_blocked += block_amount; } else if( elem.type == DT_CUT ) { float block_amount = shield.cut_resist(); elem.amount -= block_amount; + damage_blocked += block_amount; } else if( elem.type == DT_STAB ) { float block_amount = shield.stab_resist(); elem.amount -= block_amount; + damage_blocked += block_amount; } else if( elem.type == DT_BULLET ) { float block_amount = shield.bullet_resist(); elem.amount -= block_amount; + damage_blocked += block_amount; } else if( elem.type == DT_HEAT ) { float block_amount = shield.fire_resist(); elem.amount -= block_amount; + damage_blocked += block_amount; } else if( elem.type == DT_ACID ) { float block_amount = shield.acid_resist(); elem.amount -= block_amount; + damage_blocked += block_amount; } } - return true; + float block_result = ( total_damage - damage_blocked ) / total_damage; + + return block_result; } float Character::fall_damage_mod() const diff --git a/src/character.h b/src/character.h index ab7b3c81212f..e40117f5ceda 100644 --- a/src/character.h +++ b/src/character.h @@ -585,7 +585,7 @@ class Character : public Creature, public visitable bool uncanny_dodge() override; /** Checks for chance that a ranged attack will hit other armor along the way */ - bool block_ranged_hit( Creature *source, bodypart_id &bp_hit, damage_instance &dam ) override; + float block_ranged_hit( Creature *source, bodypart_id &bp_hit, damage_instance &dam ) override; // melee.cpp /** Checks for valid block abilities and reduces damage accordingly. Returns true if the player blocks */ diff --git a/src/creature.cpp b/src/creature.cpp index cd9bd9547983..7b0b710c0e53 100644 --- a/src/creature.cpp +++ b/src/creature.cpp @@ -774,17 +774,17 @@ void Creature::deal_projectile_attack( Creature *source, dealt_projectile_attack damage_mult = 1.0f; } - impact.mult_damage( damage_mult ); - if( proj.has_effect( ammo_effect_NOGIB ) ) { float dmg_ratio = static_cast( impact.total_damage() ) / get_hp_max( bp_hit ); if( dmg_ratio > 1.25f ) { impact.mult_damage( 1.0f / dmg_ratio ); } } + // If we have a shield, it might passively block ranged impacts - block_ranged_hit( source, bp_hit, impact ); - dealt_dam = deal_damage( source, bp_hit, impact ); + damage_instance d = impact; + damage_mult *= block_ranged_hit( source, bp_hit, d ); + dealt_dam = deal_damage( source, bp_hit, d ); dealt_dam.bp_hit = bp_hit->token; // Apply ammo effects to target. diff --git a/src/creature.h b/src/creature.h index 4d9ecac480b5..d018e7bb489e 100644 --- a/src/creature.h +++ b/src/creature.h @@ -240,7 +240,7 @@ class Creature damage_instance &dam ) = 0; // handles interaction of shields and ranged attacks. mutates &dam - virtual bool block_ranged_hit( Creature *source, bodypart_id &bp_hit, + virtual float block_ranged_hit( Creature *source, bodypart_id &bp_hit, damage_instance &dam ) = 0; // handles armor absorption (including clothing damage etc) diff --git a/src/monster.cpp b/src/monster.cpp index e00f4b4b750c..b0487ef6e1d5 100644 --- a/src/monster.cpp +++ b/src/monster.cpp @@ -1378,9 +1378,9 @@ bool monster::block_hit( Creature *, bodypart_id &, damage_instance & ) return false; } -bool monster::block_ranged_hit( Creature *, bodypart_id &, damage_instance & ) +float monster::block_ranged_hit( Creature *, bodypart_id &, damage_instance & ) { - return false; + return 1.0f; } void monster::absorb_hit( const bodypart_id &, damage_instance &dam ) diff --git a/src/monster.h b/src/monster.h index 511aa12bf33c..ecf22dc1b542 100644 --- a/src/monster.h +++ b/src/monster.h @@ -314,7 +314,7 @@ class monster : public Creature, public visitable void absorb_hit( const bodypart_id &bp, damage_instance &dam ) override; bool block_hit( Creature *source, bodypart_id &bp_hit, damage_instance &d ) override; - bool block_ranged_hit( Creature *source, bodypart_id &bp_hit, damage_instance &d ) override; + float block_ranged_hit( Creature *source, bodypart_id &bp_hit, damage_instance &d ) override; void melee_attack( Creature &target ); void melee_attack( Creature &target, float accuracy ); void melee_attack( Creature &p, bool ) = delete; From fe604a59b54e15c93afacb636ed17f40b4efde0c Mon Sep 17 00:00:00 2001 From: Chaosvolt Date: Sat, 20 May 2023 12:54:36 -0500 Subject: [PATCH 06/23] Update src/creature.h Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- src/creature.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/creature.h b/src/creature.h index d018e7bb489e..1044552a32ee 100644 --- a/src/creature.h +++ b/src/creature.h @@ -241,7 +241,7 @@ class Creature // handles interaction of shields and ranged attacks. mutates &dam virtual float block_ranged_hit( Creature *source, bodypart_id &bp_hit, - damage_instance &dam ) = 0; + damage_instance &dam ) = 0; // handles armor absorption (including clothing damage etc) // of damage instance. mutates &dam From f773ca0c35788b02913973356f665618ef3452e9 Mon Sep 17 00:00:00 2001 From: scarf Date: Sun, 21 May 2023 03:18:08 +0900 Subject: [PATCH 07/23] feat: drag heavier vehicles (#2840) * feat: drag heavier vehicles Co-authored-by: anoobindisguise <56016372+anoobindisguise@users.noreply.github.com> * refactor: move variables to be more local * refactor: extract `make_scraping_noise` * refactor: extract `get_grabbed_vehicle_movecost` * refactor: extract `get_vehicle_str_requirement` --------- Co-authored-by: anoobindisguise <56016372+anoobindisguise@users.noreply.github.com> --- src/grab.cpp | 127 +++++++++++++++++++++++++++++++++------------------ 1 file changed, 83 insertions(+), 44 deletions(-) diff --git a/src/grab.cpp b/src/grab.cpp index d9a710c2a213..c32400fe88d6 100644 --- a/src/grab.cpp +++ b/src/grab.cpp @@ -1,12 +1,16 @@ +#include "enums.h" #include "game.h" // IWYU pragma: associated #include #include +#include #include "avatar.h" +#include "character.h" #include "map.h" #include "messages.h" #include "monster.h" +#include "point.h" #include "sounds.h" #include "vehicle.h" #include "vpart_position.h" @@ -18,6 +22,68 @@ static const efftype_id effect_harnessed( "harnessed" ); +namespace +{ +auto make_scraping_noise( const tripoint &pos, const int volume ) -> void +{ + sounds::sound( pos, volume, sounds::sound_t::movement, + _( "a scraping noise." ), true, "misc", "scraping" ); +} + +// vehicle movement: strength check. very strong humans can move about 2,000 kg in a wheelbarrow. +auto base_str_req( vehicle *veh )-> int +{ + return veh->total_mass() / 100_kilogram; +} + +// determine movecost for terrain touching wheels +auto get_grabbed_vehicle_movecost( vehicle *veh ) -> int +{ + const int str_req = base_str_req( veh ); + const auto &map = get_map(); + const tripoint &vehpos = veh->global_pos3(); + + static const auto get_wheel_pos = [&]( const int p ) { + return vehpos + veh->part( p ).precalc[0]; + }; + + const auto &wheel_indices = veh->wheelcache; + return std::accumulate( wheel_indices.begin(), wheel_indices.end(), 0, + [&]( const int sum, const int p ) { + const tripoint wheel_pos = get_wheel_pos( p ); + const int mapcost = map.move_cost( wheel_pos, veh ); + const int movecost = str_req / static_cast( wheel_indices.size() ) * mapcost; + + return sum + movecost; + } ); +} + +//if vehicle has many or only one wheel (shopping cart), it is as if it had four. +auto get_effective_wheels( vehicle *veh ) -> int +{ + const auto &wheels = veh->wheelcache; + + return ( wheels.size() > 4 || wheels.size() == 1 ) ? 4 : wheels.size(); +} + +// very strong humans can move about 2,000 kg in a wheelbarrow. +auto get_vehicle_str_requirement( vehicle *veh ) -> int +{ + if( !veh->valid_wheel_config() ) { + return base_str_req( veh ) * 10; + } + + //if vehicle is rollable we modify str_req based on a function of movecost per wheel. + const int all_movecost = get_grabbed_vehicle_movecost( veh ); + // off-road coefficient (always 1.0 on a road, as low as 0.1 off road.) + const float traction = veh->k_traction( + get_map().vehicle_wheel_traction( *veh ) ); + return int { 1 + all_movecost / get_effective_wheels( veh ) } / traction; +} + +} // namespace + + bool game::grabbed_veh_move( const tripoint &dp ) { const optional_vpart_position grabbed_vehicle_vp = m.veh_at( u.pos() + u.grab_point ); @@ -77,58 +143,31 @@ bool game::grabbed_veh_move( const tripoint &dp ) // Make sure the mass and pivot point are correct grabbed_vehicle->invalidate_mass(); - //vehicle movement: strength check - int mc = 0; - int str_req = grabbed_vehicle->total_mass() / 25_kilogram; //strength required to move vehicle. - - //if vehicle is rollable we modify str_req based on a function of movecost per wheel. - - // Vehicle just too big to grab & move; 41-45 lets folks have a bit of a window - // (Roughly 1.1K kg = danger zone; cube vans are about the max) - if( str_req > 45 ) { - add_msg( m_info, _( "The %s is too bulky for you to move by hand." ), - grabbed_vehicle->name ); - return true; // No shoving around an RV. - } - - const auto &wheel_indices = grabbed_vehicle->wheelcache; - if( grabbed_vehicle->valid_wheel_config() ) { - //determine movecost for terrain touching wheels - const tripoint vehpos = grabbed_vehicle->global_pos3(); - for( int p : wheel_indices ) { - const tripoint wheel_pos = vehpos + grabbed_vehicle->part( p ).precalc[0]; - const int mapcost = m.move_cost( wheel_pos, grabbed_vehicle ); - mc += str_req / wheel_indices.size() * mapcost; - } - //set strength check threshold - //if vehicle has many or only one wheel (shopping cart), it is as if it had four. - if( wheel_indices.size() > 4 || wheel_indices.size() == 1 ) { - str_req = mc / 4 + 1; - } else { - str_req = mc / wheel_indices.size() + 1; - } - } else { - str_req++; - //if vehicle has no wheels str_req make a noise. - if( str_req <= u.get_str() ) { - sounds::sound( grabbed_vehicle->global_pos3(), str_req * 2, sounds::sound_t::movement, - _( "a scraping noise." ), true, "misc", "scraping" ); - } - } + //vehicle movement: strength check. very strong humans can move about 2,000 kg in a wheelbarrow. + // int str_req = grabbed_vehicle->total_mass() / 100_kilogram; //strength required to move vehicle. + const int str_req = get_vehicle_str_requirement( grabbed_vehicle ); + const int str = u.get_str(); + add_msg( m_debug, "str_req: %d", str_req ); //final strength check and outcomes ///\EFFECT_STR determines ability to drag vehicles - if( str_req <= u.get_str() ) { + if( str_req <= str ) { + if( !grabbed_vehicle->valid_wheel_config() ) { + make_scraping_noise( grabbed_vehicle->global_pos3(), str_req * 2 ); + } + //calculate exertion factor and movement penalty ///\EFFECT_STR increases speed of dragging vehicles - u.moves -= 100 * str_req / std::max( 1, u.get_str() ); - const int ex = dice( 1, 3 ) - 1 + str_req; - if( ex > u.get_str() + 1 ) { + u.moves -= 400 * str_req / std::max( 1, str ); + ///\EFFECT_STR decreases stamina cost of dragging vehicles + u.mod_stamina( -200 * str_req / std::max( 1, str ) ); + const int ex = dice( 1, 6 ) - 1 + str_req; + if( ex > str + 1 ) { // Pain and movement penalty if exertion exceeds character strength add_msg( m_bad, _( "You strain yourself to move the %s!" ), grabbed_vehicle->name ); u.moves -= 200; u.mod_pain( 1 ); - } else if( ex >= u.get_str() ) { + } else if( ex >= str ) { // Movement is slow if exertion nearly equals character strength add_msg( _( "It takes some time to move the %s." ), grabbed_vehicle->name ); u.moves -= 200; @@ -196,7 +235,7 @@ bool game::grabbed_veh_move( const tripoint &dp ) return false; } - for( int p : wheel_indices ) { + for( int p : grabbed_vehicle->wheelcache ) { if( one_in( 2 ) ) { tripoint wheel_p = grabbed_vehicle->global_part_pos3( grabbed_part ); grabbed_vehicle->handle_trap( wheel_p, p ); From 74733344777134f76377eccda65263c910fabd71 Mon Sep 17 00:00:00 2001 From: Chaosvolt Date: Sat, 20 May 2023 16:02:35 -0500 Subject: [PATCH 08/23] Fix it up to a basic working state --- data/json/items/armor/shields.json | 3 +- src/character.cpp | 65 +++++++++++++++++------------- src/character.h | 2 +- src/creature.cpp | 8 ++-- src/creature.h | 4 +- src/monster.cpp | 4 +- src/monster.h | 2 +- 7 files changed, 48 insertions(+), 40 deletions(-) diff --git a/data/json/items/armor/shields.json b/data/json/items/armor/shields.json index 21b0e89dca24..7b5fe239673a 100644 --- a/data/json/items/armor/shields.json +++ b/data/json/items/armor/shields.json @@ -55,7 +55,8 @@ "covers": [ "arm_either", "hand_either" ], "coverage": 100, "encumbrance": 10, - "material_thickness": 6, + "material_thickness": 4, + "environmental_protection": 2, "techniques": [ "WBLOCK_3" ], "flags": [ "OVERSIZE", "BELTED", "RESTRICT_HANDS", "BLOCK_WHILE_WORN" ] } diff --git a/src/character.cpp b/src/character.cpp index a7b8bcf34393..5c3d3f5e3f13 100644 --- a/src/character.cpp +++ b/src/character.cpp @@ -10636,14 +10636,14 @@ bool Character::uncanny_dodge() return character_funcs::try_uncanny_dodge( *this ); } -float Character::block_ranged_hit( Creature *source, bodypart_id &bp_hit, damage_instance &dam ) +bool Character::block_ranged_hit( Creature *source, bodypart_id &bp_hit, damage_instance &dam ) { // Having access to more than one shield is not normal in vanilla, for now keep it simple and only give one chance to catch a bullet. item &shield = best_shield(); // Bail out early just in case, if blocking with bare hands. if( shield.is_null() ) { - return 1.0f; + return false; } // Also bail out on the following conditions: @@ -10652,7 +10652,7 @@ float Character::block_ranged_hit( Creature *source, bodypart_id &bp_hit, damage // 3. Targeted bodypart is a foot, unlikely to ever successfully block that low. if( !shield.has_flag( "BLOCK_WHILE_WORN" ) || shield.covers( bp_hit->token ) || bp_hit == bodypart_str_id( "foot_l" ) || bp_hit == bodypart_str_id( "foot_r" ) ) { - return 1.0f; + return false; } // Modify chance based on coverage and blocking ability. Exclude armguards here. @@ -10664,67 +10664,74 @@ float Character::block_ranged_hit( Creature *source, bodypart_id &bp_hit, damage } else if( shield.has_technique( WBLOCK_1 ) ) { shield_coverage_modifier *= 0.4; } else { - return 1.0f; + return false; } // Targeting the legs halves the chance. if( bp_hit == bodypart_str_id( "leg_l" ) || bp_hit == bodypart_str_id( "leg_r" ) ) { shield_coverage_modifier *= 0.5; } + add_msg( m_debug, _( "block_ranged_hit success rate: %i%%" ), static_cast( shield_coverage_modifier ) ); // Now roll coverage to determine if we intercept the shot. if( rng( 1, 100 ) > shield_coverage_modifier ) { - return 1.0f; + add_msg( m_debug, _( "block_ranged_hit attempt failed" ) ); + return false; } - std::string thing_blocked_with = shield.tname(); - add_msg_player_or_npc( - _( "The shot hits your %s!" ), - _( "The shot hits 's %s!" ), - thing_blocked_with ); - float wear_modifier = 1.0f; if( source != nullptr && source->is_hallucination() ) { wear_modifier = 0.0f; } handle_melee_wear( shield, wear_modifier ); - float total_damage = 0.0; - float damage_blocked = 0.0; + int total_damage = 0; + int blocked_damage = 0; for( auto &elem : dam.damage_units ) { total_damage += elem.amount; - // Go through all relevant damage types and reduce by armor value if one exists. if( elem.type == DT_BASH ) { - float block_amount = shield.bash_resist(); + float block_amount = std::max( 0.0f, ( shield.bash_resist() - elem.res_pen ) ); elem.amount -= block_amount; - damage_blocked += block_amount; + blocked_damage += block_amount; } else if( elem.type == DT_CUT ) { - float block_amount = shield.cut_resist(); + float block_amount = std::max( 0.0f, ( shield.cut_resist() - elem.res_pen ) ); elem.amount -= block_amount; - damage_blocked += block_amount; + blocked_damage += block_amount; } else if( elem.type == DT_STAB ) { - float block_amount = shield.stab_resist(); + float block_amount = std::max( 0.0f, ( shield.stab_resist() - elem.res_pen) ); elem.amount -= block_amount; - damage_blocked += block_amount; + blocked_damage += block_amount; } else if( elem.type == DT_BULLET ) { - float block_amount = shield.bullet_resist(); + float block_amount = std::max( 0.0f, ( shield.bullet_resist() - elem.res_pen ) ); elem.amount -= block_amount; - damage_blocked += block_amount; + blocked_damage += block_amount; } else if( elem.type == DT_HEAT ) { - float block_amount = shield.fire_resist(); + float block_amount = std::max( 0.0f, ( shield.fire_resist() - elem.res_pen ) ); elem.amount -= block_amount; - damage_blocked += block_amount; + blocked_damage += block_amount; } else if( elem.type == DT_ACID ) { - float block_amount = shield.acid_resist(); + float block_amount = std::max( 0.0f, ( shield.acid_resist() - elem.res_pen ) ); elem.amount -= block_amount; - damage_blocked += block_amount; + blocked_damage += block_amount; } } + blocked_damage = std::min( total_damage, blocked_damage ); + std::string thing_blocked_with = shield.tname(); + add_msg( m_debug, _( "expected base damage: %i" ), total_damage ); + if ( blocked_damage > 0 ) { + add_msg_player_or_npc( + _( "The shot hits your %s, absorbing %i damage." ), + _( "The shot hits 's %s, absorbing %i damage." ), + thing_blocked_with, blocked_damage ); + } else { + add_msg_player_or_npc( + _( "The shot hits your %s, but it punches right through!" ), + _( "The shot hits 's %s, but it punches right through!" ), + thing_blocked_with ); + } - float block_result = ( total_damage - damage_blocked ) / total_damage; - - return block_result; + return true; } float Character::fall_damage_mod() const diff --git a/src/character.h b/src/character.h index e40117f5ceda..ab7b3c81212f 100644 --- a/src/character.h +++ b/src/character.h @@ -585,7 +585,7 @@ class Character : public Creature, public visitable bool uncanny_dodge() override; /** Checks for chance that a ranged attack will hit other armor along the way */ - float block_ranged_hit( Creature *source, bodypart_id &bp_hit, damage_instance &dam ) override; + bool block_ranged_hit( Creature *source, bodypart_id &bp_hit, damage_instance &dam ) override; // melee.cpp /** Checks for valid block abilities and reduces damage accordingly. Returns true if the player blocks */ diff --git a/src/creature.cpp b/src/creature.cpp index 7b0b710c0e53..cd9bd9547983 100644 --- a/src/creature.cpp +++ b/src/creature.cpp @@ -774,17 +774,17 @@ void Creature::deal_projectile_attack( Creature *source, dealt_projectile_attack damage_mult = 1.0f; } + impact.mult_damage( damage_mult ); + if( proj.has_effect( ammo_effect_NOGIB ) ) { float dmg_ratio = static_cast( impact.total_damage() ) / get_hp_max( bp_hit ); if( dmg_ratio > 1.25f ) { impact.mult_damage( 1.0f / dmg_ratio ); } } - // If we have a shield, it might passively block ranged impacts - damage_instance d = impact; - damage_mult *= block_ranged_hit( source, bp_hit, d ); - dealt_dam = deal_damage( source, bp_hit, d ); + block_ranged_hit( source, bp_hit, impact ); + dealt_dam = deal_damage( source, bp_hit, impact ); dealt_dam.bp_hit = bp_hit->token; // Apply ammo effects to target. diff --git a/src/creature.h b/src/creature.h index 1044552a32ee..4d9ecac480b5 100644 --- a/src/creature.h +++ b/src/creature.h @@ -240,8 +240,8 @@ class Creature damage_instance &dam ) = 0; // handles interaction of shields and ranged attacks. mutates &dam - virtual float block_ranged_hit( Creature *source, bodypart_id &bp_hit, - damage_instance &dam ) = 0; + virtual bool block_ranged_hit( Creature *source, bodypart_id &bp_hit, + damage_instance &dam ) = 0; // handles armor absorption (including clothing damage etc) // of damage instance. mutates &dam diff --git a/src/monster.cpp b/src/monster.cpp index b0487ef6e1d5..e00f4b4b750c 100644 --- a/src/monster.cpp +++ b/src/monster.cpp @@ -1378,9 +1378,9 @@ bool monster::block_hit( Creature *, bodypart_id &, damage_instance & ) return false; } -float monster::block_ranged_hit( Creature *, bodypart_id &, damage_instance & ) +bool monster::block_ranged_hit( Creature *, bodypart_id &, damage_instance & ) { - return 1.0f; + return false; } void monster::absorb_hit( const bodypart_id &, damage_instance &dam ) diff --git a/src/monster.h b/src/monster.h index ecf22dc1b542..511aa12bf33c 100644 --- a/src/monster.h +++ b/src/monster.h @@ -314,7 +314,7 @@ class monster : public Creature, public visitable void absorb_hit( const bodypart_id &bp, damage_instance &dam ) override; bool block_hit( Creature *source, bodypart_id &bp_hit, damage_instance &d ) override; - float block_ranged_hit( Creature *source, bodypart_id &bp_hit, damage_instance &d ) override; + bool block_ranged_hit( Creature *source, bodypart_id &bp_hit, damage_instance &d ) override; void melee_attack( Creature &target ); void melee_attack( Creature &target, float accuracy ); void melee_attack( Creature &p, bool ) = delete; From dcce2ad12e29a12c919e4def0d8a3a99ffa2dc10 Mon Sep 17 00:00:00 2001 From: Chaosvolt Date: Sat, 20 May 2023 16:42:32 -0500 Subject: [PATCH 09/23] Continue code tweaks, add ballistic shield --- data/json/items/armor/shields.json | 45 +++++++++++++++++++++++------- src/character.cpp | 36 +++++++++++------------- 2 files changed, 52 insertions(+), 29 deletions(-) diff --git a/data/json/items/armor/shields.json b/data/json/items/armor/shields.json index 7b5fe239673a..06ced6aaba4d 100644 --- a/data/json/items/armor/shields.json +++ b/data/json/items/armor/shields.json @@ -4,9 +4,10 @@ "type": "ARMOR", "name": { "str": "wooden shield" }, "description": "A crude wooden shield, lacking any metal or leather reinforcement. Lightweight but not very tough.", - "weight": "2267 g", - "volume": "3750 ml", - "price": 80000, + "weight": "3 kg", + "volume": "3 L", + "price": "50 USD", + "price_postapoc": "5 USD", "to_hit": -1, "bashing": 8, "material": [ "wood" ], @@ -24,10 +25,11 @@ "type": "ARMOR", "name": { "str": "large wooden shield" }, "description": "An crude wooden tower shield, lacking any metal or leather reinforcement. Bulky, but offers a decent amount of protection.", - "weight": "3828 g", + "weight": "5 kg", "volume": "5 L", - "price": 90000, - "to_hit": -1, + "price": "60 USD", + "price_postapoc": "750 cent", + "to_hit": -2, "bashing": 10, "material": [ "wood" ], "symbol": "[", @@ -44,11 +46,12 @@ "type": "ARMOR", "name": { "str": "riot shield" }, "description": "A large but fairly light plastic shield, designed for riot police officers. Not too encumbering, but designed for fending off thrown rocks rather than bullets.", - "weight": "2700 g", + "weight": "2500 g", "volume": "5 L", - "price": 120000, + "price": "200 USD", + "price_postapoc": "25 USD", "to_hit": -1, - "bashing": 8, + "bashing": 4, "material": [ "plastic" ], "symbol": "[", "color": "light_gray", @@ -56,8 +59,30 @@ "coverage": 100, "encumbrance": 10, "material_thickness": 4, - "environmental_protection": 2, + "environmental_protection": 3, "techniques": [ "WBLOCK_3" ], "flags": [ "OVERSIZE", "BELTED", "RESTRICT_HANDS", "BLOCK_WHILE_WORN" ] + }, + { + "id": "shield_ballistic", + "type": "ARMOR", + "name": { "str": "ballistic shield" }, + "description": "A heavy composite shield used by SWAT teams and other armed forces. It can handle the occasional pistol bullet, but its heavy-duty nature means it's quite encumbering and doesn't cover the legs very well.", + "weight": "8 kg", + "volume": "4 L", + "price": "1000 USD", + "price_postapoc": "50 USD", + "to_hit": -3, + "bashing": 12, + "material": [ "ceramic", "kevlar" ], + "symbol": "[", + "color": "light_gray", + "covers": [ "arm_either", "hand_either" ], + "coverage": 100, + "encumbrance": 30, + "material_thickness": 6, + "environmental_protection": 2, + "techniques": [ "WBLOCK_2" ], + "flags": [ "OVERSIZE", "BELTED", "RESTRICT_HANDS", "BLOCK_WHILE_WORN" ] } ] diff --git a/src/character.cpp b/src/character.cpp index 5c3d3f5e3f13..ad70438e53ea 100644 --- a/src/character.cpp +++ b/src/character.cpp @@ -10655,22 +10655,20 @@ bool Character::block_ranged_hit( Creature *source, bodypart_id &bp_hit, damage_ return false; } - // Modify chance based on coverage and blocking ability. Exclude armguards here. + // Modify chance based on coverage and blocking ability, with lowered chance if hitting the legs. Exclude armguards here. float shield_coverage_modifier = shield.get_coverage(); + bool leg_hit = ( bp_hit == bodypart_str_id( "leg_l" ) || bp_hit == bodypart_str_id( "leg_r" ) ); if( shield.has_technique( WBLOCK_3 ) ) { - shield_coverage_modifier *= 0.8; + shield_coverage_modifier *= leg_hit ? 0.75 : 0.9; } else if( shield.has_technique( WBLOCK_2 ) ) { - shield_coverage_modifier *= 0.6; + shield_coverage_modifier *= leg_hit ? 0.5 : 0.8; } else if( shield.has_technique( WBLOCK_1 ) ) { - shield_coverage_modifier *= 0.4; + shield_coverage_modifier *= leg_hit ? 0.25 : 0.7; } else { return false; } - // Targeting the legs halves the chance. - if( bp_hit == bodypart_str_id( "leg_l" ) || bp_hit == bodypart_str_id( "leg_r" ) ) { - shield_coverage_modifier *= 0.5; - } - add_msg( m_debug, _( "block_ranged_hit success rate: %i%%" ), static_cast( shield_coverage_modifier ) ); + add_msg( m_debug, _( "block_ranged_hit success rate: %i%%" ), + static_cast( shield_coverage_modifier ) ); // Now roll coverage to determine if we intercept the shot. if( rng( 1, 100 ) > shield_coverage_modifier ) { @@ -10699,7 +10697,7 @@ bool Character::block_ranged_hit( Creature *source, bodypart_id &bp_hit, damage_ elem.amount -= block_amount; blocked_damage += block_amount; } else if( elem.type == DT_STAB ) { - float block_amount = std::max( 0.0f, ( shield.stab_resist() - elem.res_pen) ); + float block_amount = std::max( 0.0f, ( shield.stab_resist() - elem.res_pen ) ); elem.amount -= block_amount; blocked_damage += block_amount; } else if( elem.type == DT_BULLET ) { @@ -10719,16 +10717,16 @@ bool Character::block_ranged_hit( Creature *source, bodypart_id &bp_hit, damage_ blocked_damage = std::min( total_damage, blocked_damage ); std::string thing_blocked_with = shield.tname(); add_msg( m_debug, _( "expected base damage: %i" ), total_damage ); - if ( blocked_damage > 0 ) { - add_msg_player_or_npc( - _( "The shot hits your %s, absorbing %i damage." ), - _( "The shot hits 's %s, absorbing %i damage." ), - thing_blocked_with, blocked_damage ); + if( blocked_damage > 0 ) { + add_msg_player_or_npc( + _( "The shot hits your %s, absorbing %i damage." ), + _( "The shot hits 's %s, absorbing %i damage." ), + thing_blocked_with, blocked_damage ); } else { - add_msg_player_or_npc( - _( "The shot hits your %s, but it punches right through!" ), - _( "The shot hits 's %s, but it punches right through!" ), - thing_blocked_with ); + add_msg_player_or_npc( + _( "The shot hits your %s, but it punches right through!" ), + _( "The shot hits 's %s, but it punches right through!" ), + thing_blocked_with ); } return true; From 74a019097fe74523d9eb6f30982748ff963d034e Mon Sep 17 00:00:00 2001 From: Chaosvolt Date: Sat, 20 May 2023 18:48:52 -0500 Subject: [PATCH 10/23] And some hopefully final touches --- .../itemgroups/Clothing_Gear/clothing.json | 14 ++++++-- data/json/itemgroups/Clothing_Gear/gear.json | 4 ++- .../Locations_MapExtras/locations.json | 1 + .../locations_commercial.json | 3 +- .../Locations_MapExtras/mansion.json | 7 +++- .../monster_drops_lairs.json | 3 +- data/json/itemgroups/art_antiques_crafts.json | 2 ++ data/json/items/armor/shields.json | 8 ++--- data/json/monsterdrops/zombie_cop.json | 6 ++-- data/json/monsterdrops/zombie_soldier.json | 3 +- data/json/recipes/armor/other.json | 32 +++++++++++++++++++ src/character.cpp | 15 +++++---- 12 files changed, 77 insertions(+), 21 deletions(-) diff --git a/data/json/itemgroups/Clothing_Gear/clothing.json b/data/json/itemgroups/Clothing_Gear/clothing.json index f41158258a24..8caa18cb9972 100644 --- a/data/json/itemgroups/Clothing_Gear/clothing.json +++ b/data/json/itemgroups/Clothing_Gear/clothing.json @@ -1872,7 +1872,8 @@ [ "beret_wool", 40 ], [ "elbow_pads", 50 ], [ "knee_pads", 50 ], - [ "solarpack", 5 ] + [ "solarpack", 5 ], + [ "shield_ballistic", 3 ] ] }, { @@ -1903,6 +1904,7 @@ [ "elbow_pads", 40 ], [ "knee_pads", 40 ], [ "mask_bal", 5 ], + [ "shield_ballistic", 2 ], [ "e_tool", 10 ], [ "waterproof_gunmod", 8 ], [ "grapnel", 3 ], @@ -2398,7 +2400,11 @@ [ "survivor_shavingkit", 3 ], [ "survivor_hairtrimmer", 1 ], [ "survivor_scope", 1 ], - [ "survnote", 30 ] + [ "survnote", 30 ], + [ "shield_wooden", 1 ], + [ "shield_wooden_large", 1 ], + [ "shield_riot", 3 ], + [ "shield_ballistic", 2 ] ] }, { @@ -2429,7 +2435,9 @@ [ "legguard_metal", 10 ], [ "helmet_corinthian", 45 ], [ "armor_cuirass", 25 ], - [ "legguard_bronze", 20 ] + [ "legguard_bronze", 20 ], + [ "shield_wooden", 10 ], + [ "shield_wooden_large", 5 ] ] }, { diff --git a/data/json/itemgroups/Clothing_Gear/gear.json b/data/json/itemgroups/Clothing_Gear/gear.json index bdf8f537e8e0..42976d5618da 100644 --- a/data/json/itemgroups/Clothing_Gear/gear.json +++ b/data/json/itemgroups/Clothing_Gear/gear.json @@ -31,7 +31,9 @@ [ "suppressor_compact", 20 ], [ "swat_armor", 20 ], [ "tac_fullhelmet", 5 ], - [ "tac_helmet", 10 ] + [ "tac_helmet", 10 ], + [ "shield_riot", 20 ], + [ "shield_ballistic", 5 ] ] }, { diff --git a/data/json/itemgroups/Locations_MapExtras/locations.json b/data/json/itemgroups/Locations_MapExtras/locations.json index 3ac031d246df..f90e2e3a47c9 100644 --- a/data/json/itemgroups/Locations_MapExtras/locations.json +++ b/data/json/itemgroups/Locations_MapExtras/locations.json @@ -1385,6 +1385,7 @@ [ "gloves_tactical", 10 ], [ "armguard_hard", 20 ], [ "legguard_hard", 20 ], + [ "shield_riot", 20 ], [ "emergency_book", 1 ] ] }, diff --git a/data/json/itemgroups/Locations_MapExtras/locations_commercial.json b/data/json/itemgroups/Locations_MapExtras/locations_commercial.json index 345914744961..82b406064213 100644 --- a/data/json/itemgroups/Locations_MapExtras/locations_commercial.json +++ b/data/json/itemgroups/Locations_MapExtras/locations_commercial.json @@ -585,7 +585,8 @@ [ "silver_medal", 2 ], [ "gold_medal", 1 ], [ "bionic_scanner", 5 ], - { "group": "tinware", "prob": 10 } + { "group": "tinware", "prob": 10 }, + [ "shield_riot", 5 ] ] }, { diff --git a/data/json/itemgroups/Locations_MapExtras/mansion.json b/data/json/itemgroups/Locations_MapExtras/mansion.json index c5386f275d4b..e17239cb7651 100644 --- a/data/json/itemgroups/Locations_MapExtras/mansion.json +++ b/data/json/itemgroups/Locations_MapExtras/mansion.json @@ -468,7 +468,7 @@ "id": "soa_mail", "type": "item_group", "subtype": "collection", - "items": [ { "group": "soa_weapons_mail" }, [ "chainmail_suit", 100 ] ] + "items": [ { "group": "soa_weapons_mail" }, { "group": "soa_shields_mail", "prob": 50 }, [ "chainmail_suit", 100 ] ] }, { "id": "soa_weapons_mail", @@ -480,6 +480,11 @@ { "group": "soa_real_weapon_mail", "prob": 3 } ] }, + { + "id": "soa_shields_mail", + "type": "item_group", + "items": [ [ "shield_wooden", 10 ], [ "shield_wooden_large", 5 ] ] + }, { "id": "soa_samurai", "type": "item_group", diff --git a/data/json/itemgroups/Monsters_Animals_Lairs/monster_drops_lairs.json b/data/json/itemgroups/Monsters_Animals_Lairs/monster_drops_lairs.json index 5d92978d989c..fb103f2d2297 100644 --- a/data/json/itemgroups/Monsters_Animals_Lairs/monster_drops_lairs.json +++ b/data/json/itemgroups/Monsters_Animals_Lairs/monster_drops_lairs.json @@ -334,7 +334,8 @@ { "item": "pur_tablets", "prob": 10 }, { "item": "pastaextruder", "prob": 10 }, { "item": "can_sealer", "prob": 10 }, - { "item": "remotevehcontrol", "prob": 8 } + { "item": "remotevehcontrol", "prob": 8 }, + { "item": "shield_ballistic", "prob": 10 } ] }, { diff --git a/data/json/itemgroups/art_antiques_crafts.json b/data/json/itemgroups/art_antiques_crafts.json index 286961408d3e..636789f3dff7 100644 --- a/data/json/itemgroups/art_antiques_crafts.json +++ b/data/json/itemgroups/art_antiques_crafts.json @@ -133,6 +133,8 @@ { "item": "tinderbox", "prob": 4 }, { "item": "flint_steel", "prob": 7 }, { "item": "canteen_wood", "prob": 5 }, + { "item": "shield_wooden", "prob": 3 }, + { "item": "shield_wooden_large", "prob": 3 }, { "item": "apron_leather", "prob": 1 }, { "item": "pot_copper", "prob": 3 }, { "group": "tinware", "prob": 10 } diff --git a/data/json/items/armor/shields.json b/data/json/items/armor/shields.json index 06ced6aaba4d..56abc11fbfbe 100644 --- a/data/json/items/armor/shields.json +++ b/data/json/items/armor/shields.json @@ -3,7 +3,7 @@ "id": "shield_wooden", "type": "ARMOR", "name": { "str": "wooden shield" }, - "description": "A crude wooden shield, lacking any metal or leather reinforcement. Lightweight but not very tough.", + "description": "A crude wooden shield, lacking any metal or leather reinforcement. Tolerable weight but not very tough.", "weight": "3 kg", "volume": "3 L", "price": "50 USD", @@ -16,7 +16,7 @@ "covers": [ "arm_either", "hand_either" ], "coverage": 90, "encumbrance": 15, - "material_thickness": 2, + "material_thickness": 3, "techniques": [ "WBLOCK_2" ], "flags": [ "OVERSIZE", "BELTED", "RESTRICT_HANDS", "BLOCK_WHILE_WORN" ] }, @@ -33,11 +33,11 @@ "bashing": 10, "material": [ "wood" ], "symbol": "[", - "color": "light_gray", + "color": "brown", "covers": [ "arm_either", "hand_either" ], "coverage": 90, "encumbrance": 25, - "material_thickness": 2, + "material_thickness": 3, "techniques": [ "WBLOCK_3" ], "flags": [ "OVERSIZE", "BELTED", "RESTRICT_HANDS", "BLOCK_WHILE_WORN" ] }, diff --git a/data/json/monsterdrops/zombie_cop.json b/data/json/monsterdrops/zombie_cop.json index fb955cb5b1b2..b6ffe66e4b5f 100644 --- a/data/json/monsterdrops/zombie_cop.json +++ b/data/json/monsterdrops/zombie_cop.json @@ -20,7 +20,8 @@ "collection": [ { "item": "badge_deputy", "prob": 100 }, { "item": "badge_detective", "prob": 20 } ], "prob": 10 }, - { "item": "cash_card", "charges-min": 0, "charges-max": 50000 } + { "item": "cash_card", "charges-min": 0, "charges-max": 50000 }, + { "item": "shield_riot", "prob": 5, "damage": [ 1, 4 ] } ] }, { @@ -98,7 +99,8 @@ { "group": "clothing_glasses", "prob": 5 }, { "group": "clothing_watch", "prob": 10 }, { "item": "badge_swat", "prob": 10 }, - { "item": "cash_card", "charges-min": 0, "charges-max": 50000, "prob": 50 } + { "item": "cash_card", "charges-min": 0, "charges-max": 50000, "prob": 50 }, + { "item": "shield_riot", "prob": 10, "damage": [ 1, 4 ] } ] }, { diff --git a/data/json/monsterdrops/zombie_soldier.json b/data/json/monsterdrops/zombie_soldier.json index 29a81c9f01cd..fea68524f8ae 100644 --- a/data/json/monsterdrops/zombie_soldier.json +++ b/data/json/monsterdrops/zombie_soldier.json @@ -50,7 +50,8 @@ "collection": [ { "group": "infantry_officer_gear", "prob": 90 }, { "group": "infantry_medical_gear", "prob": 80 } ] }, { "item": "cash_card", "prob": 10, "charges-min": 0, "charges-max": 50000 }, - { "group": "misc_smoking", "prob": 30 } + { "group": "misc_smoking", "prob": 30 }, + { "item": "shield_ballistic", "prob": 10, "damage": [ 1, 4 ] } ] }, { diff --git a/data/json/recipes/armor/other.json b/data/json/recipes/armor/other.json index ca62ccbbd770..90cf1230d93c 100644 --- a/data/json/recipes/armor/other.json +++ b/data/json/recipes/armor/other.json @@ -400,5 +400,37 @@ "time": "4 m", "autolearn": true, "components": [ [ [ "water", 1 ], [ "water_clean", 1 ] ], [ [ "towel_soiled", 1 ] ] ] + }, + { + "result": "shield_wooden", + "type": "recipe", + "category": "CC_ARMOR", + "subcategory": "CSC_ARMOR_OTHER", + "skill_used": "fabrication", + "difficulty": 2, + "time": "30 m", + "autolearn": true, + "qualities": [ { "id": "CUT", "level": 1 }, { "id": "HAMMER", "level": 1 } ], + "components": [ + [ [ "nail", 8 ] ], + [ [ "2x4", 4 ], [ "wood_panel", 1 ] ], + [ [ "rag", 2 ], [ "felt_patch", 2 ], [ "fur", 1 ], [ "leather", 1 ] ] + ] + }, + { + "result": "shield_wooden_large", + "type": "recipe", + "category": "CC_ARMOR", + "subcategory": "CSC_ARMOR_OTHER", + "skill_used": "fabrication", + "difficulty": 3, + "time": "50 m", + "autolearn": true, + "qualities": [ { "id": "CUT", "level": 1 }, { "id": "HAMMER", "level": 1 } ], + "components": [ + [ [ "nail", 16 ] ], + [ [ "2x4", 8 ], [ "wood_panel", 2 ] ], + [ [ "rag", 2 ], [ "felt_patch", 2 ], [ "fur", 2 ], [ "leather", 2 ] ] + ] } ] diff --git a/src/character.cpp b/src/character.cpp index ad70438e53ea..9fcfb5c73c5d 100644 --- a/src/character.cpp +++ b/src/character.cpp @@ -10686,30 +10686,31 @@ bool Character::block_ranged_hit( Creature *source, bodypart_id &bp_hit, damage_ int blocked_damage = 0; for( auto &elem : dam.damage_units ) { - total_damage += elem.amount; + total_damage += elem.amount * elem.damage_multiplier; // Go through all relevant damage types and reduce by armor value if one exists. + // Ugly, but need to check resistances separately. if( elem.type == DT_BASH ) { - float block_amount = std::max( 0.0f, ( shield.bash_resist() - elem.res_pen ) ); + float block_amount = std::max( 0.0f, ( ( shield.bash_resist() - elem.res_pen ) * elem.res_mult ) ); elem.amount -= block_amount; blocked_damage += block_amount; } else if( elem.type == DT_CUT ) { - float block_amount = std::max( 0.0f, ( shield.cut_resist() - elem.res_pen ) ); + float block_amount = std::max( 0.0f, ( ( shield.cut_resist() - elem.res_pen ) * elem.res_mult ) ); elem.amount -= block_amount; blocked_damage += block_amount; } else if( elem.type == DT_STAB ) { - float block_amount = std::max( 0.0f, ( shield.stab_resist() - elem.res_pen ) ); + float block_amount = std::max( 0.0f, ( ( shield.stab_resist() - elem.res_pen ) * elem.res_mult ) ); elem.amount -= block_amount; blocked_damage += block_amount; } else if( elem.type == DT_BULLET ) { - float block_amount = std::max( 0.0f, ( shield.bullet_resist() - elem.res_pen ) ); + float block_amount = std::max( 0.0f, ( ( shield.bullet_resist() - elem.res_pen ) * elem.res_mult ) ); elem.amount -= block_amount; blocked_damage += block_amount; } else if( elem.type == DT_HEAT ) { - float block_amount = std::max( 0.0f, ( shield.fire_resist() - elem.res_pen ) ); + float block_amount = std::max( 0.0f, ( ( shield.fire_resist() - elem.res_pen ) * elem.res_mult ) ); elem.amount -= block_amount; blocked_damage += block_amount; } else if( elem.type == DT_ACID ) { - float block_amount = std::max( 0.0f, ( shield.acid_resist() - elem.res_pen ) ); + float block_amount = std::max( 0.0f, ( ( shield.acid_resist() - elem.res_pen ) * elem.res_mult ) ); elem.amount -= block_amount; blocked_damage += block_amount; } From 9f3751710bab06b9698512bf888082d564138ba1 Mon Sep 17 00:00:00 2001 From: Chaosvolt Date: Sat, 20 May 2023 10:29:32 -0500 Subject: [PATCH 11/23] Commit what I've got for the moment --- data/json/items/armor/shields.json | 62 +++++++++++++++++++ src/character.cpp | 98 ++++++++++++++++++++++++++++++ src/character.h | 3 + src/creature.cpp | 3 +- src/creature.h | 4 ++ src/monster.cpp | 5 ++ src/monster.h | 1 + 7 files changed, 175 insertions(+), 1 deletion(-) create mode 100644 data/json/items/armor/shields.json diff --git a/data/json/items/armor/shields.json b/data/json/items/armor/shields.json new file mode 100644 index 000000000000..21b0e89dca24 --- /dev/null +++ b/data/json/items/armor/shields.json @@ -0,0 +1,62 @@ +[ + { + "id": "shield_wooden", + "type": "ARMOR", + "name": { "str": "wooden shield" }, + "description": "A crude wooden shield, lacking any metal or leather reinforcement. Lightweight but not very tough.", + "weight": "2267 g", + "volume": "3750 ml", + "price": 80000, + "to_hit": -1, + "bashing": 8, + "material": [ "wood" ], + "symbol": "[", + "color": "brown", + "covers": [ "arm_either", "hand_either" ], + "coverage": 90, + "encumbrance": 15, + "material_thickness": 2, + "techniques": [ "WBLOCK_2" ], + "flags": [ "OVERSIZE", "BELTED", "RESTRICT_HANDS", "BLOCK_WHILE_WORN" ] + }, + { + "id": "shield_wooden_large", + "type": "ARMOR", + "name": { "str": "large wooden shield" }, + "description": "An crude wooden tower shield, lacking any metal or leather reinforcement. Bulky, but offers a decent amount of protection.", + "weight": "3828 g", + "volume": "5 L", + "price": 90000, + "to_hit": -1, + "bashing": 10, + "material": [ "wood" ], + "symbol": "[", + "color": "light_gray", + "covers": [ "arm_either", "hand_either" ], + "coverage": 90, + "encumbrance": 25, + "material_thickness": 2, + "techniques": [ "WBLOCK_3" ], + "flags": [ "OVERSIZE", "BELTED", "RESTRICT_HANDS", "BLOCK_WHILE_WORN" ] + }, + { + "id": "shield_riot", + "type": "ARMOR", + "name": { "str": "riot shield" }, + "description": "A large but fairly light plastic shield, designed for riot police officers. Not too encumbering, but designed for fending off thrown rocks rather than bullets.", + "weight": "2700 g", + "volume": "5 L", + "price": 120000, + "to_hit": -1, + "bashing": 8, + "material": [ "plastic" ], + "symbol": "[", + "color": "light_gray", + "covers": [ "arm_either", "hand_either" ], + "coverage": 100, + "encumbrance": 10, + "material_thickness": 6, + "techniques": [ "WBLOCK_3" ], + "flags": [ "OVERSIZE", "BELTED", "RESTRICT_HANDS", "BLOCK_WHILE_WORN" ] + } +] diff --git a/src/character.cpp b/src/character.cpp index 176449ee5f69..25c5fe52cd8e 100644 --- a/src/character.cpp +++ b/src/character.cpp @@ -57,6 +57,7 @@ #include "mapdata.h" #include "material.h" #include "math_defines.h" +#include "martialarts.h" #include "memorial_logger.h" #include "messages.h" #include "mission.h" @@ -114,6 +115,10 @@ static const activity_id ACT_WAIT_STAMINA( "ACT_WAIT_STAMINA" ); static const bionic_id bio_eye_optic( "bio_eye_optic" ); static const bionic_id bio_watch( "bio_watch" ); +static const matec_id WBLOCK_1( "WBLOCK_1" ); +static const matec_id WBLOCK_2( "WBLOCK_2" ); +static const matec_id WBLOCK_3( "WBLOCK_3" ); + static const efftype_id effect_adrenaline( "adrenaline" ); static const efftype_id effect_ai_waiting( "ai_waiting" ); static const efftype_id effect_alarm_clock( "alarm_clock" ); @@ -10631,6 +10636,99 @@ bool Character::uncanny_dodge() return character_funcs::try_uncanny_dodge( *this ); } +bool Character::block_ranged_hit( Creature *source, bodypart_id &bp_hit, damage_instance &dam ) +{ + // Having access to more than one shield is not normal in vanilla, for now keep it simple and only give one chance to catch a bullet. + item &shield = best_shield(); + + // Bail out early just in case, if blocking with bare hands. + if( shield.is_null() ) { + return false; + } + + // Also bail out on the following conditions: + // 1. Best item available doesn't count as a shield. + // 2. Shield already protects the part we're interested in. + // 3. Targeted bodypart is a foot, unlikely to ever successfully block that low. + if( !shield.has_flag( "BLOCK_WHILE_WORN" ) || shield.covers( bp_hit->token ) || bp_hit == bodypart_str_id( "foot_l" ) || bp_hit == bodypart_str_id( "foot_r" ) ) { + return false; + } + + // Modify chance based on coverage and blocking ability. Exclude armguards here. + float shield_coverage_modifier = shield.get_coverage(); + if( shield.has_technique( WBLOCK_3 ) ) { + shield_coverage_modifier *= 0.8; + } else if( shield.has_technique( WBLOCK_2 ) ) { + shield_coverage_modifier *= 0.6; + } else if( shield.has_technique( WBLOCK_1 ) ) { + shield_coverage_modifier *= 0.4; + } else { + return false; + } + // Targeting the legs halves the chance. + if( bp_hit == bodypart_str_id( "leg_l" ) || bp_hit == bodypart_str_id( "leg_r" ) ) { + shield_coverage_modifier *= 0.5; + } + + // Now roll coverage to determine if we intercept the shot. + if( rng( 1, 100 ) > shield_coverage_modifier ) { + return false; + } + + std::string thing_blocked_with = shield.tname(); + add_msg_player_or_npc( + _( "The shot hits your %s!" ), + _( "The shot hits 's %s!" ), + thing_blocked_with ); + + float wear_modifier = 1.0f; + if( source != nullptr && source->is_hallucination() ) { + wear_modifier = 0.0f; + } + handle_melee_wear( shield, wear_modifier ); + + float total_damage = 0.0; + float damage_blocked = 0.0; + + for( auto &elem : dam.damage_units ) { + total_damage += elem.amount; + // Go through all relevant damage types and reduce by armor value if one exists. + if( elem.type == DT_BASH ) { + float previous_amount = elem.amount; + float block_amount = shield.bash_resist(); + elem.amount -= block_amount; + damage_blocked += previous_amount - elem.amount; + } else if( elem.type == DT_CUT ) { + float previous_amount = elem.amount; + float block_amount = shield.cut_resist(); + elem.amount -= block_amount; + damage_blocked += previous_amount - elem.amount; + } else if( elem.type == DT_STAB ) { + float previous_amount = elem.amount; + float block_amount = shield.stab_resist(); + elem.amount -= block_amount; + damage_blocked += previous_amount - elem.amount; + } else if( elem.type == DT_BULLET ) { + float previous_amount = elem.amount; + float block_amount = shield.bullet_resist(); + elem.amount -= block_amount; + damage_blocked += previous_amount - elem.amount; + } else if( elem.type == DT_HEAT ) { + float previous_amount = elem.amount; + float block_amount = shield.fire_resist(); + elem.amount -= block_amount; + damage_blocked += previous_amount - elem.amount; + } else if( elem.type == DT_ACID ) { + float previous_amount = elem.amount; + float block_amount = shield.acid_resist(); + elem.amount -= block_amount; + damage_blocked += previous_amount - elem.amount; + } + } + + return true; +} + float Character::fall_damage_mod() const { if( has_effect_with_flag( "EFFECT_FEATHER_FALL" ) ) { diff --git a/src/character.h b/src/character.h index 9e4814d8d374..ab7b3c81212f 100644 --- a/src/character.h +++ b/src/character.h @@ -584,6 +584,9 @@ class Character : public Creature, public visitable bool uncanny_dodge() override; + /** Checks for chance that a ranged attack will hit other armor along the way */ + bool block_ranged_hit( Creature *source, bodypart_id &bp_hit, damage_instance &dam ) override; + // melee.cpp /** Checks for valid block abilities and reduces damage accordingly. Returns true if the player blocks */ bool block_hit( Creature *source, bodypart_id &bp_hit, damage_instance &dam ) override; diff --git a/src/creature.cpp b/src/creature.cpp index a05c1542ff4e..cd9bd9547983 100644 --- a/src/creature.cpp +++ b/src/creature.cpp @@ -782,7 +782,8 @@ void Creature::deal_projectile_attack( Creature *source, dealt_projectile_attack impact.mult_damage( 1.0f / dmg_ratio ); } } - + // If we have a shield, it might passively block ranged impacts + block_ranged_hit( source, bp_hit, impact ); dealt_dam = deal_damage( source, bp_hit, impact ); dealt_dam.bp_hit = bp_hit->token; diff --git a/src/creature.h b/src/creature.h index cb9c09db863e..d85c1789bb91 100644 --- a/src/creature.h +++ b/src/creature.h @@ -239,6 +239,10 @@ class Creature virtual bool block_hit( Creature *source, bodypart_id &bp_hit, damage_instance &dam ) = 0; + // handles interaction of shields and ranged attacks. mutates &dam + virtual bool block_ranged_hit( Creature *source, bodypart_id &bp_hit, + damage_instance &dam ) = 0; + // handles armor absorption (including clothing damage etc) // of damage instance. mutates &dam virtual void absorb_hit( const bodypart_id &bp, damage_instance &dam ) = 0; diff --git a/src/monster.cpp b/src/monster.cpp index 0a779fe1c782..e00f4b4b750c 100644 --- a/src/monster.cpp +++ b/src/monster.cpp @@ -1378,6 +1378,11 @@ bool monster::block_hit( Creature *, bodypart_id &, damage_instance & ) return false; } +bool monster::block_ranged_hit( Creature *, bodypart_id &, damage_instance & ) +{ + return false; +} + void monster::absorb_hit( const bodypart_id &, damage_instance &dam ) { for( auto &elem : dam.damage_units ) { diff --git a/src/monster.h b/src/monster.h index a2fe2a9ea77b..511aa12bf33c 100644 --- a/src/monster.h +++ b/src/monster.h @@ -314,6 +314,7 @@ class monster : public Creature, public visitable void absorb_hit( const bodypart_id &bp, damage_instance &dam ) override; bool block_hit( Creature *source, bodypart_id &bp_hit, damage_instance &d ) override; + bool block_ranged_hit( Creature *source, bodypart_id &bp_hit, damage_instance &d ) override; void melee_attack( Creature &target ); void melee_attack( Creature &target, float accuracy ); void melee_attack( Creature &p, bool ) = delete; From a491e94fd4017f59032ccb7ccd76b60d8a6d3693 Mon Sep 17 00:00:00 2001 From: Chaosvolt Date: Sat, 20 May 2023 10:47:18 -0500 Subject: [PATCH 12/23] Apply suggestions from code review Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- src/character.cpp | 3 ++- src/creature.h | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/character.cpp b/src/character.cpp index 25c5fe52cd8e..61b71576f37e 100644 --- a/src/character.cpp +++ b/src/character.cpp @@ -10650,7 +10650,8 @@ bool Character::block_ranged_hit( Creature *source, bodypart_id &bp_hit, damage_ // 1. Best item available doesn't count as a shield. // 2. Shield already protects the part we're interested in. // 3. Targeted bodypart is a foot, unlikely to ever successfully block that low. - if( !shield.has_flag( "BLOCK_WHILE_WORN" ) || shield.covers( bp_hit->token ) || bp_hit == bodypart_str_id( "foot_l" ) || bp_hit == bodypart_str_id( "foot_r" ) ) { + if( !shield.has_flag( "BLOCK_WHILE_WORN" ) || shield.covers( bp_hit->token ) || + bp_hit == bodypart_str_id( "foot_l" ) || bp_hit == bodypart_str_id( "foot_r" ) ) { return false; } diff --git a/src/creature.h b/src/creature.h index d85c1789bb91..4d9ecac480b5 100644 --- a/src/creature.h +++ b/src/creature.h @@ -241,7 +241,7 @@ class Creature // handles interaction of shields and ranged attacks. mutates &dam virtual bool block_ranged_hit( Creature *source, bodypart_id &bp_hit, - damage_instance &dam ) = 0; + damage_instance &dam ) = 0; // handles armor absorption (including clothing damage etc) // of damage instance. mutates &dam From d0477c903dee0d1f0fc697f191647f2ef9b56bae Mon Sep 17 00:00:00 2001 From: Chaosvolt Date: Sat, 20 May 2023 11:10:10 -0500 Subject: [PATCH 13/23] Update character.cpp --- src/character.cpp | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/character.cpp b/src/character.cpp index 61b71576f37e..064beceb83a1 100644 --- a/src/character.cpp +++ b/src/character.cpp @@ -10688,42 +10688,32 @@ bool Character::block_ranged_hit( Creature *source, bodypart_id &bp_hit, damage_ } handle_melee_wear( shield, wear_modifier ); - float total_damage = 0.0; - float damage_blocked = 0.0; - for( auto &elem : dam.damage_units ) { - total_damage += elem.amount; // Go through all relevant damage types and reduce by armor value if one exists. if( elem.type == DT_BASH ) { float previous_amount = elem.amount; float block_amount = shield.bash_resist(); elem.amount -= block_amount; - damage_blocked += previous_amount - elem.amount; } else if( elem.type == DT_CUT ) { float previous_amount = elem.amount; float block_amount = shield.cut_resist(); elem.amount -= block_amount; - damage_blocked += previous_amount - elem.amount; } else if( elem.type == DT_STAB ) { float previous_amount = elem.amount; float block_amount = shield.stab_resist(); elem.amount -= block_amount; - damage_blocked += previous_amount - elem.amount; } else if( elem.type == DT_BULLET ) { float previous_amount = elem.amount; float block_amount = shield.bullet_resist(); elem.amount -= block_amount; - damage_blocked += previous_amount - elem.amount; } else if( elem.type == DT_HEAT ) { float previous_amount = elem.amount; float block_amount = shield.fire_resist(); elem.amount -= block_amount; - damage_blocked += previous_amount - elem.amount; } else if( elem.type == DT_ACID ) { float previous_amount = elem.amount; float block_amount = shield.acid_resist(); elem.amount -= block_amount; - damage_blocked += previous_amount - elem.amount; } } From a2adae32b2ca3a5fe29f30b102aad1c18c116b5e Mon Sep 17 00:00:00 2001 From: Chaosvolt Date: Sat, 20 May 2023 11:36:35 -0500 Subject: [PATCH 14/23] Update character.cpp --- src/character.cpp | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/character.cpp b/src/character.cpp index 064beceb83a1..6db1f46c0112 100644 --- a/src/character.cpp +++ b/src/character.cpp @@ -10691,27 +10691,21 @@ bool Character::block_ranged_hit( Creature *source, bodypart_id &bp_hit, damage_ for( auto &elem : dam.damage_units ) { // Go through all relevant damage types and reduce by armor value if one exists. if( elem.type == DT_BASH ) { - float previous_amount = elem.amount; float block_amount = shield.bash_resist(); elem.amount -= block_amount; } else if( elem.type == DT_CUT ) { - float previous_amount = elem.amount; float block_amount = shield.cut_resist(); elem.amount -= block_amount; } else if( elem.type == DT_STAB ) { - float previous_amount = elem.amount; float block_amount = shield.stab_resist(); elem.amount -= block_amount; } else if( elem.type == DT_BULLET ) { - float previous_amount = elem.amount; float block_amount = shield.bullet_resist(); elem.amount -= block_amount; } else if( elem.type == DT_HEAT ) { - float previous_amount = elem.amount; float block_amount = shield.fire_resist(); elem.amount -= block_amount; } else if( elem.type == DT_ACID ) { - float previous_amount = elem.amount; float block_amount = shield.acid_resist(); elem.amount -= block_amount; } From b18e16dbbaef60bac0dba815c55f14f877fcaf00 Mon Sep 17 00:00:00 2001 From: Chaosvolt Date: Sat, 20 May 2023 12:51:32 -0500 Subject: [PATCH 15/23] [Eternal Screaming] --- src/character.cpp | 25 +++++++++++++++++++------ src/character.h | 2 +- src/creature.cpp | 8 ++++---- src/creature.h | 2 +- src/monster.cpp | 4 ++-- src/monster.h | 2 +- 6 files changed, 28 insertions(+), 15 deletions(-) diff --git a/src/character.cpp b/src/character.cpp index 6db1f46c0112..a7b8bcf34393 100644 --- a/src/character.cpp +++ b/src/character.cpp @@ -10636,14 +10636,14 @@ bool Character::uncanny_dodge() return character_funcs::try_uncanny_dodge( *this ); } -bool Character::block_ranged_hit( Creature *source, bodypart_id &bp_hit, damage_instance &dam ) +float Character::block_ranged_hit( Creature *source, bodypart_id &bp_hit, damage_instance &dam ) { // Having access to more than one shield is not normal in vanilla, for now keep it simple and only give one chance to catch a bullet. item &shield = best_shield(); // Bail out early just in case, if blocking with bare hands. if( shield.is_null() ) { - return false; + return 1.0f; } // Also bail out on the following conditions: @@ -10652,7 +10652,7 @@ bool Character::block_ranged_hit( Creature *source, bodypart_id &bp_hit, damage_ // 3. Targeted bodypart is a foot, unlikely to ever successfully block that low. if( !shield.has_flag( "BLOCK_WHILE_WORN" ) || shield.covers( bp_hit->token ) || bp_hit == bodypart_str_id( "foot_l" ) || bp_hit == bodypart_str_id( "foot_r" ) ) { - return false; + return 1.0f; } // Modify chance based on coverage and blocking ability. Exclude armguards here. @@ -10664,7 +10664,7 @@ bool Character::block_ranged_hit( Creature *source, bodypart_id &bp_hit, damage_ } else if( shield.has_technique( WBLOCK_1 ) ) { shield_coverage_modifier *= 0.4; } else { - return false; + return 1.0f; } // Targeting the legs halves the chance. if( bp_hit == bodypart_str_id( "leg_l" ) || bp_hit == bodypart_str_id( "leg_r" ) ) { @@ -10673,7 +10673,7 @@ bool Character::block_ranged_hit( Creature *source, bodypart_id &bp_hit, damage_ // Now roll coverage to determine if we intercept the shot. if( rng( 1, 100 ) > shield_coverage_modifier ) { - return false; + return 1.0f; } std::string thing_blocked_with = shield.tname(); @@ -10688,30 +10688,43 @@ bool Character::block_ranged_hit( Creature *source, bodypart_id &bp_hit, damage_ } handle_melee_wear( shield, wear_modifier ); + float total_damage = 0.0; + float damage_blocked = 0.0; + for( auto &elem : dam.damage_units ) { + total_damage += elem.amount; + // Go through all relevant damage types and reduce by armor value if one exists. if( elem.type == DT_BASH ) { float block_amount = shield.bash_resist(); elem.amount -= block_amount; + damage_blocked += block_amount; } else if( elem.type == DT_CUT ) { float block_amount = shield.cut_resist(); elem.amount -= block_amount; + damage_blocked += block_amount; } else if( elem.type == DT_STAB ) { float block_amount = shield.stab_resist(); elem.amount -= block_amount; + damage_blocked += block_amount; } else if( elem.type == DT_BULLET ) { float block_amount = shield.bullet_resist(); elem.amount -= block_amount; + damage_blocked += block_amount; } else if( elem.type == DT_HEAT ) { float block_amount = shield.fire_resist(); elem.amount -= block_amount; + damage_blocked += block_amount; } else if( elem.type == DT_ACID ) { float block_amount = shield.acid_resist(); elem.amount -= block_amount; + damage_blocked += block_amount; } } - return true; + float block_result = ( total_damage - damage_blocked ) / total_damage; + + return block_result; } float Character::fall_damage_mod() const diff --git a/src/character.h b/src/character.h index ab7b3c81212f..e40117f5ceda 100644 --- a/src/character.h +++ b/src/character.h @@ -585,7 +585,7 @@ class Character : public Creature, public visitable bool uncanny_dodge() override; /** Checks for chance that a ranged attack will hit other armor along the way */ - bool block_ranged_hit( Creature *source, bodypart_id &bp_hit, damage_instance &dam ) override; + float block_ranged_hit( Creature *source, bodypart_id &bp_hit, damage_instance &dam ) override; // melee.cpp /** Checks for valid block abilities and reduces damage accordingly. Returns true if the player blocks */ diff --git a/src/creature.cpp b/src/creature.cpp index cd9bd9547983..7b0b710c0e53 100644 --- a/src/creature.cpp +++ b/src/creature.cpp @@ -774,17 +774,17 @@ void Creature::deal_projectile_attack( Creature *source, dealt_projectile_attack damage_mult = 1.0f; } - impact.mult_damage( damage_mult ); - if( proj.has_effect( ammo_effect_NOGIB ) ) { float dmg_ratio = static_cast( impact.total_damage() ) / get_hp_max( bp_hit ); if( dmg_ratio > 1.25f ) { impact.mult_damage( 1.0f / dmg_ratio ); } } + // If we have a shield, it might passively block ranged impacts - block_ranged_hit( source, bp_hit, impact ); - dealt_dam = deal_damage( source, bp_hit, impact ); + damage_instance d = impact; + damage_mult *= block_ranged_hit( source, bp_hit, d ); + dealt_dam = deal_damage( source, bp_hit, d ); dealt_dam.bp_hit = bp_hit->token; // Apply ammo effects to target. diff --git a/src/creature.h b/src/creature.h index 4d9ecac480b5..d018e7bb489e 100644 --- a/src/creature.h +++ b/src/creature.h @@ -240,7 +240,7 @@ class Creature damage_instance &dam ) = 0; // handles interaction of shields and ranged attacks. mutates &dam - virtual bool block_ranged_hit( Creature *source, bodypart_id &bp_hit, + virtual float block_ranged_hit( Creature *source, bodypart_id &bp_hit, damage_instance &dam ) = 0; // handles armor absorption (including clothing damage etc) diff --git a/src/monster.cpp b/src/monster.cpp index e00f4b4b750c..b0487ef6e1d5 100644 --- a/src/monster.cpp +++ b/src/monster.cpp @@ -1378,9 +1378,9 @@ bool monster::block_hit( Creature *, bodypart_id &, damage_instance & ) return false; } -bool monster::block_ranged_hit( Creature *, bodypart_id &, damage_instance & ) +float monster::block_ranged_hit( Creature *, bodypart_id &, damage_instance & ) { - return false; + return 1.0f; } void monster::absorb_hit( const bodypart_id &, damage_instance &dam ) diff --git a/src/monster.h b/src/monster.h index 511aa12bf33c..ecf22dc1b542 100644 --- a/src/monster.h +++ b/src/monster.h @@ -314,7 +314,7 @@ class monster : public Creature, public visitable void absorb_hit( const bodypart_id &bp, damage_instance &dam ) override; bool block_hit( Creature *source, bodypart_id &bp_hit, damage_instance &d ) override; - bool block_ranged_hit( Creature *source, bodypart_id &bp_hit, damage_instance &d ) override; + float block_ranged_hit( Creature *source, bodypart_id &bp_hit, damage_instance &d ) override; void melee_attack( Creature &target ); void melee_attack( Creature &target, float accuracy ); void melee_attack( Creature &p, bool ) = delete; From 980b8aea3f1f169fd4d615a23e0956adac50cbd5 Mon Sep 17 00:00:00 2001 From: Chaosvolt Date: Sat, 20 May 2023 12:54:36 -0500 Subject: [PATCH 16/23] Update src/creature.h Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- src/creature.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/creature.h b/src/creature.h index d018e7bb489e..1044552a32ee 100644 --- a/src/creature.h +++ b/src/creature.h @@ -241,7 +241,7 @@ class Creature // handles interaction of shields and ranged attacks. mutates &dam virtual float block_ranged_hit( Creature *source, bodypart_id &bp_hit, - damage_instance &dam ) = 0; + damage_instance &dam ) = 0; // handles armor absorption (including clothing damage etc) // of damage instance. mutates &dam From 892f7cfe988ac898847a5fd2c00c2f28f0889681 Mon Sep 17 00:00:00 2001 From: Chaosvolt Date: Sat, 20 May 2023 16:02:35 -0500 Subject: [PATCH 17/23] Fix it up to a basic working state --- data/json/items/armor/shields.json | 3 +- src/character.cpp | 65 +++++++++++++++++------------- src/character.h | 2 +- src/creature.cpp | 8 ++-- src/creature.h | 4 +- src/monster.cpp | 4 +- src/monster.h | 2 +- 7 files changed, 48 insertions(+), 40 deletions(-) diff --git a/data/json/items/armor/shields.json b/data/json/items/armor/shields.json index 21b0e89dca24..7b5fe239673a 100644 --- a/data/json/items/armor/shields.json +++ b/data/json/items/armor/shields.json @@ -55,7 +55,8 @@ "covers": [ "arm_either", "hand_either" ], "coverage": 100, "encumbrance": 10, - "material_thickness": 6, + "material_thickness": 4, + "environmental_protection": 2, "techniques": [ "WBLOCK_3" ], "flags": [ "OVERSIZE", "BELTED", "RESTRICT_HANDS", "BLOCK_WHILE_WORN" ] } diff --git a/src/character.cpp b/src/character.cpp index a7b8bcf34393..5c3d3f5e3f13 100644 --- a/src/character.cpp +++ b/src/character.cpp @@ -10636,14 +10636,14 @@ bool Character::uncanny_dodge() return character_funcs::try_uncanny_dodge( *this ); } -float Character::block_ranged_hit( Creature *source, bodypart_id &bp_hit, damage_instance &dam ) +bool Character::block_ranged_hit( Creature *source, bodypart_id &bp_hit, damage_instance &dam ) { // Having access to more than one shield is not normal in vanilla, for now keep it simple and only give one chance to catch a bullet. item &shield = best_shield(); // Bail out early just in case, if blocking with bare hands. if( shield.is_null() ) { - return 1.0f; + return false; } // Also bail out on the following conditions: @@ -10652,7 +10652,7 @@ float Character::block_ranged_hit( Creature *source, bodypart_id &bp_hit, damage // 3. Targeted bodypart is a foot, unlikely to ever successfully block that low. if( !shield.has_flag( "BLOCK_WHILE_WORN" ) || shield.covers( bp_hit->token ) || bp_hit == bodypart_str_id( "foot_l" ) || bp_hit == bodypart_str_id( "foot_r" ) ) { - return 1.0f; + return false; } // Modify chance based on coverage and blocking ability. Exclude armguards here. @@ -10664,67 +10664,74 @@ float Character::block_ranged_hit( Creature *source, bodypart_id &bp_hit, damage } else if( shield.has_technique( WBLOCK_1 ) ) { shield_coverage_modifier *= 0.4; } else { - return 1.0f; + return false; } // Targeting the legs halves the chance. if( bp_hit == bodypart_str_id( "leg_l" ) || bp_hit == bodypart_str_id( "leg_r" ) ) { shield_coverage_modifier *= 0.5; } + add_msg( m_debug, _( "block_ranged_hit success rate: %i%%" ), static_cast( shield_coverage_modifier ) ); // Now roll coverage to determine if we intercept the shot. if( rng( 1, 100 ) > shield_coverage_modifier ) { - return 1.0f; + add_msg( m_debug, _( "block_ranged_hit attempt failed" ) ); + return false; } - std::string thing_blocked_with = shield.tname(); - add_msg_player_or_npc( - _( "The shot hits your %s!" ), - _( "The shot hits 's %s!" ), - thing_blocked_with ); - float wear_modifier = 1.0f; if( source != nullptr && source->is_hallucination() ) { wear_modifier = 0.0f; } handle_melee_wear( shield, wear_modifier ); - float total_damage = 0.0; - float damage_blocked = 0.0; + int total_damage = 0; + int blocked_damage = 0; for( auto &elem : dam.damage_units ) { total_damage += elem.amount; - // Go through all relevant damage types and reduce by armor value if one exists. if( elem.type == DT_BASH ) { - float block_amount = shield.bash_resist(); + float block_amount = std::max( 0.0f, ( shield.bash_resist() - elem.res_pen ) ); elem.amount -= block_amount; - damage_blocked += block_amount; + blocked_damage += block_amount; } else if( elem.type == DT_CUT ) { - float block_amount = shield.cut_resist(); + float block_amount = std::max( 0.0f, ( shield.cut_resist() - elem.res_pen ) ); elem.amount -= block_amount; - damage_blocked += block_amount; + blocked_damage += block_amount; } else if( elem.type == DT_STAB ) { - float block_amount = shield.stab_resist(); + float block_amount = std::max( 0.0f, ( shield.stab_resist() - elem.res_pen) ); elem.amount -= block_amount; - damage_blocked += block_amount; + blocked_damage += block_amount; } else if( elem.type == DT_BULLET ) { - float block_amount = shield.bullet_resist(); + float block_amount = std::max( 0.0f, ( shield.bullet_resist() - elem.res_pen ) ); elem.amount -= block_amount; - damage_blocked += block_amount; + blocked_damage += block_amount; } else if( elem.type == DT_HEAT ) { - float block_amount = shield.fire_resist(); + float block_amount = std::max( 0.0f, ( shield.fire_resist() - elem.res_pen ) ); elem.amount -= block_amount; - damage_blocked += block_amount; + blocked_damage += block_amount; } else if( elem.type == DT_ACID ) { - float block_amount = shield.acid_resist(); + float block_amount = std::max( 0.0f, ( shield.acid_resist() - elem.res_pen ) ); elem.amount -= block_amount; - damage_blocked += block_amount; + blocked_damage += block_amount; } } + blocked_damage = std::min( total_damage, blocked_damage ); + std::string thing_blocked_with = shield.tname(); + add_msg( m_debug, _( "expected base damage: %i" ), total_damage ); + if ( blocked_damage > 0 ) { + add_msg_player_or_npc( + _( "The shot hits your %s, absorbing %i damage." ), + _( "The shot hits 's %s, absorbing %i damage." ), + thing_blocked_with, blocked_damage ); + } else { + add_msg_player_or_npc( + _( "The shot hits your %s, but it punches right through!" ), + _( "The shot hits 's %s, but it punches right through!" ), + thing_blocked_with ); + } - float block_result = ( total_damage - damage_blocked ) / total_damage; - - return block_result; + return true; } float Character::fall_damage_mod() const diff --git a/src/character.h b/src/character.h index e40117f5ceda..ab7b3c81212f 100644 --- a/src/character.h +++ b/src/character.h @@ -585,7 +585,7 @@ class Character : public Creature, public visitable bool uncanny_dodge() override; /** Checks for chance that a ranged attack will hit other armor along the way */ - float block_ranged_hit( Creature *source, bodypart_id &bp_hit, damage_instance &dam ) override; + bool block_ranged_hit( Creature *source, bodypart_id &bp_hit, damage_instance &dam ) override; // melee.cpp /** Checks for valid block abilities and reduces damage accordingly. Returns true if the player blocks */ diff --git a/src/creature.cpp b/src/creature.cpp index 7b0b710c0e53..cd9bd9547983 100644 --- a/src/creature.cpp +++ b/src/creature.cpp @@ -774,17 +774,17 @@ void Creature::deal_projectile_attack( Creature *source, dealt_projectile_attack damage_mult = 1.0f; } + impact.mult_damage( damage_mult ); + if( proj.has_effect( ammo_effect_NOGIB ) ) { float dmg_ratio = static_cast( impact.total_damage() ) / get_hp_max( bp_hit ); if( dmg_ratio > 1.25f ) { impact.mult_damage( 1.0f / dmg_ratio ); } } - // If we have a shield, it might passively block ranged impacts - damage_instance d = impact; - damage_mult *= block_ranged_hit( source, bp_hit, d ); - dealt_dam = deal_damage( source, bp_hit, d ); + block_ranged_hit( source, bp_hit, impact ); + dealt_dam = deal_damage( source, bp_hit, impact ); dealt_dam.bp_hit = bp_hit->token; // Apply ammo effects to target. diff --git a/src/creature.h b/src/creature.h index 1044552a32ee..4d9ecac480b5 100644 --- a/src/creature.h +++ b/src/creature.h @@ -240,8 +240,8 @@ class Creature damage_instance &dam ) = 0; // handles interaction of shields and ranged attacks. mutates &dam - virtual float block_ranged_hit( Creature *source, bodypart_id &bp_hit, - damage_instance &dam ) = 0; + virtual bool block_ranged_hit( Creature *source, bodypart_id &bp_hit, + damage_instance &dam ) = 0; // handles armor absorption (including clothing damage etc) // of damage instance. mutates &dam diff --git a/src/monster.cpp b/src/monster.cpp index b0487ef6e1d5..e00f4b4b750c 100644 --- a/src/monster.cpp +++ b/src/monster.cpp @@ -1378,9 +1378,9 @@ bool monster::block_hit( Creature *, bodypart_id &, damage_instance & ) return false; } -float monster::block_ranged_hit( Creature *, bodypart_id &, damage_instance & ) +bool monster::block_ranged_hit( Creature *, bodypart_id &, damage_instance & ) { - return 1.0f; + return false; } void monster::absorb_hit( const bodypart_id &, damage_instance &dam ) diff --git a/src/monster.h b/src/monster.h index ecf22dc1b542..511aa12bf33c 100644 --- a/src/monster.h +++ b/src/monster.h @@ -314,7 +314,7 @@ class monster : public Creature, public visitable void absorb_hit( const bodypart_id &bp, damage_instance &dam ) override; bool block_hit( Creature *source, bodypart_id &bp_hit, damage_instance &d ) override; - float block_ranged_hit( Creature *source, bodypart_id &bp_hit, damage_instance &d ) override; + bool block_ranged_hit( Creature *source, bodypart_id &bp_hit, damage_instance &d ) override; void melee_attack( Creature &target ); void melee_attack( Creature &target, float accuracy ); void melee_attack( Creature &p, bool ) = delete; From 8ec4f9d81970ebb4ced428017ea55a0961dc4030 Mon Sep 17 00:00:00 2001 From: Chaosvolt Date: Sat, 20 May 2023 16:42:32 -0500 Subject: [PATCH 18/23] Continue code tweaks, add ballistic shield --- data/json/items/armor/shields.json | 45 +++++++++++++++++++++++------- src/character.cpp | 36 +++++++++++------------- 2 files changed, 52 insertions(+), 29 deletions(-) diff --git a/data/json/items/armor/shields.json b/data/json/items/armor/shields.json index 7b5fe239673a..06ced6aaba4d 100644 --- a/data/json/items/armor/shields.json +++ b/data/json/items/armor/shields.json @@ -4,9 +4,10 @@ "type": "ARMOR", "name": { "str": "wooden shield" }, "description": "A crude wooden shield, lacking any metal or leather reinforcement. Lightweight but not very tough.", - "weight": "2267 g", - "volume": "3750 ml", - "price": 80000, + "weight": "3 kg", + "volume": "3 L", + "price": "50 USD", + "price_postapoc": "5 USD", "to_hit": -1, "bashing": 8, "material": [ "wood" ], @@ -24,10 +25,11 @@ "type": "ARMOR", "name": { "str": "large wooden shield" }, "description": "An crude wooden tower shield, lacking any metal or leather reinforcement. Bulky, but offers a decent amount of protection.", - "weight": "3828 g", + "weight": "5 kg", "volume": "5 L", - "price": 90000, - "to_hit": -1, + "price": "60 USD", + "price_postapoc": "750 cent", + "to_hit": -2, "bashing": 10, "material": [ "wood" ], "symbol": "[", @@ -44,11 +46,12 @@ "type": "ARMOR", "name": { "str": "riot shield" }, "description": "A large but fairly light plastic shield, designed for riot police officers. Not too encumbering, but designed for fending off thrown rocks rather than bullets.", - "weight": "2700 g", + "weight": "2500 g", "volume": "5 L", - "price": 120000, + "price": "200 USD", + "price_postapoc": "25 USD", "to_hit": -1, - "bashing": 8, + "bashing": 4, "material": [ "plastic" ], "symbol": "[", "color": "light_gray", @@ -56,8 +59,30 @@ "coverage": 100, "encumbrance": 10, "material_thickness": 4, - "environmental_protection": 2, + "environmental_protection": 3, "techniques": [ "WBLOCK_3" ], "flags": [ "OVERSIZE", "BELTED", "RESTRICT_HANDS", "BLOCK_WHILE_WORN" ] + }, + { + "id": "shield_ballistic", + "type": "ARMOR", + "name": { "str": "ballistic shield" }, + "description": "A heavy composite shield used by SWAT teams and other armed forces. It can handle the occasional pistol bullet, but its heavy-duty nature means it's quite encumbering and doesn't cover the legs very well.", + "weight": "8 kg", + "volume": "4 L", + "price": "1000 USD", + "price_postapoc": "50 USD", + "to_hit": -3, + "bashing": 12, + "material": [ "ceramic", "kevlar" ], + "symbol": "[", + "color": "light_gray", + "covers": [ "arm_either", "hand_either" ], + "coverage": 100, + "encumbrance": 30, + "material_thickness": 6, + "environmental_protection": 2, + "techniques": [ "WBLOCK_2" ], + "flags": [ "OVERSIZE", "BELTED", "RESTRICT_HANDS", "BLOCK_WHILE_WORN" ] } ] diff --git a/src/character.cpp b/src/character.cpp index 5c3d3f5e3f13..ad70438e53ea 100644 --- a/src/character.cpp +++ b/src/character.cpp @@ -10655,22 +10655,20 @@ bool Character::block_ranged_hit( Creature *source, bodypart_id &bp_hit, damage_ return false; } - // Modify chance based on coverage and blocking ability. Exclude armguards here. + // Modify chance based on coverage and blocking ability, with lowered chance if hitting the legs. Exclude armguards here. float shield_coverage_modifier = shield.get_coverage(); + bool leg_hit = ( bp_hit == bodypart_str_id( "leg_l" ) || bp_hit == bodypart_str_id( "leg_r" ) ); if( shield.has_technique( WBLOCK_3 ) ) { - shield_coverage_modifier *= 0.8; + shield_coverage_modifier *= leg_hit ? 0.75 : 0.9; } else if( shield.has_technique( WBLOCK_2 ) ) { - shield_coverage_modifier *= 0.6; + shield_coverage_modifier *= leg_hit ? 0.5 : 0.8; } else if( shield.has_technique( WBLOCK_1 ) ) { - shield_coverage_modifier *= 0.4; + shield_coverage_modifier *= leg_hit ? 0.25 : 0.7; } else { return false; } - // Targeting the legs halves the chance. - if( bp_hit == bodypart_str_id( "leg_l" ) || bp_hit == bodypart_str_id( "leg_r" ) ) { - shield_coverage_modifier *= 0.5; - } - add_msg( m_debug, _( "block_ranged_hit success rate: %i%%" ), static_cast( shield_coverage_modifier ) ); + add_msg( m_debug, _( "block_ranged_hit success rate: %i%%" ), + static_cast( shield_coverage_modifier ) ); // Now roll coverage to determine if we intercept the shot. if( rng( 1, 100 ) > shield_coverage_modifier ) { @@ -10699,7 +10697,7 @@ bool Character::block_ranged_hit( Creature *source, bodypart_id &bp_hit, damage_ elem.amount -= block_amount; blocked_damage += block_amount; } else if( elem.type == DT_STAB ) { - float block_amount = std::max( 0.0f, ( shield.stab_resist() - elem.res_pen) ); + float block_amount = std::max( 0.0f, ( shield.stab_resist() - elem.res_pen ) ); elem.amount -= block_amount; blocked_damage += block_amount; } else if( elem.type == DT_BULLET ) { @@ -10719,16 +10717,16 @@ bool Character::block_ranged_hit( Creature *source, bodypart_id &bp_hit, damage_ blocked_damage = std::min( total_damage, blocked_damage ); std::string thing_blocked_with = shield.tname(); add_msg( m_debug, _( "expected base damage: %i" ), total_damage ); - if ( blocked_damage > 0 ) { - add_msg_player_or_npc( - _( "The shot hits your %s, absorbing %i damage." ), - _( "The shot hits 's %s, absorbing %i damage." ), - thing_blocked_with, blocked_damage ); + if( blocked_damage > 0 ) { + add_msg_player_or_npc( + _( "The shot hits your %s, absorbing %i damage." ), + _( "The shot hits 's %s, absorbing %i damage." ), + thing_blocked_with, blocked_damage ); } else { - add_msg_player_or_npc( - _( "The shot hits your %s, but it punches right through!" ), - _( "The shot hits 's %s, but it punches right through!" ), - thing_blocked_with ); + add_msg_player_or_npc( + _( "The shot hits your %s, but it punches right through!" ), + _( "The shot hits 's %s, but it punches right through!" ), + thing_blocked_with ); } return true; From ae1a450fefe7c0795db94295103918f36d9aff43 Mon Sep 17 00:00:00 2001 From: Chaosvolt Date: Sat, 20 May 2023 20:11:05 -0500 Subject: [PATCH 19/23] Update JSON_FLAGS.md --- doc/JSON_FLAGS.md | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/doc/JSON_FLAGS.md b/doc/JSON_FLAGS.md index 9d6d199a4910..5957c1aa3a52 100644 --- a/doc/JSON_FLAGS.md +++ b/doc/JSON_FLAGS.md @@ -64,6 +64,7 @@ - [Skills](#skills) - [Tags](#tags) - [Techniques](#techniques) + - [WBLOCK_X](#wblock-x) - [Tools](#tools) - [Flags](#flags-12) - [Flags that apply to items](#flags-that-apply-to-items) @@ -239,7 +240,7 @@ Some armor flags, such as `WATCH` and `ALARMCLOCK` are compatible with other ite - ```BAROMETER``` This gear is equipped with an accurate barometer (which is used to measure atmospheric pressure). - ```BELTED``` Layer for backpacks and things worn over outerwear. - ```BLIND``` Blinds the wearer while worn, and provides nominal protection v. flashbang flashes. -- ```BLOCK_WHILE_WORN``` Allows worn armor or shields to be used for blocking attacks. +- ```BLOCK_WHILE_WORN``` Allows worn armor or shields to be used for blocking attacks. See also the `Techniques` section. - ```BULLET_IMMNUE``` Wearing an item with this flag makes you immune to bullet damage - ```CLIMATE_CONTROL``` This piece of clothing has climate control of some sort, keeping you warmer or cooler depending on ambient and bodily temperature. - ```COLLAR``` This piece of clothing has a wide collar that can keep your mouth warm. @@ -1260,6 +1261,21 @@ Techniques may be used by tools, armors, weapons and anything else that can be w - See contents of `data/json/techniques.json`. - Techniques are also used with martial arts styles, see `data/json/martialarts.json`. +### WBLOCK_X + +The following weapon techniques have some additional usage. These are defensive techniques that allow the item to assist in blocking attacks in melee, with some additional special uses. + +- ```WBLOCK_1``` "Medium blocking ability" +- ```WBLOCK_2``` "High blocking ability" +- ```WBLOCK_3``` "Very high blocking ability" + +An item with one of these techniques can be wielded to provide a bonus to damage reduced by blocking compared, or armor with the `BLOCK_WHILE_WORN` flag can also provide the use of this bonus while wearing the item, serving as a shield. Additionally, wielding or wearing an item with a combination of one of these techniques plus said flag will allow the item to block projectiles aimed at body parts the item otherwise does not cover (or is not covering, in the case of wielded items that meet those prerequisites). The chance that this will happen is based on the `coverage` percentage of the item used for its normal armor value, reduced by a penalty that depends on which blocking technique it possesses. The chance of it intercepting shots that strike the legs (again, unless the armor was set to cover the legs by default already, in which case it uses `coverage` as normal) is furtther penalized. The feet will always be vulnerable unless (for whatever reason a JSON author may devise, forcefield items for example) an item happens to be a shield that already covers the feet as armor. + + Technique | Chance to intercept (head, torso, opposing arm, etc) | Chance to intercept (legs) +-----------|------------------------------------------------------|------------------------------- + WBLOCK_1 | 90% of default coverage value | 75% of default coverage value + WBLOCK_2 | 90% of default coverage value | 75% of default coverage value + WBLOCK_3 | 90% of default coverage value | 75% of default coverage value ## Tools From 9031323f3e89714083bb52b07ff4e804880c42f5 Mon Sep 17 00:00:00 2001 From: Chaosvolt Date: Sat, 20 May 2023 20:25:02 -0500 Subject: [PATCH 20/23] Update src/character.cpp Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- src/character.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/character.cpp b/src/character.cpp index 9fcfb5c73c5d..1c03242e5988 100644 --- a/src/character.cpp +++ b/src/character.cpp @@ -10702,7 +10702,8 @@ bool Character::block_ranged_hit( Creature *source, bodypart_id &bp_hit, damage_ elem.amount -= block_amount; blocked_damage += block_amount; } else if( elem.type == DT_BULLET ) { - float block_amount = std::max( 0.0f, ( ( shield.bullet_resist() - elem.res_pen ) * elem.res_mult ) ); + float block_amount = std::max( 0.0f, + ( ( shield.bullet_resist() - elem.res_pen ) * elem.res_mult ) ); elem.amount -= block_amount; blocked_damage += block_amount; } else if( elem.type == DT_HEAT ) { From 6d806e478d8ba68a90c61720938d11c1b6f3e0f7 Mon Sep 17 00:00:00 2001 From: scarf Date: Sun, 21 May 2023 10:03:20 +0900 Subject: [PATCH 21/23] refactor: extract covered by shield --- src/character.cpp | 37 ++++++++++++++++++++++++++----------- 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/src/character.cpp b/src/character.cpp index 1c03242e5988..0fcec5382384 100644 --- a/src/character.cpp +++ b/src/character.cpp @@ -10636,22 +10636,37 @@ bool Character::uncanny_dodge() return character_funcs::try_uncanny_dodge( *this ); } +namespace +{ + +auto is_foot_hit( const bodypart_id &bp_hit ) -> bool +{ + return bp_hit == bodypart_str_id( "foot_l" ) || bp_hit == bodypart_str_id( "foot_r" ); +} + +/** + * @brief Check if the given shield can protect the given bodypart. + * + * - Best item available doesn't count as a shield. + * - Shield already protects the part we're interested in. + * - Targeted bodypart is a foot, unlikely to ever successfully block that low. + */ +auto is_covered_by_shield( bodypart_id &bp_hit, item &shield ) -> bool +{ + return shield.has_flag( "BLOCK_WHILE_WORN" ) + && !shield.covers( bp_hit->token ) + && !is_foot_hit( bp_hit ); +} + + +} // namespace + bool Character::block_ranged_hit( Creature *source, bodypart_id &bp_hit, damage_instance &dam ) { // Having access to more than one shield is not normal in vanilla, for now keep it simple and only give one chance to catch a bullet. item &shield = best_shield(); - // Bail out early just in case, if blocking with bare hands. - if( shield.is_null() ) { - return false; - } - - // Also bail out on the following conditions: - // 1. Best item available doesn't count as a shield. - // 2. Shield already protects the part we're interested in. - // 3. Targeted bodypart is a foot, unlikely to ever successfully block that low. - if( !shield.has_flag( "BLOCK_WHILE_WORN" ) || shield.covers( bp_hit->token ) || - bp_hit == bodypart_str_id( "foot_l" ) || bp_hit == bodypart_str_id( "foot_r" ) ) { + if( shield.is_null() || !is_covered_by_shield( bp_hit, shield ) ) { return false; } From 195c2715d7c9ea7bd7e7de9c2e2d68f5327edd91 Mon Sep 17 00:00:00 2001 From: scarf Date: Sun, 21 May 2023 10:30:37 +0900 Subject: [PATCH 22/23] refactor: extract shieldlevel --- src/character.cpp | 59 ++++++++++++++++++++++++++++++++++++----------- 1 file changed, 46 insertions(+), 13 deletions(-) diff --git a/src/character.cpp b/src/character.cpp index 0fcec5382384..a3a7413d4d49 100644 --- a/src/character.cpp +++ b/src/character.cpp @@ -10644,6 +10644,11 @@ auto is_foot_hit( const bodypart_id &bp_hit ) -> bool return bp_hit == bodypart_str_id( "foot_l" ) || bp_hit == bodypart_str_id( "foot_r" ); } +auto is_leg_hit( const bodypart_id &bp_hit ) -> bool +{ + return bp_hit == bodypart_str_id( "leg_l" ) || bp_hit == bodypart_str_id( "leg_r" ); +} + /** * @brief Check if the given shield can protect the given bodypart. * @@ -10651,37 +10656,65 @@ auto is_foot_hit( const bodypart_id &bp_hit ) -> bool * - Shield already protects the part we're interested in. * - Targeted bodypart is a foot, unlikely to ever successfully block that low. */ -auto is_covered_by_shield( bodypart_id &bp_hit, item &shield ) -> bool +auto is_covered_by_shield( const bodypart_id &bp_hit, const item &shield ) -> bool { return shield.has_flag( "BLOCK_WHILE_WORN" ) && !shield.covers( bp_hit->token ) && !is_foot_hit( bp_hit ); } +enum class ShieldLevel { None, Block1, Block2, Block3 }; +auto shield_level( const item &shield ) -> ShieldLevel +{ + static const auto flags = std::vector> { + { WBLOCK_3, ShieldLevel::Block3 }, + { WBLOCK_2, ShieldLevel::Block2 }, + { WBLOCK_1, ShieldLevel::Block1 } + }; + for( const auto& [flag, level] : flags ) { + if( shield.has_technique( flag ) ) { + return level; + } + } + return ShieldLevel::None; +} + +auto coverage_modifier_by_technic( ShieldLevel level, bool leg_hit ) -> float +{ + struct protect { + float leg, others; + }; + static const auto blocks = std::map { + { ShieldLevel::Block1, { 0.1f, 0.3f } }, + { ShieldLevel::Block2, { 0.2f, 0.5f } }, + { ShieldLevel::Block3, { 0.3f, 0.7f } }, + { ShieldLevel::None, { 0.0f, 0.0f } }, + }; + const auto &p = blocks.at( level ); + return leg_hit ? p.leg : p.others; +} } // namespace bool Character::block_ranged_hit( Creature *source, bodypart_id &bp_hit, damage_instance &dam ) { // Having access to more than one shield is not normal in vanilla, for now keep it simple and only give one chance to catch a bullet. - item &shield = best_shield(); + const item &shield = best_shield(); + // Bail out early just in case, if blocking with bare hands. - if( shield.is_null() || !is_covered_by_shield( bp_hit, shield ) ) { + if( shield.is_null() ) { return false; } - // Modify chance based on coverage and blocking ability, with lowered chance if hitting the legs. Exclude armguards here. - float shield_coverage_modifier = shield.get_coverage(); - bool leg_hit = ( bp_hit == bodypart_str_id( "leg_l" ) || bp_hit == bodypart_str_id( "leg_r" ) ); - if( shield.has_technique( WBLOCK_3 ) ) { - shield_coverage_modifier *= leg_hit ? 0.75 : 0.9; - } else if( shield.has_technique( WBLOCK_2 ) ) { - shield_coverage_modifier *= leg_hit ? 0.5 : 0.8; - } else if( shield.has_technique( WBLOCK_1 ) ) { - shield_coverage_modifier *= leg_hit ? 0.25 : 0.7; - } else { + const auto level = shield_level( shield ); + if ( level == ShieldLevel::None || !is_covered_by_shield( bp_hit, shield ) ) { return false; } + // Modify chance based on coverage and blocking ability, with lowered chance if hitting the legs. Exclude armguards here. + const bool leg_hit = is_leg_hit( bp_hit ); + const float coverage_modifier = coverage_modifier_by_technic( level, leg_hit ); + const float shield_coverage_modifier = shield.get_coverage() * coverage_modifier; + add_msg( m_debug, _( "block_ranged_hit success rate: %i%%" ), static_cast( shield_coverage_modifier ) ); From dec2fd31be5e24fda4d6a6db45a217b5bbaa5c23 Mon Sep 17 00:00:00 2001 From: scarf Date: Sun, 21 May 2023 10:34:41 +0900 Subject: [PATCH 23/23] refactor: extract damage calculation --- src/character.cpp | 77 +++++++++++++++++++++++------------------------ 1 file changed, 38 insertions(+), 39 deletions(-) diff --git a/src/character.cpp b/src/character.cpp index a3a7413d4d49..cc29b4f16941 100644 --- a/src/character.cpp +++ b/src/character.cpp @@ -30,6 +30,7 @@ #include "consumption.h" #include "coordinate_conversions.h" #include "coordinates.h" +#include "damage.h" #include "debug.h" #include "disease.h" #include "effect.h" @@ -10694,12 +10695,39 @@ auto coverage_modifier_by_technic( ShieldLevel level, bool leg_hit ) -> float return leg_hit ? p.leg : p.others; } +auto is_valid_hallucination( Creature *source ) -> bool +{ + return source != nullptr && source->is_hallucination(); +} + +auto get_shield_resist( const item &shield, const damage_unit &damage ) -> int +{ + // *INDENT-OFF* + switch( damage.type ) { + case DT_BASH: return shield.bash_resist(); + case DT_CUT: return shield.cut_resist(); + case DT_STAB: return shield.stab_resist(); + case DT_BULLET: return shield.bullet_resist(); + case DT_HEAT: return shield.fire_resist(); + case DT_ACID: return shield.acid_resist(); + default: return 0; + } + // *INDENT-ON* +} + +auto get_block_amount( const item &shield, const damage_unit &unit ) -> int +{ + const int resist = get_shield_resist( shield, unit ); + + return std::max( 0.0f, ( resist - unit.res_pen ) * unit.res_mult ); +} + } // namespace bool Character::block_ranged_hit( Creature *source, bodypart_id &bp_hit, damage_instance &dam ) { // Having access to more than one shield is not normal in vanilla, for now keep it simple and only give one chance to catch a bullet. - const item &shield = best_shield(); + item &shield = best_shield(); // Bail out early just in case, if blocking with bare hands. if( shield.is_null() ) { @@ -10707,13 +10735,12 @@ bool Character::block_ranged_hit( Creature *source, bodypart_id &bp_hit, damage_ } const auto level = shield_level( shield ); - if ( level == ShieldLevel::None || !is_covered_by_shield( bp_hit, shield ) ) { + if( level == ShieldLevel::None || !is_covered_by_shield( bp_hit, shield ) ) { return false; } // Modify chance based on coverage and blocking ability, with lowered chance if hitting the legs. Exclude armguards here. - const bool leg_hit = is_leg_hit( bp_hit ); - const float coverage_modifier = coverage_modifier_by_technic( level, leg_hit ); - const float shield_coverage_modifier = shield.get_coverage() * coverage_modifier; + const float technic_modifier = coverage_modifier_by_technic( level, is_leg_hit( bp_hit ) ); + const float shield_coverage_modifier = shield.get_coverage() * technic_modifier; add_msg( m_debug, _( "block_ranged_hit success rate: %i%%" ), static_cast( shield_coverage_modifier ) ); @@ -10724,49 +10751,22 @@ bool Character::block_ranged_hit( Creature *source, bodypart_id &bp_hit, damage_ return false; } - float wear_modifier = 1.0f; - if( source != nullptr && source->is_hallucination() ) { - wear_modifier = 0.0f; - } + const float wear_modifier = is_valid_hallucination( source ) ? 0.0f : 1.0f; handle_melee_wear( shield, wear_modifier ); int total_damage = 0; int blocked_damage = 0; - for( auto &elem : dam.damage_units ) { total_damage += elem.amount * elem.damage_multiplier; // Go through all relevant damage types and reduce by armor value if one exists. - // Ugly, but need to check resistances separately. - if( elem.type == DT_BASH ) { - float block_amount = std::max( 0.0f, ( ( shield.bash_resist() - elem.res_pen ) * elem.res_mult ) ); - elem.amount -= block_amount; - blocked_damage += block_amount; - } else if( elem.type == DT_CUT ) { - float block_amount = std::max( 0.0f, ( ( shield.cut_resist() - elem.res_pen ) * elem.res_mult ) ); - elem.amount -= block_amount; - blocked_damage += block_amount; - } else if( elem.type == DT_STAB ) { - float block_amount = std::max( 0.0f, ( ( shield.stab_resist() - elem.res_pen ) * elem.res_mult ) ); - elem.amount -= block_amount; - blocked_damage += block_amount; - } else if( elem.type == DT_BULLET ) { - float block_amount = std::max( 0.0f, - ( ( shield.bullet_resist() - elem.res_pen ) * elem.res_mult ) ); - elem.amount -= block_amount; - blocked_damage += block_amount; - } else if( elem.type == DT_HEAT ) { - float block_amount = std::max( 0.0f, ( ( shield.fire_resist() - elem.res_pen ) * elem.res_mult ) ); - elem.amount -= block_amount; - blocked_damage += block_amount; - } else if( elem.type == DT_ACID ) { - float block_amount = std::max( 0.0f, ( ( shield.acid_resist() - elem.res_pen ) * elem.res_mult ) ); - elem.amount -= block_amount; - blocked_damage += block_amount; - } + const float block_amount = get_block_amount( shield, elem ); + elem.amount -= block_amount; + blocked_damage += block_amount; } blocked_damage = std::min( total_damage, blocked_damage ); - std::string thing_blocked_with = shield.tname(); add_msg( m_debug, _( "expected base damage: %i" ), total_damage ); + + const std::string thing_blocked_with = shield.tname(); if( blocked_damage > 0 ) { add_msg_player_or_npc( _( "The shot hits your %s, absorbing %i damage." ), @@ -10778,7 +10778,6 @@ bool Character::block_ranged_hit( Creature *source, bodypart_id &bp_hit, damage_ _( "The shot hits 's %s, but it punches right through!" ), thing_blocked_with ); } - return true; }