From ec2e79c04201a0aa347ffcd207c2a17daa3a7e06 Mon Sep 17 00:00:00 2001 From: neckkola <65987027+neckkola@users.noreply.github.com> Date: Sun, 16 Jan 2022 20:22:56 -0400 Subject: [PATCH] More raid spell work --- zone/bot.h | 2 + zone/bot_raid.cpp | 1081 ++++++++++++++++++++++++++++++++++++++++++ zone/botspellsai.cpp | 2 +- zone/raids.h | 1 + 4 files changed, 1085 insertions(+), 1 deletion(-) diff --git a/zone/bot.h b/zone/bot.h index 8062c645a..90ae7e8cb 100644 --- a/zone/bot.h +++ b/zone/bot.h @@ -382,8 +382,10 @@ public: //Raid methods void PetAIProcess_Raid(); void AI_Process_Raid(); + bool AICastSpell_Raid(Mob* tar, uint8 iChance, uint32 iSpellTypes); static void ProcessRaidInvite(Bot* invitee, Client* invitor); //Mitch static void ProcessRaidInvite(Client* invitee, Client* invitor); //Mitch + uint8 GetNumberNeedingHealedInRaidGroup(uint8 hpr, bool includePets); //Mitch static std::list GetBotSpellsForSpellEffect(Bot* botCaster, int spellEffect); static std::list GetBotSpellsForSpellEffectAndTargetType(Bot* botCaster, int spellEffect, SpellTargetType targetType); diff --git a/zone/bot_raid.cpp b/zone/bot_raid.cpp index b695b2de6..936ba9ee9 100644 --- a/zone/bot_raid.cpp +++ b/zone/bot_raid.cpp @@ -257,6 +257,7 @@ void Bot::AI_Process_Raid() if (pull_target) { Bot::BotGroupSay(this, "Pulling %s to the group..", pull_target->GetCleanName()); + //raid->RaidBotGroupSay(this, 0, 100, "Pulling %s to the group..", pull_target->GetCleanName()); InterruptSpell(); WipeHateList(); AddToHateList(pull_target, 1); @@ -1526,5 +1527,1085 @@ std::vector Raid::GetRaidGroupMembers(uint32 gid) return raid_group_members; } +bool Bot::AICastSpell_Raid(Mob* tar, uint8 iChance, uint32 iSpellTypes) { + // Bot AI Raid + + Raid* raid = entity_list.GetRaidByBot(this); + if (!raid) + return false; + + if (!tar) { + return false; + } + + if (!AI_HasSpells()) + return false; + + if (iChance < 100) { + if (zone->random.Int(0, 100) > iChance) { + return false; + } + } + + if (tar->GetAppearance() == eaDead) { + if ((tar->IsClient() && tar->CastToClient()->GetFeigned()) || tar->IsBot()) { + // do nothing + } + else { + return false; + } + } + + uint8 botClass = GetClass(); + uint8 botLevel = GetLevel(); + + bool checked_los = false; //we do not check LOS until we are absolutely sure we need to, and we only do it once. + + bool castedSpell = false; + + BotSpell botSpell; + botSpell.SpellId = 0; + botSpell.SpellIndex = 0; + botSpell.ManaCost = 0; + + switch (iSpellTypes) { + case SpellType_Mez: { + if (tar->GetBodyType() != BT_Giant) { + if (!checked_los) { + if (!CheckLosFN(tar)) + break; //cannot see target... we assume that no spell is going to work since we will only be casting detrimental spells in this call + + checked_los = true; + } + + //TODO + //Check if single target or AoE mez is best + //if (TARGETS ON MT IS => 3 THEN botSpell = AoEMez) + //if (TARGETS ON MT IS <= 2 THEN botSpell = BestMez) + + botSpell = GetBestBotSpellForMez(this); + + if (botSpell.SpellId == 0) + break; + + Mob* addMob = GetFirstIncomingMobToMez(this, botSpell); + + if (!addMob) { + //Say("!addMob."); + break; + } + + if (!(!addMob->IsImmuneToSpell(botSpell.SpellId, this) && addMob->CanBuffStack(botSpell.SpellId, botLevel, true) >= 0)) + break; + + castedSpell = AIDoSpellCast(botSpell.SpellIndex, addMob, botSpell.ManaCost); + + if (castedSpell) + BotGroupSay(this, "Attempting to mez %s.", addMob->GetCleanName()); + //raid->RaidBotGroupSay(this, 0, 100, "Attempting to mez %s.", addMob->GetCleanName()); + } + break; + } + case SpellType_Heal: { + if (tar->DontHealMeBefore() < Timer::GetCurrentTime()) { + uint8 hpr = (uint8)tar->GetHPRatio(); + bool hasAggro = false; + bool isPrimaryHealer = false; + + if (this->IsRaidGrouped()) { + isPrimaryHealer = IsGroupHealer(); + } + + if (hpr < 95 || (tar->IsClient() && (hpr < 95)) || (botClass == BARD)) { + if (tar->GetClass() == NECROMANCER) { + // Give necromancers a chance to go lifetap something or cleric can spend too much mana on a necro + if (hpr >= 40) { + break; + } + } + + if (tar->GetClass() == SHAMAN) { + // Give shaman the chance to canni without wasting the cleric's mana + if (hpr >= 80) { + break; + } + } + + // Evaluate the situation + if ((IsEngaged()) && ((botClass == CLERIC) || (botClass == DRUID) || (botClass == SHAMAN) || (botClass == PALADIN))) { + if (tar->GetTarget() && tar->GetTarget()->GetHateTop() && tar->GetTarget()->GetHateTop() == tar) { + hasAggro = true; + } + + if (hpr < 35) { + botSpell = GetBestBotSpellForFastHeal(this); + } + else if (hpr >= 35 && hpr < 70) { + if (GetNumberNeedingHealedInRaidGroup(60, false) >= 3) + botSpell = GetBestBotSpellForGroupHeal(this); + + if (botSpell.SpellId == 0) + botSpell = GetBestBotSpellForPercentageHeal(this); + } + else if (hpr >= 70 && hpr < 95) { + if (GetNumberNeedingHealedInRaidGroup(80, false) >= 3) + botSpell = GetBestBotSpellForGroupHealOverTime(this); + + if (hasAggro) + botSpell = GetBestBotSpellForPercentageHeal(this); + } + else { + if (!tar->FindType(SE_HealOverTime)) + botSpell = GetBestBotSpellForHealOverTime(this); + } + } + else if ((botClass == CLERIC) || (botClass == DRUID) || (botClass == SHAMAN) || (botClass == PALADIN)) { + if (GetNumberNeedingHealedInRaidGroup(40, true) >= 2) { + botSpell = GetBestBotSpellForGroupCompleteHeal(this); + + if (botSpell.SpellId == 0) + botSpell = GetBestBotSpellForGroupHeal(this); + + if (botSpell.SpellId == 0) + botSpell = GetBestBotSpellForGroupHealOverTime(this); + + if (hpr < 40) { + if (botSpell.SpellId == 0) + botSpell = GetBestBotSpellForPercentageHeal(this); + } + } + else if (GetNumberNeedingHealedInRaidGroup(60, true) >= 2) { + botSpell = GetBestBotSpellForGroupHeal(this); + + if (botSpell.SpellId == 0) + botSpell = GetBestBotSpellForGroupHealOverTime(this); + + if (hpr < 40) { + if (botSpell.SpellId == 0) + botSpell = GetBestBotSpellForPercentageHeal(this); + } + } + else if (hpr < 40) + botSpell = GetBestBotSpellForPercentageHeal(this); + else if (hpr >= 40 && hpr < 75) + botSpell = GetBestBotSpellForRegularSingleTargetHeal(this); + else { + if (hpr < 90 && !tar->FindType(SE_HealOverTime)) + botSpell = GetBestBotSpellForHealOverTime(this); + } + } + else { + float hpRatioToCast = 0.0f; + + switch (this->GetBotStance()) { + case EQ::constants::stanceEfficient: + case EQ::constants::stanceAggressive: + hpRatioToCast = isPrimaryHealer ? 90.0f : 50.0f; + break; + case EQ::constants::stanceBalanced: + hpRatioToCast = isPrimaryHealer ? 95.0f : 75.0f; + break; + case EQ::constants::stanceReactive: + hpRatioToCast = isPrimaryHealer ? 100.0f : 90.0f; + break; + case EQ::constants::stanceBurn: + case EQ::constants::stanceBurnAE: + hpRatioToCast = isPrimaryHealer ? 75.0f : 25.0f; + break; + default: + hpRatioToCast = isPrimaryHealer ? 100.0f : 0.0f; + break; + } + + //If we're at specified mana % or below, don't heal as hybrid + if (tar->GetHPRatio() <= hpRatioToCast) + botSpell = GetBestBotSpellForRegularSingleTargetHeal(this); + } + + if (botSpell.SpellId == 0) + botSpell = GetBestBotSpellForRegularSingleTargetHeal(this); + + if (botSpell.SpellId == 0) + botSpell = GetFirstBotSpellForSingleTargetHeal(this); + + if (botSpell.SpellId == 0 && botClass == BARD) { + botSpell = GetFirstBotSpellBySpellType(this, SpellType_Heal); + } + + // If there is still no spell id, then there isn't going to be one so we are done + if (botSpell.SpellId == 0) + break; + + // Can we cast this spell on this target? + if (!(spells[botSpell.SpellId].targettype == ST_GroupTeleport || spells[botSpell.SpellId].targettype == ST_Target || tar == this) + && !(tar->CanBuffStack(botSpell.SpellId, botLevel, true) >= 0)) + break; + + uint32 TempDontHealMeBeforeTime = tar->DontHealMeBefore(); + + castedSpell = AIDoSpellCast(botSpell.SpellIndex, tar, botSpell.ManaCost, &TempDontHealMeBeforeTime); + + if (castedSpell) { + /*if(TempDontHealMeBeforeTime != tar->DontHealMeBefore()) + tar->SetDontHealMeBefore(TempDontHealMeBeforeTime); + + // For non-HoT heals, do a 4 second delay + // TODO: Replace this code with logic that calculates the delay based on number of clerics in rotation + // and ignores heals for anyone except the main tank + if(!IsHealOverTimeSpell(botSpell.SpellId)) { + if(IsCompleteHealSpell(botSpell.SpellId)) { + // Complete Heal 4 second rotation + tar->SetDontHealMeBefore(Timer::GetCurrentTime() + 4000); + } + else { + tar->SetDontHealMeBefore(Timer::GetCurrentTime() + 1000); + } + }*/ + if (botClass != BARD) { + if (IsGroupSpell(botSpell.SpellId)) { + if (IsRaidGrouped()) { + uint32 r_group = raid->GetGroup(GetName()); + std::vector raid_group_members = raid->GetRaidGroupMembers(r_group); + BotGroupSay(this, "Casting %s.", spells[botSpell.SpellId].name); + //raid->RaidBotGroupSay(this, 0, 100, "Casting %s.", spells[botSpell.SpellId].name); + for (RaidMember iter : raid_group_members) { + if (iter.member && !iter.member->qglobal) { + iter.member->SetDontHealMeBefore(Timer::GetCurrentTime() + 1000); + } + } + } + } + else { + if (tar != this) //we don't need spam of bots healing themselves + BotGroupSay(this, "Casting %s on %s", spells[botSpell.SpellId].name, tar->GetCleanName()); + tar->SetDontHealMeBefore(Timer::GetCurrentTime() + 2000); + } + } + } + } + } + break; + } + case SpellType_Root: { + if (!tar->IsRooted() && tar->DontRootMeBefore() < Timer::GetCurrentTime()) { + if (!checked_los) { + if (!CheckLosFN(tar)) + break; //cannot see target... we assume that no spell is going to work since we will only be casting detrimental spells in this call + + checked_los = true; + } + + // TODO: If there is a ranger in the group then don't allow root spells + + botSpell = GetFirstBotSpellBySpellType(this, iSpellTypes); + + if (botSpell.SpellId == 0) + break; + + if (tar->CanBuffStack(botSpell.SpellId, botLevel, true) == 0) + break; + + uint32 TempDontRootMeBefore = tar->DontRootMeBefore(); + + castedSpell = AIDoSpellCast(botSpell.SpellIndex, tar, botSpell.ManaCost, &TempDontRootMeBefore); + + if (TempDontRootMeBefore != tar->DontRootMeBefore()) + tar->SetDontRootMeBefore(TempDontRootMeBefore); + } + break; + } + case SpellType_Buff: { + if (tar->DontBuffMeBefore() < Timer::GetCurrentTime()) { + std::list buffSpellList = GetBotSpellsBySpellType(this, SpellType_Buff); + + for (std::list::iterator itr = buffSpellList.begin(); itr != buffSpellList.end(); ++itr) { + BotSpell selectedBotSpell = *itr; + + if (selectedBotSpell.SpellId == 0) + continue; + + // no buffs with illusions.. use #bot command to cast illusions + if (IsEffectInSpell(selectedBotSpell.SpellId, SE_Illusion) && tar != this) + continue; + + //no teleport spells use #bot command to cast teleports + if (IsEffectInSpell(selectedBotSpell.SpellId, SE_Teleport) || IsEffectInSpell(selectedBotSpell.SpellId, SE_Succor)) + continue; + + // can not cast buffs for your own pet only on another pet that isn't yours + if ((spells[selectedBotSpell.SpellId].targettype == ST_Pet) && (tar != this->GetPet())) + continue; + + // Validate target + + if (!((spells[selectedBotSpell.SpellId].targettype == ST_Target || spells[selectedBotSpell.SpellId].targettype == ST_Pet || tar == this || + spells[selectedBotSpell.SpellId].targettype == ST_Group || spells[selectedBotSpell.SpellId].targettype == ST_GroupTeleport || + (botClass == BARD && spells[selectedBotSpell.SpellId].targettype == ST_AEBard)) + && !tar->IsImmuneToSpell(selectedBotSpell.SpellId, this) + && (tar->CanBuffStack(selectedBotSpell.SpellId, botLevel, true) >= 0))) { + continue; + } + + // 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()) + || (IsEffectInSpell(selectedBotSpell.SpellId, SE_MovementSpeed) && !zone->CanCastOutdoor())) { + if (botClass != BARD || !IsSpellUsableThisZoneType(selectedBotSpell.SpellId, zone->GetZoneType())) { + continue; + } + } + + switch (tar->GetArchetype()) + { + case ARCHETYPE_CASTER: + //TODO: probably more caster specific spell effects in here + if (IsEffectInSpell(selectedBotSpell.SpellId, SE_AttackSpeed) || IsEffectInSpell(selectedBotSpell.SpellId, SE_ATK) || + IsEffectInSpell(selectedBotSpell.SpellId, SE_STR) || IsEffectInSpell(selectedBotSpell.SpellId, SE_ReverseDS)) + { + continue; + } + break; + case ARCHETYPE_MELEE: + if (IsEffectInSpell(selectedBotSpell.SpellId, SE_IncreaseSpellHaste) || IsEffectInSpell(selectedBotSpell.SpellId, SE_ManaPool) || + IsEffectInSpell(selectedBotSpell.SpellId, SE_CastingLevel) || IsEffectInSpell(selectedBotSpell.SpellId, SE_ManaRegen_v2) || + IsEffectInSpell(selectedBotSpell.SpellId, SE_CurrentMana)) + { + continue; + } + break; + case ARCHETYPE_HYBRID: + //Hybrids get all buffs + default: + break; + } + + if (botClass == ENCHANTER && IsEffectInSpell(selectedBotSpell.SpellId, SE_Rune)) + { + float manaRatioToCast = 75.0f; + + switch (this->GetBotStance()) { + case EQ::constants::stanceEfficient: + manaRatioToCast = 90.0f; + break; + case EQ::constants::stanceBalanced: + case EQ::constants::stanceAggressive: + manaRatioToCast = 75.0f; + break; + case EQ::constants::stanceReactive: + case EQ::constants::stanceBurn: + case EQ::constants::stanceBurnAE: + manaRatioToCast = 50.0f; + break; + default: + manaRatioToCast = 75.0f; + break; + } + + //If we're at specified mana % or below, don't rune as enchanter + if (this->GetManaRatio() <= manaRatioToCast) + break; + } + + if (CheckSpellRecastTimers(this, itr->SpellIndex)) + { + + uint32 TempDontBuffMeBefore = tar->DontBuffMeBefore(); + + castedSpell = AIDoSpellCast(selectedBotSpell.SpellIndex, tar, selectedBotSpell.ManaCost, &TempDontBuffMeBefore); + + if (TempDontBuffMeBefore != tar->DontBuffMeBefore()) + tar->SetDontBuffMeBefore(TempDontBuffMeBefore); + } + + if (castedSpell) + break; + } + } + break; + } + case SpellType_Escape: { + uint8 hpr = (uint8)GetHPRatio(); + bool mayGetAggro = false; + +#ifdef IPC + if (hpr <= 5 || (IsNPC() && CastToNPC()->IsInteractive() && tar != this)) +#else + if (hpr > 15 && ((botClass == WIZARD) || (botClass == ENCHANTER) || (botClass == RANGER))) + mayGetAggro = HasOrMayGetAggro(); //classes have hate reducing spells + + if (hpr <= 15 || mayGetAggro) +#endif + { + botSpell = GetFirstBotSpellBySpellType(this, iSpellTypes); + + if (botSpell.SpellId == 0) + break; + + if (IsInvulnerabilitySpell(botSpell.SpellId)) + tar = this; //target self for invul type spells + + castedSpell = AIDoSpellCast(botSpell.SpellIndex, tar, botSpell.ManaCost); + } + break; + } + case SpellType_Nuke: { + if ((tar->GetHPRatio() <= 95.0f) || ((botClass == BARD) || (botClass == SHAMAN) || (botClass == ENCHANTER))) + { + if (!checked_los) { + if (!CheckLosFN(tar)) + break; //cannot see target... we assume that no spell is going to work since we will only be casting detrimental spells in this call + + checked_los = true; + } + + if (botClass == CLERIC || botClass == ENCHANTER) + { + float manaRatioToCast = 75.0f; + + switch (this->GetBotStance()) { + case EQ::constants::stanceEfficient: + manaRatioToCast = 90.0f; + break; + case EQ::constants::stanceBalanced: + manaRatioToCast = 75.0f; + break; + case EQ::constants::stanceReactive: + case EQ::constants::stanceAggressive: + manaRatioToCast = 50.0f; + break; + case EQ::constants::stanceBurn: + case EQ::constants::stanceBurnAE: + manaRatioToCast = 25.0f; + break; + default: + manaRatioToCast = 50.0f; + break; + } + + //If we're at specified mana % or below, don't nuke as cleric or enchanter + if (this->GetManaRatio() <= manaRatioToCast) + break; + } + + if (botClass == MAGICIAN || botClass == SHADOWKNIGHT || botClass == NECROMANCER || botClass == PALADIN || botClass == RANGER || botClass == DRUID || botClass == CLERIC) { + if (tar->GetBodyType() == BT_Undead || tar->GetBodyType() == BT_SummonedUndead || tar->GetBodyType() == BT_Vampire) + botSpell = GetBestBotSpellForNukeByTargetType(this, ST_Undead); + else if (tar->GetBodyType() == BT_Summoned || tar->GetBodyType() == BT_Summoned2 || tar->GetBodyType() == BT_Summoned3) + botSpell = GetBestBotSpellForNukeByTargetType(this, ST_Summoned); + } + + if (botClass == PALADIN || botClass == DRUID || botClass == CLERIC || botClass == ENCHANTER || botClass == WIZARD) { + if (botSpell.SpellId == 0) { + uint8 stunChance = (tar->IsCasting() ? 30 : 15); + + if (botClass == PALADIN) + stunChance = 50; + + if (!tar->GetSpecialAbility(UNSTUNABLE) && !tar->IsStunned() && (zone->random.Int(1, 100) <= stunChance)) { + botSpell = GetBestBotSpellForStunByTargetType(this, ST_Target); + } + } + } + + if (botClass == WIZARD && botSpell.SpellId == 0) { + botSpell = GetBestBotWizardNukeSpellByTargetResists(this, tar); + } + + if (botSpell.SpellId == 0) + botSpell = GetBestBotSpellForNukeByTargetType(this, ST_Target); + + if (botSpell.SpellId == 0) + break; + + if (!(!tar->IsImmuneToSpell(botSpell.SpellId, this) && (tar->CanBuffStack(botSpell.SpellId, botLevel, true) >= 0))) + break; + + if (IsFearSpell(botSpell.SpellId)) { + // don't let fear cast if the npc isn't snared or rooted + if (tar->GetSnaredAmount() == -1) { + if (!tar->IsRooted()) + break; + } + } + + castedSpell = AIDoSpellCast(botSpell.SpellIndex, tar, botSpell.ManaCost); + } + break; + } + case SpellType_Dispel: { + if (tar->GetHPRatio() > 95.0f) { + if (!checked_los) { + if (!CheckLosFN(tar)) + break; //cannot see target... we assume that no spell is going to work since we will only be casting detrimental spells in this call + + checked_los = true; + } + + botSpell = GetFirstBotSpellBySpellType(this, iSpellTypes); + + if (botSpell.SpellId == 0) + break; + + // TODO: Check target to see if there is anything to dispel + + if (tar->CountDispellableBuffs() > 0) { + castedSpell = AIDoSpellCast(botSpell.SpellIndex, tar, botSpell.ManaCost); + } + } + break; + } + case SpellType_Pet: { + //keep mobs from recasting pets when they have them. + if (!IsPet() && !GetPetID() && !IsBotCharmer()) { + if (botClass == WIZARD) { + auto buffs_max = GetMaxBuffSlots(); + auto my_buffs = GetBuffs(); + int familiar_buff_slot = -1; + if (buffs_max && my_buffs) { + for (int index = 0; index < buffs_max; ++index) { + if (IsEffectInSpell(my_buffs[index].spellid, SE_Familiar)) { + MakePet(my_buffs[index].spellid, spells[my_buffs[index].spellid].teleport_zone); + familiar_buff_slot = index; + break; + } + } + } + if (GetPetID()) + break; + + if (familiar_buff_slot >= 0) { + BuffFadeBySlot(familiar_buff_slot); + break; + } + + botSpell = GetFirstBotSpellBySpellType(this, SpellType_Pet); + } + else if (botClass == MAGICIAN) { + botSpell = GetBestBotMagicianPetSpell(this); + } + else { + botSpell = GetFirstBotSpellBySpellType(this, SpellType_Pet); + } + + if (botSpell.SpellId == 0) + break; + + castedSpell = AIDoSpellCast(botSpell.SpellIndex, tar, botSpell.ManaCost); + } + break; + } + case SpellType_InCombatBuff: { + + if (botClass == SHAMAN) { + checked_los = true; + + std::list inCombatBuffList = GetBotSpellsBySpellType(this, SpellType_InCombatBuff); + + for (std::list::iterator itr = inCombatBuffList.begin(); itr != inCombatBuffList.end(); ++itr) { + BotSpell selectedBotSpell = *itr; + + if (selectedBotSpell.SpellId == 0) + continue; + + if (CheckSpellRecastTimers(this, itr->SpellIndex)) + { + if (!(!tar->IsImmuneToSpell(selectedBotSpell.SpellId, this) && (spells[selectedBotSpell.SpellId].buffduration < 1 || tar->CanBuffStack(selectedBotSpell.SpellId, botLevel, true) >= 0))) + continue; + + //short duration buffs or other buffs only to be cast during combat. + if (IsSelfConversionSpell(selectedBotSpell.SpellId)) { + if (GetManaRatio() > 90.0f || GetHPRatio() < 50.0f || GetHPRatio() < (GetManaRatio() + 10.0f)) + break; //don't cast if low hp, lots of mana, or if mana is higher than hps + } + + castedSpell = AIDoSpellCast(selectedBotSpell.SpellIndex, tar, selectedBotSpell.ManaCost); + } + + if (castedSpell) + break; + } + } + else if (botClass == BARD) { + if (tar->DontBuffMeBefore() < Timer::GetCurrentTime()) { + std::list inCombatBuffList = GetBotSpellsBySpellType(this, SpellType_InCombatBuff); + + for (std::list::iterator itr = inCombatBuffList.begin(); itr != inCombatBuffList.end(); ++itr) { + BotSpell selectedBotSpell = *itr; + + if (selectedBotSpell.SpellId == 0) + continue; + + if (CheckSpellRecastTimers(this, itr->SpellIndex)) { + uint32 TempDontBuffMeBefore = tar->DontBuffMeBefore(); + + // no buffs with illusions.. use #bot command to cast illusions + if (IsEffectInSpell(selectedBotSpell.SpellId, SE_Illusion) && tar != this) + continue; + + //no teleport spells use #bot command to cast teleports + if (IsEffectInSpell(selectedBotSpell.SpellId, SE_Teleport) || IsEffectInSpell(selectedBotSpell.SpellId, SE_Succor)) + continue; + + // can not cast buffs for your own pet only on another pet that isn't yours + if ((spells[selectedBotSpell.SpellId].targettype == ST_Pet) && (tar != this->GetPet())) + continue; + + // Validate target + + if (!((spells[selectedBotSpell.SpellId].targettype == ST_Target || spells[selectedBotSpell.SpellId].targettype == ST_Pet || tar == this || + spells[selectedBotSpell.SpellId].targettype == ST_Group || spells[selectedBotSpell.SpellId].targettype == ST_GroupTeleport || + (botClass == BARD && spells[selectedBotSpell.SpellId].targettype == ST_AEBard)) + && !tar->IsImmuneToSpell(selectedBotSpell.SpellId, this) + && (tar->CanBuffStack(selectedBotSpell.SpellId, botLevel, true) >= 0))) { + continue; + } + + // 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()) + || (IsEffectInSpell(selectedBotSpell.SpellId, SE_MovementSpeed) && !zone->CanCastOutdoor())) { + if (!IsSpellUsableThisZoneType(selectedBotSpell.SpellId, zone->GetZoneType())) { + continue; + } + } + + if (!IsGroupSpell(selectedBotSpell.SpellId)) { + //Only check archetype if song is not a group spell + switch (tar->GetArchetype()) { + case ARCHETYPE_CASTER: + //TODO: probably more caster specific spell effects in here + if (IsEffectInSpell(selectedBotSpell.SpellId, SE_AttackSpeed) || IsEffectInSpell(selectedBotSpell.SpellId, SE_ATK) || + IsEffectInSpell(selectedBotSpell.SpellId, SE_STR) || IsEffectInSpell(selectedBotSpell.SpellId, SE_ReverseDS)) + { + continue; + } + break; + case ARCHETYPE_MELEE: + if (IsEffectInSpell(selectedBotSpell.SpellId, SE_IncreaseSpellHaste) || IsEffectInSpell(selectedBotSpell.SpellId, SE_ManaPool) || + IsEffectInSpell(selectedBotSpell.SpellId, SE_CastingLevel) || IsEffectInSpell(selectedBotSpell.SpellId, SE_ManaRegen_v2) || + IsEffectInSpell(selectedBotSpell.SpellId, SE_CurrentMana)) + { + continue; + } + break; + case ARCHETYPE_HYBRID: + //Hybrids get all buffs + default: + break; + } + } + + castedSpell = AIDoSpellCast(selectedBotSpell.SpellIndex, tar, selectedBotSpell.ManaCost, &TempDontBuffMeBefore); + + if (TempDontBuffMeBefore != tar->DontBuffMeBefore()) + tar->SetDontBuffMeBefore(TempDontBuffMeBefore); + } + + if (castedSpell) + break; + } + } + } + break; + } + case SpellType_Lifetap: { + if (GetHPRatio() < 90.0f) { + if (!checked_los) { + if (!CheckLosFN(tar)) + break; //cannot see target... we assume that no spell is going to work since we will only be casting detrimental spells in this call + + checked_los = true; + } + + botSpell = GetFirstBotSpellBySpellType(this, iSpellTypes); + + if (botSpell.SpellId == 0) + break; + + if (!(!tar->IsImmuneToSpell(botSpell.SpellId, this) && (tar->CanBuffStack(botSpell.SpellId, botLevel, true) >= 0))) + break; + + castedSpell = AIDoSpellCast(botSpell.SpellIndex, tar, botSpell.ManaCost); + } + break; + } + case SpellType_Snare: { + if (tar->DontSnareMeBefore() < Timer::GetCurrentTime()) { + if (!checked_los) { + if (!CheckLosFN(tar)) + break; //cannot see target... we assume that no spell is going to work since we will only be casting detrimental spells in this call + + checked_los = true; + } + + botSpell = GetFirstBotSpellBySpellType(this, iSpellTypes); + + if (botSpell.SpellId == 0) + break; + + if (!(!tar->IsImmuneToSpell(botSpell.SpellId, this) && tar->CanBuffStack(botSpell.SpellId, botLevel, true) >= 0)) + break; + + uint32 TempDontSnareMeBefore = tar->DontSnareMeBefore(); + + castedSpell = AIDoSpellCast(botSpell.SpellIndex, tar, botSpell.ManaCost, &TempDontSnareMeBefore); + + if (TempDontSnareMeBefore != tar->DontSnareMeBefore()) + tar->SetDontSnareMeBefore(TempDontSnareMeBefore); + } + break; + } + case SpellType_DOT: { + if ((tar->GetHPRatio() <= 98.0f) && (tar->DontDotMeBefore() < Timer::GetCurrentTime()) && (tar->GetHPRatio() > 15.0f)) { + if (!checked_los) { + if (!CheckLosFN(tar)) + break; //cannot see target... we assume that no spell is going to work since we will only be casting detrimental spells in this call + + checked_los = true; + } + + if (GetClass() == BARD) { + std::list dotList = GetPrioritizedBotSpellsBySpellType(this, SpellType_DOT); + + const int maxDotSelect = 5; + int dotSelectCounter = 0; + + for (std::list::iterator itr = dotList.begin(); itr != dotList.end(); ++itr) { + BotSpell selectedBotSpell = *itr; + + if (selectedBotSpell.SpellId == 0) + continue; + + if (CheckSpellRecastTimers(this, itr->SpellIndex)) + { + + if (!(!tar->IsImmuneToSpell(selectedBotSpell.SpellId, this) && tar->CanBuffStack(selectedBotSpell.SpellId, botLevel, true) >= 0)) + continue; + + uint32 TempDontDotMeBefore = tar->DontDotMeBefore(); + + castedSpell = AIDoSpellCast(selectedBotSpell.SpellIndex, tar, selectedBotSpell.ManaCost, &TempDontDotMeBefore); + + if (TempDontDotMeBefore != tar->DontDotMeBefore()) + tar->SetDontDotMeBefore(TempDontDotMeBefore); + } + + dotSelectCounter++; + + if ((dotSelectCounter == maxDotSelect) || castedSpell) + break; + } + } + else { + std::list dotList = GetBotSpellsBySpellType(this, SpellType_DOT); + + const int maxDotSelect = 5; + int dotSelectCounter = 0; + + for (std::list::iterator itr = dotList.begin(); itr != dotList.end(); ++itr) { + BotSpell selectedBotSpell = *itr; + + if (selectedBotSpell.SpellId == 0) + continue; + + if (CheckSpellRecastTimers(this, itr->SpellIndex)) + { + + if (!(!tar->IsImmuneToSpell(selectedBotSpell.SpellId, this) && tar->CanBuffStack(selectedBotSpell.SpellId, botLevel, true) >= 0)) + continue; + + uint32 TempDontDotMeBefore = tar->DontDotMeBefore(); + + castedSpell = AIDoSpellCast(selectedBotSpell.SpellIndex, tar, selectedBotSpell.ManaCost, &TempDontDotMeBefore); + + if (TempDontDotMeBefore != tar->DontDotMeBefore()) + tar->SetDontDotMeBefore(TempDontDotMeBefore); + } + + dotSelectCounter++; + + if ((dotSelectCounter == maxDotSelect) || castedSpell) + break; + } + } + } + break; + } + case SpellType_Slow: { + if (tar->GetHPRatio() <= 99.0f) { + + if (!checked_los) { + if (!CheckLosFN(tar)) + break; //cannot see target... we assume that no spell is going to work since we will only be casting detrimental spells in this call + + checked_los = true; + } + + switch (botClass) { + case BARD: { + // probably needs attackable check + std::list botSongList = GetPrioritizedBotSpellsBySpellType(this, SpellType_Slow); + for (auto iter : botSongList) { + if (!iter.SpellId) + continue; + if (!CheckSpellRecastTimers(this, iter.SpellIndex)) + continue; + if (spells[iter.SpellId].zonetype != -1 && zone->GetZoneType() != -1 && spells[iter.SpellId].zonetype != zone->GetZoneType()) // is this bit or index? + continue; + if (spells[iter.SpellId].targettype != ST_Target) + continue; + if (tar->CanBuffStack(iter.SpellId, botLevel, true) < 0) + continue; + + castedSpell = AIDoSpellCast(iter.SpellIndex, tar, iter.ManaCost); + if (castedSpell) + break; + } + + break; + } + case ENCHANTER: { + botSpell = GetBestBotSpellForMagicBasedSlow(this); + break; + } + case SHAMAN: + case BEASTLORD: { + botSpell = GetBestBotSpellForDiseaseBasedSlow(this); + + if (botSpell.SpellId == 0 || ((tar->GetMR() - 50) < (tar->GetDR() + spells[botSpell.SpellId].ResistDiff))) + botSpell = GetBestBotSpellForMagicBasedSlow(this); + break; + } + } + + if (botSpell.SpellId == 0) + break; + + if (!(!tar->IsImmuneToSpell(botSpell.SpellId, this) && tar->CanBuffStack(botSpell.SpellId, botLevel, true) >= 0)) + break; + + castedSpell = AIDoSpellCast(botSpell.SpellIndex, tar, botSpell.ManaCost); + + if (castedSpell && GetClass() != BARD) + BotGroupSay(this, "Attempting to slow %s.", tar->GetCleanName()); + } + break; + } + case SpellType_Debuff: { + if ((tar->GetHPRatio() <= 99.0f) || ((botClass == BARD) || (botClass == SHAMAN) || (botClass == ENCHANTER) || (botClass == DRUID)) && (tar->GetHPRatio() > 40.0f)) + { + if (!checked_los) { + if (!CheckLosFN(tar)) + break; //cannot see target... we assume that no spell is going to work since we will only be casting detrimental spells in this call + + checked_los = true; + } + + botSpell = GetBestBotSpellForResistDebuff(this, tar); + + if (botSpell.SpellId == 0) + botSpell = GetDebuffBotSpell(this, tar); + + if (botSpell.SpellId == 0) + break; + + if (!(!tar->IsImmuneToSpell(botSpell.SpellId, this) && (tar->CanBuffStack(botSpell.SpellId, botLevel, true) >= 0))) + break; + + castedSpell = AIDoSpellCast(botSpell.SpellIndex, tar, botSpell.ManaCost); + } + break; + } + case SpellType_Cure: { + if (GetNeedsCured(tar) && (tar->DontCureMeBefore() < Timer::GetCurrentTime()) && !(GetNumberNeedingHealedInGroup(25, false) > 0) && !(GetNumberNeedingHealedInGroup(40, false) > 2)) + { + botSpell = GetBestBotSpellForCure(this, tar); + + 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)) { + Group* g; + + if (this->HasGroup()) { + Group* g = this->GetGroup(); + + 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); + } + } + } + } + } + else { + if (TempDontCureMeBeforeTime != tar->DontCureMeBefore()) + tar->SetDontCureMeBefore(Timer::GetCurrentTime() + 4000); + } + } + } + } + break; + } + case SpellType_Resurrect: + break; + case SpellType_HateRedux: { + // assumed group member at this point + if (GetClass() == BARD) { + std::list botSongList = GetPrioritizedBotSpellsBySpellType(this, SpellType_HateRedux); + for (auto iter : botSongList) { + if (!iter.SpellId) + continue; + if (!CheckSpellRecastTimers(this, iter.SpellIndex)) + continue; + if (spells[iter.SpellId].zonetype != -1 && zone->GetZoneType() != -1 && spells[iter.SpellId].zonetype != zone->GetZoneType()) // is this bit or index? + continue; + if (spells[iter.SpellId].targettype != ST_Target) + continue; + if (tar->CanBuffStack(iter.SpellId, botLevel, true) < 0) + continue; + + castedSpell = AIDoSpellCast(iter.SpellIndex, tar, iter.ManaCost); + if (castedSpell) { + BotGroupSay(this, "Attempting to reduce hate on %s.", tar->GetCleanName()); + break; + } + } + } + + break; + } + case SpellType_InCombatBuffSong: { + if (GetClass() != BARD || tar != this) // In-Combat songs can be cast Out-of-Combat in preparation for battle + break; + + std::list botSongList = GetPrioritizedBotSpellsBySpellType(this, SpellType_InCombatBuffSong); + for (auto iter : botSongList) { + if (!iter.SpellId) + continue; + if (!CheckSpellRecastTimers(this, iter.SpellIndex)) + continue; + if (spells[iter.SpellId].zonetype != -1 && zone->GetZoneType() != -1 && spells[iter.SpellId].zonetype != zone->GetZoneType()) // is this bit or index? + continue; + switch (spells[iter.SpellId].targettype) { + case ST_AEBard: + case ST_AECaster: + case ST_GroupTeleport: + case ST_Group: + case ST_Self: + break; + default: + continue; + } + if (tar->CanBuffStack(iter.SpellId, botLevel, true) < 0) + continue; + + castedSpell = AIDoSpellCast(iter.SpellIndex, tar, iter.ManaCost); + if (castedSpell) + break; + } + + break; + } + case SpellType_OutOfCombatBuffSong: { + if (GetClass() != BARD || tar != this || IsEngaged()) // Out-of-Combat songs can not be cast in combat + break; + + std::list botSongList = GetPrioritizedBotSpellsBySpellType(this, SpellType_OutOfCombatBuffSong); + for (auto iter : botSongList) { + if (!iter.SpellId) + continue; + if (!CheckSpellRecastTimers(this, iter.SpellIndex)) + continue; + if (spells[iter.SpellId].zonetype != -1 && zone->GetZoneType() != -1 && spells[iter.SpellId].zonetype != zone->GetZoneType()) // is this bit or index? + continue; + switch (spells[iter.SpellId].targettype) { + case ST_AEBard: + case ST_AECaster: + case ST_GroupTeleport: + case ST_Group: + case ST_Self: + break; + default: + continue; + } + if (tar->CanBuffStack(iter.SpellId, botLevel, true) < 0) + continue; + + castedSpell = AIDoSpellCast(iter.SpellIndex, tar, iter.ManaCost); + if (castedSpell) + break; + } + + break; + } + case SpellType_PreCombatBuff: { + break; + } + case SpellType_PreCombatBuffSong: { + break; + } + default: + break; + } + + return castedSpell; +} + +void Raid::RaidBotGroupSay(Bot* b, uint8 language, uint8 lang_skill, const char* msg, ...) +{ + if (!b) + return; + + char buf[1000]; + va_list ap; + va_start(ap, msg); + vsnprintf(buf, 1000, msg, ap); + va_end(ap); + + uint32 groupToUse = GetGroup(b->GetName()); + + if (groupToUse > 11) + return; + + auto pack = new ServerPacket(ServerOP_RaidGroupSay, sizeof(ServerRaidMessage_Struct) + strlen(msg) + 1); + ServerRaidMessage_Struct* rga = (ServerRaidMessage_Struct*)pack->pBuffer; + rga->rid = GetID(); + rga->gid = groupToUse; + rga->language = language; + rga->lang_skill = lang_skill; + strn0cpy(rga->from, b->GetName(), 64); + + strcpy(rga->message, buf); // this is safe because we are allocating enough space for the entire msg above + + worldserver.SendPacket(pack); + //safe_delete(pack); +} + +uint8 Bot::GetNumberNeedingHealedInRaidGroup(uint8 hpr, bool includePets) { + uint8 needHealed = 0; + Raid* raid = nullptr; + raid = entity_list.GetRaidByBot(this); + uint32 r_group = raid->GetGroup(this->GetName()); + std::vector raid_group_members = raid->GetRaidGroupMembers(r_group); + for (RaidMember iter : raid_group_members) { + if (iter.member && !iter.member->qglobal) { + if (iter.member->GetHPRatio() <= hpr) + needHealed++; + + if (includePets) { + if (iter.member->GetPet() && iter.member->GetPet()->GetHPRatio() <= hpr) + needHealed++; + } + } + } + return needHealed; +} #endif diff --git a/zone/botspellsai.cpp b/zone/botspellsai.cpp index 5dd7569de..c2e04f17d 100644 --- a/zone/botspellsai.cpp +++ b/zone/botspellsai.cpp @@ -34,7 +34,7 @@ bool Bot::AICastSpell(Mob* tar, uint8 iChance, uint32 iSpellTypes) { // Bot AI Raid* raid = entity_list.GetRaidByBot(this); if (raid) { - AI_CastSpell_Raid(); + AICastSpell_Raid(tar, iChance, iSpellTypes); return true; } diff --git a/zone/raids.h b/zone/raids.h index 432140982..edcdc65be 100644 --- a/zone/raids.h +++ b/zone/raids.h @@ -114,6 +114,7 @@ public: void AddMember(Client *c, uint32 group = 0xFFFFFFFF, bool rleader=false, bool groupleader=false, bool looter=false); void AddBot(Bot* b, uint32 group = 0xFFFFFFFF, bool rleader=false, bool groupleader=false, bool looter=false); //Mitch + void RaidBotGroupSay(Bot* b, uint8 language, uint8 lang_skill, const char* msg, ...); //Mitch void RemoveMember(const char *c); void DisbandRaid(); void MoveMember(const char *name, uint32 newGroup);