/******************************************************************************************
	CRTest PS   (c) 1999-2001 ISL

	map_view.c:			Map Walkabout
******************************************************************************************/


//#define USE_ACTIONMAN


#ifdef USE_ACTIONMAN
	#define ANIMATION_WALK		AM_WALK
	#define ANIMATION_STAND		AM_STAND	
	#define ANIMATION_RUN		AM_RUN
#else
	#define ANIMATION_WALK		0
	#define ANIMATION_STAND		0
	#define ANIMATION_RUN		0
#endif

// Set this to "1", and you'll need to define "map_scale_adjust" in map_draw
// Set it to match the shifts in "MAP_SCALE_DEPTH_DOWN" value in map_draw, and map_scale_adjust in map_draw can be removed
//#define MAP_SCALE_ADJUST 1
#define MAP_SCALE_ADJUST 8


#define CHEAT_START_POSITIONMARKER		3210
// 3211 = other rat's cheat-start-pos
#define OTHER_CHICKEN_POSITIONMARKER	3212
#define NICK_POSITIONMARKER				3213
#define FETCHER_POSITIONMARKER			3214
#define OTHER_RAT_POSITIONMARKER		3215

#include "global.h"
#include "main.h"
#include "menu.h"
#include "map_draw.h"
#include "bff_load.h"
#include "collide.h"
#include "debris.h"
#include "lights.h"
#include "timer.h"
#include "maths.h"
#include "inventory.h"
#include "enemies.h"
#include "scenics.h"
#include "charactr.h"
#include "sound.h"
#include "puzzles.h"
#include "camera.h"

#include "global.h"
#include <psitypes.h>

#include "platform.h"
#include "scenics.h"
#include "overlays.h"
#include "help.h"
#include "particle.h"
#include "deth.h"
#include "fma.h"

#include "curtains.h"
#include "demo.h"
#include "dogchase.h"
#include "seesaw.h"
#include "catapult.h"
#include "crate.h"
#include "fireworks.h"
#include "map.h"

#include "engine.h"
#include "construct.h"
#include "wings.h"
#include "egglaying.h"
#include "options.h"
#include "startup.h"
#include "sprouts.h"
#include "loadsnd.h"
#include "lev_flow.h"


extern void collideFree();
extern int options_returnlevel;

#define MAX_ROTATE1FRAME 256

//COLLDATA	actorColl;
//PARTICLEFX *pCrouchFountain = NULL;

char	sLevelName[16];
int		level_start_marker = 0;

//extern void customPsiDrawSortedPrimitives(int depth);

// ============================================

//#define CHAR_NAME_GINGER	(0)
//#define CHAR_NAME_WELLY		(1)
//#define CHAR_NAME_ROCKY		(2)
//#define CHAR_NAME_NICK		(3)
//#define CHAR_NAME_FETCHER	(4)
//#define CHAR_NAME_GNOME		(5)

// These need to match the CHAR_NAME numbers...
char *mapplay_charnames[] =
{
	"GINGER","WELLY","ROCKY","NICK","FETCHER","C_GNOME"
};


void mapplayDraw();
void mapplayCycle();

TextureBankType	*mapplay_level_textures = 0;
BFF_Header *mapplay_level_bff = NULL;
FMA_WORLD2 *mapplay_fma_world = NULL;
FMA_WORLD *mapplay_sky_world = NULL;


//char levname_to_play[10];


int	mapplay_camera_ya = 0;
int	mapplay_camera_xa = 512;
int	mapplay_camera_range = 1500 * MAP_SCALE_ADJUST;

char mapplay_leave_level_flag = 0;
char mapplay_next_level[16];	// We've got a 13-char-long level name
int mapplay_next_marker;


TextureType *pMap;

/*
Quick & tacky runaround chicken
*/



CHARACTER *pGameCharacter[10];
// CPW: 	{"DOG01",	enemyUpdateDog,		STATE_SLEEPING,	0,	NULL},

/*
ENEMY_POINT pEnemyPoints[]={
	{-244.736,  58.157, -91.534, 50},
	{-462.736,  58.157, -90.534, 50}, 
	{-459.736,  58.157, 788.466, 50}, 
	{-81.736,  58.157, 787.466, 50}, 
	{-87.736,  58.157, -93.534, 50}, 
	{339.264,  58.157, -88.534, 50}, 
	{339.000,   0.000, 794.000, 50}, 
	{-100.000,   0.000, 785.000, 50}, 
	{-90.000,   0.000, -132.000, 50}, 
	{-823.000,   0.000, -134.000, 50}, 
	{-848.000,   0.000, -908.000, 50}, 
	{-454.000,   0.000, -903.000, 50}, 
	{-449.000,   0.000, -194.000, 50} 
};
*/

// various vars for use with collision
/*
VECTOR		actorVel;
VECTOR		actorPos;
*/
int mainCharacter = 0;
int nCharacters = 0;

typedef struct SPECIALLEVEL_SWITCHSTR
{
	char *name;
	int ovl_number;	// or -1 if you don't care
	VOID_SUBROUTINE start;
}SPECIALLEVEL_SWITCHSTR;


SPECIALLEVEL_SWITCHSTR level_switches[] =
{
//	{"A1_DOGCHASE",		ACT1_SUBGAME_OVERLAY,	dogchaseStart},
	{"A1_DOGCHASE",		-1,						levflowDogchaseFromGame},
	{"A2_SEESAW",		ACT2_SUBGAME_OVERLAY,	seesawStart},
	{"A2_CATAPULT",		ACT2_SUBGAME_OVERLAY,	catapultStart},
	{"A2_FIREWORKS",	ACT2_SUBGAME_OVERLAY,	fireworksStart},
	{"A2_COGCHASE",		-1,						levflowCogchaseFromGame},

	{"A4_ENGINEHUT",	ACT4_SUBGAME_OVERLAY,	engineStart},
	{"A4_CONSTRUCT",	ACT4_SUBGAME_OVERLAY,	constructStart},
	{"A4_WINGSHUT",		ACT4_SUBGAME_OVERLAY,	wingStart},
	{"A4_EGGHUT",		ACT4_SUBGAME_OVERLAY,	elStart4},
	{"A2_EGGHUT",		ACT4_SUBGAME_OVERLAY,	elStart2},
	{"A1_EGGHUT",		ACT4_SUBGAME_OVERLAY,	elStart1},


	{"A4_FLYMACHINE",	-1,						crateStart},
};
#define N_LEVEL_SWITCHES (sizeof(level_switches) / sizeof(SPECIALLEVEL_SWITCHSTR))

