diff --git a/changelog.txt b/changelog.txt index 3c31c83e5..f3fdc3ba0 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,10 @@ EQEMu Changelog (Started on Sept 24, 2003 15:50) ------------------------------------------------------- +== 7/4/2015 == +mackal: Reworked the activated avoidace skills (riposte, dodge, etc) based on dev quotes + This also fixes the order things are checked (avoidance skills, THEN hit/miss) + Also riposte immunity from the increase riposte chance spell effect. + == 7/2/2015 == KLS/Demonstar55: AA system has been rewritten fixing a ton of bugs and extending functionality while making it easier to create new AA points. KLS/Demonstar55: New tables are needed and so older data will need to be migrated to the new system. diff --git a/zone/attack.cpp b/zone/attack.cpp index c493b159f..8a120b896 100644 --- a/zone/attack.cpp +++ b/zone/attack.cpp @@ -341,7 +341,7 @@ bool Mob::CheckHitChance(Mob* other, SkillUseTypes skillinuse, int Hand, int16 c return(tohit_roll <= chancetohit); } -bool Mob::AvoidDamage(Mob* other, int32 &damage, bool CanRiposte) +bool Mob::AvoidDamage(Mob *other, int32 &damage, int hand) { /* called when a mob is attacked, does the checks to see if it's a hit * and does other mitigation checks. 'this' is the mob being attacked. @@ -353,22 +353,32 @@ bool Mob::AvoidDamage(Mob* other, int32 &damage, bool CanRiposte) * -4 - dodge * */ - float skill; - float bonus; - float RollTable[4] = {0,0,0,0}; - float roll; - Mob *attacker=other; - Mob *defender=this; - //garunteed hit - bool ghit = false; - if((attacker->aabonuses.MeleeSkillCheck + attacker->spellbonuses.MeleeSkillCheck + attacker->itembonuses.MeleeSkillCheck) > 500) - ghit = true; + /* Order according to current (SoF+?) dev quotes: + * https://forums.daybreakgames.com/eq/index.php?threads/test-update-06-10-15.223510/page-2#post-3261772 + * https://forums.daybreakgames.com/eq/index.php?threads/test-update-06-10-15.223510/page-2#post-3268227 + * Riposte 50, hDEX, must have weapon/fists, doesn't work on archery/throwing + * Block 25, hDEX, works on archery/throwing, behind block done here if back to attacker base1 is chance + * Parry 45, hDEX, doesn't work on throwing/archery, must be facing target + * Dodge 45, hAGI, works on archery/throwing, monks can dodge attacks from behind + * Shield Block, rand base1 + * Staff Block, rand base1 + * regular strike through + * avoiding the attack (CheckHitChance) + * As soon as one succeeds, none of the rest are checked + * + * Formula (all int math) + * (posted for parry, assume rest at the same) + * Chance = (((SKILL + 100) + [((SKILL+100) * SPA(175).Base1) / 100]) / 45) + [(hDex / 25) - min([hDex / 25], hStrikethrough)]. + * hStrikethrough is a mob stat that was added to counter the bonuses of heroic stats + * Number rolled against 100, if the chance is greater than 100 it happens 100% of time + * + * Things with 10k accuracy mods can be avoided with these skills qq + */ + Mob *attacker = other; + Mob *defender = this; - bool InFront = false; - - if (attacker->InFrontMob(this, attacker->GetX(), attacker->GetY())) - InFront = true; + bool InFront = attacker->InFrontMob(this, attacker->GetX(), attacker->GetY()); /* This special ability adds a negative modifer to the defenders riposte/block/parry/chance @@ -383,10 +393,9 @@ bool Mob::AvoidDamage(Mob* other, int32 &damage, bool CanRiposte) int counter_parry = 0; int counter_dodge = 0; - if (attacker->GetSpecialAbility(COUNTER_AVOID_DAMAGE)){ - + if (attacker->GetSpecialAbility(COUNTER_AVOID_DAMAGE)) { counter_all = attacker->GetSpecialAbilityParam(COUNTER_AVOID_DAMAGE, 0); - counter_riposte = attacker->GetSpecialAbilityParam(COUNTER_AVOID_DAMAGE,1); + counter_riposte = attacker->GetSpecialAbilityParam(COUNTER_AVOID_DAMAGE, 1); counter_block = attacker->GetSpecialAbilityParam(COUNTER_AVOID_DAMAGE, 2); counter_parry = attacker->GetSpecialAbilityParam(COUNTER_AVOID_DAMAGE, 3); counter_dodge = attacker->GetSpecialAbilityParam(COUNTER_AVOID_DAMAGE, 4); @@ -400,31 +409,37 @@ bool Mob::AvoidDamage(Mob* other, int32 &damage, bool CanRiposte) Log.Out(Logs::Detail, Logs::Combat, "I am enraged, riposting frontal attack."); } - ///////////////////////////////////////////////////////// - // riposte - ///////////////////////////////////////////////////////// - float riposte_chance = 0.0f; - if (CanRiposte && damage > 0 && CanThisClassRiposte() && InFront) - { - riposte_chance = (100.0f + static_cast(aabonuses.RiposteChance + spellbonuses.RiposteChance + - itembonuses.RiposteChance - counter_riposte - counter_all)) / 100.0f; - skill = GetSkill(SkillRiposte); - if (IsClient()) { + // riposte -- it may seem crazy, but if the attacker has SPA 173 on them, they are immune to Ripo + bool ImmuneRipo = attacker->aabonuses.RiposteChance || attacker->spellbonuses.RiposteChance || attacker->itembonuses.RiposteChance; + // Need to check if we have something in MainHand to actually attack with (or fists) + if (hand != MainRange && CanThisClassRiposte() && InFront && !ImmuneRipo) { + if (IsClient()) CastToClient()->CheckIncreaseSkill(SkillRiposte, other, -10); + // check auto discs ... I guess aa/items too :P + if (spellbonuses.RiposteChance == 10000 || aabonuses.RiposteChance == 10000 || itembonuses.RiposteChance == 10000) { + damage = -3; + return true; } - - if (!ghit) { //if they are not using a garunteed hit discipline - bonus = 2.0 + skill/60.0 + (GetDEX()/200); - bonus *= riposte_chance; - bonus = mod_riposte_chance(bonus, attacker); - RollTable[0] = bonus + (itembonuses.HeroicDEX / 25); // 25 heroic = 1%, applies to ripo, parry, block + int chance = GetSkill(SkillRiposte) + 100; + chance += (chance * (aabonuses.RiposteChance + spellbonuses.RiposteChance + itembonuses.RiposteChance)) / 100; + chance /= 50; + chance += itembonuses.HeroicDEX / 25; // live has "heroic strickthrough" here to counter + if (counter_riposte || counter_all) { + float counter = (counter_riposte + counter_all) / 100.0f; + chance -= chance * counter; + } + // AA Slippery Attacks + if (hand == MainSecondary) { + int slip = aabonuses.OffhandRiposteFail + itembonuses.OffhandRiposteFail + spellbonuses.OffhandRiposteFail; + chance += chance * slip / 100; + } + if (chance > 0 && zone->random.Roll(chance)) { // could be <0 from offhand stuff + damage = -3; + return true; } } - /////////////////////////////////////////////////////// // block - /////////////////////////////////////////////////////// - bool bBlockFromRear = false; // a successful roll on this does not mean a successful block is forthcoming. only that a chance to block @@ -435,101 +450,100 @@ bool Mob::AvoidDamage(Mob* other, int32 &damage, bool CanRiposte) if (BlockBehindChance && zone->random.Roll(BlockBehindChance)) bBlockFromRear = true; - float block_chance = 0.0f; - if (damage > 0 && CanThisClassBlock() && (InFront || bBlockFromRear)) { - block_chance = (100.0f + static_cast(aabonuses.IncreaseBlockChance + spellbonuses.IncreaseBlockChance + - itembonuses.IncreaseBlockChance - counter_block - counter_all)) / 100.0f; - skill = CastToClient()->GetSkill(SkillBlock); - if (IsClient()) { + if (CanThisClassBlock() && (InFront || bBlockFromRear)) { + if (IsClient()) CastToClient()->CheckIncreaseSkill(SkillBlock, other, -10); - } - - if (!ghit) { //if they are not using a garunteed hit discipline - bonus = 2.0 + skill/35.0 + (GetDEX()/200); - bonus = mod_block_chance(bonus, attacker); - RollTable[1] = RollTable[0] + (bonus * block_chance); - } - } - else{ - RollTable[1] = RollTable[0]; - } - - //Try Shield Block OR TwoHandBluntBlockCheck - if(damage > 0 && HasShieldEquiped() && (aabonuses.ShieldBlock || spellbonuses.ShieldBlock || itembonuses.ShieldBlock) && (InFront || bBlockFromRear)) - RollTable[1] += static_cast(aabonuses.ShieldBlock + spellbonuses.ShieldBlock + itembonuses.ShieldBlock - counter_block - counter_all); - - else if(damage > 0 && HasTwoHandBluntEquiped() && (aabonuses.TwoHandBluntBlock || spellbonuses.TwoHandBluntBlock || itembonuses.TwoHandBluntBlock) && (InFront || bBlockFromRear)) - RollTable[1] += static_cast(aabonuses.TwoHandBluntBlock + spellbonuses.TwoHandBluntBlock + itembonuses.TwoHandBluntBlock - counter_block - counter_all); - - ////////////////////////////////////////////////////// - // parry - ////////////////////////////////////////////////////// - float parry_chance = 0.0f; - if (damage > 0 && CanThisClassParry() && InFront){ - parry_chance = (100.0f + static_cast(aabonuses.ParryChance + itembonuses.ParryChance + - itembonuses.ParryChance - counter_parry - counter_all)) / 100.0f; - skill = CastToClient()->GetSkill(SkillParry); - if (IsClient()) { - CastToClient()->CheckIncreaseSkill(SkillParry, other, -10); - } - - if (!ghit) { //if they are not using a garunteed hit discipline - bonus = 2.0 + skill/60.0 + (GetDEX()/200); - bonus *= parry_chance; - bonus = mod_parry_chance(bonus, attacker); - RollTable[2] = RollTable[1] + bonus; - } - } - else{ - RollTable[2] = RollTable[1]; - } - - //////////////////////////////////////////////////////// - // dodge - //////////////////////////////////////////////////////// - float dodge_chance = 0.0f; - if (damage > 0 && CanThisClassDodge() && InFront){ - - dodge_chance = (100.0f + static_cast(aabonuses.DodgeChance + spellbonuses.DodgeChance + - itembonuses.DodgeChance - counter_dodge - counter_all)) / 100.0f; - - skill = CastToClient()->GetSkill(SkillDodge); - if (IsClient()) { - CastToClient()->CheckIncreaseSkill(SkillDodge, other, -10); - } - - if (!ghit) { //if they are not using a garunteed hit discipline - bonus = 2.0 + skill/60.0 + (GetAGI()/200); - bonus *= dodge_chance; - //DCBOOMKAR - bonus = mod_dodge_chance(bonus, attacker); - RollTable[3] = RollTable[2] + bonus - (itembonuses.HeroicDEX / 25) + (itembonuses.HeroicAGI / 25); - } - } - else{ - RollTable[3] = RollTable[2]; - } - - if(damage > 0){ - roll = zone->random.Real(0,100); - if(roll <= RollTable[0]){ - damage = -3; - } - else if(roll <= RollTable[1]){ + // check auto discs ... I guess aa/items too :P + if (spellbonuses.IncreaseBlockChance == 10000 || aabonuses.IncreaseBlockChance == 10000 || + itembonuses.IncreaseBlockChance == 10000) { damage = -1; + return true; } - else if(roll <= RollTable[2]){ - damage = -2; + int chance = GetSkill(SkillBlock) + 100; + chance += (chance * (aabonuses.IncreaseBlockChance + spellbonuses.IncreaseBlockChance + itembonuses.IncreaseBlockChance)) / 100; + chance /= 25; + chance += itembonuses.HeroicDEX / 25; // live has "heroic strickthrough" here to counter + if (counter_block || counter_all) { + float counter = (counter_block + counter_all) / 100.0f; + chance -= chance * counter; } - else if(roll <= RollTable[3]){ - damage = -4; + if (zone->random.Roll(chance)) { + damage = -1; + return true; } } - Log.Out(Logs::Detail, Logs::Combat, "Final damage after all avoidances: %d", damage); + // parry + if (CanThisClassParry() && InFront && hand != MainRange) { + if (IsClient()) + CastToClient()->CheckIncreaseSkill(SkillParry, other, -10); + // check auto discs ... I guess aa/items too :P + if (spellbonuses.ParryChance == 10000 || aabonuses.ParryChance == 10000 || itembonuses.ParryChance == 10000) { + damage = -2; + return true; + } + int chance = GetSkill(SkillParry) + 100; + chance += (chance * (aabonuses.ParryChance + spellbonuses.ParryChance + itembonuses.ParryChance)) / 100; + chance /= 45; + chance += itembonuses.HeroicDEX / 25; // live has "heroic strickthrough" here to counter + if (counter_parry || counter_all) { + float counter = (counter_parry + counter_all) / 100.0f; + chance -= chance * counter; + } + if (zone->random.Roll(chance)) { + damage = -2; + return true; + } + } + + // dodge + if (CanThisClassDodge() && (InFront || GetClass() == MONK) ) { + if (IsClient()) + CastToClient()->CheckIncreaseSkill(SkillDodge, other, -10); + // check auto discs ... I guess aa/items too :P + if (spellbonuses.DodgeChance == 10000 || aabonuses.DodgeChance == 10000 || itembonuses.DodgeChance == 10000) { + damage = -4; + return true; + } + int chance = GetSkill(SkillDodge) + 100; + chance += (chance * (aabonuses.DodgeChance + spellbonuses.DodgeChance + itembonuses.DodgeChance)) / 100; + chance /= 45; + chance += itembonuses.HeroicAGI / 25; // live has "heroic strickthrough" here to counter + if (counter_dodge || counter_all) { + float counter = (counter_dodge + counter_all) / 100.0f; + chance -= chance * counter; + } + if (zone->random.Roll(chance)) { + damage = -4; + return true; + } + } + + // Try Shield Block OR TwoHandBluntBlockCheck + if (HasShieldEquiped() && (aabonuses.ShieldBlock || spellbonuses.ShieldBlock || itembonuses.ShieldBlock) && (InFront || bBlockFromRear)) { + int chance = aabonuses.ShieldBlock + spellbonuses.ShieldBlock + itembonuses.ShieldBlock; + if (counter_block || counter_all) { + float counter = (counter_block + counter_all) / 100.0f; + chance -= chance * counter; + } + if (zone->random.Roll(chance)) { + damage = -1; + return true; + } + } + + if (HasTwoHandBluntEquiped() && (aabonuses.TwoHandBluntBlock || spellbonuses.TwoHandBluntBlock || itembonuses.TwoHandBluntBlock) && (InFront || bBlockFromRear)) { + int chance = aabonuses.TwoHandBluntBlock + itembonuses.TwoHandBluntBlock + spellbonuses.TwoHandBluntBlock; + if (counter_block || counter_all) { + float counter = (counter_block + counter_all) / 100.0f; + chance -= chance * counter; + } + if (zone->random.Roll(chance)) { + damage = -1; + return true; + } + } - if (damage < 0) - return true; return false; } @@ -1286,6 +1300,7 @@ bool Client::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, b } } + // this effect is actually a min cap that happens after the final damage is calculated min_hit += min_hit * GetMeleeMinDamageMod_SE(skillinuse) / 100; if(max_hit < min_hit) @@ -1312,62 +1327,41 @@ bool Client::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, b } //check to see if we hit.. - if(!other->CheckHitChance(this, skillinuse, Hand, hit_chance_bonus)) { - Log.Out(Logs::Detail, Logs::Combat, "Attack missed. Damage set to 0."); - damage = 0; - } else { //we hit, try to avoid it - other->AvoidDamage(this, damage); - other->MeleeMitigation(this, damage, min_hit, opts); - if(damage > 0) - CommonOutgoingHitSuccess(other, damage, skillinuse); - - Log.Out(Logs::Detail, Logs::Combat, "Final damage after all reductions: %d", damage); - } - - //riposte - bool slippery_attack = false; // Part of hack to allow riposte to become a miss, but still allow a Strikethrough chance (like on Live) - if (damage == -3) { - if (bRiposte) return false; - else { - if (Hand == MainSecondary) {// Do we even have it & was attack with mainhand? If not, don't bother with other calculations - //Live AA - SlipperyAttacks - //This spell effect most likely directly modifies the actual riposte chance when using offhand attack. - int32 OffhandRiposteFail = aabonuses.OffhandRiposteFail + itembonuses.OffhandRiposteFail + spellbonuses.OffhandRiposteFail; - OffhandRiposteFail *= -1; //Live uses a negative value for this. - - if (OffhandRiposteFail && - (OffhandRiposteFail > 99 || zone->random.Roll(OffhandRiposteFail))) { - damage = 0; // Counts as a miss - slippery_attack = true; - } else - DoRiposte(other); - if (IsDead()) return false; + if (other->AvoidDamage(this, damage, Hand)) { + if (!bRiposte && !IsStrikethrough) { + int strike_through = itembonuses.StrikeThrough + spellbonuses.StrikeThrough + aabonuses.StrikeThrough; + if(strike_through && zone->random.Roll(strike_through)) { + Message_StringID(MT_StrikeThrough, STRIKETHROUGH_STRING); // You strike through your opponents defenses! + Attack(other, Hand, false, true); // Strikethrough only gives another attempted hit + return false; } - else + // I'm pretty sure you can riposte a riposte + if (damage == -3 && !bRiposte) { DoRiposte(other); - if (IsDead()) return false; + if (IsDead()) + return false; + } + } + Log.Out(Logs::Detail, Logs::Combat, "Avoided damage with code %d", damage); + } else { + if (other->CheckHitChance(this, skillinuse, Hand, hit_chance_bonus)) { + other->MeleeMitigation(this, damage, min_hit, opts); + if (damage > 0) + CommonOutgoingHitSuccess(other, damage, skillinuse); + Log.Out(Logs::Detail, Logs::Combat, "Final damage after all reductions: %d", damage); + } else { + Log.Out(Logs::Detail, Logs::Combat, "Attack missed. Damage set to 0."); + damage = 0; } } - - if (((damage < 0) || slippery_attack) && !bRiposte && !IsStrikethrough) { // Hack to still allow Strikethrough chance w/ Slippery Attacks AA - int32 bonusStrikeThrough = itembonuses.StrikeThrough + spellbonuses.StrikeThrough + aabonuses.StrikeThrough; - - if(bonusStrikeThrough && zone->random.Roll(bonusStrikeThrough)) { - Message_StringID(MT_StrikeThrough, STRIKETHROUGH_STRING); // You strike through your opponents defenses! - Attack(other, Hand, false, true); // Strikethrough only gives another attempted hit - return false; - } - } - } - else{ + } else { damage = -5; } // Hate Generation is on a per swing basis, regardless of a hit, miss, or block, its always the same. // If we are this far, this means we are atleast making a swing. - if (!bRiposte) // Ripostes never generate any aggro. - other->AddToHateList(this, hate); + other->AddToHateList(this, hate); /////////////////////////////////////////////////////////// ////// Send Attack Damage @@ -1912,21 +1906,18 @@ bool NPC::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, bool hit_chance_bonus += opts->hit_chance; } - if(!other->CheckHitChance(this, skillinuse, Hand, hit_chance_bonus)) { - damage = 0; //miss - } else { //hit, check for damage avoidance - other->AvoidDamage(this, damage); - other->MeleeMitigation(this, damage, min_dmg+eleBane, opts); - if(damage > 0) { + if (other->AvoidDamage(this, damage, Hand)) { + if (!bRiposte && damage == -3) + DoRiposte(other); + } else { + if (other->CheckHitChance(this, skillinuse, Hand, hit_chance_bonus)) { + other->MeleeMitigation(this, damage, min_dmg+eleBane, opts); CommonOutgoingHitSuccess(other, damage, skillinuse); + } else { + damage = 0; } - Log.Out(Logs::Detail, Logs::Combat, "Generating hate %d towards %s", hate, GetName()); - // now add done damage to the hate list - if(damage > 0) - other->AddToHateList(this, hate); - else - other->AddToHateList(this, 0); } + other->AddToHateList(this, hate); } Log.Out(Logs::Detail, Logs::Combat, "Final damage against %s: %d", other->GetName(), damage); @@ -1939,12 +1930,6 @@ bool NPC::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, bool else damage = -5; - //cant riposte a riposte - if (bRiposte && damage == -3) { - Log.Out(Logs::Detail, Logs::Combat, "Riposte of riposte canceled."); - return false; - } - if(GetHP() > 0 && !other->HasDied()) { other->Damage(this, damage, SPELL_UNKNOWN, skillinuse, false); // Not avoidable client already had thier chance to Avoid } else @@ -1974,11 +1959,6 @@ bool NPC::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, bool if(GetHP() > 0 && !other->HasDied()) TriggerDefensiveProcs(nullptr, other, Hand, damage); - // now check ripostes - if (damage == -3) { // riposting - DoRiposte(other); - } - if (damage > 0) return true; @@ -3581,7 +3561,7 @@ void Mob::CommonDamage(Mob* attacker, int32 &damage, const uint16 spell_id, cons //final damage has been determined. SetHP(GetHP() - damage); - + if (IsClient()) this->CastToClient()->SendHPUpdateMarquee(); diff --git a/zone/bot.cpp b/zone/bot.cpp index 5450d4f8a..4bc291e84 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -2192,24 +2192,25 @@ void Bot::DoMeleeSkillAttackDmg(Mob* other, uint16 weapon_damage, SkillUseTypes else damage = zone->random.Int(min_hit, max_hit); - if(!other->CheckHitChance(this, skillinuse, Hand, chance_mod)) - damage = 0; - else { - other->AvoidDamage(this, damage, CanRiposte); - other->MeleeMitigation(this, damage, min_hit); - if(damage > 0) { - damage += damage*focus/100; - ApplyMeleeDamageBonus(skillinuse, damage); - damage += other->GetFcDamageAmtIncoming(this, 0, true, skillinuse); - damage += ((itembonuses.HeroicSTR / 10) + (damage * other->GetSkillDmgTaken(skillinuse) / 100) + GetSkillDmgAmt(skillinuse)); - TryCriticalHit(other, skillinuse, damage, nullptr); + if (other->AvoidDamage(this, damage, CanRiposte ? MainRange : MainPrimary)) { // MainRange excludes ripo, primary doesn't have any extra behavior + if (damage == -3) { + DoRiposte(other); + if (HasDied()) + return; + } + } else { + if (other->CheckHitChance(this, skillinuse, Hand, chance_mod)) { + other->MeleeMitigation(this, damage, min_hit); + if (damage > 0) { + damage += damage*focus/100; + ApplyMeleeDamageBonus(skillinuse, damage); + damage += other->GetFcDamageAmtIncoming(this, 0, true, skillinuse); + damage += ((itembonuses.HeroicSTR / 10) + (damage * other->GetSkillDmgTaken(skillinuse) / 100) + GetSkillDmgAmt(skillinuse)); + TryCriticalHit(other, skillinuse, damage, nullptr); + } + } else { + damage = 0; } - } - - if (damage == -3) { - DoRiposte(other); - if (HasDied()) - return; } } else @@ -4813,70 +4814,38 @@ bool Bot::Attack(Mob* other, int Hand, bool FromRiposte, bool IsStrikethrough, b } //check to see if we hit.. - if(!other->CheckHitChance(other, skillinuse, Hand)) { - Log.Out(Logs::Detail, Logs::Combat, "Attack missed. Damage set to 0."); - damage = 0; - other->AddToHateList(this, 0); - } else { //we hit, try to avoid it - other->AvoidDamage(this, damage); - other->MeleeMitigation(this, damage, min_hit, opts); - if(damage > 0) { + if (other->AvoidDamage(this, damage, Hand)) { + if (!FromRiposte && !IsStrikethrough) { + int strike_through = itembonuses.StrikeThrough + spellbonuses.StrikeThrough + aabonuses.StrikeThrough; + if(strike_through && zone->random.Roll(strike_through)) { + Message_StringID(MT_StrikeThrough, STRIKETHROUGH_STRING); // You strike through your opponents defenses! + Attack(other, Hand, false, true); // Strikethrough only gives another attempted hit + return false; + } + if (damage == -3 && !FromRiposte) { + DoRiposte(other); + if (HasDied()) + return false; + } + } + } else { + if (other->CheckHitChance(this, skillinuse, Hand)) { + other->MeleeMitigation(this, damage, min_hit, opts); ApplyMeleeDamageBonus(skillinuse, damage); damage += ((itembonuses.HeroicSTR / 10) + (damage * other->GetSkillDmgTaken(skillinuse) / 100) + GetSkillDmgAmt(skillinuse)); TryCriticalHit(other, skillinuse, damage, opts); - Log.Out(Logs::Detail, Logs::Combat, "Generating hate %d towards %s", hate, GetCleanName()); - // now add done damage to the hate list - //other->AddToHateList(this, hate); - } - else - other->AddToHateList(this, 0); - Log.Out(Logs::Detail, Logs::Combat, "Final damage after all reductions: %d", damage); - } - - //riposte - bool slippery_attack = false; // Part of hack to allow riposte to become a miss, but still allow a Strikethrough chance (like on Live) - if (damage == -3) { - if (FromRiposte) - return false; - else { - if (Hand == MainSecondary) {// Do we even have it & was attack with mainhand? If not, don't bother with other calculations - //Live AA - SlipperyAttacks - //This spell effect most likely directly modifies the actual riposte chance when using offhand attack. - int32 OffhandRiposteFail = (aabonuses.OffhandRiposteFail + itembonuses.OffhandRiposteFail + spellbonuses.OffhandRiposteFail); - OffhandRiposteFail *= -1; //Live uses a negative value for this. - - if (OffhandRiposteFail && - (OffhandRiposteFail > 99 || (zone->random.Int(0, 100) < OffhandRiposteFail))) { - damage = 0; // Counts as a miss - slippery_attack = true; - } else - DoRiposte(other); - if (GetHP() < 0) - return false; - } - else - DoRiposte(other); - if (GetHP() < 0) - return false; - } - } - - if (((damage < 0) || slippery_attack) && !FromRiposte && !IsStrikethrough) { // Hack to still allow Strikethrough chance w/ Slippery Attacks AA - int32 bonusStrikeThrough = (itembonuses.StrikeThrough + spellbonuses.StrikeThrough + aabonuses.StrikeThrough); - if(bonusStrikeThrough && (zone->random.Int(0, 100) < bonusStrikeThrough)) { - Message_StringID(MT_StrikeThrough, STRIKETHROUGH_STRING); // You strike through your opponents defenses! - Attack(other, Hand, false, true); // Strikethrough only gives another attempted hit - return false; + } else { + damage = 0; } } + Log.Out(Logs::Detail, Logs::Combat, "Final damage after all reductions: %d", damage); } else damage = -5; // Hate Generation is on a per swing basis, regardless of a hit, miss, or block, its always the same. // If we are this far, this means we are atleast making a swing. - if (!FromRiposte) - other->AddToHateList(this, hate); + other->AddToHateList(this, hate); /////////////////////////////////////////////////////////// ////// Send Attack Damage @@ -5771,132 +5740,6 @@ float Bot::GetProcChances(float ProcBonus, uint16 hand) { return ProcChance; } -bool Bot::AvoidDamage(Mob* other, int32 &damage, bool CanRiposte) { - if(GetAppearance() == eaDead) - return false; - - float skill = 0; - float bonus = 0; - float RollTable[4] = {0,0,0,0}; - float roll = 0; - Mob *attacker = other; - Mob *defender = this; - - bool ghit = false; - if((attacker->GetSpellBonuses().MeleeSkillCheck + attacker->GetItemBonuses().MeleeSkillCheck) > 500) - ghit = true; - - if (IsEnraged() && !other->BehindMob(this, other->GetX(), other->GetY())) { - damage = -3; - Log.Out(Logs::Detail, Logs::Combat, "I am enraged, riposting frontal attack."); - } - - float riposte_chance = 0.0f; - if (CanRiposte && damage > 0 && CanThisClassRiposte() && !other->BehindMob(this, other->GetX(), other->GetY())) { - riposte_chance = ((100.0f + (float)defender->GetAABonuses().RiposteChance + (float)defender->GetSpellBonuses().RiposteChance + (float)defender->GetItemBonuses().RiposteChance) / 100.0f); - skill = GetSkill(SkillRiposte); - if (!ghit) { - bonus = (2.0 + skill / 60.0 + (GetDEX() / 200)); - bonus *= riposte_chance; - RollTable[0] = (bonus + (itembonuses.HeroicDEX / 25)); - } - } - - bool bBlockFromRear = false; - bool bShieldBlockFromRear = false; - if (this->IsBot()) { - int aaChance = 0; - int BlockBehindChance = (aabonuses.BlockBehind + spellbonuses.BlockBehind + itembonuses.BlockBehind); - if (BlockBehindChance && (BlockBehindChance > zone->random.Int(1, 100))){ - bBlockFromRear = true; - if (spellbonuses.BlockBehind || itembonuses.BlockBehind) - bShieldBlockFromRear = true; - } - } - - float block_chance = 0.0f; - if (damage > 0 && CanThisClassBlock() && (!other->BehindMob(this, other->GetX(), other->GetY()) || bBlockFromRear)) { - block_chance = ((100.0f + (float)spellbonuses.IncreaseBlockChance + (float)itembonuses.IncreaseBlockChance) / 100.0f); - skill = GetSkill(SkillBlock); - if (!ghit) { - bonus = (2.0 + skill / 35.0 + (GetDEX() / 200)); - RollTable[1] = (RollTable[0] + (bonus * block_chance) - riposte_chance); - block_chance *= bonus; - } - } else - RollTable[1] = RollTable[0]; - - if(damage > 0 && (aabonuses.ShieldBlock || spellbonuses.ShieldBlock || itembonuses.ShieldBlock) - && (!other->BehindMob(this, other->GetX(), other->GetY()) || bShieldBlockFromRear)) { - bool equiped = GetBotItem(MainSecondary); - if(equiped) { - uint8 shield = GetBotItem(MainSecondary)->GetItem()->ItemType; - float bonusShieldBlock = 0.0f; - if(shield == ItemTypeShield) { - bonusShieldBlock = (aabonuses.ShieldBlock + spellbonuses.ShieldBlock + itembonuses.ShieldBlock); - RollTable[1] = (RollTable[0] + bonusShieldBlock); - } - } - } - - if(damage > 0 && (aabonuses.TwoHandBluntBlock || spellbonuses.TwoHandBluntBlock || itembonuses.TwoHandBluntBlock) - && (!other->BehindMob(this, other->GetX(), other->GetY()) || bShieldBlockFromRear)) { - bool equiped2 = GetBotItem(MainPrimary); - if(equiped2) { - uint8 TwoHandBlunt = GetBotItem(MainPrimary)->GetItem()->ItemType; - float bonusStaffBlock = 0.0f; - if(TwoHandBlunt == ItemType2HBlunt) { - bonusStaffBlock = (aabonuses.TwoHandBluntBlock + spellbonuses.TwoHandBluntBlock + itembonuses.TwoHandBluntBlock); - RollTable[1] = (RollTable[0] + bonusStaffBlock); - } - } - } - - float parry_chance = 0.0f; - if (damage > 0 && CanThisClassParry() && !other->BehindMob(this, other->GetX(), other->GetY())) { - parry_chance = ((100.0f + (float)defender->GetSpellBonuses().ParryChance + (float)defender->GetItemBonuses().ParryChance) / 100.0f); - skill = GetSkill(SkillParry); - if (!ghit) { - bonus = (2.0 + skill / 60.0 + (GetDEX() / 200)); - bonus *= parry_chance; - RollTable[2] = (RollTable[1] + bonus - block_chance); - } - } else - RollTable[2] = (RollTable[1] - block_chance); - - float dodge_chance = 0.0f; - if (damage > 0 && CanThisClassDodge() && !other->BehindMob(this, other->GetX(), other->GetY())) { - dodge_chance = ((100.0f + (float)defender->GetSpellBonuses().DodgeChance + (float)defender->GetItemBonuses().DodgeChance) / 100.0f); - skill = GetSkill(SkillDodge); - if (!ghit) { - bonus = (2.0 + skill / 60.0 + (GetAGI() / 200)); - bonus *= dodge_chance; - RollTable[3] = (RollTable[2] + bonus - (itembonuses.HeroicDEX / 25) + (itembonuses.HeroicAGI / 25) - parry_chance); - } - } else { - RollTable[3] = (RollTable[2] - (itembonuses.HeroicDEX / 25) + (itembonuses.HeroicAGI / 25) - parry_chance); - } - - if(damage > 0) { - roll = zone->random.Real(0,100); - if(roll <= RollTable[0]) - damage = -3; - else if(roll <= RollTable[1]) - damage = -1; - else if(roll <= RollTable[2]) - damage = -2; - else if(roll <= RollTable[3]) - damage = -4; - } - - Log.Out(Logs::Detail, Logs::Combat, "Final damage after all avoidances: %d", damage); - - if (damage < 0) - return true; - - return false; -} - int Bot::GetMonkHandToHandDamage(void) { static int damage[66] = { // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @@ -5986,26 +5829,25 @@ void Bot::DoSpecialAttackDamage(Mob *who, SkillUseTypes skill, int32 max_damage, } min_damage += (min_damage * GetMeleeMinDamageMod_SE(skill) / 100); - if(HitChance && !who->CheckHitChance(this, skill, MainPrimary)) - max_damage = 0; - else { - bool CanRiposte = true; - if(skill == SkillThrowing || skill == SkillArchery) - CanRiposte = false; - - who->AvoidDamage(this, max_damage, CanRiposte); - who->MeleeMitigation(this, max_damage, min_damage); - - if(max_damage > 0) { + int hand = MainPrimary; + if (skill == SkillThrowing || skill == SkillArchery) + hand = MainRange; + if (who->AvoidDamage(this, max_damage, hand)) { + if (max_damage == -3) + DoRiposte(who); + } else { + if (HitChance || who->CheckHitChance(this, skill, MainPrimary)) { + who->MeleeMitigation(this, max_damage, min_damage); ApplyMeleeDamageBonus(skill, max_damage); max_damage += who->GetFcDamageAmtIncoming(this, 0, true, skill); max_damage += ((itembonuses.HeroicSTR / 10) + (max_damage * who->GetSkillDmgTaken(skill) / 100) + GetSkillDmgAmt(skill)); TryCriticalHit(who, skill, max_damage); + } else { + max_damage = 0; } } - if(max_damage >= 0) - who->AddToHateList(this, hate); + who->AddToHateList(this, hate); who->Damage(this, max_damage, SPELL_UNKNOWN, skill, false); @@ -6030,9 +5872,6 @@ void Bot::DoSpecialAttackDamage(Mob *who, SkillUseTypes skill, int32 max_damage, if (max_damage > 0 && HasSkillProcSuccess()) TrySkillProc(who, skill, (ReuseTime * 1000), true); - - if(max_damage == -3 && !(who->GetHP() <= 0)) - DoRiposte(who); } void Bot::TryBackstab(Mob *other, int ReuseTime) { diff --git a/zone/bot.h b/zone/bot.h index 86a1ef895..7f2652498 100644 --- a/zone/bot.h +++ b/zone/bot.h @@ -169,7 +169,6 @@ public: uint16 BotGetSpellType(int spellslot) { return AIspells[spellslot].type; } uint16 BotGetSpellPriority(int spellslot) { return AIspells[spellslot].priority; } virtual float GetProcChances(float ProcBonus, uint16 hand); - virtual bool AvoidDamage(Mob* other, int32 &damage, bool CanRiposte); virtual int GetMonkHandToHandDamage(void); virtual bool TryFinishingBlow(Mob *defender, SkillUseTypes skillinuse); virtual void DoRiposte(Mob* defender); @@ -314,7 +313,7 @@ public: virtual float GetAOERange(uint16 spell_id); virtual bool SpellEffect(Mob* caster, uint16 spell_id, float partial = 100); virtual void DoBuffTic(const Buffs_Struct &buff, int slot, Mob* caster = nullptr); - virtual bool CastSpell(uint16 spell_id, uint16 target_id, uint16 slot = USE_ITEM_SPELL_SLOT, int32 casttime = -1, int32 mana_cost = -1, uint32* oSpellWillFinish = 0, + virtual bool CastSpell(uint16 spell_id, uint16 target_id, uint16 slot = USE_ITEM_SPELL_SLOT, int32 casttime = -1, int32 mana_cost = -1, uint32* oSpellWillFinish = 0, uint32 item_slot = 0xFFFFFFFF, int16 *resist_adjust = nullptr, uint32 aa_id = 0); virtual bool SpellOnTarget(uint16 spell_id, Mob* spelltar); virtual bool IsImmuneToSpell(uint16 spell_id, Mob *caster); diff --git a/zone/mob.h b/zone/mob.h index 4cb4ef8dd..6a696256f 100644 --- a/zone/mob.h +++ b/zone/mob.h @@ -149,7 +149,7 @@ public: int MonkSpecialAttack(Mob* other, uint8 skill_used); virtual void TryBackstab(Mob *other,int ReuseTime = 10); void TriggerDefensiveProcs(const ItemInst* weapon, Mob *on, uint16 hand = MainPrimary, int damage = 0); - virtual bool AvoidDamage(Mob* attacker, int32 &damage, bool CanRiposte = true); + bool AvoidDamage(Mob* attacker, int32 &damage, int hand); virtual bool CheckHitChance(Mob* attacker, SkillUseTypes skillinuse, int Hand, int16 chance_mod = 0); virtual void TryCriticalHit(Mob *defender, uint16 skill, int32 &damage, ExtraAttackOptions *opts = nullptr); void TryPetCriticalHit(Mob *defender, uint16 skill, int32 &damage); diff --git a/zone/special_attacks.cpp b/zone/special_attacks.cpp index bd8000e63..1bcccf272 100644 --- a/zone/special_attacks.cpp +++ b/zone/special_attacks.cpp @@ -130,22 +130,19 @@ void Mob::DoSpecialAttackDamage(Mob *who, SkillUseTypes skill, int32 max_damage, min_damage += min_damage * GetMeleeMinDamageMod_SE(skill) / 100; - if(HitChance && !who->CheckHitChance(this, skill, MainPrimary)) - max_damage = 0; - - else{ - bool CanRiposte = true; - if(skill == SkillThrowing || skill == SkillArchery) // changed from '&&' - CanRiposte = false; - - if (CanAvoid) - who->AvoidDamage(this, max_damage, CanRiposte); - - who->MeleeMitigation(this, max_damage, min_damage); - - if(max_damage > 0) + int hand = MainPrimary; // Avoid checks hand for throwing/archery exclusion, primary should work for most + if (skill == SkillThrowing || skill == SkillArchery) + hand = MainRange; + if (who->AvoidDamage(this, max_damage, hand)) { + if (max_damage == -3) + DoRiposte(who); + } else { + if (HitChance || who->CheckHitChance(this, skill, MainPrimary)) { + who->MeleeMitigation(this, max_damage, min_damage); CommonOutgoingHitSuccess(who, max_damage, skill); - + } else { + max_damage = 0; + } } who->AddToHateList(this, hate, 0, false); @@ -168,8 +165,6 @@ void Mob::DoSpecialAttackDamage(Mob *who, SkillUseTypes skill, int32 max_damage, if (max_damage > 0 && HasSkillProcSuccess()) TrySkillProc(who, skill, ReuseTime*1000, true); - if(max_damage == -3 && !who->HasDied()) - DoRiposte(who); } @@ -972,7 +967,7 @@ void Mob::DoArcheryAttackDmg(Mob* other, const ItemInst* RangeWeapon, const Ite } if (!HeadShot) - other->AvoidDamage(this, TotalDmg, false); + other->AvoidDamage(this, TotalDmg, MainRange); other->MeleeMitigation(this, TotalDmg, minDmg); if(TotalDmg > 0){ @@ -1305,7 +1300,7 @@ void NPC::DoRangedAttackDmg(Mob* other, bool Launch, int16 damage_mod, int16 cha TotalDmg += TotalDmg * damage_mod / 100; - other->AvoidDamage(this, TotalDmg, false); + other->AvoidDamage(this, TotalDmg, MainRange); other->MeleeMitigation(this, TotalDmg, MinDmg); if (TotalDmg > 0) @@ -1539,7 +1534,7 @@ void Mob::DoThrowingAttackDmg(Mob* other, const ItemInst* RangeWeapon, const Ite Log.Out(Logs::Detail, Logs::Combat, "Item DMG %d. Max Damage %d. Hit for damage %d", WDmg, MaxDmg, TotalDmg); if (!Assassinate_Dmg) - other->AvoidDamage(this, TotalDmg, false); //CanRiposte=false - Can not riposte throw attacks. + other->AvoidDamage(this, TotalDmg, MainRange); other->MeleeMitigation(this, TotalDmg, minDmg); if(TotalDmg > 0) @@ -2411,27 +2406,26 @@ void Mob::DoMeleeSkillAttackDmg(Mob* other, uint16 weapon_damage, SkillUseTypes else damage = zone->random.Int(min_hit, max_hit); - if(!other->CheckHitChance(this, skillinuse, Hand, chance_mod)) { - damage = 0; + if (other->AvoidDamage(this, damage, CanRiposte ? MainRange : MainPrimary)) { // MainRange excludes ripo, primary doesn't have any extra behavior + if (damage == -3) { + DoRiposte(other); + if (HasDied()) + return; + } } else { - other->AvoidDamage(this, damage, CanRiposte); - other->MeleeMitigation(this, damage, min_hit); - if(damage > 0) + if (other->CheckHitChance(this, skillinuse, Hand, chance_mod)) { + other->MeleeMitigation(this, damage, min_hit); CommonOutgoingHitSuccess(other, damage, skillinuse); + } else { + damage = 0; + } } - if (damage == -3) { - DoRiposte(other); - if (HasDied()) - return; - } } else damage = -5; - other->AddToHateList(this, hate); - bool CanSkillProc = true; if (skillinuse == SkillOffense){ //Hack to allow damage to display. skillinuse = SkillTigerClaw; //'strike' your opponent - Arbitrary choice for message.