
struct Menu;
Menu* activeMenu = 0;
Menu* deadMenu = 0;

static void HackKeyPress(int key, int mod)
{
	int k = keyState[key];
	StateMakerBase::current->KeyPressed(key, mod);
	keyState[key] = k;
}

struct Menu {
	bool renderBG;

	Menu() : under(activeMenu), time(0), renderBG(true) { activeMenu = this; }
	
	virtual ~Menu() { 
		if(this!=deadMenu) FATAL(); 
		deadMenu=under; 
	}

	static void Pop()
	{
		if (!activeMenu) return;
		Menu* m = activeMenu;
		activeMenu = activeMenu->under;
		m->under = deadMenu;
		deadMenu = m;
	}

	virtual bool KeyPressed(int key, int mod)
	{
		if (key=='w' || key==SDLK_UP || key==SDLK_KP8 || key=='q' || key=='e' || key==SDLK_KP7 || key==SDLK_KP9)
			Move(-1), noMouse=1;
		else if (key=='s' || key==SDLK_DOWN || key==SDLK_KP2 || key=='a' || key=='d' || key==SDLK_KP1 || key==SDLK_KP3)
			Move(1), noMouse=1;
		else if (key==' ' || key==SDLK_RETURN)
		{
			Select();
			noMouse=1;
		}
		else if (key==SDLK_ESCAPE || key==SDLK_BACKSPACE || key==SDLK_KP_PERIOD || key==SDLK_DELETE)
			Cancel();
		else
			return false;
		return true;
	}
	virtual void Mouse(int x, int y, int dx, int dy, int buttons_pressed, int buttons_released, int buttons) 
	{
		if (buttons_pressed==4 || buttons_pressed==2)
			Cancel();
	}
	
	virtual void Move(int dir) {}
	virtual void Select() {}
	virtual void Cancel() {}

	virtual void Update(double timedelta) {time+=timedelta;}
	virtual void Render() = 0;

	Menu * under;
	double time;
};

const char * hint[] = {
	
/*EMPTY*/
"Basic controls:|Move around with the keys Q,W,E,A,S,D or the numeric  keypad. Alternatively, you can use the mouse and  click on the tile you'd like to move to.    Use 'U', backspace or the right mouse button to  undo mistakes.    The 'Esc' key (or middle mouse button) brings up a  menu from which you can restart if you get stuck.",
/*NORMAL*/
0,
/*COLLAPSABLE*/
"Objective:|Your goal is to break all the green tiles.    You mainly do this by jumping on them.    They will crack when you land on them, and  only disintegrate when you jump off.    Try not to trap yourself!", 
/*COLLAPSE_DOOR*/
"The coloured walls flatten themselves when there  are no matching coloured tiles remaining.",
/*TRAMPOLINE*/
"You can bounce on the purple trampoline tiles to  get around.    But try not to fall in the water.    If you do, remember you can undo with 'U',  backspace or the right mouse button!",
/*SPINNER*/
"A red spinner tile will rotate the pieces around  it when you step on it.",
/*WALL*/
0,
/*COLLAPSABLE2*/
"You don't need to destroy blue tiles to complete  the level.    But they'll turn green when you step off them, and  you know what you have to do to green tiles...",
/*COLLAPSE_DOOR2*/
0,
/*GUN*/
"Yellow laser tiles fire when you step on them.    Shooting other laser tiles is more destructive.",
/*TRAP*/
"Ice is slippery!    Please be careful!!",
/*COLLAPSABLE3*/
0,
/*BUILDER*/
"The dark grey tiles with arrows on are builders.    Landing on one creates green tiles in any adjacent  empty tile, and turns green tiles into walls.",
/*SWITCH*/
0,
/*FLOATING_BALL*/
"You can ride on the pink floating boats to get  across water.    They'll pop if you try and float off the edge of the  screen though, so look where you're going.",
/*LIFT_DOWN*/
"The blue lifts go up or down when you land on them.",
/*LIFT_UP*/
0,

0,0,0,0,
//Item 0 (21)
"The spiky anti-ice pickups turn icy tiles into blue ones.    They get used automatically when you land on ice.",
//Item 1 (22)
"Collecting the golden jump pickups will allow you to  do a big vertical jump.    Try it out on different types of tile.    Use the space bar or return key to jump. Or click  on the tile you're currently on with the mouse.",

0,0,

// Map (25)
"Map Screen:|You can choose which level to attempt next from  the map screen.    Silver levels are ones you've cleared.    Black levels are ones you haven't completed yet,  but are available to play.",

// Scoring (26)
"New feature unlocked!|Each level has an efficiency target for you to try  and beat.    Every move you make and each non-green tile  you destroy counts against you.    Why not try replaying some levels and going  for gold?",

0,0,0,

// End of help (30)
"Thanks for playing this little game.    I hope you  enjoy it!    -- --    All content is Copyright 2005 Tom Beaumont    email: tombeaumont@yahoo.com  Any constructive criticism gratefully received!",

// First help page (31)
"Welcome to " GAMENAME "!    This is a puzzle game based on hexagonal tiles.  There is no time limit and no real-time elements, so  take as long as you like.    Use the cursor keys or click on the arrows to  scroll through the help pages. More pages will be  added as you progress through the game.",
};