int	pauseFlag=FALSE;

int currentCharacter = CHAR_NAME_GINGER;
int currentChicken = CHAR_NAME_GINGER;
int currentRat = CHAR_NAME_NICK;	// CPW: no current rat 






void testcharStartModel(void)
{
	int inScenicHut = 0;
	int inRatInterfaceRoom = 0;
	CHARACTER *ptr;
	static int oldLevel=-1;
	SfxSampleType **ppCharSamples = NULL;
	int newAct = levinfoGetAct(levinfo->level_number);

	nCharacters = 0;

	DB("New act  =%d\n", newAct);

	if(oldLevel!=-1)
	{
		if(levinfoGetAct(oldLevel) < newAct)
		{
			DB("Setting ginger to be main character, because we have started a new act (%d)\n", newAct);
			currentCharacter = currentChicken = CHAR_NAME_GINGER;

			if(newAct == 1)
				helpOn = TRUE;
			else
				helpOn = FALSE;
		}
	}
	else
	{
		if(newAct == 1)
			helpOn = TRUE;
		else
			helpOn = FALSE;
	}

	oldLevel = levinfo->level_number;


	if(levinfo)
	{
// CPW: if((levinfo->level_number == LEVNUMBER_A2_SCENIC2) || (levinfo->level_number == LEVNUMBER_A4_SCENIC2))

		if((levinfo->level_number == LEVNUMBER_A2_TOOLEXT_D)
			|| (levinfo->level_number == LEVNUMBER_A2_HOUSEXT_D)
			|| (levinfo->level_number == LEVNUMBER_UNSPECIFIED))
		{
			DB(" *** Rocky should appear in this level\n");
			inScenicHut = TRUE;
		}

		if(    (levinfo->level_number == LEVNUMBER_A4_KITCHEN)	// Possibly the garden instead of here
			|| (levinfo->level_number == LEVNUMBER_A4_TUNNEL)
			|| (levinfo->level_number == LEVNUMBER_A4_PIEBARN))
			inRatInterfaceRoom = TRUE;

		if(levinfo->level_number == LEVNUMBER_A2_COGCHASE)
		{
			currentChicken = currentCharacter = CHAR_NAME_ROCKY;
		}
	}


	// CPW: playing the game

	// CPW: welly & gnome (do these first, because chicken & rat initialization needs them)
	if(characterIsThere(charInfo + CHAR_NAME_WELLY))
	{
		pGameCharacter[nCharacters] = characterCreate(charInfo + CHAR_NAME_WELLY);
		characterVars.pHideChar = pGameCharacter[nCharacters];
		pGameCharacter[nCharacters++]->pEnemy->runFlags&=(~RUNFLAGS_VISIBLE);
	}


	if(characterIsThere(charInfo + CHAR_NAME_GNOME))
	{
		pGameCharacter[nCharacters] = characterCreate(charInfo + CHAR_NAME_GNOME);
		characterVars.pHideChar = pGameCharacter[nCharacters];
		pGameCharacter[nCharacters++]->pEnemy->runFlags&=(~RUNFLAGS_VISIBLE);
	}


	// CPW: the players
	if(characterIsThere(charInfo + CHAR_NAME_GINGER))
	{
		ppCharSamples = loadsndSetUpCharacter(CHAR_NAME_GINGER);
		pGameCharacter[nCharacters] = characterCreate(charInfo + CHAR_NAME_GINGER);
		pGameCharacter[nCharacters]->pEnemy->readData.ppSamples = ppCharSamples;

		if(currentCharacter != CHAR_NAME_GINGER)
			pGameCharacter[nCharacters]->pEnemy->runFlags&=(~RUNFLAGS_VISIBLE);
		else
			characterMakeMain(pGameCharacter[nCharacters]);
		
		pGameCharacter[nCharacters]->pAlt = characterGetCRC(CRC_WELLY);
		if(pGameCharacter[nCharacters]->pAlt)
			pGameCharacter[nCharacters]->pAlt->pAlt = pGameCharacter[nCharacters];

		if(inScenicHut || (inRatInterfaceRoom && currentChicken == CHAR_NAME_GINGER))
			pGameCharacter[nCharacters]->pEnemy->runFlags|=RUNFLAGS_VISIBLE;

		nCharacters++;
	}

	if(characterIsThere(charInfo + CHAR_NAME_ROCKY))
	{
		ppCharSamples = loadsndSetUpCharacter(CHAR_NAME_ROCKY);
		pGameCharacter[nCharacters] = characterCreate(charInfo + CHAR_NAME_ROCKY);
		pGameCharacter[nCharacters]->pEnemy->readData.ppSamples = ppCharSamples;

		if(currentCharacter != CHAR_NAME_ROCKY)
			pGameCharacter[nCharacters]->pEnemy->runFlags&=(~RUNFLAGS_VISIBLE);
		else
			characterMakeMain(pGameCharacter[nCharacters]);

		pGameCharacter[nCharacters]->pAlt = characterGetCRC(CRC_WELLY);
		if(pGameCharacter[nCharacters]->pAlt)
			pGameCharacter[nCharacters]->pAlt->pAlt = pGameCharacter[nCharacters];

		if(inScenicHut || (inRatInterfaceRoom && currentChicken == CHAR_NAME_ROCKY))
			pGameCharacter[nCharacters]->pEnemy->runFlags|=RUNFLAGS_VISIBLE;

		nCharacters++;
	}

	if(characterIsThere(charInfo + CHAR_NAME_NICK))
	{
		ppCharSamples = loadsndSetUpCharacter(CHAR_NAME_NICK);
		pGameCharacter[nCharacters] = characterCreate(charInfo + CHAR_NAME_NICK);
		pGameCharacter[nCharacters]->pEnemy->readData.ppSamples = ppCharSamples;

		if(currentCharacter!= CHAR_NAME_NICK)
			pGameCharacter[nCharacters]->pEnemy->runFlags|=RUNFLAGS_VISIBLE;
		else
			characterMakeMain(pGameCharacter[nCharacters]);

		pGameCharacter[nCharacters]->pAlt = characterGetCRC(CRC_GNOME);
		if(pGameCharacter[nCharacters]->pAlt)
			pGameCharacter[nCharacters]->pAlt->pAlt = pGameCharacter[nCharacters];

		nCharacters++;
	}

	if(characterIsThere(charInfo + CHAR_NAME_FETCHER))
	{
		ppCharSamples = loadsndSetUpCharacter(CHAR_NAME_FETCHER);
		pGameCharacter[nCharacters] = characterCreate(charInfo + CHAR_NAME_FETCHER);
		pGameCharacter[nCharacters]->pEnemy->readData.ppSamples = ppCharSamples;

		if(currentCharacter != CHAR_NAME_FETCHER)
			pGameCharacter[nCharacters]->pEnemy->runFlags|=RUNFLAGS_VISIBLE;
		else
			characterMakeMain(pGameCharacter[nCharacters]);

		pGameCharacter[nCharacters]->pAlt = characterGetCRC(CRC_GNOME);
		if(pGameCharacter[nCharacters]->pAlt)
			pGameCharacter[nCharacters]->pAlt->pAlt = pGameCharacter[nCharacters];

		nCharacters++;
	}

// Final cleanup
// (Most likely cause for this is using the cheatmode to select a level using a different character from what might be expected)
	if(!characterGetMainCharacter())
	{
		CHARACTER *pGinger =	characterGetCRC(CRC_GINGER);
		CHARACTER *pRocky =		characterGetCRC(CRC_ROCKY);
		CHARACTER *pNick =		characterGetCRC(CRC_NICK);
		CHARACTER *pFetcher =	characterGetCRC(CRC_FETCHER);

		DB(" *** Whoops. Something's gone wrong in the initial character selection\n");
		DB(" *** We were expecting to be %i (%s), who isn't here\n",currentCharacter,mapplay_charnames[currentCharacter]);


// CPW: 		pGameCharacter[0]->pEnemy->runFlags |= RUNFLAGS_VISIBLE;
// CPW: 		characterMakeMain(pGameCharacter[0]);

		if(pGinger)
		{
			currentChicken = currentCharacter = CHAR_NAME_GINGER;
			characterMakeMain(pGinger);
		}
		else
		{
			if(pRocky)
			{
				currentChicken = currentCharacter = CHAR_NAME_ROCKY;
				characterMakeMain(pRocky);
			}
			else
			{
				if(pNick)
				{
					currentRat = currentCharacter = CHAR_NAME_NICK;
					characterMakeMain(pNick);
				}
				else
				{
					if(pFetcher)
					{
						currentRat = currentCharacter = CHAR_NAME_FETCHER;
						characterMakeMain(pFetcher);
					}
					else
					{
						DB("ERRRRRRRRRRRRRROOOOOOOOOOOOOOORRRRRRRRRRRRRRR!!!\n");
						ASSERT(0);
					}
				}
			}
		}
	}


/*
	// CPW: testing the level
	switch(levinfo->testing_character)
	{
	case LEVINFO_TESTCHAR_GINGER:
		DB("LEVINFO_TESTCHAR_GINGER\n");

		if(characterIsThere(charInfo + CHAR_NAME_GINGER))
			pGameCharacter[nCharacters++] = characterCreate(charInfo + CHAR_NAME_GINGER);

		if(characterIsThere(charInfo + CHAR_NAME_ROCKY))
			pGameCharacter[nCharacters++] = characterCreate(charInfo + CHAR_NAME_ROCKY);

		if(characterIsThere(charInfo + CHAR_NAME_WELLY))
		{
			pGameCharacter[nCharacters] = characterCreate(charInfo + CHAR_NAME_WELLY);
			characterVars.pHideChar = pGameCharacter[nCharacters];
			pGameCharacter[nCharacters++]->pEnemy->runFlags&=(~RUNFLAGS_VISIBLE);
		}

		break;

	case LEVINFO_TESTCHAR_ROCKY:
		DB("LEVINFO_TESTCHAR_ROCKY\n");

		if(characterIsThere(charInfo + CHAR_NAME_ROCKY))
			pGameCharacter[nCharacters++] = characterCreate(charInfo + CHAR_NAME_ROCKY);

		break;

	case LEVINFO_TESTCHAR_NICK:		// CPW: where there's one rat, there's another
		DB("LEVINFO_TESTCHAR_NICK\n");

		if(characterIsThere(charInfo + CHAR_NAME_NICK))
			pGameCharacter[nCharacters++] = characterCreate(charInfo + CHAR_NAME_NICK);

		if(characterIsThere(charInfo + CHAR_NAME_FETCHER))
			pGameCharacter[nCharacters++] = characterCreate(charInfo + CHAR_NAME_FETCHER);

		if(characterIsThere(charInfo + CHAR_NAME_GNOME))
		{
			pGameCharacter[nCharacters] = characterCreate(charInfo + CHAR_NAME_GNOME);
			characterVars.pHideChar = pGameCharacter[nCharacters];
			pGameCharacter[nCharacters++]->pEnemy->runFlags&=(~RUNFLAGS_VISIBLE);
		}

		break;

	case LEVINFO_TESTCHAR_FETCHER:
		DB("LEVINFO_TESTCHAR_FETCHER\n");

		if(characterIsThere(charInfo + CHAR_NAME_FETCHER))
			pGameCharacter[nCharacters++] = characterCreate(charInfo + CHAR_NAME_FETCHER);

		if(characterIsThere(charInfo + CHAR_NAME_NICK))
			pGameCharacter[nCharacters++] = characterCreate(charInfo + CHAR_NAME_NICK);

		break;

	default:
		DB("Unknown main character in this level!\n");
		ASSERT(0);
	}
*/

	ASSERT(nCharacters);

	// CPW: characterMakeMain(pGameCharacter[0]);

/*
	if(nCharacters>1)
	{
		DB("Setting alternate character\n");
		characterGetMainCharacter()->pAlt = pGameCharacter[1];
		pGameCharacter[1]->pAlt = pGameCharacter[0];
	}
*/

	// CPW: set off a default (idle) animation
	for(ptr=characterList.head.next; ptr != &characterList.head; ptr = ptr->next)
	{
		actorFlushQueue(ptr->pEnemy->readData.pActor);
		actorAnimate(ptr->pEnemy->readData.pActor, GINGER_IDLEBASE, 1, 0, 256, 0, 1024);
	}
	
	mapInitialise();
	
	DB("Initialized models OK\n");
}

