• 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
Resource icon

Release MQ2Headshot

Naes

Member
Joined
Jan 15, 2006
RedCents
60¢
Version 1.2 - Plugin is now stable. Added /headcount command. Need help finding indexes for other classes to optimize the plugin. (see: MQ2Aaindex)

OxD3r.png


Version 1.1 - Works for Paladin slay undead, Berserker decapitation, Rogue anatomy/assassinate, and Ranger headshot.

Sony likes to troll us by putting a bunch of mobs that can't actually be headshot in all the good camps, whether because they are not humanoid, or because they are too high for the max rank. Level 90's in Kaesora Library know what I'm talking about, but I have had this problem pretty much everywhere I've gone.

Here is a plugin I wrote to make it blatantly obvious if a mob can be headshot so you don't waste your time. When clearing camps, you can skip the ones that are too high level easily.

MZBDa.png


PHP:
// MQ2Headshot.cpp : Defines the entry point for the DLL application.
// Author: Naes
#include "../MQ2Plugin.h"

PreSetup("MQ2Headshot");
PLUGIN_VERSION(1.2);

#define HEADSHOT_HUMANOID 1
#define HEADSHOT_UNDEAD 3
#define HEADSHOT_ANYTHING -1

// can leave as-is in case other classes get similar in future
PCHAR szHeadshotLabel[] = { 
	"", // 0x0
	"", // Warrior
	"", // Cleric
	"UNDEAD", // Paladin
	"HEADSHOT", // Ranger
	"", // Shadow Knight
	"", // Druid
	"", // Monk
	"", // Bard
	"ASSASSINATE", // Rogue
	"", // Shaman
	"", // Necromancer
	"", // Wizard
	"", // Magician
	"", // Enchanter
	"", // Beastlord
	"DECAPITATE" // Berserker
};

PSPAWNINFO pCharFix;
int showHeadCount = -1;
char INISection[MAX_STRING];

int HeadshotStandardFormula(int startLevel, _ALTABILITY* aa, int defaultLevel = 0)
{
	return (aa) ? (startLevel - 2) + (aa->AARankRequired * 2) : defaultLevel;
}

bool CanHeadshot(PSPAWNINFO pNewSpawn)
{
	/*static const/**/ DWORD headshotAAIndex = GetAAIndexByName("Headshot"); //  = 13573;
	/*static const/**/ DWORD anatomyAAIndex = GetAAIndexByName("Anatomy");
	/*static const/**/ DWORD slayUndeadAAIndex = GetAAIndexByName("Slay Undead");
	/*static const/**/ DWORD decapitationAAIndex = GetAAIndexByName("Decapitation");

	if (GetSpawnType(pNewSpawn) == NPC && 
		 gGameState==GAMESTATE_INGAME) // not at char select
	{
		_ALTABILITY* aa = NULL;
		int bodyType = HEADSHOT_HUMANOID;
		
		int maxKillLevel = 0; // set to highest headshotable, label all under it

		if (GetCharInfo() && GetCharInfo()->pSpawn)
			pCharFix =  GetCharInfo()->pSpawn;

		switch (pCharFix->Class)
		{
			case Ranger:	
				aa = pAltAdvManager->GetAltAbility(headshotAAIndex);
				maxKillLevel = HeadshotStandardFormula(46, aa);  //rk1=46, rk22=88
			break;

			case Rogue:
				aa = pAltAdvManager->GetAltAbility(anatomyAAIndex);
				if (pCharFix->Level >= 60)
					maxKillLevel = HeadshotStandardFormula(46, aa, 44); //rk0=44, rk1=46, rk22=88
			break;

			case Paladin:
				bodyType = HEADSHOT_UNDEAD;
				aa = pAltAdvManager->GetAltAbility(slayUndeadAAIndex);
				if (aa)
					maxKillLevel = 255; // rk1=??
			break;

			case Berserker:
				bodyType = HEADSHOT_ANYTHING;
				aa = pAltAdvManager->GetAltAbility(decapitationAAIndex);
				maxKillLevel = HeadshotStandardFormula(46, aa);  //rk1=84, rk3=88
			break;
		}

		if ((bodyType == HEADSHOT_ANYTHING || GetBodyType(pNewSpawn) == bodyType) && 
			pNewSpawn->Level <= maxKillLevel)
				return true;
	}

	return false;
}

PLUGIN_API VOID OnAddSpawn(PSPAWNINFO pNewSpawn)
{
	if (CanHeadshot(pNewSpawn))
	{
		char new_name[MAX_STRING];
		sprintf(new_name, "%s: %d", szHeadshotLabel[pCharFix->Class], pNewSpawn->Level);
		strcpy(pNewSpawn->Lastname, new_name);	
	}
}

