diff --git a/common/ruletypes.h b/common/ruletypes.h index 8c665cfad..9586434f6 100644 --- a/common/ruletypes.h +++ b/common/ruletypes.h @@ -529,6 +529,8 @@ RULE_REAL(Combat, DefProcPerMinAgiContrib, 0.075, "How much agility contributes RULE_INT(Combat, NPCFlurryChance, 20, "Chance for NPC to flurry") RULE_BOOL(Combat, TauntOverLevel, 1, "Allows you to taunt NPC's over warriors level") RULE_INT(Combat, TauntOverAggro, 0, "+ amount over hate_top it will add before any bonus hate.") +RULE_INT(Combat, TauntChanceBonus, 0, "Bonus to taunt chance") +RULE_BOOL(Combat, ClassicTauntSystem, false, "Enable to use the pre 2006 taunt system.") RULE_REAL(Combat, TauntSkillFalloff, 0.33, "For every taunt skill point that's not maxed you lose this percentage chance to taunt") RULE_BOOL(Combat, EXPFromDmgShield, false, "Determine if damage from a damage shield counts for experience gain") RULE_INT(Combat, QuiverHasteCap, 1000, "Quiver haste cap 1000 on live for a while, currently 700 on live") diff --git a/zone/special_attacks.cpp b/zone/special_attacks.cpp index 8d645e25c..4a39dc313 100644 --- a/zone/special_attacks.cpp +++ b/zone/special_attacks.cpp @@ -2134,9 +2134,22 @@ void Client::DoClassAttacks(Mob *ca_target, uint16 skill, bool IsRiposte) } } + /* Classic Taunt Methodology + * This is not how Sony did it. This is a guess that fits the very limited data available. + * Low level players with maxed taunt for their level taunted about 50% on white cons. + * A 65 ranger with 150 taunt skill (max) taunted about 50% on level 60 and under NPCs. + * A 65 warrior with maxed taunt (230) was taunting around 50% on SSeru NPCs. */ + + /* Rashere in 2006: "your taunt skill was irrelevant if you were above level 60 and taunting + * something that was also above level 60." + * Also: "The chance to taunt an NPC higher level than yourself dropped off at double the rate + * if you were above level 60 than if you were below level 60 making it very hard to taunt creature + * higher level than yourself if you were above level 60." + * + * See http://www.elitegamerslounge.com/home/soearchive/viewtopic.php?t=81156 */ void Mob::Taunt(NPC *who, bool always_succeed, int chance_bonus, bool from_spell, int32 bonus_hate) { - if (!who || DivineAura() || (!from_spell && !CombatRange(who))) { + if (!who || DivineAura() || (!from_spell && !CombatRange(who)) || (IsNPC() && IsCharmed())) { return; } @@ -2144,10 +2157,10 @@ void Mob::Taunt(NPC *who, bool always_succeed, int chance_bonus, bool from_spell CastToClient()->CheckIncreaseSkill(EQ::skills::SkillTaunt, who, 10); } - Mob *hate_top = who->GetHateMost(); - + Mob *hate_top = who->GetHateMost(); int level_difference = GetLevel() - who->GetLevel(); - bool success = false; + bool success = false; + int taunt_chance = 0; // Support for how taunt worked pre 2000 on LIVE - Can not taunt NPC over your level. if ( @@ -2159,104 +2172,133 @@ void Mob::Taunt(NPC *who, bool always_succeed, int chance_bonus, bool from_spell return; } - // All values used based on live parses after taunt was updated in 2006. - if ( - (hate_top && hate_top->GetHPRatio() >= 20) || - !hate_top || - chance_bonus - ) { - // SE_Taunt this is flat chance - if (chance_bonus) { - success = zone->random.Roll(chance_bonus); - } else { - float taunt_chance = 50.0f; - - if (always_succeed) { - taunt_chance = 101.0f; + if (always_succeed) { + taunt_chance = 100; + } + + // Modern Taunt + if (!RuleB(Combat, ClassicTauntSystem)) { + if ( + (hate_top && hate_top->GetHPRatio() >= 20) || + !hate_top || + chance_bonus + ) { + if (chance_bonus) { + taunt_chance = chance_bonus; } else { - if (level_difference < 0) { - taunt_chance += static_cast(level_difference) * 3.0f; - if (taunt_chance < 20) { - taunt_chance = 20.0f; - } - } else { - taunt_chance += static_cast(level_difference) * 5.0f; - if (taunt_chance > 65) { - taunt_chance = 65.0f; - } + taunt_chance = 50; + } + } else { + if (level_difference < 0) { + taunt_chance += level_difference * 3; + if (taunt_chance < 20) { + taunt_chance = 20; + } + } else { + taunt_chance += level_difference * 5; + if (taunt_chance > 65) { + taunt_chance = 65; } } - // TauntSkillFalloff rate is not based on any real data. Default of 33% gives a reasonable - // result. if (IsClient() && !always_succeed) { taunt_chance -= (RuleR(Combat, TauntSkillFalloff) * - (CastToClient()->MaxSkill(EQ::skills::SkillTaunt) - - GetSkill(EQ::skills::SkillTaunt))); + (CastToClient()->MaxSkill(EQ::skills::SkillTaunt) - + GetSkill(EQ::skills::SkillTaunt))); } if (taunt_chance < 1) { - taunt_chance = 1.0f; + taunt_chance = 1; } - - taunt_chance /= 100.0f; - success = taunt_chance > zone->random.Real(0, 1); - - LogHate( - "Taunter mob {} target npc {} taunt_chance [{}] success [{}] hate_top [{}]", - GetMobDescription(), - who->GetMobDescription(), - taunt_chance, - success ? "true" : "false", - hate_top ? hate_top->GetMobDescription() : "not found" - ); } - - if (success) { - if (hate_top && hate_top != this) { - int64 new_hate = ( - (who->GetNPCHate(hate_top) - who->GetNPCHate(this)) + - bonus_hate + - RuleI(Combat, TauntOverAggro) + - 1 - ); - - LogHate( - "Not Top Hate - Taunter [{}] Target [{}] Hated Top [{}] Hate Top Amt [{}] This Character Amt [{}] Bonus_Hate Amt [{}] TauntOverAggro Amt [{}] - Total [{}]", - GetMobDescription(), - who->GetMobDescription(), - hate_top->GetMobDescription(), - who->GetNPCHate(hate_top), - who->GetNPCHate(this), - bonus_hate, - RuleI(Combat, TauntOverAggro), - new_hate - ); - - who->CastToNPC()->AddToHateList(this, new_hate); - success = true; + } else { // Classic Taunt + if (GetLevel() >= 60 && level_difference < 0) { + if (level_difference < -5) { + taunt_chance = 0; + } else if (level_difference == -5) { + taunt_chance = 10; } else { - who->CastToNPC()->AddToHateList(this, 12); - } - - if (who->CanTalk()) { - who->SayString(SUCCESSFUL_TAUNT, GetCleanName()); + taunt_chance = 50 + level_difference * 10; } } else { - MessageString(Chat::SpellFailure, FAILED_TAUNT); + // this will make the skill difference between the tank classes actually affect success rates + // but only for NPCs near the player's level. Mid to low blues will start to taunt at 50% + // even with lower skill + taunt_chance = 50 * GetSkill(EQ::skills::SkillTaunt) / (who->GetLevel() * 5 + 5); + taunt_chance += level_difference * 5; + + if (taunt_chance > 50) { + taunt_chance = 50; + } else if (taunt_chance < 10) { + taunt_chance = 10; + } } + + // Taunt Chance Rule Bonus + taunt_chance += RuleI(Combat, TauntChanceBonus); + } + + //success roll + success = zone->random.Roll(taunt_chance); + + // Log result + LogHate( + "Taunter mob [{}] target npc [{}] taunt_chance [{}] success [{}] hate_top [{}]", + GetMobDescription(), + who->GetMobDescription(), + taunt_chance, + success ? "true" : "false", + hate_top ? hate_top->GetMobDescription() : "not found" + ); + + // Actual Taunting + if (success) { + if (hate_top && hate_top != this) { + int64 new_hate = ( + (who->GetNPCHate(hate_top) - who->GetNPCHate(this)) + + bonus_hate + + RuleI(Combat, TauntOverAggro) + + 1 + ); + + LogHate( + "Not Top Hate - Taunter [{}] Target [{}] Hated Top [{}] Hate Top Amt [{}] This Character Amt [{}] Bonus_Hate Amt [{}] TauntOverAggro Amt [{}] - Total [{}]", + GetMobDescription(), + who->GetMobDescription(), + hate_top->GetMobDescription(), + who->GetNPCHate(hate_top), + who->GetNPCHate(this), + bonus_hate, + RuleI(Combat, TauntOverAggro), + new_hate + ); + + who->CastToNPC()->AddToHateList(this, new_hate); + } else { + LogHate("Already Hate Top"); + who->CastToNPC()->AddToHateList(this, 12); + } + + if (who->CanTalk()) { + who->SayString(SUCCESSFUL_TAUNT, GetCleanName()); + } + + MessageString(Chat::Skills, TAUNT_SUCCESS, who->GetCleanName()); } else { - MessageString(Chat::SpellFailure, FAILED_TAUNT); + MessageString(Chat::Skills, FAILED_TAUNT); } - TryCastOnSkillUse(who, EQ::skills::SkillTaunt); + // Modern Abilities + if (!RuleB(Combat, ClassicTauntSystem)) { + TryCastOnSkillUse(who, EQ::skills::SkillTaunt); - if (HasSkillProcs()) { - TrySkillProc(who, EQ::skills::SkillTaunt, TauntReuseTime * 1000); - } + if (HasSkillProcs()) { + TrySkillProc(who, EQ::skills::SkillTaunt, TauntReuseTime * 1000); + } - if (success && HasSkillProcSuccess()) { - TrySkillProc(who, EQ::skills::SkillTaunt, TauntReuseTime * 1000, true); + if (success && HasSkillProcSuccess()) { + TrySkillProc(who, EQ::skills::SkillTaunt, TauntReuseTime * 1000, true); + } } } diff --git a/zone/string_ids.h b/zone/string_ids.h index c1b6af479..1ca16e69f 100644 --- a/zone/string_ids.h +++ b/zone/string_ids.h @@ -140,6 +140,7 @@ #define SONG_NEEDS_BRASS 408 //You need to play a brass instrument for this song #define AA_GAIN_ABILITY 410 //You have gained the ability "%T1" at a cost of %2 ability %T3. #define AA_IMPROVE 411 //You have improved %T1 %2 at a cost of %3 ability %T4. +#define TAUNT_SUCCESS 412 //You taunt %1 to ignore others and attack you! #define AA_REUSE_MSG 413 //You can use the ability %B1(1) again in %2 hour(s) %3 minute(s) %4 seconds. #define AA_REUSE_MSG2 414 //You can use the ability %B1(1) again in %2 minute(s) %3 seconds. #define YOU_HEALED 419 //%1 has healed you for %2 points of damage.