Dr Chaos has entered the building

This commit is contained in:
carolus21rex 2025-05-10 16:30:14 +00:00
parent a2b2a6a5cf
commit cde60926f1
19 changed files with 437 additions and 184 deletions

View File

@ -7,6 +7,7 @@
}, },
"appPort": [ "appPort": [
"5998:5998/udp", "5998:5998/udp",
"5999:5999/udp",
"7000:7000/udp", "7000:7000/udp",
"7001:7001/udp", "7001:7001/udp",
"7002:7002/udp", "7002:7002/udp",
@ -46,4 +47,5 @@
}, },
"workspaceFolder": "/src", "workspaceFolder": "/src",
"workspaceMount": "source=${localWorkspaceFolder},target=/src,type=bind,consistency=cached" "workspaceMount": "source=${localWorkspaceFolder},target=/src,type=bind,consistency=cached"
//"postAttachCommand": "ln -nsf .devcontainer/Makefile /src/Makefile"
} }

1
.gitignore vendored
View File

@ -30,6 +30,7 @@ log/
logs/ logs/
vcpkg/ vcpkg/
perl/ perl/
base/
.idea/* .idea/*
*cbp *cbp

View File

@ -118,8 +118,8 @@ bool IsLifetapSpell(uint16 spell_id)
if ( if (
spell.target_type == ST_Tap || spell.target_type == ST_Tap ||
spell.target_type == ST_TargetAETap || spell.target_type == ST_TargetAETap //||
spell_id == SPELL_ANCIENT_LIFEBANE //spell_id == SPELL_ANCIENT_LIFEBANE
) { ) {
return true; return true;
} }

View File

@ -169,7 +169,7 @@ int Mob::compute_tohit(EQ::skills::SkillType skillinuse)
reduction = std::min((110 - reduction) / 100.0, 1.0); reduction = std::min((110 - reduction) / 100.0, 1.0);
tohit = reduction * static_cast<double>(tohit); tohit = reduction * static_cast<double>(tohit);
} else if (IsBerserk()) { } else if (IsBerserk()) {
tohit += (GetLevel() * 2) / 5; tohit += (GetLevel() * 2);
} }
} }
return std::max(tohit, 1); return std::max(tohit, 1);
@ -184,6 +184,16 @@ int Mob::GetTotalToHit(EQ::skills::SkillType skill, int chance_mod)
// calculate attacker's accuracy // calculate attacker's accuracy
auto accuracy = compute_tohit(skill) + 10; // add 10 in case the NPC's stats are fucked 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 if (chance_mod > 0) // multiplier
accuracy *= chance_mod; 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 // 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 // item mod2 accuracy isn't applied to range? Theory crafting and parses back it up I guess
// mod2 accuracy -- flat bonus // mod2 accuracy -- flat bonus
accuracy += itembonuses.HitChance;
/*
if (skill != EQ::skills::SkillArchery && skill != EQ::skills::SkillThrowing) { if (skill != EQ::skills::SkillArchery && skill != EQ::skills::SkillThrowing) {
accuracy += itembonuses.HitChance;
} else { } 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. // 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%) // 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. // Using same scale factor for Avoidance and Accuracy since they impact the formula about the same.
accuracy += itembonuses.HitChance * RuleI(Combat, PCAccuracyAvoidanceMod2Scale) / 100; accuracy += itembonuses.HitChance * RuleI(Combat, PCAccuracyAvoidanceMod2Scale) / 100;
} }
*/
//518 Increase ATK accuracy by percentage, stackable //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; auto atkhit_bonus = itembonuses.Attack_Accuracy_Max_Percent + aabonuses.Attack_Accuracy_Max_Percent + spellbonuses.Attack_Accuracy_Max_Percent;
if (atkhit_bonus) if (atkhit_bonus)
@ -275,7 +287,7 @@ int Mob::compute_defense()
defense += itembonuses.AvoidMeleeChance * RuleI(Combat, PCAccuracyAvoidanceMod2Scale) / 100; // item mod2 defense += itembonuses.AvoidMeleeChance * RuleI(Combat, PCAccuracyAvoidanceMod2Scale) / 100; // item mod2
} else { } else {
defense += (8000 * (GetAGI() - 40)) / 36000; defense += ((10 + static_cast<int>(GetLevel())) * GetAGI()) / 10;
if (IsOfClientBot()) { if (IsOfClientBot()) {
defense += itembonuses.heroic_agi_avoidance; defense += itembonuses.heroic_agi_avoidance;
@ -713,6 +725,8 @@ int Mob::GetACSoftcap()
int level = std::min(105, static_cast<int>(GetLevel())) - 1; int level = std::min(105, static_cast<int>(GetLevel())) - 1;
return 200 + level * 5;
switch (GetClass()) { switch (GetClass()) {
case Class::Warrior: case Class::Warrior:
return war_softcaps[level]; 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 // These are based on the dev post, they seem to be correct for every level
// AKA no more hard caps // AKA no more hard caps
return 0.4;
switch (GetClass()) { switch (GetClass()) {
case Class::Warrior: case Class::Warrior:
return 0.35; return 0.35;
@ -779,74 +796,74 @@ int Mob::GetClassRaceACBonus()
{ {
int ac_bonus = 0; int ac_bonus = 0;
auto level = GetLevel(); auto level = GetLevel();
if (GetClass() == Class::Monk) { //if (GetClass() == Class::Monk) {
int hardcap = 30; int hardcap = 30;
int softcap = 14; int softcap = 14;
if (level > 99) { if (level >= 40) {
hardcap = 58; hardcap = 58;
softcap = 35; softcap = 35;
} }
else if (level > 94) { else if (level > 39) {
hardcap = 57; hardcap = 57;
softcap = 34; softcap = 34;
} }
else if (level > 89) { else if (level > 38) {
hardcap = 56; hardcap = 56;
softcap = 33; softcap = 33;
} }
else if (level > 84) { else if (level > 37) {
hardcap = 55; hardcap = 55;
softcap = 32; softcap = 32;
} }
else if (level > 79) { else if (level > 36) {
hardcap = 54; hardcap = 54;
softcap = 31; softcap = 31;
} }
else if (level > 74) { else if (level > 34) {
hardcap = 53; hardcap = 53;
softcap = 30; softcap = 30;
} }
else if (level > 69) { else if (level > 32) {
hardcap = 53; hardcap = 53;
softcap = 28; softcap = 28;
} }
else if (level > 64) { else if (level > 30) {
hardcap = 53; hardcap = 53;
softcap = 26; softcap = 26;
} }
else if (level > 63) { else if (level > 27) {
hardcap = 50; hardcap = 50;
softcap = 24; softcap = 24;
} }
else if (level > 61) { else if (level > 23) {
hardcap = 47; hardcap = 47;
softcap = 24; softcap = 24;
} }
else if (level > 59) { else if (level > 19) {
hardcap = 45; hardcap = 45;
softcap = 24; softcap = 24;
} }
else if (level > 54) { else if (level > 15) {
hardcap = 40; hardcap = 40;
softcap = 20; softcap = 20;
} }
else if (level > 50) { else if (level > 12) {
hardcap = 38; hardcap = 38;
softcap = 18; softcap = 18;
} }
else if (level > 44) { else if (level > 9) {
hardcap = 36; hardcap = 36;
softcap = 17; softcap = 17;
} }
else if (level > 29) { else if (level > 6) {
hardcap = 34; hardcap = 34;
softcap = 16; softcap = 16;
} }
else if (level > 14) { else if (level > 3) {
hardcap = 32; hardcap = 32;
softcap = 15; softcap = 15;
} }
int weight = IsClient() ? CastToClient()->CalcCurrentWeight()/10 : 0; int weight = IsClient() ? CastToClient()->CalcCurrentWeight()/10 : 30;
if (weight < hardcap - 1) { if (weight < hardcap - 1) {
double temp = level + 5; double temp = level + 5;
if (weight > softcap) { if (weight > softcap) {
@ -862,7 +879,7 @@ int Mob::GetClassRaceACBonus()
temp = (4.0 * temp) / 3.0; temp = (4.0 * temp) / 3.0;
ac_bonus -= static_cast<int>(temp * multiplier); ac_bonus -= static_cast<int>(temp * multiplier);
} }
} //}
if (GetClass() == Class::Rogue) { if (GetClass() == Class::Rogue) {
int level_scaler = level - 26; int level_scaler = level - 26;
@ -880,24 +897,24 @@ int Mob::GetClassRaceACBonus()
ac_bonus = 12; ac_bonus = 12;
} }
if (GetClass() == Class::Beastlord) { //if (GetClass() == Class::Beastlord) {
int level_scaler = level - 6; int level_scaler = level;
if (GetAGI() < 80) if (GetAGI() < 10)
ac_bonus = level_scaler / 5; ac_bonus += level_scaler / 5;
else if (GetAGI() < 85) else if (GetAGI() < 15)
ac_bonus = (level_scaler * 2) / 5; ac_bonus += (level_scaler * 2) / 5;
else if (GetAGI() < 90) else if (GetAGI() < 20)
ac_bonus = (level_scaler * 3) / 5; ac_bonus += (level_scaler * 3) / 5;
else if (GetAGI() < 100) else if (GetAGI() < 25)
ac_bonus = (level_scaler * 4) / 5; ac_bonus += (level_scaler * 4) / 5;
else if (GetAGI() >= 100) else if (GetAGI() >= 25)
ac_bonus = (level_scaler * 5) / 5; ac_bonus += (level_scaler * 5) / 5;
if (ac_bonus > 16) //if (ac_bonus > 16)
ac_bonus = 16; // ac_bonus += 16;
} //}
if (GetRace() == IKSAR) //if (GetRace() == IKSAR)
ac_bonus += EQ::Clamp(static_cast<int>(level), 10, 35); // ac_bonus += EQ::Clamp(static_cast<int>(level), 10, 35);
return ac_bonus; return ac_bonus;
} }
@ -919,11 +936,11 @@ int Mob::ACSum(bool skip_caps)
shield_ac += itembonuses.heroic_str_shield_ac; shield_ac += itembonuses.heroic_str_shield_ac;
} }
// EQ math // EQ math
ac = (ac * 4) / 3; //ac = (ac * 4) / 3;
// anti-twink // anti-twink
if (!skip_caps && IsOfClientBot() && GetLevel() < RuleI(Combat, LevelToStopACTwinkControl)) { //if (!skip_caps && IsOfClientBot() && GetLevel() < RuleI(Combat, LevelToStopACTwinkControl)) {
ac = std::min(ac, 25 + 6 * GetLevel()); // ac = std::min(ac, 25 + 6 * GetLevel());
} //}
ac = std::max(0, ac + GetClassRaceACBonus()); ac = std::max(0, ac + GetClassRaceACBonus());
if (IsNPC()) { if (IsNPC()) {
// This is the developer tweaked number // 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; ac += GetSkill(EQ::skills::SkillDefense) / 3 + spell_aa_ac / 4;
} }
if (GetAGI() > 70) //if (GetAGI() > 70)
ac += GetAGI() / 20; ac += GetAGI() / 4;
if (ac < 0) if (ac < 0)
ac = 0; ac = 0;
@ -1023,11 +1040,12 @@ int Mob::offense(EQ::skills::SkillType skill)
offense = GetBestMeleeSkill(); offense = GetBestMeleeSkill();
break; break;
} }
offense += stat_bonus * 2;
/*
if (stat_bonus >= 75) { if (stat_bonus >= 75) {
offense += (2 * stat_bonus - 150) / 3; 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 // 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, // 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. // 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 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) double Mob::RollD20(int offense, int mitigation)
{ {
/*
static double mods[] = { static double mods[] = {
0.1, 0.2, 0.3, 0.4, 0.5, 0.1, 0.2, 0.3, 0.4, 0.5,
0.6, 0.7, 0.8, 0.9, 1.0, 0.6, 0.7, 0.8, 0.9, 1.0,
1.1, 1.2, 1.3, 1.4, 1.5, 1.1, 1.2, 1.3, 1.4, 1.5,
1.6, 1.7, 1.8, 1.9, 2.0 1.6, 1.7, 1.8, 1.9, 2.0
}; };
*/
if (IsOfClientBotMerc() && IsSitting()) { 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 atk_roll = zone->random.Roll0(offense);
auto def_roll = zone->random.Roll0(mitigation + 5); auto def_roll = zone->random.Roll0(mitigation);
double mod = 1.0; // starts at full damage
int avg = std::max(1, (offense + mitigation + 10) / 2); double half_attack = atk_roll / 2.0;
int index = std::max(0, (atk_roll - def_roll) + (avg / 2));
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<double>(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 //SYNC WITH: tune.cpp, mob.h TuneMeleeMitigation
void Mob::MeleeMitigation(Mob *attacker, DamageHitInfo &hit, ExtraAttackOptions *opts) 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; 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 // Add bonus to roll if level difference is sufficient
const int level_diff = attacker->GetLevel() - GetLevel(); const double level_diff = (attacker->GetLevel() - GetLevel()) * 0.02f;
const int level_diff_roll_check = RuleI(Combat, LevelDifferenceRollCheck); //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_roll_check >= 0) {
if (level_diff > level_diff_roll_check) { if (level_diff > level_diff_roll_check) {
roll += RuleR(Combat, LevelDifferenceRollBonus); roll += RuleR(Combat, LevelDifferenceRollBonus);
@ -1112,9 +1158,12 @@ void Mob::MeleeMitigation(Mob *attacker, DamageHitInfo &hit, ExtraAttackOptions
} }
} }
} }
*/
// +0.5 for rounding, min to 1 dmg // +0.5 for rounding, min to 1 dmg
hit.damage_done = std::max(static_cast<int>(roll * static_cast<double>(hit.base_damage) + 0.5), 1); double preroll = static_cast<double>(hit.damage_done);
hit.damage_done = std::max(static_cast<int>(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); 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 (hit.damage_done > 0) {
if (IsClient())
{
hit.damage_done = hit.base_damage;
}
else
{
hit.damage_done = CastToNPC()->GetMaxDMG();
}
ApplyDamageTable(hit); ApplyDamageTable(hit);
other->MeleeMitigation(this, hit, opts);
CommonOutgoingHitSuccess(other, hit, opts); CommonOutgoingHitSuccess(other, hit, opts);
} }
LogCombat("Final damage after all reductions: [{}]", hit.damage_done); LogCombat("Final damage after all reductions: [{}]", hit.damage_done);
@ -3789,12 +3846,18 @@ int64 Mob::ReduceAllDamage(int64 damage)
if (damage <= 0) if (damage <= 0)
return damage; return damage;
if (spellbonuses.ManaAbsorbPercentDamage) { int64 mana_absorb = spellbonuses.ManaAbsorbPercentDamage + itembonuses.ManaAbsorbPercentDamage + aabonuses.ManaAbsorbPercentDamage;
int64 mana_reduced = damage * spellbonuses.ManaAbsorbPercentDamage / 100; 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) { if (GetMana() >= mana_reduced) {
damage -= mana_reduced; damage -= mana_reduced;
SetMana(GetMana() - mana_reduced); SetMana(GetMana() - mana_reduced);
TryTriggerOnCastRequirement(); 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)) { if (IsValidSpell(spell_id) && IsLifetapSpell(spell_id)) {
int64 healed = damage; 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()); LogCombat("Applying lifetap heal of [{}] to [{}]", healed, attacker->GetName());
attacker->HealDamage(healed); attacker->HealDamage(healed);
@ -5349,9 +5412,9 @@ void Mob::TryCriticalHit(Mob *defender, DamageHitInfo &hit, ExtraAttackOptions *
return; return;
} }
if (IsNPC() && !RuleB(Combat, NPCCanCrit)) { //if (IsNPC() && !RuleB(Combat, NPCCanCrit)) {
return; // return;
} //}
// 1: Try Slay Undead // 1: Try Slay Undead
if (defender->GetBodyType() == BodyType::Undead || defender->GetBodyType() == BodyType::SummonedUndead || 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 // We either require an innate crit chance or some SPA 169 to crit
bool innate_crit = false; bool innate_crit = false;
int crit_chance = GetCriticalChanceBonus(hit.skill); int crit_chance = GetCriticalChanceBonus(hit.skill);
if ((GetClass() == Class::Warrior || GetClass() == Class::Berserker) && GetLevel() >= 12) { if (GetLevel() >= 4) {
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) {
innate_crit = true; innate_crit = true;
} }
@ -5425,20 +5484,35 @@ void Mob::TryCriticalHit(Mob *defender, DamageHitInfo &hit, ExtraAttackOptions *
} else { } else {
difficulty = RuleI(Combat, MeleeCritDifficulty); difficulty = RuleI(Combat, MeleeCritDifficulty);
} }
int dex_bonus = 50;
int crit_stat_bon = 0;
int roll = zone->random.Int(1, difficulty); 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) { 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 // 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)) { //if (!innate_crit || (GetClass() == Class::Berserker && hit.skill == EQ::skills::SkillThrowing)) {
dex_bonus = dex_bonus * 3 / 5; // dex_bonus = dex_bonus * 3 / 5;
} //}
if (crit_chance) { if (crit_chance) {
dex_bonus += dex_bonus * crit_chance / 100; dex_bonus += dex_bonus * crit_chance / 100;
@ -5454,7 +5528,7 @@ void Mob::TryCriticalHit(Mob *defender, DamageHitInfo &hit, ExtraAttackOptions *
// step 2: calculate damage // step 2: calculate damage
hit.damage_done = std::max(hit.damage_done, hit.base_damage) + 5; hit.damage_done = std::max(hit.damage_done, hit.base_damage) + 5;
int og_damage = hit.damage_done; 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) { if (crit_mod < 100) {
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); 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 // 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())) { if (BehindMob(defender, GetX(), GetY())) {
int chance = GetLevel() * 12; int chance = GetLevel() * 12;
if (zone->random.Int(1, 1000) < chance) { if (zone->random.Int(1, 1000) < chance) {
@ -5827,6 +5901,9 @@ const DamageTable &Mob::GetDamageTable() const
{ 415, 15, 40 }, // 105 { 415, 15, 40 }, // 105
}; };
static DamageTable melee_table = { 1000 + 50 * GetLevel(), 50 - GetLevel(), 0 - GetLevel()};
return melee_table;
bool monk = GetClass() == Class::Monk; bool monk = GetClass() == Class::Monk;
bool melee = IsWarriorClass(); bool melee = IsWarriorClass();
// tables caped at 105 for now -- future proofed for a while at least :P // 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]; return mnk_table[0];
auto &which = monk ? mnk_table : dmg_table; auto &which = monk ? mnk_table : dmg_table;
return which[level - 50]; //return which[level - 50];
} }
int Mob::GetMobFixedOffenseSkill() 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 // 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) // things that come out to 1 dmg seem to skip this (ex non-bash slam classes)
if (hit.damage_done < 2) //if (hit.damage_done < 2)
return; // return;
auto &damage_table = GetDamageTable(); auto &damage_table = GetDamageTable();
@ -5933,11 +6008,11 @@ void Mob::ApplyDamageTable(DamageHitInfo &hit)
int basebonus = hit.offense - damage_table.minusfactor; int basebonus = hit.offense - damage_table.minusfactor;
basebonus = std::max(10, basebonus / 2); basebonus = std::max(10, basebonus / 2);
int extrapercent = zone->random.Roll0(basebonus); int extrapercent = zone->random.Roll0(basebonus);
int percent = std::min(100 + extrapercent, damage_table.max_extra); int percent = std::min(128 + extrapercent, damage_table.max_extra);
hit.damage_done = (hit.damage_done * percent) / 100; hit.damage_done = (hit.damage_done * percent) / 128;
if (IsWarriorClass() && GetLevel() > 54) //if (IsWarriorClass() && GetLevel() > 54)
hit.damage_done++; // hit.damage_done++;
Log(Logs::Detail, Logs::Attack, "Damage table applied %d (max %d)", percent, damage_table.max_extra); 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 #endif
// BER weren't parsing the halving // BER weren't parsing the halving
/*
if (hit.skill == EQ::skills::SkillArchery || if (hit.skill == EQ::skills::SkillArchery ||
(hit.skill == EQ::skills::SkillThrowing && GetClass() != Class::Berserker)) (hit.skill == EQ::skills::SkillThrowing && GetClass() != Class::Berserker))
hit.damage_done /= 2; hit.damage_done /= 2;
*/
if (hit.damage_done < 1) if (hit.damage_done < 1)
hit.damage_done = 1; hit.damage_done = 1;
@ -6347,7 +6423,7 @@ void Mob::CommonOutgoingHitSuccess(Mob* defender, DamageHitInfo &hit, ExtraAttac
if (headshot > 0) { if (headshot > 0) {
hit.damage_done = headshot; 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)) { if ((defender->IsNPC() && !defender->IsMoving() && !defender->IsRooted()) || !RuleB(Combat, ArcheryBonusRequiresStationary)) {
hit.damage_done *= 2; hit.damage_done *= 2;
MessageString(Chat::MeleeCrit, BOW_DOUBLE_DAMAGE); 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; 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 if (min_mod && hit.damage_done < min_mod) // SPA 186
hit.damage_done = min_mod; hit.damage_done = min_mod;
TryCriticalHit(defender, hit, opts); if (!defender->HasShieldEquipped())
TryCriticalHit(defender, hit, opts);
hit.damage_done += hit.min_damage; hit.damage_done += hit.min_damage;
if (IsOfClientBot()) { if (IsOfClientBot()) {
@ -6746,12 +6823,62 @@ void Client::DoAttackRounds(Mob *target, int hand, bool IsFromSpell)
if (!target || (target && target->IsCorpse())) { if (!target || (target && target->IsCorpse())) {
return; 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 // extra off hand non-sense, can only double with skill of 150 or above
// or you have any amount of GiveDoubleAttack // or you have any amount of GiveDoubleAttack
/*
if (candouble && hand == EQ::invslot::slotSecondary) if (candouble && hand == EQ::invslot::slotSecondary)
candouble = candouble =
GetSkill(EQ::skills::SkillDoubleAttack) > 149 || GetSkill(EQ::skills::SkillDoubleAttack) > 149 ||
@ -6820,6 +6947,7 @@ void Client::DoAttackRounds(Mob *target, int hand, bool IsFromSpell)
} }
} }
} }
*/
} }
bool Mob::CheckDualWield() bool Mob::CheckDualWield()
@ -6827,8 +6955,7 @@ bool Mob::CheckDualWield()
// Pets /might/ follow a slightly different progression // Pets /might/ follow a slightly different progression
// although it could all be from pets having different skills than most mobs // although it could all be from pets having different skills than most mobs
int chance = GetSkill(EQ::skills::SkillDualWield); int chance = GetSkill(EQ::skills::SkillDualWield);
if (GetLevel() > 35) chance += 3 * GetLevel();
chance += GetLevel();
chance += aabonuses.Ambidexterity + spellbonuses.Ambidexterity + itembonuses.Ambidexterity; chance += aabonuses.Ambidexterity + spellbonuses.Ambidexterity + itembonuses.Ambidexterity;
int per_inc = spellbonuses.DualWieldChance + aabonuses.DualWieldChance + itembonuses.DualWieldChance; int per_inc = spellbonuses.DualWieldChance + aabonuses.DualWieldChance + itembonuses.DualWieldChance;
@ -6840,7 +6967,7 @@ bool Mob::CheckDualWield()
bool Client::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; chance += aabonuses.Ambidexterity + spellbonuses.Ambidexterity + itembonuses.Ambidexterity;
int per_inc = spellbonuses.DualWieldChance + aabonuses.DualWieldChance + itembonuses.DualWieldChance; int per_inc = spellbonuses.DualWieldChance + aabonuses.DualWieldChance + itembonuses.DualWieldChance;

View File

@ -3107,6 +3107,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *ne
if (new_bonus->ManaAbsorbPercentDamage < effect_value){ if (new_bonus->ManaAbsorbPercentDamage < effect_value){
new_bonus->ManaAbsorbPercentDamage = effect_value; new_bonus->ManaAbsorbPercentDamage = effect_value;
} }
new_bonus->ManaAbsorbPercentDamageCap += limit_value;
break; break;
} }

View File

@ -3440,7 +3440,6 @@ bool Bot::CheckIfIncapacitated() {
} }
void Bot::SetBerserkState() {// Berserk updates should occur if primary AI criteria are met 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)) { if (!berserk && GetHPRatio() < RuleI(Combat, BerserkerFrenzyStart)) {
entity_list.MessageCloseString(this, false, 200, 0, BERSERK_START, GetName()); entity_list.MessageCloseString(this, false, 200, 0, BERSERK_START, GetName());
berserk = true; 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()); entity_list.MessageCloseString(this, false, 200, 0, BERSERK_END, GetName());
berserk = false; berserk = false;
} }
}
} }
Mob* Bot::SetFollowMob(Client* leash_owner) { Mob* Bot::SetFollowMob(Client* leash_owner) {
@ -3559,7 +3558,7 @@ void Bot::Depop() {
RemoveAllAuras(); RemoveAllAuras();
Mob* bot_pet = GetPet(); Mob* bot_pet = GetPet();
if (bot_pet) { if (bot_pet) {
if (bot_pet->Charmed()) { if (bot_pet->Charmed()) {
bot_pet->BuffFadeByEffect(SE_Charm); bot_pet->BuffFadeByEffect(SE_Charm);
@ -11178,7 +11177,7 @@ void Bot::SetSpellTypePriority(uint16 spell_type, uint8 priority_type, uint16 pr
std::list<BotSpellTypeOrder> Bot::GetSpellTypesPrioritized(uint8 priority_type) { std::list<BotSpellTypeOrder> Bot::GetSpellTypesPrioritized(uint8 priority_type) {
std::list<BotSpellTypeOrder> cast_order; std::list<BotSpellTypeOrder> cast_order;
for (uint16 i = BotSpellTypes::START; i <= BotSpellTypes::END; i++) { for (uint16 i = BotSpellTypes::START; i <= BotSpellTypes::END; i++) {
BotSpellTypeOrder typeSettings = { BotSpellTypeOrder typeSettings = {
.spellType = i, .spellType = i,

View File

@ -317,7 +317,7 @@ int64 Client::CalcHPRegenCap()
int64 Client::CalcMaxHP() int64 Client::CalcMaxHP()
{ {
float nd = 10000; 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.. //The AA desc clearly says it only applies to base hp..
//but the actual effect sent on live causes the client //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 //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() int64 Client::CalcBaseHP()
@ -503,7 +504,7 @@ int64 Client::CalcBaseHP()
else { else {
Post255 = 0; 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; return base_hp;
} }
@ -653,6 +654,7 @@ int64 Client::CalcBaseManaRegen()
else { else {
regen = 2; regen = 2;
} }
regen += GetINT()/10;
return regen; return regen;
} }
@ -691,6 +693,7 @@ int64 Client::CalcManaRegen(bool bCombat)
if (level > 63) if (level > 63)
regen++; regen++;
} }
regen += GetINT()/10;
regen += aabonuses.ManaRegen; regen += aabonuses.ManaRegen;
// add in + 1 bonus for SE_CompleteHeal, but we don't do anything for it yet? // add in + 1 bonus for SE_CompleteHeal, but we don't do anything for it yet?

View File

@ -1052,7 +1052,7 @@ void Client::Handle_Connect_OP_ClientReady(const EQApplicationPacket *app)
if (!Spawned()) if (!Spawned())
SendZoneInPackets(); SendZoneInPackets();
CompleteConnect(); CompleteConnect();
SendHPUpdate(); SendHPUpdate(true);
} }
void Client::Handle_Connect_OP_ClientUpdate(const EQApplicationPacket *app) void Client::Handle_Connect_OP_ClientUpdate(const EQApplicationPacket *app)

View File

@ -463,7 +463,6 @@ bool Client::Process() {
} }
} }
if (GetClass() == Class::Warrior || GetClass() == Class::Berserker) {
if (!dead && !IsBerserk() && GetHPRatio() < RuleI(Combat, BerserkerFrenzyStart)) { if (!dead && !IsBerserk() && GetHPRatio() < RuleI(Combat, BerserkerFrenzyStart)) {
entity_list.MessageCloseString(this, false, 200, 0, BERSERK_START, GetName()); entity_list.MessageCloseString(this, false, 200, 0, BERSERK_START, GetName());
berserk = true; berserk = true;
@ -472,7 +471,7 @@ bool Client::Process() {
entity_list.MessageCloseString(this, false, 200, 0, BERSERK_END, GetName()); entity_list.MessageCloseString(this, false, 200, 0, BERSERK_END, GetName());
berserk = false; berserk = false;
} }
}
if (auto_attack && may_use_attacks && auto_attack_target != nullptr if (auto_attack && may_use_attacks && auto_attack_target != nullptr
&& CanThisClassDualWield() && attack_dw_timer.Check()) && CanThisClassDualWield() && attack_dw_timer.Check())

View File

@ -7,7 +7,7 @@
#include <cereal/cereal.hpp> #include <cereal/cereal.hpp>
#define HIGHEST_RESIST 9 //Max resist type value #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 */ /* macros for IsAttackAllowed, IsBeneficialAllowed */
#define _CLIENT(x) (x && x->IsClient() && !x->CastToClient()->IsBecomeNPC()) #define _CLIENT(x) (x && x->IsClient() && !x->CastToClient()->IsBecomeNPC())
@ -491,6 +491,7 @@ struct StatBonuses {
bool TriggerMeleeThreshold; // Has Melee Threshhold bool TriggerMeleeThreshold; // Has Melee Threshhold
bool TriggerSpellThreshold; // Has Spell Threshhold bool TriggerSpellThreshold; // Has Spell Threshhold
uint32 ManaAbsorbPercentDamage; // 0 = Mitigation value uint32 ManaAbsorbPercentDamage; // 0 = Mitigation value
uint32 ManaAbsorbPercentDamageCap; // 0 = Mitigation value
int32 EnduranceAbsorbPercentDamage[2]; // 0 = Mitigation value 1 = Percent Endurance drain per HP lost int32 EnduranceAbsorbPercentDamage[2]; // 0 = Mitigation value 1 = Percent Endurance drain per HP lost
int32 ShieldBlock; // Chance to Shield Block int32 ShieldBlock; // Chance to Shield Block
int32 BlockBehind; // Chance to Block Behind (with our without shield) int32 BlockBehind; // Chance to Block Behind (with our without shield)

View File

@ -1017,40 +1017,30 @@ uint32 Client::GetEXPForLevel(uint16 check_level)
uint16 check_levelm1 = check_level-1; uint16 check_levelm1 = check_level-1;
float mod; float mod;
if (check_level < 31) if (check_level < 5)
mod = 1.0; mod = 1.0;
else if (check_level < 36) else if (check_level < 10)
mod = 1.1; mod = 1.4 + 0.02 * check_level;
else if (check_level < 41) else if (check_level < 15)
mod = 1.2; mod = 1.5 + 0.03 * check_level;
else if (check_level < 46) else if (check_level < 20)
mod = 1.3; mod = 1.4 + 0.04 * check_level;
else if (check_level < 52) else if (check_level < 25)
mod = 1.4; mod = 1.2 + 0.05 * check_level;
else if (check_level < 53) else if (check_level < 30)
mod = 1.5; mod = 1.0 + 0.06 * check_level;
else if (check_level < 54) else if (check_level < 35)
mod = 1.6; mod = 0.7 + 0.07 * check_level;
else if (check_level < 55) else if (check_level < 40)
mod = 1.7; mod = 0.5 + 0.08 * check_level;
else if (check_level < 56) else if (check_level < 45)
mod = 1.9; mod = 0.2 + 0.09 * check_level;
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 else
mod = 3.1; mod = 1 + 0.1 * check_level;
float base = (check_levelm1)*(check_levelm1)*(check_levelm1); float base = (check_levelm1)*(check_levelm1)*(check_levelm1);
mod *= 1000; mod *= 2500;
uint32 finalxp = uint32(base * mod); uint32 finalxp = uint32(base * mod);

View File

@ -6071,7 +6071,7 @@ int32 Mob::GetPositionalDmgTaken(Mob *attacker)
if (back_arc || front_arc) { //Do they have this bonus? 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 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 else
total_mod = front_arc;//If not, apply the front arc modifer only total_mod = front_arc;//If not, apply the front arc modifer only
} }

View File

@ -1803,6 +1803,13 @@ public:
void SetSpawnedInWater(bool spawned_in_water); void SetSpawnedInWater(bool spawned_in_water);
bool turning; 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: protected:
// Bind wound // Bind wound
@ -1928,6 +1935,8 @@ protected:
uint16 m_merchant_session_entity_id; uint16 m_merchant_session_entity_id;
private: private:
Mob* target; Mob* target;
EQ::InventoryProfile m_inv; EQ::InventoryProfile m_inv;

View File

@ -424,7 +424,7 @@ public:
SwarmPet *GetSwarmInfo() { return (swarmInfoPtr); } SwarmPet *GetSwarmInfo() { return (swarmInfoPtr); }
void SetSwarmInfo(SwarmPet *mSwarmInfo) { swarmInfoPtr = mSwarmInfo; } 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;} void SetAccuracyRating(int32 d) { accuracy_rating = d;}
int32 GetAvoidanceRating() const { return (avoidance_rating); } int32 GetAvoidanceRating() const { return (avoidance_rating); }
void SetAvoidanceRating(int32 d) { avoidance_rating = d;} void SetAvoidanceRating(int32 d) { avoidance_rating = d;}

View File

@ -95,7 +95,7 @@ void Mob::MakePoweredPet(uint16 spell_id, const char* pettype, int16 petpower,
if(HasPet() || pettype == nullptr) if(HasPet() || pettype == nullptr)
return; 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 (petpower == -1) {
if (IsClient()) { if (IsClient()) {
act_power = CastToClient()->GetFocusEffect(focusPetPower, spell_id);//Client only 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) else if (petpower > 0)
act_power = petpower; act_power = petpower;
act_power += GetCHA();
// optional rule: classic style variance in pets. Achieve this by // optional rule: classic style variance in pets. Achieve this by
// adding a random 0-4 to pet power, since it only comes in increments // adding a random 0-4 to pet power, since it only comes in increments
// of five from focus effects. // of five from focus effects.

View File

@ -1027,6 +1027,31 @@ void Mob::DoArcheryAttackDmg(Mob *other, const EQ::ItemInstance *RangeWeapon, co
const EQ::ItemInstance *_Ammo = nullptr; const EQ::ItemInstance *_Ammo = nullptr;
const EQ::ItemData *last_ammo_used = 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, 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 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 {}; 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.min_damage = 0;
my_hit.damage_done = 1; 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.tohit = GetTotalToHit(my_hit.skill, chance_mod);
my_hit.hand = EQ::invslot::slotRange; my_hit.hand = EQ::invslot::slotRange;
//Attack(target, hand, false, false, IsFromSpell);
DoAttack(other, my_hit); DoAttack(other, my_hit);
TotalDmg = my_hit.damage_done; TotalDmg = my_hit.damage_done;
} else { } else {
@ -1153,22 +1184,25 @@ void Mob::DoArcheryAttackDmg(Mob *other, const EQ::ItemInstance *RangeWeapon, co
if (!DisableProcs) { if (!DisableProcs) {
// Skill Proc Attempt // Skill Proc Attempt
if (HasSkillProcs() && other && !other->HasDied()) { for (int i = 0; i < attack_count; i++)
if (ReuseTime) { {
TrySkillProc(other, EQ::skills::SkillArchery, ReuseTime); 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 // Skill Proc Success ... can proc off hits OR misses
if (HasSkillProcSuccess() && other && !other->HasDied()) { if (HasSkillProcSuccess() && other && !other->HasDied()) {
if (ReuseTime) { if (ReuseTime) {
TrySkillProc(other, EQ::skills::SkillArchery, ReuseTime, true); TrySkillProc(other, EQ::skills::SkillArchery, ReuseTime, true);
} }
else { else {
TrySkillProc(other, EQ::skills::SkillArchery, 0, true, EQ::invslot::slotRange); 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); DoThrowingAttackDmg(other, RangeWeapon, item, 0, 0, 0, 0, 0,ammo_slot);
// Consume Ammo, unless Ammo Consumption is disabled // Consume Ammo, unless Ammo Consumption is disabled
if (RuleB(Combat, ThrowingConsumesAmmo)) { //EndlessQuiver AA base1 = 100% Chance to avoid consumption arrow.
DeleteItemInInventory(ammo_slot, 1, true); int ChanceAvoidConsume = aabonuses.ConsumeProjectile + itembonuses.ConsumeProjectile + spellbonuses.ConsumeProjectile;
LogCombat("Consumed Throwing Ammo from slot {}.", ammo_slot);
} else { // Consume Ammo, unless Ammo Consumption is disabled or player has Endless Quiver
LogCombat("Throwing Ammo Consumption is disabled."); bool consumes_ammo = RuleB(Combat, ThrowingConsumesAmmo);
} if (
consumes_ammo &&
(
!ChanceAvoidConsume ||
(ChanceAvoidConsume < 100 && zone->random.Int(0,99) > ChanceAvoidConsume)
)
)
DeleteItemInInventory(ammo_slot, 1, true);
CommonBreakInvisibleFromCombat(); CommonBreakInvisibleFromCombat();
} }
@ -1627,6 +1669,7 @@ void Mob::DoThrowingAttackDmg(Mob *other, const EQ::ItemInstance *RangeWeapon, c
return; return;
} }
int attack_count = 1;
const EQ::ItemInstance *m_RangeWeapon = nullptr;//throwing weapon const EQ::ItemInstance *m_RangeWeapon = nullptr;//throwing weapon
const EQ::ItemData *last_ammo_used = nullptr; const EQ::ItemData *last_ammo_used = nullptr;
@ -1689,8 +1732,34 @@ void Mob::DoThrowingAttackDmg(Mob *other, const EQ::ItemInstance *RangeWeapon, c
int TotalDmg = 0; int TotalDmg = 0;
if (WDmg > 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 {}; 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.min_damage = 0;
my_hit.damage_done = 1; my_hit.damage_done = 1;
@ -1721,21 +1790,24 @@ void Mob::DoThrowingAttackDmg(Mob *other, const EQ::ItemInstance *RangeWeapon, c
TryCastOnSkillUse(other, EQ::skills::SkillThrowing); TryCastOnSkillUse(other, EQ::skills::SkillThrowing);
if (!DisableProcs) { if (!DisableProcs) {
if (HasSkillProcs() && other && !other->HasDied()) { for (int i = 0; i < attack_count; i ++)
if (ReuseTime) { {
TrySkillProc(other, EQ::skills::SkillThrowing, ReuseTime); 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 (HasSkillProcSuccess() && other && !other->HasDied()) {
if (ReuseTime) { if (ReuseTime) {
TrySkillProc(other, EQ::skills::SkillThrowing, ReuseTime, true); TrySkillProc(other, EQ::skills::SkillThrowing, ReuseTime, true);
} }
else { else {
TrySkillProc(other, EQ::skills::SkillThrowing, 0, true, EQ::invslot::slotRange); TrySkillProc(other, EQ::skills::SkillThrowing, 0, true, EQ::invslot::slotRange);
}
} }
} }
} }

View File

@ -263,6 +263,8 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove
if (buffslot >= 0) { if (buffslot >= 0) {
//This is here so dots with hit counters tic down on initial cast. //This is here so dots with hit counters tic down on initial cast.
if (caster && effect_value < 0) { 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); caster->GetActDoTDamage(spell_id, effect_value, this, false);
} }
break; break;
@ -279,6 +281,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove
// take partial damage into account // take partial damage into account
dmg = (int64) (dmg * partial / 100); dmg = (int64) (dmg * partial / 100);
dmg = (dmg * (100 + 2 * caster->GetINT())) / 100;
//handles AAs and what not... //handles AAs and what not...
if(caster) { 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); dmg = caster->GetActReflectedSpellDamage(spell_id, (int64)(spells[spell_id].base_value[i] * partial / 100), reflect_effectiveness);
} }
else { else {
//dmg = dmg * partial / 100;
dmg = caster->GetActSpellDamage(spell_id, dmg, this); dmg = caster->GetActSpellDamage(spell_id, dmg, this);
} }
caster->ResourceTap(-dmg, spell_id); caster->ResourceTap(-effect_value, spell_id);
} }
if (dmg <= 0) { if (dmg <= 0) {
@ -304,7 +308,10 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove
//healing spell... //healing spell...
if(caster) if(caster)
{
dmg = (dmg * (100 + 2 * caster->GetWIS())) / 100;
dmg = caster->GetActSpellHealing(spell_id, dmg, this); dmg = caster->GetActSpellHealing(spell_id, dmg, this);
}
HealDamage(dmg, caster); HealDamage(dmg, caster);
} }
@ -1377,6 +1384,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove
case SE_Rune: case SE_Rune:
{ {
effect_value = (effect_value * (100 + 2 * caster->GetCHA())) / 100;
#ifdef SPELL_EFFECT_SPAM #ifdef SPELL_EFFECT_SPAM
snprintf(effect_desc, _EDLEN, "Melee Absorb Rune: %+i", effect_value); snprintf(effect_desc, _EDLEN, "Melee Absorb Rune: %+i", effect_value);
#endif #endif
@ -1388,6 +1396,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove
case SE_AbsorbMagicAtt: case SE_AbsorbMagicAtt:
{ {
effect_value = (effect_value * (100 + 2 * caster->GetCHA())) / 100;
#ifdef SPELL_EFFECT_SPAM #ifdef SPELL_EFFECT_SPAM
snprintf(effect_desc, _EDLEN, "Spell Absorb Rune: %+i", effect_value); snprintf(effect_desc, _EDLEN, "Spell Absorb Rune: %+i", effect_value);
#endif #endif
@ -1400,6 +1409,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial, int level_ove
case SE_MitigateMeleeDamage: case SE_MitigateMeleeDamage:
{ {
effect_value = (effect_value * (100 + 2 * caster->GetCHA())) / 100;
if (buffslot > -1) { if (buffslot > -1) {
buffs[buffslot].melee_rune = spells[spell_id].max_value[i]; 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: case SE_MeleeThresholdGuard:
{ {
effect_value = (effect_value * (100 + 2 * caster->GetCHA())) / 100;
if (buffslot > -1) { if (buffslot > -1) {
buffs[buffslot].melee_rune = spells[spell_id].max_value[i]; 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: case SE_SpellThresholdGuard:
{ {
effect_value = (effect_value * (100 + 2 * caster->GetCHA())) / 100;
if (buffslot > -1) { if (buffslot > -1) {
buffs[buffslot].magic_rune = spells[spell_id].max_value[i]; 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: case SE_MitigateSpellDamage:
{ {
effect_value = (effect_value * (100 + 2 * caster->GetCHA())) / 100;
if (buffslot > -1) { if (buffslot > -1) {
buffs[buffslot].magic_rune = spells[spell_id].max_value[i]; 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: case SE_MitigateDotDamage:
{ {
effect_value = (effect_value * (100 + 2 * caster->GetCHA())) / 100;
if (buffslot > -1) { if (buffslot > -1) {
buffs[buffslot].dot_rune = spells[spell_id].max_value[i]; 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: { 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. //This is here so buffs with hit counters tic down on initial cast.
caster->GetActSpellHealing(spell_id, effect_value, nullptr, false); caster->GetActSpellHealing(spell_id, effect_value, nullptr, false);
break; break;
@ -3955,6 +3971,12 @@ void Mob::DoBuffTic(const Buffs_Struct &buff, int slot, Mob *caster)
AddToHateList(caster, -effect_value); AddToHateList(caster, -effect_value);
} }
} }
if (caster)
{
effect_value = (effect_value * (100 + 2 * caster->GetINT())) / 100;
effect_value = static_cast<int>(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); effect_value = caster->GetActDoTDamage(buff.spellid, effect_value, this);

View File

@ -3000,6 +3000,15 @@ int Mob::CalcBuffDuration(Mob *caster, Mob *target, uint16 spell_id, int32 caste
LogSpells("Spell [{}]: Casting level [{}], formula [{}], base_duration [{}]: result [{}]", LogSpells("Spell [{}]: Casting level [{}], formula [{}], base_duration [{}]: result [{}]",
spell_id, castlevel, formula, duration, res); spell_id, castlevel, formula, duration, res);
if (res < 10)
{
res += GetCHA()/20;
}
else
{
res *= 1.0f + GetCHA() / 100.0f;
}
return res; return res;
} }
@ -4495,7 +4504,7 @@ bool Mob::SpellOnTarget(
} }
if (spell_effectiveness < 100) { 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()); LogSpells("Spell [{}] was completely resisted by [{}]", spell_id, spelltar->GetName());
if (spells[spell_id].resist_type == RESIST_PHYSICAL){ 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); return(0);
} }
//Get resist modifier and adjust it based on focus 2 resist about eq to 1% resist chance //Get resist modifier and adjust it based on focus 2 resist about eq to 1% resist chance
int resist_modifier = 0; int resist_modifier = 0;
if (use_resist_override) { if (use_resist_override) {
@ -5561,6 +5571,22 @@ float Mob::ResistSpell(uint8 resist_type, uint16 spell_id, Mob *caster, bool use
//Finally our roll //Finally our roll
int roll = zone->random.Int(0, RuleB(Spells, EnableResistSoftCap) ? RuleI(Spells, SpellResistSoftCap) : 200); 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) { if(roll > resist_chance) {
return 100; return 100;
} else { } else {

View File

@ -1173,7 +1173,7 @@ void Client::GoToBind(uint8 bind_number) {
} }
void Client::GoToDeath() { 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) void Client::ClearZoneFlag(uint32 zone_id)