Compile using gettext by default
[hex-a-hop.git] / menus.h
blob5912977c36a5a5e8404af970469672ca66645aaf
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
19 #include <string>
21 struct Menu;
22 Menu* activeMenu = 0;
23 Menu* deadMenu = 0;
25 int SDLPangoTextHeight(const std::string &text_utf8, int width);
27 static void HackKeyPress(int key, int mod)
29 int k = keyState[key];
30 StateMakerBase::current->KeyPressed(key, mod);
31 keyState[key] = k;
34 struct Menu {
35 bool renderBG;
37 Menu() : renderBG(true), under(activeMenu), time(0) { activeMenu = this; }
39 virtual ~Menu() {
40 if(this!=deadMenu) FATAL("this!=deadMenu");
41 deadMenu=under;
44 static void Pop()
46 if (!activeMenu) return;
47 Menu* m = activeMenu;
48 activeMenu = activeMenu->under;
49 m->under = deadMenu;
50 deadMenu = m;
53 virtual bool KeyPressed(int key, int /*mod*/)
55 if (key=='w' || key==SDLK_UP || key==SDLK_KP8 || key=='q' || key=='e' || key==SDLK_KP7 || key==SDLK_KP9)
56 Move(-1), noMouse=1;
57 else if (key=='s' || key==SDLK_DOWN || key==SDLK_KP2 || key=='a' || key=='d' || key==SDLK_KP1 || key==SDLK_KP3)
58 Move(1), noMouse=1;
59 else if (key==' ' || key==SDLK_RETURN)
61 Select();
62 noMouse=1;
64 else if (key==SDLK_ESCAPE || key==SDLK_BACKSPACE || key==SDLK_KP_PERIOD || key==SDLK_DELETE)
65 Cancel();
66 else
67 return false;
68 return true;
70 virtual void Mouse(int /*x*/, int /*y*/, int /*dx*/, int /*dy*/, int buttons_pressed, int /*buttons_released*/, int /*buttons*/)
72 if (buttons_pressed==4 || buttons_pressed==2)
73 Cancel();
76 virtual void Move(int /*dir*/) {}
77 virtual void Select() {}
78 virtual void Cancel() {}
80 virtual void Update(double timedelta) {time+=timedelta;}
81 virtual void Render() = 0;
83 Menu * under;
84 double time;
87 const char * hint[] = {
89 /*EMPTY*/
90 _("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."),
91 /*NORMAL*/
93 /*COLLAPSABLE*/
94 _("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!"),
95 /*COLLAPSE_DOOR*/
96 _("The coloured walls flatten themselves when there are no matching coloured tiles remaining."),
97 /*TRAMPOLINE*/
98 _("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!"),
99 /*SPINNER*/
100 _("A red spinner tile will rotate the pieces around it when you step on it."),
101 /*WALL*/
103 /*COLLAPSABLE2*/
104 _("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..."),
105 /*COLLAPSE_DOOR2*/
107 /*GUN*/
108 _("Yellow laser tiles fire when you step on them. Shooting other laser tiles is more destructive."),
109 /*TRAP*/
110 _("Ice is slippery! Please be careful!!"),
111 /*COLLAPSABLE3*/
113 /*BUILDER*/
114 _("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."),
115 /*SWITCH*/
117 /*FLOATING_BALL*/
118 /* TRANSLATORS: pop means vanish and Emy drowns (you loose) */
119 _("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."),
120 /*LIFT_DOWN*/
121 _("The blue lifts go up or down when you land on them."),
122 /*LIFT_UP*/
125 0,0,0,0,
126 //Item 0 (21)
127 _("The spiky anti-ice pickups turn icy tiles into blue ones. They get used automatically when you land on ice."),
128 //Item 1 (22)
129 /* TRANSLATORS: Normally you jump from one plate to another. The golden jump (a
130 pickup) allows you to jump and land on the *same* plate */
131 _("Collecting the golden jump pickups will allow you to do a big vertical jump. Try it out on different types of tiles. Use the space bar or return key to jump. Or click on the tile you're currently on with the mouse."),
133 0,0,
135 // Map (25)
136 _("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."),
138 // Scoring (26)
139 /* TRANSLATORS: Levels are depicted as black balls. Once you passed them, they
140 turn silver. If you reached the par, they turn golden (with a crown), and if
141 you beat the par, they turn their shape/color once more */
142 _("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?"),
144 0,0,0,
146 // End of help (30)
147 _("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!"),
149 // First help page (31)
150 /* TRANSLATORS: This string is copied twice into the POT file to workaround a
151 gettext limitation (no macro expansion). The extracted string "Welcome to "
152 will not be used. */
153 _("Welcome to " GAMENAME "! This is a puzzle game based on hexagonal tiles. There is no time limit and no real-time element, 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."),
156 struct HintMessage : public Menu
158 static int flags;
160 SDL_Rect InnerTextWindowRect;
161 SDL_Rect OuterTextWindowRect;
162 const char * msg;
163 char title[500];
164 int state;
166 const SDL_Rect &GetInnerWindowRect() const
168 return InnerTextWindowRect;
171 const SDL_Rect &GetOuterWindowRect() const
173 return OuterTextWindowRect;
176 static bool FlagTile(int t, bool newStuff=true)
178 if (t==LIFT_UP) t=LIFT_DOWN;
179 if (newStuff && (flags & (1<<t))) return false;
180 if (!newStuff && !(flags & (1<<t))) return false;
181 if (t>31) return false;
182 if (!hint[t]) return false;
184 flags |= 1<<t;
185 new HintMessage(hint[t]);
186 return true;
189 HintMessage(const char * m) { Init(m); }
191 void Init(const char * m)
193 state = 0; time = 0;
194 memset(title, 0, sizeof(title));
195 char * x = strstr(m, "|");
196 if (!x)
198 msg = m;
199 strcpy(title, _("Info:"));
201 else
203 strncpy(title, m, x-m);
204 msg=x+1;
207 char msg_utf8[5000];
208 ConvertToUTF8(msg, msg_utf8, sizeof(msg_utf8)/sizeof(char));
210 std::string text(msg_utf8);
211 while (text.find(" ") != std::string::npos)
212 text.replace(text.find(" "), 2, "\n");
214 InnerTextWindowRect.w = SCREEN_W-TILE_W1*2;
215 InnerTextWindowRect.h = SDLPangoTextHeight(text, InnerTextWindowRect.w - 2*FONT_SPACING);
216 InnerTextWindowRect.h += FONT_SPACING;
217 OuterTextWindowRect = InnerTextWindowRect;
218 OuterTextWindowRect.w += 4;
219 OuterTextWindowRect.h += FONT_SPACING+4;
222 virtual void Render()
224 int y = SCREEN_H/4 + int(SCREEN_H*MAX(1-time*5, 0)*3/4);
225 if (state)
226 y = SCREEN_H/4 + int(SCREEN_H*(-time*5)*3/4);
228 Render(0, y);
230 if (!state && time>0.2)
231 PrintR(SCREEN_W-FONT_SPACING, SCREEN_H-FONT_SPACING, _("Press any key"));
234 void Render(int x, int y)
236 //if (y<0) {
237 // std::cout << "Error in Render: " << x << " " << y << "\n"; // CHECKME
238 // y = 0;
240 InnerTextWindowRect.x = x+TILE_W1;
241 InnerTextWindowRect.y = y;
242 OuterTextWindowRect.x = InnerTextWindowRect.x-2;
243 OuterTextWindowRect.y = InnerTextWindowRect.y-2-FONT_SPACING;
244 // Height is reduced in SDL_FillRect!!? Why? ==> Use a copy:
245 SDL_Rect r2 = InnerTextWindowRect;
246 SDL_Rect r = OuterTextWindowRect;
247 SDL_FillRect(screen, &r, SDL_MapRGB(screen->format, 60,90,90));
248 SDL_FillRect(screen, &r2, SDL_MapRGB(screen->format, 20,50,50));
249 Print(OuterTextWindowRect.x+FONT_SPACING/4, y-FONT_SPACING, "%s", title);
250 /* TRANSLATORS: This specifies how the text in the help dialog should
251 be aligned. Do *not* translate the text itself but use one of "left",
252 "center" or "right" (untranslated!). The default is "center". */
253 std::string alignment = _("text alignment");
254 if (alignment == "right")
255 Print_Aligned(true, InnerTextWindowRect.x + InnerTextWindowRect.w - FONT_SPACING, y+FONT_SPACING/2, InnerTextWindowRect.w - 2*FONT_SPACING, msg, 2);
256 else if (alignment == "left")
257 Print_Aligned(true, InnerTextWindowRect.x + FONT_SPACING, y+FONT_SPACING/2, InnerTextWindowRect.w - 2*FONT_SPACING, msg, 0);
258 else
259 Print_Aligned(true, x+SCREEN_W/2, y+FONT_SPACING/2, InnerTextWindowRect.w - 2*FONT_SPACING, msg, 1);
262 virtual void Mouse(int /*x*/, int /*y*/, int /*dx*/, int /*dy*/, int buttons_pressed, int /*buttons_released*/, int /*buttons*/)
264 if (buttons_pressed && state==0 && time>0.2)
265 state = 1, time=0;
268 bool KeyPressed(int /*key*/, int /*mod*/)
270 if (state==0 && time>0.2)
271 state = 1, time=0;
272 return true;
275 virtual void Update(double timedelta)
277 Menu::Update(timedelta);
278 if(state && time > 0.2)
279 Pop();
283 int HintMessage::flags = 1<<31 | 1<<30;
285 struct HintReview : public HintMessage
287 int page;
288 int page_dir;
289 int page_count;
290 int page_display;
291 HintReview() : HintMessage(hint[31]), page(31), page_dir(0), page_display(0)
293 page_count=0;
294 for (int i=0; i<32; i++)
295 if (flags & (1<<i))
296 page_count++;
299 void Cancel() { Pop(); }
300 void Select() { Pop(); }
303 virtual void Mouse(int x, int y, int dx, int dy, int buttons_pressed, int buttons_released, int buttons)
305 if (buttons_pressed==1)
307 if (y < SCREEN_H/4-FONT_SPACING+2)
308 Move(-1);
309 else if (y > HintMessage::GetOuterWindowRect().y+
310 HintMessage::GetOuterWindowRect().h)
311 Move(1);
312 else
313 Cancel();
315 else if (buttons_pressed==8)
316 Move(-1);
317 else if (buttons_pressed==16)
318 Move(1);
319 else
320 Menu::Mouse(x,y,dx,dy,buttons_pressed, buttons_released, buttons);
323 bool KeyPressed(int key, int mod)
325 if (key==SDLK_LEFT)
326 Move(-1);
327 else if (key==SDLK_RIGHT)
328 Move(1);
329 else
330 return Menu::KeyPressed(key, mod);
331 return true;
334 virtual void Render()
336 const double SPD = 10;
338 #ifdef EDIT
339 sprintf (title, _("Help (Page --)"), page_display+1, page_count);
340 #else
341 sprintf (title, _("Help (Page %d/%d)"), page_display+1, page_count);
342 #endif
344 int y=SCREEN_H/4;
345 if (state==0)
346 y = SCREEN_H/4+int(MAX(0, time*SPD)*-page_dir*SCREEN_H);
347 if (state==1)
348 y = SCREEN_H/4+int(MAX(0, 1-time*SPD)*page_dir*SCREEN_H);
350 //if (!noMouse)
352 PrintC(false, SCREEN_W/2, y-FONT_SPACING*2, "^");
353 PrintC(false, SCREEN_W/2, HintMessage::GetOuterWindowRect().y+
354 HintMessage::GetOuterWindowRect().h, "_");
357 HintMessage::Render(0,y);
359 if (time > 1.0/SPD && page_dir && state==0)
361 do {
362 page = (page+page_dir) & 31;
363 #ifdef EDIT
364 if (hint[page]) break;
365 #endif
366 } while (hint[page]==0 || !(flags&(1<<page)));
367 Init(hint[page]);
369 page_display = (page_display + page_count + page_dir) % page_count;
371 time = 0;
372 state=1;
374 if (time>1.0/SPD && state==1)
375 state=0, page_dir = 0;
377 virtual void Update(double timedelta)
379 Menu::Update(timedelta);
381 void Move(int dir)
383 if (page_dir)
384 return;
385 time = 0;
386 page_dir = dir;
387 state = 0;
391 #define MAX_GAMESLOT 4
392 enum option {
393 OPT_GAMESLOT_0,
394 OPT_GAMESLOT_LAST = OPT_GAMESLOT_0 + MAX_GAMESLOT - 1,
395 OPT_RESUME,
396 OPT_RESTART,
397 OPT_GOTO_MAP,
398 OPT_GOTO_MAP_CONTINUE,
399 OPT_FULLSCREEN,
400 OPT_OPTIONS,
401 OPT_QUIT,
402 OPT_QUIT_CONFIRM,
403 OPT_QUIT_CANCEL,
404 OPT_QUIT_MENU_CONFIRM,
405 OPT_HELP,
406 OPT_GAMESLOT_NEW,
407 OPT_DELETE_CONFIRM,
408 OPT_DELETE_CANCEL,
409 OPT_UNDO,
410 OPT_BACK,
411 OPT_END, OPT_END2,
414 char optionSlotName[MAX_GAMESLOT][400] = { {0} };
415 char currentSlot[800] = "";
416 int freeSlot = -1;
417 char* GetSlotName(int i, char * t)
419 sprintf(t, "save%d.dat", i+1);
420 return t;
423 char * optionString[] = {
424 optionSlotName[0],
425 optionSlotName[1],
426 optionSlotName[2],
427 optionSlotName[3],
428 _("Resume"),
429 _("Restart Level"),
430 _("Return to Map"),
431 _("Continue"),
432 _("Toggle Fullscreen"),
433 _("Options"),
434 _("Quit"),
435 _("Yes"),
436 _("No"),
437 _("Return to Title"),
438 _("Help"),
439 _("Start New Game"),
440 _("Yes, really delete it!"),
441 _("Don't do it!"),
442 _("Undo Last Move"),
443 _("OK"),
444 _("View Credits Sequence"), _("View Credits Sequence"),
448 struct OptMenu : public Menu
450 int select;
451 int num_opt;
452 int opt[10];
453 bool left_align;
454 const char * title;
455 SDL_Rect r, r2;
457 OptMenu(const char * t) : select(0), title(t)
459 left_align = false;
460 num_opt = 0;
463 void Init()
465 r.w=SCREEN_W/2;
466 r.x=(SCREEN_W-r.w)/2;
467 r.y=SCREEN_H/3;
469 r2 = r;
471 const int SPACE = int(FONT_SPACING * 1.5);
473 r2.h = SPACE*num_opt + FONT_SPACING/2;
474 r.h = r2.h + (FONT_SPACING+2*2);
475 r.y -= FONT_SPACING+2;
476 r.w += 2*2;
477 r.x -= 2;
480 void RenderOption(int o, const char * s)
482 int y = r2.y + FONT_SPACING/2 + int(FONT_SPACING * 1.5) * o;
483 if (left_align)
485 int x = r.x + SDLPangoTextWidth(" ");
486 int x1 = x + SDLPangoTextWidth("> ");
487 if (select==o)
489 //x += int( sin(time*9)*2.5 );
490 //y += int( sin(time*9 + 1.5)*1.5 );
491 Print(x, y, "> %s", s);
493 else
495 Print(x1, y, "%s", s);
498 else
500 int x = r.x + r.w/2;
501 if (select==o)
503 //x += int( sin(time*9)*2.5 );
504 //y += int( sin(time*9 + 1.5)*1.5 );
505 PrintC(false, x, y, "> %s <", s);
507 else
509 PrintC(false, x, y, "%s", s);
514 void Move(int dir)
516 select += dir;
517 if (select<0) select = num_opt-1;
518 if (select>=num_opt) select = 0;
520 virtual void Mouse(int x, int y, int dx, int dy, int buttons_pressed, int buttons_released, int buttons)
522 if (1)
524 if (x<r2.x || y<r2.y || x>r2.x+r2.w || y>r2.y+r2.h)
526 if (buttons_pressed!=4 && buttons_pressed!=2)
527 return;
529 else
531 select = (y-r2.y-FONT_SPACING/3) / int(FONT_SPACING*1.5);
532 if (select<0) select = 0;
533 if (select>=num_opt) select = num_opt-1;
536 if (buttons_pressed==1)
537 Select();
538 Menu::Mouse(x, y, dx, dy, buttons_pressed, buttons_released, buttons);
540 void Select();
542 void Render()
544 RenderBG();
545 RenderTitle();
546 RenderOptions();
548 void RenderBG()
550 SDL_FillRect(screen, &r, SDL_MapRGB(screen->format, 60,90,90));
551 SDL_FillRect(screen, &r2, SDL_MapRGB(screen->format, 20,50,50));
553 void RenderTitle()
555 if (left_align)
556 Print(r2.x+SDLPangoTextWidth(" "), r.y+4, title);
557 else
558 PrintC(false, r2.x+r2.w/2, r.y+4, title);
560 void RenderOptions()
562 for (int i=0; i<num_opt; i++)
563 RenderOption(i, optionString[opt[i]]);
565 void Cancel()
567 Pop();
571 struct WinLoseScreen : public OptMenu
573 bool win;
574 int score, par, best_score;
575 WinLoseScreen(bool _win, int _score=0, int _par=0, int _prev_score=0) :
576 OptMenu(_win ? _("Level Complete!") : _("Emi can't swim...")),
577 win(_win),
578 score(_score),
579 par(_par),
580 best_score(_prev_score)
582 if (!win)
583 opt[num_opt++] = OPT_UNDO;
584 if (!win)
585 opt[num_opt++] = OPT_RESTART;
586 opt[num_opt++] = win ? OPT_GOTO_MAP_CONTINUE : OPT_GOTO_MAP;
588 Init();
590 if (win)
592 r.h += FONT_SPACING * 3 + FONT_SPACING/2;
593 r2.h += FONT_SPACING * 3 + FONT_SPACING/2;
597 void Render()
599 OptMenu::RenderBG();
600 OptMenu::RenderTitle();
602 if (win)
603 r2.y += FONT_SPACING * 3 + FONT_SPACING/2;
605 OptMenu::RenderOptions();
607 if (win)
608 r2.y -= FONT_SPACING * 3 + FONT_SPACING/2;
610 if (win)
612 int x = r.x+r.w/2;
613 int y = r2.y + FONT_SPACING/2;
614 if (score < best_score && score <= par)
615 PrintC(true, x, y, _("New Best Score: %d Par Score: %d Par Beaten!"), score, par);
616 else if (score < best_score)
617 PrintC(true, x, y, _("New Best Score: %d Par Score: %d"), score, par);
618 else if (par && best_score)
619 PrintC(true, x, y, _("Score: %d Previous Best: %d Par Score: %d"), score, best_score, par);
620 else
621 PrintC(true, x, y+FONT_SPACING/2, _("Well Done! Level Completed!"));
625 static void Undo()
627 Pop();
628 HackKeyPress('z', 0);
630 bool KeyPressed(int key, int mod)
632 if (key=='z' || key=='u' || key==SDLK_DELETE || key==SDLK_BACKSPACE)
633 return Undo(), true;
634 if (key=='r' && (mod & KMOD_CTRL))
636 Pop();
637 HackKeyPress(key, mod);
638 return true;
640 return OptMenu::KeyPressed(key, mod);
642 virtual void Mouse(int x, int y, int dx, int dy, int buttons_pressed, int buttons_released, int buttons)
644 if (buttons_pressed==4)
646 Undo();
647 return;
650 OptMenu::Mouse(x, y, dx, dy, buttons_pressed, buttons_released, buttons);
652 void Cancel()
654 if (win)
655 select=0, Select();
656 else
657 Undo();
661 struct OptMenuTitle : public OptMenu
663 OptMenuTitle(const char * t) : OptMenu(t)
665 renderBG = false;
666 //left_align = true;
669 void Render()
671 SDL_Rect a = {0,0,SCREEN_W,SCREEN_H};
672 // SDL_FillRect(screen, &a, SDL_MapRGB(screen->format, 10,25,25));
675 SDL_BlitSurface(titlePage, &a, screen, &a);
677 OptMenu::RenderTitle();
678 OptMenu::RenderOptions();
681 void Init()
683 OptMenu::Init();
685 int xw = SCREEN_W/6;
686 r.w += xw; r2.w+=xw;
687 int x = SCREEN_W - r.x - r.w;// - FONT_SPACING;
688 int y = SCREEN_H - r.y - r.h + FONT_SPACING/4;// - FONT_SPACING;
689 r.x += x; r2.x += x;
690 r.y += y; r2.y += y;
691 r.w+=20; r2.w+=20; r.h+=20; r2.h+=20;
693 r.h = r2.h = SCREEN_H;
694 r2.y = SCREEN_H/2;
695 r.y = r2.y - FONT_SPACING - 2;
699 const char *ending[] = {
700 _(" Very Well Done! "),
701 "", "", "", "", "", "",
703 "", "", "*15,4", "", "",
705 _("All Levels Cleared!"),
707 "", "", "*5,7", "", "",
709 _("Not a single green hexagon is left unbroken."),
710 "",
711 _("Truly, you are a master of hexagon hopping!"),
713 "", "", "*9,10", "", "",
715 "", _("Credits"), "", "", "",
716 _("<Design & Direction:"), ">Tom Beaumont", "", "",
717 _("<Programming:"), ">Tom Beaumont", "", "",
718 _("<Graphics:"), ">Tom Beaumont", "", "",
719 _("<Thanks to:"), ">Kris Beaumont", "", "",
720 // "", "<Some useless facts...", "",
721 _("<Tools and libraries used:"), "", ">Photoshop LE", ">Inno Setup", ">Wings 3D", ">MSVC", ">SDL", "",
722 _("<Fonts used:"), "", ">Copperplate gothic bold", ">Verdana", "",
724 "", "", "*12,14", "", "",
726 _("Thanks for playing!")
729 const char *ending2[] = {
730 _(" Absolutely Amazing! "),
731 "", "", "", "", "",
733 "", "", "*15,4", "", "",
735 _("All Levels Mastered!!"),
737 "", "", "*5,7", "", "",
739 _("You crushed every last green hexagon with"),
740 _("breathtaking efficiency!"),
742 _("You truly are a grand master of hexagon hopping!"),
745 const int endingLen = sizeof(ending)/sizeof(ending[0]);
746 const int endingLen2 = sizeof(ending2)/sizeof(ending2[0]);
747 const int scrollMax = SCREEN_H + (endingLen+1) * FONT_SPACING;
749 struct Ending : public Menu
751 struct Particle{
752 double x,y,xs,ys,xa,ya;
753 double time;
754 int type;
756 void Update(double td)
758 if (type==EMPTY) return;
760 time -= td;
761 x += xs*td;
762 y += ys*td;
763 xs += xa*td;
764 ys += ya*td;
765 if (type==TILE_LASER_HEAD && time<0.3)
766 type=TILE_FIRE_PARTICLE_1;
767 if (type==TILE_FIRE_PARTICLE_1 && time<0.1)
768 type=TILE_FIRE_PARTICLE_2;
770 // if (type==COLLAPSABLE || type==COLLAPSE_DOOR)
771 // for (int i=int((time)*40); i<int((time+td)*40); i++)
772 // new Particle(TILE_GREEN_FRAGMENT_1, x+32-rand()%63, y+20-rand()%40);
774 if (y>SCREEN_H && type==TILE_GREEN_FRAGMENT)
775 xa=0, ys=-ys/2, y=SCREEN_H, type=TILE_GREEN_FRAGMENT_1, time+=rand()%100*0.01;
776 if (y>SCREEN_H && type==TILE_GREEN_FRAGMENT_1)
777 xa=0, ys=-ys/2, y=SCREEN_H, type=TILE_GREEN_FRAGMENT_2, time+=rand()%100*0.01;
778 if (y>SCREEN_H && type==TILE_GREEN_FRAGMENT_2)
779 type = EMPTY;
781 if (time<=0)
783 if (type < 10)
785 for (int i=0; i<40; i++)
786 new Particle(TILE_LASER_HEAD, x, y);
787 for (int i=0; i<40; i++)
788 new Particle(TILE_GREEN_FRAGMENT + rand() % 3, x+32-rand()%63, y+20-rand()%40);
790 if (type==COLLAPSE_DOOR)
792 type = COLLAPSABLE;
793 time += 1;
795 else
797 type = EMPTY;
801 if (type==EMPTY) return;
803 if (type<0.05 && type<15)
804 RenderTile(false, tileSolid[type] ? TILE_WHITE_WALL : TILE_WHITE_TILE, int(x)+scrollX, int(y)+scrollY);
805 else
806 RenderTile(false, type, int(x)+scrollX, int(y)+scrollY);
809 Particle() : type(EMPTY) {}
810 Particle(int t, int _x) : x(_x), type(t)
812 xa=ys=xs=0; ya=400;
813 //if (t==1)
815 xs = rand()%100-50;
816 ys=-400-rand()%200;
817 //x=rand() % SCREEN_W;
818 //y=rand() % SCREEN_H;
819 y = SCREEN_H+20;
820 time = ys/-ya;
823 Particle(int t, double _x, double _y) : type(t)
825 x=_x; y=_y;
826 xa=0; ya=-100;
827 double r = (rand() % 2000) * PI / 1000;
828 double d = rand() % 50 + 250;
830 xs = sin(r)*d;
831 ys = cos(r)*d;
832 time = 1 + (rand() & 255) /255.0;
833 if (t==TILE_WATER_PARTICLE || t==TILE_GREEN_FRAGMENT || t==TILE_GREEN_FRAGMENT_1)
834 time = 2;
835 xa=-xs/time; ya=-ys/time;
836 if (t==TILE_WATER_PARTICLE || t==TILE_GREEN_FRAGMENT || t==TILE_GREEN_FRAGMENT_1)
837 ya += 500, ya/=2, xa/=2, xs/=2, ys/=2;
839 ~Particle() { type = EMPTY; }
840 void* operator new(size_t sz);
843 Particle p[1000];
845 double scroll;
846 double t;
847 bool goodEnding;
848 static Ending* ending;
850 Ending(bool _goodEnd) : goodEnding(_goodEnd)
852 memset(p, 0, sizeof(p));
853 ending = this;
854 renderBG = false;
855 scroll = 0;
856 t=0;
859 void Render()
861 SDL_Rect a = {0,0,SCREEN_W,SCREEN_H};
862 SDL_FillRect(screen, &a, SDL_MapRGB(screen->format, 10,25,25));
864 for (unsigned int i=0; i<sizeof(p)/sizeof(p[0]); i++)
865 p[i].Update(t);
867 int x = a.x + a.w/2;
868 int xl = SCREEN_W/5;
869 int xr = SCREEN_W*4/5;
870 int y = SCREEN_H - int(scroll);
871 for (int i=0; i<endingLen; i++)
873 if (y>-FONT_SPACING*2 && y<SCREEN_H+FONT_SPACING)
875 const char * xx = (i<endingLen2 && goodEnding) ? ending2[i] : ::ending[i];
876 if (xx[0]=='<')
877 Print(xl, y+FONT_SPACING, xx+1);
878 else if (xx[0]=='>')
879 PrintR(xr, y, xx+1);
880 else if (xx[0]=='*')
882 RenderTile(false, atoi(xx+1), (xl+x)/2+scrollX, y+FONT_SPACING/2+scrollY);
883 RenderTile(false, atoi(strchr(xx, ',')+1), (xr+x)/2+scrollX, y+FONT_SPACING/2+scrollY);
885 else
886 PrintC(false, x, y, xx);
888 y+=FONT_SPACING;
890 if (scroll > scrollMax + FONT_SPACING*10)
891 PrintC(true, x, SCREEN_H/2-FONT_SPACING/2, _("The End"));
894 void Cancel();
896 bool KeyPressed(int key, int mod)
898 if (key=='r' && (mod & KMOD_CTRL))
900 time = 0;
901 memset(p, 0, sizeof(p));
903 else
904 return Menu::KeyPressed(key, mod);
905 return true;
908 void Update(double td)
910 noMouse = 1;
912 double old = time;
914 t = td;
915 if (keyState[SDLK_LSHIFT])
916 t = td*5;
917 if (keyState[SDLK_0])
918 t = MAX(-td*5, -time);
920 Menu::Update(t);
921 scroll = time * 50;
922 // if (scroll > scrollMax)
923 // scroll = fmod(scroll, scrollMax);
924 // scroll = 0, time = 0;
926 if (old>4 && time > 4)
928 if (scroll < scrollMax + FONT_SPACING*17)
930 for (int i=int( old/2.5); i<int(time/2.5); i++)
932 int xs = (rand()%SCREEN_W * 6 + 1) / 8;
933 for (int j=rand()%3+1; j; j--)
934 new Particle(rand()&1 ? COLLAPSABLE : COLLAPSE_DOOR, xs+j*64);
937 if (scroll > scrollMax + FONT_SPACING*27)
938 Cancel();
944 Ending* Ending::ending = 0;
945 void* Ending::Particle::operator new(size_t /*sz*/)
947 static int start = 0;
948 const int max = sizeof(ending->p)/sizeof(ending->p[0]);
949 for (int i=0; i<max; i++)
951 start++;
952 if (start==max) start=0;
953 if (ending->p[start].type==EMPTY)
954 return &ending->p[start];
956 return &ending->p[rand() % max];
959 struct TitleMenu : public OptMenuTitle
961 TitleMenu() : OptMenuTitle("")
963 //left_align = 1;
965 SaveState p;
966 freeSlot = -1;
967 for (int i=0; i<MAX_GAMESLOT; i++)
969 char tmp[80];
970 GetSlotName(i, tmp);
971 FILE* f = file_open(tmp, "rb");
972 if (f)
974 p.LoadSave(f, false);
975 fclose(f);
977 if (p.general.completionPercentage==100 && p.general.masteredPercentage==100)
978 sprintf(optionSlotName[i], _("Continue game %d (All Clear!)"), i+1);
979 else if (p.general.completionPercentage==100)
980 sprintf(optionSlotName[i], _("Continue game %d (%d%% + %d%%)"), i+1, p.general.completionPercentage, p.general.masteredPercentage);
981 else
982 sprintf(optionSlotName[i], _("Continue game %d (%d%% complete)"), i+1, p.general.completionPercentage);
984 opt[num_opt++] = OPT_GAMESLOT_0 + i;
986 else
988 // sprintf(optionSlotName[i], "Start new game (slot %d)", i+1);
989 // opt[num_opt++] = OPT_GAMESLOT_0 + i;
991 if (freeSlot==-1)
992 freeSlot = i;
997 if (num_opt < MAX_GAMESLOT)
998 opt[num_opt++] = OPT_GAMESLOT_NEW;
1000 opt[num_opt++] = OPT_OPTIONS;
1001 #ifdef EDIT
1002 opt[num_opt++] = OPT_END;
1003 opt[num_opt++] = OPT_END2;
1004 #endif
1005 opt[num_opt++] = OPT_QUIT;
1007 Init();
1008 #ifdef EDIT
1009 r.y-=FONT_SPACING*2;
1010 r2.y-=FONT_SPACING*2;
1011 #else
1012 if (num_opt==3)
1013 r.y+=FONT_SPACING+FONT_SPACING/2, r2.y+=FONT_SPACING+FONT_SPACING/2;
1014 #endif
1016 bool KeyPressed(int key, int mod);
1019 struct QuitConfirmMenu : public OptMenuTitle
1021 QuitConfirmMenu() : OptMenuTitle(_("Quit: Are you sure?"))
1023 opt[num_opt++] = OPT_QUIT_CONFIRM;
1024 opt[select=num_opt++] = OPT_QUIT_CANCEL;
1025 Init();
1027 r.y += FONT_SPACING*1;
1028 r2.y += FONT_SPACING*2;
1033 struct DeleteConfirmMenu : public OptMenuTitle
1035 char tmp[800];
1036 int num;
1037 DeleteConfirmMenu(int _num) : OptMenuTitle(&tmp[0]), num(_num)
1039 //left_align = 1;
1041 sprintf(tmp, _("Really delete game %d?"), num+1);
1042 opt[num_opt++] = OPT_DELETE_CONFIRM;
1043 opt[select=num_opt++] = OPT_DELETE_CANCEL;
1044 Init();
1046 r.y += FONT_SPACING*1;
1047 r2.y += FONT_SPACING*2;
1049 void Select()
1051 if (select<0 || select>=num_opt)
1052 return;
1053 if (opt[select] == OPT_DELETE_CONFIRM)
1055 GetSlotName(num, tmp);
1056 remove(tmp);
1058 Pop();
1059 Pop();
1060 new TitleMenu();
1064 bool TitleMenu::KeyPressed(int key, int mod)
1066 if (key==SDLK_DELETE || key==SDLK_BACKSPACE || key==SDLK_F2)
1068 if (select<0 || select>=num_opt || opt[select]<OPT_GAMESLOT_0 || opt[select]>OPT_GAMESLOT_LAST)
1069 return true;
1070 int i = opt[select] - OPT_GAMESLOT_0;
1072 new DeleteConfirmMenu(i);
1074 return true;
1076 return OptMenu::KeyPressed(key, mod);
1079 struct PauseMenu : public OptMenu
1081 PauseMenu(bool isMap, bool allowGotoMap, int allowEnd, int allowEnd2) : OptMenu(_("Paused"))
1083 opt[num_opt++] = OPT_RESUME;
1084 if (!isMap)
1085 opt[num_opt++] = OPT_RESTART;
1086 opt[num_opt++] = OPT_OPTIONS;
1087 opt[num_opt++] = OPT_HELP;
1088 if (allowEnd || allowEnd2)
1089 opt[num_opt++] = allowEnd2 ? OPT_END2 : OPT_END;
1090 opt[num_opt++] = (isMap || !allowGotoMap) ? OPT_QUIT_MENU_CONFIRM : OPT_GOTO_MAP;
1091 Init();
1093 virtual bool KeyPressed(int key, int mod)
1095 if (key=='p' || key==SDLK_PAUSE)
1097 Cancel();
1098 return true;
1100 return Menu::KeyPressed(key, mod);
1105 struct OptionMenu : public OptMenuTitle
1107 bool title;
1108 OptionMenu(bool _title) : OptMenuTitle(_("Options")), title(_title)
1110 opt[num_opt++] = OPT_FULLSCREEN;
1112 opt[num_opt++] = OPT_BACK;
1114 if (title)
1116 OptMenuTitle::Init(), renderBG=false;
1117 r.y += FONT_SPACING;
1118 r2.y += FONT_SPACING*2;
1120 else
1121 OptMenu::Init(), renderBG=true;
1123 void Render()
1125 if (title)
1126 OptMenuTitle::Render();
1127 else
1128 OptMenu::Render();
1132 void RenderFade(double time, int dir, int seed);
1134 struct Fader : public Menu
1136 int dir;
1137 double speed;
1138 int result;
1139 Fader(int _dir, int _result, double _speed=1) : dir(_dir), speed(_speed), result(_result)
1141 renderBG = under ? under->renderBG : true;
1143 void Render()
1145 if (under)
1146 under->Render();
1148 RenderFade(time, dir, (long)this);
1150 void Update(double timedelta)
1152 Menu::Update(timedelta * speed);
1153 if (result==-2)
1155 if (time > 0.7)
1156 quitting = 1;
1158 else if (time >= 0.5)
1160 Pop();
1161 if (result==-1)
1163 new TitleMenu();
1164 new Fader(1, -3);
1166 if (result==-4)
1168 Pop(); // Remove old menu
1169 HackKeyPress(SDLK_ESCAPE, KMOD_CTRL | KMOD_SHIFT); // Reload map combination!
1171 if (result==-6)
1173 Pop(); // Remove old menu
1174 new Fader(1, 0, speed);
1176 if (result==-5 || result==-7)
1178 new Ending(result==-7);
1179 new Fader(1, 0, speed);
1185 void Ending::Cancel()
1187 new Fader(-1, -6, 0.3);
1188 // Pop();
1191 void ToggleFullscreen();
1193 void OptMenu::Select()
1195 if (select<0 || select>=num_opt)
1196 return;
1197 switch(opt[select])
1199 case OPT_RESUME:
1200 Cancel();
1201 break;
1203 case OPT_RESTART:
1204 Cancel();
1205 HackKeyPress('r', KMOD_CTRL);
1206 break;
1208 case OPT_GOTO_MAP:
1209 case OPT_GOTO_MAP_CONTINUE:
1210 Pop();
1211 HackKeyPress(SDLK_ESCAPE, KMOD_CTRL);
1212 break;
1214 case OPT_QUIT:
1215 new QuitConfirmMenu();
1216 break;
1218 case OPT_FULLSCREEN:
1219 ToggleFullscreen();
1220 break;
1222 case OPT_QUIT_CONFIRM:
1223 new Fader(-1, -2);
1224 break;
1226 case OPT_QUIT_MENU_CONFIRM:
1227 Pop();
1228 new Fader(-1, -1);
1229 break;
1231 case OPT_OPTIONS:
1232 new OptionMenu(!activeMenu->renderBG);
1233 break;
1235 case OPT_HELP:
1236 new HintReview();
1237 break;
1239 case OPT_QUIT_CANCEL:
1240 case OPT_BACK:
1241 Pop();
1242 break;
1244 case OPT_END:
1245 new Fader(-1, -5, 0.3);
1246 break;
1248 case OPT_END2:
1249 new Fader(-1, -7, 0.3);
1250 break;
1252 case OPT_UNDO:
1253 Pop();
1254 HackKeyPress('z', 0);
1255 break;
1257 default:
1258 if (opt[select]>=OPT_GAMESLOT_0 && opt[select]<=OPT_GAMESLOT_LAST
1259 || opt[select]==OPT_GAMESLOT_NEW && freeSlot>=0)
1261 if (opt[select]==OPT_GAMESLOT_NEW)
1262 GetSlotName(freeSlot, currentSlot);
1263 else
1264 GetSlotName(opt[select]-OPT_GAMESLOT_0, currentSlot);
1265 new Fader(-1, -4);
1267 break;