
#include "global.h"
#include "loadlnd.h"
#include "puzzles.h"
#include "overlays.h"

#include "platform.h"
#include "bff_load.h"

#include "main.h"

#include "collide.h"
#include "inventory.h"
#include "enemies.h"
#include "scenics.h"
#include "charactr.h"
#include "map_play.h"
#include "maths.h"

#include "camera.h"
#include "card.h"
#include "sound.h"

#include "debris.h"
#include "deth.h"
#include "dualshock.h"

//extern ACTOR		*testchar_actor;


//extern CHARACTERVARSTR characterVars;


PUZZLEVARSSTR PuzzleVars;
VENT	*vents;
int		n_vents;

// CPW: keep track of which sound channels are playing
SfxSampleType *pChan[32];

// =================================================================================
// Unlike Glover. in Chicken Run there's no puzzle parsing code. It's all ready-converted
// into puzzle structures with PSX coordinates in the BFF file.

void puzzleInitLevel()
{
	memset(&PuzzleVars,0,sizeof(PUZZLEVARSSTR));
	memset(pChan, 0, 32 * sizeof(SfxSampleType *));
}

// ===================================================================================
//                BFF LEV Stuff
// ===================================================================================

// Find a tagged object, and tell the caller what kind of object it is
// It is advisable to cache the returned pointer & type rather than repeatedly calling this routine.
void *puzzleFindObject(int tag, int *type_ret)
{
	void * target;
	FMA_WORLD2 *world;

#ifdef DEBUG
	if(!tag)
	{
// TBD - put a similar check into CR_LEV
		DB("\n--- A puzzle is checking or changing an object with TAG=0 ---\n");
		DB("(This is not a good idea)\n");
		ASSERT(tag);
	}
#endif
	target = (void *)texanimFind(tag);
	if(target)
	{
		*type_ret = TARGET_IS_TEXANIM;
		return target;
	}
	target = (void *)platformFindPlatform(tag);
	if(target)
	{
		*type_ret = TARGET_IS_PLATFORM;
		return target;
	}

	target = (void *)EXLIGHT_FindLight(tag);
	if(target)
	{
		*type_ret = TARGET_IS_LIGHT;
		return target;
	}

	target = (void *)scenicFind(tag);
	if(target)
	{
		*type_ret = TARGET_IS_SCENIC;
		return target;
	}

	world = (void *)BFF_FindObject(BFF_FMA_WORLD2_ID,utilStr2CRC("LNDMAP"));
	target = mapdrawFindInstance(world, tag);
	if(target)
	{
		*type_ret = TARGET_IS_LANDSCAPE;
		return target;
	}

	target = (void *)enemyFind(tag);
	if(target)
	{
		*type_ret = TARGET_IS_ENEMY;
		return target;
	}

	target = (void *)ventFind(tag);
	if(target)
	{
		//DB("looking for vent\n");
		*type_ret = TARGET_IS_VENT;
		return target;
	}

// ... and enemies, and vents, etc etc etc

	{
		// RFW - Changed to an assert, before we start getting obscure problems
		DB("\nOBJECT tag %d not found for puzzle\n",tag);
		ASSERT(0);
	}
	return NULL;
}


void puzzleLinkBFF(PUZZ_LIST *lev)
{
#define INTLEV ((int)lev)

	PUZZLE *puzzle;
	int i;
	ACTION *action;
	CONDITION *condition;

// Offset-to-Pointer corrections in the level structure
	if(lev->puzzles)
		lev->puzzles = ADD2POINTER(lev->puzzles,INTLEV);

	PuzzleVars.puzzles = lev->puzzles;

// Correct the puzzles, conditions, actions
	for(puzzle = lev->puzzles; puzzle; puzzle = puzzle->next)
	{
		if(puzzle->next)
			puzzle->next = ADD2POINTER(puzzle->next,INTLEV);

		puzzle->conditions =  ADD2POINTER(puzzle->conditions,INTLEV);
		puzzle->actions =  ADD2POINTER(puzzle->actions,INTLEV);

// Do any "tag->pointer" lookups that the conditions need to do
		for(i = 0,condition = puzzle->conditions; i < puzzle->numconditions; i++,condition++)
		{
//			DB("Condition %d at  &%8x\n",condition->type,(int)condition - (int)lev);
			switch(condition->type)
			{
			case CONDITION_OBJECT_ENABLED:
			case CONDITION_AT_POINT:
			case CONDITION_SWITCH_STATE:
			case CONDITION_SWITCH_JUST_ON:
			case CONDITION_SWITCH_JUST_OFF:
//				DB("looking for object \n");
				condition->data.objparam.target = puzzleFindObject(condition->data.objparam.tag, &condition->data.objparam.target_type);
				break;
			case CONDITION_NEAR_POINT:
				{
					POSMARK *mark;

					mark = levinfoFindMarker(condition->data.nearpoint.posmark_tag);
					ASSERT(mark);
					condition->data.nearpoint.posx = mark->x;
					condition->data.nearpoint.posy = mark->y;
					condition->data.nearpoint.posz = mark->z;
					break;
				}

			}
		}


// Do any "tag->pointer" lookups that the actions need to do
		for(i = 0,action = puzzle->actions; i < puzzle->numactions; i++,action++)
		{
//			DB("Action %d at &%8x\n",action->type, (int)action - (int)lev);
			switch(action->type)
			{
				case ACTION_SET_PLATFORM_SPEED:
				case ACTION_SET_PLATFORM_POINT:
				case ACTION_SET_PLATFORM_SPINSPEED:
				case ACTION_SET_TEXANIM_SPEED:
				case ACTION_SET_CONVEYOR_SPEED:
				case ACTION_SET_EXISTENCE:
				case ACTION_SET_SORTDEPTH:
				case ACTION_SET_TERRAIN:
				case ACTION_LIGHT_ALERT:
				case ACTION_SET_SWITCH_STATE:
					action->data.setobjparam.target = puzzleFindObject(action->data.setobjparam.tag, &action->data.setobjparam.target_type);
					break;
				case ACTION_SET_SCENIC_ANIM:
					action->data.scenicanim.target =  puzzleFindObject(action->data.scenicanim.tag, &action->data.scenicanim.target_type);
//					action->data.scenicanim.target = scenicFind(action->data.scenicanim.tag);
					break;
				case ACTION_SET_LEAD_ACTION:
					action->data.lead.target = levinfoFindMarker(action->data.lead.posmark_tag);
					ASSERT(action->data.lead.target);
					break;
				case ACTION_SET_SAMPLE_STATE:
				{
					int type;
					void *p;
					VECTOR *pPos;

					ASSERT(action->data.playsample.crc);
					action->data.playsample.crc = (int)sfxFindSampleInAllBanksCRC(action->data.playsample.crc);
					ASSERT(action->data.playsample.crc);
					
					p = puzzleFindObject(action->data.playsample.tag, &type);

					switch(type)
					{
/*					case TARGET_IS_SCENIC:
						pPos = (VECTOR *)&(((SCENIC *)p)->pActor->position);

						DB("TARGET_IS_SCENIC: Scenic pos = %d, %d, %d\n", pPos->vx, pPos->vy, pPos->vz);
						break;
*/
					case TARGET_IS_PLATFORM:
						pPos = (VECTOR *)&(((PLATFORMWORK *)p)->cumPos);
						break;
					case TARGET_IS_LANDSCAPE:
						pPos = (VECTOR *)&(((WORLD_MESH_INSTANCE *)p)->posx);
						break;
					default:
						pPos = 0;
					}

					action->data.playsample.tag = (int)pPos;
				}
			}
			action->timer = -1;

		}
	}
// Coming soon, enemies, platforms, and all that jazz...
}



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


