From 3992ac02bbf589043700f402aa3332781aa55b03 Mon Sep 17 00:00:00 2001 From: KimLS Date: Mon, 8 Jul 2013 14:37:01 -0700 Subject: [PATCH] Rampage, Area Rampage, Flurry got new customizable effects. Part of that was adding a new set of stuff to attack. --- zone/MobAI.cpp | 175 +++++++++++++++++---- zone/attack.cpp | 66 ++++++-- zone/beacon.h | 3 +- zone/bot.cpp | 328 ++------------------------------------- zone/bot.h | 5 +- zone/client.h | 3 +- zone/common.h | 19 ++- zone/corpse.h | 3 +- zone/hate_list.cpp | 4 +- zone/hate_list.h | 2 +- zone/lua_mob.cpp | 86 +++++++++- zone/lua_mob.h | 3 + zone/merc.cpp | 4 +- zone/merc.h | 3 +- zone/mob.cpp | 69 ++++++-- zone/mob.h | 22 +-- zone/npc.h | 3 +- zone/special_attacks.cpp | 2 +- 18 files changed, 400 insertions(+), 400 deletions(-) diff --git a/zone/MobAI.cpp b/zone/MobAI.cpp index 903b4883f..5b011c722 100644 --- a/zone/MobAI.cpp +++ b/zone/MobAI.cpp @@ -1107,14 +1107,7 @@ void Mob::AI_Process() { } } - if (GetHPRatio() < (RuleI(NPC, StartEnrageValue)+1) && - (!RuleB(NPC, LiveLikeEnrage) || - (RuleB(NPC, LiveLikeEnrage) && - ((IsPet() && !IsCharmed() && GetOwner() && GetOwner()->IsClient()) || - (CastToNPC()->GetSwarmOwner() && entity_list.GetMob(CastToNPC()->GetSwarmOwner())->IsClient()))))) - { - StartEnrage(); - } + StartEnrage(); bool is_combat_range = CombatRange(target); @@ -1183,13 +1176,42 @@ void Mob::AI_Process() { } if (GetSpecialAbility(SPECATK_FLURRY)) { + int flurry_chance = GetSpecialAbilityParam(SPECATK_FLURRY, 0); + flurry_chance = flurry_chance > 0 ? flurry_chance : RuleI(Combat, NPCFlurryChance); - uint8 npc_flurry = RuleI(Combat, NPCFlurryChance); - if (GetFlurryChance()) - npc_flurry = GetFlurryChance(); + ExtraAttackOptions opts; + int cur = GetSpecialAbilityParam(SPECATK_FLURRY, 1); + if(cur > 0) { + opts.damage_percent = cur / 100.0f; + } - if (MakeRandomInt(0, 99) < npc_flurry) - Flurry(); + cur = GetSpecialAbilityParam(SPECATK_FLURRY, 2); + if(cur > 0) { + opts.damage_flat = cur; + } + + cur = GetSpecialAbilityParam(SPECATK_FLURRY, 3); + if(cur > 0) { + opts.armor_pen_percent = cur / 100.0f; + } + + cur = GetSpecialAbilityParam(SPECATK_FLURRY, 4); + if(cur > 0) { + opts.armor_pen_flat = cur; + } + + cur = GetSpecialAbilityParam(SPECATK_FLURRY, 5); + if(cur > 0) { + opts.crit_percent = cur / 100.0f; + } + + cur = GetSpecialAbilityParam(SPECATK_FLURRY, 6); + if(cur > 0) { + opts.crit_flat = cur; + } + + if (MakeRandomInt(0, 99) < flurry_chance) + Flurry(&opts); } if (IsPet()) { @@ -1200,23 +1222,87 @@ void Mob::AI_Process() { int16 flurry_chance = owner->aabonuses.PetFlurry + owner->spellbonuses.PetFlurry + owner->itembonuses.PetFlurry; if (flurry_chance && (MakeRandomInt(0, 99) < flurry_chance)) - Flurry(); + Flurry(nullptr); } } if (GetSpecialAbility(SPECATK_RAMPAGE)) { - //simply based off dex for now, probably a better calc - if(MakeRandomInt(0, 100) < ((int)(GetDEX() / ((GetLevel() * 0.760) + 10.0)) + 5)) - Rampage(); + int rampage_chance = GetSpecialAbilityParam(SPECATK_RAMPAGE, 0); + rampage_chance = rampage_chance > 0 ? rampage_chance : 20; + if(MakeRandomInt(0, 99) < rampage_chance) { + ExtraAttackOptions opts; + int cur = GetSpecialAbilityParam(SPECATK_RAMPAGE, 1); + if(cur > 0) { + opts.damage_percent = cur / 100.0f; + } + + cur = GetSpecialAbilityParam(SPECATK_RAMPAGE, 2); + if(cur > 0) { + opts.damage_flat = cur; + } + + cur = GetSpecialAbilityParam(SPECATK_RAMPAGE, 3); + if(cur > 0) { + opts.armor_pen_percent = cur / 100.0f; + } + + cur = GetSpecialAbilityParam(SPECATK_RAMPAGE, 4); + if(cur > 0) { + opts.armor_pen_flat = cur; + } + + cur = GetSpecialAbilityParam(SPECATK_RAMPAGE, 5); + if(cur > 0) { + opts.crit_percent = cur / 100.0f; + } + + cur = GetSpecialAbilityParam(SPECATK_RAMPAGE, 6); + if(cur > 0) { + opts.crit_flat = cur; + } + Rampage(&opts); + } } if (GetSpecialAbility(SPECATK_AREA_RAMPAGE)) { + int rampage_chance = GetSpecialAbilityParam(SPECATK_AREA_RAMPAGE, 0); + rampage_chance = rampage_chance > 0 ? rampage_chance : 20; + if(MakeRandomInt(0, 99) < rampage_chance) { + ExtraAttackOptions opts; + int cur = GetSpecialAbilityParam(SPECATK_AREA_RAMPAGE, 1); + if(cur > 0) { + opts.damage_percent = cur / 100.0f; + } - //simply based off dex for now, probably a better calc - if(MakeRandomInt(0, 100) < ((int)(GetDEX() / ((GetLevel() * 0.760) + 10.0)) + 5)) - AreaRampage(); + cur = GetSpecialAbilityParam(SPECATK_AREA_RAMPAGE, 2); + if(cur > 0) { + opts.damage_flat = cur; + } + + cur = GetSpecialAbilityParam(SPECATK_AREA_RAMPAGE, 3); + if(cur > 0) { + opts.armor_pen_percent = cur / 100.0f; + } + + cur = GetSpecialAbilityParam(SPECATK_AREA_RAMPAGE, 4); + if(cur > 0) { + opts.armor_pen_flat = cur; + } + + cur = GetSpecialAbilityParam(SPECATK_AREA_RAMPAGE, 5); + if(cur > 0) { + opts.crit_percent = cur / 100.0f; + } + + cur = GetSpecialAbilityParam(SPECATK_AREA_RAMPAGE, 6); + if(cur > 0) { + opts.crit_flat = cur; + } + + AreaRampage(&opts); + } } } @@ -1488,7 +1574,11 @@ void Mob::AI_Process() { //Do Ranged attack here if(doranged) { - RangedAttack(target); + int attacks = GetSpecialAbilityParam(SPECATK_RANGED_ATK, 0); + attacks = attacks > 0 ? attacks : 1; + for(int i = 0; i < attacks; ++i) { + RangedAttack(target); + } } } @@ -1865,11 +1955,24 @@ void Mob::StartEnrage() if(!GetSpecialAbility(SPECATK_ENRAGE)) return; + int hp_ratio = GetSpecialAbilityParam(SPECATK_ENRAGE, 0); + hp_ratio = hp_ratio > 0 ? hp_ratio : RuleI(NPC, StartEnrageValue); + if(GetHPRatio() > static_cast(hp_ratio)) { + return; + } + + if(RuleB(NPC, LiveLikeEnrage) && !((IsPet() && !IsCharmed() && GetOwner() && GetOwner()->IsClient()) || + (CastToNPC()->GetSwarmOwner() && entity_list.GetMob(CastToNPC()->GetSwarmOwner())->IsClient()))) { + return; + } + Timer *timer = GetSpecialAbilityTimer(SPECATK_ENRAGE); if (timer && !timer->Check()) return; - StartSpecialAbilityTimer(SPECATK_ENRAGE, EnragedDurationTimer); + int enraged_duration = GetSpecialAbilityParam(SPECATK_ENRAGE, 1); + enraged_duration = enraged_duration > 0 ? enraged_duration : EnragedDurationTimer; + StartSpecialAbilityTimer(SPECATK_ENRAGE, enraged_duration); // start the timer. need to call IsEnraged frequently since we dont have callback timers :-/ bEnraged = true; @@ -1881,7 +1984,10 @@ void Mob::ProcessEnrage(){ Timer *timer = GetSpecialAbilityTimer(SPECATK_ENRAGE); if(timer && timer->Check()){ entity_list.MessageClose_StringID(this, true, 200, MT_NPCEnrage, NPC_ENRAGE_END, GetCleanName()); - StartSpecialAbilityTimer(SPECATK_ENRAGE, EnragedTimer); + + int enraged_cooldown = GetSpecialAbilityParam(SPECATK_ENRAGE, 2); + enraged_cooldown = enraged_cooldown > 0 ? enraged_cooldown : EnragedTimer; + StartSpecialAbilityTimer(SPECATK_ENRAGE, enraged_cooldown); bEnraged = false; } } @@ -1892,7 +1998,7 @@ bool Mob::IsEnraged() return bEnraged; } -bool Mob::Flurry() +bool Mob::Flurry(ExtraAttackOptions *opts) { // this is wrong, flurry is extra attacks on the current target Mob *target = GetTarget(); @@ -1903,7 +2009,7 @@ bool Mob::Flurry() entity_list.MessageClose_StringID(this, true, 200, MT_PetFlurry, NPC_FLURRY, GetCleanName(), target->GetCleanName()); } for (int i = 0; i < RuleI(Combat, MaxFlurryHits); i++) - Attack(target); + Attack(target, 13, false, false, false, opts); } return true; } @@ -1931,7 +2037,7 @@ void Mob::ClearRampage(){ RampageArray.clear(); } -bool Mob::Rampage() +bool Mob::Rampage(ExtraAttackOptions *opts) { int index_hit = 0; if (!IsPet()) { @@ -1951,17 +2057,19 @@ bool Mob::Rampage() continue; if (CombatRange(m_target)) { - Attack(m_target); + Attack(m_target, 13, false, false, false, opts); index_hit++; } } } - if(index_hit < RuleI(Combat, MaxRampageTargets)) - Attack(GetTarget()); + + if(index_hit < RuleI(Combat, MaxRampageTargets)) { + Attack(GetTarget(), 13, false, false, false, opts); + } return true; } -void Mob::AreaRampage() +void Mob::AreaRampage(ExtraAttackOptions *opts) { int index_hit = 0; if (!IsPet()) { // do not know every pet AA so thought it safer to add this @@ -1969,10 +2077,11 @@ void Mob::AreaRampage() } else { entity_list.MessageClose_StringID(this, true, 200, MT_PetFlurry, AE_RAMPAGE, GetCleanName()); } - index_hit = hate_list.AreaRampage(this, GetTarget()); + index_hit = hate_list.AreaRampage(this, GetTarget(), opts); - if(index_hit == 0) - Attack(GetTarget()); + if(index_hit == 0) { + Attack(GetTarget(), 13, false, false, false, opts); + } } uint32 Mob::GetLevelCon(uint8 mylevel, uint8 iOtherLevel) { diff --git a/zone/attack.cpp b/zone/attack.cpp index cfd87d112..3abbf243b 100644 --- a/zone/attack.cpp +++ b/zone/attack.cpp @@ -543,7 +543,7 @@ bool Mob::AvoidDamage(Mob* other, int32 &damage, bool CanRiposte) return false; } -void Mob::MeleeMitigation(Mob *attacker, int32 &damage, int32 minhit) +void Mob::MeleeMitigation(Mob *attacker, int32 &damage, int32 minhit, ExtraAttackOptions *opts) { if(damage <= 0) return; @@ -582,6 +582,11 @@ void Mob::MeleeMitigation(Mob *attacker, int32 &damage, int32 minhit) armor += spellbonuses.AC + itembonuses.AC + 1; } + if(opts) { + armor *= (1.0f - opts->armor_pen_percent); + armor -= opts->armor_pen_flat; + } + if(GetClass() == WIZARD || GetClass() == MAGICIAN || GetClass() == NECROMANCER || GetClass() == ENCHANTER) { softcap = RuleI(Combat, ClothACSoftcap); @@ -708,6 +713,11 @@ void Mob::MeleeMitigation(Mob *attacker, int32 &damage, int32 minhit) // Scorpious2k: Include AC in the calculation // use serverop variables to set values int myac = GetAC(); + if(opts) { + myac *= (1.0f - opts->armor_pen_percent); + myac -= opts->armor_pen_flat; + } + if (damage > 0 && myac > 0) { int acfail=1000; char tmp[10]; @@ -1092,7 +1102,7 @@ int Mob::GetWeaponDamage(Mob *against, const ItemInst *weapon_item, uint32 *hate //note: throughout this method, setting `damage` to a negative is a way to //stop the attack calculations // IsFromSpell added to allow spell effects to use Attack. (Mainly for the Rampage AA right now.) -bool Client::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, bool IsFromSpell) +bool Client::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, bool IsFromSpell, ExtraAttackOptions *opts) { _ZP(Client_Attack); @@ -1243,17 +1253,24 @@ bool Client::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, b mlog(COMBAT__DAMAGE, "Damage calculated to %d (min %d, max %d, str %d, skill %d, DMG %d, lv %d)", damage, min_hit, max_hit, GetSTR(), GetSkill(skillinuse), weapon_damage, mylevel); + if(opts) { + damage *= opts->damage_percent; + damage += opts->damage_flat; + hate *= opts->hate_percent; + hate += opts->hate_flat; + } + //check to see if we hit.. if(!other->CheckHitChance(this, skillinuse, Hand)) { mlog(COMBAT__ATTACKS, "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); + other->MeleeMitigation(this, damage, min_hit, opts); if(damage > 0) { ApplyMeleeDamageBonus(skillinuse, damage); damage += (itembonuses.HeroicSTR / 10) + (damage * other->GetSkillDmgTaken(skillinuse) / 100) + GetSkillDmgAmt(skillinuse); - TryCriticalHit(other, skillinuse, damage); + TryCriticalHit(other, skillinuse, damage, opts); } mlog(COMBAT__DAMAGE, "Final damage after all reductions: %d", damage); } @@ -1297,7 +1314,6 @@ bool Client::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, b 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. @@ -1701,7 +1717,7 @@ bool Client::Death(Mob* killerMob, int32 damage, uint16 spell, SkillType attack_ return true; } -bool NPC::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, bool IsFromSpell) // Kaiyodo - base function has changed prototype, need to update overloaded version +bool NPC::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, bool IsFromSpell, ExtraAttackOptions *opts) { _ZP(NPC_Attack); int damage = 0; @@ -1858,25 +1874,39 @@ bool NPC::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, bool { hate = hate * 100 / GetDamageTable(skillinuse); } - //THIS IS WHERE WE CHECK TO SEE IF WE HIT: + if(other->IsClient() && other->CastToClient()->IsSitting()) { mlog(COMBAT__DAMAGE, "Client %s is sitting. Hitting for max damage (%d).", other->GetName(), (max_dmg+eleBane)); damage = (max_dmg+eleBane); damage += (itembonuses.HeroicSTR / 10) + (damage * other->GetSkillDmgTaken(skillinuse) / 100) + GetSkillDmgAmt(skillinuse); + if(opts) { + damage *= opts->damage_percent; + damage += opts->damage_flat; + hate *= opts->hate_percent; + hate += opts->hate_flat; + } + mlog(COMBAT__HITS, "Generating hate %d towards %s", hate, GetName()); // now add done damage to the hate list other->AddToHateList(this, hate); } else { + if(opts) { + damage *= opts->damage_percent; + damage += opts->damage_flat; + hate *= opts->hate_percent; + hate += opts->hate_flat; + } + if(!other->CheckHitChance(this, skillinuse, Hand)) { damage = 0; //miss } else { //hit, check for damage avoidance other->AvoidDamage(this, damage); - other->MeleeMitigation(this, damage, min_dmg+eleBane); + other->MeleeMitigation(this, damage, min_dmg+eleBane, opts); if(damage > 0) { ApplyMeleeDamageBonus(skillinuse, damage); damage += (itembonuses.HeroicSTR / 10) + (damage * other->GetSkillDmgTaken(skillinuse) / 100) + GetSkillDmgAmt(skillinuse); - TryCriticalHit(other, skillinuse, damage); + TryCriticalHit(other, skillinuse, damage, opts); } mlog(COMBAT__HITS, "Generating hate %d towards %s", hate, GetName()); // now add done damage to the hate list @@ -2433,9 +2463,16 @@ void Mob::AddToHateList(Mob* other, int32 hate, int32 damage, bool iYellForHelp, return; if(GetSpecialAbility(NPC_TUNNELVISION)) { + int tv_mod = GetSpecialAbilityParam(NPC_TUNNELVISION, 0); + Mob *top = GetTarget(); if(top && top != other) { - hate *= RuleR(Aggro, TunnelVisionAggroMod); + if(tv_mod) { + float tv = tv_mod / 100.0f; + hate *= tv; + } else { + hate *= RuleR(Aggro, TunnelVisionAggroMod); + } } } @@ -4030,7 +4067,7 @@ void Mob::TryPetCriticalHit(Mob *defender, uint16 skill, int32 &damage) } } -void Mob::TryCriticalHit(Mob *defender, uint16 skill, int32 &damage) +void Mob::TryCriticalHit(Mob *defender, uint16 skill, int32 &damage, ExtraAttackOptions *opts) { if(damage < 1) return; @@ -4113,7 +4150,12 @@ void Mob::TryCriticalHit(Mob *defender, uint16 skill, int32 &damage) critChance += critChance*(float)CritChanceBonus /100.0f; } - if(critChance > 0){ + if(opts) { + critChance *= opts->crit_percent; + critChance += opts->crit_flat; + } + + if(critChance > 0) { critChance /= 100; diff --git a/zone/beacon.h b/zone/beacon.h index 942c2e0d8..071bb8cc8 100644 --- a/zone/beacon.h +++ b/zone/beacon.h @@ -33,7 +33,8 @@ public: //abstract virtual function implementations requird by base abstract class virtual bool Death(Mob* killerMob, int32 damage, uint16 spell_id, SkillType attack_skill) { return true; } virtual void Damage(Mob* from, int32 damage, uint16 spell_id, SkillType attack_skill, bool avoidable = true, int8 buffslot = -1, bool iBuffTic = false) { return; } - virtual bool Attack(Mob* other, int Hand = 13, bool FromRiposte = false, bool IsStrikethrough = false, bool IsFromSpell = false) { return false; } + virtual bool Attack(Mob* other, int Hand = 13, bool FromRiposte = false, bool IsStrikethrough = false, bool IsFromSpell = false, + ExtraAttackOptions *opts = nullptr) { return false; } virtual bool HasRaid() { return false; } virtual bool HasGroup() { return false; } virtual Raid* GetRaid() { return 0; } diff --git a/zone/bot.cpp b/zone/bot.cpp index 86d19af41..6a3a16d7e 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -3337,7 +3337,7 @@ void Bot::DoMeleeSkillAttackDmg(Mob* other, uint16 weapon_damage, SkillType skil ApplyMeleeDamageBonus(skillinuse, damage); damage += other->GetAdditionalDamage(this, 0, true, skillinuse); damage += (itembonuses.HeroicSTR / 10) + (damage * other->GetSkillDmgTaken(skillinuse) / 100) + GetSkillDmgAmt(skillinuse); - TryCriticalHit(other, skillinuse, damage); + TryCriticalHit(other, skillinuse, damage, opts); } } @@ -6365,7 +6365,7 @@ void Bot::AddToHateList(Mob* other, int32 hate, int32 damage, bool iYellForHelp, Mob::AddToHateList(other, hate, damage, iYellForHelp, bFrenzy, iBuffTic); } -bool Bot::Attack(Mob* other, int Hand, bool FromRiposte, bool IsStrikethrough, bool IsFromSpell) +bool Bot::Attack(Mob* other, int Hand, bool FromRiposte, bool IsStrikethrough, bool IsFromSpell, ExtraAttackOptions *opts) { _ZP(Bot_Attack); @@ -6520,6 +6520,13 @@ bool Bot::Attack(Mob* other, int Hand, bool FromRiposte, bool IsStrikethrough, b mlog(COMBAT__DAMAGE, "Damage calculated to %d (min %d, max %d, str %d, skill %d, DMG %d, lv %d)", damage, min_hit, max_hit, GetSTR(), GetSkill(skillinuse), weapon_damage, GetLevel()); + if(opts) { + damage *= opts->damage_percent; + damage += opts->damage_flat; + hate *= opts->hate_percent; + hate += opts->hate_flat; + } + //check to see if we hit.. if(!other->CheckHitChance(other, skillinuse, Hand)) { mlog(COMBAT__ATTACKS, "Attack missed. Damage set to 0."); @@ -6527,11 +6534,11 @@ bool Bot::Attack(Mob* other, int Hand, bool FromRiposte, bool IsStrikethrough, b other->AddToHateList(this, 0); } else { //we hit, try to avoid it other->AvoidDamage(this, damage); - other->MeleeMitigation(this, damage, min_hit); + other->MeleeMitigation(this, damage, min_hit, opts); if(damage > 0) { ApplyMeleeDamageBonus(skillinuse, damage); damage += (itembonuses.HeroicSTR / 10) + (damage * other->GetSkillDmgTaken(skillinuse) / 100) + GetSkillDmgAmt(skillinuse); - TryCriticalHit(other, skillinuse, damage); + TryCriticalHit(other, skillinuse, damage, opts); mlog(COMBAT__HITS, "Generating hate %d towards %s", hate, GetCleanName()); // now add done damage to the hate list //other->AddToHateList(this, hate); @@ -8001,117 +8008,6 @@ int Bot::GetMonkHandToHandDamage(void) return damage[Level]; } -void Bot::TryCriticalHit(Mob *defender, uint16 skill, int32 &damage) -{ - bool slayUndeadCrit = false; - - if(damage < 1) //We can't critical hit if we don't hit. - return; - - float critChance = 0.0f; - - //1: Try Slay Undead - if(defender && defender->GetBodyType() == BT_Undead || defender->GetBodyType() == BT_SummonedUndead || defender->GetBodyType() == BT_Vampire){ - - int16 SlayRateBonus = aabonuses.SlayUndead[0] + itembonuses.SlayUndead[0] + spellbonuses.SlayUndead[0]; - - if (SlayRateBonus) { - - critChance += (float(SlayRateBonus)/100.0f); - critChance /= 100.0f; - - if(MakeRandomFloat(0, 1) < critChance){ - int16 SlayDmgBonus = aabonuses.SlayUndead[1] + itembonuses.SlayUndead[1] + spellbonuses.SlayUndead[1]; - damage = (damage*SlayDmgBonus*2.25)/100; - entity_list.MessageClose(this, false, 200, MT_CritMelee, "%s cleanses %s target!(%d)", GetCleanName(), this->GetGender() == 0 ? "his" : this->GetGender() == 1 ? "her" : "its", damage); - return; - } - } - } - - //2: Try Melee Critical - - //Base critical rate for all classes is dervived from DEX stat, this rate is then augmented - //by item,spell and AA bonuses allowing you a chance to critical hit. If the following rules - //are defined you will have an innate chance to hit at Level 1 regardless of bonuses. - //Warning: Do not define these rules if you want live like critical hits. - critChance += RuleI(Combat, MeleeBaseCritChance); - - critChance += RuleI(Combat, ClientBaseCritChance); - - if(((GetClass() == WARRIOR || GetClass() == BERSERKER) && GetLevel() >= 12)) - { - if((GetHPRatio() < 30) && GetClass() == BERSERKER){ - critChance += RuleI(Combat, BerserkBaseCritChance); - berserk = true; - } - else - critChance += RuleI(Combat, WarBerBaseCritChance); - } - - if(skill == ARCHERY && GetClass() == RANGER && GetSkill(ARCHERY) >= 65) - critChance += 6; - - if(skill == THROWING && GetClass() == ROGUE && GetSkill(THROWING) >= 65) - critChance += 6; - - int CritChanceBonus = GetCriticalChanceBonus(skill); - - if (CritChanceBonus || critChance) { - - //Get Base CritChance from Dex. (200 = ~1.6%, 255 = ~2.0%, 355 = ~2.20%) Fall off rate > 255 - //http://giline.versus.jp/shiden/su.htm , http://giline.versus.jp/shiden/damage_e.htm - if (GetDEX() <= 255) - critChance += (float(GetDEX()) / 125.0f); - else if (GetDEX() > 255) - critChance += (float(GetDEX()-255)/ 500.0f) + 2.0f; - critChance += critChance*(float)CritChanceBonus /100.0f; - } - - if(critChance > 0){ - - critChance /= 100; - - if(MakeRandomFloat(0, 1) < critChance) - { - uint16 critMod = 200; - bool crip_success = false; - int16 CripplingBlowChance = GetCrippBlowChance(); - - //Crippling Blow Chance: The percent value of the effect is applied - //to the your Chance to Critical. (ie You have 10% chance to critical and you - //have a 200% Chance to Critical Blow effect, therefore you have a 20% Chance to Critical Blow. - if (CripplingBlowChance){ - critChance *= float(CripplingBlowChance)/100.0f; - - if(MakeRandomFloat(0, 1) < critChance){ - critMod = 400; - crip_success = true; - } - } - - critMod += GetCritDmgMob(skill) * 2; // To account for base crit mod being 200 not 100 - damage = damage * critMod / 100; - - if(berserk || crip_success) - { - entity_list.MessageClose_StringID(this, false, 200, MT_CritMelee, CRIPPLING_BLOW, GetCleanName(), itoa(damage)); - // Crippling blows also have a chance to stun - //Kayen: Crippling Blow would cause a chance to interrupt for npcs < 55, with a staggers message. - if (defender->GetLevel() <= 55 && !defender->GetSpecialAbility(IMMUNE_STUN)){ - defender->Emote("staggers."); - defender->Stun(0); - } - } - - else - { - entity_list.MessageClose_StringID(this, false, 200, MT_CritMelee, CRITICAL_HIT, GetCleanName(), itoa(damage)); - } - } - } -} - bool Bot::TryFinishingBlow(Mob *defender, SkillType skillinuse) { if (!defender) @@ -8169,208 +8065,6 @@ void Bot::DoRiposte(Mob* defender) { } } -void Bot::MeleeMitigation(Mob *attacker, int32 &damage, int32 minhit) -{ - if(damage <= 0) - return; - - Mob* defender = this; - float aa_mit = 0; - - aa_mit = (aabonuses.CombatStability + itembonuses.CombatStability + spellbonuses.CombatStability)/100.0f; - - if(RuleB(Combat, UseIntervalAC)) - { - float softcap = 0.0; - float mitigation_rating = 0.0; - float attack_rating = 0.0; - int shield_ac = 0; - int armor; - float weight = 0.0; - if(IsBot()) - { - armor = GetRawACNoShield(shield_ac); - weight = (CalcCurrentWeight() / 10.0); - } - - if(GetClass() == WIZARD || GetClass() == MAGICIAN || GetClass() == NECROMANCER || GetClass() == ENCHANTER) - { - softcap = RuleI(Combat, ClothACSoftcap); - } - else if(GetClass() == MONK && weight <= 15.0) - { - softcap = RuleI(Combat, MonkACSoftcap); - } - else if(GetClass() == DRUID || GetClass() == BEASTLORD || GetClass() == MONK) - { - softcap = RuleI(Combat, LeatherACSoftcap); - } - else if(GetClass() == SHAMAN || GetClass() == ROGUE || GetClass() == BERSERKER || GetClass() == RANGER) - { - softcap = RuleI(Combat, ChainACSoftcap); - } - else - { - softcap = RuleI(Combat, PlateACSoftcap); - } - - softcap += shield_ac; - armor += shield_ac; - softcap += (softcap * (aa_mit * RuleR(Combat, AAMitigationACFactor))); - if(armor > softcap) - { - int softcap_armor = armor - softcap; - if(GetClass() == WARRIOR) - { - softcap_armor = softcap_armor * RuleR(Combat, WarriorACSoftcapReturn); - } - else if(GetClass() == SHADOWKNIGHT || GetClass() == PALADIN || (GetClass() == MONK && weight <= 15.0)) - { - softcap_armor = softcap_armor * RuleR(Combat, KnightACSoftcapReturn); - } - else if(GetClass() == CLERIC || GetClass() == BARD || GetClass() == BERSERKER || GetClass() == ROGUE || GetClass() == SHAMAN || GetClass() == MONK) - { - softcap_armor = softcap_armor * RuleR(Combat, LowPlateChainACSoftcapReturn); - } - else if(GetClass() == RANGER || GetClass() == BEASTLORD) - { - softcap_armor = softcap_armor * RuleR(Combat, LowChainLeatherACSoftcapReturn); - } - else if(GetClass() == WIZARD || GetClass() == MAGICIAN || GetClass() == NECROMANCER || GetClass() == ENCHANTER || GetClass() == DRUID) - { - softcap_armor = softcap_armor * RuleR(Combat, CasterACSoftcapReturn); - } - else - { - softcap_armor = softcap_armor * RuleR(Combat, MiscACSoftcapReturn); - } - armor = softcap + softcap_armor; - } - - mitigation_rating = 0.0; - if(GetClass() == WIZARD || GetClass() == MAGICIAN || GetClass() == NECROMANCER || GetClass() == ENCHANTER) - { - mitigation_rating = ((GetSkill(DEFENSE) + itembonuses.HeroicAGI/10) / 4.0) + armor + 1; - } - else - { - mitigation_rating = ((GetSkill(DEFENSE) + itembonuses.HeroicAGI/10) / 3.0) + (armor * 1.333333) + 1; - } - mitigation_rating *= 0.847; - - if(attacker->IsClient()) - { - attack_rating = (attacker->CastToClient()->GetATK() + ((attacker->GetSTR()-66) * 0.9) + (attacker->GetSkill(OFFENSE)*1.345)); - } - else if(attacker->IsBot()) - { - attack_rating = (attacker->CastToBot()->CalcATK() + ((attacker->GetSTR()-66) * 0.9) + (attacker->GetSkill(OFFENSE)*1.345)); - } - else - { - attack_rating = (attacker->GetATK() + (attacker->GetSkill(OFFENSE)*1.345) + ((attacker->GetSTR()-66) * 0.9)); - } - - float d = 10.0; - float mit_roll = MakeRandomFloat(0, mitigation_rating); - float atk_roll = MakeRandomFloat(0, attack_rating); - - if(atk_roll > mit_roll) - { - float a_diff = (atk_roll - mit_roll); - float thac0 = attack_rating * RuleR(Combat, ACthac0Factor); - float thac0cap = ((attacker->GetLevel() * 9) + 20); - if(thac0 > thac0cap) - { - thac0 = thac0cap; - } - d -= 10.0 * (a_diff / thac0); - } - else if(mit_roll > atk_roll) - { - float m_diff = (mit_roll - atk_roll); - float thac20 = mitigation_rating * RuleR(Combat, ACthac20Factor); - float thac20cap = ((defender->GetLevel() * 9) + 20); - if(thac20 > thac20cap) - { - thac20 = thac20cap; - } - d += 10 * (m_diff / thac20); - } - - if(d < 0.0) - { - d = 0.0; - } - - if(d > 20) - { - d = 20.0; - } - - float interval = (damage - minhit) / 20.0; - damage = damage - ((int)d * interval); - } - else{ - //////////////////////////////////////////////////////// - // Scorpious2k: Include AC in the calculation - // use serverop variables to set values - int myac = GetAC(); - if (damage > 0 && myac > 0) { - int acfail=1000; - char tmp[10]; - - if (database.GetVariable("ACfail", tmp, 9)) { - acfail = (int) (atof(tmp) * 100); - if (acfail>100) acfail=100; - } - - if (acfail<=0 || MakeRandomInt(0, 100)>acfail) { - float acreduction=1; - int acrandom=300; - if (database.GetVariable("ACreduction", tmp, 9)) - { - acreduction=atof(tmp); - if (acreduction>100) acreduction=100; - } - - if (database.GetVariable("ACrandom", tmp, 9)) - { - acrandom = (int) ((atof(tmp)+1) * 100); - if (acrandom>10100) acrandom=10100; - } - - if (acreduction>0) { - damage -= (int) (GetAC() * acreduction/100.0f); - } - if (acrandom>0) { - damage -= (myac * MakeRandomInt(0, acrandom) / 10000); - } - if (damage<1) damage=1; - mlog(COMBAT__DAMAGE, "AC Damage Reduction: fail chance %d%%. Failed. Reduction %.3f%%, random %d. Resulting damage %d.", acfail, acreduction, acrandom, damage); - } else { - mlog(COMBAT__DAMAGE, "AC Damage Reduction: fail chance %d%%. Did not fail.", acfail); - } - } - - damage -= (aa_mit * damage); - - if(damage != 0 && damage < minhit) - damage = minhit; - } - - - //reduce the damage from shielding item and aa based on the min dmg - //spells offer pure mitigation - damage -= (minhit * itembonuses.MeleeMitigation / 100); - damage -= (damage * spellbonuses.MeleeMitigation / 100); - - if(damage < 0) - damage = 0; - - mlog(COMBAT__DAMAGE, "Applied %d percent mitigation, remaining damage %d", aa_mit, damage); -} - void Bot::DoSpecialAttackDamage(Mob *who, SkillType skill, int32 max_damage, int32 min_damage, int32 hate_override,int ReuseTime, bool HitChance) { //this really should go through the same code as normal melee damage to //pick up all the special behavior there diff --git a/zone/bot.h b/zone/bot.h index f037bf107..9134a8695 100644 --- a/zone/bot.h +++ b/zone/bot.h @@ -136,7 +136,8 @@ public: //abstract virtual function implementations requird by base abstract class virtual bool Death(Mob* killerMob, int32 damage, uint16 spell_id, SkillType attack_skill); virtual void Damage(Mob* from, int32 damage, uint16 spell_id, SkillType attack_skill, bool avoidable = true, int8 buffslot = -1, bool iBuffTic = false); - virtual bool Attack(Mob* other, int Hand = 13, bool FromRiposte = false, bool IsStrikethrough = false, bool IsFromSpell = false); + virtual bool Attack(Mob* other, int Hand = 13, bool FromRiposte = false, bool IsStrikethrough = false, bool IsFromSpell = false, + ExtraAttackOptions *opts = nullptr); virtual bool HasRaid() { return (GetRaid() ? true : false); } virtual bool HasGroup() { return (GetGroup() ? true : false); } virtual Raid* GetRaid() { return entity_list.GetRaidByMob(this); } @@ -168,7 +169,6 @@ public: virtual float GetProcChances(float &ProcBonus, float &ProcChance, uint16 weapon_speed, uint16 hand); virtual bool AvoidDamage(Mob* other, int32 &damage, bool CanRiposte); virtual int GetMonkHandToHandDamage(void); - virtual void TryCriticalHit(Mob *defender, uint16 skill, int32 &damage); virtual bool TryFinishingBlow(Mob *defender, SkillType skillinuse); virtual void DoRiposte(Mob* defender); inline virtual int16 GetATK() const { return ATK + itembonuses.ATK + spellbonuses.ATK + ((GetSTR() + GetSkill(OFFENSE)) * 9 / 10); } @@ -178,7 +178,6 @@ public: uint16 GetPrimarySkillValue(); uint16 MaxSkill(SkillType skillid, uint16 class_, uint16 level) const; inline uint16 MaxSkill(SkillType skillid) const { return MaxSkill(skillid, GetClass(), GetLevel()); } - virtual void MeleeMitigation(Mob *attacker, int32 &damage, int32 minhit); virtual void DoSpecialAttackDamage(Mob *who, SkillType skill, int32 max_damage, int32 min_damage = 1, int32 hate_override = -1, int ReuseTime = 10, bool HitChance=false); virtual void TryBackstab(Mob *other,int ReuseTime = 10); virtual void RogueBackstab(Mob* other, bool min_damage = false, int ReuseTime = 10); diff --git a/zone/client.h b/zone/client.h index 8e619d04f..89ea53b98 100644 --- a/zone/client.h +++ b/zone/client.h @@ -214,7 +214,8 @@ public: //abstract virtual function implementations requird by base abstract class virtual bool Death(Mob* killerMob, int32 damage, uint16 spell_id, SkillType attack_skill); virtual void Damage(Mob* from, int32 damage, uint16 spell_id, SkillType attack_skill, bool avoidable = true, int8 buffslot = -1, bool iBuffTic = false); - virtual bool Attack(Mob* other, int Hand = 13, bool FromRiposte = false, bool IsStrikethrough = false, bool IsFromSpell = false); + virtual bool Attack(Mob* other, int Hand = 13, bool FromRiposte = false, bool IsStrikethrough = false, bool IsFromSpell = false, + ExtraAttackOptions *opts = nullptr); virtual bool HasRaid() { return (GetRaid() ? true : false); } virtual bool HasGroup() { return (GetGroup() ? true : false); } virtual Raid* GetRaid() { return entity_list.GetRaidByClient(this); } diff --git a/zone/common.h b/zone/common.h index 5414bb004..bf0625b5f 100644 --- a/zone/common.h +++ b/zone/common.h @@ -82,7 +82,6 @@ typedef enum { //focus types #define HIGHEST_FOCUS focusAdditionalHeal //Should always be last focusType in enum enum { - SPECATK_NONE = 0, SPECATK_SUMMON = 1, SPECATK_ENRAGE = 2, SPECATK_RAMPAGE = 3, @@ -472,5 +471,23 @@ private: Mob* owner; }; +struct ExtraAttackOptions { + ExtraAttackOptions() + : damage_percent(1.0f), damage_flat(0), + armor_pen_percent(0.0f), armor_pen_flat(0), + crit_percent(1.0f), crit_flat(0.0f), + hate_percent(1.0f), hate_flat(0) + { } + + float damage_percent; + int damage_flat; + float armor_pen_percent; + int armor_pen_flat; + float crit_percent; + float crit_flat; + float hate_percent; + int hate_flat; +}; + #endif diff --git a/zone/corpse.h b/zone/corpse.h index 6b2d2f4dc..79580f71b 100644 --- a/zone/corpse.h +++ b/zone/corpse.h @@ -41,7 +41,8 @@ public: //abstract virtual function implementations requird by base abstract class virtual bool Death(Mob* killerMob, int32 damage, uint16 spell_id, SkillType attack_skill) { return true; } virtual void Damage(Mob* from, int32 damage, uint16 spell_id, SkillType attack_skill, bool avoidable = true, int8 buffslot = -1, bool iBuffTic = false) { return; } - virtual bool Attack(Mob* other, int Hand = 13, bool FromRiposte = false, bool IsStrikethrough = true, bool IsFromSpell = false) { return false; } + virtual bool Attack(Mob* other, int Hand = 13, bool FromRiposte = false, bool IsStrikethrough = true, bool IsFromSpell = false, + ExtraAttackOptions *opts = nullptr) { return false; } virtual bool HasRaid() { return false; } virtual bool HasGroup() { return false; } virtual Raid* GetRaid() { return 0; } diff --git a/zone/hate_list.cpp b/zone/hate_list.cpp index fe2e44dfb..907b42527 100644 --- a/zone/hate_list.cpp +++ b/zone/hate_list.cpp @@ -473,7 +473,7 @@ void HateList::PrintToClient(Client *c) } } -int HateList::AreaRampage(Mob *caster, Mob *target) +int HateList::AreaRampage(Mob *caster, Mob *target, ExtraAttackOptions *opts) { if(!target || !caster) return 0; @@ -501,7 +501,7 @@ int HateList::AreaRampage(Mob *caster, Mob *target) Mob *cur = entity_list.GetMobID((*iter)); if(cur) { - caster->Attack(cur); + caster->Attack(cur, 13, false, false, false, opts); } iter++; } diff --git a/zone/hate_list.h b/zone/hate_list.h index b61670310..d38e60c52 100644 --- a/zone/hate_list.h +++ b/zone/hate_list.h @@ -59,7 +59,7 @@ public: //Gets the target with the most hate regardless of things like frenzy etc. Mob* GetMostHate(); - int AreaRampage(Mob *caster, Mob *target); + int AreaRampage(Mob *caster, Mob *target, ExtraAttackOptions *opts); void SpellCast(Mob *caster, uint32 spell_id, float range); diff --git a/zone/lua_mob.cpp b/zone/lua_mob.cpp index f81ab58ee..a792a0ff2 100644 --- a/zone/lua_mob.cpp +++ b/zone/lua_mob.cpp @@ -103,6 +103,79 @@ bool Lua_Mob::Attack(Lua_Mob other, int hand, bool from_riposte, bool is_striket return self->Attack(other, hand, from_riposte, is_strikethrough, is_from_spell); } +bool Lua_Mob::Attack(Lua_Mob other, int hand, bool from_riposte, bool is_strikethrough, bool is_from_spell, luabind::object opts) { + Lua_Safe_Call_Bool(); + + ExtraAttackOptions options; + if(luabind::type(opts) == LUA_TTABLE) { + auto cur = opts["armor_pen_flat"]; + if(luabind::type(cur) != LUA_TNIL) { + try { + options.armor_pen_flat = luabind::object_cast(cur); + } catch(luabind::cast_failed) { + } + } + + cur = opts["crit_flat"]; + if(luabind::type(cur) != LUA_TNIL) { + try { + options.crit_flat = luabind::object_cast(cur); + } catch(luabind::cast_failed) { + } + } + + cur = opts["damage_flat"]; + if(luabind::type(cur) != LUA_TNIL) { + try { + options.damage_flat = luabind::object_cast(cur); + } catch(luabind::cast_failed) { + } + } + + cur = opts["hate_flat"]; + if(luabind::type(cur) != LUA_TNIL) { + try { + options.hate_flat = luabind::object_cast(cur); + } catch(luabind::cast_failed) { + } + } + + cur = opts["armor_pen_percent"]; + if(luabind::type(cur) != LUA_TNIL) { + try { + options.armor_pen_percent = luabind::object_cast(cur); + } catch(luabind::cast_failed) { + } + } + + cur = opts["crit_percent"]; + if(luabind::type(cur) != LUA_TNIL) { + try { + options.crit_percent = luabind::object_cast(cur); + } catch(luabind::cast_failed) { + } + } + + cur = opts["damage_percent"]; + if(luabind::type(cur) != LUA_TNIL) { + try { + options.damage_percent = luabind::object_cast(cur); + } catch(luabind::cast_failed) { + } + } + + cur = opts["hate_percent"]; + if(luabind::type(cur) != LUA_TNIL) { + try { + options.hate_percent = luabind::object_cast(cur); + } catch(luabind::cast_failed) { + } + } + } + + return self->Attack(other, hand, from_riposte, is_strikethrough, is_from_spell, &options); +} + void Lua_Mob::Damage(Lua_Mob from, int damage, int spell_id, int attack_skill) { Lua_Safe_Call_Void(); return self->Damage(from, damage, spell_id, static_cast(attack_skill)); @@ -1693,11 +1766,21 @@ int Lua_Mob::GetSpecialAbility(int ability) { return self->GetSpecialAbility(ability); } +int Lua_Mob::GetSpecialAbilityParam(int ability, int param) { + Lua_Safe_Call_Int(); + return self->GetSpecialAbilityParam(ability, param); +} + void Lua_Mob::SetSpecialAbility(int ability, int level) { Lua_Safe_Call_Void(); self->SetSpecialAbility(ability, level); } +void Lua_Mob::SetSpecialAbilityParam(int ability, int param, int value) { + Lua_Safe_Call_Void(); + self->SetSpecialAbilityParam(ability, param, value); +} + void Lua_Mob::ClearSpecialAbilities() { Lua_Safe_Call_Void(); self->ClearSpecialAbilities(); @@ -2017,7 +2100,9 @@ luabind::scope lua_register_mob() { .def("GetFlurryChance", (int(Lua_Mob::*)(void))&Lua_Mob::GetFlurryChance) .def("GetSkill", (int(Lua_Mob::*)(int))&Lua_Mob::GetSkill) .def("GetSpecialAbility", (int(Lua_Mob::*)(int))&Lua_Mob::GetSpecialAbility) + .def("GetSpecialAbilityParam", (int(Lua_Mob::*)(int,int))&Lua_Mob::GetSpecialAbilityParam) .def("SetSpecialAbility", (void(Lua_Mob::*)(int,int))&Lua_Mob::SetSpecialAbility) + .def("SetSpecialAbilityParam", (void(Lua_Mob::*)(int,int,int))&Lua_Mob::SetSpecialAbilityParam) .def("ClearSpecialAbilities", (void(Lua_Mob::*)(void))&Lua_Mob::ClearSpecialAbilities) .def("ProcessSpecialAbilities", (void(Lua_Mob::*)(std::string))&Lua_Mob::ProcessSpecialAbilities) .def("SetAppearance", (void(Lua_Mob::*)(int))&Lua_Mob::SetAppearance) @@ -2028,7 +2113,6 @@ luabind::scope lua_register_special_abilities() { return luabind::class_("SpecialAbility") .enum_("constants") [ - luabind::value("none", static_cast(SPECATK_NONE)), luabind::value("summon", static_cast(SPECATK_SUMMON)), luabind::value("enrage", static_cast(SPECATK_ENRAGE)), luabind::value("rampage", static_cast(SPECATK_RAMPAGE)), diff --git a/zone/lua_mob.h b/zone/lua_mob.h index ddae83f4a..160fae5ad 100644 --- a/zone/lua_mob.h +++ b/zone/lua_mob.h @@ -47,6 +47,7 @@ public: bool Attack(Lua_Mob other, int hand, bool from_riposte); bool Attack(Lua_Mob other, int hand, bool from_riposte, bool is_strikethrough); bool Attack(Lua_Mob other, int hand, bool from_riposte, bool is_strikethrough, bool is_from_spell); + bool Attack(Lua_Mob other, int hand, bool from_riposte, bool is_strikethrough, bool is_from_spell, luabind::object opts); void Damage(Lua_Mob from, int damage, int spell_id, int attack_skill); void Damage(Lua_Mob from, int damage, int spell_id, int attack_skill, bool avoidable); void Damage(Lua_Mob from, int damage, int spell_id, int attack_skill, bool avoidable, int buffslot); @@ -334,7 +335,9 @@ public: int GetSkill(int skill_id); void CalcBonuses(); int GetSpecialAbility(int ability); + int GetSpecialAbilityParam(int ability, int param); void SetSpecialAbility(int ability, int level); + void SetSpecialAbilityParam(int ability, int param, int value); void ClearSpecialAbilities(); void ProcessSpecialAbilities(std::string str); void SetAppearance(int app); diff --git a/zone/merc.cpp b/zone/merc.cpp index aed882a51..db31a0835 100644 --- a/zone/merc.cpp +++ b/zone/merc.cpp @@ -4713,7 +4713,7 @@ void Merc::DoClassAttacks(Mob *target) { classattack_timer.Start(reuse*HasteModifier/100); } -bool Merc::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, bool IsFromSpell) +bool Merc::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, bool IsFromSpell, ExtraAttackOptions *opts) { _ZP(Client_Attack); @@ -4724,7 +4724,7 @@ bool Merc::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, boo return false; } - return NPC::Attack(other, Hand, bRiposte, IsStrikethrough, IsFromSpell); + return NPC::Attack(other, Hand, bRiposte, IsStrikethrough, IsFromSpell, opts); } void Merc::Damage(Mob* other, int32 damage, uint16 spell_id, SkillType attack_skill, bool avoidable, int8 buffslot, bool iBuffTic) diff --git a/zone/merc.h b/zone/merc.h index 0223386ca..a72216285 100644 --- a/zone/merc.h +++ b/zone/merc.h @@ -48,7 +48,8 @@ public: //abstract virtual function implementations requird by base abstract class virtual bool Death(Mob* killerMob, int32 damage, uint16 spell_id, SkillType attack_skill); virtual void Damage(Mob* from, int32 damage, uint16 spell_id, SkillType attack_skill, bool avoidable = true, int8 buffslot = -1, bool iBuffTic = false); - virtual bool Attack(Mob* other, int Hand = SLOT_PRIMARY, bool FromRiposte = false, bool IsStrikethrough = false, bool IsFromSpell = false); + virtual bool Attack(Mob* other, int Hand = SLOT_PRIMARY, bool FromRiposte = false, bool IsStrikethrough = false, + bool IsFromSpell = false, ExtraAttackOptions *opts = nullptr); virtual bool HasRaid() { return false; } virtual bool HasGroup() { return (GetGroup() ? true : false); } virtual Raid* GetRaid() { return 0; } diff --git a/zone/mob.cpp b/zone/mob.cpp index 50bf0c65e..78303bc35 100644 --- a/zone/mob.cpp +++ b/zone/mob.cpp @@ -2337,12 +2337,7 @@ bool Mob::HateSummon() { mob_owner = entity_list.GetMob(GetOwnerID()); int summon_level = GetSpecialAbility(SPECATK_SUMMON); - if(summon_level == 1 || summon_level == 3) { - //Summon things after we're hurt - if(GetHPRatio() >= 98 || !GetTarget() || (mob_owner && mob_owner->IsClient() && !CheckLosFN(GetTarget()))) { - return false; - } - } else if(summon_level == 2 || summon_level == 4) { + if(summon_level == 1 || summon_level == 2) { //Summon things always if(!GetTarget() || (mob_owner && mob_owner->IsClient() && !CheckLosFN(GetTarget()))) { return false; @@ -2352,24 +2347,33 @@ bool Mob::HateSummon() { return false; } + // validate hp + int hp_ratio = GetSpecialAbilityParam(SPECATK_SUMMON, 1); + hp_ratio = hp_ratio > 0 ? hp_ratio : 97; + if(GetHPRatio() > static_cast(hp_ratio)) { + return false; + } + // now validate the timer + int summon_timer_duration = GetSpecialAbilityParam(SPECATK_SUMMON, 0); + summon_timer_duration = summon_timer_duration > 0 ? summon_timer_duration : 6000; Timer *timer = GetSpecialAbilityTimer(SPECATK_SUMMON); if (!timer) { - StartSpecialAbilityTimer(SPECATK_SUMMON, 6000); + StartSpecialAbilityTimer(SPECATK_SUMMON, summon_timer_duration); } else { if(!timer->Check()) return false; - timer->Start(6000); + timer->Start(summon_timer_duration); } // get summon target SetTarget(GetHateTop()); if(target) { - if(summon_level == 1 || summon_level == 2) { - entity_list.MessageClose(this, true, 500, 10, "%s says,'You will not evade me, %s!' ", GetCleanName(), target->GetCleanName() ); + if(summon_level == 1) { + entity_list.MessageClose(this, true, 500, MT_Say, "%s says,'You will not evade me, %s!' ", GetCleanName(), target->GetCleanName() ); if (target->IsClient()) { target->CastToClient()->MovePC(zone->GetZoneID(), zone->GetInstanceID(), x_pos, y_pos, z_pos, target->GetHeading(), 0, SummonPC); @@ -2389,8 +2393,8 @@ bool Mob::HateSummon() { } return true; - } else if(summon_level == 3 || summon_level == 4) { - entity_list.MessageClose(this, true, 500, 10, "%s blinks to %s' ", GetCleanName(), target->GetCleanName()); + } else if(summon_level == 2) { + entity_list.MessageClose(this, true, 500, MT_Say, "%s says,'You will not evade me, %s!'", GetCleanName(), target->GetCleanName()); GMMove(target->GetX(), target->GetY(), target->GetZ()); } } @@ -4799,6 +4803,19 @@ int Mob::GetSpecialAbility(int ability) { return 0; } +int Mob::GetSpecialAbilityParam(int ability, int param) { + if(param >= MAX_SPECIAL_ATTACK_PARAMS || param < 0) { + return 0; + } + + auto iter = SpecialAbilities.find(ability); + if(iter != SpecialAbilities.end()) { + return iter->second.params[param]; + } + + return 0; +} + void Mob::SetSpecialAbility(int ability, int level) { auto iter = SpecialAbilities.find(ability); if(iter != SpecialAbilities.end()) { @@ -4813,6 +4830,24 @@ void Mob::SetSpecialAbility(int ability, int level) { } } +void Mob::SetSpecialAbilityParam(int ability, int param, int value) { + if(param >= MAX_SPECIAL_ATTACK_PARAMS || param < 0) { + return; + } + + auto iter = SpecialAbilities.find(ability); + if(iter != SpecialAbilities.end()) { + SpecialAbility spec = iter->second; + spec.params[param] = value; + SpecialAbilities[ability] = spec; + } else { + SpecialAbility spec; + spec.params[param] = value; + spec.timer = nullptr; + SpecialAbilities[ability] = spec; + } +} + void Mob::StartSpecialAbilityTimer(int ability, uint32 time) { auto iter = SpecialAbilities.find(ability); if(iter != SpecialAbilities.end()) { @@ -4873,7 +4908,7 @@ void Mob::ProcessSpecialAbilities(const std::string str) { std::vector sp = SplitString(str, '^'); for(auto iter = sp.begin(); iter != sp.end(); ++iter) { std::vector sub_sp = SplitString((*iter), ','); - if(sub_sp.size() == 2) { + if(sub_sp.size() >= 2) { int ability = std::stoi(sub_sp[0]); int value = std::stoi(sub_sp[1]); @@ -4894,6 +4929,14 @@ void Mob::ProcessSpecialAbilities(const std::string str) { default: break; } + + for(size_t i = 2, p = 0; i < sub_sp.size(); ++i, ++p) { + if(p >= MAX_SPECIAL_ATTACK_PARAMS) { + break; + } + + SetSpecialAbilityParam(ability, p, std::stoi(sub_sp[i])); + } } } } \ No newline at end of file diff --git a/zone/mob.h b/zone/mob.h index 757e346e1..52bfec14d 100644 --- a/zone/mob.h +++ b/zone/mob.h @@ -29,6 +29,8 @@ char* strn0cpy(char* dest, const char* source, uint32 size); +#define MAX_SPECIAL_ATTACK_PARAMS 8 + class EGNode; class MobFearState; class Mob : public Entity { @@ -40,6 +42,7 @@ public: struct SpecialAbility { int level; Timer *timer; + int params[MAX_SPECIAL_ATTACK_PARAMS]; }; Mob(const char* in_name, @@ -109,19 +112,19 @@ public: uint16 GetThrownDamage(int16 wDmg, int32& TotalDmg, int& minDmg); // 13 = Primary (default), 14 = secondary virtual bool Attack(Mob* other, int Hand = 13, bool FromRiposte = false, bool IsStrikethrough = false, - bool IsFromSpell = false) = 0; + bool IsFromSpell = false, ExtraAttackOptions *opts = nullptr) = 0; int MonkSpecialAttack(Mob* other, uint8 skill_used); virtual void TryBackstab(Mob *other,int ReuseTime = 10); void TriggerDefensiveProcs(const ItemInst* weapon, Mob *on, uint16 hand = 13, int damage = 0); virtual bool AvoidDamage(Mob* attacker, int32 &damage, bool CanRiposte = true); virtual bool CheckHitChance(Mob* attacker, SkillType skillinuse, int Hand, int16 chance_mod = 0); - virtual void TryCriticalHit(Mob *defender, uint16 skill, int32 &damage); + virtual void TryCriticalHit(Mob *defender, uint16 skill, int32 &damage, ExtraAttackOptions *opts = nullptr); void TryPetCriticalHit(Mob *defender, uint16 skill, int32 &damage); virtual bool TryFinishingBlow(Mob *defender, SkillType skillinuse); virtual bool TryHeadShot(Mob* defender, SkillType skillInUse); virtual void DoRiposte(Mob* defender); void ApplyMeleeDamageBonus(uint16 skill, int32 &damage); - virtual void MeleeMitigation(Mob *attacker, int32 &damage, int32 minhit); + virtual void MeleeMitigation(Mob *attacker, int32 &damage, int32 minhit, ExtraAttackOptions *opts = nullptr); bool CombatRange(Mob* other); //Appearance @@ -569,8 +572,8 @@ public: bool IsOffHandAtk() const { return offhand; } inline void OffHandAtk(bool val) { offhand = val; } - inline void SetFlurryChance(uint8 value) { NPC_FlurryChance = value;} - uint8 GetFlurryChance() { return NPC_FlurryChance; } + void SetFlurryChance(uint8 value) { SetSpecialAbilityParam(SPECATK_FLURRY, 0, value); } + uint8 GetFlurryChance() { return GetSpecialAbilityParam(SPECATK_FLURRY, 0); } static uint32 GetAppearanceValue(EmuAppearance iAppearance); void SendAppearancePacket(uint32 type, uint32 value, bool WholeZone = true, bool iIgnoreSelf = false, Client *specific_target=nullptr); @@ -647,11 +650,11 @@ public: virtual void DoMeleeSkillAttackDmg(Mob* other, uint16 weapon_damage, SkillType skillinuse, int16 chance_mod=0, int16 focus=0, bool CanRiposte=false); virtual void DoArcheryAttackDmg(Mob* other, const ItemInst* RangeWeapon=nullptr, const ItemInst* Ammo=nullptr, uint16 weapon_damage=0, int16 chance_mod=0, int16 focus=0); bool CanDoSpecialAttack(Mob *other); - bool Flurry(); - bool Rampage(); + bool Flurry(ExtraAttackOptions *opts); + bool Rampage(ExtraAttackOptions *opts); bool AddRampage(Mob*); void ClearRampage(); - void AreaRampage(); + void AreaRampage(ExtraAttackOptions *opts); void StartEnrage(); void ProcessEnrage(); @@ -766,7 +769,9 @@ public: bool HasNPCSpecialAtk(const char* parse); int GetSpecialAbility(int ability); + int GetSpecialAbilityParam(int ability, int param); void SetSpecialAbility(int ability, int level); + void SetSpecialAbilityParam(int ability, int param, int value); void StartSpecialAbilityTimer(int ability, uint32 time); void StopSpecialAbilityTimer(int ability); Timer *GetSpecialAbilityTimer(int ability); @@ -850,7 +855,6 @@ protected: int16 Vulnerability_Mod[HIGHEST_RESIST+2]; bool m_AllowBeneficial; bool m_DisableMelee; - uint8 NPC_FlurryChance; bool isgrouped; bool israidgrouped; diff --git a/zone/npc.h b/zone/npc.h index 8ccaec74b..8fa44db8f 100644 --- a/zone/npc.h +++ b/zone/npc.h @@ -81,7 +81,8 @@ public: //abstract virtual function implementations requird by base abstract class virtual bool Death(Mob* killerMob, int32 damage, uint16 spell_id, SkillType attack_skill); virtual void Damage(Mob* from, int32 damage, uint16 spell_id, SkillType attack_skill, bool avoidable = true, int8 buffslot = -1, bool iBuffTic = false); - virtual bool Attack(Mob* other, int Hand = 13, bool FromRiposte = false, bool IsStrikethrough = false, bool IsFromSpell = false); + virtual bool Attack(Mob* other, int Hand = 13, bool FromRiposte = false, bool IsStrikethrough = false, + bool IsFromSpell = false, ExtraAttackOptions *opts = nullptr); virtual bool HasRaid() { return false; } virtual bool HasGroup() { return false; } virtual Raid* GetRaid() { return 0; } diff --git a/zone/special_attacks.cpp b/zone/special_attacks.cpp index 7653fcb5a..2630b4b64 100644 --- a/zone/special_attacks.cpp +++ b/zone/special_attacks.cpp @@ -931,7 +931,7 @@ void Mob::DoArcheryAttackDmg(Mob* other, const ItemInst* RangeWeapon, const Item hate += (2*((GetLevel()-25)/3)); } - other->AvoidDamage(this, TotalDmg, false); //CanRiposte=false - Can not riposte archery attacks. + other->AvoidDamage(this, TotalDmg, false); other->MeleeMitigation(this, TotalDmg, minDmg); if(TotalDmg > 0) {