Merge pull request #7 from AgeOfTitans/aot-carolus-dev

Aot carolus dev
This commit is contained in:
carolus21rex 2025-05-15 22:09:27 -04:00 committed by GitHub
commit 3aed9b4ab9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 451 additions and 189 deletions

View File

@ -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"
}

1
.gitignore vendored
View File

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

View File

@ -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;
}

View File

@ -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<double>(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<int>(GetLevel())) * GetAGI()) / 10;
if (IsOfClientBot()) {
defense += itembonuses.heroic_agi_avoidance;
@ -713,6 +725,8 @@ int Mob::GetACSoftcap()
int level = std::min(105, static_cast<int>(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<int>(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<int>(level), 10, 35);
//if (GetRace() == IKSAR)
// ac_bonus += EQ::Clamp(static_cast<int>(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<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
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<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);
}
@ -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,18 +3846,28 @@ 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);
}
}
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];
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;
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;
@ -3808,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);
}
}
@ -4066,7 +4134,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);
@ -4236,6 +4304,7 @@ void Mob::CommonDamage(Mob* attacker, int64 &damage, const uint16 spell_id, cons
damage = 0;
}
SetHP(int64(GetHP() - damage));
if (HasDied()) {
@ -4470,12 +4539,12 @@ void Mob::CommonDamage(Mob* attacker, int64 &damage, const uint16 spell_id, cons
}
} //end `if damage was done`
//send damage packet...
//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()) {
@ -4485,7 +4554,7 @@ void Mob::CommonDamage(Mob* attacker, int64 &damage, const uint16 spell_id, cons
}
a->type = (EQ::ValueWithin(skill_used, EQ::skills::Skill1HBlunt, EQ::skills::Skill2HPiercing)) ?
SkillDamageTypes[skill_used] : SkillDamageTypes[EQ::skills::SkillHandtoHand]; // was 0x1c
SkillDamageTypes[skill_used] : SkillDamageTypes[EQ::skills::SkillHandtoHand]; // was 0x1c
a->damage = damage;
a->spellid = spell_id;
@ -4752,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)
@ -5349,9 +5419,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 +5476,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 +5491,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 +5535,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 +5545,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 +5908,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 +5923,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 +6002,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 +6015,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 +6415,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 +6430,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 +6460,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 +6479,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 +6830,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 +6954,7 @@ void Client::DoAttackRounds(Mob *target, int hand, bool IsFromSpell)
}
}
}
*/
}
bool Mob::CheckDualWield()
@ -6827,8 +6962,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 +6974,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;

View File

@ -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;
}
@ -3116,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->EnduranceAbsorbPercentCap += max_value;
break;
}

View File

@ -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<BotSpellTypeOrder> Bot::GetSpellTypesPrioritized(uint8 priority_type) {
std::list<BotSpellTypeOrder> cast_order;
for (uint16 i = BotSpellTypes::START; i <= BotSpellTypes::END; i++) {
BotSpellTypeOrder typeSettings = {
.spellType = i,

View File

@ -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?

View File

@ -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)

View File

@ -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())

View File

@ -7,7 +7,7 @@
#include <cereal/cereal.hpp>
#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,7 +491,9 @@ 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 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

View File

@ -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);

View File

@ -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
}

View File

@ -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;

View File

@ -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;}

View File

@ -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.

View File

@ -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);
}
}
}
}

View File

@ -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<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);

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 [{}]",
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 {

View File

@ -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)