Show mouse cursor without font
[hex-a-hop.git] / hex_puzzzle.cpp
blob580eb44155cdea1a6f495152bcb29b1b860965fb
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
17 */
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 #define GAME_NAME "Hex-a-hop"
38 #ifdef EDIT
39 // #define MAP_EDIT_HACKS
40 #define MAP_EDIT_HACKS_DISPLAY_UNLOCK 0
41 #define CHEAT
42 #define BMP_SUFFIX ".bmp"
43 #else
44 #define USE_LEVEL_PACKFILE
45 #define BMP_SUFFIX ".dat"
46 #endif
50 #ifdef EDIT
51 #define GAMENAME GAME_NAME " (EDIT MODE)"
52 #endif
53 #ifndef GAMENAME
54 #define GAMENAME GAME_NAME
55 #endif
57 #define IMAGE_DAT_OR_MASK 0xff030303 // Reduce colour depth of images slightly for better compression (and remove useless top 8 bits!)
58 #define STARTING_LEVEL "Levels\\0_green\\triangular.lev"
59 #define UNLOCK_SCORING 75
60 const char * mapname = "Levels\\map_maybe\\map.lev";
62 //////////////////////////////////////////////////////
66 #ifndef USE_OPENGL
68 #include "state.h"
70 #include "tiletypes.h"
72 #ifdef USE_LEVEL_PACKFILE
73 #include "packfile.h"
74 #endif
76 void RenderTile(bool reflect, int t, int x, int y, int cliplift=-1);
78 int keyState[SDLK_LAST] = {0};
80 FILE *file_open( const char *file, const char *flags )
82 // printf("file_open( \"%s\", \"%s\" )\n", file, flags );
83 extern String base_path;
84 static String filename; // static to reduce memory alloc/free calls.
85 if (file[0]=='\0' || file[1]!=':') //If a full path is specified, don't prepend base_path
86 filename = base_path;
87 filename += file;
88 // printf(" -> \"%s\"\n", filename );
90 filename.fix_backslashes();
91 FILE* f = fopen( filename, flags );
93 if (!f)
95 printf("Warning: unable to open file \"%s\" for %s\n", (const char*)filename, strchr(flags, 'r') ? "reading" : "writing");
98 return f;
102 #ifdef MAP_EDIT_HACKS
103 static const short value_order[]={
104 //WALL,
105 //COLLAPSE_DOOR2,
106 //COLLAPSABLE3
107 //SWITCH
108 //EMPTY, NORMAL,
110 COLLAPSABLE,
111 TRAMPOLINE,
112 COLLAPSE_DOOR, COLLAPSABLE2,
113 GUN,
114 FLOATING_BALL,
115 SPINNER,
116 TRAP,
117 0x100,
118 LIFT_DOWN, LIFT_UP,
119 BUILDER,
120 0x200,
122 #endif
124 //#define PROGRESS_FILE "progress.dat"
126 #define PI (3.1415926535897931)
127 #define PI2 (PI*2)
128 #define MAX(a,b) ((a)>(b) ? (a) : (b))
129 #define MIN(a,b) ((a)<(b) ? (a) : (b))
130 #define ABS(a) ((a)<0 ? -(a) : (a))
132 #define WATER_COLOUR 31 | (IMAGE_DAT_OR_MASK>>16)&255, 37 | (IMAGE_DAT_OR_MASK>>8)&255, 135 | (IMAGE_DAT_OR_MASK>>0)&255
134 #define ROTATION_TIME 0.25
135 #define BUILD_TIME 1
136 #define LASER_LINE_TIME 0.7
137 #define LASER_FADE_TIME 0.1
138 #define LASER_SEGMENT_TIME 0.01
139 #define LIFT_TIME 0.5
140 #define JUMP_TIME 0.4
142 #define X(NAME,FILE,ALPHA) SDL_Surface* NAME = 0;
143 #include "gfx_list.h"
144 int scrollX=0, scrollY=0, initScrollX=0, initScrollY=0;
145 int mapRightBound = 0;
146 int mapScrollX = 0;
147 bool showScoring = false;
148 bool hintsDone = false;
150 enum {
151 TILE_SPLASH_1 = 17,
152 TILE_SPLASH_2,
153 TILE_SPLASH_3,
155 TILE_SPHERE = 20,
156 TILE_SPHERE_OPEN,
157 TILE_SPHERE_DONE,
158 TILE_SPHERE_PERFECT,
159 TILE_LOCK,
161 TILE_LIFT_BACK,
162 TILE_LIFT_FRONT,
163 TILE_LIFT_SHAFT,
164 TILE_BLUE_FRONT,
165 TILE_GREEN_FRONT,
167 TILE_LINK_0 = 30,
168 TILE_LINK_1,
169 TILE_LINK_2,
170 TILE_LINK_3,
171 TILE_LINK_4,
172 TILE_LINK_5,
173 TILE_GREEN_FRAGMENT,
174 TILE_GREEN_FRAGMENT_1,
175 TILE_GREEN_FRAGMENT_2,
176 TILE_ITEM2,
178 TILE_WATER_MAP = 40,
179 TILE_GREEN_CRACKED,
180 TILE_GREEN_CRACKED_WALL,
181 TILE_BLUE_CRACKED,
182 TILE_BLUE_CRACKED_WALL,
183 TILE_LASER_HEAD,
184 TILE_FIRE_PARTICLE_1,
185 TILE_FIRE_PARTICLE_2,
186 TILE_WATER_PARTICLE,
188 TILE_LASER_0 = 50,
189 TILE_LASER_FADE_0 = 53,
190 TILE_BLUE_FRAGMENT = 56,
191 TILE_BLUE_FRAGMENT_1,
192 TILE_BLUE_FRAGMENT_2,
193 TILE_ITEM1,
194 TILE_LASER_REFRACT = 60,
195 TILE_ICE_LASER_REFRACT = TILE_LASER_REFRACT+6,
196 TILE_WHITE_TILE,
197 TILE_WHITE_WALL,
198 TILE_BLACK_TILE,
202 const int colours[] = {
203 #define X(n,col, solid) col,
204 #include "tiletypes.h"
207 const int tileSolid[] = {
208 #define X(n,col, solid) solid,
209 #include "tiletypes.h"
212 void ChangeSuffix(char* filename, char* newsuffix)
214 int len = strlen(filename);
215 int i = len-1;
216 while (i>=0 && filename[i]!='\\' && filename[i]!='.' && filename[i]!='/')
217 i--;
218 if (filename[i]=='.')
219 strcpy(filename+i+1, newsuffix);
220 else
222 strcat(filename, ".");
223 strcat(filename, newsuffix);
227 bool isMap=false, isRenderMap=false;
228 int isFadeRendering=0;
231 |--| |--| TILE_W1
232 |--------| TILE_W2
233 |-----| TILE_WL
234 |-----------| TILE_W3
236 *-----* - -
237 / \ |TILE_H1 |TILE_H2
238 / \ | |
239 * * - |
240 \ / |
241 \ / |
242 *-----* -
244 WL = sqrt(h1*h1 + w1*w1)
245 wl**2 = h1**2 + w1**2
247 w1 = sin60.wL
251 #if 1
252 #define TILE_W1 18
253 #define TILE_W3 64
254 #define GFX_SIZE TILE_W3
255 #define TILE_W2 (TILE_W3-TILE_W1)
256 #define TILE_H1 TILE_W1
257 #define TILE_HUP 22 //extra visible height of wall (used for determining whether a wall was clicked on)
258 #define TILE_H2 (TILE_H1*2)
259 #define TILE_WL (TILE_W2-TILE_W1)
260 #define TILE_H_LIFT_UP 26
261 #define TILE_H_REFLECT_OFFSET 24
262 #define TILE_HUP2 TILE_H_LIFT_UP // Displacement of object on top of wall
263 #define FONT_SPACING 25
264 #define FONT_X_SPACING (-1) // -1 in order to try and overlap the black borders of adjacent characters
265 #else
266 #define TILE_WL 30
267 #define TILE_W1 (TILE_WL/2)
268 #define TILE_W2 (TILE_W1+TILE_WL)
269 #define TILE_W3 (TILE_W1+TILE_W2)
270 #define TILE_H1 (TILE_WL*0.8660254037844386)
271 #define TILE_H2 (TILE_H1*2)
272 #endif
274 #define MAX_DIR 6
276 SDL_Rect tile[2][70];
277 short tileOffset[2][70][2];
278 int Peek(SDL_Surface* i, int x, int y)
280 if (x<0 || y<0 || x>=i->w || y>=i->h)
281 return 0;
282 unsigned int p=0;
283 const int BytesPerPixel = i->format->BytesPerPixel;
284 const int BitsPerPixel = i->format->BitsPerPixel;
285 if (BitsPerPixel==8)
286 p = ((unsigned char*)i->pixels)[i->pitch*y + x*BytesPerPixel];
287 else if (BitsPerPixel==15 || BitsPerPixel==16)
288 p = *(short*)(((char*)i->pixels) + (i->pitch*y + x*BytesPerPixel));
289 else if (BitsPerPixel==32)
290 p = *(unsigned int*)(((char*)i->pixels) + (i->pitch*y + x*BytesPerPixel));
291 else if (BitsPerPixel==24)
292 p = (int)((unsigned char*)i->pixels)[i->pitch*y + x*BytesPerPixel]
293 | (int)((unsigned char*)i->pixels)[i->pitch*y + x*BytesPerPixel] << 8
294 | (int)((unsigned char*)i->pixels)[i->pitch*y + x*BytesPerPixel] << 16;
296 return p;
298 bool IsEmpty(SDL_Surface* im, int x, int y, int w, int h)
300 for (int i=x; i<x+w; i++)
301 for (int j=y; j<y+h; j++)
302 if (Peek(im,i,j) != Peek(im,0,im->h-1))
303 return false;
304 return true;
307 void MakeTileInfo()
309 for (int i=0; i<140; i++)
311 SDL_Rect r = {(i%10)*GFX_SIZE, ((i/10)%7)*GFX_SIZE, GFX_SIZE, GFX_SIZE};
312 short * outOffset = tileOffset[i/70][i%70];
313 SDL_Surface * im = (i/70) ? tileGraphicsR : tileGraphics;
315 outOffset[0] = outOffset[1] = 0;
317 while (r.h>1 && IsEmpty(im, r.x, r.y, r.w, 1)) r.h--, r.y++, outOffset[1]++;
318 while (r.h>1 && IsEmpty(im, r.x, r.y+r.h-1, r.w, 1)) r.h--;
319 while (r.w>1 && IsEmpty(im, r.x, r.y, 1, r.h)) r.w--, r.x++, outOffset[0]++;
320 while (r.w>1 && IsEmpty(im, r.x+r.w-1, r.y, 1, r.h)) r.w--;
322 tile[i/70][i%70] = r;
326 void ConvertToUTF8(const std::string &text_locally_encoded, char *text_utf8, size_t text_utf8_length)
328 // Is this portable?
329 size_t text_length = text_locally_encoded.length()+1;
330 errno = 0;
331 static const char *locale_enc = gettext_init.GetEncoding();
332 iconv_t cd = iconv_open("UTF-8", locale_enc);
333 char *in_buf = const_cast<char *>(&text_locally_encoded[0]);
334 char *out_buf = &text_utf8[0];
335 iconv(cd, &in_buf, &text_length, &out_buf, &text_utf8_length);
336 iconv_close(cd);
337 if (errno != 0)
338 std::cerr << "An error occurred recoding " << text_locally_encoded << " to UTF8" << std::endl;
341 int SDLPangoTextWidth(const std::string &text_utf8);
342 void Print_Pango(int x, int y, const std::string &text_utf8);
343 void Print_Pango_Aligned(int x, int y, int width, const std::string &text_utf8, int align);
345 /// Prints a left aligned string (a single line) beginning at (x,y)
346 // TODO: Check that the maximal text width is already set
347 void Print(int x, int y, const char * string, ...)
349 va_list marker;
350 va_start( marker, string ); /* Initialize variable arguments. */
352 char tmp[1000], tmp_utf8[5000]; // FIXME: Check this limit
353 vsprintf((char*)tmp, string, marker);
355 ConvertToUTF8(tmp, tmp_utf8, sizeof(tmp_utf8)/sizeof(char));
356 Print_Pango(x, y, tmp_utf8);
358 va_end( marker ); /* Reset variable arguments. */
361 /// Prints a string right aligned so that it ends at (x,y)
362 // TODO: Check that the maximal text width is already set
363 void PrintR(int x, int y, const char * string, ...)
365 va_list marker;
366 va_start( marker, string ); /* Initialize variable arguments. */
368 char tmp[1000], tmp_utf8[5000]; // FIXME: Check this limit
369 vsprintf((char*)tmp, string, marker);
371 ConvertToUTF8(tmp, tmp_utf8, sizeof(tmp_utf8)/sizeof(char));
372 Print_Pango(x-SDLPangoTextWidth(tmp_utf8), y, tmp_utf8);
374 va_end( marker ); /* Reset variable arguments. */
377 /** \brief Prints a string horizontally centered around (x,y)
379 * " " in the string is interpreted as linebreak
381 void Print_Aligned(bool split, int x, int y, int width, const char * string, int align)
383 char tmp_utf8[5000]; // FIXME: Check this limit
385 ConvertToUTF8(string, tmp_utf8, sizeof(tmp_utf8)/sizeof(char));
387 std::string msg(tmp_utf8);
388 while (split && msg.find(" ") != std::string::npos)
389 msg.replace(msg.find(" "), 2, "\n");
391 Print_Pango_Aligned(x, y, width, msg, align);
394 void PrintC(bool split, int x, int y, const char * string, ...)
396 va_list marker;
397 va_start( marker, string ); /* Initialize variable arguments. */
399 char tmp[1000]; // FIXME: Check this limit
400 vsprintf((char*)tmp, string, marker);
402 va_end( marker ); /* Reset variable arguments. */
404 static bool print = true; // avoid flickering!
405 if (print) {
406 std::cerr << "Warning: don't know window width for message:\n" << tmp << "\n";
407 for (unsigned int i=0; i<strlen(tmp); ++i)
408 if (!std::isspace(tmp[i]))
409 print = false;
411 Print_Aligned(split, x, y, 2*std::min(x, SCREEN_W-x), tmp, 1);
414 #include "savestate.h"
415 #include "menus.h"
416 #include "level_list.h"
418 void SaveState::GetStuff()
420 general.hintFlags = HintMessage::flags;
422 void SaveState::ApplyStuff()
424 HintMessage::flags = general.hintFlags;
428 typedef int Tile;
429 typedef int Dir;
430 struct Pos{
431 int x,y;
432 Pos() : x(0), y(0) {}
433 Pos(int a, int b) : x(a), y(b) {}
434 bool operator == (Pos const & p) const
436 return x==p.x && y==p.y;
438 Pos operator + (Dir const d) const
440 return Pos(
441 x + ((d==1 || d==2) ? 1 : (d==4 || d==5) ? -1 : 0),
442 y + ((d==0 || d==1) ? -1 : (d==3 || d==4) ? 1 : 0)
445 int getScreenX() const {
446 return x*TILE_W2;
448 int getScreenY() const {
449 return x*TILE_H1 + y*TILE_H2;
451 static Pos GetFromWorld(double x, double y)
453 x += TILE_W3/2;
454 y += TILE_H1;
455 int tx, ty;
456 tx = (int)floor(x/TILE_W2);
457 y -= tx*TILE_H1;
458 ty = (int)floor(y/TILE_H2);
460 y -= ty * TILE_H2;
461 x -= tx * TILE_W2;
463 if (x < TILE_W1 && y < TILE_H1)
464 if (x*TILE_H1 + y * TILE_W1 < TILE_H1*TILE_W1)
465 tx--;
466 if (x < TILE_W1 && y > TILE_H1)
467 if (x*TILE_H1 + (TILE_H2-y) * TILE_W1 < TILE_H1*TILE_W1)
468 tx--, ty++;
470 return Pos(tx, ty);
473 Pos mousep(0,0), keyboardp(4,20);
475 class RenderObject;
477 struct RenderStage
479 virtual void Render(RenderObject* r, double time, bool reflect) = 0;
480 virtual int GetDepth(double time) { return 1; }
483 class RenderObject
485 RenderStage** stage;
486 double* time;
487 int numStages;
488 int maxStages;
489 int currentStage;
490 public:
491 double seed;
492 double currentTime;
493 private:
495 void Reserve()
497 if (maxStages <= numStages)
499 maxStages = maxStages ? maxStages*2 : 4;
500 stage = (RenderStage**) realloc(stage, sizeof(stage[0])*maxStages);
501 time = (double*) realloc(time, sizeof(time[0])*maxStages);
504 public:
505 RenderObject() : stage(0), time(0), numStages(0), maxStages(0), currentStage(0)
507 // TODO: use a random number with better range
508 // or maybe make seed an int or float...
509 seed = rand() / (double)RAND_MAX;
511 ~RenderObject()
513 free(stage); free(time);
515 bool Active(double t)
517 if (numStages==0) return false;
518 if (t < time[0]) return false;
519 return true;
521 void UpdateCurrent(double t)
523 if (currentStage >= numStages) currentStage = numStages-1;
524 if (currentStage < 0) currentStage = 0;
526 while (currentStage>0 && time[currentStage]>t)
527 currentStage--;
528 while (currentStage<numStages-1 && time[currentStage+1]<=t)
529 currentStage++;
531 currentTime = t;
533 RenderStage* GetStage(double t)
535 if (t==-1 && numStages>0)
536 return stage[numStages-1];
538 if (!Active(t)) return 0;
539 UpdateCurrent(t);
540 return stage[currentStage];
542 double GetLastTime()
544 return numStages>0 ? time[numStages-1] : -1;
546 void Render(double t, bool reflect)
548 if (!Active(t))
549 return;
550 UpdateCurrent(t);
551 stage[currentStage]->Render(this, t - time[currentStage], reflect);
553 int GetDepth(double t)
555 if (!Active(t))
556 return -1;
557 UpdateCurrent(t);
558 return stage[currentStage]->GetDepth(t - time[currentStage]);
560 void Reset(double t)
562 if (t<0)
563 numStages = currentStage = 0;
564 else
566 while (numStages > 0 && time[numStages-1] >= t)
567 numStages--;
570 void Wipe()
572 if (currentStage > 0 && numStages > 0)
574 memmove(&time[0], &time[currentStage], sizeof(time[0]) * numStages-currentStage);
575 memmove(&stage[0], &stage[currentStage], sizeof(stage[0]) * numStages-currentStage);
576 numStages -= currentStage;
577 currentStage = 0;
580 void Add(RenderStage* s, double t)
582 int i=0;
584 if (currentStage<numStages && time[currentStage]<=t)
585 i = currentStage;
587 while (i<numStages && time[i]<t)
588 i++;
590 if (i<numStages && time[i]==t)
591 stage[i]=s;
592 else
594 Reserve();
596 if (i<numStages)
598 memmove(&time[i+1], &time[i], (numStages-i) * sizeof(time[0]));
599 memmove(&stage[i+1], &stage[i], (numStages-i) * sizeof(stage[0]));
602 numStages++;
603 time[i] = t;
604 stage[i] = s;
609 class WorldRenderer
611 #define SIZE 30
612 #define FX 10
613 RenderObject tile[SIZE][SIZE][2];
614 RenderObject fx[FX];
615 int fxPos;
617 public:
618 RenderObject player;
619 RenderObject dummy;
621 WorldRenderer()
623 Reset();
626 void Reset(double t = -1)
628 fxPos = 0;
629 player.Reset(t);
630 dummy.Reset(-1);
632 for (int i=0; i<SIZE; i++)
633 for (int j=0; j<SIZE; j++)
634 for (int q=0; q<2; q++)
635 tile[i][j][q].Reset(t);
637 for (int j=0; j<FX; j++)
638 fx[j].Reset(t);
641 void Wipe()
643 player.Wipe();
644 dummy.Reset(-1);
646 for (int i=0; i<SIZE; i++)
647 for (int j=0; j<SIZE; j++)
648 for (int q=0; q<2; q++)
649 tile[i][j][q].Wipe();
651 for (int j=0; j<FX; j++)
652 fx[j].Wipe();
655 bool Visible(Pos p)
657 int x0 = (scrollX+TILE_W2) / TILE_W2;
658 int x1 = (scrollX+SCREEN_W+TILE_W3+TILE_W1) / TILE_W2;
659 if (p.x<0 || p.y<0 || p.x>=SIZE || p.y>=SIZE) return false;
660 if (p.x<x0) return false;
661 if (p.x>=x1-1) return false;
662 for (int j0=0; j0<SIZE*3; j0++)
664 if (j0 * TILE_H1 < scrollY-TILE_H1) continue;
665 if (j0 * TILE_H1 > scrollY+SCREEN_H+TILE_H1) break;
666 int i = j0&1;
667 int j = j0>>1;
668 j -= (x0-i)/2;
669 i += (x0-i)/2*2;
670 if (j>=SIZE) i+=(j+1-SIZE)*2, j=SIZE-1;
671 for (; i<x1 && j>=0; i+=2, j--)
673 if (Pos(i,j)==p)
674 return true;
677 return false;
680 void Render(double t, bool reflect)
682 dummy.Reset(-1);
684 int playerDepth = player.GetDepth(t);
685 if (reflect) playerDepth-=4;
686 if (playerDepth<0)
687 player.Render(t, reflect);
689 int x0 = (scrollX+TILE_W2) / TILE_W2;
690 int x1 = (scrollX+SCREEN_W+TILE_W3+TILE_W1) / TILE_W2;
691 x0 = MAX(x0, 0);
692 x1 = MIN(x1, SIZE);
693 for (int j0=0; j0<SIZE*3; j0++)
695 if (j0 * TILE_H1 < scrollY-TILE_H1) continue;
696 if (j0 * TILE_H1 > scrollY+SCREEN_H+TILE_H1) break;
697 int i = j0&1;
698 int j = j0>>1;
699 j -= (x0-i)/2;
700 i += (x0-i)/2*2;
701 if (j>=SIZE) i+=(j+1-SIZE)*2, j=SIZE-1;
702 for (; i<x1 && j>=0; i+=2, j--)
704 for (int q=reflect?1:0; q!=2 && q!=-1; q += (reflect ? -1 : 1))
705 if (tile[i][j][q].Active(t))
707 tile[i][j][q].Render(t, reflect);
711 if (playerDepth==j0 || j0==SIZE*3 && playerDepth>j0)
712 player.Render(t, reflect);
715 for (int j=0; j<FX; j++)
716 if(fx[j].Active(t))
718 fx[j].Render(t, reflect);
722 RenderObject & operator () ()
724 fxPos++;
725 if (fxPos==FX) fxPos = 0;
726 return fx[fxPos];
728 RenderObject & operator () (Pos const & p, bool item=false)
730 if (p.x<0 || p.y<0 || p.x>=SIZE || p.y>=SIZE)
731 return dummy;
732 return tile[p.x][p.y][item ? 1 : 0];
736 void RenderTile(bool reflect, int t, int x, int y, int cliplift)
738 SDL_Rect src = tile[reflect][t];
739 SDL_Rect dst = {x-scrollX-GFX_SIZE/2, y-scrollY-GFX_SIZE+TILE_H1};
740 dst.x += tileOffset[reflect][t][0];
741 dst.y += tileOffset[reflect][t][1];
742 if (reflect)
743 dst.y += TILE_H_REFLECT_OFFSET;
744 if (cliplift==-1 || reflect)
746 // dst.w=src.w; dst.h=src.h;
747 // SDL_FillRect(screen, &dst, rand());
748 SDL_BlitSurface(reflect ? tileGraphicsR : tileGraphics, &src, screen, &dst);
750 else
752 src.h -= cliplift;
753 if (src.h > TILE_W1)
755 src.h -= TILE_W1/2;
756 SDL_BlitSurface(tileGraphics, &src, screen, &dst);
757 src.y += src.h;
758 dst.y += src.h;
759 src.h = TILE_W1/2;
761 if (src.h > 0)
763 src.w -= TILE_W1*2, src.x += TILE_W1;
764 dst.x += TILE_W1;
765 SDL_BlitSurface(tileGraphics, &src, screen, &dst);
769 void RenderGirl(bool reflect, int r, int frame, int x, int y, int h)
771 int sx = r * 64;
772 int sy = frame * 80*2;
773 if (reflect)
774 y += TILE_H_REFLECT_OFFSET+20+h, sy += 80;
775 else
776 y -= h;
777 SDL_Rect src = {sx, sy, 64, 80};
778 SDL_Rect dst = {x-scrollX-32, y-scrollY-65};
779 SDL_BlitSurface(girlGraphics, &src, screen, &dst);
782 struct ItemRender : public RenderStage
784 int item;
785 Pos p;
786 int water;
788 ItemRender(int i2, int _water, Pos const & _p) : item(i2), p(_p), water(_water)
791 double Translate(double seed, double time)
793 double bob = time*2 + seed*PI2;
794 return sin(bob)*4;
797 void Render(RenderObject* r, double time, bool reflect)
799 if (item==0)
800 return;
802 int y = -5 + (int)Translate(r->seed, r->currentTime + time);
803 if (reflect)
804 y=-y;
805 if (!reflect && !water)
806 RenderTile( false, TILE_SPHERE, p.getScreenX(), p.getScreenY());
807 RenderTile(
808 reflect,
809 item==1 ? TILE_ITEM1 : TILE_ITEM2,
810 p.getScreenX(), p.getScreenY()+y
815 void RenderFade(double time, int dir, int seed)
817 int ys=0;
818 srand(seed);
819 for(int x=rand()%22-11; x<SCREEN_W+22; x+=32, ys ^= 1)
821 for (int y=ys*20; y<SCREEN_H+30; y+=40)
823 double a = (rand()&0xff)*dir;
824 double b = (time * 0x400 + (y - SCREEN_H) * 0x140/SCREEN_H)*dir;
825 if (a >= b)
827 RenderTile(false, TILE_BLACK_TILE, x+scrollX, y+scrollY);
833 struct FadeRender : public RenderStage
835 int seed;
836 int dir;
837 FadeRender(int d=-1) : seed(rand()), dir(d)
839 isFadeRendering = d;
842 void Render(RenderObject* r, double time, bool reflect)
844 if (reflect) return;
845 if (time > 0.5)
847 if (dir==1) dir=0, isFadeRendering=0;
848 return;
850 RenderFade(time, dir, seed);
854 struct ScrollRender : public RenderStage
856 int x,y;
857 bool done;
858 ScrollRender(int a,int b) : x(a), y(b), done(false) {}
860 void Render(RenderObject* r, double time, bool reflect)
862 if (done) return;
863 scrollX = x, scrollY = y;
864 isRenderMap = isMap;
865 done = true;
869 struct LevelSelectRender : public RenderStage
871 Pos p;
872 int item;
873 int adj;
874 #ifdef MAP_EDIT_HACKS
875 int magic;
876 #endif
878 LevelSelectRender(Pos const & _p, int i2, int adj) : p(_p), item(i2), adj(adj)
881 void Render(RenderObject* r, double time, bool reflect)
883 if (item==0)
884 return;
886 #ifndef MAP_LOCKED_VISIBLE
887 if (item==1) return;
888 #endif
890 if (!reflect && adj)
891 for (int i=0; i<MAX_DIR; i++)
892 if (adj & (1 << i))
893 RenderTile( false, TILE_LINK_0+i, p.getScreenX(), p.getScreenY());
895 if (item < 0)
896 return;
898 if (!reflect)
900 RenderTile(
901 reflect,
902 TILE_SPHERE + item-1,
903 p.getScreenX(), p.getScreenY()
906 #ifdef MAP_EDIT_HACKS
907 int x = p.getScreenX()-scrollX, y = p.getScreenY()-scrollY;
908 Print(x+5,y-25,"%d",magic);
909 #endif
914 struct ItemCollectRender : public ItemRender
916 ItemCollectRender(int i2, Pos const & p) : ItemRender(i2, 0, p)
919 void Render(RenderObject* r, double time, bool reflect)
924 int GetLiftHeight(double time, int t)
926 if (t==LIFT_UP)
927 time = LIFT_TIME-time;
928 time = time / LIFT_TIME;
929 if (time > 1)
930 time = 1;
931 if (time < 0)
932 time = 0;
933 time = (3 - 2*time)*time*time;
934 if (t==LIFT_UP)
935 time = (3 - 2*time)*time*time;
936 if (t==LIFT_UP)
937 return (int)((TILE_H_LIFT_UP+4) * time);
938 else
939 return (int)((TILE_H_LIFT_UP-4) * time) + 4;
942 struct TileRender : public RenderStage
944 int special;
945 int t;
946 Pos p;
947 double specialDuration;
949 TileRender(int i, Pos const & _p, int _special=0) : t(i), p(_p), special(_special), specialDuration(LASER_LINE_TIME)
952 void Render(RenderObject* r, double time, bool reflect)
954 if (t==0 && special==0)
955 return;
957 if (special && (t==LIFT_UP || t==LIFT_DOWN) && time<LIFT_TIME)
959 int y = GetLiftHeight(time, t);
960 if (!reflect)
962 RenderTile(reflect, TILE_LIFT_BACK, p.getScreenX(), p.getScreenY());
963 RenderTile(reflect, TILE_LIFT_SHAFT, p.getScreenX(), p.getScreenY()+y, y-8);
964 RenderTile(reflect, TILE_LIFT_FRONT, p.getScreenX(), p.getScreenY());
966 else
968 RenderTile(reflect, TILE_LIFT_SHAFT, p.getScreenX(), p.getScreenY()-y, y);
969 RenderTile(reflect, LIFT_DOWN, p.getScreenX(), p.getScreenY());
972 else if (special && (t==EMPTY || t==TRAP) && !reflect && time < specialDuration)
974 if (t == TRAP)
975 if (time < specialDuration-LASER_FADE_TIME)
976 RenderTile(reflect, TILE_ICE_LASER_REFRACT, p.getScreenX(), p.getScreenY());
977 else
978 RenderTile(reflect, t, p.getScreenX(), p.getScreenY());
979 int base = ((t==EMPTY) ? TILE_LASER_0 : TILE_LASER_REFRACT);
980 if (t==EMPTY && time >= specialDuration-LASER_FADE_TIME)
981 base = TILE_LASER_FADE_0;
983 int foo=special;
984 for(int i=0; foo; foo>>=1, i++)
985 if (foo & 1)
986 RenderTile(reflect, base+i, p.getScreenX(), p.getScreenY());
988 else if (t==FLOATING_BALL)
990 int y = int(1.8 * sin(r->seed*PI + time*4));
991 if (special==512)
993 if (time > 2) return;
994 if (reflect) return;
995 srand(int(r->seed * 0xfff));
996 for (int i=0; i<20 - int(time*10); i++)
998 int x = int((((rand() & 0xfff) - 0x800) / 10) * time);
999 int y = int((((rand() & 0xfff) - 0x800) / 10) * time);
1000 RenderTile(true, 19 + ((i+int(time*5))&1)*10, p.getScreenX() + x, p.getScreenY() - 14 + y);
1003 if (time < 0.05)
1004 RenderTile(true, 18, p.getScreenX(), p.getScreenY() - 14);
1006 else if (special)
1007 RenderBoat(reflect, int(special)&255, p.getScreenX(), p.getScreenY(), y);
1008 else
1009 RenderTile(reflect, t, p.getScreenX(), p.getScreenY() + (reflect ? -y : y));
1011 else if (t != EMPTY)
1012 RenderTile(reflect, t, p.getScreenX(), p.getScreenY());
1014 static void RenderBoat(bool reflect, int d, int x, int y, int yo)
1016 if (reflect)
1017 RenderGirl(reflect, d, 0, x, y, -yo);
1018 RenderTile(reflect, FLOATING_BALL, x, y+yo);
1019 if (!reflect)
1021 RenderGirl(reflect, d, 0, x, y, -yo);
1022 RenderTile(true, 17, x, y+yo-TILE_H_REFLECT_OFFSET);
1027 struct TileRotateRender : public TileRender
1029 Dir d;
1030 // int range;
1031 int mode;
1032 TileRotateRender(int i, Pos const & p, Dir _d, int m) : TileRender(i, p), d(_d), mode(m)
1034 void Render(RenderObject* r, double time, bool reflect)
1036 if (t==0)
1037 return;
1038 double f = time / ROTATION_TIME;
1040 if (mode & 1) f += 0.5;
1041 if (f<1 && f>0)
1043 if (mode & 2)
1045 else
1046 f = (3-2*f)*f*f;
1049 if (mode & 1) f=1-f; else f=f;
1050 if (f<0) f=0;
1052 if (f >= 1)
1053 TileRender::Render(r, time, reflect);
1054 else
1056 Pos dd = (Pos(0,0)+d);
1057 int x = p.getScreenX() + int(dd.getScreenX()*(f));
1058 int y = p.getScreenY() + int(dd.getScreenY()*(f));
1060 if (mode & 2)
1061 RenderBoat(reflect, (mode&1) ? (d+MAX_DIR/2)%MAX_DIR : d, x, y, 2);
1062 else
1063 RenderTile(reflect, t, x, y);
1068 struct LaserRender : public RenderStage
1070 Pos p;
1071 Dir d;
1072 int range;
1074 LaserRender(Pos _p, int dir, int r) : p(_p), d(dir), range(r)
1077 void Render(RenderObject* r, double time)
1082 struct ExplosionRender : public RenderStage
1084 Pos p;
1085 int seed;
1086 int power;
1087 int type;
1089 ExplosionRender(Pos _p, int _pow=0, int t=0) : p(_p), power(_pow), type(t)
1091 seed = rand();
1094 virtual int GetDepth(double time)
1096 return p.x + p.y*2;
1099 void Render(RenderObject* r, double time, bool reflect)
1101 if (type==1 && time > 2.5)
1102 type = -1, new WinLoseScreen(false);
1104 // if (reflect) return;
1105 if (time > 3) return;
1106 srand(seed);
1107 int q = 50 - int(time * 35);
1108 if (power) q*=2;
1109 if (type) q = 50;
1110 for (int i=0; i<q; i++)
1112 int x = p.getScreenX();
1113 int y = p.getScreenY() + (rand() & 31)-16;
1114 int xs = ((rand() & 63) - 32);
1115 int ys = (-10 - (rand() & 127)) * (1+power);
1116 if (type) ys*=2, xs/=2;
1117 x += int(xs * (1+time*(2+power)));
1118 int yo = int(time*time*128 + ys*time);
1119 //if (yo > 0) yo=-yo;//continue;
1120 if (type)
1123 if (yo > 0)
1125 if (!reflect && ys<-60)
1127 const double T = 0.06;
1128 double ct = -ys / 128.0;
1129 if (time < ct+T*4)
1131 x = p.getScreenX() + int(xs * (1+ct*(2+power)));
1132 RenderTile(
1133 reflect,
1134 time > ct+3*T ? TILE_SPLASH_3 : time > ct+2*T ? TILE_SPLASH_2 : time > ct+T ? TILE_SPLASH_1 : TILE_WATER_PARTICLE+1,
1135 x, y);
1139 else
1140 RenderTile(
1141 reflect,
1142 time - i*0.003 < 0.2 ? TILE_WATER_PARTICLE+1 : TILE_WATER_PARTICLE,
1143 x, y+(reflect?-1:1)*yo);
1145 else
1147 if (yo > 0)
1149 else
1150 RenderTile(
1151 reflect,
1152 i<q-20 || time<0.3 ? TILE_LASER_HEAD : i<q-10 || time<0.6 ? TILE_FIRE_PARTICLE_1 : TILE_FIRE_PARTICLE_2,
1153 x, y+(reflect?-1:1)*yo);
1158 struct DisintegrateRender : public RenderStage
1160 Pos p;
1161 int seed;
1162 int height;
1163 int type;
1165 DisintegrateRender(Pos _p, int _pow=0, int _t=0) : p(_p), height(_pow), type(_t)
1167 seed = rand();
1170 void Render(RenderObject* r, double time, bool reflect)
1172 if (type)
1173 RenderTile(reflect, height ? COLLAPSE_DOOR : COLLAPSABLE, p.getScreenX(), p.getScreenY());
1175 if (time > 50.0/70.0) return;
1176 if (reflect) return;
1177 srand(seed);
1178 int q = 50 - int(time * 70);
1179 if (height) q*=2;
1180 for (int i=0; i<q; i++)
1182 int x = (rand() % (TILE_W3-8))-TILE_W3/2+4;
1183 int y = (rand() % (TILE_H2-8))-TILE_H1+4;
1184 if (x<-TILE_WL/2 && ABS(y)<-TILE_WL/2-x) continue;
1185 if (x>TILE_WL/2 && ABS(y)>x-TILE_WL/2) continue;
1186 int yo=0;
1187 if (height) yo -= rand() % TILE_HUP;
1188 x += p.getScreenX();
1189 y += p.getScreenY() + 4;
1190 int xs = 0;//((rand() & 63) - 32);
1191 int ys = (- (rand() & 31));
1192 x += int(xs * (1+time*(2)));
1193 if (type) yo = -yo;
1194 yo += int(time*time*128 + ys*time);
1195 if (type) yo = -yo*2;
1196 //if (yo > 0) yo=-yo;//continue;
1197 int t = type ? TILE_BLUE_FRAGMENT : TILE_GREEN_FRAGMENT;
1198 if (i>q-20) t++;
1199 if (i>q-10) t++;
1200 if (yo > 5) yo = 5;
1201 RenderTile(false, t, x, y+(reflect?-yo:yo));
1205 struct BuildRender : public RenderStage
1207 Pos p;
1208 Dir dir;
1209 int reverse;
1210 int height;
1211 int type;
1213 BuildRender(Pos _p, Dir _d, int _h, int _r=0, int _type=0) : p(_p), dir(_d), height(_h), reverse(_r), type(_type)
1217 void Render(RenderObject* r, double time, bool reflect)
1219 if (time >= BUILD_TIME)
1220 RenderTile(reflect, height ^ reverse ? (type ? COLLAPSE_DOOR2 : COLLAPSE_DOOR) : (type ? COLLAPSABLE2 : COLLAPSABLE), p.getScreenX(), p.getScreenY());
1221 else
1223 if (height)
1224 RenderTile(reflect, type ? COLLAPSABLE2 : COLLAPSABLE, p.getScreenX(), p.getScreenY());
1226 double dist = time * 2 / BUILD_TIME;
1227 if (dir>-1)
1229 Pos from = p + ((dir+MAX_DIR/2)%MAX_DIR);
1230 if (dist <= 1)
1231 //if (dist > 1)
1233 double offset = (dist*0.7) + 0.3;
1234 int x = from.getScreenX() + int((p.getScreenX()-from.getScreenX()) * offset);
1235 int y = from.getScreenY() + int((p.getScreenY()-from.getScreenY()) * offset - dist*(1-dist)*(TILE_HUP*4));
1236 RenderTile(reflect, TILE_GREEN_FRAGMENT, x, y);
1238 dist -= 1;
1240 else
1242 if (reverse) dist = 1-dist;
1244 if (dist > 0 && !height)
1246 if (!reflect)
1247 for (int i=0; i<=int(dist*15); i++)
1249 int x = p.getScreenX(), y = p.getScreenY();
1250 double d = (i + fmod(dist*15, 1))/10.0;
1251 int x1 = int(sin(d*5+time)*MIN(d,1)*TILE_W2/2);
1252 int y1 = int(cos(d*5+time)*MIN(d,1)*TILE_H1*0.7);
1253 RenderTile(reflect, TILE_GREEN_FRAGMENT, x+x1, y+y1+4);
1254 RenderTile(reflect, TILE_GREEN_FRAGMENT, x-x1, y-y1+4);
1257 if (dist > 0 && height)
1259 int yo = int((1-dist)*(TILE_HUP*1.3));
1260 if (yo > TILE_HUP*1.1)
1261 RenderTile(reflect, TILE_WHITE_TILE, p.getScreenX(), p.getScreenY());
1262 else if (!reflect)
1264 RenderTile(reflect, type ? COLLAPSABLE2 : COLLAPSABLE, p.getScreenX(), p.getScreenY());
1265 RenderTile(reflect, type ? COLLAPSE_DOOR2 : COLLAPSE_DOOR, p.getScreenX(), p.getScreenY()+(reflect ? -yo : yo), yo+6);
1266 RenderTile(reflect, type ? TILE_BLUE_FRONT : TILE_GREEN_FRONT, p.getScreenX(), p.getScreenY());
1268 else
1270 if (yo < TILE_HUP/2)
1272 RenderTile(reflect, type ? COLLAPSE_DOOR2 : COLLAPSE_DOOR, p.getScreenX(), p.getScreenY()+(reflect ? -yo : yo), yo);
1275 RenderTile(reflect, type ? COLLAPSABLE2 : COLLAPSABLE, p.getScreenX(), p.getScreenY());
1283 struct PlayerRender : public RenderStage
1285 Pos p;
1286 Pos target;
1287 int p_h, target_h;
1288 int r;
1289 int type;
1290 double speed;
1291 bool dead;
1293 PlayerRender(Pos a, int h, bool d) : p(a), dead(d), target(a), speed(0), p_h(h), target_h(h), r(3), type(0)
1295 PlayerRender(int _r, Pos a, int h1, Pos t, int h2, bool d) : p(a), dead(d), target(t), speed(JUMP_TIME), p_h(h1), target_h(h2), r(_r), type(0)
1297 int dist = MAX(ABS(p.x-target.x), ABS(p.y-target.y));
1298 if (dist > 1)
1299 speed *= 1.5;
1300 if(dist==0)
1301 speed = 0;
1304 virtual int GetDepth(double time)
1306 double f = speed ? time / speed : 1;
1307 if (f>1) f=1;
1308 if (f==1) dead = this->dead;
1310 double x = p.x + (target.x - p.x) * f;
1311 double y = p.y + (target.y - p.y) * f;
1313 if (f==1 || f>0.5 && p_h>target_h)
1314 return target.x+target.y*2;
1315 return MAX(target.x+target.y*2 , p.x+p.y*2);
1318 void Render(RenderObject* ro, double time, bool reflect)
1320 bool dead = false;
1321 double f = speed ? time / speed : 1;
1322 if (f>1) f=1;
1323 if (f==1) dead = this->dead;
1325 int x = p.getScreenX();
1326 int y = p.getScreenY();
1327 int x2 = target.getScreenX();
1328 int y2 = target.getScreenY();
1329 int h = 0;
1330 int shadow_h = (int)((p_h+(target_h-p_h)*f)*TILE_HUP2);
1332 if (x==x2 && y==y2 && p_h!=target_h)
1334 h = TILE_H_LIFT_UP - GetLiftHeight(time, p_h ? LIFT_DOWN : LIFT_UP);
1336 else
1339 int dist = MAX(ABS(p.x-target.x), ABS(p.y-target.y));
1340 int arc = dist*dist;
1341 int h1 = p_h * TILE_HUP2;;
1342 int h2 = target_h * TILE_HUP2;
1343 if (dist==2 && h1!=0)
1345 arc += h2 ? 1 : 3;
1346 h1 = 0;
1347 shadow_h = f>=0.7 ? int(shadow_h*(f-0.7)/0.3) : 0;
1349 if (dist==0)
1350 arc = speed > JUMP_TIME ? 7 : 2;
1352 h = (int)(h1+(h2-h1)*f);
1353 // if (x==x2 && y==y2)
1354 // ;
1355 // else
1357 //h += int(TILE_H_LIFT_UP/3 * (1-f));
1358 h += (int)(f*(1-f)*TILE_HUP2*arc);
1361 if (type==2)
1362 h=0;
1365 if (!dead)
1367 int frame = 0;
1368 if (type==2 && f<1)
1370 //frame = ((int)(f*4) % 4);
1371 //if (frame==2) frame=0; else if (frame==3) frame=2;
1372 frame = 0;
1374 else if (f==1 || x==x2 && y==y2) // stationary
1375 frame = 0;
1376 else if (f > 0.7)
1377 frame = 0;
1378 else
1380 frame = type ? 2 : 1;
1381 if (f<0.1 || f>0.6)
1382 frame += 2;
1385 if (!reflect)
1386 RenderTile( false, TILE_SPHERE,
1387 (int)(x+(x2-x)*f),
1388 (int)(y+(y2-y)*f) - shadow_h
1391 RenderGirl(
1392 reflect,
1393 r, frame,
1394 (int)(x+(x2-x)*f),
1395 (int)(y+(y2-y)*f),
1400 /* RenderTile(
1401 dead ? TILE_SPHERE_OPEN : TILE_SPHERE_DONE,
1402 (int)(x+(x2-x)*f),
1403 (int)(y+(y2-y)*f),
1404 true
1405 );*/
1410 struct HexPuzzle : public State
1412 struct Undo
1414 #define MAX_TILECHANGE 64 // TODO: don't have a magic upper limit
1415 struct TileChange
1417 Pos p;
1418 Tile t;
1419 int item;
1421 TileChange()
1423 TileChange(Pos _p, Tile _t, int _i) : p(_p), t(_t), item(_i)
1425 void Restore(HexPuzzle* w)
1427 w->SetTile(p,t,false,false);
1428 w->SetItem(p,item,false,false);
1432 TileChange t[MAX_TILECHANGE];
1433 Pos playerPos;
1434 Dir playerMovement;
1435 int numT;
1436 int numItems[2];
1437 int score;
1438 double time;
1439 double endTime;
1441 void Add(TileChange const & tc)
1443 for (int i=0; i<numT; i++)
1444 if (t[i].p==tc.p)
1445 return;
1446 if (numT>=MAX_TILECHANGE)
1447 FATAL("numT>=MAX_TILECHANGE");
1448 else
1449 t[numT++] = tc;
1451 void New(Dir pmove, Pos & pp, int* items, double t, int sc)
1453 numItems[0] = items[0];
1454 numItems[1] = items[1];
1455 playerPos = pp;
1456 playerMovement = pmove;
1457 score = sc;
1458 time = t;
1459 numT = 0;
1461 void Restore(HexPuzzle* w)
1463 for (int i=numT-1; i>=0; i--)
1464 t[i].Restore(w);
1465 w->dead = false;
1466 w->win = false;
1467 w->player = playerPos;
1468 w->player_items[0] = numItems[0];
1469 w->player_items[1] = numItems[1];
1470 w->player_score = score;
1472 //w->renderer.player.Add(new PlayerRender(playerPos, w->GetHeight(playerPos), false), w->time);
1476 #define MAP_SIZE 30
1477 char* special[MAP_SIZE][MAP_SIZE];
1478 Tile map[MAP_SIZE][MAP_SIZE];
1479 int map_item[MAP_SIZE][MAP_SIZE];
1480 int tileCount[NumTileTypes];
1481 int levelPar, levelDiff;
1482 int turboAnim;
1483 Pos player;
1484 int player_items[2];
1485 int player_score;
1486 int numComplete, numLevels, numMastered, numLevelsFound;
1487 bool dead;
1488 bool win;
1489 int winFinal;
1491 SaveState progress;
1493 WorldRenderer renderer;
1494 double time;
1495 double undoTime;
1497 #define MAX_UNDO 6
1498 Undo undo[MAX_UNDO];
1499 int numUndo;
1500 LevelInfo* currentLevelInfo;
1502 char currentFile[1000];
1504 ~HexPuzzle()
1506 FreeGraphics();
1509 LevelInfo* GetLevelInfo(const char* f)
1511 if (strstr(f, "Levels\\") == f)
1512 f += 7;
1513 if (currentLevelInfo!=0 && strcmp(currentLevelInfo->file, f)==0)
1514 return currentLevelInfo;
1516 if (f[0]=='_')
1518 int t = atoi(f+1);
1519 if (t <= numComplete)
1520 return 0;
1522 static char tmp1[1000];
1523 static LevelInfo tmp = {0, "", tmp1};
1524 sprintf(tmp1, ngettext("Complete 1 more level to unlock!", "Complete %d more levels to unlock!", t-numComplete), t-numComplete);
1525 return &tmp;
1528 for (int i=0; i<sizeof(levelNames)/sizeof(levelNames[0]); i++)
1529 if (strcmp(f, levelNames[i].file)==0)
1530 return &levelNames[i];
1531 static LevelInfo tmp = {0, "", _("<<NO NAME>>")};
1532 return &tmp;
1535 #ifdef MAP_EDIT_HACKS
1536 int GetAutoTile(const char * level, bool tiletype)
1538 FILE* f = file_open(filename, "rb");
1539 int tile = EMPTY;
1540 int version;
1542 if (f && fscanf(f, "%d", &version)==1 && (version==3 || version==4))
1544 if (strstr(level,"mk"))
1545 level+=0;
1547 fgetc(f); // Remove '\n' character
1549 int par, diff;
1550 unsigned char bounds[4];
1551 Pos playerStart;
1552 fread(&par, sizeof(par), 1, f);
1553 if (version >= 4)
1554 fread(&diff, sizeof(diff), 1, f);
1555 fread(bounds, sizeof(bounds), 1, f);
1556 fread(&playerStart, sizeof(player), 1, f);
1558 int highval=0;
1560 for (int i=bounds[0]; i<=bounds[1]; i++)
1561 for (int j=bounds[2]; j<=bounds[3]; j++)
1563 unsigned char comp = map[i][j] | (map_item[i][j]<<5);
1564 fread(&comp, sizeof(comp), 1, f);
1565 int t = comp & 0x1f;
1566 int item = (comp >> 5) & 3;
1567 for (int i=highval+1; i<sizeof(value_order)/sizeof(value_order[0]); i++)
1568 if (t!=0 && t==value_order[i]
1569 || item!=0 && item==(value_order[i]>>8))
1570 highval = i;
1573 if (tiletype)
1575 tile = value_order[highval];
1576 if (tile==0x100) tile = COLLAPSABLE3;
1577 if (tile==0x200) tile = SWITCH;
1578 if (tile==LIFT_UP) tile = LIFT_DOWN;
1580 else
1582 if (value_order[highval] == LIFT_UP)
1583 tile = highval-1;
1584 else
1585 tile = highval;
1588 else
1590 level+=0;
1592 if (f)
1593 fclose(f);
1594 return tile;
1596 #endif
1598 void InitSpecials()
1600 numComplete = numLevels = numMastered = numLevelsFound = 0;
1601 for (int i=0; i<MAP_SIZE; i++)
1602 for (int j=0; j<MAP_SIZE; j++)
1603 ActivateSpecial(Pos(i,j), 0);
1604 for (int i=0; i<MAP_SIZE; i++)
1605 for (int j=0; j<MAP_SIZE; j++)
1606 ActivateSpecial(Pos(i,j), 2);
1607 numComplete = numLevels = numMastered = numLevelsFound = 0;
1608 for (int i=0; i<MAP_SIZE; i++)
1609 for (int j=0; j<MAP_SIZE; j++)
1610 ActivateSpecial(Pos(i,j), 0);
1613 void DoHints()
1615 #ifndef EDIT
1616 if (strcmp(mapname, currentFile)==0)
1618 // for (int i=0; i<32; i++)
1619 // HintMessage::FlagTile(i);
1620 if (numComplete >= UNLOCK_SCORING && !progress.general.scoringOn)
1622 HintMessage::FlagTile(26);
1623 progress.general.scoringOn = 1;
1624 InitSpecials(); // Re-initialise with gold ones available
1626 HintMessage::FlagTile(25);
1628 else
1630 for (int i=0; i<MAP_SIZE; i++)
1631 for (int j=0; j<MAP_SIZE; j++)
1633 int t = GetTile(Pos(i,j));
1634 int item = GetItem(Pos(i,j));
1635 if (t)
1636 HintMessage::FlagTile(t);
1637 if (item)
1638 HintMessage::FlagTile(item+20);
1640 HintMessage::FlagTile(EMPTY);
1642 #endif
1643 hintsDone = true;
1645 void ResetLevel()
1647 hintsDone = false;
1649 UpdateCursor(Pos(-1,-1));
1651 isMap = false;
1653 player_score = 0;
1655 numUndo = 0;
1656 undoTime = -1;
1658 dead = false;
1659 win = false;
1660 winFinal = false;
1661 player_items[0] = player_items[1] = 0;
1662 // time = 0;
1663 if (strlen(currentSlot) == 0)
1665 new TitleMenu();
1666 new Fader(1, -3);
1668 else
1670 if (!isFadeRendering && time!=0)
1672 renderer().Add(new FadeRender(-1), time);
1673 time += 0.5;
1677 // Reset renderer
1678 renderer.Reset(time);
1679 renderer.Wipe();
1681 for (int t=0; t<NumTileTypes; t++)
1682 tileCount[t] = 0;
1684 for (int i=0; i<MAP_SIZE; i++)
1685 for (int j=0; j<MAP_SIZE; j++)
1687 Pos p(i,j);
1688 int item = GetItem(p);
1689 //if (item)
1690 renderer(p,true).Add(new ItemRender(item, GetTile(p)==EMPTY, p), time);
1693 InitSpecials();
1695 for (int i=0; i<MAP_SIZE; i++)
1696 for (int j=0; j<MAP_SIZE; j++)
1698 Pos p(i,j);
1699 int t = GetTile(p);
1700 tileCount[t]++;
1702 if (isMap)
1703 t = EMPTY;
1705 //if (t)
1706 renderer(p).Add(new TileRender(t, p), time);
1709 if (!isMap)
1710 renderer.player.Add(new PlayerRender(player, GetHeight(player), dead), time);
1711 else
1712 renderer.player.Add(new PlayerRender(Pos(-100,-100), 0, true), time);
1715 int bounds[4] = {player.getScreenX(),player.getScreenX(),player.getScreenY(),player.getScreenY()};
1716 for (int i=0; i<MAP_SIZE; i++)
1717 for (int j=0; j<MAP_SIZE; j++)
1719 Pos p(i,j);
1720 if (map[i][j] !=0 || map_item[i][j]!=0)
1722 int x1 = p.getScreenX();
1723 int y1 = p.getScreenY();
1724 int x2 = x1 + TILE_W3;
1725 int y2 = y1 + TILE_H2;
1726 y1 -= TILE_H2; // Make sure objects/player will be properly visible
1728 if (x1<bounds[0]) bounds[0] = x1;
1729 if (x2>bounds[1]) bounds[1] = x2;
1730 if (y1<bounds[2]) bounds[2] = y1;
1731 if (y2>bounds[3]) bounds[3] = y2;
1735 int sx, sy;
1736 if (isMap)
1738 sx = bounds[0] - int(TILE_W2*6.35);
1739 sy = (bounds[3] + bounds[2] - SCREEN_H) / 2 - TILE_H2/2;
1741 else
1743 sx = (bounds[1] + bounds[0] - SCREEN_W) / 2 - TILE_W3/2;
1744 sy = (bounds[3] + bounds[2] - SCREEN_H) / 2 - TILE_H2/2;
1746 if (isMap)
1748 initScrollX = sx;
1749 initScrollY = sy;
1750 if (mapScrollX==0)
1751 mapScrollX = sx;
1752 else
1753 sx = mapScrollX;
1756 // time = 1; // Guarantee we can't try and do things at time=0
1758 renderer().Add(new ScrollRender(sx, sy), time);
1759 renderer().Add(new FadeRender(1), time);
1760 if (time != 0)
1761 time -= 0.5;
1764 char* ReadAll(FILE* f)
1766 int size;
1767 fseek(f, 0, SEEK_END);
1768 size = ftell(f);
1769 fseek(f, 0, SEEK_SET);
1770 char* c = loadPtr = new char [size];
1771 endLoad = loadPtr + size;
1772 fread(c, 1, size, f);
1773 return c;
1776 static char *loadPtr, *endLoad;
1777 static unsigned int fread_replace(void* d, unsigned int size, unsigned int num, FILE*)
1779 unsigned int remain = (endLoad - loadPtr) / size;
1780 if (remain < num) num = remain;
1781 memcpy(d, loadPtr, size*num);
1782 loadPtr += size*num;
1783 return num;
1786 int GetPar(const char * level, bool getdiff=false)
1788 if (strcmp(level, currentFile)==0)
1789 return getdiff ? levelDiff : levelPar;
1791 #ifdef USE_LEVEL_PACKFILE
1792 PackFile1::Entry* e = levelFiles.Find(level);
1793 if (!e) return 999;
1794 loadPtr = (char*)e->Data();
1795 endLoad = loadPtr + e->DataLen();
1796 FILE* f = 0;
1797 #else
1798 loadPtr = 0;
1799 FILE* f = file_open(level, "rb");
1800 #endif
1802 typedef unsigned int _fn(void*, unsigned int, unsigned int, FILE*);
1803 _fn * fn = (loadPtr ? (_fn*)fread_replace : (_fn*)fread);
1805 int par = 99999, diff = 0;
1806 int version;
1808 if (!f && !loadPtr)
1809 return getdiff ? diff : par;
1811 fn(&version, 2, 1, f); // skip to relevant point
1813 if (fn(&par, sizeof(par), 1, f) != 1)
1814 par = 99999;
1815 if (fn(&diff, sizeof(diff), 1, f) != 1 || diff<0 || diff>10)
1816 diff = 0;
1818 #ifdef USE_LEVEL_PACKFILE
1819 loadPtr = endLoad = 0;
1820 #else
1821 if (f)
1822 fclose(f);
1823 #endif
1825 return getdiff ? diff : par;
1828 bool LoadSave(const char * filename, bool save)
1830 if (!filename)
1831 return false;
1833 if (!save)
1835 showScoring = false;
1836 LevelSave* l = progress.GetLevel(filename, true);
1837 if (progress.general.scoringOn && l && l->Completed() )
1838 showScoring = true;
1841 #ifdef USE_LEVEL_PACKFILE
1842 if (!save)
1844 PackFile1::Entry* e = levelFiles.Find(filename);
1845 if (!e) return false;
1847 strcpy(currentFile, filename);
1848 currentLevelInfo = GetLevelInfo(currentFile);
1850 loadPtr = (char*)e->Data();
1851 endLoad = loadPtr + e->DataLen();
1852 _LoadSave(NULL, save);
1853 loadPtr = endLoad = 0;
1855 return true;
1857 #else
1858 loadPtr = 0;
1859 FILE* f = file_open(filename, save ? "wb" : "rb");
1860 if (f)
1862 strcpy(currentFile, filename);
1863 if (!save)
1864 currentLevelInfo = GetLevelInfo(currentFile);
1866 if (!save)
1868 char* data = ReadAll(f);
1869 _LoadSave(f, save);
1870 delete [] data;
1871 loadPtr = endLoad = 0;
1873 else
1875 _LoadSave(f, save);
1877 fclose(f);
1879 return true;
1881 #endif
1883 return false;
1886 void _LoadSave(FILE* f, bool save)
1888 typedef unsigned int _fn(void*, unsigned int, unsigned int, FILE*);
1889 _fn * fn = save ? (_fn*)fwrite : (loadPtr ? (_fn*)fread_replace : (_fn*)fread);
1891 #define VERSION 4
1892 int version = VERSION;
1893 if (save)
1894 fprintf(f, "%d\n", version);
1895 else
1897 char c;
1898 if (fn(&c, 1, 1, f) != 1)
1899 return;
1900 version = c-'0';
1902 // Remove '\n' character
1903 fn(&c, 1, 1, f);
1906 if (!save)
1908 for (int i=0; i<MAP_SIZE; i++)
1909 for (int j=0; j<MAP_SIZE; j++)
1911 delete [] special[i][j];
1912 special[i][j] = 0;
1916 if (version==1)
1918 for (int i=0; i<MAP_SIZE; i++)
1919 for (int j=0; j<MAP_SIZE; j++)
1920 fn(&map[i][j], sizeof(map[i][j]), 1, f);
1922 fn(&player, sizeof(player), 1, f);
1924 if (fn(map_item, sizeof(map_item), 1, f) == 0)
1925 memset(map_item, 0, sizeof(map_item));
1927 else if (version>=2 && version<=4)
1929 unsigned char bounds[4];
1930 if (save)
1932 bounds[0]=bounds[1]=player.x;
1933 bounds[2]=bounds[3]=player.y;
1934 for (int i=0; i<MAP_SIZE; i++)
1935 for (int j=0; j<MAP_SIZE; j++)
1936 if (map[i][j] !=0 || map_item[i][j]!=0 || special[i][j]!=0)
1938 if (i<bounds[0]) bounds[0] = i;
1939 if (i>bounds[1]) bounds[1] = i;
1940 if (j<bounds[2]) bounds[2] = j;
1941 if (j>bounds[3]) bounds[3] = j;
1944 else
1946 memset(map, 0, sizeof(map));
1947 memset(map_item, 0, sizeof(map_item));
1950 if (version>=3)
1951 fn(&levelPar, 1, sizeof(levelPar), f);
1952 else if (!save)
1953 levelPar = 0;
1955 if (version>=4)
1956 fn(&levelDiff, 1, sizeof(levelDiff), f);
1957 else if (!save)
1958 levelDiff = 0;
1960 fn(bounds, sizeof(bounds), 1, f);
1961 fn(&player, sizeof(player), 1, f);
1963 int offsetx=0, offsety=0;
1965 if (!save && bounds[1]-bounds[0]<15) // Hacky - don't recenter map...
1967 // Re-position map to top left (but leave a bit of space)
1968 // (This ensures the laser/boat effects don't clip prematurely against the edges of the screen)
1969 offsetx = SCREEN_W/2/TILE_W2 + 1 - (bounds[0]+bounds[1]/2);
1970 offsety = SCREEN_H/2/TILE_H2 + SCREEN_W/2/TILE_W2 - (bounds[2]+bounds[3]/2);
1971 offsetx = MAX(0, offsetx);
1972 offsety = MAX(0, offsety);
1973 // if (bounds[0] > 2)
1974 // offsetx = 2 - bounds[0];
1975 // if (bounds[2] > 2)
1976 // offsety = 2 - bounds[2];
1978 bounds[0] += offsetx;
1979 bounds[1] += offsetx;
1980 bounds[2] += offsety;
1981 bounds[3] += offsety;
1982 player.x += offsetx;
1983 player.y += offsety;
1985 for (int i=bounds[0]; i<=bounds[1]; i++)
1986 for (int j=bounds[2]; j<=bounds[3]; j++)
1988 unsigned char comp = map[i][j] | (map_item[i][j]<<5);
1989 fn(&comp, sizeof(comp), 1, f);
1990 map[i][j] = comp & 0x1f;
1991 map_item[i][j] = (comp >> 5) & 3;
1994 if (save)
1996 for (int i=bounds[0]; i<=bounds[1]; i++)
1997 for (int j=bounds[2]; j<=bounds[3]; j++)
1998 if (special[i][j])
2000 short len = strlen(special[i][j]);
2001 unsigned char x=i, y=j;
2002 fn(&x, sizeof(x), 1, f);
2003 fn(&y, sizeof(y), 1, f);
2004 fn(&len, sizeof(len), 1, f);
2005 fn(special[i][j], 1, len, f);
2008 else
2010 while(1){
2011 short len;
2012 unsigned char x, y;
2013 if (!fn(&x, sizeof(x), 1, f))
2014 break;
2015 fn(&y, sizeof(y), 1, f);
2016 x += offsetx; y += offsety;
2017 fn(&len, sizeof(len), 1, f);
2018 if (len<0) break;
2019 char* tmp = new char[len+1];
2020 tmp[len] = 0;
2021 fn(tmp, 1, len, f);
2023 SetSpecial(Pos(x,y), tmp, true, false);
2027 else
2028 return; // Unsupported version!
2030 ResetLevel();
2032 // Save when returning to map!
2033 if (isMap)
2035 progress.general.completionPercentage = numComplete*100/numLevels;
2036 progress.general.masteredPercentage = numMastered*100/numLevels;
2037 LoadSaveProgress(true);
2041 void SetTile(Pos const & p, Tile t, bool updateRenderer=true, bool undoBuffer=true)
2043 if (p.x<0 || p.x>MAP_SIZE)
2044 return;
2045 if (p.y<0 || p.y>MAP_SIZE)
2046 return;
2047 if (map[p.x][p.y] == t)
2048 return;
2049 if (map[p.x][p.y] == t)
2050 return;
2052 tileCount[map[p.x][p.y]]--;
2053 tileCount[t]++;
2055 if (undoBuffer)
2056 undo[numUndo].Add(Undo::TileChange(p,GetTile(p),GetItem(p)));
2058 map[p.x][p.y] = t;
2060 if (updateRenderer)
2061 renderer(p).Add(new TileRender(t, p), time);
2064 Tile GetTile(Pos const & p)
2066 if (p.x<0 || p.x>=MAP_SIZE)
2067 return EMPTY;
2068 if (p.y<0 || p.y>=MAP_SIZE)
2069 return EMPTY;
2070 return map[p.x][p.y];
2073 int GetHeight(Pos const & p)
2075 return tileSolid[GetTile(p)]==1;
2078 char* GetSpecial(Pos const & p)
2080 if (p.x<0 || p.x>=MAP_SIZE)
2081 return NULL;
2082 if (p.y<0 || p.y>=MAP_SIZE)
2083 return NULL;
2084 return special[p.x][p.y];
2087 void SetSpecial(Pos const & p, char * d, bool use_pointer=false, bool auto_activate=true)
2089 if (p.x<0 || p.x>=MAP_SIZE || p.y<0 || p.y>=MAP_SIZE)
2091 if (use_pointer)
2092 delete [] d;
2093 return;
2096 delete [] special[p.x][p.y];
2097 if (!use_pointer && d)
2100 special[p.x][p.y] = new char [strlen(d) + 1];
2101 strcpy(special[p.x][p.y], d);
2103 else
2104 special[p.x][p.y] = d;
2106 if (special[p.x][p.y]==0)
2107 renderer(p,true).Add(new ItemRender(GetItem(p), GetTile(p)==EMPTY, p), time);
2108 else if (auto_activate)
2109 ActivateSpecial(p, 0);
2112 int GetLevelState(Pos const & p, int recurse=0)
2114 char* x = GetSpecial(p);
2115 if (!x) return 0;
2117 LevelSave* l = progress.GetLevel(x, false);
2119 int t = 1;
2121 if (strcmp(x, STARTING_LEVEL)==0)
2122 t = 2;
2123 if (x[0]=='_' && l && l->unlocked)
2124 t=3;
2126 if (l && l->Completed())
2128 t = 3;
2130 if (recurse)
2131 return t;
2133 int par = GetPar(x);
2134 if (progress.general.scoringOn && l->PassesPar( par ))
2135 t = 4;
2137 if (recurse)
2138 return t;
2140 int adj=0;
2141 for (Dir d=0; d<MAX_DIR; d++)
2143 int i = GetLevelState(p+d, 1);
2144 // if (i>1 || i==1 && t>1)
2145 if (i>=1 && t>2 || t>=1 && i>2)
2147 adj |= 1<<d;
2148 if (t==1)
2149 t = 2;
2153 return t | adj<<8;
2156 void ActivateSpecial(Pos const & p, int type)
2158 if (p.x<0 || p.x>=MAP_SIZE || p.y<0 || p.y>=MAP_SIZE)
2159 return;
2161 char * x = special[p.x][p.y];
2163 if (x==0 || x[0]==0)
2164 return;
2166 if (type==2 && x[0]=='_') // Phase2 init - unlock
2168 int t = GetLevelState(p);
2169 int target = atoi(x+1), targetM = 0;
2170 if (target>1000) targetM=target=target-100;
2171 if (t > 1 && numComplete >= target && numMastered >= targetM)
2173 LevelSave* l = progress.GetLevel(x, true);
2174 if (!l->unlocked)
2176 l->unlocked = true;
2178 renderer(p, true).Add(new LevelSelectRender(p, 5, GetLevelState(p)>>8), time+0.01);
2179 renderer().Add(new ExplosionRender(p, 0), time + 0.6);
2180 renderer().Add(new ExplosionRender(p, 1), time + 1.1);
2181 renderer(p, true).Add(new LevelSelectRender(p, -1, GetLevelState(p)>>8), time + 1.1);
2186 if (type==0) // Init & count levels
2188 if (x[0]=='_')
2190 int t = GetLevelState(p);
2191 int unlock = progress.GetLevel(x, true)->unlocked;
2192 LevelSelectRender* lsr = new LevelSelectRender( p, unlock ? -1 : (t>>8) ? 5 : 1, t>>8 );
2193 if ((t>>8) && p.x > mapRightBound) mapRightBound = p.x;
2194 #ifdef MAP_EDIT_HACKS
2195 lsr->magic = -atoi(x+1);
2196 SetTile(p, LIFT_DOWN, true, false);
2197 #else
2198 SetTile(p, EMPTY, true, false);
2199 #endif
2200 renderer(p,true).Add(lsr, time);
2202 else
2204 //printf("Level: %s\n", x);
2206 int t = GetLevelState(p);
2207 numLevels++;
2208 if (t && !GetItem(p))
2210 if (!isMap)
2212 isMap = true;
2213 mapRightBound = 0;
2215 currentLevelInfo = 0;
2217 if ((t&0xff)>=2)
2219 LevelSave* l = progress.GetLevel(x, true);
2220 if (!l->unlocked)
2222 l->unlocked = true;
2224 renderer(p, true).Add(new LevelSelectRender(p, -1, 0), time+0.01);
2225 renderer().Add(new ExplosionRender(p, 0), time + 0.6);
2226 renderer(p, true).Add(new LevelSelectRender(p, t & 0xff, t>>8), time + 0.6);
2229 numLevelsFound++;
2230 if (p.x > mapRightBound) mapRightBound = p.x;
2232 if ((t&0xff)>=3)
2233 numComplete++;
2234 if ((t&0xff)>=4)
2235 numMastered++;
2237 LevelSelectRender* lsr = new LevelSelectRender( p, t & 0xff, t>>8 );
2239 #ifdef MAP_EDIT_HACKS
2240 lsr->magic = 0;
2241 int t = GetAutoTile(x, true);
2242 int v = GetAutoTile(x, false);
2243 if (MAP_EDIT_HACKS_DISPLAY_UNLOCK)
2244 lsr->magic = v;
2245 else
2246 lsr->magic = GetPar(x, true);
2247 t = 1;
2248 SetTile(p, t, true, false);
2249 #else
2250 SetTile(p, EMPTY, true, false);
2251 #endif
2253 renderer(p,true).Add(lsr, time);
2258 if (type==1 && x[0]!='_') // Clicked on
2260 int t = GetLevelState(p);
2261 if (t>1)
2263 LoadSave(x, false);
2268 void SetItem(Pos const & p, int t, bool updateRenderer=true, bool undoBuffer=true)
2270 if (p.x<0 || p.x>MAP_SIZE)
2271 return;
2272 if (p.y<0 || p.y>MAP_SIZE)
2273 return;
2274 if (map_item[p.x][p.y] == t)
2275 return;
2277 if (undoBuffer)
2278 undo[numUndo].Add(Undo::TileChange(p,GetTile(p),GetItem(p)));
2280 map_item[p.x][p.y] = t;
2282 if (updateRenderer)
2283 renderer(p,true).Add(new ItemRender(t, GetTile(p)==EMPTY, p), time);
2286 Tile GetItem(Pos const & p)
2288 if (p.x<0 || p.x>=MAP_SIZE)
2289 return EMPTY;
2290 if (p.y<0 || p.y>=MAP_SIZE)
2291 return EMPTY;
2292 return map_item[p.x][p.y];
2295 void LoadSaveProgress(bool save)
2297 FILE* f = file_open(currentSlot, save ? "wb" : "rb");
2298 if (f)
2300 progress.LoadSave(f, save);
2301 fclose(f);
2303 else
2305 if (!save)
2306 progress.Clear();
2309 void LoadProgress()
2311 LoadSaveProgress(false);
2313 void SaveProgress()
2315 LoadSaveProgress(true);
2318 SDL_Surface* Load(const char * bmp, bool colourKey=true)
2320 typedef unsigned int uint32;
2321 uint32* tmp = 0;
2323 SDL_Surface * g = 0;
2325 #ifdef EDIT
2326 if (strstr(bmp, ".bmp"))
2328 g = SDL_LoadBMP(bmp);
2330 char out[1024];
2331 strcpy(out, bmp);
2332 strcpy(strstr(out, ".bmp"), ".dat");
2334 // SDL_PixelFormat p;
2335 // p.sf = 1;
2336 // SDL_Surface* tmp = SDL_ConvertSurface(g, &p, SDL_SWSURFACE);
2338 short w=g->w, h=g->h;
2339 char* buf = (char*) g->pixels;
2340 if (colourKey)
2342 while (IsEmpty(g, w-1, 0, 1, h) && w>1)
2343 w--;
2344 while (IsEmpty(g, 0, h-1, w, 1) && h>1)
2345 h--;
2348 FILE* f = file_open(out, "wb");
2349 fwrite(&w, sizeof(w), 1, f);
2350 fwrite(&h, sizeof(h), 1, f);
2352 uint32 mask = IMAGE_DAT_OR_MASK;
2353 for (int i=0; i<(int)w*h; )
2355 uint32 c = (*(uint32*)&buf[(i%w)*3 + (i/w)*g->pitch] | mask);
2356 int i0 = i;
2357 while (i < (int)w*h && c == (*(uint32*)&buf[(i%w)*3 + (i/w)*g->pitch] | mask))
2358 i++;
2359 c &= 0xffffff;
2360 i0 = i-i0-1;
2361 if (i0 < 0xff)
2362 c |= i0 << 24;
2363 else
2364 c |= 0xff000000;
2366 fwrite(&c, sizeof(c), 1, f);
2368 if (i0 >= 0xff)
2369 fwrite(&i0, sizeof(i0), 1, f);
2371 fclose(f);
2373 SDL_FreeSurface(g);
2375 bmp = out;
2377 #endif
2379 FILE* f = file_open(bmp, "rb");
2380 if (!f) FATAL("Unable to open file", bmp);
2382 short w,h;
2383 fread(&w, sizeof(w), 1, f);
2384 fread(&h, sizeof(h), 1, f);
2385 if (w>1500 || h>1500) FATAL("Invalid file", bmp);
2387 tmp = new uint32[(int)w*h];
2389 uint32 c = 0;
2390 uint32 cnt = 0;
2391 for (int p=0; p<(int)w*h; p++)
2393 if (cnt)
2394 cnt -= 0x1;
2395 else
2397 fread(&c, sizeof(c), 1, f);
2398 cnt = c >> 24;
2399 if (cnt==255)
2400 fread(&cnt, sizeof(cnt), 1, f);
2402 tmp[p] = c | 0xff000000;
2405 g = SDL_CreateRGBSurfaceFrom(tmp, w, h, 32, w*4,
2406 0xff0000,
2407 0xff00,
2408 0xff,
2409 0xff000000 );
2411 fclose(f);
2414 if (!g) FATAL("Unable to create SDL surface");
2415 if (colourKey)
2416 SDL_SetColorKey(g, SDL_SRCCOLORKEY, SDL_MapRGB(g->format, WATER_COLOUR));
2417 SDL_Surface * out = SDL_DisplayFormat(g);
2418 SDL_FreeSurface(g);
2419 delete [] tmp;
2420 if (!out) FATAL("Unable to create SDL surface (SDL_DisplayFormat)");
2421 return out;
2424 #ifdef USE_LEVEL_PACKFILE
2425 PackFile1 levelFiles;
2426 #endif
2427 HexPuzzle()
2429 SDL_WM_SetCaption(GAMENAME, 0);
2431 time = 0;
2433 #ifdef USE_LEVEL_PACKFILE
2434 FILE* f = file_open("levels.dat", "rb");
2435 if (!f)
2436 FATAL("Unable to open file", "levels.dat");
2437 levelFiles.Read(f);
2438 fclose(f);
2439 #endif
2441 LoadGraphics();
2443 isMap = false;
2444 editMode = false;
2446 currentLevelInfo = 0;
2448 editTile = 0;
2449 levelPar = 0;
2450 levelDiff = 5;
2451 turboAnim = 0;
2453 memset(map, 0, sizeof(map));
2454 memset(map_item, 0, sizeof(map_item));
2455 memset(special, 0, sizeof(special));
2457 LoadProgress();
2459 // player = Pos(1,11);
2461 // ResetLevel();
2463 LoadMap();
2466 void LoadMap()
2468 #ifndef EDIT
2469 progress.GetLevel(STARTING_LEVEL, true)->unlocked = 1;
2470 if (!progress.GetLevel(STARTING_LEVEL, true)->Completed())
2472 LoadSave(STARTING_LEVEL, false);
2473 return;
2475 #endif
2477 //editMode = false;
2478 LoadSave(mapname, false);
2481 void Render()
2483 if (!activeMenu || activeMenu->renderBG)
2485 SDL_Rect src = {0,0,screen->w,screen->h};
2486 SDL_Rect dst = {0,0,screen->w,screen->h};
2487 if (isRenderMap)
2489 int boundW = mapBG->w;
2490 #ifndef EDIT
2491 boundW = MIN(boundW, (mapRightBound+4) * TILE_W2 - TILE_W1);
2492 #endif
2493 src.x = scrollX - initScrollX;
2494 if (src.x+src.w > boundW)
2496 int diff = src.x+src.w - boundW;
2497 src.x -= diff;
2498 if (isMap)
2499 scrollX -= diff;
2501 if (src.x < 0)
2503 if (isMap)
2504 scrollX -= src.x;
2505 src.x = 0;
2507 //scrollY = initScrollY;
2509 if (isMap)
2510 mapScrollX = scrollX;
2512 SDL_BlitSurface(mapBG, &src, screen, &dst);
2514 else
2515 SDL_BlitSurface(gradient, &src, screen, &dst);
2517 renderer.Render(time, true);
2519 if (!hintsDone && !isFadeRendering)
2521 DoHints();
2524 if (1)
2526 SDL_Rect src = {0,SCREEN_H-1,SCREEN_W,1};
2527 SDL_Rect dst = {0,SCREEN_H-1,SCREEN_W,1};
2528 for (int i=0; i<SCREEN_H; i++)
2530 dst.x = src.x = 0;
2531 dst.y = src.y = SCREEN_H-1-i;
2532 src.w = SCREEN_W;
2533 src.h = 1;
2535 const bool farView = false;
2536 if (isRenderMap)
2538 src.x += (int)( sin(i*0.9 + time*3.7) * sin(i*0.3 + time*0.7)*4 );
2539 src.y += (int)( (sin(i*0.3 - time*2.2) * sin(i*0.48 + time*0.47) - 1) * 1.99 );
2541 else
2543 src.x += (int)( sin(i*0.5 + time*6.2) * sin(i*0.3 + time*1.05) * 5 );
2544 src.y += (int)( (sin(i*0.4 - time*4.3) * sin(i*0.08 + time*1.9) - 1) * 2.5 );
2546 SDL_BlitSurface(screen, &src, screen, &dst);
2550 if(isRenderMap)
2551 SDL_BlitSurface(mapBG2, &src, screen, &dst);
2553 renderer.Render(time, false);
2555 if (!isRenderMap && !isMap && !isFadeRendering)
2557 int v[3] = {player_items[0], player_items[1], player_score};
2558 if (numUndo > 1 && time < undo[numUndo-2].endTime)
2560 int i = numUndo-1;
2561 while (i>1 && time<undo[i-1].time)
2562 i--;
2563 v[0] = undo[i].numItems[0];
2564 v[1] = undo[i].numItems[1];
2565 v[2] = undo[i].score;
2567 if (numUndo>1 && time < undo[0].time)
2568 v[0]=v[1]=v[2]=0;
2569 #ifdef EDIT
2570 /* TRANSLATORS: Anti-Ice are pickups, which turn ice plates into solid
2571 plates once you step on them. Each pickup changes one ice plate */
2572 Print(0,0,_("Anti-Ice: %d"), v[0]);
2573 Print(0,FONT_SPACING,_("Jumps: %d"), v[1]);
2574 Print(0,FONT_SPACING*2,_("Score: %d (%d)"), v[2], player_score);
2575 /* TRANSLATORS: Par is similar to golf, a pre defined score which you
2576 can attempt to beat */
2577 Print(0,FONT_SPACING*3,_("Par: %d"), levelPar);
2578 Print(0,FONT_SPACING*4,_("Diff: %d"), levelDiff);
2579 #else
2580 if (showScoring)
2581 Print(0, SCREEN_H-FONT_SPACING, _(" Par: %d Current: %d"), levelPar, v[2]);
2583 if (v[0])
2584 Print(0,0,_(" Anti-Ice: %d"), v[0]);
2585 else if (v[1])
2586 Print(0,0,_(" Jumps: %d"), v[1]);
2587 #endif
2589 if (isRenderMap && isMap && !isFadeRendering)
2591 #if 0//def EDIT
2592 Print(0,0,_("Points: %d"), numComplete+numMastered);
2593 Print(0,FONT_SPACING,_("Discovered: %d%% (%d/%d)"), numLevelsFound*100/numLevels, numLevelsFound, numLevels);
2594 Print(0,FONT_SPACING*2,_("Complete: %d%% (%d)"), numComplete*100/numLevels, numComplete);
2595 Print(0,FONT_SPACING*3,_("Mastered: %d%% (%d)"), numMastered*100/numLevels, numMastered);
2596 #else
2597 if (numComplete==numLevels && progress.general.endSequence>0)
2598 Print(0, SCREEN_H-FONT_SPACING, _(" %d%% Mastered"), numMastered*100/numLevels);
2599 else
2600 Print(0, SCREEN_H-FONT_SPACING, _(" %d%% Complete"), numComplete*100/numLevels);
2602 if (numMastered >= numLevels && progress.general.endSequence < 2)
2604 progress.general.endSequence = 2;
2605 LoadSaveProgress(true);
2607 new Fader(-1, -7, 0.3);
2609 if (numComplete >= numLevels && progress.general.endSequence < 1)
2611 progress.general.endSequence = 1;
2612 LoadSaveProgress(true);
2614 new Fader(-1, -5, 0.3);
2616 #endif
2618 if ((currentLevelInfo || noMouse) && isMap && isRenderMap && !activeMenu && isFadeRendering<=0)
2620 Pos p;
2621 if (noMouse)
2622 p = keyboardp;
2623 else
2624 p = mousep;
2625 int pad = SCREEN_W/80;
2626 // SDL_Rect src = {0, 0, uiGraphics->w, uiGraphics->h};
2627 SDL_Rect dst = {pad, SCREEN_H-TILE_H2-pad, 0, 0};
2628 // dst.x = p.getScreenX() + TILE_W3/2 - scrollX;
2629 // dst.y = p.getScreenY() - src.h/2 - scrollY;
2630 dst.x = p.getScreenX() - scrollX;
2631 dst.y = p.getScreenY() - scrollY - FONT_SPACING*3 - FONT_SPACING/2;
2632 // if (dst.x > SCREEN_W*2/3) dst.x -= TILE_W3 + src.w;
2633 // if (dst.y+src.h > screen->h-pad) dst.y = screen->h-pad - src.h;
2635 RenderTile(false, 0, p.getScreenX(), p.getScreenY());
2636 // SDL_BlitSurface(uiGraphics, &src, screen, &dst);
2638 // dst.x += src.w/2;
2640 if (currentLevelInfo)
2642 keyboardp = p;
2644 PrintC(true, dst.x, dst.y - FONT_SPACING/4, currentLevelInfo->name);
2646 if (currentLevelInfo->file[0]!=0)
2648 if (player_score > 0)
2650 if (progress.general.scoringOn)
2652 PrintC(false, dst.x, dst.y + FONT_SPACING*4 - FONT_SPACING/4, _("Best:% 3d"), player_score);
2653 PrintC(false, dst.x, dst.y + FONT_SPACING*5 - FONT_SPACING/4, _("Par:% 3d"), levelPar);
2655 else
2656 PrintC(false, dst.x, dst.y + FONT_SPACING*4 - FONT_SPACING/4, _("Completed"), player_score);
2658 else
2659 PrintC(false, dst.x, dst.y + FONT_SPACING*4 - FONT_SPACING/4, _("Incomplete"), player_score);
2664 // "Win"
2665 if (win && numUndo > 0 && time > undo[numUndo-1].endTime + 2)
2667 if (currentFile[0] && winFinal==0)
2669 LevelSave* l = progress.GetLevel(currentFile, true);
2671 new WinLoseScreen(true, player_score, showScoring ? levelPar : 0, l && showScoring && l->Completed() ? l->GetScore() : 0);
2673 if (l->IsNewCompletionBetter(player_score))
2675 l->SetScore(player_score);
2677 l->SetSolution(numUndo);
2679 for (int i=0; i<numUndo; i++)
2680 l->SetSolutionStep(i, undo[i].playerMovement);
2683 SaveProgress();
2686 winFinal = 1;
2688 else
2689 winFinal = 0;
2691 // Move up "level complete" writing so it doesn't feel like everything's ground to a halt...
2692 if (win && numUndo > 0 && time > undo[numUndo-1].endTime && !winFinal)
2694 double t = (time - undo[numUndo-1].endTime) / 2;
2695 t=1-t;
2696 t*=t*t;
2697 t=1-t;
2698 int y = SCREEN_H/3 - FONT_SPACING + 1;
2699 y = SCREEN_H + int((y-SCREEN_H)*t);
2700 PrintC(true, SCREEN_W/2, y, _("Level Complete!"));
2704 if (activeMenu)
2705 activeMenu->Render();
2707 if (!noMouse)
2709 // Edit cursor
2710 if (editMode)
2712 RenderTile(false, editTile, mousex+scrollX, mousey+scrollY);
2717 int Count(Tile t)
2719 return tileCount[t];
2721 int Swap(Tile t, Tile t2)
2723 const int num = Count(t) + Count(t2);
2724 if (t==t2 || num==0)
2725 return Count(t); // Nothing to do...
2727 int count=0;
2728 for (int x=0; x<MAP_SIZE; x++)
2729 for (int y=0; y<MAP_SIZE; y++)
2731 if (GetTile(Pos(x,y))==t)
2733 count++;
2734 SetTile(Pos(x,y), t2);
2736 else if (GetTile(Pos(x,y))==t2)
2738 count++;
2739 SetTile(Pos(x,y), t);
2741 if (count==num)
2742 return count;
2744 return count;
2746 int Replace(Tile t, Tile t2)
2748 const int num = Count(t);
2749 if (t==t2 || num==0)
2750 return num; // Nothing to do...
2752 int count=0;
2753 for (int x=0; x<MAP_SIZE; x++)
2754 for (int y=0; y<MAP_SIZE; y++)
2756 Pos p(x,y);
2757 if (GetTile(p)==t)
2759 count++;
2761 SetTile(p, t2, false);
2763 if (t==COLLAPSE_DOOR && t2==COLLAPSABLE)
2764 renderer(p).Add(new BuildRender(p, -1, 1, 1), time + (rand() & 255)*0.001);
2765 else if (t==COLLAPSE_DOOR2 && t2==COLLAPSABLE2)
2766 renderer(p).Add(new BuildRender(p, -1, 1, 1, 1), time + (rand() & 255)*0.001);
2767 else
2768 SetTile(p, t2);
2770 if (count==num)
2771 return count;
2774 return count;
2777 Tile editTile;
2778 bool editMode;
2779 void ResetUndo()
2781 UndoDone();
2782 undoTime = -1;
2783 numUndo = 0;
2784 win = false;
2787 void UpdateCursor(Pos const & s)
2789 static Pos _s;
2790 if (s.x!=_s.x || s.y!=_s.y)
2792 _s = s;
2794 char* sp = GetSpecial(s);
2795 char tmp[1000];
2796 tmp[0]='\0';
2797 if (sp)
2799 if (isMap)
2801 currentLevelInfo = 0;
2802 levelPar = player_score = -1;
2803 if (GetLevelState(s)>=2)
2805 LevelSave* l = progress.GetLevel(sp, true);
2806 if (l)
2808 currentLevelInfo = GetLevelInfo(sp);
2809 levelPar = GetPar(sp);
2810 player_score = l->GetScore();
2815 #ifdef EDIT
2816 sprintf(tmp, _("Special(%d,%d): %s (%d)"), s.x, s.y, sp ? sp : _("<None>"), GetPar(sp));
2817 SDL_WM_SetCaption(tmp, NULL);
2818 #endif
2820 else if (currentFile[0])
2822 #ifdef EDIT
2823 SDL_WM_SetCaption(currentFile, NULL);
2824 #endif
2825 if (isMap)
2826 currentLevelInfo = 0;
2831 virtual void Mouse(int x, int y, int dx, int dy, int button_pressed, int button_released, int button_held)
2833 if (activeMenu)
2835 activeMenu->Mouse(x,y,dx,dy,button_pressed,button_released,button_held);
2836 return;
2839 if (isFadeRendering)
2840 return;
2843 #ifndef EDIT
2844 if (button_pressed==2 || button_pressed==4 && isMap)
2846 KeyPressed(SDLK_ESCAPE, 0);
2847 keyState[SDLK_ESCAPE] = 0;
2848 return;
2850 #endif
2852 x += scrollX;
2853 y += scrollY;
2855 Pos s = Pos::GetFromWorld(x,y);
2856 if (tileSolid[GetTile(Pos::GetFromWorld(x,y+TILE_HUP))] == 1)
2857 s = Pos::GetFromWorld(x,y+TILE_HUP);
2859 mousep = s;
2861 UpdateCursor(s);
2863 #ifdef EDIT
2864 if (button_held & ~button_pressed & 4)
2866 scrollX -= dx;
2867 scrollY -= dy;
2869 #endif
2871 if (!editMode)
2873 if (isMap && (button_pressed & 1))
2875 ActivateSpecial(s, 1);
2876 return;
2878 if (!isMap && win && winFinal)
2880 if (button_pressed & 1)
2882 LoadMap();
2883 return;
2886 if(!isMap)
2888 if((button_pressed & 1) || (button_held & 1) && (numUndo==0 || time>=undo[numUndo-1].endTime))
2890 if(s.x==player.x && s.y==player.y)
2892 // Don't activate jump powerup without a new click
2893 if (button_pressed & 1)
2894 Input(-1);
2896 else if(s.x==player.x && s.y<player.y)
2897 Input(0);
2898 else if(s.x==player.x && s.y>player.y)
2899 Input(3);
2900 else if(s.y==player.y && s.x<player.x)
2901 Input(5);
2902 else if(s.y==player.y && s.x>player.x)
2903 Input(2);
2904 else if(s.y+s.x==player.y+player.x && s.x>player.x)
2905 Input(1);
2906 else if(s.y+s.x==player.y+player.x && s.x<player.x)
2907 Input(4);
2909 if ((button_pressed & 4) || (button_held & 4) && (undoTime < 0))
2910 Undo();
2912 return;
2915 #ifdef EDIT
2916 if (!button_pressed && !button_held)
2917 return;
2919 if (button_pressed==1)
2920 if (editTile<0)
2921 editTile = GetItem(s)==1 ? -3 : GetItem(s)==2 ? -2 : -1;
2923 if (button_held==1 || button_pressed==1)
2925 ResetUndo();
2926 if (editTile>=0)
2927 SetTile(s, editTile, true, false);
2928 else
2929 SetItem(s, editTile==-2 ? 0 : editTile==-1 ? 1 : 2, true, false);
2932 if (button_pressed==2)
2934 editTile = GetTile(s);
2937 if (button_pressed==8)
2939 editTile=editTile-1;
2940 if (editTile<=0) editTile=NumTileTypes-1;
2943 if (button_pressed==16)
2945 editTile=editTile+1;
2946 if (editTile<=0) editTile=1;
2947 if (editTile==NumTileTypes) editTile=0;
2950 if (button_pressed==64)
2952 ResetUndo();
2953 player = s;
2954 dead = false;
2955 renderer.player.Reset(-1);
2956 renderer.player.Add(new PlayerRender(player, GetHeight(player), dead), 0);
2959 if (button_pressed==256)
2961 char* fn = LoadSaveDialog(false, true, _("Select level"));
2962 if (fn)
2964 char * l = strstr(fn, "Levels");
2965 if(l)
2967 FILE * f = file_open(l,"rb");
2968 if (f)
2969 fclose(f);
2970 if (f)
2971 SetSpecial(s, l);
2972 else if (l[6]!=0 && l[7]=='_')
2973 SetSpecial(s, l+7);
2975 UpdateCursor(Pos(-1,-1));
2978 if (button_pressed==512)
2980 SetSpecial(s, NULL);
2981 UpdateCursor(Pos(-1,-1));
2983 if (button_pressed==1024)
2985 static char x[1000] = "";
2986 if (!(s.x<0 || s.x>=MAP_SIZE || s.y<0 || s.y>=MAP_SIZE))
2988 char tmp[1000];
2989 strcpy(tmp, x);
2990 if (GetSpecial(s))
2991 strcpy(x, GetSpecial(s));
2992 else
2993 x[0] = 0;
2994 SetSpecial(s, tmp[0] ? tmp : 0);
2995 if (!tmp[0])
2996 SetTile(s, EMPTY, true, false);
3000 if (button_pressed==32)
3002 editTile = editTile<0 ? 1 : -1;
3004 #endif // EDIT
3007 void CheckFinished()
3009 bool slow = false;
3010 if (Count(COLLAPSABLE)==0)
3012 if (Replace(COLLAPSE_DOOR, COLLAPSABLE) == 0)
3013 win = true;
3014 else
3015 slow = true;
3016 Replace(SWITCH, NORMAL);
3018 else
3019 win = false;
3021 if (Count(COLLAPSABLE2)==0)
3022 if (Replace(COLLAPSE_DOOR2, COLLAPSABLE2))
3023 slow = true;
3025 if (slow)
3026 time += BUILD_TIME;
3028 bool Collide(Pos p, bool high)
3030 Tile t = GetTile(p);
3031 // switch(t)
3032 // {
3033 // default:
3034 if (!high)
3035 return tileSolid[t]==1;
3036 else
3037 return false;
3038 // }
3040 void Undo()
3042 if (numUndo==0) return;
3044 UndoDone(); // Complete previous undo...
3046 numUndo--;
3048 if (time > undo[numUndo].endTime)
3049 time = undo[numUndo].endTime;
3050 undoTime = undo[numUndo].time;
3052 undo[numUndo].Restore(this);
3054 void UndoDone()
3056 if (undoTime < 0)
3057 return;
3058 renderer.Reset(undoTime);
3059 time = undoTime;
3060 undoTime = -1;
3062 void ScoreDestroy(Pos p)
3064 Tile t = GetTile(p);
3065 if (t==COLLAPSABLE || t==COLLAPSE_DOOR)
3067 else if (t != EMPTY)
3069 player_score += 10;
3073 bool LaserTile(Pos p, int mask, double fireTime)
3075 if (&renderer(p) == &renderer(Pos(-1,-1)))
3076 return false;
3077 //if (!renderer.Visible(p))
3078 // return false;
3080 TileRender* tr = 0;
3081 if (time <= renderer(p).GetLastTime())
3082 if (fireTime < renderer(p).GetLastTime())
3084 renderer(p).Add(tr = new TileRender(GetTile(p), p, mask), fireTime);
3085 ((TileRender*)renderer(p).GetStage(time+10/*HACKY!*/))->special |= mask;
3087 else
3089 tr = new TileRender(GetTile(p), p, mask | ((TileRender*)renderer(p).GetStage(time+10/*HACKY!*/))->special);
3090 renderer(p).Add(tr, fireTime);
3092 else
3093 renderer(p).Add(tr = new TileRender(GetTile(p), p, mask), fireTime);
3095 if (tr)
3097 tr->specialDuration = time + LASER_LINE_TIME - fireTime + LASER_FADE_TIME;
3099 return true;
3101 void FireGun(Pos newpos, Dir d, bool recurse, double fireTime)
3103 static Pos hits[100];
3104 static Dir hitDir[100];
3105 static int numHits=0;
3106 if (!recurse)
3107 numHits = 0;
3109 double starttime = fireTime;
3110 for (Dir fd=((d<0)?0:d); fd<((d<0)?MAX_DIR:d+1); fd++)
3112 fireTime = starttime;
3113 // starttime += 0.03;
3115 Pos p = newpos + fd;
3116 int range = 0;
3117 for (range; range<MAP_SIZE; range++, p=p+fd)
3119 Tile t = GetTile(p);
3120 if (tileSolid[t]!=-1)
3122 if (t!=TRAP)
3123 renderer(p).Add(new TileRender(tileSolid[t]==1 ? TILE_WHITE_WALL : TILE_WHITE_TILE, p), fireTime+0.1);
3125 int i;
3126 for (i=0; i<numHits; i++)
3127 if (hits[i]==p)
3128 break;
3129 if (i==numHits ||
3130 t==TRAP && (hitDir[i]&(1<<fd))==0
3133 if (i==numHits)
3135 if (i >= sizeof(hits)/sizeof(hits[0]))
3136 return;
3137 hitDir[i] = 1 << fd;
3138 hits[i] = p;
3139 numHits++;
3141 else
3143 hitDir[i] |= 1 << fd;
3145 if (t==TRAP)
3147 int dirmask =
3148 1<<((fd+2) % MAX_DIR)
3149 | 1<<((fd+MAX_DIR-2) % MAX_DIR);
3151 if (LaserTile(p, dirmask, fireTime))
3152 fireTime += (time+LASER_LINE_TIME - fireTime) / 40;
3153 // fireTime += LASER_SEGMENT_TIME;
3155 FireGun(p, (fd+1) % MAX_DIR, true, fireTime);
3156 FireGun(p, (fd+MAX_DIR-1) % MAX_DIR, true, fireTime);
3159 break;
3161 else
3163 LaserTile(p, 1<<(fd%3), fireTime);
3165 fireTime += (time+LASER_LINE_TIME - fireTime) / 40;
3166 // fireTime += LASER_SEGMENT_TIME;
3170 // renderer().Add(new LaserRender(newpos, fd, range), time);
3173 if (!recurse)
3175 double _time = time;
3176 time += LASER_LINE_TIME;
3177 for (int i=0; i<numHits; i++)
3179 Pos p = hits[i];
3180 Tile t = GetTile(p);
3182 if (t==TRAP)
3183 continue;
3185 ScoreDestroy(p);
3187 renderer(p).Add(new ExplosionRender(p, t==GUN), time);
3188 //renderer(p).Add(new TileRender(EMPTY, p), time+2);
3189 SetTile(p, EMPTY, false);
3191 if (GetItem(p))
3192 renderer(p,true).Add(new ItemRender(GetItem(p), 1, p), time);
3194 if (t==GUN)
3196 for (Dir j=0; j<MAX_DIR; j++)
3198 if (GetTile(p+j)!=EMPTY)
3200 renderer(p+j).Add(new TileRender(tileSolid[GetTile(p+j)]==1 ? TILE_WHITE_WALL : TILE_WHITE_TILE, p+j), time+0.05);
3201 renderer(p+j).Add(new ExplosionRender(p+j), time+0.2);
3203 if (GetItem(p+j))
3204 renderer(p+j,true).Add(new ItemRender(GetItem(p+j), 1, p), time);
3206 //renderer(p+j).Add(new TileRender(EMPTY, p+j), time+2.2);
3208 ScoreDestroy(p + j);
3209 SetTile(p + j, EMPTY, false);
3214 time += MAX(LASER_FADE_TIME, 0.15);
3215 //time = _time;
3216 CheckFinished();
3219 int GetLastPlayerRot()
3221 RenderStage* rs = renderer.player.GetStage(-1);
3222 if (!rs) return 3;
3223 return ((PlayerRender*)rs)->r;
3225 bool Input(Dir d)
3227 if (dead || win || isMap)
3228 return false;
3230 // Complete undo
3231 UndoDone();
3233 // Jump forwards in time to last move finishing
3234 if (numUndo > 0 && time < undo[numUndo-1].endTime)
3235 time = undo[numUndo-1].endTime;
3237 double realTime = time;
3238 double endAnimTime = time;
3239 bool high = (tileSolid[GetTile(player)] == 1);
3240 Pos playerStartPos = player;
3241 Pos oldpos = player;
3242 int oldPlayerHeight = GetHeight(oldpos);
3243 Pos newpos = player + d;
3245 int playerRot = GetLastPlayerRot();
3246 if (d!=-1 && d!=playerRot)
3248 while (d!=playerRot)
3250 if ((d+6-playerRot) % MAX_DIR < MAX_DIR/2)
3251 playerRot = (playerRot+1) % MAX_DIR;
3252 else
3253 playerRot = (playerRot+MAX_DIR-1) % MAX_DIR;
3255 time += 0.03;
3257 if (GetTile(oldpos) == FLOATING_BALL)
3259 TileRender* t = new TileRender(FLOATING_BALL, oldpos);
3260 t->special = playerRot + 256;
3261 renderer(oldpos).Add(t, time);
3263 renderer.player.Add(new PlayerRender(playerRot, Pos(-20,-20), oldPlayerHeight, Pos(-20,-20), oldPlayerHeight, dead), time);
3265 else
3267 PlayerRender *p = new PlayerRender(playerRot, player, oldPlayerHeight, player, oldPlayerHeight, dead);
3268 p->speed = 0;
3269 renderer.player.Add(p, time);
3273 time += 0.03;
3276 if (d<0 && player_items[1]==0)
3277 return false;
3279 if (d >= 0)
3281 if (tileSolid[GetTile(newpos)] == -1)
3283 time = realTime;
3284 return false;
3286 if (Collide(newpos, high))
3288 time = realTime;
3289 return false;
3293 // Don't change any real state before this point!
3294 if (numUndo >= MAX_UNDO)
3296 numUndo--;
3297 for(int i=0; i<MAX_UNDO-1; i++)
3298 undo[i] = undo[i+1];
3300 undo[numUndo].New(d, player, player_items, time, player_score);
3302 if (d<0)
3304 player_items[1]--;
3307 int old_score=player_score;
3309 double time0 = time;
3310 time += 0.15; //Time for leave-tile fx
3312 switch (GetTile(oldpos))
3314 case COLLAPSABLE:
3315 SetTile(oldpos, EMPTY);
3316 renderer(oldpos).Add(new DisintegrateRender(oldpos), time);
3317 CheckFinished();
3318 break;
3320 case COLLAPSE_DOOR:
3321 // Don't need to CheckFinished - can't be collapse doors around
3322 // unless there're still collapsable tiles around.
3323 SetTile(oldpos, EMPTY);
3324 renderer(oldpos).Add(new DisintegrateRender(oldpos, 1), time);
3325 break;
3327 case COLLAPSABLE2:
3328 SetTile(oldpos, COLLAPSABLE, false);
3329 renderer(oldpos).Add(new DisintegrateRender(oldpos, 0, 1), time);
3330 player_score += 10;
3331 CheckFinished();
3332 break;
3334 case COLLAPSE_DOOR2:
3335 SetTile(oldpos, COLLAPSE_DOOR, false);
3336 renderer(oldpos).Add(new DisintegrateRender(oldpos, 1, 1), time);
3337 player_score += 10;
3338 break;
3340 case COLLAPSABLE3:
3341 SetTile(oldpos, COLLAPSABLE2);
3342 break;
3345 time = time0; //End of leave-tile fx
3347 int retry_pos_count=0;
3348 retry_pos:
3349 retry_pos_count++;
3351 if (GetItem(newpos)==1)
3353 renderer(newpos, true).Add(new ItemCollectRender(GetItem(newpos), newpos), time + JUMP_TIME/2);
3354 SetItem(newpos, 0, false);
3355 player_items[0]++;
3357 if (GetItem(newpos)==2)
3359 renderer(newpos, true).Add(new ItemCollectRender(GetItem(newpos), newpos), time + JUMP_TIME/2);
3360 SetItem(newpos, 0, false);
3361 player_items[1]++;
3364 if (GetTile(player) == FLOATING_BALL)
3366 TileRender* t = new TileRender(FLOATING_BALL, player);
3367 t->special = 0;
3368 renderer(oldpos).Add(t, time);
3371 PlayerRender *p = new PlayerRender(playerRot, player, oldPlayerHeight, newpos, GetHeight(newpos), dead);
3373 // alternate leg (hacky!)
3374 if (1)
3376 static int l=0;
3377 l++;
3378 p->type = l & 1;
3381 if (retry_pos_count!=0 && GetTile(player)==TRAP)
3383 p->speed /= 1.5;
3384 p->type = 2;
3386 if (d==-1)
3387 p->speed = JUMP_TIME * 1.5;
3388 renderer.player.Add(p, time);
3389 endAnimTime = MAX(endAnimTime, time + p->speed+0.001);
3390 time += p->speed;
3392 player = newpos;
3394 switch (GetTile(newpos))
3396 case COLLAPSABLE:
3397 renderer(newpos).Add(new TileRender(TILE_GREEN_CRACKED, newpos), time);
3398 break;
3399 case COLLAPSE_DOOR:
3400 renderer(newpos).Add(new TileRender(TILE_GREEN_CRACKED_WALL, newpos), time);
3401 break;
3402 case COLLAPSABLE2:
3403 renderer(newpos).Add(new TileRender(TILE_BLUE_CRACKED, newpos), time);
3404 break;
3405 case COLLAPSE_DOOR2:
3406 renderer(newpos).Add(new TileRender(TILE_BLUE_CRACKED_WALL, newpos), time);
3407 break;
3409 case EMPTY:
3410 dead = true;
3411 break;
3413 case BUILDER:
3415 double pretime = time;
3416 bool done = false;
3417 time += 0.15;
3418 for (Dir fd=0; fd<MAX_DIR; fd++)
3420 Tile t2 = GetTile(newpos + fd);
3421 if (t2==EMPTY)
3423 done = true;
3424 SetTile(newpos+fd, COLLAPSABLE, false);
3425 renderer(newpos+fd).Add(new BuildRender(newpos+fd, fd, 0), time);
3427 else if (t2==COLLAPSABLE)
3429 done = true;
3430 SetTile(newpos+fd, COLLAPSE_DOOR, false);
3431 renderer(newpos+fd).Add(new BuildRender(newpos+fd, fd, 1), time);
3434 if (done) time += BUILD_TIME;
3435 else time = pretime;
3436 CheckFinished();
3437 endAnimTime = MAX(endAnimTime, time + 0.1);
3439 break;
3441 case SWITCH:
3442 Swap(COLLAPSE_DOOR, COLLAPSABLE);
3443 break;
3445 case FLOATING_BALL:
3447 int step=0;
3448 renderer.player.Add(new PlayerRender(playerRot, Pos(-30,-30), 0, Pos(-30,-30), 0, dead), time);
3449 while (tileSolid[GetTile(newpos+d)]==-1)
3451 step++;
3453 if (!renderer.Visible(newpos+d))
3455 TileRender* r = new TileRender(FLOATING_BALL, newpos);
3456 r->special = 512;
3457 renderer(newpos).Add(r, time);
3459 PlayerRender* pr = new PlayerRender(playerRot, newpos, 0, newpos, 0, dead);
3460 pr->speed = JUMP_TIME*1;
3461 renderer.player.Add(pr, time);
3463 time += pr->speed;
3465 dead = 1;
3466 break;
3468 oldpos = newpos;
3469 newpos = oldpos + d;
3471 SetTile(oldpos, EMPTY, false);