/* This plugin will assist in creating name zone files for identifying map locations */
 
/* $Id: plugin_MapZone.sma,v 1.0.1.6 1/29/03 by Douglas Bliss $ */
 
#include <core>
#include <string>
#include <admin>
#include <adminlib>
 
new STRING_VERSION[MAX_DATA_LENGTH] = "1.0.1.6 (c) Douglas Bliss";
 
#define ACCESS_ZONETEAM ACCESS_KICK	// Access required to locate team players
#define ACCESS_ZONEALL ACCESS_RCON	// Access required to locate all players
#define ACCESS_ZONEMAP ACCESS_RCON	// Access required to edit map files
 
#define INVALID_LINE -1			// Assigned to current line when there is no current zone
#define FLOOR_SPACE 25			// Buffer room for floor (bury)
#define CEILING_SPACE 100		// Buffer room for ceiling (jumping...)
#define WALL_SPACE 5			// Buffer room for wall
#define HIGH_Z 9999			// Max altitude value (given to comments during sort)
 
new MapFile[MAX_DATA_LENGTH];		// Name of map file (init'd in CheckMapFile)
new Undo[MAX_DATA_LENGTH];		// Undo buffer (for update errors)
new UndoLine = INVALID_LINE;		// Undo line number
new CurrentZone[MAX_DATA_LENGTH];	// Name of current zone
new CurrentLine = INVALID_LINE;		// Current zone's line number from file
new x1,y1,z1,x2,y2,z2;			// Current zone's bounds (min x, ... max z)
 
 
		/*******************
		 * Building Blocks *
		 *******************/
 
 
// strips a number from a line of text.
StripNumber(Data[MAX_TEXT_LENGTH]) {
	new Temp[MAX_DATA_LENGTH];
	strbreak(Data,Temp,Data,0);
	return strtonum(Temp);
}
 
// Returns the minimum z value (for sorting)
GetMinZ(DataLine[]) {
	new Zone[MAX_TEXT_LENGTH];
	new Data[MAX_TEXT_LENGTH];
	new Temp;
	if (strncmp(DataLine,"#",1)==0) return HIGH_Z;
	strbreak(DataLine,Zone,Data,0);
	Temp = StripNumber(Data);		// x1
	Temp = StripNumber(Data);		// y1
	Temp = StripNumber(Data);		// z1
	return Temp;
}
 
// Ensures that the mapfile is set to the correct map and it exists
CheckMapFile() {
	new DataLine[MAX_TEXT_LENGTH];
	new MapName[MAX_NAME_LENGTH];
	if ((strlen(MapFile)==0) || (fileexists(MapFile)==0)) {
		currentmap(MapName,MAX_NAME_LENGTH);
		snprintf(MapFile,MAX_DATA_LENGTH,"MapData/%s.txt",MapName);
		if(fileexists(MapFile)!=1) {
			snprintf(DataLine,MAX_TEXT_LENGTH,"# Map Data File for map %s Created by plugin_MapZone, by Douglas Bliss",MapName);
			writefile(MapFile,DataLine);
			writefile(MapFile,"# Format: ^"in/at/on location/room^" x1 y1 z1 x2 y2 z2");
			writefile(MapFile,"# Rules: x1 < x2, y1 < y2, z1 < z2");
		}
	}
	return 1;
}
 
// Loads a map entry into the current zone.
LoadMapEntry(MapLine) {
	new DataLine[MAX_TEXT_LENGTH];
 
	readfile(MapFile,DataLine,MapLine,MAX_TEXT_LENGTH);
	if (strncmp(DataLine,"#",1)!=0) {
		strbreak(DataLine,CurrentZone,DataLine,0);
		x1 = StripNumber(DataLine);
		y1 = StripNumber(DataLine);
		z1 = StripNumber(DataLine);
		x2 = StripNumber(DataLine);
		y2 = StripNumber(DataLine);
		z2 = StripNumber(DataLine);
		CurrentLine=MapLine;
		return 1;
	}
	return 0;
}
 
// Locates existing zone by name
FindZoneByName(ZoneName[]) {
	new DataLine[MAX_TEXT_LENGTH];
	new Zone[MAX_TEXT_LENGTH];
 
	for(new i=1;i<=filesize(MapFile);i++) {
		readfile(MapFile,DataLine,i,MAX_TEXT_LENGTH);
		if (strncmp(DataLine,"#",1)!=0) {
			strbreak(DataLine,Zone,DataLine,0);
			if (strcasecmp(Zone,ZoneName)==0) {
				return LoadMapEntry(i);
			}
		}
	}
	return 0;
}
 
