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 }