void testcharEnd()
{
	int i;
	for(i = 0; i < nCharacters; i++)
	{
		characterFree(pGameCharacter[i]);
/*		if(pGameCharacter[i]->pEnemy->readData.ppSamples)
		{
			FREE(pGameCharacter[i]->pEnemy->readData.ppSamples);
			pGameCharacter[i]->pEnemy->readData.ppSamples = NULL;
		}
*/
	}

	enemyDeleteAll();
}

void testcharCycle()
{
	characterProcess();
}

#define TESTCHAR_PANEL_X -240
#define TESTCHAR_PANEL_Y 32




/*
Map walkabout bits
*/





void mapplayReleaseMap()
{
	psiDestroy();
	platformShutdown();
	BFF_ReleaseFile(mapplay_level_bff);
	//textureUnloadBank(mapplay_level_textures);
	for(i=originalTexCount;i<DCKnumTextures;i++)
	{
		kmFreeTexture(DCKtextureList[i].surfacePtr);
	} 

	DCKnumTextures = originalTexCount;
	mapplay_level_bff = NULL;
}

void load_level(char *leafname)
{
	char name[40];
	int i;

	if(mapplay_level_bff)
	{
		mapplayReleaseMap();
	}

	//initCamera();

	psiInitialise(10);

	puzzleInitLevel();

#ifndef _WINDOWS
	if(streamed_bank)
	{
		streamed_bank = textureTidyBank(streamed_bank);
	}
#endif


	{
		sound_XA_EnsurePaused();
	}

// ==== Loading Files ====
//	overlayLoadingBegin(1);

	sprintf(name,"%s\\%s.SPT",leafname,leafname);
// CPW: 	mapplay_level_textures = textureLoadBank(name);
// CPW: 	textureDownloadBank(mapplay_level_textures);
// CPW: 	textureDestroyBank(mapplay_level_textures);

	DB("Loading sound bank %s ...\n", leafname);
	loadsndLoadBank(leafname);

//	memoryShow();
	mapplay_level_textures = textureLoadInOneGo(name);
	sprintf(name,"%s\\%s.BFF",leafname,leafname);

// CPW: 	memoryShow();
// CPW: 	DB("loading bff\n");

	mapplay_level_bff = BFF_LoadFile(name);
	ASSERT(mapplay_level_bff);


	afterRoomTexCount = DCKnumTextures;


	scenicPostBFF();
	curtainsInitialise();

//	overlayLoadingEnd();

// ==== Loading Done ====

	if (levinfo)
	{
		sound_XA_Resume(levinfo->tune_number);	// If tune_number's different from what we were previously playing, it'll start at the beginning, otherwise it'll keep going
	}


	mapplay_fma_world = (void *)BFF_FindObject(BFF_FMA_WORLD2_ID,utilStr2CRC("LNDMAP"));
	if(!mapplay_fma_world)
		mapplay_fma_world = (void *)BFF_FindObject(BFF_FMA_WORLD2_ID,0);	//utilStr2CRC("MAP"));
	if(!mapplay_fma_world)
		DB("WHERE'S THE WORLD!?\n");

	mapGetSizeOfWorld(mapplay_fma_world);

	mapplay_sky_world = (void *)BFF_FindObject(BFF_FMA_WORLD_ID,utilStr2CRC("SKY"));	// Should only be one world in the BFF
	if(!mapplay_sky_world)
	{
		DB("NO SKY WORLD!\n");
	}


	platformSetup();

	setupCollision(leafname);

	testcharStartModel();


	puzzleRestorePuzzleState();
	levinfoSetupBackground();
/*
	//actorColl.pPos=&testchar_actor->position;
	actorColl.pPos=&actorPos;
	actorColl.pVel=&actorVel;
	actorColl.bounce=4096;
	//actorColl.radius=(10*4096)<<3;	// need changing to cylinder collision at some point
	actorColl.radius=(40*4096);	// need changing to cylinder collision at some point
	actorColl.nBoxes = numCollBoxes;
*/



// set a directional light from the left

//	psiSetLight(0, 255, 255, 255,4096, 0, 0);
// CPW:	PSImodelctrl.lighting = DIRECTIONAL;
	PSImodelctrl.lighting = DIRECTIONONLY;

	psiSetAmbient(512,512,512);	// norm is 1024, max is 4096
//	psiSetAmbient(1024,1024,1024);	// norm is 1024, max is 4096

	psiAllocWorkspace();

	PSImodelctrl.depthShift = 3;
	PSImodelctrl.farclip = 32768;
	initCamera();
}