int puzzleConditionMet(CONDITION *cond)
{
	switch(cond->type)
	{
		case CONDITION_PLAYER_IN_BOX:
		{
			VECTOR temp;
			VECTOR *charpos;


#ifdef DEBUG

			switch(overlay_show_collision)
			{
			case DEBUG_OVERLAY_PUZZLE_BOXES:
				overlayShowBigBox(
					cond->data.in_box.posx,
					cond->data.in_box.posx + cond->data.in_box.sizex,
					cond->data.in_box.posy,
					cond->data.in_box.posy + cond->data.in_box.sizey,
					cond->data.in_box.posz,
					cond->data.in_box.posz + cond->data.in_box.sizez,
					0x0040ff
					);

				break;
			}
#endif


//			printf("box pos %d %d %d\n",cond->data.in_box.pos.vx,cond->data.in_box.pos.vy,cond->data.in_box.pos.vz);

			if(!characterVars.pMainChar)
				return 0;

			charpos = &characterVars.pMainChar->pEnemy->pos;

			temp.vx = charpos->vx - cond->data.in_box.posx;
			temp.vy = charpos->vy - cond->data.in_box.posy;
			temp.vz = charpos->vz - cond->data.in_box.posz;

			if( temp.vx >= 0 && temp.vx < cond->data.in_box.sizex
			 && temp.vy >= 0 && temp.vy < cond->data.in_box.sizey
			 && temp.vz >= 0 && temp.vz < cond->data.in_box.sizez
			 )
			{
//				PuzzleVars.hand_trigger =1;
//				DB("CONDITION_HAND_IN_BOX\n");
				return (cond->data.in_box.yes);
			}
			return(!cond->data.in_box.yes);
		}

		case CONDITION_NEAR_POINT:
		{
			VECTOR temp;
			VECTOR *charpos;
#ifdef DEBUG
			switch(overlay_show_collision)
			{
			case DEBUG_OVERLAY_PUZZLE_BOXES:
// If we're showing the platform collision, show the puzzle-boxes, too.
				overlayShowSphere(
					cond->data.nearpoint.posx,
					cond->data.nearpoint.posy,
					cond->data.nearpoint.posz,
					cond->data.nearpoint.range,
					0x0040ff);
				break;
			}
#endif
			charpos = &characterVars.pMainChar->pEnemy->pos;
			temp.vx = charpos->vx - cond->data.nearpoint.posx;
			temp.vy = charpos->vy - cond->data.nearpoint.posy;
			temp.vz = charpos->vz - cond->data.nearpoint.posz;
			if(    temp.vx >= - cond->data.nearpoint.range
				&& temp.vx <=   cond->data.nearpoint.range
				&& temp.vy >= - cond->data.nearpoint.range
				&& temp.vy <=   cond->data.nearpoint.range
				&& temp.vz >= - cond->data.nearpoint.range
				&& temp.vz <=   cond->data.nearpoint.range
				)
			{
				long r;
				r = utilSqrt(temp.vx * temp.vx + temp.vy * temp.vy + temp.vz * temp.vz) >> 16;
				if (r <=    cond->data.nearpoint.range)
				{
					return (cond->data.nearpoint.yes);
				}
				else
				{
					return(!cond->data.nearpoint.yes);
				}
			}
			else
			{
				return(!cond->data.nearpoint.yes);
			}
		}

		case CONDITION_BUTTON_PRESSED:
		{
			int but;
			int mask;

			mask = cond->data.button_pressed.button;

			if(mask == (1<<16))	// "Action"
			{
				mask = controlMaps[controlMap].action;
			}

			switch(cond->data.button_pressed.yesno)
			{
			case 0:
				but = padData.digital[cond->data.button_pressed.pad];
				but = ~but;
				break;

			case 2:
				but =padData.debounce[cond->data.button_pressed.pad];
				break;
			case 1:
			default:
				but = padData.digital[cond->data.button_pressed.pad];
				break;
			}
			but &= mask;
			return(but != 0);
		}

		case CONDITION_OBJECT_ENABLED:
			switch(cond->data.objparam.target_type)
			{
			case TARGET_IS_PLATFORM:
				return (platformIsEnabled((PLATFORMWORK *)cond->data.objparam.target) == cond->data.objparam.value);
			case TARGET_IS_LANDSCAPE:
				return (mapdrawIsEnabled((WORLD_MESH_INSTANCE *)cond->data.objparam.target) == cond->data.objparam.value);
			case TARGET_IS_LIGHT:
				return (EXLIGHT_IsLightEnabled((EXLIGHT_TYPE *)cond->data.objparam.target) == cond->data.objparam.value);
			case TARGET_IS_SCENIC:
				return (scenicIsEnabled((SCENIC *)cond->data.objparam.target) == cond->data.objparam.value);
			case TARGET_IS_ENEMY:
				return (enemyIsEnabled((ENEMY *)cond->data.objparam.target) == cond->data.objparam.value);

			}

		case CONDITION_FROM_TIME:
			return(loop_ticker2 > cond->data.objparam.value);

		case CONDITION_TASK_COMPLETED:
			ASSERT(cond->data.task.what < MAX_TASKS);
			return(gamePlayData->tasks_done[cond->data.task.what] == cond->data.task.yesno);

		case CONDITION_SWITCH_STATE:
			{
				SCENIC *pS = (SCENIC *)(cond->data.objparam.target);
				// CPW: DB("state of tim's puzzle is %d, and the value being tested for is %d\n", pS->state, cond->data.objparam.value);
				return(pS->state == cond->data.objparam.value);
			}
		case CONDITION_AT_POINT:
			switch(cond->data.objparam.target_type)
			{
			case TARGET_IS_PLATFORM:
			{
				PLATFORMWORK *ptr = (void *)cond->data.objparam.target;

//				DB("plat %d is at %d. jap %d, wait %d\n",ptr->def->tag,ptr->move.point, ptr->move.jap, ptr->move.waitTimer);
				if(ptr->move.point == cond->data.objparam.value)
				{
					if(ptr->move.jap || ptr->move.waitTimer)	// Either just arrived, or waiting
					{
						return YES;
					}
				}
				if(ptr->move.speed == 0)
				{
					if(platformAtPoint(ptr,cond->data.objparam.value))
					{
						return YES;
					}
				}
				return NO;
			}
			case TARGET_IS_ENEMY:
			{
				ENEMY *pE = (void *)cond->data.objparam.target;
				// From & To are set to be the next path segment immediately on arrival at the point
				if((pE->state == STATE_WANDERING || pE->state == STATE_WAITING) && pE->points.pointFrom  == cond->data.objparam.value)
				{
					if(pE->points.timer)	// Either just arrived (which sets timer to 1 even for no pause), or waiting
					{
						return YES;
					}
				}
				return NO;
			}
			}
		case CONDITION_SWITCH_JUST_ON:
			{
				SCENIC *pS = (SCENIC *)(cond->data.objparam.target);
				int result;

				if (pS)
				{
					if((pS->state == SCENICSTATE_SWITCH_ON) && (pS->oldState == SCENICSTATE_SWITCH_OFF))
					{	
						result = YES;
						DB("CONDITION_SWITCH_JUST_ON\n");
					}
					else
						result = NO;
				} else {
					result = NO;
				} // end-if-else

				return(result);
			}
		case CONDITION_SWITCH_JUST_OFF:
			{
				SCENIC *pS = (SCENIC *)(cond->data.objparam.target);
				int result;

				if((pS->state == SCENICSTATE_SWITCH_OFF) && (pS->oldState == SCENICSTATE_SWITCH_ON))
				{
					result = YES;
					DB("CONDITION_SWITCH_JUST_OFF\n");
				}
				else
					result = NO;

				return(result);
			}

		case CONDITION_VARIABLE_VALUE:
			{
				int var;
				int n;
				int t;
				var = cond->data.varval.variable_no;

				ASSERT(var < MAX_PUZZLE_VARS);
				n = PuzzleVars.variables[var];
				t = cond->data.varval.value;

				switch(cond->data.varval.what)
				{
				case CONDVAR_NEQUAL:
					return(n != t);
				case CONDVAR_GREATER:
					return(n > t);
				case CONDVAR_LESS:
					return (n < t);
				default:
				case CONDVAR_EQUAL:
					return (n == t);

				}
			}

		case CONDITION_CHECK_STATUS:
		{
			int var;
			int val;
			var = cond->data.varval.variable_no;
			val = cond->data.varval.value;
			switch(var)
			{
			case COND_CHECKSTATUS_CHICKENCONTROL:
				if(val)
					return(enemyControllingLeadFlag != 0);
				else
					return(enemyControllingLeadFlag == 0);
			default:
				return 0;

			}

		}

		case CONDITION_STARTED_AT:
//			DB("level start mark %d, testing vs %d\n",level_start_marker,cond->data.objparam.value);
			return (level_start_marker == cond->data.objparam.value);

		case CONDITION_RANDOM:
		{
			int range = cond->data.varval.value;

			if(range < 2)
			{
				DB("WARNING: Silly random range (%d) will always yield a TRUE result\n", range);
				return 1;
			}
			else
				return(utilRandomInt(range - 1) == 0);
			// CPW: range - 1 is because, oddly, utilRandomInt returns 0 to range INCLUSIVE  :|
		}
	}
	return NO;
}

