• You've discovered RedGuides, an EverQuest multi-boxing and scripting community 🧙‍♀️⚙️. We want you to play several EQ characters at once, come join us and say hello! 👋

  • A TLP without truebox has thawed (Very Vanilla ready)
    Frostreaver

Writing a bot plugin

PeteSampras

Active member
Joined
Mar 18, 2012
RedCents
2,143¢
EDIT:
Source code: https://github.com/PeteSampras/MQ2Bot
status board/roadmap: https://app.gitkraken.com/glo/board/Xg3hmI40NwAQ87cJ

So I see a lot of interest in people writing plugins as opposed to macros and thought I might share my process for a couple routines as I rewrite mine. This will benefit the community by showing you some methodology and some best practices that I have found through trial and error, and benefit me by having input from others that actually know c/c++ where as I am just a newb at it. I will be putting up info in individual posts and probably editing this initial thread to show an overall flow. My rework is only just started and I have only created a single struct at this point. So for now, let's just get a a few basic steps done.

I will skip around my thought process im sure so it may get a little confusing at times. Like first I would kind of draw out what I wanted to do, all the tasks i want to accomplish, and then figure out how they connect and where I can use common variables. I am skipping that for right now and doing a little cart before the horse because i know what I need already from writing my mq2bot plugin.

