NOT MAINTAINED
Version 1.2 - Added /headcount command. Need help finding indexes for other classes to optimize the plugin. (see: MQ2Aaindex)
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.
Version 1.2 - Added /headcount command. Need help finding indexes for other classes to optimize the plugin. (see: MQ2Aaindex)
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.
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");
}
- Source Repository
- https://github.com/RedGuides/MQ2Headshot
- [git] Automation options?
- Yes