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.
This commit is contained in:
KayenEQ 2014-04-27 03:57:14 -04:00
parent 62e48e7701
commit 380cf8691a
7 changed files with 208 additions and 35 deletions

View File

@ -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.

View File

@ -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');

View File

@ -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;

View File

@ -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;
}
}
}
}

View File

@ -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;
}

View File

@ -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<int>(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<int>(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<int>(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<int>(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<int>(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<int>(spellbonuses.SpellOnDeath[i + 1])) {
SpellFinished(spellbonuses.SpellOnDeath[i], this);
SpellFinished(spellbonuses.SpellOnDeath[i], this, 10, 0, -1, spells[spellbonuses.SpellOnDeath[i]].ResistDiff);
}
}
}

View File

@ -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;