diff --git a/zone/attack.cpp b/zone/attack.cpp index c1a46fbac..071ae327e 100644 --- a/zone/attack.cpp +++ b/zone/attack.cpp @@ -59,6 +59,7 @@ extern FastMath g_Math; extern EntityList entity_list; extern Zone* zone; +//SYNC WITH: tune.cpp, mob.h TuneAttackAnimation EQ::skills::SkillType Mob::AttackAnimation(int Hand, const EQ::ItemInstance* weapon, EQ::skills::SkillType skillinuse) { // Determine animation @@ -163,7 +164,7 @@ EQ::skills::SkillType Mob::AttackAnimation(int Hand, const EQ::ItemInstance* wea return skillinuse; } - +//SYNC WITH: tune.cpp, mob.h Tunecompute_tohit int Mob::compute_tohit(EQ::skills::SkillType skillinuse) { int tohit = GetSkill(EQ::skills::SkillOffense) + 7; @@ -184,6 +185,7 @@ int Mob::compute_tohit(EQ::skills::SkillType skillinuse) } // return -1 in cases that always hit +//SYNC WITH: tune.cpp, mob.h TuneGetTotalToHit int Mob::GetTotalToHit(EQ::skills::SkillType skill, int chance_mod) { if (chance_mod >= 10000) // override for stuff like SE_SkillAttack @@ -247,6 +249,7 @@ int Mob::GetTotalToHit(EQ::skills::SkillType skill, int chance_mod) // based on dev quotes // the AGI bonus has actually drastically changed from classic +//SYNC WITH: tune.cpp, mob.h Tunecompute_defense int Mob::compute_defense() { int defense = GetSkill(EQ::skills::SkillDefense) * 400 / 225; @@ -275,6 +278,7 @@ int Mob::compute_defense() } // return -1 in cases that always miss +// SYNC WITH : tune.cpp, mob.h TuneGetTotalDefense() int Mob::GetTotalDefense() { auto avoidance = compute_defense() + 10; // add 10 in case the NPC's stats are fucked @@ -302,6 +306,7 @@ int Mob::GetTotalDefense() // 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. +// SYNC WITH : tune.cpp, mob.h TuneCheckHitChance() bool Mob::CheckHitChance(Mob* other, DamageHitInfo &hit) { #ifdef LUA_EQEMU @@ -834,7 +839,7 @@ int Mob::GetClassRaceACBonus() return ac_bonus; } - +//SYNC WITH: tune.cpp, mob.h TuneACSum int Mob::ACSum(bool skip_caps) { int ac = 0; // this should be base AC whenever shrouds come around @@ -909,7 +914,7 @@ int Mob::ACSum(bool skip_caps) } int Mob::GetBestMeleeSkill() - { +{ int bestSkill=0; EQ::skills::SkillType meleeSkills[]= @@ -931,8 +936,8 @@ int Mob::GetBestMeleeSkill() } return bestSkill; - } - +} +//SYNC WITH: tune.cpp, mob.h Tuneoffense int Mob::offense(EQ::skills::SkillType skill) { int offense = GetSkill(skill); @@ -987,7 +992,7 @@ double Mob::RollD20(int offense, int mitigation) return mods[index]; } - +//SYNC WITH: tune.cpp, mob.h TuneMeleeMitigation void Mob::MeleeMitigation(Mob *attacker, DamageHitInfo &hit, ExtraAttackOptions *opts) { #ifdef LUA_EQEMU @@ -1363,6 +1368,7 @@ int Client::DoDamageCaps(int base_damage) } // other is the defender, this is the attacker +//SYNC WITH: tune.cpp, mob.h TuneDoAttack void Mob::DoAttack(Mob *other, DamageHitInfo &hit, ExtraAttackOptions *opts) { if (!other) @@ -1420,6 +1426,7 @@ void Mob::DoAttack(Mob *other, DamageHitInfo &hit, ExtraAttackOptions *opts) //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.) +//SYNC WITH: tune.cpp, mob.h TuneClientAttack bool Client::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, bool IsFromSpell, ExtraAttackOptions *opts) { if (!other) { @@ -1989,7 +1996,7 @@ bool Client::Death(Mob* killerMob, int32 damage, uint16 spell, EQ::skills::Skill parse->EventPlayer(EVENT_DEATH_COMPLETE, this, export_string, 0); return true; } - +//SYNC WITH: tune.cpp, mob.h TuneNPCAttack bool NPC::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, bool IsFromSpell, ExtraAttackOptions *opts) { if (!other) { diff --git a/zone/client.h b/zone/client.h index 7df7bb143..cbdd41084 100644 --- a/zone/client.h +++ b/zone/client.h @@ -1594,10 +1594,6 @@ public: uint32 GetLastInvSnapshotTime() { return m_epp.last_invsnapshot_time; } uint32 GetNextInvSnapshotTime() { return m_epp.next_invsnapshot_time; } - //Command #Tune functions - virtual int32 Tune_GetMeleeMitDmg(Mob* GM, Mob *attacker, int32 damage, int32 minhit, float mit_rating, float atk_rating); - int32 GetMeleeDamage(Mob* other, bool GetMinDamage = false); - void QuestReward(Mob* target, uint32 copper = 0, uint32 silver = 0, uint32 gold = 0, uint32 platinum = 0, uint32 itemid = 0, uint32 exp = 0, bool faction = false); void QuestReward(Mob* target, const QuestReward_Struct &reward, bool faction); // TODO: Fix faction processing diff --git a/zone/command.cpp b/zone/command.cpp index 8b449d1e9..7f44ad6df 100755 --- a/zone/command.cpp +++ b/zone/command.cpp @@ -429,7 +429,7 @@ int command_init(void) command_add("titlesuffix", "[text] [1 = create title table row] - Set your or your player target's title suffix", 50, command_titlesuffix) || command_add("traindisc", "[level] - Trains all the disciplines usable by the target, up to level specified. (may freeze client for a few seconds)", 150, command_traindisc) || command_add("trapinfo", "- Gets infomation about the traps currently spawned in the zone.", 81, command_trapinfo) || - command_add("tune", "Calculate ideal statical values related to combat.", 100, command_tune) || + command_add("tune", "Calculate statistical values related to combat.", 100, command_tune) || command_add("ucs", "- Attempts to reconnect to the UCS server", 0, command_ucs) || command_add("undyeme", "- Remove dye from all of your armor slots", 0, command_undyeme) || command_add("unfreeze", "- Unfreeze your target", 80, command_unfreeze) || @@ -14328,53 +14328,108 @@ void command_tune(Client *c, const Seperator *sep) { //Work in progress - Kayen - if(sep->arg[1][0] == '\0' || !strcasecmp(sep->arg[1], "help")) { + if (sep->arg[1][0] == '\0' || !strcasecmp(sep->arg[1], "help")) { c->Message(Chat::White, "Syntax: #tune [subcommand]."); c->Message(Chat::White, "-- Tune System Commands --"); - c->Message(Chat::White, "-- Usage: Returning recommended combat statistical values based on a desired outcome."); - c->Message(Chat::White, "-- Note: If targeted mob does not have a target (ie not engaged in combat), YOU will be considered the target."); + c->Message(Chat::White, "-- Usage: Returns recommended combat statistical values based on a desired outcome through simulated combat."); + c->Message(Chat::White, "-- This commmand can answer the following difficult questions whening tunings NPCs and Players."); + c->Message(Chat::White, "-- Question: What is the average damage mitigation my AC provides against a specific targets attacks?"); + c->Message(Chat::White, "-- Question: What is amount of AC would I need to add to acheive a specific average damage mitigation agianst specific targets attacks?"); + c->Message(Chat::White, "-- Question: What is amount of AC would I need to add to my target to acheive a specific average damage mitigation from my attacks?"); + c->Message(Chat::White, "-- Question: What is my targets average AC damage mitigation based on my ATK stat?"); + c->Message(Chat::White, "-- Question: What is amount of ATK would I need to add to myself to acheive a specific average damage mitigation on my target?"); + c->Message(Chat::White, "-- Question: What is amount of ATK would I need to add to my target to acheive a specific average AC damage mitigation on myself?"); + c->Message(Chat::White, "-- Question: What is my hit chance against a target?"); + c->Message(Chat::White, "-- Question: What is the amount of avoidance I need to add to my target to achieve a specific hit chance?"); + c->Message(Chat::White, "-- Question: What is the amount of accuracy I need to add to my target to achieve a specific chance of hitting me?"); + c->Message(Chat::White, "-- Question: ... and many more..."); + c->Message(Chat::White, " "); + c->Message(Chat::White, "...#tune stats [A/D]"); + c->Message(Chat::White, "...#tune FindATK [A/D] [pct mitigation] [interval] [loop_max] [AC override] [Info Level]"); + c->Message(Chat::White, "...#tune FindAC [A/D] [pct mitigation] [interval] [loop_max] [ATK override] [Info Level] "); + c->Message(Chat::White, "...#tune FindAccuracy [A/D] [hit chance] [interval] [loop_max] [Avoidance override] [Info Level]"); + c->Message(Chat::White, "...#tune FindAvoidance [A/D] [hit chance] [interval] [loop_max] [Accuracy override] [Info Level] "); + c->Message(Chat::White, " "); + c->Message(Chat::White, "-- DETAILS AND EXAMPLES ON USAGE"); + c->Message(Chat::White, " "); + c->Message(Chat::White, "...Returns combat statistics, including AC mitigation pct, hit chance, and avoid melee chance for attacker and defender."); + c->Message(Chat::White, "...#tune stats [A/D]"); + c->Message(Chat::White, "..."); + c->Message(Chat::White, "...Returns recommended ATK adjustment (+/-) on ATTACKER that will result in a specific average AC mitigation pct on DEFENDER. "); + c->Message(Chat::White, "...#tune FindATK [A/D] [pct mitigation] [interval][loop_max][AC override][Info Level]"); + c->Message(Chat::White, "...Example: Find the amount of ATK stat I need to add to the targeted NPC so that it hits me for 50 pct damage on average."); + c->Message(Chat::White, "...Example: #tune FindATK D 50"); + c->Message(Chat::White, "..."); + c->Message(Chat::White, "...Returns recommended AC adjustment(+/-) on DEFENDER for a specific average AC mitigation pct from ATTACKER. "); + c->Message(Chat::White, "...#tune FindAC [A/D] [pct mitigation] [interval][loop_max][ATK override][Info Level] "); + c->Message(Chat::White, "...Example: Find the amount of AC stat I need to add to the targeted NPC so that I hit it for 70 pct damage on average."); + c->Message(Chat::White, "...Example: #tune FindAC D 70"); + c->Message(Chat::White, "..."); + c->Message(Chat::White, "...Returns recommended Accuracy adjustment (+/-) on ATTACKER that will result in a specific hit chance pct on DEFENDER. "); + c->Message(Chat::White, "...#tune FindAccuracy [A/D] [hit chance] [interval][loop_max][Avoidance override][Info Level]"); + c->Message(Chat::White, "...Example: Find the amount of Accuracy stat I need to add to the targeted NPC so that it has a 60 pct hit chance against me."); + c->Message(Chat::White, "...Example: #tune FindAccuracy D 60"); + c->Message(Chat::White, "..."); + c->Message(Chat::White, "...Returns recommended Avoidance adjustment (+/-) on DEFENDER for in a specific hit chance pct from ATTACKER. "); + c->Message(Chat::White, "...#tune FindAvoidance [A/D] [hit chance] [interval][loop_max][Accuracy override][Info Level] "); + c->Message(Chat::White, "...Example: Find the amount of Avoidance stat I need to add to the targeted NPC so that I have a 30 pct hit chance against it."); + c->Message(Chat::White, "...Example: #tune FindAvoidance D 30"); + c->Message(Chat::White, "... "); + c->Message(Chat::White, "...Usage: [A/D] You must input either A or D."); + c->Message(Chat::White, "...Category [A] : YOU are the ATTACKER. YOUR TARGET is the DEFENDER."); + c->Message(Chat::White, "...Category [D] : YOU are the DEFENDER. YOUR TARGET is the ATTACKER."); + c->Message(Chat::White, "...If TARGET is in combat, DEFENDER is the TARGETs TARGET."); + + c->Message(Chat::White, " "); + c->Message(Chat::White, "-- Warning: The calculations done in this process are intense and can potentially cause zone crashes depending on parameters set, use with caution!"); c->Message(Chat::White, "-- Below are OPTIONAL parameters."); - c->Message(Chat::White, "-- Note: [interval] Determines how fast the stat being checked increases/decreases till it finds the best result. Default [ATK/AC 50][Acc/Avoid 10] "); - c->Message(Chat::White, "-- Note: [loop_max] Determines how many iterations are done to increases/decreases the stat till it finds the best result. Default [ATK/AC 100][Acc/Avoid 1000]"); - c->Message(Chat::White, "-- Note: [Stat Override] Will override that stat on mob being checkd with the specified value. Default=0"); - c->Message(Chat::White, "-- Note: [Info Level] How much statistical detail is displayed[0 - 3]. Default=0 "); - c->Message(Chat::White, "-- Note: Results are only approximations usually accurate to +/- 2 intervals."); - - c->Message(Chat::White, "... "); - c->Message(Chat::White, "...### Category A ### Target = ATTACKER ### YOU or Target's Target = DEFENDER ###"); - c->Message(Chat::White, "...### Category B ### Target = DEFENDER ### YOU or Target's Target = ATTACKER ###"); - c->Message(Chat::White, "... "); - c->Message(Chat::White, "...#Returns recommended ATK adjustment +/- on ATTACKER that will result in an average mitigation pct on DEFENDER. "); - c->Message(Chat::White, "...tune FindATK [A/B] [pct mitigation] [interval][loop_max][AC Overwride][Info Level]"); - c->Message(Chat::White, "... "); - c->Message(Chat::White, "...#Returns recommended AC adjustment +/- on DEFENDER for an average mitigation pct from ATTACKER. "); - c->Message(Chat::White, "...tune FindAC [A/B] [pct mitigation] [interval][loop_max][ATK Overwride][Info Level] "); - c->Message(Chat::White, "... "); - c->Message(Chat::White, "...#Returns recommended Accuracy adjustment +/- on ATTACKER that will result in a hit chance pct on DEFENDER. "); - c->Message(Chat::White, "...tune FindAccuracy [A/B] [hit chance] [interval][loop_max][Avoidance Overwride][Info Level]"); - c->Message(Chat::White, "... "); - c->Message(Chat::White, "...#Returns recommended Avoidance adjustment +/- on DEFENDER for in a hit chance pct from ATTACKER. "); - c->Message(Chat::White, "...tune FindAvoidance [A/B] [pct mitigation] [interval][loop_max][Accuracy Overwride][Info Level] "); + c->Message(Chat::White, "-- Note: [interval] Determines how much the stat being checked increases/decreases till it finds the best result. Lower is more accurate. Default=10"); + c->Message(Chat::White, "-- Note: [loop_max] Determines how many iterations are done to increases/decreases the stat till it finds the best result. Higher is more accurate. Default=1000"); + c->Message(Chat::White, "-- Note: [Stat Override] Will override that stat on mob being checked with the specified value. Default=0"); + c->Message(Chat::White, "-- Example: If as the attacker you want to find the ATK value you would need to have agianst a target with 1000 AC to achieve an average AC mitigation of 50 pct."); + c->Message(Chat::White, "-- Example: #tune FindATK A 50 0 0 1000"); + c->Message(Chat::White, "-- Note: [Info Level] How much parsing detail is displayed[0 - 1]. Default: [0] "); + c->Message(Chat::White, " "); return; } - //Default is category A for attacker/defender settings, which then are swapped under category B. - Mob* defender = c; - Mob* attacker = c->GetTarget(); + /* + Category A: YOU are the attacker and your target is the defender + Category D: YOU are the defender and your target is the attacker + */ - if (!attacker) + Mob* attacker = c; + Mob* defender = c->GetTarget(); + + if (!defender) { - c->Message(Chat::White, "#Tune - Error no target selected. [#Tune help]"); + c->Message(Chat::White, "[#Tune] - Error no target selected. [#Tune help]"); return; } + //Use if checkings on engaged targets. Mob* ttarget = attacker->GetTarget(); - - if (ttarget) + if (ttarget) { defender = ttarget; + } - if(!strcasecmp(sep->arg[1], "FindATK")) + if (!strcasecmp(sep->arg[1], "stats")) + { + + if (!strcasecmp(sep->arg[2], "A")) { + c->TuneGetStats(defender, attacker); + } + else if (!strcasecmp(sep->arg[2], "D")){ + c->TuneGetStats(attacker, defender); + } + else { + c->TuneGetStats(defender, attacker); + } + return; + } + + if (!strcasecmp(sep->arg[1], "FindATK")) { float pct_mitigation = atof(sep->arg[3]); int interval = atoi(sep->arg[4]); @@ -14384,32 +14439,48 @@ void command_tune(Client *c, const Seperator *sep) if (!pct_mitigation) { - c->Message(Chat::Red, "#Tune - Error must enter the desired percent mitigation on defender. Ie. Defender to mitigate on average 20 pct of max damage."); + c->Message(Chat::White, "[#Tune] - Error must enter the desired percent mitigation on defender."); + c->Message(Chat::White, "...Returns recommended ATK adjustment (+/-) on ATTACKER that will result in a specific average AC mitigation pct on DEFENDER. "); + c->Message(Chat::White, "...#tune FindATK [A/D] [pct mitigation] [interval][loop_max][AC override][Info Level]"); + c->Message(Chat::White, "...Example: Find the amount of ATK stat I need to add to the targeted NPC so that it hits me for 50 pct damage on average."); + c->Message(Chat::White, "...Example: #tune FindATK D 50"); return; } - if (!interval) - interval = 50; - if (!max_loop) - max_loop = 100; - if(!ac_override) + if (!interval) { + interval = 10; + } + if (!max_loop) { + max_loop = 1000; + } + if (!ac_override) { ac_override = 0; - if (!info_level) - info_level = 1; + } + if (!info_level) { + info_level = 0; + } - if(!strcasecmp(sep->arg[2], "A")) - c->Tune_FindATKByPctMitigation(defender, attacker, pct_mitigation, interval, max_loop,ac_override,info_level); - else if(!strcasecmp(sep->arg[2], "B")) - c->Tune_FindATKByPctMitigation(attacker,defender, pct_mitigation, interval, max_loop,ac_override,info_level); + if (!strcasecmp(sep->arg[2], "A")) { + c->TuneGetATKByPctMitigation(defender, attacker, pct_mitigation, interval, max_loop, ac_override, info_level); + } + else if (!strcasecmp(sep->arg[2], "D")) { + c->TuneGetATKByPctMitigation(attacker, defender, pct_mitigation, interval, max_loop, ac_override, info_level); + } else { c->Message(Chat::White, "#Tune - Error no category selcted. [#Tune help]"); c->Message(Chat::White, "Usage #tune FindATK [A/B] [pct mitigation] [interval][loop_max][AC Overwride][Info Level] "); - c->Message(Chat::White, "Example #tune FindATK A 60"); + c->Message(Chat::White, "...Usage: [A/D] You must input either A or D."); + c->Message(Chat::White, "...Category [A] : YOU are the ATTACKER. YOUR TARGET is the DEFENDER."); + c->Message(Chat::White, "...Category [D] : YOU are the DEFENDER. YOUR TARGET is the ATTACKER."); + c->Message(Chat::White, "...If TARGET is in combat, DEFENDER is the TARGETs TARGET."); + c->Message(Chat::White, "... "); + c->Message(Chat::White, "...Example: Find the amount of ATK stat I need to add to the targeted NPC so that it hits me for 50 pct damage on average."); + c->Message(Chat::White, "...Example: #tune FindATK D 50"); } return; } - if(!strcasecmp(sep->arg[1], "FindAC")) + if (!strcasecmp(sep->arg[1], "FindAC")) { float pct_mitigation = atof(sep->arg[3]); int interval = atoi(sep->arg[4]); @@ -14419,33 +14490,49 @@ void command_tune(Client *c, const Seperator *sep) if (!pct_mitigation) { - c->Message(Chat::Red, "#Tune - Error must enter the desired percent mitigation on defender. Ie. Defender to mitigate on average 20 pct of max damage."); + c->Message(Chat::White, "#Tune - Error must enter the desired percent mitigation on defender."); + c->Message(Chat::White, "...Returns recommended AC adjustment(+/-) on DEFENDER for a specific average AC mitigation pct from ATTACKER. "); + c->Message(Chat::White, "...#tune FindAC [A/D] [pct mitigation] [interval][loop_max][ATK override][Info Level] "); + c->Message(Chat::White, "...Example: Find the amount of AC stat I need to add to the targeted NPC so that I hit it for 70 pct damage on average."); + c->Message(Chat::White, "...Example: #tune FindAC D 70"); return; } - if (!interval) - interval = 50; - if (!max_loop) - max_loop = 100; - if(!atk_override) + if (!interval) { + interval = 10; + } + if (!max_loop) { + max_loop = 1000; + } + if (!atk_override) { atk_override = 0; - if (!info_level) - info_level = 1; + } + if (!info_level) { + info_level = 0; + } - if(!strcasecmp(sep->arg[2], "A")) - c->Tune_FindACByPctMitigation(defender, attacker, pct_mitigation, interval, max_loop,atk_override,info_level); - else if(!strcasecmp(sep->arg[2], "B")) - c->Tune_FindACByPctMitigation(attacker, defender, pct_mitigation, interval, max_loop,atk_override,info_level); + if (!strcasecmp(sep->arg[2], "A")) { + c->TuneGetACByPctMitigation(defender, attacker, pct_mitigation, interval, max_loop, atk_override, info_level); + } + else if (!strcasecmp(sep->arg[2], "D")) { + c->TuneGetACByPctMitigation(attacker, defender, pct_mitigation, interval, max_loop, atk_override, info_level); + } else { c->Message(Chat::White, "#Tune - Error no category selcted. [#Tune help]"); - c->Message(Chat::White, "Usage #tune FindAC [A/B] [pct mitigation] [interval][loop_max][ATK Overwride][Info Level] "); - c->Message(Chat::White, "Example #tune FindAC A 60"); + c->Message(Chat::White, "Usage #tune FindATK [A/B] [pct mitigation] [interval][loop_max][AC Overwride][Info Level] "); + c->Message(Chat::White, "...Usage: [A/D] You must input either A or D."); + c->Message(Chat::White, "...Category [A] : YOU are the ATTACKER. YOUR TARGET is the DEFENDER."); + c->Message(Chat::White, "...Category [D] : YOU are the DEFENDER. YOUR TARGET is the ATTACKER."); + c->Message(Chat::White, "...If TARGET is in combat, DEFENDER is the TARGETs TARGET."); + c->Message(Chat::White, "... "); + c->Message(Chat::White, "...Example: Find the amount of AC stat I need to add to the targeted NPC so that I hit it for 70 pct damage on average."); + c->Message(Chat::White, "...Example: #tune FindAC D 70"); } return; } - if(!strcasecmp(sep->arg[1], "FindAccuracy")) + if (!strcasecmp(sep->arg[1], "FindAccuracy")) { float hit_chance = atof(sep->arg[3]); int interval = atoi(sep->arg[4]); @@ -14455,39 +14542,47 @@ void command_tune(Client *c, const Seperator *sep) if (!hit_chance) { - c->Message(Chat::NPCQuestSay, "#Tune - Error must enter the desired percent mitigation on defender. Ie. Defender to mitigate on average 20 pct of max damage."); + c->Message(Chat::White, "#Tune - Error must enter the desired hit chance on defender."); + c->Message(Chat::White, "...Returns recommended Accuracy adjustment (+/-) on ATTACKER that will result in a specific hit chance pct on DEFENDER. "); + c->Message(Chat::White, "...#tune FindAccuracy [A/D] [hit chance] [interval][loop_max][Avoidance override][Info Level]"); + c->Message(Chat::White, "...Example: Find the amount of Accuracy stat I need to add to the targeted NPC so that it has a 60 pct hit chance against me."); + c->Message(Chat::White, "...Example: #tune FindAccuracy D 60"); return; } - if (!interval) + if (!interval) { interval = 10; - if (!max_loop) + } + if (!max_loop) { max_loop = 1000; - if(!avoid_override) + } + if (!avoid_override) { avoid_override = 0; - if (!info_level) - info_level = 1; - - if (hit_chance > RuleR(Combat,MaxChancetoHit) || hit_chance < RuleR(Combat,MinChancetoHit)) - { - c->Message(Chat::NPCQuestSay, "#Tune - Error hit chance out of bounds. [Max %.2f Min .2f]", RuleR(Combat,MaxChancetoHit),RuleR(Combat,MinChancetoHit)); - return; + } + if (!info_level) { + info_level = 0; } - if(!strcasecmp(sep->arg[2], "A")) - c->Tune_FindAccuaryByHitChance(defender, attacker, hit_chance, interval, max_loop,avoid_override,info_level); - else if(!strcasecmp(sep->arg[2], "B")) - c->Tune_FindAccuaryByHitChance(attacker, defender, hit_chance, interval, max_loop,avoid_override,info_level); + if (!strcasecmp(sep->arg[2], "A")) + c->TuneGetAccuracyByHitChance(defender, attacker, hit_chance, interval, max_loop, avoid_override, info_level); + else if (!strcasecmp(sep->arg[2], "D")) + c->TuneGetAccuracyByHitChance(attacker, defender, hit_chance, interval, max_loop, avoid_override, info_level); else { c->Message(Chat::White, "#Tune - Error no category selcted. [#Tune help]"); - c->Message(Chat::White, "Usage #tune FindAcccuracy [A/B] [hit chance] [interval][loop_max][Avoidance Overwride][Info Level]"); - c->Message(Chat::White, "Exampled #tune FindAccuracy B 30"); + c->Message(Chat::White, "...#tune FindAccuracy [A/D] [hit chance] [interval][loop_max][Avoidance override][Info Level]"); + c->Message(Chat::White, "...Usage: [A/D] You must input either A or D."); + c->Message(Chat::White, "...Category [A] : YOU are the ATTACKER. YOUR TARGET is the DEFENDER."); + c->Message(Chat::White, "...Category [D] : YOU are the DEFENDER. YOUR TARGET is the ATTACKER."); + c->Message(Chat::White, "...If TARGET is in combat, DEFENDER is the TARGETs TARGET."); + c->Message(Chat::White, "... "); + c->Message(Chat::White, "...Example: Find the amount of Accuracy stat I need to add to the targeted NPC so that it has a 60 pct hit chance against me."); + c->Message(Chat::White, "...Example: #tune FindAccuracy D 60"); } return; } - if(!strcasecmp(sep->arg[1], "FindAvoidance")) + if (!strcasecmp(sep->arg[1], "FindAvoidance")) { float hit_chance = atof(sep->arg[3]); int interval = atoi(sep->arg[4]); @@ -14497,39 +14592,46 @@ void command_tune(Client *c, const Seperator *sep) if (!hit_chance) { - c->Message(Chat::White, "#Tune - Error must enter the desired hit chance on defender. Ie. Defender to have hit chance of 40 pct."); + c->Message(Chat::White, "#Tune - Error must enter the desired hit chance on defender."); + c->Message(Chat::White, "...Returns recommended Avoidance adjustment (+/-) on DEFENDER for in a specific hit chance pct from ATTACKER. "); + c->Message(Chat::White, "...#tune FindAvoidance [A/D] [hit chance] [interval][loop_max][Accuracy override][Info Level] "); + c->Message(Chat::White, "...Example: Find the amount of Avoidance stat I need to add to the targeted NPC so that I have a 30 pct hit chance against it."); + c->Message(Chat::White, "...Example: #tune FindAvoidance D 30"); return; } - - if (!interval) + if (!interval) { interval = 10; - if (!max_loop) + } + if (!max_loop) { max_loop = 1000; - if(!acc_override) + } + if (!acc_override) { acc_override = 0; - if (!info_level) - info_level = 1; - - if (hit_chance > RuleR(Combat,MaxChancetoHit) || hit_chance < RuleR(Combat,MinChancetoHit)) - { - c->Message(Chat::NPCQuestSay, "#Tune - Error hit chance out of bounds. [Max %.2f Min .2f]", RuleR(Combat,MaxChancetoHit),RuleR(Combat,MinChancetoHit)); - return; + } + if (!info_level) { + info_level = 0; } - if(!strcasecmp(sep->arg[2], "A")) - c->Tune_FindAvoidanceByHitChance(defender, attacker, hit_chance, interval, max_loop,acc_override, info_level); - else if(!strcasecmp(sep->arg[2], "B")) - c->Tune_FindAvoidanceByHitChance(attacker, defender, hit_chance, interval, max_loop,acc_override, info_level); + if (!strcasecmp(sep->arg[2], "A")) + c->TuneGetAvoidanceByHitChance(defender, attacker, hit_chance, interval, max_loop, acc_override, info_level); + else if (!strcasecmp(sep->arg[2], "D")) + c->TuneGetAvoidanceByHitChance(attacker, defender, hit_chance, interval, max_loop, acc_override, info_level); else { c->Message(Chat::White, "#Tune - Error no category selcted. [#Tune help]"); - c->Message(Chat::White, "Usage #tune FindAvoidance [A/B] [hit chance] [interval][loop_max][Accuracy Overwride][Info Level]"); - c->Message(Chat::White, "Exampled #tune FindAvoidance B 30"); + c->Message(Chat::White, "...#tune FindAvoidance [A/D] [hit chance] [interval][loop_max][Accuracy override][Info Level] "); + c->Message(Chat::White, "...Usage: [A/D] You must input either A or D."); + c->Message(Chat::White, "...Category [A] : YOU are the ATTACKER. YOUR TARGET is the DEFENDER."); + c->Message(Chat::White, "...Category [D] : YOU are the DEFENDER. YOUR TARGET is the ATTACKER."); + c->Message(Chat::White, "...If TARGET is in combat, DEFENDER is the TARGETs TARGET."); + c->Message(Chat::White, "... "); + c->Message(Chat::White, "...Example: Find the amount of Avoidance stat I need to add to the targeted NPC so that I have a 30 pct hit chance against it."); + c->Message(Chat::White, "...Example: #tune FindAvoidance D 30"); } return; } - + c->Message(Chat::White, "#Tune - Error no command [#Tune help]"); return; } diff --git a/zone/mob.h b/zone/mob.h index a3a664f1f..327f73f9c 100644 --- a/zone/mob.h +++ b/zone/mob.h @@ -1132,7 +1132,7 @@ public: void SendItemAnimation(Mob *to, const EQ::ItemData *item, EQ::skills::SkillType skillInUse, float velocity = 4.0); inline int& GetNextIncHPEvent() { return nextinchpevent; } void SetNextIncHPEvent( int inchpevent ); - + inline bool DivineAura() const { return spellbonuses.DivineAura; } inline bool Sanctuary() const { return spellbonuses.Sanctuary; } @@ -1226,14 +1226,38 @@ public: bool mod_will_aggro(Mob *attacker, Mob *on); //Command #Tune functions - int32 Tune_MeleeMitigation(Mob* GM, Mob *attacker, int32 damage, int32 minhit, ExtraAttackOptions *opts = nullptr, int Msg =0, int ac_override=0, int atk_override=0, int add_ac=0, int add_atk = 0); - virtual int32 Tune_GetMeleeMitDmg(Mob* GM, Mob *attacker, int32 damage, int32 minhit, float mit_rating, float atk_rating); - uint32 Tune_GetMeanDamage(Mob* GM, Mob *attacker, int32 damage, int32 minhit, ExtraAttackOptions *opts = nullptr, int Msg = 0,int ac_override=0, int atk_override=0, int add_ac=0, int add_atk = 0); - void Tune_FindATKByPctMitigation(Mob* defender, Mob *attacker, float pct_mitigation, int interval = 50, int max_loop = 100, int ac_override=0,int Msg =0); - void Tune_FindACByPctMitigation(Mob* defender, Mob *attacker, float pct_mitigation, int interval = 50, int max_loop = 100, int atk_override=0,int Msg =0); - float Tune_CheckHitChance(Mob* defender, Mob* attacker, EQ::skills::SkillType skillinuse, int Hand, int16 chance_mod, int Msg = 1, int acc_override = 0, int avoid_override = 0, int add_acc = 0, int add_avoid = 0); - void Tune_FindAccuaryByHitChance(Mob* defender, Mob *attacker, float hit_chance, int interval, int max_loop, int avoid_override, int Msg = 0); - void Tune_FindAvoidanceByHitChance(Mob* defender, Mob *attacker, float hit_chance, int interval, int max_loop, int acc_override, int Msg = 0); + void TuneGetStats(Mob* defender, Mob *attacker); + void TuneGetACByPctMitigation(Mob* defender, Mob *attacker, float pct_mitigation, int interval = 10, int max_loop = 1000, int atk_override = 0, int Msg = 0); + void TuneGetATKByPctMitigation(Mob* defender, Mob *attacker, float pct_mitigation, int interval = 10, int max_loop = 1000, int ac_override = 0, int Msg = 0); + void TuneGetAvoidanceByHitChance(Mob* defender, Mob *attacker, float hit_chance, int interval, int max_loop, int accuracy_override, int Msg); + void TuneGetAccuracyByHitChance(Mob* defender, Mob *attacker, float hit_chance, int interval, int max_loop, int avoidance_override, int Msg); + /*support functions*/ + int TuneClientGetMeanDamage(Mob* other, int ac_override = 0, int atk_override = 0, int add_ac = 0, int add_atk = 0); + int TuneClientGetMaxDamage(Mob* other); + int TuneClientGetMinDamage(Mob* other, int max_hit); + float TuneGetACMitigationPct(Mob* defender, Mob *attacker); + int TuneGetOffense(Mob* defender, Mob *attacker, int atk_override = 0); + int TuneGetAccuracy(Mob* defender, Mob *attacker, int accuracy_override = 0, int add_accuracy = 0); + int TuneGetAvoidance(Mob* defender, Mob *attacker, int avoidance_override = 0, int add_avoidance = 0); + float TuneGetHitChance(Mob* defender, Mob *attacker, int avoidance_override = 0, int accuracy_override = 0, int add_avoidance = 0, int add_accuracy = 0); + float TuneGetAvoidMeleeChance(Mob* defender, Mob *attacker, int type); + int TuneCalcEvasionBonus(int final_avoidance, int base_avoidance); + /*modified combat code - These SYNC to attack.cpp, relevant changes to these functions in attack.cpp should be changed to the below as well*/ + int TuneNPCAttack(Mob* other, bool no_avoid = true, bool no_hit_chance = true, int hit_chance_bonus = 10000, int ac_override = 0, int atk_override = 0, int add_ac = 0, int add_atk = 0, + bool get_offense = false, bool get_accuracy = false, int avoidance_override = 0, int accuracy_override = 0, int add_avoidance = 0, int add_accuracy = 0); + int TuneClientAttack(Mob* other, bool no_avoid = true, bool no_hit_chance = true, int hit_chance_bonus = 10000, int ac_override = 0, int atk_override = 0, int add_ac = 0, int add_atk = 0, + bool get_offense = false, bool get_accuracy = false, int avoidance_override = 0, int accuracy_override = 0, int add_avoidance = 0, int add_accuracy = 0); + void TuneDoAttack(Mob *other, DamageHitInfo &hit, ExtraAttackOptions *opts = nullptr, bool no_avoid = true, bool no_hit_chance = true, int ac_override = 0, int add_ac = 0, + int avoidance_override = 0, int accuracy_override = 0, int add_avoidance = 0, int add_accuracy = 0); + void TuneMeleeMitigation(Mob *attacker, DamageHitInfo &hit, int ac_override, int add_ac); + int Tuneoffense(EQ::skills::SkillType skill, int atk_override = 0, int add_atk = 0); + int TuneACSum(bool skip_caps=false, int ac_override = 0, int add_ac = 0); + int TuneGetTotalToHit(EQ::skills::SkillType skill, int chance_mod, int accuracy_override = 0, int add_accurracy = 0); // compute_tohit + spell bonuses + int Tunecompute_tohit(EQ::skills::SkillType skillinuse, int accuracy_override = 0, int add_accuracy = 0); + int TuneGetTotalDefense(int avoidance_override = 0, int add_avoidance = 0); + int Tunecompute_defense(int avoidance_override = 0, int add_avoidance = 0); + bool TuneCheckHitChance(Mob* other, DamageHitInfo &hit, int avoidance_override = 0, int add_avoidance = 0); + EQ::skills::SkillType TuneAttackAnimation(int Hand, const EQ::ItemInstance* weapon, EQ::skills::SkillType skillinuse = EQ::skills::Skill1HBlunt); //aa new uint32 GetAA(uint32 rank_id, uint32 *charges = nullptr) const; diff --git a/zone/tune.cpp b/zone/tune.cpp index f22fe6a53..be806eb92 100644 --- a/zone/tune.cpp +++ b/zone/tune.cpp @@ -27,16 +27,23 @@ #include "../common/skills.h" #include "../common/spdat.h" #include "../common/string_util.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 #include #include +#include #ifdef BOTS #include "bot.h" @@ -44,6 +51,7 @@ extern QueryServ* QServ; extern WorldServer worldserver; +extern FastMath g_Math; #ifdef _WINDOWS #define snprintf _snprintf @@ -54,1035 +62,1350 @@ extern WorldServer worldserver; extern EntityList entity_list; extern Zone* zone; -void Mob::Tune_FindATKByPctMitigation(Mob* defender,Mob *attacker, float pct_mitigation, int interval, int max_loop, int ac_override, int Msg) + +void Mob::TuneGetStats(Mob* defender, Mob *attacker) { - /*Find the amount of 'ATTACK' stat that has to be added/subtracted FROM ATTACKER to reach a specific average mitigation value on the TARGET. - Can use ac_override to find the value verse a hypothetical amount of worn AC */ - - int atk = 0; - uint32 total_damage = 0; - int32 damage = 0; - uint32 minhit = 0; - int mean_dmg = 0; - float tmp_pct_mitigated = 0.0f; - int end = 0; - - if (attacker->IsNPC()) - { - damage = static_cast(attacker->CastToNPC()->GetMaxDMG()); - minhit = attacker->CastToNPC()->GetMinDMG(); - } - else if (attacker->IsClient()) - { - damage = static_cast(attacker->CastToClient()->GetMeleeDamage(this)); - minhit = attacker->CastToClient()->GetMeleeDamage(this, true); - } - - if (damage == 0 || minhit == 0) - { - Message(0, "#Tune - Processing... Abort! Damage not found! [MaxDMG %i MinDMG %i]", damage,minhit); + 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; - mean_dmg = defender->Tune_GetMeanDamage(this, attacker, damage, minhit, nullptr, 0, ac_override, 0, 0,atk); - tmp_pct_mitigated = 100.0f - static_cast( mean_dmg * 100 /damage); + max_damage = attacker->TuneClientGetMaxDamage(defender); + min_damage = attacker->TuneClientGetMinDamage(defender, max_damage); - Message(0, "#Tune - Begin Parse [Interval %i Max Loop Iterations %i]", interval, max_loop); - Message(0, "#Tune - Processing... Find ATK for attacker Mitigation (%.0f) pct on defender [MaxDMG %i MinDMG %i Current Mitigation %.2f]", pct_mitigation, damage, minhit,tmp_pct_mitigated); - - if (tmp_pct_mitigated < pct_mitigation) - interval = interval * -1; - - for (int j=0; j < max_loop; j++) + if (!max_damage) { - mean_dmg = defender->Tune_GetMeanDamage(this, attacker, damage, minhit, nullptr, 0, ac_override,0, 0,atk); - tmp_pct_mitigated = 100.0f - ( static_cast(mean_dmg) * 100.0f /static_cast(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(mean_dmg) * 100.0f / static_cast(max_damage)); - if (Msg >= 3) - Message(0, "#Tune - Processing... [%i] [ATK %i] Average Melee Hit %i | Pct Mitigated %.2f ",j,atk, mean_dmg, tmp_pct_mitigated); + hit_chance = TuneGetHitChance(defender, attacker); - 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 == minhit) - end = 2; + 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 (end >= 1){ + Message(0, "################################################"); + Message(0, "[#Tune] Attacker Statistics vs Defender"); + Message(0, "[#Tune] Attacker Name: %s", attacker->GetCleanName()); - defender->Tune_MeleeMitigation(this, attacker, damage, minhit, nullptr,Msg,ac_override, 0, 0, atk); - - if (end == 2) - Message(0, "#Tune - [WARNING] Mitigation can not be further decreased due to minium hit value (%i).",minhit); + 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] Accuracy: %i ", TuneGetAccuracy( defender,attacker)); - if (attacker->IsNPC()){ - Message(0, "#Tune - Recommended NPC ATK ADJUSTMENT ( %i ) on ' %s ' average mitigation of (%.0f) pct verse ' %s '. ",atk, attacker->GetCleanName(), pct_mitigation, defender->GetCleanName()); - Message(0, "#SET: [NPC Attack STAT] = [%i]",atk + defender->CastToNPC()->ATK); - } - if (attacker->IsClient()){ - Message(0, "#Tune - Recommended CLIENT ATK ADJUSTMENT ( %i ) on ' %s ' average mitigation of (%.0f) pct verse ' %s '. ", atk, attacker->GetCleanName(), pct_mitigation, defender->GetCleanName()); - Message(0, "#Modify (+/-): [Client Attack STAT/SE_ATK(2)] [%i]",atk); - } - - return; - } - - atk = atk + interval; + } + else{ + Message(0, "[#Tune] Can not melee this target"); } - 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 ATK ADJUSTMENT ( %i ) average target mitigation of (%.0f) pct.",atk,tmp_pct_mitigated); + Message(0, "#STATS#############COMPLETE###################"); + return; } -void Mob::Tune_FindACByPctMitigation(Mob* defender, Mob *attacker, float pct_mitigation, int interval, int max_loop, int atk_override, int Msg) +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 + */ - /*Find the amount of AC stat that has to be added/subtracted from TARGET to reach a specific average mitigation value based on ATTACKER's statistics. - Can use ac_override to find the value verse a hypothetical amount of worn AC */ - - int add_ac = 0; - uint32 total_damage = 0; - int32 damage = 0; - uint32 minhit = 0; - int mean_dmg = 0; - float tmp_pct_mitigated = 0.0f; - int end = 0; - - - if (attacker->IsNPC()) - { - damage = static_cast(attacker->CastToNPC()->GetMaxDMG()); - minhit = attacker->CastToNPC()->GetMinDMG(); + 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; } - else if (attacker->IsClient()) - { - damage = static_cast(attacker->CastToClient()->GetMeleeDamage(this)); - minhit = attacker->CastToClient()->GetMeleeDamage(this, true); + if (!defender) { + Message(0, "[#Tune] - Processing... Abort! No Defender found."); + return; } - - if (damage == 0 || minhit == 0) - { - Message(0, "#Tune - Processing... Abort! Damage not found! [MaxDMG %i MinDMG %i]", damage,minhit); + 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; } - mean_dmg = defender->Tune_GetMeanDamage(this, attacker, damage, minhit, nullptr, 0, 0, atk_override); - tmp_pct_mitigated = 100.0f - static_cast( mean_dmg * 100 /damage); + 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; - Message(0, "#Tune - Begin Parse [Interval %i Max Loop Iterations %i]", interval, max_loop); - Message(0, "#Tune - Processing... Find AC for defender Mitigation (%.0f) pct from attacker [MaxDMG %i MinDMG %i Current Mitigation %.2f]", pct_mitigation, damage, minhit,tmp_pct_mitigated); + 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(mean_dmg) * 100.0f / static_cast(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; - - for (int j=0; j < max_loop; j++) { - mean_dmg = defender->Tune_GetMeanDamage(this, attacker, damage, minhit, nullptr, 0, 0,atk_override, add_ac, 0); - tmp_pct_mitigated = 100.0f - ( static_cast(mean_dmg) * 100.0f /static_cast(damage)); + 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); + } - if (Msg >= 3) - Message(0, "#Tune - Processing... [%i] [AC %i] Average Melee Hit %i | Pct Mitigated %.2f ",j,add_ac, mean_dmg, tmp_pct_mitigated); + 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(mean_dmg) * 100.0f / static_cast(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 == minhit) - end = 2; + 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){ + if (end >= 1) { - defender->Tune_MeleeMitigation(this, attacker, damage, minhit, nullptr,Msg,0,atk_override, add_ac, 0); - - if (end == 2) - Message(0, "#Tune - [WARNING] Mitigation can not be further decreased due to minium hit value (%i).",minhit); + Message(0, "###################RESULTS###################"); - if (defender->IsNPC()){ - Message(Chat::LightGray, "#Tune - Recommended NPC AC ADJUSTMENT ( %i ) on ' %s ' for an average mitigation of (+ %.0f) pct from attacker ' %s '.",add_ac,defender->GetCleanName(), pct_mitigation, attacker->GetCleanName()); - Message(0, "#SET: [NPC Attack STAT] = [%i]",add_ac + defender->CastToNPC()->GetRawAC()); + 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->IsClient()){ - Message(Chat::LightGray, "#Tune - Recommended CLIENT AC ADJUSTMENT ( %i ) on ' %s ' for an average mitigation of (+ %.0f) pct from attacker ' %s '.",add_ac,defender->GetCleanName(), pct_mitigation, attacker->GetCleanName()); - Message(0, "#Modify (+/-): [Client AC STAT/SE_AC(1)] [%i]",add_ac); + + 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; } - - - add_ac = add_ac + interval; + loop_add_ac = loop_add_ac + interval; } - 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 AC ADJUSTMENT ( %i ) at average mitigation of (%.0f) / (%.0f) pct.",add_ac,tmp_pct_mitigated / pct_mitigation); + 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; } -uint32 Mob::Tune_GetMeanDamage(Mob* GM, Mob *attacker, int32 damage, int32 minhit, ExtraAttackOptions *opts, int Msg, - int ac_override, int atk_override, int add_ac, int add_atk) +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(mean_dmg) * 100.0f / static_cast(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(mean_dmg) * 100.0f / static_cast(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++) + { + + 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 (defender->IsNPC()) { + Message(0, "[#Tune] Recommended NPC ACCURACY ADJUSTMENT of ( %i ) on ' %s ' will result in ( %.0f pct ) chance to hit ' %s '.", loop_add_accuracy, defender->GetCleanName(), hit_chance, attacker->GetCleanName()); + Message(0, "[#Tune] SET NPC 'ACCURACY' stat value = [ %i ]", loop_add_accuracy + defender->CastToNPC()->GetAccuracyRating()); + Message(0, "###################COMPLETE###################"); + } + else if (defender->IsClient()) { + Message(0, "[#Tune] Recommended CLIENT AVOIDANCE ADJUSTMENT of ( %i ) on %s ' will result in ( %.0f pct ) chance to hit ' %s '.", loop_add_accuracy, defender->GetCleanName(), hit_chance, attacker->GetCleanName()); + + if (loop_add_accuracy >= 0) { + Message(0, "[#Tune] OPTION1: MODIFY Client Avoidance Mod2 stat or SPA 216 Melee Accuracy (spells/items/aa) [+ %i ]", loop_add_accuracy); + + } + else { + Message(0, "[#Tune] OPTION1: MODIFY Client Avoidance Mod2 stat or SPA 216 Melee Accuracy (spells/items/aa) [ %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, defender->GetCleanName(), hit_chance, attacker->GetCleanName()); + Message(0, "###################COMPLETE###################"); +} + +/* + Tune support functions +*/ + +int 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++) + for (int i = 0; i < loop_max; i++) { - total_damage += Tune_MeleeMitigation(GM, attacker, damage, minhit, nullptr,0,ac_override, atk_override, add_ac, add_atk); - } - - return(total_damage/loop_max); -} - -int32 Mob::Tune_MeleeMitigation(Mob* GM, Mob *attacker, int32 damage, int32 minhit, ExtraAttackOptions *opts, int Msg, - int ac_override, int atk_override, int add_ac, int add_atk) -{ - if (damage <= 0) - return 0; - - Mob* defender = this; - float aa_mit = (aabonuses.CombatStability + itembonuses.CombatStability + - spellbonuses.CombatStability) / 100.0f; - - if (Msg){ - - GM->Message(Chat::White, "######### Melee Mitigation Report: Start [Detail Level %i]#########", Msg); - GM->Message(Chat::White, "#ATTACKER: %s", attacker->GetCleanName()); - GM->Message(Chat::White, "#DEFENDER: %s", defender->GetCleanName()); - } - - if (RuleB(Combat, UseIntervalAC)) { - float softcap = (GetSkill(EQ::skills::SkillDefense) + GetLevel()) * - RuleR(Combat, SoftcapFactor) * (1.0 + aa_mit); - float mitigation_rating = 0.0; - float attack_rating = 0.0; - int shield_ac = 0; - int armor = 0; - float weight = 0.0; - - if (Msg >= 2){ - GM->Message(Chat::White, " "); - GM->Message(Chat::White, "### Calculate Mitigation Rating ###"); - if (aabonuses.CombatStability) - GM->Message(Chat::White, "# %i #### DEFENDER SE_CombatStability(259) AA Bonus", aabonuses.CombatStability); - if (spellbonuses.CombatStability) - GM->Message(Chat::White, "# %i #### DEFENDER SE_CombatStability(259) Spell Bonus", spellbonuses.CombatStability); - if (itembonuses.CombatStability) - GM->Message(Chat::White, "# %i #### DEFENDER SE_CombatStability(259) Worn Bonus", itembonuses.CombatStability); - - GM->Message(Chat::White, "# %.2f #### DEFENDER Base Soft Cap", softcap); - } - - float monkweight = RuleI(Combat, MonkACBonusWeight); - monkweight = mod_monk_weight(monkweight, attacker); - if (IsClient()) { - armor = CastToClient()->GetRawACNoShield(shield_ac) + add_ac; - weight = (CastToClient()->CalcCurrentWeight() / 10.0); - - if (ac_override) - armor = ac_override; - - if (Msg >=2 ){ - GM->Message(Chat::White, "# %i #### DEFENDER AC Equiped/Worn Bonus", itembonuses.AC); - GM->Message(Chat::White, "# %i #### DEFENDER SE_ArmorClass(1) AA Bonus", aabonuses.AC); - GM->Message(Chat::White, "# %i #### DEFENDER SE_ArmorClass(1) Spell Bonus", spellbonuses.AC); - GM->Message(Chat::White, "# %i #### DEFENDER Shield AC", shield_ac); - GM->Message(Chat::White, "# %i #### DEFENDER Total Client Armor - NO shield", armor); - } - - } else if (IsNPC()) { - armor = CastToNPC()->GetRawAC() + add_ac; - - if (ac_override) - armor = ac_override; - - if (Msg >=2 ){ - GM->Message(Chat::White, "# %i #### DEFENDER AC Equiped/Worn Bonus", itembonuses.AC); - GM->Message(Chat::White, "# %i #### DEFENDER SE_ArmorClass(1) Spell Bonus", spellbonuses.AC); - GM->Message(Chat::White, "# %i #### DEFENDER NPC AC Stat", CastToNPC()->GetRawAC()); - } - - int PetACBonus = 0; - - if (!IsPet()){ - armor = (armor / RuleR(Combat, NPCACFactor)); - if (Msg >=2 ) - GM->Message(Chat::White, "# %i #### DEFENDER NPC Armor after RuleR(Combat, NPCACFactor) %.2f", armor, RuleR(Combat, NPCACFactor)); - } - - Mob *owner = nullptr; - if (IsPet()) - owner = GetOwner(); - else if ((CastToNPC()->GetSwarmOwner())) - owner = entity_list.GetMobID(CastToNPC()->GetSwarmOwner()); - - if (owner){ - PetACBonus = owner->aabonuses.PetMeleeMitigation + owner->itembonuses.PetMeleeMitigation + owner->spellbonuses.PetMeleeMitigation; - - if (Msg >=2 ){ - if (owner->aabonuses.PetMeleeMitigation) - GM->Message(Chat::White, "# %i #### DEFENDER Pet Owner SE_PetMeleeMitigation(379) AA Bonus", owner->aabonuses.PetMeleeMitigation); - if (owner->spellbonuses.PetMeleeMitigation) - GM->Message(Chat::White, "# %i #### DEFENDER Pet Owner SE_PetMeleeMitigation(379) Spell Bonus",owner->spellbonuses.PetMeleeMitigation); - if (owner->itembonuses.PetMeleeMitigation) - GM->Message(Chat::White, "# %i #### DEFENDER Pet Owner SE_PetMeleeMitigation(379) Worn Bonus", owner->itembonuses.PetMeleeMitigation); - } - } - - armor += spellbonuses.AC + itembonuses.AC + PetACBonus + 1; - - if (Msg >= 2) - GM->Message(Chat::White, "# %i #### DEFENDER NPC Total Base Armor",armor); + total_damage += TuneClientAttack(other, true, true, 10000, ac_override, atk_override, add_ac, add_atk); } - - if (opts) { - armor *= (1.0f - opts->armor_pen_percent); - armor -= opts->armor_pen_flat; + else { + total_damage += TuneNPCAttack(other, true, true, 10000, ac_override, atk_override, add_ac, add_atk); } - - if (RuleB(Combat, OldACSoftcapRules)) { - if (GetClass() == WIZARD || GetClass() == MAGICIAN || - GetClass() == NECROMANCER || GetClass() == ENCHANTER) - softcap = RuleI(Combat, ClothACSoftcap); - else if (GetClass() == MONK && weight <= monkweight) - 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; - - if (RuleB(Combat, OldACSoftcapRules)) - softcap += (softcap * (aa_mit * RuleR(Combat, AAMitigationACFactor))); - if (armor > softcap) { - int softcap_armor = armor - softcap; - if (RuleB(Combat, OldACSoftcapRules)) { - if (GetClass() == WARRIOR) - softcap_armor = softcap_armor * RuleR(Combat, WarriorACSoftcapReturn); - else if (GetClass() == SHADOWKNIGHT || GetClass() == PALADIN || - (GetClass() == MONK && weight <= monkweight)) - 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); - } else { - if (GetClass() == WARRIOR) - softcap_armor *= RuleR(Combat, WarACSoftcapReturn); - else if (GetClass() == PALADIN || GetClass() == SHADOWKNIGHT) - softcap_armor *= RuleR(Combat, PalShdACSoftcapReturn); - else if (GetClass() == CLERIC || GetClass() == RANGER || - GetClass() == MONK || GetClass() == BARD) - softcap_armor *= RuleR(Combat, ClrRngMnkBrdACSoftcapReturn); - else if (GetClass() == DRUID || GetClass() == NECROMANCER || - GetClass() == WIZARD || GetClass() == ENCHANTER || - GetClass() == MAGICIAN) - softcap_armor *= RuleR(Combat, DruNecWizEncMagACSoftcapReturn); - else if (GetClass() == ROGUE || GetClass() == SHAMAN || - GetClass() == BEASTLORD || GetClass() == BERSERKER) - softcap_armor *= RuleR(Combat, RogShmBstBerACSoftcapReturn); - else - softcap_armor *= RuleR(Combat, MiscACSoftcapReturn); - } - - - armor = softcap + softcap_armor; - if (Msg >= 2) - GM->Message(Chat::White, "# %i #### DEFENDER Final Armor [Soft Cap %i Soft Cap Armor %i]",armor, softcap,softcap_armor); - } - int tmp_armor = armor; - if (GetClass() == WIZARD || GetClass() == MAGICIAN || - GetClass() == NECROMANCER || GetClass() == ENCHANTER){ - mitigation_rating = ((GetSkill(EQ::skills::SkillDefense) + itembonuses.HeroicAGI / 10) / 4.0) + armor + 1; - if (Msg >= 2) - GM->Message(Chat::White, "# + %.2f #### DEFENDER Armor Bonus [Defense Skill %i Heroic Agi %i]", mitigation_rating - tmp_armor, GetSkill(EQ::skills::SkillDefense), itembonuses.HeroicAGI); - } - else{ - mitigation_rating = ((GetSkill(EQ::skills::SkillDefense) + itembonuses.HeroicAGI / 10) / 3.0) + (armor * 1.333333) + 1; - if (Msg >= 2) - GM->Message(Chat::White, "# + %.2f #### DEFENDER Armor Bonus [Defense Skill %i Heroic Agi %i]", mitigation_rating - tmp_armor, GetSkill(EQ::skills::SkillDefense), itembonuses.HeroicAGI); - - } - mitigation_rating *= 0.847; - - if (Msg >= 1) - GM->Message(Chat::White, "# %.2f #### DEFENDER Final Mitigation Rating", mitigation_rating); - - - if (Msg >= 2){ - GM->Message(Chat::White, " "); - GM->Message(Chat::White, "### Mitigation Bonus Effects ###"); - if (itembonuses.MeleeMitigation) - GM->Message(Chat::White, "# %i #### DEFENDER Item Mod2 Shielding", itembonuses.MeleeMitigation); - if (aabonuses.MeleeMitigationEffect) - GM->Message(Chat::White, "# %i #### DEFENDER SE_MeleeMitigation(168) AA Bonus", aabonuses.MeleeMitigationEffect); - if (spellbonuses.MeleeMitigationEffect) - GM->Message(Chat::White, "# %i #### DEFENDER SE_MeleeMitigation(168) Spell Bonus", spellbonuses.MeleeMitigationEffect); - if (itembonuses.MeleeMitigationEffect) - GM->Message(Chat::White, "# %i #### DEFENDER SE_MeleeMitigation(168) Worn Bonus", itembonuses.MeleeMitigationEffect); - } - - mitigation_rating = mod_mitigation_rating(mitigation_rating, attacker); - - if (attacker->IsClient()){ - if (atk_override) - attack_rating = (atk_override + ((attacker->GetSTR() - 66) * 0.9) + (attacker->GetSkill(EQ::skills::SkillOffense)*1.345)); - else - attack_rating = ((attacker->CastToClient()->CalcATK() + add_atk) + ((attacker->GetSTR() - 66) * 0.9) + (attacker->GetSkill(EQ::skills::SkillOffense)*1.345)); - - } - else{ - if (atk_override) - attack_rating = (atk_override + (attacker->GetSkill(EQ::skills::SkillOffense)*1.345) + ((attacker->GetSTR() - 66) * 0.9)); - else - attack_rating = ((attacker->GetATK() + add_atk) + (attacker->GetSkill(EQ::skills::SkillOffense)*1.345) + ((attacker->GetSTR() - 66) * 0.9)); - } - - attack_rating = attacker->mod_attack_rating(attack_rating, this); - - if (Msg >= 2){ - GM->Message(Chat::White, " "); - GM->Message(Chat::White, "### Calculate Attack Rating ###"); - if (attacker->IsClient()){ - GM->Message(Chat::White, "# %i #### ATTACKER Worn/Equip ATK Bonus", attacker->itembonuses.ATK); - GM->Message(Chat::White, "# %i #### ATTACKER SE_ATK(2) AA Bonus", attacker->aabonuses.ATK); - GM->Message(Chat::White, "# %i #### ATTACKER SE_ATK(2) spell Bonus", attacker->spellbonuses.ATK); - GM->Message(Chat::White, "# %i #### ATTACKER Leadership Bonus", attacker->CastToClient()->GroupLeadershipAAOffenseEnhancement()); - GM->Message(Chat::White, "# %i #### ATTACKER Worn/Equip ATK Bonus", attacker->itembonuses.ATK); - GM->Message(Chat::White, "# %i #### ATTACKER Worn/Equip ATK Bonus", attacker->itembonuses.ATK); - GM->Message(Chat::White, "# %.2f #### ATTACKER Strength Stat ATK Bonus [Stat Amt: %i]", ((attacker->GetSTR()-66) * 0.9),attacker->GetSTR()); - GM->Message(Chat::White, "# %.2f #### ATTACKER Offensive Skill ATK Bonus [Stat Amt: %i]", (attacker->GetSkill(EQ::skills::SkillOffense)*1.345), attacker->GetSkill(EQ::skills::SkillOffense)); - } - - else{ - GM->Message(Chat::White, "# %i #### ATTACKER Worn/Equip ATK Bonus", attacker->itembonuses.ATK); - GM->Message(Chat::White, "# %i #### ATTACKER SE_ATK(2) spell Bonus", attacker->spellbonuses.ATK); - GM->Message(Chat::White, "# %i #### ATTACKER NPC ATK Stat", attacker->CastToNPC()->ATK); - GM->Message(Chat::White, "# %.2f #### ATTACKER Strength Stat ATK Bonus [Stat Amt: %i]", ((attacker->GetSTR()-66) * 0.9),attacker->GetSTR()); - GM->Message(Chat::White, "# %.2f #### ATTACKER Offensive Skill ATK Bonus [Stat Amt: %i]", (attacker->GetSkill(EQ::skills::SkillOffense)*1.345), attacker->GetSkill(EQ::skills::SkillOffense)); - } - } - - if (Msg >= 1){ - GM->Message(Chat::White, "# %.2f #### ATTACKER Final Attack Rating", attack_rating); - GM->Message(Chat::White, "######### Melee Mitigation Report: Complete #########", Msg); - } - - - //damage = GetMeleeMitDmg(attacker, damage, minhit, mitigation_rating, attack_rating); - } - - if (damage < 0) - damage = 0; - - return damage; -} - -// This is called when the Mob is the one being hit -int32 Mob::Tune_GetMeleeMitDmg(Mob* GM, Mob *attacker, int32 damage, int32 minhit, - float mit_rating, float atk_rating) -{ - float d = 10.0; - float mit_roll = zone->random.Real(0, mit_rating); - float atk_roll = zone->random.Real(0, atk_rating); - - if (atk_roll > mit_roll) { - float a_diff = atk_roll - mit_roll; - float thac0 = atk_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 = mit_rating * RuleR(Combat, ACthac20Factor); - float thac20cap = GetLevel() * 9 + 20; - if (thac20 > thac20cap) - thac20 = thac20cap; - - d += 10.0 * (m_diff / thac20); } - if (d < 0.0) - d = 0.0; - else if (d > 20.0) - d = 20.0; - - float interval = (damage - minhit) / 20.0; - damage -= ((int)d * interval); - - damage -= (minhit * itembonuses.MeleeMitigation / 100); - damage -= (damage * (spellbonuses.MeleeMitigationEffect + itembonuses.MeleeMitigationEffect + aabonuses.MeleeMitigationEffect) / 100); - return damage; + return(total_damage / loop_max); } -// This is called when the Client is the one being hit -int32 Client::Tune_GetMeleeMitDmg(Mob* GM, Mob *attacker, int32 damage, int32 minhit, - float mit_rating, float atk_rating) +int Mob::TuneClientGetMaxDamage(Mob* other) { - if (!attacker->IsNPC() || RuleB(Combat, UseOldDamageIntervalRules)) - return 0; //Mob::GetMeleeMitDmg(attacker, damage, minhit, mit_rating, atk_rating); - int d = 10; - // floats for the rounding issues - float dmg_interval = (damage - minhit) / 19.0; - float dmg_bonus = minhit - dmg_interval; - float spellMeleeMit = (spellbonuses.MeleeMitigationEffect + itembonuses.MeleeMitigationEffect + aabonuses.MeleeMitigationEffect) / 100.0; - if (GetClass() == WARRIOR) - spellMeleeMit += 0.05; - dmg_bonus -= dmg_bonus * (itembonuses.MeleeMitigation / 100.0); - dmg_interval -= dmg_interval * spellMeleeMit; + uint32 max_hit = 0; + uint32 current_hit = 0; + int loop_max = 1000; - float mit_roll = zone->random.Real(0, mit_rating); - float atk_roll = zone->random.Real(0, atk_rating); + 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 (atk_roll > mit_roll) { - float a_diff = atk_roll - mit_roll; - float thac0 = atk_rating * RuleR(Combat, ACthac0Factor); - float thac0cap = attacker->GetLevel() * 9 + 20; - if (thac0 > thac0cap) - thac0 = thac0cap; + if (current_hit > max_hit) { + max_hit = current_hit; + } + } + return(max_hit); +} - d += 10 * (a_diff / thac0); - } else if (mit_roll > atk_roll) { - float m_diff = mit_roll - atk_roll; - float thac20 = mit_rating * RuleR(Combat, ACthac20Factor); - float thac20cap = GetLevel() * 9 + 20; - if (thac20 > thac20cap) - thac20 = thac20cap; +int Mob::TuneClientGetMinDamage(Mob* other, int max_hit) +{ + uint32 min_hit = max_hit; + uint32 current_hit = 0; + int loop_max = 1000; - d -= 10 * (m_diff / thac20); + 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; } - if (d < 1) - d = 1; - else if (d > 20) - d = 20; + int mean_dmg = attacker->TuneClientGetMeanDamage(defender); + float tmp_pct_mitigated = 100.0f - (static_cast(mean_dmg) * 100.0f / static_cast(max_damage)); - return static_cast((dmg_bonus + dmg_interval * d)); + return tmp_pct_mitigated; } - -int32 Client::GetMeleeDamage(Mob* other, bool GetMinDamage) +int 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; +} + +int 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; +} + +int 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(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(hit_count) / 3000.0f) * 100.0f; + return chance; +} + +int Mob::TuneCalcEvasionBonus(int final_avoidance, int base_avoidance) { + + /* + float eb = static_cast(final_avoidance) / static_cast(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 = 3000; + int evasion_bonus = 10; + int current_avoidance = 0; + + int interval = 5; + + 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. +*/ + +int 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(CastToNPC()->GetPrimSkill()); + OffHandAtk(false); + + uint8 otherlevel = other->GetLevel(); + uint8 mylevel = this->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; +} + +int 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; - if (!other) - return 0; - - EQ::ItemInstance* weapon; - weapon = GetInv().GetItem(EQ::invslot::slotPrimary); - - if(weapon != nullptr) { + EQ::ItemInstance* weapon = nullptr; + weapon = CastToClient()->GetInv().GetItem(EQ::invslot::slotPrimary); + OffHandAtk(false); + + if (weapon != nullptr) { if (!weapon->IsWeapon()) { - return(0); + Message(Chat::Red, "#Tune Failure: Attack cancelled, Item %s is not a weapon!", weapon->GetItem()->Name); + return(false); } - } + } - EQ::skills::SkillType skillinuse = AttackAnimation(Hand, weapon); + DamageHitInfo my_hit; + my_hit.skill = TuneAttackAnimation(Hand, weapon); - int damage = 0; + // Now figure out damage + my_hit.damage_done = 1; + my_hit.min_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) + hate = (weapon->GetItem()->Damage + weapon->GetItem()->ElemDmgAmt); - if(weapon_damage > 0){ - if(IsBerserk() && GetClass() == BERSERKER){ - int bonus = 3 + GetLevel()/10; //unverified - weapon_damage = weapon_damage * (100+bonus) / 100; + 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; } - int min_hit = 1; - int max_hit = 2;//(2*weapon_damage*GetDamageTable(skillinuse)) / 100; - - if(GetLevel() < 10 && max_hit > RuleI(Combat, HitCapPre10)) - max_hit = (RuleI(Combat, HitCapPre10)); - else if(GetLevel() < 20 && max_hit > RuleI(Combat, HitCapPre20)) - max_hit = (RuleI(Combat, HitCapPre20)); - - CheckIncreaseSkill(skillinuse, other, -15); - CheckIncreaseSkill(EQ::skills::SkillOffense, other, -15); - - -#ifndef EQEMU_NO_WEAPON_DAMAGE_BONUS + // *************************************************************** + // *** 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); - min_hit += (int) ucDamageBonus; - max_hit += (int) ucDamageBonus; + my_hit.min_damage = ucDamageBonus; hate += ucDamageBonus; } -#endif - min_hit += min_hit * GetMeleeMinDamageMod_SE(skillinuse) / 100; - if(max_hit < min_hit) - max_hit = min_hit; + 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; - if (GetMinDamage) - return min_hit; + 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); + CommonOutgoingHitSuccess(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(roll * static_cast(hit.base_damage) + 0.5), 1); + + //Shout("mitigation %d vs offense %d. base %d rolled %f damage %d", mitigation, hit.offense, hit.base_damage, roll, hit.damage_done); +} + +int 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()) { + ac = ac_override; + 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(GetClass()), NECROMANCER, ENCHANTER)) + ac += spell_aa_ac / 3; else - return max_hit; + 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(GetClass()), NECROMANCER, ENCHANTER)) + ac += GetSkill(EQ::skills::SkillDefense) / 2 + spell_aa_ac / 3; + else + ac += GetSkill(EQ::skills::SkillDefense) / 3 + spell_aa_ac / 4; } - return 0; + 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); + } + //Shout("ACSum ac %i softcap %i returns %.2f", ac, softcap, static_cast(returns)); + } + else { + //Shout("ACSum ac %i", ac); + } + + return ac; } -void Mob::Tune_FindAccuaryByHitChance(Mob* defender, Mob *attacker, float hit_chance, int interval, int max_loop, int avoid_override, int Msg) +int Mob::Tuneoffense(EQ::skills::SkillType skill, int atk_override, int add_atk) { + int offense = GetSkill(skill); + int stat_bonus = GetSTR(); - int add_acc = 0; - float tmp_hit_chance = 0.0f; - bool end = false; + switch (skill) { + case EQ::skills::SkillArchery: + case EQ::skills::SkillThrowing: + stat_bonus = GetDEX(); + break; - EQ::skills::SkillType skillinuse = EQ::skills::SkillHandtoHand; - if (attacker->IsClient()) - {//Will check first equiped weapon for skill. Ie. remove wepaons to assess bow. - EQ::ItemInstance* weapon; - weapon = attacker->CastToClient()->GetInv().GetItem(EQ::invslot::slotPrimary); - - if(weapon && weapon->IsWeapon()){ - skillinuse = attacker->CastToClient()->AttackAnimation(EQ::invslot::slotPrimary, weapon); + // 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; +} + +int 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 { - weapon = attacker->CastToClient()->GetInv().GetItem(EQ::invslot::slotSecondary); - if (weapon && weapon->IsWeapon()) - skillinuse = attacker->CastToClient()->AttackAnimation(EQ::invslot::slotSecondary, weapon); - else { - weapon = attacker->CastToClient()->GetInv().GetItem(EQ::invslot::slotRange); - if (weapon && weapon->IsWeapon()) - skillinuse = attacker->CastToClient()->AttackAnimation(EQ::invslot::slotRange, weapon); - } + 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(tohit); + } + else if (IsBerserk()) { + tohit += (GetLevel() * 2) / 5; } } - - tmp_hit_chance = Tune_CheckHitChance(defender, attacker, skillinuse, EQ::invslot::slotPrimary, 0, 0, 0, avoid_override); - - - Message(0, "#Tune - Begin Parse [Interval %i Max Loop Iterations %i]", interval, max_loop); - Message(0, "#Tune - Processing... Find Accuracy for hit chance on attacker of (%.0f) pct on defender [Current Hit Chance %.2f]", hit_chance, tmp_hit_chance); - - - if (tmp_hit_chance > hit_chance) - interval = interval * -1; - - for (int j=0; j < max_loop; j++) - { - tmp_hit_chance = Tune_CheckHitChance(defender, attacker, skillinuse, EQ::invslot::slotPrimary, 0, false, 0, avoid_override, add_acc); - - if (Msg >= 3) - Message(Chat::Yellow, "#Tune - Processing... [%i] [ACCURACY %i] Hit Chance %.2f ",j,add_acc,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){ - - Tune_CheckHitChance(defender, attacker, skillinuse, EQ::invslot::slotPrimary, 0, Msg, 0, avoid_override);//Display Stat Report - - Message(0, " "); - - if (attacker->IsNPC()){ - Message(0, "#Recommended NPC Accuracy Statistic adjustment of ( %i ) on ' %s ' for a hit chance of (+ %.0f) pct verse ' %s '. ",add_acc,defender->GetCleanName(), hit_chance, attacker->GetCleanName()); - Message(0, "#SET: [NPC Avoidance] = [%i]",add_acc + defender->CastToNPC()->GetAccuracyRating()); - } - else if (attacker->IsClient()){ - Message(0, "#Recommended Client Accuracy Bonus adjustment of ( %i ) on ' %s ' for a hit chance of (+ %.0f) pct verse ' %s '. ",add_acc,defender->GetCleanName(), hit_chance, attacker->GetCleanName()); - Message(0, "#Modify (+/-): [Item Mod2 Accuracy] [%i]",add_acc); - Message(0, "#Modify (+/-): [SE_Accuracy(216)] [%i]",add_acc); - Message(0, "#Modify (+/-): [SE_HitChance(184)] [%i]",add_acc / 15); - } - - return; - } - - - add_acc = add_acc + interval; - } - - Message(Chat::LightGray, "#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(Chat::LightGray, "#Tune - Parse ended at ACCURACY ADJUSTMENTT ( %i ) at hit chance of (%.0f) / (%.0f) pct.",add_acc,tmp_hit_chance / hit_chance); + return std::max(tohit, 1); } -void Mob::Tune_FindAvoidanceByHitChance(Mob* defender, Mob *attacker, float hit_chance, int interval, int max_loop, int acc_override, int Msg) +// return -1 in cases that always hit +int Mob::TuneGetTotalToHit(EQ::skills::SkillType skill, int chance_mod, int accuracy_override, int add_accuracy) { - int add_avoid = 0; - float tmp_hit_chance = 0.0f; - bool end = false; + if (chance_mod >= 10000) // override for stuff like SE_SkillAttack + return -1; - EQ::skills::SkillType skillinuse = EQ::skills::SkillHandtoHand; - if (attacker->IsClient()) - {//Will check first equiped weapon for skill. Ie. remove wepaons to assess bow. - EQ::ItemInstance* weapon; - weapon = attacker->CastToClient()->GetInv().GetItem(EQ::invslot::slotPrimary); - - if(weapon && weapon->IsWeapon()){ - skillinuse = attacker->CastToClient()->AttackAnimation(EQ::invslot::slotPrimary, weapon); + // 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(accuracy) * static_cast(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 +int 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(avoidance) * static_cast(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; +} + +int 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 { - weapon = attacker->CastToClient()->GetInv().GetItem(EQ::invslot::slotSecondary); - if (weapon && weapon->IsWeapon()) - skillinuse = attacker->CastToClient()->AttackAnimation(EQ::invslot::slotSecondary, weapon); - else { - weapon = attacker->CastToClient()->GetInv().GetItem(EQ::invslot::slotRange); - if (weapon && weapon->IsWeapon()) - skillinuse = attacker->CastToClient()->AttackAnimation(EQ::invslot::slotRange, weapon); - } + 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(defense) * static_cast(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(defense); } } - tmp_hit_chance = Tune_CheckHitChance(defender, attacker, skillinuse, EQ::invslot::slotPrimary, 0, 0, acc_override, 0); - - Message(0, "#Tune - Begin Parse [Interval %i Max Loop Iterations %i]", interval, max_loop); - Message(0, "#Tune - Processing... Find Avoidance for hit chance on defender of (%.0f) pct from attacker. [Current Hit Chance %.2f]", hit_chance, tmp_hit_chance); - - if (tmp_hit_chance < hit_chance) - interval = interval * -1; - - for (int j=0; j < max_loop; j++) - { - tmp_hit_chance = Tune_CheckHitChance(defender, attacker, skillinuse, EQ::invslot::slotPrimary, 0, 0, acc_override, 0, 0, add_avoid); - - if (Msg >= 3) - Message(0, "#Tune - Processing... [%i] [AVOIDANCE %i] Hit Chance %.2f ",j,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){ - - Tune_CheckHitChance(defender, attacker, skillinuse, EQ::invslot::slotPrimary, 0, Msg, acc_override, 0);//Display Stat Report - - Message(0, " "); - - if (defender->IsNPC()){ - Message(0, "#Recommended NPC Avoidance Statistic adjustment of ( %i ) on ' %s ' for a hit chance of ( %.0f) pct from ' %s '. ",add_avoid,defender->GetCleanName(), hit_chance, attacker->GetCleanName()); - Message(0, "#SET: [NPC Avoidance] = [%i]",add_avoid + defender->CastToNPC()->GetAvoidanceRating()); - } - else if (defender->IsClient()){ - Message(0, "#Recommended Client Avoidance Bonus adjustment of ( %i ) on ' %s ' for a hit chance of ( %.0f) pct from ' %s '. ",add_avoid,defender->GetCleanName(), hit_chance, attacker->GetCleanName()); - Message(0, "#Modify (+/-): [Item Mod2 Avoidance] [%i]",add_avoid); - Message(0, "#Modify (+/-): [SE_AvoidMeleeChance(172)] [%i]",add_avoid / 10); - } - - return; - } - - add_avoid = add_avoid + interval; - } - - 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 ) at hit chance of (%.0f) / (%.0f) pct.",add_avoid,tmp_hit_chance / hit_chance); + return std::max(1, defense); } - -float Mob::Tune_CheckHitChance(Mob* defender, Mob* attacker, EQ::skills::SkillType skillinuse, int Hand, int16 chance_mod, int Msg, int acc_override, int avoid_override, int add_acc, int add_avoid) +// 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) { - float chancetohit = RuleR(Combat, BaseHitChance); + Mob *attacker = other; + Mob *defender = this; + //Shout("CheckHitChance(%s) attacked by %s", defender->GetName(), attacker->GetName()); - if(attacker->IsNPC() && !attacker->IsPet()) - chancetohit += RuleR(Combat, NPCBonusHitChance); - - if (Msg){ - - Message(0, "######### Hit Chance Report: Start [Detail Level %i]#########", Msg); - Message(0, "#ATTACKER: %s", attacker->GetCleanName()); - Message(0, "#DEFENDER: %s", defender->GetCleanName()); - if (Msg >= 2){ - Message(0, " "); - Message(0, "### Calculate Base Hit Chance ###"); - Message(0, "# + %.2f Total: %.2f #### RuleR(Combat, BaseHitChance)", RuleR(Combat, BaseHitChance), RuleR(Combat, BaseHitChance)); - if (attacker->IsNPC()) - Message(0, "# + %.2f Total: %.2f #### RuleR(Combat, NPCBonusHitChance)", RuleR(Combat, NPCBonusHitChance), chancetohit); - } - } - - float temp_chancetohit = chancetohit; - - bool pvpmode = false; - if(IsClient() && attacker->IsClient()) - pvpmode = true; - - if (chance_mod >= 10000) + if (defender->IsClient() && defender->CastToClient()->IsSitting()) return true; - float avoidanceBonus = 0; - float hitBonus = 0; + auto avoidance = defender->TuneGetTotalDefense(avoidance_override, add_avoidance); + if (avoidance == -1) // some sort of auto avoid disc + return false; - //////////////////////////////////////////////////////// - // To hit calcs go here - //////////////////////////////////////////////////////// + auto accuracy = hit.tohit; + if (accuracy == -1) + return true; - uint8 attacker_level = attacker->GetLevel() ? attacker->GetLevel() : 1; - uint8 defender_level = defender->GetLevel() ? defender->GetLevel() : 1; + // 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); + //Shout("CheckHitChance accuracy(%d => %d) avoidance(%d => %d)", accuracy, tohit_roll, avoidance, avoid_roll); - //Calculate the level difference - - 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); - } - - if (Msg >= 2) - Message(0, "# + %.2f Total: %.2f #### Level Modifers", chancetohit - temp_chancetohit, chancetohit); - - temp_chancetohit = chancetohit; - - chancetohit -= ((float)defender->GetAGI() * RuleR(Combat, AgiHitFactor)); - - if (Msg >= 2) - Message(0, "# - %.2f Total: %.2f #### DEFENDER Agility", ((float)defender->GetAGI() * RuleR(Combat, AgiHitFactor)), chancetohit); - - if(attacker->IsClient()) - { - chancetohit -= (RuleR(Combat,WeaponSkillFalloff) * (attacker->CastToClient()->MaxSkill(skillinuse) - attacker->GetSkill(skillinuse))); - if (Msg >= 2) - Message(0, "# - %.2f Total: %.2f ##### ATTACKER Wpn Skill Mod: ", (RuleR(Combat,WeaponSkillFalloff) * (attacker->CastToClient()->MaxSkill(skillinuse) - attacker->GetSkill(skillinuse))), chancetohit); - } - - if(defender->IsClient()) - { - chancetohit += (RuleR(Combat, WeaponSkillFalloff) * (defender->CastToClient()->MaxSkill(EQ::skills::SkillDefense) - defender->GetSkill(EQ::skills::SkillDefense))); - if (Msg >= 2) - Message(0, "# + %.2f Total: %.2f #### DEFENDER Defense Skill Mod", (RuleR(Combat, WeaponSkillFalloff) * (defender->CastToClient()->MaxSkill(EQ::skills::SkillDefense) - defender->GetSkill(EQ::skills::SkillDefense))), 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; - if (Msg >= 2) - Message(0, "# + %.2f Total: %.2f #### ATTACKER SE_MeleeSkillCheck(183) Spell Bonus", attacker->spellbonuses.MeleeSkillCheck , chancetohit); - } - if(attacker->itembonuses.MeleeSkillCheckSkill == skillinuse || attacker->itembonuses.MeleeSkillCheckSkill == 255) { - chancetohit += attacker->itembonuses.MeleeSkillCheck; - if (Msg >= 2) - Message(0, "# + %.2f Total: %.2f #### ATTACKER SE_MeleeSkillCheck(183) Worn Bonus", attacker->itembonuses.MeleeSkillCheck , chancetohit); - } - - if (Msg) - Message(0, "#FINAL Base Hit Chance: %.2f percent", chancetohit); - - if (Msg >= 2){ - Message(0, " "); - Message(0, "######### Calculate Avoidance Bonuses #########"); - } - - //Avoidance Bonuses on defender decreases baseline hit chance by percent. - avoidanceBonus = defender->spellbonuses.AvoidMeleeChanceEffect + - defender->itembonuses.AvoidMeleeChanceEffect + - defender->aabonuses.AvoidMeleeChanceEffect + - (defender->itembonuses.AvoidMeleeChance / 10.0f); //Item Mod 'Avoidence' - - if (Msg >= 2){ - if (defender->aabonuses.AvoidMeleeChanceEffect) - Message(0, "# %i #### DEFENDER SE_AvoidMeleeChance(172) AA Bonus", defender->aabonuses.AvoidMeleeChanceEffect); - if (defender->spellbonuses.AvoidMeleeChanceEffect) - Message(0, "# %i #### DEFENDER SE_AvoidMeleeChance(172) Spell Bonus", defender->spellbonuses.AvoidMeleeChanceEffect); - if (defender->itembonuses.AvoidMeleeChanceEffect) - Message(0, "# %i #### DEFENDER SE_AvoidMeleeChance(172) Worn Bonus", defender->itembonuses.AvoidMeleeChanceEffect); - if (defender->itembonuses.AvoidMeleeChance) - Message(0, "# %i #### DEFENDER Avoidance Item Mod2 Bonus[Amt: %i] ", defender->itembonuses.AvoidMeleeChance / 10.0f,defender->itembonuses.AvoidMeleeChance); - } - - - Mob *owner = nullptr; - if (defender->IsPet()) - owner = defender->GetOwner(); - else if ((defender->IsNPC() && defender->CastToNPC()->GetSwarmOwner())) - owner = entity_list.GetMobID(defender->CastToNPC()->GetSwarmOwner()); - - if (owner){ - avoidanceBonus += owner->aabonuses.PetAvoidance + owner->spellbonuses.PetAvoidance + owner->itembonuses.PetAvoidance; - - if (Msg >= 2){ - if (owner->aabonuses.PetAvoidance) - Message(0, "# %i #### DEFENDER SE_PetAvoidance(215) AA Bonus", owner->aabonuses.PetAvoidance); - if (owner->aabonuses.PetAvoidance) - Message(0, "# %i #### DEFENDER SE_PetAvoidance(215) Spell Bonus", owner->itembonuses.PetAvoidance); - if (owner->aabonuses.PetAvoidance) - Message(0, "# %i #### DEFENDER SE_PetAvoidance(215) Worn Bonus", owner->spellbonuses.PetAvoidance); - } - } - - if(defender->IsNPC()){ - avoidanceBonus += ((defender->CastToNPC()->GetAvoidanceRating() + add_avoid) / 10.0f); //Modifier from database - if (Msg >= 2) - Message(0, "# + %.2f #### DEFENDER NPC AVOIDANCE STAT [Stat Amt: %i] ", ((defender->CastToNPC()->GetAvoidanceRating() + add_avoid) / 10.0f),defender->CastToNPC()->GetAvoidanceRating()); - } - else if(defender->IsClient()){ - avoidanceBonus += (add_avoid / 10.0f); //Avoidance Item Mod - } - - //#tune override value - if (avoid_override){ - avoidanceBonus = (avoid_override / 10.0f); - if (Msg >= 2) - Message(0, "%.2f #### DEFENDER 'AVOIDANCE OVERRIDE'", avoidanceBonus); - } - - if (Msg) - Message(0, "#FINAL Avoidance Bonus': %.2f percent ", avoidanceBonus); - - if (Msg >= 2){ - Message(0, " "); - Message(0, "######### Calculate Accuracy Bonuses #########"); - } - - //Hit Chance Bonuses on attacker increases baseline hit chance by percent. - hitBonus += attacker->itembonuses.HitChanceEffect[skillinuse] + - attacker->spellbonuses.HitChanceEffect[skillinuse]+ - attacker->aabonuses.HitChanceEffect[skillinuse]+ - attacker->itembonuses.HitChanceEffect[EQ::skills::HIGHEST_SKILL + 1] + - attacker->spellbonuses.HitChanceEffect[EQ::skills::HIGHEST_SKILL + 1] + - attacker->aabonuses.HitChanceEffect[EQ::skills::HIGHEST_SKILL + 1]; - - if (Msg >= 2){ - if (attacker->aabonuses.HitChanceEffect[EQ::skills::HIGHEST_SKILL + 1]) - Message(0, "# %i #### ATTACKER SE_HitChance(184) AA Bonus [All Skills]", attacker->aabonuses.HitChanceEffect[EQ::skills::HIGHEST_SKILL + 1]); - if (attacker->spellbonuses.HitChanceEffect[EQ::skills::HIGHEST_SKILL + 1]) - Message(0, "# %i #### ATTACKER SE_HitChance(184) Spell Bonus [All Skills]", attacker->spellbonuses.HitChanceEffect[EQ::skills::HIGHEST_SKILL + 1]); - if (attacker->itembonuses.HitChanceEffect[EQ::skills::HIGHEST_SKILL + 1]) - Message(0, "# %i #### ATTACKER SE_HitChance(184) Worn Bonus [All Skills]", attacker->itembonuses.HitChanceEffect[EQ::skills::HIGHEST_SKILL + 1]); - if (attacker->itembonuses.HitChanceEffect[skillinuse]) - Message(0, "# %i #### ATTACKER SE_HitChance(184) AA Bonus [Skill]", attacker->aabonuses.HitChanceEffect[skillinuse]); - if (attacker->spellbonuses.HitChanceEffect[skillinuse]) - Message(0, "# %i #### ATTACKER SE_HitChance(184) Spell Bonus [Skill]", attacker->spellbonuses.HitChanceEffect[skillinuse]); - if (attacker->itembonuses.HitChanceEffect[skillinuse]) - Message(0, "# %i #### ATTACKER SE_HitChance(184) Worn Bonus [Skill]", attacker->itembonuses.HitChanceEffect[skillinuse]); - } - - //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[EQ::skills::HIGHEST_SKILL + 1] + - attacker->spellbonuses.Accuracy[EQ::skills::HIGHEST_SKILL + 1] + - attacker->aabonuses.Accuracy[EQ::skills::HIGHEST_SKILL + 1] + - attacker->aabonuses.Accuracy[skillinuse] + - attacker->itembonuses.HitChance) / 15.0f; //Item Mod 'Accuracy' - - if (Msg >= 2) { - if (attacker->aabonuses.Accuracy[EQ::skills::HIGHEST_SKILL + 1]) - Message(0, "# %.2f #### ATTACKER SE_Accuracy(216) AA Bonus [All Skills] [Stat Amt: %i]", static_cast(attacker->aabonuses.Accuracy[EQ::skills::HIGHEST_SKILL + 1]) / 15.0f, attacker->aabonuses.Accuracy[EQ::skills::HIGHEST_SKILL + 1]); - if (attacker->spellbonuses.Accuracy[EQ::skills::HIGHEST_SKILL + 1]) - Message(0, "# %.2f #### ATTACKER SE_Accuracy(216) Spell Bonus [All Skills] [Stat Amt: %i]", static_cast(attacker->spellbonuses.Accuracy[EQ::skills::HIGHEST_SKILL + 1]) / 15.0f, attacker->spellbonuses.Accuracy[EQ::skills::HIGHEST_SKILL + 1]); - if (attacker->itembonuses.Accuracy[EQ::skills::HIGHEST_SKILL + 1]) - Message(0, "# %.2f #### ATTACKER SE_Accuracy(216) Worn Bonus [All Skills] [Stat Amt: %i]", static_cast(attacker->itembonuses.Accuracy[EQ::skills::HIGHEST_SKILL + 1]) / 15.0f, attacker->itembonuses.Accuracy[EQ::skills::HIGHEST_SKILL + 1]); - if (attacker->aabonuses.Accuracy[skillinuse]) - Message(0, "# %.2f #### ATTACKER SE_Accuracy(216) AA Bonus [Skill] [Stat Amt: %i]", static_cast(attacker->aabonuses.Accuracy[skillinuse])/15.0f,attacker->aabonuses.Accuracy[skillinuse]); - if (attacker->itembonuses.HitChance) - Message(0, "# %.2f #### ATTACKER Accuracy Item Mod2 Bonus [Stat Amt: %i]", static_cast(attacker->itembonuses.HitChance)/15.0f,attacker->itembonuses.HitChance); - } - - hitBonus += chance_mod; //Modifier applied from casted/disc skill attacks. - - if(attacker->IsNPC()){ - if (acc_override){ - hitBonus = (acc_override / 10.0f); - if (Msg >= 2) - Message(0, "# %.2f #### ATTACKER 'ACCURACY OVERRIDE'", hitBonus); - } - else { - hitBonus += ((attacker->CastToNPC()->GetAccuracyRating() + add_acc) / 10.0f); //Modifier from database - if (Msg >= 2){ - Message(0, "# %.2f #### ATTACKER NPC ACCURACY STAT [Stat Amt: %i] ", ((attacker->CastToNPC()->GetAccuracyRating() + add_avoid) / 10.0f),attacker->CastToNPC()->GetAccuracyRating()); - } - } - } - else if(attacker->IsClient()){ - if (acc_override){ - hitBonus = (acc_override / 15.0f); - if (Msg >= 2) - Message(0, "# %.2f #### ATTACKER 'ACCURACY OVERRIDE': %.2f "); - } - else - hitBonus += (add_acc / 15.0f); //Modifier from database - } - - if (skillinuse == EQ::skills::SkillArchery){ - hitBonus -= hitBonus*RuleR(Combat, ArcheryHitPenalty); - if (Msg >= 2) - Message(0, "# %.2f pct #### RuleR(Combat, ArcheryHitPenalty) ", RuleR(Combat, ArcheryHitPenalty)); - } - - //Calculate final chance to hit - chancetohit += ((chancetohit * (hitBonus - avoidanceBonus)) / 100.0f); - - if (Msg){ - Message(0, "#FINAL Accuracy Bonus': %.2f percent", hitBonus); - - if (Msg >= 2) - Message(0, " "); - - Message(0, "#FINAL Hit Chance: %.2f percent [Max: %.2f Min: %.2f] ", chancetohit, RuleR(Combat,MaxChancetoHit), RuleR(Combat,MinChancetoHit) ); - Message(0, "######### Hit Chance Report: Completed #########"); - } - - chancetohit = mod_hit_chance(chancetohit, skillinuse, attacker); - - // Chance to hit; Max 95%, Min 5% DEFAULTS - if(chancetohit > 1000 || chancetohit < -1000) { - //if chance to hit is crazy high, that means a discipline is in use, and let it stay there - } - else if(chancetohit > RuleR(Combat,MaxChancetoHit)) { - chancetohit = RuleR(Combat,MaxChancetoHit); - } - else if(chancetohit < RuleR(Combat,MinChancetoHit)) { - chancetohit = RuleR(Combat,MinChancetoHit); - } - - return(chancetohit); + // 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; }