diff --git a/common/ruletypes.h b/common/ruletypes.h index 2b0b71899..21c4949ea 100644 --- a/common/ruletypes.h +++ b/common/ruletypes.h @@ -392,15 +392,12 @@ RULE_BOOL(Spells, NPCInnateProcOverride, true) // NPC innate procs override the RULE_CATEGORY_END() RULE_CATEGORY(Combat) -RULE_INT(Combat, MeleeBaseCritChance, 0) //The base crit chance for non warriors, NOTE: This will apply to NPCs as well -RULE_INT(Combat, WarBerBaseCritChance, 3) //The base crit chance for warriors and berserkers, only applies to clients -RULE_INT(Combat, BerserkBaseCritChance, 6) //The bonus base crit chance you get when you're berserk +RULE_INT(Combat, PetBaseCritChance, 0) // Pet Base crit chance RULE_INT(Combat, NPCBashKickLevel, 6) //The level that npcs can KICK/BASH RULE_INT(Combat, NPCBashKickStunChance, 15) //Percent chance that a bash/kick will stun -RULE_INT(Combat, RogueCritThrowingChance, 25) //Rogue throwing crit bonus -RULE_INT(Combat, RogueDeadlyStrikeChance, 80) //Rogue chance throwing from behind crit becomes a deadly strike -RULE_INT(Combat, RogueDeadlyStrikeMod, 2) //Deadly strike modifier to crit damage -RULE_INT(Combat, ClientBaseCritChance, 0) //The base crit chance for all clients, this will stack with warrior's/zerker's crit chance. +RULE_INT(Combat, MeleeCritDifficulty, 8900) // lower is easier +RULE_INT(Combat, ArcheryCritDifficulty, 3400) // lower is easier +RULE_INT(Combat, ThrowingCritDifficulty, 1100) // lower is easier RULE_BOOL(Combat, UseIntervalAC, true) RULE_INT(Combat, PetAttackMagicLevel, 30) RULE_BOOL(Combat, EnableFearPathing, true) diff --git a/utils/combat-sim/app.js b/utils/combat-sim/app.js index ce5a7ac69..1bd00364e 100644 --- a/utils/combat-sim/app.js +++ b/utils/combat-sim/app.js @@ -77,6 +77,8 @@ app.controller('MainCtrl', function($scope, $interval) { $scope.stop(); }); + var damage_mods = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]; + function getRandomInt(min, max) { min = Math.ceil(min); max = Math.floor(max); @@ -94,45 +96,20 @@ app.controller('MainCtrl', function($scope, $interval) { } function doCombatRound() { - var offense = $scope.offense; - var mitigation = $scope.mitigation; - mitigation = mitigation - ((mitigation - offense) / 2.0); - var diff = offense - mitigation; - var mean = 0.0; - var mult1 = 0.0; - var mult2 = 0.0; - - 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; + var offense = getRandomInt(0, $scope.offense + 5); + var mitigation = getRandomInt(0, $scope.mitigation + 5); + var avg = parseInt(($scope.offense + $scope.mitigation + 10) / 2); + var index = parseInt((offense - mitigation) + (avg / 2)); + if (index < 0) { + index = 0; } - - if (offense > mitigation) { - mean = diff / offense * mult1; - } else if (mitigation > offense) { - mean = diff / mitigation * mult2; - } - - var stddev = 8.8; - var theta = 2 * Math.PI * getRandom(0.0, 1.0); - var rho = Math.sqrt(-2 * Math.log(1 - getRandom(0.0, 1.0))); - var d = mean + stddev * rho * Math.cos(theta); - - if (d < -9.5) { - d = -9.5; - } else if (d > 9.5) { - d = 9.5; - } - d = d + 11; - addRoll(parseInt(d)); + index = parseInt((index * 20) / avg); + if (index >= 20) + index = 19; + if (index < 0) + index = 0; + var roll = damage_mods[index]; + addRoll(roll); }; }); diff --git a/zone/attack.cpp b/zone/attack.cpp index 48b58964c..c05be46cb 100644 --- a/zone/attack.cpp +++ b/zone/attack.cpp @@ -267,7 +267,7 @@ int Mob::GetTotalDefense() // 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. -bool Mob::CheckHitChance(Mob* other, EQEmu::skills::SkillType skillinuse, int chance_mod) +bool Mob::CheckHitChance(Mob* other, DamageHitInfo &hit) { Mob *attacker = other; Mob *defender = this; @@ -280,7 +280,7 @@ bool Mob::CheckHitChance(Mob* other, EQEmu::skills::SkillType skillinuse, int ch if (avoidance == -1) // some sort of auto avoid disc return false; - auto accuracy = attacker->GetTotalToHit(skillinuse, chance_mod); + auto accuracy = hit.tohit; if (accuracy == -1) return true; @@ -297,7 +297,7 @@ bool Mob::CheckHitChance(Mob* other, EQEmu::skills::SkillType skillinuse, int ch return tohit_roll > avoid_roll; } -bool Mob::AvoidDamage(Mob *other, int &damage, int hand) +bool Mob::AvoidDamage(Mob *other, DamageHitInfo &hit) { /* 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. @@ -360,9 +360,9 @@ bool Mob::AvoidDamage(Mob *other, int &damage, int hand) // riposte -- it may seem crazy, but if the attacker has SPA 173 on them, they are immune to Ripo bool ImmuneRipo = attacker->aabonuses.RiposteChance || attacker->spellbonuses.RiposteChance || attacker->itembonuses.RiposteChance; // Need to check if we have something in MainHand to actually attack with (or fists) - if (hand != EQEmu::inventory::slotRange && (CanThisClassRiposte() || IsEnraged()) && InFront && !ImmuneRipo) { + if (hit.hand != EQEmu::inventory::slotRange && (CanThisClassRiposte() || IsEnraged()) && InFront && !ImmuneRipo) { if (IsEnraged()) { - damage = -3; + hit.damage_done = DMG_RIPOSTED; Log.Out(Logs::Detail, Logs::Combat, "I am enraged, riposting frontal attack."); return true; } @@ -370,7 +370,7 @@ bool Mob::AvoidDamage(Mob *other, int &damage, int hand) CastToClient()->CheckIncreaseSkill(EQEmu::skills::SkillRiposte, other, -10); // check auto discs ... I guess aa/items too :P if (spellbonuses.RiposteChance == 10000 || aabonuses.RiposteChance == 10000 || itembonuses.RiposteChance == 10000) { - damage = -3; + hit.damage_done = DMG_RIPOSTED; return true; } int chance = GetSkill(EQEmu::skills::SkillRiposte) + 100; @@ -382,12 +382,12 @@ bool Mob::AvoidDamage(Mob *other, int &damage, int hand) chance -= chance * counter; } // AA Slippery Attacks - if (hand == EQEmu::inventory::slotSecondary) { + if (hit.hand == EQEmu::inventory::slotSecondary) { int slip = aabonuses.OffhandRiposteFail + itembonuses.OffhandRiposteFail + spellbonuses.OffhandRiposteFail; chance += chance * slip / 100; } if (chance > 0 && zone->random.Roll(chance)) { // could be <0 from offhand stuff - damage = -3; + hit.damage_done = DMG_RIPOSTED; return true; } } @@ -409,7 +409,7 @@ bool Mob::AvoidDamage(Mob *other, int &damage, int hand) // check auto discs ... I guess aa/items too :P if (spellbonuses.IncreaseBlockChance == 10000 || aabonuses.IncreaseBlockChance == 10000 || itembonuses.IncreaseBlockChance == 10000) { - damage = -1; + hit.damage_done = DMG_BLOCKED; return true; } int chance = GetSkill(EQEmu::skills::SkillBlock) + 100; @@ -421,18 +421,18 @@ bool Mob::AvoidDamage(Mob *other, int &damage, int hand) chance -= chance * counter; } if (zone->random.Roll(chance)) { - damage = -1; + hit.damage_done = DMG_BLOCKED; return true; } } // parry - if (CanThisClassParry() && InFront && hand != EQEmu::inventory::slotRange) { + if (CanThisClassParry() && InFront && hit.hand != EQEmu::inventory::slotRange) { if (IsClient()) CastToClient()->CheckIncreaseSkill(EQEmu::skills::SkillParry, other, -10); // check auto discs ... I guess aa/items too :P if (spellbonuses.ParryChance == 10000 || aabonuses.ParryChance == 10000 || itembonuses.ParryChance == 10000) { - damage = -2; + hit.damage_done = DMG_PARRIED; return true; } int chance = GetSkill(EQEmu::skills::SkillParry) + 100; @@ -444,7 +444,7 @@ bool Mob::AvoidDamage(Mob *other, int &damage, int hand) chance -= chance * counter; } if (zone->random.Roll(chance)) { - damage = -2; + hit.damage_done = DMG_PARRIED; return true; } } @@ -455,7 +455,7 @@ bool Mob::AvoidDamage(Mob *other, int &damage, int hand) CastToClient()->CheckIncreaseSkill(EQEmu::skills::SkillDodge, other, -10); // check auto discs ... I guess aa/items too :P if (spellbonuses.DodgeChance == 10000 || aabonuses.DodgeChance == 10000 || itembonuses.DodgeChance == 10000) { - damage = -4; + hit.damage_done = DMG_DODGED; return true; } int chance = GetSkill(EQEmu::skills::SkillDodge) + 100; @@ -467,7 +467,7 @@ bool Mob::AvoidDamage(Mob *other, int &damage, int hand) chance -= chance * counter; } if (zone->random.Roll(chance)) { - damage = -4; + hit.damage_done = DMG_DODGED; return true; } } @@ -480,7 +480,7 @@ bool Mob::AvoidDamage(Mob *other, int &damage, int hand) chance -= chance * counter; } if (zone->random.Roll(chance)) { - damage = -1; + hit.damage_done = DMG_BLOCKED; return true; } } @@ -492,7 +492,7 @@ bool Mob::AvoidDamage(Mob *other, int &damage, int hand) chance -= chance * counter; } if (zone->random.Roll(chance)) { - damage = -1; + hit.damage_done = DMG_BLOCKED; return true; } } @@ -826,54 +826,32 @@ int Mob::offense(EQEmu::skills::SkillType skill) // this assumes "this" is the defender // this returns between 0.1 to 2.0 -double Mob::RollD20(double offense, double mitigation) +double Mob::RollD20(int offense, int mitigation) { + static double mods[] = { + 0.1, 0.2, 0.3, 0.4, 0.5, + 0.6, 0.7, 0.8, 0.9, 1.0, + 1.1, 1.2, 1.3, 1.4, 1.5, + 1.6, 1.7, 1.8, 1.9, 2.0 + }; + if (IsClient() && CastToClient()->IsSitting()) - return 2.0; + return mods[19]; - // 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; + auto atk_roll = zone->random.Roll0(offense + 5); + auto def_roll = zone->random.Roll0(mitigation + 5); - 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; - } + int avg = (offense + mitigation + 10) / 2; + int index = std::max(0, (atk_roll - def_roll) + (avg / 2)); - // 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; + index = EQEmu::Clamp((index * 20) / avg, 0, 19); - 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; + return mods[index]; } -void Mob::MeleeMitigation(Mob *attacker, int &damage, int base_damage, int offense, EQEmu::skills::SkillType skill, ExtraAttackOptions *opts) +void Mob::MeleeMitigation(Mob *attacker, DamageHitInfo &hit, ExtraAttackOptions *opts) { - if (damage < 0 || base_damage == 0) + if (hit.damage_done < 0 || hit.base_damage == 0) return; Mob* defender = this; @@ -886,24 +864,14 @@ void Mob::MeleeMitigation(Mob *attacker, int &damage, int base_damage, int offen 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; + auto roll = RollD20(hit.offense, mitigation); // +0.5 for rounding - damage = static_cast(roll * static_cast(base_damage) + 0.5); + hit.damage_done = static_cast(roll * static_cast(hit.base_damage) + 0.5); - if (meleemitspell) - damage = (damage * (100 - meleemitspell)) / 100; - - if (damage < 0) - damage = 0; - 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); + if (hit.damage_done < 0) + hit.damage_done = 0; + Log.Out(Logs::Detail, Logs::Attack, "mitigation %d vs offense %d. base %d rolled %f damage %d", mitigation, hit.offense, hit.base_damage, roll, hit.damage_done); } //Returns the weapon damage against the input mob @@ -1227,6 +1195,44 @@ int Client::DoDamageCaps(int base_damage) return std::min(cap, base_damage); } +void Mob::DoAttack(Mob *other, DamageHitInfo &hit, ExtraAttackOptions *opts) +{ + if (!other) + return; + Log.Out(Logs::Detail, Logs::Combat, "%s::DoAttack vs %s base %d min %d offense %d tohit %d skill %d", GetName(), + other->GetName(), hit.base_damage, hit.min_damage, hit.offense, hit.tohit, hit.skill); + // check to see if we hit.. + if (other->AvoidDamage(this, hit)) { + int strike_through = itembonuses.StrikeThrough + spellbonuses.StrikeThrough + aabonuses.StrikeThrough; + if (strike_through && zone->random.Roll(strike_through)) { + Message_StringID(MT_StrikeThrough, + STRIKETHROUGH_STRING); // You strike through your opponents defenses! + hit.damage_done = 0; // set to zero, we will check this to continue + } + // I'm pretty sure you can riposte a riposte + if (hit.damage_done == DMG_RIPOSTED) { + DoRiposte(other); + //if (IsDead()) + return; + } + Log.Out(Logs::Detail, Logs::Combat, "Avoided/strikethrough damage with code %d", hit.damage_done); + } + + if (hit.damage_done == 0) { + if (other->CheckHitChance(this, hit)) { + other->MeleeMitigation(this, hit, opts); + if (hit.damage_done > 0) { + ApplyDamageTable(hit); + CommonOutgoingHitSuccess(other, hit, opts); + } + Log.Out(Logs::Detail, Logs::Combat, "Final damage after all reductions: %d", hit.damage_done); + } else { + Log.Out(Logs::Detail, Logs::Combat, "Attack missed. Damage set to 0."); + hit.damage_done = 0; + } + } +} + //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.) @@ -1284,37 +1290,34 @@ bool Client::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, b Log.Out(Logs::Detail, Logs::Combat, "Attacking without a weapon."); } + DamageHitInfo my_hit; // calculate attack_skill and skillinuse depending on hand and weapon // also send Packet to near clients - EQEmu::skills::SkillType skillinuse; - 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); + AttackAnimation(my_hit.skill, Hand, weapon); + Log.Out(Logs::Detail, Logs::Combat, "Attacking with %s in slot %d using skill %d", weapon?weapon->GetItem()->Name:"Fist", Hand, my_hit.skill); // Now figure out damage - int damage = 0; + my_hit.damage_done = 0; uint8 mylevel = GetLevel() ? GetLevel() : 1; uint32 hate = 0; if (weapon) hate = weapon->GetItem()->Damage + weapon->GetItem()->ElemDmgAmt; - int weapon_damage = GetWeaponDamage(other, weapon, &hate); - if (hate == 0 && weapon_damage > 1) hate = weapon_damage; + my_hit.base_damage = GetWeaponDamage(other, weapon, &hate); + if (hate == 0 && my_hit.base_damage > 1) hate = my_hit.base_damage; //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 (my_hit.base_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); + my_hit.base_damage = DoDamageCaps(my_hit.base_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; + my_hit.base_damage = my_hit.base_damage * (100 + shield_inc) / 100; hate = hate * (100 + shield_inc) / 100; } - int base_damage = weapon_damage; - int min_damage = 0; // damage bonus - - CheckIncreaseSkill(skillinuse, other, -15); + CheckIncreaseSkill(my_hit.skill, other, -15); CheckIncreaseSkill(EQEmu::skills::SkillOffense, other, -15); // *************************************************************** @@ -1338,7 +1341,7 @@ bool Client::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, b ucDamageBonus = GetWeaponDamageBonus(weapon ? weapon->GetItem() : (const EQEmu::ItemData*) nullptr); - min_damage = ucDamageBonus; + my_hit.min_damage = ucDamageBonus; hate += ucDamageBonus; } #endif @@ -1348,62 +1351,32 @@ bool Client::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, b ucDamageBonus = GetWeaponDamageBonus(weapon ? weapon->GetItem() : (const EQEmu::ItemData*) nullptr, true); - min_damage = ucDamageBonus; + my_hit.min_damage = ucDamageBonus; hate += ucDamageBonus; } } - // this effect is actually a min cap that happens after the final damage is calculated - int min_cap = base_damage * GetMeleeMinDamageMod_SE(skillinuse) / 100; - // damage = mod_client_damage(damage, skillinuse, Hand, weapon, other); - 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); + Log.Out(Logs::Detail, Logs::Combat, "Damage calculated: base %d min damage %d skill %d", my_hit.base_damage, my_hit.min_damage, my_hit.skill); int hit_chance_bonus = 0; - auto offense = this->offense(skillinuse); // we need this a few times + my_hit.offense = offense(my_hit.skill); // we need this a few times + my_hit.hand = Hand; if(opts) { - base_damage *= opts->damage_percent; - base_damage += opts->damage_flat; + my_hit.base_damage *= opts->damage_percent; + my_hit.base_damage += opts->damage_flat; hate *= opts->hate_percent; hate += opts->hate_flat; hit_chance_bonus += opts->hit_chance; } - //check to see if we hit.. - if (other->AvoidDamage(this, damage, Hand)) { - if (!bRiposte && !IsStrikethrough) { - int strike_through = itembonuses.StrikeThrough + spellbonuses.StrikeThrough + aabonuses.StrikeThrough; - if(strike_through && zone->random.Roll(strike_through)) { - Message_StringID(MT_StrikeThrough, STRIKETHROUGH_STRING); // You strike through your opponents defenses! - Attack(other, Hand, false, true); // Strikethrough only gives another attempted hit - return false; - } - // I'm pretty sure you can riposte a riposte - if (damage == -3 && !bRiposte) { - DoRiposte(other); - if (IsDead()) - return false; - } - } - 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, 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."); - damage = 0; - } - } + my_hit.tohit = GetTotalToHit(my_hit.skill, hit_chance_bonus); + + DoAttack(other, my_hit, opts); } else { - damage = -5; + my_hit.damage_done = DMG_INVULNERABLE; } // Hate Generation is on a per swing basis, regardless of a hit, miss, or block, its always the same. @@ -1414,28 +1387,28 @@ bool Client::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, b /////////////////////////////////////////////////////////// ////// Send Attack Damage /////////////////////////////////////////////////////////// - if (damage > 0 && aabonuses.SkillAttackProc[0] && aabonuses.SkillAttackProc[1] == skillinuse && + if (my_hit.damage_done > 0 && aabonuses.SkillAttackProc[0] && aabonuses.SkillAttackProc[1] == my_hit.skill && IsValidSpell(aabonuses.SkillAttackProc[2])) { float chance = aabonuses.SkillAttackProc[0] / 1000.0f; if (zone->random.Roll(chance)) 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, m_specialattacks); + other->Damage(this, my_hit.damage_done, SPELL_UNKNOWN, my_hit.skill, true, -1, false, m_specialattacks); if (IsDead()) return false; - MeleeLifeTap(damage); + MeleeLifeTap(my_hit.damage_done); - if (damage > 0 && HasSkillProcSuccess() && other && other->GetHP() > 0) - TrySkillProc(other, skillinuse, 0, true, Hand); + if (my_hit.damage_done > 0 && HasSkillProcSuccess() && other && other->GetHP() > 0) + TrySkillProc(other, my_hit.skill, 0, true, Hand); CommonBreakInvisibleFromCombat(); if(GetTarget()) - TriggerDefensiveProcs(other, Hand, true, damage); + TriggerDefensiveProcs(other, Hand, true, my_hit.damage_done); - if (damage > 0) + if (my_hit.damage_done > 0) return true; else @@ -1768,8 +1741,6 @@ bool Client::Death(Mob* killerMob, int32 damage, uint16 spell, EQEmu::skills::Sk bool NPC::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, bool IsFromSpell, ExtraAttackOptions *opts) { - int damage = 0; - if (!other) { SetTarget(nullptr); Log.Out(Logs::General, Logs::Error, "A null Mob object was passed to NPC::Attack() for evaluation!"); @@ -1797,13 +1768,16 @@ bool NPC::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, bool FaceTarget(GetTarget()); - EQEmu::skills::SkillType skillinuse = EQEmu::skills::SkillHandtoHand; + DamageHitInfo my_hit; + my_hit.skill = EQEmu::skills::SkillHandtoHand; + my_hit.hand = Hand; + my_hit.damage_done = 0; if (Hand == EQEmu::inventory::slotPrimary) { - skillinuse = static_cast(GetPrimSkill()); + my_hit.skill = static_cast(GetPrimSkill()); OffHandAtk(false); } if (Hand == EQEmu::inventory::slotSecondary) { - skillinuse = static_cast(GetSecSkill()); + my_hit.skill = static_cast(GetSecSkill()); OffHandAtk(true); } @@ -1826,32 +1800,32 @@ bool NPC::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, bool switch(weapon->ItemType) { case EQEmu::item::ItemType1HSlash: - skillinuse = EQEmu::skills::Skill1HSlashing; + my_hit.skill = EQEmu::skills::Skill1HSlashing; break; case EQEmu::item::ItemType2HSlash: - skillinuse = EQEmu::skills::Skill2HSlashing; + my_hit.skill = EQEmu::skills::Skill2HSlashing; break; case EQEmu::item::ItemType1HPiercing: - skillinuse = EQEmu::skills::Skill1HPiercing; + my_hit.skill = EQEmu::skills::Skill1HPiercing; break; case EQEmu::item::ItemType2HPiercing: - skillinuse = EQEmu::skills::Skill2HPiercing; + my_hit.skill = EQEmu::skills::Skill2HPiercing; break; case EQEmu::item::ItemType1HBlunt: - skillinuse = EQEmu::skills::Skill1HBlunt; + my_hit.skill = EQEmu::skills::Skill1HBlunt; break; case EQEmu::item::ItemType2HBlunt: - skillinuse = EQEmu::skills::Skill2HBlunt; + my_hit.skill = EQEmu::skills::Skill2HBlunt; break; case EQEmu::item::ItemTypeBow: - skillinuse = EQEmu::skills::SkillArchery; + my_hit.skill = EQEmu::skills::SkillArchery; break; case EQEmu::item::ItemTypeLargeThrowing: case EQEmu::item::ItemTypeSmallThrowing: - skillinuse = EQEmu::skills::SkillThrowing; + my_hit.skill = EQEmu::skills::SkillThrowing; break; default: - skillinuse = EQEmu::skills::SkillHandtoHand; + my_hit.skill = EQEmu::skills::SkillHandtoHand; break; } } @@ -1861,7 +1835,7 @@ bool NPC::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, bool //do attack animation regardless of whether or not we can hit below int16 charges = 0; EQEmu::ItemInstance weapon_inst(weapon, charges); - AttackAnimation(skillinuse, Hand, &weapon_inst); + AttackAnimation(my_hit.skill, Hand, &weapon_inst); //basically "if not immune" then do the attack if(weapon_damage > 0) { @@ -1898,54 +1872,48 @@ bool NPC::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, bool //damage = mod_npc_damage(damage, skillinuse, Hand, weapon, other); - 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; + my_hit.base_damage = GetBaseDamage() + eleBane; + my_hit.min_damage = GetMinDamage(); + int32 hate = my_hit.base_damage + my_hit.min_damage; int hit_chance_bonus = 0; - if(opts) { - lbase_damage *= opts->damage_percent; - lbase_damage += opts->damage_flat; + if (opts) { + my_hit.base_damage *= opts->damage_percent; + my_hit.base_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); - } else { - 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 { - damage = 0; - } - } + my_hit.offense = offense(my_hit.skill); + my_hit.tohit = GetTotalToHit(my_hit.skill, hit_chance_bonus); + + DoAttack(other, my_hit, opts); + other->AddToHateList(this, hate); - Log.Out(Logs::Detail, Logs::Combat, "Final damage against %s: %d", other->GetName(), damage); + Log.Out(Logs::Detail, Logs::Combat, "Final damage against %s: %d", other->GetName(), my_hit.damage_done); if(other->IsClient() && IsPet() && GetOwner()->IsClient()) { //pets do half damage to clients in pvp - damage=damage/2; + my_hit.damage_done /= 2; + if (my_hit.damage_done < 1) + my_hit.damage_done = 1; } + } else { + my_hit.damage_done = DMG_INVULNERABLE; } - else - damage = -5; if(GetHP() > 0 && !other->HasDied()) { - other->Damage(this, damage, SPELL_UNKNOWN, skillinuse, true, -1, false, m_specialattacks); // Not avoidable client already had thier chance to Avoid + other->Damage(this, my_hit.damage_done, SPELL_UNKNOWN, my_hit.skill, true, -1, false, m_specialattacks); // Not avoidable client already had thier chance to Avoid } else return false; if (HasDied()) //killed by damage shield ect return false; - MeleeLifeTap(damage); + MeleeLifeTap(my_hit.damage_done); CommonBreakInvisibleFromCombat(); @@ -1959,14 +1927,14 @@ bool NPC::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, bool if (!other->HasDied()) TrySpellProc(nullptr, weapon, other, Hand); - if (damage > 0 && HasSkillProcSuccess() && !other->HasDied()) - TrySkillProc(other, skillinuse, 0, true, Hand); + if (my_hit.damage_done > 0 && HasSkillProcSuccess() && !other->HasDied()) + TrySkillProc(other, my_hit.skill, 0, true, Hand); } if(GetHP() > 0 && !other->HasDied()) - TriggerDefensiveProcs(other, Hand, true, damage); + TriggerDefensiveProcs(other, Hand, true, my_hit.damage_done); - if (damage > 0) + if (my_hit.damage_done > 0) return true; else @@ -2874,7 +2842,7 @@ int32 Mob::ReduceDamage(int32 damage) if (spellbonuses.NegateAttacks[2] && (damage > spellbonuses.NegateAttacks[2])) damage -= spellbonuses.NegateAttacks[2]; else - return -6; + return DMG_RUNE; } } @@ -2935,13 +2903,13 @@ int32 Mob::ReduceDamage(int32 damage) } if(damage < 1) - return -6; + return DMG_RUNE; if (spellbonuses.MeleeRune[0] && spellbonuses.MeleeRune[1] >= 0) damage = RuneAbsorb(damage, SE_Rune); if(damage < 1) - return -6; + return DMG_RUNE; return(damage); } @@ -3166,11 +3134,11 @@ bool Client::CheckTripleAttack() if (chance < 1) return false; - int per_inc = aabonuses.TripleAttackChance + spellbonuses.TripleAttackChance + itembonuses.TripleAttackChance; - if (per_inc) - chance += chance * per_inc / 100; + int inc = aabonuses.TripleAttackChance + spellbonuses.TripleAttackChance + itembonuses.TripleAttackChance; + chance = static_cast(chance * (1 + inc / 100.0f)); + chance = (chance * 100) / (chance + 800); - return zone->random.Int(1, 1000) <= chance; + return zone->random.Int(1, 100) <= chance; } bool Client::CheckDoubleRangedAttack() { @@ -3923,9 +3891,9 @@ void Mob::TrySpellProc(const EQEmu::ItemInstance *inst, const EQEmu::ItemData *w return; } -void Mob::TryPetCriticalHit(Mob *defender, uint16 skill, int &damage) +void Mob::TryPetCriticalHit(Mob *defender, DamageHitInfo &hit) { - if (damage < 1) + if (hit.damage_done < 1) return; // Allows pets to perform critical hits. @@ -3935,12 +3903,9 @@ void Mob::TryPetCriticalHit(Mob *defender, uint16 skill, int &damage) Mob *owner = nullptr; int critChance = 0; - critChance += RuleI(Combat, MeleeBaseCritChance); + critChance += RuleI(Combat, PetBaseCritChance); // 0 by default int critMod = 170; - if (damage < 1) // We can't critical hit if we don't hit. - return; - if (IsPet()) owner = GetOwner(); else if ((IsNPC() && CastToNPC()->GetSwarmOwner())) @@ -3954,182 +3919,170 @@ void Mob::TryPetCriticalHit(Mob *defender, uint16 skill, int &damage) int CritPetChance = owner->aabonuses.PetCriticalHit + owner->itembonuses.PetCriticalHit + owner->spellbonuses.PetCriticalHit; - if (CritPetChance || critChance) { - + if (CritPetChance || critChance) // For pets use PetCriticalHit for base chance, pets do not innately critical with without it critChance += CritPetChance; - } if (critChance > 0) { if (zone->random.Roll(critChance)) { - critMod += GetCritDmgMob(skill); - damage += 5; - damage = (damage * critMod) / 100; + critMod += GetCritDmgMob(hit.skill); + hit.damage_done += 5; + hit.damage_done = (hit.damage_done * critMod) / 100; entity_list.FilteredMessageClose_StringID(this, false, 200, MT_CritMelee, FilterMeleeCrits, - CRITICAL_HIT, GetCleanName(), itoa(damage)); + CRITICAL_HIT, GetCleanName(), itoa(hit.damage_done + hit.min_damage)); } } } -void Mob::TryCriticalHit(Mob *defender, uint16 skill, int &damage, int min_damage, ExtraAttackOptions *opts) +void Mob::TryCriticalHit(Mob *defender, DamageHitInfo &hit, ExtraAttackOptions *opts) { - if (damage < 1 || !defender) + if (hit.damage_done < 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, hit); return; } #ifdef BOTS if (this->IsPet() && this->GetOwner() && this->GetOwner()->IsBot()) { - this->TryPetCriticalHit(defender, skill, damage); + this->TryPetCriticalHit(defender, hit); return; } #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) { - int32 SlayRateBonus = aabonuses.SlayUndead[0] + itembonuses.SlayUndead[0] + spellbonuses.SlayUndead[0]; + int 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 += 5; - damage = (damage * SlayDmgBonus) / 100; + int SlayDmgBonus = std::max( + {aabonuses.SlayUndead[1], itembonuses.SlayUndead[1], spellbonuses.SlayUndead[1]}); + hit.damage_done = std::max(hit.damage_done, hit.base_damage) + 5; + hit.damage_done = (hit.damage_done * SlayDmgBonus) / 100; if (GetGender() == 1) // female entity_list.FilteredMessageClose_StringID( this, false, 200, MT_CritMelee, FilterMeleeCrits, FEMALE_SLAYUNDEAD, - GetCleanName(), itoa(damage + min_damage)); + GetCleanName(), itoa(hit.damage_done + hit.min_damage)); else // males and neuter I guess entity_list.FilteredMessageClose_StringID( this, false, 200, MT_CritMelee, FilterMeleeCrits, MALE_SLAYUNDEAD, - GetCleanName(), itoa(damage + min_damage)); + GetCleanName(), itoa(hit.damage_done + hit.min_damage)); return; } } } // 2: Try Melee Critical + // a lot of good info: http://giline.versus.jp/shiden/damage_e.htm, http://giline.versus.jp/shiden/su.htm - // 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); - + // We either require an innate crit chance or some SPA 169 to crit + bool innate_crit = false; + int crit_chance = GetCriticalChanceBonus(hit.skill); if (IsClient()) { - critChance += RuleI(Combat, ClientBaseCritChance); - - if (spellbonuses.BerserkSPA || itembonuses.BerserkSPA || aabonuses.BerserkSPA) - IsBerskerSPA = true; - - if (((GetClass() == WARRIOR || GetClass() == BERSERKER) && GetLevel() >= 12) || IsBerskerSPA) { - if (IsBerserk() || IsBerskerSPA) - critChance += RuleI(Combat, BerserkBaseCritChance); - else - critChance += RuleI(Combat, WarBerBaseCritChance); - } + if ((GetClass() == WARRIOR || GetClass() == BERSERKER) && GetLevel() >= 12) + innate_crit = true; + else if (GetClass() == RANGER && GetLevel() >= 12 && hit.skill == EQEmu::skills::SkillArchery) + innate_crit = true; + else if (GetClass() == ROGUE && GetLevel() >= 12 && hit.skill == EQEmu::skills::SkillThrowing) + innate_crit = true; } - int deadlyChance = 0; - int deadlyMod = 0; - if (skill == EQEmu::skills::SkillArchery && GetClass() == RANGER && GetSkill(EQEmu::skills::SkillArchery) >= 65) - critChance += 6; + // we have a chance to crit! + if (innate_crit || crit_chance) { + int difficulty = 0; + if (hit.skill == EQEmu::skills::SkillArchery) + difficulty = RuleI(Combat, ArcheryCritDifficulty); + else if (hit.skill == EQEmu::skills::SkillThrowing) + difficulty = RuleI(Combat, ThrowingCritDifficulty); + else + difficulty = RuleI(Combat, MeleeCritDifficulty); + int roll = zone->random.Int(1, difficulty); - if (skill == EQEmu::skills::SkillThrowing && GetClass() == ROGUE && - GetSkill(EQEmu::skills::SkillThrowing) >= 65) { - critChance += RuleI(Combat, RogueCritThrowingChance); - deadlyChance = RuleI(Combat, RogueDeadlyStrikeChance); - deadlyMod = RuleI(Combat, RogueDeadlyStrikeMod); - } + int dex_bonus = GetDEX(); + if (dex_bonus > 255) + dex_bonus = 255 + ((dex_bonus - 255) / 5); + dex_bonus += 45; // chances did not match live without a small boost - int CritChanceBonus = GetCriticalChanceBonus(skill); + // so if we have an innate crit we have a better chance, except for ber throwing + if (!innate_crit || (GetClass() == BERSERKER && hit.skill == EQEmu::skills::SkillThrowing)) + dex_bonus = dex_bonus * 3 / 5; - if (CritChanceBonus || critChance) { + if (crit_chance) + dex_bonus += dex_bonus * crit_chance / 100; - // 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; - } - - if (opts) { - critChance *= opts->crit_percent; - critChance += opts->crit_flat; - } - - if (critChance > 0) { - - critChance /= 100; - - if (zone->random.Roll(critChance)) { - if (TryFinishingBlow(defender, static_cast(skill), damage)) + // check if we crited + if (roll < dex_bonus) { + // step 1: check for finishing blow + if (TryFinishingBlow(defender, hit.damage_done)) 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. - if (CripplingBlowChance || (IsBerserk() || IsBerskerSPA)) { - if (!IsBerserk() && !IsBerskerSPA) - critChance *= float(CripplingBlowChance) / 100.0f; + // step 2: calculate damage + hit.damage_done = std::max(hit.damage_done, hit.base_damage) + 5; + int og_damage = hit.damage_done; + int crit_mod = 170 + GetCritDmgMob(hit.skill); + hit.damage_done = hit.damage_done * crit_mod / 100; + Log.Out(Logs::Detail, Logs::Combat, + "Crit success roll %d dex chance %d og dmg %d crit_mod %d new dmg %d", roll, dex_bonus, + og_damage, crit_mod, hit.damage_done); - if ((IsBerserk() || IsBerskerSPA) || zone->random.Roll(critChance)) - crip_success = true; - } - - critMod += GetCritDmgMob(skill); - damage += 5; - int ogdmg = damage; - damage = damage * critMod / 100; - - bool deadlySuccess = false; - if (deadlyChance && zone->random.Roll(static_cast(deadlyChance) / 100.0f)) { + // step 3: check deadly strike + if (GetClass() == ROGUE && hit.skill == EQEmu::skills::SkillThrowing) { if (BehindMob(defender, GetX(), GetY())) { - damage *= deadlyMod; - deadlySuccess = true; + int chance = GetLevel() * 12; + if (zone->random.Int(1, 1000) < chance) { + // step 3a: check assassinate + int assdmg = TryAssassinate(defender, hit.skill); // I don't think this is right + if (assdmg) { + hit.damage_done = assdmg; + return; + } + hit.damage_done = hit.damage_done * 200 / 100; + entity_list.FilteredMessageClose_StringID( + this, false, 200, MT_CritMelee, FilterMeleeCrits, DEADLY_STRIKE, + GetCleanName(), itoa(hit.damage_done + hit.min_damage)); + return; + } } } - if (crip_success) { - 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)); + // step 4: check crips + // this SPA was reused on live ... + bool berserk = spellbonuses.BerserkSPA || itembonuses.BerserkSPA || aabonuses.BerserkSPA; + if (!berserk) { + if (zone->random.Roll(GetCrippBlowChance())) { + berserk = true; + } // TODO: Holyforge is suppose to have an innate extra undead chance? 1/5 which matches the SPA crip though ... + } + + if (IsBerserk() || berserk) { + hit.damage_done += og_damage * 119 / 100; + Log.Out(Logs::Detail, Logs::Combat, "Crip damage %d", hit.damage_done); + entity_list.FilteredMessageClose_StringID( + this, false, 200, MT_CritMelee, FilterMeleeCrits, CRIPPLING_BLOW, GetCleanName(), + itoa(hit.damage_done + hit.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)) { defender->Emote("staggers."); - defender->Stun(0); + defender->Stun(2000); } - } else if (deadlySuccess) { - 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 + min_damage)); + return; } + // okay, critted but didn't do anything else, just normal message now + entity_list.FilteredMessageClose_StringID(this, false, 200, MT_CritMelee, FilterMeleeCrits, + CRITICAL_HIT, GetCleanName(), + itoa(hit.damage_done + hit.min_damage)); } } } -bool Mob::TryFinishingBlow(Mob *defender, EQEmu::skills::SkillType skillinuse, int &damage) +bool Mob::TryFinishingBlow(Mob *defender, int &damage) { // base2 of FinishingBlowLvl is the HP limit (cur / max) * 1000, 10% is listed as 100 if (defender && !defender->IsClient() && defender->GetHPRatio() < 10) { @@ -4144,12 +4097,12 @@ bool Mob::TryFinishingBlow(Mob *defender, EQEmu::skills::SkillType skillinuse, i 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; + // modern AA description says rank 1 (500) is 50% chance + int ProcChance = + aabonuses.FinishingBlow[0] + spellbonuses.FinishingBlow[0] + spellbonuses.FinishingBlow[0]; if (FB_Level && FB_Dmg && (defender->GetLevel() <= FB_Level) && - (ProcChance >= zone->random.Int(0, 1000))) { + (ProcChance >= zone->random.Int(1, 1000))) { entity_list.MessageClose_StringID(this, false, 200, MT_CritMelee, FINISHING_BLOW, GetName()); damage = FB_Dmg; return true; @@ -4205,12 +4158,12 @@ void Mob::DoRiposte(Mob *defender) if (defender->GetClass() == MONK) defender->MonkSpecialAttack(this, defender->aabonuses.GiveDoubleRiposte[2]); - else if (defender->IsClient() && defender->CastToClient()->HasSkill((EQEmu::skills::SkillType)defender->aabonuses.GiveDoubleRiposte[2])) + else if (defender->IsClient()) // so yeah, even if you don't have the skill you can still do the attack :P (and we don't crash anymore) defender->CastToClient()->DoClassAttacks(this, defender->aabonuses.GiveDoubleRiposte[2], true); } } -void Mob::ApplyMeleeDamageBonus(uint16 skill, int &damage, ExtraAttackOptions *opts) +void Mob::ApplyMeleeDamageMods(uint16 skill, int &damage, Mob *defender, ExtraAttackOptions *opts) { int dmgbonusmod = 0; @@ -4218,6 +4171,13 @@ void Mob::ApplyMeleeDamageBonus(uint16 skill, int &damage, ExtraAttackOptions *o if (opts) dmgbonusmod += opts->melee_damage_bonus_flat; + if (defender) { + if (defender->IsClient() && defender->GetClass() == WARRIOR) + dmgbonusmod -= 5; + // 168 defensive + dmgbonusmod += (defender->spellbonuses.MeleeMitigationEffect + itembonuses.MeleeMitigationEffect + aabonuses.MeleeMitigationEffect); + } + damage += damage * dmgbonusmod / 100; } @@ -4368,7 +4328,7 @@ const DamageTable &Mob::GetDamageTable() const return which[level - 50]; } -void Mob::ApplyDamageTable(int &damage, int offense) +void Mob::ApplyDamageTable(DamageHitInfo &hit) { // someone may want to add this to custom servers, can remove this if that's the case if (!IsClient() @@ -4378,7 +4338,7 @@ void Mob::ApplyDamageTable(int &damage, int offense) ) 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) + if (hit.offense < 115) return; auto &damage_table = GetDamageTable(); @@ -4386,14 +4346,14 @@ void Mob::ApplyDamageTable(int &damage, int offense) if (zone->random.Roll(damage_table.chance)) return; - int basebonus = offense - damage_table.minusfactor; + int basebonus = hit.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; + hit.damage_done = (hit.damage_done * percent) / 100; if (IsWarriorClass() && GetLevel() > 54) - damage++; + hit.damage_done++; Log.Out(Logs::Detail, Logs::Attack, "Damage table applied %d (max %d)", percent, damage_table.max_extra); } @@ -4696,52 +4656,49 @@ int32 Mob::RuneAbsorb(int32 damage, uint16 type) return damage; } -// 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) +void Mob::CommonOutgoingHitSuccess(Mob* defender, DamageHitInfo &hit, 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 (hit.skill == EQEmu::skills::SkillArchery || + (hit.skill == EQEmu::skills::SkillThrowing && GetClass() != BERSERKER)) + hit.damage_done /= 2; - if (damage < 1) - damage = 1; + if (hit.damage_done < 1) + hit.damage_done = 1; - if (skillInUse == EQEmu::skills::SkillArchery) { + if (hit.skill == 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); + hit.damage_done += hit.damage_done * bonus / 100; + int headshot = TryHeadShot(defender, hit.skill); + if (headshot > 0) { + hit.damage_done = headshot; + } else if (GetClass() == RANGER && GetLevel() > 50) { // no double dmg on headshot + if (defender->IsNPC() && !defender->IsMoving() && !defender->IsRooted()) { + hit.damage_done *= 2; + Message_StringID(MT_CritMelee, BOW_DOUBLE_DAMAGE); + } } } int extra_mincap = 0; - if (skillInUse == EQEmu::skills::SkillBackstab) { + int min_mod = hit.base_damage * GetMeleeMinDamageMod_SE(hit.skill) / 100; + if (hit.skill == 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; + hit.damage_done = extra_mincap; } else { - int ass = TryAssassinate(defender, skillInUse, 10000); // reusetime shouldn't have an effect .... + int ass = TryAssassinate(defender, hit.skill); if (ass > 0) - damage = ass; + hit.damage_done = ass; } - } else if (skillInUse == EQEmu::skills::SkillFrenzy && GetClass() == BERSERKER && GetLevel() > 50) { + } else if (hit.skill == EQEmu::skills::SkillFrenzy && GetClass() == BERSERKER && GetLevel() > 50) { extra_mincap = 4 * GetLevel() / 5; } @@ -4749,23 +4706,23 @@ void Mob::CommonOutgoingHitSuccess(Mob* defender, int &damage, int min_damage, i // 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); + hit.min_damage += GetSkillDmgAmt(hit.skill); // shielding mod2 if (defender->itembonuses.MeleeMitigation) - min_damage -= min_damage * defender->itembonuses.MeleeMitigation / 100; + hit.min_damage -= hit.min_damage * defender->itembonuses.MeleeMitigation / 100; - ApplyMeleeDamageBonus(skillInUse, damage, opts); + ApplyMeleeDamageMods(hit.skill, hit.damage_done, defender, opts); min_mod = std::max(min_mod, extra_mincap); - if (min_mod && damage < min_mod) // SPA 186 - damage = min_mod; + if (min_mod && hit.damage_done < min_mod) // SPA 186 + hit.damage_done = min_mod; - TryCriticalHit(defender, skillInUse, damage, min_damage, opts); + TryCriticalHit(defender, hit, opts); - damage += min_damage; + hit.damage_done += hit.min_damage; if (IsClient()) { int extra = 0; - switch (skillInUse) { + switch (hit.skill) { case EQEmu::skills::SkillThrowing: case EQEmu::skills::SkillArchery: extra = CastToClient()->GetHeroicDEX() / 10; @@ -4774,7 +4731,7 @@ void Mob::CommonOutgoingHitSuccess(Mob* defender, int &damage, int min_damage, i extra = CastToClient()->GetHeroicSTR() / 10; break; } - damage += extra; + hit.damage_done += extra; } // this appears where they do special attack dmg mods @@ -4794,9 +4751,9 @@ void Mob::CommonOutgoingHitSuccess(Mob* defender, int &damage, int min_damage, i spec_mod = mod; } if (spec_mod > 0) - damage = (damage * spec_mod) / 100; + hit.damage_done = (hit.damage_done * spec_mod) / 100; - damage += (damage * defender->GetSkillDmgTaken(skillInUse, opts) / 100) + (defender->GetFcDamageAmtIncoming(this, 0, true, skillInUse)); + hit.damage_done += (hit.damage_done * defender->GetSkillDmgTaken(hit.skill, opts) / 100) + (defender->GetFcDamageAmtIncoming(this, 0, true, hit.skill)); CheckNumHitsRemaining(NumHit::OutgoingHitSuccess); } @@ -5003,23 +4960,22 @@ void Client::DoAttackRounds(Mob *target, int hand, bool IsFromSpell) // you can only triple from the main hand if (hand == EQEmu::inventory::slotPrimary && CanThisClassTripleAttack()) { CheckIncreaseSkill(EQEmu::skills::SkillTripleAttack, target, -10); - if (CheckTripleAttack()) + if (CheckTripleAttack()) { Attack(target, hand, false, false, IsFromSpell); + auto flurrychance = aabonuses.FlurryChance + spellbonuses.FlurryChance + + itembonuses.FlurryChance; + if (flurrychance && zone->random.Roll(flurrychance)) { + Attack(target, hand, false, false, IsFromSpell); + if (zone->random.Roll(flurrychance)) + Attack(target, hand, false, false, IsFromSpell); + Message_StringID(MT_NPCFlurry, YOU_FLURRY); + } + } } } } if (hand == EQEmu::inventory::slotPrimary) { - // According to http://www.monkly-business.net/forums/showpost.php?p=312095&postcount=168 a dev told them flurry isn't dependant on triple attack - // the parses kind of back that up and all of my parses seemed to be 4 or 5 attacks in the round which would work out to be - // doubles or triples with 2 from flurries or triple with 1 or 2 flurries ... Going with the "dev quote" I guess like we've always had it - auto flurrychance = aabonuses.FlurryChance + spellbonuses.FlurryChance + itembonuses.FlurryChance; - if (flurrychance && zone->random.Roll(flurrychance)) { - Attack(target, hand, false, false, IsFromSpell); - Attack(target, hand, false, false, IsFromSpell); - Message_StringID(MT_NPCFlurry, YOU_FLURRY); - } - // I haven't parsed where this guy happens, but it's not part of the normal chain above so this is fine auto extraattackchance = aabonuses.ExtraAttackChance + spellbonuses.ExtraAttackChance + itembonuses.ExtraAttackChance; if (extraattackchance && HasTwoHanderEquipped() && zone->random.Roll(extraattackchance)) Attack(target, hand, false, false, IsFromSpell); diff --git a/zone/bonuses.cpp b/zone/bonuses.cpp index 3acf6cb1d..1f94fb63b 100644 --- a/zone/bonuses.cpp +++ b/zone/bonuses.cpp @@ -1313,8 +1313,9 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon) } case SE_HeadShotLevel: { - if (newbon->HSLevel < base1) - newbon->HSLevel = base1; + if (newbon->HSLevel[0] < base1) + newbon->HSLevel[0] = base1; + newbon->HSLevel[1] = base2; break; } @@ -1327,8 +1328,10 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon) } case SE_AssassinateLevel: { - if (newbon->AssassinateLevel < base1) - newbon->AssassinateLevel = base1; + if (newbon->AssassinateLevel[0] < base1) { + newbon->AssassinateLevel[0] = base1; + newbon->AssassinateLevel[1] = base2; + } break; } @@ -1386,7 +1389,7 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon) } case SE_MeleeMitigation: - newbon->MeleeMitigationEffect -= base1; + newbon->MeleeMitigationEffect += base1; break; case SE_ATK: @@ -1929,8 +1932,8 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne break; case SE_MeleeMitigation: - //for some reason... this value is negative for increased mitigation - new_bonus->MeleeMitigationEffect -= effect_value; + // This value is negative because it counteracts another SPA :P + new_bonus->MeleeMitigationEffect += effect_value; break; case SE_CriticalHitChance: @@ -3026,8 +3029,10 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne case SE_HeadShotLevel: { - if(new_bonus->HSLevel < effect_value) - new_bonus->HSLevel = effect_value; + if(new_bonus->HSLevel[0] < effect_value) { + new_bonus->HSLevel[0] = effect_value; + new_bonus->HSLevel[1] = base2; + } break; } @@ -3042,8 +3047,10 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne case SE_AssassinateLevel: { - if(new_bonus->AssassinateLevel < effect_value) - new_bonus->AssassinateLevel = effect_value; + if(new_bonus->AssassinateLevel[0] < effect_value) { + new_bonus->AssassinateLevel[0] = effect_value; + new_bonus->AssassinateLevel[1] = base2; + } break; } @@ -4649,9 +4656,12 @@ void Mob::NegateSpellsBonuses(uint16 spell_id) break; case SE_HeadShotLevel: - spellbonuses.HSLevel = effect_value; - aabonuses.HSLevel = effect_value; - itembonuses.HSLevel = effect_value; + spellbonuses.HSLevel[0] = effect_value; + aabonuses.HSLevel[0] = effect_value; + itembonuses.HSLevel[0] = effect_value; + spellbonuses.HSLevel[1] = effect_value; + aabonuses.HSLevel[1] = effect_value; + itembonuses.HSLevel[1] = effect_value; break; case SE_Assassinate: @@ -4664,9 +4674,12 @@ void Mob::NegateSpellsBonuses(uint16 spell_id) break; case SE_AssassinateLevel: - spellbonuses.AssassinateLevel = effect_value; - aabonuses.AssassinateLevel = effect_value; - itembonuses.AssassinateLevel = effect_value; + spellbonuses.AssassinateLevel[0] = effect_value; + aabonuses.AssassinateLevel[0] = effect_value; + itembonuses.AssassinateLevel[0] = effect_value; + spellbonuses.AssassinateLevel[1] = effect_value; + aabonuses.AssassinateLevel[1] = effect_value; + itembonuses.AssassinateLevel[1] = effect_value; break; case SE_FinishingBlow: diff --git a/zone/bot.cpp b/zone/bot.cpp index 42ca396af..237c1144b 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -3701,24 +3701,24 @@ bool Bot::Attack(Mob* other, int Hand, bool FromRiposte, bool IsStrikethrough, b // calculate attack_skill and skillinuse depending on hand and weapon // also send Packet to near clients - EQEmu::skills::SkillType skillinuse; - 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); + DamageHitInfo my_hit; + AttackAnimation(my_hit.skill, Hand, weapon); + Log.Out(Logs::Detail, Logs::Combat, "Attacking with %s in slot %d using skill %d", weapon?weapon->GetItem()->Name:"Fist", Hand, my_hit.skill); /// Now figure out damage - int damage = 0; + my_hit.damage_done =0; uint8 mylevel = GetLevel() ? GetLevel() : 1; uint32 hate = 0; if (weapon) hate = (weapon->GetItem()->Damage + weapon->GetItem()->ElemDmgAmt); - int weapon_damage = GetWeaponDamage(other, weapon, &hate); - if (hate == 0 && weapon_damage > 1) - hate = weapon_damage; + my_hit.base_damage = GetWeaponDamage(other, weapon, &hate); + if (hate == 0 && my_hit.base_damage > 1) + hate = my_hit.base_damage; //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) { - int min_damage = 0; + if (my_hit.base_damage > 0) { + my_hit.min_damage = 0; // *************************************************************** // *** Calculate the damage bonus, if applicable, for this hit *** @@ -3736,7 +3736,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_damage = ucDamageBonus; + my_hit.min_damage = ucDamageBonus; hate += ucDamageBonus; } #endif @@ -3744,55 +3744,29 @@ 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_damage = ucDamageBonus; + my_hit.min_damage = ucDamageBonus; hate += ucDamageBonus; } } - int min_cap = (weapon_damage * GetMeleeMinDamageMod_SE(skillinuse) / 100); + Log.Out(Logs::Detail, Logs::Combat, "Damage calculated: base %d min damage %d skill %d", my_hit.base_damage, my_hit.min_damage, my_hit.skill); - 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, weapon_damage, GetSTR(), GetSkill(skillinuse), weapon_damage, mylevel); + my_hit.offense = offense(my_hit.skill); + my_hit.hand = Hand; - auto offense = this->offense(skillinuse); - - if(opts) { - weapon_damage *= opts->damage_percent; - weapon_damage += opts->damage_flat; + if (opts) { + my_hit.base_damage *= opts->damage_percent; + my_hit.base_damage += opts->damage_flat; hate *= opts->hate_percent; hate += opts->hate_flat; } - //check to see if we hit.. - if (other->AvoidDamage(this, damage, Hand)) { - if (!FromRiposte && !IsStrikethrough) { - int strike_through = itembonuses.StrikeThrough + spellbonuses.StrikeThrough + aabonuses.StrikeThrough; - if(strike_through && zone->random.Roll(strike_through)) { - Message_StringID(MT_StrikeThrough, STRIKETHROUGH_STRING); // You strike through your opponents defenses! - Attack(other, Hand, false, true); // Strikethrough only gives another attempted hit - return false; - } - if (damage == -3 && !FromRiposte) { - DoRiposte(other); - if (HasDied()) - return false; - } - } - } else { - if (other->CheckHitChance(this, skillinuse)) { - other->MeleeMitigation(this, damage, weapon_damage, offense, skillinuse, opts); - if (damage > 0) { - ApplyDamageTable(damage, offense); - CommonOutgoingHitSuccess(other, damage, min_damage, min_cap, skillinuse, opts); - } - } else { - damage = 0; - } - } - Log.Out(Logs::Detail, Logs::Combat, "Final damage after all reductions: %d", damage); + DoAttack(other, my_hit, opts); + + Log.Out(Logs::Detail, Logs::Combat, "Final damage after all reductions: %d", my_hit.damage_done); + } else { + my_hit.damage_done = DMG_INVULNERABLE; } - else - damage = -5; // Hate Generation is on a per swing basis, regardless of a hit, miss, or block, its always the same. // If we are this far, this means we are atleast making a swing. @@ -3801,14 +3775,14 @@ bool Bot::Attack(Mob* other, int Hand, bool FromRiposte, bool IsStrikethrough, b /////////////////////////////////////////////////////////// ////// Send Attack Damage /////////////////////////////////////////////////////////// - other->Damage(this, damage, SPELL_UNKNOWN, skillinuse); + other->Damage(this, my_hit.damage_done, SPELL_UNKNOWN, my_hit.skill); if (GetHP() < 0) return false; - MeleeLifeTap(damage); + MeleeLifeTap(my_hit.damage_done); - if (damage > 0) + if (my_hit.damage_done > 0) CheckNumHitsRemaining(NumHit::OutgoingHitSuccess); CommonBreakInvisibleFromCombat(); @@ -3816,9 +3790,9 @@ bool Bot::Attack(Mob* other, int Hand, bool FromRiposte, bool IsStrikethrough, b BuffFadeByEffect(SE_NegateIfCombat); if(GetTarget()) - TriggerDefensiveProcs(other, Hand, true, damage); + TriggerDefensiveProcs(other, Hand, true, my_hit.damage_done); - if (damage > 0) + if (my_hit.damage_done > 0) return true; else return false; @@ -4699,16 +4673,16 @@ int Bot::GetHandToHandDamage(void) { return 2; } -bool Bot::TryFinishingBlow(Mob *defender, EQEmu::skills::SkillType skillinuse, int &damage) +bool Bot::TryFinishingBlow(Mob *defender, int &damage) { if (!defender) return false; if (aabonuses.FinishingBlow[1] && !defender->IsClient() && defender->GetHPRatio() < 10) { - int chance = (aabonuses.FinishingBlow[0] / 10); + int chance = aabonuses.FinishingBlow[0]; int fb_damage = aabonuses.FinishingBlow[1]; int levelreq = aabonuses.FinishingBlowLvl[0]; - if (defender->GetLevel() <= levelreq && (chance >= zone->random.Int(0, 1000))) { + if (defender->GetLevel() <= levelreq && (chance >= zone->random.Int(1, 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()); @@ -4849,36 +4823,29 @@ void Bot::DoSpecialAttackDamage(Mob *who, EQEmu::skills::SkillType skill, int32 } } - int min_cap = max_damage * GetMeleeMinDamageMod_SE(skill) / 100; - int hand = EQEmu::inventory::slotPrimary; - int damage = 0; - auto offense = this->offense(skill); + DamageHitInfo my_hit; + my_hit.base_damage = max_damage; + my_hit.min_damage = min_damage; + my_hit.damage_done = 0; + + my_hit.skill = skill; + my_hit.offense = offense(my_hit.skill); + my_hit.tohit = GetTotalToHit(my_hit.skill, 0); + my_hit.hand = EQEmu::inventory::slotPrimary; + if (skill == EQEmu::skills::SkillThrowing || skill == EQEmu::skills::SkillArchery) - hand = EQEmu::inventory::slotRange; - if (who->AvoidDamage(this, damage, hand)) { - if (damage == -3) - DoRiposte(who); - } else { - if (HitChance || who->CheckHitChance(this, skill)) { - 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 { - damage = 0; - } - } + my_hit.hand = EQEmu::inventory::slotRange; + + DoAttack(who, my_hit); who->AddToHateList(this, hate); - who->Damage(this, damage, SPELL_UNKNOWN, skill, false); + who->Damage(this, my_hit.damage_done, SPELL_UNKNOWN, skill, false); if(!GetTarget() || HasDied()) return; - if (damage > 0) + if (my_hit.damage_done > 0) CheckNumHitsRemaining(NumHit::OutgoingHitSuccess); //[AA Dragon Punch] value[0] = 100 for 25%, chance value[1] = skill @@ -4894,7 +4861,7 @@ void Bot::DoSpecialAttackDamage(Mob *who, EQEmu::skills::SkillType skill, int32 if (HasSkillProcs()) TrySkillProc(who, skill, (ReuseTime * 1000)); - if (damage > 0 && HasSkillProcSuccess()) + if (my_hit.damage_done > 0 && HasSkillProcSuccess()) TrySkillProc(who, skill, (ReuseTime * 1000), true); } @@ -5030,7 +4997,6 @@ void Bot::DoClassAttacks(Mob *target, bool IsRiposte) { return; float HasteModifier = (GetHaste() * 0.01f); - int32 dmg = 0; uint16 skill_to_use = -1; int level = GetLevel(); int reuse = (TauntReuseTime * 1000); @@ -5084,18 +5050,14 @@ void Bot::DoClassAttacks(Mob *target, bool IsRiposte) { if(skill_to_use == -1) return; + int dmg = GetBaseSkillDamage(static_cast(skill_to_use), GetTarget()); + if (skill_to_use == EQEmu::skills::SkillBash) { if (target != this) { DoAnim(animTailRake); if (GetWeaponDamage(target, GetBotItem(EQEmu::inventory::slotSecondary)) <= 0 && GetWeaponDamage(target, GetBotItem(EQEmu::inventory::slotShoulders)) <= 0) - dmg = -5; - else { - if (!target->CheckHitChance(this, EQEmu::skills::SkillBash, 0)) - dmg = 0; - else { - dmg = GetBaseSkillDamage(EQEmu::skills::SkillBash); - } - } + dmg = DMG_INVULNERABLE; + reuse = (BashReuseTime * 1000); DoSpecialAttackDamage(target, EQEmu::skills::SkillBash, dmg, 0, -1, reuse); did_attack = true; @@ -5104,14 +5066,13 @@ void Bot::DoClassAttacks(Mob *target, bool IsRiposte) { if (skill_to_use == EQEmu::skills::SkillFrenzy) { int AtkRounds = 3; - int32 max_dmg = GetBaseSkillDamage(EQEmu::skills::SkillFrenzy); DoAnim(anim2HSlashing); 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, 0, max_dmg, reuse, true); + DoSpecialAttackDamage(GetTarget(), EQEmu::skills::SkillFrenzy, dmg, 0, dmg, reuse, true); } AtkRounds--; @@ -5122,14 +5083,8 @@ void Bot::DoClassAttacks(Mob *target, bool IsRiposte) { if(target != this) { DoAnim(animKick); if (GetWeaponDamage(target, GetBotItem(EQEmu::inventory::slotFeet)) <= 0) - dmg = -5; - else { - if (!target->CheckHitChance(this, EQEmu::skills::SkillKick, 0)) - dmg = 0; - else { - dmg = GetBaseSkillDamage(EQEmu::skills::SkillKick); - } - } + dmg = DMG_INVULNERABLE; + reuse = (KickReuseTime * 1000); DoSpecialAttackDamage(target, EQEmu::skills::SkillKick, dmg, 0, -1, reuse); did_attack = true; diff --git a/zone/bot.h b/zone/bot.h index 177efeffb..5431df341 100644 --- a/zone/bot.h +++ b/zone/bot.h @@ -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, int &damage); + virtual bool TryFinishingBlow(Mob *defender, 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; } diff --git a/zone/common.h b/zone/common.h index 5a6782148..2ed076b6f 100644 --- a/zone/common.h +++ b/zone/common.h @@ -35,6 +35,13 @@ #define CON_YELLOW 15 #define CON_RED 13 +#define DMG_BLOCKED -1 +#define DMG_PARRIED -2 +#define DMG_RIPOSTED -3 +#define DMG_DODGED -4 +#define DMG_INVULNERABLE -5 +#define DMG_RUNE -6 + //Spell specialization parameters, not sure of a better place for them #define SPECIALIZE_FIZZLE 11 //% fizzle chance reduce at 200 specialized #define SPECIALIZE_MANA_REDUCE 12 //% mana cost reduction at 200 specialized @@ -464,9 +471,9 @@ struct StatBonuses { int8 CriticalMend; // chance critical monk mend int32 ImprovedReclaimEnergy; // Modifies amount of mana returned from reclaim energy uint32 HeadShot[2]; // Headshot AA (Massive dmg vs humaniod w/ archery) 0= ? 1= Dmg - uint8 HSLevel; // Max Level Headshot will be effective at. + uint8 HSLevel[2]; // Max Level Headshot will be effective at. and chance mod uint32 Assassinate[2]; // Assassinate AA (Massive dmg vs humaniod w/ assassinate) 0= ? 1= Dmg - uint8 AssassinateLevel; // Max Level Assassinate will be effective at. + uint8 AssassinateLevel[2]; // Max Level Assassinate will be effective at. int32 PetMeleeMitigation; // Add AC to owner's pet. bool IllusionPersistence; // Causes illusions not to fade. uint16 extra_xtargets; // extra xtarget entries @@ -644,5 +651,17 @@ struct DamageTable { int32 minusfactor; // difficulty of rolling }; +struct DamageHitInfo { + //uint16 attacker; // id + //uint16 defender; // id + int base_damage; + int min_damage; + int damage_done; + int offense; + int tohit; + int hand; + EQEmu::skills::SkillType skill; +}; + #endif diff --git a/zone/lua_mob.cpp b/zone/lua_mob.cpp index d13ff4a52..add24d861 100644 --- a/zone/lua_mob.cpp +++ b/zone/lua_mob.cpp @@ -1270,12 +1270,6 @@ void Lua_Mob::DoSpecialAttackDamage(Lua_Mob other, int skill, int max_damage, in self->DoSpecialAttackDamage(other, static_cast(skill), max_damage, min_damage, hate_override, reuse_time); } -void Lua_Mob::DoSpecialAttackDamage(Lua_Mob other, int skill, int max_damage, int min_damage, int hate_override, int reuse_time, - bool hit_chance) { - Lua_Safe_Call_Void(); - self->DoSpecialAttackDamage(other, static_cast(skill), max_damage, min_damage, hate_override, reuse_time, hit_chance); -} - void Lua_Mob::DoThrowingAttackDmg(Lua_Mob other) { Lua_Safe_Call_Void(); self->DoThrowingAttackDmg(other); @@ -2218,7 +2212,6 @@ luabind::scope lua_register_mob() { .def("DoSpecialAttackDamage", (void(Lua_Mob::*)(Lua_Mob,int,int,int))&Lua_Mob::DoSpecialAttackDamage) .def("DoSpecialAttackDamage", (void(Lua_Mob::*)(Lua_Mob,int,int,int,int))&Lua_Mob::DoSpecialAttackDamage) .def("DoSpecialAttackDamage", (void(Lua_Mob::*)(Lua_Mob,int,int,int,int,int))&Lua_Mob::DoSpecialAttackDamage) - .def("DoSpecialAttackDamage", (void(Lua_Mob::*)(Lua_Mob,int,int,int,int,int,bool))&Lua_Mob::DoSpecialAttackDamage) .def("DoThrowingAttackDmg", (void(Lua_Mob::*)(Lua_Mob))&Lua_Mob::DoThrowingAttackDmg) .def("DoThrowingAttackDmg", (void(Lua_Mob::*)(Lua_Mob,Lua_ItemInst))&Lua_Mob::DoThrowingAttackDmg) .def("DoThrowingAttackDmg", (void(Lua_Mob::*)(Lua_Mob,Lua_ItemInst,Lua_Item))&Lua_Mob::DoThrowingAttackDmg) diff --git a/zone/lua_mob.h b/zone/lua_mob.h index 4fd1f0c69..37bde994a 100644 --- a/zone/lua_mob.h +++ b/zone/lua_mob.h @@ -267,7 +267,6 @@ public: void DoSpecialAttackDamage(Lua_Mob other, int skill, int max_damage, int min_damage); void DoSpecialAttackDamage(Lua_Mob other, int skill, int max_damage, int min_damage, int hate_override); void DoSpecialAttackDamage(Lua_Mob other, int skill, int max_damage, int min_damage, int hate_override, int reuse_time); - void DoSpecialAttackDamage(Lua_Mob other, int skill, int max_damage, int min_damage, int hate_override, int reuse_time, bool hit_chance); void DoThrowingAttackDmg(Lua_Mob other); void DoThrowingAttackDmg(Lua_Mob other, Lua_ItemInst range_weapon); void DoThrowingAttackDmg(Lua_Mob other, Lua_ItemInst range_weapon, Lua_Item item); diff --git a/zone/merc.cpp b/zone/merc.cpp index 6021f05d5..be8f75e60 100644 --- a/zone/merc.cpp +++ b/zone/merc.cpp @@ -4426,15 +4426,10 @@ void Merc::DoClassAttacks(Mob *target) { if(zone->random.Int(0, 100) > 25) //tested on live, warrior mobs both kick and bash, kick about 75% of the time, casting doesn't seem to make a difference. { DoAnim(animKick); - int32 dmg = 0; + int32 dmg = GetBaseSkillDamage(EQEmu::skills::SkillKick); - if (GetWeaponDamage(target, (const EQEmu::ItemData*)nullptr) <= 0){ - dmg = -5; - } - else{ - if (target->CheckHitChance(this, EQEmu::skills::SkillKick, 0)) - dmg = GetBaseSkillDamage(EQEmu::skills::SkillKick, GetTarget()); - } + if (GetWeaponDamage(target, (const EQEmu::ItemData*)nullptr) <= 0) + dmg = DMG_INVULNERABLE; reuse = KickReuseTime * 1000; DoSpecialAttackDamage(target, EQEmu::skills::SkillKick, dmg, 1, -1, reuse); @@ -4443,15 +4438,10 @@ void Merc::DoClassAttacks(Mob *target) { else { DoAnim(animTailRake); - int32 dmg = 0; + int32 dmg = GetBaseSkillDamage(EQEmu::skills::SkillBash); - if (GetWeaponDamage(target, (const EQEmu::ItemData*)nullptr) <= 0){ - dmg = -5; - } - else{ - if (target->CheckHitChance(this, EQEmu::skills::SkillBash, 0)) - dmg = GetBaseSkillDamage(EQEmu::skills::SkillBash, GetTarget()); - } + if (GetWeaponDamage(target, (const EQEmu::ItemData*)nullptr) <= 0) + dmg = DMG_INVULNERABLE; reuse = BashReuseTime * 1000; DoSpecialAttackDamage(target, EQEmu::skills::SkillBash, dmg, 1, -1, reuse); diff --git a/zone/mob.h b/zone/mob.h index 0dfa162c1..d6fd610cd 100644 --- a/zone/mob.h +++ b/zone/mob.h @@ -162,21 +162,22 @@ public: // 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) = 0; + void DoAttack(Mob *other, DamageHitInfo &hit, ExtraAttackOptions *opts = nullptr); int MonkSpecialAttack(Mob* other, uint8 skill_used); virtual void TryBackstab(Mob *other,int ReuseTime = 10); - bool AvoidDamage(Mob* attacker, int &damage, int hand); + bool AvoidDamage(Mob *attacker, DamageHitInfo &hit); int compute_tohit(EQEmu::skills::SkillType skillinuse); int GetTotalToHit(EQEmu::skills::SkillType skill, int chance_mod); // compute_tohit + spell bonuses int compute_defense(); int GetTotalDefense(); // compute_defense + spell bonuses - 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); + bool CheckHitChance(Mob* attacker, DamageHitInfo &hit); + void TryCriticalHit(Mob *defender, DamageHitInfo &hit, ExtraAttackOptions *opts = nullptr); + void TryPetCriticalHit(Mob *defender, DamageHitInfo &hit); + virtual bool TryFinishingBlow(Mob *defender, int &damage); int TryHeadShot(Mob* defender, EQEmu::skills::SkillType skillInUse); - int TryAssassinate(Mob* defender, EQEmu::skills::SkillType skillInUse, uint16 ReuseTime); + int TryAssassinate(Mob* defender, EQEmu::skills::SkillType skillInUse); virtual void DoRiposte(Mob* defender); - void ApplyMeleeDamageBonus(uint16 skill, int &damage,ExtraAttackOptions *opts = nullptr); + void ApplyMeleeDamageMods(uint16 skill, int &damage, Mob * defender = nullptr, ExtraAttackOptions *opts = nullptr); int ACSum(); int offense(EQEmu::skills::SkillType skill); void CalcAC() { mitigation_ac = ACSum(); } @@ -184,12 +185,12 @@ public: 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 + void MeleeMitigation(Mob *attacker, DamageHitInfo &hit, ExtraAttackOptions *opts = nullptr); + double RollD20(int offense, int 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, int &damage, int min_damage, int min_mod, EQEmu::skills::SkillType skillInUse, ExtraAttackOptions *opts = nullptr); + void CommonOutgoingHitSuccess(Mob *defender, DamageHitInfo &hit, ExtraAttackOptions *opts = nullptr); void BreakInvisibleSpells(); virtual void CancelSneakHide(); void CommonBreakInvisible(); @@ -794,7 +795,7 @@ public: uint8 GetWeaponDamageBonus(const EQEmu::ItemData* weapon, bool offhand = false); const DamageTable &GetDamageTable() const; - void ApplyDamageTable(int &damage, int offense); + void ApplyDamageTable(DamageHitInfo &hit); virtual int GetHandToHandDamage(void); bool CanThisClassDoubleAttack(void) const; @@ -817,7 +818,7 @@ public: int32 AffectMagicalDamage(int32 damage, uint16 spell_id, const bool iBuffTic, Mob* attacker); int32 ReduceAllDamage(int32 damage); - 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); + void DoSpecialAttackDamage(Mob *who, EQEmu::skills::SkillType skill, int base_damage, int min_damage = 0, int32 hate_override = -1, int ReuseTime = 10); 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); 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); @@ -1187,8 +1188,6 @@ protected: void ExecWeaponProc(const EQEmu::ItemInstance* weapon, uint16 spell_id, Mob *on, int level_override = -1); virtual float GetProcChances(float ProcBonus, uint16 hand = EQEmu::inventory::slotPrimary); virtual float GetDefensiveProcChances(float &ProcBonus, float &ProcChance, uint16 hand = EQEmu::inventory::slotPrimary, Mob *on = nullptr); - virtual float GetSpecialProcChances(uint16 hand); - virtual float GetAssassinateProcChances(uint16 ReuseTime); virtual float GetSkillProcChances(uint16 ReuseTime, uint16 hand = 0); // hand = MainCharm? uint16 GetWeaponSpeedbyHand(uint16 hand); int GetWeaponDamage(Mob *against, const EQEmu::ItemData *weapon_item); diff --git a/zone/special_attacks.cpp b/zone/special_attacks.cpp index 929c272d9..7f6ec112d 100644 --- a/zone/special_attacks.cpp +++ b/zone/special_attacks.cpp @@ -131,7 +131,7 @@ int Mob::GetBaseSkillDamage(EQEmu::skills::SkillType skill, Mob *target) } void Mob::DoSpecialAttackDamage(Mob *who, EQEmu::skills::SkillType skill, int32 base_damage, int32 min_damage, - int32 hate_override, int ReuseTime, bool CheckHitChance, bool CanAvoid) + int32 hate_override, int ReuseTime) { // this really should go through the same code as normal melee damage to // pick up all the special behavior there @@ -141,15 +141,22 @@ void Mob::DoSpecialAttackDamage(Mob *who, EQEmu::skills::SkillType skill, int32 (!IsAttackAllowed(who)))) return; - int damage = 0; + DamageHitInfo my_hit; + my_hit.damage_done = 0; + my_hit.base_damage = base_damage; + my_hit.min_damage = min_damage; + my_hit.skill = skill; + + if (my_hit.base_damage == 0) + my_hit.base_damage = GetBaseSkillDamage(my_hit.skill); if (who->GetInvul() || who->GetSpecialAbility(IMMUNE_MELEE)) - damage = -5; + my_hit.damage_done = DMG_INVULNERABLE; if (who->GetSpecialAbility(IMMUNE_MELEE_EXCEPT_BANE) && skill != EQEmu::skills::SkillBackstab) - damage = -5; + my_hit.damage_done = DMG_INVULNERABLE; - uint32 hate = base_damage; + uint32 hate = my_hit.base_damage; if (hate_override > -1) hate = hate_override; @@ -169,39 +176,26 @@ void Mob::DoSpecialAttackDamage(Mob *who, EQEmu::skills::SkillType skill, int32 } } - int min_cap = base_damage * GetMeleeMinDamageMod_SE(skill) / 100; + my_hit.offense = offense(my_hit.skill); + my_hit.tohit = GetTotalToHit(my_hit.skill, 0); - auto offense = this->offense(skill); - - int hand = EQEmu::inventory::slotPrimary; // Avoid checks hand for throwing/archery exclusion, primary should + my_hit.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, damage, hand)) { - if (damage == -3) - DoRiposte(who); - } else { - 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 { - damage = 0; - } - } + my_hit.hand = EQEmu::inventory::slotRange; + + DoAttack(who, my_hit); who->AddToHateList(this, hate, 0, false); - if (damage > 0 && aabonuses.SkillAttackProc[0] && aabonuses.SkillAttackProc[1] == skill && + if (my_hit.damage_done > 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, damage, SPELL_UNKNOWN, skill, false); + + who->Damage(this, my_hit.damage_done, SPELL_UNKNOWN, skill, false); // Make sure 'this' has not killed the target and 'this' is not dead (Damage shield ect). if (!GetTarget()) @@ -212,7 +206,7 @@ void Mob::DoSpecialAttackDamage(Mob *who, EQEmu::skills::SkillType skill, int32 if (HasSkillProcs()) TrySkillProc(who, skill, ReuseTime * 1000); - if (damage > 0 && HasSkillProcSuccess()) + if (my_hit.damage_done > 0 && HasSkillProcSuccess()) TrySkillProc(who, skill, ReuseTime * 1000, true); } @@ -302,7 +296,7 @@ void Client::OPCombatAbility(const CombatAbility_Struct *ca_atk) ReuseTime = BashReuseTime - 1 - skill_reduction; ReuseTime = (ReuseTime * HasteMod) / 100; - DoSpecialAttackDamage(GetTarget(), EQEmu::skills::SkillBash, dmg, 0, ht, ReuseTime, true); + DoSpecialAttackDamage(GetTarget(), EQEmu::skills::SkillBash, dmg, 0, ht, ReuseTime); if (ReuseTime > 0) p_timers.Start(timer, ReuseTime); } @@ -311,21 +305,26 @@ void Client::OPCombatAbility(const CombatAbility_Struct *ca_atk) if (ca_atk->m_atk == 100 && ca_atk->m_skill == EQEmu::skills::SkillFrenzy) { CheckIncreaseSkill(EQEmu::skills::SkillFrenzy, GetTarget(), 10); - int AtkRounds = 3; + int AtkRounds = 1; int32 max_dmg = GetBaseSkillDamage(EQEmu::skills::SkillFrenzy, GetTarget()); DoAnim(anim2HSlashing); max_dmg = mod_frenzy_damage(max_dmg); + if (GetClass() == BERSERKER) { + int chance = GetLevel() * 2 + GetSkill(EQEmu::skills::SkillFrenzy); + if (zone->random.Roll0(450) < chance) + AtkRounds++; + if (zone->random.Roll0(450) < chance) + AtkRounds++; + } + 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, 0, max_dmg, - ReuseTime, true); - } + if (GetTarget()) + DoSpecialAttackDamage(GetTarget(), EQEmu::skills::SkillFrenzy, max_dmg, 0, max_dmg, ReuseTime); AtkRounds--; } @@ -352,7 +351,7 @@ void Client::OPCombatAbility(const CombatAbility_Struct *ca_atk) ht = dmg = GetBaseSkillDamage(EQEmu::skills::SkillKick, GetTarget()); ReuseTime = KickReuseTime - 1 - skill_reduction; - DoSpecialAttackDamage(GetTarget(), EQEmu::skills::SkillKick, dmg, 0, ht, ReuseTime, true); + DoSpecialAttackDamage(GetTarget(), EQEmu::skills::SkillKick, dmg, 0, ht, ReuseTime); } break; case MONK: { @@ -362,25 +361,28 @@ void Client::OPCombatAbility(const CombatAbility_Struct *ca_atk) int wuchance = itembonuses.DoubleSpecialAttack + spellbonuses.DoubleSpecialAttack + aabonuses.DoubleSpecialAttack; if (wuchance) { - if (wuchance >= 100 || zone->random.Roll(wuchance)) { - const int MonkSPA[5] = {EQEmu::skills::SkillFlyingKick, EQEmu::skills::SkillDragonPunch, - EQEmu::skills::SkillEagleStrike, EQEmu::skills::SkillTigerClaw, - EQEmu::skills::SkillRoundKick}; - int extra = 1; - // always 1/4 of the double attack chance, 25% at rank 5 (100/4) - if (zone->random.Roll(wuchance / 4)) + const int MonkSPA[5] = {EQEmu::skills::SkillFlyingKick, EQEmu::skills::SkillDragonPunch, + EQEmu::skills::SkillEagleStrike, EQEmu::skills::SkillTigerClaw, + EQEmu::skills::SkillRoundKick}; + int extra = 0; + // always 1/4 of the double attack chance, 25% at rank 5 (100/4) + while (wuchance > 0) { + if (zone->random.Roll(wuchance)) extra++; - // They didn't add a string ID for this. - std::string msg = StringFormat( - "The spirit of Master Wu fills you! You gain %d additional attack(s).", extra); - // live uses 400 here -- not sure if it's the best for all clients though - SendColoredText(400, msg); - auto classic = RuleB(Combat, ClassicMasterWu); - while (extra) { - MonkSpecialAttack(GetTarget(), - classic ? MonkSPA[zone->random.Int(0, 4)] : ca_atk->m_skill); - extra--; - } + else + break; + wuchance /= 4; + } + // They didn't add a string ID for this. + std::string msg = StringFormat( + "The spirit of Master Wu fills you! You gain %d additional attack(s).", extra); + // live uses 400 here -- not sure if it's the best for all clients though + SendColoredText(400, msg); + auto classic = RuleB(Combat, ClassicMasterWu); + while (extra) { + MonkSpecialAttack(GetTarget(), + classic ? MonkSPA[zone->random.Int(0, 4)] : ca_atk->m_skill); + extra--; } } @@ -476,11 +478,11 @@ int Mob::MonkSpecialAttack(Mob *other, uint8 unchecked_type) if (IsClient()) { if (GetWeaponDamage(other, CastToClient()->GetInv().GetItem(itemslot)) <= 0) { - max_dmg = -5; + max_dmg = DMG_INVULNERABLE; } } else { if (GetWeaponDamage(other, (const EQEmu::ItemData *)nullptr) <= 0) { - max_dmg = -5; + max_dmg = DMG_INVULNERABLE; } } @@ -491,7 +493,7 @@ int Mob::MonkSpecialAttack(Mob *other, uint8 unchecked_type) // This can potentially stack with changes to kick damage ht = ndamage = mod_monk_special_damage(ndamage, skill_type); - DoSpecialAttackDamage(other, skill_type, max_dmg, min_dmg, ht, reuse, true); + DoSpecialAttackDamage(other, skill_type, max_dmg, min_dmg, ht, reuse); return reuse; } @@ -532,6 +534,7 @@ void Mob::TryBackstab(Mob *other, int ReuseTime) { RogueBackstab(other,false,ReuseTime); if (level > 54) { + // TODO: 55-59 doesn't appear to match just checking double attack, 60+ does though if(IsClient() && CastToClient()->CheckDoubleAttack()) { if(other->GetHP() > 0) @@ -583,7 +586,7 @@ void Mob::RogueBackstab(Mob* other, bool min_damage, int ReuseTime) int base_damage = GetBaseSkillDamage(EQEmu::skills::SkillBackstab, other); hate = base_damage; - DoSpecialAttackDamage(other, EQEmu::skills::SkillBackstab, base_damage, 0, hate, ReuseTime, true, false); + DoSpecialAttackDamage(other, EQEmu::skills::SkillBackstab, base_damage, 0, hate, ReuseTime); DoAnim(anim1HPiercing); } @@ -732,23 +735,20 @@ void Client::RangedAttack(Mob* other, bool CanDoubleAttack) { CommonBreakInvisibleFromCombat(); } -void Mob::DoArcheryAttackDmg(Mob* other, const EQEmu::ItemInstance* RangeWeapon, const EQEmu::ItemInstance* Ammo, uint16 weapon_damage, int16 chance_mod, int16 focus, int ReuseTime, - uint32 range_id, uint32 ammo_id, const EQEmu::ItemData *AmmoItem, int AmmoSlot, float speed) { +void Mob::DoArcheryAttackDmg(Mob *other, const EQEmu::ItemInstance *RangeWeapon, const EQEmu::ItemInstance *Ammo, + uint16 weapon_damage, int16 chance_mod, int16 focus, int ReuseTime, uint32 range_id, + uint32 ammo_id, const EQEmu::ItemData *AmmoItem, 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::ItemInstance* _Ammo = nullptr; - const EQEmu::ItemData* ammo_lost = nullptr; + const EQEmu::ItemInstance *_RangeWeapon = nullptr; + const EQEmu::ItemInstance *_Ammo = nullptr; + const EQEmu::ItemData *ammo_lost = nullptr; /* If LaunchProjectile is false this function will do archery damage on target, @@ -756,32 +756,24 @@ void Mob::DoArcheryAttackDmg(Mob* other, const EQEmu::ItemInstance* RangeWeapon this function is then run again to do the damage portion */ bool LaunchProjectile = false; - bool ProjectileImpact = false; - bool ProjectileMiss = false; - if (RuleB(Combat, ProjectileDmgOnImpact)){ - - if (AmmoItem) + if (RuleB(Combat, ProjectileDmgOnImpact)) { + if (AmmoItem) { // won't be null when we are firing the arrow LaunchProjectile = true; - else{ + } else { /* Item sync check on projectile landing. Weapon damage is already calculated so this only affects procs! Ammo proc check will use database to find proc if you used up your last ammo. - If you change range item mid projectile flight, you loose your chance to proc from bow (Deal with it!). + If you change range item mid projectile flight, you loose your chance to proc from bow (Deal + with it!). */ - if (!RangeWeapon && !Ammo && range_id && ammo_id){ - - ProjectileImpact = true; - - if (weapon_damage == 0) - ProjectileMiss = true; //This indicates that MISS was originally calculated. - - if (IsClient()){ - + if (!RangeWeapon && !Ammo && range_id && ammo_id) { + if (IsClient()) { _RangeWeapon = CastToClient()->m_inv[EQEmu::inventory::slotRange]; - if (_RangeWeapon && _RangeWeapon->GetItem() && _RangeWeapon->GetItem()->ID == range_id) + if (_RangeWeapon && _RangeWeapon->GetItem() && + _RangeWeapon->GetItem()->ID == range_id) RangeWeapon = _RangeWeapon; _Ammo = CastToClient()->m_inv[AmmoSlot]; @@ -792,103 +784,96 @@ void Mob::DoArcheryAttackDmg(Mob* other, const EQEmu::ItemInstance* RangeWeapon } } } - } - else if (AmmoItem) + } else if (AmmoItem) { SendItemAnimation(other, AmmoItem, EQEmu::skills::SkillArchery); - - if (ProjectileMiss || (!ProjectileImpact && !other->CheckHitChance(this, EQEmu::skills::SkillArchery, chance_mod))) { - Log.Out(Logs::Detail, Logs::Combat, "Ranged attack missed %s.", other->GetName()); - - if (LaunchProjectile){ - TryProjectileAttack(other, AmmoItem, EQEmu::skills::SkillArchery, 0, RangeWeapon, Ammo, AmmoSlot, speed); - return; - } - else - other->Damage(this, 0, SPELL_UNKNOWN, EQEmu::skills::SkillArchery); - } else { - Log.Out(Logs::Detail, Logs::Combat, "Ranged attack hit %s.", other->GetName()); - - uint32 hate = 0; - int32 TotalDmg = 0; - int16 WDmg = 0; - int16 ADmg = 0; - if (!weapon_damage){ - WDmg = GetWeaponDamage(other, RangeWeapon); - ADmg = GetWeaponDamage(other, Ammo); - } - else - WDmg = weapon_damage; - - if (LaunchProjectile){//1: Shoot the Projectile once we calculate weapon damage. - TryProjectileAttack(other, AmmoItem, EQEmu::skills::SkillArchery, (WDmg + ADmg), RangeWeapon, Ammo, AmmoSlot, speed); - return; - } - - // unsure when this should happen - if (focus) //From FcBaseEffects - WDmg += WDmg * focus / 100; - - if((WDmg > 0) || (ADmg > 0)) { - if(WDmg < 0) - WDmg = 0; - if(ADmg < 0) - ADmg = 0; - uint32 MaxDmg = WDmg + ADmg; - hate = ((WDmg+ADmg)); - - 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); - - if (MaxDmg == 0) - MaxDmg = 1; - - int min_cap = MaxDmg * GetMeleeMinDamageMod_SE(EQEmu::skills::SkillArchery) / 100; - auto offense = this->offense(EQEmu::skills::SkillArchery); - - other->AvoidDamage(this, TotalDmg, EQEmu::inventory::slotRange); - - other->MeleeMitigation(this, TotalDmg, MaxDmg, offense, EQEmu::skills::SkillArchery); - if(TotalDmg > 0){ - 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 (IsClient() && !CastToClient()->GetFeigned()) - other->AddToHateList(this, hate, 0, false); - - other->Damage(this, TotalDmg, SPELL_UNKNOWN, EQEmu::skills::SkillArchery); - - //Skill Proc Success - if (TotalDmg > 0 && HasSkillProcSuccess() && other && !other->HasDied()){ - if (ReuseTime) - TrySkillProc(other, EQEmu::skills::SkillArchery, ReuseTime); - else - TrySkillProc(other, EQEmu::skills::SkillArchery, 0, true, EQEmu::inventory::slotRange); - } } + Log.Out(Logs::Detail, Logs::Combat, "Ranged attack hit %s.", other->GetName()); + + uint32 hate = 0; + int TotalDmg = 0; + int WDmg = 0; + int ADmg = 0; + if (!weapon_damage) { + WDmg = GetWeaponDamage(other, RangeWeapon); + ADmg = GetWeaponDamage(other, Ammo); + } else { + WDmg = weapon_damage; + } + + if (LaunchProjectile) { // 1: Shoot the Projectile once we calculate weapon damage. + TryProjectileAttack(other, AmmoItem, EQEmu::skills::SkillArchery, (WDmg + ADmg), RangeWeapon, + Ammo, AmmoSlot, speed); + return; + } + + // unsure when this should happen + if (focus) // From FcBaseEffects + WDmg += WDmg * focus / 100; + + if (WDmg > 0 || ADmg > 0) { + if (WDmg < 0) + WDmg = 0; + if (ADmg < 0) + ADmg = 0; + int MaxDmg = WDmg + ADmg; + hate = ((WDmg + ADmg)); + + 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); + + if (MaxDmg == 0) + MaxDmg = 1; + + DamageHitInfo my_hit; + my_hit.base_damage = MaxDmg; + my_hit.min_damage = 0; + my_hit.damage_done = 0; + + my_hit.skill = EQEmu::skills::SkillArchery; + my_hit.offense = offense(my_hit.skill); + my_hit.tohit = GetTotalToHit(my_hit.skill, chance_mod); + my_hit.hand = EQEmu::inventory::slotRange; + + DoAttack(other, my_hit); + TotalDmg = my_hit.damage_done; + } else { + TotalDmg = DMG_INVULNERABLE; + } + + if (IsClient() && !CastToClient()->GetFeigned()) + other->AddToHateList(this, hate, 0, false); + + other->Damage(this, TotalDmg, SPELL_UNKNOWN, EQEmu::skills::SkillArchery); + + // Skill Proc Success + if (TotalDmg > 0 && HasSkillProcSuccess() && other && !other->HasDied()) { + if (ReuseTime) + TrySkillProc(other, EQEmu::skills::SkillArchery, ReuseTime); + else + TrySkillProc(other, EQEmu::skills::SkillArchery, 0, true, EQEmu::inventory::slotRange); + } + // end of old fuck + if (LaunchProjectile) - return;//Shouldn't reach this point durring initial launch phase, but just in case. + return; // Shouldn't reach this point durring initial launch phase, but just in case. - //Weapon Proc - if(RangeWeapon && other && !other->HasDied()) + // Weapon Proc + if (RangeWeapon && other && !other->HasDied()) TryWeaponProc(RangeWeapon, other, EQEmu::inventory::slotRange); - //Ammo Proc + // Ammo Proc if (ammo_lost) TryWeaponProc(nullptr, ammo_lost, other, EQEmu::inventory::slotRange); - else if(Ammo && other && !other->HasDied()) + else if (Ammo && other && !other->HasDied()) TryWeaponProc(Ammo, other, EQEmu::inventory::slotRange); - //Skill Proc - if (HasSkillProcs() && other && !other->HasDied()){ + // Skill Proc + if (HasSkillProcs() && other && !other->HasDied()) { if (ReuseTime) TrySkillProc(other, EQEmu::skills::SkillArchery, ReuseTime); else @@ -896,16 +881,18 @@ void Mob::DoArcheryAttackDmg(Mob* other, const EQEmu::ItemInstance* RangeWeapon } } -bool Mob::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){ - +bool Mob::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) +{ if (!other) return false; int slot = -1; - //Make sure there is an avialable slot. + // Make sure there is an avialable slot. for (int i = 0; i < MAX_SPELL_PROJECTILE; i++) { - if (ProjectileAtk[i].target_id == 0){ + if (ProjectileAtk[i].target_id == 0) { slot = i; break; } @@ -917,10 +904,11 @@ bool Mob::TryProjectileAttack(Mob* other, const EQEmu::ItemData *item, EQEmu::sk float speed_mod = speed * 0.45f; float distance = other->CalculateDistance(GetX(), GetY(), GetZ()); - float hit = 60.0f + (distance / speed_mod); //Calcuation: 60 = Animation Lag, 1.8 = Speed modifier for speed of (4) + float hit = + 60.0f + (distance / speed_mod); // Calcuation: 60 = Animation Lag, 1.8 = Speed modifier for speed of (4) ProjectileAtk[slot].increment = 1; - ProjectileAtk[slot].hit_increment = static_cast(hit); //This projected hit time if target does NOT MOVE + ProjectileAtk[slot].hit_increment = static_cast(hit); // This projected hit time if target does NOT MOVE ProjectileAtk[slot].target_id = other->GetID(); ProjectileAtk[slot].wpn_dmg = weapon_dmg; ProjectileAtk[slot].origin_x = GetX(); @@ -939,64 +927,74 @@ bool Mob::TryProjectileAttack(Mob* other, const EQEmu::ItemData *item, EQEmu::sk SetProjectileAttack(true); - if(item) + if (item) SendItemAnimation(other, item, skillInUse, speed); - //else if (IsNPC()) - //ProjectileAnimation(other, 0,false,speed,0,0,0,CastToNPC()->GetAmmoIDfile(),skillInUse); + // else if (IsNPC()) + // ProjectileAnimation(other, 0,false,speed,0,0,0,CastToNPC()->GetAmmoIDfile(),skillInUse); return true; } - void Mob::ProjectileAttack() { if (!HasProjectileAttack()) - return;; + return; + ; - Mob* target = nullptr; + Mob *target = nullptr; bool disable = true; for (int i = 0; i < MAX_SPELL_PROJECTILE; i++) { - - if (ProjectileAtk[i].increment == 0){ + if (ProjectileAtk[i].increment == 0) continue; - } disable = false; - Mob* target = entity_list.GetMobID(ProjectileAtk[i].target_id); + Mob *target = entity_list.GetMobID(ProjectileAtk[i].target_id); - if (target && target->IsMoving()){ //Only recalculate hit increment if target moving - //Due to frequency that we need to check increment the targets position variables may not be updated even if moving. Do a simple check before calculating distance. - if (ProjectileAtk[i].tlast_x != target->GetX() || ProjectileAtk[i].tlast_y != target->GetY()){ + if (target && target->IsMoving()) { // Only recalculate hit increment if target moving + // Due to frequency that we need to check increment the targets position variables may not be + // updated even if moving. Do a simple check before calculating distance. + if (ProjectileAtk[i].tlast_x != target->GetX() || ProjectileAtk[i].tlast_y != target->GetY()) { ProjectileAtk[i].tlast_x = target->GetX(); ProjectileAtk[i].tlast_y = target->GetY(); - float distance = target->CalculateDistance(ProjectileAtk[i].origin_x, ProjectileAtk[i].origin_y, ProjectileAtk[i].origin_z); - float hit = 60.0f + (distance / ProjectileAtk[i].speed_mod); //Calcuation: 60 = Animation Lag, 1.8 = Speed modifier for speed of (4) + float distance = target->CalculateDistance( + ProjectileAtk[i].origin_x, ProjectileAtk[i].origin_y, ProjectileAtk[i].origin_z); + float hit = 60.0f + (distance / ProjectileAtk[i].speed_mod); // Calcuation: 60 = + // Animation Lag, 1.8 = + // Speed modifier for speed + // of (4) ProjectileAtk[i].hit_increment = static_cast(hit); } } - if (ProjectileAtk[i].hit_increment <= ProjectileAtk[i].increment){ - - if (target){ - - if (IsNPC()){ - if (ProjectileAtk[i].skill == EQEmu::skills::SkillConjuration){ + // We hit I guess? + if (ProjectileAtk[i].hit_increment <= ProjectileAtk[i].increment) { + if (target) { + if (IsNPC()) { + if (ProjectileAtk[i].skill == EQEmu::skills::SkillConjuration) { if (IsValidSpell(ProjectileAtk[i].wpn_dmg)) - SpellOnTarget(ProjectileAtk[i].wpn_dmg, target, false, true, spells[ProjectileAtk[i].wpn_dmg].ResistDiff, true); + SpellOnTarget(ProjectileAtk[i].wpn_dmg, target, false, true, + spells[ProjectileAtk[i].wpn_dmg].ResistDiff, + true); + } else { + CastToNPC()->DoRangedAttackDmg( + target, false, ProjectileAtk[i].wpn_dmg, 0, + static_cast(ProjectileAtk[i].skill)); } - else - CastToNPC()->DoRangedAttackDmg(target, false, ProjectileAtk[i].wpn_dmg, 0, static_cast(ProjectileAtk[i].skill)); - } - - else - { + } else { if (ProjectileAtk[i].skill == EQEmu::skills::SkillArchery) - DoArcheryAttackDmg(target, nullptr, nullptr,ProjectileAtk[i].wpn_dmg,0,0,0,ProjectileAtk[i].ranged_id, ProjectileAtk[i].ammo_id, nullptr, ProjectileAtk[i].ammo_slot); + DoArcheryAttackDmg(target, nullptr, nullptr, ProjectileAtk[i].wpn_dmg, + 0, 0, 0, ProjectileAtk[i].ranged_id, + ProjectileAtk[i].ammo_id, nullptr, + ProjectileAtk[i].ammo_slot); else if (ProjectileAtk[i].skill == EQEmu::skills::SkillThrowing) - DoThrowingAttackDmg(target, nullptr, nullptr,ProjectileAtk[i].wpn_dmg,0,0,0, ProjectileAtk[i].ranged_id, ProjectileAtk[i].ammo_slot); - else if (ProjectileAtk[i].skill == EQEmu::skills::SkillConjuration && IsValidSpell(ProjectileAtk[i].wpn_dmg)) - SpellOnTarget(ProjectileAtk[i].wpn_dmg, target, false, true, spells[ProjectileAtk[i].wpn_dmg].ResistDiff, true); + DoThrowingAttackDmg(target, nullptr, nullptr, ProjectileAtk[i].wpn_dmg, + 0, 0, 0, ProjectileAtk[i].ranged_id, + ProjectileAtk[i].ammo_slot); + else if (ProjectileAtk[i].skill == EQEmu::skills::SkillConjuration && + IsValidSpell(ProjectileAtk[i].wpn_dmg)) + SpellOnTarget(ProjectileAtk[i].wpn_dmg, target, false, true, + spells[ProjectileAtk[i].wpn_dmg].ResistDiff, true); } } @@ -1013,9 +1011,7 @@ void Mob::ProjectileAttack() ProjectileAtk[i].ammo_slot = 0; ProjectileAtk[i].skill = 0; ProjectileAtk[i].speed_mod = 0.0f; - } - - else { + } else { ProjectileAtk[i].increment++; } } @@ -1158,39 +1154,38 @@ void NPC::DoRangedAttackDmg(Mob* other, bool Launch, int16 damage_mod, int16 cha if (!chance_mod) chance_mod = GetSpecialAbilityParam(SPECATK_RANGED_ATK, 2); - if (!other->CheckHitChance(this, skillInUse, chance_mod)) - { - other->Damage(this, 0, SPELL_UNKNOWN, skillInUse); + 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 + + DamageHitInfo my_hit; + my_hit.base_damage = MaxDmg; + my_hit.min_damage = MinDmg; + my_hit.damage_done = 0; + + my_hit.skill = skill; + my_hit.offense = offense(my_hit.skill); + my_hit.tohit = GetTotalToHit(my_hit.skill, chance_mod); + my_hit.hand = EQEmu::inventory::slotRange; + + DoAttack(other, my_hit); + + TotalDmg = my_hit.damage_done; + + if (TotalDmg > 0) { + TotalDmg += TotalDmg * damage_mod / 100; + other->AddToHateList(this, TotalDmg, 0, false); + } else { + other->AddToHateList(this, 0, 0, false); } - else - { - 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 + other->Damage(this, TotalDmg, SPELL_UNKNOWN, skillInUse); - TotalDmg += TotalDmg * damage_mod / 100; - - other->AvoidDamage(this, TotalDmg, EQEmu::inventory::slotRange); - other->MeleeMitigation(this, TotalDmg, MaxDmg, offense(skillInUse), skillInUse); - - if (TotalDmg > 0) - CommonOutgoingHitSuccess(other, TotalDmg, MinDmg, 0, skillInUse); - else if (TotalDmg < -4) - TotalDmg = -5; - - if (TotalDmg > 0) - other->AddToHateList(this, TotalDmg, 0, false); - else - other->AddToHateList(this, 0, 0, false); - - other->Damage(this, TotalDmg, SPELL_UNKNOWN, skillInUse); - - if (TotalDmg > 0 && HasSkillProcSuccess() && !other->HasDied()) - TrySkillProc(other, skillInUse, 0, true, EQEmu::inventory::slotRange); - } + if (TotalDmg > 0 && HasSkillProcSuccess() && !other->HasDied()) + TrySkillProc(other, skillInUse, 0, true, EQEmu::inventory::slotRange); //try proc on hits and misses if(other && !other->HasDied()) @@ -1302,23 +1297,13 @@ void Mob::DoThrowingAttackDmg(Mob *other, const EQEmu::ItemInstance *RangeWeapon this function is then run again to do the damage portion */ bool LaunchProjectile = false; - bool ProjectileImpact = false; - bool ProjectileMiss = false; if (RuleB(Combat, ProjectileDmgOnImpact)) { - - if (AmmoItem) + if (AmmoItem) { LaunchProjectile = true; - else { + } else { if (!RangeWeapon && range_id) { - - ProjectileImpact = true; - - if (weapon_damage == 0) - ProjectileMiss = true; // This indicates that MISS was originally calculated. - if (IsClient()) { - _RangeWeapon = CastToClient()->m_inv[AmmoSlot]; if (_RangeWeapon && _RangeWeapon->GetItem() && _RangeWeapon->GetItem()->ID != range_id) @@ -1328,81 +1313,66 @@ void Mob::DoThrowingAttackDmg(Mob *other, const EQEmu::ItemInstance *RangeWeapon } } } - } else if (AmmoItem) + } else if (AmmoItem) { SendItemAnimation(other, AmmoItem, EQEmu::skills::SkillThrowing); - - 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); - return; - } 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 (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); - return; - } - } else - WDmg = weapon_damage; - - 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 min_cap = WDmg * GetMeleeMinDamageMod_SE(EQEmu::skills::SkillThrowing) / 100; - auto offense = this->offense(EQEmu::skills::SkillThrowing); - if (Assassinate_Dmg) { - TotalDmg = Assassinate_Dmg; - } - - 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, 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->Damage(this, TotalDmg, SPELL_UNKNOWN, EQEmu::skills::SkillThrowing); - - if (TotalDmg > 0 && HasSkillProcSuccess() && other && !other->HasDied()) { - if (ReuseTime) - TrySkillProc(other, EQEmu::skills::SkillThrowing, ReuseTime); - else - TrySkillProc(other, EQEmu::skills::SkillThrowing, 0, true, EQEmu::inventory::slotRange); - } } + Log.Out(Logs::Detail, Logs::Combat, "Throwing attack hit %s.", other->GetName()); + + int WDmg = 0; + + 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); + return; + } + } else { + WDmg = weapon_damage; + } + + if (focus) // From FcBaseEffects + WDmg += WDmg * focus / 100; + + int TotalDmg = 0; + + if (WDmg > 0) { + DamageHitInfo my_hit; + my_hit.base_damage = WDmg; + my_hit.min_damage = 0; + my_hit.damage_done = 0; + + my_hit.skill = EQEmu::skills::SkillThrowing; + my_hit.offense = offense(my_hit.skill); + my_hit.tohit = GetTotalToHit(my_hit.skill, chance_mod); + my_hit.hand = EQEmu::inventory::slotRange; + + DoAttack(other, my_hit); + TotalDmg = my_hit.damage_done; + + Log.Out(Logs::Detail, Logs::Combat, "Item DMG %d. Hit for damage %d", WDmg, TotalDmg); + } else { + TotalDmg = DMG_INVULNERABLE; + } + + if (IsClient() && !CastToClient()->GetFeigned()) + other->AddToHateList(this, WDmg, 0, false); + + other->Damage(this, TotalDmg, SPELL_UNKNOWN, EQEmu::skills::SkillThrowing); + + if (TotalDmg > 0 && HasSkillProcSuccess() && other && !other->HasDied()) { + if (ReuseTime) + TrySkillProc(other, EQEmu::skills::SkillThrowing, ReuseTime); + else + TrySkillProc(other, EQEmu::skills::SkillThrowing, 0, true, EQEmu::inventory::slotRange); + } + // end old shit + if (LaunchProjectile) return; @@ -1609,15 +1579,10 @@ void NPC::DoClassAttacks(Mob *target) { if(level >= RuleI(Combat, NPCBashKickLevel)){ if(zone->random.Roll(75)) { //tested on live, warrior mobs both kick and bash, kick about 75% of the time, casting doesn't seem to make a difference. DoAnim(animKick); - int32 dmg = 0; + int32 dmg = GetBaseSkillDamage(EQEmu::skills::SkillKick); - if (GetWeaponDamage(target, (const EQEmu::ItemData*)nullptr) <= 0){ - dmg = -5; - } - else{ - if (target->CheckHitChance(this, EQEmu::skills::SkillKick, 0)) - dmg = GetBaseSkillDamage(EQEmu::skills::SkillKick); - } + if (GetWeaponDamage(target, (const EQEmu::ItemData*)nullptr) <= 0) + dmg = DMG_INVULNERABLE; reuse = (KickReuseTime + 3) * 1000; DoSpecialAttackDamage(target, EQEmu::skills::SkillKick, dmg, GetMinDamage(), -1, reuse); @@ -1625,15 +1590,10 @@ void NPC::DoClassAttacks(Mob *target) { } else { DoAnim(animTailRake); - int32 dmg = 0; + int32 dmg = GetBaseSkillDamage(EQEmu::skills::SkillBash); - if (GetWeaponDamage(target, (const EQEmu::ItemData*)nullptr) <= 0){ - dmg = -5; - } - else{ - if (target->CheckHitChance(this, EQEmu::skills::SkillBash, 0)) - dmg = GetBaseSkillDamage(EQEmu::skills::SkillBash); - } + if (GetWeaponDamage(target, (const EQEmu::ItemData*)nullptr) <= 0) + dmg = DMG_INVULNERABLE; reuse = (BashReuseTime + 3) * 1000; DoSpecialAttackDamage(target, EQEmu::skills::SkillBash, dmg, GetMinDamage(), -1, reuse); @@ -1643,14 +1603,21 @@ void NPC::DoClassAttacks(Mob *target) { break; } case BERSERKER: case BERSERKERGM:{ - int AtkRounds = 3; + int AtkRounds = 1; int32 max_dmg = GetBaseSkillDamage(EQEmu::skills::SkillFrenzy); DoAnim(anim2HSlashing); - while(AtkRounds > 0) { - if (GetTarget() && (AtkRounds == 1 || zone->random.Roll(75))) { - DoSpecialAttackDamage(GetTarget(), EQEmu::skills::SkillFrenzy, max_dmg, GetMinDamage(), -1, reuse, true); - } + if (GetClass() == BERSERKER) { + int chance = GetLevel() * 2 + GetSkill(EQEmu::skills::SkillFrenzy); + if (zone->random.Roll0(450) < chance) + AtkRounds++; + if (zone->random.Roll0(450) < chance) + AtkRounds++; + } + + while (AtkRounds > 0) { + if (GetTarget()) + DoSpecialAttackDamage(GetTarget(), EQEmu::skills::SkillFrenzy, max_dmg, GetMinDamage(), -1, reuse); AtkRounds--; } @@ -1662,15 +1629,10 @@ void NPC::DoClassAttacks(Mob *target) { //kick if(level >= RuleI(Combat, NPCBashKickLevel)){ DoAnim(animKick); - int32 dmg = 0; + int32 dmg = GetBaseSkillDamage(EQEmu::skills::SkillKick); - if (GetWeaponDamage(target, (const EQEmu::ItemData*)nullptr) <= 0){ - dmg = -5; - } - else{ - if (target->CheckHitChance(this, EQEmu::skills::SkillKick, 0)) - dmg = GetBaseSkillDamage(EQEmu::skills::SkillKick); - } + if (GetWeaponDamage(target, (const EQEmu::ItemData*)nullptr) <= 0) + dmg = DMG_INVULNERABLE; reuse = (KickReuseTime + 3) * 1000; DoSpecialAttackDamage(target, EQEmu::skills::SkillKick, dmg, GetMinDamage(), -1, reuse); @@ -1683,15 +1645,10 @@ void NPC::DoClassAttacks(Mob *target) { case PALADIN: case PALADINGM:{ if(level >= RuleI(Combat, NPCBashKickLevel)){ DoAnim(animTailRake); - int32 dmg = 0; + int32 dmg = GetBaseSkillDamage(EQEmu::skills::SkillBash); - if (GetWeaponDamage(target, (const EQEmu::ItemData*)nullptr) <= 0){ - dmg = -5; - } - else{ - if (target->CheckHitChance(this, EQEmu::skills::SkillBash, 0)) - dmg = GetBaseSkillDamage(EQEmu::skills::SkillBash); - } + if (GetWeaponDamage(target, (const EQEmu::ItemData*)nullptr) <= 0) + dmg = DMG_INVULNERABLE; reuse = (BashReuseTime + 3) * 1000; DoSpecialAttackDamage(target, EQEmu::skills::SkillBash, dmg, GetMinDamage(), -1, reuse); @@ -1728,8 +1685,6 @@ void Client::DoClassAttacks(Mob *ca_target, uint16 skill, bool IsRiposte) int ReuseTime = 0; float HasteMod = GetHaste() * 0.01f; - int32 dmg = 0; - uint16 skill_to_use = -1; if (skill == -1){ @@ -1784,21 +1739,14 @@ void Client::DoClassAttacks(Mob *ca_target, uint16 skill, bool IsRiposte) if(skill_to_use == -1) return; + int dmg = GetBaseSkillDamage(static_cast(skill_to_use), GetTarget()); + if (skill_to_use == EQEmu::skills::SkillBash) { if (ca_target!=this) { DoAnim(animTailRake); - if (GetWeaponDamage(ca_target, GetInv().GetItem(EQEmu::inventory::slotSecondary)) <= 0 && GetWeaponDamage(ca_target, GetInv().GetItem(EQEmu::inventory::slotShoulders)) <= 0){ - dmg = -5; - } - else{ - if (!ca_target->CheckHitChance(this, EQEmu::skills::SkillBash, 0)) { - dmg = 0; - } - else{ - dmg = GetBaseSkillDamage(EQEmu::skills::SkillBash, ca_target); - } - } + if (GetWeaponDamage(ca_target, GetInv().GetItem(EQEmu::inventory::slotSecondary)) <= 0 && GetWeaponDamage(ca_target, GetInv().GetItem(EQEmu::inventory::slotShoulders)) <= 0) + dmg = DMG_INVULNERABLE; ReuseTime = (BashReuseTime - 1) / HasteMod; @@ -1811,20 +1759,25 @@ void Client::DoClassAttacks(Mob *ca_target, uint16 skill, bool IsRiposte) return; } - if (skill_to_use == EQEmu::skills::SkillFrenzy){ + if (skill_to_use == EQEmu::skills::SkillFrenzy) { CheckIncreaseSkill(EQEmu::skills::SkillFrenzy, GetTarget(), 10); - int AtkRounds = 3; - int32 max_dmg = GetBaseSkillDamage(EQEmu::skills::SkillBash, GetTarget()); + int AtkRounds = 1; DoAnim(anim2HSlashing); ReuseTime = (FrenzyReuseTime - 1) / HasteMod; - //Live parses show around 55% Triple 35% Double 10% Single, you will always get first hit. - while(AtkRounds > 0) { + // bards can do riposte frenzy for some reason + if (!IsRiposte && GetClass() == BERSERKER) { + int chance = GetLevel() * 2 + GetSkill(EQEmu::skills::SkillFrenzy); + if (zone->random.Roll0(450) < chance) + AtkRounds++; + if (zone->random.Roll0(450) < chance) + AtkRounds++; + } - if (GetTarget() && (AtkRounds == 1 || zone->random.Roll(75))) { - DoSpecialAttackDamage(GetTarget(), EQEmu::skills::SkillFrenzy, max_dmg, 0, max_dmg, ReuseTime, true); - } + while(AtkRounds > 0) { + if (GetTarget()) + DoSpecialAttackDamage(GetTarget(), EQEmu::skills::SkillFrenzy, dmg, 0, dmg, ReuseTime); AtkRounds--; } @@ -1838,17 +1791,8 @@ void Client::DoClassAttacks(Mob *ca_target, uint16 skill, bool IsRiposte) if(ca_target!=this){ DoAnim(animKick); - if (GetWeaponDamage(ca_target, GetInv().GetItem(EQEmu::inventory::slotFeet)) <= 0){ - dmg = -5; - } - else{ - if (!ca_target->CheckHitChance(this, EQEmu::skills::SkillKick, 0)) { - dmg = 0; - } - else{ - dmg = GetBaseSkillDamage(EQEmu::skills::SkillKick, ca_target); - } - } + if (GetWeaponDamage(ca_target, GetInv().GetItem(EQEmu::inventory::slotFeet)) <= 0) + dmg = DMG_INVULNERABLE; ReuseTime = KickReuseTime-1; @@ -1866,19 +1810,28 @@ void Client::DoClassAttacks(Mob *ca_target, uint16 skill, bool IsRiposte) //Live AA - Technique of Master Wu int wuchance = itembonuses.DoubleSpecialAttack + spellbonuses.DoubleSpecialAttack + aabonuses.DoubleSpecialAttack; if (wuchance) { - if (wuchance >= 100 || zone->random.Roll(wuchance)) { - int MonkSPA[5] = { EQEmu::skills::SkillFlyingKick, EQEmu::skills::SkillDragonPunch, EQEmu::skills::SkillEagleStrike, EQEmu::skills::SkillTigerClaw, EQEmu::skills::SkillRoundKick }; - int extra = 1; - if (zone->random.Roll(wuchance / 4)) + const int MonkSPA[5] = {EQEmu::skills::SkillFlyingKick, EQEmu::skills::SkillDragonPunch, + EQEmu::skills::SkillEagleStrike, EQEmu::skills::SkillTigerClaw, + EQEmu::skills::SkillRoundKick}; + int extra = 0; + // always 1/4 of the double attack chance, 25% at rank 5 (100/4) + while (wuchance > 0) { + if (zone->random.Roll(wuchance)) extra++; - // They didn't add a string ID for this. - std::string msg = StringFormat("The spirit of Master Wu fills you! You gain %d additional attack(s).", extra); - // live uses 400 here -- not sure if it's the best for all clients though - SendColoredText(400, msg); - while (extra) { - MonkSpecialAttack(ca_target, MonkSPA[zone->random.Int(0, 4)]); - extra--; - } + else + break; + wuchance /= 4; + } + // They didn't add a string ID for this. + std::string msg = StringFormat( + "The spirit of Master Wu fills you! You gain %d additional attack(s).", extra); + // live uses 400 here -- not sure if it's the best for all clients though + SendColoredText(400, msg); + auto classic = RuleB(Combat, ClassicMasterWu); + while (extra) { + MonkSpecialAttack(GetTarget(), + classic ? MonkSPA[zone->random.Int(0, 4)] : skill_to_use); + extra--; } } } @@ -2033,23 +1986,28 @@ void Mob::InstillDoubt(Mob *who) { } } -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)) { - +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) { uint32 HeadShot_Dmg = aabonuses.HeadShot[1] + spellbonuses.HeadShot[1] + itembonuses.HeadShot[1]; - uint8 HeadShot_Level = 0; //Get Highest Headshot Level - HeadShot_Level = aabonuses.HSLevel; - if (HeadShot_Level < spellbonuses.HSLevel) - HeadShot_Level = spellbonuses.HSLevel; - else if (HeadShot_Level < itembonuses.HSLevel) - HeadShot_Level = itembonuses.HSLevel; + uint8 HeadShot_Level = 0; // Get Highest Headshot Level + HeadShot_Level = std::max({aabonuses.HSLevel[0], spellbonuses.HSLevel[0], itembonuses.HSLevel[0]}); - if(HeadShot_Dmg && HeadShot_Level && (defender->GetLevel() <= HeadShot_Level)){ - float ProcChance = GetSpecialProcChances(EQEmu::inventory::slotRange); - if(zone->random.Roll(ProcChance)) { - entity_list.MessageClose_StringID(this, false, 200, MT_CritMelee, FATAL_BOW_SHOT, GetName()); + if (HeadShot_Dmg && HeadShot_Level && (defender->GetLevel() <= HeadShot_Level)) { + int chance = GetDEX(); + chance = 100 * chance / (chance + 3500); + if (IsClient()) + chance += CastToClient()->GetHeroicDEX() / 25; + chance *= 10; + int norm = aabonuses.HSLevel[1]; + if (norm > 0) + chance = chance * norm / 100; + chance += aabonuses.HeadShot[0] + spellbonuses.HeadShot[0] + itembonuses.HeadShot[0]; + if (zone->random.Int(1, 1000) <= chance) { + entity_list.MessageClose_StringID(this, false, 200, MT_CritMelee, FATAL_BOW_SHOT, + GetName()); return HeadShot_Dmg; } } @@ -2058,61 +2016,41 @@ int Mob::TryHeadShot(Mob* defender, EQEmu::skills::SkillType skillInUse) { return 0; } -float Mob::GetSpecialProcChances(uint16 hand) +int Mob::TryAssassinate(Mob *defender, EQEmu::skills::SkillType skillInUse) { - int mydex = GetDEX(); + if (defender && (defender->GetBodyType() == BT_Humanoid) && !defender->IsClient() && GetLevel() >= 60 && + (skillInUse == EQEmu::skills::SkillBackstab || skillInUse == EQEmu::skills::SkillThrowing)) { + int chance = GetDEX(); + if (skillInUse == EQEmu::skills::SkillBackstab) { + chance = 100 * chance / (chance + 3500); + if (IsClient()) + chance += CastToClient()->GetHeroicDEX(); + chance *= 10; + int norm = aabonuses.AssassinateLevel[1]; + if (norm > 0) + chance = chance * norm / 100; + } else if (skillInUse == EQEmu::skills::SkillThrowing) { + if (chance > 255) + chance = 260; + else + chance += 5; + } - if (mydex > 255) - mydex = 255; + chance += aabonuses.Assassinate[0] + spellbonuses.Assassinate[0] + itembonuses.Assassinate[0]; - uint16 weapon_speed; - float ProcChance = 0.0f; - float ProcBonus = 0.0f; + uint32 Assassinate_Dmg = + aabonuses.Assassinate[1] + spellbonuses.Assassinate[1] + itembonuses.Assassinate[1]; - weapon_speed = GetWeaponSpeedbyHand(hand); - - if (RuleB(Combat, AdjustSpecialProcPerMinute)) { - ProcChance = (static_cast(weapon_speed) * - RuleR(Combat, AvgSpecialProcsPerMinute) / 60000.0f); - ProcBonus += static_cast(mydex/35) + static_cast(itembonuses.HeroicDEX / 25); - ProcChance += ProcChance * ProcBonus / 100.0f; - } else { - /*PRE 2014 CHANGE Dev Quote - "Elidroth SOE:Proc chance is a function of your base hardcapped Dexterity / 35 + Heroic Dexterity / 25.” - Kayen: Most reports suggest a ~ 6% chance to Headshot which consistent with above.*/ - - ProcChance = (static_cast(mydex/35) + static_cast(itembonuses.HeroicDEX / 25))/100.0f; - } - - return ProcChance; -} - -int Mob::TryAssassinate(Mob* defender, EQEmu::skills::SkillType skillInUse, uint16 ReuseTime) { - - 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]; - - uint8 Assassinate_Level = 0; //Get Highest Headshot Level - Assassinate_Level = aabonuses.AssassinateLevel; - if (Assassinate_Level < spellbonuses.AssassinateLevel) - Assassinate_Level = spellbonuses.AssassinateLevel; - else if (Assassinate_Level < itembonuses.AssassinateLevel) - Assassinate_Level = itembonuses.AssassinateLevel; + uint8 Assassinate_Level = 0; // Get Highest Headshot Level + Assassinate_Level = std::max( + {aabonuses.AssassinateLevel[0], spellbonuses.AssassinateLevel[0], itembonuses.AssassinateLevel[0]}); // 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; - - if (skillInUse == EQEmu::skills::SkillThrowing) - ProcChance = GetSpecialProcChances(EQEmu::inventory::slotRange); - else - ProcChance = GetAssassinateProcChances(ReuseTime); - - if(zone->random.Roll(ProcChance)) { + if (Assassinate_Dmg && Assassinate_Level && (defender->GetLevel() <= Assassinate_Level)) { + if (zone->random.Int(1, 1000) <= chance) { entity_list.MessageClose_StringID(this, false, 200, MT_CritMelee, ASSASSINATES, GetName()); return Assassinate_Dmg; @@ -2123,32 +2061,8 @@ int Mob::TryAssassinate(Mob* defender, EQEmu::skills::SkillType skillInUse, uint return 0; } -float Mob::GetAssassinateProcChances(uint16 ReuseTime) -{ - int mydex = GetDEX(); - - if (mydex > 255) - mydex = 255; - - float ProcChance = 0.0f; - float ProcBonus = 0.0f; - - if (RuleB(Combat, AdjustSpecialProcPerMinute)) { - ProcChance = (static_cast(ReuseTime*1000) * - RuleR(Combat, AvgSpecialProcsPerMinute) / 60000.0f); - ProcBonus += (10 + (static_cast(mydex/10) + static_cast(itembonuses.HeroicDEX /10)))/100.0f; - ProcChance += ProcChance * ProcBonus / 100.0f; - - } else { - /* Kayen: Unable to find data on old proc rate of assassinate, no idea if our formula is real or made up. */ - ProcChance = (10 + (static_cast(mydex/10) + static_cast(itembonuses.HeroicDEX /10)))/100.0f; - - } - - return ProcChance; -} - -void Mob::DoMeleeSkillAttackDmg(Mob* other, uint16 weapon_damage, EQEmu::skills::SkillType skillinuse, int16 chance_mod, int16 focus, bool CanRiposte, int ReuseTime) +void Mob::DoMeleeSkillAttackDmg(Mob *other, uint16 weapon_damage, EQEmu::skills::SkillType skillinuse, int16 chance_mod, + int16 focus, bool CanRiposte, int ReuseTime) { if (!CanDoSpecialAttack(other)) return; @@ -2164,17 +2078,19 @@ void Mob::DoMeleeSkillAttackDmg(Mob* other, uint16 weapon_damage, EQEmu::skills: int damage = 0; uint32 hate = 0; - if (hate == 0 && weapon_damage > 1) hate = weapon_damage; + if (hate == 0 && weapon_damage > 1) + hate = weapon_damage; - if(weapon_damage > 0){ - if (focus) //From FcBaseEffects - weapon_damage += weapon_damage*focus/100; + if (weapon_damage > 0) { + if (focus) // From FcBaseEffects + weapon_damage += weapon_damage * focus / 100; - if (skillinuse == EQEmu::skills::SkillBash){ - if(IsClient()){ - EQEmu::ItemInstance *item = CastToClient()->GetInv().GetItem(EQEmu::inventory::slotSecondary); - if(item){ - if (item->GetItem()->ItemType == EQEmu::item::ItemTypeShield) { + if (skillinuse == EQEmu::skills::SkillBash) { + if (IsClient()) { + EQEmu::ItemInstance *item = + CastToClient()->GetInv().GetItem(EQEmu::inventory::slotSecondary); + if (item) { + if (item->GetItem()->ItemType == EQEmu::item::ItemTypeShield) { hate += item->GetItem()->AC; } const EQEmu::ItemData *itm = item->GetItem(); @@ -2183,37 +2099,30 @@ void Mob::DoMeleeSkillAttackDmg(Mob* other, uint16 weapon_damage, EQEmu::skills: } } - int min_cap = weapon_damage * GetMeleeMinDamageMod_SE(skillinuse) / 100; - auto offense = this->offense(skillinuse); - int min_damage = 0; + DamageHitInfo my_hit; + my_hit.base_damage = weapon_damage; + my_hit.min_damage = 0; + my_hit.damage_done = 0; + + my_hit.skill = skillinuse; + my_hit.offense = offense(my_hit.skill); + my_hit.tohit = GetTotalToHit(my_hit.skill, chance_mod); + // slot range exclude ripe etc ... + my_hit.hand = CanRiposte ? EQEmu::inventory::slotRange : EQEmu::inventory::slotPrimary; + 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) { - DoRiposte(other); - if (HasDied()) - return; - } - } else { - if (other->CheckHitChance(this, skillinuse, chance_mod)) { - other->MeleeMitigation(this, damage, weapon_damage, offense, skillinuse); - ApplyDamageTable(damage, offense); - CommonOutgoingHitSuccess(other, damage, min_damage, min_cap, skillinuse); - } else { - damage = 0; - } - } + my_hit.min_damage = CastToNPC()->GetMinDamage(); + DoAttack(other, my_hit); + damage = my_hit.damage_done; + } else { + damage = DMG_INVULNERABLE; } - else - damage = -5; - bool CanSkillProc = true; - if (skillinuse == EQEmu::skills::SkillOffense){ //Hack to allow damage to display. + 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 + CanSkillProc = false; // Disable skill procs } other->AddToHateList(this, hate, 0, false); @@ -2224,6 +2133,7 @@ void Mob::DoMeleeSkillAttackDmg(Mob* other, uint16 weapon_damage, EQEmu::skills: SpellFinished(aabonuses.SkillAttackProc[2], other, EQEmu::CastingSlot::Item, 0, -1, spells[aabonuses.SkillAttackProc[2]].ResistDiff); } + other->Damage(this, damage, SPELL_UNKNOWN, skillinuse); if (HasDied())