struct HintMessage : public Menu
{
	static int flags;

	const char * msg;
	char title[50];
	int numLines;
	int state;

	static bool FlagTile(int t, bool newStuff=true)
	{
		if (t==LIFT_UP) t=LIFT_DOWN;
		if (newStuff && (flags & (1<<t))) return false;
		if (!newStuff && !(flags & (1<<t))) return false;
		if (t>31) return false;
		if (!hint[t]) return false;

		flags |= 1<<t;
		new HintMessage(hint[t]);
		return true;
	}

	HintMessage(const char * m) { Init(m); }

	void Init(const char * m)
	{
		numLines = 1;
		state = 0; time = 0;
		memset(title, 0, sizeof(title));
		char * x = strstr(m, "|");
		if (!x)
		{
			msg = m;
			strcpy(title, "Info:");
		}
		else
		{
			strncpy(title, m, x-m);
			msg=x+1;
		}

		const char * s = m;
		while ((s=strstr(s, "  ")))
			s+=2, numLines++;
	}

	virtual void Render()
	{
		int y = SCREEN_H/4 + int(SCREEN_H*MAX(1-time*5, 0)*3/4);
		if (state)
			y = SCREEN_H/4 + int(SCREEN_H*(-time*5)*3/4);

		Render(0, y);
		
		if (!state && time>0.2)
			Print(SCREEN_W*3/4, SCREEN_H-FONT_SPACING, "Press any key");
	}

	void Render(int x, int y)
	{
		SDL_Rect r2 = {x+TILE_W1, y, SCREEN_W-TILE_W1*2, numLines*FONT_SPACING+FONT_SPACING};
		SDL_Rect r = {r2.x-2, r2.y-2-FONT_SPACING, r2.w+4, r2.h+4+FONT_SPACING};
		SDL_FillRect(screen, &r, SDL_MapRGB(screen->format, 60,90,90));
		SDL_FillRect(screen, &r2, SDL_MapRGB(screen->format, 20,50,50));
		Print(r.x+FONT_SPACING/4, y-FONT_SPACING, title);
		PrintC(true, x+SCREEN_W/2, y+FONT_SPACING/2, msg);
	}

	virtual void Mouse(int x, int y, int dx, int dy, int buttons_pressed, int buttons_released, int buttons) 
	{
		if (buttons_pressed && state==0 && time>0.2)
			state = 1, time=0;
	}

	bool KeyPressed(int key, int mod)
	{
		if (state==0 && time>0.2)
			state = 1, time=0;
		return true;
	}

	virtual void Update(double timedelta)
	{
		Menu::Update(timedelta);
		if(state && time > 0.2)
			Pop();
	}
};

int HintMessage::flags = 1<<31 | 1<<30;

struct HintReview : public HintMessage
{
	int page;
	int page_dir;
	int page_count;
	int page_display;
	HintReview() : HintMessage(hint[31]), page(31), page_dir(0), page_display(0)
	{
		page_count=0;
		for (int i=0; i<32; i++)
			if (flags & (1<<i))
				page_count++;
	}

	void Cancel() { Pop(); }
	void Select() { Pop(); }


	virtual void Mouse(int x, int y, int dx, int dy, int buttons_pressed, int buttons_released, int buttons) 
	{
		if (buttons_pressed==1)
		{
			if (y < SCREEN_H/4-FONT_SPACING+2)
				Move(-1);
			else if (y > SCREEN_H/4+FONT_SPACING*numLines+FONT_SPACING)
				Move(1);
			else
				Cancel();
		}
		else
			Menu::Mouse(x,y,dx,dy,buttons_pressed, buttons_released, buttons);	
	}

	bool KeyPressed(int key, int mod)
	{
		if (key==SDLK_LEFT)
			Move(-1);
		else if (key==SDLK_RIGHT)
			Move(1);
		else
			return Menu::KeyPressed(key, mod);
		return true;
	}

