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.

main.c 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420
  1. /*
  2. * main.c
  3. * Duality
  4. *
  5. * Copyright (C) 2025 Thomas Buck <thomas@xythobuz.de>
  6. *
  7. * Based on examples from gbdk-2020:
  8. * https://github.com/gbdk-2020/gbdk-2020/blob/develop/gbdk-lib/examples/gb/rand/rand.c
  9. *
  10. * This program is free software: you can redistribute it and/or modify
  11. * it under the terms of the GNU General Public License as published by
  12. * the Free Software Foundation, either version 3 of the License, or
  13. * (at your option) any later version.
  14. *
  15. * This program is distributed in the hope that it will be useful,
  16. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  17. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  18. * GNU General Public License for more details.
  19. *
  20. * See <http://www.gnu.org/licenses/>.
  21. */
  22. #include <gbdk/metasprites.h>
  23. #include <rand.h>
  24. #include "banks.h"
  25. #include "maps.h"
  26. #include "obj.h"
  27. #include "sprites.h"
  28. #include "sound.h"
  29. #include "input.h"
  30. #include "game.h"
  31. #include "score.h"
  32. #include "sgb_border.h"
  33. #include "border_sgb.h"
  34. #include "timer.h"
  35. #include "main.h"
  36. #ifdef DEBUG
  37. enum debug_flag debug_flags = DBG_MENU | DBG_MARKER;
  38. #else
  39. enum debug_flag debug_flags = 0;
  40. #endif
  41. uint8_t debug_menu_index = 0;
  42. BANKREF(main)
  43. const struct debug_entry debug_entries[DEBUG_ENTRY_COUNT] = {
  44. { .name = "marker", .flag = DBG_MARKER },
  45. { .name = "invuln", .flag = DBG_GOD_MODE },
  46. { .name = "cl score", .flag = DBG_CLEAR_SCORE },
  47. };
  48. static void highscore(uint8_t is_black) NONBANKED {
  49. HIDE_WIN;
  50. move_win(MINWNDPOSX, MINWNDPOSY);
  51. hide_sprites_range(SPR_NUM_START, MAX_HARDWARE_SPRITES);
  52. win_score_clear(is_black ? 1 : 0);
  53. SHOW_WIN;
  54. for (uint8_t i = 0; i < SCORE_NUM; i++) {
  55. struct scores score;
  56. is_black ? score_lowest(i, &score) : score_highest(i, &score);
  57. win_score_draw(score, i, is_black);
  58. }
  59. while (1) {
  60. snd_play();
  61. key_read();
  62. if (key_pressed(J_A) || key_pressed(J_B)) {
  63. break;
  64. }
  65. vsync();
  66. }
  67. }
  68. static void about_screen(void) NONBANKED {
  69. HIDE_WIN;
  70. move_win(MINWNDPOSX, MINWNDPOSY);
  71. hide_sprites_range(SPR_NUM_START, MAX_HARDWARE_SPRITES);
  72. win_about();
  73. SHOW_WIN;
  74. while (1) {
  75. snd_play();
  76. key_read();
  77. if (key_pressed(J_A) || key_pressed(J_B) || key_pressed(J_SELECT)) {
  78. break;
  79. }
  80. vsync();
  81. }
  82. }
  83. static void splash_win(void) NONBANKED {
  84. HIDE_WIN;
  85. if (debug_flags & DBG_MENU) {
  86. win_debug();
  87. move_win(MINWNDPOSX, MINWNDPOSY);
  88. } else {
  89. // initially show the top 1 scores
  90. struct scores score;
  91. score_lowest(0, &score);
  92. int32_t low = score.score;
  93. score_highest(0, &score);
  94. int32_t high = score.score;
  95. win_splash_draw(-low, high);
  96. move_win(MINWNDPOSX, MINWNDPOSY + DEVICE_SCREEN_PX_HEIGHT - (8 * 4));
  97. }
  98. SHOW_WIN;
  99. }
  100. static void splash_anim(uint8_t *hiwater) NONBANKED {
  101. static uint8_t frame = 0;
  102. static uint8_t state = 0;
  103. if (++frame >= 60) {
  104. frame = 0;
  105. if (++state >= 10) {
  106. state = 0;
  107. }
  108. }
  109. int16_t spd_off_x = 0;
  110. int16_t spd_off_y = 0;
  111. int32_t score = 0;
  112. obj_do(&spd_off_x, &spd_off_y, &score, hiwater);
  113. switch (state) {
  114. case 0:
  115. case 2:
  116. spr_draw(SPR_SHIP, FLIP_NONE, -4, -42 - 1, 4, hiwater);
  117. break;
  118. case 1:
  119. spr_draw(SPR_SHIP, FLIP_NONE, -4, -42 - 1, 4, hiwater);
  120. if (frame == 0) {
  121. obj_add(SPR_SHOT, SHIP_OFF, -42, SHOT_SPEED, 0);
  122. snd_shot();
  123. }
  124. break;
  125. case 3:
  126. if (frame == 30) {
  127. obj_add(SPR_LIGHT, 42, -42, 0, 0);
  128. }
  129. spr_draw(SPR_SHIP, FLIP_NONE, -1, -42 + 4, 0, hiwater);
  130. break;
  131. case 8:
  132. if (frame == 30) {
  133. obj_add(SPR_DARK, -42, -42, 0, 0);
  134. }
  135. spr_draw(SPR_SHIP, FLIP_NONE, -1, -42 + 4, 0, hiwater);
  136. break;
  137. case 4:
  138. case 9:
  139. spr_draw(SPR_SHIP, FLIP_NONE, -1, -42 + 4, 0, hiwater);
  140. break;
  141. case 5:
  142. case 7:
  143. spr_draw(SPR_SHIP, FLIP_X, 4, -42, 4, hiwater);
  144. break;
  145. case 6:
  146. spr_draw(SPR_SHIP, FLIP_X, 4, -42, 4, hiwater);
  147. if (frame == 0) {
  148. obj_add(SPR_SHOT, -SHIP_OFF, -42, -SHOT_SPEED, 0);
  149. snd_shot();
  150. }
  151. break;
  152. }
  153. }
  154. static void splash(void) NONBANKED {
  155. disable_interrupts();
  156. DISPLAY_OFF;
  157. map_title();
  158. move_bkg(0, 0);
  159. SHOW_BKG;
  160. spr_init_pal();
  161. SHOW_SPRITES;
  162. SPRITES_8x8;
  163. obj_init();
  164. obj_add(SPR_LIGHT, 42, -42, 0, 0);
  165. obj_add(SPR_DARK, -42, -42, 0, 0);
  166. win_init(1);
  167. splash_win();
  168. DISPLAY_ON;
  169. enable_interrupts();
  170. snd_music_off();
  171. snd_menu_music();
  172. while (1) {
  173. snd_play();
  174. key_read();
  175. if (key_pressed(J_LEFT)) {
  176. highscore(1);
  177. splash_win();
  178. } else if (key_pressed(J_RIGHT)) {
  179. highscore(0);
  180. splash_win();
  181. } else if (key_pressed(J_SELECT)) {
  182. about_screen();
  183. splash_win();
  184. } else if (key_pressed(J_START)) {
  185. if ((key_debug() == 0) && (!(debug_flags & DBG_MENU))) {
  186. debug_flags |= DBG_MENU;
  187. splash_win();
  188. } else {
  189. break;
  190. }
  191. } else {
  192. if (debug_flags & DBG_MENU) {
  193. // do it here so you quickly see the flag going to 1 and back to 0
  194. if (debug_flags & DBG_CLEAR_SCORE) {
  195. score_reset();
  196. debug_flags &= ~DBG_CLEAR_SCORE;
  197. splash_win();
  198. }
  199. if (key_pressed(J_UP)) {
  200. if (debug_menu_index > 0) {
  201. debug_menu_index--;
  202. } else {
  203. debug_menu_index = DEBUG_ENTRY_COUNT - 1;
  204. }
  205. splash_win();
  206. } else if (key_pressed(J_DOWN)) {
  207. if (debug_menu_index < (DEBUG_ENTRY_COUNT - 1)) {
  208. debug_menu_index++;
  209. } else {
  210. debug_menu_index = 0;
  211. }
  212. splash_win();
  213. } else if (key_pressed(J_A)) {
  214. START_ROM_BANK(BANK(main));
  215. debug_flags ^= debug_entries[debug_menu_index].flag;
  216. END_ROM_BANK();
  217. splash_win();
  218. } else if (key_pressed(J_B)) {
  219. debug_flags &= ~DBG_MENU;
  220. splash_win();
  221. }
  222. }
  223. }
  224. uint8_t hiwater = SPR_NUM_START;
  225. if (!(debug_flags & DBG_MENU)) {
  226. if (debug_flags & DBG_MARKER) {
  227. spr_draw(SPR_DEBUG, FLIP_NONE, 0, -10, 0, &hiwater);
  228. spr_draw(SPR_SHOT_LIGHT, FLIP_NONE, 0, -10, 0, &hiwater);
  229. spr_draw(SPR_DEBUG, FLIP_NONE, 0, 0, 0, &hiwater);
  230. spr_draw(SPR_SHOT, FLIP_NONE, 0, 0, 0, &hiwater);
  231. spr_draw(SPR_DEBUG, FLIP_NONE, 0, 10, 0, &hiwater);
  232. spr_draw(SPR_SHOT_DARK, FLIP_NONE, 0, 10, 0, &hiwater);
  233. spr_draw(SPR_DEBUG, FLIP_NONE, 42, -42, 0, &hiwater);
  234. spr_draw(SPR_DEBUG, FLIP_NONE, 0, -42, 0, &hiwater);
  235. spr_draw(SPR_DEBUG, FLIP_NONE, -42, -42, 0, &hiwater);
  236. }
  237. splash_anim(&hiwater);
  238. }
  239. hide_sprites_range(hiwater, MAX_HARDWARE_SPRITES);
  240. vsync();
  241. }
  242. }
  243. static uint16_t ask_name(int32_t score) NONBANKED {
  244. disable_interrupts();
  245. DISPLAY_OFF;
  246. map_title();
  247. move_bkg(0, 0);
  248. SHOW_BKG;
  249. spr_init_pal();
  250. SHOW_SPRITES;
  251. SPRITES_8x8;
  252. hide_sprites_range(SPR_NUM_START, MAX_HARDWARE_SPRITES);
  253. win_init(1);
  254. win_name(score);
  255. move_win(MINWNDPOSX, MINWNDPOSY);
  256. SHOW_WIN;
  257. DISPLAY_ON;
  258. enable_interrupts();
  259. snd_music_off();
  260. snd_gameover_music();
  261. char name[3] = { 'a', 'a', 'a' };
  262. uint8_t pos = 0;
  263. win_name_draw(convert_name(name[0], name[1], name[2]), score < 0, pos);
  264. while (1) {
  265. snd_play();
  266. key_read();
  267. if (key_pressed(J_LEFT)) {
  268. if (pos > 0) {
  269. pos--;
  270. win_name_draw(convert_name(name[0], name[1], name[2]), score < 0, pos);
  271. }
  272. } else if (key_pressed(J_RIGHT)) {
  273. if (pos < 3) {
  274. pos++;
  275. win_name_draw(convert_name(name[0], name[1], name[2]), score < 0, pos);
  276. }
  277. } else if (key_pressed(J_UP)) {
  278. if (pos < 3) {
  279. name[pos]++;
  280. if (name[pos] > 'z') {
  281. name[pos] -= 'z' - 'a' + 1;
  282. }
  283. win_name_draw(convert_name(name[0], name[1], name[2]), score < 0, pos);
  284. }
  285. } else if (key_pressed(J_DOWN)) {
  286. if (pos < 3) {
  287. name[pos]--;
  288. if (name[pos] < 'a') {
  289. name[pos] += 'z' - 'a' + 1;
  290. }
  291. win_name_draw(convert_name(name[0], name[1], name[2]), score < 0, pos);
  292. }
  293. } else if (key_pressed(J_A)) {
  294. if (pos < 3) {
  295. pos++;
  296. win_name_draw(convert_name(name[0], name[1], name[2]), score < 0, pos);
  297. } else {
  298. break;
  299. }
  300. } else if (key_pressed(J_START)) {
  301. break;
  302. }
  303. vsync();
  304. }
  305. return convert_name(name[0], name[1], name[2]);
  306. }
  307. static void sgb_init(void) NONBANKED {
  308. // Wait 4 frames
  309. // For SGB on PAL SNES this delay is required on startup, otherwise borders don't show up
  310. for (uint8_t i = 0; i < 4; i++) {
  311. vsync();
  312. }
  313. DISPLAY_ON;
  314. START_ROM_BANK(BANK(border_sgb));
  315. set_sgb_border((const uint8_t *)border_sgb_tiles, sizeof(border_sgb_tiles),
  316. (const uint8_t *)border_sgb_map, sizeof(border_sgb_map),
  317. (const uint8_t *)border_sgb_palettes, sizeof(border_sgb_palettes));
  318. END_ROM_BANK();
  319. DISPLAY_OFF;
  320. }
  321. void main(void) NONBANKED {
  322. // load sgb border
  323. sgb_init();
  324. // "cheat" and enable double-speed CPU mode on GBC
  325. if (_cpu == CGB_TYPE) {
  326. cpu_fast();
  327. }
  328. timer_init();
  329. spr_init();
  330. snd_init();
  331. splash();
  332. uint16_t seed = DIV_REG;
  333. waitpadup();
  334. seed |= ((uint16_t)DIV_REG) << 8;
  335. initarand(seed);
  336. while (1) {
  337. int32_t score = game();
  338. if ((!(debug_flags & DBG_GOD_MODE)) && (score != 0) && score_ranking(score)) {
  339. uint16_t name = ask_name(score);
  340. struct scores s = { .name = name, .score = score };
  341. score_add(s);
  342. }
  343. splash();
  344. }
  345. }