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