/*	Control.c

	Control of characters that aren't enemies
*/

#include "global.h"
#include "main.h"
#include "collide.h"
#include "inventory.h"
#include "enemies.h"
#include "camera.h"
#include "platform.h"
#include "maths.h"
#include "scenics.h"
#include "charactr.h"
#include "loadsnd.h"
#include "map_play.h"
#include "sound.h"
#include "demo.h"
#include "inventory.h"
#include "puzzles.h"
#include "deth.h"
#include "curtains.h"
#include "fma.h"
#include "map.h"
#include "bff_load.h"
#include "gametext.h"
#include "sprouts.h"
#include "card.h"
#include "pause.h"
#include "help.h"
#include "dualshock.h"
#include "debris.h"
#include "startup.h"
#include "gametext.h"

#ifdef DREAMCAST
#undef NULL
#define NULL 0
#endif

int jumpVel = 58;
int antiGrav = 18;
extern int pause_levelplaying;
int forceYAngleInDesperation = -1;


CONTROLMAP controlMaps[NUM_CONTROL_MAPS] = {
	{
		PAD_CROSS,		// CPW: jump
		PAD_CIRCLE,		// CPW: throw
		PAD_CIRCLE,		// CPW: swap rats
		PAD_SQUARE,		// CPW: action
		PAD_TRIANGLE,	// CPW: inventory
		PAD_L2,			// CPW: step through inventory
		PAD_START,		// CPW: pause
		PAD_SELECT,		// CPW: help
		PAD_SQUARE,		// CPW: hide
		PAD_R2,			// CPW: creep (tiptoe)
		PAD_L1,			// CPW: camera left
		PAD_R1,			// CPW: camera right
	},

	{
		PAD_SQUARE,		// CPW: jump
		PAD_CIRCLE,		// CPW: throw
		PAD_CIRCLE,		// CPW: swap rats
		PAD_CROSS,		// CPW: action
		PAD_TRIANGLE,	// CPW: inventory
		PAD_L2,			// CPW: step through inventory
		PAD_START,		// CPW: pause
		PAD_SELECT,		// CPW: help
		PAD_CROSS,		// CPW: hide
		PAD_R2,			// CPW: creep (tiptoe)
		PAD_L1,			// CPW: camera left
		PAD_R1,			// CPW: camera right
	},

	{
		PAD_CIRCLE,		// CPW: jump
		PAD_SQUARE,		// CPW: throw
		PAD_SQUARE,		// CPW: swap rats
		PAD_CROSS,		// CPW: action
		PAD_TRIANGLE,	// CPW: inventory
		PAD_L2,			// CPW: step through inventory
		PAD_START,		// CPW: pause
		PAD_SELECT,		// CPW: help
		PAD_CROSS,		// CPW: hide
		PAD_R2,			// CPW: creep (tiptoe)
		PAD_L1,			// CPW: camera left
		PAD_R1,			// CPW: camera right
	},

	{
		PAD_CROSS,		// CPW: jump
		PAD_SQUARE,		// CPW: throw
		PAD_SQUARE,		// CPW: swap rats
		PAD_CIRCLE,		// CPW: action
		PAD_TRIANGLE,	// CPW: inventory
		PAD_L2,			// CPW: step through inventory
		PAD_START,		// CPW: pause
		PAD_SELECT,		// CPW: help
		PAD_CIRCLE,		// CPW: hide
		PAD_R2,			// CPW: creep (tiptoe)
		PAD_L1,			// CPW: camera left
		PAD_R1,			// CPW: camera right
	}
};

int controlMap = 0;
// CPW: int beenRatsYet = 0;

#define ANIMATION_WALK		AM_WALK
#define ANIMATION_STAND		AM_STAND	
#define ANIMATION_RUN		AM_RUN

#define GINGER_FORCE_CREEPSPEED	(1000)
// CPW: #define GINGER_WALKSPEED	(1500)
#define GINGER_WALKSPEED	(2000)
#define GINGER_RUNSPEED		(2500)

#ifdef SETSTATE
	#undef SETSTATE
#endif

#define SETSTATE(S)	\
{ \
	copyOfOldState = S; \
	\
	if((copyOfOldState!=pC->pEnemy->state) && (pC->pVTable[pC->pEnemy->state][copyOfOldState])) \
	{ \
		pC->pEnemy->oldState=pC->pEnemy->state; \
		pC->pEnemy->state=copyOfOldState; \
		pC->pEnemy->stateCtr=-1; \
	} \
	else \
	{ \
	} \
}
//		DB("Couldn't set state (%d -> %d not valid)\n", pC->pEnemy->state, S); \
/**/

static short footStepLUT[2][4][2] = {
	{{1894, 3942},	{2503, 455},	{625, 2708},	{625, 2708}},
	{{293, 2341},	{2341, 293},	{2731, 546},	{2731, 546}}
};


static char chickenVTable[STATE_NUMBER][STATE_NUMBER] = {
//					null	sleep	wander	persuit	getup	sitdown	wait	bywall	crawl	jump	pushshl	pickup	land	hiding	pushbut	cling
/* null */		{	1,		1,		1,		1,		1,		1,		1,		1,		1,		1,		1,		1,		1,		1,		1,		1},
/* sleep */		{	1,		1,		0,		0,		1,		0,		1,		0,		0,		0,		0,		1,		0,		0,		0,		0},
/* wander */	{	1,		0,		1,		1,		0,		1,		1,		1,		1,		1,		1,		1,		0,		0,		1,		0},
/* persuit */	{	1,		0,		1,		1,		0,		1,		1,		1,		1,		1,		1,		1,		0,		0,		0,		0},
/* getup */		{	1,		0,		1,		1,		1,		1,		1,		1,		0,		1,		1,		0,		0,		0,		0,		0},
/* sitdown */	{	1,		1,		0,		0,		1,		1,		1,		0,		1,		0,		0,		0,		0,		0,		0,		0},
/* wait */		{	1,		1,		1,		1,		1,		1,		1,		1,		1,		1,		1,		1,		1,		0,		0,		0},
/* bywall */	{	1,		0,		1,		1,		0,		1,		1,		1,		0,		1,		1,		1,		0,		0,		0,		0},
/* crawl */		{	1,		1,		0,		0,		1,		0,		1,		0,		1,		0,		0,		0,		0,		0,		0,		0},
/* jump */		{	1,		0,		1,		1,		0,		1,		1,		0,		0,		1,		1,		0,		1,		0,		0,		0},
/* pushshldr */	{	1,		0,		1,		1,		0,		1,		1,		1,		0,		1,		1,		1,		0,		0,		0,		0},
/* pickup */	{	1,		0,		1,		1,		0,		1,		1,		1,		0,		1,		1,		1,		0,		0,		0,		0},
/* land */		{	1,		0,		1,		1,		0,		0,		1,		0,		0,		1,		0,		0,		1,		0,		0,		0},
/* hiding */	{	1,		0,		1,		0,		0,		0,		0,		0,		0,		0,		0,		0,		0,		0,		0,		0},
/* pushbutton */{	1,		0,		1,		0,		0,		0,		1,		0,		0,		0,		0,		0,		0,		0,		1,		0},
/* clinging */	{	1,		0,		0,		0,		0,		0,		0,		0,		0,		0,		0,		0,		0,		0,		1,		1}
};

static char ratVTable[STATE_NUMBER][STATE_NUMBER] = {
//					null	sleep	wander	persuit	getup	sitdown	wait	bywall	crawl	jump	pushshl	pickup	land	hiding	pushbut	cling
/* null */		{	1,		1,		1,		1,		1,		1,		1,		1,		1,		1,		1,		1,		1,		1,		1,		0},
/* sleep */		{	1,		1,		0,		0,		1,		0,		1,		0,		0,		0,		0,		1,		0,		0,		0,		0},
/* wander */	{	1,		0,		1,		1,		0,		1,		1,		1,		1,		1,		1,		1,		0,		0,		1,		1},
/* persuit */	{	1,		0,		1,		1,		0,		1,		1,		1,		1,		1,		1,		1,		0,		0,		0,		0},
/* getup */		{	1,		0,		1,		1,		1,		1,		1,		1,		0,		1,		1,		0,		0,		0,		0,		0},
/* sitdown */	{	1,		1,		0,		0,		1,		1,		1,		0,		1,		0,		0,		0,		0,		0,		0,		0},
/* wait */		{	1,		1,		1,		1,		1,		1,		1,		1,		1,		1,		1,		1,		1,		0,		0,		0},
/* bywall */	{	1,		0,		1,		1,		0,		1,		1,		1,		0,		1,		0,		1,		0,		0,		0,		0},
/* crawl */		{	1,		1,		0,		0,		1,		0,		1,		0,		1,		0,		0,		0,		0,		0,		0,		0},
/* jump */		{	1,		0,		1,		1,		0,		1,		1,		1,		0,		1,		1,		0,		1,		0,		0,		0},
/* pushshldr */	{	1,		0,		1,		1,		0,		1,		1,		0,		0,		1,		1,		1,		0,		0,		0,		0},
/* pickup */	{	1,		0,		1,		1,		0,		1,		1,		1,		0,		1,		1,		1,		0,		0,		0,		0},
/* land */		{	1,		0,		1,		1,		0,		0,		1,		0,		0,		1,		0,		0,		1,		0,		0,		0},
/* hiding */	{	1,		0,		1,		0,		0,		0,		0,		0,		0,		0,		0,		0,		0,		1,		0,		0},
/* pushbutton */{	1,		0,		1,		0,		0,		0,		1,		0,		0,		0,		0,		0,		0,		0,		1,		0},
/* clinging */	{	1,		0,		1,		0,		0,		0,		0,		0,		0,		1,		0,		0,		0,		0,		0,		1}
};

static char hidingVTable[STATE_NUMBER][STATE_NUMBER] = {
//					null	sleep	wander	persuit	getup	sitdown	wait	bywall	crawl	jump	pushshl	pickup	land	hiding	pushbut	cling
/* null */		{	1,		0,		1,		0,		0,		0,		0,		0,		0,		0,		0,		0,		0,		1,		1,		0},
/* sleep */		{	0,		1,		0,		0,		0,		0,		0,		0,		0,		0,		0,		1,		0,		0,		0,		0},
/* wander */	{	1,		0,		1,		0,		0,		0,		0,		0,		0,		0,		0,		0,		0,		1,		1,		0},
/* persuit */	{	0,		0,		0,		1,		0,		0,		0,		0,		0,		0,		0,		0,		0,		0,		0,		0},
/* getup */		{	0,		0,		0,		0,		1,		0,		0,		0,		0,		0,		0,		0,		0,		0,		0,		0},
/* sitdown */	{	0,		0,		0,		0,		0,		1,		0,		0,		0,		0,		0,		0,		0,		0,		0,		0},
/* wait */		{	0,		0,		0,		0,		0,		0,		1,		0,		0,		0,		0,		0,		0,		0,		0,		0},
/* bywall */	{	0,		0,		0,		0,		0,		0,		0,		1,		0,		0,		0,		0,		0,		0,		0,		0},
/* crawl */		{	0,		0,		0,		0,		0,		0,		0,		0,		1,		0,		0,		0,		0,		0,		0,		0},
/* jump */		{	0,		0,		0,		0,		0,		0,		0,		0,		0,		1,		0,		0,		0,		0,		0,		0},
/* pushshldr */	{	0,		0,		0,		0,		0,		0,		0,		0,		0,		0,		1,		0,		0,		0,		0,		0},
/* pickup */	{	0,		0,		0,		0,		0,		0,		0,		0,		0,		0,		0,		1,		0,		0,		0,		0},
/* land */		{	0,		0,		0,		0,		0,		0,		0,		0,		0,		0,		0,		0,		1,		0,		0,		0},
/* hiding */	{	1,		0,		1,		0,		0,		0,		0,		0,		0,		0,		0,		0,		0,		0,		0,		0},
/* pushbutton */{	1,		0,		1,		0,		0,		0,		1,		0,		0,		0,		0,		0,		0,		0,		0,		0},
/* clinging */	{	1,		0,		0,		0,		0,		0,		0,		0,		0,		0,		0,		0,		0,		0,		0,		1}
};