	virtual void Render()
	{
		const double SPD = 10;

#ifdef EDIT
		sprintf (title, "Help (Page --)", page_display+1, page_count);
#else
		sprintf (title, "Help (Page %d/%d)", page_display+1, page_count);
#endif

		int y=SCREEN_H/4;
		if (state==0)
			y = SCREEN_H/4+int(MAX(0, time*SPD)*-page_dir*SCREEN_H);
		if (state==1)
			y = SCREEN_H/4+int(MAX(0, 1-time*SPD)*page_dir*SCREEN_H);

		//if (!noMouse)
		{
			PrintC(false, SCREEN_W/2, y-FONT_SPACING*2, "^");
			PrintC(false, SCREEN_W/2, y+FONT_SPACING*(numLines+1)+FONT_SPACING/2-2, "_");
		}

		HintMessage::Render(0,y);

		if (time > 1.0/SPD && page_dir && state==0)
		{
			do {
				page = (page+page_dir) & 31;
#ifdef EDIT
				if (hint[page]) break;
#endif
			} while (hint[page]==0 || !(flags&(1<<page)));
			Init(hint[page]);

			page_display = (page_display + page_count + page_dir) % page_count;

			time = 0;
			state=1;
		}
		if (time>1.0/SPD && state==1)
			state=0, page_dir = 0;
	}
	virtual void Update(double timedelta)
	{
		Menu::Update(timedelta);
	}
	void Move(int dir)
	{
		if (page_dir)
			return;
		time = 0;
		page_dir = dir;
		state = 0;
	}
};

#define MAX_GAMESLOT 4
enum option {
	OPT_GAMESLOT_0,
	OPT_GAMESLOT_LAST = OPT_GAMESLOT_0 + MAX_GAMESLOT - 1,
	OPT_RESUME,
	OPT_RESTART,
	OPT_GOTO_MAP,
	OPT_GOTO_MAP_CONTINUE,
	OPT_FULLSCREEN,
	OPT_OPTIONS,
	OPT_QUIT,
	OPT_QUIT_CONFIRM,
	OPT_QUIT_CANCEL,
	OPT_QUIT_MENU_CONFIRM,
	OPT_HELP,
	OPT_GAMESLOT_NEW,
	OPT_DELETE_CONFIRM,
	OPT_DELETE_CANCEL,
	OPT_UNDO,
	OPT_BACK,
	OPT_END, OPT_END2,
};

char optionSlotName[MAX_GAMESLOT][40] = {0};
char currentSlot[80] = "";
int freeSlot = -1;
char* GetSlotName(int i, char * t)
{
	 sprintf(t, "save%d.dat", i+1);
	 return t;
}

char * optionString[] = {
	optionSlotName[0],
	optionSlotName[1],
	optionSlotName[2],
	optionSlotName[3],
	"Resume",
	"Restart Level",
	"Return to Map",
	"Continue",
	"Toggle Fullscreen",
	"Options",
	"Quit",
	"Yes",
	"No",
	"Return to Title",
	"Help",
	"Start New Game",
	"Yes, really delete it!",
	"Don't do it!",
	"Undo Last Move",
	"OK",
	"View Credits Sequence", "View Credits Sequence",
};


struct OptMenu : public Menu
{
	int select;
	int num_opt;
	int opt[10];
	bool left_align;
	const char * title;
	SDL_Rect r, r2;

	OptMenu(const char * t) : select(0), title(t)
	{
		left_align = false;
		num_opt = 0;
	}

	void Init()
	{
		r.w=SCREEN_W/2;
		r.x=(SCREEN_W-r.w)/2;
		r.y=SCREEN_H/3;
		
		r2 = r;
		
		const int SPACE = int(FONT_SPACING * 1.5);

		r2.h = SPACE*num_opt + FONT_SPACING/2;
		r.h = r2.h + (FONT_SPACING+2*2);
		r.y -= FONT_SPACING+2;
		r.w += 2*2;
		r.x -= 2;
	}

	void RenderOption(int o, const char * s)
	{
		int y = r2.y + FONT_SPACING/2 + int(FONT_SPACING * 1.5) * o;
		if (left_align)
		{
			int x = r.x + font[' '].w;
			int x1 = x + (font[' '].w + font['>'].w + FONT_X_SPACING*2) / 2;
			if (select==o)
			{
				//x += int( sin(time*9)*2.5 );
				//y += int( sin(time*9 + 1.5)*1.5 );
				Print(x, y, "> %s", s);
			}
			else
			{
				Print(x1, y, "%s", s);
			}
		}
		else
		{
			int x = r.x + r.w/2;
			if (select==o)
			{
				//x += int( sin(time*9)*2.5 );
				//y += int( sin(time*9 + 1.5)*1.5 );
				PrintC(false, x, y, "> %s <", s);
			}
			else
			{
				PrintC(false, x, y, "%s", s);
			}
		}
	}
	
