diff --git a/changelog.txt b/changelog.txt index 3532d6e1d..1a717d165 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,9 +1,11 @@ EQEMu Changelog (Started on Sept 24, 2003 15:50) ------------------------------------------------------- + == 07/16/2014 == Uleat: Initial commit of new client/server 'dictionaries' - work in-progress... Changed equipment slot references to reflect new naming conventions. Lua enumerations maintain both the old and new names as to not break existing scripts..but, the old names are deprecated. + == 07/14/2014 == KLS: Changes to CMake build -Lua builds by default now @@ -11,6 +13,45 @@ KLS: Changes to CMake build -Binary files will now be put into ${CMAKE_BINARY_DIR}/bin instead of ${CMAKE_BINARY_DIR}/Bin The last two are of note to people on non-windows systems as case sensitivity is important. Edit your scripts accordingly, thank you. + +== 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 + +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 + + == 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..0fd413e5a 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 ) @@ -345,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 new file mode 100644 index 000000000..e44626785 --- /dev/null +++ b/utils/sql/git/optional/2014_07_10_AICastingRules.sql @@ -0,0 +1,15 @@ +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.'); + +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_04_10_No_Target_With_Hotkey.sql b/utils/sql/git/required/2014_04_10_No_Target_With_Hotkey.sql index 12e9963a5..15ce809af 100644 --- a/utils/sql/git/required/2014_04_10_No_Target_With_Hotkey.sql +++ b/utils/sql/git/required/2014_04_10_No_Target_With_Hotkey.sql @@ -1,3 +1,5 @@ -ALTER TABLE `npc_types` ADD `no_target_hotkey` tinyint( 1 ) UNSIGNED NOT NULL DEFAULT '0' AFTER `healscale`; +-- 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`; 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..1060efdd5 --- /dev/null +++ b/utils/sql/git/required/2014_07_10_npc_spells.sql @@ -0,0 +1,22 @@ +-- 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'; +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 a3caf84c5..37b4c33e7 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 5b20bf365..67f351e17 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 > 0) { - 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)*100.0f); + + //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 @@ -564,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; } @@ -713,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) @@ -755,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; } @@ -769,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); @@ -1297,11 +1299,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 +1365,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 +1901,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 +1928,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 +1938,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 +3849,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 +3871,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 +3890,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 +3904,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 +3963,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 +4041,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 != MainPrimary) //Is Archery intened to proc at 50% rate? ProcChance /= 2; @@ -4130,6 +4055,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 != MainPrimary) //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 +4080,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 +4096,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 +4748,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; +} diff --git a/zone/bonuses.cpp b/zone/bonuses.cpp index 51db73157..9fd8406b7 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; @@ -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; @@ -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) { @@ -1368,6 +1376,10 @@ void Client::ApplyAABonuses(uint32 aaid, uint32 slots, StatBonuses* newbon) break; } + case SE_MeleeMitigation: + newbon->MeleeMitigationEffect -= base1; + break; + } } } @@ -1788,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: @@ -1833,16 +1845,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; } @@ -3588,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: @@ -3610,9 +3620,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/bot.cpp b/zone/bot.cpp index 34c5801e7..31c496c3f 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -7735,10 +7735,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 MainPrimary: 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/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) diff --git a/zone/common.h b/zone/common.h index 2aa0101be..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 @@ -275,7 +276,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/mob.cpp b/zone/mob.cpp index c97cd182e..a55390050 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 acf618efe..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(); @@ -259,9 +260,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) @@ -1925,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 1e38f928f..d7290645c 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; } @@ -314,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); @@ -413,14 +434,16 @@ 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; + int32 avoidance_rating; int16 attack_count; uint32 npc_mana; float spellscale; @@ -454,11 +477,15 @@ protected: uint32 roambox_min_delay; uint16 skills[HIGHEST_SKILL+1]; + uint32 equipment[EmuConstants::EQUIPMENT_SIZE]; //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) - uint8 prim_melee_type; //Sets the Primary Weapon attack message and animation - uint8 sec_melee_type; //Sets the Secondary Weapon attack message and animation + 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 3333bf0ba..b25b1543f 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; @@ -814,39 +809,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) @@ -1019,19 +982,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() || @@ -1041,32 +1015,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); @@ -1075,54 +1050,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) @@ -1235,39 +1192,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) @@ -1313,20 +1238,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()){ @@ -1395,7 +1313,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; @@ -1413,9 +1334,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; } @@ -1445,7 +1369,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; @@ -2174,7 +2098,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; @@ -2200,6 +2124,19 @@ void Mob::DoMeleeSkillAttackDmg(Mob* other, uint16 weapon_damage, SkillUseTypes hate += ucDamageBonus; } + if(skillinuse == SkillBash){ + if(IsClient()){ + ItemInst *item = CastToClient()->GetInv().GetItem(MainSecondary); + 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; @@ -2211,18 +2148,14 @@ void Mob::DoMeleeSkillAttackDmg(Mob* other, uint16 weapon_damage, SkillUseTypes damage = max_hit; else damage = MakeRandomInt(min_hit, max_hit); - + if(!other->CheckHitChance(this, skillinuse, Hand, chance_mod)) { damage = 0; } 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) { @@ -2235,19 +2168,6 @@ void Mob::DoMeleeSkillAttackDmg(Mob* other, uint16 weapon_damage, SkillUseTypes else damage = -5; - if(skillinuse == SkillBash){ - if(IsClient()){ - ItemInst *item = CastToClient()->GetInv().GetItem(MainSecondary); - 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; @@ -2256,6 +2176,7 @@ void Mob::DoMeleeSkillAttackDmg(Mob* other, uint16 weapon_damage, SkillUseTypes 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 0255cc0df..5b20748ec 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 9bbfa8068..fac7e2c59 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) { diff --git a/zone/zonedb.cpp b/zone/zonedb.cpp index dbd430252..98b302ba4 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," @@ -1103,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," @@ -1166,8 +1169,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; @@ -1286,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/zonedb.h b/zone/zonedb.h index 7612ba84f..22aa0eba4 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..3b5b91577 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 @@ -110,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;