// Fred sez:-
// I've made a note of the "parameters that the parser reads in" alongside the un-filled-in puzzles...
// Vectors are shorts (ie, not scaled to 4096). Not all the numbers in the LND file are used:- the
// "act->data" union contains all those that are.
// ("delay", used in most puzzles, is info for the puzzle parser, rather than anything else)
void puzzleDoAction(PUZZLE *puzzle, ACTION *act)
{
//	DYNCOLLBOX *pBox;

	switch(act->type)
	{
// ----- Camera puzzle actions -----
// (note, camera cameo actions are another thing again...)



// Set the camera position to a vector for this frame only
// eg - carnival 1 & 2 , when in a box area outside some of the doors
		case ACTION_CAMERA_SNAPTO:
			//DB("cam case 1\n");
			camVars.flags|=FIXEDPOSSNAP;
			camVars.camMovePosSnap.vx=act->data.vector.posx;
			camVars.camMovePosSnap.vy=act->data.vector.posy;
			camVars.camMovePosSnap.vz=act->data.vector.posz;
			camVars.posCaseOnTime=act->data.vector.extra;
			break;


// Set the camera position for this frame only (locking
// eg - at1, after hitting the button, for a short amount of time
// (the time comes from the fact that a "set camera pos" is *always* follwed by a "look at point")


		case ACTION_CAMERA_MOVETO:
			//DB("cam case 2\n");
			camVars.flags|=FIXEDPOS;
			camVars.camMovePos.vx=act->data.vector.posx;
			camVars.camMovePos.vy=act->data.vector.posy;
			camVars.camMovePos.vz=act->data.vector.posz;
			if ( act->data.vector.extra ) camVars.posCaseOnTime=act->data.vector.extra; // else can overwright 
			break;

		case ACTION_CAMERA_LOOK_MOVETO:
			camVars.flags|=FIXEDLOOK;
			camVars.viewMovePos.vx=act->data.vector.posx;
			camVars.viewMovePos.vy=act->data.vector.posy;
			camVars.viewMovePos.vz=act->data.vector.posz;
			if ( act->data.vector.extra ) camVars.lookCaseOnTime=act->data.vector.extra;
			break;

		case ACTION_CAMERA_LOOK_SNAPTO:
			camVars.flags|=FIXEDLOOKSNAP;
			camVars.viewMovePosSnap.vx=act->data.vector.posx;
			camVars.viewMovePosSnap.vy=act->data.vector.posy;
			camVars.viewMovePosSnap.vz=act->data.vector.posz;
			camVars.lookCaseOnTime=act->data.vector.extra;
			break;

		case ACTION_SET_ADDITIONAL_DISTANCE:
			camVars.flags|=CHANGEDISTANCE;
			camVars.caseDistance=act->data.fourshorts.values[0];
			break;

		case ACTION_SET_ADDITIONAL_HEIGHT:
			camVars.flags|=CHANGEHEIGHT;
			camVars.caseHeight=act->data.fourshorts.values[0];
			break;

		case ACTION_SET_CAMERA_ANGLE:
			camVars.flags|=FIXEDROT;
			camVars.fixedAngle=act->data.fourshorts.values[0];
			break;

		case ACTION_SET_CAMERA_ANGLE_START:
			if (loop_ticker2>20) break;	// only want these cases to be active at the start of a level
			//DB("start cam angle\n");
			camVars.flags|=LEVELSTARTFIXEDROT;
			camVars.fixedAngle=act->data.fourshorts.values[0];
			break;

		case ACTION_SET_RAILCAM_ALIGNED:
			camVars.railCamPoint.vx=act->data.vector.posx;
			camVars.railCamPoint.vy=act->data.vector.posy;
			camVars.railCamPoint.vz=act->data.vector.posz;
			camVars.railCamAxis=act->data.vector.extra&31;
			camVars.railCamDirection=act->data.vector.extra>>8;
			changeCamMode(RAILCAMAXIS);	// Note the camera needs to to told what mode to run in
			break;

		case ACTION_SET_FOLLOW_CAM:
			if (act->data.fourshorts.values[0]) camVars.area=OUTSIDE;
			else camVars.area=INSIDE;
			changeCamMode(ROTCAM);	// Note the camera needs to to told what mode to run in
			break;

		case ACTION_SET_HUT_CAM:
			changeCamMode(HUTCAM);
			break;	// gets its conditions from levinfo

		case ACTION_SET_CAMERA_SWEEP:
			//DB("setting sweep on\n");
			camVars.sweepNum=act->data.fourshorts.values[0];
			camVars.sweepFlag=TRUE;
			break;

		case ACTION_SET_RAILCAM_NONALIGNED:
			if (camVars.camType==RAILCAM) break;// only want to set it if its not in that mode else expect problems
			camVars.currentRailCamNum=act->data.fourshorts.values[0];
			camVars.railCamDirection=act->data.fourshorts.values[1];
			camVars.railTargetRail=act->data.fourshorts.values[2];
			camVars.currentRailPoint=act->data.fourshorts.values[3];
			camVars.currentRailDist=0;
			DB("startting none aligned rail cam\n");
			changeCamMode(RAILCAM);
			//camVars.justStartedTimer=0;
			//camVars.timer=0;
			//camVars.posChange=TRUE;
			//camVars.camTypeChanged=FALSE;
			break;
// Note - we now do named variables with crc's
// (zeroed at level-start time, but wth?)
		case ACTION_SET_VARIABLE:
		{
			int i;
			i = act->data.setvar.variable_no;

			ASSERT(i < MAX_PUZZLE_VARS);
			PuzzleVars.variables[i] = act->data.setvar.value;
			if ( (levinfo->level_number==LEVNUMBER_A2_TOOLSHED_D) && (PuzzleVars.variables[9] == 10) && (gamePlayData->inventory_item_status[FIREWORKS]==INV_STATUS_NOTCOLLECTED) )
			{
				PuzzleVars.variables[9]=0;
				if (!camVars.tweedySpecial)
				{
					camVars.tweedySpecial=1;
				}
			}
			if (levinfo->level_number==LEVNUMBER_A2_COGCHASE)
			{
				if (PuzzleVars.variables[3]) cogChaseGravy();
			}
			break;
		}

/*
		case ACTION_SET_SPEED:
			switch(act->data.setobjparam.target_type)
			{
				case TARGET_IS_TEXANIM:
					texanimSetSpeed(act->data.setobjparam.target, act->data.setobjparam.value);
					break;
				case TARGET_IS_PLATFORM:
// not in yet. tbd
					break;
			}
			break;
*/
		case ACTION_SET_TERRAIN:
			switch(act->data.setobjparam.target_type)
			{
				case TARGET_IS_PLATFORM:
					platformSetTerrain(act->data.setobjparam.target, act->data.setobjparam.value);
					break;
			}
			break;
		case ACTION_SET_PLATFORM_SPEED:
			switch(act->data.setobjparam.target_type)
			{
				case TARGET_IS_PLATFORM:
					platformSetSpeed(act->data.setobjparam.target, act->data.setobjparam.value);
					break;
			}
			break;

		case ACTION_SET_PLATFORM_POINT:
		{
			int p;
			PLATFORMWORK *platform;

			ASSERT(act->data.setobjparam.target_type == TARGET_IS_PLATFORM);
			platform = (PLATFORMWORK *)act->data.setobjparam.target;
			p = act->data.setobjparam.value;

			platform->move.point = p;
			platform->move.waitTimer = platform->def->points[platform->move.point].wait;
			platform->pos.vx = platform->def->points[p].posx;
			platform->pos.vy = platform->def->points[p].posy;
			platform->pos.vz = platform->def->points[p].posz;
			break;
		}

		case ACTION_SET_PLATFORM_SPINSPEED:
			switch(act->data.setobjparam.target_type)
			{
				case TARGET_IS_PLATFORM:
					platformSetRotSpeed(act->data.setobjparam.target, act->data.setobjparam.value);
					break;
			}
			break;

		case ACTION_SET_TEXANIM_SPEED:
			switch(act->data.setobjparam.target_type)
			{
				case TARGET_IS_TEXANIM:
					texanimSetSpeed(act->data.setobjparam.target, act->data.setobjparam.value);
					break;
			}
			break;
		case ACTION_SET_CONVEYOR_SPEED:
			switch(act->data.setobjparam.target_type)
			{
				case TARGET_IS_PLATFORM:
					platformSetConveyorSpeed(act->data.setobjparam.target, act->data.setobjparam.value);
					break;
			}
			break;

		case ACTION_TASK_COMPLETED:
			ASSERT(act->data.task.what < MAX_TASKS);
			gamePlayData->tasks_done[act->data.task.what] = act->data.task.yesno;
			break;
		
		case ACTION_SET_SCENIC_ANIM:
			switch(act->data.scenicanim.target_type)
			{
				case TARGET_IS_SCENIC:
					scenicSetAnim(act->data.scenicanim.target, act->data.scenicanim.anim, act->data.scenicanim.flags);
					break;
				case TARGET_IS_ENEMY:
					enemySetAnim(act->data.scenicanim.target, act->data.scenicanim.anim, act->data.scenicanim.flags);
					break;
			}
			break;

		case ACTION_SET_EXISTENCE:
			switch(act->data.setobjparam.target_type)
			{
				case TARGET_IS_PLATFORM:
					platformEnable( (PLATFORMWORK *)(act->data.setobjparam.target), act->data.setobjparam.value);
					break;
				case TARGET_IS_LANDSCAPE:
					if ( (levinfo->level_number==LEVNUMBER_A2_EGGROOM) || (levinfo->level_number==LEVNUMBER_A4_EGGROOM))
					{
						//DB("params %d \n",act->data.setobjparam.value);
						
						if (camVars.flipFlop && !camVars.timeTillNextChange)
						{
							camVars.flipFlop=0;
							camVars.timeTillNextChange=30;
						}
						else if (!camVars.timeTillNextChange)
						{
							camVars.flipFlop=1;
							camVars.timeTillNextChange=30;
						}
						if (!camVars.tweedySpecial && camVars.flipFlop)
						{
							//DB("setting cam for tweedy special\n");
							camVars.tweedySpecial=1;
						}
					}
					mapdrawEnable( (WORLD_MESH_INSTANCE *)(act->data.setobjparam.target), act->data.setobjparam.value);
					break;
				case TARGET_IS_LIGHT:
					EXLIGHT_EnableLight((EXLIGHT_TYPE *)(act->data.setobjparam.target),act->data.setobjparam.value);
					break;
				case TARGET_IS_SCENIC:
					scenicEnable((SCENIC *)(act->data.setobjparam.target),act->data.setobjparam.value);
					break;
				case TARGET_IS_ENEMY:
					enemyEnable((ENEMY *)(act->data.setobjparam.target),act->data.setobjparam.value);
					break;

				case TARGET_IS_VENT:
					//DB("setting vent to %d\n",act->data.setobjparam.value);
					ventEnable((VENT *)(act->data.setobjparam.target),act->data.setobjparam.value);
					break;
			}
			break;

		case ACTION_SET_SORTDEPTH:
			switch(act->data.setobjparam.target_type)
			{
				case TARGET_IS_PLATFORM:
					platformSetDepth( (PLATFORMWORK *)(act->data.setobjparam.target), act->data.setobjparam.value);
					break;
				case TARGET_IS_LANDSCAPE:
					mapdrawSetDepth( (WORLD_MESH_INSTANCE *)(act->data.setobjparam.target), act->data.setobjparam.value);
					break;

			}
			break;

		case ACTION_GOTO_LEVEL:
		{
			printf("Go to level %s, position marker %d\n",act->data.gotolevel.level_name,act->data.gotolevel.position_marker_tag);
//			act->data.gotolevel.level_name[12];	// Coo, this isn't a CRC, for fairly obvious reasons
//			act->data.gotolevel.position_marker_tag;

			if((!deathDying) && (!mapplay_leave_level_flag) && (characterGetMainCharacter()->pEnemy->state == STATE_WANDERING))	// double check, coz deathdying is reset on the very last frame
			{
				strcpy(&mapplay_next_level[0],act->data.gotolevel.level_name);
				mapplay_next_marker = act->data.gotolevel.position_marker_tag;
				mapplay_leave_level_flag = 1;
			}
			break;
		}
		case ACTION_LIGHT_ALERT:
			switch(act->data.setobjparam.target_type)
			{
				case TARGET_IS_LIGHT:
					EXLIGHT_AlertLight(act->data.setobjparam.target, act->data.setobjparam.value);
					break;
				case TARGET_IS_ENEMY:
					enemyAlert((ENEMY *)act->data.setobjparam.target, act->data.setobjparam.value);
					break;
			}
			break;

		case ACTION_OBJECT_USABLE:
			{
				if(act->data.usable.mechanism == USEOBJECT_MECHANISM_EQUIPPED)
				{
					// Really, we want the auto mechanism to run as a "just" in box
					// (With the X-triggered mechanism as a separate, "always", puzzle)
					int triggered;

					//triggered = inventoryUseObject(currentInv->selected_item,1);

					FMAplayCheck(0);
					triggered=0;

					if(!triggered)
					{
						PuzzleVars.usable_object_area = 1;	// You can still press X if you wanna
					}
				}
				else
				{
					PuzzleVars.usable_object_area = 1;	// make it so you gotta press "X"
					// CPW: FMAplayCheck();
				}
			}
			break;

		case ACTION_SET_SWITCH_STATE:
			{
				SCENIC *pS = (SCENIC *)(act->data.setobjparam.target);

				if(act->data.setobjparam.value)
				{
					pS->oldState = pS->state;
					pS->state = SCENICSTATE_SWITCH_ON;
				}
				else
				{
					pS->oldState = pS->state;
					pS->state = SCENICSTATE_SWITCH_OFF;
				}
			}
			break;

		case ACTION_SET_LEAD_ACTION:
			enemySendLeadTo(act->data.lead.target,act->data.lead.anim,act->data.lead.speed, act->data.lead.flags);
			break;

		case ACTION_SET_SAMPLE_STATE:
			if(act->data.playsample.crc && act->data.playsample.tag)
			{
				int ch, i;
				SfxSampleType *pSample = (SfxSampleType *)(act->data.playsample.crc);

				if(act->data.playsample.tag)
				{
					if(act->data.playsample.state == 1)
					{
						DB("PUZZLE: Play sample %p, vol %d\n", pSample, act->data.playsample.vol);
						ch = soundPlaySamplePanFade(pSample, act->data.playsample.vol, 0, (VECTOR *)(act->data.playsample.tag));
					}
					else
					{
						DB("PUZZLE: Shut up the sample %p\n", pSample);
						soundStopSample(pSample);
					}
				}
				else
				{
					// CPW: char retribution[940];

					DB("former 940 byte obscene error message relating to sample positions tags\n");

					ASSERT(0);
				}
			}
			break;

		case ACTION_SET_DS_EVENT:
			{

/*				printf("dsEventAdd(%d, %d, %d, %d, %d, %d, %d)\n",
							(act->data.fivelongs.values[0] & PUZZLE_DSEVENT_BUZZER)?1:0,
							act->data.fivelongs.values[1],
							(act->data.fivelongs.values[0] & PUZZLE_DSEVENT_MOTOR)?1:0,
							act->data.fivelongs.values[2],
							act->data.fivelongs.values[3],
							act->data.fivelongs.values[4],
							(act->data.fivelongs.values[0] & PUZZLE_DSEVENT_QUEUE)?1:0);
*/

				dsEvent_Add(((act->data.fivelongs.values[0] & PUZZLE_DSEVENT_BUZZER)?1:0),
							act->data.fivelongs.values[1],
							((act->data.fivelongs.values[0] & PUZZLE_DSEVENT_MOTOR)?1:0),
							act->data.fivelongs.values[2],
							act->data.fivelongs.values[3],
							act->data.fivelongs.values[4],
							((act->data.fivelongs.values[0] & PUZZLE_DSEVENT_QUEUE)?1:0));


				// CPW: dsEvent_Add(1,1,0,0,0,0,0);
			}
			break;
	}
}