Initial setup:
Follow the how to guide on the VIP site to create a plugin (http://www.macroquest2.com/phpBB3/viewtopic.php?f=31&t=6310), let's say I called this one "MQ2PluginName", and let's set up a header section for what we think we will need for now. You will have a bunch of other sections created in your plugin and we will get to them as we get to them but for now you can just leave them alone.I am sure I will need vectors, and i might need a map.

Simple header for now:
Rich (BB code):
// MQ2PluginName.cpp : Defines the entry point for the DLL application.
// Author: PeteSampras
#pragma region Headers
// Can safely ignore these warnings
#pragma warning ( disable : 4710 4365 4018 4244 4505 4189 4101 4100 )
#include <vector>
#include <map>
#include <iterator>
#pragma endregion Headers

Also let's create a section for our baseline variables and structs that will need to be used.
Rich (BB code):
#pragma region Structs

#pragma endregion Structs



#pragma region Variables

#pragma endregion Variables

So that gives me a very basic shell for defining some variables to use while maintaining some easy to read sections later. I am using free visual studio community 2015 with ReSharper as part of the intellisense color coding.

I know that for any plugin that is using spells or skills that I will want some common core information about each spell and several options just like in a macro. I cracked open my bot macro and pulled out every option in there for each spell and made a struct based on that. Also, i know that i may or may not want to use mq2cast and so i might want to include some options from that. So what are some options I want for a spell? This is still a working concept that can easily be modified but here is what I have so far before revisiting mq2cast options by creating a struct called "BotSpells":

Struct:

Rich (BB code):
#pragma region Structs
typedef struct _BotSpells
{
    PSPELLINFO            Spell;
    char                SpellName[MAX_STRING];
    char                SpellIconName[MAX_STRING];
    char                Gem[MAX_STRING];
    char                If[MAX_STRING];
    char                Target[MAX_STRING];
    char                SpellCat[MAX_STRING];
    char                SpellType[MAX_STRING];
    int                    UseOnce;
    int                    ForceCast;
    int                    Use;
    int                    StartAt;
    int                    StopAt;
    int                    NamedOnly;
    int                    Priority;
    ULONGLONG            Recast;
    ULONGLONG            LastCast;
} BotSpells, *PBotSpells;

#pragma endregion Structs

Oh the whitespacing on this forums is all jacked up and im not going to modify these by hand, but suffice to say the whitespace all works in visual studio for me so that it is easy to read. So every variable i added in that struct is something i plan on having an optional INI entry for that if you dont have an ini entry, it will resort to a default value. I am sure I will add other options as i continue to flesh this out, but that is a solid start of things I would care about a spell on when to use it and when i last used it.

Will pick up more in the next post during lunch or after work.
 
Last edited:
Thank you Pete! Any idea how I can fix the whitespace issues?
The same spacing issue happens when i paste into notepad so i think it is just that visual studio tabbing is different.

- - - Updated - - -

I worked a little more and realized that I was missing a couple options on the spell struct and i added in comments for everything. I also added in some misc functions that are going to be required later. I still need to decide how I want to load and check spells. The way I currently do it is fine, but I think i can dramatically reduce the footprint by changing methodology. I added a sample routine prototype to remind me that it would be a better way to pass info and centralize functionality to reduce redundancy.

Header
Rich (BB code):
// MQ2PluginName.cpp : Defines the entry point for the DLL application.
// Author: PeteSampras
#pragma region Headers
// Can safely ignore these warnings
#pragma warning ( disable : 4710 4365 4018 4244 4505 4189 4101 4100 )
#include <vector>
#include <map>
#include <iterator>
#pragma endregion Headers

Presetup to create the INI and other misc stuff for any plugin:
Rich (BB code):
PreSetup("MQ2PluginName");

Inline functions for later use to figure out wtf spells are, what windows are open, and if i am even in game. not having these checks can cause crashes down the road.
Rich (BB code):
#pragma region Inlines
// Returns TRUE if character is in game and has valid character data structures
inline bool InGameOK()
{
	return(GetGameState() == GAMESTATE_INGAME && GetCharInfo() && GetCharInfo()->pSpawn && GetCharInfo2());
}
// Returns TRUE if the specified UI window is visible
static inline BOOL WinState(CXWnd *Wnd)
{
	return (Wnd && ((PCSIDLWND)Wnd)->dShow);
}
static inline LONG GetSpellAttribX(PSPELL spell, int slot) {
	return slot < GetSpellNumEffects(spell) ? GetSpellAttrib(spell, slot) : 254;
}
static inline LONG GetSpellBaseX(PSPELL spell, int slot) {
	return slot < GetSpellNumEffects(spell) ? GetSpellBase(spell, slot) : 0;
}
static inline LONG GetSpellBase2X(PSPELL spell, int slot) {
	return slot < GetSpellNumEffects(spell) ? GetSpellBase2(spell, slot) : 0;
}
static inline LONG GetSpellMaxX(PSPELL spell, int slot) {
	return slot < GetSpellNumEffects(spell) ? GetSpellMax(spell, slot) : 0;
}
static inline LONG GetSpellCalcX(PSPELL spell, int slot) {
	return slot < GetSpellNumEffects(spell) ? GetSpellCalc(spell, slot) : 0;
}
#pragma endregion Inlines

Structs to track spells and spawns and pretty much anything you'd want to do with either.
Rich (BB code):
#pragma region Structs
// struct for spell data, how to use the spell, and how the spell was used
typedef struct _BotSpells
{
	PSPELL			Spell;				// store the spell itself so I can access all of its members
	char				SpellName[MAX_STRING];		// Check ini to see if they want to use a custom spell, if not then store the name of the spell/AA/Disc/item we actually want to /cast since that is often different
	bool				CanIReprioritize;		// if a specific custom spell or priority was set, then i want to ignore reprioritizing it
	char				SpellIconName[MAX_STRING];	// store the name of the icon in case it is special like mana reiteration buff icon
	char				Gem[MAX_STRING];		// in case they want to use a custom gem, otherwise this will be alt, disc, item, or default gem
	char				If[MAX_STRING];			// is there a custom if statement in the ini?
	char				Target[MAX_STRING];		// is there a custom target in the ini?  this is really a placeholder until i make a final decision on how to use spells
	char				SpellCat[MAX_STRING];		// what SpellCategory() is this, so i dont have to check over and over
	char				SpellType[MAX_STRING];		// this will be a custom spell type for each routine.  Ie. Fast Heal, Normal Heal, Fire, Cold, Tash, because I will use this info later on checks and prioritization
	ULONGLONG			Duration;				// my actual duration, will use GetDuration2() and store it so i dont have to calculate every cast.
	int				UseOnce;			// only use this spell once per mob? this will be an option ini entry
	int				ForceCast;			// should i force memorize this spell if it is not already memmed?  ini entry, will be 1 on buffs, optional on rest
	int				Use;				// should i use this spell at all?  this will allow people to memorize but not use spells in case they want manual control
	int				StartAt;			// individual control of when to start using this spell on friend or foe
	int				StopAt;				// individual control of when to stop using this spell on friend or foe
	int				NamedOnly;			// ini entry to let user determine if they only want to use this on a named.  could use it as a level to say only use it on named above this level to avoid wasting it on easy named. int for now
	int				Priority;			// ini entry that will default to 0, but we can set this priority internally for things like heals and nukes to determine order of cast.  higher number = higher priority
	ULONGLONG			Recast;				// ini entry that will give us a timestamp to reuse a spell if different from it's fastest refresh.  I may only want to cast swarm pets once every 30 seconds even though i could every 12.
	ULONGLONG			LastCast;			// timestamp of last time i cast this spell
	int				LastTargetID;			// last target id that i cast this spell on.
	// will need to add more members here once i go back and determine if i want to use mq2cast at all or create an internal casting mechanism.
} BotSpells, *PBotSpells;



typedef struct _Spawns
{
	vector<PSPELL>		vSpellList;
	vector<ULONGLONG>	vSpellTimers;
	char				SpawnBuffList[MAX_STRING];
	int					ID;
	PSPAWNINFO			Spawn;
	bool				NeedsCheck;
	ULONGLONG			LastChecked;
	ULONGLONG			Slow;  //mq2 time stamp of when slow should last until, repeat et al for rest
	ULONGLONG			Malo;
	ULONGLONG			Tash;
	ULONGLONG			Haste;
	ULONGLONG			Root;
	ULONGLONG			Snare;
	ULONGLONG			Mez;
	ULONGLONG			DS;
	ULONGLONG			RevDS;
	ULONGLONG			Cripple;
	ULONGLONG			Charge;
	ULONGLONG			Concuss;
	ULONGLONG			MindFroze;
	ULONGLONG			Charm;
	ULONGLONG			Aego;
	ULONGLONG			Skin;
	ULONGLONG			Focus;
	ULONGLONG			Regen;
	ULONGLONG			Symbol;
	ULONGLONG			Clarity;
	ULONGLONG			Pred;
	ULONGLONG			Strength;
	ULONGLONG			Brells;
	ULONGLONG			SV;
	ULONGLONG			SE;
	ULONGLONG			HybridHP;
	ULONGLONG			Growth;
	ULONGLONG			Shining;
	ULONGLONG			DeepSleep;
	int				PoisonCounters;
	int				DiseaseCounters;
	int				CorruptedCounters;
	int				CurseCounters;
	int				DetrimentalCounters;
	ULONGLONG			Hot;
	ULONGLONG			Fero;
} Spawns, *PSpawns;


#pragma endregion Structs

Just starting a couple variables to flesh out heals and nukes, many more will be added.
Rich (BB code):
#pragma region Variables

vector<_BotSpells>	vHeals,vNukes;
char	CurrentMood[MAX_STRING]={0}, CurrentRoutine[MAX_STRING]={0},  spellCat[MAX_STRING] = { 0 };
ULONGLONG	SpellTimer=0,HealTimer=0,NukeTimer=0;
std::map<string, string> SpellIf;
std::map<string, string>::iterator SpellIt;
#pragma endregion Variables

Some functions to figure out the data I want in the spell struct.
Rich (BB code):
#pragma region Functions
void SpellCategory(PSPELL pSpell)
{
	::sprintf(spellCat, "Unknown");
	if (!pSpell)
		return;
	if (DWORD cat = pSpell->Subcategory) {
		if (PVOID ptr = pCDBStr->GetString(cat, 5, NULL)) {
			::sprintf(spellCat, "%s", pCDBStr->GetString(cat, 5, NULL));
		}
	}
	if (pSpell->Subcategory == 81 && strstr(pSpell->Name, "Mal") || pSpell->SpellIcon == 55)
	{
		::sprintf(spellCat, "Malo");
		return;
	}
	if ((pSpell->Subcategory == 81 || pSpell->SpellIcon == 72) && strstr(pSpell->Name, "Tash"))
	{
		::sprintf(spellCat, "Tash");
		return;
	}
	if (pSpell->SpellIcon == 17 || strstr(pSpell->Name, "Helix of the Undying") || strstr(pSpell->Name, "Deep Sleep's Malaise"))
	{
		::sprintf(spellCat, "Slow");
		return;
	}
	if (pSpell->SpellIcon == 5 || pSpell->SpellIcon == 160 && strstr(pSpell->Name, "Darkness"))
	{
		::sprintf(spellCat, "Snare");
		return;
	}
	if (pSpell->SpellIcon == 117)
	{
		::sprintf(spellCat, "Root");
		return;
	}
	if (pSpell->Subcategory == 35 || pSpell->SpellIcon == 25)
	{
		::sprintf(spellCat, "Mez");
		return;
	}
	if (pSpell->Subcategory == 30 || pSpell->SpellIcon == 50)
	{
		::sprintf(spellCat, "Cripple");
		return;
	}
	if (pSpell->SpellIcon == 134 || pSpell->SpellIcon == 16)
	{
		::sprintf(spellCat, "Haste");
		return;
	}
	if (pSpell->SpellIcon == 132 && !strstr(pSpell->Name, "Growth"))
	{
		::sprintf(spellCat, "Aego");
		return;
	}
	if (pSpell->SpellIcon == 131 && pSpell->Subcategory == 46)
	{
		::sprintf(spellCat, "Skin");
		return;
	}
	if (pSpell->SpellIcon == 130 || pSpell->SpellIcon == 133 && strstr(pSpell->Name, "Shield of") || pSpell->SpellIcon == 133 && strstr(pSpell->Name, "Armor of the"))
	{
		::sprintf(spellCat, "Focus");
		return;
	}
	if (pSpell->SpellIcon == 118 && pSpell->Subcategory == 43)
	{
		::sprintf(spellCat, "Regen");
		return;
	}
	if (pSpell->SpellIcon == 150 && pSpell->Subcategory == 112 && pSpell->Category == 45)
	{
		::sprintf(spellCat, "Symbol");
		return;
	}
	if (pSpell->SpellIcon == 21 && !strstr(pSpell->Name, "Knowledge") && !strstr(pSpell->Name, "Recourse") && !strstr(pSpell->Name, "Soothing") && !strstr(pSpell->Name, "Brell") && pSpell->Subcategory == 59 && pSpell->Category == 79)
	{
		::sprintf(spellCat, "Clarity");
		return;
	}
	if (pSpell->SpellIcon == 123 && strstr(pSpell->Name, "of the Predator") || pSpell->SpellIcon == 158 && strstr(pSpell->Name, "Protection of the"))
	{
		::sprintf(spellCat, "Pred");
		return;
	}
	if (pSpell->SpellIcon == 123 && strstr(pSpell->Name, "Strength of the") || pSpell->SpellIcon == 158 && strstr(pSpell->Name, "Protection of the"))
	{
		::sprintf(spellCat, "Strength");
		return;
	}
	if (pSpell->SpellIcon == 90 && strstr(pSpell->Name, "Brell's"))
	{
		::sprintf(spellCat, "Brells");
		return;
	}
	if (pSpell->SpellIcon == 1 && strstr(pSpell->Name, "Spiritual V"))
	{
		::sprintf(spellCat, "SV");
		return;
	}
	if (pSpell->SpellIcon == 72 && strstr(pSpell->Name, "Spiritual E"))
	{
		::sprintf(spellCat, "SE");
		return;
	}
	if (pSpell->SpellIcon == 132 && (strstr(pSpell->Name, "Growth") || strstr(pSpell->Name, "Stance")))
	{
		::sprintf(spellCat, "Growth");
		return;
	}
	if (pSpell->SpellIcon == 70 && strstr(pSpell->Name, "Shining"))
	{
		::sprintf(spellCat, "Shining");
		return;
	}
	if (pSpell->SpellType != 0 && pSpell->Category == 125 && pSpell->Subcategory == 21 && pSpell->TargetType != 6)
	{
		for (int x = 0; x < GetSpellNumEffects(pSpell); x++)
		{
			if (GetSpellAttrib(pSpell, x) == 59)
			{
				::sprintf(spellCat, "DS%d", x + 1);
				return;
			}
		}
	}
	if (HasSpellAttrib(pSpell, 85) && (strstr(pSpell->Name, "Talisman of the ") || strstr(pSpell->Name, "Spirit of the ")) && pSpell->SpellIcon == 2)
	{
		::sprintf(spellCat, "Panther");
		return;
	}
	if (strstr(pSpell->Name, "Ferocity") && pSpell->SpellIcon == 81)
	{
		::sprintf(spellCat, "Fero");
		return;
	}
	if (strstr(pSpell->Name, "Retort") && pSpell->SpellIcon == 2)
	{
		::sprintf(spellCat, "Retort");
		return;
	}
	if (pSpell->Category == 42 && pSpell->Subcategory == 32)
	{
		::sprintf(spellCat, "HoT");
		return;
	}
}

DWORD GetSpellDuration2(PSPELL pSpell)
{
	if (!InGameOK())
		return 0;
	if (!pSpell)
		return 0;
	int pSpellDuration = 0;
	pSpellDuration = GetSpellDuration(pSpell, (PSPAWNINFO)pLocalPlayer);
	for (int i = 0; i < GetSpellNumEffects(pSpell); i++)
	{
		int attrib = GetSpellAttrib(pSpell, i);

		if (attrib == 323 || attrib == 374 || attrib == 419 || attrib == 339 || attrib == 85 || attrib == 340)
		{
			PSPELL pSpell2 = GetSpellByID(GetSpellBase2(pSpell, i));
			int duration = pSpell2 ? GetSpellDuration(pSpell2, (PSPAWNINFO)pLocalPlayer) : 0;

			if (duration > pSpellDuration)
				pSpellDuration = duration;
		}
	}
	return pSpellDuration;
}

#pragma endregion Functions

Need to set all those values in the spell eventually even though I have not quite created the spells as of this post. I actually dont even know if I can set the values like this or if strcopy is the proper method even if the rest is right. Will worry about it later I just want to get the concept on paper. I know for sure the SpellName check needs moved to the actual routine that is creating the spell vector, i just put it in here for now to remind me.
Rich (BB code):
void LoadSpell(vector<_BotSpells> &v,char VectorName[MAX_STRING])
{
	char szTemp[MAX_STRING] = { 0 }, szSpell[MAX_STRING];
	for (int i = 0; i < v.size(); i++)
	{
		// move next 6 lines to the create spell vector
		::sprintf(szSpell, "%sSpellName%d", VectorName, i);
		if (GetPrivateProfileString(INISection, szSpell, NULL, szTemp, MAX_STRING, INIFileName))
			{
			::strcpy(v.SpellName,szTemp);
			v.CanIReprioritize = 0;
			}
		// end move
		SpellCategory(v.Spell);
		::strcpy(v.SpellCat,spellCat);
		::sprintf(szSpell, "%sSpellIconName%d", VectorName, i);
		if (GetPrivateProfileString(INISection, szSpell, NULL, szTemp, MAX_STRING, INIFileName))
			::strcpy(v.SpellIconName,szTemp);
		::sprintf(szSpell, "%sIf%d", VectorName, i);
		if (GetPrivateProfileString(INISection, szSpell, NULL, szTemp, MAX_STRING, INIFileName))
			::strcpy(v.If,szTemp);
		::sprintf(szSpell, "%sGem%d", VectorName, i);
		if (GetPrivateProfileString(INISection, szSpell, NULL, szTemp, MAX_STRING, INIFileName))
			::strcpy(v.Gem,szTemp);
		::sprintf(szSpell, "%sUseOnce%d", VectorName, i);
		v.UseOnce = GetPrivateProfileInt(INISection, szSpell, 0, INIFileName);
		::sprintf(szSpell, "%sForceCast%d", VectorName, i);
		v.ForceCast = GetPrivateProfileInt(INISection, szSpell, 0, INIFileName);
		::sprintf(szSpell, "%sUse%d", VectorName, i);
		v.Use = GetPrivateProfileInt(INISection, szSpell, 1, INIFileName);
		::sprintf(szSpell, "%sStartAt%d", VectorName, i);
		v.StartAt = GetPrivateProfileInt(INISection, szSpell, 100, INIFileName);
		::sprintf(szSpell, "%sStopAt%d", VectorName, i);
		v.StopAt = GetPrivateProfileInt(INISection, szSpell, 0, INIFileName);
		::sprintf(szSpell, "%sNamedOnly%d", VectorName, i);
		v.NamedOnly = GetPrivateProfileInt(INISection, szSpell, 0, INIFileName);
		::sprintf(szSpell, "%sPriority%d", VectorName, i);
		v.Priority = GetPrivateProfileInt(INISection, szSpell, 0, INIFileName);
	}
}

This will be the routines area. I added a CreateHeal2 routine so that we can build a list of heals from our ini, our AAs, and our memmed spells.
Rich (BB code):
#pragma region Routines
void CreateHeal2()
{
	if (!InGameOK())
		return;
	if (BardClass || GetCharInfo()->pSpawn->Class == 11 || GetCharInfo()->pSpawn->Class == 12 || GetCharInfo()->pSpawn->Class == 13 || GetCharInfo()->pSpawn->Class == 14)
		return;
	vHeal2.clear();
	::strcpy(CurrentRoutine, &(__FUNCTION__[5]));
	PCHAR szHeal[] = { "Divine Arbitration", "Burst of Life", "Beacon of Life", "Focused Celestial Regeneration", "Celestial Regeneration",
		"Convergence of Spirits", "Union of Spirits", "Focused Paragon of Spirits", "Paragon of Spirit", "Lay on Hands",
		"Hand of Piety", "Ancestral Aid", "Call of the Ancients", "Exquisite Benediction", "Blessing of Tunare", NULL };
	char szBuffer[MAX_STRING];
	char szTemp[MAX_STRING] = { 0 }, szSpell[MAX_STRING];
	int aaIndex;
	_ALTABILITY* aa = nullptr;
	// check ini for healif
	::sprintf(szSpell, "HealIf");
	if (GetPrivateProfileString(INISection, szSpell, NULL, szTemp, MAX_STRING, INIFileName))
	{
		SpellIf.insert(make_pair<string, string>(szSpell, szTemp));
		SpellIt = SpellIf.find(szSpell);
		if (SpellIt != SpellIf.end())
			WriteChatf("HealIf: %s", SpellIt->second.c_str());
	}
	//check ini for custom spell
	::sprintf(szSpell, "%sTotal",CurrentRoutine);
	int customSpells = GetPrivateProfileInt(INISection, szSpell, 0, INIFileName);
	for (int i = 0; i < customSpells; i++)
	{
		::sprintf(szSpell, "%sSpellName%d", CurrentRoutine, i);
		if (GetPrivateProfileString(INISection, szSpell, NULL, szTemp, MAX_STRING, INIFileName))
		{
			aaIndex = GetAAIndexByName(szSpell);
			if (aaIndex > 0)
			{
				aa = pAltAdvManager->GetAAById(aaIndex);
				if (aa && GetSpellByID(aa->SpellID))
				{
					BotSpells spell;
					spell.Spell = GetSpellByID(aa->SpellID);
					::strcpy(spell.SpellName,szSpell);
					spell.CanIReprioritize = 0;
					vHeal2.push_back(spell);
				}
			}
			else
			for (int nSpell = 0; nSpell < NUM_BOOK_SLOTS; nSpell++)
			{
				if (PSPELL pSpell = GetSpellByID(GetCharInfo2()->SpellBook[nSpell]))
				{
					if (!stricmp(szSpell, pSpell->Name))
					{
						BotSpells spell;
						spell.Spell = pSpell;
						::strcpy(spell.SpellName, szSpell);
						spell.CanIReprioritize = 0;
						vHeal2.push_back(spell);
					}
				}
			}
		}
	}
	int test = 0;
	for (int i = 0; szHeal; i++)
	{
		test = 0;
		for (int z = 0; z < vHeal2.size();z++)
		{
			if (!stricmp(vHeal2[z].SpellName, szHeal))
				test = 1;
		}
		if (!test)
		{
			::sprintf(szBuffer, szHeal);
			aaIndex = GetAAIndexByName(szBuffer);
			if (aaIndex > 0)
			{
				aa = pAltAdvManager->GetAAById(aaIndex);
				if (aa && GetSpellByID(aa->SpellID))
				{
					BotSpells spell;
					spell.Spell = GetSpellByID(aa->SpellID);
					::strcpy(spell.SpellName, szSpell);
					spell.CanIReprioritize = 1;
					vHeal2.push_back(spell);
				}
			}
		}
	}
	PSPELL pSpell;
	for (int i = 0; i < NUM_SPELL_GEMS; i++)
	{
		pSpell = GetSpellByID(GetCharInfo2()->MemorizedSpells);
		if (pSpell)
		{
			test = 0;
			for (int z = 0; z < vHeal2.size(); z++)
			{
				if (!stricmp(vHeal2[z].SpellName, pSpell->Name))
					test = 1;
			}
			if (!test)
			{
				if (pSpell->Category == 42 && pSpell->Subcategory != 19 && pSpell->Subcategory != 82 && (pSpell->TargetType == 45 || pSpell->TargetType == 3 || pSpell->TargetType == 5 || pSpell->TargetType == 6 || pSpell->TargetType == 8 || pSpell->TargetType == 41))
				{
					BotSpells spell;
					spell.Spell = pSpell;
					::strcpy(spell.SpellName, szSpell);
					spell.CanIReprioritize = 1;
					vHeal2.push_back(spell);
				}
			}
		}
	}
	LoadBotSpell(vHeal2,"Heal");
}


#pragma endregion Routines
 
Last edited:
I had a few errors in the code for the createheal because i was swapping around too many variables. the cleaned up version works as intended.
Rich (BB code):
void CreateHeal2()
{
	if (!InGameOK())
		return;
	if (BardClass || GetCharInfo()->pSpawn->Class == 11 || GetCharInfo()->pSpawn->Class == 12 || GetCharInfo()->pSpawn->Class == 13 || GetCharInfo()->pSpawn->Class == 14)
		return;
	vHeal2.clear();
	::strcpy(CurrentRoutine, &(__FUNCTION__[6]));
	PCHAR szHeal[] = { "Divine Arbitration", "Burst of Life", "Beacon of Life", "Focused Celestial Regeneration", "Celestial Regeneration",
		"Convergence of Spirits", "Union of Spirits", "Focused Paragon of Spirits", "Paragon of Spirit", "Lay on Hands",
		"Hand of Piety", "Ancestral Aid", "Call of the Ancients", "Exquisite Benediction", "Blessing of Tunare", NULL };
	char szTemp[MAX_STRING] = { 0 }, szSpell[MAX_STRING]={0};
	int aaIndex;
	_ALTABILITY* aa = nullptr;
	// check ini for healif
	::sprintf(szSpell, "HealIf");
	if (GetPrivateProfileString(INISection, szSpell, NULL, szTemp, MAX_STRING, INIFileName))
	{
		SpellIf.insert(make_pair<string, string>(szSpell, szTemp));
		SpellIt = SpellIf.find(szSpell);
		if (SpellIt != SpellIf.end())
			WriteChatf("HealIf: %s", SpellIt->second.c_str());
	}
	//check ini for custom spell
	// ::sprintf(szSpell, "%sTotal",CurrentRoutine);
	::sprintf(szSpell, "HealTotal");
	int customSpells = GetPrivateProfileInt(INISection, szSpell, 0, INIFileName);
	for (int i = 0; i < customSpells; i++)
	{
		//::sprintf(szSpell, "%sSpellName%d", CurrentRoutine, i);
		::sprintf(szSpell, "HealSpellName%d", i);
		if (GetPrivateProfileString(INISection, szSpell, NULL, szTemp, MAX_STRING, INIFileName))
		{
			aaIndex = GetAAIndexByName(szTemp);
			if (aaIndex > 0)
			{
				aa = pAltAdvManager->GetAAById(aaIndex);
				if (aa && GetSpellByID(aa->SpellID))
				{
					BotSpells spell;
					spell.Spell = GetSpellByID(aa->SpellID);
					::strcpy(spell.SpellName, szTemp);
					spell.CanIReprioritize = 0;
					vHeal2.push_back(spell);
				}
			}
			else
			for (int nSpell = 0; nSpell < NUM_BOOK_SLOTS; nSpell++)
			{
				if (PSPELL pSpell = GetSpellByID(GetCharInfo2()->SpellBook[nSpell]))
				{
					if (!stricmp(szTemp, pSpell->Name))
					{
						BotSpells spell;
						spell.Spell = pSpell;
						::strcpy(spell.SpellName, szTemp);
						spell.CanIReprioritize = 0;
						vHeal2.push_back(spell);
					}
				}
			}
		}
	}
	int test = 0;
	for (int i = 0; szHeal; i++)
	{
		test = 0;
		for (int z = 0; z < vHeal2.size();z++)
		{
			if (!stricmp(vHeal2[z].SpellName, szHeal))
				test = 1;
		}
		if (!test)
		{
			::sprintf(szTemp, szHeal);
			aaIndex = GetAAIndexByName(szTemp);
			if (aaIndex > 0)
			{
				aa = pAltAdvManager->GetAAById(aaIndex);
				if (aa && GetSpellByID(aa->SpellID))
				{
					BotSpells spell;
					spell.Spell = GetSpellByID(aa->SpellID);
					::strcpy(spell.SpellName, szTemp);
					spell.CanIReprioritize = 1;
					vHeal2.push_back(spell);
				}
			}
		}
	}
	PSPELL pSpell;
	for (int i = 0; i < NUM_SPELL_GEMS; i++)
	{
		pSpell = GetSpellByID(GetCharInfo2()->MemorizedSpells);
		if (pSpell)
		{
			test = 0;
			for (int z = 0; z < vHeal2.size(); z++)
			{
				if (!stricmp(vHeal2[z].SpellName, pSpell->Name))
					test = 1;
			}
			if (!test)
			{
				if (pSpell->Category == 42 && pSpell->Subcategory != 19 && pSpell->Subcategory != 82 && (pSpell->TargetType == 45 || pSpell->TargetType == 3 || pSpell->TargetType == 5 || pSpell->TargetType == 6 || pSpell->TargetType == 8 || pSpell->TargetType == 41))
				{
					BotSpells spell;
					spell.Spell = pSpell;
					::strcpy(spell.SpellName, pSpell->Name);
					spell.CanIReprioritize = 1;
					vHeal2.push_back(spell);
				}
			}
		}
	}
	LoadBotSpell(vHeal2,"Heal");
}


I then created a command to fill out all the spell data and another to populate an ini on /command.

So this function will fill out whatever vector is passed to it. It will check to see if there is an ini entry, and if not then it will use a default value. I will almost certainly create some sort of function or values to set default values rather than use what is in here. but i will decide what route to use later.
Rich (BB code):
void LoadBotSpell(vector<_BotSpells> &v, char VectorName[MAX_STRING])
{
	char szTemp[MAX_STRING] = { 0 }, szSpell[MAX_STRING];
	for (int i = 0; i < v.size(); i++)
	{
		// move next 6 lines to the create spell vector
		::sprintf(szSpell, "%sSpellName%d", VectorName, i);
		if (GetPrivateProfileString(INISection, szSpell, NULL, szTemp, MAX_STRING, INIFileName))
		{
			::strcpy(v.SpellName, szTemp);
			v.CanIReprioritize = 0;
			WriteChatf("%s=%s", szSpell, szTemp);
		}
		// end move
		SpellCategory(v.Spell);
		::strcpy(v.SpellCat, spellCat);
		::sprintf(szSpell, "%sSpellIconName%d", VectorName, i);
		if (GetPrivateProfileString(INISection, szSpell, NULL, szTemp, MAX_STRING, INIFileName))
		{
			::strcpy(v.SpellIconName, szTemp);
			WriteChatf("%s=%s", szSpell, szTemp);
		}
		::sprintf(szSpell, "%sIf%d", VectorName, i);
		if (GetPrivateProfileString(INISection, szSpell, NULL, szTemp, MAX_STRING, INIFileName))
		{
			::strcpy(v.If, szTemp);
			WriteChatf("%s=%s", szSpell, szTemp);
		}
		::sprintf(szSpell, "%sGem%d", VectorName, i);
		if (GetPrivateProfileString(INISection, szSpell, NULL, szTemp, MAX_STRING, INIFileName))
		{
			::strcpy(v.Gem, szTemp);
			WriteChatf("%s=%s", szSpell, szTemp);
		}
		::sprintf(szSpell, "%sUseOnce%d", VectorName, i);
		v.UseOnce = GetPrivateProfileInt(INISection, szSpell, 0, INIFileName);
		WriteChatf("%s=%d", szSpell, v.UseOnce);
		::sprintf(szSpell, "%sForceCast%d", VectorName, i);
		v.ForceCast = GetPrivateProfileInt(INISection, szSpell, 0, INIFileName);
		WriteChatf("%s=%d", szSpell, v.ForceCast);
		::sprintf(szSpell, "%sUse%d", VectorName, i);
		v.Use = GetPrivateProfileInt(INISection, szSpell, 1, INIFileName);
		WriteChatf("%s=%d", szSpell, v.Use);
		::sprintf(szSpell, "%sStartAt%d", VectorName, i);
		v.StartAt = GetPrivateProfileInt(INISection, szSpell, 100, INIFileName);
		WriteChatf("%s=%d", szSpell, v.StartAt);
		::sprintf(szSpell, "%sStopAt%d", VectorName, i);
		v.StopAt = GetPrivateProfileInt(INISection, szSpell, 0, INIFileName);
		WriteChatf("%s=%d", szSpell, v.StopAt);
		::sprintf(szSpell, "%sNamedOnly%d", VectorName, i);
		v.NamedOnly = GetPrivateProfileInt(INISection, szSpell, 0, INIFileName);
		WriteChatf("%s=%d", szSpell, v.NamedOnly);
		::sprintf(szSpell, "%sPriority%d", VectorName, i);
		v.Priority = GetPrivateProfileInt(INISection, szSpell, 0, INIFileName);
		WriteChatf("%s=%d", szSpell, v.Priority);
	}
}



and this populates an ini on /command
Rich (BB code):
void PopulateIni(vector<_BotSpells> &v, char VectorName[MAX_STRING])
{
	char szTemp[MAX_STRING] = { 0 }, szSpell[MAX_STRING];
	for (int i = 0; i < v.size(); i++)
	{
		::sprintf(szSpell, "%sTotal", VectorName);
		WritePrivateProfileString(INISection, szSpell, itoa(v.size(), szTemp, 10), INIFileName);
		::sprintf(szSpell, "%sSpellName%d", VectorName, i);
		WritePrivateProfileString(INISection, szSpell, v.SpellName, INIFileName);
		::sprintf(szSpell, "%sSpellIconName%d", VectorName, i);
		WritePrivateProfileString(INISection, szSpell, v.SpellIconName, INIFileName);
		::sprintf(szSpell, "%sIf%d", VectorName, i);
		WritePrivateProfileString(INISection, szSpell, v.If, INIFileName);
		::sprintf(szSpell, "%sGem%d", VectorName, i);
		WritePrivateProfileString(INISection, szSpell, v.Gem, INIFileName);
		::sprintf(szSpell, "%sUseOnce%d", VectorName, i);
		WritePrivateProfileString(INISection, szSpell, itoa(v.UseOnce, szTemp, 10), INIFileName);
		::sprintf(szSpell, "%sForceCast%d", VectorName, i);
		WritePrivateProfileString(INISection, szSpell, itoa(v.ForceCast, szTemp, 10), INIFileName);
		::sprintf(szSpell, "%sUse%d", VectorName, i);
		WritePrivateProfileString(INISection, szSpell, itoa(v.Use, szTemp, 10), INIFileName);
		::sprintf(szSpell, "%sStartAt%d", VectorName, i);
		WritePrivateProfileString(INISection, szSpell, itoa(v.StartAt, szTemp, 10), INIFileName);
		::sprintf(szSpell, "%sStopAt%d", VectorName, i);
		WritePrivateProfileString(INISection, szSpell, itoa(v.StopAt, szTemp, 10), INIFileName);
		::sprintf(szSpell, "%sNamedOnly%d", VectorName, i);
		WritePrivateProfileString(INISection, szSpell, itoa(v.NamedOnly, szTemp, 10), INIFileName);
		::sprintf(szSpell, "%sPriority%d", VectorName, i);
		WritePrivateProfileString(INISection, szSpell, itoa(v.Priority, szTemp, 10), INIFileName);
	}
}


and that spits out something like this to the ini:
Rich (BB code):
HealTotal=13
HealSpellName0=Divine Arbitration
HealSpellIconName0=
HealGem0=
HealUseOnce0=0
HealForceCast0=0
HealUse0=1
HealStartAt0=100
HealStopAt0=0
HealNamedOnly0=0
HealPriority0=0
HealSpellName1=Burst of Life
HealSpellIconName1=
HealGem1=
HealUseOnce1=0
HealForceCast1=0
HealUse1=1
HealStartAt1=100
HealStopAt1=0
HealNamedOnly1=0
HealPriority1=0
HealSpellName2=Beacon of Life
HealSpellIconName2=
HealGem2=
HealUseOnce2=0
HealForceCast2=0
HealUse2=1
HealStartAt2=100
HealStopAt2=0
HealNamedOnly2=0
HealPriority2=0
HealSpellName3=Focused Celestial Regeneration
HealSpellIconName3=
HealGem3=
HealUseOnce3=0
HealForceCast3=0
HealUse3=1
HealStartAt3=100
HealStopAt3=0
HealNamedOnly3=0
HealPriority3=0
HealSpellName4=Celestial Regeneration
HealSpellIconName4=
HealGem4=
HealUseOnce4=0
HealForceCast4=0
HealUse4=1
HealStartAt4=100
HealStopAt4=0
HealNamedOnly4=0
HealPriority4=0
HealSpellName5=Exquisite Benediction
HealSpellIconName5=
HealGem5=
HealUseOnce5=0
HealForceCast5=0
HealUse5=1
HealStartAt5=100
HealStopAt5=0
HealNamedOnly5=0
HealPriority5=0
HealSpellName6=Spiritual Remedy Rk. II
HealSpellIconName6=
HealGem6=
HealUseOnce6=0
HealForceCast6=0
HealUse6=1
HealStartAt6=100
HealStopAt6=0
HealNamedOnly6=0
HealPriority6=0
HealSpellName7=Graceful Remedy Rk. II
HealSpellIconName7=
HealGem7=
HealUseOnce7=0
HealForceCast7=0
HealUse7=1
HealStartAt7=100
HealStopAt7=0
HealNamedOnly7=0
HealPriority7=0
HealSpellName8=Undying Life
HealSpellIconName8=
HealGem8=
HealUseOnce8=0
HealForceCast8=0
HealUse8=1
HealStartAt8=100
HealStopAt8=0
HealNamedOnly8=0
HealPriority8=0
HealSpellName9=Fervid Renewal Rk. II
HealSpellIconName9=
HealIf9=
HealGem9=
HealUseOnce9=0
HealForceCast9=0
HealUse9=1
HealStartAt9=100
HealStopAt9=0
HealNamedOnly9=0
HealPriority9=0
HealSpellName10=Fraught Renewal Rk. II
HealSpellIconName10=
HealIf10=
HealGem10=
HealUseOnce10=0
HealForceCast10=0
HealUse10=1
HealStartAt10=100
HealStopAt10=0
HealNamedOnly10=0
HealPriority10=0
HealSpellName11=Word of Greater Reformation Rk. II
HealSpellIconName11=
HealIf11=
HealGem11=
HealUseOnce11=0
HealForceCast11=0
HealUse11=1
HealStartAt11=100
HealStopAt11=0
HealNamedOnly11=0
HealPriority11=0
HealSpellName12=Promised Rehabilitation Rk. II
HealSpellIconName12=
HealGem12=
HealUseOnce12=0
HealForceCast12=0
HealUse12=1
HealStartAt12=100
HealStopAt12=0
HealNamedOnly12=0
HealPriority12=0
 
I didn't actually stop this project, i just traveled for a bit and hit a few roadblocks but I have been updating it throughout. The project is currently hosted on my github and will be updated until it's not. Feel free to post questions, comments, feedback, collaborate, or whatever you kids do these days; or not. Thought I would offer the option since there seems to be more than a handful of people interested in coding plugins on the site.

https://github.com/PeteSampras/mq2

updates since that last post:
I finished the spell structure, added in detection of AAs that don't really fit anywhere else, Heals, and Discs (to cover each type of thing). It will also detect spells that you have memorized or mem/demem and figure them out on the fly. It then prioritizes the master list. You can hardcode your spells via your ini if you want using this type of setup and whatever variables you want to hardcode, the only mandatory field is the SpellXName. Example:

SpellTotal=1
Spell1Name=Complete Heal
Spell1Priority=80
Spell1Gem=3
Spell1StartAt=90
Spell1StopAt=0
Spell1Use=1
Spell1NamedOnly=0
Spell1If=${Me.PctHPs}>30 && !${Me.Pet.ID}

It will use default values if you don't specify them explicitly in the ini. The default values are set by type in the declarations and I have temporary values there for now until some thought can be put into it.

Current commands:
/plugin mq2bot - loads plugin
/bot - turns it on, loads the configure and figures out the AAs, Heals, Discs, memmed spells.
/botlist - lists the prioritized order of every AA/spell/disc it is currently tracking to use.

That list will be checked, in order, until it finds the first spell it can and should cast. That spell will cast once the rest of the code is in place, and the list will start back at the beginning. The plan is to have mez/fade/jolt spells as the highest priority, heals and other survival type spells text, offensive spells next, then finally misc stuff, with buffs/selfbuffs being dead last when nothing else is going on. Fight buffs will be higher priority since i would want those to trigger during battle.

Anyway, that is where it sits. I'm making progress but it won't be completed most likely for several months, i am just posting an update.

- - - Updated - - -

Posted some rudimentary add detection. It will add spawns to the vAdds vector and will write the add list to your window as it changes. It isn't finished and it doesnt fully populate the spawn struct. I will add a check to compare it to the vSpawns vector. That vector will house every spawn we are tracking. If the vAdd has a mob the vSpawn doesnt, the info will get pushed to vSpawn. vSpawn will populate and maintain the overall list. This is needed as spawns deaggro or radius of mob change and they dont get picked up as adds. That way if they have debuffs or mez or other on them, we can track that data without having to retarget. Eventually i will put in a communication piece to share data between toons a la netbots so you can have a battle management type bit that shares all the debuffs of every mob to everyone else in eqbc, but thats further down the road. For now, the most recent addition can be tested by using the the /bot command and then aggroing/deaggroing stuff and ensuring 1. no crashes (especially on mobs that instantly despawn and leave no corpse, or at 50% hp event mobs) and 2. it is tracking everything correctly.

- - - Updated - - -

Added group vector creation and check.
 
Last edited:
added vPets,
added .Priority to Spawns struct
added /bot populate spells
vPets and vGroup populates
vAdds checks against and populates vSpawns
added prioritization functions for spawns
added some notes.

was waiting for string changes but had some ideas i wanted to type out. might do more today but i really do want to wait on the changes so i dont have to go back and redo even more work.
 
took a 3 week break thanks to Dota internationals and i play way too much dota. I was also waiting on some string safety updates to see wtf i was doing. That said: all POSIX and string safety are now updated on the github. I also had to walk back the checking of pet stuff because it kept CTDing. I will just make it its own routine.

- - - Updated - - -

update some additional ::sprintf_s to sprintf_s and strcpy_s as appropriate. having strings all set up correctly means that if there was a buffer overflow or string related crash, then you could actually see where it happened rather than have it be some mystery. makes debugging a heckuva lot easier.
 
Whats ? MQ2VladUtil/MQ2Vlad.h mq2bot.cpp on your github requires that to compile.

that was used in the mq2cast code that i hadnt gotten around to replacing it yet since i dont invoke the mq2cast code yet. I just commented out the vlad include and it looks like it was a custom command for Execute("/book"); in place of EzCommand("/book"); or one of the other various options. i will remove it for future versions. i have been playing waaaaaaaaaaaaaay too much dota lately and not writing the bot. i will eventually get back to the bot because it is fun to learn c/c++ and frankly i dont know how many more peruvians i can deal with in dota.

and i went ahead and posted the change onto the github. some Execute() were changed to HideDoCommand() and others to EzCommand() depending on what it was. none of it is actually called yet either way but it would let you compile without errors.
 
Been lurking these boards forever but only recently have started trying to code myself, and I'm at the point where I'd like to write a custom plugin and possibly custom window to go with it. Unfortunately I can no longer access the MQ2 VIP forums to access the idiot's guide to making a plugin. Is there another resource for the how-to plugin guide?
 
I checked that out before posting here.

ImaNoob posted a very helpful thread here. (VIP Only) Titled appropriately "The Complete Idiots Guide to MQ2 and plugins"

There's a link in that to the VIP section, just curious if the content of that post is readily available outside the VIP forums.
 
Last edited:
You could just donate for access? then you'd have access to everything you need, seems reasonable.
 
I thought VIP access to the MQ2 boards was for life after a small (like $10) donation?
 
Yeah, no more VIP here either. I am totally okay with the change, Devs have and do constantly donate their own time and I have no issue with them being compensated in kind. I just wish I'd have made more of an effort to apply the information I had access to before I no longer had access to it. Either way, if the making a plugin walkthrough is no longer opensource, I'll keep using the trial and error method and see if I can hack something together to figure it out. Thanks for this walk through though, Pete!
 
Writing plugins is basically closed source atm. Even if i provide you with the source code you cant really do anything with it due to it using members that you wont have access to. I never did finish this rewrite but recently took up the idea again and started on another plugin that would either go with it or merge. I just get sidetracked playing dota and havent played eq in like 4 years so i dont maintain motivation without constant feedback from end users. if you have any questions though, just ask on the boards as this is a learning environment.
 
I finally kicked the dota habit, sold off all my hats, and have been working on this again for the last few weeks. I had to completely refactor the code. From a design point of view and seeing how CWTN is making class specific bots I wanted to take a more deliberate approach and hardcode in some things i havent in the past. i touched up the spell detection and prioritization some and am currently working on detecting buffs to make sure every single possible buff is available.. if it makes sense. like enchanters and wizards have a level 10 damage shield.. and while that is cool.. nobody needs that in the bot. beastlords get focus of alladnu at level 67 and then never get another single focus. so i will let it use that spell until level 71 when they get group version. previously ive just allowed the highest of any given line and people have responded that it didnt allow enough flexibility. so my goal now is to detect everything, ask the experts on what skill should be used when.. and auto proiritize those depending on what is happening. raid vs group vs tank tank vs assist, etc. no reason to only detect defense discs if you arent tanking for instance. or not detecting aggro discs that are a level lower than the 2hs disc on same line if you are tanking. happy to chat about plugin design here or in discord (thats easier) or if there is something macros/plugins dont get right that i should factor into mine let me know! im aware that the alliance lines could be super useful to some people but ive never coded them in, for instance. so those types of things would be nice to try to fit in and figure out how to make them work. if anyone gets froggy and wants to try the latest of whatever i have just hit me up in discord and i will drop a copy or you can DL from my git and compile for yourself.

I have been keeping my code somewhat current (i dont do my daily work out of that repo but i manually add to it) at https://github.com/PeteSampras/MQ2Bot with my status board/roadmap at: https://app.gitkraken.com/glo/board/Xg3hmI40NwAQ87cJ
 
I finally kicked the dota habit, sold off all my hats, and have been working on this again for the last few weeks. I had to completely refactor the code. From a design point of view and seeing how CWTN is making class specific bots I wanted to take a more deliberate approach and hardcode in some things i havent in the past. i touched up the spell detection and prioritization some and am currently working on detecting buffs to make sure every single possible buff is available.. if it makes sense. like enchanters and wizards have a level 10 damage shield.. and while that is cool.. nobody needs that in the bot. beastlords get focus of alladnu at level 67 and then never get another single focus. so i will let it use that spell until level 71 when they get group version. previously ive just allowed the highest of any given line and people have responded that it didnt allow enough flexibility. so my goal now is to detect everything, ask the experts on what skill should be used when.. and auto proiritize those depending on what is happening. raid vs group vs tank tank vs assist, etc. no reason to only detect defense discs if you arent tanking for instance. or not detecting aggro discs that are a level lower than the 2hs disc on same line if you are tanking. happy to chat about plugin design here or in discord (thats easier) or if there is something macros/plugins dont get right that i should factor into mine let me know! im aware that the alliance lines could be super useful to some people but ive never coded them in, for instance. so those types of things would be nice to try to fit in and figure out how to make them work. if anyone gets froggy and wants to try the latest of whatever i have just hit me up in discord and i will drop a copy or you can DL from my git and compile for yourself.

I have been keeping my code somewhat current (i dont do my daily work out of that repo but i manually add to it) at https://github.com/PeteSampras/MQ2Bot with my status board/roadmap at: https://app.gitkraken.com/glo/board/Xg3hmI40NwAQ87cJ

Welcome back ;-)
 