// Locates existing zone from x,y,z position
FindZoneByLoc(x,y,z,MakeCurrent,ZoneName[]) {
	new DataLine[MAX_TEXT_LENGTH];
	new Zone[MAX_TEXT_LENGTH];
	new xa,ya,za,xb,yb,zb;
 
	for(new i=1;i<=filesize(MapFile,lines);i++) {
		readfile(MapFile,DataLine,i,MAX_TEXT_LENGTH);
		if (strncmp(DataLine,"#",1)!=0) {
			strbreak(DataLine,Zone,DataLine,0);
			xa = StripNumber(DataLine);
			ya = StripNumber(DataLine);
			za = StripNumber(DataLine);
			xb = StripNumber(DataLine);
			yb = StripNumber(DataLine);
			zb = StripNumber(DataLine);
			if((x>=xa)&&(x<=xb)&&(y>=ya)&&(y<=yb)&&(z>=za)&&(z<=zb)) {
				strcpy(ZoneName,Zone,MAX_TEXT_LENGTH);
				if(MakeCurrent==1) {
					strcpy(CurrentZone,Zone,MAX_TEXT_LENGTH);
					CurrentLine=i;
					x1 = xa;
					y1 = ya;
					z1 = za;
					x2 = xb;
					y2 = yb;
					z2 = zb;
				}
				return 1;
			}
		}
	}
	strinit(ZoneName);
	return 0;
}
 
// Resets coordinate data for the current zone
ResetZone(x,y,z) {
	new DataLine[MAX_TEXT_LENGTH];
	x1 = x - WALL_SPACE;
	y1 = y - WALL_SPACE;
	z1 = z - FLOOR_SPACE;
	x2 = x + WALL_SPACE;
	y2 = y + WALL_SPACE;
	z2 = z + CEILING_SPACE;
	UndoLine = CurrentLine;
	readfile(MapFile,Undo,UndoLine,MAX_TEXT_LENGTH);
	snprintf(DataLine,MAX_TEXT_LENGTH,"^"%s^" %i %i %i %i %i %i",CurrentZone,x1,y1,z1,x2,y2,z2);
	return writefile(MapFile,DataLine,CurrentLine);
}
 
// Initialize data for a new Zone
InitZone(ZoneName[],x,y,z) {
	strcpy(CurrentZone,ZoneName,MAX_TEXT_LENGTH);
	CurrentLine=filesize(MapFile,lines)+1;
	return ResetZone(x,y,z);
}
 
// updates the Current Zone to include position (and writes to file).
UpdateZone(x,y,z) {
	new DataLine[MAX_TEXT_LENGTH];
	x1 = min(x1,x - WALL_SPACE);
	y1 = min(y1,y - WALL_SPACE);
	z1 = min(z1,z - FLOOR_SPACE);
	x2 = max(x2,x + WALL_SPACE);
	y2 = max(y2,y + WALL_SPACE);
	z2 = max(z2,z + CEILING_SPACE);
	UndoLine = CurrentLine;
	readfile(MapFile,Undo,UndoLine,MAX_TEXT_LENGTH);
	snprintf(DataLine,MAX_TEXT_LENGTH,"^"%s^" %i %i %i %i %i %i",CurrentZone,x1,y1,z1,x2,y2,z2);
	return writefile(MapFile,DataLine,CurrentLine);
}
 
 
		/********************
		 * Primary Routines *
		 ********************/
 
 
// admin_locate <target>
// Tells where the target is based on current location and map data.
public admin_locate(HLCommand,HLData,HLUserName,UserIndex) {
	new Text[MAX_TEXT_LENGTH];
	new User[MAX_NAME_LENGTH];
	new Target[MAX_NAME_LENGTH];
	new Zone[MAX_TEXT_LENGTH];
	new x,y,z;
	new iTeam1,iTeam2;
	new MakeCurrent;
	CheckMapFile();
	convert_string(HLUserName,User,MAX_NAME_LENGTH);
	convert_string(HLData,Target,MAX_DATA_LENGTH);
	strstripquotes(Target);
	if (strlen(Target)==0) strcpy(Target,User,MAX_NAME_LENGTH);
	MakeCurrent = streq(User,Target);
	if (check_user(Target)==0) {
		snprintf(Text,MAX_TEXT_LENGTH,"Error: Unable To Determine Who %s Is.",Target);
		messageex(User,Text,print_chat);
		return PLUGIN_HANDLED;
	}
	get_userTeam(User,iTeam1);
	get_userTeam(Target,iTeam2);
	if ((iTeam1!=iTeam2) && (access(ACCESS_ZONEALL)!=1)) {
		messageex(User,"Error: You may only locate players on your team.",print_chat);
		return PLUGIN_HANDLED;
	}
	if (get_userorigin(Target,x,y,z)==0) {
		snprintf(Text,MAX_TEXT_LENGTH,"Error: %s's Location Could Not Be Determined.",Target);
	} else if (FindZoneByLoc(x,y,z,MakeCurrent,Zone)==1) {
		if (MakeCurrent==1) {
			snprintf(Text,MAX_TEXT_LENGTH,"You are %s",Zone);
		} else {
			snprintf(Text,MAX_TEXT_LENGTH,"%s is %s",Target,Zone);
		}
	} else {
		snprintf(Text,MAX_TEXT_LENGTH,"Error: %s Is Not In Any Zone In Map File.",Target);
	}
	messageex(User,Text,print_chat);
	return PLUGIN_HANDLED;
}
 
