// MQ2PiggyZone.cpp : Defines the entry point for the DLL application.
//
// updated to work with VC6 (previously only worked in VS.NET)
//
//
// /zone qeynos2 - takes you to North Qeynos if it knows a path
// /zone force qeynos - takes you directly to North Qeynos and crashes if you aren't adjacent
// /findpath qeynos2 - displays the path to North Qeynos
// /findpath North - displays all of the zonenames whose long name contains "North"
// /fade - zones you to the zone you are already in (even if its instanced)
// /gate - Returns you to your bind point
//
#include "../MQ2Plugin.h"
PreSetup("MQ2PiggyZone");
#undef ZoneToGoTo
#undef MAX_ZONES
#define MAX_ZONES 0x189
#define LocalCEverQuest__DoTheZone 0x4C6750 // UPDATE THIS Offset
#define INFINITY ((1 << (8*sizeof (int) - 6)) - 4)
#ifdef PKT_UPDATE_POSITION
#undef PKT_UPDATE_POSITION
#endif
#define PKT_UPDATE_POSITION 0x178A
#ifdef PKT_CHANNEL_MESSAGE
#undef PKT_CHANNEL_MESSAGE
#endif
#define PKT_CHANNEL_MESSAGE 0xB5A
typedef struct
{
int connections[100]; // An array of edges which has this as the starting node
int numconnect;
} FWVertice;
typedef struct
{
int Zone;
char Name[50];
char Phrase[20][50];
int Destination[20];
float X,Y,Z;
int DestCnt;
} NPCTeleporter;
FWVertice *V=NULL;
NPCTeleporter NPCs[100];
int NPCCnt;
int* distances=NULL;
int* predecessor=NULL;
class LocalCEverQuest;
class LocalCEverQuest
{
public:
__declspec(dllexport) char * LocalCEverQuest::DoTheZone(int,char *,int,int,float,float,float,int);
};
#ifdef LocalCEverQuest__DoTheZone
FUNCTION_AT_ADDRESS(char * LocalCEverQuest::DoTheZone(int,char *,int,int,float,float,float,int),LocalCEverQuest__DoTheZone);
#endif
LocalCEverQuest **ppLEQ;
#define pLEQ (*ppLEQ)
void Setup();
bool UseNPC(PSPAWNINFO pChar, int dest);
PLUGIN_API VOID OnZoned(PSPAWNINFO pChar, PCHAR szLine);
PLUGIN_API VOID OnPulse(VOID);
DWORD ListSimilarZones(PCHAR ZoneShortName);
VOID ChangeZones(PSPAWNINFO pChar, PCHAR szLine);
void FloydWarshall(FWVertice* vertices, int nodecount);
VOID FindPath(PSPAWNINFO pChar, PCHAR szLine);
VOID SimFade(PSPAWNINFO pChar, PCHAR szLine);
VOID SimGate(PSPAWNINFO pChar, PCHAR szLine);
bool ZoneChange=false;
float X,Y,Z;
int Heading;
int DestZone;
int ChainZone[100];
int ChainZoneCnt=0;
int DestType; // 0=use supplied coords 1=succorpoint
int ChainZoneType; // 0=use supplied coords 1=succorpoint
int ZoneReason;
int LastKnownZone=-1; //don't fire MyOnZoned when you first log in
void MyOnZoned(PSPAWNINFO pChar)
{
if (ChainZoneCnt>0)
{
if (pChar->Zone != ChainZone[ChainZoneCnt])
{
ChainZoneCnt=0;
return;
}
ChainZoneCnt--;
DestZone=ChainZone[ChainZoneCnt];
if (ChainZoneCnt==0) DestType=ChainZoneType;
WriteChatColor("Attempting ChainZone...", USERCOLOR_DEFAULT);
if ( UseNPC(pChar,DestZone) == false)
{
ZoneChange=true;
}
}
return;
}
PLUGIN_API VOID OnPulse(VOID)
{
PSPAWNINFO pChar = NULL;
if (ppCharSpawn && pCharSpawn) {
pChar = (PSPAWNINFO)pCharSpawn;
if ((pChar) && (!gZoning))
{
if (LastKnownZone == -1) LastKnownZone=pChar->Zone;
if (pChar->Zone != LastKnownZone)
{
LastKnownZone=pChar->Zone;
MyOnZoned(pChar);
}
}
}
char aa[100]="test";
if(ZoneChange)
{
ZoneChange=false;
pLEQ->DoTheZone(DestZone,aa,DestType,ZoneReason,Y,X,Z,Heading);
}
return;
}
DWORD ListSimilarZones(PCHAR PartialName)
{
CHAR szMsg[MAX_STRING] = "Bad Zone.ShortName. Suggest: ";
CHAR szName[MAX_STRING] = {0};
char *partial,*longname;
PZONELIST pZone = NULL;
partial=_strlwr(_strdup(PartialName));
if (!ppWorldData | !pWorldData) return -1;
for (int nIndex=0; nIndex < MAX_ZONES+1; nIndex++) {
pZone = ((PWORLDDATA)pWorldData)->ZoneArray[nIndex];
if(pZone )
{
longname=_strlwr(_strdup(pZone->LongName));
if (strstr(longname,partial)) {
sprintf(szName,"%s(%s) ",pZone->LongName,pZone->ShortName);
if ((strlen(szMsg)+strlen(szName))>=300)
{
WriteChatColor(szMsg,USERCOLOR_DEFAULT);
szMsg[0]=0;
}
strcat(szMsg,szName);
}
free(longname);
}
}
WriteChatColor(szMsg,USERCOLOR_DEFAULT);
free(partial);
return -1;
}
VOID ChangeZones(PSPAWNINFO pChar, PCHAR szLine)
{
CHAR szMsg[MAX_STRING] = {0};
CHAR szParam[MAX_STRING] = {0};
CHAR sZoneName[MAX_STRING] ={0};
CHAR sWPName[MAX_STRING] ={0};
CHAR sKeyData[MAX_STRING]={0};
DWORD ZoneToGoTo;
int rLen;
int i,j,cnt=0;
bool IgnoreChain=false;
int Param=1;
GetArg(szParam,szLine,Param);
if (_stricmp(szParam,"setwp")==0)
{
Param++;
GetArg(sWPName,szLine,Param);
if (sWPName[0]==0)
{
WriteChatColor("Usage: /zone setwp <WayPointName>", CONCOLOR_RED);
return;
}
sprintf(sKeyData,"%.2f %.2f %2.f %d",pChar->Y,pChar->X,pChar->Z,(int)pChar->Heading);
WritePrivateProfileString(GetShortZone(pChar->Zone), sWPName, sKeyData, INIFileName);
WriteChatColor("Waypoint recorded", USERCOLOR_DEFAULT);
return;
}
if (_stricmp(szParam,"clearwp")==0)
{
Param++;
GetArg(sWPName,szLine,Param);
if (sWPName[0]==0)
{
WriteChatColor("Usage: /zone clearwp <WayPointName>", CONCOLOR_RED);
return;
}
WritePrivateProfileString(GetShortZone(pChar->Zone), sWPName, NULL, INIFileName);
WriteChatColor("Waypoint cleared", USERCOLOR_DEFAULT);
return;
}
if (_stricmp(szParam,"force")==0)
{
IgnoreChain=true;
Param++;
}
GetArg(sZoneName,szLine,Param++);
ZoneToGoTo = GetZoneID(sZoneName);
if (ZoneToGoTo == -1) {
ListSimilarZones(sZoneName);
return;
}
Setup();
if (IgnoreChain==false)
{
i=pChar->Zone;
j=ZoneToGoTo;
if (distances[i*MAX_ZONES+j]==0)
{
WriteChatColor("Poof! You are already there.", CONCOLOR_RED);
return;
}
if (distances[i*MAX_ZONES+j]==INFINITY)
{
WriteChatColor("I don't know a route to that zone.", CONCOLOR_RED);
return;
}
}
GetArg(sWPName,szLine,Param++);
if (sWPName[0]!=0)
{
CHAR sDefault[MAX_STRING]="none";
GetPrivateProfileString(sZoneName,sWPName, sDefault, sKeyData, MAX_STRING, INIFileName);
if (_stricmp(sKeyData,"none")==0)
{
rLen=GetPrivateProfileString(sZoneName,NULL, sDefault, sKeyData, MAX_STRING, INIFileName);
for (int i=0;i<rLen-1;i++) if (sKeyData==0) sKeyData=',';
sprintf(szMsg,"Bad Waypoint. Suggest: %s",sKeyData);
WriteChatColor(szMsg,USERCOLOR_DEFAULT);
return;
}
sscanf(sKeyData,"%f %f %f %d",&Y,&X,&Z,&Heading);
DestType=0;
}
else
{
sprintf(sWPName,"default");
CHAR sDefault[MAX_STRING]="none";
GetPrivateProfileString(sZoneName,sWPName, sDefault, sKeyData, MAX_STRING, INIFileName);
if (_stricmp(sKeyData,"none")!=0)
{
sscanf(sKeyData,"%f %f %f %d",&Y,&X,&Z,&Heading);
DestType=0;
}
else
{
Y=pChar->Y;
X=pChar->X;
Z=pChar->Z;
Heading = (int)pChar->Heading;
DestType=1;
}
}
ChainZoneCnt=0; //reset in case we failed to finish previous chain-zone attempt
if (IgnoreChain==false)
{
while (i!=j)
{
ChainZone[ChainZoneCnt++]=j;
j=predecessor[i*MAX_ZONES+j];
}
if (ChainZoneCnt>1)
{
ChainZoneType=DestType;
DestType=1;
}
DestZone = ChainZone[--ChainZoneCnt];
}
else
{
DestZone=ZoneToGoTo;
}
sprintf(szMsg,"Zoneing...");
WriteChatColor(szMsg,USERCOLOR_DEFAULT);
ZoneReason=0;
if (UseNPC(pChar,DestZone) == false) ZoneChange=true;
return;
}
VOID FindPath(PSPAWNINFO pChar, PCHAR szLine)
{
CHAR sDest[MAX_STRING]={0};
int ZoneToGoTo;
int i,j,cnt=0,stops[100];
GetArg(sDest,szLine,1);
if (sDest[0]==0)
{
WriteChatColor("Usage: /FindPath <ShortZoneName>", CONCOLOR_RED);
return;
}
ZoneToGoTo = GetZoneID(sDest);
if (ZoneToGoTo == -1) {
ListSimilarZones(sDest);
return;
}
Setup();
i=pChar->Zone;
j=ZoneToGoTo;
if (distances[i*MAX_ZONES+j]==0)
{
WriteChatColor("Poof! You are already there.", CONCOLOR_RED);
return;
}
if (distances[i*MAX_ZONES+j]==INFINITY)
{
WriteChatColor("I don't know a route to that zone.", CONCOLOR_RED);
return;
}
WriteChatColor("My path:", CONCOLOR_RED);
while (i!=j)
{
stops[cnt++]=j;
j=predecessor[i*MAX_ZONES+j];
}
while (cnt>0)
{
cnt--;
WriteChatColor(GetShortZone(stops[cnt]),USERCOLOR_DEFAULT);
}
}
void Setup()
{
char sKey[300];
char sKeyData[300];
char *p;
int i;
if (distances!=NULL) free(distances);
if (predecessor!=NULL) free(predecessor);
if (V!=NULL) free(V);
V = (FWVertice *)malloc(MAX_ZONES*sizeof(FWVertice));
int tmp[100],cnt;
for (i=0;i<MAX_ZONES;i++)
{
sprintf(sKey,"%d",i);
V.numconnect=0;
GetPrivateProfileString("ZoneConnections",sKey,"none",sKeyData,300,INIFileName);
if (_stricmp(sKeyData,"none")!=0)
{
strtok(sKeyData,"\"");
strtok(NULL,"\"");
cnt=0;
while ( (p=strtok(NULL,",")) != NULL)
{
tmp[cnt++]=atoi(p);
}
V.numconnect=cnt+1;
for (int j=0;j<cnt;j++) V.connections[j]=tmp[j];
}
V.connections[V.numconnect++]=GetCharInfo2()->ZoneBoundID; //always connected to my bind point
}
//NPCTeleporter support
NPCCnt=0;
for (i=0;i<100;i++)
{
sprintf(sKey,"%d",(i+1));
GetPrivateProfileString("NPCTeleporters",sKey,"none",sKeyData,300,INIFileName);
if (_stricmp(sKeyData,"none")==0) break;
NPCCnt++;
NPCs.Zone=atoi(strtok(sKeyData," \""));
strcpy(NPCs.Name,strtok(NULL,"\""));
NPCs.Y=(float)atof(strtok(NULL," "));
NPCs.X=(float)atof(strtok(NULL," "));
NPCs.Z=(float)atof(strtok(NULL," "));
NPCs.DestCnt=0;
p=strtok(NULL," \"");
while (p!=NULL)
{
NPCs.Destination[NPCs.DestCnt]=atoi(p);
strcpy(NPCs.Phrase[NPCs.DestCnt],strtok(NULL,"\""));
V[NPCs.Zone].connections[V[NPCs.Zone].numconnect++]=NPCs.Destination[NPCs.DestCnt];
NPCs.DestCnt++;
p=strtok(NULL," \"");
}
}
FloydWarshall(V,MAX_ZONES);
}
bool UseNPC(PSPAWNINFO pChar,int dest)
{
for (int i=0;i<NPCCnt;i++)
{
if (NPCs.Zone != pChar->Zone) continue;
for (int j=0;j<NPCs.DestCnt;j++)
{
if (NPCs.Destination[j]==dest)
{
// MOVEPKT Code updated 7/10/06
struct _MOVEPKT {
/*0000*/ unsigned short SpawnID;
/*0002*/ unsigned short TimeStamp;
/*0004*/ int Heading:16;
/*0006*/ int unknown1:16; //??
/*0008*/ float DeltaZ; // ?? not sure
/*0012*/ int Animation:16;
/*0014*/ int padding014:16; //??
/*0016*/ int DeltaHeading:16; //?? not sure
/*0018*/ int unknown2:16; //??
/*0020*/ float Y;
/*0024*/ float DeltaY; //?? not sure
/*0028*/ float DeltaX; //?? not sure
/*0032*/ float Z;
/*0036*/ float X;
} P;
struct _MSGPACKET {
/*0000*/ char target[64];
/*0064*/ char sender[64];
/*0128*/ unsigned int language;
/*0132*/ unsigned int channel;
/*0136*/ char padding136[8];
/*0144*/ unsigned int languageskill;
/*0148*/ char message[100];
} M;
// init packets
ZeroMemory(&P, sizeof(P));
ZeroMemory(&M, sizeof(M));
P.SpawnID = (unsigned short)pChar->SpawnID;
P.Heading = (unsigned int)(pChar->Heading * 4);
PSPAWNINFO psTarget = NULL;
Target(pChar,NPCs.Name);
if (ppTarget && pTarget) {
psTarget = (PSPAWNINFO)pTarget;
}
if (psTarget)
{
strcpy(M.target,psTarget->Name);
}
strcpy(M.sender,pChar->Name);
M.channel=8;
M.languageskill=100;
// jump to
P.Z = NPCs.Z;
P.Y = NPCs.Y;
P.X = NPCs.X;
SendEQMessage(PKT_UPDATE_POSITION, &P, sizeof(P));
sprintf(M.message,"%s",NPCs.Phrase[j]);
SendEQMessage(PKT_CHANNEL_MESSAGE,&M,sizeof(M));
return true;
}
}
}
return false;
}
void FloydWarshall(FWVertice* vertices, int nodecount) // Vertices numbered from 0 to nodecount-1
{
distances = (int*) malloc(nodecount*nodecount*sizeof(int)*8);
predecessor = (int*) malloc(nodecount*nodecount*sizeof(int)*8);
int i,j,k;
for(i = 0; i < nodecount; i++)
{
for(j = 0; j < nodecount; j++)
{
distances[i*nodecount+j] = 0;
predecessor[i*nodecount+j] = i;
}
}
for(i = 0; i < nodecount; i++)
{
for(j = 0; j < vertices.numconnect; j++)
{
distances[i*nodecount + vertices.connections[j]] =1;
// vertices.connections[j].weight;
}
for(j = 0; j < nodecount; j++)
{
if(!distances[i*nodecount+j] && (i^j))
// i ^ j returns 0 if they are equal
{
distances[i*nodecount+j] = INFINITY;
}
}
}
for(k = 0; k < nodecount; k++)
{
for(i = 0; i < nodecount; i++)
{
for(j = 0; j < nodecount; j++)
{
if(distances[i*nodecount+j] > distances[i*nodecount+k] + distances[k*nodecount+j])
{
distances[i*nodecount+j] = distances[i*nodecount+k] + distances[k*nodecount+j];
predecessor[i*nodecount+j] = predecessor[k*nodecount+j];
}
}
}
}
}
VOID SimGate(PSPAWNINFO pChar, PCHAR szLine)
{
CHAR szMsg[MAX_STRING] = {0};
PCHARINFO2 pChar2 = GetCharInfo2();
sprintf(szMsg,"Gating...");
WriteChatColor(szMsg,USERCOLOR_DEFAULT);
DestZone=pChar2->ZoneBoundID;
DestType=0;
ZoneReason=11;
Y=pChar2->ZoneBoundY;
X=pChar2->ZoneBoundX;
Z=pChar2->ZoneBoundZ;
Heading=0;
ZoneChange=true;
return;
}
VOID SimFade(PSPAWNINFO pChar, PCHAR szLine)
{
CHAR szMsg[MAX_STRING] = {0};
if (pChar->Instance != 0)
{
DestZone = *((int *)(&(pChar->Instance)-1));
}
else
{
DestZone = pChar->Zone;
}
sprintf(szMsg,"Fading...");
WriteChatColor(szMsg,USERCOLOR_DEFAULT);
DestType=0;
ZoneReason=0;
Y=pChar->Y;
X=pChar->X;
Z=pChar->Z;
Heading =(int)pChar->Heading;
ZoneChange=true;
return;
}
// Called once, when the plugin is to initialize
PLUGIN_API VOID InitializePlugin(VOID)
{
DebugSpewAlways("Initializing MQ2PiggyZone");
ppLEQ=(LocalCEverQuest**)pinstCEverQuest;
AddCommand("/zone",ChangeZones);
AddCommand("/gate",SimGate);
AddCommand("/fade",SimFade);
AddCommand("/findpath",FindPath);
}
// Called once, when the plugin is to shutdown
PLUGIN_API VOID ShutdownPlugin(VOID)
{
DebugSpewAlways("Shutting down MQ2PiggyZone");
RemoveCommand("/zone");
RemoveCommand("/gate");
RemoveCommand("/fade");
RemoveCommand("/findpath");
}