Add support for wheel mouse
[hex-a-hop.git] / menus.h
blobce530b08b061a35cfffb43d6f38a95144cf3ca77
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() : under(activeMenu), time(0), renderBG(true) { 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 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 int y = r2.y + FONT_SPACING / 2;
504 if (left_align)
505 Print(r2.x+font[' '].w, r.y+4, title);
506 else
507 PrintC(false, r2.x+r2.w/2, r.y+4, title);
509 void RenderOptions()
511 for (int i=0; i<num_opt; i++)
512 RenderOption(i, optionString[opt[i]]);
514 void Cancel()
516 Pop();
520 struct WinLoseScreen : public OptMenu
522 bool win;
523 int score, par, best_score;
524 WinLoseScreen(bool _win, int _score=0, int _par=0, int _prev_score=0) :
525 score(_score),
526 par(_par),
527 best_score(_prev_score),
528 win(_win),
529 OptMenu(_win ? "Level Complete!" : "Emi can't swim...")
531 if (!win)
532 opt[num_opt++] = OPT_UNDO;
533 if (!win)
534 opt[num_opt++] = OPT_RESTART;
535 opt[num_opt++] = win ? OPT_GOTO_MAP_CONTINUE : OPT_GOTO_MAP;
537 Init();
539 if (win)
541 r.h += FONT_SPACING * 3 + FONT_SPACING/2;
542 r2.h += FONT_SPACING * 3 + FONT_SPACING/2;
546 void Render()
548 OptMenu::RenderBG();
549 OptMenu::RenderTitle();
551 if (win)
552 r2.y += FONT_SPACING * 3 + FONT_SPACING/2;
554 OptMenu::RenderOptions();
556 if (win)
557 r2.y -= FONT_SPACING * 3 + FONT_SPACING/2;
559 if (win)
561 int x = r.x+r.w/2;
562 int y = r2.y + FONT_SPACING/2;
563 if (score < best_score && score <= par)
564 PrintC(true, x, y, "New Best Score: %d Par Score: %d Par Beaten!", score, par);
565 else if (score < best_score)
566 PrintC(true, x, y, "New Best Score: %d Par Score: %d", score, par);
567 else if (par && best_score)
568 PrintC(true, x, y, "Score: %d Previous Best: %d Par Score: %d", score, best_score, par);
569 else
570 PrintC(true, x, y+FONT_SPACING/2, "Well Done! Level Completed!");
574 static void Undo()
576 Pop();
577 HackKeyPress('z', 0);
579 bool KeyPressed(int key, int mod)
581 if (key=='z' || key=='u' || key==SDLK_DELETE || key==SDLK_BACKSPACE)
582 return Undo(), true;
583 if (key=='r' && (mod & KMOD_CTRL))
585 Pop();
586 HackKeyPress(key, mod);
587 return true;
589 return OptMenu::KeyPressed(key, mod);
591 virtual void Mouse(int x, int y, int dx, int dy, int buttons_pressed, int buttons_released, int buttons)
593 if (buttons_pressed==4)
595 Undo();
596 return;
599 OptMenu::Mouse(x, y, dx, dy, buttons_pressed, buttons_released, buttons);
601 void Cancel()
603 if (win)
604 select=0, Select();
605 else
606 Undo();
610 struct OptMenuTitle : public OptMenu
612 OptMenuTitle(const char * t) : OptMenu(t)
614 renderBG = false;
615 //left_align = true;
618 void Render()
620 SDL_Rect a = {0,0,SCREEN_W,SCREEN_H};
621 // SDL_FillRect(screen, &a, SDL_MapRGB(screen->format, 10,25,25));
624 SDL_BlitSurface(titlePage, &a, screen, &a);
626 OptMenu::RenderTitle();
627 OptMenu::RenderOptions();
630 void Init()
632 OptMenu::Init();
634 int xw = SCREEN_W/6;
635 r.w += xw; r2.w+=xw;
636 int x = SCREEN_W - r.x - r.w;// - FONT_SPACING;
637 int y = SCREEN_H - r.y - r.h + FONT_SPACING/4;// - FONT_SPACING;
638 r.x += x; r2.x += x;
639 r.y += y; r2.y += y;
640 r.w+=20; r2.w+=20; r.h+=20; r2.h+=20;
642 r.h = r2.h = SCREEN_H;
643 r2.y = SCREEN_H/2;
644 r.y = r2.y - FONT_SPACING - 2;
648 const char *ending[] = {
649 " Very Well Done! ",
650 "", "", "", "", "", "",
652 "", "", "*15,4", "", "",
654 "All Levels Cleared!",
656 "", "", "*5,7", "", "",
658 "Not a single green hexagon is left unbroken.",
659 "",
660 "Truly, you are a master of hexagon hopping!",
662 "", "", "*9,10", "", "",
664 "", "Credits", "", "", "",
665 "<Design & Direction:", ">Tom Beaumont", "", "",
666 "<Programming:", ">Tom Beaumont", "", "",
667 "<Graphics:", ">Tom Beaumont", "", "",
668 "<Thanks to:", ">Kris Beaumont", "", "",
669 // "", "<Some useless facts...", "",
670 "<Tools and libraries used:", "", ">Photoshop LE", ">Inno Setup", ">Wings 3D", ">MSVC", ">SDL", "",
671 "<Fonts used:", "", ">Copperplate gothic bold", ">Verdana", "",
673 "", "", "*12,14", "", "",
675 "Thanks for playing!"
678 const char *ending2[] = {
679 " Absolutely Amazing! ",
680 "", "", "", "", "",
682 "", "", "*15,4", "", "",
684 "All Levels Mastered!!",
686 "", "", "*5,7", "", "",
688 "You crushed every last green hexagon with",
689 "breathtaking efficiency!",
690 "",
691 "You truly are a grand master of hexagon hopping!",
694 const int endingLen = sizeof(ending)/sizeof(ending[0]);
695 const int endingLen2 = sizeof(ending2)/sizeof(ending2[0]);
696 const int scrollMax = SCREEN_H + (endingLen+1) * FONT_SPACING;
698 struct Ending : public Menu
700 struct Particle{
701 double x,y,xs,ys,xa,ya;
702 double time;
703 int type;
705 void Update(double td)
707 if (type==EMPTY) return;
709 time -= td;
710 x += xs*td;
711 y += ys*td;
712 xs += xa*td;
713 ys += ya*td;
714 if (type==TILE_LASER_HEAD && time<0.3)
715 type=TILE_FIRE_PARTICLE_1;
716 if (type==TILE_FIRE_PARTICLE_1 && time<0.1)
717 type=TILE_FIRE_PARTICLE_2;
719 // if (type==COLLAPSABLE || type==COLLAPSE_DOOR)
720 // for (int i=int((time)*40); i<int((time+td)*40); i++)
721 // new Particle(TILE_GREEN_FRAGMENT_1, x+32-rand()%63, y+20-rand()%40);
723 if (y>SCREEN_H && type==TILE_GREEN_FRAGMENT)
724 xa=0, ys=-ys/2, y=SCREEN_H, type=TILE_GREEN_FRAGMENT_1, time+=rand()%100*0.01;
725 if (y>SCREEN_H && type==TILE_GREEN_FRAGMENT_1)
726 xa=0, ys=-ys/2, y=SCREEN_H, type=TILE_GREEN_FRAGMENT_2, time+=rand()%100*0.01;
727 if (y>SCREEN_H && type==TILE_GREEN_FRAGMENT_2)
728 type = EMPTY;
730 if (time<=0)
732 if (type < 10)
734 for (int i=0; i<40; i++)
735 new Particle(TILE_LASER_HEAD, x, y);
736 for (int i=0; i<40; i++)
737 new Particle(TILE_GREEN_FRAGMENT + rand() % 3, x+32-rand()%63, y+20-rand()%40);
739 if (type==COLLAPSE_DOOR)
741 type = COLLAPSABLE;
742 time += 1;
744 else
746 type = EMPTY;
750 if (type==EMPTY) return;
752 if (type<0.05 && type<15)
753 RenderTile(false, tileSolid[type] ? TILE_WHITE_WALL : TILE_WHITE_TILE, int(x)+scrollX, int(y)+scrollY);
754 else
755 RenderTile(false, type, int(x)+scrollX, int(y)+scrollY);
758 Particle() : type(EMPTY) {}
759 Particle(int t, int _x) : type(t), x(_x)
761 xa=ys=xs=0; ya=400;
762 //if (t==1)
764 xs = rand()%100-50;
765 ys=-400-rand()%200;
766 //x=rand() % SCREEN_W;
767 //y=rand() % SCREEN_H;
768 y = SCREEN_H+20;
769 time = ys/-ya;
772 Particle(int t, double _x, double _y) : type(t)
774 x=_x; y=_y;
775 xa=0; ya=-100;
776 double r = (rand() % 2000) * PI / 1000;
777 double d = rand() % 50 + 250;
779 xs = sin(r)*d;
780 ys = cos(r)*d;
781 time = 1 + (rand() & 255) /255.0;
782 if (t==TILE_WATER_PARTICLE || t==TILE_GREEN_FRAGMENT || t==TILE_GREEN_FRAGMENT_1)
783 time = 2;
784 xa=-xs/time; ya=-ys/time;
785 if (t==TILE_WATER_PARTICLE || t==TILE_GREEN_FRAGMENT || t==TILE_GREEN_FRAGMENT_1)
786 ya += 500, ya/=2, xa/=2, xs/=2, ys/=2;
788 ~Particle() { type = EMPTY; }
789 void* operator new(size_t sz);
792 Particle p[1000];
794 double scroll;
795 double t;
796 bool goodEnding;
797 static Ending* ending;
799 Ending(bool _goodEnd) : goodEnding(_goodEnd)
801 memset(p, 0, sizeof(p));
802 ending = this;
803 renderBG = false;
804 scroll = 0;
805 t=0;
808 void Render()
810 SDL_Rect a = {0,0,SCREEN_W,SCREEN_H};
811 SDL_FillRect(screen, &a, SDL_MapRGB(screen->format, 10,25,25));
813 for (int i=0; i<sizeof(p)/sizeof(p[0]); i++)
814 p[i].Update(t);
816 int x = a.x + a.w/2;
817 int xl = SCREEN_W/5;
818 int xr = SCREEN_W*4/5;
819 int y = SCREEN_H - int(scroll);
820 for (int i=0; i<endingLen; i++)
822 if (y>-FONT_SPACING*2 && y<SCREEN_H+FONT_SPACING)
824 const char * xx = (i<endingLen2 && goodEnding) ? ending2[i] : ::ending[i];
825 if (xx[0]=='<')
826 Print(xl, y+FONT_SPACING, xx+1);
827 else if (xx[0]=='>')
828 PrintR(xr, y, xx+1);
829 else if (xx[0]=='*')
831 RenderTile(false, atoi(xx+1), (xl+x)/2+scrollX, y+FONT_SPACING/2+scrollY);
832 RenderTile(false, atoi(strchr(xx, ',')+1), (xr+x)/2+scrollX, y+FONT_SPACING/2+scrollY);
834 else
835 PrintC(false, x, y, xx);
837 y+=FONT_SPACING;
839 if (scroll > scrollMax + FONT_SPACING*10)
840 PrintC(true, x, SCREEN_H/2-FONT_SPACING/2, "The End");
843 void Cancel();
845 bool KeyPressed(int key, int mod)
847 if (key=='r' && (mod & KMOD_CTRL))
849 time = 0;
850 memset(p, 0, sizeof(p));
852 else
853 return Menu::KeyPressed(key, mod);
854 return true;
857 void Update(double td)
859 noMouse = 1;
861 double old = time;
863 t = td;
864 if (keyState[SDLK_LSHIFT])
865 t = td*5;
866 if (keyState['0'])
867 t = MAX(-td*5, -time);
869 Menu::Update(t);
870 double s = scroll = time * 50;
871 // if (scroll > scrollMax)
872 // scroll = fmod(scroll, scrollMax);
873 // scroll = 0, time = 0;
875 if (old>4 && time > 4)
877 if (scroll < scrollMax + FONT_SPACING*17)
879 for (int i=int( old/2.5); i<int(time/2.5); i++)
881 int xs = (rand()%SCREEN_W * 6 + 1) / 8;
882 for (int j=rand()%3+1; j; j--)
883 new Particle(rand()&1 ? COLLAPSABLE : COLLAPSE_DOOR, xs+j*64);
886 if (scroll > scrollMax + FONT_SPACING*27)
887 Cancel();
893 Ending* Ending::ending = 0;
894 void* Ending::Particle::operator new(size_t sz)
896 static int start = 0;
897 const int max = sizeof(ending->p)/sizeof(ending->p[0]);
898 for (int i=0; i<max; i++)
900 start++;
901 if (start==max) start=0;
902 if (ending->p[start].type==EMPTY)
903 return &ending->p[start];
905 return &ending->p[rand() % max];
908 struct TitleMenu : public OptMenuTitle
910 TitleMenu() : OptMenuTitle("")
912 //left_align = 1;
914 SaveState p;
915 freeSlot = -1;
916 for (int i=0; i<MAX_GAMESLOT; i++)
918 char tmp[80];
919 GetSlotName(i, tmp);
920 FILE* f = file_open(tmp, "rb");
921 if (f)
923 p.LoadSave(f, false);
924 fclose(f);
926 if (p.general.completionPercentage==100 && p.general.masteredPercentage==100)
927 sprintf(optionSlotName[i], "Continue game %d (All Clear!)", i+1, p.general.completionPercentage, p.general.masteredPercentage);
928 else if (p.general.completionPercentage==100)
929 sprintf(optionSlotName[i], "Continue game %d (%d%% + %d%%)", i+1, p.general.completionPercentage, p.general.masteredPercentage);
930 else
931 sprintf(optionSlotName[i], "Continue game %d (%d%% complete)", i+1, p.general.completionPercentage);
933 opt[num_opt++] = OPT_GAMESLOT_0 + i;
935 else
937 // sprintf(optionSlotName[i], "Start new game (slot %d)", i+1);
938 // opt[num_opt++] = OPT_GAMESLOT_0 + i;
940 if (freeSlot==-1)
941 freeSlot = i;
946 if (num_opt < MAX_GAMESLOT)
947 opt[num_opt++] = OPT_GAMESLOT_NEW;
949 opt[num_opt++] = OPT_OPTIONS;
950 #ifdef EDIT
951 opt[num_opt++] = OPT_END;
952 opt[num_opt++] = OPT_END2;
953 #endif
954 opt[num_opt++] = OPT_QUIT;
956 Init();
957 #ifdef EDIT
958 r.y-=FONT_SPACING*2;
959 r2.y-=FONT_SPACING*2;
960 #else
961 if (num_opt==3)
962 r.y+=FONT_SPACING+FONT_SPACING/2, r2.y+=FONT_SPACING+FONT_SPACING/2;
963 #endif
965 bool KeyPressed(int key, int mod);
968 struct QuitConfirmMenu : public OptMenuTitle
970 QuitConfirmMenu() : OptMenuTitle("Quit: Are you sure?")
972 opt[num_opt++] = OPT_QUIT_CONFIRM;
973 opt[select=num_opt++] = OPT_QUIT_CANCEL;
974 Init();
976 r.y += FONT_SPACING*1;
977 r2.y += FONT_SPACING*2;
982 struct DeleteConfirmMenu : public OptMenuTitle
984 char tmp[80];
985 int num;
986 DeleteConfirmMenu(int _num) : OptMenuTitle(&tmp[0]), num(_num)
988 //left_align = 1;
990 sprintf(tmp, "Really delete game %d?", num+1);
991 opt[num_opt++] = OPT_DELETE_CONFIRM;
992 opt[select=num_opt++] = OPT_DELETE_CANCEL;
993 Init();
995 r.y += FONT_SPACING*1;
996 r2.y += FONT_SPACING*2;
998 void Select()
1000 if (select<0 || select>=num_opt)
1001 return;
1002 if (opt[select] == OPT_DELETE_CONFIRM)
1004 GetSlotName(num, tmp);
1005 remove(tmp);
1007 Pop();
1008 Pop();
1009 new TitleMenu();
1013 bool TitleMenu::KeyPressed(int key, int mod)
1015 if (key==SDLK_DELETE || key==SDLK_BACKSPACE || key==SDLK_F2)
1017 if (select<0 || select>=num_opt || opt[select]<OPT_GAMESLOT_0 || opt[select]>OPT_GAMESLOT_LAST)
1018 return true;
1019 int i = opt[select] - OPT_GAMESLOT_0;
1021 new DeleteConfirmMenu(i);
1023 return true;
1025 return OptMenu::KeyPressed(key, mod);
1028 struct PauseMenu : public OptMenu
1030 PauseMenu(bool isMap, bool allowGotoMap, int allowEnd, int allowEnd2) : OptMenu("Paused")
1032 opt[num_opt++] = OPT_RESUME;
1033 if (!isMap)
1034 opt[num_opt++] = OPT_RESTART;
1035 opt[num_opt++] = OPT_OPTIONS;
1036 opt[num_opt++] = OPT_HELP;
1037 if (allowEnd || allowEnd2)
1038 opt[num_opt++] = allowEnd2 ? OPT_END2 : OPT_END;
1039 opt[num_opt++] = (isMap || !allowGotoMap) ? OPT_QUIT_MENU_CONFIRM : OPT_GOTO_MAP;
1040 Init();
1042 virtual bool KeyPressed(int key, int mod)
1044 if (key=='p' || key==SDLK_PAUSE)
1046 Cancel();
1047 return true;
1049 return Menu::KeyPressed(key, mod);
1054 struct OptionMenu : public OptMenuTitle
1056 bool title;
1057 OptionMenu(bool _title) : OptMenuTitle("Options"), title(_title)
1059 opt[num_opt++] = OPT_FULLSCREEN;
1061 opt[num_opt++] = OPT_BACK;
1063 if (title)
1065 OptMenuTitle::Init(), renderBG=false;
1066 r.y += FONT_SPACING;
1067 r2.y += FONT_SPACING*2;
1069 else
1070 OptMenu::Init(), renderBG=true;
1072 void Render()
1074 if (title)
1075 OptMenuTitle::Render();
1076 else
1077 OptMenu::Render();
1081 void RenderFade(double time, int dir, int seed);
1083 struct Fader : public Menu
1085 int dir;
1086 double speed;
1087 int result;
1088 Fader(int _dir, int _result, double _speed=1) : dir(_dir), result(_result), speed(_speed)
1090 renderBG = under ? under->renderBG : true;
1092 void Render()
1094 if (under)
1095 under->Render();
1097 RenderFade(time, dir, (long)this);
1099 void Update(double timedelta)
1101 Menu::Update(timedelta * speed);
1102 if (result==-2)
1104 if (time > 0.7)
1105 quitting = 1;
1107 else if (time >= 0.5)
1109 Pop();
1110 if (result==-1)
1112 new TitleMenu();
1113 new Fader(1, -3);
1115 if (result==-4)
1117 Pop(); // Remove old menu
1118 HackKeyPress(SDLK_ESCAPE, KMOD_CTRL | KMOD_SHIFT); // Reload map combination!
1120 if (result==-6)
1122 Pop(); // Remove old menu
1123 new Fader(1, 0, speed);
1125 if (result==-5 || result==-7)
1127 new Ending(result==-7);
1128 new Fader(1, 0, speed);
1134 void Ending::Cancel()
1136 new Fader(-1, -6, 0.3);
1137 // Pop();
1140 void ToggleFullscreen();
1142 void OptMenu::Select()
1144 if (select<0 || select>=num_opt)
1145 return;
1146 switch(opt[select])
1148 case OPT_RESUME:
1149 Cancel();
1150 break;
1152 case OPT_RESTART:
1153 Cancel();
1154 HackKeyPress('r', KMOD_CTRL);
1155 break;
1157 case OPT_GOTO_MAP:
1158 case OPT_GOTO_MAP_CONTINUE:
1159 Pop();
1160 HackKeyPress(SDLK_ESCAPE, KMOD_CTRL);
1161 break;
1163 case OPT_QUIT:
1164 new QuitConfirmMenu();
1165 break;
1167 case OPT_FULLSCREEN:
1168 ToggleFullscreen();
1169 break;
1171 case OPT_QUIT_CONFIRM:
1172 new Fader(-1, -2);
1173 break;
1175 case OPT_QUIT_MENU_CONFIRM:
1176 Pop();
1177 new Fader(-1, -1);
1178 break;
1180 case OPT_OPTIONS:
1181 new OptionMenu(!activeMenu->renderBG);
1182 break;
1184 case OPT_HELP:
1185 new HintReview();
1186 break;
1188 case OPT_QUIT_CANCEL:
1189 case OPT_BACK:
1190 Pop();
1191 break;
1193 case OPT_END:
1194 new Fader(-1, -5, 0.3);
1195 break;
1197 case OPT_END2:
1198 new Fader(-1, -7, 0.3);
1199 break;
1201 case OPT_UNDO:
1202 Pop();
1203 HackKeyPress('z', 0);
1204 break;
1206 default:
1207 if (opt[select]>=OPT_GAMESLOT_0 && opt[select]<=OPT_GAMESLOT_LAST
1208 || opt[select]==OPT_GAMESLOT_NEW && freeSlot>=0)
1210 if (opt[select]==OPT_GAMESLOT_NEW)
1211 GetSlotName(freeSlot, currentSlot);
1212 else
1213 GetSlotName(opt[select]-OPT_GAMESLOT_0, currentSlot);
1214 new Fader(-1, -4);
1216 break;