GameBoy (Color) port of the GTA San Andreas arcade game Duality
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

text.c 8.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  1. /*
  2. * text.c
  3. * Duality
  4. *
  5. * Copyright (C) 2025 Thomas Buck <thomas@xythobuz.de>
  6. *
  7. * This program is free software: you can redistribute it and/or modify
  8. * it under the terms of the GNU General Public License as published by
  9. * the Free Software Foundation, either version 3 of the License, or
  10. * (at your option) any later version.
  11. *
  12. * This program is distributed in the hope that it will be useful,
  13. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. * GNU General Public License for more details.
  16. *
  17. * See <http://www.gnu.org/licenses/>.
  18. */
  19. #include <gbdk/platform.h>
  20. #include <string.h>
  21. #include <stdio.h>
  22. #include "banks.h"
  23. #include "maps.h"
  24. #include "map_data.h"
  25. #include "window.h"
  26. #include "text.h"
  27. // TODO inverted score color not visible on DMG
  28. // TODO selected menu entry not visible on DMG
  29. #define TEXT_PALETTE_WHITE BKGF_CGB_PAL3
  30. #define TEXT_PALETTE_BLACK BKGF_CGB_PAL4
  31. #define TEXT_ATTR_WHITE (BKGF_PRI | TEXT_PALETTE_WHITE)
  32. #define TEXT_ATTR_BLACK (BKGF_PRI | TEXT_PALETTE_BLACK)
  33. #define ASCI_ATTR_DARK (BKGF_PRI | BKGF_BANK1 | BKGF_CGB_PAL6)
  34. #define ASCI_ATTR_LIGHT (BKGF_PRI | BKGF_BANK1 | BKGF_CGB_PAL5)
  35. BANKREF(text)
  36. static void digit(uint8_t val, uint8_t pos, uint8_t x_off, uint8_t y_off, uint8_t is_black);
  37. static void set_win_based(uint8_t x, uint8_t y, uint8_t w, uint8_t h,
  38. const uint8_t *tiles, uint8_t base_tile, uint8_t tile_bank,
  39. const uint8_t *attributes, uint8_t attr_bank) NONBANKED {
  40. if (_cpu == CGB_TYPE) {
  41. if (attributes != NULL) {
  42. START_ROM_BANK(attr_bank) {
  43. VBK_REG = VBK_ATTRIBUTES;
  44. set_win_tiles(x, y, w, h, attributes);
  45. } END_ROM_BANK
  46. } else {
  47. VBK_REG = VBK_ATTRIBUTES;
  48. fill_win_rect(x, y, w, h, 0x00);
  49. }
  50. }
  51. START_ROM_BANK(tile_bank) {
  52. VBK_REG = VBK_TILES;
  53. set_win_based_tiles(x, y, w, h, tiles, base_tile);
  54. } END_ROM_BANK
  55. }
  56. static void set_win_based_attr(uint8_t x, uint8_t y, uint8_t w, uint8_t h,
  57. const uint8_t *tiles, uint8_t base_tile, uint8_t tile_bank,
  58. const uint8_t attr) NONBANKED {
  59. if (_cpu == CGB_TYPE) {
  60. VBK_REG = VBK_ATTRIBUTES;
  61. fill_win_rect(x, y, w, h, attr);
  62. }
  63. START_ROM_BANK(tile_bank) {
  64. VBK_REG = VBK_TILES;
  65. set_win_based_tiles(x, y, w, h, tiles, base_tile);
  66. } END_ROM_BANK
  67. }
  68. // ----------------------------------------------------------------------------
  69. // Characters 16x16 (for menus)
  70. // ----------------------------------------------------------------------------
  71. static void character(uint8_t c, uint8_t pos, uint8_t x_off, uint8_t y_off, uint8_t is_black) {
  72. uint8_t off = c * maps[FNT_TEXT_16].width;
  73. set_win_based_attr(x_off + (pos * maps[FNT_TEXT_16].width), y_off,
  74. maps[FNT_TEXT_16].width, 1,
  75. maps[FNT_TEXT_16].map + off,
  76. maps[FNT_TEXT_16].tile_offset,
  77. maps[FNT_TEXT_16].bank, is_black ? TEXT_ATTR_BLACK : TEXT_ATTR_WHITE);
  78. set_win_based_attr(x_off + (pos * maps[FNT_TEXT_16].width), y_off + 1,
  79. maps[FNT_TEXT_16].width, 1,
  80. maps[FNT_TEXT_16].map + off + (maps[FNT_TEXT_16].map_count / 2),
  81. maps[FNT_TEXT_16].tile_offset,
  82. maps[FNT_TEXT_16].bank, is_black ? TEXT_ATTR_BLACK : TEXT_ATTR_WHITE);
  83. }
  84. void str3(uint16_t name, uint8_t x_off, uint8_t y_off,
  85. uint8_t is_black_a, uint8_t is_black_b, uint8_t is_black_c) BANKED {
  86. character((name >> 10) & 0x1F, 0, x_off, y_off, is_black_a);
  87. character((name >> 5) & 0x1F, 1, x_off, y_off, is_black_b);
  88. character((name >> 0) & 0x1F, 2, x_off, y_off, is_black_c);
  89. }
  90. void str_l(const char *s, uint8_t len, uint8_t x_off, uint8_t y_off, uint8_t is_black) BANKED {
  91. for (uint8_t n = 0; (*s) && (n < TEXT_LINE_WIDTH) && (n < len); n++) {
  92. char c = *(s++);
  93. if ((c >= 'A') && (c <= 'Z')) {
  94. c = c - 'A' + 'a';
  95. }
  96. if ((c >= '0') && (c <= '9')) {
  97. digit(c - '0', n, x_off, y_off, is_black);
  98. } else if ((c >= 'a') && (c <= 'z')) {
  99. character(c - 'a', n, x_off, y_off, is_black);
  100. } else {
  101. // TODO inverted color on DMG?
  102. fill_win(x_off + (n * maps[FNT_TEXT_16].width), y_off,
  103. maps[FNT_TEXT_16].width, maps[FNT_TEXT_16].width,
  104. 0, 0);
  105. }
  106. }
  107. }
  108. void str(const char *s, uint8_t x_off, uint8_t y_off, uint8_t is_black) BANKED {
  109. str_l(s, 0xFF, x_off, y_off, is_black);
  110. }
  111. void str_center(const char *s, uint8_t y_off, uint8_t is_black) BANKED {
  112. uint8_t n = strlen(s);
  113. if (n > TEXT_LINE_WIDTH) n = TEXT_LINE_WIDTH;
  114. str(s, TEXT_LINE_WIDTH - n, y_off, is_black);
  115. }
  116. void str_lines(const char *s, uint8_t y_off, uint8_t is_black) BANKED {
  117. if (strlen(s) > 10) {
  118. str(s, 0, y_off, is_black);
  119. str_center(s + 10, y_off + 2, is_black);
  120. } else {
  121. str_center(s, y_off, is_black);
  122. }
  123. }
  124. // ----------------------------------------------------------------------------
  125. // Numbers 16x16 (for scores)
  126. // ----------------------------------------------------------------------------
  127. static void digit(uint8_t val, uint8_t pos, uint8_t x_off, uint8_t y_off, uint8_t is_black) {
  128. uint8_t off = val * maps[FNT_NUM_16].width;
  129. set_win_based_attr(x_off + (pos * maps[FNT_NUM_16].width), y_off,
  130. maps[FNT_NUM_16].width, 1,
  131. maps[FNT_NUM_16].map + off,
  132. maps[FNT_NUM_16].tile_offset,
  133. maps[FNT_NUM_16].bank, is_black ? TEXT_ATTR_BLACK : TEXT_ATTR_WHITE);
  134. set_win_based_attr(x_off + (pos * maps[FNT_NUM_16].width), y_off + 1,
  135. maps[FNT_NUM_16].width, 1,
  136. maps[FNT_NUM_16].map + off + (maps[FNT_NUM_16].map_count / 2),
  137. maps[FNT_NUM_16].tile_offset,
  138. maps[FNT_NUM_16].bank, is_black ? TEXT_ATTR_BLACK : TEXT_ATTR_WHITE);
  139. }
  140. uint8_t number(int32_t score, uint8_t x_off, uint8_t y_off, uint8_t is_black) BANKED {
  141. // TODO can not set numbers larger than int16 max?!
  142. //score = 32767 + 1; // wtf?!
  143. uint8_t len = 0;
  144. uint8_t digits[MAX_DIGITS];
  145. do {
  146. digits[len++] = score % 10L;
  147. score = score / 10L;
  148. if (len >= MAX_DIGITS) {
  149. break;
  150. }
  151. } while (score > 0);
  152. // if the number was too large for our buffer don't draw anything
  153. if (score > 0) {
  154. return 0;
  155. }
  156. uint8_t off = (x_off == 0xFF) ? (TEXT_LINE_WIDTH - len)
  157. : ((x_off == 0xFE) ? ((TEXT_LINE_WIDTH * 2) - (len * 2)) : x_off);
  158. for (uint8_t i = 0; i < len; i++) {
  159. digit(digits[len - i - 1], i, off, y_off, is_black);
  160. }
  161. return 8 * len * 2;
  162. }
  163. // ----------------------------------------------------------------------------
  164. // GBC-only ASCII 8x8 font (for detailed / debug output)
  165. // ----------------------------------------------------------------------------
  166. static void char_ascii(uint8_t c, uint8_t pos, uint8_t x_off, uint8_t y_off, uint8_t light) {
  167. set_win_based_attr(x_off + pos, y_off, 1, 1,
  168. maps[FNT_ASCII_8].map + c, 0,
  169. maps[FNT_ASCII_8].bank, light ? ASCI_ATTR_LIGHT : ASCI_ATTR_DARK);
  170. }
  171. void str_ascii_l(const char *s, uint8_t len, uint8_t x_off, uint8_t y_off, uint8_t light) BANKED {
  172. for (uint8_t n = 0; (*s) && (n < (2 * TEXT_LINE_WIDTH)) && (n < len); n++) {
  173. char c = *(s++);
  174. char_ascii(c, n, x_off, y_off, light);
  175. }
  176. }
  177. void str_ascii(const char *s, uint8_t x_off, uint8_t y_off, uint8_t light) BANKED {
  178. str_ascii_l(s, 0xFF, x_off, y_off, light);
  179. }
  180. void str_ascii_lines(const char *s, uint8_t y_off, uint8_t light) BANKED {
  181. const char *nl = s;
  182. uint8_t lines = 0;
  183. do {
  184. // find next newline
  185. while (*nl && (*nl != '\n')) nl++;
  186. str_ascii_l(s, nl - s, 0, y_off + lines, light);
  187. lines++;
  188. if (*nl) nl++;
  189. s = nl;
  190. } while (*nl);
  191. }