mirror of
https://github.com/EQEmu/Server.git
synced 2026-01-03 18:53:52 +00:00
Rampage, Area Rampage, Flurry got new customizable effects. Part of that was adding a new set of stuff to attack.
This commit is contained in:
parent
2f335372a0
commit
3992ac02bb
175
zone/MobAI.cpp
175
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<float>(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) {
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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; }
|
||||
|
||||
328
zone/bot.cpp
328
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
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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); }
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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; }
|
||||
|
||||
@ -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++;
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -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<int>(cur);
|
||||
} catch(luabind::cast_failed) {
|
||||
}
|
||||
}
|
||||
|
||||
cur = opts["crit_flat"];
|
||||
if(luabind::type(cur) != LUA_TNIL) {
|
||||
try {
|
||||
options.crit_flat = luabind::object_cast<int>(cur);
|
||||
} catch(luabind::cast_failed) {
|
||||
}
|
||||
}
|
||||
|
||||
cur = opts["damage_flat"];
|
||||
if(luabind::type(cur) != LUA_TNIL) {
|
||||
try {
|
||||
options.damage_flat = luabind::object_cast<int>(cur);
|
||||
} catch(luabind::cast_failed) {
|
||||
}
|
||||
}
|
||||
|
||||
cur = opts["hate_flat"];
|
||||
if(luabind::type(cur) != LUA_TNIL) {
|
||||
try {
|
||||
options.hate_flat = luabind::object_cast<int>(cur);
|
||||
} catch(luabind::cast_failed) {
|
||||
}
|
||||
}
|
||||
|
||||
cur = opts["armor_pen_percent"];
|
||||
if(luabind::type(cur) != LUA_TNIL) {
|
||||
try {
|
||||
options.armor_pen_percent = luabind::object_cast<float>(cur);
|
||||
} catch(luabind::cast_failed) {
|
||||
}
|
||||
}
|
||||
|
||||
cur = opts["crit_percent"];
|
||||
if(luabind::type(cur) != LUA_TNIL) {
|
||||
try {
|
||||
options.crit_percent = luabind::object_cast<float>(cur);
|
||||
} catch(luabind::cast_failed) {
|
||||
}
|
||||
}
|
||||
|
||||
cur = opts["damage_percent"];
|
||||
if(luabind::type(cur) != LUA_TNIL) {
|
||||
try {
|
||||
options.damage_percent = luabind::object_cast<float>(cur);
|
||||
} catch(luabind::cast_failed) {
|
||||
}
|
||||
}
|
||||
|
||||
cur = opts["hate_percent"];
|
||||
if(luabind::type(cur) != LUA_TNIL) {
|
||||
try {
|
||||
options.hate_percent = luabind::object_cast<float>(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<SkillType>(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_<SpecialAbilities>("SpecialAbility")
|
||||
.enum_("constants")
|
||||
[
|
||||
luabind::value("none", static_cast<int>(SPECATK_NONE)),
|
||||
luabind::value("summon", static_cast<int>(SPECATK_SUMMON)),
|
||||
luabind::value("enrage", static_cast<int>(SPECATK_ENRAGE)),
|
||||
luabind::value("rampage", static_cast<int>(SPECATK_RAMPAGE)),
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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; }
|
||||
|
||||
69
zone/mob.cpp
69
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<float>(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<std::string> sp = SplitString(str, '^');
|
||||
for(auto iter = sp.begin(); iter != sp.end(); ++iter) {
|
||||
std::vector<std::string> 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]));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
22
zone/mob.h
22
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;
|
||||
|
||||
@ -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; }
|
||||
|
||||
@ -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)
|
||||
{
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user