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;