void WriteHeadcountSetting(int activate)
{
	CHAR szMsg[MAX_STRING]={0};
	CHAR szTemp[MAX_STRING]={0};
	
	sprintf(szTemp, "%d", activate);

	showHeadCount = activate;
	WritePrivateProfileString(INISection, "ShowHeadcount", szTemp, INIFileName);

	sprintf(szMsg, "[MQ2Headshot] /headcount setting: %d", activate);
	WriteChatColor(szMsg, USERCOLOR_DEFAULT);
}

void HeadcountCommand(PSPAWNINFO pChar, PCHAR szLine)
{
	if (strlen(szLine) != 0)
	{
		CHAR Arg1[MAX_STRING] = {0}; 
		GetArg(Arg1, szLine, 1); 

		if (!stricmp(Arg1, "off"))
		{
			WriteHeadcountSetting(0);
			return;
		} else if (!stricmp(Arg1, "on"))
		{
			WriteHeadcountSetting(1);
		}
	}

	PSPAWNINFO pSpawns = NULL;
    if (ppSpawnManager && pSpawnList)
    pSpawns = (PSPAWNINFO)pSpawnList;

	unsigned int count = 0;
	while (pSpawns)
	{
		if (CanHeadshot(pSpawns))
			++count;

		pSpawns = pSpawns->pNext;
	}
	
	char buffer[MAX_STRING];
	sprintf(buffer, "[MQ2Headshot] # of victims in %s: %d", ((PZONEINFO)pZoneInfo)->ShortName, count);
	WriteChatColor(buffer, USERCOLOR_DEFAULT);
}

PLUGIN_API VOID SetGameState(DWORD newGameState)
{
    DebugSpewAlways("MQ2Headshot::SetGameState(%d)", newGameState);
	// fix for first load
	if (newGameState == GAMESTATE_INGAME)
	{
		if (showHeadCount == -1)
		{
			if (!pCharFix && GetCharInfo() && GetCharInfo()->pSpawn)
				pCharFix = GetCharInfo()->pSpawn;
			sprintf(INISection,"%s_%s", pCharFix->Name, EQADDR_SERVERNAME);
			showHeadCount = GetPrivateProfileInt(INISection, "ShowHeadcount", 1, INIFileName);

			PSPAWNINFO pNewSpawns = NULL;
			if (ppSpawnManager && pSpawnList)
				pNewSpawns = (PSPAWNINFO)pSpawnList;

			while (pNewSpawns)  // clear the lastnames
			{
				OnAddSpawn(pNewSpawns);

				pNewSpawns = pNewSpawns->pNext;
			}
		}
	
		if (showHeadCount == 1)
			HeadcountCommand(pCharFix, "");
	
	} else if (newGameState == GAMESTATE_CHARSELECT)
	{
		showHeadCount = -1;
	}
}

PLUGIN_API VOID InitializePlugin(VOID)
{
	if (GetCharInfo() && GetCharInfo()->pSpawn)
		pCharFix = GetCharInfo()->pSpawn;
	
	AddCommand("/headcount", HeadcountCommand);

	WriteChatColor("[MQ2Headshot] /headcount [on/off/current]", USERCOLOR_DEFAULT);
}

// Called once, when the plugin is to shutdown
PLUGIN_API VOID ShutdownPlugin(VOID)
{
    DebugSpewAlways("Shutting down MQ2Headshot");

	PSPAWNINFO pClearSpawns = NULL;
    if (ppSpawnManager && pSpawnList)
        pClearSpawns = (PSPAWNINFO)pSpawnList;

	while (pClearSpawns)  // clear the lastnames
	{
		if (CanHeadshot(pClearSpawns))
			strcpy(pClearSpawns->Lastname, "");

		pClearSpawns = pClearSpawns->pNext;
	}

	
	RemoveCommand("/headcount"); 
}
 
Last edited:
Thanks alot. Working on getting some more plugins release-ready, adding INI options (mostly I just hardcode and compile as I play).

I guess this was causing a crash for non-rangers that had it loaded. Updating the OP

PHP:
bool isRanja = ((GetCharInfo()) && (GetCharInfo()->pSpawn) && GetCharInfo()->pSpawn->Class == Ranger);
	if (isRanja && GetBodyType(pNewSpawn) == HEADSHOT_HUMANOID && GetSpawnType(pNewSpawn) == NPC)

Redundant pointer checks for not crashing at char select too
 
Last edited:
This line is throwing up errors.

Rich (BB code):
    if (isRanja &&  == HEADSHOT_HUMANOID && GetSpawnType(pNewSpawn) == NPC)