	void Move(int dir)
	{
		select += dir;
		if (select<0) select = num_opt-1;
		if (select>=num_opt) select = 0;
	}
	virtual void Mouse(int x, int y, int dx, int dy, int buttons_pressed, int buttons_released, int buttons) 
	{
		if (1)
		{
			if (x<r2.x || y<r2.y || x>r2.x+r2.w || y>r2.y+r2.h)
			{
				if (buttons_pressed!=4 && buttons_pressed!=2)
					return;
			}
			else
			{
				select = (y-r2.y-FONT_SPACING/3) / int(FONT_SPACING*1.5);
				if (select<0) select = 0;
				if (select>=num_opt) select = num_opt-1;
			}
		}
		if (buttons_pressed==1)
			Select();
		Menu::Mouse(x, y, dx, dy, buttons_pressed, buttons_released, buttons);
	}
	void Select();

	void Render()
	{
		RenderBG();
		RenderTitle();
		RenderOptions();
	}
	void RenderBG()
	{
		SDL_FillRect(screen, &r, SDL_MapRGB(screen->format, 60,90,90));
		SDL_FillRect(screen, &r2, SDL_MapRGB(screen->format, 20,50,50));
	}
	void RenderTitle()
	{
		int y = r2.y + FONT_SPACING / 2;
		if (left_align)
			Print(r2.x+font[' '].w, r.y+4, title);
		else
			PrintC(false, r2.x+r2.w/2, r.y+4, title);
	}
	void RenderOptions()
	{
		for (int i=0; i<num_opt; i++)
			RenderOption(i, optionString[opt[i]]);
	}
	void Cancel()
	{
		Pop();
	}
};

struct WinLoseScreen : public OptMenu
{
	bool win;
	int score, par, best_score;
	WinLoseScreen(bool _win, int _score=0, int _par=0, int _prev_score=0) : 
		score(_score),
		par(_par),
		best_score(_prev_score),
		win(_win),
		OptMenu(_win ? "Level Complete!" : "Emi can't swim...")
	{
		if (!win)
			opt[num_opt++] = OPT_UNDO;
		if (!win)
			opt[num_opt++] = OPT_RESTART;
		opt[num_opt++] = win ? OPT_GOTO_MAP_CONTINUE : OPT_GOTO_MAP;

		Init();

		if (win)
		{
			r.h += FONT_SPACING * 3 + FONT_SPACING/2;
			r2.h += FONT_SPACING * 3 + FONT_SPACING/2;
		}
	}

	void Render()
	{
		OptMenu::RenderBG();
		OptMenu::RenderTitle();

		if (win)
			r2.y += FONT_SPACING * 3 + FONT_SPACING/2;
		
		OptMenu::RenderOptions();
		
		if (win)
			r2.y -= FONT_SPACING * 3 + FONT_SPACING/2;

		if (win)
		{
			int x = r.x+r.w/2;
			int y = r2.y + FONT_SPACING/2;
			if (score < best_score && score <= par)
				PrintC(true, x, y, "New Best Score: %d  Par Score: %d  Par Beaten!", score, par);
			else if (score < best_score)
				PrintC(true, x, y, "New Best Score: %d  Par Score: %d", score, par);
			else if (par && best_score)
				PrintC(true, x, y, "Score: %d  Previous Best: %d  Par Score: %d", score, best_score, par);	
			else
				PrintC(true, x, y+FONT_SPACING/2, "Well Done!  Level Completed!");	
		}
	}

	static void Undo()
	{
		Pop();
		HackKeyPress('z', 0);
	}
	bool KeyPressed(int key, int mod)
	{
		if (key=='z' || key=='u' || key==SDLK_DELETE || key==SDLK_BACKSPACE)
			return Undo(), true;
		if (key=='r' && (mod & KMOD_CTRL))
		{
			Pop();
			HackKeyPress(key, mod);
			return true;
		}
		return OptMenu::KeyPressed(key, mod);
	}
	virtual void Mouse(int x, int y, int dx, int dy, int buttons_pressed, int buttons_released, int buttons) 
	{
		if (buttons_pressed==4)
		{
			Undo();
			return;
		}
		
		OptMenu::Mouse(x, y, dx, dy, buttons_pressed, buttons_released, buttons);
	}
	void Cancel()
	{
		if (win)
			select=0, Select();
		else
			Undo();
	}
};

