1 // SONIC ROBO BLAST 2
2 //-----------------------------------------------------------------------------
3 // Copyright (C) 1993-1996 by id Software, Inc.
4 // Copyright (C) 1998-2000 by DooM Legacy Team.
5 // Copyright (C) 1999-2020 by Sonic Team Junior.
6 //
7 // This program is free software distributed under the
8 // terms of the GNU General Public License, version 2.
9 // See the 'LICENSE' file for more details.
10 //-----------------------------------------------------------------------------
11 /// \file  f_finale.c
12 /// \brief Title screen, intro, game evaluation, and credits.
13 
14 #include "doomdef.h"
15 #include "doomstat.h"
16 #include "d_main.h"
17 #include "d_netcmd.h"
18 #include "f_finale.h"
19 #include "g_game.h"
20 #include "hu_stuff.h"
21 #include "r_local.h"
22 #include "s_sound.h"
23 #include "i_video.h"
24 #include "v_video.h"
25 #include "w_wad.h"
26 #include "z_zone.h"
27 #include "i_system.h"
28 #include "i_threads.h"
29 #include "m_menu.h"
30 #include "dehacked.h"
31 #include "g_input.h"
32 #include "console.h"
33 #include "m_random.h"
34 #include "m_misc.h" // moviemode functionality
35 #include "y_inter.h"
36 #include "m_cond.h"
37 #include "p_local.h"
38 #include "p_setup.h"
39 #include "st_stuff.h" // hud hiding
40 #include "fastcmp.h"
41 #include "console.h"
42 
43 #include "lua_hud.h"
44 
45 // Stage of animation:
46 // 0 = text, 1 = art screen
47 INT32 finalecount;
48 INT32 titlescrollxspeed = 20;
49 INT32 titlescrollyspeed = 0;
50 UINT8 titlemapinaction = TITLEMAP_OFF;
51 
52 static INT32 timetonext; // Delay between screen changes
53 static INT32 continuetime; // Short delay when continuing
54 
55 static tic_t animtimer; // Used for some animation timings
56 static INT16 skullAnimCounter; // Prompts: Chevron animation
57 
58 static INT32 deplete;
59 static tic_t stoptimer;
60 
61 static boolean keypressed = false;
62 
63 // (no longer) De-Demo'd Title Screen
64 static tic_t xscrolltimer;
65 static tic_t yscrolltimer;
66 static INT32 menuanimtimer; // Title screen: background animation timing
67 mobj_t *titlemapcameraref = NULL;
68 
69 // menu presentation state
70 char curbgname[9];
71 SINT8 curfadevalue;
72 INT32 curbgcolor;
73 INT32 curbgxspeed;
74 INT32 curbgyspeed;
75 boolean curbghide;
76 boolean hidetitlemap;		// WARNING: set to false by M_SetupNextMenu and M_ClearMenus
77 
78 static UINT8  curDemo = 0;
79 static UINT32 demoDelayLeft;
80 static UINT32 demoIdleLeft;
81 
82 // customizable title screen graphics
83 
84 ttmode_enum ttmode = TTMODE_OLD;
85 UINT8 ttscale = 1; // FRACUNIT / ttscale
86 // ttmode user vars
87 char ttname[9];
88 INT16 ttx = 0;
89 INT16 tty = 0;
90 INT16 ttloop = -1;
91 UINT16 tttics = 1;
92 
93 boolean curhidepics;
94 ttmode_enum curttmode;
95 UINT8 curttscale;
96 // ttmode user vars
97 char curttname[9];
98 INT16 curttx;
99 INT16 curtty;
100 INT16 curttloop;
101 UINT16 curtttics;
102 
103 // ttmode old
104 static patch_t *ttbanner; // white banner with "robo blast" and "2"
105 static patch_t *ttwing; // wing background
106 static patch_t *ttsonic; // "SONIC"
107 static patch_t *ttswave1; // Title Sonics
108 static patch_t *ttswave2;
109 static patch_t *ttswip1;
110 static patch_t *ttsprep1;
111 static patch_t *ttsprep2;
112 static patch_t *ttspop1;
113 static patch_t *ttspop2;
114 static patch_t *ttspop3;
115 static patch_t *ttspop4;
116 static patch_t *ttspop5;
117 static patch_t *ttspop6;
118 static patch_t *ttspop7;
119 
120 // ttmode alacroix
121 static SINT8 testttscale = 0;
122 static SINT8 activettscale = 0;
123 boolean ttavailable[6];
124 boolean ttloaded[6];
125 
126 static patch_t *ttribb[6][TTMAX_ALACROIX];
127 static patch_t *ttsont[6][TTMAX_ALACROIX];
128 static patch_t *ttrobo[6][TTMAX_ALACROIX];
129 static patch_t *tttwot[6][TTMAX_ALACROIX];
130 static patch_t *ttembl[6][TTMAX_ALACROIX];
131 static patch_t *ttrbtx[6][TTMAX_ALACROIX];
132 static patch_t *ttsoib[6][TTMAX_ALACROIX];
133 static patch_t *ttsoif[6][TTMAX_ALACROIX];
134 static patch_t *ttsoba[6][TTMAX_ALACROIX];
135 static patch_t *ttsobk[6][TTMAX_ALACROIX];
136 static patch_t *ttsodh[6][TTMAX_ALACROIX];
137 static patch_t *tttaib[6][TTMAX_ALACROIX];
138 static patch_t *tttaif[6][TTMAX_ALACROIX];
139 static patch_t *tttaba[6][TTMAX_ALACROIX];
140 static patch_t *tttabk[6][TTMAX_ALACROIX];
141 static patch_t *tttabt[6][TTMAX_ALACROIX];
142 static patch_t *tttaft[6][TTMAX_ALACROIX];
143 static patch_t *ttknib[6][TTMAX_ALACROIX];
144 static patch_t *ttknif[6][TTMAX_ALACROIX];
145 static patch_t *ttknba[6][TTMAX_ALACROIX];
146 static patch_t *ttknbk[6][TTMAX_ALACROIX];
147 static patch_t *ttkndh[6][TTMAX_ALACROIX];
148 
149 #define TTEMBL (ttembl[activettscale-1])
150 #define TTRIBB (ttribb[activettscale-1])
151 #define TTSONT (ttsont[activettscale-1])
152 #define TTROBO (ttrobo[activettscale-1])
153 #define TTTWOT (tttwot[activettscale-1])
154 #define TTRBTX (ttrbtx[activettscale-1])
155 #define TTSOIB (ttsoib[activettscale-1])
156 #define TTSOIF (ttsoif[activettscale-1])
157 #define TTSOBA (ttsoba[activettscale-1])
158 #define TTSOBK (ttsobk[activettscale-1])
159 #define TTSODH (ttsodh[activettscale-1])
160 #define TTTAIB (tttaib[activettscale-1])
161 #define TTTAIF (tttaif[activettscale-1])
162 #define TTTABA (tttaba[activettscale-1])
163 #define TTTABK (tttabk[activettscale-1])
164 #define TTTABT (tttabt[activettscale-1])
165 #define TTTAFT (tttaft[activettscale-1])
166 #define TTKNIB (ttknib[activettscale-1])
167 #define TTKNIF (ttknif[activettscale-1])
168 #define TTKNBA (ttknba[activettscale-1])
169 #define TTKNBK (ttknbk[activettscale-1])
170 #define TTKNDH (ttkndh[activettscale-1])
171 
172 static boolean sonic_blink = false;
173 static boolean sonic_blink_twice = false;
174 static boolean sonic_blinked_already = false;
175 static INT32 sonic_idle_start = 0;
176 static INT32 sonic_idle_end = 0;
177 static boolean tails_blink = false;
178 static boolean tails_blink_twice = false;
179 static boolean tails_blinked_already = false;
180 static INT32 tails_idle_start = 0;
181 static INT32 tails_idle_end = 0;
182 static boolean knux_blink = false;
183 static boolean knux_blink_twice = false;
184 static boolean knux_blinked_already = false;
185 static INT32 knux_idle_start = 0;
186 static INT32 knux_idle_end = 0;
187 
188 // ttmode user
189 static patch_t *ttuser[TTMAX_USER];
190 static INT32 ttuser_count = 0;
191 
192 static boolean goodending;
193 static patch_t *endbrdr[2]; // border - blue, white, pink - where have i seen those colours before?
194 static patch_t *endbgsp[3]; // nebula, sun, planet
195 static patch_t *endegrk[2]; // eggrock - replaced midway through good ending
196 static patch_t *endfwrk[3]; // firework - replaced with skin when good ending
197 static patch_t *endspkl[3]; // sparkle
198 static patch_t *endglow[2]; // glow aura - replaced with black rock's midway through good ending
199 static patch_t *endxpld[4]; // mini explosion
200 static patch_t *endescp[5]; // escape pod + flame
201 static INT32 sparkloffs[3][2]; // eggrock explosions/blackrock sparkles
202 static INT32 sparklloop;
203 
204 //
205 // PROMPT STATE
206 //
207 boolean promptactive = false;
208 static mobj_t *promptmo;
209 static INT16 promptpostexectag;
210 static boolean promptblockcontrols;
211 static char *promptpagetext = NULL;
212 static INT32 callpromptnum = INT32_MAX;
213 static INT32 callpagenum = INT32_MAX;
214 static INT32 callplayer = INT32_MAX;
215 
216 //
217 // CUTSCENE TEXT WRITING
218 //
219 static const char *cutscene_basetext = NULL;
220 static char cutscene_disptext[1024];
221 static INT32 cutscene_baseptr = 0;
222 static INT32 cutscene_writeptr = 0;
223 static INT32 cutscene_textcount = 0;
224 static INT32 cutscene_textspeed = 0;
225 static UINT8 cutscene_boostspeed = 0;
226 static tic_t cutscene_lasttextwrite = 0;
227 
228 // STJR Intro
229 char stjrintro[9] = "STJRI000";
230 
231 //
232 // This alters the text string cutscene_disptext.
233 // Use the typical string drawing functions to display it.
234 // Returns 0 if \0 is reached (end of input)
235 //
F_WriteText(void)236 static UINT8 F_WriteText(void)
237 {
238 	INT32 numtowrite = 1;
239 	const char *c;
240 	tic_t ltw = I_GetTime();
241 
242 	if (cutscene_lasttextwrite == ltw)
243 		return 1; // singletics prevention
244 	cutscene_lasttextwrite = ltw;
245 
246 	if (cutscene_boostspeed)
247 	{
248 		// for custom cutscene speedup mode
249 		numtowrite = 8;
250 	}
251 	else
252 	{
253 		// Don't draw any characters if the count was 1 or more when we started
254 		if (--cutscene_textcount >= 0)
255 			return 1;
256 
257 		if (cutscene_textspeed < 7)
258 			numtowrite = 8 - cutscene_textspeed;
259 	}
260 
261 	for (;numtowrite > 0;++cutscene_baseptr)
262 	{
263 		c = &cutscene_basetext[cutscene_baseptr];
264 		if (!c || !*c || *c=='#')
265 			return 0;
266 
267 		// \xA0 - \xAF = change text speed
268 		if ((UINT8)*c >= 0xA0 && (UINT8)*c <= 0xAF)
269 		{
270 			cutscene_textspeed = (INT32)((UINT8)*c - 0xA0);
271 			continue;
272 		}
273 		// \xB0 - \xD2 = delay character for up to one second (35 tics)
274 		else if ((UINT8)*c >= 0xB0 && (UINT8)*c <= (0xB0+TICRATE-1))
275 		{
276 			cutscene_textcount = (INT32)((UINT8)*c - 0xAF);
277 			numtowrite = 0;
278 			continue;
279 		}
280 
281 		cutscene_disptext[cutscene_writeptr++] = *c;
282 
283 		// Ignore other control codes (color)
284 		if ((UINT8)*c < 0x80)
285 			--numtowrite;
286 	}
287 	// Reset textcount for next tic based on speed
288 	// if it wasn't already set by a delay.
289 	if (cutscene_textcount < 0)
290 	{
291 		cutscene_textcount = 0;
292 		if (cutscene_textspeed > 7)
293 			cutscene_textcount = cutscene_textspeed - 7;
294 	}
295 	return 1;
296 }
297 
F_NewCutscene(const char * basetext)298 static void F_NewCutscene(const char *basetext)
299 {
300 	cutscene_basetext = basetext;
301 	memset(cutscene_disptext,0,sizeof(cutscene_disptext));
302 	cutscene_writeptr = cutscene_baseptr = 0;
303 	cutscene_textspeed = 9;
304 	cutscene_textcount = TICRATE/2;
305 }
306 
307 // =============
308 //  INTRO SCENE
309 // =============
310 #define NUMINTROSCENES 17
311 INT32 intro_scenenum = 0;
312 INT32 intro_curtime = 0;
313 
314 const char *introtext[NUMINTROSCENES];
315 
316 static tic_t introscenetime[NUMINTROSCENES] =
317 {
318 	5*TICRATE,	// STJr Presents
319 	11*TICRATE + (TICRATE/2),	// Two months had passed since...
320 	15*TICRATE + (TICRATE/2),	// As it was about to drain the rings...
321 	14*TICRATE,					// What Sonic, Tails, and Knuckles...
322 	18*TICRATE,					// About once every year, a strange...
323 	19*TICRATE + (TICRATE/2),	// Curses! Eggman yelled. That ridiculous...
324 	19*TICRATE + (TICRATE/4),	// It was only later that he had an idea...
325 	10*TICRATE + (TICRATE/2),	// Before beginning his scheme, Eggman decided to give Sonic...
326 	16*TICRATE,					// We're ready to fire in 15 seconds, the robot said...
327 	16*TICRATE,					// Meanwhile, Sonic was tearing across the zones...
328 	16*TICRATE + (TICRATE/2),	// Sonic knew he was getting closer to the city...
329 	17*TICRATE,					// Greenflower City was gone...
330 	 7*TICRATE,					// You're not quite as dead as we thought, huh?...
331 	 8*TICRATE,					// We'll see... let's give you a quick warm up...
332 	18*TICRATE + (TICRATE/2),	// Eggman took this as his cue and blasted off...
333 	16*TICRATE,					// Easy! We go find Eggman and stop his...
334 	25*TICRATE,					// I'm just finding what mission obje...
335 };
336 
337 // custom intros
338 void F_StartCustomCutscene(INT32 cutscenenum, boolean precutscene, boolean resetplayer);
339 
F_StartIntro(void)340 void F_StartIntro(void)
341 {
342 	S_StopMusic();
343 	S_StopSounds();
344 
345 	if (introtoplay)
346 	{
347 		if (!cutscenes[introtoplay - 1])
348 			D_StartTitle();
349 		else
350 			F_StartCustomCutscene(introtoplay - 1, false, false);
351 		return;
352 	}
353 
354 	introtext[0] = " #";
355 
356 	introtext[1] = M_GetText(
357 	"Two months had passed since Dr. Eggman\n"
358 	"tried to take over the world using his\n"
359 	"Ring Satellite.\n#");
360 
361 	introtext[2] = M_GetText(
362 	"As it was about to drain the rings\n"
363 	"away from the planet, Sonic burst into\n"
364 	"the control room and for what he thought\n"
365 	"would be the last time,\xB4 defeated\n"
366 	"Dr. Eggman.\n#");
367 
368 	introtext[3] = M_GetText(
369 	"\nWhat Sonic, Tails, and Knuckles had\n"
370 	"not anticipated was that Eggman would\n"
371 	"return,\xB8 bringing an all new threat.\n#");
372 
373 	introtext[4] = M_GetText(
374 	"\xA8""About every five years, a strange asteroid\n"
375 	"hovers around the planet.\xBF It suddenly\n"
376 	"appears from nowhere, circles around, and\n"
377 	"\xB6- just as mysteriously as it arrives -\xB6\n"
378 	"vanishes after only one week.\xBF\n"
379 	"No one knows why it appears, or how.\n#");
380 
381 	introtext[5] = M_GetText(
382 	"\xA7\"Curses!\"\xA9\xBA Eggman yelled. \xA7\"That hedgehog\n"
383 	"and his ridiculous friends will pay\n"
384 	"dearly for this!\"\xA9\xC8 Just then his scanner\n"
385 	"blipped as the Black Rock made its\n"
386 	"appearance from nowhere.\xBF Eggman looked at\n"
387 	"the screen, and just shrugged it off.\n#");
388 
389 	introtext[6] = M_GetText(
390 	"It was hours later\n"
391 	"that he had an\n"
392 	"idea. \xBF\xA7\"The Black\n"
393 	"Rock has a large\n"
394 	"amount of energy\n"
395 	"within it\xAC...\xA7\xBF\n"
396 	"If I can somehow\n"
397 	"harness this,\xB8 I\n"
398 	"can turn it into\n"
399 	"the ultimate\n"
400 	"battle station\xAC...\xA7\xBF\n"
401 	"And every last\n"
402 	"person will be\n"
403 	"begging for mercy,\xB8\xA8\n"
404 	"including Sonic!\"\n#");
405 
406 	introtext[7] = M_GetText(
407 	"\xA8\nBefore beginning his scheme,\n"
408 	"Eggman decided to give Sonic\n"
409 	"a reunion party...\n#");
410 
411 	introtext[8] = M_GetText(
412 	"\xA5\"PRE-""\xB6""PARING-""\xB6""TO-""\xB4""FIRE-\xB6IN-""\xB6""15-""\xB6""SECONDS!\"\xA8\xB8\n"
413 	"his targeting system crackled\n"
414 	"robotically down the com-link. \xBF\xA7\"Good!\"\xA8\xB8\n"
415 	"Eggman sat back in his eggmobile and\n"
416 	"began to count down as he saw the\n"
417 	"Greenflower mountain on the monitor.\n#");
418 
419 	introtext[9] = M_GetText(
420 	"\xA5\"10...\xD2""9...\xD2""8...\"\xA8\xD2\n"
421 	"Meanwhile, Sonic was tearing across the\n"
422 	"zones. Everything became a blur as he\n"
423 	"ran up slopes, skimmed over water,\n"
424 	"and catapulted himself off rocks with\n"
425 	"his phenomenal speed.\n#");
426 
427 	introtext[10] = M_GetText(
428 	"\xA5\"6...\xD2""5...\xD2""4...\"\xA8\xD2\n"
429 	"Sonic knew he was getting closer to the\n"
430 	"zone, and pushed himself harder.\xB4 Finally,\n"
431 	"the mountain appeared on the horizon.\xD2\xD2\n"
432 	"\xA5\"3...\xD2""2...\xD2""1...\xD2""Zero.\"\n#");
433 
434 	introtext[11] = M_GetText(
435 	"Greenflower Mountain was no more.\xC4\n"
436 	"Sonic arrived just in time to see what\n"
437 	"little of the 'ruins' were left.\n"
438 	"The natural beauty of the zone\n"
439 	"had been obliterated.\n#");
440 
441 	introtext[12] = M_GetText(
442 	"\xA7\"You're not\n"
443 	"quite as gone\n"
444 	"as we thought,\n"
445 	"huh?\xBF Are you\n"
446 	"going to tell\n"
447 	"us your plan as\n"
448 	"usual or will I\n"
449 	"\xA8\xB4'have to work\n"
450 	"it out'\xA7 or\n"
451 	"something?\"\xD2\xD2\n#");
452 
453 	introtext[13] = M_GetText(
454 	"\"We'll see\xAA...\xA7\xBF let's give you a quick warm\n"
455 	"up, Sonic!\xA6\xC4 JETTYSYNS!\xA7\xBD Open fire!\"\n#");
456 
457 	introtext[14] = M_GetText(
458 	"Eggman took this\n"
459 	"as his cue and\n"
460 	"blasted off,\n"
461 	"leaving Sonic\n"
462 	"and Tails behind.\xB6\n"
463 	"Tails looked at\n"
464 	"the once-perfect\n"
465 	"mountainside\n"
466 	"with a grim face\n"
467 	"and sighed.\xC6\n"
468 	"\xA7\"Now\xB6 what do we\n"
469 	"do?\",\xA9 he asked.\n#");
470 
471 	introtext[15] = M_GetText(
472 	"\xA7\"Easy!\xBF We go\n"
473 	"find Eggman\n"
474 	"and stop his\n"
475 	"latest\n"
476 	"insane plan.\xBF\n"
477 	"Just like\n"
478 	"we've always\n"
479 	"done,\xBA right?\xD2\n\n"
480 	"\xAE...\xA9\xD2\n\n"
481 	"\"Tails, what\n"
482 	"\xAA*ARE*\xA9 you\n"
483 	"doing?\"\n#");
484 
485 	introtext[16] = M_GetText(
486 	"\xA8\"I'm just finding what mission obje\xAC\xB1...\xBF\n"
487 	"\xA6""a-\xB8""ha!\xBF Here it is!\xA8\xBF This will only give us\n"
488 	"the robot's primary objective.\xBF It says\xAC\xB1...\"\n"
489 	"\xD2\xA3\x83"
490 	"* LOCATE  AND  RETRIEVE:  CHAOS  EMERALDS *"
491 	"\xBF\n"
492 	"*  CLOSEST  LOCATION:  GREENFLOWER  ZONE  *"
493 	"\x80\n\xA9\xD2\xD2"
494 	"\"All right, then\xAF... \xD2\xD2\xA7let's go!\"\n#");
495 
496 /*
497 	"What are we waiting for? The first emerald is ours!" Sonic was about to
498 	run, when he saw a shadow pass over him, he recognized the silhouette
499 	instantly.
500 	"Knuckles!" Sonic said. The echidna stopped his glide and landed
501 	facing Sonic. "What are you doing here?"
502 	He replied, "This crisis affects the Floating Island,
503 	if that explosion I saw is anything to go by."
504 	If you're willing to help then... let's go!"
505 */
506 
507 	G_SetGamestate(GS_INTRO);
508 	gameaction = ga_nothing;
509 	paused = false;
510 	CON_ToggleOff();
511 	F_NewCutscene(introtext[0]);
512 
513 	intro_scenenum = 0;
514 	finalecount = animtimer = skullAnimCounter = stoptimer = 0;
515 	timetonext = introscenetime[intro_scenenum];
516 }
517 
518 //
519 // F_IntroDrawScene
520 //
F_IntroDrawScene(void)521 static void F_IntroDrawScene(void)
522 {
523 	boolean highres = true;
524 	INT32 cx = 8, cy = 128;
525 	patch_t *background = NULL;
526 	INT32 bgxoffs = 0;
527 	void *patch;
528 
529 	// DRAW A FULL PIC INSTEAD OF FLAT!
530 	switch (intro_scenenum)
531 	{
532 		case 0:
533 			bgxoffs = 28;
534 			break;
535 		case 1:
536 			background = W_CachePatchName("INTRO1", PU_PATCH_LOWPRIORITY);
537 			break;
538 		case 2:
539 			background = W_CachePatchName("INTRO2", PU_PATCH_LOWPRIORITY);
540 			break;
541 		case 3:
542 			background = W_CachePatchName("INTRO3", PU_PATCH_LOWPRIORITY);
543 			break;
544 		case 4:
545 			background = W_CachePatchName("INTRO4", PU_PATCH_LOWPRIORITY);
546 			break;
547 		case 5:
548 			if (intro_curtime >= 5*TICRATE)
549 				background = W_CachePatchName("RADAR", PU_PATCH_LOWPRIORITY);
550 			else
551 				background = W_CachePatchName("DRAT", PU_PATCH_LOWPRIORITY);
552 			break;
553 		case 6:
554 			background = W_CachePatchName("INTRO6", PU_PATCH_LOWPRIORITY);
555 			cx = 180;
556 			cy = 8;
557 			break;
558 		case 7:
559 		{
560 			if (intro_curtime >= 7*TICRATE + ((TICRATE/7)*2))
561 				background = W_CachePatchName("SGRASS5", PU_PATCH_LOWPRIORITY);
562 			else if (intro_curtime >= 7*TICRATE + (TICRATE/7))
563 				background = W_CachePatchName("SGRASS4", PU_PATCH_LOWPRIORITY);
564 			else if (intro_curtime >= 7*TICRATE)
565 				background = W_CachePatchName("SGRASS3", PU_PATCH_LOWPRIORITY);
566 			else if (intro_curtime >= 6*TICRATE)
567 				background = W_CachePatchName("SGRASS2", PU_PATCH_LOWPRIORITY);
568 			else
569 				background = W_CachePatchName("SGRASS1", PU_PATCH_LOWPRIORITY);
570 			break;
571 		}
572 		case 8:
573 			background = W_CachePatchName("WATCHING", PU_PATCH_LOWPRIORITY);
574 			break;
575 		case 9:
576 			background = W_CachePatchName("ZOOMING", PU_PATCH_LOWPRIORITY);
577 			break;
578 		case 10:
579 			break;
580 		case 11:
581 			background = W_CachePatchName("INTRO5", PU_PATCH_LOWPRIORITY);
582 			break;
583 		case 12:
584 			background = W_CachePatchName("REVENGE", PU_PATCH_LOWPRIORITY);
585 			cx = 208;
586 			cy = 8;
587 			break;
588 		case 13:
589 			background = W_CachePatchName("CONFRONT", PU_PATCH_LOWPRIORITY);
590 			cy += 48;
591 			break;
592 		case 14:
593 			background = W_CachePatchName("TAILSSAD", PU_PATCH_LOWPRIORITY);
594 			bgxoffs = 144;
595 			cx = 8;
596 			cy = 8;
597 			break;
598 		case 15:
599 			if (intro_curtime >= 7*TICRATE)
600 				background = W_CachePatchName("SONICDO2", PU_PATCH_LOWPRIORITY);
601 			else
602 				background = W_CachePatchName("SONICDO1", PU_PATCH_LOWPRIORITY);
603 			cx = 224;
604 			cy = 8;
605 			break;
606 		case 16:
607 			background = W_CachePatchName("INTRO7", PU_PATCH_LOWPRIORITY);
608 			break;
609 		default:
610 			break;
611 	}
612 
613 	V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 31);
614 
615 	if (background)
616 	{
617 		if (highres)
618 			V_DrawSmallScaledPatch(bgxoffs, 0, 0, background);
619 		else
620 			V_DrawScaledPatch(bgxoffs, 0, 0, background);
621 	}
622 	else if (intro_scenenum == 0) // STJr presents
623 	{
624 		if (intro_curtime > 1 && intro_curtime < (INT32)introscenetime[intro_scenenum])
625 		{
626 			V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 31);
627 			if (intro_curtime < TICRATE-5) // Make the text shine!
628 				sprintf(stjrintro, "STJRI%03u", intro_curtime-1);
629 			else if (intro_curtime >= TICRATE-6 && intro_curtime < 2*TICRATE-20) // Pause on black screen for just a second
630 				return;
631 			else if (intro_curtime == 2*TICRATE-19)
632 			{
633 				// Fade in the text
634 				// The text fade out is automatically handled when switching to a new intro scene
635 				strncpy(stjrintro, "STJRI029", 9);
636 				S_ChangeMusicInternal("_stjr", false);
637 
638 				background = W_CachePatchName(stjrintro, PU_PATCH_LOWPRIORITY);
639 				wipestyleflags = WSF_FADEIN;
640 				F_WipeStartScreen();
641 				F_TryColormapFade(31);
642 				V_DrawSmallScaledPatch(bgxoffs, 84, 0, background);
643 				F_WipeEndScreen();
644 				F_RunWipe(0,true);
645 			}
646 
647 			if (!WipeInAction) // Draw the patch if not in a wipe
648 			{
649 				background = W_CachePatchName(stjrintro, PU_PATCH_LOWPRIORITY);
650 				V_DrawSmallScaledPatch(bgxoffs, 84, 0, background);
651 			}
652 		}
653 	}
654 	else if (intro_scenenum == 10) // Sky Runner
655 	{
656 		if (timetonext > 5*TICRATE && timetonext < 6*TICRATE)
657 		{
658 			if (!(finalecount & 3))
659 				background = W_CachePatchName("BRITEGG1", PU_PATCH_LOWPRIORITY);
660 			else
661 				background = W_CachePatchName("DARKEGG1", PU_PATCH_LOWPRIORITY);
662 
663 			V_DrawSmallScaledPatch(0, 0, 0, background);
664 		}
665 		else if (timetonext > 3*TICRATE && timetonext < 4*TICRATE)
666 		{
667 			if (!(finalecount & 3))
668 				background = W_CachePatchName("BRITEGG2", PU_PATCH_LOWPRIORITY);
669 			else
670 				background = W_CachePatchName("DARKEGG2", PU_PATCH_LOWPRIORITY);
671 
672 			V_DrawSmallScaledPatch(0, 0, 0, background);
673 		}
674 		else if (timetonext > 1*TICRATE && timetonext < 2*TICRATE)
675 		{
676 			if (!(finalecount & 3))
677 				background = W_CachePatchName("BRITEGG3", PU_PATCH_LOWPRIORITY);
678 			else
679 				background = W_CachePatchName("DARKEGG3", PU_PATCH_LOWPRIORITY);
680 
681 			V_DrawSmallScaledPatch(0, 0, 0, background);
682 		}
683 		else
684 		{
685 			tic_t sonicdelay = max(0, timetonext - 16*TICRATE);
686 			tic_t tailsdelay = max(0, timetonext - (9*TICRATE >> 1));
687 			tic_t knucklesdelay = max(0, timetonext - (5*TICRATE >> 1));
688 			INT32 sonicx = (timetonext >> 2) + min(sonicdelay, TICRATE >> 1) * sonicdelay;
689 			INT32 tailsx = 32 + min(tailsdelay, TICRATE >> 1) * tailsdelay;
690 			INT32 knucklesx = 96 + min(knucklesdelay, TICRATE >> 1) * knucklesdelay;
691 			INT32 tailsy = 12 + P_ReturnThrustX(NULL, finalecount * ANGLE_22h, 2);
692 			INT32 knucklesy = 48 - (timetonext >> 3);
693 			INT32 skyx, grassx;
694 
695 			if (timetonext >= 0 && timetonext < 18)
696 			{
697 				deplete -= 16;
698 			}
699 			else
700 			{
701 				stoptimer = finalecount;
702 				deplete = 96;
703 			}
704 			skyx = 2 * stoptimer % 320;
705 			grassx = 16 * stoptimer % 320;
706 			sonicx += deplete;
707 			tailsx += sonicx;
708 			knucklesx += sonicx;
709 			sonicx += P_ReturnThrustX(NULL, finalecount * ANG10, 3);
710 
711 			V_DrawSmallScaledPatch(skyx, 0, 0, (patch = W_CachePatchName("INTROSKY", PU_PATCH_LOWPRIORITY)));
712 			V_DrawSmallScaledPatch(skyx - 320, 0, 0, patch);
713 			W_UnlockCachedPatch(patch);
714 			V_DrawSmallScaledPatch(grassx, 0, 0, (patch = W_CachePatchName("INTROGRS", PU_PATCH_LOWPRIORITY)));
715 			V_DrawSmallScaledPatch(grassx - 320, 0, 0, patch);
716 			W_UnlockCachedPatch(patch);
717 
718 			if (finalecount & 1)
719 			{
720 				// Sonic
721 				V_DrawSmallScaledPatch(sonicx, 54, 0, (patch = W_CachePatchName("RUN2", PU_PATCH_LOWPRIORITY)));
722 				W_UnlockCachedPatch(patch);
723 
724 				// Appendages
725 				if (finalecount & 2)
726 				{
727 					// Sonic's feet
728 					V_DrawSmallScaledPatch(sonicx - 8, 92, 0, (patch = W_CachePatchName("PEELOUT4", PU_PATCH_LOWPRIORITY)));
729 					W_UnlockCachedPatch(patch);
730 					// Tails' tails
731 					V_DrawSmallScaledPatch(tailsx, tailsy, 0, (patch = W_CachePatchName("HELICOP2", PU_PATCH_LOWPRIORITY)));
732 					W_UnlockCachedPatch(patch);
733 				}
734 				else
735 				{
736 					// Sonic's feet
737 					V_DrawSmallScaledPatch(sonicx - 8, 92, 0, (patch = W_CachePatchName("PEELOUT2", PU_PATCH_LOWPRIORITY)));
738 					W_UnlockCachedPatch(patch);
739 					// Tails' tails
740 					V_DrawSmallScaledPatch(tailsx, tailsy, 0, (patch = W_CachePatchName("HELICOP1", PU_PATCH_LOWPRIORITY)));
741 					W_UnlockCachedPatch(patch);
742 				}
743 
744 				// Tails
745 				V_DrawSmallScaledPatch(tailsx, tailsy, 0, (patch = W_CachePatchName("FLY2", PU_PATCH_LOWPRIORITY)));
746 				W_UnlockCachedPatch(patch);
747 
748 				// Knuckles
749 				V_DrawSmallScaledPatch(knucklesx, knucklesy, 0, (patch = W_CachePatchName("GLIDE2", PU_PATCH_LOWPRIORITY)));
750 				W_UnlockCachedPatch(patch);
751 			}
752 			else
753 			{
754 				// Sonic
755 				V_DrawSmallScaledPatch(sonicx, 54, 0, (patch = W_CachePatchName("RUN1", PU_PATCH_LOWPRIORITY)));
756 				W_UnlockCachedPatch(patch);
757 
758 				// Appendages
759 				if (finalecount & 2)
760 				{
761 					// Sonic's feet
762 					V_DrawSmallScaledPatch(sonicx - 8, 92, 0, (patch = W_CachePatchName("PEELOUT3", PU_PATCH_LOWPRIORITY)));
763 					W_UnlockCachedPatch(patch);
764 					// Tails' tails
765 					V_DrawSmallScaledPatch(tailsx, tailsy, 0, (patch = W_CachePatchName("HELICOP2", PU_PATCH_LOWPRIORITY)));
766 					W_UnlockCachedPatch(patch);
767 				}
768 				else
769 				{
770 					// Sonic's feet
771 					V_DrawSmallScaledPatch(sonicx - 8, 92, 0, (patch = W_CachePatchName("PEELOUT1", PU_PATCH_LOWPRIORITY)));
772 					W_UnlockCachedPatch(patch);
773 					// Tails' tails
774 					V_DrawSmallScaledPatch(tailsx, tailsy, 0, (patch = W_CachePatchName("HELICOP1", PU_PATCH_LOWPRIORITY)));
775 					W_UnlockCachedPatch(patch);
776 				}
777 
778 				// Tails
779 				V_DrawSmallScaledPatch(tailsx, tailsy, 0, (patch = W_CachePatchName("FLY1", PU_PATCH_LOWPRIORITY)));
780 				W_UnlockCachedPatch(patch);
781 
782 				// Knuckles
783 				V_DrawSmallScaledPatch(knucklesx, knucklesy, 0, (patch = W_CachePatchName("GLIDE1", PU_PATCH_LOWPRIORITY)));
784 				W_UnlockCachedPatch(patch);
785 			}
786 
787 			// Black bars to hide the sky on widescreen
788 			V_DrawFill(-80, 0, 80, 256, 31);
789 			V_DrawFill(BASEVIDWIDTH, 0, 80, 256, 31);
790 		}
791 	}
792 
793 	W_UnlockCachedPatch(background);
794 
795 	if (intro_scenenum == 4) // The asteroid SPINS!
796 	{
797 		if (intro_curtime > 1)
798 		{
799 			INT32 worktics = intro_curtime - 1;
800 			INT32 scale = FRACUNIT;
801 			patch_t *rockpat;
802 			UINT8 *colormap = NULL;
803 			patch_t *glow;
804 			INT32 trans = 0;
805 
806 			INT32 x = ((BASEVIDWIDTH - 64)<<FRACBITS) - ((intro_curtime*FRACUNIT)/3);
807 			INT32 y = 24<<FRACBITS;
808 
809 			if (worktics < 5)
810 			{
811 				scale = (worktics<<(FRACBITS-2));
812 				x += (30*(FRACUNIT-scale));
813 				y += (30*(FRACUNIT-scale));
814 			}
815 
816 			rockpat = W_CachePatchName(va("ROID00%.2d", 34 - (worktics % 35)), PU_PATCH_LOWPRIORITY);
817 			glow = W_CachePatchName(va("ENDGLOW%.1d", 2+(worktics & 1)), PU_PATCH_LOWPRIORITY);
818 
819 			if (worktics >= 5)
820 				trans = (worktics-5)>>1;
821 			if (trans < 10)
822 				V_DrawFixedPatch(x, y, scale, trans<<V_ALPHASHIFT, glow, NULL);
823 
824 			trans = (15-worktics);
825 			if (trans < 0)
826 				trans = -trans;
827 
828 			if (finalecount < 15)
829 				colormap = R_GetTranslationColormap(TC_ALLWHITE, 0, GTC_CACHE);
830 			V_DrawFixedPatch(x, y, scale, 0, rockpat, colormap);
831 			if (trans < 10)
832 				V_DrawFixedPatch(x, y, scale, trans<<V_ALPHASHIFT, rockpat, R_GetTranslationColormap(TC_BLINK, SKINCOLOR_AQUA, GTC_CACHE));
833 		}
834 	}
835 	else if (intro_scenenum == 1 && intro_curtime < 5*TICRATE)
836 	{
837 		INT32 trans = intro_curtime + 10 - (5*TICRATE);
838 		if (trans < 0)
839 			trans = 0;
840 		V_DrawRightAlignedString(BASEVIDWIDTH-4, BASEVIDHEIGHT-12, V_ALLOWLOWERCASE|(trans<<V_ALPHASHIFT), "\x86""Press ""\x82""ENTER""\x86"" to skip...");
841 	}
842 
843 	if (animtimer)
844 		animtimer--;
845 
846 	V_DrawString(cx, cy, V_ALLOWLOWERCASE, cutscene_disptext);
847 }
848 
849 //
850 // F_IntroDrawer
851 //
F_IntroDrawer(void)852 void F_IntroDrawer(void)
853 {
854 	if (timetonext <= 0)
855 	{
856 		if (intro_scenenum == 0)
857 		{
858 			if (rendermode != render_none)
859 			{
860 				wipestyleflags = WSF_FADEOUT;
861 				F_WipeStartScreen();
862 				F_TryColormapFade(31);
863 				F_WipeEndScreen();
864 				F_RunWipe(99,true);
865 			}
866 
867 			S_ChangeMusicInternal("_intro", false);
868 		}
869 		else if (intro_scenenum == 10)
870 		{
871 			if (rendermode != render_none)
872 			{
873 				wipestyleflags = (WSF_FADEOUT|WSF_TOWHITE);
874 				F_WipeStartScreen();
875 				F_TryColormapFade(0);
876 				F_WipeEndScreen();
877 				F_RunWipe(99,true);
878 			}
879 		}
880 		else if (intro_scenenum == 16)
881 		{
882 			if (rendermode != render_none)
883 			{
884 				wipestyleflags = WSF_FADEOUT;
885 				F_WipeStartScreen();
886 				F_TryColormapFade(31);
887 				F_WipeEndScreen();
888 				F_RunWipe(99,true);
889 			}
890 
891 			// Stay on black for a bit. =)
892 			{
893 				tic_t nowtime, quittime, lasttime;
894 				nowtime = lasttime = I_GetTime();
895 				quittime = nowtime + NEWTICRATE*2; // Shortened the quit time, used to be 2 seconds
896 				while (quittime > nowtime)
897 				{
898 					while (!((nowtime = I_GetTime()) - lasttime))
899 						I_Sleep();
900 					lasttime = nowtime;
901 
902 					I_OsPolling();
903 					I_UpdateNoBlit();
904 #ifdef HAVE_THREADS
905 					I_lock_mutex(&m_menu_mutex);
906 #endif
907 					M_Drawer(); // menu is drawn even on top of wipes
908 #ifdef HAVE_THREADS
909 					I_unlock_mutex(m_menu_mutex);
910 #endif
911 					I_FinishUpdate(); // Update the screen with the image Tails 06-19-2001
912 
913 					if (moviemode) // make sure we save frames for the white hold too
914 						M_SaveFrame();
915 				}
916 			}
917 
918 			D_StartTitle();
919 			wipegamestate = GS_INTRO;
920 			return;
921 		}
922 		F_NewCutscene(introtext[++intro_scenenum]);
923 		timetonext = introscenetime[intro_scenenum];
924 
925 		F_WipeStartScreen();
926 		wipegamestate = -1;
927 		wipestyleflags = WSF_CROSSFADE;
928 		animtimer = stoptimer = 0;
929 	}
930 
931 	intro_curtime = introscenetime[intro_scenenum] - timetonext;
932 
933 	if (rendermode != render_none)
934 	{
935 		if (intro_scenenum == 5 && intro_curtime == 5*TICRATE)
936 		{
937 			patch_t *radar = W_CachePatchName("RADAR", PU_PATCH_LOWPRIORITY);
938 
939 			F_WipeStartScreen();
940 			F_WipeColorFill(31);
941 			V_DrawSmallScaledPatch(0, 0, 0, radar);
942 			W_UnlockCachedPatch(radar);
943 			V_DrawString(8, 128, V_ALLOWLOWERCASE, cutscene_disptext);
944 
945 			F_WipeEndScreen();
946 			F_RunWipe(99,true);
947 		}
948 		else if (intro_scenenum == 7 && intro_curtime == 6*TICRATE) // Force a wipe here
949 		{
950 			patch_t *grass = W_CachePatchName("SGRASS2", PU_PATCH_LOWPRIORITY);
951 
952 			F_WipeStartScreen();
953 			F_WipeColorFill(31);
954 			V_DrawSmallScaledPatch(0, 0, 0, grass);
955 			W_UnlockCachedPatch(grass);
956 			V_DrawString(8, 128, V_ALLOWLOWERCASE, cutscene_disptext);
957 
958 			F_WipeEndScreen();
959 			F_RunWipe(99,true);
960 		}
961 		/*else if (intro_scenenum == 11 && intro_curtime == 7*TICRATE)
962 		{
963 			patch_t *confront = W_CachePatchName("CONFRONT", PU_PATCH_LOWPRIORITY);
964 
965 			F_WipeStartScreen();
966 			F_WipeColorFill(31);
967 			V_DrawSmallScaledPatch(0, 0, 0, confront);
968 			W_UnlockCachedPatch(confront);
969 			V_DrawString(8, 128, V_ALLOWLOWERCASE, cutscene_disptext);
970 
971 			F_WipeEndScreen();
972 			F_RunWipe(99,true);
973 		}*/
974 		if (intro_scenenum == 15 && intro_curtime == 7*TICRATE)
975 		{
976 			patch_t *sdo = W_CachePatchName("SONICDO2", PU_PATCH_LOWPRIORITY);
977 
978 			F_WipeStartScreen();
979 			F_WipeColorFill(31);
980 			V_DrawSmallScaledPatch(0, 0, 0, sdo);
981 			W_UnlockCachedPatch(sdo);
982 			V_DrawString(224, 8, V_ALLOWLOWERCASE, cutscene_disptext);
983 
984 			F_WipeEndScreen();
985 			F_RunWipe(99,true);
986 		}
987 	}
988 
989 	F_IntroDrawScene();
990 }
991 
992 //
993 // F_IntroTicker
994 //
F_IntroTicker(void)995 void F_IntroTicker(void)
996 {
997 	// advance animation
998 	finalecount++;
999 
1000 	timetonext--;
1001 
1002 	F_WriteText();
1003 
1004 	// check for skipping
1005 	if (keypressed)
1006 		keypressed = false;
1007 }
1008 
1009 //
1010 // F_IntroResponder
1011 //
F_IntroResponder(event_t * event)1012 boolean F_IntroResponder(event_t *event)
1013 {
1014 	INT32 key = event->data1;
1015 
1016 	// remap virtual keys (mouse & joystick buttons)
1017 	switch (key)
1018 	{
1019 		case KEY_MOUSE1:
1020 			key = KEY_ENTER;
1021 			break;
1022 		case KEY_MOUSE1 + 1:
1023 			key = KEY_BACKSPACE;
1024 			break;
1025 		case KEY_JOY1:
1026 		case KEY_JOY1 + 2:
1027 			key = KEY_ENTER;
1028 			break;
1029 		case KEY_JOY1 + 3:
1030 			key = 'n';
1031 			break;
1032 		case KEY_JOY1 + 1:
1033 			key = KEY_BACKSPACE;
1034 			break;
1035 		case KEY_HAT1:
1036 			key = KEY_UPARROW;
1037 			break;
1038 		case KEY_HAT1 + 1:
1039 			key = KEY_DOWNARROW;
1040 			break;
1041 		case KEY_HAT1 + 2:
1042 			key = KEY_LEFTARROW;
1043 			break;
1044 		case KEY_HAT1 + 3:
1045 			key = KEY_RIGHTARROW;
1046 			break;
1047 	}
1048 
1049 	if (event->type != ev_keydown && key != 301)
1050 		return false;
1051 
1052 	if (key != 27 && key != KEY_ENTER && key != KEY_SPACE && key != KEY_BACKSPACE)
1053 		return false;
1054 
1055 	if (keypressed)
1056 		return false;
1057 
1058 	keypressed = true;
1059 	return true;
1060 }
1061 
1062 // =========
1063 //  CREDITS
1064 // =========
1065 static const char *credits[] = {
1066 	"\1Sonic Robo Blast II",
1067 	"\1Credits",
1068 	"",
1069 	"\1Game Design",
1070 	"Sonic Team Junior",
1071 	"\"SSNTails\"",
1072 	"Johnny \"Sonikku\" Wallbank",
1073 	"",
1074 	"\1Programming",
1075 	"Alam \"GBC\" Arias",
1076 	"Logan \"GBA\" Arias",
1077 	"Zolton \"Zippy_Zolton\" Auburn",
1078 	"Colette \"fickleheart\" Bordelon",
1079 	"Andrew \"orospakr\" Clunis",
1080 	"Sally \"TehRealSalt\" Cochenour",
1081 	"Gregor \"Oogaland\" Dick",
1082 	"Callum Dickinson",
1083 	"Scott \"Graue\" Feeney",
1084 	"Victor \"SteelT\" Fuentes",
1085 	"Nathan \"Jazz\" Giroux",
1086 	"\"Golden\"",
1087 	"Vivian \"toaster\" Grannell",
1088 	"Julio \"Chaos Zero 64\" Guir",
1089 	"\"Hannu_Hanhi\"", // For many OpenGL performance improvements!
1090 	"Kepa \"Nev3r\" Iceta",
1091 	"Thomas \"Shadow Hog\" Igoe",
1092 	"\"james\"",
1093 	"Iestyn \"Monster Iestyn\" Jealous",
1094 	"\"Jimita\"",
1095 	"\"Kaito Sinclaire\"",
1096 	"\"Kalaron\"", // Coded some of Sryder13's collection of OpenGL fixes, especially fog
1097 	"Ronald \"Furyhunter\" Kinard", // The SDL2 port
1098 	"\"Lat'\"", // SRB2-CHAT, the chat window from Kart
1099 	"Matthew \"Shuffle\" Marsalko",
1100 	"Steven \"StroggOnMeth\" McGranahan",
1101 	"\"Morph\"", // For SRB2Morphed stuff
1102 	"Louis-Antoine \"LJ Sonic\" de Moulins", // de Rochefort doesn't quite fit on the screen sorry lol
1103 	"John \"JTE\" Muniz",
1104 	"Colin \"Sonict\" Pfaff",
1105 	"Sean \"Sryder13\" Ryder",
1106 	"Ehab \"Wolfy\" Saeed",
1107 	"Tasos \"tatokis\" Sahanidis", // Corrected C FixedMul, making 64-bit builds netplay compatible
1108 	"Riku \"Ors\" Salminen", // Demo consistency improvements
1109 	"Jonas \"MascaraSnake\" Sauer",
1110 	"Wessel \"sphere\" Smit",
1111 	"\"SSNTails\"",
1112 	"\"Varren\"",
1113 	"\"VelocitOni\"", // Wrote the original dashmode script
1114 	"Ikaro \"Tatsuru\" Vinhas",
1115 	"Ben \"Cue\" Woodford",
1116 	"Lachlan \"Lach\" Wright",
1117 	"Marco \"mazmazz\" Zafra",
1118 	"",
1119 	"\1Art",
1120 	"Victor \"VAdaPEGA\" Ara\x1Fjo", // Araújo -- sorry for our limited font! D:
1121 	"\"Arrietty\"",
1122 	"Ryan \"Blaze Hedgehog\" Bloom",
1123 	"Graeme P. \"SuperPhanto\" Caldwell", // for the new brak render
1124 	"\"ChrispyPixels\"",
1125 	"Paul \"Boinciel\" Clempson",
1126 	"Sally \"TehRealSalt\" Cochenour",
1127 	"\"Dave Lite\"",
1128 	"Desmond \"Blade\" DesJardins",
1129 	"Sherman \"CoatRack\" DesJardins",
1130 	"\"DirkTheHusky\"",
1131 	"Jesse \"Jeck Jims\" Emerick",
1132 	"\"Fighter_Builder\"", // for the CEZ3 button debris
1133 	"Buddy \"KinkaJoy\" Fischer",
1134 	"Vivian \"toaster\" Grannell",
1135 	"James \"SwitchKaze\" Hale",
1136 	"James \"SeventhSentinel\" Hall",
1137 	"Kepa \"Nev3r\" Iceta",
1138 	"Iestyn \"Monster Iestyn\" Jealous",
1139 	"William \"GuyWithThePie\" Kloppenberg",
1140 	"Alice \"Alacroix\" de Lemos",
1141 	"Logan \"Hyperchaotix\" McCloud",
1142 	"Alexander \"DrTapeworm\" Moench-Ford",
1143 	"Andrew \"Senku Niola\" Moran",
1144 	"\"MotorRoach\"",
1145 	"Phillip \"TelosTurntable\" Robinson",
1146 	"\"Scizor300\"",
1147 	"Wessel \"sphere\" Smit",
1148 	"David \"Instant Sonic\" Spencer Jr.",
1149 	"\"SSNTails\"",
1150 	"Daniel \"Inazuma\" Trinh",
1151 	"\"VelocitOni\"",
1152 	"Jarrett \"JEV3\" Voight",
1153 	"",
1154 	"\1Music and Sound",
1155 	"\1Production",
1156 	"Victor \"VAdaPEGA\" Ara\x1Fjo", // Araújo
1157 	"Malcolm \"RedXVI\" Brown",
1158 	"Dave \"DemonTomatoDave\" Bulmer",
1159 	"Paul \"Boinciel\" Clempson",
1160 	"\"Cyan Helkaraxe\"",
1161 	"Shane \"CobaltBW\" Ellis",
1162 	"James \"SeventhSentinel\" Hall",
1163 	"Kepa \"Nev3r\" Iceta",
1164 	"Iestyn \"Monster Iestyn\" Jealous",
1165 	"Jarel \"Arrow\" Jones",
1166 	"Alexander \"DrTapeworm\" Moench-Ford",
1167 	"Stefan \"Stuf\" Rimalia",
1168 	"Shane Mychal Sexton",
1169 	"\"Spazzo\"",
1170 	"David \"Big Wave Dave\" Spencer Sr.",
1171 	"David \"Instant Sonic\" Spencer Jr.",
1172 	"\"SSNTails\"",
1173 	"",
1174 	"\1Level Design",
1175 	"Colette \"fickleheart\" Bordelon",
1176 	"Hank \"FuriousFox\" Brannock",
1177 	"Matthew \"Fawfulfan\" Chapman",
1178 	"Paul \"Boinciel\" Clempson",
1179 	"Sally \"TehRealSalt\" Cochenour",
1180 	"Desmond \"Blade\" DesJardins",
1181 	"Sherman \"CoatRack\" DesJardins",
1182 	"Ben \"Mystic\" Geyer",
1183 	"Nathan \"Jazz\" Giroux",
1184 	"Vivian \"toaster\" Grannell",
1185 	"Dan \"Blitzzo\" Hagerstrand",
1186 	"James \"SeventhSentinel\" Hall",
1187 	"Kepa \"Nev3r\" Iceta",
1188 	"Thomas \"Shadow Hog\" Igoe",
1189 	"\"Kaito Sinclaire\"",
1190 	"Alexander \"DrTapeworm\" Moench-Ford",
1191 	"\"Revan\"",
1192 	"Anna \"QueenDelta\" Sandlin",
1193 	"Wessel \"sphere\" Smit",
1194 	"\"Spazzo\"",
1195 	"\"SSNTails\"",
1196 	"Rob Tisdell",
1197 	"\"Torgo\"",
1198 	"Jarrett \"JEV3\" Voight",
1199 	"Johnny \"Sonikku\" Wallbank",
1200 	"Marco \"mazmazz\" Zafra",
1201 	"",
1202 	"\1Boss Design",
1203 	"Ben \"Mystic\" Geyer",
1204 	"Vivian \"toaster\" Grannell",
1205 	"Thomas \"Shadow Hog\" Igoe",
1206 	"John \"JTE\" Muniz",
1207 	"Samuel \"Prime 2.0\" Peters",
1208 	"\"SSNTails\"",
1209 	"Johnny \"Sonikku\" Wallbank",
1210 	"",
1211 	"\1Testing",
1212 	"Discord Community Testers",
1213 	"Hank \"FuriousFox\" Brannock",
1214 	"Cody \"Playah\" Koester",
1215 	"Skye \"OmegaVelocity\" Meredith",
1216 	"Stephen \"HEDGESMFG\" Moellering",
1217 	"Rosalie \"ST218\" Molina",
1218 	"Samuel \"Prime 2.0\" Peters",
1219 	"Colin \"Sonict\" Pfaff",
1220 	"Bill \"Tets\" Reed",
1221 	"",
1222 	"\1Special Thanks",
1223 	"iD Software",
1224 	"Doom Legacy Project",
1225 	"FreeDoom Project", // Used some of the mancubus and rocket launcher sprites for Brak
1226 	"Kart Krew",
1227 	"Alex \"MistaED\" Fuller",
1228 	"Pascal \"CodeImp\" vd Heiden", // Doom Builder developer
1229 	"Randi Heit (<!>)", // For their MSPaint <!> sprite that we nicked
1230 	"Simon \"sirjuddington\" Judd", // SLADE developer
1231 	"SRB2 Community Contributors",
1232 	"",
1233 	"\1Produced By",
1234 	"Sonic Team Junior",
1235 	"",
1236 	"\1Published By",
1237 	"A 28K dialup modem",
1238 	"",
1239 	"\1Thank you       ",
1240 	"\1for playing!       ",
1241 	NULL
1242 };
1243 
1244 #define CREDITS_LEFT 8
1245 #define CREDITS_RIGHT ((BASEVIDWIDTH) - 8)
1246 
1247 static struct {
1248 	UINT32 x;
1249 	const char *patch;
1250 } credits_pics[] = {
1251 	{CREDITS_LEFT,                     "CREDIT01"},
1252 	{CREDITS_RIGHT - (271 >> 1),       "CREDIT02"},
1253 	{CREDITS_LEFT,                     "CREDIT03"},
1254 	{CREDITS_RIGHT - (316 >> 1),       "CREDIT04"},
1255 	{CREDITS_LEFT,                     "CREDIT05"},
1256 	{CREDITS_RIGHT - (399 >> 1),       "CREDIT06"},
1257 	{CREDITS_LEFT,                     "CREDIT07"},
1258 	{CREDITS_RIGHT - (302 >> 1),       "CREDIT08"},
1259 	{CREDITS_LEFT,                     "CREDIT09"},
1260 	{CREDITS_RIGHT - (250 >> 1),       "CREDIT10"},
1261 	{CREDITS_LEFT,                     "CREDIT11"},
1262 	{CREDITS_RIGHT - (279 >> 1),       "CREDIT12"},
1263 	//{(BASEVIDWIDTH - (279 >> 1)) >> 1, "CREDIT12"},
1264 	// CREDIT13 is extra art and is not shown by default
1265 	{0, NULL}
1266 };
1267 
1268 #undef CREDITS_LEFT
1269 #undef CREDITS_RIGHT
1270 
1271 static UINT32 credits_height = 0;
1272 static const UINT8 credits_numpics = sizeof(credits_pics)/sizeof(credits_pics[0]) - 1;
1273 
F_StartCredits(void)1274 void F_StartCredits(void)
1275 {
1276 	G_SetGamestate(GS_CREDITS);
1277 
1278 	// Just in case they're open ... somehow
1279 	M_ClearMenus(true);
1280 
1281 	if (creditscutscene)
1282 	{
1283 		F_StartCustomCutscene(creditscutscene - 1, false, false);
1284 		return;
1285 	}
1286 
1287 	gameaction = ga_nothing;
1288 	paused = false;
1289 	CON_ToggleOff();
1290 	S_StopMusic();
1291 	S_StopSounds();
1292 
1293 	S_ChangeMusicInternal("_creds", true);
1294 
1295 	finalecount = 0;
1296 	animtimer = 0;
1297 	timetonext = 2*TICRATE;
1298 }
1299 
F_CreditDrawer(void)1300 void F_CreditDrawer(void)
1301 {
1302 	UINT16 i;
1303 	INT16 zagpos = (timetonext - finalecount - animtimer) % 32;
1304 	fixed_t y = (80<<FRACBITS) - (animtimer<<FRACBITS>>1);
1305 
1306 	V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 31);
1307 
1308 	// Zig Zagz
1309 	V_DrawScaledPatch(-16,               zagpos,       V_SNAPTOLEFT,         W_CachePatchName("LTZIGZAG", PU_PATCH_LOWPRIORITY));
1310 	V_DrawScaledPatch(-16,               zagpos - 320, V_SNAPTOLEFT,         W_CachePatchName("LTZIGZAG", PU_PATCH_LOWPRIORITY));
1311 	V_DrawScaledPatch(BASEVIDWIDTH + 16, zagpos,       V_SNAPTORIGHT|V_FLIP, W_CachePatchName("LTZIGZAG", PU_PATCH_LOWPRIORITY));
1312 	V_DrawScaledPatch(BASEVIDWIDTH + 16, zagpos - 320, V_SNAPTORIGHT|V_FLIP, W_CachePatchName("LTZIGZAG", PU_PATCH_LOWPRIORITY));
1313 
1314 	// Draw background pictures first
1315 	for (i = 0; credits_pics[i].patch; i++)
1316 		V_DrawSciencePatch(credits_pics[i].x<<FRACBITS, (280<<FRACBITS) + (((i*credits_height)<<FRACBITS)/(credits_numpics)) - 4*(animtimer<<FRACBITS)/5, 0, W_CachePatchName(credits_pics[i].patch, PU_PATCH_LOWPRIORITY), FRACUNIT>>1);
1317 
1318 	// Dim the background
1319 	V_DrawFadeScreen(0xFF00, 16);
1320 
1321 	// Draw credits text on top
1322 	for (i = 0; credits[i]; i++)
1323 	{
1324 		switch(credits[i][0])
1325 		{
1326 		case 0:
1327 			y += 80<<FRACBITS;
1328 			break;
1329 		case 1:
1330 			if (y>>FRACBITS > -20)
1331 				V_DrawCreditString((160 - (V_CreditStringWidth(&credits[i][1])>>1))<<FRACBITS, y, 0, &credits[i][1]);
1332 			y += 30<<FRACBITS;
1333 			break;
1334 		case 2:
1335 			if (y>>FRACBITS > -10)
1336 				V_DrawStringAtFixed((BASEVIDWIDTH-V_StringWidth(&credits[i][1], V_ALLOWLOWERCASE|V_YELLOWMAP))<<FRACBITS>>1, y, V_ALLOWLOWERCASE|V_YELLOWMAP, &credits[i][1]);
1337 			y += 12<<FRACBITS;
1338 			break;
1339 		default:
1340 			if (y>>FRACBITS > -10)
1341 				V_DrawStringAtFixed(32<<FRACBITS, y, V_ALLOWLOWERCASE, credits[i]);
1342 			y += 12<<FRACBITS;
1343 			break;
1344 		}
1345 		if (FixedMul(y,vid.dupy) > vid.height)
1346 			break;
1347 	}
1348 }
1349 
F_CreditTicker(void)1350 void F_CreditTicker(void)
1351 {
1352 	// "Simulate" the drawing of the credits so that dedicated mode doesn't get stuck
1353 	UINT16 i;
1354 	fixed_t y = (80<<FRACBITS) - (animtimer<<FRACBITS>>1);
1355 
1356 	// Calculate credits height to display art properly
1357 	if (credits_height == 0)
1358 	{
1359 		for (i = 0; credits[i]; i++)
1360 		{
1361 			switch(credits[i][0])
1362 			{
1363 				case 0: credits_height += 80; break;
1364 				case 1: credits_height += 30; break;
1365 				default: credits_height += 12; break;
1366 			}
1367 		}
1368 		credits_height = 131*credits_height/80; // account for scroll speeds. This is a guess now, so you may need to update this if you change the credits length.
1369 	}
1370 
1371 	// Draw credits text on top
1372 	for (i = 0; credits[i]; i++)
1373 	{
1374 		switch(credits[i][0])
1375 		{
1376 			case 0: y += 80<<FRACBITS; break;
1377 			case 1: y += 30<<FRACBITS; break;
1378 			default: y += 12<<FRACBITS; break;
1379 		}
1380 		if (FixedMul(y,vid.dupy) > vid.height)
1381 			break;
1382 	}
1383 
1384 	// Do this here rather than in the drawer you doofus! (this is why dedicated mode broke at credits)
1385 	if (!credits[i] && y <= 120<<FRACBITS && !finalecount)
1386 	{
1387 		timetonext = 5*TICRATE+1;
1388 		finalecount = 5*TICRATE;
1389 	}
1390 
1391 	if (timetonext)
1392 		timetonext--;
1393 	else
1394 		animtimer++;
1395 
1396 	if (finalecount && --finalecount == 0)
1397 		F_StartGameEvaluation();
1398 }
1399 
F_CreditResponder(event_t * event)1400 boolean F_CreditResponder(event_t *event)
1401 {
1402 	INT32 key = event->data1;
1403 
1404 	// remap virtual keys (mouse & joystick buttons)
1405 	switch (key)
1406 	{
1407 		case KEY_MOUSE1:
1408 			key = KEY_ENTER;
1409 			break;
1410 		case KEY_MOUSE1 + 1:
1411 			key = KEY_BACKSPACE;
1412 			break;
1413 		case KEY_JOY1:
1414 		case KEY_JOY1 + 2:
1415 			key = KEY_ENTER;
1416 			break;
1417 		case KEY_JOY1 + 3:
1418 			key = 'n';
1419 			break;
1420 		case KEY_JOY1 + 1:
1421 			key = KEY_BACKSPACE;
1422 			break;
1423 		case KEY_HAT1:
1424 			key = KEY_UPARROW;
1425 			break;
1426 		case KEY_HAT1 + 1:
1427 			key = KEY_DOWNARROW;
1428 			break;
1429 		case KEY_HAT1 + 2:
1430 			key = KEY_LEFTARROW;
1431 			break;
1432 		case KEY_HAT1 + 3:
1433 			key = KEY_RIGHTARROW;
1434 			break;
1435 	}
1436 
1437 	if (!(timesBeaten) && !(netgame || multiplayer) && !cv_debug)
1438 		return false;
1439 
1440 	if (event->type != ev_keydown)
1441 		return false;
1442 
1443 	if (key != KEY_ESCAPE && key != KEY_ENTER && key != KEY_SPACE && key != KEY_BACKSPACE)
1444 		return false;
1445 
1446 	if (keypressed)
1447 		return true;
1448 
1449 	keypressed = true;
1450 	return true;
1451 }
1452 
1453 // ============
1454 //  EVALUATION
1455 // ============
1456 
1457 #define SPARKLLOOPTIME 7 // must be odd
1458 
F_StartGameEvaluation(void)1459 void F_StartGameEvaluation(void)
1460 {
1461 	// Credits option in extras menu
1462 	if (cursaveslot == -1)
1463 	{
1464 		S_FadeOutStopMusic(2*MUSICRATE);
1465 		F_StartGameEnd();
1466 		return;
1467 	}
1468 
1469 	S_FadeOutStopMusic(5*MUSICRATE);
1470 
1471 	G_SetGamestate(GS_EVALUATION);
1472 
1473 	// Just in case they're open ... somehow
1474 	M_ClearMenus(true);
1475 
1476 	goodending = (ALL7EMERALDS(emeralds));
1477 
1478 	gameaction = ga_nothing;
1479 	paused = false;
1480 	CON_ToggleOff();
1481 
1482 	finalecount = -1;
1483 	sparklloop = 0;
1484 }
1485 
F_GameEvaluationDrawer(void)1486 void F_GameEvaluationDrawer(void)
1487 {
1488 	INT32 x, y, i;
1489 	angle_t fa;
1490 	INT32 eemeralds_cur;
1491 	char patchname[7] = "CEMGx0";
1492 	const char* endingtext;
1493 
1494 	if (marathonmode)
1495 		endingtext = "THANKS FOR THE RUN!";
1496 	else if (goodending)
1497 		endingtext = "CONGRATULATIONS!";
1498 	else
1499 		endingtext = "TRY AGAIN...";
1500 
1501 	V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 31);
1502 
1503 	// Draw all the good crap here.
1504 
1505 	if (finalecount > 0 && useBlackRock)
1506 	{
1507 		INT32 scale = FRACUNIT;
1508 		patch_t *rockpat;
1509 		UINT8 *colormap[2] = {NULL, NULL};
1510 		patch_t *glow;
1511 		INT32 trans = 0;
1512 
1513 		x = (((BASEVIDWIDTH-82)/2)+11)<<FRACBITS;
1514 		y = (((BASEVIDHEIGHT-82)/2)+12)<<FRACBITS;
1515 
1516 		if (finalecount < 5)
1517 		{
1518 			scale = (finalecount<<(FRACBITS-2));
1519 			x += (30*(FRACUNIT-scale));
1520 			y += (30*(FRACUNIT-scale));
1521 		}
1522 
1523 		if (goodending)
1524 		{
1525 			rockpat = W_CachePatchName(va("ROID00%.2d", 34 - (finalecount % 35)), PU_PATCH_LOWPRIORITY);
1526 			glow = W_CachePatchName(va("ENDGLOW%.1d", 2+(finalecount & 1)), PU_PATCH_LOWPRIORITY);
1527 			x -= FRACUNIT;
1528 		}
1529 		else
1530 		{
1531 			rockpat = W_CachePatchName("ROID0000", PU_PATCH_LOWPRIORITY);
1532 			glow = W_CachePatchName(va("ENDGLOW%.1d", (finalecount & 1)), PU_PATCH_LOWPRIORITY);
1533 		}
1534 
1535 		if (finalecount >= 5)
1536 			trans = (finalecount-5)>>1;
1537 		if (trans < 10)
1538 			V_DrawFixedPatch(x, y, scale, trans<<V_ALPHASHIFT, glow, NULL);
1539 
1540 		trans = (15-finalecount);
1541 		if (trans < 0)
1542 			trans = -trans;
1543 
1544 		if (finalecount < 15)
1545 			colormap[0] = R_GetTranslationColormap(TC_ALLWHITE, 0, GTC_CACHE);
1546 		V_DrawFixedPatch(x, y, scale, 0, rockpat, colormap[0]);
1547 		if (trans < 10)
1548 		{
1549 			colormap[1] = R_GetTranslationColormap(TC_BLINK, SKINCOLOR_AQUA, GTC_CACHE);
1550 			V_DrawFixedPatch(x, y, scale, trans<<V_ALPHASHIFT, rockpat, colormap[1]);
1551 		}
1552 		if (goodending)
1553 		{
1554 			INT32 j = (sparklloop & 1) ? 2 : 3;
1555 			if (j > (finalecount/SPARKLLOOPTIME))
1556 				j = (finalecount/SPARKLLOOPTIME);
1557 			while (j)
1558 			{
1559 				if (j > 1 || sparklloop >= 2)
1560 				{
1561 					// if j == 0 - alternate between 0 and 1
1562 					//         1 -                   1 and 2
1563 					//         2 -                   2 and not rendered
1564 					V_DrawFixedPatch(x+sparkloffs[j-1][0], y+sparkloffs[j-1][1], FRACUNIT, 0, W_CachePatchName(va("ENDSPKL%.1d", (j - ((sparklloop & 1) ? 0 : 1))), PU_PATCH_LOWPRIORITY), R_GetTranslationColormap(TC_DEFAULT, SKINCOLOR_AQUA, GTC_CACHE));
1565 				}
1566 				j--;
1567 			}
1568 		}
1569 		else
1570 		{
1571 			patch_t *eggrock = W_CachePatchName("ENDEGRK5", PU_PATCH_LOWPRIORITY);
1572 			V_DrawFixedPatch(x, y, scale, 0, eggrock, colormap[0]);
1573 			if (trans < 10)
1574 				V_DrawFixedPatch(x, y, scale, trans<<V_ALPHASHIFT, eggrock, colormap[1]);
1575 			else if (sparklloop)
1576 				V_DrawFixedPatch(x, y, scale, (10-sparklloop)<<V_ALPHASHIFT,
1577 					W_CachePatchName("ENDEGRK0", PU_PATCH_LOWPRIORITY), colormap[1]);
1578 		}
1579 	}
1580 
1581 	eemeralds_cur = (finalecount % 360)<<FRACBITS;
1582 
1583 	for (i = 0; i < 7; ++i)
1584 	{
1585 		fa = (FixedAngle(eemeralds_cur)>>ANGLETOFINESHIFT) & FINEMASK;
1586 		x = (BASEVIDWIDTH<<(FRACBITS-1)) + (60*FINECOSINE(fa));
1587 		y = ((BASEVIDHEIGHT+16)<<(FRACBITS-1)) + (60*FINESINE(fa));
1588 		eemeralds_cur += (360<<FRACBITS)/7;
1589 
1590 		patchname[4] = 'A'+(char)i;
1591 		V_DrawFixedPatch(x, y, FRACUNIT, ((emeralds & (1<<i)) ? 0 : V_80TRANS), W_CachePatchName(patchname, PU_PATCH_LOWPRIORITY), NULL);
1592 	}
1593 
1594 	V_DrawCreditString((BASEVIDWIDTH - V_CreditStringWidth(endingtext))<<(FRACBITS-1), (BASEVIDHEIGHT-100)<<(FRACBITS-1), 0, endingtext);
1595 
1596 #if 0 // the following looks like hot garbage the more unlockables we add, and we now have a lot of unlockables
1597 	if (finalecount >= 5*TICRATE)
1598 	{
1599 		V_DrawString(8, 16, V_YELLOWMAP, "Unlocked:");
1600 
1601 		if (!(netgame) && (!modifiedgame || savemoddata))
1602 		{
1603 			INT32 startcoord = 32;
1604 
1605 			for (i = 0; i < MAXUNLOCKABLES; i++)
1606 			{
1607 				if (unlockables[i].conditionset && unlockables[i].conditionset < MAXCONDITIONSETS
1608 					&& unlockables[i].type && !unlockables[i].nocecho)
1609 				{
1610 					if (unlockables[i].unlocked)
1611 						V_DrawString(8, startcoord, 0, unlockables[i].name);
1612 					startcoord += 8;
1613 				}
1614 			}
1615 		}
1616 		else if (netgame)
1617 			V_DrawString(8, 96, V_YELLOWMAP, "Multiplayer games\ncan't unlock\nextras!");
1618 		else
1619 			V_DrawString(8, 96, V_YELLOWMAP, "Modified games\ncan't unlock\nextras!");
1620 	}
1621 #endif
1622 
1623 	if (marathonmode)
1624 	{
1625 		const char *rtatext, *cuttext;
1626 		rtatext = (marathonmode & MA_INGAME) ? "In-game timer" : "RTA timer";
1627 		cuttext = (marathonmode & MA_NOCUTSCENES) ? "" : " w/ cutscenes";
1628 		if (botskin)
1629 			endingtext = va("%s & %s, %s%s", skins[players[consoleplayer].skin].realname, skins[botskin-1].realname, rtatext, cuttext);
1630 		else
1631 			endingtext = va("%s, %s%s", skins[players[consoleplayer].skin].realname, rtatext, cuttext);
1632 		V_DrawCenteredString(BASEVIDWIDTH/2, 182, V_SNAPTOBOTTOM|(ultimatemode ? V_REDMAP : V_YELLOWMAP), endingtext);
1633 	}
1634 }
1635 
F_GameEvaluationTicker(void)1636 void F_GameEvaluationTicker(void)
1637 {
1638 	if (++finalecount > 10*TICRATE)
1639 	{
1640 		F_StartGameEnd();
1641 		return;
1642 	}
1643 
1644 	if (!useBlackRock)
1645 		;
1646 	else if (!goodending)
1647 	{
1648 		if (sparklloop)
1649 			sparklloop--;
1650 
1651 		if (finalecount == (5*TICRATE)/2
1652 			|| finalecount == (7*TICRATE)/2
1653 			|| finalecount == ((7*TICRATE)/2)+5)
1654 		{
1655 			S_StartSound(NULL, sfx_s3k5c);
1656 			sparklloop = 10;
1657 		}
1658 	}
1659 	else if (++sparklloop == SPARKLLOOPTIME) // time to roll the randomisation again
1660 	{
1661 		angle_t workingangle = FixedAngle((M_RandomKey(360))<<FRACBITS)>>ANGLETOFINESHIFT;
1662 		fixed_t workingradius = M_RandomKey(26);
1663 
1664 		sparkloffs[2][0] = sparkloffs[1][0];
1665 		sparkloffs[2][1] = sparkloffs[1][1];
1666 		sparkloffs[1][0] = sparkloffs[0][0];
1667 		sparkloffs[1][1] = sparkloffs[0][1];
1668 
1669 		sparkloffs[0][0] = (30<<FRACBITS) + workingradius*FINECOSINE(workingangle);
1670 		sparkloffs[0][1] = (30<<FRACBITS) + workingradius*FINESINE(workingangle);
1671 		sparklloop = 0;
1672 	}
1673 
1674 	if (finalecount == 5*TICRATE)
1675 	{
1676 		if (netgame || multiplayer) // modify this when we finally allow unlocking stuff in 2P
1677 		{
1678 			HU_SetCEchoFlags(V_YELLOWMAP|V_RETURN8);
1679 			HU_SetCEchoDuration(6);
1680 			HU_DoCEcho("\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Multiplayer games can't unlock extras!");
1681 			S_StartSound(NULL, sfx_s3k68);
1682 		}
1683 		else if (!modifiedgame || savemoddata)
1684 		{
1685 			++timesBeaten;
1686 
1687 			if (ALL7EMERALDS(emeralds))
1688 				++timesBeatenWithEmeralds;
1689 
1690 			if (ultimatemode)
1691 				++timesBeatenUltimate;
1692 
1693 			if (M_UpdateUnlockablesAndExtraEmblems())
1694 				S_StartSound(NULL, sfx_s3k68);
1695 
1696 			G_SaveGameData();
1697 		}
1698 		else
1699 		{
1700 			HU_SetCEchoFlags(V_YELLOWMAP|V_RETURN8);
1701 			HU_SetCEchoDuration(6);
1702 			HU_DoCEcho("\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Modified games can't unlock extras!");
1703 			S_StartSound(NULL, sfx_s3k68);
1704 		}
1705 	}
1706 }
1707 
1708 #undef SPARKLLOOPTIME
1709 
1710 // ==========
1711 //   ENDING
1712 // ==========
1713 
1714 #define INFLECTIONPOINT (6*TICRATE)
1715 #define STOPPINGPOINT (14*TICRATE)
1716 #define SPARKLLOOPTIME 15 // must be odd
1717 
F_CacheEnding(void)1718 static void F_CacheEnding(void)
1719 {
1720 	endbrdr[1] = W_CachePatchName("ENDBRDR1", PU_PATCH_LOWPRIORITY);
1721 
1722 	endegrk[0] = W_CachePatchName("ENDEGRK0", PU_PATCH_LOWPRIORITY);
1723 	endegrk[1] = W_CachePatchName("ENDEGRK1", PU_PATCH_LOWPRIORITY);
1724 
1725 	endglow[0] = W_CachePatchName("ENDGLOW0", PU_PATCH_LOWPRIORITY);
1726 	endglow[1] = W_CachePatchName("ENDGLOW1", PU_PATCH_LOWPRIORITY);
1727 
1728 	endbgsp[0] = W_CachePatchName("ENDBGSP0", PU_PATCH_LOWPRIORITY);
1729 	endbgsp[1] = W_CachePatchName("ENDBGSP1", PU_PATCH_LOWPRIORITY);
1730 	endbgsp[2] = W_CachePatchName("ENDBGSP2", PU_PATCH_LOWPRIORITY);
1731 
1732 	endspkl[0] = W_CachePatchName("ENDSPKL0", PU_PATCH_LOWPRIORITY);
1733 	endspkl[1] = W_CachePatchName("ENDSPKL1", PU_PATCH_LOWPRIORITY);
1734 	endspkl[2] = W_CachePatchName("ENDSPKL2", PU_PATCH_LOWPRIORITY);
1735 
1736 	endxpld[0] = W_CachePatchName("ENDXPLD0", PU_PATCH_LOWPRIORITY);
1737 	endxpld[1] = W_CachePatchName("ENDXPLD1", PU_PATCH_LOWPRIORITY);
1738 	endxpld[2] = W_CachePatchName("ENDXPLD2", PU_PATCH_LOWPRIORITY);
1739 	endxpld[3] = W_CachePatchName("ENDXPLD3", PU_PATCH_LOWPRIORITY);
1740 
1741 	endescp[0] = W_CachePatchName("ENDESCP0", PU_PATCH_LOWPRIORITY);
1742 	endescp[1] = W_CachePatchName("ENDESCP1", PU_PATCH_LOWPRIORITY);
1743 	endescp[2] = W_CachePatchName("ENDESCP2", PU_PATCH_LOWPRIORITY);
1744 	endescp[3] = W_CachePatchName("ENDESCP3", PU_PATCH_LOWPRIORITY);
1745 	endescp[4] = W_CachePatchName("ENDESCP4", PU_PATCH_LOWPRIORITY);
1746 
1747 	// so we only need to check once
1748 	if ((goodending = ALL7EMERALDS(emeralds)))
1749 	{
1750 		UINT8 skinnum = players[consoleplayer].skin;
1751 		spritedef_t *sprdef;
1752 		spriteframe_t *sprframe;
1753 		if (skins[skinnum].sprites[SPR2_XTRA].numframes > (XTRA_ENDING+2))
1754 		{
1755 			sprdef = &skins[skinnum].sprites[SPR2_XTRA];
1756 			// character head, skin specific
1757 			sprframe = &sprdef->spriteframes[XTRA_ENDING];
1758 			endfwrk[0] = W_CachePatchNum(sprframe->lumppat[0], PU_PATCH_LOWPRIORITY);
1759 			sprframe = &sprdef->spriteframes[XTRA_ENDING+1];
1760 			endfwrk[1] = W_CachePatchNum(sprframe->lumppat[0], PU_PATCH_LOWPRIORITY);
1761 			sprframe = &sprdef->spriteframes[XTRA_ENDING+2];
1762 			endfwrk[2] = W_CachePatchNum(sprframe->lumppat[0], PU_PATCH_LOWPRIORITY);
1763 		}
1764 		else // Show a star if your character doesn't have an ending firework display. (Basically the MISSINGs for this)
1765 		{
1766 			endfwrk[0] = W_CachePatchName("ENDFWRK3", PU_PATCH_LOWPRIORITY);
1767 			endfwrk[1] = W_CachePatchName("ENDFWRK4", PU_PATCH_LOWPRIORITY);
1768 			endfwrk[2] = W_CachePatchName("ENDFWRK5", PU_PATCH_LOWPRIORITY);
1769 		}
1770 
1771 		endbrdr[0] = W_CachePatchName("ENDBRDR2", PU_PATCH_LOWPRIORITY);
1772 	}
1773 	else
1774 	{
1775 		// eggman, skin nonspecific
1776 		endfwrk[0] = W_CachePatchName("ENDFWRK0", PU_PATCH_LOWPRIORITY);
1777 		endfwrk[1] = W_CachePatchName("ENDFWRK1", PU_PATCH_LOWPRIORITY);
1778 		endfwrk[2] = W_CachePatchName("ENDFWRK2", PU_PATCH_LOWPRIORITY);
1779 
1780 		endbrdr[0] = W_CachePatchName("ENDBRDR0", PU_PATCH_LOWPRIORITY);
1781 	}
1782 }
1783 
F_CacheGoodEnding(void)1784 static void F_CacheGoodEnding(void)
1785 {
1786 	endegrk[0] = W_CachePatchName("ENDEGRK2", PU_PATCH_LOWPRIORITY);
1787 	endegrk[1] = W_CachePatchName("ENDEGRK3", PU_PATCH_LOWPRIORITY);
1788 
1789 	endglow[0] = W_CachePatchName("ENDGLOW2", PU_PATCH_LOWPRIORITY);
1790 	endglow[1] = W_CachePatchName("ENDGLOW3", PU_PATCH_LOWPRIORITY);
1791 
1792 	endxpld[0] = W_CachePatchName("ENDEGRK4", PU_PATCH_LOWPRIORITY);
1793 }
1794 
F_StartEnding(void)1795 void F_StartEnding(void)
1796 {
1797 	G_SetGamestate(GS_ENDING);
1798 	wipetypepost = INT16_MAX;
1799 
1800 	// Just in case they're open ... somehow
1801 	M_ClearMenus(true);
1802 
1803 	gameaction = ga_nothing;
1804 	paused = false;
1805 	CON_ToggleOff();
1806 	S_StopMusic(); // todo: placeholder
1807 	S_StopSounds();
1808 
1809 	finalecount = -10; // what? this totally isn't a hack. why are you asking?
1810 
1811 	memset(sparkloffs, 0, sizeof(INT32)*3*2);
1812 	sparklloop = 0;
1813 
1814 	F_CacheEnding();
1815 }
1816 
F_EndingTicker(void)1817 void F_EndingTicker(void)
1818 {
1819 	if (++finalecount > STOPPINGPOINT)
1820 	{
1821 		F_StartCredits();
1822 		wipetypepre = INT16_MAX;
1823 		return;
1824 	}
1825 
1826 	if (finalecount == -8)
1827 		S_ChangeMusicInternal((goodending ? "_endg" : "_endb"), false);
1828 
1829 	if (goodending && finalecount == INFLECTIONPOINT) // time to swap some assets
1830 		F_CacheGoodEnding();
1831 
1832 	if (++sparklloop == SPARKLLOOPTIME) // time to roll the randomisation again
1833 	{
1834 		angle_t workingangle = FixedAngle((M_RandomRange(-170, 80))<<FRACBITS)>>ANGLETOFINESHIFT;
1835 		fixed_t workingradius = M_RandomKey(26);
1836 
1837 		sparkloffs[0][0] = (30<<FRACBITS) + workingradius*FINECOSINE(workingangle);
1838 		sparkloffs[0][1] = (30<<FRACBITS) + workingradius*FINESINE(workingangle);
1839 
1840 		sparklloop = 0;
1841 	}
1842 }
1843 
F_EndingDrawer(void)1844 void F_EndingDrawer(void)
1845 {
1846 	INT32 x, y, i, j, parallaxticker;
1847 	patch_t *rockpat;
1848 
1849 	if (!goodending || finalecount < INFLECTIONPOINT)
1850 		rockpat = W_CachePatchName("ROID0000", PU_PATCH_LOWPRIORITY);
1851 	else
1852 		rockpat = W_CachePatchName(va("ROID00%.2d", 34 - ((finalecount - INFLECTIONPOINT) % 35)), PU_PATCH_LOWPRIORITY);
1853 
1854 	V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 31);
1855 
1856 	parallaxticker = finalecount - INFLECTIONPOINT;
1857 	x = -((parallaxticker*20)<<FRACBITS)/INFLECTIONPOINT;
1858 	y = ((parallaxticker*7)<<FRACBITS)/INFLECTIONPOINT;
1859 	i = (((BASEVIDWIDTH-82)/2)+11)<<FRACBITS;
1860 	j = (((BASEVIDHEIGHT-82)/2)+12)<<FRACBITS;
1861 
1862 	if (finalecount <= -10)
1863 		;
1864 	else if (finalecount < 0)
1865 		V_DrawFadeFill(24, 24, BASEVIDWIDTH-48, BASEVIDHEIGHT-48, 0, 0, 10+finalecount);
1866 	else if (finalecount <= 20)
1867 	{
1868 		V_DrawFill(24, 24, BASEVIDWIDTH-48, BASEVIDHEIGHT-48, 0);
1869 		if (finalecount && finalecount < 20)
1870 		{
1871 			INT32 trans = (10-finalecount);
1872 			if (trans < 0)
1873 			{
1874 				trans = -trans;
1875 				V_DrawScaledPatch(BASEVIDWIDTH/2, BASEVIDHEIGHT/2, 0, endbrdr[0]);
1876 			}
1877 			V_DrawScaledPatch(BASEVIDWIDTH/2, BASEVIDHEIGHT/2, trans<<V_ALPHASHIFT, endbrdr[1]);
1878 		}
1879 		else if (finalecount == 20)
1880 			V_DrawScaledPatch(BASEVIDWIDTH/2, BASEVIDHEIGHT/2, 0, endbrdr[0]);
1881 	}
1882 	else if (goodending && (parallaxticker == -2 || !parallaxticker))
1883 	{
1884 		V_DrawFill(24, 24, BASEVIDWIDTH-48, BASEVIDHEIGHT-48, 0);
1885 		V_DrawFixedPatch(x+i, y+j, FRACUNIT, 0, endegrk[0],
1886 			R_GetTranslationColormap(TC_BLINK, SKINCOLOR_BLACK, GTC_CACHE));
1887 		//V_DrawScaledPatch(BASEVIDWIDTH/2, BASEVIDHEIGHT/2, 0, endbrdr[1]);
1888 	}
1889 	else if (goodending && parallaxticker == -1)
1890 	{
1891 		V_DrawFixedPatch(x+i, y+j, FRACUNIT, 0, rockpat,
1892 			R_GetTranslationColormap(TC_ALLWHITE, 0, GTC_CACHE));
1893 		V_DrawScaledPatch(BASEVIDWIDTH/2, BASEVIDHEIGHT/2, 0, endbrdr[1]);
1894 	}
1895 	else
1896 	{
1897 		boolean doexplosions = false;
1898 		boolean borderstuff = false;
1899 		INT32 tweakx = 0, tweaky = 0;
1900 
1901 		if (parallaxticker < 75) // f background's supposed to be visible
1902 		{
1903 			V_DrawFixedPatch(-(x/10), -(y/10), FRACUNIT, 0, endbgsp[0], NULL); // nebula
1904 			V_DrawFixedPatch(-(x/5),  -(y/5),  FRACUNIT, 0, endbgsp[1], NULL); // sun
1905 			V_DrawFixedPatch(     0,  -(y/2),  FRACUNIT, 0, endbgsp[2], NULL); // planet
1906 
1907 			// player's escape pod
1908 			V_DrawFixedPatch((200<<FRACBITS)+(finalecount<<(FRACBITS-2)),
1909 				(100<<FRACBITS)+(finalecount<<(FRACBITS-2)),
1910 				FRACUNIT, 0, endescp[4], NULL);
1911 			if (parallaxticker > -19)
1912 			{
1913 				INT32 trans = (-parallaxticker)>>1;
1914 				if (trans < 0)
1915 					trans = 0;
1916 				V_DrawFixedPatch((200<<FRACBITS)+(finalecount<<(FRACBITS-2)),
1917 					(100<<FRACBITS)+(finalecount<<(FRACBITS-2)),
1918 					FRACUNIT, trans<<V_ALPHASHIFT, endescp[(finalecount/2)&3], NULL);
1919 			}
1920 
1921 			if (goodending && parallaxticker > 0) // gunchedrock
1922 			{
1923 				INT32 scale = FRACUNIT + ((parallaxticker-10)<<7);
1924 				INT32 trans = parallaxticker>>2;
1925 				UINT8 *colormap = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_JET, GTC_CACHE);
1926 
1927 				if (parallaxticker < 10)
1928 				{
1929 					tweakx = parallaxticker<<FRACBITS;
1930 					tweaky = ((7*parallaxticker)<<(FRACBITS-2))/5;
1931 				}
1932 				else
1933 				{
1934 					tweakx = 10<<FRACBITS;
1935 					tweaky = 7<<(FRACBITS-1);
1936 				}
1937 				i += tweakx;
1938 				j -= tweaky;
1939 
1940 				x <<= 1;
1941 				y <<= 1;
1942 
1943 				// center detritrus
1944 				V_DrawFixedPatch(i-x, j-y, FRACUNIT, 0, endegrk[0], colormap);
1945 				if (trans < 10)
1946 					V_DrawFixedPatch(i-x, j-y, FRACUNIT, trans<<V_ALPHASHIFT, endegrk[0], NULL);
1947 
1948 				 // ring detritrus
1949 				V_DrawFixedPatch((30*(FRACUNIT-scale))+i-(2*x), (30*(FRACUNIT-scale))+j-(2*y) - ((7<<FRACBITS)/2), scale, 0, endegrk[1], colormap);
1950 				if (trans < 10)
1951 					V_DrawFixedPatch((30*(FRACUNIT-scale))+i-(2*x), (30*(FRACUNIT-scale))+j-(2*y), scale, trans<<V_ALPHASHIFT, endegrk[1], NULL);
1952 
1953 				scale += ((parallaxticker-10)<<7);
1954 
1955 				 // shard detritrus
1956 				V_DrawFixedPatch((30*(FRACUNIT-scale))+i-(x/2), (30*(FRACUNIT-scale))+j-(y/2) - ((7<<FRACBITS)/2), scale, 0, endxpld[0], colormap);
1957 				if (trans < 10)
1958 					V_DrawFixedPatch((30*(FRACUNIT-scale))+i-(x/2), (30*(FRACUNIT-scale))+j-(y/2), scale, trans<<V_ALPHASHIFT, endxpld[0], NULL);
1959 			}
1960 		}
1961 		else if (goodending)
1962 		{
1963 			tweakx = 10<<FRACBITS;
1964 			tweaky = 7<<(FRACBITS-1);
1965 			i += tweakx;
1966 			j += tweaky;
1967 			x <<= 1;
1968 			y <<= 1;
1969 		}
1970 
1971 		if (goodending && parallaxticker > 0)
1972 		{
1973 			i -= (3+(tweakx<<1));
1974 			j += tweaky<<2;
1975 		}
1976 
1977 		if (parallaxticker <= 70) // eggrock/blackrock
1978 		{
1979 			INT32 trans;
1980 			fixed_t scale = FRACUNIT;
1981 			UINT8 *colormap[2] = {NULL, NULL};
1982 
1983 			x += i;
1984 			y += j;
1985 
1986 			if (parallaxticker > 66)
1987 			{
1988 				scale = ((70 - parallaxticker)<<(FRACBITS-2));
1989 				x += (30*(FRACUNIT-scale));
1990 				y += (30*(FRACUNIT-scale));
1991 			}
1992 			else if ((parallaxticker > 60) || (goodending && parallaxticker > 0))
1993 				;
1994 			else
1995 			{
1996 				doexplosions = true;
1997 				if (!sparklloop)
1998 				{
1999 					x += ((sparkloffs[0][0] < 30<<FRACBITS) ? FRACUNIT : -FRACUNIT);
2000 					y += ((sparkloffs[0][1] < 30<<FRACBITS) ? FRACUNIT : -FRACUNIT);
2001 				}
2002 			}
2003 
2004 			if (goodending && finalecount > INFLECTIONPOINT)
2005 				parallaxticker -= 40;
2006 
2007 			if ((-parallaxticker/4) < 5)
2008 			{
2009 				trans = (-parallaxticker/4) + 5;
2010 				if (trans < 0)
2011 					trans = 0;
2012 				V_DrawFixedPatch(x, y, scale, trans<<V_ALPHASHIFT, endglow[(finalecount & 1) ? 0 : 1], NULL);
2013 			}
2014 
2015 			if (goodending && finalecount > INFLECTIONPOINT)
2016 			{
2017 				if (finalecount < INFLECTIONPOINT+10)
2018 					V_DrawFadeFill(24, 24, BASEVIDWIDTH-48, BASEVIDHEIGHT-48, 0, 0, INFLECTIONPOINT+10-finalecount);
2019 				parallaxticker -= 30;
2020 			}
2021 
2022 			if ((parallaxticker/2) > -15)
2023 				colormap[0] = R_GetTranslationColormap(TC_ALLWHITE, 0, GTC_CACHE);
2024 			V_DrawFixedPatch(x, y, scale, 0, rockpat, colormap[0]);
2025 			if ((parallaxticker/2) > -25)
2026 			{
2027 				trans = (parallaxticker/2) + 15;
2028 				if (trans < 0)
2029 					trans = -trans;
2030 				if (trans < 10)
2031 					V_DrawFixedPatch(x, y, scale, trans<<V_ALPHASHIFT, rockpat,
2032 						R_GetTranslationColormap(TC_BLINK, SKINCOLOR_AQUA, GTC_CACHE));
2033 			}
2034 
2035 			if (goodending && finalecount > INFLECTIONPOINT)
2036 			{
2037 				if (finalecount < INFLECTIONPOINT+10)
2038 					V_DrawFixedPatch(x, y, scale, (finalecount-INFLECTIONPOINT)<<V_ALPHASHIFT, rockpat,
2039 						R_GetTranslationColormap(TC_BLINK, SKINCOLOR_BLACK, GTC_CACHE));
2040 			}
2041 			else
2042 			{
2043 				if ((-parallaxticker/2) < -5)
2044 					colormap[1] = R_GetTranslationColormap(TC_ALLWHITE, 0, GTC_CACHE);
2045 
2046 				V_DrawFixedPatch(x, y, scale, 0, endegrk[0], colormap[1]);
2047 
2048 				if ((-parallaxticker/2) < 5)
2049 				{
2050 					trans = (-parallaxticker/2) + 5;
2051 					if (trans < 0)
2052 						trans = -trans;
2053 					if (trans < 10)
2054 						V_DrawFixedPatch(x, y, scale, trans<<V_ALPHASHIFT, endegrk[1], NULL);
2055 				}
2056 			}
2057 		}
2058 		else // firework
2059 		{
2060 			fixed_t scale = FRACUNIT;
2061 			INT32 frame;
2062 			UINT8 *colormap = NULL;
2063 			parallaxticker -= 70;
2064 			x += ((BASEVIDWIDTH-3)<<(FRACBITS-1)) - tweakx;
2065 			y += (BASEVIDHEIGHT<<(FRACBITS-1)) + tweaky;
2066 			borderstuff = true;
2067 
2068 			if (parallaxticker < 5)
2069 			{
2070 				scale = (parallaxticker<<FRACBITS)/4;
2071 				V_DrawFadeFill(24, 24, BASEVIDWIDTH-48, BASEVIDHEIGHT-48, 0, 31, parallaxticker*2);
2072 			}
2073 			else
2074 				scale += (parallaxticker-4)<<5;
2075 
2076 			if (goodending)
2077 				colormap = R_GetTranslationColormap(players[consoleplayer].skin, players[consoleplayer].skincolor, GTC_CACHE);
2078 
2079 			if ((frame = ((parallaxticker & 1) ? 1 : 0) + (parallaxticker/TICRATE)) < 3)
2080 				V_DrawFixedPatch(x, y, scale, 0, endfwrk[frame], colormap);
2081 		}
2082 
2083 		// explosions
2084 		if (sparklloop >= 3 && doexplosions)
2085 		{
2086 			INT32 boomtime = parallaxticker - sparklloop;
2087 
2088 			x = ((((BASEVIDWIDTH-82)/2)+11)<<FRACBITS) - ((boomtime*20)<<FRACBITS)/INFLECTIONPOINT;
2089 			y = ((((BASEVIDHEIGHT-82)/2)+12)<<FRACBITS) + ((boomtime*7)<<FRACBITS)/INFLECTIONPOINT;
2090 
2091 			V_DrawFixedPatch(x + sparkloffs[0][0], y + sparkloffs[0][1],
2092 				FRACUNIT, 0, endxpld[sparklloop/4], NULL);
2093 		}
2094 
2095 		// initial fade
2096 		if (finalecount < 30)
2097 			V_DrawFadeFill(24, 24, BASEVIDWIDTH-48, BASEVIDHEIGHT-48, 0, 0, 30-finalecount);
2098 
2099 		// border - only emeralds can exist outside it
2100 		{
2101 			INT32 trans = 0;
2102 			if (borderstuff)
2103 				trans = (10*parallaxticker)/(3*TICRATE);
2104 			if (trans < 10)
2105 				V_DrawScaledPatch(BASEVIDWIDTH/2, BASEVIDHEIGHT/2, trans<<V_ALPHASHIFT, endbrdr[0]);
2106 			if (borderstuff && parallaxticker < 11)
2107 				V_DrawScaledPatch(BASEVIDWIDTH/2, BASEVIDHEIGHT/2, (parallaxticker-1)<<V_ALPHASHIFT, endbrdr[1]);
2108 			else if (goodending && finalecount > INFLECTIONPOINT && finalecount < INFLECTIONPOINT+10)
2109 				V_DrawScaledPatch(BASEVIDWIDTH/2, BASEVIDHEIGHT/2, (finalecount-INFLECTIONPOINT)<<V_ALPHASHIFT, endbrdr[1]);
2110 		}
2111 
2112 		// emeralds and emerald accessories
2113 		if (goodending && finalecount >= TICRATE && finalecount < INFLECTIONPOINT)
2114 		{
2115 			INT32 workingtime = finalecount - TICRATE;
2116 			fixed_t radius = ((vid.width/vid.dupx)*(INFLECTIONPOINT - TICRATE - workingtime))/(INFLECTIONPOINT - TICRATE);
2117 			angle_t fa;
2118 			INT32 eemeralds_cur[4];
2119 			char patchname[7] = "CEMGx0";
2120 
2121 			radius <<= FRACBITS;
2122 
2123 			for (i = 0; i < 4; ++i)
2124 			{
2125 				if (i == 1)
2126 					workingtime -= sparklloop;
2127 				else if (i)
2128 					workingtime -= SPARKLLOOPTIME;
2129 				eemeralds_cur[i] = (workingtime % 360)<<FRACBITS;
2130 			}
2131 
2132 			// sparkles
2133 			for (i = 0; i < 7; ++i)
2134 			{
2135 				UINT8* colormap;
2136 				skincolornum_t col = SKINCOLOR_GREEN;
2137 				switch (i)
2138 				{
2139 					case 1:
2140 						col = SKINCOLOR_MAGENTA;
2141 						break;
2142 					case 2:
2143 						col = SKINCOLOR_BLUE;
2144 						break;
2145 					case 3:
2146 						col = SKINCOLOR_SKY;
2147 						break;
2148 					case 4:
2149 						col = SKINCOLOR_ORANGE;
2150 						break;
2151 					case 5:
2152 						col = SKINCOLOR_RED;
2153 						break;
2154 					case 6:
2155 						col = SKINCOLOR_GREY;
2156 					default:
2157 					case 0:
2158 						break;
2159 				}
2160 
2161 				colormap = R_GetTranslationColormap(TC_DEFAULT, col, GTC_CACHE);
2162 
2163 				j = (sparklloop & 1) ? 2 : 3;
2164 				while (j)
2165 				{
2166 					fa = (FixedAngle(eemeralds_cur[j])>>ANGLETOFINESHIFT) & FINEMASK;
2167 					x =  (BASEVIDWIDTH<<(FRACBITS-1)) + FixedMul(FINECOSINE(fa),radius);
2168 					y = (BASEVIDHEIGHT<<(FRACBITS-1)) + FixedMul(FINESINE(fa),radius);
2169 					eemeralds_cur[j] += (360<<FRACBITS)/7;
2170 
2171 					// if j == 0 - alternate between 0 and 1
2172 					//         1 -                   1 and 2
2173 					//         2 -                   2 and not rendered
2174 					V_DrawFixedPatch(x, y, FRACUNIT, 0, endspkl[(j - ((sparklloop & 1) ? 0 : 1))], colormap);
2175 
2176 					j--;
2177 				}
2178 			}
2179 
2180 			// ...then emeralds themselves
2181 			for (i = 0; i < 7; ++i)
2182 			{
2183 				fa = (FixedAngle(eemeralds_cur[0])>>ANGLETOFINESHIFT) & FINEMASK;
2184 				x = (BASEVIDWIDTH<<(FRACBITS-1)) + FixedMul(FINECOSINE(fa),radius);
2185 				y = ((BASEVIDHEIGHT+16)<<(FRACBITS-1)) + FixedMul(FINESINE(fa),radius);
2186 				eemeralds_cur[0] += (360<<FRACBITS)/7;
2187 
2188 				patchname[4] = 'A'+(char)i;
2189 				V_DrawFixedPatch(x, y, FRACUNIT, 0, W_CachePatchName(patchname, PU_PATCH_LOWPRIORITY), NULL);
2190 			}
2191 		} // if (goodending...
2192 	} // (finalecount > 20)
2193 
2194 	// look, i make an ending for you last-minute, the least you could do is let me have this
2195 	if (cv_soundtest.value == 413)
2196 	{
2197 		INT32 trans = 0;
2198 		boolean donttouch = false;
2199 		const char *str;
2200 		if (goodending)
2201 			str = va("[S] %s: Engage.", skins[players[consoleplayer].skin].realname);
2202 		else
2203 			str = "[S] Eggman: Abscond.";
2204 
2205 		if (finalecount < 10)
2206 			trans = (10-finalecount)/2;
2207 		else if (finalecount > STOPPINGPOINT - 20)
2208 		{
2209 			trans = 10 + (finalecount - STOPPINGPOINT)/2;
2210 			donttouch = true;
2211 		}
2212 
2213 		if (trans < 10)
2214 		{
2215 			//colset(linkmap,  164, 165, 169); -- the ideal purple colour to represent a clicked in-game link, but not worth it just for a soundtest-controlled secret
2216 			V_DrawCenteredString(BASEVIDWIDTH/2, 8, V_ALLOWLOWERCASE|(trans<<V_ALPHASHIFT), str);
2217 			V_DrawCharacter(32, BASEVIDHEIGHT-16, '>'|(trans<<V_ALPHASHIFT), false);
2218 			V_DrawString(40, ((finalecount == STOPPINGPOINT-(20+TICRATE)) ? 1 : 0)+BASEVIDHEIGHT-16, ((timesBeaten || finalecount >= STOPPINGPOINT-TICRATE) ? V_PURPLEMAP : V_BLUEMAP)|(trans<<V_ALPHASHIFT), " [S] ===>");
2219 		}
2220 
2221 		if (finalecount > STOPPINGPOINT-(20+(2*TICRATE)))
2222 		{
2223 			INT32 trans2 = abs((5*FINECOSINE((FixedAngle((finalecount*5)<<FRACBITS)>>ANGLETOFINESHIFT & FINEMASK)))>>FRACBITS)+2;
2224 			if (!donttouch)
2225 			{
2226 				trans = 10 + (STOPPINGPOINT-(20+(2*TICRATE))) - finalecount;
2227 				if (trans > trans2)
2228 					trans2 = trans;
2229 			}
2230 			else
2231 				trans2 += 2*trans;
2232 			if (trans2 < 10)
2233 				V_DrawCharacter(26, BASEVIDHEIGHT-33, '\x1C'|(trans2<<V_ALPHASHIFT), false);
2234 		}
2235 	}
2236 }
2237 
2238 #undef SPARKLLOOPTIME
2239 
2240 // ==========
2241 //  GAME END
2242 // ==========
F_StartGameEnd(void)2243 void F_StartGameEnd(void)
2244 {
2245 	G_SetGamestate(GS_GAMEEND);
2246 
2247 	gameaction = ga_nothing;
2248 	paused = false;
2249 	CON_ToggleOff();
2250 	S_StopSounds();
2251 
2252 	// In case menus are still up?!!
2253 	M_ClearMenus(true);
2254 
2255 	timetonext = TICRATE;
2256 }
2257 
2258 //
2259 // F_GameEndDrawer
2260 //
F_GameEndDrawer(void)2261 void F_GameEndDrawer(void)
2262 {
2263 	// this function does nothing
2264 }
2265 
2266 //
2267 // F_GameEndTicker
2268 //
F_GameEndTicker(void)2269 void F_GameEndTicker(void)
2270 {
2271 	if (timetonext > 0)
2272 		timetonext--;
2273 	else
2274 		D_StartTitle();
2275 }
2276 
2277 
2278 // ==============
2279 //  TITLE SCREEN
2280 // ==============
2281 
F_InitMenuPresValues(void)2282 void F_InitMenuPresValues(void)
2283 {
2284 	menuanimtimer = 0;
2285 	prevMenuId = 0;
2286 	activeMenuId = MainDef.menuid;
2287 
2288 	// Set defaults for presentation values
2289 	strncpy(curbgname, "TITLESKY", 9);
2290 	curfadevalue = 16;
2291 	curbgcolor = -1;
2292 	curbgxspeed = (gamestate == GS_TIMEATTACK) ? 0 : titlescrollxspeed;
2293 	curbgyspeed = (gamestate == GS_TIMEATTACK) ? 22 : titlescrollyspeed;
2294 	curbghide = (gamestate == GS_TIMEATTACK) ? false : true;
2295 
2296 	curhidepics = hidetitlepics;
2297 	curttmode = ttmode;
2298 	curttscale = ttscale;
2299 	strncpy(curttname, ttname, 9);
2300 	curttx = ttx;
2301 	curtty = tty;
2302 	curttloop = ttloop;
2303 	curtttics = tttics;
2304 
2305 	// Find current presentation values
2306 	M_SetMenuCurBackground((gamestate == GS_TIMEATTACK) ? "RECATTBG" : "TITLESKY");
2307 	M_SetMenuCurFadeValue(16);
2308 	M_SetMenuCurTitlePics();
2309 }
2310 
2311 //
2312 // F_SkyScroll
2313 //
F_SkyScroll(INT32 scrollxspeed,INT32 scrollyspeed,const char * patchname)2314 void F_SkyScroll(INT32 scrollxspeed, INT32 scrollyspeed, const char *patchname)
2315 {
2316 	INT32 xscrolled, x, xneg = (scrollxspeed > 0) - (scrollxspeed < 0), tilex;
2317 	INT32 yscrolled, y, yneg = (scrollyspeed > 0) - (scrollyspeed < 0), tiley;
2318 	boolean xispos = (scrollxspeed >= 0), yispos = (scrollyspeed >= 0);
2319 	INT32 dupz = (vid.dupx < vid.dupy ? vid.dupx : vid.dupy);
2320 	INT16 patwidth, patheight;
2321 	INT32 pw, ph; // scaled by dupz
2322 	patch_t *pat;
2323 	INT32 i, j;
2324 
2325 	if (rendermode == render_none)
2326 		return;
2327 
2328 	if (!patchname || !patchname[0])
2329 	{
2330 		V_DrawFill(0, 0, vid.width, vid.height, 31);
2331 		return;
2332 	}
2333 
2334 	if (!scrollxspeed && !scrollyspeed)
2335 	{
2336 		V_DrawPatchFill(W_CachePatchName(patchname, PU_PATCH_LOWPRIORITY));
2337 		return;
2338 	}
2339 
2340 	pat = W_CachePatchName(patchname, PU_PATCH_LOWPRIORITY);
2341 
2342 	patwidth = pat->width;
2343 	patheight = pat->height;
2344 	pw = patwidth * dupz;
2345 	ph = patheight * dupz;
2346 
2347 	tilex = max(FixedCeil(FixedDiv(vid.width, pw)) >> FRACBITS, 1)+2; // one tile on both sides of center
2348 	tiley = max(FixedCeil(FixedDiv(vid.height, ph)) >> FRACBITS, 1)+2;
2349 
2350 	xscrolltimer = ((menuanimtimer*scrollxspeed)/16 + patwidth*xneg) % (patwidth);
2351 	yscrolltimer = ((menuanimtimer*scrollyspeed)/16 + patheight*yneg) % (patheight);
2352 
2353 	// coordinate offsets
2354 	xscrolled = xscrolltimer * dupz;
2355 	yscrolled = yscrolltimer * dupz;
2356 
2357 	for (x = (xispos) ? -pw*(tilex-1)+pw : 0, i = 0;
2358 		i < tilex;
2359 		x += pw, i++)
2360 	{
2361 		for (y = (yispos) ? -ph*(tiley-1)+ph : 0, j = 0;
2362 			j < tiley;
2363 			y += ph, j++)
2364 		{
2365 			V_DrawScaledPatch(
2366 				(xispos) ? xscrolled - x : x + xscrolled,
2367 				(yispos) ? yscrolled - y : y + yscrolled,
2368 				V_NOSCALESTART, pat);
2369 		}
2370 	}
2371 
2372 	W_UnlockCachedPatch(pat);
2373 }
2374 
2375 #define LOADTTGFX(arr, name, maxf) \
2376 lumpnum = W_CheckNumForName(name); \
2377 if (lumpnum != LUMPERROR) \
2378 { \
2379 	arr[0] = W_CachePatchName(name, PU_PATCH_LOWPRIORITY); \
2380 	arr[min(1, maxf-1)] = 0; \
2381 } \
2382 else if (strlen(name) <= 6) \
2383 { \
2384 	fixed_t cnt = strlen(name); \
2385 	strncpy(lumpname, name, 7); \
2386 	for (i = 0; i < maxf-1; i++) \
2387 	{ \
2388 		sprintf(&lumpname[cnt], "%.2hu", (UINT16)(i+1)); \
2389 		lumpname[8] = 0; \
2390 		lumpnum = W_CheckNumForName(lumpname); \
2391 		if (lumpnum != LUMPERROR) \
2392 			arr[i] = W_CachePatchName(lumpname, PU_PATCH_LOWPRIORITY); \
2393 		else \
2394 			break; \
2395 	} \
2396 	arr[min(i, maxf-1)] = 0; \
2397 } \
2398 else \
2399 	arr[0] = 0;
2400 
F_CacheTitleScreen(void)2401 static void F_CacheTitleScreen(void)
2402 {
2403 	switch(curttmode)
2404 	{
2405 		case TTMODE_OLD:
2406 		case TTMODE_NONE:
2407 			ttbanner = W_CachePatchName("TTBANNER", PU_PATCH_LOWPRIORITY);
2408 			ttwing = W_CachePatchName("TTWING", PU_PATCH_LOWPRIORITY);
2409 			ttsonic = W_CachePatchName("TTSONIC", PU_PATCH_LOWPRIORITY);
2410 			ttswave1 = W_CachePatchName("TTSWAVE1", PU_PATCH_LOWPRIORITY);
2411 			ttswave2 = W_CachePatchName("TTSWAVE2", PU_PATCH_LOWPRIORITY);
2412 			ttswip1 = W_CachePatchName("TTSWIP1", PU_PATCH_LOWPRIORITY);
2413 			ttsprep1 = W_CachePatchName("TTSPREP1", PU_PATCH_LOWPRIORITY);
2414 			ttsprep2 = W_CachePatchName("TTSPREP2", PU_PATCH_LOWPRIORITY);
2415 			ttspop1 = W_CachePatchName("TTSPOP1", PU_PATCH_LOWPRIORITY);
2416 			ttspop2 = W_CachePatchName("TTSPOP2", PU_PATCH_LOWPRIORITY);
2417 			ttspop3 = W_CachePatchName("TTSPOP3", PU_PATCH_LOWPRIORITY);
2418 			ttspop4 = W_CachePatchName("TTSPOP4", PU_PATCH_LOWPRIORITY);
2419 			ttspop5 = W_CachePatchName("TTSPOP5", PU_PATCH_LOWPRIORITY);
2420 			ttspop6 = W_CachePatchName("TTSPOP6", PU_PATCH_LOWPRIORITY);
2421 			ttspop7 = W_CachePatchName("TTSPOP7", PU_PATCH_LOWPRIORITY);
2422 			break;
2423 
2424 		// don't load alacroix gfx yet; we do that upon first draw.
2425 		case TTMODE_ALACROIX:
2426 			break;
2427 
2428 		case TTMODE_USER:
2429 		{
2430 			UINT16 i;
2431 			lumpnum_t lumpnum;
2432 			char lumpname[9];
2433 
2434 			LOADTTGFX(ttuser, curttname, TTMAX_USER)
2435 			break;
2436 		}
2437 	}
2438 }
2439 
F_StartTitleScreen(void)2440 void F_StartTitleScreen(void)
2441 {
2442 	if (menupres[MN_MAIN].musname[0])
2443 		S_ChangeMusic(menupres[MN_MAIN].musname, menupres[MN_MAIN].mustrack, menupres[MN_MAIN].muslooping);
2444 	else
2445 		S_ChangeMusicInternal("_title", looptitle);
2446 
2447 	if (gamestate != GS_TITLESCREEN && gamestate != GS_WAITINGPLAYERS)
2448 	{
2449 		ttuser_count =\
2450 		 ttloaded[0] = ttloaded[1] = ttloaded[2] = ttloaded[3] = ttloaded[4] = ttloaded[5] =\
2451 		 testttscale = activettscale =\
2452 		 sonic_blink = sonic_blink_twice = sonic_idle_start = sonic_idle_end =\
2453 		 tails_blink = tails_blink_twice = tails_idle_start = tails_idle_end =\
2454 		 knux_blink  = knux_blink_twice  = knux_idle_start  = knux_idle_end  = 0;
2455 
2456 		sonic_blinked_already = tails_blinked_already = knux_blinked_already = 1; // don't blink on the first idle cycle
2457 
2458 		if (curttmode == TTMODE_ALACROIX)
2459 			finalecount = -3; // hack so that frames don't advance during the entry wipe
2460 		else
2461 			finalecount = 0;
2462 		wipetypepost = menupres[MN_MAIN].enterwipe;
2463 	}
2464 	else
2465 		wipegamestate = GS_TITLESCREEN;
2466 
2467 	if (titlemap)
2468 	{
2469 		mapthing_t *startpos;
2470 
2471 		gamestate_t prevwipegamestate = wipegamestate;
2472 		titlemapinaction = TITLEMAP_LOADING;
2473 		titlemapcameraref = NULL;
2474 		gamemap = titlemap;
2475 
2476 		if (!mapheaderinfo[gamemap-1])
2477 			P_AllocMapHeader(gamemap-1);
2478 
2479 		maptol = mapheaderinfo[gamemap-1]->typeoflevel;
2480 		globalweather = mapheaderinfo[gamemap-1]->weather;
2481 
2482 		G_DoLoadLevel(true);
2483 		if (!titlemap)
2484 			return;
2485 
2486 		players[displayplayer].playerstate = PST_DEAD; // Don't spawn the player in dummy (I'm still a filthy cheater)
2487 
2488 		// Set Default Position
2489 		if (playerstarts[0])
2490 			startpos = playerstarts[0];
2491 		else if (deathmatchstarts[0])
2492 			startpos = deathmatchstarts[0];
2493 		else
2494 			startpos = NULL;
2495 
2496 		if (startpos)
2497 		{
2498 			camera.x = startpos->x << FRACBITS;
2499 			camera.y = startpos->y << FRACBITS;
2500 			camera.subsector = R_PointInSubsector(camera.x, camera.y);
2501 			camera.z = camera.subsector->sector->floorheight + (startpos->z << FRACBITS);
2502 			camera.angle = (startpos->angle % 360)*ANG1;
2503 			camera.aiming = 0;
2504 		}
2505 		else
2506 		{
2507 			camera.x = camera.y = camera.z = camera.angle = camera.aiming = 0;
2508 			camera.subsector = NULL; // toast is filthy too
2509 		}
2510 
2511 		camera.chase = true;
2512 		camera.height = 0;
2513 
2514 		// Run enter linedef exec for MN_MAIN, since this is where we start
2515 		if (menupres[MN_MAIN].entertag)
2516 			P_LinedefExecute(menupres[MN_MAIN].entertag, players[displayplayer].mo, NULL);
2517 
2518 		wipegamestate = prevwipegamestate;
2519 	}
2520 	else
2521 	{
2522 		titlemapinaction = TITLEMAP_OFF;
2523 		gamemap = 1; // g_game.c
2524 		CON_ClearHUD();
2525 	}
2526 
2527 	G_SetGamestate(GS_TITLESCREEN);
2528 
2529 	// IWAD dependent stuff.
2530 
2531 	animtimer = skullAnimCounter = 0;
2532 
2533 	demoDelayLeft = demoDelayTime;
2534 	demoIdleLeft = demoIdleTime;
2535 
2536 	F_CacheTitleScreen();
2537 }
2538 
F_UnloadAlacroixGraphics(SINT8 oldttscale)2539 static void F_UnloadAlacroixGraphics(SINT8 oldttscale)
2540 {
2541 	// This all gets freed by PU_PATCH_LOWPRIORITY when exiting the menus.
2542 	// When re-visiting the menus (e.g., from exiting in-game), the gfx are force-reloaded.
2543 	// So leftover addresses here should not be a problem.
2544 
2545 	UINT16 i;
2546 	oldttscale--; // zero-based index
2547 	for (i = 0; i < TTMAX_ALACROIX; i++)
2548 	{
2549 		if(ttembl[oldttscale][i]) { Patch_Free(ttembl[oldttscale][i]); ttembl[oldttscale][i] = 0; }
2550 		if(ttribb[oldttscale][i]) { Patch_Free(ttribb[oldttscale][i]); ttribb[oldttscale][i] = 0; }
2551 		if(ttsont[oldttscale][i]) { Patch_Free(ttsont[oldttscale][i]); ttsont[oldttscale][i] = 0; }
2552 		if(ttrobo[oldttscale][i]) { Patch_Free(ttrobo[oldttscale][i]); ttrobo[oldttscale][i] = 0; }
2553 		if(tttwot[oldttscale][i]) { Patch_Free(tttwot[oldttscale][i]); tttwot[oldttscale][i] = 0; }
2554 		if(ttrbtx[oldttscale][i]) { Patch_Free(ttrbtx[oldttscale][i]); ttrbtx[oldttscale][i] = 0; }
2555 		if(ttsoib[oldttscale][i]) { Patch_Free(ttsoib[oldttscale][i]); ttsoib[oldttscale][i] = 0; }
2556 		if(ttsoif[oldttscale][i]) { Patch_Free(ttsoif[oldttscale][i]); ttsoif[oldttscale][i] = 0; }
2557 		if(ttsoba[oldttscale][i]) { Patch_Free(ttsoba[oldttscale][i]); ttsoba[oldttscale][i] = 0; }
2558 		if(ttsobk[oldttscale][i]) { Patch_Free(ttsobk[oldttscale][i]); ttsobk[oldttscale][i] = 0; }
2559 		if(ttsodh[oldttscale][i]) { Patch_Free(ttsodh[oldttscale][i]); ttsodh[oldttscale][i] = 0; }
2560 		if(tttaib[oldttscale][i]) { Patch_Free(tttaib[oldttscale][i]); tttaib[oldttscale][i] = 0; }
2561 		if(tttaif[oldttscale][i]) { Patch_Free(tttaif[oldttscale][i]); tttaif[oldttscale][i] = 0; }
2562 		if(tttaba[oldttscale][i]) { Patch_Free(tttaba[oldttscale][i]); tttaba[oldttscale][i] = 0; }
2563 		if(tttabk[oldttscale][i]) { Patch_Free(tttabk[oldttscale][i]); tttabk[oldttscale][i] = 0; }
2564 		if(tttabt[oldttscale][i]) { Patch_Free(tttabt[oldttscale][i]); tttabt[oldttscale][i] = 0; }
2565 		if(tttaft[oldttscale][i]) { Patch_Free(tttaft[oldttscale][i]); tttaft[oldttscale][i] = 0; }
2566 		if(ttknib[oldttscale][i]) { Patch_Free(ttknib[oldttscale][i]); ttknib[oldttscale][i] = 0; }
2567 		if(ttknif[oldttscale][i]) { Patch_Free(ttknif[oldttscale][i]); ttknif[oldttscale][i] = 0; }
2568 		if(ttknba[oldttscale][i]) { Patch_Free(ttknba[oldttscale][i]); ttknba[oldttscale][i] = 0; }
2569 		if(ttknbk[oldttscale][i]) { Patch_Free(ttknbk[oldttscale][i]); ttknbk[oldttscale][i] = 0; }
2570 		if(ttkndh[oldttscale][i]) { Patch_Free(ttkndh[oldttscale][i]); ttkndh[oldttscale][i] = 0; }
2571 	}
2572 	ttloaded[oldttscale] = false;
2573 }
2574 
F_LoadAlacroixGraphics(SINT8 newttscale)2575 static void F_LoadAlacroixGraphics(SINT8 newttscale)
2576 {
2577 	UINT16 i, j;
2578 	lumpnum_t lumpnum;
2579 	char lumpname[9];
2580 	char names[22][5] = {
2581 		"EMBL",
2582 		"RIBB",
2583 		"SONT",
2584 		"ROBO",
2585 		"TWOT",
2586 		"RBTX",
2587 		"SOIB",
2588 		"SOIF",
2589 		"SOBA",
2590 		"SOBK",
2591 		"SODH",
2592 		"TAIB",
2593 		"TAIF",
2594 		"TABA",
2595 		"TABK",
2596 		"TABT",
2597 		"TAFT",
2598 		"KNIB",
2599 		"KNIF",
2600 		"KNBA",
2601 		"KNBK",
2602 		"KNDH"
2603 	};
2604 	char lumpnames[22][7];
2605 
2606 	newttscale--; // 0-based index
2607 
2608 	if (!ttloaded[newttscale])
2609 	{
2610 		for (j = 0; j < 22; j++)
2611 			sprintf(&lumpnames[j][0], "T%.1hu%s", (UINT16)( (UINT8)newttscale+1 ), names[j]);
2612 
2613 		LOADTTGFX(ttembl[newttscale], lumpnames[0], TTMAX_ALACROIX)
2614 		LOADTTGFX(ttribb[newttscale], lumpnames[1], TTMAX_ALACROIX)
2615 		LOADTTGFX(ttsont[newttscale], lumpnames[2], TTMAX_ALACROIX)
2616 		LOADTTGFX(ttrobo[newttscale], lumpnames[3], TTMAX_ALACROIX)
2617 		LOADTTGFX(tttwot[newttscale], lumpnames[4], TTMAX_ALACROIX)
2618 		LOADTTGFX(ttrbtx[newttscale], lumpnames[5], TTMAX_ALACROIX)
2619 		LOADTTGFX(ttsoib[newttscale], lumpnames[6], TTMAX_ALACROIX)
2620 		LOADTTGFX(ttsoif[newttscale], lumpnames[7], TTMAX_ALACROIX)
2621 		LOADTTGFX(ttsoba[newttscale], lumpnames[8], TTMAX_ALACROIX)
2622 		LOADTTGFX(ttsobk[newttscale], lumpnames[9], TTMAX_ALACROIX)
2623 		LOADTTGFX(ttsodh[newttscale], lumpnames[10], TTMAX_ALACROIX)
2624 		LOADTTGFX(tttaib[newttscale], lumpnames[11], TTMAX_ALACROIX)
2625 		LOADTTGFX(tttaif[newttscale], lumpnames[12], TTMAX_ALACROIX)
2626 		LOADTTGFX(tttaba[newttscale], lumpnames[13], TTMAX_ALACROIX)
2627 		LOADTTGFX(tttabk[newttscale], lumpnames[14], TTMAX_ALACROIX)
2628 		LOADTTGFX(tttabt[newttscale], lumpnames[15], TTMAX_ALACROIX)
2629 		LOADTTGFX(tttaft[newttscale], lumpnames[16], TTMAX_ALACROIX)
2630 		LOADTTGFX(ttknib[newttscale], lumpnames[17], TTMAX_ALACROIX)
2631 		LOADTTGFX(ttknif[newttscale], lumpnames[18], TTMAX_ALACROIX)
2632 		LOADTTGFX(ttknba[newttscale], lumpnames[19], TTMAX_ALACROIX)
2633 		LOADTTGFX(ttknbk[newttscale], lumpnames[20], TTMAX_ALACROIX)
2634 		LOADTTGFX(ttkndh[newttscale], lumpnames[21], TTMAX_ALACROIX)
2635 
2636 		ttloaded[newttscale] = true;
2637 	}
2638 }
2639 
2640 #undef LOADTTGFX
2641 
F_FigureActiveTtScale(void)2642 static void F_FigureActiveTtScale(void)
2643 {
2644 	SINT8 newttscale = max(1, min(6, vid.dupx));
2645 	SINT8 oldttscale = activettscale;
2646 
2647 	if (newttscale == testttscale)
2648 		return;
2649 
2650 	// We have a new ttscale, so load gfx
2651 	if(oldttscale > 0)
2652 		F_UnloadAlacroixGraphics(oldttscale);
2653 
2654 	testttscale = newttscale;
2655 
2656 	// If ttscale is unavailable: look for lower scales, then higher scales.
2657 	for (; newttscale >= 1; newttscale--)
2658 	{
2659 		if (ttavailable[newttscale-1])
2660 			break;
2661 	}
2662 
2663 	for (; newttscale <= 6; newttscale++)
2664 	{
2665 		if (ttavailable[newttscale-1])
2666 			break;
2667 	}
2668 
2669 	activettscale = (newttscale >= 1 && newttscale <= 6) ? newttscale : 0;
2670 
2671 	if(activettscale > 0)
2672 		F_LoadAlacroixGraphics(activettscale);
2673 }
2674 
2675 // (no longer) De-Demo'd Title Screen
F_TitleScreenDrawer(void)2676 void F_TitleScreenDrawer(void)
2677 {
2678 	boolean hidepics;
2679 	fixed_t sc = FRACUNIT / max(1, curttscale);
2680 	INT32 whitefade = 0;
2681 	UINT8 *whitecol[2] = {NULL, NULL};
2682 
2683 	if (modeattacking)
2684 		return; // We likely came here from retrying. Don't do a damn thing.
2685 
2686 	// Draw that sky!
2687 	if (curbgcolor >= 0)
2688 		V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, curbgcolor);
2689 	else if (!curbghide || !titlemapinaction || gamestate == GS_WAITINGPLAYERS)
2690 		F_SkyScroll(curbgxspeed, curbgyspeed, curbgname);
2691 
2692 	// Don't draw outside of the title screen, or if the patch isn't there.
2693 	if (gamestate != GS_TITLESCREEN && gamestate != GS_WAITINGPLAYERS)
2694 		return;
2695 
2696 	// Don't draw if title mode is set to Old/None and the patch isn't there
2697 	if (!ttwing && (curttmode == TTMODE_OLD || curttmode == TTMODE_NONE))
2698 		return;
2699 
2700 	// rei|miru: use title pics?
2701 	hidepics = curhidepics;
2702 	if (hidepics)
2703 		goto luahook;
2704 
2705 	switch(curttmode)
2706 	{
2707 		case TTMODE_OLD:
2708 		case TTMODE_NONE:
2709 			V_DrawSciencePatch(30<<FRACBITS, 14<<FRACBITS, 0, ttwing, sc);
2710 
2711 			if (finalecount < 57)
2712 			{
2713 				if (finalecount == 35)
2714 					V_DrawSciencePatch(115<<FRACBITS, 15<<FRACBITS, 0, ttspop1, sc);
2715 				else if (finalecount == 36)
2716 					V_DrawSciencePatch(114<<FRACBITS, 15<<FRACBITS, 0,ttspop2, sc);
2717 				else if (finalecount == 37)
2718 					V_DrawSciencePatch(113<<FRACBITS, 15<<FRACBITS, 0,ttspop3, sc);
2719 				else if (finalecount == 38)
2720 					V_DrawSciencePatch(112<<FRACBITS, 15<<FRACBITS, 0,ttspop4, sc);
2721 				else if (finalecount == 39)
2722 					V_DrawSciencePatch(111<<FRACBITS, 15<<FRACBITS, 0,ttspop5, sc);
2723 				else if (finalecount == 40)
2724 					V_DrawSciencePatch(110<<FRACBITS, 15<<FRACBITS, 0, ttspop6, sc);
2725 				else if (finalecount >= 41 && finalecount <= 44)
2726 					V_DrawSciencePatch(109<<FRACBITS, 15<<FRACBITS, 0, ttspop7, sc);
2727 				else if (finalecount >= 45 && finalecount <= 48)
2728 					V_DrawSciencePatch(108<<FRACBITS, 12<<FRACBITS, 0, ttsprep1, sc);
2729 				else if (finalecount >= 49 && finalecount <= 52)
2730 					V_DrawSciencePatch(107<<FRACBITS, 9<<FRACBITS, 0, ttsprep2, sc);
2731 				else if (finalecount >= 53 && finalecount <= 56)
2732 					V_DrawSciencePatch(106<<FRACBITS, 6<<FRACBITS, 0, ttswip1, sc);
2733 				V_DrawSciencePatch(93<<FRACBITS, 106<<FRACBITS, 0, ttsonic, sc);
2734 			}
2735 			else
2736 			{
2737 				V_DrawSciencePatch(93<<FRACBITS, 106<<FRACBITS, 0,ttsonic, sc);
2738 				if (finalecount/5 & 1)
2739 					V_DrawSciencePatch(100<<FRACBITS, 3<<FRACBITS, 0,ttswave1, sc);
2740 				else
2741 					V_DrawSciencePatch(100<<FRACBITS, 3<<FRACBITS, 0,ttswave2, sc);
2742 			}
2743 
2744 			V_DrawSciencePatch(48<<FRACBITS, 142<<FRACBITS, 0,ttbanner, sc);
2745 			break;
2746 
2747 		case TTMODE_ALACROIX:
2748 			//
2749 			// PRE-INTRO: WING ON BLACK BACKGROUND
2750 			//
2751 
2752 			// Figure the gfx scale and load gfx if necessary
2753 			F_FigureActiveTtScale();
2754 
2755 			if (!activettscale) // invalid scale, draw nothing
2756 				break;
2757 			sc = FRACUNIT / activettscale;
2758 
2759 			// Start at black background. Draw it until tic 30, where we replace with a white flash.
2760 			//
2761 			// TODO: How to NOT draw the titlemap while this background is drawn?
2762 			//
2763 			if (finalecount <= 29)
2764 				V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 31);
2765 			// Flash at tic 30, timed to O__TITLE percussion. Hold the flash until tic 34.
2766 			// After tic 34, fade the flash until tic 44.
2767 			else
2768 			{
2769 				if (finalecount > 29 && finalecount < 35)
2770 					V_DrawFadeScreen(0, (whitefade = 9));
2771 				else if (finalecount > 34 && 44-finalecount > 0 && 44-finalecount < 10)
2772 					V_DrawFadeScreen(0, 44-finalecount);
2773 				if (39-finalecount > 0)
2774 				{
2775 					whitefade = (9 - (39-finalecount))<<V_ALPHASHIFT;
2776 					whitecol[0] = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_SUPERGOLD3, GTC_CACHE);
2777 					whitecol[1] = R_GetTranslationColormap(TC_ALLWHITE, 0, GTC_CACHE);
2778 				}
2779 			}
2780 
2781 			// Draw emblem
2782 			V_DrawSciencePatch(40<<FRACBITS, 20<<FRACBITS, 0, TTEMBL[0], sc);
2783 
2784 			if (whitecol[0])
2785 			{
2786 				V_DrawFixedPatch(40<<FRACBITS, 20<<FRACBITS, sc, whitefade, TTEMBL[0], whitecol[0]);
2787 				V_DrawFixedPatch(40<<FRACBITS, 20<<FRACBITS, sc, V_TRANSLUCENT + ((whitefade/2) & V_ALPHAMASK), TTEMBL[0], whitecol[1]);
2788 			}
2789 
2790 			// Animate SONIC ROBO BLAST 2 before the white flash at tic 30.
2791 			if (finalecount <= 29)
2792 			{
2793 				// Ribbon unfurls, revealing SONIC text, from tic 0 to tic 24. SONIC text is pre-baked into this ribbon graphic.
2794 				V_DrawSciencePatch(39<<FRACBITS, 88<<FRACBITS, 0, TTRIBB[min(max(0, finalecount), 24)], sc);
2795 
2796 				// Darken non-text things.
2797 				V_DrawFadeScreen(0xFF00, 12);
2798 
2799 				// Animate SONIC text while the ribbon unfurls, from tic 0 to tic 28.
2800 				if(finalecount >= 0)
2801 					V_DrawSciencePatch(89<<FRACBITS, 92<<FRACBITS, 0, TTSONT[min(finalecount, 28)], sc);
2802 
2803 				// Fade in ROBO BLAST 2 starting at tic 10.
2804 				if (finalecount > 9)
2805 				{
2806 					INT32 fadeval = 0;
2807 
2808 					// Fade between tic 10 and tic 29.
2809 					if (finalecount < 30)
2810 					{
2811 						UINT8 fadecounter = 30-finalecount;
2812 						switch(fadecounter)
2813 						{
2814 							case 20: case 19: fadeval = V_90TRANS; break;
2815 							case 18: case 17: fadeval = V_80TRANS; break;
2816 							case 16: case 15: fadeval = V_70TRANS; break;
2817 							case 14: case 13: fadeval = V_60TRANS; break;
2818 							case 12: case 11: fadeval = V_TRANSLUCENT; break;
2819 							case 10: case 9: fadeval = V_40TRANS; break;
2820 							case 8: case 7: fadeval = V_30TRANS; break;
2821 							case 6: case 5: fadeval = V_20TRANS; break;
2822 							case 4: case 3: fadeval = V_10TRANS; break;
2823 							default: break;
2824 						}
2825 					}
2826 					V_DrawSciencePatch(79<<FRACBITS, 132<<FRACBITS, fadeval, TTROBO[0], sc);
2827 
2828 					// Draw the TWO from tic 16 to tic 31, so the TWO lands right when the screen flashes white.
2829 					if(finalecount > 15)
2830 						V_DrawSciencePatch(106<<FRACBITS, 118<<FRACBITS, fadeval, TTTWOT[min(finalecount-16, 15)], sc);
2831 				}
2832 			}
2833 
2834 			//
2835 			// ALACROIX CHARACTER FRAMES
2836 			//
2837 			// Start all animation from tic 34 (or whenever the white flash begins to fade; see below.)
2838 			// Delay the start a bit for better music timing.
2839 			//
2840 
2841 #define CHARSTART 41
2842 #define SONICSTART (CHARSTART+0)
2843 #define SONICIDLE (SONICSTART+57)
2844 #define SONICX 89
2845 #define SONICY 13
2846 #define TAILSSTART (CHARSTART+27)
2847 #define TAILSIDLE (TAILSSTART+60)
2848 #define TAILSX 35
2849 #define TAILSY 19
2850 #define KNUXSTART (CHARSTART+44)
2851 #define KNUXIDLE (KNUXSTART+70)
2852 #define KNUXX 167
2853 #define KNUXY 7
2854 
2855 			// Decide who gets to blink or not.
2856 			// Make this decision at the END of an idle/blink cycle.
2857 			// Upon first idle, both idle_start and idle_end will be 0.
2858 
2859 			if (finalecount >= KNUXIDLE)
2860 			{
2861 				if (!knux_idle_start || finalecount - knux_idle_start >= knux_idle_end)
2862 				{
2863 					if (knux_blink)
2864 					{
2865 						knux_blink = false; // don't run the cycle twice in a row
2866 						knux_blinked_already = true;
2867 					}
2868 					else if (knux_blinked_already) // or after the first non-blink cycle, either.
2869 						knux_blinked_already = false;
2870 					else
2871 					{
2872 						// make this chance higher than Sonic/Tails because Knux's idle cycle is longer
2873 						knux_blink = !(M_RandomKey(100) % 2);
2874 						knux_blink_twice = knux_blink ? !(M_RandomKey(100) % 5) : false;
2875 					}
2876 					knux_idle_start = finalecount;
2877 				}
2878 
2879 				knux_idle_end = knux_blink ? (knux_blink_twice ? 17 : 7) : 46;
2880 			}
2881 
2882 			if (finalecount >= TAILSIDLE)
2883 			{
2884 				if (!tails_idle_start || finalecount - tails_idle_start >= tails_idle_end)
2885 				{
2886 					if (tails_blink)
2887 					{
2888 						tails_blink = false; // don't run the cycle twice in a row
2889 						tails_blinked_already = true;
2890 					}
2891 					else if (tails_blinked_already) // or after the first non-blink cycle, either.
2892 						tails_blinked_already = false;
2893 					else
2894 					{
2895 						tails_blink = !(M_RandomKey(100) % 3);
2896 						tails_blink_twice = tails_blink ? !(M_RandomKey(100) % 5) : false;
2897 					}
2898 					tails_idle_start = finalecount;
2899 				}
2900 
2901 				// Tails does not actually have a non-blink idle cycle, but make up a number
2902 				// so he can still blink.
2903 				tails_idle_end = tails_blink ? (tails_blink_twice ? 17 : 7) : 30;
2904 			}
2905 
2906 			if (finalecount >= SONICIDLE)
2907 			{
2908 				if (!sonic_idle_start || finalecount - sonic_idle_start >= sonic_idle_end)
2909 				{
2910 					if (sonic_blink)
2911 					{
2912 						sonic_blink = false; // don't run the cycle twice in a row
2913 						sonic_blinked_already = true;
2914 					}
2915 					else if (sonic_blinked_already) // or after the first non-blink cycle, either.
2916 						sonic_blinked_already = false;
2917 					else
2918 					{
2919 						sonic_blink = !(M_RandomKey(100) % 3);
2920 						sonic_blink_twice = sonic_blink ? !(M_RandomKey(100) % 5) : false;
2921 					}
2922 					sonic_idle_start = finalecount;
2923 				}
2924 
2925 				sonic_idle_end = sonic_blink ? (sonic_blink_twice ? 17 : 7) : 25;
2926 			}
2927 
2928 
2929 			//
2930 			// BACK TAIL LAYER
2931 			//
2932 
2933 			if (finalecount >= TAILSSTART)
2934 			{
2935 				if (finalecount >= TAILSIDLE)
2936 				{
2937 					//
2938 					// Tails Back Tail Layer Idle
2939 					//
2940 					SINT8 taftcount = (finalecount - (TAILSIDLE)) % 41;
2941 					if      (taftcount >= 0   && taftcount < 5  )
2942 						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTABT[0 ], sc);
2943 					else if (taftcount >= 5   && taftcount < 9 )
2944 						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTABT[1 ], sc);
2945 					else if (taftcount >= 9   && taftcount < 12 )
2946 						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTABT[2 ], sc);
2947 					else if (taftcount >= 12  && taftcount < 14 )
2948 						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTABT[3 ], sc);
2949 					else if (taftcount >= 14  && taftcount < 17 )
2950 						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTABT[4 ], sc);
2951 					else if (taftcount >= 17  && taftcount < 21 )
2952 						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTABT[5 ], sc);
2953 					else if (taftcount >= 21  && taftcount < 24 )
2954 						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTABT[6 ], sc);
2955 					else if (taftcount >= 24  && taftcount < 25 )
2956 						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTABT[7 ], sc);
2957 					else if (taftcount >= 25  && taftcount < 28 )
2958 						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTABT[8 ], sc);
2959 					else if (taftcount >= 28  && taftcount < 31 )
2960 						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTABT[9 ], sc);
2961 					else if (taftcount >= 31  && taftcount < 35 )
2962 						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTABT[10], sc);
2963 					else if (taftcount >= 35  && taftcount < 41 )
2964 						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTABT[11], sc);
2965 				}
2966 			}
2967 
2968 			//
2969 			// FRONT TAIL LAYER
2970 			//
2971 
2972 			if (finalecount >= TAILSSTART)
2973 			{
2974 				if (finalecount >= TAILSIDLE)
2975 				{
2976 					//
2977 					// Tails Front Tail Layer Idle
2978 					//
2979 					SINT8 tabtcount = (finalecount - (TAILSIDLE)) % 41;
2980 					if      (tabtcount >= 0   && tabtcount < 6  )
2981 						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTAFT[0 ], sc);
2982 					else if (tabtcount >= 6   && tabtcount < 11 )
2983 						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTAFT[1 ], sc);
2984 					else if (tabtcount >= 11  && tabtcount < 15 )
2985 						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTAFT[2 ], sc);
2986 					else if (tabtcount >= 15  && tabtcount < 18 )
2987 						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTAFT[3 ], sc);
2988 					else if (tabtcount >= 18  && tabtcount < 19 )
2989 						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTAFT[4 ], sc);
2990 					else if (tabtcount >= 19  && tabtcount < 22 )
2991 						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTAFT[5 ], sc);
2992 					else if (tabtcount >= 22  && tabtcount < 27 )
2993 						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTAFT[6 ], sc);
2994 					else if (tabtcount >= 27  && tabtcount < 30 )
2995 						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTAFT[7 ], sc);
2996 					else if (tabtcount >= 30  && tabtcount < 31 )
2997 						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTAFT[8 ], sc);
2998 					else if (tabtcount >= 31  && tabtcount < 34 )
2999 						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTAFT[9 ], sc);
3000 					else if (tabtcount >= 34  && tabtcount < 37 )
3001 						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTAFT[10], sc);
3002 					else if (tabtcount >= 37  && tabtcount < 41 )
3003 						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTAFT[11], sc);
3004 				}
3005 			}
3006 
3007 			//
3008 			// BACK LAYER CHARACTERS
3009 			//
3010 
3011 			if (finalecount >= KNUXSTART)
3012 			{
3013 				if (finalecount < KNUXIDLE)
3014 				{
3015 					//
3016 					// Knux Back Layer Intro
3017 					//
3018 					if      (finalecount >= KNUXSTART+0   && finalecount < KNUXSTART+6  )
3019 						V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNIB[0 ], sc);
3020 					else if (finalecount >= KNUXSTART+6   && finalecount < KNUXSTART+10 )
3021 						V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNIB[1 ], sc);
3022 					else if (finalecount >= KNUXSTART+10  && finalecount < KNUXSTART+13 )
3023 						V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNIB[2 ], sc);
3024 					else if (finalecount >= KNUXSTART+13  && finalecount < KNUXSTART+15 )
3025 						V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNIB[3 ], sc);
3026 					else if (finalecount >= KNUXSTART+15  && finalecount < KNUXSTART+18 )
3027 						V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNIB[4 ], sc);
3028 					else if (finalecount >= KNUXSTART+18  && finalecount < KNUXSTART+22 )
3029 						V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNIB[5 ], sc);
3030 					else if (finalecount >= KNUXSTART+22  && finalecount < KNUXSTART+28 )
3031 						V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNIB[6 ], sc);
3032 					else if (finalecount >= KNUXSTART+28  && finalecount < KNUXSTART+32 )
3033 						V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNIB[7 ], sc);
3034 					else if (finalecount >= KNUXSTART+32  && finalecount < KNUXSTART+35 )
3035 						V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNIB[8 ], sc);
3036 					else if (finalecount >= KNUXSTART+35  && finalecount < KNUXSTART+40 )
3037 						V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNIB[9 ], sc);
3038 					else if (finalecount >= KNUXSTART+40  && finalecount < KNUXSTART+41 )
3039 						V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNIB[10], sc);
3040 					else if (finalecount >= KNUXSTART+41  && finalecount < KNUXSTART+44 )
3041 						V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNIB[11], sc);
3042 					else if (finalecount >= KNUXSTART+44  && finalecount < KNUXSTART+50 )
3043 						V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNIB[12], sc);
3044 					else if (finalecount >= KNUXSTART+50  && finalecount < KNUXSTART+56 )
3045 						V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNIB[13], sc);
3046 					else if (finalecount >= KNUXSTART+56  && finalecount < KNUXSTART+57 )
3047 						V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNIB[14], sc);
3048 					else if (finalecount >= KNUXSTART+57  && finalecount < KNUXSTART+60 )
3049 						V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNIB[15], sc);
3050 					else if (finalecount >= KNUXSTART+60  && finalecount < KNUXSTART+63 )
3051 						V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNIB[16], sc);
3052 					else if (finalecount >= KNUXSTART+63  && finalecount < KNUXSTART+67 )
3053 						V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNIB[17], sc);
3054 					else if (finalecount >= KNUXSTART+67  && finalecount < KNUXSTART+70 )
3055 						V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNIB[18], sc);
3056 					// Start idle animation (frame K20-B)
3057 				}
3058 				else
3059 				{
3060 					//
3061 					// Knux Back Layer Idle
3062 					//
3063 					if (!knux_blink)
3064 						V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNBA[0], sc);
3065 					else
3066 					{
3067 						//
3068 						// Knux Blinking
3069 						//
3070 						SINT8 idlecount = finalecount - knux_idle_start;
3071 						if      (idlecount >= 0  && idlecount < 2 )
3072 							V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNBK[0], sc);
3073 						else if (idlecount >= 2  && idlecount < 6 )
3074 							V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNBK[1], sc);
3075 						else if (idlecount >= 6  && idlecount < 7 )
3076 							V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNBK[2], sc);
3077 						// We reach this point if knux_blink_twice == true
3078 						else if (idlecount >= 7  && idlecount < 10)
3079 							V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNBA[0], sc);
3080 						else if (idlecount >= 10 && idlecount < 12)
3081 							V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNBK[0], sc);
3082 						else if (idlecount >= 12 && idlecount < 16)
3083 							V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNBK[1], sc);
3084 						else if (idlecount >= 16 && idlecount < 17)
3085 							V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNBK[2], sc);
3086 					}
3087 				}
3088 			}
3089 
3090 			if (finalecount >= TAILSSTART)
3091 			{
3092 				if (finalecount < TAILSIDLE)
3093 				{
3094 					//
3095 					// Tails Back Layer Intro
3096 					//
3097 					if      (finalecount >= TAILSSTART+0   && finalecount < TAILSSTART+6  )
3098 						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTAIB[0 ], sc);
3099 					else if (finalecount >= TAILSSTART+6   && finalecount < TAILSSTART+10 )
3100 						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTAIB[1 ], sc);
3101 					else if (finalecount >= TAILSSTART+10  && finalecount < TAILSSTART+12 )
3102 						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTAIB[2 ], sc);
3103 					else if (finalecount >= TAILSSTART+12  && finalecount < TAILSSTART+16 )
3104 						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTAIB[3 ], sc);
3105 					else if (finalecount >= TAILSSTART+16  && finalecount < TAILSSTART+22 )
3106 						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTAIB[4 ], sc);
3107 					else if (finalecount >= TAILSSTART+22  && finalecount < TAILSSTART+23 )
3108 						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTAIB[5 ], sc);
3109 					else if (finalecount >= TAILSSTART+23  && finalecount < TAILSSTART+26 )
3110 						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTAIB[6 ], sc);
3111 					else if (finalecount >= TAILSSTART+26  && finalecount < TAILSSTART+30 )
3112 						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTAIB[7 ], sc);
3113 					else if (finalecount >= TAILSSTART+30  && finalecount < TAILSSTART+35 )
3114 						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTAIB[8 ], sc);
3115 					else if (finalecount >= TAILSSTART+35  && finalecount < TAILSSTART+41 )
3116 						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTAIB[9 ], sc);
3117 					else if (finalecount >= TAILSSTART+41  && finalecount < TAILSSTART+43 )
3118 						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTAIB[10], sc);
3119 					else if (finalecount >= TAILSSTART+43  && finalecount < TAILSSTART+47 )
3120 						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTAIB[11], sc);
3121 					else if (finalecount >= TAILSSTART+47  && finalecount < TAILSSTART+51 )
3122 						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTAIB[12], sc);
3123 					else if (finalecount >= TAILSSTART+51  && finalecount < TAILSSTART+53 )
3124 						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTAIB[13], sc);
3125 					else if (finalecount >= TAILSSTART+53  && finalecount < TAILSSTART+56 )
3126 						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTAIB[14], sc);
3127 					else if (finalecount >= TAILSSTART+56  && finalecount < TAILSSTART+60 )
3128 						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTAIB[15], sc);
3129 					// Start idle animation (frame T17-B)
3130 				}
3131 				else
3132 				{
3133 					//
3134 					// Tails Back Layer Idle
3135 					//
3136 					if (!tails_blink)
3137 						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTABA[0], sc);
3138 					else
3139 					{
3140 						//
3141 						// Tails Blinking
3142 						//
3143 						SINT8 idlecount = finalecount - tails_idle_start;
3144 						if      (idlecount >= +0  && idlecount < +2 )
3145 							V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTABK[0], sc);
3146 						else if (idlecount >= +2  && idlecount < +6 )
3147 							V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTABK[1], sc);
3148 						else if (idlecount >= +6  && idlecount < +7 )
3149 							V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTABK[2], sc);
3150 						// We reach this point if tails_blink_twice == true
3151 						else if (idlecount >= +7  && idlecount < +10)
3152 							V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTABA[0], sc);
3153 						else if (idlecount >= +10 && idlecount < +12)
3154 							V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTABK[0], sc);
3155 						else if (idlecount >= +12 && idlecount < +16)
3156 							V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTABK[1], sc);
3157 						else if (idlecount >= +16 && idlecount < +17)
3158 							V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTABK[2], sc);
3159 					}
3160 				}
3161 			}
3162 
3163 			if (finalecount >= SONICSTART)
3164 			{
3165 				if (finalecount < SONICIDLE)
3166 				{
3167 					//
3168 					// Sonic Back Layer Intro
3169 					//
3170 					if      (finalecount >= SONICSTART+0   && finalecount < SONICSTART+6  )
3171 						V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSOIB[0 ], sc);
3172 					else if (finalecount >= SONICSTART+6   && finalecount < SONICSTART+11 )
3173 						V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSOIB[1 ], sc);
3174 					else if (finalecount >= SONICSTART+11  && finalecount < SONICSTART+14 )
3175 						V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSOIB[2 ], sc);
3176 					else if (finalecount >= SONICSTART+14  && finalecount < SONICSTART+18 )
3177 						V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSOIB[3 ], sc);
3178 					else if (finalecount >= SONICSTART+18  && finalecount < SONICSTART+19 )
3179 						V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSOIB[4 ], sc);
3180 					else if (finalecount >= SONICSTART+19  && finalecount < SONICSTART+27 )
3181 						V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSOIB[5 ], sc);
3182 					else if (finalecount >= SONICSTART+27  && finalecount < SONICSTART+31 )
3183 						V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSOIB[6 ], sc);
3184 					//else if (finalecount >= SONICSTART+31  && finalecount < SONICSTART+33 )
3185 					//  Frame is blank
3186 					//	V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSOIB[7 ], sc);
3187 					else if (finalecount >= SONICSTART+33  && finalecount < SONICSTART+36 )
3188 						V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSOIB[8 ], sc);
3189 					else if (finalecount >= SONICSTART+36  && finalecount < SONICSTART+40 )
3190 						V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSOIB[9 ], sc);
3191 					else if (finalecount >= SONICSTART+40  && finalecount < SONICSTART+44 )
3192 						V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSOIB[10], sc);
3193 					else if (finalecount >= SONICSTART+44  && finalecount < SONICSTART+47 )
3194 						V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSOIB[11], sc);
3195 					else if (finalecount >= SONICSTART+47  && finalecount < SONICSTART+49 )
3196 						V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSOIB[12], sc);
3197 					else if (finalecount >= SONICSTART+49  && finalecount < SONICSTART+50 )
3198 						V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSOIB[13], sc);
3199 					else if (finalecount >= SONICSTART+50  && finalecount < SONICSTART+53 )
3200 						V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSOIB[14], sc);
3201 					else if (finalecount >= SONICSTART+53  && finalecount < SONICSTART+57 )
3202 						V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSOIB[15], sc);
3203 					// Start idle animation (frame S17-B)
3204 				}
3205 				else
3206 				{
3207 					//
3208 					// Sonic Back Layer Idle
3209 					//
3210 					if (!sonic_blink)
3211 						V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSOBA[0], sc);
3212 					else
3213 					{
3214 						//
3215 						// Sonic Blinking
3216 						//
3217 						SINT8 idlecount = finalecount - sonic_idle_start;
3218 						if      (idlecount >= 0  && idlecount < 2 )
3219 							V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSOBK[0], sc);
3220 						else if (idlecount >= 2  && idlecount < 6 )
3221 							V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSOBK[1], sc);
3222 						else if (idlecount >= 6  && idlecount < 7 )
3223 							V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSOBK[2], sc);
3224 						// We reach this point if sonic_blink_twice == true
3225 						else if (idlecount >= 7  && idlecount < 10)
3226 							V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSOBA[0], sc);
3227 						else if (idlecount >= 10 && idlecount < 12)
3228 							V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSOBK[0], sc);
3229 						else if (idlecount >= 12 && idlecount < 16)
3230 							V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSOBK[1], sc);
3231 						else if (idlecount >= 16 && idlecount < 17)
3232 							V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSOBK[2], sc);
3233 					}
3234 				}
3235 			}
3236 
3237 			//
3238 			// LOGO LAYER
3239 			//
3240 
3241 			// After tic 34, starting when the flash fades,
3242 			// draw the combined ribbon and SONIC ROBO BLAST 2 logo. Note the different Y value, because this
3243 			// graphic is cropped differently from the unfurling ribbon.
3244 			if (finalecount > 29)
3245 				V_DrawSciencePatch(39<<FRACBITS, 93<<FRACBITS, 0, TTRBTX[0], sc);
3246 
3247 			if (whitecol[0])
3248 			{
3249 				V_DrawFixedPatch(39<<FRACBITS, 93<<FRACBITS, sc, whitefade, TTRBTX[0], whitecol[0]);
3250 				V_DrawFixedPatch(39<<FRACBITS, 93<<FRACBITS, sc, V_TRANSLUCENT + ((whitefade/2) & V_ALPHAMASK), TTRBTX[0], whitecol[1]);
3251 			}
3252 
3253 			//
3254 			// FRONT LAYER CHARACTERS
3255 			//
3256 
3257 			if (finalecount >= KNUXSTART)
3258 			{
3259 				if (finalecount < KNUXIDLE)
3260 				{
3261 					//
3262 					// Knux Front Layer Intro
3263 					//
3264 					if      (finalecount >= KNUXSTART+22  && finalecount < KNUXSTART+28 )
3265 						V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNIF[6 ], sc);
3266 					else if (finalecount >= KNUXSTART+28  && finalecount < KNUXSTART+32 )
3267 						V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNIF[7 ], sc);
3268 					else if (finalecount >= KNUXSTART+32  && finalecount < KNUXSTART+35 )
3269 						V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNIF[8 ], sc);
3270 				}
3271 				else
3272 				{
3273 					//
3274 					// Knux Front Layer Idle
3275 					//
3276 					if (!knux_blink)
3277 					{
3278 						SINT8 idlecount = finalecount - knux_idle_start;
3279 						if      (idlecount >= 0  && idlecount < 5 )
3280 							V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNDH[0 ], sc);
3281 						else if (idlecount >= 5  && idlecount < 10)
3282 							V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNDH[1 ], sc);
3283 						else if (idlecount >= 10 && idlecount < 13)
3284 							V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNDH[2 ], sc);
3285 						else if (idlecount >= 13 && idlecount < 14)
3286 							V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNDH[3 ], sc);
3287 						else if (idlecount >= 14 && idlecount < 17)
3288 							V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNDH[4 ], sc);
3289 						else if (idlecount >= 17 && idlecount < 21)
3290 							V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNDH[5 ], sc);
3291 						else if (idlecount >= 21 && idlecount < 27)
3292 							V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNDH[6 ], sc);
3293 						else if (idlecount >= 27 && idlecount < 32)
3294 							V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNDH[7 ], sc);
3295 						else if (idlecount >= 32 && idlecount < 34)
3296 							V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNDH[8 ], sc);
3297 						else if (idlecount >= 34 && idlecount < 37)
3298 							V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNDH[9 ], sc);
3299 						else if (idlecount >= 37 && idlecount < 39)
3300 							V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNDH[10], sc);
3301 						else if (idlecount >= 39 && idlecount < 42)
3302 							V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNDH[11], sc);
3303 						else if (idlecount >= 42 && idlecount < 46)
3304 							V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNDH[12], sc);
3305 					}
3306 					else
3307 						V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNDH[0 ], sc);
3308 				}
3309 			}
3310 
3311 			if (finalecount >= TAILSSTART)
3312 			{
3313 				if (finalecount < TAILSIDLE)
3314 				{
3315 					//
3316 					// Tails Front Layer Intro
3317 					//
3318 					if      (finalecount >= TAILSSTART+26  && finalecount < TAILSSTART+30 )
3319 						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTAIF[7 ], sc);
3320 					else if (finalecount >= TAILSSTART+30  && finalecount < TAILSSTART+35 )
3321 						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTAIF[8 ], sc);
3322 					else if (finalecount >= TAILSSTART+35  && finalecount < TAILSSTART+41 )
3323 						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTAIF[9 ], sc);
3324 					else if (finalecount >= TAILSSTART+41  && finalecount < TAILSSTART+43 )
3325 						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTAIF[10], sc);
3326 					else if (finalecount >= TAILSSTART+43  && finalecount < TAILSSTART+47 )
3327 						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTAIF[11], sc);
3328 					else if (finalecount >= TAILSSTART+47  && finalecount < TAILSSTART+51 )
3329 						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTAIF[12], sc);
3330 				}
3331 				// No Tails Front Layer Idle
3332 			}
3333 
3334 			if (finalecount >= SONICSTART)
3335 			{
3336 				if (finalecount < SONICIDLE)
3337 				{
3338 					//
3339 					// Sonic Front Layer Intro
3340 					//
3341 					if      (finalecount >= SONICSTART+19  && finalecount < SONICSTART+27 )
3342 						V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSOIF[5 ], sc);
3343 					else if (finalecount >= SONICSTART+27  && finalecount < SONICSTART+31 )
3344 						V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSOIF[6 ], sc);
3345 					else if (finalecount >= SONICSTART+31  && finalecount < SONICSTART+33 )
3346 						V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSOIF[7 ], sc);
3347 					else if (finalecount >= SONICSTART+33  && finalecount < SONICSTART+36 )
3348 						V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSOIF[8 ], sc);
3349 					else if (finalecount >= SONICSTART+36  && finalecount < SONICSTART+40 )
3350 						V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSOIF[9 ], sc);
3351 					else if (finalecount >= SONICSTART+40  && finalecount < SONICSTART+44 )
3352 						V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSOIF[10], sc);
3353 					else if (finalecount >= SONICSTART+44  && finalecount < SONICSTART+47 )
3354 						V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSOIF[11], sc);
3355 					// ...
3356 					else if (finalecount >= SONICSTART+53  && finalecount < SONICSTART+57 )
3357 						V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSOIF[15], sc);
3358 				}
3359 				else
3360 				{
3361 					//
3362 					// Sonic Front Layer Idle
3363 					//
3364 					if (!sonic_blink)
3365 					{
3366 						SINT8 idlecount = finalecount - sonic_idle_start;
3367 						if      (idlecount >= 0  && idlecount < 5 )
3368 							V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSODH[0], sc);
3369 						else if (idlecount >= 5  && idlecount < 8 )
3370 							V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSODH[1], sc);
3371 						else if (idlecount >= 8  && idlecount < 9 )
3372 							V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSODH[2], sc);
3373 						else if (idlecount >= 9  && idlecount < 12)
3374 							V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSODH[3], sc);
3375 						else if (idlecount >= 12 && idlecount < 17)
3376 							V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSODH[4], sc);
3377 						else if (idlecount >= 17 && idlecount < 19)
3378 							V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSODH[5], sc);
3379 						else if (idlecount >= 19 && idlecount < 21)
3380 							V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSODH[6], sc);
3381 						else if (idlecount >= 21 && idlecount < 22)
3382 							V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSODH[7], sc);
3383 						else if (idlecount >= 22 && idlecount < 25)
3384 							V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSODH[8], sc);
3385 					}
3386 					else
3387 						V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSODH[0], sc);
3388 				}
3389 			}
3390 
3391 #undef CHARSTART
3392 #undef SONICSTART
3393 #undef SONICIDLE
3394 #undef SONICX
3395 #undef SONICY
3396 #undef TAILSSTART
3397 #undef TAILSIDLE
3398 #undef TAILSX
3399 #undef TAILSY
3400 #undef KNUXSTART
3401 #undef KNUXIDLE
3402 #undef KNUXX
3403 #undef KNUXY
3404 
3405 			break;
3406 
3407 		case TTMODE_USER:
3408 			if (!ttuser[max(0, ttuser_count)])
3409 			{
3410 				if(curttloop > -1 && ttuser[curttloop])
3411 					ttuser_count = curttloop;
3412 				else if (ttuser[max(0, ttuser_count-1)])
3413 					ttuser_count = max(0, ttuser_count-1);
3414 				else
3415 					break; // draw nothing
3416 			}
3417 
3418 			V_DrawSciencePatch(curttx<<FRACBITS, curtty<<FRACBITS, 0, ttuser[ttuser_count], sc);
3419 
3420 			if (!(finalecount % max(1, curtttics)))
3421 				ttuser_count++;
3422 			break;
3423 	}
3424 
3425 luahook:
3426 	LUAh_TitleHUD();
3427 }
3428 
3429 // separate animation timer for backgrounds, since we also count
3430 // during GS_TIMEATTACK
F_MenuPresTicker(boolean run)3431 void F_MenuPresTicker(boolean run)
3432 {
3433 	if (run)
3434 		menuanimtimer++;
3435 }
3436 
3437 // (no longer) De-Demo'd Title Screen
F_TitleScreenTicker(boolean run)3438 void F_TitleScreenTicker(boolean run)
3439 {
3440 	if (run)
3441 		finalecount++;
3442 
3443 	// don't trigger if doing anything besides idling on title
3444 	if (gameaction != ga_nothing || gamestate != GS_TITLESCREEN)
3445 		return;
3446 
3447 	// Execute the titlemap camera settings
3448 	if (titlemapinaction)
3449 	{
3450 		thinker_t *th;
3451 		mobj_t *mo2;
3452 		mobj_t *cameraref = NULL;
3453 
3454 		// If there's a Line 422 Switch Cut-Away view, don't force us.
3455 		if (!titlemapcameraref || titlemapcameraref->type != MT_ALTVIEWMAN)
3456 		{
3457 			for (th = thlist[THINK_MOBJ].next; th != &thlist[THINK_MOBJ]; th = th->next)
3458 			{
3459 				if (th->function.acp1 == (actionf_p1)P_RemoveThinkerDelayed)
3460 					continue;
3461 
3462 				mo2 = (mobj_t *)th;
3463 
3464 				if (!mo2)
3465 					continue;
3466 
3467 				if (mo2->type != MT_ALTVIEWMAN)
3468 					continue;
3469 
3470 				cameraref = titlemapcameraref = mo2;
3471 				break;
3472 			}
3473 		}
3474 		else
3475 			cameraref = titlemapcameraref;
3476 
3477 		if (cameraref)
3478 		{
3479 			camera.x = cameraref->x;
3480 			camera.y = cameraref->y;
3481 			camera.z = cameraref->z;
3482 			camera.angle = cameraref->angle;
3483 			camera.aiming = cameraref->cusval;
3484 			camera.subsector = cameraref->subsector;
3485 		}
3486 		else
3487 		{
3488 			// Default behavior: Do a lil' camera spin if a title map is loaded;
3489 			camera.angle += titlescrollxspeed*ANG1/64;
3490 		}
3491 	}
3492 
3493 	// no demos to play? or, are they disabled?
3494 	if (!cv_rollingdemos.value || !numDemos)
3495 		return;
3496 
3497 	// Wait for a while (for the music to finish, preferably)
3498 	// before starting demos
3499 	if (demoDelayLeft)
3500 	{
3501 		--demoDelayLeft;
3502 		return;
3503 	}
3504 
3505 	// Hold up for a bit if menu or console active
3506 	if (menuactive || CON_Ready())
3507 	{
3508 		demoIdleLeft = demoIdleTime;
3509 		return;
3510 	}
3511 
3512 	// is it time?
3513 	if (!(--demoIdleLeft))
3514 	{
3515 		char dname[9];
3516 		lumpnum_t l;
3517 
3518 		// prevent console spam if failed
3519 		demoIdleLeft = demoIdleTime;
3520 
3521 		// Replay intro when done cycling through demos
3522 		if (curDemo == numDemos)
3523 		{
3524 			curDemo = 0;
3525 			F_StartIntro();
3526 			return;
3527 		}
3528 
3529 		// Setup demo name
3530 		snprintf(dname, 9, "DEMO_%03u", ++curDemo);
3531 
3532 		if ((l = W_CheckNumForName(dname)) == LUMPERROR)
3533 		{
3534 			CONS_Alert(CONS_ERROR, M_GetText("Demo lump \"%s\" doesn't exist\n"), dname);
3535 			F_StartIntro();
3536 			return;
3537 		}
3538 
3539 		titledemo = true;
3540 		G_DoPlayDemo(dname);
3541 	}
3542 }
3543 
F_TitleDemoTicker(void)3544 void F_TitleDemoTicker(void)
3545 {
3546 	keypressed = false;
3547 }
3548 
3549 // ==========
3550 //  CONTINUE
3551 // ==========
3552 
3553 static skin_t *contskins[2];
3554 static UINT8 cont_spr2[2][6];
3555 static UINT8 *contcolormaps[2];
3556 
F_StartContinue(void)3557 void F_StartContinue(void)
3558 {
3559 	I_Assert(!netgame && !multiplayer);
3560 
3561 	if (continuesInSession && players[consoleplayer].continues <= 0)
3562 	{
3563 		Command_ExitGame_f();
3564 		return;
3565 	}
3566 
3567 	wipestyleflags = WSF_FADEOUT;
3568 	G_SetGamestate(GS_CONTINUING);
3569 	gameaction = ga_nothing;
3570 
3571 	keypressed = false;
3572 	paused = false;
3573 	CON_ToggleOff();
3574 
3575 	// In case menus are still up?!!
3576 	M_ClearMenus(true);
3577 
3578 	S_ChangeMusicInternal("_conti", false);
3579 	S_StopSounds();
3580 
3581 	contskins[0] = &skins[players[consoleplayer].skin];
3582 	cont_spr2[0][0] = P_GetSkinSprite2(contskins[0], SPR2_CNT1, NULL);
3583 	cont_spr2[0][2] = contskins[0]->contangle & 7;
3584 	contcolormaps[0] = R_GetTranslationColormap(players[consoleplayer].skin, players[consoleplayer].skincolor, GTC_CACHE);
3585 	cont_spr2[0][4] = contskins[0]->sprites[cont_spr2[0][0]].numframes;
3586 	cont_spr2[0][5] = max(1, contskins[0]->contspeed);
3587 
3588 	if (botskin)
3589 	{
3590 		INT32 secondplaya;
3591 
3592 		if (secondarydisplayplayer != consoleplayer)
3593 			secondplaya = secondarydisplayplayer;
3594 		else // HACK
3595 			secondplaya = 1;
3596 
3597 		contskins[1] = &skins[players[secondplaya].skin];
3598 		cont_spr2[1][0] = P_GetSkinSprite2(contskins[1], SPR2_CNT4, NULL);
3599 		cont_spr2[1][2] = (contskins[1]->contangle >> 3) & 7;
3600 		contcolormaps[1] = R_GetTranslationColormap(players[secondplaya].skin, players[secondplaya].skincolor, GTC_CACHE);
3601 		cont_spr2[1][4] = contskins[1]->sprites[cont_spr2[1][0]].numframes;
3602 		if (cont_spr2[1][0] == SPR2_CNT4)
3603 			cont_spr2[1][5] = 4; // sorry, this one is hardcoded
3604 		else
3605 			cont_spr2[1][5] = max(1, contskins[1]->contspeed);
3606 	}
3607 	else
3608 	{
3609 		contskins[1] = NULL;
3610 		contcolormaps[1] = NULL;
3611 		cont_spr2[1][0] = cont_spr2[1][2] = cont_spr2[1][4] = cont_spr2[1][5] = 0;
3612 	}
3613 
3614 	cont_spr2[0][1] = cont_spr2[0][3] =\
3615 	cont_spr2[1][1] = cont_spr2[1][3] = 0;
3616 
3617 	timetonext = (11*TICRATE)+11;
3618 	continuetime = 0;
3619 }
3620 
3621 //
3622 // F_ContinueDrawer
3623 // Moved continuing out of the HUD (hack removal!!)
3624 //
F_ContinueDrawer(void)3625 void F_ContinueDrawer(void)
3626 {
3627 	spritedef_t *sprdef;
3628 	spriteframe_t *sprframe;
3629 	patch_t *patch;
3630 	INT32 i, x = (BASEVIDWIDTH>>1), ncontinues = players[consoleplayer].continues;
3631 	char numbuf[9] = "CONTNUM*";
3632 	tic_t timeleft = (timetonext/TICRATE);
3633 	INT32 offsx = 0, offsy = 0, lift[2] = {0, 0};
3634 
3635 	if (continuetime >= 3*TICRATE)
3636 	{
3637 		V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 0);
3638 		return;
3639 	}
3640 
3641 	V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 31);
3642 
3643 	if (timetonext >= (11*TICRATE)+10)
3644 		return;
3645 
3646 	V_DrawLevelTitle(x - (V_LevelNameWidth("Continue?")>>1), 16, 0, "Continue?");
3647 
3648 	// Two stars...
3649 	patch = W_CachePatchName("CONTSTAR", PU_PATCH_LOWPRIORITY);
3650 	V_DrawScaledPatch(x-32, 160, 0, patch);
3651 	V_DrawScaledPatch(x+32, 160, 0, patch);
3652 
3653 	// Time left!
3654 	if (timeleft > 9)
3655 	{
3656 		numbuf[7] = '1';
3657 		V_DrawScaledPatch(x - 10, 160, 0, W_CachePatchName(numbuf, PU_PATCH_LOWPRIORITY));
3658 		numbuf[7] = '0';
3659 		V_DrawScaledPatch(x + 10, 160, 0, W_CachePatchName(numbuf, PU_PATCH_LOWPRIORITY));
3660 	}
3661 	else
3662 	{
3663 		numbuf[7] = '0'+timeleft;
3664 		V_DrawScaledPatch(x, 160, 0, W_CachePatchName(numbuf, PU_PATCH_LOWPRIORITY));
3665 	}
3666 
3667 	// Draw the continue markers! Show continues.
3668 	if (!continuesInSession)
3669 		;
3670 	else if (ncontinues > 10)
3671 	{
3672 		if (!(continuetime & 1) || continuetime > 17)
3673 			V_DrawContinueIcon(x, 68, 0, players[consoleplayer].skin, players[consoleplayer].skincolor);
3674 		V_DrawScaledPatch(x+12, 66, 0, stlivex);
3675 		V_DrawRightAlignedString(x+38, 64, 0,
3676 			va("%d",(imcontinuing ? ncontinues-1 : ncontinues)));
3677 	}
3678 	else
3679 	{
3680 		x += (ncontinues/2) * 30;
3681 		if (!(ncontinues & 1))
3682 			x -= 15;
3683 		for (i = 0; i < ncontinues; ++i)
3684 		{
3685 			if (i == (ncontinues/2) && ((continuetime & 1) || continuetime > 17))
3686 				continue;
3687 			V_DrawContinueIcon(x - (i*30), 68, 0, players[consoleplayer].skin, players[consoleplayer].skincolor);
3688 		}
3689 		x = BASEVIDWIDTH>>1;
3690 	}
3691 
3692 	// Spotlight
3693 	V_DrawScaledPatch(x, 140, 0, W_CachePatchName("CONTSPOT", PU_PATCH_LOWPRIORITY));
3694 
3695 	// warping laser
3696 	if (continuetime)
3697 	{
3698 		INT32 w = min(continuetime, 28), brightness = (continuetime>>1) & 7;
3699 		if (brightness > 3)
3700 			brightness = 8-brightness;
3701 		V_DrawFadeFill(x-w, 0, w<<1, 140, 0, 0, (3+brightness));
3702 	}
3703 
3704 	if (contskins[1])
3705 	{
3706 		if (continuetime > 15)
3707 		{
3708 			angle_t work = FixedAngle((10*(continuetime-15))<<FRACBITS)>>ANGLETOFINESHIFT;
3709 			offsy = FINESINE(work)<<1;
3710 			offsx = (27*FINECOSINE(work))>>1;
3711 		}
3712 		else
3713 			offsx = 27<<(FRACBITS-1);
3714 		lift[1] = continuetime-10;
3715 		if (lift[1] < 0)
3716 			lift[1] = 0;
3717 		else if (lift[1] > TICRATE+5)
3718 			lift[1] = TICRATE+5;
3719 	}
3720 
3721 	lift[0] = continuetime-5;
3722 	if (lift[0] < 0)
3723 		lift[0] = 0;
3724 	else if (lift[0] > TICRATE+5)
3725 		lift[0] = TICRATE+5;
3726 
3727 #define drawchar(dx, dy, n)	{\
3728 								sprdef = &contskins[n]->sprites[cont_spr2[n][0]];\
3729 								sprframe = &sprdef->spriteframes[cont_spr2[n][1]];\
3730 								patch = W_CachePatchNum(sprframe->lumppat[cont_spr2[n][2]], PU_PATCH_LOWPRIORITY);\
3731 								V_DrawFixedPatch((dx), (dy), contskins[n]->highresscale, (sprframe->flip & (1<<cont_spr2[n][2])) ? V_FLIP : 0, patch, contcolormaps[n]);\
3732 							}
3733 
3734 	if (offsy < 0)
3735 		drawchar((BASEVIDWIDTH<<(FRACBITS-1))-offsx, ((140-lift[0])<<FRACBITS)-offsy, 0);
3736 	if (contskins[1])
3737 		drawchar((BASEVIDWIDTH<<(FRACBITS-1))+offsx, ((140-lift[1])<<FRACBITS)+offsy, 1);
3738 	if (offsy >= 0)
3739 		drawchar((BASEVIDWIDTH<<(FRACBITS-1))-offsx, ((140-lift[0])<<FRACBITS)-offsy, 0);
3740 
3741 #undef drawchar
3742 
3743 	if (timetonext > (11*TICRATE))
3744 		V_DrawFadeScreen(31, timetonext-(11*TICRATE));
3745 	if (continuetime > ((3*TICRATE) - 10))
3746 		V_DrawFadeScreen(0, (continuetime - ((3*TICRATE) - 10)));
3747 }
3748 
F_ContinueTicker(void)3749 void F_ContinueTicker(void)
3750 {
3751 	if (!imcontinuing)
3752 	{
3753 		if (timetonext > 0)
3754 		{
3755 			if (!(--timetonext))
3756 			{
3757 				Command_ExitGame_f();
3758 				return;
3759 			}
3760 		}
3761 	}
3762 	else
3763 	{
3764 		if (++continuetime == 3*TICRATE)
3765 		{
3766 			G_Continue();
3767 			return;
3768 		}
3769 
3770 		if (continuetime > 5 && ((continuetime & 1) || continuetime > TICRATE) && (++cont_spr2[0][2]) >= 8)
3771 			cont_spr2[0][2] = 0;
3772 
3773 		if (continuetime > 10 && (!(continuetime & 1) || continuetime > TICRATE+5) && (++cont_spr2[1][2]) >= 8)
3774 			cont_spr2[1][2] = 0;
3775 
3776 		if (continuetime == (3*TICRATE)-10)
3777 			S_StartSound(NULL, sfx_cdfm56); // or 31
3778 		else if (continuetime == 5)
3779 		{
3780 			cont_spr2[0][0] = P_GetSkinSprite2(contskins[0], SPR2_CNT2, NULL);
3781 			cont_spr2[0][4] = contskins[0]->sprites[cont_spr2[0][0]].numframes;
3782 			cont_spr2[0][1] = cont_spr2[0][3] = 0;
3783 			cont_spr2[0][5] = 2;
3784 		}
3785 		else if (continuetime == TICRATE)
3786 		{
3787 			cont_spr2[0][0] = P_GetSkinSprite2(contskins[0], SPR2_CNT3, NULL);
3788 			cont_spr2[0][4] = contskins[0]->sprites[cont_spr2[0][0]].numframes;
3789 			cont_spr2[0][1] = cont_spr2[0][3] = 0;
3790 		}
3791 		else if (contskins[1])
3792 		{
3793 			if (continuetime == 10)
3794 			{
3795 				cont_spr2[1][0] = P_GetSkinSprite2(contskins[1], SPR2_CNT2, NULL);
3796 				cont_spr2[1][4] = contskins[1]->sprites[cont_spr2[1][0]].numframes;
3797 				cont_spr2[1][1] = cont_spr2[1][3] = 0;
3798 				cont_spr2[1][5] = 2;
3799 			}
3800 			else if (continuetime == TICRATE+5)
3801 			{
3802 				cont_spr2[1][0] = P_GetSkinSprite2(contskins[1], SPR2_CNT3, NULL);
3803 				cont_spr2[1][4] = contskins[1]->sprites[cont_spr2[1][0]].numframes;
3804 				cont_spr2[1][1] = cont_spr2[1][3] = 0;
3805 			}
3806 		}
3807 	}
3808 
3809 	if ((++cont_spr2[0][3]) >= cont_spr2[0][5])
3810 	{
3811 		cont_spr2[0][3] = 0;
3812 		if (++cont_spr2[0][1] >= cont_spr2[0][4])
3813 			cont_spr2[0][1] = 0;
3814 	}
3815 
3816 	if (contskins[1] && (++cont_spr2[1][3]) >= cont_spr2[1][5])
3817 	{
3818 		cont_spr2[1][3] = 0;
3819 		if (++cont_spr2[1][1] >= cont_spr2[1][4])
3820 			cont_spr2[1][1] = 0;
3821 	}
3822 }
3823 
F_ContinueResponder(event_t * event)3824 boolean F_ContinueResponder(event_t *event)
3825 {
3826 	INT32 key = event->data1;
3827 
3828 	if (keypressed)
3829 		return true;
3830 
3831 	if (timetonext >= 21*TICRATE/2)
3832 		return false;
3833 	if (event->type != ev_keydown)
3834 		return false;
3835 
3836 	// remap virtual keys (mouse & joystick buttons)
3837 	switch (key)
3838 	{
3839 		case KEY_ENTER:
3840 		case KEY_SPACE:
3841 		case KEY_MOUSE1:
3842 		case KEY_JOY1:
3843 		case KEY_JOY1 + 2:
3844 			break;
3845 		default:
3846 			return false;
3847 	}
3848 
3849 	keypressed = true;
3850 	imcontinuing = true;
3851 	S_StartSound(NULL, sfx_kc6b);
3852 	I_FadeSong(0, MUSICRATE, &S_StopMusic);
3853 
3854 	return true;
3855 }
3856 
3857 // ==================
3858 //  CUSTOM CUTSCENES
3859 // ==================
3860 static INT32 scenenum, cutnum;
3861 static INT32 picxpos, picypos, picnum, pictime, picmode, numpics, pictoloop;
3862 static INT32 textxpos, textypos;
3863 static boolean dofadenow = false, cutsceneover = false;
3864 static boolean runningprecutscene = false, precutresetplayer = false;
3865 
F_AdvanceToNextScene(void)3866 static void F_AdvanceToNextScene(void)
3867 {
3868 	// Don't increment until after endcutscene check
3869 	// (possible overflow / bad patch names from the one tic drawn before the fade)
3870 	if (scenenum+1 >= cutscenes[cutnum]->numscenes)
3871 	{
3872 		F_EndCutScene();
3873 		return;
3874 	}
3875 	++scenenum;
3876 
3877 	timetonext = 0;
3878 	stoptimer = 0;
3879 	picnum = 0;
3880 	picxpos = cutscenes[cutnum]->scene[scenenum].xcoord[picnum];
3881 	picypos = cutscenes[cutnum]->scene[scenenum].ycoord[picnum];
3882 
3883 	if (cutscenes[cutnum]->scene[scenenum].musswitch[0])
3884 		S_ChangeMusicEx(cutscenes[cutnum]->scene[scenenum].musswitch,
3885 			cutscenes[cutnum]->scene[scenenum].musswitchflags,
3886 			cutscenes[cutnum]->scene[scenenum].musicloop,
3887 			cutscenes[cutnum]->scene[scenenum].musswitchposition, 0, 0);
3888 
3889 	// Fade to the next
3890 	dofadenow = true;
3891 	F_NewCutscene(cutscenes[cutnum]->scene[scenenum].text);
3892 
3893 	picnum = 0;
3894 	picxpos = cutscenes[cutnum]->scene[scenenum].xcoord[picnum];
3895 	picypos = cutscenes[cutnum]->scene[scenenum].ycoord[picnum];
3896 	textxpos = cutscenes[cutnum]->scene[scenenum].textxpos;
3897 	textypos = cutscenes[cutnum]->scene[scenenum].textypos;
3898 
3899 	animtimer = pictime = cutscenes[cutnum]->scene[scenenum].picduration[picnum];
3900 }
3901 
3902 // See also G_AfterIntermission, the only other place which handles intra-map/ending transitions
F_EndCutScene(void)3903 void F_EndCutScene(void)
3904 {
3905 	cutsceneover = true; // do this first, just in case G_EndGame or something wants to turn it back false later
3906 	if (runningprecutscene)
3907 	{
3908 		if (server)
3909 			D_MapChange(gamemap, gametype, ultimatemode, precutresetplayer, 0, true, false);
3910 	}
3911 	else
3912 	{
3913 		if (cutnum == creditscutscene-1)
3914 			F_StartGameEvaluation();
3915 		else if (cutnum == introtoplay-1)
3916 			D_StartTitle();
3917 		else if (nextmap < 1100-1)
3918 			G_NextLevel();
3919 		else
3920 			G_EndGame();
3921 	}
3922 }
3923 
F_StartCustomCutscene(INT32 cutscenenum,boolean precutscene,boolean resetplayer)3924 void F_StartCustomCutscene(INT32 cutscenenum, boolean precutscene, boolean resetplayer)
3925 {
3926 	if (!cutscenes[cutscenenum])
3927 		return;
3928 
3929 	G_SetGamestate(GS_CUTSCENE);
3930 
3931 	if (wipegamestate == GS_CUTSCENE)
3932 		wipegamestate = -1;
3933 
3934 	gameaction = ga_nothing;
3935 	paused = false;
3936 	CON_ToggleOff();
3937 
3938 	F_NewCutscene(cutscenes[cutscenenum]->scene[0].text);
3939 
3940 	cutsceneover = false;
3941 	runningprecutscene = precutscene;
3942 	precutresetplayer = resetplayer;
3943 
3944 	scenenum = picnum = 0;
3945 	cutnum = cutscenenum;
3946 	picxpos = cutscenes[cutnum]->scene[0].xcoord[0];
3947 	picypos = cutscenes[cutnum]->scene[0].ycoord[0];
3948 	textxpos = cutscenes[cutnum]->scene[0].textxpos;
3949 	textypos = cutscenes[cutnum]->scene[0].textypos;
3950 
3951 	pictime = cutscenes[cutnum]->scene[0].picduration[0];
3952 
3953 	keypressed = false;
3954 	finalecount = 0;
3955 	timetonext = 0;
3956 	animtimer = cutscenes[cutnum]->scene[0].picduration[0]; // Picture duration
3957 	stoptimer = 0;
3958 
3959 	if (cutscenes[cutnum]->scene[0].musswitch[0])
3960 		S_ChangeMusicEx(cutscenes[cutnum]->scene[0].musswitch,
3961 			cutscenes[cutnum]->scene[0].musswitchflags,
3962 			cutscenes[cutnum]->scene[0].musicloop,
3963 			cutscenes[cutnum]->scene[scenenum].musswitchposition, 0, 0);
3964 	else
3965 		S_StopMusic();
3966 	S_StopSounds();
3967 }
3968 
3969 //
3970 // F_CutsceneDrawer
3971 //
F_CutsceneDrawer(void)3972 void F_CutsceneDrawer(void)
3973 {
3974 	if (dofadenow && rendermode != render_none)
3975 	{
3976 		F_WipeStartScreen();
3977 
3978 		// Fade to any palette color you want.
3979 		if (cutscenes[cutnum]->scene[scenenum].fadecolor)
3980 		{
3981 			V_DrawFill(0,0,BASEVIDWIDTH,BASEVIDHEIGHT,cutscenes[cutnum]->scene[scenenum].fadecolor);
3982 
3983 			F_WipeEndScreen();
3984 			F_RunWipe(cutscenes[cutnum]->scene[scenenum].fadeinid, true);
3985 
3986 			F_WipeStartScreen();
3987 		}
3988 	}
3989 	V_DrawFill(0,0, BASEVIDWIDTH, BASEVIDHEIGHT, 31);
3990 
3991 	if (cutscenes[cutnum]->scene[scenenum].picname[picnum][0] != '\0')
3992 	{
3993 		if (cutscenes[cutnum]->scene[scenenum].pichires[picnum])
3994 			V_DrawSmallScaledPatch(picxpos, picypos, 0,
3995 				W_CachePatchName(cutscenes[cutnum]->scene[scenenum].picname[picnum], PU_PATCH_LOWPRIORITY));
3996 		else
3997 			V_DrawScaledPatch(picxpos,picypos, 0,
3998 				W_CachePatchName(cutscenes[cutnum]->scene[scenenum].picname[picnum], PU_PATCH_LOWPRIORITY));
3999 	}
4000 
4001 	if (dofadenow && rendermode != render_none)
4002 	{
4003 		F_WipeEndScreen();
4004 		F_RunWipe(cutscenes[cutnum]->scene[scenenum].fadeoutid, true);
4005 	}
4006 
4007 	V_DrawString(textxpos, textypos, V_ALLOWLOWERCASE, cutscene_disptext);
4008 }
4009 
F_CutsceneTicker(void)4010 void F_CutsceneTicker(void)
4011 {
4012 	INT32 i;
4013 
4014 	// Online clients tend not to instantly get the map change, so just wait
4015 	// and don't send 30 of them.
4016 	if (cutsceneover)
4017 		return;
4018 
4019 	// advance animation
4020 	finalecount++;
4021 	cutscene_boostspeed = 0;
4022 
4023 	dofadenow = false;
4024 
4025 	for (i = 0; i < MAXPLAYERS; i++)
4026 	{
4027 		if (netgame && i != serverplayer && !IsPlayerAdmin(i))
4028 			continue;
4029 
4030 		if (players[i].cmd.buttons & BT_SPIN)
4031 		{
4032 			keypressed = false;
4033 			cutscene_boostspeed = 1;
4034 			if (timetonext)
4035 				timetonext = 2;
4036 		}
4037 	}
4038 
4039 	if (animtimer)
4040 	{
4041 		animtimer--;
4042 		if (animtimer <= 0)
4043 		{
4044 			if (picnum < 7 && cutscenes[cutnum]->scene[scenenum].picname[picnum+1][0] != '\0')
4045 			{
4046 				picnum++;
4047 				picxpos = cutscenes[cutnum]->scene[scenenum].xcoord[picnum];
4048 				picypos = cutscenes[cutnum]->scene[scenenum].ycoord[picnum];
4049 				pictime = cutscenes[cutnum]->scene[scenenum].picduration[picnum];
4050 				animtimer = pictime;
4051 			}
4052 			else
4053 				timetonext = 2;
4054 		}
4055 	}
4056 
4057 	if (timetonext)
4058 		--timetonext;
4059 
4060 	if (++stoptimer > 2 && timetonext == 1)
4061 		F_AdvanceToNextScene();
4062 	else if (!timetonext && !F_WriteText())
4063 		timetonext = 5*TICRATE + 1;
4064 }
4065 
F_CutsceneResponder(event_t * event)4066 boolean F_CutsceneResponder(event_t *event)
4067 {
4068 	if (cutnum == introtoplay-1)
4069 		return F_IntroResponder(event);
4070 
4071 	return false;
4072 }
4073 
4074 // ==================
4075 //  TEXT PROMPTS
4076 // ==================
4077 
F_GetPageTextGeometry(UINT8 * pagelines,boolean * rightside,INT32 * boxh,INT32 * texth,INT32 * texty,INT32 * namey,INT32 * chevrony,INT32 * textx,INT32 * textr)4078 static void F_GetPageTextGeometry(UINT8 *pagelines, boolean *rightside, INT32 *boxh, INT32 *texth, INT32 *texty, INT32 *namey, INT32 *chevrony, INT32 *textx, INT32 *textr)
4079 {
4080 	// reuse:
4081 	// cutnum -> promptnum
4082 	// scenenum -> pagenum
4083 	lumpnum_t iconlump = W_CheckNumForName(textprompts[cutnum]->page[scenenum].iconname);
4084 
4085 	*pagelines = textprompts[cutnum]->page[scenenum].lines ? textprompts[cutnum]->page[scenenum].lines : 4;
4086 	*rightside = (iconlump != LUMPERROR && textprompts[cutnum]->page[scenenum].rightside);
4087 
4088 	// Vertical calculations
4089 	*boxh = *pagelines*2;
4090 	*texth = textprompts[cutnum]->page[scenenum].name[0] ? (*pagelines-1)*2 : *pagelines*2; // name takes up first line if it exists
4091 	*texty = BASEVIDHEIGHT - ((*texth * 4) + (*texth/2)*4);
4092 	*namey = BASEVIDHEIGHT - ((*boxh * 4) + (*boxh/2)*4);
4093 	*chevrony = BASEVIDHEIGHT - (((1*2) * 4) + ((1*2)/2)*4); // force on last line
4094 
4095 	// Horizontal calculations
4096 	// Shift text to the right if we have a character icon on the left side
4097 	// Add 4 margin against icon
4098 	*textx = (iconlump != LUMPERROR && !*rightside) ? ((*boxh * 4) + (*boxh/2)*4) + 4 : 4;
4099 	*textr = *rightside ? BASEVIDWIDTH - (((*boxh * 4) + (*boxh/2)*4) + 4) : BASEVIDWIDTH-4;
4100 }
4101 
F_GetPromptHideHudBound(void)4102 static fixed_t F_GetPromptHideHudBound(void)
4103 {
4104 	UINT8 pagelines;
4105 	boolean rightside;
4106 	INT32 boxh, texth, texty, namey, chevrony;
4107 	INT32 textx, textr;
4108 
4109 	if (cutnum == INT32_MAX || scenenum == INT32_MAX || !textprompts[cutnum] || scenenum >= textprompts[cutnum]->numpages ||
4110 		!textprompts[cutnum]->page[scenenum].hidehud ||
4111 		(splitscreen && textprompts[cutnum]->page[scenenum].hidehud != 2)) // don't hide on splitscreen, unless hide all is forced
4112 		return 0;
4113 	else if (textprompts[cutnum]->page[scenenum].hidehud == 2) // hide all
4114 		return BASEVIDHEIGHT;
4115 
4116 	F_GetPageTextGeometry(&pagelines, &rightside, &boxh, &texth, &texty, &namey, &chevrony, &textx, &textr);
4117 
4118 	// calc boxheight (see V_DrawPromptBack)
4119 	boxh *= vid.dupy;
4120 	boxh = (boxh * 4) + (boxh/2)*5; // 4 lines of space plus gaps between and some leeway
4121 
4122 	// return a coordinate to check
4123 	// if negative: don't show hud elements below this coordinate (visually)
4124 	// if positive: don't show hud elements above this coordinate (visually)
4125 	return 0 - boxh; // \todo: if prompt at top of screen (someday), make this return positive
4126 }
4127 
F_GetPromptHideHudAll(void)4128 boolean F_GetPromptHideHudAll(void)
4129 {
4130 	if (cutnum == INT32_MAX || scenenum == INT32_MAX || !textprompts[cutnum] || scenenum >= textprompts[cutnum]->numpages ||
4131 		!textprompts[cutnum]->page[scenenum].hidehud ||
4132 		(splitscreen && textprompts[cutnum]->page[scenenum].hidehud != 2)) // don't hide on splitscreen, unless hide all is forced
4133 		return false;
4134 	else if (textprompts[cutnum]->page[scenenum].hidehud == 2) // hide all
4135 		return true;
4136 	else
4137 		return false;
4138 }
4139 
F_GetPromptHideHud(fixed_t y)4140 boolean F_GetPromptHideHud(fixed_t y)
4141 {
4142 	INT32 ybound;
4143 	boolean fromtop;
4144 	fixed_t ytest;
4145 
4146 	if (!promptactive)
4147 		return false;
4148 
4149 	ybound = F_GetPromptHideHudBound();
4150 	fromtop = (ybound >= 0);
4151 	ytest = (fromtop ? ybound : BASEVIDHEIGHT + ybound);
4152 
4153 	return (fromtop ? y < ytest : y >= ytest); // true means hide
4154 }
4155 
F_PreparePageText(char * pagetext)4156 static void F_PreparePageText(char *pagetext)
4157 {
4158 	UINT8 pagelines;
4159 	boolean rightside;
4160 	INT32 boxh, texth, texty, namey, chevrony;
4161 	INT32 textx, textr;
4162 
4163 	F_GetPageTextGeometry(&pagelines, &rightside, &boxh, &texth, &texty, &namey, &chevrony, &textx, &textr);
4164 
4165 	if (promptpagetext)
4166 		Z_Free(promptpagetext);
4167 	promptpagetext = (pagetext && pagetext[0]) ? V_WordWrap(textx, textr, 0, pagetext) : Z_StrDup("");
4168 
4169 	F_NewCutscene(promptpagetext);
4170 	cutscene_textspeed = textprompts[cutnum]->page[scenenum].textspeed ? textprompts[cutnum]->page[scenenum].textspeed : TICRATE/5;
4171 	cutscene_textcount = 0; // no delay in beginning
4172 	cutscene_boostspeed = 0; // don't print 8 characters to start
4173 
4174 	// \todo update control hot strings on re-config
4175 	// and somehow don't reset cutscene text counters
4176 }
4177 
F_AdvanceToNextPage(void)4178 static void F_AdvanceToNextPage(void)
4179 {
4180 	INT32 nextprompt = textprompts[cutnum]->page[scenenum].nextprompt ? textprompts[cutnum]->page[scenenum].nextprompt - 1 : INT32_MAX,
4181 		nextpage = textprompts[cutnum]->page[scenenum].nextpage ? textprompts[cutnum]->page[scenenum].nextpage - 1 : INT32_MAX,
4182 		oldcutnum = cutnum;
4183 
4184 	if (textprompts[cutnum]->page[scenenum].nexttag[0])
4185 		F_GetPromptPageByNamedTag(textprompts[cutnum]->page[scenenum].nexttag, &nextprompt, &nextpage);
4186 
4187 	// determine next prompt
4188 	if (nextprompt != INT32_MAX)
4189 	{
4190 		if (nextprompt <= MAX_PROMPTS && textprompts[nextprompt])
4191 			cutnum = nextprompt;
4192 		else
4193 			cutnum = INT32_MAX;
4194 	}
4195 
4196 	// determine next page
4197 	if (nextpage != INT32_MAX)
4198 	{
4199 		if (cutnum != INT32_MAX)
4200 		{
4201 			scenenum = nextpage;
4202 			if (scenenum >= MAX_PAGES || scenenum > textprompts[cutnum]->numpages-1)
4203 				scenenum = INT32_MAX;
4204 		}
4205 	}
4206 	else
4207 	{
4208 		if (cutnum != oldcutnum)
4209 			scenenum = 0;
4210 		else if (scenenum + 1 < MAX_PAGES && scenenum < textprompts[cutnum]->numpages-1)
4211 			scenenum++;
4212 		else
4213 			scenenum = INT32_MAX;
4214 	}
4215 
4216 	// close the prompt if either num is invalid
4217 	if (cutnum == INT32_MAX || scenenum == INT32_MAX)
4218 		F_EndTextPrompt(false, false);
4219 	else
4220 	{
4221 		// on page mode, number of tics before allowing boost
4222 		// on timer mode, number of tics until page advances
4223 		timetonext = textprompts[cutnum]->page[scenenum].timetonext ? textprompts[cutnum]->page[scenenum].timetonext : TICRATE/10;
4224 		F_PreparePageText(textprompts[cutnum]->page[scenenum].text);
4225 
4226 		// gfx
4227 		picnum = textprompts[cutnum]->page[scenenum].pictostart;
4228 		numpics = textprompts[cutnum]->page[scenenum].numpics;
4229 		picmode = textprompts[cutnum]->page[scenenum].picmode;
4230 		pictoloop = textprompts[cutnum]->page[scenenum].pictoloop > 0 ? textprompts[cutnum]->page[scenenum].pictoloop - 1 : 0;
4231 		picxpos = textprompts[cutnum]->page[scenenum].xcoord[picnum];
4232 		picypos = textprompts[cutnum]->page[scenenum].ycoord[picnum];
4233 		animtimer = pictime = textprompts[cutnum]->page[scenenum].picduration[picnum];
4234 
4235 		// music change
4236 		if (textprompts[cutnum]->page[scenenum].musswitch[0])
4237 			S_ChangeMusic(textprompts[cutnum]->page[scenenum].musswitch,
4238 				textprompts[cutnum]->page[scenenum].musswitchflags,
4239 				textprompts[cutnum]->page[scenenum].musicloop);
4240 	}
4241 }
4242 
F_EndTextPrompt(boolean forceexec,boolean noexec)4243 void F_EndTextPrompt(boolean forceexec, boolean noexec)
4244 {
4245 	boolean promptwasactive = promptactive;
4246 	promptactive = false;
4247 	callpromptnum = callpagenum = callplayer = INT32_MAX;
4248 
4249 	if (promptwasactive)
4250 	{
4251 		if (promptmo && promptmo->player && promptblockcontrols)
4252 			promptmo->reactiontime = TICRATE/4; // prevent jumping right away // \todo account freeze realtime for this)
4253 		// \todo reset frozen realtime?
4254 	}
4255 
4256 	// \todo net safety, maybe loop all player thinkers?
4257 	if ((promptwasactive || forceexec) && !noexec && promptpostexectag)
4258 	{
4259 		if (tmthing) // edge case where starting an invalid prompt immediately on level load will make P_MapStart fail
4260 			P_LinedefExecute(promptpostexectag, promptmo, NULL);
4261 		else
4262 		{
4263 			P_MapStart();
4264 			P_LinedefExecute(promptpostexectag, promptmo, NULL);
4265 			P_MapEnd();
4266 		}
4267 	}
4268 }
4269 
F_StartTextPrompt(INT32 promptnum,INT32 pagenum,mobj_t * mo,UINT16 postexectag,boolean blockcontrols,boolean freezerealtime)4270 void F_StartTextPrompt(INT32 promptnum, INT32 pagenum, mobj_t *mo, UINT16 postexectag, boolean blockcontrols, boolean freezerealtime)
4271 {
4272 	INT32 i;
4273 
4274 	// if splitscreen and we already have a prompt active, ignore.
4275 	// \todo Proper per-player splitscreen support (individual prompts)
4276 	if (promptactive && splitscreen && promptnum == callpromptnum && pagenum == callpagenum)
4277 		return;
4278 
4279 	// \todo proper netgame support
4280 	if (netgame)
4281 	{
4282 		F_EndTextPrompt(true, false); // run the post-effects immediately
4283 		return;
4284 	}
4285 
4286 	// We share vars, so no starting text prompts over cutscenes or title screens!
4287 	keypressed = false;
4288 	finalecount = 0;
4289 	timetonext = 0;
4290 	animtimer = 0;
4291 	stoptimer = 0;
4292 	skullAnimCounter = 0;
4293 
4294 	// Set up state
4295 	promptmo = mo;
4296 	promptpostexectag = postexectag;
4297 	promptblockcontrols = blockcontrols;
4298 	(void)freezerealtime; // \todo freeze player->realtime, maybe this needs to cycle through player thinkers
4299 
4300 	// Initialize current prompt and scene
4301 	callpromptnum = promptnum;
4302 	callpagenum = pagenum;
4303 	cutnum = (promptnum < MAX_PROMPTS && textprompts[promptnum]) ? promptnum : INT32_MAX;
4304 	scenenum = (cutnum != INT32_MAX && pagenum < MAX_PAGES && pagenum <= textprompts[cutnum]->numpages-1) ? pagenum : INT32_MAX;
4305 	promptactive = (cutnum != INT32_MAX && scenenum != INT32_MAX);
4306 
4307 	if (promptactive)
4308 	{
4309 		// on page mode, number of tics before allowing boost
4310 		// on timer mode, number of tics until page advances
4311 		timetonext = textprompts[cutnum]->page[scenenum].timetonext ? textprompts[cutnum]->page[scenenum].timetonext : TICRATE/10;
4312 		F_PreparePageText(textprompts[cutnum]->page[scenenum].text);
4313 
4314 		// gfx
4315 		picnum = textprompts[cutnum]->page[scenenum].pictostart;
4316 		numpics = textprompts[cutnum]->page[scenenum].numpics;
4317 		picmode = textprompts[cutnum]->page[scenenum].picmode;
4318 		pictoloop = textprompts[cutnum]->page[scenenum].pictoloop > 0 ? textprompts[cutnum]->page[scenenum].pictoloop - 1 : 0;
4319 		picxpos = textprompts[cutnum]->page[scenenum].xcoord[picnum];
4320 		picypos = textprompts[cutnum]->page[scenenum].ycoord[picnum];
4321 		animtimer = pictime = textprompts[cutnum]->page[scenenum].picduration[picnum];
4322 
4323 		// music change
4324 		if (textprompts[cutnum]->page[scenenum].musswitch[0])
4325 			S_ChangeMusic(textprompts[cutnum]->page[scenenum].musswitch,
4326 				textprompts[cutnum]->page[scenenum].musswitchflags,
4327 				textprompts[cutnum]->page[scenenum].musicloop);
4328 
4329 		// get the calling player
4330 		if (promptblockcontrols && mo && mo->player)
4331 		{
4332 			for (i = 0; i < MAXPLAYERS; i++)
4333 			{
4334 				if (players[i].mo == mo)
4335 				{
4336 					callplayer = i;
4337 					break;
4338 				}
4339 			}
4340 		}
4341 	}
4342 	else
4343 		F_EndTextPrompt(true, false); // run the post-effects immediately
4344 }
4345 
F_GetTextPromptTutorialTag(char * tag,INT32 length)4346 static boolean F_GetTextPromptTutorialTag(char *tag, INT32 length)
4347 {
4348 	INT32 gcs = gcs_custom;
4349 	boolean suffixed = true;
4350 
4351 	if (!tag || !tag[0] || !tutorialmode)
4352 		return false;
4353 
4354 	if (!strncmp(tag, "TAM", 3)) // Movement
4355 		gcs = G_GetControlScheme(gamecontrol, gcl_movement, num_gcl_movement);
4356 	else if (!strncmp(tag, "TAC", 3)) // Camera
4357 	{
4358 		// Check for gcl_movement so we can differentiate between FPS and Platform schemes.
4359 		gcs = G_GetControlScheme(gamecontrol, gcl_movement, num_gcl_movement);
4360 		if (gcs == gcs_custom) // try again, maybe we'll get a match
4361 			gcs = G_GetControlScheme(gamecontrol, gcl_camera, num_gcl_camera);
4362 		if (gcs == gcs_fps && !cv_usemouse.value)
4363 			gcs = gcs_platform; // Platform (arrow) scheme is stand-in for no mouse
4364 	}
4365 	else if (!strncmp(tag, "TAD", 3)) // Movement and Camera
4366 		gcs = G_GetControlScheme(gamecontrol, gcl_movement_camera, num_gcl_movement_camera);
4367 	else if (!strncmp(tag, "TAJ", 3)) // Jump
4368 		gcs = G_GetControlScheme(gamecontrol, gcl_jump, num_gcl_jump);
4369 	else if (!strncmp(tag, "TAS", 3)) // Spin
4370 		gcs = G_GetControlScheme(gamecontrol, gcl_spin, num_gcl_spin);
4371 	else if (!strncmp(tag, "TAA", 3)) // Char ability
4372 		gcs = G_GetControlScheme(gamecontrol, gcl_jump, num_gcl_jump);
4373 	else if (!strncmp(tag, "TAW", 3)) // Shield ability
4374 		gcs = G_GetControlScheme(gamecontrol, gcl_jump_spin, num_gcl_jump_spin);
4375 	else
4376 		gcs = G_GetControlScheme(gamecontrol, gcl_tutorial_used, num_gcl_tutorial_used);
4377 
4378 	switch (gcs)
4379 	{
4380 		case gcs_fps:
4381 			// strncat(tag, "FPS", length);
4382 			suffixed = false;
4383 			break;
4384 
4385 		case gcs_platform:
4386 			strncat(tag, "PLATFORM", length);
4387 			break;
4388 
4389 		default:
4390 			strncat(tag, "CUSTOM", length);
4391 			break;
4392 	}
4393 
4394 	return suffixed;
4395 }
4396 
F_GetPromptPageByNamedTag(const char * tag,INT32 * promptnum,INT32 * pagenum)4397 void F_GetPromptPageByNamedTag(const char *tag, INT32 *promptnum, INT32 *pagenum)
4398 {
4399 	INT32 nosuffixpromptnum = INT32_MAX, nosuffixpagenum = INT32_MAX;
4400 	INT32 tutorialpromptnum = (tutorialmode) ? TUTORIAL_PROMPT-1 : 0;
4401 	boolean suffixed = false, found = false;
4402 	char suffixedtag[33];
4403 
4404 	*promptnum = *pagenum = INT32_MAX;
4405 
4406 	if (!tag || !tag[0])
4407 		return;
4408 
4409 	strncpy(suffixedtag, tag, 33);
4410 	suffixedtag[32] = 0;
4411 
4412 	if (tutorialmode)
4413 		suffixed = F_GetTextPromptTutorialTag(suffixedtag, 33);
4414 
4415 	for (*promptnum = 0 + tutorialpromptnum; *promptnum < MAX_PROMPTS; (*promptnum)++)
4416 	{
4417 		if (!textprompts[*promptnum])
4418 			continue;
4419 
4420 		for (*pagenum = 0; *pagenum < textprompts[*promptnum]->numpages && *pagenum < MAX_PAGES; (*pagenum)++)
4421 		{
4422 			if (suffixed && fastcmp(suffixedtag, textprompts[*promptnum]->page[*pagenum].tag))
4423 			{
4424 				// this goes first because fastcmp ends early if first string is shorter
4425 				found = true;
4426 				break;
4427 			}
4428 			else if (nosuffixpromptnum == INT32_MAX && nosuffixpagenum == INT32_MAX && fastcmp(tag, textprompts[*promptnum]->page[*pagenum].tag))
4429 			{
4430 				if (suffixed)
4431 				{
4432 					nosuffixpromptnum = *promptnum;
4433 					nosuffixpagenum = *pagenum;
4434 					// continue searching for the suffixed tag
4435 				}
4436 				else
4437 				{
4438 					found = true;
4439 					break;
4440 				}
4441 			}
4442 		}
4443 
4444 		if (found)
4445 			break;
4446 	}
4447 
4448 	if (suffixed && !found && nosuffixpromptnum != INT32_MAX && nosuffixpagenum != INT32_MAX)
4449 	{
4450 		found = true;
4451 		*promptnum = nosuffixpromptnum;
4452 		*pagenum = nosuffixpagenum;
4453 	}
4454 
4455 	if (!found)
4456 		CONS_Debug(DBG_GAMELOGIC, "Text prompt: Can't find a page with named tag %s or suffixed tag %s\n", tag, suffixedtag);
4457 }
4458 
F_TextPromptDrawer(void)4459 void F_TextPromptDrawer(void)
4460 {
4461 	// reuse:
4462 	// cutnum -> promptnum
4463 	// scenenum -> pagenum
4464 	lumpnum_t iconlump;
4465 	UINT8 pagelines;
4466 	boolean rightside;
4467 	INT32 boxh, texth, texty, namey, chevrony;
4468 	INT32 textx, textr;
4469 
4470 	// Data
4471 	patch_t *patch;
4472 
4473 	if (!promptactive)
4474 		return;
4475 
4476 	iconlump = W_CheckNumForName(textprompts[cutnum]->page[scenenum].iconname);
4477 	F_GetPageTextGeometry(&pagelines, &rightside, &boxh, &texth, &texty, &namey, &chevrony, &textx, &textr);
4478 
4479 	// Draw gfx first
4480 	if (picnum >= 0 && picnum < numpics && textprompts[cutnum]->page[scenenum].picname[picnum][0] != '\0')
4481 	{
4482 		if (textprompts[cutnum]->page[scenenum].pichires[picnum])
4483 			V_DrawSmallScaledPatch(picxpos, picypos, 0,
4484 				W_CachePatchName(textprompts[cutnum]->page[scenenum].picname[picnum], PU_PATCH_LOWPRIORITY));
4485 		else
4486 			V_DrawScaledPatch(picxpos,picypos, 0,
4487 				W_CachePatchName(textprompts[cutnum]->page[scenenum].picname[picnum], PU_PATCH_LOWPRIORITY));
4488 	}
4489 
4490 	// Draw background
4491 	V_DrawPromptBack(boxh, textprompts[cutnum]->page[scenenum].backcolor);
4492 
4493 	// Draw narrator icon
4494 	if (iconlump != LUMPERROR)
4495 	{
4496 		INT32 iconx, icony, scale, scaledsize;
4497 		patch = W_CachePatchName(textprompts[cutnum]->page[scenenum].iconname, PU_PATCH_LOWPRIORITY);
4498 
4499 		// scale and center
4500 		if (patch->width > patch->height)
4501 		{
4502 			scale = FixedDiv(((boxh * 4) + (boxh/2)*4) - 4, patch->width);
4503 			scaledsize = FixedMul(patch->height, scale);
4504 			iconx = (rightside ? BASEVIDWIDTH - (((boxh * 4) + (boxh/2)*4)) : 4) << FRACBITS;
4505 			icony = ((namey-4) << FRACBITS) + FixedDiv(BASEVIDHEIGHT - namey + 4 - scaledsize, 2); // account for 4 margin
4506 		}
4507 		else if (patch->height > patch->width)
4508 		{
4509 			scale = FixedDiv(((boxh * 4) + (boxh/2)*4) - 4, patch->height);
4510 			scaledsize = FixedMul(patch->width, scale);
4511 			iconx = (rightside ? BASEVIDWIDTH - (((boxh * 4) + (boxh/2)*4)) : 4) << FRACBITS;
4512 			icony = namey << FRACBITS;
4513 			iconx += FixedDiv(FixedMul(patch->height, scale) - scaledsize, 2);
4514 		}
4515 		else
4516 		{
4517 			scale = FixedDiv(((boxh * 4) + (boxh/2)*4) - 4, patch->width);
4518 			iconx = (rightside ? BASEVIDWIDTH - (((boxh * 4) + (boxh/2)*4)) : 4) << FRACBITS;
4519 			icony = namey << FRACBITS;
4520 		}
4521 
4522 		if (textprompts[cutnum]->page[scenenum].iconflip)
4523 			iconx += FixedMul(patch->width, scale) << FRACBITS;
4524 
4525 		V_DrawFixedPatch(iconx, icony, scale, (V_SNAPTOBOTTOM|(textprompts[cutnum]->page[scenenum].iconflip ? V_FLIP : 0)), patch, NULL);
4526 		W_UnlockCachedPatch(patch);
4527 	}
4528 
4529 	// Draw text
4530 	V_DrawString(textx, texty, (V_SNAPTOBOTTOM|V_ALLOWLOWERCASE), cutscene_disptext);
4531 
4532 	// Draw name
4533 	// Don't use V_YELLOWMAP here so that the name color can be changed with control codes
4534 	if (textprompts[cutnum]->page[scenenum].name[0])
4535 		V_DrawString(textx, namey, (V_SNAPTOBOTTOM|V_ALLOWLOWERCASE), textprompts[cutnum]->page[scenenum].name);
4536 
4537 	// Draw chevron
4538 	if (promptblockcontrols && !timetonext)
4539 		V_DrawString(textr-8, chevrony + (skullAnimCounter/5), (V_SNAPTOBOTTOM|V_YELLOWMAP), "\x1B"); // down arrow
4540 }
4541 
4542 #define nocontrolallowed(j) {\
4543 		players[j].powers[pw_nocontrol] = 1;\
4544 		if (players[j].mo)\
4545 		{\
4546 			if (players[j].mo->state == states+S_PLAY_STND && players[j].mo->tics != -1)\
4547 				players[j].mo->tics++;\
4548 			else if (players[j].mo->state == states+S_PLAY_WAIT)\
4549 				P_SetPlayerMobjState(players[j].mo, S_PLAY_STND);\
4550 		}\
4551 	}
4552 
F_TextPromptTicker(void)4553 void F_TextPromptTicker(void)
4554 {
4555 	INT32 i;
4556 
4557 	if (!promptactive || paused || P_AutoPause())
4558 		return;
4559 
4560 	// advance animation
4561 	finalecount++;
4562 	cutscene_boostspeed = 0;
4563 
4564 	// for the chevron
4565 	if (--skullAnimCounter <= 0)
4566 		skullAnimCounter = 8;
4567 
4568 	// button handling
4569 	if (textprompts[cutnum]->page[scenenum].timetonext)
4570 	{
4571 		if (promptblockcontrols) // same procedure as below, just without the button handling
4572 		{
4573 			for (i = 0; i < MAXPLAYERS; i++)
4574 			{
4575 				if (netgame && i != serverplayer && !IsPlayerAdmin(i))
4576 					continue;
4577 				else if (splitscreen) {
4578 					// Both players' controls are locked,
4579 					// But only consoleplayer can advance the prompt.
4580 					// \todo Proper per-player splitscreen support (individual prompts)
4581 					if (i == consoleplayer || i == secondarydisplayplayer)
4582 						nocontrolallowed(i)
4583 				}
4584 				else if (i == consoleplayer)
4585 					nocontrolallowed(i)
4586 
4587 				if (!splitscreen)
4588 					break;
4589 			}
4590 		}
4591 
4592 		if (timetonext >= 1)
4593 			timetonext--;
4594 
4595 		if (!timetonext)
4596 			F_AdvanceToNextPage();
4597 
4598 		F_WriteText();
4599 	}
4600 	else
4601 	{
4602 		if (promptblockcontrols)
4603 		{
4604 			for (i = 0; i < MAXPLAYERS; i++)
4605 			{
4606 				if (netgame && i != serverplayer && !IsPlayerAdmin(i))
4607 					continue;
4608 				else if (splitscreen) {
4609 					// Both players' controls are locked,
4610 					// But only the triggering player can advance the prompt.
4611 					if (i == consoleplayer || i == secondarydisplayplayer)
4612 					{
4613 						players[i].powers[pw_nocontrol] = 1;
4614 
4615 						if (callplayer == consoleplayer || callplayer == secondarydisplayplayer)
4616 						{
4617 							if (i != callplayer)
4618 								continue;
4619 						}
4620 						else if (i != consoleplayer)
4621 							continue;
4622 					}
4623 					else
4624 						continue;
4625 				}
4626 				else if (i == consoleplayer)
4627 					nocontrolallowed(i)
4628 				else
4629 					continue;
4630 
4631 				if ((players[i].cmd.buttons & BT_SPIN) || (players[i].cmd.buttons & BT_JUMP))
4632 				{
4633 					if (timetonext > 1)
4634 						timetonext--;
4635 					else if (cutscene_baseptr) // don't set boost if we just reset the string
4636 						cutscene_boostspeed = 1; // only after a slight delay
4637 
4638 					if (keypressed)
4639 					{
4640 						if (!splitscreen)
4641 							break;
4642 						else
4643 							continue;
4644 					}
4645 
4646 					if (!timetonext) // is 0 when finished generating text
4647 					{
4648 						F_AdvanceToNextPage();
4649 						if (promptactive)
4650 							S_StartSound(NULL, sfx_menu1);
4651 					}
4652 					keypressed = true; // prevent repeat events
4653 				}
4654 				else if (!(players[i].cmd.buttons & BT_SPIN) && !(players[i].cmd.buttons & BT_JUMP))
4655 					keypressed = false;
4656 
4657 				if (!splitscreen)
4658 					break;
4659 			}
4660 		}
4661 
4662 		// generate letter-by-letter text
4663 		if (scenenum >= MAX_PAGES ||
4664 			!textprompts[cutnum]->page[scenenum].text ||
4665 			!textprompts[cutnum]->page[scenenum].text[0] ||
4666 			!F_WriteText())
4667 			timetonext = !promptblockcontrols; // never show the chevron if we can't toggle pages
4668 	}
4669 
4670 	// gfx
4671 	if (picnum >= 0 && picnum < numpics)
4672 	{
4673 		if (animtimer <= 0)
4674 		{
4675 			boolean persistanimtimer = false;
4676 
4677 			if (picnum < numpics-1 && textprompts[cutnum]->page[scenenum].picname[picnum+1][0] != '\0')
4678 				picnum++;
4679 			else if (picmode == PROMPT_PIC_LOOP)
4680 				picnum = pictoloop;
4681 			else if (picmode == PROMPT_PIC_DESTROY)
4682 				picnum = -1;
4683 			else // if (picmode == PROMPT_PIC_PERSIST)
4684 				persistanimtimer = true;
4685 
4686 			if (!persistanimtimer && picnum >= 0)
4687 			{
4688 				picxpos = textprompts[cutnum]->page[scenenum].xcoord[picnum];
4689 				picypos = textprompts[cutnum]->page[scenenum].ycoord[picnum];
4690 				pictime = textprompts[cutnum]->page[scenenum].picduration[picnum];
4691 				animtimer = pictime;
4692 			}
4693 		}
4694 		else
4695 			animtimer--;
4696 	}
4697 }
4698