int mapplayIdentifyCharacter(CHARACTER *pC, int extract)
{
	int i;
	for(i = 0; i < 6; i++)
	{
		if(pC->pEnemy->readData.nameCRC == utilStr2CRC(mapplay_charnames[i]))
		{
			if(extract)
			{
				if(i == CHAR_NAME_WELLY)
				{
					i = currentChicken;
				}
				if(i == CHAR_NAME_GNOME)
				{
					i = currentRat;
				}
			}
			return i;
		}
	}
	return 0;
}
void mapplayPlaceCharAtMarker(CHARACTER *pC, int marker)
{
	POSMARK *mark;

	mark = levinfoFindMarker(marker);	
	if(mark)
	{
		pC->pEnemy->pos.vx = mark->x;
		pC->pEnemy->pos.vy = mark->y - 320;
		pC->pEnemy->pos.vz = mark->z;	

		pC->pEnemy->dir = mark->ya;
		pC->pEnemy->desiredDir = mark->ya;
		pC->movement.desiredAngle=mark->ya;
	}

}

void mapplayStart(char *leafname, int marker)
{
	EXLIGHT_TYPE *l;
	int i;
	VECTOR vecs[3] =
	{
		{-2021,-1000,-2090},
		{1070,-1000,1000},
		{4485,-1000,-2090}
	};

	dcSetInventory(leafname);

	// BP: If it's Fowler's hut, we want the Options to appear, not be able to run about in the hut
	if( (strcmp(leafname,"A1_FOWLER") == 0) ||  (strcmp(leafname,"A2_FOWLER")==0) || (strcmp(leafname,"A4_FOWLER")==0))
	{
		options_returnlevel = levinfo->level_number;		// So we know which level to return to after leaving Fowler's hut
		if(streamed_bank)
		{
			textureUnloadBank(streamed_bank);
			streamed_bank = NULL;
		}
		optionsStart(0);
	}
	else
	{

		// CPW: don't forget to destroy any old 
		enemyInitialiseList();
		
		characterInitialise();

		// initialse model and actor
		actorInitialise();

		platformInit();
		texanimInit();
		scenicInit();
		EXLIGHT_Initialise();
		sproutsInit();

		strcpy(sLevelName, leafname);
		DB("sLevelName = %s\n", sLevelName);
		level_start_marker = marker;

		load_level(leafname);

		EXLIGHT_PostBFF();		

		debrisInit();

		draw_routine = mapplayDraw;
		cycle_routine = mapplayCycle;

// CPW: test stuff
// CPW: 	ASSERT(textureFindInAllBanks("CROUCH"));

		mapplay_leave_level_flag = 0;

		if(!marker)
			marker = CHEAT_START_POSITIONMARKER;

		if(marker)
		{
			ENEMY *enemy;
			CHARACTER	*mainChar;

			mainChar=characterGetMainCharacter();

			mapplayPlaceCharAtMarker(mainChar,marker);
		}

// RFW - Initial positions of the "other" character
// Entry contidions...
// Respotted rats - stand next to each other at TWO POINTS NEAR DOOR
// Chicken enters (rat/chicken) room: Two rats stand next to each other at TWO POINTS NEAR DOOR
// Rat enters (rat/chicken) room: Other Chicken stands at "a point". Other rat stands at
// Rat enters two-rat room: Other rat stands next to it.
// Chicken enters (chicken-chicken) room: Other Chicken stands at "a point"
// Need posmarks to specify Rat 1 point, Rat 2 point, and Chicken point.

		{
			CHARACTER	*pC;
			int main_no;
			int this_no;

			main_no = mapplayIdentifyCharacter(characterVars.pMainChar,1);

			for(pC = characterList.head.next; pC != &characterList.head; pC = pC->next)
			{
				// Main character's just been dealt with
				// We're not interested in "turned off" rocky/gnome/etc. Only in ones that are visible at level-start
				if(pC!=characterVars.pMainChar && pC->pEnemy->runFlags & RUNFLAGS_VISIBLE)
				{
					this_no = mapplayIdentifyCharacter(pC,1);

					if(this_no == CHAR_NAME_GINGER || this_no == CHAR_NAME_ROCKY)
					{
						mapplayPlaceCharAtMarker(pC,OTHER_CHICKEN_POSITIONMARKER);
					}
					else
					{
						if(main_no == CHAR_NAME_GINGER || main_no == CHAR_NAME_ROCKY)
						{
							if(this_no == CHAR_NAME_NICK)
							{
								mapplayPlaceCharAtMarker(pC,NICK_POSITIONMARKER);
							}
							else
							{
								mapplayPlaceCharAtMarker(pC,FETCHER_POSITIONMARKER);
							}
						}
						else
						{
							// You're one rat, stood on the level's startpoint. Place the other one.
							// mapplayPlaceCharAtMarker(pC,OTHER_RAT_POSITIONMARKER);

							mapplayPlaceCharAtMarker(pC,marker + 1);
						}
					}
				}
			}
		}


		FMAsequenceHandeler(leafname);

		inventoryBeginLevel();
	}
}