struct OptMenuTitle : public OptMenu
{
	OptMenuTitle(const char * t) : OptMenu(t)
	{
		renderBG = false;
		//left_align = true;
	}

	void Render()
	{
		SDL_Rect a = {0,0,SCREEN_W,SCREEN_H};
//		SDL_FillRect(screen, &a, SDL_MapRGB(screen->format, 10,25,25));

		
		SDL_BlitSurface(titlePage, &a, screen, &a);

		OptMenu::RenderTitle();
		OptMenu::RenderOptions();
	}

	void Init()
	{
		OptMenu::Init();

		int xw = SCREEN_W/6;
		r.w += xw; r2.w+=xw;
		int x = SCREEN_W - r.x - r.w;// - FONT_SPACING;
		int y = SCREEN_H - r.y - r.h + FONT_SPACING/4;// - FONT_SPACING;
		r.x += x; r2.x += x;
		r.y += y; r2.y += y;
		r.w+=20; r2.w+=20; r.h+=20; r2.h+=20;

		r.h = r2.h = SCREEN_H;
		r2.y = SCREEN_H/2;
		r.y = r2.y - FONT_SPACING - 2;
	}
};

const char *ending[] = {
	" Very Well Done! ",
	"", "", "", "", "", "",
	
	"", "", "*15,4", "", "", 
	
	"All Levels Cleared!",
	
	"", "", "*5,7", "", "", 
	
	"Not a single green hexagon is left unbroken.",
	"", 
	"Truly, you are a master of hexagon hopping!",
	
	"", "", "*9,10", "", "", 
	
	"", "Credits", "", "", "",
	"<Design & Direction:", ">Tom Beaumont", "", "",
	"<Programming:", ">Tom Beaumont", "", "",
	"<Graphics:", ">Tom Beaumont", "", "",
	"<Thanks to:", ">Kris Beaumont", "",  "",
//	"", "<Some useless facts...", "",
	"<Tools and libraries used:", "", ">Photoshop LE", ">Inno Setup", ">Wings 3D", ">MSVC", ">SDL", "", 
	"<Fonts used:", "", ">Copperplate gothic bold", ">Verdana", "", 

	"", "", "*12,14", "", "", 

	"Thanks for playing!"
};

const char *ending2[] = {
	" Absolutely Amazing! ",
	"", "", "", "", "", 
	
	"", "", "*15,4", "", "", 
	
	"All Levels Mastered!!",
	
	"", "", "*5,7", "", "", 
	
	"You crushed every last green hexagon with",
	"breathtaking efficiency!", 
	"",
	"You truly are a grand master of hexagon hopping!",
};

const int endingLen = sizeof(ending)/sizeof(ending[0]);
const int endingLen2 = sizeof(ending2)/sizeof(ending2[0]);
const int scrollMax = SCREEN_H + (endingLen+1) * FONT_SPACING;

struct Ending : public Menu
{
	struct Particle{
		double x,y,xs,ys,xa,ya;
		double time;
		int type;

		void Update(double td)
		{
			if (type==EMPTY) return;
			
			time -= td;
			x += xs*td;
			y += ys*td;
			xs += xa*td;
			ys += ya*td;
			if (type==TILE_LASER_HEAD && time<0.3)
				type=TILE_FIRE_PARTICLE_1;
			if (type==TILE_FIRE_PARTICLE_1 && time<0.1)
				type=TILE_FIRE_PARTICLE_2;

//			if (type==COLLAPSABLE || type==COLLAPSE_DOOR)
//				for (int i=int((time)*40); i<int((time+td)*40); i++)
//					new Particle(TILE_GREEN_FRAGMENT_1, x+32-rand()%63, y+20-rand()%40);

			if (y>SCREEN_H && type==TILE_GREEN_FRAGMENT)
				xa=0, ys=-ys/2, y=SCREEN_H, type=TILE_GREEN_FRAGMENT_1, time+=rand()%100*0.01;
			if (y>SCREEN_H && type==TILE_GREEN_FRAGMENT_1)
				xa=0, ys=-ys/2, y=SCREEN_H, type=TILE_GREEN_FRAGMENT_2, time+=rand()%100*0.01;
			if (y>SCREEN_H && type==TILE_GREEN_FRAGMENT_2)
				type = EMPTY;

			if (time<=0)
			{
				if (type < 10)
				{
					for (int i=0; i<40; i++)
						new Particle(TILE_LASER_HEAD, x, y);
					for (int i=0; i<40; i++)
						new Particle(TILE_GREEN_FRAGMENT + rand() % 3, x+32-rand()%63, y+20-rand()%40);
				}
				if (type==COLLAPSE_DOOR)
				{
					type = COLLAPSABLE;
					time += 1;
				}
				else
				{
					type = EMPTY;
				}
			}
			
			if (type==EMPTY) return;

			if (type<0.05 && type<15)
				RenderTile(false, tileSolid[type] ? TILE_WHITE_WALL : TILE_WHITE_TILE, int(x)+scrollX, int(y)+scrollY);
			else
				RenderTile(false, type, int(x)+scrollX, int(y)+scrollY);
		}

