PDA

View Full Version : Damage Bonus Functions


Cantus
09-21-2008, 11:37 PM
First of all, I have to say a big "thank you" to all of the amazingly talented folks who make this project possible!

I've enjoyed playing on EQEmu servers for three years now, so it's way past time that I offered something back. I hope it's alright if I offer some code changes for the senior developers to consider including in the next build!

I do work as a software engineer, so my guild leader on PEQ The Grand Creation (Reno) convinced me to take a look at the code used to calculate two-handed weapon damage bonuses. I did, and it's pretty seriously broken, so I hope it's alright that I've put some work into fixing it.


Let me first list the current function used to calculate damage bonuses, so that I can point out what needed to be fixed in it. This method starts on line 1935 in the 2008-09-02 version of attack.cpp:

int Mob::GetWeaponDamageBonus(const Item_Struct* Weapon)
{
// Kaiyodo - Calculate the damage bonus for a weapon on the main hand
if (GetLevel() < 28)
return(0);

// Check we're on of the classes that gets a damage bonus
if (!IsWarriorClass())
return 0;

int BasicBonus = ((GetLevel() - 25) / 3) + 1;

if(!Weapon)
return(BasicBonus);

// If we have no weapon, or only a single handed weapon, just return the default
// damage bonus of (Level - 25) / 3
if (Weapon->ItemClass == ItemClassCommon)
return BasicBonus;

if ((Weapon->ItemType == ItemType1HS) || (Weapon->ItemType == ItemTypePierce) || (Weapon->ItemType == ItemType1HB))
return BasicBonus;

// Things get more complicated with 2 handers, the bonus is based on the delay of
// the weapon as well as a number stored inside the weapon.
int WeaponBonus = 0; // How do you find this out?

// Data for this, again, from www.monkly-business.com
if (Weapon->Delay <= 27)
return (WeaponBonus + BasicBonus + 1);
if (Weapon->Delay <= 39)
return (WeaponBonus + BasicBonus + ((GetLevel()-27) / 4));
if (Weapon->Delay <= 42)
return (WeaponBonus + BasicBonus + ((GetLevel()-27) / 4) + 1);
// Weapon must be > 42 delay
return (WeaponBonus + BasicBonus + ((GetLevel()-27) / 4) + ((Weapon->Delay-34) / 3));
}


Here are some of the more major items that needed to be fixed:

int BasicBonus = ((GetLevel() - 25) / 3) + 1;

The correct formula for calculating 1H weapon damage bonuses is (GetLevel() - 25) / 3. The "+ 1" is unnecessary, and artificially increases the damage bonus for all weapons.



// If we have no weapon, or only a single handed weapon, just return the default
// damage bonus of (Level - 25) / 3
if (Weapon->ItemClass == ItemClassCommon)
return BasicBonus;

This is the BIGGEST problem with this function. Weapon->ItemClass can have one of three values: ItemClassCommon, ItemClassContainer, or ItemClassBook.

We know that this function is only called if the item in the player's mainhand is a weapon (see line 904 in attack.cpp), so Weapon->ItemClass will ALWAYS be equal to ItemClassCommon.

This line ensures that any additional code in this function will NEVER BE EXECUTED.

So even though there was some code later in this function to approximate damage bonuses for 2H weapons, it never gets executed. With the function as it is currently, 2H weapons always receive the exact same damage bonus as 1H weapons do.



if ((Weapon->ItemType == ItemType1HS) || (Weapon->ItemType == ItemTypePierce) || (Weapon->ItemType == ItemType1HB))
return BasicBonus;

Not that it matters, as the bug mentioned above ensures that we'd never actually evaluate this conditional, but ItemTypeHand2Hand is omitted. So Hand to Hand weapons would have been evaluated using the two-handed formulae, and therefore would have had damage bonuses that were unfairly high.



The code for calculating damage bonuses for 2H weapons was a good attempt to approximate the actual formula that Sony/Verant uses. But it's inaccurate in many cases.

With a ton of help from Reno, Janusd, and Romai, I've constructed a formula to accurately return damage bonuses for all two-handed weapons currently in existence, for levels 28 - 80. I warn you: at first, this function may look absolutely ridiculous. But I assure you that quite a bit of work and thought went into it. It uses a hybrid of formulas and lookup tables designed for maximum efficiency. It looks big and ugly, but for the server it's a very good way of getting an accurate result without slowing things down.



Before I post the new formula to calculate 2H damage bonuses, let me post my suggestion for re-writing the above Mob::GetWeaponDamageBonus() function:

* If you don't like all of my comments, please feel free to remove them! I figure it's better to err on the side of too many comments, rather than too few!

int Mob::GetWeaponDamageBonus(const Item_Struct* Weapon)
{
// This function calculates and returns the damage bonus for the weapon identified by the parameter "Weapon".
// Modified 9/21/2008 by Cantus

// Assert: This function should only be called for hits by the mainhand, as damage bonuses apply only to the
// weapon in the primary slot. Be sure to check that Hand == 13 before calling.

// Assert: The caller should ensure that Weapon is actually a weapon before calling this function.
// The ItemInst::IsWeapon() method can be used to quickly determine this.

if( GetLevel() < 28 || !IsWarriorClass() )
{
// Either the PC's level is less than 28 (damage bonuses do not begin to apply until level 28),
// or the PC is not a melee class (only melee classes receive a damage bonus).

return 0;
}

if( Weapon == NULL || Weapon->ItemType == ItemType1HS || Weapon->ItemType == ItemType1HB || Weapon->ItemType == ItemTypeHand2Hand || Weapon->ItemType == ItemTypePierce )
{
// The weapon in the player's main (primary) hand is a one-handed weapon, or there is no item equipped at all.
//
// According to player posts on Allakhazam, 1H damage bonuses apply to bare fists (nothing equipped in the mainhand,
// as indicated by Weapon == NULL).
//
// The following formula returns the correct damage bonus for all 1H weapons:

return (GetLevel() - 25) / 3;
}

// If we've gotten to this point, the weapon in the mainhand is a two-handed weapon.
// Calculating damage bonuses for 2H weapons is more complicated, as it's based on PC level AND the delay of the weapon.
// The formula to calculate 2H bonuses is HIDEOUS. It's a huge conglomeration of ternary operators and multiple operations.
//
// The following formula uses a hybrid approach. In cases where the Level and Delay merit a formula
// that does not use many operators, the formula is used. In other cases, lookup tables are used for speed.
// Though the body of the formula looks ridiculous, it's actually a very efficient way of calculating these bonuses.

return Calculate2HDamageBonus( GetLevel(), Weapon->Delay );
}



Now, you'll also need a copy of the new Calculate2HDamageBonus() function. As I warned you, it's long and ugly-looking, but please don't let that fool you into thinking it's inefficient!

// **************************************************
// *** Calculate2HDamageBonus()
// ***
// *** Returns the Damage Bonus for a 2-Handed
// *** weapon based on that weapon's delay, and
// *** the level of the character wielding it.
// ***
// *** Submitted September 2008 by Eric Penoyer
// *** (aka Cantus on PEQ The Grand Creation)
// *** EPenoyer (at) gmail.com
// ***
// *** Optimizations and Contributions by:
// *** Reno, Janusd (aka Zetrakyl), and Romai
// **************************************************

