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 17KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566
  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 "config.h"
  26. #include "maps.h"
  27. #include "obj.h"
  28. #include "sprites.h"
  29. #include "sound.h"
  30. #include "input.h"
  31. #include "game.h"
  32. #include "score.h"
  33. #include "sgb_border.h"
  34. #include "border_sgb.h"
  35. #include "timer.h"
  36. #include "sample.h"
  37. #include "main.h"
  38. uint8_t debug_menu_index = 0;
  39. uint8_t debug_special_value = 0;
  40. BANKREF(main)
  41. const struct conf_entry conf_entries[CONF_ENTRY_COUNT] = {
  42. { .name = "sfx-vol", .var = &snd_vol_sfx, .max = 0x0F }, // 0
  43. { .name = "musi-vol", .var = &snd_vol_music, .max = 0x0F }, // 1
  44. };
  45. const struct debug_entry debug_entries[DEBUG_ENTRY_COUNT] = {
  46. { .name = "marker", .flag = DBG_MARKER, .max = 1 }, // 0
  47. { .name = "invuln", .flag = DBG_GOD_MODE, .max = 1 }, // 1
  48. { .name = "music", .flag = DBG_NONE, .max = SND_COUNT }, // 2
  49. { .name = "sfx-test", .flag = DBG_NONE, .max = SFX_COUNT }, // 3
  50. { .name = "cl score", .flag = DBG_CLEAR_SCORE, .max = 1 }, // 4
  51. { .name = "0 scores", .flag = DBG_ZERO_SCORE, .max = 1 }, // 5
  52. };
  53. static void highscore(uint8_t is_black) NONBANKED {
  54. HIDE_WIN;
  55. move_win(MINWNDPOSX, MINWNDPOSY);
  56. hide_sprites_range(SPR_NUM_START, MAX_HARDWARE_SPRITES);
  57. win_score_clear(is_black ? 1 : 0);
  58. SHOW_WIN;
  59. for (uint8_t i = 0; i < SCORE_NUM; i++) {
  60. struct scores score;
  61. is_black ? score_lowest(i, &score) : score_highest(i, &score);
  62. win_score_draw(score, i, is_black);
  63. }
  64. while (1) {
  65. key_read();
  66. if (key_pressed(J_A) || key_pressed(J_B)) {
  67. break;
  68. }
  69. vsync();
  70. }
  71. }
  72. static void about_screen(void) NONBANKED {
  73. HIDE_WIN;
  74. move_win(MINWNDPOSX, MINWNDPOSY);
  75. hide_sprites_range(SPR_NUM_START, MAX_HARDWARE_SPRITES);
  76. win_about();
  77. SHOW_WIN;
  78. while (1) {
  79. key_read();
  80. if (key_pressed(J_A) || key_pressed(J_B) || key_pressed(J_SELECT)) {
  81. break;
  82. }
  83. vsync();
  84. }
  85. }
  86. static void conf_screen(void) NONBANKED {
  87. HIDE_WIN;
  88. move_win(MINWNDPOSX, MINWNDPOSY);
  89. hide_sprites_range(SPR_NUM_START, MAX_HARDWARE_SPRITES);
  90. win_conf();
  91. SHOW_WIN;
  92. debug_menu_index = 0;
  93. while (1) {
  94. key_read();
  95. if (key_pressed(J_SELECT)) {
  96. about_screen();
  97. break;
  98. } else if (key_pressed(J_UP)) {
  99. if (debug_menu_index > 0) {
  100. debug_menu_index--;
  101. } else {
  102. debug_menu_index = CONF_ENTRY_COUNT - 1;
  103. }
  104. win_conf();
  105. } else if (key_pressed(J_DOWN)) {
  106. if (debug_menu_index < (CONF_ENTRY_COUNT - 1)) {
  107. debug_menu_index++;
  108. } else {
  109. debug_menu_index = 0;
  110. }
  111. win_conf();
  112. } else if (key_pressed(J_LEFT)) {
  113. START_ROM_BANK(BANK(main));
  114. if (*conf_entries[debug_menu_index].var > 0) {
  115. (*conf_entries[debug_menu_index].var)--;
  116. } else {
  117. *conf_entries[debug_menu_index].var = conf_entries[debug_menu_index].max;
  118. }
  119. conf_get()->music_vol = snd_vol_music;
  120. conf_get()->sfx_vol = snd_vol_sfx;
  121. conf_write_crc();
  122. END_ROM_BANK();
  123. win_conf();
  124. } else if (key_pressed(J_RIGHT)) {
  125. START_ROM_BANK(BANK(main));
  126. if (*conf_entries[debug_menu_index].var < conf_entries[debug_menu_index].max) {
  127. (*conf_entries[debug_menu_index].var)++;
  128. } else {
  129. *conf_entries[debug_menu_index].var = 0;
  130. }
  131. conf_get()->music_vol = snd_vol_music;
  132. conf_get()->sfx_vol = snd_vol_sfx;
  133. conf_write_crc();
  134. END_ROM_BANK();
  135. win_conf();
  136. } else if (key_pressed(J_A) || key_pressed(J_B) || key_pressed(J_START)) {
  137. break;
  138. }
  139. vsync();
  140. }
  141. debug_menu_index = 0;
  142. }
  143. static void splash_win(void) NONBANKED {
  144. HIDE_WIN;
  145. if (conf_get()->debug_flags & DBG_MENU) {
  146. win_debug();
  147. move_win(MINWNDPOSX, MINWNDPOSY);
  148. } else {
  149. // initially show the top 1 scores
  150. struct scores score;
  151. score_lowest(0, &score);
  152. int32_t low = score.score;
  153. score_highest(0, &score);
  154. int32_t high = score.score;
  155. win_splash_draw(-low, high);
  156. move_win(MINWNDPOSX, MINWNDPOSY + DEVICE_SCREEN_PX_HEIGHT - (8 * 4));
  157. }
  158. SHOW_WIN;
  159. }
  160. static void splash_anim(uint8_t *hiwater) NONBANKED {
  161. static uint8_t frame = 0;
  162. static uint8_t state = 0;
  163. if (++frame >= 60) {
  164. frame = 0;
  165. if (++state >= 10) {
  166. state = 0;
  167. }
  168. }
  169. int16_t spd_off_x = 0;
  170. int16_t spd_off_y = 0;
  171. int32_t score = 0;
  172. obj_do(&spd_off_x, &spd_off_y, &score, hiwater, 1);
  173. switch (state) {
  174. case 0:
  175. case 2:
  176. spr_draw(SPR_SHIP, FLIP_NONE, -4, -42 - 1, 4, hiwater);
  177. break;
  178. case 1:
  179. spr_draw(SPR_SHIP, FLIP_NONE, -4, -42 - 1, 4, hiwater);
  180. if (frame == 0) {
  181. obj_add(SPR_SHOT, SHIP_OFF, -42, SHOT_SPEED, 0);
  182. sample_play(SFX_SHOT);
  183. }
  184. break;
  185. case 3:
  186. if (frame == 30) {
  187. obj_add(SPR_LIGHT, 42, -42, 0, 0);
  188. }
  189. spr_draw(SPR_SHIP, FLIP_NONE, -1, -42 + 4, 0, hiwater);
  190. break;
  191. case 8:
  192. if (frame == 30) {
  193. obj_add(SPR_DARK, -42, -42, 0, 0);
  194. }
  195. spr_draw(SPR_SHIP, FLIP_NONE, -1, -42 + 4, 0, hiwater);
  196. break;
  197. case 4:
  198. case 9:
  199. spr_draw(SPR_SHIP, FLIP_NONE, -1, -42 + 4, 0, hiwater);
  200. break;
  201. case 5:
  202. case 7:
  203. spr_draw(SPR_SHIP, FLIP_X, 4, -42, 4, hiwater);
  204. break;
  205. case 6:
  206. spr_draw(SPR_SHIP, FLIP_X, 4, -42, 4, hiwater);
  207. if (frame == 0) {
  208. obj_add(SPR_SHOT, -SHIP_OFF, -42, -SHOT_SPEED, 0);
  209. sample_play(SFX_SHOT);
  210. }
  211. break;
  212. }
  213. }
  214. static void splash(void) NONBANKED {
  215. snd_music_off();
  216. snd_note_off();
  217. disable_interrupts();
  218. DISPLAY_OFF;
  219. map_title();
  220. move_bkg(0, 0);
  221. SHOW_BKG;
  222. spr_init_pal();
  223. SHOW_SPRITES;
  224. SPRITES_8x8;
  225. obj_init();
  226. obj_add(SPR_LIGHT, 42, -42, 0, 0);
  227. obj_add(SPR_DARK, -42, -42, 0, 0);
  228. win_init(1);
  229. splash_win();
  230. DISPLAY_ON;
  231. enable_interrupts();
  232. if (!(conf_get()->debug_flags & DBG_MENU)) {
  233. snd_music(SND_MENU);
  234. }
  235. while (1) {
  236. key_read();
  237. if (key_pressed(J_LEFT) && (!(conf_get()->debug_flags & DBG_MENU))) {
  238. highscore(1);
  239. splash_win();
  240. } else if (key_pressed(J_RIGHT) && (!(conf_get()->debug_flags & DBG_MENU))) {
  241. highscore(0);
  242. splash_win();
  243. } else if (key_pressed(J_SELECT)) {
  244. conf_screen();
  245. splash_win();
  246. } else if (key_pressed(J_START)) {
  247. if ((key_debug() == 0) && (!(conf_get()->debug_flags & DBG_MENU))) {
  248. conf_get()->debug_flags |= DBG_MENU;
  249. snd_music_off();
  250. snd_note_off();
  251. conf_write_crc();
  252. splash_win();
  253. } else {
  254. break;
  255. }
  256. } else {
  257. if (conf_get()->debug_flags & DBG_MENU) {
  258. // do it here so you quickly see the flag going to 1 and back to 0
  259. if (conf_get()->debug_flags & DBG_CLEAR_SCORE) {
  260. score_reset();
  261. conf_get()->debug_flags &= ~DBG_CLEAR_SCORE;
  262. conf_write_crc();
  263. splash_win();
  264. }
  265. if (conf_get()->debug_flags & DBG_ZERO_SCORE) {
  266. score_zero();
  267. conf_get()->debug_flags &= ~DBG_ZERO_SCORE;
  268. conf_write_crc();
  269. splash_win();
  270. }
  271. uint8_t switch_special = 0;
  272. if (key_pressed(J_UP)) {
  273. if (debug_menu_index > 0) {
  274. debug_menu_index--;
  275. } else {
  276. debug_menu_index = DEBUG_ENTRY_COUNT - 1;
  277. }
  278. debug_special_value = 0;
  279. snd_music_off();
  280. snd_note_off();
  281. splash_win();
  282. } else if (key_pressed(J_DOWN)) {
  283. if (debug_menu_index < (DEBUG_ENTRY_COUNT - 1)) {
  284. debug_menu_index++;
  285. } else {
  286. debug_menu_index = 0;
  287. }
  288. debug_special_value = 0;
  289. snd_music_off();
  290. snd_note_off();
  291. splash_win();
  292. } else if (key_pressed(J_LEFT)) {
  293. START_ROM_BANK(BANK(main));
  294. if (debug_entries[debug_menu_index].flag != DBG_NONE) {
  295. conf_get()->debug_flags ^= debug_entries[debug_menu_index].flag;
  296. conf_write_crc();
  297. } else {
  298. if (debug_special_value > 0) {
  299. debug_special_value--;
  300. } else {
  301. debug_special_value = debug_entries[debug_menu_index].max;
  302. }
  303. switch_special = 1;
  304. }
  305. END_ROM_BANK();
  306. splash_win();
  307. } else if (key_pressed(J_RIGHT)) {
  308. START_ROM_BANK(BANK(main));
  309. if (debug_entries[debug_menu_index].flag != DBG_NONE) {
  310. conf_get()->debug_flags ^= debug_entries[debug_menu_index].flag;
  311. conf_write_crc();
  312. } else {
  313. if (debug_special_value < debug_entries[debug_menu_index].max) {
  314. debug_special_value++;
  315. } else {
  316. debug_special_value = 0;
  317. }
  318. switch_special = 1;
  319. }
  320. END_ROM_BANK();
  321. splash_win();
  322. } else if (key_pressed(J_A)) {
  323. START_ROM_BANK(BANK(main));
  324. if (debug_entries[debug_menu_index].flag != DBG_NONE) {
  325. conf_get()->debug_flags ^= debug_entries[debug_menu_index].flag;
  326. conf_write_crc();
  327. } else {
  328. if (debug_special_value < debug_entries[debug_menu_index].max) {
  329. debug_special_value++;
  330. } else {
  331. debug_special_value = 0;
  332. }
  333. switch_special = 1;
  334. }
  335. END_ROM_BANK();
  336. splash_win();
  337. } else if (key_pressed(J_B)) {
  338. conf_get()->debug_flags &= ~DBG_MENU;
  339. debug_special_value = 0;
  340. conf_write_crc();
  341. splash_win();
  342. snd_music(SND_MENU);
  343. }
  344. if (switch_special && (debug_menu_index == 2)) {
  345. snd_music_off();
  346. if (debug_special_value > 0) {
  347. snd_music(debug_special_value - 1);
  348. }
  349. snd_note_off();
  350. } else if ((switch_special || (!sample_running())) && (debug_menu_index == 3)) {
  351. if (debug_special_value > 0) {
  352. sample_play(debug_special_value - 1);
  353. }
  354. }
  355. }
  356. }
  357. uint8_t hiwater = SPR_NUM_START;
  358. if (!(conf_get()->debug_flags & DBG_MENU)) {
  359. if (conf_get()->debug_flags & DBG_MARKER) {
  360. spr_draw(SPR_DEBUG, FLIP_NONE, 0, -10, 0, &hiwater);
  361. spr_draw(SPR_SHOT_LIGHT, FLIP_NONE, 0, -10, 0, &hiwater);
  362. spr_draw(SPR_DEBUG, FLIP_NONE, 0, 0, 0, &hiwater);
  363. spr_draw(SPR_SHOT, FLIP_NONE, 0, 0, 0, &hiwater);
  364. spr_draw(SPR_DEBUG, FLIP_NONE, 0, 10, 0, &hiwater);
  365. spr_draw(SPR_SHOT_DARK, FLIP_NONE, 0, 10, 0, &hiwater);
  366. spr_draw(SPR_DEBUG, FLIP_NONE, 42, -42, 0, &hiwater);
  367. spr_draw(SPR_DEBUG, FLIP_NONE, 0, -42, 0, &hiwater);
  368. spr_draw(SPR_DEBUG, FLIP_NONE, -42, -42, 0, &hiwater);
  369. }
  370. splash_anim(&hiwater);
  371. }
  372. hide_sprites_range(hiwater, MAX_HARDWARE_SPRITES);
  373. vsync();
  374. }
  375. }
  376. static uint16_t ask_name(int32_t score) NONBANKED {
  377. snd_music_off();
  378. snd_note_off();
  379. disable_interrupts();
  380. DISPLAY_OFF;
  381. map_title();
  382. move_bkg(0, 0);
  383. SHOW_BKG;
  384. spr_init_pal();
  385. SHOW_SPRITES;
  386. SPRITES_8x8;
  387. hide_sprites_range(SPR_NUM_START, MAX_HARDWARE_SPRITES);
  388. win_init(1);
  389. win_name(score);
  390. move_win(MINWNDPOSX, MINWNDPOSY);
  391. SHOW_WIN;
  392. DISPLAY_ON;
  393. enable_interrupts();
  394. snd_music(SND_GAMEOVER);
  395. char name[3] = { 'a', 'a', 'a' };
  396. uint8_t pos = 0;
  397. win_name_draw(convert_name(name[0], name[1], name[2]), score < 0, pos);
  398. while (1) {
  399. key_read();
  400. if (key_pressed(J_LEFT)) {
  401. if (pos > 0) {
  402. pos--;
  403. win_name_draw(convert_name(name[0], name[1], name[2]), score < 0, pos);
  404. }
  405. } else if (key_pressed(J_RIGHT)) {
  406. if (pos < 3) {
  407. pos++;
  408. win_name_draw(convert_name(name[0], name[1], name[2]), score < 0, pos);
  409. }
  410. } else if (key_pressed(J_UP)) {
  411. if (pos < 3) {
  412. name[pos]++;
  413. if (name[pos] > 'z') {
  414. name[pos] -= 'z' - 'a' + 1;
  415. }
  416. win_name_draw(convert_name(name[0], name[1], name[2]), score < 0, pos);
  417. }
  418. } else if (key_pressed(J_DOWN)) {
  419. if (pos < 3) {
  420. name[pos]--;
  421. if (name[pos] < 'a') {
  422. name[pos] += 'z' - 'a' + 1;
  423. }
  424. win_name_draw(convert_name(name[0], name[1], name[2]), score < 0, pos);
  425. }
  426. } else if (key_pressed(J_A)) {
  427. if (pos < 3) {
  428. pos++;
  429. win_name_draw(convert_name(name[0], name[1], name[2]), score < 0, pos);
  430. } else {
  431. break;
  432. }
  433. } else if (key_pressed(J_START)) {
  434. break;
  435. }
  436. vsync();
  437. }
  438. return convert_name(name[0], name[1], name[2]);
  439. }
  440. static void sgb_init(void) NONBANKED {
  441. // Wait 4 frames
  442. // For SGB on PAL SNES this delay is required on startup, otherwise borders don't show up
  443. for (uint8_t i = 0; i < 4; i++) {
  444. vsync();
  445. }
  446. DISPLAY_ON;
  447. START_ROM_BANK(BANK(border_sgb));
  448. set_sgb_border((const uint8_t *)border_sgb_tiles, sizeof(border_sgb_tiles),
  449. (const uint8_t *)border_sgb_map, sizeof(border_sgb_map),
  450. (const uint8_t *)border_sgb_palettes, sizeof(border_sgb_palettes));
  451. END_ROM_BANK();
  452. DISPLAY_OFF;
  453. }
  454. void main(void) NONBANKED {
  455. // load sgb border
  456. sgb_init();
  457. // "cheat" and enable double-speed CPU mode on GBC
  458. if (_cpu == CGB_TYPE) {
  459. cpu_fast();
  460. }
  461. conf_init();
  462. timer_init();
  463. spr_init();
  464. snd_init();
  465. splash();
  466. uint16_t seed = DIV_REG;
  467. waitpadup();
  468. seed |= ((uint16_t)DIV_REG) << 8;
  469. initarand(seed);
  470. while (1) {
  471. int32_t score = game();
  472. if ((!(conf_get()->debug_flags & DBG_GOD_MODE)) && (score != 0) && score_ranking(score)) {
  473. uint16_t name = ask_name(score);
  474. struct scores s = { .name = name, .score = score };
  475. score_add(s);
  476. }
  477. splash();
  478. }
  479. }