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

Reply
 
Thread Tools Display Modes
  #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
  #2  
Old 11-12-2009, 11:07 AM
bad_captain
Developer
 
Join Date: Feb 2009
Location: Cincinnati, OH
Posts: 512
Default

I also have other fixes that I'm currently testing (95% coded), and should have up sometime this weekend, barring any problems anyone finds with this code. The changes include:
  • Hybrids not stopping to heal as much.
  • Other direct heals for Cleric besides Complete Heal
  • Fast Heals for Cleric
  • Group Heals (HoT and Direct)
  • Better AI

If I don't hear about any bugs with the above code, I'll submit these changes once I can test out the group heals (Need to do some raids!).
Reply With Quote
  #3  
Old 11-12-2009, 02:27 PM
leslamarch
Discordant
 
Join Date: Sep 2006
Location: Green Bay, WI
Posts: 436
Default

nice work on this, the AI has really increase with this patch.
Reply With Quote
  #4  
Old 11-13-2009, 01:32 PM
Kaain
Fire Beetle
 
Join Date: Jan 2009
Location: Texas
Posts: 18
Default

Sorry Capn... are these code changes on the SVN as a branch or anything? I would like to help test your changes out, but not really comfortable (read experienced) doing the code changes myself, although I would be ok with attempting a windows compile from the downloaded source off SVN if that were possible.

Kaain
Reply With Quote
  #5  
Old 11-13-2009, 01:50 PM
bad_captain
Developer
 
Join Date: Feb 2009
Location: Cincinnati, OH
Posts: 512
Default

Quote:
Originally Posted by Kaain View Post
Sorry Capn... are these code changes on the SVN as a branch or anything? I would like to help test your changes out, but not really comfortable (read experienced) doing the code changes myself, although I would be ok with attempting a windows compile from the downloaded source off SVN if that were possible.