Should it be?
Rich (BB code):
     if ((isRanja && HEADSHOT_HUMANOID && GetSpawnType(pNewSpawn) == NPC) == HEADSHOT_HUMANOID && GetSpawnType(pNewSpawn) == NPC)
 
Copy/pasted it in wrong on here, fixed the line now.

Rich (BB code):
if (isRanja &&  GetBodyType(pNewSpawn) == HEADSHOT_HUMANOID && GetSpawnType(pNewSpawn) == NPC)

edit Also

Rich (BB code):
int headshotPoints = pAltAdvManager->GetAltAbility(headshotAAIndex)->AARankRequired;
 
Last edited:
This seems to be crashing after you switch characters.

I had the level hardcoded and it worked fine, all I changed before posting it here was to pull the rank from the AA. Maybe someone has an idea?

For now I would just unload it for when you aren't using it (can be unloaded at char select too).
 
fixed was null pointer error, in OP too.

Rich (BB code):
_ALTABILITY* aa = pAltAdvManager->GetAltAbility(headshotAAIndex);
if (!aa)
	return false;

int headshotPoints = aa->AARankRequired;
 
Might want to also include a check because Rogues can use assassinate on the very same mobs that are headshotable by a Ranger... Not as efficient as a ranger bowing things down but still useful to Rogues too.
 