		Particle() : type(EMPTY) {}
		Particle(int t, int _x) : type(t), x(_x)
		{
			xa=ys=xs=0; ya=400;
			//if (t==1)
			{
				xs = rand()%100-50;
				ys=-400-rand()%200;
				//x=rand() % SCREEN_W;
				//y=rand() % SCREEN_H;
				y = SCREEN_H+20;
				time = ys/-ya;
			}
		}
		Particle(int t, double _x, double _y) : type(t)
		{
			x=_x; y=_y;
			xa=0; ya=-100;
			double r = (rand() % 2000) * PI / 1000;
			double d = rand() % 50 + 250;
			
			xs = sin(r)*d;
			ys = cos(r)*d;
			time = 1 + (rand() & 255) /255.0;
			if (t==TILE_WATER_PARTICLE || t==TILE_GREEN_FRAGMENT || t==TILE_GREEN_FRAGMENT_1)
				time = 2;
			xa=-xs/time; ya=-ys/time;
			if (t==TILE_WATER_PARTICLE || t==TILE_GREEN_FRAGMENT || t==TILE_GREEN_FRAGMENT_1)
				ya += 500, ya/=2, xa/=2, xs/=2, ys/=2;
		}
		~Particle() { type = EMPTY; }
		void* operator new(size_t sz);
	};

	Particle p[1000];

	double scroll;
	double t;
	bool goodEnding;
	static Ending* ending;
	
	Ending(bool _goodEnd) : goodEnding(_goodEnd)
	{
		memset(p, 0, sizeof(p));
		ending = this;
		renderBG = false;
		scroll = 0;
		t=0;
	}
	
	void Render()
	{
		SDL_Rect a = {0,0,SCREEN_W,SCREEN_H};
		SDL_FillRect(screen, &a, SDL_MapRGB(screen->format, 10,25,25));

		for (int i=0; i<sizeof(p)/sizeof(p[0]); i++)
			p[i].Update(t);

		int x = a.x + a.w/2;
		int xl = SCREEN_W/5;
		int xr = SCREEN_W*4/5;
		int y = SCREEN_H - int(scroll);
		for (int i=0; i<endingLen; i++)
		{
			if (y>-FONT_SPACING*2 && y<SCREEN_H+FONT_SPACING)
			{
				const char * xx = (i<endingLen2 && goodEnding) ? ending2[i] : ::ending[i];
				if (xx[0]=='<')
					Print(xl, y+FONT_SPACING, xx+1);
				else if (xx[0]=='>')
					PrintR(xr, y, xx+1);
				else if (xx[0]=='*')
				{
					RenderTile(false, atoi(xx+1), (xl+x)/2+scrollX, y+FONT_SPACING/2+scrollY);
					RenderTile(false, atoi(strchr(xx, ',')+1), (xr+x)/2+scrollX, y+FONT_SPACING/2+scrollY);
				}
				else
					PrintC(false, x, y, xx);
			}
			y+=FONT_SPACING;
		}
		if (scroll > scrollMax + FONT_SPACING*10)
			PrintC(true, x, SCREEN_H/2-FONT_SPACING/2, "The End");
	}
	
	void Cancel();
	
	bool KeyPressed(int key, int mod)
	{
		if (key=='r' && (mod & KMOD_CTRL))
		{
			time = 0;
			memset(p, 0, sizeof(p));
		}
		else
			return Menu::KeyPressed(key, mod);
		return true;
	}

	void Update(double td)
	{
		noMouse = 1;

		double old = time;

		t = td;
		if (keyState[SDLK_LSHIFT]) 
			t = td*5;
		if (keyState['0']) 
			t = MAX(-td*5, -time);

		Menu::Update(t);
		double s = scroll = time * 50;
//		if (scroll > scrollMax)
//		scroll = fmod(scroll, scrollMax);
//			scroll = 0, time = 0;

		if (old>4 && time > 4)
		{
			if (scroll < scrollMax + FONT_SPACING*17)
			{
				for (int i=int( old/2.5); i<int(time/2.5); i++)
				{
					int xs = (rand()%SCREEN_W * 6 + 1) / 8;
					for (int j=rand()%3+1; j; j--)
						new Particle(rand()&1 ? COLLAPSABLE : COLLAPSE_DOOR, xs+j*64);
				}
			}
			if (scroll > scrollMax + FONT_SPACING*27)
				Cancel();
		}

	}
};

