/* FILEIO.C
  Functions responsible for loading data from files, such as the one that
  decodes the level map files (loadmap()) and the one that loads in the
  tile attribute data contained in ep?attr.dat (loadtileattributes()).
  The functions for loading the graphics (EGALATCH&EGASPRIT) are in latch.c.
*/

#include "keen.h"
#include "sdl/CVideoDriver.h"
#include "sdl/sound/CSound.h"
#include "hqp/CMusic.h"
#include "include/fileio.h"
#include "include/fileio/rle.h"
#include "vorticon/CPlayer.h"
#include "CLogFile.h"
#include "CGraphics.h"
#include <unistd.h>
#include <sys/types.h>
#include <dirent.h>
#include "StringUtils.h"
#include "Debug.h"

extern CPlayer *Player;

unsigned int curmapx, curmapy;
unsigned char mapdone;
void addmaptile(unsigned int t)
{
  map.mapdata[curmapx][curmapy] = t;
  curmapx++;
  if (curmapx >= map.xsize)
  {
    curmapx = 0;
    curmapy++;

    if (curmapy >= map.ysize) mapdone = 1;
  }
}

bool renameFilenamesLowerCase(const std::string& dir_name)
{
#ifdef TARGET_WIN32
	return true;
#else
	DIR *p_Dir;
	char newname[256];
	char buf[256];
	struct dirent *dp;

	memset(newname,0,256);
	strcpy(buf,dir_name.c_str());

	if(buf[0] == '\0')
		strcpy(buf,"./");

	chdir("data");

	if((p_Dir = opendir(buf))==NULL)
		return false;

	bool retval = true;
	// This function checks if all the files in the directory are lower case.
	// If they aren't rename them
	while((dp = readdir(p_Dir)) != NULL)
	{
        if(dp->d_type == DT_REG)
        {
        	strcpy(newname,dp->d_name);

        	int len = strlen(newname);
        	for(int pos=0 ; pos < len ; pos++ )
        		if(newname[pos] >= 'A' && newname[pos] <= 'Z')
        			newname[pos] += ('a'-'A');

        	if(strncmp(newname,dp->d_name,strlen(dp->d_name)) != 0)
        		if(rename(dp->d_name,newname) != -1)
        			retval &= true;
        }
    }

	closedir(p_Dir);

	chdir("../");

	return retval;

#endif
}

short checkConsistencyofGameData(stGameData *p_GameData)
{
	short ok = 0;

	FILE *fp;

	// Let's define which files need to be read!
	if(p_GameData->Episode >= 1 && p_GameData->Episode <= 3)
	{
		p_GameData->FileList[1] = "keen" + itoa(p_GameData->Episode) + ".exe";
		p_GameData->FileList[2] = "egahead.ck" + itoa(p_GameData->Episode);
		p_GameData->FileList[3] = "egasprit.ck" + itoa(p_GameData->Episode);
		p_GameData->FileList[4] = "egalatch.ck" + itoa(p_GameData->Episode);
		p_GameData->FileList[5] = "finale.ck" + itoa(p_GameData->Episode);

		// Levels!
		p_GameData->FileList[6] = "level01.ck" + itoa(p_GameData->Episode);
		p_GameData->FileList[7] = "level02.ck" + itoa(p_GameData->Episode);
		p_GameData->FileList[8] = "level03.ck" + itoa(p_GameData->Episode);
		p_GameData->FileList[9] = "level04.ck" + itoa(p_GameData->Episode);
		p_GameData->FileList[10] = "level05.ck" + itoa(p_GameData->Episode);
		p_GameData->FileList[11] = "level06.ck" + itoa(p_GameData->Episode);
		p_GameData->FileList[12] = "level07.ck" + itoa(p_GameData->Episode);
		p_GameData->FileList[13] = "level08.ck" + itoa(p_GameData->Episode);
		p_GameData->FileList[14] = "level09.ck" + itoa(p_GameData->Episode);
		p_GameData->FileList[15] = "level10.ck" + itoa(p_GameData->Episode);
		p_GameData->FileList[16] = "level11.ck" + itoa(p_GameData->Episode);
		p_GameData->FileList[17] = "level12.ck" + itoa(p_GameData->Episode);
		p_GameData->FileList[18] = "level13.ck" + itoa(p_GameData->Episode);
		p_GameData->FileList[19] = "level14.ck" + itoa(p_GameData->Episode);
		p_GameData->FileList[20] = "level15.ck" + itoa(p_GameData->Episode);
		p_GameData->FileList[21] = "level16.ck" + itoa(p_GameData->Episode);
		p_GameData->FileList[22] = "level80.ck" + itoa(p_GameData->Episode);
		p_GameData->FileList[23] = "level81.ck" + itoa(p_GameData->Episode);
		p_GameData->FileList[24] = "level90.ck" + itoa(p_GameData->Episode);

		// Other
		if(p_GameData->Episode == 1)
			p_GameData->FileList[24] = "sounds.ck" + itoa(p_GameData->Episode);
	}

	// Rename all the the files in the directory to lower case
	if(!renameFilenamesLowerCase(p_GameData->DataDirectory))
	{
		g_pLogFile->ftextOut(PURPLE,"WARNING: There was an error while trying to format correctly the game data.");
		g_pLogFile->ftextOut(PURPLE,"Please check, if you have write permissions on the game data directory and it is accessible<br>");
	}

	// Finally check if they really exist!
	int c=0;
	for(c = 0 ; c < MAX_NUMBER_OF_FILES ; c++)
	{
		if(p_GameData->FileList[c][0] == 0) // If there are no more files!
			break;

		std::string buf = "data/" + p_GameData->DataDirectory;
		if(p_GameData->DataDirectory != "")
			buf += "/";
		buf += p_GameData->FileList[c];

		if((fp = fopen(buf.c_str(),"r")) != NULL)
		{
			fclose(fp);
		}
		else
		{
			g_pLogFile->ftextOut("Error! File \"%s\" was not found! Please add it to the configured directory.<br>", p_GameData->FileList[c].c_str());
			p_GameData->Episode = -1;
			ok++;
		}
	}

	if(ok==0)
		g_pLogFile->ftextOut("Game data of Episode %d is complete.<br>", p_GameData->Episode);

	return ok;
}

