diff --git a/common/skills.cpp b/common/skills.cpp index a23d77a7b..6cb063e2e 100644 --- a/common/skills.cpp +++ b/common/skills.cpp @@ -124,6 +124,30 @@ bool EQEmu::skills::IsCastingSkill(SkillType skill) } } +int32 EQEmu::skills::GetBaseDamage(SkillType skill) +{ + switch (skill) { + case SkillBash: + return 2; + case SkillDragonPunch: + return 12; + case SkillEagleStrike: + return 7; + case SkillFlyingKick: + return 25; + case SkillKick: + return 3; + case SkillRoundKick: + return 5; + case SkillTigerClaw: + return 4; + case SkillFrenzy: + return 10; + default: + return 0; + } +} + const std::map& EQEmu::skills::GetSkillTypeMap() { /* VS2013 code diff --git a/common/skills.h b/common/skills.h index 19a996729..572b8c59f 100644 --- a/common/skills.h +++ b/common/skills.h @@ -166,6 +166,7 @@ namespace EQEmu float GetSkillMeleePushForce(SkillType skill); bool IsBardInstrumentSkill(SkillType skill); bool IsCastingSkill(SkillType skill); + int32 GetBaseDamage(SkillType skill); extern const std::map& GetSkillTypeMap(); diff --git a/zone/attack.cpp b/zone/attack.cpp index 47b7bcb66..219ee231e 100644 --- a/zone/attack.cpp +++ b/zone/attack.cpp @@ -189,6 +189,9 @@ bool Mob::CheckHitChance(Mob* other, EQEmu::skills::SkillType skillinuse, int ch Mob *defender = this; Log.Out(Logs::Detail, Logs::Attack, "CheckHitChance(%s) attacked by %s", defender->GetName(), attacker->GetName()); + if (defender->IsClient() && defender->CastToClient()->IsSitting()) + return true; + // calculate defender's avoidance auto avoidance = defender->compute_defense() + 10; // add 10 in case the NPC's stats are fucked auto evasion_bonus = defender->spellbonuses.AvoidMeleeChanceEffect; // we check this first since it has a special case @@ -276,7 +279,7 @@ bool Mob::CheckHitChance(Mob* other, EQEmu::skills::SkillType skillinuse, int ch return tohit_roll > avoid_roll; } -bool Mob::AvoidDamage(Mob *other, int32 &damage, int hand) +bool Mob::AvoidDamage(Mob *other, int &damage, int hand) { /* called when a mob is attacked, does the checks to see if it's a hit * and does other mitigation checks. 'this' is the mob being attacked. @@ -479,274 +482,403 @@ bool Mob::AvoidDamage(Mob *other, int32 &damage, int hand) return false; } -void Mob::MeleeMitigation(Mob *attacker, int32 &damage, int32 minhit, ExtraAttackOptions *opts) +int Mob::GetACSoftcap() { - if (damage <= 0) + // from test server Resources/ACMitigation.txt + static int war_softcaps[] = { + 312, 314, 316, 318, 320, 322, 324, 326, 328, 330, 332, 334, 336, 338, 340, 342, 344, 346, 348, 350, 352, + 354, 356, 358, 360, 362, 364, 366, 368, 370, 372, 374, 376, 378, 380, 382, 384, 386, 388, 390, 392, 394, + 396, 398, 400, 402, 404, 406, 408, 410, 412, 414, 416, 418, 420, 422, 424, 426, 428, 430, 432, 434, 436, + 438, 440, 442, 444, 446, 448, 450, 452, 454, 456, 458, 460, 462, 464, 466, 468, 470, 472, 474, 476, 478, + 480, 482, 484, 486, 488, 490, 492, 494, 496, 498, 500, 502, 504, 506, 508, 510, 512, 514, 516, 518, 520 + }; + + static int clrbrdmnk_softcaps[] = { + 274, 276, 278, 278, 280, 282, 284, 286, 288, 290, 292, 292, 294, 296, 298, 300, 302, 304, 306, 308, 308, + 310, 312, 314, 316, 318, 320, 322, 322, 324, 326, 328, 330, 332, 334, 336, 336, 338, 340, 342, 344, 346, + 348, 350, 352, 352, 354, 356, 358, 360, 362, 364, 366, 366, 368, 370, 372, 374, 376, 378, 380, 380, 382, + 384, 386, 388, 390, 392, 394, 396, 396, 398, 400, 402, 404, 406, 408, 410, 410, 412, 414, 416, 418, 420, + 422, 424, 424, 426, 428, 430, 432, 434, 436, 438, 440, 440, 442, 444, 446, 448, 450, 452, 454, 454, 456 + }; + + static int palshd_softcaps[] = { + 298, 300, 302, 304, 306, 308, 310, 312, 314, 316, 318, 320, 322, 324, 326, 328, 330, 332, 334, 336, 336, + 338, 340, 342, 344, 346, 348, 350, 352, 354, 356, 358, 360, 362, 364, 366, 368, 370, 372, 374, 376, 378, + 380, 382, 384, 384, 386, 388, 390, 392, 394, 396, 398, 400, 402, 404, 406, 408, 410, 412, 414, 416, 418, + 420, 422, 424, 426, 428, 430, 432, 432, 434, 436, 438, 440, 442, 444, 446, 448, 450, 452, 454, 456, 458, + 460, 462, 464, 466, 468, 470, 472, 474, 476, 478, 480, 480, 482, 484, 486, 488, 490, 492, 494, 496, 498 + }; + + static int rng_softcaps[] = { + 286, 288, 290, 292, 294, 296, 298, 298, 300, 302, 304, 306, 308, 310, 312, 314, 316, 318, 320, 322, 322, + 324, 326, 328, 330, 332, 334, 336, 338, 340, 342, 344, 344, 346, 348, 350, 352, 354, 356, 358, 360, 362, + 364, 366, 368, 368, 370, 372, 374, 376, 378, 380, 382, 384, 386, 388, 390, 390, 392, 394, 396, 398, 400, + 402, 404, 406, 408, 410, 412, 414, 414, 416, 418, 420, 422, 424, 426, 428, 430, 432, 434, 436, 436, 438, + 440, 442, 444, 446, 448, 450, 452, 454, 456, 458, 460, 460, 462, 464, 466, 468, 470, 472, 474, 476, 478 + }; + + static int dru_softcaps[] = { + 254, 256, 258, 260, 262, 264, 264, 266, 268, 270, 272, 272, 274, 276, 278, 280, 282, 282, 284, 286, 288, + 290, 290, 292, 294, 296, 298, 300, 300, 302, 304, 306, 308, 308, 310, 312, 314, 316, 318, 318, 320, 322, + 324, 326, 328, 328, 330, 332, 334, 336, 336, 338, 340, 342, 344, 346, 346, 348, 350, 352, 354, 354, 356, + 358, 360, 362, 364, 364, 366, 368, 370, 372, 372, 374, 376, 378, 380, 382, 382, 384, 386, 388, 390, 390, + 392, 394, 396, 398, 400, 400, 402, 404, 406, 408, 410, 410, 412, 414, 416, 418, 418, 420, 422, 424, 426 + }; + + static int rogshmbstber_softcaps[] = { + 264, 266, 268, 270, 272, 272, 274, 276, 278, 280, 282, 282, 284, 286, 288, 290, 292, 294, 294, 296, 298, + 300, 302, 304, 306, 306, 308, 310, 312, 314, 316, 316, 318, 320, 322, 324, 326, 328, 328, 330, 332, 334, + 336, 338, 340, 340, 342, 344, 346, 348, 350, 350, 352, 354, 356, 358, 360, 362, 362, 364, 366, 368, 370, + 372, 374, 374, 376, 378, 380, 382, 384, 384, 386, 388, 390, 392, 394, 396, 396, 398, 400, 402, 404, 406, + 408, 408, 410, 412, 414, 416, 418, 418, 420, 422, 424, 426, 428, 430, 430, 432, 434, 436, 438, 440, 442 + }; + + static int necwizmagenc_softcaps[] = { + 248, 250, 252, 254, 256, 256, 258, 260, 262, 264, 264, 266, 268, 270, 272, 272, 274, 276, 278, 280, 280, + 282, 284, 286, 288, 288, 290, 292, 294, 296, 296, 298, 300, 302, 304, 304, 306, 308, 310, 312, 312, 314, + 316, 318, 320, 320, 322, 324, 326, 328, 328, 330, 332, 334, 336, 336, 338, 340, 342, 344, 344, 346, 348, + 350, 352, 352, 354, 356, 358, 360, 360, 362, 364, 366, 368, 368, 370, 372, 374, 376, 376, 378, 380, 382, + 384, 384, 386, 388, 390, 392, 392, 394, 396, 398, 400, 400, 402, 404, 406, 408, 408, 410, 412, 414, 416 + }; + + int level = std::min(105, static_cast(GetLevel())) - 1; + + switch (GetClass()) { + case WARRIOR: + return war_softcaps[level]; + case CLERIC: + case BARD: + case MONK: + return clrbrdmnk_softcaps[level]; + case PALADIN: + case SHADOWKNIGHT: + return palshd_softcaps[level]; + case RANGER: + return rng_softcaps[level]; + case DRUID: + return dru_softcaps[level]; + case ROGUE: + case SHAMAN: + case BEASTLORD: + case BERSERKER: + return rogshmbstber_softcaps[level]; + case NECROMANCER: + case WIZARD: + case MAGICIAN: + case ENCHANTER: + return necwizmagenc_softcaps[level]; + default: + return 350; + } +} + +double Mob::GetSoftcapReturns() +{ + // These are based on the dev post, they seem to be correct for every level + // AKA no more hard caps + switch (GetClass()) { + case WARRIOR: + return 0.35; + case CLERIC: + case BARD: + case MONK: + return 0.3; + case PALADIN: + case SHADOWKNIGHT: + return 0.33; + case RANGER: + return 0.315; + case DRUID: + return 0.265; + case ROGUE: + case SHAMAN: + case BEASTLORD: + case BERSERKER: + return 0.28; + case NECROMANCER: + case WIZARD: + case MAGICIAN: + case ENCHANTER: + return 0.25; + default: + return 0.3; + } +} + +int Mob::GetClassRaceACBonus() +{ + int ac_bonus = 0; + auto level = GetLevel(); + if (GetClass() == MONK) { + int hardcap = 30; + int softcap = 14; + if (level > 99) { + hardcap = 58; + softcap = 35; + } else if (level > 94) { + hardcap = 57; + softcap = 34; + } else if (level > 89) { + hardcap = 56; + softcap = 33; + } else if (level > 84) { + hardcap = 55; + softcap = 32; + } else if (level > 79) { + hardcap = 54; + softcap = 31; + } else if (level > 74) { + hardcap = 53; + softcap = 30; + } else if (level > 69) { + hardcap = 53; + softcap = 28; + } else if (level > 64) { + hardcap = 53; + softcap = 26; + } else if (level > 63) { + hardcap = 50; + softcap = 24; + } else if (level > 61) { + hardcap = 47; + softcap = 24; + } else if (level > 59) { + hardcap = 45; + softcap = 24; + } else if (level > 54) { + hardcap = 40; + softcap = 20; + } else if (level > 50) { + hardcap = 38; + softcap = 18; + } else if (level > 44) { + hardcap = 36; + softcap = 17; + } else if (level > 29) { + hardcap = 34; + softcap = 16; + } else if (level > 14) { + hardcap = 32; + softcap = 15; + } + int weight = IsClient() ? CastToClient()->CalcCurrentWeight() : 0; + if (weight < hardcap - 1) { + int temp = level + 5; + if (weight > softcap) { + double redux = (weight - softcap) * 6.66667; + redux = (100.0 - std::min(100.0, redux)) * 0.01; + temp = std::max(0, static_cast(temp * redux)); + } + ac_bonus = (4 * temp) / 3; + } else if (weight > hardcap + 1) { + int temp = level + 5; + double multiplier = std::min(1.0, (weight - (hardcap - 10.0)) / 100.0); + temp = (4 * temp) / 3; + ac_bonus -= static_cast(temp * multiplier); + } + } + + if (GetClass() == ROGUE) { + int level_scaler = level - 26; + if (GetAGI() < 80) + ac_bonus = level_scaler / 4; + else if (GetAGI() < 85) + ac_bonus = (level_scaler * 2) / 4; + else if (GetAGI() < 90) + ac_bonus = (level_scaler * 3) / 4; + else if (GetAGI() < 100) + ac_bonus = (level_scaler * 4) / 4; + else if (GetAGI() >= 100) + ac_bonus = (level_scaler * 5) / 4; + if (ac_bonus > 12) + ac_bonus = 12; + } + + if (GetClass() == BEASTLORD) { + int level_scaler = level - 6; + if (GetAGI() < 80) + ac_bonus = level_scaler / 5; + else if (GetAGI() < 85) + ac_bonus = (level_scaler * 2) / 5; + else if (GetAGI() < 90) + ac_bonus = (level_scaler * 3) / 5; + else if (GetAGI() < 100) + ac_bonus = (level_scaler * 4) / 5; + else if (GetAGI() >= 100) + ac_bonus = (level_scaler * 5) / 5; + if (ac_bonus > 16) + ac_bonus = 16; + } + + if (GetRace() == IKSAR) + ac_bonus += EQEmu::Clamp(static_cast(level), 10, 35); + + return ac_bonus; +} + +int Mob::ACSum() +{ + int ac = 0; // this should be base AC whenever shrouds come around + ac += itembonuses.AC; // items + food + tribute + int shield_ac = 0; + if (HasShieldEquiped() && IsClient()) { + auto client = CastToClient(); + auto inst = client->GetInv().GetItem(EQEmu::inventory::slotSecondary); + if (inst) { + if (inst->GetItemRecommendedLevel(true) <= GetLevel()) + shield_ac = inst->GetItemArmorClass(true); + else + shield_ac = client->CalcRecommendedLevelBonus(GetLevel(), inst->GetItemRecommendedLevel(true), inst->GetItemArmorClass(true)); + } + shield_ac += client->GetHeroicSTR() / 10; + } + // EQ math + ac = (ac * 4) / 3; + // anti-twink + if (GetLevel() < 50) + ac = std::min(ac, 25 + 6 * GetLevel()); + ac = std::max(0, ac + GetClassRaceACBonus()); + if (IsNPC()) { + // This is the developer tweaked number + // for the VAST amount of NPCs in EQ this number didn't exceed 600 until recently (PoWar) + // According to the guild hall Combat Dummies, a level 50 classic EQ mob it should be ~115 + // For a 60 PoP mob ~120, 70 OoW ~120 + ac += GetAC(); + Mob *owner = nullptr; + if (IsPet()) + owner = GetOwner(); + else if (CastToNPC()->GetSwarmOwner()) + owner = entity_list.GetMobID(CastToNPC()->GetSwarmOwner()); + if (owner) + ac += owner->aabonuses.PetAvoidance + owner->spellbonuses.PetAvoidance + owner->itembonuses.PetAvoidance; + } + auto spell_aa_ac = aabonuses.AC + spellbonuses.AC; + if (EQEmu::ValueWithin(static_cast(GetClass()), NECROMANCER, ENCHANTER)) + ac += GetSkill(EQEmu::skills::SkillDefense) / 2 + spell_aa_ac / 3; + else + ac += GetSkill(EQEmu::skills::SkillDefense) / 3 + spell_aa_ac / 4; + + if (GetAGI() > 70) + ac += GetAGI() / 20; + if (ac < 0) + ac = 0; + + if (IsClient() +#ifdef BOTS + || IsBot() +#endif + ) { + auto softcap = GetACSoftcap(); + auto returns = GetSoftcapReturns(); + int total_aclimitmod = aabonuses.CombatStability + itembonuses.CombatStability + spellbonuses.CombatStability; + if (total_aclimitmod) + softcap = (softcap * (100 + total_aclimitmod)) / 100; + softcap += shield_ac; + if (ac > softcap) { + auto over_cap = ac - softcap; + ac = softcap + (over_cap * returns); + } + Log.Out(Logs::Detail, Logs::Combat, "ACSum ac %d softcap %d returns %f", ac, softcap, returns); + } else { + Log.Out(Logs::Detail, Logs::Combat, "ACSum ac %d", ac); + } + return ac; +} + +int Mob::offense(EQEmu::skills::SkillType skill) +{ + int offense = GetSkill(skill); + int stat_bonus = 0; + if (skill == EQEmu::skills::SkillArchery || skill == EQEmu::skills::SkillThrowing) + stat_bonus = GetDEX(); + else + stat_bonus = GetSTR(); + if (stat_bonus >= 75) + offense += (2 * stat_bonus - 150) / 3; + offense += GetATK(); + return offense; +} + +// this assumes "this" is the defender +// this returns between 0.1 to 2.0 +double Mob::RollD20(double offense, double mitigation) +{ + if (IsClient() && CastToClient()->IsSitting()) + return 2.0; + + // this works pretty good. From Torven's implementation for TAKP + mitigation -= (mitigation - offense) / 2.0; + double diff = offense - mitigation; + double mean = 0; + double mult1, mult2; + + if (offense > 30.0) { + mult1 = offense / 200.0 + 25.75; + if (mitigation / offense < 0.35) + mult1 = mult1 + 1.0; + else if (mitigation / offense > 0.65) + mult1 = mult1 - 1.0; + mult2 = offense / 140 + 18.5; + } else { + mult1 = 11.5 + offense / 2.0; + mult2 = 14.0 + offense / 6.0; + } + + // changing the mean shifts the bell curve + // this was mostly determined by trial and error to find what fit best + if (offense > mitigation) + mean = diff / offense * mult1; + else if (mitigation > offense) + mean = diff / mitigation * mult2; + + double stddev = 8.8; // standard deviation adjusts the height of the bell + // again, trial and error to find what fit best + double theta = 2 * M_PI * zone->random.Real(0.0, 1.0); + double rho = std::sqrt(-2 * std::log(1 - zone->random.Real(0.0, 1.0))); + double d = mean + stddev * rho * std::cos(theta); + + // this combined with the stddev will produce ~15% DI1 and ~15% DI20 when mitigation == offens + d = EQEmu::Clamp(d, -9.5, 9.5); + d += 11.0; + int roll = static_cast(d); + + // the client has an array called damage_factor that is set to this value, so probably what they do + return roll * 0.1; +} + +void Mob::MeleeMitigation(Mob *attacker, int &damage, int base_damage, int offense, EQEmu::skills::SkillType skill, ExtraAttackOptions *opts) +{ + if (damage < 0 || base_damage == 0) return; Mob* defender = this; - float aa_mit = (aabonuses.CombatStability + itembonuses.CombatStability + - spellbonuses.CombatStability) / 100.0f; + auto mitigation = defender->GetMitigationAC(); + if (IsClient() && attacker->IsClient()) + mitigation = mitigation * 80 / 100; // 2004 PvP changes - if (RuleB(Combat, UseIntervalAC)) { - float softcap = (GetSkill(EQEmu::skills::SkillDefense) + GetLevel()) * - RuleR(Combat, SoftcapFactor) * (1.0 + aa_mit); - float mitigation_rating = 0.0; - float attack_rating = 0.0; - int shield_ac = 0; - int armor = 0; - float weight = 0.0; - - float monkweight = RuleI(Combat, MonkACBonusWeight); - monkweight = mod_monk_weight(monkweight, attacker); - - if (IsClient()) { - armor = CastToClient()->GetRawACNoShield(shield_ac); - weight = (CastToClient()->CalcCurrentWeight() / 10.0); - } else if (IsNPC()) { - armor = CastToNPC()->GetRawAC(); - int PetACBonus = 0; - - if (!IsPet()) - armor = (armor / RuleR(Combat, NPCACFactor)); - - Mob *owner = nullptr; - if (IsPet()) - owner = GetOwner(); - else if ((CastToNPC()->GetSwarmOwner())) - owner = entity_list.GetMobID(CastToNPC()->GetSwarmOwner()); - - if (owner) - PetACBonus = owner->aabonuses.PetMeleeMitigation + owner->itembonuses.PetMeleeMitigation + owner->spellbonuses.PetMeleeMitigation; - - armor += spellbonuses.AC + itembonuses.AC + PetACBonus + 1; - } - - if (opts) { - armor *= (1.0f - opts->armor_pen_percent); - armor -= opts->armor_pen_flat; - } - - if (RuleB(Combat, OldACSoftcapRules)) { - if (GetClass() == WIZARD || GetClass() == MAGICIAN || - GetClass() == NECROMANCER || GetClass() == ENCHANTER) - softcap = RuleI(Combat, ClothACSoftcap); - else if (GetClass() == MONK && weight <= monkweight) - softcap = RuleI(Combat, MonkACSoftcap); - else if(GetClass() == DRUID || GetClass() == BEASTLORD || GetClass() == MONK) - softcap = RuleI(Combat, LeatherACSoftcap); - else if(GetClass() == SHAMAN || GetClass() == ROGUE || - GetClass() == BERSERKER || GetClass() == RANGER) - softcap = RuleI(Combat, ChainACSoftcap); - else - softcap = RuleI(Combat, PlateACSoftcap); - } - - softcap += shield_ac; - armor += shield_ac; - if (RuleB(Combat, OldACSoftcapRules)) - softcap += (softcap * (aa_mit * RuleR(Combat, AAMitigationACFactor))); - if (armor > softcap) { - int softcap_armor = armor - softcap; - if (RuleB(Combat, OldACSoftcapRules)) { - if (GetClass() == WARRIOR) - softcap_armor = softcap_armor * RuleR(Combat, WarriorACSoftcapReturn); - else if (GetClass() == SHADOWKNIGHT || GetClass() == PALADIN || - (GetClass() == MONK && weight <= monkweight)) - softcap_armor = softcap_armor * RuleR(Combat, KnightACSoftcapReturn); - else if (GetClass() == CLERIC || GetClass() == BARD || - GetClass() == BERSERKER || GetClass() == ROGUE || - GetClass() == SHAMAN || GetClass() == MONK) - softcap_armor = softcap_armor * RuleR(Combat, LowPlateChainACSoftcapReturn); - else if (GetClass() == RANGER || GetClass() == BEASTLORD) - softcap_armor = softcap_armor * RuleR(Combat, LowChainLeatherACSoftcapReturn); - else if (GetClass() == WIZARD || GetClass() == MAGICIAN || - GetClass() == NECROMANCER || GetClass() == ENCHANTER || - GetClass() == DRUID) - softcap_armor = softcap_armor * RuleR(Combat, CasterACSoftcapReturn); - else - softcap_armor = softcap_armor * RuleR(Combat, MiscACSoftcapReturn); - } else { - if (GetClass() == WARRIOR) - softcap_armor *= RuleR(Combat, WarACSoftcapReturn); - else if (GetClass() == PALADIN || GetClass() == SHADOWKNIGHT) - softcap_armor *= RuleR(Combat, PalShdACSoftcapReturn); - else if (GetClass() == CLERIC || GetClass() == RANGER || - GetClass() == MONK || GetClass() == BARD) - softcap_armor *= RuleR(Combat, ClrRngMnkBrdACSoftcapReturn); - else if (GetClass() == DRUID || GetClass() == NECROMANCER || - GetClass() == WIZARD || GetClass() == ENCHANTER || - GetClass() == MAGICIAN) - softcap_armor *= RuleR(Combat, DruNecWizEncMagACSoftcapReturn); - else if (GetClass() == ROGUE || GetClass() == SHAMAN || - GetClass() == BEASTLORD || GetClass() == BERSERKER) - softcap_armor *= RuleR(Combat, RogShmBstBerACSoftcapReturn); - else - softcap_armor *= RuleR(Combat, MiscACSoftcapReturn); - } - armor = softcap + softcap_armor; - } - - if (GetClass() == WIZARD || GetClass() == MAGICIAN || - GetClass() == NECROMANCER || GetClass() == ENCHANTER) - mitigation_rating = ((GetSkill(EQEmu::skills::SkillDefense) + itembonuses.HeroicAGI / 10) / 4.0) + armor + 1; - else - mitigation_rating = ((GetSkill(EQEmu::skills::SkillDefense) + itembonuses.HeroicAGI / 10) / 3.0) + (armor * 1.333333) + 1; - mitigation_rating *= 0.847; - - mitigation_rating = mod_mitigation_rating(mitigation_rating, attacker); - - if (attacker->IsClient()) - attack_rating = (attacker->CastToClient()->CalcATK() + ((attacker->GetSTR() - 66) * 0.9) + (attacker->GetSkill(EQEmu::skills::SkillOffense)*1.345)); - else - attack_rating = (attacker->GetATK() + (attacker->GetSkill(EQEmu::skills::SkillOffense)*1.345) + ((attacker->GetSTR() - 66) * 0.9)); - - attack_rating = attacker->mod_attack_rating(attack_rating, this); - - damage = GetMeleeMitDmg(attacker, damage, minhit, mitigation_rating, attack_rating); - } else { - //////////////////////////////////////////////////////// - // Scorpious2k: Include AC in the calculation - // use serverop variables to set values - int32 myac = GetAC(); - if(opts) { - myac *= (1.0f - opts->armor_pen_percent); - myac -= opts->armor_pen_flat; - } - - if (damage > 0 && myac > 0) { - int acfail=1000; - std::string tmp; - - if (database.GetVariable("ACfail", tmp)) { - acfail = (int) (atof(tmp.c_str()) * 100); - if (acfail>100) acfail=100; - } - - if (acfail<=0 || zone->random.Int(0, 100)>acfail) { - float acreduction=1; - int acrandom=300; - if (database.GetVariable("ACreduction", tmp)) - { - acreduction=atof(tmp.c_str()); - if (acreduction>100) acreduction=100; - } - - if (database.GetVariable("ACrandom", tmp)) - { - acrandom = (int) ((atof(tmp.c_str())+1) * 100); - if (acrandom>10100) acrandom=10100; - } - - if (acreduction>0) { - damage -= (int32) (GetAC() * acreduction/100.0f); - } - if (acrandom>0) { - damage -= (myac * zone->random.Int(0, acrandom) / 10000); - } - if (damage<1) damage=1; - Log.Out(Logs::Detail, Logs::Combat, "AC Damage Reduction: fail chance %d%%. Failed. Reduction %.3f%%, random %d. Resulting damage %d.", acfail, acreduction, acrandom, damage); - } else { - Log.Out(Logs::Detail, Logs::Combat, "AC Damage Reduction: fail chance %d%%. Did not fail.", acfail); - } - } - - damage -= (aa_mit * damage); - - if(damage != 0 && damage < minhit) - damage = minhit; - //reduce the damage from shielding item and aa based on the min dmg - //spells offer pure mitigation - damage -= (minhit * defender->itembonuses.MeleeMitigation / 100); - damage -= (damage * (defender->spellbonuses.MeleeMitigationEffect + defender->itembonuses.MeleeMitigationEffect + defender->aabonuses.MeleeMitigationEffect) / 100); + if (opts) { + mitigation *= (1.0f - opts->armor_pen_percent); + mitigation -= opts->armor_pen_flat; } + auto roll = RollD20(offense, mitigation); + + // 168 Defensive -- TODO: I think this is suppose to happen after damage table + // It happening after damage table also means it acts more like negative 185, which might explain + // why the spell has a negative value :P + auto meleemitspell = spellbonuses.MeleeMitigationEffect + itembonuses.MeleeMitigationEffect + aabonuses.MeleeMitigationEffect; + if (GetClass() == WARRIOR && IsClient()) + meleemitspell += 5; + + // +0.5 for rounding + damage = static_cast(roll * static_cast(base_damage) + 0.5); + + if (meleemitspell) + damage = (damage * (100 - meleemitspell)) / 100; + if (damage < 0) damage = 0; -} - -// This is called when the Mob is the one being hit -int32 Mob::GetMeleeMitDmg(Mob *attacker, int32 damage, int32 minhit, - float mit_rating, float atk_rating) -{ - float d = 10.0; - float mit_roll = zone->random.Real(0, mit_rating); - float atk_roll = zone->random.Real(0, atk_rating); - - if (atk_roll > mit_roll) { - float a_diff = atk_roll - mit_roll; - float thac0 = atk_rating * RuleR(Combat, ACthac0Factor); - float thac0cap = attacker->GetLevel() * 9 + 20; - if (thac0 > thac0cap) - thac0 = thac0cap; - - d -= 10.0 * (a_diff / thac0); - } else if (mit_roll > atk_roll) { - float m_diff = mit_roll - atk_roll; - float thac20 = mit_rating * RuleR(Combat, ACthac20Factor); - float thac20cap = GetLevel() * 9 + 20; - if (thac20 > thac20cap) - thac20 = thac20cap; - - d += 10.0 * (m_diff / thac20); - } - - if (d < 0.0) - d = 0.0; - else if (d > 20.0) - d = 20.0; - - float interval = (damage - minhit) / 20.0; - damage -= ((int)d * interval); - - damage -= (minhit * itembonuses.MeleeMitigation / 100); - damage -= (damage * (spellbonuses.MeleeMitigationEffect + itembonuses.MeleeMitigationEffect + aabonuses.MeleeMitigationEffect) / 100); - return damage; -} - -// This is called when the Client is the one being hit -int32 Client::GetMeleeMitDmg(Mob *attacker, int32 damage, int32 minhit, - float mit_rating, float atk_rating) -{ - if (!attacker->IsNPC() || RuleB(Combat, UseOldDamageIntervalRules)) - return Mob::GetMeleeMitDmg(attacker, damage, minhit, mit_rating, atk_rating); - int d = 10; - // floats for the rounding issues - float dmg_interval = (damage - minhit) / 19.0; - float dmg_bonus = minhit - dmg_interval; - float spellMeleeMit = (spellbonuses.MeleeMitigationEffect + itembonuses.MeleeMitigationEffect + aabonuses.MeleeMitigationEffect) / 100.0; - if (GetClass() == WARRIOR) - spellMeleeMit += 0.05; - dmg_bonus -= dmg_bonus * (itembonuses.MeleeMitigation / 100.0); - dmg_interval -= dmg_interval * spellMeleeMit; - - float mit_roll = zone->random.Real(0, mit_rating); - float atk_roll = zone->random.Real(0, atk_rating); - - if (atk_roll > mit_roll) { - float a_diff = atk_roll - mit_roll; - float thac0 = atk_rating * RuleR(Combat, ACthac0Factor); - float thac0cap = attacker->GetLevel() * 9 + 20; - if (thac0 > thac0cap) - thac0 = thac0cap; - - d += 10 * (a_diff / thac0); - } else if (mit_roll > atk_roll) { - float m_diff = mit_roll - atk_roll; - float thac20 = mit_rating * RuleR(Combat, ACthac20Factor); - float thac20cap = GetLevel() * 9 + 20; - if (thac20 > thac20cap) - thac20 = thac20cap; - - d -= 10 * (m_diff / thac20); - } - - if (d < 1) - d = 1; - else if (d > 20) - d = 20; - - return static_cast((dmg_bonus + dmg_interval * d)); + Log.Out(Logs::Detail, Logs::Attack, "mitigation %d vs offense %d. base %d defensive SPA %d rolled %f damage %d", mitigation, offense, base_damage, meleemitspell, roll, damage); } //Returns the weapon damage against the input mob @@ -964,10 +1096,113 @@ int Mob::GetWeaponDamage(Mob *against, const EQEmu::ItemInstance *weapon_item, u return std::max(0, dmg); } +int Client::DoDamageCaps(int base_damage) +{ + // this is based on a client function that caps melee base_damage + auto level = GetLevel(); + int cap = 0; + if (level >= 125) { + cap = 7 * level; + } else if (level >= 110) { + cap = 6 * level; + } else if (level >= 90) { + cap = 5 * level; + } else if (level >= 70) { + cap = 4 * level; + } else if (level >= 40) { + switch (GetClass()) { + case CLERIC: + case DRUID: + case SHAMAN: + cap = 80; + break; + case NECROMANCER: + case WIZARD: + case MAGICIAN: + case ENCHANTER: + cap = 40; + break; + default: + cap = 200; + break; + } + } else if (level >= 30) { + switch (GetClass()) { + case CLERIC: + case DRUID: + case SHAMAN: + cap = 26; + break; + case NECROMANCER: + case WIZARD: + case MAGICIAN: + case ENCHANTER: + cap = 18; + break; + default: + cap = 60; + break; + } + } else if (level >= 20) { + switch (GetClass()) { + case CLERIC: + case DRUID: + case SHAMAN: + cap = 20; + break; + case NECROMANCER: + case WIZARD: + case MAGICIAN: + case ENCHANTER: + cap = 12; + break; + default: + cap = 30; + break; + } + } else if (level >= 10) { + switch (GetClass()) { + case CLERIC: + case DRUID: + case SHAMAN: + cap = 12; + break; + case NECROMANCER: + case WIZARD: + case MAGICIAN: + case ENCHANTER: + cap = 10; + break; + default: + cap = 14; + break; + } + } else { + switch (GetClass()) { + case CLERIC: + case DRUID: + case SHAMAN: + cap = 9; + break; + case NECROMANCER: + case WIZARD: + case MAGICIAN: + case ENCHANTER: + cap = 6; + break; + default: + cap = 10; // this is where the 20 damage cap comes from + break; + } + } + + return std::min(cap, base_damage); +} + //note: throughout this method, setting `damage` to a negative is a way to //stop the attack calculations // IsFromSpell added to allow spell effects to use Attack. (Mainly for the Rampage AA right now.) -bool Client::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, bool IsFromSpell, ExtraAttackOptions *opts, int special) +bool Client::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, bool IsFromSpell, ExtraAttackOptions *opts) { if (!other) { SetTarget(nullptr); @@ -1027,7 +1262,7 @@ bool Client::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, b AttackAnimation(skillinuse, Hand, weapon); Log.Out(Logs::Detail, Logs::Combat, "Attacking with %s in slot %d using skill %d", weapon?weapon->GetItem()->Name:"Fist", Hand, skillinuse); - /// Now figure out damage + // Now figure out damage int damage = 0; uint8 mylevel = GetLevel() ? GetLevel() : 1; uint32 hate = 0; @@ -1038,30 +1273,18 @@ bool Client::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, b //if weapon damage > 0 then we know we can hit the target with this weapon //otherwise we cannot and we set the damage to -5 later on if(weapon_damage > 0){ + // if we revamp this function be more general, we will have to make sure this isn't + // executed for anything BUT normal melee damage weapons from auto attack + if (Hand == EQEmu::inventory::slotPrimary || Hand == EQEmu::inventory::slotSecondary) + weapon_damage = DoDamageCaps(weapon_damage); auto shield_inc = spellbonuses.ShieldEquipDmgMod + itembonuses.ShieldEquipDmgMod + aabonuses.ShieldEquipDmgMod; if (shield_inc > 0 && HasShieldEquiped() && Hand == EQEmu::inventory::slotPrimary) { weapon_damage = weapon_damage * (100 + shield_inc) / 100; hate = hate * (100 + shield_inc) / 100; } - //Berserker Berserk damage bonus - if(IsBerserk() && GetClass() == BERSERKER){ - int bonus = 3 + GetLevel()/10; //unverified - weapon_damage = weapon_damage * (100+bonus) / 100; - Log.Out(Logs::Detail, Logs::Combat, "Berserker damage bonus increases DMG to %d", weapon_damage); - } - - //try a finishing blow.. if successful end the attack - if(TryFinishingBlow(other, skillinuse)) - return (true); - - int min_hit = 1; - int max_hit = (2*weapon_damage*GetDamageTable(skillinuse)) / 100; - - if(GetLevel() < 10 && max_hit > RuleI(Combat, HitCapPre10)) - max_hit = (RuleI(Combat, HitCapPre10)); - else if(GetLevel() < 20 && max_hit > RuleI(Combat, HitCapPre20)) - max_hit = (RuleI(Combat, HitCapPre20)); + int base_damage = weapon_damage; + int min_damage = 0; // damage bonus CheckIncreaseSkill(skillinuse, other, -15); CheckIncreaseSkill(EQEmu::skills::SkillOffense, other, -15); @@ -1087,8 +1310,7 @@ bool Client::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, b ucDamageBonus = GetWeaponDamageBonus(weapon ? weapon->GetItem() : (const EQEmu::ItemData*) nullptr); - min_hit += (int) ucDamageBonus; - max_hit += (int) ucDamageBonus; + min_damage = ucDamageBonus; hate += ucDamageBonus; } #endif @@ -1098,33 +1320,25 @@ bool Client::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, b ucDamageBonus = GetWeaponDamageBonus(weapon ? weapon->GetItem() : (const EQEmu::ItemData*) nullptr, true); - min_hit += (int) ucDamageBonus; - max_hit += (int) ucDamageBonus; + min_damage = ucDamageBonus; hate += ucDamageBonus; } } // this effect is actually a min cap that happens after the final damage is calculated - min_hit += min_hit * GetMeleeMinDamageMod_SE(skillinuse) / 100; + int min_cap = base_damage * GetMeleeMinDamageMod_SE(skillinuse) / 100; - if(max_hit < min_hit) - max_hit = min_hit; + // damage = mod_client_damage(damage, skillinuse, Hand, weapon, other); - if(RuleB(Combat, UseIntervalAC)) - damage = max_hit; - else - damage = zone->random.Int(min_hit, max_hit); - - damage = mod_client_damage(damage, skillinuse, Hand, weapon, other); - - Log.Out(Logs::Detail, Logs::Combat, "Damage calculated to %d (min %d, max %d, str %d, skill %d, DMG %d, lv %d)", - damage, min_hit, max_hit, GetSTR(), GetSkill(skillinuse), weapon_damage, mylevel); + Log.Out(Logs::Detail, Logs::Combat, "Damage calculated to %d (bonus %d, base %d, str %d, skill %d, DMG %d, lv %d)", + damage, min_damage, base_damage, GetSTR(), GetSkill(skillinuse), weapon_damage, mylevel); int hit_chance_bonus = 0; + auto offense = this->offense(skillinuse); // we need this a few times if(opts) { - damage *= opts->damage_percent; - damage += opts->damage_flat; + base_damage *= opts->damage_percent; + base_damage += opts->damage_flat; hate *= opts->hate_percent; hate += opts->hate_flat; hit_chance_bonus += opts->hit_chance; @@ -1149,9 +1363,11 @@ bool Client::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, b Log.Out(Logs::Detail, Logs::Combat, "Avoided damage with code %d", damage); } else { if (other->CheckHitChance(this, skillinuse, hit_chance_bonus)) { - other->MeleeMitigation(this, damage, min_hit, opts); - if (damage > 0) - CommonOutgoingHitSuccess(other, damage, skillinuse,opts); + other->MeleeMitigation(this, damage, base_damage, offense, skillinuse, opts); + if (damage > 0) { + ApplyDamageTable(damage, offense); + CommonOutgoingHitSuccess(other, damage, min_damage, min_cap, skillinuse, opts); + } Log.Out(Logs::Detail, Logs::Combat, "Final damage after all reductions: %d", damage); } else { Log.Out(Logs::Detail, Logs::Combat, "Attack missed. Damage set to 0."); @@ -1177,7 +1393,7 @@ bool Client::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, b SpellFinished(aabonuses.SkillAttackProc[2], other, EQEmu::CastingSlot::Item, 0, -1, spells[aabonuses.SkillAttackProc[2]].ResistDiff); } - other->Damage(this, damage, SPELL_UNKNOWN, skillinuse, true, -1, false, special); + other->Damage(this, damage, SPELL_UNKNOWN, skillinuse, true, -1, false, m_specialattacks); if (IsDead()) return false; @@ -1205,7 +1421,7 @@ void Mob::Heal() SendHPUpdate(); } -void Client::Damage(Mob* other, int32 damage, uint16 spell_id, EQEmu::skills::SkillType attack_skill, bool avoidable, int8 buffslot, bool iBuffTic, int special) +void Client::Damage(Mob* other, int32 damage, uint16 spell_id, EQEmu::skills::SkillType attack_skill, bool avoidable, int8 buffslot, bool iBuffTic, eSpecialAttacks special) { if(dead || IsCorpse()) return; @@ -1522,7 +1738,7 @@ bool Client::Death(Mob* killerMob, int32 damage, uint16 spell, EQEmu::skills::Sk return true; } -bool NPC::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, bool IsFromSpell, ExtraAttackOptions *opts, int special) +bool NPC::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, bool IsFromSpell, ExtraAttackOptions *opts) { int damage = 0; @@ -1620,12 +1836,12 @@ bool NPC::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, bool AttackAnimation(skillinuse, Hand, &weapon_inst); //basically "if not immune" then do the attack - if((weapon_damage) > 0) { + if(weapon_damage > 0) { //ele and bane dmg too //NPCs add this differently than PCs //if NPCs can't inheriently hit the target we don't add bane/magic dmg which isn't exactly the same as PCs - uint32 eleBane = 0; + int eleBane = 0; if(weapon){ if(weapon->BaneDmgBody == other->GetBodyType()){ eleBane += weapon->BaneDmgAmt; @@ -1652,71 +1868,36 @@ bool NPC::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, bool otherlevel = otherlevel ? otherlevel : 1; mylevel = mylevel ? mylevel : 1; - //instead of calcing damage in floats lets just go straight to ints - if(RuleB(Combat, UseIntervalAC)) - damage = (max_dmg+eleBane); - else - damage = zone->random.Int((min_dmg+eleBane),(max_dmg+eleBane)); + //damage = mod_npc_damage(damage, skillinuse, Hand, weapon, other); - //check if we're hitting above our max or below it. - if((min_dmg+eleBane) != 0 && damage < (min_dmg+eleBane)) { - Log.Out(Logs::Detail, Logs::Combat, "Damage (%d) is below min (%d). Setting to min.", damage, (min_dmg+eleBane)); - damage = (min_dmg+eleBane); - } - if((max_dmg+eleBane) != 0 && damage > (max_dmg+eleBane)) { - Log.Out(Logs::Detail, Logs::Combat, "Damage (%d) is above max (%d). Setting to max.", damage, (max_dmg+eleBane)); - damage = (max_dmg+eleBane); + int lbase_damage = this->base_damage + eleBane; + int lmin_damage = this->min_damage; + int32 hate = lbase_damage + lmin_damage; + + int min_cap = lbase_damage * GetMeleeMinDamageMod_SE(skillinuse) / 100; + + int hit_chance_bonus = 0; + + if(opts) { + lbase_damage *= opts->damage_percent; + lbase_damage += opts->damage_flat; + hate *= opts->hate_percent; + hate += opts->hate_flat; + hit_chance_bonus += opts->hit_chance; } - damage = mod_npc_damage(damage, skillinuse, Hand, weapon, other); - - int32 hate = damage; - if(IsPet()) - { - hate = hate * 100 / GetDamageTable(skillinuse); - } - - if(other->IsClient() && other->CastToClient()->IsSitting()) { - Log.Out(Logs::Detail, Logs::Combat, "Client %s is sitting. Hitting for max damage (%d).", other->GetName(), (max_dmg+eleBane)); - damage = (max_dmg+eleBane); - damage += (itembonuses.HeroicSTR / 10) + (damage * other->GetSkillDmgTaken(skillinuse, opts) / 100) + GetSkillDmgAmt(skillinuse); - - if(opts) { - damage *= opts->damage_percent; - damage += opts->damage_flat; - hate *= opts->hate_percent; - hate += opts->hate_flat; - } - - Log.Out(Logs::Detail, Logs::Combat, "Generating hate %d towards %s", hate, GetName()); - // now add done damage to the hate list - other->AddToHateList(this, hate); - + if (other->AvoidDamage(this, damage, Hand)) { + if (!bRiposte && damage == -3) + DoRiposte(other); } else { - - int hit_chance_bonus = 0; - - if(opts) { - damage *= opts->damage_percent; - damage += opts->damage_flat; - hate *= opts->hate_percent; - hate += opts->hate_flat; - hit_chance_bonus += opts->hit_chance; - } - - if (other->AvoidDamage(this, damage, Hand)) { - if (!bRiposte && damage == -3) - DoRiposte(other); + if (other->CheckHitChance(this, skillinuse, hit_chance_bonus)) { + other->MeleeMitigation(this, damage, lbase_damage, this->offense(skillinuse), skillinuse, opts); + CommonOutgoingHitSuccess(other, damage, lmin_damage, min_cap, skillinuse, opts); } else { - if (other->CheckHitChance(this, skillinuse, hit_chance_bonus)) { - other->MeleeMitigation(this, damage, min_dmg+eleBane, opts); - CommonOutgoingHitSuccess(other, damage, skillinuse, opts); - } else { - damage = 0; - } + damage = 0; } - other->AddToHateList(this, hate); } + other->AddToHateList(this, hate); Log.Out(Logs::Detail, Logs::Combat, "Final damage against %s: %d", other->GetName(), damage); @@ -1729,7 +1910,7 @@ bool NPC::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, bool damage = -5; if(GetHP() > 0 && !other->HasDied()) { - other->Damage(this, damage, SPELL_UNKNOWN, skillinuse, true, -1, false, special); // Not avoidable client already had thier chance to Avoid + other->Damage(this, damage, SPELL_UNKNOWN, skillinuse, true, -1, false, m_specialattacks); // Not avoidable client already had thier chance to Avoid } else return false; @@ -1764,7 +1945,7 @@ bool NPC::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, bool return false; } -void NPC::Damage(Mob* other, int32 damage, uint16 spell_id, EQEmu::skills::SkillType attack_skill, bool avoidable, int8 buffslot, bool iBuffTic, int special) { +void NPC::Damage(Mob* other, int32 damage, uint16 spell_id, EQEmu::skills::SkillType attack_skill, bool avoidable, int8 buffslot, bool iBuffTic, eSpecialAttacks special) { if(spell_id==0) spell_id = SPELL_UNKNOWN; @@ -2988,7 +3169,7 @@ bool Mob::CheckDoubleAttack() return zone->random.Int(1, 500) <= chance; } -void Mob::CommonDamage(Mob* attacker, int32 &damage, const uint16 spell_id, const EQEmu::skills::SkillType skill_used, bool &avoidable, const int8 buffslot, const bool iBuffTic, int special) { +void Mob::CommonDamage(Mob* attacker, int &damage, const uint16 spell_id, const EQEmu::skills::SkillType skill_used, bool &avoidable, const int8 buffslot, const bool iBuffTic, eSpecialAttacks special) { // This method is called with skill_used=ABJURE for Damage Shield damage. bool FromDamageShield = (skill_used == EQEmu::skills::SkillAbjuration); bool ignore_invul = false; @@ -3226,7 +3407,12 @@ void Mob::CommonDamage(Mob* attacker, int32 &damage, const uint16 spell_id, cons a->type = SkillDamageTypes[skill_used]; // was 0x1c a->damage = damage; a->spellid = spell_id; - a->special = special; + if (special == eSpecialAttacks::AERampage) + a->special = 1; + else if (special == eSpecialAttacks::Rampage) + a->special = 2; + else + a->special = 0; a->meleepush_xy = attacker ? attacker->GetHeading() * 2.0f : 0.0f; if (RuleB(Combat, MeleePush) && damage > 0 && !IsRooted() && (IsClient() || zone->random.Roll(RuleI(Combat, MeleePushChance)))) { @@ -3709,21 +3895,22 @@ void Mob::TrySpellProc(const EQEmu::ItemInstance *inst, const EQEmu::ItemData *w return; } -void Mob::TryPetCriticalHit(Mob *defender, uint16 skill, int32 &damage) +void Mob::TryPetCriticalHit(Mob *defender, uint16 skill, int &damage) { - if(damage < 1) + if (damage < 1) return; - //Allows pets to perform critical hits. - //Each rank adds an additional 1% chance for any melee hit (primary, secondary, kick, bash, etc) to critical, - //dealing up to 63% more damage. http://www.magecompendium.com/aa-short-library.html + // Allows pets to perform critical hits. + // Each rank adds an additional 1% chance for any melee hit (primary, secondary, kick, bash, etc) to critical, + // dealing up to 63% more damage. http://www.magecompendium.com/aa-short-library.html + // appears to be 70% damage, unsure if changed or just bad info before Mob *owner = nullptr; - float critChance = 0.0f; + int critChance = 0; critChance += RuleI(Combat, MeleeBaseCritChance); - uint32 critMod = 163; + int critMod = 170; - if (damage < 1) //We can't critical hit if we don't hit. + if (damage < 1) // We can't critical hit if we don't hit. return; if (IsPet()) @@ -3736,91 +3923,87 @@ void Mob::TryPetCriticalHit(Mob *defender, uint16 skill, int32 &damage) if (!owner) return; - int32 CritPetChance = owner->aabonuses.PetCriticalHit + owner->itembonuses.PetCriticalHit + owner->spellbonuses.PetCriticalHit; - int32 CritChanceBonus = GetCriticalChanceBonus(skill); + int CritPetChance = + owner->aabonuses.PetCriticalHit + owner->itembonuses.PetCriticalHit + owner->spellbonuses.PetCriticalHit; if (CritPetChance || critChance) { - //For pets use PetCriticalHit for base chance, pets do not innately critical with without it - //even if buffed with a CritChanceBonus effects. + // For pets use PetCriticalHit for base chance, pets do not innately critical with without it critChance += CritPetChance; - critChance += critChance*CritChanceBonus/100.0f; } - if(critChance > 0){ - - critChance /= 100; - - if(zone->random.Roll(critChance)) - { - critMod += GetCritDmgMob(skill) * 2; // To account for base crit mod being 200 not 100 + if (critChance > 0) { + if (zone->random.Roll(critChance)) { + critMod += GetCritDmgMob(skill); + damage += 5; damage = (damage * critMod) / 100; - entity_list.FilteredMessageClose_StringID(this, false, 200, - MT_CritMelee, FilterMeleeCrits, CRITICAL_HIT, - GetCleanName(), itoa(damage)); + entity_list.FilteredMessageClose_StringID(this, false, 200, MT_CritMelee, FilterMeleeCrits, + CRITICAL_HIT, GetCleanName(), itoa(damage)); } } } -void Mob::TryCriticalHit(Mob *defender, uint16 skill, int32 &damage, ExtraAttackOptions *opts) +void Mob::TryCriticalHit(Mob *defender, uint16 skill, int &damage, int min_damage, ExtraAttackOptions *opts) { - if(damage < 1 || !defender) + if (damage < 1 || !defender) return; // decided to branch this into it's own function since it's going to be duplicating a lot of the // code in here, but could lead to some confusion otherwise if ((IsPet() && GetOwner()->IsClient()) || (IsNPC() && CastToNPC()->GetSwarmOwner())) { - TryPetCriticalHit(defender,skill,damage); + TryPetCriticalHit(defender, skill, damage); return; } #ifdef BOTS if (this->IsPet() && this->GetOwner() && this->GetOwner()->IsBot()) { - this->TryPetCriticalHit(defender,skill,damage); + this->TryPetCriticalHit(defender, skill, damage); return; } -#endif //BOTS +#endif // BOTS float critChance = 0.0f; bool IsBerskerSPA = false; - //1: Try Slay Undead - if (defender->GetBodyType() == BT_Undead || - defender->GetBodyType() == BT_SummonedUndead || defender->GetBodyType() == BT_Vampire) { + // 1: Try Slay Undead + if (defender->GetBodyType() == BT_Undead || defender->GetBodyType() == BT_SummonedUndead || + defender->GetBodyType() == BT_Vampire) { int32 SlayRateBonus = aabonuses.SlayUndead[0] + itembonuses.SlayUndead[0] + spellbonuses.SlayUndead[0]; if (SlayRateBonus) { float slayChance = static_cast(SlayRateBonus) / 10000.0f; if (zone->random.Roll(slayChance)) { - int32 SlayDmgBonus = aabonuses.SlayUndead[1] + itembonuses.SlayUndead[1] + spellbonuses.SlayUndead[1]; - damage = (damage * SlayDmgBonus * 2.25) / 100; + int32 SlayDmgBonus = + aabonuses.SlayUndead[1] + itembonuses.SlayUndead[1] + spellbonuses.SlayUndead[1]; + damage += 5; + damage = (damage * SlayDmgBonus) / 100; if (GetGender() == 1) // female - entity_list.FilteredMessageClose_StringID(this, false, 200, - MT_CritMelee, FilterMeleeCrits, FEMALE_SLAYUNDEAD, - GetCleanName(), itoa(damage)); + entity_list.FilteredMessageClose_StringID( + this, false, 200, MT_CritMelee, FilterMeleeCrits, FEMALE_SLAYUNDEAD, + GetCleanName(), itoa(damage + min_damage)); else // males and neuter I guess - entity_list.FilteredMessageClose_StringID(this, false, 200, - MT_CritMelee, FilterMeleeCrits, MALE_SLAYUNDEAD, - GetCleanName(), itoa(damage)); + entity_list.FilteredMessageClose_StringID( + this, false, 200, MT_CritMelee, FilterMeleeCrits, MALE_SLAYUNDEAD, + GetCleanName(), itoa(damage + min_damage)); return; } } } - //2: Try Melee Critical + // 2: Try Melee Critical - //Base critical rate for all classes is dervived from DEX stat, this rate is then augmented - //by item,spell and AA bonuses allowing you a chance to critical hit. If the following rules - //are defined you will have an innate chance to hit at Level 1 regardless of bonuses. - //Warning: Do not define these rules if you want live like critical hits. + // Base critical rate for all classes is dervived from DEX stat, this rate is then augmented + // by item,spell and AA bonuses allowing you a chance to critical hit. If the following rules + // are defined you will have an innate chance to hit at Level 1 regardless of bonuses. + // Warning: Do not define these rules if you want live like critical hits. critChance += RuleI(Combat, MeleeBaseCritChance); if (IsClient()) { - critChance += RuleI(Combat, ClientBaseCritChance); + critChance += RuleI(Combat, ClientBaseCritChance); if (spellbonuses.BerserkSPA || itembonuses.BerserkSPA || aabonuses.BerserkSPA) - IsBerskerSPA = true; + IsBerskerSPA = true; - if (((GetClass() == WARRIOR || GetClass() == BERSERKER) && GetLevel() >= 12) || IsBerskerSPA) { + if (((GetClass() == WARRIOR || GetClass() == BERSERKER) && GetLevel() >= 12) || IsBerskerSPA) { if (IsBerserk() || IsBerskerSPA) critChance += RuleI(Combat, BerserkBaseCritChance); else @@ -3833,7 +4016,8 @@ void Mob::TryCriticalHit(Mob *defender, uint16 skill, int32 &damage, ExtraAttack if (skill == EQEmu::skills::SkillArchery && GetClass() == RANGER && GetSkill(EQEmu::skills::SkillArchery) >= 65) critChance += 6; - if (skill == EQEmu::skills::SkillThrowing && GetClass() == ROGUE && GetSkill(EQEmu::skills::SkillThrowing) >= 65) { + if (skill == EQEmu::skills::SkillThrowing && GetClass() == ROGUE && + GetSkill(EQEmu::skills::SkillThrowing) >= 65) { critChance += RuleI(Combat, RogueCritThrowingChance); deadlyChance = RuleI(Combat, RogueDeadlyStrikeChance); deadlyMod = RuleI(Combat, RogueDeadlyStrikeMod); @@ -3843,44 +4027,45 @@ void Mob::TryCriticalHit(Mob *defender, uint16 skill, int32 &damage, ExtraAttack if (CritChanceBonus || critChance) { - //Get Base CritChance from Dex. (200 = ~1.6%, 255 = ~2.0%, 355 = ~2.20%) Fall off rate > 255 - //http://giline.versus.jp/shiden/su.htm , http://giline.versus.jp/shiden/damage_e.htm + // Get Base CritChance from Dex. (200 = ~1.6%, 255 = ~2.0%, 355 = ~2.20%) Fall off rate > 255 + // http://giline.versus.jp/shiden/su.htm , http://giline.versus.jp/shiden/damage_e.htm if (GetDEX() <= 255) critChance += (float(GetDEX()) / 125.0f); else if (GetDEX() > 255) - critChance += (float(GetDEX()-255)/ 500.0f) + 2.0f; - critChance += critChance*(float)CritChanceBonus /100.0f; + critChance += (float(GetDEX() - 255) / 500.0f) + 2.0f; + critChance += critChance * (float)CritChanceBonus / 100.0f; } - if(opts) { + if (opts) { critChance *= opts->crit_percent; critChance += opts->crit_flat; } - if(critChance > 0) { + if (critChance > 0) { critChance /= 100; - if(zone->random.Roll(critChance)) - { - uint32 critMod = 200; + if (zone->random.Roll(critChance)) { + if (TryFinishingBlow(defender, static_cast(skill), damage)) + return; + int critMod = 170; bool crip_success = false; int32 CripplingBlowChance = GetCrippBlowChance(); - //Crippling Blow Chance: The percent value of the effect is applied - //to the your Chance to Critical. (ie You have 10% chance to critical and you - //have a 200% Chance to Critical Blow effect, therefore you have a 20% Chance to Critical Blow. + // Crippling Blow Chance: The percent value of the effect is applied + // to the your Chance to Critical. (ie You have 10% chance to critical and you + // have a 200% Chance to Critical Blow effect, therefore you have a 20% Chance to Critical Blow. if (CripplingBlowChance || (IsBerserk() || IsBerskerSPA)) { if (!IsBerserk() && !IsBerskerSPA) - critChance *= float(CripplingBlowChance)/100.0f; + critChance *= float(CripplingBlowChance) / 100.0f; - if ((IsBerserk() || IsBerskerSPA) || zone->random.Roll(critChance)) { - critMod = 400; + if ((IsBerserk() || IsBerskerSPA) || zone->random.Roll(critChance)) crip_success = true; - } } - critMod += GetCritDmgMob(skill) * 2; // To account for base crit mod being 200 not 100 + critMod += GetCritDmgMob(skill); + damage += 5; + int ogdmg = damage; damage = damage * critMod / 100; bool deadlySuccess = false; @@ -3892,33 +4077,37 @@ void Mob::TryCriticalHit(Mob *defender, uint16 skill, int32 &damage, ExtraAttack } if (crip_success) { - entity_list.FilteredMessageClose_StringID(this, false, 200, - MT_CritMelee, FilterMeleeCrits, CRIPPLING_BLOW, - GetCleanName(), itoa(damage)); + damage += ogdmg * 119 / 100; // the damage_e page says it's a ~1.192 increase of dmg before mod + entity_list.FilteredMessageClose_StringID(this, false, 200, MT_CritMelee, + FilterMeleeCrits, CRIPPLING_BLOW, + GetCleanName(), itoa(damage + min_damage)); // Crippling blows also have a chance to stun - //Kayen: Crippling Blow would cause a chance to interrupt for npcs < 55, with a staggers message. - if (defender->GetLevel() <= 55 && !defender->GetSpecialAbility(IMMUNE_STUN)){ + // Kayen: Crippling Blow would cause a chance to interrupt for npcs < 55, with a + // staggers message. + if (defender->GetLevel() <= 55 && !defender->GetSpecialAbility(IMMUNE_STUN)) { defender->Emote("staggers."); defender->Stun(0); } } else if (deadlySuccess) { - entity_list.FilteredMessageClose_StringID(this, false, 200, - MT_CritMelee, FilterMeleeCrits, DEADLY_STRIKE, - GetCleanName(), itoa(damage)); + entity_list.FilteredMessageClose_StringID(this, false, 200, MT_CritMelee, + FilterMeleeCrits, DEADLY_STRIKE, + GetCleanName(), itoa(damage + min_damage)); } else { - entity_list.FilteredMessageClose_StringID(this, false, 200, - MT_CritMelee, FilterMeleeCrits, CRITICAL_HIT, - GetCleanName(), itoa(damage)); + entity_list.FilteredMessageClose_StringID(this, false, 200, MT_CritMelee, + FilterMeleeCrits, CRITICAL_HIT, + GetCleanName(), itoa(damage + min_damage)); } } } } -bool Mob::TryFinishingBlow(Mob *defender, EQEmu::skills::SkillType skillinuse) +bool Mob::TryFinishingBlow(Mob *defender, EQEmu::skills::SkillType skillinuse, int &damage) { - if (defender && !defender->IsClient() && defender->GetHPRatio() < 10){ + // base2 of FinishingBlowLvl is the HP limit (cur / max) * 1000, 10% is listed as 100 + if (defender && !defender->IsClient() && defender->GetHPRatio() < 10) { - uint32 FB_Dmg = aabonuses.FinishingBlow[1] + spellbonuses.FinishingBlow[1] + itembonuses.FinishingBlow[1]; + uint32 FB_Dmg = + aabonuses.FinishingBlow[1] + spellbonuses.FinishingBlow[1] + itembonuses.FinishingBlow[1]; uint32 FB_Level = 0; FB_Level = aabonuses.FinishingBlowLvl[0]; @@ -3927,12 +4116,14 @@ bool Mob::TryFinishingBlow(Mob *defender, EQEmu::skills::SkillType skillinuse) else if (FB_Level < itembonuses.FinishingBlowLvl[0]) FB_Level = itembonuses.FinishingBlowLvl[0]; - //Proc Chance value of 500 = 5% - uint32 ProcChance = (aabonuses.FinishingBlow[0] + spellbonuses.FinishingBlow[0] + spellbonuses.FinishingBlow[0])/10; + // Proc Chance value of 500 = 5% + uint32 ProcChance = + (aabonuses.FinishingBlow[0] + spellbonuses.FinishingBlow[0] + spellbonuses.FinishingBlow[0]) / 10; - if(FB_Level && FB_Dmg && (defender->GetLevel() <= FB_Level) && (ProcChance >= zone->random.Int(0, 1000))){ + if (FB_Level && FB_Dmg && (defender->GetLevel() <= FB_Level) && + (ProcChance >= zone->random.Int(0, 1000))) { entity_list.MessageClose_StringID(this, false, 200, MT_CritMelee, FINISHING_BLOW, GetName()); - DoSpecialAttackDamage(defender, skillinuse, FB_Dmg, 1, -1, 10, false, false); + damage = FB_Dmg; return true; } } @@ -3991,21 +4182,10 @@ void Mob::DoRiposte(Mob *defender) } } -void Mob::ApplyMeleeDamageBonus(uint16 skill, int32 &damage,ExtraAttackOptions *opts){ - - if(!RuleB(Combat, UseIntervalAC)){ - if(IsNPC()){ //across the board NPC damage bonuses. - //only account for STR here, assume their base STR was factored into their DB damages - int dmgbonusmod = 0; - dmgbonusmod += (100*(itembonuses.STR + spellbonuses.STR))/3; - dmgbonusmod += (100*(spellbonuses.ATK + itembonuses.ATK))/5; - Log.Out(Logs::Detail, Logs::Combat, "Damage bonus: %d percent from ATK and STR bonuses.", (dmgbonusmod/100)); - damage += (damage*dmgbonusmod/10000); - } - } - +void Mob::ApplyMeleeDamageBonus(uint16 skill, int &damage, ExtraAttackOptions *opts) +{ int dmgbonusmod = 0; - + dmgbonusmod += GetMeleeDamageMod_SE(skill); if (opts) dmgbonusmod += opts->melee_damage_bonus_flat; @@ -4025,48 +4205,168 @@ bool Mob::HasDied() { return Result; } -uint16 Mob::GetDamageTable(EQEmu::skills::SkillType skillinuse) +const DamageTable &Mob::GetDamageTable() const { - if(GetLevel() <= 51) - { - uint32 ret_table = 0; - int str_over_75 = 0; - if(GetSTR() > 75) - str_over_75 = GetSTR() - 75; - if(str_over_75 > 255) - ret_table = (GetSkill(skillinuse)+255)/2; - else - ret_table = (GetSkill(skillinuse)+str_over_75)/2; + static const DamageTable dmg_table[] = { + {210, 49, 105}, // 1-50 + {245, 35, 80}, // 51 + {245, 35, 80}, // 52 + {245, 35, 80}, // 53 + {245, 35, 80}, // 54 + {245, 35, 80}, // 55 + {265, 28, 70}, // 56 + {265, 28, 70}, // 57 + {265, 28, 70}, // 58 + {265, 28, 70}, // 59 + {285, 23, 65}, // 60 + {285, 23, 65}, // 61 + {285, 23, 65}, // 62 + {290, 21, 60}, // 63 + {290, 21, 60}, // 64 + {295, 19, 55}, // 65 + {295, 19, 55}, // 66 + {300, 19, 55}, // 67 + {300, 19, 55}, // 68 + {300, 19, 55}, // 69 + {305, 19, 55}, // 70 + {305, 19, 55}, // 71 + {310, 17, 50}, // 72 + {310, 17, 50}, // 73 + {310, 17, 50}, // 74 + {315, 17, 50}, // 75 + {315, 17, 50}, // 76 + {325, 17, 45}, // 77 + {325, 17, 45}, // 78 + {325, 17, 45}, // 79 + {335, 17, 45}, // 80 + {335, 17, 45}, // 81 + {345, 17, 45}, // 82 + {345, 17, 45}, // 83 + {345, 17, 45}, // 84 + {355, 17, 45}, // 85 + {355, 17, 45}, // 86 + {365, 17, 45}, // 87 + {365, 17, 45}, // 88 + {365, 17, 45}, // 89 + {375, 17, 45}, // 90 + {375, 17, 45}, // 91 + {380, 17, 45}, // 92 + {380, 17, 45}, // 93 + {380, 17, 45}, // 94 + {385, 17, 45}, // 95 + {385, 17, 45}, // 96 + {390, 17, 45}, // 97 + {390, 17, 45}, // 98 + {390, 17, 45}, // 99 + {395, 17, 45}, // 100 + {395, 17, 45}, // 101 + {400, 17, 45}, // 102 + {400, 17, 45}, // 103 + {400, 17, 45}, // 104 + {405, 17, 45} // 105 + }; - if(ret_table < 100) - return 100; + static const DamageTable mnk_table[] = { + {220, 45, 100}, // 1-50 + {245, 35, 80}, // 51 + {245, 35, 80}, // 52 + {245, 35, 80}, // 53 + {245, 35, 80}, // 54 + {245, 35, 80}, // 55 + {285, 23, 65}, // 56 + {285, 23, 65}, // 57 + {285, 23, 65}, // 58 + {285, 23, 65}, // 59 + {290, 21, 60}, // 60 + {290, 21, 60}, // 61 + {290, 21, 60}, // 62 + {295, 19, 55}, // 63 + {295, 19, 55}, // 64 + {300, 17, 50}, // 65 + {300, 17, 50}, // 66 + {310, 17, 50}, // 67 + {310, 17, 50}, // 68 + {310, 17, 50}, // 69 + {320, 17, 50}, // 70 + {320, 17, 50}, // 71 + {325, 15, 45}, // 72 + {325, 15, 45}, // 73 + {325, 15, 45}, // 74 + {330, 15, 45}, // 75 + {330, 15, 45}, // 76 + {335, 15, 40}, // 77 + {335, 15, 40}, // 78 + {335, 15, 40}, // 79 + {345, 15, 40}, // 80 + {345, 15, 40}, // 81 + {355, 15, 40}, // 82 + {355, 15, 40}, // 83 + {355, 15, 40}, // 84 + {365, 15, 40}, // 85 + {365, 15, 40}, // 86 + {375, 15, 40}, // 87 + {375, 15, 40}, // 88 + {375, 15, 40}, // 89 + {385, 15, 40}, // 90 + {385, 15, 40}, // 91 + {390, 15, 40}, // 92 + {390, 15, 40}, // 93 + {390, 15, 40}, // 94 + {395, 15, 40}, // 95 + {395, 15, 40}, // 96 + {400, 15, 40}, // 97 + {400, 15, 40}, // 98 + {400, 15, 40}, // 99 + {405, 15, 40}, // 100 + {405, 15, 40}, // 101 + {410, 15, 40}, // 102 + {410, 15, 40}, // 103 + {410, 15, 40}, // 104 + {415, 15, 40}, // 105 + }; - return ret_table; - } - else if(GetLevel() >= 90) - { - if(GetClass() == MONK) - return 379; - else - return 345; - } - else - { - uint32 dmg_table[] = { - 275, 275, 275, 275, 275, - 280, 280, 280, 280, 285, - 285, 285, 290, 290, 295, - 295, 300, 300, 300, 305, - 305, 305, 310, 310, 315, - 315, 320, 320, 320, 325, - 325, 325, 330, 330, 335, - 335, 340, 340, 340, - }; - if(GetClass() == MONK) - return (dmg_table[GetLevel()-51]*(100+RuleI(Combat,MonkDamageTableBonus))/100); - else - return dmg_table[GetLevel()-51]; - } + bool monk = GetClass() == MONK; + bool melee = IsWarriorClass(); + // tables caped at 105 for now -- future proofed for a while at least :P + int level = std::min(static_cast(GetLevel()), 105); + + if (!melee || (!monk && level < 51)) + return dmg_table[0]; + + if (monk && level < 51) + return mnk_table[0]; + + auto &which = monk ? mnk_table : dmg_table; + return which[level - 50]; +} + +void Mob::ApplyDamageTable(int &damage, int offense) +{ + // someone may want to add this to custom servers, can remove this if that's the case + if (!IsClient() +#ifdef BOTS + && !IsBot() +#endif + ) + return; + // this was parsed, but we do see the min of 10 and the normal minus factor is 105, so makes sense + if (offense < 115) + return; + + auto &damage_table = GetDamageTable(); + + if (zone->random.Roll(damage_table.chance)) + return; + + int basebonus = offense - damage_table.minusfactor; + basebonus = std::max(10, basebonus / 2); + int extrapercent = zone->random.Roll0(basebonus); + int percent = std::min(100 + extrapercent, damage_table.max_extra); + damage = (damage * percent) / 100; + + if (IsWarriorClass() && GetLevel() > 54) + damage++; + Log.Out(Logs::Detail, Logs::Attack, "Damage table applied %d (max %d)", percent, damage_table.max_extra); } void Mob::TrySkillProc(Mob *on, uint16 skill, uint16 ReuseTime, bool Success, uint16 hand, bool IsDefensive) @@ -4368,14 +4668,108 @@ int32 Mob::RuneAbsorb(int32 damage, uint16 type) return damage; } -void Mob::CommonOutgoingHitSuccess(Mob* defender, int32 &damage, EQEmu::skills::SkillType skillInUse, ExtraAttackOptions *opts) +// min_damage is the damage bonus, we need to pass it along for a bit +// min_mod is the min hit cap, which needs to be calculated before we call this, but applied here +void Mob::CommonOutgoingHitSuccess(Mob* defender, int &damage, int min_damage, int min_mod, EQEmu::skills::SkillType skillInUse, ExtraAttackOptions *opts) { if (!defender) return; + // BER weren't parsing the halving + if (skillInUse == EQEmu::skills::SkillArchery || + (skillInUse == EQEmu::skills::SkillThrowing && GetClass() != BERSERKER)) + damage /= 2; + + if (damage < 1) + damage = 1; + + if (skillInUse == EQEmu::skills::SkillArchery) { + int bonus = aabonuses.ArcheryDamageModifier + itembonuses.ArcheryDamageModifier + spellbonuses.ArcheryDamageModifier; + damage += damage * bonus / 100; + int headshot = TryHeadShot(defender, skillInUse); + if (headshot > 0) + damage = headshot; + } + + // this parses after damage table + if (skillInUse == EQEmu::skills::SkillArchery && GetClass() == RANGER && GetLevel() > 50) { + if (defender->IsNPC() && !defender->IsMoving() && !defender->IsRooted()) { + damage *= 2; + Message_StringID(MT_CritMelee, BOW_DOUBLE_DAMAGE); + } + } + + int extra_mincap = 0; + if (skillInUse == EQEmu::skills::SkillBackstab) { + extra_mincap = GetLevel() < 7 ? 7 : GetLevel(); + if (GetLevel() >= 60) + extra_mincap = GetLevel() * 2; + else if (GetLevel() > 50) + extra_mincap = GetLevel() * 3 / 2; + if (IsSpecialAttack(eSpecialAttacks::ChaoticStab)) { + damage = extra_mincap; + } else { + int ass = TryAssassinate(defender, skillInUse, 10000); // reusetime shouldn't have an effect .... + if (ass > 0) + damage = ass; + } + } else if (skillInUse == EQEmu::skills::SkillFrenzy && GetClass() == BERSERKER && GetLevel() > 50) { + extra_mincap = 4 * GetLevel() / 5; + } + + // this has some weird ordering + // Seems the crit message is generated before some of them :P + + // worn item +skill dmg, SPA 220, 418. Live has a normalized version that should be here too + min_damage += GetSkillDmgAmt(skillInUse); + + // shielding mod2 + if (defender->itembonuses.MeleeMitigation) + min_damage -= min_damage * defender->itembonuses.MeleeMitigation / 100; + ApplyMeleeDamageBonus(skillInUse, damage, opts); - damage += (damage * defender->GetSkillDmgTaken(skillInUse, opts) / 100) + (GetSkillDmgAmt(skillInUse) + defender->GetFcDamageAmtIncoming(this, 0, true, skillInUse)); - TryCriticalHit(defender, skillInUse, damage,opts); + min_mod = std::max(min_mod, extra_mincap); + if (min_mod && damage < min_mod) // SPA 186 + damage = min_mod; + + TryCriticalHit(defender, skillInUse, damage, min_damage, opts); + + damage += min_damage; + if (IsClient()) { + int extra = 0; + switch (skillInUse) { + case EQEmu::skills::SkillThrowing: + case EQEmu::skills::SkillArchery: + extra = CastToClient()->GetHeroicDEX() / 10; + break; + default: + extra = CastToClient()->GetHeroicSTR() / 10; + break; + } + damage += extra; + } + + // this appears where they do special attack dmg mods + int spec_mod = 0; + if (IsSpecialAttack(eSpecialAttacks::Rampage)) { + int mod = GetSpecialAbilityParam(SPECATK_RAMPAGE, 2); + if (mod > 0) + spec_mod = mod; + if ((IsPet() || IsTempPet()) && IsPetOwnerClient()) { + int spell = spellbonuses.PC_Pet_Rampage[1] + itembonuses.PC_Pet_Rampage[1] + aabonuses.PC_Pet_Rampage[1]; + if (spell > spec_mod) + spec_mod = spell; + } + } else if (IsSpecialAttack(eSpecialAttacks::AERampage)) { + int mod = GetSpecialAbilityParam(SPECATK_AREA_RAMPAGE, 2); + if (mod > 0) + spec_mod = mod; + } + if (spec_mod > 0) + damage = (damage * spec_mod) / 100; + + damage += (damage * defender->GetSkillDmgTaken(skillInUse, opts) / 100) + (defender->GetFcDamageAmtIncoming(this, 0, true, skillInUse)); + CheckNumHitsRemaining(NumHit::OutgoingHitSuccess); } @@ -4533,9 +4927,9 @@ void NPC::SetAttackTimer() // Mob's delay set to 20, weapon set to 21, delay 20 int speed = 0; if (RuleB(Spells, Jun182014HundredHandsRevamp)) - speed = static_cast(((attack_delay / haste_mod) + ((hhe / 1000.0f) * (attack_delay / haste_mod))) * 100); + speed = static_cast((attack_delay / haste_mod) + ((hhe / 1000.0f) * (attack_delay / haste_mod))); else - speed = static_cast(((attack_delay / haste_mod) + ((hhe / 100.0f) * attack_delay)) * 100); + speed = static_cast((attack_delay / haste_mod) + ((hhe / 100.0f) * attack_delay)); for (int i = EQEmu::inventory::slotRange; i <= EQEmu::inventory::slotSecondary; i++) { //pick a timer @@ -4632,7 +5026,7 @@ bool Client::CheckDualWield() return zone->random.Int(1, 375) <= chance; } -void Mob::DoMainHandAttackRounds(Mob *target, ExtraAttackOptions *opts, int special) +void Mob::DoMainHandAttackRounds(Mob *target, ExtraAttackOptions *opts) { if (!target) return; @@ -4640,10 +5034,9 @@ void Mob::DoMainHandAttackRounds(Mob *target, ExtraAttackOptions *opts, int spec if (RuleB(Combat, UseLiveCombatRounds)) { // A "quad" on live really is just a successful dual wield where both double attack // The mobs that could triple lost the ability to when the triple attack skill was added in - Attack(target, EQEmu::inventory::slotPrimary, false, false, false, opts, special); + Attack(target, EQEmu::inventory::slotPrimary, false, false, false, opts); if (CanThisClassDoubleAttack() && CheckDoubleAttack()){ - Attack(target, EQEmu::inventory::slotPrimary, false, false, false, opts, special); - + Attack(target, EQEmu::inventory::slotPrimary, false, false, false, opts); if ((IsPet() || IsTempPet()) && IsPetOwnerClient()){ int chance = spellbonuses.PC_Pet_Flurry + itembonuses.PC_Pet_Flurry + aabonuses.PC_Pet_Flurry; if (chance && zone->random.Roll(chance)) @@ -4656,14 +5049,14 @@ void Mob::DoMainHandAttackRounds(Mob *target, ExtraAttackOptions *opts, int spec if (IsNPC()) { int16 n_atk = CastToNPC()->GetNumberOfAttacks(); if (n_atk <= 1) { - Attack(target, EQEmu::inventory::slotPrimary, false, false, false, opts, special); + Attack(target, EQEmu::inventory::slotPrimary, false, false, false, opts); } else { for (int i = 0; i < n_atk; ++i) { - Attack(target, EQEmu::inventory::slotPrimary, false, false, false, opts, special); + Attack(target, EQEmu::inventory::slotPrimary, false, false, false, opts); } } } else { - Attack(target, EQEmu::inventory::slotPrimary, false, false, false, opts, special); + Attack(target, EQEmu::inventory::slotPrimary, false, false, false, opts); } // we use this random value in three comparisons with different @@ -4674,21 +5067,21 @@ void Mob::DoMainHandAttackRounds(Mob *target, ExtraAttackOptions *opts, int spec // check double attack, this is NOT the same rules that clients use... && RandRoll < (GetLevel() + NPCDualAttackModifier)) { - Attack(target, EQEmu::inventory::slotPrimary, false, false, false, opts, special); + Attack(target, EQEmu::inventory::slotPrimary, false, false, false, opts); // lets see if we can do a triple attack with the main hand // pets are excluded from triple and quads... if ((GetSpecialAbility(SPECATK_TRIPLE) || GetSpecialAbility(SPECATK_QUAD)) && !IsPet() && RandRoll < (GetLevel() + NPCTripleAttackModifier)) { - Attack(target, EQEmu::inventory::slotPrimary, false, false, false, opts, special); + Attack(target, EQEmu::inventory::slotPrimary, false, false, false, opts); // now lets check the quad attack if (GetSpecialAbility(SPECATK_QUAD) && RandRoll < (GetLevel() + NPCQuadAttackModifier)) { - Attack(target, EQEmu::inventory::slotPrimary, false, false, false, opts, special); + Attack(target, EQEmu::inventory::slotPrimary, false, false, false, opts); } } } } -void Mob::DoOffHandAttackRounds(Mob *target, ExtraAttackOptions *opts, int special) +void Mob::DoOffHandAttackRounds(Mob *target, ExtraAttackOptions *opts) { if (!target) return; @@ -4698,9 +5091,9 @@ void Mob::DoOffHandAttackRounds(Mob *target, ExtraAttackOptions *opts, int speci (RuleB(Combat, UseLiveCombatRounds) && GetSpecialAbility(SPECATK_QUAD))) || GetEquipment(EQEmu::textures::weaponSecondary) != 0) { if (CheckDualWield()) { - Attack(target, EQEmu::inventory::slotSecondary, false, false, false, opts, special); + Attack(target, EQEmu::inventory::slotSecondary, false, false, false, opts); if (CanThisClassDoubleAttack() && GetLevel() > 35 && CheckDoubleAttack()){ - Attack(target, EQEmu::inventory::slotSecondary, false, false, false, opts, special); + Attack(target, EQEmu::inventory::slotSecondary, false, false, false, opts); if ((IsPet() || IsTempPet()) && IsPetOwnerClient()){ int chance = spellbonuses.PC_Pet_Flurry + itembonuses.PC_Pet_Flurry + aabonuses.PC_Pet_Flurry; diff --git a/zone/beacon.h b/zone/beacon.h index cd66269bb..b79ed318c 100644 --- a/zone/beacon.h +++ b/zone/beacon.h @@ -35,9 +35,9 @@ public: //abstract virtual function implementations requird by base abstract class virtual bool Death(Mob* killerMob, int32 damage, uint16 spell_id, EQEmu::skills::SkillType attack_skill) { return true; } - virtual void Damage(Mob* from, int32 damage, uint16 spell_id, EQEmu::skills::SkillType attack_skill, bool avoidable = true, int8 buffslot = -1, bool iBuffTic = false, int special = 0) { return; } + virtual void Damage(Mob* from, int32 damage, uint16 spell_id, EQEmu::skills::SkillType attack_skill, bool avoidable = true, int8 buffslot = -1, bool iBuffTic = false, eSpecialAttacks special = eSpecialAttacks::None) { return; } virtual bool Attack(Mob* other, int Hand = EQEmu::inventory::slotPrimary, bool FromRiposte = false, bool IsStrikethrough = false, bool IsFromSpell = false, - ExtraAttackOptions *opts = nullptr, int special = 0) { return false; } + ExtraAttackOptions *opts = nullptr) { return false; } virtual bool HasRaid() { return false; } virtual bool HasGroup() { return false; } virtual Raid* GetRaid() { return 0; } diff --git a/zone/bonuses.cpp b/zone/bonuses.cpp index beae3995d..3acf6cb1d 100644 --- a/zone/bonuses.cpp +++ b/zone/bonuses.cpp @@ -46,6 +46,7 @@ void Mob::CalcBonuses() CalcMaxHP(); CalcMaxMana(); SetAttackTimer(); + CalcAC(); rooted = FindType(SE_Root); } @@ -669,6 +670,10 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon) } switch (effect) { + case SE_ACv2: + case SE_ArmorClass: + newbon->AC += base1; + break; // Note: AA effects that use accuracy are skill limited, while spell effect is not. case SE_Accuracy: // Bad data or unsupported new skill @@ -1527,9 +1532,6 @@ void Mob::CalcSpellBonuses(StatBonuses* newbon) } } - // THIS IS WRONG, leaving for now - //this prolly suffer from roundoff error slightly... - newbon->AC = newbon->AC * 10 / 34; //ratio determined impirically from client. if (GetClass() == BARD) newbon->ManaRegen = 0; // Bards do not get mana regen from spells. } diff --git a/zone/bot.cpp b/zone/bot.cpp index 7b0739b47..ac6a6ddd7 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -1992,110 +1992,6 @@ bool Bot::CheckBotDoubleAttack(bool tripleAttack) { return false; } -void Bot::DoMeleeSkillAttackDmg(Mob* other, uint16 weapon_damage, EQEmu::skills::SkillType skillinuse, int16 chance_mod, int16 focus, bool CanRiposte, int ReuseTime) { - if (!CanDoSpecialAttack(other)) - return; - - //For spells using skill value 98 (feral swipe ect) server sets this to 67 automatically. - if (skillinuse == EQEmu::skills::SkillBegging) - skillinuse = EQEmu::skills::SkillOffense; - - int damage = 0; - uint32 hate = 0; - int Hand = EQEmu::inventory::slotPrimary; - if (hate == 0 && weapon_damage > 1) - hate = weapon_damage; - - if(weapon_damage > 0) { - if(GetClass() == BERSERKER) { - int bonus = (3 + GetLevel( )/ 10); - weapon_damage = (weapon_damage * (100 + bonus) / 100); - } - - int32 min_hit = 1; - int32 max_hit = ((2 * weapon_damage * GetDamageTable(skillinuse)) / 100); - if(GetLevel() >= 28 && IsWarriorClass()) { - int ucDamageBonus = GetWeaponDamageBonus((const EQEmu::ItemData*) nullptr); - min_hit += (int) ucDamageBonus; - max_hit += (int) ucDamageBonus; - hate += ucDamageBonus; - } - - ApplySpecialAttackMod(skillinuse, max_hit, min_hit); - min_hit += (min_hit * GetMeleeMinDamageMod_SE(skillinuse) / 100); - if(max_hit < min_hit) - max_hit = min_hit; - - if(RuleB(Combat, UseIntervalAC)) - damage = max_hit; - else - damage = zone->random.Int(min_hit, max_hit); - - if (other->AvoidDamage(this, damage, CanRiposte ? EQEmu::inventory::slotRange : EQEmu::inventory::slotPrimary)) { // MainRange excludes ripo, primary doesn't have any extra behavior - if (damage == -3) { - DoRiposte(other); - if (HasDied()) - return; - } - } else { - if (other->CheckHitChance(this, skillinuse, chance_mod)) { - other->MeleeMitigation(this, damage, min_hit); - if (damage > 0) { - damage += damage*focus/100; - ApplyMeleeDamageBonus(skillinuse, damage); - damage += other->GetFcDamageAmtIncoming(this, 0, true, skillinuse); - damage += ((itembonuses.HeroicSTR / 10) + (damage * other->GetSkillDmgTaken(skillinuse) / 100) + GetSkillDmgAmt(skillinuse)); - TryCriticalHit(other, skillinuse, damage, nullptr); - } - } else { - damage = 0; - } - } - } - else - damage = -5; - - if (skillinuse == EQEmu::skills::SkillBash){ - const EQEmu::ItemInstance* inst = GetBotItem(EQEmu::inventory::slotSecondary); - const EQEmu::ItemData* botweapon = 0; - if(inst) - botweapon = inst->GetItem(); - - if(botweapon) { - if (botweapon->ItemType == EQEmu::item::ItemTypeShield) - hate += botweapon->AC; - - hate = (hate * (100 + GetFuriousBash(botweapon->Focus.Effect)) / 100); - } - } - - other->AddToHateList(this, hate); - - bool CanSkillProc = true; - if (skillinuse == EQEmu::skills::SkillOffense){ //Hack to allow damage to display. - skillinuse = EQEmu::skills::SkillTigerClaw; //'strike' your opponent - Arbitrary choice for message. - CanSkillProc = false; //Disable skill procs - } - - other->Damage(this, damage, SPELL_UNKNOWN, skillinuse); - if (HasDied()) - return; - - if (damage > 0) - CheckNumHitsRemaining(NumHit::OutgoingHitSuccess); - - if ((skillinuse == EQEmu::skills::SkillDragonPunch) && GetAA(aaDragonPunch) && zone->random.Int(0, 99) < 25){ - SpellFinished(904, other, EQEmu::CastingSlot::Item, 0, -1, spells[904].ResistDiff); - other->Stun(100); - } - - if (CanSkillProc && HasSkillProcs()) - TrySkillProc(other, skillinuse, ReuseTime); - - if (CanSkillProc && (damage > 0) && HasSkillProcSuccess()) - TrySkillProc(other, skillinuse, ReuseTime, true); -} - void Bot::ApplySpecialAttackMod(EQEmu::skills::SkillType skill, int32 &dmg, int32 &mindmg) { int item_slot = -1; //1: Apply bonus from AC (BOOT/SHIELD/HANDS) est. 40AC=6dmg @@ -3664,7 +3560,7 @@ bool Bot::Death(Mob *killerMob, int32 damage, uint16 spell_id, EQEmu::skills::Sk return true; } -void Bot::Damage(Mob *from, int32 damage, uint16 spell_id, EQEmu::skills::SkillType attack_skill, bool avoidable, int8 buffslot, bool iBuffTic, int special) { +void Bot::Damage(Mob *from, int32 damage, uint16 spell_id, EQEmu::skills::SkillType attack_skill, bool avoidable, int8 buffslot, bool iBuffTic, eSpecialAttacks special) { if(spell_id == 0) spell_id = SPELL_UNKNOWN; @@ -3710,7 +3606,7 @@ void Bot::AddToHateList(Mob* other, uint32 hate /*= 0*/, int32 damage /*= 0*/, b Mob::AddToHateList(other, hate, damage, iYellForHelp, bFrenzy, iBuffTic); } -bool Bot::Attack(Mob* other, int Hand, bool FromRiposte, bool IsStrikethrough, bool IsFromSpell, ExtraAttackOptions *opts, int special) { +bool Bot::Attack(Mob* other, int Hand, bool FromRiposte, bool IsStrikethrough, bool IsFromSpell, ExtraAttackOptions *opts) { if (!other) { SetTarget(nullptr); Log.Out(Logs::General, Logs::Error, "A null Mob object was passed to Bot::Attack for evaluation!"); @@ -3778,25 +3674,7 @@ bool Bot::Attack(Mob* other, int Hand, bool FromRiposte, bool IsStrikethrough, b //if weapon damage > 0 then we know we can hit the target with this weapon //otherwise we cannot and we set the damage to -5 later on if(weapon_damage > 0) { - //Berserker Berserk damage bonus - if(berserk && (GetClass() == BERSERKER)){ - int bonus = (3 + GetLevel() / 10); //unverified - weapon_damage = (weapon_damage * (100 + bonus) / 100); - Log.Out(Logs::Detail, Logs::Combat, "Berserker damage bonus increases DMG to %d", weapon_damage); - } - - //try a finishing blow.. if successful end the attack - if(TryFinishingBlow(other, skillinuse)) - return true; - - //damage formula needs some work - int min_hit = 1; - int max_hit = ((2 * weapon_damage * GetDamageTable(skillinuse)) / 100); - - if(GetLevel() < 10 && max_hit > RuleI(Combat, HitCapPre10)) - max_hit = (RuleI(Combat, HitCapPre10)); - else if(GetLevel() < 20 && max_hit > RuleI(Combat, HitCapPre20)) - max_hit = (RuleI(Combat, HitCapPre20)); + int min_damage = 0; // *************************************************************** // *** Calculate the damage bonus, if applicable, for this hit *** @@ -3814,8 +3692,7 @@ bool Bot::Attack(Mob* other, int Hand, bool FromRiposte, bool IsStrikethrough, b // Damage bonuses apply only to hits from the main hand (Hand == MainPrimary) by characters level 28 and above // who belong to a melee class. If we're here, then all of these conditions apply. ucDamageBonus = GetWeaponDamageBonus(weapon ? weapon->GetItem() : (const EQEmu::ItemData*) nullptr); - min_hit += (int) ucDamageBonus; - max_hit += (int) ucDamageBonus; + min_damage = ucDamageBonus; hate += ucDamageBonus; } #endif @@ -3823,28 +3700,21 @@ bool Bot::Attack(Mob* other, int Hand, bool FromRiposte, bool IsStrikethrough, b if (Hand == EQEmu::inventory::slotSecondary) { if (aabonuses.SecondaryDmgInc || itembonuses.SecondaryDmgInc || spellbonuses.SecondaryDmgInc){ ucDamageBonus = GetWeaponDamageBonus(weapon ? weapon->GetItem() : (const EQEmu::ItemData*) nullptr); - min_hit += (int) ucDamageBonus; - max_hit += (int) ucDamageBonus; + min_damage = ucDamageBonus; hate += ucDamageBonus; } } - min_hit = (min_hit * GetMeleeMinDamageMod_SE(skillinuse) / 100); + int min_cap = (base_damage * GetMeleeMinDamageMod_SE(skillinuse) / 100); - if(max_hit < min_hit) - max_hit = min_hit; + Log.Out(Logs::Detail, Logs::Combat, "Damage calculated to %d (bonus %d, base %d, str %d, skill %d, DMG %d, lv %d)", + damage, min_damage, base_damage, GetSTR(), GetSkill(skillinuse), weapon_damage, mylevel); - if(RuleB(Combat, UseIntervalAC)) - damage = max_hit; - else - damage = zone->random.Int(min_hit, max_hit); - - Log.Out(Logs::Detail, Logs::Combat, "Damage calculated to %d (min %d, max %d, str %d, skill %d, DMG %d, lv %d)", - damage, min_hit, max_hit, GetSTR(), GetSkill(skillinuse), weapon_damage, GetLevel()); + auto offense = this->offense(skillinuse); if(opts) { - damage *= opts->damage_percent; - damage += opts->damage_flat; + base_damage *= opts->damage_percent; + base_damage += opts->damage_flat; hate *= opts->hate_percent; hate += opts->hate_flat; } @@ -3866,10 +3736,11 @@ bool Bot::Attack(Mob* other, int Hand, bool FromRiposte, bool IsStrikethrough, b } } else { if (other->CheckHitChance(this, skillinuse)) { - other->MeleeMitigation(this, damage, min_hit, opts); - ApplyMeleeDamageBonus(skillinuse, damage); - damage += ((itembonuses.HeroicSTR / 10) + (damage * other->GetSkillDmgTaken(skillinuse) / 100) + GetSkillDmgAmt(skillinuse)); - TryCriticalHit(other, skillinuse, damage, opts); + other->MeleeMitigation(this, damage, base_damage, offense, skillinuse, opts); + if (damage > 0) { + ApplyDamageTable(damage, offense); + CommonOutgoingHitSuccess(other, damage, min_damage, min_cap, skillinuse, opts); + } } else { damage = 0; } @@ -3896,39 +3767,7 @@ bool Bot::Attack(Mob* other, int Hand, bool FromRiposte, bool IsStrikethrough, b if (damage > 0) CheckNumHitsRemaining(NumHit::OutgoingHitSuccess); - //break invis when you attack - if(invisible) { - Log.Out(Logs::Detail, Logs::Combat, "Removing invisibility due to melee attack."); - BuffFadeByEffect(SE_Invisibility); - BuffFadeByEffect(SE_Invisibility2); - invisible = false; - } - - if(invisible_undead) { - Log.Out(Logs::Detail, Logs::Combat, "Removing invisibility vs. undead due to melee attack."); - BuffFadeByEffect(SE_InvisVsUndead); - BuffFadeByEffect(SE_InvisVsUndead2); - invisible_undead = false; - } - - if(invisible_animals){ - Log.Out(Logs::Detail, Logs::Combat, "Removing invisibility vs. animals due to melee attack."); - BuffFadeByEffect(SE_InvisVsAnimals); - invisible_animals = false; - } - - if(hidden || improved_hidden){ - hidden = false; - improved_hidden = false; - EQApplicationPacket* outapp = new EQApplicationPacket(OP_SpawnAppearance, sizeof(SpawnAppearance_Struct)); - SpawnAppearance_Struct* sa_out = (SpawnAppearance_Struct*)outapp->pBuffer; - sa_out->spawn_id = GetID(); - sa_out->type = 0x03; - sa_out->parameter = 0; - entity_list.QueueClients(this, outapp, true); - safe_delete(outapp); - } - + CommonBreakInvisibleFromCombat(); if (spellbonuses.NegateIfCombat) BuffFadeByEffect(SE_NegateIfCombat); @@ -4816,21 +4655,24 @@ int Bot::GetHandToHandDamage(void) { return 2; } -bool Bot::TryFinishingBlow(Mob *defender, EQEmu::skills::SkillType skillinuse) { +bool Bot::TryFinishingBlow(Mob *defender, EQEmu::skills::SkillType skillinuse, int &damage) +{ if (!defender) return false; if (aabonuses.FinishingBlow[1] && !defender->IsClient() && defender->GetHPRatio() < 10) { - uint32 chance = (aabonuses.FinishingBlow[0] / 10); - uint32 damage = aabonuses.FinishingBlow[1]; - uint16 levelreq = aabonuses.FinishingBlowLvl[0]; - if(defender->GetLevel() <= levelreq && (chance >= zone->random.Int(0, 1000))){ - Log.Out(Logs::Detail, Logs::Combat, "Landed a finishing blow: levelreq at %d, other level %d", levelreq , defender->GetLevel()); + int chance = (aabonuses.FinishingBlow[0] / 10); + int fb_damage = aabonuses.FinishingBlow[1]; + int levelreq = aabonuses.FinishingBlowLvl[0]; + if (defender->GetLevel() <= levelreq && (chance >= zone->random.Int(0, 1000))) { + Log.Out(Logs::Detail, Logs::Combat, "Landed a finishing blow: levelreq at %d, other level %d", + levelreq, defender->GetLevel()); entity_list.MessageClose_StringID(this, false, 200, MT_CritMelee, FINISHING_BLOW, GetName()); - defender->Damage(this, damage, SPELL_UNKNOWN, skillinuse); + damage = fb_damage; return true; } else { - Log.Out(Logs::Detail, Logs::Combat, "FAILED a finishing blow: levelreq at %d, other level %d", levelreq , defender->GetLevel()); + Log.Out(Logs::Detail, Logs::Combat, "FAILED a finishing blow: levelreq at %d, other level %d", + levelreq, defender->GetLevel()); return false; } } @@ -4858,6 +4700,92 @@ void Bot::DoRiposte(Mob* defender) { } } +int Bot::GetBaseSkillDamage(EQEmu::skills::SkillType skill, Mob *target) +{ + int base = EQEmu::skills::GetBaseDamage(skill); + auto skill_level = GetSkill(skill); + switch (skill) { + case EQEmu::skills::SkillDragonPunch: + case EQEmu::skills::SkillEagleStrike: + case EQEmu::skills::SkillTigerClaw: + if (skill_level >= 25) + base++; + if (skill_level >= 75) + base++; + if (skill_level >= 125) + base++; + if (skill_level >= 175) + base++; + return base; + case EQEmu::skills::SkillFrenzy: + if (GetBotItem(EQEmu::inventory::slotSecondary)) { + if (GetLevel() > 15) + base += GetLevel() - 15; + if (base > 23) + base = 23; + if (GetLevel() > 50) + base += 2; + if (GetLevel() > 54) + base++; + if (GetLevel() > 59) + base++; + } + return base; + case EQEmu::skills::SkillFlyingKick: { + float skill_bonus = skill_level / 9.0f; + float ac_bonus = 0.0f; + auto inst = GetBotItem(EQEmu::inventory::slotFeet); + if (inst) + ac_bonus = inst->GetItemArmorClass(true) / 25.0f; + if (ac_bonus > skill_bonus) + ac_bonus = skill_bonus; + return static_cast(ac_bonus + skill_bonus); + } + case EQEmu::skills::SkillKick: { + float skill_bonus = skill_level / 10.0f; + float ac_bonus = 0.0f; + auto inst = GetBotItem(EQEmu::inventory::slotFeet); + if (inst) + ac_bonus = inst->GetItemArmorClass(true) / 25.0f; + if (ac_bonus > skill_bonus) + ac_bonus = skill_bonus; + return static_cast(ac_bonus + skill_bonus); + } + case EQEmu::skills::SkillBash: { + float skill_bonus = skill_level / 10.0f; + float ac_bonus = 0.0f; + const EQEmu::ItemInstance *inst = nullptr; + if (HasShieldEquiped()) + inst = GetBotItem(EQEmu::inventory::slotSecondary); + else if (HasTwoHanderEquipped()) + inst = GetBotItem(EQEmu::inventory::slotPrimary); + if (inst) + ac_bonus = inst->GetItemArmorClass(true) / 25.0f; + if (ac_bonus > skill_bonus) + ac_bonus = skill_bonus; + return static_cast(ac_bonus + skill_bonus); + } + case EQEmu::skills::SkillBackstab: { + float skill_bonus = static_cast(skill_level) * 0.02f; + auto inst = GetBotItem(EQEmu::inventory::slotPrimary); + if (inst && inst->GetItem() && inst->GetItem()->ItemType == EQEmu::item::ItemType1HPiercing) { + base = inst->GetItemBackstabDamage(true); + if (!inst->GetItemBackstabDamage()) + base += inst->GetItemWeaponDamage(true); + if (target) { + if (inst->GetItemElementalFlag(true) && inst->GetItemElementalDamage(true)) + base += target->ResistElementalWeaponDmg(inst); + if (inst->GetItemBaneDamageBody(true) || inst->GetItemBaneDamageRace(true)) + base += target->CheckBaneDamage(inst); + } + } + return static_cast(static_cast(base) * (skill_bonus + 2.0f)); + } + default: + return 0; + } +} + void Bot::DoSpecialAttackDamage(Mob *who, EQEmu::skills::SkillType skill, int32 max_damage, int32 min_damage, int32 hate_override, int ReuseTime, bool HitChance) { int32 hate = max_damage; if(hate_override > -1) @@ -4877,33 +4805,36 @@ void Bot::DoSpecialAttackDamage(Mob *who, EQEmu::skills::SkillType skill, int32 } } - min_damage += (min_damage * GetMeleeMinDamageMod_SE(skill) / 100); + int min_cap = max_damage * GetMeleeMinDamageMod_SE(skill) / 100; int hand = EQEmu::inventory::slotPrimary; + int damage = 0; + auto offense = this->offense(skill); if (skill == EQEmu::skills::SkillThrowing || skill == EQEmu::skills::SkillArchery) hand = EQEmu::inventory::slotRange; - if (who->AvoidDamage(this, max_damage, hand)) { - if (max_damage == -3) + if (who->AvoidDamage(this, damage, hand)) { + if (damage == -3) DoRiposte(who); } else { if (HitChance || who->CheckHitChance(this, skill)) { - who->MeleeMitigation(this, max_damage, min_damage); - ApplyMeleeDamageBonus(skill, max_damage); - max_damage += who->GetFcDamageAmtIncoming(this, 0, true, skill); - max_damage += ((itembonuses.HeroicSTR / 10) + (max_damage * who->GetSkillDmgTaken(skill) / 100) + GetSkillDmgAmt(skill)); - TryCriticalHit(who, skill, max_damage); + if (max_damage > 0) + who->MeleeMitigation(this, damage, max_damage, offense, skill); + if (damage > 0) { + ApplyDamageTable(damage, offense); + CommonOutgoingHitSuccess(who, damage, min_damage, min_cap, skill); + } } else { - max_damage = 0; + damage = 0; } } who->AddToHateList(this, hate); - who->Damage(this, max_damage, SPELL_UNKNOWN, skill, false); + who->Damage(this, damage, SPELL_UNKNOWN, skill, false); if(!GetTarget() || HasDied()) return; - if (max_damage > 0) + if (damage > 0) CheckNumHitsRemaining(NumHit::OutgoingHitSuccess); //[AA Dragon Punch] value[0] = 100 for 25%, chance value[1] = skill @@ -4919,7 +4850,7 @@ void Bot::DoSpecialAttackDamage(Mob *who, EQEmu::skills::SkillType skill, int32 if (HasSkillProcs()) TrySkillProc(who, skill, (ReuseTime * 1000)); - if (max_damage > 0 && HasSkillProcSuccess()) + if (damage > 0 && HasSkillProcSuccess()) TrySkillProc(who, skill, (ReuseTime * 1000), true); } @@ -4967,72 +4898,33 @@ void Bot::TryBackstab(Mob *other, int ReuseTime) { } } } else if(aabonuses.FrontalBackstabMinDmg || itembonuses.FrontalBackstabMinDmg || spellbonuses.FrontalBackstabMinDmg) { + m_specialattacks = eSpecialAttacks::ChaoticStab; RogueBackstab(other, true); - if (level > 54) { - float DoubleAttackProbability = ((GetSkill(EQEmu::skills::SkillDoubleAttack) + GetLevel()) / 500.0f); - if(zone->random.Real(0, 1) < DoubleAttackProbability) - if(other->GetHP() > 0) - RogueBackstab(other,true, ReuseTime); - - if (tripleChance && other->GetHP() > 0 && tripleChance > zone->random.Int(0, 100)) - RogueBackstab(other,false,ReuseTime); - } + m_specialattacks = eSpecialAttacks::None; } else Attack(other, EQEmu::inventory::slotPrimary); } -void Bot::RogueBackstab(Mob* other, bool min_damage, int ReuseTime) { - int32 ndamage = 0; - int32 max_hit = 0; - int32 min_hit = 0; - int32 hate = 0; - int32 primaryweapondamage = 0; - int32 backstab_dmg = 0; - EQEmu::ItemInstance* botweaponInst = GetBotItem(EQEmu::inventory::slotPrimary); - if(botweaponInst) { - primaryweapondamage = GetWeaponDamage(other, botweaponInst); - backstab_dmg = botweaponInst->GetItem()->BackstabDmg; - for (int i = EQEmu::inventory::socketBegin; i < EQEmu::inventory::SocketCount; ++i) { - EQEmu::ItemInstance *aug = botweaponInst->GetAugment(i); - if(aug) - backstab_dmg += aug->GetItem()->BackstabDmg; - } - } else { - primaryweapondamage = ((GetLevel() / 7) + 1); - backstab_dmg = primaryweapondamage; +void Bot::RogueBackstab(Mob *other, bool min_damage, int ReuseTime) +{ + if (!other) + return; + + EQEmu::ItemInstance *botweaponInst = GetBotItem(EQEmu::inventory::slotPrimary); + if (botweaponInst) { + if (!GetWeaponDamage(other, botweaponInst)) + return; + } else if (!GetWeaponDamage(other, (const EQEmu::ItemData *)nullptr)) { + return; } - if(primaryweapondamage > 0) { - if(level > 25) { - max_hit = (((((2 * backstab_dmg) * GetDamageTable(EQEmu::skills::SkillBackstab) / 100) * 10 * GetSkill(EQEmu::skills::SkillBackstab) / 355) + ((level - 25) / 3) + 1) * ((100 + RuleI(Combat, BackstabBonus)) / 100)); - hate = (20 * backstab_dmg * GetSkill(EQEmu::skills::SkillBackstab) / 355); - } else { - max_hit = (((((2 * backstab_dmg) * GetDamageTable(EQEmu::skills::SkillBackstab) / 100) * 10 * GetSkill(EQEmu::skills::SkillBackstab) / 355) + 1) * ((100 + RuleI(Combat, BackstabBonus)) / 100)); - hate = (20 * backstab_dmg * GetSkill(EQEmu::skills::SkillBackstab) / 355); - } + uint32 hate = 0; - if (level < 51) - min_hit = (level * 15 / 10); - else - min_hit = ((level * ( level * 5 - 105)) / 100); + int base_damage = GetBaseSkillDamage(EQEmu::skills::SkillBackstab, other); + hate = base_damage; - if (!other->CheckHitChance(this, EQEmu::skills::SkillBackstab, 0)) - ndamage = 0; - else { - if (min_damage) { - ndamage = min_hit; - } else { - if (max_hit < min_hit) - max_hit = min_hit; - - ndamage = (RuleB(Combat, UseIntervalAC) ? max_hit : zone->random.Int(min_hit, max_hit)); - } - } - } else - ndamage = -5; - - DoSpecialAttackDamage(other, EQEmu::skills::SkillBackstab, ndamage, min_hit, hate, ReuseTime); + DoSpecialAttackDamage(other, EQEmu::skills::SkillBackstab, base_damage, 0, hate, ReuseTime); DoAnim(anim1HPiercing); } @@ -5157,41 +5049,25 @@ void Bot::DoClassAttacks(Mob *target, bool IsRiposte) { if (!target->CheckHitChance(this, EQEmu::skills::SkillBash, 0)) dmg = 0; else { - if(RuleB(Combat, UseIntervalAC)) - dmg = GetBashDamage(); - else - dmg = zone->random.Int(1, GetBashDamage()); + dmg = GetBaseSkillDamage(EQEmu::skills::SkillBash); } } reuse = (BashReuseTime * 1000); - DoSpecialAttackDamage(target, EQEmu::skills::SkillBash, dmg, 1, -1, reuse); + DoSpecialAttackDamage(target, EQEmu::skills::SkillBash, dmg, 0, -1, reuse); did_attack = true; } } if (skill_to_use == EQEmu::skills::SkillFrenzy) { int AtkRounds = 3; - int skillmod = 0; - if (MaxSkill(EQEmu::skills::SkillFrenzy) > 0) - skillmod = (100 * GetSkill(EQEmu::skills::SkillFrenzy) / MaxSkill(EQEmu::skills::SkillFrenzy)); - - int32 max_dmg = (26 + ((((GetLevel() - 6) * 2) * skillmod) / 100)) * ((100 + RuleI(Combat, FrenzyBonus)) / 100); - int32 min_dmg = 0; + int32 max_dmg = GetBaseSkillDamage(EQEmu::skills::SkillFrenzy); DoAnim(anim2HSlashing); - if (GetLevel() < 51) - min_dmg = 1; - else - min_dmg = (GetLevel() * 8 / 10); - - if (min_dmg > max_dmg) - max_dmg = min_dmg; - reuse = (FrenzyReuseTime * 1000); did_attack = true; while(AtkRounds > 0) { if (GetTarget() && (AtkRounds == 1 || zone->random.Int(0, 100) < 75)) { - DoSpecialAttackDamage(GetTarget(), EQEmu::skills::SkillFrenzy, max_dmg, min_dmg, max_dmg, reuse, true); + DoSpecialAttackDamage(GetTarget(), EQEmu::skills::SkillFrenzy, max_dmg, 0, max_dmg, reuse, true); } AtkRounds--; @@ -5207,14 +5083,11 @@ void Bot::DoClassAttacks(Mob *target, bool IsRiposte) { if (!target->CheckHitChance(this, EQEmu::skills::SkillKick, 0)) dmg = 0; else { - if(RuleB(Combat, UseIntervalAC)) - dmg = GetKickDamage(); - else - dmg = zone->random.Int(1, GetKickDamage()); + dmg = GetBaseSkillDamage(EQEmu::skills::SkillKick); } } reuse = (KickReuseTime * 1000); - DoSpecialAttackDamage(target, EQEmu::skills::SkillKick, dmg, 1, -1, reuse); + DoSpecialAttackDamage(target, EQEmu::skills::SkillKick, dmg, 0, -1, reuse); did_attack = true; } } @@ -5249,26 +5122,6 @@ void Bot::DoClassAttacks(Mob *target, bool IsRiposte) { classattack_timer.Start(reuse / HasteModifier); } -bool Bot::TryHeadShot(Mob* defender, EQEmu::skills::SkillType skillInUse) { - bool Result = false; - if (defender && (defender->GetBodyType() == BT_Humanoid) && (skillInUse == EQEmu::skills::SkillArchery) && (GetClass() == RANGER) && (GetLevel() >= 62)) { - int defenderLevel = defender->GetLevel(); - int rangerLevel = GetLevel(); - if(GetAA(aaHeadshot) && ((defenderLevel - 46) <= GetAA(aaHeadshot) * 2)) { - float AttackerChance = 0.20f + ((float)(rangerLevel - 51) * 0.005f); - float DefenderChance = (float)zone->random.Real(0.00f, 1.00f); - if(AttackerChance > DefenderChance) { - Log.Out(Logs::Detail, Logs::Combat, "Landed a headshot: Attacker chance was %f and Defender chance was %f.", AttackerChance, DefenderChance); - entity_list.MessageClose(this, false, 200, MT_CritMelee, "%s has scored a leathal HEADSHOT!", GetName()); - defender->Damage(this, (defender->GetMaxHP()+50), SPELL_UNKNOWN, skillInUse); - Result = true; - } else - Log.Out(Logs::Detail, Logs::Combat, "FAILED a headshot: Attacker chance was %f and Defender chance was %f.", AttackerChance, DefenderChance); - } - } - return Result; -} - int32 Bot::CheckAggroAmount(uint16 spellid) { int32 AggroAmount = Mob::CheckAggroAmount(spellid, nullptr); int32 focusAggro = GetBotFocusEffect(BotfocusSpellHateMod, spellid); diff --git a/zone/bot.h b/zone/bot.h index 736a464ab..15a1b0938 100644 --- a/zone/bot.h +++ b/zone/bot.h @@ -206,9 +206,9 @@ public: //abstract virtual function implementations requird by base abstract class virtual bool Death(Mob* killerMob, int32 damage, uint16 spell_id, EQEmu::skills::SkillType attack_skill); - virtual void Damage(Mob* from, int32 damage, uint16 spell_id, EQEmu::skills::SkillType attack_skill, bool avoidable = true, int8 buffslot = -1, bool iBuffTic = false, int special = 0); + virtual void Damage(Mob* from, int32 damage, uint16 spell_id, EQEmu::skills::SkillType attack_skill, bool avoidable = true, int8 buffslot = -1, bool iBuffTic = false, eSpecialAttacks special = eSpecialAttacks::None); virtual bool Attack(Mob* other, int Hand = EQEmu::inventory::slotPrimary, bool FromRiposte = false, bool IsStrikethrough = false, bool IsFromSpell = false, - ExtraAttackOptions *opts = nullptr, int special = 0); + ExtraAttackOptions *opts = nullptr); virtual bool HasRaid() { return (GetRaid() ? true : false); } virtual bool HasGroup() { return (GetGroup() ? true : false); } virtual Raid* GetRaid() { return entity_list.GetRaidByMob(this); } @@ -239,7 +239,7 @@ public: uint16 BotGetSpellPriority(int spellslot) { return AIspells[spellslot].priority; } virtual float GetProcChances(float ProcBonus, uint16 hand); virtual int GetHandToHandDamage(void); - virtual bool TryFinishingBlow(Mob *defender, EQEmu::skills::SkillType skillinuse); + virtual bool TryFinishingBlow(Mob *defender, EQEmu::skills::SkillType skillinuse, int &damage); virtual void DoRiposte(Mob* defender); inline virtual int32 GetATK() const { return ATK + itembonuses.ATK + spellbonuses.ATK + ((GetSTR() + GetSkill(EQEmu::skills::SkillOffense)) * 9 / 10); } inline virtual int32 GetATKBonus() const { return itembonuses.ATK + spellbonuses.ATK; } @@ -248,13 +248,12 @@ public: uint16 GetPrimarySkillValue(); uint16 MaxSkill(EQEmu::skills::SkillType skillid, uint16 class_, uint16 level) const; inline uint16 MaxSkill(EQEmu::skills::SkillType skillid) const { return MaxSkill(skillid, GetClass(), GetLevel()); } + virtual int GetBaseSkillDamage(EQEmu::skills::SkillType skill, Mob *target = nullptr); virtual void DoSpecialAttackDamage(Mob *who, EQEmu::skills::SkillType skill, int32 max_damage, int32 min_damage = 1, int32 hate_override = -1, int ReuseTime = 10, bool HitChance = false); virtual void TryBackstab(Mob *other,int ReuseTime = 10); virtual void RogueBackstab(Mob* other, bool min_damage = false, int ReuseTime = 10); virtual void RogueAssassinate(Mob* other); virtual void DoClassAttacks(Mob *target, bool IsRiposte=false); - virtual bool TryHeadShot(Mob* defender, EQEmu::skills::SkillType skillInUse); - virtual void DoMeleeSkillAttackDmg(Mob* other, uint16 weapon_damage, EQEmu::skills::SkillType skillinuse, int16 chance_mod = 0, int16 focus = 0, bool CanRiposte = false, int ReuseTime = 0); virtual void ApplySpecialAttackMod(EQEmu::skills::SkillType skill, int32 &dmg, int32 &mindmg); bool CanDoSpecialAttack(Mob *other); virtual int32 CheckAggroAmount(uint16 spellid); @@ -503,7 +502,6 @@ public: bool GetAltOutOfCombatBehavior() { return _altoutofcombatbehavior;} bool GetShowHelm() { return _showhelm; } - inline virtual int32 GetAC() const { return AC; } inline virtual int32 GetSTR() const { return STR; } inline virtual int32 GetSTA() const { return STA; } inline virtual int32 GetDEX() const { return DEX; } diff --git a/zone/client.cpp b/zone/client.cpp index ddf10d9d5..a066b9b12 100644 --- a/zone/client.cpp +++ b/zone/client.cpp @@ -6871,8 +6871,8 @@ void Client::SendStatsWindow(Client* client, bool use_window) /* DS */ indP << "DS: " << (itembonuses.DamageShield + spellbonuses.DamageShield*-1) << " (Spell: " << (spellbonuses.DamageShield*-1) << " + Item: " << itembonuses.DamageShield << " / " << RuleI(Character, ItemDamageShieldCap) << ")
" << /* Atk */ indP << "ATK: " << GetTotalATK() << "
" << /* Atk2 */ indP << "- Base: " << GetATKRating() << " | Item: " << itembonuses.ATK << " (" << RuleI(Character, ItemATKCap) << ")~Used: " << (itembonuses.ATK * 1.342) << " | Spell: " << spellbonuses.ATK << "
" << - /* AC */ indP << "AC: " << CalcAC() << "
" << - /* AC2 */ indP << "- Mit: " << GetACMit() << " | Avoid: " << GetACAvoid() << " | Spell: " << spellbonuses.AC << " | Shield: " << shield_ac << "
" << + /* AC */ indP << "AC: " << -1 << "
" << + /* AC2 */ indP << "- Mit: " << -1 << " | Avoid: " << -1 << " | Spell: " << spellbonuses.AC << " | Shield: " << shield_ac << "
" << /* Haste */ indP << "Haste: " << GetHaste() << "
" << /* Haste2 */ indP << " - Item: " << itembonuses.haste << " + Spell: " << (spellbonuses.haste + spellbonuses.hastetype2) << " (Cap: " << RuleI(Character, HasteCap) << ") | Over: " << (spellbonuses.hastetype3 + ExtraHaste) << "
" << /* RunSpeed*/ indP << "Runspeed: " << GetRunspeed() << "
" << @@ -6910,7 +6910,7 @@ void Client::SendStatsWindow(Client* client, bool use_window) client->Message(15, "~~~~~ %s %s ~~~~~", GetCleanName(), GetLastName()); client->Message(0, " Level: %i Class: %i Race: %i DS: %i/%i Size: %1.1f Weight: %.1f/%d ", GetLevel(), GetClass(), GetRace(), GetDS(), RuleI(Character, ItemDamageShieldCap), GetSize(), (float)CalcCurrentWeight() / 10.0f, GetSTR()); client->Message(0, " HP: %i/%i HP Regen: %i/%i",GetHP(), GetMaxHP(), CalcHPRegen(), CalcHPRegenCap()); - client->Message(0, " AC: %i ( Mit.: %i + Avoid.: %i + Spell: %i ) | Shield AC: %i", CalcAC(), GetACMit(), GetACAvoid(), spellbonuses.AC, shield_ac); + client->Message(0, " AC: %i ( Mit.: %i + Avoid.: %i + Spell: %i ) | Shield AC: %i", -1, -1, -1, spellbonuses.AC, shield_ac); if(CalcMaxMana() > 0) client->Message(0, " Mana: %i/%i Mana Regen: %i/%i", GetMana(), GetMaxMana(), CalcManaRegen(), CalcManaRegenCap()); client->Message(0, " End.: %i/%i End. Regen: %i/%i",GetEndurance(), GetMaxEndurance(), CalcEnduranceRegen(), CalcEnduranceRegenCap()); diff --git a/zone/client.h b/zone/client.h index 803540ffb..687ff04d6 100644 --- a/zone/client.h +++ b/zone/client.h @@ -221,18 +221,18 @@ public: //abstract virtual function implementations required by base abstract class virtual bool Death(Mob* killerMob, int32 damage, uint16 spell_id, EQEmu::skills::SkillType attack_skill); - virtual void Damage(Mob* from, int32 damage, uint16 spell_id, EQEmu::skills::SkillType attack_skill, bool avoidable = true, int8 buffslot = -1, bool iBuffTic = false, int special = 0); + virtual void Damage(Mob* from, int32 damage, uint16 spell_id, EQEmu::skills::SkillType attack_skill, bool avoidable = true, int8 buffslot = -1, bool iBuffTic = false, eSpecialAttacks special = eSpecialAttacks::None); virtual bool Attack(Mob* other, int Hand = EQEmu::inventory::slotPrimary, bool FromRiposte = false, bool IsStrikethrough = false, bool IsFromSpell = false, - ExtraAttackOptions *opts = nullptr, int special = 0); + ExtraAttackOptions *opts = nullptr); virtual bool HasRaid() { return (GetRaid() ? true : false); } virtual bool HasGroup() { return (GetGroup() ? true : false); } virtual Raid* GetRaid() { return entity_list.GetRaidByClient(this); } virtual Group* GetGroup() { return entity_list.GetGroupByClient(this); } virtual inline bool IsBerserk() { return berserk; } - virtual int32 GetMeleeMitDmg(Mob *attacker, int32 damage, int32 minhit, float mit_rating, float atk_rating); virtual void SetAttackTimer(); int GetQuiverHaste(int delay); void DoAttackRounds(Mob *target, int hand, bool IsFromSpell = false); + int DoDamageCaps(int base_damage); void AI_Init(); void AI_Start(uint32 iMoveDelay = 0); @@ -418,8 +418,6 @@ public: virtual void CalcBonuses(); //these are all precalculated now - inline virtual int32 GetAC() const { return AC; } - inline virtual int32 GetATK() const { return ATK + itembonuses.ATK + spellbonuses.ATK + ((GetSTR() + GetSkill(EQEmu::skills::SkillOffense)) * 9 / 10); } inline virtual int32 GetATKBonus() const { return itembonuses.ATK + spellbonuses.ATK; } inline virtual int GetHaste() const { return Haste; } int GetRawACNoShield(int &shield_ac) const; @@ -1301,9 +1299,6 @@ private: void HandleTraderPriceUpdate(const EQApplicationPacket *app); - int32 CalcAC(); - int32 GetACMit(); - int32 GetACAvoid(); int32 CalcATK(); int32 CalcItemATKCap(); int32 CalcHaste(); diff --git a/zone/client_mods.cpp b/zone/client_mods.cpp index d9914fb26..3d3a1d529 100644 --- a/zone/client_mods.cpp +++ b/zone/client_mods.cpp @@ -1025,111 +1025,6 @@ int32 Client::acmod() return 0; }; -// This is a testing formula for AC, the value this returns should be the same value as the one the client shows... -// ac1 and ac2 are probably the damage migitation and damage avoidance numbers, not sure which is which. -// I forgot to include the iksar defense bonus and i cant find my notes now... -// AC from spells are not included (cant even cast spells yet..) -int32 Client::CalcAC() -{ - // new formula - int avoidance = (acmod() + ((GetSkill(EQEmu::skills::SkillDefense) + itembonuses.HeroicAGI / 10) * 16) / 9); - if (avoidance < 0) { - avoidance = 0; - } - - if (RuleB(Character, EnableAvoidanceCap)) { - if (avoidance > RuleI(Character, AvoidanceCap)) { - avoidance = RuleI(Character, AvoidanceCap); - } - } - - int mitigation = 0; - if (m_pp.class_ == WIZARD || m_pp.class_ == MAGICIAN || m_pp.class_ == NECROMANCER || m_pp.class_ == ENCHANTER) { - //something is wrong with this, naked casters have the wrong natural AC -// mitigation = (spellbonuses.AC/3) + (GetSkill(DEFENSE)/2) + (itembonuses.AC+1); - mitigation = (GetSkill(EQEmu::skills::SkillDefense) + itembonuses.HeroicAGI / 10) / 4 + (itembonuses.AC + 1); - //this might be off by 4.. - mitigation -= 4; - } - else { -// mitigation = (spellbonuses.AC/4) + (GetSkill(DEFENSE)/3) + ((itembonuses.AC*4)/3); - mitigation = (GetSkill(EQEmu::skills::SkillDefense) + itembonuses.HeroicAGI / 10) / 3 + ((itembonuses.AC * 4) / 3); - if (m_pp.class_ == MONK) { - mitigation += GetLevel() * 13 / 10; //the 13/10 might be wrong, but it is close... - } - } - int displayed = 0; - displayed += ((avoidance + mitigation) * 1000) / 847; //natural AC - //Iksar AC, untested - if (GetRace() == IKSAR) { - displayed += 12; - int iksarlevel = GetLevel(); - iksarlevel -= 10; - if (iksarlevel > 25) { - iksarlevel = 25; - } - if (iksarlevel > 0) { - displayed += iksarlevel * 12 / 10; - } - } - // Shield AC bonus for HeroicSTR - if (itembonuses.HeroicSTR) { - bool equiped = CastToClient()->m_inv.GetItem(EQEmu::inventory::slotSecondary); - if (equiped) { - uint8 shield = CastToClient()->m_inv.GetItem(EQEmu::inventory::slotSecondary)->GetItem()->ItemType; - if (shield == EQEmu::item::ItemTypeShield) { - displayed += itembonuses.HeroicSTR / 2; - } - } - } - //spell AC bonuses are added directly to natural total - displayed += spellbonuses.AC; - AC = displayed; - return (AC); -} - -int32 Client::GetACMit() -{ - int mitigation = 0; - if (m_pp.class_ == WIZARD || m_pp.class_ == MAGICIAN || m_pp.class_ == NECROMANCER || m_pp.class_ == ENCHANTER) { - mitigation = (GetSkill(EQEmu::skills::SkillDefense) + itembonuses.HeroicAGI / 10) / 4 + (itembonuses.AC + 1); - mitigation -= 4; - } - else { - mitigation = (GetSkill(EQEmu::skills::SkillDefense) + itembonuses.HeroicAGI / 10) / 3 + ((itembonuses.AC * 4) / 3); - if (m_pp.class_ == MONK) { - mitigation += GetLevel() * 13 / 10; //the 13/10 might be wrong, but it is close... - } - } - // Shield AC bonus for HeroicSTR - if (itembonuses.HeroicSTR) { - bool equiped = CastToClient()->m_inv.GetItem(EQEmu::inventory::slotSecondary); - if (equiped) { - uint8 shield = CastToClient()->m_inv.GetItem(EQEmu::inventory::slotSecondary)->GetItem()->ItemType; - if (shield == EQEmu::item::ItemTypeShield) { - mitigation += itembonuses.HeroicSTR / 2; - } - } - } - return (mitigation * 1000 / 847); -} - -int32 Client::GetACAvoid() -{ - int32 avoidance = (acmod() + ((GetSkill(EQEmu::skills::SkillDefense) + itembonuses.HeroicAGI / 10) * 16) / 9); - if (avoidance < 0) { - avoidance = 0; - } - - if (RuleB(Character, EnableAvoidanceCap)) { - if ((avoidance * 1000 / 847) > RuleI(Character, AvoidanceCap)) { - return RuleI(Character, AvoidanceCap); - } - } - - return (avoidance * 1000 / 847); -} - int32 Client::CalcMaxMana() { switch (GetCasterClass()) { diff --git a/zone/common.h b/zone/common.h index e621c336b..5a6782148 100644 --- a/zone/common.h +++ b/zone/common.h @@ -638,5 +638,11 @@ struct ExtraAttackOptions { }; +struct DamageTable { + int32 max_extra; // max extra damage + int32 chance; // chance not to apply? + int32 minusfactor; // difficulty of rolling +}; + #endif diff --git a/zone/corpse.h b/zone/corpse.h index 49b1cba39..e601f2aa3 100644 --- a/zone/corpse.h +++ b/zone/corpse.h @@ -52,8 +52,8 @@ class Corpse : public Mob { /* Corpse: General */ virtual bool Death(Mob* killerMob, int32 damage, uint16 spell_id, EQEmu::skills::SkillType attack_skill) { return true; } - virtual void Damage(Mob* from, int32 damage, uint16 spell_id, EQEmu::skills::SkillType attack_skill, bool avoidable = true, int8 buffslot = -1, bool iBuffTic = false, int special = 0) { return; } - virtual bool Attack(Mob* other, int Hand = EQEmu::inventory::slotPrimary, bool FromRiposte = false, bool IsStrikethrough = true, bool IsFromSpell = false, ExtraAttackOptions *opts = nullptr, int special = 0) { return false; } + virtual void Damage(Mob* from, int32 damage, uint16 spell_id, EQEmu::skills::SkillType attack_skill, bool avoidable = true, int8 buffslot = -1, bool iBuffTic = false, eSpecialAttacks special = eSpecialAttacks::None) { return; } + virtual bool Attack(Mob* other, int Hand = EQEmu::inventory::slotPrimary, bool FromRiposte = false, bool IsStrikethrough = true, bool IsFromSpell = false, ExtraAttackOptions *opts = nullptr) { return false; } virtual bool HasRaid() { return false; } virtual bool HasGroup() { return false; } virtual Raid* GetRaid() { return 0; } diff --git a/zone/encounter.h b/zone/encounter.h index 8b672fdd4..e0d8dbcb0 100644 --- a/zone/encounter.h +++ b/zone/encounter.h @@ -35,9 +35,9 @@ public: //abstract virtual function implementations required by base abstract class virtual bool Death(Mob* killerMob, int32 damage, uint16 spell_id, EQEmu::skills::SkillType attack_skill) { return true; } - virtual void Damage(Mob* from, int32 damage, uint16 spell_id, EQEmu::skills::SkillType attack_skill, bool avoidable = true, int8 buffslot = -1, bool iBuffTic = false, int special = 0) { return; } + virtual void Damage(Mob* from, int32 damage, uint16 spell_id, EQEmu::skills::SkillType attack_skill, bool avoidable = true, int8 buffslot = -1, bool iBuffTic = false, eSpecialAttacks special = eSpecialAttacks::None) { return; } virtual bool Attack(Mob* other, int Hand = EQEmu::inventory::slotPrimary, bool FromRiposte = false, bool IsStrikethrough = false, bool IsFromSpell = false, - ExtraAttackOptions *opts = nullptr, int special = 0) { + ExtraAttackOptions *opts = nullptr) { return false; } virtual bool HasRaid() { return false; } diff --git a/zone/hate_list.cpp b/zone/hate_list.cpp index b98ca6923..34519b69f 100644 --- a/zone/hate_list.cpp +++ b/zone/hate_list.cpp @@ -553,7 +553,7 @@ int HateList::AreaRampage(Mob *caster, Mob *target, int count, ExtraAttackOption auto mob = entity_list.GetMobID(id); if (mob) { ++hit_count; - caster->ProcessAttackRounds(mob, opts, 1); + caster->ProcessAttackRounds(mob, opts); } } diff --git a/zone/merc.cpp b/zone/merc.cpp index 6dc00836b..6021f05d5 100644 --- a/zone/merc.cpp +++ b/zone/merc.cpp @@ -4432,13 +4432,8 @@ void Merc::DoClassAttacks(Mob *target) { dmg = -5; } else{ - if (target->CheckHitChance(this, EQEmu::skills::SkillKick, 0)) { - if(RuleB(Combat, UseIntervalAC)) - dmg = GetKickDamage(); - else - dmg = zone->random.Int(1, GetKickDamage()); - - } + if (target->CheckHitChance(this, EQEmu::skills::SkillKick, 0)) + dmg = GetBaseSkillDamage(EQEmu::skills::SkillKick, GetTarget()); } reuse = KickReuseTime * 1000; @@ -4454,12 +4449,8 @@ void Merc::DoClassAttacks(Mob *target) { dmg = -5; } else{ - if (target->CheckHitChance(this, EQEmu::skills::SkillBash, 0)) { - if(RuleB(Combat, UseIntervalAC)) - dmg = GetBashDamage(); - else - dmg = zone->random.Int(1, GetBashDamage()); - } + if (target->CheckHitChance(this, EQEmu::skills::SkillBash, 0)) + dmg = GetBaseSkillDamage(EQEmu::skills::SkillBash, GetTarget()); } reuse = BashReuseTime * 1000; @@ -4474,7 +4465,7 @@ void Merc::DoClassAttacks(Mob *target) { classattack_timer.Start(reuse / HasteModifier); } -bool Merc::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, bool IsFromSpell, ExtraAttackOptions *opts, int special) +bool Merc::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, bool IsFromSpell, ExtraAttackOptions *opts) { if (!other) { SetTarget(nullptr); @@ -4485,7 +4476,7 @@ bool Merc::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, boo return NPC::Attack(other, Hand, bRiposte, IsStrikethrough, IsFromSpell, opts); } -void Merc::Damage(Mob* other, int32 damage, uint16 spell_id, EQEmu::skills::SkillType attack_skill, bool avoidable, int8 buffslot, bool iBuffTic, int special) +void Merc::Damage(Mob* other, int32 damage, uint16 spell_id, EQEmu::skills::SkillType attack_skill, bool avoidable, int8 buffslot, bool iBuffTic, eSpecialAttacks special) { if(IsDead() || IsCorpse()) return; diff --git a/zone/merc.h b/zone/merc.h index b563775b0..7bd4c2b9b 100644 --- a/zone/merc.h +++ b/zone/merc.h @@ -65,9 +65,9 @@ public: //abstract virtual function implementations requird by base abstract class virtual bool Death(Mob* killerMob, int32 damage, uint16 spell_id, EQEmu::skills::SkillType attack_skill); - virtual void Damage(Mob* from, int32 damage, uint16 spell_id, EQEmu::skills::SkillType attack_skill, bool avoidable = true, int8 buffslot = -1, bool iBuffTic = false, int special = 0); + virtual void Damage(Mob* from, int32 damage, uint16 spell_id, EQEmu::skills::SkillType attack_skill, bool avoidable = true, int8 buffslot = -1, bool iBuffTic = false, eSpecialAttacks special = eSpecialAttacks::None); virtual bool Attack(Mob* other, int Hand = EQEmu::inventory::slotPrimary, bool FromRiposte = false, bool IsStrikethrough = false, - bool IsFromSpell = false, ExtraAttackOptions *opts = nullptr, int special = 0); + bool IsFromSpell = false, ExtraAttackOptions *opts = nullptr); virtual bool HasRaid() { return false; } virtual bool HasGroup() { return (GetGroup() ? true : false); } virtual Raid* GetRaid() { return 0; } @@ -197,7 +197,6 @@ public: virtual void CalcBonuses(); int32 GetEndurance() const {return cur_end;} //This gets our current endurance inline uint8 GetEndurancePercent() { return (uint8)((float)cur_end / (float)max_end * 100.0f); } - inline virtual int32 GetAC() const { return AC; } inline virtual int32 GetATK() const { return ATK; } inline virtual int32 GetATKBonus() const { return itembonuses.ATK + spellbonuses.ATK; } int32 GetRawACNoShield(int &shield_ac) const; diff --git a/zone/mob.cpp b/zone/mob.cpp index c47788ee5..c5b61f546 100644 --- a/zone/mob.cpp +++ b/zone/mob.cpp @@ -109,7 +109,9 @@ Mob::Mob(const char* in_name, m_TargetV(glm::vec3()), flee_timer(FLEE_CHECK_TIMER), m_Position(position), - tmHidden(-1) + tmHidden(-1), + mitigation_ac(0), + m_specialattacks(eSpecialAttacks::None) { targeted = 0; tar_ndx=0; @@ -4643,7 +4645,7 @@ void Mob::SetRaidGrouped(bool v) } } -int16 Mob::GetCriticalChanceBonus(uint16 skill) +int Mob::GetCriticalChanceBonus(uint16 skill) { int critical_chance = 0; diff --git a/zone/mob.h b/zone/mob.h index a581ea819..802eede5c 100644 --- a/zone/mob.h +++ b/zone/mob.h @@ -54,6 +54,13 @@ namespace EQEmu class ItemInstance; } +enum class eSpecialAttacks : int { + None, + Rampage, + AERampage, + ChaoticStab +}; + class Mob : public Entity { public: enum CLIENT_CONN_STATUS { CLIENT_CONNECTING, CLIENT_CONNECTED, CLIENT_LINKDEAD, @@ -152,45 +159,51 @@ public: float HeadingAngleToMob(Mob *other); // to keep consistent with client generated messages virtual void RangedAttack(Mob* other) { } virtual void ThrowingAttack(Mob* other) { } - uint16 GetThrownDamage(int16 wDmg, int32& TotalDmg, int& minDmg); // 13 = Primary (default), 14 = secondary virtual bool Attack(Mob* other, int Hand = EQEmu::inventory::slotPrimary, bool FromRiposte = false, bool IsStrikethrough = false, - bool IsFromSpell = false, ExtraAttackOptions *opts = nullptr, int special = 0) = 0; + bool IsFromSpell = false, ExtraAttackOptions *opts = nullptr) = 0; int MonkSpecialAttack(Mob* other, uint8 skill_used); virtual void TryBackstab(Mob *other,int ReuseTime = 10); - bool AvoidDamage(Mob* attacker, int32 &damage, int hand); + bool AvoidDamage(Mob* attacker, int &damage, int hand); int compute_tohit(EQEmu::skills::SkillType skillinuse); int compute_defense(); - virtual bool CheckHitChance(Mob* attacker, EQEmu::skills::SkillType skillinuse, int chance_mod = 0); - virtual void TryCriticalHit(Mob *defender, uint16 skill, int32 &damage, ExtraAttackOptions *opts = nullptr); - void TryPetCriticalHit(Mob *defender, uint16 skill, int32 &damage); - virtual bool TryFinishingBlow(Mob *defender, EQEmu::skills::SkillType skillinuse); - uint32 TryHeadShot(Mob* defender, EQEmu::skills::SkillType skillInUse); - uint32 TryAssassinate(Mob* defender, EQEmu::skills::SkillType skillInUse, uint16 ReuseTime); + bool CheckHitChance(Mob* attacker, EQEmu::skills::SkillType skillinuse, int chance_mod = 0); + virtual void TryCriticalHit(Mob *defender, uint16 skill, int &damage, int min_damage, ExtraAttackOptions *opts = nullptr); + void TryPetCriticalHit(Mob *defender, uint16 skill, int &damage); + virtual bool TryFinishingBlow(Mob *defender, EQEmu::skills::SkillType skillinuse, int &damage); + int TryHeadShot(Mob* defender, EQEmu::skills::SkillType skillInUse); + int TryAssassinate(Mob* defender, EQEmu::skills::SkillType skillInUse, uint16 ReuseTime); virtual void DoRiposte(Mob* defender); - void ApplyMeleeDamageBonus(uint16 skill, int32 &damage,ExtraAttackOptions *opts = nullptr); - virtual void MeleeMitigation(Mob *attacker, int32 &damage, int32 minhit, ExtraAttackOptions *opts = nullptr); - virtual int32 GetMeleeMitDmg(Mob *attacker, int32 damage, int32 minhit, float mit_rating, float atk_rating); + void ApplyMeleeDamageBonus(uint16 skill, int &damage,ExtraAttackOptions *opts = nullptr); + int ACSum(); + int offense(EQEmu::skills::SkillType skill); + void CalcAC() { mitigation_ac = ACSum(); } + int GetACSoftcap(); + double GetSoftcapReturns(); + int GetClassRaceACBonus(); + inline int GetMitigationAC() { return mitigation_ac; } + void MeleeMitigation(Mob *attacker, int &damage, int base_damage, int offense, EQEmu::skills::SkillType, ExtraAttackOptions *opts = nullptr); + double RollD20(double offense, double mitigation); // CALL THIS FROM THE DEFENDER bool CombatRange(Mob* other); virtual inline bool IsBerserk() { return false; } // only clients void RogueEvade(Mob *other); - void CommonOutgoingHitSuccess(Mob* defender, int32 &damage, EQEmu::skills::SkillType skillInUse, ExtraAttackOptions *opts = nullptr); + void CommonOutgoingHitSuccess(Mob* defender, int &damage, int min_damage, int min_mod, EQEmu::skills::SkillType skillInUse, ExtraAttackOptions *opts = nullptr); void BreakInvisibleSpells(); virtual void CancelSneakHide(); void CommonBreakInvisible(); void CommonBreakInvisibleFromCombat(); bool HasDied(); virtual bool CheckDualWield(); - void DoMainHandAttackRounds(Mob *target, ExtraAttackOptions *opts = nullptr, int special = 0); - void DoOffHandAttackRounds(Mob *target, ExtraAttackOptions *opts = nullptr, int special = 0); + void DoMainHandAttackRounds(Mob *target, ExtraAttackOptions *opts = nullptr); + void DoOffHandAttackRounds(Mob *target, ExtraAttackOptions *opts = nullptr); virtual bool CheckDoubleAttack(); // inline process for places where we need to do them outside of the AI_Process - void ProcessAttackRounds(Mob *target, ExtraAttackOptions *opts = nullptr, int special = 0) + void ProcessAttackRounds(Mob *target, ExtraAttackOptions *opts = nullptr) { if (target) { - DoMainHandAttackRounds(target, opts, special); + DoMainHandAttackRounds(target, opts); if (CanThisClassDualWield()) - DoOffHandAttackRounds(target, opts, special); + DoOffHandAttackRounds(target, opts); } return; } @@ -367,7 +380,7 @@ public: bool AffectedBySpellExcludingSlot(int slot, int effect); virtual bool Death(Mob* killerMob, int32 damage, uint16 spell_id, EQEmu::skills::SkillType attack_skill) = 0; virtual void Damage(Mob* from, int32 damage, uint16 spell_id, EQEmu::skills::SkillType attack_skill, - bool avoidable = true, int8 buffslot = -1, bool iBuffTic = false, int special = 0) = 0; + bool avoidable = true, int8 buffslot = -1, bool iBuffTic = false, eSpecialAttacks special = eSpecialAttacks::None) = 0; inline virtual void SetHP(int32 hp) { if (hp >= max_hp) cur_hp = max_hp; else cur_hp = hp;} bool ChangeHP(Mob* other, int32 amount, uint16 spell_id = 0, int8 buffslot = -1, bool iBuffTic = false); inline void SetOOCRegen(int32 newoocregen) {oocregen = newoocregen;} @@ -406,7 +419,7 @@ public: virtual void SetTarget(Mob* mob); virtual inline float GetHPRatio() const { return max_hp == 0 ? 0 : ((float)cur_hp/max_hp*100); } virtual inline int GetIntHPRatio() const { return max_hp == 0 ? 0 : static_cast(cur_hp * 100 / max_hp); } - inline virtual int32 GetAC() const { return AC + itembonuses.AC + spellbonuses.AC; } + inline int32 GetAC() const { return AC; } inline virtual int32 GetATK() const { return ATK + itembonuses.ATK + spellbonuses.ATK; } inline virtual int32 GetATKBonus() const { return itembonuses.ATK + spellbonuses.ATK; } inline virtual int32 GetSTR() const { return STR + itembonuses.STR + spellbonuses.STR; } @@ -673,7 +686,7 @@ public: int16 GetMeleeMinDamageMod_SE(uint16 skill); int16 GetCrippBlowChance(); int16 GetSkillReuseTime(uint16 skill); - int16 GetCriticalChanceBonus(uint16 skill); + int GetCriticalChanceBonus(uint16 skill); int16 GetSkillDmgAmt(uint16 skill); bool TryReflectSpell(uint32 spell_id); bool CanBlockSpell() const { return(spellbonuses.BlockNextSpell); } @@ -778,7 +791,8 @@ public: int32 GetMeleeMitigation(); uint8 GetWeaponDamageBonus(const EQEmu::ItemData* weapon, bool offhand = false); - uint16 GetDamageTable(EQEmu::skills::SkillType skillinuse); + const DamageTable &GetDamageTable() const; + void ApplyDamageTable(int &damage, int offense); virtual int GetHandToHandDamage(void); bool CanThisClassDoubleAttack(void) const; @@ -801,9 +815,9 @@ public: int32 AffectMagicalDamage(int32 damage, uint16 spell_id, const bool iBuffTic, Mob* attacker); int32 ReduceAllDamage(int32 damage); - virtual void DoSpecialAttackDamage(Mob *who, EQEmu::skills::SkillType skill, int32 max_damage, int32 min_damage = 1, int32 hate_override = -1, int ReuseTime = 10, bool CheckHitChance = false, bool CanAvoid = true); + void DoSpecialAttackDamage(Mob *who, EQEmu::skills::SkillType skill, int base_damage, int min_damage = 0, int32 hate_override = -1, int ReuseTime = 10, bool CheckHitChance = false, bool CanAvoid = true); virtual void DoThrowingAttackDmg(Mob* other, const EQEmu::ItemInstance* RangeWeapon = nullptr, const EQEmu::ItemData* AmmoItem = nullptr, uint16 weapon_damage = 0, int16 chance_mod = 0, int16 focus = 0, int ReuseTime = 0, uint32 range_id = 0, int AmmoSlot = 0, float speed = 4.0f); - virtual void DoMeleeSkillAttackDmg(Mob* other, uint16 weapon_damage, EQEmu::skills::SkillType skillinuse, int16 chance_mod = 0, int16 focus = 0, bool CanRiposte = false, int ReuseTime = 0); + void DoMeleeSkillAttackDmg(Mob* other, uint16 weapon_damage, EQEmu::skills::SkillType skillinuse, int16 chance_mod = 0, int16 focus = 0, bool CanRiposte = false, int ReuseTime = 0); virtual void DoArcheryAttackDmg(Mob* other, const EQEmu::ItemInstance* RangeWeapon = nullptr, const EQEmu::ItemInstance* Ammo = nullptr, uint16 weapon_damage = 0, int16 chance_mod = 0, int16 focus = 0, int ReuseTime = 0, uint32 range_id = 0, uint32 ammo_id = 0, const EQEmu::ItemData *AmmoItem = nullptr, int AmmoSlot = 0, float speed = 4.0f); bool TryProjectileAttack(Mob* other, const EQEmu::ItemData *item, EQEmu::skills::SkillType skillInUse, uint16 weapon_dmg, const EQEmu::ItemInstance* RangeWeapon, const EQEmu::ItemInstance* Ammo, int AmmoSlot, float speed); void ProjectileAttack(); @@ -816,6 +830,7 @@ public: bool AddRampage(Mob*); void ClearRampage(); void AreaRampage(ExtraAttackOptions *opts); + inline bool IsSpecialAttack(eSpecialAttacks in) { return m_specialattacks == in; } void StartEnrage(); void ProcessEnrage(); @@ -1042,7 +1057,7 @@ public: #endif protected: - void CommonDamage(Mob* other, int32 &damage, const uint16 spell_id, const EQEmu::skills::SkillType attack_skill, bool &avoidable, const int8 buffslot, const bool iBuffTic, int special = 0); + void CommonDamage(Mob* other, int &damage, const uint16 spell_id, const EQEmu::skills::SkillType attack_skill, bool &avoidable, const int8 buffslot, const bool iBuffTic, eSpecialAttacks specal = eSpecialAttacks::None); static uint16 GetProcID(uint16 spell_id, uint8 effect_index); float _GetMovementSpeed(int mod) const; int _GetWalkSpeed() const; @@ -1079,6 +1094,7 @@ protected: bool multitexture; int AC; + int mitigation_ac; // cached Mob::ACSum int32 ATK; int32 STR; int32 STA; @@ -1150,6 +1166,7 @@ protected: int base_walkspeed; int base_fearspeed; int current_speed; + eSpecialAttacks m_specialattacks; uint32 pLastChange; bool held; @@ -1174,9 +1191,10 @@ protected: uint16 GetWeaponSpeedbyHand(uint16 hand); int GetWeaponDamage(Mob *against, const EQEmu::ItemData *weapon_item); int GetWeaponDamage(Mob *against, const EQEmu::ItemInstance *weapon_item, uint32 *hate = nullptr); - int GetKickDamage(); - int GetBashDamage(); - virtual void ApplySpecialAttackMod(EQEmu::skills::SkillType skill, int32 &dmg, int32 &mindmg); +#ifdef BOTS + virtual +#endif + int GetBaseSkillDamage(EQEmu::skills::SkillType skill, Mob *target = nullptr); virtual int16 GetFocusEffect(focusType type, uint16 spell_id) { return 0; } void CalculateNewFearpoint(); float FindGroundZ(float new_x, float new_y, float z_offset=0.0); @@ -1211,7 +1229,7 @@ protected: Timer attack_dw_timer; Timer ranged_timer; float attack_speed; //% increase/decrease in attack speed (not haste) - int8 attack_delay; //delay between attacks in 10ths of seconds + int attack_delay; //delay between attacks in 10ths of seconds int16 slow_mitigation; // Allows for a slow mitigation (100 = 100%, 50% = 50%) Timer tic_timer; Timer mana_timer; diff --git a/zone/mob_ai.cpp b/zone/mob_ai.cpp index 836cd086f..d2340581d 100644 --- a/zone/mob_ai.cpp +++ b/zone/mob_ai.cpp @@ -1160,11 +1160,8 @@ void Mob::AI_Process() { if ((IsPet() || IsTempPet()) && IsPetOwnerClient()){ if (spellbonuses.PC_Pet_Rampage[0] || itembonuses.PC_Pet_Rampage[0] || aabonuses.PC_Pet_Rampage[0]){ int chance = spellbonuses.PC_Pet_Rampage[0] + itembonuses.PC_Pet_Rampage[0] + aabonuses.PC_Pet_Rampage[0]; - int dmg_mod = spellbonuses.PC_Pet_Rampage[1] + itembonuses.PC_Pet_Rampage[1] + aabonuses.PC_Pet_Rampage[1]; if(zone->random.Roll(chance)) { - ExtraAttackOptions opts; - opts.damage_percent = dmg_mod / 100.0f; - Rampage(&opts); + Rampage(nullptr); } } } @@ -1175,12 +1172,7 @@ void Mob::AI_Process() { rampage_chance = rampage_chance > 0 ? rampage_chance : 20; if(zone->random.Roll(rampage_chance)) { ExtraAttackOptions opts; - int cur = GetSpecialAbilityParam(SPECATK_RAMPAGE, 2); - if(cur > 0) { - opts.damage_percent = cur / 100.0f; - } - - cur = GetSpecialAbilityParam(SPECATK_RAMPAGE, 3); + int cur = GetSpecialAbilityParam(SPECATK_RAMPAGE, 3); if(cur > 0) { opts.damage_flat = cur; } @@ -1215,12 +1207,7 @@ void Mob::AI_Process() { rampage_chance = rampage_chance > 0 ? rampage_chance : 20; if(zone->random.Roll(rampage_chance)) { ExtraAttackOptions opts; - int cur = GetSpecialAbilityParam(SPECATK_AREA_RAMPAGE, 2); - if(cur > 0) { - opts.damage_percent = cur / 100.0f; - } - - cur = GetSpecialAbilityParam(SPECATK_AREA_RAMPAGE, 3); + int cur = GetSpecialAbilityParam(SPECATK_AREA_RAMPAGE, 3); if(cur > 0) { opts.damage_flat = cur; } @@ -1992,6 +1979,8 @@ bool Mob::Rampage(ExtraAttackOptions *opts) rampage_targets = RuleI(Combat, DefaultRampageTargets); if (rampage_targets > RuleI(Combat, MaxRampageTargets)) rampage_targets = RuleI(Combat, MaxRampageTargets); + + m_specialattacks = eSpecialAttacks::Rampage; for (int i = 0; i < RampageArray.size(); i++) { if (index_hit >= rampage_targets) break; @@ -2001,14 +1990,16 @@ bool Mob::Rampage(ExtraAttackOptions *opts) if (m_target == GetTarget()) continue; if (CombatRange(m_target)) { - ProcessAttackRounds(m_target, opts, 2); + ProcessAttackRounds(m_target, opts); index_hit++; } } } if (RuleB(Combat, RampageHitsTarget) && index_hit < rampage_targets) - ProcessAttackRounds(GetTarget(), opts, 2); + ProcessAttackRounds(GetTarget(), opts); + + m_specialattacks = eSpecialAttacks::None; return true; } @@ -2024,10 +2015,12 @@ void Mob::AreaRampage(ExtraAttackOptions *opts) int rampage_targets = GetSpecialAbilityParam(SPECATK_AREA_RAMPAGE, 1); rampage_targets = rampage_targets > 0 ? rampage_targets : -1; + m_specialattacks = eSpecialAttacks::AERampage; index_hit = hate_list.AreaRampage(this, GetTarget(), rampage_targets, opts); if(index_hit == 0) - ProcessAttackRounds(GetTarget(), opts, 1); + ProcessAttackRounds(GetTarget(), opts); + m_specialattacks = eSpecialAttacks::None; } uint32 Mob::GetLevelCon(uint8 mylevel, uint8 iOtherLevel) { diff --git a/zone/npc.cpp b/zone/npc.cpp index d1255f8aa..ebff4786a 100644 --- a/zone/npc.cpp +++ b/zone/npc.cpp @@ -201,6 +201,9 @@ NPC::NPC(const NPCType* d, Spawn2* in_respawn, const glm::vec4& position, int if CalcNPCDamage(); } + base_damage = round((max_dmg - min_dmg) / 1.9); + min_damage = min_dmg - round(base_damage / 10.0); + accuracy_rating = d->accuracy_rating; avoidance_rating = d->avoidance_rating; ATK = d->ATK; @@ -1054,7 +1057,7 @@ NPC* NPC::SpawnNPC(const char* spawncommand, const glm::vec4& position, Client* npc_type->WIS = 150; npc_type->CHA = 150; - npc_type->attack_delay = 30; + npc_type->attack_delay = 3000; npc_type->prim_melee_type = 28; npc_type->sec_melee_type = 28; @@ -1984,13 +1987,25 @@ void NPC::ModifyNPCStat(const char *identifier, const char *newValue) else if(id == "special_attacks") { NPCSpecialAttacks(val.c_str(), 0, 1); return; } else if(id == "special_abilities") { ProcessSpecialAbilities(val.c_str()); return; } else if(id == "attack_speed") { attack_speed = (float)atof(val.c_str()); CalcBonuses(); return; } - else if(id == "attack_delay") { attack_delay = atoi(val.c_str()); CalcBonuses(); return; } + else if(id == "attack_delay") { /* TODO: fix DB */attack_delay = atoi(val.c_str()) * 100; CalcBonuses(); return; } else if(id == "atk") { ATK = atoi(val.c_str()); return; } else if(id == "accuracy") { accuracy_rating = atoi(val.c_str()); return; } else if(id == "avoidance") { avoidance_rating = atoi(val.c_str()); return; } else if(id == "trackable") { trackable = atoi(val.c_str()); return; } - else if(id == "min_hit") { min_dmg = atoi(val.c_str()); return; } - else if(id == "max_hit") { max_dmg = atoi(val.c_str()); return; } + else if(id == "min_hit") { + min_dmg = atoi(val.c_str()); + // TODO: fix DB + base_damage = round((max_dmg - min_dmg) / 1.9); + min_damage = min_dmg - round(base_damage / 10.0); + return; + } + else if(id == "max_hit") { + max_dmg = atoi(val.c_str()); + // TODO: fix DB + base_damage = round((max_dmg - min_dmg) / 1.9); + min_damage = min_dmg - round(base_damage / 10.0); + return; + } else if(id == "attack_count") { attack_count = atoi(val.c_str()); return; } else if(id == "see_invis") { see_invis = atoi(val.c_str()); return; } else if(id == "see_invis_undead") { see_invis_undead = atoi(val.c_str()); return; } diff --git a/zone/npc.h b/zone/npc.h index 4b81d45a6..543725c28 100644 --- a/zone/npc.h +++ b/zone/npc.h @@ -109,9 +109,9 @@ public: //abstract virtual function implementations requird by base abstract class virtual bool Death(Mob* killerMob, int32 damage, uint16 spell_id, EQEmu::skills::SkillType attack_skill); - virtual void Damage(Mob* from, int32 damage, uint16 spell_id, EQEmu::skills::SkillType attack_skill, bool avoidable = true, int8 buffslot = -1, bool iBuffTic = false, int special = 0); + virtual void Damage(Mob* from, int32 damage, uint16 spell_id, EQEmu::skills::SkillType attack_skill, bool avoidable = true, int8 buffslot = -1, bool iBuffTic = false, eSpecialAttacks special = eSpecialAttacks::None); virtual bool Attack(Mob* other, int Hand = EQEmu::inventory::slotPrimary, bool FromRiposte = false, bool IsStrikethrough = false, - bool IsFromSpell = false, ExtraAttackOptions *opts = nullptr, int special = 0); + bool IsFromSpell = false, ExtraAttackOptions *opts = nullptr); virtual bool HasRaid() { return false; } virtual bool HasGroup() { return false; } virtual Raid* GetRaid() { return 0; } @@ -259,9 +259,11 @@ public: uint32 GetMaxDMG() const {return max_dmg;} uint32 GetMinDMG() const {return min_dmg;} + int GetBaseDamage() const { return base_damage; } + int GetMinDamage() const { return min_damage; } float GetSlowMitigation() const { return slow_mitigation; } float GetAttackSpeed() const {return attack_speed;} - uint8 GetAttackDelay() const {return attack_delay;} + int GetAttackDelay() const {return attack_delay;} bool IsAnimal() const { return(bodytype == BT_Animal); } uint16 GetPetSpellID() const {return pet_spell_id;} void SetPetSpellID(uint16 amt) {pet_spell_id = amt;} @@ -463,6 +465,8 @@ protected: uint32 max_dmg; uint32 min_dmg; + int base_damage; + int min_damage; int32 accuracy_rating; int32 avoidance_rating; int16 attack_count; diff --git a/zone/special_attacks.cpp b/zone/special_attacks.cpp index 86eb01921..03e4a7d90 100644 --- a/zone/special_attacks.cpp +++ b/zone/special_attacks.cpp @@ -26,96 +26,138 @@ #include - -int Mob::GetKickDamage() { - int multiple=(GetLevel()*100/5); - multiple += 100; - int32 dmg = (((GetSkill(EQEmu::skills::SkillKick) + GetSTR() + GetLevel()) * 100 / 9000) * multiple) + 600; //Set a base of 6 damage, 1 seemed too low at the sub level 30 level. - if(GetClass() == WARRIOR || GetClass() == WARRIORGM ||GetClass() == BERSERKER || GetClass() == BERSERKERGM) { - dmg*=12/10;//small increase for warriors +int Mob::GetBaseSkillDamage(EQEmu::skills::SkillType skill, Mob *target) +{ + int base = EQEmu::skills::GetBaseDamage(skill); + auto skill_level = GetSkill(skill); + switch (skill) { + case EQEmu::skills::SkillDragonPunch: + case EQEmu::skills::SkillEagleStrike: + case EQEmu::skills::SkillTigerClaw: + if (skill_level >= 25) + base++; + if (skill_level >= 75) + base++; + if (skill_level >= 125) + base++; + if (skill_level >= 175) + base++; + return base; + case EQEmu::skills::SkillFrenzy: + if (IsClient() && CastToClient()->GetInv().GetItem(EQEmu::inventory::slotPrimary)) { + if (GetLevel() > 15) + base += GetLevel() - 15; + if (base > 23) + base = 23; + if (GetLevel() > 50) + base += 2; + if (GetLevel() > 54) + base++; + if (GetLevel() > 59) + base++; + } + return base; + case EQEmu::skills::SkillFlyingKick: { + float skill_bonus = skill_level / 9.0f; + float ac_bonus = 0.0f; + if (IsClient()) { + auto inst = CastToClient()->GetInv().GetItem(EQEmu::inventory::slotFeet); + if (inst) + ac_bonus = inst->GetItemArmorClass(true) / 25.0f; + } + if (ac_bonus > skill_bonus) + ac_bonus = skill_bonus; + return static_cast(ac_bonus + skill_bonus); } - dmg /= 100; - - int32 mindmg = 1; - ApplySpecialAttackMod(EQEmu::skills::SkillKick, dmg, mindmg); - - dmg = mod_kick_damage(dmg); - - return(dmg); -} - -int Mob::GetBashDamage() { - int multiple=(GetLevel()*100/5); - multiple += 100; - - //this is complete shite - int32 dmg = ((((GetSkill(EQEmu::skills::SkillBash) + GetSTR()) * 100 + GetLevel() * 100 / 2) / 10000) * multiple) + 600; //Set a base of 6 damage, 1 seemed too low at the sub level 30 level. - dmg /= 100; - - int32 mindmg = 1; - ApplySpecialAttackMod(EQEmu::skills::SkillBash, dmg, mindmg); - - dmg = mod_bash_damage(dmg); - - return(dmg); -} - -void Mob::ApplySpecialAttackMod(EQEmu::skills::SkillType skill, int32 &dmg, int32 &mindmg) { - int item_slot = -1; - //1: Apply bonus from AC (BOOT/SHIELD/HANDS) est. 40AC=6dmg - if (IsClient()){ - switch (skill) { - case EQEmu::skills::SkillFlyingKick: - case EQEmu::skills::SkillRoundKick: - case EQEmu::skills::SkillKick: - item_slot = EQEmu::inventory::slotFeet; - break; - case EQEmu::skills::SkillBash: - item_slot = EQEmu::inventory::slotSecondary; - break; - case EQEmu::skills::SkillDragonPunch: - case EQEmu::skills::SkillEagleStrike: - case EQEmu::skills::SkillTigerClaw: - item_slot = EQEmu::inventory::slotHands; - break; - default: - break; + case EQEmu::skills::SkillKick: { + // there is some base *= 4 case in here? + float skill_bonus = skill_level / 10.0f; + float ac_bonus = 0.0f; + if (IsClient()) { + auto inst = CastToClient()->GetInv().GetItem(EQEmu::inventory::slotFeet); + if (inst) + ac_bonus = inst->GetItemArmorClass(true) / 25.0f; } - - if (item_slot >= 0){ - const EQEmu::ItemInstance* itm = nullptr; - itm = CastToClient()->GetInv().GetItem(item_slot); - if(itm) - dmg += itm->GetItem()->AC * (RuleI(Combat, SpecialAttackACBonus))/100; + if (ac_bonus > skill_bonus) + ac_bonus = skill_bonus; + return static_cast(ac_bonus + skill_bonus); + } + case EQEmu::skills::SkillBash: { + float skill_bonus = skill_level / 10.0f; + float ac_bonus = 0.0f; + const EQEmu::ItemInstance *inst = nullptr; + if (IsClient()) { + if (HasShieldEquiped()) + inst = CastToClient()->GetInv().GetItem(EQEmu::inventory::slotSecondary); + else if (HasTwoHanderEquipped()) + inst = CastToClient()->GetInv().GetItem(EQEmu::inventory::slotPrimary); } + if (inst) + ac_bonus = inst->GetItemArmorClass(true) / 25.0f; + if (ac_bonus > skill_bonus) + ac_bonus = skill_bonus; + return static_cast(ac_bonus + skill_bonus); + } + case EQEmu::skills::SkillBackstab: { + float skill_bonus = static_cast(skill_level) * 0.02f; + base = 3; // There seems to be a base 3 for NPCs or some how BS w/o weapon? + // until we get a better inv system for NPCs they get nerfed! + if (IsClient()) { + auto *inst = CastToClient()->GetInv().GetItem(EQEmu::inventory::slotPrimary); + if (inst && inst->GetItem() && inst->GetItem()->ItemType == EQEmu::item::ItemType1HPiercing) { + base = inst->GetItemBackstabDamage(true); + if (!inst->GetItemBackstabDamage()) + base += inst->GetItemWeaponDamage(true); + if (target) { + if (inst->GetItemElementalFlag(true) && inst->GetItemElementalDamage(true)) + base += target->ResistElementalWeaponDmg(inst); + if (inst->GetItemBaneDamageBody(true) || inst->GetItemBaneDamageRace(true)) + base += target->CheckBaneDamage(inst); + } + } + } else if (IsNPC()) { + auto *npc = CastToNPC(); + base = std::max(base, npc->GetBaseDamage()); + // parses show relatively low BS mods from lots of NPCs, so either their BS skill is super low + // or their mod is divided again, this is probably not the right mod, but it's better + skill_bonus /= 3.0f; + } + // ahh lets make sure everything is casted right :P ugly but w/e + return static_cast(static_cast(base) * (skill_bonus + 2.0f)); + } + default: + return 0; } } -void Mob::DoSpecialAttackDamage(Mob *who, EQEmu::skills::SkillType skill, int32 max_damage, int32 min_damage, int32 hate_override, int ReuseTime, - bool CheckHitChance, bool CanAvoid) { - //this really should go through the same code as normal melee damage to - //pick up all the special behavior there +void Mob::DoSpecialAttackDamage(Mob *who, EQEmu::skills::SkillType skill, int32 base_damage, int32 min_damage, + int32 hate_override, int ReuseTime, bool CheckHitChance, bool CanAvoid) +{ + // this really should go through the same code as normal melee damage to + // pick up all the special behavior there - if ((who == nullptr || ((IsClient() && CastToClient()->dead) || (who->IsClient() && who->CastToClient()->dead)) || HasDied() || (!IsAttackAllowed(who)))) + if ((who == nullptr || + ((IsClient() && CastToClient()->dead) || (who->IsClient() && who->CastToClient()->dead)) || HasDied() || + (!IsAttackAllowed(who)))) return; - - if(who->GetInvul() || who->GetSpecialAbility(IMMUNE_MELEE)) - max_damage = -5; + + int damage = 0; + + if (who->GetInvul() || who->GetSpecialAbility(IMMUNE_MELEE)) + damage = -5; if (who->GetSpecialAbility(IMMUNE_MELEE_EXCEPT_BANE) && skill != EQEmu::skills::SkillBackstab) - max_damage = -5; + damage = -5; - uint32 hate = max_damage; - if(hate_override > -1) + uint32 hate = base_damage; + if (hate_override > -1) hate = hate_override; - if (skill == EQEmu::skills::SkillBash){ - if(IsClient()){ + if (skill == EQEmu::skills::SkillBash) { + if (IsClient()) { EQEmu::ItemInstance *item = CastToClient()->GetInv().GetItem(EQEmu::inventory::slotSecondary); - if(item) - { - if (item->GetItem()->ItemType == EQEmu::item::ItemTypeShield) - { + if (item) { + if (item->GetItem()->ItemType == EQEmu::item::ItemTypeShield) { hate += item->GetItem()->AC; } const EQEmu::ItemData *itm = item->GetItem(); @@ -127,46 +169,54 @@ void Mob::DoSpecialAttackDamage(Mob *who, EQEmu::skills::SkillType skill, int32 } } - min_damage += min_damage * GetMeleeMinDamageMod_SE(skill) / 100; + int min_cap = base_damage * GetMeleeMinDamageMod_SE(skill) / 100; - int hand = EQEmu::inventory::slotPrimary; // Avoid checks hand for throwing/archery exclusion, primary should work for most + auto offense = this->offense(skill); + + int hand = EQEmu::inventory::slotPrimary; // Avoid checks hand for throwing/archery exclusion, primary should + // work for most if (skill == EQEmu::skills::SkillThrowing || skill == EQEmu::skills::SkillArchery) hand = EQEmu::inventory::slotRange; - if (who->AvoidDamage(this, max_damage, hand)) { - if (max_damage == -3) + if (who->AvoidDamage(this, damage, hand)) { + if (damage == -3) DoRiposte(who); } else { - if (!CheckHitChance || (CheckHitChance && who->CheckHitChance(this, skill))) { - who->MeleeMitigation(this, max_damage, min_damage); - CommonOutgoingHitSuccess(who, max_damage, skill); + if (!CheckHitChance || who->CheckHitChance(this, skill)) { + if (base_damage > 0) // we do this weird, so we have to check it first :( + who->MeleeMitigation(this, damage, base_damage, offense, skill); + if (damage > 0) { + ApplyDamageTable(damage, offense); + CommonOutgoingHitSuccess(who, damage, min_damage, min_cap, skill); + } } else { - max_damage = 0; + damage = 0; } } who->AddToHateList(this, hate, 0, false); - if (max_damage > 0 && aabonuses.SkillAttackProc[0] && aabonuses.SkillAttackProc[1] == skill && + if (damage > 0 && aabonuses.SkillAttackProc[0] && aabonuses.SkillAttackProc[1] == skill && IsValidSpell(aabonuses.SkillAttackProc[2])) { float chance = aabonuses.SkillAttackProc[0] / 1000.0f; if (zone->random.Roll(chance)) SpellFinished(aabonuses.SkillAttackProc[2], who, EQEmu::CastingSlot::Item, 0, -1, spells[aabonuses.SkillAttackProc[2]].ResistDiff); } - who->Damage(this, max_damage, SPELL_UNKNOWN, skill, false); + who->Damage(this, damage, SPELL_UNKNOWN, skill, false); - //Make sure 'this' has not killed the target and 'this' is not dead (Damage shield ect). - if(!GetTarget())return; - if (HasDied()) return; + // Make sure 'this' has not killed the target and 'this' is not dead (Damage shield ect). + if (!GetTarget()) + return; + if (HasDied()) + return; if (HasSkillProcs()) - TrySkillProc(who, skill, ReuseTime*1000); - - if (max_damage > 0 && HasSkillProcSuccess()) - TrySkillProc(who, skill, ReuseTime*1000, true); + TrySkillProc(who, skill, ReuseTime * 1000); + if (damage > 0 && HasSkillProcSuccess()) + TrySkillProc(who, skill, ReuseTime * 1000, true); } - +// We should probably refactor this to take the struct not the packet void Client::OPCombatAbility(const EQApplicationPacket *app) { if(!GetTarget()) return; @@ -251,20 +301,16 @@ void Client::OPCombatAbility(const EQApplicationPacket *app) { else{ if (!GetTarget()->CheckHitChance(this, EQEmu::skills::SkillBash, 0)) { dmg = 0; - ht = GetBashDamage(); + ht = GetBaseSkillDamage(EQEmu::skills::SkillBash, GetTarget()); } else{ - if(RuleB(Combat, UseIntervalAC)) - ht = dmg = GetBashDamage(); - else - ht = dmg = zone->random.Int(1, GetBashDamage()); - + ht = dmg = GetBaseSkillDamage(EQEmu::skills::SkillBash, GetTarget()); } } ReuseTime = BashReuseTime-1-skill_reduction; ReuseTime = (ReuseTime*HasteMod)/100; - DoSpecialAttackDamage(GetTarget(), EQEmu::skills::SkillBash, dmg, 1, ht, ReuseTime); + DoSpecialAttackDamage(GetTarget(), EQEmu::skills::SkillBash, dmg, 0, ht, ReuseTime); if(ReuseTime > 0) { p_timers.Start(timer, ReuseTime); @@ -276,28 +322,18 @@ void Client::OPCombatAbility(const EQApplicationPacket *app) { if ((ca_atk->m_atk == 100) && (ca_atk->m_skill == EQEmu::skills::SkillFrenzy)){ CheckIncreaseSkill(EQEmu::skills::SkillFrenzy, GetTarget(), 10); int AtkRounds = 3; - int skillmod = 100 * GetSkill(EQEmu::skills::SkillFrenzy) / MaxSkill(EQEmu::skills::SkillFrenzy); - int32 max_dmg = (26 + ((((GetLevel()-6) * 2)*skillmod)/100)) * ((100+RuleI(Combat, FrenzyBonus))/100); - int32 min_dmg = 0; + int32 max_dmg = GetBaseSkillDamage(EQEmu::skills::SkillFrenzy, GetTarget()); DoAnim(anim2HSlashing); max_dmg = mod_frenzy_damage(max_dmg); - if (GetLevel() < 51) - min_dmg = 1; - else - min_dmg = GetLevel()*8/10; - - if (min_dmg > max_dmg) - max_dmg = min_dmg; - ReuseTime = FrenzyReuseTime-1-skill_reduction; ReuseTime = (ReuseTime*HasteMod)/100; //Live parses show around 55% Triple 35% Double 10% Single, you will always get first hit. while(AtkRounds > 0) { if (GetTarget() && (AtkRounds == 1 || zone->random.Roll(75))) { - DoSpecialAttackDamage(GetTarget(), EQEmu::skills::SkillFrenzy, max_dmg, min_dmg, max_dmg, ReuseTime, true); + DoSpecialAttackDamage(GetTarget(), EQEmu::skills::SkillFrenzy, max_dmg, 0, max_dmg, ReuseTime, true); } AtkRounds--; } @@ -327,18 +363,15 @@ void Client::OPCombatAbility(const EQApplicationPacket *app) { else{ if (!GetTarget()->CheckHitChance(this, EQEmu::skills::SkillKick, 0)) { dmg = 0; - ht = GetKickDamage(); + ht = GetBaseSkillDamage(EQEmu::skills::SkillKick, GetTarget()); } else{ - if(RuleB(Combat, UseIntervalAC)) - ht = dmg = GetKickDamage(); - else - ht = dmg = zone->random.Int(1, GetKickDamage()); + ht = dmg = GetBaseSkillDamage(EQEmu::skills::SkillKick, GetTarget()); } } ReuseTime = KickReuseTime-1-skill_reduction; - DoSpecialAttackDamage(GetTarget(), EQEmu::skills::SkillKick, dmg, 1, ht, ReuseTime); + DoSpecialAttackDamage(GetTarget(), EQEmu::skills::SkillKick, dmg, 0, ht, ReuseTime); } break; @@ -395,99 +428,91 @@ void Client::OPCombatAbility(const EQApplicationPacket *app) { } //returns the reuse time in sec for the special attack used. -int Mob::MonkSpecialAttack(Mob* other, uint8 unchecked_type) +int Mob::MonkSpecialAttack(Mob *other, uint8 unchecked_type) { - if(!other) + if (!other) return 0; int32 ndamage = 0; int32 max_dmg = 0; - int32 min_dmg = 1; + int32 min_dmg = 0; int reuse = 0; - EQEmu::skills::SkillType skill_type; //to avoid casting... even though it "would work" + EQEmu::skills::SkillType skill_type; // to avoid casting... even though it "would work" uint8 itemslot = EQEmu::inventory::slotFeet; + if (IsNPC()) { + auto *npc = CastToNPC(); + min_dmg = npc->GetMinDamage(); + } - switch(unchecked_type) { + switch (unchecked_type) { case EQEmu::skills::SkillFlyingKick: skill_type = EQEmu::skills::SkillFlyingKick; - max_dmg = ((GetSTR()+GetSkill(skill_type)) * RuleI(Combat, FlyingKickBonus) / 100) + 35; - min_dmg = ((level*8)/10); - ApplySpecialAttackMod(skill_type, max_dmg, min_dmg); + max_dmg = GetBaseSkillDamage(skill_type); + min_dmg = 0; // revamped FK formula is missing the min mod? DoAnim(animFlyingKick); reuse = FlyingKickReuseTime; break; case EQEmu::skills::SkillDragonPunch: skill_type = EQEmu::skills::SkillDragonPunch; - max_dmg = ((GetSTR()+GetSkill(skill_type)) * RuleI(Combat, DragonPunchBonus) / 100) + 26; + max_dmg = GetBaseSkillDamage(skill_type); itemslot = EQEmu::inventory::slotHands; - ApplySpecialAttackMod(skill_type, max_dmg, min_dmg); DoAnim(animTailRake); reuse = TailRakeReuseTime; break; case EQEmu::skills::SkillEagleStrike: skill_type = EQEmu::skills::SkillEagleStrike; - max_dmg = ((GetSTR()+GetSkill(skill_type)) * RuleI(Combat, EagleStrikeBonus) / 100) + 19; + max_dmg = GetBaseSkillDamage(skill_type); itemslot = EQEmu::inventory::slotHands; - ApplySpecialAttackMod(skill_type, max_dmg, min_dmg); DoAnim(animEagleStrike); reuse = EagleStrikeReuseTime; break; case EQEmu::skills::SkillTigerClaw: skill_type = EQEmu::skills::SkillTigerClaw; - max_dmg = ((GetSTR()+GetSkill(skill_type)) * RuleI(Combat, TigerClawBonus) / 100) + 12; + max_dmg = GetBaseSkillDamage(skill_type); itemslot = EQEmu::inventory::slotHands; - ApplySpecialAttackMod(skill_type, max_dmg, min_dmg); DoAnim(animTigerClaw); reuse = TigerClawReuseTime; break; case EQEmu::skills::SkillRoundKick: skill_type = EQEmu::skills::SkillRoundKick; - max_dmg = ((GetSTR()+GetSkill(skill_type)) * RuleI(Combat, RoundKickBonus) / 100) + 10; - ApplySpecialAttackMod(skill_type, max_dmg, min_dmg); + max_dmg = GetBaseSkillDamage(skill_type); DoAnim(animRoundKick); reuse = RoundKickReuseTime; break; case EQEmu::skills::SkillKick: skill_type = EQEmu::skills::SkillKick; - max_dmg = GetKickDamage(); + max_dmg = GetBaseSkillDamage(skill_type); DoAnim(animKick); reuse = KickReuseTime; break; default: Log.Out(Logs::Detail, Logs::Attack, "Invalid special attack type %d attempted", unchecked_type); - return(1000); /* nice long delay for them, the caller depends on this! */ + return (1000); /* nice long delay for them, the caller depends on this! */ } - if(IsClient()){ - if(GetWeaponDamage(other, CastToClient()->GetInv().GetItem(itemslot)) <= 0){ + if (IsClient()) { + if (GetWeaponDamage(other, CastToClient()->GetInv().GetItem(itemslot)) <= 0) { ndamage = -5; } - } - else{ - if (GetWeaponDamage(other, (const EQEmu::ItemData*)nullptr) <= 0){ + } else { + if (GetWeaponDamage(other, (const EQEmu::ItemData *)nullptr) <= 0) { ndamage = -5; } } int32 ht = 0; - if(ndamage == 0){ - if(other->CheckHitChance(this, skill_type, 0)){ - if(RuleB(Combat, UseIntervalAC)) - ht = ndamage = max_dmg; - else - ht = ndamage = zone->random.Int(min_dmg, max_dmg); - } - else{ - ht = max_dmg; - } + if (ndamage == 0) { + ht = max_dmg; + if (other->CheckHitChance(this, skill_type, 0)) + ndamage = max_dmg; } - //This can potentially stack with changes to kick damage + // This can potentially stack with changes to kick damage ht = ndamage = mod_monk_special_damage(ndamage, skill_type); DoSpecialAttackDamage(other, skill_type, ndamage, min_dmg, ht, reuse); - return(reuse); + return (reuse); } void Mob::TryBackstab(Mob *other, int ReuseTime) { @@ -542,22 +567,15 @@ void Mob::TryBackstab(Mob *other, int ReuseTime) { } //Live AA - Chaotic Backstab else if(aabonuses.FrontalBackstabMinDmg || itembonuses.FrontalBackstabMinDmg || spellbonuses.FrontalBackstabMinDmg) { + m_specialattacks = eSpecialAttacks::ChaoticStab; //we can stab from any angle, we do min damage though. + // chaotic backstab can't double etc Seized can, but that's because it's a chance to do normal BS + // Live actually added SPA 473 which grants chance to double here when they revamped chaotic/seized RogueBackstab(other, true, ReuseTime); - if (level > 54) { - - // Check for double attack with main hand assuming maxed DA Skill (MS) - if(IsClient() && CastToClient()->CheckDoubleAttack()) - if(other->GetHP() > 0) - RogueBackstab(other,true, ReuseTime); - - if (tripleChance && other->GetHP() > 0 && zone->random.Roll(tripleChance)) - RogueBackstab(other,false,ReuseTime); - } - if(IsClient()) CastToClient()->CheckIncreaseSkill(EQEmu::skills::SkillBackstab, other, 10); + m_specialattacks = eSpecialAttacks::None; } else { //We do a single regular attack if we attack from the front without chaotic stab Attack(other, EQEmu::inventory::slotPrimary); @@ -570,79 +588,21 @@ void Mob::RogueBackstab(Mob* other, bool min_damage, int ReuseTime) if (!other) return; - int32 ndamage = 0; - int32 max_hit = 0; - int32 min_hit = 0; uint32 hate = 0; - int32 primaryweapondamage = 0; - int32 backstab_dmg = 0; + // make sure we can hit (bane, magical, etc) if (IsClient()) { const EQEmu::ItemInstance *wpn = CastToClient()->GetInv().GetItem(EQEmu::inventory::slotPrimary); - if (wpn) { - primaryweapondamage = GetWeaponDamage(other, wpn); - if (primaryweapondamage) { - backstab_dmg = wpn->GetItemBackstabDamage(true); - backstab_dmg += other->ResistElementalWeaponDmg(wpn); - if (wpn->GetItemBaneDamageBody(true) || wpn->GetItemBaneDamageRace(true)) - backstab_dmg += other->CheckBaneDamage(wpn); - } - } - } else { - primaryweapondamage = - (GetLevel() / 7) + 1; // fallback incase it's a npc without a weapon, 2 dmg at 10, 10 dmg at 65 - backstab_dmg = primaryweapondamage; + if (!GetWeaponDamage(other, wpn)) + return; + } else if (!GetWeaponDamage(other, (const EQEmu::ItemData*)nullptr)){ + return; } - // ex. bane can make this false - if (primaryweapondamage > 0) { - // this is very wrong but not worth it until we fix the full dmg - if (level > 25) { - max_hit = (((((2 * backstab_dmg) * GetDamageTable(EQEmu::skills::SkillBackstab) / 100) * 10 * GetSkill(EQEmu::skills::SkillBackstab) / 355) + ((level - 25) / 3) + 1) * ((100 + RuleI(Combat, BackstabBonus)) / 100)); - hate = 20 * backstab_dmg * GetSkill(EQEmu::skills::SkillBackstab) / 355; - } else { - max_hit = (((((2 * backstab_dmg) * GetDamageTable(EQEmu::skills::SkillBackstab) / 100) * 10 * GetSkill(EQEmu::skills::SkillBackstab) / 355) + 1) * ((100 + RuleI(Combat, BackstabBonus)) / 100)); - hate = 20 * backstab_dmg * GetSkill(EQEmu::skills::SkillBackstab) / 355; - } + int base_damage = GetBaseSkillDamage(EQEmu::skills::SkillBackstab, other); + hate = base_damage; - // determine minimum hits - if (level < 51) { - min_hit = (level*15/10); - } else { - // Trumpcard: Replaced switch statement with formula calc. This will give minhit increases all the way to 65. - min_hit = (level * ( level*5 - 105)) / 100; - } - - if (!other->CheckHitChance(this, EQEmu::skills::SkillBackstab, 0)) { - ndamage = 0; - } else { - if (min_damage) { - ndamage = min_hit; - } else { - if (max_hit < min_hit) - max_hit = min_hit; - - if (RuleB(Combat, UseIntervalAC)) - ndamage = max_hit; - else - ndamage = zone->random.Int(min_hit, max_hit); - } - } - } else { - ndamage = -5; - } - - ndamage = mod_backstab_damage(ndamage); - - uint32 Assassinate_Dmg = 0; - Assassinate_Dmg = TryAssassinate(other, EQEmu::skills::SkillBackstab, ReuseTime); - - if (Assassinate_Dmg) { - ndamage = Assassinate_Dmg; - entity_list.MessageClose_StringID(this, false, 200, MT_CritMelee, ASSASSINATES, GetName()); - } - - DoSpecialAttackDamage(other, EQEmu::skills::SkillBackstab, ndamage, min_hit, hate, ReuseTime, false, false); + DoSpecialAttackDamage(other, EQEmu::skills::SkillBackstab, base_damage, 0, hate, ReuseTime, true, false); DoAnim(anim1HPiercing); } @@ -867,11 +827,6 @@ void Mob::DoArcheryAttackDmg(Mob* other, const EQEmu::ItemInstance* RangeWeapon } else { Log.Out(Logs::Detail, Logs::Combat, "Ranged attack hit %s.", other->GetName()); - bool HeadShot = false; - uint32 HeadShot_Dmg = TryHeadShot(other, EQEmu::skills::SkillArchery); - if (HeadShot_Dmg) - HeadShot = true; - uint32 hate = 0; int32 TotalDmg = 0; int16 WDmg = 0; @@ -888,85 +843,42 @@ void Mob::DoArcheryAttackDmg(Mob* other, const EQEmu::ItemInstance* RangeWeapon return; } + // unsure when this should happen if (focus) //From FcBaseEffects - WDmg += WDmg*focus/100; + WDmg += WDmg * focus / 100; if((WDmg > 0) || (ADmg > 0)) { if(WDmg < 0) WDmg = 0; if(ADmg < 0) ADmg = 0; - uint32 MaxDmg = (RuleR(Combat, ArcheryBaseDamageBonus)*(WDmg + ADmg)*GetDamageTable(EQEmu::skills::SkillArchery)) / 100; + uint32 MaxDmg = WDmg + ADmg; hate = ((WDmg+ADmg)); - if (HeadShot) - MaxDmg = HeadShot_Dmg; - - uint16 bonusArcheryDamageModifier = aabonuses.ArcheryDamageModifier + itembonuses.ArcheryDamageModifier + spellbonuses.ArcheryDamageModifier; - - MaxDmg += MaxDmg*bonusArcheryDamageModifier / 100; - if (RuleB(Combat, ProjectileDmgOnImpact)) Log.Out(Logs::Detail, Logs::Combat, "Bow and Arrow DMG %d, Max Damage %d.", WDmg, MaxDmg); else Log.Out(Logs::Detail, Logs::Combat, "Bow DMG %d, Arrow DMG %d, Max Damage %d.", WDmg, ADmg, MaxDmg); - bool dobonus = false; - if(GetClass() == RANGER && GetLevel() > 50){ - int bonuschance = RuleI(Combat, ArcheryBonusChance); - bonuschance = mod_archery_bonus_chance(bonuschance, RangeWeapon); - - if( !RuleB(Combat, UseArcheryBonusRoll) || zone->random.Roll(bonuschance)){ - if(RuleB(Combat, ArcheryBonusRequiresStationary)){ - if(other->IsNPC() && !other->IsMoving() && !other->IsRooted()) - dobonus = true; - } - else - dobonus = true; - } - - if(dobonus){ - MaxDmg *= 2; - hate *= 2; - MaxDmg = mod_archery_bonus_damage(MaxDmg, RangeWeapon); - - Log.Out(Logs::Detail, Logs::Combat, "Ranger. Double damage success roll, doubling damage to %d", MaxDmg); - Message_StringID(MT_CritMelee, BOW_DOUBLE_DAMAGE); - } - } - if (MaxDmg == 0) MaxDmg = 1; - if(RuleB(Combat, UseIntervalAC)) - TotalDmg = MaxDmg; - else - TotalDmg = zone->random.Int(1, MaxDmg); + int min_cap = MaxDmg * GetMeleeMinDamageMod_SE(EQEmu::skills::SkillArchery) / 100; + auto offense = this->offense(EQEmu::skills::SkillArchery); - int minDmg = 1; - if(GetLevel() > 25){ - //twice, for ammo and weapon - TotalDmg += (2*((GetLevel()-25)/3)); - minDmg += (2*((GetLevel()-25)/3)); - minDmg += minDmg * GetMeleeMinDamageMod_SE(EQEmu::skills::SkillArchery) / 100; - hate += (2*((GetLevel()-25)/3)); - } + other->AvoidDamage(this, TotalDmg, EQEmu::inventory::slotRange); - if (!HeadShot) - other->AvoidDamage(this, TotalDmg, EQEmu::inventory::slotRange); - - other->MeleeMitigation(this, TotalDmg, minDmg); + other->MeleeMitigation(this, TotalDmg, MaxDmg, offense, EQEmu::skills::SkillArchery); if(TotalDmg > 0){ - CommonOutgoingHitSuccess(other, TotalDmg, EQEmu::skills::SkillArchery); - TotalDmg = mod_archery_damage(TotalDmg, dobonus, RangeWeapon); + if (IsClient()) + ApplyDamageTable(TotalDmg, offense); + CommonOutgoingHitSuccess(other, TotalDmg, 0, min_cap, EQEmu::skills::SkillArchery); + //TotalDmg = mod_archery_damage(TotalDmg, dobonus, RangeWeapon); } } else TotalDmg = -5; - if (HeadShot) - entity_list.MessageClose_StringID(this, false, 200, MT_CritMelee, FATAL_BOW_SHOT, GetName()); - if (IsClient() && !CastToClient()->GetFeigned()) other->AddToHateList(this, hate, 0, false); @@ -1227,8 +1139,8 @@ void NPC::RangedAttack(Mob* other) } } -void NPC::DoRangedAttackDmg(Mob* other, bool Launch, int16 damage_mod, int16 chance_mod, EQEmu::skills::SkillType skill, float speed, const char *IDFile) { - +void NPC::DoRangedAttackDmg(Mob* other, bool Launch, int16 damage_mod, int16 chance_mod, EQEmu::skills::SkillType skill, float speed, const char *IDFile) +{ if ((other == nullptr || (other->HasDied())) || HasDied() || @@ -1271,15 +1183,9 @@ void NPC::DoRangedAttackDmg(Mob* other, bool Launch, int16 damage_mod, int16 cha } else { - int32 TotalDmg = 0; - int32 MaxDmg = max_dmg * RuleR(Combat, ArcheryNPCMultiplier); // should add a field to npc_types - int32 MinDmg = min_dmg * RuleR(Combat, ArcheryNPCMultiplier); - - if(RuleB(Combat, UseIntervalAC)) - TotalDmg = MaxDmg; - else - TotalDmg = zone->random.Int(MinDmg, MaxDmg); - + int TotalDmg = 0; + int MaxDmg = GetBaseDamage() * RuleR(Combat, ArcheryNPCMultiplier); // should add a field to npc_types + int MinDmg = GetMinDamage() * RuleR(Combat, ArcheryNPCMultiplier); if (!damage_mod) damage_mod = GetSpecialAbilityParam(SPECATK_RANGED_ATK, 3);//Damage modifier @@ -1287,10 +1193,10 @@ void NPC::DoRangedAttackDmg(Mob* other, bool Launch, int16 damage_mod, int16 cha TotalDmg += TotalDmg * damage_mod / 100; other->AvoidDamage(this, TotalDmg, EQEmu::inventory::slotRange); - other->MeleeMitigation(this, TotalDmg, MinDmg); + other->MeleeMitigation(this, TotalDmg, MaxDmg, offense(skillInUse), skillInUse); if (TotalDmg > 0) - CommonOutgoingHitSuccess(other, TotalDmg, skillInUse); + CommonOutgoingHitSuccess(other, TotalDmg, MinDmg, 0, skillInUse); else if (TotalDmg < -4) TotalDmg = -5; @@ -1313,32 +1219,6 @@ void NPC::DoRangedAttackDmg(Mob* other, bool Launch, int16 damage_mod, int16 cha TrySkillProc(other, skillInUse, 0, false, EQEmu::inventory::slotRange); } -uint16 Mob::GetThrownDamage(int16 wDmg, int32& TotalDmg, int& minDmg) { - uint16 MaxDmg = (((2 * wDmg) * GetDamageTable(EQEmu::skills::SkillThrowing)) / 100); - - if (MaxDmg == 0) - MaxDmg = 1; - - if(RuleB(Combat, UseIntervalAC)) - TotalDmg = MaxDmg; - else - TotalDmg = zone->random.Int(1, MaxDmg); - - minDmg = 1; - if(GetLevel() > 25){ - TotalDmg += ((GetLevel()-25)/3); - minDmg += ((GetLevel()-25)/3); - minDmg += minDmg * GetMeleeMinDamageMod_SE(EQEmu::skills::SkillThrowing) / 100; - } - - if(MaxDmg < minDmg) - MaxDmg = minDmg; - - MaxDmg = mod_throwing_damage(MaxDmg); - - return MaxDmg; -} - void Client::ThrowingAttack(Mob* other, bool CanDoubleAttack) { //old was 51 //conditions to use an attack checked before we are called if (!other) @@ -1422,21 +1302,18 @@ void Client::ThrowingAttack(Mob* other, bool CanDoubleAttack) { //old was 51 CommonBreakInvisibleFromCombat(); } -void Mob::DoThrowingAttackDmg(Mob* other, const EQEmu::ItemInstance* RangeWeapon, const EQEmu::ItemData* AmmoItem, uint16 weapon_damage, int16 chance_mod, int16 focus, int ReuseTime, uint32 range_id, int AmmoSlot, float speed) +void Mob::DoThrowingAttackDmg(Mob *other, const EQEmu::ItemInstance *RangeWeapon, const EQEmu::ItemData *AmmoItem, + uint16 weapon_damage, int16 chance_mod, int16 focus, int ReuseTime, uint32 range_id, + int AmmoSlot, float speed) { if ((other == nullptr || - ((IsClient() && CastToClient()->dead) || - (other->IsClient() && other->CastToClient()->dead)) || - HasDied() || - (!IsAttackAllowed(other)) || - (other->GetInvul() || - other->GetSpecialAbility(IMMUNE_MELEE)))) - { + ((IsClient() && CastToClient()->dead) || (other->IsClient() && other->CastToClient()->dead)) || + HasDied() || (!IsAttackAllowed(other)) || (other->GetInvul() || other->GetSpecialAbility(IMMUNE_MELEE)))) { return; } - const EQEmu::ItemInstance* _RangeWeapon = nullptr; - const EQEmu::ItemData* ammo_lost = nullptr; + const EQEmu::ItemInstance *_RangeWeapon = nullptr; + const EQEmu::ItemData *ammo_lost = nullptr; /* If LaunchProjectile is false this function will do archery damage on target, @@ -1447,95 +1324,97 @@ void Mob::DoThrowingAttackDmg(Mob* other, const EQEmu::ItemInstance* RangeWeapon bool ProjectileImpact = false; bool ProjectileMiss = false; - if (RuleB(Combat, ProjectileDmgOnImpact)){ + if (RuleB(Combat, ProjectileDmgOnImpact)) { if (AmmoItem) LaunchProjectile = true; - else{ - if (!RangeWeapon && range_id){ + else { + if (!RangeWeapon && range_id) { ProjectileImpact = true; if (weapon_damage == 0) - ProjectileMiss = true; //This indicates that MISS was originally calculated. + ProjectileMiss = true; // This indicates that MISS was originally calculated. - if (IsClient()){ + if (IsClient()) { _RangeWeapon = CastToClient()->m_inv[AmmoSlot]; - if (_RangeWeapon && _RangeWeapon->GetItem() && _RangeWeapon->GetItem()->ID != range_id) + if (_RangeWeapon && _RangeWeapon->GetItem() && + _RangeWeapon->GetItem()->ID != range_id) RangeWeapon = _RangeWeapon; else ammo_lost = database.GetItem(range_id); } } } - } - else if (AmmoItem) + } else if (AmmoItem) SendItemAnimation(other, AmmoItem, EQEmu::skills::SkillThrowing); - if (ProjectileMiss || (!ProjectileImpact && !other->CheckHitChance(this, EQEmu::skills::SkillThrowing, chance_mod))){ + if (ProjectileMiss || + (!ProjectileImpact && !other->CheckHitChance(this, EQEmu::skills::SkillThrowing, chance_mod))) { Log.Out(Logs::Detail, Logs::Combat, "Ranged attack missed %s.", other->GetName()); - if (LaunchProjectile){ - TryProjectileAttack(other, AmmoItem, EQEmu::skills::SkillThrowing, 0, RangeWeapon, nullptr, AmmoSlot, speed); + if (LaunchProjectile) { + TryProjectileAttack(other, AmmoItem, EQEmu::skills::SkillThrowing, 0, RangeWeapon, nullptr, + AmmoSlot, speed); return; - } - else + } else other->Damage(this, 0, SPELL_UNKNOWN, EQEmu::skills::SkillThrowing); } else { Log.Out(Logs::Detail, Logs::Combat, "Throwing attack hit %s.", other->GetName()); int16 WDmg = 0; - if (!weapon_damage){ + if (!weapon_damage) { if (IsClient() && RangeWeapon) WDmg = GetWeaponDamage(other, RangeWeapon); else if (AmmoItem) WDmg = GetWeaponDamage(other, AmmoItem); - if (LaunchProjectile){ - TryProjectileAttack(other, AmmoItem, EQEmu::skills::SkillThrowing, WDmg, RangeWeapon, nullptr, AmmoSlot, speed); + if (LaunchProjectile) { + TryProjectileAttack(other, AmmoItem, EQEmu::skills::SkillThrowing, WDmg, RangeWeapon, + nullptr, AmmoSlot, speed); return; } - } - else + } else WDmg = weapon_damage; - if (focus) //From FcBaseEffects - WDmg += WDmg*focus/100; + if (focus) // From FcBaseEffects + WDmg += WDmg * focus / 100; int32 TotalDmg = 0; uint32 Assassinate_Dmg = 0; if (GetClass() == ROGUE && (BehindMob(other, GetX(), GetY()))) - Assassinate_Dmg = TryAssassinate(other, EQEmu::skills::SkillThrowing, ranged_timer.GetDuration()); - - if(WDmg > 0){ - int minDmg = 1; - uint16 MaxDmg = GetThrownDamage(WDmg, TotalDmg, minDmg); + Assassinate_Dmg = + TryAssassinate(other, EQEmu::skills::SkillThrowing, ranged_timer.GetDuration()); + if (WDmg > 0) { + int min_cap = WDmg * GetMeleeMinDamageMod_SE(EQEmu::skills::SkillThrowing) / 100; + auto offense = this->offense(EQEmu::skills::SkillThrowing); if (Assassinate_Dmg) { TotalDmg = Assassinate_Dmg; - entity_list.MessageClose_StringID(this, false, 200, MT_CritMelee, ASSASSINATES, GetName()); } - Log.Out(Logs::Detail, Logs::Combat, "Item DMG %d. Max Damage %d. Hit for damage %d", WDmg, MaxDmg, TotalDmg); + Log.Out(Logs::Detail, Logs::Combat, "Item DMG %d. Hit for damage %d", WDmg, TotalDmg); if (!Assassinate_Dmg) other->AvoidDamage(this, TotalDmg, EQEmu::inventory::slotRange); - other->MeleeMitigation(this, TotalDmg, minDmg); - if(TotalDmg > 0) - CommonOutgoingHitSuccess(other, TotalDmg, EQEmu::skills::SkillThrowing); + other->MeleeMitigation(this, TotalDmg, WDmg, offense, EQEmu::skills::SkillThrowing); + if (TotalDmg > 0) + if (IsClient()) + ApplyDamageTable(TotalDmg, offense); + CommonOutgoingHitSuccess(other, TotalDmg, 0, min_cap, EQEmu::skills::SkillThrowing); } else TotalDmg = -5; if (IsClient() && !CastToClient()->GetFeigned()) - other->AddToHateList(this, 2*WDmg, 0, false); + other->AddToHateList(this, 2 * WDmg, 0, false); other->Damage(this, TotalDmg, SPELL_UNKNOWN, EQEmu::skills::SkillThrowing); - if (TotalDmg > 0 && HasSkillProcSuccess() && other && !other->HasDied()){ + if (TotalDmg > 0 && HasSkillProcSuccess() && other && !other->HasDied()) { if (ReuseTime) TrySkillProc(other, EQEmu::skills::SkillThrowing, ReuseTime); else @@ -1546,13 +1425,13 @@ void Mob::DoThrowingAttackDmg(Mob* other, const EQEmu::ItemInstance* RangeWeapon if (LaunchProjectile) return; - //Throwing item Proc + // Throwing item Proc if (ammo_lost) TryWeaponProc(nullptr, ammo_lost, other, EQEmu::inventory::slotRange); - else if(RangeWeapon && other && !other->HasDied()) + else if (RangeWeapon && other && !other->HasDied()) TryWeaponProc(RangeWeapon, other, EQEmu::inventory::slotRange); - if (HasSkillProcs() && other && !other->HasDied()){ + if (HasSkillProcs() && other && !other->HasDied()) { if (ReuseTime) TrySkillProc(other, EQEmu::skills::SkillThrowing, ReuseTime); else @@ -1755,17 +1634,12 @@ void NPC::DoClassAttacks(Mob *target) { dmg = -5; } else{ - if (target->CheckHitChance(this, EQEmu::skills::SkillKick, 0)) { - if(RuleB(Combat, UseIntervalAC)) - dmg = GetKickDamage(); - else - dmg = zone->random.Int(1, GetKickDamage()); - - } + if (target->CheckHitChance(this, EQEmu::skills::SkillKick, 0)) + dmg = GetBaseSkillDamage(EQEmu::skills::SkillKick); } reuse = (KickReuseTime + 3) * 1000; - DoSpecialAttackDamage(target, EQEmu::skills::SkillKick, dmg, 1, -1, reuse); + DoSpecialAttackDamage(target, EQEmu::skills::SkillKick, dmg, GetMinDamage(), -1, reuse); did_attack = true; } else { @@ -1776,16 +1650,12 @@ void NPC::DoClassAttacks(Mob *target) { dmg = -5; } else{ - if (target->CheckHitChance(this, EQEmu::skills::SkillBash, 0)) { - if(RuleB(Combat, UseIntervalAC)) - dmg = GetBashDamage(); - else - dmg = zone->random.Int(1, GetBashDamage()); - } + if (target->CheckHitChance(this, EQEmu::skills::SkillBash, 0)) + dmg = GetBaseSkillDamage(EQEmu::skills::SkillBash); } reuse = (BashReuseTime + 3) * 1000; - DoSpecialAttackDamage(target, EQEmu::skills::SkillBash, dmg, 1, -1, reuse); + DoSpecialAttackDamage(target, EQEmu::skills::SkillBash, dmg, GetMinDamage(), -1, reuse); did_attack = true; } } @@ -1793,28 +1663,16 @@ void NPC::DoClassAttacks(Mob *target) { } case BERSERKER: case BERSERKERGM:{ int AtkRounds = 3; - int32 max_dmg = 26 + ((GetLevel()-6) * 2); - int32 min_dmg = 0; + int32 max_dmg = GetBaseSkillDamage(EQEmu::skills::SkillFrenzy); DoAnim(anim2HSlashing); - if (GetLevel() < 51) - min_dmg = 1; - else - min_dmg = GetLevel()*8/10; - - if (min_dmg > max_dmg) - max_dmg = min_dmg; - - reuse = FrenzyReuseTime * 1000; - while(AtkRounds > 0) { if (GetTarget() && (AtkRounds == 1 || zone->random.Roll(75))) { - DoSpecialAttackDamage(GetTarget(), EQEmu::skills::SkillFrenzy, max_dmg, min_dmg, -1, reuse, true); + DoSpecialAttackDamage(GetTarget(), EQEmu::skills::SkillFrenzy, max_dmg, GetMinDamage(), -1, reuse, true); } AtkRounds--; } - did_attack = true; break; } @@ -1829,16 +1687,12 @@ void NPC::DoClassAttacks(Mob *target) { dmg = -5; } else{ - if (target->CheckHitChance(this, EQEmu::skills::SkillKick, 0)) { - if(RuleB(Combat, UseIntervalAC)) - dmg = GetKickDamage(); - else - dmg = zone->random.Int(1, GetKickDamage()); - } + if (target->CheckHitChance(this, EQEmu::skills::SkillKick, 0)) + dmg = GetBaseSkillDamage(EQEmu::skills::SkillKick); } reuse = (KickReuseTime + 3) * 1000; - DoSpecialAttackDamage(target, EQEmu::skills::SkillKick, dmg, 1, -1, reuse); + DoSpecialAttackDamage(target, EQEmu::skills::SkillKick, dmg, GetMinDamage(), -1, reuse); did_attack = true; } break; @@ -1854,16 +1708,12 @@ void NPC::DoClassAttacks(Mob *target) { dmg = -5; } else{ - if (target->CheckHitChance(this, EQEmu::skills::SkillBash, 0)) { - if(RuleB(Combat, UseIntervalAC)) - dmg = GetBashDamage(); - else - dmg = zone->random.Int(1, GetBashDamage()); - } + if (target->CheckHitChance(this, EQEmu::skills::SkillBash, 0)) + dmg = GetBaseSkillDamage(EQEmu::skills::SkillBash); } reuse = (BashReuseTime + 3) * 1000; - DoSpecialAttackDamage(target, EQEmu::skills::SkillBash, dmg, 1, -1, reuse); + DoSpecialAttackDamage(target, EQEmu::skills::SkillBash, dmg, GetMinDamage(), -1, reuse); did_attack = true; } break; @@ -1873,6 +1723,7 @@ void NPC::DoClassAttacks(Mob *target) { classattack_timer.Start(reuse / HasteModifier); } +// this should be refactored to generate an OP_CombatAbility struct and call OPCombatAbility void Client::DoClassAttacks(Mob *ca_target, uint16 skill, bool IsRiposte) { if(!ca_target) @@ -1964,17 +1815,13 @@ void Client::DoClassAttacks(Mob *ca_target, uint16 skill, bool IsRiposte) dmg = 0; } else{ - if(RuleB(Combat, UseIntervalAC)) - dmg = GetBashDamage(); - else - dmg = zone->random.Int(1, GetBashDamage()); - + dmg = GetBaseSkillDamage(EQEmu::skills::SkillBash, ca_target); } } ReuseTime = (BashReuseTime - 1) / HasteMod; - DoSpecialAttackDamage(ca_target, EQEmu::skills::SkillBash, dmg, 1, -1, ReuseTime); + DoSpecialAttackDamage(ca_target, EQEmu::skills::SkillBash, dmg, 0, -1, ReuseTime); if(ReuseTime > 0 && !IsRiposte) { p_timers.Start(pTimerCombatAbility, ReuseTime); @@ -1986,26 +1833,16 @@ void Client::DoClassAttacks(Mob *ca_target, uint16 skill, bool IsRiposte) if (skill_to_use == EQEmu::skills::SkillFrenzy){ CheckIncreaseSkill(EQEmu::skills::SkillFrenzy, GetTarget(), 10); int AtkRounds = 3; - int skillmod = 100 * GetSkill(EQEmu::skills::SkillFrenzy) / MaxSkill(EQEmu::skills::SkillFrenzy); - int32 max_dmg = (26 + ((((GetLevel()-6) * 2)*skillmod)/100)) * ((100+RuleI(Combat, FrenzyBonus))/100); - int32 min_dmg = 0; + int32 max_dmg = GetBaseSkillDamage(EQEmu::skills::SkillBash, GetTarget()); DoAnim(anim2HSlashing); - if (GetLevel() < 51) - min_dmg = 1; - else - min_dmg = GetLevel()*8/10; - - if (min_dmg > max_dmg) - max_dmg = min_dmg; - ReuseTime = (FrenzyReuseTime - 1) / HasteMod; //Live parses show around 55% Triple 35% Double 10% Single, you will always get first hit. while(AtkRounds > 0) { if (GetTarget() && (AtkRounds == 1 || zone->random.Roll(75))) { - DoSpecialAttackDamage(GetTarget(), EQEmu::skills::SkillFrenzy, max_dmg, min_dmg, max_dmg, ReuseTime, true); + DoSpecialAttackDamage(GetTarget(), EQEmu::skills::SkillFrenzy, max_dmg, 0, max_dmg, ReuseTime, true); } AtkRounds--; } @@ -2028,16 +1865,13 @@ void Client::DoClassAttacks(Mob *ca_target, uint16 skill, bool IsRiposte) dmg = 0; } else{ - if(RuleB(Combat, UseIntervalAC)) - dmg = GetKickDamage(); - else - dmg = zone->random.Int(1, GetKickDamage()); + dmg = GetBaseSkillDamage(EQEmu::skills::SkillKick, ca_target); } } ReuseTime = KickReuseTime-1; - DoSpecialAttackDamage(ca_target, EQEmu::skills::SkillKick, dmg, 1, -1, ReuseTime); + DoSpecialAttackDamage(ca_target, EQEmu::skills::SkillKick, dmg, 0, -1, ReuseTime); } } @@ -2218,7 +2052,7 @@ void Mob::InstillDoubt(Mob *who) { } } -uint32 Mob::TryHeadShot(Mob* defender, EQEmu::skills::SkillType skillInUse) { +int Mob::TryHeadShot(Mob* defender, EQEmu::skills::SkillType skillInUse) { //Only works on YOUR target. if(defender && (defender->GetBodyType() == BT_Humanoid) && !defender->IsClient() && (skillInUse == EQEmu::skills::SkillArchery) && (GetTarget() == defender)) { @@ -2233,8 +2067,10 @@ uint32 Mob::TryHeadShot(Mob* defender, EQEmu::skills::SkillType skillInUse) { if(HeadShot_Dmg && HeadShot_Level && (defender->GetLevel() <= HeadShot_Level)){ float ProcChance = GetSpecialProcChances(EQEmu::inventory::slotRange); - if(zone->random.Roll(ProcChance)) + if(zone->random.Roll(ProcChance)) { + entity_list.MessageClose_StringID(this, false, 200, MT_CritMelee, FATAL_BOW_SHOT, GetName()); return HeadShot_Dmg; + } } } @@ -2269,9 +2105,9 @@ float Mob::GetSpecialProcChances(uint16 hand) return ProcChance; } -uint32 Mob::TryAssassinate(Mob* defender, EQEmu::skills::SkillType skillInUse, uint16 ReuseTime) { +int Mob::TryAssassinate(Mob* defender, EQEmu::skills::SkillType skillInUse, uint16 ReuseTime) { - if(defender && (defender->GetBodyType() == BT_Humanoid) && !defender->IsClient() && + if(defender && (defender->GetBodyType() == BT_Humanoid) && !defender->IsClient() && GetLevel() >= 60 && (skillInUse == EQEmu::skills::SkillBackstab || skillInUse == EQEmu::skills::SkillThrowing)) { uint32 Assassinate_Dmg = aabonuses.Assassinate[1] + spellbonuses.Assassinate[1] + itembonuses.Assassinate[1]; @@ -2283,13 +2119,9 @@ uint32 Mob::TryAssassinate(Mob* defender, EQEmu::skills::SkillType skillInUse, u else if (Assassinate_Level < itembonuses.AssassinateLevel) Assassinate_Level = itembonuses.AssassinateLevel; - if (GetLevel() >= 60){ //Innate Assassinate Ability if client as no bonuses. - if (!Assassinate_Level) - Assassinate_Level = 45; - - if (!Assassinate_Dmg) - Assassinate_Dmg = 32000; - } + // revamped AAs require AA line I believe? + if (!Assassinate_Level) + return 0; if(Assassinate_Dmg && Assassinate_Level && (defender->GetLevel() <= Assassinate_Level)){ float ProcChance = 0.0f; @@ -2299,8 +2131,11 @@ uint32 Mob::TryAssassinate(Mob* defender, EQEmu::skills::SkillType skillInUse, u else ProcChance = GetAssassinateProcChances(ReuseTime); - if(zone->random.Roll(ProcChance)) + if(zone->random.Roll(ProcChance)) { + entity_list.MessageClose_StringID(this, false, 200, MT_CritMelee, ASSASSINATES, + GetName()); return Assassinate_Dmg; + } } } @@ -2340,34 +2175,20 @@ void Mob::DoMeleeSkillAttackDmg(Mob* other, uint16 weapon_damage, EQEmu::skills: /* For spells using skill value 98 (feral swipe ect) server sets this to 67 automatically. Kayen: This is unlikely to be completely accurate but use OFFENSE skill value for these effects. + TODO: We need to stop moving skill 98, it's suppose to just be a dummy skill AFAIK + Spells using offense should use the skill of your primary, if you can use it, otherwise h2h */ if (skillinuse == EQEmu::skills::SkillBegging) skillinuse = EQEmu::skills::SkillOffense; int damage = 0; uint32 hate = 0; - int Hand = EQEmu::inventory::slotPrimary; if (hate == 0 && weapon_damage > 1) hate = weapon_damage; if(weapon_damage > 0){ if (focus) //From FcBaseEffects weapon_damage += weapon_damage*focus/100; - if(GetClass() == BERSERKER){ - int bonus = 3 + GetLevel()/10; - weapon_damage = weapon_damage * (100+bonus) / 100; - } - - int32 min_hit = 1; - int32 max_hit = (2 * weapon_damage*GetDamageTable(skillinuse)) / 100; - - if(GetLevel() >= 28 && IsWarriorClass() ) { - int ucDamageBonus = GetWeaponDamageBonus((const EQEmu::ItemData*) nullptr); - min_hit += (int) ucDamageBonus; - max_hit += (int) ucDamageBonus; - hate += ucDamageBonus; - } - if (skillinuse == EQEmu::skills::SkillBash){ if(IsClient()){ EQEmu::ItemInstance *item = CastToClient()->GetInv().GetItem(EQEmu::inventory::slotSecondary); @@ -2381,16 +2202,11 @@ void Mob::DoMeleeSkillAttackDmg(Mob* other, uint16 weapon_damage, EQEmu::skills: } } - ApplySpecialAttackMod(skillinuse, max_hit, min_hit); - min_hit += min_hit * GetMeleeMinDamageMod_SE(skillinuse) / 100; - - if(max_hit < min_hit) - max_hit = min_hit; - - if(RuleB(Combat, UseIntervalAC)) - damage = max_hit; - else - damage = zone->random.Int(min_hit, max_hit); + int min_cap = weapon_damage * GetMeleeMinDamageMod_SE(skillinuse) / 100; + auto offense = this->offense(skillinuse); + int min_damage = 0; + if (IsNPC()) + min_damage = CastToNPC()->GetMinDamage(); if (other->AvoidDamage(this, damage, CanRiposte ? EQEmu::inventory::slotRange : EQEmu::inventory::slotPrimary)) { // SlotRange excludes ripo, primary doesn't have any extra behavior if (damage == -3) { @@ -2400,8 +2216,9 @@ void Mob::DoMeleeSkillAttackDmg(Mob* other, uint16 weapon_damage, EQEmu::skills: } } else { if (other->CheckHitChance(this, skillinuse, chance_mod)) { - other->MeleeMitigation(this, damage, min_hit); - CommonOutgoingHitSuccess(other, damage, skillinuse); + other->MeleeMitigation(this, damage, weapon_damage, offense, skillinuse); + ApplyDamageTable(damage, offense); + CommonOutgoingHitSuccess(other, damage, min_damage, min_cap, skillinuse); } else { damage = 0; } diff --git a/zone/tune.cpp b/zone/tune.cpp index 6595ad4d0..bbb56217e 100644 --- a/zone/tune.cpp +++ b/zone/tune.cpp @@ -486,7 +486,7 @@ int32 Mob::Tune_MeleeMitigation(Mob* GM, Mob *attacker, int32 damage, int32 minh } - damage = GetMeleeMitDmg(attacker, damage, minhit, mitigation_rating, attack_rating); + //damage = GetMeleeMitDmg(attacker, damage, minhit, mitigation_rating, attack_rating); } if (damage < 0) @@ -539,7 +539,7 @@ int32 Client::Tune_GetMeleeMitDmg(Mob* GM, Mob *attacker, int32 damage, int32 mi float mit_rating, float atk_rating) { if (!attacker->IsNPC() || RuleB(Combat, UseOldDamageIntervalRules)) - return Mob::GetMeleeMitDmg(attacker, damage, minhit, mit_rating, atk_rating); + return 0; //Mob::GetMeleeMitDmg(attacker, damage, minhit, mit_rating, atk_rating); int d = 10; // floats for the rounding issues float dmg_interval = (damage - minhit) / 19.0; @@ -613,7 +613,7 @@ int32 Client::GetMeleeDamage(Mob* other, bool GetMinDamage) } int min_hit = 1; - int max_hit = (2*weapon_damage*GetDamageTable(skillinuse)) / 100; + int max_hit = 2;//(2*weapon_damage*GetDamageTable(skillinuse)) / 100; if(GetLevel() < 10 && max_hit > RuleI(Combat, HitCapPre10)) max_hit = (RuleI(Combat, HitCapPre10)); @@ -1086,4 +1086,4 @@ float Mob::Tune_CheckHitChance(Mob* defender, Mob* attacker, EQEmu::skills::Skil } return(chancetohit); -} \ No newline at end of file +} diff --git a/zone/zonedb.cpp b/zone/zonedb.cpp index c4fcfc55f..ec56b8df8 100644 --- a/zone/zonedb.cpp +++ b/zone/zonedb.cpp @@ -2133,7 +2133,7 @@ const NPCType* ZoneDatabase::LoadNPCTypesData(uint32 npc_type_id, bool bulk_load temp_npctype_data->healscale = atoi(row[87]); temp_npctype_data->no_target_hotkey = atoi(row[88]) == 1 ? true: false; temp_npctype_data->raid_target = atoi(row[89]) == 0 ? false: true; - temp_npctype_data->attack_delay = atoi(row[90]); + temp_npctype_data->attack_delay = atoi(row[90]) * 100; // TODO: fix DB temp_npctype_data->light = (atoi(row[91]) & 0x0F); temp_npctype_data->armtexture = atoi(row[92]); diff --git a/zone/zonedump.h b/zone/zonedump.h index c6121fd48..ef8da2243 100644 --- a/zone/zonedump.h +++ b/zone/zonedump.h @@ -110,7 +110,7 @@ struct NPCType uint8 spawn_limit; //only this many may be in zone at a time (0=no limit) uint8 mount_color; //only used by horse class float attack_speed; //%+- on attack delay of the mob. - uint8 attack_delay; //delay between attacks in 10ths of a second + int attack_delay; //delay between attacks in ms int accuracy_rating; // flat bonus before mods int avoidance_rating; // flat bonus before mods bool findable; //can be found with find command