Merge branch 'mouse-wheel'
[hex-a-hop.git] / menus.h
blob0151cf622ad4c95001843b2e97835c4bd4bf8911
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
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;
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*/
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*/
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 if (buttons_pressed==8)
265 Move(-1);
266 else if (buttons_pressed==16)
267 Move(1);
268 else
269 Menu::Mouse(x,y,dx,dy,buttons_pressed, buttons_released, buttons);
272 bool KeyPressed(int key, int mod)
274 if (key==SDLK_LEFT)
275 Move(-1);
276 else if (key==SDLK_RIGHT)
277 Move(1);
278 else
279 return Menu::KeyPressed(key, mod);
280 return true;
283 virtual void Render()
285 const double SPD = 10;
287 #ifdef EDIT
288 sprintf (title, "Help (Page --)", page_display+1, page_count);
289 #else
290 sprintf (title, "Help (Page %d/%d)", page_display+1, page_count);
291 #endif
293 int y=SCREEN_H/4;
294 if (state==0)
295 y = SCREEN_H/4+int(MAX(0, time*SPD)*-page_dir*SCREEN_H);
296 if (state==1)
297 y = SCREEN_H/4+int(MAX(0, 1-time*SPD)*page_dir*SCREEN_H);
299 //if (!noMouse)
301 PrintC(false, SCREEN_W/2, y-FONT_SPACING*2, "^");
302 PrintC(false, SCREEN_W/2, y+FONT_SPACING*(numLines+1)+FONT_SPACING/2-2, "_");
305 HintMessage::Render(0,y);
307 if (time > 1.0/SPD && page_dir && state==0)
309 do {
310 page = (page+page_dir) & 31;
311 #ifdef EDIT
312 if (hint[page]) break;
313 #endif
314 } while (hint[page]==0 || !(flags&(1<<page)));
315 Init(hint[page]);
317 page_display = (page_display + page_count + page_dir) % page_count;
319 time = 0;
320 state=1;
322 if (time>1.0/SPD && state==1)
323 state=0, page_dir = 0;
325 virtual void Update(double timedelta)
327 Menu::Update(timedelta);
329 void Move(int dir)
331 if (page_dir)
332 return;
333 time = 0;
334 page_dir = dir;
335 state = 0;
339 #define MAX_GAMESLOT 4
340 enum option {
341 OPT_GAMESLOT_0,
342 OPT_GAMESLOT_LAST = OPT_GAMESLOT_0 + MAX_GAMESLOT - 1,
343 OPT_RESUME,
344 OPT_RESTART,
345 OPT_GOTO_MAP,
346 OPT_GOTO_MAP_CONTINUE,
347 OPT_FULLSCREEN,
348 OPT_OPTIONS,
349 OPT_QUIT,
350 OPT_QUIT_CONFIRM,
351 OPT_QUIT_CANCEL,
352 OPT_QUIT_MENU_CONFIRM,
353 OPT_HELP,
354 OPT_GAMESLOT_NEW,
355 OPT_DELETE_CONFIRM,
356 OPT_DELETE_CANCEL,
357 OPT_UNDO,
358 OPT_BACK,
359 OPT_END, OPT_END2,
362 char optionSlotName[MAX_GAMESLOT][40] = { {0} };
363 char currentSlot[80] = "";
364 int freeSlot = -1;
365 char* GetSlotName(int i, char * t)
367 sprintf(t, "save%d.dat", i+1);
368 return t;
371 char * optionString[] = {
372 optionSlotName[0],
373 optionSlotName[1],
374 optionSlotName[2],
375 optionSlotName[3],
376 "Resume",
377 "Restart Level",
378 "Return to Map",
379 "Continue",
380 "Toggle Fullscreen",
381 "Options",
382 "Quit",
383 "Yes",
384 "No",
385 "Return to Title",
386 "Help",
387 "Start New Game",
388 "Yes, really delete it!",
389 "Don't do it!",
390 "Undo Last Move",
391 "OK",
392 "View Credits Sequence", "View Credits Sequence",
396 struct OptMenu : public Menu
398 int select;
399 int num_opt;
400 int opt[10];
401 bool left_align;
402 const char * title;
403 SDL_Rect r, r2;
405 OptMenu(const char * t) : select(0), title(t)
407 left_align = false;
408 num_opt = 0;
411 void Init()
413 r.w=SCREEN_W/2;
414 r.x=(SCREEN_W-r.w)/2;
415 r.y=SCREEN_H/3;
417 r2 = r;
419 const int SPACE = int(FONT_SPACING * 1.5);
421 r2.h = SPACE*num_opt + FONT_SPACING/2;
422 r.h = r2.h + (FONT_SPACING+2*2);
423 r.y -= FONT_SPACING+2;
424 r.w += 2*2;
425 r.x -= 2;
428 void RenderOption(int o, const char * s)
430 int y = r2.y + FONT_SPACING/2 + int(FONT_SPACING * 1.5) * o;
431 if (left_align)
433 int x = r.x + font[' '].w;
434 int x1 = x + (font[' '].w + font['>'].w + FONT_X_SPACING*2) / 2;
435 if (select==o)
437 //x += int( sin(time*9)*2.5 );
438 //y += int( sin(time*9 + 1.5)*1.5 );
439 Print(x, y, "> %s", s);
441 else
443 Print(x1, y, "%s", s);
446 else
448 int x = r.x + r.w/2;
449 if (select==o)
451 //x += int( sin(time*9)*2.5 );
452 //y += int( sin(time*9 + 1.5)*1.5 );
453 PrintC(false, x, y, "> %s <", s);
455 else
457 PrintC(false, x, y, "%s", s);
462 void Move(int dir)
464 select += dir;
465 if (select<0) select = num_opt-1;
466 if (select>=num_opt) select = 0;
468 virtual void Mouse(int x, int y, int dx, int dy, int buttons_pressed, int buttons_released, int buttons)
470 if (1)
472 if (x<r2.x || y<r2.y || x>r2.x+r2.w || y>r2.y+r2.h)
474 if (buttons_pressed!=4 && buttons_pressed!=2)
475 return;
477 else
479 select = (y-r2.y-FONT_SPACING/3) / int(FONT_SPACING*1.5);
480 if (select<0) select = 0;
481 if (select>=num_opt) select = num_opt-1;
484 if (buttons_pressed==1)
485 Select();
486 Menu::Mouse(x, y, dx, dy, buttons_pressed, buttons_released, buttons);
488 void Select();
490 void Render()
492 RenderBG();
493 RenderTitle();
494 RenderOptions();
496 void RenderBG()
498 SDL_FillRect(screen, &r, SDL_MapRGB(screen->format, 60,90,90));
499 SDL_FillRect(screen, &r2, SDL_MapRGB(screen->format, 20,50,50));
501 void RenderTitle()
503 if (left_align)
504 Print(r2.x+font[' '].w, r.y+4, title);
505 else
506 PrintC(false, r2.x+r2.w/2, r.y+4, title);
508 void RenderOptions()
510 for (int i=0; i<num_opt; i++)
511 RenderOption(i, optionString[opt[i]]);
513 void Cancel()
515 Pop();
519 struct WinLoseScreen : public OptMenu
521 bool win;
522 int score, par, best_score;
523 WinLoseScreen(bool _win, int _score=0, int _par=0, int _prev_score=0) :
524 OptMenu(_win ? _("Level Complete!") : _("Emi can't swim...")),
525 win(_win),
526 score(_score),
527 par(_par),
528 best_score(_prev_score)
530 if (!win)
531 opt[num_opt++] = OPT_UNDO;
532 if (!win)
533 opt[num_opt++] = OPT_RESTART;
534 opt[num_opt++] = win ? OPT_GOTO_MAP_CONTINUE : OPT_GOTO_MAP;
536 Init();
538 if (win)
540 r.h += FONT_SPACING * 3 + FONT_SPACING/2;
541 r2.h += FONT_SPACING * 3 + FONT_SPACING/2;
545 void Render()
547 OptMenu::RenderBG();
548 OptMenu::RenderTitle();
550 if (win)
551 r2.y += FONT_SPACING * 3 + FONT_SPACING/2;
553 OptMenu::RenderOptions();
555 if (win)
556 r2.y -= FONT_SPACING * 3 + FONT_SPACING/2;
558 if (win)
560 int x = r.x+r.w/2;
561 int y = r2.y + FONT_SPACING/2;
562 if (score < best_score && score <= par)
563 PrintC(true, x, y, "New Best Score: %d Par Score: %d Par Beaten!", score, par);
564 else if (score < best_score)
565 PrintC(true, x, y, "New Best Score: %d Par Score: %d", score, par);
566 else if (par && best_score)
567 PrintC(true, x, y, "Score: %d Previous Best: %d Par Score: %d", score, best_score, par);
568 else
569 PrintC(true, x, y+FONT_SPACING/2, "Well Done! Level Completed!");
573 static void Undo()
575 Pop();
576 HackKeyPress('z', 0);
578 bool KeyPressed(int key, int mod)
580 if (key=='z' || key=='u' || key==SDLK_DELETE || key==SDLK_BACKSPACE)
581 return Undo(), true;
582 if (key=='r' && (mod & KMOD_CTRL))
584 Pop();
585 HackKeyPress(key, mod);
586 return true;
588 return OptMenu::KeyPressed(key, mod);
590 virtual void Mouse(int x, int y, int dx, int dy, int buttons_pressed, int buttons_released, int buttons)
592 if (buttons_pressed==4)
594 Undo();
595 return;
598 OptMenu::Mouse(x, y, dx, dy, buttons_pressed, buttons_released, buttons);
600 void Cancel()
602 if (win)
603 select=0, Select();
604 else
605 Undo();
609 struct OptMenuTitle : public OptMenu
611 OptMenuTitle(const char * t) : OptMenu(t)
613 renderBG = false;
614 //left_align = true;
617 void Render()
619 SDL_Rect a = {0,0,SCREEN_W,SCREEN_H};
620 // SDL_FillRect(screen, &a, SDL_MapRGB(screen->format, 10,25,25));
623 SDL_BlitSurface(titlePage, &a, screen, &a);
625 OptMenu::RenderTitle();
626 OptMenu::RenderOptions();
629 void Init()
631 OptMenu::Init();
633 int xw = SCREEN_W/6;
634 r.w += xw; r2.w+=xw;
635 int x = SCREEN_W - r.x - r.w;// - FONT_SPACING;
636 int y = SCREEN_H - r.y - r.h + FONT_SPACING/4;// - FONT_SPACING;
637 r.x += x; r2.x += x;
638 r.y += y; r2.y += y;
639 r.w+=20; r2.w+=20; r.h+=20; r2.h+=20;
641 r.h = r2.h = SCREEN_H;
642 r2.y = SCREEN_H/2;
643 r.y = r2.y - FONT_SPACING - 2;
647 const char *ending[] = {
648 " Very Well Done! ",
649 "", "", "", "", "", "",
651 "", "", "*15,4", "", "",
653 "All Levels Cleared!",
655 "", "", "*5,7", "", "",
657 "Not a single green hexagon is left unbroken.",
658 "",
659 "Truly, you are a master of hexagon hopping!",
661 "", "", "*9,10", "", "",
663 "", "Credits", "", "", "",
664 "<Design & Direction:", ">Tom Beaumont", "", "",
665 "<Programming:", ">Tom Beaumont", "", "",
666 "<Graphics:", ">Tom Beaumont", "", "",
667 "<Thanks to:", ">Kris Beaumont", "", "",
668 // "", "<Some useless facts...", "",
669 "<Tools and libraries used:", "", ">Photoshop LE", ">Inno Setup", ">Wings 3D", ">MSVC", ">SDL", "",
670 "<Fonts used:", "", ">Copperplate gothic bold", ">Verdana", "",
672 "", "", "*12,14", "", "",
674 "Thanks for playing!"
677 const char *ending2[] = {
678 " Absolutely Amazing! ",
679 "", "", "", "", "",
681 "", "", "*15,4", "", "",
683 "All Levels Mastered!!",
685 "", "", "*5,7", "", "",
687 "You crushed every last green hexagon with",
688 "breathtaking efficiency!",
690 "You truly are a grand master of hexagon hopping!",
693 const int endingLen = sizeof(ending)/sizeof(ending[0]);
694 const int endingLen2 = sizeof(ending2)/sizeof(ending2[0]);
695 const int scrollMax = SCREEN_H + (endingLen+1) * FONT_SPACING;
697 struct Ending : public Menu
699 struct Particle{
700 double x,y,xs,ys,xa,ya;
701 double time;
702 int type;
704 void Update(double td)
706 if (type==EMPTY) return;
708 time -= td;
709 x += xs*td;
710 y += ys*td;
711 xs += xa*td;
712 ys += ya*td;
713 if (type==TILE_LASER_HEAD && time<0.3)
714 type=TILE_FIRE_PARTICLE_1;
715 if (type==TILE_FIRE_PARTICLE_1 && time<0.1)
716 type=TILE_FIRE_PARTICLE_2;
718 // if (type==COLLAPSABLE || type==COLLAPSE_DOOR)
719 // for (int i=int((time)*40); i<int((time+td)*40); i++)
720 // new Particle(TILE_GREEN_FRAGMENT_1, x+32-rand()%63, y+20-rand()%40);
722 if (y>SCREEN_H && type==TILE_GREEN_FRAGMENT)
723 xa=0, ys=-ys/2, y=SCREEN_H, type=TILE_GREEN_FRAGMENT_1, time+=rand()%100*0.01;
724 if (y>SCREEN_H && type==TILE_GREEN_FRAGMENT_1)
725 xa=0, ys=-ys/2, y=SCREEN_H, type=TILE_GREEN_FRAGMENT_2, time+=rand()%100*0.01;
726 if (y>SCREEN_H && type==TILE_GREEN_FRAGMENT_2)
727 type = EMPTY;
729 if (time<=0)
731 if (type < 10)
733 for (int i=0; i<40; i++)
734 new Particle(TILE_LASER_HEAD, x, y);
735 for (int i=0; i<40; i++)
736 new Particle(TILE_GREEN_FRAGMENT + rand() % 3, x+32-rand()%63, y+20-rand()%40);
738 if (type==COLLAPSE_DOOR)
740 type = COLLAPSABLE;
741 time += 1;
743 else
745 type = EMPTY;
749 if (type==EMPTY) return;
751 if (type<0.05 && type<15)
752 RenderTile(false, tileSolid[type] ? TILE_WHITE_WALL : TILE_WHITE_TILE, int(x)+scrollX, int(y)+scrollY);
753 else
754 RenderTile(false, type, int(x)+scrollX, int(y)+scrollY);
757 Particle() : type(EMPTY) {}
758 Particle(int t, int _x) : x(_x), type(t)
760 xa=ys=xs=0; ya=400;
761 //if (t==1)
763 xs = rand()%100-50;
764 ys=-400-rand()%200;
765 //x=rand() % SCREEN_W;
766 //y=rand() % SCREEN_H;
767 y = SCREEN_H+20;
768 time = ys/-ya;
771 Particle(int t, double _x, double _y) : type(t)
773 x=_x; y=_y;
774 xa=0; ya=-100;
775 double r = (rand() % 2000) * PI / 1000;
776 double d = rand() % 50 + 250;
778 xs = sin(r)*d;
779 ys = cos(r)*d;
780 time = 1 + (rand() & 255) /255.0;
781 if (t==TILE_WATER_PARTICLE || t==TILE_GREEN_FRAGMENT || t==TILE_GREEN_FRAGMENT_1)
782 time = 2;
783 xa=-xs/time; ya=-ys/time;
784 if (t==TILE_WATER_PARTICLE || t==TILE_GREEN_FRAGMENT || t==TILE_GREEN_FRAGMENT_1)
785 ya += 500, ya/=2, xa/=2, xs/=2, ys/=2;
787 ~Particle() { type = EMPTY; }
788 void* operator new(size_t sz);
791 Particle p[1000];
793 double scroll;
794 double t;
795 bool goodEnding;
796 static Ending* ending;
798 Ending(bool _goodEnd) : goodEnding(_goodEnd)
800 memset(p, 0, sizeof(p));
801 ending = this;
802 renderBG = false;
803 scroll = 0;
804 t=0;
807 void Render()
809 SDL_Rect a = {0,0,SCREEN_W,SCREEN_H};
810 SDL_FillRect(screen, &a, SDL_MapRGB(screen->format, 10,25,25));
812 for (unsigned int i=0; i<sizeof(p)/sizeof(p[0]); i++)
813 p[i].Update(t);
815 int x = a.x + a.w/2;
816 int xl = SCREEN_W/5;
817 int xr = SCREEN_W*4/5;
818 int y = SCREEN_H - int(scroll);
819 for (int i=0; i<endingLen; i++)
821 if (y>-FONT_SPACING*2 && y<SCREEN_H+FONT_SPACING)
823 const char * xx = (i<endingLen2 && goodEnding) ? ending2[i] : ::ending[i];
824 if (xx[0]=='<')
825 Print(xl, y+FONT_SPACING, xx+1);
826 else if (xx[0]=='>')
827 PrintR(xr, y, xx+1);
828 else if (xx[0]=='*')
830 RenderTile(false, atoi(xx+1), (xl+x)/2+scrollX, y+FONT_SPACING/2+scrollY);
831 RenderTile(false, atoi(strchr(xx, ',')+1), (xr+x)/2+scrollX, y+FONT_SPACING/2+scrollY);
833 else
834 PrintC(false, x, y, xx);
836 y+=FONT_SPACING;
838 if (scroll > scrollMax + FONT_SPACING*10)
839 PrintC(true, x, SCREEN_H/2-FONT_SPACING/2, "The End");
842 void Cancel();
844 bool KeyPressed(int key, int mod)
846 if (key=='r' && (mod & KMOD_CTRL))
848 time = 0;
849 memset(p, 0, sizeof(p));
851 else
852 return Menu::KeyPressed(key, mod);
853 return true;
856 void Update(double td)
858 noMouse = 1;
860 double old = time;
862 t = td;
863 if (keyState[SDLK_LSHIFT])
864 t = td*5;
865 if (keyState[SDLK_0])
866 t = MAX(-td*5, -time);
868 Menu::Update(t);
869 scroll = time * 50;
870 // if (scroll > scrollMax)
871 // scroll = fmod(scroll, scrollMax);
872 // scroll = 0, time = 0;
874 if (old>4 && time > 4)
876 if (scroll < scrollMax + FONT_SPACING*17)
878 for (int i=int( old/2.5); i<int(time/2.5); i++)
880 int xs = (rand()%SCREEN_W * 6 + 1) / 8;
881 for (int j=rand()%3+1; j; j--)
882 new Particle(rand()&1 ? COLLAPSABLE : COLLAPSE_DOOR, xs+j*64);
885 if (scroll > scrollMax + FONT_SPACING*27)
886 Cancel();
892 Ending* Ending::ending = 0;
893 void* Ending::Particle::operator new(size_t /*sz*/)
895 static int start = 0;
896 const int max = sizeof(ending->p)/sizeof(ending->p[0]);
897 for (int i=0; i<max; i++)
899 start++;
900 if (start==max) start=0;
901 if (ending->p[start].type==EMPTY)
902 return &ending->p[start];
904 return &ending->p[rand() % max];
907 struct TitleMenu : public OptMenuTitle
909 TitleMenu() : OptMenuTitle("")
911 //left_align = 1;
913 SaveState p;
914 freeSlot = -1;
915 for (int i=0; i<MAX_GAMESLOT; i++)
917 char tmp[80];
918 GetSlotName(i, tmp);
919 FILE* f = file_open(tmp, "rb");
920 if (f)
922 p.LoadSave(f, false);
923 fclose(f);
925 if (p.general.completionPercentage==100 && p.general.masteredPercentage==100)
926 sprintf(optionSlotName[i], _("Continue game %d (All Clear!)"), i+1);
927 else if (p.general.completionPercentage==100)
928 sprintf(optionSlotName[i], "Continue game %d (%d%% + %d%%)", i+1, p.general.completionPercentage, p.general.masteredPercentage);
929 else
930 sprintf(optionSlotName[i], "Continue game %d (%d%% complete)", i+1, p.general.completionPercentage);
932 opt[num_opt++] = OPT_GAMESLOT_0 + i;
934 else
936 // sprintf(optionSlotName[i], "Start new game (slot %d)", i+1);
937 // opt[num_opt++] = OPT_GAMESLOT_0 + i;
939 if (freeSlot==-1)
940 freeSlot = i;
945 if (num_opt < MAX_GAMESLOT)
946 opt[num_opt++] = OPT_GAMESLOT_NEW;
948 opt[num_opt++] = OPT_OPTIONS;
949 #ifdef EDIT
950 opt[num_opt++] = OPT_END;
951 opt[num_opt++] = OPT_END2;
952 #endif
953 opt[num_opt++] = OPT_QUIT;
955 Init();
956 #ifdef EDIT
957 r.y-=FONT_SPACING*2;
958 r2.y-=FONT_SPACING*2;
959 #else
960 if (num_opt==3)
961 r.y+=FONT_SPACING+FONT_SPACING/2, r2.y+=FONT_SPACING+FONT_SPACING/2;
962 #endif
964 bool KeyPressed(int key, int mod);
967 struct QuitConfirmMenu : public OptMenuTitle
969 QuitConfirmMenu() : OptMenuTitle("Quit: Are you sure?")
971 opt[num_opt++] = OPT_QUIT_CONFIRM;
972 opt[select=num_opt++] = OPT_QUIT_CANCEL;
973 Init();
975 r.y += FONT_SPACING*1;
976 r2.y += FONT_SPACING*2;
981 struct DeleteConfirmMenu : public OptMenuTitle
983 char tmp[80];
984 int num;
985 DeleteConfirmMenu(int _num) : OptMenuTitle(&tmp[0]), num(_num)
987 //left_align = 1;
989 sprintf(tmp, "Really delete game %d?", num+1);
990 opt[num_opt++] = OPT_DELETE_CONFIRM;
991 opt[select=num_opt++] = OPT_DELETE_CANCEL;
992 Init();
994 r.y += FONT_SPACING*1;
995 r2.y += FONT_SPACING*2;
997 void Select()
999 if (select<0 || select>=num_opt)
1000 return;
1001 if (opt[select] == OPT_DELETE_CONFIRM)
1003 GetSlotName(num, tmp);
1004 remove(tmp);
1006 Pop();
1007 Pop();
1008 new TitleMenu();
1012 bool TitleMenu::KeyPressed(int key, int mod)
1014 if (key==SDLK_DELETE || key==SDLK_BACKSPACE || key==SDLK_F2)
1016 if (select<0 || select>=num_opt || opt[select]<OPT_GAMESLOT_0 || opt[select]>OPT_GAMESLOT_LAST)
1017 return true;
1018 int i = opt[select] - OPT_GAMESLOT_0;
1020 new DeleteConfirmMenu(i);
1022 return true;
1024 return OptMenu::KeyPressed(key, mod);
1027 struct PauseMenu : public OptMenu
1029 PauseMenu(bool isMap, bool allowGotoMap, int allowEnd, int allowEnd2) : OptMenu("Paused")
1031 opt[num_opt++] = OPT_RESUME;
1032 if (!isMap)
1033 opt[num_opt++] = OPT_RESTART;
1034 opt[num_opt++] = OPT_OPTIONS;
1035 opt[num_opt++] = OPT_HELP;
1036 if (allowEnd || allowEnd2)
1037 opt[num_opt++] = allowEnd2 ? OPT_END2 : OPT_END;
1038 opt[num_opt++] = (isMap || !allowGotoMap) ? OPT_QUIT_MENU_CONFIRM : OPT_GOTO_MAP;
1039 Init();
1041 virtual bool KeyPressed(int key, int mod)
1043 if (key=='p' || key==SDLK_PAUSE)
1045 Cancel();
1046 return true;
1048 return Menu::KeyPressed(key, mod);
1053 struct OptionMenu : public OptMenuTitle
1055 bool title;
1056 OptionMenu(bool _title) : OptMenuTitle("Options"), title(_title)
1058 opt[num_opt++] = OPT_FULLSCREEN;
1060 opt[num_opt++] = OPT_BACK;
1062 if (title)
1064 OptMenuTitle::Init(), renderBG=false;
1065 r.y += FONT_SPACING;
1066 r2.y += FONT_SPACING*2;
1068 else
1069 OptMenu::Init(), renderBG=true;
1071 void Render()
1073 if (title)
1074 OptMenuTitle::Render();
1075 else
1076 OptMenu::Render();
1080 void RenderFade(double time, int dir, int seed);
1082 struct Fader : public Menu
1084 int dir;
1085 double speed;
1086 int result;
1087 Fader(int _dir, int _result, double _speed=1) : dir(_dir), speed(_speed), result(_result)
1089 renderBG = under ? under->renderBG : true;
1091 void Render()
1093 if (under)
1094 under->Render();
1096 RenderFade(time, dir, (long)this);
1098 void Update(double timedelta)
1100 Menu::Update(timedelta * speed);
1101 if (result==-2)
1103 if (time > 0.7)
1104 quitting = 1;
1106 else if (time >= 0.5)
1108 Pop();
1109 if (result==-1)
1111 new TitleMenu();
1112 new Fader(1, -3);
1114 if (result==-4)
1116 Pop(); // Remove old menu
1117 HackKeyPress(SDLK_ESCAPE, KMOD_CTRL | KMOD_SHIFT); // Reload map combination!
1119 if (result==-6)
1121 Pop(); // Remove old menu
1122 new Fader(1, 0, speed);
1124 if (result==-5 || result==-7)
1126 new Ending(result==-7);
1127 new Fader(1, 0, speed);
1133 void Ending::Cancel()
1135 new Fader(-1, -6, 0.3);
1136 // Pop();
1139 void ToggleFullscreen();
1141 void OptMenu::Select()
1143 if (select<0 || select>=num_opt)
1144 return;
1145 switch(opt[select])
1147 case OPT_RESUME:
1148 Cancel();
1149 break;
1151 case OPT_RESTART:
1152 Cancel();
1153 HackKeyPress('r', KMOD_CTRL);
1154 break;
1156 case OPT_GOTO_MAP:
1157 case OPT_GOTO_MAP_CONTINUE:
1158 Pop();
1159 HackKeyPress(SDLK_ESCAPE, KMOD_CTRL);
1160 break;
1162 case OPT_QUIT:
1163 new QuitConfirmMenu();
1164 break;
1166 case OPT_FULLSCREEN:
1167 ToggleFullscreen();
1168 break;
1170 case OPT_QUIT_CONFIRM:
1171 new Fader(-1, -2);
1172 break;
1174 case OPT_QUIT_MENU_CONFIRM:
1175 Pop();
1176 new Fader(-1, -1);
1177 break;
1179 case OPT_OPTIONS:
1180 new OptionMenu(!activeMenu->renderBG);
1181 break;
1183 case OPT_HELP:
1184 new HintReview();
1185 break;
1187 case OPT_QUIT_CANCEL:
1188 case OPT_BACK:
1189 Pop();
1190 break;
1192 case OPT_END:
1193 new Fader(-1, -5, 0.3);
1194 break;
1196 case OPT_END2:
1197 new Fader(-1, -7, 0.3);
1198 break;
1200 case OPT_UNDO:
1201 Pop();
1202 HackKeyPress('z', 0);
1203 break;
1205 default:
1206 if (opt[select]>=OPT_GAMESLOT_0 && opt[select]<=OPT_GAMESLOT_LAST
1207 || opt[select]==OPT_GAMESLOT_NEW && freeSlot>=0)
1209 if (opt[select]==OPT_GAMESLOT_NEW)
1210 GetSlotName(freeSlot, currentSlot);
1211 else
1212 GetSlotName(opt[select]-OPT_GAMESLOT_0, currentSlot);
1213 new Fader(-1, -4);
1215 break;