inline unsigned char Calculate2HDamageBonus( unsigned char ucPlayerLevel, const unsigned char uc2HWeaponDelay )
{

#if EQDEBUG >= 4

// Assert: This function should not be called if ucPlayerLevel is less than 28,
// as damage bonuses do not apply to players levels 1-27.

if( ucPlayerLevel < 28 )
{
// Damage Bonuses do not begin to apply until a character hits level 28.

LogFile->write( EQEMuLog::Debug, "Calculate2HDamageBonus() called for player level %d. Should only be called for players levels 28+.", ucPlayerLevel );

return (unsigned char) 0;
}

#endif


// This function would look much cleaner as a simple 53x150 cell matrix.
// However, that would occupy 7,950 Bytes of memory (7.76 KB), and would likely result
// in "thrashing the cache" when performing lookups.
//
// It would be nice to reverse-engineer the actual formula used by Verant/Sony to calculate
// 2-Handed Weapon Damage Bonuses. Initially, I thought that this formula would be much more
// efficient than performing a table lookup. But the more than Reno and I worked on figuring
// out this formula, the more we're concluded that the formula itself is going to be ugly
// (that is, it contains so many operations and conditionals that it's fairly CPU intensive).
// Because of that, we're decided that a lookup table is likely to be the most efficient way
// to calculate most of these damage bonuses.
//
// The function below is a hybrid between a pure formulaic approach and a pure, brute-force
// lookup table. In cases where a formula is the best bet, I use a formula. In other places
// where a formula would be ugly, I use a lookup table in the interests of speed.


if( uc2HWeaponDelay <= 27 )
{
// Damage Bonuses for all 2H weapons with delays of 27 or less are identical.
// They are the same as the damage bonus would be for a corresponding 1H weapon, plus one.
// This formula applies to all levels 28-80, and will probably continue to apply if
// the level cap on Live ever is increased beyond 80.

return (ucPlayerLevel - 22) / 3;
}


if( ucPlayerLevel == 65 && uc2HWeaponDelay <= 59 )
{
// Consider these two facts:
// * Level 65 is the maximum level on many EQ Emu servers.
// * If you listed the levels of all characters logged on to a server, odds are that the number you'll
// see most frequently is level 65. That is, there are more level 65 toons than any other single level.
//
// Therefore, if we can optimize this function for level 65 toons, we're speeding up the server!
//
// With that goal in mind, I create an array of Damage Bonuses for level 65 characters wielding 2H weapons with
// delays between 28 and 59 (inclusive). I suspect that this one small lookup array will therefore handle
// many of the calls to this function.

static const unsigned char ucLevel65DamageBonusesForDelays28to59[] = {35, 35, 36, 36, 37, 37, 38, 38, 39, 39, 40, 40, 42, 42, 42, 45, 45, 47, 48, 49, 49, 51, 51, 52, 53, 54, 54, 56, 56, 57, 58, 59};

return ucLevel65DamageBonusesForDelays28to59[uc2HWeaponDelay-28];
}


if( ucPlayerLevel > 65 )
{
if( ucPlayerLevel > 80 )
{
// As level 80 is currently the highest achievable level on Live, we only include
// damage bonus information up to this level.
//
// If there is a custom EQEmu server that allows players to level beyond 80, the
// damage bonus for their 2H weapons will simply not increase beyond their damage
// bonus at level 80.

ucPlayerLevel = 80;
}

// Lucy does not list a chart of damage bonuses for players levels 66+,
// so my original version of this function just applied the level 65 damage
// bonus for level 66+ toons. That sucked for higher level toons, as their
// 2H weapons stopped ramping up in DPS as they leveled past 65.
//
// Thanks to the efforts of two guys, this is no longer the case:
//
// Janusd (Zetrakyl) ran a nifty query against the PEQ item database to list
// the name of an example 2H weapon that represents each possible unique 2H delay.
//
// Romai then wrote an excellent script to automatically look up each of those
// weapons, open the Lucy item page associated with it, and iterate through all
// levels in the range 66 - 80. He saved the damage bonus for that weapon for
// each level, and that forms the basis of the lookup tables below.

if( uc2HWeaponDelay <= 59 )
{
static const unsigned char ucDelay28to59Levels66to80[32][15]=
{
/* Level: */
/* 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80 */

{36, 37, 38, 39, 41, 42, 43, 44, 45, 47, 49, 49, 49, 50, 53}, /* Delay = 28 */
{36, 38, 38, 39, 42, 43, 43, 45, 46, 48, 49, 50, 51, 52, 54}, /* Delay = 29 */
{37, 38, 39, 40, 43, 43, 44, 46, 47, 48, 50, 51, 52, 53, 55}, /* Delay = 30 */
{37, 39, 40, 40, 43, 44, 45, 46, 47, 49, 51, 52, 52, 52, 54}, /* Delay = 31 */
{38, 39, 40, 41, 44, 45, 45, 47, 48, 48, 50, 52, 53, 55, 57}, /* Delay = 32 */
{38, 40, 41, 41, 44, 45, 46, 48, 49, 50, 52, 53, 54, 56, 58}, /* Delay = 33 */
{39, 40, 41, 42, 45, 46, 47, 48, 49, 51, 53, 54, 55, 57, 58}, /* Delay = 34 */
{39, 41, 42, 43, 46, 46, 47, 49, 50, 52, 54, 55, 56, 57, 59}, /* Delay = 35 */
{40, 41, 42, 43, 46, 47, 48, 50, 51, 53, 55, 55, 56, 58, 60}, /* Delay = 36 */
{40, 42, 43, 44, 47, 48, 49, 50, 51, 53, 55, 56, 57, 59, 61}, /* Delay = 37 */
{41, 42, 43, 44, 47, 48, 49, 51, 52, 54, 56, 57, 58, 60, 62}, /* Delay = 38 */
{41, 43, 44, 45, 48, 49, 50, 52, 53, 55, 57, 58, 59, 61, 63}, /* Delay = 39 */
{43, 45, 46, 47, 50, 51, 52, 54, 55, 57, 59, 60, 61, 63, 65}, /* Delay = 40 */
{43, 45, 46, 47, 50, 51, 52, 54, 55, 57, 59, 60, 61, 63, 65}, /* Delay = 41 */
{44, 46, 47, 48, 51, 52, 53, 55, 56, 58, 60, 61, 62, 64, 66}, /* Delay = 42 */
{46, 48, 49, 50, 53, 54, 55, 58, 59, 61, 63, 64, 65, 67, 69}, /* Delay = 43 */
{47, 49, 50, 51, 54, 55, 56, 58, 59, 61, 64, 65, 66, 68, 70}, /* Delay = 44 */
{48, 50, 51, 52, 56, 57, 58, 60, 61, 63, 65, 66, 68, 70, 72}, /* Delay = 45 */
{50, 52, 53, 54, 57, 58, 59, 62, 63, 65, 67, 68, 69, 71, 74}, /* Delay = 46 */
{50, 52, 53, 55, 58, 59, 60, 62, 63, 66, 68, 69, 70, 72, 74}, /* Delay = 47 */
{51, 53, 54, 55, 58, 60, 61, 63, 64, 66, 69, 69, 71, 73, 75}, /* Delay = 48 */
{52, 54, 55, 57, 60, 61, 62, 65, 66, 68, 70, 71, 73, 75, 77}, /* Delay = 49 */
{53, 55, 56, 57, 61, 62, 63, 65, 67, 69, 71, 72, 74, 76, 78}, /* Delay = 50 */
{53, 55, 57, 58, 61, 62, 64, 66, 67, 69, 72, 73, 74, 77, 79}, /* Delay = 51 */
{55, 57, 58, 59, 63, 64, 65, 68, 69, 71, 74, 75, 76, 78, 81}, /* Delay = 52 */
{57, 55, 59, 60, 63, 65, 66, 68, 70, 72, 74, 76, 77, 79, 82}, /* Delay = 53 */
{56, 58, 59, 61, 64, 65, 67, 69, 70, 73, 75, 76, 78, 80, 82}, /* Delay = 54 */
{57, 59, 61, 62, 66, 67, 68, 71, 72, 74, 77, 78, 80, 82, 84}, /* Delay = 55 */
{58, 60, 61, 63, 66, 68, 69, 71, 73, 75, 78, 79, 80, 83, 85}, /* Delay = 56 */

/* Important Note: Janusd's search for 2H weapons did not find */
/* any 2H weapon with a delay of 57. Therefore the values below */
/* are interpolated, not exact! */
{59, 61, 62, 64, 67, 69, 70, 72, 74, 76, 77, 78, 81, 84, 86}, /* Delay = 57 INTERPOLATED */

{60, 62, 63, 65, 68, 70, 71, 74, 75, 78, 80, 81, 83, 85, 88}, /* Delay = 58 */

/* Important Note: Janusd's search for 2H weapons did not find */
/* any 2H weapon with a delay of 59. Therefore the values below */
/* are interpolated, not exact! */
{60, 62, 64, 65, 69, 70, 72, 74, 76, 78, 81, 82, 84, 86, 89}, /* Delay = 59 INTERPOLATED */
};

return ucDelay28to59Levels66to80[uc2HWeaponDelay-28][ucPlayerLevel-66];
}
else
{
// Delay is 60+

const static unsigned char ucDelayOver59Levels66to80[6][15] =
{
/* Level: */
/* 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80 */

{61, 63, 65, 66, 70, 71, 73, 75, 77, 79, 82, 83, 85, 87, 90}, /* Delay = 60 */
{65, 68, 69, 71, 75, 76, 78, 80, 82, 85, 87, 89, 91, 93, 96}, /* Delay = 65 */

/* Important Note: Currently, the only 2H weapon with a delay */
/* of 66 is not player equippable (it's None/None). So I'm */
/* leaving it commented out to keep this table smaller. */
//{66, 68, 70, 71, 75, 77, 78, 81, 83, 85, 88, 90, 91, 94, 97}, /* Delay = 66 */

{70, 72, 74, 76, 80, 81, 83, 86, 88, 88, 90, 95, 97, 99, 102}, /* Delay = 70 */
{82, 85, 87, 89, 89, 94, 98, 101, 103, 106, 109, 111, 114, 117, 120}, /* Delay = 85 */
{90, 93, 96, 98, 103, 105, 107, 111, 113, 116, 120, 122, 125, 128, 131}, /* Delay = 95 */

/* Important Note: Currently, the only 2H weapons with delay */
/* 100 are GM-only items purchased from vendors in Sunset Home */
/* (cshome). Because they are highly unlikely to be used in */
/* combat, I'm commenting it out to keep the table smaller. */
//{95, 98, 101, 103, 108, 110, 113, 116, 119, 122, 126, 128, 131, 134, 138},/* Delay = 100 */

{136, 140, 144, 148, 154, 157, 161, 166, 170, 174, 179, 183, 187, 191, 196} /* Delay = 150 */
};

if( uc2HWeaponDelay < 65 )
{
return ucDelayOver59Levels66to80[0][ucPlayerLevel-66];
}
else if( uc2HWeaponDelay < 70 )
{
return ucDelayOver59Levels66to80[1][ucPlayerLevel-66];
}
else if( uc2HWeaponDelay < 85 )
{
return ucDelayOver59Levels66to80[2][ucPlayerLevel-66];
}
else if( uc2HWeaponDelay < 95 )
{
return ucDelayOver59Levels66to80[3][ucPlayerLevel-66];
}
else if( uc2HWeaponDelay < 150 )
{
return ucDelayOver59Levels66to80[4][ucPlayerLevel-66];
}
else
{
return ucDelayOver59Levels66to80[5][ucPlayerLevel-66];
}
}
}


// If we've gotten to this point in the function without hitting a return statement,
// we know that the character's level is between 28 and 65, and that the 2H weapon's
// delay is 28 or higher.

// The Damage Bonus values returned by this function (in the level 28-65 range) are
// based on a table of 2H Weapon Damage Bonuses provided by Lucy at the following address:
// http://lucy.allakhazam.com/dmgbonus.html

if( uc2HWeaponDelay <= 39 )
{
if( ucPlayerLevel <= 53)
{
// The Damage Bonus for all 2H weapons with delays between 28 and 39 (inclusive) is the same for players level 53 and below...
static const unsigned char ucDelay28to39LevelUnder54[] = {1, 1, 2, 3, 3, 3, 4, 5, 5, 6, 6, 6, 8, 8, 8, 9, 9, 10, 11, 11, 11, 12, 13, 14, 16, 17};

// As a note: The following formula accurately calculates damage bonuses for 2H weapons with delays in the range 28-39 (inclusive)
// for characters levels 28-50 (inclusive):
// return ( (ucPlayerLevel - 22) / 3 ) + ( (ucPlayerLevel - 25) / 5 );
//
// However, the small lookup array used above is actually much faster. So we'll just use it instead of the formula
//
// (Thanks to Reno for helping figure out the above formula!)

return ucDelay28to39LevelUnder54[ucPlayerLevel-28];
}
else
{
// Use a matrix to look up the damage bonus for 2H weapons with delays between 28 and 39 wielded by characters level 54 and above.
static const unsigned char ucDelay28to39Level54to64[12][11] =
{
/* Level: */
/* 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64 */

{17, 21, 21, 23, 25, 26, 28, 30, 31, 31, 33}, /* Delay = 28 */
{17, 21, 22, 23, 25, 26, 29, 30, 31, 32, 34}, /* Delay = 29 */
{18, 21, 22, 23, 25, 27, 29, 31, 32, 32, 34}, /* Delay = 30 */
{18, 21, 22, 23, 25, 27, 29, 31, 32, 33, 34}, /* Delay = 31 */
{18, 21, 22, 24, 26, 27, 30, 32, 32, 33, 35}, /* Delay = 32 */
{18, 21, 22, 24, 26, 27, 30, 32, 33, 34, 35}, /* Delay = 33 */
{18, 22, 22, 24, 26, 28, 30, 32, 33, 34, 36}, /* Delay = 34 */
{18, 22, 23, 24, 26, 28, 31, 33, 34, 34, 36}, /* Delay = 35 */
{18, 22, 23, 25, 27, 28, 31, 33, 34, 35, 37}, /* Delay = 36 */
{18, 22, 23, 25, 27, 29, 31, 33, 34, 35, 37}, /* Delay = 37 */
{18, 22, 23, 25, 27, 29, 32, 34, 35, 36, 38}, /* Delay = 38 */
{18, 22, 23, 25, 27, 29, 32, 34, 35, 36, 38} /* Delay = 39 */
};

return ucDelay28to39Level54to64[uc2HWeaponDelay-28][ucPlayerLevel-54];
}
}
else if( uc2HWeaponDelay <= 59 )
{
if( ucPlayerLevel <= 52 )
{
if( uc2HWeaponDelay <= 45 )
{
static const unsigned char ucDelay40to45Levels28to52[6][25] =
{
/* Level: */
/* 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52 */

{2, 2, 3, 4, 4, 4, 5, 6, 6, 7, 7, 7, 9, 9, 9, 10, 10, 11, 12, 12, 12, 13, 14, 16, 18}, /* Delay = 40 */
{2, 2, 3, 4, 4, 4, 5, 6, 6, 7, 7, 7, 9, 9, 9, 10, 10, 11, 12, 12, 12, 13, 14, 16, 18}, /* Delay = 41 */
{2, 2, 3, 4, 4, 4, 5, 6, 6, 7, 7, 7, 9, 9, 9, 10, 10, 11, 12, 12, 12, 13, 14, 16, 18}, /* Delay = 42 */
{4, 4, 5, 6, 6, 6, 7, 8, 8, 9, 9, 9, 11, 11, 11, 12, 12, 13, 14, 14, 14, 15, 16, 18, 20}, /* Delay = 43 */
{4, 4, 5, 6, 6, 6, 7, 8, 8, 9, 9, 9, 11, 11, 11, 12, 12, 13, 14, 14, 14, 15, 16, 18, 20}, /* Delay = 44 */
{5, 5, 6, 7, 7, 7, 8, 9, 9, 10, 10, 10, 12, 12, 12, 13, 13, 14, 15, 15, 15, 16, 17, 19, 21} /* Delay = 45 */
};

return ucDelay40to45Levels28to52[uc2HWeaponDelay-40][ucPlayerLevel-28];
}
else
{
static const unsigned char ucDelay46Levels28to52[] = {6, 6, 7, 8, 8, 8, 9, 10, 10, 11, 11, 11, 13, 13, 13, 14, 14, 15, 16, 16, 16, 17, 18, 20, 22};

return ucDelay46Levels28to52[ucPlayerLevel-28] + ((uc2HWeaponDelay-46) / 3);
}
}
else
{
// Player is in the level range 53 - 64

// Calculating damage bonus for 2H weapons with a delay between 40 and 59 (inclusive) involves, unforunately, a brute-force matrix lookup.
static const unsigned char ucDelay40to59Levels53to64[20][37] =
{
/* Level: */
/* 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64 */

{19, 20, 24, 25, 27, 29, 31, 34, 36, 37, 38, 40}, /* Delay = 40 */
{19, 20, 24, 25, 27, 29, 31, 34, 36, 37, 38, 40}, /* Delay = 41 */
{19, 20, 24, 25, 27, 29, 31, 34, 36, 37, 38, 40}, /* Delay = 42 */
{21, 22, 26, 27, 29, 31, 33, 37, 39, 40, 41, 43}, /* Delay = 43 */
{21, 22, 26, 27, 29, 32, 34, 37, 39, 40, 41, 43}, /* Delay = 44 */
{22, 23, 27, 28, 31, 33, 35, 38, 40, 42, 43, 45}, /* Delay = 45 */
{23, 24, 28, 30, 32, 34, 36, 40, 42, 43, 44, 46}, /* Delay = 46 */
{23, 24, 29, 30, 32, 34, 37, 40, 42, 43, 44, 47}, /* Delay = 47 */
{23, 24, 29, 30, 32, 35, 37, 40, 43, 44, 45, 47}, /* Delay = 48 */
{24, 25, 30, 31, 34, 36, 38, 42, 44, 45, 46, 49}, /* Delay = 49 */
{24, 26, 30, 31, 34, 36, 39, 42, 44, 46, 47, 49}, /* Delay = 50 */
{24, 26, 30, 31, 34, 36, 39, 42, 45, 46, 47, 49}, /* Delay = 51 */
{25, 27, 31, 33, 35, 38, 40, 44, 46, 47, 49, 51}, /* Delay = 52 */
{25, 27, 31, 33, 35, 38, 40, 44, 46, 48, 49, 51}, /* Delay = 53 */
{26, 27, 32, 33, 36, 38, 41, 44, 47, 48, 49, 52}, /* Delay = 54 */
{27, 28, 33, 34, 37, 39, 42, 46, 48, 50, 51, 53}, /* Delay = 55 */
{27, 28, 33, 34, 37, 40, 42, 46, 49, 50, 51, 54}, /* Delay = 56 */
{27, 28, 33, 34, 37, 40, 43, 46, 49, 50, 52, 54}, /* Delay = 57 */
{28, 29, 34, 36, 39, 41, 44, 48, 50, 52, 53, 56}, /* Delay = 58 */
{28, 29, 34, 36, 39, 41, 44, 48, 51, 52, 54, 56} /* Delay = 59 */
};

return ucDelay40to59Levels53to64[uc2HWeaponDelay-40][ucPlayerLevel-53];
}
}
else
{
// The following table allows us to look up Damage Bonuses for weapons with delays greater than or equal to 60.
//
// There aren't a lot of 2H weapons with a delay greater than 60. In fact, both a database and Lucy search run by janusd confirm
// that the only unique 2H delays greater than 60 are: 65, 70, 85, 95, and 150.
//
// To be fair, there are also weapons with delays of 66 and 100. But they are either not equippable (None/None), or are
// only available to GMs from merchants in Sunset Home (cshome). In order to keep this table "lean and mean", I will not
// include the values for delays 66 and 100. If they ever are wielded, the 66 delay weapon will use the 65 delay bonuses,
// and the 100 delay weapon will use the 95 delay bonuses. So it's not a big deal.
//
// Still, if someone in the future decides that they do want to include them, here are the tables for these two delays:
//
// {12, 12, 13, 14, 14, 14, 15, 16, 16, 17, 17, 17, 19, 19, 19, 20, 20, 21, 22, 22, 22, 23, 24, 26, 29, 30, 32, 37, 39, 42, 45, 48, 53, 55, 57, 59, 61, 64} /* Delay = 66 */
// {24, 24, 25, 26, 26, 26, 27, 28, 28, 29, 29, 29, 31, 31, 31, 32, 32, 33, 34, 34, 34, 35, 36, 39, 43, 45, 48, 55, 57, 62, 66, 71, 77, 80, 83, 85, 89, 92} /* Delay = 100 */
//
// In case there are 2H weapons added in the future with delays other than those listed above (and until the damage bonuses
// associated with that new delay are added to this function), this function is designed to do the following:
//
// For weapons with delays in the range 60-64, use the Damage Bonus that would apply to a 2H weapon with delay 60.
// For weapons with delays in the range 65-69, use the Damage Bonus that would apply to a 2H weapon with delay 65
// For weapons with delays in the range 70-84, use the Damage Bonus that would apply to a 2H weapon with delay 70.
// For weapons with delays in the range 85-94, use the Damage Bonus that would apply to a 2H weapon with delay 85.
// For weapons with delays in the range 95-149, use the Damage Bonus that would apply to a 2H weapon with delay 95.
// For weapons with delays 150 or higher, use the Damage Bonus that would apply to a 2H weapon with delay 150.

static const unsigned char ucDelayOver59Levels28to65[6][38] =
{
/* Level: */
/* 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64. 65 */

{10, 10, 11, 12, 12, 12, 13, 14, 14, 15, 15, 15, 17, 17, 17, 18, 18, 19, 20, 20, 20, 21, 22, 24, 27, 28, 30, 35, 36, 39, 42, 45, 49, 51, 53, 54, 57, 59}, /* Delay = 60 */
{12, 12, 13, 14, 14, 14, 15, 16, 16, 17, 17, 17, 19, 19, 19, 20, 20, 21, 22, 22, 22, 23, 24, 26, 29, 30, 32, 37, 39, 42, 45, 48, 52, 55, 57, 58, 61, 63}, /* Delay = 65 */
{14, 14, 15, 16, 16, 16, 17, 18, 18, 19, 19, 19, 21, 21, 21, 22, 22, 23, 24, 24, 24, 25, 26, 28, 31, 33, 35, 40, 42, 45, 48, 52, 56, 59, 61, 62, 65, 68}, /* Delay = 70 */
{19, 19, 20, 21, 21, 21, 22, 23, 23, 24, 24, 24, 26, 26, 26, 27, 27, 28, 29, 29, 29, 30, 31, 34, 37, 39, 41, 47, 49, 54, 57, 61, 66, 69, 72, 74, 77, 80}, /* Delay = 85 */
{22, 22, 23, 24, 24, 24, 25, 26, 26, 27, 27, 27, 29, 29, 29, 30, 30, 31, 32, 32, 32, 33, 34, 37, 40, 43, 45, 52, 54, 59, 62, 67, 73, 76, 79, 81, 84, 88}, /* Delay = 95 */
{40, 40, 41, 42, 42, 42, 43, 44, 44, 45, 45, 45, 47, 47, 47, 48, 48, 49, 50, 50, 50, 51, 52, 56, 61, 65, 69, 78, 82, 89, 94, 102, 110, 115, 119, 122, 127, 132} /* Delay = 150 */
};

if( uc2HWeaponDelay < 65 )
{
return ucDelayOver59Levels28to65[0][ucPlayerLevel-28];
}
else if( uc2HWeaponDelay < 70 )
{
return ucDelayOver59Levels28to65[1][ucPlayerLevel-28];
}
else if( uc2HWeaponDelay < 85 )
{
return ucDelayOver59Levels28to65[2][ucPlayerLevel-28];
}
else if( uc2HWeaponDelay < 95 )
{
return ucDelayOver59Levels28to65[3][ucPlayerLevel-28];
}
else if( uc2HWeaponDelay < 150 )
{
return ucDelayOver59Levels28to65[4][ucPlayerLevel-28];
}
else
{
return ucDelayOver59Levels28to65[5][ucPlayerLevel-28];
}
}
}



