Thread: Bot Healing Fix
View Single Post
  #1  
Old 11-12-2009, 10:59 AM
bad_captain
Developer
 
Join Date: Feb 2009
Location: Cincinnati, OH
Posts: 512
Default Bot Healing Fix

I've been working on trying to get the Bots to heal me, and have come up with the following patch. This is the first time I've used diffs, so I'll put at the end what lines need to be replaced.

Diff for mob.h
Code:
Index: zone/mob.h
===================================================================
--- zone/mob.h	(revision 1048)
+++ zone/mob.h	(working copy)

@@ -849,12 +849,14 @@
 	void				SendToFixZ(float new_x, float new_y, float new_z);
 	void				NPCSpecialAttacks(const char* parse, int permtag);
 	inline int32		DontHealMeBefore() const { return pDontHealMeBefore; }
+	inline int32		DontHotMeBefore() const { return pDontHotMeBefore; }
 	inline int32		DontBuffMeBefore() const { return pDontBuffMeBefore; }
 	inline int32		DontDotMeBefore() const { return pDontDotMeBefore; }
 	inline int32		DontRootMeBefore() const { return pDontRootMeBefore; }
 	inline int32		DontSnareMeBefore() const { return pDontSnareMeBefore; }
 	void				SetDontRootMeBefore(int32 time) { pDontRootMeBefore = time; }
 	void				SetDontHealMeBefore(int32 time) { pDontHealMeBefore = time; }
+	void				SetDontHotMeBefore(int32 time) { pDontHotMeBefore = time; }
 	void				SetDontBuffMeBefore(int32 time) { pDontBuffMeBefore = time; }
 	void				SetDontDotMeBefore(int32 time) { pDontDotMeBefore = time; }
 	void				SetDontSnareMeBefore(int32 time) { pDontSnareMeBefore = time; }
@@ -1179,6 +1181,7 @@
 	int PathingTraversedNodes;
 
 	int32	pDontHealMeBefore;
+	int32	pDontHotMeBefore;
 	int32	pDontBuffMeBefore;
 	int32	pDontDotMeBefore;
 	int32	pDontRootMeBefore;
Diff for spells.cpp
Code:
Index: zone/spells.cpp
===================================================================
--- zone/spells.cpp	(revision 1013)
+++ zone/spells.cpp	(working copy)
@@ -2123,7 +2123,6 @@
 		}
 	}
 