// admin_zonecreate <in main blue respawn>
// Creates a new zone record (for multiple zones by same name).
public admin_zonecreate(HLCommand,HLData,HLUserName,UserIndex) {
	new Data[MAX_DATA_LENGTH];
	new User[MAX_NAME_LENGTH];
	new x,y,z;
	CheckMapFile();
	convert_string(HLUserName,User,MAX_NAME_LENGTH);
	convert_string(HLData,Data,MAX_DATA_LENGTH);
	strstripquotes(Data);
	if (get_userorigin(User,x,y,z) == 0) {
		messageex(User,"Error: Your Location Could Not Be Determined.",print_chat);
		return PLUGIN_HANDLED;
	} else if (strlen(Data)>0) {
		if (InitZone(Data,x,y,z)==1) {
			messageex(User,"New Zone Initiated.",print_chat);
		} else {
			messageex(User,"Error initializing New Zone.",print_chat);
		}
		return PLUGIN_HANDLED;
	} else {
		messageex(User,"Error: Missing Zone Name.",print_chat);
	}
	return PLUGIN_HANDLED;
}
 
// admin_zoneupdate <in main blue respawn>
// Updates a new or existing zone (blank = previous zone);
public admin_zoneupdate(HLCommand,HLData,HLUserName,UserIndex) {
	new Data[MAX_DATA_LENGTH];
	new User[MAX_NAME_LENGTH];
	new x,y,z;
	CheckMapFile();
	convert_string(HLUserName,User,MAX_NAME_LENGTH);
	convert_string(HLData,Data,MAX_DATA_LENGTH);
	strstripquotes(Data);
	if (get_userorigin(User,x,y,z) == 0) {
		messageex(User,"Error: Your Location Could Not Be Determined.",print_chat);
		return PLUGIN_HANDLED;
	} else if ((CurrentLine==INVALID_LINE) && (strlen(Data)==0)) {
		messageex(User,"Error: No Current Or Specified Zone.",print_chat);
		return PLUGIN_HANDLED;
	} else if (strlen(Data)>0) {
		// find specified zone or initialize a new one
		if (FindZoneByName(Data)!=1) {
			if (InitZone(Data,x,y,z)==1) {
				messageex(User,"New Zone Initiated.",print_chat);
			} else {
				messageex(User,"Error Initializing New Zone.",print_chat);
			}
			return PLUGIN_HANDLED;
		}
	}
	// Update existing zone data
	if (UpdateZone(x,y,z)==1) {
		messageex(User,"Zone Updated.",print_chat);
	} else {
		messageex(User,"Error Updating Zone.",print_chat);
	}
	return PLUGIN_HANDLED;
}
 
// admin_zonereset <in main blue respawn>
// Resets an existing zone to current location only (blank = previous zone);
public admin_zonereset(HLCommand,HLData,HLUserName,UserIndex) {
	new Data[MAX_DATA_LENGTH];
	new User[MAX_NAME_LENGTH];
	new x,y,z;
	CheckMapFile();
	convert_string(HLUserName,User,MAX_NAME_LENGTH);
	convert_string(HLData,Data,MAX_DATA_LENGTH);
	strstripquotes(Data);
	if (get_userorigin(User,x,y,z) == 0) {
		messageex(User,"Error: Your Location Could Not Be Determined.",print_chat);
		return PLUGIN_HANDLED;
	} else if ((CurrentLine==INVALID_LINE) && (strlen(Data)==0)) {
		messageex(User,"Error: No Current Or Specified Zone.",print_chat);
		return PLUGIN_HANDLED;
	} else if (strlen(Data)>0) {
		// find specified zone or initialize a new one
		if (FindZoneByName(Data)!=1) {
			messageex(User,"Error: Zone Not Found.",print_chat);
			return PLUGIN_HANDLED;
		}
	}
	// Reset existing zone data
	if (ResetZone(x,y,z)==1) {
		messageex(User,"Zone Reset.",print_chat);
	} else {
		messageex(User,"Error Reseting Zone.",print_chat);
	}
	return PLUGIN_HANDLED;
}
 