I am sure that it will return accurate damage bonuses for levels 28-65. However, I believe there are a couple slight inaccuracies in the level range 66-80. Janusd and Romai provided me with the raw data for this level range, and Romai is currently working to verify these values.

Please don't let that stop you from including this code in the next build. If there are any damage bonus inaccuracies for levels 66-80, they are slight. That means that this function is FAR better than what we currently have!

If Romai informs me that there are inaccuracies, I'll correct the tables, and post to let you know to include the updates.


Again, I hope that our contributions are welcomed, and that they benefit the project. Also, I do love coding, so I'd be happy to offer my services on other projects, if needed!

Have a great day, everyone!

-Eric (Cantus)

ChaosSlayer
09-22-2008, 12:04 AM
It pleases me that as of late so many people have taken interest into submitign new code. =)

Just one minor note - does your code can handle things for levels BEYOND 80?
Some of runing servers with max level of 100+ or even higher - would be nice if these things would keep scaling up and don't just become broken at 70 or 80 =)

trevius
09-22-2008, 12:04 AM
Looks interesting hehe. I am getting:

Error: Calculate2HDamageBonus was not declared in this scope

when trying to compile it. Not sure where that needs to be declared exactly, but probably just missing something small somewhere.

Seems like many people are working on making the 2H damage bonus better lately for some reason lol. Here are some other examples:

