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.

obj.c 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368
  1. /*
  2. * obj.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 <stdint.h>
  20. #include <string.h>
  21. #include <stdlib.h>
  22. #include <rand.h>
  23. #include "sprites.h"
  24. #include "game.h"
  25. #include "sample.h"
  26. #include "obj.h"
  27. /*
  28. * sprite budget:
  29. *
  30. * fixed:
  31. * status bars: 8
  32. * ship: 5
  33. * thruster: 1
  34. * --> 14 fixed
  35. *
  36. * hardware tiles: 40 - 14 = 26
  37. *
  38. * dynamic:
  39. * shot: 1
  40. * light: 4
  41. * dark: 4
  42. * --> 2x dark & 2x light = 16
  43. * --> 5x shot & 4x small = 9
  44. * --> 16 + 9 = 25
  45. */
  46. #define MAX_DARK 2
  47. #define MAX_LIGHT 2
  48. #define MAX_SHOT 5
  49. #define MAX_SHOT_DARK 2
  50. #define MAX_SHOT_LIGHT 2
  51. #define MAX_OBJ ((4 * MAX_DARK) + (4 * MAX_LIGHT) + MAX_SHOT + MAX_SHOT_DARK + MAX_SHOT_LIGHT)
  52. #define MAX_TRAVEL 128
  53. #define POS_SCALE_OBJS 5
  54. #define POS_OBJS_MAX (INT16_MAX >> (8 - POS_SCALE_OBJS))
  55. #define POS_OBJS_MIN (-(INT16_MAX >> (8 - POS_SCALE_OBJS)) - 1)
  56. #define GRAVITY_RANGE (24 << POS_SCALE_OBJS)
  57. #define GRAVITY_SHIFT (POS_SCALE_OBJS + 4)
  58. #define DAMAGE_RANGE (14 << POS_SCALE_OBJS)
  59. #define DAMAGE_INC 4
  60. #define HEALTH_RANGE (12 << POS_SCALE_OBJS)
  61. #define HEALTH_INC HEALTH_MAX
  62. #define PICKUP_SMALL_RANGE (12 << POS_SCALE_OBJS)
  63. #define SHOT_RANGE (10 << POS_SCALE_OBJS)
  64. #define SCORE_SMALL 5
  65. #define SCORE_LARGE 10
  66. //#define DESPAWN_RANGE (0x7F << POS_SCALE_OBJS)
  67. #define INITIAL_DISTANCE 30 // from center
  68. #define RESPAWN_DISTANCE 100 // from center
  69. #define PLACEMENT_DISTANCE 42 // relative to each other
  70. struct obj {
  71. uint8_t active;
  72. enum SPRITES sprite;
  73. int16_t off_x;
  74. int16_t off_y;
  75. int16_t spd_x;
  76. int16_t spd_y;
  77. uint8_t travel;
  78. };
  79. static struct obj objs[MAX_OBJ];
  80. static uint8_t obj_cnt[SPRITE_COUNT];
  81. static const uint8_t obj_max[SPRITE_COUNT] = {
  82. 1, // SPR_SHIP
  83. MAX_LIGHT, // SPR_LIGHT
  84. MAX_DARK, // SPR_DARK
  85. MAX_SHOT, // SPR_SHOT
  86. MAX_SHOT_LIGHT, // SPR_SHOT_LIGHT
  87. MAX_SHOT_DARK, // SPR_SHOT_DARK
  88. 4, // SPR_HEALTH
  89. 4, // SPR_POWER
  90. 1, // SPR_EXPL
  91. 1, // SPR_PAUSE
  92. 1, // SPR_DEBUG
  93. 1, // SPR_DEBUG_LARGE
  94. };
  95. void obj_init(void) BANKED {
  96. memset(objs, 0, sizeof(objs));
  97. memset(obj_cnt, 0, sizeof(obj_cnt));
  98. }
  99. static uint8_t is_too_close(int8_t x, int8_t y, int8_t center_dist) {
  100. if ((abs(x) < center_dist) && (abs(y) < center_dist)) {
  101. return 1;
  102. }
  103. for (uint8_t i = 0; i < MAX_OBJ; i++) {
  104. if (!objs[i].active) {
  105. continue;
  106. }
  107. int dst_x = abs((objs[i].off_x >> POS_SCALE_OBJS) - x);
  108. int dst_y = abs((objs[i].off_y >> POS_SCALE_OBJS) - y);
  109. if ((dst_x < PLACEMENT_DISTANCE) && (dst_y < PLACEMENT_DISTANCE)) {
  110. return 1;
  111. }
  112. }
  113. return 0;
  114. }
  115. static void generate_coords(int8_t *x_c, int8_t *y_c, int8_t center_dist) {
  116. int8_t x = 0;
  117. int8_t y = 0;
  118. do {
  119. x = arand();
  120. y = arand();
  121. } while (is_too_close(x, y, center_dist));
  122. *x_c = x;
  123. *y_c = y;
  124. }
  125. static void obj_respawn(int8_t center_dist) {
  126. for (uint8_t spr = SPR_LIGHT; spr <= SPR_SHOT_DARK; spr++) {
  127. if (spr == SPR_SHOT) {
  128. continue;
  129. }
  130. while (obj_cnt[spr] < obj_max[spr]) {
  131. int8_t x, y;
  132. generate_coords(&x, &y, center_dist);
  133. obj_add(spr, x, y, 0, 0);
  134. }
  135. }
  136. }
  137. void obj_spawn(void) BANKED {
  138. obj_respawn(INITIAL_DISTANCE);
  139. }
  140. enum OBJ_STATE obj_add(enum SPRITES sprite, int16_t off_x, int16_t off_y, int16_t spd_x, int16_t spd_y) BANKED {
  141. uint8_t next = 0xFF;
  142. for (uint8_t i = 0; i < MAX_OBJ; i++) {
  143. if (!objs[i].active) {
  144. next = i;
  145. break;
  146. }
  147. }
  148. if (next >= MAX_OBJ) {
  149. return OBJ_LIST_FULL;
  150. }
  151. if (obj_cnt[sprite] >= obj_max[sprite]) {
  152. return OBJ_TYPE_FULL;
  153. }
  154. obj_cnt[sprite]++;
  155. objs[next].active = 1;
  156. objs[next].sprite = sprite;
  157. objs[next].off_x = off_x << POS_SCALE_OBJS;
  158. objs[next].off_y = off_y << POS_SCALE_OBJS;
  159. objs[next].spd_x = spd_x;
  160. objs[next].spd_y = spd_y;
  161. objs[next].travel = 0;
  162. return OBJ_ADDED;
  163. }
  164. int16_t obj_do(int16_t *spd_off_x, int16_t *spd_off_y, int32_t *score, uint8_t *hiwater, uint8_t is_splash) BANKED {
  165. int16_t damage = 0;
  166. // initial speed
  167. int16_t spd_x = *spd_off_x;
  168. int16_t spd_y = *spd_off_y;
  169. for (uint8_t i = 0; i < MAX_OBJ; i++) {
  170. if (!objs[i].active) {
  171. continue;
  172. }
  173. // move objects by their speed and compensate for movement of the background / ship
  174. objs[i].off_x = objs[i].off_x + objs[i].spd_x - spd_x;
  175. objs[i].off_y = objs[i].off_y + objs[i].spd_y - spd_y;
  176. if (objs[i].off_x > POS_OBJS_MAX) {
  177. objs[i].off_x -= POS_OBJS_MAX - POS_OBJS_MIN + 1;
  178. } else if (objs[i].off_x < POS_OBJS_MIN) {
  179. objs[i].off_x += POS_OBJS_MAX - POS_OBJS_MIN + 1;
  180. }
  181. if (objs[i].off_y > POS_OBJS_MAX) {
  182. objs[i].off_y -= POS_OBJS_MAX - POS_OBJS_MIN + 1;
  183. } else if (objs[i].off_y < POS_OBJS_MIN) {
  184. objs[i].off_y += POS_OBJS_MAX - POS_OBJS_MIN + 1;
  185. }
  186. // only update travel time if we're actually moving
  187. if ((objs[i].spd_x != 0) || (objs[i].spd_y != 0)) {
  188. objs[i].travel += 1;
  189. }
  190. // remove objects that have traveled for too long
  191. if (objs[i].travel >= MAX_TRAVEL) {
  192. objs[i].active = 0;
  193. obj_cnt[objs[i].sprite]--;
  194. continue;
  195. }
  196. int abs_off_x = abs(objs[i].off_x);
  197. int abs_off_y = abs(objs[i].off_y);
  198. // handle collision
  199. switch (objs[i].sprite) {
  200. case SPR_DARK:
  201. #ifdef DESPAWN_RANGE
  202. if ((abs_off_x >= DESPAWN_RANGE) || (abs_off_y >= DESPAWN_RANGE)) {
  203. objs[i].active = 0;
  204. obj_cnt[objs[i].sprite]--;
  205. obj_respawn(RESPAWN_DISTANCE);
  206. }
  207. #endif // DESPAWN_RANGE
  208. if ((abs_off_x <= GRAVITY_RANGE) && (abs_off_y <= GRAVITY_RANGE)) {
  209. if (objs[i].off_x > 0) {
  210. *spd_off_x += (GRAVITY_RANGE - objs[i].off_x) >> GRAVITY_SHIFT;
  211. } else if (objs[i].off_x < 0) {
  212. *spd_off_x += (-GRAVITY_RANGE - objs[i].off_x) >> GRAVITY_SHIFT;
  213. }
  214. if (objs[i].off_y > 0) {
  215. *spd_off_y += (GRAVITY_RANGE - objs[i].off_y) >> GRAVITY_SHIFT;
  216. } else if (objs[i].off_y < 0) {
  217. *spd_off_y += (-GRAVITY_RANGE - objs[i].off_y) >> GRAVITY_SHIFT;
  218. }
  219. }
  220. if ((abs_off_x <= DAMAGE_RANGE) && (abs_off_y <= DAMAGE_RANGE)) {
  221. damage += DAMAGE_INC;
  222. }
  223. break;
  224. case SPR_LIGHT:
  225. #ifdef DESPAWN_RANGE
  226. if ((abs_off_x >= DESPAWN_RANGE) || (abs_off_y >= DESPAWN_RANGE)) {
  227. objs[i].active = 0;
  228. obj_cnt[objs[i].sprite]--;
  229. obj_respawn(RESPAWN_DISTANCE);
  230. }
  231. #endif // DESPAWN_RANGE
  232. if ((abs_off_x <= GRAVITY_RANGE) && (abs_off_y <= GRAVITY_RANGE)) {
  233. if (objs[i].off_x > 0) {
  234. *spd_off_x -= (GRAVITY_RANGE - objs[i].off_x) >> GRAVITY_SHIFT;
  235. } else if (objs[i].off_x < 0) {
  236. *spd_off_x -= (-GRAVITY_RANGE - objs[i].off_x) >> GRAVITY_SHIFT;
  237. }
  238. if (objs[i].off_y > 0) {
  239. *spd_off_y -= (GRAVITY_RANGE - objs[i].off_y) >> GRAVITY_SHIFT;
  240. } else if (objs[i].off_y < 0) {
  241. *spd_off_y -= (-GRAVITY_RANGE - objs[i].off_y) >> GRAVITY_SHIFT;
  242. }
  243. }
  244. if ((abs_off_x <= HEALTH_RANGE) && (abs_off_y <= HEALTH_RANGE)) {
  245. damage -= HEALTH_INC;
  246. }
  247. break;
  248. case SPR_SHOT_DARK:
  249. #ifdef DESPAWN_RANGE
  250. if ((abs_off_x >= DESPAWN_RANGE) || (abs_off_y >= DESPAWN_RANGE)) {
  251. objs[i].active = 0;
  252. obj_cnt[objs[i].sprite]--;
  253. obj_respawn(RESPAWN_DISTANCE);
  254. }
  255. #endif // DESPAWN_RANGE
  256. if ((abs_off_x <= PICKUP_SMALL_RANGE) && (abs_off_y <= PICKUP_SMALL_RANGE)) {
  257. (*score) -= SCORE_SMALL;
  258. objs[i].active = 0;
  259. obj_cnt[objs[i].sprite]--;
  260. obj_respawn(RESPAWN_DISTANCE);
  261. }
  262. break;
  263. case SPR_SHOT_LIGHT:
  264. #ifdef DESPAWN_RANGE
  265. if ((abs_off_x >= DESPAWN_RANGE) || (abs_off_y >= DESPAWN_RANGE)) {
  266. objs[i].active = 0;
  267. obj_cnt[objs[i].sprite]--;
  268. obj_respawn(RESPAWN_DISTANCE);
  269. }
  270. #endif // DESPAWN_RANGE
  271. if ((abs_off_x <= PICKUP_SMALL_RANGE) && (abs_off_y <= PICKUP_SMALL_RANGE)) {
  272. (*score) += SCORE_SMALL;
  273. objs[i].active = 0;
  274. obj_cnt[objs[i].sprite]--;
  275. obj_respawn(RESPAWN_DISTANCE);
  276. }
  277. break;
  278. case SPR_SHOT:
  279. for (uint8_t j = 0; j < MAX_OBJ; j++) {
  280. if ((!objs[j].active) || ((objs[j].sprite != SPR_LIGHT) && (objs[j].sprite != SPR_DARK))) {
  281. continue;
  282. }
  283. if ((abs(objs[i].off_x - objs[j].off_x) <= SHOT_RANGE)
  284. && (abs(objs[i].off_y - objs[j].off_y) <= SHOT_RANGE)) {
  285. sample_play_explosion_orbs();
  286. objs[i].active = 0;
  287. objs[j].active = 0;
  288. obj_cnt[objs[i].sprite]--;
  289. obj_cnt[objs[j].sprite]--;
  290. if (!is_splash) {
  291. obj_respawn(RESPAWN_DISTANCE);
  292. }
  293. if (objs[j].sprite == SPR_LIGHT) {
  294. (*score) += SCORE_LARGE;
  295. } else {
  296. (*score) -= SCORE_LARGE;
  297. }
  298. break;
  299. }
  300. }
  301. break;
  302. default:
  303. break;
  304. }
  305. spr_draw(objs[i].sprite, FLIP_NONE, objs[i].off_x >> POS_SCALE_OBJS, objs[i].off_y >> POS_SCALE_OBJS, 0, hiwater);
  306. }
  307. return damage;
  308. }