diff --git a/changelog.txt b/changelog.txt index fe35332b6..92bb01e61 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,19 @@ EQEMu Changelog (Started on Sept 24, 2003 15:50) ------------------------------------------------------- +== 04/27/2014 == +Kayen: Implemented new table 'npc_spells_effects' and 'npc_spells_effects_entires'. + Implemented new field in 'npc_spell_effects_id' in npc_types. + + +These are used to directly apply spell effect bonuses to NPC's without requirings spells/buffs. +Example: Allow an npc to spawn with an innate 50 pt damage shield and a 5% chance to critical hit. +Please see the wiki page: http://wiki.eqemulator.org/p?npc_spell_effects_entries for details. +*NPC's can now do critical heals / damage spells if bonus is applied from table. + +Required SQL: utils/sql/git/required/2014_04_27_AISpellEffects.sql +Note: 30 examples of spell effects have been included by default in this sql. Edited/removed as needed. + + == 04/25/2014 == cavedude: Corrected a crash in spawn_conditions caused by NPCs on a one way path. cavedude: Added strict column to spawn_events which will prevent an event from enabling if it's mid-cycle. diff --git a/utils/sql/git/required/2014_04_27_AISpellEffects.sql b/utils/sql/git/required/2014_04_27_AISpellEffects.sql new file mode 100644 index 000000000..6a5645513 --- /dev/null +++ b/utils/sql/git/required/2014_04_27_AISpellEffects.sql @@ -0,0 +1,104 @@ +-- Note: The data entered into the new table are only examples and can be deleted/modified as needed. + +ALTER TABLE `npc_types` ADD `npc_spells_effects_id` int( 11 ) UNSIGNED NOT NULL DEFAULT '0' AFTER `npc_spells_id`; + +SET FOREIGN_KEY_CHECKS=0; + +-- ---------------------------- +-- Table structure for `npc_spells_effects` +-- ---------------------------- +DROP TABLE IF EXISTS `npc_spells_effects`; +CREATE TABLE `npc_spells_effects` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `name` tinytext, + `parent_list` int(11) unsigned NOT NULL DEFAULT '0', + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=1080 DEFAULT CHARSET=latin1; + +-- ---------------------------- +-- Records of npc_spells_effects +-- ---------------------------- +INSERT INTO `npc_spells_effects` VALUES ('1', 'Critical Melee [All Skills]', '0'); +INSERT INTO `npc_spells_effects` VALUES ('2', 'Damage Shield', '0'); +INSERT INTO `npc_spells_effects` VALUES ('3', 'Melee Haste', '0'); +INSERT INTO `npc_spells_effects` VALUES ('4', 'Resist Spell Chance', '0'); +INSERT INTO `npc_spells_effects` VALUES ('5', 'Resist Direct Dmg Spell Chance', '0'); +INSERT INTO `npc_spells_effects` VALUES ('6', 'Reflect Spell Chance', '0'); +INSERT INTO `npc_spells_effects` VALUES ('7', 'Spell Damage Shield', '0'); +INSERT INTO `npc_spells_effects` VALUES ('8', 'Melee Mitigation [All]', '0'); +INSERT INTO `npc_spells_effects` VALUES ('9', 'Avoid Melee', '0'); +INSERT INTO `npc_spells_effects` VALUES ('10', 'Riposte Chance', '0'); +INSERT INTO `npc_spells_effects` VALUES ('11', 'Dodge Chance', '0'); +INSERT INTO `npc_spells_effects` VALUES ('12', 'Parry Chance', '0'); +INSERT INTO `npc_spells_effects` VALUES ('13', 'Decrease Dmg Taken [2HS]', '0'); +INSERT INTO `npc_spells_effects` VALUES ('14', 'Increase Dmg Taken [1HS]', '0'); +INSERT INTO `npc_spells_effects` VALUES ('15', 'Block Chance', '0'); +INSERT INTO `npc_spells_effects` VALUES ('16', 'Melee Lifetap', '0'); +INSERT INTO `npc_spells_effects` VALUES ('17', 'Hit Chance', '0'); +INSERT INTO `npc_spells_effects` VALUES ('18', 'Increase Dmg [1HS]', '0'); +INSERT INTO `npc_spells_effects` VALUES ('19', 'Increase Archery Dmg', '0'); +INSERT INTO `npc_spells_effects` VALUES ('20', 'Flurry Chance', '0'); +INSERT INTO `npc_spells_effects` VALUES ('21', 'Add Damage [2HS]', '0'); +INSERT INTO `npc_spells_effects` VALUES ('22', 'Divine Aura', '0'); +INSERT INTO `npc_spells_effects` VALUES ('23', 'Cast CH on Kill', '0'); +INSERT INTO `npc_spells_effects` VALUES ('24', 'Critical Heal', '0'); +INSERT INTO `npc_spells_effects` VALUES ('25', 'Critical Direct Dmg', '0'); +INSERT INTO `npc_spells_effects` VALUES ('26', 'Heal Rate', '0'); +INSERT INTO `npc_spells_effects` VALUES ('27', 'Negate Damage Shield', '0'); +INSERT INTO `npc_spells_effects` VALUES ('28', 'Increase Spell Vulnerability [All]', '0'); +INSERT INTO `npc_spells_effects` VALUES ('29', 'Decrease Spell Vulnerability [FR]', '0'); +INSERT INTO `npc_spells_effects` VALUES ('30', 'Movement Speed', '0'); + +SET FOREIGN_KEY_CHECKS=0; + +-- ---------------------------- +-- Table structure for `npc_spells_effects_entries` +-- ---------------------------- +DROP TABLE IF EXISTS `npc_spells_effects_entries`; +CREATE TABLE `npc_spells_effects_entries` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `npc_spells_effects_id` int(11) NOT NULL DEFAULT '0', + `spell_effect_id` smallint(5) NOT NULL DEFAULT '0', + `minlevel` tinyint(3) unsigned NOT NULL, + `maxlevel` tinyint(3) unsigned NOT NULL, + `se_base` int(11) NOT NULL DEFAULT '0', + `se_limit` int(11) NOT NULL DEFAULT '0', + `se_max` int(11) NOT NULL DEFAULT '0', + PRIMARY KEY (`id`), + UNIQUE KEY `spellsid_spellid` (`npc_spells_effects_id`,`spell_effect_id`) +) ENGINE=InnoDB AUTO_INCREMENT=18374 DEFAULT CHARSET=latin1; + +-- ---------------------------- +-- Records of npc_spells_effects_entries +-- ---------------------------- +INSERT INTO `npc_spells_effects_entries` VALUES ('1', '1', '169', '0', '255', '10000', '-1', '0'); +INSERT INTO `npc_spells_effects_entries` VALUES ('2', '2', '59', '0', '255', '-60', '0', '0'); +INSERT INTO `npc_spells_effects_entries` VALUES ('3', '3', '11', '0', '255', '150', '0', '0'); +INSERT INTO `npc_spells_effects_entries` VALUES ('4', '4', '180', '0', '255', '50', '0', '0'); +INSERT INTO `npc_spells_effects_entries` VALUES ('5', '5', '378', '0', '255', '85', '0', '0'); +INSERT INTO `npc_spells_effects_entries` VALUES ('6', '6', '158', '0', '255', '50', '0', '0'); +INSERT INTO `npc_spells_effects_entries` VALUES ('7', '7', '157', '0', '255', '-300', '0', '0'); +INSERT INTO `npc_spells_effects_entries` VALUES ('8', '8', '168', '0', '255', '-50', '0', '0'); +INSERT INTO `npc_spells_effects_entries` VALUES ('9', '9', '172', '0', '255', '10000', '0', '0'); +INSERT INTO `npc_spells_effects_entries` VALUES ('10', '10', '173', '0', '255', '10000', '0', '0'); +INSERT INTO `npc_spells_effects_entries` VALUES ('11', '11', '174', '0', '255', '10000', '0', '0'); +INSERT INTO `npc_spells_effects_entries` VALUES ('12', '12', '175', '0', '255', '10000', '0', '0'); +INSERT INTO `npc_spells_effects_entries` VALUES ('13', '13', '197', '0', '255', '-80', '3', '0'); +INSERT INTO `npc_spells_effects_entries` VALUES ('14', '14', '197', '0', '255', '80', '1', '0'); +INSERT INTO `npc_spells_effects_entries` VALUES ('15', '15', '188', '0', '255', '10000', '0', '0'); +INSERT INTO `npc_spells_effects_entries` VALUES ('16', '16', '178', '0', '255', '90', '0', '0'); +INSERT INTO `npc_spells_effects_entries` VALUES ('17', '17', '184', '0', '255', '10000', '-1', '0'); +INSERT INTO `npc_spells_effects_entries` VALUES ('18', '18', '185', '0', '255', '100', '1', '0'); +INSERT INTO `npc_spells_effects_entries` VALUES ('19', '19', '301', '0', '255', '100', '0', '0'); +INSERT INTO `npc_spells_effects_entries` VALUES ('20', '20', '279', '0', '255', '50', '0', '0'); +INSERT INTO `npc_spells_effects_entries` VALUES ('21', '21', '220', '0', '255', '2000', '1', '0'); +INSERT INTO `npc_spells_effects_entries` VALUES ('22', '22', '40', '0', '255', '1', '0', '0'); +INSERT INTO `npc_spells_effects_entries` VALUES ('23', '23', '360', '0', '255', '100', '13', '0'); +INSERT INTO `npc_spells_effects_entries` VALUES ('24', '24', '274', '0', '255', '90', '0', '0'); +INSERT INTO `npc_spells_effects_entries` VALUES ('25', '25', '294', '0', '255', '100', '200', '0'); +INSERT INTO `npc_spells_effects_entries` VALUES ('26', '26', '120', '0', '255', '50', '0', '0'); +INSERT INTO `npc_spells_effects_entries` VALUES ('27', '27', '382', '0', '255', '0', '55', '0'); +INSERT INTO `npc_spells_effects_entries` VALUES ('28', '28', '296', '0', '255', '1000', '-1', '0'); +INSERT INTO `npc_spells_effects_entries` VALUES ('29', '29', '296', '0', '255', '-50', '2', '0'); +INSERT INTO `npc_spells_effects_entries` VALUES ('30', '30', '3', '0', '255', '60', '0', '0'); + diff --git a/zone/MobAI.cpp b/zone/MobAI.cpp index 7574f19ad..3128eb111 100644 --- a/zone/MobAI.cpp +++ b/zone/MobAI.cpp @@ -1901,7 +1901,7 @@ bool NPC::AI_EngagedCastCheck() { AIautocastspell_timer->Disable(); //prevent the timer from going off AGAIN while we are casting. mlog(AI__SPELLS, "Engaged autocast check triggered. Trying to cast healing spells then maybe offensive spells."); - Shout("KAYEN"); + // try casting a heal or gate if (!AICastSpell(this, 100, SpellType_Heal | SpellType_Escape | SpellType_InCombatBuff)) { // try casting a heal on nearby @@ -2437,12 +2437,9 @@ void NPC::ApplyAISpellEffects(StatBonuses* newbon) { if (!AI_HasSpellsEffects()) return; - - for(int i=0; i < AIspellsEffects.size(); i++) { - Shout("ApplyAISpellEffects %i %i %i %i", AIspellsEffects[i].spelleffectid, AIspellsEffects[i].base, AIspellsEffects[i].limit,AIspellsEffects[i].max); ApplySpellsBonuses(0, 0, newbon, 0, false, 0,-1, true, AIspellsEffects[i].spelleffectid, AIspellsEffects[i].base, AIspellsEffects[i].limit,AIspellsEffects[i].max); } @@ -2456,7 +2453,6 @@ void NPC::AddSpellEffectToNPCList(uint16 iSpellEffectID, int32 base, int32 limit if(!iSpellEffectID) return; - HasAISpellEffects = true; AISpellsEffects_Struct t; @@ -2465,7 +2461,6 @@ void NPC::AddSpellEffectToNPCList(uint16 iSpellEffectID, int32 base, int32 limit t.base = base; t.limit = limit; t.max = max; - Shout("AddSpellEffectToNPCList %i %i %i %i", iSpellEffectID,base, limit, max ); AIspellsEffects.push_back(t); } @@ -2692,7 +2687,7 @@ DBnpcspellseffects_Struct* ZoneDatabase::GetNPCSpellsEffects(uint32 iDBSpellsEff mysql_free_result(result); if (RunQuery(query, MakeAnyLenString(&query, "SELECT spell_effect_id, minlevel, maxlevel,se_base, se_limit, se_max from npc_spells_effects_entries where npc_spells_effects_id=%d ORDER BY minlevel", iDBSpellsEffectsID), errbuf, &result)) { safe_delete_array(query); - uint32 tmpSize = sizeof(DBnpcspellseffects_Struct) + (sizeof(DBnpcspellseffects_Struct) * mysql_num_rows(result)); + uint32 tmpSize = sizeof(DBnpcspellseffects_Struct) + (sizeof(DBnpcspellseffects_entries_Struct) * mysql_num_rows(result)); npc_spellseffects_cache[iDBSpellsEffectsID] = (DBnpcspellseffects_Struct*) new uchar[tmpSize]; memset(npc_spellseffects_cache[iDBSpellsEffectsID], 0, tmpSize); npc_spellseffects_cache[iDBSpellsEffectsID]->parent_list = tmpparent_list; diff --git a/zone/bonuses.cpp b/zone/bonuses.cpp index 341a62b74..db0f81115 100644 --- a/zone/bonuses.cpp +++ b/zone/bonuses.cpp @@ -1158,14 +1158,20 @@ void Client::ApplyAABonuses(uint32 aaid, uint32 slots, StatBonuses* newbon) break; } + case SE_SpellEffectResistChance: { for(int e = 0; e < MAX_RESISTABLE_EFFECTS*2; e+=2) { - if(!newbon->SEResist[e] || ((newbon->SEResist[e] = base2) && (newbon->SEResist[e+1] < base1)) ){ - newbon->SEResist[e] = base2; - newbon->SEResist[e+1] = base1; - break; + if(newbon->SEResist[e+1] && (newbon->SEResist[e] == base2) && (newbon->SEResist[e+1] < base1)){ + newbon->SEResist[e] = base2; //Spell Effect ID + newbon->SEResist[e+1] = base1; //Resist Chance + break; + } + else if (!newbon->SEResist[e+1]){ + newbon->SEResist[e] = base2; //Spell Effect ID + newbon->SEResist[e+1] = base1; //Resist Chance + break; } } break; @@ -1791,10 +1797,10 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* ne newbon->MeleeLifetap += spells[spell_id].base[i]; else if((effect_value < 0) && (newbon->MeleeLifetap > effect_value)) - newbon->MeleeLifetap = spells[spell_id].base[i]; + newbon->MeleeLifetap = effect_value; - else if(newbon->MeleeLifetap < spells[spell_id].base[i]) - newbon->MeleeLifetap = spells[spell_id].base[i]; + else if(newbon->MeleeLifetap < effect_value) + newbon->MeleeLifetap = effect_value; break; } @@ -2494,10 +2500,14 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* ne { for(int e = 0; e < MAX_RESISTABLE_EFFECTS*2; e+=2) { - if(!newbon->SEResist[e] && - ((newbon->SEResist[e] = base2) && (newbon->SEResist[e+1] < effect_value)) ){ - newbon->SEResist[e] = base2; - newbon->SEResist[e+1] = effect_value; + if(newbon->SEResist[e+1] && (newbon->SEResist[e] == base2) && (newbon->SEResist[e+1] < effect_value)){ + newbon->SEResist[e] = base2; //Spell Effect ID + newbon->SEResist[e+1] = effect_value; //Resist Chance + break; + } + else if (!newbon->SEResist[e+1]){ + newbon->SEResist[e] = base2; //Spell Effect ID + newbon->SEResist[e+1] = effect_value; //Resist Chance break; } } @@ -2598,7 +2608,15 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* ne case SE_Screech: newbon->Screech = effect_value; break; - + + //Special custom cases for loading effects on to NPC from 'npc_spels_effects' table + if (IsAISpellEffect) { + + //Non-Focused Effect to modify incomming spell damage by resist type. + case SE_FcSpellVulnerability: + ModVulnerability(base2, effect_value); + break; + } } } } diff --git a/zone/effects.cpp b/zone/effects.cpp index 329c69084..f2a8db2a6 100644 --- a/zone/effects.cpp +++ b/zone/effects.cpp @@ -55,9 +55,36 @@ int32 NPC::GetActSpellDamage(uint16 spell_id, int32 value, Mob* target) { else value -= target->GetFcDamageAmtIncoming(this, spell_id)/spells[spell_id].buffduration; } - + value += dmg*SpellFocusDMG/100; + if (AI_HasSpellsEffects()){ + int16 chance = 0; + int ratio = 0; + + if (spells[spell_id].buffduration == 0) { + + chance += spellbonuses.CriticalSpellChance + spellbonuses.FrenziedDevastation; + + if (chance && MakeRandomInt(1,100) <= chance){ + + ratio += spellbonuses.SpellCritDmgIncrease + spellbonuses.SpellCritDmgIncNoStack; + value += (value*ratio)/100; + entity_list.MessageClose_StringID(this, true, 100, MT_SpellCrits, OTHER_CRIT_BLAST, GetCleanName(), itoa(-value)); + } + } + else { + + chance += spellbonuses.CriticalDoTChance; + + if (chance && MakeRandomInt(1,100) <= chance){ + + ratio += spellbonuses.DotCritDmgIncrease; + value += (value*ratio)/100; + } + } + } + return value; } @@ -254,6 +281,21 @@ int32 NPC::GetActSpellHealing(uint16 spell_id, int32 value, Mob* target) { value += target->GetFocusIncoming(focusFcHealAmtIncoming, SE_FcHealAmtIncoming, this, spell_id); value += value*target->GetHealRate(spell_id, this)/100; } + + //Allow for critical heal chance if NPC is loading spell effect bonuses. + if (AI_HasSpellsEffects()){ + + if(spells[spell_id].buffduration < 1) { + + if(spellbonuses.CriticalHealChance && (MakeRandomInt(0,99) < spellbonuses.CriticalHealChance)) { + value = value*2; + entity_list.MessageClose_StringID(this, true, 100, MT_SpellCrits, OTHER_CRIT_HEAL, GetCleanName(), itoa(value)); + } + } + else if(spellbonuses.CriticalHealOverTime && (MakeRandomInt(0,99) < spellbonuses.CriticalHealOverTime)) { + value = value*2; + } + } return value; } diff --git a/zone/mob.cpp b/zone/mob.cpp index 48452f26a..dcd204d0b 100644 --- a/zone/mob.cpp +++ b/zone/mob.cpp @@ -4150,10 +4150,10 @@ void Mob::TrySpellOnKill(uint8 level, uint16 spell_id) for (int i = 0; i < EFFECT_COUNT; i++) { if (spells[spell_id].effectid[i] == SE_SpellOnKill2) { - if (spells[spell_id].max[i] <= level) + if (IsValidSpell(spells[spell_id].base2[i]) && spells[spell_id].max[i] <= level) { if(MakeRandomInt(0,99) < spells[spell_id].base[i]) - SpellFinished(spells[spell_id].base2[i], this); + SpellFinished(spells[spell_id].base2[i], this, 10, 0, -1, spells[spells[spell_id].base2[i]].ResistDiff); } } } @@ -4166,19 +4166,19 @@ void Mob::TrySpellOnKill(uint8 level, uint16 spell_id) // Allow to check AA, items and buffs in all cases. Base2 = Spell to fire | Base1 = % chance | Base3 = min level for(int i = 0; i < MAX_SPELL_TRIGGER*3; i+=3) { - if(aabonuses.SpellOnKill[i] && (level >= aabonuses.SpellOnKill[i + 2])) { + if(aabonuses.SpellOnKill[i] && IsValidSpell(aabonuses.SpellOnKill[i]) && (level >= aabonuses.SpellOnKill[i + 2])) { if(MakeRandomInt(0, 99) < static_cast(aabonuses.SpellOnKill[i + 1])) - SpellFinished(aabonuses.SpellOnKill[i], this); + SpellFinished(aabonuses.SpellOnKill[i], this, 10, 0, -1, spells[aabonuses.SpellOnKill[i]].ResistDiff); } - if(itembonuses.SpellOnKill[i] && (level >= itembonuses.SpellOnKill[i + 2])){ + if(itembonuses.SpellOnKill[i] && IsValidSpell(itembonuses.SpellOnKill[i]) && (level >= itembonuses.SpellOnKill[i + 2])){ if(MakeRandomInt(0, 99) < static_cast(itembonuses.SpellOnKill[i + 1])) - SpellFinished(itembonuses.SpellOnKill[i], this); + SpellFinished(itembonuses.SpellOnKill[i], this, 10, 0, -1, spells[aabonuses.SpellOnKill[i]].ResistDiff); } - if(spellbonuses.SpellOnKill[i] && (level >= spellbonuses.SpellOnKill[i + 2])) { + if(spellbonuses.SpellOnKill[i] && IsValidSpell(spellbonuses.SpellOnKill[i]) && (level >= spellbonuses.SpellOnKill[i + 2])) { if(MakeRandomInt(0, 99) < static_cast(spellbonuses.SpellOnKill[i + 1])) - SpellFinished(spellbonuses.SpellOnKill[i], this); + SpellFinished(spellbonuses.SpellOnKill[i], this, 10, 0, -1, spells[aabonuses.SpellOnKill[i]].ResistDiff); } } @@ -4193,21 +4193,21 @@ bool Mob::TrySpellOnDeath() return false; for(int i = 0; i < MAX_SPELL_TRIGGER*2; i+=2) { - if(IsClient() && aabonuses.SpellOnDeath[i]) { + if(IsClient() && aabonuses.SpellOnDeath[i] && IsValidSpell(aabonuses.SpellOnDeath[i])) { if(MakeRandomInt(0, 99) < static_cast(aabonuses.SpellOnDeath[i + 1])) { - SpellFinished(aabonuses.SpellOnDeath[i], this); + SpellFinished(aabonuses.SpellOnDeath[i], this, 10, 0, -1, spells[aabonuses.SpellOnDeath[i]].ResistDiff); } } - if(itembonuses.SpellOnDeath[i]) { + if(itembonuses.SpellOnDeath[i] && IsValidSpell(itembonuses.SpellOnDeath[i])) { if(MakeRandomInt(0, 99) < static_cast(itembonuses.SpellOnDeath[i + 1])) { - SpellFinished(itembonuses.SpellOnDeath[i], this); + SpellFinished(itembonuses.SpellOnDeath[i], this, 10, 0, -1, spells[itembonuses.SpellOnDeath[i]].ResistDiff); } } - if(spellbonuses.SpellOnDeath[i]) { + if(spellbonuses.SpellOnDeath[i] && IsValidSpell(spellbonuses.SpellOnDeath[i])) { if(MakeRandomInt(0, 99) < static_cast(spellbonuses.SpellOnDeath[i + 1])) { - SpellFinished(spellbonuses.SpellOnDeath[i], this); + SpellFinished(spellbonuses.SpellOnDeath[i], this, 10, 0, -1, spells[spellbonuses.SpellOnDeath[i]].ResistDiff); } } } diff --git a/zone/spell_effects.cpp b/zone/spell_effects.cpp index 4ecb9d221..8731dfaab 100644 --- a/zone/spell_effects.cpp +++ b/zone/spell_effects.cpp @@ -5675,7 +5675,7 @@ uint16 Mob::GetSpellEffectResistChance(uint16 spell_id) if(!IsValidSpell(spell_id)) return 0; - if (!aabonuses.SEResist[0] && !spellbonuses.SEResist[0] && !itembonuses.SEResist[0]) + if (!aabonuses.SEResist[1] && !spellbonuses.SEResist[1] && !itembonuses.SEResist[1]) return 0; uint16 resist_chance = 0;