Skip to content

Commit

Permalink
feat: Armor values specified directly in armor JSON (#3930)
Browse files Browse the repository at this point in the history
* Custom armor values

* tmp

* Add protection reduction from damage to overriden armor

* Add test for armor reduction

* Line in docs
  • Loading branch information
Coolthulhu authored Feb 7, 2024
1 parent 01d5610 commit 9f3d069
Show file tree
Hide file tree
Showing 15 changed files with 251 additions and 95 deletions.
30 changes: 30 additions & 0 deletions data/mods/TEST_DATA/items.json
Original file line number Diff line number Diff line change
Expand Up @@ -743,5 +743,35 @@
"name": { "str": "Glock 19" },
"type": "GUN",
"extend": { "magazines": [ [ "9mm", [ "glockmag_test" ] ] ] }
},
{
"id": "test_override_armor",
"repairs_like": "survivor_suit",
"type": "ARMOR",
"category": "armor",
"name": { "str": "anti-bullet armor" },
"description": "Armor that is completely immune to .50 bullets, but without any protection from cuts. Magic!",
"weight": "100 g",
"volume": "1 L",
"price": 1,
"price_postapoc": 1,
"symbol": "[",
"looks_like": "touring_suit",
"color": "dark_gray",
"material": [ "faux_fur" ],
"resistance": { "cut": 0, "bullet": 1000 },
"warmth": 0,
"material_thickness": 1,
"coverage": 100,
"covers": [ "torso", "arm_l", "arm_r", "head", "leg_l", "leg_r" ]
},
{
"id": "test_override_armor_damageable",
"copy-from": "test_override_armor",
"type": "ARMOR",
"category": "armor",
"name": { "str": "damageable anti-bullet armor" },
"description": "This armor protects from bullets, but can be damaged to protect a bit less.",
"material_thickness": 10
}
]
1 change: 1 addition & 0 deletions doc/src/content/docs/en/mod/json/reference/json_info.md
Original file line number Diff line number Diff line change
Expand Up @@ -1537,6 +1537,7 @@ Armor can be defined like this:
"material_thickness" : 1, // Thickness of material, in millimeter units (approximately). Generally ranges between 1 - 5, more unusual armor types go up to 10 or more
"power_armor" : false, // If this is a power armor item (those are special).
"valid_mods" : ["steel_padded"] // List of valid clothing mods. Note that if the clothing mod doesn't have "restricted" listed, this isn't needed.
"resistance": { "cut": 0, "bullet": 1000 } // If set, overrides usual resistance calculation. Values are for undamaged item, thickness affects scaling with damage - 1 thickness means no reduction from damage, 2 means it's halved on first damage, 10 means each level of damage decreases armor by 10%
```

Alternately, every item (book, tool, gun, even food) can be used as armor if it has armor_data:
Expand Down
5 changes: 5 additions & 0 deletions src/assign.h
Original file line number Diff line number Diff line change
Expand Up @@ -520,6 +520,11 @@ std::enable_if<std::is_same<typename std::decay<T>::type, time_duration>::value,
return true;
}

bool assign( const JsonObject &jo,
const std::string &name,
resistances &val,
bool strict = false );

template<typename T>
inline bool assign( const JsonObject &jo, const std::string &name, std::optional<T> &val,
const bool strict = false )
Expand Down
2 changes: 1 addition & 1 deletion src/character.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6929,7 +6929,7 @@ resistances Character::mutation_armor( bodypart_id bp ) const
{
resistances res;
for( const trait_id &iter : get_mutations() ) {
res += iter->damage_resistance( bp->token );
res = res.combined_with( iter->damage_resistance( bp->token ) );
}

return res;
Expand Down
121 changes: 96 additions & 25 deletions src/damage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include <numeric>
#include <utility>

#include "assign.h"
#include "cata_utility.h"
#include "debug.h"
#include "enum_conversions.h"
Expand All @@ -14,6 +15,7 @@
#include "monster.h"
#include "mtype.h"
#include "translations.h"
#include "cata_utility.h"

bool damage_unit::operator==( const damage_unit &other ) const
{
Expand Down Expand Up @@ -183,10 +185,7 @@ int dealt_damage_instance::total_damage() const
return std::accumulate( dealt_dams.begin(), dealt_dams.end(), 0 );
}

resistances::resistances()
{
resist_vals.fill( 0 );
}
resistances::resistances() = default;

resistances::resistances( const item &armor, bool to_self )
{
Expand All @@ -211,25 +210,30 @@ resistances::resistances( monster &monster ) : resistances()
}
void resistances::set_resist( damage_type dt, float amount )
{
resist_vals[dt] = amount;
flat[dt] = amount;
}
float resistances::type_resist( damage_type dt ) const
{
return resist_vals[dt];
auto iter = flat.find( dt );
if( iter != flat.end() ) {
return iter->second;
}
return 0.0f;
}
float resistances::get_effective_resist( const damage_unit &du ) const
{
return std::max( type_resist( du.type ) - du.res_pen,
0.0f ) * du.res_mult;
}

resistances &resistances::operator+=( const resistances &other )
resistances resistances::combined_with( const resistances &other ) const
{
for( size_t i = 0; i < NUM_DT; i++ ) {
resist_vals[ i ] += other.resist_vals[ i ];
resistances ret = *this;
for( const auto &pr : other.flat ) {
ret.flat[ pr.first ] += pr.second;
}

return *this;
return ret;
}

template<>
Expand Down Expand Up @@ -425,23 +429,42 @@ damage_instance load_damage_instance_inherit( const JsonArray &jarr, const damag
return di;
}

std::array<float, NUM_DT> load_damage_array( const JsonObject &jo )
std::map<damage_type, float> load_damage_map( const JsonObject &jo )
{
std::array<float, NUM_DT> ret;
float init_val = jo.get_float( "all", 0.0f );
std::map<damage_type, float> ret;
std::optional<float> init_val = jo.has_float( "all" ) ?
jo.get_float( "all", 0.0f ) :
std::optional<float>();

float phys = jo.get_float( "physical", init_val );
ret[ DT_BASH ] = jo.get_float( "bash", phys );
ret[ DT_CUT ] = jo.get_float( "cut", phys );
ret[ DT_STAB ] = jo.get_float( "stab", phys );
ret[ DT_BULLET ] = jo.get_float( "bullet", phys );
auto load_if_present = [&ret, &jo]( const std::string & name, damage_type dt,
std::optional<float> fallback ) {
if( jo.has_float( name ) ) {
float val = jo.get_float( name );
ret[dt] = val;
} else if( fallback ) {
ret[dt] = *fallback;
}
};

float non_phys = jo.get_float( "non_physical", init_val );
ret[ DT_BIOLOGICAL ] = jo.get_float( "biological", non_phys );
ret[ DT_ACID ] = jo.get_float( "acid", non_phys );
ret[ DT_HEAT ] = jo.get_float( "heat", non_phys );
ret[ DT_COLD ] = jo.get_float( "cold", non_phys );
ret[ DT_ELECTRIC ] = jo.get_float( "electric", non_phys );
std::optional<float> phys = jo.has_float( "physical" ) ?
jo.get_float( "physical", 0.0f ) :
std::optional<float>();


load_if_present( "bash", DT_BASH, phys ? *phys : init_val );
load_if_present( "cut", DT_CUT, phys ? *phys : init_val );
load_if_present( "stab", DT_STAB, phys ? *phys : init_val );
load_if_present( "bullet", DT_BULLET, phys ? *phys : init_val );

std::optional<float> non_phys = jo.has_float( "non_physical" ) ?
jo.get_float( "non_physical", 0.0f ) :
std::optional<float>();

load_if_present( "biological", DT_BIOLOGICAL, non_phys ? *non_phys : init_val );
load_if_present( "acid", DT_ACID, non_phys ? *non_phys : init_val );
load_if_present( "heat", DT_HEAT, non_phys ? *non_phys : init_val );
load_if_present( "cold", DT_COLD, non_phys ? *non_phys : init_val );
load_if_present( "electric", DT_ELECTRIC, non_phys ? *non_phys : init_val );

// DT_TRUE should never be resisted
ret[ DT_TRUE ] = 0.0f;
Expand All @@ -451,6 +474,54 @@ std::array<float, NUM_DT> load_damage_array( const JsonObject &jo )
resistances load_resistances_instance( const JsonObject &jo )
{
resistances ret;
ret.resist_vals = load_damage_array( jo );
ret.flat = load_damage_map( jo );
return ret;
}

bool assign( const JsonObject &jo,
const std::string &name,
resistances &val,
bool /*strict*/ )
{
// Object via which to report errors which differs for proportional/relative
// values
JsonObject err = jo;
err.allow_omitted_members();
JsonObject relative = jo.get_object( "relative" );
relative.allow_omitted_members();
JsonObject proportional = jo.get_object( "proportional" );
proportional.allow_omitted_members();

if( relative.has_member( name ) ) {
err = relative;
JsonObject jo_relative = err.get_member( name );
const resistances tmp = load_resistances_instance( err );
for( size_t i = 0; i < val.flat.size(); i++ ) {
damage_type dt = static_cast<damage_type>( i );
auto iter = tmp.flat.find( dt );
if( iter != tmp.flat.end() ) {
val.flat[dt] += iter->second;
}
}

} else if( proportional.has_member( name ) ) {
err = relative;
JsonObject jo_proportional = err.get_member( name );
resistances tmp = load_resistances_instance( err );
for( size_t i = 0; i < val.flat.size(); i++ ) {
damage_type dt = static_cast<damage_type>( i );
auto iter = tmp.flat.find( dt );
if( iter != tmp.flat.end() ) {
val.flat[dt] *= iter->second;
}
}

} else if( jo.has_object( name ) ) {
JsonObject jo_inner = jo.get_object( name );
val = load_resistances_instance( jo_inner );
}

// TODO: Check for change - ie. `strict` check support

return true;
}
6 changes: 3 additions & 3 deletions src/damage.h
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ struct dealt_damage_instance {
};

struct resistances {
std::array<float, NUM_DT> resist_vals;
std::map<damage_type, float> flat;

resistances();

Expand All @@ -111,7 +111,7 @@ struct resistances {

float get_effective_resist( const damage_unit &du ) const;

resistances &operator+=( const resistances &other );
resistances combined_with( const resistances &other ) const;
};

const std::map<std::string, damage_type> &get_dt_map();
Expand All @@ -131,6 +131,6 @@ resistances load_resistances_instance( const JsonObject &jo );

// Returns damage or resistance data
// Handles some shorthands
std::array<float, NUM_DT> load_damage_array( const JsonObject &jo );
std::map<damage_type, float> load_damage_map( const JsonObject &jo );

#endif // CATA_SRC_DAMAGE_H
Loading

0 comments on commit 9f3d069

Please sign in to comment.