mirror of
https://github.com/EQEmu/Server.git
synced 2025-12-11 21:01:29 +00:00
4335 lines
140 KiB
C++
4335 lines
140 KiB
C++
/* EQEMu: Everquest Server Emulator
|
|
Copyright (C) 2001-2002 EQEMu Development Team (http://eqemulator.net)
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; version 2 of the License.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY except by those people which sell it, which
|
|
are required to give you total support for your newly bought product;
|
|
without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
|
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
|
|
#if EQDEBUG >= 5
|
|
//#define ATTACK_DEBUG 20
|
|
#endif
|
|
|
|
#include "../common/debug.h"
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
#include <math.h>
|
|
#include <iostream>
|
|
using namespace std;
|
|
#include <assert.h>
|
|
|
|
#include "masterentity.h"
|
|
#include "NpcAI.h"
|
|
#include "../common/packet_dump.h"
|
|
#include "../common/eq_packet_structs.h"
|
|
#include "../common/eq_constants.h"
|
|
#include "../common/skills.h"
|
|
#include "../common/spdat.h"
|
|
#include "zone.h"
|
|
#include "StringIDs.h"
|
|
#include "../common/MiscFunctions.h"
|
|
#include "../common/rulesys.h"
|
|
#include "QuestParserCollection.h"
|
|
#include "watermap.h"
|
|
#include "worldserver.h"
|
|
extern WorldServer worldserver;
|
|
|
|
#ifdef _WINDOWS
|
|
#define snprintf _snprintf
|
|
#define strncasecmp _strnicmp
|
|
#define strcasecmp _stricmp
|
|
#endif
|
|
|
|
extern EntityList entity_list;
|
|
extern Zone* zone;
|
|
|
|
bool Mob::AttackAnimation(SkillType &skillinuse, int Hand, const ItemInst* weapon)
|
|
{
|
|
// Determine animation
|
|
int type = 0;
|
|
if (weapon && weapon->IsType(ItemClassCommon)) {
|
|
const Item_Struct* item = weapon->GetItem();
|
|
#if EQDEBUG >= 11
|
|
LogFile->write(EQEMuLog::Debug, "Weapon skill:%i", item->ItemType);
|
|
#endif
|
|
switch (item->ItemType)
|
|
{
|
|
case ItemType1HS: // 1H Slashing
|
|
{
|
|
skillinuse = _1H_SLASHING;
|
|
type = anim1HWeapon;
|
|
break;
|
|
}
|
|
case ItemType2HS: // 2H Slashing
|
|
{
|
|
skillinuse = _2H_SLASHING;
|
|
type = anim2HSlashing;
|
|
break;
|
|
}
|
|
case ItemTypePierce: // Piercing
|
|
{
|
|
skillinuse = PIERCING;
|
|
type = animPiercing;
|
|
break;
|
|
}
|
|
case ItemType1HB: // 1H Blunt
|
|
{
|
|
skillinuse = _1H_BLUNT;
|
|
type = anim1HWeapon;
|
|
break;
|
|
}
|
|
case ItemType2HB: // 2H Blunt
|
|
{
|
|
skillinuse = _2H_BLUNT;
|
|
type = anim2HWeapon;
|
|
break;
|
|
}
|
|
case ItemType2HPierce: // 2H Piercing
|
|
{
|
|
skillinuse = PIERCING;
|
|
type = anim2HWeapon;
|
|
break;
|
|
}
|
|
case ItemTypeHand2Hand:
|
|
{
|
|
skillinuse = HAND_TO_HAND;
|
|
type = animHand2Hand;
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
skillinuse = HAND_TO_HAND;
|
|
type = animHand2Hand;
|
|
break;
|
|
}
|
|
}// switch
|
|
}
|
|
else if(IsNPC()) {
|
|
|
|
switch (skillinuse)
|
|
{
|
|
case _1H_SLASHING: // 1H Slashing
|
|
{
|
|
type = anim1HWeapon;
|
|
break;
|
|
}
|
|
case _2H_SLASHING: // 2H Slashing
|
|
{
|
|
type = anim2HSlashing;
|
|
break;
|
|
}
|
|
case PIERCING: // Piercing
|
|
{
|
|
type = animPiercing;
|
|
break;
|
|
}
|
|
case _1H_BLUNT: // 1H Blunt
|
|
{
|
|
type = anim1HWeapon;
|
|
break;
|
|
}
|
|
case _2H_BLUNT: // 2H Blunt
|
|
{
|
|
type = anim2HWeapon;
|
|
break;
|
|
}
|
|
case 99: // 2H Piercing
|
|
{
|
|
type = anim2HWeapon;
|
|
break;
|
|
}
|
|
case HAND_TO_HAND:
|
|
{
|
|
type = animHand2Hand;
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
type = animHand2Hand;
|
|
break;
|
|
}
|
|
}// switch
|
|
}
|
|
else {
|
|
skillinuse = HAND_TO_HAND;
|
|
type = animHand2Hand;
|
|
}
|
|
|
|
// If we're attacking with the secondary hand, play the dual wield anim
|
|
if (Hand == 14) // DW anim
|
|
type = animDualWield;
|
|
|
|
DoAnim(type);
|
|
return true;
|
|
}
|
|
|
|
// called when a mob is attacked, does the checks to see if it's a hit
|
|
// and does other mitigation checks. 'this' is the mob being attacked.
|
|
bool Mob::CheckHitChance(Mob* other, SkillType skillinuse, int Hand, int16 chance_mod)
|
|
{
|
|
/*/
|
|
//Reworked a lot of this code to achieve better balance at higher levels.
|
|
//The old code basically meant that any in high level (50+) combat,
|
|
//both parties always had 95% chance to hit the other one.
|
|
/*/
|
|
//If chance bonus set in spell data for Skill Attacks is 10k allow to hit without calculations.
|
|
if (chance_mod == 10000)
|
|
return true;
|
|
|
|
Mob *attacker=other;
|
|
Mob *defender=this;
|
|
float chancetohit = RuleR(Combat, BaseHitChance);
|
|
|
|
if(attacker->IsNPC() && !attacker->IsPet())
|
|
chancetohit += RuleR(Combat, NPCBonusHitChance);
|
|
|
|
#if ATTACK_DEBUG>=11
|
|
LogFile->write(EQEMuLog::Debug, "CheckHitChance(%s) attacked by %s", defender->GetName(), attacker->GetName());
|
|
#endif
|
|
mlog(COMBAT__TOHIT,"CheckHitChance(%s) attacked by %s", defender->GetName(), attacker->GetName());
|
|
|
|
bool pvpmode = false;
|
|
if(IsClient() && other->IsClient())
|
|
pvpmode = true;
|
|
|
|
float bonus;
|
|
|
|
////////////////////////////////////////////////////////
|
|
// To hit calcs go here
|
|
////////////////////////////////////////////////////////
|
|
|
|
uint8 attacker_level = attacker->GetLevel() ? attacker->GetLevel() : 1;
|
|
uint8 defender_level = defender->GetLevel() ? defender->GetLevel() : 1;
|
|
|
|
//Calculate the level difference
|
|
|
|
mlog(COMBAT__TOHIT, "Chance to hit before level diff calc %.2f", chancetohit);
|
|
double level_difference = attacker_level - defender_level;
|
|
double range = defender->GetLevel();
|
|
range = ((range / 4) + 3);
|
|
|
|
if(level_difference < 0)
|
|
{
|
|
if(level_difference >= -range)
|
|
{
|
|
chancetohit += (level_difference / range) * RuleR(Combat,HitFalloffMinor); //5
|
|
}
|
|
else if (level_difference >= -(range+3.0))
|
|
{
|
|
chancetohit -= RuleR(Combat,HitFalloffMinor);
|
|
chancetohit += ((level_difference+range) / (3.0)) * RuleR(Combat,HitFalloffModerate); //7
|
|
}
|
|
else
|
|
{
|
|
chancetohit -= (RuleR(Combat,HitFalloffMinor) + RuleR(Combat,HitFalloffModerate));
|
|
chancetohit += ((level_difference+range+3.0)/12.0) * RuleR(Combat,HitFalloffMajor); //50
|
|
}
|
|
}
|
|
else
|
|
{
|
|
chancetohit += (RuleR(Combat,HitBonusPerLevel) * level_difference);
|
|
}
|
|
|
|
mlog(COMBAT__TOHIT, "Chance to hit after level diff calc %.2f", chancetohit);
|
|
|
|
chancetohit -= ((float)defender->GetAGI() * RuleR(Combat, AgiHitFactor));
|
|
|
|
mlog(COMBAT__TOHIT, "Chance to hit after agil calc %.2f", chancetohit);
|
|
|
|
if(attacker->IsClient())
|
|
{
|
|
chancetohit -= (RuleR(Combat,WeaponSkillFalloff) * (attacker->CastToClient()->MaxSkill(skillinuse) - attacker->GetSkill(skillinuse)));
|
|
mlog(COMBAT__TOHIT, "Chance to hit after weapon falloff calc (attack) %.2f", chancetohit);
|
|
}
|
|
|
|
if(defender->IsClient())
|
|
{
|
|
chancetohit += (RuleR(Combat,WeaponSkillFalloff) * (defender->CastToClient()->MaxSkill(DEFENSE) - defender->GetSkill(DEFENSE)));
|
|
mlog(COMBAT__TOHIT, "Chance to hit after weapon falloff calc (defense) %.2f", chancetohit);
|
|
}
|
|
|
|
//I dont think this is 100% correct, but at least it does something...
|
|
if(attacker->spellbonuses.MeleeSkillCheckSkill == skillinuse || attacker->spellbonuses.MeleeSkillCheckSkill == 255) {
|
|
chancetohit += attacker->spellbonuses.MeleeSkillCheck;
|
|
mlog(COMBAT__TOHIT, "Applied spell melee skill bonus %d, yeilding %.2f", attacker->spellbonuses.MeleeSkillCheck, chancetohit);
|
|
}
|
|
if(attacker->itembonuses.MeleeSkillCheckSkill == skillinuse || attacker->itembonuses.MeleeSkillCheckSkill == 255) {
|
|
chancetohit += attacker->itembonuses.MeleeSkillCheck;
|
|
mlog(COMBAT__TOHIT, "Applied item melee skill bonus %d, yeilding %.2f", attacker->spellbonuses.MeleeSkillCheck, chancetohit);
|
|
}
|
|
|
|
//subtract off avoidance by the defender. (Live AA - Combat Agility)
|
|
bonus = defender->spellbonuses.AvoidMeleeChance + defender->itembonuses.AvoidMeleeChance + (defender->aabonuses.AvoidMeleeChance * 10);
|
|
|
|
//AA Live - Elemental Agility
|
|
if (IsPet()) {
|
|
Mob *owner = defender->GetOwner();
|
|
if (!owner)return false;
|
|
bonus += (owner->aabonuses.PetAvoidance + owner->spellbonuses.PetAvoidance + owner->itembonuses.PetAvoidance)*10;
|
|
}
|
|
|
|
if(bonus > 0) {
|
|
chancetohit -= ((bonus * chancetohit) / 1000);
|
|
mlog(COMBAT__TOHIT, "Applied avoidance chance %.2f/10, yeilding %.2f", bonus, chancetohit);
|
|
if (defender->spellbonuses.AvoidMeleeChance)
|
|
defender->CheckHitsRemaining(0, false, false,SE_AvoidMeleeChance);
|
|
}
|
|
|
|
if(attacker->IsNPC())
|
|
chancetohit += (chancetohit * attacker->CastToNPC()->GetAccuracyRating() / 1000);
|
|
|
|
mlog(COMBAT__TOHIT, "Chance to hit after accuracy rating calc %.2f", chancetohit);
|
|
|
|
float hitBonus = 0;
|
|
|
|
/*
|
|
Kayen: Unknown if the HitChance and Accuracy effect's should modify 'chancetohit'
|
|
cumulatively or successively. For now all hitBonuses are cumulative.
|
|
*/
|
|
|
|
hitBonus += attacker->itembonuses.HitChanceEffect[skillinuse] +
|
|
attacker->spellbonuses.HitChanceEffect[skillinuse]+
|
|
attacker->itembonuses.HitChanceEffect[HIGHEST_SKILL+1] +
|
|
attacker->spellbonuses.HitChanceEffect[HIGHEST_SKILL+1];
|
|
|
|
//Accuracy = Spell Effect , HitChance = 'Accuracy' from Item Effect
|
|
//Only AA derived accuracy can be skill limited. ie (Precision of the Pathfinder, Dead Aim)
|
|
hitBonus += (attacker->itembonuses.Accuracy[HIGHEST_SKILL+1] +
|
|
attacker->spellbonuses.Accuracy[HIGHEST_SKILL+1] +
|
|
attacker->aabonuses.Accuracy[HIGHEST_SKILL+1] +
|
|
attacker->aabonuses.Accuracy[skillinuse] +
|
|
attacker->itembonuses.HitChance) / 15.0f;
|
|
|
|
hitBonus += chance_mod; //Modifier applied from casted/disc skill attacks.
|
|
|
|
chancetohit += ((chancetohit * hitBonus) / 100.0f);
|
|
|
|
if(skillinuse == ARCHERY)
|
|
chancetohit -= (chancetohit * RuleR(Combat, ArcheryHitPenalty)) / 100.0f;
|
|
|
|
// Chance to hit; Max 95%, Min 30%
|
|
if(chancetohit > 1000) {
|
|
//if chance to hit is crazy high, that means a discipline is in use, and let it stay there
|
|
}
|
|
else if(chancetohit > 95) {
|
|
chancetohit = 95;
|
|
}
|
|
else if(chancetohit < 5) {
|
|
chancetohit = 5;
|
|
}
|
|
|
|
//I dont know the best way to handle a garunteed hit discipline being used
|
|
//agains a garunteed riposte (for example) discipline... for now, garunteed hit wins
|
|
|
|
|
|
#if EQDEBUG>=11
|
|
LogFile->write(EQEMuLog::Debug, "3 FINAL calculated chance to hit is: %5.2f", chancetohit);
|
|
#endif
|
|
|
|
//
|
|
// Did we hit?
|
|
//
|
|
|
|
float tohit_roll = MakeRandomFloat(0, 100);
|
|
|
|
mlog(COMBAT__TOHIT, "Final hit chance: %.2f%%. Hit roll %.2f", chancetohit, tohit_roll);
|
|
|
|
return(tohit_roll <= chancetohit);
|
|
}
|
|
|
|
|
|
bool Mob::AvoidDamage(Mob* other, int32 &damage, bool CanRiposte)
|
|
{
|
|
/* solar: called when a mob is attacked, does the checks to see if it's a hit
|
|
* and does other mitigation checks. 'this' is the mob being attacked.
|
|
*
|
|
* special return values:
|
|
* -1 - block
|
|
* -2 - parry
|
|
* -3 - riposte
|
|
* -4 - dodge
|
|
*
|
|
*/
|
|
float skill;
|
|
float bonus;
|
|
float RollTable[4] = {0,0,0,0};
|
|
float roll;
|
|
Mob *attacker=other;
|
|
Mob *defender=this;
|
|
|
|
//garunteed hit
|
|
bool ghit = false;
|
|
if((attacker->spellbonuses.MeleeSkillCheck + attacker->itembonuses.MeleeSkillCheck) > 500)
|
|
ghit = true;
|
|
|
|
//////////////////////////////////////////////////////////
|
|
// make enrage same as riposte
|
|
/////////////////////////////////////////////////////////
|
|
if (IsEnraged() && !other->BehindMob(this, other->GetX(), other->GetY())) {
|
|
damage = -3;
|
|
mlog(COMBAT__DAMAGE, "I am enraged, riposting frontal attack.");
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////
|
|
// riposte
|
|
/////////////////////////////////////////////////////////
|
|
float riposte_chance = 0.0f;
|
|
if (CanRiposte && damage > 0 && CanThisClassRiposte() && !other->BehindMob(this, other->GetX(), other->GetY()))
|
|
{
|
|
riposte_chance = (100.0f + (float)defender->aabonuses.RiposteChance + (float)defender->spellbonuses.RiposteChance + (float)defender->itembonuses.RiposteChance) / 100.0f;
|
|
skill = GetSkill(RIPOSTE);
|
|
if (IsClient()) {
|
|
CastToClient()->CheckIncreaseSkill(RIPOSTE, other, -10);
|
|
}
|
|
|
|
if (!ghit) { //if they are not using a garunteed hit discipline
|
|
bonus = 2.0 + skill/60.0 + (GetDEX()/200);
|
|
bonus *= riposte_chance;
|
|
RollTable[0] = bonus + (itembonuses.HeroicDEX / 25); // 25 heroic = 1%, applies to ripo, parry, block
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////////////////////////
|
|
// block
|
|
///////////////////////////////////////////////////////
|
|
|
|
bool bBlockFromRear = false;
|
|
bool bShieldBlockFromRear = false;
|
|
|
|
if (this->IsClient()) {
|
|
int aaChance = 0;
|
|
|
|
// a successful roll on this does not mean a successful block is forthcoming. only that a chance to block
|
|
// from a direction other than the rear is granted.
|
|
|
|
//Live AA - HightenedAwareness
|
|
int BlockBehindChance = aabonuses.BlockBehind + spellbonuses.BlockBehind + itembonuses.BlockBehind;
|
|
|
|
if (BlockBehindChance && (BlockBehindChance > MakeRandomInt(1, 100))){
|
|
bBlockFromRear = true;
|
|
|
|
if (spellbonuses.BlockBehind || itembonuses.BlockBehind)
|
|
bShieldBlockFromRear = true; //This bonus should allow a chance to Shield Block from behind.
|
|
}
|
|
}
|
|
|
|
float block_chance = 0.0f;
|
|
if (damage > 0 && CanThisClassBlock() && (!other->BehindMob(this, other->GetX(), other->GetY()) || bBlockFromRear)) {
|
|
block_chance = (100.0f + (float)spellbonuses.IncreaseBlockChance + (float)itembonuses.IncreaseBlockChance) / 100.0f;
|
|
skill = CastToClient()->GetSkill(BLOCKSKILL);
|
|
if (IsClient()) {
|
|
CastToClient()->CheckIncreaseSkill(BLOCKSKILL, other, -10);
|
|
}
|
|
|
|
if (!ghit) { //if they are not using a garunteed hit discipline
|
|
bonus = 2.0 + skill/35.0 + (GetDEX()/200);
|
|
RollTable[1] = RollTable[0] + (bonus * block_chance);
|
|
}
|
|
}
|
|
else{
|
|
RollTable[1] = RollTable[0];
|
|
}
|
|
|
|
if(damage > 0 && (aabonuses.ShieldBlock || spellbonuses.ShieldBlock || itembonuses.ShieldBlock)
|
|
&& (!other->BehindMob(this, other->GetX(), other->GetY()) || bShieldBlockFromRear)) {
|
|
bool equiped = CastToClient()->m_inv.GetItem(14);
|
|
if(equiped) {
|
|
uint8 shield = CastToClient()->m_inv.GetItem(14)->GetItem()->ItemType;
|
|
float bonusShieldBlock = 0.0f;
|
|
if(shield == ItemTypeShield) {
|
|
|
|
//Live AA - Shield Block
|
|
bonusShieldBlock = aabonuses.ShieldBlock + spellbonuses.ShieldBlock + itembonuses.ShieldBlock;
|
|
RollTable[1] += bonusShieldBlock;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(damage > 0 && (aabonuses.TwoHandBluntBlock || spellbonuses.TwoHandBluntBlock || itembonuses.TwoHandBluntBlock)
|
|
&& (!other->BehindMob(this, other->GetX(), other->GetY()) || bShieldBlockFromRear)) {
|
|
bool equiped2 = CastToClient()->m_inv.GetItem(13);
|
|
if(equiped2) {
|
|
uint8 TwoHandBlunt = CastToClient()->m_inv.GetItem(13)->GetItem()->ItemType;
|
|
float bonusStaffBlock = 0.0f;
|
|
if(TwoHandBlunt == ItemType2HB) {
|
|
|
|
bonusStaffBlock = aabonuses.TwoHandBluntBlock + spellbonuses.TwoHandBluntBlock + itembonuses.TwoHandBluntBlock;
|
|
RollTable[1] += bonusStaffBlock;
|
|
}
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////
|
|
// parry
|
|
//////////////////////////////////////////////////////
|
|
float parry_chance = 0.0f;
|
|
if (damage > 0 && CanThisClassParry() && !other->BehindMob(this, other->GetX(), other->GetY()))
|
|
{
|
|
parry_chance = (100.0f + (float)defender->spellbonuses.ParryChance + (float)defender->itembonuses.ParryChance) / 100.0f;
|
|
skill = CastToClient()->GetSkill(PARRY);
|
|
if (IsClient()) {
|
|
CastToClient()->CheckIncreaseSkill(PARRY, other, -10);
|
|
}
|
|
|
|
if (!ghit) { //if they are not using a garunteed hit discipline
|
|
bonus = 2.0 + skill/60.0 + (GetDEX()/200);
|
|
bonus *= parry_chance;
|
|
RollTable[2] = RollTable[1] + bonus;
|
|
}
|
|
}
|
|
else{
|
|
RollTable[2] = RollTable[1];
|
|
}
|
|
|
|
////////////////////////////////////////////////////////
|
|
// dodge
|
|
////////////////////////////////////////////////////////
|
|
float dodge_chance = 0.0f;
|
|
if (damage > 0 && CanThisClassDodge() && !other->BehindMob(this, other->GetX(), other->GetY()))
|
|
{
|
|
dodge_chance = (100.0f + (float)defender->spellbonuses.DodgeChance + (float)defender->itembonuses.DodgeChance) / 100.0f;
|
|
skill = CastToClient()->GetSkill(DODGE);
|
|
if (IsClient()) {
|
|
CastToClient()->CheckIncreaseSkill(DODGE, other, -10);
|
|
}
|
|
|
|
if (!ghit) { //if they are not using a garunteed hit discipline
|
|
bonus = 2.0 + skill/60.0 + (GetAGI()/200);
|
|
bonus *= dodge_chance;
|
|
RollTable[3] = RollTable[2] + bonus - (itembonuses.HeroicDEX / 25) + (itembonuses.HeroicAGI / 25);
|
|
}
|
|
}
|
|
else{
|
|
RollTable[3] = RollTable[2];
|
|
}
|
|
|
|
if(damage > 0){
|
|
roll = MakeRandomFloat(0,100);
|
|
if(roll <= RollTable[0]){
|
|
damage = -3;
|
|
}
|
|
else if(roll <= RollTable[1]){
|
|
damage = -1;
|
|
}
|
|
else if(roll <= RollTable[2]){
|
|
damage = -2;
|
|
}
|
|
else if(roll <= RollTable[3]){
|
|
damage = -4;
|
|
}
|
|
}
|
|
|
|
mlog(COMBAT__DAMAGE, "Final damage after all avoidances: %d", damage);
|
|
|
|
if (damage < 0)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
void Mob::MeleeMitigation(Mob *attacker, int32 &damage, int32 minhit)
|
|
{
|
|
if(damage <= 0)
|
|
return;
|
|
|
|
Mob* defender = this;
|
|
float aa_mit = 0;
|
|
|
|
aa_mit = (aabonuses.CombatStability + itembonuses.CombatStability + spellbonuses.CombatStability)/100.0f;
|
|
|
|
if(RuleB(Combat, UseIntervalAC))
|
|
{
|
|
float softcap = 0.0;
|
|
float mitigation_rating = 0.0;
|
|
float attack_rating = 0.0;
|
|
int shield_ac = 0;
|
|
int armor = 0;
|
|
float weight = 0.0;
|
|
if(IsClient())
|
|
{
|
|
armor = CastToClient()->GetRawACNoShield(shield_ac);
|
|
weight = (CastToClient()->CalcCurrentWeight() / 10.0);
|
|
}
|
|
else if(IsNPC())
|
|
{
|
|
armor = spellbonuses.AC + itembonuses.AC + (CastToNPC()->GetRawAC() / RuleR(Combat, NPCACFactor)) + 1;
|
|
}
|
|
|
|
if(GetClass() == WIZARD || GetClass() == MAGICIAN || GetClass() == NECROMANCER || GetClass() == ENCHANTER)
|
|
{
|
|
softcap = RuleI(Combat, ClothACSoftcap);
|
|
}
|
|
else if(GetClass() == MONK && weight <= 15.0)
|
|
{
|
|
softcap = RuleI(Combat, MonkACSoftcap);
|
|
}
|
|
else if(GetClass() == DRUID || GetClass() == BEASTLORD || GetClass() == MONK)
|
|
{
|
|
softcap = RuleI(Combat, LeatherACSoftcap);
|
|
}
|
|
else if(GetClass() == SHAMAN || GetClass() == ROGUE || GetClass() == BERSERKER || GetClass() == RANGER)
|
|
{
|
|
softcap = RuleI(Combat, ChainACSoftcap);
|
|
}
|
|
else
|
|
{
|
|
softcap = RuleI(Combat, PlateACSoftcap);
|
|
}
|
|
|
|
softcap += shield_ac;
|
|
armor += shield_ac;
|
|
softcap += (softcap * (aa_mit * RuleR(Combat, AAMitigationACFactor)));
|
|
if(armor > softcap)
|
|
{
|
|
int softcap_armor = armor - softcap;
|
|
if(GetClass() == WARRIOR)
|
|
{
|
|
softcap_armor = softcap_armor * RuleR(Combat, WarriorACSoftcapReturn);
|
|
}
|
|
else if(GetClass() == SHADOWKNIGHT || GetClass() == PALADIN || (GetClass() == MONK && weight <= 15.0))
|
|
{
|
|
softcap_armor = softcap_armor * RuleR(Combat, KnightACSoftcapReturn);
|
|
}
|
|
else if(GetClass() == CLERIC || GetClass() == BARD || GetClass() == BERSERKER || GetClass() == ROGUE || GetClass() == SHAMAN || GetClass() == MONK)
|
|
{
|
|
softcap_armor = softcap_armor * RuleR(Combat, LowPlateChainACSoftcapReturn);
|
|
}
|
|
else if(GetClass() == RANGER || GetClass() == BEASTLORD)
|
|
{
|
|
softcap_armor = softcap_armor * RuleR(Combat, LowChainLeatherACSoftcapReturn);
|
|
}
|
|
else if(GetClass() == WIZARD || GetClass() == MAGICIAN || GetClass() == NECROMANCER || GetClass() == ENCHANTER || GetClass() == DRUID)
|
|
{
|
|
softcap_armor = softcap_armor * RuleR(Combat, CasterACSoftcapReturn);
|
|
}
|
|
else
|
|
{
|
|
softcap_armor = softcap_armor * RuleR(Combat, MiscACSoftcapReturn);
|
|
}
|
|
armor = softcap + softcap_armor;
|
|
}
|
|
|
|
mitigation_rating = 0.0;
|
|
if(GetClass() == WIZARD || GetClass() == MAGICIAN || GetClass() == NECROMANCER || GetClass() == ENCHANTER)
|
|
{
|
|
mitigation_rating = ((GetSkill(DEFENSE) + itembonuses.HeroicAGI/10) / 4.0) + armor + 1;
|
|
}
|
|
else
|
|
{
|
|
mitigation_rating = ((GetSkill(DEFENSE) + itembonuses.HeroicAGI/10) / 3.0) + (armor * 1.333333) + 1;
|
|
}
|
|
mitigation_rating *= 0.847;
|
|
|
|
if(attacker->IsClient())
|
|
{
|
|
attack_rating = (attacker->CastToClient()->CalcATK() + ((attacker->GetSTR()-66) * 0.9) + (attacker->GetSkill(OFFENSE)*1.345));
|
|
}
|
|
else
|
|
{
|
|
attack_rating = (attacker->GetATK() + (attacker->GetSkill(OFFENSE)*1.345) + ((attacker->GetSTR()-66) * 0.9));
|
|
}
|
|
|
|
float d = 10.0;
|
|
float mit_roll = MakeRandomFloat(0, mitigation_rating);
|
|
float atk_roll = MakeRandomFloat(0, attack_rating);
|
|
|
|
if(atk_roll > mit_roll)
|
|
{
|
|
float a_diff = (atk_roll - mit_roll);
|
|
float thac0 = attack_rating * RuleR(Combat, ACthac0Factor);
|
|
float thac0cap = ((attacker->GetLevel() * 9) + 20);
|
|
if(thac0 > thac0cap)
|
|
{
|
|
thac0 = thac0cap;
|
|
}
|
|
d -= 10.0 * (a_diff / thac0);
|
|
}
|
|
else if(mit_roll > atk_roll)
|
|
{
|
|
float m_diff = (mit_roll - atk_roll);
|
|
float thac20 = mitigation_rating * RuleR(Combat, ACthac20Factor);
|
|
float thac20cap = ((defender->GetLevel() * 9) + 20);
|
|
if(thac20 > thac20cap)
|
|
{
|
|
thac20 = thac20cap;
|
|
}
|
|
d += 10 * (m_diff / thac20);
|
|
}
|
|
|
|
if(d < 0.0)
|
|
{
|
|
d = 0.0;
|
|
}
|
|
|
|
if(d > 20)
|
|
{
|
|
d = 20.0;
|
|
}
|
|
|
|
float interval = (damage - minhit) / 20.0;
|
|
damage = damage - ((int)d * interval);
|
|
}
|
|
else{
|
|
////////////////////////////////////////////////////////
|
|
// Scorpious2k: Include AC in the calculation
|
|
// use serverop variables to set values
|
|
int myac = GetAC();
|
|
if (damage > 0 && myac > 0) {
|
|
int acfail=1000;
|
|
char tmp[10];
|
|
|
|
if (database.GetVariable("ACfail", tmp, 9)) {
|
|
acfail = (int) (atof(tmp) * 100);
|
|
if (acfail>100) acfail=100;
|
|
}
|
|
|
|
if (acfail<=0 || MakeRandomInt(0, 100)>acfail) {
|
|
float acreduction=1;
|
|
int acrandom=300;
|
|
if (database.GetVariable("ACreduction", tmp, 9))
|
|
{
|
|
acreduction=atof(tmp);
|
|
if (acreduction>100) acreduction=100;
|
|
}
|
|
|
|
if (database.GetVariable("ACrandom", tmp, 9))
|
|
{
|
|
acrandom = (int) ((atof(tmp)+1) * 100);
|
|
if (acrandom>10100) acrandom=10100;
|
|
}
|
|
|
|
if (acreduction>0) {
|
|
damage -= (int) (GetAC() * acreduction/100.0f);
|
|
}
|
|
if (acrandom>0) {
|
|
damage -= (myac * MakeRandomInt(0, acrandom) / 10000);
|
|
}
|
|
if (damage<1) damage=1;
|
|
mlog(COMBAT__DAMAGE, "AC Damage Reduction: fail chance %d%%. Failed. Reduction %.3f%%, random %d. Resulting damage %d.", acfail, acreduction, acrandom, damage);
|
|
} else {
|
|
mlog(COMBAT__DAMAGE, "AC Damage Reduction: fail chance %d%%. Did not fail.", acfail);
|
|
}
|
|
}
|
|
|
|
damage -= (aa_mit * damage);
|
|
|
|
if(damage != 0 && damage < minhit)
|
|
damage = minhit;
|
|
}
|
|
|
|
//reduce the damage from shielding item and aa based on the min dmg
|
|
//spells offer pure mitigation
|
|
damage -= (minhit * defender->itembonuses.MeleeMitigation / 100);
|
|
damage -= (damage * defender->spellbonuses.MeleeMitigation / 100);
|
|
|
|
if(damage < 0)
|
|
damage = 0;
|
|
}
|
|
|
|
//Returns the weapon damage against the input mob
|
|
//if we cannot hit the mob with the current weapon we will get a value less than or equal to zero
|
|
//Else we know we can hit.
|
|
//GetWeaponDamage(mob*, const Item_Struct*) is intended to be used for mobs or any other situation where we do not have a client inventory item
|
|
//GetWeaponDamage(mob*, const ItemInst*) is intended to be used for situations where we have a client inventory item
|
|
int Mob::GetWeaponDamage(Mob *against, const Item_Struct *weapon_item) {
|
|
_ZP(Mob_GetWeaponDamageA);
|
|
int dmg = 0;
|
|
int banedmg = 0;
|
|
|
|
//can't hit invulnerable stuff with weapons.
|
|
if(against->GetInvul() || against->SpecAttacks[IMMUNE_MELEE]){
|
|
return 0;
|
|
}
|
|
|
|
//check to see if our weapons or fists are magical.
|
|
if(against->SpecAttacks[IMMUNE_MELEE_NONMAGICAL]){
|
|
if(weapon_item){
|
|
if(weapon_item->Magic){
|
|
dmg = weapon_item->Damage;
|
|
|
|
//this is more for non weapon items, ex: boots for kick
|
|
//they don't have a dmg but we should be able to hit magical
|
|
dmg = dmg <= 0 ? 1 : dmg;
|
|
}
|
|
else
|
|
return 0;
|
|
}
|
|
else{
|
|
if((GetClass() == MONK || GetClass() == BEASTLORD) && GetLevel() >= 30){
|
|
dmg = GetMonkHandToHandDamage();
|
|
}
|
|
else if(GetOwner() && GetLevel() >= RuleI(Combat, PetAttackMagicLevel)){
|
|
//pets wouldn't actually use this but...
|
|
//it gives us an idea if we can hit due to the dual nature of this function
|
|
dmg = 1;
|
|
}
|
|
else if(SpecAttacks[SPECATK_MAGICAL])
|
|
{
|
|
dmg = 1;
|
|
}
|
|
else
|
|
return 0;
|
|
}
|
|
}
|
|
else{
|
|
if(weapon_item){
|
|
dmg = weapon_item->Damage;
|
|
|
|
dmg = dmg <= 0 ? 1 : dmg;
|
|
}
|
|
else{
|
|
if(GetClass() == MONK || GetClass() == BEASTLORD){
|
|
dmg = GetMonkHandToHandDamage();
|
|
}
|
|
else{
|
|
dmg = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
int eledmg = 0;
|
|
if(!against->SpecAttacks[IMMUNE_MAGIC]){
|
|
if(weapon_item && weapon_item->ElemDmgAmt){
|
|
//we don't check resist for npcs here
|
|
eledmg = weapon_item->ElemDmgAmt;
|
|
dmg += eledmg;
|
|
}
|
|
}
|
|
|
|
if(against->SpecAttacks[IMMUNE_MELEE_EXCEPT_BANE]){
|
|
if(weapon_item){
|
|
if(weapon_item->BaneDmgBody == against->GetBodyType()){
|
|
banedmg += weapon_item->BaneDmgAmt;
|
|
}
|
|
|
|
if(weapon_item->BaneDmgRace == against->GetRace()){
|
|
banedmg += weapon_item->BaneDmgRaceAmt;
|
|
}
|
|
}
|
|
|
|
if(!eledmg && !banedmg){
|
|
if(!SpecAttacks[SPECATK_BANE])
|
|
return 0;
|
|
else
|
|
return 1;
|
|
}
|
|
else
|
|
dmg += banedmg;
|
|
}
|
|
else{
|
|
if(weapon_item){
|
|
if(weapon_item->BaneDmgBody == against->GetBodyType()){
|
|
banedmg += weapon_item->BaneDmgAmt;
|
|
}
|
|
|
|
if(weapon_item->BaneDmgRace == against->GetRace()){
|
|
banedmg += weapon_item->BaneDmgRaceAmt;
|
|
}
|
|
}
|
|
|
|
dmg += (banedmg + eledmg);
|
|
}
|
|
|
|
if(dmg <= 0){
|
|
return 0;
|
|
}
|
|
else
|
|
return dmg;
|
|
}
|
|
|
|
int Mob::GetWeaponDamage(Mob *against, const ItemInst *weapon_item, uint32 *hate)
|
|
{
|
|
_ZP(Mob_GetWeaponDamageB);
|
|
int dmg = 0;
|
|
int banedmg = 0;
|
|
|
|
if(!against || against->GetInvul() || against->SpecAttacks[IMMUNE_MELEE]){
|
|
return 0;
|
|
}
|
|
|
|
//check for items being illegally attained
|
|
if(weapon_item){
|
|
const Item_Struct *mWeaponItem = weapon_item->GetItem();
|
|
if(mWeaponItem){
|
|
if(mWeaponItem->ReqLevel > GetLevel()){
|
|
return 0;
|
|
}
|
|
|
|
if(!weapon_item->IsEquipable(GetBaseRace(), GetClass())){
|
|
return 0;
|
|
}
|
|
}
|
|
else{
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if(against->SpecAttacks[IMMUNE_MELEE_NONMAGICAL]){
|
|
if(weapon_item){
|
|
// check to see if the weapon is magic
|
|
bool MagicWeapon = false;
|
|
if(weapon_item->GetItem() && weapon_item->GetItem()->Magic)
|
|
MagicWeapon = true;
|
|
else {
|
|
if(spellbonuses.MagicWeapon || itembonuses.MagicWeapon)
|
|
MagicWeapon = true;
|
|
}
|
|
|
|
if(MagicWeapon) {
|
|
|
|
if(IsClient() && GetLevel() < weapon_item->GetItem()->RecLevel){
|
|
dmg = CastToClient()->CalcRecommendedLevelBonus(GetLevel(), weapon_item->GetItem()->RecLevel, weapon_item->GetItem()->Damage);
|
|
}
|
|
else{
|
|
dmg = weapon_item->GetItem()->Damage;
|
|
}
|
|
|
|
for(int x = 0; x < 5; x++){
|
|
if(weapon_item->GetAugment(x) && weapon_item->GetAugment(x)->GetItem()){
|
|
dmg += weapon_item->GetAugment(x)->GetItem()->Damage;
|
|
if (hate) *hate += weapon_item->GetAugment(x)->GetItem()->Damage + weapon_item->GetAugment(x)->GetItem()->ElemDmgAmt;
|
|
}
|
|
}
|
|
dmg = dmg <= 0 ? 1 : dmg;
|
|
}
|
|
else
|
|
return 0;
|
|
}
|
|
else{
|
|
if((GetClass() == MONK || GetClass() == BEASTLORD) && GetLevel() >= 30){
|
|
dmg = GetMonkHandToHandDamage();
|
|
if (hate) *hate += dmg;
|
|
}
|
|
else if(GetOwner() && GetLevel() >= RuleI(Combat, PetAttackMagicLevel)){ //pets wouldn't actually use this but...
|
|
dmg = 1; //it gives us an idea if we can hit
|
|
}
|
|
else if(SpecAttacks[SPECATK_MAGICAL]){
|
|
dmg = 1;
|
|
}
|
|
else
|
|
return 0;
|
|
}
|
|
}
|
|
else{
|
|
if(weapon_item){
|
|
if(weapon_item->GetItem()){
|
|
|
|
if(IsClient() && GetLevel() < weapon_item->GetItem()->RecLevel){
|
|
dmg = CastToClient()->CalcRecommendedLevelBonus(GetLevel(), weapon_item->GetItem()->RecLevel, weapon_item->GetItem()->Damage);
|
|
}
|
|
else{
|
|
dmg = weapon_item->GetItem()->Damage;
|
|
}
|
|
|
|
for(int x = 0; x < 5; x++){
|
|
if(weapon_item->GetAugment(x) && weapon_item->GetAugment(x)->GetItem()){
|
|
dmg += weapon_item->GetAugment(x)->GetItem()->Damage;
|
|
if (hate) *hate += weapon_item->GetAugment(x)->GetItem()->Damage + weapon_item->GetAugment(x)->GetItem()->ElemDmgAmt;
|
|
}
|
|
}
|
|
dmg = dmg <= 0 ? 1 : dmg;
|
|
}
|
|
}
|
|
else{
|
|
if(GetClass() == MONK || GetClass() == BEASTLORD){
|
|
dmg = GetMonkHandToHandDamage();
|
|
if (hate) *hate += dmg;
|
|
}
|
|
else{
|
|
dmg = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
int eledmg = 0;
|
|
if(!against->SpecAttacks[IMMUNE_MAGIC]){
|
|
if(weapon_item && weapon_item->GetItem() && weapon_item->GetItem()->ElemDmgAmt){
|
|
if(IsClient() && GetLevel() < weapon_item->GetItem()->RecLevel){
|
|
eledmg = CastToClient()->CalcRecommendedLevelBonus(GetLevel(), weapon_item->GetItem()->RecLevel, weapon_item->GetItem()->ElemDmgAmt);
|
|
}
|
|
else{
|
|
eledmg = weapon_item->GetItem()->ElemDmgAmt;
|
|
}
|
|
|
|
if(eledmg)
|
|
{
|
|
eledmg = (eledmg * against->ResistSpell(weapon_item->GetItem()->ElemDmgType, 0, this) / 100);
|
|
}
|
|
}
|
|
|
|
if(weapon_item){
|
|
for(int x = 0; x < 5; x++){
|
|
if(weapon_item->GetAugment(x) && weapon_item->GetAugment(x)->GetItem()){
|
|
if(weapon_item->GetAugment(x)->GetItem()->ElemDmgAmt)
|
|
eledmg += (weapon_item->GetAugment(x)->GetItem()->ElemDmgAmt * against->ResistSpell(weapon_item->GetAugment(x)->GetItem()->ElemDmgType, 0, this) / 100);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if(against->SpecAttacks[IMMUNE_MELEE_EXCEPT_BANE]){
|
|
if(weapon_item && weapon_item->GetItem()){
|
|
if(weapon_item->GetItem()->BaneDmgBody == against->GetBodyType()){
|
|
if(IsClient() && GetLevel() < weapon_item->GetItem()->RecLevel){
|
|
banedmg += CastToClient()->CalcRecommendedLevelBonus(GetLevel(), weapon_item->GetItem()->RecLevel, weapon_item->GetItem()->BaneDmgAmt);
|
|
}
|
|
else{
|
|
banedmg += weapon_item->GetItem()->BaneDmgAmt;
|
|
}
|
|
}
|
|
|
|
if(weapon_item->GetItem()->BaneDmgRace == against->GetRace()){
|
|
if(IsClient() && GetLevel() < weapon_item->GetItem()->RecLevel){
|
|
banedmg += CastToClient()->CalcRecommendedLevelBonus(GetLevel(), weapon_item->GetItem()->RecLevel, weapon_item->GetItem()->BaneDmgRaceAmt);
|
|
}
|
|
else{
|
|
banedmg += weapon_item->GetItem()->BaneDmgRaceAmt;
|
|
}
|
|
}
|
|
|
|
for(int x = 0; x < 5; x++){
|
|
if(weapon_item->GetAugment(x) && weapon_item->GetAugment(x)->GetItem()){
|
|
if(weapon_item->GetAugment(x)->GetItem()->BaneDmgBody == against->GetBodyType()){
|
|
banedmg += weapon_item->GetAugment(x)->GetItem()->BaneDmgAmt;
|
|
}
|
|
|
|
if(weapon_item->GetAugment(x)->GetItem()->BaneDmgRace == against->GetRace()){
|
|
banedmg += weapon_item->GetAugment(x)->GetItem()->BaneDmgRaceAmt;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if(!eledmg && !banedmg)
|
|
{
|
|
if(!SpecAttacks[SPECATK_BANE])
|
|
return 0;
|
|
else
|
|
return 1;
|
|
}
|
|
else {
|
|
dmg += (banedmg + eledmg);
|
|
if (hate) *hate += banedmg;
|
|
}
|
|
}
|
|
else{
|
|
if(weapon_item && weapon_item->GetItem()){
|
|
if(weapon_item->GetItem()->BaneDmgBody == against->GetBodyType()){
|
|
if(IsClient() && GetLevel() < weapon_item->GetItem()->RecLevel){
|
|
banedmg += CastToClient()->CalcRecommendedLevelBonus(GetLevel(), weapon_item->GetItem()->RecLevel, weapon_item->GetItem()->BaneDmgAmt);
|
|
}
|
|
else{
|
|
banedmg += weapon_item->GetItem()->BaneDmgAmt;
|
|
}
|
|
}
|
|
|
|
if(weapon_item->GetItem()->BaneDmgRace == against->GetRace()){
|
|
if(IsClient() && GetLevel() < weapon_item->GetItem()->RecLevel){
|
|
banedmg += CastToClient()->CalcRecommendedLevelBonus(GetLevel(), weapon_item->GetItem()->RecLevel, weapon_item->GetItem()->BaneDmgRaceAmt);
|
|
}
|
|
else{
|
|
banedmg += weapon_item->GetItem()->BaneDmgRaceAmt;
|
|
}
|
|
}
|
|
|
|
for(int x = 0; x < 5; x++){
|
|
if(weapon_item->GetAugment(x) && weapon_item->GetAugment(x)->GetItem()){
|
|
if(weapon_item->GetAugment(x)->GetItem()->BaneDmgBody == against->GetBodyType()){
|
|
banedmg += weapon_item->GetAugment(x)->GetItem()->BaneDmgAmt;
|
|
}
|
|
|
|
if(weapon_item->GetAugment(x)->GetItem()->BaneDmgRace == against->GetRace()){
|
|
banedmg += weapon_item->GetAugment(x)->GetItem()->BaneDmgRaceAmt;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
dmg += (banedmg + eledmg);
|
|
if (hate) *hate += banedmg;
|
|
}
|
|
|
|
if(dmg <= 0){
|
|
return 0;
|
|
}
|
|
else
|
|
return dmg;
|
|
}
|
|
|
|
//note: throughout this method, setting `damage` to a negative is a way to
|
|
//stop the attack calculations
|
|
// IsFromSpell added to allow spell effects to use Attack. (Mainly for the Rampage AA right now.)
|
|
bool Client::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, bool IsFromSpell)
|
|
{
|
|
|
|
_ZP(Client_Attack);
|
|
|
|
if (!other) {
|
|
SetTarget(NULL);
|
|
LogFile->write(EQEMuLog::Error, "A null Mob object was passed to Client::Attack() for evaluation!");
|
|
return false;
|
|
}
|
|
|
|
if(!GetTarget())
|
|
SetTarget(other);
|
|
|
|
mlog(COMBAT__ATTACKS, "Attacking %s with hand %d %s", other?other->GetName():"(NULL)", Hand, bRiposte?"(this is a riposte)":"");
|
|
|
|
//SetAttackTimer();
|
|
if (
|
|
(IsCasting() && GetClass() != BARD && !IsFromSpell)
|
|
|| other == NULL
|
|
|| ((IsClient() && CastToClient()->dead) || (other->IsClient() && other->CastToClient()->dead))
|
|
|| (GetHP() < 0)
|
|
|| (!IsAttackAllowed(other))
|
|
) {
|
|
mlog(COMBAT__ATTACKS, "Attack canceled, invalid circumstances.");
|
|
return false; // Only bards can attack while casting
|
|
}
|
|
if(DivineAura() && !GetGM()) {//cant attack while invulnerable unless your a gm
|
|
mlog(COMBAT__ATTACKS, "Attack canceled, Divine Aura is in effect.");
|
|
Message_StringID(MT_DefaultText, DIVINE_AURA_NO_ATK); //You can't attack while invulnerable!
|
|
return false;
|
|
}
|
|
|
|
if (GetFeigned())
|
|
return false; // Rogean: How can you attack while feigned? Moved up from Aggro Code.
|
|
|
|
|
|
ItemInst* weapon;
|
|
if (Hand == 14){ // Kaiyodo - Pick weapon from the attacking hand
|
|
weapon = GetInv().GetItem(SLOT_SECONDARY);
|
|
OffHandAtk(true);
|
|
}
|
|
else{
|
|
weapon = GetInv().GetItem(SLOT_PRIMARY);
|
|
OffHandAtk(false);
|
|
}
|
|
|
|
if(weapon != NULL) {
|
|
if (!weapon->IsWeapon()) {
|
|
mlog(COMBAT__ATTACKS, "Attack canceled, Item %s (%d) is not a weapon.", weapon->GetItem()->Name, weapon->GetID());
|
|
return(false);
|
|
}
|
|
mlog(COMBAT__ATTACKS, "Attacking with weapon: %s (%d)", weapon->GetItem()->Name, weapon->GetID());
|
|
} else {
|
|
mlog(COMBAT__ATTACKS, "Attacking without a weapon.");
|
|
}
|
|
|
|
// calculate attack_skill and skillinuse depending on hand and weapon
|
|
// also send Packet to near clients
|
|
SkillType skillinuse;
|
|
AttackAnimation(skillinuse, Hand, weapon);
|
|
mlog(COMBAT__ATTACKS, "Attacking with %s in slot %d using skill %d", weapon?weapon->GetItem()->Name:"Fist", Hand, skillinuse);
|
|
|
|
/// Now figure out damage
|
|
int damage = 0;
|
|
uint8 mylevel = GetLevel() ? GetLevel() : 1;
|
|
uint32 hate = 0;
|
|
if (weapon) hate = weapon->GetItem()->Damage + weapon->GetItem()->ElemDmgAmt;
|
|
int weapon_damage = GetWeaponDamage(other, weapon, &hate);
|
|
if (hate == 0 && weapon_damage > 1) hate = weapon_damage;
|
|
|
|
//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;
|
|
mlog(COMBAT__DAMAGE, "Berserker damage bonus increases DMG to %d", weapon_damage);
|
|
}
|
|
|
|
//try a finishing blow.. if successful end the attack
|
|
if(TryFinishingBlow(other, skillinuse))
|
|
return (true);
|
|
|
|
int min_hit = 1;
|
|
int max_hit = (2*weapon_damage*GetDamageTable(skillinuse)) / 100;
|
|
|
|
if(GetLevel() < 10 && max_hit > 20)
|
|
max_hit = (RuleI(Combat, HitCapPre10));
|
|
else if(GetLevel() < 20 && max_hit > 40)
|
|
max_hit = (RuleI(Combat, HitCapPre20));
|
|
|
|
CheckIncreaseSkill(skillinuse, other, -15);
|
|
CheckIncreaseSkill(OFFENSE, other, -15);
|
|
|
|
|
|
// ***************************************************************
|
|
// *** Calculate the damage bonus, if applicable, for this hit ***
|
|
// ***************************************************************
|
|
|
|
#ifndef EQEMU_NO_WEAPON_DAMAGE_BONUS
|
|
|
|
// If you include the preprocessor directive "#define EQEMU_NO_WEAPON_DAMAGE_BONUS", that indicates that you do not
|
|
// want damage bonuses added to weapon damage at all. This feature was requested by ChaosSlayer on the EQEmu Forums.
|
|
//
|
|
// This is not recommended for normal usage, as the damage bonus represents a non-trivial component of the DPS output
|
|
// of weapons wielded by higher-level melee characters (especially for two-handed weapons).
|
|
|
|
int ucDamageBonus = 0;
|
|
|
|
if( Hand == 13 && GetLevel() >= 28 && IsWarriorClass() )
|
|
{
|
|
// Damage bonuses apply only to hits from the main hand (Hand == 13) 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 Item_Struct*) NULL );
|
|
|
|
min_hit += (int) ucDamageBonus;
|
|
max_hit += (int) ucDamageBonus;
|
|
hate += ucDamageBonus;
|
|
}
|
|
#endif
|
|
//Live AA - Sinister Strikes *Adds weapon damage bonus to offhand weapon.
|
|
if (Hand==14) {
|
|
if (aabonuses.SecondaryDmgInc || itembonuses.SecondaryDmgInc || spellbonuses.SecondaryDmgInc){
|
|
|
|
ucDamageBonus = GetWeaponDamageBonus( weapon ? weapon->GetItem() : (const Item_Struct*) NULL );
|
|
|
|
min_hit += (int) ucDamageBonus;
|
|
max_hit += (int) ucDamageBonus;
|
|
hate += ucDamageBonus;
|
|
}
|
|
}
|
|
|
|
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 = MakeRandomInt(min_hit, max_hit);
|
|
|
|
mlog(COMBAT__DAMAGE, "Damage calculated to %d (min %d, max %d, str %d, skill %d, DMG %d, lv %d)",
|
|
damage, min_hit, max_hit, GetSTR(), GetSkill(skillinuse), weapon_damage, mylevel);
|
|
|
|
//check to see if we hit..
|
|
if(!other->CheckHitChance(this, skillinuse, Hand)) {
|
|
mlog(COMBAT__ATTACKS, "Attack missed. Damage set to 0.");
|
|
damage = 0;
|
|
} else { //we hit, try to avoid it
|
|
other->AvoidDamage(this, damage);
|
|
other->MeleeMitigation(this, damage, min_hit);
|
|
if(damage > 0) {
|
|
ApplyMeleeDamageBonus(skillinuse, damage);
|
|
damage += (itembonuses.HeroicSTR / 10) + (damage * other->GetSkillDmgTaken(skillinuse) / 100) + GetSkillDmgAmt(skillinuse);
|
|
TryCriticalHit(other, skillinuse, damage);
|
|
}
|
|
mlog(COMBAT__DAMAGE, "Final damage after all reductions: %d", damage);
|
|
}
|
|
|
|
//riposte
|
|
bool slippery_attack = false; // Part of hack to allow riposte to become a miss, but still allow a Strikethrough chance (like on Live)
|
|
if (damage == -3) {
|
|
if (bRiposte) return false;
|
|
else {
|
|
if (Hand == 14) {// Do we even have it & was attack with mainhand? If not, don't bother with other calculations
|
|
//Live AA - SlipperyAttacks
|
|
//This spell effect most likely directly modifies the actual riposte chance when using offhand attack.
|
|
int16 OffhandRiposteFail = aabonuses.OffhandRiposteFail + itembonuses.OffhandRiposteFail + spellbonuses.OffhandRiposteFail;
|
|
OffhandRiposteFail *= -1; //Live uses a negative value for this.
|
|
|
|
if (OffhandRiposteFail &&
|
|
(OffhandRiposteFail > 99 || (MakeRandomInt(0, 100) < OffhandRiposteFail))) {
|
|
damage = 0; // Counts as a miss
|
|
slippery_attack = true;
|
|
} else
|
|
DoRiposte(other);
|
|
if (IsDead()) return false;
|
|
}
|
|
else
|
|
DoRiposte(other);
|
|
if (IsDead()) return false;
|
|
}
|
|
}
|
|
|
|
if (((damage < 0) || slippery_attack) && !bRiposte && !IsStrikethrough) { // Hack to still allow Strikethrough chance w/ Slippery Attacks AA
|
|
int16 bonusStrikeThrough = itembonuses.StrikeThrough + spellbonuses.StrikeThrough + aabonuses.StrikeThrough;
|
|
|
|
if(bonusStrikeThrough && (MakeRandomInt(0, 100) < bonusStrikeThrough)) {
|
|
Message_StringID(MT_StrikeThrough, STRIKETHROUGH_STRING); // You strike through your opponents defenses!
|
|
Attack(other, Hand, false, true); // Strikethrough only gives another attempted hit
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
else{
|
|
damage = -5;
|
|
}
|
|
|
|
|
|
// Hate Generation is on a per swing basis, regardless of a hit, miss, or block, its always the same.
|
|
// If we are this far, this means we are atleast making a swing.
|
|
if (!bRiposte) // Ripostes never generate any aggro.
|
|
other->AddToHateList(this, hate);
|
|
|
|
///////////////////////////////////////////////////////////
|
|
////// Send Attack Damage
|
|
///////////////////////////////////////////////////////////
|
|
other->Damage(this, damage, SPELL_UNKNOWN, skillinuse);
|
|
|
|
if (IsDead()) return false;
|
|
|
|
if(damage > 0 && (spellbonuses.MeleeLifetap || itembonuses.MeleeLifetap))
|
|
{
|
|
int lifetap_amt = spellbonuses.MeleeLifetap + itembonuses.MeleeLifetap;
|
|
if(lifetap_amt > 100)
|
|
lifetap_amt = 100;
|
|
|
|
lifetap_amt = damage * lifetap_amt / 100;
|
|
|
|
mlog(COMBAT__DAMAGE, "Melee lifetap healing for %d damage.", damage);
|
|
//heal self for damage done..
|
|
HealDamage(lifetap_amt);
|
|
|
|
if (spellbonuses.MeleeLifetap)
|
|
CheckHitsRemaining(0, false,false, SE_MeleeLifetap);
|
|
}
|
|
|
|
//break invis when you attack
|
|
if(invisible) {
|
|
mlog(COMBAT__ATTACKS, "Removing invisibility due to melee attack.");
|
|
BuffFadeByEffect(SE_Invisibility);
|
|
BuffFadeByEffect(SE_Invisibility2);
|
|
invisible = false;
|
|
}
|
|
if(invisible_undead) {
|
|
mlog(COMBAT__ATTACKS, "Removing invisibility vs. undead due to melee attack.");
|
|
BuffFadeByEffect(SE_InvisVsUndead);
|
|
BuffFadeByEffect(SE_InvisVsUndead2);
|
|
invisible_undead = false;
|
|
}
|
|
if(invisible_animals){
|
|
mlog(COMBAT__ATTACKS, "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);
|
|
}
|
|
|
|
if(GetTarget())
|
|
TriggerDefensiveProcs(weapon, other, Hand, damage);
|
|
|
|
if (damage > 0)
|
|
return true;
|
|
|
|
else
|
|
return false;
|
|
}
|
|
|
|
//used by complete heal and #heal
|
|
void Mob::Heal()
|
|
{
|
|
SetMaxHP();
|
|
SendHPUpdate();
|
|
}
|
|
|
|
void Client::Damage(Mob* other, int32 damage, uint16 spell_id, SkillType attack_skill, bool avoidable, int8 buffslot, bool iBuffTic)
|
|
{
|
|
if(dead || IsCorpse())
|
|
return;
|
|
|
|
if(spell_id==0)
|
|
spell_id = SPELL_UNKNOWN;
|
|
|
|
if(spell_id!=0 && spell_id != SPELL_UNKNOWN && other && damage > 0)
|
|
{
|
|
if(other->IsNPC() && !other->IsPet())
|
|
{
|
|
float npcspellscale = other->CastToNPC()->GetSpellScale();
|
|
damage = ((float)damage * npcspellscale) / (float)100;
|
|
}
|
|
}
|
|
|
|
// cut all PVP spell damage to 2/3 -solar
|
|
// EverHood - Blasting ourselfs is considered PvP
|
|
//Don't do PvP mitigation if the caster is damaging himself
|
|
if(other && other->IsClient() && (other != this) && damage > 0) {
|
|
int PvPMitigation = 100;
|
|
if(attack_skill == ARCHERY)
|
|
PvPMitigation = 80;
|
|
else
|
|
PvPMitigation = 67;
|
|
damage = (damage * PvPMitigation) / 100;
|
|
}
|
|
|
|
if(!ClientFinishedLoading())
|
|
damage = -5;
|
|
|
|
//do a majority of the work...
|
|
CommonDamage(other, damage, spell_id, attack_skill, avoidable, buffslot, iBuffTic);
|
|
|
|
if (damage > 0) {
|
|
|
|
if (spell_id == SPELL_UNKNOWN)
|
|
CheckIncreaseSkill(DEFENSE, other, -15);
|
|
}
|
|
}
|
|
|
|
void Client::Death(Mob* killerMob, int32 damage, uint16 spell, SkillType attack_skill)
|
|
{
|
|
if(!ClientFinishedLoading())
|
|
return;
|
|
|
|
if(dead)
|
|
return; //cant die more than once...
|
|
|
|
int exploss;
|
|
|
|
mlog(COMBAT__HITS, "Fatal blow dealt by %s with %d damage, spell %d, skill %d", killerMob ? killerMob->GetName() : "Unknown", damage, spell, attack_skill);
|
|
|
|
//
|
|
// #1: Send death packet to everyone
|
|
//
|
|
uint8 killed_level = GetLevel();
|
|
if(!spell) spell = SPELL_UNKNOWN;
|
|
|
|
SendLogoutPackets();
|
|
|
|
//make our become corpse packet, and queue to ourself before OP_Death.
|
|
EQApplicationPacket app2(OP_BecomeCorpse, sizeof(BecomeCorpse_Struct));
|
|
BecomeCorpse_Struct* bc = (BecomeCorpse_Struct*)app2.pBuffer;
|
|
bc->spawn_id = GetID();
|
|
bc->x = GetX();
|
|
bc->y = GetY();
|
|
bc->z = GetZ();
|
|
QueuePacket(&app2);
|
|
|
|
// make death packet
|
|
EQApplicationPacket app(OP_Death, sizeof(Death_Struct));
|
|
Death_Struct* d = (Death_Struct*)app.pBuffer;
|
|
d->spawn_id = GetID();
|
|
d->killer_id = killerMob ? killerMob->GetID() : 0;
|
|
d->corpseid=GetID();
|
|
d->bindzoneid = m_pp.binds[0].zoneId;
|
|
d->spell_id = spell == SPELL_UNKNOWN ? 0xffffffff : spell;
|
|
d->attack_skill = spell != SPELL_UNKNOWN ? 0xe7 : attack_skill;
|
|
d->damage = damage;
|
|
app.priority = 6;
|
|
entity_list.QueueClients(this, &app);
|
|
|
|
//
|
|
// #2: figure out things that affect the player dying and mark them dead
|
|
//
|
|
|
|
InterruptSpell();
|
|
SetPet(0);
|
|
SetHorseId(0);
|
|
dead = true;
|
|
|
|
if(GetMerc()) {
|
|
GetMerc()->Suspend();
|
|
}
|
|
|
|
parse->EventPlayer(EVENT_DEATH, this, "", 0);
|
|
|
|
if (killerMob != NULL)
|
|
{
|
|
if (killerMob->IsNPC()) {
|
|
parse->EventNPC(EVENT_SLAY, killerMob->CastToNPC(), this, "", 0);
|
|
uint16 emoteid = killerMob->CastToNPC()->GetNPCEmoteID();
|
|
if(emoteid != 0)
|
|
killerMob->CastToNPC()->DoNPCEmote(KILLEDPC,emoteid);
|
|
killerMob->TrySpellOnKill(killed_level,spell);
|
|
}
|
|
|
|
if(killerMob->IsClient() && (IsDueling() || killerMob->CastToClient()->IsDueling())) {
|
|
SetDueling(false);
|
|
SetDuelTarget(0);
|
|
if (killerMob->IsClient() && killerMob->CastToClient()->IsDueling() && killerMob->CastToClient()->GetDuelTarget() == GetID())
|
|
{
|
|
//if duel opponent killed us...
|
|
killerMob->CastToClient()->SetDueling(false);
|
|
killerMob->CastToClient()->SetDuelTarget(0);
|
|
entity_list.DuelMessage(killerMob,this,false);
|
|
} else {
|
|
//otherwise, we just died, end the duel.
|
|
Mob* who = entity_list.GetMob(GetDuelTarget());
|
|
if(who && who->IsClient()) {
|
|
who->CastToClient()->SetDueling(false);
|
|
who->CastToClient()->SetDuelTarget(0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
entity_list.RemoveFromTargets(this);
|
|
hate_list.RemoveEnt(this);
|
|
|
|
|
|
//remove ourself from all proximities
|
|
ClearAllProximities();
|
|
|
|
//
|
|
// #3: exp loss and corpse generation
|
|
//
|
|
|
|
// figure out if they should lose exp
|
|
if(RuleB(Character, UseDeathExpLossMult)){
|
|
float GetNum [] = {0.005f,0.015f,0.025f,0.035f,0.045f,0.055f,0.065f,0.075f,0.085f,0.095f,0.110f };
|
|
int Num = RuleI(Character, DeathExpLossMultiplier);
|
|
if((Num < 0) || (Num > 10))
|
|
Num = 3;
|
|
float loss = GetNum[Num];
|
|
exploss=(int)((float)GetEXP() * (loss)); //loose % of total XP pending rule (choose 0-10)
|
|
}
|
|
|
|
if(!RuleB(Character, UseDeathExpLossMult)){
|
|
exploss = (int)(GetLevel() * (GetLevel() / 18.0) * 12000);
|
|
}
|
|
|
|
if( (GetLevel() < RuleI(Character, DeathExpLossLevel)) || (GetLevel() > RuleI(Character, DeathExpLossMaxLevel)) || IsBecomeNPC() )
|
|
{
|
|
exploss = 0;
|
|
}
|
|
else if( killerMob )
|
|
{
|
|
if( killerMob->IsClient() )
|
|
{
|
|
exploss = 0;
|
|
}
|
|
else if( killerMob->GetOwner() && killerMob->GetOwner()->IsClient() )
|
|
{
|
|
exploss = 0;
|
|
}
|
|
}
|
|
|
|
if(spell != SPELL_UNKNOWN)
|
|
{
|
|
uint32 buff_count = GetMaxTotalSlots();
|
|
for(uint16 buffIt = 0; buffIt < buff_count; buffIt++)
|
|
{
|
|
if(buffs[buffIt].spellid == spell && buffs[buffIt].client)
|
|
{
|
|
exploss = 0; // no exp loss for pvp dot
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool LeftCorpse = false;
|
|
|
|
// now we apply the exp loss, unmem their spells, and make a corpse
|
|
// unless they're a GM (or less than lvl 10
|
|
if(!GetGM())
|
|
{
|
|
if(exploss > 0) {
|
|
int32 newexp = GetEXP();
|
|
if(exploss > newexp) {
|
|
//lost more than we have... wtf..
|
|
newexp = 1;
|
|
} else {
|
|
newexp -= exploss;
|
|
}
|
|
SetEXP(newexp, GetAAXP());
|
|
//m_epp.perAA = 0; //reset to no AA exp on death.
|
|
}
|
|
|
|
//this generates a lot of 'updates' to the client that the client does not need
|
|
BuffFadeAll();
|
|
if((GetClientVersionBit() & BIT_SoFAndLater) && RuleB(Character, RespawnFromHover))
|
|
UnmemSpellAll(true);
|
|
else
|
|
UnmemSpellAll(false);
|
|
|
|
if(RuleB(Character, LeaveCorpses) && GetLevel() >= RuleI(Character, DeathItemLossLevel) || RuleB(Character, LeaveNakedCorpses))
|
|
{
|
|
// creating the corpse takes the cash/items off the player too
|
|
Corpse *new_corpse = new Corpse(this, exploss);
|
|
|
|
char tmp[20];
|
|
database.GetVariable("ServerType", tmp, 9);
|
|
if(atoi(tmp)==1 && killerMob != NULL && killerMob->IsClient()){
|
|
char tmp2[10] = {0};
|
|
database.GetVariable("PvPreward", tmp, 9);
|
|
int reward = atoi(tmp);
|
|
if(reward==3){
|
|
database.GetVariable("PvPitem", tmp2, 9);
|
|
int pvpitem = atoi(tmp2);
|
|
if(pvpitem>0 && pvpitem<200000)
|
|
new_corpse->SetPKItem(pvpitem);
|
|
}
|
|
else if(reward==2)
|
|
new_corpse->SetPKItem(-1);
|
|
else if(reward==1)
|
|
new_corpse->SetPKItem(1);
|
|
else
|
|
new_corpse->SetPKItem(0);
|
|
if(killerMob->CastToClient()->isgrouped) {
|
|
Group* group = entity_list.GetGroupByClient(killerMob->CastToClient());
|
|
if(group != 0)
|
|
{
|
|
for(int i=0;i<6;i++)
|
|
{
|
|
if(group->members[i] != NULL)
|
|
{
|
|
new_corpse->AllowMobLoot(group->members[i],i);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
entity_list.AddCorpse(new_corpse, GetID());
|
|
SetID(0);
|
|
|
|
//send the become corpse packet to everybody else in the zone.
|
|
entity_list.QueueClients(this, &app2, true);
|
|
|
|
LeftCorpse = true;
|
|
}
|
|
|
|
// if(!IsLD())//Todo: make it so an LDed client leaves corpse if its enabled
|
|
// MakeCorpse(exploss);
|
|
} else {
|
|
BuffFadeDetrimental();
|
|
}
|
|
|
|
|
|
|
|
#if 0 // solar: commenting this out for now TODO reimplement becomenpc stuff
|
|
if (IsBecomeNPC() == true)
|
|
{
|
|
if (killerMob != NULL && killerMob->IsClient()) {
|
|
if (killerMob->CastToClient()->isgrouped && entity_list.GetGroupByMob(killerMob) != 0)
|
|
entity_list.GetGroupByMob(killerMob->CastToClient())->SplitExp((uint32)(level*level*75*3.5f), this);
|
|
|
|
else
|
|
killerMob->CastToClient()->AddEXP((uint32)(level*level*75*3.5f)); // Pyro: Comment this if NPC death crashes zone
|
|
//hate_list.DoFactionHits(GetNPCFactionID());
|
|
}
|
|
|
|
Corpse* corpse = new Corpse(this->CastToClient(), 0);
|
|
entity_list.AddCorpse(corpse, this->GetID());
|
|
this->SetID(0);
|
|
if(killerMob->GetOwner() != 0 && killerMob->GetOwner()->IsClient())
|
|
killerMob = killerMob->GetOwner();
|
|
if(killerMob != 0 && killerMob->IsClient()) {
|
|
corpse->AllowMobLoot(killerMob, 0);
|
|
if(killerMob->CastToClient()->isgrouped) {
|
|
Group* group = entity_list.GetGroupByClient(killerMob->CastToClient());
|
|
if(group != 0) {
|
|
for(int i=0; i < MAX_GROUP_MEMBERS; i++) { // Doesnt work right, needs work
|
|
if(group->members[i] != NULL) {
|
|
corpse->AllowMobLoot(group->members[i],i);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
|
|
//
|
|
// Finally, send em home
|
|
//
|
|
|
|
// we change the mob variables, not pp directly, because Save() will copy
|
|
// from these and overwrite what we set in pp anyway
|
|
//
|
|
|
|
if(LeftCorpse && (GetClientVersionBit() & BIT_SoFAndLater) && RuleB(Character, RespawnFromHover))
|
|
{
|
|
ClearDraggedCorpses();
|
|
|
|
RespawnFromHoverTimer.Start(RuleI(Character, RespawnFromHoverTimer) * 1000);
|
|
|
|
SendRespawnBinds();
|
|
}
|
|
else
|
|
{
|
|
if(isgrouped)
|
|
{
|
|
Group *g = GetGroup();
|
|
if(g)
|
|
g->MemberZoned(this);
|
|
}
|
|
|
|
Raid* r = entity_list.GetRaidByClient(this);
|
|
|
|
if(r)
|
|
r->MemberZoned(this);
|
|
|
|
dead_timer.Start(5000, true);
|
|
|
|
m_pp.zone_id = m_pp.binds[0].zoneId;
|
|
m_pp.zoneInstance = 0;
|
|
database.MoveCharacterToZone(this->CharacterID(), database.GetZoneName(m_pp.zone_id));
|
|
|
|
Save();
|
|
|
|
GoToDeath();
|
|
}
|
|
}
|
|
|
|
bool NPC::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, bool IsFromSpell) // Kaiyodo - base function has changed prototype, need to update overloaded version
|
|
{
|
|
_ZP(NPC_Attack);
|
|
int damage = 0;
|
|
|
|
if (!other) {
|
|
SetTarget(NULL);
|
|
LogFile->write(EQEMuLog::Error, "A null Mob object was passed to NPC::Attack() for evaluation!");
|
|
return false;
|
|
}
|
|
|
|
if(DivineAura())
|
|
return(false);
|
|
|
|
if(!GetTarget())
|
|
SetTarget(other);
|
|
|
|
//Check that we can attack before we calc heading and face our target
|
|
if (!IsAttackAllowed(other)) {
|
|
if (this->GetOwnerID())
|
|
entity_list.MessageClose(this, 1, 200, 10, "%s says, 'That is not a legal target master.'", this->GetCleanName());
|
|
if(other) {
|
|
RemoveFromHateList(other);
|
|
mlog(COMBAT__ATTACKS, "I am not allowed to attack %s", other->GetName());
|
|
}
|
|
return false;
|
|
}
|
|
|
|
FaceTarget(GetTarget());
|
|
|
|
SkillType skillinuse = HAND_TO_HAND;
|
|
if (Hand == 13) {
|
|
skillinuse = static_cast<SkillType>(GetPrimSkill());
|
|
OffHandAtk(false);
|
|
}
|
|
if (Hand == 14) {
|
|
skillinuse = static_cast<SkillType>(GetSecSkill());
|
|
OffHandAtk(true);
|
|
}
|
|
|
|
//figure out what weapon they are using, if any
|
|
const Item_Struct* weapon = NULL;
|
|
if (Hand == 13 && equipment[SLOT_PRIMARY] > 0)
|
|
weapon = database.GetItem(equipment[SLOT_PRIMARY]);
|
|
else if (equipment[SLOT_SECONDARY])
|
|
weapon = database.GetItem(equipment[SLOT_SECONDARY]);
|
|
|
|
//We dont factor much from the weapon into the attack.
|
|
//Just the skill type so it doesn't look silly using punching animations and stuff while wielding weapons
|
|
if(weapon) {
|
|
mlog(COMBAT__ATTACKS, "Attacking with weapon: %s (%d) (too bad im not using it for much)", weapon->Name, weapon->ID);
|
|
|
|
if(Hand == 14 && weapon->ItemType == ItemTypeShield){
|
|
mlog(COMBAT__ATTACKS, "Attack with shield canceled.");
|
|
return false;
|
|
}
|
|
|
|
switch(weapon->ItemType){
|
|
case ItemType1HS:
|
|
skillinuse = _1H_SLASHING;
|
|
break;
|
|
case ItemType2HS:
|
|
skillinuse = _2H_SLASHING;
|
|
break;
|
|
case ItemTypePierce:
|
|
case ItemType2HPierce:
|
|
skillinuse = PIERCING;
|
|
break;
|
|
case ItemType1HB:
|
|
skillinuse = _1H_BLUNT;
|
|
break;
|
|
case ItemType2HB:
|
|
skillinuse = _2H_BLUNT;
|
|
break;
|
|
case ItemTypeBow:
|
|
skillinuse = ARCHERY;
|
|
break;
|
|
case ItemTypeThrowing:
|
|
case ItemTypeThrowingv2:
|
|
skillinuse = THROWING;
|
|
break;
|
|
default:
|
|
skillinuse = HAND_TO_HAND;
|
|
break;
|
|
}
|
|
}
|
|
|
|
int weapon_damage = GetWeaponDamage(other, weapon);
|
|
|
|
//do attack animation regardless of whether or not we can hit below
|
|
int16 charges = 0;
|
|
ItemInst weapon_inst(weapon, charges);
|
|
AttackAnimation(skillinuse, Hand, &weapon_inst);
|
|
|
|
//Work-around for there being no 2HP skill - We use 99 for the 2HB animation and 36 for pierce messages
|
|
if(skillinuse == 99)
|
|
skillinuse = static_cast<SkillType>(36);
|
|
|
|
//basically "if not immune" then do the attack
|
|
if((weapon_damage) > 0) {
|
|
|
|
//ele and bane dmg too
|
|
//NPCs add this differently than PCs
|
|
//if NPCs can't inheriently hit the target we don't add bane/magic dmg which isn't exactly the same as PCs
|
|
uint16 eleBane = 0;
|
|
if(weapon){
|
|
if(weapon->BaneDmgBody == other->GetBodyType()){
|
|
eleBane += weapon->BaneDmgAmt;
|
|
}
|
|
|
|
if(weapon->BaneDmgRace == other->GetRace()){
|
|
eleBane += weapon->BaneDmgRaceAmt;
|
|
}
|
|
|
|
if(weapon->ElemDmgAmt){
|
|
eleBane += (weapon->ElemDmgAmt * other->ResistSpell(weapon->ElemDmgType, 0, this) / 100);
|
|
}
|
|
}
|
|
|
|
if(!RuleB(NPC, UseItemBonusesForNonPets)){
|
|
if(!GetOwner()){
|
|
eleBane = 0;
|
|
}
|
|
}
|
|
|
|
uint8 otherlevel = other->GetLevel();
|
|
uint8 mylevel = this->GetLevel();
|
|
|
|
otherlevel = otherlevel ? otherlevel : 1;
|
|
mylevel = mylevel ? mylevel : 1;
|
|
|
|
//instead of calcing damage in floats lets just go straight to ints
|
|
if(RuleB(Combat, UseIntervalAC))
|
|
damage = (max_dmg+eleBane);
|
|
else
|
|
damage = MakeRandomInt((min_dmg+eleBane),(max_dmg+eleBane));
|
|
|
|
|
|
//check if we're hitting above our max or below it.
|
|
if((min_dmg+eleBane) != 0 && damage < (min_dmg+eleBane)) {
|
|
mlog(COMBAT__DAMAGE, "Damage (%d) is below min (%d). Setting to min.", damage, (min_dmg+eleBane));
|
|
damage = (min_dmg+eleBane);
|
|
}
|
|
if((max_dmg+eleBane) != 0 && damage > (max_dmg+eleBane)) {
|
|
mlog(COMBAT__DAMAGE, "Damage (%d) is above max (%d). Setting to max.", damage, (max_dmg+eleBane));
|
|
damage = (max_dmg+eleBane);
|
|
}
|
|
|
|
int32 hate = damage;
|
|
if(IsPet())
|
|
{
|
|
hate = hate * 100 / GetDamageTable(skillinuse);
|
|
}
|
|
//THIS IS WHERE WE CHECK TO SEE IF WE HIT:
|
|
if(other->IsClient() && other->CastToClient()->IsSitting()) {
|
|
mlog(COMBAT__DAMAGE, "Client %s is sitting. Hitting for max damage (%d).", other->GetName(), (max_dmg+eleBane));
|
|
damage = (max_dmg+eleBane);
|
|
damage += (itembonuses.HeroicSTR / 10) + (damage * other->GetSkillDmgTaken(skillinuse) / 100) + GetSkillDmgAmt(skillinuse);
|
|
|
|
mlog(COMBAT__HITS, "Generating hate %d towards %s", hate, GetName());
|
|
// now add done damage to the hate list
|
|
other->AddToHateList(this, hate);
|
|
} else {
|
|
if(!other->CheckHitChance(this, skillinuse, Hand)) {
|
|
damage = 0; //miss
|
|
} else { //hit, check for damage avoidance
|
|
other->AvoidDamage(this, damage);
|
|
other->MeleeMitigation(this, damage, min_dmg+eleBane);
|
|
if(damage > 0) {
|
|
ApplyMeleeDamageBonus(skillinuse, damage);
|
|
damage += (itembonuses.HeroicSTR / 10) + (damage * other->GetSkillDmgTaken(skillinuse) / 100) + GetSkillDmgAmt(skillinuse);
|
|
TryCriticalHit(other, skillinuse, damage);
|
|
}
|
|
mlog(COMBAT__HITS, "Generating hate %d towards %s", hate, GetName());
|
|
// now add done damage to the hate list
|
|
if(damage > 0)
|
|
{
|
|
other->AddToHateList(this, hate);
|
|
}
|
|
else
|
|
other->AddToHateList(this, 0);
|
|
}
|
|
}
|
|
|
|
mlog(COMBAT__DAMAGE, "Final damage against %s: %d", other->GetName(), damage);
|
|
|
|
if(other->IsClient() && IsPet() && GetOwner()->IsClient()) {
|
|
//pets do half damage to clients in pvp
|
|
damage=damage/2;
|
|
}
|
|
}
|
|
else
|
|
damage = -5;
|
|
|
|
//cant riposte a riposte
|
|
if (bRiposte && damage == -3) {
|
|
mlog(COMBAT__DAMAGE, "Riposte of riposte canceled.");
|
|
return false;
|
|
}
|
|
|
|
int16 DeathHP = 0;
|
|
DeathHP = other->GetDelayDeath() * -1;
|
|
|
|
if(GetHP() > 0 && other->GetHP() >= DeathHP) {
|
|
other->Damage(this, damage, SPELL_UNKNOWN, skillinuse, false); // Not avoidable client already had thier chance to Avoid
|
|
} else
|
|
return false;
|
|
|
|
if (HasDied()) //killed by damage shield ect
|
|
return false;
|
|
|
|
//break invis when you attack
|
|
if(invisible) {
|
|
mlog(COMBAT__ATTACKS, "Removing invisibility due to melee attack.");
|
|
BuffFadeByEffect(SE_Invisibility);
|
|
BuffFadeByEffect(SE_Invisibility2);
|
|
invisible = false;
|
|
}
|
|
if(invisible_undead) {
|
|
mlog(COMBAT__ATTACKS, "Removing invisibility vs. undead due to melee attack.");
|
|
BuffFadeByEffect(SE_InvisVsUndead);
|
|
BuffFadeByEffect(SE_InvisVsUndead2);
|
|
invisible_undead = false;
|
|
}
|
|
if(invisible_animals){
|
|
mlog(COMBAT__ATTACKS, "Removing invisibility vs. animals due to melee attack.");
|
|
BuffFadeByEffect(SE_InvisVsAnimals);
|
|
invisible_animals = false;
|
|
}
|
|
|
|
if(hidden || improved_hidden)
|
|
{
|
|
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);
|
|
}
|
|
|
|
|
|
hidden = false;
|
|
improved_hidden = false;
|
|
|
|
//I doubt this works...
|
|
if (!GetTarget())
|
|
return true; //We killed them
|
|
|
|
if( !bRiposte && other->GetHP() > 0 ) {
|
|
TryWeaponProc(weapon, other, Hand); //no weapon
|
|
}
|
|
|
|
TriggerDefensiveProcs(NULL, other, Hand, damage);
|
|
|
|
// now check ripostes
|
|
if (damage == -3) { // riposting
|
|
DoRiposte(other);
|
|
}
|
|
|
|
if (damage > 0)
|
|
return true;
|
|
|
|
else
|
|
return false;
|
|
}
|
|
|
|
void NPC::Damage(Mob* other, int32 damage, uint16 spell_id, SkillType attack_skill, bool avoidable, int8 buffslot, bool iBuffTic) {
|
|
if(spell_id==0)
|
|
spell_id = SPELL_UNKNOWN;
|
|
|
|
//handle EVENT_ATTACK. Resets after we have not been attacked for 12 seconds
|
|
if(attacked_timer.Check())
|
|
{
|
|
mlog(COMBAT__HITS, "Triggering EVENT_ATTACK due to attack by %s", other->GetName());
|
|
parse->EventNPC(EVENT_ATTACK, this, other, "", 0);
|
|
}
|
|
attacked_timer.Start(CombatEventTimer_expire);
|
|
|
|
if (!IsEngaged())
|
|
zone->AddAggroMob();
|
|
|
|
if(GetClass() == LDON_TREASURE)
|
|
{
|
|
if(IsLDoNLocked() && GetLDoNLockedSkill() != LDoNTypeMechanical)
|
|
{
|
|
damage = -5;
|
|
}
|
|
else
|
|
{
|
|
if(IsLDoNTrapped())
|
|
{
|
|
Message_StringID(13, LDON_ACCIDENT_SETOFF2);
|
|
SpellFinished(GetLDoNTrapSpellID(), other, 10, 0, -1, spells[GetLDoNTrapSpellID()].ResistDiff, false);
|
|
SetLDoNTrapSpellID(0);
|
|
SetLDoNTrapped(false);
|
|
SetLDoNTrapDetected(false);
|
|
}
|
|
}
|
|
}
|
|
|
|
//do a majority of the work...
|
|
CommonDamage(other, damage, spell_id, attack_skill, avoidable, buffslot, iBuffTic);
|
|
|
|
if(damage > 0) {
|
|
//see if we are gunna start fleeing
|
|
if(!IsPet()) CheckFlee();
|
|
}
|
|
}
|
|
|
|
void NPC::Death(Mob* killerMob, int32 damage, uint16 spell, SkillType attack_skill) {
|
|
_ZP(NPC_Death);
|
|
mlog(COMBAT__HITS, "Fatal blow dealt by %s with %d damage, spell %d, skill %d", killerMob->GetName(), damage, spell, attack_skill);
|
|
|
|
if (this->IsEngaged())
|
|
{
|
|
zone->DelAggroMob();
|
|
#if EQDEBUG >= 11
|
|
LogFile->write(EQEMuLog::Debug,"NPC::Death() Mobs currently Aggro %i", zone->MobsAggroCount());
|
|
#endif
|
|
}
|
|
SetHP(0);
|
|
SetPet(0);
|
|
Mob* killer = GetHateDamageTop(this);
|
|
|
|
entity_list.RemoveFromTargets(this, p_depop);
|
|
|
|
if(p_depop == true)
|
|
return;
|
|
|
|
BuffFadeAll();
|
|
uint8 killed_level = GetLevel();
|
|
|
|
EQApplicationPacket* app= new EQApplicationPacket(OP_Death,sizeof(Death_Struct));
|
|
Death_Struct* d = (Death_Struct*)app->pBuffer;
|
|
d->spawn_id = GetID();
|
|
d->killer_id = killerMob ? killerMob->GetID() : 0;
|
|
// d->unknown12 = 1;
|
|
d->bindzoneid = 0;
|
|
d->spell_id = spell == SPELL_UNKNOWN ? 0xffffffff : spell;
|
|
d->attack_skill = SkillDamageTypes[attack_skill];
|
|
d->damage = damage;
|
|
app->priority = 6;
|
|
entity_list.QueueClients(killerMob, app, false);
|
|
|
|
if(respawn2) {
|
|
respawn2->DeathReset();
|
|
}
|
|
|
|
if (killerMob) {
|
|
if(GetClass() != LDON_TREASURE)
|
|
hate_list.Add(killerMob, damage);
|
|
}
|
|
|
|
safe_delete(app);
|
|
|
|
Mob *give_exp = hate_list.GetDamageTop(this);
|
|
|
|
if(give_exp == NULL)
|
|
give_exp = killer;
|
|
|
|
if(give_exp && give_exp->HasOwner()) {
|
|
|
|
bool ownerInGroup = false;
|
|
if((give_exp->HasGroup() && give_exp->GetGroup()->IsGroupMember(give_exp->GetUltimateOwner()))
|
|
|| (give_exp->IsPet() && (give_exp->GetOwner()->IsClient()
|
|
|| ( give_exp->GetOwner()->HasGroup() && give_exp->GetOwner()->GetGroup()->IsGroupMember(give_exp->GetOwner()->GetUltimateOwner())))))
|
|
ownerInGroup = true;
|
|
|
|
give_exp = give_exp->GetUltimateOwner();
|
|
|
|
#ifdef BOTS
|
|
if(!RuleB(Bots, BotGroupXP) && !ownerInGroup) {
|
|
give_exp = NULL;
|
|
}
|
|
#endif //BOTS
|
|
}
|
|
|
|
int PlayerCount = 0; // QueryServ Player Counting
|
|
|
|
Client *give_exp_client = NULL;
|
|
if(give_exp && give_exp->IsClient())
|
|
give_exp_client = give_exp->CastToClient();
|
|
|
|
bool IsLdonTreasure = (this->GetClass() == LDON_TREASURE);
|
|
if (give_exp_client && !IsCorpse() && MerchantType == 0)
|
|
{
|
|
Group *kg = entity_list.GetGroupByClient(give_exp_client);
|
|
Raid *kr = entity_list.GetRaidByClient(give_exp_client);
|
|
|
|
if(kr)
|
|
{
|
|
if(!IsLdonTreasure) {
|
|
kr->SplitExp((EXP_FORMULA), this);
|
|
if(killerMob && (kr->IsRaidMember(killerMob->GetName()) || kr->IsRaidMember(killerMob->GetUltimateOwner()->GetName())))
|
|
killerMob->TrySpellOnKill(killed_level,spell);
|
|
}
|
|
/* Send the EVENT_KILLED_MERIT event for all raid members */
|
|
for (int i = 0; i < MAX_RAID_MEMBERS; i++) {
|
|
if (kr->members[i].member != NULL) { // If Group Member is Client
|
|
parse->EventNPC(EVENT_KILLED_MERIT, this, kr->members[i].member, "killed", 0);
|
|
if(RuleB(TaskSystem, EnableTaskSystem))
|
|
kr->members[i].member->UpdateTasksOnKill(GetNPCTypeID());
|
|
PlayerCount++;
|
|
}
|
|
}
|
|
|
|
// QueryServ Logging - Raid Kills
|
|
if(RuleB(QueryServ, PlayerLogNPCKills)){
|
|
ServerPacket* pack = new ServerPacket(ServerOP_QSPlayerLogNPCKills, sizeof(QSPlayerLogNPCKill_Struct) + (sizeof(QSPlayerLogNPCKillsPlayers_Struct) * PlayerCount));
|
|
PlayerCount = 0;
|
|
QSPlayerLogNPCKill_Struct* QS = (QSPlayerLogNPCKill_Struct*) pack->pBuffer;
|
|
QS->s1.NPCID = this->GetNPCTypeID();
|
|
QS->s1.ZoneID = this->GetZoneID();
|
|
QS->s1.Type = 2; // Raid Fight
|
|
for (int i = 0; i < MAX_RAID_MEMBERS; i++) {
|
|
if (kr->members[i].member != NULL) { // If Group Member is Client
|
|
Client *c = kr->members[i].member;
|
|
QS->Chars[PlayerCount].char_id = c->CharacterID();
|
|
PlayerCount++;
|
|
}
|
|
}
|
|
worldserver.SendPacket(pack); // Send Packet to World
|
|
safe_delete(pack);
|
|
}
|
|
// End QueryServ Logging
|
|
|
|
}
|
|
else if (give_exp_client->IsGrouped() && kg != NULL)
|
|
{
|
|
if(!IsLdonTreasure) {
|
|
kg->SplitExp((EXP_FORMULA), this);
|
|
if(killerMob && (kg->IsGroupMember(killerMob->GetName()) || kg->IsGroupMember(killerMob->GetUltimateOwner()->GetName())))
|
|
killerMob->TrySpellOnKill(killed_level,spell);
|
|
}
|
|
/* Send the EVENT_KILLED_MERIT event and update kill tasks
|
|
* for all group members */
|
|
for (int i = 0; i < MAX_GROUP_MEMBERS; i++) {
|
|
if (kg->members[i] != NULL && kg->members[i]->IsClient()) { // If Group Member is Client
|
|
Client *c = kg->members[i]->CastToClient();
|
|
parse->EventNPC(EVENT_KILLED_MERIT, this, c, "killed", 0);
|
|
if(RuleB(TaskSystem, EnableTaskSystem))
|
|
c->UpdateTasksOnKill(GetNPCTypeID());
|
|
|
|
PlayerCount++;
|
|
}
|
|
}
|
|
|
|
// QueryServ Logging - Group Kills
|
|
if(RuleB(QueryServ, PlayerLogNPCKills)){
|
|
ServerPacket* pack = new ServerPacket(ServerOP_QSPlayerLogNPCKills, sizeof(QSPlayerLogNPCKill_Struct) + (sizeof(QSPlayerLogNPCKillsPlayers_Struct) * PlayerCount));
|
|
PlayerCount = 0;
|
|
QSPlayerLogNPCKill_Struct* QS = (QSPlayerLogNPCKill_Struct*) pack->pBuffer;
|
|
QS->s1.NPCID = this->GetNPCTypeID();
|
|
QS->s1.ZoneID = this->GetZoneID();
|
|
QS->s1.Type = 1; // Group Fight
|
|
for (int i = 0; i < MAX_GROUP_MEMBERS; i++) {
|
|
if (kg->members[i] != NULL && kg->members[i]->IsClient()) { // If Group Member is Client
|
|
Client *c = kg->members[i]->CastToClient();
|
|
QS->Chars[PlayerCount].char_id = c->CharacterID();
|
|
PlayerCount++;
|
|
}
|
|
}
|
|
worldserver.SendPacket(pack); // Send Packet to World
|
|
safe_delete(pack);
|
|
}
|
|
// End QueryServ Logging
|
|
}
|
|
else
|
|
{
|
|
if(!IsLdonTreasure) {
|
|
int conlevel = give_exp->GetLevelCon(GetLevel());
|
|
if (conlevel != CON_GREEN)
|
|
{
|
|
if(GetOwner() && GetOwner()->IsClient()){
|
|
}
|
|
else {
|
|
give_exp_client->AddEXP((EXP_FORMULA), conlevel); // Pyro: Comment this if NPC death crashes zone
|
|
if(killerMob && (killerMob->GetID() == give_exp_client->GetID() || killerMob->GetUltimateOwner()->GetID() == give_exp_client->GetID()))
|
|
killerMob->TrySpellOnKill(killed_level,spell);
|
|
}
|
|
}
|
|
}
|
|
/* Send the EVENT_KILLED_MERIT event */
|
|
parse->EventNPC(EVENT_KILLED_MERIT, this, give_exp_client, "killed", 0);
|
|
if(RuleB(TaskSystem, EnableTaskSystem))
|
|
give_exp_client->UpdateTasksOnKill(GetNPCTypeID());
|
|
|
|
// QueryServ Logging - Solo
|
|
if(RuleB(QueryServ, PlayerLogNPCKills)){
|
|
ServerPacket* pack = new ServerPacket(ServerOP_QSPlayerLogNPCKills, sizeof(QSPlayerLogNPCKill_Struct) + (sizeof(QSPlayerLogNPCKillsPlayers_Struct) * 1));
|
|
QSPlayerLogNPCKill_Struct* QS = (QSPlayerLogNPCKill_Struct*) pack->pBuffer;
|
|
QS->s1.NPCID = this->GetNPCTypeID();
|
|
QS->s1.ZoneID = this->GetZoneID();
|
|
QS->s1.Type = 0; // Solo Fight
|
|
Client *c = give_exp_client;
|
|
QS->Chars[0].char_id = c->CharacterID();
|
|
PlayerCount++;
|
|
worldserver.SendPacket(pack); // Send Packet to World
|
|
safe_delete(pack);
|
|
}
|
|
// End QueryServ Logging
|
|
}
|
|
}
|
|
|
|
//do faction hits even if we are a merchant, so long as a player killed us
|
|
if(give_exp_client)
|
|
hate_list.DoFactionHits(GetNPCFactionID());
|
|
|
|
if (!HasOwner() && !IsMerc() && class_ != MERCHANT && class_ != ADVENTUREMERCHANT && !GetSwarmInfo()
|
|
&& MerchantType == 0 && killer && (killer->IsClient() || (killer->HasOwner() && killer->GetUltimateOwner()->IsClient()) ||
|
|
(killer->IsNPC() && killer->CastToNPC()->GetSwarmInfo() && killer->CastToNPC()->GetSwarmInfo()->GetOwner() && killer->CastToNPC()->GetSwarmInfo()->GetOwner()->IsClient())))
|
|
{
|
|
if(killer != 0)
|
|
{
|
|
if(killer->GetOwner() != 0 && killer->GetOwner()->IsClient())
|
|
killer = killer->GetOwner();
|
|
|
|
if(!killer->CastToClient()->GetGM() && killer->IsClient())
|
|
this->CheckMinMaxLevel(killer);
|
|
}
|
|
entity_list.RemoveFromAutoXTargets(this);
|
|
uint16 emoteid = this->GetNPCEmoteID();
|
|
Corpse* corpse = new Corpse(this, &itemlist, GetNPCTypeID(), &NPCTypedata,level>54?RuleI(NPC,MajorNPCCorpseDecayTimeMS):RuleI(NPC,MinorNPCCorpseDecayTimeMS));
|
|
entity_list.LimitRemoveNPC(this);
|
|
entity_list.AddCorpse(corpse, this->GetID());
|
|
|
|
entity_list.UnMarkNPC(GetID());
|
|
entity_list.RemoveNPC(GetID());
|
|
this->SetID(0);
|
|
if(killer != 0 && emoteid != 0)
|
|
corpse->CastToNPC()->DoNPCEmote(AFTERDEATH, emoteid);
|
|
if(killer != 0 && killer->IsClient()) {
|
|
corpse->AllowMobLoot(killer, 0);
|
|
if(killer->IsGrouped()) {
|
|
Group* group = entity_list.GetGroupByClient(killer->CastToClient());
|
|
if(group != 0) {
|
|
for(int i=0;i<6;i++) { // Doesnt work right, needs work
|
|
if(group->members[i] != NULL) {
|
|
corpse->AllowMobLoot(group->members[i],i);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if(killer->IsRaidGrouped()){
|
|
Raid* r = entity_list.GetRaidByClient(killer->CastToClient());
|
|
if(r){
|
|
int i = 0;
|
|
for(int x = 0; x < MAX_RAID_MEMBERS; x++)
|
|
{
|
|
switch(r->GetLootType())
|
|
{
|
|
case 0:
|
|
case 1:
|
|
if(r->members[x].member && r->members[x].IsRaidLeader){
|
|
corpse->AllowMobLoot(r->members[x].member, i);
|
|
i++;
|
|
}
|
|
break;
|
|
case 2:
|
|
if(r->members[x].member && r->members[x].IsRaidLeader){
|
|
corpse->AllowMobLoot(r->members[x].member, i);
|
|
i++;
|
|
}
|
|
else if(r->members[x].member && r->members[x].IsGroupLeader){
|
|
corpse->AllowMobLoot(r->members[x].member, i);
|
|
i++;
|
|
}
|
|
break;
|
|
case 3:
|
|
if(r->members[x].member && r->members[x].IsLooter){
|
|
corpse->AllowMobLoot(r->members[x].member, i);
|
|
i++;
|
|
}
|
|
break;
|
|
case 4:
|
|
if(r->members[x].member)
|
|
{
|
|
corpse->AllowMobLoot(r->members[x].member, i);
|
|
i++;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if(zone && zone->adv_data)
|
|
{
|
|
ServerZoneAdventureDataReply_Struct *sr = (ServerZoneAdventureDataReply_Struct*)zone->adv_data;
|
|
if(sr->type == Adventure_Kill)
|
|
{
|
|
zone->DoAdventureCountIncrease();
|
|
}
|
|
else if(sr->type == Adventure_Assassinate)
|
|
{
|
|
if(sr->data_id == GetNPCTypeID())
|
|
{
|
|
zone->DoAdventureCountIncrease();
|
|
}
|
|
else
|
|
{
|
|
zone->DoAdventureAssassinationCountIncrease();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
entity_list.RemoveFromXTargets(this);
|
|
|
|
// Parse quests even if we're killed by an NPC
|
|
if(killerMob) {
|
|
Mob *oos = killerMob->GetOwnerOrSelf();
|
|
parse->EventNPC(EVENT_DEATH, this, oos, "", 0);
|
|
uint16 emoteid = this->GetNPCEmoteID();
|
|
if(emoteid != 0)
|
|
this->DoNPCEmote(ONDEATH,emoteid);
|
|
if(oos->IsNPC())
|
|
{
|
|
parse->EventNPC(EVENT_NPC_SLAY, oos->CastToNPC(), this, "", 0);
|
|
uint16 emoteid = oos->CastToNPC()->GetNPCEmoteID();
|
|
if(emoteid != 0)
|
|
oos->CastToNPC()->DoNPCEmote(KILLEDNPC,emoteid);
|
|
killerMob->TrySpellOnKill(killed_level,spell);
|
|
}
|
|
}
|
|
|
|
this->WipeHateList();
|
|
p_depop = true;
|
|
if(killerMob && killerMob->GetTarget() == this) //we can kill things without having them targeted
|
|
killerMob->SetTarget(NULL); //via AE effects and such..
|
|
|
|
entity_list.UpdateFindableNPCState(this, true);
|
|
}
|
|
|
|
void Mob::AddToHateList(Mob* other, int32 hate, int32 damage, bool iYellForHelp, bool bFrenzy, bool iBuffTic) {
|
|
assert(other != NULL);
|
|
if (other == this)
|
|
return;
|
|
|
|
if(damage < 0){
|
|
hate = 1;
|
|
}
|
|
|
|
bool wasengaged = IsEngaged();
|
|
Mob* owner = other->GetOwner();
|
|
Mob* mypet = this->GetPet();
|
|
Mob* myowner = this->GetOwner();
|
|
Mob* targetmob = this->GetTarget();
|
|
|
|
if(other){
|
|
AddRampage(other);
|
|
int hatemod = 100 + other->spellbonuses.hatemod + other->itembonuses.hatemod + other->aabonuses.hatemod;
|
|
if(hatemod < 1)
|
|
hatemod = 1;
|
|
hate = ((hate * (hatemod))/100);
|
|
}
|
|
|
|
if(IsPet() && GetOwner() && GetOwner()->GetAA(aaPetDiscipline) && IsHeld() && !IsFocused()) { //ignore aggro if hold and !focus
|
|
return;
|
|
}
|
|
|
|
if(IsPet() && GetOwner() && GetOwner()->GetAA(aaPetDiscipline) && IsHeld() && GetOwner()->GetAA(aaAdvancedPetDiscipline) >= 1 && IsFocused()) {
|
|
if (!targetmob)
|
|
return;
|
|
}
|
|
|
|
if(IsClient() && !IsAIControlled())
|
|
return;
|
|
|
|
if(IsFamiliar() || SpecAttacks[IMMUNE_AGGRO])
|
|
return;
|
|
|
|
if (other == myowner)
|
|
return;
|
|
|
|
if(other->SpecAttacks[IMMUNE_AGGRO_ON])
|
|
return;
|
|
|
|
if(SpecAttacks[NPC_TUNNELVISION]) {
|
|
Mob *top = GetTarget();
|
|
if(top && top != other) {
|
|
hate *= RuleR(Aggro, TunnelVisionAggroMod);
|
|
}
|
|
}
|
|
|
|
if(IsNPC() && CastToNPC()->IsUnderwaterOnly() && zone->HasWaterMap()) {
|
|
if(!zone->watermap->InLiquid(other->GetX(), other->GetY(), other->GetZ())) {
|
|
return;
|
|
}
|
|
}
|
|
// first add self
|
|
|
|
// The damage on the hate list is used to award XP to the killer. This check is to prevent Killstealing.
|
|
// e.g. Mob has 5000 hit points, Player A melees it down to 500 hp, Player B executes a headshot (10000 damage).
|
|
// If we add 10000 damage, Player B would get the kill credit, so we only award damage credit to player B of the
|
|
// amount of HP the mob had left.
|
|
//
|
|
if(damage > GetHP())
|
|
damage = GetHP();
|
|
|
|
hate_list.Add(other, hate, damage, bFrenzy, !iBuffTic);
|
|
|
|
if(other->IsClient())
|
|
other->CastToClient()->AddAutoXTarget(this);
|
|
|
|
#ifdef BOTS
|
|
// if other is a bot, add the bots client to the hate list
|
|
if(other->IsBot()) {
|
|
if(other->CastToBot()->GetBotOwner() && other->CastToBot()->GetBotOwner()->CastToClient()->GetFeigned()) {
|
|
AddFeignMemory(other->CastToBot()->GetBotOwner()->CastToClient());
|
|
}
|
|
else {
|
|
if(!hate_list.IsOnHateList(other->CastToBot()->GetBotOwner()))
|
|
hate_list.Add(other->CastToBot()->GetBotOwner(), 0, 0, false, true);
|
|
}
|
|
}
|
|
#endif //BOTS
|
|
|
|
|
|
// if other is a merc, add the merc client to the hate list
|
|
if(other->IsMerc()) {
|
|
if(other->CastToMerc()->GetMercOwner() && other->CastToMerc()->GetMercOwner()->CastToClient()->GetFeigned()) {
|
|
AddFeignMemory(other->CastToMerc()->GetMercOwner()->CastToClient());
|
|
}
|
|
else {
|
|
if(!hate_list.IsOnHateList(other->CastToMerc()->GetMercOwner()))
|
|
hate_list.Add(other->CastToMerc()->GetMercOwner(), 0, 0, false, true);
|
|
}
|
|
} //MERC
|
|
|
|
// then add pet owner if there's one
|
|
if (owner) { // Other is a pet, add him and it
|
|
// EverHood 6/12/06
|
|
// Can't add a feigned owner to hate list
|
|
if(owner->IsClient() && owner->CastToClient()->GetFeigned()) {
|
|
//they avoid hate due to feign death...
|
|
} else {
|
|
// cb:2007-08-17
|
|
// owner must get on list, but he's not actually gained any hate yet
|
|
if(!owner->SpecAttacks[IMMUNE_AGGRO])
|
|
{
|
|
hate_list.Add(owner, 0, 0, false, !iBuffTic);
|
|
if(owner->IsClient())
|
|
owner->CastToClient()->AddAutoXTarget(this);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (mypet && (!(GetAA(aaPetDiscipline) && mypet->IsHeld()))) { // I have a pet, add other to it
|
|
if(!mypet->IsFamiliar() && !mypet->SpecAttacks[IMMUNE_AGGRO])
|
|
mypet->hate_list.Add(other, 0, 0, bFrenzy);
|
|
} else if (myowner) { // I am a pet, add other to owner if it's NPC/LD
|
|
if (myowner->IsAIControlled() && !myowner->SpecAttacks[IMMUNE_AGGRO])
|
|
myowner->hate_list.Add(other, 0, 0, bFrenzy);
|
|
}
|
|
if (!wasengaged) {
|
|
if(IsNPC() && other->IsClient() && other->CastToClient())
|
|
parse->EventNPC(EVENT_AGGRO, this->CastToNPC(), other, "", 0);
|
|
AI_Event_Engaged(other, iYellForHelp);
|
|
adverrorinfo = 8293;
|
|
}
|
|
}
|
|
|
|
// solar: this is called from Damage() when 'this' is attacked by 'other.
|
|
// 'this' is the one being attacked
|
|
// 'other' is the attacker
|
|
// a damage shield causes damage (or healing) to whoever attacks the wearer
|
|
// a reverse ds causes damage to the wearer whenever it attack someone
|
|
// given this, a reverse ds must be checked each time the wearer is attacking
|
|
// and not when they're attacked
|
|
//a damage shield on a spell is a negative value but on an item it's a positive value so add the spell value and subtract the item value to get the end ds value
|
|
void Mob::DamageShield(Mob* attacker, bool spell_ds) {
|
|
|
|
if(!attacker || this == attacker)
|
|
return;
|
|
|
|
int DS = 0;
|
|
int rev_ds = 0;
|
|
uint16 spellid = 0;
|
|
|
|
if(!spell_ds)
|
|
{
|
|
DS = spellbonuses.DamageShield;
|
|
rev_ds = attacker->spellbonuses.ReverseDamageShield;
|
|
|
|
if(spellbonuses.DamageShieldSpellID != 0 && spellbonuses.DamageShieldSpellID != SPELL_UNKNOWN)
|
|
spellid = spellbonuses.DamageShieldSpellID;
|
|
}
|
|
else {
|
|
DS = spellbonuses.SpellDamageShield;
|
|
rev_ds = 0;
|
|
// This ID returns "you are burned", seemed most appropriate for spell DS
|
|
spellid = 2166;
|
|
}
|
|
|
|
if(DS == 0 && rev_ds == 0)
|
|
return;
|
|
|
|
mlog(COMBAT__HITS, "Applying Damage Shield of value %d to %s", DS, attacker->GetName());
|
|
|
|
//invert DS... spells yield negative values for a true damage shield
|
|
if(DS < 0) {
|
|
if(!spell_ds) {
|
|
|
|
DS += aabonuses.DamageShield; //Live AA - coat of thistles. (negative value)
|
|
DS -= itembonuses.DamageShield; //+Damage Shield should only work when you already have a DS spell
|
|
|
|
//Spell data for damage shield mitigation shows a negative value for spells for clients and positive
|
|
//value for spells that effect pets. Unclear as to why. For now will convert all positive to be consistent.
|
|
if (attacker->IsOffHandAtk()){
|
|
int16 mitigation = attacker->itembonuses.DSMitigationOffHand +
|
|
attacker->spellbonuses.DSMitigationOffHand +
|
|
attacker->aabonuses.DSMitigationOffHand;
|
|
DS -= DS*mitigation/100;
|
|
}
|
|
DS -= DS * attacker->itembonuses.DSMitigation / 100;
|
|
}
|
|
attacker->Damage(this, -DS, spellid, ABJURE/*hackish*/, false);
|
|
//we can assume there is a spell now
|
|
EQApplicationPacket* outapp = new EQApplicationPacket(OP_Damage, sizeof(CombatDamage_Struct));
|
|
CombatDamage_Struct* cds = (CombatDamage_Struct*)outapp->pBuffer;
|
|
cds->target = attacker->GetID();
|
|
cds->source = GetID();
|
|
cds->type = spellbonuses.DamageShieldType;
|
|
cds->spellid = 0x0;
|
|
cds->damage = DS;
|
|
entity_list.QueueCloseClients(this, outapp);
|
|
safe_delete(outapp);
|
|
} else if (DS > 0 && !spell_ds) {
|
|
//we are healing the attacker...
|
|
attacker->HealDamage(DS);
|
|
//TODO: send a packet???
|
|
}
|
|
|
|
//Reverse DS
|
|
//this is basically a DS, but the spell is on the attacker, not the attackee
|
|
//if we've gotten to this point, we know we know "attacker" hit "this" (us) for damage & we aren't invulnerable
|
|
uint16 rev_ds_spell_id = SPELL_UNKNOWN;
|
|
|
|
if(spellbonuses.ReverseDamageShieldSpellID != 0 && spellbonuses.ReverseDamageShieldSpellID != SPELL_UNKNOWN)
|
|
rev_ds_spell_id = spellbonuses.ReverseDamageShieldSpellID;
|
|
|
|
if(rev_ds < 0) {
|
|
mlog(COMBAT__HITS, "Applying Reverse Damage Shield of value %d to %s", rev_ds, attacker->GetName());
|
|
attacker->Damage(this, -rev_ds, rev_ds_spell_id, ABJURE/*hackish*/, false); //"this" (us) will get the hate, etc. not sure how this works on Live, but it'll works for now, and tanks will love us for this
|
|
//do we need to send a damage packet here also?
|
|
/*
|
|
EQApplicationPacket* outapp = new EQApplicationPacket(OP_Damage, sizeof(CombatDamage_Struct));
|
|
CombatDamage_Struct* cds = (CombatDamage_Struct*)outapp->pBuffer;
|
|
cds->target = attacker->GetID();
|
|
cds->source = GetID();
|
|
cds->type = attacker->spellbonuses.ReverseDamageShieldType;
|
|
cds->spellid = 0x0;
|
|
cds->damage = rev_ds;
|
|
entity_list.QueueCloseClients(this, outapp);
|
|
safe_delete(outapp);
|
|
*/
|
|
}
|
|
}
|
|
|
|
uint8 Mob::GetWeaponDamageBonus( const Item_Struct *Weapon )
|
|
{
|
|
_ZP(Mob_GetWeaponDamageBonus);
|
|
// This function calculates and returns the damage bonus for the weapon identified by the parameter "Weapon".
|
|
// Modified 9/21/2008 by Cantus
|
|
|
|
|
|
// Assert: This function should only be called for hits by the mainhand, as damage bonuses apply only to the
|
|
// weapon in the primary slot. Be sure to check that Hand == 13 before calling.
|
|
|
|
// Assert: The caller should ensure that Weapon is actually a weapon before calling this function.
|
|
// The ItemInst::IsWeapon() method can be used to quickly determine this.
|
|
|
|
// Assert: This function should not be called if the player's level is below 28, as damage bonuses do not begin
|
|
// to apply until level 28.
|
|
|
|
// Assert: This function should not be called unless the player is a melee class, as casters do not receive a damage bonus.
|
|
|
|
|
|
if( Weapon == NULL || Weapon->ItemType == ItemType1HS || Weapon->ItemType == ItemType1HB || Weapon->ItemType == ItemTypeHand2Hand || Weapon->ItemType == ItemTypePierce )
|
|
{
|
|
// The weapon in the player's main (primary) hand is a one-handed weapon, or there is no item equipped at all.
|
|
//
|
|
// According to player posts on Allakhazam, 1H damage bonuses apply to bare fists (nothing equipped in the mainhand,
|
|
// as indicated by Weapon == NULL).
|
|
//
|
|
// The following formula returns the correct damage bonus for all 1H weapons:
|
|
|
|
return (uint8) ((GetLevel() - 25) / 3);
|
|
}
|
|
|
|
// If we've gotten to this point, the weapon in the mainhand is a two-handed weapon.
|
|
// Calculating damage bonuses for 2H weapons is more complicated, as it's based on PC level AND the delay of the weapon.
|
|
// The formula to calculate 2H bonuses is HIDEOUS. It's a huge conglomeration of ternary operators and multiple operations.
|
|
//
|
|
// The following is a hybrid approach. In cases where the Level and Delay merit a formula that does not use many operators,
|
|
// the formula is used. In other cases, lookup tables are used for speed.
|
|
// Though the following code may look bloated and ridiculous, it's actually a very efficient way of calculating these bonuses.
|
|
|
|
// Player Level is used several times in the code below, so save it into a variable.
|
|
// If GetLevel() were an ordinary function, this would DEFINITELY make sense, as it'd cut back on all of the function calling
|
|
// overhead involved with multiple calls to GetLevel(). But in this case, GetLevel() is a simple, inline accessor method.
|
|
// So it probably doesn't matter. If anyone knows for certain that there is no overhead involved with calling GetLevel(),
|
|
// as I suspect, then please feel free to delete the following line, and replace all occurences of "ucPlayerLevel" with "GetLevel()".
|
|
uint8 ucPlayerLevel = (uint8) GetLevel();
|
|
|
|
|
|
// The following may look cleaner, and would certainly be easier to understand, if it was
|
|
// a simple 53x150 cell matrix.
|
|
//
|
|
// However, that would occupy 7,950 Bytes of memory (7.76 KB), and would likely result
|
|
// in "thrashing the cache" when performing lookups.
|
|
//
|
|
// Initially, I thought the best approach would be to reverse-engineer the formula used by
|
|
// Sony/Verant to calculate these 2H weapon damage bonuses. But the more than Reno and I
|
|
// worked on figuring out this formula, the more we're concluded that the formula itself ugly
|
|
// (that is, it contains so many operations and conditionals that it's fairly CPU intensive).
|
|
// Because of that, we're decided that, in most cases, a lookup table is the most efficient way
|
|
// to calculate these damage bonuses.
|
|
//
|
|
// The code below is a hybrid between a pure formulaic approach and a pure, brute-force
|
|
// lookup table. In cases where a formula is the best bet, I use a formula. In other places
|
|
// where a formula would be ugly, I use a lookup table in the interests of speed.
|
|
|
|
|
|
if( Weapon->Delay <= 27 )
|
|
{
|
|
// Damage Bonuses for all 2H weapons with delays of 27 or less are identical.
|
|
// They are the same as the damage bonus would be for a corresponding 1H weapon, plus one.
|
|
// This formula applies to all levels 28-80, and will probably continue to apply if
|
|
|
|
// the level cap on Live ever is increased beyond 80.
|
|
|
|
return (ucPlayerLevel - 22) / 3;
|
|
}
|
|
|
|
|
|
if( ucPlayerLevel == 65 && Weapon->Delay <= 59 )
|
|
{
|
|
// Consider these two facts:
|
|
// * Level 65 is the maximum level on many EQ Emu servers.
|
|
// * If you listed the levels of all characters logged on to a server, odds are that the number you'll
|
|
// see most frequently is level 65. That is, there are more level 65 toons than any other single level.
|
|
//
|
|
// Therefore, if we can optimize this function for level 65 toons, we're speeding up the server!
|
|
//
|
|
// With that goal in mind, I create an array of Damage Bonuses for level 65 characters wielding 2H weapons with
|
|
// delays between 28 and 59 (inclusive). I suspect that this one small lookup array will therefore handle
|
|
// many of the calls to this function.
|
|
|
|
static const uint8 ucLevel65DamageBonusesForDelays28to59[] = {35, 35, 36, 36, 37, 37, 38, 38, 39, 39, 40, 40, 42, 42, 42, 45, 45, 47, 48, 49, 49, 51, 51, 52, 53, 54, 54, 56, 56, 57, 58, 59};
|
|
|
|
return ucLevel65DamageBonusesForDelays28to59[Weapon->Delay-28];
|
|
}
|
|
|
|
|
|
if( ucPlayerLevel > 65 )
|
|
{
|
|
if( ucPlayerLevel > 80 )
|
|
{
|
|
// As level 80 is currently the highest achievable level on Live, we only include
|
|
// damage bonus information up to this level.
|
|
//
|
|
// If there is a custom EQEmu server that allows players to level beyond 80, the
|
|
// damage bonus for their 2H weapons will simply not increase beyond their damage
|
|
// bonus at level 80.
|
|
|
|
ucPlayerLevel = 80;
|
|
}
|
|
|
|
// Lucy does not list a chart of damage bonuses for players levels 66+,
|
|
// so my original version of this function just applied the level 65 damage
|
|
// bonus for level 66+ toons. That sucked for higher level toons, as their
|
|
// 2H weapons stopped ramping up in DPS as they leveled past 65.
|
|
//
|
|
// Thanks to the efforts of two guys, this is no longer the case:
|
|
//
|
|
// Janusd (Zetrakyl) ran a nifty query against the PEQ item database to list
|
|
// the name of an example 2H weapon that represents each possible unique 2H delay.
|
|
//
|
|
// Romai then wrote an excellent script to automatically look up each of those
|
|
// weapons, open the Lucy item page associated with it, and iterate through all
|
|
// levels in the range 66 - 80. He saved the damage bonus for that weapon for
|
|
// each level, and that forms the basis of the lookup tables below.
|
|
|
|
if( Weapon->Delay <= 59 )
|
|
{
|
|
static const uint8 ucDelay28to59Levels66to80[32][15]=
|
|
{
|
|
/* Level: */
|
|
/* 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80 */
|
|
|
|
{36, 37, 38, 39, 41, 42, 43, 44, 45, 47, 49, 49, 49, 50, 53}, /* Delay = 28 */
|
|
{36, 38, 38, 39, 42, 43, 43, 45, 46, 48, 49, 50, 51, 52, 54}, /* Delay = 29 */
|
|
{37, 38, 39, 40, 43, 43, 44, 46, 47, 48, 50, 51, 52, 53, 55}, /* Delay = 30 */
|
|
{37, 39, 40, 40, 43, 44, 45, 46, 47, 49, 51, 52, 52, 52, 54}, /* Delay = 31 */
|
|
{38, 39, 40, 41, 44, 45, 45, 47, 48, 48, 50, 52, 53, 55, 57}, /* Delay = 32 */
|
|
{38, 40, 41, 41, 44, 45, 46, 48, 49, 50, 52, 53, 54, 56, 58}, /* Delay = 33 */
|
|
{39, 40, 41, 42, 45, 46, 47, 48, 49, 51, 53, 54, 55, 57, 58}, /* Delay = 34 */
|
|
{39, 41, 42, 43, 46, 46, 47, 49, 50, 52, 54, 55, 56, 57, 59}, /* Delay = 35 */
|
|
{40, 41, 42, 43, 46, 47, 48, 50, 51, 53, 55, 55, 56, 58, 60}, /* Delay = 36 */
|
|
{40, 42, 43, 44, 47, 48, 49, 50, 51, 53, 55, 56, 57, 59, 61}, /* Delay = 37 */
|
|
{41, 42, 43, 44, 47, 48, 49, 51, 52, 54, 56, 57, 58, 60, 62}, /* Delay = 38 */
|
|
{41, 43, 44, 45, 48, 49, 50, 52, 53, 55, 57, 58, 59, 61, 63}, /* Delay = 39 */
|
|
{43, 45, 46, 47, 50, 51, 52, 54, 55, 57, 59, 60, 61, 63, 65}, /* Delay = 40 */
|
|
{43, 45, 46, 47, 50, 51, 52, 54, 55, 57, 59, 60, 61, 63, 65}, /* Delay = 41 */
|
|
{44, 46, 47, 48, 51, 52, 53, 55, 56, 58, 60, 61, 62, 64, 66}, /* Delay = 42 */
|
|
{46, 48, 49, 50, 53, 54, 55, 58, 59, 61, 63, 64, 65, 67, 69}, /* Delay = 43 */
|
|
{47, 49, 50, 51, 54, 55, 56, 58, 59, 61, 64, 65, 66, 68, 70}, /* Delay = 44 */
|
|
{48, 50, 51, 52, 56, 57, 58, 60, 61, 63, 65, 66, 68, 70, 72}, /* Delay = 45 */
|
|
{50, 52, 53, 54, 57, 58, 59, 62, 63, 65, 67, 68, 69, 71, 74}, /* Delay = 46 */
|
|
{50, 52, 53, 55, 58, 59, 60, 62, 63, 66, 68, 69, 70, 72, 74}, /* Delay = 47 */
|
|
{51, 53, 54, 55, 58, 60, 61, 63, 64, 66, 69, 69, 71, 73, 75}, /* Delay = 48 */
|
|
{52, 54, 55, 57, 60, 61, 62, 65, 66, 68, 70, 71, 73, 75, 77}, /* Delay = 49 */
|
|
{53, 55, 56, 57, 61, 62, 63, 65, 67, 69, 71, 72, 74, 76, 78}, /* Delay = 50 */
|
|
{53, 55, 57, 58, 61, 62, 64, 66, 67, 69, 72, 73, 74, 77, 79}, /* Delay = 51 */
|
|
{55, 57, 58, 59, 63, 64, 65, 68, 69, 71, 74, 75, 76, 78, 81}, /* Delay = 52 */
|
|
{57, 55, 59, 60, 63, 65, 66, 68, 70, 72, 74, 76, 77, 79, 82}, /* Delay = 53 */
|
|
{56, 58, 59, 61, 64, 65, 67, 69, 70, 73, 75, 76, 78, 80, 82}, /* Delay = 54 */
|
|
{57, 59, 61, 62, 66, 67, 68, 71, 72, 74, 77, 78, 80, 82, 84}, /* Delay = 55 */
|
|
{58, 60, 61, 63, 66, 68, 69, 71, 73, 75, 78, 79, 80, 83, 85}, /* Delay = 56 */
|
|
|
|
/* Important Note: Janusd's search for 2H weapons did not find */
|
|
/* any 2H weapon with a delay of 57. Therefore the values below */
|
|
/* are interpolated, not exact! */
|
|
{59, 61, 62, 64, 67, 69, 70, 72, 74, 76, 77, 78, 81, 84, 86}, /* Delay = 57 INTERPOLATED */
|
|
|
|
{60, 62, 63, 65, 68, 70, 71, 74, 75, 78, 80, 81, 83, 85, 88}, /* Delay = 58 */
|
|
|
|
/* Important Note: Janusd's search for 2H weapons did not find */
|
|
/* any 2H weapon with a delay of 59. Therefore the values below */
|
|
/* are interpolated, not exact! */
|
|
{60, 62, 64, 65, 69, 70, 72, 74, 76, 78, 81, 82, 84, 86, 89}, /* Delay = 59 INTERPOLATED */
|
|
};
|
|
|
|
return ucDelay28to59Levels66to80[Weapon->Delay-28][ucPlayerLevel-66];
|
|
}
|
|
else
|
|
{
|
|
// Delay is 60+
|
|
|
|
const static uint8 ucDelayOver59Levels66to80[6][15] =
|
|
{
|
|
/* Level: */
|
|
/* 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80 */
|
|
|
|
{61, 63, 65, 66, 70, 71, 73, 75, 77, 79, 82, 83, 85, 87, 90}, /* Delay = 60 */
|
|
{65, 68, 69, 71, 75, 76, 78, 80, 82, 85, 87, 89, 91, 93, 96}, /* Delay = 65 */
|
|
|
|
/* Important Note: Currently, the only 2H weapon with a delay */
|
|
/* of 66 is not player equippable (it's None/None). So I'm */
|
|
/* leaving it commented out to keep this table smaller. */
|
|
//{66, 68, 70, 71, 75, 77, 78, 81, 83, 85, 88, 90, 91, 94, 97}, /* Delay = 66 */
|
|
|
|
{70, 72, 74, 76, 80, 81, 83, 86, 88, 88, 90, 95, 97, 99, 102}, /* Delay = 70 */
|
|
{82, 85, 87, 89, 89, 94, 98, 101, 103, 106, 109, 111, 114, 117, 120}, /* Delay = 85 */
|
|
{90, 93, 96, 98, 103, 105, 107, 111, 113, 116, 120, 122, 125, 128, 131}, /* Delay = 95 */
|
|
|
|
/* Important Note: Currently, the only 2H weapons with delay */
|
|
/* 100 are GM-only items purchased from vendors in Sunset Home */
|
|
/* (cshome). Because they are highly unlikely to be used in */
|
|
/* combat, I'm commenting it out to keep the table smaller. */
|
|
//{95, 98, 101, 103, 108, 110, 113, 116, 119, 122, 126, 128, 131, 134, 138},/* Delay = 100 */
|
|
|
|
{136, 140, 144, 148, 154, 157, 161, 166, 170, 174, 179, 183, 187, 191, 196} /* Delay = 150 */
|
|
};
|
|
|
|
if( Weapon->Delay < 65 )
|
|
{
|
|
return ucDelayOver59Levels66to80[0][ucPlayerLevel-66];
|
|
}
|
|
else if( Weapon->Delay < 70 )
|
|
{
|
|
return ucDelayOver59Levels66to80[1][ucPlayerLevel-66];
|
|
}
|
|
else if( Weapon->Delay < 85 )
|
|
{
|
|
return ucDelayOver59Levels66to80[2][ucPlayerLevel-66];
|
|
}
|
|
else if( Weapon->Delay < 95 )
|
|
{
|
|
return ucDelayOver59Levels66to80[3][ucPlayerLevel-66];
|
|
}
|
|
else if( Weapon->Delay < 150 )
|
|
{
|
|
return ucDelayOver59Levels66to80[4][ucPlayerLevel-66];
|
|
}
|
|
else
|
|
{
|
|
return ucDelayOver59Levels66to80[5][ucPlayerLevel-66];
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// If we've gotten to this point in the function without hitting a return statement,
|
|
// we know that the character's level is between 28 and 65, and that the 2H weapon's
|
|
// delay is 28 or higher.
|
|
|
|
// The Damage Bonus values returned by this function (in the level 28-65 range) are
|
|
// based on a table of 2H Weapon Damage Bonuses provided by Lucy at the following address:
|
|
// http://lucy.allakhazam.com/dmgbonus.html
|
|
|
|
if( Weapon->Delay <= 39 )
|
|
{
|
|
if( ucPlayerLevel <= 53)
|
|
{
|
|
// The Damage Bonus for all 2H weapons with delays between 28 and 39 (inclusive) is the same for players level 53 and below...
|
|
static const uint8 ucDelay28to39LevelUnder54[] = {1, 1, 2, 3, 3, 3, 4, 5, 5, 6, 6, 6, 8, 8, 8, 9, 9, 10, 11, 11, 11, 12, 13, 14, 16, 17};
|
|
|
|
// As a note: The following formula accurately calculates damage bonuses for 2H weapons with delays in the range 28-39 (inclusive)
|
|
// for characters levels 28-50 (inclusive):
|
|
// return ( (ucPlayerLevel - 22) / 3 ) + ( (ucPlayerLevel - 25) / 5 );
|
|
//
|
|
// However, the small lookup array used above is actually much faster. So we'll just use it instead of the formula
|
|
//
|
|
// (Thanks to Reno for helping figure out the above formula!)
|
|
|
|
return ucDelay28to39LevelUnder54[ucPlayerLevel-28];
|
|
}
|
|
else
|
|
{
|
|
// Use a matrix to look up the damage bonus for 2H weapons with delays between 28 and 39 wielded by characters level 54 and above.
|
|
static const uint8 ucDelay28to39Level54to64[12][11] =
|
|
{
|
|
/* Level: */
|
|
/* 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64 */
|
|
|
|
{17, 21, 21, 23, 25, 26, 28, 30, 31, 31, 33}, /* Delay = 28 */
|
|
{17, 21, 22, 23, 25, 26, 29, 30, 31, 32, 34}, /* Delay = 29 */
|
|
{18, 21, 22, 23, 25, 27, 29, 31, 32, 32, 34}, /* Delay = 30 */
|
|
{18, 21, 22, 23, 25, 27, 29, 31, 32, 33, 34}, /* Delay = 31 */
|
|
{18, 21, 22, 24, 26, 27, 30, 32, 32, 33, 35}, /* Delay = 32 */
|
|
{18, 21, 22, 24, 26, 27, 30, 32, 33, 34, 35}, /* Delay = 33 */
|
|
{18, 22, 22, 24, 26, 28, 30, 32, 33, 34, 36}, /* Delay = 34 */
|
|
{18, 22, 23, 24, 26, 28, 31, 33, 34, 34, 36}, /* Delay = 35 */
|
|
{18, 22, 23, 25, 27, 28, 31, 33, 34, 35, 37}, /* Delay = 36 */
|
|
{18, 22, 23, 25, 27, 29, 31, 33, 34, 35, 37}, /* Delay = 37 */
|
|
{18, 22, 23, 25, 27, 29, 32, 34, 35, 36, 38}, /* Delay = 38 */
|
|
{18, 22, 23, 25, 27, 29, 32, 34, 35, 36, 38} /* Delay = 39 */
|
|
};
|
|
|
|
return ucDelay28to39Level54to64[Weapon->Delay-28][ucPlayerLevel-54];
|
|
}
|
|
}
|
|
else if( Weapon->Delay <= 59 )
|
|
{
|
|
if( ucPlayerLevel <= 52 )
|
|
{
|
|
if( Weapon->Delay <= 45 )
|
|
{
|
|
static const uint8 ucDelay40to45Levels28to52[6][25] =
|
|
{
|
|
/* Level: */
|
|
/* 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52 */
|
|
|
|
{2, 2, 3, 4, 4, 4, 5, 6, 6, 7, 7, 7, 9, 9, 9, 10, 10, 11, 12, 12, 12, 13, 14, 16, 18}, /* Delay = 40 */
|
|
{2, 2, 3, 4, 4, 4, 5, 6, 6, 7, 7, 7, 9, 9, 9, 10, 10, 11, 12, 12, 12, 13, 14, 16, 18}, /* Delay = 41 */
|
|
{2, 2, 3, 4, 4, 4, 5, 6, 6, 7, 7, 7, 9, 9, 9, 10, 10, 11, 12, 12, 12, 13, 14, 16, 18}, /* Delay = 42 */
|
|
{4, 4, 5, 6, 6, 6, 7, 8, 8, 9, 9, 9, 11, 11, 11, 12, 12, 13, 14, 14, 14, 15, 16, 18, 20}, /* Delay = 43 */
|
|
{4, 4, 5, 6, 6, 6, 7, 8, 8, 9, 9, 9, 11, 11, 11, 12, 12, 13, 14, 14, 14, 15, 16, 18, 20}, /* Delay = 44 */
|
|
{5, 5, 6, 7, 7, 7, 8, 9, 9, 10, 10, 10, 12, 12, 12, 13, 13, 14, 15, 15, 15, 16, 17, 19, 21} /* Delay = 45 */
|
|
};
|
|
|
|
return ucDelay40to45Levels28to52[Weapon->Delay-40][ucPlayerLevel-28];
|
|
}
|
|
else
|
|
{
|
|
static const uint8 ucDelay46Levels28to52[] = {6, 6, 7, 8, 8, 8, 9, 10, 10, 11, 11, 11, 13, 13, 13, 14, 14, 15, 16, 16, 16, 17, 18, 20, 22};
|
|
|
|
return ucDelay46Levels28to52[ucPlayerLevel-28] + ((Weapon->Delay-46) / 3);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Player is in the level range 53 - 64
|
|
|
|
// Calculating damage bonus for 2H weapons with a delay between 40 and 59 (inclusive) involves, unforunately, a brute-force matrix lookup.
|
|
static const uint8 ucDelay40to59Levels53to64[20][37] =
|
|
{
|
|
/* Level: */
|
|
/* 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64 */
|
|
|
|
{19, 20, 24, 25, 27, 29, 31, 34, 36, 37, 38, 40}, /* Delay = 40 */
|
|
{19, 20, 24, 25, 27, 29, 31, 34, 36, 37, 38, 40}, /* Delay = 41 */
|
|
{19, 20, 24, 25, 27, 29, 31, 34, 36, 37, 38, 40}, /* Delay = 42 */
|
|
{21, 22, 26, 27, 29, 31, 33, 37, 39, 40, 41, 43}, /* Delay = 43 */
|
|
{21, 22, 26, 27, 29, 32, 34, 37, 39, 40, 41, 43}, /* Delay = 44 */
|
|
{22, 23, 27, 28, 31, 33, 35, 38, 40, 42, 43, 45}, /* Delay = 45 */
|
|
{23, 24, 28, 30, 32, 34, 36, 40, 42, 43, 44, 46}, /* Delay = 46 */
|
|
{23, 24, 29, 30, 32, 34, 37, 40, 42, 43, 44, 47}, /* Delay = 47 */
|
|
{23, 24, 29, 30, 32, 35, 37, 40, 43, 44, 45, 47}, /* Delay = 48 */
|
|
{24, 25, 30, 31, 34, 36, 38, 42, 44, 45, 46, 49}, /* Delay = 49 */
|
|
{24, 26, 30, 31, 34, 36, 39, 42, 44, 46, 47, 49}, /* Delay = 50 */
|
|
{24, 26, 30, 31, 34, 36, 39, 42, 45, 46, 47, 49}, /* Delay = 51 */
|
|
{25, 27, 31, 33, 35, 38, 40, 44, 46, 47, 49, 51}, /* Delay = 52 */
|
|
{25, 27, 31, 33, 35, 38, 40, 44, 46, 48, 49, 51}, /* Delay = 53 */
|
|
{26, 27, 32, 33, 36, 38, 41, 44, 47, 48, 49, 52}, /* Delay = 54 */
|
|
{27, 28, 33, 34, 37, 39, 42, 46, 48, 50, 51, 53}, /* Delay = 55 */
|
|
{27, 28, 33, 34, 37, 40, 42, 46, 49, 50, 51, 54}, /* Delay = 56 */
|
|
{27, 28, 33, 34, 37, 40, 43, 46, 49, 50, 52, 54}, /* Delay = 57 */
|
|
{28, 29, 34, 36, 39, 41, 44, 48, 50, 52, 53, 56}, /* Delay = 58 */
|
|
{28, 29, 34, 36, 39, 41, 44, 48, 51, 52, 54, 56} /* Delay = 59 */
|
|
};
|
|
|
|
return ucDelay40to59Levels53to64[Weapon->Delay-40][ucPlayerLevel-53];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// The following table allows us to look up Damage Bonuses for weapons with delays greater than or equal to 60.
|
|
//
|
|
// There aren't a lot of 2H weapons with a delay greater than 60. In fact, both a database and Lucy search run by janusd confirm
|
|
// that the only unique 2H delays greater than 60 are: 65, 70, 85, 95, and 150.
|
|
//
|
|
// To be fair, there are also weapons with delays of 66 and 100. But they are either not equippable (None/None), or are
|
|
// only available to GMs from merchants in Sunset Home (cshome). In order to keep this table "lean and mean", I will not
|
|
// include the values for delays 66 and 100. If they ever are wielded, the 66 delay weapon will use the 65 delay bonuses,
|
|
// and the 100 delay weapon will use the 95 delay bonuses. So it's not a big deal.
|
|
//
|
|
// Still, if someone in the future decides that they do want to include them, here are the tables for these two delays:
|
|
//
|
|
// {12, 12, 13, 14, 14, 14, 15, 16, 16, 17, 17, 17, 19, 19, 19, 20, 20, 21, 22, 22, 22, 23, 24, 26, 29, 30, 32, 37, 39, 42, 45, 48, 53, 55, 57, 59, 61, 64} /* Delay = 66 */
|
|
// {24, 24, 25, 26, 26, 26, 27, 28, 28, 29, 29, 29, 31, 31, 31, 32, 32, 33, 34, 34, 34, 35, 36, 39, 43, 45, 48, 55, 57, 62, 66, 71, 77, 80, 83, 85, 89, 92} /* Delay = 100 */
|
|
//
|
|
// In case there are 2H weapons added in the future with delays other than those listed above (and until the damage bonuses
|
|
// associated with that new delay are added to this function), this function is designed to do the following:
|
|
//
|
|
// For weapons with delays in the range 60-64, use the Damage Bonus that would apply to a 2H weapon with delay 60.
|
|
// For weapons with delays in the range 65-69, use the Damage Bonus that would apply to a 2H weapon with delay 65
|
|
// For weapons with delays in the range 70-84, use the Damage Bonus that would apply to a 2H weapon with delay 70.
|
|
// For weapons with delays in the range 85-94, use the Damage Bonus that would apply to a 2H weapon with delay 85.
|
|
// For weapons with delays in the range 95-149, use the Damage Bonus that would apply to a 2H weapon with delay 95.
|
|
// For weapons with delays 150 or higher, use the Damage Bonus that would apply to a 2H weapon with delay 150.
|
|
|
|
static const uint8 ucDelayOver59Levels28to65[6][38] =
|
|
{
|
|
/* Level: */
|
|
/* 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64. 65 */
|
|
|
|
{10, 10, 11, 12, 12, 12, 13, 14, 14, 15, 15, 15, 17, 17, 17, 18, 18, 19, 20, 20, 20, 21, 22, 24, 27, 28, 30, 35, 36, 39, 42, 45, 49, 51, 53, 54, 57, 59}, /* Delay = 60 */
|
|
{12, 12, 13, 14, 14, 14, 15, 16, 16, 17, 17, 17, 19, 19, 19, 20, 20, 21, 22, 22, 22, 23, 24, 26, 29, 30, 32, 37, 39, 42, 45, 48, 52, 55, 57, 58, 61, 63}, /* Delay = 65 */
|
|
{14, 14, 15, 16, 16, 16, 17, 18, 18, 19, 19, 19, 21, 21, 21, 22, 22, 23, 24, 24, 24, 25, 26, 28, 31, 33, 35, 40, 42, 45, 48, 52, 56, 59, 61, 62, 65, 68}, /* Delay = 70 */
|
|
{19, 19, 20, 21, 21, 21, 22, 23, 23, 24, 24, 24, 26, 26, 26, 27, 27, 28, 29, 29, 29, 30, 31, 34, 37, 39, 41, 47, 49, 54, 57, 61, 66, 69, 72, 74, 77, 80}, /* Delay = 85 */
|
|
{22, 22, 23, 24, 24, 24, 25, 26, 26, 27, 27, 27, 29, 29, 29, 30, 30, 31, 32, 32, 32, 33, 34, 37, 40, 43, 45, 52, 54, 59, 62, 67, 73, 76, 79, 81, 84, 88}, /* Delay = 95 */
|
|
{40, 40, 41, 42, 42, 42, 43, 44, 44, 45, 45, 45, 47, 47, 47, 48, 48, 49, 50, 50, 50, 51, 52, 56, 61, 65, 69, 78, 82, 89, 94, 102, 110, 115, 119, 122, 127, 132} /* Delay = 150 */
|
|
};
|
|
|
|
if( Weapon->Delay < 65 )
|
|
{
|
|
return ucDelayOver59Levels28to65[0][ucPlayerLevel-28];
|
|
}
|
|
else if( Weapon->Delay < 70 )
|
|
{
|
|
return ucDelayOver59Levels28to65[1][ucPlayerLevel-28];
|
|
}
|
|
else if( Weapon->Delay < 85 )
|
|
{
|
|
return ucDelayOver59Levels28to65[2][ucPlayerLevel-28];
|
|
}
|
|
else if( Weapon->Delay < 95 )
|
|
{
|
|
return ucDelayOver59Levels28to65[3][ucPlayerLevel-28];
|
|
}
|
|
else if( Weapon->Delay < 150 )
|
|
{
|
|
return ucDelayOver59Levels28to65[4][ucPlayerLevel-28];
|
|
}
|
|
else
|
|
{
|
|
return ucDelayOver59Levels28to65[5][ucPlayerLevel-28];
|
|
}
|
|
}
|
|
}
|
|
|
|
int Mob::GetMonkHandToHandDamage(void)
|
|
{
|
|
// Kaiyodo - Determine a monk's fist damage. Table data from www.monkly-business.com
|
|
// saved as static array - this should speed this function up considerably
|
|
static int damage[66] = {
|
|
// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
|
|
99, 4, 4, 4, 4, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7,
|
|
8, 8, 8, 8, 8, 9, 9, 9, 9, 9,10,10,10,10,10,11,11,11,11,11,
|
|
12,12,12,12,12,13,13,13,13,13,14,14,14,14,14,14,14,14,14,14,
|
|
14,14,15,15,15,15 };
|
|
|
|
// Have a look to see if we have epic fists on
|
|
|
|
if (IsClient() && CastToClient()->GetItemIDAt(12) == 10652)
|
|
return(9);
|
|
else
|
|
{
|
|
int Level = GetLevel();
|
|
if (Level > 65)
|
|
return(19);
|
|
else
|
|
return damage[Level];
|
|
}
|
|
}
|
|
|
|
int Mob::GetMonkHandToHandDelay(void)
|
|
{
|
|
// Kaiyodo - Determine a monk's fist delay. Table data from www.monkly-business.com
|
|
// saved as static array - this should speed this function up considerably
|
|
static int delayshuman[66] = {
|
|
// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
|
|
99,36,36,36,36,36,36,36,36,36,36,36,36,36,36,36,36,36,36,36,
|
|
36,36,36,36,36,35,35,35,35,35,34,34,34,34,34,33,33,33,33,33,
|
|
32,32,32,32,32,31,31,31,31,31,30,30,30,29,29,29,28,28,28,27,
|
|
26,24,22,20,20,20 };
|
|
static int delaysiksar[66] = {
|
|
// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
|
|
99,36,36,36,36,36,36,36,36,36,36,36,36,36,36,36,36,36,36,36,
|
|
36,36,36,36,36,36,36,36,36,36,35,35,35,35,35,34,34,34,34,34,
|
|
33,33,33,33,33,32,32,32,32,32,31,31,31,30,30,30,29,29,29,28,
|
|
27,24,22,20,20,20 };
|
|
|
|
// Have a look to see if we have epic fists on
|
|
if (IsClient() && CastToClient()->GetItemIDAt(12) == 10652)
|
|
return(16);
|
|
else
|
|
{
|
|
int Level = GetLevel();
|
|
if (GetRace() == HUMAN)
|
|
{
|
|
if (Level > 65)
|
|
return(24);
|
|
else
|
|
return delayshuman[Level];
|
|
}
|
|
else //heko: iksar table
|
|
{
|
|
if (Level > 65)
|
|
return(25);
|
|
else
|
|
return delaysiksar[Level];
|
|
}
|
|
}
|
|
}
|
|
|
|
int32 Mob::ReduceDamage(int32 damage)
|
|
{
|
|
if(damage <= 0)
|
|
return damage;
|
|
|
|
int32 slot = -1;
|
|
|
|
if (spellbonuses.NegateAttacks[0]){
|
|
slot = spellbonuses.NegateAttacks[1];
|
|
if(slot >= 0) {
|
|
if(CheckHitsRemaining(slot, false, true))
|
|
return -6;
|
|
}
|
|
}
|
|
|
|
if (spellbonuses.MitigateMeleeRune[0]){
|
|
slot = spellbonuses.MitigateMeleeRune[1];
|
|
if(slot >= 0)
|
|
{
|
|
int damage_to_reduce = damage * spellbonuses.MitigateMeleeRune[0] / 100;
|
|
if(damage_to_reduce > buffs[slot].melee_rune)
|
|
{
|
|
mlog(SPELLS__EFFECT_VALUES, "Mob::ReduceDamage SE_MitigateMeleeDamage %d damage negated, %d"
|
|
" damage remaining, fading buff.", damage_to_reduce, buffs[slot].melee_rune);
|
|
damage -= damage_to_reduce;
|
|
if(!TryFadeEffect(slot))
|
|
BuffFadeBySlot(slot);
|
|
UpdateRuneFlags();
|
|
}
|
|
else
|
|
{
|
|
mlog(SPELLS__EFFECT_VALUES, "Mob::ReduceDamage SE_MitigateMeleeDamage %d damage negated, %d"
|
|
" damage remaining.", damage_to_reduce, buffs[slot].melee_rune);
|
|
buffs[slot].melee_rune = (buffs[slot].melee_rune - damage_to_reduce);
|
|
damage -= damage_to_reduce;
|
|
if (!CheckHitsRemaining(slot))
|
|
UpdateRuneFlags();
|
|
}
|
|
}
|
|
}
|
|
|
|
if(damage < 1)
|
|
return -6;
|
|
|
|
if (HasRune())
|
|
damage = RuneAbsorb(damage, SE_Rune);
|
|
|
|
if(damage < 1)
|
|
return -6;
|
|
|
|
if (spellbonuses.ManaAbsorbPercentDamage[0]){
|
|
slot = spellbonuses.ManaAbsorbPercentDamage[1];
|
|
if(GetMana() > damage * spellbonuses.ManaAbsorbPercentDamage[0] / 100) {
|
|
damage -= (damage * spellbonuses.ManaAbsorbPercentDamage[0] / 100);
|
|
SetMana(GetMana() - damage);
|
|
CheckHitsRemaining(slot);
|
|
}
|
|
}
|
|
|
|
return(damage);
|
|
}
|
|
|
|
int32 Mob::AffectMagicalDamage(int32 damage, uint16 spell_id, const bool iBuffTic, Mob* attacker)
|
|
{
|
|
if(damage <= 0)
|
|
return damage;
|
|
|
|
int32 slot = -1;
|
|
|
|
// See if we block the spell outright first
|
|
if (spellbonuses.NegateAttacks[0]){
|
|
slot = spellbonuses.NegateAttacks[1];
|
|
if(slot >= 0) {
|
|
if(CheckHitsRemaining(slot, false, true))
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
// If this is a DoT, use DoT Shielding...
|
|
if(iBuffTic)
|
|
damage -= (damage * itembonuses.DoTShielding / 100);
|
|
|
|
// This must be a DD then so lets apply Spell Shielding and runes.
|
|
else
|
|
{
|
|
// Reduce damage by the Spell Shielding first so that the runes don't take the raw damage.
|
|
damage -= (damage * itembonuses.SpellShield / 100);
|
|
|
|
// Do runes now.
|
|
if (spellbonuses.MitigateSpellRune[0]){
|
|
slot = spellbonuses.MitigateSpellRune[1];
|
|
if(slot >= 0)
|
|
{
|
|
int damage_to_reduce = damage * spellbonuses.MitigateSpellRune[0] / 100;
|
|
if(damage_to_reduce > buffs[slot].magic_rune)
|
|
{
|
|
mlog(SPELLS__EFFECT_VALUES, "Mob::ReduceDamage SE_MitigateSpellDamage %d damage negated, %d"
|
|
" damage remaining, fading buff.", damage_to_reduce, buffs[slot].magic_rune);
|
|
damage -= damage_to_reduce;
|
|
if(!TryFadeEffect(slot))
|
|
BuffFadeBySlot(slot);
|
|
UpdateRuneFlags();
|
|
}
|
|
else
|
|
{
|
|
mlog(SPELLS__EFFECT_VALUES, "Mob::ReduceDamage SE_MitigateMeleeDamage %d damage negated, %d"
|
|
" damage remaining.", damage_to_reduce, buffs[slot].magic_rune);
|
|
buffs[slot].magic_rune = (buffs[slot].magic_rune - damage_to_reduce);
|
|
damage -= damage_to_reduce;
|
|
if (!CheckHitsRemaining(slot))
|
|
UpdateRuneFlags();
|
|
}
|
|
}
|
|
}
|
|
|
|
if(damage < 1)
|
|
return 0;
|
|
|
|
|
|
if (HasSpellRune())
|
|
damage = RuneAbsorb(damage, SE_AbsorbMagicAtt);
|
|
|
|
if(damage < 1)
|
|
return 0;
|
|
|
|
if (spellbonuses.ManaAbsorbPercentDamage[0]){
|
|
slot = spellbonuses.ManaAbsorbPercentDamage[1];
|
|
if(GetMana() > damage * spellbonuses.ManaAbsorbPercentDamage[0] / 100) {
|
|
damage -= (damage * spellbonuses.ManaAbsorbPercentDamage[0] / 100);
|
|
SetMana(GetMana() - damage);
|
|
CheckHitsRemaining(slot);
|
|
}
|
|
}
|
|
}
|
|
return damage;
|
|
}
|
|
|
|
bool Mob::HasProcs() const
|
|
{
|
|
for (int i = 0; i < MAX_PROCS; i++)
|
|
if (PermaProcs[i].spellID != SPELL_UNKNOWN || SpellProcs[i].spellID != SPELL_UNKNOWN)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
bool Mob::HasDefensiveProcs() const
|
|
{
|
|
for (int i = 0; i < MAX_PROCS; i++)
|
|
if (DefensiveProcs[i].spellID != SPELL_UNKNOWN)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
bool Mob::HasSkillProcs() const
|
|
{
|
|
for (int i = 0; i < MAX_PROCS; i++)
|
|
if (SkillProcs[i].spellID != SPELL_UNKNOWN)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
bool Mob::HasRangedProcs() const
|
|
{
|
|
for (int i = 0; i < MAX_PROCS; i++)
|
|
if (RangedProcs[i].spellID != SPELL_UNKNOWN)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
bool Client::CheckDoubleAttack(bool tripleAttack) {
|
|
|
|
//Check for bonuses that give you a double attack chance regardless of skill (ie Bestial Frenzy/Harmonious Attack AA)
|
|
uint16 bonusGiveDA = aabonuses.GiveDoubleAttack + spellbonuses.GiveDoubleAttack + itembonuses.GiveDoubleAttack;
|
|
|
|
if(!HasSkill(DOUBLE_ATTACK) && !bonusGiveDA)
|
|
return false;
|
|
|
|
float chance = 0.0f;
|
|
|
|
uint16 skill = GetSkill(DOUBLE_ATTACK);
|
|
|
|
int16 bonusDA = aabonuses.DoubleAttackChance + spellbonuses.DoubleAttackChance + itembonuses.DoubleAttackChance;
|
|
|
|
//Use skill calculations otherwise, if you only have AA applied GiveDoubleAttack chance then use that value as the base.
|
|
if (skill)
|
|
chance = (float(skill+GetLevel()) * (float(100.0f+bonusDA+bonusGiveDA) /100.0f)) /500.0f;
|
|
else
|
|
chance = (float(bonusGiveDA) * (float(100.0f+bonusDA)/100.0f) ) /100.0f;
|
|
|
|
//Live now uses a static Triple Attack skill (lv 46 = 2% lv 60 = 20%) - We do not have this skill on EMU ATM.
|
|
//A reasonable forumla would then be TA = 20% * chance
|
|
//AA's can also give triple attack skill over cap. (ie Burst of Power) NOTE: Skill ID in spell data is 76 (Triple Attack)
|
|
//Kayen: Need to decide if we can implement triple attack skill before working in over the cap effect.
|
|
if(tripleAttack) {
|
|
// Only some Double Attack classes get Triple Attack [This is already checked in client_processes.cpp]
|
|
int16 triple_bonus = spellbonuses.TripleAttackChance + itembonuses.TripleAttackChance;
|
|
chance *= 0.2f; //Baseline chance is 20% of your double attack chance.
|
|
chance *= float(100.0f+triple_bonus)/100.0f; //Apply modifiers.
|
|
}
|
|
|
|
if((MakeRandomFloat(0, 1) < chance))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
void Mob::CommonDamage(Mob* attacker, int32 &damage, const uint16 spell_id, const SkillType skill_used, bool &avoidable, const int8 buffslot, const bool iBuffTic) {
|
|
// This method is called with skill_used=ABJURE for Damage Shield damage.
|
|
bool FromDamageShield = (skill_used == ABJURE);
|
|
|
|
mlog(COMBAT__HITS, "Applying damage %d done by %s with skill %d and spell %d, avoidable? %s, is %sa buff tic in slot %d",
|
|
damage, attacker?attacker->GetName():"NOBODY", skill_used, spell_id, avoidable?"yes":"no", iBuffTic?"":"not ", buffslot);
|
|
|
|
if (GetInvul() || DivineAura()) {
|
|
mlog(COMBAT__DAMAGE, "Avoiding %d damage due to invulnerability.", damage);
|
|
damage = -5;
|
|
}
|
|
|
|
if( spell_id != SPELL_UNKNOWN || attacker == NULL )
|
|
avoidable = false;
|
|
|
|
// only apply DS if physical damage (no spell damage)
|
|
// damage shield calls this function with spell_id set, so its unavoidable
|
|
if (attacker && damage > 0 && spell_id == SPELL_UNKNOWN && skill_used != ARCHERY && skill_used != THROWING) {
|
|
DamageShield(attacker);
|
|
|
|
if (spellbonuses.DamageShield)
|
|
CheckHitsRemaining(0, false, false, SE_DamageShield);
|
|
}
|
|
|
|
if(attacker){
|
|
if(attacker->IsClient()){
|
|
if(!RuleB(Combat, EXPFromDmgShield)) {
|
|
// Damage shield damage shouldn't count towards who gets EXP
|
|
if(!attacker->CastToClient()->GetFeigned() && !FromDamageShield)
|
|
AddToHateList(attacker, 0, damage, true, false, iBuffTic);
|
|
}
|
|
else {
|
|
if(!attacker->CastToClient()->GetFeigned())
|
|
AddToHateList(attacker, 0, damage, true, false, iBuffTic);
|
|
}
|
|
}
|
|
else
|
|
AddToHateList(attacker, 0, damage, true, false, iBuffTic);
|
|
}
|
|
|
|
if(damage > 0) {
|
|
//if there is some damage being done and theres an attacker involved
|
|
if(attacker) {
|
|
if(spell_id == SPELL_HARM_TOUCH2 && attacker->IsClient() && attacker->CastToClient()->CheckAAEffect(aaEffectLeechTouch)){
|
|
int healed = damage;
|
|
healed = attacker->GetActSpellHealing(spell_id, healed);
|
|
attacker->HealDamage(healed);
|
|
entity_list.MessageClose(this, true, 300, MT_Emote, "%s beams a smile at %s", attacker->GetCleanName(), this->GetCleanName() );
|
|
attacker->CastToClient()->DisableAAEffect(aaEffectLeechTouch);
|
|
}
|
|
|
|
// if spell is lifetap add hp to the caster
|
|
if (spell_id != SPELL_UNKNOWN && IsLifetapSpell( spell_id )) {
|
|
int healed = damage;
|
|
|
|
healed = attacker->GetActSpellHealing(spell_id, healed);
|
|
mlog(COMBAT__DAMAGE, "Applying lifetap heal of %d to %s", healed, attacker->GetName());
|
|
attacker->HealDamage(healed);
|
|
|
|
//we used to do a message to the client, but its gone now.
|
|
// emote goes with every one ... even npcs
|
|
entity_list.MessageClose(this, true, 300, MT_Emote, "%s beams a smile at %s", attacker->GetCleanName(), this->GetCleanName() );
|
|
}
|
|
} //end `if there is some damage being done and theres anattacker person involved`
|
|
|
|
Mob *pet = GetPet();
|
|
if (pet && !pet->IsFamiliar() && !pet->SpecAttacks[IMMUNE_AGGRO] && !pet->IsEngaged() && attacker && attacker != this && !attacker->IsCorpse())
|
|
{
|
|
if (!pet->IsHeld()) {
|
|
mlog(PETS__AGGRO, "Sending pet %s into battle due to attack.", pet->GetName());
|
|
pet->AddToHateList(attacker, 1);
|
|
pet->SetTarget(attacker);
|
|
Message_StringID(10, PET_ATTACKING, pet->GetCleanName(), attacker->GetCleanName());
|
|
}
|
|
}
|
|
|
|
//see if any runes want to reduce this damage
|
|
if(spell_id == SPELL_UNKNOWN) {
|
|
damage = ReduceDamage(damage);
|
|
mlog(COMBAT__HITS, "Melee Damage reduced to %d", damage);
|
|
} else {
|
|
int32 origdmg = damage;
|
|
damage = AffectMagicalDamage(damage, spell_id, iBuffTic, attacker);
|
|
if (origdmg != damage && attacker && attacker->IsClient()) {
|
|
if(attacker->CastToClient()->GetFilter(FilterDamageShields) != FilterHide)
|
|
attacker->Message(15, "The Spellshield absorbed %d of %d points of damage", origdmg - damage, origdmg);
|
|
}
|
|
if (damage == 0 && attacker && origdmg != damage && IsClient()) {
|
|
//Kayen: Probably need to add a filter for this - Not sure if this msg is correct but there should be a message for spell negate/runes.
|
|
Message(263, "%s tries to cast on you, but YOUR magical skin absorbs the spell.",attacker->GetCleanName());
|
|
}
|
|
|
|
}
|
|
|
|
|
|
if(IsClient() && CastToClient()->sneaking){
|
|
CastToClient()->sneaking = false;
|
|
SendAppearancePacket(AT_Sneak, 0);
|
|
}
|
|
if(attacker && attacker->IsClient() && attacker->CastToClient()->sneaking){
|
|
attacker->CastToClient()->sneaking = false;
|
|
attacker->SendAppearancePacket(AT_Sneak, 0);
|
|
}
|
|
//final damage has been determined.
|
|
|
|
/*
|
|
//check for death conditions
|
|
if(IsClient()) {
|
|
if((GetHP()) <= -10) {
|
|
Death(attacker, damage, spell_id, skill_used);
|
|
return;
|
|
}
|
|
} else {
|
|
if (damage >= GetHP()) {
|
|
//killed...
|
|
SetHP(-100);
|
|
Death(attacker, damage, spell_id, skill_used);
|
|
return;
|
|
}
|
|
}
|
|
*/
|
|
|
|
SetHP(GetHP() - damage);
|
|
|
|
if(HasDied()) {
|
|
bool IsSaved = false;
|
|
|
|
if(TryDivineSave())
|
|
IsSaved = true;
|
|
|
|
if(!IsSaved && !TrySpellOnDeath()) {
|
|
SetHP(-500);
|
|
|
|
if(attacker && attacker->IsClient() && (spell_id != SPELL_UNKNOWN) && damage>0) {
|
|
char val1[20]={0};
|
|
entity_list.MessageClose_StringID(this, false, 100, MT_NonMelee, HIT_NON_MELEE, attacker->GetCleanName(), GetCleanName(),ConvertArray(damage,val1));
|
|
}
|
|
|
|
Death(attacker, damage, spell_id, skill_used);
|
|
return;
|
|
}
|
|
}
|
|
|
|
else{
|
|
if(GetHPRatio() < 16)
|
|
TryDeathSave();
|
|
}
|
|
|
|
//fade mez if we are mezzed
|
|
if (IsMezzed()) {
|
|
mlog(COMBAT__HITS, "Breaking mez due to attack.");
|
|
BuffFadeByEffect(SE_Mez);
|
|
}
|
|
|
|
//check stun chances if bashing
|
|
if (damage > 0 && ((skill_used == BASH || skill_used == KICK) && attacker))
|
|
{
|
|
// NPCs can stun with their bash/kick as soon as they recieve it.
|
|
// Clients can stun mobs under level 56 with their bash/kick when they get level 55 or greater.
|
|
if((attacker->IsNPC()) || (attacker->IsClient() && attacker->GetLevel() >= 55 && GetLevel() < 56))
|
|
{
|
|
if (MakeRandomInt(0,99) < (RuleI(Character, NPCBashKickStunChance)) || attacker->IsClient())
|
|
{
|
|
int stun_resist = itembonuses.StunResist+spellbonuses.StunResist;
|
|
int frontal_stun_resist = itembonuses.FrontalStunResist+spellbonuses.FrontalStunResist;
|
|
|
|
if(IsClient()){
|
|
stun_resist += aabonuses.StunResist;
|
|
frontal_stun_resist += aabonuses.FrontalStunResist;
|
|
}
|
|
|
|
if( (GetBaseRace() == OGRE && IsClient() ||
|
|
(frontal_stun_resist && ((frontal_stun_resist >= 100) || (MakeRandomInt(0,100) <= frontal_stun_resist))))
|
|
&& !attacker->BehindMob(this, attacker->GetX(), attacker->GetY()))
|
|
{
|
|
mlog(COMBAT__HITS, "Stun Resisted. Ogres are immune to frontal melee stuns.");
|
|
}
|
|
else
|
|
{
|
|
if(stun_resist <= 0 || MakeRandomInt(0,99) >= stun_resist)
|
|
{
|
|
mlog(COMBAT__HITS, "Stunned. We had %d percent resist chance.");
|
|
Stun(0);
|
|
}
|
|
else
|
|
{
|
|
if(IsClient())
|
|
Message_StringID(MT_Stun, SHAKE_OFF_STUN);
|
|
|
|
mlog(COMBAT__HITS, "Stun Resisted. We had %dpercent resist chance.");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if(spell_id != SPELL_UNKNOWN && !iBuffTic) {
|
|
//see if root will break
|
|
if (IsRooted() && !FromDamageShield) { // neotoyko: only spells cancel root
|
|
|
|
/*Dev Quote 2010: http://forums.station.sony.com/eq/posts/list.m?topic_id=161443
|
|
The Viscid Roots AA does the following: Reduces the chance for root to break by X percent.
|
|
There is no distinction of any kind between the caster inflicted damage, or anyone
|
|
else's damage. There is also no distinction between Direct and DOT damage in the root code.
|
|
There is however, a provision that if the damage inflicted is greater than 500 per hit, the
|
|
chance to break root is increased. My guess is when this code was put in place, the devs at
|
|
the time couldn't imagine DOT damage getting that high.
|
|
*/
|
|
int BreakChance = RuleI(Spells, RootBreakFromSpells);
|
|
BreakChance -= BreakChance*rooted_mod/100;
|
|
|
|
if (BreakChance < 1)
|
|
BreakChance = 1;
|
|
|
|
if (MakeRandomInt(0, 99) < BreakChance) {
|
|
mlog(COMBAT__HITS, "Spell broke root! BreakChance percent chance");
|
|
BuffFadeByEffect(SE_Root, buffslot); // buff slot is passed through so a root w/ dam doesnt cancel itself
|
|
} else {
|
|
mlog(COMBAT__HITS, "Spell did not break root. BreakChance percent chance");
|
|
}
|
|
}
|
|
}
|
|
else if(spell_id == SPELL_UNKNOWN)
|
|
{
|
|
//increment chances of interrupting
|
|
if(IsCasting()) { //shouldnt interrupt on regular spell damage
|
|
attacked_count++;
|
|
mlog(COMBAT__HITS, "Melee attack while casting. Attack count %d", attacked_count);
|
|
}
|
|
}
|
|
|
|
//send an HP update if we are hurt
|
|
if(GetHP() < GetMaxHP())
|
|
SendHPUpdate();
|
|
} //end `if damage was done`
|
|
|
|
//send damage packet...
|
|
if(!iBuffTic) { //buff ticks do not send damage, instead they just call SendHPUpdate(), which is done below
|
|
EQApplicationPacket* outapp = new EQApplicationPacket(OP_Damage, sizeof(CombatDamage_Struct));
|
|
CombatDamage_Struct* a = (CombatDamage_Struct*)outapp->pBuffer;
|
|
a->target = GetID();
|
|
if (attacker == NULL)
|
|
a->source = 0;
|
|
else if (attacker->IsClient() && attacker->CastToClient()->GMHideMe())
|
|
a->source = 0;
|
|
else
|
|
a->source = attacker->GetID();
|
|
a->type = SkillDamageTypes[skill_used]; // was 0x1c
|
|
a->damage = damage;
|
|
// if (attack_skill != 231)
|
|
// a->spellid = SPELL_UNKNOWN;
|
|
// else
|
|
a->spellid = spell_id;
|
|
|
|
//Note: if players can become pets, they will not receive damage messages of their own
|
|
//this was done to simplify the code here (since we can only effectively skip one mob on queue)
|
|
eqFilterType filter;
|
|
Mob *skip = attacker;
|
|
if(attacker && attacker->GetOwnerID()) {
|
|
//attacker is a pet, let pet owners see their pet's damage
|
|
Mob* owner = attacker->GetOwner();
|
|
if (owner && owner->IsClient()) {
|
|
if (((spell_id != SPELL_UNKNOWN) || (FromDamageShield)) && damage>0) {
|
|
//special crap for spell damage, looks hackish to me
|
|
char val1[20]={0};
|
|
owner->Message_StringID(MT_NonMelee,OTHER_HIT_NONMELEE,GetCleanName(),ConvertArray(damage,val1));
|
|
} else {
|
|
if(damage > 0) {
|
|
if(spell_id != SPELL_UNKNOWN)
|
|
filter = iBuffTic ? FilterDOT : FilterSpellDamage;
|
|
else
|
|
filter = FilterPetHits;
|
|
} else if(damage == -5)
|
|
filter = FilterNone; //cant filter invulnerable
|
|
else
|
|
filter = FilterPetMisses;
|
|
|
|
if(!FromDamageShield)
|
|
owner->CastToClient()->QueuePacket(outapp,true,CLIENT_CONNECTED,filter);
|
|
}
|
|
}
|
|
skip = owner;
|
|
} else {
|
|
//attacker is not a pet, send to the attacker
|
|
|
|
//if the attacker is a client, try them with the correct filter
|
|
if(attacker && attacker->IsClient()) {
|
|
if (((spell_id != SPELL_UNKNOWN)||(FromDamageShield)) && damage>0) {
|
|
//special crap for spell damage, looks hackish to me
|
|
char val1[20]={0};
|
|
if (FromDamageShield)
|
|
{
|
|
if(!attacker->CastToClient()->GetFilter(FilterDamageShields) == FilterHide)
|
|
{
|
|
attacker->Message_StringID(MT_DS,OTHER_HIT_NONMELEE,GetCleanName(),ConvertArray(damage,val1));
|
|
}
|
|
}
|
|
else
|
|
entity_list.MessageClose_StringID(this, true, 100, MT_NonMelee,HIT_NON_MELEE,attacker->GetCleanName(),GetCleanName(),ConvertArray(damage,val1));
|
|
} else {
|
|
if(damage > 0) {
|
|
if(spell_id != SPELL_UNKNOWN)
|
|
filter = iBuffTic ? FilterDOT : FilterSpellDamage;
|
|
else
|
|
filter = FilterNone; //cant filter our own hits
|
|
} else if(damage == -5)
|
|
filter = FilterNone; //cant filter invulnerable
|
|
else
|
|
filter = FilterMyMisses;
|
|
|
|
attacker->CastToClient()->QueuePacket(outapp, true, CLIENT_CONNECTED, filter);
|
|
}
|
|
}
|
|
skip = attacker;
|
|
}
|
|
|
|
//send damage to all clients around except the specified skip mob (attacker or the attacker's owner) and ourself
|
|
if(damage > 0) {
|
|
if(spell_id != SPELL_UNKNOWN)
|
|
filter = iBuffTic ? FilterDOT : FilterSpellDamage;
|
|
else
|
|
filter = FilterOthersHit;
|
|
} else if(damage == -5)
|
|
filter = FilterNone; //cant filter invulnerable
|
|
else
|
|
filter = FilterOthersMiss;
|
|
//make attacker (the attacker) send the packet so we can skip them and the owner
|
|
//this call will send the packet to `this` as well (using the wrong filter) (will not happen until PC charm works)
|
|
//LogFile->write(EQEMuLog::Debug, "Queue damage to all except %s with filter %d (%d), type %d", skip->GetName(), filter, IsClient()?CastToClient()->GetFilter(filter):-1, a->type);
|
|
//
|
|
// If this is Damage Shield damage, the correct OP_Damage packets will be sent from Mob::DamageShield, so
|
|
// we don't send them here.
|
|
if(!FromDamageShield) {
|
|
entity_list.QueueCloseClients(this, outapp, true, 200, skip, true, filter);
|
|
//send the damage to ourself if we are a client
|
|
if(IsClient()) {
|
|
//I dont think any filters apply to damage affecting us
|
|
CastToClient()->QueuePacket(outapp);
|
|
}
|
|
}
|
|
|
|
safe_delete(outapp);
|
|
} else {
|
|
//else, it is a buff tic...
|
|
// Everhood - So we can see our dot dmg like live shows it.
|
|
if(spell_id != SPELL_UNKNOWN && damage > 0 && attacker && attacker != this && attacker->IsClient()) {
|
|
//might filter on (attack_skill>200 && attack_skill<250), but I dont think we need it
|
|
if(attacker->CastToClient()->GetFilter(FilterDOT) != FilterHide) {
|
|
attacker->Message_StringID(MT_DoTDamage, OTHER_HIT_DOT, GetCleanName(),itoa(damage),spells[spell_id].name);
|
|
}
|
|
}
|
|
} //end packet sending
|
|
}
|
|
|
|
|
|
void Mob::HealDamage(uint32 amount, Mob* caster) {
|
|
uint32 maxhp = GetMaxHP();
|
|
uint32 curhp = GetHP();
|
|
uint32 acthealed = 0;
|
|
|
|
if(caster && amount > 0)
|
|
{
|
|
if(caster->IsNPC() && !caster->IsPet())
|
|
{
|
|
float npchealscale = caster->CastToNPC()->GetHealScale();
|
|
amount = ((float)amount * npchealscale) / (float)100;
|
|
}
|
|
}
|
|
|
|
|
|
if(amount > (maxhp - curhp))
|
|
acthealed = (maxhp - curhp);
|
|
else
|
|
acthealed = amount;
|
|
|
|
char *TempString = NULL;
|
|
|
|
MakeAnyLenString(&TempString, "%d", acthealed);
|
|
|
|
if(acthealed > 100){
|
|
if(caster){
|
|
Message_StringID(MT_NonMelee, YOU_HEALED, caster->GetCleanName(), TempString);
|
|
if(caster != this){
|
|
caster->Message_StringID(MT_NonMelee, YOU_HEAL, GetCleanName(), TempString);
|
|
}
|
|
}
|
|
else{
|
|
Message(MT_NonMelee, "You have been healed for %d points of damage.", acthealed);
|
|
}
|
|
}
|
|
|
|
if (curhp < maxhp) {
|
|
if ((curhp+amount)>maxhp)
|
|
curhp=maxhp;
|
|
else
|
|
curhp+=amount;
|
|
SetHP(curhp);
|
|
|
|
SendHPUpdate();
|
|
}
|
|
safe_delete_array(TempString);
|
|
}
|
|
|
|
|
|
|
|
//proc chance includes proc bonus
|
|
float Mob::GetProcChances(float &ProcBonus, float &ProcChance, uint16 weapon_speed, uint16 hand) {
|
|
int mydex = GetDEX();
|
|
float AABonus = 0;
|
|
ProcBonus = 0;
|
|
ProcChance = 0;
|
|
|
|
if (aabonuses.ProcChance)
|
|
AABonus = float(aabonuses.ProcChance) / 100.0f;
|
|
|
|
switch(hand){
|
|
case 13:
|
|
weapon_speed = attack_timer.GetDuration();
|
|
break;
|
|
case 14:
|
|
weapon_speed = attack_dw_timer.GetDuration();
|
|
break;
|
|
case 11:
|
|
weapon_speed = ranged_timer.GetDuration();
|
|
break;
|
|
}
|
|
|
|
|
|
//calculate the weapon speed in ms, so we can use the rule to compare against.
|
|
|
|
if(weapon_speed < RuleI(Combat, MinHastedDelay)) // fast as a client can swing, so should be the floor of the proc chance
|
|
weapon_speed = RuleI(Combat, MinHastedDelay);
|
|
|
|
ProcBonus += (float(itembonuses.ProcChance + spellbonuses.ProcChance) / 1000.0f + AABonus);
|
|
|
|
if(RuleB(Combat, AdjustProcPerMinute) == true)
|
|
{
|
|
ProcChance = ((float)weapon_speed * RuleR(Combat, AvgProcsPerMinute) / 60000.0f); // compensate for weapon_speed being in ms
|
|
ProcBonus += float(mydex) * RuleR(Combat, ProcPerMinDexContrib) / 100.0f;
|
|
ProcChance = ProcChance + (ProcChance * ProcBonus);
|
|
}
|
|
else
|
|
{
|
|
ProcChance = RuleR(Combat, BaseProcChance) + float(mydex) / RuleR(Combat, ProcDexDivideBy);
|
|
ProcChance = ProcChance + (ProcChance * ProcBonus);
|
|
}
|
|
|
|
mlog(COMBAT__PROCS, "Proc chance %.2f (%.2f from bonuses)", ProcChance, ProcBonus);
|
|
return ProcChance;
|
|
}
|
|
|
|
float Mob::GetDefensiveProcChances(float &ProcBonus, float &ProcChance, uint16 weapon_speed, uint16 hand) {
|
|
int myagi = GetAGI();
|
|
ProcBonus = 0;
|
|
ProcChance = 0;
|
|
|
|
switch(hand){
|
|
case 13:
|
|
weapon_speed = attack_timer.GetDuration();
|
|
break;
|
|
case 14:
|
|
weapon_speed = attack_dw_timer.GetDuration();
|
|
break;
|
|
case 11:
|
|
return 0;
|
|
break;
|
|
}
|
|
|
|
/*
|
|
float PermaHaste;
|
|
if(GetHaste() > 0)
|
|
PermaHaste = 1 / (1 + (float)GetHaste()/100);
|
|
else if(GetHaste() < 0)
|
|
PermaHaste = 1 * (1 - (float)GetHaste()/100);
|
|
else
|
|
PermaHaste = 1.0f;
|
|
*/
|
|
|
|
//calculate the weapon speed in ms, so we can use the rule to compare against.
|
|
//weapon_speed = ((int)(weapon_speed*(100.0f+attack_speed)*PermaHaste));
|
|
if(weapon_speed < RuleI(Combat, MinHastedDelay)) // fast as a client can swing, so should be the floor of the proc chance
|
|
weapon_speed = RuleI(Combat, MinHastedDelay);
|
|
|
|
ProcChance = ((float)weapon_speed * RuleR(Combat, AvgDefProcsPerMinute) / 60000.0f); // compensate for weapon_speed being in ms
|
|
ProcBonus += float(myagi) * RuleR(Combat, DefProcPerMinAgiContrib) / 100.0f;
|
|
ProcChance = ProcChance + (ProcChance * ProcBonus);
|
|
|
|
mlog(COMBAT__PROCS, "Defensive Proc chance %.2f (%.2f from bonuses)", ProcChance, ProcBonus);
|
|
return ProcChance;
|
|
}
|
|
|
|
void Mob::TryDefensiveProc(const ItemInst* weapon, Mob *on, uint16 hand, int damage) {
|
|
|
|
if (!on) {
|
|
SetTarget(NULL);
|
|
LogFile->write(EQEMuLog::Error, "A null Mob object was passed to Mob::TryDefensiveProc for evaluation!");
|
|
return;
|
|
}
|
|
|
|
bool bSkillProc = HasSkillProcs();
|
|
bool bDefensiveProc = HasDefensiveProcs();
|
|
|
|
if (!bDefensiveProc && !bSkillProc)
|
|
return;
|
|
|
|
if (!bDefensiveProc && (bSkillProc && damage >= 0))
|
|
return;
|
|
|
|
float ProcChance, ProcBonus;
|
|
if(weapon!=NULL)
|
|
on->GetDefensiveProcChances(ProcBonus, ProcChance, weapon->GetItem()->Delay, hand);
|
|
else
|
|
on->GetDefensiveProcChances(ProcBonus, ProcChance);
|
|
if(hand != 13)
|
|
ProcChance /= 2;
|
|
|
|
if (bDefensiveProc){
|
|
for (int i = 0; i < MAX_PROCS; i++) {
|
|
if (DefensiveProcs[i].spellID != SPELL_UNKNOWN) {
|
|
int chance = ProcChance * (DefensiveProcs[i].chance);
|
|
if ((MakeRandomInt(0, 100) < chance)) {
|
|
ExecWeaponProc(DefensiveProcs[i].spellID, on);
|
|
CheckHitsRemaining(0, false, false, 0, DefensiveProcs[i].base_spellID);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (bSkillProc && damage < 0){
|
|
|
|
if (damage == -1)
|
|
TrySkillProc(on, BLOCKSKILL, ProcChance);
|
|
|
|
if (damage == -2)
|
|
TrySkillProc(on, PARRY, ProcChance);
|
|
|
|
if (damage == -3)
|
|
TrySkillProc(on, RIPOSTE, ProcChance);
|
|
|
|
if (damage == -4)
|
|
TrySkillProc(on, DODGE, ProcChance);
|
|
}
|
|
}
|
|
|
|
void Mob::TryWeaponProc(const ItemInst* weapon_g, Mob *on, uint16 hand) {
|
|
_ZP(Mob_TryWeaponProcA);
|
|
if(!on) {
|
|
SetTarget(NULL);
|
|
LogFile->write(EQEMuLog::Error, "A null Mob object was passed to Mob::TryWeaponProc for evaluation!");
|
|
return;
|
|
}
|
|
|
|
if(!weapon_g) {
|
|
TryWeaponProc((const Item_Struct*) NULL, on, hand);
|
|
return;
|
|
}
|
|
|
|
if(!weapon_g->IsType(ItemClassCommon)) {
|
|
TryWeaponProc((const Item_Struct*) NULL, on, hand);
|
|
return;
|
|
}
|
|
|
|
//do main procs
|
|
TryWeaponProc(weapon_g->GetItem(), on, hand);
|
|
|
|
|
|
//we have to calculate these again, oh well
|
|
int ourlevel = GetLevel();
|
|
float ProcChance, ProcBonus;
|
|
GetProcChances(ProcBonus, ProcChance, weapon_g->GetItem()->Delay, hand);
|
|
if(hand != 13)
|
|
{
|
|
ProcChance /= 2;
|
|
}
|
|
|
|
//do augment procs
|
|
int r;
|
|
for(r = 0; r < MAX_AUGMENT_SLOTS; r++) {
|
|
const ItemInst* aug_i = weapon_g->GetAugment(r);
|
|
if(!aug_i)
|
|
continue;
|
|
const Item_Struct* aug = aug_i->GetItem();
|
|
if(!aug)
|
|
continue;
|
|
|
|
if (aug->Proc.Type == ET_CombatProc) {
|
|
ProcChance = ProcChance*(100+aug->ProcRate)/100;
|
|
if (MakeRandomFloat(0, 1) < ProcChance) {
|
|
if(aug->Proc.Level > ourlevel) {
|
|
Mob * own = GetOwner();
|
|
if(own != NULL) {
|
|
own->Message_StringID(13,PROC_PETTOOLOW);
|
|
} else {
|
|
Message_StringID(13,PROC_TOOLOW);
|
|
}
|
|
} else {
|
|
ExecWeaponProc(aug->Proc.Effect, on);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Mob::TryWeaponProc(const Item_Struct* weapon, Mob *on, uint16 hand) {
|
|
_ZP(Mob_TryWeaponProcB);
|
|
uint16 skillinuse = 28;
|
|
int ourlevel = GetLevel();
|
|
float ProcChance, ProcBonus;
|
|
if(weapon!=NULL)
|
|
GetProcChances(ProcBonus, ProcChance, weapon->Delay, hand);
|
|
else
|
|
GetProcChances(ProcBonus, ProcChance);
|
|
|
|
if(hand != 13) //Is Archery intended to proc at 50% rate?
|
|
ProcChance /= 2;
|
|
|
|
//give weapon a chance to proc first.
|
|
if(weapon != NULL) {
|
|
skillinuse = GetSkillByItemType(weapon->ItemType);
|
|
if (weapon->Proc.Type == ET_CombatProc) {
|
|
float WPC = ProcChance*(100.0f+(float)weapon->ProcRate)/100.0f;
|
|
if (MakeRandomFloat(0, 1) <= WPC) { // 255 dex = 0.084 chance of proc. No idea what this number should be really.
|
|
if(weapon->Proc.Level > ourlevel) {
|
|
mlog(COMBAT__PROCS, "Tried to proc (%s), but our level (%d) is lower than required (%d)", weapon->Name, ourlevel, weapon->Proc.Level);
|
|
Mob * own = GetOwner();
|
|
if(own != NULL) {
|
|
own->Message_StringID(13,PROC_PETTOOLOW);
|
|
} else {
|
|
Message_StringID(13,PROC_TOOLOW);
|
|
}
|
|
} else {
|
|
mlog(COMBAT__PROCS, "Attacking weapon (%s) successfully procing spell %d (%.2f percent chance)", weapon->Name, weapon->Proc.Effect, ProcChance*100);
|
|
ExecWeaponProc(weapon->Proc.Effect, on);
|
|
}
|
|
} else {
|
|
mlog(COMBAT__PROCS, "Attacking weapon (%s) did no proc (%.2f percent chance).", weapon->Name, ProcChance*100);
|
|
}
|
|
}
|
|
}
|
|
|
|
if(ProcBonus == -1) {
|
|
LogFile->write(EQEMuLog::Error, "ProcBonus was -1 value!");
|
|
return;
|
|
}
|
|
|
|
bool bRangedAttack = false;
|
|
if (weapon != NULL) {
|
|
if (weapon->ItemType == ItemTypeBow || weapon->ItemType == ItemTypeThrowing || weapon->ItemType == ItemTypeThrowingv2) {
|
|
bRangedAttack = true;
|
|
}
|
|
}
|
|
|
|
bool isRanged = false;
|
|
if(weapon)
|
|
{
|
|
if(weapon->ItemType == ItemTypeArrow ||
|
|
weapon->ItemType == ItemTypeThrowing ||
|
|
weapon->ItemType == ItemTypeThrowingv2 ||
|
|
weapon->ItemType == ItemTypeBow)
|
|
{
|
|
isRanged = true;
|
|
}
|
|
}
|
|
|
|
uint32 i;
|
|
for(i = 0; i < MAX_PROCS; i++) {
|
|
if (PermaProcs[i].spellID != SPELL_UNKNOWN) {
|
|
if(MakeRandomInt(0, 100) < PermaProcs[i].chance) {
|
|
mlog(COMBAT__PROCS, "Permanent proc %d procing spell %d (%d percent chance)", i, PermaProcs[i].spellID, PermaProcs[i].chance);
|
|
ExecWeaponProc(PermaProcs[i].spellID, on);
|
|
} else {
|
|
mlog(COMBAT__PROCS, "Permanent proc %d failed to proc %d (%d percent chance)", i, PermaProcs[i].spellID, PermaProcs[i].chance);
|
|
}
|
|
}
|
|
|
|
if(!isRanged)
|
|
{
|
|
if(IsPet() && hand != 13) //Pets can only proc spell procs from their primay hand (ie; beastlord pets)
|
|
{
|
|
//Maybe implement this later if pets are ever given dual procs?
|
|
}
|
|
else
|
|
{
|
|
int chance = ProcChance * (SpellProcs[i].chance);
|
|
if(MakeRandomInt(0, 100) < chance) {
|
|
mlog(COMBAT__PROCS, "Spell proc %d procing spell %d (%d percent chance)", i, SpellProcs[i].spellID, chance);
|
|
ExecWeaponProc(SpellProcs[i].spellID, on);
|
|
} else {
|
|
mlog(COMBAT__PROCS, "Spell proc %d failed to proc %d (%d percent chance)", i, SpellProcs[i].spellID, chance);
|
|
}
|
|
}
|
|
}
|
|
if (bRangedAttack) {
|
|
int chance = ProcChance * RangedProcs[i].chance;
|
|
if(MakeRandomInt(0, 100) < chance) {
|
|
mlog(COMBAT__PROCS, "Ranged proc %d procing spell %d", i, RangedProcs[i].spellID, RangedProcs[i].chance);
|
|
ExecWeaponProc(RangedProcs[i].spellID, on);
|
|
CheckHitsRemaining(0, false, false, 0, RangedProcs[i].base_spellID);
|
|
} else {
|
|
mlog(COMBAT__PROCS, "Ranged proc %d failed to proc %d", i, RangedProcs[i].spellID, RangedProcs[i].chance);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (HasSkillProcs())
|
|
TrySkillProc(on, skillinuse, ProcChance);
|
|
}
|
|
|
|
void Mob::TryPetCriticalHit(Mob *defender, uint16 skill, int32 &damage)
|
|
{
|
|
if(damage < 1)
|
|
return;
|
|
|
|
//Allows pets to perform critical hits.
|
|
//Each rank adds an additional 1% chance for any melee hit (primary, secondary, kick, bash, etc) to critical,
|
|
//dealing up to 63% more damage. http://www.magecompendium.com/aa-short-library.html
|
|
|
|
Mob *owner = NULL;
|
|
float critChance = 0.0f;
|
|
critChance += RuleI(Combat, MeleeBaseCritChance);
|
|
uint16 critMod = 163;
|
|
|
|
if (damage < 1) //We can't critical hit if we don't hit.
|
|
return;
|
|
|
|
if (!IsPet())
|
|
return;
|
|
|
|
owner = GetOwner();
|
|
|
|
if (!owner)
|
|
return;
|
|
|
|
int16 CritPetChance = owner->aabonuses.PetCriticalHit + owner->itembonuses.PetCriticalHit + owner->spellbonuses.PetCriticalHit;
|
|
int16 CritChanceBonus = GetCriticalChanceBonus(skill);
|
|
|
|
if (CritPetChance || critChance) {
|
|
|
|
//For pets use PetCriticalHit for base chance, pets do not innately critical with without it
|
|
//even if buffed with a CritChanceBonus effects.
|
|
critChance += CritPetChance;
|
|
critChance += critChance*CritChanceBonus/100.0f;
|
|
}
|
|
|
|
if(critChance > 0){
|
|
|
|
critChance /= 100;
|
|
|
|
if(MakeRandomFloat(0, 1) < critChance)
|
|
{
|
|
critMod += GetCritDmgMob(skill) * 2; // To account for base crit mod being 200 not 100
|
|
damage = (damage * critMod) / 100;
|
|
entity_list.MessageClose_StringID(this, false, 200, MT_CritMelee, CRITICAL_HIT, GetCleanName(), itoa(damage));
|
|
}
|
|
}
|
|
}
|
|
|
|
void Mob::TryCriticalHit(Mob *defender, uint16 skill, int32 &damage)
|
|
{
|
|
if(damage < 1)
|
|
return;
|
|
|
|
// decided to branch this into it's own function since it's going to be duplicating a lot of the
|
|
// code in here, but could lead to some confusion otherwise
|
|
if (IsPet() && GetOwner()->IsClient()) {
|
|
TryPetCriticalHit(defender,skill,damage);
|
|
return;
|
|
}
|
|
|
|
#ifdef BOTS
|
|
if (this->IsPet() && this->GetOwner()->IsBot()) {
|
|
this->TryPetCriticalHit(defender,skill,damage);
|
|
return;
|
|
}
|
|
#endif //BOTS
|
|
|
|
|
|
float critChance = 0.0f;
|
|
|
|
//1: Try Slay Undead
|
|
if(defender && defender->GetBodyType() == BT_Undead || defender->GetBodyType() == BT_SummonedUndead || defender->GetBodyType() == BT_Vampire){
|
|
|
|
int16 SlayRateBonus = aabonuses.SlayUndead[0] + itembonuses.SlayUndead[0] + spellbonuses.SlayUndead[0];
|
|
|
|
if (SlayRateBonus) {
|
|
|
|
critChance += (float(SlayRateBonus)/100.0f);
|
|
critChance /= 100.0f;
|
|
|
|
if(MakeRandomFloat(0, 1) < critChance){
|
|
int16 SlayDmgBonus = aabonuses.SlayUndead[1] + itembonuses.SlayUndead[1] + spellbonuses.SlayUndead[1];
|
|
damage = (damage*SlayDmgBonus*2.25)/100;
|
|
entity_list.MessageClose(this, false, 200, MT_CritMelee, "%s cleanses %s target!(%d)", GetCleanName(), this->GetGender() == 0 ? "his" : this->GetGender() == 1 ? "her" : "its", damage);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
//2: Try Melee Critical
|
|
|
|
//Base critical rate for all classes is dervived from DEX stat, this rate is then augmented
|
|
//by item,spell and AA bonuses allowing you a chance to critical hit. If the following rules
|
|
//are defined you will have an innate chance to hit at Level 1 regardless of bonuses.
|
|
//Warning: Do not define these rules if you want live like critical hits.
|
|
critChance += RuleI(Combat, MeleeBaseCritChance);
|
|
|
|
if(IsClient())
|
|
critChance += RuleI(Combat, ClientBaseCritChance);
|
|
|
|
bool IsBerserk = false;
|
|
if(((GetClass() == WARRIOR || GetClass() == BERSERKER) && GetLevel() >= 12 && IsClient()))
|
|
{
|
|
if(CastToClient()->berserk){
|
|
critChance += RuleI(Combat, BerserkBaseCritChance);
|
|
IsBerserk = true;
|
|
}
|
|
|
|
else
|
|
critChance += RuleI(Combat, WarBerBaseCritChance);
|
|
}
|
|
|
|
if(skill == ARCHERY && GetClass() == RANGER && GetSkill(ARCHERY) >= 65)
|
|
critChance += 6;
|
|
|
|
if(skill == THROWING && GetClass() == ROGUE && GetSkill(THROWING) >= 65)
|
|
critChance += 6;
|
|
|
|
int CritChanceBonus = GetCriticalChanceBonus(skill);
|
|
|
|
if (CritChanceBonus || critChance) {
|
|
|
|
//Get Base CritChance from Dex. (200 = ~1.6%, 255 = ~2.0%, 355 = ~2.20%) Fall off rate > 255
|
|
//http://giline.versus.jp/shiden/su.htm , http://giline.versus.jp/shiden/damage_e.htm
|
|
if (GetDEX() <= 255)
|
|
critChance += (float(GetDEX()) / 125.0f);
|
|
else if (GetDEX() > 255)
|
|
critChance += (float(GetDEX()-255)/ 500.0f) + 2.0f;
|
|
critChance += critChance*(float)CritChanceBonus /100.0f;
|
|
}
|
|
|
|
if(critChance > 0){
|
|
|
|
critChance /= 100;
|
|
|
|
if(MakeRandomFloat(0, 1) < critChance)
|
|
{
|
|
uint16 critMod = 200;
|
|
bool crip_success = false;
|
|
int16 CripplingBlowChance = GetCrippBlowChance();
|
|
|
|
//Crippling Blow Chance: The percent value of the effect is applied
|
|
//to the your Chance to Critical. (ie You have 10% chance to critical and you
|
|
//have a 200% Chance to Critical Blow effect, therefore you have a 20% Chance to Critical Blow.
|
|
if (CripplingBlowChance){
|
|
critChance *= float(CripplingBlowChance)/100.0f;
|
|
|
|
if(MakeRandomFloat(0, 1) < critChance){
|
|
critMod = 400;
|
|
crip_success = true;
|
|
}
|
|
}
|
|
|
|
critMod += GetCritDmgMob(skill) * 2; // To account for base crit mod being 200 not 100
|
|
damage = damage * critMod / 100;
|
|
|
|
if(IsBerserk || crip_success)
|
|
{
|
|
entity_list.MessageClose_StringID(this, false, 200, MT_CritMelee, CRIPPLING_BLOW, GetCleanName(), itoa(damage));
|
|
// Crippling blows also have a chance to stun
|
|
//Kayen: Crippling Blow would cause a chance to interrupt for npcs < 55, with a staggers message.
|
|
if (defender->GetLevel() <= 55 && !defender->SpecAttacks[IMMUNE_STUN]){
|
|
defender->Emote("staggers.");
|
|
defender->Stun(0);
|
|
}
|
|
}
|
|
|
|
else
|
|
{
|
|
entity_list.MessageClose_StringID(this, false, 200, MT_CritMelee, CRITICAL_HIT, GetCleanName(), itoa(damage));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
bool Mob::TryFinishingBlow(Mob *defender, SkillType skillinuse)
|
|
{
|
|
|
|
if (!defender)
|
|
return false;
|
|
|
|
if (aabonuses.FinishingBlow[1] && !defender->IsClient() && defender->GetHPRatio() < 10){
|
|
|
|
uint32 chance = aabonuses.FinishingBlow[0]/10; //500 = 5% chance.
|
|
uint32 damage = aabonuses.FinishingBlow[1];
|
|
uint16 levelreq = aabonuses.FinishingBlowLvl[0];
|
|
|
|
if(defender->GetLevel() <= levelreq && (chance >= MakeRandomInt(0, 1000))){
|
|
mlog(COMBAT__ATTACKS, "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);
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
mlog(COMBAT__ATTACKS, "FAILED a finishing blow: levelreq at %d, other level %d", levelreq , defender->GetLevel());
|
|
return false;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void Mob::DoRiposte(Mob* defender) {
|
|
mlog(COMBAT__ATTACKS, "Preforming a riposte");
|
|
|
|
if (!defender)
|
|
return;
|
|
|
|
defender->Attack(this, SLOT_PRIMARY, true);
|
|
if (HasDied()) return;
|
|
|
|
int16 DoubleRipChance = defender->aabonuses.GiveDoubleRiposte[0] +
|
|
defender->spellbonuses.GiveDoubleRiposte[0] +
|
|
defender->itembonuses.GiveDoubleRiposte[0];
|
|
|
|
//Live AA - Double Riposte
|
|
if(DoubleRipChance && (DoubleRipChance >= MakeRandomInt(0, 100))) {
|
|
mlog(COMBAT__ATTACKS, "Preforming a double riposed (%d percent chance)", DoubleRipChance);
|
|
defender->Attack(this, SLOT_PRIMARY, true);
|
|
if (HasDied()) return;
|
|
}
|
|
|
|
//Double Riposte effect, allows for a chance to do RIPOSTE with a skill specfic special attack (ie Return Kick).
|
|
//Coded narrowly: Limit to one per client. Limit AA only. [1 = Skill Attack Chance, 2 = Skill]
|
|
DoubleRipChance = defender->aabonuses.GiveDoubleRiposte[1];
|
|
|
|
if(DoubleRipChance && (DoubleRipChance >= MakeRandomInt(0, 100))) {
|
|
mlog(COMBAT__ATTACKS, "Preforming a return SPECIAL ATTACK (%d percent chance)", DoubleRipChance);
|
|
|
|
if (defender->GetClass() == MONK)
|
|
defender->MonkSpecialAttack(this, defender->aabonuses.GiveDoubleRiposte[2]);
|
|
else if (defender->IsClient())
|
|
defender->CastToClient()->DoClassAttacks(this,defender->aabonuses.GiveDoubleRiposte[2], true);
|
|
}
|
|
}
|
|
|
|
void Mob::ApplyMeleeDamageBonus(uint16 skill, int32 &damage){
|
|
|
|
if(!RuleB(Combat, UseIntervalAC)){
|
|
if(IsNPC()){ //across the board NPC damage bonuses.
|
|
//only account for STR here, assume their base STR was factored into their DB damages
|
|
int dmgbonusmod = 0;
|
|
dmgbonusmod += (100*(itembonuses.STR + spellbonuses.STR))/3;
|
|
dmgbonusmod += (100*(spellbonuses.ATK + itembonuses.ATK))/5;
|
|
mlog(COMBAT__DAMAGE, "Damage bonus: %d percent from ATK and STR bonuses.", (dmgbonusmod/100));
|
|
damage += (damage*dmgbonusmod/10000);
|
|
}
|
|
}
|
|
|
|
damage += damage * GetMeleeDamageMod_SE(skill) / 100;
|
|
|
|
//Rogue sneak attack disciplines make use of this, they are active for one hit
|
|
if (spellbonuses.HitChanceEffect[HIGHEST_SKILL+1] || spellbonuses.HitChanceEffect[skill])
|
|
CheckHitsRemaining(0, false, false, SE_HitChance,0,true,skill);
|
|
}
|
|
|
|
bool Mob::HasDied() {
|
|
bool Result = false;
|
|
int16 hp_below = 0;
|
|
|
|
hp_below = (GetDelayDeath() * -1);
|
|
|
|
if((GetHP()) <= (hp_below))
|
|
Result = true;
|
|
|
|
return Result;
|
|
}
|
|
|
|
uint16 Mob::GetDamageTable(SkillType skillinuse)
|
|
{
|
|
if(GetLevel() <= 51)
|
|
{
|
|
uint16 ret_table = 0;
|
|
int str_over_75 = 0;
|
|
if(GetSTR() > 75)
|
|
str_over_75 = GetSTR() - 75;
|
|
if(str_over_75 > 255)
|
|
ret_table = (GetSkill(skillinuse)+255)/2;
|
|
else
|
|
ret_table = (GetSkill(skillinuse)+str_over_75)/2;
|
|
|
|
if(ret_table < 100)
|
|
return 100;
|
|
|
|
return ret_table;
|
|
}
|
|
else if(GetLevel() >= 90)
|
|
{
|
|
if(GetClass() == MONK)
|
|
return 379;
|
|
else
|
|
return 345;
|
|
}
|
|
else
|
|
{
|
|
uint16 dmg_table[] = {
|
|
275, 275, 275, 275, 275,
|
|
280, 280, 280, 280, 285,
|
|
285, 285, 290, 290, 295,
|
|
295, 300, 300, 300, 305,
|
|
305, 305, 310, 310, 315,
|
|
315, 320, 320, 320, 325,
|
|
325, 325, 330, 330, 335,
|
|
335, 340, 340, 340,
|
|
};
|
|
if(GetClass() == MONK)
|
|
return (dmg_table[GetLevel()-51]*(100+RuleI(Combat,MonkDamageTableBonus))/100);
|
|
else
|
|
return dmg_table[GetLevel()-51];
|
|
}
|
|
}
|
|
|
|
void Mob::TrySkillProc(Mob *on, uint16 skill, float chance)
|
|
{
|
|
|
|
if (!on) {
|
|
SetTarget(NULL);
|
|
LogFile->write(EQEMuLog::Error, "A null Mob object was passed to Mob::TrySkillProc for evaluation!");
|
|
return;
|
|
}
|
|
|
|
for (int i = 0; i < MAX_PROCS; i++) {
|
|
if (SkillProcs[i].spellID != SPELL_UNKNOWN){
|
|
if (PassLimitToSkill(SkillProcs[i].base_spellID,skill)){
|
|
int ProcChance = chance * (float)SkillProcs[i].chance;
|
|
if ((MakeRandomInt(0, 100) < ProcChance)) {
|
|
ExecWeaponProc(SkillProcs[i].spellID, on);
|
|
CheckHitsRemaining(0, false, false, 0, SkillProcs[i].base_spellID);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
int32 Mob::RuneAbsorb(int32 damage, uint16 type)
|
|
{
|
|
uint32 buff_max = GetMaxTotalSlots();
|
|
if (type == SE_Rune){
|
|
for(uint32 slot = 0; slot < buff_max; slot++) {
|
|
if((buffs[slot].spellid != SPELL_UNKNOWN) && (buffs[slot].melee_rune) && IsEffectInSpell(buffs[slot].spellid, type)){
|
|
uint32 melee_rune_left = buffs[slot].melee_rune;
|
|
if(melee_rune_left >= damage)
|
|
{
|
|
melee_rune_left -= damage;
|
|
buffs[slot].melee_rune = melee_rune_left;
|
|
return -6;
|
|
}
|
|
|
|
else
|
|
{
|
|
if(melee_rune_left > 0)
|
|
damage -= melee_rune_left;
|
|
if(!TryFadeEffect(slot))
|
|
BuffFadeBySlot(slot);
|
|
UpdateRuneFlags();
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
return damage;
|
|
}
|
|
|
|
|
|
else{
|
|
for(uint32 slot = 0; slot < buff_max; slot++) {
|
|
if((buffs[slot].spellid != SPELL_UNKNOWN) && (buffs[slot].magic_rune) && IsEffectInSpell(buffs[slot].spellid, type)){
|
|
uint32 magic_rune_left = buffs[slot].magic_rune;
|
|
if(magic_rune_left >= damage)
|
|
{
|
|
magic_rune_left -= damage;
|
|
buffs[slot].magic_rune = magic_rune_left;
|
|
return 0;
|
|
}
|
|
|
|
else
|
|
{
|
|
if(magic_rune_left > 0)
|
|
damage -= magic_rune_left;
|
|
if(!TryFadeEffect(slot))
|
|
BuffFadeBySlot(slot);
|
|
UpdateRuneFlags();
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
return damage;
|
|
}
|
|
} |