From cde60926f11cf6f2e8e16ff7884bd5e14ffbb573 Mon Sep 17 00:00:00 2001 From: carolus21rex <85852042+carolus21rex@users.noreply.github.com> Date: Sat, 10 May 2025 16:30:14 +0000 Subject: [PATCH 1/4] Dr Chaos has entered the building --- .devcontainer/devcontainer.json | 2 + .gitignore | 1 + common/spdat.cpp | 4 +- zone/attack.cpp | 329 ++++++++++++++++++++++---------- zone/bonuses.cpp | 1 + zone/bot.cpp | 7 +- zone/client_mods.cpp | 9 +- zone/client_packet.cpp | 2 +- zone/client_process.cpp | 3 +- zone/common.h | 3 +- zone/exp.cpp | 48 ++--- zone/mob.cpp | 2 +- zone/mob.h | 9 + zone/npc.h | 2 +- zone/pets.cpp | 3 +- zone/special_attacks.cpp | 142 ++++++++++---- zone/spell_effects.cpp | 24 ++- zone/spells.cpp | 28 ++- zone/zoning.cpp | 2 +- 19 files changed, 437 insertions(+), 184 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index d6ccc7265..432b7fe35 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -7,6 +7,7 @@ }, "appPort": [ "5998:5998/udp", + "5999:5999/udp", "7000:7000/udp", "7001:7001/udp", "7002:7002/udp", @@ -46,4 +47,5 @@ }, "workspaceFolder": "/src", "workspaceMount": "source=${localWorkspaceFolder},target=/src,type=bind,consistency=cached" + //"postAttachCommand": "ln -nsf .devcontainer/Makefile /src/Makefile" } \ No newline at end of file diff --git a/.gitignore b/.gitignore index 627d3c630..32d555cc2 100644 --- a/.gitignore +++ b/.gitignore @@ -30,6 +30,7 @@ log/ logs/ vcpkg/ perl/ +base/ .idea/* *cbp diff --git a/common/spdat.cpp b/common/spdat.cpp index 7631d2b98..c7f08d977 100644 --- a/common/spdat.cpp +++ b/common/spdat.cpp @@ -118,8 +118,8 @@ bool IsLifetapSpell(uint16 spell_id) if ( spell.target_type == ST_Tap || - spell.target_type == ST_TargetAETap || - spell_id == SPELL_ANCIENT_LIFEBANE + spell.target_type == ST_TargetAETap //|| + //spell_id == SPELL_ANCIENT_LIFEBANE ) { return true; } diff --git a/zone/attack.cpp b/zone/attack.cpp index 78c7334f2..a811153ca 100644 --- a/zone/attack.cpp +++ b/zone/attack.cpp @@ -169,7 +169,7 @@ int Mob::compute_tohit(EQ::skills::SkillType skillinuse) reduction = std::min((110 - reduction) / 100.0, 1.0); tohit = reduction * static_cast(tohit); } else if (IsBerserk()) { - tohit += (GetLevel() * 2) / 5; + tohit += (GetLevel() * 2); } } return std::max(tohit, 1); @@ -184,6 +184,16 @@ int Mob::GetTotalToHit(EQ::skills::SkillType skill, int chance_mod) // calculate attacker's accuracy auto accuracy = compute_tohit(skill) + 10; // add 10 in case the NPC's stats are fucked + + if(skill != EQ::skills::SkillArchery && skill != EQ::skills::SkillThrowing) + { + accuracy += (10 + GetLevel()) * GetDEX() / 5; + } + else + { + accuracy += (10 + GetLevel()) * GetSTR() / 5; + } + if (chance_mod > 0) // multiplier accuracy *= chance_mod; @@ -194,8 +204,10 @@ int Mob::GetTotalToHit(EQ::skills::SkillType skill, int chance_mod) // unsure on the stacking order of these effects, rather hard to parse // item mod2 accuracy isn't applied to range? Theory crafting and parses back it up I guess // mod2 accuracy -- flat bonus + accuracy += itembonuses.HitChance; + /* if (skill != EQ::skills::SkillArchery && skill != EQ::skills::SkillThrowing) { - accuracy += itembonuses.HitChance; + } else { // Applying a scale factor as sources suggest Accuracy should reduce number of missing by 0.1% per point, so 150 = 15% reduction in misses. // Based on my calculator 150 Accuracy was reducing misses by too much (closer to 20%) @@ -203,7 +215,7 @@ int Mob::GetTotalToHit(EQ::skills::SkillType skill, int chance_mod) // Using same scale factor for Avoidance and Accuracy since they impact the formula about the same. accuracy += itembonuses.HitChance * RuleI(Combat, PCAccuracyAvoidanceMod2Scale) / 100; } - + */ //518 Increase ATK accuracy by percentage, stackable auto atkhit_bonus = itembonuses.Attack_Accuracy_Max_Percent + aabonuses.Attack_Accuracy_Max_Percent + spellbonuses.Attack_Accuracy_Max_Percent; if (atkhit_bonus) @@ -275,7 +287,7 @@ int Mob::compute_defense() defense += itembonuses.AvoidMeleeChance * RuleI(Combat, PCAccuracyAvoidanceMod2Scale) / 100; // item mod2 } else { - defense += (8000 * (GetAGI() - 40)) / 36000; + defense += ((10 + static_cast(GetLevel())) * GetAGI()) / 10; if (IsOfClientBot()) { defense += itembonuses.heroic_agi_avoidance; @@ -713,6 +725,8 @@ int Mob::GetACSoftcap() int level = std::min(105, static_cast(GetLevel())) - 1; + return 200 + level * 5; + switch (GetClass()) { case Class::Warrior: return war_softcaps[level]; @@ -746,6 +760,9 @@ double Mob::GetSoftcapReturns() { // These are based on the dev post, they seem to be correct for every level // AKA no more hard caps + + return 0.4; + switch (GetClass()) { case Class::Warrior: return 0.35; @@ -779,74 +796,74 @@ int Mob::GetClassRaceACBonus() { int ac_bonus = 0; auto level = GetLevel(); - if (GetClass() == Class::Monk) { + //if (GetClass() == Class::Monk) { int hardcap = 30; int softcap = 14; - if (level > 99) { + if (level >= 40) { hardcap = 58; softcap = 35; } - else if (level > 94) { + else if (level > 39) { hardcap = 57; softcap = 34; } - else if (level > 89) { + else if (level > 38) { hardcap = 56; softcap = 33; } - else if (level > 84) { + else if (level > 37) { hardcap = 55; softcap = 32; } - else if (level > 79) { + else if (level > 36) { hardcap = 54; softcap = 31; } - else if (level > 74) { + else if (level > 34) { hardcap = 53; softcap = 30; } - else if (level > 69) { + else if (level > 32) { hardcap = 53; softcap = 28; } - else if (level > 64) { + else if (level > 30) { hardcap = 53; softcap = 26; } - else if (level > 63) { + else if (level > 27) { hardcap = 50; softcap = 24; } - else if (level > 61) { + else if (level > 23) { hardcap = 47; softcap = 24; } - else if (level > 59) { + else if (level > 19) { hardcap = 45; softcap = 24; } - else if (level > 54) { + else if (level > 15) { hardcap = 40; softcap = 20; } - else if (level > 50) { + else if (level > 12) { hardcap = 38; softcap = 18; } - else if (level > 44) { + else if (level > 9) { hardcap = 36; softcap = 17; } - else if (level > 29) { + else if (level > 6) { hardcap = 34; softcap = 16; } - else if (level > 14) { + else if (level > 3) { hardcap = 32; softcap = 15; } - int weight = IsClient() ? CastToClient()->CalcCurrentWeight()/10 : 0; + int weight = IsClient() ? CastToClient()->CalcCurrentWeight()/10 : 30; if (weight < hardcap - 1) { double temp = level + 5; if (weight > softcap) { @@ -862,7 +879,7 @@ int Mob::GetClassRaceACBonus() temp = (4.0 * temp) / 3.0; ac_bonus -= static_cast(temp * multiplier); } - } + //} if (GetClass() == Class::Rogue) { int level_scaler = level - 26; @@ -880,24 +897,24 @@ int Mob::GetClassRaceACBonus() ac_bonus = 12; } - if (GetClass() == Class::Beastlord) { - int level_scaler = level - 6; - if (GetAGI() < 80) - ac_bonus = level_scaler / 5; - else if (GetAGI() < 85) - ac_bonus = (level_scaler * 2) / 5; - else if (GetAGI() < 90) - ac_bonus = (level_scaler * 3) / 5; - else if (GetAGI() < 100) - ac_bonus = (level_scaler * 4) / 5; - else if (GetAGI() >= 100) - ac_bonus = (level_scaler * 5) / 5; - if (ac_bonus > 16) - ac_bonus = 16; - } + //if (GetClass() == Class::Beastlord) { + int level_scaler = level; + if (GetAGI() < 10) + ac_bonus += level_scaler / 5; + else if (GetAGI() < 15) + ac_bonus += (level_scaler * 2) / 5; + else if (GetAGI() < 20) + ac_bonus += (level_scaler * 3) / 5; + else if (GetAGI() < 25) + ac_bonus += (level_scaler * 4) / 5; + else if (GetAGI() >= 25) + ac_bonus += (level_scaler * 5) / 5; + //if (ac_bonus > 16) + // ac_bonus += 16; + //} - if (GetRace() == IKSAR) - ac_bonus += EQ::Clamp(static_cast(level), 10, 35); + //if (GetRace() == IKSAR) + // ac_bonus += EQ::Clamp(static_cast(level), 10, 35); return ac_bonus; } @@ -919,11 +936,11 @@ int Mob::ACSum(bool skip_caps) shield_ac += itembonuses.heroic_str_shield_ac; } // EQ math - ac = (ac * 4) / 3; + //ac = (ac * 4) / 3; // anti-twink - if (!skip_caps && IsOfClientBot() && GetLevel() < RuleI(Combat, LevelToStopACTwinkControl)) { - ac = std::min(ac, 25 + 6 * GetLevel()); - } + //if (!skip_caps && IsOfClientBot() && 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 @@ -947,8 +964,8 @@ int Mob::ACSum(bool skip_caps) ac += GetSkill(EQ::skills::SkillDefense) / 3 + spell_aa_ac / 4; } - if (GetAGI() > 70) - ac += GetAGI() / 20; + //if (GetAGI() > 70) + ac += GetAGI() / 4; if (ac < 0) ac = 0; @@ -1023,11 +1040,12 @@ int Mob::offense(EQ::skills::SkillType skill) offense = GetBestMeleeSkill(); break; } - + offense += stat_bonus * 2; + /* if (stat_bonus >= 75) { offense += (2 * stat_bonus - 150) / 3; } - + */ // GetATK() = ATK + itembonuses.ATK + spellbonuses.ATK. However, ATK appears to already be itembonuses.ATK + spellbonuses.ATK for PCs, so as is, it is double counting attack // This causes attack to be significantly more important than it should be based on era rule of thumbs. I do not want to change the GetATK() function in case doing so breaks something, // so instead I am just adding a /2 to remedy the double counting. NPCs do not have this issue, so they are broken up. @@ -1042,29 +1060,48 @@ int Mob::offense(EQ::skills::SkillType skill) } // this assumes "this" is the defender -// this returns between 0.1 to 2.0 +// this returns between 0 to 1 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 (IsOfClientBotMerc() && IsSitting()) { - return mods[19]; + LogCombat("Skip calculations: max roll"); + return 1.0; } + if (offense < 5) offense = 5; + if (mitigation < 5) mitigation = 5; - auto atk_roll = zone->random.Roll0(offense + 5); - auto def_roll = zone->random.Roll0(mitigation + 5); + auto atk_roll = zone->random.Roll0(offense); + auto def_roll = zone->random.Roll0(mitigation); + double mod = 1.0; // starts at full damage - int avg = std::max(1, (offense + mitigation + 10) / 2); - int index = std::max(0, (atk_roll - def_roll) + (avg / 2)); + double half_attack = atk_roll / 2.0; - index = EQ::Clamp((index * 20) / avg, 0, 19); + if (atk_roll >= def_roll * 2.0) { + // Attacker crushed defender: full damage (no mitigation) + mod = 1.0; + } + else if (def_roll > half_attack) { + // Defender mitigates part of the damage + double over = def_roll - half_attack; + double mitigation_percent = over / static_cast(def_roll); // Each point of "over" gives 100/offense percent mitigation + mitigation_percent = EQ::Clamp(mitigation_percent, 0.0, 1.0); // Clamp to maximum 100% mitigation + mod = 1.0 - mitigation_percent; // Reduce the damage + } + LogCombat("def_roll: [{}] attack_roll: [{}] mod: [{}]", def_roll, atk_roll, mod); + //int avg = std::max(1, (offense + mitigation + 10) / 2); + //int index = std::max(0, (atk_roll - def_roll) + (avg / 2)); - return mods[index]; + //mod = EQ::Clamp(mod, 0, 19); + + return mod; } //SYNC WITH: tune.cpp, mob.h TuneMeleeMitigation void Mob::MeleeMitigation(Mob *attacker, DamageHitInfo &hit, ExtraAttackOptions *opts) @@ -1091,12 +1128,21 @@ void Mob::MeleeMitigation(Mob *attacker, DamageHitInfo &hit, ExtraAttackOptions mitigation -= opts->armor_pen_flat; } - auto roll = RollD20(hit.offense, mitigation); + double roll = RollD20(hit.offense, mitigation); // Add bonus to roll if level difference is sufficient - const int level_diff = attacker->GetLevel() - GetLevel(); - const int level_diff_roll_check = RuleI(Combat, LevelDifferenceRollCheck); + const double level_diff = (attacker->GetLevel() - GetLevel()) * 0.02f; + //const int level_diff_roll_check = RuleI(Combat, LevelDifferenceRollCheck); + roll += level_diff; + double roll_cap = IsClient() ? 1.0f : 2.0f; + if (roll > roll_cap) { + roll = roll_cap; + } + if (roll < 0.05f) { + roll = 0.05f; + } +/* if (level_diff_roll_check >= 0) { if (level_diff > level_diff_roll_check) { roll += RuleR(Combat, LevelDifferenceRollBonus); @@ -1112,9 +1158,12 @@ void Mob::MeleeMitigation(Mob *attacker, DamageHitInfo &hit, ExtraAttackOptions } } } - +*/ // +0.5 for rounding, min to 1 dmg - hit.damage_done = std::max(static_cast(roll * static_cast(hit.base_damage) + 0.5), 1); + double preroll = static_cast(hit.damage_done); + hit.damage_done = std::max(static_cast(roll * preroll + 0.5), 1); + + Log(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); } @@ -1543,9 +1592,17 @@ void Mob::DoAttack(Mob *other, DamageHitInfo &hit, ExtraAttackOptions *opts, boo } } } - other->MeleeMitigation(this, hit, opts); if (hit.damage_done > 0) { + if (IsClient()) + { + hit.damage_done = hit.base_damage; + } + else + { + hit.damage_done = CastToNPC()->GetMaxDMG(); + } ApplyDamageTable(hit); + other->MeleeMitigation(this, hit, opts); CommonOutgoingHitSuccess(other, hit, opts); } LogCombat("Final damage after all reductions: [{}]", hit.damage_done); @@ -3789,12 +3846,18 @@ int64 Mob::ReduceAllDamage(int64 damage) if (damage <= 0) return damage; - if (spellbonuses.ManaAbsorbPercentDamage) { - int64 mana_reduced = damage * spellbonuses.ManaAbsorbPercentDamage / 100; + int64 mana_absorb = spellbonuses.ManaAbsorbPercentDamage + itembonuses.ManaAbsorbPercentDamage + aabonuses.ManaAbsorbPercentDamage; + int64 mana_absorb_cap = spellbonuses.ManaAbsorbPercentDamageCap + itembonuses.ManaAbsorbPercentDamageCap + aabonuses.ManaAbsorbPercentDamageCap; + if (mana_absorb > 0 && mana_absorb_cap > 0) { + int64 mana_reduced = damage * mana_absorb / 100; + if (mana_reduced > mana_absorb_cap) + mana_reduced = mana_absorb_cap; + if (GetMana() >= mana_reduced) { damage -= mana_reduced; SetMana(GetMana() - mana_reduced); TryTriggerOnCastRequirement(); + Message(263, "Your mana shield has absorbed %d damage.", mana_reduced); } } @@ -4066,7 +4129,7 @@ void Mob::CommonDamage(Mob* attacker, int64 &damage, const uint16 spell_id, cons if (IsValidSpell(spell_id) && IsLifetapSpell(spell_id)) { int64 healed = damage; - healed = RuleB(Spells, CompoundLifetapHeals) ? attacker->GetActSpellHealing(spell_id, healed) : healed; + //healed = RuleB(Spells, CompoundLifetapHeals) ? attacker->GetActSpellHealing(spell_id, healed) : healed; LogCombat("Applying lifetap heal of [{}] to [{}]", healed, attacker->GetName()); attacker->HealDamage(healed); @@ -5349,9 +5412,9 @@ void Mob::TryCriticalHit(Mob *defender, DamageHitInfo &hit, ExtraAttackOptions * return; } - if (IsNPC() && !RuleB(Combat, NPCCanCrit)) { - return; - } + //if (IsNPC() && !RuleB(Combat, NPCCanCrit)) { + // return; + //} // 1: Try Slay Undead if (defender->GetBodyType() == BodyType::Undead || defender->GetBodyType() == BodyType::SummonedUndead || @@ -5406,11 +5469,7 @@ void Mob::TryCriticalHit(Mob *defender, DamageHitInfo &hit, ExtraAttackOptions * // We either require an innate crit chance or some SPA 169 to crit bool innate_crit = false; int crit_chance = GetCriticalChanceBonus(hit.skill); - if ((GetClass() == Class::Warrior || GetClass() == Class::Berserker) && GetLevel() >= 12) { - innate_crit = true; - } else if (GetClass() == Class::Ranger && GetLevel() >= 12 && hit.skill == EQ::skills::SkillArchery) { - innate_crit = true; - } else if (GetClass() == Class::Rogue && GetLevel() >= 12 && hit.skill == EQ::skills::SkillThrowing) { + if (GetLevel() >= 4) { innate_crit = true; } @@ -5425,20 +5484,35 @@ void Mob::TryCriticalHit(Mob *defender, DamageHitInfo &hit, ExtraAttackOptions * } else { difficulty = RuleI(Combat, MeleeCritDifficulty); } - + int dex_bonus = 50; + int crit_stat_bon = 0; int roll = zone->random.Int(1, difficulty); - int dex_bonus = GetDEX(); + if (hit.skill != EQ::skills::SkillArchery && !hit.skill == EQ::skills::SkillThrowing) + { + dex_bonus += GetDEX() * 10; + crit_stat_bon += GetSTR(); + } + else + { + dex_bonus += GetSTR() * 10; + crit_stat_bon += GetDEX(); + + } + if (dex_bonus > 255) { - dex_bonus = 255 + ((dex_bonus - 255) / 5); + dex_bonus = 255 + ((dex_bonus - 255) / 2); + } + if (crit_stat_bon > 30){ + crit_stat_bon = 30 + ((crit_stat_bon) / 3); } - dex_bonus += 45; // chances did not match live without a small boost + // chances did not match live without a small boost // so if we have an innate crit we have a better chance, except for ber throwing - if (!innate_crit || (GetClass() == Class::Berserker && hit.skill == EQ::skills::SkillThrowing)) { - dex_bonus = dex_bonus * 3 / 5; - } + //if (!innate_crit || (GetClass() == Class::Berserker && hit.skill == EQ::skills::SkillThrowing)) { + // dex_bonus = dex_bonus * 3 / 5; + //} if (crit_chance) { dex_bonus += dex_bonus * crit_chance / 100; @@ -5454,7 +5528,7 @@ void Mob::TryCriticalHit(Mob *defender, DamageHitInfo &hit, ExtraAttackOptions * // 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 + GetCritDmgMod(hit.skill); + int crit_mod = 170 + GetCritDmgMod(hit.skill) + crit_stat_bon; if (crit_mod < 100) { crit_mod = 100; @@ -5464,7 +5538,7 @@ void Mob::TryCriticalHit(Mob *defender, DamageHitInfo &hit, ExtraAttackOptions * LogCombatDetail("Crit success roll [{}] dex chance [{}] og dmg [{}] crit_mod [{}] new dmg [{}]", roll, dex_bonus, og_damage, crit_mod, hit.damage_done); // step 3: check deadly strike - if (GetClass() == Class::Rogue && hit.skill == EQ::skills::SkillThrowing) { + if (hit.skill == EQ::skills::SkillThrowing) { if (BehindMob(defender, GetX(), GetY())) { int chance = GetLevel() * 12; if (zone->random.Int(1, 1000) < chance) { @@ -5827,6 +5901,9 @@ const DamageTable &Mob::GetDamageTable() const { 415, 15, 40 }, // 105 }; + static DamageTable melee_table = { 1000 + 50 * GetLevel(), 50 - GetLevel(), 0 - GetLevel()}; + return melee_table; + bool monk = GetClass() == Class::Monk; bool melee = IsWarriorClass(); // tables caped at 105 for now -- future proofed for a while at least :P @@ -5839,7 +5916,7 @@ const DamageTable &Mob::GetDamageTable() const return mnk_table[0]; auto &which = monk ? mnk_table : dmg_table; - return which[level - 50]; + //return which[level - 50]; } int Mob::GetMobFixedOffenseSkill() @@ -5918,12 +5995,10 @@ void Mob::ApplyDamageTable(DamageHitInfo &hit) } // this was parsed, but we do see the min of 10 and the normal minus factor is 105, so makes sense - if (hit.offense < 115) - return; // things that come out to 1 dmg seem to skip this (ex non-bash slam classes) - if (hit.damage_done < 2) - return; + //if (hit.damage_done < 2) + // return; auto &damage_table = GetDamageTable(); @@ -5933,11 +6008,11 @@ void Mob::ApplyDamageTable(DamageHitInfo &hit) 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); - hit.damage_done = (hit.damage_done * percent) / 100; + int percent = std::min(128 + extrapercent, damage_table.max_extra); + hit.damage_done = (hit.damage_done * percent) / 128; - if (IsWarriorClass() && GetLevel() > 54) - hit.damage_done++; + //if (IsWarriorClass() && GetLevel() > 54) + // hit.damage_done++; Log(Logs::Detail, Logs::Attack, "Damage table applied %d (max %d)", percent, damage_table.max_extra); } @@ -6333,10 +6408,11 @@ void Mob::CommonOutgoingHitSuccess(Mob* defender, DamageHitInfo &hit, ExtraAttac #endif // BER weren't parsing the halving + /* if (hit.skill == EQ::skills::SkillArchery || (hit.skill == EQ::skills::SkillThrowing && GetClass() != Class::Berserker)) hit.damage_done /= 2; - + */ if (hit.damage_done < 1) hit.damage_done = 1; @@ -6347,7 +6423,7 @@ void Mob::CommonOutgoingHitSuccess(Mob* defender, DamageHitInfo &hit, ExtraAttac if (headshot > 0) { hit.damage_done = headshot; } - else if (GetClass() == Class::Ranger && GetLevel() >= RuleI(Combat, ArcheryBonusLevelRequirement)) { // no double dmg on headshot + else if (GetClass() == Class::Ranger && GetLevel() >= 10) { // 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); @@ -6377,7 +6453,7 @@ void Mob::CommonOutgoingHitSuccess(Mob* defender, DamageHitInfo &hit, ExtraAttac } } } - else if (hit.skill == EQ::skills::SkillFrenzy && GetClass() == Class::Berserker && GetLevel() > 50) { + else if (hit.skill == EQ::skills::SkillFrenzy) { extra_mincap = 4 * GetLevel() / 5; } @@ -6396,7 +6472,8 @@ void Mob::CommonOutgoingHitSuccess(Mob* defender, DamageHitInfo &hit, ExtraAttac if (min_mod && hit.damage_done < min_mod) // SPA 186 hit.damage_done = min_mod; - TryCriticalHit(defender, hit, opts); + if (!defender->HasShieldEquipped()) + TryCriticalHit(defender, hit, opts); hit.damage_done += hit.min_damage; if (IsOfClientBot()) { @@ -6746,12 +6823,62 @@ void Client::DoAttackRounds(Mob *target, int hand, bool IsFromSpell) if (!target || (target && target->IsCorpse())) { return; } + int attack_count = 1; - Attack(target, hand, false, false, IsFromSpell); - bool candouble = CanThisClassDoubleAttack(); + //Attack(target, hand, false, false, IsFromSpell); + + + if (CanThisClassDoubleAttack()) + { + CheckIncreaseSkill(EQ::skills::SkillDoubleAttack, target, -10); + + if (CheckDoubleAttack()) attack_count++; + } + + if (CanThisClassTripleAttack()) + { + CheckIncreaseSkill(EQ::skills::SkillTripleAttack, target, -10); + if (CheckTripleAttack()) attack_count++; + } + + if (HasTwoHanderEquipped()) + { + auto extraattackchance = aabonuses.ExtraAttackChance[SBIndex::EXTRA_ATTACK_CHANCE] + spellbonuses.ExtraAttackChance[SBIndex::EXTRA_ATTACK_CHANCE] + + itembonuses.ExtraAttackChance[SBIndex::EXTRA_ATTACK_CHANCE]; + if (extraattackchance && zone->random.Roll(extraattackchance)) { + attack_count += std::max({aabonuses.ExtraAttackChance[SBIndex::EXTRA_ATTACK_NUM_ATKS], spellbonuses.ExtraAttackChance[SBIndex::EXTRA_ATTACK_NUM_ATKS], itembonuses.ExtraAttackChance[SBIndex::EXTRA_ATTACK_NUM_ATKS] }); + + } + } + else + { + auto extraattackchance_primary = aabonuses.ExtraAttackChancePrimary[SBIndex::EXTRA_ATTACK_CHANCE] + spellbonuses.ExtraAttackChancePrimary[SBIndex::EXTRA_ATTACK_CHANCE] + + itembonuses.ExtraAttackChancePrimary[SBIndex::EXTRA_ATTACK_CHANCE]; + if (extraattackchance_primary && zone->random.Roll(extraattackchance_primary)) { + attack_count += std::max({aabonuses.ExtraAttackChancePrimary[SBIndex::EXTRA_ATTACK_NUM_ATKS], spellbonuses.ExtraAttackChancePrimary[SBIndex::EXTRA_ATTACK_NUM_ATKS], itembonuses.ExtraAttackChancePrimary[SBIndex::EXTRA_ATTACK_NUM_ATKS] }); + } + } + + int flurry_chance = aabonuses.FlurryChance + spellbonuses.FlurryChance + itembonuses.FlurryChance; + + if (flurry_chance && zone->random.Roll(flurry_chance)) { + attack_count++; + + if (zone->random.Roll(flurry_chance)) { + attack_count++; + } + MessageString(Chat::NPCFlurry, YOU_FLURRY); + } + + + for (int i = 0; i < attack_count; i++) { + Attack(target, hand, false, false, IsFromSpell); + } + // extra off hand non-sense, can only double with skill of 150 or above // or you have any amount of GiveDoubleAttack + /* if (candouble && hand == EQ::invslot::slotSecondary) candouble = GetSkill(EQ::skills::SkillDoubleAttack) > 149 || @@ -6820,6 +6947,7 @@ void Client::DoAttackRounds(Mob *target, int hand, bool IsFromSpell) } } } + */ } bool Mob::CheckDualWield() @@ -6827,8 +6955,7 @@ bool Mob::CheckDualWield() // Pets /might/ follow a slightly different progression // although it could all be from pets having different skills than most mobs int chance = GetSkill(EQ::skills::SkillDualWield); - if (GetLevel() > 35) - chance += GetLevel(); + chance += 3 * GetLevel(); chance += aabonuses.Ambidexterity + spellbonuses.Ambidexterity + itembonuses.Ambidexterity; int per_inc = spellbonuses.DualWieldChance + aabonuses.DualWieldChance + itembonuses.DualWieldChance; @@ -6840,7 +6967,7 @@ bool Mob::CheckDualWield() bool Client::CheckDualWield() { - int chance = GetSkill(EQ::skills::SkillDualWield) + GetLevel(); + int chance = GetSkill(EQ::skills::SkillDualWield) + 3 * GetLevel(); chance += aabonuses.Ambidexterity + spellbonuses.Ambidexterity + itembonuses.Ambidexterity; int per_inc = spellbonuses.DualWieldChance + aabonuses.DualWieldChance + itembonuses.DualWieldChance; diff --git a/zone/bonuses.cpp b/zone/bonuses.cpp index d81c3ddf8..33417d688 100644 --- a/zone/bonuses.cpp +++ b/zone/bonuses.cpp @@ -3107,6 +3107,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne if (new_bonus->ManaAbsorbPercentDamage < effect_value){ new_bonus->ManaAbsorbPercentDamage = effect_value; } + new_bonus->ManaAbsorbPercentDamageCap += limit_value; break; } diff --git a/zone/bot.cpp b/zone/bot.cpp index af6723181..20c9fbc83 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -3440,7 +3440,6 @@ bool Bot::CheckIfIncapacitated() { } void Bot::SetBerserkState() {// Berserk updates should occur if primary AI criteria are met - if (GetClass() == Class::Warrior || GetClass() == Class::Berserker) { if (!berserk && GetHPRatio() < RuleI(Combat, BerserkerFrenzyStart)) { entity_list.MessageCloseString(this, false, 200, 0, BERSERK_START, GetName()); berserk = true; @@ -3449,7 +3448,7 @@ void Bot::SetBerserkState() {// Berserk updates should occur if primary AI crite entity_list.MessageCloseString(this, false, 200, 0, BERSERK_END, GetName()); berserk = false; } - } + } Mob* Bot::SetFollowMob(Client* leash_owner) { @@ -3559,7 +3558,7 @@ void Bot::Depop() { RemoveAllAuras(); Mob* bot_pet = GetPet(); - + if (bot_pet) { if (bot_pet->Charmed()) { bot_pet->BuffFadeByEffect(SE_Charm); @@ -11178,7 +11177,7 @@ void Bot::SetSpellTypePriority(uint16 spell_type, uint8 priority_type, uint16 pr std::list Bot::GetSpellTypesPrioritized(uint8 priority_type) { std::list cast_order; - + for (uint16 i = BotSpellTypes::START; i <= BotSpellTypes::END; i++) { BotSpellTypeOrder typeSettings = { .spellType = i, diff --git a/zone/client_mods.cpp b/zone/client_mods.cpp index 23ef13454..b68324548 100644 --- a/zone/client_mods.cpp +++ b/zone/client_mods.cpp @@ -317,7 +317,7 @@ int64 Client::CalcHPRegenCap() int64 Client::CalcMaxHP() { float nd = 10000; - max_hp = (CalcBaseHP() + itembonuses.HP); + max_hp = (CalcBaseHP() + itembonuses.HP + spellbonuses.HP + aabonuses.HP); //The AA desc clearly says it only applies to base hp.. //but the actual effect sent on live causes the client //to apply it to (basehp + itemhp).. I will oblige to the client's whims over @@ -476,7 +476,8 @@ uint32 Mob::GetClassLevelFactor() } } - return multiplier; + //return multiplier; + return 3000 + 750 * mlevel; } int64 Client::CalcBaseHP() @@ -503,7 +504,7 @@ int64 Client::CalcBaseHP() else { Post255 = 0; } - base_hp = (5) + (GetLevel() * lm / 10) + (((GetSTA() - Post255) * GetLevel() * lm / 3000)) + ((Post255 * GetLevel()) * lm / 6000); + base_hp = (5) + ((GetSTA() - Post255) * lm / 3000) + (Post255 * lm / 6000); } return base_hp; } @@ -653,6 +654,7 @@ int64 Client::CalcBaseManaRegen() else { regen = 2; } + regen += GetINT()/10; return regen; } @@ -691,6 +693,7 @@ int64 Client::CalcManaRegen(bool bCombat) if (level > 63) regen++; } + regen += GetINT()/10; regen += aabonuses.ManaRegen; // add in + 1 bonus for SE_CompleteHeal, but we don't do anything for it yet? diff --git a/zone/client_packet.cpp b/zone/client_packet.cpp index 2ec433b36..f208cfc17 100644 --- a/zone/client_packet.cpp +++ b/zone/client_packet.cpp @@ -1052,7 +1052,7 @@ void Client::Handle_Connect_OP_ClientReady(const EQApplicationPacket *app) if (!Spawned()) SendZoneInPackets(); CompleteConnect(); - SendHPUpdate(); + SendHPUpdate(true); } void Client::Handle_Connect_OP_ClientUpdate(const EQApplicationPacket *app) diff --git a/zone/client_process.cpp b/zone/client_process.cpp index c9de4c160..f5782469c 100644 --- a/zone/client_process.cpp +++ b/zone/client_process.cpp @@ -463,7 +463,6 @@ bool Client::Process() { } } - if (GetClass() == Class::Warrior || GetClass() == Class::Berserker) { if (!dead && !IsBerserk() && GetHPRatio() < RuleI(Combat, BerserkerFrenzyStart)) { entity_list.MessageCloseString(this, false, 200, 0, BERSERK_START, GetName()); berserk = true; @@ -472,7 +471,7 @@ bool Client::Process() { entity_list.MessageCloseString(this, false, 200, 0, BERSERK_END, GetName()); berserk = false; } - } + if (auto_attack && may_use_attacks && auto_attack_target != nullptr && CanThisClassDualWield() && attack_dw_timer.Check()) diff --git a/zone/common.h b/zone/common.h index 10c17b7a6..af6113a85 100644 --- a/zone/common.h +++ b/zone/common.h @@ -7,7 +7,7 @@ #include #define HIGHEST_RESIST 9 //Max resist type value -#define MAX_SPELL_PROJECTILE 10 //Max amount of spell projectiles that can be active by a single mob. +#define MAX_SPELL_PROJECTILE 100 //Max amount of spell projectiles that can be active by a single mob. /* macros for IsAttackAllowed, IsBeneficialAllowed */ #define _CLIENT(x) (x && x->IsClient() && !x->CastToClient()->IsBecomeNPC()) @@ -491,6 +491,7 @@ struct StatBonuses { bool TriggerMeleeThreshold; // Has Melee Threshhold bool TriggerSpellThreshold; // Has Spell Threshhold uint32 ManaAbsorbPercentDamage; // 0 = Mitigation value + uint32 ManaAbsorbPercentDamageCap; // 0 = Mitigation value int32 EnduranceAbsorbPercentDamage[2]; // 0 = Mitigation value 1 = Percent Endurance drain per HP lost int32 ShieldBlock; // Chance to Shield Block int32 BlockBehind; // Chance to Block Behind (with our without shield) diff --git a/zone/exp.cpp b/zone/exp.cpp index bd2bacb74..fec569792 100644 --- a/zone/exp.cpp +++ b/zone/exp.cpp @@ -1017,40 +1017,30 @@ uint32 Client::GetEXPForLevel(uint16 check_level) uint16 check_levelm1 = check_level-1; float mod; - if (check_level < 31) + if (check_level < 5) mod = 1.0; - else if (check_level < 36) - mod = 1.1; - else if (check_level < 41) - mod = 1.2; - else if (check_level < 46) - mod = 1.3; - else if (check_level < 52) - mod = 1.4; - else if (check_level < 53) - mod = 1.5; - else if (check_level < 54) - mod = 1.6; - else if (check_level < 55) - mod = 1.7; - else if (check_level < 56) - mod = 1.9; - else if (check_level < 57) - mod = 2.1; - else if (check_level < 58) - mod = 2.3; - else if (check_level < 59) - mod = 2.5; - else if (check_level < 60) - mod = 2.7; - else if (check_level < 61) - mod = 3.0; + else if (check_level < 10) + mod = 1.4 + 0.02 * check_level; + else if (check_level < 15) + mod = 1.5 + 0.03 * check_level; + else if (check_level < 20) + mod = 1.4 + 0.04 * check_level; + else if (check_level < 25) + mod = 1.2 + 0.05 * check_level; + else if (check_level < 30) + mod = 1.0 + 0.06 * check_level; + else if (check_level < 35) + mod = 0.7 + 0.07 * check_level; + else if (check_level < 40) + mod = 0.5 + 0.08 * check_level; + else if (check_level < 45) + mod = 0.2 + 0.09 * check_level; else - mod = 3.1; + mod = 1 + 0.1 * check_level; float base = (check_levelm1)*(check_levelm1)*(check_levelm1); - mod *= 1000; + mod *= 2500; uint32 finalxp = uint32(base * mod); diff --git a/zone/mob.cpp b/zone/mob.cpp index b411197e6..f4f791526 100644 --- a/zone/mob.cpp +++ b/zone/mob.cpp @@ -6071,7 +6071,7 @@ int32 Mob::GetPositionalDmgTaken(Mob *attacker) if (back_arc || front_arc) { //Do they have this bonus? if (attacker->BehindMob(this, attacker->GetX(), attacker->GetY()))//Check if attacker is striking from behind - total_mod = back_arc; //If so, apply the back arc modifier only + total_mod = back_arc + 25; //If so, apply the back arc modifier only else total_mod = front_arc;//If not, apply the front arc modifer only } diff --git a/zone/mob.h b/zone/mob.h index 8664aca76..1f6a18549 100644 --- a/zone/mob.h +++ b/zone/mob.h @@ -1803,6 +1803,13 @@ public: void SetSpawnedInWater(bool spawned_in_water); bool turning; + + // AoT Custom + int Pre_Attack_Behavior(EQ::skills::SkillType); // adjusts attack round behavior. Returns the number of attack increases. + int Modify_Attack_Damage(EQ::skills::SkillType, DamageHitInfo &); // adjusts behavior of an attack while attack damage is being calculated. + int Late_Attack_Behavior(EQ::skills::SkillType, DamageHitInfo &); // adjusts post attack behavior per attack, not per round, after attack has been calculated. + int Post_Attack_Behavior(EQ::skills::SkillType, DamageHitInfo &); // adjusts post attack behavior per attack round. + protected: // Bind wound @@ -1928,6 +1935,8 @@ protected: uint16 m_merchant_session_entity_id; + + private: Mob* target; EQ::InventoryProfile m_inv; diff --git a/zone/npc.h b/zone/npc.h index 31a4e09f9..74b73e672 100644 --- a/zone/npc.h +++ b/zone/npc.h @@ -424,7 +424,7 @@ public: SwarmPet *GetSwarmInfo() { return (swarmInfoPtr); } void SetSwarmInfo(SwarmPet *mSwarmInfo) { swarmInfoPtr = mSwarmInfo; } - int32 GetAccuracyRating() const { return (accuracy_rating); } + int32 GetAccuracyRating() const { return (accuracy_rating + GetDEX()); } void SetAccuracyRating(int32 d) { accuracy_rating = d;} int32 GetAvoidanceRating() const { return (avoidance_rating); } void SetAvoidanceRating(int32 d) { avoidance_rating = d;} diff --git a/zone/pets.cpp b/zone/pets.cpp index 37028e60a..98a34e9e5 100644 --- a/zone/pets.cpp +++ b/zone/pets.cpp @@ -95,7 +95,7 @@ void Mob::MakePoweredPet(uint16 spell_id, const char* pettype, int16 petpower, if(HasPet() || pettype == nullptr) return; - int16 act_power = 0; // The actual pet power we'll use. + int32 act_power = 0; // The actual pet power we'll use. if (petpower == -1) { if (IsClient()) { act_power = CastToClient()->GetFocusEffect(focusPetPower, spell_id);//Client only @@ -106,6 +106,7 @@ void Mob::MakePoweredPet(uint16 spell_id, const char* pettype, int16 petpower, else if (petpower > 0) act_power = petpower; + act_power += GetCHA(); // optional rule: classic style variance in pets. Achieve this by // adding a random 0-4 to pet power, since it only comes in increments // of five from focus effects. diff --git a/zone/special_attacks.cpp b/zone/special_attacks.cpp index 4ce6bd150..a7ed19912 100644 --- a/zone/special_attacks.cpp +++ b/zone/special_attacks.cpp @@ -1027,6 +1027,31 @@ void Mob::DoArcheryAttackDmg(Mob *other, const EQ::ItemInstance *RangeWeapon, co const EQ::ItemInstance *_Ammo = nullptr; const EQ::ItemData *last_ammo_used = nullptr; + int attack_count = 1; + if (CanThisClassDoubleAttack()) + { + if(IsClient()) + CastToClient()->CheckIncreaseSkill(EQ::skills::SkillDoubleAttack, target, -10); + + if (CheckDoubleAttack()) attack_count++; + } + + if (IsClient() && CanThisClassTripleAttack()) + { + CastToClient()->CheckIncreaseSkill(EQ::skills::SkillTripleAttack, target, -10); + if (CastToClient()->CheckTripleAttack()) attack_count++; + } + + int flurry_chance = aabonuses.FlurryChance + spellbonuses.FlurryChance + itembonuses.FlurryChance; + + if (flurry_chance && zone->random.Roll(flurry_chance)) { + attack_count++; + + if (zone->random.Roll(flurry_chance)) { + attack_count++; + } + //MessageString(Chat::NPCFlurry, YOU_FLURRY); + } /* If LaunchProjectile is false this function will do archery damage on target, otherwise it will shoot the projectile at the target, once the projectile hits target @@ -1112,7 +1137,7 @@ void Mob::DoArcheryAttackDmg(Mob *other, const EQ::ItemInstance *RangeWeapon, co } DamageHitInfo my_hit {}; - my_hit.base_damage = MaxDmg; + my_hit.base_damage = MaxDmg * (25 + attack_count * 75) / 100; my_hit.min_damage = 0; my_hit.damage_done = 1; @@ -1121,6 +1146,12 @@ void Mob::DoArcheryAttackDmg(Mob *other, const EQ::ItemInstance *RangeWeapon, co my_hit.tohit = GetTotalToHit(my_hit.skill, chance_mod); my_hit.hand = EQ::invslot::slotRange; + + + //Attack(target, hand, false, false, IsFromSpell); + + + DoAttack(other, my_hit); TotalDmg = my_hit.damage_done; } else { @@ -1153,22 +1184,25 @@ void Mob::DoArcheryAttackDmg(Mob *other, const EQ::ItemInstance *RangeWeapon, co if (!DisableProcs) { // Skill Proc Attempt - if (HasSkillProcs() && other && !other->HasDied()) { - if (ReuseTime) { - TrySkillProc(other, EQ::skills::SkillArchery, ReuseTime); + for (int i = 0; i < attack_count; i++) + { + if (HasSkillProcs() && other && !other->HasDied()) { + if (ReuseTime) { + TrySkillProc(other, EQ::skills::SkillArchery, ReuseTime); + } + else { + TrySkillProc(other, EQ::skills::SkillArchery, 0, false, EQ::invslot::slotRange); + } } - else { - TrySkillProc(other, EQ::skills::SkillArchery, 0, false, EQ::invslot::slotRange); - } - } - // Skill Proc Success ... can proc off hits OR misses - if (HasSkillProcSuccess() && other && !other->HasDied()) { - if (ReuseTime) { - TrySkillProc(other, EQ::skills::SkillArchery, ReuseTime, true); - } - else { - TrySkillProc(other, EQ::skills::SkillArchery, 0, true, EQ::invslot::slotRange); + // Skill Proc Success ... can proc off hits OR misses + if (HasSkillProcSuccess() && other && !other->HasDied()) { + if (ReuseTime) { + TrySkillProc(other, EQ::skills::SkillArchery, ReuseTime, true); + } + else { + TrySkillProc(other, EQ::skills::SkillArchery, 0, true, EQ::invslot::slotRange); + } } } } @@ -1607,12 +1641,20 @@ void Client::ThrowingAttack(Mob* other, bool CanDoubleAttack) { //old was 51 DoThrowingAttackDmg(other, RangeWeapon, item, 0, 0, 0, 0, 0,ammo_slot); // Consume Ammo, unless Ammo Consumption is disabled - if (RuleB(Combat, ThrowingConsumesAmmo)) { - DeleteItemInInventory(ammo_slot, 1, true); - LogCombat("Consumed Throwing Ammo from slot {}.", ammo_slot); - } else { - LogCombat("Throwing Ammo Consumption is disabled."); - } + //EndlessQuiver AA base1 = 100% Chance to avoid consumption arrow. + int ChanceAvoidConsume = aabonuses.ConsumeProjectile + itembonuses.ConsumeProjectile + spellbonuses.ConsumeProjectile; + + // Consume Ammo, unless Ammo Consumption is disabled or player has Endless Quiver + bool consumes_ammo = RuleB(Combat, ThrowingConsumesAmmo); + if ( + consumes_ammo && + ( + !ChanceAvoidConsume || + (ChanceAvoidConsume < 100 && zone->random.Int(0,99) > ChanceAvoidConsume) + ) + ) + DeleteItemInInventory(ammo_slot, 1, true); + CommonBreakInvisibleFromCombat(); } @@ -1627,6 +1669,7 @@ void Mob::DoThrowingAttackDmg(Mob *other, const EQ::ItemInstance *RangeWeapon, c return; } + int attack_count = 1; const EQ::ItemInstance *m_RangeWeapon = nullptr;//throwing weapon const EQ::ItemData *last_ammo_used = nullptr; @@ -1689,8 +1732,34 @@ void Mob::DoThrowingAttackDmg(Mob *other, const EQ::ItemInstance *RangeWeapon, c int TotalDmg = 0; if (WDmg > 0) { + + if (CanThisClassDoubleAttack()) + { + if(IsClient()) + CastToClient()->CheckIncreaseSkill(EQ::skills::SkillDoubleAttack, target, -10); + + if (CheckDoubleAttack()) attack_count++; + } + + if (IsClient() && CanThisClassTripleAttack()) + { + CastToClient()->CheckIncreaseSkill(EQ::skills::SkillTripleAttack, target, -10); + if (CastToClient()->CheckTripleAttack()) attack_count++; + } + + int flurry_chance = aabonuses.FlurryChance + spellbonuses.FlurryChance + itembonuses.FlurryChance; + + if (flurry_chance && zone->random.Roll(flurry_chance)) { + attack_count++; + + if (zone->random.Roll(flurry_chance)) { + attack_count++; + } + //MessageString(Chat::NPCFlurry, YOU_FLURRY); + } + DamageHitInfo my_hit {}; - my_hit.base_damage = WDmg; + my_hit.base_damage = WDmg * (25 + attack_count * 75) / 100; my_hit.min_damage = 0; my_hit.damage_done = 1; @@ -1721,21 +1790,24 @@ void Mob::DoThrowingAttackDmg(Mob *other, const EQ::ItemInstance *RangeWeapon, c TryCastOnSkillUse(other, EQ::skills::SkillThrowing); if (!DisableProcs) { - if (HasSkillProcs() && other && !other->HasDied()) { - if (ReuseTime) { - TrySkillProc(other, EQ::skills::SkillThrowing, ReuseTime); + for (int i = 0; i < attack_count; i ++) + { + if (HasSkillProcs() && other && !other->HasDied()) { + if (ReuseTime) { + TrySkillProc(other, EQ::skills::SkillThrowing, ReuseTime); + } + else { + TrySkillProc(other, EQ::skills::SkillThrowing, 0, false, EQ::invslot::slotRange); + } } - else { - TrySkillProc(other, EQ::skills::SkillThrowing, 0, false, EQ::invslot::slotRange); - } - } - if (HasSkillProcSuccess() && other && !other->HasDied()) { - if (ReuseTime) { - TrySkillProc(other, EQ::skills::SkillThrowing, ReuseTime, true); - } - else { - TrySkillProc(other, EQ::skills::SkillThrowing, 0, true, EQ::invslot::slotRange); + if (HasSkillProcSuccess() && other && !other->HasDied()) { + if (ReuseTime) { + TrySkillProc(other, EQ::skills::SkillThrowing, ReuseTime, true); + } + else { + TrySkillProc(other, EQ::skills::SkillThrowing, 0, true, EQ::invslot::slotRange); + } } } } diff --git a/zone/spell_effects.cpp b/zone/spell_effects.cpp index e99028737..464187ae0 100644 --- a/zone/spell_effects.cpp +++ b/zone/spell_effects.cpp @@ -263,6 +263,8 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove if (buffslot >= 0) { //This is here so dots with hit counters tic down on initial cast. if (caster && effect_value < 0) { + effect_value = (effect_value * (100 + 2 * caster->GetINT())) / 100; + effect_value = effect_value * partial / 100; caster->GetActDoTDamage(spell_id, effect_value, this, false); } break; @@ -279,6 +281,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove // take partial damage into account dmg = (int64) (dmg * partial / 100); + dmg = (dmg * (100 + 2 * caster->GetINT())) / 100; //handles AAs and what not... if(caster) { @@ -286,9 +289,10 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove dmg = caster->GetActReflectedSpellDamage(spell_id, (int64)(spells[spell_id].base_value[i] * partial / 100), reflect_effectiveness); } else { + //dmg = dmg * partial / 100; dmg = caster->GetActSpellDamage(spell_id, dmg, this); } - caster->ResourceTap(-dmg, spell_id); + caster->ResourceTap(-effect_value, spell_id); } if (dmg <= 0) { @@ -304,7 +308,10 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove //healing spell... if(caster) + { + dmg = (dmg * (100 + 2 * caster->GetWIS())) / 100; dmg = caster->GetActSpellHealing(spell_id, dmg, this); + } HealDamage(dmg, caster); } @@ -1377,6 +1384,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove case SE_Rune: { + effect_value = (effect_value * (100 + 2 * caster->GetCHA())) / 100; #ifdef SPELL_EFFECT_SPAM snprintf(effect_desc, _EDLEN, "Melee Absorb Rune: %+i", effect_value); #endif @@ -1388,6 +1396,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove case SE_AbsorbMagicAtt: { + effect_value = (effect_value * (100 + 2 * caster->GetCHA())) / 100; #ifdef SPELL_EFFECT_SPAM snprintf(effect_desc, _EDLEN, "Spell Absorb Rune: %+i", effect_value); #endif @@ -1400,6 +1409,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove case SE_MitigateMeleeDamage: { + effect_value = (effect_value * (100 + 2 * caster->GetCHA())) / 100; if (buffslot > -1) { buffs[buffslot].melee_rune = spells[spell_id].max_value[i]; } @@ -1408,6 +1418,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove case SE_MeleeThresholdGuard: { + effect_value = (effect_value * (100 + 2 * caster->GetCHA())) / 100; if (buffslot > -1) { buffs[buffslot].melee_rune = spells[spell_id].max_value[i]; } @@ -1416,6 +1427,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove case SE_SpellThresholdGuard: { + effect_value = (effect_value * (100 + 2 * caster->GetCHA())) / 100; if (buffslot > -1) { buffs[buffslot].magic_rune = spells[spell_id].max_value[i]; } @@ -1424,6 +1436,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove case SE_MitigateSpellDamage: { + effect_value = (effect_value * (100 + 2 * caster->GetCHA())) / 100; if (buffslot > -1) { buffs[buffslot].magic_rune = spells[spell_id].max_value[i]; } @@ -1432,6 +1445,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove case SE_MitigateDotDamage: { + effect_value = (effect_value * (100 + 2 * caster->GetCHA())) / 100; if (buffslot > -1) { buffs[buffslot].dot_rune = spells[spell_id].max_value[i]; } @@ -3116,6 +3130,8 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove } case SE_HealOverTime: { + effect_value = (effect_value * (100 + 2 * caster->GetWIS())) / 100; + effect_value = effect_value * partial / 100; //This is here so buffs with hit counters tic down on initial cast. caster->GetActSpellHealing(spell_id, effect_value, nullptr, false); break; @@ -3955,6 +3971,12 @@ void Mob::DoBuffTic(const Buffs_Struct &buff, int slot, Mob *caster) AddToHateList(caster, -effect_value); } } + if (caster) + { + + effect_value = (effect_value * (100 + 2 * caster->GetINT())) / 100; + effect_value = static_cast(effect_value * ResistSpell(spells[buff.spellid].resist_type, buff.spellid, caster, 0, 0, 0, 0, true) / 100); + } effect_value = caster->GetActDoTDamage(buff.spellid, effect_value, this); diff --git a/zone/spells.cpp b/zone/spells.cpp index bca4caefa..322c6be7d 100644 --- a/zone/spells.cpp +++ b/zone/spells.cpp @@ -3000,6 +3000,15 @@ int Mob::CalcBuffDuration(Mob *caster, Mob *target, uint16 spell_id, int32 caste LogSpells("Spell [{}]: Casting level [{}], formula [{}], base_duration [{}]: result [{}]", spell_id, castlevel, formula, duration, res); + + if (res < 10) + { + res += GetCHA()/20; + } + else + { + res *= 1.0f + GetCHA() / 100.0f; + } return res; } @@ -4495,7 +4504,7 @@ bool Mob::SpellOnTarget( } if (spell_effectiveness < 100) { - if (spell_effectiveness == 0 || !IsPartialResistableSpell(spell_id)) { + if (spell_effectiveness == 0 || (!IsPartialResistableSpell(spell_id) && !IsDamageOverTimeSpell(spell_id) && !IsPureNukeSpell(spell_id))) { LogSpells("Spell [{}] was completely resisted by [{}]", spell_id, spelltar->GetName()); if (spells[spell_id].resist_type == RESIST_PHYSICAL){ @@ -5319,6 +5328,7 @@ float Mob::ResistSpell(uint8 resist_type, uint16 spell_id, Mob *caster, bool use return(0); } + //Get resist modifier and adjust it based on focus 2 resist about eq to 1% resist chance int resist_modifier = 0; if (use_resist_override) { @@ -5561,6 +5571,22 @@ float Mob::ResistSpell(uint8 resist_type, uint16 spell_id, Mob *caster, bool use //Finally our roll int roll = zone->random.Int(0, RuleB(Spells, EnableResistSoftCap) ? RuleI(Spells, SpellResistSoftCap) : 200); + + if(IsDamageOverTimeSpell(spell_id) || IsPureNukeSpell(spell_id)) + { + int cha = caster->GetCHA(); + resist_chance -= cha / 2; + if(resist_chance < 1) + resist_chance = 1; + + int defensive_roll = zone->random.Int(0, resist_chance); + if (roll > defensive_roll) + return 100; + if (defensive_roll > 10* roll) + return 10; + return 100 * roll / defensive_roll; + } + if(roll > resist_chance) { return 100; } else { diff --git a/zone/zoning.cpp b/zone/zoning.cpp index e8a4b5461..0a50789ab 100644 --- a/zone/zoning.cpp +++ b/zone/zoning.cpp @@ -1173,7 +1173,7 @@ void Client::GoToBind(uint8 bind_number) { } void Client::GoToDeath() { - MovePC(m_pp.binds[0].zone_id, m_pp.binds[0].instance_id, 0.0f, 0.0f, 0.0f, 0.0f, 1, ZoneToBindPoint); + MovePC(729, 0, -35.0f, 175.0f, 10.0f, 0.0f, 1, ZoneToBindPoint); } void Client::ClearZoneFlag(uint32 zone_id) From 9231109618b80b6ebb167c8f1da67031fdb15a59 Mon Sep 17 00:00:00 2001 From: carolus21rex <85852042+carolus21rex@users.noreply.github.com> Date: Thu, 15 May 2025 19:52:36 -0400 Subject: [PATCH 2/4] Fix double hit bug. Added endurance shield. --- zone/attack.cpp | 118 +++++++++++++++++++++++++---------------------- zone/bonuses.cpp | 1 + zone/common.h | 1 + 3 files changed, 65 insertions(+), 55 deletions(-) diff --git a/zone/attack.cpp b/zone/attack.cpp index a811153ca..cd91e1846 100644 --- a/zone/attack.cpp +++ b/zone/attack.cpp @@ -3862,8 +3862,12 @@ int64 Mob::ReduceAllDamage(int64 damage) } if (spellbonuses.EnduranceAbsorbPercentDamage[SBIndex::ENDURANCE_ABSORD_MITIGIATION]) { - int64 damage_reduced = damage * spellbonuses.EnduranceAbsorbPercentDamage[SBIndex::ENDURANCE_ABSORD_MITIGIATION] / 10000; //If hit for 1000, at 10% then lower damage by 100; - int32 endurance_drain = damage_reduced * spellbonuses.EnduranceAbsorbPercentDamage[SBIndex::ENDURANCE_ABSORD_DRAIN_PER_HP] / 10000; //Reduce endurance by 0.05% per HP loss + int64 end_absorb = spellbonuses.EnduranceAbsorbPercentDamage[SBIndex::ENDURANCE_ABSORD_MITIGIATION] + itembonuses.EnduranceAbsorbPercentDamage[SBIndex::ENDURANCE_ABSORD_MITIGIATION] + aabonuses.EnduranceAbsorbPercentDamage[SBIndex::ENDURANCE_ABSORD_MITIGIATION]; + int64 end_absorb_cap = spellbonuses.EnduranceAbsorbPercentCap + itembonuses.EnduranceAbsorbPercentCap + aabonuses.EnduranceAbsorbPercentDamage; + int64 damage_reduced = damage * end_absorb / 100; //If hit for 1000, at 10% then lower damage by 100; + if (damage_reduced > end_absorb_cap) + damage_reduced = end_absorb_cap; + int32 endurance_drain = damage_reduced * spellbonuses.EnduranceAbsorbPercentDamage[SBIndex::ENDURANCE_ABSORD_DRAIN_PER_HP] / 100; //Reduce endurance by 0.05% per HP loss if (endurance_drain < 1) endurance_drain = 1; @@ -3871,6 +3875,7 @@ int64 Mob::ReduceAllDamage(int64 damage) damage -= damage_reduced; CastToClient()->SetEndurance(CastToClient()->GetEndurance() - endurance_drain); TryTriggerOnCastRequirement(); + Message(263, "Your endurance shield has absorbed %d damage.", damage_reduced); } } @@ -4299,6 +4304,62 @@ void Mob::CommonDamage(Mob* attacker, int64 &damage, const uint16 spell_id, cons damage = 0; } + //send damage packet before updating hp to prevent double damage bugs and make the game feel more responsive. + if (!iBuffTic) { //buff ticks do not send damage, instead they just call SendHPUpdate(), which is done above + static EQApplicationPacket p(OP_Damage, sizeof(CombatDamage_Struct)); + auto a = (CombatDamage_Struct *) p.pBuffer; + a->target = GetID(); + + if (!attacker) { + a->source = 0; + } else if (attacker->IsClient() && attacker->CastToClient()->GMHideMe()) { + a->source = 0; + } else { + a->source = attacker->GetID(); + } + + a->type = (EQ::ValueWithin(skill_used, EQ::skills::Skill1HBlunt, EQ::skills::Skill2HPiercing)) ? + SkillDamageTypes[skill_used] : SkillDamageTypes[EQ::skills::SkillHandtoHand]; // was 0x1c + a->damage = damage; + a->spellid = spell_id; + + if (special == eSpecialAttacks::AERampage) { + a->special = 1; + } else if (special == eSpecialAttacks::Rampage) { + a->special = 2; + } else { + a->special = 0; + } + + a->hit_heading = attacker ? attacker->GetHeading() : 0.0f; + if (RuleB(Combat, MeleePush) && damage > 0 && !IsRooted() && + (IsClient() || zone->random.Roll(RuleI(Combat, MeleePushChance)))) { + a->force = EQ::skills::GetSkillMeleePushForce(skill_used); + + if (RuleR(Combat, MeleePushForceClientPercent) && IsClient()) { + a->force += a->force * RuleR(Combat, MeleePushForceClientPercent); + } + + if (RuleR(Combat, MeleePushForcePetPercent) && IsPet()) { + a->force += a->force * RuleR(Combat, MeleePushForcePetPercent); + } + + if (IsNPC()) { + if (!RuleB(Combat, NPCtoNPCPush) && attacker && attacker->IsNPC()) { + a->force = 0.0f; // 2013 change that disabled NPC vs NPC push + } else { + a->force *= 0.10f; // force against NPCs is divided by 10 I guess? ex bash is 0.3, parsed 0.03 against an NPC + } + + if (ForcedMovement == 0 && a->force != 0.0f && position_update_melee_push_timer.Check()) { + m_Delta.x += a->force * g_Math.FastSin(a->hit_heading); + m_Delta.y += a->force * g_Math.FastCos(a->hit_heading); + ForcedMovement = 3; + } + } + } + } + SetHP(int64(GetHP() - damage)); if (HasDied()) { @@ -4533,60 +4594,7 @@ void Mob::CommonDamage(Mob* attacker, int64 &damage, const uint16 spell_id, cons } } //end `if damage was done` - //send damage packet... - if (!iBuffTic) { //buff ticks do not send damage, instead they just call SendHPUpdate(), which is done above - static EQApplicationPacket p(OP_Damage, sizeof(CombatDamage_Struct)); - auto a = (CombatDamage_Struct *) p.pBuffer; - a->target = GetID(); - if (!attacker) { - a->source = 0; - } else if (attacker->IsClient() && attacker->CastToClient()->GMHideMe()) { - a->source = 0; - } else { - a->source = attacker->GetID(); - } - - a->type = (EQ::ValueWithin(skill_used, EQ::skills::Skill1HBlunt, EQ::skills::Skill2HPiercing)) ? - SkillDamageTypes[skill_used] : SkillDamageTypes[EQ::skills::SkillHandtoHand]; // was 0x1c - a->damage = damage; - a->spellid = spell_id; - - if (special == eSpecialAttacks::AERampage) { - a->special = 1; - } else if (special == eSpecialAttacks::Rampage) { - a->special = 2; - } else { - a->special = 0; - } - - a->hit_heading = attacker ? attacker->GetHeading() : 0.0f; - if (RuleB(Combat, MeleePush) && damage > 0 && !IsRooted() && - (IsClient() || zone->random.Roll(RuleI(Combat, MeleePushChance)))) { - a->force = EQ::skills::GetSkillMeleePushForce(skill_used); - - if (RuleR(Combat, MeleePushForceClientPercent) && IsClient()) { - a->force += a->force * RuleR(Combat, MeleePushForceClientPercent); - } - - if (RuleR(Combat, MeleePushForcePetPercent) && IsPet()) { - a->force += a->force * RuleR(Combat, MeleePushForcePetPercent); - } - - if (IsNPC()) { - if (!RuleB(Combat, NPCtoNPCPush) && attacker && attacker->IsNPC()) { - a->force = 0.0f; // 2013 change that disabled NPC vs NPC push - } else { - a->force *= 0.10f; // force against NPCs is divided by 10 I guess? ex bash is 0.3, parsed 0.03 against an NPC - } - - if (ForcedMovement == 0 && a->force != 0.0f && position_update_melee_push_timer.Check()) { - m_Delta.x += a->force * g_Math.FastSin(a->hit_heading); - m_Delta.y += a->force * g_Math.FastCos(a->hit_heading); - ForcedMovement = 3; - } - } - } //Note: if players can become pets, they will not receive damage messages of their own //this was done to simplify the code here (since we can only effectively skip one mob on queue) diff --git a/zone/bonuses.cpp b/zone/bonuses.cpp index 33417d688..5ccfe6b43 100644 --- a/zone/bonuses.cpp +++ b/zone/bonuses.cpp @@ -3117,6 +3117,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne new_bonus->EnduranceAbsorbPercentDamage[SBIndex::ENDURANCE_ABSORD_MITIGIATION] = effect_value; new_bonus->EnduranceAbsorbPercentDamage[SBIndex::ENDURANCE_ABSORD_DRAIN_PER_HP] = limit_value; } + new_bonus->EnduranceAbsorbPercent += max_value; break; } diff --git a/zone/common.h b/zone/common.h index af6113a85..b548ba639 100644 --- a/zone/common.h +++ b/zone/common.h @@ -493,6 +493,7 @@ struct StatBonuses { uint32 ManaAbsorbPercentDamage; // 0 = Mitigation value uint32 ManaAbsorbPercentDamageCap; // 0 = Mitigation value int32 EnduranceAbsorbPercentDamage[2]; // 0 = Mitigation value 1 = Percent Endurance drain per HP lost + uint32 EnduranceAbsorbPercentCap; int32 ShieldBlock; // Chance to Shield Block int32 BlockBehind; // Chance to Block Behind (with our without shield) bool CriticalRegenDecay; // increase critical regen chance, decays based on spell level cast From 621e70273fe15b6010fe2b436d21232923af2f2c Mon Sep 17 00:00:00 2001 From: carolus21rex <85852042+carolus21rex@users.noreply.github.com> Date: Thu, 15 May 2025 19:52:48 -0400 Subject: [PATCH 3/4] Fix double hit bug. Added endurance shield. pt 2 --- zone/attack.cpp | 56 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/zone/attack.cpp b/zone/attack.cpp index cd91e1846..efb90cd95 100644 --- a/zone/attack.cpp +++ b/zone/attack.cpp @@ -4593,6 +4593,62 @@ void Mob::CommonDamage(Mob* attacker, int64 &damage, const uint16 spell_id, cons } } } //end `if damage was done` + else { + if (!iBuffTic) { //buff ticks do not send damage, instead they just call SendHPUpdate(), which is done above + static EQApplicationPacket p(OP_Damage, sizeof(CombatDamage_Struct)); + auto a = (CombatDamage_Struct *) p.pBuffer; + a->target = GetID(); + + if (!attacker) { + a->source = 0; + } else if (attacker->IsClient() && attacker->CastToClient()->GMHideMe()) { + a->source = 0; + } else { + a->source = attacker->GetID(); + } + + a->type = (EQ::ValueWithin(skill_used, EQ::skills::Skill1HBlunt, EQ::skills::Skill2HPiercing)) ? + SkillDamageTypes[skill_used] : SkillDamageTypes[EQ::skills::SkillHandtoHand]; // was 0x1c + a->damage = damage; + a->spellid = spell_id; + + if (special == eSpecialAttacks::AERampage) { + a->special = 1; + } else if (special == eSpecialAttacks::Rampage) { + a->special = 2; + } else { + a->special = 0; + } + + a->hit_heading = attacker ? attacker->GetHeading() : 0.0f; + if (RuleB(Combat, MeleePush) && damage > 0 && !IsRooted() && + (IsClient() || zone->random.Roll(RuleI(Combat, MeleePushChance)))) { + a->force = EQ::skills::GetSkillMeleePushForce(skill_used); + + if (RuleR(Combat, MeleePushForceClientPercent) && IsClient()) { + a->force += a->force * RuleR(Combat, MeleePushForceClientPercent); + } + + if (RuleR(Combat, MeleePushForcePetPercent) && IsPet()) { + a->force += a->force * RuleR(Combat, MeleePushForcePetPercent); + } + + if (IsNPC()) { + if (!RuleB(Combat, NPCtoNPCPush) && attacker && attacker->IsNPC()) { + a->force = 0.0f; // 2013 change that disabled NPC vs NPC push + } else { + a->force *= 0.10f; // force against NPCs is divided by 10 I guess? ex bash is 0.3, parsed 0.03 against an NPC + } + + if (ForcedMovement == 0 && a->force != 0.0f && position_update_melee_push_timer.Check()) { + m_Delta.x += a->force * g_Math.FastSin(a->hit_heading); + m_Delta.y += a->force * g_Math.FastCos(a->hit_heading); + ForcedMovement = 3; + } + } + } + } + } From 307dedd49aac820ee352c8e8990ccd24d6b7b857 Mon Sep 17 00:00:00 2001 From: carolus21rex <85852042+carolus21rex@users.noreply.github.com> Date: Fri, 16 May 2025 02:07:25 +0000 Subject: [PATCH 4/4] antibugs --- zone/attack.cpp | 151 +++++++++++++++-------------------------------- zone/bonuses.cpp | 2 +- zone/common.h | 2 +- 3 files changed, 49 insertions(+), 106 deletions(-) diff --git a/zone/attack.cpp b/zone/attack.cpp index efb90cd95..2bf150d9c 100644 --- a/zone/attack.cpp +++ b/zone/attack.cpp @@ -3863,7 +3863,7 @@ int64 Mob::ReduceAllDamage(int64 damage) if (spellbonuses.EnduranceAbsorbPercentDamage[SBIndex::ENDURANCE_ABSORD_MITIGIATION]) { int64 end_absorb = spellbonuses.EnduranceAbsorbPercentDamage[SBIndex::ENDURANCE_ABSORD_MITIGIATION] + itembonuses.EnduranceAbsorbPercentDamage[SBIndex::ENDURANCE_ABSORD_MITIGIATION] + aabonuses.EnduranceAbsorbPercentDamage[SBIndex::ENDURANCE_ABSORD_MITIGIATION]; - int64 end_absorb_cap = spellbonuses.EnduranceAbsorbPercentCap + itembonuses.EnduranceAbsorbPercentCap + aabonuses.EnduranceAbsorbPercentDamage; + int32 end_absorb_cap = spellbonuses.EnduranceAbsorbPercentCap + itembonuses.EnduranceAbsorbPercentCap + aabonuses.EnduranceAbsorbPercentCap; int64 damage_reduced = damage * end_absorb / 100; //If hit for 1000, at 10% then lower damage by 100; if (damage_reduced > end_absorb_cap) damage_reduced = end_absorb_cap; @@ -4304,61 +4304,6 @@ void Mob::CommonDamage(Mob* attacker, int64 &damage, const uint16 spell_id, cons damage = 0; } - //send damage packet before updating hp to prevent double damage bugs and make the game feel more responsive. - if (!iBuffTic) { //buff ticks do not send damage, instead they just call SendHPUpdate(), which is done above - static EQApplicationPacket p(OP_Damage, sizeof(CombatDamage_Struct)); - auto a = (CombatDamage_Struct *) p.pBuffer; - a->target = GetID(); - - if (!attacker) { - a->source = 0; - } else if (attacker->IsClient() && attacker->CastToClient()->GMHideMe()) { - a->source = 0; - } else { - a->source = attacker->GetID(); - } - - a->type = (EQ::ValueWithin(skill_used, EQ::skills::Skill1HBlunt, EQ::skills::Skill2HPiercing)) ? - SkillDamageTypes[skill_used] : SkillDamageTypes[EQ::skills::SkillHandtoHand]; // was 0x1c - a->damage = damage; - a->spellid = spell_id; - - if (special == eSpecialAttacks::AERampage) { - a->special = 1; - } else if (special == eSpecialAttacks::Rampage) { - a->special = 2; - } else { - a->special = 0; - } - - a->hit_heading = attacker ? attacker->GetHeading() : 0.0f; - if (RuleB(Combat, MeleePush) && damage > 0 && !IsRooted() && - (IsClient() || zone->random.Roll(RuleI(Combat, MeleePushChance)))) { - a->force = EQ::skills::GetSkillMeleePushForce(skill_used); - - if (RuleR(Combat, MeleePushForceClientPercent) && IsClient()) { - a->force += a->force * RuleR(Combat, MeleePushForceClientPercent); - } - - if (RuleR(Combat, MeleePushForcePetPercent) && IsPet()) { - a->force += a->force * RuleR(Combat, MeleePushForcePetPercent); - } - - if (IsNPC()) { - if (!RuleB(Combat, NPCtoNPCPush) && attacker && attacker->IsNPC()) { - a->force = 0.0f; // 2013 change that disabled NPC vs NPC push - } else { - a->force *= 0.10f; // force against NPCs is divided by 10 I guess? ex bash is 0.3, parsed 0.03 against an NPC - } - - if (ForcedMovement == 0 && a->force != 0.0f && position_update_melee_push_timer.Check()) { - m_Delta.x += a->force * g_Math.FastSin(a->hit_heading); - m_Delta.y += a->force * g_Math.FastCos(a->hit_heading); - ForcedMovement = 3; - } - } - } - } SetHP(int64(GetHP() - damage)); @@ -4593,64 +4538,61 @@ void Mob::CommonDamage(Mob* attacker, int64 &damage, const uint16 spell_id, cons } } } //end `if damage was done` - else { - if (!iBuffTic) { //buff ticks do not send damage, instead they just call SendHPUpdate(), which is done above - static EQApplicationPacket p(OP_Damage, sizeof(CombatDamage_Struct)); - auto a = (CombatDamage_Struct *) p.pBuffer; - a->target = GetID(); - if (!attacker) { - a->source = 0; - } else if (attacker->IsClient() && attacker->CastToClient()->GMHideMe()) { - a->source = 0; - } else { - a->source = attacker->GetID(); + + //send damage packet before updating hp to prevent double damage bugs and make the game feel more responsive. + if (!iBuffTic) { //buff ticks do not send damage, instead they just call SendHPUpdate(), which is done above + static EQApplicationPacket p(OP_Damage, sizeof(CombatDamage_Struct)); + auto a = (CombatDamage_Struct *) p.pBuffer; + a->target = GetID(); + if (!attacker) { + a->source = 0; + } else if (attacker->IsClient() && attacker->CastToClient()->GMHideMe()) { + a->source = 0; + } else { + a->source = attacker->GetID(); + } + + a->type = (EQ::ValueWithin(skill_used, EQ::skills::Skill1HBlunt, EQ::skills::Skill2HPiercing)) ? + SkillDamageTypes[skill_used] : SkillDamageTypes[EQ::skills::SkillHandtoHand]; // was 0x1c + a->damage = damage; + a->spellid = spell_id; + + if (special == eSpecialAttacks::AERampage) { + a->special = 1; + } else if (special == eSpecialAttacks::Rampage) { + a->special = 2; + } else { + a->special = 0; + } + + a->hit_heading = attacker ? attacker->GetHeading() : 0.0f; + if (RuleB(Combat, MeleePush) && damage > 0 && !IsRooted() && + (IsClient() || zone->random.Roll(RuleI(Combat, MeleePushChance)))) { + a->force = EQ::skills::GetSkillMeleePushForce(skill_used); + + if (RuleR(Combat, MeleePushForceClientPercent) && IsClient()) { + a->force += a->force * RuleR(Combat, MeleePushForceClientPercent); } - a->type = (EQ::ValueWithin(skill_used, EQ::skills::Skill1HBlunt, EQ::skills::Skill2HPiercing)) ? - SkillDamageTypes[skill_used] : SkillDamageTypes[EQ::skills::SkillHandtoHand]; // was 0x1c - a->damage = damage; - a->spellid = spell_id; - - if (special == eSpecialAttacks::AERampage) { - a->special = 1; - } else if (special == eSpecialAttacks::Rampage) { - a->special = 2; - } else { - a->special = 0; + if (RuleR(Combat, MeleePushForcePetPercent) && IsPet()) { + a->force += a->force * RuleR(Combat, MeleePushForcePetPercent); } - a->hit_heading = attacker ? attacker->GetHeading() : 0.0f; - if (RuleB(Combat, MeleePush) && damage > 0 && !IsRooted() && - (IsClient() || zone->random.Roll(RuleI(Combat, MeleePushChance)))) { - a->force = EQ::skills::GetSkillMeleePushForce(skill_used); - - if (RuleR(Combat, MeleePushForceClientPercent) && IsClient()) { - a->force += a->force * RuleR(Combat, MeleePushForceClientPercent); + if (IsNPC()) { + if (!RuleB(Combat, NPCtoNPCPush) && attacker && attacker->IsNPC()) { + a->force = 0.0f; // 2013 change that disabled NPC vs NPC push + } else { + a->force *= 0.10f; // force against NPCs is divided by 10 I guess? ex bash is 0.3, parsed 0.03 against an NPC } - if (RuleR(Combat, MeleePushForcePetPercent) && IsPet()) { - a->force += a->force * RuleR(Combat, MeleePushForcePetPercent); - } - - if (IsNPC()) { - if (!RuleB(Combat, NPCtoNPCPush) && attacker && attacker->IsNPC()) { - a->force = 0.0f; // 2013 change that disabled NPC vs NPC push - } else { - a->force *= 0.10f; // force against NPCs is divided by 10 I guess? ex bash is 0.3, parsed 0.03 against an NPC - } - - if (ForcedMovement == 0 && a->force != 0.0f && position_update_melee_push_timer.Check()) { - m_Delta.x += a->force * g_Math.FastSin(a->hit_heading); - m_Delta.y += a->force * g_Math.FastCos(a->hit_heading); - ForcedMovement = 3; - } + if (ForcedMovement == 0 && a->force != 0.0f && position_update_melee_push_timer.Check()) { + m_Delta.x += a->force * g_Math.FastSin(a->hit_heading); + m_Delta.y += a->force * g_Math.FastCos(a->hit_heading); + ForcedMovement = 3; } } } - } - - //Note: if players can become pets, they will not receive damage messages of their own //this was done to simplify the code here (since we can only effectively skip one mob on queue) @@ -4879,6 +4821,7 @@ void Mob::CommonDamage(Mob* attacker, int64 &damage, const uint16 spell_id, cons ); } } + SendHPUpdate(true); } void Mob::HealDamage(uint64 amount, Mob* caster, uint16 spell_id) diff --git a/zone/bonuses.cpp b/zone/bonuses.cpp index 5ccfe6b43..24b004cbf 100644 --- a/zone/bonuses.cpp +++ b/zone/bonuses.cpp @@ -3117,7 +3117,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne new_bonus->EnduranceAbsorbPercentDamage[SBIndex::ENDURANCE_ABSORD_MITIGIATION] = effect_value; new_bonus->EnduranceAbsorbPercentDamage[SBIndex::ENDURANCE_ABSORD_DRAIN_PER_HP] = limit_value; } - new_bonus->EnduranceAbsorbPercent += max_value; + new_bonus->EnduranceAbsorbPercentCap += max_value; break; } diff --git a/zone/common.h b/zone/common.h index b548ba639..c9f30ec51 100644 --- a/zone/common.h +++ b/zone/common.h @@ -493,7 +493,7 @@ struct StatBonuses { uint32 ManaAbsorbPercentDamage; // 0 = Mitigation value uint32 ManaAbsorbPercentDamageCap; // 0 = Mitigation value int32 EnduranceAbsorbPercentDamage[2]; // 0 = Mitigation value 1 = Percent Endurance drain per HP lost - uint32 EnduranceAbsorbPercentCap; + int32 EnduranceAbsorbPercentCap; int32 ShieldBlock; // Chance to Shield Block int32 BlockBehind; // Chance to Block Behind (with our without shield) bool CriticalRegenDecay; // increase critical regen chance, decays based on spell level cast