1 /*
2 Copyright (C) 2009-2021 Parallel Realities
3 
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License
6 as published by the Free Software Foundation; either version 2
7 of the License, or (at your option) any later version.
8 
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.
12 
13 See the GNU General Public License for more details.
14 
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
18 */
19 
20 #include "headers.h"
21 
22 #include "audio/audio.h"
23 #include "audio/music.h"
24 #include "entity.h"
25 #include "event/map_trigger.h"
26 #include "game.h"
27 #include "graphics/graphics.h"
28 #include "hud.h"
29 #include "init.h"
30 #include "inventory.h"
31 #include "map.h"
32 #include "medal.h"
33 #include "menu/io_menu.h"
34 #include "menu/main_menu.h"
35 #include "menu/menu.h"
36 #include "player.h"
37 #include "system/error.h"
38 #include "system/load_save.h"
39 #include "system/random.h"
40 #include "system/resources.h"
41 
42 extern Game game;
43 extern Entity *self;
44 
45 static ContinueData continueData;
46 
47 static void shake(void);
48 static void wipeOutRightToLeft(void);
49 static void wipeOutLeftToRight(void);
50 static void wipeInRightToLeft(void);
51 static void wipeInLeftToRight(void);
52 static void wipeInCircleToLarge(void);
53 static void wipeInCircleToSmall(void);
54 static void wipeOutCircleToLarge(void);
55 static void wipeOutCircleToSmall(void);
56 static void creditsWipeOut(void);
57 static void fadeToNormal(void);
58 static void initEndCredits(void);
59 
initGame()60 void initGame()
61 {
62 	game.drawScreen = TRUE;
63 
64 	if (game.paused == TRUE)
65 	{
66 		pauseGame();
67 	}
68 
69 	game.status = IN_GAME;
70 
71 	game.menu = initMainMenu();
72 
73 	game.drawMenu = &drawMainMenu;
74 
75 	game.shakeThinkTime = 0;
76 
77 	game.kills = 0;
78 
79 	game.batsDrowned = 0;
80 
81 	game.timesEaten = 0;
82 
83 	game.arrowsFired = 0;
84 
85 	game.playTime = 0;
86 
87 	game.distanceTravelled = 0;
88 
89 	game.timeSpentAsSlime = 0;
90 
91 	game.attacksBlocked = 0;
92 
93 	game.thinkTime = 0;
94 
95 	game.continues = 0;
96 
97 	game.secretsFound = 0;
98 
99 	game.cheating = FALSE;
100 
101 	game.infiniteArrows = FALSE;
102 
103 	game.infiniteEnergy = FALSE;
104 
105 	game.lavaIsFatal = TRUE;
106 
107 	game.mapExitable = 0;
108 
109 	game.canContinue = FALSE;
110 
111 	game.overrideMusic = FALSE;
112 
113 	game.showHUD = TRUE;
114 
115 	game.offsetX = game.offsetY = 0;
116 
117 	if (game.pauseSurface != NULL)
118 	{
119 		destroyTexture(game.pauseSurface);
120 
121 		game.pauseSurface = NULL;
122 	}
123 
124 	if (game.gameOverSurface != NULL)
125 	{
126 		destroyTexture(game.gameOverSurface);
127 
128 		game.gameOverSurface = NULL;
129 	}
130 }
131 
doGame()132 void doGame()
133 {
134 	/* Execute the action if there is one */
135 
136 	doGameOver();
137 
138 	fadeToNormal();
139 
140 	if (game.shakeThinkTime > 0 || game.shakeThinkTime == -1)
141 	{
142 		shake();
143 
144 		if (game.shakeThinkTime > 0)
145 		{
146 			game.shakeThinkTime--;
147 
148 			if (game.shakeThinkTime <= 0)
149 			{
150 				game.offsetX = game.offsetY = 0;
151 			}
152 		}
153 	}
154 
155 	if (game.weatherType != NO_WEATHER)
156 	{
157 		game.weatherAction();
158 	}
159 
160 	game.playTime++;
161 }
162 
freeGame()163 void freeGame()
164 {
165 	if (game.pauseSurface != NULL)
166 	{
167 		destroyTexture(game.pauseSurface);
168 
169 		game.pauseSurface = NULL;
170 	}
171 
172 	if (game.gameOverSurface != NULL)
173 	{
174 		destroyTexture(game.gameOverSurface);
175 
176 		game.gameOverSurface = NULL;
177 	}
178 }
179 
drawWeather()180 void drawWeather()
181 {
182 	if (game.weatherType != NO_WEATHER)
183 	{
184 		game.weatherDraw();
185 	}
186 }
187 
drawGame()188 void drawGame()
189 {
190 	float alpha;
191 
192 	if (game.alphaSurface.w != 0 && game.alphaSurface.h != 0)
193 	{
194 		alpha = 255.0f / game.alphaTime;
195 
196 		alpha *= game.thinkTime;
197 
198 		drawBox(game.alphaSurface.x, game.alphaSurface.y, game.alphaSurface.w, game.alphaSurface.h, game.alphaSurface.r, game.alphaSurface.g, game.alphaSurface.b, alpha);
199 	}
200 
201 	drawGameOver();
202 
203 	if (game.transition != NULL)
204 	{
205 		game.transition();
206 	}
207 }
208 
setTransition(int type,void (* func)(void))209 void setTransition(int type, void (*func)(void))
210 {
211 	if (type == TRANSITION_OUT)
212 	{
213 		switch (prand() % MAX_OUT_TRANSITIONS)
214 		{
215 			case WIPE_OUT_RIGHT_TO_LEFT:
216 				game.transitionX = SCREEN_WIDTH;
217 				game.transitionY = SCREEN_HEIGHT;
218 				game.transition = &wipeOutRightToLeft;
219 			break;
220 
221 			case WIPE_OUT_CIRCLE_TO_LARGE:
222 				game.transitionX = 0;
223 				game.transition = &wipeOutCircleToLarge;
224 			break;
225 
226 			case WIPE_OUT_CIRCLE_TO_SMALL:
227 				game.transitionX = SCREEN_WIDTH;
228 				game.transition = &wipeOutCircleToSmall;
229 			break;
230 
231 			default:
232 				game.transitionX = 0;
233 				game.transitionY = SCREEN_HEIGHT;
234 				game.transition = &wipeOutLeftToRight;
235 			break;
236 		}
237 	}
238 
239 	else
240 	{
241 		switch (prand() % MAX_IN_TRANSITIONS)
242 		{
243 			case WIPE_IN_RIGHT_TO_LEFT:
244 				game.transitionX = SCREEN_WIDTH;
245 				game.transitionY = SCREEN_HEIGHT;
246 				game.transition = &wipeInRightToLeft;
247 			break;
248 
249 			case WIPE_IN_CIRCLE_TO_LARGE:
250 				game.transitionX = 0;
251 				game.transition = &wipeInCircleToLarge;
252 			break;
253 
254 			case WIPE_IN_CIRCLE_TO_SMALL:
255 				game.transitionX = SCREEN_WIDTH;
256 				game.transition = &wipeInCircleToSmall;
257 			break;
258 
259 			default:
260 				game.transitionX = 0;
261 				game.transitionY = SCREEN_HEIGHT;
262 				game.transition = &wipeInLeftToRight;
263 			break;
264 		}
265 	}
266 
267 	game.transitionCallback = func;
268 }
269 
shakeScreen(int shakeStrength,int time)270 void shakeScreen(int shakeStrength, int time)
271 {
272 	game.offsetX = game.offsetY = 0;
273 
274 	game.shakeThinkTime = time;
275 	game.shakeStrength = shakeStrength;
276 }
277 
shake()278 static void shake()
279 {
280 	switch (game.shakeStrength)
281 	{
282 		case LIGHT:
283 			game.offsetX = prand() % 2 * (prand() % 2 == 0 ? -1 : 1);
284 			game.offsetY = prand() % 2 * (prand() % 2 == 0 ? -1 : 1);
285 		break;
286 
287 		case MEDIUM:
288 			game.offsetX = prand() % 4 * (prand() % 2 == 0 ? -1 : 1);
289 			game.offsetY = prand() % 4 * (prand() % 2 == 0 ? -1 : 1);
290 		break;
291 
292 		case STRONG:
293 			game.offsetX = prand() % 6 * (prand() % 2 == 0 ? -1 : 1);
294 			game.offsetY = prand() % 6 * (prand() % 2 == 0 ? -1 : 1);
295 		break;
296 	}
297 }
298 
wipeOutRightToLeft()299 static void wipeOutRightToLeft()
300 {
301 	if (game.transitionX <= -15)
302 	{
303 		game.transition = NULL;
304 
305 		if (game.transitionCallback != NULL)
306 		{
307 			clearScreen(0, 0, 0);
308 
309 			game.transitionCallback();
310 		}
311 
312 		return;
313 	}
314 
315 	drawBox(game.transitionX < 0 ? 0 : game.transitionX, 0, SCREEN_WIDTH, SCREEN_HEIGHT, 0, 0, 0, 255);
316 
317 	game.transitionX -= 15;
318 }
319 
wipeOutLeftToRight()320 static void wipeOutLeftToRight()
321 {
322 	if (game.transitionX > SCREEN_WIDTH + 15)
323 	{
324 		game.transition = NULL;
325 
326 		if (game.transitionCallback != NULL)
327 		{
328 			clearScreen(0, 0, 0);
329 
330 			game.transitionCallback();
331 		}
332 
333 		return;
334 	}
335 
336 	drawBox(0, 0, game.transitionX, SCREEN_HEIGHT, 0, 0, 0, 255);
337 
338 	game.transitionX += 15;
339 }
340 
wipeInLeftToRight()341 static void wipeInLeftToRight()
342 {
343 	if (game.transitionX >= SCREEN_WIDTH)
344 	{
345 		game.transition = NULL;
346 
347 		if (game.transitionCallback != NULL)
348 		{
349 			clearScreen(0, 0, 0);
350 
351 			game.transitionCallback();
352 		}
353 
354 		return;
355 	}
356 
357 	drawBox(game.transitionX, 0, SCREEN_WIDTH - game.transitionX, SCREEN_HEIGHT, 0, 0, 0, 255);
358 
359 	game.transitionX += 15;
360 }
361 
wipeInRightToLeft()362 static void wipeInRightToLeft()
363 {
364 	if (game.transitionX < 0)
365 	{
366 		game.transition = NULL;
367 
368 		if (game.transitionCallback != NULL)
369 		{
370 			clearScreen(0, 0, 0);
371 
372 			game.transitionCallback();
373 		}
374 
375 		return;
376 	}
377 
378 	drawBox(0, 0, game.transitionX, SCREEN_HEIGHT, 0, 0, 0, 255);
379 
380 	game.transitionX -= 15;
381 }
382 
wipeInCircleToLarge()383 static void wipeInCircleToLarge()
384 {
385 	if (game.transitionX > SCREEN_WIDTH)
386 	{
387 		game.transition = NULL;
388 
389 		if (game.transitionCallback != NULL)
390 		{
391 			clearScreen(0, 0, 0);
392 
393 			game.transitionCallback();
394 		}
395 
396 		return;
397 	}
398 
399 	drawCircleFromSurface(SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2, game.transitionX);
400 
401 	game.transitionX += 10;
402 }
403 
wipeInCircleToSmall()404 static void wipeInCircleToSmall()
405 {
406 	if (game.transitionX < 0)
407 	{
408 		game.transition = NULL;
409 
410 		if (game.transitionCallback != NULL)
411 		{
412 			clearScreen(0, 0, 0);
413 
414 			game.transitionCallback();
415 		}
416 
417 		return;
418 	}
419 
420 	drawCircle(SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2, game.transitionX, 0, 0, 0);
421 
422 	game.transitionX -= 10;
423 }
424 
wipeOutCircleToLarge()425 static void wipeOutCircleToLarge()
426 {
427 	if (game.transitionX > SCREEN_WIDTH)
428 	{
429 		game.transition = NULL;
430 
431 		if (game.transitionCallback != NULL)
432 		{
433 			clearScreen(0, 0, 0);
434 
435 			game.transitionCallback();
436 		}
437 
438 		return;
439 	}
440 
441 	drawCircle(SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2, game.transitionX, 0, 0, 0);
442 
443 	game.transitionX += 10;
444 }
445 
wipeOutCircleToSmall()446 static void wipeOutCircleToSmall()
447 {
448 	if (game.transitionX <= -10)
449 	{
450 		game.transition = NULL;
451 
452 		if (game.transitionCallback != NULL)
453 		{
454 			clearScreen(0, 0, 0);
455 
456 			game.transitionCallback();
457 		}
458 
459 		return;
460 	}
461 
462 	drawCircleFromSurface(SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2, game.transitionX < 0 ? 0 : game.transitionX);
463 
464 	game.transitionX -= 10;
465 }
466 
creditsWipeOut()467 static void creditsWipeOut()
468 {
469 	if (game.transitionX <= -5)
470 	{
471 		game.transition = NULL;
472 
473 		if (game.transitionCallback != NULL)
474 		{
475 			clearScreen(0, 0, 0);
476 
477 			game.transitionCallback();
478 		}
479 
480 		return;
481 	}
482 
483 	drawCircleFromSurface(SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2, game.transitionX < 0 ? 0 : game.transitionX);
484 
485 	game.transitionX -= 5;
486 
487 	if (game.transitionX <= spotlightSize() / 2)
488 	{
489 		if (game.transitionY > 0)
490 		{
491 			game.transitionX = spotlightSize() / 2;
492 
493 			game.transitionY--;
494 		}
495 	}
496 }
497 
setNextLevel(char * name,char * playerStart)498 void setNextLevel(char *name, char *playerStart)
499 {
500 	STRNCPY(game.nextMap, name, sizeof(game.nextMap));
501 	STRNCPY(game.playerStart, playerStart, sizeof(game.playerStart));
502 }
503 
setNextLevelFromScript(char * token)504 void setNextLevelFromScript(char *token)
505 {
506 	char *name, *playerStart, *savePtr;
507 
508 	name = strtok_r(token, " ", &savePtr);
509 	playerStart = strtok_r(NULL, "\0", &savePtr);
510 
511 	setNextLevel(name, playerStart);
512 
513 	setTransition(TRANSITION_OUT, &goToNextMap);
514 
515 	if (game.overrideMusic == FALSE)
516 	{
517 		fadeOutMusic(1000);
518 	}
519 }
520 
showEndCredits()521 void showEndCredits()
522 {
523 	game.overrideMusic = FALSE;
524 
525 	game.transitionX = SCREEN_WIDTH;
526 	game.transitionY = 120;
527 	game.transition = &creditsWipeOut;
528 
529 	game.transitionCallback = &initEndCredits;
530 }
531 
initEndCredits()532 static void initEndCredits()
533 {
534 	game.status = IN_CREDITS;
535 }
536 
goToNextMap()537 void goToNextMap()
538 {
539 	Entity *start;
540 
541 	saveTemporaryData();
542 
543 	freeLevelResources();
544 
545 	if (hasPersistance(game.nextMap) == FALSE)
546 	{
547 		loadMap(game.nextMap, TRUE);
548 	}
549 
550 	else
551 	{
552 		loadMap(game.nextMap, FALSE);
553 
554 		loadPersitanceData(game.nextMap);
555 	}
556 
557 	if (strcmpignorecase(game.playerStart, "PLAYER_START") != 0)
558 	{
559 		start = getEntityByObjectiveName(game.playerStart);
560 
561 		if (start == NULL)
562 		{
563 			showErrorAndExit("Could not find player start %s", game.playerStart);
564 		}
565 
566 		loadPlayer(start->startX, start->startY, NULL);
567 	}
568 
569 	fireMapTrigger(game.nextMap);
570 
571 	game.nextMap[0] = '\0';
572 	game.playerStart[0] = '\0';
573 
574 	game.offsetX = game.offsetY = 0;
575 	game.shakeThinkTime = 0;
576 
577 	clearScreen(0, 0, 0);
578 }
579 
setCheckpoint(float x,float y)580 void setCheckpoint(float x, float y)
581 {
582 	game.checkpointX = x;
583 	game.checkpointY = y;
584 }
585 
getCheckpoint(float * x,float * y)586 void getCheckpoint(float *x, float *y)
587 {
588 	*x = game.checkpointX;
589 	*y = game.checkpointY;
590 }
591 
pauseGame()592 void pauseGame()
593 {
594 	switch (game.status)
595 	{
596 		case IN_GAME:
597 		case IN_TITLE:
598 			game.paused = TRUE;
599 
600 			game.previousStatus = game.status;
601 
602 			game.status = IN_MENU;
603 
604 			game.menu = initMainMenu();
605 
606 			if (game.pauseSurface == NULL)
607 			{
608 				game.pauseSurface = copyScreen();
609 			}
610 
611 			if (game.previousStatus == IN_GAME)
612 			{
613 				pauseMusic(TRUE);
614 
615 				pauseSound(TRUE);
616 			}
617 		break;
618 
619 		case IN_EDITOR:
620 			cleanup(0);
621 		break;
622 
623 		case IN_INVENTORY:
624 		case IN_CREDITS:
625 			/* Do nothing */
626 		break;
627 
628 		default:
629 			if (game.menu->returnAction == NULL)
630 			{
631 				if (game.pauseSurface != NULL)
632 				{
633 					destroyTexture(game.pauseSurface);
634 
635 					game.pauseSurface = NULL;
636 				}
637 
638 				game.paused = FALSE;
639 
640 				game.status = game.previousStatus;
641 
642 				game.menu = initMainMenu();
643 
644 				game.drawMenu = &drawMainMenu;
645 
646 				pauseMusic(FALSE);
647 
648 				pauseSound(FALSE);
649 			}
650 		break;
651 	}
652 }
653 
showSaveDialog()654 void showSaveDialog()
655 {
656 	pauseGame();
657 
658 	pauseMusic(FALSE);
659 
660 	pauseSound(FALSE);
661 
662 	game.status = IN_MENU;
663 
664 	game.paused = TRUE;
665 
666 	game.menu = initIOMenu(TRUE);
667 
668 	game.drawMenu = &drawIOMenu;
669 }
670 
pauseGameInventory()671 void pauseGameInventory()
672 {
673 	switch (game.status)
674 	{
675 		case IN_GAME:
676 			game.status = IN_INVENTORY;
677 
678 			clearInventoryDescription();
679 
680 			if (game.pauseSurface == NULL)
681 			{
682 				game.pauseSurface = copyScreen();
683 			}
684 		break;
685 
686 		case IN_INVENTORY:
687 			if (game.pauseSurface != NULL)
688 			{
689 				destroyTexture(game.pauseSurface);
690 
691 				game.pauseSurface = NULL;
692 			}
693 
694 			game.status = IN_GAME;
695 		break;
696 	}
697 }
698 
focusLost()699 void focusLost()
700 {
701 	#if DEV == 0
702 		if (game.paused == FALSE && game.status == IN_GAME)
703 		{
704 			pauseGame();
705 		}
706 	#endif
707 }
708 
showPauseDialog()709 void showPauseDialog()
710 {
711 	drawImage(game.pauseSurface, 0, 0, FALSE, 255);
712 }
713 
resetGameSettings()714 void resetGameSettings()
715 {
716 	game.audio = TRUE;
717 	game.sfxDefaultVolume = 10;
718 	game.musicDefaultVolume = 8;
719 	game.showHints = TRUE;
720 	game.fullscreen = FALSE;
721 	game.audioQuality = 22050;
722 }
723 
writeGameSettingsToFile(FILE * fp)724 void writeGameSettingsToFile(FILE *fp)
725 {
726 	fprintf(fp, "GAME_SETTINGS\n");
727 	fprintf(fp, "AUDIO %d\n", game.audio == TRUE ? TRUE : game.audioDisabled);
728 	fprintf(fp, "SFX_VOLUME %d\n", game.sfxDefaultVolume);
729 	fprintf(fp, "MUSIC_VOLUME %d\n", game.musicDefaultVolume);
730 	fprintf(fp, "HINTS %d\n", game.showHints);
731 	fprintf(fp, "FULLSCREEN %d\n", game.fullscreen);
732 	fprintf(fp, "AUDIO_QUALITY %d\n", game.audioQuality);
733 	fprintf(fp, "FONT %s\n", game.customFont);
734 	fprintf(fp, "SMALL_FONT_SIZE %d\n", game.fontSizeSmall == 0 ? NORMAL_FONT_SIZE : game.fontSizeSmall);
735 	fprintf(fp, "LARGE_FONT_SIZE %d\n", game.fontSizeLarge == 0 ? LARGE_FONT_SIZE : game.fontSizeLarge);
736 }
737 
readGameSettingsFromFile(char * buffer)738 void readGameSettingsFromFile(char *buffer)
739 {
740 	char *line, *token, *savePtr;
741 
742 	savePtr = NULL;
743 
744 	line = strtok_r(buffer, "\n", &savePtr);
745 
746 	while (line != NULL)
747 	{
748 		token = strtok(line, " ");
749 
750 		if (strcmpignorecase(token, "AUDIO") == 0)
751 		{
752 			token = strtok(NULL, "\0");
753 
754 			if (token != NULL)
755 			{
756 				game.audio = atoi(token);
757 			}
758 		}
759 
760 		else if (strcmpignorecase(token, "SFX_VOLUME") == 0)
761 		{
762 			token = strtok(NULL, "\0");
763 
764 			if (token != NULL)
765 			{
766 				game.sfxDefaultVolume = atoi(token);
767 			}
768 		}
769 
770 		else if (strcmpignorecase(token, "MUSIC_VOLUME") == 0)
771 		{
772 			token = strtok(NULL, "\0");
773 
774 			if (token != NULL)
775 			{
776 				game.musicDefaultVolume = atoi(token);
777 			}
778 		}
779 
780 		else if (strcmpignorecase(token, "HINTS") == 0)
781 		{
782 			token = strtok(NULL, "\0");
783 
784 			if (token != NULL)
785 			{
786 				game.showHints = atoi(token);
787 			}
788 		}
789 
790 		else if (strcmpignorecase(token, "FULLSCREEN") == 0)
791 		{
792 			token = strtok(NULL, "\0");
793 
794 			if (token != NULL)
795 			{
796 				game.fullscreen = atoi(token);
797 			}
798 		}
799 
800 		else if (strcmpignorecase(token, "AUDIO_QUALITY") == 0)
801 		{
802 			token = strtok(NULL, "\0");
803 
804 			if (token != NULL)
805 			{
806 				game.audioQuality = atoi(token);
807 			}
808 
809 			if (game.audioQuality == 0)
810 			{
811 				game.audioQuality = 22050;
812 			}
813 		}
814 
815 		else if (strcmpignorecase(token, "FONT") == 0)
816 		{
817 			token = strtok(NULL, "\0");
818 
819 			if (token != NULL)
820 			{
821 				STRNCPY(game.customFont, token, MAX_FILE_LENGTH);
822 			}
823 		}
824 
825 		else if (strcmpignorecase(token, "SMALL_FONT_SIZE") == 0)
826 		{
827 			token = strtok(NULL, "\0");
828 
829 			if (token != NULL)
830 			{
831 				game.fontSizeSmall = atoi(token);
832 			}
833 		}
834 
835 		else if (strcmpignorecase(token, "LARGE_FONT_SIZE") == 0)
836 		{
837 			token = strtok(NULL, "\0");
838 
839 			if (token != NULL)
840 			{
841 				game.fontSizeLarge = atoi(token);
842 			}
843 		}
844 
845 		line = strtok_r(NULL, "\n", &savePtr);
846 	}
847 
848 	if (strlen(game.customFont) != 0)
849 	{
850 		if (game.fontSizeSmall <= 0 || game.fontSizeLarge <= 0)
851 		{
852 			showErrorAndExit(_("SMALL_FONT_SIZE and LARGE_FONT_SIZE must be specified when using a custom font"));
853 		}
854 	}
855 }
856 
initGameOver()857 void initGameOver()
858 {
859 	if (game.gameOverSurface == NULL)
860 	{
861 		game.gameOverSurface = loadImage("gfx/common/gameover.png");
862 	}
863 
864 	game.gameOverX = 0;
865 }
866 
doGameOver()867 void doGameOver()
868 {
869 	if (game.gameOverSurface != NULL)
870 	{
871 		game.gameOverX += 3;
872 	}
873 }
874 
drawGameOver()875 void drawGameOver()
876 {
877 	if (game.gameOverSurface != NULL)
878 	{
879 		if (game.gameOverX >= game.gameOverSurface->w)
880 		{
881 			game.gameOverX = game.gameOverSurface->w;
882 		}
883 
884 		drawClippedImage(game.gameOverSurface, 0, 0, (SCREEN_WIDTH - game.gameOverSurface->w) / 2, (SCREEN_HEIGHT - game.gameOverSurface->h) / 2, game.gameOverX, game.gameOverSurface->h);
885 	}
886 }
887 
getPlayTimeAsString()888 char *getPlayTimeAsString()
889 {
890 	/* 1 second is 60 frames */
891 
892 	int hours, minutes;
893 	long tempTime;
894 	char *timeString;
895 
896 	timeString = malloc(15 * sizeof(char));
897 
898 	if (timeString == NULL)
899 	{
900 		showErrorAndExit("Failed to allocate a whole %d bytes for Play Time string...", 15 * (int)sizeof(char));
901 	}
902 
903 	tempTime = game.playTime;
904 
905 	hours = tempTime / (60 * 60 * 60);
906 
907 	tempTime -= hours * 60 * 60 * 60;
908 
909 	minutes = tempTime / (60 * 60);
910 
911 	SNPRINTF(timeString, 15, "%dH %dM", hours, minutes);
912 
913 	return timeString;
914 }
915 
getPlayTimeHours()916 char *getPlayTimeHours()
917 {
918 	/* 1 second is 60 frames */
919 
920 	int hours;
921 	long tempTime;
922 	char *timeString;
923 
924 	timeString = malloc(5 * sizeof(char));
925 
926 	if (timeString == NULL)
927 	{
928 		showErrorAndExit("Failed to allocate a whole %d bytes for Play Time Hours string...", 5 * (int)sizeof(char));
929 	}
930 
931 	tempTime = game.playTime;
932 
933 	hours = tempTime / (60 * 60 * 60);
934 
935 	SNPRINTF(timeString, 5, "%d", hours);
936 
937 	return timeString;
938 }
939 
getSlimeTimeAsString()940 char *getSlimeTimeAsString()
941 {
942 	/* 1 second is 60 frames */
943 
944 	int minutes;
945 	long tempTime;
946 	char *timeString;
947 
948 	timeString = malloc(15 * sizeof(char));
949 
950 	if (timeString == NULL)
951 	{
952 		showErrorAndExit("Failed to allocate a whole %d bytes for Slime Time string...", 15 * (int)sizeof(char));
953 	}
954 
955 	tempTime = game.timeSpentAsSlime;
956 
957 	minutes = tempTime / (60 * 60);
958 
959 	tempTime -= minutes * 60 * 60;
960 
961 	tempTime /= 60;
962 
963 	SNPRINTF(timeString, 15, "%dM %ldS", minutes, tempTime);
964 
965 	return timeString;
966 }
967 
fadeFromColour(int r,int g,int b,int fadeTime)968 void fadeFromColour(int r, int g, int b, int fadeTime)
969 {
970 	game.alphaTime = game.thinkTime = fadeTime;
971 
972 	game.alphaSurface.x = 0;
973 	game.alphaSurface.y = 0;
974 	game.alphaSurface.w = SCREEN_WIDTH;
975 	game.alphaSurface.h = SCREEN_HEIGHT;
976 
977 	game.alphaSurface.r = r;
978 	game.alphaSurface.g = g;
979 	game.alphaSurface.b = b;
980 
981 	drawBox(0, 0, game.transitionX, SCREEN_HEIGHT, 0, 0, 0, 255);
982 }
983 
fadeToNormal()984 static void fadeToNormal()
985 {
986 	if (game.alphaSurface.w != 0 && game.alphaSurface.h != 0)
987 	{
988 		game.thinkTime--;
989 
990 		if (game.thinkTime <= 0)
991 		{
992 			game.alphaSurface.w = 0;
993 			game.alphaSurface.h = 0;
994 		}
995 	}
996 }
997 
increaseKillCount()998 void increaseKillCount()
999 {
1000 	game.kills++;
1001 
1002 	if (game.kills == 100)
1003 	{
1004 		addMedal("kill_100");
1005 	}
1006 
1007 	else if (game.kills == 200)
1008 	{
1009 		addMedal("kill_200");
1010 	}
1011 
1012 	else if (game.kills == 500)
1013 	{
1014 		addMedal("kill_500");
1015 	}
1016 
1017 	else if (game.kills == 1000)
1018 	{
1019 		addMedal("kill_1000");
1020 	}
1021 
1022 	else if (game.kills == 2000)
1023 	{
1024 		addMedal("kill_2000");
1025 	}
1026 }
1027 
increaseSecretsFound()1028 void increaseSecretsFound()
1029 {
1030 	game.secretsFound++;
1031 
1032 	setInfoBoxMessage(90, 0, 255, 0, _("You found a secret!"));
1033 
1034 	playSound("sound/common/secret");
1035 
1036 	if (game.secretsFound == TOTAL_SECRETS)
1037 	{
1038 		addMedal("all_secrets");
1039 	}
1040 }
1041 
setContinuePoint(int cameraFollow,char * boss,void (* resumeAction)(void))1042 void setContinuePoint(int cameraFollow, char *boss, void (*resumeAction)(void))
1043 {
1044 	Entity *temp;
1045 
1046 	temp = self;
1047 
1048 	game.canContinue = TRUE;
1049 
1050 	continueData.cameraMinX = getCameraMinX();
1051 	continueData.cameraMinY = getCameraMinY();
1052 	continueData.cameraMaxX = getCameraMaxX();
1053 	continueData.cameraMaxY = getCameraMaxY();
1054 
1055 	continueData.cameraFollow = cameraFollow;
1056 
1057 	continueData.resumeAction = resumeAction;
1058 
1059 	STRNCPY(continueData.boss, boss, sizeof(continueData.boss));
1060 
1061 	saveContinueData();
1062 
1063 	self = temp;
1064 }
1065 
getContinuePoint()1066 void getContinuePoint()
1067 {
1068 	Entity *e;
1069 
1070 	loadContinueData();
1071 
1072 	limitCamera(continueData.cameraMinX, continueData.cameraMinY, continueData.cameraMaxX, continueData.cameraMaxY);
1073 
1074 	setMapStartX(continueData.cameraMinX);
1075 	setMapStartY(continueData.cameraMinY);
1076 
1077 	setCameraPosition(continueData.cameraMinX, continueData.cameraMinY);
1078 
1079 	limitPlayerToCameraLimits();
1080 
1081 	if (continueData.cameraFollow == FALSE)
1082 	{
1083 		centerMapOnEntity(NULL);
1084 	}
1085 
1086 	else
1087 	{
1088 		cameraSnapToTargetEntity();
1089 	}
1090 
1091 	if (continueData.resumeAction != NULL)
1092 	{
1093 		e = getEntityByName(continueData.boss);
1094 
1095 		if (e == NULL)
1096 		{
1097 			showErrorAndExit("Continue data could not find %s", continueData.boss);
1098 		}
1099 
1100 		e->action = continueData.resumeAction;
1101 	}
1102 
1103 	game.continues++;
1104 
1105 	if (game.continues == 20)
1106 	{
1107 		addMedal("continue_20");
1108 	}
1109 }
1110 
clearContinuePoint()1111 void clearContinuePoint()
1112 {
1113 	game.canContinue = FALSE;
1114 
1115 	continueData.resumeAction = NULL;
1116 
1117 	continueData.boss[0] = '\0';
1118 }
1119 
updateExitCount(int exitCount)1120 void updateExitCount(int exitCount)
1121 {
1122 	game.mapExitable += exitCount;
1123 
1124 	if (game.mapExitable < 0)
1125 	{
1126 		game.mapExitable = 0;
1127 	}
1128 }
1129