-
 	// solar: check for special stacking block command in spell1 against spell2
 	for(i = 0; i < EFFECT_COUNT; i++)
 	{
@@ -2164,6 +2163,11 @@
 	for(i = 0; i < EFFECT_COUNT; i++)
 	{
 		effect2 = sp2.effectid[i];
+
+		if((effect2 == SE_CurrentHP) && (sp2.buffduration == 0) && !IsDetrimentalSpell(spellid1) && !IsDetrimentalSpell(spellid2)) {
+			return 0;
+		}
+
 		if(effect2 == SE_StackingCommand_Overwrite)
 		{
 			overwrite_effect = sp2.base[i];
Diff for bot.cpp
Code:
Index: zone/bot.cpp
===================================================================
--- zone/bot.cpp	(revision 1040)
+++ zone/bot.cpp	(working copy)
@@ -2084,7 +2084,7 @@
 					case SpellType_Heal: {
 						if (
 							( (spells[AIspells[i].spellid].targettype==ST_GroupTeleport || spells[AIspells[i].spellid].targettype == ST_Target || tar == this)
-							&& tar->DontHealMeBefore() < Timer::GetCurrentTime()
+							&& ((tar->DontHealMeBefore() < Timer::GetCurrentTime()) || (tar->DontHotMeBefore() < Timer::GetCurrentTime()))
 							&& tar->CanBuffStack(AIspells[i].spellid, botLevel, true) >= 0))
 						{
 							if(botClass == BARD) {
@@ -2093,7 +2093,7 @@
 								}
 							}
 							int8 hpr = (int8)tar->GetHPRatio();
-							if(hpr<= 80 || ((tar->IsClient()||tar->IsPet()) && (hpr <= 98)) || (botClass == BARD))
+							if(hpr<= 80 || (tar->IsClient() && (hpr <= 98)) || (botClass == BARD))
 							{
 								if(tar->GetClass() == NECROMANCER) {
 									// Necro bots use too much cleric mana with thier
@@ -2104,35 +2104,64 @@
 									}
 								}
 
-								int32 TempDontHealMeBeforeTime = tar->DontHealMeBefore();
+								if(AIspells[i].priority == 1)
+								{
+									if(tar->DontHotMeBefore() < Timer::GetCurrentTime())
+									{
+										int32 TempDontHotMeBeforeTime = tar->DontHotMeBefore();
 
-								AIDoSpellCast(i, tar, mana_cost, &TempDontHealMeBeforeTime);
+										AIDoSpellCast(i, tar, mana_cost, &TempDontHotMeBeforeTime);
 
-								if(TempDontHealMeBeforeTime != tar->DontHealMeBefore())
-									tar->SetDontHealMeBefore(TempDontHealMeBeforeTime);
+										if(TempDontHotMeBeforeTime != tar->DontHotMeBefore()){
+											tar->SetDontHotMeBefore(TempDontHotMeBeforeTime);
 
-								// If the healer is casting a HoT don't immediately cast the regular heal afterwards
-								// The first HoT is at level 19 and is priority 1
-								// The regular heal is priority 2
-								// Let the HoT heal for at least 3 tics before checking for the regular heal
-								// For non-HoT heals, do a 4 second delay
-								if((botClass == CLERIC || botClass == PALADIN) && (botLevel >= 19) && (AIspells[i].priority == 1)) {
-									if(tar->GetOwnerID()) {
-										tar->SetDontHealMeBefore(Timer::GetCurrentTime() + 18000);
+											// If the healer is casting a HoT don't immediately cast the regular heal afterwards
+											// The first HoT is at level 19 and is priority 1
+											// The regular heal is priority 2
+											// Let the HoT heal for at least 3 tics before checking for the regular heal
+											// For non-HoT heals, do a 4 second delay
+											if((botClass == CLERIC || botClass == PALADIN) && (botLevel >= 19)) {
+												if(tar->GetOwnerID()) {
+													tar->SetDontHotMeBefore(Timer::GetCurrentTime() + 18000);
+													tar->SetDontHealMeBefore(Timer::GetCurrentTime() + 6000);
+												}
+												else {
+													tar->SetDontHotMeBefore(Timer::GetCurrentTime() + 18000);
+													tar->SetDontHealMeBefore(Timer::GetCurrentTime() + 2000);
+												}
+											}
+										}
 									}
-									else {
-										tar->SetDontHealMeBefore(Timer::GetCurrentTime() + 12000);
-									}
 								}
-								else if((botClass == CLERIC || botClass == PALADIN) && (botLevel >= 19) && (AIspells[i].priority == 2)) {
-									if(AIspells[i].spellid == 13) { 
-										// Complete Heal 4 second rotation
-										tar->SetDontHealMeBefore(Timer::GetCurrentTime() + 4000);
+								else if(AIspells[i].priority == 2)
+								{
+									if(tar->DontHealMeBefore() < Timer::GetCurrentTime())
+									{
+										int32 TempDontHealMeBeforeTime = tar->DontHealMeBefore();
+
+										AIDoSpellCast(i, tar, mana_cost, &TempDontHealMeBeforeTime);
+
+										if(TempDontHealMeBeforeTime != tar->DontHealMeBefore()){
+											tar->SetDontHealMeBefore(TempDontHealMeBeforeTime);
+
+											// If the healer is casting a HoT don't immediately cast the regular heal afterwards
+											// The first HoT is at level 19 and is priority 1
+											// The regular heal is priority 2
+											// Let the HoT heal for at least 3 tics before checking for the regular heal
+											// For non-HoT heals, do a 4 second delay
+											if((botClass == CLERIC || botClass == PALADIN) && (botLevel >= 19)) {
+												if(AIspells[i].spellid == 13) { 
+													// Complete Heal 4 second rotation
+													tar->SetDontHealMeBefore(Timer::GetCurrentTime() + 4000);
+												}
+												else {
+													tar->SetDontHealMeBefore(Timer::GetCurrentTime() + 1000);
+												}
+											}
+										}
 									}
-									else {
-										tar->SetDontHealMeBefore(Timer::GetCurrentTime() + 1000);
-									}
 								}
+
 								return true;
 							}
 						}

Code explanation:
mob.h changes
Need to add the following lines:
Code:
inline int32		DontHotMeBefore() const { return pDontHotMeBefore; }
Code:
void				SetDontHotMeBefore(int32 time) { pDontHotMeBefore = time; }
Code:
int32	pDontHotMeBefore;
This adds Bot property pDontHotMeBefore which is used to keep track of the last time a HoT spells was cast on the bot. Currently, whenever the bot has a HoT spell cast on them, it sets pDontHealMeBefore = Timer::GetCurrentTime() + 18000. This means that after a HoT spell is cast on that Bot (or player), they will not be healed at all for 3 ticks. If the spell is Complete Heal, that means the player would not be healed for 28 seconds after the HoT spell is cast = dead player.

spells.cpp changes
Add the following if statement in the for loop checking the effects of spell 2 in Mob::CheckStackConflict()
Code:
if((effect2 == SE_CurrentHP) && (sp2.buffduration == 0) && !IsDetrimentalSpell(spellid1) && !IsDetrimentalSpell(spellid2)) {
			return 0;
		}
A similar line of code is in Bot::CheckStackConflict() before calling Mob::CheckStackConflict() which is supposed to allow bots to heal over a regen spell, but this function is not called if the target is a client. (The Bot cleric could heal the main tank if it were a bot, but would not heal myself as a bard). The spell was being blocked by the Druid regen spell Pack Chloroplast. The direct heal was trying to overwrite the regen spell, but it was told to fail on overwrite. This is the code that could possibly be changed if there are stacking issues, but if the spell being cast is a healing spell with a duration of 0 (instant type heal), it will allow the spell. If a spell has a secondary effect that could be blocked, I'm assuming that the entire spell would have a duration.

bot.cpp changes
change the following case for SpellType_Heal in Bot::Bot_AICastSpell()
Code:
case SpellType_Heal: {
		if (
			( (spells[AIspells[i].spellid].targettype==ST_GroupTeleport || spells[AIspells[i].spellid].targettype == ST_Target || tar == this)
			&& ((tar->DontHealMeBefore() < Timer::GetCurrentTime()) || (tar->DontHotMeBefore() < Timer::GetCurrentTime()))
			&& tar->CanBuffStack(AIspells[i].spellid, botLevel, true) >= 0))
		{
			if(botClass == BARD) {
				if(IsEffectInSpell(AIspells[i].spellid, SE_MovementSpeed) && !zone->CanCastOutdoor()) {
					break;
				}
			}
			int8 hpr = (int8)tar->GetHPRatio();
			if(hpr<= 80 || (tar->IsClient() && (hpr <= 98)) || (botClass == BARD))
				{
				if(tar->GetClass() == NECROMANCER) {
					// Necro bots use too much cleric mana with thier
					// mana for life spells... give them a chance
					// to lifetap something
					if(hpr > 60) {
						break;
						}
					}

					// If the healer is casting a HoT don't immediately cast the regular heal afterwards
				        // The first HoT is at level 19 and is priority 1
					// The regular heal is priority 2
					// Let the HoT heal for at least 3 tics before checking for the regular heal
					// For non-HoT heals, do a 4 second delay
					if(AIspells[i].priority == 1)
					{
						if(tar->DontHotMeBefore() < Timer::GetCurrentTime())
						{
							int32 TempDontHotMeBeforeTime = tar->DontHotMeBefore();

								AIDoSpellCast(i, tar, mana_cost, &TempDontHotMeBeforeTime);

								if(TempDontHotMeBeforeTime != tar->DontHotMeBefore())
								{
			                                                tar->SetDontHotMeBefore(TempDontHotMeBeforeTime);

											if(tar->GetOwnerID()) {
												tar->SetDontHotMeBefore(Timer::GetCurrentTime() + 18000);
												tar->SetDontHealMeBefore(Timer::GetCurrentTime() + 6000);
											}
											else {
												tar->SetDontHotMeBefore(Timer::GetCurrentTime() + 18000);
												tar->SetDontHealMeBefore(Timer::GetCurrentTime() + 2000);
											}
										}
									}
								}
								else if(AIspells[i].priority == 2)
								{
									if(tar->DontHealMeBefore() < Timer::GetCurrentTime())
									{
										int32 TempDontHealMeBeforeTime = tar->DontHealMeBefore();

										AIDoSpellCast(i, tar, mana_cost, &TempDontHealMeBeforeTime);

										if(TempDontHealMeBeforeTime != tar->DontHealMeBefore())
										{
											tar->SetDontHealMeBefore(TempDontHealMeBeforeTime);

											if(AIspells[i].spellid == 13) { 
												if( tar != this )
												{
													this->Say("%s, %s incoming in %d seconds, please stay within range!", tar->GetCleanName(), spells[AIspells[i].spellid].name, spells[AIspells[i].spellid].cast_time);
												} 
												// Complete Heal 4 second rotation
												tar->SetDontHealMeBefore(Timer::GetCurrentTime() + 4000);
											}
											else {
												tar->SetDontHealMeBefore(Timer::GetCurrentTime() + 1000);
											}
										}
									}
								}

								return true;
							}
						}
						break;
										 }
The first if statement, I added a check for the new DontHotMeBefore(), so the bot has to pass either DontHotMeBefore() or DontHealMeBefore() to get in. For the if statement that checks hpr<=80, I took out the tar->IsPet() check for hpr<=98, as it seemed that pets were being healed before me.

The next section checks the priority of the spell: priority == 1 is a HoT heal, while priority == 2 is a direct heal. Currently, since it checks priority ==1 first, if the timer has passed, the bot will cast the HoT spell and set a new DontHealBefore time. Once that time has passed, it checks priority == 1 first again, causing the bot to cast the HoT again, and it will never get to cast a direct heal.

Once the heal type is determined, it then checks if the DontHotMeBefore (priority == 1) or DontHealMeBefore (priority == 2) is less than the current time, otherwise it's not time to heal them again. After the AIDoSpellCast() call, if the TempDontHotMeBeforeTime or TempDontHealMeBeforeTime have changed (meaning the spell is being cast and was not interrupted, etc.), then set the new DontHotMeBefore or DontHealMeBefore. It currently sets it even if the TempDontHotMeBeforeTime or TempDontHealMeBeforeTime have not changed, which means the spell could have fizzled or been interrupted, but it still sets a new DontHealMeBefore (especially bad if it were a HoT, and it won't check again for a new heal for 18 seconds).

Now, if a direct heal is cast, it sets a new DontHealMeBefore == 4 seconds for Complete Heal and 1 second otherwise.

One thing I did notice, was that if I have a cleric start casting a complete heal, and a druid or shaman in my group, the druid or shaman will have healed me some before the complete heal lands. I know this could be fixed by setting DontHealMeBefore to CurrentTime + 10 seconds, but it's at 4 seconds to allow a complete heal rotation. Besides some wasted mana, it may not be so bad right now to allow this, because currently, you could have a cleric cast a complete heal when someone is at 10%, since there are no other healing spells a cleric can use besides complete heal from lvl 39 to 69.

Feel free to give feedback and advice if you decide to test it out.
Reply With Quote