http://www.eqemulator.net/forums/showthread.php?t=26200

http://www.eqemulator.net/forums/showthread.php?t=26127

And some other combat related ones:

http://www.eqemulator.net/forums/showthread.php?t=26099

http://www.eqemulator.net/forums/showthread.php?t=26213

I wonder why the sudden flood of code for this lol. Not complaining, but it is a little weird to see 3 different posts with fixes for 2H bonuses in the past week or 2.

renoofturks1
09-22-2008, 12:28 AM
One of them was my attempt at it. It wasn't working as accurately as I had hoped and Cantus offered to take a look at it. After much discussion on our guild forum, this is where we ended up. It came out very well in the end. My biggest concern was time to parse that much code. The look up table's actually ended up parsing far faster than some of the formula's I was attempting to use. It is a very good solution to our problem.

trevius
09-22-2008, 12:42 AM
So, where does Calculate2HDamageBonus function go? I can't figure out where to put it and it errors as shown in my previous post if I put it directly in my attack.cpp.

Cantus
09-22-2008, 08:43 AM
ChaosSlayer, you're right... it is great to see people getting more involved with the project! I'm glad that our contributions seem to be well received by the established community and senior developers. I hope that I'll have more opportunities to help in the future!



Trevius, I did see that others were working on this particular problem, as well. I did work along with several of them (such as Reno) to develop what we believe to be the final solution to the problem, as posted above. Unlike the other threads, we're confident saying "it's done; it's fixed" with the code in this thread.

Please do review and test, and if you agree, please feel free to throw it into the next build!


So, where does Calculate2HDamageBonus function go? I can't figure out where to put it and it errors as shown in my previous post if I put it directly in my attack.cpp.

Trevius, you have several options.

The GetWeaponDamageBonus() is a member of the Mob class, so it's trying to call a Calculate2HDamageBonus() that's also a member of that class. There is none, as Calculate2HDamageBonus() is declared as a global function.

The quick fix is to, when Calculate2HDamageBonus() is called, add two colons (the C++ Scope Resolution Operator) before its name to indicate that it's a function that resides in the global namespace, like this:

return ::Calculate2HDamageBonus( GetLevel(), Weapon->Delay );


A better long-term choice would probably be to make the Calculate2HDamageBonus() a member of the Mob class. As this is my first contribution and I'm therefore a very junior contributor, I didn't want to presume to add a new function to someone else's class. But if you want to do it, here's a rough description of how:

Sorry that I can't give precise line numbers, as I'm at work, and do not have a copy of the Emu source available. Find the declaration of the Mob class, and add a prototype for the Calculate2HDamageBonus() function. Then add "Mob" and the scope resolution operator before the definition of the function.


A third possibility is: You could also rip out the guts of the function and plug it directly into GetWeaponDamageBonus(), but that'll make the GetWeaponDamageBonus() function huge and ugly. I'd recommend keeping Calculate2HDamageBonus() as a function.


I'll be happy to post more detail on these changes when I get home from work tonight!

Take care,

-Eric

Cantus
09-22-2008, 08:53 AM
Just one minor note - does your code can handle things for levels BEYOND 80?
Some of runing servers with max level of 100+ or even higher - would be nice if these things would keep scaling up and don't just become broken at 70 or 80 =)

Hi, ChaosSlayer!

I apologize. I've played only on The Grand Creation (level cap 65), so I didn't have an idea of how many other servers there were out there with a higher level cap.

Currently, in most cases, this function stops increasing damage bonuses for 2H weapons beyond their value at level 80. That is not ideal for servers with a level cap of 100+. However, it is still FAR better than the way things are currently, so I would recommend not waiting to get these changes added to the next build.

That does not prevent us from adding in support for toons level 81+ in the future! I will work on that for you.

Just so you know: in this function, I, and the folks that helped me, focused on returning damage bonuses that were exactly what they should be on Live. I can confidently say that it does this for levels 28-65, and that it's very close for 66-80 (it will be exact once Romai finishes verifying the data he gave me).

Because Live only goes as high as 80, I only know, for sure, what damage bonuses are up to that level.

Sony has proven to us that they often muck around with the formula used to calculate damage bonuses each time they release a new expansion. So there is no guarantee that future expansions will continue to ramp up 2H damage bonuses at the same rate as bonuses ramped up from level 75 to 80.

I will add in support for toons levels 81+, but it'll just be a "best guess" at what the damage bonuses really will be once Sony increases the level cap on Live. When they do increase the level cap, we may have to adjust the formula to better track the real servers.

Does that sound acceptable? If so, I'll work on it.

renoofturks1
09-22-2008, 10:25 AM
Just one minor note - does your code can handle things for levels BEYOND 80?
Some of running servers with max level of 100+ or even higher - would be nice if these things would keep scaling up and don't just become broken at 70 or 80 =)

Chaos, while this is a great idea. We tried to stay within the realm of live at the moment. In the future thing's may change, but as of right now, we have no idea whatsoever what those damage bonus's should be. It would be a waste of time to scale them up from 80-100 to only have to go back to it and change it when Sony raises the cap. We built it from available information.

On a side note, my previous formula, posted in this forum, should provide what you seek. It will scale up with level beyond level 80. It should be within 1 or 2 damage from levels 50 on up to 100ish, assuming Sony follows a set scale in the future. Under level 50 you will find it becomes less and less accurate as level decreases. We decided on this method as it was 100% accurate whereas every formula I was able to devise was prone to error's due to rounding and other factors. So, you do have another option if you so wish. (Please note, this is NOT tested beyond level 80 so I have no idea how it will turn out, but it should scale well from 50 on up. It isn't 100% but it is close.)

ChaosSlayer
09-22-2008, 10:50 AM
thank you for replying renoofturks1!

I just want to point out that you should not realy try to keep up with live, since we simply do not have any access to future/curent content anyway , specialy since Aniversary edition will not be supported by eqemu due to its unavalability on market.

So a simple scaling feature will work fine.

Also, could I request that you also include a RULE to TURN OFF damage bonus complitly? A kind of fail safe if it starts behave badly past lev 80, or 100 or 150 or whatever. At least this way weapons gona stay vanila.

thank you =)

PS: As i staed before damage bonus was a complitly useless feature to begin with, the soe should have simply upped the ratios on all 2handers compared to 1handers and be done with it, and this is the kind of balancing I am using on my server

Cantus
09-22-2008, 01:28 PM
I just want to point out that you should not realy try to keep up with live, since we simply do not have any access to future/curent content anyway , specialy since Aniversary edition will not be supported by eqemu due to its unavalability on market.

Hi, Chaos!

What Reno's saying, I believe, is that if future expansions on Live increase the level cap, we can then figure out exactly what 2H weapon damage bonuses will be on Live for higher levels.

That has nothing to do with upgrading the server and requiring EQEmu players to purchase a newer client. Rather, it has to do with figuring out whether Sony will change the formulas they use to calculate damage bonuses for levels 81+, as they've done in the past.


So a simple scaling feature will work fine.

Also, could I request that you also include a RULE to TURN OFF damage bonus complitly? A kind of fail safe if it starts behave badly past lev 80, or 100 or 150 or whatever. At least this way weapons gona stay vanila.

I get what you're saying, but there's no need for a flag to turn off damage bonuses.

Servers with a level cap of 81+ will not break the damage bonus calculation function that I posted above. For most weapons, the damage bonus will simply stop increasing beyond its value at 80.

Reno and I will work together on including a formula that takes a "best guess" as to what damage bonuses for levels 81+ may look like. When Sony eventually increases the level cap, we may find that our guess was off. But that's not a problem! When that happens, we'll simply come back and modify the code.

The formula for 81+ will assume that damage bonuses will keep increasing in line with how they increased from level 70 - 80. I think that should be a good, fair solution for servers with a high level cap! Agreed?

By the way, I agree with your points about Damage Bonus. But not much we can do about it now ;-).

Have a good one,

-Eric (Cantus)

cavedude
09-22-2008, 01:31 PM
This is without a doubt going into PEQ when it comes back up, along with a slew of other things!

renoofturks1
09-22-2008, 02:03 PM
thank you for replying renoofturks1!

I just want to point out that you should not realy try to keep up with live, since we simply do not have any access to future/curent content anyway , specialy since Aniversary edition will not be supported by eqemu due to its unavalability on market.

So a simple scaling feature will work fine.

Also, could I request that you also include a RULE to TURN OFF damage bonus complitly? A kind of fail safe if it starts behave badly past lev 80, or 100 or 150 or whatever. At least this way weapons gona stay vanila.

thank you =)

PS: As i staed before damage bonus was a complitly useless feature to begin with, the soe should have simply upped the ratios on all 2handers compared to 1handers and be done with it, and this is the kind of balancing I am using on my server

Sorry, the keep to live thing is a side effect of working on PEQ a few years :)

As for the proper scaling, I have never had a toon over 65 on EQEmu, Does the weapon show the damage bonus as continuing to scale beyond level 80? If so, all the information we would need to extend it into the higher levels is right in front of us.

As for my previous formula, it won't break anything beyond level 80, it is only in-accurate under level 50. I can't say if it is scaling improperly above level 80 because I have no idea what properly scaling would be :D

ChaosSlayer
09-22-2008, 03:34 PM
Cantus, renoofturks1 - Thank you guys for replying =)

My only concern to avoid an issue when weapons till 80 would have a dmg bonus and past 80 it sudenly becomes zero (or starts going backwords or become negative or whatever +) resulting in pre 80 players doing more dps than past 80 with same weapons.

If dmg bonus gona scale up using same formula or even stay the same - all fine by me , as long as it does not become chaotic from level to level =)

Thanks again!

renoofturks1
09-22-2008, 04:19 PM
If dmg bonus gona scale up using same formula or even stay the same - all fine by me , as long as it does not become chaotic from level to level =)


This system, using the look-up table's, attains exactly that, post 80, it just stops going up, it doesn't decrease, it merely stays constant from 80+

ChaosSlayer
09-22-2008, 04:36 PM
This system, using the look-up table's, attains exactly that, post 80, it just stops going up, it doesn't decrease, it merely stays constant from 80+