I finally kicked the dota habit, sold off all my hats, and have been working on this again for the last few weeks. I had to completely refactor the code. From a design point of view and seeing how CWTN is making class specific bots I wanted to take a more deliberate approach and hardcode in some things i havent in the past. i touched up the spell detection and prioritization some and am currently working on detecting buffs to make sure every single possible buff is available.. if it makes sense. like enchanters and wizards have a level 10 damage shield.. and while that is cool.. nobody needs that in the bot. beastlords get focus of alladnu at level 67 and then never get another single focus. so i will let it use that spell until level 71 when they get group version. previously ive just allowed the highest of any given line and people have responded that it didnt allow enough flexibility. so my goal now is to detect everything, ask the experts on what skill should be used when.. and auto proiritize those depending on what is happening. raid vs group vs tank tank vs assist, etc. no reason to only detect defense discs if you arent tanking for instance. or not detecting aggro discs that are a level lower than the 2hs disc on same line if you are tanking. happy to chat about plugin design here or in discord (thats easier) or if there is something macros/plugins dont get right that i should factor into mine let me know! im aware that the alliance lines could be super useful to some people but ive never coded them in, for instance. so those types of things would be nice to try to fit in and figure out how to make them work. if anyone gets froggy and wants to try the latest of whatever i have just hit me up in discord and i will drop a copy or you can DL from my git and compile for yourself.

