mirror of
https://github.com/EQEmu/Server.git
synced 2025-12-16 01:01:30 +00:00
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:
parent
891fa0411c
commit
9e824876ba
@ -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
|
||||
|
||||
@ -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();
|
||||
|
||||
|
||||
1437
zone/attack.cpp
1437
zone/attack.cpp
File diff suppressed because it is too large
Load Diff
@ -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; }
|
||||
|
||||
@ -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.
|
||||
}
|
||||
|
||||
451
zone/bot.cpp
451
zone/bot.cpp
@ -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);
|
||||
|
||||
10
zone/bot.h
10
zone/bot.h
@ -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; }
|
||||
|
||||
@ -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());
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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()) {
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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; }
|
||||
|
||||
@ -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; }
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
76
zone/mob.h
76
zone/mob.h
@ -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;
|
||||
|
||||
@ -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) {
|
||||
|
||||
23
zone/npc.cpp
23
zone/npc.cpp
@ -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; }
|
||||
|
||||
10
zone/npc.h
10
zone/npc.h
@ -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
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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]);
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user