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