I have been keeping my code somewhat current (i dont do my daily work out of that repo but i manually add to it) at https://github.com/PeteSampras/MQ2Bot with my status board/roadmap at: https://app.gitkraken.com/glo/board/Xg3hmI40NwAQ87cJ
Welcome back Pete!
 
This is exactly how I wrote MQ2AFNUKE. Instead of providing a plugin for each class, I generalized the plug-in, and made C++ classes for each playable EQ class. Then each class will have its own logic. I am still in the process of moving some of the initial test/dev code to the appropriate classes (refactoring), and moving all the settings to it's own class instead of the lazy global variables I have now. If you want to collaborate or peek at my mess, shoot me a pm.

I finally kicked the dota habit, sold off all my hats, and have been working on this again for the last few weeks. I had to completely refactor the code. From a design point of view and seeing how CWTN is making class specific bots I wanted to take a more deliberate approach and hardcode in some things i havent in the past. i touched up the spell detection and prioritization some and am currently working on detecting buffs to make sure every single possible buff is available.. if it makes sense. like enchanters and wizards have a level 10 damage shield.. and while that is cool.. nobody needs that in the bot. beastlords get focus of alladnu at level 67 and then never get another single focus. so i will let it use that spell until level 71 when they get group version. previously ive just allowed the highest of any given line and people have responded that it didnt allow enough flexibility. so my goal now is to detect everything, ask the experts on what skill should be used when.. and auto proiritize those depending on what is happening. raid vs group vs tank tank vs assist, etc. no reason to only detect defense discs if you arent tanking for instance. or not detecting aggro discs that are a level lower than the 2hs disc on same line if you are tanking. happy to chat about plugin design here or in discord (thats easier) or if there is something macros/plugins dont get right that i should factor into mine let me know! im aware that the alliance lines could be super useful to some people but ive never coded them in, for instance. so those types of things would be nice to try to fit in and figure out how to make them work. if anyone gets froggy and wants to try the latest of whatever i have just hit me up in discord and i will drop a copy or you can DL from my git and compile for yourself.