good =)

The only thing I could ask if you would add Rule: Damage Bonus on/off - so we can have an option to play with DPS only governed by natural weapon stats.

Thanks =)

Cantus
09-22-2008, 11:48 PM
Discussion: http://eqemulator.net/forums/showthread.php?t=26262

Thanks again for all of your feedback! I've made several of the changes that were suggested, like adding in a simple preprocessor directive that lets a server administrator build a copy of the Emulator that never applies damage bonuses to weapons.

I have not yet added calculations for continuing to ramp up 2H damage bonuses for levels 81+, but we'll get that added soon!


To make compilation easier and also to speed things up a tiny bit, I've changed the way this function is deployed.


** Please disregard the first post in this thread, and instead follow the following steps to compile and test the proposed fixes **


1) Open Mob.h, and scroll down to line #646. It should look like the following:

int GetWeaponDamageBonus(const Item_Struct* Weapon);


2) Please change the first word "int" to "int8", as follows:

int8 GetWeaponDamageBonus(const Item_Struct* Weapon);


3) Open Attack.cpp, and scroll down to lines #971 - 976. They should look like:

//if mainhand only, get the bonus damage from level
if(Hand==13){
int damage_bonus = GetWeaponDamageBonus(weapon ? weapon->GetItem() : (const Item_Struct*)NULL);
min_hit += damage_bonus;
max_hit += damage_bonus;
}


4) Completely replace those lines with the following:

// ************************************************** *************
// *** Calculate the damage bonus, if applicable, for this hit ***
// ************************************************** *************

#ifndef EQEMU_NO_WEAPON_DAMAGE_BONUS

// If you include the preprocessor directive "#define EQEMU_NO_WEAPON_DAMAGE_BONUS", that indicates that you do not
// want damage bonuses added to weapon damage at all. This feature was requested by ChaosSlayer on the EQEmu Forums.
//
// This is not recommended for normal usage, as the damage bonus represents a non-trivial component of the DPS output
// of weapons wielded by higher-level melee characters (especially for two-handed weapons).

if( Hand == 13 && GetLevel() >= 28 && IsWarriorClass() )
{
// Damage bonuses apply only to hits from the main hand (Hand == 13) by characters level 28 and above
// who belong to a melee class. If we're here, then all of these conditions apply.

int8 ucDamageBonus = GetWeaponDamageBonus( weapon ? weapon->GetItem() : (const Item_Struct*) NULL );

min_hit += (int) ucDamageBonus;
max_hit += (int) ucDamageBonus;
}
#endif


5) Scroll further down in Attack.cpp to around line #1952 (assuming you added the code in step 4), and look for the function that starts with the line:

int Mob::GetWeaponDamageBonus(const Item_Struct* Weapon)