void puzzleCheckPuzzles()
{
	PUZZLE *puzzle;
	CONDITION *condition;
	ACTION *action;
	int i;
	int valid;

// Reset any puzzle variables that need contunous checking
	PuzzleVars.usable_object_area = 0;

// Run through the puzzles...

	for(puzzle = PuzzleVars.puzzles; puzzle; puzzle = puzzle->next)
	{


		if(!puzzle->numtimes)
		{
			valid = 0;
		}
		else
		{
			if(puzzle->andor == CONDITION_AND)
			{
				valid = 1;
				condition = puzzle->conditions;
				for(i = puzzle->numconditions; i > 0; i--,condition++)
				{
					if(!puzzleConditionMet(condition))
					{
						valid = 0;
						break;
					}
				}
			}
			else
			{
				valid = 0;
				condition = puzzle->conditions;
				for(i = puzzle->numconditions; i > 0; i--,condition++)
				{
					if(puzzleConditionMet(condition))
					{
						valid = 1;
						break;
					}
				}
			}


			if(puzzle->just)
			{
				if(valid == puzzle->oldjust)
				{
					valid = 0;
				}
				else
				{
					puzzle->oldjust = valid;


					if(valid == puzzle->justyesno)
					{
						valid = 1;
					}
					else
					{
						valid = 0;
					}
				}
			}
		}

// If the puzzle conditions were met, set the action delay timers going
		if(valid)
		{
			action = puzzle->actions;

			puzzle->checkactions = YES;

			for(i = puzzle->numactions; i > 0; i--,action++)
			{
				if(action->timer == -1)
				{
					if(action->flags & ACTION_RANDOMISE_DELAY)
						action->timer = action->delay + utilRandomInt(6)*5;
					else
						action->timer = action->delay;
				}

				// If it's a level-switching action, with a delay, then we can start fading the music out
				if(action->type == ACTION_GOTO_LEVEL)
				{
					enemyInformEndingLevel();
					if(action->timer > 0)
					{
						sound_XA_StartFadeOut(action->timer);
					}
				}
			}
			if(puzzle->numtimes != -1)
				puzzle->numtimes--;


		}
	}

// When the action delay timer hits, do the actions

	for(puzzle = PuzzleVars.puzzles; puzzle; puzzle = puzzle->next)
	{
		if(puzzle->checkactions)
		{
			int num = 0;
			action = puzzle->actions;

			for(i = puzzle->numactions; i > 0; i--,action++)
			{
				if(action->timer != -1)
				{
					if(action->timer == 0)
					{
						puzzleDoAction(puzzle,action);

					}
					action->timer--;
				}
				else 
				{
					num++;
				}
			}
			if(num == puzzle->numactions)
			{
				puzzle->checkactions = NO;
			}
		}
	}
}

