Skip to content

Commit

Permalink
Add assign reader for damage instances, deprecate legacy damage code (#…
Browse files Browse the repository at this point in the history
…37329)

* Add assign reader for damage instances

This assign reader allows consistent and clear reading of damage
instances for all items.

Make the necessary changes to item factory and damage units to preserve
existing functionality.

* Cleanup damage parameter names

These names weren't very clear, hopefully this is an improvement.

The JSON changes are to fix errors this exposes.
  • Loading branch information
anothersimulacrum authored Apr 9, 2020
1 parent df00ef1 commit c663dfd
Show file tree
Hide file tree
Showing 12 changed files with 413 additions and 108 deletions.
1 change: 0 additions & 1 deletion data/json/items/gunmod/muzzle.json
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,6 @@
"price": 0,
"material": [ "superalloy", "ceramic" ],
"mod_targets": [ "rifle" ],
"damage_modifier": 0,
"handling_modifier": 0,
"proportional": { "loudness_modifier": 2 },
"flags": [ "IRREMOVABLE" ]
Expand Down
3 changes: 1 addition & 2 deletions data/mods/Dark-Skies-Above/monsters/mongun.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@
"type": "GUN",
"name": "fake flamesword",
"description": "a fake gun used by the consecrator. it's a bug if you find this in the wild",
"ranged_damage": { "damage_type": "heat", "amount": 10 },
"pierce": 30,
"ranged_damage": { "damage_type": "heat", "amount": 10, "armor_penetration": 30 },
"range": 9,
"loudness": 1,
"ammo_effects": [ "FLAME", "STREAM", "INCENDIARY", "NEVER_MISFIRES" ]
Expand Down
282 changes: 282 additions & 0 deletions src/assign.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

#include "calendar.h"
#include "color.h"
#include "damage.h"
#include "debug.h"
#include "json.h"
#include "units.h"
Expand Down Expand Up @@ -589,4 +590,285 @@ inline bool assign( const JsonObject &jo, const std::string &name, cata::optiona
return assign( jo, name, *val, strict );
}

static const float float_max = std::numeric_limits<float>::max();

static void assign_dmg_relative( damage_instance &out, const damage_instance &val,
damage_instance relative, bool &strict )
{
for( const damage_unit &val_dmg : val.damage_units ) {
for( damage_unit &tmp : relative.damage_units ) {
if( tmp.type != val_dmg.type ) {
continue;
}

// Do not require strict parsing for relative and proportional values as rules
// such as +10% are well-formed independent of whether they affect base value
strict = false;

// res_mult is set to 1 if it's not specified. Set it to zero so we don't accidentally add to it
if( tmp.res_mult == 1.0f ) {
tmp.res_mult = 0;
}
// Same for damage_multiplier
if( tmp.damage_multiplier == 1.0f ) {
tmp.damage_multiplier = 0;
}

// As well as the unconditional versions
if( tmp.unconditional_res_mult == 1.0f ) {
tmp.unconditional_res_mult = 0;
}

if( tmp.unconditional_damage_mult == 1.0f ) {
tmp.unconditional_damage_mult = 0;
}

damage_unit out_dmg( tmp.type, 0.0f );

out_dmg.amount = tmp.amount + val_dmg.amount;
out_dmg.res_pen = tmp.res_pen + val_dmg.res_pen;

out_dmg.res_mult = tmp.res_mult + val_dmg.res_mult;
out_dmg.damage_multiplier = tmp.damage_multiplier + val_dmg.damage_multiplier;

out_dmg.unconditional_res_mult = tmp.unconditional_res_mult + val_dmg.unconditional_res_mult;
out_dmg.unconditional_damage_mult = tmp.unconditional_damage_mult +
val_dmg.unconditional_damage_mult;

out.add( out_dmg );
}
}
}