Kaain
No, they're not. This is my first submission, so I don't think I have the ability to commit anything to the SVN. (I'm not sure I would feel comfortable doing it yet, even if I did) I think I need to wait for someone official to add it.


Thanks for the kind words, leslamarch. I play on my own server by myself, so healing bots are kind of required for me to be able to play without giving myself the best equipment. I just unfortunately don't have the hours and hours to play..
Reply With Quote
  #6  
Old 11-15-2009, 04:49 PM
Melcrin
Fire Beetle
 
Join Date: Dec 2007
Location: home
Posts: 13
Default

Good work man, I wish others would spend more time on bot AI than fluff. Very good work. Get bards working and I'll reward you with further accolades.
Reply With Quote
  #7  
Old 11-16-2009, 12:08 AM
bad_captain
Developer
 
Join Date: Feb 2009
Location: Cincinnati, OH
Posts: 512
Default

Quote:
Originally Posted by Melcrin View Post
Good work man, I wish others would spend more time on bot AI than fluff. Very good work. Get bards working and I'll reward you with further accolades.

Thanks. After I get the 2nd round of healing updates posted (soon as I can test group heals thoroughly and figure out what's screwy with pets), I can take a look at bards. Unfortunately, I have a feeling the fix will be a bit more involved, having looked through the code some. Who knows, though.
Reply With Quote
  #8  
Old 11-19-2009, 04:54 PM
laxative
Hill Giant
 
Join Date: Aug 2008
Location: NorthEast
Posts: 115
Default

Hi,

I just added your code changes, (the only ones in the thread thus far)

I'm really looking forward to trying it out.

-Lax
Reply With Quote
  #9  
Old 11-29-2009, 03:02 AM
prickle
Hill Giant
 
Join Date: Sep 2009
Posts: 147
Default

I just patched this into SVN rev 1052...

testing it out right now. Seems to be doing ok with straight regen spells...

I'm not so sure about multi-component buffs that have a regen segment to them. While testing, I used Ranger lvl 48 buff "Force of Nature" ( http://lucy.allakhazam.com/spell.html?id=2595 ), and my first run with that resulted in no heals from the Cleric...

Other than that it seems good so far... I haven't tried putting a warrior in a group with 4 clerics yet...

I'm gonna keep testing the regen + heals thing for a bit ...

I'll try out a CH cycle tomrorrow
Reply With Quote
  #10  
Old 11-29-2009, 04:04 AM
prickle
Hill Giant
 
Join Date: Sep 2009
Posts: 147
Default

OK, I just looked more into that Force of Nature + heal issue I had... Looking back at my eqclient logs and with additional testing, I found the cleric wasn't healing me because it was out of mana... which is obviousely "normal" :P

So, as far as I can tell, this does fix the regen/chloroplast/regrowth + HOT + Direct Heals bug that a lot of people have been talking about...
Reply With Quote
  #11  
Old 11-29-2009, 05:21 AM
prickle
Hill Giant
 
Join Date: Sep 2009
Posts: 147
Default

CH rotation seems to work ok... though it could go a bit faster. I just tested it against Dozekar the Cursed w/ a 6-group lvl 55 bot raid. The MT was grouped with 4 clerics and a shaman, the MT stayed up until the rampages started coming (around 40% health left on Dozekar).

Prior attempts with a 6-group lvl 55 bot raid against Dozekar would result in failures 100% of the time normally with Dozekar still at about 20% health. This was the first time I killed him without having to resort to more than 8 groups, and did not have to "zerg"...

I think it's probably good as is, but being able to adjust the CH Rotation speed in game would be the cat's meow. As it stands, a recompile for a 3 or even 2 second rotation is acceptable at this point in time, IMO.
Reply With Quote
  #12  
Old 11-30-2009, 10:03 AM
laxative
Hill Giant
 
Join Date: Aug 2008
Location: NorthEast
Posts: 115
Default

This change is awesome.

I did my level 60, 4 group against Venril.

I'm now able to keep the cleric group in the shadows, and maintain my healing needs while staying out of range of his pesky mesmerization. I had problems with my commands macros and wound up wiping.

As soon as I clean up my macros I should be able to do even more testing, but so far these changes have helped me IMMENSELY.

thank you very much.

Lax.
Reply With Quote
  #13  
Old 11-30-2009, 03:17 PM
prickle
Hill Giant
 
Join Date: Sep 2009
Posts: 147
Default

Yeah, same here...

Although, I did find some weirdness yesterday where my group's cleric was refusing to heal during a raid (all other clerics in the other groups were doing their job though)... not sure what that was about, possibly an issue with my compile, but I won't be able to really investigate it on my end until the weekend arrives...
Reply With Quote
  #14  
Old 12-01-2009, 11:30 AM
bad_captain
Developer
 
Join Date: Feb 2009
Location: Cincinnati, OH
Posts: 512
Default

Glad to hear this has helped you guys.

Quote:
Originally Posted by prickle View Post
Yeah, same here...

Although, I did find some weirdness yesterday where my group's cleric was refusing to heal during a raid (all other clerics in the other groups were doing their job though)... not sure what that was about, possibly an issue with my compile, but I won't be able to really investigate it on my end until the weekend arrives...
Let me know what you find out, if anything. I've been having trouble testing with raids, as for some reason, I haven't been able to get bots added to other Bot groups. I haven't had much time to troubleshoot that, but I have wiped my DB and started from scratch to no effect. I know its something with the group leader ID, but can't for the life of me find where it's going wrong.

One issue with lowering the time between complete heals would be that it would allow much more healing in a regular group by backup healers while a cleric was casting complete heal. If the new raid system is implemented, I'm sure the logic could be changed to lower the time between complete heals for groups in a raid, which would help with those issues, while keeping regular group healing as it is. There's nothing like the cleric casting a complete heal, with my druid and beastlord healing me to full before the complete heal lands.

** UPDATE **

Until I can get other bot groups to work, I will have trouble testing out my group heals. I have somehow broken my cleric again (I think from changing how it finds which player to heal). I hope to have enough time in the next couple of weeks to finish testing group heals. Maybe even better than the group heals are something other than complete heal for the cleric to cast.
Reply With Quote
  #15  
Old 12-16-2009, 03:04 PM
prickle
Hill Giant
 
Join Date: Sep 2009
Posts: 147
Default

I've been playing with this code tweak on my server... and i seem to be getting random times where the cleric bot(s) goes on strike. I just can't pin it down to what's causing it yet. My most recent recompile seems to be behaving more reliable though.

So far, the only thing that I could identify as a common denominator has been dieing in the zone i'm bound in. Was in Kael making repeated attempts on King Tormax, and found that my Cleric stopped healing me after my first death. To fix it, I rebound in PoK, and restarted the Kael zone (Kael is a static zone on my server).

I'll see if I can force the cleric to stop healing me again on Friday, and let you know how it goes...
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 08:17 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