Go Back   EQEmulator Home > EQEmulator Forums > Development > Development::Server Code Submissions

 
 
Thread Tools Display Modes
Prev Previous Post   Next Post Next
  #1  
Old 02-16-2011, 04:29 PM
bad_captain
Developer
 
Join Date: Feb 2009
Location: Cincinnati, OH
Posts: 512
Default Bot Spell Casting Fixes

This is my next round of bot updates. This includes some spell casting fixes as well as some additions such as group heals and debuff spells.

1: Group Heals- added support for group heals. Checks for how many in group need to be healed at specific hit point ratios, which can be quickly changed if needed.
2: Spell cast timers- bot spell casting adheres to spell recast time, as well as the specific timer a spell is on, if specified in the database.
3: Debuffs are now used. Since they were listed as nukes, and all the current nuke code checks for IsPureNuke(), none of these spells such as cripple were being cast.
4: Moved SpellProcess() call outside tic_timer.check(), as this is what ends the bot spellend_timer, and caused the bot to not be able to cast for up to 6 seconds after a spell was finished.
5: Added some checks for the spellend_timer to not enter spell ai processing code, to save some cycles (bots were just looping through their spells waiting to finish casting)
6: Changed IsRegularSingleTargetHealSpell to not allow HoT buffs to return true (beastlords casting Regrowth instead of regular heal, and it not landing, as they already had a better HoT buff, and wasting mana)
7: Made some changes in bot.cpp and botspellsai.cpp to reduce the amount of bot casting a little, as well as reordered the priorities a bit for some classes. With my prior changes, and these changes, some bots were going OOM very quickly, and Shamans and Druids weren't healing enough (bad for groups where they were the main healers). When a specific type of spell was being checked for with a chance < 100, for nukes and buffs I believe, that chance was then checked again before being cast, which instead of 50 meaning 50%, it was actually 25%. Instead of sending the chance to AICastSpell, I sent 100, since it already passed the check once. I just lower the initial chance to more closely match was it was before, and allows for better control of how you actually want the bots to cast the different spell types.
8: Added aggro as a factor for determining who to heal. Previously, it just looped through the members and found the first one who needed to be healed (in CheckCloseBeneficialSpells, it checks for clients first, then tanks, then enchanters, then others, checking for pets during each. If I had two tanks or two clients, and both needed to be healed, with the first at 75%, and the second at 25% and being beat on, or the first at 70% and the second at 75% and being beat on, it would attempt to heal the first). Now, it checks to see who has aggro and gives them precedence to being healed. It is also used to determine what spell to use. If the target needing healed is at 80%, it would pick a regular heal before. Now, it would try to cast complete heal if they have aggro, and a regular heal if not. This isn't foolproof, but it seems a lot better in practice.
9: I have included some sql updates to add in group heal spells as well as add in some other missing heals since some people may have the default peq db which doesn't have a lot of the different types of heals added in for bots sich as the fast heals for clerics. I think Complete Heal was the only regular heal for Clerics from level 39 to 69.

One thing I believe is mostly fixed is bots will cast all of their buffs. There's one issue left that I've found, but I wanted to do a lot more testing on it to make sure I don't break anything before submitting a fix for it (I'm almost certain group buffs can only be cast with the bot caster targeting themselves, so if someone loses the buff to a debuff, or accidentally clicks it off, the bot will not recast it, as they still have the buff and it won't stack on them). But, they not longer stand around not casting buffs that aren't on me.

Targets go down much more quickly and much more easily, but mana preservation will be key. I'm sure further changes could be made, but this seemed like a pretty decent start. I took down Agnarr much more easily than I had been able to before, only losing my MT instead of half my bots, as well as the first half of Vex Thal.


optional sql
Code:
-- Shaman Spells
UPDATE npc_spells_entries SET priority = 2, maxlevel = 61 WHERE spellid = (SELECT id FROM spells_new WHERE name ='Chloroblast') AND npc_spells_id = (SELECT id FROM npc_spells WHERE name = 'Shaman Bot');

REPLACE INTO npc_spells_entries (npc_spells_id, spellid, type, minlevel, maxlevel, priority) VALUES
-- HoTs
((SELECT id FROM npc_spells WHERE name = 'Shaman Bot'), (SELECT id FROM spells_new WHERE name = 'Breath of Trushar'), 2, 65, 69, 1),
((SELECT id FROM npc_spells WHERE name = 'Shaman Bot'), (SELECT id FROM spells_new WHERE name = 'Spiritual Serenity'), 2, 70, 255, 1),
-- Heal 
((SELECT id FROM npc_spells WHERE name = 'Shaman Bot'), (SELECT id FROM spells_new WHERE name = 'Kragg\'s Mending'), 2, 58, 61, 2);


-- Druid Spells
REPLACE INTO npc_spells_entries (npc_spells_id, spellid, type, minlevel, maxlevel, priority) VALUES
-- Heals
((SELECT id FROM npc_spells WHERE name = 'Druid Bot'), (SELECT id FROM spells_new WHERE name = 'Tunare\'s Renewal'), 2, 58, 63, 2),
((SELECT id FROM npc_spells WHERE name = 'Druid Bot'), (SELECT id FROM spells_new WHERE name = 'Karana\'s Renewal'), 2, 64, 255, 2);


-- Paladin Spells
UPDATE npc_spells_entries SET minlevel = 58, maxlevel = 64 WHERE spellid = (SELECT id FROM spells_new WHERE name ='Healing Wave of Prexus') AND npc_spells_id = (SELECT id FROM npc_spells WHERE name = 'Paladin Bot');
UPDATE npc_spells_entries SET maxlevel = 61 WHERE spellid = (SELECT id FROM spells_new WHERE name ='Force of Akera') AND npc_spells_id = (SELECT id FROM npc_spells WHERE name = 'Paladin Bot');
UPDATE npc_spells_entries SET maxlevel = 64 WHERE spellid = (SELECT id FROM spells_new WHERE name ='Force of Akilae') AND npc_spells_id = (SELECT id FROM npc_spells WHERE name = 'Paladin Bot');
UPDATE npc_spells_entries SET maxlevel = 67 WHERE spellid = (SELECT id FROM spells_new WHERE name ='Quellious\' Word of Serenity') AND npc_spells_id = (SELECT id FROM npc_spells WHERE name = 'Paladin Bot');
UPDATE npc_spells_entries SET maxlevel = 69 WHERE spellid = (SELECT id FROM spells_new WHERE name ='Force of Piety') AND npc_spells_id = (SELECT id FROM npc_spells WHERE name = 'Paladin Bot');
UPDATE npc_spells_entries SET maxlevel = 255 WHERE spellid = (SELECT id FROM spells_new WHERE name ='Serene Command') AND npc_spells_id = (SELECT id FROM npc_spells WHERE name = 'Paladin Bot');

REPLACE INTO npc_spells_entries (npc_spells_id, spellid, type, minlevel, maxlevel, priority) VALUES
-- Group Heals
((SELECT id FROM npc_spells WHERE name = 'Paladin Bot'), (SELECT id FROM spells_new WHERE name = 'Wave of Life'), 2, 39, 54, 2),
((SELECT id FROM npc_spells WHERE name = 'Paladin Bot'), (SELECT id FROM spells_new WHERE name = 'Wave of Healing'), 2, 55, 57, 2),
((SELECT id FROM npc_spells WHERE name = 'Paladin Bot'), (SELECT id FROM spells_new WHERE name = 'Wave of Marr'), 2, 65, 69, 2),
((SELECT id FROM npc_spells WHERE name = 'Paladin Bot'), (SELECT id FROM spells_new WHERE name = 'Wave of Trushar'), 2, 65, 69, 2),
((SELECT id FROM npc_spells WHERE name = 'Paladin Bot'), (SELECT id FROM spells_new WHERE name = 'Wave of Piety'), 2, 70, 255, 2);


