2 Copyright (C) 2005-2007 Tom Beaumont
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or
7 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 //////////////////////////////////////////////////////
28 //#define MAP_LOCKED_VISIBLE
31 #define GAME_NAME "Hex-a-hop"
39 // #define MAP_EDIT_HACKS
40 #define MAP_EDIT_HACKS_DISPLAY_UNLOCK 0
42 #define BMP_SUFFIX ".bmp"
44 #define USE_LEVEL_PACKFILE
45 #define BMP_SUFFIX ".dat"
51 #define GAMENAME GAME_NAME " (EDIT MODE)"
54 #define GAMENAME GAME_NAME
57 #define IMAGE_DAT_OR_MASK 0xff030303 // Reduce colour depth of images slightly for better compression (and remove useless top 8 bits!)
58 #define STARTING_LEVEL "Levels\\0_green\\triangular.lev"
59 #define UNLOCK_SCORING 75
60 const char * mapname
= "Levels\\map_maybe\\map.lev";
62 //////////////////////////////////////////////////////
70 #include "tiletypes.h"
72 #ifdef USE_LEVEL_PACKFILE
79 #include <sys/types.h>
85 void RenderTile(bool reflect
, int t
, int x
, int y
, int cliplift
=-1);
87 int keyState
[SDLK_LAST
] = {0};
89 FILE *file_open( const char *file
, const char *flags
)
91 // printf("file_open( \"%s\", \"%s\" )\n", file, flags );
92 extern String base_path
;
93 static String filename
; // static to reduce memory alloc/free calls.
94 if (file
[0]=='/') //If a full path is specified, don't prepend base_path
98 if (strncmp(file
, "save", 4) == 0)
100 const char *home
= getenv("HOME");
103 char save_path
[PATH_MAX
];
104 snprintf(save_path
, sizeof(save_path
), "%s/.hex-a-hop", home
);
105 if (!strchr(flags
, 'r'))
106 if (mkdir(save_path
, S_IRWXU
| S_IRWXG
| S_IROTH
| S_IXOTH
) != -1)
107 printf("Creating directory \"%s\"\n", (const char *)save_path
);
108 strncat(save_path
, "/", sizeof(save_path
));
109 filename
= save_path
;
111 else filename
= "/tmp/";
113 else filename
= base_path
;
116 // printf(" -> \"%s\"\n", filename );
118 filename
.fix_backslashes();
119 FILE* f
= fopen( filename
, flags
);
121 if (!f
&& strncmp(file
, "save", 4) != 0)
123 printf("Warning: unable to open file \"%s\" for %s\n", (const char*)filename
, strchr(flags
, 'r') ? "reading" : "writing");
130 #ifdef MAP_EDIT_HACKS
131 static const short value_order
[]={
140 COLLAPSE_DOOR
, COLLAPSABLE2
,
152 //#define PROGRESS_FILE "progress.dat"
154 #define PI (3.1415926535897931)
156 #define MAX(a,b) ((a)>(b) ? (a) : (b))
157 #define MIN(a,b) ((a)<(b) ? (a) : (b))
158 #define ABS(a) ((a)<0 ? -(a) : (a))
160 #define WATER_COLOUR 31 | (IMAGE_DAT_OR_MASK>>16)&255, 37 | (IMAGE_DAT_OR_MASK>>8)&255, 135 | (IMAGE_DAT_OR_MASK>>0)&255
162 #define ROTATION_TIME 0.25
164 #define LASER_LINE_TIME 0.7
165 #define LASER_FADE_TIME 0.1
166 #define LASER_SEGMENT_TIME 0.01
167 #define LIFT_TIME 0.5
168 #define JUMP_TIME 0.4
170 #define X(NAME,FILE,ALPHA) SDL_Surface* NAME = 0;
171 #include "gfx_list.h"
172 int scrollX
=0, scrollY
=0, initScrollX
=0, initScrollY
=0;
173 int mapRightBound
= 0;
175 bool showScoring
= false;
176 bool hintsDone
= false;
202 TILE_GREEN_FRAGMENT_1
,
203 TILE_GREEN_FRAGMENT_2
,
208 TILE_GREEN_CRACKED_WALL
,
210 TILE_BLUE_CRACKED_WALL
,
212 TILE_FIRE_PARTICLE_1
,
213 TILE_FIRE_PARTICLE_2
,
217 TILE_LASER_FADE_0
= 53,
218 TILE_BLUE_FRAGMENT
= 56,
219 TILE_BLUE_FRAGMENT_1
,
220 TILE_BLUE_FRAGMENT_2
,
222 TILE_LASER_REFRACT
= 60,
223 TILE_ICE_LASER_REFRACT
= TILE_LASER_REFRACT
+6,
230 const int colours
[] = {
231 #define X(n,col, solid) col,
232 #include "tiletypes.h"
235 const int tileSolid
[] = {
236 #define X(n,col, solid) solid,
237 #include "tiletypes.h"
240 void ChangeSuffix(char* filename
, char* newsuffix
)
242 int len
= strlen(filename
);
244 while (i
>=0 && filename
[i
]!='\\' && filename
[i
]!='.' && filename
[i
]!='/')
246 if (filename
[i
]=='.')
247 strcpy(filename
+i
+1, newsuffix
);
250 strcat(filename
, ".");
251 strcat(filename
, newsuffix
);
255 bool isMap
=false, isRenderMap
=false;
256 int isFadeRendering
=0;
262 |-----------| TILE_W3
265 / \ |TILE_H1 |TILE_H2
272 WL = sqrt(h1*h1 + w1*w1)
273 wl**2 = h1**2 + w1**2
282 #define GFX_SIZE TILE_W3
283 #define TILE_W2 (TILE_W3-TILE_W1)
284 #define TILE_H1 TILE_W1
285 #define TILE_HUP 22 //extra visible height of wall (used for determining whether a wall was clicked on)
286 #define TILE_H2 (TILE_H1*2)
287 #define TILE_WL (TILE_W2-TILE_W1)
288 #define TILE_H_LIFT_UP 26
289 #define TILE_H_REFLECT_OFFSET 24
290 #define TILE_HUP2 TILE_H_LIFT_UP // Displacement of object on top of wall
291 #define FONT_SPACING 25
292 #define FONT_X_SPACING (-1) // -1 in order to try and overlap the black borders of adjacent characters
295 #define TILE_W1 (TILE_WL/2)
296 #define TILE_W2 (TILE_W1+TILE_WL)
297 #define TILE_W3 (TILE_W1+TILE_W2)
298 #define TILE_H1 (TILE_WL*0.8660254037844386)
299 #define TILE_H2 (TILE_H1*2)
305 SDL_Rect tile
[2][70];
306 short tileOffset
[2][70][2];
307 int Peek(SDL_Surface
* i
, int x
, int y
)
309 if (x
<0 || y
<0 || x
>=i
->w
|| y
>=i
->h
)
312 const int BytesPerPixel
= i
->format
->BytesPerPixel
;
313 const int BitsPerPixel
= i
->format
->BitsPerPixel
;
315 p
= ((unsigned char*)i
->pixels
)[i
->pitch
*y
+ x
*BytesPerPixel
];
316 else if (BitsPerPixel
==15 || BitsPerPixel
==16)
317 p
= *(short*)(((char*)i
->pixels
) + (i
->pitch
*y
+ x
*BytesPerPixel
));
318 else if (BitsPerPixel
==32)
319 p
= *(unsigned int*)(((char*)i
->pixels
) + (i
->pitch
*y
+ x
*BytesPerPixel
));
320 else if (BitsPerPixel
==24)
321 p
= (int)((unsigned char*)i
->pixels
)[i
->pitch
*y
+ x
*BytesPerPixel
]
322 | (int)((unsigned char*)i
->pixels
)[i
->pitch
*y
+ x
*BytesPerPixel
] << 8
323 | (int)((unsigned char*)i
->pixels
)[i
->pitch
*y
+ x
*BytesPerPixel
] << 16;
327 bool IsEmpty(SDL_Surface
* im
, int x
, int y
, int w
, int h
)
329 for (int i
=x
; i
<x
+w
; i
++)
330 for (int j
=y
; j
<y
+h
; j
++)
331 if (Peek(im
,i
,j
) != Peek(im
,0,im
->h
-1))
337 memset(font
, 0, sizeof(font
));
339 int h
= FONT_SPACING
;
341 for (int i
=33; i
<=127; i
++)
348 if (y
>= fontImage
->h
)
350 if (y
+h
> fontImage
->h
)
351 h
= fontImage
->h
- y
;
352 } while(IsEmpty(fontImage
, x
, y
, 1, h
));
355 while(!IsEmpty(fontImage
, x
+w
, y
, 1, h
) && x
+w
<fontImage
->w
)
358 while (h1
>1 && IsEmpty(fontImage
, x
, y
+h1
-1, w
, 1))
364 //printf("character %c: % 4d % 4d % 4d % 4d\n", i, x, y, w, h1);
371 font
[i
].w
= font
['j'].w
;
376 for (int i
=0; i
<140; i
++)
378 SDL_Rect r
= {(i
%10)*GFX_SIZE
, ((i
/10)%7)*GFX_SIZE
, GFX_SIZE
, GFX_SIZE
};
379 short * outOffset
= tileOffset
[i
/70][i
%70];
380 SDL_Surface
* im
= (i
/70) ? tileGraphicsR
: tileGraphics
;
382 outOffset
[0] = outOffset
[1] = 0;
384 while (r
.h
>1 && IsEmpty(im
, r
.x
, r
.y
, r
.w
, 1)) r
.h
--, r
.y
++, outOffset
[1]++;
385 while (r
.h
>1 && IsEmpty(im
, r
.x
, r
.y
+r
.h
-1, r
.w
, 1)) r
.h
--;
386 while (r
.w
>1 && IsEmpty(im
, r
.x
, r
.y
, 1, r
.h
)) r
.w
--, r
.x
++, outOffset
[0]++;
387 while (r
.w
>1 && IsEmpty(im
, r
.x
+r
.w
-1, r
.y
, 1, r
.h
)) r
.w
--;
389 tile
[i
/70][i
%70] = r
;
393 void PrintRaw(int x
, int y
, const char * tmp
)
395 for (int i
=0; tmp
[i
]; i
++)
397 SDL_Rect dst
= {x
, y
, 1, 1};
398 SDL_BlitSurface(fontImage
, &font
[tmp
[i
]], screen
, &dst
);
399 x
+= font
[tmp
[i
]].w
+ FONT_X_SPACING
;
403 void Print(int x
, int y
, const char * string
, ...)
406 va_start( marker
, string
); /* Initialize variable arguments. */
409 vsprintf((char*)tmp
, string
, marker
);
413 va_end( marker
); /* Reset variable arguments. */
416 int FontWidth(const char * string
)
419 for (int i
=0; string
[i
]; i
++)
420 w
+= font
[string
[i
]].w
+ FONT_X_SPACING
;
424 void PrintR(int x
, int y
, const char * string
, ...)
427 va_start( marker
, string
); /* Initialize variable arguments. */
430 vsprintf((char*)tmp
, string
, marker
);
432 PrintRaw(x
-FontWidth(tmp
), y
, tmp
);
434 va_end( marker
); /* Reset variable arguments. */
437 void PrintC(bool split
, int x
, int y
, const char * string
, ...)
440 va_start( marker
, string
); /* Initialize variable arguments. */
443 vsprintf((char*)tmp
, string
, marker
);
448 char * end
= split
? strstr(scan
," ") : 0;
451 PrintRaw(x
- FontWidth(scan
)/2, y
, scan
);
457 PrintRaw(x
- FontWidth(scan
)/2, y
, scan
);
463 va_end( marker
); /* Reset variable arguments. */
467 #include "savestate.h"
469 #include "level_list.h"
471 void SaveState::GetStuff()
473 general
.hintFlags
= HintMessage::flags
;
475 void SaveState::ApplyStuff()
477 HintMessage::flags
= general
.hintFlags
;
481 // somewhere else Tile map[][] is assigned to an unsigned char not int32_t
482 // but the data file format expects it to be 32 bit wide!??
483 typedef int32_t Tile
;
487 Pos() : x(0), y(0) {}
488 Pos(int a
, int b
) : x(a
), y(b
) {}
489 bool operator == (Pos
const & p
) const
491 return x
==p
.x
&& y
==p
.y
;
493 Pos
operator + (Dir
const d
) const
496 x
+ ((d
==1 || d
==2) ? 1 : (d
==4 || d
==5) ? -1 : 0),
497 y
+ ((d
==0 || d
==1) ? -1 : (d
==3 || d
==4) ? 1 : 0)
500 int getScreenX() const {
503 int getScreenY() const {
504 return x
*TILE_H1
+ y
*TILE_H2
;
506 static Pos
GetFromWorld(double x
, double y
)
511 tx
= (int)floor(x
/TILE_W2
);
513 ty
= (int)floor(y
/TILE_H2
);
518 if (x
< TILE_W1
&& y
< TILE_H1
)
519 if (x
*TILE_H1
+ y
* TILE_W1
< TILE_H1
*TILE_W1
)
521 if (x
< TILE_W1
&& y
> TILE_H1
)
522 if (x
*TILE_H1
+ (TILE_H2
-y
) * TILE_W1
< TILE_H1
*TILE_W1
)
528 Pos
mousep(0,0), keyboardp(4,20);
534 virtual ~RenderStage() {}
535 virtual void Render(RenderObject
* r
, double time
, bool reflect
) = 0;
536 virtual int GetDepth(double /*time*/) { return 1; }
553 if (maxStages
<= numStages
)
555 maxStages
= maxStages
? maxStages
*2 : 4;
556 stage
= (RenderStage
**) realloc(stage
, sizeof(stage
[0])*maxStages
);
557 time
= (double*) realloc(time
, sizeof(time
[0])*maxStages
);
561 RenderObject() : stage(0), time(0), numStages(0), maxStages(0), currentStage(0)
563 // TODO: use a random number with better range
564 // or maybe make seed an int or float...
565 seed
= rand() / (double)RAND_MAX
;
569 free(stage
); free(time
);
571 bool Active(double t
)
573 if (numStages
==0) return false;
574 if (t
< time
[0]) return false;
577 void UpdateCurrent(double t
)
579 if (currentStage
>= numStages
) currentStage
= numStages
-1;
580 if (currentStage
< 0) currentStage
= 0;
582 while (currentStage
>0 && time
[currentStage
]>t
)
584 while (currentStage
<numStages
-1 && time
[currentStage
+1]<=t
)
589 RenderStage
* GetStage(double t
)
591 if (t
==-1 && numStages
>0)
592 return stage
[numStages
-1];
594 if (!Active(t
)) return 0;
596 return stage
[currentStage
];
600 return numStages
>0 ? time
[numStages
-1] : -1;
602 void Render(double t
, bool reflect
)
607 stage
[currentStage
]->Render(this, t
- time
[currentStage
], reflect
);
609 int GetDepth(double t
)
614 return stage
[currentStage
]->GetDepth(t
- time
[currentStage
]);
619 numStages
= currentStage
= 0;
622 while (numStages
> 0 && time
[numStages
-1] >= t
)
624 if (currentStage
> 0 && currentStage
>= numStages
)
625 currentStage
= numStages
- 1;
627 if (currentStage
< 0) currentStage
= 0;
631 if (currentStage
> 0 && numStages
> 0)
633 memmove(&time
[0], &time
[currentStage
], sizeof(time
[0]) * (numStages
-currentStage
));
634 memmove(&stage
[0], &stage
[currentStage
], sizeof(stage
[0]) * (numStages
-currentStage
));
635 numStages
-= currentStage
;
639 void Add(RenderStage
* s
, double t
)
643 if (currentStage
<numStages
&& time
[currentStage
]<=t
)
646 while (i
<numStages
&& time
[i
]<t
)
649 if (i
<numStages
&& time
[i
]==t
)
657 memmove(&time
[i
+1], &time
[i
], (numStages
-i
) * sizeof(time
[0]));
658 memmove(&stage
[i
+1], &stage
[i
], (numStages
-i
) * sizeof(stage
[0]));
672 RenderObject tile
[SIZE
][SIZE
][2];
685 void Reset(double t
= -1)
691 for (int i
=0; i
<SIZE
; i
++)
692 for (int j
=0; j
<SIZE
; j
++)
693 for (int q
=0; q
<2; q
++)
694 tile
[i
][j
][q
].Reset(t
);
696 for (int j
=0; j
<FX
; j
++)
705 for (int i
=0; i
<SIZE
; i
++)
706 for (int j
=0; j
<SIZE
; j
++)
707 for (int q
=0; q
<2; q
++)
708 tile
[i
][j
][q
].Wipe();
710 for (int j
=0; j
<FX
; j
++)
716 int x0
= (scrollX
+TILE_W2
) / TILE_W2
;
717 int x1
= (scrollX
+SCREEN_W
+TILE_W3
+TILE_W1
) / TILE_W2
;
718 if (p
.x
<0 || p
.y
<0 || p
.x
>=SIZE
|| p
.y
>=SIZE
) return false;
719 if (p
.x
<x0
) return false;
720 if (p
.x
>=x1
-1) return false;
721 for (int j0
=0; j0
<SIZE
*3; j0
++)
723 if (j0
* TILE_H1
< scrollY
-TILE_H1
) continue;
724 if (j0
* TILE_H1
> scrollY
+SCREEN_H
+TILE_H1
) break;
729 if (j
>=SIZE
) i
+=(j
+1-SIZE
)*2, j
=SIZE
-1;
730 for (; i
<x1
&& j
>=0; i
+=2, j
--)
739 void Render(double t
, bool reflect
)
743 int playerDepth
= player
.GetDepth(t
);
744 if (reflect
) playerDepth
-=4;
746 player
.Render(t
, reflect
);
748 int x0
= (scrollX
+TILE_W2
) / TILE_W2
;
749 int x1
= (scrollX
+SCREEN_W
+TILE_W3
+TILE_W1
) / TILE_W2
;
752 for (int j0
=0; j0
<SIZE
*3; j0
++)
754 if (j0
* TILE_H1
< scrollY
-TILE_H1
) continue;
755 if (j0
* TILE_H1
> scrollY
+SCREEN_H
+TILE_H1
) break;
760 if (j
>=SIZE
) i
+=(j
+1-SIZE
)*2, j
=SIZE
-1;
761 for (; i
<x1
&& j
>=0; i
+=2, j
--)
763 for (int q
=reflect
?1:0; q
!=2 && q
!=-1; q
+= (reflect
? -1 : 1))
764 if (tile
[i
][j
][q
].Active(t
))
766 tile
[i
][j
][q
].Render(t
, reflect
);
770 if (playerDepth
==j0
|| j0
==SIZE
*3 && playerDepth
>j0
)
771 player
.Render(t
, reflect
);
774 for (int j
=0; j
<FX
; j
++)
777 fx
[j
].Render(t
, reflect
);
781 RenderObject
& operator () ()
784 if (fxPos
==FX
) fxPos
= 0;
787 RenderObject
& operator () (Pos
const & p
, bool item
=false)
789 if (p
.x
<0 || p
.y
<0 || p
.x
>=SIZE
|| p
.y
>=SIZE
)
791 return tile
[p
.x
][p
.y
][item
? 1 : 0];
795 void RenderTile(bool reflect
, int t
, int x
, int y
, int cliplift
)
797 SDL_Rect src
= tile
[reflect
][t
];
798 SDL_Rect dst
= {x
-scrollX
-GFX_SIZE
/2, y
-scrollY
-GFX_SIZE
+TILE_H1
, 0, 0};
799 dst
.x
+= tileOffset
[reflect
][t
][0];
800 dst
.y
+= tileOffset
[reflect
][t
][1];
802 dst
.y
+= TILE_H_REFLECT_OFFSET
;
803 if (cliplift
==-1 || reflect
)
805 // dst.w=src.w; dst.h=src.h;
806 // SDL_FillRect(screen, &dst, rand());
807 SDL_BlitSurface(reflect
? tileGraphicsR
: tileGraphics
, &src
, screen
, &dst
);
815 SDL_BlitSurface(tileGraphics
, &src
, screen
, &dst
);
822 src
.w
-= TILE_W1
*2, src
.x
+= TILE_W1
;
824 SDL_BlitSurface(tileGraphics
, &src
, screen
, &dst
);
828 void RenderGirl(bool reflect
, int r
, int frame
, int x
, int y
, int h
)
831 int sy
= frame
* 80*2;
833 y
+= TILE_H_REFLECT_OFFSET
+20+h
, sy
+= 80;
836 SDL_Rect src
= {sx
, sy
, 64, 80};
837 SDL_Rect dst
= {x
-scrollX
-32, y
-scrollY
-65, 0, 0};
838 SDL_BlitSurface(girlGraphics
, &src
, screen
, &dst
);
841 struct ItemRender
: public RenderStage
847 ItemRender(int i2
, int _water
, Pos
const & _p
) : item(i2
), p(_p
), water(_water
)
850 double Translate(double seed
, double time
)
852 double bob
= time
*2 + seed
*PI2
;
856 void Render(RenderObject
* r
, double time
, bool reflect
)
861 int y
= -5 + (int)Translate(r
->seed
, r
->currentTime
+ time
);
864 if (!reflect
&& !water
)
865 RenderTile( false, TILE_SPHERE
, p
.getScreenX(), p
.getScreenY());
868 item
==1 ? TILE_ITEM1
: TILE_ITEM2
,
869 p
.getScreenX(), p
.getScreenY()+y
874 void RenderFade(double time
, int dir
, int seed
)
878 for(int x
=rand()%22-11; x
<SCREEN_W
+22; x
+=32, ys
^= 1)
880 for (int y
=ys
*20; y
<SCREEN_H
+30; y
+=40)
882 double a
= (rand()&0xff)*dir
;
883 double b
= (time
* 0x400 + (y
- SCREEN_H
) * 0x140/SCREEN_H
)*dir
;
886 RenderTile(false, TILE_BLACK_TILE
, x
+scrollX
, y
+scrollY
);
892 struct FadeRender
: public RenderStage
896 FadeRender(int d
=-1) : seed(rand()), dir(d
)
901 void Render(RenderObject
* /*r*/, double time
, bool reflect
)
906 if (dir
==1) dir
=0, isFadeRendering
=0;
909 RenderFade(time
, dir
, seed
);
913 struct ScrollRender
: public RenderStage
917 ScrollRender(int a
,int b
) : x(a
), y(b
), done(false) {}
919 void Render(RenderObject
* /*r*/, double /*time*/, bool /*reflect*/)
922 scrollX
= x
, scrollY
= y
;
928 struct LevelSelectRender
: public RenderStage
933 #ifdef MAP_EDIT_HACKS
937 LevelSelectRender(Pos
const & _p
, int i2
, int adj
) : p(_p
), item(i2
), adj(adj
)
940 void Render(RenderObject
* /*r*/, double /*time*/, bool reflect
)
945 #ifndef MAP_LOCKED_VISIBLE
950 for (int i
=0; i
<MAX_DIR
; i
++)
952 RenderTile( false, TILE_LINK_0
+i
, p
.getScreenX(), p
.getScreenY());
961 TILE_SPHERE
+ item
-1,
962 p
.getScreenX(), p
.getScreenY()
965 #ifdef MAP_EDIT_HACKS
966 int x
= p
.getScreenX()-scrollX
, y
= p
.getScreenY()-scrollY
;
967 Print(x
+5,y
-25,"%d",magic
);
973 struct ItemCollectRender
: public ItemRender
975 ItemCollectRender(int i2
, Pos
const & p
) : ItemRender(i2
, 0, p
)
978 void Render(RenderObject
* /*r*/, double /*time*/, bool /*reflect*/)
983 int GetLiftHeight(double time
, int t
)
986 time
= LIFT_TIME
-time
;
987 time
= time
/ LIFT_TIME
;
992 time
= (3 - 2*time
)*time
*time
;
994 time
= (3 - 2*time
)*time
*time
;
996 return (int)((TILE_H_LIFT_UP
+4) * time
);
998 return (int)((TILE_H_LIFT_UP
-4) * time
) + 4;
1001 struct TileRender
: public RenderStage
1006 double specialDuration
;
1008 TileRender(int i
, Pos
const & _p
, int _special
=0) : special(_special
), t(i
), p(_p
), specialDuration(LASER_LINE_TIME
)
1011 void Render(RenderObject
* r
, double time
, bool reflect
)
1013 if (t
==0 && special
==0)
1016 if (special
&& (t
==LIFT_UP
|| t
==LIFT_DOWN
) && time
<LIFT_TIME
)
1018 int y
= GetLiftHeight(time
, t
);
1021 RenderTile(reflect
, TILE_LIFT_BACK
, p
.getScreenX(), p
.getScreenY());
1022 RenderTile(reflect
, TILE_LIFT_SHAFT
, p
.getScreenX(), p
.getScreenY()+y
, y
-8);
1023 RenderTile(reflect
, TILE_LIFT_FRONT
, p
.getScreenX(), p
.getScreenY());
1027 RenderTile(reflect
, TILE_LIFT_SHAFT
, p
.getScreenX(), p
.getScreenY()-y
, y
);
1028 RenderTile(reflect
, LIFT_DOWN
, p
.getScreenX(), p
.getScreenY());
1031 else if (special
&& (t
==EMPTY
|| t
==TRAP
) && !reflect
&& time
< specialDuration
)
1034 if (time
< specialDuration
-LASER_FADE_TIME
)
1035 RenderTile(reflect
, TILE_ICE_LASER_REFRACT
, p
.getScreenX(), p
.getScreenY());
1037 RenderTile(reflect
, t
, p
.getScreenX(), p
.getScreenY());
1038 int base
= ((t
==EMPTY
) ? TILE_LASER_0
: TILE_LASER_REFRACT
);
1039 if (t
==EMPTY
&& time
>= specialDuration
-LASER_FADE_TIME
)
1040 base
= TILE_LASER_FADE_0
;
1043 for(int i
=0; foo
; foo
>>=1, i
++)
1045 RenderTile(reflect
, base
+i
, p
.getScreenX(), p
.getScreenY());
1047 else if (t
==FLOATING_BALL
)
1049 int y
= int(1.8 * sin(r
->seed
*PI
+ time
*4));
1052 if (time
> 2) return;
1053 if (reflect
) return;
1054 srand(int(r
->seed
* 0xfff));
1055 for (int i
=0; i
<20 - int(time
*10); i
++)
1057 int x
= int((((rand() & 0xfff) - 0x800) / 10) * time
);
1058 int y
= int((((rand() & 0xfff) - 0x800) / 10) * time
);
1059 RenderTile(true, 19 + ((i
+int(time
*5))&1)*10, p
.getScreenX() + x
, p
.getScreenY() - 14 + y
);
1063 RenderTile(true, 18, p
.getScreenX(), p
.getScreenY() - 14);
1066 RenderBoat(reflect
, int(special
)&255, p
.getScreenX(), p
.getScreenY(), y
);
1068 RenderTile(reflect
, t
, p
.getScreenX(), p
.getScreenY() + (reflect
? -y
: y
));
1070 else if (t
!= EMPTY
)
1071 RenderTile(reflect
, t
, p
.getScreenX(), p
.getScreenY());
1073 static void RenderBoat(bool reflect
, int d
, int x
, int y
, int yo
)
1076 RenderGirl(reflect
, d
, 0, x
, y
, -yo
);
1077 RenderTile(reflect
, FLOATING_BALL
, x
, y
+yo
);
1080 RenderGirl(reflect
, d
, 0, x
, y
, -yo
);
1081 RenderTile(true, 17, x
, y
+yo
-TILE_H_REFLECT_OFFSET
);
1086 struct TileRotateRender
: public TileRender
1091 TileRotateRender(int i
, Pos
const & p
, Dir _d
, int m
) : TileRender(i
, p
), d(_d
), mode(m
)
1093 void Render(RenderObject
* r
, double time
, bool reflect
)
1097 double f
= time
/ ROTATION_TIME
;
1099 if (mode
& 1) f
+= 0.5;
1108 if (mode
& 1) f
=1-f
; else f
=f
;
1112 TileRender::Render(r
, time
, reflect
);
1115 Pos dd
= (Pos(0,0)+d
);
1116 int x
= p
.getScreenX() + int(dd
.getScreenX()*(f
));
1117 int y
= p
.getScreenY() + int(dd
.getScreenY()*(f
));
1120 RenderBoat(reflect
, (mode
&1) ? (d
+MAX_DIR
/2)%MAX_DIR
: d
, x
, y
, 2);
1122 RenderTile(reflect
, t
, x
, y
);
1127 struct LaserRender
: public RenderStage
1133 LaserRender(Pos _p
, int dir
, int r
) : p(_p
), d(dir
), range(r
)
1136 void Render(RenderObject
* /*r*/, double /*time*/)
1141 struct ExplosionRender
: public RenderStage
1148 ExplosionRender(Pos _p
, int _pow
=0, int t
=0) : p(_p
), power(_pow
), type(t
)
1153 virtual int GetDepth(double /*time*/)
1158 void Render(RenderObject
* /*r*/, double time
, bool reflect
)
1160 if (type
==1 && time
> 2.5)
1161 type
= -1, new WinLoseScreen(false);
1163 // if (reflect) return;
1164 if (time
> 3) return;
1166 int q
= 50 - int(time
* 35);
1169 for (int i
=0; i
<q
; i
++)
1171 int x
= p
.getScreenX();
1172 int y
= p
.getScreenY() + (rand() & 31)-16;
1173 int xs
= ((rand() & 63) - 32);
1174 int ys
= (-10 - (rand() & 127)) * (1+power
);
1175 if (type
) ys
*=2, xs
/=2;
1176 x
+= int(xs
* (1+time
*(2+power
)));
1177 int yo
= int(time
*time
*128 + ys
*time
);
1178 //if (yo > 0) yo=-yo;//continue;
1184 if (!reflect
&& ys
<-60)
1186 const double T
= 0.06;
1187 double ct
= -ys
/ 128.0;
1190 x
= p
.getScreenX() + int(xs
* (1+ct
*(2+power
)));
1193 time
> ct
+3*T
? TILE_SPLASH_3
: time
> ct
+2*T
? TILE_SPLASH_2
: time
> ct
+T
? TILE_SPLASH_1
: TILE_WATER_PARTICLE
+1,
1201 time
- i
*0.003 < 0.2 ? TILE_WATER_PARTICLE
+1 : TILE_WATER_PARTICLE
,
1202 x
, y
+(reflect
?-1:1)*yo
);
1211 i
<q
-20 || time
<0.3 ? TILE_LASER_HEAD
: i
<q
-10 || time
<0.6 ? TILE_FIRE_PARTICLE_1
: TILE_FIRE_PARTICLE_2
,
1212 x
, y
+(reflect
?-1:1)*yo
);
1217 struct DisintegrateRender
: public RenderStage
1224 DisintegrateRender(Pos _p
, int _pow
=0, int _t
=0) : p(_p
), height(_pow
), type(_t
)
1229 void Render(RenderObject
* /*r*/, double time
, bool reflect
)
1232 RenderTile(reflect
, height
? COLLAPSE_DOOR
: COLLAPSABLE
, p
.getScreenX(), p
.getScreenY());
1234 if (time
> 50.0/70.0) return;
1235 if (reflect
) return;
1237 int q
= 50 - int(time
* 70);
1239 for (int i
=0; i
<q
; i
++)
1241 int x
= (rand() % (TILE_W3
-8))-TILE_W3
/2+4;
1242 int y
= (rand() % (TILE_H2
-8))-TILE_H1
+4;
1243 if (x
<-TILE_WL
/2 && ABS(y
)<-TILE_WL
/2-x
) continue;
1244 if (x
>TILE_WL
/2 && ABS(y
)>x
-TILE_WL
/2) continue;
1246 if (height
) yo
-= rand() % TILE_HUP
;
1247 x
+= p
.getScreenX();
1248 y
+= p
.getScreenY() + 4;
1249 int xs
= 0;//((rand() & 63) - 32);
1250 int ys
= (- (rand() & 31));
1251 x
+= int(xs
* (1+time
*(2)));
1253 yo
+= int(time
*time
*128 + ys
*time
);
1254 if (type
) yo
= -yo
*2;
1255 //if (yo > 0) yo=-yo;//continue;
1256 int t
= type
? TILE_BLUE_FRAGMENT
: TILE_GREEN_FRAGMENT
;
1260 RenderTile(false, t
, x
, y
+(reflect
?-yo
:yo
));
1264 struct BuildRender
: public RenderStage
1272 BuildRender(Pos _p
, Dir _d
, int _h
, int _r
=0, int _type
=0) : p(_p
), dir(_d
), reverse(_r
), height(_h
), type(_type
)
1276 void Render(RenderObject
* /*r*/, double time
, bool reflect
)
1278 if (time
>= BUILD_TIME
)
1279 RenderTile(reflect
, height
^ reverse
? (type
? COLLAPSE_DOOR2
: COLLAPSE_DOOR
) : (type
? COLLAPSABLE2
: COLLAPSABLE
), p
.getScreenX(), p
.getScreenY());
1283 RenderTile(reflect
, type
? COLLAPSABLE2
: COLLAPSABLE
, p
.getScreenX(), p
.getScreenY());
1285 double dist
= time
* 2 / BUILD_TIME
;
1288 Pos from
= p
+ ((dir
+MAX_DIR
/2)%MAX_DIR
);
1292 double offset
= (dist
*0.7) + 0.3;
1293 int x
= from
.getScreenX() + int((p
.getScreenX()-from
.getScreenX()) * offset
);
1294 int y
= from
.getScreenY() + int((p
.getScreenY()-from
.getScreenY()) * offset
- dist
*(1-dist
)*(TILE_HUP
*4));
1295 RenderTile(reflect
, TILE_GREEN_FRAGMENT
, x
, y
);
1301 if (reverse
) dist
= 1-dist
;
1303 if (dist
> 0 && !height
)
1306 for (int i
=0; i
<=int(dist
*15); i
++)
1308 int x
= p
.getScreenX(), y
= p
.getScreenY();
1309 double d
= (i
+ fmod(dist
*15, 1))/10.0;
1310 int x1
= int(sin(d
*5+time
)*MIN(d
,1)*TILE_W2
/2);
1311 int y1
= int(cos(d
*5+time
)*MIN(d
,1)*TILE_H1
*0.7);
1312 RenderTile(reflect
, TILE_GREEN_FRAGMENT
, x
+x1
, y
+y1
+4);
1313 RenderTile(reflect
, TILE_GREEN_FRAGMENT
, x
-x1
, y
-y1
+4);
1316 if (dist
> 0 && height
)
1318 int yo
= int((1-dist
)*(TILE_HUP
*1.3));
1319 if (yo
> TILE_HUP
*1.1)
1320 RenderTile(reflect
, TILE_WHITE_TILE
, p
.getScreenX(), p
.getScreenY());
1323 RenderTile(reflect
, type
? COLLAPSABLE2
: COLLAPSABLE
, p
.getScreenX(), p
.getScreenY());
1324 RenderTile(reflect
, type
? COLLAPSE_DOOR2
: COLLAPSE_DOOR
, p
.getScreenX(), p
.getScreenY()+(reflect
? -yo
: yo
), yo
+6);
1325 RenderTile(reflect
, type
? TILE_BLUE_FRONT
: TILE_GREEN_FRONT
, p
.getScreenX(), p
.getScreenY());
1329 if (yo
< TILE_HUP
/2)
1331 RenderTile(reflect
, type
? COLLAPSE_DOOR2
: COLLAPSE_DOOR
, p
.getScreenX(), p
.getScreenY()+(reflect
? -yo
: yo
), yo
);
1334 RenderTile(reflect
, type
? COLLAPSABLE2
: COLLAPSABLE
, p
.getScreenX(), p
.getScreenY());
1342 struct PlayerRender
: public RenderStage
1352 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
)
1354 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
)
1356 int dist
= MAX(ABS(p
.x
-target
.x
), ABS(p
.y
-target
.y
));
1363 virtual int GetDepth(double time
)
1365 double f
= speed
? time
/ speed
: 1;
1367 if (f
==1) dead
= this->dead
;
1369 if (f
==1 || f
>0.5 && p_h
>target_h
)
1370 return target
.x
+target
.y
*2;
1371 return MAX(target
.x
+target
.y
*2 , p
.x
+p
.y
*2);
1374 void Render(RenderObject
* /*ro*/, double time
, bool reflect
)
1377 double f
= speed
? time
/ speed
: 1;
1379 if (f
==1) dead
= this->dead
;
1381 int x
= p
.getScreenX();
1382 int y
= p
.getScreenY();
1383 int x2
= target
.getScreenX();
1384 int y2
= target
.getScreenY();
1386 int shadow_h
= (int)((p_h
+(target_h
-p_h
)*f
)*TILE_HUP2
);
1388 if (x
==x2
&& y
==y2
&& p_h
!=target_h
)
1390 h
= TILE_H_LIFT_UP
- GetLiftHeight(time
, p_h
? LIFT_DOWN
: LIFT_UP
);
1395 int dist
= MAX(ABS(p
.x
-target
.x
), ABS(p
.y
-target
.y
));
1396 int arc
= dist
*dist
;
1397 int h1
= p_h
* TILE_HUP2
;;
1398 int h2
= target_h
* TILE_HUP2
;
1399 if (dist
==2 && h1
!=0)
1403 shadow_h
= f
>=0.7 ? int(shadow_h
*(f
-0.7)/0.3) : 0;
1406 arc
= speed
> JUMP_TIME
? 7 : 2;
1408 h
= (int)(h1
+(h2
-h1
)*f
);
1409 // if (x==x2 && y==y2)
1413 //h += int(TILE_H_LIFT_UP/3 * (1-f));
1414 h
+= (int)(f
*(1-f
)*TILE_HUP2
*arc
);
1426 //frame = ((int)(f*4) % 4);
1427 //if (frame==2) frame=0; else if (frame==3) frame=2;
1430 else if (f
==1 || x
==x2
&& y
==y2
) // stationary
1436 frame
= type
? 2 : 1;
1442 RenderTile( false, TILE_SPHERE
,
1444 (int)(y
+(y2
-y
)*f
) - shadow_h
1457 dead ? TILE_SPHERE_OPEN : TILE_SPHERE_DONE,
1466 struct HexPuzzle
: public State
1470 #define MAX_TILECHANGE 64 // TODO: don't have a magic upper limit
1479 TileChange(Pos _p
, Tile _t
, int _i
) : p(_p
), t(_t
), item(_i
)
1481 void Restore(HexPuzzle
* w
)
1483 w
->SetTile(p
,t
,false,false);
1484 w
->SetItem(p
,item
,false,false);
1488 TileChange t
[MAX_TILECHANGE
];
1497 void Add(TileChange
const & tc
)
1499 for (int i
=0; i
<numT
; i
++)
1502 if (numT
>=MAX_TILECHANGE
)
1503 FATAL("numT>=MAX_TILECHANGE");
1507 void New(Dir pmove
, Pos
& pp
, int* items
, double t
, int sc
)
1509 numItems
[0] = items
[0];
1510 numItems
[1] = items
[1];
1512 playerMovement
= pmove
;
1517 void Restore(HexPuzzle
* w
)
1519 for (int i
=numT
-1; i
>=0; i
--)
1523 w
->player
= playerPos
;
1524 w
->player_items
[0] = numItems
[0];
1525 w
->player_items
[1] = numItems
[1];
1526 w
->player_score
= score
;
1528 //w->renderer.player.Add(new PlayerRender(playerPos, w->GetHeight(playerPos), false), w->time);
1533 char* special
[MAP_SIZE
][MAP_SIZE
];
1534 Tile map
[MAP_SIZE
][MAP_SIZE
];
1535 int32_t map_item
[MAP_SIZE
][MAP_SIZE
];
1536 int tileCount
[NumTileTypes
];
1537 int32_t levelPar
, levelDiff
;
1540 int player_items
[2];
1542 int numComplete
, numLevels
, numMastered
, numLevelsFound
;
1549 WorldRenderer renderer
;
1554 Undo undo
[MAX_UNDO
];
1556 LevelInfo
* currentLevelInfo
;
1558 char currentFile
[1000];
1565 LevelInfo
* GetLevelInfo(const char* f
)
1567 if (strstr(f
, "Levels\\") == f
)
1569 if (currentLevelInfo
!=0 && strcmp(currentLevelInfo
->file
, f
)==0)
1570 return currentLevelInfo
;
1575 if (t
<= numComplete
)
1578 static char tmp1
[100];
1579 static LevelInfo tmp
= {0, "", tmp1
};
1580 sprintf(tmp1
, "Complete %d more %s to unlock!", t
-numComplete
, t
-numComplete
==1?"level":"levels");
1584 for (unsigned int i
=0; i
<sizeof(levelNames
)/sizeof(levelNames
[0]); i
++)
1585 if (strcmp(f
, levelNames
[i
].file
)==0)
1586 return &levelNames
[i
];
1587 static LevelInfo tmp
= {0, "", "<<NO NAME>>"};
1591 #ifdef MAP_EDIT_HACKS
1592 int GetAutoTile(const char * level
, bool tiletype
)
1594 FILE* f
= file_open(filename
, "rb");
1598 if (f
&& fscanf(f
, "%d", &version
)==1 && (version
==3 || version
==4))
1600 if (strstr(level
,"mk"))
1603 fgetc(f
); // Remove '\n' character
1606 unsigned char bounds
[4];
1608 fread(&par
, sizeof(par
), 1, f
);
1612 fread(&diff
, sizeof(diff
), 1, f
);
1613 diff
= SWAP32(diff
);
1615 fread(bounds
, sizeof(bounds
), 1, f
);
1616 fread(&playerStart
, sizeof(playerStart
), 1, f
);
1617 playerStart
.x
= SWAP32(playerStart
.x
);
1618 playerStart
.y
= SWAP32(playerStart
.y
);
1622 for (int i
=bounds
[0]; i
<=bounds
[1]; i
++)
1623 for (int j
=bounds
[2]; j
<=bounds
[3]; j
++)
1625 unsigned char comp
= map
[i
][j
] | (map_item
[i
][j
]<<5);
1626 fread(&comp
, sizeof(comp
), 1, f
);
1627 int t
= comp
& 0x1f;
1628 int item
= (comp
>> 5) & 3;
1629 for (int i
=highval
+1; i
<sizeof(value_order
)/sizeof(value_order
[0]); i
++)
1630 if (t
!=0 && t
==value_order
[i
]
1631 || item
!=0 && item
==(value_order
[i
]>>8))
1637 tile
= value_order
[highval
];
1638 if (tile
==0x100) tile
= COLLAPSABLE3
;
1639 if (tile
==0x200) tile
= SWITCH
;
1640 if (tile
==LIFT_UP
) tile
= LIFT_DOWN
;
1644 if (value_order
[highval
] == LIFT_UP
)
1662 numComplete
= numLevels
= numMastered
= numLevelsFound
= 0;
1663 for (int i
=0; i
<MAP_SIZE
; i
++)
1664 for (int j
=0; j
<MAP_SIZE
; j
++)
1665 ActivateSpecial(Pos(i
,j
), 0);
1666 for (int i
=0; i
<MAP_SIZE
; i
++)
1667 for (int j
=0; j
<MAP_SIZE
; j
++)
1668 ActivateSpecial(Pos(i
,j
), 2);
1669 numComplete
= numLevels
= numMastered
= numLevelsFound
= 0;
1670 for (int i
=0; i
<MAP_SIZE
; i
++)
1671 for (int j
=0; j
<MAP_SIZE
; j
++)
1672 ActivateSpecial(Pos(i
,j
), 0);
1678 if (strcmp(mapname
, currentFile
)==0)
1680 // for (int i=0; i<32; i++)
1681 // HintMessage::FlagTile(i);
1682 if (numComplete
>= UNLOCK_SCORING
&& !progress
.general
.scoringOn
)
1684 HintMessage::FlagTile(26);
1685 progress
.general
.scoringOn
= 1;
1686 InitSpecials(); // Re-initialise with gold ones available
1688 HintMessage::FlagTile(25);
1692 for (int i
=0; i
<MAP_SIZE
; i
++)
1693 for (int j
=0; j
<MAP_SIZE
; j
++)
1695 int t
= GetTile(Pos(i
,j
));
1696 int item
= GetItem(Pos(i
,j
));
1698 HintMessage::FlagTile(t
);
1700 HintMessage::FlagTile(item
+20);
1702 HintMessage::FlagTile(EMPTY
);
1711 UpdateCursor(Pos(-1,-1));
1723 player_items
[0] = player_items
[1] = 0;
1725 if (strlen(currentSlot
) == 0)
1732 if (!isFadeRendering
&& time
!=0)
1734 renderer().Add(new FadeRender(-1), time
);
1740 renderer
.Reset(time
);
1743 for (int t
=0; t
<NumTileTypes
; t
++)
1746 for (int i
=0; i
<MAP_SIZE
; i
++)
1747 for (int j
=0; j
<MAP_SIZE
; j
++)
1750 int item
= GetItem(p
);
1752 renderer(p
,true).Add(new ItemRender(item
, GetTile(p
)==EMPTY
, p
), time
);
1757 for (int i
=0; i
<MAP_SIZE
; i
++)
1758 for (int j
=0; j
<MAP_SIZE
; j
++)
1768 renderer(p
).Add(new TileRender(t
, p
), time
);
1772 renderer
.player
.Add(new PlayerRender(player
, GetHeight(player
), dead
), time
);
1774 renderer
.player
.Add(new PlayerRender(Pos(-100,-100), 0, true), time
);
1777 int bounds
[4] = {player
.getScreenX(),player
.getScreenX(),player
.getScreenY(),player
.getScreenY()};
1778 for (int i
=0; i
<MAP_SIZE
; i
++)
1779 for (int j
=0; j
<MAP_SIZE
; j
++)
1782 if (map
[i
][j
] !=0 || map_item
[i
][j
]!=0)
1784 int x1
= p
.getScreenX();
1785 int y1
= p
.getScreenY();
1786 int x2
= x1
+ TILE_W3
;
1787 int y2
= y1
+ TILE_H2
;
1788 y1
-= TILE_H2
; // Make sure objects/player will be properly visible
1790 if (x1
<bounds
[0]) bounds
[0] = x1
;
1791 if (x2
>bounds
[1]) bounds
[1] = x2
;
1792 if (y1
<bounds
[2]) bounds
[2] = y1
;
1793 if (y2
>bounds
[3]) bounds
[3] = y2
;
1800 sx
= bounds
[0] - int(TILE_W2
*6.35);
1801 sy
= (bounds
[3] + bounds
[2] - SCREEN_H
) / 2 - TILE_H2
/2;
1805 sx
= (bounds
[1] + bounds
[0] - SCREEN_W
) / 2 - TILE_W3
/2;
1806 sy
= (bounds
[3] + bounds
[2] - SCREEN_H
) / 2 - TILE_H2
/2;
1818 // time = 1; // Guarantee we can't try and do things at time=0
1820 renderer().Add(new ScrollRender(sx
, sy
), time
);
1821 renderer().Add(new FadeRender(1), time
);
1826 char* ReadAll(FILE* f
)
1829 // FIXME: According to http://userpage.fu-berlin.de/~ram/pub/pub_jf47ht20Ht/c_faq_de
1830 // undefined for binary streams! (POSIX does not differ between ascii and binary, so
1831 // we are on the safe side in Linux)
1832 fseek(f
, 0, SEEK_END
);
1834 fseek(f
, 0, SEEK_SET
);
1835 char* c
= loadPtr
= new char [size
];
1836 endLoad
= loadPtr
+ size
;
1837 fread(c
, 1, size
, f
);
1841 static char *loadPtr
, *endLoad
;
1842 static unsigned int fread_replace(void* d
, unsigned int size
, unsigned int num
, FILE*)
1844 unsigned int remain
= (endLoad
- loadPtr
) / size
;
1845 if (remain
< num
) num
= remain
;
1846 memcpy(d
, loadPtr
, size
*num
);
1847 loadPtr
+= size
*num
;
1851 int GetPar(const char * level
, bool getdiff
=false)
1853 if (strcmp(level
, currentFile
)==0)
1854 return getdiff
? levelDiff
: levelPar
;
1856 #ifdef USE_LEVEL_PACKFILE
1857 PackFile1::Entry
* e
= levelFiles
.Find(level
);
1859 loadPtr
= (char*)e
->Data();
1860 endLoad
= loadPtr
+ e
->DataLen();
1864 FILE* f
= file_open(level
, "rb");
1867 typedef unsigned int _fn(void*, unsigned int, unsigned int, FILE*);
1868 _fn
* fn
= (loadPtr
? (_fn
*)fread_replace
: (_fn
*)fread
);
1870 int32_t par
= 99999, diff
= 0;
1874 return getdiff
? diff
: par
;
1876 fn(&version
, 2, 1, f
); // skip to relevant point
1878 if (fn(&par
, sizeof(par
), 1, f
) != 1)
1882 size_t ret
= fn(&diff
, sizeof(diff
), 1, f
);
1883 diff
= SWAP32(diff
);
1884 if (ret
!= 1 || diff
<0 || diff
>10)
1887 #ifdef USE_LEVEL_PACKFILE
1888 loadPtr
= endLoad
= 0;
1894 return getdiff
? diff
: par
;
1897 bool LoadSave(const char * filename
, bool save
)
1904 showScoring
= false;
1905 LevelSave
* l
= progress
.GetLevel(filename
, true);
1906 if (progress
.general
.scoringOn
&& l
&& l
->Completed() )
1910 #ifdef USE_LEVEL_PACKFILE
1913 PackFile1::Entry
* e
= levelFiles
.Find(filename
);
1914 if (!e
) return false;
1916 if (currentFile
!= filename
) // equal (overlapping) strings are forbidden
1917 strcpy(currentFile
, filename
);
1918 currentLevelInfo
= GetLevelInfo(currentFile
);
1920 loadPtr
= (char*)e
->Data();
1921 endLoad
= loadPtr
+ e
->DataLen();
1922 _LoadSave(NULL
, save
);
1923 loadPtr
= endLoad
= 0;
1929 FILE* f
= file_open(filename
, save
? "wb" : "rb");
1932 strcpy(currentFile
, filename
);
1934 currentLevelInfo
= GetLevelInfo(currentFile
);
1938 char* data
= ReadAll(f
);
1941 loadPtr
= endLoad
= 0;
1956 /** \brief Writes/reads game status to/from a file
1958 * The game data file is written in little endian so it can be shared
1959 * across different machines.
1961 void _LoadSave(FILE* f
, bool save
)
1963 typedef unsigned int _fn(void*, unsigned int, unsigned int, FILE*);
1964 _fn
* fn
= save
? (_fn
*)fwrite
: (loadPtr
? (_fn
*)fread_replace
: (_fn
*)fread
);
1967 int version
= VERSION
; // 1--9
1969 fprintf(f
, "%d\n", version
);
1973 if (fn(&c
, 1, 1, f
) != 1)
1977 // Remove '\n' character
1983 for (int i
=0; i
<MAP_SIZE
; i
++)
1984 for (int j
=0; j
<MAP_SIZE
; j
++)
1986 delete [] special
[i
][j
];
1993 for (int i
=0; i
<MAP_SIZE
; i
++)
1994 for (int j
=0; j
<MAP_SIZE
; j
++) {
1995 map
[i
][j
] = SWAP32(map
[i
][j
]);
1996 fn(&map
[i
][j
], sizeof(map
[i
][j
]), 1, f
);
1997 map
[i
][j
] = SWAP32(map
[i
][j
]);
2000 player
.x
= SWAP32(player
.x
);
2001 player
.y
= SWAP32(player
.y
);
2002 fn(&player
, sizeof(player
), 1, f
);
2003 player
.x
= SWAP32(player
.x
);
2004 player
.y
= SWAP32(player
.y
);
2006 for (int i
=0; i
<MAP_SIZE
; ++i
)
2007 for (int j
=0; j
<MAP_SIZE
; ++j
)
2008 map_item
[i
][j
] = SWAP32(map_item
[i
][j
]);
2009 if (fn(map_item
, sizeof(map_item
), 1, f
) == 0)
2010 memset(map_item
, 0, sizeof(map_item
));
2011 for (int i
=0; i
<MAP_SIZE
; ++i
)
2012 for (int j
=0; j
<MAP_SIZE
; ++j
)
2013 map_item
[i
][j
] = SWAP32(map_item
[i
][j
]);
2015 else if (version
>=2 && version
<=4)
2017 unsigned char bounds
[4];
2020 bounds
[0]=bounds
[1]=player
.x
;
2021 bounds
[2]=bounds
[3]=player
.y
;
2022 for (int i
=0; i
<MAP_SIZE
; i
++)
2023 for (int j
=0; j
<MAP_SIZE
; j
++)
2024 if (map
[i
][j
] !=0 || map_item
[i
][j
]!=0 || special
[i
][j
]!=0)
2026 if (i
<bounds
[0]) bounds
[0] = i
;
2027 if (i
>bounds
[1]) bounds
[1] = i
;
2028 if (j
<bounds
[2]) bounds
[2] = j
;
2029 if (j
>bounds
[3]) bounds
[3] = j
;
2034 memset(map
, 0, sizeof(map
));
2035 memset(map_item
, 0, sizeof(map_item
));
2039 levelPar
= SWAP32(levelPar
);
2040 fn(&levelPar
, 1, sizeof(levelPar
), f
);
2041 levelPar
= SWAP32(levelPar
);
2047 levelDiff
= SWAP32(levelDiff
);
2048 fn(&levelDiff
, 1, sizeof(levelDiff
), f
);
2049 levelDiff
= SWAP32(levelDiff
);
2054 fn(bounds
, sizeof(bounds
), 1, f
);
2055 player
.x
= SWAP32(player
.x
);
2056 player
.y
= SWAP32(player
.y
);
2057 fn(&player
, sizeof(player
), 1, f
);
2058 player
.x
= SWAP32(player
.x
);
2059 player
.y
= SWAP32(player
.y
);
2061 int offsetx
=0, offsety
=0;
2063 if (!save
&& bounds
[1]-bounds
[0]<15) // Hacky - don't recenter map...
2065 // Re-position map to top left (but leave a bit of space)
2066 // (This ensures the laser/boat effects don't clip prematurely against the edges of the screen)
2067 offsetx
= SCREEN_W
/2/TILE_W2
+ 1 - (bounds
[0]+bounds
[1]/2);
2068 offsety
= SCREEN_H
/2/TILE_H2
+ SCREEN_W
/2/TILE_W2
- (bounds
[2]+bounds
[3]/2);
2069 offsetx
= MAX(0, offsetx
);
2070 offsety
= MAX(0, offsety
);
2071 // if (bounds[0] > 2)
2072 // offsetx = 2 - bounds[0];
2073 // if (bounds[2] > 2)
2074 // offsety = 2 - bounds[2];
2076 bounds
[0] += offsetx
;
2077 bounds
[1] += offsetx
;
2078 bounds
[2] += offsety
;
2079 bounds
[3] += offsety
;
2080 player
.x
+= offsetx
;
2081 player
.y
+= offsety
;
2083 for (int i
=bounds
[0]; i
<=bounds
[1]; i
++)
2084 for (int j
=bounds
[2]; j
<=bounds
[3]; j
++)
2086 unsigned char comp
= map
[i
][j
] | (map_item
[i
][j
]<<5);
2087 fn(&comp
, sizeof(comp
), 1, f
);
2088 map
[i
][j
] = comp
& 0x1f;
2089 map_item
[i
][j
] = (comp
>> 5) & 3;
2094 for (int i
=bounds
[0]; i
<=bounds
[1]; i
++)
2095 for (int j
=bounds
[2]; j
<=bounds
[3]; j
++)
2098 int16_t len
= strlen(special
[i
][j
]);
2099 unsigned char x
=i
, y
=j
;
2100 fn(&x
, sizeof(x
), 1, f
);
2101 fn(&y
, sizeof(y
), 1, f
);
2103 fn(&len
, sizeof(len
), 1, f
);
2105 fn(special
[i
][j
], 1, len
, f
);
2113 if (!fn(&x
, sizeof(x
), 1, f
))
2115 fn(&y
, sizeof(y
), 1, f
);
2116 x
+= offsetx
; y
+= offsety
;
2117 fn(&len
, sizeof(len
), 1, f
);
2120 char* tmp
= new char[len
+1];
2124 SetSpecial(Pos(x
,y
), tmp
, true, false);
2129 return; // Unsupported version!
2133 // Save when returning to map!
2136 progress
.general
.completionPercentage
= numComplete
*100/numLevels
;
2137 progress
.general
.masteredPercentage
= numMastered
*100/numLevels
;
2138 LoadSaveProgress(true);
2142 void SetTile(Pos
const & p
, Tile t
, bool updateRenderer
=true, bool undoBuffer
=true)
2144 if (p
.x
<0 || p
.x
>MAP_SIZE
)
2146 if (p
.y
<0 || p
.y
>MAP_SIZE
)
2148 if (map
[p
.x
][p
.y
] == t
)
2150 if (map
[p
.x
][p
.y
] == t
)
2153 tileCount
[map
[p
.x
][p
.y
]]--;
2157 undo
[numUndo
].Add(Undo::TileChange(p
,GetTile(p
),GetItem(p
)));
2162 renderer(p
).Add(new TileRender(t
, p
), time
);
2165 Tile
GetTile(Pos
const & p
)
2167 if (p
.x
<0 || p
.x
>=MAP_SIZE
)
2169 if (p
.y
<0 || p
.y
>=MAP_SIZE
)
2171 return map
[p
.x
][p
.y
];
2174 int GetHeight(Pos
const & p
)
2176 return tileSolid
[GetTile(p
)]==1;
2179 char* GetSpecial(Pos
const & p
)
2181 if (p
.x
<0 || p
.x
>=MAP_SIZE
)
2183 if (p
.y
<0 || p
.y
>=MAP_SIZE
)
2185 return special
[p
.x
][p
.y
];
2188 void SetSpecial(Pos
const & p
, char * d
, bool use_pointer
=false, bool auto_activate
=true)
2190 if (p
.x
<0 || p
.x
>=MAP_SIZE
|| p
.y
<0 || p
.y
>=MAP_SIZE
)
2197 delete [] special
[p
.x
][p
.y
];
2198 if (!use_pointer
&& d
)
2201 special
[p
.x
][p
.y
] = new char [strlen(d
) + 1];
2202 strcpy(special
[p
.x
][p
.y
], d
);
2205 special
[p
.x
][p
.y
] = d
;
2207 if (special
[p
.x
][p
.y
]==0)
2208 renderer(p
,true).Add(new ItemRender(GetItem(p
), GetTile(p
)==EMPTY
, p
), time
);
2209 else if (auto_activate
)
2210 ActivateSpecial(p
, 0);
2213 int GetLevelState(Pos
const & p
, int recurse
=0)
2215 char* x
= GetSpecial(p
);
2218 LevelSave
* l
= progress
.GetLevel(x
, false);
2222 if (strcmp(x
, STARTING_LEVEL
)==0)
2224 if (x
[0]=='_' && l
&& l
->unlocked
)
2227 if (l
&& l
->Completed())
2234 int par
= GetPar(x
);
2235 if (progress
.general
.scoringOn
&& l
->PassesPar( par
))
2242 for (Dir d
=0; d
<MAX_DIR
; d
++)
2244 int i
= GetLevelState(p
+d
, 1);
2245 // if (i>1 || i==1 && t>1)
2246 if (i
>=1 && t
>2 || t
>=1 && i
>2)
2257 void ActivateSpecial(Pos
const & p
, int type
)
2259 if (p
.x
<0 || p
.x
>=MAP_SIZE
|| p
.y
<0 || p
.y
>=MAP_SIZE
)
2262 char * x
= special
[p
.x
][p
.y
];
2264 if (x
==0 || x
[0]==0)
2267 if (type
==2 && x
[0]=='_') // Phase2 init - unlock
2269 int t
= GetLevelState(p
);
2270 int target
= atoi(x
+1), targetM
= 0;
2271 if (target
>1000) targetM
=target
=target
-100;
2272 if (t
> 1 && numComplete
>= target
&& numMastered
>= targetM
)
2274 LevelSave
* l
= progress
.GetLevel(x
, true);
2279 renderer(p
, true).Add(new LevelSelectRender(p
, 5, GetLevelState(p
)>>8), time
+0.01);
2280 renderer().Add(new ExplosionRender(p
, 0), time
+ 0.6);
2281 renderer().Add(new ExplosionRender(p
, 1), time
+ 1.1);
2282 renderer(p
, true).Add(new LevelSelectRender(p
, -1, GetLevelState(p
)>>8), time
+ 1.1);
2287 if (type
==0) // Init & count levels
2291 int t
= GetLevelState(p
);
2292 int unlock
= progress
.GetLevel(x
, true)->unlocked
;
2293 LevelSelectRender
* lsr
= new LevelSelectRender( p
, unlock
? -1 : (t
>>8) ? 5 : 1, t
>>8 );
2294 if ((t
>>8) && p
.x
> mapRightBound
) mapRightBound
= p
.x
;
2295 #ifdef MAP_EDIT_HACKS
2296 lsr
->magic
= -atoi(x
+1);
2297 SetTile(p
, LIFT_DOWN
, true, false);
2299 SetTile(p
, EMPTY
, true, false);
2301 renderer(p
,true).Add(lsr
, time
);
2305 //printf("Level: %s\n", x);
2307 int t
= GetLevelState(p
);
2309 if (t
&& !GetItem(p
))
2316 currentLevelInfo
= 0;
2320 LevelSave
* l
= progress
.GetLevel(x
, true);
2325 renderer(p
, true).Add(new LevelSelectRender(p
, -1, 0), time
+0.01);
2326 renderer().Add(new ExplosionRender(p
, 0), time
+ 0.6);
2327 renderer(p
, true).Add(new LevelSelectRender(p
, t
& 0xff, t
>>8), time
+ 0.6);
2331 if (p
.x
> mapRightBound
) mapRightBound
= p
.x
;
2338 LevelSelectRender
* lsr
= new LevelSelectRender( p
, t
& 0xff, t
>>8 );
2340 #ifdef MAP_EDIT_HACKS
2342 int t
= GetAutoTile(x
, true);
2343 int v
= GetAutoTile(x
, false);
2344 if (MAP_EDIT_HACKS_DISPLAY_UNLOCK
)
2347 lsr
->magic
= GetPar(x
, true);
2349 SetTile(p
, t
, true, false);
2351 SetTile(p
, EMPTY
, true, false);
2354 renderer(p
,true).Add(lsr
, time
);
2359 if (type
==1 && x
[0]!='_') // Clicked on
2361 int t
= GetLevelState(p
);
2369 void SetItem(Pos
const & p
, int t
, bool updateRenderer
=true, bool undoBuffer
=true)
2371 if (p
.x
<0 || p
.x
>MAP_SIZE
)
2373 if (p
.y
<0 || p
.y
>MAP_SIZE
)
2375 if (map_item
[p
.x
][p
.y
] == t
)
2379 undo
[numUndo
].Add(Undo::TileChange(p
,GetTile(p
),GetItem(p
)));
2381 map_item
[p
.x
][p
.y
] = t
;
2384 renderer(p
,true).Add(new ItemRender(t
, GetTile(p
)==EMPTY
, p
), time
);
2387 Tile
GetItem(Pos
const & p
)
2389 if (p
.x
<0 || p
.x
>=MAP_SIZE
)
2391 if (p
.y
<0 || p
.y
>=MAP_SIZE
)
2393 return map_item
[p
.x
][p
.y
];
2396 void LoadSaveProgress(bool save
)
2398 FILE* f
= file_open(currentSlot
, save
? "wb" : "rb");
2401 progress
.LoadSave(f
, save
);
2412 LoadSaveProgress(false);
2416 LoadSaveProgress(true);
2419 SDL_Surface
* Load(const char * bmp
, bool colourKey
=true)
2421 typedef unsigned int uint32
;
2424 SDL_Surface
* g
= 0;
2427 if (strstr(bmp
, ".bmp"))
2429 g
= SDL_LoadBMP(bmp
);
2433 strcpy(strstr(out
, ".bmp"), ".dat");
2435 // SDL_PixelFormat p;
2437 // SDL_Surface* tmp = SDL_ConvertSurface(g, &p, SDL_SWSURFACE);
2439 short w
=g
->w
, h
=g
->h
;
2440 char* buf
= (char*) g
->pixels
;
2443 while (IsEmpty(g
, w
-1, 0, 1, h
) && w
>1)
2445 while (IsEmpty(g
, 0, h
-1, w
, 1) && h
>1)
2449 FILE* f
= file_open(out
, "wb");
2450 fwrite(&w
, sizeof(w
), 1, f
);
2451 fwrite(&h
, sizeof(h
), 1, f
);
2453 uint32 mask
= IMAGE_DAT_OR_MASK
;
2454 for (int i
=0; i
<(int)w
*h
; )
2456 uint32 c
= (*(uint32
*)&buf
[(i
%w
)*3 + (i
/w
)*g
->pitch
] | mask
);
2458 while (i
< (int)w
*h
&& c
== (*(uint32
*)&buf
[(i
%w
)*3 + (i
/w
)*g
->pitch
] | mask
))
2467 fwrite(&c
, sizeof(c
), 1, f
);
2470 fwrite(&i0
, sizeof(i0
), 1, f
);
2480 FILE* f
= file_open(bmp
, "rb");
2481 if (!f
) FATAL("Unable to open file", bmp
);
2484 fread(&w
, sizeof(w
), 1, f
);
2485 fread(&h
, sizeof(h
), 1, f
);
2488 if (w
>1500 || h
>1500 || w
<=0 || h
<=0) FATAL("Invalid file", bmp
);
2490 tmp
= new uint32
[(int)w
*h
];
2494 for (int p
=0; p
<(int)w
*h
; p
++)
2500 fread(&c
, sizeof(c
), 1, f
);
2504 fread(&cnt
, sizeof(cnt
), 1, f
);
2508 tmp
[p
] = c
| 0xff000000;
2511 g
= SDL_CreateRGBSurfaceFrom(tmp
, w
, h
, 32, w
*4,
2520 if (!g
) FATAL("Unable to create SDL surface");
2522 SDL_SetColorKey(g
, SDL_SRCCOLORKEY
, SDL_MapRGB(g
->format
, WATER_COLOUR
));
2523 SDL_Surface
* out
= SDL_DisplayFormat(g
);
2526 if (!out
) FATAL("Unable to create SDL surface (SDL_DisplayFormat)");
2530 #ifdef USE_LEVEL_PACKFILE
2531 PackFile1 levelFiles
;
2535 SDL_WM_SetCaption(GAMENAME
, 0);
2539 #ifdef USE_LEVEL_PACKFILE
2540 FILE* f
= file_open("levels.dat", "rb");
2542 FATAL("Unable to open file", "levels.dat");
2552 currentLevelInfo
= 0;
2559 memset(map
, 0, sizeof(map
));
2560 memset(map_item
, 0, sizeof(map_item
));
2561 memset(special
, 0, sizeof(special
));
2565 // player = Pos(1,11);
2575 progress
.GetLevel(STARTING_LEVEL
, true)->unlocked
= 1;
2576 if (!progress
.GetLevel(STARTING_LEVEL
, true)->Completed())
2578 LoadSave(STARTING_LEVEL
, false);
2584 LoadSave(mapname
, false);
2589 if (!activeMenu
|| activeMenu
->renderBG
)
2591 SDL_Rect src
= {0,0,screen
->w
,screen
->h
};
2592 SDL_Rect dst
= {0,0,screen
->w
,screen
->h
};
2595 int boundW
= mapBG
->w
;
2597 boundW
= MIN(boundW
, (mapRightBound
+4) * TILE_W2
- TILE_W1
);
2599 src
.x
= scrollX
- initScrollX
;
2600 if (src
.x
+src
.w
> boundW
)
2602 int diff
= src
.x
+src
.w
- boundW
;
2613 //scrollY = initScrollY;
2616 mapScrollX
= scrollX
;
2618 SDL_BlitSurface(mapBG
, &src
, screen
, &dst
);
2621 SDL_BlitSurface(gradient
, &src
, screen
, &dst
);
2623 renderer
.Render(time
, true);
2625 if (!hintsDone
&& !isFadeRendering
)
2632 SDL_Rect src
= {0,SCREEN_H
-1,SCREEN_W
,1};
2633 SDL_Rect dst
= {0,SCREEN_H
-1,SCREEN_W
,1};
2634 for (int i
=0; i
<SCREEN_H
; i
++)
2637 dst
.y
= src
.y
= SCREEN_H
-1-i
;
2643 src
.x
+= (int)( sin(i
*0.9 + time
*3.7) * sin(i
*0.3 + time
*0.7)*4 );
2644 src
.y
+= (int)( (sin(i
*0.3 - time
*2.2) * sin(i
*0.48 + time
*0.47) - 1) * 1.99 );
2648 src
.x
+= (int)( sin(i
*0.5 + time
*6.2) * sin(i
*0.3 + time
*1.05) * 5 );
2649 src
.y
+= (int)( (sin(i
*0.4 - time
*4.3) * sin(i
*0.08 + time
*1.9) - 1) * 2.5 );
2651 SDL_BlitSurface(screen
, &src
, screen
, &dst
);
2656 SDL_BlitSurface(mapBG2
, &src
, screen
, &dst
);
2658 renderer
.Render(time
, false);
2660 if (!isRenderMap
&& !isMap
&& !isFadeRendering
)
2662 int v
[3] = {player_items
[0], player_items
[1], player_score
};
2663 if (numUndo
> 1 && time
< undo
[numUndo
-2].endTime
)
2666 while (i
>1 && time
<undo
[i
-1].time
)
2668 v
[0] = undo
[i
].numItems
[0];
2669 v
[1] = undo
[i
].numItems
[1];
2670 v
[2] = undo
[i
].score
;
2672 if (numUndo
>1 && time
< undo
[0].time
)
2675 Print(0,0,"Anti-Ice: %d", v
[0]);
2676 Print(0,FONT_SPACING
,"Jumps: %d", v
[1]);
2677 Print(0,FONT_SPACING
*2,"Score: %d (%d)", v
[2], player_score
);
2678 Print(0,FONT_SPACING
*3,"Par: %d", levelPar
);
2679 Print(0,FONT_SPACING
*4,"Diff: %d", levelDiff
);
2682 Print(0, SCREEN_H
-FONT_SPACING
, " Par: %d Current: %d", levelPar
, v
[2]);
2685 Print(0,0," Anti-Ice: %d", v
[0]);
2687 Print(0,0," Jumps: %d", v
[1]);
2690 if (isRenderMap
&& isMap
&& !isFadeRendering
)
2693 Print(0,0,"Points: %d", numComplete
+numMastered
);
2694 Print(0,FONT_SPACING
,"Discovered: %d%% (%d/%d)", numLevelsFound
*100/numLevels
, numLevelsFound
, numLevels
);
2695 Print(0,FONT_SPACING
*2,"Complete: %d%% (%d)", numComplete
*100/numLevels
, numComplete
);
2696 Print(0,FONT_SPACING
*3,"Mastered: %d%% (%d)", numMastered
*100/numLevels
, numMastered
);
2698 if (numComplete
==numLevels
&& progress
.general
.endSequence
>0)
2699 Print(0, SCREEN_H
-FONT_SPACING
, " %d%% Mastered", numMastered
*100/numLevels
);
2701 Print(0, SCREEN_H
-FONT_SPACING
, " %d%% Complete", numComplete
*100/numLevels
);
2703 if (numMastered
>= numLevels
&& progress
.general
.endSequence
< 2)
2705 progress
.general
.endSequence
= 2;
2706 LoadSaveProgress(true);
2708 new Fader(-1, -7, 0.3);
2710 if (numComplete
>= numLevels
&& progress
.general
.endSequence
< 1)
2712 progress
.general
.endSequence
= 1;
2713 LoadSaveProgress(true);
2715 new Fader(-1, -5, 0.3);
2719 if ((currentLevelInfo
|| noMouse
) && isMap
&& isRenderMap
&& !activeMenu
&& isFadeRendering
<=0)
2726 int pad
= SCREEN_W
/80;
2727 // SDL_Rect src = {0, 0, uiGraphics->w, uiGraphics->h};
2728 SDL_Rect dst
= {pad
, SCREEN_H
-TILE_H2
-pad
, 0, 0};
2729 // dst.x = p.getScreenX() + TILE_W3/2 - scrollX;
2730 // dst.y = p.getScreenY() - src.h/2 - scrollY;
2731 dst
.x
= p
.getScreenX() - scrollX
;
2732 dst
.y
= p
.getScreenY() - scrollY
- FONT_SPACING
*3 - FONT_SPACING
/2;
2733 // if (dst.x > SCREEN_W*2/3) dst.x -= TILE_W3 + src.w;
2734 // if (dst.y+src.h > screen->h-pad) dst.y = screen->h-pad - src.h;
2736 RenderTile(false, 0, p
.getScreenX(), p
.getScreenY());
2737 // SDL_BlitSurface(uiGraphics, &src, screen, &dst);
2739 // dst.x += src.w/2;
2741 if (currentLevelInfo
)
2745 PrintC(true, dst
.x
, dst
.y
- FONT_SPACING
/4, currentLevelInfo
->name
);
2747 if (currentLevelInfo
->file
[0]!=0)
2749 if (player_score
> 0)
2751 if (progress
.general
.scoringOn
)
2753 PrintC(false, dst
.x
, dst
.y
+ FONT_SPACING
*4 - FONT_SPACING
/4, "Best:% 3d", player_score
);
2754 PrintC(false, dst
.x
, dst
.y
+ FONT_SPACING
*5 - FONT_SPACING
/4, "Par:% 3d", levelPar
);
2757 PrintC(false, dst
.x
, dst
.y
+ FONT_SPACING
*4 - FONT_SPACING
/4, "Completed", player_score
);
2760 PrintC(false, dst
.x
, dst
.y
+ FONT_SPACING
*4 - FONT_SPACING
/4, "Incomplete", player_score
);
2766 if (win
&& numUndo
> 0 && time
> undo
[numUndo
-1].endTime
+ 2)
2768 if (currentFile
[0] && winFinal
==0)
2770 LevelSave
* l
= progress
.GetLevel(currentFile
, true);
2772 new WinLoseScreen(true, player_score
, showScoring
? levelPar
: 0, l
&& showScoring
&& l
->Completed() ? l
->GetScore() : 0);
2774 if (l
->IsNewCompletionBetter(player_score
))
2776 l
->SetScore(player_score
);
2778 l
->SetSolution(numUndo
);
2780 for (int i
=0; i
<numUndo
; i
++)
2781 l
->SetSolutionStep(i
, undo
[i
].playerMovement
);
2792 // Move up "level complete" writing so it doesn't feel like everything's ground to a halt...
2793 if (win
&& numUndo
> 0 && time
> undo
[numUndo
-1].endTime
&& !winFinal
)
2795 double t
= (time
- undo
[numUndo
-1].endTime
) / 2;
2799 int y
= SCREEN_H
/3 - FONT_SPACING
+ 1;
2800 y
= SCREEN_H
+ int((y
-SCREEN_H
)*t
);
2801 PrintC(true, SCREEN_W
/2, y
, "Level Complete!");
2806 activeMenu
->Render();
2813 RenderTile(false, editTile
, mousex
+scrollX
, mousey
+scrollY
);
2817 Print(mousex
, mousey
-2, "\x7f");
2824 return tileCount
[t
];
2826 int Swap(Tile t
, Tile t2
)
2828 const int num
= Count(t
) + Count(t2
);
2829 if (t
==t2
|| num
==0)
2830 return Count(t
); // Nothing to do...
2833 for (int x
=0; x
<MAP_SIZE
; x
++)
2834 for (int y
=0; y
<MAP_SIZE
; y
++)
2836 if (GetTile(Pos(x
,y
))==t
)
2839 SetTile(Pos(x
,y
), t2
);
2841 else if (GetTile(Pos(x
,y
))==t2
)
2844 SetTile(Pos(x
,y
), t
);
2851 int Replace(Tile t
, Tile t2
)
2853 const int num
= Count(t
);
2854 if (t
==t2
|| num
==0)
2855 return num
; // Nothing to do...
2858 for (int x
=0; x
<MAP_SIZE
; x
++)
2859 for (int y
=0; y
<MAP_SIZE
; y
++)
2866 SetTile(p
, t2
, false);
2868 if (t
==COLLAPSE_DOOR
&& t2
==COLLAPSABLE
)
2869 renderer(p
).Add(new BuildRender(p
, -1, 1, 1), time
+ (rand() & 255)*0.001);
2870 else if (t
==COLLAPSE_DOOR2
&& t2
==COLLAPSABLE2
)
2871 renderer(p
).Add(new BuildRender(p
, -1, 1, 1, 1), time
+ (rand() & 255)*0.001);
2892 void UpdateCursor(Pos
const & s
)
2895 if (s
.x
!=_s
.x
|| s
.y
!=_s
.y
)
2899 char* sp
= GetSpecial(s
);
2906 currentLevelInfo
= 0;
2907 levelPar
= player_score
= -1;
2908 if (GetLevelState(s
)>=2)
2910 LevelSave
* l
= progress
.GetLevel(sp
, true);
2913 currentLevelInfo
= GetLevelInfo(sp
);
2914 levelPar
= GetPar(sp
);
2915 player_score
= l
->GetScore();
2921 sprintf(tmp
, "Special(%d,%d): %s (%d)", s
.x
, s
.y
, sp
? sp
: "<None>", GetPar(sp
));
2922 SDL_WM_SetCaption(tmp
, NULL
);
2925 else if (currentFile
[0])
2928 SDL_WM_SetCaption(currentFile
, NULL
);
2931 currentLevelInfo
= 0;
2936 virtual void Mouse(int x
, int y
, int dx
, int dy
, int button_pressed
, int button_released
, int button_held
)
2940 activeMenu
->Mouse(x
,y
,dx
,dy
,button_pressed
,button_released
,button_held
);
2944 if (isFadeRendering
)
2949 if (button_pressed
==2 || button_pressed
==4 && isMap
)
2951 KeyPressed(SDLK_ESCAPE
, 0);
2952 keyState
[SDLK_ESCAPE
] = 0;
2960 Pos s
= Pos::GetFromWorld(x
,y
);
2961 if (tileSolid
[GetTile(Pos::GetFromWorld(x
,y
+TILE_HUP
))] == 1)
2962 s
= Pos::GetFromWorld(x
,y
+TILE_HUP
);
2969 if (button_held
& ~button_pressed
& 4)
2978 if (isMap
&& (button_pressed
& 1))
2980 ActivateSpecial(s
, 1);
2983 if (!isMap
&& win
&& winFinal
)
2985 if (button_pressed
& 1)
2993 if((button_pressed
& 1) || (button_held
& 1) && (numUndo
==0 || time
>=undo
[numUndo
-1].endTime
))
2995 if(s
.x
==player
.x
&& s
.y
==player
.y
)
2997 // Don't activate jump powerup without a new click
2998 if (button_pressed
& 1)
3001 else if(s
.x
==player
.x
&& s
.y
<player
.y
)
3003 else if(s
.x
==player
.x
&& s
.y
>player
.y
)
3005 else if(s
.y
==player
.y
&& s
.x
<player
.x
)
3007 else if(s
.y
==player
.y
&& s
.x
>player
.x
)
3009 else if(s
.y
+s
.x
==player
.y
+player
.x
&& s
.x
>player
.x
)
3011 else if(s
.y
+s
.x
==player
.y
+player
.x
&& s
.x
<player
.x
)
3014 if ((button_pressed
& 4) || (button_held
& 4) && (undoTime
< 0))
3021 if (!button_pressed
&& !button_held
)
3024 if (button_pressed
==1)
3026 editTile
= GetItem(s
)==1 ? -3 : GetItem(s
)==2 ? -2 : -1;
3028 if (button_held
==1 || button_pressed
==1)
3032 SetTile(s
, editTile
, true, false);
3034 SetItem(s
, editTile
==-2 ? 0 : editTile
==-1 ? 1 : 2, true, false);
3037 if (button_pressed
==2)
3039 editTile
= GetTile(s
);
3042 if (button_pressed
==8)
3044 editTile
=editTile
-1;
3045 if (editTile
<=0) editTile
=NumTileTypes
-1;
3048 if (button_pressed
==16)
3050 editTile
=editTile
+1;
3051 if (editTile
<=0) editTile
=1;
3052 if (editTile
==NumTileTypes
) editTile
=0;
3055 if (button_pressed
==64)
3060 renderer
.player
.Reset(-1);
3061 renderer
.player
.Add(new PlayerRender(player
, GetHeight(player
), dead
), 0);
3064 if (button_pressed
==256)
3066 char* fn
= LoadSaveDialog(false, true, "Select level");
3069 char * l
= strstr(fn
, "Levels");
3072 FILE * f
= file_open(l
,"rb");
3077 else if (l
[6]!=0 && l
[7]=='_')
3080 UpdateCursor(Pos(-1,-1));
3083 if (button_pressed
==512)
3085 SetSpecial(s
, NULL
);
3086 UpdateCursor(Pos(-1,-1));
3088 if (button_pressed
==1024)
3090 static char x
[1000] = "";
3091 if (!(s
.x
<0 || s
.x
>=MAP_SIZE
|| s
.y
<0 || s
.y
>=MAP_SIZE
))
3096 strcpy(x
, GetSpecial(s
));
3099 SetSpecial(s
, tmp
[0] ? tmp
: 0);
3101 SetTile(s
, EMPTY
, true, false);
3105 if (button_pressed
==32)
3107 editTile
= editTile
<0 ? 1 : -1;
3112 void CheckFinished()
3115 if (Count(COLLAPSABLE
)==0)
3117 if (Replace(COLLAPSE_DOOR
, COLLAPSABLE
) == 0)
3121 Replace(SWITCH
, NORMAL
);
3126 if (Count(COLLAPSABLE2
)==0)
3127 if (Replace(COLLAPSE_DOOR2
, COLLAPSABLE2
))
3133 bool Collide(Pos p
, bool high
)
3135 Tile t
= GetTile(p
);
3140 return tileSolid
[t
]==1;
3147 if (numUndo
==0) return;
3149 UndoDone(); // Complete previous undo...
3153 if (time
> undo
[numUndo
].endTime
)
3154 time
= undo
[numUndo
].endTime
;
3155 undoTime
= undo
[numUndo
].time
;
3157 undo
[numUndo
].Restore(this);
3163 renderer
.Reset(undoTime
);
3167 void ScoreDestroy(Pos p
)
3169 Tile t
= GetTile(p
);
3170 if (t
==COLLAPSABLE
|| t
==COLLAPSE_DOOR
)
3172 else if (t
!= EMPTY
)
3178 bool LaserTile(Pos p
, int mask
, double fireTime
)
3180 if (&renderer(p
) == &renderer(Pos(-1,-1)))
3182 //if (!renderer.Visible(p))
3186 if (time
<= renderer(p
).GetLastTime())
3187 if (fireTime
< renderer(p
).GetLastTime())
3189 renderer(p
).Add(tr
= new TileRender(GetTile(p
), p
, mask
), fireTime
);
3190 ((TileRender
*)renderer(p
).GetStage(time
+10/*HACKY!*/))->special
|= mask
;
3194 tr
= new TileRender(GetTile(p
), p
, mask
| ((TileRender
*)renderer(p
).GetStage(time
+10/*HACKY!*/))->special
);
3195 renderer(p
).Add(tr
, fireTime
);
3198 renderer(p
).Add(tr
= new TileRender(GetTile(p
), p
, mask
), fireTime
);
3202 tr
->specialDuration
= time
+ LASER_LINE_TIME
- fireTime
+ LASER_FADE_TIME
;
3206 void FireGun(Pos newpos
, Dir d
, bool recurse
, double fireTime
)
3208 static Pos hits
[100];
3209 static Dir hitDir
[100];
3210 static unsigned int numHits
=0;
3214 double starttime
= fireTime
;
3215 for (Dir fd
=((d
<0)?0:d
); fd
<((d
<0)?MAX_DIR
:d
+1); fd
++)
3217 fireTime
= starttime
;
3218 // starttime += 0.03;
3220 Pos p
= newpos
+ fd
;
3222 for (; range
<MAP_SIZE
; range
++, p
=p
+fd
)
3224 Tile t
= GetTile(p
);
3225 if (tileSolid
[t
]!=-1)
3228 renderer(p
).Add(new TileRender(tileSolid
[t
]==1 ? TILE_WHITE_WALL
: TILE_WHITE_TILE
, p
), fireTime
+0.1);
3231 for (i
=0; i
<numHits
; i
++)
3235 t
==TRAP
&& (hitDir
[i
]&(1<<fd
))==0
3240 if (i
>= sizeof(hits
)/sizeof(hits
[0]))
3242 hitDir
[i
] = 1 << fd
;
3248 hitDir
[i
] |= 1 << fd
;
3253 1<<((fd
+2) % MAX_DIR
)
3254 | 1<<((fd
+MAX_DIR
-2) % MAX_DIR
);
3256 if (LaserTile(p
, dirmask
, fireTime
))
3257 fireTime
+= (time
+LASER_LINE_TIME
- fireTime
) / 40;
3258 // fireTime += LASER_SEGMENT_TIME;
3260 FireGun(p
, (fd
+1) % MAX_DIR
, true, fireTime
);
3261 FireGun(p
, (fd
+MAX_DIR
-1) % MAX_DIR
, true, fireTime
);
3268 LaserTile(p
, 1<<(fd
%3), fireTime
);
3270 fireTime
+= (time
+LASER_LINE_TIME
- fireTime
) / 40;
3271 // fireTime += LASER_SEGMENT_TIME;
3275 // renderer().Add(new LaserRender(newpos, fd, range), time);
3280 //double _time = time;
3281 time
+= LASER_LINE_TIME
;
3282 for (unsigned int i
=0; i
<numHits
; i
++)
3285 Tile t
= GetTile(p
);
3292 renderer(p
).Add(new ExplosionRender(p
, t
==GUN
), time
);
3293 //renderer(p).Add(new TileRender(EMPTY, p), time+2);
3294 SetTile(p
, EMPTY
, false);
3297 renderer(p
,true).Add(new ItemRender(GetItem(p
), 1, p
), time
);
3301 for (Dir j
=0; j
<MAX_DIR
; j
++)
3303 if (GetTile(p
+j
)!=EMPTY
)
3305 renderer(p
+j
).Add(new TileRender(tileSolid
[GetTile(p
+j
)]==1 ? TILE_WHITE_WALL
: TILE_WHITE_TILE
, p
+j
), time
+0.05);
3306 renderer(p
+j
).Add(new ExplosionRender(p
+j
), time
+0.2);
3309 renderer(p
+j
,true).Add(new ItemRender(GetItem(p
+j
), 1, p
), time
);
3311 //renderer(p+j).Add(new TileRender(EMPTY, p+j), time+2.2);
3313 ScoreDestroy(p
+ j
);
3314 SetTile(p
+ j
, EMPTY
, false);
3319 time
+= MAX(LASER_FADE_TIME
, 0.15);
3324 int GetLastPlayerRot()
3326 RenderStage
* rs
= renderer
.player
.GetStage(-1);
3328 return ((PlayerRender
*)rs
)->r
;
3332 if (dead
|| win
|| isMap
)
3338 // Jump forwards in time to last move finishing
3339 if (numUndo
> 0 && time
< undo
[numUndo
-1].endTime
)
3340 time
= undo
[numUndo
-1].endTime
;
3342 double realTime
= time
;
3343 double endAnimTime
= time
;
3344 bool high
= (tileSolid
[GetTile(player
)] == 1);
3345 Pos playerStartPos
= player
;
3346 Pos oldpos
= player
;
3347 int oldPlayerHeight
= GetHeight(oldpos
);
3348 Pos newpos
= player
+ d
;
3350 int playerRot
= GetLastPlayerRot();
3351 if (d
!=-1 && d
!=playerRot
)
3353 while (d
!=playerRot
)
3355 if ((d
+6-playerRot
) % MAX_DIR
< MAX_DIR
/2)
3356 playerRot
= (playerRot
+1) % MAX_DIR
;
3358 playerRot
= (playerRot
+MAX_DIR
-1) % MAX_DIR
;
3362 if (GetTile(oldpos
) == FLOATING_BALL
)
3364 TileRender
* t
= new TileRender(FLOATING_BALL
, oldpos
);
3365 t
->special
= playerRot
+ 256;
3366 renderer(oldpos
).Add(t
, time
);
3368 renderer
.player
.Add(new PlayerRender(playerRot
, Pos(-20,-20), oldPlayerHeight
, Pos(-20,-20), oldPlayerHeight
, dead
), time
);
3372 PlayerRender
*p
= new PlayerRender(playerRot
, player
, oldPlayerHeight
, player
, oldPlayerHeight
, dead
);
3374 renderer
.player
.Add(p
, time
);
3381 if (d
<0 && player_items
[1]==0)
3386 if (tileSolid
[GetTile(newpos
)] == -1)
3391 if (Collide(newpos
, high
))
3398 // Don't change any real state before this point!
3399 if (numUndo
>= MAX_UNDO
)
3402 for(int i
=0; i
<MAX_UNDO
-1; i
++)
3403 undo
[i
] = undo
[i
+1];
3405 undo
[numUndo
].New(d
, player
, player_items
, time
, player_score
);
3412 double time0
= time
;
3413 time
+= 0.15; //Time for leave-tile fx
3415 switch (GetTile(oldpos
))
3418 SetTile(oldpos
, EMPTY
);
3419 renderer(oldpos
).Add(new DisintegrateRender(oldpos
), time
);
3424 // Don't need to CheckFinished - can't be collapse doors around
3425 // unless there're still collapsable tiles around.
3426 SetTile(oldpos
, EMPTY
);
3427 renderer(oldpos
).Add(new DisintegrateRender(oldpos
, 1), time
);
3431 SetTile(oldpos
, COLLAPSABLE
, false);
3432 renderer(oldpos
).Add(new DisintegrateRender(oldpos
, 0, 1), time
);
3437 case COLLAPSE_DOOR2
:
3438 SetTile(oldpos
, COLLAPSE_DOOR
, false);
3439 renderer(oldpos
).Add(new DisintegrateRender(oldpos
, 1, 1), time
);
3444 SetTile(oldpos
, COLLAPSABLE2
);
3448 time
= time0
; //End of leave-tile fx
3450 int retry_pos_count
=0;
3454 if (GetItem(newpos
)==1)
3456 renderer(newpos
, true).Add(new ItemCollectRender(GetItem(newpos
), newpos
), time
+ JUMP_TIME
/2);
3457 SetItem(newpos
, 0, false);
3460 if (GetItem(newpos
)==2)
3462 renderer(newpos
, true).Add(new ItemCollectRender(GetItem(newpos
), newpos
), time
+ JUMP_TIME
/2);
3463 SetItem(newpos
, 0, false);
3467 if (GetTile(player
) == FLOATING_BALL
)
3469 TileRender
* t
= new TileRender(FLOATING_BALL
, player
);
3471 renderer(oldpos
).Add(t
, time
);
3474 PlayerRender
*p
= new PlayerRender(playerRot
, player
, oldPlayerHeight
, newpos
, GetHeight(newpos
), dead
);
3476 // alternate leg (hacky!)
3484 if (retry_pos_count
!=0 && GetTile(player
)==TRAP
)
3490 p
->speed
= JUMP_TIME
* 1.5;
3491 renderer
.player
.Add(p
, time
);
3492 endAnimTime
= MAX(endAnimTime
, time
+ p
->speed
+0.001);
3497 switch (GetTile(newpos
))
3500 renderer(newpos
).Add(new TileRender(TILE_GREEN_CRACKED
, newpos
), time
);
3503 renderer(newpos
).Add(new TileRender(TILE_GREEN_CRACKED_WALL
, newpos
), time
);
3506 renderer(newpos
).Add(new TileRender(TILE_BLUE_CRACKED
, newpos
), time
);
3508 case COLLAPSE_DOOR2
:
3509 renderer(newpos
).Add(new TileRender(TILE_BLUE_CRACKED_WALL
, newpos
), time
);
3518 double pretime
= time
;
3521 for (Dir fd
=0; fd
<MAX_DIR
; fd
++)
3523 Tile t2
= GetTile(newpos
+ fd
);
3527 SetTile(newpos
+fd
, COLLAPSABLE
, false);
3528 renderer(newpos
+fd
).Add(new BuildRender(newpos
+fd
, fd
, 0), time
);
3530 else if (t2
==COLLAPSABLE
)
3533 SetTile(newpos
+fd
, COLLAPSE_DOOR
, false);
3534 renderer(newpos
+fd
).Add(new BuildRender(newpos
+fd
, fd
, 1), time
);
3537 if (done
) time
+= BUILD_TIME
;
3538 else time
= pretime
;
3540 endAnimTime
= MAX(endAnimTime
, time
+ 0.1);
3545 Swap(COLLAPSE_DOOR
, COLLAPSABLE
);
3551 renderer
.player
.Add(new PlayerRender(playerRot
, Pos(-30,-30), 0, Pos(-30,-30), 0, dead
), time
);
3552 while (tileSolid
[GetTile(newpos
+d
)]==-1)
3556 if (!renderer
.Visible(newpos
+d
))
3558 TileRender
* r
= new TileRender(FLOATING_BALL
, newpos
);
3560 renderer(newpos
).Add(r
, time
);
3562 PlayerRender
* pr
= new PlayerRender(playerRot
, newpos
, 0, newpos
, 0, dead
);
3563 pr
->speed
= JUMP_TIME
*1;
3564 renderer
.player
.Add(pr
, time
);
3572 newpos
= oldpos
+ d
;
3574 SetTile(oldpos
, EMPTY
, false);
3575 SetTile(newpos
, FLOATING_BALL
, false);
3577 renderer(oldpos
).Add(new TileRotateRender(FLOATING_BALL
, oldpos
, d
, 2), time
);
3578 renderer(oldpos
).Add(new TileRender(EMPTY
, oldpos
), time
+ ROTATION_TIME
/2);
3579 renderer(newpos
).Add(new TileRotateRender(FLOATING_BALL
, newpos
, (d
+3)%MAX_DIR
, 3), time
+ ROTATION_TIME
/2);
3581 // PlayerRender *p = new PlayerRender(playerRot, oldpos, 0, newpos, 0, dead);
3582 // p->speed = ROTATION_TIME*0.9;
3583 // renderer.player.Add(p, time);
3585 endAnimTime
= MAX(endAnimTime
, time
+ ROTATION_TIME
+ ROTATION_TIME
/2);
3586 time
+= ROTATION_TIME
;
3589 // renderer.player.Add(new PlayerRender(playerRot, player, 0, player, 0, 0), time);
3595 TileRender
* r
= new TileRender(FLOATING_BALL
, newpos
);
3596 r
->special
= playerRot
+ 256;
3597 renderer(newpos
).Add(r
, time
);
3605 SetTile(newpos
, GetTile(newpos
)==LIFT_UP
? LIFT_DOWN
: LIFT_UP
, false);
3606 renderer(newpos
).Add(new TileRender(GetTile(newpos
), newpos
, 1), time
);
3608 PlayerRender
*p
= new PlayerRender(playerRot
, newpos
, 1-GetHeight(newpos
), newpos
, GetHeight(newpos
), dead
);
3609 renderer
.player
.Add(p
, time
);
3610 endAnimTime
= MAX(endAnimTime
, time
+ JUMP_TIME
);
3618 if (Collide(newpos
+ d
, high
))
3620 if (Collide((newpos
+ d
) + d
, high
) == 1)
3621 newpos
= (newpos
+ d
);
3623 newpos
= (newpos
+ d
) + d
;
3624 if (tileSolid
[GetTile(newpos
)] == -1)
3631 for (Dir d
=0; d
<MAX_DIR
; d
++)
3633 Tile tmp
= GetTile(newpos
+ d
);
3634 renderer(newpos
+ d
).Add(new TileRotateRender(tmp
, newpos
+ d
, (d
+2)%MAX_DIR
, false), time
);
3636 Tile tmp
= GetTile(newpos
+ Dir(MAX_DIR
-1));
3637 for (Dir d
=0; d
<MAX_DIR
; d
++)
3639 Tile t2
= GetTile(newpos
+ d
);
3640 SetTile(newpos
+ d
, tmp
, false);
3641 renderer(newpos
+ d
).Add(new TileRotateRender(tmp
, newpos
+ d
, (d
+4)%MAX_DIR
, true), time
+ ROTATION_TIME
/2);
3642 if (GetItem(newpos
+ d
))
3643 renderer(newpos
+ d
,true).Add(new ItemRender(GetItem(newpos
+ d
), GetTile(newpos
+ d
)==EMPTY
, newpos
+d
), time
+ ROTATION_TIME
/2);
3647 endAnimTime
= MAX(endAnimTime
, time
+ROTATION_TIME
);
3648 // renderer(newpos).Add(new TileRotateRender(SPINNER, Dir(0), 0), time);
3656 if (player_items
[0]==0)
3658 if (tileSolid
[GetTile(newpos
+ d
)] == 1)
3660 newpos
= newpos
+ d
;
3661 if (tileSolid
[GetTile(newpos
)] == -1)
3668 SetTile(newpos
, COLLAPSABLE3
);
3676 FireGun(newpos
, d
, false, time
);
3678 endAnimTime
= MAX(endAnimTime
, time
);
3680 if (GetTile(newpos
)==EMPTY
)
3682 PlayerRender
* pr
= new PlayerRender(playerRot
, player
, 0, player
, 0, dead
);
3683 pr
->speed
= JUMP_TIME
*1;
3684 renderer
.player
.Add(pr
, time
);
3694 for (Dir fd=((d<0)?0:d); fd<((d<0)?MAX_DIR:d+1); fd++)
3696 Pos p = newpos + fd;
3698 for (range; range<MAP_SIZE; range++, p=p+fd)
3700 Tile t = GetTile(p);
3701 if (tileSolid[t]!=-1)
3703 hits[numHits++] = p;
3708 renderer().Add(new LaserRender(newpos, fd, range), time);
3711 double _time = time;
3713 for (int i=0; i<numHits; i++)
3716 Tile t = GetTile(p);
3718 renderer().Add(new ExplosionRender(p), time);
3724 for (Dir j=0; j<MAX_DIR; j++)
3726 ScoreDestroy(p + j);
3727 SetTile(p + j, EMPTY);
3729 if (GetTile(newpos)==EMPTY)
3733 endAnimTime = MAX(endAnimTime, time);
3743 endAnimTime
= MAX(endAnimTime
, time
);
3749 PlayerRender
* pr
= new PlayerRender(player
, 0, dead
);
3750 pr
->speed
= 0; // Don't sit around before disappearing!
3751 renderer
.player
.Add(pr
, time
);
3753 // If the tile we're drowning on isn't visible, give the ownership of the splash effect to the player, rather than a tile.
3754 if (renderer
.Visible(player
))
3755 renderer(player
).Add(new ExplosionRender(player
, 0, 1), time
);
3757 renderer
.player
.Add(new ExplosionRender(player
, 0, 1), time
);
3759 endAnimTime
= MAX(endAnimTime
, time
+2);
3766 undo
[numUndo
].endTime
= endAnimTime
;
3771 void Update(double timedelta
)
3778 activeMenu
->Update(timedelta
);
3783 for (int i
=0; i
<SDLK_LAST
; i
++)
3790 if (isMap
&& isRenderMap
)
3793 static double scrollHi
= 0;
3798 int xx
= noMouse
? keyboardp
.getScreenX()-scrollX
: mousex
;
3799 if (xx
> SCREEN_W
) xx
= SCREEN_W
;
3802 x
= (double)xx
/ (w
) - 1;
3803 if (xx
> SCREEN_W
- w
)
3804 x
= 1 - (double)(SCREEN_W
-xx
) / (w
);
3806 if (x
<-min
|| x
>min
)
3808 scrollHi
+= timedelta
* x
;
3809 scrollX
+= (int)scrollHi
;
3810 scrollHi
-= (int)scrollHi
;
3815 if (undoTime
>=0 && undoTime
< time
)
3817 double acc
= (time
- undoTime
) / 2;
3818 if (acc
< 3) acc
= 3;
3819 time
-= timedelta
* acc
;
3820 if (undoTime
>= time
)
3827 time
+= timedelta
* 20;
3830 void FileDrop(const char* filename
)
3832 LoadSave(filename
, false);
3837 if (keyState
[SDLK_LALT
] || keyState
[SDLK_LCTRL
])
3841 if (!isMap
&& !editMode
&& undoTime
< 0)
3843 if (keyState
[SDLK_z
] || keyState
[SDLK_BACKSPACE
] || keyState
[SDLK_u
])
3849 if (isMap
&& !editMode
)
3852 if ((keyState
[SDLK_q
] | keyState
[SDLK_KP7
]) & 2) keyboardp
.x
--;
3853 else if ((keyState
[SDLK_d
] | keyState
[SDLK_KP3
]) & 2) keyboardp
.x
++;
3854 else if ((keyState
[SDLK_e
] | keyState
[SDLK_KP9
]) & 2) keyboardp
.x
++, keyboardp
.y
--;
3855 else if ((keyState
[SDLK_a
] | keyState
[SDLK_KP1
]) & 2) keyboardp
.x
--, keyboardp
.y
++;
3856 else if ((keyState
[SDLK_w
] | keyState
[SDLK_KP8
] | keyState
[SDLK_UP
]) & 2) keyboardp
.y
--;
3857 else if ((keyState
[SDLK_s
] | keyState
[SDLK_KP2
] | keyState
[SDLK_DOWN
]) & 2) keyboardp
.y
++;
3858 else if ((keyState
[SDLK_LEFT
]) & 2) keyboardp
.x
--, keyboardp
.y
+=keyboardp
.x
&1;
3859 else if (((keyState
[SDLK_RIGHT
]) & 2)) { if (keyboardp
.x
< mapRightBound
) keyboardp
.y
-=keyboardp
.x
&1, keyboardp
.x
++; }
3860 else if ((keyState
[SDLK_RETURN
] | keyState
[SDLK_KP5
] | keyState
[SDLK_SPACE
] | keyState
[SDLK_KP_ENTER
]) & 2)
3862 // Simulate user clicking on it...
3863 Mouse(keyboardp
.getScreenX()-scrollX
, keyboardp
.getScreenY()-scrollY
, 0, 0, 1, 0, 0);
3870 UpdateCursor(keyboardp
);
3873 int min
[21] = { 17, 16, 15, 14, 13, 13, 13, 13, 13, 13, 12, 11, 11, 13, 12, 11, 8, 8, 7, 6, 7 };
3874 int max
[21] = { 20, 20, 19, 19, 19, 19, 18, 21, 20, 20, 19, 19, 18, 18, 17, 16, 16, 16, 15, 15, 14 };
3875 if (keyboardp
.x
< 3) keyboardp
.x
= 3;
3876 if (keyboardp
.x
> mapRightBound
) keyboardp
.x
= mapRightBound
;
3878 if (keyboardp
.y
< min
[keyboardp
.x
-3]) keyboardp
.y
= min
[keyboardp
.x
-3];
3879 if (keyboardp
.y
> max
[keyboardp
.x
-3]) keyboardp
.y
= max
[keyboardp
.x
-3];
3881 UpdateCursor(keyboardp
);
3883 else if (!editMode
&& (numUndo
==0 || time
>=undo
[numUndo
-1].endTime
))
3885 static int usedDiag
= 0;
3887 if (keyState
[SDLK_q
] || keyState
[SDLK_KP7
]) HandleKey('q', 0);
3888 else if (keyState
[SDLK_w
] || keyState
[SDLK_KP8
]) HandleKey('w', 0);
3889 else if (keyState
[SDLK_e
] || keyState
[SDLK_KP9
]) HandleKey('e', 0);
3890 else if (keyState
[SDLK_a
] || keyState
[SDLK_KP1
]) HandleKey('a', 0);
3891 else if (keyState
[SDLK_s
] || keyState
[SDLK_KP2
]) HandleKey('s', 0);
3892 else if (keyState
[SDLK_d
] || keyState
[SDLK_KP3
]) HandleKey('d', 0);
3894 else if (keyState
[SDLK_UP
] && keyState
[SDLK_LEFT
]) HandleKey('q', 0), usedDiag
=1;
3895 else if (keyState
[SDLK_UP
] && keyState
[SDLK_RIGHT
]) HandleKey('e', 0), usedDiag
=1;
3896 else if (keyState
[SDLK_DOWN
] && keyState
[SDLK_LEFT
]) HandleKey('a', 0), usedDiag
=1;
3897 else if (keyState
[SDLK_DOWN
] && keyState
[SDLK_RIGHT
]) HandleKey('d', 0), usedDiag
=1;
3898 else if (keyState
[SDLK_UP
] && !usedDiag
) HandleKey('w', 0);
3899 else if (keyState
[SDLK_DOWN
] && !usedDiag
) HandleKey('s', 0);
3904 void KeyReleased(int key
)
3908 bool KeyPressed(int key
, int mod
)
3914 bool eat
= activeMenu
->KeyPressed(key
, mod
);
3916 memset(keyState
, 0, sizeof(keyState
));
3921 if ((key
==SDLK_ESCAPE
&& (mod
& KMOD_CTRL
)))
3923 if (mod
& KMOD_SHIFT
)
3927 LoadSaveProgress(false);
3933 if (isFadeRendering
)
3936 return HandleKey(key
, mod
);
3939 bool HandleKey(int key
, int mod
)
3944 if (isMap
&& key
=='r' && (mod
& KMOD_ALT
))
3953 else if ((key
=='p' && !editMode
|| key
==SDLK_PAUSE
|| key
==SDLK_ESCAPE
))
3956 new PauseMenu(isMap
, progress
.GetLevel(STARTING_LEVEL
, true)->Completed(), progress
.general
.endSequence
>=1, progress
.general
.endSequence
>=2);
3960 else if (key
=='e' && (mod
& KMOD_ALT
))
3961 editMode
= !editMode
;
3963 else if (key
=='p' && (mod
& KMOD_ALT
) && numUndo
>0
3964 || key
>='0' && key
<='9' && (mod
& KMOD_SHIFT
) && !isMap
)
3966 if (key
>='0' && key
<='9')
3967 levelDiff
= (key
=='0') ? 10 : key
-'0';
3969 if (key
=='p' && levelPar
==0)
3970 levelPar
= player_score
;
3975 undo
[numUndo
-1].Restore(this);
3979 if (LoadSave(currentFile
, true))
3981 if (key
>='0' && key
<='9')
3987 /////////////////////////////////////////////////////////////////////////
3988 if (isMap
&& !editMode
)
3991 else if (key
==SDLK_KP9
|| key
=='e') Input(1), noMouse
=1;
3992 else if (key
==SDLK_KP3
|| key
=='d') Input(2), noMouse
=1;
3993 else if (key
==SDLK_KP1
|| key
=='a') Input(4), noMouse
=1;
3994 else if (key
==SDLK_KP7
|| key
=='q') Input(5), noMouse
=1;
3995 else if (key
==SDLK_KP8
|| key
=='w') Input(0), noMouse
=1;
3996 else if (key
==SDLK_KP2
|| (key
=='s' && (((mod
& (KMOD_CTRL
|KMOD_ALT
))==0)||!editMode
))) Input(3), noMouse
=1;
3997 else if (key
==SDLK_KP5
|| key
==SDLK_SPACE
|| key
==SDLK_RETURN
|| key
==SDLK_KP_ENTER
)
4000 if (win
&& winFinal
)
4001 LoadMap(), memset(keyState
, 0, sizeof(keyState
));
4006 else if (key
=='r' && (mod
& KMOD_CTRL
))
4007 LoadSave(currentFile
, false);
4010 else if (key
=='z' && (mod
& KMOD_ALT
))
4012 if (numUndo
>0 && !isMap
)
4014 time
= undo
[numUndo
-1].endTime
;
4015 undoTime
= undo
[0].time
;
4018 undo
[numUndo
-1].Restore(this);
4023 else if (key
=='z' || key
==SDLK_BACKSPACE
|| key
==SDLK_DELETE
|| key
=='u')
4030 else if (key
=='s' && (mod
& KMOD_ALT
)){
4031 if (win
&& strlen(currentFile
)>0 && !isMap
)
4034 strcpy(tmp
, currentFile
);
4035 ChangeSuffix(tmp
, "sol");
4036 FILE* f
= file_open(tmp
, "wb");
4039 for (int i
=0; i
<numUndo
; i
++)
4041 fputc(undo
[i
].playerMovement
, f
);
4050 else if (key
=='/' && (mod
& KMOD_ALT
)){
4058 if (mod
& KMOD_SHIFT
)
4060 LevelSave
* l
= progress
.GetLevel(currentFile
, false);
4061 if (l
&& l
->Completed())
4063 for (int i
=0; i
<l
->bestSolutionLength
; i
++)
4064 Input(l
->bestSolution
[i
]);
4073 strcpy(tmp
, currentFile
);
4074 ChangeSuffix(tmp
, "sol");
4075 FILE* f
= file_open(tmp
, "rb");
4079 while ((dir
= fgetc(f
)) != -1)
4100 else if (key
>='0' && key
<='9' && (mod
& KMOD_ALT
) && !isMap
)
4101 levelPar
= levelPar
*10 + key
-'0';
4102 else if (key
==SDLK_BACKSPACE
&& (mod
& KMOD_ALT
) && !isMap
)
4106 Mouse(mousex
, mousey
, 0, 0, 32, 0, mouse_buttons
);
4107 else if (key
=='p' && !(mod
& KMOD_ALT
))
4108 Mouse(mousex
, mousey
, 0, 0, 64, 0, mouse_buttons
);
4110 Mouse(mousex
, mousey
, 0, 0, 128, 0, mouse_buttons
);
4111 else if (key
==SDLK_RETURN
)
4112 Mouse(mousex
, mousey
, 0, 0, 256, 0, mouse_buttons
);
4113 else if (key
==SDLK_BACKSPACE
)
4114 Mouse(mousex
, mousey
, 0, 0, 512, 0, mouse_buttons
);
4116 Mouse(mousex
, mousey
, 0, 0, 1024, 0, mouse_buttons
);
4118 else if (key
=='s' && (mod
& KMOD_CTRL
)){
4119 char *fn
= LoadSaveDialog(true, true, "Save level");
4121 SDL_WM_SetCaption(currentFile
, NULL
);
4124 else if (key
=='o' && (mod
& KMOD_CTRL
)){
4125 char* fn
= LoadSaveDialog(false, true, "Open level");
4126 LoadSave(fn
, false);
4127 SDL_WM_SetCaption(currentFile
, NULL
);
4138 #define X(NAME,FILE,ALPHA) NAME = Load(DATA_DIR "/graphics/" FILE BMP_SUFFIX, ALPHA);
4139 #include "gfx_list.h"
4141 static int first
= 1;
4149 // unsigned int d = {
4152 // static SDL_Cursor * c = SDL_CreateCursor(data, mask, 32, 32, 1, 1);
4153 // SDL_SetCursor(c);
4158 #define X(NAME,FILE,ALPHA) if (NAME) SDL_FreeSurface(NAME), NAME=0;
4159 #include "gfx_list.h"
4161 virtual void ScreenModeChanged()
4168 MAKE_STATE(HexPuzzle
, SDLK_F1
, false);
4170 char * HexPuzzle::loadPtr
= 0;
4171 char * HexPuzzle::endLoad
= 0;