From 965bb039be1124dd03ecb77568f7d32eb373e68d Mon Sep 17 00:00:00 2001 From: KayenEQ Date: Thu, 10 Jul 2014 22:46:39 -0400 Subject: [PATCH 01/11] Updates to npc_spells and npc_types table. Implemented innate defensive and range procs Implemented ability to fine tune AI casting behavior/timers Global rules for AI casting behavior/timers NPC Ranged attack updates, set skill and ammo type in npc_types Various clean ups in attack related functions. Other minor fixes. See Change Log, +required, +optional SQL --- changelog.txt | 33 +++ common/ruletypes.h | 13 + .../optional/2014_07_10_AICastingRules.sql | 12 + .../git/required/2014_07_10_npc_spells.sql | 21 ++ zone/MobAI.cpp | 142 ++++++++- zone/attack.cpp | 195 ++++++------- zone/bot.cpp | 4 +- zone/bot.h | 2 +- zone/mob.cpp | 36 ++- zone/mob.h | 16 +- zone/npc.cpp | 2 + zone/npc.h | 24 +- zone/special_attacks.cpp | 275 ++++++------------ zone/spell_effects.cpp | 4 +- zone/spells.cpp | 6 +- zone/zonedb.cpp | 4 + zone/zonedb.h | 18 +- zone/zonedump.h | 2 + 18 files changed, 488 insertions(+), 321 deletions(-) create mode 100644 utils/sql/git/optional/2014_07_10_AICastingRules.sql create mode 100644 utils/sql/git/required/2014_07_10_npc_spells.sql diff --git a/changelog.txt b/changelog.txt index a55917f97..217f71ecd 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,38 @@ EQEMu Changelog (Started on Sept 24, 2003 15:50) ------------------------------------------------------- +== 07/10/2014 == +Kayen: Updated table npc_spells to now support defensive and ranged procs. +Note: Proc rate modifier work as it does for spell effects (ie 200 = 200% baseline chance modifier) +Table is also now contains 12 AI spell casting variables that can be set to fine tune casting behaviors per spell set. +Global default rules have also been added that can further fine tune all content if no specific variables are set. + +Descriptions of new AI casting fields in npc_spells +'fail_recast' AI spell recast time(MS) when an spell is cast but fails (ie stunned) +'engaged_no_sp_recast_min' AI spell recast time(MS) checked when no spell is cast while engaged in combat. (min time in random) +'engaged_no_sp_recast_max' AI spell recast time(MS) checked when no spell is cast while engaged in combat. (max time in random) +'engaged_b_self_chance' Chance during first AI Cast check to do a beneficial spell on self (ie check to heal self) +'engaged_b_other_chance' Chance during second AI Cast check to do a beneficial spell on others.(ie check to heal others) +'engaged_d_chance' 'Chance during third AI Cast check to do a determental spell on others (ie check to nuke others) +'pursue_no_sp_recast_min' AI spell recast time(MS) checked when no spell is cast while chasing target. (min time in random) +'pursue_no_sp_recast_max' AI spell recast time(MS) checked when no spell is cast while chasing target. (max time in random) +'pursue_d_chance' Chance while chasing target to cast a detrimental spell. +'idle_no_sp_recast_min' AI spell recast time(MS) checked when no spell is cast while idle. (min time in random) +'idle_no_sp_recast_max' AI spell recast time(MS) checked when no spell is cast while idle. (max time in random) +'idle_b_chance' Chance to cast a beneficial spell while idle (ie cast heal on self while out of combat). + +Kayen: Updated table npc_types, adding field 'ranged_type' and 'ammo_idfile' +'ranged_type' Will set what skill / animation is used when NPC uses a ranged attacked (special ability 11) +'ammo_idfile' Will set what projectile graphic an NPC uses in a ranged attacked (special ability 11) Format IT#### (same as item 'idfile') +(*Set to IT11118 for some fun*) +Added parameters: SPECATK_RANGED_ATK = 11 +Param0: Min Ranged distance (default: 25) +Param1: Max Ranged distance (default: 250) +Param2: Percent Chance to Hit modifier +Param3: Percent Total Damage modifier + +Required SQL: utils/sql/git/required/2014_07_10_npc_spells.sql +Optional SQL: utils/sql/git/optional/2014_07_10_AICastingRules.sql + == 07/5/2014 == Kayen: Updated SE_Sanctuary - Adjust way hate lowering effect worked to be more accurate Kayen: Updated SE_SympatheticProc - Revised proc rate formula to be accurate to live. diff --git a/common/ruletypes.h b/common/ruletypes.h index 8c9ac543e..53b5ab341 100644 --- a/common/ruletypes.h +++ b/common/ruletypes.h @@ -311,6 +311,19 @@ RULE_INT ( Spells, FRProjectileItem_NPC, 80684) // Item id for NPC Fire 'spell p RULE_BOOL ( Spells, UseLiveSpellProjectileGFX, false) // Use spell projectile graphics set in the spells_new table (player_1). Server must be using UF+ spell file. RULE_BOOL ( Spells, FocusCombatProcs, false) //Allow all combat procs to receive focus effects. RULE_BOOL ( Spells, PreNerfBardAEDoT, false) //Allow bard AOE dots to damage targets when moving. +RULE_INT ( Spells, AI_SpellCastFinishedFailRecast, 800) // AI spell recast time(MS) when an spell is cast but fails (ie stunned). +RULE_INT ( Spells, AI_EngagedNoSpellMinRecast, 500) // AI spell recast time(MS) check when no spell is cast while engaged. (min time in random) +RULE_INT ( Spells, AI_EngagedNoSpellMaxRecast, 1000) // AI spell recast time(MS) check when no spell is cast engaged.(max time in random) +RULE_INT ( Spells, AI_EngagedBeneficialSelfChance, 100) // Chance during first AI Cast check to do a beneficial spell on self. +RULE_INT ( Spells, AI_EngagedBeneficialOtherChance, 25) // Chance during second AI Cast check to do a beneficial spell on others. +RULE_INT ( Spells, AI_EngagedDetrimentalChance, 20) // Chance during third AI Cast check to do a determental spell on others. +RULE_INT ( Spells, AI_PursueNoSpellMinRecast, 500) // AI spell recast time(MS) check when no spell is cast while chasing target. (min time in random) +RULE_INT ( Spells, AI_PursueNoSpellMaxRecast, 2000) // AI spell recast time(MS) check when no spell is cast while chasing target. (max time in random) +RULE_INT ( Spells, AI_PursueDetrimentalChance, 90) // Chance while chasing target to cast a detrimental spell. +RULE_INT ( Spells, AI_IdleNoSpellMinRecast, 500) // AI spell recast time(MS) check when no spell is cast while idle. (min time in random) +RULE_INT ( Spells, AI_IdleNoSpellMaxRecast, 2000) // AI spell recast time(MS) check when no spell is cast while chasing target. (max time in random) +RULE_INT ( Spells, AI_IdleBeneficialChance, 100) // Chance while idle to do a beneficial spell on self or others. + RULE_CATEGORY_END() RULE_CATEGORY( Combat ) diff --git a/utils/sql/git/optional/2014_07_10_AICastingRules.sql b/utils/sql/git/optional/2014_07_10_AICastingRules.sql new file mode 100644 index 000000000..ad88a2e80 --- /dev/null +++ b/utils/sql/git/optional/2014_07_10_AICastingRules.sql @@ -0,0 +1,12 @@ +INSERT INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (1, 'Spells, AI_SpellCastFinishedFailRecast','800', 'AI spell recast time(MS) when an spell is cast but fails (ie stunned)'); +INSERT INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (1, 'Spells, AI_EngagedNoSpellMinRecast','500', 'AI spell recast time(MS) check when no spell is cast while engaged. (min time in random)'); +INSERT INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (1, 'Spells, AI_EngagedNoSpellMaxRecast','1000','AI spell recast time(MS) check when no spell is cast engaged.(max time in random)'); +INSERT INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (1, 'Spells, AI_EngagedBeneficialSelfChance,','100', 'Chance during first AI Cast check to do a beneficial spell on self.'); +INSERT INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (1, 'Spells, AI_EngagedBeneficialOtherChance','25', 'Chance during second AI Cast check to do a beneficial spell on others.'); +INSERT INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (1, 'Spells, AI_EngagedDetrimentalChance','20','Chance during third AI Cast check to do a determental spell on others.'); +INSERT INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (1, 'Spells, AI_PursueNoSpellMinRecast','500', 'AI spell recast time(MS) check when no spell is cast while chasing target. (min time in random)'); +INSERT INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (1, 'Spells, AI_PursueNoSpellMaxRecast','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_PursueDetrimentalChance','90','Chance during third AI Cast check to do a determental spell on self.'); +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 diff --git a/utils/sql/git/required/2014_07_10_npc_spells.sql b/utils/sql/git/required/2014_07_10_npc_spells.sql new file mode 100644 index 000000000..fd3344d5f --- /dev/null +++ b/utils/sql/git/required/2014_07_10_npc_spells.sql @@ -0,0 +1,21 @@ +-- 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`; + +-- npc spells +ALTER TABLE `npc_spells` ADD `range_proc` smallint(5) NOT NULL DEFAULT '-1'; +ALTER TABLE `npc_spells` ADD `rproc_chance` smallint(5) NOT NULL DEFAULT '0'; +ALTER TABLE `npc_spells` ADD `defensive_proc` smallint(5) NOT NULL DEFAULT '-1'; +ALTER TABLE `npc_spells` ADD `dproc_chance` smallint(5) NOT NULL DEFAULT '0'; +ALTER TABLE `npc_spells` ADD `fail_recast` int(11) unsigned NOT NULL DEFAULT '0'; +ALTER TABLE `npc_spells` ADD `engaged_no_sp_recast_min` int(11) unsigned NOT NULL DEFAULT '0'; +ALTER TABLE `npc_spells` ADD `engaged_no_sp_recast_max` int(11) unsigned NOT NULL DEFAULT '0'; +ALTER TABLE `npc_spells` ADD `engaged_b_self_chance` tinyint(3) unsigned NOT NULL DEFAULT '0'; +ALTER TABLE `npc_spells` ADD `engaged_b_other_chance` tinyint(3) unsigned NOT NULL DEFAULT '0'; +ALTER TABLE `npc_spells` ADD `engaged_d_chance` tinyint(3) unsigned NOT NULL DEFAULT '0'; +ALTER TABLE `npc_spells` ADD `pursue_no_sp_recast_min` int(3) unsigned NOT NULL DEFAULT '0'; +ALTER TABLE `npc_spells` ADD `pursue_no_sp_recast_max` int(11) unsigned NOT NULL DEFAULT '0'; +ALTER TABLE `npc_spells` ADD `pursue_d_chance` tinyint(3) unsigned NOT NULL DEFAULT '0'; +ALTER TABLE `npc_spells` ADD `idle_no_sp_recast_min` int(11) unsigned NOT NULL DEFAULT '0'; +ALTER TABLE `npc_spells` ADD `idle_no_sp_recast_max` int(11) unsigned NOT NULL DEFAULT '0'; +ALTER TABLE `npc_spells` ADD `idle_b_chance` tinyint(11) unsigned NOT NULL DEFAULT '0'; \ No newline at end of file diff --git a/zone/MobAI.cpp b/zone/MobAI.cpp index 386b31e2b..f2db25b10 100644 --- a/zone/MobAI.cpp +++ b/zone/MobAI.cpp @@ -1897,7 +1897,7 @@ void NPC::AI_Event_SpellCastFinished(bool iCastSucceeded, uint8 slot) { AIautocastspell_timer->Start(recovery_time, false); } else - AIautocastspell_timer->Start(800, false); + AIautocastspell_timer->Start(AISpellVar.fail_recast, false); casting_spell_AIindex = AIspells.size(); } } @@ -1910,13 +1910,13 @@ bool NPC::AI_EngagedCastCheck() { mlog(AI__SPELLS, "Engaged autocast check triggered. Trying to cast healing spells then maybe offensive spells."); // try casting a heal or gate - if (!AICastSpell(this, 100, SpellType_Heal | SpellType_Escape | SpellType_InCombatBuff)) { + if (!AICastSpell(this, AISpellVar.engaged_beneficial_self_chance, SpellType_Heal | SpellType_Escape | SpellType_InCombatBuff)) { // try casting a heal on nearby - if (!entity_list.AICheckCloseBeneficialSpells(this, 25, MobAISpellRange, SpellType_Heal)) { + if (!entity_list.AICheckCloseBeneficialSpells(this, AISpellVar.engaged_beneficial_other_chance, MobAISpellRange, SpellType_Heal)) { //nobody to heal, try some detrimental spells. - if(!AICastSpell(GetTarget(), 20, SpellType_Nuke | SpellType_Lifetap | SpellType_DOT | SpellType_Dispel | SpellType_Mez | SpellType_Slow | SpellType_Debuff | SpellType_Charm | SpellType_Root)) { + if(!AICastSpell(GetTarget(), AISpellVar.engaged_detrimental_chance, SpellType_Nuke | SpellType_Lifetap | SpellType_DOT | SpellType_Dispel | SpellType_Mez | SpellType_Slow | SpellType_Debuff | SpellType_Charm | SpellType_Root)) { //no spell to cast, try again soon. - AIautocastspell_timer->Start(RandomTimer(500, 1000), false); + AIautocastspell_timer->Start(RandomTimer(AISpellVar.engaged_no_sp_recast_min, AISpellVar.engaged_no_sp_recast_max), false); } } } @@ -1931,9 +1931,9 @@ bool NPC::AI_PursueCastCheck() { AIautocastspell_timer->Disable(); //prevent the timer from going off AGAIN while we are casting. mlog(AI__SPELLS, "Engaged (pursuing) autocast check triggered. Trying to cast offensive spells."); - if(!AICastSpell(GetTarget(), 90, SpellType_Root | SpellType_Nuke | SpellType_Lifetap | SpellType_Snare | SpellType_DOT | SpellType_Dispel | SpellType_Mez | SpellType_Slow | SpellType_Debuff)) { + if(!AICastSpell(GetTarget(), AISpellVar.pursue_detrimental_chance, SpellType_Root | SpellType_Nuke | SpellType_Lifetap | SpellType_Snare | SpellType_DOT | SpellType_Dispel | SpellType_Mez | SpellType_Slow | SpellType_Debuff)) { //no spell cast, try again soon. - AIautocastspell_timer->Start(RandomTimer(500, 2000), false); + AIautocastspell_timer->Start(RandomTimer(AISpellVar.pursue_no_sp_recast_min, AISpellVar.pursue_no_sp_recast_max), false); } //else, spell casting finishing will reset the timer. return(true); } @@ -1946,11 +1946,11 @@ bool NPC::AI_IdleCastCheck() { std::cout << "Non-Engaged autocast check triggered: " << this->GetName() << std::endl; #endif AIautocastspell_timer->Disable(); //prevent the timer from going off AGAIN while we are casting. - if (!AICastSpell(this, 100, SpellType_Heal | SpellType_Buff | SpellType_Pet)) { + if (!AICastSpell(this, AISpellVar.idle_beneficial_chance, SpellType_Heal | SpellType_Buff | SpellType_Pet)) { if(!entity_list.AICheckCloseBeneficialSpells(this, 33, MobAISpellRange, SpellType_Heal | SpellType_Buff)) { //if we didnt cast any spells, our autocast timer just resets to the //last duration it was set to... try to put up a more reasonable timer... - AIautocastspell_timer->Start(RandomTimer(1000, 5000), false); + AIautocastspell_timer->Start(RandomTimer(AISpellVar.idle_no_sp_recast_min, AISpellVar.idle_no_sp_recast_max), false); } //else, spell casting finishing will reset the timer. } //else, spell casting finishing will reset the timer. return(true); @@ -2340,11 +2340,44 @@ bool NPC::AI_AddNPCSpells(uint32 iDBSpellsID) { std::cout << " (not found)"; std::cout << std::endl; #endif - int16 attack_proc_spell = -1; + uint16 attack_proc_spell = -1; int8 proc_chance = 3; + uint16 range_proc_spell = -1; + int16 rproc_chance = 0; + uint16 defensive_proc_spell = -1; + int16 dproc_chance = 0; + uint32 _fail_recast = 0; + uint32 _engaged_no_sp_recast_min = 0; + uint32 _engaged_no_sp_recast_max = 0; + uint8 _engaged_beneficial_self_chance = 0; + uint8 _engaged_beneficial_other_chance = 0; + uint8 _engaged_detrimental_chance = 0; + uint32 _pursue_no_sp_recast_min = 0; + uint32 _pursue_no_sp_recast_max = 0; + uint8 _pursue_detrimental_chance = 0; + uint32 _idle_no_sp_recast_min = 0; + uint32 _idle_no_sp_recast_max = 0; + uint8 _idle_beneficial_chance = 0; + if (parentlist) { attack_proc_spell = parentlist->attack_proc; proc_chance = parentlist->proc_chance; + range_proc_spell = parentlist->range_proc; + rproc_chance = parentlist->rproc_chance; + defensive_proc_spell = parentlist->defensive_proc; + dproc_chance = parentlist->dproc_chance; + _fail_recast = parentlist->fail_recast; + _engaged_no_sp_recast_min = parentlist->engaged_no_sp_recast_min; + _engaged_no_sp_recast_max = parentlist->engaged_no_sp_recast_max; + _engaged_beneficial_self_chance = parentlist->engaged_beneficial_self_chance; + _engaged_beneficial_other_chance = parentlist->engaged_beneficial_other_chance; + _engaged_detrimental_chance = parentlist->engaged_detrimental_chance; + _pursue_no_sp_recast_min = parentlist->pursue_no_sp_recast_min; + _pursue_no_sp_recast_max = parentlist->pursue_no_sp_recast_max; + _pursue_detrimental_chance = parentlist->pursue_detrimental_chance; + _idle_no_sp_recast_min = parentlist->idle_no_sp_recast_min; + _idle_no_sp_recast_max = parentlist->idle_no_sp_recast_max; + _idle_beneficial_chance = parentlist->idle_beneficial_chance; for (i=0; inumentries; i++) { if (GetLevel() >= parentlist->entries[i].minlevel && GetLevel() <= parentlist->entries[i].maxlevel && parentlist->entries[i].spellid > 0) { if (!IsSpellInList(spell_list, parentlist->entries[i].spellid)) @@ -2361,6 +2394,36 @@ bool NPC::AI_AddNPCSpells(uint32 iDBSpellsID) { attack_proc_spell = spell_list->attack_proc; proc_chance = spell_list->proc_chance; } + + if (spell_list->range_proc >= 0) { + range_proc_spell = spell_list->range_proc; + rproc_chance = spell_list->rproc_chance; + } + + if (spell_list->defensive_proc >= 0) { + defensive_proc_spell = spell_list->defensive_proc; + dproc_chance = spell_list->dproc_chance; + } + + //If any casting variables are defined in the current list, ignore those in the parent list. + if (spell_list->fail_recast || spell_list->engaged_no_sp_recast_min || spell_list->engaged_no_sp_recast_max + || spell_list->engaged_beneficial_self_chance || spell_list->engaged_beneficial_other_chance || spell_list->engaged_detrimental_chance + || spell_list->pursue_no_sp_recast_min || spell_list->pursue_no_sp_recast_max || spell_list->pursue_detrimental_chance + || spell_list->idle_no_sp_recast_min || spell_list->idle_no_sp_recast_max || spell_list->idle_beneficial_chance) { + _fail_recast = spell_list->fail_recast; + _engaged_no_sp_recast_min = spell_list->engaged_no_sp_recast_min; + _engaged_no_sp_recast_max = spell_list->engaged_no_sp_recast_max; + _engaged_beneficial_self_chance = spell_list->engaged_beneficial_self_chance; + _engaged_beneficial_other_chance = spell_list->engaged_beneficial_other_chance; + _engaged_detrimental_chance = spell_list->engaged_detrimental_chance; + _pursue_no_sp_recast_min = spell_list->pursue_no_sp_recast_min; + _pursue_no_sp_recast_max = spell_list->pursue_no_sp_recast_max; + _pursue_detrimental_chance = spell_list->pursue_detrimental_chance; + _idle_no_sp_recast_min = spell_list->idle_no_sp_recast_min; + _idle_no_sp_recast_max = spell_list->idle_no_sp_recast_max; + _idle_beneficial_chance = spell_list->idle_beneficial_chance; + } + for (i=0; inumentries; i++) { if (GetLevel() >= spell_list->entries[i].minlevel && GetLevel() <= spell_list->entries[i].maxlevel && spell_list->entries[i].spellid > 0) { AddSpellToNPCList(spell_list->entries[i].priority, @@ -2371,9 +2434,30 @@ bool NPC::AI_AddNPCSpells(uint32 iDBSpellsID) { } std::sort(AIspells.begin(), AIspells.end(), Compare_AI_Spells); - if (attack_proc_spell > 0) + if (IsValidSpell(attack_proc_spell)) AddProcToWeapon(attack_proc_spell, true, proc_chance); + if (IsValidSpell(range_proc_spell)) + AddRangedProc(range_proc_spell, (rproc_chance + 100)); + + if (IsValidSpell(defensive_proc_spell)) + AddDefensiveProc(defensive_proc_spell, (dproc_chance + 100)); + + //Set AI casting variables + + AISpellVar.fail_recast = (_fail_recast) ? _fail_recast : RuleI(Spells, AI_SpellCastFinishedFailRecast); + AISpellVar.engaged_no_sp_recast_min = (_engaged_no_sp_recast_min) ? _engaged_no_sp_recast_min : RuleI(Spells, AI_EngagedNoSpellMinRecast); + AISpellVar.engaged_no_sp_recast_max = (_engaged_no_sp_recast_max) ? _engaged_no_sp_recast_max : RuleI(Spells, AI_EngagedNoSpellMaxRecast); + AISpellVar.engaged_beneficial_self_chance = (_engaged_beneficial_self_chance) ? _engaged_beneficial_self_chance : RuleI(Spells, AI_EngagedBeneficialSelfChance); + AISpellVar.engaged_beneficial_other_chance = (_engaged_beneficial_other_chance) ? _engaged_beneficial_other_chance : RuleI(Spells, AI_EngagedBeneficialOtherChance); + AISpellVar.engaged_detrimental_chance = (_engaged_detrimental_chance) ? _engaged_detrimental_chance : RuleI(Spells, AI_EngagedDetrimentalChance); + AISpellVar.pursue_no_sp_recast_min = (_pursue_no_sp_recast_min) ? _pursue_no_sp_recast_min : RuleI(Spells, AI_PursueNoSpellMinRecast); + AISpellVar.pursue_no_sp_recast_max = (_pursue_no_sp_recast_max) ? _pursue_no_sp_recast_max : RuleI(Spells, AI_PursueNoSpellMaxRecast); + AISpellVar.pursue_detrimental_chance = (_pursue_detrimental_chance) ? _pursue_detrimental_chance : RuleI(Spells, AI_PursueDetrimentalChance); + AISpellVar.idle_no_sp_recast_min = (_idle_no_sp_recast_min) ? _idle_no_sp_recast_min : RuleI(Spells, AI_IdleNoSpellMinRecast); + AISpellVar.idle_no_sp_recast_max = (_idle_no_sp_recast_max) ? _idle_no_sp_recast_max : RuleI(Spells, AI_IdleNoSpellMaxRecast); + AISpellVar.idle_beneficial_chance = (_idle_beneficial_chance) ? _idle_beneficial_chance : RuleI(Spells, AI_IdleBeneficialChance); + if (AIspells.size() == 0) AIautocastspell_timer->Disable(); else @@ -2568,13 +2652,29 @@ DBnpcspells_Struct* ZoneDatabase::GetNPCSpells(uint32 iDBSpellsID) { MYSQL_RES *result; MYSQL_ROW row; - if (RunQuery(query, MakeAnyLenString(&query, "SELECT id, parent_list, attack_proc, proc_chance from npc_spells where id=%d", iDBSpellsID), errbuf, &result)) { + if (RunQuery(query, MakeAnyLenString(&query, "SELECT id, parent_list, attack_proc, proc_chance, range_proc, rproc_chance, defensive_proc, dproc_chance, fail_recast, engaged_no_sp_recast_min, engaged_no_sp_recast_max, engaged_b_self_chance, engaged_b_other_chance, engaged_d_chance, pursue_no_sp_recast_min, pursue_no_sp_recast_max, pursue_d_chance, idle_no_sp_recast_min, idle_no_sp_recast_max, idle_b_chance from npc_spells where id=%d", iDBSpellsID), errbuf, &result)) { safe_delete_array(query); if (mysql_num_rows(result) == 1) { row = mysql_fetch_row(result); uint32 tmpparent_list = atoi(row[1]); - int16 tmpattack_proc = atoi(row[2]); + uint16 tmpattack_proc = atoi(row[2]); uint8 tmpproc_chance = atoi(row[3]); + uint16 tmprange_proc = atoi(row[4]); + int16 tmprproc_chance = atoi(row[5]); + uint16 tmpdefensive_proc = atoi(row[6]); + int16 tmpdproc_chance = atoi(row[7]); + uint32 tmppfail_recast = atoi(row[8]); + uint32 tmpengaged_no_sp_recast_min = atoi(row[9]); + uint32 tmpengaged_no_sp_recast_max = atoi(row[10]); + uint8 tmpengaged_b_self_chance = atoi(row[11]); + uint8 tmpengaged_b_other_chance = atoi(row[12]); + uint8 tmpengaged_d_chance = atoi(row[13]); + uint32 tmppursue_no_sp_recast_min = atoi(row[14]); + uint32 tmppursue_no_sp_recast_max = atoi(row[15]); + uint8 tmppursue_d_chance = atoi(row[16]); + uint32 tmpidle_no_sp_recast_min = atoi(row[17]); + uint32 tmpidle_no_sp_recast_max = atoi(row[18]); + uint8 tmpidle_b_chance = atoi(row[19]); mysql_free_result(result); if (RunQuery(query, MakeAnyLenString(&query, "SELECT spellid, type, minlevel, maxlevel, manacost, recast_delay, priority, resist_adjust from npc_spells_entries where npc_spells_id=%d ORDER BY minlevel", iDBSpellsID), errbuf, &result)) { safe_delete_array(query); @@ -2584,6 +2684,22 @@ DBnpcspells_Struct* ZoneDatabase::GetNPCSpells(uint32 iDBSpellsID) { npc_spells_cache[iDBSpellsID]->parent_list = tmpparent_list; npc_spells_cache[iDBSpellsID]->attack_proc = tmpattack_proc; npc_spells_cache[iDBSpellsID]->proc_chance = tmpproc_chance; + npc_spells_cache[iDBSpellsID]->range_proc = tmprange_proc; + npc_spells_cache[iDBSpellsID]->rproc_chance = tmpdproc_chance; + npc_spells_cache[iDBSpellsID]->defensive_proc = tmpdefensive_proc; + npc_spells_cache[iDBSpellsID]->dproc_chance = tmpdproc_chance; + npc_spells_cache[iDBSpellsID]->fail_recast = tmppfail_recast; + npc_spells_cache[iDBSpellsID]->engaged_no_sp_recast_min = tmpengaged_no_sp_recast_min; + npc_spells_cache[iDBSpellsID]->engaged_no_sp_recast_max = tmpengaged_no_sp_recast_max; + npc_spells_cache[iDBSpellsID]->engaged_beneficial_self_chance = tmpengaged_b_self_chance; + npc_spells_cache[iDBSpellsID]->engaged_beneficial_other_chance = tmpengaged_b_other_chance; + npc_spells_cache[iDBSpellsID]->engaged_detrimental_chance = tmpengaged_d_chance; + npc_spells_cache[iDBSpellsID]->pursue_no_sp_recast_min = tmppursue_no_sp_recast_min; + npc_spells_cache[iDBSpellsID]->pursue_no_sp_recast_max = tmppursue_no_sp_recast_max; + npc_spells_cache[iDBSpellsID]->pursue_detrimental_chance = tmppursue_d_chance; + npc_spells_cache[iDBSpellsID]->idle_no_sp_recast_min = tmpidle_no_sp_recast_min; + npc_spells_cache[iDBSpellsID]->idle_no_sp_recast_max = tmpidle_no_sp_recast_max; + npc_spells_cache[iDBSpellsID]->idle_beneficial_chance = tmpidle_b_chance; npc_spells_cache[iDBSpellsID]->numentries = mysql_num_rows(result); int j = 0; while ((row = mysql_fetch_row(result))) { diff --git a/zone/attack.cpp b/zone/attack.cpp index 37759bc9a..94005f251 100644 --- a/zone/attack.cpp +++ b/zone/attack.cpp @@ -1297,11 +1297,9 @@ bool Client::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, b } else { //we hit, try to avoid it other->AvoidDamage(this, damage); other->MeleeMitigation(this, damage, min_hit, opts); - if(damage > 0) { - ApplyMeleeDamageBonus(skillinuse, damage); - damage += (itembonuses.HeroicSTR / 10) + (damage * other->GetSkillDmgTaken(skillinuse) / 100) + GetSkillDmgAmt(skillinuse); - TryCriticalHit(other, skillinuse, damage, opts); - } + if(damage > 0) + CommonOutgoingHitSuccess(other, damage, skillinuse); + mlog(COMBAT__DAMAGE, "Final damage after all reductions: %d", damage); } @@ -1365,39 +1363,7 @@ bool Client::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, b TrySkillProc(other, skillinuse, 0, true, Hand); } - //break invis when you attack - if(invisible) { - mlog(COMBAT__ATTACKS, "Removing invisibility due to melee attack."); - BuffFadeByEffect(SE_Invisibility); - BuffFadeByEffect(SE_Invisibility2); - invisible = false; - } - if(invisible_undead) { - mlog(COMBAT__ATTACKS, "Removing invisibility vs. undead due to melee attack."); - BuffFadeByEffect(SE_InvisVsUndead); - BuffFadeByEffect(SE_InvisVsUndead2); - invisible_undead = false; - } - if(invisible_animals){ - mlog(COMBAT__ATTACKS, "Removing invisibility vs. animals due to melee attack."); - BuffFadeByEffect(SE_InvisVsAnimals); - invisible_animals = false; - } - - if (spellbonuses.NegateIfCombat) - BuffFadeByEffect(SE_NegateIfCombat); - - if(hidden || improved_hidden){ - hidden = false; - improved_hidden = false; - EQApplicationPacket* outapp = new EQApplicationPacket(OP_SpawnAppearance, sizeof(SpawnAppearance_Struct)); - SpawnAppearance_Struct* sa_out = (SpawnAppearance_Struct*)outapp->pBuffer; - sa_out->spawn_id = GetID(); - sa_out->type = 0x03; - sa_out->parameter = 0; - entity_list.QueueClients(this, outapp, true); - safe_delete(outapp); - } + CommonBreakInvisible(); if(GetTarget()) TriggerDefensiveProcs(weapon, other, Hand, damage); @@ -1933,16 +1899,12 @@ bool NPC::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, bool other->AvoidDamage(this, damage); other->MeleeMitigation(this, damage, min_dmg+eleBane, opts); if(damage > 0) { - ApplyMeleeDamageBonus(skillinuse, damage); - damage += (itembonuses.HeroicSTR / 10) + (damage * other->GetSkillDmgTaken(skillinuse) / 100) + GetSkillDmgAmt(skillinuse); - TryCriticalHit(other, skillinuse, damage, opts); + CommonOutgoingHitSuccess(other, damage, skillinuse); } mlog(COMBAT__HITS, "Generating hate %d towards %s", hate, GetName()); // now add done damage to the hate list if(damage > 0) - { other->AddToHateList(this, hate); - } else other->AddToHateList(this, 0); } @@ -1964,10 +1926,7 @@ bool NPC::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, bool return false; } - int16 DeathHP = 0; - DeathHP = other->GetDelayDeath() * -1; - - if(GetHP() > 0 && other->GetHP() >= DeathHP) { + if(GetHP() > 0 && !other->HasDied()) { other->Damage(this, damage, SPELL_UNKNOWN, skillinuse, false); // Not avoidable client already had thier chance to Avoid } else return false; @@ -1977,59 +1936,24 @@ bool NPC::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, bool MeleeLifeTap(damage); - if (damage > 0) - CheckNumHitsRemaining(NUMHIT_OutgoingHitSuccess); - - //break invis when you attack - if(invisible) { - mlog(COMBAT__ATTACKS, "Removing invisibility due to melee attack."); - BuffFadeByEffect(SE_Invisibility); - BuffFadeByEffect(SE_Invisibility2); - invisible = false; - } - if(invisible_undead) { - mlog(COMBAT__ATTACKS, "Removing invisibility vs. undead due to melee attack."); - BuffFadeByEffect(SE_InvisVsUndead); - BuffFadeByEffect(SE_InvisVsUndead2); - invisible_undead = false; - } - if(invisible_animals){ - mlog(COMBAT__ATTACKS, "Removing invisibility vs. animals due to melee attack."); - BuffFadeByEffect(SE_InvisVsAnimals); - invisible_animals = false; - } - - if (spellbonuses.NegateIfCombat) - BuffFadeByEffect(SE_NegateIfCombat); - - if(hidden || improved_hidden) - { - EQApplicationPacket* outapp = new EQApplicationPacket(OP_SpawnAppearance, sizeof(SpawnAppearance_Struct)); - SpawnAppearance_Struct* sa_out = (SpawnAppearance_Struct*)outapp->pBuffer; - sa_out->spawn_id = GetID(); - sa_out->type = 0x03; - sa_out->parameter = 0; - entity_list.QueueClients(this, outapp, true); - safe_delete(outapp); - } - - - hidden = false; - improved_hidden = false; + CommonBreakInvisible(); //I doubt this works... if (!GetTarget()) return true; //We killed them - if(!bRiposte && other && other->GetHP() > 0) { + if(!bRiposte && !other->HasDied()) { TryWeaponProc(nullptr, weapon, other, Hand); //no weapon - TrySpellProc(nullptr, weapon, other, Hand); - if (damage > 0 && HasSkillProcSuccess()) + if (!other->HasDied()) + TrySpellProc(nullptr, weapon, other, Hand); + + if (damage > 0 && HasSkillProcSuccess() && !other->HasDied()) TrySkillProc(other, skillinuse, 0, true, Hand); } - TriggerDefensiveProcs(nullptr, other, Hand, damage); + if(GetHP() > 0 && !other->HasDied()) + TriggerDefensiveProcs(nullptr, other, Hand, damage); // now check ripostes if (damage == -3) { // riposting @@ -3923,12 +3847,12 @@ void Mob::HealDamage(uint32 amount, Mob *caster, uint16 spell_id) } //proc chance includes proc bonus -float Mob::GetProcChances(float ProcBonus, uint16 weapon_speed, uint16 hand) +float Mob::GetProcChances(float ProcBonus, uint16 hand) { int mydex = GetDEX(); float ProcChance = 0.0f; - weapon_speed = GetWeaponSpeedbyHand(hand); + uint16 weapon_speed = GetWeaponSpeedbyHand(hand); if (RuleB(Combat, AdjustProcPerMinute)) { ProcChance = (static_cast(weapon_speed) * @@ -3945,12 +3869,16 @@ float Mob::GetProcChances(float ProcBonus, uint16 weapon_speed, uint16 hand) return ProcChance; } -float Mob::GetDefensiveProcChances(float &ProcBonus, float &ProcChance, uint16 weapon_speed, uint16 hand) { - int myagi = GetAGI(); +float Mob::GetDefensiveProcChances(float &ProcBonus, float &ProcChance, uint16 hand, Mob* on) { + + if (!on) + return ProcChance; + + int myagi = on->GetAGI(); ProcBonus = 0; ProcChance = 0; - weapon_speed = GetWeaponSpeedbyHand(hand); + uint16 weapon_speed = GetWeaponSpeedbyHand(hand); ProcChance = (static_cast(weapon_speed) * RuleR(Combat, AvgDefProcsPerMinute) / 60000.0f); // compensate for weapon_speed being in ms ProcBonus += static_cast(myagi) * RuleR(Combat, DefProcPerMinAgiContrib) / 100.0f; @@ -3960,7 +3888,7 @@ float Mob::GetDefensiveProcChances(float &ProcBonus, float &ProcChance, uint16 w return ProcChance; } -void Mob::TryDefensiveProc(const ItemInst* weapon, Mob *on, uint16 hand, int damage) { +void Mob::TryDefensiveProc(const ItemInst* weapon, Mob *on, uint16 hand) { if (!on) { SetTarget(nullptr); @@ -3974,10 +3902,8 @@ void Mob::TryDefensiveProc(const ItemInst* weapon, Mob *on, uint16 hand, int dam return; float ProcChance, ProcBonus; - if(weapon!=nullptr) - on->GetDefensiveProcChances(ProcBonus, ProcChance, weapon->GetItem()->Delay, hand); - else - on->GetDefensiveProcChances(ProcBonus, ProcChance); + on->GetDefensiveProcChances(ProcBonus, ProcChance, hand , this); + if(hand != 13) ProcChance /= 2; @@ -4035,7 +3961,7 @@ void Mob::TryWeaponProc(const ItemInst *inst, const Item_Struct *weapon, Mob *on float ProcBonus = static_cast(aabonuses.ProcChanceSPA + spellbonuses.ProcChanceSPA + itembonuses.ProcChanceSPA); ProcBonus += static_cast(itembonuses.ProcChance) / 10.0f; // Combat Effects - float ProcChance = GetProcChances(ProcBonus, weapon->Delay, hand); + float ProcChance = GetProcChances(ProcBonus, hand); if (hand != 13) //Is Archery intened to proc at 50% rate? ProcChance /= 2; @@ -4113,10 +4039,7 @@ void Mob::TrySpellProc(const ItemInst *inst, const Item_Struct *weapon, Mob *on, float ProcBonus = static_cast(spellbonuses.SpellProcChance + itembonuses.SpellProcChance + aabonuses.SpellProcChance); float ProcChance = 0.0f; - if (weapon) - ProcChance = GetProcChances(ProcBonus, weapon->Delay, hand); - else - ProcChance = GetProcChances(ProcBonus); + ProcChance = GetProcChances(ProcBonus, hand); if (hand != 13) //Is Archery intened to proc at 50% rate? ProcChance /= 2; @@ -4130,6 +4053,9 @@ void Mob::TrySpellProc(const ItemInst *inst, const Item_Struct *weapon, Mob *on, rangedattk = true; } + if (!weapon && hand == 11 && GetSpecialAbility(SPECATK_RANGED_ATK)) + rangedattk = true; + for (uint32 i = 0; i < MAX_PROCS; i++) { if (IsPet() && hand != 13) //Pets can only proc spell procs from their primay hand (ie; beastlord pets) continue; // If pets ever can proc from off hand, this will need to change @@ -4152,7 +4078,7 @@ void Mob::TrySpellProc(const ItemInst *inst, const Item_Struct *weapon, Mob *on, // Spell procs (buffs) if (SpellProcs[i].spellID != SPELL_UNKNOWN) { - float chance = ProcChance * (SpellProcs[i].chance / 100.0f); + float chance = ProcChance * (static_cast(SpellProcs[i].chance) / 100.0f); if (MakeRandomFloat(0, 1) <= chance) { mlog(COMBAT__PROCS, "Spell proc %d procing spell %d (%.2f percent chance)", @@ -4168,8 +4094,8 @@ void Mob::TrySpellProc(const ItemInst *inst, const Item_Struct *weapon, Mob *on, } else if (rangedattk) { // ranged only // ranged spell procs (buffs) if (RangedProcs[i].spellID != SPELL_UNKNOWN) { - float chance = ProcChance * (RangedProcs[i].chance / 100.0f); - if (MakeRandomFloat(0, 1) <= chance) { + float chance = ProcChance * (static_cast(RangedProcs[i].chance) / 100.0f); + if (MakeRandomFloat(0, 1) <= chance) { mlog(COMBAT__PROCS, "Ranged proc %d procing spell %d (%.2f percent chance)", i, RangedProcs[i].spellID, chance); @@ -4820,3 +4746,56 @@ int32 Mob::RuneAbsorb(int32 damage, uint16 type) return damage; } +void Mob::CommonOutgoingHitSuccess(Mob* defender, int32 &damage, SkillUseTypes skillInUse) +{ + if (!defender) + return; + + ApplyMeleeDamageBonus(skillInUse, damage); + damage += (damage * defender->GetSkillDmgTaken(skillInUse) / 100) + (GetSkillDmgAmt(skillInUse) + defender->GetFcDamageAmtIncoming(this, 0, true, skillInUse)); + TryCriticalHit(defender, skillInUse, damage); + CheckNumHitsRemaining(NUMHIT_OutgoingHitSuccess); +} + +void Mob::CommonBreakInvisible() +{ + //break invis when you attack + if(invisible) { + mlog(COMBAT__ATTACKS, "Removing invisibility due to melee attack."); + BuffFadeByEffect(SE_Invisibility); + BuffFadeByEffect(SE_Invisibility2); + invisible = false; + } + if(invisible_undead) { + mlog(COMBAT__ATTACKS, "Removing invisibility vs. undead due to melee attack."); + BuffFadeByEffect(SE_InvisVsUndead); + BuffFadeByEffect(SE_InvisVsUndead2); + invisible_undead = false; + } + if(invisible_animals){ + mlog(COMBAT__ATTACKS, "Removing invisibility vs. animals due to melee attack."); + BuffFadeByEffect(SE_InvisVsAnimals); + invisible_animals = false; + } + + if (spellbonuses.NegateIfCombat) + BuffFadeByEffect(SE_NegateIfCombat); + + if(hidden || improved_hidden){ + hidden = false; + improved_hidden = false; + EQApplicationPacket* outapp = new EQApplicationPacket(OP_SpawnAppearance, sizeof(SpawnAppearance_Struct)); + SpawnAppearance_Struct* sa_out = (SpawnAppearance_Struct*)outapp->pBuffer; + sa_out->spawn_id = GetID(); + sa_out->type = 0x03; + sa_out->parameter = 0; + entity_list.QueueClients(this, outapp, true); + safe_delete(outapp); + } + + if (spellbonuses.NegateIfCombat) + BuffFadeByEffect(SE_NegateIfCombat); + + hidden = false; + improved_hidden = false; +} \ No newline at end of file diff --git a/zone/bot.cpp b/zone/bot.cpp index 86a2a9b64..cba7b92a4 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -7732,10 +7732,10 @@ int16 Bot::CalcBotFocusEffect(BotfocusType bottype, uint16 focus_id, uint16 spel } //proc chance includes proc bonus -float Bot::GetProcChances(float ProcBonus, uint16 weapon_speed, uint16 hand) { +float Bot::GetProcChances(float ProcBonus, uint16 hand) { int mydex = GetDEX(); float ProcChance = 0.0f; - + uint16 weapon_speed = 0; switch (hand) { case SLOT_PRIMARY: weapon_speed = attack_timer.GetDuration(); diff --git a/zone/bot.h b/zone/bot.h index 84f04e85b..493f43683 100644 --- a/zone/bot.h +++ b/zone/bot.h @@ -167,7 +167,7 @@ public: uint16 BotGetSpells(int spellslot) { return AIspells[spellslot].spellid; } uint16 BotGetSpellType(int spellslot) { return AIspells[spellslot].type; } uint16 BotGetSpellPriority(int spellslot) { return AIspells[spellslot].priority; } - virtual float GetProcChances(float ProcBonus, uint16 weapon_speed, uint16 hand); + virtual float GetProcChances(float ProcBonus, uint16 hand); virtual bool AvoidDamage(Mob* other, int32 &damage, bool CanRiposte); virtual int GetMonkHandToHandDamage(void); virtual bool TryFinishingBlow(Mob *defender, SkillUseTypes skillinuse); diff --git a/zone/mob.cpp b/zone/mob.cpp index 340dfdfea..58a54d205 100644 --- a/zone/mob.cpp +++ b/zone/mob.cpp @@ -238,9 +238,6 @@ Mob::Mob(const char* in_name, RangedProcs[j].spellID = SPELL_UNKNOWN; RangedProcs[j].chance = 0; RangedProcs[j].base_spellID = SPELL_UNKNOWN; - SkillProcs[j].spellID = SPELL_UNKNOWN; - SkillProcs[j].chance = 0; - SkillProcs[j].base_spellID = SPELL_UNKNOWN; } for (i = 0; i < _MaterialCount; i++) @@ -3033,7 +3030,7 @@ void Mob::TriggerDefensiveProcs(const ItemInst* weapon, Mob *on, uint16 hand, in if (!on) return; - on->TryDefensiveProc(weapon, this, hand, damage); + on->TryDefensiveProc(weapon, this, hand); //Defensive Skill Procs if (damage < 0 && damage >= -4) { @@ -4702,6 +4699,11 @@ uint16 Mob::GetSkillByItemType(int ItemType) return Skill2HBlunt; case ItemType2HPiercing: return Skill1HPiercing; // change to 2HPiercing once activated + case ItemTypeBow: + return SkillArchery; + case ItemTypeLargeThrowing: + case ItemTypeSmallThrowing: + return SkillThrowing; case ItemTypeMartial: return SkillHandtoHand; default: @@ -4710,6 +4712,32 @@ uint16 Mob::GetSkillByItemType(int ItemType) return SkillHandtoHand; } +uint8 Mob::GetItemTypeBySkill(SkillUseTypes skill) +{ + switch (skill) + { + case SkillThrowing: + return ItemTypeSmallThrowing; + case SkillArchery: + return ItemTypeArrow; + case Skill1HSlashing: + return ItemType1HSlash; + case Skill2HSlashing: + return ItemType2HSlash; + case Skill1HPiercing: + return ItemType1HPiercing; + case Skill1HBlunt: + return ItemType1HBlunt; + case Skill2HBlunt: + return ItemType2HBlunt; + case SkillHandtoHand: + return ItemTypeMartial; + default: + return ItemTypeMartial; + } + return ItemTypeMartial; + } + bool Mob::PassLimitToSkill(uint16 spell_id, uint16 skill) { diff --git a/zone/mob.h b/zone/mob.h index 4cd6d5f49..a38b429f3 100644 --- a/zone/mob.h +++ b/zone/mob.h @@ -150,6 +150,9 @@ public: bool CombatRange(Mob* other); virtual inline bool IsBerserk() { return false; } // only clients void RogueEvade(Mob *other); + void CommonOutgoingHitSuccess(Mob* defender, int32 &damage, SkillUseTypes skillInUse); + void CommonBreakInvisible(); + bool HasDied(); //Appearance void SendLevelAppearance(); @@ -164,7 +167,7 @@ public: virtual void WearChange(uint8 material_slot, uint16 texture, uint32 color); void DoAnim(const int animnum, int type=0, bool ackreq = true, eqFilterType filter = FilterNone); void ProjectileAnimation(Mob* to, int item_id, bool IsArrow = false, float speed = 0, - float angle = 0, float tilt = 0, float arc = 0, const char *IDFile = nullptr); + float angle = 0, float tilt = 0, float arc = 0, const char *IDFile = nullptr, SkillUseTypes skillInUse = SkillArchery); void ChangeSize(float in_size, bool bNoRestriction = false); inline uint8 SeeInvisible() const { return see_invis; } inline bool SeeInvisibleUndead() const { return see_invis_undead; } @@ -480,6 +483,7 @@ public: static uint32 RandomTimer(int min, int max); static uint8 GetDefaultGender(uint16 in_race, uint8 in_gender = 0xFF); uint16 GetSkillByItemType(int ItemType); + uint8 GetItemTypeBySkill(SkillUseTypes skill); virtual void MakePet(uint16 spell_id, const char* pettype, const char *petname = nullptr); virtual void MakePoweredPet(uint16 spell_id, const char* pettype, int16 petpower, const char *petname = nullptr, float in_size = 0.0f); bool IsWarriorClass() const; @@ -819,7 +823,7 @@ public: void SetNextIncHPEvent( int inchpevent ); inline bool DivineAura() const { return spellbonuses.DivineAura; } - inline bool Sanctuary() const { return spellbonuses.Sanctuary; } + inline bool Sanctuary() const { return spellbonuses.Sanctuary; } bool HasNPCSpecialAtk(const char* parse); int GetSpecialAbility(int ability); @@ -989,13 +993,13 @@ protected: void TrySkillProc(Mob *on, uint16 skill, uint16 ReuseTime, bool Success = false, uint16 hand = 0, bool IsDefensive = false); bool PassLimitToSkill(uint16 spell_id, uint16 skill); bool PassLimitClass(uint32 Classes_, uint16 Class_); - void TryDefensiveProc(const ItemInst* weapon, Mob *on, uint16 hand = 13, int damage=0); + void TryDefensiveProc(const ItemInst* weapon, Mob *on, uint16 hand = 13); void TryWeaponProc(const ItemInst* inst, const Item_Struct* weapon, Mob *on, uint16 hand = 13); void TrySpellProc(const ItemInst* inst, const Item_Struct* weapon, Mob *on, uint16 hand = 13); void TryWeaponProc(const ItemInst* weapon, Mob *on, uint16 hand = 13); void ExecWeaponProc(const ItemInst* weapon, uint16 spell_id, Mob *on); - virtual float GetProcChances(float ProcBonus, uint16 weapon_speed = 30, uint16 hand = 13); - virtual float GetDefensiveProcChances(float &ProcBonus, float &ProcChance, uint16 weapon_speed = 30, uint16 hand = 13); + virtual float GetProcChances(float ProcBonus, uint16 hand = 13); + virtual float GetDefensiveProcChances(float &ProcBonus, float &ProcChance, uint16 hand = 13, Mob *on = nullptr); virtual float GetSpecialProcChances(uint16 hand); virtual float GetAssassinateProcChances(uint16 ReuseTime); virtual float GetSkillProcChances(uint16 ReuseTime, uint16 hand = 0); @@ -1005,7 +1009,6 @@ protected: int GetKickDamage(); int GetBashDamage(); virtual void ApplySpecialAttackMod(SkillUseTypes skill, int32 &dmg, int32 &mindmg); - bool HasDied(); void CalculateNewFearpoint(); float FindGroundZ(float new_x, float new_y, float z_offset=0.0); Map::Vertex UpdatePath(float ToX, float ToY, float ToZ, float Speed, bool &WaypointChange, bool &NodeReached); @@ -1018,7 +1021,6 @@ protected: tProc SpellProcs[MAX_PROCS]; tProc DefensiveProcs[MAX_PROCS]; tProc RangedProcs[MAX_PROCS]; - tProc SkillProcs[MAX_PROCS]; char name[64]; char orig_name[64]; diff --git a/zone/npc.cpp b/zone/npc.cpp index 38989bccb..f6daa3ea2 100644 --- a/zone/npc.cpp +++ b/zone/npc.cpp @@ -259,9 +259,11 @@ NPC::NPC(const NPCType* d, Spawn2* in_respawn, float x, float y, float z, float d_meele_texture1 = d->d_meele_texture1; d_meele_texture2 = d->d_meele_texture2; + ammo_idfile = d->ammo_idfile; memset(equipment, 0, sizeof(equipment)); prim_melee_type = d->prim_melee_type; sec_melee_type = d->sec_melee_type; + ranged_type = d->ranged_type; // If Melee Textures are not set, set attack type to Hand to Hand as default if(!d_meele_texture1) diff --git a/zone/npc.h b/zone/npc.h index 07c81e79b..939ada71a 100644 --- a/zone/npc.h +++ b/zone/npc.h @@ -73,6 +73,22 @@ struct AISpellsEffects_Struct { int32 max; }; +struct AISpellsVar_Struct { + uint32 fail_recast; + uint32 engaged_no_sp_recast_min; + uint32 engaged_no_sp_recast_max; + uint8 engaged_beneficial_self_chance; + uint8 engaged_beneficial_other_chance; + uint8 engaged_detrimental_chance; + uint32 pursue_no_sp_recast_min; + uint32 pursue_no_sp_recast_max; + uint8 pursue_detrimental_chance; + uint32 idle_no_sp_recast_min; + uint32 idle_no_sp_recast_max; + uint8 idle_beneficial_chance; +}; + + class AA_SwarmPetInfo; class NPC : public Mob @@ -215,8 +231,10 @@ public: uint8 GetPrimSkill() const { return prim_melee_type; } uint8 GetSecSkill() const { return sec_melee_type; } + uint8 GetRangedSkill() const { return ranged_type; } void SetPrimSkill(uint8 skill_type) { prim_melee_type = skill_type; } void SetSecSkill(uint8 skill_type) { sec_melee_type = skill_type; } + void SetRangedSkill(uint8 skill_type) { ranged_type = skill_type; } uint32 MerchantType; bool merchant_open; @@ -257,6 +275,7 @@ public: void CheckSignal(); inline bool IsTargetableWithHotkey() const { return no_target_hotkey; } int32 GetNPCHPRegen() const { return hp_regen + itembonuses.HPRegen + spellbonuses.HPRegen; } + inline const char* GetAmmoIDfile() const { return ammo_idfile; } //waypoint crap int GetMaxWp() const { return max_wp; } @@ -413,11 +432,12 @@ protected: bool HasAISpell; virtual bool AICastSpell(Mob* tar, uint8 iChance, uint16 iSpellTypes); virtual bool AIDoSpellCast(uint8 i, Mob* tar, int32 mana_cost, uint32* oDontDoAgainBefore = 0); + AISpellsVar_Struct AISpellVar; uint32 npc_spells_effects_id; std::vector AIspellsEffects; bool HasAISpellEffects; - + uint32 max_dmg; uint32 min_dmg; int32 accuracy_rating; @@ -457,8 +477,10 @@ protected: uint32 equipment[MAX_WORN_INVENTORY]; //this is an array of item IDs uint16 d_meele_texture1; //this is an item Material value uint16 d_meele_texture2; //this is an item Material value (offhand) + const char* ammo_idfile; //this determines projectile graphic "IT###" (see item field 'idfile') uint8 prim_melee_type; //Sets the Primary Weapon attack message and animation uint8 sec_melee_type; //Sets the Secondary Weapon attack message and animation + uint8 ranged_type; //Sets the Ranged Weapon attack message and animation AA_SwarmPetInfo *swarmInfoPtr; bool ldon_trapped; diff --git a/zone/special_attacks.cpp b/zone/special_attacks.cpp index 46bd810ad..2b094c3dc 100644 --- a/zone/special_attacks.cpp +++ b/zone/special_attacks.cpp @@ -105,6 +105,9 @@ void Mob::DoSpecialAttackDamage(Mob *who, SkillUseTypes skill, int32 max_damage, //this really should go through the same code as normal melee damage to //pick up all the special behavior there + if (!who) + return; + int32 hate = max_damage; if(hate_override > -1) hate = hate_override; @@ -141,26 +144,18 @@ void Mob::DoSpecialAttackDamage(Mob *who, SkillUseTypes skill, int32 max_damage, who->MeleeMitigation(this, max_damage, min_damage); - if(max_damage > 0) { - ApplyMeleeDamageBonus(skill, max_damage); - max_damage += who->GetFcDamageAmtIncoming(this, 0, true, skill); - max_damage += (itembonuses.HeroicSTR / 10) + (max_damage * who->GetSkillDmgTaken(skill) / 100) + GetSkillDmgAmt(skill); - TryCriticalHit(who, skill, max_damage); - } + if(max_damage > 0) + CommonOutgoingHitSuccess(who, max_damage, skill); + } - if(max_damage >= 0) //You should probably get aggro no matter what, but unclear why it was set like this. - who->AddToHateList(this, hate); - + who->AddToHateList(this, hate, 0, false); who->Damage(this, max_damage, SPELL_UNKNOWN, skill, false); //Make sure 'this' has not killed the target and 'this' is not dead (Damage shield ect). if(!GetTarget())return; if (HasDied()) return; - if (max_damage > 0) - CheckNumHitsRemaining(NUMHIT_OutgoingHitSuccess); - //[AA Dragon Punch] value[0] = 100 for 25%, chance value[1] = skill if(aabonuses.SpecialAttackKBProc[0] && aabonuses.SpecialAttackKBProc[1] == skill){ int kb_chance = 25; @@ -813,39 +808,7 @@ void Client::RangedAttack(Mob* other, bool CanDoubleAttack) { CheckIncreaseSkill(SkillArchery, GetTarget(), -15); - //break invis when you attack - if(invisible) { - mlog(COMBAT__ATTACKS, "Removing invisibility due to melee attack."); - BuffFadeByEffect(SE_Invisibility); - BuffFadeByEffect(SE_Invisibility2); - invisible = false; - } - if(invisible_undead) { - mlog(COMBAT__ATTACKS, "Removing invisibility vs. undead due to melee attack."); - BuffFadeByEffect(SE_InvisVsUndead); - BuffFadeByEffect(SE_InvisVsUndead2); - invisible_undead = false; - } - if(invisible_animals){ - mlog(COMBAT__ATTACKS, "Removing invisibility vs. animals due to melee attack."); - BuffFadeByEffect(SE_InvisVsAnimals); - invisible_animals = false; - } - - if (spellbonuses.NegateIfCombat) - BuffFadeByEffect(SE_NegateIfCombat); - - if(hidden || improved_hidden){ - hidden = false; - improved_hidden = false; - EQApplicationPacket* outapp = new EQApplicationPacket(OP_SpawnAppearance, sizeof(SpawnAppearance_Struct)); - SpawnAppearance_Struct* sa_out = (SpawnAppearance_Struct*)outapp->pBuffer; - sa_out->spawn_id = GetID(); - sa_out->type = 0x03; - sa_out->parameter = 0; - entity_list.QueueClients(this, outapp, true); - safe_delete(outapp); - } + CommonBreakInvisible(); } void Mob::DoArcheryAttackDmg(Mob* other, const ItemInst* RangeWeapon, const ItemInst* Ammo, uint16 weapon_damage, int16 chance_mod, int16 focus, int ReuseTime) @@ -1018,19 +981,30 @@ void NPC::RangedAttack(Mob* other) return; } - float range = 250; // needs to be longer than 200(most spells) - mlog(COMBAT__RANGED, "Calculated bow range to be %.1f", range); - range *= range; - if(DistNoRootNoZ(*GetTarget()) > range) { - mlog(COMBAT__RANGED, "Ranged attack out of range...%.2f vs %.2f", DistNoRootNoZ(*GetTarget()), range); + int sa_min_range = GetSpecialAbilityParam(SPECATK_RANGED_ATK, 0); //Min Range of NPC attack + int sa_max_range = GetSpecialAbilityParam(SPECATK_RANGED_ATK, 1); //Max Range of NPC attack + + float min_range = static_cast(RuleI(Combat, MinRangedAttackDist)); + float max_range = 250; // needs to be longer than 200(most spells) + + if (sa_max_range) + max_range = static_cast(sa_max_range); + + if (sa_min_range) + min_range = static_cast(sa_min_range); + + mlog(COMBAT__RANGED, "Calculated bow range to be %.1f", max_range); + max_range *= max_range; + if(DistNoRootNoZ(*other) > max_range) { + mlog(COMBAT__RANGED, "Ranged attack out of range...%.2f vs %.2f", DistNoRootNoZ(*other), max_range); //target is out of range, client does a message return; } - else if(DistNoRootNoZ(*GetTarget()) < (RuleI(Combat, MinRangedAttackDist)*RuleI(Combat, MinRangedAttackDist))){ + else if(DistNoRootNoZ(*other) < (min_range * min_range)) return; - } + - if(!IsAttackAllowed(GetTarget()) || + if(!other || !IsAttackAllowed(other) || IsCasting() || DivineAura() || IsStunned() || @@ -1040,32 +1014,33 @@ void NPC::RangedAttack(Mob* other) return; } - if(!ammo) - { + SkillUseTypes skillinuse = SkillArchery; + skillinuse = static_cast(GetRangedSkill()); + + if(!ammo && !GetAmmoIDfile()) ammo = database.GetItem(8005); - } if(ammo) - SendItemAnimation(GetTarget(), ammo, SkillArchery); + SendItemAnimation(other, ammo, SkillArchery); + else + ProjectileAnimation(other, 0,false,0,0,0,0,GetAmmoIDfile(),skillinuse); + + FaceTarget(other); - // Face the Target - FaceTarget(GetTarget()); - - // Hit? - if (!GetTarget()->CheckHitChance(this, SkillArchery, 13)) + if (!other->CheckHitChance(this, skillinuse, 11, GetSpecialAbilityParam(SPECATK_RANGED_ATK, 2))) { - mlog(COMBAT__RANGED, "Ranged attack missed %s.", GetTarget()->GetName()); - GetTarget()->Damage(this, 0, SPELL_UNKNOWN, SkillArchery); + mlog(COMBAT__RANGED, "Ranged attack missed %s.", other->GetName()); + other->Damage(this, 0, SPELL_UNKNOWN, skillinuse); } else { - int16 WDmg = GetWeaponDamage(GetTarget(), weapon); - int16 ADmg = GetWeaponDamage(GetTarget(), ammo); + int16 WDmg = GetWeaponDamage(other, weapon); + int16 ADmg = GetWeaponDamage(other, ammo); + int32 TotalDmg = 0; if(WDmg > 0 || ADmg > 0) { - mlog(COMBAT__RANGED, "Ranged attack hit %s.", GetTarget()->GetName()); - int32 TotalDmg = 0; - + mlog(COMBAT__RANGED, "Ranged attack hit %s.", other->GetName()); + int32 MaxDmg = max_dmg * RuleR(Combat, ArcheryNPCMultiplier); // should add a field to npc_types int32 MinDmg = min_dmg * RuleR(Combat, ArcheryNPCMultiplier); @@ -1074,54 +1049,36 @@ void NPC::RangedAttack(Mob* other) else TotalDmg = MakeRandomInt(MinDmg, MaxDmg); - int32 hate = TotalDmg; - - GetTarget()->MeleeMitigation(this, TotalDmg, MinDmg); - ApplyMeleeDamageBonus(SkillArchery, TotalDmg); - TryCriticalHit(GetTarget(), SkillArchery, TotalDmg); - GetTarget()->AddToHateList(this, hate, 0, false); - GetTarget()->Damage(this, TotalDmg, SPELL_UNKNOWN, SkillArchery); - CheckNumHitsRemaining(NUMHIT_OutgoingHitSuccess); + TotalDmg += TotalDmg * GetSpecialAbilityParam(SPECATK_RANGED_ATK, 3) / 100; //Damage modifier + + other->AvoidDamage(this, TotalDmg, false); + other->MeleeMitigation(this, TotalDmg, MinDmg); + if (TotalDmg > 0) + CommonOutgoingHitSuccess(other, TotalDmg, skillinuse); } + else - { - GetTarget()->Damage(this, -5, SPELL_UNKNOWN, SkillArchery); - } + TotalDmg = -5; + + if (TotalDmg > 0) + other->AddToHateList(this, TotalDmg, 0, false); + else + other->AddToHateList(this, 0, 0, false); + + other->Damage(this, TotalDmg, SPELL_UNKNOWN, skillinuse); + + if (TotalDmg > 0 && HasSkillProcSuccess() && GetTarget() && !other->HasDied()) + TrySkillProc(other, skillinuse, 0, true, 11); } - //break invis when you attack - if(invisible) { - mlog(COMBAT__ATTACKS, "Removing invisibility due to melee attack."); - BuffFadeByEffect(SE_Invisibility); - BuffFadeByEffect(SE_Invisibility2); - invisible = false; - } - if(invisible_undead) { - mlog(COMBAT__ATTACKS, "Removing invisibility vs. undead due to melee attack."); - BuffFadeByEffect(SE_InvisVsUndead); - BuffFadeByEffect(SE_InvisVsUndead2); - invisible_undead = false; - } - if(invisible_animals){ - mlog(COMBAT__ATTACKS, "Removing invisibility vs. animals due to melee attack."); - BuffFadeByEffect(SE_InvisVsAnimals); - invisible_animals = false; - } + //try proc on hits and misses + if(other && !other->HasDied()) + TrySpellProc(nullptr, (const Item_Struct*)nullptr, other, 11); - if (spellbonuses.NegateIfCombat) - BuffFadeByEffect(SE_NegateIfCombat); + if (HasSkillProcs() && other && !other->HasDied()) + TrySkillProc(other, skillinuse, 0, false, 11); - if(hidden || improved_hidden){ - hidden = false; - improved_hidden = false; - EQApplicationPacket* outapp = new EQApplicationPacket(OP_SpawnAppearance, sizeof(SpawnAppearance_Struct)); - SpawnAppearance_Struct* sa_out = (SpawnAppearance_Struct*)outapp->pBuffer; - sa_out->spawn_id = GetID(); - sa_out->type = 0x03; - sa_out->parameter = 0; - entity_list.QueueClients(this, outapp, true); - safe_delete(outapp); - } + CommonBreakInvisible(); } uint16 Mob::GetThrownDamage(int16 wDmg, int32& TotalDmg, int& minDmg) @@ -1234,39 +1191,7 @@ void Client::ThrowingAttack(Mob* other, bool CanDoubleAttack) { //old was 51 DeleteItemInInventory(ammo_slot, 1, true); CheckIncreaseSkill(SkillThrowing, GetTarget()); - //break invis when you attack - if(invisible) { - mlog(COMBAT__ATTACKS, "Removing invisibility due to melee attack."); - BuffFadeByEffect(SE_Invisibility); - BuffFadeByEffect(SE_Invisibility2); - invisible = false; - } - if(invisible_undead) { - mlog(COMBAT__ATTACKS, "Removing invisibility vs. undead due to melee attack."); - BuffFadeByEffect(SE_InvisVsUndead); - BuffFadeByEffect(SE_InvisVsUndead2); - invisible_undead = false; - } - if(invisible_animals){ - mlog(COMBAT__ATTACKS, "Removing invisibility vs. animals due to melee attack."); - BuffFadeByEffect(SE_InvisVsAnimals); - invisible_animals = false; - } - - if (spellbonuses.NegateIfCombat) - BuffFadeByEffect(SE_NegateIfCombat); - - if(hidden || improved_hidden){ - hidden = false; - improved_hidden = false; - EQApplicationPacket* outapp = new EQApplicationPacket(OP_SpawnAppearance, sizeof(SpawnAppearance_Struct)); - SpawnAppearance_Struct* sa_out = (SpawnAppearance_Struct*)outapp->pBuffer; - sa_out->spawn_id = GetID(); - sa_out->type = 0x03; - sa_out->parameter = 0; - entity_list.QueueClients(this, outapp, true); - safe_delete(outapp); - } + CommonBreakInvisible(); } void Mob::DoThrowingAttackDmg(Mob* other, const ItemInst* RangeWeapon, const Item_Struct* item, uint16 weapon_damage, int16 chance_mod,int16 focus, int ReuseTime) @@ -1312,20 +1237,13 @@ void Mob::DoThrowingAttackDmg(Mob* other, const ItemInst* RangeWeapon, const Ite other->MeleeMitigation(this, TotalDmg, minDmg); if(TotalDmg > 0) - { - ApplyMeleeDamageBonus(SkillThrowing, TotalDmg); - TotalDmg += other->GetFcDamageAmtIncoming(this, 0, true, SkillThrowing); - TotalDmg += (itembonuses.HeroicDEX / 10) + (TotalDmg * other->GetSkillDmgTaken(SkillThrowing) / 100) + GetSkillDmgAmt(SkillThrowing); - TryCriticalHit(other, SkillThrowing, TotalDmg); - int32 hate = (2*WDmg); - other->AddToHateList(this, hate, 0, false); - CheckNumHitsRemaining(NUMHIT_OutgoingHitSuccess); - } + CommonOutgoingHitSuccess(other, TotalDmg, SkillThrowing); } else TotalDmg = -5; + other->AddToHateList(this, 2*WDmg, 0, false); other->Damage(this, TotalDmg, SPELL_UNKNOWN, SkillThrowing); if (TotalDmg > 0 && HasSkillProcSuccess() && GetTarget() && other && !other->HasDied()){ @@ -1394,7 +1312,10 @@ void Mob::SendItemAnimation(Mob *to, const Item_Struct *item, SkillUseTypes skil safe_delete(outapp); } -void Mob::ProjectileAnimation(Mob* to, int item_id, bool IsArrow, float speed, float angle, float tilt, float arc, const char *IDFile) { +void Mob::ProjectileAnimation(Mob* to, int item_id, bool IsArrow, float speed, float angle, float tilt, float arc, const char *IDFile, SkillUseTypes skillInUse) { + + if (!to) + return; const Item_Struct* item = nullptr; uint8 item_type = 0; @@ -1412,9 +1333,12 @@ void Mob::ProjectileAnimation(Mob* to, int item_id, bool IsArrow, float speed, f if(IsArrow) { item_type = 27; } - if(!item_type) { + if(!item_type && !skillInUse) { item_type = item->ItemType; } + else if (skillInUse) + item_type = GetItemTypeBySkill(skillInUse); + if(!speed) { speed = 4.0; } @@ -1444,7 +1368,7 @@ void Mob::ProjectileAnimation(Mob* to, int item_id, bool IsArrow, float speed, f as->target_id = to->GetID(); as->item_id = item->ID; as->item_type = item_type; - as->skill = 0; // Doesn't seem to have any effect + as->skill = skillInUse; // Doesn't seem to have any effect strn0cpy(as->model_name, item_IDFile, 16); as->velocity = speed; as->launch_angle = angle; @@ -2173,7 +2097,7 @@ void Mob::DoMeleeSkillAttackDmg(Mob* other, uint16 weapon_damage, SkillUseTypes skillinuse = SkillOffense; int damage = 0; - uint32 hate = 0; + int32 hate = 0; int Hand = 13; if (hate == 0 && weapon_damage > 1) hate = weapon_damage; @@ -2199,6 +2123,19 @@ void Mob::DoMeleeSkillAttackDmg(Mob* other, uint16 weapon_damage, SkillUseTypes hate += ucDamageBonus; } + if(skillinuse == SkillBash){ + if(IsClient()){ + ItemInst *item = CastToClient()->GetInv().GetItem(SLOT_SECONDARY); + if(item){ + if(item->GetItem()->ItemType == ItemTypeShield) { + hate += item->GetItem()->AC; + } + const Item_Struct *itm = item->GetItem(); + hate = hate * (100 + GetFuriousBash(itm->Focus.Effect)) / 100; + } + } + } + ApplySpecialAttackMod(skillinuse, max_hit, min_hit); min_hit += min_hit * GetMeleeMinDamageMod_SE(skillinuse) / 100; @@ -2216,12 +2153,8 @@ void Mob::DoMeleeSkillAttackDmg(Mob* other, uint16 weapon_damage, SkillUseTypes } else { other->AvoidDamage(this, damage, CanRiposte); other->MeleeMitigation(this, damage, min_hit); - if(damage > 0) { - ApplyMeleeDamageBonus(skillinuse, damage); - damage += other->GetFcDamageAmtIncoming(this, 0, true, skillinuse); - damage += (itembonuses.HeroicSTR / 10) + (damage * other->GetSkillDmgTaken(skillinuse) / 100) + GetSkillDmgAmt(skillinuse); - TryCriticalHit(other, skillinuse, damage); - } + if(damage > 0) + CommonOutgoingHitSuccess(other, damage, skillinuse); } if (damage == -3) { @@ -2234,27 +2167,13 @@ void Mob::DoMeleeSkillAttackDmg(Mob* other, uint16 weapon_damage, SkillUseTypes else damage = -5; - if(skillinuse == SkillBash){ - if(IsClient()){ - ItemInst *item = CastToClient()->GetInv().GetItem(SLOT_SECONDARY); - if(item){ - if(item->GetItem()->ItemType == ItemTypeShield) { - hate += item->GetItem()->AC; - } - const Item_Struct *itm = item->GetItem(); - hate = hate * (100 + GetFuriousBash(itm->Focus.Effect)) / 100; - } - } - } - - other->AddToHateList(this, hate); - bool CanSkillProc = true; if (skillinuse == SkillOffense){ //Hack to allow damage to display. skillinuse = SkillTigerClaw; //'strike' your opponent - Arbitrary choice for message. CanSkillProc = false; //Disable skill procs } + other->AddToHateList(this, hate, 0, false); other->Damage(this, damage, SPELL_UNKNOWN, skillinuse); if (HasDied()) diff --git a/zone/spell_effects.cpp b/zone/spell_effects.cpp index 84c1ec889..5d36d73d2 100644 --- a/zone/spell_effects.cpp +++ b/zone/spell_effects.cpp @@ -5765,8 +5765,6 @@ int32 Mob::GetFocusIncoming(focusType type, int effect, Mob *caster, uint32 spel int value = 0; if (spellbonuses.FocusEffects[type]){ - uint32 buff_count = GetMaxTotalSlots(); - for(int i = 0; i < buff_count; i++){ int32 tmp_focus = 0; int tmp_buffslot = -1; @@ -5799,7 +5797,7 @@ int32 Mob::GetFocusIncoming(focusType type, int effect, Mob *caster, uint32 spel CheckNumHitsRemaining(NUMHIT_MatchingSpells, tmp_buffslot); } - } + return value; } diff --git a/zone/spells.cpp b/zone/spells.cpp index f8ce8baa0..35416dc53 100644 --- a/zone/spells.cpp +++ b/zone/spells.cpp @@ -5018,7 +5018,7 @@ bool Mob::IsCombatProc(uint16 spell_id) { for (int i = 0; i < MAX_PROCS; i++){ if (PermaProcs[i].spellID == spell_id || SpellProcs[i].spellID == spell_id - || SkillProcs[i].spellID == spell_id || RangedProcs[i].spellID == spell_id){ + || RangedProcs[i].spellID == spell_id){ return true; } } @@ -5075,7 +5075,7 @@ bool Mob::AddDefensiveProc(uint16 spell_id, uint16 iChance, uint16 base_spell_id { if(spell_id == SPELL_UNKNOWN) return(false); - + int i; for (i = 0; i < MAX_PROCS; i++) { if (DefensiveProcs[i].spellID == SPELL_UNKNOWN) { @@ -5107,7 +5107,7 @@ bool Mob::AddRangedProc(uint16 spell_id, uint16 iChance, uint16 base_spell_id) { if(spell_id == SPELL_UNKNOWN) return(false); - + Shout("Add Ranged Proc %i %i %i", spell_id, iChance, base_spell_id); int i; for (i = 0; i < MAX_PROCS; i++) { if (RangedProcs[i].spellID == SPELL_UNKNOWN) { diff --git a/zone/zonedb.cpp b/zone/zonedb.cpp index 2178a15c9..4bf877a22 100644 --- a/zone/zonedb.cpp +++ b/zone/zonedb.cpp @@ -1067,8 +1067,10 @@ const NPCType* ZoneDatabase::GetNPCType (uint32 id) { "npc_types.npc_spells_effects_id," "npc_types.d_meele_texture1," "npc_types.d_meele_texture2," + "npc_types.ammo_idfile," "npc_types.prim_melee_type," "npc_types.sec_melee_type," + "npc_types.ranged_type," "npc_types.runspeed," "npc_types.findable," "npc_types.trackable," @@ -1166,8 +1168,10 @@ const NPCType* ZoneDatabase::GetNPCType (uint32 id) { tmpNPCType->npc_spells_effects_id = atoi(row[r++]); tmpNPCType->d_meele_texture1 = atoi(row[r++]); tmpNPCType->d_meele_texture2 = atoi(row[r++]); + strn0cpy(tmpNPCType->ammo_idfile, row[r++], 30); tmpNPCType->prim_melee_type = atoi(row[r++]); tmpNPCType->sec_melee_type = atoi(row[r++]); + tmpNPCType->ranged_type = atoi(row[r++]); tmpNPCType->runspeed= atof(row[r++]); tmpNPCType->findable = atoi(row[r++]) == 0? false : true; tmpNPCType->trackable = atoi(row[r++]) == 0? false : true; diff --git a/zone/zonedb.h b/zone/zonedb.h index e53b45530..bdbbad84c 100644 --- a/zone/zonedb.h +++ b/zone/zonedb.h @@ -43,9 +43,25 @@ struct DBnpcspellseffects_entries_Struct { struct DBnpcspells_Struct { uint32 parent_list; - int16 attack_proc; + uint16 attack_proc; uint8 proc_chance; + uint16 range_proc; + int16 rproc_chance; + uint16 defensive_proc; + int16 dproc_chance; uint32 numentries; + uint32 fail_recast; + uint32 engaged_no_sp_recast_min; + uint32 engaged_no_sp_recast_max; + uint8 engaged_beneficial_self_chance; + uint8 engaged_beneficial_other_chance; + uint8 engaged_detrimental_chance; + uint32 pursue_no_sp_recast_min; + uint32 pursue_no_sp_recast_max; + uint8 pursue_detrimental_chance; + uint32 idle_no_sp_recast_min; + uint32 idle_no_sp_recast_max; + uint8 idle_beneficial_chance; DBnpcspells_entries_Struct entries[0]; }; diff --git a/zone/zonedump.h b/zone/zonedump.h index e18734d4f..7a2e4a5ec 100644 --- a/zone/zonedump.h +++ b/zone/zonedump.h @@ -94,8 +94,10 @@ struct NPCType char special_abilities[512]; uint16 d_meele_texture1; uint16 d_meele_texture2; + char ammo_idfile[30]; uint8 prim_melee_type; uint8 sec_melee_type; + uint8 ranged_type; int32 hp_regen; int32 mana_regen; int32 aggroradius; // added for AI improvement - neotokyo From 48c24186d12b728f4e9bdd36f4e8fb8c7a6389e0 Mon Sep 17 00:00:00 2001 From: KayenEQ Date: Thu, 10 Jul 2014 23:44:03 -0400 Subject: [PATCH 02/11] sql comment fix --- utils/sql/git/optional/2014_07_10_AICastingRules.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/sql/git/optional/2014_07_10_AICastingRules.sql b/utils/sql/git/optional/2014_07_10_AICastingRules.sql index ad88a2e80..c84d43cfb 100644 --- a/utils/sql/git/optional/2014_07_10_AICastingRules.sql +++ b/utils/sql/git/optional/2014_07_10_AICastingRules.sql @@ -6,7 +6,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_EngagedDetrimentalChance','20','Chance during third AI Cast check to do a determental spell on others.'); INSERT INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (1, 'Spells, AI_PursueNoSpellMinRecast','500', 'AI spell recast time(MS) check when no spell is cast while chasing target. (min time in random)'); INSERT INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (1, 'Spells, AI_PursueNoSpellMaxRecast','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_PursueDetrimentalChance','90','Chance during third AI Cast check to do a determental spell on self.'); +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 From d99f9c1c0924f734eee99e9e40a418bba09cbbea Mon Sep 17 00:00:00 2001 From: KayenEQ Date: Fri, 18 Jul 2014 17:57:03 -0400 Subject: [PATCH 03/11] fix for aa pet flurry bonus calc --- zone/bonuses.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zone/bonuses.cpp b/zone/bonuses.cpp index 51db73157..eb251412a 100644 --- a/zone/bonuses.cpp +++ b/zone/bonuses.cpp @@ -974,7 +974,7 @@ void Client::ApplyAABonuses(uint32 aaid, uint32 slots, StatBonuses* newbon) newbon->FlurryChance += base1; break; case SE_PetFlurry: - newbon->PetFlurry = base1; + newbon->PetFlurry += base1; break; case SE_BardSongRange: newbon->SongRange += base1; From 6b107d4197fccdaca5a2316bf1ac14b5289f276e Mon Sep 17 00:00:00 2001 From: KayenEQ Date: Mon, 21 Jul 2014 09:46:36 -0400 Subject: [PATCH 04/11] sql fix --- .../optional/2014_07_10_AICastingRules.sql | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/utils/sql/git/optional/2014_07_10_AICastingRules.sql b/utils/sql/git/optional/2014_07_10_AICastingRules.sql index c84d43cfb..2b8ea4511 100644 --- a/utils/sql/git/optional/2014_07_10_AICastingRules.sql +++ b/utils/sql/git/optional/2014_07_10_AICastingRules.sql @@ -1,12 +1,12 @@ -INSERT INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (1, 'Spells, AI_SpellCastFinishedFailRecast','800', 'AI spell recast time(MS) when an spell is cast but fails (ie stunned)'); -INSERT INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (1, 'Spells, AI_EngagedNoSpellMinRecast','500', 'AI spell recast time(MS) check when no spell is cast while engaged. (min time in random)'); -INSERT INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (1, 'Spells, AI_EngagedNoSpellMaxRecast','1000','AI spell recast time(MS) check when no spell is cast engaged.(max time in random)'); -INSERT INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (1, 'Spells, AI_EngagedBeneficialSelfChance,','100', 'Chance during first AI Cast check to do a beneficial spell on self.'); -INSERT INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (1, 'Spells, AI_EngagedBeneficialOtherChance','25', 'Chance during second AI Cast check to do a beneficial spell on others.'); -INSERT INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (1, 'Spells, AI_EngagedDetrimentalChance','20','Chance during third AI Cast check to do a determental spell on others.'); -INSERT INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (1, 'Spells, AI_PursueNoSpellMinRecast','500', 'AI spell recast time(MS) check when no spell is cast while chasing target. (min time in random)'); -INSERT INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (1, 'Spells, AI_PursueNoSpellMaxRecast','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_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_SpellCastFinishedFailRecast','800', 'AI spell recast time(MS) when an spell is cast but fails (ie stunned)'); +INSERT INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (1, 'Spells:AI_EngagedNoSpellMinRecast','500', 'AI spell recast time(MS) check when no spell is cast while engaged. (min time in random)'); +INSERT INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (1, 'Spells:AI_EngagedNoSpellMaxRecast','1000','AI spell recast time(MS) check when no spell is cast engaged.(max time in random)'); +INSERT INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (1, 'Spells:AI_EngagedBeneficialSelfChance,','100', 'Chance during first AI Cast check to do a beneficial spell on self.'); +INSERT INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (1, 'Spells:AI_EngagedBeneficialOtherChance','25', 'Chance during second AI Cast check to do a beneficial spell on others.'); +INSERT INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (1, 'Spells:AI_EngagedDetrimentalChance','20','Chance during third AI Cast check to do a determental spell on others.'); +INSERT INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (1, 'Spells:AI_PursueNoSpellMinRecast','500', 'AI spell recast time(MS) check when no spell is cast while chasing target. (min time in random)'); +INSERT INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (1, 'Spells:AI_PursueNoSpellMaxRecast','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_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 From 9e4a21d93466b9bb048492b2a2c1e2029f60806c Mon Sep 17 00:00:00 2001 From: KayenEQ Date: Wed, 23 Jul 2014 13:13:51 -0400 Subject: [PATCH 05/11] Allow negative values for avoidance to work. (debuffs) --- zone/attack.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zone/attack.cpp b/zone/attack.cpp index bf83c64fb..4c49b9187 100644 --- a/zone/attack.cpp +++ b/zone/attack.cpp @@ -278,7 +278,7 @@ bool Mob::CheckHitChance(Mob* other, SkillUseTypes skillinuse, int Hand, int16 c bonus += (owner->aabonuses.PetAvoidance + owner->spellbonuses.PetAvoidance + owner->itembonuses.PetAvoidance)*10; } - if(bonus > 0) { + if(bonus) { chancetohit -= ((bonus * chancetohit) / 1000); mlog(COMBAT__TOHIT, "Applied avoidance chance %.2f/10, yeilding %.2f", bonus, chancetohit); } From 152a7410b6aae022ab6d1d1b4d7e5c57a3ebee69 Mon Sep 17 00:00:00 2001 From: KayenEQ Date: Wed, 23 Jul 2014 16:57:59 -0400 Subject: [PATCH 06/11] debug msg removal --- zone/spells.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zone/spells.cpp b/zone/spells.cpp index f5cfcfd4a..fac7e2c59 100644 --- a/zone/spells.cpp +++ b/zone/spells.cpp @@ -5107,7 +5107,7 @@ bool Mob::AddRangedProc(uint16 spell_id, uint16 iChance, uint16 base_spell_id) { if(spell_id == SPELL_UNKNOWN) return(false); - Shout("Add Ranged Proc %i %i %i", spell_id, iChance, base_spell_id); + int i; for (i = 0; i < MAX_PROCS; i++) { if (RangedProcs[i].spellID == SPELL_UNKNOWN) { From 4f07be234387b5f225263adab4e78067d7212b67 Mon Sep 17 00:00:00 2001 From: KayenEQ Date: Wed, 23 Jul 2014 21:24:21 -0400 Subject: [PATCH 07/11] 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; From 0d5a0525cd9132c2ac75cd4a64db58d8991f4490 Mon Sep 17 00:00:00 2001 From: KayenEQ Date: Wed, 23 Jul 2014 22:08:47 -0400 Subject: [PATCH 08/11] minor fix --- zone/attack.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zone/attack.cpp b/zone/attack.cpp index d2438cfae..45e32e848 100644 --- a/zone/attack.cpp +++ b/zone/attack.cpp @@ -311,7 +311,7 @@ bool Mob::CheckHitChance(Mob* other, SkillUseTypes skillinuse, int Hand, int16 c hitBonus += (attacker->CastToNPC()->GetAccuracyRating() / 10.0f); //Modifier from database if(skillinuse == SkillArchery) - hitBonus -= hitBonus*RuleR(Combat, ArcheryHitPenalty); + hitBonus -= hitBonus*(RuleR(Combat, ArcheryHitPenalty)*100.0f); //Calculate final chance to hit chancetohit += ((chancetohit * (hitBonus - avoidanceBonus)) / 100.0f); From 83f94da43b74d10781aec6b58adc26b3ae087e6c Mon Sep 17 00:00:00 2001 From: KayenEQ Date: Thu, 24 Jul 2014 11:23:14 -0400 Subject: [PATCH 09/11] Spell Effect for melee mitigation will no longer use same bonus item shielding effect. Added support for spell effect melee mitigation to work on as item worn effects and AA. --- zone/attack.cpp | 6 +++--- zone/bonuses.cpp | 12 ++++++++---- zone/common.h | 1 + 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/zone/attack.cpp b/zone/attack.cpp index 45e32e848..7831ac9d6 100644 --- a/zone/attack.cpp +++ b/zone/attack.cpp @@ -715,7 +715,7 @@ void Mob::MeleeMitigation(Mob *attacker, int32 &damage, int32 minhit, ExtraAttac //reduce the damage from shielding item and aa based on the min dmg //spells offer pure mitigation damage -= (minhit * defender->itembonuses.MeleeMitigation / 100); - damage -= (damage * defender->spellbonuses.MeleeMitigation / 100); + damage -= (damage * (defender->spellbonuses.MeleeMitigationEffect + defender->itembonuses.MeleeMitigationEffect + defender->aabonuses.MeleeMitigationEffect) / 100); } if (damage < 0) @@ -757,7 +757,7 @@ int32 Mob::GetMeleeMitDmg(Mob *attacker, int32 damage, int32 minhit, damage -= ((int)d * interval); damage -= (minhit * itembonuses.MeleeMitigation / 100); - damage -= (damage * spellbonuses.MeleeMitigation / 100); + damage -= (damage * (spellbonuses.MeleeMitigationEffect + itembonuses.MeleeMitigationEffect + aabonuses.MeleeMitigationEffect) / 100); return damage; } @@ -771,7 +771,7 @@ int32 Client::GetMeleeMitDmg(Mob *attacker, int32 damage, int32 minhit, // floats for the rounding issues float dmg_interval = (damage - minhit) / 19.0; float dmg_bonus = minhit - dmg_interval; - float spellMeleeMit = spellbonuses.MeleeMitigation / 100.0; + float spellMeleeMit = (spellbonuses.MeleeMitigationEffect + itembonuses.MeleeMitigationEffect + aabonuses.MeleeMitigationEffect) / 100.0; if (GetClass() == WARRIOR) spellMeleeMit += 0.05; dmg_bonus -= dmg_bonus * (itembonuses.MeleeMitigation / 100.0); diff --git a/zone/bonuses.cpp b/zone/bonuses.cpp index cabdd4aa4..9fd8406b7 100644 --- a/zone/bonuses.cpp +++ b/zone/bonuses.cpp @@ -1376,6 +1376,10 @@ void Client::ApplyAABonuses(uint32 aaid, uint32 slots, StatBonuses* newbon) break; } + case SE_MeleeMitigation: + newbon->MeleeMitigationEffect -= base1; + break; + } } } @@ -1796,7 +1800,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* ne case SE_MeleeMitigation: //for some reason... this value is negative for increased mitigation - newbon->MeleeMitigation -= effect_value; + newbon->MeleeMitigationEffect -= effect_value; break; case SE_CriticalHitChance: @@ -3594,9 +3598,9 @@ void Mob::NegateSpellsBonuses(uint16 spell_id) break; case SE_MeleeMitigation: - spellbonuses.MeleeMitigation = effect_value; - itembonuses.MeleeMitigation = effect_value; - aabonuses.MeleeMitigation = effect_value; + spellbonuses.MeleeMitigationEffect = effect_value; + itembonuses.MeleeMitigationEffect = effect_value; + aabonuses.MeleeMitigationEffect = effect_value; break; case SE_CriticalHitChance: diff --git a/zone/common.h b/zone/common.h index 525ced928..19cd51ce1 100644 --- a/zone/common.h +++ b/zone/common.h @@ -266,6 +266,7 @@ struct StatBonuses { int16 StrikeThrough; // PoP: Strike Through % int16 MeleeMitigation; //i = Shielding + int16 MeleeMitigationEffect; //i = Spell Effect Melee Mitigation int16 CriticalHitChance[HIGHEST_SKILL+2]; //i int16 CriticalSpellChance; //i int16 SpellCritDmgIncrease; //i From 1b239b71193ea6ca8e7b8712804a61990e32aed1 Mon Sep 17 00:00:00 2001 From: KayenEQ Date: Thu, 24 Jul 2014 11:31:22 -0400 Subject: [PATCH 10/11] Support for AA derived AC bonus. --- zone/client_mods.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zone/client_mods.cpp b/zone/client_mods.cpp index 7ea18655f..0ead2cdfe 100644 --- a/zone/client_mods.cpp +++ b/zone/client_mods.cpp @@ -1967,7 +1967,7 @@ int32 Client::CalcEnduranceRegenCap() { int Client::GetRawACNoShield(int &shield_ac) const { - int ac = itembonuses.AC + spellbonuses.AC; + int ac = itembonuses.AC + spellbonuses.AC + aabonuses.AC; shield_ac = 0; const ItemInst *inst = m_inv.GetItem(MainSecondary); if(inst) From 9fcea56fbfbb4dfcd0a14e23b21aa96fa5bfaea9 Mon Sep 17 00:00:00 2001 From: KayenEQ Date: Thu, 24 Jul 2014 14:06:15 -0400 Subject: [PATCH 11/11] Alllow SE_PetMeleeMitigation to work on swarm pets. --- zone/attack.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/zone/attack.cpp b/zone/attack.cpp index 7831ac9d6..67f351e17 100644 --- a/zone/attack.cpp +++ b/zone/attack.cpp @@ -566,15 +566,15 @@ void Mob::MeleeMitigation(Mob *attacker, int32 &damage, int32 minhit, ExtraAttac if (!IsPet()) armor = (armor / RuleR(Combat, NPCACFactor)); - else{ - Mob *owner = nullptr; + + Mob *owner = nullptr; + if (IsPet()) owner = GetOwner(); - if (owner){ - PetACBonus = owner->aabonuses.PetMeleeMitigation - + owner->itembonuses.PetMeleeMitigation + - owner->spellbonuses.PetMeleeMitigation; - } - } + else if ((CastToNPC()->GetSwarmOwner())) + owner = entity_list.GetMobID(CastToNPC()->GetSwarmOwner()); + + if (owner) + PetACBonus = owner->aabonuses.PetMeleeMitigation + owner->itembonuses.PetMeleeMitigation + owner->spellbonuses.PetMeleeMitigation; armor += spellbonuses.AC + itembonuses.AC + PetACBonus + 1; }