Ending* Ending::ending = 0;
void* Ending::Particle::operator new(size_t sz)
{
	static int start = 0;
	const int max = sizeof(ending->p)/sizeof(ending->p[0]);
	for (int i=0; i<max; i++)
	{
		start++;
		if (start==max) start=0;
		if (ending->p[start].type==EMPTY)
			return &ending->p[start];
	}
	return &ending->p[rand() % max];
}

struct TitleMenu : public OptMenuTitle
{
	TitleMenu() : OptMenuTitle("")
	{
		//left_align = 1;

		SaveState p;
		freeSlot = -1;
		for (int i=0; i<MAX_GAMESLOT; i++)
		{
			char tmp[80];
			GetSlotName(i, tmp);
			FILE* f = file_open(tmp, "rb");
			if (f)
			{
				p.LoadSave(f, false);
				fclose(f);

				if (p.general.completionPercentage==100 && p.general.masteredPercentage==100)
					sprintf(optionSlotName[i], "Continue game %d (All Clear!)", i+1, p.general.completionPercentage, p.general.masteredPercentage);
				else if (p.general.completionPercentage==100)
					sprintf(optionSlotName[i], "Continue game %d (%d%% + %d%%)", i+1, p.general.completionPercentage, p.general.masteredPercentage);
				else
					sprintf(optionSlotName[i], "Continue game %d (%d%% complete)", i+1, p.general.completionPercentage);
	
				opt[num_opt++] = OPT_GAMESLOT_0 + i;
			}
			else
			{
//				sprintf(optionSlotName[i], "Start new game (slot %d)", i+1);
//				opt[num_opt++] = OPT_GAMESLOT_0 + i;

				if (freeSlot==-1)
					freeSlot = i;
			}
		}
		
		
		if (num_opt < MAX_GAMESLOT)
			opt[num_opt++] = OPT_GAMESLOT_NEW;

		opt[num_opt++] = OPT_OPTIONS;
#ifdef EDIT
		opt[num_opt++] = OPT_END;
		opt[num_opt++] = OPT_END2;
#endif
		opt[num_opt++] = OPT_QUIT;

		Init();
#ifdef EDIT
		r.y-=FONT_SPACING*2;
		r2.y-=FONT_SPACING*2;
#else
		if (num_opt==3)
			r.y+=FONT_SPACING+FONT_SPACING/2, r2.y+=FONT_SPACING+FONT_SPACING/2;
#endif
	}
	bool KeyPressed(int key, int mod);
};

struct QuitConfirmMenu : public OptMenuTitle
{
	QuitConfirmMenu() : OptMenuTitle("Quit: Are you sure?")
	{
		opt[num_opt++] = OPT_QUIT_CONFIRM;
		opt[select=num_opt++] = OPT_QUIT_CANCEL;
		Init();

		r.y += FONT_SPACING*1;
		r2.y += FONT_SPACING*2;
	}

};

struct DeleteConfirmMenu : public OptMenuTitle
{
	char tmp[80];
	int num;
	DeleteConfirmMenu(int _num) : OptMenuTitle(&tmp[0]), num(_num)
	{
		//left_align = 1;

		sprintf(tmp, "Really delete game %d?", num+1);
		opt[num_opt++] = OPT_DELETE_CONFIRM;
		opt[select=num_opt++] = OPT_DELETE_CANCEL;
		Init();

		r.y += FONT_SPACING*1;
		r2.y += FONT_SPACING*2;
	}
	void Select()
	{
		if (select<0 || select>=num_opt)
			return;
		if (opt[select] == OPT_DELETE_CONFIRM)
		{
			GetSlotName(num, tmp);
			remove(tmp);
		}
		Pop();
		Pop();
		new TitleMenu();
	}
};

bool TitleMenu::KeyPressed(int key, int mod)
{
	if (key==SDLK_DELETE || key==SDLK_BACKSPACE || key==SDLK_F2)
	{
		if (select<0 || select>=num_opt || opt[select]<OPT_GAMESLOT_0 || opt[select]>OPT_GAMESLOT_LAST)
			return true;
		int i = opt[select] - OPT_GAMESLOT_0;

		new DeleteConfirmMenu(i);

		return true;
	}
	return OptMenu::KeyPressed(key, mod);
}

