diff --git a/utils/sql/git/required/2014_04_25_AISpellEffects.sql b/utils/sql/git/required/2014_04_25_AISpellEffects.sql new file mode 100644 index 000000000..4a450b312 --- /dev/null +++ b/utils/sql/git/required/2014_04_25_AISpellEffects.sql @@ -0,0 +1,50 @@ +-- TEST SQL -- + +ALTER TABLE `npc_types` ADD `npc_spells_effects` int( 11 ) UNSIGNED NOT NULL DEFAULT '0' AFTER `npc_spells`; + +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', '1', '168', '0', '255', '3999', '-1', '0'); +INSERT INTO `npc_spells_effects_entries` VALUES ('3', '2', '167', '0', '255', '98', '-1', '0'); + + + +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=1079 DEFAULT CHARSET=latin1; + +-- ---------------------------- +-- Records of npc_spells_effects +-- ---------------------------- +INSERT INTO `npc_spells_effects` VALUES ('1', 'Critical', '0'); +INSERT INTO `npc_spells_effects` VALUES ('2', 'ParentTest', '1'); diff --git a/zone/MobAI.cpp b/zone/MobAI.cpp index bcb4b131b..48814425b 100644 --- a/zone/MobAI.cpp +++ b/zone/MobAI.cpp @@ -530,6 +530,7 @@ void NPC::AI_Start(uint32 iMoveDelay) { if (NPCTypedata) { AI_AddNPCSpells(NPCTypedata->npc_spells_id); ProcessSpecialAbilities(NPCTypedata->special_abilities); + AI_AddNPCSpellsEffects(NPCTypedata->npc_spells_effects_id); } SendTo(GetX(), GetY(), GetZ()); @@ -1879,7 +1880,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 @@ -2277,6 +2278,7 @@ create table npc_spells_entries ( */ bool IsSpellInList(DBnpcspells_Struct* spell_list, int16 iSpellID); +bool IsSpellEffectInList(DBnpcspellseffects_Struct* spelleffect_list, uint16 iSpellEffectID, int32 base, int32 limit, int32 max); bool Compare_AI_Spells(AISpells_Struct i, AISpells_Struct j); bool NPC::AI_AddNPCSpells(uint32 iDBSpellsID) { @@ -2351,6 +2353,110 @@ bool NPC::AI_AddNPCSpells(uint32 iDBSpellsID) { return true; } +bool NPC::AI_AddNPCSpellsEffects(uint32 iDBSpellsEffectsID) { + + npc_spells_effects_id = iDBSpellsEffectsID; + AIspellsEffects.clear(); + + if (iDBSpellsEffectsID == 0) + return false; + + DBnpcspellseffects_Struct* spell_effects_list = database.GetNPCSpellsEffects(iDBSpellsEffectsID); + + if (!spell_effects_list) { + return false; + } + + DBnpcspellseffects_Struct* parentlist = database.GetNPCSpellsEffects(spell_effects_list->parent_list); + + uint32 i; +#if MobAI_DEBUG_Spells >= 10 + std::cout << "Loading NPCSpellsEffects onto " << this->GetName() << ": dbspellseffectsid=" << iDBSpellsEffectsID; + if (spell_effects_list) { + std::cout << " (found, " << spell_effects_list->numentries << "), parentlist=" << spell_effects)list->parent_list; + if (spell_effects_list->parent_list) { + if (parentlist) { + std::cout << " (found, " << parentlist->numentries << ")"; + } + else + std::cout << " (not found)"; + } + } + else + std::cout << " (not found)"; + std::cout << std::endl; +#endif + + if (parentlist) { + for (i=0; inumentries; i++) { + if (GetLevel() >= parentlist->entries[i].minlevel && GetLevel() <= parentlist->entries[i].maxlevel && parentlist->entries[i].spelleffectid > 0) { + if (!IsSpellEffectInList(spell_effects_list, parentlist->entries[i].spelleffectid, parentlist->entries[i].base, + parentlist->entries[i].limit, parentlist->entries[i].max)) + { + AddSpellEffectToNPCList(parentlist->entries[i].spelleffectid, + parentlist->entries[i].base, parentlist->entries[i].limit, + parentlist->entries[i].max); + } + } + } + } + + for (i=0; inumentries; i++) { + if (GetLevel() >= spell_effects_list->entries[i].minlevel && GetLevel() <= spell_effects_list->entries[i].maxlevel && spell_effects_list->entries[i].spelleffectid > 0) { + AddSpellEffectToNPCList(spell_effects_list->entries[i].spelleffectid, + spell_effects_list->entries[i].base, spell_effects_list->entries[i].limit, + spell_effects_list->entries[i].max); + } + } + + return true; +} + +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); + } + + return; +} + +// adds a spell to the list, taking into account priority and resorting list as needed. +void NPC::AddSpellEffectToNPCList(uint16 iSpellEffectID, int32 base, int32 limit, int32 max) +{ + + if(!iSpellEffectID) + return; + + + HasAISpellEffects = true; + AISpellsEffects_Struct t; + + t.spelleffectid = iSpellEffectID; + t.base = base; + t.limit = limit; + t.max = max; + Shout("AddSpellEffectToNPCList %i %i %i %i", iSpellEffectID,base, limit, max ); + AIspellsEffects.push_back(t); +} + +bool IsSpellEffectInList(DBnpcspellseffects_Struct* spelleffect_list, uint16 iSpellEffectID, int32 base, int32 limit, int32 max) { + for (uint32 i=0; i < spelleffect_list->numentries; i++) { + if (spelleffect_list->entries[i].spelleffectid == iSpellEffectID && spelleffect_list->entries[i].base == base + && spelleffect_list->entries[i].limit == limit && spelleffect_list->entries[i].max == max) + return true; + } + return false; +} + bool IsSpellInList(DBnpcspells_Struct* spell_list, int16 iSpellID) { for (uint32 i=0; i < spell_list->numentries; i++) { if (spell_list->entries[i].spellid == iSpellID) @@ -2415,6 +2521,7 @@ void NPC::AISpellsList(Client *c) DBnpcspells_Struct* ZoneDatabase::GetNPCSpells(uint32 iDBSpellsID) { if (iDBSpellsID == 0) return 0; + if (!npc_spells_cache) { npc_spells_maxid = GetMaxNPCSpellsID(); npc_spells_cache = new DBnpcspells_Struct*[npc_spells_maxid+1]; @@ -2424,11 +2531,13 @@ DBnpcspells_Struct* ZoneDatabase::GetNPCSpells(uint32 iDBSpellsID) { npc_spells_loadtried[i] = false; } } + if (iDBSpellsID > npc_spells_maxid) return 0; if (npc_spells_cache[iDBSpellsID]) { // it's in the cache, easy =) return npc_spells_cache[iDBSpellsID]; } + else if (!npc_spells_loadtried[iDBSpellsID]) { // no reason to ask the DB again if we have failed once already npc_spells_loadtried[iDBSpellsID] = true; char errbuf[MYSQL_ERRMSG_SIZE]; @@ -2439,7 +2548,7 @@ DBnpcspells_Struct* ZoneDatabase::GetNPCSpells(uint32 iDBSpellsID) { if (RunQuery(query, MakeAnyLenString(&query, "SELECT id, parent_list, attack_proc, proc_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); + row = mysql_fetch_row(result); uint32 tmpparent_list = atoi(row[1]); int16 tmpattack_proc = atoi(row[2]); uint8 tmpproc_chance = atoi(row[3]); @@ -2527,3 +2636,104 @@ uint32 ZoneDatabase::GetMaxNPCSpellsID() { return 0; } +DBnpcspellseffects_Struct* ZoneDatabase::GetNPCSpellsEffects(uint32 iDBSpellsEffectsID) { + if (iDBSpellsEffectsID == 0) + return 0; + + if (!npc_spellseffects_cache) { + npc_spellseffects_maxid = GetMaxNPCSpellsEffectsID(); + npc_spellseffects_cache = new DBnpcspellseffects_Struct*[npc_spellseffects_maxid+1]; + npc_spellseffects_loadtried = new bool[npc_spellseffects_maxid+1]; + for (uint32 i=0; i<=npc_spellseffects_maxid; i++) { + npc_spellseffects_cache[i] = 0; + npc_spellseffects_loadtried[i] = false; + } + } + + if (iDBSpellsEffectsID > npc_spellseffects_maxid) + return 0; + if (npc_spellseffects_cache[iDBSpellsEffectsID]) { // it's in the cache, easy =) + return npc_spellseffects_cache[iDBSpellsEffectsID]; + } + + else if (!npc_spellseffects_loadtried[iDBSpellsEffectsID]) { // no reason to ask the DB again if we have failed once already + npc_spellseffects_loadtried[iDBSpellsEffectsID] = true; + char errbuf[MYSQL_ERRMSG_SIZE]; + char *query = 0; + MYSQL_RES *result; + MYSQL_ROW row; + + if (RunQuery(query, MakeAnyLenString(&query, "SELECT id, parent_list from npc_spells_effects where id=%d", iDBSpellsEffectsID), errbuf, &result)) { + safe_delete_array(query); + if (mysql_num_rows(result) == 1) { + row = mysql_fetch_row(result); + uint32 tmpparent_list = atoi(row[1]); + 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)); + npc_spellseffects_cache[iDBSpellsEffectsID] = (DBnpcspellseffects_Struct*) new uchar[tmpSize]; + memset(npc_spellseffects_cache[iDBSpellsEffectsID], 0, tmpSize); + npc_spellseffects_cache[iDBSpellsEffectsID]->parent_list = tmpparent_list; + npc_spellseffects_cache[iDBSpellsEffectsID]->numentries = mysql_num_rows(result); + int j = 0; + while ((row = mysql_fetch_row(result))) { + int spell_effect_id = atoi(row[0]); + npc_spellseffects_cache[iDBSpellsEffectsID]->entries[j].spelleffectid = spell_effect_id; + npc_spellseffects_cache[iDBSpellsEffectsID]->entries[j].minlevel = atoi(row[1]); + npc_spellseffects_cache[iDBSpellsEffectsID]->entries[j].maxlevel = atoi(row[2]); + npc_spellseffects_cache[iDBSpellsEffectsID]->entries[j].base = atoi(row[3]); + npc_spellseffects_cache[iDBSpellsEffectsID]->entries[j].limit = atoi(row[4]); + npc_spellseffects_cache[iDBSpellsEffectsID]->entries[j].max = atoi(row[5]); + j++; + } + mysql_free_result(result); + return npc_spellseffects_cache[iDBSpellsEffectsID]; + } + else { + std::cerr << "Error in AddNPCSpells query1 '" << query << "' " << errbuf << std::endl; + safe_delete_array(query); + return 0; + } + } + else { + mysql_free_result(result); + } + } + else { + std::cerr << "Error in AddNPCSpells query1 '" << query << "' " << errbuf << std::endl; + safe_delete_array(query); + return 0; + } + return 0; + } + return 0; +} + +uint32 ZoneDatabase::GetMaxNPCSpellsEffectsID() { + char errbuf[MYSQL_ERRMSG_SIZE]; + char *query = 0; + MYSQL_RES *result; + MYSQL_ROW row; + + if (RunQuery(query, MakeAnyLenString(&query, "SELECT max(id) from npc_spells_effects"), errbuf, &result)) { + safe_delete_array(query); + if (mysql_num_rows(result) == 1) { + row = mysql_fetch_row(result); + uint32 ret = 0; + if (row[0]) + ret = atoi(row[0]); + mysql_free_result(result); + return ret; + } + mysql_free_result(result); + } + else { + std::cerr << "Error in GetMaxNPCSpellsEffectsID query '" << query << "' " << errbuf << std::endl; + safe_delete_array(query); + return 0; + } + + return 0; +} + diff --git a/zone/attack.cpp b/zone/attack.cpp index e1f6b5135..fbcec2f59 100644 --- a/zone/attack.cpp +++ b/zone/attack.cpp @@ -2120,7 +2120,8 @@ bool NPC::Death(Mob* killerMob, int32 damage, uint16 spell, SkillUseTypes attack if(p_depop == true) return false; - + + HasAISpellEffects = false; BuffFadeAll(); uint8 killed_level = GetLevel(); diff --git a/zone/bonuses.cpp b/zone/bonuses.cpp index c726333f2..341a62b74 100644 --- a/zone/bonuses.cpp +++ b/zone/bonuses.cpp @@ -1258,6 +1258,10 @@ void Mob::CalcSpellBonuses(StatBonuses* newbon) } } + //Applies any perma NPC spell bonuses from npc_spells_effects table. + if (IsNPC()) + CastToNPC()->ApplyAISpellEffects(newbon); + //Removes the spell bonuses that are effected by a 'negate' debuff. if (spellbonuses.NegateEffects){ for(i = 0; i < buff_count; i++) { @@ -1270,12 +1274,13 @@ void Mob::CalcSpellBonuses(StatBonuses* newbon) if (GetClass() == BARD) newbon->ManaRegen = 0; // Bards do not get mana regen from spells. } -void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* newbon, uint16 casterId, bool item_bonus, uint32 ticsremaining, int buffslot) +void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* newbon, uint16 casterId, bool item_bonus, uint32 ticsremaining, int buffslot, + bool IsAISpellEffect, uint16 effect_id, int32 se_base, int32 se_limit, int32 se_max) { - int i, effect_value; + int i, effect_value, base2, max, effectid; Mob *caster = nullptr; - if(!IsValidSpell(spell_id)) + if(!IsAISpellEffect && !IsValidSpell(spell_id)) return; if(casterId > 0) @@ -1283,19 +1288,35 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* ne for (i = 0; i < EFFECT_COUNT; i++) { - if(IsBlankSpellEffect(spell_id, i)) - continue; + //Buffs/Item effects + if (!IsAISpellEffect) { - uint8 focus = IsFocusEffect(spell_id, i); - if (focus) - { - newbon->FocusEffects[focus] = spells[spell_id].effectid[i]; - continue; + if(IsBlankSpellEffect(spell_id, i)) + continue; + + uint8 focus = IsFocusEffect(spell_id, i); + if (focus) + { + newbon->FocusEffects[focus] = spells[spell_id].effectid[i]; + continue; + } + + + effectid = spells[spell_id].effectid[i]; + effect_value = CalcSpellEffectValue(spell_id, i, casterlevel, caster, ticsremaining); + base2 = spells[spell_id].base2[i]; + max = spells[spell_id].max[i]; + } + //Use AISpellEffects + else { + effectid = effect_id; + effect_value = se_base; + base2 = se_limit; + max = se_max; + i = EFFECT_COUNT; //End the loop } - effect_value = CalcSpellEffectValue(spell_id, i, casterlevel, caster, ticsremaining); - - switch (spells[spell_id].effectid[i]) + switch (effectid) { case SE_CurrentHP: //regens if(effect_value > 0) { @@ -1631,27 +1652,27 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* ne case SE_CriticalHitChance: { - if (RuleB(Spells, AdditiveBonusValues) && item_bonus) { - if(spells[spell_id].base2[i] == -1) + if(base2 == -1) newbon->CriticalHitChance[HIGHEST_SKILL+1] += effect_value; else - newbon->CriticalHitChance[spells[spell_id].base2[i]] += effect_value; + newbon->CriticalHitChance[base2] += effect_value; } else if(effect_value < 0) { - if(spells[spell_id].base2[i] == -1 && newbon->CriticalHitChance[HIGHEST_SKILL+1] > effect_value) + if(base2 == -1 && newbon->CriticalHitChance[HIGHEST_SKILL+1] > effect_value) newbon->CriticalHitChance[HIGHEST_SKILL+1] = effect_value; - else if(spells[spell_id].base2[i] != -1 && newbon->CriticalHitChance[spells[spell_id].base2[i]] > effect_value) - newbon->CriticalHitChance[spells[spell_id].base2[i]] = effect_value; + else if(base2 != -1 && newbon->CriticalHitChance[base2] > effect_value) + newbon->CriticalHitChance[base2] = effect_value; } - else if(spells[spell_id].base2[i] == -1 && newbon->CriticalHitChance[HIGHEST_SKILL+1] < effect_value) + else if(base2 == -1 && newbon->CriticalHitChance[HIGHEST_SKILL+1] < effect_value) newbon->CriticalHitChance[HIGHEST_SKILL+1] = effect_value; - else if(spells[spell_id].base2[i] != -1 && newbon->CriticalHitChance[spells[spell_id].base2[i]] < effect_value) - newbon->CriticalHitChance[spells[spell_id].base2[i]] = effect_value; + else if(base2 != -1 && newbon->CriticalHitChance[base2] < effect_value) + newbon->CriticalHitChance[base2] = effect_value; + break; } @@ -1812,7 +1833,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* ne case SE_HundredHands: { if (RuleB(Spells, AdditiveBonusValues) && item_bonus) - newbon->HundredHands += spells[spell_id].base[i]; + newbon->HundredHands += effect_value; if (effect_value > 0 && effect_value > newbon->HundredHands) newbon->HundredHands = effect_value; //Increase Weapon Delay @@ -1825,7 +1846,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* ne { if(newbon->MeleeSkillCheck < effect_value) { newbon->MeleeSkillCheck = effect_value; - newbon->MeleeSkillCheckSkill = spells[spell_id].base2[i]==-1?255:spells[spell_id].base2[i]; + newbon->MeleeSkillCheckSkill = base2==-1?255:base2; } break; } @@ -1834,13 +1855,13 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* ne { if (RuleB(Spells, AdditiveBonusValues) && item_bonus){ - if(spells[spell_id].base2[i] == -1) + if(base2 == -1) newbon->HitChanceEffect[HIGHEST_SKILL+1] += effect_value; else - newbon->HitChanceEffect[spells[spell_id].base2[i]] += effect_value; + newbon->HitChanceEffect[base2] += effect_value; } - else if(spells[spell_id].base2[i] == -1){ + else if(base2 == -1){ if ((effect_value < 0) && (newbon->HitChanceEffect[HIGHEST_SKILL+1] > effect_value)) newbon->HitChanceEffect[HIGHEST_SKILL+1] = effect_value; @@ -1852,12 +1873,12 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* ne else { - if ((effect_value < 0) && (newbon->HitChanceEffect[spells[spell_id].base2[i]] > effect_value)) - newbon->HitChanceEffect[spells[spell_id].base2[i]] = effect_value; + if ((effect_value < 0) && (newbon->HitChanceEffect[base2] > effect_value)) + newbon->HitChanceEffect[base2] = effect_value; - else if (!newbon->HitChanceEffect[spells[spell_id].base2[i]] || - ((newbon->HitChanceEffect[spells[spell_id].base2[i]] > 0) && (newbon->HitChanceEffect[spells[spell_id].base2[i]] < effect_value))) - newbon->HitChanceEffect[spells[spell_id].base2[i]] = effect_value; + else if (!newbon->HitChanceEffect[base2] || + ((newbon->HitChanceEffect[base2] > 0) && (newbon->HitChanceEffect[base2] < effect_value))) + newbon->HitChanceEffect[base2] = effect_value; } break; @@ -1866,19 +1887,19 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* ne case SE_DamageModifier: { - if(spells[spell_id].base2[i] == -1) + if(base2 == -1) newbon->DamageModifier[HIGHEST_SKILL+1] += effect_value; else - newbon->DamageModifier[spells[spell_id].base2[i]] += effect_value; + newbon->DamageModifier[base2] += effect_value; break; } case SE_MinDamageModifier: { - if(spells[spell_id].base2[i] == -1) + if(base2 == -1) newbon->MinDamageModifier[HIGHEST_SKILL+1] += effect_value; else - newbon->MinDamageModifier[spells[spell_id].base2[i]] += effect_value; + newbon->MinDamageModifier[base2] += effect_value; break; } @@ -1921,8 +1942,8 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* ne newbon->DeathSave[0] = effect_value; //1='Partial' 2='Full' newbon->DeathSave[1] = buffslot; //These are used in later expansion spell effects. - newbon->DeathSave[2] = spells[spell_id].base2[i];//Min level for HealAmt - newbon->DeathSave[3] = spells[spell_id].max[i];//HealAmt + newbon->DeathSave[2] = base2;//Min level for HealAmt + newbon->DeathSave[3] = max;//HealAmt } break; } @@ -1937,7 +1958,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* ne else if(newbon->DivineSaveChance[0] < effect_value) { newbon->DivineSaveChance[0] = effect_value; - newbon->DivineSaveChance[1] = spells[spell_id].base2[i]; + newbon->DivineSaveChance[1] = base2; //SetDeathSaveChance(true); } break; @@ -1972,10 +1993,10 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* ne case SE_SkillDamageTaken: { - if(spells[spell_id].base2[i] == -1) + if(base2 == -1) newbon->SkillDmgTaken[HIGHEST_SKILL+1] += effect_value; else - newbon->SkillDmgTaken[spells[spell_id].base2[i]] += effect_value; + newbon->SkillDmgTaken[base2] += effect_value; break; } @@ -2000,8 +2021,8 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* ne { newbon->CriticalSpellChance += effect_value; - if (spells[spell_id].base2[i] > newbon->SpellCritDmgIncNoStack) - newbon->SpellCritDmgIncNoStack = spells[spell_id].base2[i]; + if (base2 > newbon->SpellCritDmgIncNoStack) + newbon->SpellCritDmgIncNoStack = base2; break; } @@ -2053,9 +2074,9 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* ne if(!newbon->SpellOnKill[e]) { // Base2 = Spell to fire | Base1 = % chance | Base3 = min level - newbon->SpellOnKill[e] = spells[spell_id].base2[i]; + newbon->SpellOnKill[e] = base2; newbon->SpellOnKill[e+1] = effect_value; - newbon->SpellOnKill[e+2] = spells[spell_id].max[i]; + newbon->SpellOnKill[e+2] = max; break; } } @@ -2069,7 +2090,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* ne if(!newbon->SpellOnDeath[e]) { // Base2 = Spell to fire | Base1 = % chance - newbon->SpellOnDeath[e] = spells[spell_id].base2[i]; + newbon->SpellOnDeath[e] = base2; newbon->SpellOnDeath[e+1] = effect_value; break; } @@ -2079,26 +2100,26 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* ne case SE_CriticalDamageMob: { - if(spells[spell_id].base2[i] == -1) + if(base2 == -1) newbon->CritDmgMob[HIGHEST_SKILL+1] += effect_value; else - newbon->CritDmgMob[spells[spell_id].base2[i]] += effect_value; + newbon->CritDmgMob[base2] += effect_value; break; } case SE_ReduceSkillTimer: { - if(newbon->SkillReuseTime[spells[spell_id].base2[i]] < effect_value) - newbon->SkillReuseTime[spells[spell_id].base2[i]] = effect_value; + if(newbon->SkillReuseTime[base2] < effect_value) + newbon->SkillReuseTime[base2] = effect_value; break; } case SE_SkillDamageAmount: { - if(spells[spell_id].base2[i] == -1) + if(base2 == -1) newbon->SkillDamageAmount[HIGHEST_SKILL+1] += effect_value; else - newbon->SkillDamageAmount[spells[spell_id].base2[i]] += effect_value; + newbon->SkillDamageAmount[base2] += effect_value; break; } @@ -2188,10 +2209,10 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* ne case SE_SkillDamageAmount2: { - if(spells[spell_id].base2[i] == -1) + if(base2 == -1) newbon->SkillDamageAmount2[HIGHEST_SKILL+1] += effect_value; else - newbon->SkillDamageAmount2[spells[spell_id].base2[i]] += effect_value; + newbon->SkillDamageAmount2[base2] += effect_value; break; } @@ -2219,7 +2240,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* ne if (newbon->MeleeThresholdGuard[0] < effect_value){ newbon->MeleeThresholdGuard[0] = effect_value; newbon->MeleeThresholdGuard[1] = buffslot; - newbon->MeleeThresholdGuard[2] = spells[spell_id].base2[i]; + newbon->MeleeThresholdGuard[2] = base2; } break; } @@ -2229,7 +2250,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* ne if (newbon->SpellThresholdGuard[0] < effect_value){ newbon->SpellThresholdGuard[0] = effect_value; newbon->SpellThresholdGuard[1] = buffslot; - newbon->SpellThresholdGuard[2] = spells[spell_id].base2[i]; + newbon->SpellThresholdGuard[2] = base2; } break; } @@ -2263,20 +2284,20 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* ne case SE_TriggerMeleeThreshold: { - if (newbon->TriggerMeleeThreshold[2] < spells[spell_id].base2[i]){ + if (newbon->TriggerMeleeThreshold[2] < base2){ newbon->TriggerMeleeThreshold[0] = effect_value; newbon->TriggerMeleeThreshold[1] = buffslot; - newbon->TriggerMeleeThreshold[2] = spells[spell_id].base2[i]; + newbon->TriggerMeleeThreshold[2] = base2; } break; } case SE_TriggerSpellThreshold: { - if (newbon->TriggerSpellThreshold[2] < spells[spell_id].base2[i]){ + if (newbon->TriggerSpellThreshold[2] < base2){ newbon->TriggerSpellThreshold[0] = effect_value; newbon->TriggerSpellThreshold[1] = buffslot; - newbon->TriggerSpellThreshold[2] = spells[spell_id].base2[i]; + newbon->TriggerSpellThreshold[2] = base2; } break; } @@ -2291,7 +2312,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* ne case SE_ShieldEquipDmgMod: newbon->ShieldEquipDmgMod[0] += effect_value; - newbon->ShieldEquipDmgMod[1] += spells[spell_id].base2[i]; + newbon->ShieldEquipDmgMod[1] += base2; break; case SE_BlockBehind: @@ -2380,7 +2401,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* ne break; case SE_AddSingingMod: - switch (spells[spell_id].base2[i]) + switch (base2) { case ItemTypeWindInstrument: newbon->windMod += effect_value; @@ -2474,8 +2495,8 @@ 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] = spells[spell_id].base2[i]) && (newbon->SEResist[e+1] < effect_value)) ){ - newbon->SEResist[e] = spells[spell_id].base2[i]; + ((newbon->SEResist[e] = base2) && (newbon->SEResist[e+1] < effect_value)) ){ + newbon->SEResist[e] = base2; newbon->SEResist[e+1] = effect_value; break; } @@ -2493,7 +2514,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* ne case SE_GiveDoubleRiposte: { //Only allow for regular double riposte chance. - if(newbon->GiveDoubleRiposte[spells[spell_id].base2[i]] == 0){ + if(newbon->GiveDoubleRiposte[base2] == 0){ if(newbon->GiveDoubleRiposte[0] < effect_value) newbon->GiveDoubleRiposte[0] = effect_value; } @@ -2504,7 +2525,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* ne { if(newbon->SlayUndead[1] < effect_value) newbon->SlayUndead[0] = effect_value; // Rate - newbon->SlayUndead[1] = spells[spell_id].base2[i]; // Damage Modifier + newbon->SlayUndead[1] = base2; // Damage Modifier break; } @@ -2520,7 +2541,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* ne case SE_ImprovedTaunt: if (newbon->ImprovedTaunt[0] < effect_value) { newbon->ImprovedTaunt[0] = effect_value; - newbon->ImprovedTaunt[1] = spells[spell_id].base2[i]; + newbon->ImprovedTaunt[1] = base2; newbon->ImprovedTaunt[2] = buffslot; } break; @@ -2531,7 +2552,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* ne break; case SE_FrenziedDevastation: - newbon->FrenziedDevastation += spells[spell_id].base2[i]; + newbon->FrenziedDevastation += base2; break; case SE_Root: diff --git a/zone/mob.h b/zone/mob.h index 0784b7925..90013645b 100644 --- a/zone/mob.h +++ b/zone/mob.h @@ -182,7 +182,8 @@ public: bool IsBeneficialAllowed(Mob *target); virtual int GetCasterLevel(uint16 spell_id); void ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* newbon, uint16 casterID = 0, - bool item_bonus = false, uint32 ticsremaining = 0, int buffslot = -1); + bool item_bonus = false, uint32 ticsremaining = 0, int buffslot = -1, + bool IsAISpellEffect = false, uint16 effect_id = 0, int32 se_base = 0, int32 se_limit = 0, int32 se_max = 0); void NegateSpellsBonuses(uint16 spell_id); virtual float GetActSpellRange(uint16 spell_id, float range, bool IsBard = false) { return range;} virtual int32 GetActSpellDamage(uint16 spell_id, int32 value, Mob* target = nullptr) { return value; } diff --git a/zone/npc.cpp b/zone/npc.cpp index 2611300d8..3886d7c66 100644 --- a/zone/npc.cpp +++ b/zone/npc.cpp @@ -231,6 +231,7 @@ NPC::NPC(const NPCType* d, Spawn2* in_respawn, float x, float y, float z, float npc_spells_id = 0; HasAISpell = false; + HasAISpellEffects = false; if(GetClass() == MERCERNARY_MASTER && RuleB(Mercs, AllowMercs)) { diff --git a/zone/npc.h b/zone/npc.h index 55bed4abc..ae444c099 100644 --- a/zone/npc.h +++ b/zone/npc.h @@ -66,6 +66,13 @@ struct AISpells_Struct { int16 resist_adjust; }; +struct AISpellsEffects_Struct { + uint16 spelleffectid; + int32 base; + int32 limit; + int32 max; +}; + class AA_SwarmPetInfo; class NPC : public Mob @@ -96,8 +103,11 @@ public: virtual void AI_Stop(); void AI_DoMovement(); bool AI_AddNPCSpells(uint32 iDBSpellsID); + bool AI_AddNPCSpellsEffects(uint32 iDBSpellsEffectsID); virtual bool AI_EngagedCastCheck(); bool AI_HasSpells() { return HasAISpell; } + bool AI_HasSpellsEffects() { return HasAISpellEffects; } + void ApplyAISpellEffects(StatBonuses* newbon); virtual bool AI_PursueCastCheck(); virtual bool AI_IdleCastCheck(); @@ -289,6 +299,7 @@ public: inline void GiveNPCTypeData(NPCType *ours) { NPCTypedata_ours = ours; } inline const uint32 GetNPCSpellsID() const { return npc_spells_id; } + inline const uint32 GetNPCSpellsEffectsID() const { return npc_spells_effects_id; } ItemList itemlist; //kathgar - why is this public? Doing other things or I would check the code @@ -339,6 +350,7 @@ public: uint32 GetAdventureTemplate() const { return adventure_template_id; } void AddSpellToNPCList(int16 iPriority, int16 iSpellID, uint16 iType, int16 iManaCost, int32 iRecastDelay, int16 iResistAdjust); + void AddSpellEffectToNPCList(uint16 iSpellEffectID, int32 base, int32 limit, int32 max); void RemoveSpellFromNPCList(int16 spell_id); Timer *GetRefaceTimer() const { return reface_timer; } const uint32 GetAltCurrencyType() const { return NPCTypedata->alt_currency_type; } @@ -400,8 +412,11 @@ 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); - - + + uint32 npc_spells_effects_id; + std::vector AIspellsEffects; + bool HasAISpellEffects; + uint32 max_dmg; uint32 min_dmg; int32 accuracy_rating; diff --git a/zone/zonedb.cpp b/zone/zonedb.cpp index 893a1a27a..2178a15c9 100644 --- a/zone/zonedb.cpp +++ b/zone/zonedb.cpp @@ -33,8 +33,11 @@ ZoneDatabase::ZoneDatabase(const char* host, const char* user, const char* passw void ZoneDatabase::ZDBInitVars() { memset(door_isopen_array, 0, sizeof(door_isopen_array)); npc_spells_maxid = 0; + npc_spellseffects_maxid = 0; npc_spells_cache = 0; + npc_spellseffects_cache = 0; npc_spells_loadtried = 0; + npc_spellseffects_loadtried = 0; max_faction = 0; faction_array = nullptr; } @@ -49,6 +52,14 @@ ZoneDatabase::~ZoneDatabase() { } safe_delete_array(npc_spells_loadtried); + if (npc_spellseffects_cache) { + for (x=0; x<=npc_spellseffects_maxid; x++) { + safe_delete_array(npc_spellseffects_cache[x]); + } + safe_delete_array(npc_spellseffects_cache); + } + safe_delete_array(npc_spellseffects_loadtried); + if (faction_array != nullptr) { for (x=0; x <= max_faction; x++) { if (faction_array[x] != 0) @@ -1053,6 +1064,7 @@ const NPCType* ZoneDatabase::GetNPCType (uint32 id) { "npc_types.attack_count," "npc_types.special_abilities," "npc_types.npc_spells_id," + "npc_types.npc_spells_effects_id," "npc_types.d_meele_texture1," "npc_types.d_meele_texture2," "npc_types.prim_melee_type," @@ -1151,6 +1163,7 @@ const NPCType* ZoneDatabase::GetNPCType (uint32 id) { tmpNPCType->attack_count = atoi(row[r++]); strn0cpy(tmpNPCType->special_abilities, row[r++], 512); tmpNPCType->npc_spells_id = atoi(row[r++]); + tmpNPCType->npc_spells_effects_id = atoi(row[r++]); tmpNPCType->d_meele_texture1 = atoi(row[r++]); tmpNPCType->d_meele_texture2 = atoi(row[r++]); tmpNPCType->prim_melee_type = atoi(row[r++]); diff --git a/zone/zonedb.h b/zone/zonedb.h index 797b59980..e53b45530 100644 --- a/zone/zonedb.h +++ b/zone/zonedb.h @@ -30,6 +30,17 @@ struct DBnpcspells_entries_Struct { }; #pragma pack() +#pragma pack(1) +struct DBnpcspellseffects_entries_Struct { + int16 spelleffectid; + uint8 minlevel; + uint8 maxlevel; + int32 base; + int32 limit; + int32 max; +}; +#pragma pack() + struct DBnpcspells_Struct { uint32 parent_list; int16 attack_proc; @@ -38,6 +49,12 @@ struct DBnpcspells_Struct { DBnpcspells_entries_Struct entries[0]; }; +struct DBnpcspellseffects_Struct { + uint32 parent_list; + uint32 numentries; + DBnpcspellseffects_entries_Struct entries[0]; +}; + struct DBTradeskillRecipe_Struct { SkillUseTypes tradeskill; int16 skill_needed; @@ -345,7 +362,9 @@ public: void AddLootTableToNPC(NPC* npc,uint32 loottable_id, ItemList* itemlist, uint32* copper, uint32* silver, uint32* gold, uint32* plat); void AddLootDropToNPC(NPC* npc,uint32 lootdrop_id, ItemList* itemlist, uint8 droplimit, uint8 mindrop); uint32 GetMaxNPCSpellsID(); + uint32 GetMaxNPCSpellsEffectsID(); DBnpcspells_Struct* GetNPCSpells(uint32 iDBSpellsID); + DBnpcspellseffects_Struct* GetNPCSpellsEffects(uint32 iDBSpellsEffectsID); /* * Mercs @@ -475,8 +494,11 @@ protected: uint32 max_faction; Faction** faction_array; uint32 npc_spells_maxid; + uint32 npc_spellseffects_maxid; DBnpcspells_Struct** npc_spells_cache; bool* npc_spells_loadtried; + DBnpcspellseffects_Struct** npc_spellseffects_cache; + bool* npc_spellseffects_loadtried; uint8 door_isopen_array[255]; }; diff --git a/zone/zonedump.h b/zone/zonedump.h index 39c6b39d7..e18734d4f 100644 --- a/zone/zonedump.h +++ b/zone/zonedump.h @@ -53,6 +53,7 @@ struct NPCType uint8 helmtexture; uint32 loottable_id; uint32 npc_spells_id; + uint32 npc_spells_effects_id; int32 npc_faction_id; uint32 merchanttype; uint32 alt_currency_type;