// admin_zoneundo
// Undoes the last update command
public admin_zoneundo(HLCommand,HLData,HLUserName,UserIndex) {
	new User[MAX_NAME_LENGTH];
	CheckMapFile();
	convert_string(HLUserName,User,MAX_NAME_LENGTH);
	if (UndoLine==INVALID_LINE) {
		messageex(User,"Error: No undo available.",print_chat);
	} else if (writefile(MapFile,Undo,UndoLine)==1) {
		UndoLine = INVALID_LINE;
		messageex(User,"Undo Complete.",print_chat);
	} else {
		messageex(User,"Error: Unable To Undo.",print_chat);
	}
	return PLUGIN_HANDLED;
}
 
// admin_zonesort
// Sorts map file to remove elevation conflicts (also clears current zone).
public admin_zonesort(HLCommand,HLData,HLUserName,UserIndex) {
	new User[MAX_NAME_LENGTH];
	new DataLineI[MAX_TEXT_LENGTH];
	new DataLineJ[MAX_TEXT_LENGTH];
	new zI,zJ;
	new i,j,iMax;
	convert_string(HLUserName,User,MAX_NAME_LENGTH);
	CheckMapFile();
	iMax = filesize(MapFile,lines);
	// Not the most efficient sort, but we should be dealing with a small number of entries
	for (i=1;i<iMax;i++) {
		readfile(MapFile,DataLineI,i,MAX_TEXT_LENGTH);
		zI = GetMinZ(DataLineI);
		if (zI < HIGH_Z) {
			for (j=i+1;j<=iMax;j++) {
				readfile(MapFile,DataLineJ,j,MAX_TEXT_LENGTH);
				zJ = GetMinZ(DataLineJ);
				if (zJ > zI) {
					writefile(MapFile,DataLineI,j);
					writefile(MapFile,DataLineJ,i);
					// Only need to retain DataLineJ as DataLineI now...
					strcpy(DataLineI,DataLineJ,MAX_TEXT_LENGTH);
					zI = zJ;
				}
			}
		}
	}
	messageex(User,"Sort Completed.",print_chat);
	UndoLine = INVALID_LINE;
	CurrentLine = INVALID_LINE;
}
 
 
		/********************
		 * Admin Mod Plugin *
		 ********************/
 
 
public plugin_init() {
	plugin_registerinfo("DougWare Map Zones","Creates map zone/location files.",STRING_VERSION);
 
	plugin_registercmd("admin_locate","admin_locate",ACCESS_ZONETEAM,"admin_locate <target>: Tells where the target is based on current location (target optional).");
	plugin_registercmd("admin_zonecreate","admin_zonecreate",ACCESS_ZONEMAP,"admin_zonecreate <zone name>: Initializes a new zone even if name matches an existing zone.");
	plugin_registercmd("admin_zoneupdate","admin_zoneupdate",ACCESS_ZONEMAP,"admin_zoneupdate <zone name>: Updates an existing zone or initializes a new zone (name optional).");
	plugin_registercmd("admin_zonereset","admin_zonereset",ACCESS_ZONEMAP,"admin_zonereset <zone name>: Resets an existing zone to current location only (blank = previous zone).");
	plugin_registercmd("admin_zoneundo","admin_zoneundo",ACCESS_ZONEMAP,"admin_zoneundo: Undoes the last update command.");
	plugin_registercmd("admin_zonesort","admin_zonesort",ACCESS_ZONEMAP,"admin_zonesort: Sorts map file to remove elevation conflicts (also clears current zone).");
 
	// also register commands for "admin_help plugin_mapzone"
	plugin_registerhelp("plugin_mapzone",ACCESS_ZONETEAM,"admin_locate <target>: Tells where the target is based on current location (target optional).");
	plugin_registerhelp("plugin_mapzone",ACCESS_ZONEMAP,"admin_zonecreate <zone name>: Initializes a new zone even if name matches an existing zone.");
	plugin_registerhelp("plugin_mapzone",ACCESS_ZONEMAP,"admin_zoneupdate <zone name>: Updates an existing zone or initializes a new zone (name optional).");
	plugin_registerhelp("plugin_mapzone",ACCESS_ZONEMAP,"admin_zonereset <zone name>: Resets an existing zone to current location only (blank = previous zone).");
	plugin_registerhelp("plugin_mapzone",ACCESS_ZONEMAP,"admin_zoneundo: Undoes the last update command.");
	plugin_registerhelp("plugin_mapzone",ACCESS_ZONEMAP,"admin_zonesort: Sorts map file to remove elevation conflicts (also clears current zone).");
 
	return PLUGIN_CONTINUE;
}