1 /*
2 * Copyright (C) 2011-2013 Me and My Shadow
3 *
4 * This file is part of Me and My Shadow.
5 *
6 * Me and My Shadow is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * Me and My Shadow is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with Me and My Shadow. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 #include "LevelPlaySelect.h"
21 #include "GameState.h"
22 #include "Functions.h"
23 #include "FileManager.h"
24 #include "Globals.h"
25 #include "LevelSelect.h"
26 #include "GUIObject.h"
27 #include "GUIListBox.h"
28 #include "GUIScrollBar.h"
29 #include "InputManager.h"
30 #include "ThemeManager.h"
31 #include "SoundManager.h"
32 #include "StatisticsManager.h"
33 #include "Game.h"
34 #include <stdio.h>
35 #include <string>
36 #include <sstream>
37 #include <iostream>
38
39 #include "libs/tinyformat/tinyformat.h"
40
41 /////////////////////LEVEL SELECT/////////////////////
LevelPlaySelect(ImageManager & imageManager,SDL_Renderer & renderer)42 LevelPlaySelect::LevelPlaySelect(ImageManager& imageManager, SDL_Renderer& renderer)
43 :LevelSelect(imageManager,renderer,_("Select Level")),
44 levelInfoRender(imageManager,renderer,getDataPath(),*fontText,objThemes.getTextColor(false)){
45 //Load the play button if needed.
46 playButtonImage=imageManager.loadTexture(getDataPath()+"gfx/playbutton.png", renderer);
47
48 //Create the gui.
49 createGUI(imageManager,renderer, true);
50
51 //Show level list
52 refresh(imageManager,renderer);
53 }
54
~LevelPlaySelect()55 LevelPlaySelect::~LevelPlaySelect(){
56 play=NULL;
57
58 //Clear the selected level.
59 if(selectedNumber!=NULL){
60 delete selectedNumber;
61 selectedNumber=NULL;
62 }
63 }
64
createGUI(ImageManager & imageManager,SDL_Renderer & renderer,bool initial)65 void LevelPlaySelect::createGUI(ImageManager& imageManager,SDL_Renderer &renderer, bool initial){
66 //Create the play button.
67 if(initial){
68 play=new GUIButton(imageManager,renderer,SCREEN_WIDTH-240,SCREEN_HEIGHT-60,240,32,_("Play"));
69 }else{
70 play->left=SCREEN_WIDTH-240;
71 play->top=SCREEN_HEIGHT-60;
72 }
73 play->name="cmdPlay";
74 play->eventCallback=this;
75 play->enabled=false;
76 if(initial)
77 GUIObjectRoot->addChild(play);
78 }
79
refresh(ImageManager & imageManager,SDL_Renderer & renderer,bool)80 void LevelPlaySelect::refresh(ImageManager& imageManager, SDL_Renderer& renderer, bool /*change*/){
81 const int m=levels->getLevelCount();
82 numbers.clear();
83 levelInfoRender.resetText(renderer, *fontText, objThemes.getTextColor(false));
84
85 //Create the non selected number.
86 if (selectedNumber == NULL){
87 selectedNumber = new Number(imageManager, renderer);
88 }
89 SDL_Rect box={40,SCREEN_HEIGHT-130,50,50};
90 selectedNumber->init(renderer," ",box);
91 selectedNumber->setLocked(true);
92 selectedNumber->setMedal(0);
93
94 bestTimeFilePath.clear();
95 bestRecordingFilePath.clear();
96
97 //Disable the play button.
98 play->enabled=false;
99
100 for(int n=0; n<m; n++){
101 numbers.emplace_back(imageManager, renderer);
102 }
103
104 for(int n=0; n<m; n++){
105 SDL_Rect box={(n%LEVELS_PER_ROW)*64+static_cast<int>(SCREEN_WIDTH*0.2)/2,(n/LEVELS_PER_ROW)*64+184,0,0};
106 numbers[n].init(renderer,n,box);
107 numbers[n].setLocked(n>0 && levels->getLocked(n));
108 int medal=levels->getLevel(n)->won?1:0;
109 if(medal){
110 const int targetTime = levels->getLevel(n)->targetTime;
111 const int targetRecordings = levels->getLevel(n)->targetRecordings;
112 const int time = levels->getLevel(n)->time;
113 const int recordings = levels->getLevel(n)->recordings;
114 if (time >= 0 && (targetTime < 0 || time <= targetTime))
115 medal++;
116 if (recordings >= 0 && (targetRecordings < 0 || recordings <= targetRecordings))
117 medal++;
118 }
119 numbers[n].setMedal(medal);
120 }
121
122 if(m>LEVELS_DISPLAYED_IN_SCREEN){
123 levelScrollBar->maxValue=(m-LEVELS_DISPLAYED_IN_SCREEN+(LEVELS_PER_ROW-1))/LEVELS_PER_ROW;
124 levelScrollBar->visible=true;
125 }else{
126 levelScrollBar->maxValue=0;
127 levelScrollBar->visible=false;
128 }
129 if (levels->levelpackPath == LEVELS_PATH || levels->levelpackPath == CUSTOM_LEVELS_PATH)
130 levelpackDescription->caption = _("Individual levels which are not contained in any level packs");
131 else if (!levels->levelpackDescription.empty())
132 levelpackDescription->caption = _CC(levels->getDictionaryManager(), levels->levelpackDescription);
133 else
134 levelpackDescription->caption = "";
135 }
136
selectNumber(ImageManager & imageManager,SDL_Renderer & renderer,unsigned int number,bool selected)137 void LevelPlaySelect::selectNumber(ImageManager& imageManager, SDL_Renderer& renderer, unsigned int number,bool selected){
138 if (selected) {
139 if (number >= 0 && number < levels->getLevelCount()) {
140 levels->setCurrentLevel(number);
141 setNextState(STATE_GAME);
142 }
143 }else{
144 displayLevelInfo(imageManager, renderer,number);
145 }
146 }
147
checkMouse(ImageManager & imageManager,SDL_Renderer & renderer)148 void LevelPlaySelect::checkMouse(ImageManager &imageManager, SDL_Renderer &renderer){
149 int x,y;
150
151 //Get the current mouse location.
152 SDL_GetMouseState(&x,&y);
153
154 //Check if we should replay the record.
155 if(selectedNumber!=NULL){
156 SDL_Rect mouse={x,y,0,0};
157 if(!bestTimeFilePath.empty()){
158 SDL_Rect box={SCREEN_WIDTH-420,SCREEN_HEIGHT-130,372,32};
159 if(pointOnRect(mouse, box)){
160 Game::recordFile=bestTimeFilePath;
161 levels->setCurrentLevel(selectedNumber->getNumber());
162 setNextState(STATE_GAME);
163 return;
164 }
165 }
166 if(!bestRecordingFilePath.empty()){
167 SDL_Rect box={SCREEN_WIDTH-420,SCREEN_HEIGHT-98,372,32};
168 if(pointOnRect(mouse, box)){
169 Game::recordFile=bestRecordingFilePath;
170 levels->setCurrentLevel(selectedNumber->getNumber());
171 setNextState(STATE_GAME);
172 return;
173 }
174 }
175 }
176
177 //Call the base method from the super class.
178 LevelSelect::checkMouse(imageManager, renderer);
179 }
180
displayLevelInfo(ImageManager & imageManager,SDL_Renderer & renderer,int number)181 void LevelPlaySelect::displayLevelInfo(ImageManager& imageManager, SDL_Renderer& renderer, int number){
182 //Update currently selected level
183 if(selectedNumber==NULL){
184 selectedNumber=new Number(imageManager, renderer);
185 }
186 SDL_Rect box={40,SCREEN_HEIGHT-130,50,50};
187
188 if (number >= 0 && number < levels->getLevelCount()) {
189 selectedNumber->init(renderer, number, box);
190 selectedNumber->setLocked(false);
191
192 //Show level medal
193 int medal = levels->getLevel(number)->won ? 1 : 0;
194 int time = levels->getLevel(number)->time;
195 int targetTime = levels->getLevel(number)->targetTime;
196 int recordings = levels->getLevel(number)->recordings;
197 int targetRecordings = levels->getLevel(number)->targetRecordings;
198
199 if (medal){
200 if (time >= 0 && (targetTime < 0 || time <= targetTime))
201 medal++;
202 if (recordings >= 0 && (targetRecordings < 0 || recordings <= targetRecordings))
203 medal++;
204 }
205 selectedNumber->setMedal(medal);
206 std::string levelTime;
207 std::string levelRecs;
208
209 //Show best time and recordings
210 if (medal){
211 if (time >= 0)
212 if (targetTime >= 0)
213 levelTime = tfm::format("%-.2fs / %-.2fs", time / 40.0, targetTime / 40.0);
214 else
215 levelTime = tfm::format("%-.2fs / -", time / 40.0);
216 else
217 levelTime.clear();
218
219 if (recordings >= 0)
220 if (targetRecordings >= 0)
221 levelRecs = tfm::format("%5d / %d", recordings, targetRecordings);
222 else
223 levelRecs = tfm::format("%5d / -", recordings);
224 else
225 levelRecs.clear();
226 } else{
227 levelTime = "- / -";
228 levelRecs = "- / -";
229 }
230
231 //Show the play button.
232 play->enabled = true;
233
234 //Check if there is auto record file
235 levels->getLevelAutoSaveRecordPath(number, bestTimeFilePath, bestRecordingFilePath, false);
236 if (!bestTimeFilePath.empty()){
237 FILE *f;
238 f = fopen(bestTimeFilePath.c_str(), "rb");
239 if (f == NULL){
240 bestTimeFilePath.clear();
241 } else{
242 fclose(f);
243 }
244 }
245 if (!bestRecordingFilePath.empty()){
246 FILE *f;
247 f = fopen(bestRecordingFilePath.c_str(), "rb");
248 if (f == NULL){
249 bestRecordingFilePath.clear();
250 } else{
251 fclose(f);
252 }
253 }
254
255 //Show level description
256 levelInfoRender.update(renderer, *fontText, objThemes.getTextColor(false),
257 _CC(levels->getDictionaryManager(), levels->getLevelName(number)), levelTime, levelRecs);
258 } else {
259 levelInfoRender.resetText(renderer, *fontText, objThemes.getTextColor(false));
260
261 selectedNumber->init(renderer, " ", box);
262 selectedNumber->setLocked(true);
263 selectedNumber->setMedal(0);
264
265 bestTimeFilePath.clear();
266 bestRecordingFilePath.clear();
267
268 //Disable the play button.
269 play->enabled = false;
270 }
271 }
272
handleEvents(ImageManager & imageManager,SDL_Renderer & renderer)273 void LevelPlaySelect::handleEvents(ImageManager& imageManager, SDL_Renderer& renderer){
274 //Call handleEvents() of base class.
275 LevelSelect::handleEvents(imageManager, renderer);
276
277 //Check if the cheat code is input which is used to skip locked level.
278 //NOTE: The cheat code is NOT in plain text, since we don't want you to find it out immediately.
279 //NOTE: If you type it wrong, please press a key which is NOT a-z before retype it (as the code suggests).
280 if (event.type == SDL_KEYDOWN) {
281 static Uint32 hash = 0;
282 if (event.key.keysym.sym >= SDLK_a && event.key.keysym.sym <= SDLK_z) {
283 Uint32 c = event.key.keysym.sym - SDLK_a + 1;
284 hash = hash * 1296096U + c;
285 if (hash == 498506457U) {
286 if (selectedNumber) {
287 int n = selectedNumber->getNumber();
288 if (n >= 0 && n < (int)numbers.size() - 1 && numbers[n + 1].getLocked()) {
289 //unlock the level temporarily
290 numbers[n + 1].setLocked(false);
291
292 //play a sound effect
293 getSoundManager()->playSound("hit");
294
295 //new achievement
296 statsMgr.newAchievement("cheat");
297 }
298 }
299 hash = 0;
300 }
301 } else {
302 hash = 0;
303 }
304 }
305
306 if (section == 3) {
307 //Check focus movement
308 if (inputMgr.isKeyDownEvent(INPUTMGR_DOWN) || inputMgr.isKeyDownEvent(INPUTMGR_RIGHT)){
309 isKeyboardOnly = true;
310 section2++;
311 } else if (inputMgr.isKeyDownEvent(INPUTMGR_UP) || inputMgr.isKeyDownEvent(INPUTMGR_LEFT)){
312 isKeyboardOnly = true;
313 section2--;
314 }
315 if (section2 > 3) section2 = 1;
316 else if (section2 < 1) section2 = 3;
317
318 //Check if enter is pressed
319 if (isKeyboardOnly && inputMgr.isKeyDownEvent(INPUTMGR_SELECT) && selectedNumber) {
320 int n = selectedNumber->getNumber();
321 if (n >= 0) {
322 switch (section2) {
323 case 1:
324 if (!bestTimeFilePath.empty()) {
325 Game::recordFile = bestTimeFilePath;
326 levels->setCurrentLevel(n);
327 setNextState(STATE_GAME);
328 }
329 break;
330 case 2:
331 if (!bestRecordingFilePath.empty()) {
332 Game::recordFile = bestRecordingFilePath;
333 levels->setCurrentLevel(n);
334 setNextState(STATE_GAME);
335 }
336 break;
337 case 3:
338 selectNumber(imageManager, renderer, n, true);
339 break;
340 }
341 }
342 }
343 }
344 }
345
render(ImageManager & imageManager,SDL_Renderer & renderer)346 void LevelPlaySelect::render(ImageManager& imageManager, SDL_Renderer &renderer){
347 //First let the levelselect render.
348 LevelSelect::render(imageManager,renderer);
349
350 int x,y,dy=0;
351
352 //Get the current mouse location.
353 SDL_GetMouseState(&x,&y);
354
355 if(levelScrollBar)
356 dy=levelScrollBar->value;
357 //Upper bound of levels we'd like to display.
358 y+=dy*64;
359
360 SDL_Rect mouse={x,y,0,0};
361
362 //Show currently selected level (if any)
363 if(selectedNumber!=NULL){
364 selectedNumber->show(renderer, 0);
365
366 //Only show the replay button if the level is completed (won).
367 if(selectedNumber->getNumber()>=0 && selectedNumber->getNumber()<levels->getLevelCount()) {
368 if(levels->getLevel(selectedNumber->getNumber())->won){
369 if(!bestTimeFilePath.empty()){
370
371 SDL_Rect r={0,0,32,32};
372 const SDL_Rect box={SCREEN_WIDTH-420,SCREEN_HEIGHT-130,372,32};
373
374 if (isKeyboardOnly ? (section == 3 && section2 == 1) : pointOnRect(mouse, box)){
375 r.x = 32;
376 drawGUIBox(box.x, box.y, box.w, box.h, renderer, 0xFFFFFF40);
377 }
378 const SDL_Rect dstRect = {SCREEN_WIDTH-80,SCREEN_HEIGHT-130,r.w,r.h};
379 SDL_RenderCopy(&renderer,playButtonImage.get(),&r, &dstRect);
380 }
381
382 if(!bestRecordingFilePath.empty()){
383 SDL_Rect r={0,0,32,32};
384 const SDL_Rect box={SCREEN_WIDTH-420,SCREEN_HEIGHT-98,372,32};
385
386 if (isKeyboardOnly ? (section == 3 && section2 == 2) : pointOnRect(mouse, box)){
387 r.x = 32;
388 drawGUIBox(box.x, box.y, box.w, box.h, renderer, 0xFFFFFF40);
389 }
390
391 const SDL_Rect dstRect = {SCREEN_WIDTH-80,SCREEN_HEIGHT-98,r.w,r.h};
392 SDL_RenderCopy(&renderer,playButtonImage.get(),&r, &dstRect);
393 }
394 }
395 }
396
397 levelInfoRender.render(renderer);
398 }
399
400 //Draw highlight for play button.
401 if (isKeyboardOnly && play && play->enabled) {
402 play->state = (section == 3 && section2 == 3) ? 1 : 0;
403 }
404 }
405
renderTooltip(SDL_Renderer & renderer,unsigned int number,int dy)406 void LevelPlaySelect::renderTooltip(SDL_Renderer &renderer, unsigned int number, int dy){
407 if (!toolTip.name || toolTip.number != number) {
408 //Render the name of the level.
409 toolTip.name = textureFromText(renderer, *fontText, _CC(levels->getDictionaryManager(), levels->getLevelName(number)), objThemes.getTextColor(true));
410 toolTip.time=nullptr;
411 toolTip.recordings=nullptr;
412 toolTip.number=number;
413
414 //The time it took.
415 if(levels->getLevel(number)->time>0){
416 toolTip.time = textureFromText(renderer, *fontText,
417 tfm::format("%-.2fs", levels->getLevel(number)->time / 40.0).c_str(),
418 objThemes.getTextColor(true));
419 }
420
421 //The number of recordings it took.
422 if(levels->getLevel(number)->recordings>=0){
423 toolTip.recordings = textureFromText(renderer, *fontText,
424 tfm::format("%d", levels->getLevel(number)->recordings).c_str(),
425 objThemes.getTextColor(true));
426 }
427 }
428
429 const SDL_Rect nameSize = rectFromTexture(*toolTip.name);
430 //Now draw a square the size of the three texts combined.
431 SDL_Rect r=numbers[number].box;
432 r.y-=dy*64;
433 if(toolTip.time && toolTip.recordings){
434 const int recW = textureWidth(*toolTip.recordings);
435 const int timeW = textureWidth(*toolTip.time);
436 r.w=(nameSize.w)>(25+timeW+40+recW)?(nameSize.w):(25+timeW+40+recW);
437 r.h=nameSize.h+5+20;
438 }else{
439 r.w=nameSize.w;
440 r.h=nameSize.h;
441 }
442
443 //Make sure the tooltip doesn't go outside the window.
444 if(r.y>SCREEN_HEIGHT-200){
445 r.y-=nameSize.h+4;
446 }else{
447 r.y+=numbers[number].box.h+2;
448 }
449 if(r.x+r.w>SCREEN_WIDTH-50)
450 r.x=SCREEN_WIDTH-50-r.w;
451
452 //Draw a rectange
453 Uint32 color=0xFFFFFFFF;
454 drawGUIBox(r.x-5,r.y-5,r.w+10,r.h+10,renderer,color);
455
456 //Calc the position to draw.
457 SDL_Rect r2=r;
458
459 //Now we render the name if the surface isn't null.
460 if(toolTip.name){
461 //Draw the name.
462 applyTexture(r2.x, r2.y, toolTip.name, renderer);
463 }
464 //Increase the height to leave a gap between name and stats.
465 r2.y+=30;
466 if(toolTip.time){
467 //Now draw the time.
468 applyTexture(r2.x,r2.y,levelInfoRender.timeIcon,renderer);
469 r2.x+=25;
470 applyTexture(r2.x, r2.y, toolTip.time, renderer);
471 r2.x+=textureWidth(*toolTip.time)+15;
472 }
473 if(toolTip.recordings){
474 //Now draw the recordings.
475 applyTexture(r2.x,r2.y,levelInfoRender.recordingsIcon,renderer);
476 r2.x+=25;
477 applyTexture(r2.x, r2.y, toolTip.recordings, renderer);
478 }
479 }
480
resize(ImageManager & imageManager,SDL_Renderer & renderer)481 void LevelPlaySelect::resize(ImageManager &imageManager, SDL_Renderer &renderer){
482 //Let the LevelSelect do his stuff.
483 LevelSelect::resize(imageManager, renderer);
484
485 //Now create our gui again.
486 createGUI(imageManager,renderer, false);
487 }
488
GUIEventCallback_OnEvent(ImageManager & imageManager,SDL_Renderer & renderer,std::string name,GUIObject * obj,int eventType)489 void LevelPlaySelect::GUIEventCallback_OnEvent(ImageManager& imageManager, SDL_Renderer& renderer, std::string name,GUIObject* obj,int eventType){
490 //Let the level select handle his GUI events.
491 LevelSelect::GUIEventCallback_OnEvent(imageManager,renderer,name,obj,eventType);
492
493 //Check for the play button.
494 if(name=="cmdPlay"){
495 if(selectedNumber!=NULL){
496 levels->setCurrentLevel(selectedNumber->getNumber());
497 setNextState(STATE_GAME);
498 }
499 }
500 }
501