char NessieAlreadySpawned;
void addobjectlayertile(unsigned int t, stCloneKeenPlus *pCKP)
{
int o;
  switch(t)
  {
   case 0: break;       // blank
   case 255:            // player start

	 //Player[0].setCoord(curmapx << 4 << CSF, curmapy << 4 << CSF);

	 player[0].x = curmapx << 4 << CSF;
     player[0].y = curmapy << 4 << CSF;
     map.objectlayer[curmapx][curmapy] = 0;
     break;
   case NESSIE_PATH:          // spawn nessie at first occurance of her path
     if (pCKP->Control.levelcontrol.episode==3)
     {
       if (!NessieAlreadySpawned)
       {
         o = spawn_object(curmapx<<4<<CSF, curmapy<<4<<CSF, OBJ_NESSIE);
         objects[o].hasbeenonscreen = 1;
         NessieAlreadySpawned = 1;
         NessieObjectHandle = o;
       }
     }
     goto levelmarker;
   break;
   default:             // level marker
levelmarker: ;
     if ((t&0x7fff) < 256 && pCKP->Control.levelcontrol.levels_completed[t&0x00ff])
     {
    	 if(!options[OPT_LVLREPLAYABILITY].value)
    		map.objectlayer[curmapx][curmapy] = 0;
    	 else
    		 map.objectlayer[curmapx][curmapy] = t;

    	 int newtile = tiles[map.mapdata[curmapx][curmapy]].chgtile;

    	 // Consistency check! Some Mods have issues with that.
    	 if(pCKP->Control.levelcontrol.episode == 1 || pCKP->Control.levelcontrol.episode == 2)
    	 {
    		 if(newtile<77 || newtile>81)
    			 // something went wrong. Use default tile
    			 newtile = 77;

    		 // try to guess, if it is a 32x32 (4 16x16) Tile
    		 if(map.mapdata[curmapx-1][curmapy-1] == (unsigned int) newtile &&
    				 map.mapdata[curmapx][curmapy-1] == (unsigned int) newtile  &&
    				 map.mapdata[curmapx-1][curmapy] == (unsigned int) newtile)
    		 {
    			 map.mapdata[curmapx-1][curmapy-1] = 78; //ul
    			 map.mapdata[curmapx][curmapy-1] = 79; //ur
    			 map.mapdata[curmapx-1][curmapy] = 80; //bl
    			 newtile = 81; // br. this one
    		 }
    	 }
    	 else if(pCKP->Control.levelcontrol.episode == 3)
    	 {
    		 if(newtile<52 || newtile>56)
    			 // something went wrong. Use default tile
    			 newtile = 56;
    		 // try to guess, if it is a 32x32 (4 16x16) Tile
    		 if(map.mapdata[curmapx-1][curmapy-1] == (unsigned int) newtile &&
    				 map.mapdata[curmapx][curmapy-1] == (unsigned int) newtile  &&
    				 map.mapdata[curmapx-1][curmapy] == (unsigned int) newtile)
    		 {
    			 map.mapdata[curmapx-1][curmapy-1] = 52; //bl
    			 map.mapdata[curmapx][curmapy-1] = 53; //ur
    			 map.mapdata[curmapx-1][curmapy] = 54; //ul
    			 newtile = 55; // br. this one
    		 }
    	 }

 		map.mapdata[curmapx][curmapy] = newtile;

     }
     else
     {
       map.objectlayer[curmapx][curmapy] = t;
     }
     break;
  }

  curmapx++;
  if (curmapx >= map.xsize)
  {
    curmapx = 0;
    curmapy++;
    if (curmapy >= map.ysize) mapdone = 1;
  }
}