void mapplayEnd()
{
	puzzleSavePuzzleState();

	debrisFree();
	scenicEnd();

	collideFree();

	testcharEnd();
	sproutsShutdown();

	EXLIGHT_Shutdown();	// needs to come after testcharEnd (coz Mr T discards his torch)

	if(mapplay_level_bff)
	{
		mapplayReleaseMap();

	}

#ifndef _WINDOWS
	sound_XA_PauseForLoading();
#endif

	// The PC version re-compiles the models and level meshes at level-load time. The pointers to these
	// new meshes are stored in a list, so we can free them when we unload the level...kinda like now.
#ifdef _WINDOWS
	activeTPL_Destroy();
#endif
}

void mapplayQuit()
{
	mapplayEnd();
	menuStart();
}


void mapplayDraw()
{
//	overlayPreviewOverlay();

	// CPW: 	testcharDraw();
	debrisDraw();

// 	characterDraw(pGameCharacter[0]);

	texanimDraw();
	platformDraw();
	scenicDrawAll();

	routeDraw();
	deathDraw();
	sproutsDraw();


	TIMER_START(TIMER_MAPREND);
	MapDraw_DrawFMA_World(mapplay_fma_world);
	TIMER_STOP(TIMER_MAPREND);

	enemyDrawAll();  	


#ifndef _WINDOWS	
	mapDrawSprite();
#endif

	TIMER_START(TIMER_SKYREND);
	{
		if(mapplay_sky_world)
		{
			MapDraw_Draw_SkyWorld(mapplay_sky_world);
		}
	}
	TIMER_STOP(TIMER_SKYREND);

	EXLIGHT_DebugDrawLights();
}

