]> git.jsancho.org Git - lugaru.git/blob - Source/Menu/Menu.cpp
Rename all C++ headers with .hpp extension
[lugaru.git] / Source / Menu / Menu.cpp
1 /*
2 Copyright (C) 2003, 2010 - Wolfire Games
3 Copyright (C) 2010-2016 - Lugaru contributors (see AUTHORS file)
4
5 This file is part of Lugaru.
6
7 Lugaru is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2 of the License, or
10 (at your option) any later version.
11
12 Lugaru is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with Lugaru.  If not, see <http://www.gnu.org/licenses/>.
19 */
20
21 #include "Menu/Menu.hpp"
22
23 #include "Audio/openal_wrapper.hpp"
24 #include "Graphic/gamegl.hpp"
25 #include "Level/Campaign.hpp"
26 #include "User/Settings.hpp"
27 #include "Utils/Input.hpp"
28
29 // Should not be needed, Menu should call methods from other classes to launch maps and challenges and so on
30 #include "Level/Awards.hpp"
31
32 #include <set>
33 #include <string>
34 #include <vector>
35
36 using namespace Game;
37
38 extern float multiplier;
39 extern std::set<std::pair<int,int>> resolutions;
40 extern int mainmenu;
41 extern std::vector<CampaignLevel> campaignlevels;
42 extern float musicvolume[4];
43 extern float oldmusicvolume[4];
44 extern bool stillloading;
45 extern bool visibleloading;
46 extern int whichchoice;
47 extern int leveltheme;
48
49 extern void toggleFullscreen();
50
51 int entername = 0;
52
53 std::vector<MenuItem> Menu::items;
54
55 MenuItem::MenuItem(MenuItemType _type, int _id, const string& _text, Texture _texture,
56           int _x, int _y, int _w, int _h, float _r, float _g, float _b,
57           float _linestartsize, float _lineendsize):
58     type(_type),
59     id(_id),
60     text(_text),
61     texture(_texture),
62     x(_x),
63     y(_y),
64     w(_w),
65     h(_h),
66     r(_r),
67     g(_g),
68     b(_b),
69     effectfade(0),
70     linestartsize(_linestartsize),
71     lineendsize(_lineendsize)
72 {
73     if (type == MenuItem::BUTTON) {
74         if (w == -1) {
75             w = text.length() * 10;
76         }
77         if (h == -1) {
78             h = 20;
79         }
80     }
81 }
82
83 void Menu::clearMenu()
84 {
85     items.clear();
86 }
87
88 void Menu::addLabel(int id, const string& text, int x, int y, float r, float g, float b)
89 {
90     items.emplace_back(MenuItem::LABEL, id, text, Texture(), x, y, -1, -1, r, g, b);
91 }
92 void Menu::addButton(int id, const string& text, int x, int y, float r, float g, float b)
93 {
94     items.emplace_back(MenuItem::BUTTON, id, text, Texture(), x, y, -1, -1, r, g, b);
95 }
96 void Menu::addImage(int id, Texture texture, int x, int y, int w, int h, float r, float g, float b)
97 {
98     items.emplace_back(MenuItem::IMAGE, id, "", texture, x, y, w, h, r, g, b);
99 }
100 void Menu::addButtonImage(int id, Texture texture, int x, int y, int w, int h, float r, float g, float b)
101 {
102     items.emplace_back(MenuItem::IMAGEBUTTON, id, "", texture, x, y, w, h, r, g, b);
103 }
104 void Menu::addMapLine(int x, int y, int w, int h, float startsize, float endsize, float r, float g, float b)
105 {
106     items.emplace_back(MenuItem::MAPLINE, -1, "", Texture(), x, y, w, h, r, g, b, startsize, endsize);
107 }
108 void Menu::addMapMarker(int id, Texture texture, int x, int y, int w, int h, float r, float g, float b)
109 {
110     items.emplace_back(MenuItem::MAPMARKER, id, "", texture, x, y, w, h, r, g, b);
111 }
112 void Menu::addMapLabel(int id, const string& text, int x, int y, float r, float g, float b)
113 {
114     items.emplace_back(MenuItem::MAPLABEL, id, text, Texture(), x, y, -1, -1, r, g, b);
115 }
116
117 void Menu::setText(int id, const string& text)
118 {
119     for (vector<MenuItem>::iterator it = items.begin(); it != items.end(); it++)
120         if (it->id == id) {
121             it->text = text;
122             it->w = it->text.length() * 10;
123             break;
124         }
125 }
126
127 void Menu::setText(int id, const string& text, int x, int y, int w, int h)
128 {
129     for (vector<MenuItem>::iterator it = items.begin(); it != items.end(); it++)
130         if (it->id == id) {
131             it->text = text;
132             it->x = x;
133             it->y = y;
134             if (w == -1)
135                 it->w = it->text.length() * 10;
136             if (h == -1)
137                 it->h = 20;
138             break;
139         }
140 }
141
142 int Menu::getSelected(int mousex, int mousey)
143 {
144     for (vector<MenuItem>::iterator it = items.begin(); it != items.end(); it++)
145         if (it->type == MenuItem::BUTTON || it->type == MenuItem::IMAGEBUTTON || it->type == MenuItem::MAPMARKER) {
146             int mx = mousex;
147             int my = mousey;
148             if (it->type == MenuItem::MAPMARKER) {
149                 mx -= 1;
150                 my += 2;
151             }
152             if (mx >= it->x && mx < it->x + it->w && my >= it->y && my < it->y + it->h)
153                 return it->id;
154         }
155     return -1;
156 }
157
158 void Menu::handleFadeEffect()
159 {
160     for (vector<MenuItem>::iterator it = items.begin(); it != items.end(); it++) {
161         if (it->id == Game::selected) {
162             it->effectfade += multiplier * 5;
163             if (it->effectfade > 1)
164                 it->effectfade = 1;
165         } else {
166             it->effectfade -= multiplier * 5;
167             if (it->effectfade < 0)
168                 it->effectfade = 0;
169         }
170     }
171 }
172
173 void Menu::drawItems()
174 {
175     handleFadeEffect();
176     glEnable(GL_TEXTURE_2D);
177     glEnable(GL_ALPHA_TEST);
178     glEnable(GL_BLEND);
179     for (vector<MenuItem>::iterator it = items.begin(); it != items.end(); it++) {
180         switch (it->type) {
181         case MenuItem::IMAGE:
182         case MenuItem::IMAGEBUTTON:
183         case MenuItem::MAPMARKER:
184             glColor4f(it->r, it->g, it->b, 1);
185             glPushMatrix();
186             if (it->type == MenuItem::MAPMARKER) {
187                 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
188                 glTranslatef(2.5, -4.5, 0); //from old code
189             } else {
190                 glBlendFunc(GL_SRC_ALPHA, GL_ONE);
191             }
192             it->texture.bind();
193             glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
194             glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
195             glBegin(GL_QUADS);
196             glTexCoord2f(0, 0);
197             glVertex3f(it->x, it->y, 0);
198             glTexCoord2f(1, 0);
199             glVertex3f(it->x + it->w, it->y, 0);
200             glTexCoord2f(1, 1);
201             glVertex3f(it->x + it->w, it->y + it->h, 0);
202             glTexCoord2f(0, 1);
203             glVertex3f(it->x, it->y + it->h, 0);
204             glEnd();
205             if (it->type != MenuItem::IMAGE) {
206                 //mouseover highlight
207                 for (int i = 0; i < 10; i++) {
208                     if (1 - ((float)i) / 10 - (1 - it->effectfade) > 0) {
209                         glColor4f(it->r, it->g, it->b, (1 - ((float)i) / 10 - (1 - it->effectfade))*.25);
210                         glBegin(GL_QUADS);
211                         glTexCoord2f(0, 0);
212                         glVertex3f(it->x - ((float)i) * 1 / 2, it->y - ((float)i) * 1 / 2, 0);
213                         glTexCoord2f(1, 0);
214                         glVertex3f(it->x + it->w + ((float)i) * 1 / 2, it->y - ((float)i) * 1 / 2, 0);
215                         glTexCoord2f(1, 1);
216                         glVertex3f(it->x + it->w + ((float)i) * 1 / 2, it->y + it->h + ((float)i) * 1 / 2, 0);
217                         glTexCoord2f(0, 1);
218                         glVertex3f(it->x - ((float)i) * 1 / 2, it->y + it->h + ((float)i) * 1 / 2, 0);
219                         glEnd();
220                     }
221                 }
222             }
223             glPopMatrix();
224             break;
225         case MenuItem::LABEL:
226         case MenuItem::BUTTON:
227             glColor4f(it->r, it->g, it->b, 1);
228             glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
229             Game::text->glPrint(it->x, it->y, it->text.c_str(), 0, 1, 640, 480);
230             if (it->type != MenuItem::LABEL) {
231                 //mouseover highlight
232                 glBlendFunc(GL_SRC_ALPHA, GL_ONE);
233                 for (int i = 0; i < 15; i++) {
234                     if (1 - ((float)i) / 15 - (1 - it->effectfade) > 0) {
235                         glColor4f(it->r, it->g, it->b, (1 - ((float)i) / 10 - (1 - it->effectfade))*.25);
236                         Game::text->glPrint(it->x - ((float)i), it->y, it->text.c_str(), 0, 1 + ((float)i) / 70, 640, 480);
237                     }
238                 }
239             }
240             break;
241         case MenuItem::MAPLABEL:
242             Game::text->glPrintOutlined(0.9, 0, 0, it->x, it->y, it->text.c_str(), 0, 0.6, 640, 480);
243             break;
244         case MenuItem::MAPLINE: {
245             XYZ linestart;
246             linestart.x = it->x;
247             linestart.y = it->y;
248             linestart.z = 0;
249             XYZ lineend;
250             lineend.x = it->x + it->w;
251             lineend.y = it->y + it->h;
252             lineend.z = 0;
253             XYZ offset = lineend - linestart;
254             XYZ fac = offset;
255             Normalise(&fac);
256             offset = DoRotation(offset, 0, 0, 90);
257             Normalise(&offset);
258
259             linestart += fac * 4 * it->linestartsize;
260             lineend -= fac * 4 * it->lineendsize;
261
262             glDisable(GL_TEXTURE_2D);
263             glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
264             glColor4f(it->r, it->g, it->b, 1);
265             glPushMatrix();
266             glTranslatef(2, -5, 0); //from old code
267             glBegin(GL_QUADS);
268             glVertex3f(linestart.x - offset.x * it->linestartsize, linestart.y - offset.y * it->linestartsize, 0.0f);
269             glVertex3f(linestart.x + offset.x * it->linestartsize, linestart.y + offset.y * it->linestartsize, 0.0f);
270             glVertex3f(lineend.x + offset.x * it->lineendsize, lineend.y + offset.y * it->lineendsize, 0.0f);
271             glVertex3f(lineend.x - offset.x * it->lineendsize, lineend.y - offset.y * it->lineendsize, 0.0f);
272             glEnd();
273             glPopMatrix();
274             glEnable(GL_TEXTURE_2D);
275         }
276         break;
277         default:
278         case MenuItem::NONE:
279         break;
280         }
281     }
282 }
283
284 void Menu::updateSettingsMenu()
285 {
286     std::string sbuf = std::string("Resolution: ") + to_string(newscreenwidth) + "*" + to_string(newscreenheight);
287     if (((float)newscreenwidth <= (float)newscreenheight * 1.61) && ((float)newscreenwidth >= (float)newscreenheight * 1.59)) {
288         sbuf += " (widescreen)";
289     }
290     setText(0, sbuf);
291     setText(14, fullscreen ? "Fullscreen: On" : "Fullscreen: Off");
292     if (newdetail == 0) setText(1, "Detail: Low");
293     if (newdetail == 1) setText(1, "Detail: Medium");
294     if (newdetail == 2) setText(1, "Detail: High");
295     if (bloodtoggle == 0) setText(2, "Blood: Off");
296     if (bloodtoggle == 1) setText(2, "Blood: On, low detail");
297     if (bloodtoggle == 2) setText(2, "Blood: On, high detail (slower)");
298     setText(4, ismotionblur ? "Blur Effects: Enabled (less compatible)" : "Blur Effects: Disabled (more compatible)");
299     setText(5, decals ? "Decals: Enabled (slower)" : "Decals: Disabled");
300     setText(6, musictoggle ? "Music: Enabled" : "Music: Disabled");
301     setText(9, invertmouse ? "Invert mouse: Yes" : "Invert mouse: No");
302     setText(10, std::string("Mouse Speed: ") + to_string(int(usermousesensitivity * 5)));
303     setText(11, std::string("Volume: ") + to_string(int(volume * 100)) + "%");
304     setText(13, showdamagebar ? "Damage Bar: On" : "Damage Bar: Off");
305     if ((newdetail == detail) && (newscreenheight == (int)screenheight) && (newscreenwidth == (int)screenwidth)) {
306         setText(8, "Back");
307     } else {
308         setText(8, "Back (some changes take effect next time Lugaru is opened)");
309     }
310 }
311
312 void Menu::updateStereoConfigMenu()
313 {
314     setText(0, std::string("Stereo mode: ") + StereoModeName(newstereomode));
315     setText(1, std::string("Stereo separation: ") + to_string(stereoseparation));
316     setText(2, std::string("Reverse stereo: ") + (stereoreverse ? "Yes" : "No"));
317 }
318
319 void Menu::updateControlsMenu()
320 {
321     setText(0, (string)"Forwards: " + (keyselect == 0 ? "_" : Input::keyToChar(forwardkey)));
322     setText(1, (string)"Back: "    + (keyselect == 1 ? "_" : Input::keyToChar(backkey)));
323     setText(2, (string)"Left: "    + (keyselect == 2 ? "_" : Input::keyToChar(leftkey)));
324     setText(3, (string)"Right: "   + (keyselect == 3 ? "_" : Input::keyToChar(rightkey)));
325     setText(4, (string)"Crouch: "  + (keyselect == 4 ? "_" : Input::keyToChar(crouchkey)));
326     setText(5, (string)"Jump: "    + (keyselect == 5 ? "_" : Input::keyToChar(jumpkey)));
327     setText(6, (string)"Draw: "    + (keyselect == 6 ? "_" : Input::keyToChar(drawkey)));
328     setText(7, (string)"Throw: "   + (keyselect == 7 ? "_" : Input::keyToChar(throwkey)));
329     setText(8, (string)"Attack: "  + (keyselect == 8 ? "_" : Input::keyToChar(attackkey)));
330     if (devtools) {
331         setText(9, (string)"Console: " + (keyselect == 9 ? "_" : Input::keyToChar(consolekey)));
332     }
333 }
334
335 /*
336 Values of mainmenu :
337 1 Main menu
338 2 Menu pause (resume/end game)
339 3 Option menu
340 4 Controls configuration menu
341 5 Main game menu (choose level or challenge)
342 6 Deleting user menu
343 7 User managment menu (select/add)
344 8 Choose difficulty menu
345 9 Challenge level selection menu
346 10 End of the campaign congratulation (is that really a menu?)
347 11 Same that 9 ??? => unused
348 18 stereo configuration
349 */
350
351 void Menu::Load()
352 {
353     clearMenu();
354     switch (mainmenu) {
355     case 1:
356     case 2:
357         addImage(0, Mainmenuitems[0], 150, 480 - 128, 256, 128);
358         addButtonImage(1, Mainmenuitems[mainmenu == 1 ? 1 : 5], 18, 480 - 152 - 32, 128, 32);
359         addButtonImage(2, Mainmenuitems[2], 18, 480 - 228 - 32, 112, 32);
360         addButtonImage(3, Mainmenuitems[mainmenu == 1 ? 3 : 6], 18, 480 - 306 - 32, mainmenu == 1 ? 68 : 132, 32);
361         break;
362     case 3:
363         addButton( 0, "", 10 + 20, 440);
364         addButton(14, "", 10 + 400, 440);
365         addButton( 1, "", 10 + 60, 405);
366         addButton( 2, "", 10 + 70, 370);
367         addButton( 4, "", 10   , 335);
368         addButton( 5, "", 10 + 60, 300);
369         addButton( 6, "", 10 + 70, 265);
370         addButton( 9, "", 10   , 230);
371         addButton(10, "", 20   , 195);
372         addButton(11, "", 10 + 60, 160);
373         addButton(13, "", 30   , 125);
374         addButton( 7, "-Configure Controls-", 10 + 15, 90);
375         addButton(12, "-Configure Stereo -", 10 + 15, 55);
376         addButton(8, "Back", 10, 10);
377         updateSettingsMenu();
378         break;
379     case 4:
380         addButton(0, "", 10   , 400);
381         addButton(1, "", 10 + 40, 360);
382         addButton(2, "", 10 + 40, 320);
383         addButton(3, "", 10 + 30, 280);
384         addButton(4, "", 10 + 20, 240);
385         addButton(5, "", 10 + 40, 200);
386         addButton(6, "", 10 + 40, 160);
387         addButton(7, "", 10 + 30, 120);
388         addButton(8, "", 10 + 20, 80);
389         if (devtools) {
390             addButton(9, "", 10 + 10, 40);
391         }
392         addButton(devtools ? 10 : 9, "Back", 10, 10);
393         updateControlsMenu();
394         break;
395     case 5: {
396         LoadCampaign();
397         addLabel(-1, Account::active().getName(), 5, 400);
398         addButton(1, "Tutorial", 5, 300);
399         addButton(2, "Challenge", 5, 240);
400         addButton(3, "Delete User", 400, 10);
401         addButton(4, "Main Menu", 5, 10);
402         addButton(5, "Change User", 5, 180);
403         addButton(6, "Campaign : " + Account::active().getCurrentCampaign(), 200, 420);
404
405         //show campaign map
406         //with (2,-5) offset from old code
407         addImage(-1, Mainmenuitems[7], 150 + 2, 60 - 5, 400, 400);
408         //show levels
409         int numlevels = Account::active().getCampaignChoicesMade();
410         numlevels += numlevels > 0 ? campaignlevels[numlevels - 1].nextlevel.size() : 1;
411         for (int i = 0; i < numlevels; i++) {
412             XYZ midpoint = campaignlevels[i].getCenter();
413             float itemsize = campaignlevels[i].getWidth();
414             const bool active = (i >= Account::active().getCampaignChoicesMade());
415             if (!active) {
416                 itemsize /= 2;
417             }
418
419             if (i >= 1) {
420                 XYZ start = campaignlevels[i - 1].getCenter();
421                 addMapLine(start.x, start.y, midpoint.x - start.x, midpoint.y - start.y, 0.5, active ? 1 : 0.5, active ? 1 : 0.5, 0, 0);
422             }
423             addMapMarker(NB_CAMPAIGN_MENU_ITEM + i, Mapcircletexture,
424                                midpoint.x - itemsize / 2, midpoint.y - itemsize / 2, itemsize, itemsize, active ? 1 : 0.5, 0, 0);
425
426             if (active) {
427                 addMapLabel(-2, campaignlevels[i].description,
428                                   campaignlevels[i].getStartX() + 10,
429                                   campaignlevels[i].getStartY() - 4);
430             }
431         }
432     }
433     break;
434     case 6:
435         addLabel(-1, "Are you sure you want to delete this user?", 10, 400);
436         addButton(1, "Yes", 10, 360);
437         addButton(2, "No", 10, 320);
438         break;
439     case 7:
440         if (Account::getNbAccounts() < 8)
441             addButton(0, "New User", 10, 400);
442         else
443             addLabel(0, "No More Users", 10, 400);
444         addLabel(-2, "", 20, 400);
445         addButton(Account::getNbAccounts() + 1, "Back", 10, 10);
446         for (int i = 0; i < Account::getNbAccounts(); i++) {
447             addButton(i + 1, Account::get(i).getName(), 10, 340 - 20 * (i + 1));
448         }
449         break;
450     case 8:
451         addButton(0, "Easier", 10, 400);
452         addButton(1, "Difficult", 10, 360);
453         addButton(2, "Insane", 10, 320);
454         break;
455     case 9:
456         for (int i = 0; i < numchallengelevels; i++) {
457             string name = "Level ";
458             name += to_string(i + 1);
459             if (name.size() < 17) {
460                 name.append((17 - name.size()), ' ');
461             }
462             name += to_string(int(Account::active().getHighScore(i)));
463             if (name.size() < 32) {
464                 name.append((32 - name.size()), ' ');
465             }
466             int fasttime = (int)round(Account::active().getFastTime(i));
467             name += to_string(int((fasttime - fasttime % 60) / 60));
468             name += ":";
469             if (fasttime % 60 < 10)
470                 name += "0";
471             name += to_string(fasttime % 60);
472
473             addButton(i, name, 10, 400 - i * 25, i > Account::active().getProgress() ? 0.5 : 1, 0, 0);
474         }
475
476         addButton(-1, "             High Score      Best Time", 10, 440);
477         addButton(numchallengelevels, "Back", 10, 10);
478         break;
479     case 10: {
480         addLabel(0, "Congratulations!", 220, 330);
481         addLabel(1, "You have avenged your family and", 140, 300);
482         addLabel(2, "restored peace to the island of Lugaru.", 110, 270);
483         addButton(3, "Back", 10, 10);
484         addLabel(4, string("Your score:         ") + to_string((int)Account::active().getCampaignScore()), 190, 200);
485         addLabel(5, string("Highest score:      ") + to_string((int)Account::active().getCampaignHighScore()), 190, 180);
486     }
487     break;
488     case 18:
489         addButton(0, "", 70, 400);
490         addButton(1, "", 10, 360);
491         addButton(2, "", 40, 320);
492         addButton(3, "Back", 10, 10);
493         updateStereoConfigMenu();
494         break;
495     }
496 }
497
498 void Menu::Tick()
499 {
500     //escape key pressed
501     if (Input::isKeyPressed(SDL_SCANCODE_ESCAPE) &&
502         (mainmenu >= 3) && (mainmenu != 8) && !((mainmenu == 7) && entername)) {
503         selected = -1;
504         //finished with settings menu
505         if (mainmenu == 3) {
506             SaveSettings();
507         }
508         //effects
509         if (mainmenu >= 3 && mainmenu != 8) {
510             fireSound();
511             flash();
512         }
513         //go back
514         switch (mainmenu) {
515         case 3:
516         case 5:
517             mainmenu = gameon ? 2 : 1;
518             break;
519         case 4:
520         case 18:
521             mainmenu = 3;
522             break;
523         case 6:
524         case 7:
525         case 9:
526         case 10:
527             mainmenu = 5;
528             break;
529         }
530     }
531
532     //menu buttons
533     selected = getSelected(mousecoordh * 640 / screenwidth, 480 - mousecoordv * 480 / screenheight);
534
535     // some specific case where we do something even if the left mouse button is not pressed.
536     if ((mainmenu == 5) && (endgame == 2)) {
537         Account::active().endGame();
538         endgame = 0;
539     }
540     if (mainmenu == 10)
541         endgame = 2;
542     if (mainmenu == 18 && Input::isKeyPressed(MOUSEBUTTON2) && selected == 1) {
543         stereoseparation -= 0.001;
544         updateStereoConfigMenu();
545     }
546
547     static int oldmainmenu = mainmenu;
548
549     if (Input::MouseClicked() && (selected >= 0)) { // handling of the left mouse clic in menus
550         set<pair<int,int>>::iterator newscreenresolution;
551         switch (mainmenu) {
552         case 1:
553         case 2:
554             switch (selected) {
555             case 1:
556                 if (gameon) { //resume
557                     mainmenu = 0;
558                     pause_sound(stream_menutheme);
559                     resume_stream(leveltheme);
560                 } else { //new game
561                     fireSound(firestartsound);
562                     flash();
563                     mainmenu = (Account::hasActive() ? 5 : 7);
564                     selected = -1;
565                 }
566                 break;
567             case 2: //options
568                 fireSound();
569                 flash();
570                 mainmenu = 3;
571                 if (newdetail > 2)
572                     newdetail = detail;
573                 if (newdetail < 0)
574                     newdetail = detail;
575                 if (newscreenwidth > 3000)
576                     newscreenwidth = screenwidth;
577                 if (newscreenwidth < 0)
578                     newscreenwidth = screenwidth;
579                 if (newscreenheight > 3000)
580                     newscreenheight = screenheight;
581                 if (newscreenheight < 0)
582                     newscreenheight = screenheight;
583                 break;
584             case 3:
585                 fireSound();
586                 flash();
587                 if (gameon) { //end game
588                     gameon = 0;
589                     mainmenu = 1;
590                 } else { //quit
591                     tryquit = 1;
592                     pause_sound(stream_menutheme);
593                 }
594                 break;
595             }
596             break;
597         case 3:
598             fireSound();
599             switch (selected) {
600             case 0:
601                 newscreenresolution = resolutions.find(make_pair(newscreenwidth, newscreenheight));
602                 /* Next one (end() + 1 is also end() so the ++ is safe even if it was not found) */
603                 newscreenresolution++;
604                 if (newscreenresolution == resolutions.end()) {
605                     /* It was the last one (or not found), go back to the beginning */
606                     newscreenresolution = resolutions.begin();
607                 }
608                 newscreenwidth  = newscreenresolution->first;
609                 newscreenheight = newscreenresolution->second;
610                 break;
611             case 1:
612                 newdetail++;
613                 if (newdetail > 2)
614                     newdetail = 0;
615                 break;
616             case 2:
617                 bloodtoggle++;
618                 if (bloodtoggle > 2)
619                     bloodtoggle = 0;
620                 break;
621             case 4:
622                 ismotionblur = !ismotionblur;
623                 break;
624             case 5:
625                 decals = !decals;
626                 break;
627             case 6:
628                 musictoggle = !musictoggle;
629                 if (musictoggle) {
630                     emit_stream_np(stream_menutheme);
631                 } else {
632                     pause_sound(leveltheme);
633                     pause_sound(stream_fighttheme);
634                     pause_sound(stream_menutheme);
635
636                     for (int i = 0; i < 4; i++) {
637                         oldmusicvolume[i] = 0;
638                         musicvolume[i] = 0;
639                     }
640                 }
641                 break;
642             case 7: // controls
643                 flash();
644                 mainmenu = 4;
645                 selected = -1;
646                 keyselect = -1;
647                 break;
648             case 8:
649                 flash();
650                 SaveSettings();
651                 mainmenu = gameon ? 2 : 1;
652                 break;
653             case 9:
654                 invertmouse = !invertmouse;
655                 break;
656             case 10:
657                 usermousesensitivity += .2;
658                 if (usermousesensitivity > 2)
659                     usermousesensitivity = .2;
660                 break;
661             case 11:
662                 volume += .1f;
663                 if (volume > 1.0001f)
664                     volume = 0;
665                 OPENAL_SetSFXMasterVolume((int)(volume * 255));
666                 break;
667             case 12:
668                 flash();
669                 newstereomode = stereomode;
670                 mainmenu = 18;
671                 keyselect = -1;
672                 break;
673             case 13:
674                 showdamagebar = !showdamagebar;
675                 break;
676             case 14:
677                 toggleFullscreen();
678                 break;
679             }
680             updateSettingsMenu();
681             break;
682         case 4:
683             if (!waiting) {
684                 fireSound();
685                 if (selected < (devtools ? 10 : 9) && keyselect == -1)
686                     keyselect = selected;
687                 if (keyselect != -1)
688                     setKeySelected();
689                 if (selected == (devtools ? 10 : 9)) {
690                     flash();
691                     mainmenu = 3;
692                 }
693             }
694             updateControlsMenu();
695             break;
696         case 5:
697             fireSound();
698             flash();
699             if ((selected - NB_CAMPAIGN_MENU_ITEM >= Account::active().getCampaignChoicesMade())) {
700                 startbonustotal = 0;
701
702                 loading = 2;
703                 loadtime = 0;
704                 targetlevel = 7;
705                 if (firstload)
706                     TickOnceAfter();
707                 else
708                     LoadStuff();
709                 whichchoice = selected - NB_CAMPAIGN_MENU_ITEM - Account::active().getCampaignChoicesMade();
710                 actuallevel = (Account::active().getCampaignChoicesMade() > 0 ? campaignlevels[Account::active().getCampaignChoicesMade() - 1].nextlevel[whichchoice] : 0);
711                 visibleloading = 1;
712                 stillloading = 1;
713                 Loadlevel(campaignlevels[actuallevel].mapname.c_str());
714                 campaign = 1;
715                 mainmenu = 0;
716                 gameon = 1;
717                 pause_sound(stream_menutheme);
718             }
719             switch (selected) {
720             case 1:
721                 startbonustotal = 0;
722
723                 loading = 2;
724                 loadtime = 0;
725                 targetlevel = -1;
726                 if (firstload) {
727                     TickOnceAfter();
728                 } else
729                     LoadStuff();
730                 Loadlevel(-1);
731
732                 mainmenu = 0;
733                 gameon = 1;
734                 pause_sound(stream_menutheme);
735                 break;
736             case 2:
737                 mainmenu = 9;
738                 break;
739             case 3:
740                 mainmenu = 6;
741                 break;
742             case 4:
743                 mainmenu = (gameon ? 2 : 1);
744                 break;
745             case 5:
746                 mainmenu = 7;
747                 break;
748             case 6:
749                 vector<string> campaigns = ListCampaigns();
750                 vector<string>::iterator c;
751                 if ((c = find(campaigns.begin(), campaigns.end(), Account::active().getCurrentCampaign())) == campaigns.end()) {
752                     if (!campaigns.empty())
753                         Account::active().setCurrentCampaign(campaigns.front());
754                 } else {
755                     c++;
756                     if (c == campaigns.end())
757                         c = campaigns.begin();
758                     Account::active().setCurrentCampaign(*c);
759                 }
760                 Load();
761                 break;
762             }
763             break;
764         case 6:
765             fireSound();
766             if (selected == 1) {
767                 flash();
768                 Account::destroyActive();
769                 mainmenu = 7;
770             } else if (selected == 2) {
771                 flash();
772                 mainmenu = 5;
773             }
774             break;
775         case 7:
776             fireSound();
777             if (selected == 0 && Account::getNbAccounts() < 8) {
778                 entername = 1;
779             } else if (selected < Account::getNbAccounts() + 1) {
780                 flash();
781                 mainmenu = 5;
782                 Account::setActive(selected - 1);
783             } else if (selected == Account::getNbAccounts() + 1) {
784                 flash();
785                 if (Account::hasActive()) {
786                     mainmenu = 5;
787                 } else {
788                     mainmenu = 1;
789                 }
790                 displaytext[0].clear();
791                 displayselected = 0;
792                 entername = 0;
793             }
794             break;
795         case 8:
796             fireSound();
797             flash();
798             if (selected <= 2)
799                 Account::active().setDifficulty(selected);
800             mainmenu = 5;
801             break;
802         case 9:
803             if (selected < numchallengelevels && selected <= Account::active().getProgress()) {
804                 fireSound();
805                 flash();
806
807                 startbonustotal = 0;
808
809                 loading = 2;
810                 loadtime = 0;
811                 targetlevel = selected;
812                 if (firstload)
813                     TickOnceAfter();
814                 else
815                     LoadStuff();
816                 Loadlevel(selected);
817                 campaign = 0;
818
819                 mainmenu = 0;
820                 gameon = 1;
821                 pause_sound(stream_menutheme);
822             }
823             if (selected == numchallengelevels) {
824                 fireSound();
825                 flash();
826                 mainmenu = 5;
827             }
828             break;
829         case 10:
830             if (selected == 3) {
831                 fireSound();
832                 flash();
833                 mainmenu = 5;
834             }
835             break;
836         case 18:
837             if (selected == 1)
838                 stereoseparation += 0.001;
839             else {
840                 fireSound();
841                 if (selected == 0) {
842                     newstereomode = (StereoMode)(newstereomode + 1);
843                     while (!CanInitStereo(newstereomode)) {
844                         printf("Failed to initialize mode %s (%i)\n", StereoModeName(newstereomode).c_str(), newstereomode);
845                         newstereomode = (StereoMode)(newstereomode + 1);
846                         if (newstereomode >= stereoCount)
847                             newstereomode = stereoNone;
848                     }
849                 } else if (selected == 2) {
850                     stereoreverse = !stereoreverse;
851                 } else if (selected == 3) {
852                     flash();
853                     mainmenu = 3;
854
855                     stereomode = newstereomode;
856                     InitStereo(stereomode);
857                 }
858             }
859             updateStereoConfigMenu();
860             break;
861         }
862     }
863
864     OPENAL_SetFrequency(channels[stream_menutheme]);
865
866     if (entername) {
867         inputText(displaytext[0], &displayselected);
868         if (!waiting) { // the input as finished
869             if (!displaytext[0].empty()) { // with enter
870                 Account::add(string(displaytext[0]));
871
872                 mainmenu = 8;
873
874                 flash();
875
876                 fireSound(firestartsound);
877
878                 displaytext[0].clear();
879
880                 displayselected = 0;
881             }
882             entername = 0;
883             Load();
884         }
885
886         displayblinkdelay -= multiplier;
887         if (displayblinkdelay <= 0) {
888             displayblinkdelay = .3;
889             displayblink = !displayblink;
890         }
891     }
892
893     if (entername) {
894         setText(0, displaytext[0], 20, 400, -1, -1);
895         setText(-2, displayblink ? "_" : "", 20 + displayselected * 10, 400, -1, -1);
896     }
897
898     if (oldmainmenu != mainmenu)
899         Load();
900     oldmainmenu = mainmenu;
901
902 }
903
904 int setKeySelected_thread(void* data)
905 {
906     using namespace Game;
907     int scancode = -1;
908     SDL_Event evenement;
909     while (scancode == -1) {
910         SDL_WaitEvent(&evenement);
911         switch (evenement.type) {
912         case SDL_KEYDOWN:
913             scancode = evenement.key.keysym.scancode;
914             break;
915         case SDL_MOUSEBUTTONDOWN:
916             scancode = SDL_NUM_SCANCODES + evenement.button.button;
917             break;
918         default:
919             break;
920         }
921     }
922     if (scancode != SDL_SCANCODE_ESCAPE) {
923         fireSound();
924         switch (keyselect) {
925         case 0:
926             forwardkey = scancode;
927             break;
928         case 1:
929             backkey = scancode;
930             break;
931         case 2:
932             leftkey = scancode;
933             break;
934         case 3:
935             rightkey = scancode;
936             break;
937         case 4:
938             crouchkey = scancode;
939             break;
940         case 5:
941             jumpkey = scancode;
942             break;
943         case 6:
944             drawkey = scancode;
945             break;
946         case 7:
947             throwkey = scancode;
948             break;
949         case 8:
950             attackkey = scancode;
951             break;
952         case 9:
953             consolekey = scancode;
954             break;
955         default:
956             break;
957         }
958     }
959     keyselect = -1;
960     waiting = false;
961     Menu::Load();
962     return 0;
963 }
964
965 void Menu::setKeySelected()
966 {
967     waiting = true;
968     printf("launch thread\n");
969     SDL_Thread* thread = SDL_CreateThread(setKeySelected_thread, NULL, NULL);
970     if ( thread == NULL ) {
971         fprintf(stderr, "Unable to create thread: %s\n", SDL_GetError());
972         waiting = false;
973         return;
974     }
975 }