6) Replace that entire function (it should take up 36 lines, ranging from about line #1952 down to #1988 ) with the following (much larger!) function:

int8 Mob::GetWeaponDamageBonus( const Item_Struct *Weapon )
{
// This function calculates and returns the damage bonus for the weapon identified by the parameter "Weapon".
// Modified 9/21/2008 by Cantus


// Assert: This function should only be called for hits by the mainhand, as damage bonuses apply only to the
// weapon in the primary slot. Be sure to check that Hand == 13 before calling.

// Assert: The caller should ensure that Weapon is actually a weapon before calling this function.
// The ItemInst::IsWeapon() method can be used to quickly determine this.

// Assert: This function should not be called if the player's level is below 28, as damage bonuses do not begin
// to apply until level 28.

// Assert: This function should not be called unless the player is a melee class, as casters do not receive a damage bonus.


if( Weapon == NULL || Weapon->ItemType == ItemType1HS || Weapon->ItemType == ItemType1HB || Weapon->ItemType == ItemTypeHand2Hand || Weapon->ItemType == ItemTypePierce )
{
// The weapon in the player's main (primary) hand is a one-handed weapon, or there is no item equipped at all.
//
// According to player posts on Allakhazam, 1H damage bonuses apply to bare fists (nothing equipped in the mainhand,
// as indicated by Weapon == NULL).
//
// The following formula returns the correct damage bonus for all 1H weapons:

return (int8) ((GetLevel() - 25) / 3);
}

// If we've gotten to this point, the weapon in the mainhand is a two-handed weapon.
// Calculating damage bonuses for 2H weapons is more complicated, as it's based on PC level AND the delay of the weapon.
// The formula to calculate 2H bonuses is HIDEOUS. It's a huge conglomeration of ternary operators and multiple operations.
//
// The following is a hybrid approach. In cases where the Level and Delay merit a formula that does not use many operators,
// the formula is used. In other cases, lookup tables are used for speed.
// Though the following code may look bloated and ridiculous, it's actually a very efficient way of calculating these bonuses.

// Player Level is used several times in the code below, so save it into a variable.
// If GetLevel() were an ordinary function, this would DEFINITELY make sense, as it'd cut back on all of the function calling
// overhead involved with multiple calls to GetLevel(). But in this case, GetLevel() is a simple, inline accessor method.
// So it probably doesn't matter. If anyone knows for certain that there is no overhead involved with calling GetLevel(),
// as I suspect, then please feel free to delete the following line, and replace all occurences of "ucPlayerLevel" with "GetLevel()".
int8 ucPlayerLevel = (int8) GetLevel();


// The following may look cleaner, and would certainly be easier to understand, if it was
// a simple 53x150 cell matrix.
//
// However, that would occupy 7,950 Bytes of memory (7.76 KB), and would likely result
// in "thrashing the cache" when performing lookups.
//
// Initially, I thought the best approach would be to reverse-engineer the formula used by
// Sony/Verant to calculate these 2H weapon damage bonuses. But the more than Reno and I
// worked on figuring out this formula, the more we're concluded that the formula itself ugly
// (that is, it contains so many operations and conditionals that it's fairly CPU intensive).
// Because of that, we're decided that, in most cases, a lookup table is the most efficient way
// to calculate these damage bonuses.
//
// The code below is a hybrid between a pure formulaic approach and a pure, brute-force
// lookup table. In cases where a formula is the best bet, I use a formula. In other places
// where a formula would be ugly, I use a lookup table in the interests of speed.


if( Weapon->Delay <= 27 )
{
// Damage Bonuses for all 2H weapons with delays of 27 or less are identical.
// They are the same as the damage bonus would be for a corresponding 1H weapon, plus one.
// This formula applies to all levels 28-80, and will probably continue to apply if
// the level cap on Live ever is increased beyond 80.

return (ucPlayerLevel - 22) / 3;
}


if( ucPlayerLevel == 65 && Weapon->Delay <= 59 )
{
// Consider these two facts:
// * Level 65 is the maximum level on many EQ Emu servers.
// * If you listed the levels of all characters logged on to a server, odds are that the number you'll
// see most frequently is level 65. That is, there are more level 65 toons than any other single level.
//
// Therefore, if we can optimize this function for level 65 toons, we're speeding up the server!
//
// With that goal in mind, I create an array of Damage Bonuses for level 65 characters wielding 2H weapons with
// delays between 28 and 59 (inclusive). I suspect that this one small lookup array will therefore handle
// many of the calls to this function.

static const int8 ucLevel65DamageBonusesForDelays28to59[] = {35, 35, 36, 36, 37, 37, 38, 38, 39, 39, 40, 40, 42, 42, 42, 45, 45, 47, 48, 49, 49, 51, 51, 52, 53, 54, 54, 56, 56, 57, 58, 59};

return ucLevel65DamageBonusesForDelays28to59[Weapon->Delay-28];
}


if( ucPlayerLevel > 65 )
{
if( ucPlayerLevel > 80 )
{
// As level 80 is currently the highest achievable level on Live, we only include
// damage bonus information up to this level.
//
// If there is a custom EQEmu server that allows players to level beyond 80, the
// damage bonus for their 2H weapons will simply not increase beyond their damage
// bonus at level 80.

ucPlayerLevel = 80;
}

// Lucy does not list a chart of damage bonuses for players levels 66+,
// so my original version of this function just applied the level 65 damage
// bonus for level 66+ toons. That sucked for higher level toons, as their
// 2H weapons stopped ramping up in DPS as they leveled past 65.
//
// Thanks to the efforts of two guys, this is no longer the case:
//
// Janusd (Zetrakyl) ran a nifty query against the PEQ item database to list
// the name of an example 2H weapon that represents each possible unique 2H delay.
//
// Romai then wrote an excellent script to automatically look up each of those
// weapons, open the Lucy item page associated with it, and iterate through all
// levels in the range 66 - 80. He saved the damage bonus for that weapon for
// each level, and that forms the basis of the lookup tables below.

if( Weapon->Delay <= 59 )
{
static const int8 ucDelay28to59Levels66to80[32][15]=
{
/* Level: */
/* 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80 */

{36, 37, 38, 39, 41, 42, 43, 44, 45, 47, 49, 49, 49, 50, 53}, /* Delay = 28 */
{36, 38, 38, 39, 42, 43, 43, 45, 46, 48, 49, 50, 51, 52, 54}, /* Delay = 29 */
{37, 38, 39, 40, 43, 43, 44, 46, 47, 48, 50, 51, 52, 53, 55}, /* Delay = 30 */
{37, 39, 40, 40, 43, 44, 45, 46, 47, 49, 51, 52, 52, 52, 54}, /* Delay = 31 */
{38, 39, 40, 41, 44, 45, 45, 47, 48, 48, 50, 52, 53, 55, 57}, /* Delay = 32 */
{38, 40, 41, 41, 44, 45, 46, 48, 49, 50, 52, 53, 54, 56, 58}, /* Delay = 33 */
{39, 40, 41, 42, 45, 46, 47, 48, 49, 51, 53, 54, 55, 57, 58}, /* Delay = 34 */
{39, 41, 42, 43, 46, 46, 47, 49, 50, 52, 54, 55, 56, 57, 59}, /* Delay = 35 */
{40, 41, 42, 43, 46, 47, 48, 50, 51, 53, 55, 55, 56, 58, 60}, /* Delay = 36 */
{40, 42, 43, 44, 47, 48, 49, 50, 51, 53, 55, 56, 57, 59, 61}, /* Delay = 37 */
{41, 42, 43, 44, 47, 48, 49, 51, 52, 54, 56, 57, 58, 60, 62}, /* Delay = 38 */
{41, 43, 44, 45, 48, 49, 50, 52, 53, 55, 57, 58, 59, 61, 63}, /* Delay = 39 */
{43, 45, 46, 47, 50, 51, 52, 54, 55, 57, 59, 60, 61, 63, 65}, /* Delay = 40 */
{43, 45, 46, 47, 50, 51, 52, 54, 55, 57, 59, 60, 61, 63, 65}, /* Delay = 41 */
{44, 46, 47, 48, 51, 52, 53, 55, 56, 58, 60, 61, 62, 64, 66}, /* Delay = 42 */
{46, 48, 49, 50, 53, 54, 55, 58, 59, 61, 63, 64, 65, 67, 69}, /* Delay = 43 */
{47, 49, 50, 51, 54, 55, 56, 58, 59, 61, 64, 65, 66, 68, 70}, /* Delay = 44 */
{48, 50, 51, 52, 56, 57, 58, 60, 61, 63, 65, 66, 68, 70, 72}, /* Delay = 45 */
{50, 52, 53, 54, 57, 58, 59, 62, 63, 65, 67, 68, 69, 71, 74}, /* Delay = 46 */
{50, 52, 53, 55, 58, 59, 60, 62, 63, 66, 68, 69, 70, 72, 74}, /* Delay = 47 */
{51, 53, 54, 55, 58, 60, 61, 63, 64, 66, 69, 69, 71, 73, 75}, /* Delay = 48 */
{52, 54, 55, 57, 60, 61, 62, 65, 66, 68, 70, 71, 73, 75, 77}, /* Delay = 49 */
{53, 55, 56, 57, 61, 62, 63, 65, 67, 69, 71, 72, 74, 76, 78}, /* Delay = 50 */
{53, 55, 57, 58, 61, 62, 64, 66, 67, 69, 72, 73, 74, 77, 79}, /* Delay = 51 */
{55, 57, 58, 59, 63, 64, 65, 68, 69, 71, 74, 75, 76, 78, 81}, /* Delay = 52 */
{57, 55, 59, 60, 63, 65, 66, 68, 70, 72, 74, 76, 77, 79, 82}, /* Delay = 53 */
{56, 58, 59, 61, 64, 65, 67, 69, 70, 73, 75, 76, 78, 80, 82}, /* Delay = 54 */
{57, 59, 61, 62, 66, 67, 68, 71, 72, 74, 77, 78, 80, 82, 84}, /* Delay = 55 */
{58, 60, 61, 63, 66, 68, 69, 71, 73, 75, 78, 79, 80, 83, 85}, /* Delay = 56 */

/* Important Note: Janusd's search for 2H weapons did not find */
/* any 2H weapon with a delay of 57. Therefore the values below */
/* are interpolated, not exact! */
{59, 61, 62, 64, 67, 69, 70, 72, 74, 76, 77, 78, 81, 84, 86}, /* Delay = 57 INTERPOLATED */

{60, 62, 63, 65, 68, 70, 71, 74, 75, 78, 80, 81, 83, 85, 88}, /* Delay = 58 */

/* Important Note: Janusd's search for 2H weapons did not find */
/* any 2H weapon with a delay of 59. Therefore the values below */
/* are interpolated, not exact! */
{60, 62, 64, 65, 69, 70, 72, 74, 76, 78, 81, 82, 84, 86, 89}, /* Delay = 59 INTERPOLATED */
};

return ucDelay28to59Levels66to80[Weapon->Delay-28][ucPlayerLevel-66];
}
else
{
// Delay is 60+

const static int8 ucDelayOver59Levels66to80[6][15] =
{
/* Level: */
/* 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80 */

{61, 63, 65, 66, 70, 71, 73, 75, 77, 79, 82, 83, 85, 87, 90}, /* Delay = 60 */
{65, 68, 69, 71, 75, 76, 78, 80, 82, 85, 87, 89, 91, 93, 96}, /* Delay = 65 */

/* Important Note: Currently, the only 2H weapon with a delay */
/* of 66 is not player equippable (it's None/None). So I'm */
/* leaving it commented out to keep this table smaller. */
//{66, 68, 70, 71, 75, 77, 78, 81, 83, 85, 88, 90, 91, 94, 97}, /* Delay = 66 */

{70, 72, 74, 76, 80, 81, 83, 86, 88, 88, 90, 95, 97, 99, 102}, /* Delay = 70 */
{82, 85, 87, 89, 89, 94, 98, 101, 103, 106, 109, 111, 114, 117, 120}, /* Delay = 85 */
{90, 93, 96, 98, 103, 105, 107, 111, 113, 116, 120, 122, 125, 128, 131}, /* Delay = 95 */

/* Important Note: Currently, the only 2H weapons with delay */
/* 100 are GM-only items purchased from vendors in Sunset Home */
/* (cshome). Because they are highly unlikely to be used in */
/* combat, I'm commenting it out to keep the table smaller. */
//{95, 98, 101, 103, 108, 110, 113, 116, 119, 122, 126, 128, 131, 134, 138},/* Delay = 100 */

{136, 140, 144, 148, 154, 157, 161, 166, 170, 174, 179, 183, 187, 191, 196} /* Delay = 150 */
};

if( Weapon->Delay < 65 )
{
return ucDelayOver59Levels66to80[0][ucPlayerLevel-66];
}
else if( Weapon->Delay < 70 )
{
return ucDelayOver59Levels66to80[1][ucPlayerLevel-66];
}
else if( Weapon->Delay < 85 )
{
return ucDelayOver59Levels66to80[2][ucPlayerLevel-66];
}
else if( Weapon->Delay < 95 )
{
return ucDelayOver59Levels66to80[3][ucPlayerLevel-66];
}
else if( Weapon->Delay < 150 )
{
return ucDelayOver59Levels66to80[4][ucPlayerLevel-66];
}
else
{
return ucDelayOver59Levels66to80[5][ucPlayerLevel-66];
}
}
}


// If we've gotten to this point in the function without hitting a return statement,
// we know that the character's level is between 28 and 65, and that the 2H weapon's
// delay is 28 or higher.

// The Damage Bonus values returned by this function (in the level 28-65 range) are
// based on a table of 2H Weapon Damage Bonuses provided by Lucy at the following address:
// http://lucy.allakhazam.com/dmgbonus.html

if( Weapon->Delay <= 39 )
{
if( ucPlayerLevel <= 53)
{
// The Damage Bonus for all 2H weapons with delays between 28 and 39 (inclusive) is the same for players level 53 and below...
static const int8 ucDelay28to39LevelUnder54[] = {1, 1, 2, 3, 3, 3, 4, 5, 5, 6, 6, 6, 8, 8, 8, 9, 9, 10, 11, 11, 11, 12, 13, 14, 16, 17};

// As a note: The following formula accurately calculates damage bonuses for 2H weapons with delays in the range 28-39 (inclusive)
// for characters levels 28-50 (inclusive):
// return ( (ucPlayerLevel - 22) / 3 ) + ( (ucPlayerLevel - 25) / 5 );
//
// However, the small lookup array used above is actually much faster. So we'll just use it instead of the formula
//
// (Thanks to Reno for helping figure out the above formula!)

return ucDelay28to39LevelUnder54[ucPlayerLevel-28];
}
else
{
// Use a matrix to look up the damage bonus for 2H weapons with delays between 28 and 39 wielded by characters level 54 and above.
static const int8 ucDelay28to39Level54to64[12][11] =
{
/* Level: */
/* 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64 */

{17, 21, 21, 23, 25, 26, 28, 30, 31, 31, 33}, /* Delay = 28 */
{17, 21, 22, 23, 25, 26, 29, 30, 31, 32, 34}, /* Delay = 29 */
{18, 21, 22, 23, 25, 27, 29, 31, 32, 32, 34}, /* Delay = 30 */
{18, 21, 22, 23, 25, 27, 29, 31, 32, 33, 34}, /* Delay = 31 */
{18, 21, 22, 24, 26, 27, 30, 32, 32, 33, 35}, /* Delay = 32 */
{18, 21, 22, 24, 26, 27, 30, 32, 33, 34, 35}, /* Delay = 33 */
{18, 22, 22, 24, 26, 28, 30, 32, 33, 34, 36}, /* Delay = 34 */
{18, 22, 23, 24, 26, 28, 31, 33, 34, 34, 36}, /* Delay = 35 */
{18, 22, 23, 25, 27, 28, 31, 33, 34, 35, 37}, /* Delay = 36 */
{18, 22, 23, 25, 27, 29, 31, 33, 34, 35, 37}, /* Delay = 37 */
{18, 22, 23, 25, 27, 29, 32, 34, 35, 36, 38}, /* Delay = 38 */
{18, 22, 23, 25, 27, 29, 32, 34, 35, 36, 38} /* Delay = 39 */
};

return ucDelay28to39Level54to64[Weapon->Delay-28][ucPlayerLevel-54];
}
}
else if( Weapon->Delay <= 59 )
{
if( ucPlayerLevel <= 52 )
{
if( Weapon->Delay <= 45 )
{
static const int8 ucDelay40to45Levels28to52[6][25] =
{
/* Level: */
/* 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52 */

{2, 2, 3, 4, 4, 4, 5, 6, 6, 7, 7, 7, 9, 9, 9, 10, 10, 11, 12, 12, 12, 13, 14, 16, 18}, /* Delay = 40 */
{2, 2, 3, 4, 4, 4, 5, 6, 6, 7, 7, 7, 9, 9, 9, 10, 10, 11, 12, 12, 12, 13, 14, 16, 18}, /* Delay = 41 */
{2, 2, 3, 4, 4, 4, 5, 6, 6, 7, 7, 7, 9, 9, 9, 10, 10, 11, 12, 12, 12, 13, 14, 16, 18}, /* Delay = 42 */
{4, 4, 5, 6, 6, 6, 7, 8, 8, 9, 9, 9, 11, 11, 11, 12, 12, 13, 14, 14, 14, 15, 16, 18, 20}, /* Delay = 43 */
{4, 4, 5, 6, 6, 6, 7, 8, 8, 9, 9, 9, 11, 11, 11, 12, 12, 13, 14, 14, 14, 15, 16, 18, 20}, /* Delay = 44 */
{5, 5, 6, 7, 7, 7, 8, 9, 9, 10, 10, 10, 12, 12, 12, 13, 13, 14, 15, 15, 15, 16, 17, 19, 21} /* Delay = 45 */
};

return ucDelay40to45Levels28to52[Weapon->Delay-40][ucPlayerLevel-28];
}
else
{
static const int8 ucDelay46Levels28to52[] = {6, 6, 7, 8, 8, 8, 9, 10, 10, 11, 11, 11, 13, 13, 13, 14, 14, 15, 16, 16, 16, 17, 18, 20, 22};

return ucDelay46Levels28to52[ucPlayerLevel-28] + ((Weapon->Delay-46) / 3);
}
}
else
{
// Player is in the level range 53 - 64

// Calculating damage bonus for 2H weapons with a delay between 40 and 59 (inclusive) involves, unforunately, a brute-force matrix lookup.
static const int8 ucDelay40to59Levels53to64[20][37] =
{
/* Level: */
/* 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64 */

{19, 20, 24, 25, 27, 29, 31, 34, 36, 37, 38, 40}, /* Delay = 40 */
{19, 20, 24, 25, 27, 29, 31, 34, 36, 37, 38, 40}, /* Delay = 41 */
{19, 20, 24, 25, 27, 29, 31, 34, 36, 37, 38, 40}, /* Delay = 42 */
{21, 22, 26, 27, 29, 31, 33, 37, 39, 40, 41, 43}, /* Delay = 43 */
{21, 22, 26, 27, 29, 32, 34, 37, 39, 40, 41, 43}, /* Delay = 44 */
{22, 23, 27, 28, 31, 33, 35, 38, 40, 42, 43, 45}, /* Delay = 45 */
{23, 24, 28, 30, 32, 34, 36, 40, 42, 43, 44, 46}, /* Delay = 46 */
{23, 24, 29, 30, 32, 34, 37, 40, 42, 43, 44, 47}, /* Delay = 47 */
{23, 24, 29, 30, 32, 35, 37, 40, 43, 44, 45, 47}, /* Delay = 48 */
{24, 25, 30, 31, 34, 36, 38, 42, 44, 45, 46, 49}, /* Delay = 49 */
{24, 26, 30, 31, 34, 36, 39, 42, 44, 46, 47, 49}, /* Delay = 50 */
{24, 26, 30, 31, 34, 36, 39, 42, 45, 46, 47, 49}, /* Delay = 51 */
{25, 27, 31, 33, 35, 38, 40, 44, 46, 47, 49, 51}, /* Delay = 52 */
{25, 27, 31, 33, 35, 38, 40, 44, 46, 48, 49, 51}, /* Delay = 53 */
{26, 27, 32, 33, 36, 38, 41, 44, 47, 48, 49, 52}, /* Delay = 54 */
{27, 28, 33, 34, 37, 39, 42, 46, 48, 50, 51, 53}, /* Delay = 55 */
{27, 28, 33, 34, 37, 40, 42, 46, 49, 50, 51, 54}, /* Delay = 56 */
{27, 28, 33, 34, 37, 40, 43, 46, 49, 50, 52, 54}, /* Delay = 57 */
{28, 29, 34, 36, 39, 41, 44, 48, 50, 52, 53, 56}, /* Delay = 58 */
{28, 29, 34, 36, 39, 41, 44, 48, 51, 52, 54, 56} /* Delay = 59 */
};

return ucDelay40to59Levels53to64[Weapon->Delay-40][ucPlayerLevel-53];
}
}
else
{
// The following table allows us to look up Damage Bonuses for weapons with delays greater than or equal to 60.
//
// There aren't a lot of 2H weapons with a delay greater than 60. In fact, both a database and Lucy search run by janusd confirm
// that the only unique 2H delays greater than 60 are: 65, 70, 85, 95, and 150.
//
// To be fair, there are also weapons with delays of 66 and 100. But they are either not equippable (None/None), or are
// only available to GMs from merchants in Sunset Home (cshome). In order to keep this table "lean and mean", I will not
// include the values for delays 66 and 100. If they ever are wielded, the 66 delay weapon will use the 65 delay bonuses,
// and the 100 delay weapon will use the 95 delay bonuses. So it's not a big deal.
//
// Still, if someone in the future decides that they do want to include them, here are the tables for these two delays:
//
// {12, 12, 13, 14, 14, 14, 15, 16, 16, 17, 17, 17, 19, 19, 19, 20, 20, 21, 22, 22, 22, 23, 24, 26, 29, 30, 32, 37, 39, 42, 45, 48, 53, 55, 57, 59, 61, 64} /* Delay = 66 */
// {24, 24, 25, 26, 26, 26, 27, 28, 28, 29, 29, 29, 31, 31, 31, 32, 32, 33, 34, 34, 34, 35, 36, 39, 43, 45, 48, 55, 57, 62, 66, 71, 77, 80, 83, 85, 89, 92} /* Delay = 100 */
//
// In case there are 2H weapons added in the future with delays other than those listed above (and until the damage bonuses
// associated with that new delay are added to this function), this function is designed to do the following:
//
// For weapons with delays in the range 60-64, use the Damage Bonus that would apply to a 2H weapon with delay 60.
// For weapons with delays in the range 65-69, use the Damage Bonus that would apply to a 2H weapon with delay 65
// For weapons with delays in the range 70-84, use the Damage Bonus that would apply to a 2H weapon with delay 70.
// For weapons with delays in the range 85-94, use the Damage Bonus that would apply to a 2H weapon with delay 85.
// For weapons with delays in the range 95-149, use the Damage Bonus that would apply to a 2H weapon with delay 95.
// For weapons with delays 150 or higher, use the Damage Bonus that would apply to a 2H weapon with delay 150.

static const int8 ucDelayOver59Levels28to65[6][38] =
{
/* Level: */
/* 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64. 65 */

{10, 10, 11, 12, 12, 12, 13, 14, 14, 15, 15, 15, 17, 17, 17, 18, 18, 19, 20, 20, 20, 21, 22, 24, 27, 28, 30, 35, 36, 39, 42, 45, 49, 51, 53, 54, 57, 59}, /* Delay = 60 */
{12, 12, 13, 14, 14, 14, 15, 16, 16, 17, 17, 17, 19, 19, 19, 20, 20, 21, 22, 22, 22, 23, 24, 26, 29, 30, 32, 37, 39, 42, 45, 48, 52, 55, 57, 58, 61, 63}, /* Delay = 65 */
{14, 14, 15, 16, 16, 16, 17, 18, 18, 19, 19, 19, 21, 21, 21, 22, 22, 23, 24, 24, 24, 25, 26, 28, 31, 33, 35, 40, 42, 45, 48, 52, 56, 59, 61, 62, 65, 68}, /* Delay = 70 */
{19, 19, 20, 21, 21, 21, 22, 23, 23, 24, 24, 24, 26, 26, 26, 27, 27, 28, 29, 29, 29, 30, 31, 34, 37, 39, 41, 47, 49, 54, 57, 61, 66, 69, 72, 74, 77, 80}, /* Delay = 85 */
{22, 22, 23, 24, 24, 24, 25, 26, 26, 27, 27, 27, 29, 29, 29, 30, 30, 31, 32, 32, 32, 33, 34, 37, 40, 43, 45, 52, 54, 59, 62, 67, 73, 76, 79, 81, 84, 88}, /* Delay = 95 */
{40, 40, 41, 42, 42, 42, 43, 44, 44, 45, 45, 45, 47, 47, 47, 48, 48, 49, 50, 50, 50, 51, 52, 56, 61, 65, 69, 78, 82, 89, 94, 102, 110, 115, 119, 122, 127, 132} /* Delay = 150 */
};

if( Weapon->Delay < 65 )
{
return ucDelayOver59Levels28to65[0][ucPlayerLevel-28];
}
else if( Weapon->Delay < 70 )
{
return ucDelayOver59Levels28to65[1][ucPlayerLevel-28];
}
else if( Weapon->Delay < 85 )
{
return ucDelayOver59Levels28to65[2][ucPlayerLevel-28];
}
else if( Weapon->Delay < 95 )
{
return ucDelayOver59Levels28to65[3][ucPlayerLevel-28];
}
else if( Weapon->Delay < 150 )
{
return ucDelayOver59Levels28to65[4][ucPlayerLevel-28];
}
else
{
return ucDelayOver59Levels28to65[5][ucPlayerLevel-28];
}
}
}


7) Compile, and let me know if it works! I'm sorry, but I wound up working late and never got home to actually test this. If there are any errors, please let me know, and I'll fix them ASAP!

