1 /***************************************************************************
2     OutRun Engine Entry Point.
3 
4     This is the hub of the ported OutRun code.
5 
6     Copyright Chris White.
7     See license.txt for more details.
8 ***************************************************************************/
9 
10 #include "main.hpp"
11 #include "trackloader.hpp"
12 #include "../utils.hpp"
13 #include "engine/oattractai.hpp"
14 #include "engine/oanimseq.hpp"
15 #include "engine/obonus.hpp"
16 #include "engine/ocrash.hpp"
17 #include "engine/oferrari.hpp"
18 #include "engine/ohiscore.hpp"
19 #include "engine/ohud.hpp"
20 #include "engine/oinputs.hpp"
21 #include "engine/olevelobjs.hpp"
22 #include "engine/ologo.hpp"
23 #include "engine/omap.hpp"
24 #include "engine/omusic.hpp"
25 #include "engine/ooutputs.hpp"
26 #include "engine/osmoke.hpp"
27 #include "engine/outrun.hpp"
28 #include "engine/opalette.hpp"
29 #include "engine/ostats.hpp"
30 #include "engine/otiles.hpp"
31 #include "engine/otraffic.hpp"
32 #include "engine/outils.hpp"
33 
34 Outrun outrun;
35 
36 /*
37     Known Core Engine Issues:
38 
39     - Road split. Minor bug on positioning traffic on correct side of screen for one frame or so at the point of split.
40       Most noticeable in 60fps mode.
41       The Dreamcast version exhibits a bug where the road renders on the wrong side of the screen for one frame at this point.
42       The original version (and Cannonball) has a problem where the cars face the wrong direction for one frame.
43 
44     Bugs Present In Original 1986 Release:
45 
46     - Millisecond displays incorrectly on Extend Time screen [fixed]
47     - Erroneous values in sprite zooming table [fixed]
48     - Shadow popping into position randomly. Try setting car x position to 0x1E2. (0x260050) [fixed]
49     - Stage 2a: Incomplete arches due to lack of sprite slots [fixed]
50     - Best OutRunners screen looks odd after Stage 2 Gateway
51     - Stage 3c: Clouds overlapping trees [unable to fix easily]
52     - Sometimes the Ferrari stalls on the start-line on game restart. Happens in Attract Mode too.
53     - On completion screen, some of the side crowd graphics are misplaced. Japanese version only [fixed]
54 
55 */
56 
Outrun()57 Outrun::Outrun()
58 {
59     outputs = new OOutputs();
60 }
61 
~Outrun()62 Outrun::~Outrun()
63 {
64     delete outputs;
65 }
66 
init()67 void Outrun::init()
68 {
69     freeze_timer = cannonball_mode == MODE_TTRIAL ? true : config.engine.freeze_timer;
70     video.enabled = false;
71     select_course(config.engine.jap != 0, config.engine.prototype != 0);
72     video.clear_text_ram();
73 
74     tick_counter = 0;
75 
76     if (config.smartypi.enabled)
77     {
78         outputs->set_mode(OOutputs::MODE_CABINET);
79         if (config.smartypi.cabinet == Config::CABINET_MOVING)
80         {
81             init_motor_calibration();
82             return;
83         }
84     }
85     else if (config.controls.haptic)
86         outputs->set_mode(OOutputs::MODE_FFEEDBACK);
87     else if (config.controls.rumble)
88         outputs->set_mode(OOutputs::MODE_RUMBLE);
89 
90     boot();
91 }
92 
boot()93 void Outrun::boot()
94 {
95     game_state = config.engine.layout_debug ? GS_INIT_GAME : GS_INIT;
96     // Initialize default hi-score entries
97     ohiscore.init_def_scores();
98     // Load saved hi-score entries
99     config.load_scores(cannonball_mode == Outrun::MODE_ORIGINAL);
100     ostats.init(cannonball_mode == MODE_TTRIAL);
101     init_jump_table();
102     oinitengine.init(cannonball_mode == MODE_TTRIAL ? ttrial.level : 0);
103     osoundint.init();
104     outils::reset_random_seed(); // Ensure we match the genuine boot up of the original game each time
105 }
106 
tick(bool tick_frame)107 void Outrun::tick(bool tick_frame)
108 {
109     this->tick_frame = tick_frame;
110 
111     if (tick_frame)
112     {
113         tick_counter++;
114 
115         if (game_state >= GS_START1 && game_state <= GS_INGAME)
116         {
117             if (input.has_pressed(Input::VIEWPOINT))
118             {
119                 int mode = oroad.get_view_mode() + 1;
120                 if (mode > ORoad::VIEW_INCAR)
121                     mode = ORoad::VIEW_ORIGINAL;
122 
123                 oroad.set_view_mode(mode);
124             }
125         }
126     }
127 
128     // Only tick the road cpu twice for every time we tick the main cpu
129     // The timing here isn't perfect, as normally the road CPU would run in parallel with the main CPU.
130     // We can potentially hack this by calling the road CPU twice.
131     // Most noticeable with clipping sprites on hills.
132 
133     // 30 FPS
134     // Updates Game Logic 1/2 frames
135     // Updates V-Blank 1/2 frames
136     if (config.fps == 30 && config.tick_fps == 30)
137     {
138         jump_table();
139         oroad.tick();
140         vint();
141         vint();
142     }
143     // 30/60 FPS Hybrid. (This is the same as the original game)
144     // Updates Game Logic 1/2 frames
145     // Updates V-Blank 1/1 frames
146     else if (config.fps == 60 && config.tick_fps == 30)
147     {
148         if (tick_frame)
149         {
150             jump_table();
151             oroad.tick();
152         }
153         vint();
154     }
155     // 60 FPS. Smooth Mode.
156     // Updates Game Logic 1/1 frames
157     // Updates V-Blank 1/1 frames
158     else
159     {
160         jump_table();
161         oroad.tick();
162         vint();
163     }
164 
165     // Moved out of vertical interrupt
166     if (tick_frame)
167     {
168         uint8_t coin = oinputs.do_credits();
169         outputs->coin_chute_out(&outputs->chute1, coin == 1);
170         outputs->coin_chute_out(&outputs->chute2, coin == 2);
171     }
172 
173     // Draw FPS
174     if (config.video.fps_count)
175         ohud.draw_fps_counter(cannonball::fps_counter);
176 }
177 
178 // Vertical Interrupt
vint()179 void Outrun::vint()
180 {
181     otiles.write_tilemap_hw();
182     osprites.update_sprites();
183     otiles.update_tilemaps(cannonball_mode == MODE_ORIGINAL ? ostats.cur_stage : 0);
184     opalette.cycle_sky_palette();
185     opalette.fade_palette();
186     ostats.do_timers();
187     if (cannonball_mode != MODE_TTRIAL) ohud.draw_timer1(ostats.time_counter);
188     oinitengine.set_granular_position();
189 }
190 
jump_table()191 void Outrun::jump_table()
192 {
193     if (tick_frame && game_state != GS_CALIBRATE_MOTOR)
194     {
195         main_switch();                  // Address #1 (0xB128) - Main Switch
196         oinputs.adjust_inputs();        // Address #2 (0x74D8) - Adjust Analogue Inputs
197     }
198 
199     switch (game_state)
200     {
201         case GS_REINIT:
202         case GS_CALIBRATE_MOTOR:
203             break;
204 
205         // ----------------------------------------------------------------------------------------
206         // Couse Map Specific Code
207         // ----------------------------------------------------------------------------------------
208         case GS_MAP:
209             omap.tick();
210             break;
211 
212 
213         case GS_MUSIC:
214             if (tick_frame) omusic.check_start(); // Check for start button
215             osprites.tick();
216             olevelobjs.do_sprite_routine();
217 
218             if (!outrun.tick_frame)
219             {
220                 omusic.blit();
221             }
222             break;
223 
224         // ----------------------------------------------------------------------------------------
225         // Best OutRunners Entry (EditJumpTable3 Entries)
226         // ----------------------------------------------------------------------------------------
227         case GS_INIT_BEST2:
228         case GS_BEST2:
229             osprites.tick();
230             olevelobjs.do_sprite_routine();
231 
232             if (!tick_frame)
233             {
234                 // Check for start button if credits are remaining and set state to Music Selection
235                 if (ostats.credits && input.is_pressed_clear(Input::START))
236                     game_state = GS_INIT_MUSIC;
237             }
238             break;
239 
240         // ----------------------------------------------------------------------------------------
241         // Core Game Engine Routines
242         // ----------------------------------------------------------------------------------------
243         case GS_LOGO:
244             if (!tick_frame)
245                 ologo.blit();
246 
247         case GS_ATTRACT:
248         case GS_BEST1:
249             if (tick_frame) check_freeplay_start();
250 
251         default:
252             if (tick_frame) osprites.tick();                // Address #3 Jump_SetupSprites
253             olevelobjs.do_sprite_routine();                 // replaces calling each sprite individually
254             if (!config.engine.disable_traffic)
255                 otraffic.tick();                            // Spawn & Tick Traffic
256             if (tick_frame) oinitengine.init_crash_bonus(); // Initalize crash sequence or bonus code
257             oferrari.tick();
258             if (oferrari.state != OFerrari::FERRARI_END_SEQ)
259             {
260                 oanimseq.flag_seq();
261                 ocrash.tick();
262                 osmoke.draw_ferrari_smoke(&osprites.jump_table[OSprites::SPRITE_SMOKE1]); // Do Left Hand Smoke
263                 oferrari.draw_shadow();                                                   // (0xF1A2) - Draw Ferrari Shadow
264                 osmoke.draw_ferrari_smoke(&osprites.jump_table[OSprites::SPRITE_SMOKE2]); // Do Right Hand Smoke
265             }
266             else
267             {
268                 osmoke.draw_ferrari_smoke(&osprites.jump_table[OSprites::SPRITE_SMOKE1]); // Do Left Hand Smoke
269                 osmoke.draw_ferrari_smoke(&osprites.jump_table[OSprites::SPRITE_SMOKE2]); // Do Right Hand Smoke
270             }
271             break;
272     }
273 
274     osprites.sprite_copy();
275 
276     // Motor Code
277     if (tick_frame)
278     {
279         if (game_state == GS_CALIBRATE_MOTOR)
280         {
281            // if (outputs->calibrate_motor(packet->ai1, packet->mci, 0))
282             {
283                 video.enabled     = false;
284                 video.clear_text_ram();
285                 oroad.horizon_set = 0;
286                 boot();
287             }
288         }
289         else
290         {
291             outputs->tick(oinputs.input_steering); // Todo: Should be motor_input on real cab!
292         }
293     }
294 }
295 
296 // Source: 0xB15E
main_switch()297 void Outrun::main_switch()
298 {
299     switch (game_state)
300     {
301         case GS_INIT:
302             init_attract();
303             // fall through
304 
305         // ----------------------------------------------------------------------------------------
306         // Attract Mode
307         // ----------------------------------------------------------------------------------------
308         case GS_ATTRACT:
309             tick_attract();
310             break;
311 
312         case GS_INIT_BEST1:
313             oferrari.car_ctrl_active = false;
314             oinitengine.car_increment = 0;
315             oferrari.car_inc_old = 0;
316             ostats.time_counter = 5;
317             ostats.frame_counter = ostats.frame_reset;
318             ohiscore.init();
319             osoundint.queue_sound(sound::FM_RESET);
320             cannonball::audio.clear_wav();
321             game_state = GS_BEST1;
322 
323         case GS_BEST1:
324             ohud.draw_copyright_text();
325             ohiscore.display_scores();
326             ohud.draw_credits();
327             ohud.draw_insert_coin();
328             if (ostats.credits)
329                 game_state = GS_INIT_MUSIC;
330             else if (decrement_timers())
331                 game_state = GS_INIT_LOGO;
332             break;
333 
334         case GS_INIT_LOGO:
335             video.clear_text_ram();
336             oferrari.car_ctrl_active = false;
337             oinitengine.car_increment = 0;
338             oferrari.car_inc_old = 0;
339             ostats.time_counter = 5;
340             ostats.frame_counter = ostats.frame_reset;
341             osoundint.queue_sound(0);
342             ologo.enable(sound::FM_RESET);
343             game_state = GS_LOGO;
344 
345         case GS_LOGO:
346             ohud.draw_credits();
347             ohud.draw_copyright_text();
348             ohud.draw_insert_coin();
349             ologo.tick();
350 
351             if (ostats.credits)
352                 game_state = GS_INIT_MUSIC;
353             else if (decrement_timers())
354             {
355                 ologo.disable();
356                 game_state = GS_INIT; // Resume attract mode
357             }
358             break;
359 
360         // ----------------------------------------------------------------------------------------
361         // Music Select Screen
362         // ----------------------------------------------------------------------------------------
363 
364         case GS_INIT_MUSIC:
365             omusic.enable();
366             game_state = GS_MUSIC;
367 
368         case GS_MUSIC:
369             ohud.draw_credits();
370             ohud.draw_insert_coin();
371             omusic.tick();
372             if (decrement_timers())
373             {
374                 omusic.disable();
375                 game_state = GS_INIT_GAME;
376             }
377             break;
378         // ----------------------------------------------------------------------------------------
379         // In-Game
380         // ----------------------------------------------------------------------------------------
381 
382         case GS_INIT_GAME:
383             //ROM:0000B3E8                 move.w  #-1,(ingame_active1).l              ; Denote in-game engine is active
384             //ROM:0000B3F0                 clr.l   (prev_game_time).l                  ; Reset overall game time
385             //ROM:0000B3F6                 move.w  #-1,(ingame_active2).l
386             video.clear_text_ram();
387             oferrari.car_ctrl_active = true;
388             init_jump_table();
389             oinitengine.init(cannonball_mode == MODE_TTRIAL ? ttrial.level : 0);
390             // Timing Hack to ensure horizon is correct
391             // Note that the original code disables the screen, and waits for the second CPU's interrupt instead
392             oroad.tick();
393             oroad.tick();
394             oroad.tick();
395             osoundint.queue_sound(sound::STOP_CHEERS);
396             osoundint.queue_sound(sound::VOICE_GETREADY);
397             osoundint.queue_sound(sound::REVS);             // Moved from Z80 Code for extra flexibility
398             omusic.play_music();
399 
400             if (!freeze_timer)
401                 ostats.time_counter = ostats.TIME[config.engine.dip_time * 40]; // Set time to begin level with
402             else
403                 ostats.time_counter = 0x30;
404 
405             ostats.frame_counter = ostats.frame_reset + 50;
406             ostats.credits--;                                   // Update Credits
407             ohud.blit_text1(TEXT1_CLEAR_START);
408             ohud.blit_text1(TEXT1_CLEAR_CREDITS);
409             osoundint.queue_sound(sound::INIT_CHEERS);
410             video.enabled = true;
411             game_state = GS_START1;
412             ohud.draw_main_hud();
413             // fall through
414 
415         //  Start Game - Car Driving In
416         case GS_START1:
417         case GS_START2:
418             if (--ostats.frame_counter < 0)
419             {
420                 osoundint.queue_sound(sound::SIGNAL1);
421                 ostats.frame_counter = ostats.frame_reset;
422                 game_state++;
423             }
424             break;
425 
426         case GS_START3:
427             if (--ostats.frame_counter < 0)
428             {
429                 if (cannonball_mode == MODE_TTRIAL)
430                 {
431                     ohud.clear_timetrial_text();
432                 }
433 
434                 osoundint.queue_sound(sound::SIGNAL2);
435                 osoundint.queue_sound(sound::STOP_CHEERS);
436                 ostats.frame_counter = ostats.frame_reset;
437                 game_state++;
438             }
439             break;
440 
441         case GS_INGAME:
442             if (decrement_timers())
443                 game_state = GS_INIT_GAMEOVER;
444             break;
445 
446         // ----------------------------------------------------------------------------------------
447         // Bonus Mode
448         // ----------------------------------------------------------------------------------------
449         case GS_INIT_BONUS:
450             ostats.frame_counter = ostats.frame_reset;
451             obonus.bonus_control = OBonus::BONUS_INIT;  // Initialize Bonus Mode Logic
452             oroad.road_load_end   |= BIT_0;             // Instruct CPU 1 to load end road section
453             ostats.game_completed |= BIT_0;             // Denote game completed
454             obonus.bonus_timer = 3600;                  // Safety Timer Added in Rev. A Roms
455             game_state = GS_BONUS;
456 
457         case GS_BONUS:
458             if (--obonus.bonus_timer < 0)
459             {
460                 obonus.bonus_control = OBonus::BONUS_DISABLE;
461                 game_state = GS_INIT_GAMEOVER;
462             }
463             break;
464 
465         // ----------------------------------------------------------------------------------------
466         // Display Game Over Text
467         // ----------------------------------------------------------------------------------------
468         case GS_INIT_GAMEOVER:
469             if (cannonball_mode != MODE_TTRIAL)
470             {
471                 oferrari.car_ctrl_active = false; // -1
472                 oinitengine.car_increment = 0;
473                 oferrari.car_inc_old = 0;
474                 ostats.time_counter = 3;
475                 ostats.frame_counter = ostats.frame_reset;
476                 ohud.blit_text2(TEXT2_GAMEOVER);
477             }
478             else
479             {
480                 ohud.blit_text_big(7, ttrial.new_high_score ? "NEW RECORD" : "BAD LUCK");
481 
482                 ohud.blit_text1(TEXT1_LAPTIME1);
483                 ohud.blit_text1(TEXT1_LAPTIME2);
484                 ohud.draw_lap_timer(0x110554, ttrial.best_lap, ttrial.best_lap[2]);
485 
486                 ohud.blit_text_new(9,  14, "OVERTAKES          - ");
487                 ohud.blit_text_new(31, 14, Utils::to_string((int) ttrial.overtakes).c_str(), OHud::GREEN);
488                 ohud.blit_text_new(9,  16, "VEHICLE COLLISIONS - ");
489                 ohud.blit_text_new(31, 16, Utils::to_string((int) ttrial.vehicle_cols).c_str(), OHud::GREEN);
490                 ohud.blit_text_new(9,  18, "CRASHES            - ");
491                 ohud.blit_text_new(31, 18, Utils::to_string((int) ttrial.crashes).c_str(), OHud::GREEN);
492             }
493             osoundint.queue_sound(sound::NEW_COMMAND);
494             game_state = GS_GAMEOVER;
495 
496         case GS_GAMEOVER:
497             if (cannonball_mode == MODE_ORIGINAL)
498             {
499                 if (decrement_timers())
500                     game_state = GS_INIT_MAP;
501             }
502             else if (cannonball_mode == MODE_CONT)
503             {
504                 if (decrement_timers())
505                     init_best_outrunners();
506             }
507             else if (cannonball_mode == MODE_TTRIAL)
508             {
509                 if (outrun.tick_counter & BIT_4)
510                     ohud.blit_text1(10, 20, TEXT1_PRESS_START);
511                 else
512                     ohud.blit_text1(10, 20, TEXT1_CLEAR_START);
513 
514                 if (input.is_pressed(Input::START))
515                     cannonball::state = cannonball::STATE_INIT_MENU;
516             }
517             break;
518 
519         // ----------------------------------------------------------------------------------------
520         // Display Course Map
521         // ----------------------------------------------------------------------------------------
522         case GS_INIT_MAP:
523             omap.init();
524             ohud.blit_text2(TEXT2_COURSEMAP);
525             game_state = GS_MAP;
526             // fall through
527 
528         case GS_MAP:
529             break;
530 
531         // ----------------------------------------------------------------------------------------
532         // Best OutRunners / Score Entry
533         // ----------------------------------------------------------------------------------------
534         case GS_INIT_BEST2:
535             oroad.set_view_mode(ORoad::VIEW_ORIGINAL, true);
536             // bsr.w   EndGame
537             osprites.disable_sprites();
538             otraffic.disable_traffic();
539             // bsr.w   EditJumpTable3
540             osprites.clear_palette_data();
541             olevelobjs.init_hiscore_sprites();
542             ocrash.coll_count1   = 0;
543             ocrash.coll_count2   = 0;
544             ocrash.crash_counter = 0;
545             ocrash.skid_counter  = 0;
546             ocrash.spin_control1 = 0;
547             oferrari.car_ctrl_active = false; // -1
548             oinitengine.car_increment = 0;
549             oferrari.car_inc_old = 0;
550             ostats.time_counter = config.engine.hiscore_timer;
551             ostats.frame_counter = ostats.frame_reset;
552             ohiscore.init();
553             osoundint.queue_sound(sound::NEW_COMMAND);
554             osoundint.queue_sound(sound::FM_RESET);
555             cannonball::audio.clear_wav();
556             game_state = GS_BEST2;
557             // fall through
558 
559         case GS_BEST2:
560             ohiscore.tick(); // Do High Score Logic
561             ohud.draw_credits();
562 
563             // If countdown has expired
564             if (decrement_timers())
565             {
566                 //ROM:0000B700                 bclr    #5,(ppi1_value).l                   ; Turn screen off (not activated until PPI written to)
567                 oferrari.car_ctrl_active = true; // 0 : Allow road updates
568                 init_jump_table();
569                 oinitengine.init(cannonball_mode == MODE_TTRIAL ? ttrial.level : 0);
570                 //ROM:0000B716                 bclr    #0,(byte_260550).l
571                 game_state = GS_REINIT;          // Reinit game to attract mode
572             }
573             break;
574 
575         // ----------------------------------------------------------------------------------------
576         // Reinitialize Game After High Score Entry
577         // ----------------------------------------------------------------------------------------
578         case GS_REINIT:
579             video.clear_text_ram();
580             game_state = GS_INIT;
581             break;
582     }
583 
584     oinitengine.update_road();
585     oinitengine.update_engine();
586 
587     // --------------------------------------------------------------------------------------------
588     // Debugging Only
589     // --------------------------------------------------------------------------------------------
590     if (DEBUG_LEVEL)
591     {
592         if (oinitengine.rd_split_state != 0)
593         {
594             if (!fork_chosen)
595             {
596                 if (oinitengine.camera_x_off < 0)
597                     fork_chosen = -1;
598                 else
599                     fork_chosen = 1;
600             }
601         }
602         else if (fork_chosen)
603             fork_chosen = 0;
604 
605         // Hack to allow user to choose road fork with left/right
606         if (fork_chosen == -1)
607         {
608             oroad.road_width_bak = oroad.road_width >> 16;
609             oroad.car_x_bak = -oroad.road_width_bak;
610             oinitengine.car_x_pos = oroad.car_x_bak;
611         }
612         else
613         {
614             oroad.road_width_bak = oroad.road_width >> 16;
615             oroad.car_x_bak = oroad.road_width_bak;
616             oinitengine.car_x_pos = oroad.car_x_bak;
617         }
618     }
619 }
620 
621 // Setup Jump Table. Move from ROM to RAM.
622 //
623 // Source Address: 0x7E1C
624 // Input:          Sprite To Copy
625 // Output:         None
626 //
627 // ROM Format [0xF000 - 0xF1F5]
628 //
629 // Word 1: Number of entries [7D]
630 // Long 1: Address 1 (address of jump information)
631 // ...
632 // Long x: Address x
633 //
634 // Each address in the jump table is a pointer into ROM containing 0x1F words
635 // of info (so info is at 0x40 boundary in bytes)
636 //
637 // RAM Format[0x61800]
638 //
639 // 0x00 byte: If high byte set, take jump
640 // 0x01 byte: Index number
641 // 0x02 long: Address to jump to
init_jump_table()642 void Outrun::init_jump_table()
643 {
644     // Reset value to restore car increment to during attract mode
645     car_inc_bak = 0;
646 
647     osprites.init();
648     if (cannonball_mode != MODE_TTRIAL)
649     {
650         otraffic.init_stage1_traffic();      // Hard coded traffic in right hand lane
651         if (trackloader.display_start_line)
652             olevelobjs.init_startline_sprites(); // Hard coded start line sprites (not part of level data)
653     }
654     else if (trackloader.display_start_line)
655         olevelobjs.init_timetrial_sprites();
656 
657     otraffic.init();
658     osmoke.init();
659     oroad.init();
660     otiles.init();
661     opalette.init();
662     oinputs.init();
663     obonus.init();
664     outputs->init();
665 
666     video.tile_layer->set_x_clamp(video.tile_layer->RIGHT);
667     video.sprite_layer->set_x_clip(false);
668 }
669 
670 // -------------------------------------------------------------------------------
671 // Decrement Game Time
672 //
673 // Decrements Frame Count, and Overall Time Counter
674 //
675 // Returns true if timer expired.
676 // Source: 0xB736
677 // -------------------------------------------------------------------------------
decrement_timers()678 bool Outrun::decrement_timers()
679 {
680     // Cheat
681     if (freeze_timer && game_state == GS_INGAME)
682         return false;
683 
684     // Correct count-down timer running fast at 1/29th (3%)
685     // Fix timer counting extra second
686     if (config.engine.fix_timer)
687     {
688         if (--ostats.frame_counter > 0)
689             return false;
690 
691         ostats.frame_counter = ostats.frame_reset;
692         ostats.time_counter  = outils::bcd_sub(1, ostats.time_counter);
693 
694         // We need to manually refresh the HUD here to display '0' seconds
695         if (ostats.time_counter == 0)
696             ohud.draw_timer1(0);
697 
698         return (ostats.time_counter == 0);
699     }
700     else
701     {
702         if (--ostats.frame_counter >= 0)
703             return false;
704 
705         ostats.frame_counter = ostats.frame_reset;
706         ostats.time_counter  = outils::bcd_sub(1, ostats.time_counter);
707         return (ostats.time_counter < 0);
708     }
709 }
710 
711 // -------------------------------------------------------------------------------
712 // SMARTYPI: Motor Calibration
713 // -------------------------------------------------------------------------------
714 
init_motor_calibration()715 void Outrun::init_motor_calibration()
716 {
717     otiles.init();
718     opalette.init();
719     oinputs.init();
720     outputs->init();
721 
722     video.tile_layer->set_x_clamp(video.tile_layer->RIGHT);
723     video.sprite_layer->set_x_clip(false);
724 
725     otiles.fill_tilemap_color(0x4F60); // Fill Tilemap Light Blue
726 
727     video.enabled        = true;
728     osoundint.has_booted = true;
729 
730     oroad.init();
731     oroad.horizon_set    = 1;
732     oroad.horizon_base   = ORoad::HORIZON_OFF;
733     game_state           = GS_CALIBRATE_MOTOR;
734 
735 
736     // Write Palette To RAM
737     uint32_t dst = 0x120000;
738     const static uint32_t PAL_SERVICE[] = {0xFF, 0xFF00FF, 0xFF00FF, 0xFF0000};
739     video.write_pal32(&dst, PAL_SERVICE[0]);
740     video.write_pal32(&dst, PAL_SERVICE[1]);
741     video.write_pal32(&dst, PAL_SERVICE[2]);
742     video.write_pal32(&dst, PAL_SERVICE[3]);
743 }
744 
745 // -------------------------------------------------------------------------------
746 // Attract Mode Control
747 // -------------------------------------------------------------------------------
748 
init_attract()749 void Outrun::init_attract()
750 {
751     video.enabled             = true;
752     osoundint.has_booted      = true;
753     oferrari.car_ctrl_active  = true;
754     oferrari.car_inc_old      = car_inc_bak >> 16;
755     oinitengine.car_increment = car_inc_bak;
756     ostats.time_counter       = config.engine.new_attract ? 0x80 : 0x15;
757     ostats.frame_counter      = ostats.frame_reset;
758     attract_counter           = 0;
759     attract_view              = 0;
760     oattractai.init();
761     game_state = cannonball_mode == MODE_TTRIAL ? GS_INIT_MUSIC : GS_ATTRACT;
762 }
763 
tick_attract()764 void Outrun::tick_attract()
765 {
766     ohud.draw_credits();
767     ohud.draw_copyright_text();
768     ohud.draw_insert_coin();
769 
770     // Enhanced Attract Mode (Switch Between Views)
771     if (config.engine.new_attract)
772     {
773         if (++attract_counter > 240)
774         {
775             const static uint8_t VIEWS[] = {ORoad::VIEW_ORIGINAL, ORoad::VIEW_ELEVATED, ORoad::VIEW_INCAR};
776 
777             attract_counter = 0;
778             if (++attract_view > 2)
779                 attract_view = 0;
780             bool snap = VIEWS[attract_view] == ORoad::VIEW_INCAR;
781             oroad.set_view_mode(VIEWS[attract_view], snap);
782         }
783     }
784 
785     if (ostats.credits)
786         game_state = GS_INIT_MUSIC;
787 
788     else if (decrement_timers())
789     {
790         car_inc_bak = oinitengine.car_increment;
791         game_state = GS_INIT_BEST1;
792     }
793 }
794 
check_freeplay_start()795 void Outrun::check_freeplay_start()
796 {
797     if (config.engine.freeplay)
798     {
799         if (!ostats.credits && input.has_pressed(Input::START))
800         {
801             ostats.credits = 1;
802         }
803     }
804 }
805 
806 // -------------------------------------------------------------------------------
807 // Best OutRunners Initialization
808 // -------------------------------------------------------------------------------
809 
init_best_outrunners()810 void Outrun::init_best_outrunners()
811 {
812     video.enabled = false;
813     video.sprite_layer->set_x_clip(false); // Stop clipping in wide-screen mode.
814     otiles.fill_tilemap_color(0); // Fill Tilemap Black
815     osprites.disable_sprites();
816     oroad.horizon_base = 0x154;
817     ohiscore.setup_pal_best();    // Setup Palettes
818     ohiscore.setup_road_best();
819     game_state = GS_INIT_BEST2;
820 }
821 
822 // -------------------------------------------------------------------------------
823 // Remap ROM addresses and select course.
824 // -------------------------------------------------------------------------------
825 
select_course(bool jap,bool prototype)826 void Outrun::select_course(bool jap, bool prototype)
827 {
828     if (jap)
829     {
830         roms.rom0p = &roms.j_rom0;
831         roms.rom1p = &roms.j_rom1;
832 
833         // Main CPU
834         adr.tiles_def_lookup      = TILES_DEF_LOOKUP_J;
835         adr.tiles_table           = TILES_TABLE_J;
836         adr.sprite_master_table   = SPRITE_MASTER_TABLE_J;
837         adr.sprite_type_table     = SPRITE_TYPE_TABLE_J;
838         adr.sprite_def_props1     = SPRITE_DEF_PROPS1_J;
839         adr.sprite_def_props2     = SPRITE_DEF_PROPS2_J;
840         adr.sprite_cloud          = SPRITE_CLOUD_FRAMES_J;
841         adr.sprite_minitree       = SPRITE_MINITREE_FRAMES_J;
842         adr.sprite_grass          = SPRITE_GRASS_FRAMES_J;
843         adr.sprite_sand           = SPRITE_SAND_FRAMES_J;
844         adr.sprite_stone          = SPRITE_STONE_FRAMES_J;
845         adr.sprite_water          = SPRITE_WATER_FRAMES_J;
846         adr.sprite_ferrari_frames = SPRITE_FERRARI_FRAMES_J;
847         adr.sprite_skid_frames    = SPRITE_SKID_FRAMES_J;
848         adr.sprite_pass_frames    = SPRITE_PASS_FRAMES_J;
849         adr.sprite_pass1_skidl    = SPRITE_PASS1_SKIDL_J;
850         adr.sprite_pass1_skidr    = SPRITE_PASS1_SKIDR_J;
851         adr.sprite_pass2_skidl    = SPRITE_PASS2_SKIDL_J;
852         adr.sprite_pass2_skidr    = SPRITE_PASS2_SKIDR_J;
853         adr.sprite_crash_spin1    = SPRITE_CRASH_SPIN1_J;
854         adr.sprite_crash_spin2    = SPRITE_CRASH_SPIN2_J;
855         adr.sprite_bump_data1     = SPRITE_BUMP_DATA1_J;
856         adr.sprite_bump_data2     = SPRITE_BUMP_DATA2_J;
857         adr.sprite_crash_man1     = SPRITE_CRASH_MAN1_J;
858         adr.sprite_crash_girl1    = SPRITE_CRASH_GIRL1_J;
859         adr.sprite_crash_flip     = SPRITE_CRASH_FLIP_J;
860         adr.sprite_crash_flip_m1  = SPRITE_CRASH_FLIP_MAN1_J;
861         adr.sprite_crash_flip_g1  = SPRITE_CRASH_FLIP_GIRL1_J;
862         adr.sprite_crash_flip_m2  = SPRITE_CRASH_FLIP_MAN2_J;
863         adr.sprite_crash_flip_g2  = SPRITE_CRASH_FLIP_GIRL2_J;
864         adr.sprite_crash_man2     = SPRITE_CRASH_MAN2_J;
865         adr.sprite_crash_girl2    = SPRITE_CRASH_GIRL2_J;
866         adr.smoke_data            = SMOKE_DATA_J;
867         adr.spray_data            = SPRAY_DATA_J;
868         adr.anim_ferrari_frames   = ANIM_FERRARI_FRAMES_J;
869         adr.anim_endseq_obj1      = ANIM_ENDSEQ_OBJ1_J;
870         adr.anim_endseq_obj2      = ANIM_ENDSEQ_OBJ2_J;
871         adr.anim_endseq_obj3      = ANIM_ENDSEQ_OBJ3_J;
872         adr.anim_endseq_obj4      = ANIM_ENDSEQ_OBJ4_J;
873         adr.anim_endseq_obj5      = ANIM_ENDSEQ_OBJ5_J;
874         adr.anim_endseq_obj6      = ANIM_ENDSEQ_OBJ6_J;
875         adr.anim_endseq_obj7      = ANIM_ENDSEQ_OBJ7_J;
876         adr.anim_endseq_obj8      = ANIM_ENDSEQ_OBJ8_J;
877         adr.anim_endseq_objA      = ANIM_ENDSEQ_OBJA_J;
878         adr.anim_endseq_objB      = ANIM_ENDSEQ_OBJB_J;
879         adr.anim_end_table        = ANIM_END_TABLE_J;
880         adr.shadow_data           = SPRITE_SHADOW_DATA_J;
881         adr.shadow_frames         = SPRITE_SHDW_FRAMES_J;
882         adr.sprite_shadow_small   = SPRITE_SHDW_SMALL_J;
883         adr.sprite_logo_bg        = SPRITE_LOGO_BG_J;
884         adr.sprite_logo_car       = SPRITE_LOGO_CAR_J;
885         adr.sprite_logo_bird1     = SPRITE_LOGO_BIRD1_J;
886         adr.sprite_logo_bird2     = SPRITE_LOGO_BIRD2_J;
887         adr.sprite_logo_base      = SPRITE_LOGO_BASE_J;
888         adr.sprite_logo_text      = SPRITE_LOGO_TEXT_J;
889         adr.sprite_logo_palm1     = SPRITE_LOGO_PALM1_J;
890         adr.sprite_logo_palm2     = SPRITE_LOGO_PALM2_J;
891         adr.sprite_logo_palm3     = SPRITE_LOGO_PALM3_J;
892         adr.sprite_fm_left        = SPRITE_FM_LEFT_J;
893         adr.sprite_fm_centre      = SPRITE_FM_CENTRE_J;
894         adr.sprite_fm_right       = SPRITE_FM_RIGHT_J;
895         adr.sprite_dial_left      = SPRITE_DIAL_LEFT_J;
896         adr.sprite_dial_centre    = SPRITE_DIAL_CENTRE_J;
897         adr.sprite_dial_right     = SPRITE_DIAL_RIGHT_J;
898         adr.sprite_eq             = SPRITE_EQ_J;
899         adr.sprite_radio          = SPRITE_RADIO_J;
900         adr.sprite_hand_left      = SPRITE_HAND_LEFT_J;
901         adr.sprite_hand_centre    = SPRITE_HAND_CENTRE_J;
902         adr.sprite_hand_right     = SPRITE_HAND_RIGHT_J;
903         adr.sprite_coursemap_top  = SPRITE_COURSEMAP_TOP_J;
904         adr.sprite_coursemap_bot  = SPRITE_COURSEMAP_BOT_J;
905         adr.sprite_coursemap_end  = SPRITE_COURSEMAP_END_J;
906         adr.sprite_minicar_right  = SPRITE_MINICAR_RIGHT_J;
907         adr.sprite_minicar_up     = SPRITE_MINICAR_UP_J;
908         adr.sprite_minicar_down   = SPRITE_MINICAR_DOWN_J;
909         adr.anim_seq_flag         = ANIM_SEQ_FLAG_J;
910         adr.anim_ferrari_curr     = ANIM_FERRARI_CURR_J;
911         adr.anim_ferrari_next     = ANIM_FERRARI_NEXT_J;
912         adr.anim_pass1_curr       = ANIM_PASS1_CURR_J;
913         adr.anim_pass1_next       = ANIM_PASS1_NEXT_J;
914         adr.anim_pass2_curr       = ANIM_PASS2_CURR_J;
915         adr.anim_pass2_next       = ANIM_PASS2_NEXT_J;
916         adr.traffic_props         = TRAFFIC_PROPS_J;
917         adr.traffic_data          = TRAFFIC_DATA_J;
918         adr.sprite_porsche        = SPRITE_PORSCHE_J;
919         adr.sprite_coursemap      = SPRITE_COURSEMAP_J;
920         adr.road_seg_table        = ROAD_SEG_TABLE_J;
921         adr.road_seg_end          = ROAD_SEG_TABLE_END_J;
922         adr.road_seg_split        = ROAD_SEG_TABLE_SPLIT_J;
923 
924         // Sub CPU
925         adr.road_height_lookup    = ROAD_HEIGHT_LOOKUP_J;
926     }
927     else
928     {
929         roms.rom0p = &roms.rom0;
930         roms.rom1p = &roms.rom1;
931 
932         // Main CPU
933         adr.tiles_def_lookup      = TILES_DEF_LOOKUP;
934         adr.tiles_table           = TILES_TABLE;
935         adr.sprite_master_table   = SPRITE_MASTER_TABLE;
936         adr.sprite_type_table     = SPRITE_TYPE_TABLE;
937         adr.sprite_def_props1     = SPRITE_DEF_PROPS1;
938         adr.sprite_def_props2     = SPRITE_DEF_PROPS2;
939         adr.sprite_cloud          = SPRITE_CLOUD_FRAMES;
940         adr.sprite_minitree       = SPRITE_MINITREE_FRAMES;
941         adr.sprite_grass          = SPRITE_GRASS_FRAMES;
942         adr.sprite_sand           = SPRITE_SAND_FRAMES;
943         adr.sprite_stone          = SPRITE_STONE_FRAMES;
944         adr.sprite_water          = SPRITE_WATER_FRAMES;
945         adr.sprite_ferrari_frames = SPRITE_FERRARI_FRAMES;
946         adr.sprite_skid_frames    = SPRITE_SKID_FRAMES;
947         adr.sprite_pass_frames    = SPRITE_PASS_FRAMES;
948         adr.sprite_pass1_skidl    = SPRITE_PASS1_SKIDL;
949         adr.sprite_pass1_skidr    = SPRITE_PASS1_SKIDR;
950         adr.sprite_pass2_skidl    = SPRITE_PASS2_SKIDL;
951         adr.sprite_pass2_skidr    = SPRITE_PASS2_SKIDR;
952         adr.sprite_crash_spin1    = SPRITE_CRASH_SPIN1;
953         adr.sprite_crash_spin2    = SPRITE_CRASH_SPIN2;
954         adr.sprite_bump_data1     = SPRITE_BUMP_DATA1;
955         adr.sprite_bump_data2     = SPRITE_BUMP_DATA2;
956         adr.sprite_crash_man1     = SPRITE_CRASH_MAN1;
957         adr.sprite_crash_girl1    = SPRITE_CRASH_GIRL1;
958         adr.sprite_crash_flip     = SPRITE_CRASH_FLIP;
959         adr.sprite_crash_flip_m1  = SPRITE_CRASH_FLIP_MAN1;
960         adr.sprite_crash_flip_g1  = SPRITE_CRASH_FLIP_GIRL1;
961         adr.sprite_crash_flip_m2  = SPRITE_CRASH_FLIP_MAN2;
962         adr.sprite_crash_flip_g2  = SPRITE_CRASH_FLIP_GIRL2;
963         adr.sprite_crash_man2     = SPRITE_CRASH_MAN2;
964         adr.sprite_crash_girl2    = SPRITE_CRASH_GIRL2;
965         adr.smoke_data            = SMOKE_DATA;
966         adr.spray_data            = SPRAY_DATA;
967         adr.shadow_data           = SPRITE_SHADOW_DATA;
968         adr.shadow_frames         = SPRITE_SHDW_FRAMES;
969         adr.sprite_shadow_small   = SPRITE_SHDW_SMALL;
970         adr.sprite_logo_bg        = SPRITE_LOGO_BG;
971         adr.sprite_logo_car       = SPRITE_LOGO_CAR;
972         adr.sprite_logo_bird1     = SPRITE_LOGO_BIRD1;
973         adr.sprite_logo_bird2     = SPRITE_LOGO_BIRD2;
974         adr.sprite_logo_base      = SPRITE_LOGO_BASE;
975         adr.sprite_logo_text      = SPRITE_LOGO_TEXT;
976         adr.sprite_logo_palm1     = SPRITE_LOGO_PALM1;
977         adr.sprite_logo_palm2     = SPRITE_LOGO_PALM2;
978         adr.sprite_logo_palm3     = SPRITE_LOGO_PALM3;
979         adr.sprite_fm_left        = SPRITE_FM_LEFT;
980         adr.sprite_fm_centre      = SPRITE_FM_CENTRE;
981         adr.sprite_fm_right       = SPRITE_FM_RIGHT;
982         adr.sprite_dial_left      = SPRITE_DIAL_LEFT;
983         adr.sprite_dial_centre    = SPRITE_DIAL_CENTRE;
984         adr.sprite_dial_right     = SPRITE_DIAL_RIGHT;
985         adr.sprite_eq             = SPRITE_EQ;
986         adr.sprite_radio          = SPRITE_RADIO;
987         adr.sprite_hand_left      = SPRITE_HAND_LEFT;
988         adr.sprite_hand_centre    = SPRITE_HAND_CENTRE;
989         adr.sprite_hand_right     = SPRITE_HAND_RIGHT;
990         adr.sprite_coursemap_top  = SPRITE_COURSEMAP_TOP;
991         adr.sprite_coursemap_bot  = SPRITE_COURSEMAP_BOT;
992         adr.sprite_coursemap_end  = SPRITE_COURSEMAP_END;
993         adr.sprite_minicar_right  = SPRITE_MINICAR_RIGHT;
994         adr.sprite_minicar_up     = SPRITE_MINICAR_UP;
995         adr.sprite_minicar_down   = SPRITE_MINICAR_DOWN;
996         adr.anim_seq_flag         = ANIM_SEQ_FLAG;
997         adr.anim_ferrari_curr     = ANIM_FERRARI_CURR;
998         adr.anim_ferrari_next     = ANIM_FERRARI_NEXT;
999         adr.anim_pass1_curr       = ANIM_PASS1_CURR;
1000         adr.anim_pass1_next       = ANIM_PASS1_NEXT;
1001         adr.anim_pass2_curr       = ANIM_PASS2_CURR;
1002         adr.anim_pass2_next       = ANIM_PASS2_NEXT;
1003         adr.anim_ferrari_frames   = ANIM_FERRARI_FRAMES;
1004         adr.anim_endseq_obj1      = ANIM_ENDSEQ_OBJ1;
1005         adr.anim_endseq_obj2      = ANIM_ENDSEQ_OBJ2;
1006         adr.anim_endseq_obj3      = ANIM_ENDSEQ_OBJ3;
1007         adr.anim_endseq_obj4      = ANIM_ENDSEQ_OBJ4;
1008         adr.anim_endseq_obj5      = ANIM_ENDSEQ_OBJ5;
1009         adr.anim_endseq_obj6      = ANIM_ENDSEQ_OBJ6;
1010         adr.anim_endseq_obj7      = ANIM_ENDSEQ_OBJ7;
1011         adr.anim_endseq_obj8      = ANIM_ENDSEQ_OBJ8;
1012         adr.anim_endseq_objA      = ANIM_ENDSEQ_OBJA;
1013         adr.anim_endseq_objB      = ANIM_ENDSEQ_OBJB;
1014         adr.anim_end_table        = ANIM_END_TABLE;
1015         adr.shadow_data           = SPRITE_SHADOW_DATA;
1016         adr.shadow_frames         = SPRITE_SHDW_FRAMES;
1017         adr.sprite_shadow_small   = SPRITE_SHDW_SMALL;
1018         adr.traffic_props         = TRAFFIC_PROPS;
1019         adr.traffic_data          = TRAFFIC_DATA;
1020         adr.sprite_porsche        = SPRITE_PORSCHE;
1021         adr.sprite_coursemap      = SPRITE_COURSEMAP;
1022         adr.road_seg_table        = ROAD_SEG_TABLE;
1023         adr.road_seg_end          = ROAD_SEG_TABLE_END;
1024         adr.road_seg_split        = ROAD_SEG_TABLE_SPLIT;
1025 
1026         // Sub CPU
1027         adr.road_height_lookup    = ROAD_HEIGHT_LOOKUP;
1028     }
1029 
1030     trackloader.init(jap);
1031 
1032     // Use Prototype Coconut Beach Track
1033     trackloader.stage_data[0] = prototype ? 0x3A : 0x3C;
1034 }