// ------------- Restoring the puzzle status -----------------


void puzzleRestorePushablePlatform(int tag,PLATFORM_SAVESTR *info)
{
	PLATFORMWORK *plat;

	if(!info->filledin)
		return;

	plat = platformFindPlatform(tag);
	ASSERT(plat);
	plat->pos.vx = info->posx;
	plat->pos.vy = info->posy;
	plat->pos.vz = info->posz;

	plat->info.pushable.pushvec.vx = info->pushx;
	plat->info.pushable.pushvec.vy = info->pushy;
	plat->info.pushable.pushvec.vz = info->pushz;

	platformEnable(plat,info->enabled);


}
void puzzleSavePushablePlatform(int tag,PLATFORM_SAVESTR *info)
{
	PLATFORMWORK *plat;

	info->filledin = 1;
	plat = platformFindPlatform(tag);
	ASSERT(plat);

	info->posx = plat->pos.vx;
	info->posy = plat->pos.vy;
	info->posz = plat->pos.vz;

	info->pushx = plat->info.pushable.pushvec.vx;
	info->pushy = plat->info.pushable.pushvec.vy;
	info->pushz = plat->info.pushable.pushvec.vz;

	info->enabled = platformIsEnabled(plat);
}


enum
{
	A2TS_INITIAL,
	A2TS_REAR,
	A2TS_FRONT,
	A2TS_SILLY,
	A2TS_TROLLEY,
	A2TS_TROLLEY_REAR,
	A2TS_TROLLEY_FRONT,
	A2TS_N_STATES
};

enum
{
	A4TS_B,
	A4TS_J_B,
	A4TS_J,
	A4TS_DOWN,

	A4TS_B_RUN,
	A4TS_J_B_RUN,
	A4TS_J_RUN,
	A4TS_DOWN_RUN,

	A4TS_FINISHED,

	A4TS_N_STATES
};

void puzzleSaveA2Toolshed()
{
	SCENIC *enginebutton;
	SCENIC *drivelever;
	SCENIC *armbale;
	PLATFORMWORK *trolley;
	int state;

	enginebutton = scenicFind(71);
	drivelever = scenicFind(70);
	armbale = scenicFind(61);
	trolley = platformFindPlatform(80);

	if(enginebutton->state == SCENICSTATE_SWITCH_OFF)
	{
		if(scenicIsEnabled(armbale))
		{
			overlayDebugMessage("Shed = TROLLEY\n", 25*2);
			state = A2TS_TROLLEY;
		}
		else
		{
			overlayDebugMessage("Shed = INITAL\n", 25*2);
			state = A2TS_INITIAL;
		}
	}
	else
	{
		if(drivelever->state == SCENICSTATE_SWITCH_OFF)
		{
			if(scenicIsEnabled(armbale))
			{
				overlayDebugMessage("Shed = TROLLEY REAR\n", 25*2);
				state = A2TS_TROLLEY_REAR;
			}
			else
			{
				overlayDebugMessage("Shed = REAR\n", 25*2);
				state = A2TS_REAR;
			}
		}
		else
		{
			if(scenicIsEnabled(armbale))
			{
				overlayDebugMessage("Shed = TROLLEY FRONT\n", 25*2);
				state = A2TS_TROLLEY_FRONT;
			}
			else
			{
				if(platformAtPoint(trolley, 0))
				{
					overlayDebugMessage("Shed = FRONT\n", 25*2);
					state = A2TS_FRONT;
				}
				else
				{
					overlayDebugMessage("Shed = SILLY\n", 25*2);
					state = A2TS_SILLY;
				}
			}
		}
	}
	gamePlayData->a2ts_state = state;
}

/*
void puzzleDescribeA4Toolshed()
{
	SCENIC *jackhandle;
	SCENIC *jack;
	SCENIC *lever;
	SCENIC *button;
	SCENIC *block;

	SCENIC *chain;
	SCENIC *animtrac;

	PLATFORMWORK *block2;
	PLATFORMWORK *back;
	PLATFORMWORK *engine;

	PLATFORMWORK *tractor;
	PLATFORMWORK *bricks;
	PLATFORMWORK *doors;
	PLATFORMWORK *temp;
	PLATFORMWORK *fakechain;
	PLATFORMWORK *crash;
	PLATFORMWORK *sortpoly;

	int var1;


// need to know "enables" on pretty much everything
// (Though I think that's a two-state case on most)
	jackhandle = scenicFind(60);		// state,anim
	ASSERT(jackhandle);
	jack = scenicFind(61);				// anim
	ASSERT(jack);
	button = scenicFind(71);			// state,anim
	ASSERT(button);
	lever = scenicFind(45);				// state, anim
	ASSERT(lever);
	block = scenicFind(90);				// anim
	ASSERT(block);

	chain = scenicFind(202);				// invent item
	ASSERT(chain);
	animtrac = scenicFind(300);
	ASSERT(animtrac);

	block2 = platformFindPlatform(42);	// speed. point
	ASSERT(block2);
	back = platformFindPlatform(66);	// spin
	ASSERT(back);
	engine = platformFindPlatform(40);	// speed
	ASSERT(engine);

	tractor = platformFindPlatform(100);	// point
	ASSERT(tractor);
	bricks = platformFindPlatform(200);
	ASSERT(bricks);
	doors = platformFindPlatform(201);
	ASSERT(doors);
	fakechain = platformFindPlatform(222);
	ASSERT(fakechain);
	temp = platformFindPlatform(203);
	ASSERT(temp);
	crash = platformFindPlatform(111);
	ASSERT(crash);
	sortpoly = platformFindPlatform(99);
	ASSERT(sortpoly);

	printf("==Vars==\n");
	printf("var1 = %d\n",PuzzleVars.variables[1]);
	printf("task = %d\n",gamePlayData->tasks_done[TASK_FINISHED_A4_TRACTOR]);

	printf("==Scenics==\n");
	printf("jack handle   states %d %d. enab %d\n",jackhandle->state,jackhandle->oldState,scenicIsEnabled(jackhandle));
	printf("engine button states %d %d. enab %d\n",button->state,button->oldState,scenicIsEnabled(button));
	printf("block lever   states %d %d. enab %d\n",lever->state,lever->oldState,scenicIsEnabled(lever));

	printf("jack. enab %d. Animframe %d\n",scenicIsEnabled(jack),jack->pActor->animation.animData[0].actualFrame);
	printf("block. enab %d. Animframe %d\n",scenicIsEnabled(block),block->pActor->animation.animData[0].actualFrame);

	printf("chain. enab %d.\n",scenicIsEnabled(chain));
	printf("animtrac. enab %d.\n",scenicIsEnabled(animtrac));

	printf("==Platforms==\n");

	printf("block2 %d. point %d %d %d. speed %d\n",block2->active, block2->pos.vx, block2->pos.vy, block2->pos.vz, block2->move.speed);
	printf("back %d. spin %d\n",back->active,back->def->definfo.rotating.speed);
	printf("engine %d. speed %d\n",engine->active, engine->move.speed);
	printf("tractor %d. point %d %d %d. speed %d\n",tractor->active, tractor->pos.vx, tractor->pos.vy, tractor->pos.vz, tractor->move.speed);
	printf("bricks %d\n",bricks->active);
	printf("doors %d\n",doors->active);
	printf("fakechain %d\n",fakechain->active);
	printf("temp %d\n",temp->active);
	printf("crash %d\n",crash->active);
	printf("sortpoly %d\n",sortpoly->active);
}
*/

