[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
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 610 additions and 122 deletions

View File

@ -217,6 +217,25 @@ namespace EQ
stanceBurnAE
};
enum BotSpellIDs : int {
Warrior = 3001,
Cleric,
Paladin,
Ranger,
Shadowknight,
Druid,
Monk,
Bard,
Rogue,
Shaman,
Necromancer,
Wizard,
Magician,
Enchanter,
Beastlord,
Berserker
};
enum GravityBehavior : int8 {
Ground,
Flying,

View File

@ -38,6 +38,11 @@
OutF(LogSys, Logs::General, Logs::AI, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
} while (0)
#define LogAIModerate(message, ...) do {\
if (LogSys.IsLogEnabled(Logs::General, Logs::AI))\
OutF(LogSys, Logs::Moderate, Logs::AI, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
} while (0)
#define LogAIDetail(message, ...) do {\
if (LogSys.IsLogEnabled(Logs::Detail, Logs::AI))\
OutF(LogSys, Logs::Detail, Logs::AI, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\

View File

@ -843,7 +843,7 @@ void Bot::GenerateBaseStats()
int32 CorruptionResist = _baseCorrup;
// pulling fixed values from an auto-increment field is dangerous...
switch(GetClass()) {
switch (GetClass()) {
case WARRIOR:
BotSpellID = 3001;
Strength += 10;
@ -2173,6 +2173,47 @@ bool Bot::Process()
return true;
}
void Bot::AI_Bot_Start(uint32 iMoveDelay) {
Mob::AI_Start(iMoveDelay);
if (!pAIControlled) {
return;
}
if (AIBot_spells.empty()) {
AIautocastspell_timer = std::make_unique<Timer>(1000);
AIautocastspell_timer->Disable();
} else {
AIautocastspell_timer = std::make_unique<Timer>(500);
AIautocastspell_timer->Start(RandomTimer(0, 300), false);
}
if (NPCTypedata) {
AI_AddBotSpells(NPCTypedata->npc_spells_id);
ProcessSpecialAbilities(NPCTypedata->special_abilities);
AI_AddNPCSpellsEffects(NPCTypedata->npc_spells_effects_id);
}
SendTo(GetX(), GetY(), GetZ());
SaveGuardSpot(GetPosition());
}
void Bot::AI_Bot_Init()
{
AIautocastspell_timer.reset(nullptr);
casting_spell_AIindex = static_cast<uint8>(AIBot_spells.size());
roambox_max_x = 0;
roambox_max_y = 0;
roambox_min_x = 0;
roambox_min_y = 0;
roambox_distance = 0;
roambox_destination_x = 0;
roambox_destination_y = 0;
roambox_destination_z = 0;
roambox_min_delay = 2500;
roambox_delay = 2500;
}
void Bot::SpellProcess() {
if(spellend_timer.Check(false)) {
NPC::SpellProcess();
@ -2259,7 +2300,7 @@ void Bot::BotRangedAttack(Mob* other) {
if (spellbonuses.NegateIfCombat)
BuffFadeByEffect(SE_NegateIfCombat);
if(hidden || improved_hidden){
if (hidden || improved_hidden) {
hidden = false;
improved_hidden = false;
EQApplicationPacket* outapp = new EQApplicationPacket(OP_SpawnAppearance, sizeof(SpawnAppearance_Struct));
@ -5161,7 +5202,7 @@ bool Bot::Attack(Mob* other, int Hand, bool FromRiposte, bool IsStrikethrough, b
#endif
//Live AA - Sinister Strikes *Adds weapon damage bonus to offhand weapon.
if (Hand == EQ::invslot::slotSecondary) {
if (aabonuses.SecondaryDmgInc || itembonuses.SecondaryDmgInc || spellbonuses.SecondaryDmgInc){
if (aabonuses.SecondaryDmgInc || itembonuses.SecondaryDmgInc || spellbonuses.SecondaryDmgInc) {
ucDamageBonus = GetWeaponDamageBonus(weapon ? weapon->GetItem() : (const EQ::ItemData*) nullptr);
my_hit.min_damage = ucDamageBonus;
hate += ucDamageBonus;
@ -6430,9 +6471,9 @@ void Bot::DoClassAttacks(Mob *target, bool IsRiposte) {
return;
}
if(ka_time){
if (ka_time) {
switch(GetClass()){
switch (GetClass()) {
case SHADOWKNIGHT: {
CastSpell(SPELL_NPC_HARM_TOUCH, target->GetID());
knightattack_timer.Start(HarmTouchReuseTime * 1000);
@ -6530,9 +6571,9 @@ void Bot::DoClassAttacks(Mob *target, bool IsRiposte) {
int level = GetLevel();
int reuse = (TauntReuseTime * 1000);
bool did_attack = false;
switch(GetClass()) {
switch (GetClass()) {
case WARRIOR:
if(level >= RuleI(Combat, NPCBashKickLevel)){
if (level >= RuleI(Combat, NPCBashKickLevel)) {
bool canBash = false;
if ((GetRace() == OGRE || GetRace() == TROLL || GetRace() == BARBARIAN) || (m_inv.GetItem(EQ::invslot::slotSecondary) && m_inv.GetItem(EQ::invslot::slotSecondary)->GetItem()->ItemType == EQ::item::ItemTypeShield) || (m_inv.GetItem(EQ::invslot::slotPrimary) && m_inv.GetItem(EQ::invslot::slotPrimary)->GetItem()->IsType2HWeapon() && GetAA(aa2HandBash) >= 1))
canBash = true;
@ -6552,7 +6593,7 @@ void Bot::DoClassAttacks(Mob *target, bool IsRiposte) {
case CLERIC:
case SHADOWKNIGHT:
case PALADIN:
if(level >= RuleI(Combat, NPCBashKickLevel)){
if (level >= RuleI(Combat, NPCBashKickLevel)) {
if ((GetRace() == OGRE || GetRace() == TROLL || GetRace() == BARBARIAN) || (m_inv.GetItem(EQ::invslot::slotSecondary) && m_inv.GetItem(EQ::invslot::slotSecondary)->GetItem()->ItemType == EQ::item::ItemTypeShield) || (m_inv.GetItem(EQ::invslot::slotPrimary) && m_inv.GetItem(EQ::invslot::slotPrimary)->GetItem()->IsType2HWeapon() && GetAA(aa2HandBash) >= 1))
skill_to_use = EQ::skills::SkillBash;
}
@ -7006,7 +7047,7 @@ int64 Bot::GetActSpellDamage(uint16 spell_id, int64 value, Mob* target) {
}
else if (GetClass() == WIZARD || (IsMerc() && GetClass() == CASTERDPS)) {
if ((GetLevel() >= RuleI(Spells, WizCritLevel)) && zone->random.Roll(RuleI(Spells, WizCritChance))){
if ((GetLevel() >= RuleI(Spells, WizCritLevel)) && zone->random.Roll(RuleI(Spells, WizCritChance))) {
//Wizard innate critical chance is calculated seperately from spell effect and is not a set ratio. (20-70 is parse confirmed)
ratio += zone->random.Int(20,70);
Critical = true;
@ -7436,16 +7477,16 @@ bool Bot::CastSpell(uint16 spell_id, uint16 target_id, EQ::spells::CastingSlot s
MessageString(Chat::White, MELEE_SILENCE);
if(casting_spell_id)
AI_Event_SpellCastFinished(false, static_cast<uint16>(casting_spell_slot));
AI_Bot_Event_SpellCastFinished(false, static_cast<uint16>(casting_spell_slot));
return false;
}
}
if(IsDetrimentalSpell(spell_id) && !zone->CanDoCombat()){
if (IsDetrimentalSpell(spell_id) && !zone->CanDoCombat()) {
MessageString(Chat::White, SPELL_WOULDNT_HOLD);
if(casting_spell_id)
AI_Event_SpellCastFinished(false, static_cast<uint16>(casting_spell_slot));
AI_Bot_Event_SpellCastFinished(false, static_cast<uint16>(casting_spell_slot));
return false;
}
@ -7733,7 +7774,7 @@ bool Bot::DoFinishedSpellSingleTarget(uint16 spell_id, Mob* spellTarget, EQ::spe
if(IsGrouped() && (spellTarget->IsBot() || spellTarget->IsClient()) && RuleB(Bots, GroupBuffing)) {
bool noGroupSpell = false;
uint16 thespell = spell_id;
for(int i = 0; i < AIspells.size(); i++) {
for (int i = 0; i < AIBot_spells.size(); i++) {
int j = BotGetSpells(i);
int spelltype = BotGetSpellType(i);
bool spellequal = (j == thespell);
@ -7833,7 +7874,7 @@ void Bot::CalcBonuses() {
end_regen = CalcEnduranceRegen();
}
int64 Bot::CalcHPRegenCap(){
int64 Bot::CalcHPRegenCap() {
int level = GetLevel();
int64 hpregen_cap = 0;
hpregen_cap = (RuleI(Character, ItemHealthRegenCap) + itembonuses.HeroicSTA / 25);
@ -7841,7 +7882,7 @@ int64 Bot::CalcHPRegenCap(){
return (hpregen_cap * RuleI(Character, HPRegenMultiplier) / 100);
}
int64 Bot::CalcManaRegenCap(){
int64 Bot::CalcManaRegenCap() {
int64 cap = RuleI(Character, ItemManaRegenCap) + aabonuses.ItemManaRegenCap;
switch(GetCasterClass()) {
case 'I':
@ -8278,7 +8319,7 @@ int64 Bot::CalcManaRegen() {
uint64 Bot::GetClassHPFactor() {
uint32 factor;
switch(GetClass()) {
switch (GetClass()) {
case BEASTLORD:
case BERSERKER:
case MONK:
@ -9101,7 +9142,7 @@ void Bot::AddItemBonuses(const EQ::ItemInstance *inst, StatBonuses* newbon, bool
}
}
if (item->SkillModValue != 0 && item->SkillModType <= EQ::skills::HIGHEST_SKILL){
if (item->SkillModValue != 0 && item->SkillModType <= EQ::skills::HIGHEST_SKILL) {
if ((item->SkillModValue > 0 && newbon->skillmod[item->SkillModType] < item->SkillModValue) ||
(item->SkillModValue < 0 && newbon->skillmod[item->SkillModType] > item->SkillModValue))
{
@ -9194,7 +9235,7 @@ void Bot::CalcBotStats(bool showtext) {
CalcBonuses();
AI_AddNPCSpells(GetBotSpellID());
AI_AddBotSpells(GetBotSpellID());
if(showtext) {
GetBotOwner()->Message(Chat::Yellow, "%s has been updated.", GetCleanName());

View File

@ -174,9 +174,9 @@ public:
virtual bool Save();
virtual void Depop();
void CalcBotStats(bool showtext = true);
uint16 BotGetSpells(int spellslot) { return AIspells[spellslot].spellid; }
uint32 BotGetSpellType(int spellslot) { return AIspells[spellslot].type; }
uint16 BotGetSpellPriority(int spellslot) { return AIspells[spellslot].priority; }
uint16 BotGetSpells(int spellslot) { return AIBot_spells[spellslot].spellid; }
uint32 BotGetSpellType(int spellslot) { return AIBot_spells[spellslot].type; }
uint16 BotGetSpellPriority(int spellslot) { return AIBot_spells[spellslot].priority; }
virtual float GetProcChances(float ProcBonus, uint16 hand);
virtual int GetHandToHandDamage(void);
virtual bool TryFinishingBlow(Mob *defender, int64 &damage);
@ -220,7 +220,6 @@ public:
virtual void AddToHateList(Mob* other, int64 hate = 0, int64 damage = 0, bool iYellForHelp = true, bool bFrenzy = false, bool iBuffTic = false, bool pet_command = false);
virtual void SetTarget(Mob* mob);
virtual void Zone();
std::vector<AISpells_Struct> GetBotSpells() { return AIspells; }
bool IsArcheryRange(Mob* target);
void ChangeBotArcherWeapons(bool isArcher);
void Sit();
@ -308,6 +307,9 @@ public:
void DoEnduranceRegen(); //This Regenerates endurance
void DoEnduranceUpkeep(); //does the endurance upkeep
bool AI_AddBotSpells(uint32 iDBSpellsID);
void AddSpellToBotList(int16 iPriority, uint16 iSpellID, uint32 iType, int16 iManaCost, int32 iRecastDelay, int16 iResistAdjust, int8 min_hp, int8 max_hp);
void AI_Bot_Event_SpellCastFinished(bool iCastSucceeded, uint16 slot);
// AI Methods
virtual bool AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes);
virtual bool AI_EngagedCastCheck();
@ -321,6 +323,10 @@ public:
void SetGuardMode();
void SetHoldMode();
// Bot AI Methods
void AI_Bot_Init();
void AI_Bot_Start(uint32 iMoveDelay = 0);
// Mob AI Virtual Override Methods
virtual void AI_Process();
virtual void AI_Stop();
@ -637,7 +643,6 @@ private:
// Class Members
uint32 _botID;
uint32 _botOwnerCharacterID;
//uint32 _botSpellID;
bool _spawnStatus;
Mob* _botOwner;
bool _botOrderAttack;
@ -744,6 +749,8 @@ private:
static uint8 spell_casting_chances[SPELL_TYPE_COUNT][PLAYER_CLASS_COUNT][EQ::constants::STANCE_TYPE_COUNT][cntHSND];
};
bool IsSpellInBotList(DBbotspells_Struct* spell_list, uint16 iSpellID);
#endif // BOTS
#endif // BOT_H

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

View File

@ -2138,11 +2138,11 @@ bool Merc::AICastSpell(int8 iChance, uint32 iSpellTypes) {
}
else {
//check for heal over time. if not present, try it first
if(!tar->FindType(SE_HealOverTime)) {
if (!tar->FindType(SE_HealOverTime)) {
selectedMercSpell = GetBestMercSpellForHealOverTime(this);
//get regular heal
if(selectedMercSpell.spellid == 0) {
if (selectedMercSpell.spellid == 0) {
selectedMercSpell = GetBestMercSpellForRegularSingleTargetHeal(this);
}
}

View File

@ -2988,16 +2988,9 @@ DBnpcspells_Struct *ZoneDatabase::GetNPCSpells(uint32 iDBSpellsID)
query = StringFormat(
"SELECT spellid, type, minlevel, maxlevel, "
"manacost, recast_delay, priority, min_hp, max_hp, resist_adjust "
#ifdef BOTS
"FROM %s "
"WHERE npc_spells_id=%d ORDER BY minlevel",
(iDBSpellsID >= 3001 && iDBSpellsID <= 3016 ? "bot_spells_entries" : "npc_spells_entries"),
iDBSpellsID);
#else
"FROM npc_spells_entries "
"WHERE npc_spells_id=%d ORDER BY minlevel",
iDBSpellsID);
#endif
results = QueryDatabase(query);
if (!results.Success()) {

View File

@ -31,6 +31,7 @@
#include "../common/linked_list.h"
#include "../common/servertalk.h"
#include "../common/say_link.h"
#include "../common/data_verification.h"
#include "client.h"
#include "entity.h"
@ -42,6 +43,10 @@
#include "water_map.h"
#include "npc_scale_manager.h"
#ifdef BOTS
#include "bot.h"
#endif
#include <cctype>
#include <stdio.h>
#include <string>
@ -309,8 +314,16 @@ NPC::NPC(const NPCType *npc_type_data, Spawn2 *in_respawn, const glm::vec4 &posi
AISpellVar.idle_no_sp_recast_max = static_cast<uint32>(RuleI(Spells, AI_IdleNoSpellMaxRecast));
AISpellVar.idle_beneficial_chance = static_cast<uint8> (RuleI(Spells, AI_IdleBeneficialChance));
AI_Init();
AI_Start();
// It's possible for IsBot() to not be set yet during Bot loading, so have to use an alternative to catch Bots
if (!EQ::ValueWithin(npc_type_data->npc_spells_id, EQ::constants::BotSpellIDs::Warrior, EQ::constants::BotSpellIDs::Berserker)) {
AI_Init();
AI_Start();
#ifdef BOTS
} else {
CastToBot()->AI_Bot_Init();
CastToBot()->AI_Bot_Start();
#endif
}
d_melee_texture1 = npc_type_data->d_melee_texture1;
d_melee_texture2 = npc_type_data->d_melee_texture2;

View File

@ -68,6 +68,18 @@ struct AISpells_Struct {
int8 max_hp; // >0 won't cast if HP is above
};
struct BotSpells_Struct {
uint32 type; // 0 = never, must be one (and only one) of the defined values
int16 spellid; // <= 0 = no spell
int16 manacost; // -1 = use spdat, -2 = no cast time
uint32 time_cancast; // when we can cast this spell next
int32 recast_delay;
int16 priority;
int16 resist_adjust;
int16 min_hp; // >0 won't cast if HP is below
int16 max_hp; // >0 won't cast if HP is above
};
struct AISpellsEffects_Struct {
uint16 spelleffectid;
int32 base_value;
@ -585,6 +597,7 @@ protected:
uint32* pDontCastBefore_casting_spell;
std::vector<AISpells_Struct> AIspells;
std::vector<BotSpells_Struct> AIBot_spells; //Will eventually be moved to Bot Class once Bots are no longer reliant on NPC constructor
bool HasAISpell;
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);

View File

@ -1874,7 +1874,8 @@ bool Zone::Depop(bool StartSpawnTimer) {
// clear spell cache
database.ClearNPCSpells();
database.ClearBotSpells();
zone->spawn_group_list.ReloadSpawnGroups();
return true;

View File

@ -107,6 +107,44 @@ struct DBnpcspellseffects_Struct {
DBnpcspellseffects_entries_Struct entries[0];
};
#pragma pack(1)
struct DBbotspells_entries_Struct {
uint16 spellid;
uint8 minlevel;
uint8 maxlevel;
uint32 type;
int16 manacost;
int16 priority;
int32 recast_delay;
int16 resist_adjust;
int8 min_hp;
int8 max_hp;
};
#pragma pack()
struct DBbotspells_Struct {
uint32 parent_list;
uint16 attack_proc;
uint8 proc_chance;
uint16 range_proc;
int16 rproc_chance;
uint16 defensive_proc;
int16 dproc_chance;
uint32 fail_recast;
uint32 engaged_no_sp_recast_min;
uint32 engaged_no_sp_recast_max;
uint8 engaged_beneficial_self_chance;
uint8 engaged_beneficial_other_chance;
uint8 engaged_detrimental_chance;
uint32 pursue_no_sp_recast_min;
uint32 pursue_no_sp_recast_max;
uint8 pursue_detrimental_chance;
uint32 idle_no_sp_recast_min;
uint32 idle_no_sp_recast_max;
uint8 idle_beneficial_chance;
std::vector<DBbotspells_entries_Struct> entries;
};
struct DBTradeskillRecipe_Struct {
EQ::skills::SkillType tradeskill;
int16 skill_needed;
@ -491,6 +529,10 @@ public:
void ClearNPCSpells() { npc_spells_cache.clear(); npc_spells_loadtried.clear(); }
const NPCType* LoadNPCTypesData(uint32 id, bool bulk_load = false);
/*Bots */
DBbotspells_Struct* GetBotSpells(uint32 iDBSpellsID);
void ClearBotSpells() { Bot_Spells_Cache.clear(); Bot_Spells_LoadTried.clear(); }
/* Mercs */
const NPCType* GetMercType(uint32 id, uint16 raceid, uint32 clientlevel);
void LoadMercEquipment(Merc *merc);
@ -595,6 +637,8 @@ protected:
std::unordered_set<uint32> npc_spells_loadtried;
DBnpcspellseffects_Struct** npc_spellseffects_cache;
bool* npc_spellseffects_loadtried;
std::unordered_map<uint32, DBbotspells_Struct> Bot_Spells_Cache;
std::unordered_set<uint32> Bot_Spells_LoadTried;
};
extern ZoneDatabase database;