diff --git a/data/json/monsters/misc.json b/data/json/monsters/misc.json index fa6229dc48077..8b51827e63795 100644 --- a/data/json/monsters/misc.json +++ b/data/json/monsters/misc.json @@ -17,6 +17,8 @@ "morale": 100, "melee_cut": 0, "vision_day": 1, + "armor_bullet": 100, + "weakpoints": [ { "name": "knee", "armor_mult": { "bullet": 0.5 }, "armor_penalty": { "bullet": 25 }, "coverage": 50 } ], "harvest": "exempt", "death_function": { "corpse_type": "NO_CORPSE", "message": "The %s melts away." }, "regenerates": 50, diff --git a/doc/MONSTERS.md b/doc/MONSTERS.md index 32c3b73870e22..9d13314f0d984 100644 --- a/doc/MONSTERS.md +++ b/doc/MONSTERS.md @@ -63,6 +63,7 @@ Monsters may also have any of these optional properties: | `armor_stab` | (integer) Monster's protection from stab damage | `armor_acid` | (integer) Monster's protection from acid damage | `armor_fire` | (integer) Monster's protection from fire damage +| `weakpoints` | (array of objects) Weakpoints in the monster's protection | `vision_day` | (integer) Vision range in full daylight, with `50` being the typical maximum | `vision_night` | (integer) Vision range in total darkness, ex. coyote `5`, bear `10`, sewer rat `30`, flaming eye `40` | `tracking_distance` | (integer) Amount of tiles the monster will keep between itself and its current tracked enemy or followed leader. Defaults to `3`. @@ -315,6 +316,18 @@ Amount of cutting damage added to die roll on monster melee attack. Monster protection from bashing, cutting, stabbing, acid and fire damage. +## "weakpoints" +(array of objects, optional) + +Weakpoints in the monster's protection. + +| field | description +| --- | --- +| `name` | Name of the weakpoint. +| `coverage` | Base percentage chance of hitting the weakpoint. May be increased by skill level. (e.g. A coverage of 5 means a 5% base chance of hitting the weakpoint) +| `armor_multiplier` | multipler on the monster's base protection when hitting the weakpoint. +| `armor_penalty` | a flat penalty to the monster's protection, applied after the multiplier. + ## "vision_day", "vision_night" (integer, optional) diff --git a/src/damage.cpp b/src/damage.cpp index 61e89aa443f6c..52f098bbb9c9f 100644 --- a/src/damage.cpp +++ b/src/damage.cpp @@ -430,10 +430,11 @@ damage_instance load_damage_instance_inherit( const JsonArray &jarr, const damag return di; } -std::array( damage_type::NUM )> load_damage_array( const JsonObject &jo ) +std::array( damage_type::NUM )> load_damage_array( const JsonObject &jo, + float default_value ) { std::array( damage_type::NUM )> ret; - float init_val = jo.get_float( "all", 0.0f ); + float init_val = jo.get_float( "all", default_value ); float phys = jo.get_float( "physical", init_val ); ret[ static_cast( damage_type::BASH ) ] = jo.get_float( "bash", phys ); diff --git a/src/damage.h b/src/damage.h index 53236657eba49..3a5093ed616a6 100644 --- a/src/damage.h +++ b/src/damage.h @@ -151,6 +151,7 @@ resistances load_resistances_instance( const JsonObject &jo ); // Returns damage or resistance data // Handles some shorthands -std::array( damage_type::NUM )> load_damage_array( const JsonObject &jo ); +std::array( damage_type::NUM )> load_damage_array( const JsonObject &jo, + float default_value = 0.0f ); #endif // CATA_SRC_DAMAGE_H diff --git a/src/monster.cpp b/src/monster.cpp index 21c1382528cdb..2d76c3c06a04a 100644 --- a/src/monster.cpp +++ b/src/monster.cpp @@ -62,6 +62,7 @@ #include "trap.h" #include "units.h" #include "viewer.h" +#include "weakpoint.h" #include "weather.h" static const efftype_id effect_badpoison( "badpoison" ); @@ -1489,10 +1490,18 @@ bool monster::block_hit( Creature *, bodypart_id &, damage_instance & ) void monster::absorb_hit( const bodypart_id &, damage_instance &dam ) { + resistances r = resistances( *this ); + const weakpoint *weakpoint = type->weakpoints.select_weakpoint(); + weakpoint->apply_to( r ); for( auto &elem : dam.damage_units ) { add_msg_debug( debugmode::DF_MONSTER, "Dam Type: %s :: Ar Pen: %.1f :: Armor Mult: %.1f", name_by_dt( elem.type ), elem.res_pen, elem.res_mult ); - elem.amount -= std::min( resistances( *this ).get_effective_resist( elem ) + + add_msg_debug( debugmode::DF_MONSTER, + "Weakpoint: %s :: Armor Mult: %.1f :: Armor Penalty: %.1f :: Resist: %.1f", + weakpoint->id, weakpoint->armor_mult[static_cast( elem.type )], + weakpoint->armor_penalty[static_cast( elem.type )], + r.get_effective_resist( elem ) ); + elem.amount -= std::min( r.get_effective_resist( elem ) + get_worn_armor_val( elem.type ), elem.amount ); } } diff --git a/src/monstergenerator.cpp b/src/monstergenerator.cpp index 6213362657ba9..7f2a90b5c7af2 100644 --- a/src/monstergenerator.cpp +++ b/src/monstergenerator.cpp @@ -34,6 +34,7 @@ #include "rng.h" #include "translations.h" #include "units.h" +#include "weakpoint.h" namespace behavior { @@ -717,6 +718,11 @@ void mtype::load( const JsonObject &jo, const std::string &src ) assign( jo, "armor_acid", armor_acid, strict, 0 ); assign( jo, "armor_fire", armor_fire, strict, 0 ); + if( jo.has_array( "weakpoints" ) ) { + weakpoints.clear(); + weakpoints.load( jo.get_array( "weakpoints" ) ); + } + optional( jo, was_loaded, "bleed_rate", bleed_rate, 100 ); assign( jo, "vision_day", vision_day, strict, 0 ); diff --git a/src/mtype.cpp b/src/mtype.cpp index f17da63217314..01b564ceda3b5 100644 --- a/src/mtype.cpp +++ b/src/mtype.cpp @@ -14,6 +14,7 @@ #include "monstergenerator.h" #include "translations.h" #include "units.h" +#include "weakpoint.h" static const itype_id itype_bone( "bone" ); static const itype_id itype_bone_tainted( "bone_tainted" ); diff --git a/src/mtype.h b/src/mtype.h index 47cc9d237d04c..8ef1009b9e2f8 100644 --- a/src/mtype.h +++ b/src/mtype.h @@ -22,6 +22,7 @@ #include "translations.h" #include "type_id.h" #include "units.h" // IWYU pragma: keep +#include "weakpoint.h" class Creature; class monster; @@ -327,6 +328,7 @@ struct mtype { int armor_bullet = -1; /** innate armor vs. bullet */ int armor_acid = -1; /** innate armor vs. acid */ int armor_fire = -1; /** innate armor vs. fire */ + ::weakpoints weakpoints; // Bleed rate in percent, 0 makes the monster immune to bleeding int bleed_rate = 100; diff --git a/src/weakpoint.cpp b/src/weakpoint.cpp new file mode 100644 index 0000000000000..a04c3d4371bc7 --- /dev/null +++ b/src/weakpoint.cpp @@ -0,0 +1,77 @@ +#include "weakpoint.h" + +#include +#include + +#include "assign.h" +#include "creature.h" +#include "damage.h" +#include "item.h" +#include "rng.h" + +class JsonArray; +class JsonObject; + +weakpoint::weakpoint() +{ + // arrays must be filled manually to avoid UB. + armor_mult.fill( 1.0f ); + armor_penalty.fill( 0.0f ); +} + +void weakpoint::load( const JsonObject &jo ) +{ + assign( jo, "id", id ); + assign( jo, "name", name ); + assign( jo, "coverage", coverage, false, 0.0f, 100.0f ); + if( jo.has_object( "armor_mult" ) ) { + armor_mult = load_damage_array( jo.get_object( "armor_mult" ), 1.0f ); + } + if( jo.has_object( "armor_penalty" ) ) { + armor_penalty = load_damage_array( jo.get_object( "armor_penalty" ), 0.0f ); + } + // Set the ID to the name, if not provided. + if( id.empty() ) { + id = name; + } +} + +void weakpoint::apply_to( resistances &resistances ) const +{ + for( int i = 0; i < static_cast( damage_type::NUM ); ++i ) { + resistances.resist_vals[i] *= armor_mult[i]; + resistances.resist_vals[i] -= armor_penalty[i]; + } +} + +float weakpoint::hit_chance() const +{ + return coverage; +} + +const weakpoint *weakpoints::select_weakpoint() const +{ + float idx = rng_float( 0.0f, 100.0f ); + for( const weakpoint &weakpoint : weakpoint_list ) { + float hit_chance = weakpoint.hit_chance( ); + if( hit_chance <= idx ) { + return &weakpoint; + } + idx -= hit_chance; + } + return &default_weakpoint; +} + +void weakpoints::clear() +{ + weakpoint_list.clear(); +} + +void weakpoints::load( const JsonArray &ja ) +{ + for( const JsonObject jo : ja ) { + weakpoint tmp; + tmp.load( jo ); + weakpoint_list.push_back( std::move( tmp ) ); + } +} diff --git a/src/weakpoint.h b/src/weakpoint.h new file mode 100644 index 0000000000000..f3fc39c313577 --- /dev/null +++ b/src/weakpoint.h @@ -0,0 +1,48 @@ +#pragma once +#ifndef CATA_SRC_WEAKPOINT_H +#define CATA_SRC_WEAKPOINT_H + +#include +#include +#include + +#include "creature.h" +#include "damage.h" + +class JsonArray; +class JsonObject; + +struct weakpoint { + // ID of the weakpoint. Equal to the name, if not provided. + std::string id; + // Name of the weakpoint. Can be empty. + std::string name; + // Percent chance of hitting the weakpoint. Can be increased by skill. + float coverage = 100.0f; + // Multiplier for existing armor values. Defaults to 1. + std::array( damage_type::NUM )> armor_mult; + // Flat penalty to armor values. Applied after the multiplier. + std::array( damage_type::NUM )> armor_penalty; + + weakpoint(); + // Apply the armor multipliers and offsets to a set of resistances. + void apply_to( resistances &resistances ) const; + // Return the change of the creature hitting the weakpoint. + float hit_chance( ) const; + void load( const JsonObject &jo ); +}; + +struct weakpoints { + // List of weakpoints. Each weakpoint should have a unique id. + std::vector weakpoint_list; + // Default weakpoint to return. + weakpoint default_weakpoint; + + // Selects a weakpoint to hit. + const weakpoint *select_weakpoint( ) const; + + void clear(); + void load( const JsonArray &ja ); +}; + +#endif // CATA_SRC_WEAKPOINT_H \ No newline at end of file