From 4f07be234387b5f225263adab4e78067d7212b67 Mon Sep 17 00:00:00 2001 From: KayenEQ Date: Wed, 23 Jul 2014 21:24:21 -0400 Subject: [PATCH] Update to how bonuses are calculated in chance to hit code to be consistent across all relevant effects (treating avoidance and hit chance bonuses equally). Rule ArcheryHitPenalty will now calc correctly (Was doing basically nothing) New field npc_types 'Avoidance' (add avoidance bonus to npc) Rules for setting min / max chance to hit --- changelog.txt | 4 ++ common/ruletypes.h | 2 + .../optional/2014_07_10_AICastingRules.sql | 5 +- .../git/required/2014_07_10_npc_spells.sql | 1 + zone/attack.cpp | 72 ++++++++++--------- zone/bonuses.cpp | 28 +++++--- zone/common.h | 3 +- zone/npc.cpp | 7 ++ zone/npc.h | 3 + zone/zonedb.cpp | 2 + zone/zonedump.h | 1 + 11 files changed, 80 insertions(+), 48 deletions(-) diff --git a/changelog.txt b/changelog.txt index 761814a47..1a717d165 100644 --- a/changelog.txt +++ b/changelog.txt @@ -44,6 +44,10 @@ Param1: Max Ranged distance (default: 250) Param2: Percent Chance to Hit modifier Param3: Percent Total Damage modifier +Kayen: Updated to Chance to Hit code with how bonuses are applied to be consistent for all effects. +Added field to npc_types 'Avoidance' which will modify chance to avoid melee +Added rules to set max and min chance to hit from melee/ranged (Default 95% / 5%) + Required SQL: utils/sql/git/required/2014_07_10_npc_spells.sql Optional SQL: utils/sql/git/optional/2014_07_10_AICastingRules.sql diff --git a/common/ruletypes.h b/common/ruletypes.h index 53b5ab341..0fd413e5a 100644 --- a/common/ruletypes.h +++ b/common/ruletypes.h @@ -358,6 +358,8 @@ RULE_REAL ( Combat, HitBonusPerLevel, 1.2) //You gain this % of hit for every le RULE_REAL ( Combat, WeaponSkillFalloff, 0.33) //For every weapon skill point that's not maxed you lose this % of hit RULE_REAL ( Combat, ArcheryHitPenalty, 0.25) //Archery has a hit penalty to try to help balance it with the plethora of long term +hit modifiers for it RULE_REAL ( Combat, AgiHitFactor, 0.01) +RULE_REAL ( Combat, MinChancetoHit, 5.0) //Minimum % chance to hit with regular melee/ranged +RULE_REAL ( Combat, MaxChancetoHit, 95.0) //Maximum % chance to hit with regular melee/ranged RULE_INT ( Combat, MinRangedAttackDist, 25) //Minimum Distance to use Ranged Attacks RULE_BOOL ( Combat, ArcheryBonusRequiresStationary, true) //does the 2x archery bonus chance require a stationary npc RULE_REAL ( Combat, ArcheryBaseDamageBonus, 1) // % Modifier to Base Archery Damage (.5 = 50% base damage, 1 = 100%, 2 = 200%) diff --git a/utils/sql/git/optional/2014_07_10_AICastingRules.sql b/utils/sql/git/optional/2014_07_10_AICastingRules.sql index 2b8ea4511..e44626785 100644 --- a/utils/sql/git/optional/2014_07_10_AICastingRules.sql +++ b/utils/sql/git/optional/2014_07_10_AICastingRules.sql @@ -9,4 +9,7 @@ INSERT INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VAL INSERT INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (1, 'Spells:AI_PursueDetrimentalChance','90','Chance while chasing target to cast a detrimental spell.'); INSERT INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (1, 'Spells:AI_IdleNoSpellMinRecast','500','AI spell recast time(MS) check when no spell is cast while idle. (min time in random)'); INSERT INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (1, 'Spells:AI_IdleNoSpellMaxRecast','2000','AI spell recast time(MS) check when no spell is cast while chasing target. (max time in random)'); -INSERT INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (1, 'Spells:AI_IdleBeneficialChance','100','Chance while idle to do a beneficial spell on self or others.'); \ No newline at end of file +INSERT INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (1, 'Spells:AI_IdleBeneficialChance','100','Chance while idle to do a beneficial spell on self or others.'); + +INSERT INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (1, 'Combat:MinChancetoHit','5','Minimum % chance to hit with regular melee/ranged.'); +INSERT INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (1, 'Combat:MaxChancetoHit','95','Maximum % chance to hit with regular melee/ranged.'); \ No newline at end of file diff --git a/utils/sql/git/required/2014_07_10_npc_spells.sql b/utils/sql/git/required/2014_07_10_npc_spells.sql index fd3344d5f..1060efdd5 100644 --- a/utils/sql/git/required/2014_07_10_npc_spells.sql +++ b/utils/sql/git/required/2014_07_10_npc_spells.sql @@ -1,6 +1,7 @@ -- npc_types ALTER TABLE `npc_types` ADD `ammo_idfile` varchar( 30 ) NOT NULL DEFAULT 'IT10' AFTER `d_meele_texture2`; ALTER TABLE `npc_types` ADD `ranged_type` tinyint( 4 ) UNSIGNED NOT NULL DEFAULT '7' AFTER `sec_melee_type`; +ALTER TABLE `npc_types` ADD `Avoidance` mediumint(9) UNSIGNED NOT NULL DEFAULT '0' AFTER `Accuracy`; -- npc spells ALTER TABLE `npc_spells` ADD `range_proc` smallint(5) NOT NULL DEFAULT '-1'; diff --git a/zone/attack.cpp b/zone/attack.cpp index 4c49b9187..d2438cfae 100644 --- a/zone/attack.cpp +++ b/zone/attack.cpp @@ -202,7 +202,8 @@ bool Mob::CheckHitChance(Mob* other, SkillUseTypes skillinuse, int Hand, int16 c if (chance_mod >= 10000) return true; - float bonus; + float avoidanceBonus = 0; + float hitBonus = 0; //////////////////////////////////////////////////////// // To hit calcs go here @@ -214,6 +215,7 @@ bool Mob::CheckHitChance(Mob* other, SkillUseTypes skillinuse, int Hand, int16 c //Calculate the level difference mlog(COMBAT__TOHIT, "Chance to hit before level diff calc %.2f", chancetohit); + double level_difference = attacker_level - defender_level; double range = defender->GetLevel(); range = ((range / 4) + 3); @@ -268,37 +270,32 @@ bool Mob::CheckHitChance(Mob* other, SkillUseTypes skillinuse, int Hand, int16 c mlog(COMBAT__TOHIT, "Applied item melee skill bonus %d, yeilding %.2f", attacker->spellbonuses.MeleeSkillCheck, chancetohit); } - //subtract off avoidance by the defender. (Live AA - Combat Agility) - bonus = defender->spellbonuses.AvoidMeleeChance + defender->itembonuses.AvoidMeleeChance + (defender->aabonuses.AvoidMeleeChance * 10); + //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' - //AA Live - Elemental Agility - if (IsPet()) { - Mob *owner = defender->GetOwner(); - if (!owner)return false; - bonus += (owner->aabonuses.PetAvoidance + owner->spellbonuses.PetAvoidance + owner->itembonuses.PetAvoidance)*10; - } + 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(bonus) { - chancetohit -= ((bonus * chancetohit) / 1000); - mlog(COMBAT__TOHIT, "Applied avoidance chance %.2f/10, yeilding %.2f", bonus, chancetohit); - } - - if(attacker->IsNPC()) - chancetohit += (chancetohit * attacker->CastToNPC()->GetAccuracyRating() / 1000); - - mlog(COMBAT__TOHIT, "Chance to hit after accuracy rating calc %.2f", chancetohit); - - float hitBonus = 0; - - /* - Kayen: Unknown if the HitChance and Accuracy effect's should modify 'chancetohit' - cumulatively or successively. For now all hitBonuses are cumulative. - */ + if(defender->IsNPC()) + avoidanceBonus += (defender->CastToNPC()->GetAvoidanceRating() / 10.0f); //Modifier from database + //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[HIGHEST_SKILL+1] + - attacker->spellbonuses.HitChanceEffect[HIGHEST_SKILL+1]; + attacker->spellbonuses.HitChanceEffect[HIGHEST_SKILL+1] + + attacker->aabonuses.HitChanceEffect[HIGHEST_SKILL+1]; + //Accuracy = Spell Effect , HitChance = 'Accuracy' from Item Effect //Only AA derived accuracy can be skill limited. ie (Precision of the Pathfinder, Dead Aim) @@ -306,26 +303,31 @@ bool Mob::CheckHitChance(Mob* other, SkillUseTypes skillinuse, int Hand, int16 c attacker->spellbonuses.Accuracy[HIGHEST_SKILL+1] + attacker->aabonuses.Accuracy[HIGHEST_SKILL+1] + attacker->aabonuses.Accuracy[skillinuse] + - attacker->itembonuses.HitChance) / 15.0f; + attacker->itembonuses.HitChance) / 15.0f; //Item Mod 'Accuracy' hitBonus += chance_mod; //Modifier applied from casted/disc skill attacks. - chancetohit += ((chancetohit * hitBonus) / 100.0f); - + if(attacker->IsNPC()) + hitBonus += (attacker->CastToNPC()->GetAccuracyRating() / 10.0f); //Modifier from database + if(skillinuse == SkillArchery) - chancetohit -= (chancetohit * RuleR(Combat, ArcheryHitPenalty)) / 100.0f; + hitBonus -= hitBonus*RuleR(Combat, ArcheryHitPenalty); + + //Calculate final chance to hit + chancetohit += ((chancetohit * (hitBonus - avoidanceBonus)) / 100.0f); + mlog(COMBAT__TOHIT, "Chance to hit %.2f after accuracy calc %.2f and avoidance calc %.2f", chancetohit, hitBonus, avoidanceBonus); chancetohit = mod_hit_chance(chancetohit, skillinuse, attacker); - // Chance to hit; Max 95%, Min 30% + // 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 > 95) { - chancetohit = 95; + else if(chancetohit > RuleR(Combat,MaxChancetoHit)) { + chancetohit = RuleR(Combat,MaxChancetoHit); } - else if(chancetohit < 5) { - chancetohit = 5; + else if(chancetohit < RuleR(Combat,MinChancetoHit)) { + chancetohit = RuleR(Combat,MinChancetoHit); } //I dont know the best way to handle a garunteed hit discipline being used diff --git a/zone/bonuses.cpp b/zone/bonuses.cpp index eb251412a..cabdd4aa4 100644 --- a/zone/bonuses.cpp +++ b/zone/bonuses.cpp @@ -879,7 +879,7 @@ void Client::ApplyAABonuses(uint32 aaid, uint32 slots, StatBonuses* newbon) newbon->PetMaxHP += base1; break; case SE_AvoidMeleeChance: - newbon->AvoidMeleeChance += base1; + newbon->AvoidMeleeChanceEffect += base1; break; case SE_CombatStability: newbon->CombatStability += base1; @@ -989,6 +989,14 @@ void Client::ApplyAABonuses(uint32 aaid, uint32 slots, StatBonuses* newbon) newbon->CrippBlowChance += base1; break; + case SE_HitChance: + { + if(base2 == -1) + newbon->HitChanceEffect[HIGHEST_SKILL+1] += base1; + else + newbon->HitChanceEffect[base2] += base1; + } + case SE_ProcOnKillShot: for(int i = 0; i < MAX_SPELL_TRIGGER*3; i+=3) { @@ -1833,16 +1841,14 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* ne case SE_AvoidMeleeChance: { - //multiplier is to be compatible with item effects, watching for overflow too - effect_value = effect_value<3000? effect_value * 10 : 30000; if (RuleB(Spells, AdditiveBonusValues) && item_bonus) - newbon->AvoidMeleeChance += effect_value; + newbon->AvoidMeleeChanceEffect += effect_value; - else if((effect_value < 0) && (newbon->AvoidMeleeChance > effect_value)) - newbon->AvoidMeleeChance = effect_value; + else if((effect_value < 0) && (newbon->AvoidMeleeChanceEffect > effect_value)) + newbon->AvoidMeleeChanceEffect = effect_value; - else if(newbon->AvoidMeleeChance < effect_value) - newbon->AvoidMeleeChance = effect_value; + else if(newbon->AvoidMeleeChanceEffect < effect_value) + newbon->AvoidMeleeChanceEffect = effect_value; break; } @@ -3610,9 +3616,9 @@ void Mob::NegateSpellsBonuses(uint16 spell_id) break; case SE_AvoidMeleeChance: - spellbonuses.AvoidMeleeChance = effect_value; - aabonuses.AvoidMeleeChance = effect_value; - itembonuses.AvoidMeleeChance = effect_value; + spellbonuses.AvoidMeleeChanceEffect = effect_value; + aabonuses.AvoidMeleeChanceEffect = effect_value; + itembonuses.AvoidMeleeChanceEffect = effect_value; break; case SE_RiposteChance: diff --git a/zone/common.h b/zone/common.h index 2aa0101be..525ced928 100644 --- a/zone/common.h +++ b/zone/common.h @@ -275,7 +275,8 @@ struct StatBonuses { int16 CriticalHealOverTime; //i int16 CriticalDoTChance; //i int16 CrippBlowChance; // - int16 AvoidMeleeChance; //AvoidMeleeChance/10 == % chance i = Avoidance + int16 AvoidMeleeChance; //AvoidMeleeChance/10 == % chance i = Avoidance (item mod) + int16 AvoidMeleeChanceEffect; //AvoidMeleeChance Spell Effect int16 RiposteChance; //i int16 DodgeChance; //i int16 ParryChance; //i diff --git a/zone/npc.cpp b/zone/npc.cpp index fe71ac2e2..565a89598 100644 --- a/zone/npc.cpp +++ b/zone/npc.cpp @@ -194,6 +194,7 @@ NPC::NPC(const NPCType* d, Spawn2* in_respawn, float x, float y, float z, float } accuracy_rating = d->accuracy_rating; + avoidance_rating = d->avoidance_rating; ATK = d->ATK; CalcMaxMana(); @@ -1927,6 +1928,12 @@ void NPC::ModifyNPCStat(const char *identifier, const char *newValue) return; } + if(id == "avoidance") + { + avoidance_rating = atoi(val.c_str()); + return; + } + if(id == "trackable") { trackable = atoi(val.c_str()); diff --git a/zone/npc.h b/zone/npc.h index f6152496f..d7290645c 100644 --- a/zone/npc.h +++ b/zone/npc.h @@ -333,6 +333,8 @@ public: int32 GetAccuracyRating() const { return (accuracy_rating); } void SetAccuracyRating(int32 d) { accuracy_rating = d;} + int32 GetAvoidanceRating() const { return (avoidance_rating); } + void SetAvoidanceRating(int32 d) { avoidance_rating = d;} int32 GetRawAC() const { return AC; } void ModifyNPCStat(const char *identifier, const char *newValue); @@ -441,6 +443,7 @@ protected: uint32 max_dmg; uint32 min_dmg; int32 accuracy_rating; + int32 avoidance_rating; int16 attack_count; uint32 npc_mana; float spellscale; diff --git a/zone/zonedb.cpp b/zone/zonedb.cpp index b02ccea0f..98b302ba4 100644 --- a/zone/zonedb.cpp +++ b/zone/zonedb.cpp @@ -1105,6 +1105,7 @@ const NPCType* ZoneDatabase::GetNPCType (uint32 id) { "npc_types.see_improved_hide," "npc_types.ATK," "npc_types.Accuracy," + "npc_types.Avoidance," "npc_types.slow_mitigation," "npc_types.maxlevel," "npc_types.scalerate," @@ -1290,6 +1291,7 @@ const NPCType* ZoneDatabase::GetNPCType (uint32 id) { tmpNPCType->see_improved_hide = atoi(row[r++])==0?false:true; tmpNPCType->ATK = atoi(row[r++]); tmpNPCType->accuracy_rating = atoi(row[r++]); + tmpNPCType->avoidance_rating = atoi(row[r++]); tmpNPCType->slow_mitigation = atoi(row[r++]); tmpNPCType->maxlevel = atoi(row[r++]); tmpNPCType->scalerate = atoi(row[r++]); diff --git a/zone/zonedump.h b/zone/zonedump.h index 7a2e4a5ec..3b5b91577 100644 --- a/zone/zonedump.h +++ b/zone/zonedump.h @@ -112,6 +112,7 @@ struct NPCType uint8 mount_color; //only used by horse class float attack_speed; //%+- on attack delay of the mob. int accuracy_rating; //10 = 1% accuracy + int avoidance_rating; //10 = 1% avoidance bool findable; //can be found with find command bool trackable; int16 slow_mitigation;