Skip to content

Commit

Permalink
feat(balance): throwing weapons sanity-checking (#5474)
Browse files Browse the repository at this point in the history
* feat(balance): throwing weapons sanity-checking

* style(autofix.ci): automated formatting

* Simplify

* style(autofix.ci): automated formatting

* Fixes

* style(autofix.ci): automated formatting

* Update item.cpp

* style(autofix.ci): automated formatting

* Slight improvements?

* Update character.cpp

* refactor: move to `ranged.h`

* refactor: log plus to multiply

* refactor: return int

* refactor: extract projectile creation

* Update item.cpp

* I cri

How was it that simple this whole fucking time aaaaa

* thingy

* style(autofix.ci): automated formatting

* Okay fine maybe revert the JSON nerfs, also I borked a bit of it

* things

* style(autofix.ci): automated formatting

* Update ranged.cpp

* style(autofix.ci): automated formatting

* Update item.cpp

* Update item.cpp

* Save the throwing damage info for later because it murders tests

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: scarf <greenscarf005@gmail.com>
  • Loading branch information
3 people authored Dec 6, 2024
1 parent 9d64fa9 commit add4b21
Show file tree
Hide file tree
Showing 5 changed files with 93 additions and 27 deletions.
10 changes: 5 additions & 5 deletions data/json/items/ranged/slings.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,16 @@
"type": "GUN",
"color": "brown",
"name": { "ctxt": "weapon", "str": "sling" },
"description": "A leather sling, easy to use and accurate. It uses pebbles as ammunition.",
"description": "A leather sling, easy to use and accurate. It uses pebbles or rocks as ammunition.",
"price": "150 cent",
"//": "It's little more than a piece of slightly shaped leather",
"material": "leather",
"extend": { "flags": [ "BELT_CLIP" ] },
"ammo": [ "pebble", "rock" ],
"weight": "100 g",
"volume": "250 ml",
"price_postapoc": "250 cent",
"to_hit": -2,
"//": "Gets more damage and range based on throwing skill and strength",
"ranged_damage": { "damage_type": "bash", "amount": 6 },
"range": 8,
"dispersion": 150,
Expand All @@ -30,7 +30,6 @@
"name": { "str": "staff sling" },
"description": "A leather sling attached to a staff, easy to use and accurate. It uses rocks as ammunition.",
"price": "150 cent",
"//": "The staff sling,consists of a staff with a short sling at one end. ",
"material": [ "wood", "leather" ],
"weapon_category": [ "QUARTERSTAVES" ],
"extend": { "flags": [ "BELTED", "DURABLE_MELEE", "NONCONDUCTIVE", "SHEATH_SPEAR", "ALWAYS_TWOHAND" ] },
Expand All @@ -40,6 +39,7 @@
"volume": "2 L",
"price_postapoc": "250 cent",
"to_hit": 1,
"//": "Gets more damage and range based on throwing skill and strength",
"ranged_damage": { "damage_type": "bash", "amount": 10 },
"bashing": 18,
"range": 10,
Expand Down Expand Up @@ -102,9 +102,9 @@
"weight": "8 kg",
"volume": "4 L",
"skill": "launcher",
"ranged_damage": { "damage_type": "bash", "amount": 20 },
"ranged_damage": { "damage_type": "bash", "amount": 25 },
"dispersion": 60,
"range": 24,
"range": 30,
"reload": 800,
"durability": 6,
"clip_size": 1,
Expand Down
12 changes: 11 additions & 1 deletion src/character_functions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include "npc.h"
#include "output.h"
#include "player.h"
#include "ranged.h"
#include "rng.h"
#include "skill.h"
#include "submap.h"
Expand Down Expand Up @@ -67,6 +68,8 @@ static const bionic_id bio_uncanny_dodge( "bio_uncanny_dodge" );
static const itype_id itype_battery( "battery" );
static const itype_id itype_UPS( "UPS" );

static const skill_id skill_throw( "throw" );

namespace character_funcs
{

Expand Down Expand Up @@ -897,7 +900,14 @@ item_reload_option select_ammo( const Character &who, item &base,
row += string_format( "| %-3d*%3d%% ", static_cast<int>( dam_amt ),
clamp( static_cast<int>( du.damage_multiplier * 100 ), 0, 999 ) );
} else {
float dam_amt = dam.total_damage();
float throw_amt = 0;
if( base.gun_skill() == skill_throw ) {
item &tmp = *item::spawn_temporary( item( ammo ) );
throw_amt += ranged::throw_damage( tmp,
who.get_skill_level( skill_throw ),
who.get_str() );
}
float dam_amt = std::max( dam.total_damage(), throw_amt );
row += string_format( "| %-8d ", static_cast<int>( dam_amt ) );
}
if( du.res_mult != 1.0f ) {
Expand Down
34 changes: 28 additions & 6 deletions src/item.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ static const itype_id itype_water_acid( "water_acid" );
static const itype_id itype_water_acid_weak( "water_acid_weak" );

static const skill_id skill_survival( "survival" );
static const skill_id skill_throw( "throw" );
static const skill_id skill_unarmed( "unarmed" );
static const skill_id skill_weapon( "weapon" );

Expand Down Expand Up @@ -2270,9 +2271,19 @@ void item::gun_info( const item *mod, std::vector<iteminfo> &info, const iteminf

gun_du.damage_multiplier *= ranged::str_draw_damage_modifier( *mod, viewer );

const damage_unit &ammo_du = curammo != nullptr
? curammo->ammo->damage.damage_units.front()
: damage_unit( DT_STAB, 0 );
damage_unit thrown_du = damage_unit( DT_STAB, 0 );

damage_unit ammo_du = curammo != nullptr
? curammo->ammo->damage.damage_units.front()
: damage_unit( DT_STAB, 0 );

if( skill.ident() == skill_throw && curammo != nullptr ) {
item &tmp = *item::spawn_temporary( item( curammo ) );

thrown_du.amount += ranged::throw_damage( tmp,
get_avatar().get_skill_level( skill_throw ),
get_avatar().get_str() );
}

if( parts->test( iteminfo_parts::GUN_DAMAGE ) ) {
insert_separation_line( info );
Expand All @@ -2287,14 +2298,14 @@ void item::gun_info( const item *mod, std::vector<iteminfo> &info, const iteminf
damage_instance ammo_dam = curammo->ammo->damage;
info.emplace_back( "GUN", "ammo_damage", "",
iteminfo::no_newline | iteminfo::no_name |
iteminfo::show_plus, ammo_du.amount );
iteminfo::show_plus, std::max( ammo_du.amount, thrown_du.amount ) );
}

if( parts->test( iteminfo_parts::GUN_DAMAGE_TOTAL ) ) {
// Intentionally not using total_damage() as it applies multipliers
info.emplace_back( "GUN", "sum_of_damage", _( " = <num>" ),
iteminfo::no_newline | iteminfo::no_name,
gun_du.amount + ammo_du.amount );
gun_du.amount + std::max( ammo_du.amount, thrown_du.amount ) );
}
}
info.back().bNewLine = true;
Expand Down Expand Up @@ -2510,6 +2521,11 @@ void item::gun_info( const item *mod, std::vector<iteminfo> &info, const iteminf
mod->get_gun_ups_drain() ) );
}