CHARACTERINFO charInfo[] = {
	{"GINGER",	chickenVTable,	characterGingerAndRockyHandler,	(2*4096)<<3, 0, STATE_WANDERING},
	{"WELLY",	hidingVTable,	characterGingerAndRockyHandler,	(2*4096)<<3, 0, STATE_WANDERING},
	{"ROCKY",	chickenVTable,	characterGingerAndRockyHandler,	(2*4096)<<3, 0, STATE_WANDERING},
	{"NICK",	ratVTable,		characterGingerAndRockyHandler,	(2*4096)<<3, 0, STATE_WANDERING},
	{"FETCHER",	ratVTable,		characterGingerAndRockyHandler,	(2*4096)<<3, 0, STATE_WANDERING},
#ifdef DREAMCAST
	{"C_GNOME",	hidingVTable,	characterGingerAndRockyHandler,	(2*4096)<<3,-100, STATE_WANDERING}
#else
	{"C_GNOME",	hidingVTable,	characterGingerAndRockyHandler,	(2*4096)<<3, 0, STATE_WANDERING}
#endif
};

extern int inv_messagetimer;			// A timer for printing 'Too heavy / Not enough room' etc messages while trying to pick things up 
extern char *inv_currentmessage;
// CPW: camera.c needs this
int testchar_speed = 0;
int characterDisableController = CONTROLLER_ENABLED;

CHARACTERLIST	characterList;
MOVEDATA moveData;
/*
struct
{
	CHARACTER *pMainChar;
}characterVars;
*/
CHARACTERVARSTR characterVars;



int characterIsThere(CHARACTERINFO *pCI)
{
	return (int)(BFF_IsModelLoaded(pCI->sName));
}

void characterInitialise(void)
{
	DB(" *** Initialising characterList ***\n");

	FMEMZERO(&characterVars,sizeof(CHARACTERVARSTR));

	characterList.head.next=characterList.head.prev=&characterList.head;
	characterList.numEntries=0;
}

// CPW: I don't think there's enough characters going at once to be worth using a linked list
CHARACTER *characterCreate(CHARACTERINFO *pCI)
{
	ENEMYINFO *pEnemyInfo;
	CHARACTER *ptr;
	CHARACTER *pC = (CHARACTER *)MALLOC(sizeof(CHARACTER));

	pEnemyInfo = &pC->enemyInfo;

	VERIFY(pC);
	VERIFY(pCI);

	memset(pC, 0, sizeof(CHARACTER));

	ptr=characterList.head.next;
	VERIFY(ptr);

	pC->next=ptr;
	pC->prev=ptr->prev;
	ptr->prev->next=pC;
	ptr->prev=pC;

	characterList.numEntries++;

	memcpy(pEnemyInfo, enemyInfo + ENEMY_UNDEFINED, sizeof(ENEMYINFO));

	pEnemyInfo->sName = pCI->sName;
	pEnemyInfo->flags = ENEMYFLAGS_NOTANENEMY;
	pEnemyInfo->ppSpecial = NULL;
	pEnemyInfo->pHandler = NULL;
	pEnemyInfo->defaultState = pCI->initialState;
	pEnemyInfo->collRadius = pCI->collRadius;
	pEnemyInfo->collYoffset = pCI->collYoffset;

	pC->pVTable = pCI->pVTable;
	pC->pHandler = pCI->pHandler;

	pC->pEnemy = enemyAdd(pEnemyInfo);
	VERIFY(pC->pEnemy);

	return pC;
}

void characterFree(CHARACTER *pC)
{
	VERIFY(pC);

	enemyDelete(pC->pEnemy);
	FREE(pC);
}

void characterVerify(void)
{
	CHARACTER *ptr;
	int c=0;

	DB("\nVerifying character list ...\n");
	
	for(ptr=characterList.head.next; ptr != &characterList.head; ptr = ptr->next)
	{
		VERIFY(ptr);
		c++;
	}

	if(c==characterList.numEntries)
	{
		DB("Verified OK (Num entries = %d)\n", c);
	}
	else
	{
		DB("Verify failed. c = %d, characterList.numEntries = %d\n", c, characterList.numEntries);
		ASSERT(0);
	}
}

void characterChange(CHARACTER *pChange, char *sName)
{
	CHARACTER *ptr;
	unsigned long crc = utilStr2CRC(sName);

	camTargetChange();

	//DB("now in char change\n");

	if(pChange==NULL)
		pChange = characterVars.pMainChar;

	if(pChange->pEnemy->readData.nameCRC == crc)
		return;

	for(ptr=characterList.head.next; ptr != &characterList.head; ptr = ptr->next)
	{
		if(ptr->pEnemy->readData.nameCRC == crc)
			break;
	}

	if(ptr->pEnemy->readData.nameCRC != crc)
	{
		DB("I can't find character '%s', you feelthy peeg!\n", sName);
		ASSERT(0);
	}

	ptr->pEnemy->runFlags|=RUNFLAGS_VISIBLE;
	pChange->pEnemy->runFlags&=~RUNFLAGS_VISIBLE;

	COPYVECTOR(&ptr->pEnemy->pos, &pChange->pEnemy->pos);
	characterMakeMain(ptr);

	characterVerify();
}

void characterSetHideStatus(int s)
{
	int dir;

	if(s)
	{
// CPW: 		if(characterVars.pMainChar->movement.hidden)
// CPW: 			return;

/*		unsigned long nameCRC = characterVars.pMainChar->pEnemy->readData.nameCRC;

		if(((nameCRC == CRC_NICK) || (nameCRC == CRC_FETCHER)) && (characterVars.pMainChar->pEnemy->state == STATE_CLINGING))
		{
			DB("Can't change to gnome if on rope\n");
			return;
		}
*/
		DB("Setting main character to hidden state\n");
		characterVars.pHideChar->pEnemy->runFlags|=RUNFLAGS_VISIBLE;
		characterVars.pMainChar->pEnemy->runFlags&=~RUNFLAGS_VISIBLE;

		if(forceYAngleInDesperation>-1)
			dir = forceYAngleInDesperation;
		else
			dir = characterVars.pMainChar->pEnemy->readData.pActor->psiData.object->rotate.vy;

		characterVars.pHideChar->pEnemy->dir = dir;
		characterVars.pHideChar->pEnemy->desiredDir = dir;
		characterVars.pHideChar->movement.desiredAngle = dir;
		characterVars.pHideChar->pEnemy->readData.pActor->psiData.object->rotate.vy = dir;

// CPW: 		COPYVECTOR(&characterVars.pHideChar->pEnemy->pos, &characterVars.pMainChar->pEnemy->pos);
		enemyPlaceAt(
			characterVars.pHideChar->pEnemy,
			characterVars.pMainChar->pEnemy->pos.vx,
			characterVars.pMainChar->pEnemy->pos.vy,
			characterVars.pMainChar->pEnemy->pos.vz
			);

		characterVars.pMainChar = characterVars.pHideChar;
	}
	else
	{
// CPW: 		if(!characterVars.pMainChar->movement.hidden)
// CPW: 			return;

		DB("Setting main character to un-hidden state\n");
		characterVars.pNormChar->pEnemy->runFlags|=RUNFLAGS_VISIBLE;
		characterVars.pMainChar->pEnemy->runFlags&=~RUNFLAGS_VISIBLE;

		dir = characterVars.pHideChar->pEnemy->readData.pActor->psiData.object->rotate.vy;

		characterVars.pNormChar->pEnemy->dir = dir;
		characterVars.pNormChar->pEnemy->desiredDir = dir;
		characterVars.pNormChar->movement.desiredAngle = dir;
		characterVars.pNormChar->pEnemy->readData.pActor->psiData.object->rotate.vy = dir;

// CPW: 		COPYVECTOR(&characterVars.pNormChar->pEnemy->pos, &characterVars.pMainChar->pEnemy->pos);
		enemyPlaceAt(
			characterVars.pNormChar->pEnemy,
			characterVars.pMainChar->pEnemy->pos.vx,
			characterVars.pMainChar->pEnemy->pos.vy,
			characterVars.pMainChar->pEnemy->pos.vz
			);

		characterVars.pMainChar = characterVars.pNormChar;
	}

	enemyMakeMain(characterVars.pMainChar->pEnemy);
	camVars.targetEnemy = characterVars.pMainChar->pEnemy;
	mapSetPos(&(characterVars.pMainChar->pEnemy->pos));
	mapSetRot(&(camVars.camAngle));//characterVars.pMainChar->pEnemy->dir));
}

void characterMakeMain(CHARACTER *pC)
{
	// CPW: set old character state to be idle, to stop characters 'wandering off'

	if(!pC)
		return;

	if(((pC->pEnemy->readData.nameCRC == CRC_NICK) || (pC->pEnemy->readData.nameCRC == CRC_FETCHER)) && !gameSaveData.beenRatsYet)
	{
		gameSaveData.beenRatsYet++;
		helpOn = TRUE;
	}

	if(characterVars.pMainChar && (characterVars.pMainChar->pEnemy->state!=STATE_CLINGING))
	{	
		// CPW: the second bit stops rats falling off the rope
		characterVars.pMainChar->pEnemy->oldState = characterVars.pMainChar->pEnemy->state;
		characterVars.pMainChar->pEnemy->state = STATE_WANDERING;
		characterVars.pMainChar->pEnemy->stateCtr = -1;
		characterVars.pMainChar->movement.speed = 0;
		ZEROVECTOR(&characterVars.pMainChar->pEnemy->vel);

		actorFlushQueue(characterVars.pMainChar->pEnemy->readData.pActor);
		actorAnimate(characterVars.pMainChar->pEnemy->readData.pActor, GINGER_IDLEBASE, 1, 0, 256, 0, 1024);
	}

	characterVars.pMainChar = pC;
	characterVars.pNormChar = pC;
	
	//camTargetChange();
	
	enemyMakeMain(pC->pEnemy);
	camVars.targetEnemy = pC->pEnemy;
	mapSetPos(&(pC->pEnemy->pos));
	mapSetRot(&(camVars.camAngle));//pC->pEnemy->dir));
}

CHARACTER *characterGetCRC(unsigned long crc)
{
	CHARACTER *ptr;

	for(ptr=characterList.head.next; ptr != &characterList.head; ptr = ptr->next)
	{
		if(ptr->pEnemy->readData.nameCRC == crc)
		{
			return ptr;
		}
	}

	DB("Unable to locate character %x\n", crc);
	DB("(has it been added to the custom batch?)\n");

	return NULL;
}

void characterMakeMainCRC(unsigned long crc)
{
	characterMakeMain(characterGetCRC(crc));
}

int characterDistanceBetween(CHARACTER *pC1, CHARACTER *pC2)
{
	int dsq;
	VECTOR v;

	ACTOR *pA1 = pC1->pEnemy->readData.pActor;
	ACTOR *pA2 = pC2->pEnemy->readData.pActor;

	if((!pA1) || (!pA2))
		return 0x7fffffff;

	SUBVECTOR(&v, &pA1->position, &pA2->position);

	return Magnitude(&v);
}

CHARACTER *characterGetMainCharacter(void)
{
// CPW: 	characterVerify();

	return(characterVars.pMainChar);
}