void addenemytile(unsigned int t, stCloneKeenPlus *pCKP)
{
int o,x;
  map.objectlayer[curmapx][curmapy] = t;

  if (t)
  {
    if (t==255)
    {
        if(curmapx >= map.xsize-2) // Edge bug. Keen would fall in some levels without this.
      	   curmapx = 4;

        if(curmapy >= map.ysize-2) // Edge bug. Keen would fall in some levels without this.
      	   curmapx = 4;

       //Player[0].setCoord(curmapx << 4 << CSF, ((curmapy << 4) + 8) << CSF);

       player[0].x = curmapx << 4 << CSF;
       player[0].y = ((curmapy << 4) + 8) << CSF;
    }
    else
    {
      switch(t)
      {
      case 0: break;
      case -1: break;
      case 1:  // yorp (ep1) vort (ep2&3)
           if (pCKP->Control.levelcontrol.episode==1)
           {
              x = curmapx;

              if (TileProperty[map.mapdata[x][curmapy+1]][BLEFT]) x--;
              spawn_object(x<<4<<CSF, ((curmapy<<4)+8)<<CSF, OBJ_YORP);
           }
           else
           {
              // in ep2 level 16 there a vorticon embedded in the floor for
              // some reason! that's what the if() is for--to fix it.
        	  if (TileProperty[map.mapdata[curmapx][curmapy+1]][BLEFT])
              {
                spawn_object(curmapx<<4<<CSF, ((curmapy<<4)-16)<<CSF, OBJ_VORT);
              }
              else
              {
                spawn_object(curmapx<<4<<CSF, curmapy<<4<<CSF, OBJ_VORT);
              }
           }
           break;
      case 2:    // garg (ep1) baby vorticon (ep2&3)
           if (pCKP->Control.levelcontrol.episode==1)
           {
             // those bastards. sometimes embedding garg's in the floor in
             // the original maps.
        	 if(TileProperty[map.mapdata[curmapx+1][curmapy+1]][BLEFT])
             {
               if (pCKP->Control.levelcontrol.chglevelto==7)
               {
                 spawn_object(curmapx<<4<<CSF, (curmapy-1)<<4<<CSF, OBJ_GARG);
               }
               else
               {
                 spawn_object((curmapx-1)<<4<<CSF, (curmapy)<<4<<CSF, OBJ_GARG);
               }
             }
             else
             {
               spawn_object(curmapx<<4<<CSF, curmapy<<4<<CSF, OBJ_GARG);
             }
           }
           else
           {
             spawn_object(curmapx<<4<<CSF, curmapy<<4<<CSF, OBJ_BABY);
           }
           break;
      case 3:    // vorticon (ep1) bear (ep2)
           if (pCKP->Control.levelcontrol.episode==1)
           {
             spawn_object(curmapx<<4<<CSF, curmapy<<4<<CSF, OBJ_VORT);
           }
           else if (pCKP->Control.levelcontrol.episode==2)
           {
             spawn_object(curmapx<<4<<CSF, curmapy<<4<<CSF, OBJ_BEAR);
           }
           else if (pCKP->Control.levelcontrol.episode==3)
           {
              spawn_object(curmapx<<4<<CSF, curmapy<<4<<CSF, OBJ_MOTHER);
           }
           break;
      case 4:    // butler (ep1) OR walker (ep2) OR meep (ep3)
           if (pCKP->Control.levelcontrol.episode==1)
             spawn_object(curmapx<<4<<CSF, curmapy<<4<<CSF, OBJ_BUTLER);
           else if (pCKP->Control.levelcontrol.episode==2)
             spawn_object(curmapx<<4<<CSF, curmapy<<4<<CSF, OBJ_WALKER);
           else if (pCKP->Control.levelcontrol.episode==3)
             spawn_object(curmapx<<4<<CSF, ((curmapy<<4)+8)<<CSF, OBJ_MEEP);
           break;
      case 5:    // tank robot (ep1&2) karate bear (ep3)
           if (pCKP->Control.levelcontrol.episode==1)
           {
              o = spawn_object(curmapx<<4<<CSF, ((curmapy<<4)+8)<<CSF, OBJ_TANK);
              // set tank robot guarding bonus level to be active at startup
              if (pCKP->Control.levelcontrol.chglevelto==13)
              {
                objects[o].hasbeenonscreen = 1;
              }
           }
           else if (pCKP->Control.levelcontrol.episode==2)
              spawn_object(curmapx<<4<<CSF, ((curmapy<<4)+0)<<CSF, OBJ_TANKEP2);
           else if (pCKP->Control.levelcontrol.episode==3)
           {
        	  if(TileProperty[map.mapdata[curmapx][curmapy+1]][BLEFT])
              {
                spawn_object(curmapx<<4<<CSF, (curmapy-1)<<4<<CSF, OBJ_NINJA);
              }
              else
              {
                spawn_object(curmapx<<4<<CSF, curmapy<<4<<CSF, OBJ_NINJA);
              }
           }
           break;
      case 6:    // up-right-flying ice chunk (ep1) horiz platform (ep2)
                 // foob (ep3)
           if (pCKP->Control.levelcontrol.episode==1)
           {
              o = spawn_object((((curmapx+1)<<4)+4)<<CSF, ((curmapy<<4)-4)<<CSF, OBJ_SECTOREFFECTOR);
              objects[o].ai.se.type = SE_ICECANNON;
              objects[o].ai.se.dir = DUPRIGHT;
              objects[o].inhibitfall = 1;
              objects[o].hasbeenonscreen = 1;
           }
           else if (pCKP->Control.levelcontrol.episode==2)
           {
              o = spawn_object(curmapx<<4<<CSF, ((curmapy<<4)-3)<<CSF, OBJ_PLATFORM);
           }
           else if (pCKP->Control.levelcontrol.episode==3)
           {
              o = spawn_object(curmapx<<4<<CSF, curmapy<<4<<CSF, OBJ_FOOB);
           }
           break;
      case 7:   // spark (ep2) ball (ep3)
           if (pCKP->Control.levelcontrol.episode==2)
           {
              o = spawn_object(curmapx<<4<<CSF,curmapy<<4<<CSF,OBJ_SECTOREFFECTOR);
              objects[o].ai.se.type = SE_SPARK;
              pCKP->Control.levelcontrol.canexit = 0;    // can't exit till spark is shot
           }
           else
           {
              o = spawn_object(curmapx<<4<<CSF,curmapy<<4<<CSF,OBJ_BALL);
              objects[o].hasbeenonscreen = 1;
           }
           break;
      case 8:    // jack (ep3)
           if (pCKP->Control.levelcontrol.episode==3)
           {
              o = spawn_object(curmapx<<4<<CSF,curmapy<<4<<CSF,OBJ_JACK);
              objects[o].hasbeenonscreen = 1;
           }
           break;
      case 9:    // up-left-flying ice chunk (ep1) horiz platform (ep3)
           if (pCKP->Control.levelcontrol.episode==1)
           {
             o = spawn_object(((curmapx<<4)-4)<<CSF, ((curmapy<<4)-4)<<CSF, OBJ_SECTOREFFECTOR);
//             objects[o].ai.icechunk.movedir = DUPLEFT;
              objects[o].ai.se.type = SE_ICECANNON;
              objects[o].ai.se.dir = DUPLEFT;
             objects[o].inhibitfall = 1;
             objects[o].hasbeenonscreen = 1;
           }
           else if (pCKP->Control.levelcontrol.episode==3)
           {
             o = spawn_object(curmapx<<4<<CSF, (((curmapy)<<4)-4)<<CSF, OBJ_PLATFORM);
           }
           break;
      case 10:   // rope holding the stone above the final vorticon (ep1)
                 // vert platform (ep3)
           if (pCKP->Control.levelcontrol.episode==1)
           {
             spawn_object(curmapx<<4<<CSF, curmapy<<4<<CSF, OBJ_ROPE);
           }
           else if (pCKP->Control.levelcontrol.episode==3)
           {
             spawn_object(curmapx<<4<<CSF, curmapy<<4<<CSF, OBJ_PLATVERT);
           }
           break;
      case 11:   // jumping vorticon (ep3)
           if (pCKP->Control.levelcontrol.episode==3)
           {
             spawn_object(curmapx<<4<<CSF, ((curmapy<<4)-8)<<CSF, OBJ_VORT);
           }
           break;
      case 12:   // sparks in mortimer's machine
          o = spawn_object(curmapx<<4<<CSF, curmapy<<4<<CSF, OBJ_SECTOREFFECTOR);
          objects[o].ai.se.type = SE_MORTIMER_SPARK;
          objects[o].hasbeenonscreen = 1;
          break;
      case 13:   // mortimer's heart
          o = spawn_object(curmapx<<4<<CSF, curmapy<<4<<CSF, OBJ_SECTOREFFECTOR);
          objects[o].ai.se.type = SE_MORTIMER_HEART;
          objects[o].hasbeenonscreen = 1;
          break;
      case 14:   // right-pointing raygun (ep3)
           if (pCKP->Control.levelcontrol.episode==3)
           {
             o = spawn_object(curmapx<<4<<CSF, curmapy<<4<<CSF, OBJ_SECTOREFFECTOR);
             objects[o].ai.se.type = SE_GUN_RIGHT;
             objects[o].hasbeenonscreen = 1;
           }
           break;
      case 15:   // vertical raygun (ep3)
           if (pCKP->Control.levelcontrol.episode==3)
           {
             o = spawn_object(curmapx<<4<<CSF, curmapy<<4<<CSF, OBJ_SECTOREFFECTOR);
             objects[o].ai.se.type = SE_GUN_VERT;
             objects[o].hasbeenonscreen = 1;
           }
           break;
      case 16:  // mortimer's arms
          o = spawn_object(curmapx<<4<<CSF, curmapy<<4<<CSF, OBJ_SECTOREFFECTOR);
          objects[o].ai.se.type = SE_MORTIMER_ARM;
          objects[o].hasbeenonscreen = 1;
      break;
      case 17:  // mortimer's left leg
          o = spawn_object(curmapx<<4<<CSF, curmapy<<4<<CSF, OBJ_SECTOREFFECTOR);
          objects[o].ai.se.type = SE_MORTIMER_LEG_LEFT;
          objects[o].hasbeenonscreen = 1;
      break;
      case 18:  // mortimer's right leg
          o = spawn_object(curmapx<<4<<CSF, curmapy<<4<<CSF, OBJ_SECTOREFFECTOR);
          objects[o].ai.se.type = SE_MORTIMER_LEG_RIGHT;
          objects[o].hasbeenonscreen = 1;
      break;
      default:
    	   g_pGraphics->fadePalette(20);
           g_pLogFile->ftextOut(PURPLE,"unknown enemy type %d at (%d,%d)<br>", t, curmapx, curmapy);
           //while(g_pInput->getPressedCommand(KENTER));

           break;
      }
    }
  }
  curmapx++;
  if (curmapx >= map.xsize)
  {
    curmapx = 0;
    curmapy++;
    if (curmapy >= map.ysize) mapdone = 1;
  }
}