if( skill.ident() == skill_throw ) {
info.emplace_back( "GUN",
_( "Damage/range will vary with <info>throwing skill and ammo.</info>" ) );
}

if( parts->test( iteminfo_parts::GUN_AIMING_STATS ) ) {
insert_separation_line( info );
info.emplace_back( "GUN", _( "<bold>Base aim speed</bold>: " ), "<num>", iteminfo::no_flags,
Expand Down Expand Up @@ -7747,7 +7763,13 @@ int item::gun_range( bool with_ammo ) const
if( ammo_shape ) {
ret = ammo_shape->get_range();
} else {
ret += ammo_data()->ammo->range;
int ret_thrown = 0;
if( gun_skill() == skill_throw && ammo_data() ) {
const itype *curammo = ammo_data();
item &tmp = *item::spawn_temporary( item( curammo ) );
ret_thrown += get_avatar().throw_range( tmp );
}
ret += std::max( ammo_data()->ammo->range, ret_thrown );
}
}
return std::min( std::max( 0, ret ), RANGE_HARD_CAP );
Expand Down
55 changes: 43 additions & 12 deletions src/ranged.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -918,6 +918,17 @@ int ranged::fire_gun( Character &who, const tripoint &target, int max_shots, ite
: nullptr;
projectile projectile = make_gun_projectile( gun );

// Slings use ammo damage or damage from throwing the ammo, whichever is higher
if( gun.gun_skill() == skill_throw && !who.is_fake() && gun.ammo_data() ) {
item &tmp = *item::spawn_temporary( item( gun.ammo_data() ) );
if( throw_damage( tmp, who.get_skill_level( skill_throw ),
who.get_str() ) > gun.ammo_data()->ammo->damage.damage_units.front().amount ) {
projectile.impact = gun.gun_damage( false );
auto &impact = projectile.impact;
impact.add_damage( DT_BASH,
throw_damage( tmp, who.get_skill_level( skill_throw ), who.get_str() ) );
}
}
// Damage reduction from insufficient strength, if using a STR_DRAW weapon.
projectile.impact.mult_damage( ranged::str_draw_damage_modifier( gun, who ) );

Expand Down Expand Up @@ -1169,6 +1180,35 @@ int throwing_dispersion( const Character &c, const item &to_throw, Creature *cri
return std::max( 0, dispersion );
}

namespace
{
auto throw_damage_projectile( const item &it, const int skill, const int str ) -> projectile
{
const units::mass weight = it.weight();

projectile proj;
proj.impact = it.base_damage_thrown();
proj.speed = std::log2( std::max( 1, skill ) * std::max( 1, str ) );

const int damage = 0.5 * ( weight / 1_gram / 1000.0 ) * std::pow( proj.speed, 2 );

proj.impact.add_damage( DT_BASH, damage );

// add_msg( m_info, "skill_level is %s", skill );
// add_msg( m_info, "effective_strength is %s", str );
// add_msg( m_info, "Thrown item weight is %s grams", to_gram( weight ) );

// add_msg( m_info, "Calculated damage is %s", damage );

return proj;
}
} // namespace

auto throw_damage( const item &it, const int skill, const int str ) -> int
{
return throw_damage_projectile( it, skill, str ).impact.total_damage();
}

dealt_projectile_attack throw_item( Character &who, const tripoint &target,
detached_ptr<item> &&to_throw,
std::optional<tripoint> blind_throw_from_pos )
Expand Down Expand Up @@ -1218,16 +1258,7 @@ dealt_projectile_attack throw_item( Character &who, const tripoint &target,
throw_assist ? throw_assist_str : do_railgun ? who.get_str() * 2 : who.get_str();

// We'll be constructing a projectile
projectile proj;
proj.impact = thrown.base_damage_thrown();
proj.speed = std::log2( std::max( 1, skill_level ) )
+ std::log2( std::max( 1, effective_strength ) );
auto &impact = proj.impact;

// calculate extra damage, proportional to 1/2mv^2
// @see https://www.desmos.com/calculator/ibo2jh9cqa
const float damage = 0.5 * ( weight / 1_gram / 1000.0 ) * std::pow( proj.speed, 2 );
impact.add_damage( DT_BASH, static_cast<int>( damage ) );
projectile proj = throw_damage_projectile( thrown, skill_level, effective_strength );

if( thrown.has_flag( flag_ACT_ON_RANGED_HIT ) ) {
proj.add_effect( ammo_effect_ACT_ON_RANGED_HIT );
Expand Down Expand Up @@ -1267,7 +1298,7 @@ dealt_projectile_attack throw_item( Character &who, const tripoint &target,

// Deal extra cut damage if the item breaks
if( shatter ) {
impact.add_damage( DT_CUT, units::to_milliliter( volume ) / 500.0f );
proj.impact.add_damage( DT_CUT, units::to_milliliter( volume ) / 500.0f );
proj.add_effect( ammo_effect_SHATTER_SELF );
}

Expand All @@ -1278,7 +1309,7 @@ dealt_projectile_attack throw_item( Character &who, const tripoint &target,

// Some minor (skill/2) armor piercing for skillful throws
// Not as much as in melee, though
for( damage_unit &du : impact.damage_units ) {
for( damage_unit &du : proj.impact.damage_units ) {
du.res_pen += skill_level / 2.0f;
}
// handling for tangling thrown items
Expand Down
9 changes: 6 additions & 3 deletions src/ranged.h
Original file line number Diff line number Diff line change
Expand Up @@ -192,15 +192,18 @@ int fire_gun( Character &who, const tripoint &target, int shots = 1 );
int fire_gun( Character &who, const tripoint &target, int shots, item &gun,
item *ammo );

/** Expected thrown damage with a given item, given the thrower's effective strength and skill. */
auto throw_damage( const item &it, const int skill, const int str ) -> int;

/**
* Execute a throw.
* @param who Character whose stats to use
* @param to_throw Item being thrown
* @param blind_throw_from_pos Position of blind throw (if blind throwing)
*/
dealt_projectile_attack throw_item( Character &who, const tripoint &target,
detached_ptr<item> &&to_throw,
std::optional<tripoint> blind_throw_from_pos );
auto throw_item( Character &who, const tripoint &target,
detached_ptr<item> &&to_throw,
std::optional<tripoint> blind_throw_from_pos ) -> dealt_projectile_attack;

} // namespace ranged

Expand Down

0 comments on commit add4b21

Please sign in to comment.