@@ -58,6 +58,7 @@ const skill_id skill_melee( "melee" );
const skill_id skill_bashing( "bashing" );
const skill_id skill_cutting( "cutting" );
const skill_id skill_stabbing( "stabbing" );
const skill_id skill_unarmed( "unarmed" );

const quality_id quality_jack( "JACK" );
const quality_id quality_lift( "LIFT" );
@@ -699,15 +700,23 @@ std::string item::info( bool showtext, std::vector<iteminfo> &info ) const
info.emplace_back( "BASE", _( "<bold>Rigid</bold>: " ), _( "No (contents increase volume)" ) );
}

if( damage_bash() > 0 || damage_cut() > 0 ) {
info.push_back( iteminfo( "BASE", _( "Bash: " ), "", damage_bash(), true, "", false ) );
if( has_flag( "SPEAR" ) ) {
info.push_back( iteminfo( "BASE", space + _( "Pierce: " ), "", damage_cut(), true, "", false ) );
} else if( has_flag( "STAB" ) ) {
info.push_back( iteminfo( "BASE", space + _( "Stab: " ), "", damage_cut(), true, "", false ) );
} else {
info.push_back( iteminfo( "BASE", space + _( "Cut: " ), "", damage_cut(), true, "", false ) );
}
int dmg_bash = damage_melee( DT_BASH );
int dmg_cut = damage_melee( DT_CUT );
int dmg_stab = damage_melee( DT_STAB );

if( dmg_bash ) {
info.emplace_back( "BASE", _( "Bash: " ), "", dmg_bash, true, "", false );
}
if( dmg_cut ) {
info.emplace_back( "BASE", ( dmg_bash ? space : std::string() ) + _( "Cut: " ),
"", dmg_cut, true, "", false );
}
if( dmg_stab ) {
info.emplace_back( "BASE", ( ( dmg_bash || dmg_cut ) ? space : std::string() ) + _( "Pierce: " ),
"", dmg_stab, true, "", false );
}

if( dmg_bash || dmg_cut || dmg_stab ) {
info.push_back( iteminfo( "BASE", space + _( "To-hit bonus: " ),
( ( type->m_to_hit > 0 ) ? "+" : "" ),
type->m_to_hit, true, "" ) );
@@ -1665,8 +1674,8 @@ std::string item::info( bool showtext, std::vector<iteminfo> &info ) const
}

///\EFFECT_MELEE >2 allows seeing melee damage stats on weapons
if( debug_mode || ( g->u.get_skill_level( skill_melee ) > 2 && ( damage_bash() > 0 ||
damage_cut() > 0 || type->m_to_hit > 0 ) ) ) {
if( debug_mode || ( g->u.get_skill_level( skill_melee ) > 2 && ( damage_melee( DT_BASH ) > 0 ||
damage_melee( DT_CUT ) > 0 || damage_melee( DT_STAB ) > 0 || type->m_to_hit > 0 ) ) ) {
damage_instance non_crit;
g->u.roll_all_damage( false, non_crit, true, *this );
damage_instance crit;
@@ -2012,8 +2021,8 @@ void item::on_wield( player &p, int mv )
float d = 32.0; // arbitrary linear scaling factor
if( is_gun() ) {
d /= std::max( (float)p.get_skill_level( gun_skill() ), 1.0f );
} else if( is_weap() ) {
d /= std::max( (float)p.get_skill_level( weap_skill() ), 1.0f );
} else if( is_melee() ) {
d /= std::max( (float)p.get_skill_level( melee_skill() ), 1.0f );
}

int penalty = get_var( "volume", type->volume / units::legacy_volume_factor ) * d;
@@ -2528,65 +2537,51 @@ int item::attack_time() const
return ret;
}

int item::damage_bash() const
{
int total = type->melee_dam;
if( is_null() ) {
return 0;
}
total -= total * damage() * 0.1;
if(has_flag("REDUCED_BASHING")) {
total *= 0.5;
}
if (total > 0) {
return total;
} else {
return 0;
}
}

int item::damage_cut() const
int item::damage_melee( damage_type dt ) const
{
assert( dt >= DT_NULL && dt < NUM_DT );
if( is_null() ) {
return 0;
}

// effectiveness is reduced by 10% per damage level
int res = type->melee_cut;
int res = type->melee[ dt ];
res -= res * damage() * 0.1;

// apply type specific flags
switch( dt ) {
case DT_BASH:
if( has_flag( "REDUCED_BASHING" ) ) {
res *= 0.5;
}

This comment has been minimized.

Copy link
@codemime

codemime Oct 9, 2016

Member

Forgotten break?

This comment has been minimized.

Copy link
@mugling

mugling Oct 9, 2016

Author Contributor

Fixed

break;

case DT_CUT:
case DT_STAB:
if( has_flag( "DIAMOND" ) ) {
res *= 1.3;
}
break;

default:
break;
}

// consider any melee gunmods
if( is_gun() ) {
std::vector<int> opts = { res };
for( const auto &e : gun_all_modes() ) {
if( e.second.target != this && e.second.melee() ) {
opts.push_back( e.second.target->damage_cut() );
opts.push_back( e.second.target->damage_melee( dt ) );
}
}
return *std::max_element( opts.begin(), opts.end() );

} else if( has_flag( "DIAMOND" ) ) {
res *= 1.3;
}

return std::max( res, 0 );
}

int item::damage_by_type( damage_type dt ) const
{
switch( dt ) {
case DT_BASH:
return damage_bash();
case DT_CUT:
return ( has_flag( "SPEAR" ) || has_flag( "STAB" ) ) ? 0 : damage_cut();
case DT_STAB:
return ( has_flag( "SPEAR" ) || has_flag( "STAB" ) ) ? damage_cut() : 0;
default:
break;
}

return 0;
}

int item::reach_range( const player &p ) const
{
int res = 1;
@@ -3481,31 +3476,19 @@ bool item::is_ammo_container() const
return !is_magazine() && !contents.empty() && contents.front().is_ammo();
}

bool item::is_weap() const
bool item::is_melee() const
{
if( is_null() )
return false;

if (is_gun() || is_food() || is_ammo() || is_food_container() || is_armor() ||
is_book() || is_tool() || is_bionic() || is_gunmod())
return false;
return (type->melee_dam > 7 || type->melee_cut > 5);
}

bool item::is_bashing_weapon() const
{
if( is_null() )
return false;

return (type->melee_dam >= 8);
for( auto idx = DT_NULL + 1; idx != NUM_DT; ++idx ) {
if( is_melee( static_cast<damage_type>( idx ) ) ) {
return true;
}
}
return false;
}

bool item::is_cutting_weapon() const
bool item::is_melee( damage_type dt ) const
{
if( is_null() )
return false;

return (type->melee_cut >= 8 && !has_flag("SPEAR"));
return damage_melee( dt ) > MELEE_STAT;
}

const islot_armor *item::find_armor_data() const
@@ -3827,15 +3810,29 @@ std::string item::gun_type() const
return gun_skill().c_str();
}

skill_id item::weap_skill() const
skill_id item::melee_skill() const
{
if( !is_weap() && !is_tool() ) {
if( !is_melee() ) {
return NULL_ID;
}

if (type->melee_dam >= type->melee_cut) return skill_bashing;
if( has_flag("STAB") || has_flag( "SPEAR" ) ) return skill_stabbing;
return skill_cutting;
if( has_flag( "UNARMED_WEAPON" ) ) {
return skill_unarmed;
}

int hi = 0;
skill_id res = NULL_ID;

for( auto idx = DT_NULL + 1; idx != NUM_DT; ++idx ) {
auto val = damage_melee( static_cast<damage_type>( idx ) );
auto sk = skill_by_dt ( static_cast<damage_type>( idx ) );
if( val > hi && sk ) {
hi = val;
res = sk;
}
}

return res;
}

int item::gun_dispersion( bool with_ammo ) const
@@ -5789,8 +5786,8 @@ skill_id item::contextualize_skill( const skill_id &id ) const
if( id == weapon_skill ) {
if( is_gun() ) {
return gun_skill();
} else if( is_weap() ) {
return weap_skill();
} else if( is_melee() ) {
return melee_skill();
}
}
}
@@ -367,34 +367,30 @@ class item : public JsonSerializer, public JsonDeserializer, public visitable<it
* takes. The actual time depends heavily on the attacker, see melee.cpp.
*/
int attack_time() const;
/**
* Damage of type @ref DT_BASH that is caused by using this item as melee weapon.
*/
int damage_bash() const;
/**
* Damage of type @ref DT_CUT that is caused by using this item as melee weapon.
*/
int damage_cut() const;
/**
* Damage of a given type that is caused by using this item as melee weapon.
* NOTE: Does NOT respect the legacy "stabbing is cutting"!
*/
int damage_by_type( damage_type dt ) const;

/** Damage of given type caused when this item is used as melee weapon */
int damage_melee( damage_type dt ) const;

/**
* Whether the character needs both hands to wield this item.
*/
bool is_two_handed( const player &u ) const;
/** The weapon is considered a suitable melee weapon. */
bool is_weap() const;
/** The item is considered a bashing weapon (inflicts a considerable bash damage). */
bool is_bashing_weapon() const;
/** The item is considered a cutting weapon (inflicts a considerable cutting damage). */
bool is_cutting_weapon() const;

/** Is this item an effective melee weapon for the given damage type? */
bool is_melee( damage_type dt ) const;

/**
* Is this item an effective melee weapon for any damage type?
* @see item::is_gun()
* @note an item can be both a gun and melee weapon concurrently
*/
bool is_melee() const;

/**
* The most relevant skill used with this melee weapon. Can be "null" if this is not a weapon.
* Note this function returns null if the item is a gun for which you can use gun_skill() instead.
*/
skill_id weap_skill() const;
skill_id melee_skill() const;
/*@}*/

/** Max range weapon usable for melee attack accounting for player/NPC abilities */
@@ -1202,6 +1198,12 @@ class item : public JsonSerializer, public JsonDeserializer, public visitable<it
*/
/*@{*/
bool is_gunmod() const;

/**
* Can this item be used to perform a ranged attack?
* @see item::is_melee()
* @note an item can be both a gun and melee weapon concurrently
*/
bool is_gun() const;

/** Quantity of ammunition currently loaded in tool, gun or axuiliary gunmod */
@@ -120,6 +120,10 @@ void Item_factory::finalize() {
for( auto& e : m_templates ) {
itype& obj = *e.second;

if( obj.item_tags.count( "STAB" ) || obj.item_tags.count( "SPEAR" ) ) {
std::swap(obj.melee[DT_CUT], obj.melee[DT_STAB]);
}

// add usage methods (with default values) based upon qualities
// if a method was already set the specific values remain unchanged
for( const auto &q : obj.qualities ) {
@@ -1532,8 +1536,8 @@ void Item_factory::load_basic_info( JsonObject &jo, itype *new_item_template, co
assign( jo, "price", new_item_template->price );
assign( jo, "price_postapoc", new_item_template->price_post );
assign( jo, "integral_volume", new_item_template->integral_volume );
assign( jo, "bashing", new_item_template->melee_dam, strict, 1 );
assign( jo, "cutting", new_item_template->melee_cut, strict, 1 );
assign( jo, "bashing", new_item_template->melee[DT_BASH], strict, 0 );
assign( jo, "cutting", new_item_template->melee[DT_CUT], strict, 0 );
assign( jo, "to_hit", new_item_template->m_to_hit, strict );
assign( jo, "container", new_item_template->default_container );
assign( jo, "rigid", new_item_template->rigid );
@@ -2093,10 +2097,12 @@ const std::string &Item_factory::calc_category( const itype *it )
if( it->bionic ) {
return category_id_cbm;
}
if (it->melee_dam > 7 || it->melee_cut > 5) {
return category_id_weapons;
}
return category_id_other;

bool weap = std::any_of( it->melee.begin(), it->melee.end(), []( int qty ) {
return qty > MELEE_STAT;
} );

return weap ? category_id_weapons : category_id_other;
}

std::vector<Group_tag> Item_factory::get_all_group_names()
@@ -12,6 +12,7 @@
#include "vitamin.h"
#include "emit.h"
#include "units.h"
#include "damage.h"

#include <string>
#include <vector>
@@ -648,8 +649,9 @@ struct itype {

bool rigid = true; // If non-rigid volume (and if worn encumbrance) increases proportional to contents

int melee_dam = 0; // Bonus for melee damage; may be a penalty
int melee_cut = 0; // Cutting damage in melee
/** Damage output in melee for zero or more damage types */
std::array<int, NUM_DT> melee = {};

int m_to_hit = 0; // To-hit bonus for melee combat; -5 to 5 is reasonable

unsigned light_emission = 0; // Exactly the same as item_tags LIGHT_*, this is for lightmap.
@@ -357,7 +357,7 @@ bool ma_requirements::is_valid_weapon( const item &i ) const
}
}
for( const auto &pr : min_damage ) {
if( i.damage_by_type( pr.first ) < pr.second ) {
if( i.damage_melee( pr.first ) < pr.second ) {
return false;
}
}

Large diffs are not rendered by default.

@@ -68,7 +68,7 @@ void mdefense::acidsplash( monster &m, Creature *const source,
size_t num_drops = rng( 4, 6 );
player const *const foe = dynamic_cast<player *>( source );
if( proj == nullptr && foe != nullptr ) {
if( foe->weapon.is_cutting_weapon() ) {
if( foe->weapon.is_melee( DT_CUT ) || foe->weapon.is_melee( DT_STAB ) ) {
num_drops += rng( 3, 4 );
}

@@ -1586,7 +1586,7 @@ int npc::smash_ability() const
{
if( !is_following() || rules.allow_bash ) {
///\EFFECT_STR_NPC increases smash ability
return str_cur + weapon.type->melee_dam;
return str_cur + weapon.damage_melee( DT_BASH );
}

// Not allowed to bash
@@ -2180,7 +2180,7 @@ bool npc::wield_better_weapon()

visit_items( [this, &compare_weapon]( item *node ) {
// Skip some bad items
if( !node->is_gun() && node->type->melee_dam + node->type->melee_cut < 5 ) {
if( !node->is_melee() ) {
return VisitResponse::SKIP;
}

@@ -4479,7 +4479,9 @@ int player::intimidation() const
if (weapon.is_gun()) {
ret += 10;
}
if (weapon.damage_bash() >= 12 || weapon.damage_cut() >= 12) {
if( weapon.damage_melee( DT_BASH ) >= 12 ||
weapon.damage_melee( DT_CUT ) >= 12 ||
weapon.damage_melee( DT_STAB ) >= 12 ) {
ret += 5;
}
if (has_trait("SAPIOVORE")) {
@@ -10342,7 +10344,7 @@ int player::item_store_cost( const item& it, const item& /* container */, bool e
///\EFFECT_STABBING decreases time taken to store a stabbing weapon
///\EFFECT_CUTTING decreases time taken to store a cutting weapon
///\EFFECT_BASHING decreases time taken to store a bashing weapon
int lvl = get_skill_level( it.is_gun() ? it.gun_skill() : it.weap_skill() );
int lvl = get_skill_level( it.is_gun() ? it.gun_skill() : it.melee_skill() );
return item_handling_cost( it, effects, factor ) / std::max( sqrt( ( lvl + 3 ) / 3 ), 1.0 );
}

@@ -13033,7 +13035,7 @@ bool player::wield_contents( item *container, int pos, int factor, bool effects
///\EFFECT_STABBING decreases time taken to draw stabbing weapons from sheathes
///\EFFECT_CUTTING decreases time taken to draw cutting weapons from scabbards
///\EFFECT_BASHING decreases time taken to draw bashing weapons from holsters
int lvl = get_skill_level( weapon.is_gun() ? weapon.gun_skill() : weapon.weap_skill() );
int lvl = get_skill_level( weapon.is_gun() ? weapon.gun_skill() : weapon.melee_skill() );
mv += item_handling_cost( weapon, effects, factor ) / std::max( sqrt( ( lvl + 3 ) / 3 ), 1.0 );

moves -= mv;
@@ -13737,13 +13739,17 @@ void player::blossoms()

float player::power_rating() const
{
int dmg = std::max( { weapon.damage_melee( DT_BASH ),
weapon.damage_melee( DT_CUT ),
weapon.damage_melee( DT_STAB ) } );

This comment has been minimized.

Copy link
@Coolthulhu

Coolthulhu Oct 9, 2016

Contributor

Side note: sum of damage is a better measure of "weaponiness" than maximum.

This comment has been minimized.

Copy link
@mugling

mugling Oct 9, 2016

Author Contributor

Can overhaul that whole function in a further PR


int ret = 2;
// Small guns can be easily hidden from view
if( weapon.volume() <= 250_ml ) {
ret = 2;
} else if( weapon.is_gun() ) {
ret = 4;
} else if( weapon.damage_bash() + weapon.damage_cut() > 20 ) {
} else if( dmg > 12 ) {
ret = 3; // Melee weapon or weapon-y tool
}
if( has_trait("HUGE") || has_trait("HUGE_OK") ) {
@@ -687,7 +687,7 @@ dealt_projectile_attack player::throw_item( const tripoint &target, const item &
// The damage dealt due to item's weight and player's strength
///\EFFECT_STR increases throwing damage
int real_dam = ( (thrown.weight() / 452)
+ (thrown.type->melee_dam / 2)
+ (thrown.damage_melee(DT_BASH) / 2)
+ (str_cur / 2) )
/ (2.0 + (vol / 4.0));
if( real_dam > thrown.weight() / 40 ) {
@@ -745,11 +745,12 @@ dealt_projectile_attack player::throw_item( const tripoint &target, const item &
proj_effects.insert( "SHATTER_SELF" );
}

if( rng(0, 100) < 20 + skill_level * 12 && thrown.type->melee_cut > 0 ) {
const auto type =
( thrown.has_flag("SPEAR") || thrown.has_flag("STAB") ) ?
DT_STAB : DT_CUT;
proj.impact.add_damage( type, thrown.type->melee_cut );
if( rng(0, 100) < 20 + skill_level * 12 ) {
int cut = thrown.damage_melee( DT_CUT );
int stab = thrown.damage_melee( DT_STAB );
if( cut > 0 || stab > 0 ) {
proj.impact.add_damage( cut > stab ? DT_CUT : DT_STAB, std::max( cut, stab ) );
}
}

// Put the item into the projectile
@@ -635,7 +635,7 @@ sfx::sound_thread::sound_thread( const tripoint &source, const tripoint &target,
vol_targ = std::max(heard_volume - 20, 0);
}
ang_targ = get_heard_angle( target );
weapon_skill = p->weapon.weap_skill();
weapon_skill = p->weapon.melee_skill();
weapon_volume = p->weapon.volume() / units::legacy_volume_factor;
}

@@ -229,7 +229,7 @@ void tutorial_game::post_action( action_id act )
add_message( LESSON_GOT_TOOL );
} else if( it.is_food() ) {
add_message( LESSON_GOT_FOOD );
} else if( it.is_weap() ) {
} else if( it.is_melee() ) {
add_message( LESSON_GOT_WEAPON );
}