unsigned int fgeti(FILE *fp) {
unsigned int temp1, temp2;
  temp1 = fgetc(fp);
  temp2 = fgetc(fp);
  return (temp2<<8) | temp1;
}

unsigned long fgetl(FILE *fp) {
unsigned int temp1, temp2, temp3, temp4;
  temp1 = fgetc(fp);
  temp2 = fgetc(fp);
  temp3 = fgetc(fp);
  temp4 = fgetc(fp);
  return (temp4<<24) | (temp3<<16) | (temp2<<8) | temp1;
}

unsigned int loadmap(const std::string& filename, const std::string& path, int lvlnum, int isworldmap, stCloneKeenPlus *pCKP)
{
FILE *fp;
int t;
unsigned int c;
int numruns = 0;
int gottenazero;
int resetcnt, resetpt;

  NessieAlreadySpawned = 0;
  map.isworldmap = isworldmap;

	std::string buffer = formatPathString(path);
	std::string fname = buffer + filename;
  fp = fopen(fname.c_str(), "rb");
  if (!fp)
  {
    // only record this error message on build platforms that log errors
    // to a file and not to the screen.
	  g_pLogFile->ftextOut("loadmap(): unable to open file %s<br>", fname.c_str());
    return 1;
  }
  g_pLogFile->ftextOut("loadmap(): file %s opened. Loading...<br>", fname.c_str());

    // decompress map RLEW data
  curmapx = curmapy = mapdone = 0;

 unsigned int *filebuf; // big File Buffer for the uncompression
 filebuf = (unsigned int*) malloc(500000*sizeof(int));

 if(filebuf == NULL)
 {
	 g_pLogFile->ftextOut("loadmap(): unable to allocate memory for unpacking the level file<br>", fname.c_str());
	    return 1;
 }

  int finsize; 		   // Final size

  finsize = unRLEW(fp, filebuf);

  c=0;

  if(finsize == -1)
  {
	  rewind(fp);
	  while(!feof(fp))
	  {
		  filebuf[c] = fgeti(fp);
		  c++;
	  }
  }


  map.xsize = filebuf[2];
  map.ysize = filebuf[3];

  if (map.xsize > 260 || map.ysize > 260)
  {
    g_pLogFile->textOut(RED,"loadmap(): level is too big<br>");
    return 1;
  }

  c=18;

  while(c < ((filebuf[9] / 2)+18)) // Check against Tilesize
  {
	  t = filebuf[c];
	  if(!mapdone)
		  addmaptile(t);

	  if(t > 255)
	  {
		  t=0; // If there are some invalid values in the file
	  }

      c++;
  }


  // now do the enemies
    gottenazero = 0;

  // get enemy/objectlayer data
    curmapx = curmapy = mapdone = numruns = 0;
    resetcnt = resetpt = 0;

    while(!mapdone)
        {
          t = filebuf[c];

          if (t==0 && !gottenazero)
          {
             curmapx = curmapy = 0;
             gottenazero = 1;
          }
          if (map.isworldmap) addobjectlayertile(t, pCKP); else addenemytile(t, pCKP);
          if (++resetcnt==resetpt) curmapx=curmapy=0;

          c++;
        }

    free(filebuf);

    fclose(fp);

    // HQ Sounds. Load Music for a level if you have HQP
    g_pMusicPlayer->stop();

    if((fp=fopen("data/hqp/music/table.cfg","rt")) != NULL)
    {
		static const int MAX_STRING_LENGTH = 256;
    	char buf1[MAX_STRING_LENGTH];
    	char buf2[MAX_STRING_LENGTH];

    	memset(buf1,0,sizeof(char)*MAX_STRING_LENGTH);
    	memset(buf2,0,sizeof(char)*MAX_STRING_LENGTH);

    	while(!feof(fp))
    	{
    		fscanf(fp,"%s",buf1);

    		if(strcmp(buf1,filename.c_str()) == 0)
    		{
    			fscanf(fp,"%s",buf2);
    			break;
    		}
    		else
    			fgets(buf1,MAX_STRING_LENGTH,fp);
    	}


    	fclose(fp);

    	if(*buf2 != 0)
    	{
    		strcpy(buf1,"data/hqp/music/");
    		strcat(buf1,buf2);
    		g_pMusicPlayer->load(g_pSound->getAudioSpec(),buf1);
    		g_pMusicPlayer->play();
     	}
    }

    // Didn't it work? Don't matter. HQP is optional, so continue

 // install enemy stoppoints as needed
 if (pCKP->Control.levelcontrol.episode==1 && lvlnum==13)
 {
    map.objectlayer[94][13] = GARG_STOPPOINT;
    map.objectlayer[113][13] = GARG_STOPPOINT;
    map.objectlayer[48][6] = GARG_STOPPOINT;
    map.objectlayer[80][5] = GARG_STOPPOINT;
    map.objectlayer[87][5] = GARG_STOPPOINT;
    map.objectlayer[39][18] = GARG_STOPPOINT;
 }
 else if (pCKP->Control.levelcontrol.episode==3 && lvlnum==6)
 {
    map.objectlayer[40][7] = BALL_NOPASSPOINT;
    map.objectlayer[50][7] = BALL_NOPASSPOINT;
 }
 else if (pCKP->Control.levelcontrol.episode==3 && lvlnum==9)
 {
    map.objectlayer[45][106] = BALL_NOPASSPOINT;

 }
 else if (pCKP->Control.levelcontrol.episode==3 && lvlnum==4)
 {
    map.objectlayer[94][17] = BALL_NOPASSPOINT;
 }

  return 0;
}

