1 /***************************************************************************
2     Heads-Up Display (HUD) Code
3 
4     - Score Rendering
5     - Timer Rendering
6     - Rev Rendering
7     - Minimap Rendering
8     - Text Rendering
9 
10     Copyright Chris White.
11     See license.txt for more details.
12 ***************************************************************************/
13 
14 #include <cstring>
15 
16 #include "../utils.hpp"
17 #include "engine/oferrari.hpp"
18 #include "engine/outils.hpp"
19 #include "engine/ohud.hpp"
20 #include "engine/ooutputs.hpp"
21 #include "engine/ostats.hpp"
22 
23 OHud ohud;
24 
OHud(void)25 OHud::OHud(void)
26 {
27 }
28 
29 
~OHud(void)30 OHud::~OHud(void)
31 {
32 }
33 
34 // Draw Text Labels For HUD
35 //
36 // Source: 0xB462
draw_main_hud()37 void OHud::draw_main_hud()
38 {
39     blit_text1(HUD_LAP1);
40     blit_text1(HUD_LAP2);
41 
42     if (outrun.cannonball_mode == Outrun::MODE_ORIGINAL)
43     {
44         blit_text1(HUD_TIME1);
45         blit_text1(HUD_TIME2);
46         blit_text1(HUD_SCORE1);
47         blit_text1(HUD_SCORE2);
48         blit_text1(HUD_STAGE1);
49         blit_text1(HUD_STAGE2);
50         blit_text1(HUD_ONE);
51         do_mini_map();
52     }
53     else if (outrun.cannonball_mode == Outrun::MODE_TTRIAL)
54     {
55         draw_score(translate(3, 2), 0, 2);
56         blit_text1(2, 1, HUD_SCORE1);
57         blit_text1(2, 2, HUD_SCORE2);
58         blit_text_big(4, "TIME TO BEAT");
59         draw_lap_timer(translate(16, 7), outrun.ttrial.best_lap, outrun.ttrial.best_lap[2]);
60     }
61     else if (outrun.cannonball_mode == Outrun::MODE_CONT)
62     {
63         blit_text1(HUD_TIME1);
64         blit_text1(HUD_TIME2);
65         blit_text1(HUD_SCORE1);
66         blit_text1(HUD_SCORE2);
67         blit_text1(HUD_STAGE1);
68         blit_text1(HUD_STAGE2);
69         blit_text1(HUD_ONE);
70     }
71 }
72 
clear_timetrial_text()73 void OHud::clear_timetrial_text()
74 {
75     blit_text_big(4,     "            ");
76     blit_text_new(16, 7, "            ");
77 }
78 
draw_fps_counter(int16_t fps)79 void OHud::draw_fps_counter(int16_t fps)
80 {
81     std::string str = "FPS " + Utils::to_string(fps);
82     blit_text_new(30, 0, str.c_str());
83 }
84 
85 
86 // Routine to setup and draw mini-map (bottom RHS of HUD)
87 //
88 // Source: 0x8B52
do_mini_map()89 void OHud::do_mini_map()
90 {
91     if (outrun.game_state == GS_ATTRACT)
92         return;
93 
94     uint32_t tile_addr = setup_mini_map();
95     draw_mini_map(tile_addr);
96 }
97 
98 // Setup Appropriate Tile Address For Minimap.
99 //
100 // Returns start address of block of 4 tiles. Represents square of route on mini-map screen.
101 //
102 // Source: 0x8B68
setup_mini_map()103 uint32_t OHud::setup_mini_map()
104 {
105     if (ostats.route_info > 0x4F)
106         ostats.route_info = 0x4F;
107 
108     // Map Route to appropriate tile
109     const uint8_t ROUTE_MAPPING[] =
110     {
111         0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
112         0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
113         0x03, 0x03, 0x03, 0x03, 0x04, 0x04, 0x04, 0x04, 0x05, 0x05, 0x05, 0x05, 0x06, 0x06, 0x06, 0x06,
114         0x07, 0x07, 0x08, 0x08, 0x09, 0x09, 0x0A, 0x0A, 0x0B, 0x0B, 0x0C, 0x0C, 0x0D, 0x0D, 0x0E, 0x0E,
115         0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E,
116     };
117 
118     return TILES_MINIMAP + (ROUTE_MAPPING[ostats.route_info] << 2);
119 }
120 
draw_mini_map(uint32_t tile_addr)121 void OHud::draw_mini_map(uint32_t tile_addr)
122 {
123     // Address in text ram to copy this to
124     uint32_t dst = 0x110CFA;
125 
126     // Base Tile to use
127     const uint16_t BASE = 0x8B00;
128 
129     uint16_t tile = (BASE | roms.rom0.read8(&tile_addr));
130     video.write_text16(dst, tile);
131 
132     tile = BASE | roms.rom0.read8(&tile_addr);
133     video.write_text16(2 + dst, tile);
134 
135     tile = BASE | roms.rom0.read8(&tile_addr);
136     video.write_text16(0x80 + dst, tile);
137 
138     tile = BASE | roms.rom0.read8(&tile_addr);
139     video.write_text16(0x82 + dst, tile);
140 }
141 
142 // Print Timer To Top Left Hand Corner Of Screen
143 //
144 // Source: 0x8216
draw_timer1(uint16_t time)145 void OHud::draw_timer1(uint16_t time)
146 {
147     if (outrun.game_state < GS_START1 || outrun.game_state > GS_INGAME)
148         return;
149 
150     if (!outrun.freeze_timer)
151     {
152         const uint16_t BASE_TILE = 0x8C80;
153         draw_timer2(time > 0x99 ? 0x99 : time, 0x1100BE, BASE_TILE);
154 
155         // Blank out the OFF text area
156         video.write_text16(0x110C2, 0);
157         video.write_text16(0x110C2 + 0x80, 0);
158     }
159     else
160     {
161         uint32_t dst_addr = translate(7, 1);
162         const uint16_t PAL = 0x8AA0;
163         const uint16_t O = (('O' - 0x41) * 2) + PAL; // Convert character to real index (D0-0x41) so A is 0x01
164         const uint16_t F = (('F' - 0x41) * 2) + PAL;
165 
166         video.write_text16(&dst_addr,       O);     // Write first row to text ram
167         video.write_text16(0x7E + dst_addr, O + 1); // Write second row to text ram
168         video.write_text16(&dst_addr,       F);     // Write first row to text ram
169         video.write_text16(0x7E + dst_addr, F + 1); // Write second row to text ram
170         video.write_text16(&dst_addr,       F);     // Write first row to text ram
171         video.write_text16(0x7E + dst_addr, F + 1); // Write second row to text ram
172     }
173 }
174 
175 // Called directly by High Score Table
176 //
177 // Source: 0x8234
draw_timer2(uint16_t time_counter,uint32_t addr,uint16_t base_tile)178 void OHud::draw_timer2(uint16_t time_counter, uint32_t addr, uint16_t base_tile)
179 {
180     uint16_t digit1 = time_counter & 0xF;
181     uint16_t digit2 = (time_counter & 0xF0) >> 4;
182 
183     // Low Digit
184     uint16_t value = (digit1 << 1) + base_tile;
185     video.write_text16(0x02 + addr, value);
186     video.write_text16(0x82 + addr, value+1);
187 
188     // High Digit
189     value = (digit2 << 1);
190 
191     if (value)
192     {
193         value += base_tile;
194         video.write_text16(0x00 + addr, value);
195         video.write_text16(0x80 + addr, value+1);
196     }
197     else
198     {
199         video.write_text16(0x00 + addr, 0);
200         video.write_text16(0x80 + addr, 0);
201     }
202 }
203 
draw_lap_timer(uint32_t addr,uint8_t * digits,uint8_t ms_value)204 void OHud::draw_lap_timer(uint32_t addr, uint8_t* digits, uint8_t ms_value)
205 {
206     const uint16_t BASE = 0x8230;
207     const uint16_t APOSTROPHE1 = 0x835E;
208     const uint16_t APOSTROPHE2 = 0x835F;
209 
210     // Write Minute Digit
211     video.write_text16(&addr, BASE | digits[0]);
212     video.write_text16(&addr, APOSTROPHE1);
213 
214     // Write Seconds
215     video.write_text16(&addr, BASE | (digits[1] & 0xF0) >> 4);
216     video.write_text16(&addr, BASE | (digits[1] & 0xF));
217     video.write_text16(&addr, APOSTROPHE2);
218 
219     // Write Milliseconds
220     video.write_text16(&addr, BASE | (ms_value & 0xF0) >> 4);
221     video.write_text16(&addr, BASE | (ms_value & 0xF));
222 }
223 
224 // Draw Score (In-Game HUD)
225 //
226 // Source: 0x7382
draw_score_ingame(uint32_t score)227 void OHud::draw_score_ingame(uint32_t score)
228 {
229     if (outrun.game_state < GS_START1 || outrun.game_state > GS_BONUS)
230         return;
231 
232     draw_score(0x110150, score, 2);
233 }
234 
235 // Draw Score
236 //
237 // + 8530 = Digit 0
238 // + 8531 = Digit 1
239 //
240 // Source: 0x7146
draw_score(uint32_t addr,const uint32_t score,uint8_t font)241 void OHud::draw_score(uint32_t addr, const uint32_t score, uint8_t font)
242 {
243     // Base address of Digit 0 setup here
244     const uint16_t BASE = 0x30 | (font << 9) | 0x8100;
245 
246     // Blank tile for comparison purposes
247     const uint16_t BLANK = 0x8020;
248 
249     uint8_t* digits = new uint8_t[8];
250 
251     // Topmost digit
252     digits[0] = ((score >> 16) & 0xF000) >> 12;
253     digits[1] = ((score >> 16) & 0xF00) >> 8;
254     digits[2] = ((score >> 16) & 0xF0) >> 4;
255     digits[3] = ((score >> 16) & 0xF);
256     digits[4] = (score & 0xF000) >> 12;
257     digits[5] = (score & 0xF00) >> 8;
258     digits[6] = (score & 0xF0) >> 4;
259     digits[7] = (score & 0xF);
260 
261     bool found = false;
262 
263     // Draw blank digits until we find first digit
264     // Then use zero for blank digits
265     for (uint8_t i = 0; i < 7; i++)
266     {
267         if (!found && !digits[i])
268             video.write_text16(&addr, BLANK);
269         else
270         {
271             video.write_text16(&addr, digits[i] + BASE);
272             found = true;
273         }
274     }
275 
276     video.write_text16(&addr, digits[7] + BASE); // Always draw last digit
277     delete[] digits;
278 }
279 
280 // Same as above function but writes to tile ram instead.
281 // Not an ideal solution, really a workaround because text and tile ram are not one big lump in my implementation
draw_score_tile(uint32_t addr,const uint32_t score,uint8_t font)282 void OHud::draw_score_tile(uint32_t addr, const uint32_t score, uint8_t font)
283 {
284     // Base address of Digit 0 setup here
285     const uint16_t BASE = 0x30 | (font << 9) | 0x8100;
286 
287     // Blank tile for comparison purposes
288     const uint16_t BLANK = 0x8020;
289 
290     uint8_t* digits = new uint8_t[8];
291 
292     // Topmost digit
293     digits[0] = ((score >> 16) & 0xF000) >> 12;
294     digits[1] = ((score >> 16) & 0xF00) >> 8;
295     digits[2] = ((score >> 16) & 0xF0) >> 4;
296     digits[3] = ((score >> 16) & 0xF);
297     digits[4] = (score & 0xF000) >> 12;
298     digits[5] = (score & 0xF00) >> 8;
299     digits[6] = (score & 0xF0) >> 4;
300     digits[7] = (score & 0xF);
301 
302     bool found = false;
303 
304     // Draw blank digits until we find first digit
305     // Then use zero for blank digits
306     for (uint8_t i = 0; i < 7; i++)
307     {
308         if (!found && !digits[i])
309             video.write_tile16(&addr, BLANK);
310         else
311         {
312             video.write_tile16(&addr, digits[i] + BASE);
313             found = true;
314         }
315     }
316 
317     video.write_tile16(&addr, digits[7] + BASE); // Always draw last digit
318     delete[] digits;
319 }
320 
321 // Modified Version Of Draw Digits
322 //
323 // Source: C3A0
draw_stage_number(uint32_t addr,uint8_t digit,uint16_t col)324 void OHud::draw_stage_number(uint32_t addr, uint8_t digit, uint16_t col)
325 {
326     if (digit < 10)
327     {
328         video.write_text16(addr, digit + (col << 8) + DIGIT_BASE);
329     }
330     else
331     {
332         int hex = outils::convert16_dechex(digit);
333 
334         video.write_text16(addr + 2, (hex & 0xF) + (col << 8) + DIGIT_BASE);
335         video.write_text16(addr    , (hex >> 4)  + (col << 8) + DIGIT_BASE);
336     }
337 }
338 
339 // Draw Rev Counter
340 //
341 // Source: 0x6B08
draw_rev_counter()342 void OHud::draw_rev_counter()
343 {
344     // Return in attract mode and don't draw rev counter
345     if (outrun.game_state <= GS_INIT_GAME) return;
346     uint16_t revs = oferrari.rev_stop_flag ? oferrari.revs_post_stop : oferrari.revs >> 16;
347 
348     // Boost revs during countdown phase, so the bar goes further into the red
349     if (oinitengine.car_increment >> 16 == 0)
350         revs += (revs >> 2);
351 
352     revs >>= 4;
353 
354     uint32_t addr = 0x110DB4; // Address of rev counter
355 
356     const uint16_t REV_OFF = 0x8120; // Rev counter: Off (Blank Tile)
357     const uint16_t REV_ON1 = 0x81FE; // Rev counter: On (Single Digit)
358     const uint16_t REV_ON2 = 0x81FD; // Rev counter: On (Double Digit)
359     const uint16_t GREEN = 0x200;
360     const uint16_t WHITE = 0x400;
361     const uint16_t RED   = 0x600;
362 
363     for (int8_t i = 0; i <= 0x13; i++)
364     {
365         uint16_t tile = 0;
366 
367         if (revs > i)
368         {
369             tile = REV_ON2;
370             if (i >= 0xE) tile |= RED;
371             else if (i <= 9) tile |= WHITE;
372             else tile |= GREEN;
373         }
374         else if (revs != i)
375         {
376             tile = REV_OFF | WHITE;
377         }
378         else
379         {
380             tile = REV_ON1;
381             if (i >= 0xE) tile |= RED;
382             else if (i <= 9) tile |= WHITE;
383             else tile |= GREEN;
384         }
385 
386         video.write_text16(addr, tile);
387 
388         // On odd indexes, we don't increment to next word - to effectively shorten the length of the rev counter
389         // It would be twice as long otherwise
390         if (i & 1)
391             addr += 2;
392     }
393     oferrari.rev_pitch2 = oferrari.rev_pitch1;
394 }
395 
396 // Convert & Blit car speed to screen
397 //
398 // Source: 0xBB72
blit_speed(uint32_t dst_addr,uint16_t speed)399 void OHud::blit_speed(uint32_t dst_addr, uint16_t speed)
400 {
401     const uint16_t TILE_BASE = 0x8C60; // Base tile number
402 
403     // Convert to human readable speed
404     speed = outils::convert16_dechex(speed);
405 
406     uint16_t digit1 = speed & 0xF;
407     uint16_t digit2 = (speed & 0xF0) >> 4;
408     uint16_t digit3 = (speed & 0xF00) >> 8;
409 
410     digit3 <<= 1;
411     if (digit3 == 0)
412     {
413         digit2 <<= 1;
414         if (digit2 != 0)
415             digit2 += TILE_BASE;
416     }
417     else
418     {
419         digit3 += TILE_BASE;
420         digit2 <<= 1;
421         digit2 += TILE_BASE;
422     }
423     // Blit Top Line Of Tiles
424     digit1 <<= 1;
425     digit1 += TILE_BASE;
426 
427     video.write_text16(&dst_addr, digit3);
428     video.write_text16(&dst_addr, digit2);
429     video.write_text16(dst_addr, digit1);
430     dst_addr += 0x7C; // Set to next horizontal line of number tiles
431 
432     // Blit Bottom Line Of Tiles
433     if (digit3 != 0) digit3++;
434     if (digit2 != 0) digit2++;
435     digit1++;
436     video.write_text16(&dst_addr, digit3);
437     video.write_text16(&dst_addr, digit2);
438     video.write_text16(dst_addr, digit1);
439 }
440 
441 // Blit large digit spanning two rows.
442 //
443 // Source: 0x9BF2
blit_large_digit(uint32_t * addr,uint8_t digit)444 void OHud::blit_large_digit(uint32_t* addr, uint8_t digit)
445 {
446     video.write_text16(*addr,        (digit + 0x80) | 0x8C00);
447     video.write_text16(*addr + 0x80, (digit + 0x81) | 0x8C00);
448 
449     *addr += 2;
450 }
451 
452 // Draw Copyright Text to text ram
453 //
454 // Source Address: 0xB844
455 // Input:          None
456 // Output:         None
457 
draw_copyright_text()458 void OHud::draw_copyright_text()
459 {
460     blit_text1(TEXT1_1986_SEGA);
461     blit_text1(TEXT1_COPYRIGHT);
462 }
463 
464 // Draw Insert Coin text
465 //
466 // Source: 0xB7D0
draw_insert_coin()467 void OHud::draw_insert_coin()
468 {
469     // Update text
470     if ((outrun.tick_counter ^ (outrun.tick_counter - 1)) & BIT_4)
471     {
472         // Flash Press Start
473         if (ostats.credits)
474         {
475             if (outrun.tick_counter & BIT_4)
476             {
477                 blit_text1(TEXT1_PRESS_START);
478                 outrun.outputs->set_digital(OOutputs::D_START_LAMP);
479             }
480             else
481             {
482                 blit_text1(TEXT1_CLEAR_START);
483                 outrun.outputs->clear_digital(OOutputs::D_START_LAMP);
484             }
485         }
486         // Flash Insert Coins / Freeplay Press Start
487         else
488         {
489             if (config.engine.freeplay)
490             {
491                 uint32_t dst_addr = 0x110ACC;
492                 const static uint8_t PRESS_START[] = {0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x28};
493 
494                 if (outrun.tick_counter & BIT_4)
495                 {
496                     // Blit each tile
497                     for (uint16_t i = 0; i < sizeof(PRESS_START); i++)
498                         video.write_text16(&dst_addr, (0x8700 | PRESS_START[i]));
499                 }
500                 else
501                 {
502                     for (uint16_t i = 0; i < sizeof(PRESS_START); i++)
503                         video.write_text16(&dst_addr, (0x8700 | 0x20));
504                 }
505             }
506             else
507             {
508                 blit_text1((outrun.tick_counter & BIT_4) ? TEXT1_INSERT_COINS : TEXT1_CLEAR_COINS);
509             }
510         }
511     }
512 }
513 
514 // Source: 0x6CDE
draw_credits()515 void OHud::draw_credits()
516 {
517     if (config.engine.freeplay)
518     {
519         blit_text1(TEXT1_FREEPLAY);
520     }
521     else
522     {
523         video.write_text16(0x110D44, ostats.credits | 0x8630); // blit digit
524         blit_text1(ostats.credits >= 2 ? TEXT1_CREDITS : TEXT1_CREDIT);
525     }
526 }
527 
528 // Blit Tiles to text ram layer (Single Row)
529 //
530 // Source Address: 0xB844
531 // Input:          Source address in rom of data format
532 // Output:         None
533 //
534 // Format of the input data is as follows:
535 //
536 // Long 1: Destination address to move data to. [e.g. 0x00110D34 which would go to the text ram]
537 // Word 1: Number of tiles to draw / counter
538 // Byte 1: High byte to apply to every copy, containing priority info [86]
539 // Byte 2: Not used
540 //
541 // Byte 3: Second byte to copy containing tile number (low)
542 // Byte 4: Third byte to copy containing tile number (low)
543 // Byte 5: etc.
544 //
545 // Text layer name table format:
546 //
547 // MSB          LSB
548 // p???cccnnnnnnnnn
549 //
550 // p : Priority. If 0, sprites with priority level 3 are shown over the text.
551 //               If 1, the text layer is shown over sprites regardless of priority.
552 // c : Color palette
553 // n : Tile index to use
554 // ? : Unknown
555 
blit_text1(uint32_t src_addr)556 void OHud::blit_text1(uint32_t src_addr)
557 {
558     uint32_t dst_addr = roms.rom0.read32(&src_addr); // Text RAM destination address
559     uint16_t counter = roms.rom0.read16(&src_addr);  // Number of tiles to blit
560     uint16_t data = roms.rom0.read16(&src_addr);     // Tile data to blit
561 
562     // Blit each tile
563     for (uint16_t i = 0; i <= counter; i++)
564     {
565         data = (data & 0xFF00) | roms.rom0.read8(&src_addr);
566         video.write_text16(&dst_addr, data);
567     }
568 }
569 
blit_text1(uint8_t x,uint8_t y,uint32_t src_addr)570 void OHud::blit_text1(uint8_t x, uint8_t y, uint32_t src_addr)
571 {
572     uint32_t dst_addr = translate(x, y);
573     src_addr += 4;
574     uint16_t counter = roms.rom0.read16(&src_addr);  // Number of tiles to blit
575     uint16_t data = roms.rom0.read16(&src_addr);     // Tile data to blit
576 
577     // Blit each tile
578     for (uint16_t i = 0; i <= counter; i++)
579     {
580         data = (data & 0xFF00) | roms.rom0.read8(&src_addr);
581         video.write_text16(&dst_addr, data);
582     }
583 }
584 
585 // Blit Tiles to text ram layer (Double Row)
586 //
587 // Source Address: 0xB844
588 // Input:          Source address in rom of data format
589 // Output:         None
590 //
591 // Format of the input data is as follows:
592 //
593 // Byte 0: Offset into text RAM [First 2 bytes]
594 // Byte 1: Offset into text RAM [First 2 bytes]
595 // Byte 2: Palette to use
596 // Byte 3: Number of characters to display (used as a counter)
597 // Byte 4: Start of text to display
598 
599 
blit_text2(uint32_t src_addr)600 void OHud::blit_text2(uint32_t src_addr)
601 {
602     uint32_t dst_addr = 0x110000 + roms.rom0.read16(&src_addr); // Text RAM destination address
603 
604     uint16_t pal = roms.rom0.read8(&src_addr);
605     pal = 0x80A0 | ((pal << 9) | (pal >> 7) & 1);
606     // same as ror 7 and extending to word
607     uint16_t counter = roms.rom0.read8(&src_addr); // Number of tiles to blit
608 
609     // Blit each tile
610     for (uint16_t i = 0; i <= counter; i++)
611     {
612         uint16_t data = roms.rom0.read8(&src_addr); // Tile data to blit
613 
614         // Blank space
615         if (data == 0x20)
616         {
617             data = 0;
618             video.write_text16(&dst_addr, data); // Write blank space to text ram
619         }
620         // Normal character
621         else
622         {
623             // Convert character to real index (D0-0x41) so A is 0x01
624             data -= 0x41;
625             data = (data * 2) + pal;
626             video.write_text16(&dst_addr, data); // Write first row to text ram
627             data++;
628         }
629         video.write_text16(0x7E + dst_addr, data); // Write second row to text ram
630     }
631 }
632 
633 // ------------------------------------------------------------------------------------------------
634 // Enhanced Cannonball Routines Below
635 // ------------------------------------------------------------------------------------------------
636 
draw_debug_info(uint32_t pos,uint16_t height_pat,uint8_t sprite_pat)637 void OHud::draw_debug_info(uint32_t pos, uint16_t height_pat, uint8_t sprite_pat)
638 {
639     ohud.blit_text_new(0,  4, "LEVEL POS", OHud::GREEN);
640     ohud.blit_text_new(16, 4, "    ");
641     ohud.blit_text_new(16, 4, Utils::to_string((int)(pos >> 16)).c_str(), OHud::PINK);
642     ohud.blit_text_new(0,  5, "HEIGHT PATTERN", OHud::GREEN);
643     ohud.blit_text_new(16, 5, "    ");
644     ohud.blit_text_new(16, 5, Utils::to_string((int)height_pat).c_str(), OHud::PINK);
645     ohud.blit_text_new(0,  6, "SPRITE PATTERN", OHud::GREEN);
646     ohud.blit_text_new(16, 6, "    ");
647     ohud.blit_text_new(16, 6, Utils::to_string((int)sprite_pat).c_str(), OHud::PINK);
648 }
649 
650 // Big Yellow Text. Always Centered.
blit_text_big(const uint8_t Y,const char * text,bool do_notes)651 void OHud::blit_text_big(const uint8_t Y, const char* text, bool do_notes)
652 {
653     uint16_t length = (uint16_t) strlen(text);
654 
655     const uint16_t X = 20 - (length >> 1);
656 
657     // Clear complete row in text ram before blitting
658     for (uint8_t x = 0; x < 40; x++)
659     {
660         video.write_text16(translate(x, Y) + 0x110000, 0); // Write blank space to text ram
661         video.write_text16(translate(x, Y) + 0x11007E, 0); // Write blank space to text ram
662     }
663 
664     // Draw Notes
665     if (do_notes)
666     {
667         // Note tiles to append to left side of text
668         const uint32_t NOTE_TILES1 = 0x8A7A8A7B;
669         const uint32_t NOTE_TILES2 = 0x8A7C8A7D;
670 
671         video.write_text32(translate(X - 2, Y) + 0x110000, NOTE_TILES1);
672         video.write_text32(translate(X - 2, Y) + 0x110080, NOTE_TILES2);
673     }
674 
675     uint32_t dst_addr = translate(X, Y) + 0x110000;
676 
677     // Blit each tile
678     for (uint16_t i = 0; i < length; i++)
679     {
680         char c = *text++;
681         // Convert lowercase characters to uppercase
682         if (c >= 'a' && c <= 'z')
683             c -= 0x20;
684         // Numerals: Use different palette for numbers so they display more nicely
685         else if (c >= '0' && c <= '9')
686         {
687             const uint16_t pal = 0x8CA0;
688             c -= 0x40;
689             c = (c * 2);
690             video.write_text16(&dst_addr,       c + pal);     // Write first row to text ram
691             video.write_text16(0x7E + dst_addr, c + pal + 1); // Write second row to text ram
692         }
693         // Blank space
694         else if (c == ' ')
695         {
696             c = 0;
697             video.write_text16(&dst_addr, c); // Write blank space to text ram
698             video.write_text16(0x7E + dst_addr, c); // Write blank space to text ram
699         }
700         // Normal character
701         if (c >= 'A' && c <= 'Z')
702         {
703             const uint16_t pal = do_notes ? 0x8AA0 : 0x8CA0;
704             // Convert character to real index (D0-0x41) so A is 0x01
705             c -= 0x41;
706             c = (c * 2);
707             video.write_text16(&dst_addr,       c + pal);     // Write first row to text ram
708             video.write_text16(0x7E + dst_addr, c + pal + 1); // Write second row to text ram
709         }
710     }
711 }
712 
713 // Custom Routine To Blit Text Easily
714 //
715 // The name table is 64x28, but only 40x28 is shown. The viewable portion of
716 // the name table starts at column 24 and goes to column 63, which maps to
717 // screen columns 0 through 39.
718 //
719 // Normal font: 41 onwards
blit_text_new(uint16_t x,uint16_t y,const char * text,uint16_t pal)720 void OHud::blit_text_new(uint16_t x, uint16_t y, const char* text, uint16_t pal)
721 {
722     uint32_t dst_addr = translate(x, y);
723     uint16_t length = (uint16_t) strlen(text);
724 
725     for (uint16_t i = 0; i < length; i++)
726     {
727         char c = *text++;
728 
729         // Convert lowercase characters to uppercase
730         if (c >= 'a' && c <= 'z')
731             c -= 0x20;
732         else if (c == '�')
733             c = 0x10;
734         else if (c == '-')
735             c = 0x2d;
736         else if (c == '.')
737             c = 0x5b;
738 
739         video.write_text16(&dst_addr, (pal << 8) | c);
740     }
741 }
742 
743 // Translate x, y column position to tilemap address
744 // Base Position defaults to 0,0
translate(uint16_t x,uint16_t y,const uint32_t BASE_POS)745 uint32_t OHud::translate(uint16_t x, uint16_t y, const uint32_t BASE_POS)
746 {
747     if (x > 63) x = 63;
748     if (y > 27) y = 27;
749 
750     // Calculate destination address based on x, y position
751     return BASE_POS + ((x + (y * 64)) << 1);
752 }