I have been keeping my code somewhat current (i dont do my daily work out of that repo but i manually add to it) at https://github.com/PeteSampras/MQ2Bot with my status board/roadmap at: https://app.gitkraken.com/glo/board/Xg3hmI40NwAQ87cJ
 
I kept waffling back and forth over redundancy. and did some mind mapping of ideas. i decide the long and short of it is i need spells, spawns, and queues with some routines to find all those spells and spawns, figure out when to use each spell on each spawn, use them, and store those combinations. so how i previously did things was to break each thing out on its own like: Heal1 and FightBuff2 and Buff4. instead it all goes into a single spell queue with the individual spell priority driving when something is checked. Those are roughly default prioritized at things to stay alive > keeping others alive > debuffing/crowd control NPCs > hurting NPCs > buffing.

a fairly large change from original mq2bot is that in the new version i have a fairly elaborate spell and spawn struct. here is the current spell struct:
C:
typedef struct _BotSpell // has to be before the FunctionDeclarations because a bunch use it
{
    PSPELL                Spell;                    // store the spell itself so I can access all of its members
    char                Name[MAX_STRING];        // Check ini to see if they want to use a custom spell, if not then store the name of the spell/AA/Disc/item we actually want to /cast since that is often different
    bool                CanIReprioritize;        // if a specific custom spell or priority was set, then i want to ignore reprioritizing it
    DWORD                SpellIcon;                // store the name of the icon in case it is special like mana reiteration buff icon
    char                Gem[MAX_STRING];        // in case they want to use a custom gem, otherwise this will be alt, disc, item, or default gem
    char                If[MAX_STRING];            // is there a custom if statement in the ini?
    _Spawns                MyTarget;                    // My Target for this spell
    char                SpellCat[MAX_STRING];    // what SpellType() is this, so i dont have to check over and over
    char                SpellType[MAX_STRING] = { 0 };    // this will be a custom spell type for each routine.  Ie. Fast Heal, Normal Heal, Fire, Cold, Tash, because I will use this info later on checks and prioritization
    OPTIONS                SpellTypeOption;        // int conversion from spell type
    ULONGLONG            Duration;                // my actual duration, will use GetDuration2() and store it so i dont have to calculate every cast.
    int                    UseOnce;                // only use this spell once per mob? this will be an option ini entry
    int                    ForceCast;                // should i force memorize this spell if it is not already memmed?  ini entry, will be 1 on buffs, optional on rest
    int                    Use;                    // should i use this spell at all?  this will allow people to memorize but not use spells in case they want manual control
    int                    StartAt;                // individual control of when to start using this spell on friend or foe
    int                    StopAt;                    // individual control of when to stop using this spell on friend or foe
    int                    NamedOnly;                // ini entry to let user determine if they only want to use this on a named.  could use it as a level to say only use it on named above this level to avoid wasting it on easy named. int for now
    int                    Priority;                // ini entry that will default to 0, but we can set this priority internally for things like heals and nukes to determine order of cast.  higher number = higher priority
    ULONGLONG            Recast;                    // ini entry that will give us a timestamp to reuse a spell if different from it's fastest refresh.  I may only want to cast swarm pets once every 30 seconds even though i could every 12.
    ULONGLONG            LastCast;                // timestamp of last time i cast this spell
    int                    LastTargetID;            // last target id that i cast this spell on.
    char                Color[10];                // color to use when cast a spell
    DWORD                ID = 0;                    // ID, for Alt ability for now, maybe others
    PALTABILITY            AA;                        // AA if this is an AA
    DWORD                PreviousID;                // Needed for my current memmed spells to see if something changed
    int                    Type;                    // TYPE_AA, TYPE_SPELL, TYPE_DISC, TYPE_ITEM
    int                    CastTime;                // Casting time
    DWORD                TargetID;                // Id of target i want to cast on
    int                    IniMatch;                // what spell number is this in the ini, if any.
    bool                Prestige;                // if this is an item, is it prestige?
    int                    RequiredLevel;            // required level for this
    int                    UseInCombat;            // use the skill in combat? default yes combat skill/heals/fightbuffs, default no on buffs, pets, etc.
    char                Classes[MAX_STRING] = { 0 }; // for buffs only. what classes should i buff this one?                                           
    DWORD                GroupID;                // what is the group ID for ez access
    BUFFTYPE            BuffType = ::BUFFTYPE::NOTYPE;    // what BUFFTYPE enum is this, if any
    PSPELL                UnitySpell;                // whats my unified spell for this, if any
                                                // save this just in case:  void(*CheckFunc)(std::vector<_BotSpell> &, int);
    void(*CheckFunc)(int);
} BotSpell, * PBotSpell;

