Skip to content

Commit

Permalink
Allow using stronger bows at penalty (#1501)
Browse files Browse the repository at this point in the history
* Allow using stronger bows at penalty

* Update ranged.cpp

* Apply suggestions from code review

Co-authored-by: Olanti <olanti-p@yandex.ru>

* astylerizify suggested code

* Suggestions per feedback

* Update ranged.cpp

* Remove churl bonus for now, better function names

* stylin'

* Update JSON_FLAGS.md

* Adjust values per feedback

Co-authored-by: Olanti <olanti-p@yandex.ru>
  • Loading branch information
chaosvolt and olanti-p authored Jun 7, 2022
1 parent 7e13a3f commit b2083d2
Show file tree
Hide file tree
Showing 10 changed files with 115 additions and 17 deletions.
6 changes: 6 additions & 0 deletions data/json/flags.json
Original file line number Diff line number Diff line change
Expand Up @@ -1767,6 +1767,12 @@
"type": "json_flag",
"context": [ ]
},
{
"id": "STR_DRAW",
"type": "json_flag",
"context": [ ],
"info": "Using this weapon with <info>less strength than its requirement</info> will still allow you to fire, but at reduced <bad>damage and range</bad>."
},
{
"id": "STR_RELOAD",
"type": "json_flag",
Expand Down
12 changes: 7 additions & 5 deletions data/json/items/ranged/archery.json
Original file line number Diff line number Diff line change
Expand Up @@ -376,7 +376,7 @@
"description": "Though not as powerful as the longbow, this bow is quicker and easier to draw, and can be used effectively by those of average strength. Good for small game or the survivor on the go.",
"price": 16000,
"material": "wood",
"flags": [ "FIRE_TWOHAND", "RELOAD_AND_SHOOT", "PRIMITIVE_RANGED_WEAPON", "BELTED", "WATER_FRIENDLY" ],
"flags": [ "FIRE_TWOHAND", "RELOAD_AND_SHOOT", "PRIMITIVE_RANGED_WEAPON", "BELTED", "WATER_FRIENDLY", "STR_DRAW" ],
"skill": "archery",
"min_strength": 8,
"ammo": "arrow",
Expand Down Expand Up @@ -404,7 +404,7 @@
"description": "A high-power bow with shaped cams and extra cables for high velocity shots that can be used effectively by fairly strong archers. Currently set to a medium weight.",
"price": 55000,
"material": [ "steel", "plastic" ],
"flags": [ "FIRE_TWOHAND", "RELOAD_AND_SHOOT", "PRIMITIVE_RANGED_WEAPON" ],
"flags": [ "FIRE_TWOHAND", "RELOAD_AND_SHOOT", "PRIMITIVE_RANGED_WEAPON", "STR_DRAW" ],
"skill": "archery",
"min_strength": 9,
"ammo": "arrow",
Expand Down Expand Up @@ -437,6 +437,7 @@
"description": "A high-power bow with shaped cams and extra cables for high velocity shots that can be used effectively by very strong archers. Currently set to a high weight, and ready to cause some real damage - if you can draw it.",
"min_strength": 11,
"ranged_damage": { "damage_type": "stab", "amount": 40 },
"range": 20,
"use_action": {
"menu_text": "Loosen Limbs",
"type": "transform",
Expand All @@ -453,6 +454,7 @@
"description": "A high-power bow with shaped cams and extra cables for high velocity shots that can be used effectively by average archers. Currently set to a low weight, making it much easier to draw.",
"min_strength": 7,
"ranged_damage": { "damage_type": "stab", "amount": 32 },
"range": 16,
"use_action": {
"menu_text": "Tighten Limbs",
"type": "transform",
Expand All @@ -471,7 +473,7 @@
"description": "A bow made from multiple materials for increased energy efficiency. Complicated to make, but stronger than an all-wood short bow.",
"price": 32000,
"material": [ "wood", "bone" ],
"flags": [ "FIRE_TWOHAND", "RELOAD_AND_SHOOT", "PRIMITIVE_RANGED_WEAPON", "BELTED", "WATER_FRIENDLY" ],
"flags": [ "FIRE_TWOHAND", "RELOAD_AND_SHOOT", "PRIMITIVE_RANGED_WEAPON", "BELTED", "WATER_FRIENDLY", "STR_DRAW" ],
"skill": "archery",
"min_strength": 12,
"ammo": "arrow",
Expand All @@ -498,7 +500,7 @@
"description": "A six-foot wooden bow that takes a fair amount of strength to draw. It can be used effectively by those of somewhat above-average strength. Used mainly in medieval England in wartime, but pierces zombie hide just as well as chainmail.",
"price": 50000,
"material": "wood",
"flags": [ "FIRE_TWOHAND", "RELOAD_AND_SHOOT", "PRIMITIVE_RANGED_WEAPON", "BELTED", "WATER_FRIENDLY" ],
"flags": [ "FIRE_TWOHAND", "RELOAD_AND_SHOOT", "PRIMITIVE_RANGED_WEAPON", "BELTED", "WATER_FRIENDLY", "STR_DRAW" ],
"skill": "archery",
"min_strength": 9,
"ammo": "arrow",
Expand Down Expand Up @@ -526,7 +528,7 @@
"description": "An extremely large and powerful compound bow, made with enormous limbs and a thick string to take an immense amount of energy. Takes incredible strength to draw.",
"price": 160000,
"material": [ "steel", "plastic" ],
"flags": [ "FIRE_TWOHAND", "RELOAD_AND_SHOOT", "PRIMITIVE_RANGED_WEAPON" ],
"flags": [ "FIRE_TWOHAND", "RELOAD_AND_SHOOT", "PRIMITIVE_RANGED_WEAPON", "STR_DRAW" ],
"skill": "archery",
"min_strength": 15,
"ammo": "arrow",
Expand Down
6 changes: 3 additions & 3 deletions data/json/obsoletion/items.json
Original file line number Diff line number Diff line change
Expand Up @@ -2291,7 +2291,7 @@
"description": "A modern bow that curves away from the archer at the tips for increased power, which can be used effectively by those of somewhat above-average strength. Good and easy to use, but nothing special.",
"price": 38000,
"material": [ "steel", "plastic" ],
"flags": [ "FIRE_TWOHAND", "RELOAD_AND_SHOOT", "PRIMITIVE_RANGED_WEAPON", "BELTED", "WATER_FRIENDLY" ],
"flags": [ "FIRE_TWOHAND", "STR_DRAW", "RELOAD_AND_SHOOT", "PRIMITIVE_RANGED_WEAPON", "BELTED", "WATER_FRIENDLY" ],
"skill": "archery",
"min_strength": 9,
"ammo": "arrow",
Expand Down Expand Up @@ -2319,7 +2319,7 @@
"description": "A bow with limbs that curve away from the archer near the riser for power. Smaller than some bows, but decently powerful and quick.",
"price": 42000,
"material": [ "wood" ],
"flags": [ "FIRE_TWOHAND", "RELOAD_AND_SHOOT", "PRIMITIVE_RANGED_WEAPON", "BELTED", "WATER_FRIENDLY" ],
"flags": [ "FIRE_TWOHAND", "STR_DRAW", "RELOAD_AND_SHOOT", "PRIMITIVE_RANGED_WEAPON", "BELTED", "WATER_FRIENDLY" ],
"skill": "archery",
"ammo": "arrow",
"min_strength": 9,
Expand Down Expand Up @@ -2392,7 +2392,7 @@
"description": "A modernized bow that combines the traits from both the reflex and the recurve bows, with both limbs and tips curving away from the archer, and reinforced with composite materials. This dramatically increases the power and requires significant strength to draw the bow effectively, but can still be fired quickly.",
"price": 72000,
"material": [ "wood" ],
"flags": [ "FIRE_TWOHAND", "RELOAD_AND_SHOOT", "PRIMITIVE_RANGED_WEAPON", "BELTED", "WATER_FRIENDLY" ],
"flags": [ "FIRE_TWOHAND", "STR_DRAW", "RELOAD_AND_SHOOT", "PRIMITIVE_RANGED_WEAPON", "BELTED", "WATER_FRIENDLY" ],
"skill": "archery",
"min_strength": 8,
"ammo": "arrow",
Expand Down
1 change: 1 addition & 0 deletions data/mods/Magiclysm/items/ethereal_items.json
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,7 @@
"description": "A magically conjured ornate recurve bow of solid flexible wood. A matching conjured wooden arrow appears when you draw the string back for firing.",
"flags": [
"FIRE_TWOHAND",
"STR_DRAW",
"PRIMITIVE_RANGED_WEAPON",
"BELTED",
"UNBREAKABLE_MELEE",
Expand Down
2 changes: 1 addition & 1 deletion doc/JSON_FLAGS.md
Original file line number Diff line number Diff line change
Expand Up @@ -721,7 +721,7 @@ List of known flags, used in both `terrain.json` and `furniture.json`.
- ```RELOAD_AND_SHOOT``` Firing automatically reloads and then shoots.
- ```RELOAD_EJECT``` Ejects shell from gun on reload instead of when fired.
- ```RELOAD_ONE``` Only reloads one round at a time.
- ```STR_DRAW``` Range with this weapon is reduced unless character has at least twice the required minimum strength
- ```STR_DRAW``` If the weapon also has a strength requirement, lacking the requirement will penalize damage, range, and dispersion until you're unable to fire if below 50% the listed strength, instead of being a strict limit like it normally would be.
- ```STR_RELOAD``` Reload speed is affected by strength.
- ```UNDERWATER_GUN``` Gun is optimized for usage underwater, does perform badly outside of water.
- ```WATERPROOF_GUN``` Gun does not rust and can be used underwater.
Expand Down
2 changes: 1 addition & 1 deletion src/activity_actor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,7 @@ bool aim_activity_actor::load_RAS_weapon()
}

// Burn 0.2% max base stamina x the strength required to fire.
you.mod_stamina( gun->get_min_str() * static_cast<int>( 0.002f *
you.mod_stamina( gun->get_min_str() * static_cast<int>( -0.002f *
get_option<int>( "PLAYER_MAX_STAMINA" ) ) );
// At low stamina levels, firing starts getting slow.
int sta_percent = ( 100 * you.get_stamina() ) / you.get_stamina_max();
Expand Down
3 changes: 2 additions & 1 deletion src/character.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,7 @@ static const std::string flag_SEMITANGIBLE( "SEMITANGIBLE" );
static const std::string flag_SKINTIGHT( "SKINTIGHT" );
static const std::string flag_SPEEDLOADER( "SPEEDLOADER" );
static const std::string flag_SPLINT( "SPLINT" );
static const std::string flag_STR_DRAW( "STR_DRAW" );
static const std::string flag_STURDY( "STURDY" );
static const std::string flag_SWIMMABLE( "SWIMMABLE" );
static const std::string flag_SWIM_GOGGLES( "SWIM_GOGGLES" );
Expand Down Expand Up @@ -3326,7 +3327,7 @@ bool Character::meets_skill_requirements( const construction &con ) const

bool Character::meets_stat_requirements( const item &it ) const
{
return get_str() >= it.get_min_str() &&
return ( it.has_flag( flag_STR_DRAW ) || get_str() >= it.get_min_str() ) &&
get_dex() >= it.type->min_dex &&
get_int() >= it.type->min_int &&
get_per() >= it.type->min_per;
Expand Down
28 changes: 22 additions & 6 deletions src/item.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1528,7 +1528,21 @@ void item::basic_info( std::vector<iteminfo> &info, const iteminfo_query *parts,
// Display any minimal stat or skill requirements for the item
std::vector<std::string> req;
if( get_min_str() > 0 ) {
req.push_back( string_format( "%s %d", _( "strength" ), get_min_str() ) );
avatar &viewer = get_avatar();
if( has_flag( flag_STR_DRAW ) && ranged::get_str_draw_penalty( *this, viewer ) < 1.0f ) {
if( ranged::get_str_draw_penalty( *this, viewer ) < 0.5f ) {
req.push_back( string_format( _( "%s %d <color_magenta>(Can't use!)</color>" ), _( "strength" ),
get_min_str() ) );
} else if( ranged::get_str_draw_penalty( *this, viewer ) < 0.75f ) {
req.push_back( string_format( "%s %d <color_red>(Damage/Range 0.5x, Dispersion 2.0x)</color>",
_( "strength" ), get_min_str() ) );
} else {
req.push_back( string_format( "%s %d <color_yellow>(Damage/Range 0.75x)</color>", _( "strength" ),
get_min_str() ) );
}
} else {
req.push_back( string_format( "%s %d", _( "strength" ), get_min_str() ) );
}
}
if( type->min_dex > 0 ) {
req.push_back( string_format( "%s %d", _( "dexterity" ), type->min_dex ) );
Expand Down Expand Up @@ -1941,6 +1955,7 @@ void item::gun_info( const item *mod, std::vector<iteminfo> &info, const iteminf
const std::string space = " ";
const islot_gun &gun = *mod->type->gun;
const Skill &skill = *mod->gun_skill();
avatar &viewer = get_avatar();

// many statistics are dependent upon loaded ammo
// if item is unloaded (or is RELOAD_AND_SHOOT) shows approximate stats using default ammo
Expand Down Expand Up @@ -1970,7 +1985,10 @@ void item::gun_info( const item *mod, std::vector<iteminfo> &info, const iteminf
debugmsg( "curammo is nullptr in item::gun_info()" );
return;
}
const damage_unit &gun_du = gun.damage.damage_units.front();
damage_unit gun_du = gun.damage.damage_units.front();

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 );
Expand Down Expand Up @@ -7101,10 +7119,8 @@ int item::gun_range( const player *p ) const
return 0;
}

// Reduce bow range until player has twice minimm required strength
if( has_flag( flag_STR_DRAW ) ) {
ret += std::max( 0.0, ( p->get_str() - get_min_str() ) * 0.5 );
}
// Reduce bow range if player has less than minimum strength.
ret *= ranged::str_draw_range_modifier( *this, *p );

return std::max( 0, ret );
}
Expand Down
66 changes: 66 additions & 0 deletions src/ranged.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ static const std::string flag_PRIMITIVE_RANGED_WEAPON( "PRIMITIVE_RANGED_WEAPON"
static const std::string flag_PYROMANIAC_WEAPON( "PYROMANIAC_WEAPON" );
static const std::string flag_RELOAD_AND_SHOOT( "RELOAD_AND_SHOOT" );
static const std::string flag_RESTRICT_HANDS( "RESTRICT_HANDS" );
static const std::string flag_STR_DRAW( "STR_DRAW" );
static const std::string flag_UNDERWATER_GUN( "UNDERWATER_GUN" );
static const std::string flag_VEHICLE( "VEHICLE" );

Expand Down Expand Up @@ -858,6 +859,10 @@ int player::fire_gun( const tripoint &target, const int max_shots, item &gun )
const vehicle *in_veh = has_effect( effect_on_roof ) ? veh_pointer_or_null( g->m.veh_at(
pos() ) ) : nullptr;
projectile projectile = make_gun_projectile( gun );

// Damage reduction from insufficient strength, if using a STR_DRAW weapon.
projectile.impact.mult_damage( ranged::str_draw_damage_modifier( gun, *this ) );

if( has_trait( trait_NORANGEDCRIT ) ) {
projectile.add_effect( ammo_effect_NO_CRIT );
}
Expand Down Expand Up @@ -983,6 +988,58 @@ int throw_cost( const player &c, const item &to_throw )
return std::max( 25, move_cost );
}

float get_str_draw_penalty( const item &it, const Character &p )
{
// We only care if weapon has STR_DRAW, and that the user is weaker than required strength.
// Also avoid dividing by zero, and skip if we'd just get a result of 1 anyway.
if( !it.has_flag( flag_STR_DRAW ) || p.get_str() >= it.get_min_str() || it.get_min_str() <= 1 ) {
return 1.0f;
}
// We also don't want to actually reduce values to zero, even if user is debuffed to zero strength.
float archer_str = std::max( 1, p.get_str() );
return ( archer_str / it.get_min_str() );
}

float str_draw_damage_modifier( const item &it, const Character &p )
{
if( !it.has_flag( flag_STR_DRAW ) || p.get_str() >= it.get_min_str() || it.get_min_str() <= 1 ) {
return 1.0f;
}
if( ranged::get_str_draw_penalty( it, p ) < 0.75f ) {
return 0.5f;
} else if( ranged::get_str_draw_penalty( it, p ) < 1.0f ) {
return 0.75f;
} else {
return 1.0f;
}
}

float str_draw_dispersion_modifier( const item &it, const Character &p )
{
if( !it.has_flag( flag_STR_DRAW ) || p.get_str() >= it.get_min_str() || it.get_min_str() <= 1 ) {
return 1.0f;
}
if( ranged::get_str_draw_penalty( it, p ) < 0.75f ) {
return 0.5f;
} else {
return 1.0f;
}
}

float str_draw_range_modifier( const item &it, const Character &p )
{
if( !it.has_flag( flag_STR_DRAW ) || p.get_str() >= it.get_min_str() || it.get_min_str() <= 1 ) {
return 1.0f;
}
if( ranged::get_str_draw_penalty( it, p ) < 0.75f ) {
return 0.5f;
} else if( ranged::get_str_draw_penalty( it, p ) < 1.0f ) {
return 0.75f;
} else {
return 1.0f;
}
}

int throw_dispersion_per_dodge( const Character &c, bool add_encumbrance )
{
// +200 per dodge point at 0 dexterity
Expand Down Expand Up @@ -1845,6 +1902,9 @@ dispersion_sources player::get_weapon_dispersion( const item &obj ) const
dispersion.add_multiplier( 0.75 );
}

// If using a bow you lack the strength for, increase based on how much weaker shooter is.
dispersion.add_multiplier( 1 / ranged::str_draw_dispersion_modifier( obj, *this ) );

// Range is effectively four times longer when shooting unflagged/flagged guns underwater/out of water.
if( is_underwater() != obj.has_flag( flag_UNDERWATER_GUN ) ) {
// Adding dispersion for additional debuff
Expand Down Expand Up @@ -3603,6 +3663,12 @@ bool ranged::gunmode_checks_common( avatar &you, const map &m, std::vector<std::
result = false;
}

if( ranged::get_str_draw_penalty( *gmode, you ) < 0.5f ) {
messages.push_back( string_format( _( "You don't have enough strength to fire your %s." ),
gmode->tname() ) );
result = false;
}

return result;
}

Expand Down
6 changes: 6 additions & 0 deletions src/ranged.h
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,12 @@ int throwing_dispersion( const Character &c, const item &to_throw, Creature *cri
bool is_blind_throw );
int throw_cost( const player &c, const item &to_throw );

/** Penalties potentially incurred by STR_DRAW weapons */
float get_str_draw_penalty( const item &it, const Character &p );
float str_draw_damage_modifier( const item &it, const Character &p );
float str_draw_dispersion_modifier( const item &it, const Character &p );
float str_draw_range_modifier( const item &it, const Character &p );

/** AoE attack, with area given by shape */
void execute_shaped_attack( const shape &sh, const projectile &proj, Creature &attacker );

Expand Down

0 comments on commit b2083d2

Please sign in to comment.