Rework some Spell AI so NPCs can have spammy spells

Lots of encounters in EQ will spam spells, like dragon fear is on a very
tight timer etc. In order to eliminate the need to script all of these
encounters AI spells with a priority of '0' will be treated as "innate
spells." Devs have used this term and it is what I believe they mean by
it.

You can run update npc_spells_entries set priority = priority + 1 where priority >= 0;
to disable the behavior.
This commit is contained in:
Michael Cook (mackal)
2018-01-28 18:06:54 -05:00
parent ceb2b287bb
commit f8ce10472b
3 changed files with 52 additions and 30 deletions
+42 -29
View File
@@ -47,7 +47,7 @@ extern Zone *zone;
#endif
//NOTE: do NOT pass in beneficial and detrimental spell types into the same call here!
bool NPC::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) {
bool NPC::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes, bool bInnates) {
if (!tar)
return false;
@@ -61,7 +61,8 @@ bool NPC::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) {
// Any sane mob would cast if they can.
bool cast_only_option = (IsRooted() && !CombatRange(tar));
if (!cast_only_option && iChance < 100) {
// innates are always attempted
if (!cast_only_option && iChance < 100 && !bInnates) {
if (zone->random.Int(0, 100) >= iChance)
return false;
}
@@ -84,6 +85,12 @@ bool NPC::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) {
//return false;
continue;
}
if ((AIspells[i].priority == 0 && !bInnates) || (AIspells[i].priority != 0 && bInnates)) {
// so "innate" spells are special and spammed a bit
// we define an innate spell as a spell with priority 0
continue;
}
if (iSpellTypes & AIspells[i].type) {
// manacost has special values, -1 is no mana cost, -2 is instant cast (no mana)
int32 mana_cost = AIspells[i].manacost;
@@ -99,7 +106,7 @@ bool NPC::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) {
dist2 <= spells[AIspells[i].spellid].range*spells[AIspells[i].spellid].range
)
&& (mana_cost <= GetMana() || GetMana() == GetMaxMana())
&& (AIspells[i].time_cancast + (zone->random.Int(0, 4) * 1000)) <= Timer::GetCurrentTime() //break up the spelling casting over a period of time.
&& (AIspells[i].time_cancast + (zone->random.Int(0, 4) * 500)) <= Timer::GetCurrentTime() //break up the spelling casting over a period of time.
) {
#if MobAI_DEBUG_Spells >= 21
@@ -127,7 +134,7 @@ bool NPC::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) {
}
case SpellType_Root: {
Mob *rootee = GetHateRandom();
if (rootee && !rootee->IsRooted() && !rootee->IsFeared() && zone->random.Roll(50)
if (rootee && !rootee->IsRooted() && !rootee->IsFeared() && (bInnates || zone->random.Roll(50))
&& rootee->DontRootMeBefore() < Timer::GetCurrentTime()
&& rootee->CanBuffStack(AIspells[i].spellid, GetLevel(), true) >= 0
) {
@@ -166,7 +173,7 @@ bool NPC::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) {
}
case SpellType_InCombatBuff: {
if(zone->random.Roll(50))
if(bInnates || zone->random.Roll(50))
{
AIDoSpellCast(i, tar, mana_cost);
return true;
@@ -185,7 +192,7 @@ bool NPC::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) {
case SpellType_Slow:
case SpellType_Debuff: {
Mob * debuffee = GetHateRandom();
if (debuffee && manaR >= 10 && zone->random.Roll(70) &&
if (debuffee && manaR >= 10 && (bInnates || zone->random.Roll(70)) &&
debuffee->CanBuffStack(AIspells[i].spellid, GetLevel(), true) >= 0) {
if (!checked_los) {
if (!CheckLosFN(debuffee))
@@ -199,8 +206,8 @@ bool NPC::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) {
}
case SpellType_Nuke: {
if (
manaR >= 10 && zone->random.Roll(70)
&& tar->CanBuffStack(AIspells[i].spellid, GetLevel(), true) >= 0
manaR >= 10 && (bInnates || zone->random.Roll(70))
&& tar->CanBuffStack(AIspells[i].spellid, GetLevel(), false) >= 0 // saying it's a nuke here, AI shouldn't care too much if overwriting
) {
if(!checked_los) {
if(!CheckLosFN(tar))
@@ -213,7 +220,7 @@ bool NPC::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) {
break;
}
case SpellType_Dispel: {
if(zone->random.Roll(15))
if(bInnates || zone->random.Roll(15))
{
if(!checked_los) {
if(!CheckLosFN(tar))
@@ -229,7 +236,7 @@ bool NPC::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) {
break;
}
case SpellType_Mez: {
if(zone->random.Roll(20))
if(bInnates || zone->random.Roll(20))
{
Mob * mezTar = nullptr;
mezTar = entity_list.GetTargetForMez(this);
@@ -245,7 +252,7 @@ bool NPC::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) {
case SpellType_Charm:
{
if(!IsPet() && zone->random.Roll(20))
if(!IsPet() && (bInnates || zone->random.Roll(20)))
{
Mob * chrmTar = GetHateRandom();
if(chrmTar && chrmTar->CanBuffStack(AIspells[i].spellid, GetLevel(), true) >= 0)
@@ -259,7 +266,7 @@ bool NPC::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) {
case SpellType_Pet: {
//keep mobs from recasting pets when they have them.
if (!IsPet() && !GetPetID() && zone->random.Roll(25)) {
if (!IsPet() && !GetPetID() && (bInnates || zone->random.Roll(25))) {
AIDoSpellCast(i, tar, mana_cost);
return true;
}
@@ -267,7 +274,7 @@ bool NPC::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) {
}
case SpellType_Lifetap: {
if (GetHPRatio() <= 95
&& zone->random.Roll(50)
&& (bInnates || zone->random.Roll(50))
&& tar->CanBuffStack(AIspells[i].spellid, GetLevel(), true) >= 0
) {
if(!checked_los) {
@@ -283,7 +290,7 @@ bool NPC::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) {
case SpellType_Snare: {
if (
!tar->IsRooted()
&& zone->random.Roll(50)
&& (bInnates || zone->random.Roll(50))
&& tar->DontSnareMeBefore() < Timer::GetCurrentTime()
&& tar->CanBuffStack(AIspells[i].spellid, GetLevel(), true) >= 0
) {
@@ -301,7 +308,7 @@ bool NPC::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) {
}
case SpellType_DOT: {
if (
zone->random.Roll(60)
(bInnates || zone->random.Roll(60))
&& tar->DontDotMeBefore() < Timer::GetCurrentTime()
&& tar->CanBuffStack(AIspells[i].spellid, GetLevel(), true) >= 0
) {
@@ -502,7 +509,7 @@ void NPC::AI_Start(uint32 iMoveDelay) {
AIautocastspell_timer = std::unique_ptr<Timer>(new Timer(1000));
AIautocastspell_timer->Disable();
} else {
AIautocastspell_timer = std::unique_ptr<Timer>(new Timer(750));
AIautocastspell_timer = std::unique_ptr<Timer>(new Timer(500));
AIautocastspell_timer->Start(RandomTimer(0, 300), false);
}
@@ -1855,7 +1862,7 @@ void NPC::AI_Event_SpellCastFinished(bool iCastSucceeded, uint16 slot) {
recovery_time += spells[AIspells[casting_spell_AIindex].spellid].recovery_time;
if (AIspells[casting_spell_AIindex].recast_delay >= 0)
{
if (AIspells[casting_spell_AIindex].recast_delay < 10000)
if (AIspells[casting_spell_AIindex].recast_delay < 1000)
AIspells[casting_spell_AIindex].time_cancast = Timer::GetCurrentTime() + (AIspells[casting_spell_AIindex].recast_delay*1000);
}
else
@@ -1878,14 +1885,17 @@ bool NPC::AI_EngagedCastCheck() {
Log(Logs::Detail, Logs::AI, "Engaged autocast check triggered. Trying to cast healing spells then maybe offensive spells.");
// try casting a heal or gate
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, AISpellVar.engaged_beneficial_other_chance, MobAISpellRange, SpellType_Heal)) {
//nobody to heal, try some detrimental spells.
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(AISpellVar.engaged_no_sp_recast_min, AISpellVar.engaged_no_sp_recast_max), false);
// first try innate (spam) spells
if(!AICastSpell(GetTarget(), 0, SpellType_Nuke | SpellType_Lifetap | SpellType_DOT | SpellType_Dispel | SpellType_Mez | SpellType_Slow | SpellType_Debuff | SpellType_Charm | SpellType_Root, true)) {
// try casting a heal or gate
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, AISpellVar.engaged_beneficial_other_chance, MobAISpellRange, SpellType_Heal)) {
//nobody to heal, try some detrimental spells.
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(AISpellVar.engaged_no_sp_recast_min, AISpellVar.engaged_no_sp_recast_max), false);
}
}
}
}
@@ -1900,10 +1910,13 @@ bool NPC::AI_PursueCastCheck() {
AIautocastspell_timer->Disable(); //prevent the timer from going off AGAIN while we are casting.
Log(Logs::Detail, Logs::AI, "Engaged (pursuing) autocast check triggered. Trying to cast offensive spells.");
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(AISpellVar.pursue_no_sp_recast_min, AISpellVar.pursue_no_sp_recast_max), false);
} //else, spell casting finishing will reset the timer.
// checking innate (spam) spells first
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, true)) {
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(AISpellVar.pursue_no_sp_recast_min, AISpellVar.pursue_no_sp_recast_max), false);
} //else, spell casting finishing will reset the timer.
}
return(true);
}
return(false);