AndMetal
09-23-2008, 01:49 AM
So, where does Calculate2HDamageBonus function go? I can't figure out where to put it and it errors as shown in my previous post if I put it directly in my attack.cpp.

You should be able to define it in zone/mob.h (http://eqemulator.cvs.sourceforge.net/eqemulator/EQEmuCVS/Source/zone/mob.h?view=markup), just like GetWeaponDamageBonus is. It's all a part of the Mob class (http://eqemulator.cvs.sourceforge.net/eqemulator/EQEmuCVS/Source/zone/mob.h?revision=1.65&view=markup#l_278), which is basically what the Mob:: prefix means.

renoofturks1
09-23-2008, 06:43 AM
It's all merged together in that new set he posted. No need to define a new function.

trevius
09-23-2008, 07:02 AM
if you wanted to add a rule in for damage bonuses, all you should have to do is add:

In attack.cpp
if(RuleB(Combat, UseDamageBonus)){

}

So that it encompasses your damage bonus calculations completely.

Then, just add it to the ruletypes.h file like this:

RULE_BOOL ( Combat, UseDamageBonus, true) //default is true to enable damage bonuses

into the combat section of the file.

Then just add the optional SQL if you want to disable it. But you don't even need the SQL if you want to leave it enabled, since the default is true:

Optional SQL:
Insert into rule_values values (0, 'Combat:UseDamageBonus', true);

trevius
09-23-2008, 08:30 AM
Oh and I got the latest submission in this thread compiled and tested on my server. It looks good to me! Definitely better than the current code in the emulator lol.

ChaosSlayer
09-23-2008, 10:45 AM
very good Trev! I hope it makes in with the Rule you described 8)

Cantus
09-23-2008, 11:35 AM
very good Trev! I hope it makes in with the Rule you described 8)

Heya, Chaos and Trev!

ChaosSlayer's suggestion of a flag to allow server administrators to turn damage bonuses on and off is already in the code I posted above. It is not implemented as a standard type of rule as Trevius suggests, but there's a reason for that. Please let me explain, but first, here's the code snippet that mentions the change:

#ifndef EQEMU_NO_WEAPON_DAMAGE_BONUS

// If you include the preprocessor directive "#define EQEMU_NO_WEAPON_DAMAGE_BONUS", that indicates that you do not
// want damage bonuses added to weapon damage at all. This feature was requested by ChaosSlayer on the EQEmu Forums.
//
// This is not recommended for normal usage, as the damage bonus represents a non-trivial component of the DPS output
// of weapons wielded by higher-level melee characters (especially for two-handed weapons).

All you'd have to do to disable Damage Bonuses, Chaos, is include the line ""#define EQEMU_NO_WEAPON_DAMAGE_BONUS" anywhere in a file that gets parsed before Attack.cpp (or even at the top of Attack.cpp), and damage bonuses will never apply on your server. If you want to enable them in the future, just remove that line and re-compile.


While I understand ChaosSlayer's points that it would be nice to try things out without damage bonuses, and rely entirely upon the native damage/delay of the weapon, the fact is that most servers are not going to choose to disable damage bonuses.

If we implemented the check for whether or not the server administrator wants damage bonuses at runtime, using the standard Rule method that Trevius suggests, that means that every time a melee toon lands a hit with a 2H weapon, there's the extra overhead of calling that Rule function.

