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

Reply
 
Thread Tools Display Modes
  #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
  #2  
Old 02-17-2011, 12:22 AM
c0ncrete's Avatar
c0ncrete
Dragon
 
Join Date: Dec 2009
Posts: 719
Default

I'm really glad to see someone putting some effort into this. The second "chance to cast" check always bothered me as well.

Another thing that concerns me is the fact that healers cast specific types of healing spells (or don't heal at all) dependent on whether or not the target of the heal is engaged. This isn't a problem in and of itself, but the way it checks it doesn't seem correct. I'm under the impression that a call to IsEngaged() on a client object will almost always return false, because it just executes return(!hate_list.IsEmpty()). I don't think a client uses the hate list unless it happens to be AI controlled (charmed).

This is, of course, also a problem when running client/group engagement checks when spawning bots.

If I'm not completely off-base with my assumptions, this will work better:

Code:
bool mbrIsEngaged = g->members[i]->IsClient() ? g->members[i]->CastToClient()->GetAggroCount() : g->members[i]->IsEngaged();
Reply With Quote
  #3  
Old 02-17-2011, 11:06 AM
Congdar
Developer
 
Join Date: Jul 2007
Location: my own little world
Posts: 751
Default

that's correct, clients don't use their hatelist unless charmed.
__________________
The Realm
Reply With Quote
  #4  
Old 02-17-2011, 11:12 AM
bad_captain
Developer
 
Join Date: Feb 2009
Location: Cincinnati, OH
Posts: 512
Default

Thanks for the idea. I will work on adding this on, but in its current state, it is no worse than it was before (I guess for clients, it should still find the one with the lowest hpr, instead of just the first one who needs to be healed, and is still better than before).

I guess I just need to know if I should change it before it would be considered for being committed.
Reply With Quote
  #5  
Old 02-17-2011, 01:32 PM
c0ncrete's Avatar
c0ncrete
Dragon
 
Join Date: Dec 2009
Posts: 719
Default

I haven't looked through your code too much, but I'm sure there is significant improvement.

It might be easiest to just make mob->IsEgnaged() a virtual function and overload it in the client object. I can't think of anything that would break. The only time IsEngaged() is used in reference to a client is in the bot code as far as I'm aware. That way, you wouldn't have to mess with all of the group iterations in the bot code.
Reply With Quote
  #6  
Old 02-17-2011, 02:09 PM
realityincarnate
Developer
 
Join Date: Dec 2007
Posts: 122
Default

If you're going to use AggroCount to track whether the client is engaged (and using that with a virtual IsEngaged() seems like a good idea to me), you'll also want to change the IncrementAggroCount() and DecrementAggroCount() functions around a little bit. Currently, they only actually change AggroCount if the rest regen rules are in effect.
Reply With Quote
  #7  
Old 02-24-2011, 12:49 PM
bad_captain
Developer
 
Join Date: Feb 2009
Location: Cincinnati, OH
Posts: 512
Default

Currently working to rewrite some of this. Took out the aggro portion of the heal code, and the fix to keep BSTs from casting Regrowth as a heal broke Bard casting of heal spells. Hopefully will have this finished this weekend.
Reply With Quote
Reply


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 06:46 AM.


 

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 - 2024, Jelsoft Enterprises Ltd.
Template by Bluepearl Design and vBulletin Templates - Ver3.3