#include "../common/debug.h" #include "../common/timer.h" #include #include #include "spawn2.h" #include "entity.h" #include "masterentity.h" #include "zone.h" #include "spawngroup.h" #include "zonedb.h" #include "npc.h" #include "mob.h" #include "client.h" #include "worldserver.h" #include "QuestParserCollection.h" #include #include extern EntityList entity_list; extern Zone* zone; extern WorldServer worldserver; using namespace std; #define DW_STATBASE 70 void Zone::mod_init() { return; } void Zone::mod_repop() { return; } void NPC::mod_prespawn(Spawn2 *sp) { //The spawn has to have 1 kill to qualify if(sp->GetKillCount() < 1) { return; } //Ignore existing bosses if(lastname[0] == '[') { return; } //5% chance to elevate if(MakeRandomInt(0, 100) > 5) { return; } //Let everything else happen in perl. Our job here is to set the last name int npcscore = GetScore(); std::string bosstag = "<"; int lvs = (int)(npcscore/10); for(int x = 0; x < 10; x++) { if(x < lvs) { bosstag += "+"; } else { bosstag += "="; } } bosstag += ">"; strn0cpy(lastname, bosstag.c_str(), sizeof(lastname)); TempName("DYNBOSS"); } //Base damage from NPC::Attack int NPC::mod_npc_damage(int damage, SkillType skillinuse, int hand, const Item_Struct* weapon, Mob* other) { float chbonus; int lbonus; // if(!IsPet() || !GetOwner()) { return(damage); } // if(!GetOwner()->IsClient()) { return(damage); } //Client pet power bonuses if(GetOwner() && GetOwner()->IsClient()) { chbonus = (float)((float)GetOwner()->CastToClient()->GetActCHA() - DW_STATBASE) / 100; chbonus += 1; if(GetOwner()->GetLevel() > 50) { lbonus = GetOwner()->GetLevel() - 50; damage += lbonus * (20 * lbonus); } if(weapon) { damage += (int)ceil(((float)weapon->Damage / (float)weapon->Delay) * (float)GetLevel()); } damage = (int)ceil( (float)damage * chbonus ); return(damage); } //Regular NPC damage - test for debuffs that currently do nothing. //str, sta, agi, dex, AC, ATK return(damage); } void NPC::mod_npc_killed_merit(Mob* c) { return; } void NPC::mod_npc_killed(Mob* oos) { return; } //Base damage from Client::Attack - can cover myriad skill types int Client::mod_client_damage(int damage, SkillType skillinuse, int hand, const ItemInst* weapon, Mob* other) { float dmult = 1.1f; float cdex = (float)GetActDEX() - DW_STATBASE; float cstr = (float)GetActSTR() - DW_STATBASE; float cagi = (float)GetActAGI() - DW_STATBASE; float csta = (float)GetActSTA() - DW_STATBASE; float cmax = ((float)GetLevel() * 2) + 150; if(GetLevel() > 49) { cmax = 10000; } if(cdex > cmax) { cdex = cmax; } if(cstr > cmax) { cstr = cmax; } if(cagi > cmax) { cagi = cmax; } if(csta > cmax) { csta = cmax; } switch(skillinuse) { case ARCHERY: dmult += cdex / 600; dmult += csta / 600; if(GetClass() == RANGER) { dmult += cdex / 1000; } break; case KICK: case HAND_TO_HAND: case BACKSTAB: case _1H_BLUNT: case _1H_SLASHING: case PIERCING: case THROWING: if(GetClass() == MONK || GetClass() == BEASTLORD) { if(GetRace() == BARBARIAN || GetRace() == TROLL || GetRace() == OGRE) { dmult += cstr / 320; dmult += csta / 320; dmult += cdex / 150; //2.325 } else { dmult += cdex / 400; dmult += csta / 200; dmult += cstr / 200; //2.25 } } else { dmult += cdex / 400; dmult += cstr / 200; dmult += csta / 200; //2.25 } break; case BASH: if(GetClass() == WARRIOR) { dmult += csta / 100; dmult += cstr / 100; } else { dmult += csta / 150; dmult += cstr / 150; } break; case _2H_SLASHING: case _2H_BLUNT: case FRENZY: if(GetClass() == BERSERKER) { dmult += csta / 100; dmult += cstr / 100; } else { dmult += csta / 150; dmult += cstr / 150; } break; default: dmult += cstr / 200; dmult += csta / 200; break; } if(GetLevel() > 50) { float lbonus = GetLevel() - 50; dmult += lbonus * 0.2; } int final = (int)((float)damage * dmult); if(skillinuse == ARCHERY) { final += (GetSkill(ARCHERY) + (GetLevel() * 2)) / 2; if(weapon) //We should always have a weapon here { final = (int)((float)final * (float)(0.6 + ((float)weapon->GetItem()->Delay / 100))); } } return(final); } //message is char[4096], don't screw it up. bool Client::mod_client_message(char* message, uint8 chan_num) { return(true); } //Potentially dangerous string handling here //Skillup override. When this is called the regular skillup check has failed. Return false to proceed with default behavior. //This will NOT allow a client to increase skill past a cap. bool Client::mod_can_increase_skill(SkillType skillid, Mob* against_who) { //Let people skillup on golem training dummies. if(against_who->GetRace() == 405) { return(true); } return(false); } //chance of general skill increase, rolled against 0-99 where higher chance is better. int16 Client::mod_increase_skill_chance(int16 chance, Mob* against_who) { float cint = (float)GetActINT() - DW_STATBASE; float cwis = (float)GetActWIS() - DW_STATBASE; float bonus = (cint + (cwis / 2)) / 10; if(bonus < 0) { bonus = 0; } return( (int)((float)chance * bonus) ); } //Max percent of health you can bind wound starting with default value for class, item, and AA bonuses int Client::mod_bindwound_percent(int max_percent, Mob* bindmob) { return(max_percent + 20); } //Final bind HP value after bonuses int Client::mod_bindwound_hp(int bindhps, Mob* bindmob) { if(GetSkill(BIND_WOUND) > 200) { bindhps += GetSkill(BIND_WOUND) * 2; } else { bindhps += GetSkill(BIND_WOUND); } if(GetLevel() > 50) { float bonus = ((GetLevel() - 50) / 10) + 1; bindhps += (int)( (float)bindhps * bonus ); } return(bindhps); } //Client haste as calculated by default formulas - In percent from 0-100 int Client::mod_client_haste(int h) { float agibase = (float)GetActAGI() - DW_STATBASE; float agibonus = 0; if(agibase > 0) { agibonus = agibase; float l1, l2, l3; switch(GetClass()) { case ROGUE: case MONK: l1 = 1.5; l2 = 2.0; l3 = 2.5; break; default: l1 = 1.0; l2 = 1.5; l3 = 2.0; } if(GetLevel() > 19) { agibonus = agibase * l1; } if(GetLevel() > 39) { agibonus = agibase * l2; } if(GetLevel() > 59) { agibonus = agibase * l3; } } h += (int)(agibonus / 10); return(h); } void Client::mod_consider(Mob* tmob, Consider_Struct* con) { return; } bool Client::mod_saylink(const std::string&, bool silentsaylink) { return(true); } //Client pet power as calculated by default formulas and bonuses int16 Client::mod_pet_power(int16 act_power, uint16 spell_id) { act_power += (int)(((float)GetActCHA() - DW_STATBASE) /2 ) + GetLevel(); return(act_power); } //Chance to combine rolled against a random 0-99 where higher is better. float Client::mod_tradeskill_chance(float chance, DBTradeskillRecipe_Struct *spec) { chance += 10; return(chance); } //Chance to skillup rolled against a random 0-99 where higher is better. float Client::mod_tradeskill_skillup(float chance_stage2) { float cint = (float)CastToClient()->GetActINT() - DW_STATBASE; float cwis = (float)CastToClient()->GetActWIS() - DW_STATBASE; float bonus = (cint + (cwis / 2)) / 10; if(bonus < 0) { bonus = 0; } return(chance_stage2 + bonus); } //Tribute value override int32 Client::mod_tribute_item_value(int32 pts, const ItemInst* item) { return(0); } //Death reporting void Client::mod_client_death_npc(Mob* killerMob) { return; } void Client::mod_client_death_duel(Mob* killerMob) { return; } void Client::mod_client_death_env() { return; } //Calculated xp before consider modifier int32 Client::mod_client_xp(int32 in_xp, NPC *npc) { float xpmult = 2.0f; if(GetLevel() > 5) { xpmult = 1.5f; } if(GetLevel() > 10) { xpmult = 1.25f; } if(GetLevel() > 15) { xpmult = 1.0f; } return( (int32)((float)in_xp * xpmult) ); } //effect_vallue - Spell effect value as calculated by default formulas. You will want to ignore effects that don't lend themselves to scaling - pet ID's, gate coords, etc. int Mob::mod_effect_value(int effect_value, uint16 spell_id, int effect_type, Mob* caster) { if(IsClient()) { return(effect_value); } if(!caster) { return(effect_value); } if(!caster->IsClient()) { return(effect_value); } float mult = 1.0f; float spbonus = 0.0f; float spadd = 0.0f; if(caster->GetClass() == BARD) { spbonus = (float)(caster->CastToClient()->GetActCHA() - DW_STATBASE) / 150; spbonus += (float)(caster->CastToClient()->GetActINT() - DW_STATBASE) / 150; } else if(caster->GetCasterClass() == 'W') { spbonus = (float)(caster->CastToClient()->GetActWIS() - DW_STATBASE) / 400; spbonus += (float)(caster->CastToClient()->GetActSTA() - DW_STATBASE) / 400; } else if(caster->GetCasterClass() == 'I') { spbonus = (float)(caster->CastToClient()->GetActINT() - DW_STATBASE) / 400; spbonus += (float)(caster->CastToClient()->GetActDEX() - DW_STATBASE) / 400; } else { //No proc/click bonuses for non casters return(effect_value); } //Add a fixed value to help things like bard songs, dots, and other low effect value spells spadd = (mult + spbonus) * ((float)caster->GetLevel() / 10); if(caster->GetLevel() > 50) { float lbonus = caster->GetLevel() - 50; spadd += spadd * (lbonus / 2); } switch(effect_type) { case SE_CriticalHitChance: case SE_SpellCritChance: case SE_CrippBlowChance: case SE_AvoidMeleeChance: case SE_RiposteChance: case SE_DodgeChance: case SE_ParryChance: case SE_DualWieldChance: case SE_DoubleAttackChance: case SE_MovementSpeed: case SE_AttackSpeed4: case SE_AttackSpeed3: case SE_AttackSpeed2: case SE_AttackSpeed: if(caster->GetLevel() < 30) { spbonus = spbonus / 2; } if(caster->GetLevel() >= 30 && caster->GetLevel() < 46) { spbonus = spbonus / 1.5; } mult += spbonus; //No fixed bonus on these types spadd = 0; break; case SE_CurrentHPOnce: case SE_CurrentHP: if(caster->GetLevel() < 30) { spbonus = spbonus / 2; } if(caster->GetLevel() >= 30 && caster->GetLevel() < 46) { spbonus = spbonus / 1.5; } mult += spbonus; break; case SE_DamageShield: case SE_Stun: case SE_ArmorClass: case SE_ATK: case SE_STR: case SE_DEX: case SE_AGI: case SE_STA: case SE_INT: case SE_WIS: case SE_CHA: case SE_CurrentMana: case SE_Lull: case SE_AddFaction: case SE_Stamina: case SE_ChangeFrenzyRad: case SE_DiseaseCounter: case SE_PoisonCounter: case SE_ResistFire: case SE_ResistCold: case SE_ResistPoison: case SE_ResistDisease: case SE_ResistMagic: case SE_Rune: case SE_TotalHP: case SE_TossUp: case SE_ManaPool: case SE_HealOverTime: case SE_CastingLevel: case SE_Hunger: case SE_CurseCounter: case SE_HealRate: case SE_ImprovedDamage: case SE_ImprovedHeal: case SE_SpellResistReduction: case SE_IncreaseSpellHaste: case SE_IncreaseSpellDuration: case SE_IncreaseRange: case SE_AllStats: case SE_MeleeLifetap: case SE_AllInstrumentMod: case SE_ResistSpellChance: case SE_ResistFearChance: case SE_HitChance: case SE_DamageModifier: case SE_MinDamageModifier: case SE_IncreaseBlockChance: case SE_CurrentEndurance: case SE_EndurancePool: case SE_CurrentEnduranceOnce: case SE_MaxHPChange: case SE_Accuracy: case SE_BardAEDot: case SE_CurrentManaOnce: case SE_FactionMod: case SE_CorruptionCounter: case SE_ResistCorruption: mult += spbonus; break; default: return(effect_value); break; } //Shroud of the bear if(caster->FindBuff(5045)) { spadd = spadd * -1; mult = 1.0f; } if(effect_value > 0) { effect_value += (int)ceil(spadd); } else { effect_value -= (int)ceil(spadd); } effect_value *= mult; return( (int)(ceil(effect_value)) ); } //chancetohit - 0 to 100 percent - set over 1000 for a guaranteed hit float Mob::mod_hit_chance(float chancetohit, SkillType skillinuse, Mob* attacker) { if(!IsClient()) { //Factor NPC debuffs return(chancetohit); } int divisor; switch(GetClass()) { case MONK: case ROGUE: divisor = 8; divisor -= (GetLevel() / 100) * 2; break; default: divisor = 8; } float hitmod = ((float)CastToClient()->GetActDEX() - DW_STATBASE) / divisor; hitmod += ((float)CastToClient()->GetActAGI() - DW_STATBASE) / divisor; if(hitmod < 0) { hitmod = -5; } return(chancetohit + hitmod); } float Mob::mod_riposte_chance(float ripostechance, Mob* attacker) { return(ripostechance); } float Mob::mod_block_chance(float blockchance, Mob* attacker) { return(blockchance); } float Mob::mod_parry_chance(float parrychance, Mob* attacker) { return(parrychance); } float Mob::mod_dodge_chance(float dodgechance, Mob* attacker) { return(dodgechance); } float Mob::mod_monk_weight(float monkweight, Mob* attacker) { return(monkweight); } float Mob::mod_mitigation_rating(float mitigation_rating, Mob* attacker) { return(mitigation_rating); } float Mob::mod_attack_rating(float attack_rating, Mob* defender) { return(attack_rating); } //Kick damage after all other bonuses are applied int32 Mob::mod_kick_damage(int32 dmg) { if(!IsClient()) { return(dmg); } ItemInst *item = CastToClient()->GetInv().GetItem(SLOT_FEET); if(item) { dmg += item->GetItem()->AC; for(int i = 0; i < MAX_AUGMENT_SLOTS; ++i) { ItemInst *aug = item->GetAugment(i); if(aug) { dmg += aug->GetItem()->AC; } } } dmg += GetLevel() / 4; dmg = CastToClient()->mod_client_damage(dmg, KICK, 0, nullptr, nullptr); return(dmg); } //Slam and bash damage after all other bonuses are applied int32 Mob::mod_bash_damage(int32 dmg) { if(!IsClient()) { return(dmg); } float lmult = (((float)GetLevel() * 2 ) / 100) + 1; bool foundshield = false; ItemInst *item = CastToClient()->GetInv().GetItem(SLOT_SECONDARY); if(item) { if(item->GetItem()->ItemType == ItemTypeShield) { foundshield = true; dmg += item->GetItem()->AC * lmult; for(int i = 0; i < MAX_AUGMENT_SLOTS; ++i) { ItemInst *aug = item->GetAugment(i); if(aug) { dmg += aug->GetItem()->AC; } } } } if(!foundshield) //This is from a slam { item = CastToClient()->GetInv().GetItem(SLOT_SHOULDER); if(item) { if(item->GetItem()->ItemType == ItemTypeArmor) { dmg += item->GetItem()->AC * lmult; for(int i = 0; i < MAX_AUGMENT_SLOTS; ++i) { ItemInst *aug = item->GetAugment(i); if(aug) { dmg += aug->GetItem()->AC; } } } } } dmg += GetLevel() / 4; dmg = CastToClient()->mod_client_damage(dmg, BASH, 0, nullptr, nullptr); return(dmg); } //Frenzy damage after all other bonuses are applied int32 Mob::mod_frenzy_damage(int32 dmg) { if(!IsClient()) { return(dmg); } dmg = CastToClient()->mod_client_damage(dmg, FRENZY, 0, nullptr, nullptr); return(dmg); } //Special attack damage after all other bonuses are applied. int32 Mob::mod_monk_special_damage(int32 ndamage, SkillType skill_type) { if(!IsClient()) { return(ndamage); } ndamage = CastToClient()->mod_client_damage(ndamage, KICK, 0, nullptr, nullptr); return(ndamage); } //ndamage - Backstab damage as calculated by default formulas int32 Mob::mod_backstab_damage(int32 ndamage) { if(!IsClient()) { return(ndamage); } float cdex = (float)CastToClient()->GetActDEX() - DW_STATBASE; float cstr = (float)CastToClient()->GetActSTR() - DW_STATBASE; float cagi = (float)CastToClient()->GetActAGI() - DW_STATBASE; float bsm = 1.0f; bsm += (cdex / 200) + ((float)GetLevel() / 100); bsm += cstr / 300; bsm += cagi / 300; if(GetLevel() > 50) { float lbonus = GetLevel() - 50; bsm += (lbonus * 0.1); } return( (int32)((float)ndamage * bsm) ); } //Chance for 50+ archery bonus damage if Combat:UseArcheryBonusRoll is true. Base is Combat:ArcheryBonusChance int Mob::mod_archery_bonus_chance(int bonuschance, const ItemInst* RangeWeapon) { return(bonuschance); } //Archery bonus damage uint32 Mob::mod_archery_bonus_damage(uint32 MaxDmg, const ItemInst* RangeWeapon) { return(MaxDmg); } //Final archery damage including bonus if it was applied. int32 Mob::mod_archery_damage(int32 TotalDmg, bool hasbonus, const ItemInst* RangeWeapon) { if(!IsClient()) { return(TotalDmg); } TotalDmg += (GetLevel() * 2); TotalDmg = CastToClient()->mod_client_damage(TotalDmg, ARCHERY, 0, RangeWeapon, nullptr); return(TotalDmg); } //Thrown weapon damage after all other calcs uint16 Mob::mod_throwing_damage(uint16 MaxDmg) { if(!IsClient()) { return(MaxDmg); } MaxDmg = CastToClient()->mod_client_damage(MaxDmg, THROWING, 0, nullptr, nullptr); return(MaxDmg); } int32 Mob::mod_cast_time(int32 cast_time) { if(!IsClient()) { return(cast_time); } float ctmod = 1; float cdex = (float)CastToClient()->GetActDEX() - DW_STATBASE; float ccast = 0; if(GetCasterClass() == 'W') { ccast = (float)CastToClient()->GetActWIS() - DW_STATBASE; } if(GetCasterClass() == 'I') { ccast = (float)CastToClient()->GetActINT() - DW_STATBASE; } if(ccast < 0.0001) { return(cast_time); } ctmod += cdex / 185; ctmod += ccast / 185; if(GetLevel() > 50) { float lbonus = GetLevel() - 50; ctmod += (lbonus * 0.1); } return( (int32)((float)cast_time / ctmod) ); } //res - Default buff duration formula int Mob::mod_buff_duration(int res, Mob* caster, Mob* target, uint16 spell_id) { if(!IsClient()) { return(res); } float cmult = 1; if(GetClass() == BARD) { cmult = (float)(CastToClient()->GetActCHA() - DW_STATBASE) / 200; } else if(GetCasterClass() == 'W') { cmult = (float)(CastToClient()->GetActWIS() - DW_STATBASE) / 200; } else if(GetCasterClass() == 'I') { cmult = (float)(CastToClient()->GetActINT() - DW_STATBASE) / 200; } cmult += (float)(CastToClient()->GetActSTA() - DW_STATBASE) / 400; if(cmult < 1) { cmult = 1; } return( (int)((float)res * cmult) ); } //Spell stack override - If this returns anything < 2, it will ignore all other stacking rules. // See spells.cpp: Mob::CheckStackConflict // 0 - No conflict // 1 - Overwrite, spellid1 is replaced by spellid2 // -1 - Blocked, spellid2 will not land // 2 - Default stacking behavior int Mob::mod_spell_stack(uint16 spellid1, int caster_level1, Mob* caster1, uint16 spellid2, int caster_level2, Mob* caster2) { if(IsEffectInSpell(spellid1, SE_Illusion) && IsEffectInSpell(spellid2, SE_Illusion)) { return(1); } return(2); } //Sum of various resists rolled against a value of 200. int Mob::mod_spell_resist(int resist_chance, int level_mod, int resist_modifier, int target_resist, uint8 resist_type, uint16 spell_id, Mob* caster) { int final = resist_chance + level_mod + resist_modifier + target_resist; int temp_level_diff = GetLevel() - caster->GetLevel(); if(temp_level_diff > 15 && caster->GetLevel() < 46) { if(caster->IsClient()) { if(caster->CastToClient()->GetAAXP() < 100) { return(0); } } } if(final > 185) { final = 185; } // Cap resist so it's always possible to land a spell (unless we hit the client level diff max). if(!IsClient()) { return(final); } float resistmod = 1.0f; //Make charisma a part of all resists resistmod += ((float)CastToClient()->GetActCHA() - DW_STATBASE) / 20; //The other half is the casting stat if(GetClass() == BARD) { resistmod += ((float)CastToClient()->GetActCHA() - DW_STATBASE) / 20; } else if(GetCasterClass() == 'W') { resistmod += ((float)CastToClient()->GetActWIS() - DW_STATBASE) / 20; } else if(GetCasterClass() == 'I') { resistmod += ((float)CastToClient()->GetActINT() - DW_STATBASE) / 20; } final += resistmod; if(caster->GetLevel() > 50) { final -= (int)( (caster->GetLevel() - 50) * 20 ); } //Let the client be highly resistant to their own AoE if( (spells[spell_id].targettype == ST_AECaster || spells[spell_id].targettype == ST_AETarget) && caster->CastToClient()->CharacterID() == CastToClient()->CharacterID() ) { final = 185; } return(final); }