char loadstrings_AddAttr(char *attr, int stringIndex)
{
char stAttrName[80];
char stAttrValue[80];
int attrvalue;
int RAMAllocSize;
char *copyPtr;
unsigned int i;

  // if the attribute does not have an equals sign bail
  if (!strstr(attr, "="))
  {
	  g_pLogFile->ftextOut("loadstrings_AddAttr(): '%s' is not a valid attribute definition.<br>", attr);
    return 1;
  }

  // split the attribute up into it's name and it's value
  copyPtr = stAttrName;
  for(i=0;i<strlen(attr);i++)
  {
    if (attr[i] != ' ' && attr[i]!=9)      // strip out spaces and tabs
    {
      if (attr[i] != '=')
      {
        *copyPtr = attr[i];
        copyPtr++;
      }
      else
      { // hit the equals sign
        *copyPtr = 0;
        copyPtr = stAttrValue;
      }
    }
  }
  *copyPtr = 0;

  attrvalue = atoi(stAttrValue);

  // malloc space for the attribute name
  RAMAllocSize = strlen(stAttrName) + 1;
  strings[stringIndex].attrnames[strings[stringIndex].numAttributes] = (unsigned char*) malloc(RAMAllocSize+1);
  if (!strings[stringIndex].attrnames[strings[stringIndex].numAttributes])
  {
    g_pLogFile->textOut(RED,"loadstrings_AddAttr(): Unable to allocate space for attribute name ('%s').<br>", stAttrName);
    return 1;
  }

  // copy the data into the strings structure
  memcpy(strings[stringIndex].attrnames[strings[stringIndex].numAttributes], stAttrName, RAMAllocSize);
  strings[stringIndex].attrvalues[strings[stringIndex].numAttributes] = attrvalue;

  strings[stringIndex].numAttributes++;
  return 0;
}

