Merge branch 'reduce-cpu'
[hex-a-hop.git] / menus.h
blobe52e371c4783360077f837288d53f5f1f0d21a13
1 /*
2 Copyright (C) 2005-2007 Tom Beaumont
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or
7 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17 */
20 struct Menu;
21 Menu* activeMenu = 0;
22 Menu* deadMenu = 0;
24 static void HackKeyPress(int key, int mod)
26 int k = keyState[key];
27 StateMakerBase::current->KeyPressed(key, mod);
28 keyState[key] = k;
31 struct Menu {
32 bool renderBG;
34 Menu() : renderBG(true), under(activeMenu), time(0) { activeMenu = this; }
36 virtual ~Menu() {
37 if(this!=deadMenu) FATAL("this!=deadMenu");
38 deadMenu=under;
41 static void Pop()
43 if (!activeMenu) return;
44 Menu* m = activeMenu;
45 activeMenu = activeMenu->under;
46 m->under = deadMenu;
47 deadMenu = m;
50 virtual bool KeyPressed(int key, int /*mod*/)
52 if (key=='w' || key==SDLK_UP || key==SDLK_KP8 || key=='q' || key=='e' || key==SDLK_KP7 || key==SDLK_KP9)
53 Move(-1), noMouse=1;
54 else if (key=='s' || key==SDLK_DOWN || key==SDLK_KP2 || key=='a' || key=='d' || key==SDLK_KP1 || key==SDLK_KP3)
55 Move(1), noMouse=1;
56 else if (key==' ' || key==SDLK_RETURN)
58 Select();
59 noMouse=1;
61 else if (key==SDLK_ESCAPE || key==SDLK_BACKSPACE || key==SDLK_KP_PERIOD || key==SDLK_DELETE)
62 Cancel();
63 else
64 return false;
65 return true;
67 virtual void Mouse(int /*x*/, int /*y*/, int /*dx*/, int /*dy*/, int buttons_pressed, int /*buttons_released*/, int /*buttons*/)
69 if (buttons_pressed==4 || buttons_pressed==2)
70 Cancel();
73 virtual void Move(int /*dir*/) {}
74 virtual void Select() {}
75 virtual void Cancel() {}
77 virtual void Update(double timedelta) {time+=timedelta;}
78 virtual void Render() = 0;
80 Menu * under;
81 double time;
82 };
84 const char * hint[] = {
86 /*EMPTY*/
87 "Basic controls:|Move around with the keys Q,W,E,A,S,D or the numeric keypad. Alternatively, you can use the mouse and click on the tile you'd like to move to. Use 'U', backspace or the right mouse button to undo mistakes. The 'Esc' key (or middle mouse button) brings up a menu from which you can restart if you get stuck.",
88 /*NORMAL*/
89 0,
90 /*COLLAPSABLE*/
91 "Objective:|Your goal is to break all the green tiles. You mainly do this by jumping on them. They will crack when you land on them, and only disintegrate when you jump off. Try not to trap yourself!",
92 /*COLLAPSE_DOOR*/
93 "The coloured walls flatten themselves when there are no matching coloured tiles remaining.",
94 /*TRAMPOLINE*/
95 "You can bounce on the purple trampoline tiles to get around. But try not to fall in the water. If you do, remember you can undo with 'U', backspace or the right mouse button!",
96 /*SPINNER*/
97 "A red spinner tile will rotate the pieces around it when you step on it.",
98 /*WALL*/
99 0,
100 /*COLLAPSABLE2*/
101 "You don't need to destroy blue tiles to complete the level. But they'll turn green when you step off them, and you know what you have to do to green tiles...",
102 /*COLLAPSE_DOOR2*/
104 /*GUN*/
105 "Yellow laser tiles fire when you step on them. Shooting other laser tiles is more destructive.",
106 /*TRAP*/
107 "Ice is slippery! Please be careful!!",
108 /*COLLAPSABLE3*/
110 /*BUILDER*/
111 "The dark grey tiles with arrows on are builders. Landing on one creates green tiles in any adjacent empty tile, and turns green tiles into walls.",
112 /*SWITCH*/
114 /*FLOATING_BALL*/
115 "You can ride on the pink floating boats to get across water. They'll pop if you try and float off the edge of the screen though, so look where you're going.",
116 /*LIFT_DOWN*/
117 "The blue lifts go up or down when you land on them.",
118 /*LIFT_UP*/
121 0,0,0,0,
122 //Item 0 (21)
123 "The spiky anti-ice pickups turn icy tiles into blue ones. They get used automatically when you land on ice.",
124 //Item 1 (22)
125 "Collecting the golden jump pickups will allow you to do a big vertical jump. Try it out on different types of tile. Use the space bar or return key to jump. Or click on the tile you're currently on with the mouse.",
127 0,0,
129 // Map (25)
130 "Map Screen:|You can choose which level to attempt next from the map screen. Silver levels are ones you've cleared. Black levels are ones you haven't completed yet, but are available to play.",
132 // Scoring (26)
133 "New feature unlocked!|Each level has an efficiency target for you to try and beat. Every move you make and each non-green tile you destroy counts against you. Why not try replaying some levels and going for gold?",
135 0,0,0,
137 // End of help (30)
138 "Thanks for playing this little game. I hope you enjoy it! -- -- All content is Copyright 2005 Tom Beaumont email: tombeaumont@yahoo.com Any constructive criticism gratefully received!",
140 // First help page (31)
141 "Welcome to " GAMENAME "! This is a puzzle game based on hexagonal tiles. There is no time limit and no real-time elements, so take as long as you like. Use the cursor keys or click on the arrows to scroll through the help pages. More pages will be added as you progress through the game.",
144 struct HintMessage : public Menu
146 static int flags;
148 const char * msg;
149 char title[50];
150 int numLines;
151 int state;
153 static bool FlagTile(int t, bool newStuff=true)
155 if (t==LIFT_UP) t=LIFT_DOWN;
156 if (newStuff && (flags & (1<<t))) return false;
157 if (!newStuff && !(flags & (1<<t))) return false;
158 if (t>31) return false;
159 if (!hint[t]) return false;
161 flags |= 1<<t;
162 new HintMessage(hint[t]);
163 return true;
166 HintMessage(const char * m) { Init(m); }
168 void Init(const char * m)
170 numLines = 1;
171 state = 0; time = 0;
172 memset(title, 0, sizeof(title));
173 char * x = strstr(m, "|");
174 if (!x)
176 msg = m;
177 strcpy(title, "Info:");
179 else
181 strncpy(title, m, x-m);
182 msg=x+1;
185 const char * s = m;
186 while ((s=strstr(s, " ")))
187 s+=2, numLines++;
190 virtual void Render()
192 int y = SCREEN_H/4 + int(SCREEN_H*MAX(1-time*5, 0)*3/4);
193 if (state)
194 y = SCREEN_H/4 + int(SCREEN_H*(-time*5)*3/4);
196 Render(0, y);
198 if (!state && time>0.2)
199 Print(SCREEN_W*3/4, SCREEN_H-FONT_SPACING, "Press any key");
202 void Render(int x, int y)
204 SDL_Rect r2 = {x+TILE_W1, y, SCREEN_W-TILE_W1*2, numLines*FONT_SPACING+FONT_SPACING};
205 SDL_Rect r = {r2.x-2, r2.y-2-FONT_SPACING, r2.w+4, r2.h+4+FONT_SPACING};
206 SDL_FillRect(screen, &r, SDL_MapRGB(screen->format, 60,90,90));
207 SDL_FillRect(screen, &r2, SDL_MapRGB(screen->format, 20,50,50));
208 Print(r.x+FONT_SPACING/4, y-FONT_SPACING, title);
209 PrintC(true, x+SCREEN_W/2, y+FONT_SPACING/2, msg);
212 virtual void Mouse(int /*x*/, int /*y*/, int /*dx*/, int /*dy*/, int buttons_pressed, int /*buttons_released*/, int /*buttons*/)
214 if (buttons_pressed && state==0 && time>0.2)
215 state = 1, time=0;
218 bool KeyPressed(int /*key*/, int /*mod*/)
220 if (state==0 && time>0.2)
221 state = 1, time=0;
222 return true;
225 virtual void Update(double timedelta)
227 Menu::Update(timedelta);
228 if(state && time > 0.2)
229 Pop();
233 int HintMessage::flags = 1<<31 | 1<<30;
235 struct HintReview : public HintMessage
237 int page;
238 int page_dir;
239 int page_count;
240 int page_display;
241 HintReview() : HintMessage(hint[31]), page(31), page_dir(0), page_display(0)
243 page_count=0;
244 for (int i=0; i<32; i++)
245 if (flags & (1<<i))
246 page_count++;
249 void Cancel() { Pop(); }
250 void Select() { Pop(); }
253 virtual void Mouse(int x, int y, int dx, int dy, int buttons_pressed, int buttons_released, int buttons)
255 if (buttons_pressed==1)
257 if (y < SCREEN_H/4-FONT_SPACING+2)
258 Move(-1);
259 else if (y > SCREEN_H/4+FONT_SPACING*numLines+FONT_SPACING)
260 Move(1);
261 else
262 Cancel();
264 else
265 Menu::Mouse(x,y,dx,dy,buttons_pressed, buttons_released, buttons);
268 bool KeyPressed(int key, int mod)
270 if (key==SDLK_LEFT)
271 Move(-1);
272 else if (key==SDLK_RIGHT)
273 Move(1);
274 else
275 return Menu::KeyPressed(key, mod);
276 return true;
279 virtual void Render()
281 const double SPD = 10;
283 #ifdef EDIT
284 sprintf (title, "Help (Page --)", page_display+1, page_count);
285 #else
286 sprintf (title, "Help (Page %d/%d)", page_display+1, page_count);
287 #endif
289 int y=SCREEN_H/4;
290 if (state==0)
291 y = SCREEN_H/4+int(MAX(0, time*SPD)*-page_dir*SCREEN_H);
292 if (state==1)
293 y = SCREEN_H/4+int(MAX(0, 1-time*SPD)*page_dir*SCREEN_H);
295 //if (!noMouse)
297 PrintC(false, SCREEN_W/2, y-FONT_SPACING*2, "^");
298 PrintC(false, SCREEN_W/2, y+FONT_SPACING*(numLines+1)+FONT_SPACING/2-2, "_");
301 HintMessage::Render(0,y);
303 if (time > 1.0/SPD && page_dir && state==0)
305 do {
306 page = (page+page_dir) & 31;
307 #ifdef EDIT
308 if (hint[page]) break;
309 #endif
310 } while (hint[page]==0 || !(flags&(1<<page)));
311 Init(hint[page]);
313 page_display = (page_display + page_count + page_dir) % page_count;
315 time = 0;
316 state=1;
318 if (time>1.0/SPD && state==1)
319 state=0, page_dir = 0;
321 virtual void Update(double timedelta)
323 Menu::Update(timedelta);
325 void Move(int dir)
327 if (page_dir)
328 return;
329 time = 0;
330 page_dir = dir;
331 state = 0;
335 #define MAX_GAMESLOT 4
336 enum option {
337 OPT_GAMESLOT_0,
338 OPT_GAMESLOT_LAST = OPT_GAMESLOT_0 + MAX_GAMESLOT - 1,
339 OPT_RESUME,
340 OPT_RESTART,
341 OPT_GOTO_MAP,
342 OPT_GOTO_MAP_CONTINUE,
343 OPT_FULLSCREEN,
344 OPT_OPTIONS,
345 OPT_QUIT,
346 OPT_QUIT_CONFIRM,
347 OPT_QUIT_CANCEL,
348 OPT_QUIT_MENU_CONFIRM,
349 OPT_HELP,
350 OPT_GAMESLOT_NEW,
351 OPT_DELETE_CONFIRM,
352 OPT_DELETE_CANCEL,
353 OPT_UNDO,
354 OPT_BACK,
355 OPT_END, OPT_END2,
358 char optionSlotName[MAX_GAMESLOT][40] = { {0} };
359 char currentSlot[80] = "";
360 int freeSlot = -1;
361 char* GetSlotName(int i, char * t)
363 sprintf(t, "save%d.dat", i+1);
364 return t;
367 char * optionString[] = {
368 optionSlotName[0],
369 optionSlotName[1],
370 optionSlotName[2],
371 optionSlotName[3],
372 "Resume",
373 "Restart Level",
374 "Return to Map",
375 "Continue",
376 "Toggle Fullscreen",
377 "Options",
378 "Quit",
379 "Yes",
380 "No",
381 "Return to Title",
382 "Help",
383 "Start New Game",
384 "Yes, really delete it!",
385 "Don't do it!",
386 "Undo Last Move",
387 "OK",
388 "View Credits Sequence", "View Credits Sequence",
392 struct OptMenu : public Menu
394 int select;
395 int num_opt;
396 int opt[10];
397 bool left_align;
398 const char * title;
399 SDL_Rect r, r2;
401 OptMenu(const char * t) : select(0), title(t)
403 left_align = false;
404 num_opt = 0;
407 void Init()
409 r.w=SCREEN_W/2;
410 r.x=(SCREEN_W-r.w)/2;
411 r.y=SCREEN_H/3;
413 r2 = r;
415 const int SPACE = int(FONT_SPACING * 1.5);
417 r2.h = SPACE*num_opt + FONT_SPACING/2;
418 r.h = r2.h + (FONT_SPACING+2*2);
419 r.y -= FONT_SPACING+2;
420 r.w += 2*2;
421 r.x -= 2;
424 void RenderOption(int o, const char * s)
426 int y = r2.y + FONT_SPACING/2 + int(FONT_SPACING * 1.5) * o;
427 if (left_align)
429 int x = r.x + font[' '].w;
430 int x1 = x + (font[' '].w + font['>'].w + FONT_X_SPACING*2) / 2;
431 if (select==o)
433 //x += int( sin(time*9)*2.5 );
434 //y += int( sin(time*9 + 1.5)*1.5 );
435 Print(x, y, "> %s", s);
437 else
439 Print(x1, y, "%s", s);
442 else
444 int x = r.x + r.w/2;
445 if (select==o)
447 //x += int( sin(time*9)*2.5 );
448 //y += int( sin(time*9 + 1.5)*1.5 );
449 PrintC(false, x, y, "> %s <", s);
451 else
453 PrintC(false, x, y, "%s", s);
458 void Move(int dir)
460 select += dir;
461 if (select<0) select = num_opt-1;
462 if (select>=num_opt) select = 0;
464 virtual void Mouse(int x, int y, int dx, int dy, int buttons_pressed, int buttons_released, int buttons)
466 if (1)
468 if (x<r2.x || y<r2.y || x>r2.x+r2.w || y>r2.y+r2.h)
470 if (buttons_pressed!=4 && buttons_pressed!=2)
471 return;
473 else
475 select = (y-r2.y-FONT_SPACING/3) / int(FONT_SPACING*1.5);
476 if (select<0) select = 0;
477 if (select>=num_opt) select = num_opt-1;
480 if (buttons_pressed==1)
481 Select();
482 Menu::Mouse(x, y, dx, dy, buttons_pressed, buttons_released, buttons);
484 void Select();
486 void Render()
488 RenderBG();
489 RenderTitle();
490 RenderOptions();
492 void RenderBG()
494 SDL_FillRect(screen, &r, SDL_MapRGB(screen->format, 60,90,90));
495 SDL_FillRect(screen, &r2, SDL_MapRGB(screen->format, 20,50,50));
497 void RenderTitle()
499 if (left_align)
500 Print(r2.x+font[' '].w, r.y+4, title);
501 else
502 PrintC(false, r2.x+r2.w/2, r.y+4, title);
504 void RenderOptions()
506 for (int i=0; i<num_opt; i++)
507 RenderOption(i, optionString[opt[i]]);
509 void Cancel()
511 Pop();
515 struct WinLoseScreen : public OptMenu
517 bool win;
518 int score, par, best_score;
519 WinLoseScreen(bool _win, int _score=0, int _par=0, int _prev_score=0) :
520 OptMenu(_win ? _("Level Complete!") : _("Emi can't swim...")),
521 win(_win),
522 score(_score),
523 par(_par),
524 best_score(_prev_score)
526 if (!win)
527 opt[num_opt++] = OPT_UNDO;
528 if (!win)
529 opt[num_opt++] = OPT_RESTART;
530 opt[num_opt++] = win ? OPT_GOTO_MAP_CONTINUE : OPT_GOTO_MAP;
532 Init();
534 if (win)
536 r.h += FONT_SPACING * 3 + FONT_SPACING/2;
537 r2.h += FONT_SPACING * 3 + FONT_SPACING/2;
541 void Render()
543 OptMenu::RenderBG();
544 OptMenu::RenderTitle();
546 if (win)
547 r2.y += FONT_SPACING * 3 + FONT_SPACING/2;
549 OptMenu::RenderOptions();
551 if (win)
552 r2.y -= FONT_SPACING * 3 + FONT_SPACING/2;
554 if (win)
556 int x = r.x+r.w/2;
557 int y = r2.y + FONT_SPACING/2;
558 if (score < best_score && score <= par)
559 PrintC(true, x, y, "New Best Score: %d Par Score: %d Par Beaten!", score, par);
560 else if (score < best_score)
561 PrintC(true, x, y, "New Best Score: %d Par Score: %d", score, par);
562 else if (par && best_score)
563 PrintC(true, x, y, "Score: %d Previous Best: %d Par Score: %d", score, best_score, par);
564 else
565 PrintC(true, x, y+FONT_SPACING/2, "Well Done! Level Completed!");
569 static void Undo()
571 Pop();
572 HackKeyPress('z', 0);
574 bool KeyPressed(int key, int mod)
576 if (key=='z' || key=='u' || key==SDLK_DELETE || key==SDLK_BACKSPACE)
577 return Undo(), true;
578 if (key=='r' && (mod & KMOD_CTRL))
580 Pop();
581 HackKeyPress(key, mod);
582 return true;
584 return OptMenu::KeyPressed(key, mod);
586 virtual void Mouse(int x, int y, int dx, int dy, int buttons_pressed, int buttons_released, int buttons)
588 if (buttons_pressed==4)
590 Undo();
591 return;
594 OptMenu::Mouse(x, y, dx, dy, buttons_pressed, buttons_released, buttons);
596 void Cancel()
598 if (win)
599 select=0, Select();
600 else
601 Undo();
605 struct OptMenuTitle : public OptMenu
607 OptMenuTitle(const char * t) : OptMenu(t)
609 renderBG = false;
610 //left_align = true;
613 void Render()
615 SDL_Rect a = {0,0,SCREEN_W,SCREEN_H};
616 // SDL_FillRect(screen, &a, SDL_MapRGB(screen->format, 10,25,25));
619 SDL_BlitSurface(titlePage, &a, screen, &a);
621 OptMenu::RenderTitle();
622 OptMenu::RenderOptions();
625 void Init()
627 OptMenu::Init();
629 int xw = SCREEN_W/6;
630 r.w += xw; r2.w+=xw;
631 int x = SCREEN_W - r.x - r.w;// - FONT_SPACING;
632 int y = SCREEN_H - r.y - r.h + FONT_SPACING/4;// - FONT_SPACING;
633 r.x += x; r2.x += x;
634 r.y += y; r2.y += y;
635 r.w+=20; r2.w+=20; r.h+=20; r2.h+=20;
637 r.h = r2.h = SCREEN_H;
638 r2.y = SCREEN_H/2;
639 r.y = r2.y - FONT_SPACING - 2;
643 const char *ending[] = {
644 " Very Well Done! ",
645 "", "", "", "", "", "",
647 "", "", "*15,4", "", "",
649 "All Levels Cleared!",
651 "", "", "*5,7", "", "",
653 "Not a single green hexagon is left unbroken.",
654 "",
655 "Truly, you are a master of hexagon hopping!",
657 "", "", "*9,10", "", "",
659 "", "Credits", "", "", "",
660 "<Design & Direction:", ">Tom Beaumont", "", "",
661 "<Programming:", ">Tom Beaumont", "", "",
662 "<Graphics:", ">Tom Beaumont", "", "",
663 "<Thanks to:", ">Kris Beaumont", "", "",
664 // "", "<Some useless facts...", "",
665 "<Tools and libraries used:", "", ">Photoshop LE", ">Inno Setup", ">Wings 3D", ">MSVC", ">SDL", "",
666 "<Fonts used:", "", ">Copperplate gothic bold", ">Verdana", "",
668 "", "", "*12,14", "", "",
670 "Thanks for playing!"
673 const char *ending2[] = {
674 " Absolutely Amazing! ",
675 "", "", "", "", "",
677 "", "", "*15,4", "", "",
679 "All Levels Mastered!!",
681 "", "", "*5,7", "", "",
683 "You crushed every last green hexagon with",
684 "breathtaking efficiency!",
685 "",
686 "You truly are a grand master of hexagon hopping!",
689 const int endingLen = sizeof(ending)/sizeof(ending[0]);
690 const int endingLen2 = sizeof(ending2)/sizeof(ending2[0]);
691 const int scrollMax = SCREEN_H + (endingLen+1) * FONT_SPACING;
693 struct Ending : public Menu
695 struct Particle{
696 double x,y,xs,ys,xa,ya;
697 double time;
698 int type;
700 void Update(double td)
702 if (type==EMPTY) return;
704 time -= td;
705 x += xs*td;
706 y += ys*td;
707 xs += xa*td;
708 ys += ya*td;
709 if (type==TILE_LASER_HEAD && time<0.3)
710 type=TILE_FIRE_PARTICLE_1;
711 if (type==TILE_FIRE_PARTICLE_1 && time<0.1)
712 type=TILE_FIRE_PARTICLE_2;
714 // if (type==COLLAPSABLE || type==COLLAPSE_DOOR)
715 // for (int i=int((time)*40); i<int((time+td)*40); i++)
716 // new Particle(TILE_GREEN_FRAGMENT_1, x+32-rand()%63, y+20-rand()%40);
718 if (y>SCREEN_H && type==TILE_GREEN_FRAGMENT)
719 xa=0, ys=-ys/2, y=SCREEN_H, type=TILE_GREEN_FRAGMENT_1, time+=rand()%100*0.01;
720 if (y>SCREEN_H && type==TILE_GREEN_FRAGMENT_1)
721 xa=0, ys=-ys/2, y=SCREEN_H, type=TILE_GREEN_FRAGMENT_2, time+=rand()%100*0.01;
722 if (y>SCREEN_H && type==TILE_GREEN_FRAGMENT_2)
723 type = EMPTY;
725 if (time<=0)
727 if (type < 10)
729 for (int i=0; i<40; i++)
730 new Particle(TILE_LASER_HEAD, x, y);
731 for (int i=0; i<40; i++)
732 new Particle(TILE_GREEN_FRAGMENT + rand() % 3, x+32-rand()%63, y+20-rand()%40);
734 if (type==COLLAPSE_DOOR)
736 type = COLLAPSABLE;
737 time += 1;
739 else
741 type = EMPTY;
745 if (type==EMPTY) return;
747 if (type<0.05 && type<15)
748 RenderTile(false, tileSolid[type] ? TILE_WHITE_WALL : TILE_WHITE_TILE, int(x)+scrollX, int(y)+scrollY);
749 else
750 RenderTile(false, type, int(x)+scrollX, int(y)+scrollY);
753 Particle() : type(EMPTY) {}
754 Particle(int t, int _x) : x(_x), type(t)
756 xa=ys=xs=0; ya=400;
757 //if (t==1)
759 xs = rand()%100-50;
760 ys=-400-rand()%200;
761 //x=rand() % SCREEN_W;
762 //y=rand() % SCREEN_H;
763 y = SCREEN_H+20;
764 time = ys/-ya;
767 Particle(int t, double _x, double _y) : type(t)
769 x=_x; y=_y;
770 xa=0; ya=-100;
771 double r = (rand() % 2000) * PI / 1000;
772 double d = rand() % 50 + 250;
774 xs = sin(r)*d;
775 ys = cos(r)*d;
776 time = 1 + (rand() & 255) /255.0;
777 if (t==TILE_WATER_PARTICLE || t==TILE_GREEN_FRAGMENT || t==TILE_GREEN_FRAGMENT_1)
778 time = 2;
779 xa=-xs/time; ya=-ys/time;
780 if (t==TILE_WATER_PARTICLE || t==TILE_GREEN_FRAGMENT || t==TILE_GREEN_FRAGMENT_1)
781 ya += 500, ya/=2, xa/=2, xs/=2, ys/=2;
783 ~Particle() { type = EMPTY; }
784 void* operator new(size_t sz);
787 Particle p[1000];
789 double scroll;
790 double t;
791 bool goodEnding;
792 static Ending* ending;
794 Ending(bool _goodEnd) : goodEnding(_goodEnd)
796 memset(p, 0, sizeof(p));
797 ending = this;
798 renderBG = false;
799 scroll = 0;
800 t=0;
803 void Render()
805 SDL_Rect a = {0,0,SCREEN_W,SCREEN_H};
806 SDL_FillRect(screen, &a, SDL_MapRGB(screen->format, 10,25,25));
808 for (unsigned int i=0; i<sizeof(p)/sizeof(p[0]); i++)
809 p[i].Update(t);
811 int x = a.x + a.w/2;
812 int xl = SCREEN_W/5;
813 int xr = SCREEN_W*4/5;
814 int y = SCREEN_H - int(scroll);
815 for (int i=0; i<endingLen; i++)
817 if (y>-FONT_SPACING*2 && y<SCREEN_H+FONT_SPACING)
819 const char * xx = (i<endingLen2 && goodEnding) ? ending2[i] : ::ending[i];
820 if (xx[0]=='<')
821 Print(xl, y+FONT_SPACING, xx+1);
822 else if (xx[0]=='>')
823 PrintR(xr, y, xx+1);
824 else if (xx[0]=='*')
826 RenderTile(false, atoi(xx+1), (xl+x)/2+scrollX, y+FONT_SPACING/2+scrollY);
827 RenderTile(false, atoi(strchr(xx, ',')+1), (xr+x)/2+scrollX, y+FONT_SPACING/2+scrollY);
829 else
830 PrintC(false, x, y, xx);
832 y+=FONT_SPACING;
834 if (scroll > scrollMax + FONT_SPACING*10)
835 PrintC(true, x, SCREEN_H/2-FONT_SPACING/2, "The End");
838 void Cancel();
840 bool KeyPressed(int key, int mod)
842 if (key=='r' && (mod & KMOD_CTRL))
844 time = 0;
845 memset(p, 0, sizeof(p));
847 else
848 return Menu::KeyPressed(key, mod);
849 return true;
852 void Update(double td)
854 noMouse = 1;
856 double old = time;
858 t = td;
859 if (keyState[SDLK_LSHIFT])
860 t = td*5;
861 if (keyState[SDLK_0])
862 t = MAX(-td*5, -time);
864 Menu::Update(t);
865 scroll = time * 50;
866 // if (scroll > scrollMax)
867 // scroll = fmod(scroll, scrollMax);
868 // scroll = 0, time = 0;
870 if (old>4 && time > 4)
872 if (scroll < scrollMax + FONT_SPACING*17)
874 for (int i=int( old/2.5); i<int(time/2.5); i++)
876 int xs = (rand()%SCREEN_W * 6 + 1) / 8;
877 for (int j=rand()%3+1; j; j--)
878 new Particle(rand()&1 ? COLLAPSABLE : COLLAPSE_DOOR, xs+j*64);
881 if (scroll > scrollMax + FONT_SPACING*27)
882 Cancel();
888 Ending* Ending::ending = 0;
889 void* Ending::Particle::operator new(size_t /*sz*/)
891 static int start = 0;
892 const int max = sizeof(ending->p)/sizeof(ending->p[0]);
893 for (int i=0; i<max; i++)
895 start++;
896 if (start==max) start=0;
897 if (ending->p[start].type==EMPTY)
898 return &ending->p[start];
900 return &ending->p[rand() % max];
903 struct TitleMenu : public OptMenuTitle
905 TitleMenu() : OptMenuTitle("")
907 //left_align = 1;
909 SaveState p;
910 freeSlot = -1;
911 for (int i=0; i<MAX_GAMESLOT; i++)
913 char tmp[80];
914 GetSlotName(i, tmp);
915 FILE* f = file_open(tmp, "rb");
916 if (f)
918 p.LoadSave(f, false);
919 fclose(f);
921 if (p.general.completionPercentage==100 && p.general.masteredPercentage==100)
922 sprintf(optionSlotName[i], _("Continue game %d (All Clear!)"), i+1);
923 else if (p.general.completionPercentage==100)
924 sprintf(optionSlotName[i], "Continue game %d (%d%% + %d%%)", i+1, p.general.completionPercentage, p.general.masteredPercentage);
925 else
926 sprintf(optionSlotName[i], "Continue game %d (%d%% complete)", i+1, p.general.completionPercentage);
928 opt[num_opt++] = OPT_GAMESLOT_0 + i;
930 else
932 // sprintf(optionSlotName[i], "Start new game (slot %d)", i+1);
933 // opt[num_opt++] = OPT_GAMESLOT_0 + i;
935 if (freeSlot==-1)
936 freeSlot = i;
941 if (num_opt < MAX_GAMESLOT)
942 opt[num_opt++] = OPT_GAMESLOT_NEW;
944 opt[num_opt++] = OPT_OPTIONS;
945 #ifdef EDIT
946 opt[num_opt++] = OPT_END;
947 opt[num_opt++] = OPT_END2;
948 #endif
949 opt[num_opt++] = OPT_QUIT;
951 Init();
952 #ifdef EDIT
953 r.y-=FONT_SPACING*2;
954 r2.y-=FONT_SPACING*2;
955 #else
956 if (num_opt==3)
957 r.y+=FONT_SPACING+FONT_SPACING/2, r2.y+=FONT_SPACING+FONT_SPACING/2;
958 #endif
960 bool KeyPressed(int key, int mod);
963 struct QuitConfirmMenu : public OptMenuTitle
965 QuitConfirmMenu() : OptMenuTitle("Quit: Are you sure?")
967 opt[num_opt++] = OPT_QUIT_CONFIRM;
968 opt[select=num_opt++] = OPT_QUIT_CANCEL;
969 Init();
971 r.y += FONT_SPACING*1;
972 r2.y += FONT_SPACING*2;
977 struct DeleteConfirmMenu : public OptMenuTitle
979 char tmp[80];
980 int num;
981 DeleteConfirmMenu(int _num) : OptMenuTitle(&tmp[0]), num(_num)
983 //left_align = 1;
985 sprintf(tmp, "Really delete game %d?", num+1);
986 opt[num_opt++] = OPT_DELETE_CONFIRM;
987 opt[select=num_opt++] = OPT_DELETE_CANCEL;
988 Init();
990 r.y += FONT_SPACING*1;
991 r2.y += FONT_SPACING*2;
993 void Select()
995 if (select<0 || select>=num_opt)
996 return;
997 if (opt[select] == OPT_DELETE_CONFIRM)
999 GetSlotName(num, tmp);
1000 remove(tmp);
1002 Pop();
1003 Pop();
1004 new TitleMenu();
1008 bool TitleMenu::KeyPressed(int key, int mod)
1010 if (key==SDLK_DELETE || key==SDLK_BACKSPACE || key==SDLK_F2)
1012 if (select<0 || select>=num_opt || opt[select]<OPT_GAMESLOT_0 || opt[select]>OPT_GAMESLOT_LAST)
1013 return true;
1014 int i = opt[select] - OPT_GAMESLOT_0;
1016 new DeleteConfirmMenu(i);
1018 return true;
1020 return OptMenu::KeyPressed(key, mod);
1023 struct PauseMenu : public OptMenu
1025 PauseMenu(bool isMap, bool allowGotoMap, int allowEnd, int allowEnd2) : OptMenu("Paused")
1027 opt[num_opt++] = OPT_RESUME;
1028 if (!isMap)
1029 opt[num_opt++] = OPT_RESTART;
1030 opt[num_opt++] = OPT_OPTIONS;
1031 opt[num_opt++] = OPT_HELP;
1032 if (allowEnd || allowEnd2)
1033 opt[num_opt++] = allowEnd2 ? OPT_END2 : OPT_END;
1034 opt[num_opt++] = (isMap || !allowGotoMap) ? OPT_QUIT_MENU_CONFIRM : OPT_GOTO_MAP;
1035 Init();
1037 virtual bool KeyPressed(int key, int mod)
1039 if (key=='p' || key==SDLK_PAUSE)
1041 Cancel();
1042 return true;
1044 return Menu::KeyPressed(key, mod);
1049 struct OptionMenu : public OptMenuTitle
1051 bool title;
1052 OptionMenu(bool _title) : OptMenuTitle("Options"), title(_title)
1054 opt[num_opt++] = OPT_FULLSCREEN;
1056 opt[num_opt++] = OPT_BACK;
1058 if (title)
1060 OptMenuTitle::Init(), renderBG=false;
1061 r.y += FONT_SPACING;
1062 r2.y += FONT_SPACING*2;
1064 else
1065 OptMenu::Init(), renderBG=true;
1067 void Render()
1069 if (title)
1070 OptMenuTitle::Render();
1071 else
1072 OptMenu::Render();
1076 void RenderFade(double time, int dir, int seed);
1078 struct Fader : public Menu
1080 int dir;
1081 double speed;
1082 int result;
1083 Fader(int _dir, int _result, double _speed=1) : dir(_dir), speed(_speed), result(_result)
1085 renderBG = under ? under->renderBG : true;
1087 void Render()
1089 if (under)
1090 under->Render();
1092 RenderFade(time, dir, (long)this);
1094 void Update(double timedelta)
1096 Menu::Update(timedelta * speed);
1097 if (result==-2)
1099 if (time > 0.7)
1100 quitting = 1;
1102 else if (time >= 0.5)
1104 Pop();
1105 if (result==-1)
1107 new TitleMenu();
1108 new Fader(1, -3);
1110 if (result==-4)
1112 Pop(); // Remove old menu
1113 HackKeyPress(SDLK_ESCAPE, KMOD_CTRL | KMOD_SHIFT); // Reload map combination!
1115 if (result==-6)
1117 Pop(); // Remove old menu
1118 new Fader(1, 0, speed);
1120 if (result==-5 || result==-7)
1122 new Ending(result==-7);
1123 new Fader(1, 0, speed);
1129 void Ending::Cancel()
1131 new Fader(-1, -6, 0.3);
1132 // Pop();
1135 void ToggleFullscreen();
1137 void OptMenu::Select()
1139 if (select<0 || select>=num_opt)
1140 return;
1141 switch(opt[select])
1143 case OPT_RESUME:
1144 Cancel();
1145 break;
1147 case OPT_RESTART:
1148 Cancel();
1149 HackKeyPress('r', KMOD_CTRL);
1150 break;
1152 case OPT_GOTO_MAP:
1153 case OPT_GOTO_MAP_CONTINUE:
1154 Pop();
1155 HackKeyPress(SDLK_ESCAPE, KMOD_CTRL);
1156 break;
1158 case OPT_QUIT:
1159 new QuitConfirmMenu();
1160 break;
1162 case OPT_FULLSCREEN:
1163 ToggleFullscreen();
1164 break;
1166 case OPT_QUIT_CONFIRM:
1167 new Fader(-1, -2);
1168 break;
1170 case OPT_QUIT_MENU_CONFIRM:
1171 Pop();
1172 new Fader(-1, -1);
1173 break;
1175 case OPT_OPTIONS:
1176 new OptionMenu(!activeMenu->renderBG);
1177 break;
1179 case OPT_HELP:
1180 new HintReview();
1181 break;
1183 case OPT_QUIT_CANCEL:
1184 case OPT_BACK:
1185 Pop();
1186 break;
1188 case OPT_END:
1189 new Fader(-1, -5, 0.3);
1190 break;
1192 case OPT_END2:
1193 new Fader(-1, -7, 0.3);
1194 break;
1196 case OPT_UNDO:
1197 Pop();
1198 HackKeyPress('z', 0);
1199 break;
1201 default:
1202 if (opt[select]>=OPT_GAMESLOT_0 && opt[select]<=OPT_GAMESLOT_LAST
1203 || opt[select]==OPT_GAMESLOT_NEW && freeSlot>=0)
1205 if (opt[select]==OPT_GAMESLOT_NEW)
1206 GetSlotName(freeSlot, currentSlot);
1207 else
1208 GetSlotName(opt[select]-OPT_GAMESLOT_0, currentSlot);
1209 new Fader(-1, -4);
1211 break;