1 /***************************************************************************
2 Best Outrunners Name Entry & Display.
3 Used in attract mode, and at game end.
4
5 Copyright Chris White.
6 See license.txt for more details.
7 ***************************************************************************/
8
9 #include "main.hpp"
10 #include "engine/ohud.hpp"
11 #include "engine/oinputs.hpp"
12 #include "engine/ostats.hpp"
13 #include "engine/outils.hpp"
14 #include "engine/ohiscore.hpp"
15
16 OHiScore ohiscore;
17
OHiScore(void)18 OHiScore::OHiScore(void)
19 {
20 }
21
~OHiScore(void)22 OHiScore::~OHiScore(void)
23 {
24 }
25
26 // Clear score variables (not scores themselves)
27 //
28 // Source: 0xCE74
init()29 void OHiScore::init()
30 {
31 //ostats.score = 0x04100000; // hack
32
33 best_or_state = 0;
34 state = STATE_GETPOS;
35 score_pos = -1;
36 initial_selected = 0;
37 letter_selected = 0;
38 acc_curr = 0;
39 acc_prev = 0;
40 flash = 0;
41 score_display_pos = 0;
42 dest_total = 0;
43 }
44
45 // Setup palette for Best Outrunners High Score Entry
46 // This is the shaded red background for the hi-score entry
47 // Source: 0x360C
setup_pal_best()48 void OHiScore::setup_pal_best()
49 {
50 uint32_t src = PAL_BESTOR;
51 uint32_t dst = 0x120F00;
52
53 for (int i = 0; i <= 0x1F; i++)
54 video.write_pal32(&dst, roms.rom0.read32(&src));
55 }
56
57 // Setup road colour for Best Outrunners High Score Entry
58 // This is a pure black road for the hi-score entry
59 // Source: 0x3624
setup_road_best()60 void OHiScore::setup_road_best()
61 {
62 uint32_t dst = 0x120800;
63
64 for (int i = 0; i <= 0x1F; i++)
65 video.write_pal32(&dst, 0);
66 }
67
68 // Initalize Default Score Table
69 // Source: 0xD17A
init_def_scores()70 void OHiScore::init_def_scores()
71 {
72 uint32_t adr = DEFAULT_SCORES;
73
74 for (int i = 0; i < NO_SCORES; i++)
75 {
76 // Read default score
77 scores[i].score = roms.rom0.read32(&adr);
78
79 // Read initials
80 uint32_t initials = roms.rom0.read32(&adr);
81 scores[i].initial1 = (initials >> 24) & 0xFF;
82 scores[i].initial2 = (initials >> 16) & 0xFF;
83 scores[i].initial3 = (initials >> 8) & 0xFF;
84
85 // Read default time
86 scores[i].time = roms.rom0.read16(&adr);
87 //scores[i].time = (i & 1) ? 0x4321 : 0x1234; // hack to display 4m 43 51 or 1m 16 56
88 // Read map tiles
89 scores[i].maptiles = roms.rom0.read32(&adr);
90 //scores[i].maptiles = 0xe5c8c2d1; // hack to populate map tiles for testing
91 }
92 }
93
94 // Hi Score Processing Logic
95 //
96 // Source: 0xD1C4
tick()97 void OHiScore::tick()
98 {
99 switch (state & 3)
100 {
101 // Detect Score Position, Insert Score, Init Table
102 case STATE_GETPOS:
103 get_score_pos();
104
105 // New High Score
106 if (score_pos != -1)
107 {
108 osoundint.queue_sound(sound::PCM_WAVE);
109 osoundint.queue_sound(sound::MUSIC_LASTWAVE);
110 insert_score();
111 }
112 // Not a High Score
113 else
114 {
115 ostats.time_counter = 5;
116 }
117 set_display_pos();
118 acc_prev = -1;
119 state = STATE_DISPLAY;
120 video.enabled = true;
121 break;
122
123 // Display Basic High Score Table
124 case STATE_DISPLAY:
125 display_scores();
126 if (best_or_state >= 2)
127 state = STATE_ENTRY; // Only allow name entry when minicars have animation finished
128 break;
129
130 // Init Name Entry
131 case STATE_ENTRY:
132 check_name_entry();
133 break;
134
135 // Score Done
136 case STATE_DONE:
137 return;
138 }
139 }
140
141 // Calculate high score position.
142 // Source: D318
get_score_pos()143 void OHiScore::get_score_pos()
144 {
145 for (int i = 0; i < NO_SCORES; i++)
146 {
147 if (ostats.score > scores[i].score)
148 {
149 score_pos = i;
150 set_display_pos();
151 return;
152 }
153 }
154
155 score_pos = -1; // Not a new high-score
156 }
157
158 // - Insert Score Entry
159 // - Move Other Entries Down In Memory
160 // - Calculate Completed Time
161 // - Setup Appropriate Minimap Tiles
162 //
163 // Source: 0xD2C0
insert_score()164 void OHiScore::insert_score()
165 {
166 // Move entries down in memory
167 for (int i = NO_SCORES - 1; i > score_pos; i--)
168 {
169 scores[i] = scores[i-1];
170 }
171
172 scores[score_pos].score = ostats.score;
173 scores[score_pos].initial1 = 0x20;
174 scores[score_pos].initial2 = 0x20;
175 scores[score_pos].initial3 = 0x20;
176
177 // Calculate total time if game completed. Store result in $20
178 if (ostats.game_completed)
179 {
180 const uint8_t entries = outrun.cannonball_mode == Outrun::MODE_ORIGINAL ? 5 : 15;
181
182 scores[score_pos].time = 0;
183
184 for (int i = 0; i < entries; i++)
185 scores[score_pos].time += ostats.stage_counters[i];
186 }
187 else
188 {
189 scores[score_pos].time = 0;
190 ostats.game_completed = false;
191 }
192
193 // Setup Appropriate Minimap Tiles
194 scores[score_pos].maptiles = roms.rom0.read32(ohud.setup_mini_map());
195 }
196
197 // Set Table Position To Display Score From. Store Result in $26
198 // Source: 0xD298
set_display_pos()199 void OHiScore::set_display_pos()
200 {
201 if (score_pos < 0)
202 {
203 score_display_pos = 13;
204 }
205 else
206 {
207 score_display_pos = score_pos - 3;
208
209 if (score_display_pos < 0)
210 score_display_pos = 0;
211 else if (score_display_pos > 13)
212 score_display_pos = 13;
213 }
214 //score_display_pos = 0; // HACK!
215 }
216
217 // Check whether to perform name entry.
218 // Print alphabet and other stuff if necessary.
219 // Source: 0xD252
check_name_entry()220 void OHiScore::check_name_entry()
221 {
222 // No High Score
223 if (score_pos == -1)
224 {
225 ohud.blit_text1(TEXT1_YOURSCORE);
226 ohud.draw_score(0x110BDA, ostats.score, 3); // Select font 3 and print score
227 state = STATE_DONE;
228 }
229 else
230 {
231 // Get text ram address of score to blit
232 uint32_t score_adr = get_score_adr();
233 // Blit Alphabet. Highlight selected letter red.
234 blit_alphabet();
235 // Flash current initial that is being entered
236 flash_entry(score_adr);
237 // Draw big red countdown timer
238 const uint16_t BIG_RED_FONT = 0x8080;
239 ohud.draw_timer2(ostats.time_counter, 0x1101EC, BIG_RED_FONT);
240 // Input from controls
241 do_input(score_adr);
242
243 // Save new score info
244 if (state == STATE_DONE)
245 config.save_scores(outrun.cannonball_mode == Outrun::MODE_ORIGINAL);
246 }
247 }
248
249 // Get Address in text ram at which to output score
250 // Source: 0xD542
get_score_adr()251 uint32_t OHiScore::get_score_adr()
252 {
253 if (score_pos < 3)
254 return 0x110452 + (score_pos << 8); // top 3 positions
255 if (score_pos >= 17)
256 return 0x110A52 + ((score_pos - 19) << 8); // last 3 positions
257
258 return 0x110752; // middle positions
259 }
260
261 // Blit Alphabet. Highlight selected letter red.
262 // Source: 0xD45A
blit_alphabet()263 void OHiScore::blit_alphabet()
264 {
265 // Print Text: "ABCDEFGHIJK..."
266 ohud.blit_text2(TEXT2_ALPHABET);
267
268 // Address in text ram for characters
269 uint32_t adr = 0x110BF0;
270
271 video.write_text16(&adr, 0x8D00); // Full Stop
272 video.write_text16(adr + 0x7E, 0x8D01);
273 video.write_text16(&adr, 0x8D04); // Arrow
274 video.write_text16(adr + 0x7E, 0x8D05);
275 video.write_text16(&adr, 0x8D02); // ED
276 video.write_text16(adr + 0x7E, 0x8D03);
277
278 // Colour selected tile red
279 const uint16_t RED = 0x80;
280 adr = 0x110BBC + (letter_selected << 1);
281 video.write_text8(adr, (video.read_text8(adr) & 1) | RED);
282 video.write_text8(adr + 0x80, (video.read_text8(adr + 0x80) & 1) | RED);
283 }
284
285 // Flash current initial that is being entered
286 //
287 // Takes address of score entry as input
288 //
289 // Source: 0xD42C
flash_entry(uint32_t adr)290 void OHiScore::flash_entry(uint32_t adr)
291 {
292 uint16_t tile = 0x20; // Default blank tile
293 flash++; // Increment flashing counter
294
295 if (flash & BIT_3)
296 {
297 tile = (roms.rom0.read8(letter_selected + TILES_ALPHABET) & 0xFF) | 0x8600;
298 }
299
300 video.write_text16(adr + (initial_selected << 1), tile);
301 }
302
303 // High Score Input
304 //
305 // Source: 0xD33A
do_input(uint32_t adr)306 void OHiScore::do_input(uint32_t adr)
307 {
308 // Read Steering Left / Right & Denote Letter To Be Highlighted
309
310 const static uint8_t ENTRIES = 28; // 28 Possible entries we can select from
311 const static uint8_t DELETE = ENTRIES - 1;
312
313 int16_t position = read_controls() + letter_selected;
314
315 if (position > ENTRIES)
316 letter_selected = position = 0;
317 else if (position < (initial_selected == 3 ? DELETE : 0))
318 letter_selected = position = ENTRIES;
319 else
320 letter_selected = position;
321
322 // Check accelerator for press and depress
323 if (!acc_curr || !(acc_prev ^ acc_curr)) return;
324
325 // End option selected
326 if (letter_selected == ENTRIES)
327 {
328 video.write_text16(adr + (initial_selected << 1), 0x20); // Write blank tile to ram
329 ostats.frame_counter = 0;
330 ostats.time_counter = 0;
331 state = STATE_DONE;
332 }
333 // Delete option selected
334 else if (letter_selected == DELETE)
335 {
336 // Delete if not at first position
337 if (initial_selected != 0)
338 {
339 if (initial_selected == 1)
340 scores[score_pos].initial2 = 0x20;
341 else if (initial_selected == 2)
342 scores[score_pos].initial3 = 0x20;
343
344 video.write_text16(adr + (initial_selected << 1), 0x20); // Write blank tile to ram
345
346 initial_selected--;
347 }
348 }
349 // Normal character selected
350 else
351 {
352 uint8_t tile = roms.rom0.read8(TILES_ALPHABET + letter_selected);
353
354 // Store initial to score structure
355 if (initial_selected == 0)
356 scores[score_pos].initial1 = tile;
357 else if (initial_selected == 1)
358 scores[score_pos].initial2 = tile;
359 else if (initial_selected == 2)
360 {
361 scores[score_pos].initial3 = tile;
362 letter_selected = ENTRIES;
363 }
364
365 video.write_text16(adr + (initial_selected << 1), tile | 0x8600); // Write initial tile to ram
366
367 // Final Initial
368 // Note we have optional functionality to delete the final entry here
369 if (++initial_selected >= (config.engine.hiscore_delete ? 4 : 3))
370 {
371 state = STATE_DONE;
372 ostats.frame_counter = ostats.frame_reset;
373 ostats.time_counter = 2;
374 // code to enable easter egg if YU. is inputted goes here.
375 }
376 }
377 }
378
379 // Read controls for high score input screen
380 //
381 // Output:
382 // 0 = No Movement
383 // -1 = Left
384 // 1 = Right
385 //
386 // Source: 0xD4DA
read_controls()387 int8_t OHiScore::read_controls()
388 {
389 // Determine when accelerator has been pressed then depressed
390 if (oinputs.input_acc < 0x30)
391 {
392 acc_prev = acc_curr;
393 acc_curr = 0;
394 }
395 else if (oinputs.input_acc < 0x60)
396 {
397 acc_curr = acc_prev;
398 }
399 else
400 {
401 acc_prev = acc_curr;
402 acc_curr = -1;
403 }
404
405 // Check Steering Wheel
406 int8_t movement = 1; // default to right
407 int16_t steering = (oinputs.input_steering & 0xFF) - 0x80;
408 if (steering < 0)
409 {
410 steering = -steering;
411 movement = -1; // left
412 }
413
414 // Set increment to potentially advance to next letter.
415 // This depends on how far the steering wheel is turned.
416 if (steering >= 0x30)
417 steer += 5;
418 else if (steering >= 0x10)
419 steer += 1;
420
421 if (steer >= 0x14)
422 steer = 0;
423 else
424 movement = 0; // no movement
425
426 return movement;
427 }
428
429 // Display Best Outrunners in attract mode and name entry screen
430 //
431 // Source: 0xCE84
display_scores()432 void OHiScore::display_scores()
433 {
434 switch (best_or_state)
435 {
436 // Init
437 case 0:
438 video.clear_text_ram();
439 setup_minicars();
440 blit_score_table();
441 best_or_state = 1; // Set State to TICK
442 break;
443
444 // Tick
445 case 1:
446 tick_minicars();
447 // Have all mini-cars reached their destination?
448 if (dest_total >= 7)
449 best_or_state = 2; // Set State to DONE
450 break;
451
452 // Return
453 case 2:
454 return;
455 }
456 }
457
458 // ------------------------------------------------------------------------------------------------
459 // Mini car Movement
460 // ------------------------------------------------------------------------------------------------
461
462 // Setup minicars before they move across screen
463 // Source: 0xCED2
setup_minicars()464 void OHiScore::setup_minicars()
465 {
466 for (int i = 0; i < NO_MINICARS; i++)
467 {
468 minicars[i].pos = 0x100;
469 minicars[i].dst_reached = 0;
470 minicars[i].speed = (outils::random() & 0x180) | 0xF0;
471 minicars[i].base_speed = (outils::random() & 0x7) | 0x01;
472 }
473 }
474
475 // Move minicars across screen on text ram layer
476 // Source: 0xCF0E
tick_minicars()477 void OHiScore::tick_minicars()
478 {
479 // Destination in text ram
480 uint32_t dst = 0x11047C;
481
482 // Source tile data
483 uint32_t tiles_adr = TILES_MINICARS1;
484
485 // There are seven lines / entries to blit
486 for (int i = 0; i < NO_MINICARS; i++)
487 {
488 minicar_entry* minicar = &minicars[i];
489
490 // Minicar is on-screen
491 if (!minicar->dst_reached & BIT_0)
492 {
493 // Minicar has reached destination position (off-screen)
494 if ((minicar->pos >> 8) >= 0x5A)
495 {
496 minicar->dst_reached |= BIT_0;
497 dest_total++; // Increment total minicars that have reached destination
498 }
499
500 minicar->speed += minicar->base_speed;
501
502 if (minicar->speed >= 0x200)
503 minicar->speed = 0x180;
504
505 minicar->pos += minicar->speed;
506
507 setup_minicars_pal(minicar);
508
509 // Masked off the lower bit
510 int16_t pos = (minicar->pos >> 8) & 0xFFFE;
511
512 // Get final address in text ram for minicar based on position
513 uint32_t textram_adr = dst - pos;
514
515 // Address for following smoke tiles
516 uint32_t tiles_smoke_adr = TILES_MINICARS2;
517
518 // The minicar is two tiles wide.
519 // Two versions of routine, one that only blits the car in two tiles
520 if ((minicar->pos >> 8) & BIT_0)
521 {
522 video.write_text32(&textram_adr, roms.rom0.read32(tiles_adr)); // blit car in 2 tiles
523 video.write_text32(&textram_adr, roms.rom0.read32(&tiles_smoke_adr)); // smoke trail tile 1
524 video.write_text16(&textram_adr, roms.rom0.read16(&tiles_smoke_adr)); // smoke trail tile 2
525 }
526 // Blit at an offset
527 // The second blits the mini-car at an offset halfway into the tile (and hence takes 3 tiles)
528 else
529 {
530 video.write_text32(&textram_adr, roms.rom0.read32(4 + tiles_adr)); // blit car in 3 tiles
531 video.write_text16(&textram_adr, roms.rom0.read16(8 + tiles_adr)); // blit car in 3 tiles
532 video.write_text32(&textram_adr, roms.rom0.read32(&tiles_smoke_adr)); // smoke trail tile 1
533 video.write_text16(&textram_adr, roms.rom0.read16(&tiles_smoke_adr)); // smoke trail tile 2
534 }
535
536 // Erase Minicar tiles (0xCFB2)
537 // Reveal info from tile ram by copying to text ram
538
539 // Bottom Line
540 uint16_t tile_bits = video.read_tile8(textram_adr - 0x2000 + 1) | minicar->tile_props;
541 video.write_text16(textram_adr, tile_bits);
542 // Top Line
543 tile_bits = video.read_tile8(textram_adr - 0x2000 - 0x7F) | minicar->tile_props;
544 video.write_text16(textram_adr - 0x80, tile_bits);
545 }
546
547 dst += 0x100; // Advance to next row in text ram
548 tiles_adr += 0x0A; // Advance to next block of minicar data
549 }
550 }
551
552 // Setup palette and priority data for the copied tiles behind the minicar.
553 // The palette & priority used for the text depends on the position.
554 // Source: 0xCFCC
setup_minicars_pal(minicar_entry * minicar)555 void OHiScore::setup_minicars_pal(minicar_entry* minicar)
556 {
557 uint8_t pos = minicar->pos >> 8;
558
559 // Lap Time Tile Properties
560 minicar->tile_props = 0x8400;
561 if (pos <= 0x20) return; // Was 0x1F in original: Changed to handle longer times
562
563 // Route Tile Properties
564 minicar->tile_props = 0x8B00;
565 if (pos <= 0x2D) return;
566
567 // Initial Tile Properties
568 minicar->tile_props = 0x8200;
569 if (pos <= 0x39) return;
570
571 // Score Tile Properties
572 minicar->tile_props = 0x8400;
573 if (pos <= 0x4A) return;
574
575 // 1.2.3. Tile Properties
576 minicar->tile_props = 0x8600;
577 }
578
579 // ------------------------------------------------------------------------------------------------
580 // Score Table Rendering
581 // ------------------------------------------------------------------------------------------------
582
583 // Source: 0xD00C
blit_score_table()584 void OHiScore::blit_score_table()
585 {
586 // Clear tile table ready for High Score Display
587 uint32_t tile_addr = 0x10E000; // Tile Table 15
588 for (int i = 0; i <= 0x3FF; i++)
589 video.write_tile32(&tile_addr, 0x200020);
590
591 ohud.blit_text2(TEXT2_BEST_OR); // Print "BEST OUTRUNNERS"
592 ohud.blit_text1(TEXT1_SCORE_ETC); // Print Score, Name, Route, Record
593 blit_digit(); // Blit 1. 2. 3. etc.
594 blit_scores(); // Blit list of scores
595 blit_initials(); // Blit initials attached to those scores
596 if (outrun.cannonball_mode != Outrun::MODE_CONT)
597 blit_route_map(); // Blit Mini Route Map
598 blit_lap_time();
599 }
600
601 // Blit 7x single digit at start of score table (1. 2. 3. 4. 5. 6. 7.)
602 // Source: 0xD03A
blit_digit()603 void OHiScore::blit_digit()
604 {
605 // Destination in tile ram for digit
606 uint32_t dst = 0x10E438;
607
608 // Starting display position
609 int16_t pos = score_display_pos + 1;
610
611 // Display numbers 1 to 7
612 for (int i = 0; i < 7; i++)
613 {
614 int32_t tile = (pos / 10) | ((pos % 10) << 16);
615
616 // Draw blank
617 if (!(tile & 0xFFFF))
618 {
619 tile = (tile & 0xFFFF0000) | 0x20;
620 outils::swap32(tile);
621 tile |= 0x30;
622 }
623 // Draw tile
624 else
625 {
626 outils::swap32(tile);
627 tile |= 0x300030;
628 }
629
630 video.write_tile32(dst, tile); // Output number digit
631 video.write_tile16(4 + dst, 0x5B); // Output full stop following digit
632
633 dst += 0x100; // Advance to next text row
634 pos++;
635 }
636 }
637
638 // Blit High Scores
639 //
640 // Source: 0xD078
blit_scores()641 void OHiScore::blit_scores()
642 {
643 // Destination in tile ram for digit
644 uint32_t dst = 0x10E43E;
645
646 // Starting display position
647 int16_t pos = score_display_pos;
648
649 // Display scores 1 to 7
650 for (int i = 0; i < 7; i++)
651 {
652 ohud.draw_score_tile(dst, scores[pos++].score, 0);
653 dst += 0x100; // Advance to next text row
654 }
655 }
656
657 // Blit Initials
658 //
659 // Source: 0xD0A4
blit_initials()660 void OHiScore::blit_initials()
661 {
662 // Destination in tile ram for digit
663 uint32_t dst = 0x10E452;
664
665 // Starting display position
666 int16_t pos = score_display_pos;
667
668 // Write 3 initials for entries 1 to 7
669 for (int i = 0; i < 7; i++)
670 {
671 video.write_tile8(dst + 1, scores[pos].initial1);
672 video.write_tile8(dst + 3, scores[pos].initial2);
673 video.write_tile8(dst + 5, scores[pos].initial3);
674 pos++;
675 dst += 0x100; // Advance to next text row
676 }
677 }
678
679 // Blit mini route map
680 //
681 // Source: 0xD0D8
blit_route_map()682 void OHiScore::blit_route_map()
683 {
684 // Destination in tile ram for digit
685 uint32_t dst = 0x10E45E;
686
687 // Starting display position
688 int16_t pos = score_display_pos;
689
690 // Write 7 map entries
691 for (int i = 0; i < 7; i++)
692 {
693 uint32_t tiles = scores[pos++].maptiles;
694
695 // eg e5 c8 c2 d1 (4 tile indexes of route map)
696 video.write_tile8(dst - 0x7F, (tiles >> 24) & 0xFF);
697 video.write_tile8(dst - 0x7D, (tiles >> 16) & 0xFF);
698 video.write_tile8(dst + 0x01, (tiles >> 8) & 0xFF);
699 video.write_tile8(dst + 0x03, tiles & 0xFF);
700
701 dst += 0x100; // Advance to next text row
702 }
703 }
704
705 // Blit laptime
706 //
707 // Source: 0xD112
blit_lap_time()708 void OHiScore::blit_lap_time()
709 {
710 // Destination in tile ram for digit
711 uint32_t dst = 0x10E46A;
712
713 // Starting display position
714 int16_t pos = score_display_pos;
715
716 // Write 7 lap entries
717 for (int i = 0; i < 7; i++)
718 {
719 uint16_t time = scores[pos++].time;
720
721 if (time)
722 {
723 convert_lap_time(time);
724
725 // Write laptime
726 if (laptime[0] != TILE_PROPS)
727 {
728 video.write_tile16(dst - 0x2, laptime[0]); // Minutes Digit 1
729 }
730
731 video.write_tile16(0x0 + dst, laptime[1]); // Minutes Digit 2
732 video.write_tile16(0x2 + dst, 0x5E); // '
733 video.write_tile16(0x4 + dst, laptime[2]); // Seconds Digit 1
734 video.write_tile16(0x6 + dst, laptime[3]); // Seconds Digit 2
735 video.write_tile16(0x8 + dst, 0x5F); // '
736 video.write_tile16(0xA + dst, laptime[4]); // Milliseconds Digit 1
737 video.write_tile16(0xC + dst, laptime[5]); // Milliseconds Digit 2
738 }
739
740 dst += 0x100; // Advance to next text row
741 }
742 }
743
744 // Convert laptime to tile data and store in laptime array.
745 // Enhanced routine to handle minutes > 9
746 //
747 // Source: 0x806C
convert_lap_time(uint16_t time)748 void OHiScore::convert_lap_time(uint16_t time)
749 {
750 const uint16_t MINUTE = 3600;
751
752 int32_t src_time = time; // laptime copy [d0]
753 int16_t minutes = -1; // Store number of minutes
754
755 // Calculate Minutes
756 do
757 {
758 src_time -= MINUTE;
759 minutes++;
760 }
761 while (src_time >= 0);
762
763 src_time += MINUTE;
764 minutes = outils::convert16_dechex(minutes);
765
766 // Store Millisecond Lookup
767 uint16_t ms_lookup = src_time & 0x3F;
768
769 // Calculate Seconds
770 uint16_t seconds = src_time >> 6; // Store Seconds
771
772 uint16_t s1 = seconds & 0xF; // First digit [d1]
773 uint16_t s2 = seconds >> 4; // Second digit [d2]
774
775 if (s1 > 9)
776 seconds += 6;
777
778 s2 = outils::bcd_add(s2, s2);
779 int16_t d3 = s2;
780 s2 = outils::bcd_add(s2, s2);
781 s2 = outils::bcd_add(s2, d3);
782 seconds = outils::bcd_add(s2, seconds);
783
784 // Output Milliseconds
785 laptime[5] = (ostats.lap_ms[ms_lookup] & 0xF) | TILE_PROPS;
786 laptime[4] = ((ostats.lap_ms[ms_lookup] & 0xF0) >> 4) | TILE_PROPS;
787
788 // Output Seconds
789 laptime[3] = (seconds & 0xF) | TILE_PROPS;
790 laptime[2] = ((seconds & 0xF0) >> 4) | TILE_PROPS;
791
792 // Output Minutes
793 laptime[1] = (minutes & 0xF) | TILE_PROPS;
794 laptime[0] = ((minutes & 0xF0) >> 4) | TILE_PROPS;
795 }