// load strings from file *fname ("strings.dat")
char loadstrings(const std::string& fname)
{
FILE *fp;
char state;
	std::string stName;
	std::string stString;
char stAttr[80];
int i,c;
int attrIndex=0;
int waitChar, gotoState;
char highlight;

  #define STSTATE_WAITCHAR      0
  #define STSTATE_READNAME      1
  #define STSTATE_READSTRING    2
  #define STSTATE_READATTR      3

g_pLogFile->ftextOut("loadstrings(): Opening string file '%s'.<br>", fname.c_str());
  fp = fopen(fname.c_str(), "rb");
  if (!fp)
  {
	  g_pLogFile->ftextOut("loadstrings(): String file unable to open.<br>");
    return 1;
  }

  // go through all the strings and NULL out the entries...this will
  // let us know which ones are in use (and need to be free()d at shutdown)
  for(i=0;i<MAX_STRINGS;i++)
  {
    strings[i].name = "";
    strings[i].stringptr = "";
    strings[i].numAttributes = 0;
  }

  numStrings = 0;
  highlight = 0;

  // read until we get to the first string name
  state = STSTATE_WAITCHAR;
  waitChar = '[';
  gotoState = STSTATE_READNAME;
  do
  {
    c = fgetc(fp);              // read byte from file

    if (c<0)
    {   // EOF
      break;
    }
    // ignore LF's
    if (c==10) continue;

    switch(state)
    {
     case STSTATE_WAITCHAR:
      // ignore chars until we read a waitChar, then go to state gotoState
      if (c==waitChar)
      {
        state = gotoState;
      }
      break;
     case STSTATE_READATTR:
      if (c==13)
      { // reached CR, start reading string
        if (attrIndex)
        {
          stAttr[attrIndex] = 0;
          if (loadstrings_AddAttr( (char*) stAttr, numStrings)) return 1;
        }
        state = STSTATE_READSTRING;
      }
      else if (c==' ')
      { // end of an attribute definition
        if (attrIndex)
        {
          stAttr[attrIndex] = 0;  // null-terminate
          if (loadstrings_AddAttr( (char*) stAttr, numStrings)) return 1;
        }
        attrIndex = 0;
      }
      else
      { // save char to attribute buffer
        stAttr[attrIndex] = c;
        attrIndex++;
      }
      break;
     case STSTATE_READNAME:
      // read in the string name until we get to ']'
      if (c != ']')
      {
		  stName += (char)c;
      }
      else
      {
		  highlight = 0;
        // read any attributes until the CR
        state = STSTATE_READATTR;
        attrIndex = 0;
      }
      break;
     case STSTATE_READSTRING:
      // read in string data until we see another '['
      if (c != '[')
      {
        // allow delimiters:
        // you can put [ and ] in the string by using \( and \).
        // set a highlight (change font color to the +128 font) with \H
        // stop highlighting with \h
        if (stString.size()>0 && stString[stString.size()-1]==char('\\'+(highlight*128)))
        {  // delimiter detected
          if (c=='(')
          {
            stString[stString.size()-1] = '[' + (highlight*128);
          }
          else if (c==')')
          {
            stString[stString.size()-1] = ']' + (highlight*128);
          }
          else if (c=='H')
          {
            highlight = 1;
            stString.erase(stString.size()-1);
          }
          else if (c=='h')
          {
            highlight = 0;
			stString.erase(stString.size()-1);
          }
          else if (c=='\\')
          {
            stString[stString.size() - 1] = '\\' + (highlight*128);
          }
        }
        else
        { // normal non-delimited char
			stString += (char)c + ((highlight && c!=0 && c!=13) ? 128 : 0);
        }
      }
      else
      {
//		  HexDump(GetConstIterator(stName), printOnLogger<notes>, 0);
//		  HexDump(GetConstIterator(stString), printOnLogger<notes>, 0);
//		  notes.flush();
        /* save the string to the strings[] structure */

        // copy the string info to the newly malloc()'d memory area
		  strings[numStrings].name = stName;
		  strings[numStrings].stringptr = stString;
		  stName = "";
		  stString = "";
		  
        numStrings++;
        // read the name of the next string
        state = STSTATE_READNAME;
      }
      break;
    }

  } while(1);

  g_pLogFile->ftextOut("loadstrings(): loaded %d strings from '%s'.<br>", numStrings, fname.c_str());
  fclose(fp);
  return 0;
}

