eqemu-server/zone/tune.cpp
Alex King 3335cacac1
[Bots] Cleanup and remove preprocessors. (#2757)
* [Bots] Cleanup and remove preprocessors.

- Removes every `#ifdef BOTS` we have and locks bots behind `Bots:AllowBots` rule.
- Bot updates are now done by default similar to regular database updates.
- Modify `CMakeLists.txt`, `.drone.yml`, and `BUILD.md` to match the removal of `EQEMU_ENABLE_BOTS`.

* Cleanup

- Add SQL for enabling bots for servers with bots.
- Add message that tells players/operators bots are disabled.

* Suggested changes.

* Bot injection stuff

* Change SQL to bot SQL.

* Tweaks

* Remove `is_bot`

* Update version.h

* Update main.cpp

* Update database.cpp

* Fix name availability crash

* Remove bots from update script

Co-authored-by: Akkadius <akkadius1@gmail.com>
2023-01-20 12:35:33 -06:00

1544 lines
53 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 TUNE_DEBUG 20
#endif
#include "../common/global_define.h"
#include "../common/eq_constants.h"
#include "../common/eq_packet_structs.h"
#include "../common/rulesys.h"
#include "../common/skills.h"
#include "../common/spdat.h"
#include "../common/strings.h"
#include "../common/data_verification.h"
#include "../common/misc_functions.h"
#include "queryserv.h"
#include "quest_parser_collection.h"
#include "string_ids.h"
#include "water_map.h"
#include "worldserver.h"
#include "zone.h"
#include "lua_parser.h"
#include "fastmath.h"
#include "mob.h"
#include "npc.h"
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <boost/concept_check.hpp>
#include "bot.h"
extern QueryServ* QServ;
extern WorldServer worldserver;
extern FastMath g_Math;
#ifdef _WINDOWS
#define snprintf _snprintf
#define strncasecmp _strnicmp
#define strcasecmp _stricmp
#endif
extern EntityList entity_list;
extern Zone* zone;
void Mob::TuneGetStats(Mob* defender, Mob *attacker)
{
if (!defender || !attacker) {
Message(0, "#Tune - Processing... Abort! Can not find attacker or defender");
return;
}
int max_damage = 0;
int min_damage = 0;
int mean_dmg = 0;
float tmp_pct_mitigated = 0.0f;
float hit_chance = 0.0f;
max_damage = attacker->TuneClientGetMaxDamage(defender);
min_damage = attacker->TuneClientGetMinDamage(defender, max_damage);
if (!max_damage)
{
Message(0, "#Tune - Processing... Abort! Damage not found! [MaxDMG %i MinDMG %i]", max_damage, min_damage);
return;
}
mean_dmg = attacker->TuneClientGetMeanDamage(defender);
tmp_pct_mitigated = 100.0f - (static_cast<float>(mean_dmg) * 100.0f / static_cast<float>(max_damage));
hit_chance = TuneGetHitChance(defender, attacker);
Message(0, "#STATS#############START######################");
Message(0, "[#Tune] Defender Statistics vs Attacker");
Message(0, "[#Tune] Defender Name: %s", defender->GetCleanName());
Message(0, "[#Tune] AC Mitigation pct: %.0f pct ", round(tmp_pct_mitigated));
Message(0, "[#Tune] Total AC: %i ", defender->TuneACSum());
Message(0, "[#Tune] Mean Damage Taken: %i per hit", mean_dmg);
Message(0, "[#Tune] Chance to be missed: %.0f pct", (100.0f - round(hit_chance)));
Message(0, "[#Tune] Avoidance: %i ", TuneGetAvoidance(defender, attacker));
Message(0, "[#Tune] Riposte Chance: %.0f pct ", round(TuneGetAvoidMeleeChance(defender, attacker, DMG_RIPOSTED)));
Message(0, "[#Tune] Block Chance: %.0f pct ", round(TuneGetAvoidMeleeChance(defender, attacker, DMG_BLOCKED)));
Message(0, "[#Tune] Parry Chance: %.0f pct ", round(TuneGetAvoidMeleeChance(defender, attacker, DMG_PARRIED)));
Message(0, "[#Tune] Dodge Chance: %.0f pct ", round(TuneGetAvoidMeleeChance(defender, attacker, DMG_DODGED)));
if (defender->IsNPC())
{
Message(0, "[#Tune] NPC STAT AC: %i ", static_cast<int>(defender->CastToNPC()->GetNPCStat("ac")));
Message(0, "[#Tune] NPC STAT Avoidance: %i ", static_cast<int>(defender->CastToNPC()->GetNPCStat("avoidance")));
}
Message(0, "################################################");
Message(0, "[#Tune] Attacker Statistics vs Defender");
Message(0, "[#Tune] Attacker Name: %s", attacker->GetCleanName());
if (max_damage > 0) {
Message(0, "[#Tune] Max Damage %i Min Damage %i", max_damage, min_damage);
Message(0, "[#Tune] Total Offense: %i ", TuneGetOffense(defender, attacker));
Message(0, "[#Tune] Chance to hit: %.0f pct", round(hit_chance));
Message(0, "[#Tune] Total Accuracy: %i ", TuneGetAccuracy( defender,attacker));
if (attacker->IsNPC())
{
Message(0, "[#Tune] NPC STAT ATK: %i ", static_cast<int>(attacker->CastToNPC()->GetNPCStat("atk")));
Message(0, "[#Tune] NPC STAT Accuracy: %i ", static_cast<int>(attacker->CastToNPC()->GetNPCStat("accuracy")));
}
}
else{
Message(0, "[#Tune] Can not melee this target");
}
Message(0, "#STATS#############COMPLETE###################");
return;
}
void Mob::TuneGetACByPctMitigation(Mob* defender, Mob *attacker, float pct_mitigation, int interval, int max_loop, int atk_override, int Msg)
{
Message(0, " ");
/*
Find the amount of AC stat that has to be added/subtracted from DEFENDER to reach a specific average mitigation value based on ATTACKER's offense statistics.
Can use atk_override to find the value verse a hypothetical amount of worn ATK
*/
if (pct_mitigation > 100 || pct_mitigation < 0) {
Message(0, "[#Tune] - Processing... Abort! Mitigation value out of range ( %.0f ) pct. Must be between 0-100.", pct_mitigation);
return;
}
if (!defender) {
Message(0, "[#Tune] - Processing... Abort! No Defender found.");
return;
}
if (!attacker) {
Message(0, "[#Tune] - Processing... Abort! No Attacker found.");
return;
}
if (defender->GetID() == attacker->GetID()) {
Message(0, "[#Tune] - Processing... Abort! Error Attacker can not be the Defender.");
return;
}
int max_damage = 0;
int min_damage = 0;
int mean_dmg = 0;
float tmp_pct_mitigated = 0.0f;
float base_pct_mitigation = pct_mitigation;
int loop_add_ac = 0;
int end = 0;
int value = 0;
max_damage = attacker->TuneClientGetMaxDamage(defender);
min_damage = attacker->TuneClientGetMinDamage(defender, max_damage);
if (!max_damage)
{
Message(0, "#Tune - Processing... Abort! Damage not found! [MaxDMG %i MinDMG %i]", max_damage, min_damage);
return;
}
//Obtain baseline mitigation for current stats
mean_dmg = attacker->TuneClientGetMeanDamage(defender,0,atk_override);
tmp_pct_mitigated = 100.0f - (static_cast<float>(mean_dmg) * 100.0f / static_cast<float>(max_damage));
Message(0, "###################START###################");
Message(0, "[#Tune] DFENDER Name: %s", defender->GetCleanName());
Message(0, "[#Tune] DEFENDER AC Mitigation pct: %.0f pct ", round(tmp_pct_mitigated));
Message(0, "[#Tune] DEFENDER Total AC: %i ", defender->TuneACSum());
Message(0, "[#Tune] ATTACKER Name: %s", attacker->GetCleanName());
Message(0, "[#Tune] ATTACKER Max Damage %i Min Damage %i", max_damage, min_damage);
Message(0, "[#Tune] ATTACKER Total Offense: %i ", TuneGetOffense(defender, attacker, atk_override));
Message(0, "##########################################");
Message(0, "[#Tune] Begin Parse [Interval %i Max Loop Iterations %i]", interval, max_loop);
if (tmp_pct_mitigated > pct_mitigation)
{
interval = interval * -1;
Message(0, "[#Tune] NOTE: Defenders 'AC' must be LOWERED due to defenders AC Mitigation ( %.0f pct ) being greater than the desired ( %.0f pct )", tmp_pct_mitigated, pct_mitigation);
}
Message(0, "[#Tune] Processing... Find AC for defender to have Mitigation of ( %.0f pct ) agianst this attacker.", pct_mitigation);
for (int j = 0; j < max_loop; j++)
{
mean_dmg = attacker->TuneClientGetMeanDamage(defender, 0, atk_override, loop_add_ac,0);
tmp_pct_mitigated = 100.0f - (static_cast<float>(mean_dmg) * 100.0f / static_cast<float>(max_damage));
if (Msg >= 1)
{
Message(0, "[#Tune] - Processing... [%i] [AC %i] Average Melee Hit %i | Pct Mitigated %.2f ", j, loop_add_ac, mean_dmg, tmp_pct_mitigated);
}
if (interval > 0 && tmp_pct_mitigated >= pct_mitigation)
{
end = 1;
}
else if (interval < 0 && tmp_pct_mitigated <= pct_mitigation)
{
end = 1;
}
else if (interval < 0 && mean_dmg == min_damage)
{
Message(0, "[#Tune][WARNING] Mitigation can not be further decreased due to minium hit value (%i). Minium mitigation ( %.0f ) pct", min_damage, tmp_pct_mitigated);
base_pct_mitigation = tmp_pct_mitigated;
end = 1;
}
if (end >= 1) {
Message(0, "###################RESULTS###################");
if (atk_override) {
Message(0, "[#Tune] ATK STAT OVERRRIDE. This is the amount of AC adjustment needed if this attacker had ( %i ) raw ATK stat", atk_override);
}
if (defender->IsNPC())
{
Message(0, "[#Tune] Recommended NPC RAW AC ADJUSTMENT ( %i ) on ' %s ' to acheive an average mitigation of ( %.0f pct ) verse ' %s '", loop_add_ac, defender->GetCleanName(), base_pct_mitigation, attacker->GetCleanName());
Message(0, "[#Tune] SET NPC 'AC' stat value = [ %i ]", loop_add_ac + defender->CastToNPC()->GetRawAC());
Message(0, "###################COMPLETE###################");
}
if (defender->IsClient())
{
Message(0, "[#Tune] Recommended CLIENT AC ADJUSTMENT ( %i ) on ' %s ' to acheive an average mitigation of ( %.0f pct ) verse ' %s '", loop_add_ac, defender->GetCleanName(), base_pct_mitigation, attacker->GetCleanName());
if (loop_add_ac >= 0) {
Message(0, "[#Tune] MODIFY Client Item AC or Spell Effect AC by [+ %i ]", loop_add_ac);
}
else {
Message(0, "[#Tune] MODIFY Client Item AC or Spell Effect AC by [ %i ]", loop_add_ac);
}
Message(0, "###################COMPLETE###################");
}
return;
}
loop_add_ac = loop_add_ac + interval;
}
Message(0, "###################ABORT#######################");
Message(0, "[#Tune] - Error: Unable to find desired result for ( %.0f pct ) - Increase interval ( %i ) AND/OR max loop value ( %i ) and run again.", pct_mitigation, interval, max_loop);
Message(0, "[#Tune] - Parse ended at an AC ADJUSTMENT of ( %i ) on ' %s ' to acheive an average mitigation of ( %.0f pct ) verse ' %s '", loop_add_ac, attacker->GetCleanName(), tmp_pct_mitigated, defender->GetCleanName());
Message(0, "###################COMPLETE###################");
return;
}
void Mob::TuneGetATKByPctMitigation(Mob* defender, Mob *attacker, float pct_mitigation, int interval, int max_loop, int ac_override, int Msg)
{
Message(0, " ");
/*
Find the amount of ATK stat that has to be added/subtracted from ATTACKER to reach a specific average mitigation value based on DEFENDERS's mitigation statistics.
Can use ac_override to find the value verse a hypothetical amount of worn AC
*/
if (pct_mitigation > 100 || pct_mitigation < 0) {
Message(0, "[#Tune] - Processing... Abort! Mitigation value out of range ( %.0f ) pct. Must be between 0-100.", pct_mitigation);
return;
}
if (!defender) {
Message(0, "[#Tune] - Processing... Abort! No Defender found.");
return;
}
if (!attacker) {
Message(0, "[#Tune] - Processing... Abort! No Attacker found.");
return;
}
if (defender->GetID() == attacker->GetID()) {
Message(0, "[#Tune] - Processing... Abort! Error Attacker can not be the Defender.");
return;
}
int max_damage = 0;
int min_damage = 0;
int mean_dmg = 0;
float tmp_pct_mitigated = 0.0f;
float base_pct_mitigation = pct_mitigation;
int loop_add_atk = 0;
int end = 0;
int value = 0;
max_damage = attacker->TuneClientGetMaxDamage(defender);
min_damage = attacker->TuneClientGetMinDamage(defender, max_damage);
if (!max_damage)
{
Message(0, "#Tune - Processing... Abort! Damage not found! [MaxDMG %i MinDMG %i]", max_damage, min_damage);
return;
}
//Obtain baseline mitigation for current stats
mean_dmg = attacker->TuneClientGetMeanDamage(defender, ac_override);
tmp_pct_mitigated = 100.0f - (static_cast<float>(mean_dmg) * 100.0f / static_cast<float>(max_damage));
Message(0, "###################START###################");
Message(0, "[#Tune] DFENDER Name: %s", defender->GetCleanName());
Message(0, "[#Tune] DEFENDER AC Mitigation pct: %.0f pct ", round(tmp_pct_mitigated));
Message(0, "[#Tune] DEFENDER Total AC: %i ", defender->TuneACSum(false, ac_override));
Message(0, "[#Tune] ATTACKER Name: %s", attacker->GetCleanName());
Message(0, "[#Tune] ATTACKER Max Damage %i Min Damage %i", max_damage, min_damage);
Message(0, "[#Tune] ATTACKER Total Offense: %i ", TuneGetOffense(defender, attacker));
Message(0, "##########################################");
Message(0, "[#Tune] Begin Parse [Interval %i Max Loop Iterations %i]", interval, max_loop);
if (tmp_pct_mitigated < pct_mitigation) {
interval = interval * -1;
Message(0, "[#Tune] NOTE: Attackers 'ATK' must be LOWERED due to defenders AC Mitigation ( %.0f pct ) being less than the desired ( %.0f pct )", tmp_pct_mitigated, pct_mitigation);
}
Message(0, "[#Tune] Processing... Find ATK on attacker for defender to have Mitigation of ( %.0f pct ) agianst this attacker.", pct_mitigation);
for (int j = 0; j < max_loop; j++)
{
mean_dmg = attacker->TuneClientGetMeanDamage(defender, ac_override, 0, 0, loop_add_atk);
tmp_pct_mitigated = 100.0f - (static_cast<float>(mean_dmg) * 100.0f / static_cast<float>(max_damage));
if (Msg >= 3)
{
Message(0, "[#Tune] - Processing... [%i] [ATK %i] Average Melee Hit %i | Pct Mitigated %.2f ", j, loop_add_atk, mean_dmg, tmp_pct_mitigated);
}
if (interval > 0 && tmp_pct_mitigated <= pct_mitigation) {
end = 1;
}
else if (interval < 0 && tmp_pct_mitigated >= pct_mitigation) {
end = 1;
}
else if (interval < 0 && mean_dmg == min_damage)
{
Message(0, "[#Tune] [WARNING] Mitigation can not be further decreased due to minium hit value ( %i ). Minium mitigation ( %.0f pct )", min_damage, tmp_pct_mitigated);
base_pct_mitigation = tmp_pct_mitigated;
end = 1;
}
if (end >= 1) {
Message(0, "###################RESULTS###################");
if (ac_override) {
Message(0, "[#Tune] AC STAT OVERRRIDE. This is the amount of ATK adjustment needed if this defender had ( %i ) raw AC stat", ac_override);
}
if (attacker->IsNPC()) {
Message(0, "[#Tune] Recommended NPC ATK ADJUSTMENT ( %i ) on ' %s ' so that their hits on average are mitgiated by ( %.0f pct ) verse ' %s '. ", loop_add_atk, attacker->GetCleanName(), base_pct_mitigation, defender->GetCleanName());
Message(0, "[#Tune] SET NPC 'ATK' stat value = [ %i ]", loop_add_atk + defender->CastToNPC()->ATK);
Message(0, "###################COMPLETE###################");
}
if (attacker->IsClient()) {
Message(0, "[#Tune] Recommended CLIENT ATK ADJUSTMENT ( %i ) on ' %s ' so that their hits on average are mitigated by ( %.0f pct ) verse ' %s '. ", loop_add_atk, attacker->GetCleanName(), base_pct_mitigation, defender->GetCleanName());
if (loop_add_atk >= 0) {
Message(0, "[#Tune] MODIFY Client Item ATK or Spell Effect ATK by [+ %i ]", loop_add_atk);
}
else {
Message(0, "[#Tune] MODIFY Client Item ATK or Spell Effect ATK by [ %i ]", loop_add_atk);
}
Message(0, "###################COMPLETE###################");
}
return;
}
loop_add_atk = loop_add_atk + interval;
}
Message(0, "###################ABORT#######################");
Message(0, "[#Tune] - Error: Unable to find desired result for ( %.0f pct ) - Increase interval ( %i ) AND/OR max loop value ( %i ) and run again.", pct_mitigation, interval, max_loop);
Message(0, "[#Tune] - Parse ended at an ATK ADJUSTMENT of ( %i ) on ' %s ' so that their hits on average are mitigated by ( %.0f pct ) verse ' %s '.", loop_add_atk, attacker->GetCleanName(), tmp_pct_mitigated, defender->GetCleanName());
Message(0, "###################COMPLETE###################");
return;
}
void Mob::TuneGetAvoidanceByHitChance(Mob* defender, Mob *attacker, float hit_chance, int interval, int max_loop, int accuracy_override, int Msg)
{
Message(0, " ");
if (hit_chance > 100 || hit_chance < 0) {
Message(0, "[#Tune] - Processing... Abort! Hit Chance value out of range ( %.0f ) pct. Must be between 0-100.", hit_chance);
return;
}
if (!defender) {
Message(0, "[#Tune] - Processing... Abort! No Defender found.");
return;
}
if (!attacker) {
Message(0, "[#Tune] - Processing... Abort! No Attacker found.");
return;
}
if (defender->GetID() == attacker->GetID()) {
Message(0, "[#Tune] - Processing... Abort! Error Attacker can not be the Defender.");
return;
}
int loop_add_avoid = 0;
float tmp_hit_chance = 0.0f;
bool end = false;
int base_avoidance = TuneGetAvoidance(defender, attacker);
tmp_hit_chance = TuneGetHitChance(defender, attacker, 0, accuracy_override);
Message(0, "###################START###################");
Message(0, "[#Tune] DEFENDER Name: %s", defender->GetCleanName());
Message(0, "[#Tune] DEFENDER Chance to be missed: %.0f pct", (100.0f - round(tmp_hit_chance)));
Message(0, "[#Tune] DEFENDER Avoidance: %i ", TuneGetAvoidance(defender, attacker));
Message(0, "[#Tune] ATTACKER Name: %s", attacker->GetCleanName());
Message(0, "[#Tune] ATTACKER Chance to hit: %.0f pct", round(tmp_hit_chance));
Message(0, "[#Tune] ATTACKER Accuracy: %i ", TuneGetAccuracy(defender, attacker, accuracy_override));
Message(0, "##########################################");
Message(0, "[#Tune] Begin Parse [Interval %i Max Loop Iterations %i]", interval, max_loop);
if (tmp_hit_chance < hit_chance) {
interval = interval * -1;
Message(0, "[#Tune] NOTE: Defenders 'AVOIDANCE' must be LOWERED due to defenders ( %.0f pct ) chance to be hit being less than the desired ( %.0f pct )", tmp_hit_chance, hit_chance);
}
Message(0, "[#Tune] - Processing... Find Avoidance needed on defender for a ( %.0f pct ) hit chance from attacker. Base attacker hit chance ( %.0f pct ). ", hit_chance, tmp_hit_chance);
for (int j = 0; j < max_loop; j++)
{
tmp_hit_chance = TuneGetHitChance(defender, attacker,0, accuracy_override, loop_add_avoid,0);
if (Msg >= 3)
{
Message(0, "[#Tune] - Processing... [%i] AVOIDANCE %i | Hit Chance %.2f ", j, loop_add_avoid, tmp_hit_chance);
}
if (interval > 0 && tmp_hit_chance <= hit_chance)
{
end = true;
}
else if (interval < 0 && tmp_hit_chance >= hit_chance)
{
end = true;
}
if (end) {
Message(0, "###################RESULTS###################");
if (accuracy_override) {
Message(0, "[#Tune] ACCURACY STAT OVERRRIDE. This is the amount of AVOIDANCE adjustment needed if this attacker had ( %i ) raw ACCURACY stat", accuracy_override);
}
if (defender->IsNPC()) {
Message(0, "[#Tune] Recommended NPC AVOIDANCE ADJUSTMENT of ( %i ) on ' %s ' will result in ' %s ' having a ( %.0f pct) hit chance.", loop_add_avoid, defender->GetCleanName(), attacker->GetCleanName(), hit_chance);
Message(0, "[#Tune] SET NPC 'AVOIDANCE' stat value = [ %i ]", loop_add_avoid + defender->CastToNPC()->GetAvoidanceRating());
Message(0, "###################COMPLETE###################");
}
else if (defender->IsClient()) {
Message(0, "[#Tune] Recommended CLIENT AVOIDANCE ADJUSTMENT of ( %i ) on ' %s ' will result in ' %s ' having a ( %.0f pct) hit chance.", loop_add_avoid, defender->GetCleanName(), attacker->GetCleanName(), hit_chance);
int final_avoidance = TuneGetAvoidance(defender, attacker, 0, loop_add_avoid);
int evasion_bonus = TuneCalcEvasionBonus(final_avoidance, base_avoidance);
if (loop_add_avoid >= 0) {
Message(0, "[#Tune] OPTION1: MODIFY Client Heroic AGI or Avoidance Mod2 stat by [+ %i ]", loop_add_avoid);
Message(0, "[#Tune] OPTION2: Give CLIENT an evasion bonus using SPA 172 Evasion SE_AvoidMeleeChance from (spells/items/aa) of [+ %i pct ]", evasion_bonus);
}
else {
Message(0, "[#Tune] OPTION1: MODIFY Client Heroic AGI or Avoidance Mod2 stat by [ %i ]", loop_add_avoid);
Message(0, "[#Tune] OPTION2: Give CLIENT an evasion bonus using SPA 172 Evasion SE_AvoidMeleeChance from (spells/items/aa) of [ %i pct ]", evasion_bonus);
}
Message(0, "###################COMPLETE###################");
}
return;
}
loop_add_avoid = loop_add_avoid + interval;
}
Message(0, "###################ABORT#######################");
Message(0, "[#Tune] Error: Unable to find desired result for ( %.0f pct) - Increase interval (%i) AND/OR max loop value (%i) and run again.", hit_chance, interval, max_loop);
Message(0, "[#Tune] Parse ended at AVOIDANCE ADJUSTMENT ( %i ) on ' %s ' will result in ' %s ' having a ( %.0f pct) hit chance.", loop_add_avoid, defender->GetCleanName(), hit_chance, attacker->GetCleanName());
Message(0, "###################COMPLETE###################");
}
void Mob::TuneGetAccuracyByHitChance(Mob* defender, Mob *attacker, float hit_chance, int interval, int max_loop, int avoidance_override, int Msg)
{
Message(0, " ");
if (hit_chance > 100 || hit_chance < 0) {
Message(0, "[#Tune] - Processing... Abort! Hit Chance value out of range ( %.0f ) pct. Must be between 0-100.", hit_chance);
return;
}
if (!defender) {
Message(0, "[#Tune] - Processing... Abort! No Defender found.");
return;
}
if (!attacker) {
Message(0, "[#Tune] - Processing... Abort! No Attacker found.");
return;
}
if (defender->GetID() == attacker->GetID()) {
Message(0, "[#Tune] - Processing... Abort! Error Attacker can not be the Defender.");
return;
}
int loop_add_accuracy = 0;
float tmp_hit_chance = 0.0f;
bool end = false;
tmp_hit_chance = TuneGetHitChance(defender, attacker, avoidance_override);
Message(0, "###################START###################");
Message(0, "[#Tune] DEFENDER Name: %s", defender->GetCleanName());
Message(0, "[#Tune] DEFENDER Chance to be missed: %.0f pct", (100.0f - round(tmp_hit_chance)));
Message(0, "[#Tune] DEFENDER Avoidance: %i ", TuneGetAvoidance(defender, attacker, avoidance_override));
Message(0, "[#Tune] ATTACKER Name: %s", attacker->GetCleanName());
Message(0, "[#Tune] ATTACKER Chance to hit: %.0f pct", round(tmp_hit_chance));
Message(0, "[#Tune] ATTACKER Accuracy: %i ", TuneGetAccuracy(defender, attacker));
Message(0, "##########################################");
Message(0, "[#Tune] Begin Parse [Interval %i Max Loop Iterations %i]", interval, max_loop);
if (tmp_hit_chance > hit_chance) {
interval = interval * -1;
Message(0, "[#Tune] NOTE: Attackers 'ACCURACY' must be LOWERED due to attackers ( %.0f pct ) chance to hit being less than the desired ( %.0f pct )", tmp_hit_chance, hit_chance);
}
Message(0, "[#Tune] - Processing... Find Accuracy needed on attacker for a ( %.0f pct ) hit chance on defender. Base attacker hit chance ( %.0f pct ). ", hit_chance, tmp_hit_chance);
for (int j = 0; j < max_loop; j++)
{
tmp_hit_chance = TuneGetHitChance(defender, attacker, avoidance_override, 0, 0, loop_add_accuracy);
if (Msg >= 3)
{
Message(0, "[#Tune] - Processing... [%i] ACCURACY %i | Hit Chance %.2f ", j, loop_add_accuracy, tmp_hit_chance);
}
if (interval > 0 && tmp_hit_chance >= hit_chance)
{
end = true;
}
else if (interval < 0 && tmp_hit_chance <= hit_chance)
{
end = true;
}
if (end) {
Message(0, "###################RESULTS###################");
if (avoidance_override) {
Message(0, "[#Tune] AVOIDANCE STAT OVERRRIDE. This is the amount of ACCURACY adjustment needed if this defender had ( %i ) raw AVOIDANCE stat", avoidance_override);
}
if (attacker->IsNPC()) {
Message(0, "[#Tune] Recommended NPC ACCURACY ADJUSTMENT of ( %i ) on ' %s ' will result in ( %.0f pct ) chance to hit ' %s '.", loop_add_accuracy, attacker->GetCleanName(), hit_chance, defender->GetCleanName());
Message(0, "[#Tune] SET NPC 'ACCURACY' stat value = [ %i ]", loop_add_accuracy + attacker->CastToNPC()->GetAccuracyRating());
Message(0, "###################COMPLETE###################");
}
else if (attacker->IsClient()) {
Message(0, "[#Tune] Recommended CLIENT ACCURACY ADJUSTMENT of ( %i ) on %s ' will result in ( %.0f pct ) chance to hit ' %s '.", loop_add_accuracy, attacker->GetCleanName(), hit_chance, defender->GetCleanName());
if (loop_add_accuracy >= 0) {
Message(0, "[#Tune] MODIFY Client Accuracy Mod2 stat or SPA 216 Melee Accuracy (spells/items/aa) [+ %i ]", loop_add_accuracy);
}
else {
Message(0, "[#Tune] Give Client Accuracy Mod2 stat or SPA 216 Melee Accuracy (spells/items/aa) of [ %i ]", loop_add_accuracy);
}
Message(0, "###################COMPLETE###################");
}
return;
}
loop_add_accuracy = loop_add_accuracy + interval;
}
Message(0, "###################ABORT#######################");
Message(0, "[#Tune] Error: Unable to find desired result for ( %.0f pct) - Increase interval (%i) AND/OR max loop value (%i) and run again.", hit_chance, interval, max_loop);
Message(0, "[#Tune] Parse ended at ACCURACY ADJUSTMENT of ( %i ) on ' %s ' will result in ( %.0f pct ) chance to hit ' %s '.", loop_add_accuracy, attacker->GetCleanName(), hit_chance, defender->GetCleanName());
Message(0, "###################COMPLETE###################");
}
/*
Tune support functions
*/
int64 Mob::TuneClientGetMeanDamage(Mob* other, int ac_override, int atk_override, int add_ac, int add_atk)
{
uint32 total_damage = 0;
int loop_max = 1000;
for (int i = 0; i < loop_max; i++)
{
if (IsClient()) {
total_damage += TuneClientAttack(other, true, true, 10000, ac_override, atk_override, add_ac, add_atk);
}
else {
total_damage += TuneNPCAttack(other, true, true, 10000, ac_override, atk_override, add_ac, add_atk);
}
}
return(total_damage / loop_max);
}
int64 Mob::TuneClientGetMaxDamage(Mob* other)
{
uint32 max_hit = 0;
uint32 current_hit = 0;
int loop_max = 1000;
for (int i = 0; i < loop_max; i++)
{
if (IsClient()) {
current_hit = TuneClientAttack(other, true, true, 10000, 1, 10000);
}
else {
current_hit = TuneNPCAttack(other, true, true, 10000, 1, 10000);
}
if (current_hit > max_hit) {
max_hit = current_hit;
}
}
return(max_hit);
}
int64 Mob::TuneClientGetMinDamage(Mob* other, int max_hit)
{
uint32 min_hit = max_hit;
uint32 current_hit = 0;
int loop_max = 1000;
for (int i = 0; i < loop_max; i++)
{
if (IsClient()) {
current_hit = TuneClientAttack(other, true, true, 10000, 10000, 1);
}
else {
current_hit = TuneNPCAttack(other, true, true, 10000, 10000, 1);
}
if (current_hit < min_hit) {
min_hit = current_hit;
}
}
return(min_hit);
}
float Mob::TuneGetACMitigationPct(Mob* defender, Mob *attacker) {
int max_damage = 0;
int min_damage = 0;
max_damage = attacker->TuneClientGetMaxDamage(defender);
min_damage = attacker->TuneClientGetMinDamage(defender, max_damage);
if (!max_damage)
{
Message(0, "[#Tune] Calculation Failure. Error: [Mob::TuneGetACMitigationPct] No max damage found");
return max_damage;
}
int mean_dmg = attacker->TuneClientGetMeanDamage(defender);
float tmp_pct_mitigated = 100.0f - (static_cast<float>(mean_dmg) * 100.0f / static_cast<float>(max_damage));
return tmp_pct_mitigated;
}
int64 Mob::TuneGetOffense(Mob* defender, Mob *attacker, int atk_override)
{
int offense_rating = 0;
if (attacker->IsClient()) {
offense_rating = attacker->TuneClientAttack(defender, true, true, 0, 0, atk_override, 0, 0, true);
}
else {
offense_rating = attacker->TuneNPCAttack(defender, true, true, 0, 0, atk_override, 0, 0, true);
}
return offense_rating;
}
int64 Mob::TuneGetAccuracy(Mob* defender, Mob *attacker, int accuracy_override, int add_accuracy)
{
int accuracy = 0;
if (attacker->IsClient()) {
accuracy = attacker->TuneClientAttack(defender, true, true, 0, 0, 0, 0, 0, false, true,0,accuracy_override,0,add_accuracy);
}
else {
accuracy = attacker->TuneNPCAttack(defender, true, true, 0, 0, 0, 0, 0, false, true, 0, accuracy_override, 0, add_accuracy);
}
return accuracy;
}
int64 Mob::TuneGetAvoidance(Mob* defender, Mob *attacker, int avoidance_override, int add_avoidance)
{
return defender->TuneGetTotalDefense(avoidance_override, add_avoidance);
}
float Mob::TuneGetHitChance(Mob* defender, Mob *attacker, int avoidance_override, int accuracy_override, int add_avoidance, int add_accuracy)
{
uint32 hit_count = 0;
uint32 current_hit = 0;
int loop_max = 2000;
for (int i = 0; i < loop_max; i++)
{
if (attacker->IsClient()) {
current_hit = attacker->TuneClientAttack(defender, true, false, 0, 0, 0, 0, 0, false, false, avoidance_override, accuracy_override, add_avoidance, add_accuracy);
}
else {
current_hit = attacker->TuneNPCAttack(defender, true, false, 0, 0, 0, 0, 0, false, false, avoidance_override, accuracy_override, add_avoidance, add_accuracy);
}
if (current_hit > 0) {
hit_count++;
}
}
float chance = (static_cast<float>(hit_count) / 2000.0f) * 100.0f;
return chance;
}
float Mob::TuneGetAvoidMeleeChance(Mob* defender, Mob *attacker, int type)
{
uint32 current_hit = 0;
uint32 hit_count = 0;
/*
-1 - block
-2 - parry
-3 - riposte
-4 - dodge
*/
int loop_max = 3000;
for (int i = 0; i < loop_max; i++)
{
if (attacker->IsClient()) {
current_hit = attacker->TuneClientAttack(defender, false, true, 0);
}
else {
current_hit = attacker->TuneNPCAttack(defender, false, true, 0);
}
if (current_hit == type) {
hit_count++;
}
}
float chance = (static_cast<float>(hit_count) / 3000.0f) * 100.0f;
return chance;
}
int64 Mob::TuneCalcEvasionBonus(int final_avoidance, int base_avoidance) {
/*
float eb = static_cast<float>(final_avoidance) / static_cast<float>(base_avoidance);
Shout(" eb %.2f ", eb);
eb = eb * 100.f;
Shout(" eb %.2f ", eb);
eb = eb - 100.0f;
Shout(" eb %.2f ", eb);
return eb;
*/
int loop_max = 5000;
int evasion_bonus = 10;
int current_avoidance = 0;
int interval = 1;
if (base_avoidance > final_avoidance)
{
interval = interval * -1;
}
for (int i = 0; i < loop_max; i++)
{
current_avoidance = (base_avoidance * (100 + evasion_bonus)) / 100;
if (interval > 0 && current_avoidance >= final_avoidance)
{
return evasion_bonus;
}
else if (interval < 0 && current_avoidance <= final_avoidance)
{
return evasion_bonus;
}
evasion_bonus = evasion_bonus + interval;
}
return 0;
}
/*
Calculate from modified attack.cpp functions.
*/
int64 Mob::TuneNPCAttack(Mob* other, bool no_avoid, bool no_hit_chance, int hit_chance_bonus, int ac_override, int atk_override, int add_ac, int add_atk, bool get_offense, bool get_accuracy,
int avoidance_override, int accuracy_override, int add_avoidance, int add_accuracy)
{
if (!IsNPC()) {
Message(Chat::Red, "#Tune Failure: A null NON NPC object was passed to TuneNPCAttack() for evaluation!");
return false;
}
if (!other) {
Message(Chat::Red, "#Tune Failure: A null Mob object was passed to TuneNPCAttack() for evaluation!");
return false;
}
//Check that we can attack before we calc heading and face our target
if (!IsAttackAllowed(other)) {
Message(Chat::Red, "#Tune Failure: This NPC can not attack this target!");
return false;
}
DamageHitInfo my_hit;
my_hit.skill = EQ::skills::SkillHandtoHand;
my_hit.hand = EQ::invslot::slotPrimary;
my_hit.damage_done = 1;
my_hit.skill = static_cast<EQ::skills::SkillType>(CastToNPC()->GetPrimSkill());
OffHandAtk(false);
uint8 otherlevel = other->GetLevel();
uint8 mylevel = GetLevel();
otherlevel = otherlevel ? otherlevel : 1;
mylevel = mylevel ? mylevel : 1;
my_hit.base_damage = CastToNPC()->GetBaseDamage();
my_hit.min_damage = CastToNPC()->GetMinDamage();
//int32 hate = my_hit.base_damage + my_hit.min_damage;
my_hit.offense = Tuneoffense(my_hit.skill, atk_override, add_atk);
if (get_offense) {
return my_hit.offense;
}
my_hit.tohit = TuneGetTotalToHit(my_hit.skill, hit_chance_bonus, accuracy_override, add_accuracy);
if (get_accuracy) {
return my_hit.tohit;
}
TuneDoAttack(other, my_hit, nullptr, no_avoid, no_hit_chance, ac_override, add_ac, avoidance_override, accuracy_override, add_avoidance, add_accuracy);
LogCombat("Final damage against [{}]: [{}]", other->GetName(), my_hit.damage_done);
if (other->IsClient() && IsPet() && GetOwner()->IsClient()) {
//pets do half damage to clients in pvp
my_hit.damage_done /= 2;
if (my_hit.damage_done < 1)
my_hit.damage_done = 1;
}
return my_hit.damage_done;
}
int64 Mob::TuneClientAttack(Mob* other, bool no_avoid, bool no_hit_chance, int hit_chance_bonus, int ac_override, int atk_override, int add_ac, int add_atk, bool get_offense, bool get_accuracy,
int avoidance_override, int accuracy_override, int add_avoidance, int add_accuracy)
{
if (!IsClient()) {
Message(Chat::Red, "#Tune Failure: A null NON CLIENT object was passed to TuneClientAttack() for evaluation!");
return false;
}
if (!other) {
Message(Chat::Red, "#Tune Failure: A null Mob object was passed to TuneClientAttack() for evaluation!");
return false;
}
int Hand = EQ::invslot::slotPrimary;
EQ::ItemInstance* weapon = nullptr;
weapon = CastToClient()->GetInv().GetItem(EQ::invslot::slotPrimary);
OffHandAtk(false);
if (weapon != nullptr) {
if (!weapon->IsWeapon()) {
Message(Chat::Red, "#Tune Failure: Attack cancelled, Item %s is not a weapon!", weapon->GetItem()->Name);
return(false);
}
}
DamageHitInfo my_hit;
my_hit.skill = TuneAttackAnimation(Hand, weapon);
// Now figure out damage
my_hit.damage_done = 1;
my_hit.min_damage = 0;
uint8 mylevel = GetLevel() ? GetLevel() : 1;
int64 hate = 0;
if (weapon)
hate = (weapon->GetItem()->Damage + weapon->GetItem()->ElemDmgAmt);
my_hit.base_damage = GetWeaponDamage(other, weapon, &hate);
if (hate == 0 && my_hit.base_damage > 1)
hate = my_hit.base_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 (my_hit.base_damage > 0) {
// if we revamp this function be more general, we will have to make sure this isn't
// executed for anything BUT normal melee damage weapons from auto attack
if (Hand == EQ::invslot::slotPrimary || Hand == EQ::invslot::slotSecondary)
my_hit.base_damage = CastToClient()->DoDamageCaps(my_hit.base_damage);
auto shield_inc = spellbonuses.ShieldEquipDmgMod + itembonuses.ShieldEquipDmgMod + aabonuses.ShieldEquipDmgMod;
if (shield_inc > 0 && HasShieldEquiped() && Hand == EQ::invslot::slotPrimary) {
my_hit.base_damage = my_hit.base_damage * (100 + shield_inc) / 100;
hate = hate * (100 + shield_inc) / 100;
}
// ***************************************************************
// *** Calculate the damage bonus, if applicable, for this hit ***
// ***************************************************************
int ucDamageBonus = 0;
if (Hand == EQ::invslot::slotPrimary && GetLevel() >= 28 && IsWarriorClass())
{
// Damage bonuses apply only to hits from the main hand (Hand == MainPrimary) by characters level 28 and above
// who belong to a melee class. If we're here, then all of these conditions apply.
ucDamageBonus = GetWeaponDamageBonus(weapon ? weapon->GetItem() : (const EQ::ItemData*) nullptr);
my_hit.min_damage = ucDamageBonus;
hate += ucDamageBonus;
}
my_hit.offense = Tuneoffense(my_hit.skill, atk_override, add_atk); // we need this a few times
if (get_offense) {
return my_hit.offense;
}
my_hit.hand = Hand;
my_hit.tohit = TuneGetTotalToHit(my_hit.skill, hit_chance_bonus, accuracy_override, add_accuracy);
if (get_accuracy) {
return my_hit.tohit;
}
TuneDoAttack(other, my_hit, nullptr, no_avoid, no_hit_chance, ac_override, add_ac, avoidance_override, accuracy_override, add_avoidance, add_accuracy);
}
else {
my_hit.damage_done = DMG_INVULNERABLE;
}
///////////////////////////////////////////////////////////
////// Send Attack Damage
///////////////////////////////////////////////////////////
//other->Damage(this, my_hit.damage_done, SPELL_UNKNOWN, my_hit.skill, true, -1, false, m_specialattacks);
return my_hit.damage_done;
}
void Mob::TuneDoAttack(Mob *other, DamageHitInfo &hit, ExtraAttackOptions *opts, bool no_avoid, bool no_hit_chance, int ac_override, int add_ac,
int avoidance_override, int accuracy_override, int add_avoidance, int add_accuracy)
{
if (!other)
return;
LogCombat("[{}]::DoAttack vs [{}] base [{}] min [{}] offense [{}] tohit [{}] skill [{}]", GetName(),
other->GetName(), hit.base_damage, hit.min_damage, hit.offense, hit.tohit, hit.skill);
// check to see if we hit..
if (no_avoid || (!no_avoid && other->AvoidDamage(this, hit))) {
int strike_through = itembonuses.StrikeThrough + spellbonuses.StrikeThrough + aabonuses.StrikeThrough;
if (strike_through && zone->random.Roll(strike_through)) {
MessageString(Chat::StrikeThrough,
STRIKETHROUGH_STRING); // You strike through your opponents defenses!
hit.damage_done = 1; // set to one, we will check this to continue
}
// I'm pretty sure you can riposte a riposte
if (hit.damage_done == DMG_RIPOSTED) {
//DoRiposte(other); //Disabled for TUNE
//if (IsDead())
return;
}
LogCombat("Avoided/strikethrough damage with code [{}]", hit.damage_done);
}
if (hit.damage_done >= 0) {
if (no_hit_chance || (!no_hit_chance && other->TuneCheckHitChance(this, hit, avoidance_override, add_avoidance))) {
other->TuneMeleeMitigation(this, hit, ac_override, add_ac);
if (hit.damage_done > 0) {
ApplyDamageTable(hit);
TuneCommonOutgoingHitSuccess(other, hit, opts);
}
LogCombat("Final damage after all reductions: [{}]", hit.damage_done);
}
else {
LogCombat("Attack missed. Damage set to 0");
hit.damage_done = 0;
}
}
}
void Mob::TuneMeleeMitigation(Mob *attacker, DamageHitInfo &hit, int ac_override, int add_ac)
{
if (hit.damage_done < 0 || hit.base_damage == 0)
return;
Mob* defender = this;
//auto mitigation = defender->GetMitigationAC();
auto mitigation = defender->TuneACSum(false, ac_override, add_ac);
if (IsClient() && attacker->IsClient())
mitigation = mitigation * 80 / 100; // 2004 PvP changes
auto roll = RollD20(hit.offense, mitigation);
// +0.5 for rounding, min to 1 dmg
hit.damage_done = std::max(static_cast<int>(roll * static_cast<double>(hit.base_damage) + 0.5), 1);
}
int64 Mob::TuneACSum(bool skip_caps, int ac_override, int add_ac)
{
int ac = 0; // this should be base AC whenever shrouds come around
ac += itembonuses.AC; // items + food + tribute
if (IsClient()) {
if (ac_override) {
ac = ac_override;
}
if (add_ac) {
ac += add_ac;
}
}
int shield_ac = 0;
if (HasShieldEquiped() && IsClient()) {
auto client = CastToClient();
auto inst = client->GetInv().GetItem(EQ::invslot::slotSecondary);
if (inst) {
if (inst->GetItemRecommendedLevel(true) <= GetLevel())
shield_ac = inst->GetItemArmorClass(true);
else
shield_ac = client->CalcRecommendedLevelBonus(GetLevel(), inst->GetItemRecommendedLevel(true), inst->GetItemArmorClass(true));
}
shield_ac += client->GetHeroicSTR() / 10;
}
// EQ math
ac = (ac * 4) / 3;
// anti-twink
if (!skip_caps && IsClient() && GetLevel() < RuleI(Combat, LevelToStopACTwinkControl))
ac = std::min(ac, 25 + 6 * GetLevel());
ac = std::max(0, ac + GetClassRaceACBonus());
if (IsNPC()) {
// This is the developer tweaked number
// for the VAST amount of NPCs in EQ this number didn't exceed 600 until recently (PoWar)
// According to the guild hall Combat Dummies, a level 50 classic EQ mob it should be ~115
// For a 60 PoP mob ~120, 70 OoW ~120
if (ac_override) {
ac += ac_override;
}
else {
ac += GetAC();
}
ac += add_ac;
ac += GetPetACBonusFromOwner();
auto spell_aa_ac = aabonuses.AC + spellbonuses.AC;
ac += GetSkill(EQ::skills::SkillDefense) / 5;
if (EQ::ValueWithin(static_cast<int>(GetClass()), NECROMANCER, ENCHANTER))
ac += spell_aa_ac / 3;
else
ac += spell_aa_ac / 4;
}
else { // TODO: so we can't set NPC skills ... so the skill bonus ends up being HUGE so lets nerf them a bit
auto spell_aa_ac = aabonuses.AC + spellbonuses.AC;
if (EQ::ValueWithin(static_cast<int>(GetClass()), NECROMANCER, ENCHANTER))
ac += GetSkill(EQ::skills::SkillDefense) / 2 + spell_aa_ac / 3;
else
ac += GetSkill(EQ::skills::SkillDefense) / 3 + spell_aa_ac / 4;
}
if (GetAGI() > 70)
ac += GetAGI() / 20;
if (ac < 0)
ac = 0;
if (!skip_caps && (IsClient())) {
auto softcap = GetACSoftcap();
auto returns = GetSoftcapReturns();
int total_aclimitmod = aabonuses.CombatStability + itembonuses.CombatStability + spellbonuses.CombatStability;
if (total_aclimitmod)
softcap = (softcap * (100 + total_aclimitmod)) / 100;
softcap += shield_ac;
if (ac > softcap) {
auto over_cap = ac - softcap;
ac = softcap + (over_cap * returns);
}
}
return ac;
}
int64 Mob::Tuneoffense(EQ::skills::SkillType skill, int atk_override, int add_atk)
{
int offense = GetSkill(skill);
int stat_bonus = GetSTR();
switch (skill) {
case EQ::skills::SkillArchery:
case EQ::skills::SkillThrowing:
stat_bonus = GetDEX();
break;
// Mobs with no weapons default to H2H.
// Since H2H is capped at 100 for many many classes,
// lets not handicap mobs based on not spawning with a
// weapon.
//
// Maybe we tweak this if Disarm is actually implemented.
case EQ::skills::SkillHandtoHand:
offense = GetBestMeleeSkill();
break;
}
if (stat_bonus >= 75)
offense += (2 * stat_bonus - 150) / 3;
int32 tune_atk = GetATK();
if (atk_override) {
tune_atk = atk_override;
}
tune_atk += add_atk;
offense += tune_atk + GetPetATKBonusFromOwner();
return offense;
}
EQ::skills::SkillType Mob::TuneAttackAnimation(int Hand, const EQ::ItemInstance* weapon, EQ::skills::SkillType skillinuse)
{
// Determine animation
int type = 0;
if (weapon && weapon->IsClassCommon()) {
const EQ::ItemData* item = weapon->GetItem();
switch (item->ItemType) {
case EQ::item::ItemType1HSlash: // 1H Slashing
skillinuse = EQ::skills::Skill1HSlashing;
type = anim1HWeapon;
break;
case EQ::item::ItemType2HSlash: // 2H Slashing
skillinuse = EQ::skills::Skill2HSlashing;
type = anim2HSlashing;
break;
case EQ::item::ItemType1HPiercing: // Piercing
skillinuse = EQ::skills::Skill1HPiercing;
type = anim1HPiercing;
break;
case EQ::item::ItemType1HBlunt: // 1H Blunt
skillinuse = EQ::skills::Skill1HBlunt;
type = anim1HWeapon;
break;
case EQ::item::ItemType2HBlunt: // 2H Blunt
skillinuse = EQ::skills::Skill2HBlunt;
type = RuleB(Combat, Classic2HBAnimation) ? anim2HWeapon : anim2HSlashing;
break;
case EQ::item::ItemType2HPiercing: // 2H Piercing
if (IsClient() && CastToClient()->ClientVersion() < EQ::versions::ClientVersion::RoF2)
skillinuse = EQ::skills::Skill1HPiercing;
else
skillinuse = EQ::skills::Skill2HPiercing;
type = anim2HWeapon;
break;
case EQ::item::ItemTypeMartial:
skillinuse = EQ::skills::SkillHandtoHand;
type = animHand2Hand;
break;
default:
skillinuse = EQ::skills::SkillHandtoHand;
type = animHand2Hand;
break;
}// switch
}
else if (IsNPC()) {
switch (skillinuse) {
case EQ::skills::Skill1HSlashing: // 1H Slashing
type = anim1HWeapon;
break;
case EQ::skills::Skill2HSlashing: // 2H Slashing
type = anim2HSlashing;
break;
case EQ::skills::Skill1HPiercing: // Piercing
type = anim1HPiercing;
break;
case EQ::skills::Skill1HBlunt: // 1H Blunt
type = anim1HWeapon;
break;
case EQ::skills::Skill2HBlunt: // 2H Blunt
type = anim2HSlashing; //anim2HWeapon
break;
case EQ::skills::Skill2HPiercing: // 2H Piercing
type = anim2HWeapon;
break;
case EQ::skills::SkillHandtoHand:
type = animHand2Hand;
break;
default:
type = animHand2Hand;
break;
}// switch
}
else {
skillinuse = EQ::skills::SkillHandtoHand;
type = animHand2Hand;
}
// If we're attacking with the secondary hand, play the dual wield anim
if (Hand == EQ::invslot::slotSecondary) // DW anim
type = animDualWield;
return skillinuse;
}
int64 Mob::Tunecompute_tohit(EQ::skills::SkillType skillinuse, int accuracy_override, int add_accuracy)
{
int tohit = GetSkill(EQ::skills::SkillOffense) + 7;
tohit += GetSkill(skillinuse);
if (IsNPC()) {
if (accuracy_override) {
tohit += accuracy_override;
}
else {
tohit += CastToNPC()->GetAccuracyRating();
}
tohit += add_accuracy;
}
if (IsClient()) {
double reduction = CastToClient()->m_pp.intoxication / 2.0;
if (reduction > 20.0) {
reduction = std::min((110 - reduction) / 100.0, 1.0);
tohit = reduction * static_cast<double>(tohit);
}
else if (IsBerserk()) {
tohit += (GetLevel() * 2) / 5;
}
}
return std::max(tohit, 1);
}
// return -1 in cases that always hit
int64 Mob::TuneGetTotalToHit(EQ::skills::SkillType skill, int chance_mod, int accuracy_override, int add_accuracy)
{
if (chance_mod >= 10000) // override for stuff like SE_SkillAttack
return -1;
// calculate attacker's accuracy
auto accuracy = Tunecompute_tohit(skill, accuracy_override, add_accuracy) + 10; // add 10 in case the NPC's stats are fucked
if (chance_mod > 0) // multiplier
accuracy *= chance_mod;
// Torven parsed an apparent constant of 1.2 somewhere in here * 6 / 5 looks eqmathy to me!
// new test clients have 121 / 100
accuracy = (accuracy * 121) / 100;
// unsure on the stacking order of these effects, rather hard to parse
// item mod2 accuracy isn't applied to range? Theory crafting and parses back it up I guess
// mod2 accuracy -- flat bonus
if (skill != EQ::skills::SkillArchery && skill != EQ::skills::SkillThrowing)
accuracy += itembonuses.HitChance;
//518 Increase ATK accuracy by percentage, stackable
auto atkhit_bonus = itembonuses.Attack_Accuracy_Max_Percent + aabonuses.Attack_Accuracy_Max_Percent + spellbonuses.Attack_Accuracy_Max_Percent;
if (atkhit_bonus)
accuracy += round(static_cast<double>(accuracy) * static_cast<double>(atkhit_bonus) * 0.0001);
// 216 Melee Accuracy Amt aka SE_Accuracy -- flat bonus
accuracy += itembonuses.Accuracy[EQ::skills::HIGHEST_SKILL + 1] +
aabonuses.Accuracy[EQ::skills::HIGHEST_SKILL + 1] +
spellbonuses.Accuracy[EQ::skills::HIGHEST_SKILL + 1] +
itembonuses.Accuracy[skill] +
aabonuses.Accuracy[skill] +
spellbonuses.Accuracy[skill];
if (IsClient()) {
if (accuracy_override) {
accuracy = accuracy_override;
}
accuracy += add_accuracy;
}
// auto hit discs (and looks like there are some autohit AAs)
if (spellbonuses.HitChanceEffect[skill] >= 10000 || aabonuses.HitChanceEffect[skill] >= 10000)
return -1;
if (spellbonuses.HitChanceEffect[EQ::skills::HIGHEST_SKILL + 1] >= 10000)
return -1;
// 184 Accuracy % aka SE_HitChance -- percentage increase
auto hit_bonus = itembonuses.HitChanceEffect[EQ::skills::HIGHEST_SKILL + 1] +
aabonuses.HitChanceEffect[EQ::skills::HIGHEST_SKILL + 1] +
spellbonuses.HitChanceEffect[EQ::skills::HIGHEST_SKILL + 1] +
itembonuses.HitChanceEffect[skill] +
aabonuses.HitChanceEffect[skill] +
spellbonuses.HitChanceEffect[skill];
accuracy = (accuracy * (100 + hit_bonus)) / 100;
return accuracy;
}
// return -1 in cases that always miss
int64 Mob::TuneGetTotalDefense(int avoidance_override, int add_avoidance)
{
auto avoidance = Tunecompute_defense(avoidance_override, add_avoidance) + 10; // add 10 in case the NPC's stats are fucked
auto evasion_bonus = spellbonuses.AvoidMeleeChanceEffect; // we check this first since it has a special case
if (evasion_bonus >= 10000)
return -1;
// 515 SE_AC_Avoidance_Max_Percent
auto ac_aviodance_bonus = itembonuses.AC_Avoidance_Max_Percent + aabonuses.AC_Avoidance_Max_Percent + spellbonuses.AC_Avoidance_Max_Percent;
if (ac_aviodance_bonus)
avoidance += round(static_cast<double>(avoidance) * static_cast<double>(ac_aviodance_bonus) * 0.0001);
// 172 Evasion aka SE_AvoidMeleeChance
evasion_bonus += itembonuses.AvoidMeleeChanceEffect + aabonuses.AvoidMeleeChanceEffect; // item bonus here isn't mod2 avoidance
// 215 Pet Avoidance % aka SE_PetAvoidance
evasion_bonus += GetPetAvoidanceBonusFromOwner();
// Evasion is a percentage bonus according to AA descriptions
if (evasion_bonus)
avoidance = (avoidance * (100 + evasion_bonus)) / 100;
return avoidance;
}
int64 Mob::Tunecompute_defense(int avoidance_override, int add_avoidance)
{
int defense = GetSkill(EQ::skills::SkillDefense) * 400 / 225;
defense += (8000 * (GetAGI() - 40)) / 36000;
if (IsClient()) {
if (avoidance_override) {
defense = avoidance_override;
}
else {
defense += CastToClient()->GetHeroicAGI() / 10;
}
defense += add_avoidance; //1 pt = 10 heroic agi
}
//516 SE_AC_Mitigation_Max_Percent
auto ac_bonus = itembonuses.AC_Mitigation_Max_Percent + aabonuses.AC_Mitigation_Max_Percent + spellbonuses.AC_Mitigation_Max_Percent;
if (ac_bonus)
defense += round(static_cast<double>(defense) * static_cast<double>(ac_bonus) * 0.0001);
defense += itembonuses.AvoidMeleeChance; // item mod2
if (IsNPC()) {
if (avoidance_override) {
defense += avoidance_override;
}
else {
defense += CastToNPC()->GetAvoidanceRating();
}
defense += add_avoidance;
}
if (IsClient()) {
double reduction = CastToClient()->m_pp.intoxication / 2.0;
if (reduction > 20.0) {
reduction = std::min((110 - reduction) / 100.0, 1.0);
defense = reduction * static_cast<double>(defense);
}
}
return std::max(1, defense);
}
// 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::TuneCheckHitChance(Mob* other, DamageHitInfo &hit, int avoidance_override, int add_avoidance)
{
Mob *attacker = other;
Mob *defender = this;
if (defender->IsClient() && defender->CastToClient()->IsSitting())
return true;
auto avoidance = defender->TuneGetTotalDefense(avoidance_override, add_avoidance);
if (avoidance == -1) // some sort of auto avoid disc
return false;
auto accuracy = hit.tohit;
if (accuracy == -1)
return true;
// so now we roll!
// relevant dev quote:
// Then your chance to simply avoid the attack is checked (defender's avoidance roll beat the attacker's accuracy roll.)
int tohit_roll = zone->random.Roll0(accuracy);
int avoid_roll = zone->random.Roll0(avoidance);
// tie breaker? Don't want to be biased any one way
if (tohit_roll == avoid_roll)
return zone->random.Roll(50);
return tohit_roll > avoid_roll;
}
void Mob::TuneCommonOutgoingHitSuccess(Mob* defender, DamageHitInfo &hit, ExtraAttackOptions *opts)
{
if (!defender)
return;
#ifdef LUA_EQEMU
bool ignoreDefault = false;
LuaParser::Instance()->CommonOutgoingHitSuccess(this, defender, hit, opts, ignoreDefault);
if (ignoreDefault) {
return;
}
#endif
// BER weren't parsing the halving
if (hit.skill == EQ::skills::SkillArchery ||
(hit.skill == EQ::skills::SkillThrowing && GetClass() != BERSERKER))
hit.damage_done /= 2;
if (hit.damage_done < 1)
hit.damage_done = 1;
if (hit.skill == EQ::skills::SkillArchery) {
int bonus = aabonuses.ArcheryDamageModifier + itembonuses.ArcheryDamageModifier + spellbonuses.ArcheryDamageModifier;
hit.damage_done += hit.damage_done * bonus / 100;
int headshot = TryHeadShot(defender, hit.skill);
if (headshot > 0) {
hit.damage_done = headshot;
}
else if (GetClass() == RANGER && GetLevel() > 50) { // no double dmg on headshot
if ((defender->IsNPC() && !defender->IsMoving() && !defender->IsRooted()) || !RuleB(Combat, ArcheryBonusRequiresStationary)) {
hit.damage_done *= 2;
MessageString(Chat::MeleeCrit, BOW_DOUBLE_DAMAGE);
}
}
}
int extra_mincap = 0;
int min_mod = hit.base_damage * GetMeleeMinDamageMod_SE(hit.skill) / 100;
if (hit.skill == EQ::skills::SkillBackstab) {
extra_mincap = GetLevel() < 7 ? 7 : GetLevel();
if (GetLevel() >= 60)
extra_mincap = GetLevel() * 2;
else if (GetLevel() > 50)
extra_mincap = GetLevel() * 3 / 2;
if (IsSpecialAttack(eSpecialAttacks::ChaoticStab)) {
hit.damage_done = extra_mincap;
}
else {
int ass = TryAssassinate(defender, hit.skill);
if (ass > 0)
hit.damage_done = ass;
}
}
else if (hit.skill == EQ::skills::SkillFrenzy && GetClass() == BERSERKER && GetLevel() > 50) {
extra_mincap = 4 * GetLevel() / 5;
}
// this has some weird ordering
// Seems the crit message is generated before some of them :P
// worn item +skill dmg, SPA 220, 418. Live has a normalized version that should be here too
hit.min_damage += GetSkillDmgAmt(hit.skill) + GetPositionalDmgAmt(defender);
// shielding mod2
if (defender->itembonuses.MeleeMitigation)
hit.min_damage -= hit.min_damage * defender->itembonuses.MeleeMitigation / 100;
ApplyMeleeDamageMods(hit.skill, hit.damage_done, defender, opts);
min_mod = std::max(min_mod, extra_mincap);
if (min_mod && hit.damage_done < min_mod) // SPA 186
hit.damage_done = min_mod;
hit.damage_done += hit.min_damage;
if (IsClient()) {
int extra = 0;
switch (hit.skill) {
case EQ::skills::SkillThrowing:
case EQ::skills::SkillArchery:
extra = CastToClient()->GetHeroicDEX() / 10;
break;
default:
extra = CastToClient()->GetHeroicSTR() / 10;
break;
}
hit.damage_done += extra;
}
// this appears where they do special attack dmg mods
int spec_mod = 0;
if (IsSpecialAttack(eSpecialAttacks::Rampage)) {
int mod = GetSpecialAbilityParam(SPECATK_RAMPAGE, 2);
if (mod > 0)
spec_mod = mod;
if ((IsPet() || IsTempPet()) && IsPetOwnerClient()) {
//SE_PC_Pet_Rampage SPA 464 on pet, damage modifier
int spell_mod = spellbonuses.PC_Pet_Rampage[SBIndex::PET_RAMPAGE_DMG_MOD] + itembonuses.PC_Pet_Rampage[SBIndex::PET_RAMPAGE_DMG_MOD] + aabonuses.PC_Pet_Rampage[SBIndex::PET_RAMPAGE_DMG_MOD];
if (spell_mod > spec_mod)
spec_mod = spell_mod;
}
}
else if (IsSpecialAttack(eSpecialAttacks::AERampage)) {
int mod = GetSpecialAbilityParam(SPECATK_AREA_RAMPAGE, 2);
if (mod > 0)
spec_mod = mod;
if ((IsPet() || IsTempPet()) && IsPetOwnerClient()) {
//SE_PC_Pet_AE_Rampage SPA 465 on pet, damage modifier
int spell_mod = spellbonuses.PC_Pet_AE_Rampage[SBIndex::PET_RAMPAGE_DMG_MOD] + itembonuses.PC_Pet_AE_Rampage[SBIndex::PET_RAMPAGE_DMG_MOD] + aabonuses.PC_Pet_AE_Rampage[SBIndex::PET_RAMPAGE_DMG_MOD];
if (spell_mod > spec_mod)
spec_mod = spell_mod;
}
}
if (spec_mod > 0)
hit.damage_done = (hit.damage_done * spec_mod) / 100;
int pct_damage_reduction = defender->GetSkillDmgTaken(hit.skill, opts) + defender->GetPositionalDmgTaken(this);
hit.damage_done += (hit.damage_done * pct_damage_reduction / 100) + defender->GetPositionalDmgTakenAmt(this);
if (defender->GetShielderID()) {
DoShieldDamageOnShielder(defender, hit.damage_done, hit.skill);
hit.damage_done -= hit.damage_done * defender->GetShieldTargetMitigation() / 100; //Default shielded takes 50 pct damage
}
}