diff --git a/common/ruletypes.h b/common/ruletypes.h index ba0c2f41b..50ff39084 100644 --- a/common/ruletypes.h +++ b/common/ruletypes.h @@ -613,6 +613,7 @@ RULE_BOOL(Combat, UseEnhancedMobStaticWeaponSkill, false, "Toggle to enabled the RULE_INT(Combat, PCAttackPowerScaling, 100, "Applies scaling to PC Attack Power (75 = 75%). DEFAULT: 100 to not adjust existing Servers") RULE_INT(Combat, PCAccuracyAvoidanceMod2Scale, 100, "Scale Factor for PC Accuracy and Avoidance (Mod2, found on items). Found a value of 100 to make both too strong (75 = x0.75). DEFAULT: 100 to not adjust existing Servers") RULE_BOOL(Combat, AllowRaidTargetBlind, false, "Toggle to allow raid targets to be blinded, default is false (Live-like)") +RULE_BOOL(Combat, RogueBackstabHasteCorrection, false, "Toggle to enable correction for Haste impacting Backstab DPS too much. DEFAULT: false") RULE_CATEGORY_END() RULE_CATEGORY(NPC) diff --git a/zone/special_attacks.cpp b/zone/special_attacks.cpp index 152badf2f..4525f125d 100644 --- a/zone/special_attacks.cpp +++ b/zone/special_attacks.cpp @@ -257,10 +257,40 @@ void Mob::DoSpecialAttackDamage(Mob *who, EQ::skills::SkillType skill, int32 bas my_hit.offense = offense(my_hit.skill); my_hit.tohit = GetTotalToHit(my_hit.skill, 0); + // Rogue Backstab Haste Correction + // Haste should only provide a max of a 2 s reduction to Backstab cooldown, but it seems that while BackstabReuseTimer can be reduced, there is another timer (repop on the button) + // that is controlling the actual cooldown. I'm not sure how this is implemented, but it is impacted by spell haste (including bard v2 and v3), but not worn haste. + // This code applies an adjustment to backstab accuracy to compensate for this so that Rogue DPS doesn't significantly outclass other classes. + + if ( + RuleB(Combat, RogueBackstabHasteCorrection) && + skill == EQ::skills::SkillBackstab && + GetHaste() > 100 + ) { + int haste_spell = spellbonuses.haste - spellbonuses.inhibitmelee + spellbonuses.hastetype2 + spellbonuses.hastetype3; + int haste_worn = itembonuses.haste; + + // Compute Intended Cooldown. 100% Spell = 1 s reduction (max), 40% Worn = 1 s reduction (max). + int reduction_intended_spell = haste_spell > 100 ? 100 : haste_spell; + int reduction_intended_worn = 2.5 * (haste_worn > 40 ? 40 : haste_worn); + int16 intended_cooldown = 1000 - reduction_intended_spell - reduction_intended_worn; + + // Compute Actual Cooldown. Actual only impacted by spell haste ( + v2 + v3), and is 10 s / (100 + haste) + int actual_cooldown = 100000 / (100 + haste_spell); + + // Compute Accuracy Adjustment + int backstab_accuracy_adjust = actual_cooldown * 1000 / intended_cooldown; + + // orig_accuracy = my_hit.tohit; + int adjusted_accuracy = my_hit.tohit * backstab_accuracy_adjust / 1000; + my_hit.tohit = adjusted_accuracy; + } + my_hit.hand = EQ::invslot::slotPrimary; // Avoid checks hand for throwing/archery exclusion, primary should // work for most - if (skill == EQ::skills::SkillThrowing || skill == EQ::skills::SkillArchery) + if (skill == EQ::skills::SkillThrowing || skill == EQ::skills::SkillArchery) { my_hit.hand = EQ::invslot::slotRange; + } DoAttack(who, my_hit); @@ -268,16 +298,20 @@ void Mob::DoSpecialAttackDamage(Mob *who, EQ::skills::SkillType skill, int32 bas 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()) + if (!GetTarget()) { return; - if (HasDied()) + } + + if (HasDied()) { return; + } TryCastOnSkillUse(who, skill); if (HasSkillProcs()) { TrySkillProc(who, skill, ReuseTime * 1000); } + if (my_hit.damage_done > 0 && HasSkillProcSuccess()) { TrySkillProc(who, skill, ReuseTime * 1000, true); }