1 /*
2     Copyright (C) 2005-2007 Tom Beaumont
3 
4     This program is free software; you can redistribute it and/or modify
5     it under the terms of the GNU General Public License as published by
6     the Free Software Foundation; either version 2 of the License, or
7     (at your option) any later version.
8 
9     This program is distributed in the hope that it will be useful,
10     but WITHOUT ANY WARRANTY; without even the implied warranty of
11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12     GNU General Public License for more details.
13 
14     You should have received a copy of the GNU General Public License
15     along with this program; if not, write to the Free Software
16     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17 */
18 
19 #include "config.h"
20 #include "i18n.h"
21 #include "sfx.h"
22 #include <string>
23 #include <iostream>
24 #include <cctype> // TODO: remove it later
25 #include <errno.h>
26 
27 //////////////////////////////////////////////////////
28 // Config
29 
30 
31 #ifdef _DEBUG
32 #define EDIT
33 #endif
34 
35 //#define MAP_LOCKED_VISIBLE
36 
37 #ifdef EDIT
38 //	#define MAP_EDIT_HACKS
39 	#define MAP_EDIT_HACKS_DISPLAY_UNLOCK 0
40 	#define CHEAT
41 	#define BMP_SUFFIX ".bmp"
42 #else
43 	#define USE_LEVEL_PACKFILE
44 	#define BMP_SUFFIX ".dat"
45 #endif
46 
47 
48 
49 #ifdef EDIT
50 #define GAMENAME PACKAGE_NAME " (EDIT MODE)"
51 #endif
52 #ifndef GAMENAME
53 #define GAMENAME PACKAGE_NAME
54 #endif
55 
56 #define IMAGE_DAT_OR_MASK 0xff030303 // Reduce colour depth of images slightly for better compression (and remove useless top 8 bits!)
57 #define STARTING_LEVEL "Levels\\0_green\\triangular.lev"
58 #define UNLOCK_SCORING 75
59 const char * mapname = "Levels\\map_maybe\\map.lev";
60 
61 //////////////////////////////////////////////////////
62 
63 
64 
65 #ifndef USE_OPENGL
66 
67 #include "state.h"
68 
69 #include "tiletypes.h"
70 
71 #ifdef USE_LEVEL_PACKFILE
72 #include "packfile.h"
73 #endif
74 
75 #include <unistd.h>
76 #include <limits.h>
77 #include <sys/stat.h>
78 #include <sys/types.h>
79 
80 #ifndef PATH_MAX
81 #define PATH_MAX 4096
82 #endif
83 
84 void RenderTile(bool reflect, int t, int x, int y, int cliplift=-1);
85 
86 int keyState[SDLK_LAST] = {0};
87 
file_open(const char * file,const char * flags)88 FILE *file_open( const char *file, const char *flags )
89 {
90 	extern String base_path;
91 	static String filename; // static to reduce memory alloc/free calls.
92 
93 	//printf("file_open( \"%s\", \"%s\" )\n", file, flags );
94 	if (strncmp(file, "save", 4) == 0)
95 	{
96 #ifndef WIN32
97 		const char *home = getenv("HOME");
98 		if (home)
99 		{
100 			char save_path[PATH_MAX];
101 			snprintf(save_path, sizeof(save_path), "%s/.hex-a-hop", home);
102 			if (!strchr(flags, 'r'))
103 				if (mkdir(save_path, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) != -1)
104 					printf("Creating directory \"%s\"\n", (const char *)save_path);
105 			strncat(save_path, "/", sizeof(save_path));
106 			filename = save_path;
107 			filename += file;
108 		}
109 		else
110 		{
111 			filename = "/tmp/";
112 			filename += file;
113 		}
114 #else
115 		filename = base_path + file;
116 #endif
117 	}
118 	else
119 		filename = base_path + file;
120 	//printf("   -> \"%s\"\n", (const char*) filename );
121 
122 	filename.fix_backslashes();
123 	FILE* f = fopen( filename, flags );
124 
125 	if (!f && strncmp(file, "save", 4) != 0)
126 	{
127 		printf("Warning: unable to open file \"%s\" for %s\n", (const char*)filename, strchr(flags, 'r') ? "reading" : "writing");
128 	}
129 
130 	return f;
131 }
132 
133 
134 #ifdef MAP_EDIT_HACKS
135 	static const short value_order[]={
136 		//WALL,
137 		//COLLAPSE_DOOR2,
138 		//COLLAPSABLE3
139 		//SWITCH
140 		//EMPTY, NORMAL,
141 
142 		COLLAPSABLE,
143 		TRAMPOLINE,
144 		COLLAPSE_DOOR, COLLAPSABLE2,
145 		GUN,
146 		FLOATING_BALL,
147 		SPINNER,
148 		TRAP,
149 		0x100,
150 		LIFT_DOWN, LIFT_UP,
151 		BUILDER,
152 		0x200,
153 	};
154 #endif
155 
156 //#define PROGRESS_FILE "progress.dat"
157 
158 #define PI (3.1415926535897931)
159 #define PI2 (PI*2)
160 #define MAX(a,b) ((a)>(b) ? (a) : (b))
161 #define MIN(a,b) ((a)<(b) ? (a) : (b))
162 #define ABS(a) ((a)<0 ? -(a) : (a))
163 
164 #define WATER_COLOUR 31 | ((IMAGE_DAT_OR_MASK>>16)&255), 37 | ((IMAGE_DAT_OR_MASK>>8)&255), 135 | ((IMAGE_DAT_OR_MASK>>0)&255)
165 
166 #define ROTATION_TIME 0.25
167 #define BUILD_TIME 1
168 #define LASER_LINE_TIME 0.7
169 #define LASER_FADE_TIME 0.1
170 #define LASER_SEGMENT_TIME 0.01
171 #define LIFT_TIME 0.5
172 #define JUMP_TIME 0.4
173 
174 #define X(NAME,FILE,ALPHA) SDL_Surface* NAME = 0;
175 #include "gfx_list.h"
176 int scrollX=0, scrollY=0, initScrollX=0, initScrollY=0;
177 int mapRightBound = 0;
178 int mapScrollX = 0;
179 bool showScoring = false;
180 bool hintsDone = false;
181 
182 enum {
183 	TILE_SPLASH_1 = 17,
184 	TILE_SPLASH_2,
185 	TILE_SPLASH_3,
186 
187 	TILE_SPHERE = 20,
188 	TILE_SPHERE_OPEN,
189 	TILE_SPHERE_DONE,
190 	TILE_SPHERE_PERFECT,
191 	TILE_LOCK,
192 
193 	TILE_LIFT_BACK,
194 	TILE_LIFT_FRONT,
195 	TILE_LIFT_SHAFT,
196 	TILE_BLUE_FRONT,
197 	TILE_GREEN_FRONT,
198 
199 	TILE_LINK_0 = 30,
200 	TILE_LINK_1,
201 	TILE_LINK_2,
202 	TILE_LINK_3,
203 	TILE_LINK_4,
204 	TILE_LINK_5,
205 	TILE_GREEN_FRAGMENT,
206 	TILE_GREEN_FRAGMENT_1,
207 	TILE_GREEN_FRAGMENT_2,
208 	TILE_ITEM2,
209 
210 	TILE_WATER_MAP = 40,
211 	TILE_GREEN_CRACKED,
212 	TILE_GREEN_CRACKED_WALL,
213 	TILE_BLUE_CRACKED,
214 	TILE_BLUE_CRACKED_WALL,
215 	TILE_LASER_HEAD,
216 	TILE_FIRE_PARTICLE_1,
217 	TILE_FIRE_PARTICLE_2,
218 	TILE_WATER_PARTICLE,
219 
220 	TILE_LASER_0 = 50,
221 	TILE_LASER_FADE_0 = 53,
222 	TILE_BLUE_FRAGMENT = 56,
223 	TILE_BLUE_FRAGMENT_1,
224 	TILE_BLUE_FRAGMENT_2,
225 	TILE_ITEM1,
226 	TILE_LASER_REFRACT = 60,
227 	TILE_ICE_LASER_REFRACT = TILE_LASER_REFRACT+6,
228 	TILE_WHITE_TILE,
229 	TILE_WHITE_WALL,
230 	TILE_BLACK_TILE,
231 
232 };
233 
234 const int colours[] = {
235 	#define X(n,col, solid) col,
236 	#include "tiletypes.h"
237 };
238 
239 const int tileSolid[] = {
240 	#define X(n,col, solid) solid,
241 	#include "tiletypes.h"
242 };
243 
ChangeSuffix(char * filename,char * newsuffix)244 void ChangeSuffix(char* filename, char* newsuffix)
245 {
246 	int len = strlen(filename);
247 	int i = len-1;
248 	while (i>=0 && filename[i]!='\\' && filename[i]!='.' && filename[i]!='/')
249 		i--;
250 	if (filename[i]=='.')
251 		strcpy(filename+i+1, newsuffix);
252 	else
253 	{
254 		strcat(filename, ".");
255 		strcat(filename, newsuffix);
256 	}
257 }
258 
259 bool isMap=false, isRenderMap=false;
260 int isFadeRendering=0;
261 
262 /*
263 	 |--|     |--|   TILE_W1
264 	 |--------|	 	 TILE_W2
265 		|-----|	 	 TILE_WL
266 	 |-----------|	 TILE_W3
267 
268 		*-----*		-			-
269 	   /       \    |TILE_H1	|TILE_H2
270 	  /         \	|			|
271 	 *           *	-			|
272 	  \         /				|
273 	   \       /				|
274 		*-----*					-
275 
276 	WL = sqrt(h1*h1 + w1*w1)
277 	wl**2 = h1**2 + w1**2
278 
279 	w1 = sin60.wL
280 
281 */
282 
283 #if 1
284 	#define TILE_W1 18
285 	#define TILE_W3	64
286 	#define GFX_SIZE TILE_W3
287 	#define TILE_W2 (TILE_W3-TILE_W1)
288 	#define TILE_H1 TILE_W1
289 	#define TILE_HUP 22	//extra visible height of wall (used for determining whether a wall was clicked on)
290 	#define TILE_H2 (TILE_H1*2)
291 	#define TILE_WL (TILE_W2-TILE_W1)
292 	#define TILE_H_LIFT_UP   26
293 	#define TILE_H_REFLECT_OFFSET 24
294 	#define TILE_HUP2 TILE_H_LIFT_UP	// Displacement of object on top of wall
295 	#define FONT_SPACING 25
296 	#define FONT_X_SPACING (-1)	// -1 in order to try and overlap the black borders of adjacent characters
297 #else
298 	#define TILE_WL 30
299 	#define TILE_W1 (TILE_WL/2)
300 	#define TILE_W2 (TILE_W1+TILE_WL)
301 	#define TILE_W3 (TILE_W1+TILE_W2)
302 	#define TILE_H1 (TILE_WL*0.8660254037844386)
303 	#define TILE_H2 (TILE_H1*2)
304 #endif
305 
306 #define MAX_DIR 6
307 
308 SDL_Rect tile[2][70];
309 short tileOffset[2][70][2];
Peek(SDL_Surface * i,int x,int y)310 int Peek(SDL_Surface* i, int x, int y)
311 {
312 	if (x<0 || y<0 || x>=i->w || y>=i->h)
313 		return 0;
314 	unsigned int p=0;
315 	const int BytesPerPixel = i->format->BytesPerPixel;
316 	const int BitsPerPixel = i->format->BitsPerPixel;
317 	if (BitsPerPixel==8)
318 		p = ((unsigned char*)i->pixels)[i->pitch*y + x*BytesPerPixel];
319 	else if (BitsPerPixel==15 || BitsPerPixel==16)
320 		p = *(short*)(((char*)i->pixels) + (i->pitch*y + x*BytesPerPixel));
321 	else if (BitsPerPixel==32)
322 		p = *(unsigned int*)(((char*)i->pixels) + (i->pitch*y + x*BytesPerPixel));
323 	else if (BitsPerPixel==24)
324 		p = (int)((unsigned char*)i->pixels)[i->pitch*y + x*BytesPerPixel]
325 		  | (int)((unsigned char*)i->pixels)[i->pitch*y + x*BytesPerPixel] << 8
326 		  | (int)((unsigned char*)i->pixels)[i->pitch*y + x*BytesPerPixel] << 16;
327 
328 	return p;
329 }
IsEmpty(SDL_Surface * im,int x,int y,int w,int h)330 bool IsEmpty(SDL_Surface* im, int x, int y, int w, int h)
331 {
332 	for (int i=x; i<x+w; i++)
333 		for (int j=y; j<y+h; j++)
334 			if (Peek(im,i,j) != Peek(im,0,im->h-1))
335 				return false;
336 	return true;
337 }
338 
MakeTileInfo()339 void MakeTileInfo()
340 {
341 	for (int i=0; i<140; i++)
342 	{
343 		SDL_Rect r = {(i%10)*GFX_SIZE, ((i/10)%7)*GFX_SIZE, GFX_SIZE, GFX_SIZE};
344 		short * outOffset = tileOffset[i/70][i%70];
345 		SDL_Surface * im = (i/70) ? tileGraphicsR : tileGraphics;
346 
347 		outOffset[0] = outOffset[1] = 0;
348 
349 		while (r.h>1 && IsEmpty(im, r.x, r.y, r.w, 1)) r.h--, r.y++, outOffset[1]++;
350 		while (r.h>1 && IsEmpty(im, r.x, r.y+r.h-1, r.w, 1)) r.h--;
351 		while (r.w>1 && IsEmpty(im, r.x, r.y, 1, r.h)) r.w--, r.x++, outOffset[0]++;
352 		while (r.w>1 && IsEmpty(im, r.x+r.w-1, r.y, 1, r.h)) r.w--;
353 
354 		tile[i/70][i%70] = r;
355 	}
356 }
357 
358 #include "text.h"
359 #include "savestate.h"
360 #include "menus.h"
361 #include "level_list.h"
362 
GetStuff()363 void SaveState::GetStuff()
364 {
365 	general.hintFlags = HintMessage::flags;
366 }
ApplyStuff()367 void SaveState::ApplyStuff()
368 {
369 	HintMessage::flags = general.hintFlags;
370 }
371 
372 
373 // somewhere else Tile map[][] is assigned to an unsigned char not int32_t
374 // but the data file format expects it to be 32 bit wide!??
375 typedef int32_t Tile;
376 typedef int Dir;
377 struct Pos{
378 	int32_t x,y;
PosPos379 	Pos() : x(0), y(0) {}
PosPos380 	Pos(int a, int b) : x(a), y(b) {}
operator ==Pos381 	bool operator == (Pos const & p) const
382 	{
383 		return x==p.x && y==p.y;
384 	}
operator +Pos385 	Pos operator + (Dir const d) const
386 	{
387 		return Pos(
388 			x + ((d==1 || d==2) ?  1 : (d==4 || d==5) ? -1 : 0),
389 			y + ((d==0 || d==1) ? -1 : (d==3 || d==4) ?  1 : 0)
390 		);
391 	}
getScreenXPos392 	int getScreenX() const {
393 		return x*TILE_W2;
394 	}
getScreenYPos395 	int getScreenY() const {
396 		return x*TILE_H1 + y*TILE_H2;
397 	}
GetFromWorldPos398 	static Pos GetFromWorld(double x, double y)
399 	{
400 		x += TILE_W3/2;
401 		y += TILE_H1;
402 		int tx, ty;
403 		tx = (int)floor(x/TILE_W2);
404 		y -= tx*TILE_H1;
405 		ty = (int)floor(y/TILE_H2);
406 
407 		y -= ty * TILE_H2;
408 		x -= tx * TILE_W2;
409 
410 		if (x < TILE_W1 && y < TILE_H1)
411 			if (x*TILE_H1 + y * TILE_W1 < TILE_H1*TILE_W1)
412 				tx--;
413 		if (x < TILE_W1 && y > TILE_H1)
414 			if (x*TILE_H1 + (TILE_H2-y) * TILE_W1 < TILE_H1*TILE_W1)
415 				tx--, ty++;
416 
417 		return Pos(tx, ty);
418 	}
419 };
420 Pos mousep(0,0), keyboardp(4,20);
421 
422 class RenderObject;
423 
424 struct RenderStage
425 {
~RenderStageRenderStage426 	virtual ~RenderStage() {}
427 	virtual void Render(RenderObject* r, double time, bool reflect) = 0;
GetDepthRenderStage428 	virtual int GetDepth(double /*time*/) { return 1; }
429 };
430 
431 class RenderObject
432 {
433 	RenderStage** stage;
434 	double* time;
435 	int numStages;
436 	int maxStages;
437 	int currentStage;
438 public:
439 	double seed;
440 	double currentTime;
441 private:
442 
Reserve()443 	void Reserve()
444 	{
445 		if (maxStages <= numStages)
446 		{
447 			maxStages = maxStages ? maxStages*2 : 4;
448 			stage = (RenderStage**)	realloc(stage, sizeof(stage[0])*maxStages);
449 			time  = (double*)		realloc(time, sizeof(time[0])*maxStages);
450 		}
451 	}
452 public:
RenderObject()453 	RenderObject() : stage(0), time(0), numStages(0), maxStages(0), currentStage(0)
454 	{
455 		// TODO:	use a random number with better range
456 		//			or maybe make seed an int or float...
457 		seed = rand() / (double)RAND_MAX;
458 	}
~RenderObject()459 	~RenderObject()
460 	{
461 		free(stage); free(time);
462 	}
Active(double t)463 	bool Active(double t)
464 	{
465 		if (numStages==0) return false;
466 		if (t < time[0]) return false;
467 		return true;
468 	}
UpdateCurrent(double t)469 	void UpdateCurrent(double t)
470 	{
471 		if (currentStage >= numStages) currentStage = numStages-1;
472 		if (currentStage < 0) currentStage = 0;
473 
474 		while (currentStage>0 && time[currentStage]>t)
475 			currentStage--;
476 		while (currentStage<numStages-1 && time[currentStage+1]<=t)
477 			currentStage++;
478 
479 		currentTime = t;
480 	}
GetStage(double t)481 	RenderStage* GetStage(double t)
482 	{
483 		if (t==-1 && numStages>0)
484 			return stage[numStages-1];
485 
486 		if (!Active(t)) return 0;
487 		UpdateCurrent(t);
488 		return stage[currentStage];
489 	}
GetLastTime()490 	double GetLastTime()
491 	{
492 		return numStages>0 ? time[numStages-1] : -1;
493 	}
Render(double t,bool reflect)494 	void Render(double t, bool reflect)
495 	{
496 		if (!Active(t))
497 			return;
498 		UpdateCurrent(t);
499 		stage[currentStage]->Render(this, t - time[currentStage], reflect);
500 	}
GetDepth(double t)501 	int GetDepth(double t)
502 	{
503 		if (!Active(t))
504 			return -1;
505 		UpdateCurrent(t);
506 		return stage[currentStage]->GetDepth(t - time[currentStage]);
507 	}
Reset(double t)508 	void Reset(double t)
509 	{
510 		if (t<0)
511 			numStages = currentStage = 0;
512 		else
513 		{
514 			while (numStages > 0 && time[numStages-1] >= t)
515 				numStages--;
516 		}
517 	}
Wipe()518 	void Wipe()
519 	{
520 		if (currentStage > 0 && numStages > 0)
521 		{
522 			memmove(&time[0], &time[currentStage], sizeof(time[0]) * (numStages-currentStage));
523 			memmove(&stage[0], &stage[currentStage], sizeof(stage[0]) * (numStages-currentStage));
524 			numStages -= currentStage;
525 			currentStage = 0;
526 		}
527 	}
Add(RenderStage * s,double t)528 	void Add(RenderStage* s, double t)
529 	{
530 		int i=0;
531 
532 		if (currentStage<numStages && time[currentStage]<=t)
533 			i = currentStage;
534 
535 		while (i<numStages && time[i]<t)
536 			i++;
537 
538 		if (i<numStages && time[i]==t)
539 			stage[i]=s;
540 		else
541 		{
542 			Reserve();
543 
544 			if (i<numStages)
545 			{
546 				memmove(&time[i+1], &time[i], (numStages-i) * sizeof(time[0]));
547 				memmove(&stage[i+1], &stage[i], (numStages-i) * sizeof(stage[0]));
548 			}
549 
550 			numStages++;
551 			time[i] = t;
552 			stage[i] = s;
553 		}
554 	}
555 };
556 
557 class WorldRenderer
558 {
559 	#define SIZE 30
560 	#define FX 10
561 	RenderObject tile[SIZE][SIZE][2];
562 	RenderObject fx[FX];
563 	int fxPos;
564 
565 public:
566 	RenderObject player;
567 	RenderObject dummy;
568 
WorldRenderer()569 	WorldRenderer()
570 	{
571 		Reset();
572 	}
573 
Reset(double t=-1)574 	void Reset(double t = -1)
575 	{
576 		fxPos = 0;
577 		player.Reset(t);
578 		dummy.Reset(-1);
579 
580 		for (int i=0; i<SIZE; i++)
581 			for (int j=0; j<SIZE; j++)
582 				for (int q=0; q<2; q++)
583 					tile[i][j][q].Reset(t);
584 
585 		for (int j=0; j<FX; j++)
586 			fx[j].Reset(t);
587 	}
588 
Wipe()589 	void Wipe()
590 	{
591 		player.Wipe();
592 		dummy.Reset(-1);
593 
594 		for (int i=0; i<SIZE; i++)
595 			for (int j=0; j<SIZE; j++)
596 				for (int q=0; q<2; q++)
597 					tile[i][j][q].Wipe();
598 
599 		for (int j=0; j<FX; j++)
600 			fx[j].Wipe();
601 	}
602 
Visible(Pos p)603 	bool Visible(Pos p)
604 	{
605 		int x0 = (scrollX+TILE_W2) / TILE_W2;
606 		int x1 = (scrollX+SCREEN_W+TILE_W3+TILE_W1) / TILE_W2;
607 		if (p.x<0 || p.y<0 || p.x>=SIZE || p.y>=SIZE) return false;
608 		if (p.x<x0) return false;
609 		if (p.x>=x1-1) return false;
610 		for (int j0=0; j0<SIZE*3; j0++)
611 		{
612 			if (j0 * TILE_H1 < scrollY-TILE_H1) continue;
613 			if (j0 * TILE_H1 > scrollY+SCREEN_H+TILE_H1) break;
614 			int i = j0&1;
615 			int j = j0>>1;
616 			j -= (x0-i)/2;
617 			i += (x0-i)/2*2;
618 			if (j>=SIZE) i+=(j+1-SIZE)*2, j=SIZE-1;
619 			for (; i<x1 && j>=0; i+=2, j--)
620 			{
621 				if (Pos(i,j)==p)
622 					return true;
623 			}
624 		}
625 		return false;
626 	}
627 
Render(double t,bool reflect)628 	void Render(double t, bool reflect)
629 	{
630 		dummy.Reset(-1);
631 
632 		int playerDepth = player.GetDepth(t);
633 		if (reflect) playerDepth-=4;
634 		if (playerDepth<0)
635 			player.Render(t, reflect);
636 
637 		int x0 = (scrollX+TILE_W2) / TILE_W2;
638 		int x1 = (scrollX+SCREEN_W+TILE_W3+TILE_W1) / TILE_W2;
639 		x0 = MAX(x0, 0);
640 		x1 = MIN(x1, SIZE);
641 		for (int j0=0; j0<SIZE*3; j0++)
642 		{
643 			if (j0 * TILE_H1 < scrollY-TILE_H1) continue;
644 			if (j0 * TILE_H1 > scrollY+SCREEN_H+TILE_H1) break;
645 			int i = j0&1;
646 			int j = j0>>1;
647 			j -= (x0-i)/2;
648 			i += (x0-i)/2*2;
649 			if (j>=SIZE) i+=(j+1-SIZE)*2, j=SIZE-1;
650 			for (; i<x1 && j>=0; i+=2, j--)
651 			{
652 				for (int q=reflect?1:0; q!=2 && q!=-1; q += (reflect ? -1 : 1))
653 					if (tile[i][j][q].Active(t))
654 					{
655 						tile[i][j][q].Render(t, reflect);
656 					}
657 			}
658 
659 			if (playerDepth==j0 || (j0==SIZE*3 && playerDepth>j0))
660 				player.Render(t, reflect);
661 		}
662 
663 		for (int j=0; j<FX; j++)
664 			if(fx[j].Active(t))
665 			{
666 				fx[j].Render(t, reflect);
667 			}
668 
669 	}
operator ()()670 	RenderObject & operator () ()
671 	{
672 		fxPos++;
673 		if (fxPos==FX) fxPos = 0;
674 		return fx[fxPos];
675 	}
operator ()(Pos const & p,bool item=false)676 	RenderObject & operator () (Pos const & p, bool item=false)
677 	{
678 		if (p.x<0 || p.y<0 || p.x>=SIZE || p.y>=SIZE)
679 			return dummy;
680 		return tile[p.x][p.y][item ? 1 : 0];
681 	}
682 };
683 
RenderTile(bool reflect,int t,int x,int y,int cliplift)684 void RenderTile(bool reflect, int t, int x, int y, int cliplift)
685 {
686 	SDL_Rect src = tile[reflect][t];
687 	SDL_Rect dst = {x-scrollX-GFX_SIZE/2, y-scrollY-GFX_SIZE+TILE_H1, 0, 0};
688 	dst.x += tileOffset[reflect][t][0];
689 	dst.y += tileOffset[reflect][t][1];
690 	if (reflect)
691 		dst.y += TILE_H_REFLECT_OFFSET;
692 	if (cliplift==-1 || reflect)
693 	{
694 	//	dst.w=src.w; dst.h=src.h;
695 	//	SDL_FillRect(screen, &dst, rand());
696 		SDL_BlitSurface(reflect ? tileGraphicsR : tileGraphics, &src, screen, &dst);
697 	}
698 	else
699 	{
700 		src.h -= cliplift;
701 		if (src.h > TILE_W1)
702 		{
703 			src.h -= TILE_W1/2;
704 			SDL_BlitSurface(tileGraphics, &src, screen, &dst);
705 			src.y += src.h;
706 			dst.y += src.h;
707 			src.h = TILE_W1/2;
708 		}
709 		if (src.h > 0)
710 		{
711 			src.w -= TILE_W1*2, src.x += TILE_W1;
712 			dst.x += TILE_W1;
713 			SDL_BlitSurface(tileGraphics, &src, screen, &dst);
714 		}
715 	}
716 }
RenderGirl(bool reflect,int r,int frame,int x,int y,int h)717 void RenderGirl(bool reflect, int r, int frame, int x, int y, int h)
718 {
719 	int sx = r * 64;
720 	int sy = frame * 80*2;
721 	if (reflect)
722 		y += TILE_H_REFLECT_OFFSET+20+h, sy += 80;
723 	else
724 		y -= h;
725 	SDL_Rect src = {sx, sy, 64, 80};
726 	SDL_Rect dst = {x-scrollX-32, y-scrollY-65, 0, 0};
727 	SDL_BlitSurface(girlGraphics, &src, screen, &dst);
728 }
729 
730 struct ItemRender : public RenderStage
731 {
732 	int item;
733 	Pos p;
734 	int water;
735 
ItemRenderItemRender736 	ItemRender(int i2, int _water, Pos const & _p) :  item(i2), p(_p), water(_water)
737 	{}
738 
TranslateItemRender739 	double Translate(double seed, double time)
740 	{
741 		double bob = time*2 + seed*PI2;
742 		return sin(bob)*4;
743 	}
744 
RenderItemRender745 	void Render(RenderObject* r, double time, bool reflect)
746 	{
747 		if (item==0)
748 			return;
749 
750 		int y = -5 + (int)Translate(r->seed, r->currentTime + time);
751 		if (reflect)
752 			y=-y;
753 		if (!reflect && !water)
754 			RenderTile( false, TILE_SPHERE, p.getScreenX(), p.getScreenY());
755 		RenderTile(
756 			reflect,
757 			item==1 ? TILE_ITEM1 : TILE_ITEM2,
758 			p.getScreenX(), p.getScreenY()+y
759 		);
760 	}
761 };
762 
RenderFade(double time,int dir,int seed)763 void RenderFade(double time, int dir, int seed)
764 {
765 	int ys=0;
766 	srand(seed);
767 	for(int x=rand()%22-11; x<SCREEN_W+22; x+=32, ys ^= 1)
768 	{
769 		for (int y=ys*20; y<SCREEN_H+30; y+=40)
770 		{
771 			double a = (rand()&0xff)*dir;
772 			double b = (time * 0x400 + (y - SCREEN_H) * 0x140/SCREEN_H)*dir;
773 			if (a >= b)
774 			{
775 				RenderTile(false, TILE_BLACK_TILE, x+scrollX, y+scrollY);
776 			}
777 		}
778 	}
779 }
780 
781 struct FadeRender : public RenderStage
782 {
783 	int seed;
784 	int dir;
FadeRenderFadeRender785 	FadeRender(int d=-1) : seed(rand()), dir(d)
786 	{
787 		isFadeRendering = d;
788 	}
789 
RenderFadeRender790 	void Render(RenderObject* /*r*/, double time, bool reflect)
791 	{
792 		if (reflect) return;
793 		if (time > 0.5)
794 		{
795 			if (dir==1) dir=0, isFadeRendering=0;
796 			return;
797 		}
798 		RenderFade(time, dir, seed);
799 	}
800 };
801 
802 struct ScrollRender : public RenderStage
803 {
804 	int x,y;
805 	bool done;
ScrollRenderScrollRender806 	ScrollRender(int a,int b) : x(a), y(b), done(false) {}
807 
RenderScrollRender808 	void Render(RenderObject* /*r*/, double /*time*/, bool /*reflect*/)
809 	{
810 		if (done) return;
811 		scrollX = x, scrollY = y;
812 		isRenderMap = isMap;
813 		done = true;
814 	}
815 };
816 
817 struct LevelSelectRender : public RenderStage
818 {
819 	Pos p;
820 	int item;
821 	int adj;
822 #ifdef MAP_EDIT_HACKS
823 	int magic;
824 #endif
825 
LevelSelectRenderLevelSelectRender826 	LevelSelectRender(Pos const & _p, int i2, int adj) : p(_p), item(i2), adj(adj)
827 	{}
828 
RenderLevelSelectRender829 	void Render(RenderObject* /*r*/, double /*time*/, bool reflect)
830 	{
831 		if (item==0)
832 			return;
833 
834 	#ifndef MAP_LOCKED_VISIBLE
835 		if (item==1) return;
836 	#endif
837 
838 	if (!reflect && adj)
839 	{
840 		for (int i=0; i<MAX_DIR; i++)
841 		{
842 			if (adj & (1 << i))
843 				RenderTile( false, TILE_LINK_0+i, p.getScreenX(), p.getScreenY());
844 		}
845 	}
846 
847 	if (item < 0)
848 		return;
849 
850 	if (!reflect)
851 	{
852 		RenderTile(
853 			reflect,
854 			TILE_SPHERE + item-1,
855 			p.getScreenX(), p.getScreenY()
856 		);
857 
858 		#ifdef MAP_EDIT_HACKS
859 			int x = p.getScreenX()-scrollX, y = p.getScreenY()-scrollY;
860 			Print(x+5,y-25,"%d",magic);
861 		#endif
862 	}
863 	}
864 };
865 
866 struct ItemCollectRender : public ItemRender
867 {
ItemCollectRenderItemCollectRender868 	ItemCollectRender(int i2, Pos const & p) :  ItemRender(i2, 0, p)
869 	{}
870 
RenderItemCollectRender871 	void Render(RenderObject* /*r*/, double /*time*/, bool /*reflect*/)
872 	{
873 	}
874 };
875 
GetLiftHeight(double time,int t)876 int GetLiftHeight(double time, int t)
877 {
878 	if (t==LIFT_UP)
879 		time = LIFT_TIME-time;
880 	time = time / LIFT_TIME;
881 	if (time > 1)
882 		time = 1;
883 	if (time < 0)
884 		time = 0;
885 	time = (3 - 2*time)*time*time;
886 	if (t==LIFT_UP)
887 		time = (3 - 2*time)*time*time;
888 	if (t==LIFT_UP)
889 		return (int)((TILE_H_LIFT_UP+4) * time);
890 	else
891 		return (int)((TILE_H_LIFT_UP-4) * time) + 4;
892 }
893 
894 struct TileRender : public RenderStage
895 {
896 	int special;
897 	int t;
898 	Pos p;
899 	double specialDuration;
900 
TileRenderTileRender901 	TileRender(int i, Pos const & _p, int _special=0) : special(_special), t(i), p(_p), specialDuration(LASER_LINE_TIME)
902 	{}
903 
RenderTileRender904 	void Render(RenderObject* r, double time, bool reflect)
905 	{
906 		if (t==0 && special==0)
907 			return;
908 
909 		if (special && (t==LIFT_UP || t==LIFT_DOWN) && time<LIFT_TIME)
910 		{
911 			int y = GetLiftHeight(time, t);
912 			if (!reflect)
913 			{
914 				RenderTile(reflect, TILE_LIFT_BACK, p.getScreenX(), p.getScreenY());
915 				RenderTile(reflect, TILE_LIFT_SHAFT, p.getScreenX(), p.getScreenY()+y, y-8);
916 				RenderTile(reflect, TILE_LIFT_FRONT, p.getScreenX(), p.getScreenY());
917 			}
918 			else
919 			{
920 				RenderTile(reflect, TILE_LIFT_SHAFT, p.getScreenX(), p.getScreenY()-y, y);
921 				RenderTile(reflect, LIFT_DOWN, p.getScreenX(), p.getScreenY());
922 			}
923 		}
924 		else if (special && (t==EMPTY || t==TRAP) && !reflect && time < specialDuration)
925 		{
926 			if (t == TRAP)
927 			{
928 				if (time < specialDuration-LASER_FADE_TIME)
929 					RenderTile(reflect, TILE_ICE_LASER_REFRACT, p.getScreenX(), p.getScreenY());
930 				else
931 					RenderTile(reflect, t, p.getScreenX(), p.getScreenY());
932 			}
933 			int base = ((t==EMPTY) ? TILE_LASER_0 : TILE_LASER_REFRACT);
934 			if (t==EMPTY && time >= specialDuration-LASER_FADE_TIME)
935 				base = TILE_LASER_FADE_0;
936 
937 			int foo=special;
938 			for(int i=0; foo; foo>>=1, i++)
939 				if (foo & 1)
940 					RenderTile(reflect, base+i, p.getScreenX(), p.getScreenY());
941 		}
942 		else if (t==FLOATING_BALL)
943 		{
944 			int y = int(1.8 * sin(r->seed*PI + time*4));
945 			if (special==512)
946 			{
947 				if (time > 2) return;
948 				if (reflect) return;
949 				srand(int(r->seed * 0xfff));
950 				for (int i=0; i<20 - int(time*10); i++)
951 				{
952 					int x = int((((rand() & 0xfff) - 0x800) / 10) * time);
953 					int y = int((((rand() & 0xfff) - 0x800) / 10) * time);
954 					RenderTile(true, 19 + ((i+int(time*5))&1)*10, p.getScreenX() + x, p.getScreenY() - 14 + y);
955 				}
956 
957 				if (time < 0.05)
958 					RenderTile(true, 18, p.getScreenX(), p.getScreenY() - 14);
959 			}
960 			else if (special)
961 				RenderBoat(reflect, int(special)&255,  p.getScreenX(), p.getScreenY(), y);
962 			else
963 				RenderTile(reflect, t, p.getScreenX(), p.getScreenY() + (reflect ? -y : y));
964 		}
965 		else if (t != EMPTY)
966 			RenderTile(reflect, t, p.getScreenX(), p.getScreenY());
967 	}
RenderBoatTileRender968 	static void RenderBoat(bool reflect, int d, int x, int y, int yo)
969 	{
970 		if (reflect)
971 			RenderGirl(reflect, d, 0, x, y, -yo);
972 		RenderTile(reflect, FLOATING_BALL, x, y+yo);
973 		if (!reflect)
974 		{
975 			RenderGirl(reflect, d, 0, x, y, -yo);
976 			RenderTile(true, 17, x, y+yo-TILE_H_REFLECT_OFFSET);
977 		}
978 	}
979 };
980 
981 struct TileRotateRender : public TileRender
982 {
983 	Dir d;
984 //	int range;
985 	int mode;
TileRotateRenderTileRotateRender986 	TileRotateRender(int i, Pos const & p, Dir _d, int m) : TileRender(i, p), d(_d), mode(m)
987 	{}
RenderTileRotateRender988 	void Render(RenderObject* r, double time, bool reflect)
989 	{
990 		if (t==0)
991 			return;
992 		double f = time / ROTATION_TIME;
993 
994 		if (mode & 1) f += 0.5;
995 		if (f<1 && f>0)
996 		{
997 			if (mode & 2)
998 				;
999 			else
1000 				f = (3-2*f)*f*f;
1001 		}
1002 
1003 		if (mode & 1) f=1-f; else f=f;
1004 		if (f<0) f=0;
1005 
1006 		if (f >= 1)
1007 			TileRender::Render(r, time, reflect);
1008 		else
1009 		{
1010 			Pos dd = (Pos(0,0)+d);
1011 			int x = p.getScreenX() + int(dd.getScreenX()*(f));
1012 			int y = p.getScreenY() + int(dd.getScreenY()*(f));
1013 
1014 			if (mode & 2)
1015 				RenderBoat(reflect, (mode&1) ? (d+MAX_DIR/2)%MAX_DIR : d, x, y, 2);
1016 			else
1017 				RenderTile(reflect, t, x, y);
1018 		}
1019 	}
1020 };
1021 
1022 struct LaserRender : public RenderStage
1023 {
1024 	Pos p;
1025 	Dir d;
1026 	int range;
1027 
LaserRenderLaserRender1028 	LaserRender(Pos _p, int dir, int r) : p(_p), d(dir), range(r)
1029 	{}
1030 
RenderLaserRender1031 	void Render(RenderObject* /*r*/, double /*time*/)
1032 	{
1033 	}
1034 };
1035 
1036 struct ExplosionRender : public RenderStage
1037 {
1038 	Pos p;
1039 	int seed;
1040 	int power;
1041 	int type;
1042 
ExplosionRenderExplosionRender1043 	ExplosionRender(Pos _p, int _pow=0, int t=0) : p(_p), power(_pow), type(t)
1044 	{
1045 		seed = rand();
1046 	}
1047 
GetDepthExplosionRender1048 	virtual int GetDepth(double /*time*/)
1049 	{
1050 		return p.x + p.y*2;
1051 	}
1052 
RenderExplosionRender1053 	void Render(RenderObject* /*r*/, double time, bool reflect)
1054 	{
1055 		if (type==1 && time > 2.5)
1056 			type = -1, new WinLoseScreen(false);
1057 
1058 	//	if (reflect) return;
1059 		if (time > 3) return;
1060 		srand(seed);
1061 		int q = 50 - int(time * 35);
1062 		if (power) q*=2;
1063 		if (type) q = 50;
1064 		for (int i=0; i<q; i++)
1065 		{
1066 			int x = p.getScreenX();
1067 			int y = p.getScreenY() + (rand() & 31)-16;
1068 			int xs = ((rand() & 63) - 32);
1069 			int ys = (-10 - (rand() & 127)) * (1+power);
1070 			if (type) ys*=2, xs/=2;
1071 			x += int(xs * (1+time*(2+power)));
1072 			int yo = int(time*time*128 + ys*time);
1073 			//if (yo > 0) yo=-yo;//continue;
1074 			if (type)
1075 			{
1076 
1077 				if (yo > 0)
1078 				{
1079 					if (!reflect && ys<-60)
1080 					{
1081 						const double T = 0.06;
1082 						double ct = -ys / 128.0;
1083 						if (time < ct+T*4)
1084 						{
1085 							x = p.getScreenX() + int(xs * (1+ct*(2+power)));
1086 							RenderTile(
1087 								reflect,
1088 								time > ct+3*T ? TILE_SPLASH_3 : time > ct+2*T ? TILE_SPLASH_2 :  time > ct+T ?  TILE_SPLASH_1 : TILE_WATER_PARTICLE+1,
1089 								x, y);
1090 						}
1091 					}
1092 				}
1093 				else
1094 					RenderTile(
1095 						reflect,
1096 						time - i*0.003 < 0.2 ? TILE_WATER_PARTICLE+1 : TILE_WATER_PARTICLE,
1097 						x, y+(reflect?-1:1)*yo);
1098 			}
1099 			else
1100 			{
1101 				if (yo > 0)
1102 					;
1103 				else
1104 					RenderTile(
1105 						reflect,
1106 						i<q-20 || time<0.3 ? TILE_LASER_HEAD : i<q-10 || time<0.6 ? TILE_FIRE_PARTICLE_1 : TILE_FIRE_PARTICLE_2,
1107 						x, y+(reflect?-1:1)*yo);
1108 			}
1109 		}
1110 	}
1111 };
1112 struct DisintegrateRender : public RenderStage
1113 {
1114 	Pos p;
1115 	int seed;
1116 	int height;
1117 	int type;
1118 
DisintegrateRenderDisintegrateRender1119 	DisintegrateRender(Pos _p, int _pow=0, int _t=0) : p(_p), height(_pow), type(_t)
1120 	{
1121 		seed = rand();
1122 	}
1123 
RenderDisintegrateRender1124 	void Render(RenderObject* /*r*/, double time, bool reflect)
1125 	{
1126 		if (type)
1127 			RenderTile(reflect, height ? COLLAPSE_DOOR : COLLAPSABLE, p.getScreenX(), p.getScreenY());
1128 
1129 		if (time > 50.0/70.0) return;
1130 		if (reflect) return;
1131 		srand(seed);
1132 		int q = 50 - int(time * 70);
1133 		if (height) q*=2;
1134 		for (int i=0; i<q; i++)
1135 		{
1136 			int x = (rand() % (TILE_W3-8))-TILE_W3/2+4;
1137 			int y = (rand() % (TILE_H2-8))-TILE_H1+4;
1138 			if (x<-TILE_WL/2 && ABS(y)<-TILE_WL/2-x) continue;
1139 			if (x>TILE_WL/2 && ABS(y)>x-TILE_WL/2) continue;
1140 			int yo=0;
1141 			if (height) yo -= rand() % TILE_HUP;
1142 			x += p.getScreenX();
1143 			y += p.getScreenY() + 4;
1144 			int xs = 0;//((rand() & 63) - 32);
1145 			int ys = (- (rand() & 31));
1146 			x += int(xs * (1+time*(2)));
1147 			if (type) yo = -yo;
1148 			yo += int(time*time*128 + ys*time);
1149 			if (type) yo = -yo*2;
1150 			//if (yo > 0) yo=-yo;//continue;
1151 			int t = type ? TILE_BLUE_FRAGMENT : TILE_GREEN_FRAGMENT;
1152 			if (i>q-20) t++;
1153 			if (i>q-10) t++;
1154 			if (yo > 5) yo = 5;
1155 			RenderTile(false, t, x, y+(reflect?-yo:yo));
1156 		}
1157 	}
1158 };
1159 struct BuildRender : public RenderStage
1160 {
1161 	Pos p;
1162 	Dir dir;
1163 	int reverse;
1164 	int height;
1165 	int type;
1166 
BuildRenderBuildRender1167 	BuildRender(Pos _p, Dir _d, int _h, int _r=0, int _type=0) : p(_p), dir(_d), reverse(_r), height(_h), type(_type)
1168 	{
1169 	}
1170 
RenderBuildRender1171 	void Render(RenderObject* /*r*/, double time, bool reflect)
1172 	{
1173 		if (time >= BUILD_TIME)
1174 			RenderTile(reflect, height ^ reverse ? (type ? COLLAPSE_DOOR2 : COLLAPSE_DOOR) : (type ? COLLAPSABLE2 : COLLAPSABLE), p.getScreenX(), p.getScreenY());
1175 		else
1176 		{
1177 			if (height)
1178 				RenderTile(reflect, type ? COLLAPSABLE2 : COLLAPSABLE, p.getScreenX(), p.getScreenY());
1179 
1180 			double dist = time * 2 / BUILD_TIME;
1181 			if (dir>-1)
1182 			{
1183 				Pos from = p + ((dir+MAX_DIR/2)%MAX_DIR);
1184 				if (dist <= 1)
1185 				//if (dist > 1)
1186 				{
1187 					double offset = (dist*0.7) + 0.3;
1188 					int x = from.getScreenX() + int((p.getScreenX()-from.getScreenX()) * offset);
1189 					int y = from.getScreenY() + int((p.getScreenY()-from.getScreenY()) * offset - dist*(1-dist)*(TILE_HUP*4));
1190 					RenderTile(reflect, TILE_GREEN_FRAGMENT, x, y);
1191 				}
1192 				dist -= 1;
1193 			}
1194 			else
1195 			{
1196 				if (reverse) dist = 1-dist;
1197 			}
1198 			if (dist > 0 && !height)
1199 			{
1200 				if (!reflect)
1201 				{
1202 					for (int i=0; i<=int(dist*15); i++)
1203 					{
1204 						int x = p.getScreenX(), y = p.getScreenY();
1205 						double d = (i + fmod(dist*15, 1))/10.0;
1206 						int x1 = int(sin(d*5+time)*MIN(d,1)*TILE_W2/2);
1207 						int y1 = int(cos(d*5+time)*MIN(d,1)*TILE_H1*0.7);
1208 						RenderTile(reflect, TILE_GREEN_FRAGMENT, x+x1, y+y1+4);
1209 						RenderTile(reflect, TILE_GREEN_FRAGMENT, x-x1, y-y1+4);
1210 					}
1211 				}
1212 			}
1213 			if (dist > 0 && height)
1214 			{
1215 				int yo = int((1-dist)*(TILE_HUP*1.3));
1216 				if (yo > TILE_HUP*1.1)
1217 					RenderTile(reflect, TILE_WHITE_TILE, p.getScreenX(), p.getScreenY());
1218 				else if (!reflect)
1219 				{
1220 					RenderTile(reflect, type ? COLLAPSABLE2 : COLLAPSABLE, p.getScreenX(), p.getScreenY());
1221 					RenderTile(reflect, type ? COLLAPSE_DOOR2 : COLLAPSE_DOOR, p.getScreenX(), p.getScreenY()+(reflect ? -yo : yo), yo+6);
1222 					RenderTile(reflect, type ? TILE_BLUE_FRONT : TILE_GREEN_FRONT, p.getScreenX(), p.getScreenY());
1223 				}
1224 				else
1225 				{
1226 					if (yo < TILE_HUP/2)
1227 					{
1228 						RenderTile(reflect, type ? COLLAPSE_DOOR2 : COLLAPSE_DOOR, p.getScreenX(), p.getScreenY()+(reflect ? -yo : yo), yo);
1229 
1230 					}
1231 					RenderTile(reflect, type ? COLLAPSABLE2 : COLLAPSABLE, p.getScreenX(), p.getScreenY());
1232 
1233 				}
1234 			}
1235 		}
1236 	}
1237 };
1238 
1239 struct PlayerRender : public RenderStage
1240 {
1241 	Pos p;
1242 	Pos target;
1243 	int p_h, target_h;
1244 	int r;
1245 	int type;
1246 	double speed;
1247 	bool dead;
1248 
PlayerRenderPlayerRender1249 	PlayerRender(Pos a, int h, bool d) : p(a), target(a), p_h(h), target_h(h), r(3), type(0), speed(0), dead(d)
1250 	{}
PlayerRenderPlayerRender1251 	PlayerRender(int _r, Pos a, int h1, Pos t, int h2, bool d) : p(a), target(t), p_h(h1), target_h(h2), r(_r), type(0), speed(JUMP_TIME), dead(d)
1252 	{
1253 		int dist = MAX(ABS(p.x-target.x), ABS(p.y-target.y));
1254 		if (dist > 1)
1255 			speed *= 1.5;
1256 		if(dist==0)
1257 			speed = 0;
1258 	}
1259 
GetDepthPlayerRender1260 	virtual int GetDepth(double time)
1261 	{
1262 		double f = speed ? time / speed : 1;
1263 		if (f>1) f=1;
1264 		if (f==1) dead = this->dead;
1265 
1266 		if (f==1 || (f>0.5 && p_h>target_h))
1267 			return target.x+target.y*2;
1268 		return MAX(target.x+target.y*2 , p.x+p.y*2);
1269 	}
1270 
RenderPlayerRender1271 	void Render(RenderObject* /*ro*/, double time, bool reflect)
1272 	{
1273 		bool dead = false;
1274 		double f = speed ? time / speed : 1;
1275 		if (f>1) f=1;
1276 		if (f==1) dead = this->dead;
1277 
1278 		int x = p.getScreenX();
1279 		int y = p.getScreenY();
1280 		int x2 = target.getScreenX();
1281 		int y2 = target.getScreenY();
1282 		int h = 0;
1283 		int shadow_h = (int)((p_h+(target_h-p_h)*f)*TILE_HUP2);
1284 
1285 		if (x==x2 && y==y2 && p_h!=target_h)
1286 		{
1287 			h = TILE_H_LIFT_UP - GetLiftHeight(time, p_h ? LIFT_DOWN : LIFT_UP);
1288 		}
1289 		else
1290 		{
1291 
1292 			int dist = MAX(ABS(p.x-target.x), ABS(p.y-target.y));
1293 			int arc = dist*dist;
1294 			int h1 = p_h * TILE_HUP2;;
1295 			int h2 = target_h * TILE_HUP2;
1296 			if (dist==2 && h1!=0)
1297 			{
1298 				arc += h2 ? 1 : 3;
1299 				h1 = 0;
1300 				shadow_h = f>=0.7 ? int(shadow_h*(f-0.7)/0.3) : 0;
1301 			}
1302 			if (dist==0)
1303 				arc = speed > JUMP_TIME ? 7 : 2;
1304 
1305 			h = (int)(h1+(h2-h1)*f);
1306 		//	if (x==x2 && y==y2)
1307 		//		;
1308 		//	else
1309 			{
1310 				//h += int(TILE_H_LIFT_UP/3 * (1-f));
1311 				h += (int)(f*(1-f)*TILE_HUP2*arc);
1312 			}
1313 
1314 			if (type==2)
1315 				h=0;
1316 		}
1317 
1318 		if (!dead)
1319 		{
1320 			int frame = 0;
1321 			if (type==2 && f<1)
1322 			{
1323 				//frame = ((int)(f*4) % 4);
1324 				//if (frame==2) frame=0; else if (frame==3) frame=2;
1325 				frame = 0;
1326 			}
1327 			else if (f==1 || (x==x2 && y==y2))	// stationary
1328 				frame = 0;
1329 			else if (f > 0.7)
1330 				frame = 0;
1331 			else
1332 			{
1333 				frame = type ? 2 : 1;
1334 				if (f<0.1 || f>0.6)
1335 					frame += 2;
1336 			}
1337 
1338 			if (!reflect)
1339 				RenderTile( false, TILE_SPHERE,
1340 					(int)(x+(x2-x)*f),
1341 					(int)(y+(y2-y)*f) - shadow_h
1342 				);
1343 
1344 			RenderGirl(
1345 				reflect,
1346 				r, frame,
1347 				(int)(x+(x2-x)*f),
1348 				(int)(y+(y2-y)*f),
1349 				h
1350 				);
1351 
1352 		}
1353 	/*	RenderTile(
1354 			dead ? TILE_SPHERE_OPEN : TILE_SPHERE_DONE,
1355 			(int)(x+(x2-x)*f),
1356 			(int)(y+(y2-y)*f),
1357 			true
1358 			);*/
1359 	}
1360 };
1361 
1362 
1363 struct HexPuzzle : public State
1364 {
1365 	struct Undo
1366 	{
1367 		#define MAX_TILECHANGE 64 // TODO: don't have a magic upper limit
1368 		struct TileChange
1369 		{
1370 			Pos p;
1371 			Tile t;
1372 			int item;
1373 
TileChangeHexPuzzle::Undo::TileChange1374 			TileChange()
1375 			{}
TileChangeHexPuzzle::Undo::TileChange1376 			TileChange(Pos _p, Tile _t, int _i) : p(_p), t(_t), item(_i)
1377 			{}
RestoreHexPuzzle::Undo::TileChange1378 			void Restore(HexPuzzle* w)
1379 			{
1380 				w->SetTile(p,t,false,false);
1381 				w->SetItem(p,item,false,false);
1382 			}
1383 		};
1384 
1385 		TileChange t[MAX_TILECHANGE];
1386 		Pos playerPos;
1387 		Dir playerMovement;
1388 		int numT;
1389 		int numItems[2];
1390 		int score;
1391 		double time;
1392 		double endTime;
1393 
AddHexPuzzle::Undo1394 		void Add(TileChange const & tc)
1395 		{
1396 			for (int i=0; i<numT; i++)
1397 				if (t[i].p==tc.p)
1398 					return;
1399 			if (numT>=MAX_TILECHANGE)
1400 				FATAL("numT>=MAX_TILECHANGE");
1401 			else
1402 				t[numT++] = tc;
1403 		}
NewHexPuzzle::Undo1404 		void New(Dir pmove, Pos & pp, int* items, double t, int sc)
1405 		{
1406 			numItems[0] = items[0];
1407 			numItems[1] = items[1];
1408 			playerPos = pp;
1409 			playerMovement = pmove;
1410 			score = sc;
1411 			time = t;
1412 			numT = 0;
1413 		}
RestoreHexPuzzle::Undo1414 		void Restore(HexPuzzle* w)
1415 		{
1416 			for (int i=numT-1; i>=0; i--)
1417 				t[i].Restore(w);
1418 			w->dead = false;
1419 			w->win = false;
1420 			w->player = playerPos;
1421 			w->player_items[0] = numItems[0];
1422 			w->player_items[1] = numItems[1];
1423 			w->player_score = score;
1424 
1425 			//w->renderer.player.Add(new PlayerRender(playerPos, w->GetHeight(playerPos), false), w->time);
1426 		}
1427 	};
1428 
1429 	#define MAP_SIZE 30
1430 	char* special[MAP_SIZE][MAP_SIZE];
1431 	Tile map[MAP_SIZE][MAP_SIZE];
1432 	int32_t map_item[MAP_SIZE][MAP_SIZE];
1433 	int tileCount[NumTileTypes];
1434 	int32_t levelPar, levelDiff;
1435 	int turboAnim;
1436 	Pos player;
1437 	int player_items[2];
1438 	int player_score;
1439 	int numComplete, numLevels, numMastered, numLevelsFound;
1440 	bool dead;
1441 	bool win;
1442 	int winFinal;
1443 
1444 	SaveState progress;
1445 
1446 	WorldRenderer renderer;
1447 	double time;
1448 	double undoTime;
1449 
1450 	#define MAX_UNDO 50
1451 	Undo undo[MAX_UNDO];
1452 	int numUndo;
1453 	LevelInfo* currentLevelInfo;
1454 
1455 	char currentFile[1000];
1456 
~HexPuzzleHexPuzzle1457 	~HexPuzzle()
1458 	{
1459 		FreeGraphics();
1460 	}
1461 
GetLevelInfoHexPuzzle1462 	LevelInfo* GetLevelInfo(const char* f)
1463 	{
1464 		if (strstr(f, "Levels\\") == f)
1465 			f += 7;
1466 		if (currentLevelInfo!=0 && strcmp(currentLevelInfo->file, f)==0)
1467 			return currentLevelInfo;
1468 
1469 		if (f[0]=='_')
1470 		{
1471 			int t = atoi(f+1);
1472 			if (t <= numComplete)
1473 				return 0;
1474 
1475 			static char tmp1[1000];
1476 			static LevelInfo tmp = {0, "", tmp1};
1477 			sprintf(tmp1, ngettext("Complete 1  more level  to unlock!", "Complete %d  more levels  to unlock!", t-numComplete), t-numComplete);
1478 			return &tmp;
1479 		}
1480 
1481 		for (unsigned int i=0; i<sizeof(levelNames)/sizeof(levelNames[0]); i++)
1482 			if (strcmp(f, levelNames[i].file)==0)
1483 				return &levelNames[i];
1484 		static LevelInfo tmp = {0, "", _("<<NO NAME>>")};
1485 		return &tmp;
1486 	}
1487 
1488 #ifdef MAP_EDIT_HACKS
GetAutoTileHexPuzzle1489 	int GetAutoTile(const char * level, bool tiletype)
1490 	{
1491 		FILE* f = file_open(filename, "rb");
1492 		int tile = EMPTY;
1493 		int version;
1494 
1495 		if (f && fscanf(f, "%d", &version)==1 && (version==3 || version==4))
1496 		{
1497 			if (strstr(level,"mk"))
1498 				level+=0;
1499 
1500 			fgetc(f); // Remove '\n' character
1501 
1502 			int32_t par, diff;
1503 			unsigned char bounds[4];
1504 			Pos playerStart;
1505 			fread(&par, sizeof(par), 1, f);
1506 			par = SWAP32(par);
1507 
1508 			if (version >= 4) {
1509 				fread(&diff, sizeof(diff), 1, f);
1510   			diff = SWAP32(diff);
1511       }
1512 			fread(bounds, sizeof(bounds), 1, f);
1513 			fread(&playerStart, sizeof(playerStart), 1, f);
1514 			playerStart.x = SWAP32(playerStart.x);
1515 			playerStart.y = SWAP32(playerStart.y);
1516 
1517 			int highval=0;
1518 
1519 			for (int i=bounds[0]; i<=bounds[1]; i++)
1520 				for (int j=bounds[2]; j<=bounds[3]; j++)
1521 				{
1522 					unsigned char comp = map[i][j] | (map_item[i][j]<<5);
1523 					fread(&comp, sizeof(comp), 1, f);
1524 					int t = comp & 0x1f;
1525 					int item = (comp >> 5) & 3;
1526 					for (int i=highval+1; i<sizeof(value_order)/sizeof(value_order[0]); i++)
1527 						if (t!=0 && t==value_order[i]
1528 						 ||	item!=0 && item==(value_order[i]>>8))
1529 							highval = i;
1530 				}
1531 
1532 			if (tiletype)
1533 			{
1534 				tile = value_order[highval];
1535 				if (tile==0x100) tile = COLLAPSABLE3;
1536 				if (tile==0x200) tile = SWITCH;
1537 				if (tile==LIFT_UP) tile = LIFT_DOWN;
1538 			}
1539 			else
1540 			{
1541 				if (value_order[highval] == LIFT_UP)
1542 					tile = highval-1;
1543 				else
1544 					tile = highval;
1545 			}
1546 		}
1547 		else
1548 		{
1549 			level+=0;
1550 		}
1551 		if (f)
1552 			fclose(f);
1553 		return tile;
1554 	}
1555 #endif
1556 
InitSpecialsHexPuzzle1557 	void InitSpecials()
1558 	{
1559 		numComplete = numLevels = numMastered = numLevelsFound = 0;
1560 		for (int i=0; i<MAP_SIZE; i++)
1561 			for (int j=0; j<MAP_SIZE; j++)
1562 				ActivateSpecial(Pos(i,j), 0);
1563 		for (int i=0; i<MAP_SIZE; i++)
1564 			for (int j=0; j<MAP_SIZE; j++)
1565 				ActivateSpecial(Pos(i,j), 2);
1566 		numComplete = numLevels = numMastered = numLevelsFound = 0;
1567 		for (int i=0; i<MAP_SIZE; i++)
1568 			for (int j=0; j<MAP_SIZE; j++)
1569 				ActivateSpecial(Pos(i,j), 0);
1570 
1571 	}
DoHintsHexPuzzle1572 	void DoHints()
1573 	{
1574 		#ifndef EDIT
1575 			if (strcmp(mapname, currentFile)==0)
1576 			{
1577 //				for (int i=0; i<32; i++)
1578 //					HintMessage::FlagTile(i);
1579 				if (numComplete >= UNLOCK_SCORING && !progress.general.scoringOn)
1580 				{
1581 					HintMessage::FlagTile(26);
1582 					progress.general.scoringOn = 1;
1583 					InitSpecials(); // Re-initialise with gold ones available
1584 				}
1585 				HintMessage::FlagTile(25);
1586 			}
1587 			else
1588 			{
1589 				for (int i=0; i<MAP_SIZE; i++)
1590 					for (int j=0; j<MAP_SIZE; j++)
1591 					{
1592 						int t = GetTile(Pos(i,j));
1593 						int item = GetItem(Pos(i,j));
1594 						if (t)
1595 							HintMessage::FlagTile(t);
1596 						if (item)
1597 							HintMessage::FlagTile(item+20);
1598 					}
1599 				HintMessage::FlagTile(EMPTY);
1600 			}
1601 		#endif
1602 		hintsDone = true;
1603 	}
ResetLevelHexPuzzle1604 	void ResetLevel()
1605 	{
1606 		hintsDone = false;
1607 
1608 		UpdateCursor(Pos(-1,-1));
1609 
1610 		isMap = false;
1611 
1612 		player_score = 0;
1613 
1614 		numUndo = 0;
1615 		undoTime = -1;
1616 
1617 		dead = false;
1618 		win = false;
1619 		winFinal = false;
1620 		player_items[0] = player_items[1] = 0;
1621 //		time = 0;
1622 		if (strlen(currentSlot) == 0)
1623 		{
1624 			new TitleMenu();
1625 			new Fader(1, -3);
1626 		}
1627 		else
1628 		{
1629 			if (!isFadeRendering && time!=0)
1630 			{
1631 				renderer().Add(new FadeRender(-1), time);
1632 				time += 0.5;
1633 			}
1634 		}
1635 
1636 		// Reset renderer
1637 		renderer.Reset(time);
1638 		renderer.Wipe();
1639 
1640 		for (int t=0; t<NumTileTypes; t++)
1641 			tileCount[t] = 0;
1642 
1643 		for (int i=0; i<MAP_SIZE; i++)
1644 			for (int j=0; j<MAP_SIZE; j++)
1645 			{
1646 				Pos p(i,j);
1647 				int item = GetItem(p);
1648 				//if (item)
1649 					renderer(p,true).Add(new ItemRender(item, GetTile(p)==EMPTY, p), time);
1650 			}
1651 
1652 		InitSpecials();
1653 
1654 		for (int i=0; i<MAP_SIZE; i++)
1655 			for (int j=0; j<MAP_SIZE; j++)
1656 			{
1657 				Pos p(i,j);
1658 				int t = GetTile(p);
1659 				tileCount[t]++;
1660 
1661 				if (isMap)
1662 					t = EMPTY;
1663 
1664 				//if (t)
1665 					renderer(p).Add(new TileRender(t, p), time);
1666 			}
1667 
1668 		if (!isMap)
1669 			renderer.player.Add(new PlayerRender(player, GetHeight(player), dead), time);
1670 		else
1671 			renderer.player.Add(new PlayerRender(Pos(-100,-100), 0, true), time);
1672 
1673 
1674 		int bounds[4] = {player.getScreenX(),player.getScreenX(),player.getScreenY(),player.getScreenY()};
1675 		for (int i=0; i<MAP_SIZE; i++)
1676 			for (int j=0; j<MAP_SIZE; j++)
1677 			{
1678 				Pos p(i,j);
1679 				if (map[i][j] !=0 || map_item[i][j]!=0)
1680 				{
1681 					int x1 = p.getScreenX();
1682 					int y1 = p.getScreenY();
1683 					int x2 = x1 + TILE_W3;
1684 					int y2 = y1 + TILE_H2;
1685 					y1 -= TILE_H2;	// Make sure objects/player will be properly visible
1686 
1687 					if (x1<bounds[0]) bounds[0] = x1;
1688 					if (x2>bounds[1]) bounds[1] = x2;
1689 					if (y1<bounds[2]) bounds[2] = y1;
1690 					if (y2>bounds[3]) bounds[3] = y2;
1691 				}
1692 			}
1693 
1694 		int sx, sy;
1695 		if (isMap)
1696 		{
1697 			sx = bounds[0] - int(TILE_W2*6.35);
1698 			sy = (bounds[3] + bounds[2] - SCREEN_H) / 2 - TILE_H2/2;
1699 		}
1700 		else
1701 		{
1702 			sx = (bounds[1] + bounds[0] - SCREEN_W) / 2 - TILE_W3/2;
1703 			sy = (bounds[3] + bounds[2] - SCREEN_H) / 2 - TILE_H2/2;
1704 		}
1705 		if (isMap)
1706 		{
1707 			initScrollX = sx;
1708 			initScrollY = sy;
1709 			if (mapScrollX==0)
1710 				mapScrollX = sx;
1711 			else
1712 				sx = mapScrollX;
1713 		}
1714 
1715 //		time = 1;	// Guarantee we can't try and do things at time=0
1716 
1717 		renderer().Add(new ScrollRender(sx, sy), time);
1718 		renderer().Add(new FadeRender(1), time);
1719 		if (time != 0)
1720 			time -= 0.5;
1721 	}
1722 
ReadAllHexPuzzle1723 	char* ReadAll(FILE* f)
1724 	{
1725 		int size;
1726     // FIXME: According to http://userpage.fu-berlin.de/~ram/pub/pub_jf47ht20Ht/c_faq_de
1727     // undefined for binary streams! (POSIX does not differ between ascii and binary, so
1728     // we are on the save side in Linux)
1729 		fseek(f, 0, SEEK_END);
1730 		size = ftell(f);
1731 		fseek(f, 0, SEEK_SET);
1732 		char* c = loadPtr = new char [size];
1733 		endLoad = loadPtr + size;
1734 		fread(c, 1, size, f);
1735 		return c;
1736 	}
1737 
1738 	static char *loadPtr, *endLoad;
fread_replaceHexPuzzle1739 	static unsigned int fread_replace(void* d, unsigned int size, unsigned int num, FILE*)
1740 	{
1741 		unsigned int remain = (endLoad - loadPtr) / size;
1742 		if (remain < num) num = remain;
1743 		memcpy(d, loadPtr, size*num);
1744 		loadPtr += size*num;
1745 		return num;
1746 	}
1747 
GetParHexPuzzle1748 	int GetPar(const char * level, bool getdiff=false)
1749 	{
1750 		if (strcmp(level, currentFile)==0)
1751 			return getdiff ? levelDiff : levelPar;
1752 
1753 		#ifdef USE_LEVEL_PACKFILE
1754 			PackFile1::Entry* e = levelFiles.Find(level);
1755 			if (!e) return 999;
1756 			loadPtr = (char*)e->Data();
1757 			endLoad = loadPtr + e->DataLen();
1758 			FILE* f = 0;
1759 		#else
1760 			loadPtr = 0;
1761 			FILE* f = file_open(level, "rb");
1762 		#endif
1763 
1764 		typedef unsigned int _fn(void*, unsigned int, unsigned int, FILE*);
1765 		_fn * fn = (loadPtr ? (_fn*)fread_replace : (_fn*)fread);
1766 
1767 		int32_t par = 99999, diff = 0;
1768 		int16_t version;
1769 
1770 		if (!f && !loadPtr)
1771 			return getdiff ? diff : par;
1772 
1773 		fn(&version, 2, 1, f); // skip to relevant point
1774 
1775 		if (fn(&par, sizeof(par), 1, f) != 1)
1776 			par = 99999;
1777 		else
1778 			par = SWAP32(par);
1779 		size_t ret = fn(&diff, sizeof(diff), 1, f);
1780 		diff = SWAP32(diff);
1781 		if (ret != 1 || diff<0 || diff>10)
1782 			diff = 0;
1783 
1784 		#ifdef USE_LEVEL_PACKFILE
1785 			loadPtr = endLoad = 0;
1786 		#else
1787 			if (f)
1788 				fclose(f);
1789 		#endif
1790 
1791 		return getdiff ? diff : par;
1792 	}
1793 
LoadSaveHexPuzzle1794 	bool LoadSave(const char * filename, bool save)
1795 	{
1796 		if (!filename)
1797 			return false;
1798 
1799 		if (!save)
1800 		{
1801 			showScoring = false;
1802 			LevelSave* l = progress.GetLevel(filename, true);
1803 			if (progress.general.scoringOn && l && l->Completed() )
1804 				showScoring = true;
1805 		}
1806 
1807 		#ifdef USE_LEVEL_PACKFILE
1808 			if (!save)
1809 			{
1810 				PackFile1::Entry* e = levelFiles.Find(filename);
1811 				if (!e) return false;
1812 
1813 				if (currentFile != filename) // equal (overlapping) strings are forbidden
1814 					strcpy(currentFile, filename);
1815 				currentLevelInfo = GetLevelInfo(currentFile);
1816 
1817 				loadPtr = (char*)e->Data();
1818 				endLoad = loadPtr + e->DataLen();
1819 				_LoadSave(NULL, save);
1820 				loadPtr = endLoad = 0;
1821 
1822 				if (!isMap && !activeMenu)
1823 					PlayMusic(HHOP_MUSIC_GAME);
1824 
1825 				return true;
1826 			}
1827 		#else
1828 			loadPtr = 0;
1829 			FILE* f = file_open(filename, save ? "wb" : "rb");
1830 			if (f)
1831 			{
1832 				strcpy(currentFile, filename);
1833 				if (!save)
1834 					currentLevelInfo = GetLevelInfo(currentFile);
1835 
1836 				if (!save)
1837 				{
1838 					char* data = ReadAll(f);
1839 					_LoadSave(f, save);
1840 					delete [] data;
1841 					loadPtr = endLoad = 0;
1842 				}
1843 				else
1844 				{
1845 					_LoadSave(f, save);
1846 				}
1847 				fclose(f);
1848 
1849 				if (!isMap && !activeMenu && !save)
1850 					PlayMusic(HHOP_MUSIC_GAME);
1851 
1852 				return true;
1853 			}
1854 		#endif
1855 
1856 		return false;
1857 	}
1858 
1859   /** \brief Writes/reads game status to/from a file
1860    *
1861    *  The game data file is written in little endian so it can be shared
1862    *  across different machines.
1863    */
_LoadSaveHexPuzzle1864 	void _LoadSave(FILE* f, bool save)
1865 	{
1866 		typedef unsigned int _fn(void*, unsigned int, unsigned int, FILE*);
1867 		_fn * fn = save ? (_fn*)fwrite : (loadPtr ? (_fn*)fread_replace : (_fn*)fread);
1868 
1869 		#define SAVEVERSION 4
1870 		int version = SAVEVERSION; // 1--9
1871 		if (save)
1872 			fprintf(f, "%d\n", version);
1873 		else
1874 		{
1875 			char c;
1876 			if (fn(&c, 1, 1, f) != 1)
1877 				return;
1878 			version = c-'0';
1879 
1880 			// Remove '\n' character
1881 			fn(&c, 1, 1, f);
1882 		}
1883 
1884 		if (!save)
1885 		{
1886 			for (int i=0; i<MAP_SIZE; i++)
1887 				for (int j=0; j<MAP_SIZE; j++)
1888 				{
1889 					delete [] special[i][j];
1890 					special[i][j] = 0;
1891 				}
1892 		}
1893 
1894 		if (version==1)
1895 		{
1896 			for (int i=0; i<MAP_SIZE; i++)
1897 				for (int j=0; j<MAP_SIZE; j++) {
1898 					map[i][j] = SWAP32(map[i][j]);
1899 					fn(&map[i][j], sizeof(map[i][j]), 1, f);
1900 					map[i][j] = SWAP32(map[i][j]);
1901 				}
1902 
1903 			player.x = SWAP32(player.x);
1904 			player.y = SWAP32(player.y);
1905 			fn(&player, sizeof(player), 1, f);
1906 			player.x = SWAP32(player.x);
1907 			player.y = SWAP32(player.y);
1908 
1909 			for (int i=0; i<MAP_SIZE; ++i)
1910 				for (int j=0; j<MAP_SIZE; ++j)
1911 					map_item[i][j] = SWAP32(map_item[i][j]);
1912 			if (fn(map_item, sizeof(map_item), 1, f) == 0)
1913 				memset(map_item, 0, sizeof(map_item));
1914 			for (int i=0; i<MAP_SIZE; ++i)
1915 				for (int j=0; j<MAP_SIZE; ++j)
1916 					map_item[i][j] = SWAP32(map_item[i][j]);
1917 		}
1918 		else if (version>=2 && version<=4)
1919 		{
1920 			unsigned char bounds[4];
1921 			if (save)
1922 			{
1923 				bounds[0]=bounds[1]=player.x;
1924 				bounds[2]=bounds[3]=player.y;
1925 				for (int i=0; i<MAP_SIZE; i++)
1926 					for (int j=0; j<MAP_SIZE; j++)
1927 						if (map[i][j] !=0 || map_item[i][j]!=0 || special[i][j]!=0)
1928 						{
1929 							if (i<bounds[0]) bounds[0] = i;
1930 							if (i>bounds[1]) bounds[1] = i;
1931 							if (j<bounds[2]) bounds[2] = j;
1932 							if (j>bounds[3]) bounds[3] = j;
1933 						}
1934 			}
1935 			else
1936 			{
1937 				memset(map, 0, sizeof(map));
1938 				memset(map_item, 0, sizeof(map_item));
1939 			}
1940 
1941 			if (version>=3) {
1942 				levelPar = SWAP32(levelPar);
1943 				fn(&levelPar, 1, sizeof(levelPar), f);
1944 				levelPar = SWAP32(levelPar);
1945 			}
1946 			else if (!save)
1947 				levelPar = 0;
1948 
1949 			if (version>=4) {
1950 				levelDiff = SWAP32(levelDiff);
1951 				fn(&levelDiff, 1, sizeof(levelDiff), f);
1952 				levelDiff = SWAP32(levelDiff);
1953 			}
1954 			else if (!save)
1955 				levelDiff = 0;
1956 
1957 			fn(bounds, sizeof(bounds), 1, f);
1958 			player.x = SWAP32(player.x);
1959 			player.y = SWAP32(player.y);
1960 			fn(&player, sizeof(player), 1, f);
1961 			player.x = SWAP32(player.x);
1962 			player.y = SWAP32(player.y);
1963 
1964 			int offsetx=0, offsety=0;
1965 
1966 			if (!save && bounds[1]-bounds[0]<15) // Hacky - don't recenter map...
1967 			{
1968 				// Re-position map to top left (but leave a bit of space)
1969 				// (This ensures the laser/boat effects don't clip prematurely against the edges of the screen)
1970 				offsetx = SCREEN_W/2/TILE_W2 + 1 - (bounds[0]+bounds[1]/2);
1971 				offsety = SCREEN_H/2/TILE_H2 + SCREEN_W/2/TILE_W2 - (bounds[2]+bounds[3]/2);
1972 				offsetx = MAX(0, offsetx);
1973 				offsety = MAX(0, offsety);
1974 //				if (bounds[0] > 2)
1975 //					offsetx = 2 - bounds[0];
1976 //				if (bounds[2] > 2)
1977 //					offsety = 2 - bounds[2];
1978 			}
1979 			bounds[0] += offsetx;
1980 			bounds[1] += offsetx;
1981 			bounds[2] += offsety;
1982 			bounds[3] += offsety;
1983 			player.x += offsetx;
1984 			player.y += offsety;
1985 
1986 			for (int i=bounds[0]; i<=bounds[1]; i++)
1987 				for (int j=bounds[2]; j<=bounds[3]; j++)
1988 				{
1989 					unsigned char comp = map[i][j] | (map_item[i][j]<<5);
1990 					fn(&comp, sizeof(comp), 1, f);
1991 					map[i][j] = comp & 0x1f;
1992 					map_item[i][j] = (comp >> 5) & 3;
1993 				}
1994 
1995 			if (save)
1996 			{
1997 				for (int i=bounds[0]; i<=bounds[1]; i++)
1998 					for (int j=bounds[2]; j<=bounds[3]; j++)
1999 						if (special[i][j])
2000 						{
2001 							int16_t len = strlen(special[i][j]);
2002 							unsigned char x=i, y=j;
2003 							fn(&x, sizeof(x), 1, f);
2004 							fn(&y, sizeof(y), 1, f);
2005 							len = SWAP16(len);
2006 							fn(&len, sizeof(len), 1, f);
2007 							len = SWAP16(len);
2008 							fn(special[i][j], 1, len, f);
2009 						}
2010 			}
2011 			else
2012 			{
2013 				while(1){
2014 					int16_t len;
2015 					unsigned char x, y;
2016 					if (!fn(&x, sizeof(x), 1, f))
2017 						break;
2018 					fn(&y, sizeof(y), 1, f);
2019 					x += offsetx; y += offsety;
2020 					fn(&len, sizeof(len), 1, f);
2021 					len = SWAP16(len);
2022 					if (len<0) break;
2023 					char* tmp = new char[len+1];
2024 					tmp[len] = 0;
2025 					fn(tmp, 1, len, f);
2026 
2027 					SetSpecial(Pos(x,y), tmp, true, false);
2028 				}
2029 			}
2030 		}
2031 		else
2032 			return;	// Unsupported version!
2033 
2034 		ResetLevel();
2035 
2036 		// Save when returning to map!
2037 		if (isMap)
2038 		{
2039 			progress.general.completionPercentage = numComplete*100/numLevels;
2040 			progress.general.masteredPercentage = numMastered*100/numLevels;
2041 			LoadSaveProgress(true);
2042 		}
2043 	}
2044 
SetTileHexPuzzle2045 	void SetTile(Pos const & p, Tile t, bool updateRenderer=true, bool undoBuffer=true)
2046 	{
2047 		if (p.x<0 || p.x>MAP_SIZE)
2048 			return;
2049 		if (p.y<0 || p.y>MAP_SIZE)
2050 			return;
2051 		if (map[p.x][p.y] == t)
2052 			return;
2053 		if (map[p.x][p.y] == t)
2054 			return;
2055 
2056 		tileCount[map[p.x][p.y]]--;
2057 		tileCount[t]++;
2058 
2059 		if (undoBuffer)
2060 			undo[numUndo].Add(Undo::TileChange(p,GetTile(p),GetItem(p)));
2061 
2062 		map[p.x][p.y] = t;
2063 
2064 		if (updateRenderer)
2065 			renderer(p).Add(new TileRender(t, p), time);
2066 	}
2067 
GetTileHexPuzzle2068 	Tile GetTile(Pos const & p)
2069 	{
2070 		if (p.x<0 || p.x>=MAP_SIZE)
2071 			return EMPTY;
2072 		if (p.y<0 || p.y>=MAP_SIZE)
2073 			return EMPTY;
2074 		return map[p.x][p.y];
2075 	}
2076 
GetHeightHexPuzzle2077 	int GetHeight(Pos const & p)
2078 	{
2079 		return tileSolid[GetTile(p)]==1;
2080 	}
2081 
GetSpecialHexPuzzle2082 	char* GetSpecial(Pos const & p)
2083 	{
2084 		if (p.x<0 || p.x>=MAP_SIZE)
2085 			return NULL;
2086 		if (p.y<0 || p.y>=MAP_SIZE)
2087 			return NULL;
2088 		return special[p.x][p.y];
2089 	}
2090 
SetSpecialHexPuzzle2091 	void SetSpecial(Pos const & p, char * d, bool use_pointer=false, bool auto_activate=true)
2092 	{
2093 		if (p.x<0 || p.x>=MAP_SIZE || p.y<0 || p.y>=MAP_SIZE)
2094 		{
2095 			if (use_pointer)
2096 				delete [] d;
2097 			return;
2098 		}
2099 
2100 		delete [] special[p.x][p.y];
2101 		if (!use_pointer && d)
2102 		{
2103 
2104 			special[p.x][p.y] = new char [strlen(d) + 1];
2105 			strcpy(special[p.x][p.y], d);
2106 		}
2107 		else
2108 			special[p.x][p.y] = d;
2109 
2110 		if (special[p.x][p.y]==0)
2111 			renderer(p,true).Add(new ItemRender(GetItem(p), GetTile(p)==EMPTY, p), time);
2112 		else if (auto_activate)
2113 			ActivateSpecial(p, 0);
2114 	}
2115 
GetLevelStateHexPuzzle2116 	int GetLevelState(Pos const & p, int recurse=0)
2117 	{
2118 		char* x = GetSpecial(p);
2119 		if (!x) return 0;
2120 
2121 		LevelSave* l = progress.GetLevel(x, false);
2122 
2123 		int t = 1;
2124 
2125 		if (strcmp(x, STARTING_LEVEL)==0)
2126 			t = 2;
2127 		if (x[0]=='_' && l && l->unlocked)
2128 			t=3;
2129 
2130 		if (l && l->Completed())
2131 		{
2132 			t = 3;
2133 
2134 			if (recurse)
2135 				return t;
2136 
2137 			int par = GetPar(x);
2138 			if (progress.general.scoringOn && l->PassesPar( par ))
2139 				t = l->BeatsPar( par ) ? 40 : 4;
2140 		}
2141 		if (recurse)
2142 			return t;
2143 
2144 		int adj=0;
2145 		for (Dir d=0; d<MAX_DIR; d++)
2146 		{
2147 			int i = GetLevelState(p+d, 1);
2148 //			if (i>1 || i==1 && t>1)
2149 			if ((i>=1 && t>2) || (t>=1 && i>2))
2150 			{
2151 				adj |= 1<<d;
2152 				if (t==1)
2153 					t = 2;
2154 			}
2155 		}
2156 
2157 		return t | adj<<8;
2158 	}
2159 
ActivateSpecialHexPuzzle2160 	void ActivateSpecial(Pos const & p, int type)
2161 	{
2162 		if (p.x<0 || p.x>=MAP_SIZE || p.y<0 || p.y>=MAP_SIZE)
2163 			return;
2164 
2165 		char * x = special[p.x][p.y];
2166 
2167 		if (x==0 || x[0]==0)
2168 			return;
2169 
2170 		if (type==2 && x[0]=='_') // Phase2 init - unlock
2171 		{
2172 			int t = GetLevelState(p);
2173 			int target = atoi(x+1), targetM = 0;
2174 			if (target>1000) targetM=target=target-100;
2175 			if (t > 1 && numComplete >= target && numMastered >= targetM)
2176 			{
2177 				LevelSave* l = progress.GetLevel(x, true);
2178 				if (!l->unlocked)
2179 				{
2180 					l->unlocked = true;
2181 
2182 					renderer(p, true).Add(new LevelSelectRender(p, 5, GetLevelState(p)>>8), time+0.01);
2183 					renderer().Add(new ExplosionRender(p, 0), time + 0.6);
2184 					renderer().Add(new ExplosionRender(p, 1), time + 1.1);
2185 					renderer(p, true).Add(new LevelSelectRender(p, -1, GetLevelState(p)>>8), time + 1.1);
2186 				}
2187 			}
2188 		}
2189 
2190 		if (type==0) // Init & count levels
2191 		{
2192 			if (x[0]=='_')
2193 			{
2194 				int t = GetLevelState(p);
2195 				int unlock = progress.GetLevel(x, true)->unlocked;
2196 				LevelSelectRender* lsr = new LevelSelectRender( p, unlock ? -1 : (t>>8) ? 5 : 1, t>>8 );
2197 				if ((t>>8) && p.x > mapRightBound) mapRightBound = p.x;
2198 				#ifdef MAP_EDIT_HACKS
2199 					lsr->magic = -atoi(x+1);
2200 					SetTile(p, LIFT_DOWN, true, false);
2201 				#else
2202 					SetTile(p, EMPTY, true, false);
2203 				#endif
2204 				renderer(p,true).Add(lsr, time);
2205 			}
2206 			else
2207 			{
2208 				//printf("Level: %s\n", x);
2209 
2210 				int t = GetLevelState(p);
2211 				numLevels++;
2212 				if (t && !GetItem(p))
2213 				{
2214 					if (!isMap)
2215 					{
2216 						isMap = true;
2217 						mapRightBound = 0;
2218 					}
2219 					currentLevelInfo = 0;
2220 
2221 					if ((t&0xff)>=2)
2222 					{
2223 						LevelSave* l = progress.GetLevel(x, true);
2224 						if (!l->unlocked)
2225 						{
2226 							l->unlocked = true;
2227 
2228 							renderer(p, true).Add(new LevelSelectRender(p, -1, 0), time+0.01);
2229 							renderer().Add(new ExplosionRender(p, 0), time + 0.6);
2230 							renderer(p, true).Add(new LevelSelectRender(p, t & 0xff, t>>8), time + 0.6);
2231 						}
2232 
2233 						numLevelsFound++;
2234 						if (p.x > mapRightBound) mapRightBound = p.x;
2235 					}
2236 					if ((t&0xff)>=3)
2237 						numComplete++;
2238 					if ((t&0xff)>=4)
2239 						numMastered++;
2240 
2241 					LevelSelectRender* lsr = new LevelSelectRender( p, t & 0xff, t>>8 );
2242 
2243 					#ifdef MAP_EDIT_HACKS
2244 						lsr->magic = 0;
2245 						int t = GetAutoTile(x, true);
2246 						int v = GetAutoTile(x, false);
2247 						if (MAP_EDIT_HACKS_DISPLAY_UNLOCK)
2248 							lsr->magic = v;
2249 						else
2250 							lsr->magic = GetPar(x, true);
2251 						t = 1;
2252 						SetTile(p, t, true, false);
2253 					#else
2254 						SetTile(p, EMPTY, true, false);
2255 					#endif
2256 
2257 					renderer(p,true).Add(lsr, time);
2258 				}
2259 			}
2260 		}
2261 
2262 		if (type==1 && x[0]!='_') // Clicked on
2263 		{
2264 			int t = GetLevelState(p);
2265 			if (t>1)
2266 			{
2267 				LoadSave(x, false);
2268 			}
2269 		}
2270 	}
2271 
SetItemHexPuzzle2272 	void SetItem(Pos const & p, int t, bool updateRenderer=true, bool undoBuffer=true)
2273 	{
2274 		if (p.x<0 || p.x>MAP_SIZE)
2275 			return;
2276 		if (p.y<0 || p.y>MAP_SIZE)
2277 			return;
2278 		if (map_item[p.x][p.y] == t)
2279 			return;
2280 
2281 		if (undoBuffer)
2282 			undo[numUndo].Add(Undo::TileChange(p,GetTile(p),GetItem(p)));
2283 
2284 		map_item[p.x][p.y] = t;
2285 
2286 		if (updateRenderer)
2287 			renderer(p,true).Add(new ItemRender(t, GetTile(p)==EMPTY, p), time);
2288 	}
2289 
GetItemHexPuzzle2290 	Tile GetItem(Pos const & p)
2291 	{
2292 		if (p.x<0 || p.x>=MAP_SIZE)
2293 			return EMPTY;
2294 		if (p.y<0 || p.y>=MAP_SIZE)
2295 			return EMPTY;
2296 		return map_item[p.x][p.y];
2297 	}
2298 
LoadSaveProgressHexPuzzle2299 	void LoadSaveProgress(bool save)
2300 	{
2301 		FILE* f = file_open(currentSlot, save ? "wb" : "rb");
2302 		if (f)
2303 		{
2304 			progress.LoadSave(f, save);
2305 			fclose(f);
2306 		}
2307 		else
2308 		{
2309 			if (!save)
2310 				progress.Clear();
2311 		}
2312 	}
LoadProgressHexPuzzle2313 	void LoadProgress()
2314 	{
2315 		LoadSaveProgress(false);
2316 	}
SaveProgressHexPuzzle2317 	void SaveProgress()
2318 	{
2319 		LoadSaveProgress(true);
2320 	}
2321 
LoadHexPuzzle2322 	SDL_Surface* Load(const char * bmp, bool colourKey=true)
2323 	{
2324 		typedef unsigned int uint32;
2325 		uint32* tmp = 0;
2326 
2327 		SDL_Surface * g = 0;
2328 
2329 #ifdef EDIT
2330 		if (strstr(bmp, ".bmp"))
2331 		{
2332 			g = SDL_LoadBMP(bmp);
2333 
2334 			char out[1024];
2335 			strcpy(out, bmp);
2336 			strcpy(strstr(out, ".bmp"), ".dat");
2337 
2338 //			SDL_PixelFormat p;
2339 //			p.sf = 1;
2340 //			SDL_Surface* tmp = SDL_ConvertSurface(g, &p, SDL_SWSURFACE);
2341 
2342 			short w=g->w, h=g->h;
2343 			char* buf = (char*) g->pixels;
2344 			if (colourKey)
2345 			{
2346 				while (IsEmpty(g, w-1, 0, 1, h) && w>1)
2347 					w--;
2348 				while (IsEmpty(g, 0, h-1, w, 1) && h>1)
2349 					h--;
2350 			}
2351 
2352 			FILE* f = file_open(out, "wb");
2353 			fwrite(&w, sizeof(w), 1, f);
2354 			fwrite(&h, sizeof(h), 1, f);
2355 
2356 			uint32 mask = IMAGE_DAT_OR_MASK;
2357 			for (int i=0; i<(int)w*h; )
2358 			{
2359 				uint32 c = (*(uint32*)&buf[(i%w)*3 + (i/w)*g->pitch] | mask);
2360 				int i0 = i;
2361 				while (i < (int)w*h && c == (*(uint32*)&buf[(i%w)*3 + (i/w)*g->pitch] | mask))
2362 					i++;
2363 				c &= 0xffffff;
2364 				i0 = i-i0-1;
2365 				if (i0 < 0xff)
2366 					c |= i0 << 24;
2367 				else
2368 					c |= 0xff000000;
2369 
2370 				fwrite(&c, sizeof(c), 1, f);
2371 
2372 				if (i0 >= 0xff)
2373 					fwrite(&i0, sizeof(i0), 1, f);
2374 			}
2375 			fclose(f);
2376 
2377 			SDL_FreeSurface(g);
2378 
2379 			bmp = out;
2380 		}
2381 #endif
2382 
2383 		FILE* f = file_open(bmp, "rb");
2384 		if (!f) FATAL("Unable to open file", bmp);
2385 
2386 		int16_t w,h;
2387 		fread(&w, sizeof(w), 1, f);
2388 		fread(&h, sizeof(h), 1, f);
2389 		w = SWAP16(w);
2390 		h = SWAP16(h);
2391 		if (w>1500 || h>1500 || w<=0 || h<=0) FATAL("Invalid file", bmp);
2392 
2393 		tmp = new uint32[(int)w*h];
2394 
2395 		uint32 c = 0;
2396 		uint32 cnt = 0;
2397 		for (int p=0; p<(int)w*h; p++)
2398 		{
2399 			if (cnt)
2400 				cnt -= 0x1;
2401 			else
2402 			{
2403 				fread(&c, sizeof(c), 1, f);
2404 				c = SWAP32(c);
2405 				cnt = c >> 24;
2406 				if (cnt==255) {
2407 					fread(&cnt, sizeof(cnt), 1, f);
2408 					cnt = SWAP32(cnt);
2409 				}
2410 			}
2411 			tmp[p] = c | 0xff000000;
2412 		}
2413 
2414 		g = SDL_CreateRGBSurfaceFrom(tmp, w, h, 32, w*4,
2415 			0xff0000,
2416 			0xff00,
2417 			0xff,
2418 			0xff000000 );
2419 
2420 		fclose(f);
2421 
2422 
2423 		if (!g) FATAL("Unable to create SDL surface");
2424 		if (colourKey)
2425 			SDL_SetColorKey(g, SDL_SRCCOLORKEY, SDL_MapRGB(g->format, WATER_COLOUR));
2426 		SDL_Surface * out = SDL_DisplayFormat(g);
2427 		SDL_FreeSurface(g);
2428 		delete [] tmp;
2429 		if (!out) FATAL("Unable to create SDL surface (SDL_DisplayFormat)");
2430 		return out;
2431 	}
2432 
2433 	#ifdef USE_LEVEL_PACKFILE
2434 		PackFile1 levelFiles;
2435 	#endif
HexPuzzleHexPuzzle2436 	HexPuzzle()
2437 	{
2438 		SDL_WM_SetCaption(GAMENAME, 0);
2439 
2440 		time = 0;
2441 
2442 		#ifdef USE_LEVEL_PACKFILE
2443 			FILE* f = file_open("levels.dat", "rb");
2444 			if (!f)
2445 				FATAL("Unable to open file", "levels.dat");
2446 			levelFiles.Read(f);
2447 			fclose(f);
2448 		#endif
2449 
2450 		LoadGraphics();
2451 
2452 		isMap = false;
2453 		editMode = false;
2454 
2455 		currentLevelInfo = 0;
2456 
2457 		editTile = 0;
2458 		levelPar = 0;
2459 		levelDiff = 5;
2460 		turboAnim = 0;
2461 
2462 		memset(map, 0, sizeof(map));
2463 		memset(map_item, 0, sizeof(map_item));
2464 		memset(special, 0, sizeof(special));
2465 
2466 		LoadProgress();
2467 
2468 //		player = Pos(1,11);
2469 
2470 //		ResetLevel();
2471 
2472 		LoadMap();
2473 	}
2474 
LoadMapHexPuzzle2475 	void LoadMap()
2476 	{
2477 		#ifndef EDIT
2478 			progress.GetLevel(STARTING_LEVEL, true)->unlocked = 1;
2479 			if (!progress.GetLevel(STARTING_LEVEL, true)->Completed())
2480 			{
2481 				LoadSave(STARTING_LEVEL, false);
2482 				return;
2483 			}
2484 		#endif
2485 
2486 		//editMode = false;
2487 		LoadSave(mapname, false);
2488 		PlayMusic(HHOP_MUSIC_MAP);
2489 	}
2490 
RenderHexPuzzle2491 	void Render()
2492 	{
2493 		if (!activeMenu || activeMenu->renderBG)
2494 		{
2495 			SDL_Rect src  = {0,0,screen->w,screen->h};
2496 			SDL_Rect dst  = {0,0,screen->w,screen->h};
2497 			if (isRenderMap)
2498 			{
2499 				int boundW = mapBG->w;
2500 	#ifndef EDIT
2501 				boundW = MIN(boundW, (mapRightBound+4) * TILE_W2 - TILE_W1);
2502 	#endif
2503 				src.x = scrollX - initScrollX;
2504 				if (src.x+src.w > boundW)
2505 				{
2506 					int diff = src.x+src.w - boundW;
2507 					src.x -= diff;
2508 					if (isMap)
2509 						scrollX -= diff;
2510 				}
2511 				if (src.x < 0)
2512 				{
2513 					if (isMap)
2514 						scrollX -= src.x;
2515 					src.x = 0;
2516 				}
2517 				//scrollY = initScrollY;
2518 
2519 				if (isMap)
2520 					mapScrollX = scrollX;
2521 
2522 				SDL_BlitSurface(mapBG, &src, screen, &dst);
2523 			}
2524 			else
2525 				SDL_BlitSurface(gradient, &src, screen, &dst);
2526 
2527 			renderer.Render(time, true);
2528 
2529 			if (!hintsDone && !isFadeRendering)
2530 			{
2531 				DoHints();
2532 			}
2533 
2534 			if (1)
2535 			{
2536 				SDL_Rect src = {0,SCREEN_H-1,SCREEN_W,1};
2537 				SDL_Rect dst = {0,SCREEN_H-1,SCREEN_W,1};
2538 				for (int i=0; i<SCREEN_H; i++)
2539 				{
2540 					dst.x = src.x = 0;
2541 					dst.y = src.y = SCREEN_H-1-i;
2542 					src.w = SCREEN_W;
2543 					src.h = 1;
2544 
2545 					if (isRenderMap)
2546 					{
2547 						src.x += (int)( sin(i*0.9 + time*3.7) * sin(i*0.3 + time*0.7)*4 );
2548 						src.y += (int)( (sin(i*0.3 - time*2.2) * sin(i*0.48 + time*0.47) - 1) * 1.99 );
2549 					}
2550 					else
2551 					{
2552 						src.x += (int)( sin(i*0.5 + time*6.2) * sin(i*0.3 + time*1.05) * 5 );
2553 						src.y += (int)( (sin(i*0.4 - time*4.3) * sin(i*0.08 + time*1.9) - 1) * 2.5 );
2554 					}
2555 					SDL_BlitSurface(screen, &src, screen, &dst);
2556 				}
2557 			}
2558 
2559 			if(isRenderMap)
2560 				SDL_BlitSurface(mapBG2, &src, screen, &dst);
2561 
2562 			renderer.Render(time, false);
2563 
2564 			if (!isRenderMap && !isMap && !isFadeRendering)
2565 			{
2566 				int v[3] = {player_items[0], player_items[1], player_score};
2567 				if (numUndo > 1 && time < undo[numUndo-2].endTime)
2568 				{
2569 					int i = numUndo-1;
2570 					while (i>1 && time<undo[i-1].time)
2571 						i--;
2572 					v[0] = undo[i].numItems[0];
2573 					v[1] = undo[i].numItems[1];
2574 					v[2] = undo[i].score;
2575 				}
2576 				if (numUndo>1 && time < undo[0].time)
2577 					v[0]=v[1]=v[2]=0;
2578 	#ifdef EDIT
2579         /* TRANSLATORS: Anti-Ice are pickups, which turn ice plates into solid
2580            plates once you step on them. Each pickup changes one ice plate */
2581 				Print(0,0,_("Anti-Ice: %d"), v[0]);
2582 				Print(0,FONT_SPACING,_("Jumps: %d"), v[1]);
2583 				Print(0,FONT_SPACING*2,_("Score: %d (%d)"), v[2], player_score);
2584         /* TRANSLATORS: Par is similar to golf, a pre defined score which you
2585            can attempt to beat */
2586 				Print(0,FONT_SPACING*3,_("Par:   %d"), levelPar);
2587 				Print(0,FONT_SPACING*4,_("Diff:  %d"), levelDiff);
2588 	#else
2589 				if (showScoring)
2590 					Print(0, SCREEN_H-FONT_SPACING, _(" Par: %d   Current: %d"), levelPar, v[2]);
2591 
2592 				if (v[0])
2593 					Print(0,0,_(" Anti-Ice: %d"), v[0]);
2594 				else if (v[1])
2595 					Print(0,0,_(" Jumps: %d"), v[1]);
2596 	#endif
2597 			}
2598 			if (isRenderMap && isMap && !isFadeRendering)
2599 			{
2600 	#if 0//def EDIT
2601 				Print(0,0,_("Points: %d"), numComplete+numMastered);
2602 				Print(0,FONT_SPACING,_("Discovered: %d%% (%d/%d)"), numLevelsFound*100/numLevels, numLevelsFound, numLevels);
2603 				Print(0,FONT_SPACING*2,_("Complete: %d%% (%d)"), numComplete*100/numLevels, numComplete);
2604 				Print(0,FONT_SPACING*3,_("Mastered: %d%% (%d)"), numMastered*100/numLevels, numMastered);
2605 	#else
2606 				if (numComplete==numLevels && progress.general.endSequence>0)
2607 					Print(0, SCREEN_H-FONT_SPACING, _(" %d%% Mastered"), numMastered*100/numLevels);
2608 				else
2609 					Print(0, SCREEN_H-FONT_SPACING, _(" %d%% Complete"), numComplete*100/numLevels);
2610 
2611 				if (numMastered >= numLevels && progress.general.endSequence < 2)
2612 				{
2613 					progress.general.endSequence = 2;
2614 					LoadSaveProgress(true);
2615 
2616 					new Fader(-1, -7, 0.3);
2617 				}
2618 				if (numComplete >= numLevels && progress.general.endSequence < 1)
2619 				{
2620 					progress.general.endSequence = 1;
2621 					LoadSaveProgress(true);
2622 
2623 					new Fader(-1, -5, 0.3);
2624 				}
2625 	#endif
2626 			}
2627 			if ((currentLevelInfo || noMouse) && isMap && isRenderMap && !activeMenu && isFadeRendering<=0)
2628 			{
2629 				Pos p;
2630 				if (noMouse)
2631 					p = keyboardp;
2632 				else
2633 					p = mousep;
2634 				int pad = SCREEN_W/80;
2635 	//			SDL_Rect src = {0, 0, uiGraphics->w, uiGraphics->h};
2636 				SDL_Rect dst = {pad, SCREEN_H-TILE_H2-pad, 0, 0};
2637 		//		dst.x = p.getScreenX() + TILE_W3/2 - scrollX;
2638 		//		dst.y = p.getScreenY() - src.h/2 - scrollY;
2639 				dst.x = p.getScreenX() - scrollX;
2640 				dst.y = p.getScreenY() - scrollY - FONT_SPACING*3 - FONT_SPACING/2;
2641 		//		if (dst.x > SCREEN_W*2/3) dst.x -= TILE_W3 + src.w;
2642 		//		if (dst.y+src.h > screen->h-pad) dst.y = screen->h-pad - src.h;
2643 
2644 				RenderTile(false, 0, p.getScreenX(), p.getScreenY());
2645 			//	SDL_BlitSurface(uiGraphics, &src, screen, &dst);
2646 
2647 		//		dst.x += src.w/2;
2648 
2649 				if (currentLevelInfo)
2650 				{
2651 					keyboardp = p;
2652 
2653 					PrintC(true, dst.x, dst.y - FONT_SPACING/4, currentLevelInfo->name);
2654 
2655 					if (currentLevelInfo->file[0]!=0)
2656 					{
2657 						if (player_score > 0)
2658 						{
2659 							if (progress.general.scoringOn)
2660 							{
2661 								PrintC(false, dst.x, dst.y + FONT_SPACING*4 - FONT_SPACING/4, _("Best:% 3d"), player_score);
2662 								PrintC(false, dst.x, dst.y + FONT_SPACING*5 - FONT_SPACING/4, _("Par:% 3d"), levelPar);
2663 							}
2664 							else
2665 								PrintC(false, dst.x, dst.y + FONT_SPACING*4 - FONT_SPACING/4, _("Completed"), player_score);
2666 						}
2667 						else
2668 							PrintC(false, dst.x, dst.y + FONT_SPACING*4 - FONT_SPACING/4, _("Incomplete"), player_score);
2669 					}
2670 				}
2671 			}
2672 
2673 			// "Win"
2674 			if (win && numUndo > 0 && time > undo[numUndo-1].endTime + 2)
2675 			{
2676 				if (currentFile[0] && winFinal==0)
2677 				{
2678 					LevelSave* l = progress.GetLevel(currentFile, true);
2679 
2680 					new WinLoseScreen(true, player_score, showScoring ? levelPar : 0, l && showScoring && l->Completed() ? l->GetScore() : 0);
2681 
2682 					if (l->IsNewCompletionBetter(player_score))
2683 					{
2684 						l->SetScore(player_score);
2685 
2686 						l->SetSolution(numUndo);
2687 
2688 						for (int i=0; i<numUndo; i++)
2689 							l->SetSolutionStep(i, undo[i].playerMovement);
2690 					}
2691 
2692 					SaveProgress();
2693 				}
2694 
2695 				winFinal = 1;
2696 			}
2697 			else
2698 				winFinal = 0;
2699 
2700 			// Move up "level complete" writing so it doesn't feel like everything's ground to a halt...
2701 			if (win && numUndo > 0 && time > undo[numUndo-1].endTime && !winFinal)
2702 			{
2703 				double t = (time - undo[numUndo-1].endTime) / 2;
2704 				t=1-t;
2705 				t*=t*t;
2706 				t=1-t;
2707 				int y = SCREEN_H/3 - FONT_SPACING + 1;
2708 				y = SCREEN_H + int((y-SCREEN_H)*t);
2709 				PrintC(true, SCREEN_W/2, y, _("Level Complete!"));
2710 			}
2711 		}
2712 
2713 		if (activeMenu)
2714 			activeMenu->Render();
2715 
2716 		if (!noMouse)
2717 		{
2718 			// Edit cursor
2719 			if (editMode)
2720 			{
2721 				RenderTile(false, editTile, mousex+scrollX, mousey+scrollY);
2722 			}
2723 		}
2724 	}
2725 
CountHexPuzzle2726 	int Count(Tile t)
2727 	{
2728 		return tileCount[t];
2729 	}
SwapHexPuzzle2730 	int Swap(Tile t, Tile t2)
2731 	{
2732 		const int num = Count(t) + Count(t2);
2733 		if (t==t2 || num==0)
2734 			return Count(t);	// Nothing to do...
2735 
2736 		int count=0;
2737 		for (int x=0; x<MAP_SIZE; x++)
2738 			for (int y=0; y<MAP_SIZE; y++)
2739 			{
2740 				if (GetTile(Pos(x,y))==t)
2741 				{
2742 					count++;
2743 					SetTile(Pos(x,y), t2);
2744 				}
2745 				else if (GetTile(Pos(x,y))==t2)
2746 				{
2747 					count++;
2748 					SetTile(Pos(x,y), t);
2749 				}
2750 				if (count==num)
2751 					return count;
2752 			}
2753 		return count;
2754 	}
ReplaceHexPuzzle2755 	int Replace(Tile t, Tile t2)
2756 	{
2757 		const int num = Count(t);
2758 		if (t==t2 || num==0)
2759 			return num;	// Nothing to do...
2760 
2761 		int count=0;
2762 		for (int x=0; x<MAP_SIZE; x++)
2763 			for (int y=0; y<MAP_SIZE; y++)
2764 			{
2765 				Pos p(x,y);
2766 				if (GetTile(p)==t)
2767 				{
2768 					count++;
2769 
2770 					SetTile(p, t2, false);
2771 
2772 					if (t==COLLAPSE_DOOR && t2==COLLAPSABLE)
2773 						renderer(p).Add(new BuildRender(p, -1, 1, 1), time + (rand() & 255)*0.001);
2774 					else if (t==COLLAPSE_DOOR2 && t2==COLLAPSABLE2)
2775 						renderer(p).Add(new BuildRender(p, -1, 1, 1, 1), time + (rand() & 255)*0.001);
2776 					else
2777 						SetTile(p, t2);
2778 
2779 					if (count==num)
2780 						return count;
2781 				}
2782 			}
2783 		return count;
2784 	}
2785 
2786 	Tile editTile;
2787 	bool editMode;
ResetUndoHexPuzzle2788 	void ResetUndo()
2789 	{
2790 		UndoDone();
2791 		undoTime = -1;
2792 		numUndo = 0;
2793 		win = false;
2794 	}
2795 
UpdateCursorHexPuzzle2796 	void UpdateCursor(Pos const & s)
2797 	{
2798 		static Pos _s;
2799 		if (s.x!=_s.x || s.y!=_s.y)
2800 		{
2801 			_s = s;
2802 
2803 			char* sp = GetSpecial(s);
2804 			char tmp[1000];
2805 			tmp[0]='\0';
2806 			if (sp)
2807 			{
2808 				if (isMap)
2809 				{
2810 					currentLevelInfo = 0;
2811 					levelPar = player_score = -1;
2812 					if (GetLevelState(s)>=2)
2813 					{
2814 						LevelSave* l = progress.GetLevel(sp, true);
2815 						if (l)
2816 						{
2817 							currentLevelInfo = GetLevelInfo(sp);
2818 							levelPar = GetPar(sp);
2819 							player_score = l->GetScore();
2820 						}
2821 					}
2822 				}
2823 
2824 #ifdef EDIT
2825 				sprintf(tmp, _("Special(%d,%d): %s (%d)"), s.x, s.y, sp ? sp : _("<None>"), GetPar(sp));
2826 				SDL_WM_SetCaption(tmp, NULL);
2827 #endif
2828 			}
2829 			else if (currentFile[0])
2830 			{
2831 #ifdef EDIT
2832 				SDL_WM_SetCaption(currentFile, NULL);
2833 #endif
2834 				if (isMap)
2835 					currentLevelInfo = 0;
2836 			}
2837 		}
2838 	}
2839 
MouseHexPuzzle2840 	virtual void Mouse(int x, int y, int dx, int dy, int button_pressed, int button_released, int button_held)
2841 	{
2842 		if (activeMenu)
2843 		{
2844 			activeMenu->Mouse(x,y,dx,dy,button_pressed,button_released,button_held);
2845 			return;
2846 		}
2847 
2848 		if (isFadeRendering)
2849 			return;
2850 
2851 
2852 #ifndef EDIT
2853 		if (button_pressed==2 || (button_pressed==4 && isMap))
2854 		{
2855 			KeyPressed(SDLK_ESCAPE, 0);
2856 			keyState[SDLK_ESCAPE] = 0;
2857 			return;
2858 		}
2859 #endif
2860 
2861 		x += scrollX;
2862 		y += scrollY;
2863 
2864 		Pos s = Pos::GetFromWorld(x,y);
2865 		if (tileSolid[GetTile(Pos::GetFromWorld(x,y+TILE_HUP))] == 1)
2866 			s = Pos::GetFromWorld(x,y+TILE_HUP);
2867 
2868 		mousep = s;
2869 
2870 		UpdateCursor(s);
2871 
2872 #ifdef EDIT
2873 		if (button_held & ~button_pressed & 4)
2874 		{
2875 			scrollX -= dx;
2876 			scrollY -= dy;
2877 		}
2878 #endif
2879 
2880 		if (!editMode)
2881 		{
2882 			if (isMap && (button_pressed & 1))
2883 			{
2884 				ActivateSpecial(s, 1);
2885 				return;
2886 			}
2887 			if (!isMap && win && winFinal)
2888 			{
2889 				if (button_pressed & 1)
2890 				{
2891 					LoadMap();
2892 					return;
2893 				}
2894 			}
2895 			if(!isMap)
2896 			{
2897 				if((button_pressed & 1) || ((button_held & 1) && (numUndo==0 || time>=undo[numUndo-1].endTime)))
2898 				{
2899 					if(s.x==player.x && s.y==player.y)
2900 					{
2901 						// Don't activate jump powerup without a new click
2902 						if (button_pressed & 1)
2903 							Input(-1);
2904 					}
2905 					else if(s.x==player.x && s.y<player.y)
2906 						Input(0);
2907 					else if(s.x==player.x && s.y>player.y)
2908 						Input(3);
2909 					else if(s.y==player.y && s.x<player.x)
2910 						Input(5);
2911 					else if(s.y==player.y && s.x>player.x)
2912 						Input(2);
2913 					else if(s.y+s.x==player.y+player.x && s.x>player.x)
2914 						Input(1);
2915 					else if(s.y+s.x==player.y+player.x && s.x<player.x)
2916 						Input(4);
2917 				}
2918 				if ((button_pressed & 4) || ((button_held & 4) && (undoTime < 0)))
2919 					Undo();
2920 			}
2921 			return;
2922 		}
2923 
2924 #ifdef EDIT
2925 		if (!button_pressed && !button_held)
2926 			return;
2927 
2928 		if (button_pressed==1)
2929 			if (editTile<0)
2930 				editTile = GetItem(s)==1 ? -3 : GetItem(s)==2 ? -2 : -1;
2931 
2932 		if (button_held==1 || button_pressed==1)
2933 		{
2934 			ResetUndo();
2935 			if (editTile>=0)
2936 				SetTile(s, editTile, true, false);
2937 			else
2938 				SetItem(s, editTile==-2 ? 0 : editTile==-1 ? 1 : 2, true, false);
2939 		}
2940 
2941 		if (button_pressed==2)
2942 		{
2943 			editTile = GetTile(s);
2944 		}
2945 
2946 		if (button_pressed==8)
2947 		{
2948 			editTile=editTile-1;
2949 			if (editTile<=0) editTile=NumTileTypes-1;
2950 		}
2951 
2952 		if (button_pressed==16)
2953 		{
2954 			editTile=editTile+1;
2955 			if (editTile<=0) editTile=1;
2956 			if (editTile==NumTileTypes) editTile=0;
2957 		}
2958 
2959 		if (button_pressed==64)
2960 		{
2961 			ResetUndo();
2962 			player = s;
2963 			dead = false;
2964 			renderer.player.Reset(-1);
2965 			renderer.player.Add(new PlayerRender(player, GetHeight(player), dead), 0);
2966 		}
2967 
2968 		if (button_pressed==256)
2969 		{
2970 			char* fn = LoadSaveDialog(false, true, _("Select level"));
2971 			if (fn)
2972 			{
2973 				char * l = strstr(fn, "Levels");
2974 				if(l)
2975 				{
2976 					FILE * f = file_open(l,"rb");
2977 					if (f)
2978 						fclose(f);
2979 					if (f)
2980 						SetSpecial(s, l);
2981 					else if (l[6]!=0 && l[7]=='_')
2982 						SetSpecial(s, l+7);
2983 				}
2984 				UpdateCursor(Pos(-1,-1));
2985 			}
2986 		}
2987 		if (button_pressed==512)
2988 		{
2989 			SetSpecial(s, NULL);
2990 			UpdateCursor(Pos(-1,-1));
2991 		}
2992 		if (button_pressed==1024)
2993 		{
2994 			static char x[1000] = "";
2995 			if (!(s.x<0 || s.x>=MAP_SIZE || s.y<0 || s.y>=MAP_SIZE))
2996 			{
2997 				char tmp[1000];
2998 				strcpy(tmp, x);
2999 				if (GetSpecial(s))
3000 					strcpy(x, GetSpecial(s));
3001 				else
3002 					x[0] = 0;
3003 				SetSpecial(s, tmp[0] ? tmp : 0);
3004 				if (!tmp[0])
3005 					SetTile(s, EMPTY, true, false);
3006 			}
3007 		}
3008 
3009 		if (button_pressed==32)
3010 		{
3011 			editTile = editTile<0 ? 1 : -1;
3012 		}
3013 #endif // EDIT
3014 	}
3015 
CheckFinishedHexPuzzle3016 	void CheckFinished()
3017 	{
3018 		bool slow = false;
3019 		if (Count(COLLAPSABLE)==0)
3020 		{
3021 			if (Replace(COLLAPSE_DOOR, COLLAPSABLE) == 0)
3022 				win = true;
3023 			else
3024 				slow = true;
3025 			Replace(SWITCH, NORMAL);
3026 		}
3027 		else
3028 			win = false;
3029 
3030 		if (Count(COLLAPSABLE2)==0)
3031 			if (Replace(COLLAPSE_DOOR2, COLLAPSABLE2))
3032 				slow = true;
3033 
3034 		if (slow)
3035 		{
3036 			QueueSound(HHOP_SOUND_COLLAPSE, time);
3037 			time += BUILD_TIME;
3038 		}
3039 	}
CollideHexPuzzle3040 	bool Collide(Pos p, bool high)
3041 	{
3042 		Tile t = GetTile(p);
3043 //		switch(t)
3044 //		{
3045 //		default:
3046 			if (!high)
3047 				return tileSolid[t]==1;
3048 			else
3049 				return false;
3050 //		}
3051 	}
UndoHexPuzzle3052 	void Undo()
3053 	{
3054 		if (numUndo==0) return;
3055 
3056 		UndoDone(); // Complete previous undo...
3057 
3058 		numUndo--;
3059 
3060 		if (time > undo[numUndo].endTime)
3061 			time = undo[numUndo].endTime;
3062 		undoTime = undo[numUndo].time;
3063 
3064 		undo[numUndo].Restore(this);
3065 
3066 		// Cancel all queued sounds.
3067 		UndoSound();
3068 
3069 		// Restore game music if undid winning move.
3070 		PlayMusic(HHOP_MUSIC_GAME);
3071 	}
UndoDoneHexPuzzle3072 	void UndoDone()
3073 	{
3074 		if (undoTime < 0)
3075 			return;
3076 		renderer.Reset(undoTime);
3077 		time = undoTime;
3078 		undoTime = -1;
3079 	}
ScoreDestroyHexPuzzle3080 	void ScoreDestroy(Pos p)
3081 	{
3082 		Tile t = GetTile(p);
3083 		if (t==COLLAPSABLE || t==COLLAPSE_DOOR)
3084 		{}
3085 		else if (t != EMPTY)
3086 		{
3087 			player_score += 10;
3088 		}
3089 	}
3090 
LaserTileHexPuzzle3091 	bool LaserTile(Pos p, int mask, double fireTime)
3092 	{
3093 		if (&renderer(p) == &renderer(Pos(-1,-1)))
3094 			return false;
3095 		//if (!renderer.Visible(p))
3096 		//	return false;
3097 
3098 		TileRender* tr = 0;
3099 		if (time <= renderer(p).GetLastTime())
3100 			if (fireTime < renderer(p).GetLastTime())
3101 			{
3102 				renderer(p).Add(tr = new TileRender(GetTile(p), p, mask), fireTime);
3103 				((TileRender*)renderer(p).GetStage(time+10/*HACKY!*/))->special |= mask;
3104 			}
3105 			else
3106 			{
3107 				tr = new TileRender(GetTile(p), p, mask | ((TileRender*)renderer(p).GetStage(time+10/*HACKY!*/))->special);
3108 				renderer(p).Add(tr, fireTime);
3109 			}
3110 		else
3111 			renderer(p).Add(tr = new TileRender(GetTile(p), p, mask), fireTime);
3112 
3113 		if (tr)
3114 		{
3115 			tr->specialDuration = time + LASER_LINE_TIME - fireTime + LASER_FADE_TIME;
3116 		}
3117 		return true;
3118 	}
FireGunHexPuzzle3119 	void FireGun(Pos newpos, Dir d, bool recurse, double fireTime)
3120 	{
3121 		static Pos hits[100];
3122 		static Dir hitDir[100];
3123 		static unsigned int numHits=0;
3124 		if (!recurse)
3125 			numHits = 0;
3126 
3127 		double starttime = fireTime;
3128 		for (Dir fd=((d<0)?0:d); fd<((d<0)?MAX_DIR:d+1); fd++)
3129 		{
3130 			fireTime = starttime;
3131 		//	starttime += 0.03;
3132 
3133 			Pos p = newpos + fd;
3134 			int range = 0;
3135 			for (; range<MAP_SIZE; range++, p=p+fd)
3136 			{
3137 				Tile t = GetTile(p);
3138 				if (tileSolid[t]!=-1)
3139 				{
3140 					if (t!=TRAP)
3141 						renderer(p).Add(new TileRender(tileSolid[t]==1 ? TILE_WHITE_WALL : TILE_WHITE_TILE, p), fireTime+0.1);
3142 
3143 					unsigned int i;
3144 					for (i=0; i<numHits; i++)
3145 						if (hits[i]==p)
3146 							break;
3147 					if (i==numHits ||
3148 						(t==TRAP && (hitDir[i]&(1<<fd))==0)
3149 					   )
3150 					{
3151 						if (i==numHits)
3152 						{
3153 							if (i >= sizeof(hits)/sizeof(hits[0]))
3154 								return;
3155 							hitDir[i] = 1 << fd;
3156 							hits[i] = p;
3157 							numHits++;
3158 						}
3159 						else
3160 						{
3161 							hitDir[i] |= 1 << fd;
3162 						}
3163 						if (t==TRAP)
3164 						{
3165 							int dirmask =
3166 								  1<<((fd+2) % MAX_DIR)
3167 								| 1<<((fd+MAX_DIR-2) % MAX_DIR);
3168 
3169 							if (LaserTile(p, dirmask, fireTime))
3170 								fireTime += (time+LASER_LINE_TIME - fireTime) / 40;
3171 //							fireTime += LASER_SEGMENT_TIME;
3172 
3173 							FireGun(p, (fd+1) % MAX_DIR, true, fireTime);
3174 							FireGun(p, (fd+MAX_DIR-1) % MAX_DIR, true, fireTime);
3175 						}
3176 					}
3177 					break;
3178 				}
3179 				else
3180 				{
3181 					LaserTile(p, 1<<(fd%3), fireTime);
3182 
3183 					fireTime += (time+LASER_LINE_TIME - fireTime) / 40;
3184 //					fireTime += LASER_SEGMENT_TIME;
3185 				}
3186 			}
3187 
3188 //			renderer().Add(new LaserRender(newpos, fd, range), time);
3189 		}
3190 
3191 		if (!recurse)
3192 		{
3193 			//double _time = time;
3194 			time += LASER_LINE_TIME;
3195 			for (unsigned int i=0; i<numHits; i++)
3196 			{
3197 				Pos p = hits[i];
3198 				Tile t = GetTile(p);
3199 
3200 				if (t==TRAP)
3201 					continue;
3202 
3203 				ScoreDestroy(p);
3204 
3205 				renderer(p).Add(new ExplosionRender(p, t==GUN), time);
3206 				//renderer(p).Add(new TileRender(EMPTY, p), time+2);
3207 				SetTile(p, EMPTY, false);
3208 
3209 				if (GetItem(p))
3210 					renderer(p,true).Add(new ItemRender(GetItem(p), 1, p), time);
3211 
3212 				if (t==GUN)
3213 					QueueSound(HHOP_SOUND_EXPLODE_BIG, time);
3214 				else
3215 					QueueSound(HHOP_SOUND_EXPLODE_SMALL, time);
3216 
3217 				if (t==GUN)
3218 				{
3219 					for (Dir j=0; j<MAX_DIR; j++)
3220 					{
3221 						if (GetTile(p+j)!=EMPTY)
3222 						{
3223 							renderer(p+j).Add(new TileRender(tileSolid[GetTile(p+j)]==1 ? TILE_WHITE_WALL : TILE_WHITE_TILE, p+j), time+0.05);
3224 							renderer(p+j).Add(new ExplosionRender(p+j), time+0.2);
3225 
3226 							if (GetItem(p+j))
3227 								renderer(p+j,true).Add(new ItemRender(GetItem(p+j), 1, p), time);
3228 
3229 							//renderer(p+j).Add(new TileRender(EMPTY, p+j), time+2.2);
3230 						}
3231 						ScoreDestroy(p + j);
3232 						SetTile(p + j, EMPTY, false);
3233 					}
3234 				}
3235 			}
3236 
3237 			time += MAX(LASER_FADE_TIME, 0.15);
3238 			//time = _time;
3239 			CheckFinished();
3240 		}
3241 	}
GetLastPlayerRotHexPuzzle3242 	int GetLastPlayerRot()
3243 	{
3244 		RenderStage* rs = renderer.player.GetStage(-1);
3245 		if (!rs) return 3;
3246 		return ((PlayerRender*)rs)->r;
3247 	}
InputHexPuzzle3248 	bool Input(Dir d)
3249 	{
3250 		if (dead || win || isMap)
3251 			return false;
3252 
3253 		// Complete undo
3254 		UndoDone();
3255 
3256 		// Jump forwards in time to last move finishing
3257 		if (numUndo > 0 && time < undo[numUndo-1].endTime)
3258 			time = undo[numUndo-1].endTime;
3259 
3260 		double realTime = time;
3261 		double endAnimTime = time;
3262 		bool high = (tileSolid[GetTile(player)] == 1);
3263 		Pos playerStartPos = player;
3264 		Pos oldpos = player;
3265 		int oldPlayerHeight = GetHeight(oldpos);
3266 		Pos newpos = player + d;
3267 
3268 		int playerRot = GetLastPlayerRot();
3269 		if (d!=-1 && d!=playerRot)
3270 		{
3271 			while (d!=playerRot)
3272 			{
3273 				if ((d+6-playerRot) % MAX_DIR < MAX_DIR/2)
3274 					playerRot = (playerRot+1) % MAX_DIR;
3275 				else
3276 					playerRot = (playerRot+MAX_DIR-1) % MAX_DIR;
3277 
3278 				time += 0.03;
3279 
3280 				if (GetTile(oldpos) == FLOATING_BALL)
3281 				{
3282 					TileRender* t = new TileRender(FLOATING_BALL, oldpos);
3283 					t->special = playerRot + 256;
3284 					renderer(oldpos).Add(t, time);
3285 
3286 					renderer.player.Add(new PlayerRender(playerRot, Pos(-20,-20), oldPlayerHeight, Pos(-20,-20), oldPlayerHeight, dead), time);
3287 				}
3288 				else
3289 				{
3290 					PlayerRender *p = new PlayerRender(playerRot, player, oldPlayerHeight, player, oldPlayerHeight, dead);
3291 					p->speed = 0;
3292 					renderer.player.Add(p, time);
3293 				}
3294 			}
3295 
3296 			time += 0.03;
3297 		}
3298 
3299 		if (d<0 && player_items[1]==0)
3300 			return false;
3301 
3302 		if (d >= 0)
3303 		{
3304 			if (tileSolid[GetTile(newpos)] == -1)
3305 			{
3306 				time = realTime;
3307 				return false;
3308 			}
3309 			if (Collide(newpos, high))
3310 			{
3311 				time = realTime;
3312 				return false;
3313 			}
3314 		}
3315 
3316 		// Don't change any real state before this point!
3317 		if (numUndo >= MAX_UNDO)
3318 		{
3319 			numUndo--;
3320 			for(int i=0; i<MAX_UNDO-1; i++)
3321 				undo[i] = undo[i+1];
3322 		}
3323 		undo[numUndo].New(d, player, player_items, time, player_score);
3324 
3325 		if (d<0)
3326 		{
3327 			QueueSound(HHOP_SOUND_USED_JUMP, time);
3328 			player_items[1]--;
3329 		}
3330 
3331 		double time0 = time;
3332 		time += 0.15;	//Time for leave-tile fx
3333 
3334 		if (d>=0)
3335 		{
3336 			QueueSound(HHOP_SOUND_STEP, time);
3337 			switch (GetTile (newpos))
3338 			{
3339 				case COLLAPSABLE:
3340 				case COLLAPSABLE2:
3341 				case COLLAPSE_DOOR:
3342 				case COLLAPSE_DOOR2:
3343 					QueueSound(HHOP_SOUND_CRACK, time + 0.28);
3344 					break;
3345 			}
3346 		}
3347 
3348 		switch (GetTile(oldpos))
3349 		{
3350 			case COLLAPSABLE:
3351 				QueueSound(HHOP_SOUND_DISINTEGRATE, time);
3352 				SetTile(oldpos, EMPTY);
3353 				renderer(oldpos).Add(new DisintegrateRender(oldpos), time);
3354 				CheckFinished();
3355 				break;
3356 
3357 			case COLLAPSE_DOOR:
3358 				// Don't need to CheckFinished - can't be collapse doors around
3359 				//  unless there're still collapsable tiles around.
3360 				QueueSound(HHOP_SOUND_DISINTEGRATE, time);
3361 				SetTile(oldpos, EMPTY);
3362 				renderer(oldpos).Add(new DisintegrateRender(oldpos, 1), time);
3363 				break;
3364 
3365 			case COLLAPSABLE2:
3366 				QueueSound(HHOP_SOUND_DISINTEGRATE, time);
3367 				SetTile(oldpos, COLLAPSABLE, false);
3368 				renderer(oldpos).Add(new DisintegrateRender(oldpos, 0, 1), time);
3369 				player_score += 10;
3370 				CheckFinished();
3371 				break;
3372 
3373 			case COLLAPSE_DOOR2:
3374 				QueueSound(HHOP_SOUND_DISINTEGRATE, time);
3375 				SetTile(oldpos, COLLAPSE_DOOR, false);
3376 				renderer(oldpos).Add(new DisintegrateRender(oldpos, 1, 1), time);
3377 				player_score += 10;
3378 				break;
3379 
3380 			case COLLAPSABLE3:
3381 				SetTile(oldpos, COLLAPSABLE2);
3382 				break;
3383 		}
3384 
3385 		time = time0;	//End of leave-tile fx
3386 
3387 		int retry_pos_count=0;
3388 retry_pos:
3389 		retry_pos_count++;
3390 
3391 		if (GetItem(newpos)==1)
3392 		{
3393 			QueueSound(HHOP_SOUND_FOUND_ANTIICE, time);
3394 			renderer(newpos, true).Add(new ItemCollectRender(GetItem(newpos), newpos), time + JUMP_TIME/2);
3395 			SetItem(newpos, 0, false);
3396 			player_items[0]++;
3397 		}
3398 		if (GetItem(newpos)==2)
3399 		{
3400 			QueueSound(HHOP_SOUND_FOUND_JUMP, time);
3401 			renderer(newpos, true).Add(new ItemCollectRender(GetItem(newpos), newpos), time + JUMP_TIME/2);
3402 			SetItem(newpos, 0, false);
3403 			player_items[1]++;
3404 		}
3405 
3406 		if (GetTile(player) == FLOATING_BALL)
3407 		{
3408 			TileRender* t = new TileRender(FLOATING_BALL, player);
3409 			t->special = 0;
3410 			renderer(oldpos).Add(t, time);
3411 		}
3412 
3413 		PlayerRender *p = new PlayerRender(playerRot, player, oldPlayerHeight, newpos, GetHeight(newpos), dead);
3414 
3415 		// alternate leg (hacky!)
3416 		if (1)
3417 		{
3418 			static int l=0;
3419 			l++;
3420 			p->type = l & 1;
3421 		}
3422 
3423 		if (retry_pos_count!=0 && GetTile(player)==TRAP)
3424 		{
3425 			p->speed /= 1.5;
3426 			p->type = 2;
3427 		}
3428 		if (d==-1)
3429 			p->speed = JUMP_TIME * 1.5;
3430 		renderer.player.Add(p, time);
3431 
3432 		endAnimTime = MAX(endAnimTime, time + p->speed+0.001);
3433 		time += p->speed;
3434 
3435 		player = newpos;
3436 
3437 		switch (GetTile(newpos))
3438 		{
3439 			case COLLAPSABLE:
3440 				renderer(newpos).Add(new TileRender(TILE_GREEN_CRACKED, newpos), time);
3441 				break;
3442 			case COLLAPSE_DOOR:
3443 				renderer(newpos).Add(new TileRender(TILE_GREEN_CRACKED_WALL, newpos), time);
3444 				break;
3445 			case COLLAPSABLE2:
3446 				renderer(newpos).Add(new TileRender(TILE_BLUE_CRACKED, newpos), time);
3447 				break;
3448 			case COLLAPSE_DOOR2:
3449 				renderer(newpos).Add(new TileRender(TILE_BLUE_CRACKED_WALL, newpos), time);
3450 				break;
3451 
3452 			case EMPTY:
3453 				dead = true;
3454 				break;
3455 
3456 			case BUILDER:
3457 			{
3458 				double pretime = time;
3459 				bool done = false;
3460 				time += 0.15;
3461 				for (Dir fd=0; fd<MAX_DIR; fd++)
3462 				{
3463 					Tile t2 = GetTile(newpos + fd);
3464 					if (t2==EMPTY)
3465 					{
3466 						done = true;
3467 						SetTile(newpos+fd, COLLAPSABLE, false);
3468 						renderer(newpos+fd).Add(new BuildRender(newpos+fd, fd, 0), time);
3469 					}
3470 					else if (t2==COLLAPSABLE)
3471 					{
3472 						done = true;
3473 						SetTile(newpos+fd, COLLAPSE_DOOR, false);
3474 						renderer(newpos+fd).Add(new BuildRender(newpos+fd, fd, 1), time);
3475 					}
3476 				}
3477 				if (done)
3478 				{
3479 					QueueSound(HHOP_SOUND_BUILDER, time);
3480 					time += BUILD_TIME;
3481 				}
3482 				else
3483 					time = pretime;
3484 				CheckFinished();
3485 				endAnimTime = MAX(endAnimTime, time + 0.1);
3486 			}
3487 			break;
3488 
3489 			case SWITCH:
3490 				// FIXME SOUND: No switches in the game currently?
3491 				Swap(COLLAPSE_DOOR, COLLAPSABLE);
3492 				break;
3493 
3494 			case FLOATING_BALL:
3495 			{
3496 				int step=0;
3497 				QueueSound(HHOP_SOUND_FLOATER_ENTER, time);
3498 				renderer.player.Add(new PlayerRender(playerRot, Pos(-30,-30), 0, Pos(-30,-30), 0, dead), time);
3499 				while (tileSolid[GetTile(newpos+d)]==-1)
3500 				{
3501 					step++;
3502 
3503 					if (!renderer.Visible(newpos+d))
3504 					{
3505 						TileRender* r = new TileRender(FLOATING_BALL, newpos);
3506 						r->special = 512;
3507 						renderer(newpos).Add(r, time);
3508 
3509 						PlayerRender* pr = new PlayerRender(playerRot, newpos, 0, newpos, 0, dead);
3510 						pr->speed = JUMP_TIME*1;
3511 						renderer.player.Add(pr, time);
3512 
3513 						time += pr->speed;
3514 
3515 						dead = 1;
3516 						break;
3517 					}
3518 					oldpos = newpos;
3519 					newpos = oldpos + d;
3520 
3521 					SetTile(oldpos, EMPTY, false);
3522 					SetTile(newpos, FLOATING_BALL, false);
3523 
3524 					renderer(oldpos).Add(new TileRotateRender(FLOATING_BALL, oldpos, d, 2), time);
3525 					renderer(oldpos).Add(new TileRender(EMPTY, oldpos), time + ROTATION_TIME/2);
3526 					renderer(newpos).Add(new TileRotateRender(FLOATING_BALL, newpos, (d+3)%MAX_DIR, 3), time + ROTATION_TIME/2);
3527 
3528 //					PlayerRender *p = new PlayerRender(playerRot, oldpos, 0, newpos, 0, dead);
3529 //					p->speed = ROTATION_TIME*0.9;
3530 //					renderer.player.Add(p, time);
3531 
3532 					endAnimTime = MAX(endAnimTime, time + ROTATION_TIME + ROTATION_TIME/2);
3533 					time += ROTATION_TIME;
3534 					QueueSound(HHOP_SOUND_FLOATER_MOVE, time);
3535 				}
3536 				player = newpos;
3537 //				renderer.player.Add(new PlayerRender(playerRot, player, 0, player, 0, 0), time);
3538 				if (dead)
3539 				{
3540 				}
3541 				else
3542 				{
3543 					TileRender* r = new TileRender(FLOATING_BALL, newpos);
3544 					r->special = playerRot + 256;
3545 					renderer(newpos).Add(r, time);
3546 				}
3547 			}
3548 			break;
3549 
3550 			case LIFT_DOWN:
3551 			case LIFT_UP:
3552 			{
3553 				if (GetTile(newpos)==LIFT_UP)
3554 					QueueSound(HHOP_SOUND_LIFT_DOWN, time);
3555 				else
3556 					QueueSound(HHOP_SOUND_LIFT_UP, time);
3557 				SetTile(newpos, GetTile(newpos)==LIFT_UP ? LIFT_DOWN : LIFT_UP, false);
3558 				renderer(newpos).Add(new TileRender(GetTile(newpos), newpos, 1), time);
3559 
3560 				PlayerRender *p = new PlayerRender(playerRot, newpos, 1-GetHeight(newpos), newpos, GetHeight(newpos), dead);
3561 				renderer.player.Add(p, time);
3562 				endAnimTime = MAX(endAnimTime, time + JUMP_TIME);
3563 			}
3564 			break;
3565 
3566 			case TRAMPOLINE:
3567 				if (d<0) break;
3568 				QueueSound(HHOP_SOUND_TRAMPOLINE, time);
3569 
3570 				oldpos = newpos;
3571 				if (Collide(newpos + d, high))
3572 					break;
3573 				if (Collide((newpos + d) + d, high) == 1)
3574 					newpos = (newpos + d);
3575 				else
3576 					newpos = (newpos + d) + d;
3577 				if (tileSolid[GetTile(newpos)] == -1)
3578 					dead=1;
3579 				//player = newpos;
3580 				goto retry_pos;
3581 
3582 			case SPINNER:
3583 			{
3584 				QueueSound(HHOP_SOUND_SPINNER, time);
3585 				for (Dir d=0; d<MAX_DIR; d++)
3586 				{
3587 					Tile tmp = GetTile(newpos + d);
3588 					renderer(newpos + d).Add(new TileRotateRender(tmp, newpos + d, (d+2)%MAX_DIR, false), time);
3589 				}
3590 				Tile tmp = GetTile(newpos + Dir(MAX_DIR-1));
3591 				for (Dir d=0; d<MAX_DIR; d++)
3592 				{
3593 					Tile t2 = GetTile(newpos + d);
3594 					SetTile(newpos + d, tmp, false);
3595 					renderer(newpos + d).Add(new TileRotateRender(tmp, newpos + d, (d+4)%MAX_DIR, true), time + ROTATION_TIME/2);
3596 					if (GetItem(newpos + d))
3597 						renderer(newpos + d,true).Add(new ItemRender(GetItem(newpos + d), GetTile(newpos + d)==EMPTY, newpos+d), time + ROTATION_TIME/2);
3598 
3599 					tmp = t2;
3600 				}
3601 				endAnimTime = MAX(endAnimTime, time+ROTATION_TIME);
3602 //				renderer(newpos).Add(new TileRotateRender(SPINNER, Dir(0), 0), time);
3603 			}
3604 			break;
3605 
3606 			case TRAP:
3607 			{
3608 				if (d<0) break;
3609 
3610 				if (player_items[0]==0)
3611 				{
3612 					QueueSound(HHOP_SOUND_ICE, time);
3613 					if (tileSolid[GetTile(newpos + d)] == 1)
3614 						break;
3615 					newpos = newpos + d;
3616 					if (tileSolid[GetTile(newpos)] == -1)
3617 						dead=1;
3618 					//player = newpos;
3619 					goto retry_pos;
3620 				}
3621 				else
3622 				{
3623 					QueueSound(HHOP_SOUND_USED_ANTIICE, time);
3624 					SetTile(newpos, COLLAPSABLE3);
3625 					player_items[0]--;
3626 				}
3627 			}
3628 			break;
3629 
3630 			case GUN:
3631 			{
3632 				QueueSound(HHOP_SOUND_LASER, time);
3633 				FireGun(newpos, d, false, time);
3634 
3635 				endAnimTime = MAX(endAnimTime, time);
3636 
3637 				if (GetTile(newpos)==EMPTY)
3638 				{
3639 					PlayerRender* pr = new PlayerRender(playerRot, player, 0, player, 0, dead);
3640 					pr->speed = JUMP_TIME*1;
3641 					renderer.player.Add(pr, time);
3642 
3643 					time += pr->speed;
3644 					dead = 1;
3645 				}
3646 
3647 				/*
3648 				Pos hits[MAX_DIR];
3649 				int numHits=0;
3650 
3651 				for (Dir fd=((d<0)?0:d); fd<((d<0)?MAX_DIR:d+1); fd++)
3652 				{
3653 					Pos p = newpos + fd;
3654 					int range = 0;
3655 					for (range; range<MAP_SIZE; range++, p=p+fd)
3656 					{
3657 						Tile t = GetTile(p);
3658 						if (tileSolid[t]!=-1)
3659 						{
3660 							hits[numHits++] = p;
3661 							break;
3662 						}
3663 					}
3664 
3665 					renderer().Add(new LaserRender(newpos, fd, range), time);
3666 				}
3667 
3668 				double _time = time;
3669 				time += 0.25;
3670 				for (int i=0; i<numHits; i++)
3671 				{
3672 					Pos p = hits[i];
3673 					Tile t = GetTile(p);
3674 
3675 					renderer().Add(new ExplosionRender(p), time);
3676 					ScoreDestroy(p);
3677 					SetTile(p, EMPTY);
3678 
3679 					if (t==GUN)
3680 					{
3681 						for (Dir j=0; j<MAX_DIR; j++)
3682 						{
3683 							ScoreDestroy(p + j);
3684 							SetTile(p + j, EMPTY);
3685 						}
3686 						if (GetTile(newpos)==EMPTY)
3687 							dead = 1;
3688 					}
3689 				}
3690 				endAnimTime = MAX(endAnimTime, time);
3691 
3692 				time = _time;
3693 
3694 				CheckFinished();
3695 */
3696 				break;
3697 			}
3698 		}
3699 
3700 		endAnimTime = MAX(endAnimTime, time);
3701 
3702 		if (dead)
3703 		{
3704 			QueueSound(HHOP_SOUND_DEATH, time);
3705 			win = false;
3706 
3707 			PlayerRender* pr = new PlayerRender(player, 0, dead);
3708 			pr->speed = 0; // Don't sit around before disappearing!
3709 			renderer.player.Add(pr, time);
3710 
3711 			// If the tile we're drowning on isn't visible, give the ownership of the splash effect to the player, rather than a tile.
3712 			if (renderer.Visible(player))
3713 				renderer(player).Add(new ExplosionRender(player, 0, 1), time);
3714 			else
3715 				renderer.player.Add(new ExplosionRender(player, 0, 1), time);
3716 
3717 			endAnimTime = MAX(endAnimTime, time+2);
3718 		}
3719 		if (win)
3720 		{
3721 			QueueSound(HHOP_SOUND_WIN, time);
3722 			PlayMusic(HHOP_MUSIC_WIN);
3723 		}
3724 
3725 		time = realTime;
3726 
3727 		player_score += 1;
3728 
3729 		undo[numUndo].endTime = endAnimTime;
3730 		numUndo++;
3731 
3732 		return true;
3733 	}
UpdateHexPuzzle3734 	void Update(double timedelta)
3735 	{
3736 		while(deadMenu)
3737 			delete deadMenu;
3738 
3739 		if (activeMenu)
3740 		{
3741 			activeMenu->Update(timedelta);
3742 		}
3743 		else
3744 			UpdateKeys();
3745 
3746 		for (int i=0; i<SDLK_LAST; i++)
3747 			if (keyState[i])
3748 				keyState[i] = 1;
3749 
3750 		if (activeMenu)
3751 			return;
3752 
3753 		if (isMap && isRenderMap)
3754 		{
3755 			double min = 50;
3756 			static double scrollHi = 0;
3757 			double x = 0;
3758 #ifndef EDIT
3759 //			if (!noMouse)
3760 			{
3761 				int xx = noMouse ? keyboardp.getScreenX()-scrollX : mousex;
3762 				if (xx > SCREEN_W) xx = SCREEN_W;
3763 				int w = TILE_W2*4;
3764 				if (xx < w)
3765 					x = (double)xx / (w) - 1;
3766 				if (xx > SCREEN_W - w)
3767 					x = 1 - (double)(SCREEN_W-xx) / (w);
3768 				x *= 500;
3769 				if (x<-min || x>min)
3770 				{
3771 					scrollHi += timedelta * x;
3772 					scrollX += (int)scrollHi;
3773 					scrollHi -= (int)scrollHi;
3774 				}
3775 			}
3776 #endif
3777 		}
3778 		if (undoTime>=0 && undoTime < time)
3779 		{
3780 			double acc = (time - undoTime) / 2;
3781 			if (acc < 3) acc = 3;
3782 			time -= timedelta * acc;
3783 			if (undoTime >= time)
3784 				UndoDone();
3785 		}
3786 		else
3787 		{
3788 			time += timedelta;
3789 			if (turboAnim)
3790 				time += timedelta * 20;
3791 			UpdateSound(time);
3792 		}
3793 	}
FileDropHexPuzzle3794 	void FileDrop(const char* filename)
3795 	{
3796 		LoadSave(filename, false);
3797 	}
UpdateKeysHexPuzzle3798 	void UpdateKeys()
3799 	{
3800 #ifdef EDIT
3801 		if (keyState[SDLK_LALT] || keyState[SDLK_LCTRL])
3802 			return;
3803 #endif
3804 
3805 		if (!isMap && !editMode && undoTime < 0)
3806 		{
3807 			if (keyState[SDLK_z] || keyState[SDLK_BACKSPACE] || keyState[SDLK_u])
3808 			{
3809 				Undo();
3810 				return;
3811 			}
3812 		}
3813 		if (isMap && !editMode)
3814 		{
3815 
3816 			if ((keyState[SDLK_q] | keyState[SDLK_KP7]) & 2) keyboardp.x--;
3817 			else if ((keyState[SDLK_d] | keyState[SDLK_KP3]) & 2) keyboardp.x++;
3818 			else if ((keyState[SDLK_e] | keyState[SDLK_KP9]) & 2) keyboardp.x++, keyboardp.y--;
3819 			else if ((keyState[SDLK_a] | keyState[SDLK_KP1]) & 2) keyboardp.x--, keyboardp.y++;
3820 			else if ((keyState[SDLK_w] | keyState[SDLK_KP8] | keyState[SDLK_UP]) & 2) keyboardp.y--;
3821 			else if ((keyState[SDLK_s] | keyState[SDLK_KP2] | keyState[SDLK_DOWN]) & 2) keyboardp.y++;
3822 			else if ((keyState[SDLK_LEFT]) & 2) keyboardp.x--, keyboardp.y+=keyboardp.x&1;
3823 			else if (((keyState[SDLK_RIGHT]) & 2)) { if (keyboardp.x < mapRightBound) keyboardp.y-=keyboardp.x&1, keyboardp.x++; }
3824 			else if ((keyState[SDLK_RETURN] | keyState[SDLK_KP5] | keyState[SDLK_SPACE] | keyState[SDLK_KP_ENTER]) & 2)
3825 			{
3826 				// Simulate user clicking on it...
3827 				Mouse(keyboardp.getScreenX()-scrollX, keyboardp.getScreenY()-scrollY, 0, 0, 1, 0, 0);
3828 				noMouse = 1;
3829 				return;
3830 			}
3831 			else
3832 			{
3833 				if (noMouse)
3834 					UpdateCursor(keyboardp);
3835 				return;
3836 			}
3837 			int min[21] = { 17, 16, 15, 14, 13, 13, 13, 13, 13, 13, 12, 11, 11, 13, 12, 11,  8,  8,  7,  6,  7 };
3838 			int max[21] = { 20, 20, 19, 19, 19, 19, 18, 21, 20, 20, 19, 19, 18, 18, 17, 16, 16, 16, 15, 15, 14 };
3839 			if (keyboardp.x < 3) keyboardp.x = 3;
3840 			if (keyboardp.x > mapRightBound) keyboardp.x = mapRightBound;
3841 
3842 			if (keyboardp.y < min[keyboardp.x-3]) keyboardp.y = min[keyboardp.x-3];
3843 			if (keyboardp.y > max[keyboardp.x-3]) keyboardp.y = max[keyboardp.x-3];
3844 			noMouse = 1;
3845 			UpdateCursor(keyboardp);
3846 		}
3847 		else if (!editMode && (numUndo==0 || time>=undo[numUndo-1].endTime))
3848 		{
3849 			static int usedDiag = 0;
3850 
3851 			if (keyState[SDLK_q] || keyState[SDLK_KP7]) HandleKey('q', 0);
3852 			else if (keyState[SDLK_w] || keyState[SDLK_KP8]) HandleKey('w', 0);
3853 			else if (keyState[SDLK_e] || keyState[SDLK_KP9]) HandleKey('e', 0);
3854 			else if (keyState[SDLK_a] || keyState[SDLK_KP1]) HandleKey('a', 0);
3855 			else if (keyState[SDLK_s] || keyState[SDLK_KP2]) HandleKey('s', 0);
3856 			else if (keyState[SDLK_d] || keyState[SDLK_KP3]) HandleKey('d', 0);
3857 
3858 			else if (keyState[SDLK_UP] && keyState[SDLK_LEFT]) HandleKey('q', 0), usedDiag=1;
3859 			else if (keyState[SDLK_UP] && keyState[SDLK_RIGHT]) HandleKey('e', 0), usedDiag=1;
3860 			else if (keyState[SDLK_DOWN] && keyState[SDLK_LEFT]) HandleKey('a', 0), usedDiag=1;
3861 			else if (keyState[SDLK_DOWN] && keyState[SDLK_RIGHT]) HandleKey('d', 0), usedDiag=1;
3862 			else if (keyState[SDLK_UP] && !usedDiag) HandleKey('w', 0);
3863 			else if (keyState[SDLK_DOWN] && !usedDiag) HandleKey('s', 0);
3864 
3865 			else usedDiag = 0;
3866 		}
3867 	}
KeyReleasedHexPuzzle3868 	void KeyReleased(int key)
3869 	{
3870 		keyState[key] = 0;
3871 	}
KeyPressedHexPuzzle3872 	bool KeyPressed(int key, int mod)
3873 	{
3874 		keyState[key] = 2;
3875 
3876 		if (activeMenu)
3877 		{
3878 			bool eat = activeMenu->KeyPressed(key, mod);
3879 			if (!activeMenu)
3880 				memset(keyState, 0, sizeof(keyState));
3881 			return eat;
3882 		}
3883 		else
3884 		{
3885 			if ((key==SDLK_ESCAPE && (mod & KMOD_CTRL)))
3886 			{
3887 				if (mod & KMOD_SHIFT)
3888 				{
3889 					time = 0;
3890 					renderer.Reset();
3891 					LoadSaveProgress(false);
3892 				}
3893 
3894 				LoadMap();
3895 			}
3896 
3897 			if (isFadeRendering)
3898 				return false;
3899 
3900 			return HandleKey(key, mod);
3901 		}
3902 	}
HandleKeyHexPuzzle3903 	bool HandleKey(int key, int mod)
3904 	{
3905 		turboAnim = 0;
3906 
3907 #ifdef CHEAT
3908 		if (isMap && key=='r' && (mod & KMOD_ALT))
3909 		{
3910 			progress.Clear();
3911 			LoadMap();
3912 		}
3913 #endif
3914 
3915 		if (0) {}
3916 
3917 		else if ((key=='p' && !editMode) || key==SDLK_PAUSE || key==SDLK_ESCAPE)
3918 		{
3919 			noMouse = 1;
3920 			new PauseMenu(isMap, progress.GetLevel(STARTING_LEVEL, true)->Completed(), progress.general.endSequence>=1, progress.general.endSequence>=2);
3921 		}
3922 
3923 #ifdef EDIT
3924 		else if (key=='e' && (mod & KMOD_ALT))
3925 			editMode = !editMode;
3926 
3927 		else if (key=='p' && (mod & KMOD_ALT) && numUndo>0
3928 		      || key>='0' && key<='9' && (mod & KMOD_SHIFT) && !isMap)
3929 		{
3930 			if (key>='0' && key<='9')
3931 				levelDiff = (key=='0') ? 10 : key-'0';
3932 
3933 			if (key=='p' && levelPar==0)
3934 				levelPar = player_score;
3935 
3936 			if (numUndo)
3937 			{
3938 				do
3939 					undo[numUndo-1].Restore(this);
3940 				while (--numUndo);
3941 			}
3942 			time = 0;
3943 			if (LoadSave(currentFile, true))
3944 			{
3945 				if (key>='0' && key<='9')
3946 					LoadMap();
3947 			}
3948 		}
3949 #endif
3950 
3951 		/////////////////////////////////////////////////////////////////////////
3952 		if (isMap && !editMode)
3953 			return false;
3954 
3955 		else if (key==SDLK_KP9 || key=='e') Input(1), noMouse=1;
3956 		else if (key==SDLK_KP3 || key=='d') Input(2), noMouse=1;
3957 		else if (key==SDLK_KP1 || key=='a') Input(4), noMouse=1;
3958 		else if (key==SDLK_KP7 || key=='q') Input(5), noMouse=1;
3959 		else if (key==SDLK_KP8 || key=='w') Input(0), noMouse=1;
3960 		else if (key==SDLK_KP2 || (key=='s' && (((mod & (KMOD_CTRL|KMOD_ALT))==0)||!editMode))) Input(3), noMouse=1;
3961 		else if (key==SDLK_KP5 || key==SDLK_SPACE || key==SDLK_RETURN || key==SDLK_KP_ENTER)
3962 		{
3963 			noMouse=1;
3964 			if (win && winFinal)
3965 				LoadMap(), memset(keyState, 0, sizeof(keyState));
3966 			else
3967 				Input(-1);
3968 		}
3969 
3970 		else if (key=='r' && (mod & KMOD_CTRL))
3971 			LoadSave(currentFile, false);
3972 
3973 #ifdef EDIT
3974 		else if (key=='z' && (mod & KMOD_ALT))
3975 		{
3976 			if (numUndo>0 && !isMap)
3977 			{
3978 				time = undo[numUndo-1].endTime;
3979 				undoTime = undo[0].time;
3980 
3981 				do
3982 					undo[numUndo-1].Restore(this);
3983 				while (--numUndo);
3984 			}
3985 		}
3986 #endif
3987 		else if (key=='z' || key==SDLK_BACKSPACE || key==SDLK_DELETE || key=='u')
3988 		{
3989 			if (!isMap)
3990 				Undo();
3991 		}
3992 
3993 #ifdef EDIT
3994 		else if (key=='s' && (mod & KMOD_ALT)){
3995 			if (win && strlen(currentFile)>0 && !isMap)
3996 			{
3997 				char tmp[1000];
3998 				strcpy(tmp, currentFile);
3999 				ChangeSuffix(tmp, "sol");
4000 				FILE* f = file_open(tmp, "wb");
4001 				if (f)
4002 				{
4003 					for (int i=0; i<numUndo; i++)
4004 					{
4005 						fputc(undo[i].playerMovement, f);
4006 					}
4007 					fclose(f);
4008 				}
4009 			}
4010 		}
4011 #endif
4012 
4013 #ifdef CHEAT
4014 		else if (key=='/' && (mod & KMOD_ALT)){
4015 			turboAnim = 1;
4016 			if (!isMap)
4017 			{
4018 				while (numUndo)
4019 					Undo();
4020 				ResetLevel();
4021 
4022 				if (mod & KMOD_SHIFT)
4023 				{
4024 					LevelSave* l = progress.GetLevel(currentFile, false);
4025 					if (l && l->Completed())
4026 					{
4027 						for (int i=0; i<l->bestSolutionLength; i++)
4028 							Input(l->bestSolution[i]);
4029 						time = 0;
4030 					}
4031 					if (!win && l)
4032 						l->Clear();
4033 				}
4034 				else
4035 				{
4036 					char tmp[1000];
4037 					strcpy(tmp, currentFile);
4038 					ChangeSuffix(tmp, "sol");
4039 					FILE* f = file_open(tmp, "rb");
4040 					if (f)
4041 					{
4042 						int dir;
4043 						while ((dir = fgetc(f)) != -1)
4044 						{
4045 							if (dir==0xff)
4046 								dir = -1;
4047 							Input(dir);
4048 						}
4049 						time = 0;
4050 						fclose(f);
4051 
4052 						if (!win)
4053 							remove(tmp);
4054 					}
4055 				}
4056 			}
4057 		}
4058 #endif
4059 
4060 #ifdef EDIT
4061 		else if (!editMode)
4062 			return false;
4063 
4064 		else if (key>='0' && key<='9' && (mod & KMOD_ALT) && !isMap)
4065 			levelPar = levelPar*10 + key-'0';
4066 		else if (key==SDLK_BACKSPACE  && (mod & KMOD_ALT) && !isMap)
4067 			levelPar /= 10;
4068 
4069 		else if (key=='i')
4070 			Mouse(mousex, mousey, 0, 0, 32, 0, mouse_buttons);
4071 		else if (key=='p' && !(mod & KMOD_ALT))
4072 			Mouse(mousex, mousey, 0, 0, 64, 0, mouse_buttons);
4073 		else if (key=='x')
4074 			Mouse(mousex, mousey, 0, 0, 128, 0, mouse_buttons);
4075 		else if (key==SDLK_RETURN)
4076 			Mouse(mousex, mousey, 0, 0, 256, 0, mouse_buttons);
4077 		else if (key==SDLK_BACKSPACE)
4078 			Mouse(mousex, mousey, 0, 0, 512, 0, mouse_buttons);
4079 		else if (key=='c')
4080 			Mouse(mousex, mousey, 0, 0, 1024, 0, mouse_buttons);
4081 
4082 		else if (key=='s' && (mod & KMOD_CTRL)){
4083 			char *fn = LoadSaveDialog(true, true, _("Save level"));
4084 			LoadSave(fn, true);
4085 			SDL_WM_SetCaption(currentFile, NULL);
4086 		}
4087 
4088 		else if (key=='o' && (mod & KMOD_CTRL)){
4089 			char* fn = LoadSaveDialog(false, true, _("Open level"));
4090 			LoadSave(fn, false);
4091 			SDL_WM_SetCaption(currentFile, NULL);
4092 		}
4093 #endif
4094 
4095 		else
4096 			return false;
4097 
4098 		return true;
4099 	}
LoadGraphicsHexPuzzle4100 	void LoadGraphics()
4101 	{
4102 		#define X(NAME,FILE,ALPHA) NAME = Load(String(FILE) + BMP_SUFFIX, ALPHA);
4103 		#include "gfx_list.h"
4104 
4105 		static int first = 1;
4106 		if (first)
4107 		{
4108 			first = false;
4109 			MakeTileInfo();
4110 		}
4111 
4112 	//	unsigned int d = {
4113 
4114 	//	};
4115 	//	static SDL_Cursor * c = SDL_CreateCursor(data, mask, 32, 32, 1, 1);
4116 	//	SDL_SetCursor(c);
4117 		SDL_ShowCursor(1);
4118 	}
FreeGraphicsHexPuzzle4119 	void FreeGraphics()
4120 	{
4121 		#define X(NAME,FILE,ALPHA) if (NAME) SDL_FreeSurface(NAME), NAME=0;
4122 		#include "gfx_list.h"
4123 	}
ScreenModeChangedHexPuzzle4124 	virtual void ScreenModeChanged()
4125 	{
4126 //		FreeGraphics();
4127 //		LoadGraphics();
4128 	}
4129 };
4130 
4131 MAKE_STATE(HexPuzzle, SDLK_F1, false);
4132 
4133 char * HexPuzzle::loadPtr = 0;
4134 char * HexPuzzle::endLoad = 0;
4135 
4136 #endif //USE_OPENGL
4137