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