struct PauseMenu : public OptMenu
{
	PauseMenu(bool isMap, bool allowGotoMap, int allowEnd, int allowEnd2) : OptMenu("Paused")
	{
		opt[num_opt++] = OPT_RESUME;
		if (!isMap)
			opt[num_opt++] = OPT_RESTART;
		opt[num_opt++] = OPT_OPTIONS;
		opt[num_opt++] = OPT_HELP;
		if (allowEnd || allowEnd2)
			opt[num_opt++] = allowEnd2 ? OPT_END2 : OPT_END;
		opt[num_opt++] = (isMap || !allowGotoMap) ? OPT_QUIT_MENU_CONFIRM : OPT_GOTO_MAP;
		Init();
	}
	virtual bool KeyPressed(int key, int mod)
	{
		if (key=='p' || key==SDLK_PAUSE)
		{
			Cancel();
			return true;
		}
		return Menu::KeyPressed(key, mod);
	}
	
};

struct OptionMenu : public OptMenuTitle
{
	bool title;
	OptionMenu(bool _title) : OptMenuTitle("Options"), title(_title)
	{
		opt[num_opt++] = OPT_FULLSCREEN;
		
		opt[num_opt++] = OPT_BACK;
		
		if (title)
		{
			OptMenuTitle::Init(), renderBG=false;
			r.y += FONT_SPACING;
			r2.y += FONT_SPACING*2;
		}
		else
			OptMenu::Init(), renderBG=true;
	}
	void Render()
	{
		if (title)
			OptMenuTitle::Render();
		else
			OptMenu::Render();
	}
};

void RenderFade(double time, int dir, int seed);

struct Fader : public Menu
{
	int dir;
	double speed;
	int result;
	Fader(int _dir, int _result, double _speed=1) : dir(_dir), result(_result), speed(_speed)
	{
		renderBG = under ? under->renderBG : true;
	}
	void Render()
	{
		if (under)
			under->Render();

		RenderFade(time, dir, (int)this);
	}
	void Update(double timedelta)
	{
		Menu::Update(timedelta * speed);
		if (result==-2)
		{
			if (time > 0.7)
				quitting = 1;
		}
		else if (time >= 0.5)
		{
			Pop();
			if (result==-1)
			{
				new TitleMenu();
				new Fader(1, -3);
			}
			if (result==-4)
			{
				Pop();	// Remove old menu
				HackKeyPress(SDLK_ESCAPE, KMOD_CTRL | KMOD_SHIFT);	// Reload map combination!
			}
			if (result==-6)
			{
				Pop();	// Remove old menu
				new Fader(1, 0, speed);
			}
			if (result==-5 || result==-7)
			{
				new Ending(result==-7);
				new Fader(1, 0, speed);
			}
		}
	}
};

void Ending::Cancel()
{
	new Fader(-1, -6, 0.3);
//	Pop();
}

void ToggleFullscreen();

void OptMenu::Select()
{
	if (select<0 || select>=num_opt)
		return;
	switch(opt[select])
	{
		case OPT_RESUME:
			Cancel();
			break;

		case OPT_RESTART:
			Cancel();
			HackKeyPress('r', KMOD_CTRL);
			break;

		case OPT_GOTO_MAP:
		case OPT_GOTO_MAP_CONTINUE:
			Pop();
			HackKeyPress(SDLK_ESCAPE, KMOD_CTRL);
			break;

		case OPT_QUIT:
			new QuitConfirmMenu();
			break;

		case OPT_FULLSCREEN:
			ToggleFullscreen();
			break;

		case OPT_QUIT_CONFIRM:
			new Fader(-1, -2);
			break;

		case OPT_QUIT_MENU_CONFIRM:
			Pop();
			new Fader(-1, -1);
			break;

		case OPT_OPTIONS:
			new OptionMenu(!activeMenu->renderBG);
			break;

		case OPT_HELP:
			new HintReview();
			break;

		case OPT_QUIT_CANCEL:
		case OPT_BACK:
			Pop();
			break;

		case OPT_END:
			new Fader(-1, -5, 0.3);
			break;

		case OPT_END2:
			new Fader(-1, -7, 0.3);
			break;

		case OPT_UNDO:
			Pop();
			HackKeyPress('z', 0);
			break;

		default:
			if (opt[select]>=OPT_GAMESLOT_0 && opt[select]<=OPT_GAMESLOT_LAST 
				|| opt[select]==OPT_GAMESLOT_NEW && freeSlot>=0)
			{
				if (opt[select]==OPT_GAMESLOT_NEW)
					GetSlotName(freeSlot, currentSlot);
				else
					GetSlotName(opt[select]-OPT_GAMESLOT_0, currentSlot);
				new Fader(-1, -4);
			}
			break;
	}
}
