Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for partial armor coverage for monsters #51304

Merged
merged 16 commits into from
Sep 4, 2021
Merged
2 changes: 2 additions & 0 deletions data/json/monsters/misc.json
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
13 changes: 13 additions & 0 deletions doc/MONSTERS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand Down Expand Up @@ -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)

Expand Down
5 changes: 3 additions & 2 deletions src/damage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -430,10 +430,11 @@ damage_instance load_damage_instance_inherit( const JsonArray &jarr, const damag
return di;
}

std::array<float, static_cast<int>( damage_type::NUM )> load_damage_array( const JsonObject &jo )
std::array<float, static_cast<int>( damage_type::NUM )> load_damage_array( const JsonObject &jo,
float default_value )
{
std::array<float, static_cast<int>( 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<int>( damage_type::BASH ) ] = jo.get_float( "bash", phys );
Expand Down
3 changes: 2 additions & 1 deletion src/damage.h
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ resistances load_resistances_instance( const JsonObject &jo );

// Returns damage or resistance data
// Handles some shorthands
std::array<float, static_cast<int>( damage_type::NUM )> load_damage_array( const JsonObject &jo );
std::array<float, static_cast<int>( damage_type::NUM )> load_damage_array( const JsonObject &jo,
float default_value = 0.0f );

#endif // CATA_SRC_DAMAGE_H
11 changes: 10 additions & 1 deletion src/monster.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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" );
Expand Down Expand Up @@ -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<int>( elem.type )],
weakpoint->armor_penalty[static_cast<int>( elem.type )],
r.get_effective_resist( elem ) );
elem.amount -= std::min( r.get_effective_resist( elem ) +
get_worn_armor_val( elem.type ), elem.amount );
}
}
Expand Down
6 changes: 6 additions & 0 deletions src/monstergenerator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
#include "rng.h"
#include "translations.h"
#include "units.h"
#include "weakpoint.h"

namespace behavior
{
Expand Down Expand Up @@ -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 );
Expand Down
1 change: 1 addition & 0 deletions src/mtype.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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" );
Expand Down
2 changes: 2 additions & 0 deletions src/mtype.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include "translations.h"
#include "type_id.h"
#include "units.h" // IWYU pragma: keep
#include "weakpoint.h"

class Creature;
class monster;
Expand Down Expand Up @@ -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;
Expand Down
77 changes: 77 additions & 0 deletions src/weakpoint.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
#include "weakpoint.h"

#include <string>
#include <utility>

#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<int>( 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 ) {
Joshua-Chin marked this conversation as resolved.
Show resolved Hide resolved
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 ) );
}
}
48 changes: 48 additions & 0 deletions src/weakpoint.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#pragma once
#ifndef CATA_SRC_WEAKPOINT_H
#define CATA_SRC_WEAKPOINT_H

#include <array>
#include <string>
#include <vector>

#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<float, static_cast<int>( damage_type::NUM )> armor_mult;
// Flat penalty to armor values. Applied after the multiplier.
std::array<float, static_cast<int>( 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> 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