[Bots] Cleanup Bot Spell Functions, reduce reliance on NPC Functions/Attributes (#2495)

* [Bots] Initial Cleanup of Functions, moved Bot Casting out of mob_ai.cpp

* Moved Bots off NPC AI_Spells Struct, and AI_Spells private attribute.

* Formatting Fixes, fixed LogAI entries, Added LogAIModerate Alias

* Add Constants.

* Added Bot DB Struct, fixed some potential casting issues

* Formatting

* Formatting
This commit is contained in:
Aeadoin
2022-10-29 16:38:15 -04:00
committed by GitHub
parent 5708164511
commit bf43bda1e2
11 changed files with 610 additions and 122 deletions
+439 -87
View File
@@ -39,7 +39,7 @@ bool Bot::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) {
return false;
if (iChance < 100) {
if (zone->random.Int(0, 100) > iChance){
if (zone->random.Int(0, 100) > iChance) {
return false;
}
}
@@ -87,13 +87,14 @@ bool Bot::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) {
Mob* addMob = GetFirstIncomingMobToMez(this, botSpell);
if(!addMob){
if (!addMob) {
//Say("!addMob.");
break;}
if(!(!addMob->IsImmuneToSpell(botSpell.SpellId, this) && addMob->CanBuffStack(botSpell.SpellId, botLevel, true) >= 0))
break;
}
if (!(!addMob->IsImmuneToSpell(botSpell.SpellId, this) && addMob->CanBuffStack(botSpell.SpellId, botLevel, true) >= 0)) {
break;
}
castedSpell = AIDoSpellCast(botSpell.SpellIndex, addMob, botSpell.ManaCost);
if(castedSpell)
@@ -135,58 +136,65 @@ bool Bot::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) {
if(hpr < 35) {
botSpell = GetBestBotSpellForFastHeal(this);
}
else if(hpr >= 35 && hpr < 70){
if(GetNumberNeedingHealedInGroup(60, false) >= 3)
else if (hpr >= 35 && hpr < 70) {
if (GetNumberNeedingHealedInGroup(60, false) >= 3) {
botSpell = GetBestBotSpellForGroupHeal(this);
if(botSpell.SpellId == 0)
}
if (botSpell.SpellId == 0) {
botSpell = GetBestBotSpellForPercentageHeal(this);
}
}
else if(hpr >= 70 && hpr < 95){
if(GetNumberNeedingHealedInGroup(80, false) >= 3)
else if (hpr >= 70 && hpr < 95) {
if (GetNumberNeedingHealedInGroup(80, false) >= 3) {
botSpell = GetBestBotSpellForGroupHealOverTime(this);
if(hasAggro)
}
if (hasAggro) {
botSpell = GetBestBotSpellForPercentageHeal(this);
}
}
else {
if(!tar->FindType(SE_HealOverTime))
if (!tar->FindType(SE_HealOverTime)) {
botSpell = GetBestBotSpellForHealOverTime(this);
}
}
}
else if ((botClass == CLERIC) || (botClass == DRUID) || (botClass == SHAMAN) || (botClass == PALADIN)) {
if(GetNumberNeedingHealedInGroup(40, true) >= 2){
if (GetNumberNeedingHealedInGroup(40, true) >= 2) {
botSpell = GetBestBotSpellForGroupCompleteHeal(this);
if(botSpell.SpellId == 0)
if (botSpell.SpellId == 0) {
botSpell = GetBestBotSpellForGroupHeal(this);
if(botSpell.SpellId == 0)
}
if (botSpell.SpellId == 0) {
botSpell = GetBestBotSpellForGroupHealOverTime(this);
if(hpr < 40) {
if(botSpell.SpellId == 0)
}
if (hpr < 40) {
if (botSpell.SpellId == 0) {
botSpell = GetBestBotSpellForPercentageHeal(this);
}
}
}
else if(GetNumberNeedingHealedInGroup(60, true) >= 2){
else if (GetNumberNeedingHealedInGroup(60, true) >= 2) {
botSpell = GetBestBotSpellForGroupHeal(this);
if(botSpell.SpellId == 0)
if (botSpell.SpellId == 0) {
botSpell = GetBestBotSpellForGroupHealOverTime(this);
if(hpr < 40) {
}
if (hpr < 40) {
if(botSpell.SpellId == 0)
botSpell = GetBestBotSpellForPercentageHeal(this);
}
}
else if(hpr < 40)
else if (hpr < 40) {
botSpell = GetBestBotSpellForPercentageHeal(this);
else if(hpr >= 40 && hpr < 75)
}
else if (hpr >= 40 && hpr < 75) {
botSpell = GetBestBotSpellForRegularSingleTargetHeal(this);
}
else {
if(hpr < 90 && !tar->FindType(SE_HealOverTime))
if (hpr < 90 && !tar->FindType(SE_HealOverTime)) {
botSpell = GetBestBotSpellForHealOverTime(this);
}
}
}
else {
@@ -213,17 +221,18 @@ bool Bot::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) {
}
//If we're at specified mana % or below, don't heal as hybrid
if(tar->GetHPRatio() <= hpRatioToCast)
if (tar->GetHPRatio() <= hpRatioToCast) {
botSpell = GetBestBotSpellForRegularSingleTargetHeal(this);
}
}
if(botSpell.SpellId == 0)
if (botSpell.SpellId == 0) {
botSpell = GetBestBotSpellForRegularSingleTargetHeal(this);
if(botSpell.SpellId == 0)
}
if (botSpell.SpellId == 0) {
botSpell = GetFirstBotSpellForSingleTargetHeal(this);
if(botSpell.SpellId == 0 && botClass == BARD){
}
if (botSpell.SpellId == 0 && botClass == BARD) {
botSpell = GetFirstBotSpellBySpellType(this, SpellType_Heal);
}
@@ -257,8 +266,8 @@ bool Bot::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) {
}
}*/
if(botClass != BARD) {
if(IsGroupSpell(botSpell.SpellId)){
if(HasGroup()) {
if (IsGroupSpell(botSpell.SpellId)) {
if (HasGroup()) {
Group *g = GetGroup();
if(g) {
@@ -345,14 +354,14 @@ bool Bot::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) {
}
// Put the zone levitate and movement check here since bots are able to bypass the client casting check
if((IsEffectInSpell(selectedBotSpell.SpellId, SE_Levitate) && !zone->CanLevitate())
if ((IsEffectInSpell(selectedBotSpell.SpellId, SE_Levitate) && !zone->CanLevitate())
|| (IsEffectInSpell(selectedBotSpell.SpellId, SE_MovementSpeed) && !zone->CanCastOutdoor())) {
if(botClass != BARD || !IsSpellUsableThisZoneType(selectedBotSpell.SpellId, zone->GetZoneType())){
if (botClass != BARD || !IsSpellUsableThisZoneType(selectedBotSpell.SpellId, zone->GetZoneType())) {
continue;
}
}
switch(tar->GetArchetype())
switch (tar->GetArchetype())
{
case ARCHETYPE_CASTER:
//TODO: probably more caster specific spell effects in here
@@ -491,8 +500,8 @@ bool Bot::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) {
botSpell = GetBestBotSpellForNukeByTargetType(this, ST_Summoned);
}
if(botClass == PALADIN || botClass == DRUID || botClass == CLERIC || botClass == ENCHANTER || botClass == WIZARD) {
if(botSpell.SpellId == 0) {
if (botClass == PALADIN || botClass == DRUID || botClass == CLERIC || botClass == ENCHANTER || botClass == WIZARD) {
if (botSpell.SpellId == 0) {
uint8 stunChance = (tar->IsCasting() ? 30: 15);
if(botClass == PALADIN)
@@ -913,29 +922,29 @@ bool Bot::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) {
break;
}
case SpellType_Cure: {
if(GetNeedsCured(tar) && (tar->DontCureMeBefore() < Timer::GetCurrentTime()) && !(GetNumberNeedingHealedInGroup(25, false) > 0) && !(GetNumberNeedingHealedInGroup(40, false) > 2))
if (GetNeedsCured(tar) && (tar->DontCureMeBefore() < Timer::GetCurrentTime()) && !(GetNumberNeedingHealedInGroup(25, false) > 0) && !(GetNumberNeedingHealedInGroup(40, false) > 2))
{
botSpell = GetBestBotSpellForCure(this, tar);
if(botSpell.SpellId == 0)
if (botSpell.SpellId == 0)
break;
uint32 TempDontCureMeBeforeTime = tar->DontCureMeBefore();
castedSpell = AIDoSpellCast(botSpell.SpellIndex, tar, botSpell.ManaCost, &TempDontCureMeBeforeTime);
if(castedSpell) {
if(botClass != BARD) {
if(IsGroupSpell(botSpell.SpellId)){
if (castedSpell) {
if (botClass != BARD) {
if (IsGroupSpell(botSpell.SpellId)) {
Group *g;
if(HasGroup()) {
if (HasGroup()) {
Group *g = GetGroup();
if(g) {
for( int i = 0; i<MAX_GROUP_MEMBERS; i++) {
if(g->members[i] && !g->members[i]->qglobal) {
if(TempDontCureMeBeforeTime != tar->DontCureMeBefore())
if (g) {
for ( int i = 0; i<MAX_GROUP_MEMBERS; i++) {
if (g->members[i] && !g->members[i]->qglobal) {
if (TempDontCureMeBeforeTime != tar->DontCureMeBefore())
g->members[i]->SetDontCureMeBefore(Timer::GetCurrentTime() + 4000);
}
}
@@ -943,8 +952,9 @@ bool Bot::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) {
}
}
else {
if(TempDontCureMeBeforeTime != tar->DontCureMeBefore())
if (TempDontCureMeBeforeTime != tar->DontCureMeBefore()) {
tar->SetDontCureMeBefore(Timer::GetCurrentTime() + 4000);
}
}
}
}
@@ -1063,7 +1073,7 @@ bool Bot::AIDoSpellCast(uint8 i, Mob* tar, int32 mana_cost, uint32* oDontDoAgain
int32 manaCost = mana_cost;
if (manaCost == -1)
manaCost = spells[AIspells[i].spellid].mana;
manaCost = spells[AIBot_spells[i].spellid].mana;
else if (manaCost == -2)
manaCost = 0;
@@ -1074,7 +1084,7 @@ bool Bot::AIDoSpellCast(uint8 i, Mob* tar, int32 mana_cost, uint32* oDontDoAgain
if(RuleB(Bots, FinishBuffing)) {
if(manaCost > hasMana) {
// Let's have the bots complete the buff time process
if(AIspells[i].type & SpellType_Buff) {
if(AIBot_spells[i].type & SpellType_Buff) {
extraMana = manaCost - hasMana;
SetMana(manaCost);
}
@@ -1083,20 +1093,22 @@ bool Bot::AIDoSpellCast(uint8 i, Mob* tar, int32 mana_cost, uint32* oDontDoAgain
float dist2 = 0;
if (AIspells[i].type & SpellType_Escape) {
if (AIBot_spells[i].type & SpellType_Escape) {
dist2 = 0;
} else
dist2 = DistanceSquared(m_Position, tar->GetPosition());
if (((((spells[AIspells[i].spellid].target_type==ST_GroupTeleport && AIspells[i].type==2)
|| spells[AIspells[i].spellid].target_type==ST_AECaster
|| spells[AIspells[i].spellid].target_type==ST_Group
|| spells[AIspells[i].spellid].target_type==ST_AEBard)
&& dist2 <= spells[AIspells[i].spellid].aoe_range*spells[AIspells[i].spellid].aoe_range)
|| dist2 <= GetActSpellRange(AIspells[i].spellid, spells[AIspells[i].spellid].range)*GetActSpellRange(AIspells[i].spellid, spells[AIspells[i].spellid].range)) && (mana_cost <= GetMana() || GetMana() == GetMaxMana()))
if (((((spells[AIBot_spells[i].spellid].target_type==ST_GroupTeleport && AIBot_spells[i].type==2)
|| spells[AIBot_spells[i].spellid].target_type==ST_AECaster
|| spells[AIBot_spells[i].spellid].target_type==ST_Group
|| spells[AIBot_spells[i].spellid].target_type==ST_AEBard)
&& dist2 <= spells[AIBot_spells[i].spellid].aoe_range*spells[AIBot_spells[i].spellid].aoe_range)
|| dist2 <= GetActSpellRange(AIBot_spells[i].spellid, spells[AIBot_spells[i].spellid].range)*GetActSpellRange(AIBot_spells[i].spellid, spells[AIBot_spells[i].spellid].range)) && (mana_cost <= GetMana() || GetMana() == GetMaxMana()))
{
result = NPC::AIDoSpellCast(i, tar, mana_cost, oDontDoAgainBefore);
casting_spell_AIindex = i;
LogAIDetail("Bot::AIDoSpellCast: spellid = [{}], tar = [{}], mana = [{}], Name: [{}]", AIBot_spells[i].spellid, tar->GetName(), mana_cost, spells[AIBot_spells[i].spellid].name);
result = Mob::CastSpell(AIBot_spells[i].spellid, tar->GetID(), EQ::spells::CastingSlot::Gem2, spells[AIBot_spells[i].spellid].cast_time, AIBot_spells[i].manacost == -2 ? 0 : mana_cost, oDontDoAgainBefore, -1, -1, 0, &(AIBot_spells[i].resist_adjust));
if(IsCasting() && IsSitting())
Stand();
}
@@ -1107,20 +1119,20 @@ bool Bot::AIDoSpellCast(uint8 i, Mob* tar, int32 mana_cost, uint32* oDontDoAgain
extraMana = false;
}
else { //handle spell recast and recast timers
//if(GetClass() == BARD && IsGroupSpell(AIspells[i].spellid)) {
//if(GetClass() == BARD && IsGroupSpell(AIBot_spells[i].spellid)) {
// // Bard buff songs have been moved to their own npc spell type..
// // Buff stacking is now checked as opposed to manipulating the timer to avoid rapid casting
// //AIspells[i].time_cancast = (spells[AIspells[i].spellid].recast_time > (spells[AIspells[i].spellid].buffduration * 6000)) ? Timer::GetCurrentTime() + spells[AIspells[i].spellid].recast_time : Timer::GetCurrentTime() + spells[AIspells[i].spellid].buffduration * 6000;
// //spellend_timer.Start(spells[AIspells[i].spellid].cast_time);
// //AIBot_spells[i].time_cancast = (spells[AIBot_spells[i].spellid].recast_time > (spells[AIBot_spells[i].spellid].buffduration * 6000)) ? Timer::GetCurrentTime() + spells[AIBot_spells[i].spellid].recast_time : Timer::GetCurrentTime() + spells[AIBot_spells[i].spellid].buffduration * 6000;
// //spellend_timer.Start(spells[AIBot_spells[i].spellid].cast_time);
//}
//else
// AIspells[i].time_cancast = Timer::GetCurrentTime() + spells[AIspells[i].spellid].recast_time;
// AIBot_spells[i].time_cancast = Timer::GetCurrentTime() + spells[AIBot_spells[i].spellid].recast_time;
AIspells[i].time_cancast = Timer::GetCurrentTime() + spells[AIspells[i].spellid].recast_time;
AIBot_spells[i].time_cancast = Timer::GetCurrentTime() + spells[AIBot_spells[i].spellid].recast_time;
if(spells[AIspells[i].spellid].timer_id > 0) {
SetSpellRecastTimer(spells[AIspells[i].spellid].timer_id, spells[AIspells[i].spellid].recast_time);
if(spells[AIBot_spells[i].spellid].timer_id > 0) {
SetSpellRecastTimer(spells[AIBot_spells[i].spellid].timer_id, spells[AIBot_spells[i].spellid].recast_time);
}
}
@@ -1551,13 +1563,13 @@ bool Bot::AIHealRotation(Mob* tar, bool useFastHeals) {
else {
botSpell = GetBestBotSpellForPercentageHeal(this);
if(botSpell.SpellId == 0)
if (botSpell.SpellId == 0) {
botSpell = GetBestBotSpellForRegularSingleTargetHeal(this);
if(botSpell.SpellId == 0)
}
if (botSpell.SpellId == 0) {
botSpell = GetFirstBotSpellForSingleTargetHeal(this);
if(botSpell.SpellId == 0){
}
if (botSpell.SpellId == 0) {
botSpell = GetFirstBotSpellBySpellType(this, SpellType_Heal);
}
}
@@ -1593,7 +1605,7 @@ std::list<BotSpell> Bot::GetBotSpellsForSpellEffect(Bot* botCaster, int spellEff
std::list<BotSpell> result;
if(botCaster && botCaster->AI_HasSpells()) {
std::vector<AISpells_Struct> botSpellList = botCaster->GetBotSpells();
std::vector<BotSpells_Struct> botSpellList = botCaster->AIBot_spells;
for (int i = botSpellList.size() - 1; i >= 0; i--) {
if (botSpellList[i].spellid <= 0 || botSpellList[i].spellid >= SPDAT_RECORDS) {
@@ -1620,7 +1632,7 @@ std::list<BotSpell> Bot::GetBotSpellsForSpellEffectAndTargetType(Bot* botCaster,
std::list<BotSpell> result;
if(botCaster && botCaster->AI_HasSpells()) {
std::vector<AISpells_Struct> botSpellList = botCaster->GetBotSpells();
std::vector<BotSpells_Struct> botSpellList = botCaster->AIBot_spells;
for (int i = botSpellList.size() - 1; i >= 0; i--) {
if (botSpellList[i].spellid <= 0 || botSpellList[i].spellid >= SPDAT_RECORDS) {
@@ -1649,7 +1661,7 @@ std::list<BotSpell> Bot::GetBotSpellsBySpellType(Bot* botCaster, uint32 spellTyp
std::list<BotSpell> result;
if(botCaster && botCaster->AI_HasSpells()) {
std::vector<AISpells_Struct> botSpellList = botCaster->GetBotSpells();
std::vector<BotSpells_Struct> botSpellList = botCaster->AIBot_spells;
for (int i = botSpellList.size() - 1; i >= 0; i--) {
if (botSpellList[i].spellid <= 0 || botSpellList[i].spellid >= SPDAT_RECORDS) {
@@ -1676,7 +1688,7 @@ std::list<BotSpell_wPriority> Bot::GetPrioritizedBotSpellsBySpellType(Bot* botCa
std::list<BotSpell_wPriority> result;
if (botCaster && botCaster->AI_HasSpells()) {
std::vector<AISpells_Struct> botSpellList = botCaster->GetBotSpells();
std::vector<BotSpells_Struct> botSpellList = botCaster->AIBot_spells;
for (int i = botSpellList.size() - 1; i >= 0; i--) {
if (botSpellList[i].spellid <= 0 || botSpellList[i].spellid >= SPDAT_RECORDS) {
@@ -1711,7 +1723,7 @@ BotSpell Bot::GetFirstBotSpellBySpellType(Bot* botCaster, uint32 spellType) {
result.ManaCost = 0;
if(botCaster && botCaster->AI_HasSpells()) {
std::vector<AISpells_Struct> botSpellList = botCaster->GetBotSpells();
std::vector<BotSpells_Struct> botSpellList = botCaster->AIBot_spells;
for (int i = botSpellList.size() - 1; i >= 0; i--) {
if (botSpellList[i].spellid <= 0 || botSpellList[i].spellid >= SPDAT_RECORDS) {
@@ -1767,7 +1779,7 @@ BotSpell Bot::GetBestBotSpellForHealOverTime(Bot* botCaster) {
if(botCaster) {
std::list<BotSpell> botHoTSpellList = GetBotSpellsForSpellEffect(botCaster, SE_HealOverTime);
std::vector<AISpells_Struct> botSpellList = botCaster->GetBotSpells();
std::vector<BotSpells_Struct> botSpellList = botCaster->AIBot_spells;
for(std::list<BotSpell>::iterator botSpellListItr = botHoTSpellList.begin(); botSpellListItr != botHoTSpellList.end(); ++botSpellListItr) {
// Assuming all the spells have been loaded into this list by level and in descending order
@@ -1803,7 +1815,7 @@ BotSpell Bot::GetBestBotSpellForPercentageHeal(Bot *botCaster) {
result.ManaCost = 0;
if(botCaster && botCaster->AI_HasSpells()) {
std::vector<AISpells_Struct> botSpellList = botCaster->GetBotSpells();
std::vector<BotSpells_Struct> botSpellList = botCaster->AIBot_spells;
for (int i = botSpellList.size() - 1; i >= 0; i--) {
if (botSpellList[i].spellid <= 0 || botSpellList[i].spellid >= SPDAT_RECORDS) {
@@ -1909,7 +1921,7 @@ BotSpell Bot::GetBestBotSpellForGroupHealOverTime(Bot* botCaster) {
if(botCaster) {
std::list<BotSpell> botHoTSpellList = GetBotSpellsForSpellEffect(botCaster, SE_HealOverTime);
std::vector<AISpells_Struct> botSpellList = botCaster->GetBotSpells();
std::vector<BotSpells_Struct> botSpellList = botCaster->AIBot_spells;
for(std::list<BotSpell>::iterator botSpellListItr = botHoTSpellList.begin(); botSpellListItr != botHoTSpellList.end(); ++botSpellListItr) {
// Assuming all the spells have been loaded into this list by level and in descending order
@@ -2320,7 +2332,7 @@ BotSpell Bot::GetDebuffBotSpell(Bot* botCaster, Mob *tar) {
return result;
if(botCaster && botCaster->AI_HasSpells()) {
std::vector<AISpells_Struct> botSpellList = botCaster->GetBotSpells();
std::vector<BotSpells_Struct> botSpellList = botCaster->AIBot_spells;
for (int i = botSpellList.size() - 1; i >= 0; i--) {
if (botSpellList[i].spellid <= 0 || botSpellList[i].spellid >= SPDAT_RECORDS) {
@@ -2367,7 +2379,7 @@ BotSpell Bot::GetBestBotSpellForResistDebuff(Bot* botCaster, Mob *tar) {
bool needsDiseaseResistDebuff = (tar->GetDR() + level_mod) > 100 ? true: false;
if(botCaster && botCaster->AI_HasSpells()) {
std::vector<AISpells_Struct> botSpellList = botCaster->GetBotSpells();
std::vector<BotSpells_Struct> botSpellList = botCaster->AIBot_spells;
for (int i = botSpellList.size() - 1; i >= 0; i--) {
if (botSpellList[i].spellid <= 0 || botSpellList[i].spellid >= SPDAT_RECORDS) {
@@ -2526,8 +2538,8 @@ int32 Bot::GetSpellRecastTimer(Bot *caster, int timer_index) {
bool Bot::CheckSpellRecastTimers(Bot *caster, int SpellIndex) {
if(caster) {
if(caster->AIspells[SpellIndex].time_cancast < Timer::GetCurrentTime()) { //checks spell recast
if(GetSpellRecastTimer(caster, spells[caster->AIspells[SpellIndex].spellid].timer_id) < Timer::GetCurrentTime()) { //checks for spells on the same timer
if(caster->AIBot_spells[SpellIndex].time_cancast < Timer::GetCurrentTime()) { //checks spell recast
if(GetSpellRecastTimer(caster, spells[caster->AIBot_spells[SpellIndex].spellid].timer_id) < Timer::GetCurrentTime()) { //checks for spells on the same timer
return true; //can cast spell
}
}
@@ -2674,4 +2686,344 @@ uint8 Bot::GetChanceToCastBySpellType(uint32 spellType)
return database.botdb.GetSpellCastingChance(spell_type_index, class_index, stance_index, type_index);
}
bool Bot::AI_AddBotSpells(uint32 iDBSpellsID) {
// ok, this function should load the list, and the parent list then shove them into the struct and sort
npc_spells_id = iDBSpellsID;
AIBot_spells.clear();
if (iDBSpellsID == 0) {
AIautocastspell_timer->Disable();
return false;
}
DBbotspells_Struct* spell_list = content_db.GetBotSpells(iDBSpellsID);
if (!spell_list) {
AIautocastspell_timer->Disable();
return false;
}
DBbotspells_Struct* parentlist = content_db.GetBotSpells(spell_list->parent_list);
std::string debug_msg = StringFormat("Loading NPCSpells onto %s: dbspellsid=%u, level=%u", GetName(), iDBSpellsID, GetLevel());
if (spell_list) {
debug_msg.append(StringFormat(" (found, %u), parentlist=%u", spell_list->entries.size(), spell_list->parent_list));
if (spell_list->parent_list) {
if (parentlist)
debug_msg.append(StringFormat(" (found, %u)", parentlist->entries.size()));
else
debug_msg.append(" (not found)");
}
}
else {
debug_msg.append(" (not found)");
}
LogAIDetail("[{}]", debug_msg.c_str());
if (parentlist) {
for (const auto &iter : parentlist->entries) {
LogAI("([{}]) [{}]", iter.spellid, spells[iter.spellid].name);
}
}
LogAIModerate("fin (parent list)");
if (spell_list) {
for (const auto &iter : spell_list->entries) {
LogAIDetail("([{}]) [{}]", iter.spellid, spells[iter.spellid].name);
}
}
LogAIModerate("fin (spell list)");
uint16 attack_proc_spell = -1;
int8 proc_chance = 3;
uint16 range_proc_spell = -1;
int16 rproc_chance = 0;
uint16 defensive_proc_spell = -1;
int16 dproc_chance = 0;
uint32 _fail_recast = 0;
uint32 _engaged_no_sp_recast_min = 0;
uint32 _engaged_no_sp_recast_max = 0;
uint8 _engaged_beneficial_self_chance = 0;
uint8 _engaged_beneficial_other_chance = 0;
uint8 _engaged_detrimental_chance = 0;
uint32 _pursue_no_sp_recast_min = 0;
uint32 _pursue_no_sp_recast_max = 0;
uint8 _pursue_detrimental_chance = 0;
uint32 _idle_no_sp_recast_min = 0;
uint32 _idle_no_sp_recast_max = 0;
uint8 _idle_beneficial_chance = 0;
if (parentlist) {
attack_proc_spell = parentlist->attack_proc;
proc_chance = parentlist->proc_chance;
range_proc_spell = parentlist->range_proc;
rproc_chance = parentlist->rproc_chance;
defensive_proc_spell = parentlist->defensive_proc;
dproc_chance = parentlist->dproc_chance;
_fail_recast = parentlist->fail_recast;
_engaged_no_sp_recast_min = parentlist->engaged_no_sp_recast_min;
_engaged_no_sp_recast_max = parentlist->engaged_no_sp_recast_max;
_engaged_beneficial_self_chance = parentlist->engaged_beneficial_self_chance;
_engaged_beneficial_other_chance = parentlist->engaged_beneficial_other_chance;
_engaged_detrimental_chance = parentlist->engaged_detrimental_chance;
_pursue_no_sp_recast_min = parentlist->pursue_no_sp_recast_min;
_pursue_no_sp_recast_max = parentlist->pursue_no_sp_recast_max;
_pursue_detrimental_chance = parentlist->pursue_detrimental_chance;
_idle_no_sp_recast_min = parentlist->idle_no_sp_recast_min;
_idle_no_sp_recast_max = parentlist->idle_no_sp_recast_max;
_idle_beneficial_chance = parentlist->idle_beneficial_chance;
for (auto &e : parentlist->entries) {
if (GetLevel() >= e.minlevel && GetLevel() <= e.maxlevel && e.spellid > 0) {
if (!IsSpellInBotList(spell_list, e.spellid))
{
AddSpellToBotList(e.priority, e.spellid, e.type, e.manacost, e.recast_delay, e.resist_adjust, e.min_hp, e.max_hp);
}
}
}
}
if (spell_list->attack_proc >= 0) {
attack_proc_spell = spell_list->attack_proc;
proc_chance = spell_list->proc_chance;
}
if (spell_list->range_proc >= 0) {
range_proc_spell = spell_list->range_proc;
rproc_chance = spell_list->rproc_chance;
}
if (spell_list->defensive_proc >= 0) {
defensive_proc_spell = spell_list->defensive_proc;
dproc_chance = spell_list->dproc_chance;
}
//If any casting variables are defined in the current list, ignore those in the parent list.
if (spell_list->fail_recast || spell_list->engaged_no_sp_recast_min || spell_list->engaged_no_sp_recast_max
|| spell_list->engaged_beneficial_self_chance || spell_list->engaged_beneficial_other_chance || spell_list->engaged_detrimental_chance
|| spell_list->pursue_no_sp_recast_min || spell_list->pursue_no_sp_recast_max || spell_list->pursue_detrimental_chance
|| spell_list->idle_no_sp_recast_min || spell_list->idle_no_sp_recast_max || spell_list->idle_beneficial_chance) {
_fail_recast = spell_list->fail_recast;
_engaged_no_sp_recast_min = spell_list->engaged_no_sp_recast_min;
_engaged_no_sp_recast_max = spell_list->engaged_no_sp_recast_max;
_engaged_beneficial_self_chance = spell_list->engaged_beneficial_self_chance;
_engaged_beneficial_other_chance = spell_list->engaged_beneficial_other_chance;
_engaged_detrimental_chance = spell_list->engaged_detrimental_chance;
_pursue_no_sp_recast_min = spell_list->pursue_no_sp_recast_min;
_pursue_no_sp_recast_max = spell_list->pursue_no_sp_recast_max;
_pursue_detrimental_chance = spell_list->pursue_detrimental_chance;
_idle_no_sp_recast_min = spell_list->idle_no_sp_recast_min;
_idle_no_sp_recast_max = spell_list->idle_no_sp_recast_max;
_idle_beneficial_chance = spell_list->idle_beneficial_chance;
}
for (auto &e : spell_list->entries) {
if (GetLevel() >= e.minlevel && GetLevel() <= e.maxlevel && e.spellid > 0) {
AddSpellToBotList(e.priority, e.spellid, e.type, e.manacost, e.recast_delay, e.resist_adjust, e.min_hp, e.max_hp);
}
}
std::sort(AIBot_spells.begin(), AIBot_spells.end(), [](const BotSpells_Struct& a, const BotSpells_Struct& b) {
return a.priority > b.priority;
});
if (IsValidSpell(attack_proc_spell)) {
AddProcToWeapon(attack_proc_spell, true, proc_chance);
if (RuleB(Spells, NPCInnateProcOverride)) {
innate_proc_spell_id = attack_proc_spell;
}
}
if (IsValidSpell(range_proc_spell)) {
AddRangedProc(range_proc_spell, (rproc_chance + 100));
}
if (IsValidSpell(defensive_proc_spell)) {
AddDefensiveProc(defensive_proc_spell, (dproc_chance + 100));
}
//Set AI casting variables
AISpellVar.fail_recast = (_fail_recast) ? _fail_recast : RuleI(Spells, AI_SpellCastFinishedFailRecast);
AISpellVar.engaged_no_sp_recast_min = (_engaged_no_sp_recast_min) ? _engaged_no_sp_recast_min : RuleI(Spells, AI_EngagedNoSpellMinRecast);
AISpellVar.engaged_no_sp_recast_max = (_engaged_no_sp_recast_max) ? _engaged_no_sp_recast_max : RuleI(Spells, AI_EngagedNoSpellMaxRecast);
AISpellVar.engaged_beneficial_self_chance = (_engaged_beneficial_self_chance) ? _engaged_beneficial_self_chance : RuleI(Spells, AI_EngagedBeneficialSelfChance);
AISpellVar.engaged_beneficial_other_chance = (_engaged_beneficial_other_chance) ? _engaged_beneficial_other_chance : RuleI(Spells, AI_EngagedBeneficialOtherChance);
AISpellVar.engaged_detrimental_chance = (_engaged_detrimental_chance) ? _engaged_detrimental_chance : RuleI(Spells, AI_EngagedDetrimentalChance);
AISpellVar.pursue_no_sp_recast_min = (_pursue_no_sp_recast_min) ? _pursue_no_sp_recast_min : RuleI(Spells, AI_PursueNoSpellMinRecast);
AISpellVar.pursue_no_sp_recast_max = (_pursue_no_sp_recast_max) ? _pursue_no_sp_recast_max : RuleI(Spells, AI_PursueNoSpellMaxRecast);
AISpellVar.pursue_detrimental_chance = (_pursue_detrimental_chance) ? _pursue_detrimental_chance : RuleI(Spells, AI_PursueDetrimentalChance);
AISpellVar.idle_no_sp_recast_min = (_idle_no_sp_recast_min) ? _idle_no_sp_recast_min : RuleI(Spells, AI_IdleNoSpellMinRecast);
AISpellVar.idle_no_sp_recast_max = (_idle_no_sp_recast_max) ? _idle_no_sp_recast_max : RuleI(Spells, AI_IdleNoSpellMaxRecast);
AISpellVar.idle_beneficial_chance = (_idle_beneficial_chance) ? _idle_beneficial_chance : RuleI(Spells, AI_IdleBeneficialChance);
if (AIBot_spells.empty()) {
AIautocastspell_timer->Disable();
} else {
AIautocastspell_timer->Trigger();
}
return true;
}
bool IsSpellInBotList(DBbotspells_Struct* spell_list, uint16 iSpellID) {
auto it = std::find_if(spell_list->entries.begin(), spell_list->entries.end(),
[iSpellID](const DBbotspells_entries_Struct &a) { return a.spellid == iSpellID; });
return it != spell_list->entries.end();
}
DBbotspells_Struct *ZoneDatabase::GetBotSpells(uint32 iDBSpellsID)
{
if (iDBSpellsID == 0) {
return nullptr;
}
auto it = Bot_Spells_Cache.find(iDBSpellsID);
if (it != Bot_Spells_Cache.end()) { // it's in the cache, easy =)
return &it->second;
}
if (!Bot_Spells_LoadTried.count(iDBSpellsID)) { // no reason to ask the DB again if we have failed once already
Bot_Spells_LoadTried.insert(iDBSpellsID);
std::string query = StringFormat("SELECT id, parent_list, attack_proc, proc_chance, "
"range_proc, rproc_chance, defensive_proc, dproc_chance, "
"fail_recast, engaged_no_sp_recast_min, engaged_no_sp_recast_max, "
"engaged_b_self_chance, engaged_b_other_chance, engaged_d_chance, "
"pursue_no_sp_recast_min, pursue_no_sp_recast_max, "
"pursue_d_chance, idle_no_sp_recast_min, idle_no_sp_recast_max, "
"idle_b_chance FROM npc_spells WHERE id=%d",
iDBSpellsID);
auto results = QueryDatabase(query);
if (!results.Success()) {
return nullptr;
}
if (results.RowCount() != 1) {
return nullptr;
}
auto row = results.begin();
DBbotspells_Struct spell_set;
spell_set.parent_list = atoi(row[1]);
spell_set.attack_proc = atoi(row[2]);
spell_set.proc_chance = atoi(row[3]);
spell_set.range_proc = atoi(row[4]);
spell_set.rproc_chance = atoi(row[5]);
spell_set.defensive_proc = atoi(row[6]);
spell_set.dproc_chance = atoi(row[7]);
spell_set.fail_recast = atoi(row[8]);
spell_set.engaged_no_sp_recast_min = atoi(row[9]);
spell_set.engaged_no_sp_recast_max = atoi(row[10]);
spell_set.engaged_beneficial_self_chance = atoi(row[11]);
spell_set.engaged_beneficial_other_chance = atoi(row[12]);
spell_set.engaged_detrimental_chance = atoi(row[13]);
spell_set.pursue_no_sp_recast_min = atoi(row[14]);
spell_set.pursue_no_sp_recast_max = atoi(row[15]);
spell_set.pursue_detrimental_chance = atoi(row[16]);
spell_set.idle_no_sp_recast_min = atoi(row[17]);
spell_set.idle_no_sp_recast_max = atoi(row[18]);
spell_set.idle_beneficial_chance = atoi(row[19]);
// pulling fixed values from an auto-increment field is dangerous...
query = StringFormat(
"SELECT spellid, type, minlevel, maxlevel, "
"manacost, recast_delay, priority, min_hp, max_hp, resist_adjust "
"FROM bot_spells_entries "
"WHERE npc_spells_id=%d ORDER BY minlevel",
iDBSpellsID);
results = QueryDatabase(query);
if (!results.Success()) {
return nullptr;
}
int entryIndex = 0;
for (row = results.begin(); row != results.end(); ++row, ++entryIndex) {
DBbotspells_entries_Struct entry;
int spell_id = atoi(row[0]);
entry.spellid = spell_id;
entry.type = atoul(row[1]);
entry.minlevel = atoi(row[2]);
entry.maxlevel = atoi(row[3]);
entry.manacost = atoi(row[4]);
entry.recast_delay = atoi(row[5]);
entry.priority = atoi(row[6]);
entry.min_hp = atoi(row[7]);
entry.max_hp = atoi(row[8]);
// some spell types don't make much since to be priority 0, so fix that
if (!(entry.type & SPELL_TYPES_INNATE) && entry.priority == 0)
entry.priority = 1;
if (row[9]) {
entry.resist_adjust = atoi(row[9]);
}
else if (IsValidSpell(spell_id)) {
entry.resist_adjust = spells[spell_id].resist_difficulty;
}
spell_set.entries.push_back(entry);
}
Bot_Spells_Cache.insert(std::make_pair(iDBSpellsID, spell_set));
return &Bot_Spells_Cache[iDBSpellsID];
}
return nullptr;
}
// adds a spell to the list, taking into account priority and resorting list as needed.
void Bot::AddSpellToBotList(int16 iPriority, uint16 iSpellID, uint32 iType,
int16 iManaCost, int32 iRecastDelay, int16 iResistAdjust, int8 min_hp, int8 max_hp)
{
if(!IsValidSpell(iSpellID)) {
return;
}
HasAISpell = true;
BotSpells_Struct t;
t.priority = iPriority;
t.spellid = iSpellID;
t.type = iType;
t.manacost = iManaCost;
t.recast_delay = iRecastDelay;
t.time_cancast = 0;
t.resist_adjust = iResistAdjust;
t.min_hp = min_hp;
t.max_hp = max_hp;
AIBot_spells.push_back(t);
// If we're going from an empty list, we need to start the timer
if (AIBot_spells.empty()) {
AIautocastspell_timer->Start(RandomTimer(0, 300), false);
}
}
//this gets called from InterruptSpell() for failure or SpellFinished() for success
void Bot::AI_Bot_Event_SpellCastFinished(bool iCastSucceeded, uint16 slot) {
if (slot == 1) {
uint32 recovery_time = 0;
if (iCastSucceeded) {
if (casting_spell_AIindex < AIBot_spells.size()) {
recovery_time += spells[AIBot_spells[casting_spell_AIindex].spellid].recovery_time;
if (AIBot_spells[casting_spell_AIindex].recast_delay >= 0) {
if (AIBot_spells[casting_spell_AIindex].recast_delay < 10000) {
AIBot_spells[casting_spell_AIindex].time_cancast = Timer::GetCurrentTime() + (AIBot_spells[casting_spell_AIindex].recast_delay*1000);
}
}
else {
AIBot_spells[casting_spell_AIindex].time_cancast = Timer::GetCurrentTime() + spells[AIBot_spells[casting_spell_AIindex].spellid].recast_time;
}
}
if (recovery_time < AIautocastspell_timer->GetSetAtTrigger()) {
recovery_time = AIautocastspell_timer->GetSetAtTrigger();
}
AIautocastspell_timer->Start(recovery_time, false);
}
else {
AIautocastspell_timer->Start(AISpellVar.fail_recast, false);
}
casting_spell_AIindex = AIBot_spells.size();
}
}
#endif