static void assign_dmg_proportional( const JsonObject &jo, const std::string &name,
damage_instance &out,
const damage_instance &val,
damage_instance proportional, bool &strict )
{
for( const damage_unit &val_dmg : val.damage_units ) {
for( damage_unit &scalar : proportional.damage_units ) {
if( scalar.type != val_dmg.type ) {
continue;
}

// Do not require strict parsing for relative and proportional values as rules
// such as +10% are well-formed independent of whether they affect base value
strict = false;

// Can't have negative percent, and 100% is pointless
// If it's 0, it wasn't loaded
if( scalar.amount == 1 || scalar.amount < 0 ) {
jo.throw_error( "Proportional damage amount is not a valid scalar", name );
}

// If it's 0, it wasn't loaded
if( scalar.res_pen < 0 || scalar.res_pen == 1 ) {
jo.throw_error( "Proportional armor penetration is not a valid scalar", name );
}

// It wasn't loaded, so set it 100%
if( scalar.res_pen == 0 ) {
scalar.res_pen = 1.0f;
}

// Ditto
if( scalar.amount == 0 ) {
scalar.amount = 1.0f;
}

// If it's 1, it wasn't loaded (or was loaded as 1)
if( scalar.res_mult <= 0 ) {
jo.throw_error( "Proportional armor penetration multiplier is not a valid scalar", name );
}

// If it's 1, it wasn't loaded (or was loaded as 1)
if( scalar.damage_multiplier <= 0 ) {
jo.throw_error( "Proportional damage multipler is not a valid scalar", name );
}

// If it's 1, it wasn't loaded (or was loaded as 1)
if( scalar.unconditional_res_mult <= 0 ) {
jo.throw_error( "Proportional unconditional armor penetration multiplier is not a valid scalar",
name );
}

// It's it's 1, it wasn't loaded (or was loaded as 1)
if( scalar.unconditional_damage_mult <= 0 ) {
jo.throw_error( "Proportional unconditional damage multiplier is not a valid scalar", name );
}

damage_unit out_dmg( scalar.type, 0.0f );

out_dmg.amount = val_dmg.amount * scalar.amount;
out_dmg.res_pen = val_dmg.res_pen * scalar.res_pen;

out_dmg.res_mult = val_dmg.res_mult * scalar.res_mult;
out_dmg.damage_multiplier = val_dmg.damage_multiplier * scalar.damage_multiplier;

out_dmg.unconditional_res_mult = val_dmg.unconditional_res_mult * scalar.unconditional_res_mult;
out_dmg.unconditional_damage_mult = val_dmg.unconditional_damage_mult *
scalar.unconditional_damage_mult;

out.add( out_dmg );
}
}
}

static void check_assigned_dmg( const JsonObject &err, const std::string &name,
const damage_instance &out, const damage_instance &lo_inst, const damage_instance &hi_inst )
{
for( const damage_unit &out_dmg : out.damage_units ) {
auto lo_iter = std::find_if( lo_inst.damage_units.begin(),
lo_inst.damage_units.end(), [&out_dmg]( const damage_unit & du ) {
return du.type == out_dmg.type || du.type == DT_NULL;
} );

auto hi_iter = std::find_if( hi_inst.damage_units.begin(),
hi_inst.damage_units.end(), [&out_dmg]( const damage_unit & du ) {
return du.type == out_dmg.type || du.type == DT_NULL;
} );

if( lo_iter == lo_inst.damage_units.end() ) {
err.throw_error( "Min damage type used in assign does not match damage type assigned", name );
}
if( hi_iter == hi_inst.damage_units.end() ) {
err.throw_error( "Max damage type used in assign does not match damage type assigned", name );
}

const damage_unit &hi_dmg = *hi_iter;
const damage_unit &lo_dmg = *lo_iter;

if( out_dmg.amount < lo_dmg.amount || out_dmg.amount > hi_dmg.amount ) {
err.throw_error( "value for damage outside supported range", name );
}
if( out_dmg.res_pen < lo_dmg.res_pen || out_dmg.res_pen > hi_dmg.res_pen ) {
err.throw_error( "value for armor penetration outside supported range", name );
}
if( out_dmg.res_mult < lo_dmg.res_mult || out_dmg.res_mult > hi_dmg.res_mult ) {
err.throw_error( "value for armor penetration multiplier outside supported range", name );
}
if( out_dmg.damage_multiplier < lo_dmg.damage_multiplier ||
out_dmg.damage_multiplier > hi_dmg.damage_multiplier ) {
err.throw_error( "value for damage multiplier outside supported range", name );
}
}
}

