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.

window.c 16KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482
  1. /*
  2. * window.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 <assert.h>
  23. #include "banks.h"
  24. #include "config.h"
  25. #include "gb/hardware.h"
  26. #include "score.h"
  27. #include "title_map.h"
  28. #include "bg_map.h"
  29. #include "numbers_fnt16.h"
  30. #include "text_fnt16.h"
  31. #include "vincent_fnt8.h"
  32. #include "git.h"
  33. #include "main.h"
  34. #include "gbprinter.h"
  35. #include "multiplayer.h"
  36. #include "window.h"
  37. #define MAX_DIGITS 7
  38. #define LINE_WIDTH 10
  39. // TODO inverted score color not visible on DMG
  40. // TODO 8x8 font only available on GBC
  41. BANKREF(window)
  42. const palette_color_t num_pal_inv[4] = {
  43. //RGB8( 0, 0, 0), RGB8(248,252,248), RGB8( 0, 0, 0), RGB8( 0, 0, 0)
  44. RGB8( 0, 0, 0), RGB8( 0, 0, 0), RGB8(248,252,248), RGB8( 0, 0, 0)
  45. };
  46. static uint8_t fnt_off = 0;
  47. void win_init(uint8_t is_splash) NONBANKED {
  48. fnt_off = is_splash ? title_map_TILE_COUNT : bg_map_TILE_COUNT;
  49. START_ROM_BANK(BANK(numbers_fnt16)) {
  50. set_bkg_palette(OAMF_CGB_PAL0 + bg_map_PALETTE_COUNT,
  51. numbers_fnt16_PALETTE_COUNT, numbers_fnt16_palettes);
  52. set_win_data(fnt_off, numbers_fnt16_TILE_COUNT, numbers_fnt16_tiles);
  53. } END_ROM_BANK
  54. START_ROM_BANK_2(BANK(window)) {
  55. set_bkg_palette(OAMF_CGB_PAL0 + bg_map_PALETTE_COUNT + numbers_fnt16_PALETTE_COUNT,
  56. numbers_fnt16_PALETTE_COUNT, num_pal_inv);
  57. } END_ROM_BANK
  58. if (is_splash) {
  59. START_ROM_BANK_2(BANK(text_fnt16)) {
  60. set_win_data(fnt_off + numbers_fnt16_TILE_COUNT,
  61. text_fnt16_TILE_COUNT, text_fnt16_tiles);
  62. } END_ROM_BANK
  63. if (_cpu == CGB_TYPE) {
  64. VBK_REG = VBK_BANK_1;
  65. START_ROM_BANK_2(BANK(vincent_fnt8)) {
  66. set_win_data(0, vincent_fnt8_TILE_COUNT, vincent_fnt8_tiles);
  67. set_bkg_palette(OAMF_CGB_PAL0 + bg_map_PALETTE_COUNT + (2 * numbers_fnt16_PALETTE_COUNT),
  68. vincent_fnt8_PALETTE_COUNT, vincent_fnt8_palettes);
  69. } END_ROM_BANK
  70. }
  71. }
  72. }
  73. static void set_win_based(uint8_t x, uint8_t y, uint8_t w, uint8_t h,
  74. const uint8_t *tiles, uint8_t base_tile, uint8_t tile_bank,
  75. const uint8_t *attributes, uint8_t attr_bank) NONBANKED {
  76. if (attributes != NULL) {
  77. START_ROM_BANK(attr_bank) {
  78. VBK_REG = VBK_ATTRIBUTES;
  79. set_win_tiles(x, y, w, h, attributes);
  80. } END_ROM_BANK
  81. } else {
  82. VBK_REG = VBK_ATTRIBUTES;
  83. fill_win_rect(x, y, w, h, 0x00);
  84. }
  85. START_ROM_BANK(tile_bank) {
  86. VBK_REG = VBK_TILES;
  87. set_win_based_tiles(x, y, w, h, tiles, base_tile);
  88. } END_ROM_BANK
  89. }
  90. static void set_win_based_attr(uint8_t x, uint8_t y, uint8_t w, uint8_t h,
  91. const uint8_t *tiles, uint8_t base_tile, uint8_t tile_bank,
  92. const uint8_t attr) NONBANKED {
  93. VBK_REG = VBK_ATTRIBUTES;
  94. fill_win_rect(x, y, w, h, attr);
  95. START_ROM_BANK(tile_bank) {
  96. VBK_REG = VBK_TILES;
  97. set_win_based_tiles(x, y, w, h, tiles, base_tile);
  98. } END_ROM_BANK
  99. }
  100. static void character(uint8_t c, uint8_t pos, uint8_t x_off, uint8_t y_off, uint8_t is_black) {
  101. uint8_t off = c * text_fnt16_WIDTH / text_fnt16_TILE_W;
  102. set_win_based_attr(x_off + (pos * text_fnt16_WIDTH / text_fnt16_TILE_W), y_off,
  103. text_fnt16_WIDTH / text_fnt16_TILE_W, 1,
  104. text_fnt16_map + off, fnt_off + numbers_fnt16_TILE_COUNT,
  105. BANK(text_fnt16), is_black ? 0x82 : 0x81);
  106. set_win_based_attr(x_off + (pos * text_fnt16_WIDTH / text_fnt16_TILE_W), y_off + 1,
  107. text_fnt16_WIDTH / text_fnt16_TILE_W, 1,
  108. text_fnt16_map + off + (sizeof(text_fnt16_map) / 2), fnt_off + numbers_fnt16_TILE_COUNT,
  109. BANK(text_fnt16), is_black ? 0x82 : 0x81);
  110. }
  111. static void char_ascii(uint8_t c, uint8_t pos, uint8_t x_off, uint8_t y_off, uint8_t light) {
  112. set_win_based_attr(x_off + pos, y_off, 1, 1,
  113. vincent_fnt8_map + c, 0,
  114. BANK(vincent_fnt8), light ? 0x88 : 0x8B);
  115. }
  116. static void str3(uint16_t name, uint8_t x_off, uint8_t y_off,
  117. uint8_t is_black_a, uint8_t is_black_b, uint8_t is_black_c) {
  118. character((name >> 10) & 0x1F, 0, x_off, y_off, is_black_a);
  119. character((name >> 5) & 0x1F, 1, x_off, y_off, is_black_b);
  120. character((name >> 0) & 0x1F, 2, x_off, y_off, is_black_c);
  121. }
  122. static void digit(uint8_t val, uint8_t pos, uint8_t x_off, uint8_t y_off, uint8_t is_black) {
  123. uint8_t off = val * numbers_fnt16_WIDTH / numbers_fnt16_TILE_W;
  124. set_win_based_attr(x_off + (pos * numbers_fnt16_WIDTH / numbers_fnt16_TILE_W), y_off,
  125. numbers_fnt16_WIDTH / numbers_fnt16_TILE_W, 1,
  126. numbers_fnt16_map + off, fnt_off,
  127. BANK(numbers_fnt16), is_black ? 0x82 : 0x81);
  128. set_win_based_attr(x_off + (pos * numbers_fnt16_WIDTH / numbers_fnt16_TILE_W), y_off + 1,
  129. numbers_fnt16_WIDTH / numbers_fnt16_TILE_W, 1,
  130. numbers_fnt16_map + off + (sizeof(numbers_fnt16_map) / 2), fnt_off,
  131. BANK(numbers_fnt16), is_black ? 0x82 : 0x81);
  132. }
  133. static void str_l(const char *s, uint8_t len, uint8_t x_off, uint8_t y_off, uint8_t is_black) {
  134. for (uint8_t n = 0; (*s) && (n < LINE_WIDTH) && (n < len); n++) {
  135. char c = *(s++);
  136. if ((c >= 'A') && (c <= 'Z')) {
  137. c = c - 'A' + 'a';
  138. }
  139. if ((c >= '0') && (c <= '9')) {
  140. digit(c - '0', n, x_off, y_off, is_black);
  141. } else if ((c >= 'a') && (c <= 'z')) {
  142. character(c - 'a', n, x_off, y_off, is_black);
  143. }
  144. }
  145. }
  146. static void str(const char *s, uint8_t x_off, uint8_t y_off, uint8_t is_black) {
  147. str_l(s, 0xFF, x_off, y_off, is_black);
  148. }
  149. static void str_ascii_l(const char *s, uint8_t len, uint8_t x_off, uint8_t y_off, uint8_t light) {
  150. for (uint8_t n = 0; (*s) && (n < (2 * LINE_WIDTH)) && (n < len); n++) {
  151. char c = *(s++);
  152. char_ascii(c, n, x_off, y_off, light);
  153. }
  154. }
  155. static void str_ascii(const char *s, uint8_t x_off, uint8_t y_off, uint8_t light) {
  156. str_ascii_l(s, 0xFF, x_off, y_off, light);
  157. }
  158. static void str_center(const char *s, uint8_t y_off, uint8_t is_black) {
  159. uint8_t n = strlen(s);
  160. if (n > LINE_WIDTH) n = LINE_WIDTH;
  161. str(s, LINE_WIDTH - n, y_off, is_black);
  162. }
  163. void win_str_center(const char *s, uint8_t y_off, uint8_t is_black) NONBANKED {
  164. START_ROM_BANK(BANK(window)) {
  165. str_center(s, y_off, is_black);
  166. } END_ROM_BANK
  167. }
  168. static void str_lines(const char *s, uint8_t y_off, uint8_t is_black) {
  169. if (strlen(s) > 10) {
  170. str(s, 0, y_off, is_black);
  171. str_center(s + 10, y_off + 2, is_black);
  172. } else {
  173. str_center(s, y_off, is_black);
  174. }
  175. }
  176. static void str_ascii_lines(const char *s, uint8_t y_off, uint8_t light) {
  177. const char *nl = s;
  178. uint8_t lines = 0;
  179. do {
  180. // find next newline
  181. while (*nl && (*nl != '\n')) nl++;
  182. str_ascii_l(s, nl - s, 0, y_off + lines, light);
  183. lines++;
  184. if (*nl) nl++;
  185. s = nl;
  186. } while (*nl);
  187. }
  188. static uint8_t number(int32_t score, uint8_t x_off, uint8_t y_off, uint8_t is_black) {
  189. // TODO can not set numbers larger than int16 max?!
  190. //score = 32767 + 1; // wtf?!
  191. uint8_t len = 0;
  192. uint8_t digits[MAX_DIGITS];
  193. do {
  194. digits[len++] = score % 10L;
  195. score = score / 10L;
  196. if (len >= MAX_DIGITS) {
  197. break;
  198. }
  199. } while (score > 0);
  200. // if the number was too large for our buffer don't draw anything
  201. if (score > 0) {
  202. return 0;
  203. }
  204. uint8_t off = (x_off == 0xFF) ? (LINE_WIDTH - len) : ((x_off == 0xFE) ? ((LINE_WIDTH * 2) - (len * 2)) : x_off);
  205. for (uint8_t i = 0; i < len; i++) {
  206. digit(digits[len - i - 1], i, off, y_off, is_black);
  207. }
  208. return 8 * len * 2;
  209. }
  210. static void fill_win(uint8_t x, uint8_t y, uint8_t w, uint8_t h, uint8_t tile, uint8_t attr) {
  211. VBK_REG = VBK_ATTRIBUTES;
  212. fill_win_rect(x, y, w, h, attr);
  213. VBK_REG = VBK_TILES;
  214. fill_win_rect(x, y, w, h, tile);
  215. }
  216. void win_splash_draw(int32_t lowest, int32_t highest) BANKED {
  217. set_win_based(0, 0,
  218. title_map_WIDTH / title_map_TILE_W, title_map_HEIGHT / title_map_TILE_H,
  219. title_map_map, 0, BANK(title_map), title_map_MAP_ATTRIBUTES, BANK(title_map));
  220. // only show on splash if they fit
  221. if ((lowest <= 99999) && (highest <= 99999)) {
  222. number(lowest, 0, DEVICE_SCREEN_HEIGHT - 4, 1);
  223. number(highest, 0xFE, DEVICE_SCREEN_HEIGHT - 4, 0);
  224. str("top", 0, DEVICE_SCREEN_HEIGHT - 2, 1);
  225. str("score", 10, DEVICE_SCREEN_HEIGHT - 2, 0);
  226. }
  227. }
  228. void win_splash_mp(void) BANKED {
  229. static uint8_t prev = 0;
  230. if ((_cpu == CGB_TYPE) && (mp_connection_status != prev)) {
  231. prev = mp_connection_status;
  232. char c = mp_connection_status & 0x1F;
  233. str_ascii_l(&c, 1, 19, 0, 1);
  234. }
  235. }
  236. void win_score_clear(uint8_t is_black, uint8_t no_bg) BANKED {
  237. if (no_bg) {
  238. fill_win(0, 0, DEVICE_SCREEN_WIDTH, DEVICE_SCREEN_HEIGHT, 1, 0x00);
  239. } else {
  240. set_win_based(0, 0,
  241. title_map_WIDTH / title_map_TILE_W, title_map_HEIGHT / title_map_TILE_H,
  242. title_map_map, 0, BANK(title_map), title_map_MAP_ATTRIBUTES, BANK(title_map));
  243. }
  244. if (is_black < 2) {
  245. str_center(is_black ? "black" : "white", 1, is_black);
  246. }
  247. }
  248. void win_score_draw(struct scores score, uint8_t off, uint8_t is_black) BANKED {
  249. str3(score.name, 0, 4 + off * 3, is_black, is_black, is_black);
  250. number(is_black ? -score.score : score.score, 7, 4 + off * 3, is_black);
  251. }
  252. void win_score_print(enum PRN_STATUS status) BANKED {
  253. static char buff[128];
  254. if (_cpu == CGB_TYPE) {
  255. str_ascii("GB Printer", 0, 0, 0);
  256. str_ascii("Score Printout", 0, 1, 0);
  257. str_ascii("Result:", 0, 3, 0);
  258. if (status == PRN_STATUS_OK) {
  259. str_ascii("success", 0, 8, 0);
  260. } else {
  261. sprintf(buff, "error: 0x%04x", (uint16_t)status);
  262. str_ascii(buff, 0, 5, 0);
  263. gbprinter_error(status, buff);
  264. str_ascii_lines(buff, 6, 0);
  265. }
  266. } else {
  267. str("printout", 0, 4, 0);
  268. if (status == PRN_STATUS_OK) {
  269. str("success", 0, 8, 0);
  270. } else {
  271. str("error", 0, 8, 1);
  272. number(status, 11, 8, 1);
  273. }
  274. }
  275. }
  276. static void get_git(char *line_buff) NONBANKED {
  277. START_ROM_BANK(BANK(git)) {
  278. strncpy(line_buff, git_version, 2 * LINE_WIDTH);
  279. } END_ROM_BANK
  280. }
  281. void win_about(void) BANKED {
  282. set_win_based(0, 0,
  283. title_map_WIDTH / title_map_TILE_W, title_map_HEIGHT / title_map_TILE_H,
  284. title_map_map, 0, BANK(title_map), title_map_MAP_ATTRIBUTES, BANK(title_map));
  285. str_center("Duality", 0, 1);
  286. str_center("xythobuz", 2, 1);
  287. char line_buff[2 * LINE_WIDTH + 1] = {0};
  288. get_git(line_buff);
  289. if (_cpu == CGB_TYPE) {
  290. str_ascii("Git Commit Hash:", 0, 6, 0);
  291. str_ascii(line_buff, 0, 7, 0);
  292. str_ascii("Build Date:", 0, 10, 0);
  293. str_ascii(__DATE__, 0, 11, 0);
  294. str_ascii(__TIME__, 0, 12, 0);
  295. str_ascii("MP Tx:", 14, 11, 1);
  296. str_ascii("Wait", 14, 12, 1);
  297. str_ascii("Visit:", 0, 15, 0);
  298. str_ascii("https://xythobuz.de", 0, 16, 0);
  299. } else {
  300. str_lines(line_buff, 7, 0);
  301. str_l(&__DATE__[7], 4, 0, 14, 1); // year (4)
  302. str_l(&__DATE__[0], 3, (4 * 2) + 1, 14, 1); // month (3)
  303. str_l(&__DATE__[4], 2, (7 * 2) + 2, 14, 1); // day (2)
  304. str(__TIME__, 4, 16, 0);
  305. }
  306. }
  307. void win_about_mp(void) BANKED {
  308. static uint8_t prev = 0;
  309. if ((_cpu == CGB_TYPE) && (mp_connection_status != prev)) {
  310. prev = mp_connection_status;
  311. char c = mp_connection_status & 0x7F;
  312. str_ascii_l(&c, 1, 19, 12, 1);
  313. }
  314. }
  315. static uint8_t get_debug(char *name_buff, uint8_t i) NONBANKED {
  316. uint8_t n_len;
  317. START_ROM_BANK(BANK(main)) {
  318. strncpy(name_buff, debug_entries[i].name, ENTRY_NAME_LEN + 1);
  319. n_len = strlen(name_buff);
  320. name_buff[n_len] = ' ';
  321. if (debug_entries[i].flag == DBG_NONE) {
  322. if (debug_menu_index == i) {
  323. name_buff[n_len + 1] = debug_special_value + '0';
  324. } else {
  325. name_buff[n_len + 1] = '0';
  326. }
  327. } else {
  328. name_buff[n_len + 1] = (conf_get()->debug_flags & debug_entries[i].flag) ? '1' : '0';
  329. }
  330. name_buff[n_len + 2] = '\0';
  331. n_len += 2;
  332. } END_ROM_BANK
  333. return n_len;
  334. }
  335. void win_debug(void) BANKED {
  336. set_win_based(0, 0,
  337. title_map_WIDTH / title_map_TILE_W, title_map_HEIGHT / title_map_TILE_H,
  338. title_map_map, 0, BANK(title_map), title_map_MAP_ATTRIBUTES, BANK(title_map));
  339. // TODO prettier pagination
  340. uint8_t off = (10 - (DEBUG_ENTRY_COUNT - debug_menu_index)) / 2;
  341. str_center("Debug Menu", 0, 0);
  342. for (uint8_t i = debug_menu_index; (i < DEBUG_ENTRY_COUNT) && (i < (7 + debug_menu_index)); i++) {
  343. char name_buff[ENTRY_NAME_LEN + 2 + 1] = {0};
  344. uint8_t n_len = get_debug(name_buff, i);
  345. str(name_buff, (LINE_WIDTH - n_len) * 2, ((i - debug_menu_index) * 2) + 3 + off, (debug_menu_index == i) ? 1 : 0);
  346. }
  347. }
  348. static uint8_t get_conf(char *name_buff, uint8_t i) NONBANKED {
  349. uint8_t n_len;
  350. START_ROM_BANK(BANK(main)) {
  351. strncpy(name_buff, conf_entries[i].name, ENTRY_NAME_LEN + 1);
  352. n_len = strlen(name_buff);
  353. name_buff[n_len] = ' ';
  354. if (*conf_entries[i].var < 10) {
  355. name_buff[n_len + 1] = *conf_entries[i].var + '0';
  356. } else {
  357. name_buff[n_len + 1] = *conf_entries[i].var - 10 + 'A';
  358. }
  359. name_buff[n_len + 2] = '\0';
  360. n_len += 2;
  361. } END_ROM_BANK
  362. return n_len;
  363. }
  364. void win_conf(void) BANKED {
  365. set_win_based(0, 0,
  366. title_map_WIDTH / title_map_TILE_W, title_map_HEIGHT / title_map_TILE_H,
  367. title_map_map, 0, BANK(title_map), title_map_MAP_ATTRIBUTES, BANK(title_map));
  368. // TODO paging when more options added
  369. static_assert(CONF_ENTRY_COUNT <= 7, "too many conf menu entries");
  370. uint8_t off = (10 - CONF_ENTRY_COUNT) / 2;
  371. str_center("Conf Menu", 0, 0);
  372. for (uint8_t i = 0; (i < CONF_ENTRY_COUNT) && (i < 7); i++) {
  373. char name_buff[ENTRY_NAME_LEN + 2 + 1] = {0};
  374. uint8_t n_len = get_conf(name_buff, i);
  375. str(name_buff, (LINE_WIDTH - n_len) * 2, (i * 2) + 3 + off, (debug_menu_index == i) ? 1 : 0);
  376. }
  377. }
  378. void win_name(int32_t score) BANKED {
  379. set_win_based(0, 0,
  380. title_map_WIDTH / title_map_TILE_W, title_map_HEIGHT / title_map_TILE_H,
  381. title_map_map, 0, BANK(title_map), title_map_MAP_ATTRIBUTES, BANK(title_map));
  382. str_center("score", 1, score < 0);
  383. number(score < 0 ? -score : score, 0xFF, 3, score < 0);
  384. str_center("enter", 6, score < 0);
  385. str_center("name", 8, score < 0);
  386. str_center("start ok", 16, score < 0);
  387. }
  388. void win_name_draw(uint16_t name, uint8_t is_black, uint8_t pos) BANKED {
  389. str3(name, LINE_WIDTH - 3, 12,
  390. (pos == 0) ? !is_black : is_black,
  391. (pos == 1) ? !is_black : is_black,
  392. (pos == 2) ? !is_black : is_black);
  393. }
  394. uint8_t win_game_draw(int32_t score) BANKED {
  395. fill_win(0, 0, 10, 2, fnt_off + numbers_fnt16_TILE_COUNT, 0x81);
  396. uint8_t is_black = 0;
  397. if (score < 0) {
  398. score = -score;
  399. is_black = 1;
  400. }
  401. return number(score, 0, 0, is_black);
  402. }