nightstrike.ino 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944
  1. #include <EEPROM.h>
  2. #include <SPI.h>
  3. #include <GD2.h>
  4. // For random events.
  5. // PROB(1, 50) One time in 50
  6. // PROB(7, 8) 7 times in 8
  7. #define PROB(num, denom) (GD.random() < (uint16_t)(65535UL * (num) / (denom)))
  8. #include "nightstrike_welcome_assets.h"
  9. #include "nightstrike_1_assets.h"
  10. static uint32_t t;
  11. void draw_dxt1(byte color_handle, byte bit_handle) //' dxt1{
  12. {
  13. GD.Begin(BITMAPS);
  14. GD.BlendFunc(ONE, ZERO);
  15. GD.ColorA(0x55);
  16. GD.Vertex2ii(0, 0, bit_handle, 0);
  17. GD.BlendFunc(ONE, ONE);
  18. GD.ColorA(0xaa);
  19. GD.Vertex2ii(0, 0, bit_handle, 1);
  20. GD.ColorMask(1,1,1,0);
  21. GD.cmd_scale(F16(4), F16(4));
  22. GD.cmd_setmatrix();
  23. GD.BlendFunc(DST_ALPHA, ZERO);
  24. GD.Vertex2ii(0, 0, color_handle, 1);
  25. GD.BlendFunc(ONE_MINUS_DST_ALPHA, ONE);
  26. GD.Vertex2ii(0, 0, color_handle, 0);
  27. GD.RestoreContext();
  28. } //' }dxt1
  29. struct Element {
  30. public:
  31. int16_t x, y;
  32. const shape_t *shape;
  33. void set(const shape_t *_shape) {
  34. shape = _shape;
  35. }
  36. void setxy(int _x, int _y) {
  37. x = _x;
  38. y = _y;
  39. }
  40. void vertex(byte cell, byte scale) {
  41. int x0, y0;
  42. x0 = x - (shape->w >> 1) * scale;
  43. y0 = y - (shape->h >> 1) * scale;
  44. if (((x0 | y0) & 511) == 0) {
  45. GD.Vertex2ii(x0, y0, shape->handle, cell);
  46. } else {
  47. GD.BitmapHandle(shape->handle);
  48. GD.Cell(cell);
  49. GD.Vertex2f(x0 << 4, y0 << 4);
  50. }
  51. }
  52. void draw(byte cell, byte flip = 0, byte scale = 1) {
  53. if (!flip && scale == 1) {
  54. vertex(cell, scale);
  55. } else {
  56. GD.SaveContext();
  57. GD.cmd_loadidentity();
  58. GD.cmd_translate(F16(scale * shape->w / 2), F16(scale * shape->h / 2));
  59. if (flip)
  60. GD.cmd_scale(F16(-scale), F16(scale));
  61. else
  62. GD.cmd_scale(F16(scale), F16(scale));
  63. GD.cmd_translate(F16(-(shape->w / 2)), F16(-(shape->h / 2)));
  64. GD.cmd_setmatrix();
  65. vertex(cell, scale);
  66. GD.RestoreContext();
  67. }
  68. }
  69. };
  70. struct RotatingElement : public Element {
  71. public:
  72. uint16_t angle;
  73. void set(const shape_t *_shape) {
  74. shape = _shape;
  75. }
  76. void setxy(int _x, int _y) {
  77. x = _x << 4;
  78. y = _y << 4;
  79. }
  80. void setxy_16ths(int _x, int _y) {
  81. x = _x;
  82. y = _y;
  83. }
  84. void draw(byte cell) {
  85. GD.SaveContext();
  86. GD.cmd_loadidentity();
  87. GD.cmd_translate(F16(shape->size / 2), F16(shape->size / 2));
  88. GD.cmd_rotate(angle);
  89. GD.cmd_translate(F16(-(shape->w / 2)), F16(-(shape->h / 2)));
  90. GD.cmd_setmatrix();
  91. int x0, y0;
  92. x0 = x - (shape->size << 3);
  93. y0 = y - (shape->size << 3);
  94. GD.BitmapHandle(shape->handle);
  95. GD.Cell(cell);
  96. GD.Vertex2f(x0, y0);
  97. GD.RestoreContext();
  98. }
  99. };
  100. class Stack {
  101. int8_t n;
  102. int8_t *s; // 0 means free
  103. public:
  104. void initialize(int8_t *_s, size_t _n) {
  105. s = _s;
  106. n = _n;
  107. memset(s, 0, n);
  108. }
  109. int8_t alloc(void) {
  110. for (int8_t i = 0; i < n; i++)
  111. if (s[i] == 0) {
  112. s[i] = 1;
  113. return i;
  114. }
  115. return -1;
  116. }
  117. void free(int8_t i) {
  118. s[i] = 0;
  119. }
  120. int8_t alive() {
  121. return next(-1);
  122. }
  123. int8_t next(int8_t i) {
  124. i++;
  125. for (; i < n; i++) {
  126. if (t > 148000UL) {
  127. REPORT(n);
  128. REPORT(i);
  129. }
  130. if (s[i])
  131. return i;
  132. }
  133. return -1;
  134. }
  135. };
  136. #define NUM_MISSILES 8
  137. #define MISSILE_TRAIL 8
  138. #define NUM_FIRES 16
  139. #define NUM_EXPLOSIONS 16
  140. static const PROGMEM uint8_t fire_a[] = {0,1,2,3,4,5,6,7,8, 9,9, 10,10, 11,11, 12,12, 13,13, 14,14, 15,15, 16,16};
  141. class Fires {
  142. int8_t ss[NUM_FIRES];
  143. Stack stack;
  144. Element e[NUM_FIRES];
  145. byte anim[NUM_FIRES];
  146. public:
  147. void initialize() {
  148. stack.initialize(ss, sizeof(ss));
  149. }
  150. void create(int x, int y) {
  151. int8_t i = stack.alloc();
  152. if (i >= 0) {
  153. e[i].set(&FIRE_SHAPE);
  154. e[i].setxy(x, y);
  155. anim[i] = 0;
  156. }
  157. }
  158. void draw() {
  159. for (int8_t i = stack.alive(); i >= 0; i = stack.next(i))
  160. e[i].draw(pgm_read_byte(fire_a + anim[i]));
  161. }
  162. void update(int t) {
  163. if ((t & 1) == 0) {
  164. for (int8_t i = stack.alive(); i >= 0; i = stack.next(i)) {
  165. if (++anim[i] == sizeof(fire_a))
  166. stack.free(i);
  167. }
  168. }
  169. }
  170. };
  171. static const PROGMEM uint8_t explode_a[] = {0,1,2,3,4,5,5,6,6,7,7,8,8,9,9};
  172. class Explosions {
  173. int8_t ss[NUM_EXPLOSIONS];
  174. Stack stack;
  175. RotatingElement e[NUM_EXPLOSIONS];
  176. byte anim[NUM_EXPLOSIONS];
  177. public:
  178. void initialize() {
  179. stack.initialize(ss, sizeof(ss));
  180. }
  181. void create(int x, int y, uint16_t angle) {
  182. int8_t i = stack.alloc();
  183. if (i >= 0) {
  184. e[i].set(&EXPLODE_BIG_SHAPE);
  185. e[i].setxy_16ths(x, y);
  186. e[i].angle = angle;
  187. anim[i] = 0;
  188. }
  189. GD.play(KICKDRUM);
  190. }
  191. void draw() {
  192. for (int8_t i = stack.alive(); i >= 0; i = stack.next(i))
  193. e[i].draw(pgm_read_byte(explode_a + anim[i]));
  194. }
  195. void update(int t) {
  196. if ((t & 1) == 0) {
  197. for (int8_t i = stack.alive(); i >= 0; i = stack.next(i)) {
  198. if (++anim[i] == sizeof(explode_a))
  199. stack.free(i);
  200. }
  201. }
  202. }
  203. };
  204. #define NUM_SOLDIERS 3
  205. #define SOLDIER_LEFT (-SOLDIER_RUN_WIDTH / 2)
  206. #define SOLDIER_RIGHT (480 + (SOLDIER_RUN_WIDTH / 2))
  207. static const PROGMEM uint8_t soldier_a[] = {0,0,0,1,1,2,2,3,3,4,4,4,5,5,6,6,7,7};
  208. class SoldierObject {
  209. Element e;
  210. int8_t vx;
  211. byte a;
  212. public:
  213. void initialize(int8_t _vx) {
  214. a = 0;
  215. vx = _vx;
  216. e.set(&SOLDIER_RUN_SHAPE);
  217. e.setxy((e.x < 0) ? SOLDIER_RIGHT : SOLDIER_LEFT, 272 - SOLDIER_RUN_HEIGHT / 2);
  218. }
  219. void draw() {
  220. e.draw(pgm_read_byte(soldier_a + a), vx > 0);
  221. }
  222. byte update(int t);
  223. };
  224. class Soldiers {
  225. int8_t ss[NUM_SOLDIERS];
  226. Stack stack;
  227. SoldierObject soldiers[NUM_SOLDIERS];
  228. public:
  229. void initialize() {
  230. stack.initialize(ss, sizeof(ss));
  231. }
  232. void create() {
  233. int8_t i = stack.alloc();
  234. if (i >= 0) {
  235. soldiers[i].initialize(PROB(1, 2) ? -1 : 1);
  236. }
  237. }
  238. void draw() {
  239. for (int8_t i = stack.alive(); i >= 0; i = stack.next(i))
  240. soldiers[i].draw();
  241. }
  242. void update(int t) {
  243. if ((t & 1) == 0) {
  244. for (int8_t i = stack.alive(); i >= 0; i = stack.next(i)) {
  245. if (soldiers[i].update(t))
  246. stack.free(i);
  247. }
  248. }
  249. if (PROB(1, 100))
  250. create();
  251. }
  252. };
  253. #define HOMING_SPEED 32
  254. #define HOMING_SLEW 400
  255. class MissileObject {
  256. RotatingElement e;
  257. union {
  258. struct { int16_t x, y; } trail[MISSILE_TRAIL];
  259. int dir;
  260. };
  261. int8_t th, ts;
  262. int16_t vx, vy;
  263. public:
  264. void initialize(uint16_t angle, uint16_t vel) {
  265. e.set(&MISSILE_A_SHAPE);
  266. e.angle = angle;
  267. e.setxy_16ths(16*240 - GD.rsin(16*110, angle),
  268. 16*265 + GD.rcos(16*110, angle));
  269. vx = -GD.rsin(vel, e.angle);
  270. vy = GD.rcos(vel, e.angle);
  271. th = 0;
  272. ts = 0;
  273. }
  274. void air(Element &o) {
  275. e.set(&MISSILE_C_SHAPE);
  276. e.setxy(o.x, o.y);
  277. ts = -1;
  278. trail[0].x = (vx < 0) ? -1 : 1;
  279. }
  280. void draw() {
  281. byte player = (0 <= ts);
  282. if (player) {
  283. GD.Begin(LINE_STRIP);
  284. for (int i = 0; i < ts; i++) {
  285. GD.ColorA(255 - (i << 5));
  286. GD.LineWidth(48 - (i << 2));
  287. int j = (th - i) & (MISSILE_TRAIL - 1);
  288. GD.Vertex2f(trail[j].x, trail[j].y);
  289. }
  290. GD.ColorA(255);
  291. GD.Begin(BITMAPS);
  292. e.angle = GD.atan2(vy, vx);
  293. }
  294. e.draw(0);
  295. // if (!player) GD.cmd_number(e.x >> 4, e.y >>4, 26, OPT_CENTER | OPT_SIGNED, trail[0].x);
  296. }
  297. byte update(int t);
  298. void blowup();
  299. byte collide(Element &other) {
  300. if (ts == -1)
  301. return 0;
  302. int dx = abs((e.x >> 4) - other.x);
  303. int dy = abs((e.y >> 4) - other.y);
  304. return (dx < 32) && (dy < 32);
  305. }
  306. byte hitbase() {
  307. if (0 <= ts)
  308. return 0;
  309. int dx = abs((e.x >> 4) - 240);
  310. int dy = abs((e.y >> 4) - 270);
  311. return (dx < 50) && (dy < 50);
  312. }
  313. void tailpipe(int &x, int &y) {
  314. uint16_t a = 0x8000 + e.angle;
  315. x = e.x - GD.rsin(22 * 16, a);
  316. y = e.y + GD.rcos(22 * 16, a);
  317. }
  318. };
  319. class Missiles {
  320. int8_t ss[NUM_MISSILES];
  321. Stack stack;
  322. MissileObject m[NUM_MISSILES];
  323. public:
  324. void initialize() {
  325. stack.initialize(ss, sizeof(ss));
  326. }
  327. void launch(uint16_t angle, uint16_t vel) {
  328. int8_t i = stack.alloc();
  329. if (i >= 0) {
  330. m[i].initialize(angle, vel);
  331. }
  332. }
  333. void airlaunch(Element &e, uint16_t angle, uint16_t vel) {
  334. int8_t i = stack.alloc();
  335. if (i >= 0) {
  336. m[i].initialize(angle, 10);
  337. m[i].air(e);
  338. }
  339. }
  340. void draw() {
  341. for (int8_t i = stack.alive(); i >= 0; i = stack.next(i))
  342. m[i].draw();
  343. }
  344. byte collide(Element &e) {
  345. for (int8_t i = stack.alive(); i >= 0; i = stack.next(i))
  346. if (m[i].collide(e)) {
  347. stack.free(i);
  348. m[i].blowup();
  349. return 1;
  350. }
  351. return 0;
  352. }
  353. byte hitbase() {
  354. for (int8_t i = stack.alive(); i >= 0; i = stack.next(i))
  355. if (m[i].hitbase()) {
  356. stack.free(i);
  357. m[i].blowup();
  358. return 1;
  359. }
  360. return 0;
  361. }
  362. void update(int t) {
  363. for (int8_t i = stack.alive(); i >= 0; i = stack.next(i)) {
  364. // REPORT(i);
  365. if (m[i].update(t)) {
  366. stack.free(i);
  367. m[i].blowup();
  368. }
  369. }
  370. }
  371. };
  372. #define NUM_SPLATS 10
  373. #define RED 0
  374. #define YELLOW 1
  375. class Sparks {
  376. int x[NUM_SPLATS];
  377. int y[NUM_SPLATS];
  378. int8_t xv[NUM_SPLATS];
  379. int8_t yv[NUM_SPLATS];
  380. byte age[NUM_SPLATS];
  381. byte kind[NUM_SPLATS];
  382. int8_t ss[NUM_SPLATS];
  383. Stack stack;
  384. public:
  385. void initialize() {
  386. stack.initialize(ss, sizeof(ss));
  387. }
  388. void launch(byte n, byte _kind, int _x, int _y) {
  389. while (n--) {
  390. int8_t i = stack.alloc();
  391. if (i >= 0) {
  392. uint16_t angle = 0x5000 + GD.random(0x6000);
  393. byte v = 64 + (GD.random() & 63);
  394. xv[i] = -GD.rsin(v, angle);
  395. yv[i] = GD.rcos(v, angle);
  396. x[i] = _x - (xv[i] << 2);
  397. y[i] = _y - (yv[i] << 2);
  398. kind[i] = _kind;
  399. age[i] = 0;
  400. }
  401. }
  402. }
  403. void draw() {
  404. GD.Begin(LINES);
  405. for (int8_t i = stack.alive(); i >= 0; i = stack.next(i)) {
  406. byte size;
  407. if (kind[i] == YELLOW) {
  408. GD.ColorRGB(0xffe000);
  409. size = 60;
  410. } else {
  411. GD.ColorRGB(0xc00000);
  412. size = 100;
  413. }
  414. GD.LineWidth(GD.rsin(size, age[i] << 11)); //' sparks{
  415. GD.Vertex2f(x[i], y[i]);
  416. GD.Vertex2f(x[i] + xv[i], y[i] + yv[i]); //' }sparks
  417. }
  418. GD.Begin(BITMAPS);
  419. }
  420. void update() {
  421. for (int8_t i = stack.alive(); i >= 0; i = stack.next(i)) {
  422. x[i] += xv[i];
  423. y[i] += yv[i];
  424. yv[i] += 3;
  425. if (++age[i] == 16)
  426. stack.free(i);
  427. }
  428. }
  429. };
  430. #define NUM_REWARDS 3
  431. #define REWARDS_FONT INFOFONT_HANDLE
  432. class Rewards {
  433. int x[NUM_REWARDS];
  434. int y[NUM_REWARDS];
  435. const char *amount[NUM_REWARDS];
  436. byte age[NUM_REWARDS];
  437. int8_t ss[NUM_REWARDS];
  438. Stack stack;
  439. public:
  440. void initialize() {
  441. stack.initialize(ss, sizeof(ss));
  442. }
  443. void create(int _x, int _y, const char *_amount) {
  444. int8_t i = stack.alloc();
  445. if (i >= 0) {
  446. x[i] = _x;
  447. y[i] = _y;
  448. amount[i] = _amount;
  449. age[i] = 0;
  450. }
  451. }
  452. void draw() {
  453. GD.PointSize(24 * 16);
  454. for (int8_t i = stack.alive(); i >= 0; i = stack.next(i)) {
  455. GD.ColorA(255 - age[i] * 4);
  456. GD.ColorRGB(0x000000);
  457. GD.cmd_text(x[i] - 1, y[i] - 1, REWARDS_FONT, OPT_CENTER, amount[i]);
  458. GD.ColorRGB(0xffffff);
  459. GD.cmd_text(x[i], y[i], REWARDS_FONT, OPT_CENTER, amount[i]);
  460. }
  461. }
  462. void update() {
  463. for (int8_t i = stack.alive(); i >= 0; i = stack.next(i)) {
  464. y[i]--;
  465. if (++age[i] == 60)
  466. stack.free(i);
  467. }
  468. }
  469. };
  470. #define THRESH 20
  471. class BaseObject {
  472. public:
  473. RotatingElement turret;
  474. Element front;
  475. byte power;
  476. byte prev_touching;
  477. uint32_t cash;
  478. byte life;
  479. byte hurting;
  480. void initialize() {
  481. front.set(&DEFENSOR_FRONT_SHAPE);
  482. front.setxy(240, 272 - 30);
  483. turret.set(&DEFENSOR_TURRET_SHAPE);
  484. turret.angle = 0x7000;
  485. power = 0;
  486. cash = 0;
  487. life = 100;
  488. hurting = 0;
  489. }
  490. void draw_base() {
  491. // turret.angle += 200;
  492. // if (turret.angle > 0xb800)
  493. // turret.angle = 0x4800;
  494. turret.setxy_16ths(16*240 - GD.rsin(16*77, turret.angle),
  495. 16*265 + GD.rcos(16*77, turret.angle));
  496. if (hurting && (hurting < 16)) {
  497. GD.ColorA(255 - hurting * 16);
  498. GD.ColorRGB(0xff0000);
  499. GD.PointSize(50 * 16 + hurting * 99);
  500. GD.Begin(POINTS);
  501. GD.Vertex2ii(240, 270);
  502. GD.ColorA(255);
  503. GD.ColorRGB(0xffffff);
  504. GD.Begin(BITMAPS);
  505. }
  506. turret.draw(0);
  507. GD.ColorRGB(0x000000);
  508. front.draw(0);
  509. if (hurting)
  510. GD.ColorRGB(0xff0000);
  511. else
  512. GD.ColorRGB(0xffffff);
  513. front.draw(1);
  514. GD.ColorRGB(0xffffff);
  515. }
  516. void draw_status() {
  517. GD.SaveContext();
  518. GD.Begin(LINES);
  519. GD.ColorA(0x70);
  520. GD.ColorRGB(0x000000);
  521. GD.LineWidth(5 * 16);
  522. GD.Vertex2ii(10, 10);
  523. GD.Vertex2ii(10 + 460, 10);
  524. if (power > THRESH) {
  525. int x0 = (240 - power);
  526. int x1 = (240 + power);
  527. GD.ColorRGB(0xff6040);
  528. GD.Vertex2ii(x0, 10);
  529. GD.Vertex2ii(x1, 10);
  530. GD.ColorA(0xff);
  531. GD.ColorRGB(0xffd0a0);
  532. GD.LineWidth(2 * 16);
  533. GD.Vertex2ii(x0, 10);
  534. GD.Vertex2ii(x1, 10);
  535. }
  536. GD.RestoreContext();
  537. GD.ColorA(0xf0);
  538. GD.ColorRGB(0xd7f2fd);
  539. GD.Begin(BITMAPS);
  540. GD.cmd_text(3, 16, INFOFONT_HANDLE, 0, "$");
  541. GD.cmd_number(15, 16, INFOFONT_HANDLE, 0, cash);
  542. GD.cmd_number(30, 32, INFOFONT_HANDLE, OPT_RIGHTX, life);
  543. GD.cmd_text(30, 32, INFOFONT_HANDLE, 0, "/100");
  544. }
  545. void reward(uint16_t amt) {
  546. cash += amt;
  547. }
  548. void damage() {
  549. life = max(0, life - 9);
  550. hurting = 1;
  551. }
  552. byte update(Missiles &missiles) {
  553. byte touching = (GD.inputs.x != -32768);
  554. if (touching)
  555. power = min(230, power + 3);
  556. if (!touching && prev_touching && (power > THRESH)) {
  557. GD.play(HIHAT);
  558. missiles.launch(turret.angle, power);
  559. power = 0;
  560. }
  561. prev_touching = touching;
  562. if ((GD.inputs.track_tag & 0xff) == 1) //' track{
  563. turret.angle = GD.inputs.track_val; //' }track
  564. if (hurting) {
  565. if (++hurting == 30)
  566. hurting = 0;
  567. }
  568. if (missiles.hitbase())
  569. damage();
  570. return (life != 0);
  571. }
  572. };
  573. #define NUM_HELIS 5
  574. #define HELI_LEFT (-HELI_WIDTH / 2)
  575. #define HELI_RIGHT (480 + (HELI_WIDTH / 2))
  576. class HeliObject {
  577. Element e;
  578. int8_t vx, vy;
  579. int8_t state; // -1 means alive, 0..n is death anim
  580. public:
  581. void initialize() {
  582. e.set(&HELI_SHAPE);
  583. int x;
  584. if (PROB(1, 2)) {
  585. vx = 1 + GD.random(2);
  586. x = HELI_LEFT;
  587. } else {
  588. vx = -1 - GD.random(2);
  589. x = HELI_RIGHT;
  590. }
  591. e.setxy(x, (HELI_HEIGHT / 2) + GD.random(100));
  592. state = -1;
  593. vy = 0;
  594. }
  595. void draw(byte anim) {
  596. if (state == -1)
  597. e.draw(anim, vx > 0);
  598. else {
  599. byte aframes[] = {0x00, 0x01, 0x02, 0x81, 0x80, 0x03, 0x83};
  600. byte a = aframes[(state >> 1) % sizeof(aframes)];
  601. e.draw(a & 0x7f, a & 0x80, 2);
  602. }
  603. }
  604. byte update();
  605. };
  606. class Helis {
  607. int8_t ss[NUM_HELIS];
  608. Stack stack;
  609. HeliObject m[NUM_HELIS];
  610. public:
  611. void initialize() {
  612. stack.initialize(ss, sizeof(ss));
  613. }
  614. void launch() {
  615. int8_t i = stack.alloc();
  616. if (i >= 0) {
  617. m[i].initialize();
  618. }
  619. }
  620. void draw(int t) {
  621. for (int8_t i = stack.alive(); i >= 0; i = stack.next(i))
  622. m[i].draw(((i + t) >> 2) & 1);
  623. }
  624. void update(byte level) {
  625. for (int8_t i = stack.alive(); i >= 0; i = stack.next(i))
  626. if (m[i].update())
  627. stack.free(i);
  628. }
  629. };
  630. class GameObject {
  631. public:
  632. BaseObject base;
  633. Missiles missiles;
  634. Fires fires;
  635. Explosions explosions;
  636. Helis helis;
  637. Soldiers soldiers;
  638. Sparks sparks;
  639. Rewards rewards;
  640. byte level;
  641. int t;
  642. void load_level(byte n) {
  643. level = n;
  644. GD.Clear();
  645. GD.cmd_text(240, 110, 29, OPT_CENTER, "LEVEL");
  646. GD.cmd_number(240, 145, 31, OPT_CENTER, n + 1);
  647. GD.swap();
  648. GD.finish();
  649. char filename[] = "night#.gd2";
  650. filename[5] = '0' + (n % 5);
  651. GD.load(filename);
  652. if (n == 0)
  653. base.initialize();
  654. missiles.initialize();
  655. fires.initialize();
  656. explosions.initialize();
  657. helis.initialize();
  658. soldiers.initialize();
  659. sparks.initialize();
  660. rewards.initialize();
  661. t = 0;
  662. }
  663. void draw() {
  664. GD.Tag(1);
  665. draw_dxt1(BACKGROUND_COLOR_HANDLE, BACKGROUND_BITS_HANDLE);
  666. GD.TagMask(0);
  667. GD.Begin(BITMAPS);
  668. base.draw_base();
  669. helis.draw(t);
  670. soldiers.draw();
  671. fires.draw();
  672. missiles.draw();
  673. explosions.draw();
  674. sparks.draw();
  675. rewards.draw();
  676. base.draw_status();
  677. }
  678. byte update(byte playing) {
  679. byte alive = base.update(missiles);
  680. // if ((t % 30) == 29) missiles.launch(base.turret.angle, 120);
  681. uint16_t launch;
  682. launch = min(1024, 128 + level * 64);
  683. if (GD.random() < launch)
  684. helis.launch();
  685. helis.update(level);
  686. soldiers.update(t);
  687. fires.update(t);
  688. explosions.update(t);
  689. sparks.update();
  690. rewards.update();
  691. missiles.update(t);
  692. t++;
  693. byte leveltime = min(50, 10 + level * 5);
  694. if (playing && (t == (leveltime * 60))) {
  695. load_level(level + 1);
  696. }
  697. return alive;
  698. }
  699. };
  700. static GameObject game;
  701. void MissileObject::blowup() {
  702. game.explosions.create(e.x, e.y /* - 16 * 40 */, e.angle);
  703. }
  704. byte MissileObject::update(int t)
  705. {
  706. if (0 <= ts) {
  707. if ((t & 1) == 0) {
  708. th = (th + 1) & (MISSILE_TRAIL - 1);
  709. trail[th].x = e.x;
  710. trail[th].y = e.y;
  711. ts = min(MISSILE_TRAIL, ts + 1);
  712. }
  713. vy += 4;
  714. } else {
  715. vx = -GD.rsin(HOMING_SPEED, e.angle);
  716. vy = GD.rcos(HOMING_SPEED, e.angle);
  717. int16_t dy = (16 * 272) - e.y;
  718. int16_t dx = (16 * 240) - e.x;
  719. uint16_t seek = GD.atan2(dy, dx);
  720. int16_t steer = seek - e.angle;
  721. if (abs(steer) > HOMING_SLEW)
  722. e.angle -= dir * HOMING_SLEW;
  723. else
  724. dir = 0;
  725. if (((t & 7) == 0) && PROB(7, 8)) {
  726. int x, y;
  727. tailpipe(x, y);
  728. if ((0 <= y) && (0 <= x) && (x < (16 * 480)))
  729. game.fires.create(x >> 4, y >> 4);
  730. }
  731. }
  732. e.setxy_16ths(e.x + vx, e.y + vy);
  733. // Return true if way offscreen
  734. return (e.x < (16 * -200)) ||
  735. (e.x > (16 * 680)) ||
  736. (e.y > (16 * 262));
  737. }
  738. byte HeliObject::update()
  739. {
  740. e.x += vx;
  741. if (0 <= state) {
  742. e.y += vy;
  743. vy = min(vy + 1, 6);
  744. state++;
  745. }
  746. if (PROB(1, 300)) {
  747. game.missiles.airlaunch(e, (vx < 0) ? 0x4000 : 0xc000, vx);
  748. }
  749. if ((state == -1) && game.missiles.collide(e)) {
  750. game.rewards.create(e.x, e.y, "+$100");
  751. game.base.reward(100);
  752. game.sparks.launch(8, YELLOW, e.x << 4, e.y << 4);
  753. e.set(&COPTER_FALL_SHAPE);
  754. state = 0;
  755. vy = -4;
  756. GD.play(TUBA, 36);
  757. }
  758. if ((0 <= state) && e.y > 252) {
  759. game.sparks.launch(5, YELLOW, e.x << 4, e.y << 4);
  760. game.explosions.create(e.x << 4, e.y << 4, 0x0000);
  761. return 1;
  762. }
  763. return ((e.x < HELI_LEFT) || (e.x > HELI_RIGHT));
  764. }
  765. byte SoldierObject::update(int t)
  766. {
  767. if ((t & 1) == 0) {
  768. a = (a + 1) % sizeof(soldier_a);
  769. e.x += vx;
  770. }
  771. if (game.missiles.collide(e)) {
  772. game.fires.create(e.x, e.y);
  773. game.rewards.create(e.x, e.y, "+$50");
  774. game.base.reward(50);
  775. game.sparks.launch(6, RED, e.x << 4, e.y << 4);
  776. GD.play(TUBA, 108);
  777. return 1;
  778. }
  779. return ((e.x < SOLDIER_LEFT) || (e.x > SOLDIER_RIGHT));
  780. }
  781. static void blocktext(int x, int y, byte font, const char *s) //' context{
  782. {
  783. GD.SaveContext();
  784. GD.ColorRGB(0x000000);
  785. GD.cmd_text(x-1, y-1, font, 0, s);
  786. GD.cmd_text(x+1, y-1, font, 0, s);
  787. GD.cmd_text(x-1, y+1, font, 0, s);
  788. GD.cmd_text(x+1, y+1, font, 0, s);
  789. GD.RestoreContext();
  790. GD.cmd_text(x, y, font, 0, s);
  791. } //' }context
  792. static void draw_fade(byte fade)
  793. {
  794. GD.TagMask(0); //' fade{
  795. GD.ColorA(fade);
  796. GD.ColorRGB(0x000000);
  797. GD.Begin(RECTS);
  798. GD.Vertex2ii(0, 0);
  799. GD.Vertex2ii(480, 272); //' }fade
  800. }
  801. static void welcome()
  802. {
  803. // Streamer stream;
  804. GD.safeload("nightw.gd2");
  805. byte fade = 0;
  806. int t;
  807. const char song[] = "mesmeriz.ima";
  808. // stream.begin(song);
  809. byte playing = 1;
  810. do {
  811. GD.get_inputs();
  812. if (GD.inputs.tag == 100)
  813. fade = 1;
  814. if (fade != 0)
  815. fade = min(255, fade + 30);
  816. draw_dxt1(BACKGROUND_COLOR_HANDLE, BACKGROUND_BITS_HANDLE);
  817. GD.ColorRGB(0xd7f2fd);
  818. blocktext(25, 16, WELCOME_DISPLAYFONT_HANDLE, "NIGHTSTRIKE");
  819. byte flash = 128 + GD.rsin(127, t);
  820. t += 1000;
  821. GD.ColorA(flash);
  822. GD.Tag(100);
  823. blocktext(51, 114, WELCOME_DISPLAYFONT_HANDLE, "START");
  824. draw_fade(fade);
  825. GD.swap();
  826. GD.random();
  827. // if (!stream.feed()) {
  828. // playing = 0;
  829. // GD.sample(0, 0, 0, 0);
  830. // }
  831. } while (fade != 255);
  832. GD.sample(0, 0, 0, 0);
  833. // GD.play(MUTE); GD.play(UNMUTE);
  834. }
  835. void setup()
  836. {
  837. Serial.begin(1000000); // JCB
  838. GD.begin();
  839. GD.cmd_track(240, 271, 1, 1, 0x01); //' cmd_track{ }cmd_track
  840. }
  841. void loop()
  842. {
  843. static byte in_welcome = 1;
  844. if (in_welcome) {
  845. welcome();
  846. game.load_level(0);
  847. in_welcome = 0;
  848. } else {
  849. GD.get_inputs();
  850. uint32_t t0 = millis();
  851. byte alive = game.update(true);
  852. game.draw();
  853. if (!alive) {
  854. for (byte i = 255; i; i--) {
  855. game.draw();
  856. draw_fade(255 - i);
  857. GD.ColorA(255);
  858. GD.ColorRGB(0xffffff);
  859. blocktext(200, 136, INFOFONT_HANDLE, "GAME OVER");
  860. GD.swap();
  861. game.update(false);
  862. }
  863. in_welcome = 1;
  864. GD.Clear();
  865. GD.swap();
  866. return;
  867. }
  868. // uint32_t took = millis() - t0; // JCB
  869. // GD.cmd_number(479, 100, 27, 3 | OPT_RIGHTX, took); // JCB
  870. // caption(t - 428, "Graphics by MindChamber"); // JCB
  871. t++; // JCB
  872. GD.swap();
  873. }
  874. }