void puzzleSaveA4Toolshed()
{
	SCENIC *enginebutton;
	SCENIC *jackhandle;
	int blockpos;
	int complete;
	int state;

	enginebutton = scenicFind(71);
	jackhandle = scenicFind(60);

	blockpos = PuzzleVars.variables[1];
	complete = gamePlayData->tasks_done[TASK_FINISHED_A4_TRACTOR] != 0;

	if(complete)
	{
		overlayDebugMessage("Shed = FIN\n", 25*2);
		state = A4TS_FINISHED;
	}
	else
	{
		if(enginebutton->state == SCENICSTATE_SWITCH_OFF)
		{
			if(jackhandle->state == SCENICSTATE_SWITCH_OFF)
			{
				if(!blockpos)
				{
					overlayDebugMessage("Shed = B\n", 25*2);
					state = A4TS_B;
				}
				else
				{
					overlayDebugMessage("Shed = DOWN\n", 25*2);
					state = A4TS_DOWN;
				}
			}
			else
			{
				if(!blockpos)
				{
					overlayDebugMessage("Shed = J B\n", 25*2);
					state = A4TS_J_B;
				}
				else
				{
					overlayDebugMessage("Shed = J\n", 25*2);
					state = A4TS_J;
				}
			}
		}
		else
		{
			if(jackhandle->state == SCENICSTATE_SWITCH_OFF)
			{
				if(!blockpos)
				{
					overlayDebugMessage("Shed = B RUN\n", 25*2);
					state = A4TS_B_RUN;
				}
				else
				{
					overlayDebugMessage("Shed = DOWN RUN\n", 25*2);
					state = A4TS_DOWN_RUN;
				}
			}
			else
			{
				if(!blockpos)
				{
					overlayDebugMessage("Shed = J B RUN\n", 25*2);
					state = A4TS_J_B_RUN;
				}
				else
				{
					overlayDebugMessage("Shed = J RUN\n", 25*2);
					state = A4TS_J_RUN;
				}
			}
		}
	}
	gamePlayData->a4ts_state = state;

//	DB("----- A4 Toolshed: State = %d ------\n",state);
//	puzzleDescribeA4Toolshed();

}



typedef struct
{
	int armenable:1;
	int armpos:1;
	int armbaleenable:1;
	int armbalepos:1;
	int trolleyenable:1;
	int trolleypoint:1;
	int	fwd_state:1;
	int enginestate:1;
	int front_spin:1;
	int back_spin:1;
	int mesh1:1;
	int mesh2:1;
	int mesh3:1;
	int mesh4:1;
}A2TS_STATEREF;

//	arm		armbl	trol	fwd,eng,f,	b,	m1	m2	m3	m4
A2TS_STATEREF a2ts_state_ref[A2TS_N_STATES] =
{
	{1,0,	0,0,	1,0,	0,	0,	0,	0,	0,	0,	1,	0},	// init
	{1,0,	0,0,	1,0,	0,	1,	0,	1,	0,	0,	1,	0},	// rear
	{1,1,	0,1,	1,0,	1,	1,	1,	0,	1, 	0,	0,	0},	// front
	{1,1,	0,1,	1,1,	1,	1,	1,	0,	1, 	0,	0,	0},	// silly
	
	{0,0,	1,0,	0,0,	0,	0,	0,	0,	0,	0,	0,	1},	// tr
	{0,0,	1,0,	0,0,	0,	1,	0,	1,	0,	0,	0,	1},	// tr rear
	{0,1,	1,1,	0,1,	1,	1,	1,	0,	0,	1,	0,	0}	// tr front
};


void puzzleRestoreA2Toolshed()
{
	A2TS_STATEREF *info;

	SCENIC *enginebutton;
	SCENIC *drivelever;
	SCENIC *arm;
	SCENIC *armbale;

	PLATFORMWORK *trolley;
	PLATFORMWORK *front;
	PLATFORMWORK *back;
	PLATFORMWORK *engine;

	PLATFORMWORK *mesh1;
	PLATFORMWORK *mesh2;
	PLATFORMWORK *mesh3;
	PLATFORMWORK *mesh4;
	VENTHANDLE vent;

//	if(gamePlayData->a2ts_state == A2TS_INITIAL)
//		return;

	enginebutton = scenicFind(71);
	ASSERT(enginebutton);
	drivelever = scenicFind(70);
	ASSERT(drivelever);
	arm = scenicFind(60);
	ASSERT(arm);
	armbale = scenicFind(61);

	trolley = platformFindPlatform(80);
	ASSERT(trolley);
	front = platformFindPlatform(68);
	ASSERT(front);
	back = platformFindPlatform(66);
	ASSERT(back);
	engine = platformFindPlatform(65);
	ASSERT(engine);

	mesh1 = platformFindPlatform(90);
	ASSERT(mesh1);
	mesh2 = platformFindPlatform(91);
	ASSERT(mesh2);
	mesh3 = platformFindPlatform(92);
	ASSERT(mesh3);
	mesh4 = platformFindPlatform(93);
	ASSERT(mesh4);
	vent = ventFind(96);
	ASSERT(vent);

	DB("Restoring the toolshed\n");

	info = &a2ts_state_ref[(int)(gamePlayData->a2ts_state)];


// scenic arms. enable & animation starting point
	scenicEnable(armbale, (info->armbaleenable != 0));
	scenicEnable(arm, (info->armenable != 0));
	if(info->armpos)
	{
		// Start 'em both off at the top

//		actorAnimate(pS->pActor, anim, loop, queue , speed, 0, 2048);

		actorAnimate(    arm->pActor, 3, 0, 0 , 1<<ANIMSHIFT, 0, 0);
		actorAnimate(armbale->pActor, 3, 0, 0 , 1<<ANIMSHIFT, 0, 0);
	}


// Trolley position. Enable.
	if(info->trolleypoint)
	{
		trolley->pos.vx = trolley->def->points[1].posx;
		trolley->pos.vy = trolley->def->points[1].posy;
		trolley->pos.vz = trolley->def->points[1].posz;
	}
	platformEnable(trolley, (info->trolleyenable != 0));
		
		
// FWD switch state
	if(info->fwd_state)
	{
		drivelever->state = drivelever->oldState = SCENICSTATE_SWITCH_ON;
		PuzzleVars.variables[1] = 3;
	}
	else
	{
		drivelever->state = drivelever->oldState = SCENICSTATE_SWITCH_OFF;
	}
// Engine switch state, platform move spped

	if(info->enginestate)
	{
		SfxSampleType *pSample = sfxFindSampleInAllBanks("tractorrun2");

		ASSERT(pSample);

		enginebutton->state = enginebutton->oldState = SCENICSTATE_SWITCH_ON;
		platformSetSpeed(engine,1*8);
		ventEnable(vent,1);
		soundPlaySamplePanFade(pSample, 100, 0, &engine->cumPos);

	}
	else
	{
		enginebutton->state = enginebutton->oldState = SCENICSTATE_SWITCH_OFF;
	}

// wheel spins
	if(info->front_spin)
		platformSetRotSpeed(front, -100);
	if(info->back_spin)
		platformSetRotSpeed(back, -100);

// collision mesh enables
	platformEnable(mesh1, (info->mesh1 != 0));
	platformEnable(mesh2, (info->mesh2 != 0));
	platformEnable(mesh3, (info->mesh3 != 0));
	platformEnable(mesh4, (info->mesh4 != 0));

	DB("Restoring done ok\n");

}

typedef struct
{
	int var:1;
	int task:1;
	int engine:1;
	int jackstate:1;
	int tractorpos:1;
}A4TS_STATEREF;

A4TS_STATEREF a4ts_state_ref[A4TS_N_STATES] = 
{
	{0,0,0,0,0},

	{0,0,0,1,0},
	{1,0,0,1,0},
	{1,0,0,0,1},

	{0,0,1,0,0},
	{0,0,1,1,0},
	{1,0,1,1,0},
	{1,0,1,0,1},

	{1,1,1,0,0}
};


