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 }