void mapplayCycle()
{
CHARACTER *cChar;
/*
	printf("Chicken Pos %d %d %d\n",
		pGameCharacter[0]->pEnemy->pos.vx,
		pGameCharacter[0]->pEnemy->pos.vy,
		pGameCharacter[0]->pEnemy->pos.vz);
*/

	// CPW: testcharCycle();


/*	if((loop_ticker&15)==0)
	{
		VECTOR zeroVec = {0,-128,0};
		TextureType *pT;

		pT = textureFindInAllBanks("SMOKE01");
		ASSERT(pT);

		debrisNewSmoke(&zeroVec, pT, 100, 200);
	}

	// CPW: if(particleCount()<16)
	{
		VECTOR origin;

		origin.vx = utilRandomInt(1024);
		origin.vy = -256-utilRandomInt(512);
		origin.vz = utilRandomInt(1024);
		
		pCrouchFountain = particleAdd(&origin, textureFindInAllBanks("SPARKLE1A"),
			80, 10, 15, effectsGetPrimary(utilRandomInt(6), 0, 63) & 0xffffff,
			PARTICLE_SPHERICAL|PARTICLE_GRAVITY);
	}

// CPW: 	DB("There are %d particle systems ...\n", particleCount());
*/

	if (!pauseFlag)
	{
		particleProcessAll();
		ventProccessAll(); // TD may need changing
	}

	particleDrawAll();



// CPW: 	commented out so that I can use the triangle for pause test TD
// CPW: 	if(padData.debounce[0] & PAD_TRIANGLE)
	if(padData.debounce[0] & controlMaps[controlMap].swapRats)
	{
		characterDoSwap(SWAP_RATTORAT);
	}

/*		CHARACTER *pThis = characterGetMainCharacter();

		CHARACTER *pNew = NULL;
		
		CHARACTER *pGinger =	characterGetCRC(CRC_GINGER);
		CHARACTER *pRocky =		characterGetCRC(CRC_ROCKY);
		CHARACTER *pNick =		characterGetCRC(CRC_NICK);
		CHARACTER *pFetcher =	characterGetCRC(CRC_FETCHER);

		ASSERT(pThis);

		DB("Attempting character swap\n");

		switch(pThis->pEnemy->readData.nameCRC)
		{
		case CRC_ROCKY:
			{
				if(pNick && (characterDistanceBetween(pNick, pThis) < 800) && (pNick->pEnemy->runFlags & RUNFLAGS_VISIBLE))
				{
					pNew = pNick;
					currentRat = currentCharacter = CHAR_NAME_NICK;
				}
				else
				{
					if(pFetcher && (characterDistanceBetween(pFetcher, pThis) < 800) && (pFetcher->pEnemy->runFlags & RUNFLAGS_VISIBLE))
					{
						pNew = pFetcher;
						currentRat = currentCharacter = CHAR_NAME_FETCHER;
					}
					else
					{
						if(pGinger && (characterDistanceBetween(pGinger, pThis) < 800) && (pGinger->pEnemy->runFlags & RUNFLAGS_VISIBLE))
						{
							pNew = pGinger;
							currentChicken = currentCharacter = CHAR_NAME_GINGER;
						}
					}
				}

				if(pNew)
					characterMakeMain(pNew);

				break;
			}
		case CRC_GINGER:
			{
				if(pNick && (characterDistanceBetween(pNick, pThis) < 800) && (pNick->pEnemy->runFlags & RUNFLAGS_VISIBLE))
				{
					pNew = pNick;
					currentRat = currentCharacter = CHAR_NAME_NICK;
				}
				else
				{
					if(pFetcher && (characterDistanceBetween(pFetcher, pThis) < 800) && (pFetcher->pEnemy->runFlags & RUNFLAGS_VISIBLE))
					{
						pNew = pFetcher;
						currentRat = currentCharacter = CHAR_NAME_FETCHER;
					}
					else
					{
						if(pRocky && (characterDistanceBetween(pRocky, pThis) < 800) && (pRocky->pEnemy->runFlags & RUNFLAGS_VISIBLE))
						{
							pNew = pRocky;
							currentChicken = currentCharacter = CHAR_NAME_ROCKY;
						}
					}
				}

				if(pNew)
					characterMakeMain(pNew);

				break;
			}
		case CRC_NICK:
			if(pGinger && (characterDistanceBetween(pGinger, pThis) < 800) && (pGinger->pEnemy->runFlags & RUNFLAGS_VISIBLE))
			{
				characterMakeMainCRC(CRC_GINGER);
				currentChicken = currentCharacter = CHAR_NAME_GINGER;
				break;
			}

			if(pRocky && (characterDistanceBetween(pRocky, pThis) < 800) && (pRocky->pEnemy->runFlags & RUNFLAGS_VISIBLE))
			{
				characterMakeMainCRC(CRC_ROCKY);
				currentChicken = currentCharacter = CHAR_NAME_ROCKY;
				break;
			}

			characterMakeMainCRC(CRC_FETCHER);
			currentRat = currentCharacter = CHAR_NAME_FETCHER;
			break;

		case CRC_FETCHER:
			if(pGinger && (characterDistanceBetween(pGinger, pThis) < 800) && (pGinger->pEnemy->runFlags & RUNFLAGS_VISIBLE))
			{
				characterMakeMainCRC(CRC_GINGER);
				currentChicken = currentCharacter = CHAR_NAME_GINGER;
				break;
			}

			if(pRocky && (characterDistanceBetween(pRocky, pThis) < 800) && (pRocky->pEnemy->runFlags & RUNFLAGS_VISIBLE))
			{
				characterMakeMainCRC(CRC_ROCKY);
				currentChicken = currentCharacter = CHAR_NAME_ROCKY;
				break;
			}

			characterMakeMainCRC(CRC_NICK);
			currentRat = currentCharacter = CHAR_NAME_NICK;
			break;
		}

		if(currentCharacter == CHAR_NAME_GINGER || currentCharacter == CHAR_NAME_ROCKY)
		{
			if(inventoryCheckForItem(GNOME)!=INV_INVALIDOBJECT)
			{
				inventoryRemoveItem(GNOME);
				scenicInventoryNotification(GNOME,SCENIC_INVENTORY_RESPOT);
			}
		}
	}
*/

	if(padData.debounce[4] & PAD_TRIANGLE)
	{
		if (pauseFlag) pauseFlag=FALSE;
		else pauseFlag=TRUE;
	}


/*	if(padData.debounce[4] & PAD_CIRCLE)
	{
		if(demoStatus == DEMO_NO)
		{
			DB("Going to start recording demo ...\n");
			demoStatus = DEMO_RECORDING;
			demoStartRecording();
		}
		else
		{
			if(demoStatus == DEMO_RECORDING)
			{
				DB("Stopping demo\n");
				demoStatus = DEMO_NO;
				demoStopRecording();
			}
		}
	}

	if(padData.debounce[4] & PAD_SQUARE)
	{
		if(demoStatus == DEMO_NO)
		{
			DB("Starting to play demo ...\n");
			demoStartPlaying();
		}
// CPW: 		else
// CPW: 		{
// CPW: 			if(demoStatus == DEMO_PLAYING)
// CPW: 			{
// CPW: 				DB("Stopping playback\n");
// CPW: 				demoStatus = DEMO_NO;
// CPW: 				demoStopPlaying();
// CPW: 			}
// CPW: 		}
// CPW: 
	}
*/

	// JS : Had to change this to store the main char in a local var, as in some
	// cases (such as the sub-game mesh view stuff it was causing a crash.
	if ((cChar = characterGetMainCharacter()) != NULL)
	{
		switch(cChar->pEnemy->readData.nameCRC)
		{
		case CRC_NICK:
		case CRC_FETCHER:
			if(currentInv->selected_item==GNOME)
				characterSetHideStatus(1);
			break;

		case CRC_GNOME:
			if(currentInv->selected_item!=GNOME)
				characterSetHideStatus(0);
			break;
			
		case CRC_ROCKY:
		case CRC_GINGER:
			if(currentInv->selected_item==BOOTS)
				characterSetHideStatus(1);
			break;

		case CRC_WELLY:
			if(currentInv->selected_item!=BOOTS)
				characterSetHideStatus(0);
		}
	}


	if (!pauseFlag)
	{
		characterProcess();


// TBD - Move to the character handler

		if(characterVars.pMainChar)
		{
			VECTOR *interesting_pos;
			ENEMY *pE;
			int range;

			pE = characterVars.pMainChar->pEnemy;
			// "100 * 8 * 4" is the range at which scenics become interesting to Ginger.
			// The null is for a returned "new range" return value that we can use to see if anything else
			// is closer still should we want to look at non-scenic interesting things.

			range = 100 * 8 * 4;
			interesting_pos = NULL;

			scenicFindInteresting(&pE->pos, pE->dir, &interesting_pos, &range);
			posmarkFindInteresting(&pE->pos, pE->dir, &interesting_pos, &range);
			enemyFindInteresting(&pE->pos, pE->dir, &interesting_pos, &range);

//	tbd - look for platforms (or posmarkers?) marked as "interesting" (Though at the moment they can't be)

			if(interesting_pos)
			{
				enemyLookAtPos3D(pE, interesting_pos);
			}
			else
			{
				enemyLookAtPos3D(pE, NULL);
			}
		}



		// CPW: enemy stuff
	 	enemyProcessAll();
		deathPoll();
		debrisCycle();
		EXLIGHT_Update();
		platformCycle();
		texanimCycle();
		scenicCycle();
		sproutsCycle();
		//helpUpdate(HELPTEXT_CHICKENS);
	}
	else
	{
		ENEMY *pE;
		SCENIC *pS;
		VECTOR	debug[4];
		VECTOR	campos,camlook;

		char	message[255];

		int	frame;
		for(pE=enemyList.head.next; pE != &enemyList.head; pE = pE->next)
		{
			frame=pE->readData.pActor->animation.animData[0].actualFrame;

			psiSetKeyFrames(pE->readData.pActor->psiData.object,frame);
		}

		for(pS = scenicList; pS; pS=pS->next)	// I'm going to assume that all scenics are setup correctly
		{
			frame=pS->pActor->animation.animData[0].actualFrame;
			psiSetKeyFrames(pS->pActor->psiData.object,frame);
		}

		campos.vx=mycamera.vpx;
		campos.vy=mycamera.vpy;
		campos.vz=mycamera.vpz;

		camlook.vx=mycamera.vrx;
		camlook.vy=mycamera.vry;
		camlook.vz=mycamera.vrz;

		if (padData.digital[0]&PAD_L1) campos.vy-=32;
		if (padData.digital[0]&PAD_L2) campos.vy+=32;

		if (padData.digital[0]&PAD_R1) camlook.vy-=32;
		if (padData.digital[0]&PAD_R2) camlook.vy+=32;

		if (padData.digital[0]&PAD_CROSS)
		{
			camlook.vx+=padData.analogXS[0] >> 6;
			camlook.vz+=padData.analogYS[0] >> 6;
		}
		else 
		{
			campos.vx+=padData.analogXS[0] >> 6;
			campos.vz+=padData.analogYS[0] >> 6;
		}

		camlook.vx+=padData.analogX2S[0] >> 6;
		camlook.vz+=padData.analogY2S[0] >> 6;

		debug[0].vx=camlook.vx-10;
		debug[0].vy=camlook.vy;
		debug[0].vz=camlook.vz+10;

		debug[1].vx=camlook.vx+10;
		debug[1].vy=camlook.vy;
		debug[1].vz=camlook.vz+10;

		debug[2].vx=camlook.vx-10;
		debug[2].vy=camlook.vy;
		debug[2].vz=camlook.vz-10;

		debug[3].vx=camlook.vx+10;
		debug[3].vy=camlook.vy;
		debug[3].vz=camlook.vz-10;

		mycamera.vpx=campos.vx;
		mycamera.vpy=campos.vy;
		mycamera.vpz=campos.vz;

		mycamera.vrx=camlook.vx;
		mycamera.vry=camlook.vy;
		mycamera.vrz=camlook.vz;
		mapCamSetRefView2L(&mycamera);

		if (padData.digital[0]&PAD_SQUARE)
		{
			overlayShowDebugPolygon(&debug[0], 4, 0xff0000, 4);
			sprintf(message,"campos  X %ld Y %ld Z %ld",mycamera.vpx/8,mycamera.vpy/8,mycamera.vpz/8);
			fontPrint(font, -220,-100, message, 128,128,0);
			sprintf(message,"camlook X %ld Y %ld Z %ld",mycamera.vrx/8,mycamera.vry/8,mycamera.vrz/8);
			fontPrint(font, -220,-85, message, 128,128,0);
		}
		else if (padData.digital[0]&PAD_CIRCLE)
		{
			//GsSetProjection(startfov);
			GsSetProjection(950);
			overlayShowDebugPolygon(&debug[0], 4, 0xff0000, 4);
			sprintf(message,"campos  X %ld Y %ld Z %ld",mycamera.vpx/8,mycamera.vpy/8,mycamera.vpz/8);
			fontPrint(font, -220,-100, message, 128,128,0);
			sprintf(message,"camlook X %ld Y %ld Z %ld",mycamera.vrx/8,mycamera.vry/8,mycamera.vrz/8);
			fontPrint(font, -220,-85, message, 128,128,0);
		}
		else GsSetProjection( levinfo->initial_fov );
	}
	helpUpdate(HELPTEXT_CHICKENS);


	if(pReadPad->digital[0] & PAD_TRIANGLE)
	{
		mapplay_camera_range += pReadPad->analogY2S[0] >> (8 - MAP_SCALE_ADJUST * 3/10);
	}
	else
	{
		mapplay_camera_ya += pReadPad->analogX2S[0] >> 8;
		mapplay_camera_xa += pReadPad->analogY2S[0] >> 8;
	}
	if(mapplay_camera_range < 100) mapplay_camera_range = 100;
	if(mapplay_camera_range > 5000 * MAP_SCALE_ADJUST) mapplay_camera_range = 5000 * MAP_SCALE_ADJUST;
	if(mapplay_camera_xa > 1000) mapplay_camera_xa = 1000;
	if(mapplay_camera_xa < -1000) mapplay_camera_xa = -1000;

	puzzleCheckPuzzles();

	if (!pauseFlag)
		upDateCamera();


	if(PuzzleVars.variables[0])
		printf("in box = %d\n",PuzzleVars.variables[0]);


	{
	static int ticker = 0;
	ticker++;
#define SFX_DEFAULT_VOLUME	0x3fff
#define SFX_DEFAULT_PITCH	1024
		if(!(ticker & 31))
		{
//			sfxPlaySound(utilRandomInt(50), SFX_DEFAULT_VOLUME, SFX_DEFAULT_PITCH);
		}
	}

	// CPW: This should be the very last thing in this function

	if(SwimCheatCycle())
	{
		if(levinfo->level_number == LEVNUMBER_A2_COGCHASE && gameCalledFrom != GAMECALLEDFROM_DEBUG)
		{
			// Actually, this gets picked up on, and it goes to the splash screen instead
			strcpy(&mapplay_next_level[0],"A4_HUT17");
			mapplay_next_marker = 0;
			mapplay_leave_level_flag = 1;
		}
		else
		{
			mapplayQuit();
		}
	}

/*
	if(mapplay_leave_level_flag)
	{
		mapplayEnd();
		mapplayStart(mapplay_next_level,mapplay_next_marker);
	}
*/

	if(mapplay_leave_level_flag)
	{
		int i;
		mapplayEnd();




		for(i = 0; i < N_LEVEL_SWITCHES; i++)
		{
			if(!strcmp(level_switches[i].name,mapplay_next_level))
			{
				if(streamed_bank)	// The boss chases are short of vram, so we need to ditch the inventory sprites for the duration...
				{
					textureUnloadBank(streamed_bank);
					streamed_bank = NULL;
				}

#ifndef _WINDOWS
				if(level_switches[i].ovl_number != -1)
				{
					main_EnsureOverlay(level_switches[i].ovl_number);
				}
#endif

				level_switches[i].start();
				break;
			}
		}

/*
// A bit of a messy solution, stricmping this, but anyway...
		if(!strcmp("A1_DOGCHASE",mapplay_next_level))
		{
			if(streamed_bank)	// The boss chases are short of vram, so we need to ditch the inventory sprites for the duration...
			{
				textureUnloadBank(streamed_bank);
				streamed_bank = NULL;
			}

			dogchaseStart();
		}
		else
*/
		if(i == N_LEVEL_SWITCHES)
		{

			// Special case for a successful end to the cogchase
			if(levinfo->level_number == LEVNUMBER_A2_COGCHASE && !strcmp(mapplay_next_level,"A4_HUT17"))
			{
				if(streamed_bank)	// The boss chases are short of vram, so we need to ditch the inventory sprites for the duration...
				{
					textureUnloadBank(streamed_bank);
					streamed_bank = NULL;
				}
				levflowCogchaseSuccess();
			}
			else
			{
				mapplayStart(mapplay_next_level,mapplay_next_marker);
			}
		}
	}

#ifdef DEBUG
	{
		static int testpos = 0;
		static char *testnames[] =
		{
			"Mannequin",
			"Catapult",
			"Fireworks",
			"Seesaw",
			"CogChase",
			"Construct",
			"Engine",
			"Wings",
			"Crate",
			"All Vid",
			"All Levs",
		};
		VOID_SUBROUTINE testrous[] =
		{
			levflowCheatToManniquin,
			levflowCheatToCatapult,
			levflowCheatToFireworks,
			levflowCheatToSeesaw,
			levflowCheatToCogChase,
			levflowCheatToConstruct,
			levflowCheatToEngine,
			levflowCheatToWings,
			levflowCheatToCrate,
			levflowCheatAllVids,
			levflowCheatAllLevs,
		};

		static int n_tests = sizeof(testnames)/4;

		if(padData.digital[0] & PAD_R3)
		{
			if(padData.debounce[0] & PAD_UP)
			{
				testpos = (testpos + n_tests-1) % n_tests;
				overlayDebugMessage(testnames[testpos], 20);
			}
			if(padData.debounce[0] & PAD_DOWN)
			{
				testpos = (testpos + 1) % n_tests;
				overlayDebugMessage(testnames[testpos], 20);
			}

			if(padData.debounce[0] & PAD_L1)
			{
	// Trigger completion
				mapplayEnd();
				testrous[testpos]();
			}
		}
	}
#endif
}