void puzzleRestoreA4Toolshed()
{
	SCENIC *jackhandle;
	SCENIC *jack;
	SCENIC *lever;
	SCENIC *button;
	SCENIC *block;

	SCENIC *chain;
	SCENIC *animtrac;
//	VENTHANDLE vent;

	PLATFORMWORK *block2;
	PLATFORMWORK *back;
	PLATFORMWORK *engine;

	PLATFORMWORK *tractor;
	PLATFORMWORK *bricks;
	PLATFORMWORK *doors;
	PLATFORMWORK *temp;
//	PLATFORMWORK *fakechain;
	PLATFORMWORK *crash;
	PLATFORMWORK *sortpoly;
	PLATFORMWORK *trakker_2;

	A4TS_STATEREF *info;

// need to know "enables" on pretty much everything
// (Though I think that's a two-state case on most)
	jackhandle = scenicFind(60);		// state,anim
	ASSERT(jackhandle);
	jack = scenicFind(61);				// anim
	ASSERT(jack);
	button = scenicFind(71);			// state,anim
	ASSERT(button);
	lever = scenicFind(45);				// state, anim
	ASSERT(lever);
	block = scenicFind(90);				// anim
	ASSERT(block);
	ASSERT(!(block->flags & SCENICFLAG_NEEDSACTOR));

	chain = scenicFind(202);				// invent item
	ASSERT(chain);
	animtrac = scenicFind(300);
	ASSERT(animtrac);
	ASSERT(!(animtrac->flags & SCENICFLAG_NEEDSACTOR));

//	vent = ventFind(96);
//	ASSERT(vent);

	block2 = platformFindPlatform(42);	// speed. point
	ASSERT(block2);
	back = platformFindPlatform(66);	// spin
	ASSERT(back);
	engine = platformFindPlatform(40);	// speed
	ASSERT(engine);

	tractor = platformFindPlatform(100);	// point
	ASSERT(tractor);
	trakker_2 = platformFindPlatform(265);	// Tim's new dummy collision
	ASSERT(trakker_2);

	bricks = platformFindPlatform(200);
	ASSERT(bricks);
	doors = platformFindPlatform(201);
	ASSERT(doors);

// CPW: 	fakechain = platformFindPlatform(222);
// CPW: 	ASSERT(fakechain);
	temp = platformFindPlatform(203);
	ASSERT(temp);
	crash = platformFindPlatform(111);
	ASSERT(crash);
	sortpoly = platformFindPlatform(99);
	ASSERT(sortpoly);

	DB("Restoring the toolshed\n");

// Actually, we could set the "finished" state according to the task_complete variable
	info = &a4ts_state_ref[(int)(gamePlayData->a4ts_state)];

	if(info->var)
	{
		// Var1, BlockLever state, "block" animframe, block platform pos
		PuzzleVars.variables[1] = 1;
		actorAnimate(block->pActor, 4, 0, 0 , 1<<ANIMSHIFT, 0, 0);

		block2->pos.vx = block2->def->points[1].posx;
		block2->pos.vy = block2->def->points[1].posy;
		block2->pos.vz = block2->def->points[1].posz;

	}
	else
	{
		// var 1 = 0,
		// block animframe = 1,
		// blockpos = 319 (point 0)
	}

	if(info->task)
	{
		// lots of enables/disables, plus the animtrac frame

		// Jack Handle DIS
		// Engine Button DIS
		// Jack DIS
		// Block DIS

		scenicEnable(jackhandle, 0);
		scenicEnable(button, 0);
		scenicEnable(jack, 0);
		scenicEnable(block, 0);

		// Chain ENA
		// AnimTrac ENA, anim

		scenicEnable(chain, 1);
		scenicEnable(animtrac, 1);
		actorAnimate(animtrac->pActor, 2, 0, 0 , 1<<ANIMSHIFT, 0, 0);

		// Back Wheel DIS
		// Engine DIS
		// Tractor DIS
		// (tractor speed = 8 is irrelevant)

		platformEnable(back, 0);
		platformEnable(engine, 0);
		platformEnable(tractor, 0);

		// bricks DIS
		// doors DIS
		// fakechain DIS
		// temp ENA
		// crash ENA
		// sortpoly DIS

		// dummy Collision DIS

		platformEnable(bricks, 0);
		platformEnable(doors, 0);
//		platformEnable(fakechain, 0);
		platformEnable(temp, 1);
		platformEnable(crash, 1);
		platformEnable(sortpoly, 0);

		platformEnable(trakker_2, 0);

	}
	else
	{
	}

	if(info->engine)
	{
//		SfxSampleType *pSample = sfxFindSampleInAllBanks("tractorrun2");
//		ASSERT(pSample);
		// engine button state, platform speed, wheelspin
		button->state = button->oldState = SCENICSTATE_SWITCH_ON;
		platformSetSpeed(engine,40);
		platformSetRotSpeed(back, -100);


//		ventEnable(vent,1);
//		soundPlaySamplePanFade(pSample, 100, 0, &engine->cumPos);

	}
	else
	{
	}

	if(info->jackstate)
	{
		//  jacklever state, and jackscenic anim frame (0 or 32)
		jackhandle->state = jackhandle->oldState = SCENICSTATE_SWITCH_ON;
		actorAnimate(jack->pActor, 2, 0, 0 , 1<<ANIMSHIFT, 0, 0);
	}
	else
	{
	}
	
	if(info->tractorpos)
	{
		// tractor plat height

// -77 is y of point 0	616
// -89 is y of point 1	712
		tractor->pos.vx = tractor->def->points[1].posx;
		tractor->pos.vy = tractor->def->points[1].posy;
		tractor->pos.vz = tractor->def->points[1].posz;

		back->pos.vx = back->def->points[1].posx;
		back->pos.vy = back->def->points[1].posy;
		back->pos.vz = back->def->points[1].posz;

		sortpoly->pos.vx = sortpoly->def->points[1].posx;
		sortpoly->pos.vy = sortpoly->def->points[1].posy;
		sortpoly->pos.vz = sortpoly->def->points[1].posz;

	}
	else
	{
	}

	DB("Restoring done ok\n");
}



/*
void puzzleDescribeA2Toolshed()
{
	SCENIC *enginebutton;
	SCENIC *drivelever;
	SCENIC *arm;
	SCENIC *armbale;

	PLATFORMWORK *trolley;
	PLATFORMWORK *front;
	PLATFORMWORK *back;
	PLATFORMWORK *engine;

	PLATFORMWORK *mesh1;
	PLATFORMWORK *mesh2;
	PLATFORMWORK *mesh3;
	PLATFORMWORK *mesh4;


	enginebutton = scenicFind(71);
	ASSERT(enginebutton);
	drivelever = scenicFind(70);
	ASSERT(drivelever);
	arm = scenicFind(60);
	ASSERT(arm);
	armbale = scenicFind(61);

	trolley = platformFindPlatform(80);
	ASSERT(trolley);
	front = platformFindPlatform(68);
	ASSERT(front);
	back = platformFindPlatform(66);
	ASSERT(back);
	engine = platformFindPlatform(65);
	ASSERT(engine);

	mesh1 = platformFindPlatform(90);
	ASSERT(mesh1);
	mesh2 = platformFindPlatform(91);
	ASSERT(mesh2);
	mesh3 = platformFindPlatform(92);
	ASSERT(mesh3);
	mesh4 = platformFindPlatform(93);
	ASSERT(mesh4);

	printf("trolley enable %d\n",trolley->active);
	printf("trolley pos %d %d %d\n",trolley->pos.vx,trolley->pos.vy,trolley->pos.vz);
	printf("mesh1   enable %d\n",mesh1->active);
	printf("mesh2   enable %d\n",mesh2->active);
	printf("mesh3   enable %d\n",mesh3->active);
	printf("mesh4   enable %d\n",mesh4->active);
	printf("engine button states %d %d\n",enginebutton->state,enginebutton->oldState);
	printf("fwd lever     states %d %d\n",drivelever->state,drivelever->oldState);
	printf("var1 = %d\n",PuzzleVars.variables[1]);


}
*/
// Called after everything all loaded & ready to roll.
// (1) TBD - Trigger any unconditional NumTimes1 puzzles
// (2) Restore any puzhable platform puzzles to where they were left.
void puzzleRestorePuzzleState()
{
	PUZZLE *puzzle;
	ACTION *action;
	int i;

// Scan through for initial conditions puzzles: "numtimes 1, unconditional", and run them
// (So they don't interfere with what we're about to do afterwards)

	for(puzzle = PuzzleVars.puzzles; puzzle; puzzle = puzzle->next)
	{
//		if(puzzle->numtimes == 1 && )
		if(!puzzle->numconditions)
		{
			action = puzzle->actions;
// There's not really much point in having delayed actions on an always-true puzzle, so just assume they've all got delay = 0
			for(i = puzzle->numactions; i > 0; i--,action++)
			{
				puzzleDoAction(puzzle,action);
			}


			if(puzzle->numtimes != -1)
				puzzle->numtimes--;
		}
	}

	switch(levinfo->level_number)
	{
	case LEVNUMBER_A2_EGGROOM:
		puzzleRestorePushablePlatform(400,&gamePlayData->a2er_plat[0]);
		puzzleRestorePushablePlatform(401,&gamePlayData->a2er_plat[1]);
		puzzleRestorePushablePlatform(402,&gamePlayData->a2er_plat[2]);
		puzzleRestorePushablePlatform(403,&gamePlayData->a2er_plat[3]);
		// tbd - Mrs Tweedy Inside/Outside with a2_housext_d
		break;
	case LEVNUMBER_A2_HOUSEXT_D:
		// tbd - Mrs Tweedy Inside/Outside with eggroom
		break;

	case LEVNUMBER_A4_EGGROOM:
		puzzleRestorePushablePlatform(400,&gamePlayData->a4er_plat[0]);
		puzzleRestorePushablePlatform(401,&gamePlayData->a4er_plat[1]);
		puzzleRestorePushablePlatform(402,&gamePlayData->a4er_plat[2]);
		puzzleRestorePushablePlatform(403,&gamePlayData->a4er_plat[3]);
		break;
	case LEVNUMBER_A4_HOUSEXT_N:
		break;

	case LEVNUMBER_A2_OLDBARN_D:
		puzzleRestorePushablePlatform(10,&gamePlayData->a2ob_plat[0]);
		puzzleRestorePushablePlatform(11,&gamePlayData->a2ob_plat[1]);
		puzzleRestorePushablePlatform(12,&gamePlayData->a2ob_plat[2]);
		break;

	case LEVNUMBER_A4_KITCHEN:
		puzzleRestorePushablePlatform(400,&gamePlayData->a4ki_plat[0]);
		break;

	case LEVNUMBER_A2_TOOLSHED_D:
		puzzleRestoreA2Toolshed();
		break;

	case LEVNUMBER_A4_TOOLSHED_N:
		puzzleRestoreA4Toolshed();

		break;
	}
}
void puzzleSavePuzzleState()
{
	switch(levinfo->level_number)
	{
	case LEVNUMBER_A2_EGGROOM:
		puzzleSavePushablePlatform(400,&gamePlayData->a2er_plat[0]);
		puzzleSavePushablePlatform(401,&gamePlayData->a2er_plat[1]);
		puzzleSavePushablePlatform(402,&gamePlayData->a2er_plat[2]);
		puzzleSavePushablePlatform(403,&gamePlayData->a2er_plat[3]);
		break;
	case LEVNUMBER_A2_HOUSEXT_D:
		break;

	case LEVNUMBER_A4_EGGROOM:
		puzzleSavePushablePlatform(400,&gamePlayData->a4er_plat[0]);
		puzzleSavePushablePlatform(401,&gamePlayData->a4er_plat[1]);
		puzzleSavePushablePlatform(402,&gamePlayData->a4er_plat[2]);
		puzzleSavePushablePlatform(403,&gamePlayData->a4er_plat[3]);
		break;
	case LEVNUMBER_A4_HOUSEXT_N:
		break;

	case LEVNUMBER_A2_OLDBARN_D:
		puzzleSavePushablePlatform(10,&gamePlayData->a2ob_plat[0]);
		puzzleSavePushablePlatform(11,&gamePlayData->a2ob_plat[1]);
		puzzleSavePushablePlatform(12,&gamePlayData->a2ob_plat[2]);
		break;

	case LEVNUMBER_A4_KITCHEN:
		puzzleSavePushablePlatform(400,&gamePlayData->a4ki_plat[0]);
		break;
	
	case LEVNUMBER_A2_TOOLSHED_D:
//		puzzleDescribeA2Toolshed();
		puzzleSaveA2Toolshed();
		break;

	case LEVNUMBER_A4_TOOLSHED_N:
		puzzleSaveA4Toolshed();
		break;
	}
}