So now i do all the normal categorizations to figure out what a spell is.. but then i just throw that PSPELL and into the spell struct. I then have a BuildSpell routine that grabs all the ini data and sets all the default values that are built up for each time.

That let's me just build the struct and call the builder function(s) as required like so:
C:
        if (aegosingle)
        {
            BotSpell spell;
            spell.Spell = paegosingle;
            strcpy_s(spell.Name, paegosingle->Name);
            BuildSpell(spell);
            BuildBuff(spell);
            WriteChatf("[Buff] Aego \ao[Single]\aw: %s", paegosingle->Name);
        }

which then calls a builder function.
C:
void BuildSpell(_BotSpell& spell)
{
    bool bFound = false;
    // blank out the id value
    storeID = 0;
    //WriteChatf("Buildspell: %s", spell.Name);
    // Figure out type of data
    SkillType(spell.Name);
    spell.Spell = storeSpell;
    spell.SpellIcon = storeSpell->SpellIcon;
    if (!spell.Type)
        spell.Type = storeType;
    spell.ID = storeID;
    spell.Prestige = storePrestige;
    spell.RequiredLevel = storeRequiredLevel;
    sprintf_s(spell.Gem, storeGem);

    //check the loadbot stuff
    if (spell.Type == TYPE_DISC)
        DiscCategory(spell.Spell);
    else
        SpellType(spell.Spell);
    strcpy_s(spell.SpellCat, spellCat);
    spell.BuffType = buffType;
    if (strlen(spell.SpellType) < 2)
        strcpy_s(spell.SpellType, spellType);
    if (!spell.SpellTypeOption)
        spell.SpellTypeOption = spellTypeOption;
    if (!spell.CheckFunc)
        spell.CheckFunc = spellFunc;

    // check the ini otherwise use default settings
    char szTemp[MAX_STRING] = { 0 }, szSpell[MAX_STRING], szClasses[MAX_STRING] = { 0 }, spellNum[10] = { 0 }, color[10] = { 0 }, szRank2[MAX_STRING] = { 0 }, szRank3[MAX_STRING] = { 0 };
    int customSpells = GetPrivateProfileInt(INISection, "SpellTotal", 0, INIFileName);
    int defStartAt = 0, defUse = 0, defStopAt = 0, defPriority = 0, defNamedOnly = 0, defUseOnce = 0, defForceCast = 0, defUseInCombat = 0;
    int found = 0;
    for (int i = 0; DefaultSection[i]; i++)
    {
        if (!_stricmp(spell.SpellType, DefaultSection[i]))
        {
            strcpy_s(color, DefaultColor[i]);
            defStartAt = atoi(DefaultStartAt[i]);
            defUse = atoi(DefaultUse[i]);
            defStopAt = atoi(DefaultStopAt[i]);
            defPriority = atoi(DefaultPriority[i]);
            defNamedOnly = atoi(DefaultNamedOnly[i]);
            defUseOnce = atoi(DefaultUseOnce[i]);
            defForceCast = atoi(DefaultForceCast[i]);
            defUseInCombat = atoi(DefaultUseInCombat[i]);
            found++;
            break;
        }
    }
    if (!found)
    {
        WriteChatf("Somethings fucky, bois. SpellType %s for spell: %s. is unknown.", spell.SpellType, spell.Name);
        defStartAt = 0;
        defUse = 1;
        defStopAt = 0;
        defPriority = 0;
        defNamedOnly = 0;
        defUseOnce = 0;
        defForceCast = 0;
        defUseInCombat = 0;
    }
    // actually search for it
    for (int i = 1; i < customSpells; i++)
    {
        sprintf_s(szSpell, "Spell%dName", i);
        if (GetPrivateProfileString(INISection, szSpell, NULL, szTemp, MAX_STRING, INIFileName))
        {
            // see if it is a match
            // check 3 ranks
            sprintf_s(szRank2, "%s Rk. II", szTemp);
            sprintf_s(szRank3, "%s Rk. II", szTemp);
            if (!_stricmp(szTemp, spell.Name) || !_stricmp(szRank2, spell.Name) || !_stricmp(szRank3, spell.Name))
            {
                bFound = true;
                // great it is a match, let's load any non-default values
                spell.IniMatch = i;
                strcpy_s(spell.Color, color);
                sprintf_s(szSpell, "Spell%dIf", spell.IniMatch);
                if (GetPrivateProfileString(INISection, szSpell, "1", szTemp, MAX_STRING, INIFileName))
                {
                    strcpy_s(spell.If, szTemp);
                }
                sprintf_s(szSpell, "Spell%dGem", spell.IniMatch);
                if (GetPrivateProfileString(INISection, szSpell, spell.Gem, szTemp, MAX_STRING, INIFileName)) // defaults to existing gem if already memmed
                {
                    strcpy_s(spell.Gem, szTemp);
                }
                sprintf_s(szSpell, "Spell%dUseOnce", spell.IniMatch);
                spell.UseOnce = GetPrivateProfileInt(INISection, szSpell, defUseOnce, INIFileName);
                sprintf_s(szSpell, "Spell%dForceCast", spell.IniMatch);
                spell.ForceCast = GetPrivateProfileInt(INISection, szSpell, defForceCast, INIFileName);
                sprintf_s(szSpell, "Spell%dUse", spell.IniMatch);
                spell.Use = GetPrivateProfileInt(INISection, szSpell, defUse, INIFileName);
                sprintf_s(szSpell, "Spell%dStartAt", spell.IniMatch);
                spell.StartAt = GetPrivateProfileInt(INISection, szSpell, defStartAt, INIFileName);
                sprintf_s(szSpell, "Spell%dStopAt", spell.IniMatch);
                spell.StopAt = GetPrivateProfileInt(INISection, szSpell, defStopAt, INIFileName);
                sprintf_s(szSpell, "Spell%dNamedOnly", spell.IniMatch);
                spell.NamedOnly = GetPrivateProfileInt(INISection, szSpell, defNamedOnly, INIFileName);
                sprintf_s(szSpell, "Spell%dPriority", spell.IniMatch);
                spell.Priority = GetPrivateProfileInt(INISection, szSpell, defPriority, INIFileName);
                sprintf_s(szSpell, "Spell%dUseInCombat", spell.IniMatch);
                spell.UseInCombat = GetPrivateProfileInt(INISection, szSpell, defUseInCombat, INIFileName);
                sprintf_s(szSpell, "|%s|", spell.SpellType);
                if (StrStrIA(BuffClasses, szSpell))
                {
                    sprintf_s(szClasses, "%sClasses", spell.SpellType);
                    GetPrivateProfileString(INISection, szClasses, "ALL", szTemp, MAX_STRING, INIFileName);
                    strcpy_s(spell.Classes, szTemp);
                }
            }
        }
    }
    // but what if we didnt find it? guess we better use some defaults, Timmy
    if (!bFound)
    {
        strcpy_s(spell.Color, color);
        spell.UseOnce = defUseOnce;
        spell.ForceCast = defForceCast;
        spell.Use = 1;
        spell.StartAt = defStartAt;
        spell.StopAt = defStopAt;
        spell.NamedOnly = defNamedOnly;
        spell.Priority = defPriority;
        spell.UseInCombat = defUseInCombat;
        sprintf_s(spell.If, "1");
    }
    // set some defaults
    spell.LastTargetID = 0;
    spell.LastCast = 0;
    spell.Recast = 0;
    spell.TargetID = 0;
}

