Combat Revamp - MAJOR BREAKING CHANGE

This commit makes combat much more live like. This is based on a lot of parses
done by TAKP and myself. There are numerous things based on dev quotes and
hints. Pretty much all combat has changed, spell effects correct, stacking
correct, etc.

This is the fist stage of the revamp, I will be trying to remove some code
duplication and make things generally cleaner.

Server ops will have to rebalance their NPCs. AC actually means something now.
Rough recommendations?
Level 50 "classic" trash should be no more than 115.
Classic raid mobs should be more 200+ etc
Other "classic" NPCs should be a lot lower as well.
PoP trash probably shouldn't exceed 120 AC
PoP raids should be higher
Devs have said the vast majority of NPCs didn't exceed 600 AC until very
recently. The exceptions were mostly raid encounters.

There really isn't a good "default" for every server, so this will be up to
the devs to find where they want their server stats to be.
This commit is contained in:
Michael Cook (mackal) 2017-01-15 00:03:02 -05:00
parent 891fa0411c
commit 9e824876ba
25 changed files with 1520 additions and 1514 deletions

View File

@ -124,6 +124,30 @@ bool EQEmu::skills::IsCastingSkill(SkillType skill)
}
}
int32 EQEmu::skills::GetBaseDamage(SkillType skill)
{
switch (skill) {
case SkillBash:
return 2;
case SkillDragonPunch:
return 12;
case SkillEagleStrike:
return 7;
case SkillFlyingKick:
return 25;
case SkillKick:
return 3;
case SkillRoundKick:
return 5;
case SkillTigerClaw:
return 4;
case SkillFrenzy:
return 10;
default:
return 0;
}
}
const std::map<EQEmu::skills::SkillType, std::string>& EQEmu::skills::GetSkillTypeMap()
{
/* VS2013 code

View File

@ -166,6 +166,7 @@ namespace EQEmu
float GetSkillMeleePushForce(SkillType skill);
bool IsBardInstrumentSkill(SkillType skill);
bool IsCastingSkill(SkillType skill);
int32 GetBaseDamage(SkillType skill);
extern const std::map<SkillType, std::string>& GetSkillTypeMap();

File diff suppressed because it is too large Load Diff

View File

@ -35,9 +35,9 @@ public:
//abstract virtual function implementations requird by base abstract class
virtual bool Death(Mob* killerMob, int32 damage, uint16 spell_id, EQEmu::skills::SkillType attack_skill) { return true; }
virtual void Damage(Mob* from, int32 damage, uint16 spell_id, EQEmu::skills::SkillType attack_skill, bool avoidable = true, int8 buffslot = -1, bool iBuffTic = false, int special = 0) { return; }
virtual void Damage(Mob* from, int32 damage, uint16 spell_id, EQEmu::skills::SkillType attack_skill, bool avoidable = true, int8 buffslot = -1, bool iBuffTic = false, eSpecialAttacks special = eSpecialAttacks::None) { return; }
virtual bool Attack(Mob* other, int Hand = EQEmu::inventory::slotPrimary, bool FromRiposte = false, bool IsStrikethrough = false, bool IsFromSpell = false,
ExtraAttackOptions *opts = nullptr, int special = 0) { return false; }
ExtraAttackOptions *opts = nullptr) { return false; }
virtual bool HasRaid() { return false; }
virtual bool HasGroup() { return false; }
virtual Raid* GetRaid() { return 0; }

View File

@ -46,6 +46,7 @@ void Mob::CalcBonuses()
CalcMaxHP();
CalcMaxMana();
SetAttackTimer();
CalcAC();
rooted = FindType(SE_Root);
}
@ -669,6 +670,10 @@ void Mob::ApplyAABonuses(const AA::Rank &rank, StatBonuses *newbon)
}
switch (effect) {
case SE_ACv2:
case SE_ArmorClass:
newbon->AC += base1;
break;
// Note: AA effects that use accuracy are skill limited, while spell effect is not.
case SE_Accuracy:
// Bad data or unsupported new skill
@ -1527,9 +1532,6 @@ void Mob::CalcSpellBonuses(StatBonuses* newbon)
}
}
// THIS IS WRONG, leaving for now
//this prolly suffer from roundoff error slightly...
newbon->AC = newbon->AC * 10 / 34; //ratio determined impirically from client.
if (GetClass() == BARD)
newbon->ManaRegen = 0; // Bards do not get mana regen from spells.
}

View File

@ -1992,110 +1992,6 @@ bool Bot::CheckBotDoubleAttack(bool tripleAttack) {
return false;
}
void Bot::DoMeleeSkillAttackDmg(Mob* other, uint16 weapon_damage, EQEmu::skills::SkillType skillinuse, int16 chance_mod, int16 focus, bool CanRiposte, int ReuseTime) {
if (!CanDoSpecialAttack(other))
return;
//For spells using skill value 98 (feral swipe ect) server sets this to 67 automatically.
if (skillinuse == EQEmu::skills::SkillBegging)
skillinuse = EQEmu::skills::SkillOffense;
int damage = 0;
uint32 hate = 0;
int Hand = EQEmu::inventory::slotPrimary;
if (hate == 0 && weapon_damage > 1)
hate = weapon_damage;
if(weapon_damage > 0) {
if(GetClass() == BERSERKER) {
int bonus = (3 + GetLevel( )/ 10);
weapon_damage = (weapon_damage * (100 + bonus) / 100);
}
int32 min_hit = 1;
int32 max_hit = ((2 * weapon_damage * GetDamageTable(skillinuse)) / 100);
if(GetLevel() >= 28 && IsWarriorClass()) {
int ucDamageBonus = GetWeaponDamageBonus((const EQEmu::ItemData*) nullptr);
min_hit += (int) ucDamageBonus;
max_hit += (int) ucDamageBonus;
hate += ucDamageBonus;
}
ApplySpecialAttackMod(skillinuse, max_hit, min_hit);
min_hit += (min_hit * GetMeleeMinDamageMod_SE(skillinuse) / 100);
if(max_hit < min_hit)
max_hit = min_hit;
if(RuleB(Combat, UseIntervalAC))
damage = max_hit;
else
damage = zone->random.Int(min_hit, max_hit);
if (other->AvoidDamage(this, damage, CanRiposte ? EQEmu::inventory::slotRange : EQEmu::inventory::slotPrimary)) { // MainRange excludes ripo, primary doesn't have any extra behavior
if (damage == -3) {
DoRiposte(other);
if (HasDied())
return;
}
} else {
if (other->CheckHitChance(this, skillinuse, chance_mod)) {
other->MeleeMitigation(this, damage, min_hit);
if (damage > 0) {
damage += damage*focus/100;
ApplyMeleeDamageBonus(skillinuse, damage);
damage += other->GetFcDamageAmtIncoming(this, 0, true, skillinuse);
damage += ((itembonuses.HeroicSTR / 10) + (damage * other->GetSkillDmgTaken(skillinuse) / 100) + GetSkillDmgAmt(skillinuse));
TryCriticalHit(other, skillinuse, damage, nullptr);
}
} else {
damage = 0;
}
}
}
else
damage = -5;
if (skillinuse == EQEmu::skills::SkillBash){
const EQEmu::ItemInstance* inst = GetBotItem(EQEmu::inventory::slotSecondary);
const EQEmu::ItemData* botweapon = 0;
if(inst)
botweapon = inst->GetItem();
if(botweapon) {
if (botweapon->ItemType == EQEmu::item::ItemTypeShield)
hate += botweapon->AC;
hate = (hate * (100 + GetFuriousBash(botweapon->Focus.Effect)) / 100);
}
}
other->AddToHateList(this, hate);
bool CanSkillProc = true;
if (skillinuse == EQEmu::skills::SkillOffense){ //Hack to allow damage to display.
skillinuse = EQEmu::skills::SkillTigerClaw; //'strike' your opponent - Arbitrary choice for message.
CanSkillProc = false; //Disable skill procs
}
other->Damage(this, damage, SPELL_UNKNOWN, skillinuse);
if (HasDied())
return;
if (damage > 0)
CheckNumHitsRemaining(NumHit::OutgoingHitSuccess);
if ((skillinuse == EQEmu::skills::SkillDragonPunch) && GetAA(aaDragonPunch) && zone->random.Int(0, 99) < 25){
SpellFinished(904, other, EQEmu::CastingSlot::Item, 0, -1, spells[904].ResistDiff);
other->Stun(100);
}
if (CanSkillProc && HasSkillProcs())
TrySkillProc(other, skillinuse, ReuseTime);
if (CanSkillProc && (damage > 0) && HasSkillProcSuccess())
TrySkillProc(other, skillinuse, ReuseTime, true);
}
void Bot::ApplySpecialAttackMod(EQEmu::skills::SkillType skill, int32 &dmg, int32 &mindmg) {
int item_slot = -1;
//1: Apply bonus from AC (BOOT/SHIELD/HANDS) est. 40AC=6dmg
@ -3664,7 +3560,7 @@ bool Bot::Death(Mob *killerMob, int32 damage, uint16 spell_id, EQEmu::skills::Sk
return true;
}
void Bot::Damage(Mob *from, int32 damage, uint16 spell_id, EQEmu::skills::SkillType attack_skill, bool avoidable, int8 buffslot, bool iBuffTic, int special) {
void Bot::Damage(Mob *from, int32 damage, uint16 spell_id, EQEmu::skills::SkillType attack_skill, bool avoidable, int8 buffslot, bool iBuffTic, eSpecialAttacks special) {
if(spell_id == 0)
spell_id = SPELL_UNKNOWN;
@ -3710,7 +3606,7 @@ void Bot::AddToHateList(Mob* other, uint32 hate /*= 0*/, int32 damage /*= 0*/, b
Mob::AddToHateList(other, hate, damage, iYellForHelp, bFrenzy, iBuffTic);
}
bool Bot::Attack(Mob* other, int Hand, bool FromRiposte, bool IsStrikethrough, bool IsFromSpell, ExtraAttackOptions *opts, int special) {
bool Bot::Attack(Mob* other, int Hand, bool FromRiposte, bool IsStrikethrough, bool IsFromSpell, ExtraAttackOptions *opts) {
if (!other) {
SetTarget(nullptr);
Log.Out(Logs::General, Logs::Error, "A null Mob object was passed to Bot::Attack for evaluation!");
@ -3778,25 +3674,7 @@ bool Bot::Attack(Mob* other, int Hand, bool FromRiposte, bool IsStrikethrough, b
//if weapon damage > 0 then we know we can hit the target with this weapon
//otherwise we cannot and we set the damage to -5 later on
if(weapon_damage > 0) {
//Berserker Berserk damage bonus
if(berserk && (GetClass() == BERSERKER)){
int bonus = (3 + GetLevel() / 10); //unverified
weapon_damage = (weapon_damage * (100 + bonus) / 100);
Log.Out(Logs::Detail, Logs::Combat, "Berserker damage bonus increases DMG to %d", weapon_damage);
}
//try a finishing blow.. if successful end the attack
if(TryFinishingBlow(other, skillinuse))
return true;
//damage formula needs some work
int min_hit = 1;
int max_hit = ((2 * weapon_damage * GetDamageTable(skillinuse)) / 100);
if(GetLevel() < 10 && max_hit > RuleI(Combat, HitCapPre10))
max_hit = (RuleI(Combat, HitCapPre10));
else if(GetLevel() < 20 && max_hit > RuleI(Combat, HitCapPre20))
max_hit = (RuleI(Combat, HitCapPre20));
int min_damage = 0;
// ***************************************************************
// *** Calculate the damage bonus, if applicable, for this hit ***
@ -3814,8 +3692,7 @@ bool Bot::Attack(Mob* other, int Hand, bool FromRiposte, bool IsStrikethrough, b
// Damage bonuses apply only to hits from the main hand (Hand == MainPrimary) by characters level 28 and above
// who belong to a melee class. If we're here, then all of these conditions apply.
ucDamageBonus = GetWeaponDamageBonus(weapon ? weapon->GetItem() : (const EQEmu::ItemData*) nullptr);
min_hit += (int) ucDamageBonus;
max_hit += (int) ucDamageBonus;
min_damage = ucDamageBonus;
hate += ucDamageBonus;
}
#endif
@ -3823,28 +3700,21 @@ bool Bot::Attack(Mob* other, int Hand, bool FromRiposte, bool IsStrikethrough, b
if (Hand == EQEmu::inventory::slotSecondary) {
if (aabonuses.SecondaryDmgInc || itembonuses.SecondaryDmgInc || spellbonuses.SecondaryDmgInc){
ucDamageBonus = GetWeaponDamageBonus(weapon ? weapon->GetItem() : (const EQEmu::ItemData*) nullptr);
min_hit += (int) ucDamageBonus;
max_hit += (int) ucDamageBonus;
min_damage = ucDamageBonus;
hate += ucDamageBonus;
}
}
min_hit = (min_hit * GetMeleeMinDamageMod_SE(skillinuse) / 100);
int min_cap = (base_damage * GetMeleeMinDamageMod_SE(skillinuse) / 100);
if(max_hit < min_hit)
max_hit = min_hit;
Log.Out(Logs::Detail, Logs::Combat, "Damage calculated to %d (bonus %d, base %d, str %d, skill %d, DMG %d, lv %d)",
damage, min_damage, base_damage, GetSTR(), GetSkill(skillinuse), weapon_damage, mylevel);
if(RuleB(Combat, UseIntervalAC))
damage = max_hit;
else
damage = zone->random.Int(min_hit, max_hit);
Log.Out(Logs::Detail, Logs::Combat, "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());
auto offense = this->offense(skillinuse);
if(opts) {
damage *= opts->damage_percent;
damage += opts->damage_flat;
base_damage *= opts->damage_percent;
base_damage += opts->damage_flat;
hate *= opts->hate_percent;
hate += opts->hate_flat;
}
@ -3866,10 +3736,11 @@ bool Bot::Attack(Mob* other, int Hand, bool FromRiposte, bool IsStrikethrough, b
}
} else {
if (other->CheckHitChance(this, skillinuse)) {
other->MeleeMitigation(this, damage, min_hit, opts);
ApplyMeleeDamageBonus(skillinuse, damage);
damage += ((itembonuses.HeroicSTR / 10) + (damage * other->GetSkillDmgTaken(skillinuse) / 100) + GetSkillDmgAmt(skillinuse));
TryCriticalHit(other, skillinuse, damage, opts);
other->MeleeMitigation(this, damage, base_damage, offense, skillinuse, opts);
if (damage > 0) {
ApplyDamageTable(damage, offense);
CommonOutgoingHitSuccess(other, damage, min_damage, min_cap, skillinuse, opts);
}
} else {
damage = 0;
}
@ -3896,39 +3767,7 @@ bool Bot::Attack(Mob* other, int Hand, bool FromRiposte, bool IsStrikethrough, b
if (damage > 0)
CheckNumHitsRemaining(NumHit::OutgoingHitSuccess);
//break invis when you attack
if(invisible) {
Log.Out(Logs::Detail, Logs::Combat, "Removing invisibility due to melee attack.");
BuffFadeByEffect(SE_Invisibility);
BuffFadeByEffect(SE_Invisibility2);
invisible = false;
}
if(invisible_undead) {
Log.Out(Logs::Detail, Logs::Combat, "Removing invisibility vs. undead due to melee attack.");
BuffFadeByEffect(SE_InvisVsUndead);
BuffFadeByEffect(SE_InvisVsUndead2);
invisible_undead = false;
}
if(invisible_animals){
Log.Out(Logs::Detail, Logs::Combat, "Removing invisibility vs. animals due to melee attack.");
BuffFadeByEffect(SE_InvisVsAnimals);
invisible_animals = false;
}
if(hidden || improved_hidden){
hidden = false;
improved_hidden = false;
EQApplicationPacket* outapp = new EQApplicationPacket(OP_SpawnAppearance, sizeof(SpawnAppearance_Struct));
SpawnAppearance_Struct* sa_out = (SpawnAppearance_Struct*)outapp->pBuffer;
sa_out->spawn_id = GetID();
sa_out->type = 0x03;
sa_out->parameter = 0;
entity_list.QueueClients(this, outapp, true);
safe_delete(outapp);
}
CommonBreakInvisibleFromCombat();
if (spellbonuses.NegateIfCombat)
BuffFadeByEffect(SE_NegateIfCombat);
@ -4816,21 +4655,24 @@ int Bot::GetHandToHandDamage(void) {
return 2;
}
bool Bot::TryFinishingBlow(Mob *defender, EQEmu::skills::SkillType skillinuse) {
bool Bot::TryFinishingBlow(Mob *defender, EQEmu::skills::SkillType skillinuse, int &damage)
{
if (!defender)
return false;
if (aabonuses.FinishingBlow[1] && !defender->IsClient() && defender->GetHPRatio() < 10) {
uint32 chance = (aabonuses.FinishingBlow[0] / 10);
uint32 damage = aabonuses.FinishingBlow[1];
uint16 levelreq = aabonuses.FinishingBlowLvl[0];
if(defender->GetLevel() <= levelreq && (chance >= zone->random.Int(0, 1000))){
Log.Out(Logs::Detail, Logs::Combat, "Landed a finishing blow: levelreq at %d, other level %d", levelreq , defender->GetLevel());
int chance = (aabonuses.FinishingBlow[0] / 10);
int fb_damage = aabonuses.FinishingBlow[1];
int levelreq = aabonuses.FinishingBlowLvl[0];
if (defender->GetLevel() <= levelreq && (chance >= zone->random.Int(0, 1000))) {
Log.Out(Logs::Detail, Logs::Combat, "Landed a finishing blow: levelreq at %d, other level %d",
levelreq, defender->GetLevel());
entity_list.MessageClose_StringID(this, false, 200, MT_CritMelee, FINISHING_BLOW, GetName());
defender->Damage(this, damage, SPELL_UNKNOWN, skillinuse);
damage = fb_damage;
return true;
} else {
Log.Out(Logs::Detail, Logs::Combat, "FAILED a finishing blow: levelreq at %d, other level %d", levelreq , defender->GetLevel());
Log.Out(Logs::Detail, Logs::Combat, "FAILED a finishing blow: levelreq at %d, other level %d",
levelreq, defender->GetLevel());
return false;
}
}
@ -4858,6 +4700,92 @@ void Bot::DoRiposte(Mob* defender) {
}
}
int Bot::GetBaseSkillDamage(EQEmu::skills::SkillType skill, Mob *target)
{
int base = EQEmu::skills::GetBaseDamage(skill);
auto skill_level = GetSkill(skill);
switch (skill) {
case EQEmu::skills::SkillDragonPunch:
case EQEmu::skills::SkillEagleStrike:
case EQEmu::skills::SkillTigerClaw:
if (skill_level >= 25)
base++;
if (skill_level >= 75)
base++;
if (skill_level >= 125)
base++;
if (skill_level >= 175)
base++;
return base;
case EQEmu::skills::SkillFrenzy:
if (GetBotItem(EQEmu::inventory::slotSecondary)) {
if (GetLevel() > 15)
base += GetLevel() - 15;
if (base > 23)
base = 23;
if (GetLevel() > 50)
base += 2;
if (GetLevel() > 54)
base++;
if (GetLevel() > 59)
base++;
}
return base;
case EQEmu::skills::SkillFlyingKick: {
float skill_bonus = skill_level / 9.0f;
float ac_bonus = 0.0f;
auto inst = GetBotItem(EQEmu::inventory::slotFeet);
if (inst)
ac_bonus = inst->GetItemArmorClass(true) / 25.0f;
if (ac_bonus > skill_bonus)
ac_bonus = skill_bonus;
return static_cast<int>(ac_bonus + skill_bonus);
}
case EQEmu::skills::SkillKick: {
float skill_bonus = skill_level / 10.0f;
float ac_bonus = 0.0f;
auto inst = GetBotItem(EQEmu::inventory::slotFeet);
if (inst)
ac_bonus = inst->GetItemArmorClass(true) / 25.0f;
if (ac_bonus > skill_bonus)
ac_bonus = skill_bonus;
return static_cast<int>(ac_bonus + skill_bonus);
}
case EQEmu::skills::SkillBash: {
float skill_bonus = skill_level / 10.0f;
float ac_bonus = 0.0f;
const EQEmu::ItemInstance *inst = nullptr;
if (HasShieldEquiped())
inst = GetBotItem(EQEmu::inventory::slotSecondary);
else if (HasTwoHanderEquipped())
inst = GetBotItem(EQEmu::inventory::slotPrimary);
if (inst)
ac_bonus = inst->GetItemArmorClass(true) / 25.0f;
if (ac_bonus > skill_bonus)
ac_bonus = skill_bonus;
return static_cast<int>(ac_bonus + skill_bonus);
}
case EQEmu::skills::SkillBackstab: {
float skill_bonus = static_cast<float>(skill_level) * 0.02f;
auto inst = GetBotItem(EQEmu::inventory::slotPrimary);
if (inst && inst->GetItem() && inst->GetItem()->ItemType == EQEmu::item::ItemType1HPiercing) {
base = inst->GetItemBackstabDamage(true);
if (!inst->GetItemBackstabDamage())
base += inst->GetItemWeaponDamage(true);
if (target) {
if (inst->GetItemElementalFlag(true) && inst->GetItemElementalDamage(true))
base += target->ResistElementalWeaponDmg(inst);
if (inst->GetItemBaneDamageBody(true) || inst->GetItemBaneDamageRace(true))
base += target->CheckBaneDamage(inst);
}
}
return static_cast<int>(static_cast<float>(base) * (skill_bonus + 2.0f));
}
default:
return 0;
}
}
void Bot::DoSpecialAttackDamage(Mob *who, EQEmu::skills::SkillType skill, int32 max_damage, int32 min_damage, int32 hate_override, int ReuseTime, bool HitChance) {
int32 hate = max_damage;
if(hate_override > -1)
@ -4877,33 +4805,36 @@ void Bot::DoSpecialAttackDamage(Mob *who, EQEmu::skills::SkillType skill, int32
}
}
min_damage += (min_damage * GetMeleeMinDamageMod_SE(skill) / 100);
int min_cap = max_damage * GetMeleeMinDamageMod_SE(skill) / 100;
int hand = EQEmu::inventory::slotPrimary;
int damage = 0;
auto offense = this->offense(skill);
if (skill == EQEmu::skills::SkillThrowing || skill == EQEmu::skills::SkillArchery)
hand = EQEmu::inventory::slotRange;
if (who->AvoidDamage(this, max_damage, hand)) {
if (max_damage == -3)
if (who->AvoidDamage(this, damage, hand)) {
if (damage == -3)
DoRiposte(who);
} else {
if (HitChance || who->CheckHitChance(this, skill)) {
who->MeleeMitigation(this, max_damage, min_damage);
ApplyMeleeDamageBonus(skill, max_damage);
max_damage += who->GetFcDamageAmtIncoming(this, 0, true, skill);
max_damage += ((itembonuses.HeroicSTR / 10) + (max_damage * who->GetSkillDmgTaken(skill) / 100) + GetSkillDmgAmt(skill));
TryCriticalHit(who, skill, max_damage);
if (max_damage > 0)
who->MeleeMitigation(this, damage, max_damage, offense, skill);
if (damage > 0) {
ApplyDamageTable(damage, offense);
CommonOutgoingHitSuccess(who, damage, min_damage, min_cap, skill);
}
} else {
max_damage = 0;
damage = 0;
}
}
who->AddToHateList(this, hate);
who->Damage(this, max_damage, SPELL_UNKNOWN, skill, false);
who->Damage(this, damage, SPELL_UNKNOWN, skill, false);
if(!GetTarget() || HasDied())
return;
if (max_damage > 0)
if (damage > 0)
CheckNumHitsRemaining(NumHit::OutgoingHitSuccess);
//[AA Dragon Punch] value[0] = 100 for 25%, chance value[1] = skill
@ -4919,7 +4850,7 @@ void Bot::DoSpecialAttackDamage(Mob *who, EQEmu::skills::SkillType skill, int32
if (HasSkillProcs())
TrySkillProc(who, skill, (ReuseTime * 1000));
if (max_damage > 0 && HasSkillProcSuccess())
if (damage > 0 && HasSkillProcSuccess())
TrySkillProc(who, skill, (ReuseTime * 1000), true);
}
@ -4967,72 +4898,33 @@ void Bot::TryBackstab(Mob *other, int ReuseTime) {
}
}
} else if(aabonuses.FrontalBackstabMinDmg || itembonuses.FrontalBackstabMinDmg || spellbonuses.FrontalBackstabMinDmg) {
m_specialattacks = eSpecialAttacks::ChaoticStab;
RogueBackstab(other, true);
if (level > 54) {
float DoubleAttackProbability = ((GetSkill(EQEmu::skills::SkillDoubleAttack) + GetLevel()) / 500.0f);
if(zone->random.Real(0, 1) < DoubleAttackProbability)
if(other->GetHP() > 0)
RogueBackstab(other,true, ReuseTime);
if (tripleChance && other->GetHP() > 0 && tripleChance > zone->random.Int(0, 100))
RogueBackstab(other,false,ReuseTime);
}
m_specialattacks = eSpecialAttacks::None;
}
else
Attack(other, EQEmu::inventory::slotPrimary);
}
void Bot::RogueBackstab(Mob* other, bool min_damage, int ReuseTime) {
int32 ndamage = 0;
int32 max_hit = 0;
int32 min_hit = 0;
int32 hate = 0;
int32 primaryweapondamage = 0;
int32 backstab_dmg = 0;
EQEmu::ItemInstance* botweaponInst = GetBotItem(EQEmu::inventory::slotPrimary);
if(botweaponInst) {
primaryweapondamage = GetWeaponDamage(other, botweaponInst);
backstab_dmg = botweaponInst->GetItem()->BackstabDmg;
for (int i = EQEmu::inventory::socketBegin; i < EQEmu::inventory::SocketCount; ++i) {
EQEmu::ItemInstance *aug = botweaponInst->GetAugment(i);
if(aug)
backstab_dmg += aug->GetItem()->BackstabDmg;
}
} else {
primaryweapondamage = ((GetLevel() / 7) + 1);
backstab_dmg = primaryweapondamage;
void Bot::RogueBackstab(Mob *other, bool min_damage, int ReuseTime)
{
if (!other)
return;
EQEmu::ItemInstance *botweaponInst = GetBotItem(EQEmu::inventory::slotPrimary);
if (botweaponInst) {
if (!GetWeaponDamage(other, botweaponInst))
return;
} else if (!GetWeaponDamage(other, (const EQEmu::ItemData *)nullptr)) {
return;
}
if(primaryweapondamage > 0) {
if(level > 25) {
max_hit = (((((2 * backstab_dmg) * GetDamageTable(EQEmu::skills::SkillBackstab) / 100) * 10 * GetSkill(EQEmu::skills::SkillBackstab) / 355) + ((level - 25) / 3) + 1) * ((100 + RuleI(Combat, BackstabBonus)) / 100));
hate = (20 * backstab_dmg * GetSkill(EQEmu::skills::SkillBackstab) / 355);
} else {
max_hit = (((((2 * backstab_dmg) * GetDamageTable(EQEmu::skills::SkillBackstab) / 100) * 10 * GetSkill(EQEmu::skills::SkillBackstab) / 355) + 1) * ((100 + RuleI(Combat, BackstabBonus)) / 100));
hate = (20 * backstab_dmg * GetSkill(EQEmu::skills::SkillBackstab) / 355);
}
uint32 hate = 0;
if (level < 51)
min_hit = (level * 15 / 10);
else
min_hit = ((level * ( level * 5 - 105)) / 100);
int base_damage = GetBaseSkillDamage(EQEmu::skills::SkillBackstab, other);
hate = base_damage;
if (!other->CheckHitChance(this, EQEmu::skills::SkillBackstab, 0))
ndamage = 0;
else {
if (min_damage) {
ndamage = min_hit;
} else {
if (max_hit < min_hit)
max_hit = min_hit;
ndamage = (RuleB(Combat, UseIntervalAC) ? max_hit : zone->random.Int(min_hit, max_hit));
}
}
} else
ndamage = -5;
DoSpecialAttackDamage(other, EQEmu::skills::SkillBackstab, ndamage, min_hit, hate, ReuseTime);
DoSpecialAttackDamage(other, EQEmu::skills::SkillBackstab, base_damage, 0, hate, ReuseTime);
DoAnim(anim1HPiercing);
}
@ -5157,41 +5049,25 @@ void Bot::DoClassAttacks(Mob *target, bool IsRiposte) {
if (!target->CheckHitChance(this, EQEmu::skills::SkillBash, 0))
dmg = 0;
else {
if(RuleB(Combat, UseIntervalAC))
dmg = GetBashDamage();
else
dmg = zone->random.Int(1, GetBashDamage());
dmg = GetBaseSkillDamage(EQEmu::skills::SkillBash);
}
}
reuse = (BashReuseTime * 1000);
DoSpecialAttackDamage(target, EQEmu::skills::SkillBash, dmg, 1, -1, reuse);
DoSpecialAttackDamage(target, EQEmu::skills::SkillBash, dmg, 0, -1, reuse);
did_attack = true;
}
}
if (skill_to_use == EQEmu::skills::SkillFrenzy) {
int AtkRounds = 3;
int skillmod = 0;
if (MaxSkill(EQEmu::skills::SkillFrenzy) > 0)
skillmod = (100 * GetSkill(EQEmu::skills::SkillFrenzy) / MaxSkill(EQEmu::skills::SkillFrenzy));
int32 max_dmg = (26 + ((((GetLevel() - 6) * 2) * skillmod) / 100)) * ((100 + RuleI(Combat, FrenzyBonus)) / 100);
int32 min_dmg = 0;
int32 max_dmg = GetBaseSkillDamage(EQEmu::skills::SkillFrenzy);
DoAnim(anim2HSlashing);
if (GetLevel() < 51)
min_dmg = 1;
else
min_dmg = (GetLevel() * 8 / 10);
if (min_dmg > max_dmg)
max_dmg = min_dmg;
reuse = (FrenzyReuseTime * 1000);
did_attack = true;
while(AtkRounds > 0) {
if (GetTarget() && (AtkRounds == 1 || zone->random.Int(0, 100) < 75)) {
DoSpecialAttackDamage(GetTarget(), EQEmu::skills::SkillFrenzy, max_dmg, min_dmg, max_dmg, reuse, true);
DoSpecialAttackDamage(GetTarget(), EQEmu::skills::SkillFrenzy, max_dmg, 0, max_dmg, reuse, true);
}
AtkRounds--;
@ -5207,14 +5083,11 @@ void Bot::DoClassAttacks(Mob *target, bool IsRiposte) {
if (!target->CheckHitChance(this, EQEmu::skills::SkillKick, 0))
dmg = 0;
else {
if(RuleB(Combat, UseIntervalAC))
dmg = GetKickDamage();
else
dmg = zone->random.Int(1, GetKickDamage());
dmg = GetBaseSkillDamage(EQEmu::skills::SkillKick);
}
}
reuse = (KickReuseTime * 1000);
DoSpecialAttackDamage(target, EQEmu::skills::SkillKick, dmg, 1, -1, reuse);
DoSpecialAttackDamage(target, EQEmu::skills::SkillKick, dmg, 0, -1, reuse);
did_attack = true;
}
}
@ -5249,26 +5122,6 @@ void Bot::DoClassAttacks(Mob *target, bool IsRiposte) {
classattack_timer.Start(reuse / HasteModifier);
}
bool Bot::TryHeadShot(Mob* defender, EQEmu::skills::SkillType skillInUse) {
bool Result = false;
if (defender && (defender->GetBodyType() == BT_Humanoid) && (skillInUse == EQEmu::skills::SkillArchery) && (GetClass() == RANGER) && (GetLevel() >= 62)) {
int defenderLevel = defender->GetLevel();
int rangerLevel = GetLevel();
if(GetAA(aaHeadshot) && ((defenderLevel - 46) <= GetAA(aaHeadshot) * 2)) {
float AttackerChance = 0.20f + ((float)(rangerLevel - 51) * 0.005f);
float DefenderChance = (float)zone->random.Real(0.00f, 1.00f);
if(AttackerChance > DefenderChance) {
Log.Out(Logs::Detail, Logs::Combat, "Landed a headshot: Attacker chance was %f and Defender chance was %f.", AttackerChance, DefenderChance);
entity_list.MessageClose(this, false, 200, MT_CritMelee, "%s has scored a leathal HEADSHOT!", GetName());
defender->Damage(this, (defender->GetMaxHP()+50), SPELL_UNKNOWN, skillInUse);
Result = true;
} else
Log.Out(Logs::Detail, Logs::Combat, "FAILED a headshot: Attacker chance was %f and Defender chance was %f.", AttackerChance, DefenderChance);
}
}
return Result;
}
int32 Bot::CheckAggroAmount(uint16 spellid) {
int32 AggroAmount = Mob::CheckAggroAmount(spellid, nullptr);
int32 focusAggro = GetBotFocusEffect(BotfocusSpellHateMod, spellid);

View File

@ -206,9 +206,9 @@ public:
//abstract virtual function implementations requird by base abstract class
virtual bool Death(Mob* killerMob, int32 damage, uint16 spell_id, EQEmu::skills::SkillType attack_skill);
virtual void Damage(Mob* from, int32 damage, uint16 spell_id, EQEmu::skills::SkillType attack_skill, bool avoidable = true, int8 buffslot = -1, bool iBuffTic = false, int special = 0);
virtual void Damage(Mob* from, int32 damage, uint16 spell_id, EQEmu::skills::SkillType attack_skill, bool avoidable = true, int8 buffslot = -1, bool iBuffTic = false, eSpecialAttacks special = eSpecialAttacks::None);
virtual bool Attack(Mob* other, int Hand = EQEmu::inventory::slotPrimary, bool FromRiposte = false, bool IsStrikethrough = false, bool IsFromSpell = false,
ExtraAttackOptions *opts = nullptr, int special = 0);
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); }
@ -239,7 +239,7 @@ public:
uint16 BotGetSpellPriority(int spellslot) { return AIspells[spellslot].priority; }
virtual float GetProcChances(float ProcBonus, uint16 hand);
virtual int GetHandToHandDamage(void);
virtual bool TryFinishingBlow(Mob *defender, EQEmu::skills::SkillType skillinuse);
virtual bool TryFinishingBlow(Mob *defender, EQEmu::skills::SkillType skillinuse, int &damage);
virtual void DoRiposte(Mob* defender);
inline virtual int32 GetATK() const { return ATK + itembonuses.ATK + spellbonuses.ATK + ((GetSTR() + GetSkill(EQEmu::skills::SkillOffense)) * 9 / 10); }
inline virtual int32 GetATKBonus() const { return itembonuses.ATK + spellbonuses.ATK; }
@ -248,13 +248,12 @@ public:
uint16 GetPrimarySkillValue();
uint16 MaxSkill(EQEmu::skills::SkillType skillid, uint16 class_, uint16 level) const;
inline uint16 MaxSkill(EQEmu::skills::SkillType skillid) const { return MaxSkill(skillid, GetClass(), GetLevel()); }
virtual int GetBaseSkillDamage(EQEmu::skills::SkillType skill, Mob *target = nullptr);
virtual void DoSpecialAttackDamage(Mob *who, EQEmu::skills::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);
virtual void RogueAssassinate(Mob* other);
virtual void DoClassAttacks(Mob *target, bool IsRiposte=false);
virtual bool TryHeadShot(Mob* defender, EQEmu::skills::SkillType skillInUse);
virtual void DoMeleeSkillAttackDmg(Mob* other, uint16 weapon_damage, EQEmu::skills::SkillType skillinuse, int16 chance_mod = 0, int16 focus = 0, bool CanRiposte = false, int ReuseTime = 0);
virtual void ApplySpecialAttackMod(EQEmu::skills::SkillType skill, int32 &dmg, int32 &mindmg);
bool CanDoSpecialAttack(Mob *other);
virtual int32 CheckAggroAmount(uint16 spellid);
@ -503,7 +502,6 @@ public:
bool GetAltOutOfCombatBehavior() { return _altoutofcombatbehavior;}
bool GetShowHelm() { return _showhelm; }
inline virtual int32 GetAC() const { return AC; }
inline virtual int32 GetSTR() const { return STR; }
inline virtual int32 GetSTA() const { return STA; }
inline virtual int32 GetDEX() const { return DEX; }

View File

@ -6871,8 +6871,8 @@ void Client::SendStatsWindow(Client* client, bool use_window)
/* DS */ indP << "DS: " << (itembonuses.DamageShield + spellbonuses.DamageShield*-1) << " (Spell: " << (spellbonuses.DamageShield*-1) << " + Item: " << itembonuses.DamageShield << " / " << RuleI(Character, ItemDamageShieldCap) << ")<br>" <<
/* Atk */ indP << "<c \"#CCFF00\">ATK: " << GetTotalATK() << "</c><br>" <<
/* Atk2 */ indP << "- Base: " << GetATKRating() << " | Item: " << itembonuses.ATK << " (" << RuleI(Character, ItemATKCap) << ")~Used: " << (itembonuses.ATK * 1.342) << " | Spell: " << spellbonuses.ATK << "<br>" <<
/* AC */ indP << "<c \"#CCFF00\">AC: " << CalcAC() << "</c><br>" <<
/* AC2 */ indP << "- Mit: " << GetACMit() << " | Avoid: " << GetACAvoid() << " | Spell: " << spellbonuses.AC << " | Shield: " << shield_ac << "<br>" <<
/* AC */ indP << "<c \"#CCFF00\">AC: " << -1 << "</c><br>" <<
/* AC2 */ indP << "- Mit: " << -1 << " | Avoid: " << -1 << " | Spell: " << spellbonuses.AC << " | Shield: " << shield_ac << "<br>" <<
/* Haste */ indP << "<c \"#CCFF00\">Haste: " << GetHaste() << "</c><br>" <<
/* Haste2 */ indP << " - Item: " << itembonuses.haste << " + Spell: " << (spellbonuses.haste + spellbonuses.hastetype2) << " (Cap: " << RuleI(Character, HasteCap) << ") | Over: " << (spellbonuses.hastetype3 + ExtraHaste) << "<br>" <<
/* RunSpeed*/ indP << "<c \"#CCFF00\">Runspeed: " << GetRunspeed() << "</c><br>" <<
@ -6910,7 +6910,7 @@ void Client::SendStatsWindow(Client* client, bool use_window)
client->Message(15, "~~~~~ %s %s ~~~~~", GetCleanName(), GetLastName());
client->Message(0, " Level: %i Class: %i Race: %i DS: %i/%i Size: %1.1f Weight: %.1f/%d ", GetLevel(), GetClass(), GetRace(), GetDS(), RuleI(Character, ItemDamageShieldCap), GetSize(), (float)CalcCurrentWeight() / 10.0f, GetSTR());
client->Message(0, " HP: %i/%i HP Regen: %i/%i",GetHP(), GetMaxHP(), CalcHPRegen(), CalcHPRegenCap());
client->Message(0, " AC: %i ( Mit.: %i + Avoid.: %i + Spell: %i ) | Shield AC: %i", CalcAC(), GetACMit(), GetACAvoid(), spellbonuses.AC, shield_ac);
client->Message(0, " AC: %i ( Mit.: %i + Avoid.: %i + Spell: %i ) | Shield AC: %i", -1, -1, -1, spellbonuses.AC, shield_ac);
if(CalcMaxMana() > 0)
client->Message(0, " Mana: %i/%i Mana Regen: %i/%i", GetMana(), GetMaxMana(), CalcManaRegen(), CalcManaRegenCap());
client->Message(0, " End.: %i/%i End. Regen: %i/%i",GetEndurance(), GetMaxEndurance(), CalcEnduranceRegen(), CalcEnduranceRegenCap());

View File

@ -221,18 +221,18 @@ public:
//abstract virtual function implementations required by base abstract class
virtual bool Death(Mob* killerMob, int32 damage, uint16 spell_id, EQEmu::skills::SkillType attack_skill);
virtual void Damage(Mob* from, int32 damage, uint16 spell_id, EQEmu::skills::SkillType attack_skill, bool avoidable = true, int8 buffslot = -1, bool iBuffTic = false, int special = 0);
virtual void Damage(Mob* from, int32 damage, uint16 spell_id, EQEmu::skills::SkillType attack_skill, bool avoidable = true, int8 buffslot = -1, bool iBuffTic = false, eSpecialAttacks special = eSpecialAttacks::None);
virtual bool Attack(Mob* other, int Hand = EQEmu::inventory::slotPrimary, bool FromRiposte = false, bool IsStrikethrough = false, bool IsFromSpell = false,
ExtraAttackOptions *opts = nullptr, int special = 0);
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); }
virtual Group* GetGroup() { return entity_list.GetGroupByClient(this); }
virtual inline bool IsBerserk() { return berserk; }
virtual int32 GetMeleeMitDmg(Mob *attacker, int32 damage, int32 minhit, float mit_rating, float atk_rating);
virtual void SetAttackTimer();
int GetQuiverHaste(int delay);
void DoAttackRounds(Mob *target, int hand, bool IsFromSpell = false);
int DoDamageCaps(int base_damage);
void AI_Init();
void AI_Start(uint32 iMoveDelay = 0);
@ -418,8 +418,6 @@ public:
virtual void CalcBonuses();
//these are all precalculated now
inline virtual int32 GetAC() const { return AC; }
inline virtual int32 GetATK() const { return ATK + itembonuses.ATK + spellbonuses.ATK + ((GetSTR() + GetSkill(EQEmu::skills::SkillOffense)) * 9 / 10); }
inline virtual int32 GetATKBonus() const { return itembonuses.ATK + spellbonuses.ATK; }
inline virtual int GetHaste() const { return Haste; }
int GetRawACNoShield(int &shield_ac) const;
@ -1301,9 +1299,6 @@ private:
void HandleTraderPriceUpdate(const EQApplicationPacket *app);
int32 CalcAC();
int32 GetACMit();
int32 GetACAvoid();
int32 CalcATK();
int32 CalcItemATKCap();
int32 CalcHaste();

View File

@ -1025,111 +1025,6 @@ int32 Client::acmod()
return 0;
};
// This is a testing formula for AC, the value this returns should be the same value as the one the client shows...
// ac1 and ac2 are probably the damage migitation and damage avoidance numbers, not sure which is which.
// I forgot to include the iksar defense bonus and i cant find my notes now...
// AC from spells are not included (cant even cast spells yet..)
int32 Client::CalcAC()
{
// new formula
int avoidance = (acmod() + ((GetSkill(EQEmu::skills::SkillDefense) + itembonuses.HeroicAGI / 10) * 16) / 9);
if (avoidance < 0) {
avoidance = 0;
}
if (RuleB(Character, EnableAvoidanceCap)) {
if (avoidance > RuleI(Character, AvoidanceCap)) {
avoidance = RuleI(Character, AvoidanceCap);
}
}
int mitigation = 0;
if (m_pp.class_ == WIZARD || m_pp.class_ == MAGICIAN || m_pp.class_ == NECROMANCER || m_pp.class_ == ENCHANTER) {
//something is wrong with this, naked casters have the wrong natural AC
// mitigation = (spellbonuses.AC/3) + (GetSkill(DEFENSE)/2) + (itembonuses.AC+1);
mitigation = (GetSkill(EQEmu::skills::SkillDefense) + itembonuses.HeroicAGI / 10) / 4 + (itembonuses.AC + 1);
//this might be off by 4..
mitigation -= 4;
}
else {
// mitigation = (spellbonuses.AC/4) + (GetSkill(DEFENSE)/3) + ((itembonuses.AC*4)/3);
mitigation = (GetSkill(EQEmu::skills::SkillDefense) + itembonuses.HeroicAGI / 10) / 3 + ((itembonuses.AC * 4) / 3);
if (m_pp.class_ == MONK) {
mitigation += GetLevel() * 13 / 10; //the 13/10 might be wrong, but it is close...
}
}
int displayed = 0;
displayed += ((avoidance + mitigation) * 1000) / 847; //natural AC
//Iksar AC, untested
if (GetRace() == IKSAR) {
displayed += 12;
int iksarlevel = GetLevel();
iksarlevel -= 10;
if (iksarlevel > 25) {
iksarlevel = 25;
}
if (iksarlevel > 0) {
displayed += iksarlevel * 12 / 10;
}
}
// Shield AC bonus for HeroicSTR
if (itembonuses.HeroicSTR) {
bool equiped = CastToClient()->m_inv.GetItem(EQEmu::inventory::slotSecondary);
if (equiped) {
uint8 shield = CastToClient()->m_inv.GetItem(EQEmu::inventory::slotSecondary)->GetItem()->ItemType;
if (shield == EQEmu::item::ItemTypeShield) {
displayed += itembonuses.HeroicSTR / 2;
}
}
}
//spell AC bonuses are added directly to natural total
displayed += spellbonuses.AC;
AC = displayed;
return (AC);
}
int32 Client::GetACMit()
{
int mitigation = 0;
if (m_pp.class_ == WIZARD || m_pp.class_ == MAGICIAN || m_pp.class_ == NECROMANCER || m_pp.class_ == ENCHANTER) {
mitigation = (GetSkill(EQEmu::skills::SkillDefense) + itembonuses.HeroicAGI / 10) / 4 + (itembonuses.AC + 1);
mitigation -= 4;
}
else {
mitigation = (GetSkill(EQEmu::skills::SkillDefense) + itembonuses.HeroicAGI / 10) / 3 + ((itembonuses.AC * 4) / 3);
if (m_pp.class_ == MONK) {
mitigation += GetLevel() * 13 / 10; //the 13/10 might be wrong, but it is close...
}
}
// Shield AC bonus for HeroicSTR
if (itembonuses.HeroicSTR) {
bool equiped = CastToClient()->m_inv.GetItem(EQEmu::inventory::slotSecondary);
if (equiped) {
uint8 shield = CastToClient()->m_inv.GetItem(EQEmu::inventory::slotSecondary)->GetItem()->ItemType;
if (shield == EQEmu::item::ItemTypeShield) {
mitigation += itembonuses.HeroicSTR / 2;
}
}
}
return (mitigation * 1000 / 847);
}
int32 Client::GetACAvoid()
{
int32 avoidance = (acmod() + ((GetSkill(EQEmu::skills::SkillDefense) + itembonuses.HeroicAGI / 10) * 16) / 9);
if (avoidance < 0) {
avoidance = 0;
}
if (RuleB(Character, EnableAvoidanceCap)) {
if ((avoidance * 1000 / 847) > RuleI(Character, AvoidanceCap)) {
return RuleI(Character, AvoidanceCap);
}
}
return (avoidance * 1000 / 847);
}
int32 Client::CalcMaxMana()
{
switch (GetCasterClass()) {

View File

@ -638,5 +638,11 @@ struct ExtraAttackOptions {
};
struct DamageTable {
int32 max_extra; // max extra damage
int32 chance; // chance not to apply?
int32 minusfactor; // difficulty of rolling
};
#endif

View File

@ -52,8 +52,8 @@ class Corpse : public Mob {
/* Corpse: General */
virtual bool Death(Mob* killerMob, int32 damage, uint16 spell_id, EQEmu::skills::SkillType attack_skill) { return true; }
virtual void Damage(Mob* from, int32 damage, uint16 spell_id, EQEmu::skills::SkillType attack_skill, bool avoidable = true, int8 buffslot = -1, bool iBuffTic = false, int special = 0) { return; }
virtual bool Attack(Mob* other, int Hand = EQEmu::inventory::slotPrimary, bool FromRiposte = false, bool IsStrikethrough = true, bool IsFromSpell = false, ExtraAttackOptions *opts = nullptr, int special = 0) { return false; }
virtual void Damage(Mob* from, int32 damage, uint16 spell_id, EQEmu::skills::SkillType attack_skill, bool avoidable = true, int8 buffslot = -1, bool iBuffTic = false, eSpecialAttacks special = eSpecialAttacks::None) { return; }
virtual bool Attack(Mob* other, int Hand = EQEmu::inventory::slotPrimary, 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; }

View File

@ -35,9 +35,9 @@ public:
//abstract virtual function implementations required by base abstract class
virtual bool Death(Mob* killerMob, int32 damage, uint16 spell_id, EQEmu::skills::SkillType attack_skill) { return true; }
virtual void Damage(Mob* from, int32 damage, uint16 spell_id, EQEmu::skills::SkillType attack_skill, bool avoidable = true, int8 buffslot = -1, bool iBuffTic = false, int special = 0) { return; }
virtual void Damage(Mob* from, int32 damage, uint16 spell_id, EQEmu::skills::SkillType attack_skill, bool avoidable = true, int8 buffslot = -1, bool iBuffTic = false, eSpecialAttacks special = eSpecialAttacks::None) { return; }
virtual bool Attack(Mob* other, int Hand = EQEmu::inventory::slotPrimary, bool FromRiposte = false, bool IsStrikethrough = false, bool IsFromSpell = false,
ExtraAttackOptions *opts = nullptr, int special = 0) {
ExtraAttackOptions *opts = nullptr) {
return false;
}
virtual bool HasRaid() { return false; }

View File

@ -553,7 +553,7 @@ int HateList::AreaRampage(Mob *caster, Mob *target, int count, ExtraAttackOption
auto mob = entity_list.GetMobID(id);
if (mob) {
++hit_count;
caster->ProcessAttackRounds(mob, opts, 1);
caster->ProcessAttackRounds(mob, opts);
}
}

View File

@ -4432,13 +4432,8 @@ void Merc::DoClassAttacks(Mob *target) {
dmg = -5;
}
else{
if (target->CheckHitChance(this, EQEmu::skills::SkillKick, 0)) {
if(RuleB(Combat, UseIntervalAC))
dmg = GetKickDamage();
else
dmg = zone->random.Int(1, GetKickDamage());
}
if (target->CheckHitChance(this, EQEmu::skills::SkillKick, 0))
dmg = GetBaseSkillDamage(EQEmu::skills::SkillKick, GetTarget());
}
reuse = KickReuseTime * 1000;
@ -4454,12 +4449,8 @@ void Merc::DoClassAttacks(Mob *target) {
dmg = -5;
}
else{
if (target->CheckHitChance(this, EQEmu::skills::SkillBash, 0)) {
if(RuleB(Combat, UseIntervalAC))
dmg = GetBashDamage();
else
dmg = zone->random.Int(1, GetBashDamage());
}
if (target->CheckHitChance(this, EQEmu::skills::SkillBash, 0))
dmg = GetBaseSkillDamage(EQEmu::skills::SkillBash, GetTarget());
}
reuse = BashReuseTime * 1000;
@ -4474,7 +4465,7 @@ void Merc::DoClassAttacks(Mob *target) {
classattack_timer.Start(reuse / HasteModifier);
}
bool Merc::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, bool IsFromSpell, ExtraAttackOptions *opts, int special)
bool Merc::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, bool IsFromSpell, ExtraAttackOptions *opts)
{
if (!other) {
SetTarget(nullptr);
@ -4485,7 +4476,7 @@ bool Merc::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, boo
return NPC::Attack(other, Hand, bRiposte, IsStrikethrough, IsFromSpell, opts);
}
void Merc::Damage(Mob* other, int32 damage, uint16 spell_id, EQEmu::skills::SkillType attack_skill, bool avoidable, int8 buffslot, bool iBuffTic, int special)
void Merc::Damage(Mob* other, int32 damage, uint16 spell_id, EQEmu::skills::SkillType attack_skill, bool avoidable, int8 buffslot, bool iBuffTic, eSpecialAttacks special)
{
if(IsDead() || IsCorpse())
return;

View File

@ -65,9 +65,9 @@ public:
//abstract virtual function implementations requird by base abstract class
virtual bool Death(Mob* killerMob, int32 damage, uint16 spell_id, EQEmu::skills::SkillType attack_skill);
virtual void Damage(Mob* from, int32 damage, uint16 spell_id, EQEmu::skills::SkillType attack_skill, bool avoidable = true, int8 buffslot = -1, bool iBuffTic = false, int special = 0);
virtual void Damage(Mob* from, int32 damage, uint16 spell_id, EQEmu::skills::SkillType attack_skill, bool avoidable = true, int8 buffslot = -1, bool iBuffTic = false, eSpecialAttacks special = eSpecialAttacks::None);
virtual bool Attack(Mob* other, int Hand = EQEmu::inventory::slotPrimary, bool FromRiposte = false, bool IsStrikethrough = false,
bool IsFromSpell = false, ExtraAttackOptions *opts = nullptr, int special = 0);
bool IsFromSpell = false, ExtraAttackOptions *opts = nullptr);
virtual bool HasRaid() { return false; }
virtual bool HasGroup() { return (GetGroup() ? true : false); }
virtual Raid* GetRaid() { return 0; }
@ -197,7 +197,6 @@ public:
virtual void CalcBonuses();
int32 GetEndurance() const {return cur_end;} //This gets our current endurance
inline uint8 GetEndurancePercent() { return (uint8)((float)cur_end / (float)max_end * 100.0f); }
inline virtual int32 GetAC() const { return AC; }
inline virtual int32 GetATK() const { return ATK; }
inline virtual int32 GetATKBonus() const { return itembonuses.ATK + spellbonuses.ATK; }
int32 GetRawACNoShield(int &shield_ac) const;

View File

@ -109,7 +109,9 @@ Mob::Mob(const char* in_name,
m_TargetV(glm::vec3()),
flee_timer(FLEE_CHECK_TIMER),
m_Position(position),
tmHidden(-1)
tmHidden(-1),
mitigation_ac(0),
m_specialattacks(eSpecialAttacks::None)
{
targeted = 0;
tar_ndx=0;
@ -4643,7 +4645,7 @@ void Mob::SetRaidGrouped(bool v)
}
}
int16 Mob::GetCriticalChanceBonus(uint16 skill)
int Mob::GetCriticalChanceBonus(uint16 skill)
{
int critical_chance = 0;

View File

@ -54,6 +54,13 @@ namespace EQEmu
class ItemInstance;
}
enum class eSpecialAttacks : int {
None,
Rampage,
AERampage,
ChaoticStab
};
class Mob : public Entity {
public:
enum CLIENT_CONN_STATUS { CLIENT_CONNECTING, CLIENT_CONNECTED, CLIENT_LINKDEAD,
@ -152,45 +159,51 @@ public:
float HeadingAngleToMob(Mob *other); // to keep consistent with client generated messages
virtual void RangedAttack(Mob* other) { }
virtual void ThrowingAttack(Mob* other) { }
uint16 GetThrownDamage(int16 wDmg, int32& TotalDmg, int& minDmg);
// 13 = Primary (default), 14 = secondary
virtual bool Attack(Mob* other, int Hand = EQEmu::inventory::slotPrimary, bool FromRiposte = false, bool IsStrikethrough = false,
bool IsFromSpell = false, ExtraAttackOptions *opts = nullptr, int special = 0) = 0;
bool IsFromSpell = false, ExtraAttackOptions *opts = nullptr) = 0;
int MonkSpecialAttack(Mob* other, uint8 skill_used);
virtual void TryBackstab(Mob *other,int ReuseTime = 10);
bool AvoidDamage(Mob* attacker, int32 &damage, int hand);
bool AvoidDamage(Mob* attacker, int &damage, int hand);
int compute_tohit(EQEmu::skills::SkillType skillinuse);
int compute_defense();
virtual bool CheckHitChance(Mob* attacker, EQEmu::skills::SkillType skillinuse, int chance_mod = 0);
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, EQEmu::skills::SkillType skillinuse);
uint32 TryHeadShot(Mob* defender, EQEmu::skills::SkillType skillInUse);
uint32 TryAssassinate(Mob* defender, EQEmu::skills::SkillType skillInUse, uint16 ReuseTime);
bool CheckHitChance(Mob* attacker, EQEmu::skills::SkillType skillinuse, int chance_mod = 0);
virtual void TryCriticalHit(Mob *defender, uint16 skill, int &damage, int min_damage, ExtraAttackOptions *opts = nullptr);
void TryPetCriticalHit(Mob *defender, uint16 skill, int &damage);
virtual bool TryFinishingBlow(Mob *defender, EQEmu::skills::SkillType skillinuse, int &damage);
int TryHeadShot(Mob* defender, EQEmu::skills::SkillType skillInUse);
int TryAssassinate(Mob* defender, EQEmu::skills::SkillType skillInUse, uint16 ReuseTime);
virtual void DoRiposte(Mob* defender);
void ApplyMeleeDamageBonus(uint16 skill, int32 &damage,ExtraAttackOptions *opts = nullptr);
virtual void MeleeMitigation(Mob *attacker, int32 &damage, int32 minhit, ExtraAttackOptions *opts = nullptr);
virtual int32 GetMeleeMitDmg(Mob *attacker, int32 damage, int32 minhit, float mit_rating, float atk_rating);
void ApplyMeleeDamageBonus(uint16 skill, int &damage,ExtraAttackOptions *opts = nullptr);
int ACSum();
int offense(EQEmu::skills::SkillType skill);
void CalcAC() { mitigation_ac = ACSum(); }
int GetACSoftcap();
double GetSoftcapReturns();
int GetClassRaceACBonus();
inline int GetMitigationAC() { return mitigation_ac; }
void MeleeMitigation(Mob *attacker, int &damage, int base_damage, int offense, EQEmu::skills::SkillType, ExtraAttackOptions *opts = nullptr);
double RollD20(double offense, double mitigation); // CALL THIS FROM THE DEFENDER
bool CombatRange(Mob* other);
virtual inline bool IsBerserk() { return false; } // only clients
void RogueEvade(Mob *other);
void CommonOutgoingHitSuccess(Mob* defender, int32 &damage, EQEmu::skills::SkillType skillInUse, ExtraAttackOptions *opts = nullptr);
void CommonOutgoingHitSuccess(Mob* defender, int &damage, int min_damage, int min_mod, EQEmu::skills::SkillType skillInUse, ExtraAttackOptions *opts = nullptr);
void BreakInvisibleSpells();
virtual void CancelSneakHide();
void CommonBreakInvisible();
void CommonBreakInvisibleFromCombat();
bool HasDied();
virtual bool CheckDualWield();
void DoMainHandAttackRounds(Mob *target, ExtraAttackOptions *opts = nullptr, int special = 0);
void DoOffHandAttackRounds(Mob *target, ExtraAttackOptions *opts = nullptr, int special = 0);
void DoMainHandAttackRounds(Mob *target, ExtraAttackOptions *opts = nullptr);
void DoOffHandAttackRounds(Mob *target, ExtraAttackOptions *opts = nullptr);
virtual bool CheckDoubleAttack();
// inline process for places where we need to do them outside of the AI_Process
void ProcessAttackRounds(Mob *target, ExtraAttackOptions *opts = nullptr, int special = 0)
void ProcessAttackRounds(Mob *target, ExtraAttackOptions *opts = nullptr)
{
if (target) {
DoMainHandAttackRounds(target, opts, special);
DoMainHandAttackRounds(target, opts);
if (CanThisClassDualWield())
DoOffHandAttackRounds(target, opts, special);
DoOffHandAttackRounds(target, opts);
}
return;
}
@ -367,7 +380,7 @@ public:
bool AffectedBySpellExcludingSlot(int slot, int effect);
virtual bool Death(Mob* killerMob, int32 damage, uint16 spell_id, EQEmu::skills::SkillType attack_skill) = 0;
virtual void Damage(Mob* from, int32 damage, uint16 spell_id, EQEmu::skills::SkillType attack_skill,
bool avoidable = true, int8 buffslot = -1, bool iBuffTic = false, int special = 0) = 0;
bool avoidable = true, int8 buffslot = -1, bool iBuffTic = false, eSpecialAttacks special = eSpecialAttacks::None) = 0;
inline virtual void SetHP(int32 hp) { if (hp >= max_hp) cur_hp = max_hp; else cur_hp = hp;}
bool ChangeHP(Mob* other, int32 amount, uint16 spell_id = 0, int8 buffslot = -1, bool iBuffTic = false);
inline void SetOOCRegen(int32 newoocregen) {oocregen = newoocregen;}
@ -406,7 +419,7 @@ public:
virtual void SetTarget(Mob* mob);
virtual inline float GetHPRatio() const { return max_hp == 0 ? 0 : ((float)cur_hp/max_hp*100); }
virtual inline int GetIntHPRatio() const { return max_hp == 0 ? 0 : static_cast<int>(cur_hp * 100 / max_hp); }
inline virtual int32 GetAC() const { return AC + itembonuses.AC + spellbonuses.AC; }
inline int32 GetAC() const { return AC; }
inline virtual int32 GetATK() const { return ATK + itembonuses.ATK + spellbonuses.ATK; }
inline virtual int32 GetATKBonus() const { return itembonuses.ATK + spellbonuses.ATK; }
inline virtual int32 GetSTR() const { return STR + itembonuses.STR + spellbonuses.STR; }
@ -673,7 +686,7 @@ public:
int16 GetMeleeMinDamageMod_SE(uint16 skill);
int16 GetCrippBlowChance();
int16 GetSkillReuseTime(uint16 skill);
int16 GetCriticalChanceBonus(uint16 skill);
int GetCriticalChanceBonus(uint16 skill);
int16 GetSkillDmgAmt(uint16 skill);
bool TryReflectSpell(uint32 spell_id);
bool CanBlockSpell() const { return(spellbonuses.BlockNextSpell); }
@ -778,7 +791,8 @@ public:
int32 GetMeleeMitigation();
uint8 GetWeaponDamageBonus(const EQEmu::ItemData* weapon, bool offhand = false);
uint16 GetDamageTable(EQEmu::skills::SkillType skillinuse);
const DamageTable &GetDamageTable() const;
void ApplyDamageTable(int &damage, int offense);
virtual int GetHandToHandDamage(void);
bool CanThisClassDoubleAttack(void) const;
@ -801,9 +815,9 @@ public:
int32 AffectMagicalDamage(int32 damage, uint16 spell_id, const bool iBuffTic, Mob* attacker);
int32 ReduceAllDamage(int32 damage);
virtual void DoSpecialAttackDamage(Mob *who, EQEmu::skills::SkillType skill, int32 max_damage, int32 min_damage = 1, int32 hate_override = -1, int ReuseTime = 10, bool CheckHitChance = false, bool CanAvoid = true);
void DoSpecialAttackDamage(Mob *who, EQEmu::skills::SkillType skill, int base_damage, int min_damage = 0, int32 hate_override = -1, int ReuseTime = 10, bool CheckHitChance = false, bool CanAvoid = true);
virtual void DoThrowingAttackDmg(Mob* other, const EQEmu::ItemInstance* RangeWeapon = nullptr, const EQEmu::ItemData* AmmoItem = nullptr, uint16 weapon_damage = 0, int16 chance_mod = 0, int16 focus = 0, int ReuseTime = 0, uint32 range_id = 0, int AmmoSlot = 0, float speed = 4.0f);
virtual void DoMeleeSkillAttackDmg(Mob* other, uint16 weapon_damage, EQEmu::skills::SkillType skillinuse, int16 chance_mod = 0, int16 focus = 0, bool CanRiposte = false, int ReuseTime = 0);
void DoMeleeSkillAttackDmg(Mob* other, uint16 weapon_damage, EQEmu::skills::SkillType skillinuse, int16 chance_mod = 0, int16 focus = 0, bool CanRiposte = false, int ReuseTime = 0);
virtual void DoArcheryAttackDmg(Mob* other, const EQEmu::ItemInstance* RangeWeapon = nullptr, const EQEmu::ItemInstance* Ammo = nullptr, uint16 weapon_damage = 0, int16 chance_mod = 0, int16 focus = 0, int ReuseTime = 0, uint32 range_id = 0, uint32 ammo_id = 0, const EQEmu::ItemData *AmmoItem = nullptr, int AmmoSlot = 0, float speed = 4.0f);
bool TryProjectileAttack(Mob* other, const EQEmu::ItemData *item, EQEmu::skills::SkillType skillInUse, uint16 weapon_dmg, const EQEmu::ItemInstance* RangeWeapon, const EQEmu::ItemInstance* Ammo, int AmmoSlot, float speed);
void ProjectileAttack();
@ -816,6 +830,7 @@ public:
bool AddRampage(Mob*);
void ClearRampage();
void AreaRampage(ExtraAttackOptions *opts);
inline bool IsSpecialAttack(eSpecialAttacks in) { return m_specialattacks == in; }
void StartEnrage();
void ProcessEnrage();
@ -1042,7 +1057,7 @@ public:
#endif
protected:
void CommonDamage(Mob* other, int32 &damage, const uint16 spell_id, const EQEmu::skills::SkillType attack_skill, bool &avoidable, const int8 buffslot, const bool iBuffTic, int special = 0);
void CommonDamage(Mob* other, int &damage, const uint16 spell_id, const EQEmu::skills::SkillType attack_skill, bool &avoidable, const int8 buffslot, const bool iBuffTic, eSpecialAttacks specal = eSpecialAttacks::None);
static uint16 GetProcID(uint16 spell_id, uint8 effect_index);
float _GetMovementSpeed(int mod) const;
int _GetWalkSpeed() const;
@ -1079,6 +1094,7 @@ protected:
bool multitexture;
int AC;
int mitigation_ac; // cached Mob::ACSum
int32 ATK;
int32 STR;
int32 STA;
@ -1150,6 +1166,7 @@ protected:
int base_walkspeed;
int base_fearspeed;
int current_speed;
eSpecialAttacks m_specialattacks;
uint32 pLastChange;
bool held;
@ -1174,9 +1191,10 @@ protected:
uint16 GetWeaponSpeedbyHand(uint16 hand);
int GetWeaponDamage(Mob *against, const EQEmu::ItemData *weapon_item);
int GetWeaponDamage(Mob *against, const EQEmu::ItemInstance *weapon_item, uint32 *hate = nullptr);
int GetKickDamage();
int GetBashDamage();
virtual void ApplySpecialAttackMod(EQEmu::skills::SkillType skill, int32 &dmg, int32 &mindmg);
#ifdef BOTS
virtual
#endif
int GetBaseSkillDamage(EQEmu::skills::SkillType skill, Mob *target = nullptr);
virtual int16 GetFocusEffect(focusType type, uint16 spell_id) { return 0; }
void CalculateNewFearpoint();
float FindGroundZ(float new_x, float new_y, float z_offset=0.0);
@ -1211,7 +1229,7 @@ protected:
Timer attack_dw_timer;
Timer ranged_timer;
float attack_speed; //% increase/decrease in attack speed (not haste)
int8 attack_delay; //delay between attacks in 10ths of seconds
int attack_delay; //delay between attacks in 10ths of seconds
int16 slow_mitigation; // Allows for a slow mitigation (100 = 100%, 50% = 50%)
Timer tic_timer;
Timer mana_timer;

View File

@ -1160,11 +1160,8 @@ void Mob::AI_Process() {
if ((IsPet() || IsTempPet()) && IsPetOwnerClient()){
if (spellbonuses.PC_Pet_Rampage[0] || itembonuses.PC_Pet_Rampage[0] || aabonuses.PC_Pet_Rampage[0]){
int chance = spellbonuses.PC_Pet_Rampage[0] + itembonuses.PC_Pet_Rampage[0] + aabonuses.PC_Pet_Rampage[0];
int dmg_mod = spellbonuses.PC_Pet_Rampage[1] + itembonuses.PC_Pet_Rampage[1] + aabonuses.PC_Pet_Rampage[1];
if(zone->random.Roll(chance)) {
ExtraAttackOptions opts;
opts.damage_percent = dmg_mod / 100.0f;
Rampage(&opts);
Rampage(nullptr);
}
}
}
@ -1175,12 +1172,7 @@ void Mob::AI_Process() {
rampage_chance = rampage_chance > 0 ? rampage_chance : 20;
if(zone->random.Roll(rampage_chance)) {
ExtraAttackOptions opts;
int cur = GetSpecialAbilityParam(SPECATK_RAMPAGE, 2);
if(cur > 0) {
opts.damage_percent = cur / 100.0f;
}
cur = GetSpecialAbilityParam(SPECATK_RAMPAGE, 3);
int cur = GetSpecialAbilityParam(SPECATK_RAMPAGE, 3);
if(cur > 0) {
opts.damage_flat = cur;
}
@ -1215,12 +1207,7 @@ void Mob::AI_Process() {
rampage_chance = rampage_chance > 0 ? rampage_chance : 20;
if(zone->random.Roll(rampage_chance)) {
ExtraAttackOptions opts;
int cur = GetSpecialAbilityParam(SPECATK_AREA_RAMPAGE, 2);
if(cur > 0) {
opts.damage_percent = cur / 100.0f;
}
cur = GetSpecialAbilityParam(SPECATK_AREA_RAMPAGE, 3);
int cur = GetSpecialAbilityParam(SPECATK_AREA_RAMPAGE, 3);
if(cur > 0) {
opts.damage_flat = cur;
}
@ -1992,6 +1979,8 @@ bool Mob::Rampage(ExtraAttackOptions *opts)
rampage_targets = RuleI(Combat, DefaultRampageTargets);
if (rampage_targets > RuleI(Combat, MaxRampageTargets))
rampage_targets = RuleI(Combat, MaxRampageTargets);
m_specialattacks = eSpecialAttacks::Rampage;
for (int i = 0; i < RampageArray.size(); i++) {
if (index_hit >= rampage_targets)
break;
@ -2001,14 +1990,16 @@ bool Mob::Rampage(ExtraAttackOptions *opts)
if (m_target == GetTarget())
continue;
if (CombatRange(m_target)) {
ProcessAttackRounds(m_target, opts, 2);
ProcessAttackRounds(m_target, opts);
index_hit++;
}
}
}
if (RuleB(Combat, RampageHitsTarget) && index_hit < rampage_targets)
ProcessAttackRounds(GetTarget(), opts, 2);
ProcessAttackRounds(GetTarget(), opts);
m_specialattacks = eSpecialAttacks::None;
return true;
}
@ -2024,10 +2015,12 @@ void Mob::AreaRampage(ExtraAttackOptions *opts)
int rampage_targets = GetSpecialAbilityParam(SPECATK_AREA_RAMPAGE, 1);
rampage_targets = rampage_targets > 0 ? rampage_targets : -1;
m_specialattacks = eSpecialAttacks::AERampage;
index_hit = hate_list.AreaRampage(this, GetTarget(), rampage_targets, opts);
if(index_hit == 0)
ProcessAttackRounds(GetTarget(), opts, 1);
ProcessAttackRounds(GetTarget(), opts);
m_specialattacks = eSpecialAttacks::None;
}
uint32 Mob::GetLevelCon(uint8 mylevel, uint8 iOtherLevel) {

View File

@ -201,6 +201,9 @@ NPC::NPC(const NPCType* d, Spawn2* in_respawn, const glm::vec4& position, int if
CalcNPCDamage();
}
base_damage = round((max_dmg - min_dmg) / 1.9);
min_damage = min_dmg - round(base_damage / 10.0);
accuracy_rating = d->accuracy_rating;
avoidance_rating = d->avoidance_rating;
ATK = d->ATK;
@ -1054,7 +1057,7 @@ NPC* NPC::SpawnNPC(const char* spawncommand, const glm::vec4& position, Client*
npc_type->WIS = 150;
npc_type->CHA = 150;
npc_type->attack_delay = 30;
npc_type->attack_delay = 3000;
npc_type->prim_melee_type = 28;
npc_type->sec_melee_type = 28;
@ -1984,13 +1987,25 @@ void NPC::ModifyNPCStat(const char *identifier, const char *newValue)
else if(id == "special_attacks") { NPCSpecialAttacks(val.c_str(), 0, 1); return; }
else if(id == "special_abilities") { ProcessSpecialAbilities(val.c_str()); return; }
else if(id == "attack_speed") { attack_speed = (float)atof(val.c_str()); CalcBonuses(); return; }
else if(id == "attack_delay") { attack_delay = atoi(val.c_str()); CalcBonuses(); return; }
else if(id == "attack_delay") { /* TODO: fix DB */attack_delay = atoi(val.c_str()) * 100; CalcBonuses(); return; }
else if(id == "atk") { ATK = atoi(val.c_str()); return; }
else if(id == "accuracy") { accuracy_rating = atoi(val.c_str()); return; }
else if(id == "avoidance") { avoidance_rating = atoi(val.c_str()); return; }
else if(id == "trackable") { trackable = atoi(val.c_str()); return; }
else if(id == "min_hit") { min_dmg = atoi(val.c_str()); return; }
else if(id == "max_hit") { max_dmg = atoi(val.c_str()); return; }
else if(id == "min_hit") {
min_dmg = atoi(val.c_str());
// TODO: fix DB
base_damage = round((max_dmg - min_dmg) / 1.9);
min_damage = min_dmg - round(base_damage / 10.0);
return;
}
else if(id == "max_hit") {
max_dmg = atoi(val.c_str());
// TODO: fix DB
base_damage = round((max_dmg - min_dmg) / 1.9);
min_damage = min_dmg - round(base_damage / 10.0);
return;
}
else if(id == "attack_count") { attack_count = atoi(val.c_str()); return; }
else if(id == "see_invis") { see_invis = atoi(val.c_str()); return; }
else if(id == "see_invis_undead") { see_invis_undead = atoi(val.c_str()); return; }

View File

@ -109,9 +109,9 @@ public:
//abstract virtual function implementations requird by base abstract class
virtual bool Death(Mob* killerMob, int32 damage, uint16 spell_id, EQEmu::skills::SkillType attack_skill);
virtual void Damage(Mob* from, int32 damage, uint16 spell_id, EQEmu::skills::SkillType attack_skill, bool avoidable = true, int8 buffslot = -1, bool iBuffTic = false, int special = 0);
virtual void Damage(Mob* from, int32 damage, uint16 spell_id, EQEmu::skills::SkillType attack_skill, bool avoidable = true, int8 buffslot = -1, bool iBuffTic = false, eSpecialAttacks special = eSpecialAttacks::None);
virtual bool Attack(Mob* other, int Hand = EQEmu::inventory::slotPrimary, bool FromRiposte = false, bool IsStrikethrough = false,
bool IsFromSpell = false, ExtraAttackOptions *opts = nullptr, int special = 0);
bool IsFromSpell = false, ExtraAttackOptions *opts = nullptr);
virtual bool HasRaid() { return false; }
virtual bool HasGroup() { return false; }
virtual Raid* GetRaid() { return 0; }
@ -259,9 +259,11 @@ public:
uint32 GetMaxDMG() const {return max_dmg;}
uint32 GetMinDMG() const {return min_dmg;}
int GetBaseDamage() const { return base_damage; }
int GetMinDamage() const { return min_damage; }
float GetSlowMitigation() const { return slow_mitigation; }
float GetAttackSpeed() const {return attack_speed;}
uint8 GetAttackDelay() const {return attack_delay;}
int GetAttackDelay() const {return attack_delay;}
bool IsAnimal() const { return(bodytype == BT_Animal); }
uint16 GetPetSpellID() const {return pet_spell_id;}
void SetPetSpellID(uint16 amt) {pet_spell_id = amt;}
@ -463,6 +465,8 @@ protected:
uint32 max_dmg;
uint32 min_dmg;
int base_damage;
int min_damage;
int32 accuracy_rating;
int32 avoidance_rating;
int16 attack_count;

File diff suppressed because it is too large Load Diff

View File

@ -486,7 +486,7 @@ int32 Mob::Tune_MeleeMitigation(Mob* GM, Mob *attacker, int32 damage, int32 minh
}
damage = GetMeleeMitDmg(attacker, damage, minhit, mitigation_rating, attack_rating);
//damage = GetMeleeMitDmg(attacker, damage, minhit, mitigation_rating, attack_rating);
}
if (damage < 0)
@ -539,7 +539,7 @@ int32 Client::Tune_GetMeleeMitDmg(Mob* GM, Mob *attacker, int32 damage, int32 mi
float mit_rating, float atk_rating)
{
if (!attacker->IsNPC() || RuleB(Combat, UseOldDamageIntervalRules))
return Mob::GetMeleeMitDmg(attacker, damage, minhit, mit_rating, atk_rating);
return 0; //Mob::GetMeleeMitDmg(attacker, damage, minhit, mit_rating, atk_rating);
int d = 10;
// floats for the rounding issues
float dmg_interval = (damage - minhit) / 19.0;
@ -613,7 +613,7 @@ int32 Client::GetMeleeDamage(Mob* other, bool GetMinDamage)
}
int min_hit = 1;
int max_hit = (2*weapon_damage*GetDamageTable(skillinuse)) / 100;
int max_hit = 2;//(2*weapon_damage*GetDamageTable(skillinuse)) / 100;
if(GetLevel() < 10 && max_hit > RuleI(Combat, HitCapPre10))
max_hit = (RuleI(Combat, HitCapPre10));
@ -1086,4 +1086,4 @@ float Mob::Tune_CheckHitChance(Mob* defender, Mob* attacker, EQEmu::skills::Skil
}
return(chancetohit);
}
}

View File

@ -2133,7 +2133,7 @@ const NPCType* ZoneDatabase::LoadNPCTypesData(uint32 npc_type_id, bool bulk_load
temp_npctype_data->healscale = atoi(row[87]);
temp_npctype_data->no_target_hotkey = atoi(row[88]) == 1 ? true: false;
temp_npctype_data->raid_target = atoi(row[89]) == 0 ? false: true;
temp_npctype_data->attack_delay = atoi(row[90]);
temp_npctype_data->attack_delay = atoi(row[90]) * 100; // TODO: fix DB
temp_npctype_data->light = (atoi(row[91]) & 0x0F);
temp_npctype_data->armtexture = atoi(row[92]);

View File

@ -110,7 +110,7 @@ struct NPCType
uint8 spawn_limit; //only this many may be in zone at a time (0=no limit)
uint8 mount_color; //only used by horse class
float attack_speed; //%+- on attack delay of the mob.
uint8 attack_delay; //delay between attacks in 10ths of a second
int attack_delay; //delay between attacks in ms
int accuracy_rating; // flat bonus before mods
int avoidance_rating; // flat bonus before mods
bool findable; //can be found with find command