void characterDoSwap(int action)
{
		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);

		VERIFY(pThis);

		DB("Attempting character swap\n");
		//camTargetChange();

		platformStopCurrentPushSample();

		switch(pThis->pEnemy->readData.nameCRC)
		{
		case CRC_ROCKY:
			{
				if(action == SWAP_RATTORAT)
					break;

				if(pNick && (characterDistanceBetween(pNick, pThis) < 800) && (pNick->pEnemy->runFlags & RUNFLAGS_VISIBLE) && (action!=SWAP_CHICKENTOCHICKEN))
				{
					pNew = pNick;
					currentRat = currentCharacter = CHAR_NAME_NICK;
				}
				else
				{
					if(pFetcher && (characterDistanceBetween(pFetcher, pThis) < 800) && (pFetcher->pEnemy->runFlags & RUNFLAGS_VISIBLE) && (action!=SWAP_CHICKENTOCHICKEN))
					{
						pNew = pFetcher;
						currentRat = currentCharacter = CHAR_NAME_FETCHER;
					}
					else
					{
						if(pGinger && (characterDistanceBetween(pGinger, pThis) < 800) && (pGinger->pEnemy->runFlags & RUNFLAGS_VISIBLE))
						{
							if(inventoryCheckForItem(COOKING_WEIGHTS) != INV_INVALIDOBJECT)
							{
								return;
							}

							pNew = pGinger;
							currentChicken = currentCharacter = CHAR_NAME_GINGER;
						}
					}
				}

				if(pNew)
				{
					camTargetChange();
					characterMakeMain(pNew);
				}


				break;
			}
		case CRC_GINGER:
			{
				if(action == SWAP_RATTORAT)
					break;

				if(pNick && (characterDistanceBetween(pNick, pThis) < 800) && (pNick->pEnemy->runFlags & RUNFLAGS_VISIBLE) && (action!=SWAP_CHICKENTOCHICKEN))
				{
					pNew = pNick;
					currentRat = currentCharacter = CHAR_NAME_NICK;
				}
				else
				{
					if(pFetcher && (characterDistanceBetween(pFetcher, pThis) < 800) && (pFetcher->pEnemy->runFlags & RUNFLAGS_VISIBLE) && (action!=SWAP_CHICKENTOCHICKEN))
					{
						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)
				{
					camTargetChange();
					characterMakeMain(pNew);
				}

				break;
			}
/*		case CRC_GNOME:
			if(characterVars.pNormChar->pEnemy->readData.nameCRC == CRC_FETCHER)
				goto fetcher;
*/
		case CRC_NICK:
			if(action == SWAP_CHICKENTOCHICKEN)
				break;

			soundPlaySamplePanFade(cardmenusfx2, 255, 0, (VECTOR *)&(pNick->pEnemy->pos));

			if(pGinger && (characterDistanceBetween(pGinger, pThis) < 800) && (pGinger->pEnemy->runFlags & RUNFLAGS_VISIBLE) && (action!=SWAP_RATTORAT))
			{
				camTargetChange();
				characterMakeMainCRC(CRC_GINGER);
				currentChicken = currentCharacter = CHAR_NAME_GINGER;
				break;
			}

			if(pRocky && (characterDistanceBetween(pRocky, pThis) < 800) && (pRocky->pEnemy->runFlags & RUNFLAGS_VISIBLE) && (action!=SWAP_RATTORAT))
			{
				camTargetChange();
				characterMakeMainCRC(CRC_ROCKY);
				currentChicken = currentCharacter = CHAR_NAME_ROCKY;
				break;
			}
			camTargetChange();
			characterMakeMainCRC(CRC_FETCHER);
			currentRat = currentCharacter = CHAR_NAME_FETCHER;
			break;

		case CRC_FETCHER:
fetcher:	if(action == SWAP_CHICKENTOCHICKEN)
				break;
			
			soundPlaySamplePanFade(cardmenusfx2, 255, 0, (VECTOR *)&(pFetcher->pEnemy->pos));

 			if(pGinger && (characterDistanceBetween(pGinger, pThis) < 800) && (pGinger->pEnemy->runFlags & RUNFLAGS_VISIBLE) && (action!=SWAP_RATTORAT))
			{
				camTargetChange();
				characterMakeMainCRC(CRC_GINGER);
				currentChicken = currentCharacter = CHAR_NAME_GINGER;
				break;
			}

			if(pRocky && (characterDistanceBetween(pRocky, pThis) < 800) && (pRocky->pEnemy->runFlags & RUNFLAGS_VISIBLE) && (action!=SWAP_RATTORAT))
			{
				camTargetChange();
				characterMakeMainCRC(CRC_ROCKY);
				currentChicken = currentCharacter = CHAR_NAME_ROCKY;
				break;
			}
			camTargetChange();
			characterMakeMainCRC(CRC_NICK);
			currentRat = currentCharacter = CHAR_NAME_NICK;
			break;

/*
		case CRC_GNOME:
			DB("Gnome!\n");

			if(action == SWAP_CHICKENTOCHICKEN)	// CPW: SHOULD be impossible, since chickens can't be the gnome...
				break;

			if(characterVars.pMainChar->pEnemy->readData.nameCRC == CRC_NICK)
			{
				camTargetChange();
				characterMakeMainCRC(CRC_FETCHER);
				currentRat = currentCharacter = CHAR_NAME_FETCHER;
			}
			else
			{
				camTargetChange();
				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);
			}
		}


	//camTargetChange();
}

void characterProcess(void)
{
	int dx1, dz1;
	CHARACTER *ptr;

	// CPW: DB("Processing character ...\n");

	if(deathDying || characterDisableController)
	{
		if(deathDying)
		{
			if(padData.debounce[0])
				deathSkip = TRUE;
		}

		dx1 = dz1 = 0;
		pReadPad->digital[0] = 0;
		pReadPad->debounce[0] = 0;

		if(characterDisableController == CONTROLLER_DISABLED_RESET)
			characterDisableController = CONTROLLER_ENABLED;
	}
	else
	{
#ifndef _WINDOWS
		dx1 = pReadPad->analogXS[0];
		dz1 = pReadPad->analogYS[0];
#else

		dx1=0; dz1=0;

		if(pReadPad->digital[0] & PAD_LEFT)
			dx1 = -4096;
		if(pReadPad->digital[0] & PAD_RIGHT)
			dx1 = 4096;
		if(pReadPad->digital[0] & PAD_UP)
			dz1 = -4096;
		if(pReadPad->digital[0] & PAD_DOWN)
			dz1 = 4096;
#endif

		if(pReadPad->analog[0])
		{
			// If we're on an analog pad, and the analog's not in use,
			// check the digital.
			// ? Align dx & dz to the nearest world axis ?
			if(!dx1 && !dz1)
			{
				if(pReadPad->digital[0] & PAD_LEFT)
					dx1 = -4096;
				if(pReadPad->digital[0] & PAD_RIGHT)
					dx1 = 4096;
				if(pReadPad->digital[0] & PAD_UP)
					dz1 = -4096;
				if(pReadPad->digital[0] & PAD_DOWN)
					dz1 = 4096;
			}
		}
		else
		{
			// If we're a digital pad, it might be worth replacing Andy S's
			// amazing analogue information with something more digital
		}
	}

	{
		int m;
		m = Magnitude2D(dx1,dz1);
		if(m > 4096)
		{
			dx1 = dx1 * 4096/m;
			dz1 = dz1 * 4096/m;
		}

	}

	dx1 = (dx1 * 5000) >> 12;
	dz1 = (dz1 * 5000) >> 12;

	moveData.joyDir.vx = dx1;
	moveData.joyDir.vy = dz1;

	// rotate into camera space
	moveData.camDir.vx = (dx1 * rcos(camVars.camAngle) - dz1  * rsin(camVars.camAngle))>>12;
	moveData.camDir.vy = - (dx1 * rsin(camVars.camAngle) + dz1  * rcos(camVars.camAngle))>>12;


/*
	if(pReadPad->debounce[0] & PAD_R2)		// make R2 the 'equip / un-equip' button for the gnome and / or welly
	{
		if(inventoryCheckForItem(GNOME)!=INV_INVALIDOBJECT)
		{
			if(currentInv->selected_item==GNOME)
				currentInv->selected_item = EMPTY;
			else
				currentInv->selected_item = GNOME;
		}

		if(inventoryCheckForItem(BOOTS)!=INV_INVALIDOBJECT)
		{
			if(currentInv->selected_item==BOOTS)
				currentInv->selected_item = EMPTY;
			else
				currentInv->selected_item = BOOTS;
		}
	}
*/


// CPW: 	DB("B4: %d\n", gamepaused);

#ifndef DREAMCAST
	if(gamepaused == 3)
	{
		pauseStart();
		return;
	}
	else
	{
		if((gamepaused > 0) && (gamepaused < 3))
			gamepaused++;
	}
#endif

	if((pKillChar == NULL) && (pReadPad->debounce[0] & controlMaps[controlMap].pause))
	{
		pause_levelplaying = NULL;
		pauseNotReallyStart();
#ifdef DREAMCAST
		return;
#endif
	}

// CPW: 	DB("AFTER: %d\n", gamepaused);


// CPW: 	if(pReadPad->debounce[0] & PAD_L2)		// make L2 the 'cycle through inventory items' button

	if((pReadPad->debounce[0] & controlMaps[controlMap].inventoryStep)
		&& (characterGetMainCharacter()->pEnemy->state!=STATE_CLINGING))		// make L2 the 'cycle through inventory items' button
	{
		int olditem;
		int nextslot;

		if(currentInv->selected_item==EMPTY)		// If empty is currently equipped, equip whatevers in slot 0
		{
			debugPrintf("Equipping slot 0\n");
			currentInv->selected_item = currentInv->slot_contents[0];
		}
		else			// slot containing current item plus the size of that item
		{
			nextslot = inventoryCheckForItem(currentInv->selected_item) + 1;
			if(nextslot>5)
				nextslot = 0;
				
			//if(currentInv->slot_contents[nextslot]!=EMPTY)
				currentInv->selected_item = currentInv->slot_contents[nextslot];
		}

		debugPrintf("Equipping item %d\n",currentInv->selected_item);
	}


// CPW: 	if(pReadPad->debounce[0] & PAD_START)
	// CPW: if(pReadPad->debounce[0] & controlMaps[controlMap].inventory)
#ifdef DREAMCAST
	if((!pauseOn) && (pReadPad->debounce[0] & controlMaps[controlMap].inventory) && (characterGetMainCharacter()->pEnemy->state!=STATE_PICKINGUP))
#else
	if((pReadPad->debounce[0] & controlMaps[controlMap].inventory) && (characterGetMainCharacter()->pEnemy->state!=STATE_PICKINGUP))
#endif
	{
		DB("Giving ...\n");

// We only use one inventory, oddly enough, despite having four characters
// who can carry objects around...
// (oh, & it's used for eqipping objects which are "given" using the generic "action" button
// if I remember right)
//		currentInv = &(characterVars.pMainChar->pEnemy->inv);
		if(levinfo->level_number!=LEVNUMBER_A2_COGCHASE)
			inventoryStart();
	}

	curtainsMain();

	for(ptr=characterList.head.next; ptr != &characterList.head; ptr = ptr->next)
	{
		if(deathDying == FALSE)
		{
			if(ptr==characterVars.pMainChar)
			{
				// CPW: process the enemy
				characterVars.pMainChar->pHandler(characterVars.pMainChar, &moveData);

				characterVars.pMainChar->movement.desiredAngle&=4095;
				characterVars.pMainChar->pEnemy->dir&=4095;
			}
			else
			{
				if(ptr->pEnemy->state != STATE_CLINGING)
				{
					// CPW: if not the main charcter, then just basically idle ...
					if(actorNearEndOfAnimation(ptr->pEnemy->readData.pActor, ptr->pEnemy->readData.pActor->animation.animData[0].segment, 3))
					{
						int idleOffs, idleAction;

						DB("Idling ...\n");

						if(utilRandomInt(8) == 0)
							idleOffs = utilRandomInt(GINGER_IDLENUM-1);
						else
							idleOffs = 0;

						idleAction = idleOffs + GINGER_IDLEBASE;

						actorFlushQueue(ptr->pEnemy->readData.pActor);
						actorAnimate(ptr->pEnemy->readData.pActor, idleAction, 0, 0, 256, 0, 1024);
					}
				}
			}
		}
		else
		{
			// CPW: really got to FORCE rocky into  death anim, because something weird is going on

			if(levinfo->level_number == LEVNUMBER_A2_COGCHASE)
			{
				if(ptr->pEnemy->readData.pActor->animation.animData[0].segment != ROCKY_COGDIE)
				{
					DB("FORCING rocky into COGDIE\n");
					actorAnimate(ptr->pEnemy->readData.pActor, ROCKY_COGDIE, 0, 0, 256, 0, 1024);
				}
			}
		}
	}
}

void characterDraw(CHARACTER *pC)
{
	;
}

int characterNickAndFetcherHandler(CHARACTER *pC, MOVEDATA *pMove)
{	// CPW: Rats!
	return 0;
}


#define SETIFKEYDOWN(P, B, S)	if(pReadPad->digital[P] & B)		{SETSTATE(S)}
#define SETIFKEYUP(P, B, S)		if(!(pReadPad->digital[P] & B))	{SETSTATE(S)}
#define SETIFKEYDEB(P, B, S)	if(pReadPad->debounce[P] & B)		{SETSTATE(S)}

//#define GINGER_JUMPFORCE			(200)
#define GINGER_JUMPFORCE			(120)
#define GINGER_BOUNCYJUMPFORCE		(300)
#define GINGER_JUMPTIME				(30)

// CPW: 400 = 50 * 8
#define GINGER_SINGLE_JUMP_HEIGHT	(400)
#define GINGER_BOUNCY_JUMP_HEIGHT	(3400)
#ifdef DREAMCAST
int dblJumpPitch = 1300;
#endif
int characterGingerAndRockyHandler(CHARACTER *pC, MOVEDATA *pMove)
{
	// CPW: Chickens
	ENEMY *pE = pC->pEnemy;
//	static ENEMY *pPickUp = NULL;
	static int pickup_item = 0;
	static SCENIC *pPickupItem = NULL;
	static SCENIC *pNearRatClingThing = NULL;
	int dx1, dz1, dx2, dz2;

	int onSticky;

	int	copyOfOldState = 0;
	static int blink=0;

	if(pMove)
	{
		dx1 = pMove->joyDir.vx;
		dz1 = pMove->joyDir.vy;

		dx2 = pMove->camDir.vx;
		dz2 = pMove->camDir.vy;
	}
	else
	{
		dx1 = dz1 = dx2 = dz2 = 0;
	}


	VERIFY(pE);


// Fred's cheap & nasty stickiness code. Slidiness is further down...

	if(pC->pEnemy->coll.collision.flags & COLLBOXFLAG_STICKY)
	{
		onSticky = TRUE;
	}
	else
	{
		onSticky = FALSE;
	}

// CPW: 	SETIFKEYDOWN(0, PAD_CIRCLE, STATE_CRAWLING);


	if(pC->pEnemy->coll.collision.flags & COLLBOXFLAG_HARMFUL)
	{
		deathKill(pC, DEATH_REASON_HARMFUL_PLATFORM, NULL);
	}
	else if(pC->pEnemy->coll.collision.flags & COLLBOXFLAG_DEADLY)
	{
		deathKill(pC, DEATH_REASON_DEADLY_PLATFORM, NULL);	// kill 'em. okay, it should just hurt. But anyway.
	}

	helpCurrentAction = 0;

	if(pC->movement.action==0)
	{
// CPW: 		if(pReadPad->digital[0] & PAD_CROSS)
		if(pReadPad->digital[0] & controlMaps[controlMap].action)
		{
			SCENIC *scenic;
			int done = 0;

//			DB("CROSS pressed\n");
			pC->movement.action = TRUE;

			if(!done)
			{
				scenic =scenicAvailableForPickup(&pC->pEnemy->readData.pActor->position, pC->pEnemy->dir);
				if(scenic && (pE->readData.nameCRC != CRC_WELLY) && (pE->readData.nameCRC != CRC_GNOME))
				{
					pickup_item = scenic->inventory_item_number;
					DB("Pickup item %d available for pickup\n",pickup_item);


					if(((pickup_item == COOKING_WEIGHTS)/*||(pickup_item == PRAM_CHASSIS) ||(pickup_item==BROKEN_LADDER)*/) && (characterGetMainCharacter()->pEnemy->readData.nameCRC == CRC_GINGER))
					{		
						DB("Thats too heavy for me... maybe Rocky could lift it?\n");

						//SB - replaced PALMODE since DC has a 50/60 Hz switch! Please leave!!
						if(palMode)
							inv_messagetimer = 50;
						else
							inv_messagetimer = 60;

						inv_currentmessage = TEXTSTR(TEXTSTR_HELP_TOO_HEAVY);
					}
					else
					{
						SETSTATE(STATE_PICKINGUP);
						pPickupItem = scenic;
						pC->movement.action = 0;
						done = 1;
					}
				}
			}

			if(!done)
			{
				SCENIC *pS, *scenic;

				scenic = NULL;
				for(pS = scenicList; pS; pS=pS->next)
				{
					if(pS->flags & SCENICFLAG_ACTIVE)
					{
						VECTOR temp;
						int d;

						SUBVECTOR(&temp, &pC->pEnemy->readData.pActor->position, &pS->pActor->position);
			
						// CPW: d = Magnitude(&temp);
						d = Magnitude2D(temp.vx, temp.vz);

						if((d < 500) && ((pS->flags & SCENICFLAG_IS_BUTTON) || (pS->flags & SCENICFLAG_IS_SWITCH)))
						{
							scenic = pS;
						}
					}
				}

				// CPW: scenic = scenicNear(&pC->pEnemy->readData.pActor->position, pC->pEnemy->dir, 500);

/*				if(scenic)
				{
					DB("Near scenic ... (%p) ya = %d, dir = %d, diff = %d\n", scenic, scenic->ya, pC->pEnemy->dir, findShortestAngle(scenic->ya, pC->pEnemy->dir));
					if(scenic->flags & SCENICFLAG_IS_BUTTON)
						DB("SCENICFLAG_IS_BUTTON\n");
					if(scenic->flags & SCENICFLAG_IS_SWITCH)
						DB("SCENICFLAG_IS_SWITCH\n");
				}
*/
				if(scenic && (ABS(findShortestAngle(scenic->ya, pC->pEnemy->dir))<1000) && ((scenic->flags & SCENICFLAG_IS_BUTTON) || (scenic->flags & SCENICFLAG_IS_SWITCH)))
				{
					DB("Near button/switch\n");
					pC->movement.pPushButton = scenic;
					SETSTATE(STATE_PUSHBUTTON);
					done = 1;
				}
/*				else
				{
					if((pE->readData.nameCRC == CRC_GINGER) || (pE->readData.nameCRC == CRC_ROCKY))
						characterDoSwap(SWAP_CHICKENTOCHICKEN);

				}
*/
			}

			if(!done)
			{	// The inventory-action check needs to come after the pick-up-scenic check
				// (coz otherwise you can't pick up the butter knife)
// CPW: 				if(PuzzleVars.usable_object_area && pReadPad->debounce[0] & PAD_CROSS)	// It needs to be debounced so coming out of the inventory behaves

				if(PuzzleVars.usable_object_area && pReadPad->debounce[0] & controlMaps[controlMap].action)	// It needs to be debounced so coming out of the inventory behaves
				{
					//if(inventoryUseObject(currentInv->selected_item,0))
					FMAplayCheck(1);
					done = 1;	// "done" WON'T always be set - eg, (somethimes) if selected_item is "EMPTY".

					// New improved almost working version
					//if(inventoryUseObject(currentInv->selected_item,0))
					//	done = 1;	// "done" WON'T always be set - eg, (somethimes) if selected_item is "EMPTY".
				}
			}

		}
	}
	else
	{
		if((pReadPad->digital[0] & controlMaps[controlMap].action)==0)
			pC->movement.action = 0;
	}


/*
	if(pC->movement.action==0)
	{
		if(pReadPad->digital[0] & PAD_CROSS)
		{
			SCENIC *scenic;
			DB("CROSS pressed\n");
			pC->movement.action = TRUE;

			scenic =scenicAvailableForPickup(&pC->pEnemy->readData.pActor->position, pC->pEnemy->dir);
			if(scenic)
			{
				pickup_item = scenic->inventory_item_number;
				DB("Pickup item %d available for pickup\n",pickup_item);
				SETSTATE(STATE_PICKINGUP);
				pC->movement.action = 0;
			}

			scenic = scenicNear(&pC->pEnemy->readData.pActor->position, pC->pEnemy->dir, 500);
			if(scenic && ((scenic->flags & SCENICFLAG_IS_BUTTON) || (scenic->flags & SCENICFLAG_IS_SWITCH)))
			{
				DB("Near button\n");
				pC->movement.pPushButton = scenic;
				SETSTATE(STATE_PUSHBUTTON);
			}
		}
	}
	else
	{
		if((pReadPad->digital[0] & PAD_CROSS)==0)
			pC->movement.action = 0;
	}
*/

	if(pC->pEnemy->readData.nameCRC == utilStr2CRC("WELLY"))
	{
		if(pE->state!=STATE_HIDING)
		{
// CPW: 			SETIFKEYDEB(0, PAD_SQUARE, STATE_HIDING);
			SETIFKEYDEB(0, controlMaps[controlMap].hide, STATE_HIDING);
			pC->pVTable = hidingVTable;
		}
	}
	else
	{
		if((pC->pEnemy->readData.nameCRC == CRC_NICK) || (pC->pEnemy->readData.nameCRC == CRC_FETCHER))
		{
			pNearRatClingThing = scenicNearARatClingThing(&pE->pos, 0);
			if(pNearRatClingThing)
			{
				VERIFY(pNearRatClingThing);
			}
		}
		else
			pNearRatClingThing = NULL;

		if((!pNearRatClingThing) && (pE->runFlags & RUNFLAGS_ONGROUND))
		{
/*			if(pE->state == STATE_AGAINSTWALL)
			{
				// CPW: if against wall, turn us around, and give us a small forward vel.
				int dir = pC->movement.againstWallAng;

				pC->pEnemy->dir = dir;
				pC->pEnemy->desiredDir = dir;
				pC->movement.desiredAngle = dir;
				pC->pEnemy->readData.pActor->psiData.object->rotate.vy = dir;
			}
*/
			SETIFKEYDEB(0, controlMaps[controlMap].jump, STATE_JUMPING);
		}
	}

	
	pC->movement.speed = utilSqrt((dx2*dx2)+(dz2*dz2))>>16;
	pC->pEnemy->moveSpeed = pC->movement.speed;


	if(dx1 || dz1)
		pC->movement.desiredAngle=(utilCalcAngle(dx2,dz2) + 2048) & 4095;

	if(pC->pEnemy->stateCtr<0)
		pC->pEnemy->stateCtr=0;

	switch(pC->pEnemy->state)
	{
	case STATE_AGAINSTWALL:

		// CPW: DB("A bunch of mindless jerks who'll be first against the wall when the revolution comes\n");

		dx2 = dx2 >> 3;
		dz2 = dz2 >> 3;

		pC->pEnemy->readData.pActor->psiData.object->rotate.vy = pC->movement.againstWallAng;
 		pC->pEnemy->flags|=RUNFLAGS_FORCEFACE;


		if(pC->movement.speed == 0) // addition to keep chicken pressed against wall
		{
			VECTOR	temp;
			temp.vx=0;
			temp.vy=0;
			temp.vz=2;
			RotateVector2D(&temp,&temp,pC->pEnemy->readData.pActor->psiData.object->rotate.vy);

			pE->pos.vx+=temp.vx;
			pE->pos.vy+=temp.vy;
			pE->pos.vz+=temp.vz;
		}

		if((pC->movement.speed == 0) || (pC->pEnemy->stateCtr == 0))
		{
			if(pE->readData.pActor->animation.animData[0].segment!=GINGER_STRAFEIDLE)
			{
				actorFlushQueue(pE->readData.pActor);
				actorAnimate(pE->readData.pActor, GINGER_STRAFEIDLE, 1, 0, 256, 0, 1024);
			}
/*
			pC->movement.anim = GINGER_STRAFEIDLE;
			actorSetAnimSpeed(pE->readData.pActor, 256);
*/			
		}
		else
		{
 			int diff = pC->movement.desiredAngle - pC->pEnemy->readData.pActor->psiData.object->rotate.vy;
			int lr;
			int strafespeed;

			diff&=4095;

			if(diff>2048)
				diff-=4096;
			else
			{
				if(diff<-2048)
					diff+=4096;
			}

			lr = (diff>0)?GINGER_STRAFELEFT:GINGER_STRAFERIGHT;

			if(abs(diff)>1950)
			{
				int sx, sz;

				lr = GINGER_STRAFEIDLE;
				// CPW: stop it sliding ...

				sx = abs(rcos(pC->movement.againstWallAng));
				sz = abs(rsin(pC->movement.againstWallAng));

				dx2 = (dx2 * sz) >> 12;
				dz2 = (dz2 * sx) >> 12;

				// CPW: DB("VEC = (%d, %d)\n", sx, sz);
			}

			strafespeed = ABS(findShortestAngle(calc_angle(dx2, dz2) & 0xfff, pC->movement.againstWallAng))>>1;

			if(pE->readData.pActor->animation.animData[0].segment!=lr)
			{
				actorFlushQueue(pE->readData.pActor);
				actorAnimate(pE->readData.pActor, lr, 1, 0, strafespeed, 0, 1024);
			}

			actorSetAnimSpeed(pE->readData.pActor, strafespeed);
		
/*
			pC->movement.anim = (diff>0)?GINGER_STRAFERIGHT:GINGER_STRAFELEFT;
			actorSetAnimSpeed(pE->readData.pActor, pC->movement.speed>>4);
*/
		}
/*
		if((pC->pEnemy->readData.nameCRC == utilStr2CRC("NICK")) || (pC->pEnemy->readData.nameCRC == utilStr2CRC("FETCHER")))
		{
			DB("Rat touched wall!\n");
		}
*/

		pC->pEnemy->vel.vx = dx2>>6;
		pC->pEnemy->vel.vz = dz2>>6;
		
		if(pC->movement.againstWall == 0)
		{
			pC->movement.desiredAngle = pC->pEnemy->readData.pActor->psiData.object->rotate.vy;
			pC->pEnemy->dir = pC->pEnemy->readData.pActor->psiData.object->rotate.vy;
			SETSTATE(STATE_WANDERING);
		}

		break;
	
	 case STATE_SHOULDERPUSHING:
		 // CPW: DB("State Shoulderpushing\n");

// RFW - Ginger pushes sideways, everyone else pushes forwards
		if((pC->pEnemy->readData.nameCRC == CRC_GINGER))
		{
			pC->pEnemy->readData.pActor->psiData.object->rotate.vy = (pC->movement.againstWallAng + 1024) & 4095;
		}
		else
		{
			pC->pEnemy->readData.pActor->psiData.object->rotate.vy = (pC->movement.againstWallAng) & 4095;
		}
 		pC->pEnemy->flags|=RUNFLAGS_FORCEFACE;



		if(pC->movement.speed==0)
		{
			// RFW - I've left the "revert to strafe" in, & corrected the angle.
//			pC->pEnemy->readData.pActor->psiData.object->rotate.vy = (pC->movement.againstWallAng + 2048) & 4095;

			if(pE->readData.pActor->animation.animData[0].segment!=GINGER_STRAFEIDLE)
			{
				actorFlushQueue(pE->readData.pActor);
				actorAnimate(pE->readData.pActor, GINGER_IDLESTILL, 1, 0, 256, 0, 1024);
			}

			pC->pEnemy->vel.vx = pC->pEnemy->vel.vz = 0;
		}
		else
		{
			int as;

			switch(pE->readData.nameCRC)
			{
			case CRC_ROCKY:
				as = pC->movement.speed>>2;
				break;
			default:
				as = pC->movement.speed>>3;
			}

			if(pE->readData.pActor->animation.animData[0].segment!=GINGER_PUSHRIGHT)
			{
				actorFlushQueue(pE->readData.pActor);
				actorAnimate(pE->readData.pActor, GINGER_PUSHRIGHT, 1, 0, as, 0, 1024);
			}

			pC->pEnemy->vel.vx = dx2>>8;	// RFW 8 shifts looks about right for non ice-skating
			pC->pEnemy->vel.vz = dz2>>8;

			if(!pC->movement.pushDir.vx)	// RFW - Restrict movement to the platform's direction of motion
				pC->pEnemy->vel.vx = 0;
			if(!pC->movement.pushDir.vz)
				pC->pEnemy->vel.vz = 0;
		}

		if(pC->movement.shoulderPush == 0)
		{
			// RFW - I've corrected the angle after pushing, so you don't end up splayed at a silly angle.
			pE->dir = pC->movement.desiredAngle = pC->pEnemy->readData.pActor->psiData.object->rotate.vy;

// CPW: 			pC->pEnemy->pos.vx -= pC->movement.pushDir.vx>>4;
// CPW: 			pC->pEnemy->pos.vz -= pC->movement.pushDir.vz>>4;

			DB("Finishing push\n");


			SETSTATE(STATE_WANDERING);
		}
		break;

	case STATE_WANDERING:

		if(pC->pEnemy->stateCtr == 0)
		{
/*
			if(pC->pEnemy->runFlags & RUNFLAGS_BEENONGROUND)
				dsEvent_Add(1, 1, 0, 0, 0, 0, 0);
*/
			if(pC->pEnemy->oldState == STATE_CLINGING)
				curtainsRemoveRatFromRopes(pC);
		}

		if((pC->pEnemy->runFlags & RUNFLAGS_TRULYJUSTLANDED)/* && (pC->pEnemy->runFlags & RUNFLAGS_BEENONGROUND)*/)
		{
			dsEvent_Add(1, 1, 0, 0, 0, 0, 0);
			pE->runFlags &= ~RUNFLAGS_TRULYJUSTLANDED;
		}
		// if fall on to bouncy platform then ping char up in air 
		//if( (pC->pEnemy->coll.collision.flags&COLLBOXFLAG_BOUNCY) && (pC->pEnemy->runFlags & RUNFLAGS_JUSTLANDED))
		if((pC->pEnemy->coll.collision.flags&COLLBOXFLAG_BOUNCY) && ((pC->pEnemy->runFlags & RUNFLAGS_JUSTLANDED) || (pC->pEnemy->vel.vy>0)))
		{
			int reqFrames, jumpFrames;
			reqFrames = GINGER_JUMPFORCE/GRAVITY;
			jumpFrames = pE->readData.pActor->animation.pSegments[GINGER_JUMP].actualAnimLength;

			// CPW: DB("Starting jump ...\n");
			pC->pEnemy->vel.vy = -(GINGER_BOUNCYJUMPFORCE);
			pC->jump.singleHeight = GINGER_BOUNCY_JUMP_HEIGHT;

			pC->pEnemy->runFlags&=(~RUNFLAGS_DJUMPPENDING);
			pC->pEnemy->runFlags|=RUNFLAGS_DJUMPALLOWED;

			actorFlushQueue(pE->readData.pActor);
			actorAnimate(pE->readData.pActor, GINGER_JUMP, 0, 0, 280, 0, 0);
// CPW: 			soundPlaySamplePanFade(pC->pEnemy->readData.ppSamples[CHAR_SFX_JUMP1], 255, 0, (VECTOR *)&(pC->pEnemy->pos));
// CPW: 			DB("Playing CHAR_SFX_JUMP1 0x%p\n", pC->pEnemy->readData.ppSamples[CHAR_SFX_JUMP1]);
			//pC->pEnemy->stateCtr=-1;	// CPW: reset jumping. Hope this works.
		}


		pC->pEnemy->dir = enemyTendTowards(pC->movement.desiredAngle, pC->pEnemy->dir, 400);
		{
			int idleAction = GINGER_IDLEBASE;

			// CPW: Fred's sprout throwing stuff
			if(	(pReadPad->debounce[0] & controlMaps[controlMap].chuck)
				&& (gamePlayData->sprouts_carried>0)
				&& (characterVars.pMainChar->pEnemy->readData.nameCRC != CRC_GNOME)
				&& (characterVars.pMainChar->pEnemy->readData.nameCRC != CRC_WELLY)
				&& (characterVars.pMainChar->pEnemy->readData.nameCRC != CRC_NICK)
				&& (characterVars.pMainChar->pEnemy->readData.nameCRC != CRC_FETCHER)
				)
			{
				actorStartThrow(pC->pEnemy->readData.pActor);
				// CPW: jump sounds god-awful as a jump, but OK as a throw, so we'll use it for that instead.
				// CPW: soundPlaySamplePanFade(pC->pEnemy->readData.ppSamples[CHAR_SFX_THROW], 255, 0, (VECTOR *)&(pC->pEnemy->pos));
			}

			if(pC->pEnemy->readData.pActor->animation.throwCounter == 5)
			{
				VECTOR pos;
				VECTOR vel;
		
				pos = pC->pEnemy->pos;
		
				pos.vy -= 320;
				pos.vx -= rcos(pC->pEnemy->dir) * 160 / 4096;
				pos.vz += rsin(pC->pEnemy->dir) * 160 / 4096;

				vel.vx = -rsin(pC->pEnemy->dir) * SPROUT_INIT_HSPEED/4096;
				vel.vy = SPROUT_INIT_YVEL;
				vel.vz = -rcos(pC->pEnemy->dir) * SPROUT_INIT_HSPEED/4096;

				vel.vx += pC->pEnemy->vel.vx;
				vel.vy += pC->pEnemy->vel.vy;
				vel.vz += pC->pEnemy->vel.vz;

				sproutsThrow(&pos, &vel);
			}


			if(pC->pEnemy->runFlags & RUNFLAGS_ONGROUND)
			{
				if(pC->movement.speed==0)
				{
					if(actorNearEndOfAnimation(pC->pEnemy->readData.pActor, pC->pEnemy->readData.pActor->animation.animData[0].segment, 3) ||
						(pC->pEnemy->readData.pActor->animation.animData[0].segment == GINGER_CRAWL) ||
						(pC->pEnemy->readData.pActor->animation.animData[0].segment == GINGER_CREEP) ||
						(pC->pEnemy->readData.pActor->animation.animData[0].segment == GINGER_WALK) ||
						(pC->pEnemy->readData.pActor->animation.animData[0].segment == GINGER_RUN) ||
						(pC->pEnemy->readData.pActor->animation.animData[0].segment == GINGER_FLAP))
					{
						int idleOffs;
	
						if(utilRandomInt(8) == 0)
							idleOffs = utilRandomInt(GINGER_IDLENUM-1);
						else
							idleOffs = 0;
	
						idleAction = idleOffs + GINGER_IDLEBASE;
	
						// CPW: DB("Starting to idle ...\n");
						// CPW: if(pC->pEnemy->readData.pActor->animation.animData[0].segment!=idleAction)
						{
							actorFlushQueue(pE->readData.pActor);
							actorAnimate(pE->readData.pActor, idleAction, 0, 0, 256, 0, 1024);
						}
					}
				}
				else
				{

					if(pReadPad->digital[0] & controlMaps[controlMap].creep)
					{
						int s,m;

						DB("Creeping!\n");
						pC->movement.speed = GINGER_FORCE_CREEPSPEED;

// CPW: 						s = dx1 * dx1 + dz1 * dz1;
// CPW: 						FASTSQRT(m, s);

						m = Magnitude2D(dx1, dz1);
						m = m >> 9;

						if(m==0)
							m=1;

						dx1 = dx1/m;
						dz1 = dz1/m;

						m = Magnitude2D(dx2, dz2);
						m = m >> 9;

						if(m==0)
							m=1;

						dx2 = dx2/m;
						dz2 = dz2/m;


/*						s = dx2 * dx2 + dz2 * dz2;
						FASTSQRT(m, s);
						m = m >> 16;

						dx2 = dx2/m;
						dz2 = dz2/m;
*/
					}

					if(onSticky == TRUE)
					{
						dx2 = dx2/4;	// Okay, that works, but we should possibly not be tiptoeing...
						dz2 = dz2/4;
						dx1 = dx1/4;
						dz1 = dz1/4;
					}

	  				if(pC->movement.speed>GINGER_WALKSPEED)
	 				{
						if(pC->pEnemy->readData.pActor->animation.animData[0].segment!=GINGER_RUN)
							actorAnimate(pE->readData.pActor, GINGER_RUN, 1, 0, pC->movement.speed>>4, 0, 512);
					
						actorSetAnimSpeed(pE->readData.pActor, (onSticky == TRUE)?pC->movement.speed>>6:pC->movement.speed>>4);
					}
					else
					{
// CPW: 						if(pC->movement.speed<GINGER_WALKSPEED)
						{
							if(pC->pEnemy->readData.pActor->animation.animData[0].segment!=GINGER_CREEP)
								actorAnimate(pE->readData.pActor, GINGER_CREEP, 1, 0, pC->movement.speed>>1, 0, 512);
	
							actorSetAnimSpeed(pE->readData.pActor, (onSticky == TRUE)?pC->movement.speed>>3:pC->movement.speed>>1);
						}
/*						else
						{
							if(pC->pEnemy->readData.pActor->animation.animData[0].segment!=GINGER_WALK)
								actorAnimate(pE->readData.pActor, GINGER_WALK, 1, 0, pC->movement.speed>>2, 0, 512);
								// CPW: actorAnimate(pE->readData.pActor, GINGER_WALK, 1, 0, 32, 0, 512);
	
							actorSetAnimSpeed(pE->readData.pActor, (onSticky == TRUE)?pC->movement.speed>>4:pC->movement.speed>>2);
							// CPW: actorSetAnimSpeed(pE->readData.pActor, 32);
						}*/
					}
	
	
/*					if(	(actorReachedFrameRatio(pC->pEnemy->readData.pActor, -1, 1024) == ACTORFRAME_AT) ||
						(actorReachedFrameRatio(pC->pEnemy->readData.pActor, -1, 3072) == ACTORFRAME_AT))
					{
						if(pC->pEnemy->readData.pActor->animation.animData[0].segment == GINGER_CREEP)
							enemyPlayEnvironmentFootstepNoDogCheck((VECTOR *)&(pC->pEnemy->pos), 64);
						else
							enemyPlayEnvironmentFootstep((VECTOR *)&(pC->pEnemy->pos), 112);
					}
*/

					{
						int c=-1, a=-1;

						if(pC->pEnemy->readData.pActor->animation.animData[0].segment == GINGER_CREEP)
							a = 0;
						else
						{
							if(pC->pEnemy->readData.pActor->animation.animData[0].segment == GINGER_RUN)
								a = 1;
						}

						ASSERT(a!=-1);

						switch(pE->readData.nameCRC)
						{
						case CRC_GINGER:
							c = 0;
							break;
						case CRC_ROCKY:
							c = 1;
							break;
						case CRC_NICK:
							c = 2;
							break;
						case CRC_FETCHER:
							c = 3;
						}

						if(c==-1) c=0;

						if(	(actorReachedFrameRatio(pC->pEnemy->readData.pActor, -1, footStepLUT[a][c][0]) == ACTORFRAME_AT) ||
							(actorReachedFrameRatio(pC->pEnemy->readData.pActor, -1, footStepLUT[a][c][1]) == ACTORFRAME_AT))
						{
							if(pC->pEnemy->readData.pActor->animation.animData[0].segment == GINGER_CREEP)
								enemyPlayEnvironmentFootstepNoDogCheck((VECTOR *)&(pC->pEnemy->pos), 48);
							else
								enemyPlayEnvironmentFootstep((VECTOR *)&(pC->pEnemy->pos), 112);
						}
					}
				}
			}
			else
			{
				if(pE->runFlags & RUNFLAGS_BEENONGROUND)
				{
					if(pC->pEnemy->stateCtr == 0)
					{
						DB("SHOCK 2!\n");
						dsEvent_Add(1, 1, 0, 0, 0, 0, 0);
					}

					if((pC->pEnemy->readData.nameCRC == CRC_GINGER) || (pC->pEnemy->readData.nameCRC == CRC_ROCKY))
					{
						if(pC->pEnemy->readData.pActor->animation.animData[0].segment!=GINGER_FLAP)
							actorAnimate(pE->readData.pActor, GINGER_FLAP, 1, 0, 512, 0, 512);
					}
				}
			}
		}


// Fred's cheap & nasty "slippery surface" code... stickiness is done right at the start

		if(pC->pEnemy->runFlags & RUNFLAGS_VISIBLE)
		{
			if(pC->pEnemy->coll.collision.flags & COLLBOXFLAG_SLIPPY)
			{
				// One thing this doesn't handle is that hitting the wall doesn't zero the velocity
				pC->pEnemy->vel.vx = (pC->pEnemy->vel.vx * 250 + (dx2>>6) * 6) / 256;
				pC->pEnemy->vel.vz = (pC->pEnemy->vel.vz * 250 + (dz2>>6) * 6) / 256;
			}
			else
			{
				pC->pEnemy->vel.vx = dx2>>6;
				pC->pEnemy->vel.vz = dz2>>6;
			}
		}
		else
		{
			;
		}

		if(pC->pEnemy->coll.collision.flags & COLLBOXFLAG_NOISY)
		{
			DB("Hit noisy platform\n");

			if(pHitData[0].pPlatHit && (((PLATFORMWORK *)pHitData[0].pPlatHit)->noisy.touchCounter>50))
			{
				soundPlaySamplePanFade(((PLATFORMWORK *)pHitData[0].pPlatHit)->noisy.pSample, 255, 0, (VECTOR *)&(pC->pEnemy->pos));
				((PLATFORMWORK *)pHitData[0].pPlatHit)->noisy.touchCounter=0;

				// CPW: enemyDogFindAndAlert(&characterVars.pMainChar->pEnemy->pos);
				nmeDogStartHourglass(2);
			}
		}

// CPW: 		if(((pReadPad->debounce[0] & PAD_CROSS) || (pReadPad->debounce[0] & PAD_SQUARE)) && pNearRatClingThing)

		if(((pReadPad->debounce[0] & controlMaps[controlMap].action)
			|| (pReadPad->debounce[0] & controlMaps[controlMap].jump)))
		{
			DB("Can I get onto rope?\n");
			if(pNearRatClingThing)// CPW:  && (!curtainsGetRatOnRope(0)))
			{
				VERIFY(pNearRatClingThing);

				DB("Get on to rope (tag = %d)\n", pNearRatClingThing->tag);
				pE->ratClimbing.tag = pNearRatClingThing->tag;
				pE->ratClimbing.pScenic = pNearRatClingThing;
				SETSTATE(STATE_CLINGING);
			}
		}
		break;

	case STATE_JUMPING:
		{
			int reqFrames, jumpFrames;
			pC->pEnemy->dir = enemyTendTowards(pC->movement.desiredAngle, pC->pEnemy->dir, 196);
			if(pC->pEnemy->stateCtr==0)	
			{
				if(pE->oldState == STATE_AGAINSTWALL)
				{
					// CPW: if against wall, turn us around, and give us a small forward vel.
					int dir = pC->movement.againstWallAng;

					pC->pEnemy->dir = dir;
					pC->pEnemy->desiredDir = dir;
					pC->movement.desiredAngle = dir;
					pC->pEnemy->readData.pActor->psiData.object->rotate.vy = dir;
				}

				pC->jump.timeAtJumpTop = 0;
				pC->jump.stage = 0;	// CPW: starting jump

				pC->jump.heightAtJumpStart = pE->pos.vy;
//				DB("Started jump at %d\n", pC->jump.heightAtJumpStart/8);

				reqFrames = GINGER_JUMPFORCE/GRAVITY;
				jumpFrames = pE->readData.pActor->animation.pSegments[GINGER_JUMP].actualAnimLength;


/*				if(pC->pEnemy->coll.collision.flags&COLLBOXFLAG_BOUNCY)
				{
					pC->pEnemy->vel.vy = -(GINGER_JUMPFORCE*2 + GINGER_JUMPFORCE/4);
					pC->jump.singleHeight = GINGER_BOUNCY_JUMP_HEIGHT;
				}
				else
*/				{
					pC->pEnemy->vel.vy = -GINGER_JUMPFORCE;
					pC->jump.singleHeight = (GINGER_SINGLE_JUMP_HEIGHT- pC->pEnemy->coll.collision.radius) ;
				}

				pC->pEnemy->runFlags&=(~RUNFLAGS_DJUMPPENDING);
				pC->pEnemy->runFlags|=RUNFLAGS_DJUMPALLOWED;

				actorFlushQueue(pE->readData.pActor);
				actorAnimate(pE->readData.pActor, GINGER_JUMP, 0, 0, 280, 0, 0);
				// CPW: actorAnimate(pE->readData.pActor, GINGER_FALL, 0, 1, 128, 0, 0);
				// CPW: actorAnimate(pE->readData.pActor, GINGER_FLAP, 1, 1,	380, 0, 0);

				// CPW: soundPlaySamplePanFade(pSamples[SFX_GINGERJUMP1], 255, 0, (VECTOR *)&(pC->pEnemy->pos));

				soundPlaySamplePanFade(pC->pEnemy->readData.ppSamples[CHAR_SFX_JUMP1], 255, 0, (VECTOR *)&(pC->pEnemy->pos));
 			}
			else
			{
				{
					// CPW: Test for double jump request

// CPW: 					if((pReadPad->debounce[0] & PAD_SQUARE) && ((pC->pEnemy->readData.nameCRC == CRC_GINGER) || (pC->pEnemy->readData.nameCRC == CRC_FETCHER)))
					if((pReadPad->debounce[0] & controlMaps[controlMap].jump) && ((pC->pEnemy->readData.nameCRC == CRC_GINGER) || (pC->pEnemy->readData.nameCRC == CRC_FETCHER) || (pC->pEnemy->readData.nameCRC == CRC_ROCKY)))
					{
//						DB("Double jump pending ...\n");
						pC->pEnemy->runFlags|=RUNFLAGS_DJUMPPENDING;
					}

/*					if(pC->pEnemy->coll.collision.flags&COLLBOXFLAG_BOUNCY)
					{
						int reqFrames, jumpFrames;
						reqFrames = GINGER_JUMPFORCE/GRAVITY;
						jumpFrames = pE->readData.pActor->animation.pSegments[GINGER_JUMP].actualAnimLength;

						// CPW: DB("Starting jump ...\n");
						pC->pEnemy->vel.vy = -(GINGER_JUMPFORCE*2 + GINGER_JUMPFORCE/4);
						pC->jump.singleHeight = GINGER_BOUNCY_JUMP_HEIGHT;

						pC->pEnemy->runFlags&=(~RUNFLAGS_DJUMPPENDING);
						pC->pEnemy->runFlags|=RUNFLAGS_DJUMPALLOWED;

						actorFlushQueue(pE->readData.pActor);
						actorAnimate(pE->readData.pActor, GINGER_JUMP, 0, 0, 280, 0, 0);
						soundPlaySamplePanFade(pC->pEnemy->readData.ppSamples[CHAR_SFX_JUMP1], 255, 0, (VECTOR *)&(pC->pEnemy->pos));
						//pC->pEnemy->stateCtr=-1;	// CPW: reset jumping. Hope this works.

					}
*/		
				
					switch(pC->jump.stage)
					{
					case 0:

						//if((pC->jump.heightAtJumpStart - pE->pos.vy) >= pC->jump.singleHeight)
						//DB("vel %d  pos %d\n",pE->vel.vy,pE->pos.vy/8);
						if (pE->vel.vy>0)
						{
//							DB("I have reached the pinnacle of my momentous leap\n");
//							DB("vel %d  pos %d\n",pE->vel.vy,pE->pos.vy/8);
							// CPW: pE->vel.vy = -GRAVITY;
							pE->vel.vy = (96 * pE->vel.vy) / 256;
				
							pC->jump.timeAtJumpTop = frame;
							pC->jump.stage = 1;	// CPW: reached height
						}
						/*else
						{
							pE->vel.vy = -jumpVel;
						}
						*/
						break;
				
					case 1:
						if(frame >= pC->jump.timeAtJumpTop + 20)
						{
							pC->jump.timeAtJumpTop = 0;
							pC->jump.stage = 2;
						}
						else
						{
//							DB("Waiting ...\n");
						}
				
						break;
				
					case 2:
//						DB("Falling ...\n");
						//pE->vel.vy = jumpVel;
						break;
				
					case 3:
						if((pC->jump.heightAtJumpStart - pE->pos.vy) >= GINGER_SINGLE_JUMP_HEIGHT + (25*8))
						{
//							DB("Top of double jump\n");
							//pE->vel.vy = -GRAVITY;

							//pE->pos.vy = pC->jump.heightAtJumpStart-(GINGER_SINGLE_JUMP_HEIGHT + (25*8));

							pC->jump.timeAtDoubleJumpTop = frame;
							pC->jump.stage = 4;	// CPW: reached height
						}
						break;
				
					case 4:
						if(frame >= pC->jump.timeAtDoubleJumpTop + 0)
						{
							pC->jump.timeAtJumpTop = 0;
							pC->jump.stage = 5;
							//pE->vel.vy = -GRAVITY;
						}
						else
						{
//							DB("Waiting ...\n");
							//pE->vel.vy = -GRAVITY;
						}
						break;
				
					case 5:
						break;
					}

					if(pE->pos.vy > pC->jump.heightAtJumpStart)
					{
//						DB("Fallen below jump start height!\n");
						pC->jump.heightAtJumpStart = 0x7fffffff;
						SETSTATE(STATE_WANDERING);
					}

					if((pC->pEnemy->runFlags & RUNFLAGS_DJUMPPENDING) && (pC->pEnemy->vel.vy>0) && ((pC->pEnemy->runFlags & RUNFLAGS_DJUMPALLOWED)) )
					{
						int sRate;

						pC->jump.stage = 3;
						//pE->vel.vy = -(jumpVel / 2);
						pE->vel.vy = -((GINGER_JUMPFORCE*2/3));
				
						actorFlushQueue(pE->readData.pActor);
						actorAnimate(pE->readData.pActor, GINGER_DOUBLEJUMP, 0, 0, 256, 0, 1024);
						// CPW: actorAnimate(pE->readData.pActor, GINGER_FLAP, 1, 1, 400, 0, 1024);
				
						pC->pEnemy->runFlags&=(~RUNFLAGS_DJUMPPENDING);
						pC->pEnemy->runFlags&=(~RUNFLAGS_DJUMPALLOWED);
				
#ifndef _WINDOWS
#ifdef DREAMCAST
						soundPlaySamplePanFade(pC->pEnemy->readData.ppSamples[CHAR_SFX_DOUBLEJUMP], 255, dblJumpPitch, (VECTOR *)&(pC->pEnemy->pos));
#else
						sRate = pC->pEnemy->readData.ppSamples[CHAR_SFX_DOUBLEJUMP]->sampleRate;
						soundPlaySamplePanFade(pC->pEnemy->readData.ppSamples[CHAR_SFX_DOUBLEJUMP], 255, sRate + (sRate>>2), (VECTOR *)&(pC->pEnemy->pos));
#endif
#endif
					}
				}

				if(pC->pEnemy->runFlags & RUNFLAGS_ONGROUND)
				{
					actorFlushQueue(pE->readData.pActor);
					actorAnimate(pE->readData.pActor, GINGER_JUMPLAND, 0, 0, 256, 0, 1024);
//					DB("Ginger land\n");
					//pE->vel.vy=0;
				
					// CPW: soundPlaySamplePanFade(pSamples[SFX_GINGERLAND], 255, 0, (VECTOR *)&(pC->pEnemy->pos));

					enemyPlayEnvironmentFootstep((VECTOR *)&(pC->pEnemy->pos), 255);


/*					if(pC->pEnemy->coll.collision.flags&COLLBOXFLAG_BOUNCY)
					{
						int reqFrames, jumpFrames;
						reqFrames = GINGER_JUMPFORCE/GRAVITY;
						jumpFrames = pE->readData.pActor->animation.pSegments[GINGER_JUMP].actualAnimLength;

						// CPW: DB("Starting jump ...\n");
						pC->pEnemy->vel.vy = -(GINGER_JUMPFORCE*2 + GINGER_JUMPFORCE/4);
						pC->jump.singleHeight = GINGER_BOUNCY_JUMP_HEIGHT;

						pC->pEnemy->runFlags&=(~RUNFLAGS_DJUMPPENDING);
						pC->pEnemy->runFlags|=RUNFLAGS_DJUMPALLOWED;

						actorFlushQueue(pE->readData.pActor);
						actorAnimate(pE->readData.pActor, GINGER_JUMP, 0, 0, 280, 0, 0);
						soundPlaySamplePanFade(pC->pEnemy->readData.ppSamples[CHAR_SFX_JUMP1], 255, 0, (VECTOR *)&(pC->pEnemy->pos));
						pC->pEnemy->stateCtr=-1;	// CPW: reset jumping. Hope this works.

					}
					else
*/					{
						SETSTATE(STATE_WANDERING);
					}
				}
			}

/*
			{
				SCENIC *pS;

				pS = scenicNearARatClingThing(&pE->pos, 0);
				if(pS)
				{
					if((pC->pEnemy->readData.nameCRC == CRC_NICK) || (pC->pEnemy->readData.nameCRC == CRC_FETCHER))
					{
						DB("Start RAT climbing FMA (tag = %d)\n", pS->tag);
						pE->ratClimbing.tag = pS->tag;
						pE->ratClimbing.pScenic = pS;
						SETSTATE(STATE_CLINGING);
					}
				}
			}
*/
		}

		pC->pEnemy->vel.vx = dx2>>6;
		pC->pEnemy->vel.vz = dz2>>6;


		if( (pC->pEnemy->coll.collision.flags&COLLBOXFLAG_BOUNCY) && ((pC->pEnemy->runFlags & RUNFLAGS_JUSTLANDED) || (pC->pEnemy->vel.vy>0)) )
		{
			int reqFrames, jumpFrames;
			reqFrames = GINGER_JUMPFORCE/GRAVITY;
			jumpFrames = pE->readData.pActor->animation.pSegments[GINGER_JUMP].actualAnimLength;

			// CPW: DB("Starting jump ...\n");
			pC->pEnemy->vel.vy = -(GINGER_BOUNCYJUMPFORCE);
			pC->jump.singleHeight = GINGER_BOUNCY_JUMP_HEIGHT;

			pC->pEnemy->runFlags&=(~RUNFLAGS_DJUMPPENDING);
			pC->pEnemy->runFlags|=RUNFLAGS_DJUMPALLOWED;

			actorFlushQueue(pE->readData.pActor);
			actorAnimate(pE->readData.pActor, GINGER_JUMP, 0, 0, 280, 0, 0);
			soundPlaySamplePanFade(pC->pEnemy->readData.ppSamples[CHAR_SFX_JUMP1], 255, 0, (VECTOR *)&(pC->pEnemy->pos));
			//pC->pEnemy->stateCtr=-1;	// CPW: reset jumping. Hope this works.
			SETSTATE(STATE_WANDERING);
		}



		break;

	case STATE_LANDING:

		// CPW: DB("Waiting for land to end ...\n");

		if(actorNearEndOfAnimation(pC->pEnemy->readData.pActor, GINGER_JUMPLAND, 3))
		{
			pC->jump.stage = 0;
			SETSTATE(STATE_WANDERING);
		}

		pC->pEnemy->vel.vx = dx2>>6;
		pC->pEnemy->vel.vz = dz2>>6;

		break;

	case STATE_CRAWLING:

		if(pE->readData.pActor->animation.animData[0].segment!=GINGER_CRAWL)
		{
			actorFlushQueue(pE->readData.pActor);
			actorAnimate(pE->readData.pActor, GINGER_CRAWL, 1, 0, pC->movement.speed>>3, 0, 1024);
		}

		actorSetAnimSpeed(pE->readData.pActor, pC->movement.speed>>3);
/*
		pC->movement.anim=GINGER_CRAWL;
		actorSetAnimSpeed(pE->readData.pActor, pC->movement.speed>>3);
*/
		pC->pEnemy->dir = enemyTendTowards(pC->movement.desiredAngle, pC->pEnemy->dir, 512);

		dx2 = (-rsin(pC->pEnemy->dir) * pC->movement.speed)>>14;
		dz2 = (-rcos(pC->pEnemy->dir) * pC->movement.speed)>>14;

		pC->pEnemy->vel.vx = dx2>>6;
		pC->pEnemy->vel.vz = dz2>>6;

		SETIFKEYUP(0, PAD_CIRCLE, STATE_GETTINGUP);
		break;

	case STATE_GETTINGUP:
		DB("Getting up from crawl...\n");
		if(pC->pEnemy->stateCtr >= 0)
			SETSTATE(STATE_WANDERING);

		break;

	case STATE_HIDING:
		// CPW: DB("Hiding\n");

		if(pC->pEnemy->stateCtr==0)
		{
			actorAnimate(pE->readData.pActor, WELLY_HIDE, 0, 0, 256, 0, 1024);
		}

//		DB("frame = %d\n", pE->readData.pActor->animation.animData[0].actualFrame);

		pE->vel.vx = 0;
		pE->vel.vz = 0;

// CPW: 		if(!(pReadPad->digital[0] & PAD_SQUARE))
		if(!(pReadPad->digital[0] & controlMaps[controlMap].hide))
		{
			SETSTATE(STATE_WANDERING);

			pC->pVTable = chickenVTable;
			actorAnimate(pE->readData.pActor, WELLY_UNHIDE, 0, 0, 256, 0, 1024);
		}

		break;

	case STATE_PUSHBUTTON:
		{
			int anim, div, cAnimSpeed, ang;
	
// CPW: Attempting, unsuccessfully, to stop character spinning
			pC->pEnemy->vel.vx = 0;
			pC->pEnemy->vel.vz = 0;

			VERIFY(pC->movement.pPushButton);

			if(pC->movement.pPushButton->flags & SCENICFLAG_IS_BUTTON)
			{
				cAnimSpeed = 512;
				div=20;
				anim=GINGER_PUSHBUTTON;
			}
			else
			{
				cAnimSpeed = 256;
				div=10;
				anim=GINGER_LEVERDOWN;
			}

			ang = pC->movement.pPushButton->ya;
			pC->movement.desiredAngle = (ang)&0xfff;

			if(pC->pEnemy->stateCtr==0)
			{
				pC->movement.gotThere=0;

				actorAnimate(pE->readData.pActor, GINGER_STRAFELEFT, 1, 0, 512, 0, 1024);

				pC->movement.desiredPos.vx = pC->movement.pPushButton->pActor->position.vx + (rsin(ang)/div);
				pC->movement.desiredPos.vz = pC->movement.pPushButton->pActor->position.vz + (rcos(ang)/div);
			}

			pC->pEnemy->dir = enemyTendTowards(pC->movement.desiredAngle, pC->pEnemy->dir, 400);

			if(VectorMoveTo2D(&pE->pos, &pC->movement.desiredPos, 20) && (pC->movement.gotThere==0))
			{
				pC->movement.gotThere=1;
				actorAnimate(pE->readData.pActor, anim, 0, 0, cAnimSpeed, 0, 1024);
			}

			if(pC->movement.gotThere==1)
			{
				if(actorReachedFrameRatio(pC->pEnemy->readData.pActor, anim, 1024) == ACTORFRAME_AT)
				{
					actorAnimate(pC->movement.pPushButton->pActor, PUSHBUTTON_IN, 0, 0, 512, 0, 1024);
				}

				if(actorReachedFrameRatio(pC->pEnemy->readData.pActor, anim, 1536) == ACTORFRAME_AT)
				{
					soundPlaySamplePanFade(pC->movement.pPushButton->pNoise, 255, 0, (VECTOR *)&(pC->pEnemy->pos));
				}

				if(actorNearEndOfAnimation(pC->pEnemy->readData.pActor, anim, 3))
				{
					// CPW: pushbutton has been operated
					
					if(pC->movement.pPushButton->state == SCENICSTATE_SWITCH_OFF)
						pC->movement.pPushButton->state = SCENICSTATE_SWITCH_ON;
					else
						pC->movement.pPushButton->state = SCENICSTATE_SWITCH_OFF;

					actorAnimate(pC->movement.pPushButton->pActor, PUSHBUTTON_OUT, 0, 0, 1024, 0, 1024);
					SETSTATE(STATE_WANDERING);
				}
			}
		}
		break;

	case STATE_PICKINGUP:
		pC->pEnemy->vel.vx = 0;
		pC->pEnemy->vel.vz = 0;

		if(pC->pEnemy->stateCtr==0)
		{
			DB("Starting to pick up\n");

			// CPW: soundPlaySamplePanFade(pC->pEnemy->readData.ppSamples[CHAR_SFX_PICKUPTALK],
			// CPW: 						255, 0, (VECTOR *)&(pC->pEnemy->pos));

			soundPlaySamplePanFade(mapData.pickupSample, 255, 0, (VECTOR *)&(pC->pEnemy->pos));


			actorFlushQueue(pE->readData.pActor);

			if(pickup_item == BROKEN_LADDER)
				actorAnimate(pE->readData.pActor, GINGER_PICKUPMEDIUM, 0, 0, 256, 0, 1024);
			else
				actorAnimate(pE->readData.pActor, GINGER_PICKUPLOW, 0, 0, 256, 0, 1024);

			pE->desiredDir = utilCalcAngle(pE->readData.pActor->position.vx - pPickupItem->pActor->position.vx, pE->readData.pActor->position.vz - pPickupItem->pActor->position.vz) & 0xfff;

/*
			pC->movement.anim = GINGER_PICKUP1;
			pC->movement.animflags = ANIM_DONTLOOP|ANIM_DONTBLEND;
			actorSetAnimSpeed(pE->readData.pActor, 256);
*/
		}
		else
		{
			int pickUpAnim;
			DB("Picking up ...\n");

			if(pickup_item == BROKEN_LADDER)
				pickUpAnim = GINGER_PICKUPMEDIUM;
			else
				pickUpAnim = GINGER_PICKUPLOW;

			pE->dir = enemyTendTowards(pE->desiredDir, pE->dir, 512);

// RFW - when I play the game, this never happens, so I've stuck the picking up code at the end
// tbd - what SHOULD happen, is that
// the "ReachedFrame" should do the "inventoryPickUpItem" call
// and the "NearEnd" should go to the inventory if the previous bit returned "INV_OK"

			if(actorReachedFrame(pC->pEnemy->readData.pActor, pickUpAnim, 10)==ACTORFRAME_AT)
			{

				int ok = 1;
				DB("Pick up done. item = %i\n",pickup_item);

				if((utilRandomInt(4) == 0) && (gameTextLang == LANG_UK))	// RFW: Don't play "what's this" pickup samples in euro
				{
					soundPlaySamplePanFade(	pC->pEnemy->readData.ppSamples[CHAR_SFX_PICKUPTALK],
											255, 0, (VECTOR *)&(pC->pEnemy->pos));
				}

				actorSetAnimSpeed(pE->readData.pActor, 0);

// oh, and you can't do it if the dog's awake, either
				if(pickup_item == TENNIS_RACKET)
				{
					ok = enemyDogTennisRacketPickup(currentInv->selected_item);
				}

				if(ok)
				{
					int result;		// BP: We want to test for more than just INV_OK

					result = inventoryPickUpItem(pickup_item);
					
					if(result==INV_OK)
					{

						if(pickup_item>GNOME)		// BP: if it's a piece of map
						{
							inventoryShowMapStart();		// New routine that shouldnt crash...
						}
						else
						{
							inventoryStart();
						}
					}
					else if(result==INV_NOSPACE)	// We cant carry this, we're full!
					{
						DB("No room in my inventory to carry that!\n");

						//SB - replaced PALMODE since DC has a 50/60 Hz switch! Please leave!!
						if(palMode)
							inv_messagetimer = 100;
						else
							inv_messagetimer = 120;

						inv_currentmessage = TEXTSTR(TEXTSTR_HELP_NO_ROOM);
					}
				}

				actorSetAnimSpeed(pE->readData.pActor, 256);
				SETSTATE(STATE_WANDERING);
			}
		}

		break;

#define RAT_CLING_LEFT	(100)
#define RAT_CLING_RIGHT	(101)

	case STATE_CLINGING:
		// CPW: Rats only

		DB("Clinging ...\n");

		if(pC->pEnemy->stateCtr == 0)	
		{
			ZEROVECTOR(&pC->pEnemy->vel);
			// CPW: pC->pEnemy->pos.vx = pC->pEnemy->ratClimbing.pScenic->pActor->position.vx;
			// CPW: pC->pEnemy->pos.vz = pC->pEnemy->ratClimbing.pScenic->pActor->position.vz + 300;

			// CPW: COPYVECTOR(&pC->pEnemy->pos, &pC->pEnemy->ratClimbing.pScenic->pActor->position);

			pC->pEnemy->pos.vx = pC->pEnemy->ratClimbing.pScenic->pActor->position.vx;
			pC->pEnemy->pos.vz = pC->pEnemy->ratClimbing.pScenic->pActor->position.vz;

			// CPW: pC->pEnemy->pos.vy+=2400;

			pC->pEnemy->pos.vx+=200;

			pC->pEnemy->dir = 1024;

			curtainsRatOnRope(pC, pC->pEnemy->ratClimbing.pScenic);
			actorAnimate(pE->readData.pActor, RAT_CLINGTOROPE, 0, 0, 256, 0, 512);
		}
		else
		{
// CPW: 			SETIFKEYDEB(0, PAD_CROSS, STATE_WANDERING);
// CPW: 			SETIFKEYDEB(0, PAD_SQUARE, STATE_WANDERING);

			SETIFKEYDEB(0, controlMaps[controlMap].jump, STATE_WANDERING);
			SETIFKEYDEB(0, controlMaps[controlMap].action, STATE_WANDERING);
		}

/*
		{
			int result = curtainsEvaluatePuzzle();
			static VOID_SUBROUTINE oldCycleRoutine = NULL;
			CHARACTER *pLeftRat = curtainsGetRatOnRope(0), *pRightRat = curtainsGetRatOnRope(1);
			static int result2 = CURTAINS_PUZZLE_NOTHINGHAPPENS;

			if(result == CURTAINS_PUZZLE_SUCCESS)
			{
				// CPW: VERIFY(pLeftRat);
				// CPW: VERIFY(pRightRat);

				result2 = result;
				DB("Playing WORKS\n");
				startFMA("WORKS");
			}

			if(result == CURTAINS_PUZZLE_CLOSEBUTNOCIGAR)
			{
				result2 = result;

				if((!pLeftRat) && (pRightRat && pRightRat->pEnemy->readData.nameCRC == CRC_NICK))
				{
					DB("Playing FAIL03\n");
					startFMA("FAIL03");
				}

				if((!pLeftRat) && (pRightRat && pRightRat->pEnemy->readData.nameCRC == CRC_FETCHER))
				{
					DB("Playing FAIL02\n");
					startFMA("FAIL02");
				}

				if(	(pLeftRat && pLeftRat->pEnemy->readData.nameCRC == CRC_NICK)
					&& (pRightRat && pRightRat->pEnemy->readData.nameCRC == CRC_FETCHER))
				{
					DB("Playing FAIL01\n");
					startFMA("FAIL01");
				}
			}

			DB("cycle_routine = %p, FMAtestCycle = %p\n", cycle_routine, FMAtestCycle);

			if(((result2 == CURTAINS_PUZZLE_SUCCESS) || (result2 == CURTAINS_PUZZLE_CLOSEBUTNOCIGAR)) && (cycle_routine != FMAtestCycle))
			{
				POSMARK *pMark;

				DB("Setting to STATE_WANDERING\n");

				// CPW: force rats to a different state. Should normally use SETSTATE()
				if(pLeftRat)
				{
					pLeftRat->pEnemy->state = STATE_WANDERING;
					pLeftRat->pEnemy->stateCtr = 0;
					curtainsSetRatOnRope(0, NULL);
				}
	
				if(pRightRat)
				{
					pRightRat->pEnemy->state = STATE_WANDERING;
					pRightRat->pEnemy->stateCtr = 0;
					curtainsSetRatOnRope(1, NULL);
				}

				// CPW: put the rat on the windowsill
				pMark = levinfoFindMarker(123);
				VERIFY(pMark);

				pLeftRat->pEnemy->pos.vx = pMark->x;
				pLeftRat->pEnemy->pos.vy = pMark->y;
				pLeftRat->pEnemy->pos.vz = pMark->z;

				characterMakeMain(pLeftRat);

			}


			oldCycleRoutine = cycle_routine;
		}
	*/

	}


	// only attempt to update if animation has changed OR we've finished interpolating from the last time
/*
	if((pC->movement.oldanim!=pC->movement.anim) || (pC->movement.stillblending==TRUE))
	{
		int result;

		int anim = pC->movement.anim;
		int blend = (pC->movement.animflags & ANIM_DONTBLEND)?0:1024;
		int loop = (pC->movement.animflags & ANIM_DONTLOOP)?0:1;

		int queue = 0;

		pC->movement.animspeed = pE->readData.pActor->animation.animData[0].speed;
		result = actorAnimate(pC->pEnemy->readData.pActor, pC->movement.anim, loop, queue, pC->movement.animspeed, 0, blend);
		pC->movement.animflags = 0;

		DB("Changing anim to %d, blendCounter = %d\n", pC->movement.anim, pC->pEnemy->readData.pActor->animation.blendCounter);

		if(result==ANIMATE_STILLBLENDING)
		{
			DB("Couldn't change animation! Still blending from last one.\n");
			pC->movement.stillblending=TRUE;
		}
		else
		{
			pC->movement.stillblending=FALSE;
		}
	}
*/

	// CPW: DB("vx = %d, vz = %d\n", pC->movement.pPushPlat->move.realVel.vx, pC->movement.pPushPlat->move.realVel.vz);
	if (pC->movement.pPushPlat)
	{
		if((pC->movement.pPushPlat->move.realVel.vx == 0) && (pC->movement.pPushPlat->move.realVel.vz == 0) && (pC->movement.shoulderPush < 4))
		{
			if(pC->movement.pPushPlat->noisy.pSample && (pC->movement.pPushPlat->noisy.playing == TRUE))
			{
				pC->movement.pPushPlat->noisy.playing = FALSE;
				soundStopSample(pC->movement.pPushPlat->noisy.pSample);
			}
		}
	}

	if(pC->movement.shoulderPush>0)
		pC->movement.shoulderPush--;

	if(pC->movement.againstWall>0)
		pC->movement.againstWall--;

	pC->movement.oldanim = pC->movement.anim;

	return 0;
}

void chickenPressingAgainstWall(COLLDATA *pCollData, VECTOR *pNormal)
{
	int copyOfOldState;
	CHARACTER *pC = characterVars.pMainChar;

// CPW: 	if(pC->pEnemy->readData.pActor->animation.animData[0].segment == GINGER_RUN)
// CPW: 		return;

	if(pC->movement.shoulderPush)
		return;

	if(camVars.area == INSIDE)
	{
		DB("Can't strafe against wall when inside\n");
		return;
	}

	pC->movement.againstWallAng = utilCalcAngle(pNormal->vx, pNormal->vz)&4095;

	SETSTATE(STATE_AGAINSTWALL);
	pC->movement.againstWall=8;
}

void chickenPressingAgainstPlatform(COLLDATA *pCollData, PLATFORMWORK *pPlat, COLLBOX *pCollBox, VECTOR *pPushDir)
{
	;
}

int chickenReadyToPushPlatform(COLLDATA *pCollData, PLATFORMWORK *pPlat, COLLBOX *pCollBox, VECTOR *pPushDir)
{
	int copyOfOldState;
	CHARACTER *pC = characterVars.pMainChar;

	if((&pC->pEnemy->coll.collision)!=pCollData)
		return 0;

	// CPW: DB("Ready to push platform?\n");

	if(pC->movement.action)
	{
		DB("Shoulder pushing\n");
		COPYVECTOR(&pC->movement.pushDir, pPushDir);

// This is Rocky/Nick/Fetcher's push-angle.
// Ginger's 90-degree offset is dealt with in STATE_SHOULDERPUSHING
		pC->movement.againstWallAng = (utilCalcAngle(pPushDir->vx, pPushDir->vz) + 2048) & 4095;

		SETSTATE(STATE_SHOULDERPUSHING);
		pC->movement.pPushPlat = pPlat;

/*
		if(pPlat->noisy.pSample && (pPlat->noisy.playing == FALSE))
		{
			pPlat->noisy.playing = TRUE;
			soundPlaySamplePanFade(pPlat->noisy.pSample, 255, 0, (VECTOR *)&(pPlat->cumPos));
		}
*/

		pC->movement.shoulderPush=8;
		return 1;
	}

	return 0;
}