I need to sort a better way to clear my global placeholders because i think it ocassionally messes up. But all in all this seems to be really effective at building a spell queue and factoring in any custom ini settings that exist. Theres some screwy rk detection issues that are happening like when i detect spells unity casts it says rk2 even though im on a bronze account. i dont know whether that is eq or mq2 issue or if it is main or if ive done something wrong. basically just have to go through each time an issue pops up and build a function to account for it though. detection looks about 99% accurate for all tested classes though for the lines ive added in. ive seen a handful of dead spell lines that need hardcoded away like the ones ive mentioned.
 
Got caught up in military drill with 14 hour days so I havent had a chance to work on much. I finally got a chance and put together a new plugin with external API access to handle spawn storage / ignores / immunities / notes / and a bunch of other data that can be used by any macro or plugin. it was a needed feature for mq2bot but is versatile enough that i felt like it could warrant its own plugin. Plus CWTN said he would be interested in that functionality for his plugins, so why not make it usable by all. I posted it on my git for now with some wikis but itll need to evolve some before it is in its final fighting shape to go along with these advanced botting plugins. For now if any of you are interested you can check it out at https://github.com/PeteSampras/MQ2SpawnDB Be forewarned that it is more difficult to set up than most plugins because it requires you to throw some dlls into your EQ folder and to install and create a postgres database. but there are walk through instructions and videos for everything. I still need to piece together the remainder of the functionality either in this standalone plugin or within bot so that I can track spawns and their buffs and their statuses. Once that is complete I can start making additional progress within mq2bot itself.
 
Thanks for both walking us through your dev process as well as posting new goodies along the way. Following along closely!
 
Writing a bot plugin

Users who are viewing this thread

Back
Top
Cart