convert \r\n to \n
[hex-a-hop.git] / hex_puzzzle.cpp
blob3f341c6003f08523265e51f26290c0a8925f2326
1 /*
2 Copyright (C) 2005-2007 Tom Beaumont
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.
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.
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
19 #include "i18n.h"
20 #include <string>
21 #include <iostream>
22 #include <cctype> // TODO: remove it later
23 #include <errno.h>
24 #include <iconv.h>
26 //////////////////////////////////////////////////////
27 // Config
30 #ifdef _DEBUG
31 #define EDIT
32 #endif
34 //#define MAP_LOCKED_VISIBLE
36 #ifndef GAME_NAME
37 #define GAME_NAME "Hex-a-hop"
38 #endif
40 #ifndef DATA_DIR
41 #define DATA_DIR "."
42 #endif
44 #ifdef EDIT
45 // #define MAP_EDIT_HACKS
46 #define MAP_EDIT_HACKS_DISPLAY_UNLOCK 0
47 #define CHEAT
48 #define BMP_SUFFIX ".bmp"
49 #else
50 #define USE_LEVEL_PACKFILE
51 #define BMP_SUFFIX ".dat"
52 #endif
56 #ifdef EDIT
57 #define GAMENAME GAME_NAME " (EDIT MODE)"
58 #endif
59 #ifndef GAMENAME
60 #define GAMENAME GAME_NAME
61 #endif
63 #define IMAGE_DAT_OR_MASK 0xff030303 // Reduce colour depth of images slightly for better compression (and remove useless top 8 bits!)
64 #define STARTING_LEVEL "Levels\\0_green\\triangular.lev"
65 #define UNLOCK_SCORING 75
66 const char * mapname = "Levels\\map_maybe\\map.lev";
68 //////////////////////////////////////////////////////
72 #ifndef USE_OPENGL
74 #include "state.h"
76 #include "tiletypes.h"
78 #ifdef USE_LEVEL_PACKFILE
79 #include "packfile.h"
80 #endif
82 #include <unistd.h>
83 #include <limits.h>
84 #include <sys/stat.h>
85 #include <sys/types.h>
87 #ifndef PATH_MAX
88 #define PATH_MAX 4096
89 #endif
91 void RenderTile(bool reflect, int t, int x, int y, int cliplift=-1);
93 int keyState[SDLK_LAST] = {0};
95 FILE *file_open( const char *file, const char *flags )
97 // printf("file_open( \"%s\", \"%s\" )\n", file, flags );
98 extern String base_path;
99 static String filename; // static to reduce memory alloc/free calls.
100 if (file[0]=='/') //If a full path is specified, don't prepend base_path
101 filename = "";
102 else
104 if (strncmp(file, "save", 4) == 0)
106 const char *home = getenv("HOME");
107 if (home)
109 char save_path[PATH_MAX];
110 snprintf(save_path, sizeof(save_path), "%s/.hex-a-hop", home);
111 if (!strchr(flags, 'r'))
112 if (mkdir(save_path, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) != -1)
113 printf("Creating directory \"%s\"\n", (const char *)save_path);
114 strncat(save_path, "/", sizeof(save_path));
115 filename = save_path;
117 else filename = "/tmp/";
119 else filename = base_path;
121 filename += file;
122 // printf(" -> \"%s\"\n", filename );
124 filename.fix_backslashes();
125 FILE* f = fopen( filename, flags );
127 if (!f && strncmp(file, "save", 4) != 0)
129 printf("Warning: unable to open file \"%s\" for %s\n", (const char*)filename, strchr(flags, 'r') ? "reading" : "writing");
132 return f;
136 #ifdef MAP_EDIT_HACKS
137 static const short value_order[]={
138 //WALL,
139 //COLLAPSE_DOOR2,
140 //COLLAPSABLE3
141 //SWITCH
142 //EMPTY, NORMAL,
144 COLLAPSABLE,
145 TRAMPOLINE,
146 COLLAPSE_DOOR, COLLAPSABLE2,
147 GUN,
148 FLOATING_BALL,
149 SPINNER,
150 TRAP,
151 0x100,
152 LIFT_DOWN, LIFT_UP,
153 BUILDER,
154 0x200,
156 #endif
158 //#define PROGRESS_FILE "progress.dat"
160 #define PI (3.1415926535897931)
161 #define PI2 (PI*2)
162 #define MAX(a,b) ((a)>(b) ? (a) : (b))
163 #define MIN(a,b) ((a)<(b) ? (a) : (b))
164 #define ABS(a) ((a)<0 ? -(a) : (a))
166 #define WATER_COLOUR 31 | (IMAGE_DAT_OR_MASK>>16)&255, 37 | (IMAGE_DAT_OR_MASK>>8)&255, 135 | (IMAGE_DAT_OR_MASK>>0)&255
168 #define ROTATION_TIME 0.25
169 #define BUILD_TIME 1
170 #define LASER_LINE_TIME 0.7
171 #define LASER_FADE_TIME 0.1
172 #define LASER_SEGMENT_TIME 0.01
173 #define LIFT_TIME 0.5
174 #define JUMP_TIME 0.4
176 #define X(NAME,FILE,ALPHA) SDL_Surface* NAME = 0;
177 #include "gfx_list.h"
178 int scrollX=0, scrollY=0, initScrollX=0, initScrollY=0;
179 int mapRightBound = 0;
180 int mapScrollX = 0;
181 bool showScoring = false;
182 bool hintsDone = false;
184 enum {
185 TILE_SPLASH_1 = 17,
186 TILE_SPLASH_2,
187 TILE_SPLASH_3,
189 TILE_SPHERE = 20,
190 TILE_SPHERE_OPEN,
191 TILE_SPHERE_DONE,
192 TILE_SPHERE_PERFECT,
193 TILE_LOCK,
195 TILE_LIFT_BACK,
196 TILE_LIFT_FRONT,
197 TILE_LIFT_SHAFT,
198 TILE_BLUE_FRONT,
199 TILE_GREEN_FRONT,
201 TILE_LINK_0 = 30,
202 TILE_LINK_1,
203 TILE_LINK_2,
204 TILE_LINK_3,
205 TILE_LINK_4,
206 TILE_LINK_5,
207 TILE_GREEN_FRAGMENT,
208 TILE_GREEN_FRAGMENT_1,
209 TILE_GREEN_FRAGMENT_2,
210 TILE_ITEM2,
212 TILE_WATER_MAP = 40,
213 TILE_GREEN_CRACKED,
214 TILE_GREEN_CRACKED_WALL,
215 TILE_BLUE_CRACKED,
216 TILE_BLUE_CRACKED_WALL,
217 TILE_LASER_HEAD,
218 TILE_FIRE_PARTICLE_1,
219 TILE_FIRE_PARTICLE_2,
220 TILE_WATER_PARTICLE,
222 TILE_LASER_0 = 50,
223 TILE_LASER_FADE_0 = 53,
224 TILE_BLUE_FRAGMENT = 56,
225 TILE_BLUE_FRAGMENT_1,
226 TILE_BLUE_FRAGMENT_2,
227 TILE_ITEM1,
228 TILE_LASER_REFRACT = 60,
229 TILE_ICE_LASER_REFRACT = TILE_LASER_REFRACT+6,
230 TILE_WHITE_TILE,
231 TILE_WHITE_WALL,
232 TILE_BLACK_TILE,
236 const int colours[] = {
237 #define X(n,col, solid) col,
238 #include "tiletypes.h"
241 const int tileSolid[] = {
242 #define X(n,col, solid) solid,
243 #include "tiletypes.h"
246 void ChangeSuffix(char* filename, char* newsuffix)
248 int len = strlen(filename);
249 int i = len-1;
250 while (i>=0 && filename[i]!='\\' && filename[i]!='.' && filename[i]!='/')
251 i--;
252 if (filename[i]=='.')
253 strcpy(filename+i+1, newsuffix);
254 else
256 strcat(filename, ".");
257 strcat(filename, newsuffix);
261 bool isMap=false, isRenderMap=false;
262 int isFadeRendering=0;
265 |--| |--| TILE_W1
266 |--------| TILE_W2
267 |-----| TILE_WL
268 |-----------| TILE_W3
270 *-----* - -
271 / \ |TILE_H1 |TILE_H2
272 / \ | |
273 * * - |
274 \ / |
275 \ / |
276 *-----* -
278 WL = sqrt(h1*h1 + w1*w1)
279 wl**2 = h1**2 + w1**2
281 w1 = sin60.wL
285 #if 1
286 #define TILE_W1 18
287 #define TILE_W3 64
288 #define GFX_SIZE TILE_W3
289 #define TILE_W2 (TILE_W3-TILE_W1)
290 #define TILE_H1 TILE_W1
291 #define TILE_HUP 22 //extra visible height of wall (used for determining whether a wall was clicked on)
292 #define TILE_H2 (TILE_H1*2)
293 #define TILE_WL (TILE_W2-TILE_W1)
294 #define TILE_H_LIFT_UP 26
295 #define TILE_H_REFLECT_OFFSET 24
296 #define TILE_HUP2 TILE_H_LIFT_UP // Displacement of object on top of wall
297 #define FONT_SPACING 25
298 #define FONT_X_SPACING (-1) // -1 in order to try and overlap the black borders of adjacent characters
299 #else
300 #define TILE_WL 30
301 #define TILE_W1 (TILE_WL/2)
302 #define TILE_W2 (TILE_W1+TILE_WL)
303 #define TILE_W3 (TILE_W1+TILE_W2)
304 #define TILE_H1 (TILE_WL*0.8660254037844386)
305 #define TILE_H2 (TILE_H1*2)
306 #endif
308 #define MAX_DIR 6
310 SDL_Rect tile[2][70];
311 short tileOffset[2][70][2];
312 int Peek(SDL_Surface* i, int x, int y)
314 if (x<0 || y<0 || x>=i->w || y>=i->h)
315 return 0;
316 unsigned int p=0;
317 const int BytesPerPixel = i->format->BytesPerPixel;
318 const int BitsPerPixel = i->format->BitsPerPixel;
319 if (BitsPerPixel==8)
320 p = ((unsigned char*)i->pixels)[i->pitch*y + x*BytesPerPixel];
321 else if (BitsPerPixel==15 || BitsPerPixel==16)
322 p = *(short*)(((char*)i->pixels) + (i->pitch*y + x*BytesPerPixel));
323 else if (BitsPerPixel==32)
324 p = *(unsigned int*)(((char*)i->pixels) + (i->pitch*y + x*BytesPerPixel));
325 else if (BitsPerPixel==24)
326 p = (int)((unsigned char*)i->pixels)[i->pitch*y + x*BytesPerPixel]
327 | (int)((unsigned char*)i->pixels)[i->pitch*y + x*BytesPerPixel] << 8
328 | (int)((unsigned char*)i->pixels)[i->pitch*y + x*BytesPerPixel] << 16;
330 return p;
332 bool IsEmpty(SDL_Surface* im, int x, int y, int w, int h)
334 for (int i=x; i<x+w; i++)
335 for (int j=y; j<y+h; j++)
336 if (Peek(im,i,j) != Peek(im,0,im->h-1))
337 return false;
338 return true;
341 void MakeTileInfo()
343 for (int i=0; i<140; i++)
345 SDL_Rect r = {(i%10)*GFX_SIZE, ((i/10)%7)*GFX_SIZE, GFX_SIZE, GFX_SIZE};
346 short * outOffset = tileOffset[i/70][i%70];
347 SDL_Surface * im = (i/70) ? tileGraphicsR : tileGraphics;
349 outOffset[0] = outOffset[1] = 0;
351 while (r.h>1 && IsEmpty(im, r.x, r.y, r.w, 1)) r.h--, r.y++, outOffset[1]++;
352 while (r.h>1 && IsEmpty(im, r.x, r.y+r.h-1, r.w, 1)) r.h--;
353 while (r.w>1 && IsEmpty(im, r.x, r.y, 1, r.h)) r.w--, r.x++, outOffset[0]++;
354 while (r.w>1 && IsEmpty(im, r.x+r.w-1, r.y, 1, r.h)) r.w--;
356 tile[i/70][i%70] = r;
360 void ConvertToUTF8(const std::string &text_locally_encoded, char *text_utf8, size_t text_utf8_length)
362 // Is this portable?
363 size_t text_length = text_locally_encoded.length()+1;
364 errno = 0;
365 static const char *locale_enc = gettext_init.GetEncoding();
366 iconv_t cd = iconv_open("UTF-8", locale_enc);
367 char *in_buf = const_cast<char *>(&text_locally_encoded[0]);
368 char *out_buf = &text_utf8[0];
369 iconv(cd, &in_buf, &text_length, &out_buf, &text_utf8_length);
370 iconv_close(cd);
371 if (errno != 0)
372 std::cerr << "An error occurred recoding " << text_locally_encoded << " to UTF8" << std::endl;
375 int SDLPangoTextWidth(const std::string &text_utf8);
376 void Print_Pango(int x, int y, const std::string &text_utf8);
377 void Print_Pango_Aligned(int x, int y, int width, const std::string &text_utf8, int align);
379 /// Prints a left aligned string (a single line) beginning at (x,y)
380 // TODO: Check that the maximal text width is already set
381 void Print(int x, int y, const char * string, ...)
383 va_list marker;
384 va_start( marker, string ); /* Initialize variable arguments. */
386 char tmp[1000], tmp_utf8[5000]; // FIXME: Check this limit
387 vsprintf((char*)tmp, string, marker);
389 ConvertToUTF8(tmp, tmp_utf8, sizeof(tmp_utf8)/sizeof(char));
390 Print_Pango(x, y, tmp_utf8);
392 va_end( marker ); /* Reset variable arguments. */
395 /// Prints a string right aligned so that it ends at (x,y)
396 // TODO: Check that the maximal text width is already set
397 void PrintR(int x, int y, const char * string, ...)
399 va_list marker;
400 va_start( marker, string ); /* Initialize variable arguments. */
402 char tmp[1000], tmp_utf8[5000]; // FIXME: Check this limit
403 vsprintf((char*)tmp, string, marker);
405 ConvertToUTF8(tmp, tmp_utf8, sizeof(tmp_utf8)/sizeof(char));
406 Print_Pango(x-SDLPangoTextWidth(tmp_utf8), y, tmp_utf8);
408 va_end( marker ); /* Reset variable arguments. */
411 /** \brief Prints a string horizontally centered around (x,y)
413 * " " in the string is interpreted as linebreak
415 void Print_Aligned(bool split, int x, int y, int width, const char * string, int align)
417 char tmp_utf8[5000]; // FIXME: Check this limit
419 ConvertToUTF8(string, tmp_utf8, sizeof(tmp_utf8)/sizeof(char));
421 std::string msg(tmp_utf8);
422 while (split && msg.find(" ") != std::string::npos)
423 msg.replace(msg.find(" "), 2, "\n");
425 Print_Pango_Aligned(x, y, width, msg, align);
428 void PrintC(bool split, int x, int y, const char * string, ...)
430 va_list marker;
431 va_start( marker, string ); /* Initialize variable arguments. */
433 char tmp[1000]; // FIXME: Check this limit
434 vsprintf((char*)tmp, string, marker);
436 va_end( marker ); /* Reset variable arguments. */
438 static bool print = true; // avoid flickering!
439 if (print) {
440 std::cerr << "Warning: don't know window width for message:\n" << tmp << "\n";
441 for (unsigned int i=0; i<strlen(tmp); ++i)
442 if (!std::isspace(tmp[i]))
443 print = false;
445 Print_Aligned(split, x, y, 2*std::min(x, SCREEN_W-x), tmp, 1);
448 #include "savestate.h"
449 #include "menus.h"
450 #include "level_list.h"
452 void SaveState::GetStuff()
454 general.hintFlags = HintMessage::flags;
456 void SaveState::ApplyStuff()
458 HintMessage::flags = general.hintFlags;
462 // somewhere else Tile map[][] is assigned to an unsigned char not int32_t
463 // but the data file format expects it to be 32 bit wide!??
464 typedef int32_t Tile;
465 typedef int Dir;
466 struct Pos{
467 int32_t x,y;
468 Pos() : x(0), y(0) {}
469 Pos(int a, int b) : x(a), y(b) {}
470 bool operator == (Pos const & p) const
472 return x==p.x && y==p.y;
474 Pos operator + (Dir const d) const
476 return Pos(
477 x + ((d==1 || d==2) ? 1 : (d==4 || d==5) ? -1 : 0),
478 y + ((d==0 || d==1) ? -1 : (d==3 || d==4) ? 1 : 0)
481 int getScreenX() const {
482 return x*TILE_W2;
484 int getScreenY() const {
485 return x*TILE_H1 + y*TILE_H2;
487 static Pos GetFromWorld(double x, double y)
489 x += TILE_W3/2;
490 y += TILE_H1;
491 int tx, ty;
492 tx = (int)floor(x/TILE_W2);
493 y -= tx*TILE_H1;
494 ty = (int)floor(y/TILE_H2);
496 y -= ty * TILE_H2;
497 x -= tx * TILE_W2;
499 if (x < TILE_W1 && y < TILE_H1)
500 if (x*TILE_H1 + y * TILE_W1 < TILE_H1*TILE_W1)
501 tx--;
502 if (x < TILE_W1 && y > TILE_H1)
503 if (x*TILE_H1 + (TILE_H2-y) * TILE_W1 < TILE_H1*TILE_W1)
504 tx--, ty++;
506 return Pos(tx, ty);
509 Pos mousep(0,0), keyboardp(4,20);
511 class RenderObject;
513 struct RenderStage
515 virtual ~RenderStage() {}
516 virtual void Render(RenderObject* r, double time, bool reflect) = 0;
517 virtual int GetDepth(double /*time*/) { return 1; }
520 class RenderObject
522 RenderStage** stage;
523 double* time;
524 int numStages;
525 int maxStages;
526 int currentStage;
527 public:
528 double seed;
529 double currentTime;
530 private:
532 void Reserve()
534 if (maxStages <= numStages)
536 maxStages = maxStages ? maxStages*2 : 4;
537 stage = (RenderStage**) realloc(stage, sizeof(stage[0])*maxStages);
538 time = (double*) realloc(time, sizeof(time[0])*maxStages);
541 public:
542 RenderObject() : stage(0), time(0), numStages(0), maxStages(0), currentStage(0)
544 // TODO: use a random number with better range
545 // or maybe make seed an int or float...
546 seed = rand() / (double)RAND_MAX;
548 ~RenderObject()
550 free(stage); free(time);
552 bool Active(double t)
554 if (numStages==0) return false;
555 if (t < time[0]) return false;
556 return true;
558 void UpdateCurrent(double t)
560 if (currentStage >= numStages) currentStage = numStages-1;
561 if (currentStage < 0) currentStage = 0;
563 while (currentStage>0 && time[currentStage]>t)
564 currentStage--;
565 while (currentStage<numStages-1 && time[currentStage+1]<=t)
566 currentStage++;
568 currentTime = t;
570 RenderStage* GetStage(double t)
572 if (t==-1 && numStages>0)
573 return stage[numStages-1];
575 if (!Active(t)) return 0;
576 UpdateCurrent(t);
577 return stage[currentStage];
579 double GetLastTime()
581 return numStages>0 ? time[numStages-1] : -1;
583 void Render(double t, bool reflect)
585 if (!Active(t))
586 return;
587 UpdateCurrent(t);
588 stage[currentStage]->Render(this, t - time[currentStage], reflect);
590 int GetDepth(double t)
592 if (!Active(t))
593 return -1;
594 UpdateCurrent(t);
595 return stage[currentStage]->GetDepth(t - time[currentStage]);
597 void Reset(double t)
599 if (t<0)
600 numStages = currentStage = 0;
601 else
603 while (numStages > 0 && time[numStages-1] >= t)
604 numStages--;
605 if (currentStage > 0 && currentStage >= numStages)
606 currentStage = numStages - 1;
608 if (currentStage < 0) currentStage = 0;
610 void Wipe()
612 if (currentStage > 0 && numStages > 0)
614 memmove(&time[0], &time[currentStage], sizeof(time[0]) * (numStages-currentStage));
615 memmove(&stage[0], &stage[currentStage], sizeof(stage[0]) * (numStages-currentStage));
616 numStages -= currentStage;
617 currentStage = 0;
620 void Add(RenderStage* s, double t)
622 int i=0;
624 if (currentStage<numStages && time[currentStage]<=t)
625 i = currentStage;
627 while (i<numStages && time[i]<t)
628 i++;
630 if (i<numStages && time[i]==t)
631 stage[i]=s;
632 else
634 Reserve();
636 if (i<numStages)
638 memmove(&time[i+1], &time[i], (numStages-i) * sizeof(time[0]));
639 memmove(&stage[i+1], &stage[i], (numStages-i) * sizeof(stage[0]));
642 numStages++;
643 time[i] = t;
644 stage[i] = s;
649 class WorldRenderer
651 #define SIZE 30
652 #define FX 10
653 RenderObject tile[SIZE][SIZE][2];
654 RenderObject fx[FX];
655 int fxPos;
657 public:
658 RenderObject player;
659 RenderObject dummy;
661 WorldRenderer()
663 Reset();
666 void Reset(double t = -1)
668 fxPos = 0;
669 player.Reset(t);
670 dummy.Reset(-1);
672 for (int i=0; i<SIZE; i++)
673 for (int j=0; j<SIZE; j++)
674 for (int q=0; q<2; q++)
675 tile[i][j][q].Reset(t);
677 for (int j=0; j<FX; j++)
678 fx[j].Reset(t);
681 void Wipe()
683 player.Wipe();
684 dummy.Reset(-1);
686 for (int i=0; i<SIZE; i++)
687 for (int j=0; j<SIZE; j++)
688 for (int q=0; q<2; q++)
689 tile[i][j][q].Wipe();
691 for (int j=0; j<FX; j++)
692 fx[j].Wipe();
695 bool Visible(Pos p)
697 int x0 = (scrollX+TILE_W2) / TILE_W2;
698 int x1 = (scrollX+SCREEN_W+TILE_W3+TILE_W1) / TILE_W2;
699 if (p.x<0 || p.y<0 || p.x>=SIZE || p.y>=SIZE) return false;
700 if (p.x<x0) return false;
701 if (p.x>=x1-1) return false;
702 for (int j0=0; j0<SIZE*3; j0++)
704 if (j0 * TILE_H1 < scrollY-TILE_H1) continue;
705 if (j0 * TILE_H1 > scrollY+SCREEN_H+TILE_H1) break;
706 int i = j0&1;
707 int j = j0>>1;
708 j -= (x0-i)/2;
709 i += (x0-i)/2*2;
710 if (j>=SIZE) i+=(j+1-SIZE)*2, j=SIZE-1;
711 for (; i<x1 && j>=0; i+=2, j--)
713 if (Pos(i,j)==p)
714 return true;
717 return false;
720 void Render(double t, bool reflect)
722 dummy.Reset(-1);
724 int playerDepth = player.GetDepth(t);
725 if (reflect) playerDepth-=4;
726 if (playerDepth<0)
727 player.Render(t, reflect);
729 int x0 = (scrollX+TILE_W2) / TILE_W2;
730 int x1 = (scrollX+SCREEN_W+TILE_W3+TILE_W1) / TILE_W2;
731 x0 = MAX(x0, 0);
732 x1 = MIN(x1, SIZE);
733 for (int j0=0; j0<SIZE*3; j0++)
735 if (j0 * TILE_H1 < scrollY-TILE_H1) continue;
736 if (j0 * TILE_H1 > scrollY+SCREEN_H+TILE_H1) break;
737 int i = j0&1;
738 int j = j0>>1;
739 j -= (x0-i)/2;
740 i += (x0-i)/2*2;
741 if (j>=SIZE) i+=(j+1-SIZE)*2, j=SIZE-1;
742 for (; i<x1 && j>=0; i+=2, j--)
744 for (int q=reflect?1:0; q!=2 && q!=-1; q += (reflect ? -1 : 1))
745 if (tile[i][j][q].Active(t))
747 tile[i][j][q].Render(t, reflect);
751 if (playerDepth==j0 || j0==SIZE*3 && playerDepth>j0)
752 player.Render(t, reflect);
755 for (int j=0; j<FX; j++)
756 if(fx[j].Active(t))
758 fx[j].Render(t, reflect);
762 RenderObject & operator () ()
764 fxPos++;
765 if (fxPos==FX) fxPos = 0;
766 return fx[fxPos];
768 RenderObject & operator () (Pos const & p, bool item=false)
770 if (p.x<0 || p.y<0 || p.x>=SIZE || p.y>=SIZE)
771 return dummy;
772 return tile[p.x][p.y][item ? 1 : 0];
776 void RenderTile(bool reflect, int t, int x, int y, int cliplift)
778 SDL_Rect src = tile[reflect][t];
779 SDL_Rect dst = {x-scrollX-GFX_SIZE/2, y-scrollY-GFX_SIZE+TILE_H1, 0, 0};
780 dst.x += tileOffset[reflect][t][0];
781 dst.y += tileOffset[reflect][t][1];
782 if (reflect)
783 dst.y += TILE_H_REFLECT_OFFSET;
784 if (cliplift==-1 || reflect)
786 // dst.w=src.w; dst.h=src.h;
787 // SDL_FillRect(screen, &dst, rand());
788 SDL_BlitSurface(reflect ? tileGraphicsR : tileGraphics, &src, screen, &dst);
790 else
792 src.h -= cliplift;
793 if (src.h > TILE_W1)
795 src.h -= TILE_W1/2;
796 SDL_BlitSurface(tileGraphics, &src, screen, &dst);
797 src.y += src.h;
798 dst.y += src.h;
799 src.h = TILE_W1/2;
801 if (src.h > 0)
803 src.w -= TILE_W1*2, src.x += TILE_W1;
804 dst.x += TILE_W1;
805 SDL_BlitSurface(tileGraphics, &src, screen, &dst);
809 void RenderGirl(bool reflect, int r, int frame, int x, int y, int h)
811 int sx = r * 64;
812 int sy = frame * 80*2;
813 if (reflect)
814 y += TILE_H_REFLECT_OFFSET+20+h, sy += 80;
815 else
816 y -= h;
817 SDL_Rect src = {sx, sy, 64, 80};
818 SDL_Rect dst = {x-scrollX-32, y-scrollY-65, 0, 0};
819 SDL_BlitSurface(girlGraphics, &src, screen, &dst);
822 struct ItemRender : public RenderStage
824 int item;
825 Pos p;
826 int water;
828 ItemRender(int i2, int _water, Pos const & _p) : item(i2), p(_p), water(_water)
831 double Translate(double seed, double time)
833 double bob = time*2 + seed*PI2;
834 return sin(bob)*4;
837 void Render(RenderObject* r, double time, bool reflect)
839 if (item==0)
840 return;
842 int y = -5 + (int)Translate(r->seed, r->currentTime + time);
843 if (reflect)
844 y=-y;
845 if (!reflect && !water)
846 RenderTile( false, TILE_SPHERE, p.getScreenX(), p.getScreenY());
847 RenderTile(
848 reflect,
849 item==1 ? TILE_ITEM1 : TILE_ITEM2,
850 p.getScreenX(), p.getScreenY()+y
855 void RenderFade(double time, int dir, int seed)
857 int ys=0;
858 srand(seed);
859 for(int x=rand()%22-11; x<SCREEN_W+22; x+=32, ys ^= 1)
861 for (int y=ys*20; y<SCREEN_H+30; y+=40)
863 double a = (rand()&0xff)*dir;
864 double b = (time * 0x400 + (y - SCREEN_H) * 0x140/SCREEN_H)*dir;
865 if (a >= b)
867 RenderTile(false, TILE_BLACK_TILE, x+scrollX, y+scrollY);
873 struct FadeRender : public RenderStage
875 int seed;
876 int dir;
877 FadeRender(int d=-1) : seed(rand()), dir(d)
879 isFadeRendering = d;
882 void Render(RenderObject* /*r*/, double time, bool reflect)
884 if (reflect) return;
885 if (time > 0.5)
887 if (dir==1) dir=0, isFadeRendering=0;
888 return;
890 RenderFade(time, dir, seed);
894 struct ScrollRender : public RenderStage
896 int x,y;
897 bool done;
898 ScrollRender(int a,int b) : x(a), y(b), done(false) {}
900 void Render(RenderObject* /*r*/, double /*time*/, bool /*reflect*/)
902 if (done) return;
903 scrollX = x, scrollY = y;
904 isRenderMap = isMap;
905 done = true;
909 struct LevelSelectRender : public RenderStage
911 Pos p;
912 int item;
913 int adj;
914 #ifdef MAP_EDIT_HACKS
915 int magic;
916 #endif
918 LevelSelectRender(Pos const & _p, int i2, int adj) : p(_p), item(i2), adj(adj)
921 void Render(RenderObject* /*r*/, double /*time*/, bool reflect)
923 if (item==0)
924 return;
926 #ifndef MAP_LOCKED_VISIBLE
927 if (item==1) return;
928 #endif
930 if (!reflect && adj)
931 for (int i=0; i<MAX_DIR; i++)
932 if (adj & (1 << i))
933 RenderTile( false, TILE_LINK_0+i, p.getScreenX(), p.getScreenY());
935 if (item < 0)
936 return;
938 if (!reflect)
940 RenderTile(
941 reflect,
942 TILE_SPHERE + item-1,
943 p.getScreenX(), p.getScreenY()
946 #ifdef MAP_EDIT_HACKS
947 int x = p.getScreenX()-scrollX, y = p.getScreenY()-scrollY;
948 Print(x+5,y-25,"%d",magic);
949 #endif
954 struct ItemCollectRender : public ItemRender
956 ItemCollectRender(int i2, Pos const & p) : ItemRender(i2, 0, p)
959 void Render(RenderObject* /*r*/, double /*time*/, bool /*reflect*/)
964 int GetLiftHeight(double time, int t)
966 if (t==LIFT_UP)
967 time = LIFT_TIME-time;
968 time = time / LIFT_TIME;
969 if (time > 1)
970 time = 1;
971 if (time < 0)
972 time = 0;
973 time = (3 - 2*time)*time*time;
974 if (t==LIFT_UP)
975 time = (3 - 2*time)*time*time;
976 if (t==LIFT_UP)
977 return (int)((TILE_H_LIFT_UP+4) * time);
978 else
979 return (int)((TILE_H_LIFT_UP-4) * time) + 4;
982 struct TileRender : public RenderStage
984 int special;
985 int t;
986 Pos p;
987 double specialDuration;
989 TileRender(int i, Pos const & _p, int _special=0) : special(_special), t(i), p(_p), specialDuration(LASER_LINE_TIME)
992 void Render(RenderObject* r, double time, bool reflect)
994 if (t==0 && special==0)
995 return;
997 if (special && (t==LIFT_UP || t==LIFT_DOWN) && time<LIFT_TIME)
999 int y = GetLiftHeight(time, t);
1000 if (!reflect)
1002 RenderTile(reflect, TILE_LIFT_BACK, p.getScreenX(), p.getScreenY());
1003 RenderTile(reflect, TILE_LIFT_SHAFT, p.getScreenX(), p.getScreenY()+y, y-8);
1004 RenderTile(reflect, TILE_LIFT_FRONT, p.getScreenX(), p.getScreenY());
1006 else
1008 RenderTile(reflect, TILE_LIFT_SHAFT, p.getScreenX(), p.getScreenY()-y, y);
1009 RenderTile(reflect, LIFT_DOWN, p.getScreenX(), p.getScreenY());
1012 else if (special && (t==EMPTY || t==TRAP) && !reflect && time < specialDuration)
1014 if (t == TRAP)
1015 if (time < specialDuration-LASER_FADE_TIME)
1016 RenderTile(reflect, TILE_ICE_LASER_REFRACT, p.getScreenX(), p.getScreenY());
1017 else
1018 RenderTile(reflect, t, p.getScreenX(), p.getScreenY());
1019 int base = ((t==EMPTY) ? TILE_LASER_0 : TILE_LASER_REFRACT);
1020 if (t==EMPTY && time >= specialDuration-LASER_FADE_TIME)
1021 base = TILE_LASER_FADE_0;
1023 int foo=special;
1024 for(int i=0; foo; foo>>=1, i++)
1025 if (foo & 1)
1026 RenderTile(reflect, base+i, p.getScreenX(), p.getScreenY());
1028 else if (t==FLOATING_BALL)
1030 int y = int(1.8 * sin(r->seed*PI + time*4));
1031 if (special==512)
1033 if (time > 2) return;
1034 if (reflect) return;
1035 srand(int(r->seed * 0xfff));
1036 for (int i=0; i<20 - int(time*10); i++)
1038 int x = int((((rand() & 0xfff) - 0x800) / 10) * time);
1039 int y = int((((rand() & 0xfff) - 0x800) / 10) * time);
1040 RenderTile(true, 19 + ((i+int(time*5))&1)*10, p.getScreenX() + x, p.getScreenY() - 14 + y);
1043 if (time < 0.05)
1044 RenderTile(true, 18, p.getScreenX(), p.getScreenY() - 14);
1046 else if (special)
1047 RenderBoat(reflect, int(special)&255, p.getScreenX(), p.getScreenY(), y);
1048 else
1049 RenderTile(reflect, t, p.getScreenX(), p.getScreenY() + (reflect ? -y : y));
1051 else if (t != EMPTY)
1052 RenderTile(reflect, t, p.getScreenX(), p.getScreenY());
1054 static void RenderBoat(bool reflect, int d, int x, int y, int yo)
1056 if (reflect)
1057 RenderGirl(reflect, d, 0, x, y, -yo);
1058 RenderTile(reflect, FLOATING_BALL, x, y+yo);
1059 if (!reflect)
1061 RenderGirl(reflect, d, 0, x, y, -yo);
1062 RenderTile(true, 17, x, y+yo-TILE_H_REFLECT_OFFSET);
1067 struct TileRotateRender : public TileRender
1069 Dir d;
1070 // int range;
1071 int mode;
1072 TileRotateRender(int i, Pos const & p, Dir _d, int m) : TileRender(i, p), d(_d), mode(m)
1074 void Render(RenderObject* r, double time, bool reflect)
1076 if (t==0)
1077 return;
1078 double f = time / ROTATION_TIME;
1080 if (mode & 1) f += 0.5;
1081 if (f<1 && f>0)
1083 if (mode & 2)
1085 else
1086 f = (3-2*f)*f*f;
1089 if (mode & 1) f=1-f; else f=f;
1090 if (f<0) f=0;
1092 if (f >= 1)
1093 TileRender::Render(r, time, reflect);
1094 else
1096 Pos dd = (Pos(0,0)+d);
1097 int x = p.getScreenX() + int(dd.getScreenX()*(f));
1098 int y = p.getScreenY() + int(dd.getScreenY()*(f));
1100 if (mode & 2)
1101 RenderBoat(reflect, (mode&1) ? (d+MAX_DIR/2)%MAX_DIR : d, x, y, 2);
1102 else
1103 RenderTile(reflect, t, x, y);
1108 struct LaserRender : public RenderStage
1110 Pos p;
1111 Dir d;
1112 int range;
1114 LaserRender(Pos _p, int dir, int r) : p(_p), d(dir), range(r)
1117 void Render(RenderObject* /*r*/, double /*time*/)
1122 struct ExplosionRender : public RenderStage
1124 Pos p;
1125 int seed;
1126 int power;
1127 int type;
1129 ExplosionRender(Pos _p, int _pow=0, int t=0) : p(_p), power(_pow), type(t)
1131 seed = rand();
1134 virtual int GetDepth(double /*time*/)
1136 return p.x + p.y*2;
1139 void Render(RenderObject* /*r*/, double time, bool reflect)
1141 if (type==1 && time > 2.5)
1142 type = -1, new WinLoseScreen(false);
1144 // if (reflect) return;
1145 if (time > 3) return;
1146 srand(seed);
1147 int q = 50 - int(time * 35);
1148 if (power) q*=2;
1149 if (type) q = 50;
1150 for (int i=0; i<q; i++)
1152 int x = p.getScreenX();
1153 int y = p.getScreenY() + (rand() & 31)-16;
1154 int xs = ((rand() & 63) - 32);
1155 int ys = (-10 - (rand() & 127)) * (1+power);
1156 if (type) ys*=2, xs/=2;
1157 x += int(xs * (1+time*(2+power)));
1158 int yo = int(time*time*128 + ys*time);
1159 //if (yo > 0) yo=-yo;//continue;
1160 if (type)
1163 if (yo > 0)
1165 if (!reflect && ys<-60)
1167 const double T = 0.06;
1168 double ct = -ys / 128.0;
1169 if (time < ct+T*4)
1171 x = p.getScreenX() + int(xs * (1+ct*(2+power)));
1172 RenderTile(
1173 reflect,
1174 time > ct+3*T ? TILE_SPLASH_3 : time > ct+2*T ? TILE_SPLASH_2 : time > ct+T ? TILE_SPLASH_1 : TILE_WATER_PARTICLE+1,
1175 x, y);
1179 else
1180 RenderTile(
1181 reflect,
1182 time - i*0.003 < 0.2 ? TILE_WATER_PARTICLE+1 : TILE_WATER_PARTICLE,
1183 x, y+(reflect?-1:1)*yo);
1185 else
1187 if (yo > 0)
1189 else
1190 RenderTile(
1191 reflect,
1192 i<q-20 || time<0.3 ? TILE_LASER_HEAD : i<q-10 || time<0.6 ? TILE_FIRE_PARTICLE_1 : TILE_FIRE_PARTICLE_2,
1193 x, y+(reflect?-1:1)*yo);
1198 struct DisintegrateRender : public RenderStage
1200 Pos p;
1201 int seed;
1202 int height;
1203 int type;
1205 DisintegrateRender(Pos _p, int _pow=0, int _t=0) : p(_p), height(_pow), type(_t)
1207 seed = rand();
1210 void Render(RenderObject* /*r*/, double time, bool reflect)
1212 if (type)
1213 RenderTile(reflect, height ? COLLAPSE_DOOR : COLLAPSABLE, p.getScreenX(), p.getScreenY());
1215 if (time > 50.0/70.0) return;
1216 if (reflect) return;
1217 srand(seed);
1218 int q = 50 - int(time * 70);
1219 if (height) q*=2;
1220 for (int i=0; i<q; i++)
1222 int x = (rand() % (TILE_W3-8))-TILE_W3/2+4;
1223 int y = (rand() % (TILE_H2-8))-TILE_H1+4;
1224 if (x<-TILE_WL/2 && ABS(y)<-TILE_WL/2-x) continue;
1225 if (x>TILE_WL/2 && ABS(y)>x-TILE_WL/2) continue;
1226 int yo=0;
1227 if (height) yo -= rand() % TILE_HUP;
1228 x += p.getScreenX();
1229 y += p.getScreenY() + 4;
1230 int xs = 0;//((rand() & 63) - 32);
1231 int ys = (- (rand() & 31));
1232 x += int(xs * (1+time*(2)));
1233 if (type) yo = -yo;
1234 yo += int(time*time*128 + ys*time);
1235 if (type) yo = -yo*2;
1236 //if (yo > 0) yo=-yo;//continue;
1237 int t = type ? TILE_BLUE_FRAGMENT : TILE_GREEN_FRAGMENT;
1238 if (i>q-20) t++;
1239 if (i>q-10) t++;
1240 if (yo > 5) yo = 5;
1241 RenderTile(false, t, x, y+(reflect?-yo:yo));
1245 struct BuildRender : public RenderStage
1247 Pos p;
1248 Dir dir;
1249 int reverse;
1250 int height;
1251 int type;
1253 BuildRender(Pos _p, Dir _d, int _h, int _r=0, int _type=0) : p(_p), dir(_d), reverse(_r), height(_h), type(_type)
1257 void Render(RenderObject* /*r*/, double time, bool reflect)
1259 if (time >= BUILD_TIME)
1260 RenderTile(reflect, height ^ reverse ? (type ? COLLAPSE_DOOR2 : COLLAPSE_DOOR) : (type ? COLLAPSABLE2 : COLLAPSABLE), p.getScreenX(), p.getScreenY());
1261 else
1263 if (height)
1264 RenderTile(reflect, type ? COLLAPSABLE2 : COLLAPSABLE, p.getScreenX(), p.getScreenY());
1266 double dist = time * 2 / BUILD_TIME;
1267 if (dir>-1)
1269 Pos from = p + ((dir+MAX_DIR/2)%MAX_DIR);
1270 if (dist <= 1)
1271 //if (dist > 1)
1273 double offset = (dist*0.7) + 0.3;
1274 int x = from.getScreenX() + int((p.getScreenX()-from.getScreenX()) * offset);
1275 int y = from.getScreenY() + int((p.getScreenY()-from.getScreenY()) * offset - dist*(1-dist)*(TILE_HUP*4));
1276 RenderTile(reflect, TILE_GREEN_FRAGMENT, x, y);
1278 dist -= 1;
1280 else
1282 if (reverse) dist = 1-dist;
1284 if (dist > 0 && !height)
1286 if (!reflect)
1287 for (int i=0; i<=int(dist*15); i++)
1289 int x = p.getScreenX(), y = p.getScreenY();
1290 double d = (i + fmod(dist*15, 1))/10.0;
1291 int x1 = int(sin(d*5+time)*MIN(d,1)*TILE_W2/2);
1292 int y1 = int(cos(d*5+time)*MIN(d,1)*TILE_H1*0.7);
1293 RenderTile(reflect, TILE_GREEN_FRAGMENT, x+x1, y+y1+4);
1294 RenderTile(reflect, TILE_GREEN_FRAGMENT, x-x1, y-y1+4);
1297 if (dist > 0 && height)
1299 int yo = int((1-dist)*(TILE_HUP*1.3));
1300 if (yo > TILE_HUP*1.1)
1301 RenderTile(reflect, TILE_WHITE_TILE, p.getScreenX(), p.getScreenY());
1302 else if (!reflect)
1304 RenderTile(reflect, type ? COLLAPSABLE2 : COLLAPSABLE, p.getScreenX(), p.getScreenY());
1305 RenderTile(reflect, type ? COLLAPSE_DOOR2 : COLLAPSE_DOOR, p.getScreenX(), p.getScreenY()+(reflect ? -yo : yo), yo+6);
1306 RenderTile(reflect, type ? TILE_BLUE_FRONT : TILE_GREEN_FRONT, p.getScreenX(), p.getScreenY());
1308 else
1310 if (yo < TILE_HUP/2)
1312 RenderTile(reflect, type ? COLLAPSE_DOOR2 : COLLAPSE_DOOR, p.getScreenX(), p.getScreenY()+(reflect ? -yo : yo), yo);
1315 RenderTile(reflect, type ? COLLAPSABLE2 : COLLAPSABLE, p.getScreenX(), p.getScreenY());
1323 struct PlayerRender : public RenderStage
1325 Pos p;
1326 Pos target;
1327 int p_h, target_h;
1328 int r;
1329 int type;
1330 double speed;
1331 bool dead;
1333 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)
1335 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)
1337 int dist = MAX(ABS(p.x-target.x), ABS(p.y-target.y));
1338 if (dist > 1)
1339 speed *= 1.5;
1340 if(dist==0)
1341 speed = 0;
1344 virtual int GetDepth(double time)
1346 double f = speed ? time / speed : 1;
1347 if (f>1) f=1;
1348 if (f==1) dead = this->dead;
1350 if (f==1 || f>0.5 && p_h>target_h)
1351 return target.x+target.y*2;
1352 return MAX(target.x+target.y*2 , p.x+p.y*2);
1355 void Render(RenderObject* /*ro*/, double time, bool reflect)
1357 bool dead = false;
1358 double f = speed ? time / speed : 1;
1359 if (f>1) f=1;
1360 if (f==1) dead = this->dead;
1362 int x = p.getScreenX();
1363 int y = p.getScreenY();
1364 int x2 = target.getScreenX();
1365 int y2 = target.getScreenY();
1366 int h = 0;
1367 int shadow_h = (int)((p_h+(target_h-p_h)*f)*TILE_HUP2);
1369 if (x==x2 && y==y2 && p_h!=target_h)
1371 h = TILE_H_LIFT_UP - GetLiftHeight(time, p_h ? LIFT_DOWN : LIFT_UP);
1373 else
1376 int dist = MAX(ABS(p.x-target.x), ABS(p.y-target.y));
1377 int arc = dist*dist;
1378 int h1 = p_h * TILE_HUP2;;
1379 int h2 = target_h * TILE_HUP2;
1380 if (dist==2 && h1!=0)
1382 arc += h2 ? 1 : 3;
1383 h1 = 0;
1384 shadow_h = f>=0.7 ? int(shadow_h*(f-0.7)/0.3) : 0;
1386 if (dist==0)
1387 arc = speed > JUMP_TIME ? 7 : 2;
1389 h = (int)(h1+(h2-h1)*f);
1390 // if (x==x2 && y==y2)
1391 // ;
1392 // else
1394 //h += int(TILE_H_LIFT_UP/3 * (1-f));
1395 h += (int)(f*(1-f)*TILE_HUP2*arc);
1398 if (type==2)
1399 h=0;
1402 if (!dead)
1404 int frame = 0;
1405 if (type==2 && f<1)
1407 //frame = ((int)(f*4) % 4);
1408 //if (frame==2) frame=0; else if (frame==3) frame=2;
1409 frame = 0;
1411 else if (f==1 || x==x2 && y==y2) // stationary
1412 frame = 0;
1413 else if (f > 0.7)
1414 frame = 0;
1415 else
1417 frame = type ? 2 : 1;
1418 if (f<0.1 || f>0.6)
1419 frame += 2;
1422 if (!reflect)
1423 RenderTile( false, TILE_SPHERE,
1424 (int)(x+(x2-x)*f),
1425 (int)(y+(y2-y)*f) - shadow_h
1428 RenderGirl(
1429 reflect,
1430 r, frame,
1431 (int)(x+(x2-x)*f),
1432 (int)(y+(y2-y)*f),
1437 /* RenderTile(
1438 dead ? TILE_SPHERE_OPEN : TILE_SPHERE_DONE,
1439 (int)(x+(x2-x)*f),
1440 (int)(y+(y2-y)*f),
1441 true
1442 );*/
1447 struct HexPuzzle : public State
1449 struct Undo
1451 #define MAX_TILECHANGE 64 // TODO: don't have a magic upper limit
1452 struct TileChange
1454 Pos p;
1455 Tile t;
1456 int item;
1458 TileChange()
1460 TileChange(Pos _p, Tile _t, int _i) : p(_p), t(_t), item(_i)
1462 void Restore(HexPuzzle* w)
1464 w->SetTile(p,t,false,false);
1465 w->SetItem(p,item,false,false);
1469 TileChange t[MAX_TILECHANGE];
1470 Pos playerPos;
1471 Dir playerMovement;
1472 int numT;
1473 int numItems[2];
1474 int score;
1475 double time;
1476 double endTime;
1478 void Add(TileChange const & tc)
1480 for (int i=0; i<numT; i++)
1481 if (t[i].p==tc.p)
1482 return;
1483 if (numT>=MAX_TILECHANGE)
1484 FATAL("numT>=MAX_TILECHANGE");
1485 else
1486 t[numT++] = tc;
1488 void New(Dir pmove, Pos & pp, int* items, double t, int sc)
1490 numItems[0] = items[0];
1491 numItems[1] = items[1];
1492 playerPos = pp;
1493 playerMovement = pmove;
1494 score = sc;
1495 time = t;
1496 numT = 0;
1498 void Restore(HexPuzzle* w)
1500 for (int i=numT-1; i>=0; i--)
1501 t[i].Restore(w);
1502 w->dead = false;
1503 w->win = false;
1504 w->player = playerPos;
1505 w->player_items[0] = numItems[0];
1506 w->player_items[1] = numItems[1];
1507 w->player_score = score;
1509 //w->renderer.player.Add(new PlayerRender(playerPos, w->GetHeight(playerPos), false), w->time);
1513 #define MAP_SIZE 30
1514 char* special[MAP_SIZE][MAP_SIZE];
1515 Tile map[MAP_SIZE][MAP_SIZE];
1516 int32_t map_item[MAP_SIZE][MAP_SIZE];
1517 int tileCount[NumTileTypes];
1518 int32_t levelPar, levelDiff;
1519 int turboAnim;
1520 Pos player;
1521 int player_items[2];
1522 int player_score;
1523 int numComplete, numLevels, numMastered, numLevelsFound;
1524 bool dead;
1525 bool win;
1526 int winFinal;
1528 SaveState progress;
1530 WorldRenderer renderer;
1531 double time;
1532 double undoTime;
1534 #define MAX_UNDO 6
1535 Undo undo[MAX_UNDO];
1536 int numUndo;
1537 LevelInfo* currentLevelInfo;
1539 char currentFile[1000];
1541 ~HexPuzzle()
1543 FreeGraphics();
1546 LevelInfo* GetLevelInfo(const char* f)
1548 if (strstr(f, "Levels\\") == f)
1549 f += 7;
1550 if (currentLevelInfo!=0 && strcmp(currentLevelInfo->file, f)==0)
1551 return currentLevelInfo;
1553 if (f[0]=='_')
1555 int t = atoi(f+1);
1556 if (t <= numComplete)
1557 return 0;
1559 static char tmp1[1000];
1560 static LevelInfo tmp = {0, "", tmp1};
1561 sprintf(tmp1, ngettext("Complete 1 more level to unlock!", "Complete %d more levels to unlock!", t-numComplete), t-numComplete);
1562 return &tmp;
1565 for (unsigned int i=0; i<sizeof(levelNames)/sizeof(levelNames[0]); i++)
1566 if (strcmp(f, levelNames[i].file)==0)
1567 return &levelNames[i];
1568 static LevelInfo tmp = {0, "", _("<<NO NAME>>")};
1569 return &tmp;
1572 #ifdef MAP_EDIT_HACKS
1573 int GetAutoTile(const char * level, bool tiletype)
1575 FILE* f = file_open(filename, "rb");
1576 int tile = EMPTY;
1577 int version;
1579 if (f && fscanf(f, "%d", &version)==1 && (version==3 || version==4))
1581 if (strstr(level,"mk"))
1582 level+=0;
1584 fgetc(f); // Remove '\n' character
1586 int32_t par, diff;
1587 unsigned char bounds[4];
1588 Pos playerStart;
1589 fread(&par, sizeof(par), 1, f);
1590 par = SWAP32(par);
1592 if (version >= 4) {
1593 fread(&diff, sizeof(diff), 1, f);
1594 diff = SWAP32(diff);
1596 fread(bounds, sizeof(bounds), 1, f);
1597 fread(&playerStart, sizeof(playerStart), 1, f);
1598 playerStart.x = SWAP32(playerStart.x);
1599 playerStart.y = SWAP32(playerStart.y);
1601 int highval=0;
1603 for (int i=bounds[0]; i<=bounds[1]; i++)
1604 for (int j=bounds[2]; j<=bounds[3]; j++)
1606 unsigned char comp = map[i][j] | (map_item[i][j]<<5);
1607 fread(&comp, sizeof(comp), 1, f);
1608 int t = comp & 0x1f;
1609 int item = (comp >> 5) & 3;
1610 for (int i=highval+1; i<sizeof(value_order)/sizeof(value_order[0]); i++)
1611 if (t!=0 && t==value_order[i]
1612 || item!=0 && item==(value_order[i]>>8))
1613 highval = i;
1616 if (tiletype)
1618 tile = value_order[highval];
1619 if (tile==0x100) tile = COLLAPSABLE3;
1620 if (tile==0x200) tile = SWITCH;
1621 if (tile==LIFT_UP) tile = LIFT_DOWN;
1623 else
1625 if (value_order[highval] == LIFT_UP)
1626 tile = highval-1;
1627 else
1628 tile = highval;
1631 else
1633 level+=0;
1635 if (f)
1636 fclose(f);
1637 return tile;
1639 #endif
1641 void InitSpecials()
1643 numComplete = numLevels = numMastered = numLevelsFound = 0;
1644 for (int i=0; i<MAP_SIZE; i++)
1645 for (int j=0; j<MAP_SIZE; j++)
1646 ActivateSpecial(Pos(i,j), 0);
1647 for (int i=0; i<MAP_SIZE; i++)
1648 for (int j=0; j<MAP_SIZE; j++)
1649 ActivateSpecial(Pos(i,j), 2);
1650 numComplete = numLevels = numMastered = numLevelsFound = 0;
1651 for (int i=0; i<MAP_SIZE; i++)
1652 for (int j=0; j<MAP_SIZE; j++)
1653 ActivateSpecial(Pos(i,j), 0);
1656 void DoHints()
1658 #ifndef EDIT
1659 if (strcmp(mapname, currentFile)==0)
1661 // for (int i=0; i<32; i++)
1662 // HintMessage::FlagTile(i);
1663 if (numComplete >= UNLOCK_SCORING && !progress.general.scoringOn)
1665 HintMessage::FlagTile(26);
1666 progress.general.scoringOn = 1;
1667 InitSpecials(); // Re-initialise with gold ones available
1669 HintMessage::FlagTile(25);
1671 else
1673 for (int i=0; i<MAP_SIZE; i++)
1674 for (int j=0; j<MAP_SIZE; j++)
1676 int t = GetTile(Pos(i,j));
1677 int item = GetItem(Pos(i,j));
1678 if (t)
1679 HintMessage::FlagTile(t);
1680 if (item)
1681 HintMessage::FlagTile(item+20);
1683 HintMessage::FlagTile(EMPTY);
1685 #endif
1686 hintsDone = true;
1688 void ResetLevel()
1690 hintsDone = false;
1692 UpdateCursor(Pos(-1,-1));
1694 isMap = false;
1696 player_score = 0;
1698 numUndo = 0;
1699 undoTime = -1;
1701 dead = false;
1702 win = false;
1703 winFinal = false;
1704 player_items[0] = player_items[1] = 0;
1705 // time = 0;
1706 if (strlen(currentSlot) == 0)
1708 new TitleMenu();
1709 new Fader(1, -3);
1711 else
1713 if (!isFadeRendering && time!=0)
1715 renderer().Add(new FadeRender(-1), time);
1716 time += 0.5;
1720 // Reset renderer
1721 renderer.Reset(time);
1722 renderer.Wipe();
1724 for (int t=0; t<NumTileTypes; t++)
1725 tileCount[t] = 0;
1727 for (int i=0; i<MAP_SIZE; i++)
1728 for (int j=0; j<MAP_SIZE; j++)
1730 Pos p(i,j);
1731 int item = GetItem(p);
1732 //if (item)
1733 renderer(p,true).Add(new ItemRender(item, GetTile(p)==EMPTY, p), time);
1736 InitSpecials();
1738 for (int i=0; i<MAP_SIZE; i++)
1739 for (int j=0; j<MAP_SIZE; j++)
1741 Pos p(i,j);
1742 int t = GetTile(p);
1743 tileCount[t]++;
1745 if (isMap)
1746 t = EMPTY;
1748 //if (t)
1749 renderer(p).Add(new TileRender(t, p), time);
1752 if (!isMap)
1753 renderer.player.Add(new PlayerRender(player, GetHeight(player), dead), time);
1754 else
1755 renderer.player.Add(new PlayerRender(Pos(-100,-100), 0, true), time);
1758 int bounds[4] = {player.getScreenX(),player.getScreenX(),player.getScreenY(),player.getScreenY()};
1759 for (int i=0; i<MAP_SIZE; i++)
1760 for (int j=0; j<MAP_SIZE; j++)
1762 Pos p(i,j);
1763 if (map[i][j] !=0 || map_item[i][j]!=0)
1765 int x1 = p.getScreenX();
1766 int y1 = p.getScreenY();
1767 int x2 = x1 + TILE_W3;
1768 int y2 = y1 + TILE_H2;
1769 y1 -= TILE_H2; // Make sure objects/player will be properly visible
1771 if (x1<bounds[0]) bounds[0] = x1;
1772 if (x2>bounds[1]) bounds[1] = x2;
1773 if (y1<bounds[2]) bounds[2] = y1;
1774 if (y2>bounds[3]) bounds[3] = y2;
1778 int sx, sy;
1779 if (isMap)
1781 sx = bounds[0] - int(TILE_W2*6.35);
1782 sy = (bounds[3] + bounds[2] - SCREEN_H) / 2 - TILE_H2/2;
1784 else
1786 sx = (bounds[1] + bounds[0] - SCREEN_W) / 2 - TILE_W3/2;
1787 sy = (bounds[3] + bounds[2] - SCREEN_H) / 2 - TILE_H2/2;
1789 if (isMap)
1791 initScrollX = sx;
1792 initScrollY = sy;
1793 if (mapScrollX==0)
1794 mapScrollX = sx;
1795 else
1796 sx = mapScrollX;
1799 // time = 1; // Guarantee we can't try and do things at time=0
1801 renderer().Add(new ScrollRender(sx, sy), time);
1802 renderer().Add(new FadeRender(1), time);
1803 if (time != 0)
1804 time -= 0.5;
1807 char* ReadAll(FILE* f)
1809 int size;
1810 // FIXME: According to http://userpage.fu-berlin.de/~ram/pub/pub_jf47ht20Ht/c_faq_de
1811 // undefined for binary streams! (POSIX does not differ between ascii and binary, so
1812 // we are on the safe side in Linux)
1813 fseek(f, 0, SEEK_END);
1814 size = ftell(f);
1815 fseek(f, 0, SEEK_SET);
1816 char* c = loadPtr = new char [size];
1817 endLoad = loadPtr + size;
1818 fread(c, 1, size, f);
1819 return c;
1822 static char *loadPtr, *endLoad;
1823 static unsigned int fread_replace(void* d, unsigned int size, unsigned int num, FILE*)
1825 unsigned int remain = (endLoad - loadPtr) / size;
1826 if (remain < num) num = remain;
1827 memcpy(d, loadPtr, size*num);
1828 loadPtr += size*num;
1829 return num;
1832 int GetPar(const char * level, bool getdiff=false)
1834 if (strcmp(level, currentFile)==0)
1835 return getdiff ? levelDiff : levelPar;
1837 #ifdef USE_LEVEL_PACKFILE
1838 PackFile1::Entry* e = levelFiles.Find(level);
1839 if (!e) return 999;
1840 loadPtr = (char*)e->Data();
1841 endLoad = loadPtr + e->DataLen();
1842 FILE* f = 0;
1843 #else
1844 loadPtr = 0;
1845 FILE* f = file_open(level, "rb");
1846 #endif
1848 typedef unsigned int _fn(void*, unsigned int, unsigned int, FILE*);
1849 _fn * fn = (loadPtr ? (_fn*)fread_replace : (_fn*)fread);
1851 int32_t par = 99999, diff = 0;
1852 int16_t version;
1854 if (!f && !loadPtr)
1855 return getdiff ? diff : par;
1857 fn(&version, 2, 1, f); // skip to relevant point
1859 if (fn(&par, sizeof(par), 1, f) != 1)
1860 par = 99999;
1861 else
1862 par = SWAP32(par);
1863 size_t ret = fn(&diff, sizeof(diff), 1, f);
1864 diff = SWAP32(diff);
1865 if (ret != 1 || diff<0 || diff>10)
1866 diff = 0;
1868 #ifdef USE_LEVEL_PACKFILE
1869 loadPtr = endLoad = 0;
1870 #else
1871 if (f)
1872 fclose(f);
1873 #endif
1875 return getdiff ? diff : par;
1878 bool LoadSave(const char * filename, bool save)
1880 if (!filename)
1881 return false;
1883 if (!save)
1885 showScoring = false;
1886 LevelSave* l = progress.GetLevel(filename, true);
1887 if (progress.general.scoringOn && l && l->Completed() )
1888 showScoring = true;
1891 #ifdef USE_LEVEL_PACKFILE
1892 if (!save)
1894 PackFile1::Entry* e = levelFiles.Find(filename);
1895 if (!e) return false;
1897 if (currentFile != filename) // equal (overlapping) strings are forbidden
1898 strcpy(currentFile, filename);
1899 currentLevelInfo = GetLevelInfo(currentFile);
1901 loadPtr = (char*)e->Data();
1902 endLoad = loadPtr + e->DataLen();
1903 _LoadSave(NULL, save);
1904 loadPtr = endLoad = 0;
1906 return true;
1908 #else
1909 loadPtr = 0;
1910 FILE* f = file_open(filename, save ? "wb" : "rb");
1911 if (f)
1913 strcpy(currentFile, filename);
1914 if (!save)
1915 currentLevelInfo = GetLevelInfo(currentFile);
1917 if (!save)
1919 char* data = ReadAll(f);
1920 _LoadSave(f, save);
1921 delete [] data;
1922 loadPtr = endLoad = 0;
1924 else
1926 _LoadSave(f, save);
1928 fclose(f);
1930 return true;
1932 #endif
1934 return false;
1937 /** \brief Writes/reads game status to/from a file
1939 * The game data file is written in little endian so it can be shared
1940 * across different machines.
1942 void _LoadSave(FILE* f, bool save)
1944 typedef unsigned int _fn(void*, unsigned int, unsigned int, FILE*);
1945 _fn * fn = save ? (_fn*)fwrite : (loadPtr ? (_fn*)fread_replace : (_fn*)fread);
1947 #define VERSION 4
1948 int version = VERSION; // 1--9
1949 if (save)
1950 fprintf(f, "%d\n", version);
1951 else
1953 char c;
1954 if (fn(&c, 1, 1, f) != 1)
1955 return;
1956 version = c-'0';
1958 // Remove '\n' character
1959 fn(&c, 1, 1, f);
1962 if (!save)
1964 for (int i=0; i<MAP_SIZE; i++)
1965 for (int j=0; j<MAP_SIZE; j++)
1967 delete [] special[i][j];
1968 special[i][j] = 0;
1972 if (version==1)
1974 for (int i=0; i<MAP_SIZE; i++)
1975 for (int j=0; j<MAP_SIZE; j++) {
1976 map[i][j] = SWAP32(map[i][j]);
1977 fn(&map[i][j], sizeof(map[i][j]), 1, f);
1978 map[i][j] = SWAP32(map[i][j]);
1981 player.x = SWAP32(player.x);
1982 player.y = SWAP32(player.y);
1983 fn(&player, sizeof(player), 1, f);
1984 player.x = SWAP32(player.x);
1985 player.y = SWAP32(player.y);
1987 for (int i=0; i<MAP_SIZE; ++i)
1988 for (int j=0; j<MAP_SIZE; ++j)
1989 map_item[i][j] = SWAP32(map_item[i][j]);
1990 if (fn(map_item, sizeof(map_item), 1, f) == 0)
1991 memset(map_item, 0, sizeof(map_item));
1992 for (int i=0; i<MAP_SIZE; ++i)
1993 for (int j=0; j<MAP_SIZE; ++j)
1994 map_item[i][j] = SWAP32(map_item[i][j]);
1996 else if (version>=2 && version<=4)
1998 unsigned char bounds[4];
1999 if (save)
2001 bounds[0]=bounds[1]=player.x;
2002 bounds[2]=bounds[3]=player.y;
2003 for (int i=0; i<MAP_SIZE; i++)
2004 for (int j=0; j<MAP_SIZE; j++)
2005 if (map[i][j] !=0 || map_item[i][j]!=0 || special[i][j]!=0)
2007 if (i<bounds[0]) bounds[0] = i;
2008 if (i>bounds[1]) bounds[1] = i;
2009 if (j<bounds[2]) bounds[2] = j;
2010 if (j>bounds[3]) bounds[3] = j;
2013 else
2015 memset(map, 0, sizeof(map));
2016 memset(map_item, 0, sizeof(map_item));
2019 if (version>=3) {
2020 levelPar = SWAP32(levelPar);
2021 fn(&levelPar, 1, sizeof(levelPar), f);
2022 levelPar = SWAP32(levelPar);
2024 else if (!save)
2025 levelPar = 0;
2027 if (version>=4) {
2028 levelDiff = SWAP32(levelDiff);
2029 fn(&levelDiff, 1, sizeof(levelDiff), f);
2030 levelDiff = SWAP32(levelDiff);
2032 else if (!save)
2033 levelDiff = 0;
2035 fn(bounds, sizeof(bounds), 1, f);
2036 player.x = SWAP32(player.x);
2037 player.y = SWAP32(player.y);
2038 fn(&player, sizeof(player), 1, f);
2039 player.x = SWAP32(player.x);
2040 player.y = SWAP32(player.y);
2042 int offsetx=0, offsety=0;
2044 if (!save && bounds[1]-bounds[0]<15) // Hacky - don't recenter map...
2046 // Re-position map to top left (but leave a bit of space)
2047 // (This ensures the laser/boat effects don't clip prematurely against the edges of the screen)
2048 offsetx = SCREEN_W/2/TILE_W2 + 1 - (bounds[0]+bounds[1]/2);
2049 offsety = SCREEN_H/2/TILE_H2 + SCREEN_W/2/TILE_W2 - (bounds[2]+bounds[3]/2);
2050 offsetx = MAX(0, offsetx);
2051 offsety = MAX(0, offsety);
2052 // if (bounds[0] > 2)
2053 // offsetx = 2 - bounds[0];
2054 // if (bounds[2] > 2)
2055 // offsety = 2 - bounds[2];
2057 bounds[0] += offsetx;
2058 bounds[1] += offsetx;
2059 bounds[2] += offsety;
2060 bounds[3] += offsety;
2061 player.x += offsetx;
2062 player.y += offsety;
2064 for (int i=bounds[0]; i<=bounds[1]; i++)
2065 for (int j=bounds[2]; j<=bounds[3]; j++)
2067 unsigned char comp = map[i][j] | (map_item[i][j]<<5);
2068 fn(&comp, sizeof(comp), 1, f);
2069 map[i][j] = comp & 0x1f;
2070 map_item[i][j] = (comp >> 5) & 3;
2073 if (save)
2075 for (int i=bounds[0]; i<=bounds[1]; i++)
2076 for (int j=bounds[2]; j<=bounds[3]; j++)
2077 if (special[i][j])
2079 int16_t len = strlen(special[i][j]);
2080 unsigned char x=i, y=j;
2081 fn(&x, sizeof(x), 1, f);
2082 fn(&y, sizeof(y), 1, f);
2083 len = SWAP16(len);
2084 fn(&len, sizeof(len), 1, f);
2085 len = SWAP16(len);
2086 fn(special[i][j], 1, len, f);
2089 else
2091 while(1){
2092 int16_t len;
2093 unsigned char x, y;
2094 if (!fn(&x, sizeof(x), 1, f))
2095 break;
2096 fn(&y, sizeof(y), 1, f);
2097 x += offsetx; y += offsety;
2098 fn(&len, sizeof(len), 1, f);
2099 len = SWAP16(len);
2100 if (len<0) break;
2101 char* tmp = new char[len+1];
2102 tmp[len] = 0;
2103 fn(tmp, 1, len, f);
2105 SetSpecial(Pos(x,y), tmp, true, false);
2109 else
2110 return; // Unsupported version!
2112 ResetLevel();
2114 // Save when returning to map!
2115 if (isMap)
2117 progress.general.completionPercentage = numComplete*100/numLevels;
2118 progress.general.masteredPercentage = numMastered*100/numLevels;
2119 LoadSaveProgress(true);
2123 void SetTile(Pos const & p, Tile t, bool updateRenderer=true, bool undoBuffer=true)
2125 if (p.x<0 || p.x>MAP_SIZE)
2126 return;
2127 if (p.y<0 || p.y>MAP_SIZE)
2128 return;
2129 if (map[p.x][p.y] == t)
2130 return;
2131 if (map[p.x][p.y] == t)
2132 return;
2134 tileCount[map[p.x][p.y]]--;
2135 tileCount[t]++;
2137 if (undoBuffer)
2138 undo[numUndo].Add(Undo::TileChange(p,GetTile(p),GetItem(p)));
2140 map[p.x][p.y] = t;
2142 if (updateRenderer)
2143 renderer(p).Add(new TileRender(t, p), time);
2146 Tile GetTile(Pos const & p)
2148 if (p.x<0 || p.x>=MAP_SIZE)
2149 return EMPTY;
2150 if (p.y<0 || p.y>=MAP_SIZE)
2151 return EMPTY;
2152 return map[p.x][p.y];
2155 int GetHeight(Pos const & p)
2157 return tileSolid[GetTile(p)]==1;
2160 char* GetSpecial(Pos const & p)
2162 if (p.x<0 || p.x>=MAP_SIZE)
2163 return NULL;
2164 if (p.y<0 || p.y>=MAP_SIZE)
2165 return NULL;
2166 return special[p.x][p.y];
2169 void SetSpecial(Pos const & p, char * d, bool use_pointer=false, bool auto_activate=true)
2171 if (p.x<0 || p.x>=MAP_SIZE || p.y<0 || p.y>=MAP_SIZE)
2173 if (use_pointer)
2174 delete [] d;
2175 return;
2178 delete [] special[p.x][p.y];
2179 if (!use_pointer && d)
2182 special[p.x][p.y] = new char [strlen(d) + 1];
2183 strcpy(special[p.x][p.y], d);
2185 else
2186 special[p.x][p.y] = d;
2188 if (special[p.x][p.y]==0)
2189 renderer(p,true).Add(new ItemRender(GetItem(p), GetTile(p)==EMPTY, p), time);
2190 else if (auto_activate)
2191 ActivateSpecial(p, 0);
2194 int GetLevelState(Pos const & p, int recurse=0)
2196 char* x = GetSpecial(p);
2197 if (!x) return 0;
2199 LevelSave* l = progress.GetLevel(x, false);
2201 int t = 1;
2203 if (strcmp(x, STARTING_LEVEL)==0)
2204 t = 2;
2205 if (x[0]=='_' && l && l->unlocked)
2206 t=3;
2208 if (l && l->Completed())
2210 t = 3;
2212 if (recurse)
2213 return t;
2215 int par = GetPar(x);
2216 if (progress.general.scoringOn && l->PassesPar( par ))
2217 t = l->BeatsPar( par ) ? 40 : 4;
2219 if (recurse)
2220 return t;
2222 int adj=0;
2223 for (Dir d=0; d<MAX_DIR; d++)
2225 int i = GetLevelState(p+d, 1);
2226 // if (i>1 || i==1 && t>1)
2227 if (i>=1 && t>2 || t>=1 && i>2)
2229 adj |= 1<<d;
2230 if (t==1)
2231 t = 2;
2235 return t | adj<<8;
2238 void ActivateSpecial(Pos const & p, int type)
2240 if (p.x<0 || p.x>=MAP_SIZE || p.y<0 || p.y>=MAP_SIZE)
2241 return;
2243 char * x = special[p.x][p.y];
2245 if (x==0 || x[0]==0)
2246 return;
2248 if (type==2 && x[0]=='_') // Phase2 init - unlock
2250 int t = GetLevelState(p);
2251 int target = atoi(x+1), targetM = 0;
2252 if (target>1000) targetM=target=target-100;
2253 if (t > 1 && numComplete >= target && numMastered >= targetM)
2255 LevelSave* l = progress.GetLevel(x, true);
2256 if (!l->unlocked)
2258 l->unlocked = true;
2260 renderer(p, true).Add(new LevelSelectRender(p, 5, GetLevelState(p)>>8), time+0.01);
2261 renderer().Add(new ExplosionRender(p, 0), time + 0.6);
2262 renderer().Add(new ExplosionRender(p, 1), time + 1.1);
2263 renderer(p, true).Add(new LevelSelectRender(p, -1, GetLevelState(p)>>8), time + 1.1);
2268 if (type==0) // Init & count levels
2270 if (x[0]=='_')
2272 int t = GetLevelState(p);
2273 int unlock = progress.GetLevel(x, true)->unlocked;
2274 LevelSelectRender* lsr = new LevelSelectRender( p, unlock ? -1 : (t>>8) ? 5 : 1, t>>8 );
2275 if ((t>>8) && p.x > mapRightBound) mapRightBound = p.x;
2276 #ifdef MAP_EDIT_HACKS
2277 lsr->magic = -atoi(x+1);
2278 SetTile(p, LIFT_DOWN, true, false);
2279 #else
2280 SetTile(p, EMPTY, true, false);
2281 #endif
2282 renderer(p,true).Add(lsr, time);
2284 else
2286 //printf("Level: %s\n", x);
2288 int t = GetLevelState(p);
2289 numLevels++;
2290 if (t && !GetItem(p))
2292 if (!isMap)
2294 isMap = true;
2295 mapRightBound = 0;
2297 currentLevelInfo = 0;
2299 if ((t&0xff)>=2)
2301 LevelSave* l = progress.GetLevel(x, true);
2302 if (!l->unlocked)
2304 l->unlocked = true;
2306 renderer(p, true).Add(new LevelSelectRender(p, -1, 0), time+0.01);
2307 renderer().Add(new ExplosionRender(p, 0), time + 0.6);
2308 renderer(p, true).Add(new LevelSelectRender(p, t & 0xff, t>>8), time + 0.6);
2311 numLevelsFound++;
2312 if (p.x > mapRightBound) mapRightBound = p.x;
2314 if ((t&0xff)>=3)
2315 numComplete++;
2316 if ((t&0xff)>=4)
2317 numMastered++;
2319 LevelSelectRender* lsr = new LevelSelectRender( p, t & 0xff, t>>8 );
2321 #ifdef MAP_EDIT_HACKS
2322 lsr->magic = 0;
2323 int t = GetAutoTile(x, true);
2324 int v = GetAutoTile(x, false);
2325 if (MAP_EDIT_HACKS_DISPLAY_UNLOCK)
2326 lsr->magic = v;
2327 else
2328 lsr->magic = GetPar(x, true);
2329 t = 1;
2330 SetTile(p, t, true, false);
2331 #else
2332 SetTile(p, EMPTY, true, false);
2333 #endif
2335 renderer(p,true).Add(lsr, time);
2340 if (type==1 && x[0]!='_') // Clicked on
2342 int t = GetLevelState(p);
2343 if (t>1)
2345 LoadSave(x, false);
2350 void SetItem(Pos const & p, int t, bool updateRenderer=true, bool undoBuffer=true)
2352 if (p.x<0 || p.x>MAP_SIZE)
2353 return;
2354 if (p.y<0 || p.y>MAP_SIZE)
2355 return;
2356 if (map_item[p.x][p.y] == t)
2357 return;
2359 if (undoBuffer)
2360 undo[numUndo].Add(Undo::TileChange(p,GetTile(p),GetItem(p)));
2362 map_item[p.x][p.y] = t;
2364 if (updateRenderer)
2365 renderer(p,true).Add(new ItemRender(t, GetTile(p)==EMPTY, p), time);
2368 Tile GetItem(Pos const & p)
2370 if (p.x<0 || p.x>=MAP_SIZE)
2371 return EMPTY;
2372 if (p.y<0 || p.y>=MAP_SIZE)
2373 return EMPTY;
2374 return map_item[p.x][p.y];
2377 void LoadSaveProgress(bool save)
2379 FILE* f = file_open(currentSlot, save ? "wb" : "rb");
2380 if (f)
2382 progress.LoadSave(f, save);
2383 fclose(f);
2385 else
2387 if (!save)
2388 progress.Clear();
2391 void LoadProgress()
2393 LoadSaveProgress(false);
2395 void SaveProgress()
2397 LoadSaveProgress(true);
2400 SDL_Surface* Load(const char * bmp, bool colourKey=true)
2402 typedef unsigned int uint32;
2403 uint32* tmp = 0;
2405 SDL_Surface * g = 0;
2407 #ifdef EDIT
2408 if (strstr(bmp, ".bmp"))
2410 g = SDL_LoadBMP(bmp);
2412 char out[1024];
2413 strcpy(out, bmp);
2414 strcpy(strstr(out, ".bmp"), ".dat");
2416 // SDL_PixelFormat p;
2417 // p.sf = 1;
2418 // SDL_Surface* tmp = SDL_ConvertSurface(g, &p, SDL_SWSURFACE);
2420 short w=g->w, h=g->h;
2421 char* buf = (char*) g->pixels;
2422 if (colourKey)
2424 while (IsEmpty(g, w-1, 0, 1, h) && w>1)
2425 w--;
2426 while (IsEmpty(g, 0, h-1, w, 1) && h>1)
2427 h--;
2430 FILE* f = file_open(out, "wb");
2431 fwrite(&w, sizeof(w), 1, f);
2432 fwrite(&h, sizeof(h), 1, f);
2434 uint32 mask = IMAGE_DAT_OR_MASK;
2435 for (int i=0; i<(int)w*h; )
2437 uint32 c = (*(uint32*)&buf[(i%w)*3 + (i/w)*g->pitch] | mask);
2438 int i0 = i;
2439 while (i < (int)w*h && c == (*(uint32*)&buf[(i%w)*3 + (i/w)*g->pitch] | mask))
2440 i++;
2441 c &= 0xffffff;
2442 i0 = i-i0-1;
2443 if (i0 < 0xff)
2444 c |= i0 << 24;
2445 else
2446 c |= 0xff000000;
2448 fwrite(&c, sizeof(c), 1, f);
2450 if (i0 >= 0xff)
2451 fwrite(&i0, sizeof(i0), 1, f);
2453 fclose(f);
2455 SDL_FreeSurface(g);
2457 bmp = out;
2459 #endif
2461 FILE* f = file_open(bmp, "rb");
2462 if (!f) FATAL("Unable to open file", bmp);
2464 int16_t w,h;
2465 fread(&w, sizeof(w), 1, f);
2466 fread(&h, sizeof(h), 1, f);
2467 w = SWAP16(w);
2468 h = SWAP16(h);
2469 if (w>1500 || h>1500 || w<=0 || h<=0) FATAL("Invalid file", bmp);
2471 tmp = new uint32[(int)w*h];
2473 uint32 c = 0;
2474 uint32 cnt = 0;
2475 for (int p=0; p<(int)w*h; p++)
2477 if (cnt)
2478 cnt -= 0x1;
2479 else
2481 fread(&c, sizeof(c), 1, f);
2482 c = SWAP32(c);
2483 cnt = c >> 24;
2484 if (cnt==255) {
2485 fread(&cnt, sizeof(cnt), 1, f);
2486 cnt = SWAP32(cnt);
2489 tmp[p] = c | 0xff000000;
2492 g = SDL_CreateRGBSurfaceFrom(tmp, w, h, 32, w*4,
2493 0xff0000,
2494 0xff00,
2495 0xff,
2496 0xff000000 );
2498 fclose(f);
2501 if (!g) FATAL("Unable to create SDL surface");
2502 if (colourKey)
2503 SDL_SetColorKey(g, SDL_SRCCOLORKEY, SDL_MapRGB(g->format, WATER_COLOUR));
2504 SDL_Surface * out = SDL_DisplayFormat(g);
2505 SDL_FreeSurface(g);
2506 delete [] tmp;
2507 if (!out) FATAL("Unable to create SDL surface (SDL_DisplayFormat)");
2508 return out;
2511 #ifdef USE_LEVEL_PACKFILE
2512 PackFile1 levelFiles;
2513 #endif
2514 HexPuzzle()
2516 SDL_WM_SetCaption(GAMENAME, 0);
2518 time = 0;
2520 #ifdef USE_LEVEL_PACKFILE
2521 FILE* f = file_open("levels.dat", "rb");
2522 if (!f)
2523 FATAL("Unable to open file", "levels.dat");
2524 levelFiles.Read(f);
2525 fclose(f);
2526 #endif
2528 LoadGraphics();
2530 isMap = false;
2531 editMode = false;
2533 currentLevelInfo = 0;
2535 editTile = 0;
2536 levelPar = 0;
2537 levelDiff = 5;
2538 turboAnim = 0;
2540 memset(map, 0, sizeof(map));
2541 memset(map_item, 0, sizeof(map_item));
2542 memset(special, 0, sizeof(special));
2544 LoadProgress();
2546 // player = Pos(1,11);
2548 // ResetLevel();
2550 LoadMap();
2553 void LoadMap()
2555 #ifndef EDIT
2556 progress.GetLevel(STARTING_LEVEL, true)->unlocked = 1;
2557 if (!progress.GetLevel(STARTING_LEVEL, true)->Completed())
2559 LoadSave(STARTING_LEVEL, false);
2560 return;
2562 #endif
2564 //editMode = false;
2565 LoadSave(mapname, false);
2568 void Render()
2570 if (!activeMenu || activeMenu->renderBG)
2572 SDL_Rect src = {0,0,screen->w,screen->h};
2573 SDL_Rect dst = {0,0,screen->w,screen->h};
2574 if (isRenderMap)
2576 int boundW = mapBG->w;
2577 #ifndef EDIT
2578 boundW = MIN(boundW, (mapRightBound+4) * TILE_W2 - TILE_W1);
2579 #endif
2580 src.x = scrollX - initScrollX;
2581 if (src.x+src.w > boundW)
2583 int diff = src.x+src.w - boundW;
2584 src.x -= diff;
2585 if (isMap)
2586 scrollX -= diff;
2588 if (src.x < 0)
2590 if (isMap)
2591 scrollX -= src.x;
2592 src.x = 0;
2594 //scrollY = initScrollY;
2596 if (isMap)
2597 mapScrollX = scrollX;
2599 SDL_BlitSurface(mapBG, &src, screen, &dst);
2601 else
2602 SDL_BlitSurface(gradient, &src, screen, &dst);
2604 renderer.Render(time, true);
2606 if (!hintsDone && !isFadeRendering)
2608 DoHints();
2611 if (1)
2613 SDL_Rect src = {0,SCREEN_H-1,SCREEN_W,1};
2614 SDL_Rect dst = {0,SCREEN_H-1,SCREEN_W,1};
2615 for (int i=0; i<SCREEN_H; i++)
2617 dst.x = src.x = 0;
2618 dst.y = src.y = SCREEN_H-1-i;
2619 src.w = SCREEN_W;
2620 src.h = 1;
2622 if (isRenderMap)
2624 src.x += (int)( sin(i*0.9 + time*3.7) * sin(i*0.3 + time*0.7)*4 );
2625 src.y += (int)( (sin(i*0.3 - time*2.2) * sin(i*0.48 + time*0.47) - 1) * 1.99 );
2627 else
2629 src.x += (int)( sin(i*0.5 + time*6.2) * sin(i*0.3 + time*1.05) * 5 );
2630 src.y += (int)( (sin(i*0.4 - time*4.3) * sin(i*0.08 + time*1.9) - 1) * 2.5 );
2632 SDL_BlitSurface(screen, &src, screen, &dst);
2636 if(isRenderMap)
2637 SDL_BlitSurface(mapBG2, &src, screen, &dst);
2639 renderer.Render(time, false);
2641 if (!isRenderMap && !isMap && !isFadeRendering)
2643 int v[3] = {player_items[0], player_items[1], player_score};
2644 if (numUndo > 1 && time < undo[numUndo-2].endTime)
2646 int i = numUndo-1;
2647 while (i>1 && time<undo[i-1].time)
2648 i--;
2649 v[0] = undo[i].numItems[0];
2650 v[1] = undo[i].numItems[1];
2651 v[2] = undo[i].score;
2653 if (numUndo>1 && time < undo[0].time)
2654 v[0]=v[1]=v[2]=0;
2655 #ifdef EDIT
2656 /* TRANSLATORS: Anti-Ice are pickups, which turn ice plates into solid
2657 plates once you step on them. Each pickup changes one ice plate */
2658 Print(0,0,_("Anti-Ice: %d"), v[0]);
2659 Print(0,FONT_SPACING,_("Jumps: %d"), v[1]);
2660 Print(0,FONT_SPACING*2,_("Score: %d (%d)"), v[2], player_score);
2661 /* TRANSLATORS: Par is similar to golf, a pre defined score which you
2662 can attempt to beat */
2663 Print(0,FONT_SPACING*3,_("Par: %d"), levelPar);
2664 Print(0,FONT_SPACING*4,_("Diff: %d"), levelDiff);
2665 #else
2666 if (showScoring)
2667 Print(0, SCREEN_H-FONT_SPACING, _(" Par: %d Current: %d"), levelPar, v[2]);
2669 if (v[0])
2670 Print(0,0,_(" Anti-Ice: %d"), v[0]);
2671 else if (v[1])
2672 Print(0,0,_(" Jumps: %d"), v[1]);
2673 #endif
2675 if (isRenderMap && isMap && !isFadeRendering)
2677 #if 0//def EDIT
2678 Print(0,0,_("Points: %d"), numComplete+numMastered);
2679 Print(0,FONT_SPACING,_("Discovered: %d%% (%d/%d)"), numLevelsFound*100/numLevels, numLevelsFound, numLevels);
2680 Print(0,FONT_SPACING*2,_("Complete: %d%% (%d)"), numComplete*100/numLevels, numComplete);
2681 Print(0,FONT_SPACING*3,_("Mastered: %d%% (%d)"), numMastered*100/numLevels, numMastered);
2682 #else
2683 if (numComplete==numLevels && progress.general.endSequence>0)
2684 Print(0, SCREEN_H-FONT_SPACING, _(" %d%% Mastered"), numMastered*100/numLevels);
2685 else
2686 Print(0, SCREEN_H-FONT_SPACING, _(" %d%% Complete"), numComplete*100/numLevels);
2688 if (numMastered >= numLevels && progress.general.endSequence < 2)
2690 progress.general.endSequence = 2;
2691 LoadSaveProgress(true);
2693 new Fader(-1, -7, 0.3);
2695 if (numComplete >= numLevels && progress.general.endSequence < 1)
2697 progress.general.endSequence = 1;
2698 LoadSaveProgress(true);
2700 new Fader(-1, -5, 0.3);
2702 #endif
2704 if ((currentLevelInfo || noMouse) && isMap && isRenderMap && !activeMenu && isFadeRendering<=0)
2706 Pos p;
2707 if (noMouse)
2708 p = keyboardp;
2709 else
2710 p = mousep;
2711 int pad = SCREEN_W/80;
2712 // SDL_Rect src = {0, 0, uiGraphics->w, uiGraphics->h};
2713 SDL_Rect dst = {pad, SCREEN_H-TILE_H2-pad, 0, 0};
2714 // dst.x = p.getScreenX() + TILE_W3/2 - scrollX;
2715 // dst.y = p.getScreenY() - src.h/2 - scrollY;
2716 dst.x = p.getScreenX() - scrollX;
2717 dst.y = p.getScreenY() - scrollY - FONT_SPACING*3 - FONT_SPACING/2;
2718 // if (dst.x > SCREEN_W*2/3) dst.x -= TILE_W3 + src.w;
2719 // if (dst.y+src.h > screen->h-pad) dst.y = screen->h-pad - src.h;
2721 RenderTile(false, 0, p.getScreenX(), p.getScreenY());
2722 // SDL_BlitSurface(uiGraphics, &src, screen, &dst);
2724 // dst.x += src.w/2;
2726 if (currentLevelInfo)
2728 keyboardp = p;
2730 PrintC(true, dst.x, dst.y - FONT_SPACING/4, currentLevelInfo->name);
2732 if (currentLevelInfo->file[0]!=0)
2734 if (player_score > 0)
2736 if (progress.general.scoringOn)
2738 PrintC(false, dst.x, dst.y + FONT_SPACING*4 - FONT_SPACING/4, _("Best:% 3d"), player_score);
2739 PrintC(false, dst.x, dst.y + FONT_SPACING*5 - FONT_SPACING/4, _("Par:% 3d"), levelPar);
2741 else
2742 PrintC(false, dst.x, dst.y + FONT_SPACING*4 - FONT_SPACING/4, _("Completed"), player_score);
2744 else
2745 PrintC(false, dst.x, dst.y + FONT_SPACING*4 - FONT_SPACING/4, _("Incomplete"), player_score);
2750 // "Win"
2751 if (win && numUndo > 0 && time > undo[numUndo-1].endTime + 2)
2753 if (currentFile[0] && winFinal==0)
2755 LevelSave* l = progress.GetLevel(currentFile, true);
2757 new WinLoseScreen(true, player_score, showScoring ? levelPar : 0, l && showScoring && l->Completed() ? l->GetScore() : 0);
2759 if (l->IsNewCompletionBetter(player_score))
2761 l->SetScore(player_score);
2763 l->SetSolution(numUndo);
2765 for (int i=0; i<numUndo; i++)
2766 l->SetSolutionStep(i, undo[i].playerMovement);
2769 SaveProgress();
2772 winFinal = 1;
2774 else
2775 winFinal = 0;
2777 // Move up "level complete" writing so it doesn't feel like everything's ground to a halt...
2778 if (win && numUndo > 0 && time > undo[numUndo-1].endTime && !winFinal)
2780 double t = (time - undo[numUndo-1].endTime) / 2;
2781 t=1-t;
2782 t*=t*t;
2783 t=1-t;
2784 int y = SCREEN_H/3 - FONT_SPACING + 1;
2785 y = SCREEN_H + int((y-SCREEN_H)*t);
2786 PrintC(true, SCREEN_W/2, y, _("Level Complete!"));
2790 if (activeMenu)
2791 activeMenu->Render();
2793 if (!noMouse)
2795 // Edit cursor
2796 if (editMode)
2798 RenderTile(false, editTile, mousex+scrollX, mousey+scrollY);
2803 int Count(Tile t)
2805 return tileCount[t];
2807 int Swap(Tile t, Tile t2)
2809 const int num = Count(t) + Count(t2);
2810 if (t==t2 || num==0)
2811 return Count(t); // Nothing to do...
2813 int count=0;
2814 for (int x=0; x<MAP_SIZE; x++)
2815 for (int y=0; y<MAP_SIZE; y++)
2817 if (GetTile(Pos(x,y))==t)
2819 count++;
2820 SetTile(Pos(x,y), t2);
2822 else if (GetTile(Pos(x,y))==t2)
2824 count++;
2825 SetTile(Pos(x,y), t);
2827 if (count==num)
2828 return count;
2830 return count;
2832 int Replace(Tile t, Tile t2)
2834 const int num = Count(t);
2835 if (t==t2 || num==0)
2836 return num; // Nothing to do...
2838 int count=0;
2839 for (int x=0; x<MAP_SIZE; x++)
2840 for (int y=0; y<MAP_SIZE; y++)
2842 Pos p(x,y);
2843 if (GetTile(p)==t)
2845 count++;
2847 SetTile(p, t2, false);
2849 if (t==COLLAPSE_DOOR && t2==COLLAPSABLE)
2850 renderer(p).Add(new BuildRender(p, -1, 1, 1), time + (rand() & 255)*0.001);
2851 else if (t==COLLAPSE_DOOR2 && t2==COLLAPSABLE2)
2852 renderer(p).Add(new BuildRender(p, -1, 1, 1, 1), time + (rand() & 255)*0.001);
2853 else
2854 SetTile(p, t2);
2856 if (count==num)
2857 return count;
2860 return count;
2863 Tile editTile;
2864 bool editMode;
2865 void ResetUndo()
2867 UndoDone();
2868 undoTime = -1;
2869 numUndo = 0;
2870 win = false;
2873 void UpdateCursor(Pos const & s)
2875 static Pos _s;
2876 if (s.x!=_s.x || s.y!=_s.y)
2878 _s = s;
2880 char* sp = GetSpecial(s);
2881 char tmp[1000];
2882 tmp[0]='\0';
2883 if (sp)
2885 if (isMap)
2887 currentLevelInfo = 0;
2888 levelPar = player_score = -1;
2889 if (GetLevelState(s)>=2)
2891 LevelSave* l = progress.GetLevel(sp, true);
2892 if (l)
2894 currentLevelInfo = GetLevelInfo(sp);
2895 levelPar = GetPar(sp);
2896 player_score = l->GetScore();
2901 #ifdef EDIT
2902 sprintf(tmp, _("Special(%d,%d): %s (%d)"), s.x, s.y, sp ? sp : _("<None>"), GetPar(sp));
2903 SDL_WM_SetCaption(tmp, NULL);
2904 #endif
2906 else if (currentFile[0])
2908 #ifdef EDIT
2909 SDL_WM_SetCaption(currentFile, NULL);
2910 #endif
2911 if (isMap)
2912 currentLevelInfo = 0;
2917 virtual void Mouse(int x, int y, int dx, int dy, int button_pressed, int button_released, int button_held)
2919 if (activeMenu)
2921 activeMenu->Mouse(x,y,dx,dy,button_pressed,button_released,button_held);
2922 return;
2925 if (isFadeRendering)
2926 return;
2929 #ifndef EDIT
2930 if (button_pressed==2 || button_pressed==4 && isMap)
2932 KeyPressed(SDLK_ESCAPE, 0);
2933 keyState[SDLK_ESCAPE] = 0;
2934 return;
2936 #endif
2938 x += scrollX;
2939 y += scrollY;
2941 Pos s = Pos::GetFromWorld(x,y);
2942 if (tileSolid[GetTile(Pos::GetFromWorld(x,y+TILE_HUP))] == 1)
2943 s = Pos::GetFromWorld(x,y+TILE_HUP);
2945 mousep = s;
2947 UpdateCursor(s);
2949 #ifdef EDIT
2950 if (button_held & ~button_pressed & 4)
2952 scrollX -= dx;
2953 scrollY -= dy;
2955 #endif
2957 if (!editMode)
2959 if (isMap && (button_pressed & 1))
2961 ActivateSpecial(s, 1);
2962 return;
2964 if (!isMap && win && winFinal)
2966 if (button_pressed & 1)
2968 LoadMap();
2969 return;
2972 if(!isMap)
2974 if((button_pressed & 1) || (button_held & 1) && (numUndo==0 || time>=undo[numUndo-1].endTime))
2976 if(s.x==player.x && s.y==player.y)
2978 // Don't activate jump powerup without a new click
2979 if (button_pressed & 1)
2980 Input(-1);
2982 else if(s.x==player.x && s.y<player.y)
2983 Input(0);
2984 else if(s.x==player.x && s.y>player.y)
2985 Input(3);
2986 else if(s.y==player.y && s.x<player.x)
2987 Input(5);
2988 else if(s.y==player.y && s.x>player.x)
2989 Input(2);
2990 else if(s.y+s.x==player.y+player.x && s.x>player.x)
2991 Input(1);
2992 else if(s.y+s.x==player.y+player.x && s.x<player.x)
2993 Input(4);
2995 if ((button_pressed & 4) || (button_held & 4) && (undoTime < 0))
2996 Undo();
2998 return;
3001 #ifdef EDIT
3002 if (!button_pressed && !button_held)
3003 return;
3005 if (button_pressed==1)
3006 if (editTile<0)
3007 editTile = GetItem(s)==1 ? -3 : GetItem(s)==2 ? -2 : -1;
3009 if (button_held==1 || button_pressed==1)
3011 ResetUndo();
3012 if (editTile>=0)
3013 SetTile(s, editTile, true, false);
3014 else
3015 SetItem(s, editTile==-2 ? 0 : editTile==-1 ? 1 : 2, true, false);
3018 if (button_pressed==2)
3020 editTile = GetTile(s);
3023 if (button_pressed==8)
3025 editTile=editTile-1;
3026 if (editTile<=0) editTile=NumTileTypes-1;
3029 if (button_pressed==16)
3031 editTile=editTile+1;
3032 if (editTile<=0) editTile=1;
3033 if (editTile==NumTileTypes) editTile=0;
3036 if (button_pressed==64)
3038 ResetUndo();
3039 player = s;
3040 dead = false;
3041 renderer.player.Reset(-1);
3042 renderer.player.Add(new PlayerRender(player, GetHeight(player), dead), 0);
3045 if (button_pressed==256)
3047 char* fn = LoadSaveDialog(false, true, _("Select level"));
3048 if (fn)
3050 char * l = strstr(fn, "Levels");
3051 if(l)
3053 FILE * f = file_open(l,"rb");
3054 if (f)
3055 fclose(f);
3056 if (f)
3057 SetSpecial(s, l);
3058 else if (l[6]!=0 && l[7]=='_')
3059 SetSpecial(s, l+7);
3061 UpdateCursor(Pos(-1,-1));
3064 if (button_pressed==512)
3066 SetSpecial(s, NULL);
3067 UpdateCursor(Pos(-1,-1));
3069 if (button_pressed==1024)
3071 static char x[1000] = "";
3072 if (!(s.x<0 || s.x>=MAP_SIZE || s.y<0 || s.y>=MAP_SIZE))
3074 char tmp[1000];
3075 strcpy(tmp, x);
3076 if (GetSpecial(s))
3077 strcpy(x, GetSpecial(s));
3078 else
3079 x[0] = 0;
3080 SetSpecial(s, tmp[0] ? tmp : 0);
3081 if (!tmp[0])
3082 SetTile(s, EMPTY, true, false);
3086 if (button_pressed==32)
3088 editTile = editTile<0 ? 1 : -1;
3090 #endif // EDIT
3093 void CheckFinished()
3095 bool slow = false;
3096 if (Count(COLLAPSABLE)==0)
3098 if (Replace(COLLAPSE_DOOR, COLLAPSABLE) == 0)
3099 win = true;
3100 else
3101 slow = true;
3102 Replace(SWITCH, NORMAL);
3104 else
3105 win = false;
3107 if (Count(COLLAPSABLE2)==0)
3108 if (Replace(COLLAPSE_DOOR2, COLLAPSABLE2))
3109 slow = true;
3111 if (slow)
3112 time += BUILD_TIME;
3114 bool Collide(Pos p, bool high)
3116 Tile t = GetTile(p);
3117 // switch(t)
3118 // {
3119 // default:
3120 if (!high)
3121 return tileSolid[t]==1;
3122 else
3123 return false;
3124 // }
3126 void Undo()
3128 if (numUndo==0) return;
3130 UndoDone(); // Complete previous undo...
3132 numUndo--;
3134 if (time > undo[numUndo].endTime)
3135 time = undo[numUndo].endTime;
3136 undoTime = undo[numUndo].time;
3138 undo[numUndo].Restore(this);
3140 void UndoDone()
3142 if (undoTime < 0)
3143 return;
3144 renderer.Reset(undoTime);
3145 time = undoTime;
3146 undoTime = -1;
3148 void ScoreDestroy(Pos p)
3150 Tile t = GetTile(p);
3151 if (t==COLLAPSABLE || t==COLLAPSE_DOOR)
3153 else if (t != EMPTY)
3155 player_score += 10;
3159 bool LaserTile(Pos p, int mask, double fireTime)
3161 if (&renderer(p) == &renderer(Pos(-1,-1)))
3162 return false;
3163 //if (!renderer.Visible(p))
3164 // return false;
3166 TileRender* tr = 0;
3167 if (time <= renderer(p).GetLastTime())
3168 if (fireTime < renderer(p).GetLastTime())
3170 renderer(p).Add(tr = new TileRender(GetTile(p), p, mask), fireTime);
3171 ((TileRender*)renderer(p).GetStage(time+10/*HACKY!*/))->special |= mask;
3173 else
3175 tr = new TileRender(GetTile(p), p, mask | ((TileRender*)renderer(p).GetStage(time+10/*HACKY!*/))->special);
3176 renderer(p).Add(tr, fireTime);
3178 else
3179 renderer(p).Add(tr = new TileRender(GetTile(p), p, mask), fireTime);
3181 if (tr)
3183 tr->specialDuration = time + LASER_LINE_TIME - fireTime + LASER_FADE_TIME;
3185 return true;
3187 void FireGun(Pos newpos, Dir d, bool recurse, double fireTime)
3189 static Pos hits[100];
3190 static Dir hitDir[100];
3191 static unsigned int numHits=0;
3192 if (!recurse)
3193 numHits = 0;
3195 double starttime = fireTime;
3196 for (Dir fd=((d<0)?0:d); fd<((d<0)?MAX_DIR:d+1); fd++)
3198 fireTime = starttime;
3199 // starttime += 0.03;
3201 Pos p = newpos + fd;
3202 int range = 0;
3203 for (; range<MAP_SIZE; range++, p=p+fd)
3205 Tile t = GetTile(p);
3206 if (tileSolid[t]!=-1)
3208 if (t!=TRAP)
3209 renderer(p).Add(new TileRender(tileSolid[t]==1 ? TILE_WHITE_WALL : TILE_WHITE_TILE, p), fireTime+0.1);
3211 unsigned int i;
3212 for (i=0; i<numHits; i++)
3213 if (hits[i]==p)
3214 break;
3215 if (i==numHits ||
3216 t==TRAP && (hitDir[i]&(1<<fd))==0
3219 if (i==numHits)
3221 if (i >= sizeof(hits)/sizeof(hits[0]))
3222 return;
3223 hitDir[i] = 1 << fd;
3224 hits[i] = p;
3225 numHits++;
3227 else
3229 hitDir[i] |= 1 << fd;
3231 if (t==TRAP)
3233 int dirmask =
3234 1<<((fd+2) % MAX_DIR)
3235 | 1<<((fd+MAX_DIR-2) % MAX_DIR);
3237 if (LaserTile(p, dirmask, fireTime))
3238 fireTime += (time+LASER_LINE_TIME - fireTime) / 40;
3239 // fireTime += LASER_SEGMENT_TIME;
3241 FireGun(p, (fd+1) % MAX_DIR, true, fireTime);
3242 FireGun(p, (fd+MAX_DIR-1) % MAX_DIR, true, fireTime);
3245 break;
3247 else
3249 LaserTile(p, 1<<(fd%3), fireTime);
3251 fireTime += (time+LASER_LINE_TIME - fireTime) / 40;
3252 // fireTime += LASER_SEGMENT_TIME;
3256 // renderer().Add(new LaserRender(newpos, fd, range), time);
3259 if (!recurse)
3261 //double _time = time;
3262 time += LASER_LINE_TIME;
3263 for (unsigned int i=0; i<numHits; i++)
3265 Pos p = hits[i];
3266 Tile t = GetTile(p);
3268 if (t==TRAP)
3269 continue;
3271 ScoreDestroy(p);
3273 renderer(p).Add(new ExplosionRender(p, t==GUN), time);
3274 //renderer(p).Add(new TileRender(EMPTY, p), time+2);
3275 SetTile(p, EMPTY, false);
3277 if (GetItem(p))
3278 renderer(p,true).Add(new ItemRender(GetItem(p), 1, p), time);
3280 if (t==GUN)
3282 for (Dir j=0; j<MAX_DIR; j++)
3284 if (GetTile(p+j)!=EMPTY)
3286 renderer(p+j).Add(new TileRender(tileSolid[GetTile(p+j)]==1 ? TILE_WHITE_WALL : TILE_WHITE_TILE, p+j), time+0.05);
3287 renderer(p+j).Add(new ExplosionRender(p+j), time+0.2);
3289 if (GetItem(p+j))
3290 renderer(p+j,true).Add(new ItemRender(GetItem(p+j), 1, p), time);
3292 //renderer(p+j).Add(new TileRender(EMPTY, p+j), time+2.2);
3294 ScoreDestroy(p + j);
3295 SetTile(p + j, EMPTY, false);
3300 time += MAX(LASER_FADE_TIME, 0.15);
3301 //time = _time;
3302 CheckFinished();
3305 int GetLastPlayerRot()
3307 RenderStage* rs = renderer.player.GetStage(-1);
3308 if (!rs) return 3;
3309 return ((PlayerRender*)rs)->r;
3311 bool Input(Dir d)
3313 if (dead || win || isMap)
3314 return false;
3316 // Complete undo
3317 UndoDone();
3319 // Jump forwards in time to last move finishing
3320 if (numUndo > 0 && time < undo[numUndo-1].endTime)
3321 time = undo[numUndo-1].endTime;
3323 double realTime = time;
3324 double endAnimTime = time;
3325 bool high = (tileSolid[GetTile(player)] == 1);
3326 Pos playerStartPos = player;
3327 Pos oldpos = player;
3328 int oldPlayerHeight = GetHeight(oldpos);
3329 Pos newpos = player + d;
3331 int playerRot = GetLastPlayerRot();
3332 if (d!=-1 && d!=playerRot)
3334 while (d!=playerRot)
3336 if ((d+6-playerRot) % MAX_DIR < MAX_DIR/2)
3337 playerRot = (playerRot+1) % MAX_DIR;
3338 else
3339 playerRot = (playerRot+MAX_DIR-1) % MAX_DIR;
3341 time += 0.03;
3343 if (GetTile(oldpos) == FLOATING_BALL)
3345 TileRender* t = new TileRender(FLOATING_BALL, oldpos);
3346 t->special = playerRot + 256;
3347 renderer(oldpos).Add(t, time);
3349 renderer.player.Add(new PlayerRender(playerRot, Pos(-20,-20), oldPlayerHeight, Pos(-20,-20), oldPlayerHeight, dead), time);
3351 else
3353 PlayerRender *p = new PlayerRender(playerRot, player, oldPlayerHeight, player, oldPlayerHeight, dead);
3354 p->speed = 0;
3355 renderer.player.Add(p, time);
3359 time += 0.03;
3362 if (d<0 && player_items[1]==0)
3363 return false;
3365 if (d >= 0)
3367 if (tileSolid[GetTile(newpos)] == -1)
3369 time = realTime;
3370 return false;
3372 if (Collide(newpos, high))
3374 time = realTime;
3375 return false;
3379 // Don't change any real state before this point!
3380 if (numUndo >= MAX_UNDO)
3382 numUndo--;
3383 for(int i=0; i<MAX_UNDO-1; i++)
3384 undo[i] = undo[i+1];
3386 undo[numUndo].New(d, player, player_items, time, player_score);
3388 if (d<0)
3390 player_items[1]--;
3393 double time0 = time;
3394 time += 0.15; //Time for leave-tile fx
3396 switch (GetTile(oldpos))
3398 case COLLAPSABLE:
3399 SetTile(oldpos, EMPTY);
3400 renderer(oldpos).Add(new DisintegrateRender(oldpos), time);
3401 CheckFinished();
3402 break;
3404 case COLLAPSE_DOOR:
3405 // Don't need to CheckFinished - can't be collapse doors around
3406 // unless there're still collapsable tiles around.
3407 SetTile(oldpos, EMPTY);
3408 renderer(oldpos).Add(new DisintegrateRender(oldpos, 1), time);
3409 break;
3411 case COLLAPSABLE2:
3412 SetTile(oldpos, COLLAPSABLE, false);
3413 renderer(oldpos).Add(new DisintegrateRender(oldpos, 0, 1), time);
3414 player_score += 10;
3415 CheckFinished();
3416 break;
3418 case COLLAPSE_DOOR2:
3419 SetTile(oldpos, COLLAPSE_DOOR, false);
3420 renderer(oldpos).Add(new DisintegrateRender(oldpos, 1, 1), time);
3421 player_score += 10;
3422 break;
3424 case COLLAPSABLE3:
3425 SetTile(oldpos, COLLAPSABLE2);
3426 break;
3429 time = time0; //End of leave-tile fx
3431 int retry_pos_count=0;
3432 retry_pos:
3433 retry_pos_count++;
3435 if (GetItem(newpos)==1)
3437 renderer(newpos, true).Add(new ItemCollectRender(GetItem(newpos), newpos), time + JUMP_TIME/2);
3438 SetItem(newpos, 0, false);
3439 player_items[0]++;
3441 if (GetItem(newpos)==2)
3443 renderer(newpos, true).Add(new ItemCollectRender(GetItem(newpos), newpos), time + JUMP_TIME/2);
3444 SetItem(newpos, 0, false);
3445 player_items[1]++;
3448 if (GetTile(player) == FLOATING_BALL)
3450 TileRender* t = new TileRender(FLOATING_BALL, player);
3451 t->special = 0;
3452 renderer(oldpos).Add(t, time);
3455 PlayerRender *p = new PlayerRender(playerRot, player, oldPlayerHeight, newpos, GetHeight(newpos), dead);
3457 // alternate leg (hacky!)
3458 if (1)
3460 static int l=0;
3461 l++;
3462 p->type = l & 1;
3465 if (retry_pos_count!=0 && GetTile(player)==TRAP)
3467 p->speed /= 1.5;
3468 p->type = 2;
3470 if (d==-1)
3471 p->speed = JUMP_TIME * 1.5;
3472 renderer.player.Add(p, time);
3473 endAnimTime = MAX(endAnimTime, time + p->speed+0.001);
3474 time += p->speed;
3476 player = newpos;
3478 switch (GetTile(newpos))
3480 case COLLAPSABLE:
3481 renderer(newpos).Add(new TileRender(TILE_GREEN_CRACKED, newpos), time