Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial cleanup of melee code #18697

Merged
merged 24 commits into from Oct 9, 2016

Conversation

Projects
None yet
5 participants
@mugling
Copy link
Contributor

commented Oct 9, 2016

The melee code is amongst the oldest in the code base. It is poorly documented and contains many inconsistencies.

Key changes:

  • Start separate piercing (STAB, SPEAR) damage from cutting. In the long-term I'd like to start further differentiating these types.
  • Unarmed skill is also heading towards being a 'first-class' weapon skill
  • Items benefit from weapon skills when they do more than MELEE_STAT (5) damage of the relevant type. Below this threshold only melee skill can provide bonuses.
  • Crits and to hit chance are now dependent on the highest damage type. Previously it was possible for high bashing skill to cause crits for a cutting 30, bashing 5 sword etc
  • Starts defining some constants, especially BIO_CQB_LEVEL
  • Add encapsulation eg. damage_melee( DT_BASH ) as opposed to type->melee_dam so that flags such as REDUCED_BASHING and DIAMOND are always correctly handled

@mugling mugling force-pushed the mugling:melee branch 2 times, most recently to b82cf7b Oct 9, 2016

@codemime

This comment has been minimized.

Copy link
Member

commented Oct 9, 2016

I'd also suggest renaming is_weap() and weap_skill() to is_melee() and melee_skill() respectively.
Renaming is_gun() and gun_skill() to is_ranged() and ranged_skill() would be also nice. The strongest argument, apart from consistency, is that bows, slingshots, throwable spears e.t.c aren't guns.

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

@mugling

This comment has been minimized.

Copy link
Contributor Author

commented Oct 9, 2016

I'd also suggest renaming is_weap() and weap_skill() to is_melee() and melee_skill() respectively.

Yes, but separate PR (keep line count down for now)

@mugling

This comment has been minimized.

Copy link
Contributor Author

commented Oct 9, 2016

Ready

@Noctifer-de-Mortem

This comment has been minimized.

Copy link
Contributor

commented Oct 9, 2016

@mugling Maybe not the place but since changes to CQB Bionic are included maybe #18470 could be considered on another melee code re-balance PR?

@@ -97,4 +97,10 @@ constexpr double accuracy_goodhit = 0.5;
constexpr double accuracy_standard = 0.8;
constexpr double accuracy_grazing = 1.0;

/** Minimum item damage output of relevant type to allow using with relevant weapon skill */
#define MELEE_STAT 5

This comment has been minimized.

Copy link
@Coolthulhu

Coolthulhu Oct 9, 2016

Contributor

That name is really ambiguous.
The old option - having is_cutting_weapon etc. was better.
Could be combined into is_weapon( damage_type ) - this would be both clear and centralized.

This comment has been minimized.

Copy link
@mugling

mugling Oct 9, 2016

Author Contributor

Or is_melee( damage_type ) to combine it with @codemime's suggestion

This comment has been minimized.

Copy link
@mugling

mugling Oct 9, 2016

Author Contributor

I've updated that retaining the constant only so that item_factory.cpp assigns the weapon category consistently with the check in is_melee()

@@ -3829,13 +3803,32 @@ std::string item::gun_type() const