inline bool assign( const JsonObject &jo, const std::string &name, damage_instance &val,
bool strict = false,
const damage_instance &lo = damage_instance( DT_NULL, 0.0f, 0.0f, 0.0f, 0.0f ),
const damage_instance &hi = damage_instance( DT_NULL, float_max, float_max, float_max,
float_max ) )
{
// What we'll eventually be returning for the damage instance
damage_instance out;

if( jo.has_array( name ) ) {
out = load_damage_instance_inherit( jo.get_array( name ), val );
} else if( jo.has_object( name ) ) {
out = load_damage_instance_inherit( jo.get_object( name ), val );
} else {
// Legacy: remove after 0.F
float amount = 0.0f;
float arpen = 0.0f;
float unc_dmg_mult = 1.0f;

// There will always be either a prop_damage or damage (name)
if( jo.has_member( name ) ) {
amount = jo.get_float( name );
} else if( jo.has_member( "prop_damage" ) ) {
unc_dmg_mult = jo.get_float( "prop_damage" );
}
// And there may or may not be armor penetration
if( jo.has_member( "pierce" ) ) {
arpen = jo.get_float( "pierce" );
}

out.add_damage( DT_STAB, amount, arpen, 1.0f, 1.0f, 1.0f, unc_dmg_mult );
}

// Object via which to report errors which differs for proportional/relative values
const JsonObject &err = jo;
JsonObject relative = jo.get_object( "relative" );
relative.allow_omitted_members();
JsonObject proportional = jo.get_object( "proportional" );
proportional.allow_omitted_members();

// Currently, we load only either relative or proportional when loading damage
// There's no good reason for this, but it's simple for now
if( relative.has_object( name ) ) {
assign_dmg_relative( out, val, load_damage_instance( relative.get_object( name ) ), strict );
} else if( relative.has_array( name ) ) {
assign_dmg_relative( out, val, load_damage_instance( relative.get_array( name ) ), strict );
} else if( proportional.has_object( name ) ) {
assign_dmg_proportional( proportional, name, out, val,
load_damage_instance( proportional.get_object( name ) ),
strict );
} else if( proportional.has_array( name ) ) {
assign_dmg_proportional( proportional, name, out, val,
load_damage_instance( proportional.get_array( name ) ),
strict );
} else if( relative.has_member( name ) || relative.has_member( "pierce" ) ||
relative.has_member( "prop_damage" ) ) {
// Legacy: Remove after 0.F
// It is valid for relative to adjust any of pierce, prop_damage, or damage
// So check for what it's modifying, and modify that
float amt = 0.0f;
float arpen = 0.0f;
float unc_dmg_mul = 1.0f;

if( relative.has_member( name ) ) {
amt = relative.get_float( name );
}
if( relative.has_member( "pierce" ) ) {
arpen = relative.get_float( "pierce" );
}
if( relative.has_member( "prop_damage" ) ) {
unc_dmg_mul = relative.get_float( "prop_damage" );
}

assign_dmg_relative( out, val, damage_instance( DT_STAB, amt, arpen, 1.0f, 1.0f, 1.0f,
unc_dmg_mul ), strict );
} else if( proportional.has_member( name ) || proportional.has_member( "pierce" ) ||
proportional.has_member( "prop_damage" ) ) {
// Legacy: Remove after 0.F
// It is valid for proportional to adjust any of pierce, prop_damage, or damage
// So check if it's modifying any of the things before going on to modify it
float amt = 0.0f;
float arpen = 0.0f;
float unc_dmg_mul = 1.0f;

if( proportional.has_member( name ) ) {
amt = proportional.get_float( name );
}
if( proportional.has_member( "pierce" ) ) {
arpen = proportional.get_float( "pierce" );
}
if( proportional.has_member( "prop_damage" ) ) {
unc_dmg_mul = proportional.get_float( "prop_damage" );
}

assign_dmg_proportional( proportional, name, out, val, damage_instance( DT_STAB, amt, arpen, 1.0f,
1.0f, 1.0f, unc_dmg_mul ), strict );
} else if( !jo.has_member( name ) && !jo.has_member( "prop_damage" ) ) {
// Straight copy-from, not modified by proportional or relative
out = val;
strict = false;
}

check_assigned_dmg( err, name, out, lo, hi );

if( strict && out == val ) {
report_strict_violation( err, "assignment does not update value", name );
}

if( out.damage_units.empty() ) {
out = damage_instance( DT_STAB, 0.0f );
}

// Now that we've verified everything in out is all good, set val to it
val = out;

return true;
}
#endif
2 changes: 1 addition & 1 deletion src/creature.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -849,7 +849,7 @@ void Creature::deal_damage_handle_type( const damage_unit &du, body_part bp, int
}

// Apply damage multiplier from skill, critical hits or grazes after all other modifications.
const int adjusted_damage = du.amount * du.damage_multiplier;
const int adjusted_damage = du.amount * du.damage_multiplier * du.unconditional_damage_mult;
if( adjusted_damage <= 0 ) {
return;
}
Expand Down
Loading

0 comments on commit c663dfd

Please sign in to comment.