I know, that doesn't seem like a big deal. But keep in mind that if someone Rampages with an Earthshaker, this code could potentially be called hundreds of times in a split second, for example. Or if someone pops a riposte discipline like Furious or Whirlwind with lots of mobs hitting them. Or if a lot of toons are raiding a mob, and they're landing many hits at once (double, triple, quad hits against the target). In all of these cases, this function will be called many times in rapid succession, so even a small difference -- like the function call overhead involved with checking a rule in the traditional way -- may become significant.

So, as this is one of the places where we want things to be as efficient as possible, I used a C/C++ preprocessor directive to allow folks like ChaosSlayer to turn on or off damage bonuses. It is not as slick as Trev's suggestion, as it must be done by adding a line of code and then re-compiling the server, vs. making a database change during runtime, but I still think it's in everyone's best interests -- as it does not slow down processing by involving needless function call overhead every time a 2H weapon hits.

I may be wrong... that happens a lot! So if you prefer, feel free to make it a rule, or just ask me and I'll do it for you!

Cantus
09-23-2008, 11:46 AM
Oh and I got the latest submission in this thread compiled and tested on my server. It looks good to me! Definitely better than the current code in the emulator lol.

That's great to hear, Trevius!

It may already be too late, as you've re-built your server using the new code. But if you still have an old build around on a test machine, it'd be neat to run a quick test to make sure things are working as expected.

What I'd recommend is either use your GM to twink a toon with a nice, slow 2H weapon like "A Weighted Axe (http://lucy.allakhazam.com/item.html?id=11543)", or just farm one in the Burning Woods. They drop commonly.

That's a perfect 2H weapon to test with, as its native ratio sucks, but due to its slow speed, it receives a massive damage bonus.

Fight a mob with it on a server compiled with the old code. Watch for the average and maximum hit (or just go all out and parse it).

Then re-build that server with the new damage bonus code. Attack a similar mob, and watch the damage output. It should be greatly increased if things are working well.

Derision
09-23-2008, 01:35 PM
doesn't that involve a call to MySQL to get the rule? Or does it store them somewhere else at start-up? So, every time someone make's a hit, it calls MySQL, checks the rule, then parses the DMGB code? Correct me if i am wrong?

I won't make any comment on whether you should use a rule in this particular case, but the rules are all setup when the zone loads, so there is no database access required once the zone is initialised.

Out of interest, I compiled zone with VC++ 2008 Express with optimise for speed selected, and used IDA to look at the code it generates when a boolean rule is checked. It is only 4 machine instructions:


mov edx, dword_A48964 ; Load Rule base pointer
add esp, 18h
cmp [edx+162h], bl ; Compare Rule offset 0x162 to 0 (register bl is set to zero earlier in the code)
jz short loc_5F4950 ; Jump if Rule set to false


which would probably take a few microseconds to execute, so there wouldn't be any performance hit if you chose to implement this using a rule.

renoofturks1
09-23-2008, 01:38 PM
That answer's my question. Thanks!

trevius
09-23-2008, 05:33 PM
Cantus, your way would work fine for people who compile their own source. But, many people use the win32 binaries if they are running a windows server.

If the rule did have any performance impact at all, I don't think it would be worth making 99% of all servers suffer so 1% could have a special rule for it :P

But if there is no performance impact, the rule would be best. If there is, then your way would be best, so there is still an option available if someone absolutely must disable damage bonus for any reason.

Again, I am personally against having a rule in place for this. I just don't see a need. Unless you are running a server with 100% custom created weapons, then your weapon balance will be off if you don't have damage bonuses enabled.

I only posted the rule, so it could be added if needed. It is a pretty simple option. Though, it would be nice if there was a way to store that weapon bonus calculation and use it over and over instead of having to calculate it with every swing. I know for sure on my server that even before this change, anyone rampaging more than like 20 mobs is risking a zone crash. I haven't had feedback on the new code yet to see if it still causes that, but not many people use 2H weapons on my server due to them sucking in the first place. I don't think it is only damage bonus that is messed up with 2H weapons. I think there seems to be some weird damage capping going on as well which makes them almost pointless.

renoofturks1
09-23-2008, 06:46 PM
All the testing I have done put's the 2 hander's where they should be now. If there are other issue's, bring them up and I'll see about getting them kinked out

Cantus
09-24-2008, 10:43 AM
Derision,

Excellent information there. I apologize that I don't have the code in front of me... are the rule-checking functions inline? In other words, does the ASM that you posted also take into account function call overhead, unwinding the stack, etc. that would result from invoking a non-inline function?

Either way, I think you're right: The performance hit for implementing a rule is trivial. Though your point that the rule is queried from the DB only when a zone first boots is important for folks like ChaosSlayer to remember. If you toggle Damage Bonuses on or off in the DB, you'll have to reboot the zone for it to take effect.



Trevius,

Excellent points, as always! I think the info that Derision provided shows that adding in a rule adds only a tiny amount of overhead to the code, so let's go that route. I'll make the addition of that rule when I also add the code to continue ramping up Damage Bonus for levels 81+. Hopefully should have that posted in a day or two (work's been killing me this week -- very little free time. Sorry.)

You make another excellent point here. I may be able to put your fears to rest:

it would be nice if there was a way to store that weapon bonus calculation and use it over and over instead of having to calculate it with every swing. I know for sure on my server that even before this change, anyone rampaging more than like 20 mobs is risking a zone crash. I haven't had feedback on the new code yet to see if it still causes that

That was a big concern for me, too. So I ran some tests. There's no problem at all. In fact, storing damage bonuses in an associative array along with character and/or weapon name would be MUCH slower and more complicated than just looking them up for each hit. At that point, you may as well go with a full-size, brute-force lookup table. Lookup tables are great and fast, but only if they're relatively small and cacheable.

Here is some data from a test I ran, as posted to our guild messageboard:

I was worried about whether this function would slow down the server when, for example, a Warrior Shakerpages (uses an Earthshaker, which is a 2H weapon, along with Rampage to simultaneously hit a large number of mobs around him/her). So I ran a test.

On a Pentium II 450 MHz with 256 MB of RAM (the worst computer I have available), it takes:

227 milliseconds (0.227 seconds) to call the function 12,231,000 times. That's twelve million times.

That means that 122,310 Warriors could Rampage on 100 mobs each at precisely the same moment and only delay a server running on fifteen year old hardware for less than a quarter of a second.

At its peak, there were 430,000 subscriptions to Live EQ. Note that not all of those people who had an account were logged on at once. And they were certainly not all logged onto the same server. So we're never going to have anywhere close to 122,310 people logged on to the same EQEmu server at the same time.

Not to mention that there aren't 12 million mobs available across all of the zones in EQ to Shakerpage. And there aren't enough Earthshakers to be distributed to those 122,310 warriors.

Long story short: The function is smokin' fast. If anyone tells us that they don't like the fact that it uses some lookup tables, well, they're kinda showing their ignorance ;-).

I can assure you that there is no problem with this function, and no added risk of server crash or latency. The Damage Bonus solution as posted in this thread is very stable and fast, and should have no noticible impact on a server (other than making players who like 2H weapons happy!)


One last point you made:

not many people use 2H weapons on my server due to them sucking in the first place. I don't think it is only damage bonus that is messed up with 2H weapons. I think there seems to be some weird damage capping going on as well which makes them almost pointless.

Damage Bonus really does make up a significant chunk of the DPS output of 2H weapons... especially slower 2H weaps wielded by high level toons.

Please do give it another shot after compiling a version of the server using the new code in this thread. I bet you'll be a lot more pleased with 2H weapons than before! In fact, troll the Allakhazam pages for some popular 2H weapons to get people's feedback on how hard these weapons hit. Then use a GM to twink a toon out with that same weapon at the same player level, and see if it hits for about the same on EQEmu.

I believe Reno already ran this test, and concluded that, with these fixes, 2H weapons perform just like they did on Live.

If that's not the case, well then, we'll look at the code more carefully, find the problem, and fix it! Sound good?

Derision
09-24-2008, 11:54 AM
Derision,

Excellent information there. I apologize that I don't have the code in front of me... are the rule-checking functions inline? In other words, does the ASM that you posted also take into account function call overhead, unwinding the stack, etc. that would result from invoking a non-inline function?

The rule checking functions are declared inline, however I discovered that if zone is built using the 'debug build' in VC++ 2008, with optimisations disabled, the function is not actually inlined and is compiled as a subroutine and accessed via a CALL. With Optimise for speed selected, the function is inlined and there is no subroutine call. I didn't check what GCC produces under Linux as I don't have any experience in dissasembly in that environment.


Though your point that the rule is queried from the DB only when a zone first boots is important for folks like ChaosSlayer to remember. If you toggle Damage Bonuses on or off in the DB, you'll have to reboot the zone for it to take effect.


I was simplifying things a bit, what I should have said was that there is no further DB access for rules, unless you issue the #rules reload command to force them to be re-read.

trevius
09-27-2008, 03:14 AM
I moved all of the off-topic discussion comments here:

http://www.eqemulator.net/forums/showthread.php?t=26348

Continue the discussion there instead of here, please.

Flare83
09-27-2008, 11:25 AM
i created a new level 1 paladin and started leveling today, and i'm hitting things for 250+. Did i merge something wrong or can someone else confirm this?

Flare83
09-27-2008, 12:15 PM
after more testing my paladin using a 1hs damage 4 delay 29 hit for 250+ until level 23 then started hitting for 10-20 (seems more normal) 2hs damage seemed fine tho.

Flare83
09-27-2008, 01:41 PM
i just readded the level check (think you forgot to put it back in after some editing) and eveything works fine again

if( GetLevel() < 28 || !IsWarriorClass() )
{
// Either the PC's level is less than 28 (damage bonuses do not begin to apply until level 28),
// or the PC is not a melee class (only melee classes receive a damage bonus).

return 0;
}

Cantus
09-27-2008, 10:05 PM
i just readded the level check (think you forgot to put it back in after some editing) and eveything works fine again

Hi, Flare!

Please check step #4 in the instructions on how to build these new Damage Bonus changes. The level check is still in there; I just moved it outside of the Damage Bonus function itself (after all, why bother with function calling overhead if you aren't high enough level to receive a damage bonus?)

Here are the relevant lines:

if( Hand == 13 && GetLevel() >= 28 && IsWarriorClass() )
{
// Damage bonuses apply only to hits from the main hand (Hand == 13) by characters level 28 and above
// who belong to a melee class. If we're here, then all of these conditions apply.

int8 ucDamageBonus = GetWeaponDamageBonus( weapon ? weapon->GetItem() : (const Item_Struct*) NULL );

min_hit += (int) ucDamageBonus;
max_hit += (int) ucDamageBonus;
}

Sounds like perhaps you overlooked this when building the changes. No worries!

Take care!