// ------------- Note: This shouldn't really be in puzzles. But it can be for now -----------------
LEVINFO *levinfo;
void levinfoLinkBFF(LEVINFO *info)
{
	int i;
	EXLIGHT_TYPE *light;

	levinfo = info;

	if(info->posmarks)
		info->posmarks = ADD2POINTER(info->posmarks, (int)info);
	if(info->lights)
		info->lights = ADD2POINTER(info->lights, (int)info);

	if(info->vents)
	{
		info->vents = ADD2POINTER(info->vents, (int)info);
		vents=info->vents;
		n_vents=info->n_vents;
	}
	else
	{
		vents=NULL;
		n_vents=0;
	}

	if(!levinfo->characters_here)
	{
		levinfo->characters_here = LEVINFO_CHARACTERS_CURRENTCHICKEN;
	}

//	utilPrintf("projection %d\n",info->initial_fov);
	GsSetProjection(info->initial_fov);
	// added for setting initiail cam dist/height
	camVars.camHeight=info->initial_cam_height;
	camVars.actualHeight=camVars.requiredHeight=camVars.normalHeight=camVars.camHeight;
	camVars.camDistance=info->initial_cam_distance;

	camVars.lookDistance=camVars.camDistance;
	camVars.requiredDistance=camVars.lookDistance;
	camVars.actualDistance=camVars.lookDistance;
	camVars.oldRequiredDistance=camVars.requiredDistance;

	camVars.camStartAngle=info->cam_start_angle;
	camVars.camAngle=camVars.camStartAngle;
	{
		int	cos,sin;

		sin=rsin(camVars.camAngle);
		cos=rcos(camVars.camAngle);
		mycamera.vpx=mycamera.vrx+sin;
		mycamera.vpz=mycamera.vrz-cos;
	}
	camVars.camTransitionSpeed=info->cam_transition_speed;

	DB("level number %d\n",levinfo->level_number);


	if(info->n_lights)
	{
		for(light = info->lights; light; light = light->next)
		{
			if(light->next)
			{
				light->next = ADD2POINTER(light->next, (int)levinfo);
				ASSERT(BFF_IsInBFF(light->next));

			}
			if(light->meshswaps)
			{
				light->meshswaps = ADD2POINTER(light->meshswaps, (int)levinfo);
				light->type=1; //light bodge//MIKEY
			}

			if(light->flags & LIGHTFLAG_MOBILE)
			{
				LIGHT_MOBILESTR *mob;
				mob = (LIGHT_MOBILESTR *)light->meshswaps;

				mob->points = ADD2POINTER(mob->points,(int)levinfo);
			}

		}

		EXLIGHT_MergeLights(info->lights);
	}

	EXLIGHT_SetAmbient(info->ambient_colour);

}

void levinfoSetupBackground()
{
	if(levinfo->bg_colour == -1)
	{
		displayPage[0].drawenv.isbg = 0;
		displayPage[1].drawenv.isbg = 0;
	}
	else
	{
//			bg_colour = (b << 16) | (g << 8) | r;

		displayPage[0].drawenv.isbg = 1;
		displayPage[1].drawenv.isbg = 1;
		displayPage[0].drawenv.r0 = displayPage[1].drawenv.r0 = ((levinfo->bg_colour    ) & 0xff);
		displayPage[0].drawenv.g0 = displayPage[1].drawenv.g0 = ((levinfo->bg_colour>>8 ) & 0xff);
		displayPage[0].drawenv.b0 = displayPage[1].drawenv.b0 = ((levinfo->bg_colour>>16) & 0xff);
	}
}


POSMARK *levinfoFindMarker(int tag)
{
	int i;
	for(i = 0; i < levinfo->n_posmarks; i++)
	{
		if(levinfo->posmarks[i].tag == tag)
		{
			return (&levinfo->posmarks[i]);
		}
	}
	return NULL;
}


int levinfoGetAct(int level)
{
	if(level == -1)
		level = levinfo->level_number;

	if(level<=LEVNUMBER_A1_DOGCHASE)
		return 1;
	else if(level<=LEVNUMBER_A2_COGCHASE)
		return 2;
	else if(level<=LEVNUMBER_A4_FLYMACHINE)
		return 4;

// Note that this *must* be 1,2, or 4 (see deth.c)
	return 1;
}



void posmarkFindInteresting(VECTOR *character_pos, int character_dir, VECTOR **best, int *max_range)
{
	POSMARK *pP;
	POSMARK *nearest_interesting;
	int m;
	int a;
	int i;

	nearest_interesting = NULL;

	pP = &levinfo->posmarks[0];

	for(i = 0; i < levinfo->n_posmarks; i++,pP++)
	{
		if((pP->flags & POSMARKFLAG_IS_INTERESTING))
		{
			VECTOR t;
			int m;


			t.vx = character_pos->vx - pP->x;
			t.vy = character_pos->vy - pP->y;
			t.vz = character_pos->vz - pP->z;
			m = Magnitude(&t);
			if (m < *max_range)
			{
// Check it's in front of us
				a = utilCalcAngle(character_pos->vx - pP->x, character_pos->vz - pP->z);
				a -= character_dir;
				a &=4095;
				if((a<1024) || (a>4096 - 1024))
				{
					nearest_interesting = pP;

					*best = (VECTOR *)&pP->x;
					*max_range = m;
				}
			}
		}
	}
}

