Make CheckHitChance much more live like

This should be fairly close to live-like.

Based on client decompiling, Torven's write up and parses and more parses.

It will probably break your server.
This commit is contained in:
Michael Cook (mackal) 2016-12-25 21:11:10 -05:00
parent 1d19bd11d0
commit f5827174ee
5 changed files with 124 additions and 156 deletions

View File

@ -139,175 +139,141 @@ bool Mob::AttackAnimation(EQEmu::skills::SkillType &skillinuse, int Hand, const
return true;
}
int Mob::compute_tohit(EQEmu::skills::SkillType skillinuse)
{
int tohit = GetSkill(EQEmu::skills::SkillOffense) + 7;
tohit += GetSkill(skillinuse);
if (IsNPC())
tohit += CastToNPC()->GetAccuracyRating();
if (IsClient()) {
double reduction = CastToClient()->m_pp.intoxication / 2.0;
if (reduction > 20.0) {
reduction = std::min((110 - reduction) / 100.0, 1.0);
tohit = reduction * static_cast<double>(tohit);
} else if (IsBerserk()) {
tohit += (GetLevel() * 2) / 5;
}
}
return std::max(tohit, 1);
}
// based on dev quotes
// the AGI bonus has actually drastically changed from classic
int Mob::compute_defense()
{
int defense = GetSkill(EQEmu::skills::SkillDefense) * 400 / 225;
defense += (8000 * (GetAGI() - 40)) / 36000;
if (IsClient())
defense += CastToClient()->GetHeroicAGI() / 10;
defense += itembonuses.AvoidMeleeChance; // item mod2
if (IsNPC())
defense += CastToNPC()->GetAvoidanceRating();
if (IsClient()) {
double reduction = CastToClient()->m_pp.intoxication / 2.0;
if (reduction > 20.0) {
reduction = std::min((110 - reduction) / 100.0, 1.0);
defense = reduction * static_cast<double>(defense);
}
}
return std::max(1, defense);
}
// called when a mob is attacked, does the checks to see if it's a hit
// and does other mitigation checks. 'this' is the mob being attacked.
bool Mob::CheckHitChance(Mob* other, EQEmu::skills::SkillType skillinuse, int Hand, int16 chance_mod)
bool Mob::CheckHitChance(Mob* other, EQEmu::skills::SkillType skillinuse, int chance_mod)
{
/*/
//Reworked a lot of this code to achieve better balance at higher levels.
//The old code basically meant that any in high level (50+) combat,
//both parties always had 95% chance to hit the other one.
/*/
Mob *attacker=other;
Mob *defender=this;
float chancetohit = RuleR(Combat, BaseHitChance);
if(attacker->IsNPC() && !attacker->IsPet())
chancetohit += RuleR(Combat, NPCBonusHitChance);
Mob *attacker = other;
Mob *defender = this;
Log.Out(Logs::Detail, Logs::Attack, "CheckHitChance(%s) attacked by %s", defender->GetName(), attacker->GetName());
bool pvpmode = false;
if(IsClient() && other->IsClient())
pvpmode = true;
if (chance_mod >= 10000)
// calculate defender's avoidance
auto avoidance = defender->compute_defense() + 10; // add 10 in case the NPC's stats are fucked
auto evasion_bonus = defender->spellbonuses.AvoidMeleeChanceEffect; // we check this first since it has a special case
if (evasion_bonus <= -100)
return true;
float avoidanceBonus = 0;
float hitBonus = 0;
////////////////////////////////////////////////////////
// To hit calcs go here
////////////////////////////////////////////////////////
uint8 attacker_level = attacker->GetLevel() ? attacker->GetLevel() : 1;
uint8 defender_level = defender->GetLevel() ? defender->GetLevel() : 1;
//Calculate the level difference
Log.Out(Logs::Detail, Logs::Attack, "Chance to hit before level diff calc %.2f", chancetohit);
double level_difference = attacker_level - defender_level;
double range = defender->GetLevel();
range = ((range / 4) + 3);
if(level_difference < 0)
{
if(level_difference >= -range)
{
chancetohit += (level_difference / range) * RuleR(Combat,HitFalloffMinor); //5
}
else if (level_difference >= -(range+3.0))
{
chancetohit -= RuleR(Combat,HitFalloffMinor);
chancetohit += ((level_difference+range) / (3.0)) * RuleR(Combat,HitFalloffModerate); //7
}
else
{
chancetohit -= (RuleR(Combat,HitFalloffMinor) + RuleR(Combat,HitFalloffModerate));
chancetohit += ((level_difference+range+3.0)/12.0) * RuleR(Combat,HitFalloffMajor); //50
}
}
else
{
chancetohit += (RuleR(Combat,HitBonusPerLevel) * level_difference);
}
Log.Out(Logs::Detail, Logs::Attack, "Chance to hit after level diff calc %.2f", chancetohit);
chancetohit -= ((float)defender->GetAGI() * RuleR(Combat, AgiHitFactor));
Log.Out(Logs::Detail, Logs::Attack, "Chance to hit after Agility calc %.2f", chancetohit);
if(attacker->IsClient())
{
chancetohit -= (RuleR(Combat,WeaponSkillFalloff) * (attacker->CastToClient()->MaxSkill(skillinuse) - attacker->GetSkill(skillinuse)));
Log.Out(Logs::Detail, Logs::Attack, "Chance to hit after agil calc %.2f", "Chance to hit after weapon falloff calc (attack) %.2f", chancetohit);
}
if(defender->IsClient())
{
chancetohit += (RuleR(Combat, WeaponSkillFalloff) * (defender->CastToClient()->MaxSkill(EQEmu::skills::SkillDefense) - defender->GetSkill(EQEmu::skills::SkillDefense)));
Log.Out(Logs::Detail, Logs::Attack, "Chance to hit after weapon falloff calc (defense) %.2f", chancetohit);
}
//I dont think this is 100% correct, but at least it does something...
if(attacker->spellbonuses.MeleeSkillCheckSkill == skillinuse || attacker->spellbonuses.MeleeSkillCheckSkill == 255) {
chancetohit += attacker->spellbonuses.MeleeSkillCheck;
Log.Out(Logs::Detail, Logs::Attack, "Applied spell melee skill bonus %d, yeilding %.2f", attacker->spellbonuses.MeleeSkillCheck, chancetohit);
}
if(attacker->itembonuses.MeleeSkillCheckSkill == skillinuse || attacker->itembonuses.MeleeSkillCheckSkill == 255) {
chancetohit += attacker->itembonuses.MeleeSkillCheck;
Log.Out(Logs::Detail, Logs::Attack, "Applied item melee skill bonus %d, yeilding %.2f", attacker->spellbonuses.MeleeSkillCheck, chancetohit);
}
//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 (evasion_bonus >= 10000) // some sort of auto avoid disc
return false;
// 172 Evasion aka SE_AvoidMeleeChance
evasion_bonus += defender->itembonuses.AvoidMeleeChanceEffect + defender->aabonuses.AvoidMeleeChanceEffect; // item bonus here isn't mod2 avoidance
Mob *owner = nullptr;
if (defender->IsPet())
owner = defender->GetOwner();
else if ((defender->IsNPC() && defender->CastToNPC()->GetSwarmOwner()))
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 (owner) // 215 Pet Avoidance % aka SE_PetAvoidance
evasion_bonus += owner->aabonuses.PetAvoidance + owner->spellbonuses.PetAvoidance + owner->itembonuses.PetAvoidance;
if(defender->IsNPC())
avoidanceBonus += (defender->CastToNPC()->GetAvoidanceRating() / 10.0f); //Modifier from database
// Evasion is a percentage bonus according to AA descriptions
if (evasion_bonus)
avoidance = (avoidance * (100 + evasion_bonus)) / 100;
//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[EQEmu::skills::HIGHEST_SKILL + 1] +
attacker->spellbonuses.HitChanceEffect[EQEmu::skills::HIGHEST_SKILL + 1] +
attacker->aabonuses.HitChanceEffect[EQEmu::skills::HIGHEST_SKILL + 1];
if (chance_mod >= 10000) // override for stuff like SE_SkillAttack
return true;
//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[EQEmu::skills::HIGHEST_SKILL + 1] +
attacker->spellbonuses.Accuracy[EQEmu::skills::HIGHEST_SKILL + 1] +
// calculate attacker's accuracy
auto accuracy = attacker->compute_tohit(skillinuse) + 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!
accuracy = accuracy * 6 / 5;
// 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 (skillinuse != EQEmu::skills::SkillArchery && skillinuse != EQEmu::skills::SkillThrowing)
accuracy += attacker->itembonuses.HitChance;
// 216 Melee Accuracy Amt aka SE_Accuracy -- flat bonus
accuracy += attacker->itembonuses.Accuracy[EQEmu::skills::HIGHEST_SKILL + 1] +
attacker->aabonuses.Accuracy[EQEmu::skills::HIGHEST_SKILL + 1] +
attacker->spellbonuses.Accuracy[EQEmu::skills::HIGHEST_SKILL + 1] +
attacker->itembonuses.Accuracy[skillinuse] +
attacker->aabonuses.Accuracy[skillinuse] +
attacker->itembonuses.HitChance) / 15.0f; //Item Mod 'Accuracy'
attacker->spellbonuses.Accuracy[skillinuse];
hitBonus += chance_mod; //Modifier applied from casted/disc skill attacks.
// auto hit discs (and looks like there are some autohit AAs)
if (attacker->spellbonuses.HitChanceEffect[skillinuse] >= 10000 || attacker->aabonuses.HitChanceEffect[skillinuse] >= 10000)
return true;
if(attacker->IsNPC())
hitBonus += (attacker->CastToNPC()->GetAccuracyRating() / 10.0f); //Modifier from database
if (attacker->spellbonuses.HitChanceEffect[EQEmu::skills::HIGHEST_SKILL + 1] >= 10000)
return true;
if (skillinuse == EQEmu::skills::SkillArchery)
hitBonus -= hitBonus*RuleR(Combat, ArcheryHitPenalty);
// 184 Accuracy % aka SE_HitChance -- percentage increase
auto hit_bonus = attacker->itembonuses.HitChanceEffect[EQEmu::skills::HIGHEST_SKILL + 1] +
attacker->aabonuses.HitChanceEffect[EQEmu::skills::HIGHEST_SKILL + 1] +
attacker->spellbonuses.HitChanceEffect[EQEmu::skills::HIGHEST_SKILL + 1] +
attacker->itembonuses.HitChanceEffect[skillinuse] +
attacker->aabonuses.HitChanceEffect[skillinuse] +
attacker->spellbonuses.HitChanceEffect[skillinuse];
//Calculate final chance to hit
chancetohit += ((chancetohit * (hitBonus - avoidanceBonus)) / 100.0f);
Log.Out(Logs::Detail, Logs::Attack, "Chance to hit %.2f after accuracy calc %.2f and avoidance calc %.2f", chancetohit, hitBonus, avoidanceBonus);
accuracy = (accuracy * (100 + hit_bonus)) / 100;
chancetohit = mod_hit_chance(chancetohit, skillinuse, attacker);
// TODO: April 2003 added an archery/throwing PVP accuracy penalty while moving, should be in here some where,
// but PVP is less important so I haven't tried parsing it at all
// 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);
}
// There is also 110 Ranger Archery Accuracy % which should probably be in here some where, but it's not in any spells/aas
// Name implies it's a percentage increase, if one wishes to implement, do it like the hit_bonus above but limited to ranger archery
//I dont know the best way to handle a garunteed hit discipline being used
//agains a garunteed riposte (for example) discipline... for now, garunteed hit wins
// There is also 183 UNUSED - Skill Increase Chance which devs say isn't used at all in code, but some spells reference it
// I do not recommend implementing this once since there are spells that use it which would make this not live-like with default spell files
// 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);
Log.Out(Logs::Detail, Logs::Attack, "CheckHitChance accuracy(%d => %d) avoidance(%d => %d)", accuracy, tohit_roll, avoidance, avoid_roll);
Log.Out(Logs::Detail, Logs::Attack, "3 FINAL calculated chance to hit is: %5.2f", chancetohit);
//
// Did we hit?
//
float tohit_roll = zone->random.Real(0, 100);
Log.Out(Logs::Detail, Logs::Attack, "Final hit chance: %.2f%%. Hit roll %.2f", chancetohit, tohit_roll);
return(tohit_roll <= 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;
}
bool Mob::AvoidDamage(Mob *other, int32 &damage, int hand)
@ -1182,7 +1148,7 @@ bool Client::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, b
}
Log.Out(Logs::Detail, Logs::Combat, "Avoided damage with code %d", damage);
} else {
if (other->CheckHitChance(this, skillinuse, Hand, hit_chance_bonus)) {
if (other->CheckHitChance(this, skillinuse, hit_chance_bonus)) {
other->MeleeMitigation(this, damage, min_hit, opts);
if (damage > 0)
CommonOutgoingHitSuccess(other, damage, skillinuse,opts);
@ -1742,7 +1708,7 @@ bool NPC::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, bool
if (!bRiposte && damage == -3)
DoRiposte(other);
} else {
if (other->CheckHitChance(this, skillinuse, Hand, hit_chance_bonus)) {
if (other->CheckHitChance(this, skillinuse, hit_chance_bonus)) {
other->MeleeMitigation(this, damage, min_dmg+eleBane, opts);
CommonOutgoingHitSuccess(other, damage, skillinuse, opts);
} else {

View File

@ -2038,7 +2038,7 @@ void Bot::DoMeleeSkillAttackDmg(Mob* other, uint16 weapon_damage, EQEmu::skills:
return;
}
} else {
if (other->CheckHitChance(this, skillinuse, Hand, chance_mod)) {
if (other->CheckHitChance(this, skillinuse, chance_mod)) {
other->MeleeMitigation(this, damage, min_hit);
if (damage > 0) {
damage += damage*focus/100;
@ -3865,7 +3865,7 @@ bool Bot::Attack(Mob* other, int Hand, bool FromRiposte, bool IsStrikethrough, b
}
}
} else {
if (other->CheckHitChance(this, skillinuse, Hand)) {
if (other->CheckHitChance(this, skillinuse)) {
other->MeleeMitigation(this, damage, min_hit, opts);
ApplyMeleeDamageBonus(skillinuse, damage);
damage += ((itembonuses.HeroicSTR / 10) + (damage * other->GetSkillDmgTaken(skillinuse) / 100) + GetSkillDmgAmt(skillinuse));
@ -4885,7 +4885,7 @@ void Bot::DoSpecialAttackDamage(Mob *who, EQEmu::skills::SkillType skill, int32
if (max_damage == -3)
DoRiposte(who);
} else {
if (HitChance || who->CheckHitChance(this, skill, EQEmu::inventory::slotPrimary)) {
if (HitChance || who->CheckHitChance(this, skill)) {
who->MeleeMitigation(this, max_damage, min_damage);
ApplyMeleeDamageBonus(skill, max_damage);
max_damage += who->GetFcDamageAmtIncoming(this, 0, true, skill);

View File

@ -159,7 +159,9 @@ public:
int MonkSpecialAttack(Mob* other, uint8 skill_used);
virtual void TryBackstab(Mob *other,int ReuseTime = 10);
bool AvoidDamage(Mob* attacker, int32 &damage, int hand);
virtual bool CheckHitChance(Mob* attacker, EQEmu::skills::SkillType skillinuse, int Hand, int16 chance_mod = 0);
int compute_tohit(EQEmu::skills::SkillType skillinuse);
int compute_defense();
virtual bool CheckHitChance(Mob* attacker, EQEmu::skills::SkillType skillinuse, int chance_mod = 0);
virtual void TryCriticalHit(Mob *defender, uint16 skill, int32 &damage, ExtraAttackOptions *opts = nullptr);
void TryPetCriticalHit(Mob *defender, uint16 skill, int32 &damage);
virtual bool TryFinishingBlow(Mob *defender, EQEmu::skills::SkillType skillinuse);

View File

@ -136,7 +136,7 @@ void Mob::DoSpecialAttackDamage(Mob *who, EQEmu::skills::SkillType skill, int32
if (max_damage == -3)
DoRiposte(who);
} else {
if (!CheckHitChance || (CheckHitChance && who->CheckHitChance(this, skill, EQEmu::inventory::slotPrimary))) {
if (!CheckHitChance || (CheckHitChance && who->CheckHitChance(this, skill))) {
who->MeleeMitigation(this, max_damage, min_damage);
CommonOutgoingHitSuccess(who, max_damage, skill);
} else {
@ -855,7 +855,7 @@ void Mob::DoArcheryAttackDmg(Mob* other, const EQEmu::ItemInstance* RangeWeapon
else if (AmmoItem)
SendItemAnimation(other, AmmoItem, EQEmu::skills::SkillArchery);
if (ProjectileMiss || (!ProjectileImpact && !other->CheckHitChance(this, EQEmu::skills::SkillArchery, EQEmu::inventory::slotPrimary, chance_mod))) {
if (ProjectileMiss || (!ProjectileImpact && !other->CheckHitChance(this, EQEmu::skills::SkillArchery, chance_mod))) {
Log.Out(Logs::Detail, Logs::Combat, "Ranged attack missed %s.", other->GetName());
if (LaunchProjectile){
@ -1265,7 +1265,7 @@ void NPC::DoRangedAttackDmg(Mob* other, bool Launch, int16 damage_mod, int16 cha
if (!chance_mod)
chance_mod = GetSpecialAbilityParam(SPECATK_RANGED_ATK, 2);
if (!other->CheckHitChance(this, skillInUse, EQEmu::inventory::slotRange, chance_mod))
if (!other->CheckHitChance(this, skillInUse, chance_mod))
{
other->Damage(this, 0, SPELL_UNKNOWN, skillInUse);
}
@ -1473,7 +1473,7 @@ void Mob::DoThrowingAttackDmg(Mob* other, const EQEmu::ItemInstance* RangeWeapon
else if (AmmoItem)
SendItemAnimation(other, AmmoItem, EQEmu::skills::SkillThrowing);
if (ProjectileMiss || (!ProjectileImpact && !other->CheckHitChance(this, EQEmu::skills::SkillThrowing, EQEmu::inventory::slotPrimary, chance_mod))){
if (ProjectileMiss || (!ProjectileImpact && !other->CheckHitChance(this, EQEmu::skills::SkillThrowing, chance_mod))){
Log.Out(Logs::Detail, Logs::Combat, "Ranged attack missed %s.", other->GetName());
if (LaunchProjectile){
TryProjectileAttack(other, AmmoItem, EQEmu::skills::SkillThrowing, 0, RangeWeapon, nullptr, AmmoSlot, speed);
@ -2399,7 +2399,7 @@ void Mob::DoMeleeSkillAttackDmg(Mob* other, uint16 weapon_damage, EQEmu::skills:
return;
}
} else {
if (other->CheckHitChance(this, skillinuse, Hand, chance_mod)) {
if (other->CheckHitChance(this, skillinuse, chance_mod)) {
other->MeleeMitigation(this, damage, min_hit);
CommonOutgoingHitSuccess(other, damage, skillinuse);
} else {

View File

@ -111,8 +111,8 @@ struct NPCType
uint8 mount_color; //only used by horse class
float attack_speed; //%+- on attack delay of the mob.
uint8 attack_delay; //delay between attacks in 10ths of a second
int accuracy_rating; //10 = 1% accuracy
int avoidance_rating; //10 = 1% avoidance
int accuracy_rating; // flat bonus before mods
int avoidance_rating; // flat bonus before mods
bool findable; //can be found with find command
bool trackable;
int16 slow_mitigation;