From 0e5d578d7163ed22d19459c849ff5026c719b316 Mon Sep 17 00:00:00 2001 From: KayenEQ Date: Fri, 11 Mar 2022 10:13:51 -0500 Subject: [PATCH] [Bug Fix] #tune command various fixes (#2046) * tune fixes * [Bug Fix] #tune command various fixes accuracy tune fix --- zone/attack.cpp | 2 +- zone/mob.h | 1 + zone/tune.cpp | 174 +++++++++++++++++++++++++++++++++++++++++++----- 3 files changed, 160 insertions(+), 17 deletions(-) diff --git a/zone/attack.cpp b/zone/attack.cpp index 8fac3e20c..8283ce0f2 100644 --- a/zone/attack.cpp +++ b/zone/attack.cpp @@ -5453,7 +5453,7 @@ int32 Mob::RuneAbsorb(int32 damage, uint16 type) return damage; } - +//SYNC WITH: tune.cpp, mob.h TuneCommonOutgoingHitSucces void Mob::CommonOutgoingHitSuccess(Mob* defender, DamageHitInfo &hit, ExtraAttackOptions *opts) { if (!defender) diff --git a/zone/mob.h b/zone/mob.h index e88b3eded..9b82937a9 100644 --- a/zone/mob.h +++ b/zone/mob.h @@ -1303,6 +1303,7 @@ public: int Tunecompute_defense(int avoidance_override = 0, int add_avoidance = 0); bool TuneCheckHitChance(Mob* other, DamageHitInfo &hit, int avoidance_override = 0, int add_avoidance = 0); EQ::skills::SkillType TuneAttackAnimation(int Hand, const EQ::ItemInstance* weapon, EQ::skills::SkillType skillinuse = EQ::skills::Skill1HBlunt); + void TuneCommonOutgoingHitSuccess(Mob *defender, DamageHitInfo &hit, ExtraAttackOptions *opts = nullptr); //aa new uint32 GetAA(uint32 rank_id, uint32 *charges = nullptr) const; diff --git a/zone/tune.cpp b/zone/tune.cpp index a5bf04fed..00f16e85d 100644 --- a/zone/tune.cpp +++ b/zone/tune.cpp @@ -101,6 +101,12 @@ void Mob::TuneGetStats(Mob* defender, Mob *attacker) Message(0, "[#Tune] Parry Chance: %.0f pct ", round(TuneGetAvoidMeleeChance(defender, attacker, DMG_PARRIED))); Message(0, "[#Tune] Dodge Chance: %.0f pct ", round(TuneGetAvoidMeleeChance(defender, attacker, DMG_DODGED))); + if (defender->IsNPC()) + { + Message(0, "[#Tune] NPC STAT AC: %i ", static_cast(defender->CastToNPC()->GetNPCStat("ac"))); + Message(0, "[#Tune] NPC STAT Avoidance: %i ", static_cast(defender->CastToNPC()->GetNPCStat("avoidance"))); + } + Message(0, "################################################"); Message(0, "[#Tune] Attacker Statistics vs Defender"); Message(0, "[#Tune] Attacker Name: %s", attacker->GetCleanName()); @@ -109,8 +115,13 @@ void Mob::TuneGetStats(Mob* defender, Mob *attacker) Message(0, "[#Tune] Max Damage %i Min Damage %i", max_damage, min_damage); Message(0, "[#Tune] Total Offense: %i ", TuneGetOffense(defender, attacker)); Message(0, "[#Tune] Chance to hit: %.0f pct", round(hit_chance)); - Message(0, "[#Tune] Accuracy: %i ", TuneGetAccuracy( defender,attacker)); - + Message(0, "[#Tune] Total Accuracy: %i ", TuneGetAccuracy( defender,attacker)); + + if (attacker->IsNPC()) + { + Message(0, "[#Tune] NPC STAT ATK: %i ", static_cast(attacker->CastToNPC()->GetNPCStat("atk"))); + Message(0, "[#Tune] NPC STAT Accuracy: %i ", static_cast(attacker->CastToNPC()->GetNPCStat("accuracy"))); + } } else{ Message(0, "[#Tune] Can not melee this target"); @@ -535,6 +546,7 @@ void Mob::TuneGetAccuracyByHitChance(Mob* defender, Mob *attacker, float hit_cha for (int j = 0; j < max_loop; j++) { + tmp_hit_chance = TuneGetHitChance(defender, attacker, avoidance_override, 0, 0, loop_add_accuracy); if (Msg >= 3) { @@ -559,20 +571,20 @@ void Mob::TuneGetAccuracyByHitChance(Mob* defender, Mob *attacker, float hit_cha Message(0, "[#Tune] AVOIDANCE STAT OVERRRIDE. This is the amount of ACCURACY adjustment needed if this defender had ( %i ) raw AVOIDANCE stat", avoidance_override); } - if (defender->IsNPC()) { - Message(0, "[#Tune] Recommended NPC ACCURACY ADJUSTMENT of ( %i ) on ' %s ' will result in ( %.0f pct ) chance to hit ' %s '.", loop_add_accuracy, defender->GetCleanName(), hit_chance, attacker->GetCleanName()); - Message(0, "[#Tune] SET NPC 'ACCURACY' stat value = [ %i ]", loop_add_accuracy + defender->CastToNPC()->GetAccuracyRating()); + if (attacker->IsNPC()) { + Message(0, "[#Tune] Recommended NPC ACCURACY ADJUSTMENT of ( %i ) on ' %s ' will result in ( %.0f pct ) chance to hit ' %s '.", loop_add_accuracy, attacker->GetCleanName(), hit_chance, defender->GetCleanName()); + Message(0, "[#Tune] SET NPC 'ACCURACY' stat value = [ %i ]", loop_add_accuracy + attacker->CastToNPC()->GetAccuracyRating()); Message(0, "###################COMPLETE###################"); } - else if (defender->IsClient()) { - Message(0, "[#Tune] Recommended CLIENT AVOIDANCE ADJUSTMENT of ( %i ) on %s ' will result in ( %.0f pct ) chance to hit ' %s '.", loop_add_accuracy, defender->GetCleanName(), hit_chance, attacker->GetCleanName()); + else if (attacker->IsClient()) { + Message(0, "[#Tune] Recommended CLIENT ACCURACY ADJUSTMENT of ( %i ) on %s ' will result in ( %.0f pct ) chance to hit ' %s '.", loop_add_accuracy, attacker->GetCleanName(), hit_chance, defender->GetCleanName()); if (loop_add_accuracy >= 0) { - Message(0, "[#Tune] OPTION1: MODIFY Client Avoidance Mod2 stat or SPA 216 Melee Accuracy (spells/items/aa) [+ %i ]", loop_add_accuracy); + Message(0, "[#Tune] MODIFY Client Accuracy Mod2 stat or SPA 216 Melee Accuracy (spells/items/aa) [+ %i ]", loop_add_accuracy); } else { - Message(0, "[#Tune] OPTION1: MODIFY Client Avoidance Mod2 stat or SPA 216 Melee Accuracy (spells/items/aa) [ %i ]", loop_add_accuracy); + Message(0, "[#Tune] Give Client Accuracy Mod2 stat or SPA 216 Melee Accuracy (spells/items/aa) of [ %i ]", loop_add_accuracy); } Message(0, "###################COMPLETE###################"); @@ -586,7 +598,7 @@ void Mob::TuneGetAccuracyByHitChance(Mob* defender, Mob *attacker, float hit_cha Message(0, "###################ABORT#######################"); Message(0, "[#Tune] Error: Unable to find desired result for ( %.0f pct) - Increase interval (%i) AND/OR max loop value (%i) and run again.", hit_chance, interval, max_loop); - Message(0, "[#Tune] Parse ended at ACCURACY ADJUSTMENT of ( %i ) on ' %s ' will result in ( %.0f pct ) chance to hit ' %s '.", loop_add_accuracy, defender->GetCleanName(), hit_chance, attacker->GetCleanName()); + Message(0, "[#Tune] Parse ended at ACCURACY ADJUSTMENT of ( %i ) on ' %s ' will result in ( %.0f pct ) chance to hit ' %s '.", loop_add_accuracy, attacker->GetCleanName(), hit_chance, defender->GetCleanName()); Message(0, "###################COMPLETE###################"); } @@ -771,11 +783,11 @@ int Mob::TuneCalcEvasionBonus(int final_avoidance, int base_avoidance) { return eb; */ - int loop_max = 3000; + int loop_max = 5000; int evasion_bonus = 10; int current_avoidance = 0; - int interval = 5; + int interval = 1; if (base_avoidance > final_avoidance) { @@ -988,7 +1000,7 @@ void Mob::TuneDoAttack(Mob *other, DamageHitInfo &hit, ExtraAttackOptions *opts, other->TuneMeleeMitigation(this, hit, ac_override, add_ac); if (hit.damage_done > 0) { ApplyDamageTable(hit); - CommonOutgoingHitSuccess(other, hit, opts); + TuneCommonOutgoingHitSuccess(other, hit, opts); } LogCombat("Final damage after all reductions: [{}]", hit.damage_done); } @@ -1022,8 +1034,12 @@ int Mob::TuneACSum(bool skip_caps, int ac_override, int add_ac) ac += itembonuses.AC; // items + food + tribute if (IsClient()) { - ac = ac_override; - ac += add_ac; + if (ac_override) { + ac = ac_override; + } + if (add_ac) { + ac += add_ac; + } } int shield_ac = 0; @@ -1040,10 +1056,12 @@ int Mob::TuneACSum(bool skip_caps, int ac_override, int add_ac) } // EQ math ac = (ac * 4) / 3; + // anti-twink if (!skip_caps && IsClient() && GetLevel() < RuleI(Combat, LevelToStopACTwinkControl)) ac = std::min(ac, 25 + 6 * GetLevel()); ac = std::max(0, ac + GetClassRaceACBonus()); + if (IsNPC()) { // This is the developer tweaked number // for the VAST amount of NPCs in EQ this number didn't exceed 600 until recently (PoWar) @@ -1092,7 +1110,6 @@ int Mob::TuneACSum(bool skip_caps, int ac_override, int add_ac) ac = softcap + (over_cap * returns); } } - return ac; } @@ -1401,3 +1418,128 @@ bool Mob::TuneCheckHitChance(Mob* other, DamageHitInfo &hit, int avoidance_overr return zone->random.Roll(50); return tohit_roll > avoid_roll; } + +void Mob::TuneCommonOutgoingHitSuccess(Mob* defender, DamageHitInfo &hit, ExtraAttackOptions *opts) +{ + if (!defender) + return; + +#ifdef LUA_EQEMU + bool ignoreDefault = false; + LuaParser::Instance()->CommonOutgoingHitSuccess(this, defender, hit, opts, ignoreDefault); + + if (ignoreDefault) { + return; + } +#endif + + // BER weren't parsing the halving + if (hit.skill == EQ::skills::SkillArchery || + (hit.skill == EQ::skills::SkillThrowing && GetClass() != BERSERKER)) + hit.damage_done /= 2; + + if (hit.damage_done < 1) + hit.damage_done = 1; + + if (hit.skill == EQ::skills::SkillArchery) { + int bonus = aabonuses.ArcheryDamageModifier + itembonuses.ArcheryDamageModifier + spellbonuses.ArcheryDamageModifier; + 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()) || !RuleB(Combat, ArcheryBonusRequiresStationary)) { + hit.damage_done *= 2; + MessageString(Chat::MeleeCrit, BOW_DOUBLE_DAMAGE); + } + } + } + + int extra_mincap = 0; + int min_mod = hit.base_damage * GetMeleeMinDamageMod_SE(hit.skill) / 100; + if (hit.skill == EQ::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)) { + hit.damage_done = extra_mincap; + } + else { + int ass = TryAssassinate(defender, hit.skill); + if (ass > 0) + hit.damage_done = ass; + } + } + else if (hit.skill == EQ::skills::SkillFrenzy && GetClass() == BERSERKER && GetLevel() > 50) { + extra_mincap = 4 * GetLevel() / 5; + } + + // this has some weird ordering + // Seems the crit message is generated before some of them :P + + // worn item +skill dmg, SPA 220, 418. Live has a normalized version that should be here too + hit.min_damage += GetSkillDmgAmt(hit.skill) + GetPositionalDmgAmt(defender); + + // shielding mod2 + if (defender->itembonuses.MeleeMitigation) + hit.min_damage -= hit.min_damage * defender->itembonuses.MeleeMitigation / 100; + + ApplyMeleeDamageMods(hit.skill, hit.damage_done, defender, opts); + min_mod = std::max(min_mod, extra_mincap); + if (min_mod && hit.damage_done < min_mod) // SPA 186 + hit.damage_done = min_mod; + + hit.damage_done += hit.min_damage; + if (IsClient()) { + int extra = 0; + switch (hit.skill) { + case EQ::skills::SkillThrowing: + case EQ::skills::SkillArchery: + extra = CastToClient()->GetHeroicDEX() / 10; + break; + default: + extra = CastToClient()->GetHeroicSTR() / 10; + break; + } + hit.damage_done += extra; + } + + // this appears where they do special attack dmg mods + int spec_mod = 0; + if (IsSpecialAttack(eSpecialAttacks::Rampage)) { + int mod = GetSpecialAbilityParam(SPECATK_RAMPAGE, 2); + if (mod > 0) + spec_mod = mod; + if ((IsPet() || IsTempPet()) && IsPetOwnerClient()) { + //SE_PC_Pet_Rampage SPA 464 on pet, damage modifier + int spell_mod = spellbonuses.PC_Pet_Rampage[SBIndex::PET_RAMPAGE_DMG_MOD] + itembonuses.PC_Pet_Rampage[SBIndex::PET_RAMPAGE_DMG_MOD] + aabonuses.PC_Pet_Rampage[SBIndex::PET_RAMPAGE_DMG_MOD]; + if (spell_mod > spec_mod) + spec_mod = spell_mod; + } + } + else if (IsSpecialAttack(eSpecialAttacks::AERampage)) { + int mod = GetSpecialAbilityParam(SPECATK_AREA_RAMPAGE, 2); + if (mod > 0) + spec_mod = mod; + if ((IsPet() || IsTempPet()) && IsPetOwnerClient()) { + //SE_PC_Pet_AE_Rampage SPA 465 on pet, damage modifier + int spell_mod = spellbonuses.PC_Pet_AE_Rampage[SBIndex::PET_RAMPAGE_DMG_MOD] + itembonuses.PC_Pet_AE_Rampage[SBIndex::PET_RAMPAGE_DMG_MOD] + aabonuses.PC_Pet_AE_Rampage[SBIndex::PET_RAMPAGE_DMG_MOD]; + if (spell_mod > spec_mod) + spec_mod = spell_mod; + } + } + if (spec_mod > 0) + hit.damage_done = (hit.damage_done * spec_mod) / 100; + + int pct_damage_reduction = defender->GetSkillDmgTaken(hit.skill, opts) + defender->GetPositionalDmgTaken(this); + + hit.damage_done += (hit.damage_done * pct_damage_reduction / 100) + defender->GetPositionalDmgTakenAmt(this); + + if (defender->GetShielderID()) { + DoShieldDamageOnShielder(defender, hit.damage_done, hit.skill); + hit.damage_done -= hit.damage_done * defender->GetShieldTargetMitigation() / 100; //Default shielded takes 50 pct damage + } +}