From c8cff24e8c75a336c9f82ec813d3546871fe46eb Mon Sep 17 00:00:00 2001 From: GuardianDll Date: Thu, 20 Jun 2024 22:36:28 +0200 Subject: [PATCH 01/15] fail to feed, initial commit --- src/item_factory.cpp | 1 + src/itype.h | 3 +++ src/ranged.cpp | 7 +++++++ 3 files changed, 11 insertions(+) diff --git a/src/item_factory.cpp b/src/item_factory.cpp index 5f75230722fcb..d09444f01763f 100644 --- a/src/item_factory.cpp +++ b/src/item_factory.cpp @@ -3569,6 +3569,7 @@ void Item_factory::load( islot_magazine &slot, const JsonObject &jo, const std:: assign( jo, "count", slot.count, strict, 0 ); assign( jo, "default_ammo", slot.default_ammo, strict ); assign( jo, "reload_time", slot.reload_time, strict, 0 ); + assign( jo, "mag_jam_odds", slot.mag_jam_odds, strict, 0, 1000 ); assign( jo, "linkage", slot.linkage, strict ); } diff --git a/src/itype.h b/src/itype.h index 5a8dcff50cfee..d9fd7cbd22a5b 100644 --- a/src/itype.h +++ b/src/itype.h @@ -945,6 +945,9 @@ struct islot_magazine { /** How long it takes to load each unit of ammo into the magazine */ int reload_time = 100; + /** Permille for the gun to jam, usually due the size of the magazine*/ + int mag_jam_odds = 0; + /** For ammo belts one linkage (of given type) is dropped for each unit of ammo consumed */ std::optional linkage; diff --git a/src/ranged.cpp b/src/ranged.cpp index 4e61fc8532e8e..571a41770b6a7 100644 --- a/src/ranged.cpp +++ b/src/ranged.cpp @@ -703,6 +703,13 @@ bool Character::handle_gun_damage( item &it ) add_msg_player_or_npc( _( "Your %s misfires with a muffled click!" ), _( "'s %s misfires with a muffled click!" ), it.tname() ); + } + // Here we check for a chance for the weapon to suffer a failure to feed + // usually caused by the magazine size or condition + else if ( x_in_y( it.magazine_current()->type->magazine->mag_jam_odds, 1000 )) { + add_msg_player_or_npc(_("Your %s didn't load into the chamber!"), + _("'s %s didn't load into the chamber!"), + it.tname()); return false; // Here we check for a chance for attached mods to get damaged if they are flagged as 'CONSUMABLE'. // This is mostly for crappy handmade expedient stuff or things that rarely receive damage during normal usage. From 9666ad5d6371c34a050cbe3f2c3ef8de7ea8a11f Mon Sep 17 00:00:00 2001 From: GuardianDll Date: Thu, 27 Jun 2024 00:33:00 +0200 Subject: [PATCH 02/15] refine calculations, add field for guns also, add debug tool to damage items --- data/json/items/tool/debug_tools.json | 65 +++++++++++++++++++++++++++ src/item_factory.cpp | 3 +- src/itype.h | 9 +++- src/ranged.cpp | 26 ++++++++--- 4 files changed, 95 insertions(+), 8 deletions(-) diff --git a/data/json/items/tool/debug_tools.json b/data/json/items/tool/debug_tools.json index f45d84ad27d84..755608eb9fdb7 100644 --- a/data/json/items/tool/debug_tools.json +++ b/data/json/items/tool/debug_tools.json @@ -11,5 +11,70 @@ "symbol": ";", "color": "light_gray", "use_action": [ "DBG_LUX_METER" ] + }, + { + "id": "debug_item_damager", + "type": "TOOL", + "category": "tools", + "name": { "str_sp": "debug item damager (simple)" }, + "description": "Deals 1 full bar of damage to an item you wield.", + "weight": "1 g", + "volume": "1 ml", + "material": [ "plastic" ], + "symbol": ";", + "color": "light_gray", + "use_action": { + "type": "effect_on_conditions", + "menu_text": "Damage item", + "effect_on_conditions": [ + { + "id": "EOC_FIND_ITEM_simple", + "effect": { + "u_run_inv_eocs": "all", + "search_data": [ { "wielded_only": true } ], + "true_eocs": { + "id": "EOC_DAMAGE_ITEM_simple", + "effect": [ + { "math": [ "_hp_before", "=", "n_hp('ALL')" ] }, + { "math": [ "n_hp('ALL')", "-=", "1000" ] }, + { "math": [ "_hp_after", "=", "n_hp('ALL')" ] }, + { "u_message": " have had hp, now has hp." } + ] + } + } + } + ] + } + }, + { + "id": "debug_item_damager_adv", + "type": "TOOL", + "category": "tools", + "name": { "str_sp": "debug item damager (advanced)" }, + "description": "Deals damage to items you pick.", + "weight": "1 g", + "volume": "1 ml", + "material": [ "plastic" ], + "symbol": ";", + "color": "light_gray", + "use_action": { + "type": "effect_on_conditions", + "menu_text": "Damage item", + "effect_on_conditions": [ + { + "id": "EOC_FIND_ITEM", + "effect": { + "u_run_inv_eocs": "manual_mult", + "true_eocs": { + "id": "EOC_DAMAGE_ITEM", + "effect": [ + { "math": [ "n_hp('ALL')", "-=", "num_input('Amount of damage, 1000 is one bar of damage.', 1000)" ] }, + { "u_message": "Dealt damage to all items." } + ] + } + } + } + ] + } } ] diff --git a/src/item_factory.cpp b/src/item_factory.cpp index f12a9dc4b0e3b..0239489a98936 100644 --- a/src/item_factory.cpp +++ b/src/item_factory.cpp @@ -2839,6 +2839,7 @@ void Item_factory::load( islot_gun &slot, const JsonObject &jo, const std::strin assign( jo, "heat_per_shot", slot.heat_per_shot, strict, 0.0 ); assign( jo, "cooling_value", slot.cooling_value, strict, 0.0 ); assign( jo, "overheat_threshold", slot.overheat_threshold, strict, -1.0 ); + optional(jo, false, "gun_fail_to_feed_chance", slot.gun_fail_to_feed_chance, 0.00027 ); if( jo.has_array( "valid_mod_locations" ) ) { slot.valid_mod_locations.clear(); @@ -3571,7 +3572,7 @@ void Item_factory::load( islot_magazine &slot, const JsonObject &jo, const std:: assign( jo, "count", slot.count, strict, 0 ); assign( jo, "default_ammo", slot.default_ammo, strict ); assign( jo, "reload_time", slot.reload_time, strict, 0 ); - assign( jo, "mag_jam_odds", slot.mag_jam_odds, strict, 0, 1000 ); + optional(jo, false, "mag_fail_to_feed_chance", slot.mag_fail_to_feed_chance, 0.00053 ); assign( jo, "linkage", slot.linkage, strict ); } diff --git a/src/itype.h b/src/itype.h index 938d1549fdc9f..68f2cecd8e7c8 100644 --- a/src/itype.h +++ b/src/itype.h @@ -787,6 +787,11 @@ struct islot_gun : common_ranged_data { */ double overheat_threshold = -1.0; + /** + * Chance for the gun to fail to feed. + */ + double gun_fail_to_feed_chance = 0.00027; + std::map> cached_ammos; /** @@ -945,8 +950,8 @@ struct islot_magazine { /** How long it takes to load each unit of ammo into the magazine */ int reload_time = 100; - /** Permille for the gun to jam, usually due the size of the magazine*/ - int mag_jam_odds = 0; + /** Permille for the gun to fail to feed, usually due the size of the magazine*/ + double mag_fail_to_feed_chance = 0.00053 ; /** For ammo belts one linkage (of given type) is dropped for each unit of ammo consumed */ std::optional linkage; diff --git a/src/ranged.cpp b/src/ranged.cpp index 250b5104d500f..25f3cc8343cbc 100644 --- a/src/ranged.cpp +++ b/src/ranged.cpp @@ -675,6 +675,20 @@ bool Character::handle_gun_damage( item &it ) return false; } + + double mag_ftf_chance = 0.0; + double mag_damage = 0.0; + if (it.magazine_current()) { + mag_ftf_chance = it.magazine_current()->type->magazine->mag_fail_to_feed_chance; + mag_damage = it.magazine_current()->damage() / 1000.0; + } + const double gun_damage = it.damage() / 1000.0; + const double gun_ftf_chance = firing.gun_fail_to_feed_chance; + + const double jam_chance = (mag_ftf_chance + gun_ftf_chance) * std::pow(2, (gun_damage * 1.75) + (mag_damage * 2)); + + add_msg_debug(debugmode::DF_RANGED, "Gun fail to feed chance: %g\nMagazine fail to feed chance: %g\nGun damage level: %g\nMagazine damage level: %g\nFail to feed chance: %g%%", gun_ftf_chance, mag_ftf_chance, gun_damage, mag_damage, jam_chance * 100); + // Here we check if we're underwater and whether we should misfire. // As a result this causes no damage to the firearm, note that some guns are waterproof // and so are immune to this effect, note also that WATERPROOF_GUN status does not @@ -690,14 +704,16 @@ bool Character::handle_gun_damage( item &it ) // effect as current guns have a durability between 5 and 9 this results in // a chance of mechanical failure between 1/(64*3) and 1/(1024*3) on any given shot. // the malfunction can't cause damage - } else if( one_in( ( 2 << effective_durability ) * 3 ) && !it.has_flag( flag_NEVER_JAMS ) ) { - add_msg_player_or_npc( _( "Your %s malfunctions!" ), - _( "'s %s malfunctions!" ), - it.tname() ); + } + else if (one_in((2 << effective_durability) * 3) && !it.has_flag(flag_NEVER_JAMS)) { + add_msg_player_or_npc(_("Your %s malfunctions!"), + _("'s %s malfunctions!"), + it.tname()); return false; // Here we check for a chance for the weapon to suffer a failure to feed // usually caused by the magazine size or condition - else if ( x_in_y( it.magazine_current()->type->magazine->mag_jam_odds, 1000 )) { + } + else if (x_in_y(jam_chance, 1 )) { add_msg_player_or_npc(_("Your %s didn't load into the chamber!"), _("'s %s didn't load into the chamber!"), it.tname()); From 2bc350c4f51fb4396776d8263b67b0f5c464d614 Mon Sep 17 00:00:00 2001 From: GuardianDll Date: Thu, 15 Aug 2024 22:29:02 +0200 Subject: [PATCH 03/15] add jams, apply not a single jam, but random from group --- data/json/faults/faults_guns.json | 46 +++++++++++++---- src/fault.cpp | 37 +++++++++++++- src/fault.h | 3 ++ src/item_factory.cpp | 4 +- src/itype.h | 8 +-- src/ranged.cpp | 82 ++++++++++++++++++++----------- 6 files changed, 135 insertions(+), 45 deletions(-) diff --git a/data/json/faults/faults_guns.json b/data/json/faults/faults_guns.json index 4c7eb3cf42322..8b95c292ebb60 100644 --- a/data/json/faults/faults_guns.json +++ b/data/json/faults/faults_guns.json @@ -7,14 +7,6 @@ "item_prefix": "rusting", "flags": [ "BLACKPOWDER_FOULING_DAMAGE", "NO_DIRTYING" ] }, - { - "id": "fault_gun_chamber_spent", - "type": "fault", - "name": { "str": "Spent casing in chamber" }, - "description": "This gun currently has an empty casing chambered. It will have to be removed before firing.", - "item_prefix": "jammed", - "flags": [ "JAMMED_GUN" ] - }, { "id": "fault_gun_unlubricated", "type": "fault", @@ -54,6 +46,42 @@ "type": "fault", "name": { "str": "Overheat safety" }, "description": "This weapon will attempt to enter a cooling cycle when overheated.", - "flags": [ "JAMMED_GUN" ] + "flags": [ "OVERHEATED_GUN" ] + }, + { + "id": "fault_fail_to_feed", + "type": "fault", + "//": "gun_mechanical_simple can be fixed on the fly", + "fault_type": "gun_mechanical_simple", + "name": { "str": "Fail to feed" }, + "description": "This gun did not load the round in the chamber properly.", + "item_prefix": "jammed" + }, + { + "id": "fault_gun_chamber_spent", + "//": "aka fail to extract", + "type": "fault", + "fault_type": "gun_mechanical_simple", + "name": { "str": "Spent casing in chamber" }, + "description": "This gun currently has an empty casing in the chamber.", + "item_prefix": "jammed" + }, + { + "id": "fault_stovepipe", + "//": "only closed bolt", + "type": "fault", + "fault_type": "gun_mechanical_simple", + "name": { "str": "Stovepipe" }, + "description": "Casing of the bullet stuck without being properly ejected.", + "item_prefix": "jammed" + }, + { + "id": "fault_double_feed", + "//": "only magazine-fed firearms", + "type": "fault", + "fault_type": "gun_mechanical_simple", + "name": { "str": "Double feed" }, + "description": "Magazine of the gun tried to put two rounds in the chamber at once.", + "item_prefix": "jammed" } ] diff --git a/src/fault.cpp b/src/fault.cpp index 8653874314ce3..0871aa2626f49 100644 --- a/src/fault.cpp +++ b/src/fault.cpp @@ -22,16 +22,51 @@ std::map> faults_by_type; } // namespace -const fault_id &faults::random_of_type( const std::string &type ) +const fault_id faults::get_random_of_type_item_can_have( const item &it, const std::string &type ) { const auto &typed = faults_by_type.find( type ); if( typed == faults_by_type.end() ) { debugmsg( "there are no faults with type '%s'", type ); return fault_id::NULL_ID(); } + + for( const fault_id &fid : typed->second ) { + if( it.faults_potential( ).count( fid ) ) { + return fid; + } + } + + return fault_id::NULL_ID(); +} + +const fault_id &faults::random_of_type( const std::string &type ) +{ + const auto &typed = faults_by_type.find( type ); + if( typed == faults_by_type.end() ) { + debugmsg( "there are no faults with type '%s'", type ); + return fault_id::NULL_ID(); + } return random_entry_ref( typed->second ); } +const fault_id &faults::random_of_type_item_has( const item &it, const std::string &type ) +{ + const auto &typed = faults_by_type.find( type ); + if( typed == faults_by_type.end() ) { + debugmsg( "there are no faults with type '%s'", type ); + return fault_id::NULL_ID(); + } + + // not actually random + for( const fault_id &fid : typed->second ) { + if( it.has_fault( fid ) ) { + return fid; + } + } + + return fault_id::NULL_ID(); +} + void faults::load_fault( const JsonObject &jo, const std::string &src ) { fault_factory.load( jo, src ); diff --git a/src/fault.h b/src/fault.h index 2abb3ab847cac..06d4e499642f9 100644 --- a/src/fault.h +++ b/src/fault.h @@ -29,7 +29,10 @@ void reset(); void finalize(); void check_consistency(); +const fault_id get_random_of_type_item_can_have(const item& it, const std::string& type); + const fault_id &random_of_type( const std::string &type ); +const fault_id &random_of_type_item_has( const item &it, const std::string &type ); } // namespace faults class fault_fix diff --git a/src/item_factory.cpp b/src/item_factory.cpp index 14f1c599b1a24..dadf3dbaebec7 100644 --- a/src/item_factory.cpp +++ b/src/item_factory.cpp @@ -2839,7 +2839,7 @@ void Item_factory::load( islot_gun &slot, const JsonObject &jo, const std::strin assign( jo, "heat_per_shot", slot.heat_per_shot, strict, 0.0 ); assign( jo, "cooling_value", slot.cooling_value, strict, 0.0 ); assign( jo, "overheat_threshold", slot.overheat_threshold, strict, -1.0 ); - optional(jo, false, "gun_fail_to_feed_chance", slot.gun_fail_to_feed_chance, 0.00027 ); + optional(jo, false, "gun_jam_mult", slot.gun_jam_mult, 0.00027 ); if( jo.has_array( "valid_mod_locations" ) ) { slot.valid_mod_locations.clear(); @@ -3574,7 +3574,7 @@ void Item_factory::load( islot_magazine &slot, const JsonObject &jo, const std:: assign( jo, "count", slot.count, strict, 0 ); assign( jo, "default_ammo", slot.default_ammo, strict ); assign( jo, "reload_time", slot.reload_time, strict, 0 ); - optional(jo, false, "mag_fail_to_feed_chance", slot.mag_fail_to_feed_chance, 0.00053 ); + optional(jo, false, "mag_jam_mult", slot.mag_jam_mult, 0.00053 ); assign( jo, "linkage", slot.linkage, strict ); } diff --git a/src/itype.h b/src/itype.h index b5b2a0b2cb34f..d8e98b8caab65 100644 --- a/src/itype.h +++ b/src/itype.h @@ -788,9 +788,9 @@ struct islot_gun : common_ranged_data { double overheat_threshold = -1.0; /** - * Chance for the gun to fail to feed. + * Multiplier of the chance for the gun to jam. */ - double gun_fail_to_feed_chance = 0.00027; + double gun_jam_mult = 1; std::map> cached_ammos; @@ -953,8 +953,8 @@ struct islot_magazine { /** How long it takes to load each unit of ammo into the magazine */ int reload_time = 100; - /** Permille for the gun to fail to feed, usually due the size of the magazine*/ - double mag_fail_to_feed_chance = 0.00053 ; + /** Multiplier for the gun jamming from physical damage */ + double mag_jam_mult = 1 ; /** For ammo belts one linkage (of given type) is dropped for each unit of ammo consumed */ std::optional linkage; diff --git a/src/ranged.cpp b/src/ranged.cpp index 3608db4fecae0..06a810f44ecb7 100644 --- a/src/ranged.cpp +++ b/src/ranged.cpp @@ -33,6 +33,7 @@ #include "enums.h" #include "event.h" #include "event_bus.h" +#include "fault.h" #include "flag.h" #include "game.h" #include "game_constants.h" @@ -174,6 +175,8 @@ static const trait_id trait_PYROMANIA( "PYROMANIA" ); static const trap_str_id tr_practice_target( "tr_practice_target" ); +static const std::string gun_mechanical_simple( "gun_mechanical_simple" ); + static const std::set ferric = { material_iron, material_steel, material_budget_steel, material_case_hardened_steel, material_high_steel, material_low_steel, material_med_steel, material_tempered_steel }; // Maximum duration of aim-and-fire loop, in turns @@ -654,10 +657,29 @@ bool Character::handle_gun_damage( item &it ) int dirt = it.get_var( "dirt", 0 ); int dirtadder = 0; double dirt_dbl = static_cast( dirt ); - if( it.has_fault_flag( "JAMMED_GUN" ) ) { - add_msg_if_player( m_warning, _( "Your %s can't fire." ), it.tname() ); + if( it.has_fault_flag( "OVERHEATED_GUN" ) ) { + add_msg_if_player( m_warning, _( "Your %s is too hot, it is too dangerous to continue the fire." ), + it.tname() ); return false; } + + // if it's a simple fault, character can try to fix it on the fly + if( faults::random_of_type_item_has( it, gun_mechanical_simple ) != fault_id::NULL_ID() ) { + if( one_in( 10 ) ) { + add_msg_if_player( m_warning, + _( "Your %s has some mechanical malfunction. You tried to quickly fix it, and it works now!" ), + it.tname() ); + it.faults.erase( faults::random_of_type_item_has( it, gun_mechanical_simple ) ); + } else { + add_msg_if_player( m_warning, + _( "Your %s has some mechanical malfunction. You tried to quickly fix it, but failed!" ), + it.tname() ); + } + mod_moves( -get_speed() * rng( 1, 3 ) ); + recoil = MAX_RECOIL; + return false; + } + if( it.has_fault_flag( "RUINED_GUN" ) ) { add_msg_if_player( m_bad, _( "Your %s is little more than an awkward club now." ), it.tname() ); return false; @@ -684,19 +706,26 @@ bool Character::handle_gun_damage( item &it ) return false; } - - double mag_ftf_chance = 0.0; - double mag_damage = 0.0; - if (it.magazine_current()) { - mag_ftf_chance = it.magazine_current()->type->magazine->mag_fail_to_feed_chance; - mag_damage = it.magazine_current()->damage() / 1000.0; + double default_gun_jam_chance = 0.00027; + double default_magazine_jam_chance = 0.00053; + double mag_jam_mult = 0; + double mag_damage = 0; + if( it.magazine_current() ) { + mag_jam_mult = it.magazine_current()->type->magazine->mag_jam_mult; + mag_damage = it.magazine_current()->damage() / 1000.0; } const double gun_damage = it.damage() / 1000.0; - const double gun_ftf_chance = firing.gun_fail_to_feed_chance; + const double gun_jam_mult = firing.gun_jam_mult; - const double jam_chance = (mag_ftf_chance + gun_ftf_chance) * std::pow(2, (gun_damage * 1.75) + (mag_damage * 2)); - - add_msg_debug(debugmode::DF_RANGED, "Gun fail to feed chance: %g\nMagazine fail to feed chance: %g\nGun damage level: %g\nMagazine damage level: %g\nFail to feed chance: %g%%", gun_ftf_chance, mag_ftf_chance, gun_damage, mag_damage, jam_chance * 100); + double gun_jam_chance = default_gun_jam_chance * gun_jam_mult; + double mag_jam_chance = default_magazine_jam_chance * mag_jam_mult; + + const double jam_chance = ( gun_jam_chance + mag_jam_chance ) * std::pow( 2, + ( gun_damage * 1.75 ) + ( mag_damage * 2 ) ); + + add_msg_debug( debugmode::DF_RANGED, + "Gun jam chance: %g\nMagazine jam chance: %g\nGun damage level: %g\nMagazine damage level: %g\nFail to feed chance: %g%%", + gun_jam_chance, mag_jam_chance, gun_damage, mag_damage, jam_chance * 100 ); // Here we check if we're underwater and whether we should misfire. // As a result this causes no damage to the firearm, note that some guns are waterproof @@ -713,20 +742,20 @@ bool Character::handle_gun_damage( item &it ) // effect as current guns have a durability between 5 and 9 this results in // a chance of mechanical failure between 1/(64*3) and 1/(1024*3) on any given shot. // the malfunction can't cause damage - } - else if (one_in((2 << effective_durability) * 3) && !it.has_flag(flag_NEVER_JAMS)) { - add_msg_player_or_npc(_("Your %s malfunctions!"), - _("'s %s malfunctions!"), - it.tname()); + } else if( one_in( ( 2 << effective_durability ) * 3 ) && !it.has_flag( flag_NEVER_JAMS ) ) { + add_msg_player_or_npc( _( "Your %s malfunctions!" ), + _( "'s %s malfunctions!" ), + it.tname() ); return false; - // Here we check for a chance for the weapon to suffer a failure to feed - // usually caused by the magazine size or condition - } - else if (x_in_y(jam_chance, 1 )) { - add_msg_player_or_npc(_("Your %s didn't load into the chamber!"), - _("'s %s didn't load into the chamber!"), - it.tname()); + + // Chance for the weapon to suffer a failure, caused by the magazine size, quality, or condition + } else if( x_in_y( jam_chance, 1 ) ) { + add_msg_player_or_npc( _( "Your %s didn't load into the chamber!" ), + _( "'s %s didn't load into the chamber!" ), + it.tname() ); + it.faults.insert( faults::get_random_of_type_item_can_have( it, gun_mechanical_simple ) ); return false; + // Here we check for a chance for attached mods to get damaged if they are flagged as 'CONSUMABLE'. // This is mostly for crappy handmade expedient stuff or things that rarely receive damage during normal usage. // Default chance is 1/10000 unless set via json, damage is proportional to caliber(see below). @@ -988,11 +1017,6 @@ int Character::fire_gun( const tripoint &target, int shots, item &gun, item_loca you.burn_energy_arms( - gun.get_min_str() * static_cast( 0.006f * get_option( "PLAYER_MAX_STAMINA_BASE" ) ) ); } - if( gun.faults.count( fault_gun_chamber_spent ) && curshot == 0 ) { - mod_moves( -get_speed() * 0.5 ); - gun.faults.erase( fault_gun_chamber_spent ); - add_msg_if_player( _( "You cycle your %s manually." ), gun.tname() ); - } if( !handle_gun_damage( gun ) ) { break; From e008cfee2a4cf375fcdb490389cc91a8959ca939 Mon Sep 17 00:00:00 2001 From: GuardianDll Date: Fri, 16 Aug 2024 14:33:21 +0200 Subject: [PATCH 04/15] move quick fixing to aim activity actor --- src/activity_actor.cpp | 48 +++++++++++++++++++++++++++++++- src/activity_actor_definitions.h | 1 + src/ranged.cpp | 27 ------------------ 3 files changed, 48 insertions(+), 28 deletions(-) diff --git a/src/activity_actor.cpp b/src/activity_actor.cpp index 4f00739a03709..af450064e92aa 100644 --- a/src/activity_actor.cpp +++ b/src/activity_actor.cpp @@ -46,6 +46,7 @@ #include "event_bus.h" #include "faction.h" #include "field_type.h" +#include "fault.h" #include "flag.h" #include "flexbuffer_json-inl.h" #include "flexbuffer_json.h" @@ -252,6 +253,7 @@ static const quality_id qual_SHEAR( "SHEAR" ); static const skill_id skill_computer( "computer" ); static const skill_id skill_electronics( "electronics" ); static const skill_id skill_fabrication( "fabrication" ); +static const skill_id skill_gun( "gun" ); static const skill_id skill_mechanics( "mechanics" ); static const skill_id skill_survival( "survival" ); static const skill_id skill_traps( "traps" ); @@ -283,6 +285,8 @@ static const zone_type_id zone_type_LOOT_IGNORE_FAVORITES( "LOOT_IGNORE_FAVORITE static const zone_type_id zone_type_STRIP_CORPSES( "STRIP_CORPSES" ); static const zone_type_id zone_type_UNLOAD_ALL( "UNLOAD_ALL" ); +static const std::string gun_mechanical_simple( "gun_mechanical_simple" ); + std::string activity_actor::get_progress_message( const player_activity &act ) const { if( act.moves_total > 0 ) { @@ -317,13 +321,55 @@ aim_activity_actor aim_activity_actor::use_mutation( const item &fake_gun ) return act; } -void aim_activity_actor::start( player_activity &act, Character &/*who*/ ) +void aim_activity_actor::start( player_activity &act, Character &who ) { + item_location weapon = get_weapon(); + item it = *weapon.get_item(); + + if( !check_gun_ability_to_shoot( who, it ) ) { + aborted = true; // why doesn't interrupt? + act.set_to_null(); + } + // Time spent on aiming is determined on the go by the player act.moves_total = 1; act.moves_left = 1; } +bool aim_activity_actor::check_gun_ability_to_shoot( Character &who, item &it ) +{ + + if( it.has_fault_flag( "RUINED_GUN" ) ) { + who.add_msg_if_player( m_bad, _( "Your %s is little more than an awkward club now." ), it.tname() ); + return false; + } + + // if it's a simple fault, character can try to fix it on the fly + if( faults::random_of_type_item_has( it, gun_mechanical_simple ) != fault_id::NULL_ID() ) { + who.mod_moves( -who.get_speed() * rng( 1, 3 ) ); + who.recoil = MAX_RECOIL; + if( one_in( std::max( 2.0f, ( 7 - ( 2 * who.get_skill_level( skill_gun ) ) ) ) ) ) { + who.add_msg_if_player( m_good, + _( "Your %s has some mechanical malfunction. You tried to quickly fix it, and it works now!" ), + it.tname() ); + it.faults.erase( faults::random_of_type_item_has( it, gun_mechanical_simple ) ); + } else { + who.add_msg_if_player( m_warning, + _( "Your %s has some mechanical malfunction. You tried to quickly fix it, but failed!" ), + it.tname() ); + return false; + } + } + + if( it.has_fault_flag( "OVERHEATED_GUN" ) ) { + who.add_msg_if_player( m_warning, + _( "Your %s is too hot, and little screen signalizes the gun is inoperable." ), it.tname() ); + return false; + } + + return true; +} + void aim_activity_actor::do_turn( player_activity &act, Character &who ) { if( !who.is_avatar() ) { diff --git a/src/activity_actor_definitions.h b/src/activity_actor_definitions.h index 56397e22db89c..936158c5a09f2 100644 --- a/src/activity_actor_definitions.h +++ b/src/activity_actor_definitions.h @@ -90,6 +90,7 @@ class aim_activity_actor : public activity_actor } void start( player_activity &act, Character &who ) override; + bool check_gun_ability_to_shoot( Character &who, item &it ); void do_turn( player_activity &act, Character &who ) override; void finish( player_activity &act, Character &who ) override; void canceled( player_activity &act, Character &who ) override; diff --git a/src/ranged.cpp b/src/ranged.cpp index 06a810f44ecb7..d812d3b3de53b 100644 --- a/src/ranged.cpp +++ b/src/ranged.cpp @@ -657,33 +657,6 @@ bool Character::handle_gun_damage( item &it ) int dirt = it.get_var( "dirt", 0 ); int dirtadder = 0; double dirt_dbl = static_cast( dirt ); - if( it.has_fault_flag( "OVERHEATED_GUN" ) ) { - add_msg_if_player( m_warning, _( "Your %s is too hot, it is too dangerous to continue the fire." ), - it.tname() ); - return false; - } - - // if it's a simple fault, character can try to fix it on the fly - if( faults::random_of_type_item_has( it, gun_mechanical_simple ) != fault_id::NULL_ID() ) { - if( one_in( 10 ) ) { - add_msg_if_player( m_warning, - _( "Your %s has some mechanical malfunction. You tried to quickly fix it, and it works now!" ), - it.tname() ); - it.faults.erase( faults::random_of_type_item_has( it, gun_mechanical_simple ) ); - } else { - add_msg_if_player( m_warning, - _( "Your %s has some mechanical malfunction. You tried to quickly fix it, but failed!" ), - it.tname() ); - } - mod_moves( -get_speed() * rng( 1, 3 ) ); - recoil = MAX_RECOIL; - return false; - } - - if( it.has_fault_flag( "RUINED_GUN" ) ) { - add_msg_if_player( m_bad, _( "Your %s is little more than an awkward club now." ), it.tname() ); - return false; - } const auto &curammo_effects = it.ammo_effects(); const islot_gun &firing = *it.type->gun; From 8391eb377b465876144d62953183441882db8260 Mon Sep 17 00:00:00 2001 From: GuardianDll Date: Fri, 16 Aug 2024 18:02:39 +0200 Subject: [PATCH 05/15] better calculations for jam chance --- src/ranged.cpp | 57 ++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 44 insertions(+), 13 deletions(-) diff --git a/src/ranged.cpp b/src/ranged.cpp index d812d3b3de53b..9177600f48a33 100644 --- a/src/ranged.cpp +++ b/src/ranged.cpp @@ -679,22 +679,52 @@ bool Character::handle_gun_damage( item &it ) return false; } - double default_gun_jam_chance = 0.00027; - double default_magazine_jam_chance = 0.00053; - double mag_jam_mult = 0; - double mag_damage = 0; + + // i am bad at math, so we will use vibes instead + double gun_jam_chance; + int gun_damage = it.damage() / 1000.0; + switch( gun_damage ) { + case 0: + gun_jam_chance = 0.0000018 * firing.gun_jam_mult; + break; + case 1: + gun_jam_chance = 0.03 * firing.gun_jam_mult; + break; + case 2: + gun_jam_chance = 0.15 * firing.gun_jam_mult; + break; + case 3: + gun_jam_chance = 0.6 * firing.gun_jam_mult; + break; + case 4: + gun_jam_chance = 1.5 * firing.gun_jam_mult; + break; + } + + int mag_damage; + double mag_jam_chance = 0; if( it.magazine_current() ) { - mag_jam_mult = it.magazine_current()->type->magazine->mag_jam_mult; mag_damage = it.magazine_current()->damage() / 1000.0; + switch( mag_damage ) { + case 0: + mag_jam_chance = 0.00000288 * it.magazine_current()->type->magazine->mag_jam_mult; + break; + case 1: + mag_jam_chance = 0.05 * it.magazine_current()->type->magazine->mag_jam_mult; + break; + case 2: + mag_jam_chance = 0.24 * it.magazine_current()->type->magazine->mag_jam_mult; + break; + case 3: + mag_jam_chance = 0.96 * it.magazine_current()->type->magazine->mag_jam_mult; + break; + case 4: + mag_jam_chance = 2.5 * it.magazine_current()->type->magazine->mag_jam_mult; + break; + } } - const double gun_damage = it.damage() / 1000.0; - const double gun_jam_mult = firing.gun_jam_mult; - - double gun_jam_chance = default_gun_jam_chance * gun_jam_mult; - double mag_jam_chance = default_magazine_jam_chance * mag_jam_mult; - const double jam_chance = ( gun_jam_chance + mag_jam_chance ) * std::pow( 2, - ( gun_damage * 1.75 ) + ( mag_damage * 2 ) ); + const double jam_chance = ( gun_jam_chance + mag_jam_chance ) * 1.8; add_msg_debug( debugmode::DF_RANGED, "Gun jam chance: %g\nMagazine jam chance: %g\nGun damage level: %g\nMagazine damage level: %g\nFail to feed chance: %g%%", @@ -722,7 +752,8 @@ bool Character::handle_gun_damage( item &it ) return false; // Chance for the weapon to suffer a failure, caused by the magazine size, quality, or condition - } else if( x_in_y( jam_chance, 1 ) ) { + } else if( x_in_y( jam_chance, 1 ) && + faults::get_random_of_type_item_can_have( it, gun_mechanical_simple ) != fault_id::NULL_ID() ) { add_msg_player_or_npc( _( "Your %s didn't load into the chamber!" ), _( "'s %s didn't load into the chamber!" ), it.tname() ); From dbcf82fe26775f6cb7d9a07c4facd8f5c63265b4 Mon Sep 17 00:00:00 2001 From: GuardianDll Date: Fri, 16 Aug 2024 19:01:27 +0200 Subject: [PATCH 06/15] swap fault functions because github do not show them correctly --- src/fault.cpp | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/src/fault.cpp b/src/fault.cpp index 0871aa2626f49..f3d5e9f8565c7 100644 --- a/src/fault.cpp +++ b/src/fault.cpp @@ -22,23 +22,6 @@ std::map> faults_by_type; } // namespace -const fault_id faults::get_random_of_type_item_can_have( const item &it, const std::string &type ) -{ - const auto &typed = faults_by_type.find( type ); - if( typed == faults_by_type.end() ) { - debugmsg( "there are no faults with type '%s'", type ); - return fault_id::NULL_ID(); - } - - for( const fault_id &fid : typed->second ) { - if( it.faults_potential( ).count( fid ) ) { - return fid; - } - } - - return fault_id::NULL_ID(); -} - const fault_id &faults::random_of_type( const std::string &type ) { const auto &typed = faults_by_type.find( type ); @@ -60,6 +43,24 @@ const fault_id &faults::random_of_type_item_has( const item &it, const std::stri // not actually random for( const fault_id &fid : typed->second ) { if( it.has_fault( fid ) ) { + debugmsg( "picked fault: '%s'", fid.c_str() ); + return fid; + } + } + + return fault_id::NULL_ID(); +} + +const fault_id faults::get_random_of_type_item_can_have(const item& it, const std::string& type) +{ + const auto& typed = faults_by_type.find(type); + if (typed == faults_by_type.end()) { + debugmsg("there are no faults with type '%s'", type); + return fault_id::NULL_ID(); + } + + for (const fault_id& fid : typed->second) { + if (it.faults_potential().count(fid)) { return fid; } } From 0510a468bb03bcd16ae4dee9f6e27885db222003 Mon Sep 17 00:00:00 2001 From: GuardianDll Date: Sat, 17 Aug 2024 00:03:47 +0200 Subject: [PATCH 07/15] mics changes --- src/fault.cpp | 14 +++++++------- src/fault.h | 2 +- src/item_factory.cpp | 4 ++-- src/ranged.cpp | 4 ++-- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/fault.cpp b/src/fault.cpp index f3d5e9f8565c7..2d91ffbc49394 100644 --- a/src/fault.cpp +++ b/src/fault.cpp @@ -24,7 +24,7 @@ std::map> faults_by_type; const fault_id &faults::random_of_type( const std::string &type ) { - const auto &typed = faults_by_type.find( type ); + const auto &typed = faults_by_type.find( type ); if( typed == faults_by_type.end() ) { debugmsg( "there are no faults with type '%s'", type ); return fault_id::NULL_ID(); @@ -51,16 +51,16 @@ const fault_id &faults::random_of_type_item_has( const item &it, const std::stri return fault_id::NULL_ID(); } -const fault_id faults::get_random_of_type_item_can_have(const item& it, const std::string& type) +const fault_id &faults::get_random_of_type_item_can_have( const item &it, const std::string &type ) { - const auto& typed = faults_by_type.find(type); - if (typed == faults_by_type.end()) { - debugmsg("there are no faults with type '%s'", type); + const auto &typed = faults_by_type.find( type ); + if( typed == faults_by_type.end() ) { + debugmsg( "there are no faults with type '%s'", type ); return fault_id::NULL_ID(); } - for (const fault_id& fid : typed->second) { - if (it.faults_potential().count(fid)) { + for( const fault_id &fid : typed->second ) { + if( it.faults_potential().count( fid ) ) { return fid; } } diff --git a/src/fault.h b/src/fault.h index 06d4e499642f9..8c75e4c1e4310 100644 --- a/src/fault.h +++ b/src/fault.h @@ -29,7 +29,7 @@ void reset(); void finalize(); void check_consistency(); -const fault_id get_random_of_type_item_can_have(const item& it, const std::string& type); +const fault_id &get_random_of_type_item_can_have(const item& it, const std::string& type); const fault_id &random_of_type( const std::string &type ); const fault_id &random_of_type_item_has( const item &it, const std::string &type ); diff --git a/src/item_factory.cpp b/src/item_factory.cpp index dadf3dbaebec7..fe511f4cc5aad 100644 --- a/src/item_factory.cpp +++ b/src/item_factory.cpp @@ -2839,7 +2839,7 @@ void Item_factory::load( islot_gun &slot, const JsonObject &jo, const std::strin assign( jo, "heat_per_shot", slot.heat_per_shot, strict, 0.0 ); assign( jo, "cooling_value", slot.cooling_value, strict, 0.0 ); assign( jo, "overheat_threshold", slot.overheat_threshold, strict, -1.0 ); - optional(jo, false, "gun_jam_mult", slot.gun_jam_mult, 0.00027 ); + optional(jo, false, "gun_jam_mult", slot.gun_jam_mult, 1 ); if( jo.has_array( "valid_mod_locations" ) ) { slot.valid_mod_locations.clear(); @@ -3574,7 +3574,7 @@ void Item_factory::load( islot_magazine &slot, const JsonObject &jo, const std:: assign( jo, "count", slot.count, strict, 0 ); assign( jo, "default_ammo", slot.default_ammo, strict ); assign( jo, "reload_time", slot.reload_time, strict, 0 ); - optional(jo, false, "mag_jam_mult", slot.mag_jam_mult, 0.00053 ); + optional(jo, false, "mag_jam_mult", slot.mag_jam_mult, 1 ); assign( jo, "linkage", slot.linkage, strict ); } diff --git a/src/ranged.cpp b/src/ranged.cpp index 9177600f48a33..86878f2f9d744 100644 --- a/src/ranged.cpp +++ b/src/ranged.cpp @@ -727,8 +727,8 @@ bool Character::handle_gun_damage( item &it ) const double jam_chance = ( gun_jam_chance + mag_jam_chance ) * 1.8; add_msg_debug( debugmode::DF_RANGED, - "Gun jam chance: %g\nMagazine jam chance: %g\nGun damage level: %g\nMagazine damage level: %g\nFail to feed chance: %g%%", - gun_jam_chance, mag_jam_chance, gun_damage, mag_damage, jam_chance * 100 ); + "Gun jam chance: %s\nMagazine jam chance: %s\nGun damage level: %d\nMagazine damage level: %d\nFail to feed chance: %s", + gun_jam_chance, mag_jam_chance, gun_damage, mag_damage, jam_chance ); // Here we check if we're underwater and whether we should misfire. // As a result this causes no damage to the firearm, note that some guns are waterproof From e714e292a9b6eae43166bc4579c520475c234ed4 Mon Sep 17 00:00:00 2001 From: GuardianDll Date: Sat, 17 Aug 2024 23:58:49 +0200 Subject: [PATCH 08/15] Prevent loop of "fixed fail to feed - found the gun failed to feed" --- src/activity_actor.cpp | 2 ++ src/itype.h | 5 +++++ src/ranged.cpp | 8 +++++++- 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/activity_actor.cpp b/src/activity_actor.cpp index af450064e92aa..1d54a7bbdcddd 100644 --- a/src/activity_actor.cpp +++ b/src/activity_actor.cpp @@ -353,6 +353,8 @@ bool aim_activity_actor::check_gun_ability_to_shoot( Character &who, item &it ) _( "Your %s has some mechanical malfunction. You tried to quickly fix it, and it works now!" ), it.tname() ); it.faults.erase( faults::random_of_type_item_has( it, gun_mechanical_simple ) ); + islot_gun &firing = *it.type->gun; + firing.u_know_round_in_chamber = true; } else { who.add_msg_if_player( m_warning, _( "Your %s has some mechanical malfunction. You tried to quickly fix it, but failed!" ), diff --git a/src/itype.h b/src/itype.h index d8e98b8caab65..f8e38278508d4 100644 --- a/src/itype.h +++ b/src/itype.h @@ -792,6 +792,11 @@ struct islot_gun : common_ranged_data { */ double gun_jam_mult = 1; + /** + * character spend time to repair failure to feed, so if true, it is guarantee the round is in the chamber + */ + bool u_know_round_in_chamber = false; + std::map> cached_ammos; /** diff --git a/src/ranged.cpp b/src/ranged.cpp index 86878f2f9d744..82843d212c33e 100644 --- a/src/ranged.cpp +++ b/src/ranged.cpp @@ -752,7 +752,7 @@ bool Character::handle_gun_damage( item &it ) return false; // Chance for the weapon to suffer a failure, caused by the magazine size, quality, or condition - } else if( x_in_y( jam_chance, 1 ) && + } else if( x_in_y( jam_chance, 1 ) && !firing.u_know_round_in_chamber && faults::get_random_of_type_item_can_have( it, gun_mechanical_simple ) != fault_id::NULL_ID() ) { add_msg_player_or_npc( _( "Your %s didn't load into the chamber!" ), _( "'s %s didn't load into the chamber!" ), @@ -853,6 +853,12 @@ bool Character::handle_gun_damage( item &it ) // Don't increment until after the message it.inc_damage(); } + + if( firing.u_know_round_in_chamber ) { + islot_gun gun = *it.type->gun; + gun.u_know_round_in_chamber = false; + } + return true; } From 1d4f1236a21125a191b403b5c91dad3aa4a9bf10 Mon Sep 17 00:00:00 2001 From: GuardianDll Date: Sun, 18 Aug 2024 00:16:39 +0200 Subject: [PATCH 09/15] mics changes --- src/activity_actor.cpp | 2 +- src/fault.cpp | 1 - src/ranged.cpp | 8 ++++---- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/activity_actor.cpp b/src/activity_actor.cpp index 1d54a7bbdcddd..387ed8e14b6de 100644 --- a/src/activity_actor.cpp +++ b/src/activity_actor.cpp @@ -324,7 +324,7 @@ aim_activity_actor aim_activity_actor::use_mutation( const item &fake_gun ) void aim_activity_actor::start( player_activity &act, Character &who ) { item_location weapon = get_weapon(); - item it = *weapon.get_item(); + item &it = *weapon.get_item(); if( !check_gun_ability_to_shoot( who, it ) ) { aborted = true; // why doesn't interrupt? diff --git a/src/fault.cpp b/src/fault.cpp index 2d91ffbc49394..ce89c757ce3e1 100644 --- a/src/fault.cpp +++ b/src/fault.cpp @@ -43,7 +43,6 @@ const fault_id &faults::random_of_type_item_has( const item &it, const std::stri // not actually random for( const fault_id &fid : typed->second ) { if( it.has_fault( fid ) ) { - debugmsg( "picked fault: '%s'", fid.c_str() ); return fid; } } diff --git a/src/ranged.cpp b/src/ranged.cpp index 82843d212c33e..4912ab076fa12 100644 --- a/src/ranged.cpp +++ b/src/ranged.cpp @@ -694,10 +694,10 @@ bool Character::handle_gun_damage( item &it ) gun_jam_chance = 0.15 * firing.gun_jam_mult; break; case 3: - gun_jam_chance = 0.6 * firing.gun_jam_mult; + gun_jam_chance = 0.45 * firing.gun_jam_mult; break; case 4: - gun_jam_chance = 1.5 * firing.gun_jam_mult; + gun_jam_chance = 0.8 * firing.gun_jam_mult; break; } @@ -754,8 +754,8 @@ bool Character::handle_gun_damage( item &it ) // Chance for the weapon to suffer a failure, caused by the magazine size, quality, or condition } else if( x_in_y( jam_chance, 1 ) && !firing.u_know_round_in_chamber && faults::get_random_of_type_item_can_have( it, gun_mechanical_simple ) != fault_id::NULL_ID() ) { - add_msg_player_or_npc( _( "Your %s didn't load into the chamber!" ), - _( "'s %s didn't load into the chamber!" ), + add_msg_player_or_npc( m_bad, _( "Your %s malfunctions!" ), + _( "'s %s malfunctions!" ), it.tname() ); it.faults.insert( faults::get_random_of_type_item_can_have( it, gun_mechanical_simple ) ); return false; From 22a1ad8cd8d9577363e34a8af87b56eaa31e74c8 Mon Sep 17 00:00:00 2001 From: GuardianDll Date: Sun, 18 Aug 2024 16:04:05 +0200 Subject: [PATCH 10/15] mics changes --- src/activity_actor.cpp | 9 ++++----- src/itype.h | 5 ----- src/ranged.cpp | 8 ++++---- 3 files changed, 8 insertions(+), 14 deletions(-) diff --git a/src/activity_actor.cpp b/src/activity_actor.cpp index 387ed8e14b6de..a2a1cb6f54b5b 100644 --- a/src/activity_actor.cpp +++ b/src/activity_actor.cpp @@ -346,17 +346,16 @@ bool aim_activity_actor::check_gun_ability_to_shoot( Character &who, item &it ) // if it's a simple fault, character can try to fix it on the fly if( faults::random_of_type_item_has( it, gun_mechanical_simple ) != fault_id::NULL_ID() ) { - who.mod_moves( -who.get_speed() * rng( 1, 3 ) ); + who.mod_moves( -who.get_speed() * rng( 2, 3 ) ); who.recoil = MAX_RECOIL; - if( one_in( std::max( 2.0f, ( 7 - ( 2 * who.get_skill_level( skill_gun ) ) ) ) ) ) { + if( one_in( std::max( 5.0f, ( 10.0f - ( 2.0f * who.get_skill_level( skill_gun ) ) ) ) ) ) { who.add_msg_if_player( m_good, _( "Your %s has some mechanical malfunction. You tried to quickly fix it, and it works now!" ), it.tname() ); it.faults.erase( faults::random_of_type_item_has( it, gun_mechanical_simple ) ); - islot_gun &firing = *it.type->gun; - firing.u_know_round_in_chamber = true; + it.set_var( "u_know_round_in_chamber", true ); } else { - who.add_msg_if_player( m_warning, + who.add_msg_if_player( m_bad, _( "Your %s has some mechanical malfunction. You tried to quickly fix it, but failed!" ), it.tname() ); return false; diff --git a/src/itype.h b/src/itype.h index f8e38278508d4..d8e98b8caab65 100644 --- a/src/itype.h +++ b/src/itype.h @@ -792,11 +792,6 @@ struct islot_gun : common_ranged_data { */ double gun_jam_mult = 1; - /** - * character spend time to repair failure to feed, so if true, it is guarantee the round is in the chamber - */ - bool u_know_round_in_chamber = false; - std::map> cached_ammos; /** diff --git a/src/ranged.cpp b/src/ranged.cpp index 4912ab076fa12..9e9459d88e1d9 100644 --- a/src/ranged.cpp +++ b/src/ranged.cpp @@ -725,6 +725,7 @@ bool Character::handle_gun_damage( item &it ) } const double jam_chance = ( gun_jam_chance + mag_jam_chance ) * 1.8; + bool u_know_round_in_chamber = it.has_var( "u_know_round_in_chamber" ); add_msg_debug( debugmode::DF_RANGED, "Gun jam chance: %s\nMagazine jam chance: %s\nGun damage level: %d\nMagazine damage level: %d\nFail to feed chance: %s", @@ -752,7 +753,7 @@ bool Character::handle_gun_damage( item &it ) return false; // Chance for the weapon to suffer a failure, caused by the magazine size, quality, or condition - } else if( x_in_y( jam_chance, 1 ) && !firing.u_know_round_in_chamber && + } else if( x_in_y( jam_chance, 1 ) && !it.has_var( "u_know_round_in_chamber" ) && faults::get_random_of_type_item_can_have( it, gun_mechanical_simple ) != fault_id::NULL_ID() ) { add_msg_player_or_npc( m_bad, _( "Your %s malfunctions!" ), _( "'s %s malfunctions!" ), @@ -854,9 +855,8 @@ bool Character::handle_gun_damage( item &it ) it.inc_damage(); } - if( firing.u_know_round_in_chamber ) { - islot_gun gun = *it.type->gun; - gun.u_know_round_in_chamber = false; + if( it.has_var( "u_know_round_in_chamber" ) ) { + it.erase_var( "u_know_round_in_chamber" ); } return true; From 4846380e3a998c16931938a6832bb68f69b82ae6 Mon Sep 17 00:00:00 2001 From: GuardianDll Date: Sun, 18 Aug 2024 17:58:13 +0200 Subject: [PATCH 11/15] finishing bits --- src/activity_actor.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/activity_actor.cpp b/src/activity_actor.cpp index a2a1cb6f54b5b..3a17adce7afba 100644 --- a/src/activity_actor.cpp +++ b/src/activity_actor.cpp @@ -346,9 +346,13 @@ bool aim_activity_actor::check_gun_ability_to_shoot( Character &who, item &it ) // if it's a simple fault, character can try to fix it on the fly if( faults::random_of_type_item_has( it, gun_mechanical_simple ) != fault_id::NULL_ID() ) { - who.mod_moves( -who.get_speed() * rng( 2, 3 ) ); + // fixing fault should cost more than 1 second + // but until game running the next activity actor without ever verifying + // was the previous one successful or not will be resolved, + // it would be safer to limit it somewhat + who.mod_moves( -who.get_speed() ); who.recoil = MAX_RECOIL; - if( one_in( std::max( 5.0f, ( 10.0f - ( 2.0f * who.get_skill_level( skill_gun ) ) ) ) ) ) { + if( one_in( std::max( 7.0f, ( 15.0f - ( 4.0f * who.get_skill_level( skill_gun ) ) ) ) ) ) { who.add_msg_if_player( m_good, _( "Your %s has some mechanical malfunction. You tried to quickly fix it, and it works now!" ), it.tname() ); From 1b6312eb2f1cb9bf507d03afe300c1359a98a527 Mon Sep 17 00:00:00 2001 From: GuardianDll Date: Sun, 18 Aug 2024 18:40:46 +0200 Subject: [PATCH 12/15] document thing --- doc/JSON_INFO.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/JSON_INFO.md b/doc/JSON_INFO.md index be90d131f6f2a..0bab1f75d7031 100644 --- a/doc/JSON_INFO.md +++ b/doc/JSON_INFO.md @@ -3608,6 +3608,7 @@ ammo_effects define what effect the projectile, that you shoot, would have. List "count" : 0, // Default amount of ammo contained by a magazine (set this for ammo belts) "default_ammo": "556", // If specified override the default ammo (optionally set this for ammo belts) "reload_time" : 100, // How long it takes to load each unit of ammo into the magazine +"mag_jam_mult": 1.25 // Multiplier for gun mechanincal malfunctioning from magazine, mostly when it's damaged; Values lesser than 1 reflect better quality of the magazine, that jam less; bigger than 1 result in gun being more prone to malfunction and jam at lesser damage level; zero mag_jam_mult (and zero gun_jam_mult in a gun) would remove any chance for a gun to malfunction. Only works if gun has any fault from gun_mechanical_simple group presented; Jam chances are described in Character::handle_gun_damage(); at this moment it is roughly: 0.000288% for undamaged magazine, 5% for 1 damage (|\), 24% for 2 damage (|.), 96% for 3 damage (\.), and 250% for 4 damage (XX), then this and gun values are summed up and multiplied by 1.8. "linkage" : "ammolink" // If set one linkage (of given type) is dropped for each unit of ammo consumed (set for disintegrating ammo belts) ``` @@ -4155,6 +4156,7 @@ Guns can be defined like this: "sight_dispersion": 10, // Inaccuracy of gun derived from the sight mechanism, measured in 100ths of Minutes Of Angle (MOA) "recoil": 0, // Recoil caused when firing, measured in 100ths of Minutes Of Angle (MOA) "durability": 8, // Resistance to damage/rusting, also determines misfire chance +"gun_jam_mult": 1.25 // Multiplier for gun mechanincal malfunctioning, mostly when it's damaged; Values lesser than 1 reflect better quality of the gun, that jam less; bigger than 1 result in gun being more prone to malfunction and jam at lesser damage level; zero gun_jam_mult (and zero mag_jam_mult if magazine is presented) would remove any chance for a gun to malfunction. Only apply if gun has any fault from gun_mechanical_simple group presented; Jam chances are described in Character::handle_gun_damage(); at this moment it is roughly: 0.00018% for undamaged gun, 3% for 1 damage (|\), 15% for 2 damage (|.), 45% for 3 damage (\.), and 80% for 4 damage (XX), then this and magazine values are summed up and multiplied by 1.8 "blackpowder_tolerance": 8,// One in X chance to get clogged up (per shot) when firing blackpowder ammunition (higher is better). Optional, default is 8. "min_cycle_recoil": 0, // Minimum ammo recoil for gun to be able to fire more than once per attack. "clip_size": 100, // Maximum amount of ammo that can be loaded From 58934643869ce8c277c4cd73ff03cc093c9d822b Mon Sep 17 00:00:00 2001 From: GuardianDll Date: Sun, 18 Aug 2024 19:05:35 +0200 Subject: [PATCH 13/15] add manual fault fixes --- data/json/faults/fixes_gun.json | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/data/json/faults/fixes_gun.json b/data/json/faults/fixes_gun.json index 6665665e0a0f3..c64585066ed45 100644 --- a/data/json/faults/fixes_gun.json +++ b/data/json/faults/fixes_gun.json @@ -66,14 +66,42 @@ "time_save_profs": { "prof_gun_cleaning": 0.5 }, "time_save_flags": { "EASY_CLEAN": 0.5 } }, + { + "type": "fault_fix", + "id": "mend_fault_fail_to_feed_manual_feed", + "name": "Manual cycle", + "success_msg": "You manually cycle your %s to put round in the chamber.", + "time": "10 s", + "set_variables": { "u_know_round_in_chamber": "true" }, + "faults_removed": [ "fault_fail_to_feed" ] + }, { "type": "fault_fix", "id": "mend_fault_gun_chamber_spent_eject", "name": "Eject spent casing", - "success_msg": "You eject the spent casing from the %s.", - "time": "1 s", + "success_msg": "You eject the spent casing from the %s's chamber.", + "time": "10 s", + "set_variables": { "u_know_round_in_chamber": "true" }, "faults_removed": [ "fault_gun_chamber_spent" ] }, + { + "type": "fault_fix", + "id": "mend_fault_stovepipe_eject", + "name": "Eject spent casing", + "success_msg": "You eject the spent casing, stuck in %s's slide.", + "time": "10 s", + "set_variables": { "u_know_round_in_chamber": "true" }, + "faults_removed": [ "fault_stovepipe" ] + }, + { + "type": "fault_fix", + "id": "mend_fault_double_feed_clean", + "name": "Clean double feed", + "success_msg": "You eject the second round stuck in %s's chamber.", + "time": "10 s", + "set_variables": { "u_know_round_in_chamber": "true" }, + "faults_removed": [ "fault_double_feed" ] + }, { "type": "fault_fix", "id": "mend_fault_gun_unlubricated", From e3276ba4177e3b25d392ffd0b62cad815021f46a Mon Sep 17 00:00:00 2001 From: Anton Simakov <67688115+GuardianDll@users.noreply.github.com> Date: Sun, 18 Aug 2024 19:07:51 +0200 Subject: [PATCH 14/15] Astyle Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- src/fault.h | 2 +- src/item_factory.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/fault.h b/src/fault.h index 8c75e4c1e4310..dd0ccacacc380 100644 --- a/src/fault.h +++ b/src/fault.h @@ -29,7 +29,7 @@ void reset(); void finalize(); void check_consistency(); -const fault_id &get_random_of_type_item_can_have(const item& it, const std::string& type); +const fault_id &get_random_of_type_item_can_have( const item &it, const std::string &type ); const fault_id &random_of_type( const std::string &type ); const fault_id &random_of_type_item_has( const item &it, const std::string &type ); diff --git a/src/item_factory.cpp b/src/item_factory.cpp index fe511f4cc5aad..17ebe49c0af46 100644 --- a/src/item_factory.cpp +++ b/src/item_factory.cpp @@ -2839,7 +2839,7 @@ void Item_factory::load( islot_gun &slot, const JsonObject &jo, const std::strin assign( jo, "heat_per_shot", slot.heat_per_shot, strict, 0.0 ); assign( jo, "cooling_value", slot.cooling_value, strict, 0.0 ); assign( jo, "overheat_threshold", slot.overheat_threshold, strict, -1.0 ); - optional(jo, false, "gun_jam_mult", slot.gun_jam_mult, 1 ); + optional( jo, false, "gun_jam_mult", slot.gun_jam_mult, 1 ); if( jo.has_array( "valid_mod_locations" ) ) { slot.valid_mod_locations.clear(); @@ -3574,7 +3574,7 @@ void Item_factory::load( islot_magazine &slot, const JsonObject &jo, const std:: assign( jo, "count", slot.count, strict, 0 ); assign( jo, "default_ammo", slot.default_ammo, strict ); assign( jo, "reload_time", slot.reload_time, strict, 0 ); - optional(jo, false, "mag_jam_mult", slot.mag_jam_mult, 1 ); + optional( jo, false, "mag_jam_mult", slot.mag_jam_mult, 1 ); assign( jo, "linkage", slot.linkage, strict ); } From a5a99d6de70951ef3f4d31cd0a882e7824cf48d2 Mon Sep 17 00:00:00 2001 From: Anton Simakov <67688115+GuardianDll@users.noreply.github.com> Date: Mon, 19 Aug 2024 10:50:04 +0200 Subject: [PATCH 15/15] Apply suggestions from code review --- src/ranged.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/ranged.cpp b/src/ranged.cpp index 9e9459d88e1d9..b2b8092cbb87e 100644 --- a/src/ranged.cpp +++ b/src/ranged.cpp @@ -725,7 +725,6 @@ bool Character::handle_gun_damage( item &it ) } const double jam_chance = ( gun_jam_chance + mag_jam_chance ) * 1.8; - bool u_know_round_in_chamber = it.has_var( "u_know_round_in_chamber" ); add_msg_debug( debugmode::DF_RANGED, "Gun jam chance: %s\nMagazine jam chance: %s\nGun damage level: %d\nMagazine damage level: %d\nFail to feed chance: %s",