-- Cleric Spells
REPLACE INTO npc_spells_entries (npc_spells_id, spellid, type, minlevel, maxlevel, priority) VALUES
-- Fast Heals
((SELECT id FROM npc_spells WHERE name = 'Cleric Bot'), (SELECT id FROM spells_new WHERE name = 'Remedy'), 2, 51, 60, 2),
((SELECT id FROM npc_spells WHERE name = 'Cleric Bot'), (SELECT id FROM spells_new WHERE name = 'Supernal Remedy'), 2, 61, 65, 2),
((SELECT id FROM npc_spells WHERE name = 'Cleric Bot'), (SELECT id FROM spells_new WHERE name = 'Pious Remedy'), 2, 66, 255, 2),
-- Regular Heals
((SELECT id FROM npc_spells WHERE name = 'Cleric Bot'), (SELECT id FROM spells_new WHERE name = 'Divine Light'), 2, 53, 57, 2),
((SELECT id FROM npc_spells WHERE name = 'Cleric Bot'), (SELECT id FROM spells_new WHERE name = 'Ethereal Light'), 2, 58, 62, 2),
((SELECT id FROM npc_spells WHERE name = 'Cleric Bot'), (SELECT id FROM spells_new WHERE name = 'Supernal Light'), 2, 63, 64, 2),
((SELECT id FROM npc_spells WHERE name = 'Cleric Bot'), (SELECT id FROM spells_new WHERE name = 'Holy Light'), 2, 65, 67, 2),
((SELECT id FROM npc_spells WHERE name = 'Cleric Bot'), (SELECT id FROM spells_new WHERE name = 'Pious Light'), 2, 68, 69, 2),
-- Group Heals
((SELECT id FROM npc_spells WHERE name = 'Cleric Bot'), (SELECT id FROM spells_new WHERE name = 'Word of Health'), 2, 30, 44, 2),
((SELECT id FROM npc_spells WHERE name = 'Cleric Bot'), (SELECT id FROM spells_new WHERE name = 'Word of Healing'), 2, 45, 51, 2),
((SELECT id FROM npc_spells WHERE name = 'Cleric Bot'), (SELECT id FROM spells_new WHERE name = 'Word of Vigor'), 2, 52, 56, 2),
((SELECT id FROM npc_spells WHERE name = 'Cleric Bot'), (SELECT id FROM spells_new WHERE name = 'Word of Restoration'), 2, 57, 63, 2),
((SELECT id FROM npc_spells WHERE name = 'Cleric Bot'), (SELECT id FROM spells_new WHERE name = 'Word of Redemption'), 2, 60, 255, 2),
((SELECT id FROM npc_spells WHERE name = 'Cleric Bot'), (SELECT id FROM spells_new WHERE name = 'Word of Replenishment'), 2, 64, 68, 2),
((SELECT id FROM npc_spells WHERE name = 'Cleric Bot'), (SELECT id FROM spells_new WHERE name = 'Word of Vivification'), 2, 69, 255, 2);


bot.h
Code:
Index: zone/bot.h
===================================================================
--- zone/bot.h	(revision 1838)
+++ zone/bot.h	(working copy)
@@ -24,7 +24,7 @@
 extern WorldServer worldserver;
 
 const int BotAISpellRange = 100; // TODO: Write a method that calcs what the bot's spell range is based on spell, equipment, AA, whatever and replace this
-const int SpellType_Slow = 8192;
+const int MaxSpellTimer = 15;
 
 class Bot : public NPC {
 public:
@@ -140,6 +140,7 @@
 	bool IsStanding();
 	bool IsBotCasterCombatRange(Mob *target);
 	bool CalculateNewPosition2(float x, float y, float z, float speed, bool checkZ = true) ;
+	int8 GetNumberNeedingHealedInGroup(int8 hpr, bool includePets);
 	inline virtual sint16  GetMaxStat();
 	inline virtual sint16  GetMaxResist();
 	inline virtual sint16  GetMaxSTR();
@@ -255,6 +256,8 @@
 	static void ProcessBotOwnerRefDelete(Mob* botOwner);	// Removes a Client* reference when the Client object is destroyed
 	static void ProcessGuildInvite(Client* guildOfficer, Bot* botToGuild);	// Processes a client's request to guild a bot
 	static bool ProcessGuildRemoval(Client* guildOfficer, std::string botName);	// Processes a client's request to deguild a bot
+	static sint32 GetSpellRecastTimer(Bot *caster, int timer_index);
+	static bool CheckSpellRecastTimers(Bot *caster, int SpellIndex);
 	static std::list<BotSpell> GetBotSpellsForSpellEffect(Bot* botCaster, int spellEffect);
 	static std::list<BotSpell> GetBotSpellsForSpellEffectAndTargetType(Bot* botCaster, int spellEffect, SpellTargetType targetType);
 	static std::list<BotSpell> GetBotSpellsBySpellType(Bot* botCaster, int16 spellType);
@@ -263,6 +266,9 @@
 	static BotSpell GetBestBotSpellForHealOverTime(Bot* botCaster);
 	static BotSpell GetBestBotSpellForPercentageHeal(Bot* botCaster);
 	static BotSpell GetBestBotSpellForRegularSingleTargetHeal(Bot* botCaster);
+	static BotSpell GetBestBotSpellForGroupHealOverTime(Bot* botCaster);
+	static BotSpell GetBestBotSpellForGroupCompleteHeal(Bot* botCaster);
+	static BotSpell GetBestBotSpellForGroupHeal(Bot* botCaster);
 	static BotSpell GetBestBotSpellForMagicBasedSlow(Bot* botCaster);
 	static BotSpell GetBestBotSpellForDiseaseBasedSlow(Bot* botCaster);
 	static Mob* GetFirstIncomingMobToMez(Bot* botCaster, BotSpell botSpell);
@@ -272,6 +278,7 @@
 	static BotSpell GetBestBotSpellForNukeByTargetType(Bot* botCaster, SpellTargetType targetType);
 	static BotSpell GetBestBotSpellForStunByTargetType(Bot* botCaster, SpellTargetType targetType);
 	static BotSpell GetBestBotWizardNukeSpellByTargetResists(Bot* botCaster, Mob* target);
+	static BotSpell GetDebuffBotSpell(Bot* botCaster, Mob* target);
 	static NPCType CreateDefaultNPCTypeStructForBot(std::string botName, std::string botLastName, uint8 botLevel, uint16 botRace, uint8 botClass, uint8 gender);
 
 	// Static Bot Group Methods
@@ -364,6 +371,7 @@
 	// void SetBotOwnerCharacterID(uint32 botOwnerCharacterID) { _botOwnerCharacterID = botOwnerCharacterID; }
 	void SetRangerAutoWeaponSelect(bool enable) { GetClass() == RANGER ? _rangerAutoWeaponSelect = enable : _rangerAutoWeaponSelect = false; }
 	void SetBotRole(BotRoleType botRole) { _botRole = botRole; }
+	void SetSpellRecastTimer(int timer_index, sint32 recast_delay);
 
 	// Class Destructors
 	virtual ~Bot();
@@ -407,6 +415,7 @@
 	unsigned int RestRegenHP;
 	unsigned int RestRegenMana;
 	Timer rest_timer;
+	int32 spellRecastTimers[MaxSpellTimer];
 
 	// Private "base stats" Members
 	sint16 _baseMR;


bot.cpp
Code:
Index: zone/bot.cpp
===================================================================
--- zone/bot.cpp	(revision 1838)
+++ zone/bot.cpp	(working copy)
@@ -66,6 +66,11 @@
 	hp_regen = CalcHPRegen();
 	mana_regen = CalcManaRegen();
 
+	for (int i = 0; i < MaxSpellTimer; i++)
+	{
+		spellRecastTimers[i] = 0;
+	}
+
 	strcpy(this->name, this->GetCleanName());
 }
 
@@ -129,6 +134,11 @@
 			GetBotOwner()->Message(13, TempErrorMessage.c_str());
 	}
 
+	for (int i = 0; i < MaxSpellTimer; i++)
+	{
+		spellRecastTimers[i] = 0;
+	}
+
 	GenerateBaseStats();
 
 	// Load saved buffs
@@ -2182,6 +2192,8 @@
 		return false;
 	}
 