int freestrings(void)
{
int i,j;
int NumStringsFreed;

  NumStringsFreed = 0;
  for(i=0;i<MAX_STRINGS;i++)
  {
    if (strings[i].name != "")
    {
      // free all attribute names
      for(j=0;j<strings[i].numAttributes;j++)
      {
        free(strings[i].attrnames[j]);
      }
      strings[i].numAttributes = 0;

      NumStringsFreed++;
    }
  }

  return NumStringsFreed;
}

static void dumpstrings() {
	notes << "Available strings: ";
	for(int i=0;i<numStrings;i++)
		notes << strings[i].name << ", ";
	notes << endl;
}

// returns a pointer to the string with name 'name'
std::string getstring(const std::string& name)
{
  for(int i=0;i<numStrings;i++)
  {
	  if (name == strings[i].name)
    {
      return strings[i].stringptr;
    }
  }

	//dumpstrings();
	
  return "UNKNOWN '" + name + "' STRING";
}

// because windows and linux read files differently, these is to function to get them correctly
std::string formatPathString(const std::string& path)
{
	size_t p = path.find('\r');
	return "data/" + path.substr(0,p) + "/";
}

// returns attribute attrname of string stringname, or -1 if it doesn't exist.
int GetStringAttribute(const std::string& stringName, const char *attrName)
{
int i,j;
  for(i=0;i<numStrings;i++)
  {
    if (stringName == strings[i].name)
    {
      // we found the string, now find the requested attribute
      for(j=0;j<strings[i].numAttributes;j++)
      {
        if (!strcmp((char*) attrName, (char*) strings[i].attrnames[j]))
        {
          return strings[i].attrvalues[j];
        }
      }
      // failed to find attribute
      return -1;
    }
  }
  // failed to find string
  return -1;
}

