mirror of
https://github.com/EQEmu/Server.git
synced 2025-12-12 05:21:29 +00:00
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:
parent
ceb2b287bb
commit
f8ce10472b
@ -1,5 +1,14 @@
|
||||
EQEMu Changelog (Started on Sept 24, 2003 15:50)
|
||||
-------------------------------------------------------
|
||||
== 01/28/2018 ==
|
||||
Mackal: Spell AI tweaks
|
||||
|
||||
AI spells are treated as "innate" spells (devs use this term, and I think this is what they mean by it)
|
||||
These spells are spammed by the NPC, lots of encounters on live work like this and this will greatly reduce
|
||||
the need to do quest scripting on these types of encounters.
|
||||
|
||||
You can safely run update npc_spells_entries set priority = priority + 1 where priority >= 0; if you want to disable this new behavior
|
||||
|
||||
== 10/08/2017 ==
|
||||
Mackal: Rework regens
|
||||
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -457,7 +457,7 @@ protected:
|
||||
uint32* pDontCastBefore_casting_spell;
|
||||
std::vector<AISpells_Struct> AIspells;
|
||||
bool HasAISpell;
|
||||
virtual bool AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes);
|
||||
virtual bool AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes, bool bInnates = false);
|
||||
virtual bool AIDoSpellCast(uint8 i, Mob* tar, int32 mana_cost, uint32* oDontDoAgainBefore = 0);
|
||||
AISpellsVar_Struct AISpellVar;
|
||||
int16 GetFocusEffect(focusType type, uint16 spell_id);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user