Decapitation can be done. Need more info on rogues stuff though. I guess it starts at 44 (automatically when you're level 60) and goes from there +2 per AA rank.

Are decap and assass both humanoid only?


Also looking into slay undead. Seems it works as long as you have rk1 and mob is undead, no level cap.


edit: Okay, I have written the code for the logic described in this post, but will only be able to test it on a ranger (and am unable to do so tonight). Will have something in the morning.
 
Last edited:
Releasing this as version 1.1

Has the code for paladins, zerkers, rogues and rangers. I've only tested it on rangers.

I don't know if the plugin should be renamed or what at this point as it encompasses more than just headshot. I guess its okay I can't think of anything better.

Rich (BB code):
// MQ2Headshot.cpp : Defines the entry point for the DLL application.
// Author: Naes
#include "../MQ2Plugin.h"

PreSetup("MQ2Headshot");
PLUGIN_VERSION(1.1);

#define HEADSHOT_HUMANOID 1
#define HEADSHOT_UNDEAD 3
#define HEADSHOT_ANYTHING -1

// can leave as-is in case other classes get similar in future
PCHAR szHeadshotLabel[] = { 
	"", // 0x0
	"", // Warrior
	"", // Cleric
	"UNDEAD", // Paladin
	"HEADSHOT", // Ranger
	"", // Shadow Knight
	"", // Druid
	"", // Monk
	"", // Bard
	"ASSASSINATE", // Rogue
	"", // Shaman
	"", // Necromancer
	"", // Wizard
	"", // Magician
	"", // Enchanter
	"", // Beastlord
	"DECAPITATE" // Berserker
};

int HeadshotStandardFormula(int startLevel, _ALTABILITY* aa, int defaultLevel = 0)
{
	return (aa) ? (startLevel - 2) + (aa->AARankRequired * 2) : defaultLevel;
}

bool CanHeadshot(PSPAWNINFO pNewSpawn)
{
	static DWORD headshotAAIndex = GetAAIndexByName("Headshot");
	static DWORD anatomyAAIndex = GetAAIndexByName("Anatomy");
	static DWORD slayUndeadAAIndex = GetAAIndexByName("Slay Undead");
	static DWORD decapitationAAIndex = GetAAIndexByName("Decapitation");

	if (GetSpawnType(pNewSpawn) == NPC && 
		(GetCharInfo()) && (GetCharInfo()->pSpawn)) // not at char select
	{
		_ALTABILITY* aa = NULL;
		int bodyType = HEADSHOT_HUMANOID;
		
		int maxKillLevel = 0; // set to highest headshotable, label all under it

		switch (GetCharInfo()->pSpawn->Class)
		{
			case Ranger:	
				aa = pAltAdvManager->GetAltAbility(headshotAAIndex);
				maxKillLevel = HeadshotStandardFormula(46, aa);  //rk1=46, rk22=88
			break;

			case Rogue:
				aa = pAltAdvManager->GetAltAbility(anatomyAAIndex);
				if (GetCharInfo()->pSpawn->Level >= 60)
					maxKillLevel = HeadshotStandardFormula(46, aa, 44); //rk0=44, rk1=46, rk22=88
			break;

			case Paladin:
				bodyType = HEADSHOT_UNDEAD;
				aa = pAltAdvManager->GetAltAbility(slayUndeadAAIndex);
				if (aa)
					maxKillLevel = 255; // rk1=??
			break;

			case Berserker:
				bodyType = HEADSHOT_ANYTHING;
				aa = pAltAdvManager->GetAltAbility(decapitationAAIndex);
				maxKillLevel = HeadshotStandardFormula(46, aa);  //rk1=84, rk3=88
			break;
		}

		if ((bodyType == HEADSHOT_ANYTHING || GetBodyType(pNewSpawn) == bodyType) && 
			pNewSpawn->Level <= maxKillLevel)
				return true;
	}

	return false;
}

PLUGIN_API VOID OnAddSpawn(PSPAWNINFO pNewSpawn)
{
	if (CanHeadshot(pNewSpawn))
	{	
		char new_name[MAX_STRING];
		sprintf(new_name, "%s: %d", szHeadshotLabel[GetCharInfo()->pSpawn->Class], pNewSpawn->Level);
		strcpy(pNewSpawn->Lastname, new_name);
	}
}

// Called once, when the plugin is to shutdown
PLUGIN_API VOID ShutdownPlugin(VOID)
{
    DebugSpewAlways("Shutting down MQ2Headshot");

	PSPAWNINFO pClearSpawns = NULL;
    if (ppSpawnManager && pSpawnList)
        pClearSpawns = (PSPAWNINFO)pSpawnList;

	while (pClearSpawns)  // clear the lastnames
	{
		if (CanHeadshot(pClearSpawns))
			strcpy(pClearSpawns->Lastname, "");

		pClearSpawns = pClearSpawns->pNext;
	}
}

updated OP as well
 
Last edited:
Decapitation can be done. Need more info on rogues stuff though. I guess it starts at 44 (automatically when you're level 60) and goes from there +2 per AA rank.

Are decap and assass both humanoid only?


Also looking into slay undead. Seems it works as long as you have rk1 and mob is undead, no level cap.


edit: Okay, I have written the code for the logic described in this post, but will only be able to test it on a ranger (and am unable to do so tonight). Will have something in the morning.

Rogues follow the same guidelines as rangers, including level and humanoid requirement. Not sure about zerkers, though.
 
Version 1.2
There were some issues with this plugin that I was finally able to resolve. Basically it wouldn't work when you first loaded in from char select due to the limitation of how EQ sends information (sends spawns before completing the character profile). I had the AA indexes as static ints, which meant the bug stayed until the plugin was reloaded completely. I was able to get around this by using the SetGameState() plugin API. So I am happy to say any bugs and crashes that plagued previous releases are finally exterminated.

OxD3r.png


While I was at it, I figured it would be nice to have a count of mobs you can headshot (or class-specific special attack) when you enter a zone. The only compromise I was able to come up with, because of how MQ2 calls OnAddSpawn() before OnZoned() is completed, was to scour the spawnlist twice. This is not necessarily negligible overhead, but even after trying some of the ugliest possible hacks to work around it, MacroQuest2 was still calling the function twice per zone anyway. There is no noticeable increase in load times, and executing the command by itself to get the current count happens instantly and without lagging the client (at least on my i3). I have added an option to disable it however, because lets say on an alt, you really don't want it to say "0 victims" every time you zone.

Settings syntax
Rich (BB code):
/headcount [on/off/current]  (or just '/headcount' for the current)

MQ2Headshot.ini (automatically created)
Rich (BB code):
[Ranjaname_zek]
ShowHeadcount=1

I have updated the source in the OP and attached a compiled .dll
 

Attachments

Last edited:
Thanks for posting this. I really want to see this one working.

I have been trying to use this plugin on a 90 ranger. The plugin loads and I can use headcount but I keep getting a 0 count even though I'm able to HS massive numbers of mobs in the zone and get XXX PERFORMED A FATAL BOW SHOT during range attack and when using focused arrow spells. The headshot title does not appear above their names. I ran the index, now what do I do with it?

The mobs are green and LB and I do have 2 more AA's to get for my level but I'm still manually HSing.

Any suggestions?

Thanks.
 
Same as dragonfire yesterday with a 73 ranger with HS 9 in HoH zone. No count and no tags above head
 
What is the index # you got from running the mq2aaindex plugin? Is it also 13573? It might be different depending on rank, in which case hardcoding it would be bad (I'm actually not sure how the Index is pulled, seems its an internal EQ function not mq2). That is the only reason I could see it would work fine for me but not lower ranks of the AA.

Even if can't hardcode the value, shouldn't be a big deal with modern memory caching schemes. Tell me if this one works, it's not hardcoded.
 

Attachments

Can some people test this please for pally zerker ranger and rogue. post back here if its working.
 

Attachments

I fixed it awhile ago and its in the current compile. fun plugin if you need it.
 
I forget the mq2 c++ code to do it offhand, but the new formula looks like this:

${Math.Calc[${Spell[${Me.AltAbility[headshot]}].MaxLevel}-1]}

That should work for zerker,rogue,ranger if you choose the correct AA name, and paladin is just straight undead so there is no level.
 
We are looking to update this in the next few weeks. It is on the list.
 
v1.3
- Updated for new headshot/decap/assassinate AA rules
- Added undead for SHD, NEC, CLR since they all have a bunch of AAs/spells that work only on undead
- Added summoned for MAG for same reason, and druid as summoned too for their summoned only spells, though they have less than mage. Was considering animals for druid.

Considering adding plant for SHD/NEC and animal for druid. not sure how to make multiple for classes though. So maybe not.
Rich (BB code):
// MQ2Headshot.cpp : Defines the entry point for the DLL application.
// Author: Naes
#include "../MQ2Plugin.h"

PreSetup("MQ2Headshot");
PLUGIN_VERSION(1.3);

#define HEADSHOT_HUMANOID 1
#define HEADSHOT_UNDEAD 3
#define HEADSHOT_SUMMONED 28
#define HEADSHOT_ANYTHING -1

// can leave as-is in case other classes get similar in future
PCHAR szHeadshotLabel[] = { 
    "", // 0x0
    "", // Warrior
    "UNDEAD", // Cleric
    "UNDEAD", // Paladin
    "HEADSHOT", // Ranger
    "UNDEAD", // Shadow Knight
    "SUMMONED", // Druid
    "", // Monk
    "", // Bard
    "ASSASSINATE", // Rogue
    "", // Shaman
    "UNDEAD", // Necromancer
    "", // Wizard
    "SUMMONED", // Magician
    "", // Enchanter
    "", // Beastlord
    "DECAPITATE" // Berserker
};

PSPAWNINFO pCharFix;
int showHeadCount = -1;
char INISection[MAX_STRING];



bool CanHeadshot(PSPAWNINFO pNewSpawn)
{
    /*static const/**/ DWORD headshotAAIndex = GetAAIndexByName("Headshot"); //  = 13573;
    /*static const/**/ DWORD anatomyAAIndex = GetAAIndexByName("Anatomy");
    /*static const/**/ DWORD slayUndeadAAIndex = GetAAIndexByName("Slay Undead");
    /*static const/**/ DWORD decapitationAAIndex = GetAAIndexByName("Decapitation");
    /*static const/**/ DWORD turnUndeadAAIndex = GetAAIndexByName("Turn Undead");
    /*static const/**/ DWORD turnSummonedAAIndex = GetAAIndexByName("Turn Summoned");
    /*static const/**/ DWORD originAAIndex = GetAAIndexByName("Origin");
	
    if (GetSpawnType(pNewSpawn) == NPC && 
         gGameState==GAMESTATE_INGAME) // not at char select
    {
        _ALTABILITY* aa = NULL;
        int bodyType = HEADSHOT_HUMANOID;
        
        int maxKillLevel = 0; // set to highest headshotable, label all under it

        if (GetCharInfo() && GetCharInfo()->pSpawn)
            pCharFix =  GetCharInfo()->pSpawn;

        switch (pCharFix->Class)
        {
            case Ranger:    
                aa = pAltAdvManager->GetAltAbility(headshotAAIndex);
                maxKillLevel = aa->MinLevel-1;
            break;

            case Rogue:
                aa = pAltAdvManager->GetAltAbility(anatomyAAIndex);
                if (pCharFix->Level >= 60)
                    maxKillLevel = aa->MinLevel-1;
            break;

            case Paladin:
                bodyType = HEADSHOT_UNDEAD;
                aa = pAltAdvManager->GetAltAbility(slayUndeadAAIndex);
                if (aa)
                    maxKillLevel = 255; // rk1=??
            break;

            case Berserker:
                bodyType = HEADSHOT_ANYTHING;
                aa = pAltAdvManager->GetAltAbility(decapitationAAIndex);
                maxKillLevel = aa->MinLevel-1;
            break;
            
            case Cleric:   
                bodyType = HEADSHOT_UNDEAD; 
                aa = pAltAdvManager->GetAltAbility(turnUndeadAAIndex);
                maxKillLevel = 255;
            break;
            case Mage:   
                bodyType = HEADSHOT_SUMMONED; 
                aa = pAltAdvManager->GetAltAbility(turnSummonedAAIndex);
                maxKillLevel = 255;
            break;
            case Druid:   
                bodyType = HEADSHOT_SUMMONED; 
                aa = pAltAdvManager->GetAltAbility(turnSummonedAAIndex);
                maxKillLevel = 255;
            break;
            case Shadowknight:
                bodyType = HEADSHOT_UNDEAD;
                aa = pAltAdvManager->GetAltAbility(originAAIndex);
                if (aa)
                    maxKillLevel = 255; 
            break;
            case Necromancer:
                bodyType = HEADSHOT_UNDEAD;
                aa = pAltAdvManager->GetAltAbility(originAAIndex);
                if (aa)
                    maxKillLevel = 255; 
            break;
        }

        if ((bodyType == HEADSHOT_ANYTHING || GetBodyType(pNewSpawn) == bodyType) && 
            pNewSpawn->Level <= maxKillLevel)
                return true;
    }

    return false;
}

PLUGIN_API VOID OnAddSpawn(PSPAWNINFO pNewSpawn)
{
    if (CanHeadshot(pNewSpawn))
    {
        char new_name[MAX_STRING];
        sprintf(new_name, "%s: %d", szHeadshotLabel[pCharFix->Class], pNewSpawn->Level);
        strcpy(pNewSpawn->Lastname, new_name);    
    }
}

void WriteHeadcountSetting(int activate)
{
    CHAR szMsg[MAX_STRING]={0};
    CHAR szTemp[MAX_STRING]={0};
    
    sprintf(szTemp, "%d", activate);

    showHeadCount = activate;
    WritePrivateProfileString(INISection, "ShowHeadcount", szTemp, INIFileName);

    sprintf(szMsg, "[MQ2Headshot] /headcount setting: %d", activate);
    WriteChatColor(szMsg, USERCOLOR_DEFAULT);
}

void HeadcountCommand(PSPAWNINFO pChar, PCHAR szLine)
{
    if (strlen(szLine) != 0)
    {
        CHAR Arg1[MAX_STRING] = {0}; 
        GetArg(Arg1, szLine, 1); 

        if (!stricmp(Arg1, "off"))
        {
            WriteHeadcountSetting(0);
            return;
        } else if (!stricmp(Arg1, "on"))
        {
            WriteHeadcountSetting(1);
        }
    }

    PSPAWNINFO pSpawns = NULL;
    if (ppSpawnManager && pSpawnList)
    pSpawns = (PSPAWNINFO)pSpawnList;

    unsigned int count = 0;
    while (pSpawns)
    {
        if (CanHeadshot(pSpawns))
            ++count;

        pSpawns = pSpawns->pNext;
    }
    
    char buffer[MAX_STRING];
    sprintf(buffer, "[MQ2Headshot] # of %s mobs in %s: %d", szHeadshotLabel[pCharFix->Class],((PZONEINFO)pZoneInfo)->ShortName, count);
    WriteChatColor(buffer, USERCOLOR_DEFAULT);
}

PLUGIN_API VOID SetGameState(DWORD newGameState)
{
    DebugSpewAlways("MQ2Headshot::SetGameState(%d)", newGameState);
    // fix for first load
    if (newGameState == GAMESTATE_INGAME)
    {
        if (showHeadCount == -1)
        {
            if (!pCharFix && GetCharInfo() && GetCharInfo()->pSpawn)
                pCharFix = GetCharInfo()->pSpawn;
            sprintf(INISection,"%s_%s", pCharFix->Name, EQADDR_SERVERNAME);
            showHeadCount = GetPrivateProfileInt(INISection, "ShowHeadcount", 1, INIFileName);

            PSPAWNINFO pNewSpawns = NULL;
            if (ppSpawnManager && pSpawnList)
                pNewSpawns = (PSPAWNINFO)pSpawnList;

            while (pNewSpawns)  // clear the lastnames
            {
                OnAddSpawn(pNewSpawns);

                pNewSpawns = pNewSpawns->pNext;
            }
        }
    
        if (showHeadCount == 1)
            HeadcountCommand(pCharFix, "");
    
    } else if (newGameState == GAMESTATE_CHARSELECT)
    {
        showHeadCount = -1;
    }
}

PLUGIN_API VOID InitializePlugin(VOID)
{
    if (GetCharInfo() && GetCharInfo()->pSpawn)
        pCharFix = GetCharInfo()->pSpawn;
    
    AddCommand("/headcount", HeadcountCommand);

    WriteChatColor("[MQ2Headshot] \agLoaded", USERCOLOR_DEFAULT);
}

// Called once, when the plugin is to shutdown
PLUGIN_API VOID ShutdownPlugin(VOID)
{
    DebugSpewAlways("Shutting down MQ2Headshot");

    PSPAWNINFO pClearSpawns = NULL;
    if (ppSpawnManager && pSpawnList)
        pClearSpawns = (PSPAWNINFO)pSpawnList;

    while (pClearSpawns)  // clear the lastnames
    {
        if (CanHeadshot(pClearSpawns))
            strcpy(pClearSpawns->Lastname, "");

        pClearSpawns = pClearSpawns->pNext;
    }

    
    RemoveCommand("/headcount"); 
}

Only limited testing in a couple zones with each class, worked correctly, but if you come across something funky, let me know.
 
if you have any compile errors let me know, i use a slightly different version due to different #includes
 
Version 1.2 - Plugin is now stable. Added /headcount command. Need help finding indexes for other classes to optimize the plugin. (see: MQ2Aaindex)

OxD3r.png


Version 1.1 - Works for Paladin slay undead, Berserker decapitation, Rogue anatomy/assassinate, and Ranger headshot.

Sony likes to troll us by putting a bunch of mobs that can't actually be headshot in all the good camps, whether because they are not humanoid, or because they are too high for the max rank. Level 90's in Kaesora Library know what I'm talking about, but I have had this problem pretty much everywhere I've gone.

Here is a plugin I wrote to make it blatantly obvious if a mob can be headshot so you don't waste your time. When clearing camps, you can skip the ones that are too high level easily.

MZBDa.png


PHP:
// MQ2Headshot.cpp : Defines the entry point for the DLL application.
// Author: Naes
#include "../MQ2Plugin.h"

PreSetup("MQ2Headshot");
PLUGIN_VERSION(1.2);

#define HEADSHOT_HUMANOID 1
#define HEADSHOT_UNDEAD 3
#define HEADSHOT_ANYTHING -1

// can leave as-is in case other classes get similar in future
PCHAR szHeadshotLabel[] = {
    "", // 0x0
    "", // Warrior
    "", // Cleric
    "UNDEAD", // Paladin
    "HEADSHOT", // Ranger
    "", // Shadow Knight
    "", // Druid
    "", // Monk
    "", // Bard
    "ASSASSINATE", // Rogue
    "", // Shaman
    "", // Necromancer
    "", // Wizard
    "", // Magician
    "", // Enchanter
    "", // Beastlord
    "DECAPITATE" // Berserker
};

PSPAWNINFO pCharFix;
int showHeadCount = -1;
char INISection[MAX_STRING];

int HeadshotStandardFormula(int startLevel, _ALTABILITY* aa, int defaultLevel = 0)
{
    return (aa) ? (startLevel - 2) + (aa->AARankRequired * 2) : defaultLevel;
}

bool CanHeadshot(PSPAWNINFO pNewSpawn)
{
    /*static const/**/ DWORD headshotAAIndex = GetAAIndexByName("Headshot"); //  = 13573;
    /*static const/**/ DWORD anatomyAAIndex = GetAAIndexByName("Anatomy");
    /*static const/**/ DWORD slayUndeadAAIndex = GetAAIndexByName("Slay Undead");
    /*static const/**/ DWORD decapitationAAIndex = GetAAIndexByName("Decapitation");

    if (GetSpawnType(pNewSpawn) == NPC &&
         gGameState==GAMESTATE_INGAME) // not at char select
    {
        _ALTABILITY* aa = NULL;
        int bodyType = HEADSHOT_HUMANOID;
       
        int maxKillLevel = 0; // set to highest headshotable, label all under it

        if (GetCharInfo() && GetCharInfo()->pSpawn)
            pCharFix =  GetCharInfo()->pSpawn;

        switch (pCharFix->Class)
        {
            case Ranger:   
                aa = pAltAdvManager->GetAltAbility(headshotAAIndex);
                maxKillLevel = HeadshotStandardFormula(46, aa);  //rk1=46, rk22=88
            break;

            case Rogue:
                aa = pAltAdvManager->GetAltAbility(anatomyAAIndex);
                if (pCharFix->Level >= 60)
                    maxKillLevel = HeadshotStandardFormula(46, aa, 44); //rk0=44, rk1=46, rk22=88
            break;

            case Paladin:
                bodyType = HEADSHOT_UNDEAD;
                aa = pAltAdvManager->GetAltAbility(slayUndeadAAIndex);
                if (aa)
                    maxKillLevel = 255; // rk1=??
            break;

            case Berserker:
                bodyType = HEADSHOT_ANYTHING;
                aa = pAltAdvManager->GetAltAbility(decapitationAAIndex);
                maxKillLevel = HeadshotStandardFormula(46, aa);  //rk1=84, rk3=88
            break;
        }

        if ((bodyType == HEADSHOT_ANYTHING || GetBodyType(pNewSpawn) == bodyType) &&
            pNewSpawn->Level <= maxKillLevel)
                return true;
    }

    return false;
}

PLUGIN_API VOID OnAddSpawn(PSPAWNINFO pNewSpawn)
{
    if (CanHeadshot(pNewSpawn))
    {
        char new_name[MAX_STRING];
        sprintf(new_name, "%s: %d", szHeadshotLabel[pCharFix->Class], pNewSpawn->Level);
        strcpy(pNewSpawn->Lastname, new_name);   
    }
}

void WriteHeadcountSetting(int activate)
{
    CHAR szMsg[MAX_STRING]={0};
    CHAR szTemp[MAX_STRING]={0};
   
    sprintf(szTemp, "%d", activate);

    showHeadCount = activate;
    WritePrivateProfileString(INISection, "ShowHeadcount", szTemp, INIFileName);

    sprintf(szMsg, "[MQ2Headshot] /headcount setting: %d", activate);
    WriteChatColor(szMsg, USERCOLOR_DEFAULT);
}

void HeadcountCommand(PSPAWNINFO pChar, PCHAR szLine)
{
    if (strlen(szLine) != 0)
    {
        CHAR Arg1[MAX_STRING] = {0};
        GetArg(Arg1, szLine, 1);

        if (!stricmp(Arg1, "off"))
        {
            WriteHeadcountSetting(0);
            return;
        } else if (!stricmp(Arg1, "on"))
        {
            WriteHeadcountSetting(1);
        }
    }

    PSPAWNINFO pSpawns = NULL;
    if (ppSpawnManager && pSpawnList)
    pSpawns = (PSPAWNINFO)pSpawnList;

    unsigned int count = 0;
    while (pSpawns)
    {
        if (CanHeadshot(pSpawns))
            ++count;

        pSpawns = pSpawns->pNext;
    }
   
    char buffer[MAX_STRING];
    sprintf(buffer, "[MQ2Headshot] # of victims in %s: %d", ((PZONEINFO)pZoneInfo)->ShortName, count);
    WriteChatColor(buffer, USERCOLOR_DEFAULT);
}

PLUGIN_API VOID SetGameState(DWORD newGameState)
{
    DebugSpewAlways("MQ2Headshot::SetGameState(%d)", newGameState);
    // fix for first load
    if (newGameState == GAMESTATE_INGAME)
    {
        if (showHeadCount == -1)
        {
            if (!pCharFix && GetCharInfo() && GetCharInfo()->pSpawn)
                pCharFix = GetCharInfo()->pSpawn;
            sprintf(INISection,"%s_%s", pCharFix->Name, EQADDR_SERVERNAME);
            showHeadCount = GetPrivateProfileInt(INISection, "ShowHeadcount", 1, INIFileName);

            PSPAWNINFO pNewSpawns = NULL;
            if (ppSpawnManager && pSpawnList)
                pNewSpawns = (PSPAWNINFO)pSpawnList;

            while (pNewSpawns)  // clear the lastnames
            {
                OnAddSpawn(pNewSpawns);

                pNewSpawns = pNewSpawns->pNext;
            }
        }
   
        if (showHeadCount == 1)
            HeadcountCommand(pCharFix, "");
   
    } else if (newGameState == GAMESTATE_CHARSELECT)
    {
        showHeadCount = -1;
    }
}

PLUGIN_API VOID InitializePlugin(VOID)
{
    if (GetCharInfo() && GetCharInfo()->pSpawn)
        pCharFix = GetCharInfo()->pSpawn;
   
    AddCommand("/headcount", HeadcountCommand);

    WriteChatColor("[MQ2Headshot] /headcount [on/off/current]", USERCOLOR_DEFAULT);
}
// Called once, when the plugin is to shutdown
PLUGIN_API VOID ShutdownPlugin(VOID)
{
    DebugSpewAlways("Shutting down MQ2Headshot");

    PSPAWNINFO pClearSpawns = NULL;
    if (ppSpawnManager && pSpawnList)
        pClearSpawns = (PSPAWNINFO)pSpawnList;

    while (pClearSpawns)  // clear the lastnames
    {
        if (CanHeadshot(pClearSpawns))
            strcpy(pClearSpawns->Lastname, "");

        pClearSpawns = pClearSpawns->pNext;
    }

   
    RemoveCommand("/headcount");
}

Would it be fair to say that this wonderful Macro was written prior to the nerfing of all the SK, Rogue, Ranger, Mage, Zerker that allowed only 8 to 10 mobs to be affected by the headshot etc. totals at a particular pull.
 
Does this know if this still work? Having issues with Ranger on this it seems to only hit 1 mob but could just be me.
 
Release MQ2Headshot

Users who are viewing this thread

Back
Top
Cart