skill_id item::weap_skill() const
{
if( !is_weap() && !is_tool() ) {
if( is_null() || is_gun() || is_gunmod() || is_food() || is_ammo() ||

This comment has been minimized.

Copy link
@Coolthulhu

Coolthulhu Oct 9, 2016

Contributor

This makes guns with bayonets lose any scaling.

This comment has been minimized.

Copy link
@Coolthulhu

Coolthulhu Oct 9, 2016

Contributor

Oh and it also makes pure unarmed combat have no weap_skill.

This comment has been minimized.

Copy link
@mugling

mugling Oct 9, 2016

Author Contributor

This makes guns with bayonets lose any scaling.

I'm not sure what you mean about scaling.

Oh and it also makes pure unarmed combat have no weap_skill

The null item shouldn't have a skill but I've added checks for unarmed combat where necessary in 3fa13d4:

  • player::get_hit_weapon
  • player::crit_chance

This comment has been minimized.

Copy link
@Coolthulhu

Coolthulhu Oct 9, 2016

Contributor

I'm not sure what you mean about scaling.

If you're using a gun with a bayonet as a spear, it should train spears (and vice versa).

The null item shouldn't have a skill

It should - it is treated as the fist in pretty much all cases.

This comment has been minimized.

Copy link
@mugling

mugling Oct 9, 2016

Author Contributor

If you're using a gun with a bayonet as a spear, it should train spears (and vice versa).

That would mean both is_gun() and is_melee() could be true at the same time. That isn't necessarily a problem but we would need that so code continues to perform the checks in that order (currently the case for all existing code)

The null item shouldn't have a skill

It should - it is treated as the fist in pretty much all cases.

I'm less sure about this. The only cases where it matters were the two functions above

src/itype.h Outdated
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::map<damage_type, int> melee;

This comment has been minimized.

Copy link
@Coolthulhu

Coolthulhu Oct 9, 2016

Contributor

Could just be an array. damage_type is an enum small enough that map will both slow things down and make it occupy more memory.

This comment has been minimized.

Copy link
@mugling

mugling Oct 9, 2016

Author Contributor

Agreed, done

best_bonus = std::max( best_bonus, bashing_skill / 3.0f );
// CQB bionic acts as a lower bound providing item uses a weapon skill
if( bonus < 5 && has_active_bionic( "bio_cqb" ) ) {
bonus = 5;

This comment has been minimized.

Copy link
@Coolthulhu

Coolthulhu Oct 9, 2016

Contributor

You declared a constant for this purpose and aren't using it here.

This comment has been minimized.

Copy link
@mugling

mugling Oct 9, 2016

Author Contributor

Fixed

///\EFFECT_MELEE adds to other weapon bonuses
return int(melee_skill / 2.0f + best_bonus);
///\EFFECT_MELEE improves hit chance for all items (including non-weapons)
return bonus + ( get_skill_level( skill_melee ) / 2.0f );

This comment has been minimized.

Copy link
@Coolthulhu

Coolthulhu Oct 9, 2016

Contributor

This block will conflict with #18351, which waited much longer and changes much more.
This PR should probably wait for it.

This comment has been minimized.

Copy link
@mugling

mugling Oct 9, 2016

Author Contributor

Yes, #18351 should take precedence

This comment has been minimized.

Copy link
@mugling

mugling Oct 9, 2016

Author Contributor

What is the plan with #18351 as I don't want to hold either PR up given I've reviewed #18351 and it's otherwise ready to merge and we are also a long way along with this.

If you have an update ready for test tolerances and standard survivors then I'll wait on that, otherwise one of us can do that in a followup PR.

// Practice melee and relevant weapon skill (if any) except when using CQB bionic
if( !has_active_bionic( "bio_cqb" ) ) {
practice( skill_melee, rng( 5, 10 ) );
practice( weapon.weap_skill(), rng( 5, 10 ) );

This comment has been minimized.

Copy link
@Coolthulhu

Coolthulhu Oct 9, 2016

Contributor

This significantly changes the behavior of training - now you only train the highest (or first of the highest) skill.
Meaning that using a wood axe will only teach you bashing. Same for fire axe which is 20/20.

This comment has been minimized.

Copy link
@mugling

mugling Oct 9, 2016

Author Contributor

Eventually I want to move to a system where the player makes either a bashing, cutting or stabbing attack (dependent on what would be most effective). For now we could split 150% of the current XP between skills proportionally?

This comment has been minimized.

Copy link
@Coolthulhu

Coolthulhu Oct 9, 2016

Contributor

Splitting into different attacks is good, but making them all "typed" is not. We had this earlier in the development for knives and it lead to them always training only one of the types due to it scaling better at low levels.

Splitting training sounds good.
But it shouldn't be 150% - more two practices which sum up to 150%, but each is at most 100%.
This is because low amount practices are inherently worse when repeated a lot. Each practice drains focus regardless of amount.
Alternatively, if training one type only, it could be trained twice instead of for twice the amount.

This comment has been minimized.

Copy link
@mugling

mugling Oct 9, 2016

Author Contributor

Can you look at what I wrote (d1438d8) which defacto results in either 1 or 2 weapon skills being trained plus melee.

@@ -483,7 +453,7 @@ void player::reach_attack( const tripoint &p )
g->m.has_flag( "THIN_OBSTACLE", p ) &&
x_in_y( skill, 10 ) ) ) {
///\EFFECT_STR increases bash effects when reach attacking past something
g->m.bash( p, str_cur + weapon.type->melee_dam );
g->m.bash( p, str_cur + weapon.damage_melee( DT_BASH ) );

This comment has been minimized.

Copy link
@Coolthulhu

Coolthulhu Oct 9, 2016

Contributor

The function name here is ambiguous in a very important aspect: is this damage base or modified?
Currently all weapon bashes are done on base power.

This comment has been minimized.

Copy link
@mugling

mugling Oct 9, 2016

Author Contributor

Intentional change to use modified values. For example REDUCED_BASHING should reduce the effectiveness

if( weap.type->m_to_hit > 0 ) {
weapon_crit_chance = std::max(
weapon_crit_chance, 0.5 + 0.1 * weap.type->m_to_hit );
weapon_crit_chance = std::max( weapon_crit_chance, 0.5 + 0.1 * weap.type->m_to_hit );

This comment has been minimized.

Copy link
@Coolthulhu

Coolthulhu Oct 9, 2016

Contributor

That's a nerf to proper unarmed (ie. no punch daggers), which is already underpowered.

This comment has been minimized.

Copy link
@mugling

mugling Oct 9, 2016

Author Contributor

What should be done here?

This comment has been minimized.

Copy link
@Coolthulhu

Coolthulhu Oct 9, 2016

Contributor

Old code allowed max( weap.type->m_to_hit, unarmed / 2 ).
It could be a good idea to allow pure unarmed (!is_armed() as opposed to unarmed_strike(), preferably with a comment explaining it) to benefit from the max, but still require regular unarmed to only use weapon type to hit.

This comment has been minimized.

Copy link
@mugling

mugling Oct 9, 2016

Author Contributor

I see. Given I fixed player::unarmed_attack() to actually match its function docs (c9678a3) we could restore that initial check using that?

This comment has been minimized.

Copy link
@mugling

mugling Oct 9, 2016

Author Contributor

Done

@mugling mugling force-pushed the mugling:melee branch 2 times, most recently Oct 9, 2016

@mugling mugling force-pushed the mugling:melee branch to 72e142b Oct 9, 2016

@mugling

This comment has been minimized.

Copy link
Contributor Author

commented Oct 9, 2016

Everything fixed except waiting for #18351 and the last comment about nerf to unarmed which may not still apply?

@@ -248,6 +248,25 @@ void player::roll_all_damage( bool crit, damage_instance &di, bool average, cons
roll_stab_damage( crit, di, average, weap );
}

static void melee_train( player &p, int lo, int hi ) {
p.practice( skill_melee, rng( lo, hi ) );

This comment has been minimized.

Copy link
@Coolthulhu

Coolthulhu Oct 9, 2016

Contributor

Should probably train for 50%-75% of primary weapon skill. Otherwise it will always be higher than any weapon skill.

This comment has been minimized.

Copy link
@mugling

mugling Oct 9, 2016

Author Contributor

It's a copy-pasta from the original but sure, fixed

{
if( is_null() ) {
if( is_null() || dt >= NUM_DT ) {

This comment has been minimized.

Copy link
@Coolthulhu

Coolthulhu Oct 9, 2016

Contributor

This check should not be caught like that. If anything calls for a damage_type outside range, it should either debugmsg, crash or bug out hard to show that something is going wrong.

This comment has been minimized.

Copy link
@mugling

mugling Oct 9, 2016

Author Contributor

Fixed with an assert

{
if( is_null() )
if( is_null() || is_gun() || is_gunmod() || is_food() || is_ammo() ||
is_food_container() || is_armor() || is_book() || is_bionic() ) {

This comment has been minimized.

Copy link
@Coolthulhu

Coolthulhu Oct 9, 2016

Contributor

This whole check is needlessly restricting.
Consider the cases:

  • Someone grabs an alien leg (is_food() == true) and bashes stuff with it
  • Gun is used to bash/stab something
  • Someone mods in gasoline as edible, chainsaw suddenly stops being a weapon when loaded
  • Bible-thumping with a very large bible

is_null would make sense (but be redundant due to low damage) here if you didn't add the unarmed check below - if unarmed weapons are weapons, then pure unarmed should count too.

This comment has been minimized.

Copy link
@mugling

mugling Oct 9, 2016

Author Contributor

Fair enough, have pushed fix

src/itype.h Outdated
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;

This comment has been minimized.

Copy link
@Coolthulhu

Coolthulhu Oct 9, 2016

Contributor

Is it zeroed properly somewhere?
I created at least 2 bugs by not explicitly zeroing arrays.

This comment has been minimized.

Copy link
@mugling

mugling Oct 9, 2016

Author Contributor

No - I had presumed it would be default initialized - is this not the case?

This comment has been minimized.

Copy link
@Coolthulhu

Coolthulhu Oct 9, 2016

Contributor

Pretty sure it isn't - otherwise I wouldn't have made those 2 bugs.
It can be compiler dependent.

@@ -77,7 +77,7 @@ bool player::is_armed() const

bool player::unarmed_attack() const
{
return weapon.has_flag("UNARMED_WEAPON");
return !is_armed() || weapon.has_flag( "UNARMED_WEAPON" );

This comment has been minimized.

Copy link
@Coolthulhu

Coolthulhu Oct 9, 2016

Contributor

Redundant

This comment has been minimized.

Copy link
@mugling

mugling Oct 9, 2016

Author Contributor

Why? If the player has no weapon they are by default making an unarmed attack?

This comment has been minimized.

Copy link
@Coolthulhu

Coolthulhu Oct 9, 2016

Contributor

"none" has the flag:

{
        "type":"GENERIC",
        "id": "null",
        "symbol": "$",
        "color": "red",
        "name": "none",
        "name_plural": "none",
        "description": "seeing this is a bug",
        "price": 0,
        "volume": 0,
        "flags": ["PSEUDO", "UNARMED_WEAPON", "TRADER_AVOID"], "//" : "Fist is internally represented as 'wielding a none as a weapon'"
    }
@@ -13738,13 +13740,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 cut = thrown.damage_melee( DT_CUT );
int stab = thrown.damage_melee( DT_STAB );
if( cut || stab ) {
proj.impact.add_damage( cut > stab ? DT_CUT : DT_STAB, cut > stab ? cut : stab );

This comment has been minimized.

Copy link
@Coolthulhu

Coolthulhu Oct 9, 2016

Contributor

Would be significantly more readable as

if( cut > 0 || stab > 0 ) {
    proj.impact.add_damage( cut > stab ? DT_CUT : DT_STAB, std::max( cut, stab ) );
}

Or even just by allowing stuff to deal both types separately.

This comment has been minimized.

Copy link
@mugling

mugling Oct 9, 2016

Author Contributor

Agreed, fixed

@mugling

This comment has been minimized.

Copy link
Contributor Author

commented Oct 9, 2016

Fixed everything.

What is the plan with #18351 as I don't want to hold either PR up given I've reviewed #18351 and it's otherwise ready to merge and we are also a long way along with this.

If you have an update ready for test tolerances and standard survivors then I'll wait on that, otherwise one of us can do that in a followup PR.

@@ -251,7 +251,7 @@ void player::roll_all_damage( bool crit, damage_instance &di, bool average, cons
static void melee_train( player &p, int lo, int hi ) {
p.practice( skill_melee, ceil( rng( lo, hi ) / 2.0 ) );

if( p.unarmed_attack() ) {
if( !p.is_armed() ) {

This comment has been minimized.

Copy link
@Coolthulhu

Coolthulhu Oct 9, 2016

Contributor

Is this special case needed?
It probably doesn't hurt, but looks totally redundant, considering that the check below now covers all sorts of unarmed.

This comment has been minimized.

Copy link
@mugling

mugling Oct 9, 2016

Author Contributor

Agreed, fixed

@mugling mugling referenced this pull request Oct 9, 2016

Merged

[CR]New hit/dodge formula #18351

3 of 3 tasks complete
@mugling

This comment has been minimized.

Copy link
Contributor Author

commented Oct 9, 2016

Includes all fixes from review and merged against master to resolve conflict from #18351

Ready

@Coolthulhu Coolthulhu self-assigned this Oct 9, 2016

@Coolthulhu

This comment has been minimized.

Copy link
Contributor

commented Oct 9, 2016

src/melee.cpp: In member function ‘virtual void player::melee_attack(Creature&, bool, const matec_id&)’:
src/melee.cpp:367:18: error: variable ‘bashing’ set but not used [-Werror=unused-but-set-variable]
             bool bashing = (d.type_damage(DT_BASH) >= 10 && !unarmed_attack());
                  ^
src/melee.cpp:368:18: error: variable ‘cutting’ set but not used [-Werror=unused-but-set-variable]
             bool cutting = (d.type_damage(DT_CUT) >= 10);
                  ^
src/melee.cpp:369:18: error: variable ‘stabbing’ set but not used [-Werror=unused-but-set-variable]
             bool stabbing = (d.type_damage(DT_STAB) >= 10);
@mugling

This comment has been minimized.

Copy link
Contributor Author

commented Oct 9, 2016

@Coolthulhu fixed - that entire block had become redundant

@Coolthulhu Coolthulhu merged commit ff6eed3 into CleverRaven:master Oct 9, 2016

1 check passed

default This has been rescheduled for testing as the 'master' branch has been updated.
@Coolthulhu

This comment has been minimized.

Copy link
Contributor

commented Oct 9, 2016

Hint for the future: CQB bionic skill adjustment could easily be handled wholly in get_skill_level by adding a flag to affected skills and checking said flag there.

@mugling

This comment has been minimized.

Copy link
Contributor Author

commented Oct 9, 2016

Good idea

@Zireael07

This comment has been minimized.

Copy link
Contributor

commented Oct 10, 2016

This PR broke the buildbot?
See #18711

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.