From 9e824876ba5dac262b121c0e60d022bb2ecc45bd Mon Sep 17 00:00:00 2001 From: "Michael Cook (mackal)" Date: Sun, 15 Jan 2017 00:03:02 -0500 Subject: [PATCH] Combat Revamp - MAJOR BREAKING CHANGE This commit makes combat much more live like. This is based on a lot of parses done by TAKP and myself. There are numerous things based on dev quotes and hints. Pretty much all combat has changed, spell effects correct, stacking correct, etc. This is the fist stage of the revamp, I will be trying to remove some code duplication and make things generally cleaner. Server ops will have to rebalance their NPCs. AC actually means something now. Rough recommendations? Level 50 "classic" trash should be no more than 115. Classic raid mobs should be more 200+ etc Other "classic" NPCs should be a lot lower as well. PoP trash probably shouldn't exceed 120 AC PoP raids should be higher Devs have said the vast majority of NPCs didn't exceed 600 AC until very recently. The exceptions were mostly raid encounters. There really isn't a good "default" for every server, so this will be up to the devs to find where they want their server stats to be. --- common/skills.cpp | 24 + common/skills.h | 1 + zone/attack.cpp | 1437 ++++++++++++++++++++++++-------------- zone/beacon.h | 4 +- zone/bonuses.cpp | 8 +- zone/bot.cpp | 451 ++++-------- zone/bot.h | 10 +- zone/client.cpp | 6 +- zone/client.h | 11 +- zone/client_mods.cpp | 105 --- zone/common.h | 6 + zone/corpse.h | 4 +- zone/encounter.h | 4 +- zone/hate_list.cpp | 2 +- zone/merc.cpp | 21 +- zone/merc.h | 5 +- zone/mob.cpp | 6 +- zone/mob.h | 76 +- zone/mob_ai.cpp | 31 +- zone/npc.cpp | 23 +- zone/npc.h | 10 +- zone/special_attacks.cpp | 777 ++++++++------------- zone/tune.cpp | 8 +- zone/zonedb.cpp | 2 +- zone/zonedump.h | 2 +- 25 files changed, 1520 insertions(+), 1514 deletions(-) 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