+	SpellProcess();
+
 	if(tic_timer.Check())
 	{
 		//6 seconds, or whatever the rule is set to has passed, send this position to everyone to avoid ghosting
@@ -2904,7 +2916,7 @@
 			}
 		} // end in combat range
 		else {
-			if(GetTarget()->IsFeared()){
+			if(GetTarget()->IsFeared() && !spellend_timer.Enabled()){
 				// This is a mob that is fleeing either because it has been feared or is low on hitpoints
 				AI_PursueCastCheck();
 			}
@@ -2923,7 +2935,7 @@
 			}
 		} // end not in combat range
 
-		if(!IsMoving()) {
+		if(!IsMoving() && !spellend_timer.Enabled()) {
 			AI_EngagedCastCheck();
 			BotMeditate(false);
 		}
@@ -2932,7 +2944,7 @@
 		// Not engaged in combat
 		SetTarget(0);
 
-		if(!IsMoving() && AIthink_timer->Check()) {
+		if(!IsMoving() && AIthink_timer->Check() && !spellend_timer.Enabled()) {
 			if(!AI_IdleCastCheck() && !IsCasting())
 				BotMeditate(true);
 		}
@@ -13199,38 +13211,74 @@
 			// check in group
 			if(caster->HasGroup()) {
 				Group *g = caster->GetGroup();
-				
+				Mob *m = NULL;
+				int8 hpratio = 100;
+
 				if(g) {
 					for(int i = 0; i < MAX_GROUP_MEMBERS; i++) {
+						bool hasAggro = false;
+
 						if(g->members[i] && !g->members[i]->qglobal) {
+							if(g->members[i]->IsEngaged()) {
+								if(g->members[i]->GetTarget() && g->members[i]->GetTarget()->GetHateTop() == g->members[i]) {
+									hasAggro = true;
+								}
+							}
+
 							if(g->members[i]->IsClient() && g->members[i]->GetHPRatio() < 90) {
-								if(caster->AICastSpell(g->members[i], iChance, SpellType_Heal))
-									return true;
+								if(g->members[i]->GetHPRatio() < hpratio) {
+									m = g->members[i];
+									hpratio = (int8)g->members[i]->GetHPRatio();
+								}
+								else if(hasAggro){
+									m = g->members[i];
+									hpratio = (int8)g->members[i]->GetHPRatio();
+								}
 							}
-							else if((g->members[i]->GetClass() ==  WARRIOR || g->members[i]->GetClass() == PALADIN || g->members[i]->GetClass() == SHADOWKNIGHT) &&
+
+							if((hasAggro || !m) && (g->members[i]->GetClass() ==  WARRIOR || g->members[i]->GetClass() == PALADIN || g->members[i]->GetClass() == SHADOWKNIGHT) &&
 								g->members[i]->GetHPRatio() < 95) 
 							{
-								if(caster->AICastSpell(g->members[i], iChance, SpellType_Heal))
-									return true;
+								if(g->members[i]->GetHPRatio() < hpratio) {
+									m = g->members[i];
+									hpratio = (int8)g->members[i]->GetHPRatio();
+								}
+								else if(hasAggro){
+									m = g->members[i];
+									hpratio = (int8)g->members[i]->GetHPRatio();
+								}
 							}
-							else if(g->members[i]->GetClass() ==  ENCHANTER && g->members[i]->GetHPRatio() < 80) {
-								if(caster->AICastSpell(g->members[i], iChance, SpellType_Heal))
-									return true;
+
+							if(!m && g->members[i]->GetClass() ==  ENCHANTER && (g->members[i]->GetHPRatio() < 80 || (!g->members[i]->IsEngaged() && g->members[i]->GetHPRatio() < 95))) {
+								if(g->members[i]->GetHPRatio() < hpratio) {
+									m = g->members[i];
+									hpratio = (int8)g->members[i]->GetHPRatio();
+								}
 							}
-							else if(g->members[i]->GetHPRatio() < 70) {
-								if(caster->AICastSpell(g->members[i], iChance, SpellType_Heal))
-									return true;
+
+							if(!m && (g->members[i]->GetHPRatio() < 70  || (!g->members[i]->IsEngaged() && g->members[i]->GetHPRatio() < 95))) {
+								if(g->members[i]->GetHPRatio() < hpratio) {
+									m = g->members[i];
+									hpratio = (int8)g->members[i]->GetHPRatio();
+								}
 							}
-						}
 
-						if(g->members[i] && !g->members[i]->qglobal && g->members[i]->HasPet() && g->members[i]->GetPet()->GetHPRatio() < 50) {
-							if(g->members[i]->GetPet()->GetOwner() != caster && caster->IsEngaged() && g->members[i]->IsCasting() && g->members[i]->GetClass() != ENCHANTER )
-								continue;
+							if(g->members[i] && !g->members[i]->qglobal && g->members[i]->HasPet() && (g->members[i]->GetPet()->GetHPRatio() < 70  || (!g->members[i]->IsEngaged() && g->members[i]->GetHPRatio() < 95))) {
+								if(g->members[i]->GetPet()->GetOwner() != caster && caster->IsEngaged() && g->members[i]->IsCasting() && g->members[i]->GetClass() != ENCHANTER )
+									continue;
 
-							if(caster->AICastSpell(g->members[i]->GetPet(), iChance, SpellType_Heal))
-								return true;
+								if(!m ) {
+									m = g->members[i]->GetPet();
+									hpratio = (int8)g->members[i]->GetPet()->GetHPRatio();
+								}
+							}
 						}
 					}
+
+					if(m) {
+						if(caster->AICastSpell(m, 100, SpellType_Heal))
+							return true;
+					}
 				}
 			}
 
@@ -13254,10 +13302,10 @@
 			if(g) {
 				for( int i = 0; i < MAX_GROUP_MEMBERS; i++) {
 					if(g->members[i]) {
-						if(caster->AICastSpell(g->members[i], iChance, SpellType_Buff))
+						if(caster->AICastSpell(g->members[i], 100, SpellType_Buff))
 							return true;
 
-						if(caster->AICastSpell(g->members[i]->GetPet(), iChance, SpellType_Buff))
+						if(caster->AICastSpell(g->members[i]->GetPet(), 100, SpellType_Buff))
 							return true;
 					}
 				}
@@ -13565,4 +13613,31 @@
 	return; 
 }
 
+int8 Bot::GetNumberNeedingHealedInGroup(int8 hpr, bool includePets) {
+	int8 needHealed = 0;
+	Group *g;
+	
+	if(this->HasGroup()) {
+		g = this->GetGroup();
+			
+		if(g) {
+			for( int i = 0; i<MAX_GROUP_MEMBERS; i++) {
+				if(g->members[i] && !g->members[i]->qglobal) {
+
+					if(g->members[i]->GetHPRatio() <= hpr) 
+						needHealed++;
+
+					if(includePets) {
+						if(g->members[i]->GetPet() && g->members[i]->GetPet()->GetHPRatio() <= hpr) {
+							needHealed++;
+						}
+					}
+				}
+			}	
+		}
+	}
+
+	return needHealed;
+}
+
 #endif

botspellsai.cpp
Code:
Index: zone/botspellsai.cpp
===================================================================
--- zone/botspellsai.cpp	(revision 1838)
+++ zone/botspellsai.cpp	(working copy)
@@ -43,6 +43,7 @@
 		case SpellType_Heal: {
 			if (tar->DontHealMeBefore() < Timer::GetCurrentTime()) {
 				int8 hpr = (int8)tar->GetHPRatio();
+				bool hasAggro = false;
 
 				if(hpr < 95 || (tar->IsClient() && (hpr < 95)) || (botClass == BARD)) {
 					if(tar->GetClass() == NECROMANCER) {
@@ -61,28 +62,73 @@
 					}
 
 					// Evaluate the situation
-					if((tar->IsEngaged()) && ((botClass == CLERIC) || (botClass == DRUID) || (botClass == SHAMAN))) {
-						if(hpr < 35)
+					if((tar->IsEngaged()) && ((botClass == CLERIC) || (botClass == DRUID) || (botClass == SHAMAN) || (botClass == PALADIN))) {
+						if(tar->IsEngaged()) {
+							if(tar->GetTarget() && tar->GetTarget()->GetHateTop() == tar) {
+								hasAggro = true;
+							}
+						}
+
+						if(hpr < 35) {
 							botSpell = GetBestBotSpellForFastHeal(this);
-						else if(hpr >= 35 && hpr < 70)
-							botSpell = GetBestBotSpellForPercentageHeal(this);
-						else if(hpr >= 70 && hpr < 90)
-							botSpell = GetBestBotSpellForRegularSingleTargetHeal(this);
+						}
+						else if(hpr >= 35 && hpr < 70){
+							if(GetNumberNeedingHealedInGroup(60, false) >= 3)
+								botSpell = GetBestBotSpellForGroupHeal(this);
+
+							if(botSpell.SpellId == 0)
+								botSpell = GetBestBotSpellForPercentageHeal(this);
+						}
+						else if(hpr >= 70 && hpr < 90){
+							if(GetNumberNeedingHealedInGroup(80, false) >= 3)
+								botSpell = GetBestBotSpellForGroupHealOverTime(this);
+
+							if(hasAggro)
+								botSpell = GetBestBotSpellForPercentageHeal(this);
+						}
 						else
 							botSpell = GetBestBotSpellForHealOverTime(this);
 					}
-					else if ((botClass == CLERIC) || (botClass == DRUID) || (botClass == SHAMAN)) {
-						if(hpr < 30)
+					else if ((botClass == CLERIC) || (botClass == DRUID) || (botClass == SHAMAN) || (botClass == PALADIN)) {
+						if(GetNumberNeedingHealedInGroup(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(GetNumberNeedingHealedInGroup(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 >= 30 && hpr < 75)
+						else if(hpr >= 40 && hpr < 75)
 							botSpell = GetBestBotSpellForRegularSingleTargetHeal(this);
 						else
 							botSpell = GetBestBotSpellForHealOverTime(this);
 					}	
 			
 					if(botSpell.SpellId == 0)
-						botSpell = GetFirstBotSpellBySpellType(this, iSpellTypes);
+						botSpell = GetBestBotSpellForRegularSingleTargetHeal(this);
 
+					if(botSpell.SpellId == 0 && botClass == PALADIN)
+						botSpell = GetBestBotSpellForFastHeal(this);
+
 					// If there is still no spell id, then there isn't going to be one so we are done
 					if(botSpell.SpellId == 0)
 						break;
@@ -112,8 +158,24 @@
 								tar->SetDontHealMeBefore(Timer::GetCurrentTime() + 1000);
 							}
 						}*/
+						if(IsGroupSpell(botSpell.SpellId)){
+							Group *g;
+	
+							if(this->HasGroup()) {
+								Group *g = this->GetGroup();
 
-						tar->SetDontHealMeBefore(Timer::GetCurrentTime() + 2000);
+								if(g) {
+									for( int i = 0; i<MAX_GROUP_MEMBERS; i++) {
+										if(g->members[i] && !g->members[i]->qglobal) {
+											g->members[i]->SetDontHealMeBefore(Timer::GetCurrentTime() + 2000);
+										}
+									}
+								}
+							}
+						}
+						else {
+							tar->SetDontHealMeBefore(Timer::GetCurrentTime() + 2000);
+						}
 					}
 				}
 			}
@@ -161,6 +223,10 @@
 					if(IsEffectInSpell(selectedBotSpell.SpellId, SE_Illusion))
 						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;
@@ -178,13 +244,17 @@
 							continue;
 					}
 
-					int32 TempDontBuffMeBefore = tar->DontBuffMeBefore();
+					if(CheckSpellRecastTimers(this, itr->SpellIndex))
+					{
 
-					castedSpell = AIDoSpellCast(selectedBotSpell.SpellIndex, tar, selectedBotSpell.ManaCost, &TempDontBuffMeBefore);
+						int32 TempDontBuffMeBefore = tar->DontBuffMeBefore();
 
-					if(TempDontBuffMeBefore != tar->DontBuffMeBefore())
-						tar->SetDontBuffMeBefore(TempDontBuffMeBefore);
+						castedSpell = AIDoSpellCast(selectedBotSpell.SpellIndex, tar, selectedBotSpell.ManaCost, &TempDontBuffMeBefore);
 
+						if(TempDontBuffMeBefore != tar->DontBuffMeBefore())
+							tar->SetDontBuffMeBefore(TempDontBuffMeBefore);
+					}
+
 					if(castedSpell)
 						break;
 				}
@@ -238,6 +308,10 @@
 					}
 				}
 
+				if(botClass == WIZARD && botSpell.SpellId == 0) {
+					botSpell = GetBestBotWizardNukeSpellByTargetResists(this, tar);
+				}
+
 				if(botSpell.SpellId == 0)
 					botSpell = GetBestBotSpellForNukeByTargetType(this, ST_Target);
 
@@ -363,16 +437,20 @@
 					if(selectedBotSpell.SpellId == 0)
 						continue;
 
-					if(!(!tar->IsImmuneToSpell(selectedBotSpell.SpellId, this) && tar->CanBuffStack(selectedBotSpell.SpellId, botLevel, true) >= 0))
-						continue;
+					if(CheckSpellRecastTimers(this, itr->SpellIndex))
+					{
 
-					int32 TempDontDotMeBefore = tar->DontDotMeBefore();
+						if(!(!tar->IsImmuneToSpell(selectedBotSpell.SpellId, this) && tar->CanBuffStack(selectedBotSpell.SpellId, botLevel, true) >= 0))
+							continue;
 
-					castedSpell = AIDoSpellCast(selectedBotSpell.SpellIndex, tar, selectedBotSpell.ManaCost, &TempDontDotMeBefore);
+						int32 TempDontDotMeBefore = tar->DontDotMeBefore();
 
-					if(TempDontDotMeBefore != tar->DontDotMeBefore())
-						tar->SetDontDotMeBefore(TempDontDotMeBefore);
+						castedSpell = AIDoSpellCast(selectedBotSpell.SpellIndex, tar, selectedBotSpell.ManaCost, &TempDontDotMeBefore);
 
+						if(TempDontDotMeBefore != tar->DontDotMeBefore())
+							tar->SetDontDotMeBefore(TempDontDotMeBefore);
+					}
+
 					dotSelectCounter++;
 
 					if((dotSelectCounter == maxDotSelect) || castedSpell)
@@ -407,6 +485,28 @@
 			}
 			break;
 							}
+		case SpellType_Debuff: {
+			if((tar->GetHPRatio() <= 99.0f) || ((botClass == BARD) || (botClass == SHAMAN) || (botClass == ENCHANTER) || (botClass == DRUID)) && (tar->GetHPRatio() > 25.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 = 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_Mez: {
 			if (tar->GetBodyType() != BT_Giant) {
 					if(!checked_los) {
@@ -491,6 +591,12 @@
 		SetMana(hasMana);
 		extraMana = false;
 	}
+	else {  //handle spell recast and recast timers
+		AIspells[i].time_cancast = Timer::GetCurrentTime() + spells[AIspells[i].spellid].recast_time;
+		if(spells[AIspells[i].spellid].EndurTimerIndex > 0) {
+			SetSpellRecastTimer(spells[AIspells[i].spellid].EndurTimerIndex, spells[AIspells[i].spellid].recast_time);
+		}
+	}
 
 	return result;
 }
@@ -559,8 +665,10 @@
 				if (!AICastSpell(this, 100, SpellType_Buff)) {
 					if (!AICastSpell(this, 100, SpellType_Heal)) {
 						if(!entity_list.Bot_AICheckCloseBeneficialSpells(this, 100, BotAISpellRange, SpellType_Heal)) {
-							if(!entity_list.Bot_AICheckCloseBeneficialSpells(this, 100, BotAISpellRange, SpellType_Buff)) {
-								//
+							if (!AICastSpell(GetPet(), 100, SpellType_Heal)) {
+								if(!entity_list.Bot_AICheckCloseBeneficialSpells(this, 100, BotAISpellRange, SpellType_Buff)) {
+									//
+								}
 							}
 						}
 					}
@@ -598,7 +706,7 @@
 		if(botClass == CLERIC) {
 			if(!AICastSpell(this, 100, SpellType_Heal)) {
 				if(!entity_list.Bot_AICheckCloseBeneficialSpells(this, 100, BotAISpellRange, SpellType_Heal)) {
-					if(!AICastSpell(GetTarget(), 50, SpellType_Nuke)) {
+					if(!AICastSpell(GetTarget(), 25, SpellType_Nuke)) {
 						//AIautocastspell_timer->Start(RandomTimer(100, 250), false);		// Do not give healer classes a lot of time off or your tank's die
 					}
 				}
@@ -607,11 +715,13 @@
 			result = true;
 		}
 		else if(botClass == DRUID) {
-			if (!AICastSpell(GetTarget(), 100, SpellType_DOT)) {
+			if (!AICastSpell(GetTarget(), 50, SpellType_DOT)) {
 				if(!AICastSpell(this, 100, SpellType_Heal)) {
 					if(!entity_list.Bot_AICheckCloseBeneficialSpells(this, 100, BotAISpellRange, SpellType_Heal)) {
-						if(!AICastSpell(GetTarget(), 50, SpellType_Nuke)) {
-							//AIautocastspell_timer->Start(RandomTimer(100, 250), false);		// Do not give healer classes a lot of time off or your tank's die
+						if (!AICastSpell(GetTarget(), 25, SpellType_Debuff)) {
+							if(!AICastSpell(GetTarget(), 25, SpellType_Nuke)) {
+								//AIautocastspell_timer->Start(RandomTimer(100, 250), false);		// Do not give healer classes a lot of time off or your tank's die
+							}
 						}
 					}
 				}
@@ -621,13 +731,15 @@
 		}
 		else if(botClass == SHAMAN) {
 			if (!AICastSpell(GetTarget(), 100, SpellType_Slow)) {
-				if (!AICastSpell(this, 100, SpellType_Pet)) {
-					if (!AICastSpell(GetTarget(), 100, SpellType_DOT)) {
-						if(!AICastSpell(this, 100, SpellType_Heal)) {
-							if (!AICastSpell(GetPet(), 100, SpellType_Heal)) {
-								if(!entity_list.Bot_AICheckCloseBeneficialSpells(this, 100, BotAISpellRange, SpellType_Heal)) {
-									if(!AICastSpell(GetTarget(), 50, SpellType_Nuke)) {
-										//AIautocastspell_timer->Start(RandomTimer(100, 250), false);		// Do not give healer classes a lot of time off or your tank's die
+				if (!AICastSpell(GetTarget(), 50, SpellType_Debuff)) {
+					if(!AICastSpell(this, 100, SpellType_Heal)) {
+						if (!AICastSpell(GetPet(), 100, SpellType_Heal)) {
+							if(!entity_list.Bot_AICheckCloseBeneficialSpells(this, 100, BotAISpellRange, SpellType_Heal)) {
+								if (!AICastSpell(this, 100, SpellType_Pet)) {
+									if (!AICastSpell(GetTarget(), 25, SpellType_DOT)) {
+										if(!AICastSpell(GetTarget(), 25, SpellType_Nuke)) {
+											//AIautocastspell_timer->Start(RandomTimer(100, 250), false);		// Do not give healer classes a lot of time off or your tank's die
+										}
 									}
 								}
 							}
@@ -639,10 +751,10 @@
 			result = true;
 		}
 		else if(botClass == RANGER) {
-			if (!AICastSpell(GetTarget(), 50, SpellType_DOT)) {
+			if (!AICastSpell(GetTarget(), 25, SpellType_DOT)) {
 				if (!AICastSpell(this, 100, SpellType_Heal)) {
-					if (!entity_list.Bot_AICheckCloseBeneficialSpells(this, 30, BotAISpellRange, SpellType_Heal)) {
-						if (!AICastSpell(GetTarget(), 50, SpellType_Nuke)) {
+					if (!entity_list.Bot_AICheckCloseBeneficialSpells(this, 10, BotAISpellRange, SpellType_Heal)) {
+						if (!AICastSpell(GetTarget(), 25, SpellType_Nuke)) {
 							//
 						}
 					}
@@ -654,12 +766,14 @@
 		else if(botClass == BEASTLORD) {
 			if (!AICastSpell(GetTarget(), 100, SpellType_Slow)) {
 				if (!AICastSpell(this, 100, SpellType_Pet)) {
-					if (!AICastSpell(GetTarget(), 50, SpellType_DOT)) {
+					if (!AICastSpell(GetTarget(), 25, SpellType_DOT)) {
 						if (!AICastSpell(this, 100, SpellType_Heal)) {
 							if (!AICastSpell(GetPet(), 100, SpellType_Heal)) {
-								if (!entity_list.Bot_AICheckCloseBeneficialSpells(this, 30, BotAISpellRange, SpellType_Heal)) {
-									if(!AICastSpell(GetTarget(), 50, SpellType_Nuke)) {
-										//
+								if (!entity_list.Bot_AICheckCloseBeneficialSpells(this, 15, BotAISpellRange, SpellType_Heal)) {
+									if (!AICastSpell(GetTarget(), 25, SpellType_Debuff)) {
+										if(!AICastSpell(GetTarget(), 25, SpellType_Nuke)) {
+											//
+										}
 									}
 								}
 							}
@@ -679,8 +793,8 @@
 		}
 		else if(botClass == PALADIN) {
 			if (!AICastSpell(this, 100, SpellType_Heal)) {
-				if (!entity_list.Bot_AICheckCloseBeneficialSpells(this, 30, BotAISpellRange, SpellType_Heal)) {
-					if (!AICastSpell(GetTarget(), 50, SpellType_Nuke)) {
+				if (!entity_list.Bot_AICheckCloseBeneficialSpells(this, 15, BotAISpellRange, SpellType_Heal)) {
+					if (!AICastSpell(GetTarget(), 25, SpellType_Nuke)) {
 						//
 					}
 				}
@@ -692,9 +806,11 @@
 			if (!AICastSpell(this, 100, SpellType_Pet)) {
 				if (!AICastSpell(GetTarget(), 100, SpellType_Lifetap)) {
 					if (!AICastSpell(GetPet(), 100, SpellType_Heal)) {
-						if (!AICastSpell(GetTarget(), 50, SpellType_DOT)) {
-							if (!AICastSpell(GetTarget(), 50, SpellType_Nuke)) {
-								//
+						if (!AICastSpell(GetTarget(), 25, SpellType_DOT)) {
+							if (!AICastSpell(GetTarget(), 25, SpellType_Debuff)) {
+								if (!AICastSpell(GetTarget(), 25, SpellType_Nuke)) {
+									//
+								}
 							}
 						}
 					}
@@ -706,8 +822,10 @@
 		else if(botClass == MAGICIAN) {
 			if (!AICastSpell(this, 100, SpellType_Pet)) {
 				if (!AICastSpell(GetPet(), 100, SpellType_Heal)) {
-					if (!AICastSpell(GetTarget(), 100, SpellType_Nuke)) {
-						//
+					if (!AICastSpell(GetTarget(), 25, SpellType_Debuff)) {
+						if (!AICastSpell(GetTarget(), 100, SpellType_Nuke)) {
+							//
+						}
 					}
 				}
 			}
@@ -717,10 +835,12 @@
 		else if(botClass == NECROMANCER) {
 			if (!AICastSpell(this, 100, SpellType_Pet)) {
 				if (!AICastSpell(GetTarget(), 100, SpellType_Lifetap)) {
-					if (!AICastSpell(GetTarget(), 100, SpellType_DOT)) {
-						if (!AICastSpell(GetPet(), 100, SpellType_Heal)) {
-							if (!AICastSpell(GetTarget(), 100, SpellType_Nuke)) {
-								//
+					if (!AICastSpell(GetTarget(), 50, SpellType_DOT)) {
+						if (!AICastSpell(GetTarget(), 50, SpellType_Debuff)) {
+							if (!AICastSpell(GetPet(), 100, SpellType_Heal)) {
+								if (!AICastSpell(GetTarget(), 25, SpellType_Nuke)) {
+									//
+								}
 							}
 						}
 					}
@@ -732,11 +852,13 @@
 		else if(botClass == ENCHANTER) {
 			if (!AICastSpell(GetTarget(), 100, SpellType_Mez)) {
 				if (!AICastSpell(GetTarget(), 100, SpellType_Slow)) {
-					if (!AICastSpell(this, 100, SpellType_Pet)) {
-						if (!AICastSpell(GetTarget(), 100, SpellType_DOT)) {
-							if (!AICastSpell(GetTarget(), 50, SpellType_Nuke)) {
-								if(!AICastSpell(GetTarget(), 50, SpellType_Escape)) {
-									//
+					if (!AICastSpell(GetTarget(), 50, SpellType_Debuff)) {
+						if (!AICastSpell(this, 100, SpellType_Pet)) {
+							if (!AICastSpell(GetTarget(), 50, SpellType_DOT)) {
+								if (!AICastSpell(GetTarget(), 50, SpellType_Nuke)) {
+									if(!AICastSpell(GetTarget(), 50, SpellType_Escape)) {
+										//
+									}
 								}
 							}
 						}
@@ -864,7 +986,7 @@
 				continue;
 			}
 
-			if(botSpellList[i].type & spellType) {
+			if((botSpellList[i].type & spellType) && CheckSpellRecastTimers(botCaster, i)) {
 				result.SpellId = botSpellList[i].spellid;
 				result.SpellIndex = i;
 				result.ManaCost = botSpellList[i].manacost;
@@ -889,7 +1011,7 @@
 
 		for(std::list<BotSpell>::iterator botSpellListItr = botSpellList.begin(); botSpellListItr != botSpellList.end(); botSpellListItr++) {
 			// Assuming all the spells have been loaded into this list by level and in descending order
-			if(IsFastHealSpell(botSpellListItr->SpellId)) {
+			if(IsFastHealSpell(botSpellListItr->SpellId) && CheckSpellRecastTimers(botCaster, botSpellListItr->SpellIndex)) {
 				result.SpellId = botSpellListItr->SpellId;
 				result.SpellIndex = botSpellListItr->SpellIndex;
 				result.ManaCost = botSpellListItr->ManaCost;
@@ -914,7 +1036,7 @@
 
 		for(std::list<BotSpell>::iterator botSpellListItr = botSpellList.begin(); botSpellListItr != botSpellList.end(); botSpellListItr++) {
 			// Assuming all the spells have been loaded into this list by level and in descending order
-			if(IsHealOverTimeSpell(botSpellListItr->SpellId)) {
+			if(IsHealOverTimeSpell(botSpellListItr->SpellId) && CheckSpellRecastTimers(botCaster, botSpellListItr->SpellIndex)) {
 				result.SpellId = botSpellListItr->SpellId;
 				result.SpellIndex = botSpellListItr->SpellIndex;
 				result.ManaCost = botSpellListItr->ManaCost;
@@ -944,7 +1066,7 @@
 				continue;
 			}
 
-			if(IsCompleteHealSpell(botSpellList[i].spellid)) {
+			if(IsCompleteHealSpell(botSpellList[i].spellid) && CheckSpellRecastTimers(botCaster, i)) {
 				result.SpellId = botSpellList[i].spellid;
 				result.SpellIndex = i;
 				result.ManaCost = botSpellList[i].manacost;
@@ -969,11 +1091,11 @@
 
 		for(std::list<BotSpell>::iterator botSpellListItr = botSpellList.begin(); botSpellListItr != botSpellList.end(); botSpellListItr++) {
 			// Assuming all the spells have been loaded into this list by level and in descending order
-			if(IsRegularSingleTargetHealSpell(botSpellListItr->SpellId)) {
+			if(IsRegularSingleTargetHealSpell(botSpellListItr->SpellId) && CheckSpellRecastTimers(botCaster, botSpellListItr->SpellIndex)) {
 				result.SpellId = botSpellListItr->SpellId;
 				result.SpellIndex = botSpellListItr->SpellIndex;
 				result.ManaCost = botSpellListItr->ManaCost;
-				
+
 				break;
 			}
 		}
@@ -982,6 +1104,81 @@
 	return result;
 }
 
+BotSpell Bot::GetBestBotSpellForGroupHeal(Bot* botCaster) {
+        BotSpell result;
+        
+        result.SpellId = 0;
+        result.SpellIndex = 0;
+        result.ManaCost = 0;
+
+        if(botCaster) {
+                std::list<BotSpell> botSpellList = GetBotSpellsForSpellEffect(botCaster, SE_CurrentHP);
+
+				for(std::list<BotSpell>::iterator botSpellListItr = botSpellList.begin(); botSpellListItr != botSpellList.end(); botSpellListItr++) {
+					// Assuming all the spells have been loaded into this list by level and in descending order
+					if(IsRegularGroupHealSpell(botSpellListItr->SpellId) && CheckSpellRecastTimers(botCaster, botSpellListItr->SpellIndex)) {
+						result.SpellId = botSpellListItr->SpellId;
+						result.SpellIndex = botSpellListItr->SpellIndex;
+						result.ManaCost = botSpellListItr->ManaCost;
+						
+						break;
+					}
+				}
+        }
+
+        return result;
+}
+
+BotSpell Bot::GetBestBotSpellForGroupHealOverTime(Bot* botCaster) {
+        BotSpell result;
+        
+        result.SpellId = 0;
+        result.SpellIndex = 0;
+        result.ManaCost = 0;
+
+        if(botCaster) {
+                std::list<BotSpell> botSpellList = GetBotSpellsForSpellEffect(botCaster, SE_HealOverTime);
+
+				for(std::list<BotSpell>::iterator botSpellListItr = botSpellList.begin(); botSpellListItr != botSpellList.end(); botSpellListItr++) {
+					// Assuming all the spells have been loaded into this list by level and in descending order
+					if(IsGroupHealOverTimeSpell(botSpellListItr->SpellId) && CheckSpellRecastTimers(botCaster, botSpellListItr->SpellIndex)) {
+						result.SpellId = botSpellListItr->SpellId;
+						result.SpellIndex = botSpellListItr->SpellIndex;
+						result.ManaCost = botSpellListItr->ManaCost;
+						
+						break;
+					}
+				}
+        }
+
+        return result;
+}
+
+BotSpell Bot::GetBestBotSpellForGroupCompleteHeal(Bot* botCaster) {
+        BotSpell result;
+        
+        result.SpellId = 0;
+        result.SpellIndex = 0;
+        result.ManaCost = 0;
+
+        if(botCaster) {
+                std::list<BotSpell> botSpellList = GetBotSpellsForSpellEffect(botCaster, SE_CompleteHeal);
+
+				for(std::list<BotSpell>::iterator botSpellListItr = botSpellList.begin(); botSpellListItr != botSpellList.end(); botSpellListItr++) {
+					// Assuming all the spells have been loaded into this list by level and in descending order
+					if(IsGroupCompleteHealSpell(botSpellListItr->SpellId) && CheckSpellRecastTimers(botCaster, botSpellListItr->SpellIndex)) {
+						result.SpellId = botSpellListItr->SpellId;
+						result.SpellIndex = botSpellListItr->SpellIndex;
+						result.ManaCost = botSpellListItr->ManaCost;
+						
+						break;
+					}
+				}
+        }
+
+	return result;
+}
+
 BotSpell Bot::GetBestBotSpellForMez(Bot* botCaster) {
 	BotSpell result;
 	
@@ -994,7 +1191,7 @@
 
 		for(std::list<BotSpell>::iterator botSpellListItr = botSpellList.begin(); botSpellListItr != botSpellList.end(); botSpellListItr++) {
 			// Assuming all the spells have been loaded into this list by level and in descending order
-			if(IsMezSpell(botSpellListItr->SpellId)) {
+			if(IsMezSpell(botSpellListItr->SpellId) && CheckSpellRecastTimers(botCaster, botSpellListItr->SpellIndex)) {
 				result.SpellId = botSpellListItr->SpellId;
 				result.SpellIndex = botSpellListItr->SpellIndex;
 				result.ManaCost = botSpellListItr->ManaCost;
@@ -1019,7 +1216,7 @@
 
 		for(std::list<BotSpell>::iterator botSpellListItr = botSpellList.begin(); botSpellListItr != botSpellList.end(); botSpellListItr++) {
 			// Assuming all the spells have been loaded into this list by level and in descending order
-			if(IsSlowSpell(botSpellListItr->SpellId) && spells[botSpellListItr->SpellId].resisttype == RESIST_MAGIC) {
+			if(IsSlowSpell(botSpellListItr->SpellId) && spells[botSpellListItr->SpellId].resisttype == RESIST_MAGIC && CheckSpellRecastTimers(botCaster, botSpellListItr->SpellIndex)) {
 				result.SpellId = botSpellListItr->SpellId;
 				result.SpellIndex = botSpellListItr->SpellIndex;
 				result.ManaCost = botSpellListItr->ManaCost;
@@ -1044,7 +1241,7 @@
 
 		for(std::list<BotSpell>::iterator botSpellListItr = botSpellList.begin(); botSpellListItr != botSpellList.end(); botSpellListItr++) {
 			// Assuming all the spells have been loaded into this list by level and in descending order
-			if(IsSlowSpell(botSpellListItr->SpellId) && spells[botSpellListItr->SpellId].resisttype == RESIST_DISEASE) {
+			if(IsSlowSpell(botSpellListItr->SpellId) && spells[botSpellListItr->SpellId].resisttype == RESIST_DISEASE && CheckSpellRecastTimers(botCaster, botSpellListItr->SpellIndex)) {
 				result.SpellId = botSpellListItr->SpellId;
 				result.SpellIndex = botSpellListItr->SpellIndex;
 				result.ManaCost = botSpellListItr->ManaCost;
@@ -1107,7 +1304,7 @@
 
 		for(std::list<BotSpell>::iterator botSpellListItr = botSpellList.begin(); botSpellListItr != botSpellList.end(); botSpellListItr++) {
 			// Assuming all the spells have been loaded into this list by level and in descending order
-			if(IsSummonPetSpell(botSpellListItr->SpellId)) {
+			if(IsSummonPetSpell(botSpellListItr->SpellId) && CheckSpellRecastTimers(botCaster, botSpellListItr->SpellIndex)) {
 				if(!strncmp(spells[botSpellListItr->SpellId].teleport_zone, petType.c_str(), petType.length())) {
 					result.SpellId = botSpellListItr->SpellId;
 					result.SpellIndex = botSpellListItr->SpellIndex;
@@ -1216,7 +1413,7 @@
 
 		for(std::list<BotSpell>::iterator botSpellListItr = botSpellList.begin(); botSpellListItr != botSpellList.end(); botSpellListItr++) {
 			// Assuming all the spells have been loaded into this list by level and in descending order
-			if(IsPureNukeSpell(botSpellListItr->SpellId) && IsDamageSpell(botSpellListItr->SpellId)) {
+			if(IsPureNukeSpell(botSpellListItr->SpellId) && IsDamageSpell(botSpellListItr->SpellId) && CheckSpellRecastTimers(botCaster, botSpellListItr->SpellIndex)) {
 				result.SpellId = botSpellListItr->SpellId;
 				result.SpellIndex = botSpellListItr->SpellIndex;
 				result.ManaCost = botSpellListItr->ManaCost;
@@ -1244,7 +1441,7 @@
 		for(std::list<BotSpell>::iterator botSpellListItr = botSpellList.begin(); botSpellListItr != botSpellList.end(); botSpellListItr++)
 		{
 			// Assuming all the spells have been loaded into this list by level and in descending order
-			if(IsStunSpell(botSpellListItr->SpellId))
+			if(IsStunSpell(botSpellListItr->SpellId) && CheckSpellRecastTimers(botCaster, botSpellListItr->SpellIndex))
 			{
 				result.SpellId = botSpellListItr->SpellId;
 				result.SpellIndex = botSpellListItr->SpellIndex;
@@ -1284,33 +1481,32 @@
 			// Assuming all the spells have been loaded into this list by level and in descending order
 			bool spellSelected = false;
 
-			if(selectLureNuke && (spells[botSpellListItr->SpellId].ResistDiff < lureResisValue)) {
-				spellSelected = true;
-			}
-			else if(IsStunSpell(botSpellListItr->SpellId) && !target->SpecAttacks[UNSTUNABLE] && (MakeRandomInt(0, 5) == 5)) {
-				spellSelected = true;
-			}
-			else if(IsPureNukeSpell(botSpellListItr->SpellId)) {
-				if(((target->GetMR() < target->GetCR()) || (target->GetMR() < target->GetFR())) && (GetSpellResistType(botSpellListItr->SpellId) == RESIST_MAGIC) 
-					&& (spells[botSpellListItr->SpellId].ResistDiff > lureResisValue))
-				{
+			if(CheckSpellRecastTimers(botCaster, botSpellListItr->SpellIndex)) {
+				if(selectLureNuke && (spells[botSpellListItr->SpellId].ResistDiff < lureResisValue)) {
 					spellSelected = true;
 				}
-				else if(((target->GetCR() < target->GetMR()) || (target->GetCR() < target->GetFR())) && (GetSpellResistType(botSpellListItr->SpellId) == RESIST_COLD) 
-					&& (spells[botSpellListItr->SpellId].ResistDiff > lureResisValue)) 
-				{
-					spellSelected = true;
+				else if(IsPureNukeSpell(botSpellListItr->SpellId)) {
+					if(((target->GetMR() < target->GetCR()) || (target->GetMR() < target->GetFR())) && (GetSpellResistType(botSpellListItr->SpellId) == RESIST_MAGIC) 
+						&& (spells[botSpellListItr->SpellId].ResistDiff > lureResisValue))
+					{
+						spellSelected = true;
+					}
+					else if(((target->GetCR() < target->GetMR()) || (target->GetCR() < target->GetFR())) && (GetSpellResistType(botSpellListItr->SpellId) == RESIST_COLD) 
+						&& (spells[botSpellListItr->SpellId].ResistDiff > lureResisValue)) 
+					{
+						spellSelected = true;
+					}
+					else if(((target->GetFR() < target->GetCR()) || (target->GetFR() < target->GetMR())) && (GetSpellResistType(botSpellListItr->SpellId) == RESIST_FIRE)
+						&& (spells[botSpellListItr->SpellId].ResistDiff > lureResisValue)) 
+					{
+						spellSelected = true;
+					}
+					else if((GetSpellResistType(botSpellListItr->SpellId) == RESIST_MAGIC) && (spells[botSpellListItr->SpellId].ResistDiff > lureResisValue) && !IsStunSpell(botSpellListItr->SpellId)) {
+						firstWizardMagicNukeSpellFound.SpellId = botSpellListItr->SpellId;
+						firstWizardMagicNukeSpellFound.SpellIndex = botSpellListItr->SpellIndex;
+						firstWizardMagicNukeSpellFound.ManaCost = botSpellListItr->ManaCost;
+					}
 				}
-				else if(((target->GetFR() < target->GetCR()) || (target->GetFR() < target->GetMR())) && (GetSpellResistType(botSpellListItr->SpellId) == RESIST_FIRE)
-					&& (spells[botSpellListItr->SpellId].ResistDiff > lureResisValue)) 
-				{
-					spellSelected = true;
-				}
-				else if((GetSpellResistType(botSpellListItr->SpellId) == RESIST_MAGIC) && (spells[botSpellListItr->SpellId].ResistDiff > lureResisValue)) {
-					firstWizardMagicNukeSpellFound.SpellId = botSpellListItr->SpellId;
-					firstWizardMagicNukeSpellFound.SpellIndex = botSpellListItr->SpellIndex;
-					firstWizardMagicNukeSpellFound.ManaCost = botSpellListItr->ManaCost;
-				}
 			}
 
 			if(spellSelected) {
@@ -1330,4 +1526,63 @@
 	return result;
 }
 
+BotSpell Bot::GetDebuffBotSpell(Bot* botCaster, Mob *tar) {
+	BotSpell result;
+	
+	result.SpellId = 0;
+	result.SpellIndex = 0;
+	result.ManaCost = 0;
+
+	if(botCaster && botCaster->AI_HasSpells()) {
+		std::vector<AISpells_Struct> botSpellList = botCaster->GetBotSpells();
+
+		for (int i = botSpellList.size() - 1; i >= 0; i--) {
+			if (botSpellList[i].spellid <= 0 || botSpellList[i].spellid >= SPDAT_RECORDS) {
+				// this is both to quit early to save cpu and to avoid casting bad spells
+				// Bad info from database can trigger this incorrectly, but that should be fixed in DB, not here
+				continue;
+			}
+
+			if((IsDebuffSpell(botSpellList[i].spellid) || (botSpellList[i].type & SpellType_Debuff)) && (!tar->IsImmuneToSpell(botSpellList[i].spellid, botCaster) 
+				&& tar->CanBuffStack(botSpellList[i].spellid, botCaster->GetLevel(), true) >= 0) 
+				&& CheckSpellRecastTimers(botCaster, i) && !(botSpellList[i].type & SpellType_Dispel)) {
+				result.SpellId = botSpellList[i].spellid;
+				result.SpellIndex = i;
+				result.ManaCost = botSpellList[i].manacost;
+				
+				break;
+			}
+		}
+	}
+
+	return result;
+}
+
+void Bot::SetSpellRecastTimer(int timer_index, sint32 recast_delay) {
+	if(timer_index > 0 && timer_index <= MaxSpellTimer) {
+		spellRecastTimers[timer_index - 1] = Timer::GetCurrentTime() + recast_delay;
+	}
+}
+
+sint32 Bot::GetSpellRecastTimer(Bot *caster, int timer_index) {
+	sint32 result = 0;
+	if(caster) {
+		if(timer_index > 0 && timer_index <= MaxSpellTimer) {
+			result = caster->spellRecastTimers[timer_index - 1];
+		}
+	}
+	return result;
+}
+
+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].EndurTimerIndex) < Timer::GetCurrentTime()) {   //checks for spells on the same timer
+				return true;    //can cast spell
+			}
+		}
+	}
+	return false;
+}
+
 #endif

spdat.h
Code:
Index: zone/spdat.h
===================================================================
--- zone/spdat.h	(revision 1829)
+++ zone/spdat.h	(working copy)
@@ -61,8 +61,10 @@
 const int SpellType_InCombatBuff=1024;
 const int SpellType_Mez=2048;
 const int SpellType_Charm=4096;
+const int SpellType_Slow = 8192;
+const int SpellType_Debuff = 16384;
 
-const int SpellTypes_Detrimental = SpellType_Nuke|SpellType_Root|SpellType_Lifetap|SpellType_Snare|SpellType_DOT|SpellType_Dispel|SpellType_Mez|SpellType_Charm;
+const int SpellTypes_Detrimental = SpellType_Nuke|SpellType_Root|SpellType_Lifetap|SpellType_Snare|SpellType_DOT|SpellType_Dispel|SpellType_Mez|SpellType_Charm|SpellType_Debuff;
 const int SpellTypes_Beneficial = SpellType_Heal|SpellType_Buff|SpellType_Escape|SpellType_Pet|SpellType_InCombatBuff;
 
 #define SpellType_Any		0xFFFF
@@ -783,6 +785,10 @@
 bool IsCompleteHealSpell(int16 spell_id);
 bool IsFastHealSpell(int16 spell_id);
 bool IsRegularSingleTargetHealSpell(int16 spell_id);
+bool IsRegularGroupHealSpell(int16 spell_id);
+bool IsGroupCompleteHealSpell(int16 spell_id);
+bool IsGroupHealOverTimeSpell(int16 spell_id);
+bool IsDebuffSpell(int16 spell_id);
 uint32 GetMorphTrigger(uint32 spell_id);
 uint32 GetPartialMeleeRuneReduction(uint32 spell_id);
 uint32 GetPartialMagicRuneReduction(uint32 spell_id);


spdat.cpp
Code:
Index: zone/spdat.cpp
===================================================================
--- zone/spdat.cpp	(revision 1829)
+++ zone/spdat.cpp	(working copy)
@@ -856,7 +856,7 @@
 }
 
 bool IsHealOverTimeSpell(int16 spell_id) {
-	if(IsEffectInSpell(spell_id, SE_HealOverTime))
+	if(IsEffectInSpell(spell_id, SE_HealOverTime) && !IsGroupSpell(spell_id))
 		return true;
 	else
 		return false;
@@ -864,7 +864,7 @@
 
 bool IsCompleteHealSpell(int16 spell_id) {
 	
-	if(spell_id == 13 || IsEffectInSpell(spell_id, SE_CompleteHeal) || IsPercentalHealSpell(spell_id))
+	if(spell_id == 13 || IsEffectInSpell(spell_id, SE_CompleteHeal) || IsPercentalHealSpell(spell_id) && !IsGroupSpell(spell_id))
 		return true;
 	else
 		return false;
@@ -873,7 +873,7 @@
 bool IsFastHealSpell(int16 spell_id) {
 	const int MaxFastHealCastingTime = 2000;
 
-	if(spells[spell_id].cast_time <= MaxFastHealCastingTime && spells[spell_id].effectid[0] == 0 && spells[spell_id].base[0] > 0)
+	if(spells[spell_id].cast_time <= MaxFastHealCastingTime && spells[spell_id].effectid[0] == 0 && spells[spell_id].base[0] > 0 && !IsGroupSpell(spell_id))
 		return true;
 	else
 		return false;
@@ -882,14 +882,48 @@
 bool IsRegularSingleTargetHealSpell(int16 spell_id) {
 	bool result = false;
 
-	if(spells[spell_id].effectid[0] == 0 && spells[spell_id].base[0] > 0 && spells[spell_id].targettype == ST_Target
-		&& !IsFastHealSpell(spell_id) && !IsCompleteHealSpell(spell_id) && !IsHealOverTimeSpell(spell_id)) {
+	if(spells[spell_id].effectid[0] == 0 && spells[spell_id].base[0] > 0 && spells[spell_id].targettype == ST_Target && spells[spell_id].buffduration == 0
+		&& !IsFastHealSpell(spell_id) && !IsCompleteHealSpell(spell_id) && !IsHealOverTimeSpell(spell_id) && !IsGroupSpell(spell_id)) {
 		result = true;
 	}
 
 	return result;
 }
 
+bool IsRegularGroupHealSpell(int16 spell_id) {
+
+        if(IsGroupSpell(spell_id) && !IsCompleteHealSpell(spell_id) && !IsHealOverTimeSpell(spell_id))
+                return true;
+        else
+                return false;
+}
+
+bool IsGroupCompleteHealSpell(int16 spell_id) {
+
+        if(IsGroupSpell(spell_id) && IsCompleteHealSpell(spell_id))
+                return true;
+        else
+                return false;
+}
+
+bool IsGroupHealOverTimeSpell(int16 spell_id) {
+
+        if(IsGroupSpell(spell_id) && IsHealOverTimeSpell(spell_id) && spells[spell_id].buffduration < 10)
+                return true;
+        else
+                return false;
+}
+
+bool IsDebuffSpell(int16 spell_id) {
+
+        if(IsBeneficialSpell(spell_id) || IsEffectHitpointsSpell(spell_id) || IsStunSpell(spell_id) || IsMezSpell(spell_id) 
+			|| IsCharmSpell(spell_id) || IsSlowSpell(spell_id) || IsEffectInSpell(spell_id, SE_Root) 
+			|| IsEffectInSpell(spell_id, SE_MovementSpeed) || IsFearSpell(spell_id))
+                return false;
+        else
+                return true;
+}
+
 uint32 GetMorphTrigger(uint32 spell_id) 
 {
 	for(int i = 0; i < EFFECT_COUNT; ++i)
@@ -1003,4 +1037,4 @@
 			return true;
 	}
 	return false;
-}
\ No newline at end of file
+}

If there are any questions about anything in here or want something taken out, just let me know.
Reply With Quote
 


Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off

Forum Jump

   

All times are GMT -4. The time now is 12:37 PM.


 

Everquest is a registered trademark of Daybreak Game Company LLC.
EQEmulator is not associated or affiliated in any way with Daybreak Game Company LLC.
Except where otherwise noted, this site is licensed under a Creative Commons License.
       
Powered by vBulletin®, Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
Template by Bluepearl Design and vBulletin Templates - Ver3.3